前面瞭解完List接口的相關實現類 Android中須要瞭解的數據結構(一)java
Map與List、Set接口不一樣,它是由一系列鍵值對組成的集合,提供了key到Value的映射。在Map中它保證了key與value之間的一一對應關係。也就是說一個key對應一個value,因此它不能存在相同的key值,固然value值能夠相同。
實現map的集合有:HashMap、HashTable、TreeMap、WeakHashMap。bootstrap
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {}
複製代碼
HashMap繼承了Map,實現了map的全部方法。key和value容許使用所有的元素,包括null, 注意遍歷hashMap是隨機的,若是你想定義遍歷順序,請使用LinkedHashMap。
在Java言中,最基本的結構就是兩種,一個是數組,另一個是模擬指針(引用),全部的數據結構均可以用這兩個基本結構來構造的,HashMap也不例外。HashMap其實是一個「鏈表散列」的數據結構,即數組和鏈表的結合體。數組
Java8 對HashMap 底層作了優化 本文以Java8爲例安全
/**
* An empty table instance to share when the table is not inflated.
* Orcle的JDK中名字叫Node<K,V>
*/
static final HashMapEntry<?,?>[] EMPTY_TABLE = {};
/**
The table, resized as necessary. Length MUST Always be a power of two.
Orcle的JDK中名字叫Node<K,V>
*/
transient HashMapEntry<K,V>[] table = (HashMapEntry<K,V>[]) EMPTY_TABLE;
//Orcle的JDK
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
/**
* Holds cached entrySet(). Note that AbstractMap fields are used
* for keySet() and values().
*/
transient Set<Map.Entry<K,V>> entrySet;
複製代碼
數組名叫table,初始化時爲空。HashMapEntry/Node是HashMap的靜態內部類,數據節點都保存在這裏面:bash
static class HashMapEntry<K, V> implements Entry<K, V> {
final K key;
V value;
final int hash;
HashMapEntry<K, V> next;
}
//java8
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
複製代碼
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
}
複製代碼
int threshold;// 所能容納的key-value對極限
final float loadFactor;//負載因子 默認0.75
int modCount;
int size;
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
複製代碼
HashMap是經過transient Node<K,V>[]table
來存儲數據,Node就是數組的元素,每一個Node其實就是一個key-value對,它持有一個指向下一個元素的引用,這就構成了鏈表。 數據結構
那麼爲何要有鏈表呢?緣由是爲了解決 哈希衝突 當咱們新增或者查找一個元素的時候,咱們都會經過將咱們的key的hashcode經過哈希函數映射到數組中的某個位置,經過數組下標一次定位就可完成操做。
若是兩個不一樣的元素,經過哈希函數得出的實際存儲地址相同怎麼辦?也就是說,當咱們對某個元素進行哈希運算,獲得一個存儲地址,而後要進行插入的時候,發現已經被其餘元素佔用了,其實這就是所謂的哈希衝突,也叫哈希碰撞。
哈希衝突的解決方案有多種:開放定址法(發生衝突,繼續尋找下一塊未被佔用的存儲地址),再散列函數法,鏈地址法,而HashMap便是採用了鏈地址法,也就是數組+鏈表的方式。多線程
HashMap中的核心put方法:app
public V put(K key, V value) {
// 對key的hashCode()作hash
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//tab爲空則建立
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//計算index,並對null作處理
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 節點key存在,直接覆蓋value
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判斷該鏈爲紅黑樹
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//該鏈爲鏈表
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//鏈表長度大於8轉換爲紅黑樹進行處理
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//超過最大容量 就擴容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
複製代碼
HashMap中put元素的時候,先根據key的hashCode從新計算hash值,根據hash值獲得這個元素在數組中的位置(即下標),若是數組該位置上已經存放有其餘元素了,則經過key 的 equals 比較返回 true,新添加 Node 的 value 將覆蓋集合中原有 Node 的 value,但key不會覆蓋。若是這兩個 Node 的 key 經過 equals 比較返回 false,新添加的 Node 將與集合中原有 Node 造成 鏈表。函數
因此重寫equals方法必需要重寫hashcode方法post
HashMap由數組+鏈表組成的,數組是HashMap的主體,鏈表則是主要爲了解決哈希衝突而存在的,若是定位到的數組位置不含鏈表,那麼對於查找,添加等操做很快,僅需一次尋址便可;若是定位到的數組包含鏈表,對於添加操做,其時間複雜度爲O(n),首先遍歷鏈表,存在即覆蓋,不然新增;對於查找操做來說,仍需遍歷鏈表,而後經過key對象的equals方法逐一比對查找。因此,性能考慮,HashMap中的鏈表出現越少,性能纔會越好。
public class Hashtable<K,V> extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable{}
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* Constructs a new, empty hashtable with a default initial capacity (11)
* and load factor (0.75).
*/
public Hashtable() {
this(11, 0.75f);
}
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
複製代碼
Hashtable繼承Dictionary類,一樣是經過key-value鍵值對保存數據的數據結構。 解決衝突時與HashMap也同樣也是採用了散列鏈表的形式,Hashtable和HashMap最大的不一樣是Hashtable的方法都是同步的,在多線程中,你能夠直接使用Hashtable,而若是要使用HashMap,則必需要本身實現同步來保證線程安全。固然,若是你不須要使用同步的話,HashMap的性能是確定優於Hashtable的。此外,HashMap是接收null鍵和null值的,而Hashtable不能夠。
Hashtable於HashMap的區別
//Hashtable
for (int i = oldCapacity ; i-- > 0 ;) {
for (HashtableEntry<K,V> old = (HashtableEntry<K,V>)oldMap[i] ; old != null ; ){
HashtableEntry<K,V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (HashtableEntry<K,V>)newMap[index];
newMap[index] = e;
}
//HashMap
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
複製代碼
Hashtable在計算元素的位置時須要進行一次除法運算,而除法運算是比較耗時的。public class TreeMap<K,V> extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable{}
複製代碼
有序散列表,實現SortedMap接口,底層經過紅黑樹實現。能夠根據key的天然順序進行自動排序,當key是自定義對象時,TreeMap也能夠根據自定義的Comparator進行排序。另外,TreeMap和HashMap同樣,也是非同步的。