面試別再問我String了

碼上實戰發表於2019-03-28

閱讀原文:面試別再問我String了

字串廣泛應用 在Java 程式設計中,在 Java 中字串屬於物件,Java 提供了 String 類來建立和操作字串。

String 簡介

String定義:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {}
複製程式碼

為什麼設計為不可變類呢?

String設計為不可變類主要考慮到:效率和安全。

  • 效率:1.在早期的JVM實現版本中,被final修飾的方法會被轉為內嵌呼叫以提升執行效率。而從Java SE5/6開始,就漸漸擯棄這種方式了。因此在現在的Java SE版本中,不需要考慮用final去提升方法呼叫效率。只有在確定不想讓該方法被覆蓋時,才將方法設定為final。2.快取hashcode,String不可變,所以hashcode不變,這樣快取才有意義,不必重新計算。
  • 安全:String常被作為網路連線,檔案操作等引數型別,倘若可改變,會出現意想不到的結果。

測試掌握程度

為了不浪費你的時間,請看下面的題目,若你一目瞭然,可以跳過本文了。

public class Test {
    public static void main(String[] args) {
        String str1 = "HelloFlyapi";
        String str2 = "HelloFlyapi";
        String str3 = new String("HelloFlyapi");
        String str4 = "Hello";
        String str5 = "Flyapi";
        String str6 = "Hello" + "Flyapi";
        String str7 = str4 + str5;

        System.out.println("str1 == str2 result: " + (str1 == str2));

        System.out.println("str1 == str3 result: " + (str1 == str3));

        System.out.println("str1 == str6 result: " + (str1 == str6));

        System.out.println("str1 == str7 result: " + (str1 == str7));

        System.out.println("str1 == str7.intern() result: " + (str1 == str7.intern()));

        System.out.println("str3 == str3.intern() result: " + (str3 == str3.intern()));
    }
}


複製程式碼

String 的建立方式

從上面的題中你會知道,String的建立方式有兩種:

直接賦值

  • 此方式在方法區中字串常量池中建立物件
    String str = "flyapi";
    複製程式碼

構造器

  • 此方式在堆記憶體建立物件

    String str = new String();
    複製程式碼

分析

要理解String,那麼要了解JVM記憶體中的棧(stack)、堆(heap)和方法區。簡要圖如下:

![JVM簡記憶體分佈圖](images/JVM簡記憶體.png)
![JVM簡記憶體分佈圖](images/JVM簡記憶體.png)

  • str1 == str2

    String str1 = "HelloFlyapi";
    String str2 = "HelloFlyapi";
    
    System.out.println(str1 == str2); // true
    複製程式碼

當執行第一句時,JVM會先去常量池中查詢是否存在HelloFlyapi,當存在時直接返回常量池裡的引用;當不存在時,會在字元創常量池中建立一個物件並返回引用。

當執行第二句時,同樣的道理,由於第一句已經在常量池中建立了,所以直接返回上句建立的物件的引用。

  • str1 == str3

    String str1 = "HelloFlyapi";
    String str3 = new String("HelloFlyapi");
    
    System.out.println(str1 == str3); // false
    複製程式碼

執行第一句,同上第一句。

執行第二句時,會在堆(heap)中建立一個物件,當字元創常量池中沒有‘HelloFlyapi’時,會在常量池中也建立一個物件;當常量池中已經存在了,就不會建立新的了。

  • str1 == str6

    String str1 = "HelloFlyapi";
    String str6 = "Hello" + "Flyapi";
    
    System.out.println(str1 == str6); // true
    複製程式碼

由於"Hello"和"Flyapi"都是常量,編譯時,第二句會被自動編譯為‘String str6 = "HelloFlyapi";’

  • str1 == str7

    String str1 = "HelloFlyapi";
    String str4 = "Hello";
    String str5 = "Flyapi";
    String str7 = str4 + str5;
    
    System.out.println(str1 == str7); // false
    複製程式碼

其中前三句變數儲存的是常量池中的引用地址。

第四句執行時,JVM會在堆(heap)中建立一個以str4為基礎的一個StringBuilder物件,然後呼叫StringBuilder的append()方法完成與str5的合併,之後會呼叫toString()方法在堆(heap)中建立一個String物件,並把這個String物件的引用賦給str7。

常用方法

下面是 String 類支援的方法,更多詳細,參看 Java String API 文件:

