1. 程式人生 > >IT兄弟連 Java語法教程 陣列 深入陣列 記憶體中的陣列

IT兄弟連 Java語法教程 陣列 深入陣列 記憶體中的陣列

3eead53441a84a69adba0a796b0084d2.jpg

陣列是一種引用資料型別,陣列引用變數只是一個引用,陣列元素和陣列變數在記憶體裡是分開存放的。下面將深入介紹陣列在記憶體中的執行機制。

 

記憶體中的陣列

陣列引用變數只是一個引用,這個引用變數可以指向任何有效的記憶體,只有當該引用指向有效記憶體後,才可以通過該陣列變數來訪問陣列元素。

與所有引用變數相同的是,引用變數是訪問真是物件的根本方式。也就是說,如果希望在程式中訪問陣列物件本身,則只能通過這個陣列的引用變數來訪問它。

實際的陣列物件被儲存在堆記憶體中;如果引用該陣列物件的陣列引用變數是一個區域性變數,那麼它被儲存在棧記憶體中。陣列在記憶體中的儲存示意圖如圖3所示。

a45e4a9c63d645e9a760d444be73f069.png

圖3  陣列在記憶體中的儲存示意圖

 

如果需要訪問如圖3所示堆記憶體中的陣列元素,則程式只能通過p[index]的形式實現。也就是說,陣列引用變數是訪問堆記憶體中陣列元素的根本方式。

如果堆記憶體陣列不再有任何引用變數指向自己,則這個陣列將成為垃圾,該陣列所佔的記憶體將會被系統的垃圾回收機制回收。因此,為了讓垃圾回收機制回收一個數組所佔的記憶體空間,可以將該陣列變數賦值為null,也就切斷了陣列引用變數和實際陣列之間的引用關係,實際的陣列也就成了垃圾。

只要型別相互相容,就可以讓一個數組變數指向另一個實際陣列,這種操作會讓人產生陣列的長度可變的錯覺。如下程式碼所示。

public class ArrayInRam{

    public static void main(String[] args){

         int[] a = {5,6,1};

         int[] b = new int[4];

         System.out.println("b陣列的長度為:" + b.length);

         for(int i = 0,len = a.length; i<len; i++){

              System.out.println(a[i]);

         }

         for(int i = 0,len = b.length; i<len; i++){

              System.out.println(b[i]);

         }

         b = a;

         System.out.println("b陣列的長度為:" + b.length);

    }

}

執行上面程式碼後,將可以看到先輸出b陣列的長度為4,然後依次輸出a陣列和b陣列的每個元素,接著會輸出b陣列的長度為3.看起來視乎陣列的長度是可變的,但這只是一個假象。必須牢記:定義並初始化一個數組後,在記憶體中分配了兩個空間,一個用於存放陣列的引用變數,另一個用於存放陣列本身。下面將結合示意圖來說明上面程式的執行過程。

aad22cacfbc24605b7b2e4a016bb31ab.png

圖4  定義並初始化a、b兩個陣列後記憶體示意圖

 

當程式定義並初始化了a、b兩個陣列後,系統記憶體中實際上產生了4塊記憶體區域,其中棧記憶體中有兩個引用變數:a和b;堆記憶體中也有兩塊記憶體區域,分別用於儲存a和b引用所指向的陣列本身。此時計算機記憶體的儲存示意圖如圖5.4所示。

從圖4中可以非常清楚地看出a引用和b引用各自所引用的陣列物件,並可以很清楚地看出a變數所引用的陣列長度是3,b變數所引用的陣列長度是4。

當執行上面程式中的程式碼b = a時,系統將會把a的值賦給b,a和b都是引用型別變數,儲存的是地址。因此把a的值賦給b後,就是讓b指向a所指向的地址。此時計算機記憶體的儲存示意圖如圖5所示。

5c56a10db6e04dcb85ca11132096332f.png

圖5  讓b引用指向a引用所指向的陣列後的儲存示意圖

 

從圖5中可以看出,當執行了b = a之後,堆記憶體中的第一個陣列具有了兩個引用;a變數和b變數都引用了第一個陣列。此時第二個陣列失去了引用,變成垃圾,只有等待垃圾回收機制來回收它,但是它的長度依然不會改變,直