JAVA8 之 HashMap
类继承层次
底层数据结构
hash表 +(普通链表 | 红黑树)
科普搬运
hashcode
散列函数,给每个对象一个编号,计算速度越快且冲突(不同对象得到相同编号视为冲突)越小越好
计算机怎么存数
补码
为什么要用补码
让逻辑电路计算实现更简单,尤其是正和负数相加不用借位操作
原码反码补码的关系
正数:原码=反码=补码
负数:反码=原码按位取反,补码=反码+1
左移<< 右移>>
直接在补码基础上左移或者右移,左移右边补0,右移时正数左边补0,负数左边补1
如:5 << 1 等于 10
5的补码:00000000000000000000000000000101
左移1位后:00000000000000000000000000001010
如:-5 << 1 等于 -10
-5的补码:11111111111111111111111111111011
左移1位:11111111111111111111111111110110
为什么等于-10,计算机显示的时候需要对补码11111111111111111111111111110110求原码即-1再取反
即11111111111111111111111111111010
无符号右移>>>(没有<<<这种操作)
在补码的基础上左移右边补0,右移左边补0
如:7 >>> 2 等于 1
7的补码: 00000000000000000000000000000111
无符号右移2位: 000000000000000000000000000001
如:-7 >>> 2 等于 1073741822
7的补码: 11111111111111111111111111111001
无符号右移2位: 00111111111111111111111111111110
上面已经是一个正数原码就等于补码最后结果即是00111111111111111111111111111110,十进制则为2^30-2=1073741822
HashMap中的索引计算
/**
* Computes key.hashCode() and spreads (XORs) higher bits of hash
* to lower. Because the table uses power-of-two masking, sets of
* hashes that vary only in bits above the current mask will
* always collide. (Among known examples are sets of Float keys
* holding consecutive whole numbers in small tables.) So we
* apply a transform that spreads the impact of higher bits
* downward. There is a tradeoff between speed, utility, and
* quality of bit-spreading. Because many common sets of hashes
* are already reasonably distributed (so don't benefit from
* spreading), and because we use trees to handle large sets of
* collisions in bins, we just XOR some shifted bits in the
* cheapest possible way to reduce systematic lossage, as well as
* to incorporate impact of the highest bits that would otherwise
* never be used in index calculations because of table bounds.
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
(n - 1) & hash n是hash table的长度
因为n为2^n次方,所以n-1则得到类似这样的二进制编码 111111
假设n=32则,n-1的二进制为:0000 0000 0000 0000 0000 0000 0001 1111
所以于hash值取&的时候就相当于只有最后5位存在非0,取值也就限定在0-31之间了。hash函数越均匀则碰撞概率越低。
默认初始容量
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
什么时机触发扩容
/**
* 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;
当元素大于容量乘以装载因子时触发
扩容
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
扩容容量为原来的两倍
什么时机普通链表膨胀为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
/**
* Replaces all linked nodes in bin at index for given hash unless
* table is too small, in which case resizes instead.
*/
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
....省略.....
}
当链表元素大于等于TREEIFY_THRESHOLD(8)时且hash表容量大于MIN_TREEIFY_CAPACITY(64)
什么时机红黑树退化为普通链表
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
当节点数少于等于UNTREEIFY_THRESHOLD(6)时退化为普通链表
主要操作时间复杂度
get(key) 为 O(1)或O(logn)
contain(key) 为 O(1)或O(logn)
contain(value) 为 O(n)或O(nlogn)
remove(key) 为 O(1)或O(logn)
remove(key, value) 为 O(1)或O(logn)
安全性
fail-fast
线程不安全