在介紹 Python 的列表是如何運作前,先介紹一個特別的物件:陣列(array),一個陣列可以想像成由一連串固定長度的記憶體空間組成,並且從 0 到 N-1 進行索引(index)。在(一)資料類型與節點提到,Java 所有的資料都可以區分為 primitive 或是 reference type,因此在宣告一個陣列時,須指定陣列裡面資料存放的資料類型。
一維陣列(1D-Array)
宣告變數 x 為存放整數的陣列,而這個陣列的長度為 3,索引分別為 0, 1, 2。宣告為整數的預設值為 0,因此變數 x 會指向一個填入三個整數 0 的記憶體空間。
int[] x = new int[3];
而如果要初始化整數,可以用{}將預設值填入,而此時則不需指定整數的數量。
x = new int[]{1, 2, 3};
更改陣列內存放的資料除了可以使用迴圈,指定索引逐步修改陣列內容以外,Java 提供了一個內建的方法,可以直接指定要複製的陣列起始位置、目標陣列的起始位置、複製陣列的長度,直接複製。
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
初學 Python 時,如果想要複製一個陣列,直覺上會使用 y = x 將陣列 x 複製到陣列 y,不過實際上如果使用等號,會直接將陣列 y 的 reference type 地址指向陣列 x,並非實際上的複製,因此需要使用 y = x[:] 的語法才能建立一個新的地址,其內容等同 x,即 Python 對列表的分割(slicing)語法。
二維陣列(2D-Array)
以此類推,同時用兩個中括號可以建立一個二維陣列,從存放整數的陣列,轉變成存放陣列的陣列,意即一個存放一維陣列的一維陣列。同理,可以在中括號內指定矩陣的大小,預設值為 0。
int[][] matrix = new int[3][4];
不同於類別(Class),需要用 「.」(dot)語法來呼叫類別的屬性(attribute),陣列可以直接由索引呼叫或更改陣列內儲存的資料;除此之外,陣列的長度和資料類型在宣告後也無法修改,也沒有方法可以使用,比起 Python 的列表(List),多出許多限制。
(二)單向串列及雙向串列介紹的 DList,在使用 get 方法時,必須使用迴圈或是遞迴逐步搜尋相對應的節點,直到終點。當節點數量愈來愈多時,搜尋所需要的時間也會愈來愈多,即使採用 DList 的資料結構,最差的狀況也要花上列表長度一半的時間才能找到目標節點;相對地,陣列使用索引存取資料時,所花費的時間固定且不受陣列長度影響。接下來將會介紹如何以陣列來實現列表。
陣列列表(Array List)
首先初始化一個陣列,由於需要指定陣列長度,先預設長度為 100 的陣列,同樣地建立一個變數 size 紀錄存放的數量,以及賦予操作物件方法 addLast、getLast、get、size。
目前為止,直觀的透過改變變數 size,實現這些方法。然而當在實現 removeLast 的方法時,並非將索引位置等於變數 size 的內容移除,只要單純的將變數 size 減 1,即使在原索引位置的內容還存在,也不會影響到其他方法的使用,如圖一所示。
然而,當陣列所儲存的內容愈來愈多,直到一開始預設的大小時,因陣列無法改變大小的特性,所以需要重新建立一個更大的陣列,用來存放更多的內容。每次陣列內的空間不夠,也就是變數 size 等於陣列長度時,首先要考慮擴充 n 個空間,即建立一個長度為 size + n 的陣列,複製原陣列內容到新陣列。這個方法雖然可以解決陣列空間不足的問題,不過在效能上的表現並不好,原因是當存放的內容愈來愈多時,size 會遠大於 n,導致陣列需要頻繁地被重新建立。因此,通常會採用 size × n (n=2)的方式「放大」陣列。
當陣列被擴充到愈來愈大時,如果需要將陣列的內容刪除,會產生很多未使用的空間,為了不浪費記憶體,會希望能縮小陣列的長度。同樣地,因陣列無法改變大小的特性,所以需要新建立一個長度更小的陣列存放原陣列的內容,來 「縮小」陣列。通常會定義一個空間使用率的參數來決定縮小的時機,當 size 佔整個陣列長度的百分比低於使用率(0.25)時,縮小陣列;另一方面,定義最小長度,當陣列長度低於最小長度(16)時,不再縮小。
上述提到透過調整變數 size,不刪除陣列的內容,也能實現 removeLast 的方法;不過,當陣列裡面儲存的內容並非整數,而是物件時,此時建議還是一併刪除陣列的內容,原因是當儲存的物件容量非常大時,雖然不刪除也不會影響陣列的功能,卻會浪費許多記憶體空間。
這種透過參數放大或縮小的陣列又稱動態陣列(Dynamic array),Python 的列表(List),即是利用動態陣列來實現列表這個抽象的概念,而在(二)單向串列及雙向串列介紹的串列,同樣也可以實現列表,而不同的方法在不同的情境中各有優缺點,且效能在不同狀況下其表現上也不盡相同。