相關API
獲取信息操作
字符串長度 length()
比較字符串引用 “==”
比較字符串內容 equals() 或 compareTo()
已知位置,找字符 charAt()
已知字符(串),找位置 indexOf() 或 lastIndexOf()
判斷開頭和結尾 startWith() 或 endWith()
其他類型轉換爲字符串 valueOf()
更改操作
連接字符串 “+” 或者 concat()
替換子字符串 replace()
獲取子字符串 subString()
分割字符串 split()
更換大小寫 toUpperCase()、toLowerCase()
去除空白符 trim()
String是常量,我們常稱其爲不可變性,意思是一旦創建就不能更改。
所以對於上面的“更改操作”,返回值都是字符串,但並不是對源字符串進行更改,而是返回了新的字符串。下面有測試代碼。
小實驗:如果查看Java API文檔中對String的方法的說明,會發現用了很多的“return a new String ”。
2. 驗證String的不可變性
所謂String的不可變性,是說一旦字符串被創建,對其所做的任何修改都會生成新的字符串對象。
代碼如下:
public static void main(String[] args) {
String a = "abc";
String b = a.toUpperCase();
System.out.println("a: " + a);
System.out.println("b: " + b);
System.out.println("a==b: "+ (a==b));
//當a不發生變化時,不返回新字符串。
String c = a.toLowerCase();
System.out.println("c: " + c);
System.out.println("a==c: "+ (a==c));
}
運行結果:
[java] view plaincopyprint?
a: abc
b: ABC
a==b: false
c: abc
a==c: true
運行結果分析:
字符串a指向"abc",全爲小寫;字符串b由a得來,指向"ABC";這時a的內容並沒有變化,也就證明了Java中String的不變性。
後面利用"a==b"來判斷a,b是否指向同一個對象,返回值爲false,也能證明String的不變性。
對於字符串c的例子說明如果a沒有發生變化,那麼不返回也不需要返回新字符串,所以"a==c"的返回值爲tru
String類不可變性的解釋:
對於String常量,它的值是在常量池中的。而JVM中的常量池在內存當中是以表的形式存在的, 對於String類型,有一張固定長度的CONSTANT_String_info表用來存儲文字字符串值,注意:該表只存儲文字字符串值,不存儲符號引用。說到這裏,對常量池中的字符串值的存儲位置應該有一個比較明瞭的理解了。在程序執行的時候,常量池會儲存在Method Area,而不是堆中。常量池中保存着很多String對象; 並且可以被共享使用,因此它提高了效率。
當創建一個新的String時,首先會在JVM常量池中尋找。如果常量池中存在,則直接將字符串的引用指向該常量的地址;若常量池不存在,則創建新的字符串並存到常量池中,同時將字符串的引用指向新創建的常量的地址。在對已有引用重新賦值時,只是引用指向了新的常量,原來創建的常量並沒有改變。
String s="abcd"; String s2=s;
s=s.concat("ef“)
關於String str1="hello"; 和 String str2= new String("hello");在JVM中內存分配的區別:
首先簡要了解一下JVM的基本知識:
編譯器(或者JVM)爲了更高效地處理數據,會用不同的算法把內存分爲各種區域,不同的區域擁有各自的特性,Java中,內存可以分爲棧,堆,靜態域和常量池等。(可能有不同的叫法,但邏輯是一致的)
不同內存區域的功能和特點:
棧區:存放局部變量(變量名,對象的引用等)特點:內存隨着函數的調用而開闢,隨着函數調用結束而釋放。
堆區:存放對象(也就是new出來的東西)特點:可以跨函數使用,每個對象有自己對應的存儲空間。
靜態域:存放在對象中用static定義的靜態成員。
常量池:存放常量。(常量池指的是在編譯期被確定,並被保存在已編譯的.class文件中的一些數據。)
String str1 = "hello";:引用str1被存放在棧區,字符串常量"hello"被存放在常量池,引用str1指向了常量池中的"hello"(str1中的存放了常量池中"hello"的地址)。
Srtring str2 = new String ("hello");:引用str2被存放在棧區,同時在堆區開闢一塊內存用於存放一個新的String類型對象。(同上,str2指向了堆區新開闢的String類型的對象)
如下圖:
這兩種方法的區別是什麼?
第一種:常量池的字符串常量,不能重複出現,也就是說,在定義多個常量時,編譯器先去常量池查找該常量是否已經存在,如果不存在,則在常量池創建一個新的字符串常量;如果該常量已經存在,那麼新創建的String類型引用指向常量池中已經存在的值相同的字符串常量,也就是說這是不在常量池開闢新的內存。
String str1 = "hello";
String str2 = "hello";
示意圖如圖1
第二種:在堆中創建新的內存空間,不考慮該String類型對象的值是否已經存在。換句話說:不管它的 只是多少,第二種方法的這個操作已經會產生的結果是:在堆區開闢一塊新的內存,用來存放新定義的String類型的對象。
String str1 = new String("hello");
String str2 = new String("hello");
示意圖如果2
案例分析:
public static void main(String[] args) {
/**
* 情景一:字符串池
* JAVA虛擬機(JVM)中存在着一個字符串池,其中保存着很多String對象;
* 並且可以被共享使用,因此它提高了效率。
* 由於String類是final的,它的值一經創建就不可改變。
* 字符串池由String類維護,我們可以調用intern()方法來訪問字符串池。
*/
String s1 = "abc";
//↑ 在字符串池創建了一個對象
String s2 = "abc";
//↑ 字符串pool已經存在對象“abc”(共享),所以創建0個對象,累計創建一個對象
System.out.println("s1 == s2 : "+(s1==s2));
//↑ true 指向同一個對象,
System.out.println("s1.equals(s2) : " + (s1.equals(s2)));
//↑ true 值相等
//↑------------------------------------------------------over
/**
* 情景二:關於new String("")
*
*/
String s3 = new String("abc");
//↑ 創建了兩個對象,一個存放在字符串池中,一個存在與堆區中;
//↑ 還有一個對象引用s3存放在棧中
String s4 = new String("abc");
//↑ 字符串池中已經存在“abc”對象,所以只在堆中創建了一個對象
System.out.println("s3 == s4 : "+(s3==s4));
//↑false s3和s4棧區的地址不同,指向堆區的不同地址;
System.out.println("s3.equals(s4) : "+(s3.equals(s4)));
//↑true s3和s4的值相同
System.out.println("s1 == s3 : "+(s1==s3));
//↑false 存放的地區多不同,一個棧區,一個堆區
System.out.println("s1.equals(s3) : "+(s1.equals(s3)));
//↑true 值相同
//↑------------------------------------------------------over
/**
* 情景三:
* 由於常量的值在編譯的時候就被確定(優化)了。
* 在這裏,"ab"和"cd"都是常量,因此變量str3的值在編譯時就可以確定。
* 這行代碼編譯後的效果等同於: String str3 = "abcd";
*/
String str1 = "ab" + "cd"; //1個對象
String str11 = "abcd";
System.out.println("str1 = str11 : "+ (str1 == str11));
//↑------------------------------------------------------over
/**
* 情景四:
* 局部變量str2,str3存儲的是存儲兩個拘留字符串對象(intern字符串對象)的地址。
*
* 第三行代碼原理(str2+str3):
* 運行期JVM首先會在堆中創建一個StringBuilder類,
* 同時用str2指向的拘留字符串對象完成初始化,
* 然後調用append方法完成對str3所指向的拘留字符串的合併,
* 接着調用StringBuilder的toString()方法在堆中創建一個String對象,
* 最後將剛生成的String對象的堆地址存放在局部變量str3中。
*
* 而str5存儲的是字符串池中"abcd"所對應的拘留字符串對象的地址。
* str4與str5地址當然不一樣了。
*
* 內存中實際上有五個字符串對象:
* 三個拘留字符串對象、一個String對象和一個StringBuilder對象。
*/
String str2 = "ab"; //1個對象
String str3 = "cd"; //1個對象
String str4 = str2+str3;
String str5 = "abcd";
System.out.println("str4 = str5 : " + (str4==str5)); // false
//↑------------------------------------------------------over
/**
* 情景五:
* JAVA編譯器對string + 基本類型/常量 是當成常量表達式直接求值來優化的。
* 運行期的兩個string相加,會產生新的對象的,存儲在堆(heap)中
*/
String str6 = "b";
String str7 = "a" + str6;
String str67 = "ab";
System.out.println("str7 = str67 : "+ (str7 == str67));
//↑str6爲變量,在運行期纔會被解析。
final String str8 = "b";
String str9 = "a" + str8;
String str89 = "ab";
System.out.println("str9 = str89 : "+ (str9 == str89));
//↑str8爲常量變量,編譯期會被優化
//↑------------------------------------------------------over
}
注意:1、使用String不一定創建對象。String s ="abc";若此時常量池中有abc,則引用直接指向該常量,此時不必創建新的常量。
2、使用String s = new String("abc");一定創建新的對象。