聊一聊Java字符串的不可變

前言

在 Java 開發中 String (字符串)對象是咱們使用最頻繁的對象,也是很重要的對象。正是使用得如此頻繁,String 在實現層面上不斷進行優化,從 Java6 到 Java7,再到 Java9 的新實現 ,都是爲了提高 String 對象的性能,而其中不變的是 String 所生俱來的特性:不可變。本文主要聊一聊 String 的不可變,以及爲何存在的。html

什麼是 String 的不可變

首先咱們先來看下什麼是不可變對象:一旦對象被建立並初始化後,內部的狀態數據就會保持不變。查看 JDK 源碼中的 String 類,能夠看到類自己被 final 修飾,而且內部的大部分屬性都是 final 修飾的,除了字段 hash 是經過字符串內容計算並緩存起來的。這樣的行爲讓 String 類沒法被擴展,內部屬性也沒法被修改。java

接着咱們再來用畫圖的形式來講明下 String 的不可變性。編程

一般咱們初始化字符串都是如下形式:緩存

String 類型的引用變量 a 保留了一個字符串對象 string 的引用,就如同下圖所示,箭頭則表示了變量 a 與真正 String 對象的引用關係。安全

再經過上述代碼,咱們將變量 a 賦值給變量 b ,變量 b 也存儲了字符串對象 string的引用,它們指向的是同一個對象。網絡

當咱們嘗試對變量 a 從新賦值,看下對變量 b 會不會有影響呢數據結構

想必小夥伴一看就知道,打印的結果確定是 string2,string,一樣用畫圖的方式展現這兩個變量與字符串對象的引用關係。多線程

將變量 a 從新賦值後,保存了新的引用,而不是直接在原有的字符串對象上進行數據改變,同時變量 b 仍然存的是對象 string 的引用,變量 ab 二者相互獨立,不影響,這也正是說明了 String 對象的不可變。oracle

在這裏初認 Java 的小夥伴還可能會有些困惑:對一個String對象 a 賦值 string,而後又讓 a 值爲 string2,這個時候a的值變成 了string2, a 的值改變了,爲何還說 String 對象不可變呢。jvm

其實問題也很簡單,這裏的 a 只是存儲 String 對象的引用,並非對象自己,a 存儲的是指向對象所在內存的地址引用罷了,當第二次賦值時,a 引用指向了對象 string2的內存地址,而對象 string2 是從新建立的,以前的 string 對象仍在內存中,而且由變量 b 引用着。

除此以外,String 類的返回 String 對象的方法不會改變自身,都是返回一個新的 String 對象來實現,好比 concatreplacesubstring 等等。

爲何 String 須要不可變

聊完什麼是 String 的不可變後,接下來咱們再說說 String 爲何須要不可變呢,又有什麼好處呢?

字符串常量池的實現

在Java中,咱們一般有兩種方式建立字符串對象,一種是經過字符串字面量方式建立,就如上文的代碼,另一種就是經過 new 方式去建立,如 String c = new String("string 3"); 而二者區別就在於經過字符串字面量的方式建立時,JVM 會如今字符串池中檢查字符串內容是否已經存在,若是存在就會直接返回對應的引用,而不是再次分配內存進行建立,若是不存在就會分配在內存中建立的同時將字符串數據緩存在字符串池中,便於重用。正是是因爲字符串的不可變,一樣的字符串內容可讓 JVM 能夠減小額外的內存分配操做,直接使用在字符串池中字符串對象便可,對性能提高和內存節省都大有好處。

關於字符串池,這裏稍微簡單介紹一下:**Java 的字符串池屬於 JVM 專門給指定的特殊內存區域,用來存儲字符串字面量。**在 Java 7 以前,分配於 JVM 的方法區內,屬於常量池的一部分;而 Java7 以後字符串池被移至堆內存進行管理,這樣的好處就是容許被 JVM 進行垃圾回收操做,將未被引用的字符串所佔內存即便回收,以此節省內存。

Hashcode 緩存

字符串做爲基礎的數據結構,大量地應用在一些集合容器之中,尤爲是一些散列集合,在散列集合中,存放元素都要根據對象的 hashCode() 方法來肯定元素的位置。因爲字符串 hashcode 屬性不會變動,保證了惟一性,使得相似 HashMap,HashSet 等容器才能實現相應的緩存功能。因爲 String 的不可變,避免重複計算 hashcode,只有使用緩存的 hashcode 便可,這樣一來大大提升了在散列集合中使用 String 對象的性能。

線程安全

在多線程中,只有不變的對象和值是線程安全的,能夠在多個線程中共享數據。因爲 String 自然的不可變,當一個線程」修改「了字符串的值,只會產生一個新的字符串對象,不會對其餘線程的訪問產生反作用,訪問的都是一樣的字符串數據,不須要任何同步操做。

安全性

因爲字符串不管在任何 Java 系統中都普遍使用,會用來存儲敏感信息,如帳號,密碼,網絡路徑,文件處理等場景裏,保證字符串 String 類的安全性就尤其重要了,若是字符串是可變的,容易被篡改,那咱們就沒法保證使用字符串進行操做時,它是安全的,頗有可能出現 SQL 注入,訪問危險文件等操做。

結語

經過本文,咱們介紹 String 是不可變的,能夠將它們的引用能夠被看成一個普通的變量來使用,不管是在方法間,仍是線程間傳遞它們,都不用擔憂它指向的實際 String 對象發生改變,而且不可變的特性也在語言層面和程序層面上帶了許多好處,在日常編程實踐中咱們也應該多學習效仿,用 James Gosling,Java之父的話說就是」我會盡量地使用不可變對象「。

推薦閱讀

參考資料

相關文章
相關標籤/搜索