Java數據結構解析

前不久面試官讓我說一下怎麼理解java數據結構框架,之前也看過部分源碼,balabala講了一堆,現在總結一下。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章