Java?String類的理解及字符串常量池介紹
一. String類簡(jiǎn)介
1. 介紹
字符串廣泛應(yīng)用 在 Java 編程中,在 Java 中字符串屬于對(duì)象,Java 提供了 String 類來創(chuàng)建和操作字符串。
Java的String類在lang包里,java.lang.String是java字符串類,包含了字符串的值和實(shí)現(xiàn)字符串相關(guān)操作的一些方法;java.lang包里面的類都不需要手動(dòng)導(dǎo)入,是由程序自動(dòng)導(dǎo)入。
String表示字符串類型,屬于引用數(shù)據(jù)類型, 內(nèi)部并不存儲(chǔ)字符串本身 ;Java 程序中的所有字符串字面值(如 “abc” )都作為此類的實(shí)例實(shí)現(xiàn)。
在String類的實(shí)現(xiàn)源碼中,String類實(shí)例變量如下:

public static void main(String[] args) {
// s1和s2引用的是不同對(duì)象 s1和s3引用的是同一對(duì)象
String s1 = new String("hello");
String s2 = new String("world");
String s3 = s1;
System.out.println(s1.length());
// 獲取字符串長(zhǎng)度---輸出5
System.out.println(s1.isEmpty());
// 如果字符串長(zhǎng)度為0,返回true,否則返回false
}
字符串存儲(chǔ)在字符串常量池中,后文中給出具體的理解與分析。
2. 字符串構(gòu)造
String類提供的構(gòu)造方式非常多,常用的就以下三種:
public static void main(String[] args) {
// 使用常量串構(gòu)造
String s1 = "hello bit";
System.out.println(s1);
// 直接newString對(duì)象
String s2 = new String("hello bit");
System.out.println(s1);
// 使用字符數(shù)組進(jìn)行構(gòu)造
char[] array = {'h','e','l','l','o','b','i','t'};
String s3 = new String(array);
System.out.println(s1);
}二. 字符串常量池(StringTable)
1. 思考?
通過下面的代碼,分析和思考字符串對(duì)象的創(chuàng)建和字符串常量池。
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s3 == s4); // false
}執(zhí)行及調(diào)試結(jié)果:

思考為什么執(zhí)行結(jié)果中,創(chuàng)建的字符串的地址是一樣的,使用new String的時(shí)候比較兩個(gè)對(duì)象的地址卻不一樣,直接使用字符串常量(“ ”)進(jìn)行賦值的兩個(gè)對(duì)象比較是相同的;
為什么s1和s2引用的是同一個(gè)對(duì)象,而s3和s4不是呢?
2. 介紹和分析
在Java程序中,類似于:1, 2, 3,3.14,“hello”等字面類型的常量經(jīng)常頻繁使用,為了使程序的運(yùn)行速度更快、 更節(jié)省內(nèi)存,Java為8種基本數(shù)據(jù)類型和String類都提供了常量池。
為了節(jié)省存儲(chǔ)空間以及程序的運(yùn)行效率,Java中引入了:
- Class文件常量池:每個(gè).Java源文件編譯后生成.Class文件中會(huì)保存當(dāng)前類中的字面常量以及符號(hào)信息
- 運(yùn)行時(shí)常量池:在.Class文件被加載時(shí),.Class文件中的常量池被加載到內(nèi)存中稱為運(yùn)行時(shí)常量池,運(yùn)行時(shí)常量池每個(gè)類都有一份
- 字符串常量池(StringTable) :字符串常量池在JVM中是StringTable類,實(shí)際是一個(gè)固定大小的HashTable(一種高效用來進(jìn)行查找的數(shù)據(jù)結(jié)構(gòu)),不同JDK版本下字符串常量池的位置以及默認(rèn)大小是不同的:
| JDK版本 | 字符串常量池位置 | 大小設(shè)置 |
|---|---|---|
| Java6 | (方法區(qū))永久代 | 固定大小:1009 |
| Java7 | 堆中 | 可設(shè)置,沒有大小限制,默認(rèn)大?。?0013 |
| Java8 | 堆中 | 可設(shè)置,有范圍限制,最小是1009 |
對(duì)1中的代碼進(jìn)行分析:
直接使用字符串常量進(jìn)行賦值
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true
}
當(dāng)字節(jié)碼文件加載時(shí),字符常量串“hello”已經(jīng)創(chuàng)建好了,并保存在字符串常量池中,
當(dāng)直接使用常量串賦值的時(shí)候( String s1 = “hello”;)會(huì)優(yōu)先從字符串常量池找,找到了就將該字符串引用賦值給要賦值的對(duì)象(s1和s2);
所以s1和s2內(nèi)放的都是字符串常量池中“hello”字符串所創(chuàng)建對(duì)象的引用,是相同的。
通過new創(chuàng)建String類對(duì)象


