Java面试_Map
博客原文: https://blog.csdn.net/zhoucheng05_13/article/details/79842142
一、Map的继承体系图
二、详解
第一层
包含AbstractMap抽象类和一个SortedMap接口。
AbstractMap
该类提供了一个Map接口的框架性实现,降低了实现Map接口的难度。该Map实现允许存入null值。它实现了Map接口的绝大多数方法。
SortedMap
- 该接口继承了Map接口,它定义了key有序的Map。key如果实现Comparable接口,那么可以使用自然顺序进行排序。否则,在创建该map是提供外部comparator。对该map返回的视图(entrySet, keySet and values 方法)进行迭代遍历时就是根据key的顺序进行。该类还额外提供了几种方法来利用key有序这个特性。
- 该接口要求所有的key都必须是可以进行比较的,要么通过k1.compareTo(k2),要么通过comparator.compare(k1, k2)进行比较。它同时还规定了所有通用的sorted map需要提供4个构造器,分别是:
- 无参数构造器,给子类调用
- 只含有一个Comparator的构造器
- 只含有一个Map类型的参数,根据Map的key的自然顺序排序
- 只含有一个SortedMap类型参数的构造器。
- 接口提供了几个根据key的范围返回submaps的方法。这些范围都是半闭半开的。可以通过m.subMap(low, high+”\0”)来获得全闭范围。或者通过m.subMap(low+”\0”, high)来获得全开范围。该接口定义了几种利用key有序特性的方法:
- subMap(K fromKey, K toKey):获取指定范围内的子Map视图。
- headMap(K toKey)
- tailMap(K fromKey)
- firstKey():获取第一个key
- lastKey():获取最后一个key
第二层
HashMap
HashMap是一个基于哈希表来存储键值对,以期获得O(1)的操作时间。它的底层实现时这样的:
- 定义了Node节点,该节点类似于链表节点。使用Node数组作为哈希表
- 在插入元素时,通过(n-1)&hash(hash为hashCode值>>>16)求得下标,并将其插入到该下标处。
- 如果下标处已经有元素了,那么就将元素插入到该哈希桶的链表头部。
- 执行删除时,同样通过(n-1)&hash求得下标,然后对该哈希桶的链表进行遍历,找到了就删除。
他有如下特点:
- 容量始终是2的整数次幂,默认容量是16.当用户通过构造函数指定初始容量时,该类会将其转化为大于它的最接近的2的幂。如用户输入30,那么初始容量会是32
- 默认的装填因子(load factor)是0.75.
- 它使用链地址法解决哈希冲突。
- 在哈希表大小大于68,且链的长度大于8时,HashMap会将链转化为红黑树结构,以提高查询效率。
- 在哈希表容量小于68,那么HashMap会对哈希表进行扩容,并重新哈希。
- 在冲突链的长度大于8时,HashMap又会将红黑树转化为链表形式。
- 对HashMap进行遍历时,所花的时间与capacity而不是size成正比。因为它需要遍历Node数组的每一个位置。
- 当容量达到阈值(装载因子 * 容量)时,将Node数组增大2倍(因为容量室长要为2的幂)。
WeakHashMap & IdentityHashMap
- WeakHashMap的key只保留对实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,WeakHashMap也可能自动删除这些key所对应的key-value对。
- 在IdentityHashMap中,当且仅当两个key严格相等(key1==key2)时,IdentityHashMap才认为两个key相等;相对于普通HashMap而言,只要key1和key2通过equals()方法返回true,且它们的hashCode值相等即可。
HashTable
可以将HashTable理解为HashMap的线程安全版本,但是它们在哈希算法等方面还是存在一些不同。该类已经逐渐别废弃,在线程安全的场景下,JDK推荐使用HashMap代替Hashtable。如果需要一个线程安全的高并发实现,那么建议使用ComcurrentHashMap而不是该类。
- HashTable具有如下特点:
- 通过在所有方法上添加Synchronized关键字进行同步锁定,因此线程安全,但效率很低
- 初始容量是11,如果用户指定了初始容量,那么HashTable不会做任何修改。
- load factor同样是0.75
- 使用Entry
TreeMap
TreeMap是一个基于NavigableMap的红黑树实现。该实现保证了containsKey、get、put、remove操作的log(n)时间界。
TreeMap最重要的特点是,使用了红黑树这种有序平衡二叉树来保持了key的顺序,同时又利用有序二叉树的常用操作的log(n)时间界。
注意,该方法不是同步的。如果要在多线程环境下使用,那么需要进行外部同步,通常是通过锁定一个外部对象,如果不存在这样的对象,那么应该使用Collections.synchronizedSortedMap进行包裹。
由该类集合视图方法返回的集合,它们的迭代器都是快速失败机制的。(快速失败机制有什么用?首先,快速失败是通过在每次进行操作前判断modCount是否等于expectedModCount,如果不等则说明集合在外部被修改了,那么抛出异常,终止遍历,将风险降到最小,而不是在未来的某一不确定的时间,冒任意不确定的风险。该机制并不可靠(为什么不可靠?存在不同步的并发修改时,不可能做出任何绝对的保证),不能依赖于该机制编程,官方文档中指明,该机制仅用于检测错误)
第三层
LinkedHashMap
LinkedHashMap是通过链表的方式使得插入变得有序的Map接口实现。这个实现是为了解决HashMap和HashTable无序问题,而又不增加像TreeMap那样的成本。它基于HashMap,并在HashMap的基础上为每个节点增加了一个双向链,形成了一个双向链表,以此来保证了元素的有序。
重点: LinkedHashMap的一个特殊的构造器LinkedHashMap(int,float,boolean)被用来创建一个从最近最少到最近被访问的访问顺序排序的LinkedHashMap。这样的map非常适合用于实现LRU缓存。关于LinkedHashMap与LRU的关系。
这里请参考:https://blog.csdn.net/zhoucheng05_13/article/details/79832885
-
LinkedHashMap与HashMap的不同点有:
- LinkedHashMap在视图上的迭代器需要与size成比例的时间,与容量无关。而HashMap的迭代器依赖于哈希桶的容量,这将花费比LinkedHashMap更多的时间。
- LinkedHashMap的initial capacity和load facotry参数的定义与HashMap中一样。不过,需要注意的是,初始容量过高给LinkedHashMap带来的影响比HashMap要小得多。因为LinkedHashMap的遍历是通过链表完成(HashMap是通过遍历所有哈希桶,时间与容量成正比)。由于添加了链表,因此LinkedHashMap的性能会略低于HashMap。
- LinkedHashMap可以通过accessOrder参数来选择两种不同的排序方式:accessOrder为true时,以元素的访问顺序排序。为false时(默认情况),以元素的插入顺序排序。
properties
key和value都只能是String类型的,可使用成员方法load将.properties文件中的键值对加载进来,也可以从xml文件中加载属性列表(键值对)。
- 使用load方法从读取属性列
- load(InputStream inStream) 从输入流中读取属性列表(键和元素对)。
- load(Reader reader) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
- loadFromXML(InputStream in) 将指定输入流中由 XML 文档所表示的所有属性加载到此属性表中。
- 使用store方法
- store(OutputStream out, String comments) 将此 Properties 表中的属性列表(键和元素对)写入输出流。
- store(Writer writer, String comments) 将此 Properties 表中的属性列表(键和元素对)写入输出字符。
- storeToXML(OutputStream os, String comment) 发出一个表示此表中包含的所有属性的 XML 文档。
- storeToXML(OutputStream os, String comment, String encoding) 使用指定的编码发出一个表示此表中包含的所有属性的 XML 文档。