String類是一個不可變類,他是被final修飾的類,不可被繼承,而且值不可改變。java
值不可變就意味着咱們在進行字符串運算的時候,將會生成新的字符串對象,以前的字符串對象的值不會被改變,好比:數組
String a = new String(「aa」) + new String(「bb」);//這期間會生成3個堆對象,new String(」aa」)、new String(」bb」)、new String(」aabb」),安全
而多線程
String a = 「aa」 + 「bb」;//JVM編譯後會優化成 String a = 「aabb」; 此處注意。app
---------------------------------------------------------------------------------------------------------------------------------less
字符串拼接性能:jvm
1 String a = 「aa」 + 「bb」;ide
2 String c = "c".concat("d");函數
3 StringBuilder a = new StringBuilder(「aa」).append(「bb」).toString();源碼分析
4 String a = 「aa」 + new String(「bb」);
性能比較1>2>3>4,注意這只是針對上面3句單純的表達式,實際開發中咱們不多對上面那3句的性能較真,由於在程序中,咱們常常面臨的String操做多是這樣的:
//+
String a = 「a」;
String b = a + 「b」;
//concat
String a = 「a」;
String b = a.concat("d");
//new String
String a = new String(「a」);
String b = new String(「a」) + new String(「b」);
//StringBuilder
StringBuilder a = new StringBuilder(「aa」);
a.append(「bb」).toString();
對於上面的幾種狀況咱們經過實驗得出效率排名,每種操做循環100000次效率以下:
因此,對於頻繁的String操做,仍是乖乖的用StringBuilder吧
---------------------------------------------------------------------------------------------------------------------------------
String 與 StringBuilder:
String a = "a" + "b" + "c";
至關於
String a = "abc";
----------------------------
String a = "a";
String b = "b";
String d = a + b;
至關於
String a = "a";
String b = "b";
String d = new StringBuilder().append(a).append(b).toString();
----------------------------
String a = "a";
String b = "b";
for(int i=0;i<10000;i++){
a = a + b;/a += b;
}
至關於
String a = "a";
String b = "b";
for(int i=0;i<10000;i++){
String a = new StringBuilder(a).append(b).toString();
}
它要比
String a = a + b + b + b + b + b...省略... + b + b;
至關於
String a = new StringBuilder(a).append(b).append(b).append(b)....省略......append(b).toString();
慢好多數量級,緣由是上面的+=表達式寫法,中間會生成好多新的StringBuilder對象,以及好多toString()後的String對象,而下面的寫法不會生成不少StringBuilder對象和String對象。
因此面對較爲頻繁的字符串計算操做,儘可能使用StringBuilder
---------------------------------------------------------------------------------------------------------------------------------
各類字符串的定義與對象建立關係說明:
//直接在常量池生成「11」對象,s直接指向常量池中「11」對象的地址
String s = "11";
// 此時生成了四個對象 常量池中的"1"對象 + 2個堆中的"1"對象 + s3指向的堆中的」11」對象(注此時常量池不會生成"11")
String s3 = new String("1") + new String("1"); //返回新的String對象」11」
// 同時會生成堆中的"1"對象 以及常量池中的"1"對象,可是此時s1是指向堆中的"1"對象的
String s1 = new String("1");
//會在常量池生成"aabb"對象。 且此時jvm作了優化,不會同時生成"aa"對象和"bb"對象在字符串常量池中
String c = "aa" + "bb";
//不會在字符串常量池生成"aabb"對象,只會生成堆對象
String a = "aa";/String a = new String("aa");
String c = a + "bb";
對於何時會在常量池存儲字符串對象,我想咱們能夠基本得出結論:
1.new String 的時候,會在堆內存中生成一個String對象,還會在常量池中生成一個String對象。
2. 顯示調用String的intern方法的時候JDK1.6會判斷常量池中是否有此String對象,沒有將其添加到常量池,JDK1.7會檢查該String對象是否在常量池中,有直接返回該String對象的引用地址,沒有將該字符串對象地址保存到常量池中,並返回;
3. 直接聲明字符串字面常量的時候,例如: String a = "aaa";JDK1.6中若是常量池中沒有該字符串,直接在常量池中建立字符串對象,有則不建立。JDK1.7中若是常量池中有該字符串對象或對象地址,則不建立,不然直接在常量池中建立該字符串對象。
4. 字符串常量直接相加的時候。
例如: String c = "aa" + "bb"; 其中的aa/bb只要有任何一個不是字符串字面常量形式,都不會在常量池生成"aabb"對象. 且此時jvm作了優化,不會同時生成"aa"對象和"bb"對象在字符串常量池中。好比:String c = "aa" + "bb";會在字符串常量池生成"aabb"對象,而String a = "aa"/new String("aa");String c = a + "bb";將不會在字符串常量池生成"aabb"對象。 字符串在常量池中的操做同第3條。
總之,簡單的定義都會在字符串成亮翅生成對象,而涉及字符串運算的字符串定義,就要看狀況了。
---------------------------------------------------------------------------------------------------------------------------------
JDK1.6中字符串常量只存儲字符串對象,JDK1.7及其之後字符串常量不只存儲對象,還存儲對象地址,正常狀況都是直接存儲字符串對象,好比:
String s = "11";//直接在常量池生成字符串對象
String s = new String("11");//不只生成字符串堆對象,同時還在字符串常量池中生成一個字符串對象
String s = "11"+"22";//直接在常量池生成"1122"字符串對象
一下狀況不會在常量池生成字符串對象,只在堆內存生成一個字符串堆對象。只能經過再次調用intern()方法將字符串堆對象的引用地址保存到常量池中。
String a = "aa";
String c = a + "bb";//此處常量池不會生成字符串對象
String a = new String("aa");
String c = a + "bb";//此處常量池不會生成字符串對象
String a = new String("aa") + new String("bb");//此處常量池不會生成字符串對象
String a = new String("aa") +"bb";//此處常量池不會生成字符串對象
---------------------------------------------------------------------------------------------------------------------------------
intern()方法
在JDK1.6中,intern()方法會把首次遇到的字符串實例複製到永久代中,返回的也是永久代中這個字符串的實例的引用,因此調用String的intern()方法返回的引用與字符串對象(注意是對象,不是字符串常量)必然不是同一個引用,將返回false。
在JDK1.7中,字符串常量池從永久代遷移到Java的Heap堆內存當中,intern()的實現不會在複製實例,只是在常量池中記錄首次出現的字符串實例引用,所以若是字符串對象首次出現,intern()方法返回的引用和建立的字符串對象實例的引用將會是是同一個引用,不然返回的是首次出現的字符串對象的引用。
---------------------------------------------------------------------------------------------------------------------------------
瞭解了以上的知識點,咱們來舉例鞏固分析下:
String s = new String("1"); // 1 s.intern();// 2 String s2 = "1";// 3 System.out.println(s == s2);// 4 -------------------- JDK1.6以及如下:false JDK1.7以及以上:false 解析: 1.在堆內存建立一個String("1")對象,在常量池建立String("1")一個對象,共兩個對象。但此時 s 指向堆內存中String("1")對象的地址。 2.不論JDK1.6仍是JDK1.7,此時常量池中已經存在String("1")對象,所以intern()在此處失去了做用。 3.因爲常量池中已經存在String("1")對象,s2此時賦予常量池中String("1")對象的地址。 4.因爲s和s2的值分別是堆對象和常量池對象的對象地址,是兩個對象,所以不論JDK1.6仍是JDK1.7他們都不相等。返回false。 代碼示例 String s3 = new String("1") + new String("1"); // 1 s3.intern();// 2 String s4 = "11";// 3 System.out.println(s3 == s4);// 4 -------------------- JDK1.6以及如下:false JDK1.7以及以上:true 解析: 1.這裏涉及了字符串運算,因此此處將生成4個對象:堆內存中2個String("1")對象,1個String("11")對象,常量池中1個String("1")對象,注意常量池中不會生成String("11")對象。 2.此處調用intern()方法,因爲常量池中沒有String("11")對象,JDK1.6中將會在常量池生成String("11")對象,JDK1.7中將會在常量池中保存堆內存String("11")對象的引用地址(JDK1.7中將常量池從永久區遷移到堆內存)。 3.此處JDK1.6中將永久代中常量池的String("11")對象的引用地址返回給 s4 ,JDK1.7中將堆內存中保存的堆內存String("11")對象的引用地址返回給 s4。 4.JDK1.6中因爲 s3 爲堆內存中String("11")對象的地址,s4 爲永久代常量池中String("11")對象的地址,兩個不一樣的對象地址肯你定不一樣,返回false。JDK1.7中 s3 爲堆內存中String("11")對象的地址,s4 爲堆內存常量池中保存的堆內存中String("11")對象的地址(它兩就是一個地址)所以返回true。
-----------------------------------------------
String s = new String("1"); // 1 String s2 = "1"; // 2 s.intern(); // 3 System.out.println(s == s2); // 4 -------------------- JDK1.6以及如下:false JDK1.7以及以上:false 解析: 1.在堆內存建立一個String("1")對象,在常量池建立String("1")一個對象,共兩個對象。但此時 s 指向堆內存中String("1")對象的地址。 2.因爲常量池中已經存在String("1")對象,s2 此時賦予常量池中String("1")對象的地址。 3.不論JDK1.6仍是JDK1.7,此時常量池中已經存在String("1")對象,所以intern()在此處失去了做用。 4.因爲 s 和 s2 的值分別是堆對象和常量池對象的對象地址,是兩個對象,所以不論JDK1.6仍是JDK1.7他們都不相等。返回false 代碼示例 String s3 = new String("1") + new String("1"); // 1 String s4 = "11";// 2 s3.intern();// 3 System.out.println(s3 == s4);// 4 -------------------- JDK1.6以及如下:false JDK1.7以及以上:false 解析: 1.這裏涉及了字符串運算,因此此處將生成4個對象:堆內存中2個String("1")對象,1個String("11")對象,常量池中1個String("1")對象,注意常量池中不會生成String("11")對象。 2.此處在常量池中生成一個String("11")對象,並將 s4 賦予常量池String("11")對象的地址。 3.不論JDK1.6仍是JDK1.7,此時常量池中已經存在String("11")對象,所以intern()在此處失去了做用。 4.因爲 s3 爲堆內存String("11")對象的地址,s4 爲常量池String("11")對象的地址,s3 和 s4 是兩個對象,因此不論JDK1.6仍是JDK1.7他們都不相等。返回false
---------------------------------------------------------------------------------------------------------------------------------
String、StringBuilder、StringBuffer源碼分析
String類的關鍵源碼分析以下:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[];//final類型char數組 //省略其餘代碼…… …… }
從上述的代碼片斷中咱們能夠看到,String類在類開始處就定義了一個final 類型的char數組value。也就是說經過 String類定義的字符串中的全部字符都是存儲在這個final 類型的char數組中的。
下面咱們來看一下String類對字符串的截取操做,關鍵源碼以下:
public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } //當對原來的字符串進行截取的時候(beginIndex >0),返回的結果是新建的對象 return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); }
當咱們對字符串從第beginIndex(beginIndex >0) 個字符開始進行截取時,返回的結果是從新new出來的對象。因此,在對String類型的字符串進行大量「插入」和「刪除」操做時會產生大量的臨時變量。
StringBuffer和StringBuilder類關鍵源碼分析:
在進行這兩個類的源碼分析前,咱們先來分析下一個抽象類AbstractStringBuilder,由於,StringBuffer和StringBuilder都繼承自這個抽象類,即AbstractStringBuilder類是StringBuffer和StringBuilder的共同父類。AbstractStringBuilder類的關鍵代碼片斷以下:
abstract class AbstractStringBuilder implements Appendable, CharSequence { /** * The value is used for character storage. */ char[] value;//一個char類型的數組,非final類型,這一點與String類不一樣 /** * This no-arg constructor is necessary for serialization of subclasses. */ AbstractStringBuilder() { } /** * Creates an AbstractStringBuilder of the specified capacity. */ AbstractStringBuilder(int capacity) { value = new char[capacity];//構建了長度爲capacity大小的數組 } //其餘代碼省略…… …… } StringBuffer類的關鍵代碼以下: public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence { /** * Constructs a string buffer with no characters in it and an * initial capacity of 16 characters. */ public StringBuffer() { super(16);//建立一個默認大小爲16的char型數組 } /** * Constructs a string buffer with no characters in it and * the specified initial capacity. * * @param capacity the initial capacity. * @exception NegativeArraySizeException if the {@code capacity} * argument is less than {@code 0}. */ public StringBuffer(int capacity) { super(capacity);//自定義建立大小爲capacity的char型數組 } //省略其餘代碼…… ……
StringBuilder類的構造函數與StringBuffer類的構造函數實現方式相同,此處就不貼代碼了。
下面來看看StringBuilder類的append方法和insert方法的代碼,因StringBuilder和StringBuffer的方法實現基本上一致,不一樣的是StringBuffer類的方法前多了個synchronized關鍵字,即StringBuffer是線程安全的。因此接下來咱們就只分析StringBuilder類的代碼了。StringBuilder類的append方法,insert方法都是Override 父類AbstractStringBuilder的方法,因此咱們直接來分析AbstractStringBuilder類的相關方法。
public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); //調用下面的ensureCapacityInternal方法 ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) //調用下面的expandCapacity方法實現「擴容」特性 expandCapacity(minimumCapacity); } /** * This implements the expansion semantics of ensureCapacity with no * size check or synchronization. */ void expandCapacity(int minimumCapacity) { //「擴展」的數組長度是按「擴展」前數組長度的2倍再加上2 byte的規則來擴展 int newCapacity = value.length * 2 + 2; if (newCapacity - minimumCapacity < 0) newCapacity = minimumCapacity; if (newCapacity < 0) { if (minimumCapacity < 0) // overflow throw new OutOfMemoryError(); newCapacity = Integer.MAX_VALUE; } //將value變量指向Arrays返回的新的char[]對象,從而達到「擴容」的特性 value = Arrays.copyOf(value, newCapacity); }
從上述代碼分析得出,StringBuilder和StringBuffer的append方法「擴容」特性本質上是經過調用Arrays類的copyOf方法來實現的。接下來咱們順藤摸瓜,再分析下Arrays.copyOf(value, newCapacity)這個方法吧。代碼以下:
public static char[] copyOf(char[] original, int newLength) { //建立長度爲newLength的char數組,也就是「擴容」後的char 數組,並做爲返回值 char[] copy = new char[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy;//返回「擴容」後的數組變量 }
其中,insert方法也是調用了expandCapacity方法來實現「擴容」特性的,此處就不在贅述了。
接下來,分析下delete(int start, int end)方法,代碼以下:
public AbstractStringBuilder delete(int start, int end) { if (start < 0) throw new StringIndexOutOfBoundsException(start); if (end > count) end = count; if (start > end) throw new StringIndexOutOfBoundsException(); int len = end - start; if (len > 0) { //調用native方法arraycopy對value數組進行復制操做,而後從新賦值count變量達到「刪除」特性 System.arraycopy(value, start+len, value, start, count-end); count -= len; } return this; }
從源碼能夠看出delete方法的「刪除」特性是調用native方法arraycopy對value數組進行復制操做,而後從新賦值count變量實現的
最後,來看下substring方法,源碼以下 :
public String substring(int start, int end) { if (start < 0) throw new StringIndexOutOfBoundsException(start); if (end > count) throw new StringIndexOutOfBoundsException(end); if (start > end) throw new StringIndexOutOfBoundsException(end - start); //根據start,end參數建立String對象並返回 return new String(value, start, end - start); }
4、總結:
一、String類型的字符串對象是不可變的,一旦String對象建立後,包含在這個對象中的字符系列是不能夠改變的,直到這個對象被銷燬。
二、StringBuilder和StringBuffer類型的字符串是可變的,不一樣的是StringBuffer類型的是線程安全的,而StringBuilder不是線程安全的
三、若是是多線程環境下涉及到共享變量的插入和刪除操做,StringBuffer則是首選。若是是非多線程操做而且有大量的字符串拼接,插入,刪除操做則StringBuilder是首選。畢竟String類是經過建立臨時變量來實現字符串拼接的,耗內存還效率不高,怎麼說StringBuilder是經過JNI方式實現終極操做的。
四、StringBuilder和StringBuffer的「可變」特性總結以下:
(1)append,insert,delete方法最根本上都是調用System.arraycopy()這個方法來達到目的
(2)substring(int, int)方法是經過從新new String(value, start, end - start)的方式來達到目的。所以,在執行substring操做時,StringBuilder和String基本上沒什麼區別。