欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java字符串原理分析之String是否可變

 更新時間:2023年05月17日 10:08:09   作者:一一哥Sun  
當我們在求職時,面試官很喜歡問我們關于String的一些原理性知識,比如String的不可變性、字符串的內存分配等,為了讓大家更好地應對面試,并理解String的底層設計,接下來會給大家聊聊String的一些原理,比如String為什么具有不可變性,需要的朋友可以參考下

一. Spring源碼中的final關鍵詞

為了弄清楚String為什么具有不可變性,我們先來看看String的源碼,尤其是源碼中帶有final關鍵詞的地方。

1. final的特點

為了更好地理解String相關的內容,在閱讀String源碼之前,我們先來復習一下final關鍵詞有哪些特點,因為在String中會涉及到很多final相關的內容。

final關鍵詞修飾的類不可以被其他類繼承,但是該類本身可以繼承其他類,通俗的說就是這個類可以有父類,但是不能有子類;

final關鍵詞修飾的方法不可以被覆蓋重寫,但是可以被繼承使用;

final關鍵詞修飾的基本數據類型變量稱為常量,只能被賦值一次;

final關鍵詞修飾的引用數據類型的變量值為地址值,地址值不能改變,但是地址內的數據對象可以被改變;

final關鍵詞修飾的成員變量,需要在創(chuàng)建對象前賦值,否則會報錯(即需要在定義時直接賦值,如果是在構造方法中賦值,則多個構造方法均需賦值)。

復習了final的特點之后,接下來我們就可以閱讀String的源碼了。

2. String源碼解讀

接下來就請大家請跟著小編來看看String源碼中關于不可變性的內容吧。

2.1 final修飾的String類

/**
 * ......其他略......
 *
 * Strings are constant; their values cannot be changed after they
 * are created. String buffers support mutable strings.
 * Because String objects are immutable they can be shared. For example:
 * 
 * ......其他略......
 */
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    ......

先對上面的源碼及其注釋進行簡單的解釋:

  • final:請參考第1小節(jié)對final特點的介紹;
  • Serializable:用于序列化;
  • Comparable:默認的比較器;
  • CharSequence: 提供對字符序列進行統(tǒng)一、只讀的操作。

從這一段源碼及注釋中,我們可以得出如下結論:

  • String類用final關鍵字修飾,說明String不可被繼承;
  • String字符串是常量,字符串的值一旦被創(chuàng)建,就不能被改變;
  • String字符串緩沖區(qū)支持可變字符串;
  • 因為String對象是不可變的,所以它們是可以被共享的。

2.2 final修飾的value[]屬性

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    ......

從源碼中可以看出,value[]是一個私有的字符數組,String類其實就是通過這個char數組來保存字符串內容的。簡單的說,我們定義的字符串會被拆成一個一個的字符,這些字符都被存放在這個value字符數組里面。

這里的value[]數組被final修飾,初始化之后就不能再被更改。但是大家注意,我們這里說的value[]不可變,指的是value的引用地址不可變,但是value數組里面的數據元素其實是可變的! 這是因為value是數組類型,根據我們之前學過的知識,value的引用地址會分配在棧中 ,而其對應的數據是在常量池中保存的。所以我們說String不可變,指的就是value在棧中的引用地址不可變,而不是說常量池中數組本身的數據元素不可變。

另外我們要注意,Java中的字符串常量池,用來存儲字符串字面量! 但是由于JDK版本的不同,常量池的位置也不同:

