第十三章 字符串 正则表达式

时间:2022-03-27 18:48:49

导语

  • 正则表达式是一种强大而灵活的文本处理工具。
  • 使用正则表达式,我们能以编程的方式,构造复杂的文本模式,并对输入的字符串进行搜索。一旦匹配这些模式的部分,就可以对它们进行处理。
  • 正则表达式提供了一种完全通用的方式,能够解决各种字符串处理相关的问题匹配、选择、编辑以及验证。
  • Java中的字符串操作集中于String、StringBuffer、StringTokenizer类。与正则表达式相比,它们只提供简单的功能。

1.基础

  • 一般来说,正则表达式就是以某种方式来描述字符串。“如果一个字符串中含有这些东西,那么它就是我正在找的东西”。
  • 正则表达式中,用\d表示一位数字。
  • 反斜杠:在其他语言中,\\表示“我想要在正则表达式中插入一个普通的反斜线,它没有任何意义”

    在java中,\\表示“插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义”。
    eg:表示一个整数,那么正则表达式应该是\\d,如果插入一个普通的反斜线,则应该是\\\\,
    不过换行和制表符之类的东西只需使用单反斜线:\n\t。
  • 要表示“一个或多个之前的表达式”,应该使用+。所以如果要表示可能有一个负号,后面跟着一位或多位数字。-?\\d+,\\d是之前的表达式
  • 表示或者没有这个字符的意思。
    public class IntegerMatch {
        public static void main(String[] args) {
            //String的matches方法内可以放一个正则表达式,他会跟前面的字符串进行匹配
            System.out.println("-1234".matches("-?\\d+"));//  可能有一个-号,后面跟着多个数字吗
            System.out.println("5678".matches("-?\\d+"));//  可能有一个-号,后面跟着多个数字吗
            System.out.println("5678".matches("-\\d+"));// 有一个-号,后面跟着多个数字吗 -数字数字数字 \\d数字
            System.out.println("+110".matches("-?\\d+"));
            System.out.println("+119".matches("(-|\\+)?\\d+"));//可能有一个+或-号,后面跟着多个数字
        }
    }
    /**
    true
    true
    false
    false
    true
     */
    
    |线表示或操作。括号有分组的功能
    (-|\\+)?
    这个正则表达式中,表示字符串的起始字符可能是一个-或+,或二者皆没有(因为后面跟着?修饰符,可能没有)
    因为+在正则表达式中有特殊的意义,所以使用\\将其转义,使之成为表达式中一个普通的字符串。
  • split()方法
    1. split()方法是一个正则表达式工具类。将字符串从正则表达式匹配的地方切开。

      public class Splitting {
      	public static String knights = 
      			"Then, when you have found the shrubbery, you must " +
      	"cut down the mightiest tree in the forest..."
      					+"with...a herring";
      	public static void split(String regex){
      		System.out.println(Arrays.toString(knights.split(regex)));
      	}
      	public static void main(String[] args) {
      		split(" ");//根据空格来分割字符串,匹配到空格就切开字符串,放入一个数组。
      		split("\\W+");// \\W代表非单词字符,它将标点都删除了,匹配到非单词字符,就切开字符串,放入一个数组
      		split("n\\W+");// 字母n后面跟个一个或多个非单词字符 ,这个正则表达式表示(n非单词字符非单词字符.......),匹配到就切开
      	}
      }
      /**
      [Then,, when, you, have, found, the, shrubbery,, you, must, cut, down, the, mightiest, tree, in, the, forest...with...a, herring]
      [Then, when, you, have, found, the, shrubbery, you, must, cut, down, the, mightiest, tree, in, the, forest, with, a, herring]
      [The, whe, you have found the shrubbery, you must cut dow, the mightiest tree i, the forest...with...a herring]
       */
    2. String.split()还有一个重载版本,允许限制字符串的分割的次数。
  • Replace()方法
    1. 只替换正则表达式第一个匹配的字符串,或替换所有匹配到的地方。
      public class Replacing {
      	static String s = Splitting.knights;
      	public static void main(String[] args) {
      		System.out.println(s);
      		System.out.println(s.replaceFirst("f\\w+", "汉字"));//\w小写 表示一个单词字符
      		System.out.println(s.replaceAll("shrubbery|three|herring", "汉字"));//将这几个字符全部替换掉
      	}
      }
      /**
      Then, when you have found the shrubbery, you must cut down the mightiest tree in the forest...with...a herring
      Then, when you have 汉字 the shrubbery, you must cut down the mightiest tree in the forest...with...a herring
      Then, when you have found the 汉字, you must cut down the mightiest tree in the forest...with...a 汉字
       */ 
      

