如何让你的程序运行的更快(1)---String VS StringBuffer

简介:

最开始给这篇文章起名的时候准备叫“如何让你的程序运行的更快(1)之String StringBuffer谁与争锋!”,后来想想武侠的味道太浓了,有吸引眼球骗取点击率的嫌疑,还是简单一点的好,毕竟这是技术不是文学,需要严谨。你说呢?^+^

在总体的框架设计确定以后,多注意一些编程细节,积少成多,可以获得更佳的性能,让程序跑的更快!

前些天同事优化代码时提到了String和StringBuffer。仔细想想,发现自己也就是知道个大概,所以查了 一下资料。文章中有错误的地方你可一定要告诉我啊,呵呵^+^

一.介绍

String:非可变类(immutable),一旦创建就不能再被改变。
StringBuffer:可变类,创建之后可以被改变。

何谓非可变类?
简单说,非可变类的实例是不能被修改的,每个实例中包含的信息都必须在该实例创建的时候就提供出来 ,并且在对象的整个生存周期内固定不变。

非可变类好处:状态单一,对象简单,便于维护;通常是线程安全的;用户可以共享非可变对象,甚至可 以共享它们的内部信息


二.创建字符串

两种方法:
1. String s1 = "hello";
2. String s2 = new String("hello");

哪种方式性能更好?
例1:

 1 //create String without "new" keyword 
 2 long startTime1 =   System.currentTimeMillis();
 3 for(int i=0;i<100000;i++
 
){
 4        String str1  =   " hello " 
;
 5  
}
 6 long endTime1 =
 
 System.currentTimeMillis();
 7 System.out.println("create String without 'new' keyword : " + (endTime1 - startTime1) + " milli seconds"
 
 );
 8  
     
 9 //create String with 'new' keyword       
 

10 long startTime2 =   System.currentTimeMillis();
11 for(int i=0;i<100000;i++
 
){
12       String str2 = new String("hello"
 
);
13  
}
14 long endTime2 =
 
 System.currentTimeMillis();
15 System.out.println("create String with 'new' keyword : " + (endTime2 - startTime2) + " milli seconds");


输出结果为(注:程序的输出结果也许和你的结果不同,但是总体趋势应该是一致的):

create String without  ' new '  keyword :  0   milli seconds
create String with 
 ' new '  keyword :  16  milli seconds

结论: 创建字符串变量时尽可能不使用new关键字
 
说明:
虽然两个语句都是返回一个String对象的引用,但是jvm对两者的处理方式是不一样的。

对于第一种不用new关键字创建String对象:
JVM首先会在内部维护的滞留字符串中通过String的equels方法查找是对象池中是否存放有该 String对象。如果有,返回已有的String对象给用户,而不会在heap中重新创建一个新的String对象;如 果对象池中没有该String对象,JVM则在heap中创建新的String对象,将其引用返回给用户,同时将该引 用添加至滞留字符串中。

对于第二种使用new关键字创建String对象:
JVM会马上在heap中创建一个String对象,然后将该对象的引用返回给用户。JVM是不会主动把该 对象放到滞留字符串里面的,除非程序调用 String的intern()方法.


JVM为字符串自变量维护一些唯一的String对象,程序员不需要为字符串自变量而发愁。但是使用new关键 字可能会在内存中创建重复的String对象,你不必为此而烦恼,intern()方法可以帮你解决问题。

String.intern():检查字符串对象的存在性,如果需要的字符串对象已经存在,那么它会将引用指向已 经存在的字符串对象而不是重新创建一个。

例2:

 1 String str3 = "world";      /*JVM在滞留字符串中找不到值为“world”的字符串,就在堆上创建一个string对象,并将该对象的引用加入到滞留字符串中*/ 
 2  
 3 String str4 = new String("world");      /*JVM在堆上重新创建一个值为“world”的字符串,此时堆上有两个值为“world”的字符串*/ 
 4 if(str3 ==   str4){
 5       System.out.println("str3 == str4"
 
);
 6  
}
 7 else
 
{
 8       System.out.println("str3 != str4"
 
);
 9  
}
10 //输出:str3 != str4
 

11  
12 String str5 = "world";      /*JVM在发现滞留字符串中存在“world”对象,因此返回str3指向的对象给str5,即str3和str5是指向同一个对象的引用*/ 
13  
14 if(str3 ==   str5){
15       System.out.println("str3 == str5"
 
);
16  
}
17 else
 
