1001:陣列
總結
一、什麼是陣列?
1.定義
陣列是一種線性表,它用連續的記憶體空間來儲存多個相同資料型別的資料。怎麼理解這個定義?主要從線性表、連續記憶體和相同資料型別三個關鍵詞來看。
2.什麼是線性表?
顧名思義,資料就像一條直線一樣進行組織,每一個數據都只有前後兩個方向,除了陣列之外,佇列、棧、連結串列也都是線性表。與線性表相對應的就是非線性表,比如樹、圖等。
3.連續記憶體和相同資料型別的限制有什麼影響?
正是由於這兩個限制,才有了陣列的殺手鐗:O(1)級別隨機訪問,但與此同時也造成了其他一些操作的低效,比如O(n)級別隨機插入、刪除操作。為什麼?看下面陣列的特點。
二、為什麼使用陣列?即陣列的特點:擅長隨機訪問,但不擅長隨機插入、刪除操作
1.陣列如何實現高效隨機訪問?
1)陣列根據定址公式獲得資料的儲存位置,然後直接訪問該位置的資料,這是O(1)級別的時間複雜度。
2)定址公式:a[ i ]_address = base_address + i * data_type_size,其中a[ i ]_address表示陣列第i+1個元素下標值,base_address表示陣列的起始地址, data_type_size表示資料型別佔用儲存空間大小。
3)例子:int[ ] a = new int[10]; 要獲得下標值為2的元素,則其地址就是a[ 2 ]_address = base_address + 2 * 4,其中int型別資料佔用4個位元組的記憶體空間。
2.為什麼陣列隨機插入、刪除操作低效?如何優化?
1)為了保證陣列中資料儲存空間的連續性,在指定位置插入或刪除元素時,需要將該位置及其後面的資料整體移動(插入則是後移,刪除則是前移),這會產生O(n)級別的時間複雜度。
2)隨機插入
以int[ ] a = new int[n]為例,向a陣列指定下標值處插入元素,根據具體的位置不同有n中情況,每種情況出現的概率都是1/n。比如在末尾插入,則時間複雜度為O(1),在首位置插入元素,則時間複雜度為O(n),按照平均複雜度計算方式,在a陣列中隨機插入一個數據的平均複雜度為1/n*1+1/n*2+…+1/n*n = O(n)。所以,陣列隨機插入的平均時間複雜度是O(n)級別。
如何優化呢?這裡分為2種情況,即陣列中資料有序還是無序。若陣列中的資料需保持有序,那沒辦法還得一個一個的移動資料,O(n)級別的時間複雜度;若陣列中的資料不要求有序,僅僅作為儲存資料的容器,那就好辦了,可以把插入位置的舊資料先移動到末尾位置,再把新資料插入進去,這下平均時間複雜度就變為O(1)級別了。
3)隨機刪除
按照正常的邏輯,我們每刪除一個數據,就必須向前移動部分其他資料以保證資料儲存空間的連續性,這就是O(n)級別的時間複雜度。
如何優化呢?在特殊情況下,比如刪除多個連續位置的資料,可以將操作合併,只整體移動一次資料,而不是每刪除一次就移動一次或者當我們並不要求資料空間一定連續時,執行刪除操作時並不真正刪除而是先標記該資料無效即可,當陣列空間不足時,再一次性刪除和移動資料,這也是JVM垃圾回收器演算法的核心精髓。這些都能在一定程度上提升隨機刪除的效率。
三、陣列的注意事項有哪些?
使用陣列的過程中最常見的錯誤就是陣列下標越界,在Java語言中,編譯器會自動判斷,若下標越界則編譯不通過。
四、陣列和ArrayList集合的區別是什麼?(面試常問)
1.ArrayList集合是什麼?
ArrayList對陣列的操作細節(比如插入、刪除時的資料移動,還有就是1.5倍自動擴容)進行了封裝。
2.ArrayList集合效能消耗
在自動擴容時有申請記憶體空間和複製整份資料的效能消耗。所以,若能確定資料規模則最好指定ArrayList集合的容量,這樣就能避免自動擴容帶來的效能消耗。
3.陣列和ArrayList集合如何選擇?
1)陣列能夠儲存基本型別和引用型別,集合只能引用型別,因此集合儲存基本型別會發生自動裝箱或拆箱,這會有一定的效能消耗。如果對效能要求特別高,可以考慮選擇陣列。
2)如果資料大小已知且用不到大多數集合方法時,可以考慮選擇陣列。
3)進行業務開發時可以選擇集合以提升開發效率,而進行底層開發時,比如開發網路框架,效能優化需要做大極致,這時陣列就會優於集合。
五、擴充套件知識點
1.為什麼陣列下標從0開始?
因為陣列的首地址是陣列第1個元素儲存空間的起始位置,若用下標0標記第1元素則通過定址公式計算地址時直接使用下標值計算,即a[0]_address = base_address + 0 * data_type_size。若用下標1標記第1個元素則通過定址公式計算地址時需將下標值減1再計算,即a[1]_address = base_address + (1-1) * data_type_size,這樣每次定址計算都多了一步減法操作,增加了效能開銷。
2.多維陣列如何定址?
這個在Java中沒有意義,因為Java中多維陣列的記憶體空間是不連續的,所以,暫不考慮。
3.JVM垃圾回收器演算法的核心精髓是什麼?
若堆中的物件沒有被引用,則其就被JVM標記為垃圾但並沒有釋放記憶體空間,當陣列空間不足時,再一次性釋放被標記的物件的記憶體空間,這就是JVM垃圾回收器演算法的核心精髓。