1. 程式人生 > >JAVA常用資料結構及原理分析

JAVA常用資料結構及原理分析

java.util包中三個重要的介面及特點:List(列表)、Set(保證集合中元素唯一)、Map(維護多個key-value鍵值對,保證key唯一)。其不同子類的實現各有差異,如是否同步(執行緒安全)、是否有序。
常用類繼承樹:
這裡寫圖片描述

以下結合原始碼講解常用類實現原理及相互之間的差異。

Collection (所有集合類的介面)
List、Set都繼承自Collection介面,檢視JDK API,操作集合常用的方法大部分在該介面中定義了。
這裡寫圖片描述

Collections (操作集合的工具類)
對於集合類的操作不得不提到工具類Collections,它提供了許多方便的方法,如求兩個集合的差集、並集、拷貝、排序等等。
由於大部分的集合介面實現類都是不同步的,可以使用Collections.synchronized*方法建立同步的集合類物件。


如建立一個同步的List:
List synList = Collections.synchronizedList(new ArrayList());
其實現原理就是重新封裝new出來的物件,操作物件時用關鍵字synchronized同步。看原始碼很容易理解。
Collections部分原始碼:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <code class="language-java hljs ">//Collections.synchronizedList返回的是靜態類SynchronizedCollection的例項,最終將new出來的ArrayList物件賦值給了Collection<e> c。
static class SynchronizedCollection<e> implements Collection<e>, Serializable { final Collection<e> c;  // Backing Collection final Object mutex;     // Object on which to synchronize SynchronizedCollection(Collection<e> c) { if (c==null) throw new NullPointerException(); this
.c = c; mutex = this; } //... public boolean add(E e) { //操作集合時簡單呼叫原本的ArrayList物件,只是做了同步 synchronized (mutex) {return c.add(e);} } //... }</e></e></e></e></e></code>

List (列表)

ArrayList、Vector是線性表,使用Object陣列作為容器去儲存資料的,添加了很多方法維護這個陣列,使其容量可以動態增長,極大地提升了開發效率。它們明顯的區別是ArrayList是非同步的,Vector是同步的。不用考慮多執行緒時應使用ArrayList來提升效率。
ArrayList、Vector 部分原始碼:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <code class="language-java hljs ">//ArrayList.add public boolean add(E e) { ensureCapacityInternal(size + 1);  // Increments modCount!! //可以看出新增的物件放到elementData陣列中去了 elementData[size++] = e; return true; } //ArrayList.remove public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) //移除元素時陣列產生的空位由System.arraycopy方法將其後的所有元素往前移一位,System.arraycopy呼叫虛擬機器提供的本地方法來提升效率 System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work return oldValue; } //Vector add方法上多了synchronized關鍵字 public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }</code>

LinkedList是連結串列,略懂資料結構就知道其實現原理了。連結串列隨機位置插入、刪除資料時比線性錶快,遍歷比線性錶慢。
雙向連結串列原理圖:
這裡寫圖片描述
LinkedList部分原始碼:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <code class="language-java hljs ">//原始碼很清晰地表達了原理圖 public class LinkedList<e> extends AbstractSequentialList<e> implements List<e>, Deque<e>, Cloneable, java.io.Serializable { //頭尾節點 transient Node<e> first; transient Node<e> last; } //節點類 private static class Node<e> { //節點儲存的資料 E item; Node<e> next; Node<e> prev; Node(Node<e> prev, E element, Node<e> next) { this.item = element; this.next = next; this.prev = prev; } }</e></e></e></e></e></e></e></e></e></e></e></code>

由此可根據實際情況來選擇使用ArrayList(非同步、非頻繁刪除時選擇)、Vector(需同步時選擇)、LinkedList(頻繁在任意位置插入、刪除時選擇)。

Map(儲存鍵值對,key唯一)

HashMap結構的實現原理是將put進來的key-value封裝成一個Entry物件儲存到一個Entry陣列中,位置(陣列下標)由key的雜湊值與陣列長度計算而來。如果陣列當前下標已有值,則將陣列當前下標的值指向新新增的Entry物件。
有點暈,看圖吧:
這裡寫圖片描述
看完圖再看原始碼,非常清晰,都不需要註釋。

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <code class="language-java hljs ">public class HashMap<k,v> extends AbstractMap<k,v> implements Map<k,v>, Cloneable, Serializable { transient Entry<k,v>[] table; public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); //遍歷當前下標的Entry物件鏈,如果key已存在則替換 for (Entry<k,v> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } addEntry(hash, key, value, i); return null; } } static class Entry<k,v> implements Map.Entry<k,v> { final K key; V value; Entry<k,v> next; int hash; }</k,v></k,v></k,v></k,v></k,v></k,v></k,v></k,v></code>

TreeMap是由Entry物件為節點組成的一顆紅黑樹,put到TreeMap的資料預設按key的自然順序排序,new TreeMap時傳入Comparator自定義排序。紅黑樹網上很多資料,我講不清,這裡就不介紹了。

Set(保證容器內元素唯一性)
之所以先講Map是因為Set結構其實就是維護一個Map來儲存資料的,利用Map結構key值唯一性
HashSet部分原始碼:

?
1 2 3 4 5 6 7 8 9 10 11 <code class="language-java hljs ">public class HashSet<e> extends AbstractSet<e> implements Set<e>, Cloneable, java.io.Serializable {    //無意義物件來作為Map的value private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; } }</e></e></e></code>

HashSet、TreeSet分別預設維護一個HashMap、TreeMap。