我們已經瞭解瞭數組,它是一種引用類型,本篇將詳細介紹數組的內存分配等知識點。數組用來存儲同一種數據類型的數據,一旦初始化完成,即所占的空間就已固定下來,即使某個元素被清空,但其所在空間仍然保留,因此數組長度將不能被改變。當僅定義一個數組變量(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
//還可以使用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();
}
}
}
對於數組元素為引用類型在內存中的存儲與基本類型不一樣,此時數組元素仍然存放引用,指向另一塊內存,在其中存放有效的數據。
談到這裡,不知是否有朋友要問:Java的多維數組是什麼樣的?我的回答是:可以有。為什麼呢?從底層來看,數組元素可以存放引用類型,包含數組。也就是說在數組元素的內部還可以包含數組(如int[][] numbers = new int[length][]),也即二維數組可當作一維數組(數組長度為length)來處理,也可以同時指定多個維度的長度(如int[][] matrix = new int[length][width]),不過必須至少指定最左端的數組長度length。由此我們得出結論: 任何多維數組(維度為n,n>1)都當作一維數組,其數組元素為n-1維數組。
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
}
}
}
最後,簡單介紹一下Arrays(位於java.util下)的靜態方法:binarySearch、copyOf、copyOfRange、equals、fill、sort、toString等方法(具體用法參見JDK)。
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 = { "拾", "佰","仟" };
private String[] pide(double number) {
long zheng = (long)number;
long xiao = Math.round((number – zheng) * 100);
return new String[] { zheng + "", String.valueOf(xiao) };
}
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.pide(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();
}