String類的實(shí)現(xiàn)方式
在Java 9之前,String類是由char數(shù)組實(shí)現(xiàn)的蟆湖,每個(gè)char占用兩個(gè)字節(jié)的內(nèi)存空間五嫂。而在Java 9中房官,String類引入了一種稱為"Compact Strings"的新實(shí)現(xiàn)方式切距,將字符串的表示方式從char數(shù)組改為byte數(shù)組朽缎,并使用一種編碼方式將Unicode字符映射到一個(gè)或兩個(gè)字節(jié)的表示方式惨远。這種實(shí)現(xiàn)方式可以大大減少內(nèi)存使用谜悟,尤其是對(duì)于包含大量ASCII字符的字符串。
為什么這么說(shuō)呢北秽?JDK9中葡幸,引入了一個(gè)coder標(biāo)識(shí),用來(lái)區(qū)分是普通的拉丁字母還是UTF16字符贺氓。
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
我們?cè)谌粘J褂弥锌赡芎芏嗲闆r下大量使用英文字母蔚叨,較少使用一些中文或者其他復(fù)雜的字符诀紊,這時(shí)候JDK9中的這種優(yōu)化就能有很大的用處问裕,因?yàn)榘凑赵綣DK8的方案抹蚀,無(wú)論是什么內(nèi)容晌端,就統(tǒng)一按照char來(lái)存儲(chǔ)呜象,這樣對(duì)于那些普通的英文字母根本不需要用兩個(gè)字節(jié)的空間斤讥,一個(gè)字節(jié)就夠了急侥,如果涉及到大量的這種純英文字母的字符串执赡,此時(shí)JDK9的存儲(chǔ)上的優(yōu)化就大大體現(xiàn)了出來(lái)尾抑。
String類中的方法
- 構(gòu)造方法
String()
: 創(chuàng)建一個(gè)空字符串歇父。String(char[] value)
: 創(chuàng)建一個(gè)包含指定字符序列的字符串蒂培。String(byte[] bytes)
: 使用默認(rèn)字符集解碼指定的字節(jié)數(shù)組,創(chuàng)建一個(gè)新的字符串榜苫。String(String original)
: 創(chuàng)建一個(gè)與指定字符串內(nèi)容相同的新字符串护戳。
- 字符串操作方法
charAt(int index)
: 返回指定位置上的字符。concat(String str)
: 將指定字符串連接到此字符串的末尾垂睬。substring(int beginIndex)
: 返回一個(gè)新的字符串媳荒,它是此字符串的子字符串。substring(int beginIndex, int endIndex)
: 返回一個(gè)新的字符串羔飞,它是此字符串的子字符串肺樟。replace(char oldChar, char newChar)
: 返回一個(gè)新字符串,它是通過(guò)用新字符替換此字符串中出現(xiàn)的所有舊字符得到的逻淌。replaceAll(String regex, String replacement)
: 用指定的字符串替換所有匹配給定的正則表達(dá)式的子字符串么伯。trim()
: 返回字符串的副本,忽略前導(dǎo)空白和尾部空白卡儒。toLowerCase()
: 使用默認(rèn)語(yǔ)言環(huán)境的規(guī)則將此字符串轉(zhuǎn)換為小寫田柔。toUpperCase()
: 使用默認(rèn)語(yǔ)言環(huán)境的規(guī)則將此字符串轉(zhuǎn)換為大寫。getBytes()
: 使用平臺(tái)默認(rèn)字符集將此字符串編碼為字節(jié)數(shù)組骨望。
- 字符串比較方法
equals(Object anObject)
: 將此字符串與指定對(duì)象進(jìn)行比較硬爆。equalsIgnoreCase(String anotherString)
: 將此字符串與指定字符串進(jìn)行比較,忽略大小寫差異擎鸠。compareTo(String anotherString)
: 按字典順序比較兩個(gè)字符串缀磕。compareToIgnoreCase(String str)
: 按字典順序比較兩個(gè)字符串,忽略大小寫差異劣光。
- 其他方法
length()
: 返回此字符串的長(zhǎng)度袜蚕。isEmpty()
: 當(dāng)且僅當(dāng)字符串長(zhǎng)度為 0 時(shí)返回 true。valueOf(int i)
: 返回 int 參數(shù)的字符串表示形式绢涡。join(CharSequence delimiter, CharSequence... elements)
: 將給定的字符串序列以指定的分隔符拼接起來(lái)牲剃,并返回結(jié)果字符串。
新增的repeat以及strip方法
- repeat()方法
repeat(int count)
方法可以將原字符串重復(fù)指定次數(shù)雄可,并返回一個(gè)新字符串凿傅。例如:
javaCopy code
String str = "hello";
String repeatedStr = str.repeat(3);
System.out.println(repeatedStr); // "hellohellohello"
在這個(gè)例子中,我們通過(guò)調(diào)用repeat()
方法將字符串"hello"重復(fù)了三次数苫,并返回了一個(gè)新字符串"hellohellohello"聪舒。
- strip()方法
strip()
方法用于去除字符串兩端的空白字符,包括空格虐急、制表符和換行符等箱残,返回一個(gè)新字符串。例如:
javaCopy code
String str = " hello \n";
String strippedStr = str.strip();
System.out.println(strippedStr); // "hello"
在這個(gè)例子中戏仓,原字符串為" hello \n"疚宇,包含兩個(gè)前導(dǎo)空格和一個(gè)換行符亡鼠,調(diào)用strip()
方法后返回的新字符串為"hello",兩端的空格和換行符都被去掉了敷待。
除了strip()
方法间涵,Java 9還新增了stripLeading()
和stripTrailing()
方法,分別用于去除字符串的前導(dǎo)空格和尾部空格榜揖。這些方法對(duì)于處理輸入數(shù)據(jù)和進(jìn)行字符串比較時(shí)非常有用勾哩。
字符串常量池的優(yōu)化
在Java 9之前,字符串常量池是在永久代(PermGen)中實(shí)現(xiàn)的举哟。這意味著在運(yùn)行時(shí)思劳,所有的字符串常量都被存儲(chǔ)在一塊固定的內(nèi)存區(qū)域中。這種實(shí)現(xiàn)方式存在一些問(wèn)題妨猩,比如常量池容易被填滿潜叛,導(dǎo)致OutOfMemoryError異常;并且壶硅,永久代是Java虛擬機(jī)中一個(gè)相對(duì)較小的區(qū)域威兜,因此可能會(huì)導(dǎo)致內(nèi)存不足的問(wèn)題。
在Java 8中庐椒,永久代被移除椒舵,字符串常量池被轉(zhuǎn)移到了堆(Heap)中。這種實(shí)現(xiàn)方式解決了一些問(wèn)題约谈,但仍然存在一些性能和內(nèi)存使用方面的問(wèn)題笔宿。在Java 9中,字符串常量池進(jìn)行了優(yōu)化棱诱,主要包括以下幾個(gè)方面:
字符串常量池被移到了元空間(Metaspace)中泼橘,這是一個(gè)更大的內(nèi)存區(qū)域,可以動(dòng)態(tài)調(diào)整大小军俊,從而避免了OutOfMemoryError異常侥加。
在元空間中捧存,字符串常量池使用了一種新的數(shù)據(jù)結(jié)構(gòu)粪躬,稱為“G1特殊化常量池”。這種數(shù)據(jù)結(jié)構(gòu)在性能和內(nèi)存使用方面都有所優(yōu)化昔穴,能夠更快地查找和添加常量镰官。
對(duì)于使用字符串常量的程序,編譯器現(xiàn)在會(huì)生成更高效的字節(jié)碼吗货,以利用這些優(yōu)化泳唠。例如,編譯器可以使用ldc2指令來(lái)加載常量池中的雙字節(jié)字符串宙搬,而不是使用兩個(gè)ldc指令笨腥。
總之拓哺,Java 9中對(duì)字符串常量池的優(yōu)化使得它更加高效和可靠,能夠更好地滿足大規(guī)模應(yīng)用程序的需要脖母。
性能分析
前面說(shuō)了這么多士鸥,都是關(guān)于JDK9性能上的一些優(yōu)化介紹,但是具體提升了多少呢谆级?下面使用一些直觀的例子來(lái)感受一下到底提升了多少:
字符串拼接
首先來(lái)看看字符串拼接烤礁,一直以來(lái)我們編程中有一個(gè)原則:對(duì)于頻繁拼接字符串的操作,不要直接使用String肥照,而是考慮使用StringBuilder或者StringBuffer脚仔,這是因?yàn)镾tring的不可變特性,導(dǎo)致在拼接過(guò)程中會(huì)產(chǎn)生大量的String對(duì)象從而導(dǎo)致內(nèi)存浪費(fèi):
運(yùn)行下面這段代碼:
long startTime = System.nanoTime();
String s = "";
for (int i = 0; i < 100000; i++) {
s += "a";
}
long endTime = System.nanoTime();
System.out.println("cost time: " + (endTime - startTime) + "ns");
將這段代碼分別放到JDK1.8和JDK1.9版本對(duì)比一下執(zhí)行時(shí)間舆绎,這里推薦一個(gè)在線的Java編譯運(yùn)行網(wǎng)站鲤脏,支持動(dòng)態(tài)選擇JDK版本:https://www.jdoodle.com/online-java-compiler
為了稍微準(zhǔn)確一點(diǎn),排除一定的偶然性吕朵,我JDK1.8和JDK1.9各自都運(yùn)行了三遍:
// JDK1.8
cost time: 8043104665ns
cost time: 8029676317ns
cost time: 8084159060ns
//JDK1.9
cost time: 1596969483ns
cost time: 2130420129ns
cost time: 2150930645ns
這里可以看到凑兰,在十萬(wàn)級(jí)別的字符串拼接操作中,JDK1.9的性能提升屬于倍數(shù)級(jí)別的提升了边锁,基本都在四倍左右的提升姑食。
同時(shí)每次執(zhí)行時(shí),頁(yè)面上結(jié)果輸出框的上方也有對(duì)應(yīng)的cpu time和memory數(shù)據(jù):
//JDK1.8
1394148 kilobyte(s)
1394176 kilobyte(s)
1394388 kilobyte(s)
//JDK1.9
660312 kilobyte(s)
660324 kilobyte(s)
660500 kilobyte(s)
可以看到茅坛,這內(nèi)存占用上直接下降了一個(gè)量級(jí)音半,由此可見(jiàn)JDK1.9在字符串的拼接這塊,性能上得到了巨大的提升贡蓖。
字符串替換
long startTime = System.nanoTime();
for (int i = 0; i < 100000; i++) {
String str = "abcdefg";
str.replace("c", "x");
}
long endTime = System.nanoTime();
System.out.println("cost time: " + (endTime - startTime) + "ns");
同樣的操作曹鸠,各自執(zhí)行3次看看情況:
//JDK1.8
cost time: 130509721ns
cost time: 226153962ns
cost time: 152726076ns
//JDK1.9
cost time: 53052617ns
cost time: 52959038ns
cost time: 66009582ns
/////////////memory////////
//JDK1.8
97120 kilobyte(s)
97096 kilobyte(s)
96936 kilobyte(s)
//JDK1.9
48488 kilobyte(s)
49688 kilobyte(s)
49732 kilobyte(s)
這里可以看到在效率上,直接下降了一個(gè)量級(jí)斥铺,同時(shí)在哪存上也有接近50%的節(jié)約彻桃。
至于其它一些常規(guī)的方法,比如:查找(indexOf)晾蜘、分割(split)這里JDK1.8和JDK1.9并沒(méi)有多少提升邻眷,甚至可能出現(xiàn)JDK1.9的執(zhí)行效率上反而更低,這并不是說(shuō)9版本中出現(xiàn)了倒退剔交,而是內(nèi)部實(shí)現(xiàn)的算法不一樣肆饶,可能在某些比較特殊的場(chǎng)景下,JDK1.9更有優(yōu)勢(shì)岖常,就像一些排序算法一樣驯镊,雖然時(shí)間復(fù)雜度可以衡量,但是在數(shù)據(jù)量不大的時(shí)候,可能一些復(fù)雜度高的算法反而效果更好板惑。
總體來(lái)說(shuō)橄镜,JDK1.9中String得split方法相比于JDK1.8,一般認(rèn)為有如下改進(jìn):
使用新的UTF-16字符串匹配器冯乘,代替了JDK 8中使用的正則表達(dá)式引擎蛉鹿。
改進(jìn)了分隔符的識(shí)別,當(dāng)分隔符為單個(gè)字符時(shí)往湿,使用位運(yùn)算進(jìn)行匹配妖异,避免了使用正則表達(dá)式引擎的開(kāi)銷。
引入了一種優(yōu)化领追,當(dāng)字符串不包含分隔符時(shí)他膳,不進(jìn)行任何操作直接返回原字符串。這里的好處就是避免一些極端情況下產(chǎn)生大量冗余重復(fù)的字符串對(duì)象
一些內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)上做了微調(diào)绒窑。