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);   //  [《你好,李焕英》, 《喜羊羊与灰太狼》, 《神雕侠侣》, 《三国演义》, 《射雕英雄传》]
        }
    }
    
    [《你好,李焕英》, 《喜羊羊与灰太狼》, 《神雕侠侣》, 《神雕侠侣》, JavaSE基础, 《三国演义》, JavaSE基础]
    -------------------------------------
    删除索引为4的元素:JavaSE基础
    删除索引为2的元素:《神雕侠侣》
    [《你好,李焕英》, 《喜羊羊与灰太狼》, 《神雕侠侣》, 《三国演义》, JavaSE基础]
    -------------------------------------
    获取索引为0的元素:《你好,李焕英》
    获取索引为3的元素:《三国演义》
    -------------------------------------
    修改索引为3的元素:JavaSE基础
    [《你好,李焕英》, 《喜羊羊与灰太狼》, 《神雕侠侣》, 《三国演义》, 《射雕英雄传》]
    
    Process finished with exit code 0
    
    

总结

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、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



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<String> 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);
    }
}
[第4颗子弹, 第3颗子弹, 第2颗子弹, 第1颗子弹]
第4颗子弹已打出~~
第3颗子弹已打出~~
第2颗子弹已打出~~
[第1颗子弹]
--------------------------------
[第1个人, 第2个人, 第3个人, 第4个人]
第1个人已出队~~
第2个人已出队~~
第3个人已出队~~
[第4个人]

Process finished with exit code 0





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

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


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

哪些遍历存在问题?
  • 迭代器、增强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<String> 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);
    }
}
[biubiu~~, biubiu~~, 甄子丹, 甄子丹, JavaSE]
[biubiu~~, biubiu~~, JavaSE]

Process finished with exit code 0



结论:

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

  • 迭代器遍历集合,但是要用迭代器自己的删除方法操作可以解决。
  • 使用for循环遍历集合,做一些简单处理就可以解决了。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值