Java基礎(chǔ)類學(xué)習(xí)之String詳解
1 String不可變性
- String類被聲明為 final,因此它不可被繼承。
- 內(nèi)部使用char數(shù)組存儲(chǔ)數(shù)據(jù),該數(shù)組被聲明為final,這意味著value數(shù)組初始化之后就不能再指向其它數(shù)組。
- String內(nèi)部沒有改變value數(shù)組的方法
- String類中所有修改String值的方法,如果內(nèi)容沒有改變,則返回原來的String對(duì)象引用,如果改變了,創(chuàng)建了一個(gè)全新的String對(duì)象,包含修改后的字符串內(nèi)容,最初的String對(duì)象沒有任何改變。(目的:節(jié)約存儲(chǔ)空間、避免額外的開銷)
//String的類聲明以及value字段代碼:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[]; //字符數(shù)組存儲(chǔ)String的內(nèi)容
/** Cache the hash code for the string */
private int hash; // Default to 0
}
不可變的驗(yàn)證分析:
public class Immutable {
public static String upcase(String s) {
return s.toUpperCase();
}
public static void main(String[] args) {
String q = "howdy";
System.out.println(q); // howdy
String qq = upcase(q);
System.out.println(qq); // HOWDY
System.out.println(q); // howdy
}
} /* 輸出:
howdy
HOWDY
howdy
*///:~
- 當(dāng)把q傳給upcase0方法時(shí),實(shí)際傳遞的是引用的一個(gè)拷貝。
- upcase0方法中,傳入引用s,只有upcase0運(yùn)行的時(shí)候,局部引用s才存在。一旦upcase0運(yùn)行結(jié)束,s就消失。upcaseO的返回值是最終結(jié)果的引用。
- 綜上,upcase()返回的引用已經(jīng)指向了一個(gè)新的對(duì)象,而原本的q則還在原地。
延伸結(jié)論:
String對(duì)象作為方法的參數(shù)時(shí),都會(huì)復(fù)制一份引用,參數(shù)傳遞是引用的拷貝
2 不可變的好處
1. 可以緩存 hash 值
String的hash值經(jīng)常被使用,例如String用做HashMap的key。不可變的特性可以使得hash值也不可變,因此只需要進(jìn)行一次計(jì)算。
2. String Pool 的需要
如果一個(gè)String對(duì)象已經(jīng)被創(chuàng)建過了,那么就會(huì)從 String Pool 中取得引用。只有String是不可變的,才可能使用 String Pool。
3. 線程安全
String不可變性天生具備線程安全,可以在多個(gè)線程中安全地使用。
3 String+和StringBuilder效率差異
String使用“+”表示字符串拼接
先說結(jié)論:
- “+”操作,javac編譯器會(huì)自動(dòng)優(yōu)化為StringBuilder.append() 調(diào)用。
- StringBuilder要比“+”操作高效
- 涉及循環(huán)追加的,手動(dòng)創(chuàng)建StringBuilder對(duì)象操作比“+”操作編譯器優(yōu)化,更高效
驗(yàn)證:
public class StringBuilderTest {
public static void main(String[] args) {
String s1 = "ABC";
String s2 = "123";
String result = s1+s2;
System.out.println(result);
}
}
編譯并查看字節(jié)碼:javap -verbose StringBuilderTest.class

執(zhí)行過程:
調(diào)用了2次append()方法,最后調(diào)用StringBuilder.toString()返回最終結(jié)果
為什么StringBuilder要比+高效?
- +操作,按照:每次追加都創(chuàng)建新的String對(duì)象,把字符加入value數(shù)組中。這里產(chǎn)生一次對(duì)象創(chuàng)建操作,以及對(duì)應(yīng)的垃圾回收
- StringBuilder的底層數(shù)組value也是用到了char[],但它沒有聲明為final,故它可變,所以追加內(nèi)容時(shí)不用創(chuàng)建新的數(shù)組,而是直接修改value
- StringBuilder比+省去String對(duì)象創(chuàng)建以及垃圾回收的開銷,因此效率更高。
源碼追溯:
//StringBuilder.append()
@Override
public StringBuilder append(char c) {
super.append(c);
return this;
}
// 父類 AbstractStringBuilder.append()
@Override
public AbstractStringBuilder append(char c) {
ensureCapacityInternal(count + 1);
value[count++] = c;
return this;
}
//AbstractStringBuilder value 字段
abstract class AbstractStringBuilder implements Appendable, CharSequence {
//The value is used for character storage.
char[] value; // 沒有聲明為final,因此value可變
}
手動(dòng)實(shí)現(xiàn)StringBuilder對(duì)象操作比編譯器自行優(yōu)化,更高效:
- 通過字節(jié)碼分析可知(我這里省去了,可以自己實(shí)現(xiàn)驗(yàn)證):循環(huán)部分的代碼更簡短、更簡單,而且它只生成了一個(gè)StringBuilder對(duì)象。
- 顯式地創(chuàng)建StringBuilder還允許你預(yù)先為其指定大小。如果你已經(jīng)知道最終的字符串大概有多長,那預(yù)先指定StringBuilder的大小可以避免多次重新分配緩沖。
當(dāng)你為一個(gè)類編寫toString方法時(shí),如果字符串操作比較簡單,那就可以信賴編譯器,它會(huì)為你合理地構(gòu)造最終的字符串結(jié)果。但是,如果你要在toString0方法中使用循環(huán),那么最好自己創(chuàng)建一個(gè)StringBuilder對(duì)象來實(shí)現(xiàn)。
4 String, StringBuffer and StringBuilder
可變性
- String 不可變
- StringBuffer和StringBuilder可變
線程安全
- String不可變,因此是線程安全的
- StringBuilder 不是線程安全的
- StringBuffer 是線程安全的,內(nèi)部使用synchronized進(jìn)行同步
效率
- 如果要操作少量的數(shù)據(jù)用String
- 單線程環(huán)境且字符串緩沖區(qū)涉及大量數(shù)據(jù) StringBuilder
- 多線程環(huán)境且字符串緩沖區(qū)涉及大量數(shù)據(jù) StringBuffer
5 String與JVM內(nèi)存管理
一、引入字符串常量池
- Javac編譯后,字節(jié)碼文件中有一塊區(qū)域:常量池,存儲(chǔ)了包括類中聲明的字符串常量值等字面量
- 運(yùn)行時(shí),JVM開辟實(shí)際內(nèi)存空間:字符串常量值寫入了字符串常量池,屬于方法區(qū)的一部分。
案例1:
String s1 = "win"; String s2 = "win"; System.out.println(s1==s2); //輸出結(jié)果:true //引用 s1 s2 的值等于win在字符串常量池的地址值
結(jié)論:
引用 s1 s2 的值等于win在字符串常量池的地址值
分析字節(jié)碼的執(zhí)行過程:

