String、Stringbuffer和StringBuilder的区别:
·String对象的内容不允许修改;
·StringBuffer对象的内容可以修改,所有的方法都使用了synchronized声明,属于线程安全的操作(查看JDK的源码即可看到);
·StringBuilder是JDK1.5之后追加的新类,此类对象可以修改,所有方法使用异步处理,属于非线程安全的操作。
总结来讲,开发的首选还是String类,要修改数据,需要保证线程安全,使用StringBuffer;如果不需要保证线程安全,则使用StringBuilder。其实StringBuffer和StringBuilder在性能上的差距并不是很大,如何选择主要看个人编码习惯。
下面详细介绍String和StringBuffer(StringBuilder应用很少):
一.String类详解
1、String类既然是类,那么它实例化的时候就必须依靠对象,而String类的对象有两种定义方式:
·直接赋值:String str = “Hello”;
·利用构造方法完成,String是一个类,那么既然是类就一定会提供有构造方法,那么在String类里面提供有一个有参构造:public String(String str);
从使用上来讲第一种更简单,但是第二种更符合程序员的习惯性思维。
那么两种实例化方式有什么区别?
|-直接赋值:String str = “hello”;
字符串常量是String类的匿名对象(因为字符串常量可以调用String类的方法,如”hello”.equals(str)),直接赋值的内存关系如下图所示:
[img]http://dl2.iteye.com/upload/attachment/0121/3112/d2f0095f-daa4-30f8-86d2-ca637805bbe8.jpg[/img]
此时通过内存关系图可以发现,只开辟了一块堆内存空间。
|-采用构造方法进行处理
String str = new String(“hello”);
此种实例化方式的内存关系如下图所示:
[img]http://dl2.iteye.com/upload/attachment/0121/3108/092a6a3a-5aaf-3f53-bd04-c7df59410c45.jpg[/img]
通过观察内存图可以发现,此种方式实例化String类对象时产生了两块堆内存空间,并且有一块是垃圾空间,正是因为如此,我们不会采用此类模式。
此时String类两种实例化方式的区别便可总结如下:
*直接赋值:只会开辟一块堆内存空间;
*构造方法实例化:会开辟两块堆内存空间,并且有一块将成为垃圾。
考虑到以上区别, 在开发之中全部要求使用直接赋值的模式完成
2.字符串一旦声明则不可改变
字符串的内容是不可变的,但是在实际应用过程中,我们用“+”实现了字符串的连接,这个不就是改变了字符串的内容吗?
假设有如下代码:
public class TestString {
public static void main(String args[]) {
String str = "hello " ;
str = str + "world " ;
str += "!!!" ;
System.out.println(str) ;
}
}
该程序的执行内存图如下所示:
[img]http://dl2.iteye.com/upload/attachment/0121/3102/148c579f-4d42-3e37-8515-288c983de974.jpg[/img]
通过以上的分析可以发现,所谓的字符串的内容的修改本质上也只是对象引用的变化而已。那么正因为如此,所以在以后的开发之中此类代码必须要回避,不能使用的过多。因为如果是高并发的访问的话,内存一定会被沾满,因为垃圾回收还没执行前可能内存空间就已经被沾满了。
3.字符串的比较以及对象池的概念
要表较两个int型的数据是否相等,可以用“==”进行比较。如果要比较两个字符串,是不是也可以使用“==”呢?请看下面代码:
public class TestString {
public static void main(String args[]) {
String strA = "hello" ; //直接赋值
String strB = new String("hello") ; //使用构造方法实例化
String strC = strB ; //
System.out.println(strA == strB) ; // false
System.out.println(strA == strC) ; // false
System.out.println(strB == strC) ; // true
}
}
为什么会出现上面的结果呢?请看下面的内存关系图。
[img]http://dl2.iteye.com/upload/attachment/0121/3104/e3645610-101e-3f8f-9fb1-cb35adc792d5.jpg[/img]
通过以上的分析可以看出,使用“==”的确可以比较String类对象是否相等,但是它比较的只是两个对象的内存地址数值,并没有比较字符串的内容,所以结果为false。若需要比较字符串的内容,可以利用String类中定义的对象比较的方法:
public boolean equals(Object anObject); 如strA.equals(strB);
另有一点需要注意一下,在使用equals()方法进行比较,最好用肯定不会为null的变量来调用equals()方法,否则很容易出现NullPointerException的异常。如下面代码所示:
public class TestString {
public static void main(String args[]) {
String userName = null ; //
if ( “hello”.equals(userName)) {
System.out.println("两个对象内容相同。") ;
} else {
System.out.println("两个对象内容不相同。") ;
}
}
}
下面再来解释下对象池的概念:
public class TestString {
public static void main(String args[]) {
String strA = "hello" ;
String strB = "hello" ;
String strC = "hello" ;
System.out.println(strA == strB) ; // true
System.out.println(strB == strC) ; // true
System.out.println(strA == strC) ; // true
}
}
以上三个String类的对象,内容都是相同,并且通过代码的执行结果可以发现, “==”比较的是两个对象的地址数值,那么如果是相同的,就说明这三个对象都指向想了同一个堆内存(因为通过以上分析可知,“==”比较的是堆内存的地址编码)。内存示意图如下所示:
[img]http://dl2.iteye.com/upload/attachment/0121/3110/766e9e6f-52b0-345d-9664-aec13c153bfb.jpg[/img]
实际上在JVM的底层有一块内存区域(对象池),对象池是一块共享的数据区,所有的其他对象都可以共享这一个对象池中的内容。String类的设计正好使用了这种共享设计模式的概念。
如果采用直接赋值的方式定义有一个String类对象,那么此对象一旦声明之后将保存在对象池之中。以供下次继续使用。而当继续采用直接赋值的方式再次声明一个字符串时,这时会首先在对象池中进行查找,看对象池中是否有该字符串,如果发现有该内容,则不会开辟新的堆内存空间,而将已有字符串的堆内存的指向交由新的String类对象。如果对象池中不存在该字符串,则开辟新的堆内存空间,并将该字符串自动入池。
补充:用构造方法实例化的字符串对象不能实现自动入池,但是可以通过String类中public String intern();如String str = new String(“hello“); str.intern()即可实现字符串手动入池。
解释String类两种实例化方式的区别?
·直接赋值:只会开辟一块堆内存空间,并且声明的对象可以自动保存在对象池之中,以供相同内容的字符串对象引用;
·构造方法实例化:会开辟两块堆内存空间,并且有一块堆内存将成为垃圾,此类模式产生的实例化对象,不会自动入池,但是可以通过intern()方法实现手工入池。
二、StringBuffer类详解
StringBuffer本身也属于一个字符串的操作类,但是很明显里面带有一个Buffer就表示缓冲。所以一旦带有缓冲的操作,那么一般有两个意义:一个意义是可以快速的修改,另一个意义是可以快速的读取。
String类对象的内容不可改变,而StringBuffer类对象的内容可以改变。
1.实例化方式的比较:
·String类有直接赋值实例化和采用构造方法实例化两种实例化方式;
·StringBuffer类只能通过构造方法进行实例化;StringBuffer but = new StringBuffer();
2.字符串连接方式的比较:
·String类中用“+”完成字符串之间的连接;如str1 = “hello”; str2 = “world”; str = str1 + str2;
·StringBuffer类中要实现字符串之间的连接,需要使用append();
public String append(StringBuffer buf);如buf1 = “hello”;buf2 = “world”;
buf = buf1.append(buf2);
3.String和StringBuffer之间的转换
查看javaSE的API文档可以发现,String和StringBuffer都是CharSequence接口的子类,但是这两个类的对象绝对不可能直接转换。
|-String变为StringBuffer:
·直接利用StringBuffer类提供的构造方法:public StringBuffer(String str);
StringBuffer buf = new StringBuffer(“hello”);
·直接利用append()方法将字符串保存在StringBuffer之中:
StringBuffer buf = new StringBuffer();
Buf.append(“hello”);
|-StringBuffer变为String
任何类的对象都可以变为String类型,因为所有类中都提供有toString()方法:
StringBuffer buf = new StringBuffer();
buf.append(“hello”);
String str = buf.toString();
总结:在开发中由于StringBuffer可以被改变,所以很少用其作为引用传递或返回数据类型。作为参数或返回类型的时候90%的情况下都会选择String。