HashMap的实现思路:
HashMap是采用数组( table[] ) + 链表存储的,table数组的每个元素都是一个链表。 链表的核心数据结构是Node:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
...
}
即存储一个key-value键值对,外加hash值和其指向的节点的地址引用。
当put(key,value)时,过程如下:
- 根据key值计算一个hash值,然后根据hash值和table的length计算该key所在的桶的索引(即table[i])
- 在table[i] 这个链表中依次查找key,如果能找到,就用新value替换掉之前的老value; 如果找不到,就在此链表中新增一个节点,用来存储这个key-value键值对;
- 新增节点时,new 一个node1并令其存储table[i],然后再new一个node2,令node2的next指向node1, 再令table[i] = node2,这样就把新增的node2插入到了之前的table[i]链表的第一个位置;
- 插入完成后,当前的hashmap中包含的key-value总数(size)增加了1,这时要检查是否超过了我们设定的阈值threshold,如果超过了,就要对hashmap进行扩容,并将数据从老的hashmap迁移到新的hashmap中去
- 扩容(resize)时,遍历老的table,在其中遍历每个table[i]链表,根据hash值和新容量对每个node计算其新的桶索引(newTable[i])。将node插入到新的桶的最前边去。
至此,put过程结束。
而在多线程的情况下,当执行到步骤5时,由于多个线程都要不断地将node插入到桶的最前面去,在这个过程中可能会形成环形链接。
参考文章:https://coolshell.cn/articles/9606.html 写的太好了,必须推荐。以上只是对文章中的思想进行了总结。
线程安全的Hashtable
hashtable是通过将put,get等方法加上synchronized关键字(同步锁),每次只能有一个线程进行存或取。以此避免发生线程不安全问题,但这种方式必然会以牺牲效率为代价。
线程安全的ConcurrentHashMap
ConcurrentHashMap的实现与Hashtable有所不同,它是通过在table之外再加一层segment
图片引用(http://www.yupoo.com/photos/goldendoc/81556254/)
当每次进行put和get操作时,不必对整个map都加锁,只需要对table所在的segment加上同步锁就可以了。另外rehash也是在单个segment中进行,成本较低。这样既避免了线程安全问题,又保证了高并发的存取效率。
参考文章:https://liqianglv2005.iteye.com/blog/2025016
其他实现了map接口的类:
TreeMap:除了实现Map接口之外还实现了SortedMap接口,集合中的映射关系有一定的顺序(按照键升序、降序或自定义顺序),但性能较差,线程不安全。
LinkedHashMap:它是HashMap的子类,用双向链表存储,遍历顺序与插入时的顺序一样。它在resize时不用对key-value重新排序,但它是用链表维护内部顺序,增加了空间开销的同时,也因此遍历的性能较高。它也是线程不安全的。