集合框架

集合常问题

1.为什么 ArrayList 的 elementData 加上 transient 修饰?
transient 是一个关键字,用于修饰类的成员变量。当一个成员变量被声明为 transient 时,它表示该成员变量在序列化过程中会被忽略,不会被持久化保存。
在 ArrayList 中,elementData 是用于存储元素的数组。它被声明为 transient 的原因是为了在序列化和反序列化过程中提高效率和减少内存占用。
将 elementData 声明为 transient,可以避免不必要的序列化和反序列化操作,节省空间,并在反序列化时重新构建 elementData 数组,以确保序列化和反序列化的正确性和高效性
2.多线程场景下如何使用 ArrayList?
使用 Collections.synchronizedList 方法:
可以使用 Collections 类的 synchronizedList 方法来创建一个线程安全的 List,该方法返回一个线程安全的包装器(synchronized wrapper)。
使用 CopyOnWriteArrayList:
CopyOnWriteArrayList 是 Java 并发包中提供的线程安全的 List 实现,适用于读操作频繁、写操作较少的场景。它通过在写操作时复制一份新的数组来实现线程安全。
使用线程安全的并发集合类:
如果需要更高效的并发操作,并且不要求保持元素插入顺序,可以考虑使用 ConcurrentHashMap 或 ConcurrentLinkedQueue 等线程安全的并发集合类。
手动同步控制:
如果不使用上述线程安全的方式,也可以通过在访问 ArrayList 时手动添加同步控制(使用 synchronized 关键字或 Lock 接口),确保多个线程之间的互斥访问。
3.哪些集合类是线程安全的?它们在实现线程安全的方式上有什么区别?
Vector:
Vector 是最早引入的线程安全的集合类之一,它使用 synchronized 来保证对集合的操作是线程安全的。即所有方法都使用 synchronized 关键字进行同步。
Hashtable:
Hashtable 也是一个线程安全的键值对集合类,它通过在每个方法中使用 synchronized 来确保线程安全。
Collections.synchronizedList/synchronizedSet/synchronizedMap:
Collections 工具类提供了一系列静态方法来创建线程安全的 List、Set 和 Map。这些方法返回的集合都是通过包装器(wrapper)实现的,内部使用 synchronized 来保证线程安全。
ConcurrentHashMap:
ConcurrentHashMap 是 Java 并发包中提供的线程安全的 HashMap 实现。它采用分段锁(Segment)机制来实现并发访问,不同段(Segment)的数据可以被多个线程同时访问和修改,从而提高并发性能。
CopyOnWriteArrayList/CopyOnWriteArraySet:
CopyOnWriteArrayList 和 CopyOnWriteArraySet 是线程安全的 List 和 Set 实现,它们通过写时复制(copy-on-write)机制来实现线程安全。在修改操作时,会复制一个新的数组,并在新数组上进行修改,从而避免并发修改异常。
ConcurrentLinkedQueue:
ConcurrentLinkedQueue 是 Java 并发包中提供的线程安全的队列实现,它使用无锁算法(lock-free algorithm)来实现并发操作,保证在多线程环境下的线程安全。
4.了解什么是红黑树?
红黑树是一种自平衡的二叉查找树,它在每个节点上增加了一个存储位来表示节点的颜色(红色或黑色),并通过一些约束条件保持二叉查找树的近似平衡。红黑树具有如下性质:
节点颜色: 每个节点要么是红色,要么是黑色。
根节点和叶子节点: 根节点是黑色的,叶子节点(NIL 节点)是黑色的。
红色节点: 红色节点的子节点必须是黑色的(不存在两个相邻的红色节点)。
路径约束: 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点(即,所有路径具有相同的黑色节点数目)。
这些约束条件确保了红黑树的关键特性:
红黑树是一棵近似平衡的树,树的高度为 O(log n),其中 n 是树中节点的数量。
对于搜索、插入和删除等操作,红黑树的时间复杂度都是 O(log n)。
5.了解Java集合的快速失败机制 “fail-fast”。
在 Java 中,集合类中的“快速失败”(fail-fast)机制是一种设计策略,用于在多线程环境下检测并发修改操作。当一个集合被多个线程同时访问,其中一个线程对集合进行了结构性修改(如增加、删除元素),而此时其他线程正在遍历该集合,那么就可能导致遍历操作出现异常情况。
快速失败机制通过在迭代器中使用 modCount(表示集合被修改的次数)和 expectedModCount(表示迭代器期望的集合修改次数)来实现。当迭代器初始化时,会将集合的 modCount 值赋给 expectedModCount,然后在每次迭代操作之前都会检查 modCount 是否等于 expectedModCount,如果不相等,则抛出 ConcurrentModificationException 异常,从而快速失败。
快速失败机制的优点在于能够及时发现并发修改操作,避免在不确定状态下继续操作可能导致的错误结果。但也需要注意的是,快速失败机制只能检测到通过集合自身的方法(如 add、remove 等)所引起的并发修改,对于通过迭代器以外的方式修改集合的情况,并不能保证一定能够检测到。
在 Java 的集合框架中,像 ArrayList、HashMap、HashSet 等非线程安全的集合类都采用了快速失败机制。而像 ConcurrentHashMap、CopyOnWriteArrayList 等线程安全的集合类则采用了一些其他机制来保证线程安全性。
6.HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
数组大小限制: 数组的大小是有限的,通常是一个固定的大小。如果直接使用 hashCode() 的返回值作为下标,可能会超出数组的范围。
取模运算: 为了将大范围的哈希值映射到数组范围内,通常会对哈希值进行取模运算(hash & (length-1)),这样可以保证得到的下标在数组范围内,但取模运算会比较耗时。
解决哈希冲突: 在哈希表中,不同的键可能会计算出相同的哈希值,即发生了哈希冲突。为了解决哈希冲突,HashMap 需要使用链表、红黑树等数据结构来存储相同哈希值的键值对,直接使用哈希值作为下标无法处理这种情况。
7.HashMap 的长度为什么是2的幂次方?
HashMap 的长度通常选择为 2 的幂次方是为了更好地利用哈希算法的特性,并且能够通过位运算快速计算出元素所在的位置。这种选择可以让哈希值的低几位参与索引的计算,更均匀地分布元素到数组中,从而减少哈希冲突的概率。
当 HashMap 的长度为 2 的幂次方时,对于任意的 key,其 hashCode() 取模数组长度的结果等价于对数组长度减一进行位与操作,即 (n - 1) & hash,其中 n 为数组长度,hash 为 key 的哈希值。这种位与操作比取模运算更高效,可以提高计算速度。
另外,当数组长度为 2 的幂次方时,如果采用默认的扰动函数(hash & (length-1)),可以保证哈希值的低几位参与计算,从而更均匀地将元素分布到数组中,减少哈希碰撞的可能性。
8.ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?
ConcurrentHashMap 在 Java 中是一个线程安全的哈希表实现,它允许多个线程同时读取和修改其中的键值对,而不需要额外的同步措施。ConcurrentHashMap 的底层实现主要基于分段锁(Segment)和 CAS 操作(Compare and Swap)来实现并发安全性。
具体来说,ConcurrentHashMap 的底层数据结构是由一个 Segment 数组构成的。每个 Segment 类似于一个小的 HashMap,它包含一个 HashEntry 数组来存储键值对,以及用于控制并发访问的锁。整个 ConcurrentHashMap 中的所有键值对会被均匀地分散到各个 Segment 中,而每个 Segment 只锁定自己的部分,这样可以提高并发度。
在进行插入、删除或查找操作时,首先通过 key 的 hashCode() 方法计算出哈希值,然后根据哈希值定位到对应的 Segment,接着在该 Segment 中进行具体的操作。对于写操作,会先尝试使用 CAS 操作来更新数据,如果失败则会加锁进行操作;而对于读操作,则不需要加锁,可以直接读取数据。
ConcurrentHashMap 利用分段锁和 CAS 操作实现了更细粒度的并发控制,不同线程可以同时访问不同的 Segment,从而提高了并发性能。相比传统的使用全局锁的方式,ConcurrentHashMap 在大部分场景下能够获得更好的并发性能。

9.为什么HashMap中String、Integer这样的包装类适合作为K?如果使用Object作为HashMap的Key,应该怎么办呢?
String、Integer等包装类适合作为 HashMap 的 Key,主要是因为它们具有不可变性和良好的哈希分布特性。
不可变性: String、Integer等包装类对象是不可变的,一旦创建其值就不会发生改变。这意味着它们的 hashCode() 方法返回的哈希值是固定的,不会因为对象内容的改变而改变,保证了相同键对象的哈希值始终一致。
哈希分布特性: String、Integer等对象的 hashCode() 方法通常实现得比较好,能够将不同的对象映射到不同的哈希值上。这有助于减少哈希碰撞,使得元素能够更均匀地分布到 HashMap 的桶中,提高了查询、插入和删除操作的性能。
如果要使用 Object 作为 HashMap 的 Key,则需要重写 Object 类的 hashCode() 和 equals() 方法。确保正确地实现这两个方法非常重要,因为 HashMap 在判断两个 Key 是否相等时会使用 equals() 方法,而在确定存储位置时会使用 hashCode() 方法。如果没有正确覆盖这两个方法,可能会导致相同内容的 Key 被认为是不同的,或者导致元素无法正确存储和检索。
当使用自定义的对象作为 HashMap 的 Key 时,需要注意以下几点:
确保正确实现 hashCode() 和 equals() 方法,以保证对象能够正确地参与哈希计算和相等性比较。
尽量避免可变对象作为 Key,因为对象发生改变后其哈希值也会改变,可能导致无法正确检索元素。

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值