JDK 6 及以下版本的字符串常量池是在方法區(qū)(Perm Gen)中,此時常量池中存儲的是字符串對象;在 JDK 8.0 中,方法區(qū)(永久代被元空間取代了;

JDK 7、8以后的字符串常量池被轉移到了堆中,此時常量池存儲的就是字符串對象的引用,而不是字符串對象本身。

至此,就帶各位把String類中的核心源碼分析完了,接下來我們再進一步分析String不可變的原因,及其他底層原理設計。

二. String的不可變性

1. 實驗案例

了解了上面的這些核心源碼之后,接下來再帶各位來驗證一下,看看String到底能不能變!我先給各位來一段案例代碼,代碼案例如下圖所示。

結果s的內容變了,好像是啪啪打臉了????。?!咋回事,不是說了String不可變嗎?怎么這么快就翻車打臉了?別急,讓我們好好來分析一下。

2. 結果剖析

首先我們從結果上來看String s 變量的結果好像改變了,但為什么我們又說String是不可變的呢?

要想明白這個問題,我們得先弄清楚一個點,即引用和值的區(qū)別!在上面的代碼中,我們先是創(chuàng)建了一個 "yiyige" 為內容的字符串引用s,s其實先是指向了value對象,而value對象則指向存儲了 "y,i,y,i,g,e" 字符的字符數組。因為value被final修飾,所以value的值不可被更改。因此,上面代碼中改變的其實是s的引用指向,而不是改變了String對象的值 。 換句話說,上面實例中 s的值 只是 value的引用地址,并不是String內容本身!當我們執(zhí)行 s = "yyg"; 語句時,Java中會創(chuàng)建一個新的字面量對象 "yyg",而原來的 "yiyige" 字面量對象依然存在于內存的intern緩存池中。 在Java中,因為數組也是對象, 所以value中存儲的也只是一個引用,它指向一個真正的數組對象。在執(zhí)行了String s = “yiyige”; 這句代碼之后,真正的內存布局應該是下圖這樣的:

因為value是String封裝的字符數組,value中的所有字符都屬于String這個對象。由于value是private的,且沒有提供setValue等公共方法來修改這個value值,所以在String類的外部是無法修改value值的,也就是說一旦初始化就不能被修改。此外,value變量是final的, 也就是說在String類內部,一旦這個值初始化了,value這個變量所引用的地址就不會改變了,即一直引用同一個對象。正是基于這一層,所以說String對象是不可變的對象。 但其實value所引用對象的內容完全可以發(fā)生改變,我們可以利用反射來消除String類對象的不可變特性

所以String的不可變性,指的是value在棧中的引用地址不可變,而不是說常量池中array本身的數據元素不可變!

而String對象的改變實際上是通過內存地址的 “斷開-連接” 變化來完成的,這個過程中原字符串中的內容并沒有任何的改變。String s = "yiyige"; 和 s = "yyg"; 實質上是開辟了2個內存空間,s 只是由原來指向 "yiyige" 變?yōu)橹赶?"yyg" 而已,而其原來的字符串內容,是沒有改變的,如下圖所示。

因此,我們在以后的開發(fā)中,如果要經常修改字符串的內容,請盡量少用String,因為字符串的指向“斷開-連接”會大大降低性能,建議使用:StringBuilder、StringBuffer。

那么String一定不可變嗎?有沒有辦法讓String真的可變呢?我們繼續(xù)往下學習!

三. String真的不可變嗎?

1. 實驗案例

我在前面的章節(jié)中給大家說,String的不可變,其實指的是String類中value屬性在棧中的引用地址不可變,而不是說常量池中array本身的數據元素不可變!也就是說String字符串的內容其實是可變的!那怎么實現(xiàn)呢?利用反射就可以實現(xiàn),我們通過一個案例來證明一下。

try {
    String str = "yyg";
    System.out.println("str=" + str + ", 唯一性hash值=" + System.identityHashCode(str));

    Class stringClass = str.getClass();
    //獲取String類中的value屬性
    Field field = stringClass.getDeclaredField("value");
    //設置私有成員的可訪問性,進行暴力反射
    field.setAccessible(true);
    //獲取value數組中的內容
    char[] value = (char[]) field.get(str);
    System.out.println("value=" + Arrays.toString(value));

    value[1] = 'z';
    System.out.println("str=" + str + ", 唯一性hash值=" + System.identityHashCode(str));
} catch (NoSuchFieldException | IllegalAccessException e) {
    e.printStackTrace();
}

2. 結果剖析

上面案例的執(zhí)行結果如下圖所示:

我們可以看到,String字符串的字符數組可以通過反射進行修改,導致字符串的“內容”真的發(fā)生了變化! 并且我們又利用底層的java.lang.System#identityHashCode()方法(不管是否重寫了hashCode方法)獲取了對象的唯一哈希值,該方法獲取的hash值與hashCode()方法是一樣的。我們可以看到兩個字符串的唯一性hash值是一樣的,證明字符串引用地址沒有發(fā)生改變!所以在這里,我們并不是像之前那樣創(chuàng)建了一個新的String字符串,而是真的改變了String的內容。這個代碼案例進一步說明,String類的不可變指的是中value屬性在棧中的引用地址不可變,而不是說常量池中array本身的數據元素不可變!也就是說String字符串的內容其實是可變的!

四. 結語

String作為Java中使用最為廣泛的一個類,之所以設計為不可變,主要是出于效率與安全性方面考慮。這種設計有優(yōu)點,也有缺點。

1. 不可變性的優(yōu)點

只有當字符串是不可變的,字符串池才有可能實現(xiàn)。 字符串池的實現(xiàn)可以在運行時節(jié)約很多heap空間,因為不同的字符串引用都可以指向池中的同一個字符串。但如果字符串是可變的,如果一個引用變量改變了字符串的值,那么其它指向這個值的變量內容也會跟著一起改變。

如果字符串是可變的,那么可能會引起很嚴重的安全問題。 譬如,數據庫的用戶名、密碼都是以字符串的形式傳入數據庫,以獲得數據庫的連接;或者在socket編程中,主機名和端口都是以字符串的形式傳入。因為字符串是不可變的,所以它的值是不可改變的,否則黑客們可以鉆到空子,改變字符串指向的對象值,造成安全漏洞。

