什么是數據結構?
簡單說就是以某種方式把一堆數據組織起來。通常不同的組織方式會有不同的特性。Java中常見的數據結構有:數組、鏈表、List、Map等等。
為什么會有這么多數據結構?
不同的數據結構形式通常都有各自的使用場景。比如常見的ArrayList。使用ArrayList你可以方便添加和移除數據。但如果看過ArrayList源碼的話會知道ArrayList底層還是使用數組。那為什么還要用ArrayList?嚴格來說Java最底層提供的數據結構方式只有基本類型、引用類型和數組。其他任何類型都是由這些基本的數據結構封裝起來的,或者說其他任何Java數據結構,一層層往下拆最終都會變成這幾個。比如ArrayList就是從數組封裝過來的。那問題來了,數組能做到,那為什么還要用ArrayList?主要是因為ArrayList進行增刪改查使用起來非常方便,但數組處理起來就要復雜多了。所以把這些常用的需求封裝起來就出現了ArrayList。
List
大家在使用List的時候可能通常都會寫下面這樣的代碼
List<String> list = new ArrayList<>();
Java中List是個接口,所以需要用它的實現類來實例化。那當你寫下上面代碼的時候有沒有思考過一個問題,為什么要用ArrayList。Java里有沒有其他的List實現類?當然有。AndroidStudio或者Intelij Idea可以找到List實現類,然后通過Ctrl+H看到所有的繼承自List的類和接口。你可能會看到非常多,但比較常用的主要就是ArrayList,LinkedList和Vector。這三個都是List實現類。有沒有想過一個問題,如果你需要用到List了,那么究竟應該用什么哪個類?就比如你寫了一個RecyclerView的Adapter。有Adapter就一定會需要有個數據源。這時候應該選哪個實現類?其實明白這三者區別就很容易選了。
- ArrayList內部實現是個數組、有序存儲。數組大家都知道長度不可變,那么append數據消耗可能會比較大,因為可能會需要resize,如果是insert或者remove數據消耗就會比較大,因為需要數組拷貝。但是get速度非常快,因為可以直接找到對應位置。
private static List<String> add0(String[] array) {
List<String> list = new ArrayList<>();
for (String s : array) {
list.add(s);
}
return list;
}
private static List<String> add1(String[] array) {
List<String> list = new ArrayList<>();
list.addAll(Arrays.asList(array));
return list;
}
private static void remove0(List<String> list, int index, int count) {
for (int i = 0; i < count; i++) {
list.remove(index + i);
}
}
private static void remove1(List<String> list, int index, int count) {
list.subList(index, index + count).clear();
}
add0和add1哪個寫法好?remove0和remove1哪個寫法好?add0一個個把數據add到ArrayList,最多ArrayList可能會發生array.length次數組resize。但是add1就最多只會發生一次數組resize。remove0和remove1也是一樣。
- LinkedList 看名字就知道這是一個鏈表實現的List,內部不再有數組。那么對于鏈表來說添加數據就是打開鏈條,接上鏈條,再鎖上鏈條的過程。如果是頻發插入數據,比如數據從1條連續插入到10萬條,效率可以認為是一樣。但是要get數據就比較坑了,通過index并不能直接定位到數據,所以就需要遍歷數據了。remove數據也一樣,只要任何是設計到index的操作效率都會比較低,除了頭和尾,因為LinkedList針對頭尾做了緩存。
- Vector 平時見的會非常少,它內部實現也是用數組,但跟ArrayList不同的是它是線程安全的,你會發現它方法有synchronized關鍵字。除了這點其他特性可以認為是等同于ArrayList。
好,又回到前面老問題,RecyclerView的Adapter應該用什么數據結構?我們思考下Adapter的應用場景。通常Adapter不會非常頻繁的添加數據,每次滑過RecyclerView都會觸發onBindViewHolder,要bind就一定需要從數據源通過index取數據出來,取操作會比較頻繁,那我們肯定不能用LinkedList了(但其實考慮到RecyclerView顯示數據都是連續的,所以如果用鏈表來訪問臨近數據效率反而會更高),又因為Adapter基本上不會出現要去子線程操作數據源的情況。所以也不需要線程安全。剩下的就當然是ArrayList了,而且ArrayList取效率本來就非常高,完全符合需求。
Map
Map是一個鍵值對數據結構。Map的實現類也非常多,但主要是:HashMap、LinkedHashMap,TreeMap,HashTable,Android里又加了ArrayMap和SparseArray。Map跟List不一樣的是不光能存數據了,還能額外多保存一個key。那有個問題,假如我新建一個下面這樣的Data類。然后把所有鍵值對通過Data對象保存到List里,那我不是一樣實現了鍵值對保存嗎?
public class Data<K,V>{
private K mKey;
private V mValue;
public Data(K key, V value) {
mKey = key;
mValue = value;
}
public K getKey() {
return mKey;
}
public void setKey(K key) {
mKey = key;
}
public V getValue() {
return mValue;
}
public void setValue(V value) {
mValue = value;
}
}
那我為什么要用各種Map實現類呢?還是同樣的問題,雖然功能上確實能實現但是效率上不同的實現方式差別會非常大。
- HashMap
大家寫Map可能絕大部分情況下都是使用HashMap。HashMap底層是通過數組加鏈表(Java1.8新加了紅黑樹)來實現。為什么要搞這么復雜呢?又是數組又是鏈表又是樹的?其實還是為了提高效率。上面的把鍵值對封裝成Data,加到List中。get效率跟HashMap比就會非常慘了。因為get只能一個個去遍歷,但是HashMap就可以通過key的hashcode非常高效的get數據。 - LinkedHashMap
看名字就知道多了個Linked,意思就是排序。LinkedHashMap本身就是繼承自HashMap,所以增刪改查方面和HashMap基本是一致的。區別主要就是通過entrySet遍歷了。LinkedHashMap會保存元素的添加順序然后按順序遍歷,但HashMap就是無序了。 - TreeMap
TreeMap內部是個紅黑樹。從使用角度來看,它跟LinkedHashMap主要區別就LinkedHashMap保存的是元素的插入順序,而TreeMap則是對key排序。遍歷可以得到一個按key排序的結果。 - HashTable
內部是個鏈表,另外就是線程同步。 - SparseArray
SparseArray內部依然是使用數組來實現。但是限制了key只能int,而且沒有裝箱,HashMap的key只能是Integer。所以Sparse性能會更好,它內部做了數據壓縮,來稀疏數組的數據,節省內存。 - ArrayMap
ArrayMap內部也是使用數組。查找數據會通過二分法來提高效率,Google推薦用ArrayMap來代替HashMap。