看到这个有点懵逼,一时还真不知道怎么解释,能让彻底没有接触过的人都能听懂java
想到生活中一个有意思的场景,和咱们使用Map很是像,拿着新华词典查字数组
咱们这里以拼音方式查询字时,通常步骤以下:安全
再转换看一下Map的工做原理(主要是HashMap)数据结构
经过hash()计算key,得出一个hash值(同字转拼音)多线程
经过hash值,获取Node在数组中的索引 (同经过拼音获取页码)并发
获取Node,而后遍历Node#next
,查到咱们须要的节点(同在页码中找到对应的字)性能
JDK中实现了一些可以覆盖绝大部分场景的Map容器,罗列一些常见的以下优化
HashMap
: 主要是利用key的hash来定位对应的元素HashTable
EnumMap
: key为枚举的map,根据枚举的ordinal
做为定位对应的元素 (用的比较少,后面不归入分析范畴)TreeMap
LinkedHashMap
ConcurrentHashMap
根据是否线程安全进行分类线程
线程安全 | 非线程安全 |
---|---|
HashTable , ConcurrentHashMap |
HashMap , TreeMap , LinkedHashMap |
根据Map是否有序进行分类设计
有序 | 无序 |
---|---|
TreeMap , LinkedHashMap |
HashMap , ConcurrentHashMap , HashTable |
根据key怎么获取对应的元素
hash定位 | 其余定位 |
---|---|
HashTable , ConcurrentHashMap , HashMap , LinkedHashMap |
TreeMap |
TreeMap比较有意思,要求指定一个比较器,或者key可自比较,并且若是塞入两个不一样的kv对,可是key经过比较器发现相等时,会用后入的kv对中的value替换前面的那个,即定位是根据比较器来的
根据底层数据结构进行分类
数组+链表 | 树 |
---|---|
HashTable , ConcurrentHashMap , HashMap , LinkedHashMap |
TreeMap |
>>> 如何使用
最最多见的使用方式,三把斧便可,以下
// 1. 建立一个Map对象 Map<String, String> map = new HashMap<>(); // 2. 塞入kv数据对 map.put("key", "value"); // 3. 取出key对应的数据 String value = map.get("key");
其次就是使用的注意事项
ConcurrentHashMap
,不要用HashTable
)TreeMap
, LinkedHashMap
,实际后者用得更多)此外分享一个实际项目中关于HashMap的一个优化点
通常来讲,HashMap的get(key)
方法是O(1)时间开销,可是因为获取对应value,会频繁的计算hash值,且不可避免的会产生Hash碰撞,这些都是会有额外的开销(cpu和时间开销)
咱们的一个应用中,存在大量的配置开关(用与各类预案,各类场景的切换)存在一个大的HashMap中,致使每次提供服务时,都会去这个Map中屡次查询Map中的配置值,咱们作的优化是将Map映射到一个配置类,以此减小频繁的hash操做
遗憾的是最后性能提高并非特别明显,也就1-2毫秒的样子...(若是系统的rt要求特别严格的能够考虑从这个方面出发)
>>> 如何实现的
简单来说,从两个点出发,一是数据结构,二是如何向其中添加和取数据
底层存储结构以下图:
数组+链表(or红黑树),数组的容量,必然为2的n次方
读写数据:
equals()
判断相等另一点就是扩容
capacity * loadFactor
(通常是容量*0.75),则数组会出现扩容,扩为原来的两倍二者都是线程安全的,但底层的实现原理确实彻底不一样
HashTable
synchronized
关键字,实行加锁同步ConcurrentHashMap
HashMap无序,但实际的业务场景中,须要有序的地方还很多,通常来将,常见的顺序要求是根据前后塞入Map容器的顺序来肯定,此时能够考虑采用 LinkedHashMap
,确保先塞入Map的,在遍历时,优先出来
若是有比较复杂的排序场景,则能够采用TreeMap
,使用的时候须要额外注意一些使用事项
通常遍历就是下面三中场景了
// 遍历kv for(Map.Entry<String, String> entry: map.entrySet()) { // .... } // 遍历key值 for(Object key : map.keySet()) { // xxx } // 遍历value值 for(Object value: map.values()) { // xxxx }
根据不一样的场景选择遍历方式
上面的遍历过程当中,都是不容许对Map进行增删操做的,不然会抛一个并发修改异常;若是在遍历过程当中,须要根据对应的值,作一些处理,采用迭代器方式, 一个demo以下
Map<String, String> map = new HashMap<>(); // .... Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, String> entry = iterator.next(); iterator.remove(); iterator.next(); }
蛋疼的问题,真要本身来设计的话,最简单的就是HashTable这种全加锁的机制;可是这种实际是强制使多线程串行工做了,若是须要并发工做呢?
除了ConcurrentHashMap
的锁分段机制,感受能够参考CopyOnWriteArrayList
的实现方式,来一个CopyOnWriteHashMap
,对写进行加锁,读无锁,具体的实现,有待完善...