一、String類
想要瞭解一個類,最好的辦法就是看這個類的實現原始碼,來看一下String類的源碼:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{ /** The value is used for character storage. */ private final char value[]; /** The offset is the first index of the storage that is used. */ private final int offset; /** The count is the number of characters in the String. */ private final int count; /** 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; ........}
從上面可以看出幾點:
1)String類是final類,也即意味著String類不能被繼承,並且它的成員方法都預設為final方法。在Java中,被final修飾的類是不允許被繼承的,並且該類中的成員方法都預設為final方法。
2)上面列舉出了String類中所有的成員屬性,從上面可以看出String類其實是通過char數組來儲存字串的。
下面再繼續看String類的一些方法實現:
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > count) { throw new StringIndexOutOfBoundsException(endIndex); } if (beginIndex > endIndex) { throw new StringIndexOutOfBoundsException(endIndex - beginIndex); } return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value);}public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } char buf[] = new char[count + otherLen]; getChars(0, count, buf, 0); str.getChars(0, otherLen, buf, count); return new String(0, count + otherLen, buf);}public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = count; int i = -1; char[] val = value; /* avoid getfield opcode */ int off = offset; /* avoid getfield opcode */ while (++i < len) { if (val[off + i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0 ; j < i ; j++) { buf[j] = val[off+j]; } while (i < len) { char c = val[off + i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(0, len, buf); } } return this;}
從上面的三個方法可以看出,無論是sub操、concat還是replace操作都不是在原有的字串上進行的,而是重建了一個新的字串對象。也就是說進行這些操作後,最原始的字串並沒有被改變。
在這裡要永遠記住一點:“String對象一旦被建立就是固定不變的了,對String對象的任何改變都不影響到原對象,相關的任何change操作都會產生新的對象”。
二、字串常量池
我們知道字串的分配和其他對象分配一樣,是需要消耗高昂的時間和空間的,而且字串我們使用的非常多。JVM為了提高效能和減少記憶體的開銷,在執行個體化字串的時候進行了一些最佳化:使用字串常量池。每當我們建立字串常量時,JVM會首先檢查字串常量池,如果該字串已經存在常量池中,那麼就直接返回常量池中的執行個體引用。如果字串不存在常量池中,就會執行個體化該字串並且將其放到常量池中。由於String字串的不可變性我們可以十分肯定常量池中一定不存在兩個相同的字串(這點對理解上面至關重要)。
Java中的常量池,實際上分為兩種形態:靜態常量池和運行時常量池。
所謂靜態常量池,即*.class檔案中的常量池,class檔案中的常量池不僅僅包含字串(數字)字面量,還包含類、方法的資訊,佔用class檔案絕大部分空間。
而運行時常量池,則是jvm虛擬機器在完成類裝載操作後,將class檔案中的常量池載入到記憶體中,並儲存在方法區中,我們常說的常量池,就是指方法區中的運行時常量池。
來看下面的程式:
String a = "chenssy";String b = "chenssy";
a、b和字面上的chenssy都是指向JVM字串常量池中的"chenssy"對象,他們指向同一個對象。
String c = new String("chenssy");
new關鍵字一定會產生一個對象chenssy(注意這個chenssy和上面的chenssy不同),同時這個對象是儲存在堆中。所以上面應該產生了兩個對象:儲存在棧中的c和儲存堆中chenssy。但是在Java中根本就不存在兩個完全一模一樣的字串對象。故堆中的chenssy應該是引用字串常量池中chenssy。所以c、chenssy、池chenssy的關係應該是:c--->chenssy--->池chenssy。整個關係如下:
通過上面的圖我們可以非常清晰的認識他們之間的關係。所以我們修改記憶體中的值,他變化的是所有。
總結:雖然a、b、c、chenssy是不同的對象,但是從String的內部結構我們是可以理解上面的。String c = new String("chenssy");雖然c的內容是建立在堆中,但是他的內部value還是指向JVM常量池的chenssy的value,它構造chenssy時所用的參數依然是chenssy字串常量。
下面再來看幾個例子:
例子1:
/** * 採用字面值的方式賦值 */public void test1(){ String str1="aaa"; String str2="aaa"; System.out.println("===========test1============"); System.out.println(str1==str2);//true 可以看出str1跟str2是指向同一個對象 }
執行上述代碼,結果為:true。
分析:當執行String str1="aaa"時,JVM首先會去字串池中尋找是否存在"aaa"這個對象,如果不存在,則在字串池中建立"aaa"這個對象,然後將池中"aaa"這個對象的引用地址返回給字串常量str1,這樣str1會指向池中"aaa"這個字串對象;如果存在,則不建立任何對象,直接將池中"aaa"這個對象的地址返回,賦給字串常量。當建立字串對象str2時,字串池中已經存在"aaa"這個對象,直接把對象"aaa"的引用地址返回給str2,這樣str2指向了池中"aaa"這個對象,也就是說str1和str2指向了同一個對象,因此語句System.out.println(str1 == str2)輸出:true。
例子2:
/** * 採用new關鍵字建立一個字串對象 */public void test2(){ String str3=new String("aaa"); String str4=new String("aaa"); System.out.println("===========test2============"); System.out.println(str3==str4);//false 可以看出用new的方式是產生不同的對象 }
執行上述代碼,結果為:false。
分析: 採用new關鍵字建立一個字串對象時,JVM首先在字串池中尋找有沒有"aaa"這個字串對象,如果有,則不在池中再去建立"aaa"這個對象了,直接在堆中建立一個"aaa"字串對象,然後將堆中的這個"aaa"對象的地址返回賦給引用str3,這樣,str3就指向了堆中建立的這個"aaa"字串對象;如果沒有,則首先在字串池中建立一個"aaa"字串對象,然後再在堆中建立一個"aaa"字串對象,然後將堆中這個"aaa"字串對象的地址返回賦給str3引用,這樣,str3指向了堆中建立的這個"aaa"字串對象。當執行String str4=new String("aaa")時, 因為採用new關鍵字建立對象時,每次new出來的都是一個新的對象,也即是說引用str3和str4指向的是兩個不同的對象,因此語句System.out.println(str3 == str4)輸出:false。
例子3:
/** * 編譯期確定 */public void test3(){ String s0="helloworld"; String s1="helloworld"