{
18       System.out.println("str3 != str5"
 
);
19  
}
20 //输出:str3 == str5     
 

21  
22 str4 = str4.intern();      /*此时,JVM发现滞留字符串中已经存在“world”对象,因此返回str3指向的对象给str4,即str3和str4是指向同一个对象的引用*/ 
23        
24 if(str3 ==
 
 str4){
25       System.out.println("after intern() str3 == str4"
 
);
26  
}
27 else
 
{
28       System.out.println("after intern() str3 != str4"
 
);
29  
}
30 //输出:after intern() str3 == str4
 

31  
32 

结论: 如果使用new关键字创建了字符串变量,则尽可能使用intern()方法。

上面的例子执行正是用到了string对象的不可变性质。既然string对象一旦创建就不可以改变,那么多个 引用指向同一个对象就不会对彼此产生影响。


三.字符串连接

  你可以使用+操作符或者String.concat()或者StringBuffer.append()等办法来连接多个字符串,哪 一种方法性能最佳?

如何选择取决于两种情景:
第一种情景:需要连接的字符串是在编译期间决定的,还是在运行期间决定。
第二种情景:你使用的是 StringBuffer还是String。

通常程序员会认为StringBuffer.append()方法会优于+操作符或 String.concat()方法,但是在一些特定 的情况下这个假想是不成立的。
 

1) 第一种情景:编译期间决定VS
 
运行期间决定

 1 //test the string Concatenation 
 2 long startTime6 =   System.currentTimeMillis();
 3 for(int k=0; k<100000; k++
 
){
 4       String str6 = "this is " + "a test " + "for string concatenation"
 
;
 5  
}
 6 long endTime6 =
 
 System.currentTimeMillis();
 7 System.out.println("string concatenation using '+' : " + (endTime6 - startTime6) + " milli seconds"
 
);
 8  
  
 9 long startTime7 =
 
 System.currentTimeMillis();
10 for(int k=0; k<100000; k++
 
){
11       String str7 = "this is "
 
;
12       str7.concat("a test "
 
);
13       str7.concat("for string concatenation"
 
);
14  
}
15 long endTime7 =
 
 System.currentTimeMillis();
16 System.out.println("string concatenation using concat() : " + (endTime7 - startTime7) + " milli seconds"
 
);
17  

18 long startTime8 =   System.currentTimeMillis();
19 for(int l=0; l<100000; l++
 
){
20       StringBuffer sb8 = new
 
 StringBuffer();
21       sb8.append("this is "
 
);
22       sb8.append("a test "
 
);
23       sb8.append("for string concatenation"
 
);
24  
}
25 long endTime8 =
 
 System.currentTimeMillis();
26 System.out.println("string concatenation using append() : " + (endTime8 - startTime8) + " milli seconds");

上面代码的输出结果:

string concatenation using  ' + '  :  0   milli seconds
string concatenation using concat() : 
 31 
 milli seconds
string concatenation using append() : 
 62  milli seconds

很有趣,+操作符比StringBuffer.append()方法要快. Why?
 
这是因为编译器对简单的字符串进行了优化。即使使用new关键字来创建String对象的时候也是如此。例 如,

编译前:
String str6 = "this is " + "a test " + "for string concatenation";
编译后:
String str6 = "this is a test for string concatenation";

这里String对象在编译期间就决定了而StringBuffer对象是在运行期间决定的。运行期间决定需要额外的 开销。

结论: 如果字符串在编译期间就可以决定它的值,则字符串拼接的时候, “+”操作符效率更高,简单的 认为append()效率高于“+”是错误的。
 

2) 第二种情景:StringBufferVS
 
String

 1 //string concatenation using '+=' 
 2 long startTime9 =   System.currentTimeMillis();
 3 String str9 = "hello"
 
;
 4 for(int i=0; i<10000; i++
 
){
 5       str9 += "hello"
 
;
 6  
}
 7 long endTime9 =
 
 System.currentTimeMillis();
 8 System.out.println("string concatenation using '+=' : " + (endTime9 - startTime9) + " milli seconds"
 
);
 9  
  
10 //string concatenation using append()
 

