HashMap类声明

HashMap类声明如下:

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {//……//……
}

从定义中可以看出,哈希表是支持克隆、序列化的。

HashMap继承了AbstractMap,除了toString方法,HashMap重写了其他所有的AbstractMap方法。

HashMap实现的克隆是浅复制。

HashMap自定义了序列化相关的方法。

HashMap实现原理

分析HashMap代码之前先了解它的实现原理,HashMap是通过拉链法实现的,何谓拉链法?HashMap是一个数组,数组的元素是一个链表,键值对是存储在该链表中的。通过这种方法能够解决哈希冲突问题,哈希码相同的键对应的键值对会存储在同一个链表中。拉链表示例图如下:

该哈希表的容量为8,键值对数量为6。目前该哈希表中只有索引0、4、7处有数据。其中e0和e1节点键的哈希码相同,e3、e4、e5节点键的哈希码相同。

HashMap中有加载因子的概念,它代表了哈希表有多满的一种程度,这个值默认是0.75。当哈希表的键值对数量大于等于加载因子和哈希表容量乘积的时候,将会触发重哈希。例如,上图哈希表重哈希的键值对数量临界值为6(8*0.75=6)。因为该哈希表键值对数量已经为6,此时若继续往该哈希表添加键值对,将会触发重哈希。

加载因子是为了哈希表的效率而提出的,这会保证哈希表中的链表不至于太长,从而减少冲突,增加查询效率。平均来说,哈希表中每个链表长度都将小于1,因此查找效率非常高。加载因子越大,冲突的可能性就越大,加载因子越小,冲突的可能性就越小。

有了实现原理的基本了解,接下来分析HashMap的源码实现。

HashMap源码分析

成员变量

HashMap包括了很多成员变量,下面代码详细注释了每个字段的含意:

    //哈希表的默认容量是16//注意:哈希表容量必须是2的n次幂,这是为了更快的定位哈希索引static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //哈希表的最大容量static final int MAXIMUM_CAPACITY = 1 << 30;//哈希表的加载因子默认值0.75static final float DEFAULT_LOAD_FACTOR = 0.75f;//若没有对哈希表执行任何操作,数组是一个空值,只有在需要的时候才会去分配内存static final Entry<?,?>[] EMPTY_TABLE = {};//哈希表的数组,其中每个元素都是一个链表,哈希表数组大小必须是2的n次幂transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//哈希表的键值对数量transient int size;//哈希表扩容的临界值,大小等于加载因子*哈希表的容量int threshold;//加载因子final float loadFactor;//哈希表结构性改变的次数,结构性改变指的是改变了哈希表键值对数量//该字段主要用在迭代器的快速失败策略(fail-fast),后面还会说明transient int modCount;//默认扩容的临界值static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;//一个随机的值,用于计算key的哈希值transient int hashSeed = 0;//键值对的Setprivate transient Set<Map.Entry<K,V>> entrySet = null;

Holder静态内部类

//HashMap的静态内部类,主要是去获取jdk内置的扩容临界值
private static class Holder {static final int ALTERNATIVE_HASHING_THRESHOLD;static {//取jdk内置的扩容临界值String altThreshold = java.security.AccessController.doPrivileged(new sun.security.action.GetPropertyAction("jdk.map.althashing.threshold"));int threshold;try {threshold = (null != altThreshold)? Integer.parseInt(altThreshold): ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;// disable alternative hashing if -1if (threshold == -1) {threshold = Integer.MAX_VALUE;}if (threshold < 0) {throw new IllegalArgumentException("value must be positive integer.");}} catch(IllegalArgumentException failed) {throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);}ALTERNATIVE_HASHING_THRESHOLD = threshold;}
}

构造函数

//包含初始容易和加载因子的构造函数
public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)//HashMap的容量不能大于MAXMUM_CAPACITYinitialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);this.loadFactor = loadFactor;threshold = initialCapacity;//HashMap的init方法其实是个空方法init();
}
//根据指定初始容量、默认加载因子创建一个空的HashMap
public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//根据默认容量和默认加载因子创建一个空的HashMap
public HashMap() {this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//根据指定的HashMap创建一个新的HashMap,新的HashMap将拥有指定HashMap所有的键值对。
//新HashMap的初始容量根据指定HashMap的键值对数量决定,加载因子取默认值
public HashMap(Map<? extends K, ? extends V> m) {this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);//创建好空的HashMap后,根据扩容临界值扩容该HashMapinflateTable(threshold);//将指定哈希表m的所有键值对添加到该哈希表中putAllForCreate(m);
}