使用new來創(chuàng)建String對(duì)象,每次new都會(huì)新創(chuàng)建一個(gè)對(duì)象,每個(gè)對(duì)象的地址都是唯一的,所以s3和s4的引用是不相同的。
使用常量串創(chuàng)建String類型對(duì)象的效率更高,而且更節(jié)省空間。用戶也可以將創(chuàng)建的 字符串對(duì)象通過 intern 方式添加進(jìn)字符串常量池中。
3. intern方法
intern 是一個(gè)native方法(Native方法指:底層使用C++實(shí)現(xiàn)的,看不到其實(shí)現(xiàn)的源代碼);
該方法的作用是手動(dòng)將創(chuàng)建的String對(duì)象添加到常量池中。
public static void main(String[] args) {
char[] ch = new char[]{'a', 'b', 'c'};
String s1 = new String(ch); // s1對(duì)象并不在常量池中
//s1.intern();
//intern調(diào)用之后,會(huì)將s1對(duì)象的引用放入到常量池中
String s2 = "abc"; // "abc" 在常量池中存在了,s2創(chuàng)建時(shí)直接用常量池中"abc"的引用
System.out.println(s1 == s2);
}
// 輸出false
// 將上述方法打開之后,就會(huì)輸出true三. 面試題:String類中兩種對(duì)象實(shí)例化的區(qū)別
JDK1.8中
- String str = “hello”;只會(huì)開辟一塊堆內(nèi)存空間,保存在字符串常量池中,然后str共享常量池中的
- String對(duì)象String str = new String(“hello”);會(huì)開辟兩塊堆內(nèi)存空間,字符串"hello"保存在字符串常量池中,然后用常量池中的String對(duì)象給新開辟的String對(duì)象賦值。
- String str = new String(new char[]{‘h’, ‘e’, ‘l’, ‘l’, ‘o’})先在堆上創(chuàng)建一個(gè)String對(duì)象,然后利用copyof將重新開辟數(shù)組空間,將參數(shù)字符串?dāng)?shù)組中內(nèi)容拷貝到String對(duì)象中
四. 字符串的不可變性
String是一種不可變對(duì)象. 字符串中的內(nèi)容是不可改變。字符串不可被修改,是因?yàn)椋?/p>
String類在設(shè)計(jì)時(shí)就是不可改變的,String類實(shí)現(xiàn)描述中已經(jīng)說明了


String類中的字符實(shí)際保存在內(nèi)部維護(hù)的value字符數(shù)組中,該圖還可以看出:
- String類被final修飾,表明該類不能被繼承
- value被修飾被final修飾,表明value自身的值不能改變,即不能引用其它字符數(shù)組,但是其引用空間中的內(nèi)容可以修改。
- 字符串真正不能被修改的原因是,存儲(chǔ)字符串的value是被private修飾的,只能在String類中使用,但String中并沒有提供訪問value的公開方法
網(wǎng)上有些人說:字符串不可變是因?yàn)槠鋬?nèi)部保存字符的數(shù)組被final修飾了,因此不能改變;這種說法是錯(cuò)誤的,不是因?yàn)镾tring類自身,或者其內(nèi)部value被final修飾而不能被修改; final修飾類表明該類不想被繼承,final修飾引用類型表明該引用變量不能引用其他對(duì)象,但是其引用對(duì)象中的內(nèi) 容是可以修改的。
所有涉及到可能修改字符串內(nèi)容的操作都是創(chuàng)建一個(gè)新對(duì)象,改變的是新對(duì)象
比如 replace 方法:

