前段时间一个实习的面试遇到了map接口的问题,面试官让我说出几种Map的实现类以及它们的区别,在说到谁是线程安全的问题上出现了知识欠缺,所以在这里重新整理一下java中的集合并详细介绍一下Map接口的相关知识。
根据学习,Java 集合可分为 Collection 和 Map 两种体系 。
Collection接口:单列数据,定义了存取一组对象的方法的集合,它是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
注意:JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List) 实现。
Set:元素无序、不可重复的集合
List:元素有序、可重复的集合
Map接口:双列数据,保存具有映射关系“key-value对”的集合
注意:
1.Map 中的 key 和 value 都可以是任何引用类型的数据
2.Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应 的类,须重写hashCode()和equals()方法
3.常用String类作为Map的“键”
4.key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到 唯一的、确定的 value
5.Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和 Properties。其中,HashMap是 Map 接口使用频率最高的实现类
Map接口主要有以下几个实现类:
Hashmap
1.它是 Map 接口使用频率最高的实现类。
2.它允许使用null键和null值,与HashSet一样,不保证映射的顺序。
3.所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写: equals()和hashCode()
4.所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类 要重写:equals()
5.一个key-value构成一个entry
6.所有的entry构成的集合是Set:无序的、不可重复的
7.HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true, hashCode 值也相等。
8.HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。
注意:JDK 7及以前版本:HashMap是数组+链表结构(即为链地址法)
JDK 8版本发布以后:HashMap是数组+链表+红黑树实现。当链表数量大于8时,结构转化为红黑树。
问:负载因子值的大小,对HashMap有什么影响 ?
1. 负载因子的大小决定了HashMap的数据密度。
2.负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长, 造成查询或插入时的比较次数增多,性能会下降。
3.负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的 几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性 能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间。
4.按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数
问:hashCode() 和equals() 方法的重要性体现在什么地方?
Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。
ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment。HashEntry 用来封装映射表的键 / 值对;Segment 用来充当锁的角色,每个 Segment 对象守护整个散列映射表的若干个桶。每个桶是由若干个 HashEntry 对象链接起来的链表。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组。HashEntry 用来封装散列映射表中的键值对。在 HashEntry 类中,key,hash 和 next 域都被声明为 final 型,value 域被声明为 volatile 型。
|
在ConcurrentHashMap 中,在散列时如果产生“碰撞”,将采用“分离链接法”来处理“碰撞”:把“碰撞”的 HashEntry 对象链接成一个链表。由于 HashEntry 的 next 域为 final 型,所以新节点只能在链表的表头处插入。
注意:由于只能在表头插入,所以链表中节点的顺序和插入的顺序相反。
Segment 类继承于 ReentrantLock 类,从而使得 Segment 对象能充当锁的角色。每个 Segment 对象用来守护其(成员对象 table 中)包含的若干个桶。
注意:Concurrenthashmap是线程安全的!(就这里说错了= =)
Hashtable
1.Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。
2.Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
3.与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
4.与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
5.Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。
LinkedHashMap
1.LinkedHashMap 是 HashMap 的子类
2.在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序
3.与LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代 顺序:迭代顺序与 Key-Value 对的插入顺序一致
TreeMap
1.TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。 TreeMap 可以保证所有的 Key-Value 对处于有序状态。
2.TreeSet底层使用红黑树结构存储数据
3.TreeMap 的 Key 的排序:
3.1 自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有 的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
3.2 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
4.TreeMap判断两个key相等的标准:两个key通过compareTo()方法或 者compare()方法返回0。
Properties
1.Properties 类是 Hashtable 的子类,该对象用于处理属性文件
2.由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
3.存取数据时,建议使用setProperty(String key,String value)方法和 getProperty(String key)方法