扩容方法

//大于等于number的第一个2的n次幂的值,这个保证了哈希表的容量是2的n次幂
private static int roundUpToPowerOf2(int number) {return number >= MAXIMUM_CAPACITY? MAXIMUM_CAPACITY: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}//扩容哈希表
private void inflateTable(int toSize) {int capacity = roundUpToPowerOf2(toSize);threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);table = new Entry[capacity];//初始化哈希种子initHashSeedAsNeeded(capacity);
}

Integer.highestOneBit方法

上面代码片断使用了Integer.highestOneBit方法,这片代码挺有意思的,看下源码实现:

public static int highestOneBit(int i) {i |= (i >>  1);i |= (i >>  2);i |= (i >>  4);i |= (i >>  8);i |= (i >> 16);return i - (i >>> 1);
}

这个代码片断返回不大于参数i的最大的2的n次幂的值。例如如果传入参数17,返回16(2的4次幂)。传入参数16,返回16。传入参数15,返回8。(确实很巧妙~)

这断代码怎么实现的呢?是将参数i的二进制最左边为1的位的后面都设置为0而得出的,例如对于10进制数17,它的二进制为10001,将最高位1的右边都设置为0,变为10000,也就是10进制16。同样对于10进制数15,它的二进制数为1111,将最高位1的右边都设置为0,变为1000,也就是10进制8。

再来看这断代码的实现,首先将i右移1位并和原来的i作或操作,结果就是最高位为1的后面那一位也将变为1,现在最高位为1的连续两位都是1。接着继续将i右移2位并和原来的i作或操作,结果就是最高位为1的连续4位都是1。以此类推,直到最高位为1的后面都变为1。最后将该值减去将最高位设置为0的值,也就只保留了最高位为1的值。

例如对于i=10001(10进制为17):

  • i |= (i >> 1),即i = 10001 | (10001 >> 1),即i = 10001|01000=11001,此时i=11001

  • i |= (i >> 2),即i = 11001 | (11001 >> 2),即i = 11001|00110=11111,此时i=11111

  • i |= (i >> 4),即i = 11111 | (11111 >> 4),即i = 11111|00001=11111,此时i=11111

  • i |= (i >> 8),即i = 11111 | (11111 >> 8),即i = 11111|00000=11111,此时i=11111

  • i |= (i >> 16),即i = 11111 | (11111 >> 16),即i = 11111|00000=11111,此时i=11111

  • i - (i >>> 1),即11111-01111=10000,10进制即是16

HaspMap中的roundUpToPowerOf2方法,传入给highestOneBit的是参数number减1的值,为何需要减1呢?

当参数number的二进制位中不止一个位是1时,此时将该值减1不影响最高位为1的值,因此减1后的值左移1位后最高位为1的就有了两个连续1,调用highestOneBit方法后将最高位的1后面所有值设置为0。得到的值就是符合要求的值。

当参数number的二进制位中只有一个位是1时,那么该值肯定是2的n次幂。其实直接返回该值就好了,此处减1后左移1位,再调用highestOneBit实现的效果即是得到number值。

好了,继续研究HashMap源代码:

哈希码计算和哈希表的定位

//int方法是空实现
void init() {
}
//初始化hashSeed的值
final boolean initHashSeedAsNeeded(int capacity) {boolean currentAltHashing = hashSeed != 0;boolean useAltHashing = sun.misc.VM.isBooted() &&(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);boolean switching = currentAltHashing ^ useAltHashing;if (switching) {hashSeed = useAltHashing? sun.misc.Hashing.randomHashSeed(this): 0;}return switching;
}
//这断代码计算键的哈希码
//如果键是字符串,会调用特定的方法去计算字符串的哈希码
final int hash(Object k) {int h = hashSeed;if (0 != h && k instanceof String) {return sun.misc.Hashing.stringHash32((String) k);}h ^= k.hashCode();h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);
}
//通过给定的哈希码h和哈希表长度length计算该哈希码所在表的位置
//其中length必须是2的n次幂,这样可以更快定位
static int indexFor(int h, int length) {//等价于h%lengthreturn h & (length-1);
}
//返回哈希表键值对数量
public int size() {return size;
}
//返回哈希表是否为空
public boolean isEmpty() {return size == 0;
}

