1. 程式人生 > >關於String str1 = "123" 和 String str2 = new String("123")記憶體分析

關於String str1 = "123" 和 String str2 = new String("123")記憶體分析

最近在自學java基礎,由於嵌入式以後的就業前景不是很好,加上自己本學期學習了51單片機發現自己對硬體不是很在行,可能是因為初中以來物理一直不是很好吧,導致自己現在一看到電路板,電壓電阻電流都會產生一種恐懼感,就像大三現在的我對與資料結構也有一種畏難情緒(不願意花很多時間去研究,很多時候資料結構都和數學邏輯有關,但資料結構真的很重要,大家在大學一定要好好學資料結構,以後對你自己程式設計會有很大幫助)。。

   由於自己學了很長時間的C(記得那時候對指標很感興趣花了很多課外時間去研究)加上自己本學期學過彙編,涉及到底層。所以每學一門新的語言就會分析它的記憶體分配問題。

好吧,閒扯了幾句,我最近碰到一個很有趣的問題,關於

String str = “123” String str =new String(“123”); 分析它的記憶體分配。。。

在分析上面那個問題之前我們先來補充下JAVA的一些預備知識:

JAVA中的記憶體區劃分

棧區:儲存基本資料型別(引用),物件的引用

堆區:儲存每個物件的具體屬性

全域性資料區:儲存static型別的屬性

全域性程式碼區:儲存所有方法的定義

常量區:字元常量區(“111”、”112”...)和基本資料常量區(12.2...)及其他

接下來舉個簡單的例子-》 貼程式碼:

為了更直觀我畫了幾個圖,可能比文字描述更直觀。。

在畫圖之前,我們先來說下java檔案的編譯過程。。

.javajava檔案)經過編譯(dos介面通過javac.exe藉助於jdk編譯,或者一些IDE自帶的編譯器編譯)----》》》》.class位元組碼檔案 在通過jdk中的java虛擬機器

(jvm).class位元組碼檔案在不同的作業系統上  ------》》》執行(關於虛擬機器jvm的作用這裡不做具體解釋,自行百度哈哈哈)

好了上圖吧(由於是畫圖板請點選放大檢視)


好了,認真看完上文(扯了這麼多。。)

就來分析下String str = “123” String str =new String(“123”)的記憶體分配問題吧。

想要了解一個問題,我們得進入到String內中看它的原型(關於如何看原始碼一種是看API還一種是自己在除錯的時候進入String內後面這種方式得自行百度有詳細的步驟)

public final class String

    implements java.io.Serializable, Comparable<String>, CharSequence {

    /*進入到這是不是有點想起C了,莫名的親切 */

       private final char value[];//加了final關鍵字說明不可string字串不可改變

       private int hash;// Default to 0

       private static final long serialVersionUID = -6849794470754667710L;

       private static final ObjectStreamField[]serialPersistentFields =

        new ObjectStreamField[0];

 public String() {

        this.value ="".value;

     }

public String(Stringoriginal) {

        this.value =original.value;

        this.hash =original.hash;

    }

    public String(char value[]) {

        this.value = Arrays.copyOf(value,value.length);

    }

這裡重點說兩個問題:一個是private final char value[]說明了String類中並沒有給字串的記憶體分配空間只是分配了一個final char* 指標

二是String類的建構函式public String(String original)

public String(char value[]),String str = new String(“xxx”);時是呼叫那個建構函式呢。

接下來上程式碼


按F11進行除錯,F5進入String建構函式

 

注意看呼叫了public String(Stringoriginal)建構函式,而且是value值之間的相互賦值...

所以到了這裡可以簡單畫一個記憶體分析圖了



分析::string str1 =123,如果string池之前沒有”abc”物件就現在str池例項化一個”abc”物件,然後str1引用指向它。。(假設這時候在來一句string copyStr1=”abc”, 就同時指向了物件”abc”)思考一下這樣做的目的其實是java為了節約記憶體,只在第一次的時候分配記憶體然後後面如果有其他的引用指向相同的字串物件就只需要在棧區分配一個引用的空間了。。是不是很贊(可以用System.out.println(str1==copyStr1))驗證為True

說明指向的是同一塊空間

String str2 = new String(abc);在堆區分配物件的記憶體然後在呼叫建構函式將”abc”.value的值賦值給str2物件的value,它們是不同的物件(可以用System.out.println(str1==str2)驗證為False),但它們的value值是相同的都是指向同一個字串常量”abc”;

綜上所述:String類雖然叫字元類,但String只是一個包裝類,並沒有為字串分配空間去存取它們,只留了一個value值去指向字串常量

所以:雖然string str1 = 123 String str2 = new String(abc);能達到相同的目的,但是出於記憶體節約的原則,建議用前者,給堆區節省記憶體

補充一點關於String池:這部分記憶體應該在堆區jvm自動分配的,有些書上會寫成匿名(String)物件,這部分記憶體應該是有java自己的(GC- java回收機制)自己去回收。。。

留一個問題:假如你去面試java有關的工作,技術官問你

string str1 = “123” String str2 = new String(“abc”);分別分配了幾次記憶體???仔細想想

最後關於補充兩個小問題:

1>>>JAVAint a = 1int b = 1的記憶體分配我發表一下自己看法(可能不正確)

我們在C 語言中我們知道這肯定是分配了兩個int的記憶體然後值都為1,而且我們輸出ab的地址 並且ab的地址不相同。(自己用vsvc++試驗一下)這裡就不截圖了

但是由於java中沒有指標的概念不能與記憶體直接溝通,所以這裡沒法驗證地址

但是這裡有一個很有趣問題或許可以給我們啟發:在C語言中比如直接定義int a;然後輸出a結果是個垃圾值。。。但是在java中如果沒有賦初值輸出的時候會編譯報錯

 我們是不是可以這樣想,既然要輸出a的值,編譯器可能要去常量區找值,結合開頭我們提到,a指向常量值(儲存某個常量值的地址)這裡未初始化,也就是說a沒有存地址,這時候編譯執行的時候發現a沒有地址就不知道怎麼去常量區取值了(這只是個人猜想,有待我自己翻閱資料驗證)

2>> 關於str1 = str1 +xxx” 以及   字串比較的問題

我們不是說String類一旦確定就不能改變的麼(string語言中有一個value加了final關鍵字,代表value的指向字串不可以改變)

上一個程式碼:


輸出結果如下,(自己可以嘗試畫畫記憶體圖,分析一下)


我們進入"123abc".equals(str1)的原始碼:


紅色框框裡面的是不是很熟悉?其實就是我們C語言裡很簡單的字串比較

但是為什麼第一個輸出為false呢?我們可以推測如果直接==的話應該是比較物件地址的值,也就是引用的值,你想一下str1和匿名物件”123abc”在堆疊不同的記憶體塊中,肯定不相等是吧。。附上我自己畫的很醜的圖


基於上圖的記憶體分析圖:我們可以得知用str1 = str1 + “abc”,在堆中又重新分配了一塊堆記憶體(雖然後面GC會自動回收但還是浪費了堆記憶體)所以我們不建議這麼做(自己可以看java書,用StringBuffer類比較合適)

結尾:

好了就說這麼多了,由於這是本人第一次寫部落格,文筆不是很好所以語言組織的可能有點雜亂(個人覺得寫部落格是一件很酷的事情,可以把自己想法傳到部落格和別人分享)所以一定會有很多地方寫的不夠專業,用的詞語也不像一些大神很術語,所以如果各位讀者看到了有什麼錯誤,或者覺得有疑問,和自己平時的認知有衝突的,可以在下面留言板給我留言,大家一起交流,一起進步----祥子留奮鬥奮鬥得意得意