java面試題----String、StringBuffer、StringBudder區別


面試題1 - 什么情況下用+運算符進行字符串連接比調用StringBuffer/StringBuilder對象的append方法連接字符串性能更好?

面試題2 - 請說出下面程序的輸出。

class StringEqualTest {

    public static void main(String[] args) {
        String s1 = "Programming"; String s2 = new String("Programming"); String s3 = "Program"; String s4 = "ming"; String s5 = "Program" + "ming"; String s6 = s3 + s4; System.out.println(s1 == s2); System.out.println(s1 == s5); System.out.println(s1 == s6); System.out.println(s1 == s6.intern()); System.out.println(s2 == s2.intern()); } }
返回值:
false
true
false
true
false


  今天在看面試題的時候注意到一個String的intern()方法,平常沒用過,只是見過這個方法,也沒去仔細看過這個方法。所以今天看了一下。個人覺得給String類中加入這個方法可能是為了提升一點點性能,因為從常量池取數據比從堆里面去數據要快一些。(個人感覺)

  API上的那幾句關於這個方法,其實總結一句就是調用這個方法之后把字符串對象加入常量池中,常量池我們都知道他是存在於方法區的,他是方法區的一部分,而方法區是線程共享的,所以常量池也就是線程共享的,但是他並不是線程不安全的,他其實是線程安全的,他僅僅是讓有相同值的引用指向同一個位置而已,如果引用值變化了,但是常量池中沒有新的值,那么就會新開辟一個常量結果來交給新的引用,而並非像線程不同步那樣,針對同一個對象,new出來的字符串和直接賦值給變量的字符串存放的位置是不一樣的,前者是在堆里面,而后者在常量池里面,另外,在做字符串拼接操作,也就是字符串相"+"的時候,得出的結果是存在在常量池或者堆里面,這個是根據情況不同不一定的,我寫了幾行代碼測試了一下。

  先上結果:

    1.直接定義字符串變量的時候賦值,如果表達式右邊只有字符串常量,那么就是把變量存放在常量池里面。

    2.new出來的字符串是存放在堆里面。

    3.對字符串進行拼接操作,也就是做"+"運算的時候,分2中情況:

      i.表達式右邊是純字符串常量,那么存放在棧里面。

      ii.表達式右邊如果存在字符串引用,也就是字符串對象的句柄,那么就存放在堆里面。

復制代碼
    String str1 = "aaa";
        String str2 = "bbb"; String str3 = "aaabbb"; String str4 = str1 + str2; String str5 = "aaa" + "bbb"; System.out.println(str3 == str4); // false System.out.println(str3 == str4.intern()); // true System.out.println(str3 == str5);// true
復制代碼

  結果:str1、str2、str3、str5都是存在於常量池,str4由於表達式右半邊有引用類型,所以str4存在於堆內存,而str5表達式右邊沒有引用類型,是純字符串常量,就存放在了常量池里面。其實Integer這種包裝類型的-128 ~ +127也是存放在常量池里面,比如Integer i1 = 10;Integer i2 = 10; i1 == i2結果是true,估計也是為了性能優化。

 

 

學習了String類和StringBuffer類,現在從三分面來總結一下String、StringBuffer、StringBudder三者的區別:

  • 是否可變:

String:底層利用字符數組保存字符串常量,是不可變的,因為String類的原碼中有:private final char value[];因為有final修飾,所以String類的對象是不可改變的。所以每次修String對象的值時,實際上是生成了一個新的對象,而指針指向了新的String對象;

StringBufferStringBudder底層是利用字符數組保存字符串變量的,在jdk1.7中它們都繼承了AbstractStringBuilder類,而在AbstractStringBuilder類中有char[] value;,所以這兩者對象是可變的;

  • 執行速度:一般情況StringBudder > StringBuffer > String

    String:因為String對象是不可變的,所以每次修String對象的時候,實際上是生成了一個新的對象,而指針指向了新的String對象;所以經常改變內容的字符串最好不要用 String ,因為每次生成對象都會對系統性能產生影響,特別當內存中無引用對象多了以后, JVM 的 GC 就會開始工作,那速度是一定會相當慢的。

    StringBuffer:而如果是使用 StringBuffer 類則結果就不一樣了,每次結果都會對 StringBuffer 對象本身進行操作,而不是生成新的對象,再改變對象引用。
    但是有特殊情況: String str = “abc” + “def”;StringBuffer Sb = new StringBuilder(“abc”).append(“ def”);這時這種情況下:String的速度就要比StringBuffer的速度要快,因為這時jvm認為String str = “abc” + “def”;其實是:String str = “abcdef”;所以速度當然很快,這里說的是當String保存的字符串中有其他String的字符串時,速度就會變慢。

  • 是否線程安全:

    String: String中的對象是不可變的,也就可以理解為常量,顯然線程安全。

    StringBuffer:是線程安全的,因為對方法加了同步鎖或者對調用的方法加了同步鎖,部分方法原碼如下:

 public synchronized int length() { return count; } public synchronized int capacity() { return value.length; } public synchronized void ensureCapacity(int minimumCapacity) { if (minimumCapacity > value.length) { expandCapacity(minimumCapacity); } }

StringBudder:並沒有對方法進行加同步鎖,所以是非線程安全的。部分方法原碼如下:

 private StringBuilder append(StringBuilder sb) { if (sb == null) return append("null"); int len = sb.length(); int newcount = count + len; if (newcount > value.length) expandCapacity(newcount); sb.getChars(0, len, value, count); count = newcount; return this; } public StringBuilder delete(int start, int end) { super.delete(start, end); return this; }
  • 對三者的使用意見:

1.如果要操作少量的數據用: String
2.單線程操作字符串緩沖區 下操作大量數據 :StringBuilder
3.多線程操作字符串緩沖區 下操作大量數據 : StringBuffer

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM