Java 面试题 041 - 050

本文深入探讨了Java集合框架中的ArrayList、Vector、LinkedList之间的性能差异,以及它们在内存占用上的特点。另外,还详细阐述了HashSet、LinkedHashSet和TreeSet的区别,以及HashMap和HashTable的不同之处。特别提到了JDK1.8中HashMap的实现优化,包括计算哈希值的方法和为何数组长度是2的幂次方。最后,分析了HashMap在多线程环境下的线程不安全性及可能导致的问题,并推荐在并发场景下使用ConcurrentHashMap。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

041、ArrayList VS Vector

  • ArrayList 底层是数组,线程不安全,适合多读的场景使用
  • Vector 底层是数组,线程安全,效率比较低

042、ArrayList VS LinkedList

  • ArrayList 底层是数组,查询效率高,插入效率低
  • LinkedList 底层是双向链表,查询效率低,插入效率高
  • 从内存空间占用的角度来看,ArrayList 主要使用空间浪费表现在列表结尾为预留一定的内存空间,LinkedList 空间花费主要表现在它的每一个元素都比 ArrayList 要消耗更多的空间,因为除去元素本身外还要存储它的直接前驱和直接后继。

043、HashSet、LinkedHashSet 和 TreeSet 的区别?

  • HashSet:是 Set 接口的实现类,存储元素无序,唯一,可存储 null,线程不安全,其底层是 HashMap,Map 的 key 用来存储元素,所以可以保证唯一,Map 的 value 都存储同一个空对象 private static final Object PRESENT = new Object();
  • LinkedHashSet:HashSet 的子类,内部通过 LinkedHashMap 实现,可以按照元素的添加顺序遍历。
  • TreeSet:底层使用红黑树,有自然排序和定制排序两种排序方式,默认使用自然排序。自然排序要求存储元素所在的类实现 Comparable 接口中的 compareTo(Object o) 方法;定制排序则需要自定义实现 Comparator 接口。

044、HahsMap 和 HashTable 的区别?

  • HashMap 线程不安全,HashTable 线程安全。
  • HashMap 的效率比 HashTable 的效率高。
  • HashMap 允许 key 和 value 为 null,HashTable 不允许 key 和 value 为 null。
  • 在 JDK 1.8 以后 HashMap 底层数据结构实现为数组 + 链表 + 红黑树,当链表长度达到阈值(默认为 8)并且当前数组长度大于等于 64 会把链表转换为红黑树,当红黑树节点数减少为 6 时,会将红黑树转换为链表,HashTable 则没有这个机制。
  • 初始容量大小和扩容机制不同:HashTable 默认初始大小为 11,之后每次扩容为原来的 2n + 1 倍;HashMap 默认初始大小为 16,之后每次扩容为原来的 2 倍;如果在初始化时给定了容量初始值,HashTable 会直接使用给定的大小,而 HashMap 会将初始值转换为大于给定值的最小的 2 的 n 次幂。

045、HashMap 和 HashSet 的区别?

  • HashMap 实现了 Map 接口,存储键值对;HashSet 实现了 Set 接口,仅存储对象。
  • HashMap 调用 put() 向 map 中存储键值对;HashSet 调用 add() 方法向集合中存储对象。
  • 实际上 HashSet 就是通过 HashMap 实现的,HashSet 在键值对中的 key 中存储对象,value 统一存储同一个空对象 PRESENT。

046、HashMap 和 TreeMap 的区别?

TreeMap 比 HashMap 多了对集合中的元素根据键排序的能力。

047、HashSet 如何检查重复?

当你往 HashSet 中存储对象时,HashSet 会先通过对象的 hashcode 来计算出对象的存储位置,如果对应的位置没有元素存储,HashSet 会认为对象还没有出现过,就会把对象存储到这个位置,如果这个位置已经有对象存储,HashSet 会调用 equals() 来检查这两个对象是否真的相等,如果相等则插入操作失败。

048、JDK 1.8 中计算 hash 值得方法?

static final int hash(Object key) {
    int h;
    // key.hashCode():返回散列值也就是 hashcode
    // ^:按位异或
    // >>>:无符号右移,忽略符号位,空位都以0补齐
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

049、HashMap 的长度为什么是 2 的幂次方?

为了能让 HashMap 存取高效,必须尽量减少碰撞,也就是说要尽量把数据分配均匀。

hash 值的取值范围前后加起来大概有 40 亿的映射空间,一般情况下是不会出现碰撞的,但问题是一个 40 亿长的数组内存是放不下的,所以散列值是不能直接拿来当数组下标的,为了尽可能平均分配数据,我们需要通过 hash 值按照数组长度取余来计算数组下标,为了尽可能提高效率直接采用二进制 & 操作进行计算,为了 & 操作和取余操作等价,我们必须保证数组长度是 2 的幂次方。也就是说在 n 的值为 2 的幂次方时,n % hash == (n - 1) & hash

050、为什么 HashMap 是线程不安全的?

  1. 同时 put 碰撞导致数据丢失

    两个线程同时 put,可能会发生哈希碰撞,导致其中一个数据丢失。

  2. 同时 put 扩容导致数据丢失

    多个线程同时 put,然后同时发现需要扩容,但最后只有一个扩容的数组会被保留下来,这样会导致部分数据丢失。

  3. 死循环造成 CPU 使用率 100%

    在 JDK 1.8 之前,HashMap 在多线程下进行 Rehash 可能会造成元素之间形成一个循环链表,最终会抛出 OOM 异常,导致 CPU 使用率 100%。

在并发环境下推荐使用 ConcurrentHashMap。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值