String类是不可变的,很多时候我们在使用String时已经将他看做了一种基本类型。也的确是如此,在整个java体系中只有两个重载的操作符:String类的+和+=。可见对String的重视程度。字符串操作是计算机程序设计中最常见的一种行为。在面试的时候也会屡屡问及String有关问题,对字符串类型的深入理解是非常有必要的。
从原理上来讲,String存放在堆上还是栈中是有区别的。通过字符串字面量来赋值的是在栈中存放,使用new关键字实例化的字符串在堆中存放。在堆中,我们通过new的方法创建对象,这种方法和我们创建其他对象没有任何区别。在栈中,我们将字符串存放进一个叫做“字符串常量池”的地方。字符串常量池是JVM为了节省空间使用的一种手法,当一个字符串通过字面量创建之后,java会先去常量池中查找一番,看看有没有这个字符串对象,如果没有,就创建一个这样的字符串对象,并把创建的这个放入常量池中。如果第二次我们同样适用字面量的方法创建了内容相同的字符串,java在常量池中查找到这个对象,就不会创建对象了,而是直接把这个对象的引用拿出来。因为String是不可变的,所以这种类似基本类型的操作方式没有任何问题。
我们通过一段代码来分析这两者的不同。
package StringEx;
/**
*
* @author QuinnNorris
*
* 不同方法构造的String的不同存放位置
*/
public class HeapOrStack {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String s1 = "str";
String s2 = new String("str");
// 实际上,Effective Java中告诉我们:尽量不要使用new来实例化String对象
// 首先我们用一个字符串来实例化这个字符串本身非常蠢,而且会增加不必要的创建对象的开销
String s3 = "str";
System.out.println(s1 == s2);
// false
System.out.println(s2.equals(s1));
// true
System.out.println(s1 == s3);
// true
String s4 = s2.intern();
//通过intern方法将s2的字符串内容添加进栈字符串常量池中
//将这个添加之后的栈中字符串常量池的值赋值给s4
System.out.println(s1 == s4);
//true
}
}
首先我们解释第一个输出为false的原因:如上所说,像s1的方式创建的字符串是存在栈的字符串的常量池之中的,而s2创建位置在堆上,==运算符是简单的看地址比较的,s1与s2地址不同,答案为false。与此不同的,equals方法的比较是根据内容的,s1和s2内容是相同的,都是”str“,所以为true。第三个是true就不需要解释了。需要注意的是下面的第四个输出。第四个输出前我们通过intern方法将s2的内容添加到了常量池中,并且会返回一个引用了常量池中内容相同的对象的引用,这个引用和s1的指向地址是完全一样的,所以输出了true。
使用+来连接字符串是非常方便的,但是在这个操作符背后,jvm是采用了什么手法来实现的呢?我们先来编写一段代码,然后还是采用老方法javap反编译来看。
package StringEx;
public class JavapString {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String s1 = "a";
String s2 = "b";
String s3 = s1+s2;
}
}
这就是我们把上面的代码进行反编译之后的结果。让人吃惊的,实际上重载的操作符+在编译器中是用StringBuilder来实现的。编译器自动的引入了StringBuilder类,即使我们在源代码中没有使用,编译器自作主张的使用了他,因为这样更加高效。同理+=也是采用这种方法来进行运算,这就是String的操作符重载的实际情况。
public boolean equals(Object anObject){}
//将此字符串与指定的对象比较。当且仅当该参数不为 null,
//并且是与此对象表示相同字符序列的 String 对象时,结果才为 true。
public boolean contentEquals(StringBuffer/CharSequence s){}
//将此字符串与指定的 StringBuffer或CharSequence 比较。
//当且仅当此 String 与指定 StringBuffer或CharSequence 表示相同的字符序列时,结果才为 true。
public boolean equalsIgnoreCase(String anotherString){}
//将此 String 与另一个 String 比较,不考虑大小写。
//如果两个字符串的长度相同,并且其中的相应字符都相等(忽略大小写),则认为这两个字符串是相等的。
public int compareTo(String anotherString){}
//按字典顺序比较两个字符串。该比较基于字符串中各个字符的 Unicode 值。
//按字典顺序将此 String 对象表示的字符序列与参数字符串所表示的字符序列进行比较。
//如果按字典顺序此 String 对象位于参数字符串之前,则比较结果为一个负整数。
//如果按字典顺序此 String 对象位于参数字符串之后,则比较结果为一个正整数。
//如果这两个字符串相等,则结果为 0;
//compareTo 只在方法 equals(Object) 返回 true 时才返回 0。
public int compareToIgnoreCase(String str){}
//按字典顺序比较两个字符串,不考虑大小写。
//此方法返回一个整数,其符号与使用规范化的字符串调用 compareTo 所得符号相同。
public boolean startsWith(String prefix,int toffset){}
//测试此字符串从指定索引开始的子字符串是否以指定前缀开始。
public boolean startsWith(String prefix){}
//测试此字符串是否以指定的前缀开始。
public int indexOf(String str,int fromIndex){}
//返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。
//如果不存在这样的 k 值,则返回 -1。
public String substring(int beginIndex,int endIndex){}
//返回一个新字符串,它是此字符串的一个子字符串。
//该子字符串从指定的 beginIndex 处开始,直到索引 endIndex - 1 处的字符。
//因此,该子字符串的长度为 endIndex-beginIndex。
public String concat(String str){}
//将指定字符串连接到此字符串的结尾。
//如果参数字符串的长度为 0,则返回此 String 对象。
//否则创建一个新的 String ,用来表示由此 String 对象表示的字符序列和参数字符串表示的字符序列连接而成的字符序列。
public String replace(char oldChar, char newChar){}
//返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
//如果 oldChar 在此 String 对象表示的字符序列中没有出现,则返回对此 String 对象的引用。
//否则,创建一个新的 String 对象,它所表示的字符序列除了所有的 oldChar 都被替换为 newChar。
public boolean matches(String regex){}
//告知此字符串是否匹配给定的正则表达式。
//调用此方法的 str.matches(regex) 形式与以下表达式产生的结果完全相同: Pattern.matches(regex, str)
public boolean contains(CharSequence s){}
//当且仅当此字符串包含指定的 char 值序列时,返回 true。
public String replaceFirst(String regex,String replacement){}
//使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
//注意,在替代字符串中使用反斜杠 (\) 和美元符号 ($) 与将其视为字面值替代字符串所得的结果可能不同。
public String replaceAll(String regex,String replacement){}
//使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
//注意,在替代字符串中使用反斜杠 (\) 和美元符号 ($) 与将其视为字面值替代字符串所得的结果可能不同。
public String[] split(String regex,int limit){}
//根据匹配给定的正则表达式来拆分此字符串。 limit 参数控制模式应用的次数,因此影响所得数组的长度。
public String toLowerCase(){}
//使用默认语言环境的规则将此 String 中的所有字符都转换为小写。
public String toUpperCase(){}
//使用默认语言环境的规则将此 String 中的所有字符都转换为大写。
public String trim(){}
//返回字符串的副本,忽略前导空白和尾部空白。
public static String format(String format,Object... args){}
//使用指定的格式字符串和参数返回一个格式化字符串。
public String intern(){}
//返回字符串对象的规范化表示形式。
//一个初始为空的字符串池,它由类 String 私有地维护。
在很多持有对象的容器里,我们都会存入String类型的值。不需要我们给出这个类型的比较方法,java有着自己对String的比较方法,也就是字典序。用字典序比较两个字符串,大写字母永远小于(前于)小写字母(可以查ascii表)。相同的,compareTo方法也是采用字典序比较String,但是比较奇怪的在于,compareTo用一个负数表示原字符串的值前于参数字符串(可能是直接将两个字符串做差的原因)。如果返回值为0,表示这两个字符串内容相同。如果我们不喜欢大小写带来的干扰,请使用compareToIgnoreCase方法来进行判断比较。
普通的replace方法是用一个String替代原字符串中所有的要替代的那个String,这个方法很简单。但是replaceFirst和replaceAll这两个方法却是使用正则表达式来做替换条件。正则表达式的应用非常的广泛,我们在这篇文章后面会详细介绍。这里不详细说明,但先留下个印象:这两个方法中要替换成为的String字符串是会被转义的。
其实这里想说的并不是toString方法,因为这个方法在String类中不存在任何作用。如果我们想要获得一个类的地址的话,通常的想法是使用this关键字,然后拼接成字符串,但是值得注意的是:我们不能在toString方法中试图这样写来返回一个对象的地址,因为this在字符串中会试图转变成String类型,这是他会调用本类中的toString方法,这样就形成了递归调用,最后栈溢出而报错。
在漫长的等待之后,java se5终于也推出了类似c语言风格的printf输出方法。这让我们对字符串的驾驭能力更上一层。
实际上,这两种方法在使用上是等价的。printf的使用方法不需要再多说了,这里放一张具体的符号对应表:
符号 | 含义 |
---|---|
%d | 十进制有符号整数 |
%u | 十进制无符号整数 |
%f | 浮点数 |
%s | 字符串 |
%c | 单个字符 |
%e | 指数形式的浮点数 |
%x, %X | 无符号以十六进制表示的整数 |
%o | 无符号以八进制表示的整数 |
%g | 把输出的值按照%e或者%f类型中输出长度较小的方式输出 |
\n | 换行 |
\t | Tab符 |
- | 结果左对齐,右边填空格 |
+ | 输出符号(正号或负号) |
空格 | 输出值为正时冠以空格,为负时冠以负号 |
# | 对c、s、d、u类无影响 |
java中有个专门用来格式化的类,这个类就是Formatter,这个类是在某个版本之后推出的,精心打造,功能非常强大,如果在这里展开讨论则太长,下面推荐一篇说的比较明白的文章:
Formatter类传送门:http://blog.csdn.net/quinnnorris/article/details/54614446
String类中也有一个静态方法——format,这个方法的参数和printf非常类似,但是不同的是,System.out.printf方法要直接输出在控制台上,这个方法会返回一个String类型的字符串,你可以在你需要的地方使用它。
其实在String.format内部,他也是创建一个Formatter对象,然后将你传入的参数传给Formatter。不过,我们与其自己做这些事情,不如使用便捷的String.format方法,何况这样的代码更清晰易读。
作为面试题“String、StringBuffer、StringBuilder三者之间的比较”已经烂大街了。那么我们就来具体的分析一下这三种类的特点。
只能这么说,因为在针对字符串进行操作时,String类的效率真的低的很难过。在上例中我们看见了+操作符在实际的JVM中是采用StringBuilder来操作的。而且String是字符串常量,常量就意味着他是不能被改变的:
str = str+ "a";
你所看见的等号左边的str是另外一个新的内存,在这个新的内存中放入了右边的结果值。这个时候str已经改成指向这个新的栈内存,而原来的str的内容还在栈的某个内存中保存着,并没有去改变它。所以,正是因为这种原因,String永不改变值,所以在字符串的运算中他是最慢的。另外值得一提的是,但是String是线程安全的。
在这三者之中StringBuilder是运行最快的,因为非线程安全不需要加锁所以速度是最快的。StringBuilder是字符串变量可以进行append等操作。StringBuilder和StringBuffer有共同的抽象父类AbstractStringBuilder。
StringBuffer的功能和Builder的功能是差不多的,但是通过使用同步锁,可以保证StringBuffer是线程安全的,因此速度略微慢于StringBuilder。但是它的速度还是快于String。StringBuilder和StringBuffer有共同的抽象父类AbstractStringBuilder。
很久之前,正则表达式就添加进入了Unix的工具集之中,比如sed和awk等等。在编程语言之中也为正则表达式提供了不同程度的支持,java和我们其他语言的支持可能有些差别,我们在之后会具体的分析。在java中,如果不使用正则表达式,字符串操作只能在String、StringBuffer、StringTokenizer(已渐渐不使用)中进行,效率和功能都不是很高。正则表达式的语法确实是一个难点,而且太久不使用非常容易忘记,但是它确实是一种简洁、动态的预言。正则表达式提供了一种完全通用的方式,能够解决各种字符串处理的相关问题:匹配、选择、编辑以及验证。
在这之前,我相信大家都知道反斜杠转义(\)的用法,这里不会介绍了。如果我们要描述一位数字,在正则表达式中\d代表匹配一位数字。同理的,在其他的语言中,\\表示“我要在正则表达式中插入一个普通的反斜杠,请不要给这个插入的反斜杠特殊意义”。而在java中,\\表示“我要插入一个正则表达式的反斜杠,所以这个反斜杠后面的字符有特殊的意义”。比如,你要是希望在java的正则表达式中插入一个正常的反斜杠,应该这样写“\\\\”。不过在java中换行和制表这种只需要单反斜杠“\n”“\t”。
正则表达式由一些普通字符和一些元字符组成。普通字符包括大小写的字母和数字,没有特殊的含义,普通字符表示在要匹配的字符串中会出现这个普通字符,比如“page”这段普通字符可以匹配“page1”、“pagesds”这类字符串,而不能匹配“pagE”。元字符则具有特殊的含义,当你的正则表达式中出现元字符时表示一些特殊的含义,我们在下面给出元字符的表格。
元字符 | 描述 |
---|---|
\ | 将下一个字符标记符、或一个向后引用、或一个八进制转义符。相当于多种编程语言中都有的“转义字符”的概念。 |
^ | 匹配输入字符串的开始位置。 |
$ | 匹配输入字符串的结束位置。 |
* | 匹配前面的子表达式零次或多次。例如,zo*能匹配“z”,也能匹配“zo”以及“zoo”。 |
+ | 匹配前面的子表达式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。 |
? | 匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“do”或“does”中的“do”。?等价于{0,1}。 |
{n} | n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。 |
{n,} | n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。 |
{n,m} | m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。请注意在逗号和两个数之间不能有空格。 |
. | 匹配除“\r\n”之外的任何单个字符。要匹配包括“\r\n”在内的任何字符,请使用像“[\s\S]”的模式。 |
(x) | 捕获组。可以在正则表达式中使用\i引用第i个捕获组 |
x|y | 匹配x或y。例如,“z|food”能匹配“z”或“food”。“[z|f]ood”则匹配“zood”或“food”或”|ood”。 |
[xyz] | 字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。 |
[^xyz] | 负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“plin”。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。 |
[^a-z] | 负值字符范围。匹配任何不在指定范围内的任意字符。“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。 |
\b | 匹配一个单词边界,单词和空格间的位置。“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。 |
\B | 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。 |
\cx | 匹配由x指明的控制字符。\cM匹配Ctrl-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。 |
\d | 匹配一个数字字符。等价于[0-9]。 |
\D | 匹配一个非数字字符。等价于[^0-9]。 |
\f | 匹配一个换页符。等价于\x0c和\cL。 |
\n | 匹配一个换行符。等价于\x0a和\cJ。 |
\r | 匹配一个回车符。等价于\x0d和\cM。 |
\s | 匹配任何不可见字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。 |
\S | 匹配任何可见字符。等价于[^ \f\n\r\t\v]。 |
\t | 匹配一个制表符。等价于\x09和\cI。 |
\v | 匹配一个垂直制表符。等价于\x0b和\cK。 |
\w | 匹配包括下划线的任何单词字符。类似但不等价于“[A-Za-z0-9_]”,这里的”单词”字符使用Unicode字符集。 |
\W | 匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。 |
\xn | 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。正则表达式中可以使用ASCII编码。 |
\num | 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。 |
以上是常用的元字符,还有很多复用的技巧,比如[a-zA-Z]可以匹配一个无论大小写的字符,[a-z&&[jhk]]可以匹配j、h或k。
在java中我们把?(一个或零个)、*(零个或多个)、+(一个或多个)、{n}、{n,m}、{n,}统称为正则表达式的量词。量词描述了一个模式吸收输入文本的方式,那么这些量词又有很多种状态,比如贪婪型、勉强型(懒惰型、非贪婪型)和java独有的占有型(完美型)。
贪婪型匹配:
默认情况下为贪婪型匹配。量词总是贪婪的,除非有其他的选项被设置。贪婪型表达式会为所有的模式发现尽可能多的匹配。如果贪婪型匹配模式已经发现了一个符合条件的字符串,他会继续向下探测可不可能继续匹配字符串。
贪婪型: X? X* X+
勉强型匹配:
在量词的尾部添加“?”表示量词匹配为勉强型匹配。勉强型匹配会匹配可以满足条件的尽可能少的字符数。
勉强型: X?? X*? X+?
占有型匹配(java可用):
占有型匹配仅仅在java中可以使用。在整个表达式的尾部添加“+”表示量词匹配为占有型匹配。它很类似贪婪型,但是不同的是,贪婪型在尽可能多的去匹配,但是发现不成功时会回退,去保证匹配一个可以匹配的字符串,而占有性不会回退。
占有型: X?+ X*+ X++
通过一个实例,我们来彻底分析一下三种情况的不同之处。
package Regex;
/**
*
* @author QuinnNorris
*
* 三种不同的匹配类型
*/
public class RegexEx {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String str = "atttzaccczapppz";
// 我们在下面调用String类的replaceAll方法,将正则表达式匹配到的内容替换成***
System.out.println(str.replaceAll("a.+z", "***"));
// ***
System.out.println(str.replaceAll("a.+?z", "***"));
// *********
System.out.println(str.replaceAll("a.++z", "***"));
// atttzaccczapppz
System.out.println(str.replaceAll("ac++z", "***"));
// atttz***apppz
}
}
在上面的例子中,我们调用了replaceAll方法来使用正则表达式。
第一个贪婪型a.+z是表示由一个a开始,后面跟着一个或多个字符由z结束。因为是贪婪型,所以它匹配最长的结果:从第一个a匹配到最后一个z,所以整个str字符串都被替换成了*。
第二个勉强型,我们去匹配最短的一个字符串,开始我们匹配了atttz,之后我们又匹配了acccz和apppz。所以整个字符串被替换成九个*(如果不想全部替换,要调用replaceFirst方法,匹配个数和正则表达式无关)。
第三种情况为什么什么都没有匹配到呢?我们想解决这个问题首先可以研究一下贪婪型匹配的方式:贪婪型是这样工作的:先匹配”a“,在字符串第一个匹配到了”a“,然后匹配“.+”,因为“.”是任意字符的意思,所以贪婪型一直匹配到整个字符串结束都符合这个“.+”。当匹配到字符串结束时,贪婪型发现没法匹配“z”了,这时匹配失败,为了防止失败,贪婪型匹配开始回退,一个字符一个字符的回退,一直回退到字母“z”。贪婪型回退了一个发现是“z”,因为成功匹配到“z”了所以匹配结束,贪婪型最后匹配的范围是从第一个”a”到最后一个“z”,最大范围。占有型的特点是:不会回退。因为开始的时候a作为起点匹配,之后我们匹配”.++“,也就是一个或多个字符,此时整个字符串一直匹配一直匹配匹配完整个表达式,因为占有型不会退回,所以匹配失败。整个字符串没有被替换的内容。
第四种情况,在我们知道了占有型的工作原理之后,第四种就很好理解了。因为第四种是“c++”,所以会匹配所有的c,到“z”的时候“c++”匹配结束,正好匹配上“z”。所以替换结果是atttz***apppz。
在String类中,我们能使用有关正则表达式的也仅有replaceAll和replaceFirst两种方法。为了能够使用功能更加强大的正则表达式。我们可以使用java.util.regex包中的Pattern类和Mather类。在下面的例子中可能会涉及很多新类,但是我们会一一介绍的。
package Regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* @author QuinnNorris
*
* Pattern类 Matcher类
*/
public class PatternM {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String regex = "..+?";
//两个两个截取字母
String input = "abcdefghijklmn";
//输入的字符串
Pattern p = Pattern.compile(regex);
//p是正则表达式的编译表示形式
Matcher m = p.matcher(input);
//m是从p中创建的匹配引擎,Matcher类中有很多操作的方式
while(m.find()){
System.out.println("Match \""+m.group() + "\" at positions " + m.start() + "-" + (m.end() - 1));
}
}
}
输出结果:
Match “ab” at positions 0-1
Match “cd” at positions 2-3
Match “ef” at positions 4-5
Match “gh” at positions 6-7
Match “ij” at positions 8-9
Match “kl” at positions 10-11
Match “mn” at positions 12-13
我们通过调用Pattern类中的静态方法Pattern.compile(String regex)可以将正则表达式以字符串的形式作为参数传入,那么这个返回的Pattern对象p就是该正则表达式的编译表示形式。Pattern中的matcher(String input)方法可以接受一个字符串参数,这个参数就是我们要处理的字符串。这个方法会返回一个Matcher类型的对象m,这个对象可以对这个匹配正则表达式的字符串进行操作。换句话说,p就是一个有了正则表达式的容器,在这容器中我们通过放入我们要处理的字符串,返回一个m对象,通过操作这个m对象,我们达到我们想要的结果。
public static Pattern compile(String regex){}
//将给定的正则表达式编译到模式中。
public static Pattern compile(String regex,int flags){}
//将给定的正则表达式编译到具有给定标志的模式中。
public String pattern(){}
//返回在其中编译过此模式的正则表达式。
public Matcher matcher(CharSequence input){}
//创建匹配给定输入与此模式的匹配器。
public int flags(){}
//返回此模式的匹配标志。
public String[] split(CharSequence input){}
//围绕此模式的匹配拆分给定输入序列。
public static String quote(String s){}
//返回指定 String 的字面值模式 String。
//此方法产生一个 String,可以将其用于创建与字符串 s 匹配的 Pattern,就好像它是字面值模式一样。
//输入序列中的元字符和转义序列不具有任何特殊意义。
Pattern类中的方法都是较为常用的,需要解释的是,静态方法quote是返回一个字符串的字面值,这个方法返回的内容在很多情况下不同,不是很常用。而flags方法是返回这个Pattern对象的模式,在Pattern对象中有很多int类型的属性表示这个对象的模式:
static int CANON_EQ
启用规范等价。
static int CASE_INSENSITIVE
启用不区分大小写的匹配。 常用
static int COMMENTS
模式中允许空白和注释。 常用
static int DOTALL
启用 dotall 模式。
static int LITERAL
启用模式的字面值解析。
static int MULTILINE
启用多行模式。 用^和$来匹配一行的开始和结束。 常用
static int UNICODE_CASE
启用 Unicode 感知的大小写折叠。
static int UNIX_LINES
启用 Unix 行模式。
public boolean find(){}
//尝试查找与该模式匹配的输入序列的下一个子序列。
//此方法从匹配器区域的开头开始,如果该方法的前一次调用成功了并且从那时开始匹配器没有被重置,
//则从以前匹配操作没有匹配的第一个字符开始。
//如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。
find方法是这个Matcher类中比较重要的方法,我们说过这个Matcher对象是有了字符串和正则表达式匹配之后结果处理的地方,find则是从头部开始查找匹配的子序列。我们通过find方法获得匹配的子序列,再去使用start获得这个子序列开始的索引,用end获得这个子序列结束的索引,用group获得这个匹配出来的子序列。
public String group(){}
//返回由以前匹配操作所匹配的输入子序列。
//对于具有输入序列 s 的匹配器 m,表达式 m.group() 和 s.substring(m.start(), m.end()) 是等效的。
//注意,某些模式(例如,a*)匹配空字符串。当模式成功匹配输入中的空字符串时,此方法将返回空字符串。
public String group(int group){}
//返回在以前匹配操作期间由给定组捕获的输入子序列。
//对于匹配器 m、输入序列 s 和组索引 g,表达式 m.group(g) 和 s.substring(m.start(g), m.end(g)) 是等效的。
//捕获组是从 1 开始从左到右的索引。组零表示整个模式,因此表达式 m.group(0) 等效于 m.group()。
//如果该匹配成功了,但指定组未能匹配输入序列的任何部分,则返回 null。
public int groupCount(){}
//返回此匹配器模式中的捕获组数。
//根据惯例,零组表示整个模式。它不包括在此计数中。
//任何小于等于此方法返回值的非负整数保证是此匹配器的有效组索引。
Matcher类中有个非常复杂的组(groups)的概念,我在这里尽量把它说明白。
组是用括号划分的正则表达式,可以跟剧组的编号来引用某个组。组号为0表示整个表达式,组号为1表示第一对括号括起的组:
A(B(C))D
在上面这个正则表达式中,第0组是ABCD,第一组是BC,第二组是C。groupCount方法返回该匹配器的模式中的分组数目,第0组不包括在内。而group方法返回前一次匹配操作的第0组(也就是返回全部匹配),group(int i)返回前一次匹配的第i组。这个方式可以让你通过给正则表达式打括号的方法更精确的获得想要的内容。
public int start(){}
//返回以前匹配的初始索引。
public int start(int group){}
//返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引。
//捕获组是从 1 开始从左到右的索引。组零表示整个模式,因此表达式 m.start(0) 等效于 m.start()。
public int end(){}
//返回最后匹配字符之后的偏移量。
public int end(int group){}
//返回在以前的匹配操作期间,由给定组所捕获子序列的最后字符之后的偏移量。
//捕获组是从 1 开始从左到右的索引。组零表示整个模式,因此表达式 m.end(0) 等效于 m.end()。
获得匹配的子序列的索引。
public Matcher reset(CharSequence input){}
//重置此具有新输入序列的匹配器。
通过reset方法,我们可以重置这个Matcher对象中的被处理的字符串,不用太在乎CharSequence类型,这个类型和String类型没有太大的区别。实际上CharSequence类型的值是char值的一个可读序列,也就是要被匹配的字符串。
public Matcher appendReplacement(StringBuffer sb,String replacement){}
//实现非终端添加和替换步骤。
此方法执行以下操作:
- 它从添加位置开始在输入序列读取字符,并将其添加到给定字符串缓冲区。
- 它将给定替换字符串添加到字符串缓冲区。
- 它将此匹配器的添加位置设置为最后匹配位置的索引加 1,即 end()。
替换字符串可能包含到以前匹配期间所捕获的子序列的引用:$g 每次出现时,都将被 group(g) 的计算结果替换。$ 之后的第一个数始终被视为组引用的一部分。如果后续的数可以形成合法组引用,则将被合并到 g 中。只有数字 ‘0’ 到 ‘9’ 被视为组引用的可能组件。例如,如果第二个组匹配字符串 “foo”,则传递替换字符串 “$2bar” 将导致 “foobar” 被添加到字符串缓冲区。可能将美元符号 ($) 作为替换字符串中的字面值(通过前面使用一个反斜线 ($))包括进来。 注意,在替换字符串中使用反斜线 (\) 和美元符号 ($) 可能导致与作为字面值替换字符串时所产生的结果不同。美元符号可视为到如上所述已捕获子序列的引用,反斜线可用于转义替换字符串中的字面值字符。
通过一个例子,我们来具体看一下这个方法的使用情况:
package Regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* @author QuinnNorris
*
* 动态的替换内容
*/
public class AppendRe {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Pattern p = Pattern.compile("cat");
Matcher m = p.matcher("one cat two cats in the yard");
StringBuffer sb = new StringBuffer();
while (m.find()) {
m.appendReplacement(sb, "dog");
}
m.appendTail(sb);
System.out.println(sb.toString());
}
}
输出结果:
one dog two dogs in the yard