当我们需要存储key-value这样格式的数据的时候,我们通常会使用Map,实现Map接口的类有我经常使用的是HashMap,所以简单介绍下HashMap的源码部分。。。。。。。
1. HashMap的存储结构
HashMap的数据结构包括了数组与链表,他首先将每一对key-value数据存储为一个Entry类型(Entry内部包括key,value,一个int型的hash,一个next对象,类似指针),然后将他们存储到数组的对应位置,数组的每个位置对应一个链表,链表内存储Entry类型(每个Entry都有一个next,所以类似链表,下文暂且这么叫)。
如图:
2. 首先看下hashMap的基本变量们:
private static final int MINIMUM_CAPACITY = 4; //map的最小容量
private static final int MAXIMUM_CAPACITY = 1 << 30;//map的最大容量 一定要是2的n次方
static final float DEFAULT_LOAD_FACTOR = .75F; //加载因子 容量不够的时候扩容用的
transient HashMapEntry<K, V>[] table;//就是上文中说的数组
3. 构造函数
取最常用的介绍:
public HashMap(int capacity) { if (capacity < 0) { throw new IllegalArgumentException("Capacity: " + capacity); } if (capacity == 0) { @SuppressWarnings("unchecked") HashMapEntry<K, V>[] tab = (HashMapEntry<K, V>[]) EMPTY_TABLE; table = tab; threshold = -1; // Forces first put() to replace EMPTY_TABLE return; } if (capacity < MINIMUM_CAPACITY) { capacity = MINIMUM_CAPACITY; } else if (capacity > MAXIMUM_CAPACITY) { capacity = MAXIMUM_CAPACITY; } else { capacity = Collections.roundUpToPowerOfTwo(capacity); } makeTable(capacity); }
capacity是指Map的大小,上面的码码指定了上文说到的数组的大小,进入最后的方法:
private HashMapEntry<K, V>[] makeTable(int newCapacity) { HashMapEntry<K, V>[] newTable = (HashMapEntry<K, V>[]) new HashMapEntry[newCapacity]; table = newTable; threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity return newTable; }
新建了一个数组叫table(上文的数组叫table),也就是HashMap中主要的数据存储部分,threshold是指允许数据最多的时候的数量。
4. 常用方法
a. put
public V put(K key, V value) { if (key == null) { return putValueForNullKey(value); } int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) { if (e.hash == hash && key.equals(e.key)) { preModify(e); V oldValue = e.value; e.value = value; return oldValue; } } // No entry for (non-null) key is present; create one modCount++; if (size++ > threshold) { tab = doubleCapacity(); index = hash & (tab.length - 1); } addNewEntry(key, value, hash, index); return null; }
当添加了一个key为null时,调用putValueForNullKey(value)方法,将这个对象添加到table[0]中。正常添加数据时,根据找到key对应的hash值,然后将获得的hash值与HashMap的长度-1进行与运算(HashMap的长度为2的n次方的好处就在这一步体现,充分节约数据空间也减少了冲突),获得的数值就是这个Entry英才存储的数组的位置,即table[index],然后找到数组的index位置上的链表(通过Entry内部的next不断遍历),并将Entry添加到链表最后一位,那个if判断是当内部存在hash和key都与将添加的key一致的情况时,返回原来的key对应的value值,然后替换成新加入的value值,put完成啦~
b. get
public V get(Object key) {
if (key == null) {
HashMapEntry<K, V> e =entryForNullKey;
return e == null ? null :e.value;
}
int hash =Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab =table;
for (HashMapEntry<K, V> e =tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash ==hash && key.equals(eKey))) {
return e.value;
}
}
return null;
}
与put操作相反,为null就在table[0]中寻找,如果不为空,根据key计算hash值,然后将hash值和HashMap的长度减1相与,遍历链表找到hash 与key和传入的Key对应的相同的Entry,然后取出其中的value值。
简直匆忙,理解尚浅,日后修补其他方法。