java容器(二):Map
Collection 和 Map是java容器里面的俩大中流砥柱,在我的上一篇博客中,已经介绍过了Collection,那么今天我们再来聊聊Map里的好东西。
No picture you say a JB!啥也不先说,先上张图瞧瞧(度娘的)
这张图感觉好像有点问题,LinkedHashMap应该是HashMap的子类。。。,再就是少了一个我们今天要讲的ConcurrentHashMap
一. HashMap :
- 一个我们平常使用最最频繁的Map容器,它的查询速度是最快的,HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。
- HashMap的实现原理在面试中经常被问到,我的第一次电话面试也是被问到了,当时我是一点都没答上来,尴尬得一匹。。。,HashMap在jdk1.7和jdk1.8中实现原理都不一样,这边我们也来介绍一下
- jdk1.7中,基于数组和链表实现,如下图所示:
- 大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。上图中,每个绿色的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。
1.capacity:当前数组容量,初始值16,始终保持 2^n(可以思考一下这里为什么是2的n次幂),可以扩容,扩容后数组大小为当前的 2 倍。
2.loadFactor:负载因子,默认为 0.75。
3.threshold:扩容的阈值,等于 capacity * loadFactor
- jdk1.8中加入了红黑树,当链表的长度超过8以后,改用红黑树,因为链表遍历的时间是o(n),而红黑树是o(logn),结构如下图:
- 这里其实关于HashMap讲得还太少,比如说如何根据在散列表中找到对应的key值、为啥数容量保持2^n次幂。。。主要是我个人自己也还不是太懂,讲得比较粗糙,有兴趣的建议大家看看《Java编程思想》关于集合的知识,或者还可以自己看看HashMap的源码
二、Hashtable
- Hashtable 是遗留类,已经过时了的,很多映射的常用功能与 HashMap 类似,不同的是它承自 Dictionary 类,并且是线程安全的,任一时间只有一个线程能写 Hashtable,但是并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁。Hashtable 不建议在新代码中使用,不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换。
三、ConcurrentHashMap
- Concurrent意思是同时发生的、并发的,顾名思义它是为了解决HashMap的线程不安全问题,实现原理和HashMap大致相似,但是由于是可以并发的,所以在一些方面实现比较复杂。
- 整个 ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分“或”一段“的意思,所以很多地方都会将其描述为分段锁。也有些博文喜欢用“槽”来代表一个segment。简单理解就是,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
- 上图是concurrentHashMap的一个结构图,基于jdk1.7的,暂未引入红黑树。concurrencyLevel:并行级别、并发数、Segment 数,怎么翻译不重要,理解它。默认是 16,也就是说 ConcurrentHashMap 有 16 个 Segments,所以理论上,这个时候,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。这个值可以在初始化的时候设置为其他值,但是一旦初始化以后,它是不可以扩容的。再具体到每个 Segment 内部,其实每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安全,所以处理起来要麻烦些。
四、TreeMap - TreeMap 实现 SortedMap 接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。如果使用排序的映射,建议使用 TreeMap。
在使用 TreeMap 时,key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常。
五、LinkedHashMap - LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。由于在添加key-value时还要额外维护链表操作,性能上比HashMap要差(HashMap是最强的,速度最快,没有别的额外需求,首先使用它)
总结:
其实关于这些Map的知识还很多,一篇短博客讲不了那么多