Java學習之旅基礎知識篇:數組及引用類型內存分配


      在上一篇中,我們已經了解了數組,它是一種引用類型,本篇將詳細介紹數組的內存分配等知識點。數組用來存儲同一種數據類型的數據,一旦初始化完成,即所占的空間就已固定下來,即使某個元素被清空,但其所在空間仍然保留,因此數組長度將不能被改變。當僅定義一個數組變量(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();
        }
    }
}

對於數組元素為引用類型在內存中的存儲與基本類型不一樣,此時數組元素仍然存放引用,指向另一塊內存,在其中存放有效的數據。

談到這里,不知是否有朋友要問: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 = { "拾", "佰","仟" };
    /**
    * 把一個浮點數分成整數部分和小數部分
    * @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表示不處理任何異常,將在后續的篇章中繼續講解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM