01
前言
我们在声名HashMap的时候,一般都会这样写。
public class MapTest {
public static void main(String[] args) {
HashMapmap=new HashMap<>();
}
}
我们不会向里面加入初始容量,它自己会给我们一个初始化的容量,一般是16。
大家有没有看过hashmap的底层,java7版本是数组加链表
1.8之后引入红黑树。性能提升百分之十到到百分之15左右。
02
加载因子为何0.75
HashMap有两个参数影响其性能:初始容量和加载因子。容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动扩容之前可以达到多满的一种度量。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行扩容、rehash操作(即重建内部数据结构),扩容后的哈希表将具有两倍的原容量。
通常,加载因子需要在时间和空间成本上寻求一种折衷。
加载因子过高,例如为1,虽然减少了空间开销,提高了空间利用率,但同时也增加了查询时间成本;
加载因子过低,例如0.5,虽然可以减少查询时间成本,但是空间利用率很低,同时提高了rehash操作的次数。
在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少rehash操作次数,所以,一般在使用HashMap时建议根据预估值设置初始容量,减少扩容操作。
选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择。
03
为何初始化值2的指数幂
1.奇数不行的解释很能被接受,在计算hash的时候,确定落在数组的位置的时候,计算方法是(n - 1) & hash ,奇数n-1为偶数,偶数2进制的结尾都是0,经过&运算末尾都是0,会
增加hash冲突
2.为啥要是2的幂,不能是2的倍数么,比如6,10?
2.1 hashmap 结构是数组,每个数组里面的结构是node(链表或红黑树),正常情况下,如果你想放数据到不同的位置,肯定会想到取余数确定放在那个数据里, 计算公式:
hash % n,这个是十进制计算。在计算机中, (n - 1) & hash,当n为2次幂时,会满足一个公式:(n - 1) & hash = hash % n,计算更加高效。
2.2 只有是2的幂数的数字经过n-1之后,二进制肯定是 ...11111111 这样的格式,这种格式计算的位置的时候,完全是由产生的hash值类决定,而不受n-1 影响。你可能会想,
受影响不是更好么,又计算了一下 ,hash冲突可能更低了,这里要考虑到扩容了,2的幂次方*2,在二进制中比如4和8,代表2的2次方和3次方,他们的2进制结构相 似,比如
4和8 00000100 0000 1000 只是高位向前移了一位,这样扩容的时候,只需要判断高位hash,移动到之前位置的倍数就可以了,免去了重新计算位置的运算。
04
红黑树定义
通过源码解析,我们已经很清楚HashMap是在“当链表已经有8个节点了,此时再新链上第9个节点,在成功添加了这个新节点之后,立马做链表转红黑树
定义:红黑树是一种自平衡的二叉查找树,是一种高效的查找树。红黑树有良好的效率,它可以在时间复杂度为O(logN)时间内完成查找 增加 删除等操作。
性质:
1.根是黑色
2.节点是红色或者黑色
3.所有的叶子都是黑色的(叶子是NIL节点)
4. 每个红色节点必须有两个黑色的子节点
5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
05
二叉树查找
public class BST, Value> {
private Node root;
class Node {
Key key;
Value value;
Node left, right;//左右子结点
int N;//以该结点为父结点的所有子结点的数量
public Node(Key key, Value value, int N) {
this.key = key;
this.value = value;
this.N = N;
}
}
public void put(Key key, Value value) {
root=put(root,key,value);
}
/**
* 先递归向下寻找key所在的位置,如果有则更新对应的value,
* 如果没有,当在树的底部添加了一个新结点之后,还需要
* 从下往上更新所有父结点的N的值。
*/
private Node put(Node node, Key key, Value value) {
if (node==null){
return new Node(key,value,1);
}
int cmp=key.compareTo(node.key);
if (cmp<0) {
node.left=put(node.left,key,value);
}else if (cmp>0){
node.right= put(node.right,key,value);
}else {
node.value=value;
}
node.N=size(node.left)+size(node.right)+1;
return node;
}
public Value get(Key key) {
return get(root, key);
}
/**
* 使用递归的方式,从根节出发,不断向下查找
*/
private Value get(Node node, Key key) {
if (node == null) {
return null;
}
int cmp = key.compareTo(node.key);
if (cmp < 0) {
return get(node.left, key);
} else if (cmp > 0) {
return get(node.right, key);
} else {
return node.value;
}
}
public int size() {
return size(root);
}
public int size(Node node) {
if (node == null) return 0;
return node.N;
}
}