數組(Array)是一種線性表數據結構。它用一組連續的內存空間,來存儲一組具備相同類型的數據。對於數組,你要掌握兩個關鍵點。redis
1. 線性表算法
線性表就是數據排成像一條線同樣的結構。每一個線性表上的數據最多隻有前和後兩個方向。其實除了數組,鏈表、隊列、棧等也是線性表結構。編程
而與它相對立的概念是非線性表,好比二叉樹、堆、圖等。之因此叫非線性,是由於,在非線性表中,數據之間並非簡單的先後關係。好比說下面樹形結構中的D節點就有三個方向的數據。數組
2. 連續的內存空間和相同類型的數據安全
數組的存儲空間是連續的,並且必須存儲相同類型的數據。正是由於這兩個限制,它纔有了一個堪稱「殺手鐗」的特性:「隨機訪問」。但有利就有弊,這兩個限制也讓數組的不少操做變得很是低效,好比要想在數組中刪除、插入一個數據,爲了保證連續性,就須要作大量的數據搬移工做。網絡
這邊解釋下隨機訪問的含義。隨機訪問是指經過元素的下標能立馬定位到元素在數組中的位置,隨機查找的時間複雜度爲O(1)。
有的人可能把在數組中查找元素和隨機訪問搞混了。在數組中查找元素必須進行數組遍歷,時間複雜度是O(n),即便是排序的數組,經過二分查找,時間複雜度是O(logn)。數據結構
1. 插入操做框架
假設數組的長度爲 n,如今,若是咱們須要將一個數據插入到數組中的第 k 個位置。爲了把第 k 個位置騰出來,給新來的數據,咱們須要將第 k~n 這部分的元素都順序地日後挪一位。這個操做的時間複雜度是O(n)。編程語言
若是數組中的數據是有序的,咱們在某個位置插入一個新的元素時,就必須按照剛纔的方法搬移 k 以後的數據。可是,若是數組中存儲的數據並無任何規律,數組只是被看成一個存儲數據的集合。在這種狀況下,若是要將某個數據插入到第 k 個位置,爲了不大規模的數據搬移,咱們還有一個簡單的辦法就是,直接將第 k 位的數據搬移到數組元素的最後,把新的元素直接放入第 k 個位置。性能
爲了更好地理解,咱們舉一個例子。假設數組 a[10]中存儲了以下 5 個元素:a,b,c,d,e。咱們如今須要將元素 x 插入到第 3 個位置。咱們只須要將 c 放入到 a[5],將 a[2]賦值爲 x 便可。最後,數組中的元素以下: a,b,x,d,e,c。
利用這種處理技巧,在特定場景下,在第 k 個位置插入一個元素的時間複雜度就會降爲 O(1)。(直接將指定位置的元素放到數組最後一位後面array[array.length]=k)
2. 刪除操做
跟插入數據相似,若是咱們要刪除第 k 個位置的數據,爲了內存的連續性,也須要搬移數據,否則中間就會出現空洞,內存就不連續了。刪除操做的時間複雜度也是O(n)。
實際上,在某些特殊場景下,咱們並不必定非得追求數組中數據的連續性。若是咱們將屢次刪除操做集中在一塊兒執行,刪除的效率是否是會提升不少呢?
咱們繼續來看例子。數組 a[10]中存儲了 8 個元素:a,b,c,d,e,f,g,h。如今,咱們要依次刪除 a,b,c 三個元素。
爲了不 d,e,f,g,h 這幾個數據會被搬移三次,咱們能夠先記錄下已經刪除的數據。每次的刪除操做並非真正地搬移數據,只是記錄數據已經被刪除。當數組沒有更多空間存儲數據時,咱們再觸發執行一次真正的刪除操做,這樣就大大減小了刪除操做致使的數據搬移。
若是你瞭解 JVM,你會發現,這不就是 JVM 標記清除垃圾回收算法的核心思想。
數組是每一個編程語言都會直接提供的數據結構。並且不少語言提供了更高級的容器實現,好比Java中的ArrayList。ArrayList 最大的優點就是能夠將不少數組操做的細節封裝起來。好比前面提到的數組插入、刪除數據時須要搬移其餘數據等。另外,它還有一個優點,就是支持動態擴容。
數組自己在定義的時候須要預先指定大小,由於須要分配連續的內存空間。若是咱們申請了大小爲 10 的數組,當第 11 個數據須要存儲到數組中時,咱們就須要從新分配一塊更大的空間,將原來的數據複製過去,而後再將新的數據插入。
若是使用 ArrayList,咱們就徹底不須要關心底層的擴容邏輯,ArrayList 已經幫咱們實現好了。每次存儲空間不夠的時候,它都會將空間自動擴容爲 1.5 倍大小。
不過,這裏須要注意一點,由於擴容操做涉及內存申請和數據搬移,是比較耗時的。因此,若是事先能肯定須要存儲的數據大小,最好在建立 ArrayList 的時候事先指定數據大小。
做爲高級語言編程者,是否是數組就無用武之地了呢?固然不是,有些時候,用數組會更合適些,我總結了幾點本身的經驗:
對於業務開發,直接使用容器就足夠了,省時省力。畢竟損耗一丟丟性能,徹底不會影響到系統總體的性能。但若是你是作一些很是底層的開發,好比開發網絡框架,性能的優化須要作到極致,這個時候數組就會優於容器,成爲首選。
數組的下標爲何從0開始?
其實數組的下標更確切的表述是相對於首地址的偏移量,這樣更容易尋址。
從數組存儲的內存模型上來看,「下標」最確切的定義應該是「偏移(offset)」。前面也講到,若是用 a 來表示數組的首地址,a[0]就是偏移爲 0 的位置,也就是首地址,a[k]就表示偏移 k 個 type_size 的位置,因此計算 a[k]的內存地址只須要用這個公式: a[k]_address = base_address + k * type_size 可是,若是數組從 1 開始計數,那咱們計算數組元素 a[k]的內存地址就會變爲: a[k]_address = base_address + (k-1)*type_size
1. 數組的一些其餘應用
數組的應用真的不少,好比redis的內部實現,壓縮鏈表,快速鏈表,還有後來搞出一個緊湊列表來替代壓縮列表。並且不少自定義協議都是用數組作的,好比rocketmq的協議,前面幾位表明什麼,後面幾位表明什麼。
2. 標記清除法
標記清除具體步驟以下:
其實全部的垃圾收集算法均可以分爲:標記階段和收集階段。只是不一樣的垃圾回收機制在這兩個階段使用的算法不同。
標記清除法帶來的問題
STW指的是JVM把全部線程都暫停了,這樣全部的對象都不會被修改,這個時候去掃描是絕對安全的。
3. 畫圖軟件推薦
ipad Paper
4.分代收集法 分代收集算法(針對JDK1.8如下): 根據對象的存活週期分爲老年代,新生代,永久代 a、在新生代中,每次GC時都發現有大批對象死去,只有少許存活,使用複製算法。即在垃圾回收時,將正在使用的內存中存活對象複製到另外一塊未使用的內存中。以後清理正在使用的內存中全部對象,交換兩塊內存角色。反覆進行,完成垃圾回收。 b、在老年代中,由於對象存活率高、沒有額外空間對他進行分配擔保,使用「標記-清理」/「標記-整理」算法。即在標記階段,遍歷全部的GC Roots,而後將全部GC Roots可達的對象標記爲存活的對象。清除階段,清除的過程將遍歷堆中全部的對象,將沒有標記的對象所有清除掉。 c、永久代(Permanet Generation)/ 元空間(Metaspace) 永久代用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯後的代碼等數據。是JVM規範中方法區的具體實現。 是Hotspot虛擬機特有的概念,方法區/永久代是非堆內存。