標籤:
1、String的本質
安全執行緒
開啟String的源碼,類注釋中有這麼一段話“Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings.Because String objects are immutable they can be shared.”。這句話總結歸納了String的一個最重要的特點:String是值不可變(immutable)的常量,是安全執行緒的(can be shared)。
不可繼承
String類使用了final修飾符,表明了String類的第二個特點:String類是不可繼承的。
在Java中,被final修飾的類是不允許被繼承的,並且該類中的成員方法都預設為final方法。在早期的JVM實現版本中,被final修飾的方法會被轉為內嵌調用以提升執行效率。而從Java SE5/6開始,就漸漸擯棄這種方式了。因此在現在的Java SE版本中,不需要考慮用final去提升方法調用效率。只有在確定不想讓該方法被覆蓋時,才將方法設定為final。
值不可變,儲存方式為字元數組
private final char value[];private final int count;
從final的修飾可以看出其不可變性
其中的String類的 concat方法為起擴容,如果方法的參數長度等於0這返回this,否則並組成一個新的string,並緊隨其後拼接參數後返回新的string
我們看String類的concat方法。實現該方法第一步要做的肯定是擴大成員變數value的容量,擴容的方法重新定義一個大容量的字元數組buf。第二步就是把原來value中的字元copy到buf中來,再把需要concat的字串值也copy到buf中來,這樣子,buf中就包含了concat之後的字串值。下面就是問題的關鍵了,如果value不是final的,直接讓value指向buf,然後返回this,則大功告成,沒有必要返回一個新的String對象。但是。。。可惜。。。由於value是final型的,所以無法指向新定義的大容量數組buf,那怎麼辦呢?“return new String(0, count + otherLen, buf);”,這是String類concat實現方法的最後一條語句,重新new一個String對象返回。這下真相大白了吧!
String類其實是通過char數組來儲存字串的。
總結:String實質是字元數組,兩個特點:1、該類不可被繼承;2、不可變性(immutable)。
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對象的任何改變都不影響到原對象,相關的任何change操作都會產生新的對象”。
2、String的記憶體機制
JVM運行時,會將記憶體分為兩個部分:堆和棧。堆中存放的是建立的對象,而棧中存放的方法調用過程的局部變數或引用。而設計Java字串對象記憶體實現的時候,在堆中又開闢了一塊很小的記憶體,稱之為字串常量池,專門用來存放特定的字串對象。
建立Java字串對象有兩種常用的方式:
String 引用變數名="字串內容";String 應用變數名=new String(<參數序列>);
我們先來看看建立字串對象的第一種方式記憶體如何分配的,代碼如下:
String s1="osEye.net";String s2="osEye.net";
如描述了引用對象的關係,以及記憶體的分配。Java實現的步驟如下:
- 查看字串常量池中是否存在內容與“osEye.net”相同的字串對象。
- 若沒有,則新建立一個包含該內容的字串對象,並讓引用變數指向該對象。例如,建立字串s1的時候,字串常量池中沒有,則建立一個新對象,並讓引用s1指向該對象。
- 若已經存在包含該內容的字串對象,則讓字串引用直接指向該對象。例如,在建立字串s2的時候,字串常量池中已經有包含該內容的對象了,所以引用s2直接指向已有的對象。
在來看看第二種建立字串對象的方式:
String s1="osEye.net";String s2=new String("osEye.net");
如描述了引用對象的關係,以及記憶體的分配。Java實現的步驟如下:
- 首先在堆(不是常量池)中建立一個包含指定內容的字串對象,並將字串引用指向該對象。例如上述代碼中,使用new建立字串s3,其會直接在堆中建立一個內容為“osEye.net”的字串對對象,並將引用s3指向該對象。
- 去字串常量池中查看,是否有包含該內容的對象。
- 若有,則將new出來的字串對象與字串常量池中內容相同的對象聯絡起來。例如,本例中s3所指向的對象與s1所指向的聯絡起來。
- 若沒有,則在字串常量池再建立一個包含該內容的字串對象,並將堆中的對象與字串常量池中新建立出來的對象聯絡起來。
我們知道,new出來的字串對象和字串常量池中的對象是有聯絡的,可以通過intern方法來查看,方法簽名:
public String intern()
此方法將指定的字串引用在字串常量池中對應的對象,若其指向的對象本身就在字串常量池中,則直接將自己指向的對象返回;若該字串引用指向的對象在堆中,則返回字串常量池中與其聯絡的對象。執行個體如下:
package net.oseye;public class ExceptionTest { public static void main(String[] args) { String s1="osEye.net"; String s2=new String("osEye.net"); if(s1==s2){ System.out.println("字串引用s1和字串引用s2所指向的是同一個對象"); }else{ System.out.println("字串引用s1和字串引用s2所指向的不是同一個對象"); } if(s1.intern()==s2.intern()){ System.out.println("字串引用s1和字串引用s2在字串常量池中聯絡的是同一個對象"); }else{ System.out.println("字串引用s1和字串引用s2在字串常量池中聯絡的不是同一個對象"); } }}
輸出結果:
字串引用s1和字串引用s2所指向的不是同一個對象字串引用s1和字串引用s2在字串常量池中聯絡的是同一個對象
Java基礎-關鍵字-String