String是java中非常常用的一個對象類型。可以說java中使用最多的就是String了。那麼String到底有哪些秘密呢?接下來本文將會一一講解。
String是不可變的
String是不可變的,官方的說法叫做immutable或者constant。
String的底層其實是一個Char的數組。
private final char value[];
所有的String字面量比如」abc」都是String的實現。
考慮下面的賦值操作:
String a="abc";
String b="abc";
對於java虛擬機來說,」abc」是字符串字面量,在JDK 7之後,這個字符串字面量是存儲在java heap中的。而在JDK 7之前是有個專門的方法區來存儲的。
有了「abc」,然後我們將「abc」 賦值給a和b。
可以看到這裡a和b只是java heap中字符串的引用。
再看看下面的代碼發生了什麼:
String c= new String("abc");
首先在java heap中創建了「abc」,然後調用String的構造函數:
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
在構造函數中,String將底層的字符串數組賦值給value。
因為Array的賦值只是引用的賦值,所以上述new操作並不會產生新的字符串字面值。
但是new操作新創建了一個String對象,並將其賦值給了c。
String的不可變性還在於,String的所有操作都會產生新的字符串字面量。原來的字符串是永遠不會變化的。
字符串不變的好處就在於,它是線程安全的。任何線程都可以很安全的讀取字符串。
傳值還是傳引用
一直以來,java開發者都有這樣的問題,java到底是傳值還是傳引用呢?
我想,這個問題可以從兩方面來考慮。
首先對於基礎類型int,long,double來說,對他們的賦值是值的拷貝。而對於對象來說,賦值操作是引用。
另一方面,在方法調用的參數中,全部都是傳值操作。
public static void main(String[] args) {
String x = new String("ab");
change(x);
System.out.println(x);
}
public static void change(String x) {
x = "cd";
}
我們看上面的例子,上面的例子輸出ab。因為x是對「ab」的引用,但是在change方法中,因為是傳值調用,所以會創建一個新的x,其值是「ab」的引用地址。當x被重新賦值之後,改變的只是拷貝之後的x值。而本身的x值是不變的。
substring() 導致的內存泄露
第一次看到這個話題,大家可能會很驚訝,substring方法居然會導致內存泄露?這個話題要從JDK 6開始講起。
我們先看下JDK 6的實現:
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
//check boundary
return new String(offset + beginIndex, endIndex - beginIndex, value);
}
可以看到,JDK 6的substring方法底層還是引用的原始的字符串數組。唯一的區別就是offset和count不同。
我們考慮一下下面的應用:
String string = "abcdef";
String subString = string.substring(1, 3);
string = null;
雖然最後我們將String賦值為null,但是subString仍然引用了最初的string。將不會被垃圾回收。
在JDK 7之後,String的實現發送了變化:
public String(char value[], int offset, int count) {
//check boundary
this.value = Arrays.copyOfRange(value, offset, offset + count);
}
public String substring(int beginIndex, int endIndex) {
//check boundary
int subLen = endIndex - beginIndex;
return new String(value, beginIndex, subLen);
}
Arrays.copyOfRange將會拷貝一份新的數組,而不是使用之前的數組。從而不會發生上面的內存泄露的問題。
雖然String是我們經常使用的對象,但是裡面的原理還是值得我們了解的。
文章來源: https://twgreatdaily.com/zh-sg/dr2gvXEBnkjnB-0z7zfY.html