最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時(shí)間:2023年05月17日 10:08:09   作者:一一哥Sun  
當(dāng)我們?cè)谇舐殨r(shí),面試官很喜歡問(wèn)我們關(guān)于String的一些原理性知識(shí),比如String的不可變性、字符串的內(nèi)存分配等,為了讓大家更好地應(yīng)對(duì)面試,并理解String的底層設(shè)計(jì),接下來(lái)會(huì)給大家聊聊String的一些原理,比如String為什么具有不可變性,需要的朋友可以參考下

一. Spring源碼中的final關(guān)鍵詞

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

1. final的特點(diǎn)

為了更好地理解String相關(guān)的內(nèi)容,在閱讀String源碼之前,我們先來(lái)復(fù)習(xí)一下final關(guān)鍵詞有哪些特點(diǎn),因?yàn)樵赟tring中會(huì)涉及到很多final相關(guān)的內(nèi)容。

final關(guān)鍵詞修飾的類(lèi)不可以被其他類(lèi)繼承,但是該類(lèi)本身可以繼承其他類(lèi),通俗的說(shuō)就是這個(gè)類(lèi)可以有父類(lèi),但是不能有子類(lèi);

final關(guān)鍵詞修飾的方法不可以被覆蓋重寫(xiě),但是可以被繼承使用;

final關(guān)鍵詞修飾的基本數(shù)據(jù)類(lèi)型變量稱(chēng)為常量,只能被賦值一次;

final關(guān)鍵詞修飾的引用數(shù)據(jù)類(lèi)型的變量值為地址值,地址值不能改變,但是地址內(nèi)的數(shù)據(jù)對(duì)象可以被改變;

final關(guān)鍵詞修飾的成員變量,需要在創(chuàng)建對(duì)象前賦值,否則會(huì)報(bào)錯(cuò)(即需要在定義時(shí)直接賦值,如果是在構(gòu)造方法中賦值,則多個(gè)構(gòu)造方法均需賦值)。

復(fù)習(xí)了final的特點(diǎn)之后,接下來(lái)我們就可以閱讀String的源碼了。

2. String源碼解讀

接下來(lái)就請(qǐng)大家請(qǐng)跟著小編來(lái)看看String源碼中關(guān)于不可變性的內(nèi)容吧。

2.1 final修飾的String類(lèi)

