今天带来的都是几条和String字符串操作有关的建议,细品、细品。
建议54:正确使用String、StringBuffer、StringBuilder
Java的CharSequence接口有三个实现类与字符串有关:String、StringBuffer、StringBuilder。 String是一个不可变量,也就是当他创建之后就会中内存中永久存在且不能修改,即使通过String自身的方法产生的也是一个新的字符串。
String str = "hello";
String str1 = str.substring(1);
str字符串通过substring方法重新生成了一个str1字符串其值为“ello”,那有没有可能不创建对象返回自己呢?str.substring(0)就不会产生新对象,JVM会从字符串池只能够返回str的引用。
StringBuffer和String一样中内存中保存的都是一个有序的字符序列,不同点是StringBuffer对象的值是可变的,例如:
StringBuffer sb = new StringBuffer("hello");
sb.append(" world");
上面的代码sb的值一直在变化,经过append后变为了“hello world”,那这个和String类通过“+”连接字符串有什么区别呢?
当然有区别,通过String加号连接的字符串,字符串变量指向了新的引用地址,而StringBuffer则不会变更其引用地址。
StringBuilder和StringBuffer基本相同,不同点是,StringBuffer是线程安全的,而StringBuilder是线程不安全的,所以可以看出String类的操作要远慢于StringBuffer和StringBuilder。
弄清楚了三者的原理,再来看看他们的使用场景: String类使用场景:在字符串不经常变化的时候使用,例如声明常量、少量变量等 StringBuffer类使用场景:频繁进行字符串的运算,如拼接、替换、删除等,并且运行在多线程环境中,例如XML解析、HTTP参数解析和封装等 * StringBuilder类使用场景:频繁进行字符串的运算,如拼接、替换、删除等,并且运行在单线程环境中,例如SQL语句的拼装、JSON封装等
建议56:自由选择字符串拼接方法
对于字符串等拼接一般有三种方法:加号、concat方法、StringBuffer或StringBuilder的append方法,那这三者具体有什么区别呢?来看看下面的例子:
str += "a"; // 加号连接
str = str.concat("a"); // concat方法连接
分别用这三种方法做字符串拼接,循环10W次后,检查其执行时间:
public class Proposal_56 {
public static void doWithAdd() {
String str = "a";
for (int i = 0; i < 100000; i++) {
str += "c";
}
}
public static void doWithConcat() {
String str = "a";
for (int i = 0; i < 100000; i++) {
str = str.concat("c");
}
}
public static void doWithStringBuilder() {
StringBuilder sb = new StringBuilder("a");
for (int i = 0; i < 100000; i++) {
sb.append("c");
}
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
doWithAdd();
long endTime = System.currentTimeMillis();
System.out.println("doWithAdd运行时间:" + (endTime - startTime) + "ms");
startTime = System.currentTimeMillis();
doWithConcat();
endTime = System.currentTimeMillis();
System.out.println("doWithConcat运行时间:" + (endTime - startTime) + "ms");
startTime = System.currentTimeMillis();
doWithStringBuilder();
endTime = System.currentTimeMillis();
System.out.println("doWithStringBuilder运行时间:" + (endTime - startTime) + "ms");
}
}
结果如下:
1.加号拼接字符串: 编译器对字符串使用加号做了优化,它会使用StringBuilder的append方法进行追加,其效果和下面的代码相同:
str = new StringBuilder(str).append("c").toString();
那按道理,不应该也和StringBuilder的效率一样吗,为什么用加号花了4372ms,而StringBuilder只花了2ms,原因很简答,一它每次循环都会创建一个StringBuilder对象,循环10W次就是10W个对象,二是每次执行完毕调用toString方法,转换成字符串也需要消耗时间。
2.concat方法拼接字符串: 先来看一下concat方法的源码:
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
整体看上去就是一个数组拷贝,虽然这内存中的处理是原子操作,速度非常快,但是注意看最后的return,每次concat方法都会创建一个新的String对象,这就是concat方法慢下来的原因,循环10W次,同样创建来10W个对象。
3.append方法拼接字符串: 同样也先看一下append的源码:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
整个append方法都中做字符数组处理,加长,然后数组拷贝,这些都是基本的数据操作,没有新建任何对象,所以速度也就快来。
这三种拼接字符串的方法,功能相同,性能各不相同,但并不表示我们一定要使用StringBuilder,这是因为“+”非常符合我们但编程习惯,便于阅读,在大多数情况用加号即可,只有在系统性能临界的时候才考虑concat或append方法。
建议57:推荐在复杂字符串操作中使用正则表达式
在日常字符串的操作中经常会用到诸如追加、合并、替换、倒叙、分割等操作,而且Java也为我们提供了append、replace、reverse、split等方法,但是更多的时候,我们还是需要借助正则表达式完成复杂的处理,下面这个例子,统计一篇文章中的英语单词数量,代码如下:
public class Proposal_57 {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
while (scan.hasNext()) {
String str = scan.nextLine();
int wordsCount = str.split(" ").length;
System.out.println(str + " 单词数:" + wordsCount);
}
}
}
返回结果如下:
我们发现除了第一条正确外其他都错了,第二条没有考虑用户输入都连续空格,第三条没有考虑连续都单词,第四条没有把连写符“'”考虑进去。那该如何处理呢?我们考虑使用正则表达式:
public class Proposal_57 {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
while (scan.hasNext()) {
String str = scan.nextLine();
// int wordsCount = str.split(" ").length;
Pattern pattern = Pattern.compile("bw+b");
Matcher matcher = pattern.matcher(str);
int wordsCount = 0;
while (matcher.find()) {
wordsCount++;
}
System.out.println(str + " 单词数:" + wordsCount);
}
}
}
改成上述代码之后,得到了下面都结果:
此时所有的结果都正确,b表示单词边界,w表示数字或者字符,这样匹配出来的都将会是有效都代码。正则表达式都字符串匹配可以应用在很多场合,比如常见的服务器日志分析等。