Java刷题错题笔记-day06-集合

发布时间:2024年01月09日

1.遍历List集合时删除元素可能会发生什么异常?

ConcurrentModificationException

在 Java 中,ArrayList 是一个使用 Fail-Fast 机制的例子。
ConcurrentHashMap是一个使用 Fail-Safe机制的例子。

Fail-Fast 机制,当在迭代过程中检测到集合被修改(添加、删除元素)时,立即抛出ConcurrentModificationException异常,以避免出现并发修改导致的不确定性行为。

在Java中,Fail-Fast Fail-Safe 是两种不同的迭代器(Iterator)机制,它们主要涉及到在遍历集合时对集合的修改的处理方式。以下是它们的简要介绍:

  1. Fail-Fast(快速失败)机制:
  • 定义: 当在迭代过程中检测到集合被修改(添加、删除元素)时,立即抛出ConcurrentModificationException异常,以避免出现并发修改导致的不确定性行为。

  • 实现: 基于集合(AbstractList)内部维护的一个"modCount"变量,该变量记录了集合被修改的次数。在每次迭代开始时,迭代器会自己维护一个"expectedModCount"变量,初始值取自集合的"modCount"变量,在迭代过程中,如果"modCount"和"expectedModCount"不一致,就抛出异常。

    public class ArrayList<E> extends AbstractList<E> implements List<E> {
        // ...
    	//继承自AbstractList,写在这里便于理解
        private int modCount = 0;
    
        // ...
    
        public Iterator<E> iterator() {
            return new Itr();
        }
    	//迭代器
        private class Itr implements Iterator<E> {
            int cursor;       // 当前元素的索引
            int expectedModCount = modCount; // 迭代器创建时的集合修改次数
    
            // ...
    
            public E next() {
                checkForComodification(); // 检查是否有并发修改
                // 返回下一个元素
            }
    
            final void checkForComodification() {
                if (modCount != expectedModCount) {
                    throw new ConcurrentModificationException();
                }
            }
        }
        
        // ...
    	//集合的add方法
        public boolean add(E e) {
            modCount++;
            // 添加元素的实现
        }
    
        // ...
    }
    
  • 应用: 主要用于单线程环境,以尽早发现并发修改问题,帮助开发者排除错误。

  • 错误示例

    public static void main(String[] args) {
            List<String> myList = new ArrayList<>();
            myList.add("Item1");
            myList.add("Item2");
            myList.add("Item3");
    
            Iterator<String> iterator = myList.iterator();
            while (iterator.hasNext()) {
                String item = iterator.next(); //2.抛出ConcurrentModificationException
                myList.remove(item); //1.在迭代过程中删除元素
            }
        }
    
  1. Fail-Safe(安全失败)机制:
  • 定义: 允许在迭代过程中对集合进行修改,但不会抛出异常。迭代器仅仅对原始集合的一个快照进行操作,而不会直接操作原始集合。

  • 实现: 通常使用迭代器的副本或者复制的集合来遍历,这样就不会受到原始集合的修改影响。

       //ConcurrentHashMap源码较为繁琐。此处略过
    
  • 应用: 主要用于多线程环境,以确保在遍历集合时不会被其他线程的修改所干扰。

总体来说,选择使用哪种机制取决于具体的应用场景。

  1. 在单线程环境下,fail-fast机制可以帮助尽早发现错误。
  2. 在多线程环境下,fail-safe机制可以提供更好的并发性能和稳定性。在选择时,需要根据应用的需求和性能要求进行权衡。

2.ConcurrentHashMap 为什么不允许有null值?

因无法区分空值和键不存在两种情况

以下仅供参考
HashMap可以有null值,HashMap因为不需要保证多线程环境下的线程安全问题。所以只需要考虑单线程环境下能否区分空值和键不存在两种情况。显然,是可知的
多线程环境下,ConcurrentHashMap无法区分空值和键不存在两种情况,即使当前线程key不为null,也可能在get时被其他线程修改为null

3.为什么不建议双括号"{{}}"初始化集合?

有内存泄漏风险
性能问题

双括号初始化集合是指在集合初始化的时候使用两层花括号的形式,比如:

List<String> list = new ArrayList<String>() {{
   add("item1");
   add("item2");
}};

虽然这种语法可以实现在初始化时直接添加元素,但通常不被推荐使用,原因如下:

  1. 匿名内部类的创建: 双括号初始化实际上创建了一个匿名内部类的实例。每次使用双括号初始化时,都会创建一个新的类,这可能会导致类数量增加,增加类加载和内存开销。

  2. 继承关系: 双括号初始化实际上创建了一个匿名内部类,该类继承了指定集合类。这可能导致继承关系的混乱和不必要的复杂性。

  3. 内存泄漏风险: 由于创建了匿名内部类,如果在初始化时引用了外部类的实例,那么可能会导致对外部类实例的引用,从而增加了内存泄漏的风险。

  4. 性能问题: 双括号初始化的方式可能对性能造成一些微小的影响,尤其是在大型集合的情况下。

相对而言,使用正常的初始化方式更为清晰、简洁,并且没有上述的问题:

List<String> list = new ArrayList<>();
list.add("item1");
list.add("item2");

如果你想在一行代码中初始化和添加元素,可以使用 Arrays.asList

List<String> list = new ArrayList<>(Arrays.asList("item1", "item2"));

这种方式更为简洁,不引入额外的复杂性和潜在的问题。所以,一般来说,推荐使用正常的初始化方式来创建和初始化集合。

文章来源:https://blog.csdn.net/Demo_00/article/details/135428307
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。