前不久面試官讓我說一下怎麼理解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