面試別再問我String了

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

字符串普遍應用 在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常被做爲網絡鏈接,文件操做等參數類型,假若可改變,會出現意想不到的結果。

測試掌握程度

爲了避免浪費你的時間,請看下面的題目,若你一目瞭然,能夠跳過本文了。api

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 == str2bash

    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
相關文章
相關標籤/搜索