java面试题 --- 集合

2021-12-10 14:22:55 浏览数 (1)

1. java 集合你了解吗?

  • java 集合最顶层接口是 Collection 和 Map;
  • Collection 有三个核心接口,分别是 List,Set,Queue;
  • List 是有序可重复的,它的主要实现类有 ArrayList、LinkedList 和 Vector;
  • ArrayList 是数组实现的,查询快增删慢线程不安全;
  • LinkedList 是链表实现的,查询慢增删快线程不安全;
  • Vector 相当于线程安全的 ArrayList;
  • Set 是无序不重复的,它的主要实现类有 HashSet、TreeSet 和 LinkedHashSet,它们分别是用对应的 Map 实现的,比如 HashSet 就是用 HashMap实现的,注意 TreeSet 是有序的;
  • Queue 是队列,主要有阻塞队列 BlockingQueue。Map 的主要实现类有 HashMap、LinkedHashMap、HashTable 和 TreeMap。

2. 什么是集合的快速失败机制?

  • 集合内部会维护一个 modCount 变量,遍历的时候,会判断 modCount 变量的值是否等于期望值,不等就会报并发修改异常。

3. 用 for 循环遍历集合的同时移除元素可以吗?

  • 不可以,会报并发修改异常。要边遍历边移除元素,只能用迭代器。

4. HashMap 底层是用什么实现的?

  • jdk1.7 的 HashMap 采用拉链法,数组加链表实现的;
  • jdk1.8 的 HashMap 采用了数组加链表加红黑树实现。

5. HashMap (jdk1.8) 怎么初始化的?

  • 如果调用的是无参构造,不会立即初始化数组,要等 put 元素时才会初始化一个长度为 16 的数组;
  • 如果调用的是带参构造,就会将数组长度初始化为最接近传入参数的 2 的 n 次幂,比如传入的是 6,那就初始化为 8。

6. HashMap (jdk1.8) 怎么计算索引的?

  • 首先会对 key 的 hashCode 值的高十六位与低十六位做一个异或 (^) 运算,这样做是为了让 key 的整个 hashCode 都能参与接下来的计算,减少 hash 碰撞的概率,且异或运算得到 0 和 1 的概率一样,不会影响数据分布;
  • 接着拿到刚才计算出来的值,和数组长度减一之后的值进行与 (&) 运算,就得到了索引。

7. HashMap (jdk1.8) 计算索引时为什么用与 (&) 操作?

  • 正常情况计算索引应该是 hashCode 值对数组长度取模,即求余,但是取模运算的效率不高,所以改用与 (&) 操作。

8. HashMap (jdk1.8) 数组长度为什么是 2 的 n 次幂?

  • 只有当数组长度为 2 的 n 次幂时,hashCode 值与 (&) 数组长度减一的计算结果才会和 hashCode 值对数组长度取模的计算结果才会一致;
  • 同时 2 的 n 次幂减一的二进制是若干个 1,和奇数计算最后结果是奇数,和偶数计算的结果是偶数,如果最后一位是 0,那么不管和奇数还是偶数进行与 (&) 计算的结果都是偶数,不能保证散列分布均匀。

9. HashMap (jdk1.8) 计算拿到索引后直接把元素存在那个位置吗?

  • 拿到索引后,先判断索引位置是否有元素,如果没有,直接把元素放到索引位置;
  • 如果有,判断 key 是否一样,如果一样,新值覆盖旧值;
  • 如果不一样,就在此处生成链表,元素存到链表中。

10. HashMap (jdk1.8) 什么时候生成红黑树?

  • 当链表长度达到 8,且数组长度达到 64 的时候,就会生成红黑树;
  • 当红黑树节点少于 6 个的时候,又会将红黑树转回链表。

11. HashMap (jdk1.8) 数组什么时候扩容?

  • 扩容因子是 0.75,当数组中元素个数达到数组长度的 3/4 时就扩容,扩容为原来的两倍。

12. HashMap (jdk1.8) 数组扩容后数据怎么转移?

  • 如果原先数组那位位置的元素是单个元素或者红黑树,那就放到 hash 与 (&) 新数组长度减一的位置;
  • 如果是链表,那就判断 hash 与 (&) 旧数组长度是否为 0,如果是,就放在原来索引处,如果不是,就放在原来索引加上旧数组长度处。

13. 既然 HashMap (jdk1.8) 不安全,那并发情况下用什么?

  • 用 ConcurrentHashMap。

14. ConcurrentHashMap 的底层你知道吗?

  • jdk1.7 的 ConcurrentHashMap 底层是 Segment 数组,Segment 数组存放的元素是 HashEntry 数组,HashEntry 数组存放的元素是链表。每次锁住一个 Segment,保证安全性的同时提高了并发性,这就是锁分段机制;
  • jdk1.8 的 ConcurrentHashMap 的底层是数组加链表加红黑树,用 Synchronized 和 CAS 来保证线程安全。

15. ArrayList 的 elementData 为什么用 transient 修饰?

  • 加上 transient 就不会直接序列化整个数组,序列化的时候只序列化数组中存的元素,而不是整个数组,既加快了序列化速度也减小了序列化后文件的大小。

16. List 和 Set 如何选用?

  • List 支持随机快速访问,支持用索引获取元素,Set 不支持。所以如果增删操作比较多,适合用 Set,查询操作比较多,适合用 List;
  • List 是有序可重复的,而 Set 是无序不重复的。所以可以根据存入的元素是否需要顺序、是否可以重复来决定用什么。

17. HashSet 如何保证元素不重复?

  • HashSet 底层是 HashMap,HashSet 存储的元素就存放在 HashMap 的 key 中,HashMap 的 key 是否相同是先比较 hashCode 值再用 equals 方法比较。

18. 为什么 String、Integer 适合作为 HashMap 的 key?

  • 因为 String 和 Integer 都是 final 类型的,能够保证 hash 值的不可更改性;
  • String 和 Integer 已经重写了 hashCode 方法和 equals 方法,可以保证计算的准确性。

0 人点赞