「JAVA」細述合理建立字符串,分析字符串的底層存儲,你不應錯過

Java基礎之字符串及——String

Java基礎之字符串操做——Stringsegmentfault

字符串

什麼是字符串?若是直接按照字面意思來理解就是多個字符鏈接起來組合成的字符序列。爲了更好的理解以上的理論,咱們先來解釋下字符序列,字符序列:把多個字符按照必定的順序排列起來;而字符序列就是做爲字符串的內容而存在的。因此能夠把字符串理解爲:把多個字符按照必定的順序排列起來而構成的排列組合。數組

若是仍是很差理解,沒有關係,我還有法寶。咱們能夠用烤串來比喻說明,能夠把字符串看做是烤串,烤串上的每一塊肉都至關因而一個字符。把一塊塊肉按照肥瘦相間的順序排列並串起來便成了咱們吃的烤串,同理,把多個字符按照必定的順序「串」起來就構成了字符串。安全

字符串的分類,字符串分爲可變的字符串不可變的字符串兩種;這裏的不可變與可變指的是字符串的對象仍是不是同一個,會不會由於字符串對象內容的改變而建立新的對象。多線程

  • 不可變的字符串:當字符串對象建立完畢以後,該對象的內容(上述的字符序列)是不能改變的,一旦內容改變就會建立一個新的字符串對象;Java中的String類的對象就是不可變的。
  • 可變的字符串StringBuilder類和StringBuffer類的對象就是可變的;當對象建立完畢以後,該對象的內容發生改變時不會建立新的對象,也就是說對象的內容能夠發生改變,當對象的內容發生改變時,對象保持不變,仍是同一個。

String 類

String類表示不可變的字符串,當前String類對象建立完畢以後,該對象的內容(字符序列)是不變的,由於內容一旦改變就會建立一個一個新的對象。併發

String對象的建立:app

  1. 方式一:經過字面量賦值建立,String s1 = "laofu"; 須要注意這裏是雙引號:"",區別與字符char類型的單引號:''
  2. 方式二:經過構造器建立, String s2 = new String("laofu");;

以上兩種建立方式的對象在JVM中又是如何分佈的呢? 分別有什麼區別呢?性能

方式一和方式二在JVM中又是如何分佈?測試

字符串對象在JVM中的分佈

上圖中的常量池:用於存儲常量的地方內存區域,位於方法區中。常量池又分爲編譯常量池運行常量池兩種:優化

  • 編譯常量池:當把字節碼加載進JVM的時候,其中存儲的是字節碼的相關信息(如:當前執行到的字節碼行號等)。
  • 運行常量池:其中存儲的是代碼中的常量數據。

方式一和方式二有何不一樣?ui

  • 方式一:String s1 = "laofu"; 有可能只建立一個String對象,也有可能建立不建立String對象;若是在常量池中已經存在"laofu",那麼對象s1會直接引用,不會建立新的String對象;不然,會先在常量池先建立常量"laofu"的內存空間,而後再引用。
  • 方式二:String s2 = new String("laofu"); 最多會建立兩個String對象,最少建立一個String對象。可以使用new關鍵字建立對象是會在堆空間建立內存區域,這是第一個對象;而後對象中的字符串字面量可能會建立第二個對象,而第二個對象如方式一中所描述的那樣,是有可能會不被建立的,因此至少建立一個String對象。

字符串的本質,字符串在底層其實就是char[]char表示一個字符,好比:

String str = "laofu"; 
等價於 
char[] cs = new char[]{'l','a','o','f','u'};

字符串在底層其實就是char[]

String對象的空值:

  1. 對象引用爲空,即:String s1 = null; 此時s1沒有初始化,也在JVM中沒有分配內存空間。
  2. 對象內容爲空字符串, 好比:String s2 = ""; 此時對象s2已經初始化,值爲""JVM已經爲其分配內存空間。

字符串的比較:使用"==""equals"會有不一樣效果,詳情在以前的文章中分享過:[JAVA] 只知對象屬性,不知類屬性?就算類答應,static都不答應

  1. 使用==號:用於比較對象引用的內存地址是否相同。
  2. 使用equals方法:在Object類中和==號相同,但在自定義類中,建議覆蓋equals方法去實現比較本身內容的細節;因爲String類覆蓋已經覆蓋了equals方法,因此其比較的是字符內容。

String equals方法的實現細節

因此能夠這樣來判斷字符串非空:

  1. 對象引用不能爲空:s1 != null;;
  2. 字符內容不能爲空字符串(""):"".equals(s1);;

若是上述兩個條件都知足,說明字符串確實爲空!

字符串拼接:Java中的字符串能夠經過是"+"實現拼接,那麼代碼中字符串拼接在JVM中又是如何處理的呢?咱們經過一個例子說明:經過比較拼接字符串代碼編譯先後的代碼來查看JVM對字符串拼接的處理。

