集合
- 1. Arraylist与 LinkedList
- 2. Array 和 ArrayList 有什么区别
- 3. hash冲突
- 4. 解决hash冲突的办法有哪些?HashMap用的哪种?
- 5. 为什么在解决 hash 冲突的时候,不直接用红黑树?而 选择先用链表,再转红黑树?
- 6. ConcurrentHashMap 的实现原理是什么?
- 7. ConcurrentHashMap 的 put 方法执行逻辑是什么?
- 8. ConcurrentHashMap 的 get 方法是否要加锁,为什么?
- 9. get方法不需要加锁与volatile修饰的哈希桶有关吗?
- 10. ConcurrentHashMap 不支持 key 或者 value 为null
- 11. ConcurrentHashMap 的并发度是多少?
- 12. Collection框架中实现比较
- 13. Iterator 和 ListIterator 有什么区别?
1. Arraylist与 LinkedList
底层数据结构:
Arraylist 底层使用的是Object数组;
LinkedList 底层使用的是双向循环链表数据
2. Array 和 ArrayList 有什么区别
- Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型
- Array 大小是固定的,ArrayList 的大小是动态变化的。
- ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。
3. hash冲突
hash冲突就是键(key)经过hash函数得到的结果作为地址去存放当前的键值对(key-value)(这个是hashmap的存值方式),但是却发现该地址已经有人先来了,一山不容二虎,就会产生冲突。这个冲突就是hash冲突了。
一句话说就是:如果两个不同对象的hashCode相同,这种现象称为hash冲突。
4. 解决hash冲突的办法有哪些?HashMap用的哪种?
解决Hash冲突方法有:开放定址法、再哈希法、链地址法(拉链法)、建立公共溢出区。
HashMap中采用的是链地址法 。
-
开放定址法也称为 再散列法 ,基本思想就是,如果 p=H(key) 出现冲突时,则以 p 为基础,再次hash, p1=H(p ) ,如果p1再次出现冲突,则以p1为基础,以此类推,直到找到一个不冲突的哈希地址 pi 。
因此开放定址法所需要的hash表的长度要大于等于所需要存放的元素,而且因为存在再次hash,所以 只能在删除的节点上做标记,而不能真正删除节点。 -
再哈希法(双重散列,多重散列),提供多个不同的hash函数,当 R1=H1(key1) 发生冲突时,再计算 R2=H2(key1) ,直到没有冲突为止。
这样做虽然不易产生堆集,但增加了计算的时间。 -
链地址法(拉链法),将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行。
链表法适用于经常进行插入和删除的情况。 -
建立公共溢出区,将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区。
5. 为什么在解决 hash 冲突的时候,不直接用红黑树?而 选择先用链表,再转红黑树?
因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。
当元素小于 8 个的时候,此时做查询操作,链表结构已经能保证查询性能。
当元素大于 8 个的时候, 红黑树搜索时间复杂度是 O(logn),而链表是 O(n),此时需要红黑树来加快查询速度,但是新增节点的效率变慢了。
因此,如果一开始就用红黑树结构,元素太少,新增效率又比较慢,无疑这是浪费性能的。
6. ConcurrentHashMap 的实现原理是什么?
jdk1.8:
在数据结构上, JDK1.8 中的ConcurrentHashMap 选择了与 HashMap 相同的数组+链表+红黑树结构;
在锁的实现上,抛弃了原有的 Segment 分段锁,采用 CAS + synchronized 实现更加低粒度的锁。
将锁的级别控制在了更细粒度的哈希桶元素级别,也就是说只需要锁住这个链表头结点(红黑树的根节点),就不会影响其他的哈希桶元素的读写,大大提高了并发度
7. ConcurrentHashMap 的 put 方法执行逻辑是什么?
jdk1.7:
首先,会尝试获取锁,如果获取失败,利用自旋获取锁;如果自旋重试的次数超过 64 次,则改为阻塞获取锁。
jdk1.8:
- 根据 key 计算出 hash值。
- 判断是否需要进行初始化。
- 定位到 Node,拿到首节点 f,判断首节点 f:
- 如果为 null ,则通过cas的方式尝试添加。
- 如果为 f.hash = MOVED = -1 ,说明其他线程在扩容,参与一起扩容。
- 如果都不满足 ,synchronized 锁住 f 节点,判断是链表还是红黑树,遍历插入。
- 当在链表长度达到8的时候,数组扩容或者将链表转换为红黑树。
8. ConcurrentHashMap 的 get 方法是否要加锁,为什么?
get 方法不需要加锁。因为 Node 的元素 val 和指针 next 是用 volatile 修饰的,在多线程环境下线程A修改结点的val或者新增节点的时候是对线程B可见的。
这也是它比其他并发集合比如 Hashtable、用 Collections.synchronizedMap()包装的 HashMap 安全效率高的原因之一。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
//可以看到这些都用了volatile修饰
volatile V val;
volatile Node<K,V> next;
}
9. get方法不需要加锁与volatile修饰的哈希桶有关吗?
没有关系。哈希桶 table 用volatile修饰主要是保证在数组扩容的时候保证可见性。
10. ConcurrentHashMap 不支持 key 或者 value 为null
因为 ConcurrentHashMap 是用于多线程的 ,如果map.get(key) 得到了 null ,无法判断,是映射的value是 null ,还是没有找到对应的key而为 null ,这就有了二义性。
而用于单线程状态的 HashMap 却可以用 containsKey(key) 去判断到底是否包含了这个 null 。
HashMap是非并发的,可以通过contains(key)来做这个判断。而支持并发的Map在调用m.contains(key)和m.get(key),m可能已经不同了。
11. ConcurrentHashMap 的并发度是多少?
在JDK1.7中,并发度默认是16,这个值可以在构造函数中设置。
如果自己设置了并发度,ConcurrentHashMap 会使用大于等于该值的最小的2的幂指数作为实际并发度,也就是比如你设置的值是17,那么实际并发度是32。
12. Collection框架中实现比较
- 实体类实现 lang 包下的comparable接口,实现 a.compareTo(Object b)方法,称作内部比较器
- 创建外部比较器实现 Util 包下的comparator接口,实现compare( Object a, Object b),称作外部比较器
13. Iterator 和 ListIterator 有什么区别?
- 遍历。使用Iterator,可以遍历所有集合,如Map,List,Set;但只能在向前方向上遍历集合中的元素。
使用ListIterator,只能遍历List实现的对象,但可以向前和向后遍历集合中的元素。 - 添加元素。Iterator无法向集合中添加元素;而,ListIteror可以向集合添加元素。
- 修改元素。Iterator无法修改集合中的元素;而,ListIterator可以使用set()修改集合中的元素。
- 索引。Iterator无法获取集合中元素的索引;而,使用ListIterator,可以获取集合中元素的索引。