2.创建正则表达式(具体看书吧)

  • 正则表达式的完整构造自列表,请参考java.util.regex包中的Pattern类。
    public class Rudolph {
    	public static void main(String[] args) {
    		for(String pattern : new String[]{"Rudolph",
    				"[rR]udolph","[rR][aeiou][a-z]ol.*",
    				"R.*"})
    			System.out.println("Rudolph".matches(pattern));
    	}
    }
    /**
    true
    true
    true
    true
     */ 
    

3.量词

  • 量词描述了一个模式吸收输入文本的方式:
    1. 贪婪型:贪婪表达式会为所有可能的模式发现尽可能多的匹配。假定我们的模式仅能匹配第一个可能的字符串组,如果表达式是贪婪的,那么它就会继续往下匹配。
    2. 勉强型:用问号来指定,这个量词匹配满足模式所需的最少字符数。因此也称作懒惰的、最少匹配的、非贪婪的、或不贪婪的。
    3. 占有型:这种类型的量词只有Java语言中才可用,并且更高级。当正则表达式被应用与字符串时,它会产生相当多的状态,以便在匹配失败时可以会说。而“占有的”量词并不保存这些中间状态,以防止他们回溯。他们常常用于防止正则表达式失控,因此可以使正则表达式执行起来更有效。

    第十三章 字符串 正则表达式

  • 表达式X通常必须用圆括号括起来,以便它能够按照我们期望的效果去执行。
    eg:abe+   匹配一个或多个abc序列,但这样是匹配ab,后面跟随一个或多个c,必须要(abc)+
  • 接口CharSequence从CharBuffer、String、StringBuffer、StringBuilder类中抽象出字符序列的一般化定义。
    多数正则表达式都接受CharSequence类型的参数。

4.Pattern和Matcher(构造功能强大的正则表达式对象)

  • 构造功能强大的正则表达式对象。
    public class TestRegularExpression {
        public static void main(String[] args) {
            if (args.length < 2) {
                System.out.println("Usage:\njava TestRegularExpression " + "characterSequence regularExpression+");
                System.exit(0);
            }
            System.out.println("Input: \"" + args[0] + "\"");
            for (String arg : args) {
                System.out.println("Regular expression: \"" + arg + "\"");
                //compile 编译要匹配的字符串 ,编译要匹配的正则表达式
                Pattern p = Pattern.compile(arg);
                //matcher 匹配compile内的字符串 输入matcher的字符串是否匹配上面的正则表达式 并生成一个matcher对象
                Matcher m = p.matcher(args[0]);
                while (m.find()) {//判断是否还有下一个匹配到的
                    //输出匹配字符串和开始结束位置
                    System.out.println("Match \"" + m.group() + "\" at position " + m.start() + "-" + (m.end() - 1));
                }
            }
        }
    }
    /**
    Match "abcabcabc" at position 0-8
    Regular expression: "abc"
    Match "abc" at position 0-2
    Match "abc" at position 3-5
    Match "abc" at position 6-8
    Regular expression: "abc+"
    Match "abc" at position 0-2
    Match "abc" at position 3-5
    Match "abc" at position 6-8
     */
    eclipse 怎样在运行时输入数据:右键->Run As -> Run Configuration ->在argument内输入参数,这样main方法内args[]就有值啦。
    第十三章 字符串 正则表达式
  • Pattern表示编译后的正则表达式,这样就不需要我们写正则表达式了(如 “\\x+”等),Pattern会为我们自动生成。

5.static boolean matches(String regex, CharSequence input)

  • 用以检查regex是否匹配整个Charsequence 类型的input参数。