字符串拼接的JVM實現

經過上述例子不難發現,JVM會對字符串拼接作一些優化操做,若是字符串字面量之間的拼接,不管有多少個字符串,JVM都會同樣的處理;若是是對象之間拼接,或者是對象和字面量之間的拼接,亦或是方法執行結果參與拼接,String內部會使用StringBuilder先來獲取對象的值,而後使用append方法來執行拼接。由此能夠總結得出:

  1. 使用字符串字面量建立的字符串,也就是單獨使用""引號建立的字符串都是直接量,在編譯期就會將其存儲到常量池中;
  2. 使用new String("")建立的對象會存儲到堆內存中,在運行期才建立;
  3. 使用只包含直接量的字符串鏈接符如"aa" + "bb"建立的也是直接量,這樣的字符串在編譯期就能肯定,因此也會存儲到常量池中;
  4. 使用包含String直接量的字符串表達式(如"aa" + s1)建立的對象是運行期才建立的,對象存儲在堆中,由於其底層是創新了StringBuilder對象來實現拼接的;

5. 不管是使用變量,仍是調用方法來鏈接字符串,都只能在運行期才能肯定變量的值和方法的返回值,不存在編譯優化操做。

String 的經常使用API

這裏列舉了一些經常使用String API,更多的能夠查閱jdk使用手冊,作Java必定得學會查閱jdk手冊

jdk 手冊

String 的建立和轉換:

  • byte[] getBytes():把字符串轉換爲byte數組。
  • char[] toCharArray():把字符串轉換爲char數組。
  • String(byte[] bytes):把byte數組轉換爲字符串。
  • String(char[] value):把char數組轉換爲字符串。

獲取字符串信息

  • int length() :返回此字符串的長度。
  • char charAt(int index) :返回指定索引處的 char 值。
  • int indexOf(String str) :返回指定字符串在此字符串中首次(從最左邊算起)出現處的索引。
  • int lastIndexOf(String str) :返回指定字符串在此字符串中最後(最右邊算起)出現處的索引。

字符串比較判斷

  • boolean equals(Object anObject): 將此字符串與指定的對象比較。
  • boolean equalsIgnoreCase(String anotherString): 將此 String 與另外一個 String 作忽略大小寫的比較。
  • boolean contentEquals(CharSequence cs): 將此字符串與指定的 CharSequence 比較,比較的是內容;

    • ps:String類是現實了CharSequence(字符序列)接口的。

字符串大小寫轉換:調用方法的字符串就是當前字符串

  • String toUpperCase(): 把當前字符串轉換爲大寫
  • String toLowerCase(): 把當前字符串轉換爲小寫

StringBuilder/StringBuffer

先來分別使用String/StringBuilder/StringBuffer來拼接30000次字符串,對比各自損耗的時間,通過測試發現:

String作字符串拼接的時候,耗時最高,性能極低,緣由是String內容是不可變的,每次內容改變都會在內存中建立新的對象。

性能最好的是StringBuilder,其次是StringBuffer,最後是StringStringBuilderStringBuffer區別並非很大,也有多是測試次數還不夠吧。感興趣的小夥伴能夠增長拼接次數來看看。代碼很簡單,就不展現出來了。

因此在開發中拼接字符串時,優先使用StringBuffer/StringBuilder,不到萬不得已,不要輕易使用String

StringBuilder以及StringBuffer的區別

StringBufferStringBuilder都表示可變的字符串,兩種’的功能方法都是相同的。但惟一的區別:

  • StringBufferStringBuffer中的方法都使用了synchronized修飾符,表示同步操做,在多線程併發的時候能夠保證線程安全,但在保證線程安全的時候,對其性能有必定影響,會下降其性能。
  • StringBuilderStringBuilder中的方法都沒有使用了synchronized修飾符,線程不安全,正由於如此,其性能較高。

對併發安全沒有很高要求的狀況下,建議使用StringBuilder,由於其性能很高。像這樣的狀況會較多些。使用StringBuilder無參數的構造器,在底層建立了一個長度爲16char數組:

StringBuilder 無參構造器

此時該數組只能存儲16個字符,若是超過了16個字符,會自動擴容(建立長度更大的數組,再把以前的數組拷貝到新數組),此時性能極低;若是事先知道大概須要存儲多少字符,能夠經過構造器來設置字符的初始值:

設置初始值的StringBuilder構造器

//建立一個長度爲80的char數組.

new StringBuilder(80);

StringBuilder的經常使用方法:

  • append(Object val):追加任意類型數據到當前StringBuilder對象中。
  • StringBuilder deleteCharAt(int index) :刪除字符串中指定位置的字符。

完結。雖然老夫不正經,但老夫一身的才華

相關文章
相關標籤/搜索