get和contains相关方法

//返回键key对应的值value,若不存在返回null
public V get(Object key) {if (key == null)//key为null直接调用了getForNullKey方法return getForNullKey();//通过键key获得键值对Entry<K,V> entry = getEntry(key);return null == entry ? null : entry.getValue();
}
//处理键key为null的情况
private V getForNullKey() {if (size == 0) {return null;}//查找哈希表索引0处的链表,可以看出key为null的键值对一定存储在哈希表的索引0上for (Entry<K,V> e = table[0]; e != null; e = e.next) {if (e.key == null)return e.value;}return null;
}
//返回键key是否存在
public boolean containsKey(Object key) {return getEntry(key) != null;
}
//返回键key对应的键值对,若不存在返回null
final Entry<K,V> getEntry(Object key) {if (size == 0) {return null;}//计算键key的哈希码int hash = (key == null) ? 0 : hash(key);//首先根据哈希码定位到哈希表中的索引,若键key存在则一定存在于该索引对应的链表中for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) {Object k;//比较Entry的hash值和键key的哈希值是否相等、比较当前键值对的键是否和key相等if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;}return null;
}

put相关方法

//将键key、值value添加到哈希表中
//返回之前key关联的值,若之前key不存在则返回null
public V put(K key, V value) {//因为初始化时的哈希表是一个空的哈希表,当需要的时候就要扩容if (table == EMPTY_TABLE) {inflateTable(threshold);}if (key == null)//键key为null时走putForNullKey方法return putForNullKey(value);//取键key的哈希码并定位到哈希表中的索引int hash = hash(key);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))) {//此处说明键key已经存在,用新值value代替旧值oldValueV oldValue = e.value;e.value = value;//记录本次操作,这里的recordAccess其实是个空实现e.recordAccess(this);return oldValue;}}//每次对哈希表的结构性改变时都会递增modCount的值modCount++;//键key不存在,添加新的键值对到哈希表中,键值对的hash属性就是key的哈希码值addEntry(hash, key, value, i);//因为键值对不存在,所以返回nullreturn null;
}
//键为null情况下的put操作
private V putForNullKey(V value) {//始终将key为null的键值对添加到哈希表table的索引0处for (Entry<K,V> e = table[0]; e != null; e = e.next) {if (e.key == null) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;addEntry(0, null, value, 0);return null;
}
//添加键值对,该私有方法不会对哈希表进行扩容,并且该方法不会记录modCount
//该方法仅在通过另一个哈希表来创建哈希表的时候使用
private void putForCreate(K key, V value) {int hash = null == key ? 0 : hash(key);int i = indexFor(hash, table.length);//查找之前已经存在的键为key的键值对,并将用新的value代替旧的value//若没有找到,创建一个新的键值对到哈希表中for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) {e.value = value;return;}}//新建一个新的键值对createEntry(hash, key, value, i);
}
//该方法很简单,将哈希表m中的键值对添加到本哈希表中
private void putAllForCreate(Map<? extends K, ? extends V> m) {for (Map.Entry<? extends K, ? extends V> e : m.entrySet())putForCreate(e.getKey(), e.getValue());
}

重哈希: resize和transfer方法