6.Match.find()方法

  • Matcher.find()方法可用来在CharSequence中查找多个匹配项。
  • public class Finding {
    	public static void main(String[] args) {
    		Matcher m = Pattern.compile("\\w+")//传入compile方法一个正则表达式 (\\w+表示 多个单词字符)
    				//传入要对上面正则表达式进行匹配的字符串,根据单词进行匹配
    				.matcher("Evening is full of the linnet's wings");
    		/**
    		 * find()方法,用来在CharSequence中查找多个匹配正则表达式的“字符段” ,
    		 * 比如这个例子,就是查找多个单词字符的字符段,存在下一个匹配就一直while循环下去
    		 */
    		while(m.find())//像迭代器那样
    			//m.group()用来获取匹配到的字符段 这里是CharSequence中匹配到的单词字符
    			System.out.print(m.group() + " ");
    		System.out.println();
    		int i = 0;
    		while(m.find(i)){//i表示字符串中字符的位置,并以其作为索引的起点。
    			System.out.print(m.group() + " ");
    			i++;
    		}
    	}
    }
    /**
    Evening is full of the linnet s wings 
    Evening vening ening ning ing ng g is is s full full ull ll l of of f the the he e linnet linnet innet nnet net et t s s wings wings ings ngs gs s 
     */
    
  • 模式\\w+将字符串划分为单词。find()像迭代器那样遍历输入的字符串。m.group()用于配合其输出。
  • 第二个find()能够接受一个整数作为参数,该整数表示字符串中字符的位置,并以其作为搜索的起点。
    从输出可以看出,find(i)会不断的重新设定搜索的起始位置。

7.组(Groups)Mather.group()方法

  • 组是用括号划分的正则表达式,可以根据编号来引用某个组。组号为0表示整个表达式,组号1表示被第一个括号括起来的组,以此类推。
    eg:A(B(C))D 中有三个组,组0是ABCD,组1是BC,组2是C
  • public int groupCount()返回改匹配器的模式中的分组数目,第0组不包括在内。
  • public String group()返回前一次匹配操作(例如find())的第0组(整个匹配).
  • public String group(int i)返回前一次匹配操作期间指定的组号,如果匹配成功,但是指定的组没有匹配输入字符串的任何部分,则返回null。
  • public int start(int group)返回在前一次匹配操作中寻找到的组的起始索引,
  • public int end(int group)返回在前一次匹配操作中寻找到的组的最后一个字符索引加一的值。
    public class Groups {
    	public static void main(String[] args) {
    		Pattern p = Pattern.compile("(\\d+,)(\\d+)");
    		String s = "123,456-34,345";
    		Matcher m = p.matcher(s);
    		while (m.find()) {//匹配到的字符串循环,首先会招到第一个,然后匹配到的第二个,第三个·····
    			System.out.println("m.group():" + m.group()); // 打印匹配期间的大组(整个匹配)
    			System.out.println("m.group(1):" + m.group(1)); // 打印匹配期间的组1
    			System.out.println("m.group(2):" + m.group(2)); // 打印匹配期间的组2
    			System.out.println();
    		}
    		System.out.println("捕获个数:m.groupCount()" + m.groupCount());
    	}
    }
    /**
    m.group():123,456
    m.group(1):123,
    m.group(2):456
    
    m.group():34,345
    m.group(1):34,
    m.group(2):345
    
    捕获个数:m.groupCount()2
     */


8.start()和end() 【lookingAt()和matches() 和find()方法区别】

  • 匹配操作成功之后,start()方法返回先前匹配的起始位置的索引,end()方法返回所匹配的最后字符的索引加一的值。
    匹配操作失败后调用抛出IllegalStateException。
  1. find()方法可以在输入任意位置定位正则表达式。
  2. lookingAt()和matches()只有在正则表达式与输入的最开始处就开始匹配时才会成功。
  3. matches()方法只有在整个输入都匹配正则表达式时才会成功。
  4. lookingAt()只要输入的第一部分匹配就会成功。
  5. public class StartEnd2 {
    	public static void main(String[] args) {
    		String content = "123-456,789:102";
    		Pattern p = Pattern.compile("\\d+");
    		Matcher m = p.matcher(content);
    		while(m.find()){//查找多个匹配项,像迭代器的next方法
    		    System.out.println("find(): " + m.group() + " start at " + m.start() + " end at " + (m.end() - 1));
    		}
    		if(m.lookingAt()){//第一部分匹配就可成功
    		    System.out.println("lookingAt(): " + m.group() + " start at " + m.start() + " end at " + (m.end() - 1));
    		}
    		if(m.matches()){//全都匹配才能成功
    		    System.out.println("matches(): " + m.group() + " start at " + m.start() + " end at " + (m.end() - 1));
    		}
    	}
    }
    /**
    find(): 456 start at 1 end at 3
    find(): 789 start at 5 end at 7
    find(): 102 start at 9 end at 11
     */
    

