Использование регулярных выражений в Java

Java

вводить

  • Регулярные выражения обычно используются для сопоставления строк, поиска строк и замены строк. Не стоит недооценивать их роль, гибкое использование регулярных выражений для обработки строк в работе и учебе может значительно повысить эффективность, а удовольствие от программирования так просто.
  • Может быть страшно сразу давать кучу правил сопоставления.Ниже будет объяснено использование регулярных выражений от поверхностного к глубокому.С практическими примерами.

Понимание сопоставления регулярных выражений на простом примере

  • код сначала
public class Demo1 {
    public static void main(String[] args) {
        //字符串abc匹配正则表达式"...", 其中"."表示一个字符
        //"..."表示三个字符
        System.out.println("abc".matches("..."));

        System.out.println("abcd".matches("..."));
    }
}
//输出结果
true
false
  • Stringкласс имеетmatches(String regex)Метод, возвращаемое логическое значение, используется, чтобы определить, соответствует ли эта строка заданному регулярному выражению.
  • В этом примере мы даем регулярное выражение как..., каждый, из которых.Представляет один символ, все регулярное выражение означает три символа, очевидно, при сопоставленииabcкогда результатtrue, Спичкиabcdкогда результатfalse.

Поддержка регулярных выражений в Java (есть соответствующие реализации для разных языков)

  • существуетjava.util.regexВ пакете есть два класса для регулярных выражений, один из нихMatcherкласс, другойPatternТипичное использование этих двух классов приведено в официальной документации Java, код выглядит следующим образом:
public class Demo2 {
    public static void main(String[] args) {
        //[a-z]表示a~z之间的任何一个字符, {3}表示3个字符, 意思是匹配一个长度为3, 并且每个字符属于a~z的字符串
        Pattern p = Pattern.compile("[a-z]{3}");
        Matcher m = p.matcher("abc");
        System.out.println(m.matches());
    }
}
//输出结果
true
  • Если вы хотите углубиться в принцип работы регулярных выражений, это будет связано с такими знаниями, как автоматы в принципе компиляции, и не будет здесь описываться, а для облегчения понимания будет описано более ярким языком здесь .
  • PatternМожно понимать как шаблон, строка должна соответствовать определенному шаблону, напримерDemo2, шаблон, который мы определяем,一个长度为3的字符串, 其中每个字符必须是a~z中的一个.
  • мы видим созданиеPatternобъект вызывается, когдаPatternв классеcompileметод, то есть объект шаблона получается после компиляции регулярного выражения, которое мы передаем. Этот скомпилированный объект шаблона значительно повысит эффективность использования регулярного выражения, и в качестве константы его можно безопасно использовать для нескольких потоков. одновременно.
  • MatcherЕго можно понимать как результат сопоставления определенной строки с шаблоном.После того, как строка соответствует определенному шаблону, может быть много результатов, которые будут объяснены в следующих примерах.
  • Наконец, когда мы звонимm.matches()возвращает результат, который соответствует полной строке с шаблоном
  • Приведенные выше три строки кода можно сократить до одной строки кода.
    System.out.println("abc".matches("[a-z]{3}"));
  • Но если регулярному выражению необходимо многократно сопоставляться, этот способ написания менее эффективен.

Предварительное понимание + * ?

  • Прежде чем вступить, первое, что нужно отметить, это то, что конкретное значение регулярных выражений не нужно запоминать, значение каждого символа указано в официальном документе Java.PatternПодробные определения есть в описании класса или на сайте, конечно лучше с ним ознакомиться.
public class Demo3 {
    /**
     * 为了省略每次写打印语句, 这里把输出语句封装起来
     * @param o
     */
    private static void p(Object o){
        System.out.println(o);
    }

