StringTabele
String的基本特性
-
String:字符串,使用一對 “” 引起來表示
String s1 = "mogublog" ; // 字面量的定義方式 String s2 = new String("moxi"); // new 對象的方式
-
String聲明爲final的,不可被繼承
-
String實現了Serializable接口:表示字符串是支持序列化的。實現了Comparable接口:表示String可以比較大小
-
string在jdk8及以前內部定義了final char[] value用於存儲字符串數據。JDK9時改爲byte[],同時基於String的數據結構,例如StringBuffer和StringBuilder也同樣做了修改
jdk8及以前:
jdk9及以後:
爲什麼 JDK9 改變了 String 的結構(char改爲byte)
改爲 byte[] 存儲的原因
- char數組中,每個字符使用兩個字節(16位)。
- 從許多不同的應用程序收集的數據表明,字符串是堆使用的主要組成部分,而且大多數字符串對象只包含拉丁字符。這些字符只需要一個字節的存儲空間,因此這些字符串對象的內部char數組中有一半的空間將不會使用。
- 因此String再也不用char[] 來存儲了,改成了byte [] 加上編碼標記,節約了一些空間
之前 String 類使用 UTF-16 的 char[] 數組存儲,現在改爲 byte[] 數組 外加一個編碼標誌位存儲,該編碼標誌將指定 String 類中 byte[] 數組的編碼方式
-
String:代表不可變的字符序列。簡稱:不可變性。
-
通過字面量的方式(區別於new)給一個字符串賦值,此時的字符串值聲明在字符串常量池中。
-
字符串常量池是不會存儲相同內容的字符串的,因爲底層用的hashtable
Java語言規範裏要求完全相同的字符串字面量,應該包含同樣的Unicode字符序列(包含同一份碼點序列的常量),並且必須是指向同一個String類實例。
面試題
String 底層 Hashtable 結構
- String的String Pool是一個固定大小的Hashtable
hashtable就是數組加鏈表 - 使用-XX:StringTablesize可設置StringTable的長度
- StringTable的長度是固定的,不同的jdk版本默認值不一樣
-
在JDK6中StringTable是固定的,就是1009的長度,所以如果常量池中的字符串過多就會導致效率下降很快,StringTablesize設置的大小沒有要求
-
在JDK7中,StringTable的長度默認值是60013,StringTablesize設置的大小沒有要求
-
在JDK8中,StringTable的長度默認值是60013,StringTable可以設置的最小值爲1009
設置的值小於1009就報錯了
-
爲什麼要idk要增大stringtable的長度
如果放進String Pool的String非常多,就會造成Hash衝突嚴重,從而導致鏈表會很長,而鏈表長了後直接會造成的影響就是當調用String.intern()方法時性能會大幅下降。
String 內存結構的分配位置
-
在Java語言中有8種基本數據類型和一種比較特殊的類型String。這些類型爲了使它們在運行過程中速度更快、更節省內存,都提供了一種常量池的概念。
-
常量池就類似一個Java系統級別提供的緩存。8種基本數據類型的常量池都是系統協調的,String類型的常量池比較特殊。它的主要使用方法有兩種。
- 直接使用雙引號聲明出來的String對象會直接存儲在常量池中。
比如:String info=“atguigu.com”; - 如果不是用雙引號聲明的String對象,可以使用String提供的intern()方法。
- 直接使用雙引號聲明出來的String對象會直接存儲在常量池中。
String在不同jdk版本中存在的位置
-
Java 6及以前,字符串常量池存放在永久代
-
Java 7中 Oracle的工程師對字符串池的邏輯做了很大的改變,即將字符串常量池的位置調整到Java堆內
所有的字符串都保存在堆(Heap)中,和其他普通對象一樣,這樣可以讓你在進行調優應用時僅需要調整堆大小就可以了。
字符串常量池概念原本使用得比較多,但是這個改動使得我們有足夠的理由讓我們重新考慮在Java 7中使用String.intern()。 -
Java8元空間,字符串常量在堆
調整String位置的原因
- 永久代默認比較小,如果放大量的字符串就容易OOM
- 永久代垃圾回收頻率低
- 堆中空間足夠大,字符串可被及時回收
String 的基本操作
字符串拼接操作
字符串拼接相關面試題
字符串拼接操作的相關面試題:
常量與常量的拼接結果在常量池,原理是編譯期優化
編譯期優化也就是生成字節碼文件的時候就直接等同於拼接後的結果了
示例:
因爲我們將.class 反編譯後的結果直接就如下:
也就是執行String s1=“abc"的時候就像常量池中放了"abc”,第二次就沒有放
或者直接看字節碼也可以看出來第一行ldc就是從字符串常量池加載abc,說明已經創建了abc
拼接前後,只要其中有一個是變量,結果就在堆中(堆中除字符串常量的空間),相當於新new了一個對象。StringBuilder原理
調用 intern() 方法,則主動將字符串對象存入字符串常量池中,並將其地址返回
拼接操作底層原理
只要出現變量就會new StringBuilder(5.0之後使用的StringBuilder,之前使用的是StringBuffer)
s1+s2 實際上是通過兩次append方法實現的
查看字節碼文件:
分析拼接的步驟:
-
new StringBuilder()
9 new #9 <java/lang/StringBuilder> 12 dup 13 invokespecial #10 <java/lang/StringBuilder.<init>>
-
加載字符串變量,進行 append 操作
16 aload_1 17 invokevirtual #11 <java/lang/StringBuilder.append> 20 aload_2 21 invokevirtual #11 <java/lang/StringBuilder.append> 24 invokevirtual #12 <java/lang/StringBuilder.toString>
-
調用 StringBuilder 類的 toString() 方法,轉換爲字符串,並存儲在局部變量中
24 invokevirtual #12 <java/lang/StringBuilder.toString> 27 astore 4
toString()方法 約等於new String
- 字符串拼接操作不一定使用的是StringBuilder!
如果拼接符號左右兩邊都是字符串常量或常量引用,則仍然使用編譯期優化,即非StringBuilder的方式。 - 因此針對於final修飾類、方法、基本數據類型、引用數據類型的量的結構時,能使用上final的時候建議使用上。