2.java- 再談字符串類 String Stringbuffer,StringBuilder

1.String類能被繼承嗎,爲什麼?

          不能。類被final 修飾,不能被繼承。

2. String,Stringbuffer,StringBuilder的區別?

       2.1我想從三個角度講下區別

        2.11 是否可變?  String 對象是不可變對象,每次進行字符串變量修改的時候都會新創建一個對象,  而StringBuffer StringBuilder 可變的字符串對象, 在原有的數據上進行修改。(反應在內存堆中的變化)

       2.12 是否線程安全?

         只有StringBuffer 線程安全的字符串對象,在方法上加了Synchronized  

       2.13 相加效率?

           StringBuilder > Stringbuffer > String

 

      2.2 使用場景的考慮

              每次改變String的值,都要新創建一個對象,再將引用指向該對象,浪費內存空間,內存中的無用引用多了以後,jvm的GC就開始工作,系統就會慢了。

          2.21.單線程下在字符串緩存區進行大量操作。 StringBuilder

          2.22 多線程下在字符串緩存區進行大量操作。 StringBuffer  【實際開發中當中 基本不用,多線程下就考慮用鎖,而不是這貨】

          2. 23 String 少量的字符串操作

     2.3 談談String在內存中JVM 分佈

           這裏寫圖片描述

         這裏寫圖片描述   

 

這裏寫圖片描述

 

 

3.介紹String.intern()

JDK 1.7後,intern方法還是會先去查詢常量池中是否有已經存在,如果存在,則返回常量池中的引用,這一點與之前沒有區別,區別在於,如果在常量池找不到對應的字符串,則不會再將字符串拷貝到常量池,而只是在常量池中生成一個對原字符串的引用。簡單的說,就是往常量池放的東西變了:原來在常量池中找不到時,複製一個副本放到常量池,1.7後則是將在堆上的地址引用複製到常量池。 
這裏寫圖片描述

 

                  jdk1.7 調用的時候,常量池未有這個常量,把對應這個常量的引用拷貝到常量池 ,並返回這個指向 (堆) 的引用

                  jdk1.7之前的:(區別低版 本,拷貝副本到常量池,再返回 (常量池) 引用)

 

4.其他的一些面試題

1. 下面這段代碼的輸出結果是什麼?

  String a = "hello2";   String b = "hello" + 2;   System.out.println((a == b));

  輸出結果爲:true。原因很簡單,"hello"+2在編譯期間就已經被優化成"hello2",因此在運行期間,變量a和變量b指向的是同一個對象。

2.下面這段代碼的輸出結果是什麼?

  String a = "hello2";    String b = "hello";       String c = b + 2;       System.out.println((a == c));

  輸出結果爲:false。由於有符號引用的存在,所以  String c = b + 2;不會在編譯期間被優化,不會把b+2當做字面常量來處理的,因此這種方式生成的對象事實上是保存在堆上的。因此a和c指向的並不是同一個對象。javap -c得到的內容:

3.下面這段代碼的輸出結果是什麼?

  String a = "hello2";     final String b = "hello";       String c = b + 2;       System.out.println((a == c));

  輸出結果爲:true。對於被final修飾的變量,會在class文件常量池中保存一個副本,也就是說不會通過連接而進行訪問,對final變量的訪問在編譯期間都會直接被替代爲真實的值。那麼String c = b + 2;在編譯期間就會被優化成:String c = "hello" + 2; 下圖是javap -c的內容:

 

4.下面這段代碼輸出結果爲:

1

2

3

4

5

6

7

8

9

10

11

12

public class Main {

    public static void main(String[] args) {

        String a = "hello2";

        final String b = getHello();

        String c = b + 2;

        System.out.println((a == c));

    }

     

    public static String getHello() {

        return "hello";

    }

}

  輸出結果爲false。這裏面雖然將b用final修飾了,但是由於其賦值是通過方法調用返回的,那麼它的值只能在運行期間確定,因此a和c指向的不是同一個對象

 