    /**
     * .	Any character (may or may not match line terminators), 任意字符
     * X?	X, once or not at all       零个或一个
     * X*	X, zero or more times       零个或多个
     * X+	X, one or more times        一个或多个
     * X{n}	X, exactly n times          x出现n次
     * X{n,}	X, at least n times     x出现至少n次
     * X{n,m}	X, at least n but not more than m times 出现n~m次
     * @param args
     */
    public static void main(String[] args) {
        p("a".matches("."));
        p("aa".matches("aa"));
        p("aaaa".matches("a*"));
        p("aaaa".matches("a+"));
        p("".matches("a*"));
        p("a".matches("a?"));

        // \d	A digit: [0-9], 表示数字, 但是在java中对"\"这个符号需要使用\进行转义, 所以出现\\d
        p("2345".matches("\\d{2,5}"));
        // \\.用于匹配"."
        p("192.168.0.123".matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"));
        // [0-2]指必须是0~2中的一个数字
        p("192".matches("[0-2][0-9][0-9]"));
    }
}
//输出结果
//全为true

Сфера

  • []Используется для описания ряда символов, вот несколько примеров
public class Demo4 {
    private static void p(Object o){
        System.out.println(o);
    }

    public static void main(String[] args) {
        //[abc]指abc中的其中一个字母
        p("a".matches("[abc]"));
        //[^abc]指除了abc之外的字符
        p("1".matches("[^abc]"));
        //a~z或A~Z的字符, 以下三个均是或的写法
        p("A".matches("[a-zA-Z]"));
        p("A".matches("[a-z|A-Z]"));
        p("A".matches("[a-z[A-Z]]"));
        //[A-Z&&[REQ]]指A~Z中并且属于REQ其中之一的字符
        p("R".matches("[A-Z&&[REQ]]"));
    }
}
//输出结果
全部为true

Распознать \s \w \d \

  • Далее представлены регулярные выражения из цифр и букв, которые являются наиболее часто используемыми символами в программировании.

о\

  • Здесь мы сосредоточимся на самом непонятном\, В строке в Java, если вы хотите использовать специальные символы, вы должны добавить к ней префикс, добавив\Сбежать.
  • В качестве примера рассмотрим эту строку"老师大声说:"同学们,快交作业!"", Если мы не экранировали символ, конец открывающей двойной кавычки должен быть в说:"Здесь, но нам нужно использовать двойные кавычки в нашей строке, поэтому нам нужно использовать escape-символы
  • Строка после использования управляющих символов"老师大声说:\"同学们,快交作业!\"", чтобы можно было правильно определить наше первоначальное намерение.
  • Точно так же, если мы хотим использовать в строке\, также должен предшествовать\, представленный в строке как"\\"
  • Итак, как выразить в регулярном выражении соответствие\Что ж, ответ"\\\\".
  • Рассмотрим отдельно: Так как регулярное выражение выражает\Его также нужно экранировать, поэтому предыдущий\\Представляет escape-символ в регулярном выражении\, Назад\\значит в регулярном выражении\сами по себе, взятые вместе для представления в регулярном выражении\.
  • Если это кажется немного запутанным, см. код ниже.
public class Demo5 {
    private static void p(Object o){
        System.out.println(o);
    }

    public static void main(String[] args) {
        /**
         * \d	A digit: [0-9]          数字
         * \D	A non-digit: [^0-9]     非数字
         * \s	A whitespace character: [ \t\n\x0B\f\r] 空格
         * \S	A non-whitespace character: [^\s]       非空格
         * \w	A word character: [a-zA-Z_0-9]          数字字母和下划线
         * \W	A non-word character: [^\w]             非数字字母和下划线
         */
        // \\s{4}表示4个空白符
        p(" \n\r\t".matches("\\s{4}"));
        // \\S表示非空白符
        p("a".matches("\\S"));
        // \\w{3}表示数字字母和下划线
        p("a_8".matches("\\w{3}"));
        p("abc888&^%".matches("[a-z]{1,3}\\d+[%^&*]+"));
        // 匹配 \
        p("\\".matches("\\\\"));
    }
}
//输出结果
全部为true

Граничная обработка

  • ^В квадратных скобках это означает отрицание[^], если не в скобках, означает начало строки.
public class Demo6 {
    private static void p(Object o){
        System.out.println(o);
    }

    public static void main(String[] args) {
        /**
         * ^	The beginning of a line 一个字符串的开始
         * $	The end of a line       字符串的结束
         * \b	A word boundary         一个单词的边界, 可以是空格, 换行符等
         */
        p("hello sir".matches("^h.*"));
        p("hello sir".matches(".*r$"));
        p("hello sir".matches("^h[a-z]{1,3}o\\b.*"));
        p("hellosir".matches("^h[a-z]{1,3}o\\b.*"));
    }
}

Упражнение: сопоставьте пустые строки с адресами электронной почты

  • Когда я получаю статью, как мне определить, сколько в ней пустых строк? Регулярные выражения можно использовать для простого сопоставления, обратите внимание, что пустые строки могут включать пробелы, символы табуляции и т. д.
p(" \n".matches("^[\\s&&[^\n]]*\\n$"));
  • объяснять:^[\\s&&[^\n]]*является пробелом, но не символом новой строки,\\n$заканчиваться новой строкой
  • Ниже приведен соответствующий адрес электронной почты
p("liuyj24@126.com".matches("[\\w[.-]]+@[\\w[.-]]+\\.[\\w]+"));
  • объяснять:[\\w[.-]]+Подчеркнуть одним или несколькими буквенно-цифровыми символами.или-сочинение,@затем знак @, затем то же самое[\\w[.-]]+, тогда\\.совпадение., и, наконец, то же самое[\\w]+

Класс сопоставленияmatches(),find()иlookingAt()

  • matches()метод сопоставит всю строку с шаблоном.
  • find()Это должно начинаться с текущей позиции для соответствия, если строка передается первой,find(), то текущая позиция является началом строки. Конкретный анализ текущей позиции см. в следующем примере кода.
  • lookingAt()Метод будет соответствовать с начала строки.
public class Demo8 {
    private static void p(Object o){
        System.out.println(o);
    }

    public static void main(String[] args) {
        Pattern pattern = Pattern.compile("\\d{3,5}");
        String s = "123-34345-234-00";
        Matcher m = pattern.matcher(s);

        //先演示matches(), 与整个字符串匹配.
        p(m.matches());
        //结果为false, 显然要匹配3~5个数字会在-处匹配失败

        //然后演示find(), 先使用reset()方法把当前位置设置为字符串的开头
        m.reset();
        p(m.find());//true 匹配123成功
        p(m.find());//true 匹配34345成功
        p(m.find());//true 匹配234成功
        p(m.find());//false 匹配00失败

        //下面我们演示不在matches()使用reset(), 看看当前位置的变化
        m.reset();//先重置
        p(m.matches());//false 匹配整个字符串失败, 当前位置来到-
        p(m.find());// true 匹配34345成功
        p(m.find());// true 匹配234成功
        p(m.find());// false 匹配00始边
        p(m.find());// false 没有东西匹配, 失败

        //演示lookingAt(), 从头开始找
        p(m.lookingAt());//true 找到123, 成功
    }
}

в классе Matcherstart()иend()

  • Если матч успешенstart()используется для возврата позиции начала матча,end()Используется для возврата позиции после соответствующего конечного символа
public class Demo9 {
    private static void p(Object o){
        System.out.println(o);
    }

