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

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關鍵詞修飾的基本數(shù)據(jù)類型變量稱為常量,只能被賦值一次;

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

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[]是一個私有的字符數(shù)組,String類其實就是通過這個char數(shù)組來保存字符串內容的。簡單的說,我們定義的字符串會被拆成一個一個的字符,這些字符都被存放在這個value字符數(shù)組里面。

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

另外我們要注意,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" 字符的字符數(shù)組。因為value被final修飾,所以value的值不可被更改。因此,上面代碼中改變的其實是s的引用指向,而不是改變了String對象的值 。 換句話說,上面實例中 s的值 只是 value的引用地址,并不是String內容本身!當我們執(zhí)行 s = "yyg"; 語句時,Java中會創(chuàng)建一個新的字面量對象 "yyg",而原來的 "yiyige" 字面量對象依然存在于內存的intern緩存池中。 在Java中,因為數(shù)組也是對象, 所以value中存儲的也只是一個引用,它指向一個真正的數(shù)組對象。在執(zhí)行了String s = “yiyige”; 這句代碼之后,真正的內存布局應該是下圖這樣的:

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

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

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

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

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

三. String真的不可變嗎?

1. 實驗案例

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

四. 結語

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

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

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

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

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

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

因為字符串是不可變的,所以在字符串對象創(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原理的資料請關注腳本之家其它相關文章!

相關文章

  • 解決Java提示正在嘗試分配更低的訪問權限問題

    解決Java提示正在嘗試分配更低的訪問權限問題

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

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

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

    Spring依賴注入底層原理詳解

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

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

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

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

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

    MyEclipse8.6首次運行maven項目圖標上沒有小M的標識怎么解決

    myeclipse8.6導入maven項目后識別為普通java項目,即項目圖標上沒有小M的標識。這時是無法直接運行的,怎么解決這一問題呢?下面小編給大家?guī)砹私鉀Q方案,需要的朋友參考下吧
    2016-11-11
  • 讓IntelliJ IDEA支持.vue文件的方法

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

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

    處理SpringMVC中的文件上傳過程

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

    關于maven pom文件配置加載lib下的jar包

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

    Java基礎之并發(fā)相關知識總結

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

最新評論

龙州县| 陈巴尔虎旗| 弥渡县| 全椒县| 张北县| 新丰县| 洞口县| 海晏县| 宜昌市| 新河县| 玉树县| 抚顺县| 大埔区| 青海省| 莆田市| 新闻| 铜川市| 五大连池市| 昆明市| 获嘉县| 曲靖市| 诏安县| 建始县| 郓城县| 科尔| 资阳市| 崇文区| 花垣县| 陇川县| 通河县| 滨海县| 乐都县| 阳泉市| 花莲市| 红原县| 安国市| 左权县| 齐齐哈尔市| 大方县| 安多县| 界首市|