Java中String的探究

1. String的基本介紹
        相關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");一定創建新的對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章