字符串StringJava编程中使用概率最高的变量,也许你觉得没有什么可讲的,随手拈来,然而字符串的处理却尤其需要我们的关注,因为大量的字符串实例的随意创建,给系统的效率带来了很大的问题。
比如下面我们来做一个测试,对比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节的讲解可知,StringStringBufferStringBuilder三者最大的区别是:
     String是字符串常量
     StringBuffer是字符串变量(线程安全)
     StringBuilder是字符串变量(非线程安全)
简要的说,String类型和StringBuffer类型的主要性能区别其实在于,String是不可变的对象,因此在每次对String类型进行改变的时候,其实都等同于生成了一个新的String对象,然后将指针指向新的String对象,所以经常改变内容的字符串最好不要用String,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后,JVMGC就会开始工作,那速度是一定会相当慢的。
而如果是使用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
String10w次循环时就溢出了,而StringBuffer100万次循环时间为246msStringBuilder的时间为137ms。显然选择优先级为:StringBuilder>StringBuffer>String。因此,对于这三个类的使用,我们需要按照以下情况去选择:
     如果你偶尔对简单的字符串常量进行拼接,那么可以使用String,它足够简单而且轻量级;
     如果你需要经常进行字符串的拼接、累加操作,请使用StringBufferStringBuilder
     如果是在单线程的环境中,建议使用StringBuilder,它要比StringBuffer快;如果是在多线程的环境中,建议使用StringBuffer,它是线程安全的;
因此,StringBuilder实际上是我们的首选,只有在多线程时才可以考虑使用StringBuffer,只有在字符串的拼接足够简单时才使用String