Map
- Map内存储的是键/值对形式的数据,通过“键”对象来查询“值”对象
- Map中,key值是唯一的(不能重复),而key对象是与value对象关联在一起的
HashMap
代码一步步解析源码
Map hashMap = new HashMap();
hashMap.put("key1","value1");
hashMap.put("ooookey1","value1");
hashMap.put("key1","value2");
- new HashMap()干了什么:
指定initial capacity为默认的16,load factor为默认的0.75 - hashMap.put()干了什么:
调用了hashMap的putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
首先判断map的容量是否为0,如果为0,resize为默认的16和0.75的系数。然后用map最大下标-1对hash(key)取余( 也就是源码中(p = tab[i = (n - 1) & hash]) == null 的这一行代码,这里多说一句为什么用按位与,主要是考虑性能,而且不管key的hash值有多大,(n - 1) & hash都不会大于n-1,所以确保不会数组越界),从而判断新key的哈希值是否已经存在。如果不存在,直接创建一个新的Node节点,如果存在,后续会判读equals方法是否相同,也就是所说的hash冲突引起的碰撞探测,具体参照5. 解决hash冲突。判断onlyIfAbsent参数是否为false(true为不替换存在的value,默认为false),替换掉已经存在的key对于的value。 - hashMap是无序的:和put的顺序无关
- hashMap的resize方法:
顾名思义初始化或扩容hashMap的容量,当hashMap中填满了容量得75%的时候,就会去扩容,首先确定 新容量的大小,也就是 旧容量翻倍(oldCap << 1),然后将容量阈值threshold设置为新的容量值 * 0.75,最后将之前的Node数组循环遍历(由此可见map的底层实现是数组),复制放进新的数组中。 - 解决hash冲突:
HashMap使用链表的方式避免哈希冲突(相同的hash值),当链表长度大于 TREEIFY_THRESHOLD (默认为8)的时候,将链表转换成红黑树;当小于UNTREEIFY_THRESHOLD (默认为6)的时候,又会转回链表以达到性能均衡。
hashMap的node节点中有一个next,这个参数就是记录如果hash碰撞后,hash值相同时链表结构中下一级Node节点。
如果同一个hash值下,链表的长度大于8时,将链表结构转换为红黑树,也就是下面的TreeNode
hashMap中的数据存储概览图
Hashtable
- Hashtable继承于古老的Dictionary类
- Hashtable是线程安全的
- Hashtable键值不可为空
- Hashtable的任何操作都会把整个表锁住,是阻塞的。好处是总能获取最实时的更新,比如说线程A调用putAll写入大量数据,期间线程B调用get,线程B就会被阻塞,直到线程A完成putAll,因此线程B肯定能获取到线程A写入的完整数据。坏处是所有调用都要排队,效率较低。
ConcurrentHashMap
核心原理
- 底层实现和hashMap一样,通过数组+链表+红黑树实现
- 通过CAS+synchronized关键字来保证并发安全
代码解析
ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap();
concurrentHashMap.put("key1","value1");
concurrentHashMap.put("key2","value1");
concurrentHashMap.remove("key3");
concurrentHashMap.get("key2");
1、初始化: