如何选择String
字符串String是Java编程中使用概率最高的变量,也许你觉得没有什么可讲的,随手拈来,然而字符串的处理却尤其需要我们的关注,因为大量的字符串实例的随意创建,给系统的效率带来了很大的问题。
比如下面我们来做一个测试,对比String类和StringBuffer的执行效率:
● String执行10000次累加
long start = System.currentTimeMillis();
String str = "";
for (int i = 0; i < 10000; i++) {
str += "," + i;
}
long end = System.currentTimeMillis();
System.out.println(end - start);
执行的结果花费了702ms。
● 使用StringBuffer类来代替String类:
long start = System.currentTimeMillis();
StringBuffer str = new StringBuffer();
for (int i = 0; i < 10000; i++) {
str.append(",").append(i);
}
long end = System.currentTimeMillis();
System.out.println(end - start);
运行共花费了0ms。
通过对比发现StringBuffer几乎不花费时间。这是因为,String对象的每一次累加,都会先将累加的字符串创建一个实例对象然后再累加,等于是创建了10000个实例。而StringBuffer每次都是修改的原有实例对象,只是创建了1个实例。通过学习第2章我们已经知道,创建实例需要申请内存地址、写入数据的过程,大量的这种操作就会消耗大量的CPU计算资源。
也许你会说,StringBuffer这么高效率,那我们不再使用String类就可以了,然而实际情况是,它们在不同的情况下各有选择的优势。通过《高手真经 Java核心编程技术》第11.2节的讲解可知,String、StringBuffer与StringBuilder三者最大的区别是:
● String是字符串常量
● StringBuffer是字符串变量(线程安全)
● StringBuilder是字符串变量(非线程安全)
简要的说,String类型和StringBuffer类型的主要性能区别其实在于,String是不可变的对象,因此在每次对String类型进行改变的时候,其实都等同于生成了一个新的String对象,然后将指针指向新的String对象,所以经常改变内容的字符串最好不要用String,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后,JVM的GC就会开始工作,那速度是一定会相当慢的。
而如果是使用StringBuffer类则结果就不一样了,每次结果都会对StringBuffer对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用StringBuffer,特别是字符串对象经常改变的情况下。而在某些特别情况下,String对象的字符串拼接其实是被JVM解释成了StringBuffer对象的拼接,所以这些时候String对象的速度并不会比StringBuffer对象慢,而特别是以下的字符串对象生成中,String效率是远要比StringBuffer快的:
String str = “This is only a” + “ simple” + “ test”;
StringBuffer builder = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
你会很惊讶的发现,生成str对象的速度简直太快了,而这个时候StringBuffer居然速度上根本一点都不占优势。其实这是JVM的一个把戏,实际上:
String str = “This is only a” + “ simple” + “test”;
其实就是:
String str = “This is only a simple test”;
所以不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的String对象的话,速度就没那么快了,譬如:
String str2 = “This is only a”;
String str3 = “ simple”;
String str4 = “ test”;
String str1 = str2 +str3 + str4;
这时候JVM会规规矩矩的按照原来的方式去做。
为了测试这三个类的累加不同次数字符串时的效率,我们编写一个测试类,分别按次数累加字符串:
程序11-8 TestString.java
package test.String;
public class TestString {
static int count = 100;//循环次数
// 测试String
public static void testString() {
long start = System.nanoTime();
String str = "";
for (int i = 0; i < count; i++) {
str += "," + i;
}
long end = System.nanoTime();
System.out.println("String:" + (end - start));
}
// 测试StringBuffer
public static void testStringBuffer() {
long start = System.nanoTime();
StringBuffer str = new StringBuffer();
for (int i = 0; i < count; i++) {
str.append(",").append(i);
}
long end = System.nanoTime();
System.out.println("StringBuffer:" + (end - start));
}
// 测试StringBuilder
public static void testStringBuilder() {
long start = System.nanoTime();
StringBuilder str = new StringBuilder();
for (int i = 0; i < count; i++) {
str.append(",").append(i);
}
long end = System.nanoTime();
System.out.println("StringBuilder:" + (end - start));
}
public static void main(String[] args) {
TestString.testString();
TestString.testStringBuffer();
TestString.testStringBuilder();
}
}
运行该程序执行的测试时间:
表11-2 测试结果
毫微秒 |
String |
StringBuffer |
StringBuilder |
1次 |
69,562 |
46,934 |
8,101 |
10次 |
109,791 |
57,269 |
24,025 |
100次 |
431,619 |
172,089 |
128,228 |
1000次 |
8,274,236 |
876,368 |
270,985 |
1万次 |
704,425,841 |
2,673,524 |
1,388,166 |
10万次 |
溢出 |
20,926,961 |
11,669,361 |
100万次 |
溢出 |
246,871,041 |
137,586,760 |
String在10w次循环时就溢出了,而StringBuffer在100万次循环时间为246ms,StringBuilder的时间为137ms。显然选择优先级为:StringBuilder>StringBuffer>String。因此,对于这三个类的使用,我们需要按照以下情况去选择:
● 如果你偶尔对简单的字符串常量进行拼接,那么可以使用String,它足够简单而且轻量级;
● 如果你需要经常进行字符串的拼接、累加操作,请使用StringBuffer或StringBuilder;
● 如果是在单线程的环境中,建议使用StringBuilder,它要比StringBuffer快;如果是在多线程的环境中,建议使用StringBuffer,它是线程安全的;
因此,StringBuilder实际上是我们的首选,只有在多线程时才可以考虑使用StringBuffer,只有在字符串的拼接足够简单时才使用String。