11 long startTime10 =   System.currentTimeMillis();
12 StringBuffer sb10 = new StringBuffer("hello"
 
);
13 for(int i=0; i<10000; i++
 
){
14       sb10.append("hello"
 
);
15  
}
16 long endTime10 =
 
 System.currentTimeMillis();
17 System.out.println("string concatenation using append() : " + (endTime10 - startTime10) + " milli seconds");


上面代码的输出结果:

string concatenation using  ' += '  :  3094   milli seconds
string concatenation using append() : 
 16  milli seconds

结论: 避免使用“+=”来构造字符串

虽然两者都是在运行期间决定字符串对象,但是使用+=操作符会产生更多的临时对象。

在上例中,由于String类是不可变的,所以进行字符串拼接的时候,每循环一次就会产生临时对象来保存 str9和“hello”的值,之后创建一个临时的StringBuffer对象,并调用其append()方法来完成字符串的 拼接,最后调用toString()方法,将临时StringBuffer对象转为String再赋值给str9。此时str9已经改变 ,指向了新的对象。


3) 第三种情景:设置StringBuffer的容量来提升性能

 1 long startTime10 =   System.currentTimeMillis();
 2 StringBuffer sb10 = new StringBuffer("hello"
 
);
 3 for(int i=0; i<10000; i++
 
){
 4       sb10.append("hello"
 
);
 5  
}
 6 long endTime10 =
 
 System.currentTimeMillis();
 7 System.out.println("string concatenation using append() : " + (endTime10 - startTime10) + " milli seconds"
 
);
 8  
  
 9 //set the StringBuffer capacity
 

10 long startTime11 =   System.currentTimeMillis();
11 StringBuffer sb11 = new StringBuffer("hello"
 
);
12 sb11.ensureCapacity(10000
 
);
13 for(int i=0; i<10000; i++
 
){
14       sb11.append("hello"
 
);
15  
}
16 long endTime11 =
 
 System.currentTimeMillis();
17 System.out.println("string concatenation using append() after set the StringBuffer capacity : " + (endTime11 - startTime11) + " milli seconds");


  上面代码的输出结果:

string concatenation using append() :  16   milli seconds
string concatenation using append() after set the StringBuffer capacity : 
 0  milli seconds

结论: 
声明StringBuffer对象的时候,指定合适的capacity,会提升程序性能。

1)使用StringBuffer的构造函数来设定它的初始化容量:StringBuffer(int length)
2)使用ensureCapacity(int minimumcapacity)方法在StringBuffer对象创建之后设置它的容量。

首先我们看看StringBuffer的缺省行为,然后再找出一条更好的提升性能的途径。
 
StringBuffer的缺省行为:
StringBuffer在内部维护一个字符数组,当你使用缺省的构造函数来创建StringBuffer对象的时候, StringBuffer的容量被初始化为16个字符,也就是说缺省容量就是16个字符。当StringBuffer达到最大容 量的时候,它会将自身容量增加到当前的2倍再加2,也就是(2*旧值+2)。
如果你使用缺省值,初始化之后接着往里面追加字符,在你追加到第17(原文是16,其实是错误的,因为在追加到第16个字符的时候,容量不会发生变化,很抱歉,以后会更严谨一些^+^)个字符的时候它会将容量增加 到34(2*16+2),当追加到34个字符的时候就会将容量增加到70(2*34+2)。无论何事只要StringBuffer 到达它的最大容量它就不得不创建一个新的字符数组然后重新将旧字符和新字符都拷贝一遍。所以给 StringBuffer设置一个合理的初始化容量值,会提升程序的性能。

但是为什么容量变化的时候是2*旧值+2呢?有谁能告诉我么?查资料的时候没有找到

 

附:查资料的过程中发现了jdk 5.0还提供了StringBuilder类,我是没有用过。不过也介绍一下好了。( 来源 JavaWorld ):

       Java.lang.StringBuffer 线程安全的可变字符序列。类似于 String 的字符串缓冲区,但不能修 改。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的 所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。

       每个字符串缓冲区都有一定的容量。只要字符串缓冲区所包含的字符序列的长度没有超出此容量 ,就无需分配新的内部缓冲区数组。如果内部缓冲区溢出,则此容量自动增大。从 JDK 5.0 开始,为该 类增添了一个单个线程使用的等价类,即 StringBuilder 。与该类相比,通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。 但是如果将 StringBuilder 的实例用于多个线程是不安全的。需要这样的同步,则建议使用 StringBuffer 。


