深入理解陣列(上)

弒曉風發表於2019-02-18

定義:

陣列(Array)是一種線性表資料結構。它用一組連續的記憶體空間,來儲存一組具有相同型別的資料

該定義有幾個關鍵點:

1.線性表。

顧名思義,線性表就是資料排成像一條線一樣的結構。這種結構具有下列特點:存在一個唯一的沒有前驅的(頭)資料元素;存在一個唯一的沒有後繼的(尾)資料元素;此外,每一個資料元素均有一個直接前驅和一個直接後繼資料元素。

常見的線性表還有連結串列佇列等等.   

2.一組連續的記憶體空間和相同型別的資料。

連續記憶體空間,說明在宣告陣列時需要預先指定大小

相同型別資料很簡單,這裡和容器作區分,更容易瞭解,以java ArrayList容器為例,首先陣列只能儲存基本型別,比如int、long,ArrayList則需要在進一步,使用基本型別的包裝類,儲存Integer,Long。這裡要提一下Autoboxing、Unboxing 則有一定的效能消耗,所以如果特別關注效能,或者希望使用基本型別,就可以選用陣列。

這也就是說,儲存陣列時,對記憶體要求比較高,這就涉及到記憶體的分配了。假設宣告一個1M大小的陣列時,也就說不能是零散的記憶體塊不能連線成一個大的空間,而必須要一整塊連續的記憶體空間才能申請成功。

因為在申請和釋放許多小的塊可能會產生如下狀態:在已用塊之間存在很多小的空閒塊。進而申請大塊記憶體失敗,雖然空閒塊的總和足夠,但是空閒的小塊是零散的,不能滿足申請的大小。

特性:隨機訪問。

優點:陣列支援隨機訪問,根據下標隨機訪問的時間複雜度為 O(1)。

說到隨機訪問,提一下定址公式:

一維陣列:

a[i]_address = base_address + i * data_type_size

對於 m * n 的二維陣列,a [ i ][ j ] (i < m,j < n)的地址為:

address = base_address + ( i * n + j) * type_size

(data_type_size 表示陣列中每個元素的大小)

在這裡在說一下定址公式。以一個int[10]的陣列為例,計算機給陣列分配了一塊連續記憶體空間 1000~1039,其中,記憶體塊的首地址為 base_address = 1000,因為存的是int型,這裡data_type_size便是4個位元組。

套入定址公式可得 a[0] 對應 1000~1003,a[1]對應1004~1007,,,以此類推。

缺點:增刪低效,因為陣列中的資料是有序的,每一次的增刪(除了頭部和尾部)都伴隨著陣列的移動。

簡單羅列一下時間複雜度:

1) 插入:最好O(1) 最壞O(n) 平均O(n)

小技巧:陣列若無序,插入新的元素時,可以將第K個位置元素移動到陣列末尾,把心的元素,插入到第k個位置,此處複雜度為O(1)。
3) 刪除:最好O(1) 最壞O(n) 平均O(n)

小技巧:多次刪除集中在一起,提高刪除效率(JVM 標記清除垃圾回收演算法的核心思想)。簡單地說,就是每次的刪除不是真正的刪除,而是新增標記,什麼時候等存滿了,統一集中在一起刪。

注意:

下標越界

一定要主要陣列的下標是從0開始,一定要注意最大的訪問值是size-1

在 C 語言中,只要不是訪問受限的記憶體,所有的記憶體空間都是可以自由訪問的。根據我們前面講的陣列定址公式,a[6] 也會被定位到某塊不屬於陣列的記憶體地址上,而這個地址正好是儲存變數 i 的記憶體地址,那麼 a[6]=0 就相當於 i=0,所以就會導致程式碼無限迴圈。

陣列越界在 C 語言中是一種未決行為,並沒有規定陣列訪問越界時編譯器應該如何處理。因為,訪問陣列的本質就是訪問一段連續記憶體,只要陣列通過偏移計算得到的記憶體地址是可用的,那麼程式就可能不會報任何錯誤。

JAVA本身還好,自身會做越界檢查,如果越界則會丟擲                        java.lang.ArrayIndexOutOfBoundsException

疑問:

陣列和容器之間有什麼區別,哪個更好一些?

以Java ArrayList為例:

ArrayList是Java集合框架類的一員,可以稱它為一個動態陣列。它還有一個優勢就是將陣列的操作封裝起來,比如增刪查,以及動態擴容。

重點來了,動態擴容

當大小為n的陣列此時已經存了n個資料,當n+1的資料需要儲存的時候,陣列就需要重新分配一塊更大的連續的記憶體,然後將之前的資料複製過去,然後再進行操作,費時費力。

如果使用ArrayList的話,就完全不需要考慮底層了,因為它已經將底層擴容邏輯封裝好了,當空間不夠的時候,它會自動擴充為原來的1.5倍大小。

總結:對於業務開發,直接使用容器就足夠了,省時省力。雖然損耗一丟丟效能,但是完全不會影響到系統整體的效能。但如果做一些非常底層的開發,比如開發網路框架,效能的優化需要做到極致,這個時候陣列就會優於容器,成為首選。

end

您的點贊和關注是對我最大的支援,謝謝!

相關文章