背景

資料結構是指帶有結構特性的資料元素的集合。在資料結構中,資料之間通過一定的組織結構關聯在一起,便於計算機儲存和使用。從大類劃分,資料結構可以分為線性結構和非線性結構,適用於不同的應用場景。

  • 線性結構:

  線性結構作為最常用的資料結構,它的特點是單個數據之間存在一對一的線性關係。包含兩種不同的儲存結構:順序儲存結構和鏈式儲存結構。順序儲存的線性表稱為順序表,順序表中的儲存元素是連續的。

(線性結構)

  鏈式儲存的線性表被叫做連結串列,連結串列中的儲存元素不一定是連續的,元素節點中存放資料元素以及相鄰元素的地址資訊

  線性結構常見的有:陣列、佇列、連結串列和棧。

  • 非線性結構:

除了線性結構,其他的資料結構均為非線性結構,特點是單個數據之間存在多個對應關係,常見的有:二維陣列,多維陣列,廣義表,樹結構,圖結構

(常見的非線性結構)

稀疏陣列(Sparse Array)

在各種各樣的資料結構中,最基礎、最常用的是陣列。陣列可以非常直觀的表示資料在一維或多維空間中的關係,與現實中的情形更接近,所以被大多數程式設計師當做"首選"的資料結構,然而,在部分應用場景中使用陣列儲存資料時會出現各種各樣的情況,這是就需要在陣列的基礎上,對資料結構進行優化,衍生出稀疏陣列等新的資料結構。

以五子棋局為例,我們應該如何儲存棋盤上的落子情況呢?

(使用二位陣列儲存五子棋盤)

如果使用一個二維陣列對棋盤落子進行儲存,當我們拿到一個棋盤類資料內容時,大部分內容都是沒有意義的0,有意義資料並不相鄰,很多空間被浪費。對於五子棋來說,這個問題可能不是很明顯,但如果"棋盤"足夠大,被浪費的空間就會影響到軟體的功能實現,此時引入稀疏陣列(SparseArray)就具有了重要的意義。

稀疏陣列將陣列中的內容進行壓縮,儲存在一個更為精練的二維陣列中,稀疏陣列的本質其實就是用時間置換空間。

具體的處理的方法是:

  1. 該陣列之中一共有幾行幾列進行記錄
  2. 把相同元素內容忽略後,只記錄具有不同內容單元的位置

稀疏陣列的實現

節約儲存空間顯然是稀疏陣列的一個優勢,但是讀取效能是否可以會比二維陣列差很多?

為了講清這個問題,我們可以先看一下Android中SparseArray的實現邏輯。SparseArray內部是通過兩個陣列來進行資料儲存的。一個儲存key,另外一個儲存value。我們從原始碼中能夠看到key和value各自是用陣列表示:

  private int[] mKeys;

   private Object[] mValues;

同時,SparseArray在儲存和讀取資料時候,使用的是二分查詢法:

 public void put(int key, E value) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
...
}
public E get(int key, E valueIfKeyNotFound) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
...
}

在put新增資料的時候,會使用二分查詢法和之前的key比較當前我們新增的元素的key的大小,然後按照從小到大的順序排列好。所以,SparseArray儲存的元素都是按元素的key值從小到大排列好的。 而在獲取資料的時候,也是使用二分查詢法判斷元素的位置,這樣可以使資料的獲取變得更加高效。所以,在key的資料量(可以理解為棋盤上去掉空白後的棋子數量)不大時,稀疏陣列讀取效能是有保障的。

典型應用場景

做開發的都知道,想讓系統變快有個最簡單的辦法就是加記憶體。對於程式可以做大量的快取來加速,即所謂"空間換時間"。但是在特定環境,程式可使用的記憶體是有限的。

在移動裝置上,記憶體是個稀缺資源,例如iPhone 7的記憶體為2G,而最新款的iPhone 13也僅為4G。所以,稀疏陣列這種"時間換空間"的技術最早被廣泛應用在移動開發領域。

除了移動端,另一個記憶體緊缺的執行環境是瀏覽器。雖然沒有明文規定,但在業界的共同認知裡,瀏覽器會對單一執行緒進行記憶體限制,例如64位的chrome,每個tab頁的記憶體消耗不允許超過4G。這個限制,在單頁面應用還不成熟的十幾年前,不會成為問題。因為,那時大家所關注的,還是如何提升後端的處理效能,前端只是一種靜態的網頁表達方式。

隨著前端工程化的高速發展,各種前端工程腳手架日漸成熟,WebComponent標準被提上日程,企業開始由C/S向B/S應用轉型。這就要求前端開發者,需要面對單頁面處理複雜業務資料的挑戰。前端程式從最開始設計以及整個開發過程中都需要考慮記憶體的使用情況,儘可能的降低記憶體佔用,防止網頁崩潰。以前端電子表格為例,我們通常需要為使用者提供上百萬個單元格(100列 x 1萬行),但其中有資料的單元格可能只有幾百個。為了減少資料模型佔用的記憶體,我們最終的解決方式是將表格的資料儲存方式由常規陣列改成稀疏陣列,記憶體佔用可以降低到幾十分之一,以確保瀏覽器記憶體不會被撐爆。

(稀疏矩陣儲存策略)

不只是“時間換空間”;

相較於傳統的鏈式儲存或是陣列儲存,稀疏矩陣儲存構建了基於索引Key的資料字典。在鬆散佈局的表格資料中,稀疏矩陣只會對非空資料進行儲存,而不需要對空資料開闢額外的記憶體空間。

使用這種特殊的儲存策略,除了可以降低記憶體佔用,還使得資料片段化變得容易,可以隨時框取整個資料層中的一片資料,進行序列化或反序列化,而無需處理同一資料結構內的其他資料。

借用這樣的特性,我們可以隨時替換或恢復整個儲存結構中的任何一個級別的節點,以改變引用的方式高效解決了表格資料回滾和恢復,而這一點也是電子表格支援線上協同的技術基礎。

總結

本節為大家介紹了稀疏陣列的基礎知識,技術實現和應用場景,以前端電子表格為例,展示了這個技術在節約記憶體空間,實現回滾恢復等領域的優勢。

在後續我們還會繼續為大家介紹更多嚴肅和有趣的內容~

覺得不錯點個贊再走吧~