udpate hashmap
This commit is contained in:
@@ -10,6 +10,10 @@ java 中 hashmap的实现原理。
|
||||
* hash冲突如何解决(链表和红黑树)? 为什么hashmap中的链表需要转成红黑树?
|
||||
|
||||
|
||||
好的哈希函数会尽可能地保证 计算简单和散列地址分布均匀,但是,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。
|
||||
|
||||
|
||||
|
||||
```
|
||||
public class HashMap<K,V> extends AbstractMap<K,V>
|
||||
implements Map<K,V>, Cloneable, Serializable {
|
||||
@@ -96,6 +100,9 @@ final Node<K,V> 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<K,V>[] tab; Node<K,V> 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<K,V>[] resize() {
|
||||
|
||||
|
||||
|
||||
扩容后元素怎么重排到新的容器中,直接复制拷贝可以吗?
|
||||
### 扩容后元素怎么重排到新的容器中,直接复制拷贝可以吗?
|
||||
|
||||
扩容会 rehash,复制数据等耗时操作。
|
||||
|
||||
|
||||
@@ -271,6 +291,14 @@ final Node<K,V>[] 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 不需要移动
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user