这几天看了一下HashMap的源码,现此于大家分享!
1,HashMap的底层实现。
2,HashMap的扩容机制。
1,底层实现
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); }
此为HashMap的默认构造函数:
loadFactor 加载因子,默认为0.75。
threshold 是能实际容纳的量。(DEFAULT_INITIAL_CAPACITY*loadFactor)
table = new Entry[DEFAULT_INITIAL_CAPACITY];通过这句可以看出HashMap的底层是一个Entry类型的数组。
DEFAULT_INITIAL_CAPACITY是默认数组大小为16
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash; /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; }
Entry类型是什么呢?
final K key; 要存入的key值 V value; 要存入的value值 Entry<K,V> next; 下一个元素的Entry final int hash; 当前key的hash值
由上面的代码可见,这是一个链表对象。 接下来,我们看一下HashMap是如何存值的呢。
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); 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; } } modCount++; addEntry(hash, key, value, i); return null; }
HashMap和Hashtable的区别HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。当
1)key为空的处理 if (key == null) 通过这个方法进行处理 putForNullKey。即如果key为null则其hash值以0处理。
private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) { //判断是否存在key为null的值 if (e.key == null) { //存在进行for循环,如果e.key是null,则将value值进行替换 V oldValue = e.value; e.value = value; e.recordAccess(this); //此方法暂无实现 return oldValue; //返回覆盖前的值 } } modCount++; addEntry(0, null, value, 0); //如果key为null的值不存在,则加入到Entry的数组当中 return null; }
addEntry方法传入hash为0,key是null,bucketIndex为0
void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; //将table[bucketIndex]赋值e, table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //然后再new一个Entry对象赋值给table[bucketIndex], //table数组bucketIndex位置现存放的是新创建的Entry对象 if (size++ >= threshold) resize(2 * table.length); } //如果对象中的值超过了能实际容纳的量threshold,就要进行扩容
2)key不为空的处理
public V put(K key, V value) { if (key == null) return putForNullKey(value); //红色部分为key为空的处理,下面为key非空处理 int hash = hash(key.hashCode()); //计算key的hash值 int i = indexFor(hash, table.length);//通过hash值和table数组的长度来计算本次Entry对象所放table数组的位置。 for (Entry<K,V> e = table[i]; e != null; e = e.next) {//判断table数组位置是否为空,如果不为空,说明此位置为值 Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//判断此位置的值和将要放入的key是否相同,如果相同覆盖。并返回原来的value值。 V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i);//此为往数组所在链表中添加值 return null; }
大家也许在想他是怎么产生链表的。要看一下Entry这个内部类了。
Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; }
最后一个参数是产生链表的关键。从addEntry这个方法可以看出,最后一个参数是对应i位置的Entry对象,而此传入变为当前Entry对象的next即为对应i位置的Entry对象。
以此类推,每出现相同hash而Key不同的元素时,都会往前面添加到列表中。
2,HashMap的扩容机制。
void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); //传入要扩容的大小,即原数据大小的2倍 }
void resize(int newCapacity) { Entry[] oldTable = table; //将扩容之前的Entry的数组赋值给oldTable int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity];//生成新的Entry数组对象 transfer(newTable); //将数据复制到新数组对象中去 table = newTable; threshold = (int)(newCapacity * loadFactor);//重新计算hashMap能存储对象的个数 }
void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity);//重新计算对象在新数组中的位置 e.next = newTable[i]; // 循环每一个Entry对象放到新数组的相应位置 newTable[i] = e; e = next; } while (e != null); } } }
时间: 2024-10-15 18:52:38