    public static void main(String[] args) {
        Pattern pattern = Pattern.compile("\\d{3,5}");
        String s = "123-34345-234-00";
        Matcher m = pattern.matcher(s);

        p(m.find());//true 匹配123成功
        p("start: " + m.start() + " - end:" + m.end());
        p(m.find());//true 匹配34345成功
        p("start: " + m.start() + " - end:" + m.end());
        p(m.find());//true 匹配234成功
        p("start: " + m.start() + " - end:" + m.end());
        p(m.find());//false 匹配00失败
        try {
            p("start: " + m.start() + " - end:" + m.end());
        }catch (Exception e){
            System.out.println("报错了...");
        }
        p(m.lookingAt());
        p("start: " + m.start() + " - end:" + m.end());
    }
}
//输出结果
true
start: 0 - end:3
true
start: 4 - end:9
true
start: 10 - end:13
false
报错了...
true
start: 0 - end:3

заменить строку

  • Если вы хотите заменить строку, вы должны сначала найти замененную строку, вот новое введениеMatcherметод в классеgroup(), который возвращает совпадающую строку.
  • Давайте посмотрим на пример, поместите строкуjavaПреобразовать в верхний регистр.
public class Demo10 {
    private static void p(Object o){
        System.out.println(o);
    }

    public static void main(String[] args) {
        Pattern p = Pattern.compile("java");
        Matcher m = p.matcher("java Java JAVA JAva I love Java and you");
        p(m.replaceAll("JAVA"));//replaceAll()方法会替换所有匹配到的字符串
    }
}
//输出结果
JAVA Java JAVA JAva I love Java and you

Обновление: поиск и замена строк без учета регистра

  • Чтобы не учитывать регистр при сопоставлении, нам нужно указать нечувствительность к регистру при создании шаблона шаблона.
public static void main(String[] args) {
    Pattern p = Pattern.compile("java", Pattern.CASE_INSENSITIVE);//指定为大小写不敏感的
    Matcher m = p.matcher("java Java JAVA JAva I love Java and you");
    p(m.replaceAll("JAVA"));
}
//输出结果
JAVA JAVA JAVA JAVA I love JAVA and you

Повторное обновление: без учета регистра, заменить указанную найденную строку

  • Эта демонстрация преобразует найденную строку с нечетным номером в верхний регистр, а строку с четным номером — в нижний.
  • будет представлен здесьMatcherМощный метод в классеappendReplacement(StringBuffer sb, String replacement), он должен передать StringBuffer для конкатенации строк.
public static void main(String[] args) {
    Pattern p = Pattern.compile("java", Pattern.CASE_INSENSITIVE);
    Matcher m = p.matcher("java Java JAVA JAva I love Java and you ?");
    StringBuffer sb = new StringBuffer();
    int index = 1;
    while(m.find()){
        //m.appendReplacement(sb, (index++ & 1) == 0 ? "java" : "JAVA"); 较为简洁的写法
        if((index & 1) == 0){//偶数
            m.appendReplacement(sb, "java");
        }else{
            m.appendReplacement(sb, "JAVA");
        }
        index++;
    }
    m.appendTail(sb);//把剩余的字符串加入
    p(sb);
}
//输出结果
JAVA java JAVA java I love JAVA and you ?

группировка

  • Давайте начнем с проблемы, посмотрите на следующий код
public static void main(String[] args) {
    Pattern p = Pattern.compile("\\d{3,5}[a-z]{2}");
    String s = "123aa-5423zx-642oi-00";
    Matcher m = p.matcher(s);
    while(m.find()){
        p(m.group());
    }
}
//输出结果
123aa
5423zx
642oi
  • где регулярное выражение"\\d{3,5}[a-z]{2}"Представляет от 3 до 5 чисел, за которыми следуют две буквы, а затем выводит каждую совпадающую строку.
  • Как это сделать, если вы хотите напечатать число в каждой совпадающей строке.
  • Прежде всего, вы можете подумать о сопоставлении совпадающих строк, но это слишком хлопотно, механизм группировки может помочь нам сгруппировать в регулярных выражениях.
  • Указываем использование () для группировки, здесь мы делим буквы и цифры на группы"(\\d{3,5})([a-z]{2})"
  • затем звонюm.group(int group)Номер группы можно передать в метод
  • Обратите внимание, что номер группы начинается с 0, а группа 0 представляет все регулярное выражение. После 0 каждая открывающая скобка соответствует группе слева направо в регулярном выражении. В этом выражении первая группа является числом, первые 2 группы - буквы.
public static void main(String[] args) {
    Pattern p = Pattern.compile("(\\d{3,5})([a-z]{2})");//正则表达式为3~5个数字跟上两个字母
    String s = "123aa-5423zx-642oi-00";
    Matcher m = p.matcher(s);
    while(m.find()){
        p(m.group(1));
    }
}
//输出结果
123
5423
642

Практика 1. Сканирование адресов электронной почты на веб-страницах (сканеры)

