2. 集合

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:

  1. 根据 key 计算出 hash值。
  2. 判断是否需要进行初始化。
  3. 定位到 Node,拿到首节点 f,判断首节点 f:
    • 如果为 null ,则通过cas的方式尝试添加。
    • 如果为 f.hash = MOVED = -1 ,说明其他线程在扩容,参与一起扩容。
    • 如果都不满足 ,synchronized 锁住 f 节点,判断是链表还是红黑树,遍历插入。
  4. 当在链表长度达到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,可以获取集合中元素的索引。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值