• 75-Java的List系列集合、集合的并发修改异常问题


    一、List系列集合

    在这里插入图片描述



    1、List集合特点、特有API

    • ArrayList、LinkedList:有序、可重复、有索引:
      • 有序:存储和取出的元素顺序一致;
      • 有索引:可以通过索引操作元素;
      • 可重复:存储的元素可以重复。

    • List集合因为支持索引,所以多了很多索引操作的独特API,其他Collection的功能List也都能继承了。

      方法说明
      void add(int index, E element)在此集合中的指定位置插入指定的元素
      E remove(int index)删除指定索引处的元素,返回被删除的元素
      E set(int index, E element)修改指定索引处的元素,返回被修改的元素
      E get(int index)返回指定索引处的元素
      package com.app.d5_collection_list;
      
      import java.util.ArrayList;
      import java.util.List;
      
      /**
          目标:掌握List集合特有的方法:API
       */
      public class ListDemo1 {
          public static void main(String[] args) {
      //         1、创建一个ArrayList集合
              // List: 有序、可重复、有索引
              // 多态写法
              List<String> list = new ArrayList<>();
              list.add("《你好,李焕英》");
              list.add("《喜羊羊与灰太狼》");
              list.add("《神雕侠侣》");
              list.add("《神雕侠侣》");
              list.add("JavaSE基础");
              list.add("JavaSE基础");
      
      //         2、根据指定索引处插入指定的元素
              list.add(5, "《三国演义》");
      
              System.out.println(list);   //  [《你好,李焕英》, 《喜羊羊与灰太狼》, 《神雕侠侣》, 《神雕侠侣》, JavaSE基础, 《三国演义》, JavaSE基础]
      
      
              System.out.println("-------------------------------------");
      //         3、删除指定索引处的元素,返回被删除的元素
              System.out.println("删除索引为4的元素:" + list.remove(4));  // JavaSE基础
              System.out.println("删除索引为2的元素:" + list.remove(2));  // 《神雕侠侣》
      
              System.out.println(list);   //  [《你好,李焕英》, 《喜羊羊与灰太狼》, 《神雕侠侣》, 《三国演义》, JavaSE基础]
      
      
              System.out.println("-------------------------------------");
      //         4、获取指定索引处的元素,返回被指定的元素
              System.out.println("获取索引为0的元素:" + list.get(0));     // 《你好,李焕英》
              System.out.println("获取索引为3的元素:" + list.get(3));     // 《三国演义》
      
      
              System.out.println("-------------------------------------");
      //         5、修改指定索引处的元素
              System.out.println("修改索引为3的元素:" + list.set(4, "《射雕英雄传》"));
      
              System.out.println(list);   //  [《你好,李焕英》, 《喜羊羊与灰太狼》, 《神雕侠侣》, 《三国演义》, 《射雕英雄传》]
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      [《你好,李焕英》, 《喜羊羊与灰太狼》, 《神雕侠侣》, 《神雕侠侣》, JavaSE基础, 《三国演义》, JavaSE基础]
      -------------------------------------
      删除索引为4的元素:JavaSE基础
      删除索引为2的元素:《神雕侠侣》
      [《你好,李焕英》, 《喜羊羊与灰太狼》, 《神雕侠侣》, 《三国演义》, JavaSE基础]
      -------------------------------------
      获取索引为0的元素:《你好,李焕英》
      获取索引为3的元素:《三国演义》
      -------------------------------------
      修改索引为3的元素:JavaSE基础
      [《你好,李焕英》, 《喜羊羊与灰太狼》, 《神雕侠侣》, 《三国演义》, 《射雕英雄传》]
      
      Process finished with exit code 0
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

    总结

    1、List系列集合的特点是什么?

    • ArrayList、LinkedList:有序、可重复、有索引

    2、List的实现类的底层原理?

    • ArrayList底层是基于数组实现的,根据索引查询元素快,增删相对慢(理论上)
    • LinkedList底层基于双链表实现的,查询元素慢,增删首尾元素是非常快的


    2、List集合的遍历方式小结

    • 迭代器(Iterator)

    • 增强for循环(foreach)

    • Lambda表达式

    • for循环(因为List集合存在索引)


    package com.app.d5_collection_list;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.LinkedList;
    import java.util.List;
    
    /**
        目标:总结List系列集合的遍历方式
     */
    public class ListDemo2 {
        public static void main(String[] args) {
            // 多态写法
            List<String> list = new ArrayList<>();
            list.add("JavaSE01");
            list.add("JavaSE02");
            list.add("JavaSE03");
    
    //        1、for循环遍历(List系列集合独有的遍历方式)
            System.out.println("1、for循环遍历(List系列集合独有的遍历方式):");
            for (int i = 0; i < list.size(); i++) {
                String ele = list.get(i);
                System.out.println(ele);
            }
    
    
            System.out.println("---------------------------");
    //        2、迭代器(Iterator)
            System.out.println("2、迭代器(Iterator):");
            Iterator<String> it = list.iterator();
            while (it.hasNext()) {
                String ele = it.next();
                System.out.println(ele);
            }
    
    
            System.out.println("---------------------------");
    //        3、增强for循环(foreach)
            System.out.println("3、增强for循环(foreach):");
            for (String ele : list) {
                System.out.println(ele);
            }
    
    
            System.out.println("---------------------------");
    //        4、JDK 1.8之后的Lambda表达式
            System.out.println("4、Lambda表达式:");
            list.forEach(
                    ele -> System.out.println(ele)
            );
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    1、for循环遍历(List系列集合独有的遍历方式):
    JavaSE01
    JavaSE02
    JavaSE03
    ---------------------------
    2、迭代器(Iterator):
    JavaSE01
    JavaSE02
    JavaSE03
    ---------------------------
    3、增强for循环(foreach):
    JavaSE01
    JavaSE02
    JavaSE03
    ---------------------------
    4、Lambda表达式:
    JavaSE01
    JavaSE02
    JavaSE03
    
    Process finished with exit code 0
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22


    3、ArrayList集合的底层原理

    • ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。

    • 第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。

    在这里插入图片描述


    在这里插入图片描述




    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述



    • List集合存储的元素要超过容量怎么办?

    在这里插入图片描述


    在这里插入图片描述




    在这里插入图片描述


    在这里插入图片描述



    思考:为何ArrayList查询快、增删元素相对较慢?
    • 因为,在中间插入元素时,需要进行元素个数+1的操作,后面所有的元素往后迁移,留出空位给新插入的元素;
    • 在中间删除元素时,需要进行元素个数-1的操作,后面所有的元素往前迁移,补齐空位。



    4、LinkedList集合的底层原理

    (1)LinkedList的特点
    • LinkedList集合底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API。

    在这里插入图片描述


    (2)LinkedList集合的特有方法(API)
    方法说明
    public void addFirst(E e)在此列表开头插入指定的元素
    public void addLast(E e)将指定的元素追加到此列表的末尾
    public E getFirst()返回此列表中的第一个元素
    public E getLast()返回此列表中的最后一个元素
    public E removeFirst()从此列表中删除并返回第一个元素
    Public E removeLast()从此列表中删除并返回最后一个元素
    package com.app.d5_collection_list;
    
    import java.util.LinkedList;
    import java.util.List;
    
    /**
        目标:掌握List系列集合的 LinkedList集合的特有方法(API)
     */
    public class ListDemo3 {
        public static void main(String[] args) {
    //         1、创建一个LinkedList集合
            // 它可以完成队列、栈结构(双链表)
            // 栈:后进先出,先进后出
            // 因为需要用它自己独有的方法,因此不适合多态写法
            // List list = new LinkedList<>();
            LinkedList<String> stack = new LinkedList<>();
            // a、上弹(压栈,入栈)
            // 压栈,入栈API:public void push(E e):方法内部包装了一个 addFirst()方法,效果一样
            stack.push("第1颗子弹");
            stack.push("第2颗子弹");    // 但是入栈用push,显得专业、B格高
            stack.push("第3颗子弹");
            stack.addFirst("第4颗子弹");
            System.out.println(stack);
    
            // b、打出子弹(弹栈,出栈)
            // 弹栈,出栈API:public E pop():方法内部包装了一个 removeFirst()方法,效果一样
            System.out.println(stack.pop() + "已打出~~");      // 但是出栈用pop,显得专业、B格高
            System.out.println(stack.pop() + "已打出~~");
            System.out.println(stack.removeFirst() + "已打出~~");
    
            // c、弹夹中剩余子弹
            System.out.println(stack);
    
    
            System.out.println("--------------------------------");
            // 队列:后进后出,先进先出
            LinkedList<String> queue = new LinkedList<>();
            // a、入队
            queue.addLast("第1个人");
            queue.addLast("第2个人");
            queue.addLast("第3个人");
            queue.addLast("第4个人");
            System.out.println(queue);
    
            // b、出队
            System.out.println(queue.removeFirst() + "已出队~~");
            System.out.println(queue.removeFirst() + "已出队~~");
            System.out.println(queue.removeFirst() + "已出队~~");
    
            // c、队列中剩余人数
            System.out.println(queue);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    [第4颗子弹, 第3颗子弹, 第2颗子弹, 第1颗子弹]
    第4颗子弹已打出~~
    第3颗子弹已打出~~
    第2颗子弹已打出~~
    [第1颗子弹]
    --------------------------------
    [第1个人, 第2个人, 第3个人, 第4个人]
    第1个人已出队~~
    第2个人已出队~~
    第3个人已出队~~
    [第4个人]
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13





    二、集合的并发修改异常问题(补充知识)

    从集合中的一批元素中找出某些数据并删除,如何操作?是否存在问题?


    问题引出:
    • 当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题。

    哪些遍历存在问题?
    • 迭代器、增强for循环遍历集合,并且直接用集合删除元素的时候可能出现。

      在这里插入图片描述


    package com.app.d6_collection_update_delete;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    /**
        目标:学会解决集合的并发修改异常
     */
    public class Test {
        public static void main(String[] args) {
    //         1、创建一个ArrayList集合
            // 多态写法
            List<String> list = new ArrayList<>();
            list.add("biubiu~~");
            list.add("biubiu~~");
            list.add("甄子丹");
            list.add("甄子丹");
            list.add("JavaSE");
            System.out.println(list);   // [biubiu~~, biubiu~~, 甄子丹, 甄子丹, JavaSE]
    
    //        2、需求:删除全部 ”甄子丹”
    //         a、迭代器
            // 获取迭代器
            /*Iterator it = list.iterator();
            // 问一下有没有数据?
            while (it.hasNext()) {
                // 有数据,存一下
                String ele = it.next();
                if ("甄子丹".equals(ele)) {     // 问一下这个数据 是否与 “甄子丹” 相等??
                    // 相同,删除掉
                    // bug: 迭代器直接使用集合的方法删除,会出现并发修改异常错误,但是可以解决
                    // list.remove("甄子丹");
    
                    // 解决方案:直接使用迭代器的方法删除,因为它的方法在底层做了处理
                    it.remove();
                }
            }
            System.out.println(list);   // [biubiu~~, biubiu~~, JavaSE]*/
    
    
    //        b、增强for循环(foreach)
            /*for (String ele : list) {
                if ("甄子丹".equals(ele)) {
                    // bug: foreach直接使用集合的方法删除,会出现并发修改异常错误,无法解决
                    // 因为foreach的底层也是for循环,并且没有做过处理,因此无法解决
                    list.remove(ele);
                }
            }*/
    
    
    //        c、Lambda表达式(forEach)
            /*list.forEach(ele -> {
                if ("甄子丹".equals(ele)) {
                    // bug: forEach直接使用集合的方法删除,会出现并发修改异常错误,无法解决
                    // 因为forEach的底层也是for循环,并且没有做过处理,因此无法解决
                    list.remove(ele);
                }
            });*/
    
    
    //        d、for循环(for循环直接使用集合的方法删除,不会出现并发修改异常错误,但是也存在bug(会漏掉数据),不过有解决方案)
            /*for (int i = 0; i < list.size(); i++) {
                String ele = list.get(i);
                if ("甄子丹".equals(ele)) {
                    // bug: for循环直接使用集合的方法删除,不会出现并发修改异常错误
                    // 但是也存在bug(会漏掉数据),不过有解决方案
                    // 因为如果集合中有许多个 “甄子丹”,删除一个,下一个就会上来,但是i已经往前++了,因此会漏掉数据
                    list.remove(ele);
                }
            }
            System.out.println(list);*/
    
    //        解决方案1:倒着删除
            /*
                list.size-1 : 集合个数-1(最后一个位置)
                i >= 0 : 判断条件(必须大于等于0)
                i-- : 倒着走
             */
            /*for (int i = list.size()-1; i >= 0; i--) {
                String ele = list.get(i);
                if ("甄子丹".equals(ele)) {
                    list.remove(ele);
                }
            }
            System.out.println(list);*/
    
    
    //         解决方案2:每删除掉一个甄子丹,就往后移一个位置
            for (int i = 0; i < list.size(); i++) {
                String ele = list.get(i);
                if ("甄子丹".equals(ele)) {
                    list.remove(ele);
                    i--;    // 每删除掉一个甄子丹,往后移一个位置
                }
            }
            System.out.println(list);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    [biubiu~~, biubiu~~, 甄子丹, 甄子丹, JavaSE]
    [biubiu~~, biubiu~~, JavaSE]
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4



    结论:

    哪种遍历删除元素不会出现bug?

    • 迭代器遍历集合,但是要用迭代器自己的删除方法操作可以解决。
    • 使用for循环遍历集合,做一些简单处理就可以解决了。
  • 相关阅读:
    [SpringBoot系列]进阶配置
    linux 容量调整
    PIE-engine 教程 ——动态展示全国2000-2020年人口变化信息
    stm32——hal库学习笔记(ADC)
    Java 类加载机制
    四川大学2023考研真题复习资料可以找学长学姐吗?
    分布式日志部署
    束从轩的“网红人设”,正在加重老乡鸡的割裂
    Linux系统编程(七)网络编程TCP、UDP
    [Unity] GPU动画实现(二)——网格合并
  • 原文地址:https://blog.csdn.net/yelitoudu/article/details/126236507