String,StringBuffer与StringBuilder的区别

String

String在java编程中广泛应用,首先从源码进行分析
String,StringBuffer与StringBuilder的区别
从这我们可以得知,String底层是一个final类型的字符数组,所以String的值是不可变的,每次对String的操作都会生成新的String对象,造成内存浪费
而StringBuffer和StringBuilder就不一样了,他们两都继承了AbstractStringBuilder抽象类,从AbstractStringBuilder抽象类中我们可以看到
String,StringBuffer与StringBuilder的区别
他们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBuffer和StringBuilder来进行操作。
接着看一下他们的继承结构以及部分源码实现
String,StringBuffer与StringBuilder的区别
String,StringBuffer与StringBuilder的区别
String,StringBuffer与StringBuilder的区别
从这三张图我们不难得知:StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

再详细说一下String
String并不是基本数据类型,而是一个对象。字符串为对象,那么在初始化之前,它的值为null,到这里就有必要提下””、null、new String()三者的区别。null 表示string还没有new ,也就是说对象的引用还没有创建,也没有分配内存空间给他,而””、new String()则说明了已经new了,只不过内部为空,但是它创建了对象的引用,是需要分配内存空间的。
java的虚拟机在内存中开辟出一块单独的区域,用来存储字符串对象,这块内存区域被称为字符串缓冲池。那个java的字符串缓冲池是如何工作的呢?
String,StringBuffer与StringBuilder的区别

String a = "abc";
String b = "abc";
String c = new String("xyz");

例如上边的代码:

String a = "abc";

创建字符串的时候先查找字符串缓冲池中有没有相同的对象,如果有相同的对象就直接返回该对象的引用,如果没有相同的对象就在字符串缓冲池中创建该对象,然后将该对象的应用返回。对于这一步而言,缓冲池中没有abc这个字符串对象,所以首先创建一个字符串对象,然后将对象引用返回给a。

String b = "abc";

这一句也是想要创建一个对象引用变量b使其指向abc这一对象。这时,首先查找字符串缓冲池,发现abc这个对象已经有了,这是就直接将这个对象的引用返回给b,此时a和b就共用了一个对象abc,不过不用担心,a改变了字符串不会影响b,因为字符串都是常量,一旦创建就没办法修改了,除非创建一个新的对象。

String c = new String("xyz");

查找字符串缓冲池发现没有xyz这个字符串对象,于是就在字符串缓冲池中创建了一个xyz对象然后再将引用返回。
从上边的分析可以看出,当new一个字符串时并不一定是创建了一个新的对象,有可能是与别的引用变量共同使用了同一个对象。下面看几个常见的有关字符串缓冲池的问题。

创建了几个对象

String a = "abc";
String b = "abc";
String c = new String("xyz");
String d = new String("xyz");
String e = "ab" + "cd";

这个程序与上边的程序比较相似,我们分比来看一下:

1、String a = “abc”;这一句由于缓冲池中没有abc这个字符串对象,所以会创建一个对象;

2、String b = “abc”;由于缓冲池中已经有了abc这个对象,所以不会再创建新的对象;

3、String c = new String(“xyz”);由于没有xyz这个字符串对象,所以会首先创建一个xyz的对象,然后这个字符串对象由作为String的构造方法,在内存中(不是缓冲池中)又创建了一个新的字符串对象,所以一共创建了两个对象;

4、String d = new String(“xyz”);缓冲池中已有该字符串对象,则缓冲池中不再创建该对象,然后会在内存中创建一个新的字符串对象,所以只创建了一个对象;

5、String e = ”ab” + ”cd”;由于常量的值在编译的时候就被确定了。所以这一句等价于String e = ”abcd”;所以创建了一个对象;

所以创建的对象的个数分别是:1,0,2,1,1。

到底相等不相等

我们在学习java时就知道两个字符串对象相等的判断要用equal而不能使用==,但是学习了字符串缓冲池以后,应该知道为什么不能用==, 什么情况下==,和equal是等价的,首先,必须知道的是,equal比较的是两个字符串的值是否相等,而==比较的是两个对象的内存地址是否相等,下面我们就通过几个程序来看一下。
实例一:

public static void main(String[] args) { 
    String s1 = "Monday"; 
    String s2 = "Monday"; 
    if (s1 == s2) {
    	System.out.println("s1 == s2"); 
    }
    else {
    	System.out.println("s1 != s2"); 
    }
}

输出结果:

s1 == s2
分析:通过上边的介绍字符串缓冲池,我们知道s1和s2都是指向字符串缓冲池中的同一个对象,所以内存地址是一样的,所以用==可以判断两个字符串是否相等。

实例二:

public static void main(String[] args) { 
    String s1 = "Monday"; 
    String s2 = new String("Monday"); 
    if (s1 == s2) {
    	System.out.println("s1 == s2"); 
    }
    else {
		 System.out.println("s1 != s2"); 
	}
    if (s1.equals(s2)) {
    	System.out.println("s1 equals s2"); 
    }
    else {
    	System.out.println("s1 not equals s2"); 
    }
} 

输出结果:

s1 != s2
s1 equals s2
分析:由上边的分析我们知道,String s2 = new String(“Monday”);这一句话没有在字符串缓冲池中创建新的对象,但是会在内存的其他位置创建一个新的对象,所以s1是指向字符串缓冲池的,s2是指向内存的其他位置,两者的内存地址不同的。

实例三:

public static void main(String[] args) { 
    String s1 = "Monday"; 
    String s2 = new String("Monday"); 
    s2 = s2.intern(); 
    if (s1 == s2) {
        System.out.println("s1 == s2"); 
    }
    else {
        System.out.println("s1 != s2"); 
    }
    if (s1.equals(s2)) {
        System.out.println("s1 equals s2"); 
    }
    else {
        System.out.println("s1 not equals s2"); 
    }
}

输出结果:

s1 == s2
s1 equals s2
分析:先来说说intern()这个方法的作用吧,这个方法的作用是返回在字符串缓冲池中的对象的引用,所以s2指向的也是字符串缓冲池中的地址,和s1是相等的。

实例四:

public static void main(String[] args) { 
 
    String Monday = "Monday";  
    String Mon = "Mon";  
    String  day = "day";  
    System.out.println(Monday == "Mon" + "day");  
    System.out.println(Monday == "Mon" + day);  
 
}

输出结果:

true
false
分析:第一个为什么等于true我们已经说过了,因为两者都是常量所以在编译阶段就已经能确定了,在第二个中,day是一个变量,所以不能提前确定他的值,所以两者不相等,从这个例子我们可以看出,只有+连接的两边都是字符串常量时,引用才会指向字符串缓冲池,都则都是指向内存中的其他地址。

实例五:

public static void main(String[] args) { 
 
    String Monday = "Monday";  
    String Mon = "Mon";  
    final String  day = "day";  
    System.out.println(Monday == "Mon" + "day");  
    System.out.println(Monday == "Mon" + day);  
 
}

输出结果:

true
true

分析:加上final后day也变成了常量,所以第二句的引用也是指向的字符串缓冲池。