Java基礎之字符串及——String


Java基礎之字符串及——String

Java基礎之字符串操作——String

字符串

什麼是字符串?如果直接按照字面意思來理解就是多個字符連接起來組合成的字符序列。為了更好的理解以上的理論,我們先來解釋下字符序列,字符序列:把多個字符按照一定的順序排列起來;而字符序列就是作為字符串的內容而存在的。所以可以把字符串理解為:把多個字符按照一定的順序排列起來而構成的排列組合。

如果還是不好理解,沒有關係,我還有法寶。我們可以用烤串來比喻說明,可以把字符串看作是烤串,烤串上的每一塊肉都相當於是一個字符。把一塊塊肉按照肥瘦相間的順序排列並串起來便成了我們吃的烤串,同理,把多個字符按照一定的順序“串”起來就構成了字符串。


字符串的分類

,字符串分為可變的字符串不可變的字符串兩種;這裡的不可變與可變指的是字符串的對象還是不是同一個,會不會因為字符串對象內容的改變而創建新的對象。

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


String 類

String類表示不可變的字符串,當前String類對象創建完畢之後,該對象的內容(字符序列)是不變的,因為內容一旦改變就會創建一個一個新的對象。

String對象的創建:

  1. 方式一:通過字面量賦值創建,String s1 = “laofu”; 需要注意這裡是雙引號:“”,區別與字符char類型的單引號:‘’;
  2. 方式二:通過構造器創建, String s2 = new String(“laofu”);

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

下面就來一一解密:

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


Java基礎之字符串及——String

字符串對象在JVM中的分佈

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

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


方式一和方式二有何不同?

方式一:String s1 = “laofu”; 有可能只創建一個String對象,也有可能創建不創建String對象;如果在常量池中已經存在”laofu”,那麼對象s1會直接引用,不會創建新的String對象;否則,會先在常量池先創建常量”laofu”的內存空間,然後再引用。

方式二:String s2 = new String(“laofu”); 最多會創建兩個String對象,最少創建一個String對象。可使用new關鍵字創建對象是會在堆空間創建內存區域,這是第一個對象;然後對象中的字符串字面量可能會創建第二個對象,而第二個對象如方式一中所描述的那樣,是有可能會不被創建的,所以至少創建一個String個對象。


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

<code>String str = "laofu"; 

等價於

char[] cs = new char[]{'l','a','o','f','u'};/<code>


Java基礎之字符串及——String

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


String對象的空值:

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


字符串的比較:使用“==”和“equals”會有不同效果,詳情在之前的文章中分享過:

  1. 使用”==”號:用於比較對象引用的內存地址是否相同。
  2. 使用equals方法:在Object類中和”==”號相同,但在自定義類中,建議覆蓋equals方法去實現比較自己內容的細節;由於String類覆蓋已經覆蓋了equals方法,所以其比較的是字符內容。
Java基礎之字符串及——String

String equals方法的實現細節


所以可以這樣來判斷字符串非空:

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

如果上述兩個條件都滿足,說明字符串確實為空!


字符串拼接:Java中的字符串可以通過是“+”實現拼接,那麼代碼中字符串拼接在JVM中又是如何處理的呢?我們通過一個例子說明:通過比較拼接字符串代碼編譯前後的代碼來查看JVM對字符串拼接的處理。


Java基礎之字符串及——String

字符串拼接的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手冊。


Java基礎之字符串及——String

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,最後是String。StringBuilder和StringBuffer區別並不是很大,也有可能是測試次數還不夠吧。感興趣的小夥伴可以增加拼接次數來看看。代碼很簡單,就不展示出來了。

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


StringBuilder以及StringBuffer的區別

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

  • StringBuffer:StringBuffer中的方法都使用了synchronized修飾符,表示同步操作,在多線程併發的時候可以保證線程安全,但在保證線程安全的時候,對其性能有一定影響,會降低其性能。
  • StringBuilder:StringBuilder中的方法都沒有使用了synchronized修飾符,線程不安全,正因為如此,其性能較高。

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


Java基礎之字符串及——String

StringBuilder 無參構造器


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


Java基礎之字符串及——String

設置初始值的StringBuilder構造器


<code>//創建一個長度為80的char數組.

new StringBuilder(80);/<code>


StringBuilder的常用方法:

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


完結。

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


分享到:


相關文章: