13.1java
String对象是不可变的。String类中每个看起来会修改String值的方法实际上都是建立了一个全新的String对象来包含修改后的字符串内容,而最初的String对象则丝毫未动。程序员
13.2正则表达式
用于String的+与+=是Java中仅有的两个重载过的操做符,并不容许程序员重载任何操做符。api
做者在解释String不可变性对效率的影响时使用了javap工具,它是Java Class文件分解器,能够反编译。javap -c命令用来分解代码,从输出上看就是按函数将字节码拆分显示。数组
经过查看字节码,知道了因为String的不可变性,为了提升运行效率例以下面的语句:安全
String s = "abc" + mango + "def" + 47;app
编译时编译器自动引入了java.lang.StringBuilder类,用它的append()方法完成字符串拼接,最后用它的toString()方法返回结果字符串。函数
做者而后演示了一个在循环中使用拼接的例子,若是让编译器自动优化,那么在每一个循环中就会建立一个StringBuilder对象;在这时咱们应该在代码中本身定义StringBuilder来提升效率。工具
StringBuilder是Java SE5引入的,在此以前使用StringBuffer,StringBuffer是线程安全的,所以开销也会大一些。测试
练习1分析reusing/SprinklerSystem.java的SprinklerSystem.toString()方法(方法中使用了+重载形式),若是明确的使用StringBuilder对象是否产生过多的StringBuilder对象。
首先,使用javap -c分解原SpringklerSystem代码,发现编译器自动为咱们建立了一个StringBuilder对象,而后我修改程序本身建立一个StringBuilder对象,而后将代码中的每一行加入到StringBuilder.append()方法(每一行中也使用了+重载):
StringBuilder sb = new StringBuilder(); sb.append("valve1 = " + valve1 + " "); sb.append("valve2 = " + valve2 + " "); sb.append("valve3 = " + valve3 + " "); sb.append("valve4 = " + valve4 + " "); sb.append("i = " + i + " " + "f = " + f + " "); sb.append("source = " + source); return sb.toString();
这种状况下使用javap -c查看,会建立了不少的StringBuilder,每一行一个。这种状况表面上使用了StringBuilder,并且代码看起来也很整洁,可是效率却没有如下形式高:
"valve1 = " + valve1 + " " + "valve2 = " + valve2 + " " + "valve3 = " + valve3 + " " + "valve4 = " + valve4 + " " + "i = " + i + " " + "f = " + f + " " + "source = " + source;
由于编译器自动替咱们优化,仅仅使用了一个StringBuilder。
13.4
String类的大多数方法,当须要改变字符串的内容时,方法都返回一个新的String对象;若是内容没有改变方法仅仅返回指向原字符串对象的引用。
13.5
从Java 1.5开始加入了c中printf()函数的功能,它能够用于 PrintStream类和PrintWriter类,由于System.out对象是一个PrintStream对象,故它拥有format()方法和printf()方法(二者功能同样,等同于c中的printf()方法用于格式化输出)。
Java5中新添加的格式化输出功能都由java.util.Formatter类处理。能够将Formatter看做一个翻译器,它将格式化字符串和数据翻译成想要的结果。建立Formatter对象时须要向构造函数传递一些信息,告诉Formatter将翻译的结果输出到哪里。
Formatter构造函数通过重载能够接受多种输出目的地,不过最经常使用的仍是PrintStream、OutputStream和FIle。
格式化说明符
常规类型、字符类型和数值类型的格式说明符语法以下:
%[argument_index$][flags][width][.precision]conversion
argument_index是一个十进制整数,用于表面参数在参数列表中的位置。第一个参数由“1$”引用,第二个参数由“2$”引用,以此类推;
flags是修改输出格式的字符集;
width指定输出的最小宽度,必要时经过添加空格来维持最小宽度,默认是右对齐,可经过添加-改变对齐方向。
precision,应用于String表示打印出字符的最大长度,应用于浮点数表示小数点后要显示出的位数,超出的作舍入运算,不够的添加0,默认是6位。
conversion是代表应该若是格式化参数的字符。
用于表示日期和时间类型的格式说明符语法以下:
%[argument_index$][flags][width]conversion
须要特殊注意的是 %b 转换成boolean类型,对全部引用只要不会Null都会输出true,通过编码测试
int i = 0; f.format("%b\n", i);
输出的依旧是true。
Java 1.5在String类中添加了静态方法format(),它接受与Formatter.format同样的参数,但返回一个String对象。其实在String.format()内部也使用Formatter完成相应的任务。
13.6
使用String对象的matches()方法能够判断该对象是否匹配方法参数表示的正则表达式;
String对象的replaceFirst()或者replaceAll()能将对象中成功匹配正则表达式的部分替代为参数中的字符串。
正则表达式量词的三种类型:
贪婪型:尽量多的匹配;
勉强型或非贪婪型:尽量少的匹配;例如(例子摘自http://www.jb51.net/article/31491.htm):
源字符串:aa
test1
bb
test2
cc
正则表达式一:
.*
匹配结果一:
test1
bb
test2
正则表达式二:
.*?
匹配结果二:
test1
(这里指的是一次匹配结果,因此没包括
test2
)
第一种正则表达式是贪婪型,第二种是勉强型。
占有型,它是Java正则表达式独有的,支配就是对整个字符串进行一次匹配,匹配以后返回,并不回溯。这个不大好理解,有一个例子摘自http://bbs.csdn.net/topics/390269371比较形象:
字符串为bbb,正则表达式为[b]*+,这是一个贪婪的匹配,直接返回bbb。可是若是是占有型的,正则为[b]*+b,返回结果是false,是空。
若是a*a,*是匹配优先的,也就是说先匹配,若是正则的后续部分不能再匹配,就回溯,在这个例子中,匹配字符串aaa的时候,首先a*匹配到最后一个,而后发现正则后面还有一个a无法匹配,就会将a*回溯到字符串的中间一个a,这时候正则中的最后一个a与字符串的最后一个a正好匹配,匹配结束。
若是正则是a*+a,*+是占有优先,也就是说*+前面的字符会尽量匹配,匹配了的就不会再回溯,不会让回去了,即所谓占有。若是字符串是aaa,那么这个例子中匹配过程就是a*+匹配了字符串的三个a,正则中的最后一个a不会再被匹配,由于a*+不会回溯。
接口java.lang.CharSequence从CharBuffer、String、StringBuilder和StringBuffer类中抽象出了字符序列的通常定义:
interface CharSequence { charAt(int i); length(); subSequence(int start, int end); toString(); }
多数正则表达式的操做都接受CharSequence类型的参数。
使用java.util.regex.Pattern类的静态函数complie()方法来编译正则表达式。它会根据String类型的正则表达式生成一个Pattern对象。接下来把想要检索的字符串传入Pattern对象的matcher()方法。matcher()方法会生成一个Matcher对象。matches()方法用来判断整个输入字符串是否匹配正则表达式模式,而lookingAt()用来判断该字符串(没必要是整个字符串)的始部分是否可以匹配模式。find()方法尝试查找与该模式匹配的输入序列的下一个子序列。group()方法匹配的输入子序列。find(int i)的重载版本,输入的整数参数是字符串中字符的位置,以其做为搜索的起点。注意若是使用
while(m.find(i)) {
}
这种形式,必定记得在循环里改变i的值或者加入break条件,不然每次m.find(i)都从固定的位置开始查找匹配,会陷入死循环。
类Pattern类提供了静态方法
static boolean matches(String regex, CharSequence input)
该方法用以检查regex是否匹配了整个input参数。编译后的Pattern对象还提供了split()方法,它从匹配了regex的地方分隔输入字符串,返回分隔后的子字符串String数组。
(?i)在Java的正则表达式中表示忽略大小写。
组是用括号划分的正则表达式,能够用组号来引用组,组号为0表示整个正则表达式,组号为1表示第一个括号包含的正则表达式,以此类推。例如:
A(B(C))D
group0表示ABCD,group1表示BC,group2表示C。
Matcher类的对象有一系列获取组相关信息的方法:
public int groupCount()返回该匹配器模式中的分组数目,第0组不包括在内。
public String group()返回前一次匹配模式操做(例如find())的第0组。
public String group(int i )返回前一次匹配模式操做指定组号的组,若是匹配成功,可是指定的组没有匹配输入字符串的任何部分,则会返回Null;
public in start(int group)返回在前一次匹配操做中寻找到的组的起始索引;
public int end(int group)返回在前一次匹配操做中寻找到的组的最后一个字符索引加一的值。
例子中的正则表达式使用了(?m),正常状况下将$与整个输入序列的末端相匹配,使用模式标记(?m)显示的告诉正则表达式注意输入序列中的换行符。
在匹配操做成功以后,start()返回先前匹配的起始位置的索引,end()返回匹配的最后字符的索引加1的值。匹配操做失败以后(或先与一个正在进行的匹配操做去尝试)调用start()或end()将会产生IllegalStateException。
find()能够在输入的任意位置定位正则表达式,而lookingAt()和matches()只有在正则表达式与输入的最开始处就开始匹配时才会成功。matches()只有在整个输入都匹配正则表达式时才会成功,而lookingAt()只要输入的第一部分匹配就会成功。
Pattern类的compile()方法有一个重载版本,它接受一个flag参数,以调整匹配的行为:
Pattern Pattern.compile(String regex, int flag);
Pattern.Case_INSENSITIVE、Pattern.NULTILINE以及Pattern.COMMENTS比较经常使用。另外,能够直接在正则表达式中使用其中大多数标记,只要将上表括号括起的字符插入到正则表达式中但愿起到做用的位置便可。
Pattern对象的
String[] split(CharSequence input)
String[] split(CharSequence input, int limit)
将输入字符串根据正则表达式断开成字符串对象数组。第二种形式的split()方法能够限制输入分割成字符串的数量。
Matcher类的对象有一个强大的appendReplacement(StringBuffer, String)方法,它执行如下操做:
(1)从添加位置开始在输入序列读取字符并将其添加到StringBuffer中,在匹配以前的那一字符中止;
(2)将给定的字符串添加到StringBuffer;
(3)将此匹配器的添加位置设置为最后匹配位置的索引加1,即end();
在执行一次或屡次appendReplacement()以后,调用appendTail(StringBuffer)方法将输入字符串剩余的部分复制到sbuf中。替换字符串还能够包含匹配的组引用,$g将被group(g)的计算结果替换。
Matcher对象的reset()方法能够从新设定字符序列。
练习1七、1八、19让咱们解析java源代码文件,使用正则表达式时总有想不全的状况,写出正确全面的正则表达式很是有挑战性,做者推荐了一个java代码解析器叫javacc。
13.7
Java 1.5新增了java.util.Scanner类,它的构造器能够接受任何类型的输入对象,包括File对象、InputStream、String或者Readable对象。Readable是1.5新增的接口,表示具备read()方法的某种类,它的实现类主要包括**Reader。用Scanner,全部的输入、粉刺以及翻译的操做 都隐藏在不一样类型的next方法中。普通的next()方法返回下一个String。全部基本类型(除char以外)都有对应的next方法,包括BigDecimal和BigInteger。全部的next方法只有在找到一个完整的分词以后才会返回。还有相应的hasNext方法,用来判断下一个输入分词是不是所需的类型。
Scanner的操做不会抛出IOException,而是将它们吞掉,可使用ioException()方法返回最近底层产生的IOException。
默认状况下Scanner使用空白字符对输入进行分词,可使用正则表达式指定本身须要的定界符:
useDelimiter(String regex);
delimiter()方法用来返回该Scanner对象的定界符使用的正则表达式的Pattern对象。
next(Pattern)和hasNext(Pattern)两个函数的重载版本使用正则表达式匹配。须要注意的是,它仅仅针对下一个分词进行匹配,若是正则表达式中含有定界符,匹配永远不会成功。
13.8
做者提到StringTokenizer类,在Java引入正则表达式(1.4开始)和Scanner类后(1.5开始),StringTokenizer处于废弃状态,官方api推荐使用Stirng.split()或java.util.regex包中的功能替代它。