Java字符串常量池用法及說明(StringTable)
前言:
在介紹字符串常量池之前,我們先來簡(jiǎn)單了解下Java中字符串的概念以及常見的一些問題.
一、字符串
Java字符串其實(shí)只是Unicode字符序列,Java本身沒有內(nèi)置的字符串類型,而是在標(biāo)準(zhǔn)Java類庫中提供了一個(gè)預(yù)定類,很自然的叫做String,每一個(gè)雙引號(hào)引起來的字符串都是String類的一個(gè)實(shí)例:
String s = "";//空的字符串
String str = "Hello";1.1 子串
提到子串,那就免不了介紹以下subString這個(gè)方法了,該方法的功能為:從一個(gè)較大的字符串中提取一個(gè)子串。
- 其一為,subString(x,y):(注意這里是左閉右開區(qū)間) 所以該方法是截取下標(biāo)x~y-1 的字符串。
- 其二為,subString(x) : 該方法是截去前x個(gè)字符串后的字符串。
具體情況如下代碼:

ps:subString 的工作方式有一個(gè)優(yōu)點(diǎn),容易計(jì)算子串的長(zhǎng)度,比如:字符串s.subString(a,b) 的長(zhǎng)度就為b-a。
1.2 拼接
1.2.1 字符串與字符串拼接
與絕大多數(shù)設(shè)計(jì)語言一樣,Java語言允許使用+號(hào)來連接兩個(gè)字符串。
下述代碼將“hello”賦值給了s3變量:

1.2.2 字符串與非字符串拼接
當(dāng)將一個(gè)字符串與非字符串的值進(jìn)行拼接的時(shí)候,后者會(huì)轉(zhuǎn)換成字符串,例如:

此特性其實(shí)我們經(jīng)常在打印時(shí)候用到:

注意:這里是字符串與非字符拼接,并不是字符與非字符拼接:
如果是后者的話;可能會(huì)轉(zhuǎn)變成數(shù)字,例:

1.3 字符串的不可變性
String類中沒有提供修改字符串中某個(gè)字符的方法,如果希望將”hello“修改為“help!” ,只能提取想要保存的字符串,再與希望替換的字符串拼接,比如:

分析:
由于不能修改Java字符串中的某個(gè)字符,所以Java我、文檔中將String類稱之為不可變的,不過可以修改s指向的值,讓它引用另外的字符串。
可能有人會(huì)說,拼接字符串這樣的做法是否會(huì)降低運(yùn)行效率呢?
答案其實(shí)并不確定,雖然通過拼接的方法,讓s2指向了"help!"看上去有些低效,但是不可變字符串卻有一個(gè)優(yōu)點(diǎn):編譯器可以讓字符串共享。
原因:

通過觀察源碼可知,String類的value是被private修飾的,類外是拿不到這個(gè)值的。
可能很多人以為String類不可變是因?yàn)関alue[] 這個(gè)數(shù)組被final修飾,但其實(shí)并不是這樣的,因?yàn)関alue存儲(chǔ)的是一個(gè)引用,而不是常量。
下面用代碼詮釋一下:

我們發(fā)現(xiàn),arr[1]的值的確被改為了c。準(zhǔn)確的說 這里的final限制的是arr指向的值不能改變,也就是不能重新指向一個(gè)新的數(shù)組對(duì)象。

總結(jié):
- String類被final修飾,表明該類不能被繼承
- value被修飾被final修飾,表明value自身的值不能改變,即不能引用其它字符數(shù)組,但是其引用空間中的內(nèi)容可以修改。
所以一些設(shè)計(jì)字符串修改的函數(shù),都是創(chuàng)建一個(gè)新的對(duì)象,然后修改新的對(duì)象。

注意 += 這種的拼接操作,也不是在String類自身上進(jìn)行的,而是通過中間創(chuàng)建許多臨時(shí)變量而完成的,效率很低下。于是引入了StringBuffer和StringBuilder。
以下代碼可以看出String類在拼接時(shí)的效率比StringBuffer和StringBuilder低:
public static void main(String[] args) {
long start = System.currentTimeMillis();
String s = "";
for(int i = 0; i < 1_0000; ++i){
s += i;
}
long end = System.currentTimeMillis();
System.out.println(end - start);
start = System.currentTimeMillis();
StringBuffer sbf = new StringBuffer("");
for(int i = 0; i < 1_0000; ++i){
sbf.append(i);
}
end = System.currentTimeMillis();
System.out.println(end - start);
start = System.currentTimeMillis();
StringBuilder sbd = new StringBuilder();
for(int i = 0; i < 1_0000; ++i){
sbd.append(i);
}
end = System.currentTimeMillis();
System.out.println(end - start);
}
1.3.1為什么設(shè)計(jì)成不可變的
為什么 String 要涉及成不可變的?(不可變對(duì)象的好處是什么?)
- 方便實(shí)現(xiàn)字符串對(duì)象池. 如果 String 可變, 那么對(duì)象池就需要考慮寫時(shí)拷貝的問題了.
- 不可變對(duì)象是線程安全的.
- 不可變對(duì)象更方便緩存 hash code, 作為 key 時(shí)可以更高效的保存到 HashMap 中.
1.4 判斷字符串是否相等
可以使用equals方法來檢測(cè)字符串是否相等,形如:s.equals(r); 如果字符串s與字符串r相等,則返回true,否則返回false。
需要注意的是:s與t可以是字符串常量,也可以是字符串字面量(用雙引號(hào)引起來的部分)。
比如以下的語句也是合法的:

