简介
-
HashMap实现了Map接口,该接口主要有四个常用的实现类,分别是HashMap,LinkedHashMap,Hashtable,TreeMap,IdentityHashMap。
-
HashMap的数据结构:HashMap的数据结构为 数组+(链表或红黑树),上图:
-
为什么默认的负载因子是0.75?
- 阈值(threshold) = 负载因子(loadFactor) x 容量(capacity) 根据HashMap的扩容机制,他会保证容量(capacity)的值永远都是2的幂 为了保证**负载因子x容量的结果是一个整数,**这个值是0.75(4/3)比较合理,因为这个数和任何2的次幂乘积结果都是整数。
- 负载因子越大,意味着会出现大量的Hash的冲突,底层的红黑树变得异常复杂,对于查询效率极其不利。负载因子越小,费的空间也就越大,底层的链表长度或者是红黑树的高度就会降低,查询效率就会增加。这是一个无法避免的利弊关系,当负载因子是0.75时,空间利用率比较高,而且避免了相当多的Hash冲突,使得底层的链表或者是红黑树的高度比较低,提升了空间效率。
- https://www.cnblogs.com/kelelipeng/p/13261409.html#:~:text=%E5%A4%A7%E8%87%B4%E6%84%8F%E6%80%9D%E5%B0%B1%E6%98%AF%E8%AF%B4%E8%B4%9F,%E6%8F%90%E5%8D%87%E4%BA%86%E7%A9%BA%E9%97%B4%E6%95%88%E7%8E%87%E3%80%82
构造方法
//1、无参构造方法,默认大小是16,使用默认的加载因子0.75
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // 默认的加载因子0.75
}
//2、指定大小和加载因子的构造方法
public HashMap(int initialCapacity, float loadFactor) {
//如果大小 > 2^31-1,那么设置容量为最大值
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
//如果大小 > 2^31-1,那么设置容量为最大值
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//如果加载因子小于0或者不是数字的话,那么抛出异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
this.loadFactor = loadFactor;
//threshold:要调整大小的下一个大小值(容量*加载因子)
this.threshold = tableSizeFor(initialCapacity);
}
//3、指定大小,使用默认的加载因子0.75
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//Float的静态方法,传入的v不是一个数,那么就返回true
public static boolean isNaN(float v) {
return (v != v);
}
//返回给定目标容量的 最小2的n次方数。
//2的n次方数:二进制高位为1,后面为n个0,例如2^1 = 0010、2^2 = 0100、2^3 = 1000。
//求得2的最小n次方数:将最高位1到最低位全置为1,最后将结果再+1,即可。
static final int tableSizeFor(int cap) {
int n = cap - 1;
//无符号右移运算符,将n和n>>>1进行或运算,最后再将值赋给n。
n |= n >>> 1;//右移1位保证前两位为1.
n |= n >>> 2;//再右移两位,保证前4位为1.
n |= n >>> 4;//右移4位,保证前八位为1.
n |= n >>> 8;
n |= n >>> 16;//右移16位,保证前32位为1.
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
//如果n=8,那么最终返回的结果是16.
}
数据结构
数组
//该表,在首次使用时初始化,并根据需要调整大小。分配时,长度始终是 2 的幂。(我们还允许某些操作中的长度为零,以允许当前不需要的自举机制。
transient Node<K,V>[] table;
结点的数据结构
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//key的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; }
//结点Node的hash值
public final int hashCode() {
//两个哈希值做 异或运算(不带进位的二进制加法):0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0。
//同为0,异为1.
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
//返回旧值
public final V setValue(V newValue) {
V oldValue = value;
value = newVal