Java字符串——String深刻

 

轉載請註明原文地址:http://www.javashuo.com/article/p-mozkuidp-hm.htmlhtml

 

一:字符串的不可變性

   一、可變 與 不可變 辨析

    Java中的對象按照建立後,對象的內容是否能夠被修改,分爲 mutable object 和 immutable object。【注意:是對象的內容不可變,而不是指向該對象的引用變量內容不可變。】java

    咱們常見的不可變對象是幾個基本數據類型的包裝類——Integer、Double、String等。【想一想爲何?——Tips:出於節省內存開銷,避免重複建立。】c++

    不可變類有5大基本原則:正則表達式

    1)類定義時,添加final修飾符,保證類不被繼承【即:不容許在子類中被修改】數組

    2)類定義時,其成員變量一律使用 final private 修飾,保證變量私有的同時不容許修改緩存

    3)不提供能夠修改爲員變量的方法,包括setter安全

    4)在構造函數中採用deep copy的形式將參數值拷貝給成員變量,而不是直接將參數值賦給成員變量【由於引用類型的參數只是傳了一個地址,這樣在外部改變該地址的內容會致使不可變對象的成員變量改變】多線程

    5)在成員變量的getter方法中,不能直接返回成員變量自己,而是返回成員變量的copy對象【這也是爲了防止引用類型的成員變量被外部獲取後,改變引用指向的對象值引發不可變對象的內容變化】app

 

    二、不可變對象的優缺點

    1)字符串常量池的須要——避免每次使用相同的字符串常量都從新建立相同的對象、節省存儲空間jvm

    2)線程安全:當一個String對象被多個線程共享時,無需擔憂線程安全問題

    3)支持hash和緩存:因爲字符串對象是不可變的而且hashcode就緩存在對象中【在下文講解】了,不須要從新計算,所以很適合做爲Map中的鍵,由於字符串鍵的哈希處理速度要快過其它的鍵對象【這就是HashMap中的鍵每每都使用字符串的緣由】

    4)使用類加載器要用字符串來傳遞加載的類名,而字符串的不可變性提供了安全性,以保證正確的類被加載。

    5)  缺點:當對String變量有從新賦值、修改等操做時,會不斷建立大量的String對象。【當修改後的值未出如今字符串常量池的前提下】————【延伸:所以,在代碼中涉及大量字符串操做時,使用StringBuilder或StringBuffer來進行

 

    三、「不可變對象」的很是規手段修改

    對於不可變對象,能夠經過反射機制的手段改變其值——獲取類的字段定義->改變該字段的可見性和可修改性->修改對象的變量值

    例如:

  //建立字符串"Hello World", 並賦給引用s
    String s = "Hello World"; 
    System.out.println("s = " + s); //Hello World

    //獲取String類中的value字段
    Field valueFieldOfString = String.class.getDeclaredField("value");
    //改變value屬性的訪問權限
    valueFieldOfString.setAccessible(true);

    //獲取s對象上的value屬性的值
    char[] value = (char[]) valueFieldOfString.get(s);
    //改變value所引用的數組中的第5個字符
    value[5] = '_';
    System.out.println("s = " + s);  //Hello_World


結果爲:
s = Hello World
s = Hello_World

 

 

二:replaceFirst、replaceAll、replace 區別

    咱們先來看一下程序:

String s = "my.test.txt";
System.out.println(s.replace(".", "#"));
System.out.println(s.replaceAll(".", "#"));
System.out.println(s.replaceFirst(".", "#"));

    它們的結果會同樣嗎?——No。

my#test#txt
###########
#y.test.txt

 

    這三個函數中,replaceFirst、replaceAll在替換時使用了正則表達式,所以上面三個函數的參數含義是不一樣的

    replace(src,des):將字符串中的src子串[也是一段字符串]替換成des。

    replaceAll(reg,des):將字符串中符合reg模式的內容替換成des。

    replaceFirst(reg,des):將字符串中,第一個匹配reg模式的內容替換成des。

 

    因此上面示例代碼中,replace函數是將字符串中的「.」字符換成「#」,而replaceAll則是將全部字符[「.」是正則表達式的通配符]替換成「#」,replaceFirst則是將第一個字符替換成「#」。

 

三:String對「+」運算符的重載

    「在Java中是不支持重載運算符的!」

    java不支持運算符重載,由於java的語法比較繁雜,會致使使用類對象 像基本數據類型那樣 用運算符進行操做時,沒法作到像c++同樣流暢。所以,Java中針對類對象的運算操做通常都是經過方法來定義,而不是運算符重載。

    惟一例外的是String類,它的拼接運算(+) 通過了重載,這個重載是經過jvm編譯實現的,具體原理能夠 手寫一個字符串相+的java類文件並編譯,而後經過 javap -c 文件.class 查看具體的過程。

   其原理是:String的+會被轉化爲StringBuilder的append方法,並生成一個新的String對象返回。

 