案例2:
public class StringPool {
public static void main(String[] args) {
String s3 = new String("win");
String s4 = new String("win");
System.out.println(s3==s4);//false
}
}
結(jié)論:
通過new操作符創(chuàng)建的字符串對(duì)象不指向字符串池中的任何對(duì)象。
字節(jié)碼分析:

綜上:
public class StringPool {
public static void main(String[] args) {
String s1 = "win";
String s2 = "win";
String s3 = new String("win");
String s4 = new String("win");
System.out.println(s1==s2);//true
System.out.println(s1==s3);//false
System.out.println(s3==s4);//false
}
}
6 String api方法
從這個(gè)表中可以看出,當(dāng)需要改變字符串的內(nèi)容時(shí),String類的方法都會(huì)返回一個(gè)新的String對(duì)象。同時(shí),如果內(nèi)容沒有發(fā)生改變,String的方法只是返回指向原對(duì)象的引用而已。這可以節(jié)約存儲(chǔ)空間以及避免額外的開銷。


以上就是Java基礎(chǔ)類學(xué)習(xí)之String詳解的詳細(xì)內(nèi)容,更多關(guān)于Java String類的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章
相關(guān)文章
spring boot如何基于JWT實(shí)現(xiàn)單點(diǎn)登錄詳解
這篇文章主要介紹了spring boot如何基于JWT實(shí)現(xiàn)單點(diǎn)登錄詳解,用戶只需登錄一次就能夠在這兩個(gè)系統(tǒng)中進(jìn)行操作。很明顯這就是單點(diǎn)登錄(Single Sign-On)達(dá)到的效果,需要的朋友可以參考下2019-06-06
SpringBoot 集成 ShedLock 分布式鎖的示例詳解
ShedLock是一個(gè)在分布式環(huán)境中使用的定時(shí)任務(wù)框架,用于解決在分布式環(huán)境中的多個(gè)實(shí)例的相同定時(shí)任務(wù)在同一時(shí)間點(diǎn)重復(fù)執(zhí)行的問題,本文重點(diǎn)給大家介紹SpringBoot 分布式鎖ShedLock的相關(guān)知識(shí),感興趣的朋友一起看看吧2021-08-08
PowerJob的TimingStrategyHandler工作流程源碼解讀
這篇文章主要為大家介紹了PowerJob的TimingStrategyHandler工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
Spring?Boot自定義監(jiān)控指標(biāo)的詳細(xì)過程
這篇文章主要介紹了Spring?Boot如何自定義監(jiān)控指標(biāo)?,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03
Java BigDecimal詳解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
BigDecimal 由任意精度的整數(shù)非標(biāo)度值 和32 位的整數(shù)標(biāo)度 (scale) 組成。接下來通過本文給大家介紹Java BigDecimal詳解,需要的的朋友參考下吧2017-04-04
Maven項(xiàng)目如何在pom文件中引入lib下的第三方j(luò)ar包并打包進(jìn)去
在使用Maven進(jìn)行項(xiàng)目開發(fā)時(shí),引入第三方私有的Jar包可能會(huì)遇到問題,一種常見的解決方案是將Jar包添加到項(xiàng)目的lib目錄,并通過IDE進(jìn)行配置,但這需要每個(gè)開發(fā)者單獨(dú)操作,效率低下,更好的方法是通過Maven的pom.xml文件管理這些Jar包2024-09-09

