在上一篇中,我們已經了解了數組,它是一種引用類型,本篇將詳細介紹數組的內存分配等知識點。數組用來存儲同一種數據類型的數據,一旦初始化完成,即所占的空間就已固定下來,即使某個元素被清空,但其所在空間仍然保留,因此數組長度將不能被改變。當僅定義一個數組變量(int[] numbers)時,該變量還未指向任何有效的內存,因此不能指定數組的長度,只有對數組進行初始化(為數組元素分配內存空間)后才可以使用。數組初始化分為靜態初始化(在定義時就指定數組元素的值,此時不能指定數組長度)和動態初始化(只指定數組長度,由系統分配初始值)。
//靜態初始化 int[] numbers = new int[] { 3, 5, 12, 8, 7 }; String[] names = { "Miracle", "Miracle He" };//使用靜態初始化的簡化形式
//動態初始化 int[] numbers = new int[5]; String[] names = new String[2];
建議不要混用靜態初始化和動態初始化,即不要既指定數組的長度的同時又指定每個元素的值。當初始化完畢后,就可以按索引位置(0~array.length-1)來訪問數組元素了。當使用動態初始化時,如在對應的索引位未指定值的話,系統將指定相應數據類型對應的默認值(整數為0,浮點數為0.0,字符為'\u0000',布爾類型為false,引用類型為null)。
public class TestArray { public static void main(String[] args) { String[] names = new String[3]; names[0] = "Miracle"; names[1] = "Miracle He"; //以下代碼將輸出Miracle Miracle He null /* for(int i = 0; i < names.length;i++) { System.out.print(names[i] + " "); } */ //還可以使用foreach來遍歷 for(String name : names) { System.out.print(name + " "); } } }
請注意:java中是沒有foreach這個關鍵字的,其語法是for(type item : items)來表示,但foreach只能用於遍歷元素的值而不能改變,必須使用for才能實現。
public class TestForEach { public static void main(String[] args) { int[] numbers = { 3, 5, 12, 8, 7 }; for(int number : numbers) { int num = number * 10; System.out.print(num + ","); } System.out.println(""); //numbers仍然未發生變化(如果換成for將改變) for(int i = 0;i < numbers.length;i++) { System.out.print(numbers[i] + ","); } } }
以上簡單的介紹了數組的初始化和應用,接下來講詳細介紹數組(數組引用和數組元素)在內存中的存放形式。首先給出結論:數組引用變量是存放在棧內存(stack)中,數組元素是存放在堆內存(heap)中,通過棧內存中的指針指向對應元素的在堆內存中的位置來實現訪問,以下圖來說明數組此時的存放形式。

那什么是棧內存和堆內存呢?我舉例作一一解釋。當執行方法時,該方法都會建立自身的內存棧,以用來將該方法內部定義的變量逐個加入到內存棧中,當執行結束時方法的內存棧也隨之銷毀,我們說所有變量存放在棧內存中,即隨着寄存主體的消亡而消亡;反之,當我們創建一個對象時,這個對象被保存到運行時數據區中,以便反復利用(因為創建成本很高),此時不會隨着執行方法的結束而消亡,同時該對象還可被其他對象所引用,只有當這個對象沒有被任何引用變量引用時,才會在垃圾回收在合適的時間點回收,我們說此時變量所指向的運行時數據區存在堆內存中。
只有類型兼容(即屬於同一數據類型體系且遵守優先級由低到高原則),才能將數組引用傳遞給另一數組引用,但仍然不能改變數組長度(僅僅只是調整數組引用指針的指向)。
public class TestArrayLength { public static void main(String[] args) { int[] numbers = { 3, 5, 12 }; int[] digits = new int[4]; System.out.println("digits數組長度:" + digits.length);//4 for(int number : numbers) { System.out.print(number + ",");//3,5,12, } System.out.println(""); for(int digit : digits) { System.out.print(digit + ",");//0,0,0,0, } System.out.println(""); digits = numbers; System.out.println("digits數組長度:" + digits.length);//3 } }
雖然看似digits的數組長度看似由4變成3,其實只是numbers和digits指向同一個數組而已,而digits本身失去引用而變成垃圾,等待垃圾回收來回收(但其長度仍然為4),但其內部運行機制如下圖所示。


因此當我們看一個數組時(或者其他引用變量),通常看成兩部分:數組引用變量和數組元素本身,而數據元素是存放在堆內存中,只能通過數組引用變量來訪問。
從上述的示例中看出數組中存放的是基本類型,其實數組中還可以存放引用類型的。而存放基本類型的內存分布已經解釋了,而存放引用類型的內存分布則相對復雜了。來看一段非常簡單的程序。
public class TestPrimitiveArray { public static void main(String[] args) { //1.定義數組 int[] numbers; //2.分配內存空間 numbers = new int[4]; //3.為數組元素指定值 for(int i = 0;i < numbers.length;i++) { numbers[i] = i * 10; } } }
按以上步驟的內存分布示意圖:
從圖中可看出數組元素直接存放在堆內存中,當操作數組元素時,實際上是操作基本類型的變量。接下來再看一段程序:
class Person { public int age; public String name; public void display() { System.out.println(name + "的年齡是: " + age); } } public class TestReferenceArray { public static void main(String[] args) { //1.定義數組 Person[] persons; //2.分配內存空間 persons = new Person[2]; //3.為數組元素指定值 Person p1 = new Person(); p1.age = 28; p1.name = "Miracle"; Person p2 = new Person(); p2.age = 30; p2.name = "Miracle He"; persons[0] = p1; persons[1] = p2; //輸出元素的值 for(Person p : persons) { p.display(); } } }
對於數組元素為引用類型在內存中的存儲與基本類型不一樣,此時數組元素仍然存放引用,指向另一塊內存,在其中存放有效的數據。

public class TestMultiArray { public static void main(String[] args) { //1.定義二維數組 int[][] numbers; //2.分配內存空間 numbers = new int[3][]; //可以把numbers看作一維數組來處理 for(int i = 0;i < numbers.length;i++) { System.out.print(numbers[i] + ",");//null,null,null } System.out.println(""); //3.為數組元素指定值 numbers[0] = new int[2]; numbers[0][1] = 1; for(int i = 0;i < numbers[0].length;i++) { System.out.print(numbers[0][i] + ",");//0,1 } } }

import java.util.Arrays; public class TestArrays { public static void main(String[] args) { int[] a = {3, 4, 5, 6}; int[] b = {3, 4, 5, 6}; System.out.println("a和b是否相等:" + Arrays.equals(a, b));//true System.out.println("5在a中的位置:" + Arrays.binarySearch(a, 5));//2 int[] c = Arrays.copyOf(a, 6); System.out.println("a和c是否相等:" + Arrays.equals(a, c));//false System.out.println("c的元素:" + Arrays.toString(c));//3,4,5,6,0,0 Arrays.fill(c, 2, 4, 1);//將c中第3個到第5個元素(不包含)賦值為1 System.out.println("c的元素:" + Arrays.toString(c));//3,4,1,1,0,0 Arrays.sort(c); System.out.println("c的元素:" + Arrays.toString(c));//0,0,1,1,3,4 } }
接下來,給出兩個數組實際應用場景的示例。

import java.util.Arrays; public class NumberToRMB { private String[] numbers = { "零", "壹", "貳", "叄", "肆", "伍", "陸", "柒", "捌", "玖" }; private String[] units = { "拾", "佰","仟" }; /** * 把一個浮點數分成整數部分和小數部分 * @param number 要進行分割的浮點數 * @return 由整數部分和小數部分組成的字符串數組 */ private String[] divide(double number) { long zheng = (long)number; long xiao = Math.round((number - zheng) * 100); return new String[] { zheng + "", String.valueOf(xiao) }; } /** * 把一個四位數字字符串轉化四位人民幣大寫字符串 * @param str 要轉化的四位數字字符串 * @return 四位人民幣大寫字符串 */ private String toRMBString(String str) { String money = ""; for(int i = 0, len = str.length(); i < len; i++) { int num = str.charAt(i) - 48; if(i != len - 1 && num != 0) { money += numbers[num] + units[len - 2 - i]; } else { money += numbers[num]; } } return money; } public static void main(String[] args) { NumberToRMB rmb = new NumberToRMB(); System.out.println(Arrays.toString(rmb.divide(2346.789))); System.out.println(rmb.toRMBString("2346")); } }

import java.io.*; public class WZQ { //定義一個二維數組當作棋盤 private String[][] board; //定義棋盤大小 private static int BOARD_SIZE = 15; //初始化棋盤 private void initBoard() { board = new String[BOARD_SIZE][BOARD_SIZE]; for(int i = 0; i < BOARD_SIZE; i++) { for(int j = 0; j < BOARD_SIZE; j++) { board[i][j] = "+"; } } } //打印棋盤 private void printBoard() { for(int i = 0; i < BOARD_SIZE; i++) { for(int j = 0; j < BOARD_SIZE; j++) { System.out.print(board[i][j]); } System.out.println(""); } } //開始下棋 public void play() throws Exception { initBoard(); printBoard(); //獲取鍵盤輸入 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String input = null; do { if(input != null) { String[] pos = input.split(","); int x = Integer.parseInt(pos[0]); int y = Integer.parseInt(pos[1]); board[x - 1][y - 1] = "●"; printBoard(); } System.out.print("請輸入你下棋的坐標(以x,y的形式):"); } while((input = br.readLine()) != null); } public static void main(String[] args) throws Exception { WZQ wzq = new WZQ(); wzq.play(); } }
數字轉化為人民幣大寫程序中,利用了一維數組表示大寫及單位;五子棋游戲中,利用了二維數組表示棋盤。從程序中可看到throws Exception表示不處理任何異常,將在后續的篇章中繼續講解。