四:字符串拼接的5種方式比較

    Java中有如下五種方法處理字符串拼接:

    1. 加號 「+」:適用於小數據量的操做,代碼簡潔方便。

    2. String contact() 方法:適用於小數據量的操做,代碼簡潔方便。

    3. StringUtils.join() 方法:適用於將ArrayList轉換成字符串的情景,能夠省掉用for循環讀取ArrayList手動拼接的過程。

    4. StringBuffer append() 方法:繼承自AbstractStringBuilder,效率高,大批量的數據處理的好選擇,該方法線程安全,因爲加了線程鎖,速度會比下面第5中慢一點。

    5. StringBuilder append() 方法:繼承自AbstractStringBuilder,效率最高,大批量的數據處理的好選擇,該方法線程不安全,所以速度最快,若是不涉及多線程操做,優先使用此方法。

 

五:String.valueOf(val) 和 obj.toString 的異同

    同:都返回參數的字符串表示形式。

    異: 對空值的調用結果不一樣:

           java.lang.Object類裏已有public方法.toString(),因此對任何嚴格意義上的java對象均可以調用此方法。但在使用時要注意,必須保證object不是null值,不然將拋出NullPointerException異常。

           而valueOf(Object obj)對null值進行了處理,不會報任何異常。但當object爲null 時,String.valueOf(object)的值是字符串」null」,而不是null。

 

     

六:switch 對 String 的支持

    Java1.7以前,switch只能侷限於int 、short 、byte 、char四類作判斷條件,由於在JVM內部實際大部分字節碼指令只有int類型的值。

    在使用switch的時候,若是是非int型,會先轉爲int型,再進行條件判斷

    可是在Java1.7中,switch增長了對String做爲判斷條件的支持,可String並不能直接轉爲int型,這是怎麼作到的呢?

    原理:switch比較的是字符串常量的哈希值(緩存的int類型值,前文提到過),可是hash值可能會有衝突,因此還須要再調用equals方法將 switch(param)的param 與 case str的str 進行二次比較,兩者綜合之下達到惟一匹配的目的。

 

七:字符串池、常量池(運行時常量池、Class 常量池)、intern

    全局字符串常量池(string pool):全局常量池在每一個JVM中只有一份,存放的是字符串常量值。

                                                string pool功能的是一個StringTable類,它是一個哈希表,裏面存的是駐留字符串(也就是咱們常說的用雙引號括起來的),
                                                字符串在第一次出現時被建立而且把引用放到stringtable中,在後面再出現時就不會重複建立而是直接從stringtable中找到字符串字面值的地址,返回給字符串引用變量。

  

    Class常量池:class常量池是在編譯的時候每一個class都有的,用於編譯階段,存放的是class文件中的字面量(常量)和符號引用

                       字面量就是咱們所說的常量,如文本字符串、八種基本類型的值、被聲明爲final的常量值等。

                       符號引用是一組符號,用來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時能無歧義地定位到目標便可。
                       直接引用是指向方法區的本地指針,相對偏移量或是一個能間接定位到目標的句柄。

                                               

    運行時常量池:運行時常量池是在類加載完成以後,將每一個class常量池中的符號引用值轉存到運行時常量池中,將符號引用替換成直接引用,與全局常量池中的引用值保持一致

                         運行時常量池更具動態性,在運行期間也能夠將新的變量放入常量池中,而不是必定要在編譯時肯定的常量才能放入。最主要的運用即是String類的intern()方法

 

    str.intern()函數:把字符串對象str加入常量池中,若是常量池中已有該字符串字面值,則返回stringtable中的引用值。(這樣作主要是爲了不在堆中不斷地建立新的字符串對象)

                           常量池咱們都知道他是存在於方法區的,他是方法區的一部分,而方法區是線程共享的,因此常量池也就是線程共享的,可是他並非線程不安全的,他實際上是線程安全的,由於它讓有相同值的引用指向同一個位置。若是引用值變化了,可是常量池中沒有新的值,那麼就會新開闢一個常量結果來交給新的引用。

 

    詳細原理請看:http://www.javashuo.com/article/p-ghvihton-ko.html

 

八:String的New操做建立了幾個對象?——兩個

咱們以String s1=new String("abc");爲例

首先當咱們的類Class在被ClassLoader加載時,"abc"被做爲常量讀入,在String Pool(字符串常量池)建立了一個"abc"的實例。

而後,調用到new String("abc")的時候,會在Heap裏面複製一個相同的對象。

 

(1)類加載對一個類只會進行一次。"abc"在類加載時就已經建立並駐留了(若是該類被加載以前已經有"abc"字符串被駐留過則不須要重複建立用於駐留的"abc"實例)。駐留的字符串引用是放在全局共享的字符串常量池中的。[加載時建立一次]

(2)在這段代碼後續被運行的時候,"abc"字面量對應的String實例已經固定了,不會再被重複建立。因此這段代碼將常量池中的對象實例複製一份放到heap中,而且把heap中的這個對象的引用交給s1 持有。[運行時複製一次]

  所以,這條語句建立了2個對象。【這兩個引用,它們的對象實例是不一樣的。】

九:String的hashCode的緩存和懶加載初始化

    String類中定義了一個私有成員變量——hash,它是一個整數,保存String對象的哈希值,也就是說String類型的對象的哈希值不會重複計算,計算過一次後就保存起來了,之後在被hash時直接取該值便可,無需從新計算

 

    這個值在String對象第一次被調用時進行初始化(懶加載,不是在String對象建立時初始化)。

相關文章
相關標籤/搜索