//当哈希表键值对数量达到threshold的时候自动重哈希,重哈希后的哈希表容量变得更大
//newCapacity是重哈希后新的容量
void resize(int newCapacity) {Entry[] oldTable = table;int oldCapacity = oldTable.length;//哈希表的最大容量是MAXIMUN_CAPACITY,达到该值后不会再重哈希if (oldCapacity == MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return;}//根据新的容量为哈希表重新分配内存Entry[] newTable = new Entry[newCapacity];//将老的哈希表所有键值对迁移到新的哈希表transfer(newTable, initHashSeedAsNeeded(newCapacity));table = newTable;threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
//将老的哈希表中所有的键值对迁移到新的哈希表中
void transfer(Entry[] newTable, boolean rehash) {int newCapacity = newTable.length;//遍历老的哈希表for (Entry<K,V> e : table) {//哈希表的每一个元素都是一个链表,遍历该链表while(null != e) {Entry<K,V> next = e.next;//如果需要重新计算键的哈希值if (rehash) {e.hash = null == e.key ? 0 : hash(e.key);}//计算键在新的哈希表中的索引int i = indexFor(e.hash, newCapacity);//这三行代码完成了键值对的迁移e.next = newTable[i];newTable[i] = e;e = next;}}
}

上面代码片断功能是迁移老的哈希表所有的键值对到新的哈希表,核心思想是就是上面最底行的三行代码,我们分析下,假设老的哈希表如下图示:

老的哈希表的容量为4,假设扩容后的哈希表容量为8,并且e0重哈希后到新哈希表的索引3处:

可以看到,这三行代码执行完成后就将e0插入到新哈希表的索引3处,并且处于索引3处链表的头。

继续看源代码:

//该方法将哈希表m的所有键对加入到本哈希表中
public void putAll(Map<? extends K, ? extends V> m) {int numKeysToBeAdded = m.size();if (numKeysToBeAdded == 0)return;if (table == EMPTY_TABLE) {inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold));}//计算是否要扩容、重哈希if (numKeysToBeAdded > threshold) {int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);if (targetCapacity > MAXIMUM_CAPACITY)targetCapacity = MAXIMUM_CAPACITY;int newCapacity = table.length;while (newCapacity < targetCapacity)newCapacity <<= 1;if (newCapacity > table.length)resize(newCapacity);}//将哈希表m的所有键值对添加到本哈希表中for (Map.Entry<? extends K, ? extends V> e : m.entrySet())put(e.getKey(), e.getValue());
}

remove相关方法

