溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

String字符串的示例分析

發(fā)布時(shí)間:2020-12-29 14:33:01 來(lái)源:億速云 閱讀:160 作者:小新 欄目:編程語(yǔ)言

這篇文章主要介紹String字符串的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

實(shí)現(xiàn)原理

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

從 Java7 版本開(kāi)始到 Java8 版本,String 類(lèi)中不再有 offset 和 count 兩個(gè)變量了。這樣的好處是 String 對(duì)象占用的內(nèi)存稍微少了些。

從 Java9 版本開(kāi)始,將 char[]字段改為了 byte[]字段,又維護(hù)了一個(gè)新的屬性 coder,它是一個(gè)編碼格式的標(biāo)識(shí)。

一個(gè) char 字符占 16 位,2 個(gè)字節(jié)。這個(gè)情況下,存儲(chǔ)單字節(jié)編碼內(nèi)的字符(占一個(gè)字節(jié)的字符)就顯得非常浪費(fèi)。JDK1.9 的 String 類(lèi)為了節(jié)約內(nèi)存空間,于是使用了占 8 位,1 個(gè)字節(jié)的 byte 數(shù)組來(lái)存放字符串。

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

不可變

查看String類(lèi)的代碼可以發(fā)現(xiàn),String類(lèi)被final關(guān)鍵字修飾,因此這個(gè)類(lèi)不能被繼承,并且String類(lèi)里面的變量char 數(shù)組也被 final 修飾了,因此String對(duì)象不能被修改。

String對(duì)象不可變主要有如下幾個(gè)優(yōu)點(diǎn):

第一,保證 String 對(duì)象的安全性。假設(shè) String 對(duì)象是可變的,那么 String 對(duì)象將可能被惡意修改。

第二,保證 hash 屬性值不會(huì)頻繁變更,確保了唯一性,使得類(lèi)似 HashMap 容器才能實(shí)現(xiàn)相應(yīng)的 key-value 緩存功能。

第三,可以實(shí)現(xiàn)字符串常量池。

在 Java 中,通常有兩種創(chuàng)建字符串對(duì)象的方式:

第一種是通過(guò)字符串常量的方式創(chuàng)建,如String str = "abc"。

第二種是字符串變量通過(guò)new 形式的創(chuàng)建,如 String str = new String("abc")。

當(dāng)代碼中使用第一種方式創(chuàng)建字符串對(duì)象時(shí),在編譯類(lèi)文件時(shí),”abc”常量字符串將會(huì)放入到常量結(jié)構(gòu)中,在類(lèi)加載時(shí),“abc”將會(huì)在常量池中創(chuàng)建;然后,str將引用常量池中的字符串對(duì)象。這種方式可以減少同一個(gè)值的字符串對(duì)象的重復(fù)創(chuàng)建,節(jié)約內(nèi)存。

String str = new String("abc") 這種方式,首先在編譯類(lèi)文件時(shí),”abc”常量字符串將會(huì)放入到常量結(jié)構(gòu)中,在類(lèi)加載時(shí),“abc”將會(huì)在常量池中創(chuàng)建;其次,在調(diào)用new時(shí),JVM 命令將會(huì)調(diào)用 String 的構(gòu)造函數(shù),String 對(duì)象中的 char 數(shù)組將會(huì)引用常量池中”abc”字符串的char 數(shù)組,在堆內(nèi)存中創(chuàng)建一個(gè) String 對(duì)象;最后,str 將引用 String 對(duì)象,String對(duì)象的引用跟常量池中”abc”字符串的引用是不一樣的。

對(duì)象與引用:對(duì)象的內(nèi)容存儲(chǔ)在內(nèi)存中,操作系統(tǒng)通過(guò)內(nèi)存地址來(lái)找到存儲(chǔ)的內(nèi)容,引用就是指內(nèi)存的地址。

比如:String str = new String("abc"),變量str指向的是String對(duì)象的存儲(chǔ)地址,也就是說(shuō) str 并不是對(duì)象,而只是一個(gè)對(duì)象引用。

字符串拼接

常量相加

String str = "ab" + "cd" + "ef";

查看編譯后的字節(jié)碼

0 ldc #2 <abcdef>2 astore_13 return

可以發(fā)現(xiàn)編譯器將代碼優(yōu)化成如下所示

String str= "abcdef";

變量相加

String a = "ab";String b = "cd";String c = a + b;

查看編譯后的字節(jié)碼

 0 ldc #2 <ab>
 2 astore_1 3 ldc #3 <cd>
 5 astore_2 6 new #4 <java/lang/StringBuilder>
 9 dup10 invokespecial #5 <java/lang/StringBuilder.<init>>13 aload_114 invokevirtual #6 <java/lang/StringBuilder.append>17 aload_218 invokevirtual #6 <java/lang/StringBuilder.append>21 invokevirtual #7 <java/lang/StringBuilder.toString>24 astore_325 return

可以發(fā)現(xiàn),Java在進(jìn)行字符串相加的時(shí)候,底層使用的是StringBuilder,代碼被優(yōu)化成如下所示:

String c = new StringBuilder().append("ab").append("cd").toString();

String.intern

String a = new String("abc").intern();String b = new String("abc").intern();System.out.print(a == b);

輸出結(jié)果:

true

在字符串常量中,默認(rèn)會(huì)將對(duì)象放入常量池。例如:String a = "123"

在字符串變量中,對(duì)象是會(huì)創(chuàng)建在堆內(nèi)存中,同時(shí)也會(huì)在常量池中創(chuàng)建一個(gè)字符串對(duì)象,String 對(duì)象中的 char 數(shù)組將會(huì)引用常量池中的 char 數(shù)組,并返回堆內(nèi)存對(duì)象引用。例如:String b = new String("abc")