9.Pattern标记

  • Pattern Pattern.complile(String regex, int flg),它接受一个标记参数,以调整匹配的行为。其中flg来自于Pattern中的常量。
  • 在这些标记中CASE_INSENSITIVE,MULTILINE COMMENTS非常重要
    public class ReFlags {
    	public static void main(String[] args) {
    		//表达式^和$分别匹配匹配一行的开始和结束
    		Pattern p = Pattern.compile("^java",Pattern.CASE_INSENSITIVE|Pattern.MULTILINE);
    		Matcher m = p.matcher("java has regex\njava has regex\n"+
    								"JAVA has pretty good regular expressions\n"+
    								"Regular expressions are in java");
    		while(m.find())
    			System.out.println(m.group());
    	}
    }
    /**
    java
    java
    JAVA
     */

10.split()方法

  • 将输入的字符串断开成字符串对象数组,断开边界由正则表达式确定。
    public class SplitDemo {
    	public static void main(String[] args) {
    		String input = "This!!unusual use!of exclamation!!points";
    		//断成对象数组,用Arrays转成 list并输出
    		System.out.println(Arrays.toString(Pattern.compile("!!").split(input)));
    		System.out.println(Arrays.toString(Pattern.compile("!!").split(input, 2)));
    	}
    }
    /**
    [This, unusual use!of exclamation, points]
    [This, unusual use!of exclamation, points]
     */

11.替换操作

  1. replaceFirst(String replancement)以参数字符串replancement替换掉第一个匹配成功的部分。
  2. replaceAll(String replancement)以参数字符串replancement替换掉所有匹配成功的部分。
  3. appendReplacement(StringBuffer sbuf, String replancement)执行渐进的替换。允许调用其他方法来生成或处理replacement参数,使你能够以编程的方式将目标分割成组,从而具备更强大的替换功能。
  4. appendTail(StringBuffer sbuf),在执行了一次或多次appendReplacement()之后调用,可以将输入字符串余下的部分复制到sbuf中
    public class TheReplacements {
    	public static void main(String[] args) {
    		Pattern p = Pattern.compile("a*b");
    		// 获取 matcher 对象
    		Matcher m = p.matcher("aabfooaabfooabfoobcasab");
    		StringBuffer sb = new StringBuffer();
    		while (m.find()) {
    			m.appendReplacement(sb, "-");//渐进式替换,将m匹配到的字符端替成“-”
    		}
    		m.appendTail(sb);//把余下的字符串复制到sb中
    		System.out.println(sb.toString());
    		String s = "123123,456";
    		System.out.println("s:" + s);
    		s = s.replaceFirst("23", "--");
    		System.out.println("replaceFirst:" + s);
    		s = s.replaceAll("2", "-");
    		System.out.println("replaceAll:" + s);
    	}
    }
    /**
    -foo-foo-foo-cas-
    s:123123,456
    replaceFirst:1--123,456
    replaceAll:1--1-3,456
    */
    

12.rese()方法

  • 将现有的Mather对象应用于一个新的字符序列。
    public class Resetting {
    	public static void main(String[] args) {
    		Matcher m = Pattern.compile("[frb][aiu][gx]")
    				.matcher("fix the rug with bags");
    		while(m.find())
    			System.out.print(m.group() + " ");
    		System.out.println();
    		m.reset("fix the rig with rags");//将matcher应用于第二个字符串
    		while(m.find())
    			System.out.print(m.group() + " ");
    	}
    }
    /**
    fix rug bag 
    fix rig rag 
    */
    

13.正则表达式与java I/O

  • 使用正则表达式在一个文件中进行搜索匹配操作。输出的是匹配的部分以及匹配部分在行中的位置。

。。。。。。。。。。。。。。。。。。。。这儿还有好多先空着


