From 0824018756624f99436611e24aa35c46ff4d49b9 Mon Sep 17 00:00:00 2001 From: ranwenjie Date: Wed, 14 Jul 2021 00:20:45 +0800 Subject: [PATCH] udpate hashmap --- 3 Hash Table/HashMap in Java.md | 46 ++++++++++++++++++++++++++++++--- 4 Tree/3-平衡树AVL/AVLTree.c | 29 --------------------- 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/3 Hash Table/HashMap in Java.md b/3 Hash Table/HashMap in Java.md index 1285ac4..5db7725 100644 --- a/3 Hash Table/HashMap in Java.md +++ b/3 Hash Table/HashMap in Java.md @@ -10,6 +10,10 @@ java 中 hashmap的实现原理。 * hash冲突如何解决(链表和红黑树)? 为什么hashmap中的链表需要转成红黑树? +好的哈希函数会尽可能地保证 计算简单和散列地址分布均匀,但是,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。 + + + ``` public class HashMap extends AbstractMap implements Map, Cloneable, Serializable { @@ -96,6 +100,9 @@ final Node getNode(int hash, Object key) { ### put(key, value) 方法 + +hash存储的过程是: key -> hashcode -> hash -> indexFor() + ``` public V put(K key, V value) { return putVal(hash(key), key, value, false, true); @@ -103,6 +110,7 @@ public V put(K key, V value) { final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { + Node[] tab; Node p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; @@ -147,6 +155,17 @@ public V put(K key, V value) { ``` +``` +//返回数组下标 +static int indexFor(int h, int length) { + return h & (length-1); +} +``` + +这里用的位运行,而不是取模操作; 位运算性能更高。 + + + ## Hash冲突 HashMap是怎么处理hash碰撞的? @@ -161,12 +180,12 @@ HashMap是怎么处理hash碰撞的? static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16, 为啥用位运算呢?直接写16不好么? ``` -为什么需要扩容? +### 为什么需要扩容? 主要为缓解哈希冲突造成的外挂链表太长,造成查询性能低下。 -HashMap的扩容方式? 负载因子是多少? 扩容时机?什么时候会触发扩容? +### HashMap的扩容方式? 负载因子是多少? 扩容时机?什么时候会触发扩容? HashMap 中 `final float loadFactor` , loadFactor 默认 0.75 , 也就是达到容量的 75%时就会开始扩容。 @@ -255,7 +274,8 @@ final Node[] resize() { -扩容后元素怎么重排到新的容器中,直接复制拷贝可以吗? +### 扩容后元素怎么重排到新的容器中,直接复制拷贝可以吗? + 扩容会 rehash,复制数据等耗时操作。 @@ -271,6 +291,14 @@ final Node[] resize() { ## 问题 + +### JDK7 和 8 HashMap 有什么区别? + +* JDK8 实现引入红黑树,优化链表过长的查询效率 +* 1.7 采用头插法,1.8采用尾插法 + + + ### 链表上使用的头插还是尾插方式? @@ -319,6 +347,18 @@ h & (length-1)` 就是取 hashcode 的低 4位 length 保持为 2 的幂, 那么length-1就会变成一个mask, 它会将hashcode低位取出来,hashcode的低位实际就是余数,和取余操作相比,与操作会将性能提升很多。 +另外,hash扩容时 rehash 操作,只有 hash二进制 高位是 1 的hash key 需要 移动到新的 slot (pos + oldCap), 高位是 0 的 key 不需要移动 + +![hashmap rehash](https://pic3.zhimg.com/80/v2-ed0ca17db342562dfc18434d12227be2_720w.jpg) + + + + + + + + + diff --git a/4 Tree/3-平衡树AVL/AVLTree.c b/4 Tree/3-平衡树AVL/AVLTree.c index 2dc947f..dd1cc3c 100644 --- a/4 Tree/3-平衡树AVL/AVLTree.c +++ b/4 Tree/3-平衡树AVL/AVLTree.c @@ -1,35 +1,6 @@ /* -自平衡二叉查找树(AVL tree): 首先也是二次查找树,其实 任何2个子树的高度差不大于1 -在删除,插入的过程中不断调整子树的高度,保证平均和最坏情况下都是O(logn) -Adelson-Velskii 和 Landis 1962年 创造。 - -1) 平衡因子 -1 0 1 节点是正常的。平衡因子 = 左子树高度-右字数高度 -2) 除此之外的节点是不平衡的,需要重新平衡这个树。也就是AVL旋转 - -插入节点: - -a: 左旋转(RR型:节点x的右孩子的右孩子上插入新元素)平衡因子由-1 -》-2 时,需要绕节点x左旋转 -b:右旋转(LL型:节点X的左孩子的左孩子上插入新元素) 平衡因子有1-》2,右旋转 -c: 先左后右旋转:(LR型:树中节点X的左孩子的右孩子上插入新元素) 平衡因子从1变成2后,就需要 先绕X的左子节点Y左旋转,接着再绕X右旋转 -d: 先右后左旋转:(RL型:节点X的右孩子的左孩子上插入新元素) - - - 6 6 6 6 - / \ / \ - 5 7 3 9 - / \ \ / - 3 8 5 7 - (LL型) (RR) (LR) (RL) - - -删除节点: - - - - -可以看到,为了保证高度平衡,插入和删除操作代价增加 记于2014-2-28 by @nonstriater */