真的懂Java的String嗎?

喝水會長肉發表於2021-12-07

1.String的特性

1.1不變性

我們常常聽人說,HashMap 的 key 建議使用不可變類,比如說 String 這種不可變類。這裡說不可變指的是類值一旦被初始化,就不能再被改變了,如果被修改,將會是新的類,我們寫個demo 來演示一下。


public 
class 
test 
{

    public static void main ( String [ ] args ) {
       String str = "hello" ;
       str =str + "world" ;
    }
}

從程式碼上來看,s 的值好像被修改了,但從 debug 的日誌來看,其實是 s 的記憶體地址已經被修了,也就說 s =“world” 這個看似簡單的賦值,其實已經把 s 的引用指向了新的 String,debug 截圖顯示記憶體地址已經被修改,兩張截圖如下,我們可以看到標紅的地址值已經修改了。

真的懂Java的String嗎?


真的懂Java的String嗎?

用示意圖來表示堆記憶體,即見下圖。

真的懂Java的String嗎?

我們可以看下str的地址已經改了,說了生成了兩個字串,String類的官方註釋為 Strings are constant; their values cannot be changed after they are created. 簡單翻譯下為 字串是常量;它們的值在建立後不能更改。

下面為String的相關程式碼,如下程式碼,我們可以看到:

1. String 被 final 修飾,說明 String 類絕不可能被繼承了,也就是說任何對 String 的操作方法,都不會被繼承覆寫,即可保證雙親委派機制,保證基類的安全性。
2. String 中儲存資料的是一個 char 的陣列 value。我們發現 value 也是被 final 修飾的,也就是說 value 一旦被賦值,記憶體地址是絕對無法修改的,而且 value 的許可權是 private 的,外部絕對訪問不到,String沒有開放出可以對 value 進行賦值的方法,所以說 value 一旦產生,記憶體地址就根本無法被修改。

 
/** The value is used for character storage. */

    private final char value [ ] ;

    /** Cache the hash code for the string */
    private int hash ; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = - 6849794470754667710L ;

1.2相等判斷

相等判斷邏輯寫的很清楚明瞭,如果有人問如何判斷兩者是否相等時,我們可以從兩者的底層結構出發,這樣可以迅速想到一種貼合實際的思路和方法,就像 String 底層的資料結構是 char 的陣列一樣,判斷相等時,就挨個比較 char 陣列中的字元是否相等即可。

(這裡先挖個坑,攜程問過類似題目)


public boolean 
equals
(
Object anObject
) 
{

      //如果地址相等,則直接返回true      
      if ( this == anObject ) {
            return true ;
        }
        //如果為String字串,則進行下面的邏輯判斷
        if (anObject instanceof String ) {
            //將物件轉化為String
           String anotherString = (String )anObject ;
            //獲取當前值的長度
           int n = value .length ;
            //先比較長度是否相等,如果長度不相等,這兩個肯定不相等
            if ( n == anotherString .value .length ) {
               char v1 [ ] = value ;
               char v2 [ ] = anotherString .value ;
               int i = 0 ;
                //while迴圈挨個比較每個char
                while ( n -- != 0 ) {
                    if (v1 [i ] != v2 [i ] )
                        return false ;
                   i ++ ;
                }
                return true ;
            }
        }
        return false ;
    }

相等邏輯的流程圖如下,我們可以看到整個流程還是很清楚的。

真的懂Java的String嗎?


1.3替換操作

替換在平時工作中也經常使用,主要有 replace 替換所有字元、replaceAll 批量替換字串、replaceFirst這三種場景。
下面寫了一個 demo 演示一下三種場景:

 
public 
static 
void 
main
(
String
[
] args
) 
{

       String str = "hello word !!" ;
       System .out . println ( "替換之前 :" + str ) ;
       str = str . replace ( 'l' , 'd' ) ;
       System .out . println ( "替換所有字元 :" + str ) ;
       str = str . replaceAll ( "d" , "l" ) ;
       System .out . println ( "替換全部 :" + str ) ;
       str = str . replaceFirst ( "l" , "" ) ;
       System .out . println ( "替換第一個 l :" + str ) ;
    }

輸出的結果是:

真的懂Java的String嗎?

這邊要注意一點是 replacereplaceAll的區別, 不是替換和替換所有的區別哦。

而是replaceAll支援 正規表示式,因此會對引數進行解析(兩個引數均是),如replaceAll("\\d", "*"),而replace則不會,replace("\\d","*")就是替換"\\d"的字串,而不會解析為正則。

1.4 intern方法

String.intern() 是一個 Native 方法,即是c和c++與底層互動的程式碼,它的作用(在

JDK1.6和1.7操作不同

)是:

如果執行時常量池中已經包含一個等於此 String 物件內容的字串,則直接返回常量池中該字串的引用;

如果沒有, 那麼

在jdk1.6中,將此String物件新增到常量池中,然後返回這個String物件的引用(此時引用的串在常量池)。

在jdk1.7中,放入一個引用,指向堆中的String物件的地址,返回這個引用地址(此時引用的串在堆)。

 
/**

    * Returns a canonical representation for the string object.
    * <p>
    * A pool of strings, initially empty, is maintained privately by the
    * class {@code String}.
    * <p>
    * When the intern method is invoked, if the pool already contains a
    * string equal to this {@code String} object as determined by
    * the {@link #equals(Object)} method, then the string from the pool is
    * returned. Otherwise, this {@code String} object is added to the
    * pool and a reference to this {@code String} object is returned.
    * <p>
    * It follows that for any two strings {@code s} and {@code t},
    * {@code s.intern() == t.intern()} is {@code true}
    * if and only if {@code s.equals(t)} is {@code true}.
    * <p>
    * All literal strings and string-valued constant expressions are
    * interned. String literals are defined in section 3.10.5 of the
    * <cite>The Java™ Language Specification</cite>.
    *java學習交流:737251827  進入可領取學習資源及對十年開發經驗大佬提問,免費解答!
    * @return  a string that has the same contents as this string, but is
    *          guaranteed to be from a pool of unique strings.
    */
   
public native String intern ( ) ;

如果看上面看不懂,我們來看下一下具體的例子,並來分析下。


public 
static 
void 
main
(
String
[
] args
) 
{

       String s1 = new String ( "學習Java" ) ;
       s1 . intern ( ) ;
       String s2 = "學習Java" ;
       System .out . println (s1 == s2 ) ;

       String s3 = new String ( "學習Java" ) + new String ( "test" ) ;
       s3 . intern ( ) ;
       String s4 = "學習Javatest" ;
       System .out . println (s3 == s4 ) ;

    }

我們來看下結果,實際的列印資訊如下。

真的懂Java的String嗎?

為什麼顯示這樣的結果,我們來看下。所以在 jdk7 的版本中,字串常量池已經從方法區移到正常的堆 區域了。

  • 第一個false: 第一句程式碼String s1 = new String("學習Java");生成了2個物件。常量池中的“學習Java” 和堆中的字串物件。 s1.intern(); 這一句是 s1 物件去常量池中尋找後,發現 “學習Java的小姐姐” 已經在常量池裡了。接下來 String s2 = "學習Java的 "; 這句程式碼是生成一個 s2的引用指向常量池中的“學習Java”物件。 結果就是 s 和 s2 的引用地址明顯不同,所以為列印結果是false。

  • 第二個true:先看 s3和s4字串。 String s3 = new String("學習Java") + new String("test");,這句程式碼中現在生成了3個物件,是字串常量池中的“學習Java” ,"test"和堆 中的 s3引用指向的物件。此時s3引用物件內容是”學習Javatest”,但此時常量池中是沒有 “學習Javatest”物件的,接下來 s3.intern();這一句程式碼,是將 s3中的“學習Javatest”字串放入 String 常量池中,因為此時常量池中不存在“學習Javatest”字串,常量池不需要再儲存一份物件了,可以直接儲存堆中的引用。這份引用指向 s3 引用的物件。 也就是說引用地址是相同的。最後 String s4 = "學習Javatest "; 這句程式碼中”學習Javatest”是顯示宣告的,因此會直接去常量池中建立,建立的時候發現已經有這個物件了,此時也就是指向 s3 引用物件的一個引用。所以 s4 引用就指向和 s3 一樣了。因此最後的比較  s3 == s4 是 true。

真的懂Java的String嗎?

我們再看下,如果把上面的兩行程式碼調整下位置,列印結果是不是不同。

  
public 
static 
void 
main
(
String
[
] args
) 
{

       String s1 = new String ( "學習Java" ) ;
       String s2 = "學習Java" ;
       s1 . intern ( ) ;
       System .out . println (s1 == s2 ) ;

       String s3 = new String ( "學習Java" ) + new String ( "test" ) ;
       String s4 = "學習Javatest" ;
       s3 . intern ( ) ;
       System .out . println (s3 == s4 ) ;
//java學習交流:737251827  進入可領取學習資源及對十年開發經驗大佬提問,免費解答!

    }


真的懂Java的String嗎?

第一個false: s1 和 s2 程式碼中, s1.intern();,這一句往後放也不會有什麼影響了,因為物件池中在執行第一句程式碼 String s = new String("學習Java");的時候已經生成“ 學習Java”物件了。下邊的s2宣告都是直接從常量池中取地址引用的。 s 和 s2 的引用地址是不會相等的。

**第二個false:**與上面唯一的區別在於  s3.intern(); 的順序是放在 String s4 = "學習Javatest";後了。這樣,首先執行 String s4 = "學習Javatest";宣告 s4 的時候常量池中是不存在“ 學習Javatest”物件的,執行完畢後,“ 學習Javatest“物件是 s4 宣告產生的新物件。然後再執行 s3.intern();時,常量池中“ 學習Javatest”物件已經存在了,因此 s3 和 s4 的引用是不同的。

2. String、StringBuilder和StringBuffer

2.1 繼承結構

2.2 主要區別

1)String是不可變字元序列,StringBuilder和StringBuffer是可變字元序列。
2)執行速度StringBuilder > StringBuffer > String。
3)StringBuilder是非執行緒安全的,StringBuffer是執行緒安全的。




來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70010294/viewspace-2846286/,如需轉載,請註明出處,否則將追究法律責任。

相關文章