您好,登錄后才能下訂單哦!
這篇文章主要介紹“分析JDK中String的存儲區(qū)與不可變性”,在日常操作中,相信很多人在分析JDK中String的存儲區(qū)與不可變性問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”分析JDK中String的存儲區(qū)與不可變性”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
1. 數(shù)據(jù)存儲區(qū)
String是一個比較特殊的類,除了new之外,還可以用字面常量來定義。為了弄清楚這二者間的區(qū)別,首先我們得明白JVM運行時數(shù)據(jù)存儲區(qū),這里有一張圖對此有清晰的描述:
非共享數(shù)據(jù)存儲區(qū)
非共享數(shù)據(jù)存儲區(qū)是在線程啟動時被創(chuàng)建的,包括:
程序計數(shù)器(program counter register)控制線程的執(zhí)行;
棧(JVM Stack, Native Method Stack)存儲方法調(diào)用與對象的引用等。
共享數(shù)據(jù)存儲區(qū)
該存儲區(qū)被所有線程所共享,可分為:
堆(Heap)存儲所有的Java對象,當執(zhí)行new對象時,會在堆里自動進行內(nèi)存分配。
方法區(qū)(Method Area)存儲常量池(run-time constant pool)、字段與方法的數(shù)據(jù)、方法與構造器的代碼。
2. 兩種實例化
實例化String對象:
public class StringLiterals { public static void main(String[] args) { String one = "Test"; String two = "Test"; String three = "T" + "e" + "s" + "t"; String four = new String("Test"); } }
javap -c StringLiterals反編譯生成字節(jié)碼,我們選取感興趣的部分如下:
public static void main(java.lang.String[]); Code: 0: ldc #2 // String Test 2: astore_1 3: ldc #2 // String Test 5: astore_2 6: ldc #2 // String Test 8: astore_3 9: new #3 // class java/lang/String 12: dup 13: ldc #2 // String Test 15: invokespecial #4 // Method java/lang/String."": (Ljava/lang/String;)V 18: astore 4 20: return }
ldc #2表示從常量池中取#2的常量入棧,astore_1表示將引用存在本地變量1中。因此,我們可以看出:對象one、two、three均指向常量池中的字面常量"Test";對象four是在堆中new的新對象;如下圖所示:
總結如下:
當用字面常量實例化時,String對象存儲在常量池;
當用new實例化時,String對象存儲在堆中;
操作符==比較的是對象的引用,當其指向的對象不同時,則為false。因此,開篇中的代碼會出現(xiàn)通過new所創(chuàng)建String對象不一樣。
3. 不可變String
String源碼
JDK7的String類:
public final class String implements java.io.Serializable, Comparable, 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 }
String類被聲明為final,不可以被繼承,所有的方法隱式地指定為final,因為無法被覆蓋。字段char value[]表示String類所對應的字符串,被聲明為private final;即初始化后不能被修改。常用的new實例化對象String s1 = new String("abcd");的構造器:
public String(String original) { this.value = original.value; this.hash = original.hash; }
只需將value與hash的字段值進行傳遞即可。
不可變性
所謂不可變性(immutability)指類不可以通過常用的API被修改。為了更好地理解不可變性,我們先來看《Thinking in Java》中的一段代碼:
//: operators/Assignment.java // Assignment with objects is a bit tricky. import static net.mindview.util.Print.*; class Tank { int level; } public class Assignment { public static void main(String[] args) { Tank t1 = new Tank(); Tank t2 = new Tank(); t1.level = 9; t2.level = 47; print("1: t1.level: " + t1.level + ", t2.level: " + t2.level); t1 = t2; print("2: t1.level: " + t1.level + ", t2.level: " + t2.level); t1.level = 27; print("3: t1.level: " + t1.level + ", t2.level: " + t2.level); } } /* Output: 1: t1.level: 9, t2.level: 47 2: t1.level: 47, t2.level: 47 3: t1.level: 27, t2.level: 27 *///:~
上述代碼中,在賦值操作t1 = t2;之后,t1、t2包含的是相同的引用,指向同一個對象。因此對t1對象的修改,直接影響了t2對象的字段改變。顯然,Tank類是可變的。
也許,有人會說s = s.concat("ef");不是修改了對象s么?而事實上,我們?nèi)タ碿oncat的實現(xiàn),會發(fā)現(xiàn)其返回的是新String對象(return new String(buf, true););改變的只是s1引用所指向的對象,如下圖所示:
4. 反射
String的value字段是final的,可不可以通過過某種方式修改呢?答案是反射。在stackoverflow上有這樣一段修改value字段的代碼:
String s1 = "Hello World"; String s2 = "Hello World"; String s3 = s1.substring(6); System.out.println(s1); // Hello World System.out.println(s2); // Hello World System.out.println(s3); // World Field field = String.class.getDeclaredField("value"); field.setAccessible(true); char[] value = (char[])field.get(s1); value[6] = 'J'; value[7] = 'a'; value[8] = 'v'; value[9] = 'a'; value[10] = '!'; System.out.println(s1); // Hello Java! System.out.println(s2); // Hello Java! System.out.println(s3); // World
在上述代碼中,為什么對象s2的值也會被修改,而對象s3的值卻不會呢?根據(jù)前面的介紹,s1與s2指向同一個對象;所以當s1被修改后,s2也會對應地被修改。至于s3對象為什么不會?我們來看看substring()的實現(xiàn):
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); }
當beginIndex不為0時,返回的是new的String對象;當beginIndex為0時,返回的是原對象本身。如果將String s3 = s1.substring(6);改為String s3 = s1.substring(0);,那么對象s3也會被修改了。
如果仔細看java.lang.String.java,我們會發(fā)現(xiàn):當需要改變字符串內(nèi)容時,String類的方法返回的是新String對象;如果沒有改變,String類的方法則返回原對象引用。這節(jié)省了存儲空間與額外的開銷。
到此,關于“分析JDK中String的存儲區(qū)與不可變性”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。