注意:
盡量避免直接對(duì)String類型對(duì)象進(jìn)行修改,因?yàn)镾tring類是不能修改的,所有的修改都會(huì)創(chuàng)建新對(duì)象,效率非常低下。
public static void main(String[] args) {
String str = "";
for (int i = 0; i < 100; i++) {
str += i;
}
System.out.println(str);
}執(zhí)行結(jié)果:

這種方式不推薦使用,因?yàn)槠湫史浅5?,中間創(chuàng)建了好多臨時(shí)對(duì)象。
下圖是上面代碼的匯編,可以看到每一次循環(huán)都需要重新創(chuàng)建一個(gè)StringBuuilder對(duì)象,效率非常低。


- 創(chuàng)建一個(gè)StringBuild的對(duì)象,假設(shè)為temp
- 將str對(duì)象append(追加)temp之后
- 將"world"字符串a(chǎn)ppend(追加)在temp之后
- .temp調(diào)用其toString方法構(gòu)造一個(gè)新的String對(duì)象

將新String對(duì)象的引用賦直給str
將上述匯編過程轉(zhuǎn)化為類似代碼如下:
public static void main(String[] args) {
String str = "";
for (int i = 0; i < 100; i++) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(str);
stringBuilder.append(i);
str = stringBuilder.toString();
}
System.out.println(str);
}這里可以將上述代碼優(yōu)化一下進(jìn)行對(duì)比,只創(chuàng)建一次StringBuilder即可:
public static void main8(String[] args) {
String str = "";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(str);
for (int i = 0; i < 100; i++) {
stringBuilder.append(i);
}
System.out.println(stringBuilder);
}通過下面的代碼對(duì)比String和StringBuildder、StringBuffer效率上的差異:
ublic static void main(String[] args) {
long start = System.currentTimeMillis();
String s = "";
for(int i = 0; i < 10000; ++i){
s += i;
}
long end = System.currentTimeMillis();
System.out.println(end - start);
start = System.currentTimeMillis();
StringBuffer sbf = new StringBuffer("");
for(int i = 0; i < 10000; ++i){
sbf.append(i);
}
end = System.currentTimeMillis();
System.out.println(end - start);
start = System.currentTimeMillis();
StringBuilder sbd = new StringBuilder();
for(int i = 0; i < 10000; ++i){
sbd.append(i);
}
end = System.currentTimeMillis();
System.out.println(end - start);
}執(zhí)行結(jié)果:

可以看出在對(duì)String類進(jìn)行修改時(shí),效率是非常慢的,因此:盡量避免對(duì)String的直接需要,如果要修改建議盡量 使用StringBuffer或者StringBuilder。
到此這篇關(guān)于Java String類的理解及字符串常量池介紹的文章就介紹到這了,更多相關(guān)Java String類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis千萬級(jí)數(shù)據(jù)查詢的解決方式,避免OOM問題
這篇文章主要介紹了Mybatis千萬級(jí)數(shù)據(jù)查詢的解決方式,避免OOM問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
Java利用釘釘機(jī)器人實(shí)現(xiàn)發(fā)送群消息
這篇文章主要為大家詳細(xì)介紹了Java語言如何通過釘釘機(jī)器人發(fā)送群消息通知,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-09-09
Java Spring Boot實(shí)現(xiàn)簡(jiǎn)易掃碼登錄詳解
這篇文章主要為大家詳細(xì)介紹了java Spring Boot實(shí)現(xiàn)app掃碼登錄功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2021-09-09