????千萬不要使用 == 來判斷兩個(gè)字符串是否相等,這個(gè)運(yùn)算符只能確認(rèn)兩個(gè)字符串是否處在同一位置,雖然,字符串在同一位置上,它們必然相等, 但是完全可能將內(nèi)容相同的多個(gè)字符串放在不同的位置上。
例如:

如果虛擬機(jī)始終將相同的字符串共享,就可以使用 == 運(yùn)算符來檢測(cè)是否相等,但實(shí)際上字符串字面量是共享的,而+ 或 substring 等操作得到的字符串并不共享,因此千萬不能使用==來判斷字符串的相等性,以免出現(xiàn)bug。
二、字符串常量池
字符串常量池在JVM中是StringTable類,實(shí)際是一個(gè)固定大小的HashTable(一種高效用來進(jìn)行查找的數(shù)據(jù)結(jié)構(gòu),后序給大家詳細(xì)介紹),不同JDK版本下字符串常量池的位置以及默認(rèn)大小是不同的:
| JDK版本 | 字符串常量池位置 | 大小設(shè)置 |
| Java6 | (方法區(qū))永久代 | 固定大?。?009 |
| Java7 | 堆中 | 可設(shè)置,沒有大小限制,默認(rèn)大?。?0013 |
| Java8 | 堆中 | 可設(shè)置,有范圍限制,最小是1009 |
2.1 字符串常量池的應(yīng)用
2.1.1 再談String對(duì)象創(chuàng)建

大家可能會(huì)好奇,為什么s1,s2指向的是同一個(gè)對(duì)象,而s3,s4指向的對(duì)象卻不相同呢?
在Java程序中,類似于:1, 2, 3,3.14,“hello”等字面類型的常量經(jīng)常頻繁使用,為了使程序的運(yùn)行速度更快、更節(jié)省內(nèi)存,提供了一個(gè)String類型的常量池,只要是雙引號(hào)引起來的對(duì)象,都會(huì)存放在常量池中。

分析:因?yàn)閟1,s2分別都是被雙引號(hào)引起來的對(duì)象,所以都會(huì)被存儲(chǔ)到常量池中,可能有人會(huì)問,為什么s2并沒有單獨(dú)在常量池中存儲(chǔ)一份而是引用了s1所指向的對(duì)象呢?
那是因?yàn)楸灰?hào)引起來的對(duì)象,會(huì)先到常量池中尋找是否已經(jīng)存在相同的內(nèi)容,如果存在相同內(nèi)容,那么就直接引用它,就不需要再創(chuàng)建一個(gè)對(duì)象了(這也是為什么常量池效率高的原因)。
現(xiàn)在我們?cè)賮砜磶捉M代碼;
代碼1:

我們可以發(fā)現(xiàn),s1,s2,s3都引用了同一個(gè)數(shù)組對(duì)象,這里s3不與s1,s2相同的原因是因?yàn)?,s3是通過new了一個(gè)String對(duì)象,而String對(duì)象里面的value值存儲(chǔ)的是hello的地址值。

代碼2:


下面難度加大一點(diǎn):

小插曲:
這里需要注意的是s1的new string對(duì)象中存放的是數(shù)組,根據(jù)源碼我們可以得知,是將這個(gè)ch數(shù)組拷貝一份再存入string對(duì)象中的。

但是我們只要加入intern這個(gè)方法,就可以讓其相等。
原因:intern 是一個(gè)native方法(Native方法指,底層使用C++實(shí)現(xiàn)的,看不到其實(shí)現(xiàn)的源代碼)
該方法會(huì)檢查常量池中是否有這個(gè)對(duì)象,如果無,則入池(入池并不會(huì)將自身銷毀,而是在常量池中添加這個(gè)對(duì)象),有的話,則返回該對(duì)象(并不會(huì)再次入池了)。

如果將這個(gè)順序一調(diào)換,就輸出false。
原因也很簡(jiǎn)單,因?yàn)橐呀?jīng)在常量池中存在這個(gè)”abc“了,不會(huì)再將s1的string對(duì)象的abc再次入池。

總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringMVC4.3?HttpMessageConverter接口實(shí)現(xiàn)源碼分析
這篇文章主要為大家介紹了SpringMVC4.3?HttpMessageConverter接口實(shí)現(xiàn)源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Spring Cloud超詳細(xì)i講解Feign自定義配置與使用
這篇文章主要介紹了SpringCloud Feign自定義配置與使用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06
springboot logback調(diào)整mybatis日志級(jí)別無效的解決
這篇文章主要介紹了springboot logback調(diào)整mybatis日志級(jí)別無效的解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
java通過snmp協(xié)議獲取物理設(shè)備信息
這篇文章主要介紹了java通過snmp協(xié)議獲取物理設(shè)備信息,snmp中文含義是簡(jiǎn)單網(wǎng)絡(luò)管理協(xié)議,可用完成對(duì)計(jì)算機(jī)、路由器和其他網(wǎng)絡(luò)設(shè)備的遠(yuǎn)程管理和監(jiān)視,本文我們是通過java程序來獲取,需要的朋友可以參考下2023-07-07