方法 描述
char charAt(int index) 返回指定索引處的 char 值。
int compareTo(Object o) 把這個字串和另一個物件比較。
int compareTo(String anotherString) 按字典順序比較兩個字串。
boolean endsWith(String suffix) 測試此字串是否以指定的字尾結束。
boolean equals(Object anObject) 將此字串與指定的物件比較。
boolean equalsIgnoreCase(String anotherString) 將此 String 與另一個 String 比較,不考慮大小寫。
byte[] getBytes() 使用平臺的預設字符集將此 String 編碼為 byte 序列,並將結果儲存到一個新的 byte 陣列中。
byte[] getBytes(String charsetName) 使用指定的字符集將此 String 編碼為 byte 序列,並將結果儲存到一個新的 byte 陣列中。
int indexOf(int ch) 返回指定字元在此字串中第一次出現處的索引。
int indexOf(int ch, int fromIndex) 返回在此字串中第一次出現指定字元處的索引,從指定的索引開始搜尋。
int indexOf(String str) 返回指定子字串在此字串中第一次出現處的索引。
int indexOf(String str, int fromIndex) 返回指定子字串在此字串中第一次出現處的索引,從指定的索引開始。
String intern() 返回字串物件的規範化表示形式。
int lastIndexOf(int ch) 返回指定字元在此字串中最後一次出現處的索引。
int lastIndexOf(int ch, int fromIndex) 返回指定字元在此字串中最後一次出現處的索引,從指定的索引處開始進行反向搜尋。
int lastIndexOf(String str) 返回指定子字串在此字串中最右邊出現處的索引。
int lastIndexOf(String str, int fromIndex) 返回指定子字串在此字串中最後一次出現處的索引,從指定的索引開始反向搜尋。
int length() 返回此字串的長度。
boolean matches(String regex) 告知此字串是否匹配給定的正規表示式。
String replace(char oldChar, char newChar) 返回一個新的字串,它是通過用 newChar 替換此字串中出現的所有 oldChar 得到的。
String replaceAll(String regex, String replacement) 使用給定的 replacement 替換此字串所有匹配給定的正規表示式的子字串。
String replaceFirst(String regex, String replacement) 使用給定的 replacement 替換此字串匹配給定的正規表示式的第一個子字串。
String[] split(String regex) 根據給定正規表示式的匹配拆分此字串。
String[] split(String regex, int limit) 根據匹配給定的正規表示式來拆分此字串。
boolean startsWith(String prefix) 測試此字串是否以指定的字首開始。
boolean startsWith(String prefix, int toffset) 測試此字串從指定索引開始的子字串是否以指定字首開始。
String substring(int beginIndex) 返回一個新的字串,它是此字串的一個子字串。
String substring(int beginIndex, int endIndex) 返回一個新字串,它是此字串的一個子字串。
char[] toCharArray() 將此字串轉換為一個新的字元陣列。
String toLowerCase() 使用預設語言環境的規則將此 String 中的所有字元都轉換為小寫。
String toUpperCase() 使用預設語言環境的規則將此 String 中的所有字元都轉換為大寫。
String trim() 返回字串的副本,忽略前導空白和尾部空白。

String相關

由於String的不可變性導致,字串變更時效率低下,在之後得JDK版本中出現了StringBuilder和StringBuffer.

可變性 執行緒安全
String 不可變 安全
StringBuffer 可變 安全
StringBuilder 可變 非安全
  • 使用選擇
  1. 當有少量連線操作時,使用String
  2. 當單執行緒下有大量連線操作時,使用StringBuilder
  3. 當多執行緒下有大量連線操作時,使用StringBuffer

常見String面試題

  • String str = new String("abc")建立了多少個例項?

這個問題其實是不嚴謹的,但面試一般會遇到,所以我們要補充來說明。

類的載入和執行要分開來講: 建立了兩個

  1. 當載入類時,"abc"被建立並駐留在了字元創常量池中(如果先前載入中沒有建立駐留過)。
  2. 當執行此句時,因為"abc"對應的String例項已經存在於字串常量池中,所以JVM會將此例項複製到會在堆(heap)中並返回引用地址。

通過位元組碼我們可以看到:

原始碼:String str = new String("abc")

位元組碼:

    Code:
       0: new           #2                  // class java/lang/String
       3: dup
       4: ldc           #3                  // String abc
       6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)
       9: astore_1
      10: return
複製程式碼

執行時僅(#2)建立了一個物件。

關於這個面試題,可以看看一個超大牛的回答:rednaxelafx.iteye.com/blog/774673

給大家整理了一些面試文件和視訊,公眾號回覆:架構師 或 面試視訊

qrcode

本文優先釋出於微信公眾號:碼上實戰

qrcode

相關文章