1 String
String:字符串常量,字符串长度不可变。
2 StringBuffer
StringBuffer:字符串变量(Synchronized,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用StringBuffer,如果想转成String类型,可以调用StringBuffer的toString()方法。
Java.lang.StringBuffer线程安全的可变字符序列。在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。
StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将?定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。
3 StringBuilder
StringBuilder:字符串变量(非线程安全)。
java.lang.StringBuilder是一个可变的字符序列,是JDK5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因?在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。
在大部分情况下,StringBuilder > StringBuffer。
4 三者区别
String 类型和StringBuffer的主要性能区别:String是不可变的对象, 因此在每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因?每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,性能就会降低。
使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。所以多数情况下推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。
在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,例如:
1
2
|
String S1 = “This is only a” + “ simple” + “ test”; StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”); |
生成 String S1 对象的速度并不比 StringBuffer慢。其实在 JVM 里,自动做了如下转换:
1
|
String S1 = “This is only a” + “ simple” + “test”; |
JVM直接把上述语句当作:
1
|
String S1 = “This is only a simple test”; |
所以速度很快。但要注意的是,如果拼接的字符串来自另外的String对象的话,JVM就不会自动转换了,速度也就没那么快了,例如:
1
2
3
4
|
String S2 = “This is only a”; String S3 = “ simple”; String S4 = “ test”; String S1 = S2 +S3 + S4; |
这时候,JVM 会规规矩矩的按照原来的方式去做。
在大部分情况下,StringBuffer > String。
4.StringBuffer和StringBuilder
二者几乎没什么区别,基本都是在调用父类的各个方法,一个重要的区别就是StringBuffer是线程安全的,内部的大多数方法前面都有关键字synchronized,这样就会有一定的性能消耗,StringBuilder是非线程安全的,所以效率要高些。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public static void main(String[] args) throws Exception { String string = "0" ; int n = 10000 ; long begin = System.currentTimeMillis(); for ( int i = 1 ; i < n; i++) { string += i; } long end = System.currentTimeMillis(); long between = end - begin; System.out.println( "使用String类耗时:" + between+ "ms" ); int n1 = 10000 ; StringBuffer sb = new StringBuffer( "0" ); long begin1 = System.currentTimeMillis(); for ( int j = 1 ; j < n1; j++) { sb.append(j); } long end1 = System.currentTimeMillis(); long between1 = end1 - begin1; System.out.println( "使用StringBuffer类耗时:" + between1+ "ms" ); int n2 = 10000 ; StringBuilder sb2 = new StringBuilder( "0" ); long begin2 = System.currentTimeMillis(); for ( int k = 1 ; k < n2; k++) { sb2.append(k); } long end2 = System.currentTimeMillis(); long between2 = end2 - begin2; System.out.println( "使用StringBuilder类耗时:" + between2+ "ms" ); } |
输出:
1
2
3
|
使用String类耗时:982ms 使用StringBuffer类耗时:2ms 使用StringBuilder类耗时:1ms |
虽然这个数字每次执行都不一样,而且每个机子的情况也不一样,但是有几点是确定的,String类消耗的明显比另外两个多得多。还有一点就是,StringBuffer要比StringBuilder消耗的多,尽管相差不明显。
5 使用策略
(1)基本原则:如果要操作少量的数据,用String ;单线程操作大量数据,用StringBuilder ;多线程操作大量数据,用StringBuffer。
(2)不要使用String类的"+"来进行频繁的拼接,因?那?的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则。举个例子:在使用String 的时候,拼接字符串时使用“+”在JVM上形成临时的StringBuffer对象,同时在每一个字符串上都建立一个对象,拼接了两个字符串一共需要创建4个对象!(一个保存结果的String,两个字符串对象,一个临时的StringBuffer对象)。而使用StringBuffer的话,只需创建2个对象!一个StringBuffer对象和保存最后结果的的String对象 。
(3)?了获得更好的性能,在构造 StirngBuffer 或 StirngBuilder 时应尽可能指定它们的容量。当然,如果你操作的字符串长度不超过 16 个字符就不用了。不指定容量会显著降低性能。
(4)StringBuilder一般使用在方法内部来完成类似"+"功能,因?是线程不安全的,所以用完以后可以丢弃。StringBuffer主要用在全局变量中。
(5)相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非确定系统的瓶颈是在 StringBuffer 上,并且确定你的模块不会运行在多线程模式下,才可以采用StringBuilder;否则还是用StringBuffer。