//删除键key对应的键值对
//返回之前key关联的值,如果不存在,返回null
public V remove(Object key) {//其实是调用removeEntryForKey方法删除的Entry<K,V> e = removeEntryForKey(key);return (e == null ? null : e.value);
}
//删除键key对应的键值对
final Entry<K,V> removeEntryForKey(Object key) {if (size == 0) {return null;}int hash = (key == null) ? 0 : hash(key);int i = indexFor(hash, table.length);Entry<K,V> prev = table[i];Entry<K,V> e = prev;while (e != null) {Entry<K,V> next = e.next;Object k;if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) {//找到了键为key的键值对,执行删除操作//这里其实就是执行链表的删除操作,修改相关的指针即可modCount++;size--;//要删除的结点就是第一个结点if (prev == e)table[i] = next;elseprev.next = next;e.recordRemoval(this);return e;}prev = e;e = next;}return e;
}
//删除指定的键值对,这里的o必须是一个Map.Entry
final Entry<K,V> removeMapping(Object o) {if (size == 0 || !(o instanceof Map.Entry))return null;Map.Entry<K,V> entry = (Map.Entry<K,V>) o;Object key = entry.getKey();int hash = (key == null) ? 0 : hash(key);int i = indexFor(hash, table.length);Entry<K,V> prev = table[i];Entry<K,V> e = prev;while (e != null) {Entry<K,V> next = e.next;//键和值都相等时才会删除if (e.hash == hash && e.equals(entry)) {modCount++;size--;if (prev == e)table[i] = next;elseprev.next = next;e.recordRemoval(this);return e;}prev = e;e = next;}return e;
}
//清空哈希表
public void clear() {modCount++;//将table每个值设为nullArrays.fill(table, null);size = 0;
}
//返回哈希表中是否包含值value
public boolean containsValue(Object value) {if (value == null)//是否有空的值return containsNullValue();Entry[] tab = table;for (int i = 0; i < tab.length ; i++)for (Entry e = tab[i] ; e != null ; e = e.next)if (value.equals(e.value))return true;return false;
}
//返回哈希表中是否包含null值
private boolean containsNullValue() {Entry[] tab = table;for (int i = 0; i < tab.length ; i++)for (Entry e = tab[i] ; e != null ; e = e.next)if (e.value == null)return true;return false;
}

clone-浅复制

//克隆方法,实现了浅复制
public Object clone() {HashMap<K,V> result = null;try {//调用父类的clone方法result = (HashMap<K,V>)super.clone();} catch (CloneNotSupportedException e) {// assert false;}if (result.table != EMPTY_TABLE) {result.inflateTable(Math.min((int) Math.min(size * Math.min(1 / loadFactor, 4.0f),// we have limits...HashMap.MAXIMUM_CAPACITY),table.length));}result.entrySet = null;result.modCount = 0;result.size = 0;result.init();//这里将键值对添加到result中result.putAllForCreate(this);return result;
}

HashMap.Entry静态内部类

//键值对,实现了Map.Entry接口
//这里的Entry其实是一个链表结点,每个结点有键、值、next指针、哈希码
static class Entry<K,V> implements Map.Entry<K,V> {final K key;V value;Entry<K,V> next;int hash;Entry(int h, K k, V v, Entry<K,V> n) {value = v;next = n;key = k;hash = h;}public final K getKey() {return key;}public final V getValue() {return value;}public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}//equals方法比较了键是否相等、值是否相等,只有键和值都相等时两个Entry才相等public final boolean equals(Object o) {if (!(o instanceof Map.Entry))return false;Map.Entry e = (Map.Entry)o;Object k1 = getKey();Object k2 = e.getKey();if (k1 == k2 || (k1 != null && k1.equals(k2))) {Object v1 = getValue();Object v2 = e.getValue();if (v1 == v2 || (v1 != null && v1.equals(v2)))return true;}return false;}//键值对的哈希码,需要和hash属性区别开来,这里的hash存储的是键的哈希码public final int hashCode() {return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());}public final String toString() {return getKey() + "=" + getValue();}void recordAccess(HashMap<K,V> m) {}void recordRemoval(HashMap<K,V> m) {}
}

addEntry方法

//增加键值对、需要考虑重哈希
void addEntry(int hash, K key, V value, int bucketIndex) {if ((size >= threshold) && (null != table[bucketIndex])) {//新哈希表的容量扩充为原来的2倍resize(2 * table.length);hash = (null != key) ? hash(key) : 0;bucketIndex = indexFor(hash, table.length);}createEntry(hash, key, value, bucketIndex);
}
//创建键值对
void createEntry(int hash, K key, V value, int bucketIndex) {//每次将新的键值对添加到链表的表头Entry<K,V> e = table[bucketIndex];table[bucketIndex] = new Entry<>(hash, key, value, e);size++;
}

HashMap内部迭代器以及迭代器快速失败机制

//HashMap使用到的迭代器的抽象基类
private abstract class HashIterator<E> implements Iterator<E> {Entry<K,V> next;       //通过expectedModCount实现迭代器的快速失败机制int expectedModCount;   //迭代器当前所在的哈希表索引int index;              Entry<K,V> current;     HashIterator() {//expectedModCount初始化为HashMap的成员变量modCountexpectedModCount = modCount;if (size > 0) { //找到第一个有值的链表Entry[] t = table;while (index < t.length && (next = t[index++]) == null);}}public final boolean hasNext() {return next != null;}final Entry<K,V> nextEntry() {//说明在迭代器迭代的时候HashMap已经被结构性改变,抛出并发修改异常if (modCount != expectedModCount)throw new ConcurrentModificationException();Entry<K,V> e = next;if (e == null)throw new NoSuchElementException();//返回Entry前,需要准备好下一次迭代的Entryif ((next = e.next) == null) {Entry[] t = table;while (index < t.length && (next = t[index++]) == null);}current = e;return e;}//删除操作,调用HashMap.removeEntryForKey方法实现的public void remove() {//还没有开始遍历、或者没有任何节点、或者已经删除,current为空if (current == null)throw new IllegalStateException();if (modCount != expectedModCount)throw new ConcurrentModificationException();Object k = current.key;//删除后将current设置为空,防止重复删除current = null;HashMap.this.removeEntryForKey(k);//删除后更新expectedModCount的值,不然会抛出ConcurrentModificationExceptionexpectedModCount = modCount;}
}
//值集合的迭代器,静态内部类
private final class ValueIterator extends HashIterator<V> {public V next() {return nextEntry().value;}
}
//键集合的迭代器,静态内部类
private final class KeyIterator extends HashIterator<K> {public K next() {return nextEntry().getKey();}
}
//键值对的迭代器,静态内部类
private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {public Map.Entry<K,V> next() {return nextEntry();}
}
//返回一个键集合的迭代器
Iterator<K> newKeyIterator()   {return new KeyIterator();
}
//返回一个值集合的迭代器
Iterator<V> newValueIterator()   {return new ValueIterator();
}
//返回一个键值对集合的迭代器
Iterator<Map.Entry<K,V>> newEntryIterator()   {return new EntryIterator();
}

键、值、键值对集合

//返回键的Set集合
public Set<K> keySet() {Set<K> ks = keySet;return (ks != null ? ks : (keySet = new KeySet()));
}
//键的集合,继承自AbstractSet的内部类
private final class KeySet extends AbstractSet<K> {//返回一个键的迭代器public Iterator<K> iterator() {return newKeyIterator();}//键的数量public int size() {return size;}//是否存在键opublic boolean contains(Object o) {return containsKey(o);}//删除键o对应的键值对public boolean remove(Object o) {return HashMap.this.removeEntryForKey(o) != null;}//清空public void clear() {HashMap.this.clear();}
}
//返回值的集合
public Collection<V> values() {Collection<V> vs = values;return (vs != null ? vs : (values = new Values()));
}
//值的集合,继承自AbstractCollection的内部类
private final class Values extends AbstractCollection<V> {//返回一个值的迭代器public Iterator<V> iterator() {return newValueIterator();}public int size() {return size;}public boolean contains(Object o) {return containsValue(o);}public void clear() {HashMap.this.clear();}
}
//返回键值对的Set集合
public Set<Map.Entry<K,V>> entrySet() {return entrySet0();
}
private Set<Map.Entry<K,V>> entrySet0() {Set<Map.Entry<K,V>> es = entrySet;return es != null ? es : (entrySet = new EntrySet());
}
//键值对的集合,继承自AbstractSet的内部类
private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {//返回键值对集合的迭代器public Iterator<Map.Entry<K,V>> iterator() {return newEntryIterator();}public boolean contains(Object o) {if (!(o instanceof Map.Entry))return false;Map.Entry<K,V> e = (Map.Entry<K,V>) o;Entry<K,V> candidate = getEntry(e.getKey());return candidate != null && candidate.equals(e);}public boolean remove(Object o) {return removeMapping(o) != null;}public int size() {return size;}public void clear() {HashMap.this.clear();}
}

writeObject和readObject序列化相关方法

另外,HashMap也实现了writeObject和readObject方法,实现自定义的序列化:

//序列化方法
private void writeObject(java.io.ObjectOutputStream s)throws IOException
{//默认序列化s.defaultWriteObject();//序列化哈希表的大小if (table==EMPTY_TABLE) {s.writeInt(roundUpToPowerOf2(threshold));} else {s.writeInt(table.length);}//序列化哈希表键值对数量s.writeInt(size);//序列化键值对if (size > 0) {for(Map.Entry<K,V> e : entrySet0()) {s.writeObject(e.getKey());s.writeObject(e.getValue());}}
}//反序列化方法
private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException{// 默认反序列化s.defaultReadObject();if (loadFactor <= 0 || Float.isNaN(loadFactor)) {throw new InvalidObjectException("Illegal load factor: " +loadFactor);}table = (Entry<K,V>[]) EMPTY_TABLE;// 反序列化哈希表的大小s.readInt();// 反序列化哈希表键值对数量int mappings = s.readInt();if (mappings < 0)throw new InvalidObjectException("Illegal mappings count: " +mappings);int capacity = (int) Math.min(mappings * Math.min(1 / loadFactor, 4.0f),HashMap.MAXIMUM_CAPACITY);if (mappings > 0) {inflateTable(capacity);} else {threshold = capacity;}init(); //反序列化键值对for (int i = 0; i < mappings; i++) {K key = (K) s.readObject();V value = (V) s.readObject();putForCreate(key, value);}
}
//返回哈希表的容量,也就是table数组的大小
int   capacity()     { return table.length; }
//返回加载因子
float loadFactor()   { return loadFactor;   }

到此为止,HashMap源码分析完了

参考:

  1. jdk源码

HashMap源代码详解相关推荐

  1. 【Java基础】HashMap原理详解

    [Java基础]HashMap原理详解 HashMap的实现 1. 数组 2.线性链表 3.红黑树 3.1概述 3.2性质 4.HashMap扩容死锁 5. BATJ一线大厂技术栈 HashMap的实 ...

  2. emule中节点加入Kad网络过程(源代码详解)【对原文部分改进】

    from: http://blog.csdn.net/chenbuaa/article/details/2301656 emule中节点加入Kad网络过程(源代码详解) 程序启动: EmuleDlg. ...

  3. 线程池源代码详解,参数详解

    线程池源代码详解,参数详解 ThreadPoolExecutor 构造函数源代码 public ThreadPoolExecutor(int corePoolSize, int maximumPool ...

  4. HashMap原理详解(基于jdk1.8)

    HashMap原理详解(基于jdk1.8) HashMap原理详解,有兴趣的同学可以看下.有错误的地方也希望大佬们能指点下. HashMap的内部存储是一个数组(bucket),数组的元素Node实现 ...

  5. 大白话解析Apriori算法python实现(含源代码详解)

    大白话解析Apriori算法python实现(含源代码详解) 一.专业名词解释 二.算法思路 三.python代码实现 四.Aprioir的优点.缺点及改进方法 本文为博主原创文章,转载请注明出处,并 ...

  6. Py之seaborn:数据可视化seaborn库(三)的矩阵图可视化之jointplot/JointGrid/pairplot/PairGrid/FacetGrid密度图等的函数源代码详解之最强攻略

    Py之seaborn:数据可视化seaborn库(三)的矩阵图可视化之jointplot/JointGrid/pairplot/PairGrid/FacetGrid折线图/柱状图+散点图/矩形密度图的 ...

  7. HCTF 2018:WarmUp(源代码详解)

    前言 之前刷BUUCTF时遇到过这题,这次刷XCTF时也遇到了,那就写个详细点的WP吧 寻找利用点 打开题目,是一个滑稽图 没发现什么,查看下网页源代码,发现了source.php 访问source. ...

  8. HashMap 原理详解

    一.HashMap的原理详解 首先我们要知道什么是哈希表以及它的结构.在介绍哈希表之前我们需要了解并且掌握数组.链表以及红黑树的结构以及特点. 1.我们先来看一下HashMap的使用 public c ...

  9. Marlin固件之二:源代码详解与移植

    由于需要进行固件定制化,Marlin固件太过于强大和紧凑,我对这个固件进行了裁剪,只剩下主枝干,实现功能的定制和裁剪.以下的代码详解是基于我已经移植在stm32上面的一个程序进行的.

最新文章

  1. C++ - 模板函数须要类型转换时使用友元(friend)模板函数
  2. 将数组中的值按逆序重新存放
  3. 学习机器学习的项目_辅助项目在机器学习中的重要性
  4. Cassandra中的数据建模
  5. Taro+react开发(91):chidren和组合
  6. thinkphp 模板 php函数调用,thinkphp模版调用函数方法
  7. c++编程规范101条规则
  8. java建议:避免使用终结方法
  9. 简单理解php的socket编程
  10. POJ-1699 Best Sequence 状态压缩DP
  11. CocosCreator2.3.1切换场景出现Failed to load scene ‘xxx‘ because ‘xxx‘ is already being loaded问题的解决方案
  12. DXL之通过程序修改Domino的设计
  13. mysql 分割后循环,mysql实现字符串分割SPLIT函数的四种方法
  14. ubuntu 恢复被删除的文件
  15. 动态规划练习(1)--[编程题] 风口的猪-中国牛市
  16. 企业大数据平台解决方案
  17. 自制拖把机器人_懒出新境界:可以自己洗拖布的机器人
  18. JavaBeans分类
  19. Android OpenCV (五十九):离散傅里叶变换
  20. libtool-2.4.6-9-x86_64.pkg.tar.xz无法下载

热门文章

  1. DDR2/3-PCB设计规则
  2. 小i机器人发布新一代智能Bot开放平台:让AI唾手可得
  3. 如何屏蔽掉电脑上因下载软件捆绑的广告(烦人的广告让人十分尴尬)
  4. 称重软件地磅称重在实际应用中需要注意哪些?
  5. Vista BitLocker 驱动器加密原理
  6. python ssh 爆破_ssh爆破(python脚本)
  7. 【OpenCV】OpenCV的样本训练
  8. 另类的手机壁纸!壁纸颜色像变色龙一样随环境变化而变色!好喜欢!
  9. 职级有哪些?看这篇文章就够了
  10. 一个很NB的数学绘图工具-geogebra