字符串操作是计算机程序设计中最常见的行为
String对象是不可变的。String类中每个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容。而最初的String对象则丝毫未动。
用于String的+与+=是Java中仅有的两个重载过的操作符,并不允许程序员重载任何操作符。
String的不可变性带来了一定的效率问题,比如String的“+”运算,每“+”一次都会生成一个新的String对象。Java编译器一般会自动优化,但不同情况下,优化的程度不够。
解释String不可变性对效率的影响时使用了javap工具,它是Java Class文件分解器,可以反编译。javap -c命令用来分解代码,从输出上看就是按函数将字节码拆分显示。
编译时编译器自动引入了java.lang.StringBuilder类,用它的append()方法完成字符串拼接,最后用它的toString()方法返回结果字符串。
StringBuilder是Java SE5引入的,在此之前使用StringBuffer,StringBuffer是线程安全的,因此开销也会大一些。
由String对象后面跟着一个“+”,再后面的对象不是String时,编译器会使后面的对象通过toString()自动类型转换成String,如果这发生在自定义的类的重写的toString()方法体内,就有可能发生无限递归,运行时抛出java.lang.StackOverflowError栈溢出异常。
原代码:
// InfiniteRecursion.java
public class InfiniteRecursion{
public String toString(){
//应该调用Object.toString()方法,所以this处应为super.toString()。
return " InfiniteRecursion address: " + this + "\n";
}
public static void main(String[] args){
List v = new ArrayList();
for(int i = 0; i < 10; i++)
v.add(new InfiniteRecursion());
System.out.println(v);
}
}
String类的大多数方法,当需要改变字符串的内容时,方法都返回一个新的String对象;如果内容没有改变方法仅仅返回指向原字符串对象的引用。
表格见书P511
从Java 1.5开始加入了c中printf()函数的功能,它可以用于 PrintStream类和PrintWriter类,因为System.out对象是一个PrintStream对象,故它拥有format()方法和printf()方法(两者功能一样,等同于c中的printf()方法用于格式化输出)。
ava5中新添加的格式化输出功能都由java.util.Formatter类处理。可以将Formatter看作一个翻译器,它将格式化字符串和数据翻译成想要的结果。创建Formatter对象时需要向构造函数传递一些信息,告诉Formatter将翻译的结果输出到哪里。
Formatter构造函数经过重载可以接受多种输出目的地,不过最常用的还是PrintStream、OutputStream和FIle。
常规类型、字符类型和数值类型的格式说明符语法如下:
%[argument_index$][flags][width][.precision]conversion
argument_index是一个十进制整数,用于表面参数在参数列表中的位置。
flags是修改输出格式的字符集。
width指定输出的最小宽度,必要时通过添加空格来维持最小宽度,默认是右对齐,可通过添加-改变对齐方向。
precision,应用于String表示打印出字符的最大长度,应用于浮点数表示小数点后要显示出的位数,超出的做舍入运算,不够的添加0,默认是6位。
conversion是表明应该如果格式化参数的字符。
注意 %b除了null永远为true
Java 1.5在String类中添加了静态方法format(),它接受与Formatter.format一样的参数,但返回一个String对象。其实在String.format()内部也使用Formatter完成相应的任务。
(参考来源:https://www.cnblogs.com/chenshengjava/p/8584293.html)
一个正则表达式是一个用于文本搜索的文本模式。换句话说,在文本中搜索出现的模式。
使用String对象的matches()方法可以判断该对象是否匹配方法参数表示的正则表达式。
String对象的replaceFirst()或者replaceAll()能将对象中成功匹配正则表达式的部分替代为参数中的字符串。
正则表达式量词的三种类型:
贪婪型:尽可能多的匹配。
勉强型:尽可能少的匹配。
占有型:Java独有
演示文本:John went for a walk, and John fell down, and John hurt his knee.
A、勉强型:John.*?
这个表达式匹配John 后跟0个或多个字符。 . 表示任意字符。* 表示0或多次。? 跟在 * 后面,表示 * 采用勉强型
量词只会匹配尽可能少的字符,即0个字符。上例中的表达式将会匹配单词John,在输入文本中出现3次。
B、贪婪型:John.*
量词会匹配尽可能多的字符。现在表达式会匹配第一个出现的John,以及在贪婪模式下 匹配剩余的所有字符。这样,只有一个匹配项。
C、占有型:John.*+hurt
*后跟+ 表示占有型量词
这个表达式在输入文本中没有匹配项,尽管文本中包括 John 和 hurt。
占有型会尽可能的多的匹配,但不考虑表达式剩余部分是否能匹配上。
.*+ 将会匹配第一个John之后的所有字符,这会导致表达式中剩余的 hurt 没有匹配项。
想匹配字符 a,b 或c,表达式如:[abc]
用一对方括号[] 表示字符分类。方括号本身并不是要匹配的一部分。
如想要匹配单词John,首字母可以为大写和小写J:[Jj]ohn
字符分类[Jj] 匹配J或j,剩余的 ohn 会准确匹配字符ohn.
^匹配行首,$ 匹配行尾。例如:^This is a single line$
量词 * 表示0次或多次。+ 表示1次或多次。? 表示0次或1次。
与操作是默认的,表达式 John ,意味着J 与 o与h与n。
或操作需要显示指定,用 | 表示。例如表达式 John|hurt 意味着John 或 hurt 。
符号图片:(来源:https://www.cnblogs.com/chenshengjava/p/8584293.html)
字符:
字符分类:
内置字符分类:
量词:
Pattern:
Pattern 是Java正则表达式API中的主要入口,无论何时,需要使用正则表达式,从Pattern 类开始。
检查一个正则表达式的模式是否匹配一段文本的最直接方法是调用静态方法Pattern.matches()
如果需要匹配多次出现,甚至输出不同的匹配文本,或者只是需要非默认设置。需要通过Pattern.compile() 方法创建一个Pattern对象。
一旦获得了Pattern对象,接着可以获得Matcher对象。
Matcher类有一个matches()方法,可以检查文本是否匹配模式。
Pattern 类的 split()方法,可以用正则表达式作为分隔符,把文本分割为String类型的数组。
示例代码:
String text = "A sep Text sep With sep Many sep Separators";
String patternString = "sep";
Pattern pattern = Pattern.compile(patternString);
String[] split = pattern.split(text);
System.out.println("split.length = " + split.length);
for(String element : split){
System.out.println("element = " + element);
}
output:
split.length = 5
element = A
element = Text
element = With
element = Many
element = Separators
Pattern 类的 pattern 返回用于创建Pattern 对象的正则表达式。
Pattern.Case_INSENSITIVE、Pattern.NULTILINE以及Pattern.COMMENTS比较常用。
Matcher :
Matcher 类用于匹配一段文本中多次出现一个正则表达式,Matcher 也适用于多文本中匹配同一个正则表达式。
Matcher 类的 matches() 方法用于在文本中匹配正则表达式,如果文本匹配正则表达式,matches() 方法返回true。否则返回false。
matches() 方法不能用于查找正则表达式多次出现。如果需要,请使用find(), start() 和 end() 方法。
如果正则表达式匹配文本开头而不匹配整个文本,lookingAt() 返回true,而matches() 返回false。
示例代码:
String text =
"This is the text to be searched " +
"for occurrences of the http:// pattern.";
String patternString = "This is the";
Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(text);
System.out.println("lookingAt = " + matcher.lookingAt());
System.out.println("matches = " + matcher.matches());
output:
lookingAt = true
matches = false
find() 方法用于在文本中查找出现的正则表达式,文本是创建Matcher时,通过 Pattern.matcher(text) 方法传入的。如果在文本中多次匹配,find() 方法返回第一个,之后每次调用 find() 都会返回下一个。find(int i)的重载版本,输入的整数参数是字符串中字符的位置。
start() 和 end() 返回每次匹配的字串在整个文本中的开始和结束位置。
reset() 方法会重置Matcher 内部的 匹配状态。当find() 方法开始匹配时,Matcher 内部会记录截至当前查找的距离。调用 reset() 会重新从文本开头查找。
也可以调用 reset(CharSequence) 方法. 这个方法重置Matcher,同时把一个新的字符串作为参数传入,用于代替创建 Matcher 的原始字符串。
使用group(int groupNo) 方法访问一个分组。一个正则表达式可以有多个分组。每个分组由一对括号标记。想要访问正则表达式中某分组匹配的文本,可以把分组编号传入 group(int groupNo)方法。
示例代码:
String text =
"John writes about this, and John Doe writes about that," +
" and John Wayne writes about everything."
;
String patternString1 = "(John) (.+?) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
System.out.println("found: " + matcher.group(1) +
" " + matcher.group(2));
}
output:
found: John writes
found: John Doe
found: John Wayne
groupCount()返回该匹配器模式中的分组数目。
String group(int i )返回前一次匹配模式操作指定组号的组,如果匹配成功,但是指定的组没有匹配输入字符串的任何部分,则会返回Null。
((John) (.+?)),分组1 是那个大分组。分组2 是包括John的分组,分组3 是包括 .+? 的分组。
replaceAll() 和 replaceFirst() 方法可以用于替换Matcher搜索字符串中的一部分。replaceAll() 方法替换全部匹配的正则表达式,replaceFirst() 只替换第一个匹配的。
appendReplacement() 和 appendTail() 方法用于替换输入文本中的字符串短语,同时把替换后的字符串附加到一个 StringBuffer 中。
appendReplacement() 会记录拷贝StringBuffer 中的内容,可以持续调用find(),直到没有匹配项。
通过调用 appendTail() 方法,可以把这部分内容拷贝到 StringBuffer 中.
示例代码:
String text =
"John writes about this, and John Doe writes about that," +
" and John Wayne writes about everything."
;
String patternString1 = "((John) (.+?)) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
StringBuffer stringBuffer = new StringBuffer();
while(matcher.find()){
matcher.appendReplacement(stringBuffer, "Joe Blocks ");
System.out.println(stringBuffer.toString());
}
matcher.appendTail(stringBuffer);
System.out.println(stringBuffer.toString());
output:
Joe Blocks
Joe Blocks about
this
, and Joe Blocks
Joe Blocks about
this
, and Joe Blocks writes about that, and Joe Blocks
Joe Blocks about
this
, and Joe Blocks writes about that, and Joe Blocks
writes about everything.
Java 1.5新增了java.util.Scanner类,它的构造器可以接受任何类型的输入对象,包括File对象、InputStream、String或者Readable对象。Readable是1.5新增的接口,表示具有read()方法的某种类。
用Scanner,所有的输入、分词以及翻译的操作 都隐藏在不同类型的next方法中。普通的next()方法返回下一个String。所有基本类型(除char之外)都有对应的next方法,包括BigDecimal和BigInteger。所有的next方法只有在找到一个完整的分词之后才会返回。还有相应的hasNext方法,用来判断下一个输入分词是否是所需的类型。
Scanner的操作不会抛出IOException,而是将它们吞掉,可以使用ioException()方法返回最近底层产生的IOException。
默认情况下Scanner使用空白字符对输入进行分词,可以使用正则表达式指定自己需要的定界符。
delimiter()方法用来返回该Scanner对象的定界符使用的正则表达式的Pattern对象。
next(Pattern)和hasNext(Pattern)两个函数的重载版本使用正则表达式匹配。需要注意的是,它仅仅针对下一个分词进行匹配,如果正则表达式中含有定界符,匹配永远不会成功。
在Java引入正则表达式(1.4开始)和Scanner类后(1.5开始),StringTokenizer处于废弃状态。