Java之String

Java之String

開篇

下面這段代碼的輸出:


        String str1= "abc";
        String str2= new String("abc");
        String str3= str2.intern();

        System.out.println(str1==str2);

        System.out.println(str2==str3);

        System.out.println(str1==str3);


String對象的內(nèi)部實現(xiàn)

圖示:

圖1.png
  • 在 Java6 以及之前的版本中秘案,String 對象是對 char 數(shù)組進(jìn)行了封裝實現(xiàn)的對象,主要有四個成員變量:char 數(shù)組、偏移量 offset、字符數(shù)量 count、哈希值 hash倡勇。

String 對象是通過 offset 和 count 兩個屬性來定位 char[] 數(shù)組,獲取字符串嘉涌。這么做可以高效妻熊、快速地共享數(shù)組對象,同時節(jié)省內(nèi)存空間仑最,但這種方式很有可能會導(dǎo)致內(nèi)存泄漏扔役。

  • 從 Java7 版本開始到 Java8 版本,Java 對 String 類做了一些改變警医。String 類中不再有 offset 和 count 兩個變量了亿胸。這樣的好處是 String 對象占用的內(nèi)存稍微少了些,同時,String.substring 方法也不再共享 char[]损敷,從而解決了使用該方法可能導(dǎo)致的內(nèi)存泄漏問題葫笼。

  • 從 Java9 版本開始,將 char[] 字段改為了 byte[] 字段拗馒,又維護(hù)了一個新的屬性 coder路星,它是一個編碼格式的標(biāo)識。

為什么這樣修改诱桂?

一個 char 字符占 16 位洋丐,2 個字節(jié)。這個情況下挥等,存儲單字節(jié)編碼內(nèi)的字符(占一個字節(jié)的字符)就顯得非常浪費友绝。JDK1.9 的 String 類為了節(jié)約內(nèi)存空間,于是使用了占 8 位肝劲,1 個字節(jié)的 byte 數(shù)組來存放字符串迁客。

而新屬性 coder 的作用是,在計算字符串長度或者使用 indexOf()函數(shù)時辞槐,我們需要根據(jù)這個字段掷漱,判斷如何計算字符串長度。coder 屬性默認(rèn)有 0 和 1 兩個值榄檬,0 代表 Latin-1(單字節(jié)編碼)卜范,1 代表 UTF-16。如果 String 判斷字符串只包含了 Latin-1鹿榜,則 coder 屬性值為 0海雪,反之則為 1。

String 對象的不可變性

String 類被 final 關(guān)鍵字修飾了舱殿,而且變量 char 數(shù)組也被 final 修飾了

類被 final 修飾代表該類不可繼承奥裸,而 char[] 被 final+private 修飾,代表了 String 對象不可被更改怀薛。Java 實現(xiàn)的這個特性叫作 String 對象的不可變性刺彩,即 String 對象一旦創(chuàng)建成功,就不能再對它進(jìn)行改變枝恋。

優(yōu)點

  • 保證 String 對象的安全性创倔。假設(shè) String 對象是可變的,那么 String 對象將可能被惡意修改焚碌。

  • 保證 hash 屬性值不會頻繁變更畦攘,確保了唯一性,使得類似 HashMap 容器才能實現(xiàn)相應(yīng)的 key-value 緩存功能十电。

  • 可以實現(xiàn)字符串常量池知押。在 Java 中叹螟,通常有兩種創(chuàng)建字符串對象的方式,一種是通過字符串常量的方式創(chuàng)建台盯,如 String str=“abc”罢绽;另一種是字符串變量通過 new 形式的創(chuàng)建,如 String str = new String(“abc”)静盅。

當(dāng)代碼中使用第一種方式創(chuàng)建字符串對象時良价,JVM 首先會檢查該對象是否在字符串常量池中,如果在蒿叠,就返回該對象引用明垢,否則新的字符串將在常量池中被創(chuàng)建。這種方式可以減少同一個值的字符串對象的重復(fù)創(chuàng)建市咽,節(jié)約內(nèi)存痊银。

