String 源碼學(xué)習(xí)
1. String
1.1不變性
不可變指的是類值一旦被初始化腕柜,就不能被修改,如果被修改那將會是新的類。
String s = "hello";
s = "world";
從代碼上看前弯,s的值好像被修改了但是debug來看
s的內(nèi)存地址已經(jīng)被修改了,也就是說 s= "world",看似簡單的賦值秫逝,其實已經(jīng)將s的引用指向了新的String恕出。
補充(使用idea dubug模式,@XXX;就是變量的相對內(nèi)存地址)
查看源碼
通過源碼可以看出
- string類被final修飾违帆,說明string類不能夠被繼承浙巫,也就是說對任何string的操作方法,都不會被繼承覆蓋。
- string保存數(shù)據(jù)是byte[]的畴。我們看到value也是被final修飾的渊抄,也就是說value一旦被賦值,那內(nèi)存地址就無法被修改丧裁,而且value的權(quán)限是private的护桦,外部訪問不到,string也沒有開放出對value進行賦值的方法煎娇,所以說value一旦產(chǎn)生二庵,內(nèi)存地址根本無法被修改。
注:(在jdk8中string實現(xiàn)是char[] ,而在jdk9中變更成byte[] ,使用byte數(shù)組可以減少一半的內(nèi)存缓呛,byte使用一個字節(jié)來存儲一個char字符催享,char使用兩個字節(jié)來存儲一個char字符。只有當(dāng)一個char字符大小超過0xFF時哟绊,才會將byte數(shù)組變?yōu)樵瓉淼膬杀兑蛎睿脙蓚€字節(jié)存儲一個char字符。)
以上兩點是string不變性原因票髓,充分利用了final關(guān)鍵字的特性兰迫,如果你定義類時也希望是不變的,可以參考string這兩點操作炬称。
因為string的不變性汁果,所以string的大多數(shù)操作方法,都會返回新的string玲躯,下面需要注意
String str ="hello world";
// 這種寫法是替換不掉的据德,必須接受 replace 方法返回的參數(shù)才行,這樣才行:str = str.replace("h","xx");
str.replace("h","xx");
System.out.println(str);
str = str.replace("d", "qq");
System.out.println(str);
1.2 字符串亂碼
在平時進行二進制轉(zhuǎn)化操作時跷车,本地測試沒問題棘利,但是運行到其他機器上時,會出現(xiàn)字符串亂碼情況朽缴,主要原因是在進行二進制轉(zhuǎn)化操作時善玫,并沒有強制規(guī)定文件編碼,而不同的環(huán)境默認(rèn)的文件編碼不一致導(dǎo)致密强。
輸出 : nihao ?? ??
打印的結(jié)果為茅郎??或渤,這就是常見的亂碼表現(xiàn)形式系冗。這時候有人說,是不是我把代碼修改成 String s2 = new String(bytes,"ISO-8859-1"); 就可以了薪鹦?這是不行的掌敬。主要是因為 ISO-8859-1 這種編碼對中文的支持有限惯豆,導(dǎo)致中文會顯示亂碼。唯一的解決辦法奔害,就是在所有需要用到編碼的地方楷兽,都統(tǒng)一使用 UTF-8,對于 String 來說华临,getBytes 和 new String 兩個方法都會使用到編碼拄养,我們把這兩處的編碼替換成 UTF-8 后,打印出的結(jié)果就正常了银舱。
1.3首字母大小寫
如果項目被spring托管,有時候我們會通過 application.getBean(className); 這種方式得到SpringBean跛梗,這時className必須滿足首字母小寫寻馏。除了該場景,在反射場景下核偿,我們也經(jīng)常要使類屬性的首字母小寫诚欠。
str.substring(0,1).toLowerCase()+ str.substring(1);
使用 substring 方法,該方法主要是為了截取字符串連續(xù)的一部分漾岳,substring 有兩個方法:
/*beginIndex :開始位置轰绵,結(jié)束位置為文本末尾 */
public java.lang.String substring(int beginIndex)
/*beginIndex :開始位置,endIndex 結(jié)束位置 */
public java.lang.String substring(int beginIndex, int endIndex)
substring 方法的底層使用的是字符數(shù)組范圍截取的方法 :
Arrays.copyOfRange(字符數(shù)組, 開始位置, 結(jié)束位置);
從字符數(shù)組中進行一段范圍的拷貝尼荆。
1.4相等判斷
我們判斷相等有兩種辦法左腔,equals 和 equalsIgnoreCase。后者判斷相等時捅儒,會忽略大小寫液样。
一些面試題在問:如果讓你寫判斷兩個 String 相等的邏輯,應(yīng)該如何寫巧还,我們來一起看下 equals 的源碼(之前的源碼)鞭莽,整理一下思路:
public boolean equals(Object anObject) {
// 判斷內(nèi)存地址是否相同
if (this == anObject) {
return true;
}
// 待比較的對象是否是 String,如果不是 String麸祷,直接返回不相等
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
// 兩個字符串的長度是否相等澎怒,不等則直接返回不相等
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// 依次比較每個字符是否相等,若有一個不等阶牍,直接返回不相等
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
目前源碼
String重寫Object的equals方法喷面,先用“==”判斷地址,地址相同則直接返回true走孽;然后再比較類型乖酬,類型不同則直接返回false;最后才比較內(nèi)容融求。
用于比較兩字符串對象是否相等咬像,如果引用相同則返回 true。否則判斷比較對象是否為 String 類的實例,是的話轉(zhuǎn)成 String 類型县昂,接著比較編碼是否相同肮柜,分別以 LATIN1 編碼和 UTF16 編碼進行比較。
代碼如下:
public boolean equals(Object anObject) {
//判斷地址是否相等
if (this == anObject) {
return true;
} else {
//比較類型
if (anObject instanceof String) {
//比較內(nèi)容
//轉(zhuǎn)成 String 類型倒彰,接著比較編碼是否相同审洞,
//分別以 LATIN1 編 碼和 UTF16 編碼進行比較
String aString = (String)anObject;
if (this.coder() == aString.coder()) {
return this.isLatin1() ? StringLatin1.equals(this.value, aString.value) : StringUTF16.equals(this.value, aString.value);
}
}
return false;
}
}
由于equals是Objec的方法,意味著任意引用類型對象都可以調(diào)用待讳,而且芒澜,入?yún)⑹荗bject類型,所以创淡,不同類型是可以用equals()方法的痴晦,不會像“==”一樣編譯異常。
注:會遇到的一個小坑琳彩,例如:char chr = ‘a(chǎn)’誊酌,String str = “a”,我經(jīng)常會寫成str.equals(chr)露乏,而且還傻傻的等著返回true碧浊,上面說到過,兩個不同類型的變量比較瘟仿,equals()會直接返回false箱锐。str.equals(chr+"")倒是可以解決。
1.5替換劳较、刪除
有 replace 替換所有字符瑞躺、replaceAll 批量替換字符串、replaceFirst 替換遇到的第一個字符串三種場景兴想。
replace 有兩個方法幢哨,一個入?yún)⑹?char,一個入?yún)⑹?String嫂便,
前者表示替換所有字符捞镰,如:str.replace('a','b')
后者表示替換所有字符串,如:str.replace("a","b")
兩者就是單引號和多引號的區(qū)別毙替。
需要注意的是岸售, replace 并不只是替換一個,是替換所有匹配到的字符或字符串哦厂画。
如果是刪除 某些字符凸丸,也可以使用 replace 方法,把想刪除的字符替換成 “” 即可
1.6拆分袱院,合并
拆分我們使用 split 方法屎慢,該方法有兩個入?yún)?shù)瞭稼。第一個參數(shù)是我們拆分的標(biāo)準(zhǔn)字符,第二個參數(shù)是一個 int 值腻惠,叫 limit环肘,來限制我們需要拆分成幾個元素。如果 limit 比實際能拆分的個數(shù)小集灌,按照 limit 的個數(shù)進行拆分悔雹。
從演示的結(jié)果來看,limit 對拆分的結(jié)果欣喧,是具有限制作用的腌零,還有就是拆分結(jié)果里面不會出現(xiàn)被拆分的字段。
那如果字符串里面有一些空值呢唆阿,空值是拆分不掉的益涧,仍然成為結(jié)果數(shù)組的一員,如果我們想刪除空值酷鸦,只能自己拿到結(jié)果后再做操作。
但 Guava(Google 開源的技術(shù)工具) 提供了一些可靠的工具類牙咏,可以幫助我們快速去掉空值臼隔,如下:
List<String> on = Splitter.on(":")
.trimResults() //去掉空值
.omitEmptyStrings() //去掉空格
.splitToList(s);
輸出:
[bbb, aaa, oo, o, asd]
合并我們使用 join 方法,此方法是靜態(tài)的妄壶,我們可以直接使用摔握。方法有兩個入?yún)ⅲ瑓?shù)一是合并的分隔符丁寄,參數(shù)二是合并的數(shù)據(jù)源氨淌,數(shù)據(jù)源支持?jǐn)?shù)組和 List,在使用的時候伊磺,我們發(fā)現(xiàn)有兩個不太方便的地方:
- 不支持依次 join 多個字符串盛正,比如我們想依次 join 字符串 s 和 s1,如果你這么寫的話 String.join(",",s).join(",",s1) 最后得到的是 s1 的值屑埋,第一次 join 的值被第二次 join 覆蓋了豪筝;
- 如果 join 的是一個 List,無法自動過濾掉 null 值摘能。
// 依次 join 多個字符串续崖,Joiner 是 Guava 提供的 API
Joiner joiner = Joiner.on(",").skipNulls();
String result = joiner.join("hello",null,"china");
List<String> list = Lists.newArrayList(new String[]{"hello","china",null});
// 輸出的結(jié)果為;
依次 join 多個字符串:hello,china
自動刪除 list 中空值:hello,china