  • Предположим, у нас есть несколько высококачественных ресурсов, и мы планируем поделиться ими с пользователями сети, поэтому мы идем в Tieba и отправляем сообщение с электронными письмами для отправки ресурсов.Неожиданно пользователи сети настолько воодушевлены, что оставили почти сотню электронных писем. Но копировать и отправлять их по одному слишком утомительно, рассмотрим программную реализацию.
  • Я не буду расширять здесь часть отправки электронных писем и сосредоточусь на использовании регулярных выражений, которые научились перехватывать все адреса электронной почты с веб-страниц.
  • Сначала получите html-код сообщенияПросто найди его, щелкни, чтобы прыгнуть, Щелкните правой кнопкой мыши в браузере, чтобы сохранить html-файл
  • Далее посмотрите на код:
public class Demo12 {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader("C:\\emailTest.html"));
            String line = "";
            while((line = br.readLine()) != null){//读取文件的每一行
                parse(line);//解析其中的email地址
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(br != null){
                try {
                    br.close();
                    br = null;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void parse(String line){
        Pattern p = Pattern.compile("[\\w[.-]]+@[\\w[.-]]+\\.[\\w]+");
        Matcher m = p.matcher(line);
        while(m.find()){
            System.out.println(m.group());
        }
    }
}
//输出结果
2819531636@qq.com
2819531636@qq.com
2405059759@qq.com
2405059759@qq.com
1013376804@qq.com
...

Практика 2: апплет статистики кода

  • Последний практический случай: подсчитайте, сколько строк кода, сколько строк комментариев и сколько пустых строк в проекте.Вы могли бы также сделать статистику по проектам, которые вы сделали, и обнаружить, что вы написали тысячи строк кода. код по незнанию.народ...
  • Я выбрал проект на гитхабе, это небольшой проект, написанный на чистой java, что удобно для статистики.Нажмите, чтобы прыгнуть
  • Ниже приведен конкретный код.Помимо использования регулярных выражений для оценки пустых строк, API класса String используется для оценки строк кода и строк комментариев.
public class Demo13 {
    private static long codeLines = 0;
    private static long commentLines = 0;
    private static long whiteLines = 0;
    private static String filePath = "C:\\TankOnline";

    public static void main(String[] args) {
        process(filePath);
        System.out.println("codeLines : " + codeLines);
        System.out.println("commentLines : " + commentLines);
        System.out.println("whiteLines : " + whiteLines);
    }

    /**
     * 递归查找文件
     * @param pathStr
     */
    public static void process(String pathStr){
        File file = new File(pathStr);
        if(file.isDirectory()){//是文件夹则递归查找
            File[] fileList = file.listFiles();
            for(File f : fileList){
                String fPath = f.getAbsolutePath();
                process(fPath);
            }
        }else if(file.isFile()){//是文件则判断是否是.java文件
            if(file.getName().matches(".*\\.java$")){
                parse(file);
            }
        }
    }

    private static void parse(File file) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(file));
            String line = "";
            while((line = br.readLine()) != null){
                line = line.trim();//清空每行首尾的空格
                if(line.matches("^[\\s&&[^\\n]]*$")){//注意不是以\n结尾, 因为在br.readLine()会去掉\n
                    whiteLines++;
                }else if(line.startsWith("/*") || line.startsWith("*") || line.endsWith("*/")){
                    commentLines++;
                }else if(line.startsWith("//") || line.contains("//")){
                    commentLines++;
                }else{
                    if(line.startsWith("import") || line.startsWith("package")){//导包不算
                        continue;
                    }
                    codeLines++;
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(null != br){
                try {
                    br.close();
                    br = null;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
//输出结果
codeLines : 1139
commentLines : 124
whiteLines : 172

Жадный режим и нежадный режим

  • После двух реальных сражений я считаю, что все освоили базовое использование регулярных выражений.Следующее представляет жадный режим и нежадный режим.Глядя на официальный API, мы обнаруживаем, чтоPatternКласс имеет следующие определения:
Greedy quantifiers 贪婪模式
X?	X, once or not at all
X*	X, zero or more times
X+	X, one or more times
X{n}	X, exactly n times
X{n,}	X, at least n times
X{n,m}	X, at least n but not more than m times
 
Reluctant quantifiers 非贪婪模式(勉强的, 不情愿的)
X??	X, once or not at all
X*?	X, zero or more times
X+?	X, one or more times
X{n}?	X, exactly n times
X{n,}?	X, at least n times
X{n,m}?	X, at least n but not more than m times
 
Possessive quantifiers  独占模式
X?+	X, once or not at all
X*+	X, zero or more times
X++	X, one or more times
X{n}+	X, exactly n times
X{n,}+	X, at least n times
X{n,m}+	X, at least n but not more than m times
  • Эти три режима выражают одно и то же значение. В предыдущем объяснении мы все использовали жадный режим. Так в чем же разница между двумя другими режимами? Объясняется следующим примером кода.
public static void main(String[] args) {
    Pattern p = Pattern.compile(".{3,10}[0-9]");
    String s = "aaaa5bbbb6";//10个字符
    Matcher m = p.matcher(s);
    if(m.find()){
        System.out.println(m.start() + " - " + m.end());
    }else {
        System.out.println("not match!");
    }
}
//输出结果
0 - 10
  • Регулярное выражение означает 3~10 символов плюс число.При сопоставлении в жадном режиме система сначала проглотит 10 символов, затем проверит, является ли последний символ числом, и обнаружит, что символов больше нет, поэтому выплюнет символ, снова сопоставить число, успешно сопоставить, получить0-10.
  • Ниже демо нежадного режима (неохотно, неохотно)
public static void main(String[] args) {
    Pattern p = Pattern.compile(".{3,10}?[0-9]");//添加了一个?
    String s = "aaaa5bbbb6";
    Matcher m = p.matcher(s);
    if(m.find()){
        System.out.println(m.start() + " - " + m.end());
    }else {
        System.out.println("not match!");
    }
}
//输出结果
0 - 5
  • В нежадном режиме сначала проглатываются только 3 (минимум 3), а затем судят, является ли следующий числом, и по результату нет, после проглатывания символа продолжают судить, является ли последний числом. число, результат есть, вывод0-5
  • Наконец, демонстрируется эксклюзивный режим, который обычно делается только в погоне за эффективностью и используется реже.
public static void main(String[] args) {
    Pattern p = Pattern.compile(".{3,10}+[0-9]");//多了个+
    String s = "aaaa5bbbb6";
    Matcher m = p.matcher(s);
    if(m.find()){
        System.out.println(m.start() + " - " + m.end());
    }else {
        System.out.println("not match!");
    }
}
//输出结果
not match!
  • Эксклюзивный режим будет проглатывать 10 символов за раз, а затем определять, является ли последний числом, и не будет продолжать проглатывать или выдавать символ независимо от того, было ли совпадение успешным или нет.

Заканчивать

  • Пусть регулярные выражения принесут вам более приятный опыт программирования.