String str = new String(“abc”) 這種方式,首先在編譯類文件時施绎,"abc"常量字符串將會放入到常量結(jié)構(gòu)中溯革,在類加載時,“abc"將會在常量池中創(chuàng)建粘姜;其次鬓照,在調(diào)用 new 時,JVM 命令將會調(diào)用 String 的構(gòu)造函數(shù)孤紧,同時引用常量池中的"abc” 字符串,在堆內(nèi)存中創(chuàng)建一個 String 對象拒秘;最后号显,str 將引用 String 對象。

使用

字符串常量的累計


  public static void main(String[] args) {
        //字符串常量的累計
        
        String s = "a" + "b" + "c";

        System.out.println(s);

    }

首先會生成 a 對象躺酒,再生成 ab 對象押蚤,最后生成 abc 對象,從理論上來說羹应,這段代碼是低效的揽碘。

實際運行中,發(fā)現(xiàn)只有一個對象生成

查看字節(jié)碼园匹,編譯器自動優(yōu)化了這行代碼


//  public static void main(java.lang.String[]);
//    descriptor: ([Ljava/lang/String;)V
//    flags: ACC_PUBLIC, ACC_STATIC
//    Code:
//      stack=2, locals=2, args_size=1
//         0: ldc           #2                  // String abc
//         2: astore_1
//         3: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
//         6: aload_1
//         7: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
//        10: return

字符串變量的累計



    public static void main(String[] args) {

        //字符串變量的累計
        String str = "abcdef";

        for(int i=0; i<1000; i++) {
            str = str + i;
        }

    }

反編譯class文件:


//public class T5
//{
//
//    public T5()
//    {
//    }
//
//    public static void main(String args[])
//    {
//        String str = "abcdef";
//        for(int i = 0; i < 1000; i++)
//            str = (new StringBuilder()).append(str).append(i).toString();
//
//    }

編譯器同樣對這段代碼進(jìn)行了優(yōu)化雳刺。Java 在進(jìn)行字符串的拼接時,偏向使用StringBuilder裸违,這樣可以提高程序的效率掖桦。

String.intern

JDK文檔:


  /**
     * Returns a canonical representation for the string object.
     * <p>
     * A pool of strings, initially empty, is maintained privately by the
     * class {@code String}.
     * <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>
     * It follows that for any two strings {@code s} and {@code t},
     * {@code s.intern() == t.intern()} is {@code true}
     * if and only if {@code s.equals(t)} is {@code true}.
     * <p>
     * All literal strings and string-valued constant expressions are
     * interned. String literals are defined in section 3.10.5 of the
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * @return  a string that has the same contents as this string, but is
     *          guaranteed to be from a pool of unique strings.
     */
    public native String intern();

從注釋中看到,這個方法的作用是如果常量池 中存在當(dāng)前字符串供汛,就會直接返回當(dāng)前字符串枪汪,如果常量池中沒有此字符串涌穆,會將此字符串放入常量池中后再返回

例子



 public static void main(String[] args) {

        String s = new String("1");
        s.intern();
        String s2 = "1";
        System.out.println(s == s2);

        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = "11";
        System.out.println(s3 == s4);
        /*
        false
        true
         */

    }



圖示:

圖2.png

圖中綠色線條代表 string 對象的內(nèi)容指向。 藍(lán)色線條代表地址指向雀久。

jdk7 的版本中宿稀,字符串常量池已經(jīng)從 Perm 區(qū)移到正常的 Java Heap 區(qū)域

s3和s4字符串

String s3 = new String("1") + new String("1");,這句代碼中現(xiàn)在生成了2最終個對象赖捌,是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的對象原叮。中間還有2個匿名的new String("1")我們不去討論它們。此時s3引用對象內(nèi)容是”11”巡蘸,但此時常量池中是沒有 “11”對象的奋隶。

接下來s3.intern();這一句代碼,是將 s3中的“11”字符串放入 String 常量池中悦荒,因為此時常量池中不存在“11”字符串唯欣,所以在常量池中生成一個 “11” 的對象,關(guān)鍵點是 jdk7 中常量池不在 Perm 區(qū)域了搬味,這塊做了調(diào)整境氢。常量池中不需要再存儲一份對象了,可以直接存儲堆中的引用碰纬。這份引用指向 s3 引用的對象萍聊。 也就是說引用地址是相同的。

