java.lang.Object
↳ java.util.AbstractMap
↳ java.util.HashMap
public class HashMap extends AbstractMap implements Map, Cloneable, Serializable { }
HashMap是基于哈希表实现的,每个元素是一个key-value对,实现了Serializable、Cloneable接口,容许使用null值和null键。不保证映射的顺序,内部经过单链表解决冲突问题,容量超过(容量*加载因子)时,会自动增加。(除了不一样步和容许使用null以外,HashMap类与Hashtable大体相同)
HashMap不是线程安全的,若是想获取线程安全的HashMapjava
Map map = Collections.synchronizedMap(new HashMap());
HashMap由数组+链表组成的,主干是一个Entry数组,每个entry包含一个(key-value)键值对,链表则是主要为了解决哈希冲突而存在的,HashMap经过key的hashCode来计算hash值,当hashCode相同时,经过“拉链法”解决冲突,以下图所示。web
//默认初始化化容量,即16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //最大容量,即2的30次方 static final int MAXIMUM_CAPACITY = 1 << 30; //默认加载因子,当容器使用率达到75%的时候就扩容 static final float DEFAULT_LOAD_FACTOR = 0.75f; //HashMap内部的存储结构是一个数组,此处数组为空,即没有初始化以前的状态 static final Entry<?,?>[] EMPTY_TABLE = {}; //空的存储实体 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; //实际存储的key-value键值对的个数 transient int size; //扩容的临界点,若是当前容量达到该值,则须要扩容了. //若是当前数组容量为0时(空数组),则该值做为初始化内部数组的初始容量 int threshold; //由构造函数传入的指定负载因子 final float loadFactor; //修改次数,用于快速失败机制 transient int modCount; //默认的threshold值 static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
构造方法主要完成容量和加载因子的设置算法
/** * 经过初始容量和状态因子构造HashMap * @param initialCapacity 容量 * @param loadFactor 加载因子 */ 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; threshold = initialCapacity; //init方法在HashMap中没有实际实现,不过在其子类如 linkedHashMap中就会有对应实现 init(); }
/** * 经过扩容因子构造HashMap,容量去默认值,即16 */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }
/** * 加载因子取0.75,容量取16,构造HashMap */ public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); }
/** * 经过其余Map来初始化HashMap,容量经过其余Map的size来计算,加载因子取0.75 */ 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底层的数组结构 inflateTable(threshold); //添加m中的元素 putAllForCreate(m); }
/** * 存入一个键值对,若是key重复,则更新value * @param key 键值名 * @param value 键值 * @return 若是存的是新key则返回null,若是覆盖了旧键值对,则返回旧value */ public V put(K key, V value) { //若是数组为空,则新建数组 if (table == EMPTY_TABLE) { //初始化HashMap底层的数组结构 inflateTable(threshold); } //若是key为null,则把value放在table[0]中 if (key == null) return putForNullKey(value); //生成key所对应的hash值 int hash = hash(key); //根据hash值和数组的长度找到:该key所属entry在table中的位置i int i = indexFor(hash, table.length); /** * 数组中每一项存的都是一个链表, * 先找到i位置,而后循环该位置上的每个entry, * 若是发现存在key与传入key相等,则替换其value。而后结束方法。 * 若是没有找到相同的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; } } //map操做次数加一 modCount++; //查看是否须要扩容,并将该键值对存入指定下标的链表头中 addEntry(hash, key, value, i); //若是是新存入的键值对,则返回null return null; }
/** * 将该键值对存入指定下标的链表头中 * @param hash hash值 * @param key 键值名 * @param value 键值 * @param bucketIndex 索引 */ void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { //当size超过临界阈值threshold,而且即将发生哈希冲突时进行扩容 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); }
/** * 将键值对与他的hash值做为一个entry,插入table的指定下标中的链表头中 * @param hash hash值 * @param key 键值名 * @param value 键值 * @param 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++; }
static int hash(int h) { //此功能确保hash码不一样,有限数量的碰撞 h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
/** * 对数组扩容,即建立一个新数组,并将旧数组里的东西从新存入新数组 * @param newCapacity 新数组容量 */ void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length //若是当前数组容量已经达到最大值了,则将扩容的临界值设置为Integer.MAX_VALUE(Integer.MAX_VALUE是容量的临界点) 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); }
/** * 将现有数组中的内容从新经过hash计算存入新数组 * @param newTable 新数组 * @param rehash */ void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; //遍历现有数组中的每个单链表的头entry for (Entry<K,V> e : table) { //查找链表里的每个entry while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } //根据新的数组长度,从新计算此entry所在下标i int i = indexFor(e.hash, newCapacity); //将entry放入下标i处链表的头部(将新数组此处的原有链表存入entry的next指针) e.next = newTable[i]; //将链表存回下标i newTable[i] = e; //查看下一个entry e = next; } } }
/** * 返回此hashmap中存储的键值对个数 * @return 键值对个数 */ public int size() { return size; }
/** * 根据key找到对应value * @param key 键值名 * @return 键值value */ public V get(Object key) { //若是key为null,则从table[0]中取value if (key == null) return getForNullKey(); //若是key不为null,则先根据key,找到其entry Entry<K,V> entry = getEntry(key); //返回entry节点里的value值 return null == entry ? null : entry.getValue(); }
/** * 返回一个set集合,里面装的都是hashmap的value。 * 由于map中的key不能重复,set集合中的值也不能重复,因此能够装入set。 * 在hashmap的父类AbstractMap中,定义了Set<K> keySet = null; * 若是keySet为null,则返回内部类KeySet。 * @return 含有全部key的set集合 */ public Set<K> keySet() { Set<K> ks = keySet; return (ks != null ? ks : (keySet = new KeySet())); }
/** * 返回一个Collection集合,里面装的都是hashmap的value。 * 由于map中的value能够重复,因此装入Collection。 * 在hashmap的父类AbstractMap中,定义了Collection<V> values = null; * 若是values为null,则返回内部类Values。 */ public Collection<V> values() { Collection<V> vs = values; return (vs != null ? vs : (values = new Values())); }
/** * 删除hashmap中的全部元素 */ public void clear() { modCount++; //将table中的每个元素都设置成null Arrays.fill(table, null); size = 0; }
/** * 根据key删除entry节点 * @param key 被删除的entry的key值 * @return 被删除的节点的value,删除失败则返回null */ public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); }
/** * 根据key删除entry节点 * @param key 被删除的entry的key值 * @return 被删除的节点,删除失败则返回null */ final Entry<K,V> removeEntryForKey(Object key) { if (size == 0) { return null; } //计算key的hash值 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)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
“快速失败”也就是fail-fast,它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操做时,有可能会产生fail-fast机制。 注意 :记住是有可能,而不是必定。 例如:假设存在两个线程(线程一、线程2),线程1经过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),这个时候程序就会抛出ConcurrentModificationException 异常,从而产生fail-fast机制。数组
/** * @param key * @return */ static final int hash(Object key) { int h; // h = key.hashCode() 为第一步 取hashCode值 // h ^ (h >>> 16) 为第二步 高位参与运算 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }