特点
- Map接口的常用实现类:HashMap、Hashtable和Properties
- HashMap 是以 key-value 对的方式来存储数据(HashMap$Node类型)
- 键不能重复,但是值可以重复,允许使用null键和null值
- 如果添加相同的key,则会覆盖原来 key-value ,等同于修改(key不会替换,value会替换)
- 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的
- HashMap线程不安全的
关于HashMap的线程安全
| 方法 | 示例 | 原理 | 性能 |
|---|---|---|---|
| HashTable | Map<String, Object> map = new Hashtable<>(); | synchronized修饰get/put方法。方法级阻塞,只能同时一个线程操作get或put | 很差 |
| Collections.synchronizedMap | Map<String, Object> map =Collections.synchronizedMap(new HashMap<String, Object>()); | 内部有个mutex对象,对它加锁 | 很差 |
| JUC中的ConcurrentHashMap | Map<String, Object> map = new ConcurrentHashMap<>(); | 每次只给一个桶(数组项)加锁 | 很好 |
扩容机制(具体源码看HashSet)
- HashMap底层维护了Node类型的数组table,默认为null
- 当创建对象时,将加载因子(loadfactor)初始化为0.75
- 当添加key-value时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素则直接添加;如果该索引处有元素,则继续判断该元素的key和准备加入的key是否相等,如果相等,则直接替换value;如果不相等,则需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容
- 第一次添加,则需要扩容table容量为16,临界值(threshold)为12(16*0.75)
- 以后再扩容,则需要扩容table容量为原来的2倍(32),临界值为原来的2倍,即24,依次类推
- 在java8中,如果一条链表的元素个数 超过 TREEIFY_THRESHOLD(默认是8),并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认是64),就会进行树化(红黑树)