Java并发编程系列-(5) Java并发容器 (2)

首先通过 hash()方法求得 key 的哈希值,然后根据 hash 值得到 index 索引。然后迭代链表,返回匹配的 key 的对应的 value;找不到则返回 null。

public synchronized V get(Object key) { Entry tab[] = table; int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return e.value; } } return null; }

5. rehash扩容

数组长度增加一倍(如果超过上限,则设置成上限值)。

更新哈希表的扩容门限值。

遍历旧表中的节点,计算在新表中的index,插入到对应位置链表的头节点。

protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1; if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; } Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; for (int i = oldCapacity ; i-- > 0 ;) { for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = (Entry<K,V>)newMap[index]; newMap[index] = e; } } }

6. Remove方法

remove方法主要逻辑如下:

先获取synchronized锁。

计算key的哈希值和index。

遍历对应位置的链表,寻找待删除节点,如果存在,用e表示待删除节点,pre表示前驱节点。如果不存在,返回null。

更新前驱节点的next,指向e的next。返回待删除节点的value值。

Hash值的不同实现:JDK7 Vs JDK8

以上给出的代码均为jdk7中的实现,注意到在jdk7和8里面,关于元素hash值的计算方法是不一样的。

在JDK7中,hashtable专门实现了hash函数,在以上的例子中都有看到,具体的实现如下:

//利用异或,移位等运算,对key的hashcode进一步进行计算以及二进制位的调整等来保证最终获取的存储位置尽量分布均匀 final int hash(Object k) { int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }

以上hash函数计算出的值,通过indexFor进一步处理来获取实际的存储位置

//返回数组下标 static int indexFor(int h, int length) { return h & (length-1); }

在jdk8里面,直接调用key.hashCode()来获取key的hash值,接着在保证hash值为正数的前提下,得到相应的下标,

int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length;

注意到都使用到了hashCode,这个方法是在Object方法中定义的,

@HotSpotIntrinsicCandidate public native int hashCode();

可以看到是Object里没有给出hashCode的实现,只是声明为一个native方法,说明Java会去调用本地C/C++对hashcode的具体实现。

在JDK8及以后,可以通过如下指令来获取到所有的hash算法,

java -XX:+PrintFlagsFinal | grep hashCode

具体大概有如下几种,第5个算法是默认使用的,用到了异或操作和一些偏移算法来生成hash值。

0 == Lehmer random number generator,
1 == "somehow" based on memory address
2 == always 1
3 == increment counter
4 == memory based again ("somehow")
5 == Marsaglia XOR-Shift algorithm, that has nothing to do with memory.

HashTable相对于HashMap的最大特点就是线程安全,所有的操作都是被synchronized锁保护的

参考:

https://www.imooc.com/article/23015

https://wiki.jikexueyuan.com/project/java-collection/hashtable.html

https://stackoverflow.com/questions/49172698/default-hashcode-implementation-for-java-objects

HashMap

HashMap是java中使用最为频繁的map类型,其读写效率较高,但是因为其是非同步的,即读写等操作都是没有锁保护的,所以在多线程场景下是不安全的,容易出现数据不一致的问题。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwzxpw.html