/**
 * ......其他略......
 *
 * 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 {
    ......

先對(duì)上面的源碼及其注釋進(jìn)行簡(jiǎn)單的解釋?zhuān)?/p>

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

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

  • String類(lèi)用final關(guān)鍵字修飾,說(shuō)明String不可被繼承;
  • String字符串是常量,字符串的值一旦被創(chuàng)建,就不能被改變;
  • String字符串緩沖區(qū)支持可變字符串;
  • 因?yàn)镾tring對(duì)象是不可變的,所以它們是可以被共享的。

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[]是一個(gè)私有的字符數(shù)組,String類(lèi)其實(shí)就是通過(guò)這個(gè)char數(shù)組來(lái)保存字符串內(nèi)容的。簡(jiǎn)單的說(shuō),我們定義的字符串會(huì)被拆成一個(gè)一個(gè)的字符,這些字符都被存放在這個(gè)value字符數(shù)組里面。

這里的value[]數(shù)組被final修飾,初始化之后就不能再被更改。但是大家注意,我們這里說(shuō)的value[]不可變,指的是value的引用地址不可變,但是value數(shù)組里面的數(shù)據(jù)元素其實(shí)是可變的! 這是因?yàn)関alue是數(shù)組類(lèi)型,根據(jù)我們之前學(xué)過(guò)的知識(shí),value的引用地址會(huì)分配在棧中 ,而其對(duì)應(yīng)的數(shù)據(jù)是在常量池中保存的。所以我們說(shuō)String不可變,指的就是value在棧中的引用地址不可變,而不是說(shuō)常量池中數(shù)組本身的數(shù)據(jù)元素不可變。

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

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

JDK 7、8以后的字符串常量池被轉(zhuǎn)移到了堆中,此時(shí)常量池存儲(chǔ)的就是字符串對(duì)象的引用,而不是字符串對(duì)象本身。

至此,就帶各位把String類(lèi)中的核心源碼分析完了,接下來(lái)我們?cè)龠M(jìn)一步分析String不可變的原因,及其他底層原理設(shè)計(jì)。

二. String的不可變性

1. 實(shí)驗(yàn)案例

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

結(jié)果s的內(nèi)容變了,好像是啪啪打臉了???!?。≌厥?,不是說(shuō)了String不可變嗎?怎么這么快就翻車(chē)打臉了?別急,讓我們好好來(lái)分析一下。

2. 結(jié)果剖析

首先我們從結(jié)果上來(lái)看String s 變量的結(jié)果好像改變了,但為什么我們又說(shuō)String是不可變的呢?

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

因?yàn)関alue是String封裝的字符數(shù)組,value中的所有字符都屬于String這個(gè)對(duì)象。由于value是private的,且沒(méi)有提供setValue等公共方法來(lái)修改這個(gè)value值,所以在String類(lèi)的外部是無(wú)法修改value值的,也就是說(shuō)一旦初始化就不能被修改。此外,value變量是final的, 也就是說(shuō)在String類(lèi)內(nèi)部,一旦這個(gè)值初始化了,value這個(gè)變量所引用的地址就不會(huì)改變了,即一直引用同一個(gè)對(duì)象。正是基于這一層,所以說(shuō)String對(duì)象是不可變的對(duì)象。 但其實(shí)value所引用對(duì)象的內(nèi)容完全可以發(fā)生改變,我們可以利用反射來(lái)消除String類(lèi)對(duì)象的不可變特性 。

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

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

因此,我們?cè)谝院蟮拈_(kāi)發(fā)中,如果要經(jīng)常修改字符串的內(nèi)容,請(qǐng)盡量少用String,因?yàn)樽址闹赶?ldquo;斷開(kāi)-連接”會(huì)大大降低性能,建議使用:StringBuilder、StringBuffer。

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

三. String真的不可變嗎?

1. 實(shí)驗(yàn)案例

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

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

    Class stringClass = str.getClass();
    //獲取String類(lèi)中的value屬性
    Field field = stringClass.getDeclaredField("value");
    //設(shè)置私有成員的可訪問(wèn)性,進(jìn)行暴力反射
    field.setAccessible(true);
    //獲取value數(shù)組中的內(nèi)容
    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. 結(jié)果剖析

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

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

四. 結(jié)語(yǔ)

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

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

只有當(dāng)字符串是不可變的,字符串池才有可能實(shí)現(xiàn)。 字符串池的實(shí)現(xiàn)可以在運(yùn)行時(shí)節(jié)約很多heap空間,因?yàn)椴煌淖址枚伎梢灾赶虺刂械耐粋€(gè)字符串。但如果字符串是可變的,如果一個(gè)引用變量改變了字符串的值,那么其它指向這個(gè)值的變量?jī)?nèi)容也會(huì)跟著一起改變。

如果字符串是可變的,那么可能會(huì)引起很?chē)?yán)重的安全問(wèn)題。 譬如,數(shù)據(jù)庫(kù)的用戶(hù)名、密碼都是以字符串的形式傳入數(shù)據(jù)庫(kù),以獲得數(shù)據(jù)庫(kù)的連接;或者在socket編程中,主機(jī)名和端口都是以字符串的形式傳入。因?yàn)樽址遣豢勺兊?,所以它的值是不可改變的,否則黑客們可以鉆到空子,改變字符串指向的對(duì)象值,造成安全漏洞。

因?yàn)樽址遣豢勺兊模?/strong> 在物理上是絕對(duì)的線程安全 ,所以同一個(gè)字符串實(shí)例可以被多個(gè)線程共享。 由于不可變對(duì)象不可能被修改,因此能夠在多線程中被任意自由訪問(wèn)而不導(dǎo)致線程安全問(wèn)題,不需要多余的同步操作。即在并發(fā)場(chǎng)景下,多個(gè)線程同時(shí)讀一個(gè)資源,并不會(huì)引發(fā)競(jìng)態(tài)條件,只有對(duì)資源進(jìn)行讀寫(xiě)才有危險(xiǎn)。不可變對(duì)象不能被寫(xiě),所以線程安全。

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

因?yàn)樽址遣豢勺兊模栽谧址畬?duì)象創(chuàng)建的時(shí)候hashCode()就被執(zhí)行并把執(zhí)行結(jié)果緩存了,不需要重新計(jì)算。這就使得字符串很適合作為Map中的鍵,所以字符串的處理速度要快過(guò)其它的鍵對(duì)象,這就是HashMap中的鍵往往都使用字符串的原因,當(dāng)我們需要頻繁讀取訪問(wèn)任意鍵值對(duì)時(shí),能夠節(jié)省很多的CPU計(jì)算開(kāi)銷(xiāo)。

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

2. 不可變性的缺點(diǎn)

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

以上就是Java字符串原理分析之String是否可變的詳細(xì)內(nèi)容,更多關(guān)于Java String原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 解決Java提示正在嘗試分配更低的訪問(wèn)權(quán)限問(wèn)題

    解決Java提示正在嘗試分配更低的訪問(wèn)權(quán)限問(wèn)題

    在本篇文章里小編給大家整理的是關(guān)于解決Java提示正在嘗試分配更低的訪問(wèn)權(quán)限問(wèn)題的相關(guān)方法內(nèi)容,有需要的朋友們跟著參考學(xué)習(xí)下。
    2019-07-07
  • Springboot使用@Aspect、自定義注解記錄日志方式

    Springboot使用@Aspect、自定義注解記錄日志方式

    本文介紹了如何使用AOP技術(shù)進(jìn)行日志記錄,包括掃描包和自定義注解兩種方式,掃描包方式通過(guò)添加依賴(lài)和配置切入點(diǎn)表達(dá)式實(shí)現(xiàn),自定義注解方式則通過(guò)創(chuàng)建自定義注解和AOP切面類(lèi)來(lái)實(shí)現(xiàn)方法執(zhí)行時(shí)間的統(tǒng)計(jì)和記錄
    2024-11-11
  • Spring依賴(lài)注入底層原理詳解

    Spring依賴(lài)注入底層原理詳解

    這篇文章主要介紹了Spring依賴(lài)注入底層原理詳解,??依賴(lài)注入是一種設(shè)計(jì)模式,它將對(duì)象之間的依賴(lài)關(guān)系從代碼中移除,并由容器來(lái)管理這些依賴(lài)關(guān)系,依賴(lài)注入的主要目的是降低代碼的耦合度,使代碼更加靈活和可維護(hù),需要的朋友可以參考下
    2023-09-09
  • springboot整合mybatis中的問(wèn)題及出現(xiàn)的一些問(wèn)題小結(jié)

    springboot整合mybatis中的問(wèn)題及出現(xiàn)的一些問(wèn)題小結(jié)

    這篇文章主要介紹了springboot整合mybatis中的問(wèn)題及出現(xiàn)的一些問(wèn)題小結(jié),本文給出了解決方案,需要的朋友可以參考下
    2018-11-11
  • 基于SpringBoot + Redis實(shí)現(xiàn)密碼暴力破解防護(hù)

    基于SpringBoot + Redis實(shí)現(xiàn)密碼暴力破解防護(hù)

    在現(xiàn)代應(yīng)用程序中,保護(hù)用戶(hù)密碼的安全性是至關(guān)重要的,密碼暴力破解是指通過(guò)嘗試多個(gè)密碼組合來(lái)非法獲取用戶(hù)賬戶(hù)的密碼,為了保護(hù)用戶(hù)密碼不被暴力破解,我們可以使用Spring Boot和Redis來(lái)實(shí)現(xiàn)一些防護(hù)措施,本文將介紹如何利用這些技術(shù)來(lái)防止密碼暴力破解攻擊
    2023-06-06
  • MyEclipse8.6首次運(yùn)行maven項(xiàng)目圖標(biāo)上沒(méi)有小M的標(biāo)識(shí)怎么解決

    MyEclipse8.6首次運(yùn)行maven項(xiàng)目圖標(biāo)上沒(méi)有小M的標(biāo)識(shí)怎么解決

    myeclipse8.6導(dǎo)入maven項(xiàng)目后識(shí)別為普通java項(xiàng)目,即項(xiàng)目圖標(biāo)上沒(méi)有小M的標(biāo)識(shí)。這時(shí)是無(wú)法直接運(yùn)行的,怎么解決這一問(wèn)題呢?下面小編給大家?guī)?lái)了解決方案,需要的朋友參考下吧
    2016-11-11
  • 讓IntelliJ IDEA支持.vue文件的方法

    讓IntelliJ IDEA支持.vue文件的方法

    這篇文章主要介紹了讓IntelliJ IDEA支持.vue文件的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05
  • 處理SpringMVC中的文件上傳過(guò)程

    處理SpringMVC中的文件上傳過(guò)程

    配置SpringMVC支持文件上傳,在在配置文件中添加支持;創(chuàng)建包含文件上傳字段的表單;在控制器中編寫(xiě)處理文件上傳請(qǐng)求的方法,使用@RequestParam注解將上傳的文件綁定到方法參數(shù)
    2026-05-05
  • 關(guān)于maven pom文件配置加載lib下的jar包

    關(guān)于maven pom文件配置加載lib下的jar包

    在項(xiàng)目中引用放置在lib文件夾中的第三方j(luò)ar包需要通過(guò)POM文件進(jìn)行特定配置,雖然將依賴(lài)放置在公司的Maven私服是更好的做法,但如果遇到部署問(wèn)題,可以利用maven-jar-plugin進(jìn)行配置,Spring Boot項(xiàng)目可以通過(guò)特定設(shè)置來(lái)實(shí)現(xiàn)
    2024-09-09
  • Java基礎(chǔ)之并發(fā)相關(guān)知識(shí)總結(jié)

    Java基礎(chǔ)之并發(fā)相關(guān)知識(shí)總結(jié)

    隨著摩爾定律逐步失效,cpu單核性能達(dá)到瓶頸,并發(fā)逐漸逐漸得到廣泛應(yīng)用,因而學(xué)習(xí)了解以及使用并發(fā)就顯得十分重要,但并發(fā)相關(guān)的知識(shí)比較瑣碎,不易系統(tǒng)學(xué)習(xí),因而本篇文章參照王寶令老師《Java并發(fā)編程》來(lái)勾勒出一張“并發(fā)全景圖”,需要的朋友可以參考下
    2021-05-05

最新評(píng)論

新蔡县| 宜君县| 清水县| 会泽县| 柯坪县| 光山县| 从江县| 嘉善县| 大姚县| 扎兰屯市| 清远市| 噶尔县| 赤峰市| 临武县| 冕宁县| 唐海县| 赣榆县| 项城市| 信丰县| 从化市| 织金县| 城步| 南汇区| 开平市| 新蔡县| 安义县| 怀化市| 祁东县| 荣成市| 洛扎县| 苍山县| 石家庄市| 宝山区| 海晏县| 林州市| 永定县| 固镇县| 塔河县| 大荔县| 济阳县| 阿拉善右旗|