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。