您好,登錄后才能下訂單哦!
本篇內容主要講解“String、String Builder、String Buffer的區別是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“String、String Builder、String Buffer的區別是什么”吧!
小宅:這個太簡單了吧,這是看不起我?
從可變性來講String的是不可變的,StringBuilder,StringBuffer的長度是可變的。
從運行速度上來講StringBuilder > StringBuffer > String。
從線程安全上來StringBuilder是線程不安全的,而StringBuffer是線程安全的。
??所以 String:適用于少量的字符串操作的情況,StringBuilder:適用于單線程下在字符緩沖區進行大量操作的情況,StringBuffer:適用多線程下在字符緩沖區進行大量操作的情況。
面試官:為什么String的是不可變的?
小宅:因為存儲數據的char數組是使用final進行修飾的,所以不可變。
public class Demo { public static void main(String[] args) { String str = "不一樣的"; str = str + "科技宅"; System.out.println(str); } }
很明顯上面運行的結果是:不一樣的科技宅。
我們先使用javac Demo.class
進行編譯,然后反編譯javap -verbose Demo
得到如下結果:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: ldc #2 // String 不一樣的 2: astore_1 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 10: aload_1 11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 14: ldc #6 // String 科技宅 16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: astore_1 23: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 26: aload_1 27: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: return
??我們可以發現,在使用+
進行拼接的時候,實際上jvm是初始化了一個StringBuilder
進行拼接的。相當于編譯后的代碼如下:
public class Demo { public static void main(String[] args) { String str = "不一樣的"; StringBuilder builder =new StringBuilder(); builder.append(str); builder.append("科技宅"); str = builder.toString(); System.out.println(str); } }
我們可以看下builder.toString();
的實現。
@Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
??很明顯toString
方法是生成了一個新的String
對象而不是更改舊的str
的內容,相當于把舊str
的引用指向的新的String
對象。這也就是str
發生變化的原因。
分享我碰到過的一道面試題,大家可以猜猜答案是啥?文末有解析哦
public class Demo { public static void main(String[] args) { String str = null; str = str + ""; System.out.println(str); } }
小宅:不可以,因為String類使用final關鍵字進行修飾,所以不能被繼承,并且StringBuilder,StringBuffer也是如此都被final關鍵字修飾。
小宅:這是因為在StringBuffer
類內,常用的方法都使用了synchronized
進行同步所以是線程安全的,然而StringBuilder
并沒有。這也就是運行速度StringBuilder
> StringBuffer
的原因了。
synchronized
關鍵字 ,那能講講synchronized
的表現形式嘛?小宅:
對于普通同步方法 ,鎖是當前實例對象。
對于靜態同步方法,鎖是當前類的class對象。
對于同步方法塊,鎖是Synchonized括號配置的對象。
synchronized
的原理嘛?小宅:synchronized
是一個重量級鎖,實現依賴于JVM
的 monitor
監視器鎖。主要使用monitorenter
和monitorexit
指令來實現方法同步和代碼塊同步。在編譯的是時候,會將monitorexit
指令插入到同步代碼塊的開始位置,而monitorexit
插入方法結束處和異常處,并且每一個monitorexit
都有一個與之對應的monitorexit
。
??任何對象都有一個monitor
與之關聯,當一個monitor
被持有后,它將被處于鎖定狀態,線程執行到monitorenter
指令時間,會嘗試獲取對象所對應的monitor
的所有權,即獲取獲得對象的鎖,由于在編譯期會將monitorexit
插入到方法結束處和異常處,所以在方法執行完畢或者出現異常的情況會自動釋放鎖。
synchronized
是個重量級鎖,那它的優化有了解嘛?小宅:為了減少獲得鎖和和釋放鎖帶來的性能損耗引入了偏向鎖、輕量級鎖、重量級鎖來進行優化,鎖升級的過程如下:
??首先是一個無鎖的狀態,當線程進入同步代碼塊的時候,會檢查對象頭內和棧幀中的鎖記錄里是否存入存入當前線程的ID,如果沒有使用CAS
進行替換。以后該線程進入和退出同步代碼塊不需要進行CAS
操作來加鎖和解鎖,只需要判斷對象頭的Mark word
內是否存儲指向當前線程的偏向鎖。如果有表示已經獲得鎖,如果沒有或者不是,則需要使用CAS
進行替換,如果設置成功則當前線程持有偏向鎖,反之將偏向鎖進行撤銷并升級為輕量級鎖。
??輕量級鎖加鎖過程,線程在執行同步塊之前,JVM會在當前線程的棧幀中創建用于存儲鎖記錄的空間,并將對象頭的Mark Word
復制到鎖記錄(Displaced Mark Word
)中,然后線程嘗試使用CAS
將對象頭中的Mark Word
替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,反之表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲得鎖。
??輕量級鎖解鎖過程,解鎖時,會使用CAS將Displaced Mark Word
替換回到對象頭,如果成功,則表示競爭沒有發生,反之則表示當前鎖存在競爭鎖就會膨脹成重量級鎖。
升級過程流程圖
白話一下:
??可能上面的升級過程和升級過程圖,有點難理解并且還有點繞。我們先可以了解下為什么會有鎖升級這個過程?HotSpot的作者經過研究發現,大多數情況下鎖不僅不存在多線程競爭,而且總是由同一個線程多次獲得。為了避免獲得鎖和和釋放鎖帶來的性能損耗引入鎖升級這樣一個過程。理解鎖升級這個流程需要明確一個點:發生了競爭才鎖會進行升級并且不能降級。
??我們以兩個線程T1,T2執行同步代碼塊來演示鎖是如何膨脹起來的。我們從無鎖的狀態開始 ,這個時候T1進入了同步代碼塊,判斷當前鎖的一個狀態。發現是一個無鎖的狀態,這個時候會使用CAS
將鎖記錄內的線程Id指向T1并從無鎖狀態變成了偏向鎖。運行了一段時間后T2進入了同步代碼塊,發現已經是偏向鎖了,于是嘗試使用CAS
去嘗試將鎖記錄內的線程Id改為T2,如果更改成功則T2持有偏向鎖。失敗了說明存在競爭就升級為輕量級鎖了。
??可能你會有疑問,為啥會失敗呢?我們要從CAS
操作入手,CAS
是Compare-and-swap(比較與替換)的簡寫,是一種有名的無鎖算法。CAS需要有3個操作數,內存地址V,舊的預期值A,即將要更新的目標值B,換句話說就是,內存地址0x01存的是數字6我想把他變成7。這個時候我先拿到0x01的值是6,然后再一次獲取0x01的值并判斷是不是6,如果是就更新為7,如果不是就再來一遍之道成功為止。這個主要是由于CPU的時間片原因,可能執行到一半被掛起了,然后別的線程把值給改了,這個時候程序就可能將錯誤的值設置進去,導致結果異常。
??簡單了解了一下CAS
現在讓我們繼續回到鎖升級這個過程,T2嘗試使用CAS
進行替換鎖記錄內的線程ID,結果CAS
失敗了這也就意味著,這個時候T1搶走了原本屬于T2的鎖,很明顯這一刻發生了競爭所以鎖需要升級。在升級為輕量級鎖前,持有偏向鎖的線程T1會被暫停,并檢查T1的狀態,如果T1處于未活動的狀態/已經退出同步代碼塊的時候,T1會釋放偏向鎖并被喚醒。如果未退出同步代碼塊,則這個時候會升級為輕量級鎖,并且由T1獲得鎖,從安全點繼續執行,執行完后對輕量級鎖進行釋放。
??偏向鎖的使用了出現競爭了才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。并且偏向鎖的撤銷需要等待全局安全點(這個時間點沒有任何正在執行的字節碼)。
??T1由于沒有人競爭經過一段時間的平穩運行,在某一個時間點時候T2進來了,產生使用CAS
獲得鎖,但是發現失敗了,這個時候T2會等待一下(自旋獲得鎖),由于競爭不是很激烈所以等T1執行完后,就能獲取到鎖并進行執行。如果長時間獲取不到鎖則就可能發生競爭了,可能出現了個T3把原本屬于T2的輕量級鎖給搶走了,這個時候就會升級成重量級鎖了。
面試官:內心OS:竟然沒問倒他,看來讓他培訓是沒啥希望了,讓他回去等通知吧 。
??小宅是吧,你的水平我這邊基本了解了,我對你還是比較滿意的,但是我們這邊還有幾個候選人還沒面試,沒辦法直接給你答復,你先回去等通知吧。
小宅:好的好的,謝謝面試官,我這邊先回去了。多虧我準備的充分,全回答上來了,應該能收到offer了吧。
public class Demo { public static void main(String[] args) { String str = null; str = str + ""; System.out.println(str); } }
答案是 null,從之前我們了解到使用+
進行拼接實際上是會轉換為StringBuilder
使用append
方法進行拼接。所以我們看看append
方法實現邏輯就明白了。
public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; }
private AbstractStringBuilder appendNull() { int c = count; ensureCapacityInternal(c + 4); final char[] value = this.value; value[c++] = 'n'; value[c++] = 'u'; value[c++] = 'l'; value[c++] = 'l'; count = c; return this; }
從代碼中可以發現,如果傳入的字符串是null
時,調用appendNull
方法,而appendNull
會返回null。
到此,相信大家對“String、String Builder、String Buffer的區別是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。