因為字符串是不可變的, 在物理上是絕對的線程安全 ,所以同一個字符串實例可以被多個線程共享。 由于不可變對象不可能被修改,因此能夠在多線程中被任意自由訪問而不導致線程安全問題,不需要多余的同步操作。即在并發(fā)場景下,多個線程同時讀一個資源,并不會引發(fā)競態(tài)條件,只有對資源進行讀寫才有危險。不可變對象不能被寫,所以線程安全。

類加載器要用到字符串,不可變性也提供了安全性,以便正確的類可以被加載。 譬如你想加載java.sql.Connection類,而這個值被改成了myhacked.Connection,那么會對你的數據庫造成不可知的破壞。

因為字符串是不可變的,所以在字符串對象創(chuàng)建的時候hashCode()就被執(zhí)行并把執(zhí)行結果緩存了,不需要重新計算。這就使得字符串很適合作為Map中的鍵,所以字符串的處理速度要快過其它的鍵對象,這就是HashMap中的鍵往往都使用字符串的原因,當我們需要頻繁讀取訪問任意鍵值對時,能夠節(jié)省很多的CPU計算開銷。

Sting的不可變性會提高執(zhí)行性能和效率,基于Sting不可變,我們就可以用緩存池將String對象緩存起來,同時把一個String對象的地址賦值給多個String引用,這樣可以安全保證多個變量共享同一個對象。因此,構造一萬個string s = "xyz",實際上得到都是同一個字符串對象,避免了很多不必要的空間開銷。

2. 不可變性的缺點

  • 喪失了部分靈活性。我們平時使用的大部分都是可變對象,比如內容變化時,只需要利用setValue()更新一下就可以了,不需要重新創(chuàng)建一個對象,但是String很難做到這一點。當然,我們完全可以使用StringBuilder來彌補這個缺點。
  • 脆弱的不可變性,String其實可以利用JNI或反射來改變其不可變性。

以上就是Java字符串原理分析之String是否可變的詳細內容,更多關于Java String原理的資料請關注腳本之家其它相關文章!

相關文章

  • C++ 歸并排序(merge sort)案例詳解

    C++ 歸并排序(merge sort)案例詳解

    這篇文章主要介紹了C++ 歸并排序(merge sort)案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下
    2021-08-08
  • Java List 集合如何去除null元素

    Java List 集合如何去除null元素

    這篇文章主要介紹了Java List 集合如何去除null元素,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Spring Boot集成Spring Cloud Security進行安全增強的方法

    Spring Boot集成Spring Cloud Security進行安全增強的方法

    Spring Cloud Security是Spring Security的擴展,它提供了對Spring Cloud體系中的服務認證和授權的支持,包括OAuth2、JWT等,這篇文章主要介紹了Spring Boot集成Spring Cloud Security進行安全增強,需要的朋友可以參考下
    2024-11-11
  • 實例講解Java HashSet

    實例講解Java HashSet

    這篇文章主要介紹了Java HashSet的相關資料,文中示例代碼非常詳細,幫助大家更好的理解和學習,感興趣的朋友可以了解下
    2020-07-07
  • MySQL?MyBatis?默認插入當前時間方式

    MySQL?MyBatis?默認插入當前時間方式

    這篇文章主要介紹了MySQL?MyBatis?默認插入當前時間方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • mybatis-plus條件構造器的操作代碼

    mybatis-plus條件構造器的操作代碼

    mybatis-plus提供了AbstractWrapper抽象類,提供了很多sql語法支持的方法,比如模糊查詢,比較,區(qū)間,分組查詢,排序,判斷空,子查詢等等,方便我們用面向對象的方式去實現(xiàn)sql語句,本文重點給大家介紹mybatis-plus條件構造器的操作代碼,感興趣的朋友一起看看吧
    2022-03-03
  • springboot實現(xiàn)全局異常捕獲的使用示例

    springboot實現(xiàn)全局異常捕獲的使用示例

    任何系統(tǒng),我們不會傻傻的在每一個地方進行異常捕獲和處理,整個系統(tǒng)一般我們會在一個的地方統(tǒng)一進行異常處理,本文主要介紹了springboot實現(xiàn)全局異常捕獲的使用示例,感興趣的可以了解一下
    2023-11-11
  • SpringBoot使用Jsp的示例代碼

    SpringBoot使用Jsp的示例代碼

    這篇文章主要介紹了SpringBoot使用Jsp的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • java連接orcale數據庫示例分享

    java連接orcale數據庫示例分享

    這篇文章主要介紹了java連接orcale數據庫示例,需要的朋友可以參考下
    2014-02-02
  • Java開發(fā)學習之Bean的作用域和生命周期詳解

    Java開發(fā)學習之Bean的作用域和生命周期詳解

    這篇文章主要介紹了淺談Spring中Bean的作用域,生命周期和注解,從創(chuàng)建到消亡的完整過程,例如人從出生到死亡的整個過程就是一個生命周期。本文將通過示例為大家詳細講講,感興趣的可以學習一下
    2022-06-06

最新評論