本文转自BlogJavaOo缘来是你oO的博客,原文链接:如何让你的程序运行的更快(1)---String VS StringBuffer,如需转载请自行联系原博主。

相关文章
|
6月前
|
存储 安全 Java
String StringBuffer StringBuilder 区别详解与对比分析
本文详细解析了Java中String、StringBuffer和StringBuilder的区别,从可变性、线程安全性和性能三个方面进行对比,并结合具体应用场景分析了三者的适用范围。通过性能测试示例展示了它们在字符串拼接时的效率差异,同时提供了实际代码案例帮助理解。总结指出,String适合少量操作或线程安全场景,StringBuffer适用于多线程环境,而StringBuilder则在单线程下性能最优。开发者应根据需求选择合适的类以优化程序性能。文末还附有相关面试资料供参考。
1051 2
|
存储 安全 Java
String、StringBuffer 和 StringBuilder 的区别
【10月更文挑战第21天】String、StringBuffer 和 StringBuilder 都有各自的特点和适用场景。了解它们之间的区别,可以帮助我们在编程中更合理地选择和使用这些类,从而提高程序的性能和质量。还可以结合具体的代码示例和实际应用场景,进一步深入分析它们的性能差异和使用技巧,使对它们的理解更加全面和深入。
544 57
String、StringBuffer、StringBuilder的区别
String 由 char[] 数组构成,使用了 final 修饰,对 String 进行改变时每次都会新生成一个 String 对象,然后把指针指向新的引用对象。 StringBuffer可变并且线程安全;有一定缓冲区容量,字符串大小没超过容量,不会重新分配新的容量,适合多线程操作字符串; StringBuiler可变并且线程不安全。速度比StringBuffer更快,适合单线程操作字符串。 操作少量字符数据用 String;单线程操作大量数据用 StringBuilder;多线程操作大量数据用 StringBuffer
String、StringBuffer、StringBuilder的区别
这篇文章讨论了Java中String、StringBuffer和StringBuilder的区别。String是不可变的,每次操作都会产生新的对象,效率低且浪费内存。StringBuilder可以在原字符串基础上进行操作,不开辟额外内存,弥补了String的缺陷。StringBuffer和StringBuilder类似,但StringBuffer的方法是线程安全的。文章还列举了StringBuffer的常用方法,并提供了使用示例代码。最后总结了这三者的主要区别。
String、StringBuffer、StringBuilder的区别
【Java基础面试二十六】、说一说String和StringBuffer有什么区别
这篇文章区分了Java中的String和StringBuffer类:String是不可变类,一旦创建字符序列就不能改变;而StringBuffer代表可变的字符串,可以通过其方法修改字符序列,最终可以通过`toString()`方法转换为String对象。
【Java基础面试二十六】、说一说String和StringBuffer有什么区别
|
安全
String,Stringbuffer,StringBuilder的区别
【8月更文挑战第16天】String,Stringbuffer,StringBuilder的区别
138 2
|
安全 Java API
Java系类 之 String、StringBuffer和StringBuilder类的区别
这篇文章讨论了Java中`String`、`StringBuffer`和`StringBuilder`三个类的区别,其中`String`是不可变的,而`StringBuffer`是线程安全的可变字符串类,`StringBuilder`是非线程安全的可变字符串类,通常在单线程环境下性能更优。
Java系类 之 String、StringBuffer和StringBuilder类的区别
|
安全 Java 索引
带你快速掌握Java中的String类和StringBuffer类(详解常用方法 | 区别 )
带你快速掌握Java中的String类和StringBuffer类(详解常用方法 | 区别 )
190 2
|
缓存 安全 Java
【揭秘】String vs StringBuilder vs StringBuffer:三大字符串类的秘密较量!你真的知道何时该用哪个吗?
【8月更文挑战第19天】探讨Java中`String`、`StringBuilder`与`StringBuffer`的区别及应用场景。`String`不可变,适合做哈希表键或多线程共享。`StringBuilder`支持动态修改字符串,适用于单线程环境以提高性能。`StringBuffer`与`StringBuilder`功能相似,但线程安全。示例代码展示各类型的基本用法。选择哪种类型取决于具体需求和性能考量。
228 0