5.下面這段代碼的輸出結果是什麼?

1

2

3

4

5

6

7

8

9

10

11

12

13

public class Main {

    public static void main(String[] args) {

        String a = "hello";

        String b =  new String("hello");

        String c =  new String("hello");

        String d = b.intern();

         

        System.out.println(a==b);

        System.out.println(b==c);

        System.out.println(b==d);

        System.out.println(a==d);

    }

}

  輸出結果爲(JDK版本 JDK6):

  

 

 

 

 

作者:wuxinliulei
鏈接:https://www.zhihu.com/question/29495158/answer/72534524
來源:知乎
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

String 字符串常量 since JDK1.0
java.lang.String public final class String


StringBuffer 字符串變量(線程安全) since JDK1.0
java.lang.StringBuffer public final class StringBuffer


StringBuilder 字符串變量(非線程安全)since JDK1.5
java.lang.StringBuilder public final class StringBuilder

全部是lang包下的類,全部是final的類,不能被繼承。

------------------------------------------------------------------

簡要的說,String 類型和 StringBuffer 類型的主要性能區別其實在於 String 是不可變的對象, 因此在每次對 String 類型進行改變的時候其實都等同於生成了一個新的 String 對象,然後將指針指向新的 String 對象,所以經常改變內容的字符串最好不要用 String ,因爲每次生成對象都會對系統性能產生影響,特別當內存中無引用對象多了以後, JVM 的 GC 就會開始工作,那速度是一定會相當慢的。(曾經有朋友遇到過打日誌使用+號的字符串導致頻繁Full GC的)

而如果是使用 StringBuffer 類則結果就不一樣了,每次結果都會對 StringBuffer 對象本身進行操作,而不是生成新的對象,再改變對象引用。
所以在一般情況下我們推薦使用 StringBuffer ,特別是字符串對象經常改變的情況下。而在某些特別情況下, String 對象的字符串拼接其實是被 JVM 解釋成了 StringBuffer 對象的拼接,所以這些時候 String 對象的速度並不會比 StringBuffer 對象慢,而特別是以下的字符串對象生成中, String 效率是遠要比 StringBuffer 快的:

String S1 = “This is only a” + “ simple” + “ test”;
StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);


你會很驚訝的發現,生成 String S1 對象的速度簡直太快了,而這個時候 StringBuffer 居然速度上根本一點都不佔優勢。其實這是 JVM 的一個把戲,在 JVM 眼裏,這個
String S1 = “This is only a” + “ simple” + “test”; 其實就是:
String S1 = “This is only a simple test”; 所以當然不需要太多的時間了。但大家這裏要注意的是,如果你的字符串是來自另外的 String 對象的話,速度就沒那麼快了,譬如:

String S2 = "This is only a";
String S3 = "simple";
String S4 = "test";
String S1 = S2 +S3 + S4;


這時候 JVM 會規規矩矩的按照原來的方式去做

在大部分情況下

 StringBuffer > String


StringBuffer
Java.lang.StringBuffer線程安全的可變字符序列。一個類似於 String 的字符串緩衝區,但不能修改。雖然在任意時間點上它都包含某種特定的字符序列,但通過某些方法調用可以改變該序列的長度和內容。
可將字符串緩衝區安全地用於多個線程。可以在必要時對這些方法進行同步,因此任意特定實例上的所有操作就好像是以串行順序發生的,該順序與所涉及的每個線程進行的方法調用順序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可重載這些方法,以接受任意類型的數據。每個方法都能有效地將給定的數據轉換成字符串,然後將該字符串的字符追加或插入到字符串緩衝區中。append 方法始終將這些字符添加到緩衝區的末端;而 insert 方法則在指定的點添加字符。
例如,如果 z 引用一個當前內容是“start”的字符串緩衝區對象,則此方法調用 z.append("le") 會使字符串緩衝區包含“startle”,而 z.insert(4, "le") 將更改字符串緩衝區,使之包含“starlet”。
在大部分情況下 StringBuilder > StringBuffer