如果調(diào)用 intern 方法,會(huì)去查看字符串常量池中是否有等于該對(duì)象的字符串的引用,如果沒(méi)有,在 JDK1.6 版本中會(huì)復(fù)制堆中的字符串到常量池中,并返回該字符串引用,堆內(nèi)存中原有的字符串由于沒(méi)有引用指向它,將會(huì)通過(guò)垃圾回收器回收。

在 JDK1.7 版本以后,由于常量池已經(jīng)合并到了堆中,所以不會(huì)再?gòu)?fù)制具體字符串了,只是會(huì)把首次遇到的字符串的引用添加到常量池中;如果有,就返回常量池中的字符串引用。

下面開(kāi)始分析上面的代碼塊:

在一開(kāi)始字符串”abc”會(huì)在加載類(lèi)時(shí),在常量池中創(chuàng)建一個(gè)字符串對(duì)象。

創(chuàng)建 a 變量時(shí),調(diào)用 new Sting() 會(huì)在堆內(nèi)存中創(chuàng)建一個(gè) String 對(duì)象,String 對(duì)象中的 char 數(shù)組將會(huì)引用常量池中字符串。在調(diào)用 intern 方法之后,會(huì)去常量池中查找是否有等于該字符串對(duì)象的引用,有就返回常量池中的字符串引用。

創(chuàng)建 b 變量時(shí),調(diào)用 new Sting() 會(huì)在堆內(nèi)存中創(chuàng)建一個(gè) String 對(duì)象,String 對(duì)象中的 char 數(shù)組將會(huì)引用常量池中字符串。在調(diào)用 intern 方法之后,會(huì)去常量池中查找是否有等于該字符串對(duì)象的引用,有就返回常量池中的字符串引用。

而在堆內(nèi)存中的兩個(gè)String對(duì)象,由于沒(méi)有引用指向它,將會(huì)被垃圾回收。所以 a 和 b 引用的是同一個(gè)對(duì)象。

如果在運(yùn)行時(shí),創(chuàng)建字符串對(duì)象,將會(huì)直接在堆內(nèi)存中創(chuàng)建,不會(huì)在常量池中創(chuàng)建。所以動(dòng)態(tài)創(chuàng)建的字符串對(duì)象,調(diào)用 intern 方法,在 JDK1.6 版本中會(huì)去常量池中創(chuàng)建運(yùn)行時(shí)常量以及返回字符串引用,在 JDK1.7 版本之后,會(huì)將堆中的字符串常量的引用放入到常量池中,當(dāng)其它堆中的字符串對(duì)象通過(guò) intern 方法獲取字符串對(duì)象引用時(shí),則會(huì)去常量池中判斷是否有相同值的字符串的引用,此時(shí)有,則返回該常量池中字符串引用,跟之前的字符串指向同一地址的字符串對(duì)象。

以一張圖來(lái)總結(jié) String 字符串的創(chuàng)建分配內(nèi)存地址情況:

String字符串的示例分析

使用 intern 方法需要注意的一點(diǎn)是,一定要結(jié)合實(shí)際場(chǎng)景。因?yàn)槌A砍氐膶?shí)現(xiàn)是類(lèi)似于一個(gè) HashTable 的實(shí)現(xiàn)方式,HashTable 存儲(chǔ)的數(shù)據(jù)越大,遍歷的時(shí)間復(fù)雜度就會(huì)增加。如果數(shù)據(jù)過(guò)大,會(huì)增加整個(gè)字符串常量池的負(fù)擔(dān)。

判斷字符串是否相等

// 運(yùn)行環(huán)境 JDK1.8String str1 = "abc";String str2 = new String("abc");String str3= str2.intern();System.out.println(str1==str2); // falseSystem.out.println(str2==str3); // falseSystem.out.println(str1==str3); // true
// 運(yùn)行環(huán)境 JDK1.8String s1 = new String("1") + new String("1");s1.intern();String s2 = "11";System.out.println(s1 == s2); // true , 如果不執(zhí)行1.intern(),則返回false

String s1 = new String("1") + new String("1")會(huì)在堆中組合一個(gè)新的字符串對(duì)象"11",在s1.intern()之后,由于常量池中沒(méi)有該字符串的引用,所以常量池中生成一個(gè)堆中字符串"11"的引用,此時(shí)String s2 = "11"返回的是堆字符串"11"的引用,所以s1==s2

在JDK1.7版本以及之后的版本運(yùn)行以下代碼,你會(huì)發(fā)現(xiàn)結(jié)果為true,在JDK1.6版本運(yùn)行的結(jié)果卻為false:

String s1 = new String("1") + new String("1");System.out.println( s1.intern()==s1);

StringBuilder與StringBuffer

由于String的值是不可變的,這就導(dǎo)致每次對(duì)String的操作都會(huì)生成新的String對(duì)象,這樣不僅效率低下,而且大量浪費(fèi)有限的內(nèi)存空間。

和 String 類(lèi)不同的是,StringBuffer 和 StringBuilder 類(lèi)的對(duì)象能夠被多次的修改,并且不產(chǎn)生新的對(duì)象。

StringBuilder 類(lèi)在 Java 5 中被提出,它和 StringBuffer 之間的最大不同在于 StringBuilder 的方法不是線程安全的(不能同步訪問(wèn))。

由于 StringBuilder 相較于 StringBuffer 有速度優(yōu)勢(shì),所以多數(shù)情況下建議使用 StringBuilder 類(lèi)。然而在應(yīng)用程序要求線程安全的情況下,則必須使用 StringBuffer 類(lèi)。

以上是“String字符串的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI