數(shù)組是一種觀念引用數(shù)據(jù)類型,數(shù)組引用變量只是一個引用,數(shù)組元素和數(shù)組變量在內(nèi)存里是分開存放的。

數(shù)組引用變量只是一個引用,這個引用變量可以指向任何有效的內(nèi)存,只有當(dāng)該引用指向有效內(nèi)存后,才可通過該數(shù)組變量來訪問數(shù)組元素。與所有引用變量相同的是,引用變量是訪問真實對象的根本方式。也就是說,如果我們希望在程序中訪問數(shù)組,則只能通過這個數(shù)組的引用變量來訪問它。實際的數(shù)組元素被存儲在堆(heap)內(nèi)存中;數(shù)組引用變量是一個引用類型的變量,被存儲在棧(stack)內(nèi)存中。數(shù)組在內(nèi)存中的存儲如圖所示:、
如果需要訪問圖中堆內(nèi)存中的數(shù)組元素,程序中只能通過p[index]的形式實現(xiàn)。也就是說,數(shù)組引用變量是訪問堆內(nèi)存中數(shù)組元素的唯一方式。
為什么有棧內(nèi)存和堆內(nèi)存之分?
當(dāng)一個方法執(zhí)行時,每個方法都會建立自己的內(nèi)存棧,在這個方法內(nèi)定義的變量將會逐個放入這塊棧內(nèi)存里,隨著方法的執(zhí)行結(jié)束,這個方法的內(nèi)存棧也將自然銷毀了。因此,所有在方法中定義的變量都是放在棧內(nèi)存中的;當(dāng)我們在程序中創(chuàng)建一個對象時,這個對象將被保存到運行時數(shù)據(jù)區(qū)中,以便反復(fù)利用(因為對象的創(chuàng)建成本通常較大),這個運行時數(shù)據(jù)區(qū)就是堆內(nèi)存。堆內(nèi)存中的對象不會隨方法的結(jié)束而銷毀,即使方法結(jié)束后,這個對象還可能被另一個引用變量所引用(方法的參數(shù)傳遞時很常見),則這個對象依然不會被銷毀。只有當(dāng)一個對象沒有任何引用變量引用它時,系統(tǒng)的垃圾回收機制才會在合適的時候回收它。
如果堆內(nèi)存中數(shù)組不再有任何引用變量指向自己,則這個數(shù)組將成為垃圾,該數(shù)組所占的內(nèi)存將會被系統(tǒng)的垃圾回收機制回收。因此,為了讓垃圾回收機制回收一個數(shù)組所占的內(nèi)存空間,則可以將該數(shù)組變量賦為null,也就切斷了數(shù)組引用變量和實際數(shù)組之間的引用關(guān)系,實際數(shù)組也就成了垃圾。
只要類型相互兼容,可以讓一個數(shù)組變量指向另一個實際的數(shù)組,這種操作會產(chǎn)生數(shù)組的長度可變的錯覺。如下代碼所示:
public class ArrayInRam{
public static void main(String[] args){
//定義并初始化數(shù)組,使用靜態(tài)初始化
int[] a = {5,7,20};
//定義并初始化數(shù)組,使用動態(tài)初始化
int[] b =newint[4];
//輸出b數(shù)組的長度
System.out.println("b數(shù)組的長度為:"+ b.length);
//循環(huán)輸出a數(shù)組的元素
for(inti =0; i < a.length ; i++ ){
System.out.println(a[i]);
}//循環(huán)輸出b數(shù)組的元素
for(inti =0; i < b.length ; i++ ){
System.out.println(b[i]);
}
//因為a是int[]類型,b也是int[]類型,所以可以將a的值賦給b。
//也就是讓b引用指向a引用指向的數(shù)組b = a;
//再次輸出b數(shù)組的長度
System.out.println("b數(shù)組的長度為:"+ b.length);
}
}
運行上面代碼后,將可以看到先輸出b數(shù)組的長度為4,然后依次輸出a數(shù)組和b數(shù)組的每個數(shù)組元素,接著會輸出b數(shù)組的長度為3??雌饋硭坪鯏?shù)組的長度是可變的,但這只是一個假象。必須牢記:定義并初始化一個數(shù)組后,在內(nèi)存里分配了兩個空間,一個用于存放數(shù)組的引用變量,一個用于存放數(shù)組本身。下面將結(jié)合示意圖來說明上面程序的運行過程。
當(dāng)程序定義并初始化了a、b兩個數(shù)組后,系統(tǒng)內(nèi)存中實際上產(chǎn)生了4塊內(nèi)存區(qū),其中棧內(nèi)存中有兩個引用變量:a和b;堆內(nèi)存中也有兩塊內(nèi)存區(qū),分別用于存儲a和b引用所指向的數(shù)組本身。此時計算機內(nèi)存的存儲示意如圖2所示:

從圖2中可以非常清楚地看出,a引用和b引用所指向數(shù)組里數(shù)組元素的值,并可以很明白地看出b數(shù)組的長度是4。
當(dāng)執(zhí)行上面粗體字標識代碼:b = a代碼時,系統(tǒng)將會把a的值賦給b,a和b都是引用類型變量,存儲的是地址。因此把a的值賦給b后,就是讓b指向a所指向的地址。此時計算機內(nèi)存的存儲示意如圖3所示:

從圖3中可以看出,當(dāng)執(zhí)行了b = a之后,堆內(nèi)存中第一個數(shù)組具有了兩個引用:a變量和b變量都指向了第一個數(shù)組。此時第二個數(shù)組失去了引用,變成垃圾,只有等待垃圾回收來回收它——但它的長度依然不會改變,直到它徹底消失。
程序員進行程序開發(fā)時,不要僅僅停留在代碼表面,而要深入底層的運行機制,才可以對程序的運行機制有更準確的把握。當(dāng)我們看一個數(shù)組時,一樣要把數(shù)組看成兩個部分:一個是數(shù)組引用,也就是在代碼中定義的數(shù)組引用變量;還有一個是實際數(shù)組本身,這個部分是運行在系統(tǒng)內(nèi)存里的,通常無法直接訪問它,只能通過數(shù)組引用變量來訪問它。