思维导图
思维导图是个好东西,哈哈
继承关系
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
可以看出具有map的通性,也能够被克隆
底层结构
hashMap的hash算法
JDK1.7中
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算
JDk1.8中
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);key.hashCode()为哈希算法,返回初始哈希值
}
扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算
key.hashCode()是Key自带的hashCode()方法,返回一个int类型的散列值。
32位带符号的int表值范围从-2147483648到2147483648,大概40亿的样子。
这么大的值里,一般是很难发生碰撞的,但是内存放不下这么长的数组,同时HashMap的初始容量只有16,
所以这样的散列值使用需要对数组的长度取模运算,得到余数才是索引值。
int index = (n - 1) & hash;
为什么HashMap的数组长度是2的整数幂?
以初始长度为16为举例,16-1 = 15,15的二进制数位是0000 0000 0000 0000 0000 1111,
再比如31的二进制是0000 0000 0000 0000 0001 1111,
可以看出一个基数二进制最后一位必然位1,当与一个hash值进行与运算时,最后一位可能是0也可能是1。
但偶数与一个hash值进行与运算最后一位必然为0,造成有些位置永远映射不上值。
相与下来高位全部为0,只保留低位,
但是这时,又出现了一个问题,即使散列函数很松散,但只取最后几位碰撞也会很严重。
这时候hash算法的价值就体现出来了,
扰动函数
hashCode右移16位,正好是32bit的一半,
与自己本身做异或操作(相同为0,不同为1),
也是为了混合哈希值的高位和低位,增加低位的随机性,
同时混合后的值也变相保持了高位的特征。
JDK1.7的底层结构
数组+链表
JDK1.8的底层结构
数组+链表+红黑树
先是数组加链表结构,当同一个hash值存放超过8个元素时,即当链表长度超过阈值(8)时,转换为红黑树结构,加快查询速度。
初始化
一些基本属性table、entrySet、size、modCount、threshold、loadFactor
/**
* 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.)
* 表格,在第一次使用时初始化,调整大小为必要的。当分配时,长度总是2的幂。(在某些操作中,我们也允许长度为零目前不需要的引导机制。)
* 其定义为 Node<K,V>[],即用来存储 key-value 的节点对象。在 HashMap 中它有个专业的叫法 buckets ,中文叫作桶。
*/
transient Node<K,V>[] table;
/**
* Holds cached entrySet(). Note that AbstractMap fields are used
* for keySet() and values().
* 保存缓存entrySet ()。注意,这里使用了AbstractMap字段表示keySet()和values()。
*
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* The number of key-value mappings contained in this map.
* 此映射中包含的键-值映射的数目
* 容器中实际存放的node大小
*/
transient int size;
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
* 这个HashMap在结构上被修改的次数,结构修改是指改变映射的数量HashMap或修改其内部结构(例如,重复)。
* 记录容器被修改的次数
*/
transient int modCount;
/**
* The next size value at which to resize (capacity * load factor).
* 下一个需要扩容的阈值
* @serial
*/
// (The javadoc description is true upon serialization.
// Additionally, if the table array has not been allocated, this
// field holds the initial array capacity, or zero signifying
// DEFAULT_INITIAL_CAPACITY.)
int threshold;
/**
* The load factor for the hash table.
* 负载因子
* @serial
*/
final float loadFactor;
内部桶的源码
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
* 基本哈希bin节点,用于大多数条目。(见下文TreeNode子类,在LinkedHashMap中为它的Entry子类。)
*/
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);//重写hashcode方法
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//重写equals方法
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
无参构造方法
这是用得最多的构造方法
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
* 使用默认的初始容量构造一个空的HashMap,默认大小16,默认负载因子为0.75
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
指定大小的构造方法
默认负载因子是0.75
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative. 当初始化大小为负数时,抛出异常
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
指定大小和负载因子的构造方法
这种情况下需要算出阈值。
如果操出了map的最大值,即2的30次方,就将值设置为最大值,
如果负载因子为负或者不是数字类型,都抛出异常。
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
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);
}
调用tableSizeFor方法获取大于该初始值的2的n次方值。
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>