最后String s4 = "11"; 這句代碼中”11”是顯示聲明的悦析,因此會直接去常量池中創(chuàng)建寿桨,創(chuàng)建的時候發(fā)現(xiàn)已經(jīng)有這個對象了,此時也就是指向 s3 引用對象的一個引用强戴。所以 s4 引用就指向和 s3 一樣了亭螟。因此最后的比較 s3 == s4 是 true

s 和 s2 對象

String s = new String("1"); 第一句代碼,生成了2個對象骑歹。常量池中的“1” 和 JAVA Heap 中的字符串對象预烙。s.intern(); 這一句是 s 對象去常量池中尋找后發(fā)現(xiàn) “1” 已經(jīng)在常量池里了。

接下來String s2 = "1"; 這句代碼是生成一個 s2的引用指向常量池中的“1”對象道媚。 結(jié)果就是 s 和 s2 的引用地址明顯不同

調(diào)整下代碼:



 public static void main(String[] args) {

        String s = new String("1");
        String s2 = "1";
        s.intern();
        System.out.println(s == s2);

        String s3 = new String("1") + new String("1");
        String s4 = "11";
        s3.intern();
        System.out.println(s3 == s4);
        /*

        false
        false
         */

    }

圖示:


圖3.png

圖中綠色線條代表 string 對象的內(nèi)容指向扁掸。 藍(lán)色線條代表地址指向。

第一段代碼和第二段代碼的改變就是 s3.intern(); 的順序是放在String s4 = "11";后了最域。這樣谴分,首先執(zhí)行String s4 = "11";聲明 s4 的時候常量池中是不存在“11”對象的,執(zhí)行完畢后羡宙,“11“對象是 s4 聲明產(chǎn)生的新對象狸剃。然后再執(zhí)行s3.intern();時,常量池中“11”對象已經(jīng)存在了狗热,因此 s3 和 s4 的引用是不同的钞馁。

第二段代碼中的 s 和 s2 代碼中虑省,s.intern();,這一句往后放也不會有什么影響了僧凰,因為對象池中在執(zhí)行第一句代碼String s = new String("1");的時候已經(jīng)生成“1”對象了探颈。下邊的s2聲明都是直接從常量池中取地址引用的。 s 和 s2 的引用地址是不會相等的训措。

參考資料

https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伪节,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子绩鸣,更是在濱河造成了極大的恐慌怀大,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呀闻,死亡現(xiàn)場離奇詭異化借,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)捡多,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進(jìn)店門蓖康,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人垒手,你說我怎么就攤上這事蒜焊。” “怎么了科贬?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵泳梆,是天一觀的道長。 經(jīng)常有香客問我唆迁,道長鸭丛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任唐责,我火速辦了婚禮,結(jié)果婚禮上瘾带,老公的妹妹穿的比我還像新娘鼠哥。我一直安慰自己,他們只是感情好看政,可當(dāng)我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布朴恳。 她就那樣靜靜地躺著,像睡著了一般允蚣。 火紅的嫁衣襯著肌膚如雪于颖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天嚷兔,我揣著相機(jī)與錄音森渐,去河邊找鬼做入。 笑死,一個胖子當(dāng)著我的面吹牛同衣,可吹牛的內(nèi)容都是我干的竟块。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼耐齐,長吁一口氣:“原來是場噩夢啊……” “哼浪秘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起埠况,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤耸携,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后辕翰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體夺衍,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年金蜀,在試婚紗的時候發(fā)現(xiàn)自己被綠了刷后。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡渊抄,死狀恐怖尝胆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情护桦,我是刑警寧澤含衔,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站二庵,受9級特大地震影響贪染,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜催享,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一杭隙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧因妙,春花似錦痰憎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至以故,卻和暖如春蜗细,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怒详。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工炉媒, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留踪区,地道東北人。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓橱野,卻偏偏與公主長得像朽缴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子水援,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容