String實現(xiàn)源碼
在java6之前嗽测,String對象主要有四個成員變量:char[]數(shù)組,offset偏移量,count字符數(shù)量驻呐,hash哈希值;通過offset和count兩個屬性可以定位char[]數(shù)組芳来,共享數(shù)組對象含末,但是有可能會導(dǎo)致內(nèi)存泄露。
泄露原因:調(diào)用subString獲取小段字符串時即舌,會共享原String對象佣盒,如果subString的對象一直被引用,且原String對象非常大顽聂,就會導(dǎo)致String對象的字符串一直無法被GC肥惭,出現(xiàn)內(nèi)存泄露。
java7/8去掉了offset和count屬性紊搪,同時修復(fù)了subString方法的bug蜜葱。
java9中維護(hù)了一個新的屬性coder,標(biāo)識字符串的字節(jié)編碼耀石;char[]改成byte[]笼沥,可以減少每一個字符的占用空間,由16字節(jié)減少為8字節(jié)娶牌。
截取部分java8字符串源碼
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
......
}
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
不可變性的好處
- 不可變對象不會被惡意修改奔浅,所以多線程共享時是線程安全的。
- hash屬性值一旦確定诗良,不會被變更汹桦,確保唯一性。
- 可以節(jié)約內(nèi)存鉴裹,實現(xiàn)字符串常量池舞骆。
==String str = "abc",String str = new String("abc")的區(qū)別==
String str = "abc"的方式會檢查對象是否在字符串常量池中,如果在径荔,就直接返回該對象的引用督禽;否則新的字符串將在常量池中被創(chuàng)建。
str = new String("abc")每次都會在堆中新建一個對象总处。
String使用優(yōu)化
字符串常量定義
字符串常量狈惫,使用String str = “test str”的方式來定義,==String str = “a” + “b” + “cc”鹦马,字符串常量的拼接編譯器會自動優(yōu)化成String str = “abcc”胧谈,但字符串變量的拼接則不是如此==
例如:
String str = "haha";
for(int i=0; i<10; i++) {
str = str + i;
}
編譯器會自動優(yōu)化成:
String str = "haha";
for(int i=0; i<10; i++) {
str = (new StringBuilder(String.valueOf(str))).append(i).toString();
}
這樣在循環(huán)體內(nèi)一直生成新的StringBuilder對象忆肾,性能是比較低的,這種情況下菱肖,最好在循環(huán)外層定義一個StringBuilder對象客冈,然后使用該對象進(jìn)行字符串的拼接。
String.intern大有可為
調(diào)用String的intern方法稳强,會檢查字符串常量池中是否有等于該對象的字符串场仲,如果沒有,就在常量池中新增該對象退疫,并返回對象的引用燎窘;如果有,就返回常量池中字符串的引用蹄咖。
==通俗點說褐健,針對某個字符串常量,大家是共用常量池中的字符串常量的澜汤。這個優(yōu)化蚜迅,導(dǎo)致的內(nèi)存空間節(jié)約可能是巨大的==
舉個例子:
我們有個居民信息管理系統(tǒng),存儲的信息涉及到每個居民的省份俊抵,城市等谁不,對應(yīng)每個person的關(guān)鍵字段有String province, String city徽诲,假設(shè)居民數(shù)量是10億刹帕,province和city的平均占用空間分別是10字節(jié)和5字節(jié)。
不使用String.intern時谎替,占用的空間可能就是10億15(字節(jié))
==如果使用String.intern偷溺,所有居民共用同一份常量池里的字符串資源。如果全國所有省市都遍歷完的話钱贯,大概占用空間為1540(省的個數(shù))*100(每個省城市的個數(shù))挫掏,差的可不是一兩個數(shù)量級。==
String經(jīng)典問題
對象地址是否相同
有了上面的基礎(chǔ)秩命,判斷定義字符串的地址是否相同就比較容易了尉共。
public static void main(String[] args) {
String str1= "abc";
String str2= new String("abc");
String str3= str2.intern();
System.out.println((str1 == str2));
System.out.println((str2 == str3));
System.out.println((str1 == str3));
}
//輸出
false
false
true
String、StringBuffer弃锐、StringBuilder區(qū)別
- String袄友,定義字符串常量,每次對字符串的修改霹菊,都會返回一個新的字符串對象剧蚣。如果涉及到字符串變量的拼接,不建議使用String。
- StringBuilder券敌,主要用于字符串變量的拼接,性能比String的拼接要高柳洋。線程不安全待诅。適用于單線程或沒有線程安全問題的字符串變量拼接。
-
StringBuffer熊镣,與StringBuilder類似卑雁,不過StringBuffer是線程安全的,也就是說任何對字符串的操作绪囱,都是加鎖的测蹲,所以性能比StringBuilder低。適用于多線程下字符串變量的修改鬼吵。