位元詩人 [Java] 程式設計教學:使用陣列 (Array)

Java陣列
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

雖然在處理物件型態時,我們常會依據不同情境選擇 ArrayListLinkedList;但對於基礎型態(Primitive Types)而言,傳統陣列依然具有強大優勢。由於陣列在記憶體中是連續配置的,且不具備物件型態的額外開銷(Overhead),因此能提供更高的存取效能。

宣告陣列

以下指令宣告元素型態為 double 的一維陣列:

double vec[];

傳統陣列最核心的優勢在於記憶體空間的連續性,這能帶來極佳的存取效能。

我們也可以宣告多維陣列。以下指令宣告元素型態為 double 的二維陣列:

double mtx[][];

⚠️ 記憶體關鍵觀念:
值得注意的是,Java 的多維陣列本質上是「陣列的陣列(Array of Arrays)」。這意味著 mtx 每一列(Row)的一維陣列,在記憶體中通常是分散且不連續的。雖然每一列內部的元素依然連續,但整張二維矩陣在記憶體中並非完美的一體成型。
在進行大型科學運算或需要極致效能的場景時,必須特別留意這個特性所帶來的 CPU 快取(Cache Miss)開銷。

實作記憶體連續的二維陣列

為了克服 Java 多維陣列不連續的問題,在影像處理或科學運算等追求極致效能的場景中,業界標準作法是將二維矩陣「扁平化(Flatten)」為一維陣列

我們可以根據列(Rows)與行(Cols)的乘積總和,宣告一個真正連續的一維陣列。存取元素時,透過「列索引 * 總行數 + 行索引」的公式計算出對應的一維索引值。參考以下程式碼片段:

int rows = 3;
int cols = 4;
double[] mtx = new double[rows * cols]; 

double value = mtx[1 * cols + 2]; 

透過這種設計,整張矩陣在記憶體中會呈現連續的排列,能完美契合 CPU 快取機制(Cache Locality),大幅提升迴圈運算時的執行效率。

建立陣列

使用保留字 new 來建立陣列。以下指令建立元素型態為 double、容量為 10 的一維陣列:

double vec[] = new double[10];

以下指令建立元素型態為 double、維度分別為 32 的二維陣列:

double mtx[][] = new double[3][2];

也可以建立陣列的陣列 (array of array)。參考以下指令:

/* Initialize the outer layer
    of the 2D array. */
double mtx[][] = new double[3][];

/* Initialize the inner layer
    of the 2D array. */
for (int i = 0; i < mtx.length; ++i) {
    mtx[i] = new double[2];
}

以下指令使用陣列實字 (array literal) 來建立陣列並對其賦值:

double vec[] = { 1, 2, 3, 4, 5 };

同理,也可以用在二維陣列上:

double mtx[][] = {
    { 1, 2 },
    { 3, 4 },
    { 5, 6 }
};

存取陣列元素

陣列使用非負整數做為索引來存取元素。陣列索引的起始值為 0 而非 1,這是因為陣列索引是陣列元素的偏移值 (offset)。

參考以下範例:

public class MainProgram
{
    public static void main(String[] args)
    {
        int arr[] = {1, 2, 3, 4, 5};

        assertCond(5 == arr[4]);
    }

    /* Home-made assertion. */
    public static void assertCond(boolean cond)
    {
        if (!cond)
            throw new IllegalArgumentException("Wrong condition");
    }
}

走訪陣列

比較傳統的方式是使用計數器 (counter) 來走訪陣列元素:

int arr[] = {1, 2, 3, 4, 5};

for (int i = 0; i < arr.length; ++i)
    System.out.println(arr[i]);

由於 Java 的陣列有儲存陣列長度的資訊,使用計數器會比在 C 或 C++ 簡單。

也可以使用隱式的迭代器 (iterator) 來走訪:

int arr[] = {1, 2, 3, 4, 5};

for (int n : arr)
    System.out.println(n);

動態增減陣列元素

陣列無法增減長度。雖然程式設計者可以使用資料結構的概念來製作動態陣列 (dynamic array),Java 標準物件庫已經有 ArrayList 了,程式設計者不應該重造次等輪子。

關於作者

位元詩人 (ByteBard) 是資訊領域碩士,喜歡用開源技術來解決各式各樣的問題。這類技術跨平台、重用性高、技術生命長。

除了開源技術以外,位元詩人喜歡日本料理和黑咖啡,會一些日文,有時會自助旅行。

近期在學習韓文,並將語言學習的心得轉化為開源專案,回饋社群。

這裡是位元詩人的 GitHub 個人頁