以前对string和stringbuffer很多细节没弄清楚,看了以下内容,觉得清晰了很多。这些内容对string和stringbuffer做了很详细的讲解,对巩固string和stringbuffer很有帮助。
1、基本介绍
在java中有3个类来负责字符的操作:
1.Character 是进行单个字符操作的。
2.String 对一串字符进行操作,不可变类。
3.StringBuffer 也是对一串字符进行操作,但是可变类。
1.1、String
是对象不是原始类型。为不可变对象,一旦被创建,就不能修改它的值。
对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去。
String 是final类,即不能被继承。
1.2、StringBuffer
是一个可变对象,当对他进行修改的时候不会像String那样重新建立对象它只能通过构造函数来建立。
StringBuffer sb = new StringBuffer(); //不能通过赋值符号对他进行赋值。
sb = "welcome to here!";//error 这种写法是错误的
对象被建立以后,在内存中就会分配内存空间,并初始保存一个null.向StringBuffer中赋值的时候可以通过它的append方法.
sb.append("hello");
字符串连接操作中StringBuffer的效率要比String高:
String str = new String("welcome to ");
str += "here";
的处理步骤实际上是通过建立一个StringBuffer,然后调用append(),最后再将StringBuffer toSting();这样的话String的连接操作就比StringBuffer多出了一些附加操作,当然效率上要打折扣。并且由于String 对象是不可变对象,每次操作Sting 都会重新建立新的对象来保存新的值。这样原来的对象就没用了,就要被垃圾回收.这也是要影响性能的.
2、String和StringBuffer的区别
他们的区别是很大的,它们是两个独立的类,可以这么说吧,除了名字有些相似,完全不相干.String类是不可改变的,如果你更改了String类的实例,那么以前的就“不存在了”,所指向的是一个新的实例。而StringBuffer类是可以更改的。
看如下例子,体会这一点:
String s1="Hello";
String s2="Hello";
if(s1==s2)
System.out.println("Equal");
else
System.out.println("Not equla");
这个程序片断将输出Equal,原因是String类的实例是不能更改的.在生成第2个实例时它发现在缓冲池中已经有了"Hello"字符串,于是它就直接索引到这个字符串. 然而如果,你将第二行代码改为:
String s2 = new String("Hello");
再运行结果将是Not equal,因为更换了一个构造器形式,在生成第二个实例时,它就不在缓冲池中索引了,而是直接在内存中构造这样一个对象.因此,其句柄的值是不同的.也就是说,当你更改了String类的实例,那么其句柄索引的将是在Heap Memory的另外一个位置的对象,以前的就不能再索引到了.
但是,StringBuffer类却不是如此,如果你更改了它的实例,但是在Heap Memory的缓冲池中,索引的是同一个实例.即更改了实例,但句柄的只是不变的,它是可以更改的!
string 的 “+” 操作就是根据 StringBuilder (或 StringBuffer )类及其 append 方法实现的。
String 不可变其实就是说一个 String 对象创建之后不能再在这个对象上做其他操作(如追加,删除一个字符),只能通过创建别的 String 对象来获取这个效果,而 StringBuilder 就可以在一个 StringBuilder 对象上进行改变:
String str = “ hello ” ;
str = str+ “ , world ! ” ; // 这时并不是在原来的 “ hello ”对象 上追加 “ , world ! ” ,而是重新创建了一个 “ hello , world ! ”对象
String 不能被修改,事实上很简单。就是因为 String 没有提供写操作方法,没有提供能够修改 String 对象的成员变量的方法。而 StringBuilder 则提供了这样的方法( append ()等方法)。
也许有人会说 String 不是有个 concat 方法可以在字符串后面追加字符串吗?呵呵,我们看过 API 的解释就知道是怎么回事了:
public String concat(String str)
将指定字符串联到此字符串的结尾。 如果参数字符串的长度为 0,则返回此 String 对象。否则,创建一个新的 String 对象,用来表示由此 String 对象表示的字符序列和由参数字符串表示的字符序列串联而成的字符序列。
对了,区别就在于 String 的调用 concat 方法会新建立一个 String 对象,而 StringBuilder 的 append 方法返回的还是原来对象的应用。
String 对象是不可改变的。每次使用 System.String 类中的方法之一或进行运算时(如赋值、拼接等)时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间。而 StringBuilder 则不会。在需要对字符串执行重复修改的情况下,与创建新的 String 对象相关的系统开销可能会非常昂贵。如果要修改字符串而不创建新的对象,则可以使用 System.Text.StringBuilder 类。例如,当在一个循环中将许多字符串连接在一起时,使用 StringBuilder 类可以提升性能。
1.它是引用类型,在堆上分配内存
2.运算时会产生一个新的实例
3.String 对象一旦生成不可改变(Immutable)
4.定义相等运算符(== 和 !=)是为了比较 String 对象的值(而不是引用)
3、String和StringBuffer的性能区别
3.1、创建字符串的较佳途径
你可以按照以下方式创建字符串对象:
这段代码的输出:
JVM是怎样处理字符串的呢?
Java虚拟机会维护一个内部的滞留字符串对象的列表(唯一字符串的池)来避免在堆内存中产生重复的String对象。当JVM从class文件里加载字符串字面量并执行的时候,它会先检查一下当前的字符串是否已经存在于滞留字符串列表,如果已经存在,那就不会再创建一个新的String对象而是将引用指向已经存在的String对象,JVM会在内部为字符串字面量作这种检查,但并不会为通过new关键字创建的String对象作这种检查。当然你可以明确地使用String.intern()方法强制JVM为通过 new关键字创建的String对象作这样的检查。这样可以强制JVM检查内部列表而使用已有的String对象。
所以结论是,JVM会内在地为字符串字面量维护一些唯一的String对象,程序员不需要为字符串字面量而发愁,但是可能会被一些通过 new关键字创建的String对象而困扰,不过他们可以使用intern()方法来避免在堆内存上创建重复的String对象来改善Java的运行性能。
3.2、连接字符串时候的优化技巧
你可以使用+操作符或者String.concat()或者StringBuffer.append()等办法来连接多个字符串,那一种办法具有最佳的性能呢?
如何作出选择取决于两种情景,第一种情景是需要连接的字符串是在编译期决定的还是在运行期决定的,第二种情景是你使用的是 StringBuffer还是String。通常程序员会认为StringBuffer.append()方法会优于+操作符或 String.concat()方法,但是在一些特定的情况下这个假想是不成立的。
1) 第一种情景:编译期决定相对于运行期决定
请看下面的StringTest3.java代码和输出结果。
这是上面的代码的输出结果:
很有趣地,+操作符居然比StringBuffer.append()方法要快,为什么呢?
这里编译器的优化起了关键作用,编译器像下面举例的那样简单地在编译期连接多个字符串。它使用编译期决定取代运行期决定,在你使用new关键字来创建String对象的时候也是如此。
编译前:
String result = "This is"+"testing the"+"difference"+"between"+"String"+"and"+"StringBuffer";
编译后:
String result = "This is testing the difference between String and StringBuffer";
这里String对象在编译期就决定了而StringBuffer对象是在运行期决定的。运行期决定需要额外的开销当字符串的值无法预先知道的时候,编译期决定作用于字符串的值可以预先知道的时候,下面是一个例子。
编译前:
public String getString(String str1,String str2) {
return str1+str2;
}
编译后:
return new StringBuffer().append(str1).append(str2).toString();
运行期决定需要更多的时间来运行。
2) 第二种情景:使用StringBuffer取代String
看看下面的代码你会发现与情景一相反的结果——连接多个字符串的时候StringBuffer要比String快。
这是上面的代码的输出结果:
看得出StringBuffer.append()方法要比+操作符要快得多,为什么呢?原因是两者都是在运行期决定字符串对象,但是+操作符使用不同于StringBuffer.append()的规则通过String和StringBuffer来完成字符串连接操作。
3.3、借助StringBuffer的初始化过程的优化技巧
你可以通过StringBuffer的构造函数来设定它的初始化容量,这样可以明显地提升性能。这里提到的构造函数是StringBuffer(int length),length参数表示当前的StringBuffer能保持的字符数量。你也可以使用ensureCapacity(int minimumcapacity)方法在StringBuffer对象创建之后设置它的容量。首先我们看看StringBuffer的缺省行为,然后再找出一条更好的提升性能的途径。
StringBuffer的缺省行为:
StringBuffer在内部维护一个字符数组,当你使用缺省的构造函数来创建StringBuffer对象的时候,因为没有设置初始化字符长度,StringBuffer的容量被初始化为16个字符,也就是说缺省容量就是16个字符。当StringBuffer达到最大容量的时候,它会将自身容量增加到当前的2倍再加2,也就是(2*旧值+2)。
如果你使用缺省值,初始化之后接着往里面追加字符,在你追加到第16个字符的时候它会将容量增加到34(2*16+2),当追加到34个字符的时候就会将容量增加到70(2*34+2)。无论何事只要StringBuffer到达它的最大容量它就不得不创建一个新的字符数组然后重新将旧字符和新字符都拷贝一遍——这也太昂贵了点。所以总是给StringBuffer设置一个合理的初始化容量值是错不了的,这样会带来立竿见影的性能增益。
我利用两个StringBuffer重新测试了上面的StringTest4.java代码,一个未使用初始化容量值而另一个使用了。这次我追加了50000个’hello’对象没有使用+操作符。区别是我使用StringBuffer(250000)的构造函数来初始化第二个 StringBuffer了。
输出结果如下:
Time taken for String concatenation using StringBuffer with out setting size: 280 milli seconds
Time taken for String concatenation using StringBuffer with setting size: 0 milli seconds
StringBuffer初始化过程的调整的作用由此可见一斑。所以,使用一个合适的容量值来初始化StringBuffer永远都是一个最佳的建议。
3.4、关键点
1. 无论何时只要可能的话使用字符串变量来创建字符串而不是使用new关键字来创建字符串。
2. 无论何时当你要使用new关键字来创建很多内容重复的字符串的话,请使用String.intern()方法。
3. +操作符会为字符串连接提供最佳的性能——当字符串是在编译期决定的时候。
4. 如果字符串在运行期决定,使用一个合适的初期容量值初始化的StringBuffer会为字符串连接提供最佳的性能。