HASHMAP

Posted 程序员每天不一样

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HASHMAP相关的知识,希望对你有一定的参考价值。

HASHMAP

Hashmap查找操作是O(1),是以空间来换时间的。

c++ deprecated hashmap,没有深入研究原因,所以拿jdk1.8的HashMap来探讨。

先贴代码:

static final int hash(Object key) {

   int h;

   return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);////Q1: h为啥要右移16位?

}

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;

if ((p = tab[i = (n - 1) & hash]) == null)

tab[i] = newNode(hash, key, value, null);

else {

Node<K,V> e; K k;

if (p.hash == hash &&

((k = p.key) == key || (key != null && key.equals(k))))

e = p;

else if (p instanceof TreeNode)

e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

else {

for (int binCount = 0; ; ++binCount) {

if ((e = p.next) == null) {

p.next = newNode(hash, key, value, null);

if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st

treeifyBin(tab, hash);

break;

}

if (e.hash == hash &&

((k = e.key) == key || (key != null && key.equals(k))))

break;

p = e;

}

}

if (e != null) { // existing mapping for key

V oldValue = e.value;

if (!onlyIfAbsent || oldValue == null)

e.value = value;

afterNodeAccess(e);

return oldValue;

}

}

++modCount;

if (++size > threshold)

resize();

afterNodeInsertion(evict);

return null;

}

哈希冲突主要有三种方式:

2. 再哈希法

4.建立公共溢出区

if ((tab = table) == null || (n = tab.length) == 0)   hashmap初始容量为0

n = (tab = resize()).length;  n为hashmap table的容量,第一次调用put分配容量为16,tab = resize(),即hashmap自动根据大小进行扩容,newCap = oldCap << 1,即每次扩容都要乘以2,啥时候扩容呢?

static final float DEFAULT_LOAD_FACTOR = 0.75f;

newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

可以看出是容量乘以0.75,当当前数据量为容量的0.75时自动进行扩容。

我们可以看到在下面的resize()函数中,

Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];

table = newTab;

if (oldTab != null) {

for (int j = 0; j < oldCap; ++j) {

Node<K,V> e;

if ((e = oldTab[j]) != null) {

oldTab[j] = null;

if (e.next == null)//数组中存放的只有一个值

else if (e instanceof TreeNode)//存放的是红黑树

((TreeNode<K,V>)e).split(this, newTab, j, oldCap);

else { // preserve order//保持顺序

Node<K,V> loHead = null, loTail = null;

Node<K,V> hiHead = null, hiTail = null;

Node<K,V> next;

do {

next = e.next;

if (loTail == null)

loHead = e;

else

loTail.next = e;

loTail = e;

}

if (hiTail == null)

hiHead = e;

else

hiTail.next = e;

hiTail = e;

}

} while ((e = next) != null);

if (loTail != null) {

loTail.next = null;

}

if (hiTail != null) {

hiTail.next = null;

}

}

}

}

当链表长度<=TREEIFY_THRESHOLD-1时,会自动将链表维护为一颗红黑树。红黑树的好处就是,遍历链表的时间复杂度是O(N),而红黑树的时间复杂度是O(log(N))。

static final int TREEIFY_THRESHOLD = 8;

总体来说,hashmap的源码写的非常好,效率很高。

下面回到static final int hash(Object key) 函数,

return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//// h为啥要右移16位?

右移后如上图,这样就极大降低了碰撞。至于为什么要移动16位,据说,因为hash值是32位的int,取中位数16.。。。。。。。。

以上是关于HASHMAP的主要内容,如果未能解决你的问题,请参考以下文章

HashMap学习

MAP

HashMap

HashMap问答

Map的实现

HashMapConcurrentHashMapHashTable