java.lang.StringBuilder
java.lang.StringBuilder一個可變的字符序列是5.0新增的。此類提供一個與 StringBuffer 兼容的 API,但不保證同步。該類被設計用作 StringBuffer 的一個簡易替換,用在字符串緩衝區被單個線程使用的時候(這種情況很普遍)。如果可能,建議優先採用該類,因爲在大多數實現中,它比 StringBuffer 要快。兩者的方法基本相同。

 

StringBuffer:
是一個可變對象,當對他進行修改的時候不會像String那樣重新建立對象
它只能通過構造函數來建立,
StringBuffer sb = new StringBuffer();
note:不能通過付值符號對他進行付值. 
sb = "welcome to here!";//error
對象被建立以後,在內存中就會分配內存空間,並初始保存一個null.向StringBuffer
中付值的時候可以通過它的append方法.
sb.append("hello");
並且由於String 對象是不可變對象,每次操作Sting 都會重新建立新的對象來保存新的值.
這樣原來的對象就沒用了,就要被垃圾回收.這也是要影響性能的.

 

StringBuilder:

是一個可變對象,和StringBuffer相比,不是線程安全的,一般用在單個線程操作的時候(這種情況很普遍,所以一般優先選用StringBuilder),速度比StringBuffe快很多。

字符串連接操作中StringBuffer的效率要比String高:

String str = new String("welcome to ");

str += "here";

的處理步驟實際上是通過建立一個StringBuffer,讓侯調用append(),最後

再將StringBuffer toSting();

這樣的話String的連接操作就比StringBuffer多出了一些附加操作,當然效率上要打折扣.

看看以下代碼:
將26個英文字母重複加了5000次,

 

String tempstr = "abcdefghijklmnopqrstuvwxyz"; 
int times = 5000; 
long lstart1 = System.currentTimeMillis(); 
String str = ""; 
for (int i = 0; i < times; i++) 
{ 
	str += tempstr; 
} 
long lend1 = System.currentTimeMillis(); 
long time = (lend1 - lstart1); 
System.out.println(time);


可惜我的計算機不是超級計算機,得到的結果每次不一定一樣一般爲 46687左右。
也就是46秒。
我們再看看以下代碼

String tempstr = "abcdefghijklmnopqrstuvwxyz"; 
int times = 5000; 
long lstart2 = System.currentTimeMillis(); 
StringBuffer sb = new StringBuffer(); 
for (int i = 0; i < times; i++) 
{ 
    sb.append(tempstr); 
} 
long lend2 = System.currentTimeMillis(); 
long time2 = (lend2 - lstart2); 
System.out.println(time2); 


得到的結果爲 16 有時還是 0
所以結論很明顯,StringBuffer 的速度幾乎是String 上萬倍。當然這個數據不是很準確。因爲循環的次數在100000次的時候,差異更大。不信你試試。

根據上面所說:

str += "here";
的處理步驟實際上是通過建立一個StringBuffer,然後調用append(),最後
再將StringBuffer toSting();

所以str += "here";可以等同於

StringBuffer sb = new StringBuffer(str);
sb.append("here");
str = sb.toString();

所以上面直接利用"+"來連接String的代碼可以基本等同於以下代碼

  String tempstr = "abcdefghijklmnopqrstuvwxyz"; 
  int times = 5000; 
  long lstart2 = System.currentTimeMillis(); 
  String str = ""; 
  for (int i = 0; i < times; i++) { 
         StringBuffer sb = new StringBuffer(str); 
         sb.append(tempstr); 
         str = sb.toString(); 
   } 
   long lend2 = System.currentTimeMillis(); 
   long time2 = (lend2 - lstart2); 
        System.out.println(time2);


平均執行時間爲46922左右,也就是46秒。

總結: 如果在程序中需要對字符串進行頻繁的修改連接操作的話.使用StringBuffer性能會更好

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章