14.扫描输入

  • 从文件或标准输入读取数据非常痛苦。解决办法是读取其中一行文本,对其进行分词,然后使用Integer、Double等类的各种解析方法解析数据:
    public class SimpleRead {
    	public static BufferedReader input = new BufferedReader(new StringReader("Sir Robin of Camelot\n22 1.61803"));
    	public static void main(String[] args) {
    		try {
    			System.out.println("What is your name?");
    			String name = input.readLine();//读取一行 并转换为String
    			System.out.println(name);
    			System.out.println("How old are you? What is your favorite double ?");
    			System.out.println("(input: <age> <double>)");
    			String numbers = input.readLine();//读取第二行
    			System.out.println(numbers);
    			String[] numArray = numbers.split(" ");//遇空格符分开字符串 分词
    			int age = Integer.parseInt(numArray[0]);//解析成integer
    			double favorite = Double.parseDouble(numArray[1]);//解析成double
    			System.out.format("Hi %s.\n", name);//匹配格式输出
    			System.out.format("In 5 years you will be %d.\n", age + 5);
    			System.out.format("My favorite double is %f.", favorite/2);
    		} catch (IOException e) {
    			System.err.println("I/O exception");
    		}
    	}
    }
    /**
    What is your name?
    Sir Robin of Camelot
    How old are you? What is your favorite double ?
    (input: <age> <double>)
    22 1.61803
    Hi Sir Robin of Camelot.
    In 5 years you will be 27.
    My favorite double is 0.809015.
    */
  • readLine()读取一行输入转为String对象,如果每一行对应一个输入值可行,但如果两个输入值在同一行,必须分解这一行,获取到两个不等的输入值。
  • Scanner:
    public class BetterRead {
    	public static void main(String[] args) {
    		BufferedReader input = new BufferedReader(new StringReader("Sir Robin of Camelot\n22 1.61803"));
    		Scanner stdin  = new Scanner(SimpleRead.input);//放入Scanner中
    		System.out.println("What is your name?");
    		String name = stdin.nextLine();//读取一行
    		System.out.println(name);
    		System.out.println("How old are you? What is your favorite double?");
    		System.out.println("(input: <age> <double>)");
    		int age = stdin.nextInt();//读取下一行中的int型值
    		double favorite  = stdin.nextDouble();//读取下一行中的double值
    		System.out.println(age);
    		System.out.println(favorite);
    		System.out.format("Hi %s.\n", name);//匹配格式输出
    		System.out.format("In 5 years you will be %d.\n", age + 5);
    		System.out.format("My favorite double is %f.", favorite/2);
    	}
    }
    /**
    What is your name?
    Sir Robin of Camelot
    How old are you? What is your favorite double?
    (input: <age> <double>)
    22
    1.61803
    Hi Sir Robin of Camelot.
    In 5 years you will be 27.
    My favorite double is 0.809015.
     */
    1. scanner的构造器可以接受任何类型的输入对象,包括File InputStream String或者 Readable.
      它将所有的输入、分词以及翻译操作都隐藏在不同类型的next方法中。所有的next方法,只有在找到一个完整的分词之后才会返回。
    2. hasNext,用以判断下一个输入分词是否所需的类型。
  • Scanner定界符:默认情况下,Scanner根据空格字符对输入进行分词,但可以使用正则表达式制定自己所需的定界符。

    public class ScannerDelimiter {
    	public static void main(String[] args) {
    		Scanner scanner = new Scanner("12, 42, 54, 99, 21");
    		scanner.useDelimiter("\\s*,\\s*");//自己的正则表达式,设定定界符
    		while(scanner.hasNextInt())
    			System.out.println(scanner.nextInt());
    	}
    }

15.用正则表达式扫描

  • public class ThreatAnalyzer {
    	static String threatData = 
    			"58.27.82.161@02/10/2005\n"+
    					"204.45.234.40@02/10/2005\n"+
    					"58.27.82.161@02/10/2005\n"+
    					"58.27.82.161@02/11/2005\n"+
    					"58.27.82.161@02/12/2005\n"+
    					"58.27.82.161@02/12/2005\n";
    	public static void main(String[] args) {
    		//定义一个扫描类
    		Scanner scanner = new Scanner(threatData);
    		String pattern = "(\\d+[.]\\d+[.]\\d+[.]\\d+)@"
    				+ "(\\d{2}/\\d{2}/\\d{4})";
    		while(scanner.hasNext(pattern)){
    			scanner.next(pattern);//找到下一个匹配部分,依次为第二组 第三组
    			MatchResult mathc = scanner.match();//调用match获得匹配结果
    			String ip = mathc.group(1);//获得第一组的数据
    			String date = mathc.group(2);//获得第二组的数据
    			System.out.format("Thread on %s from %s\n", date,ip);//格式化输出
    		}
    	}
    }
    /**
     Thread on 02/10/2005 from 58.27.82.161
    Thread on 02/10/2005 from 204.45.234.40
    Thread on 02/10/2005 from 58.27.82.161
    Thread on 02/11/2005 from 58.27.82.161
    Thread on 02/12/2005 from 58.27.82.161
    Thread on 02/12/2005 from 58.27.82.161
     */
    
    注意:next()仅仅针对下一个输入分词进行匹配,如果你的正则表达式中含有定界符,那永远都不可能匹配成功。

16.StringTokenizer

  • 一个已经废弃的类,在以前使用他来对字符串进行分词。