栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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;
    
    
    public class ListDemo1 {
        public static void main(String[] args) {
    //         1、创建一个ArrayList集合
            // List: 有序、可重复、有索引
            // 多态写法
            List 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;


public class ListDemo2 {
    public static void main(String[] args) {
        // 多态写法
        List 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 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;


public class ListDemo3 {
    public static void main(String[] args) {
//         1、创建一个LinkedList集合
        // 它可以完成队列、栈结构(双链表)
        // 栈:后进先出,先进后出
        // 因为需要用它自己独有的方法,因此不适合多态写法
        // List list = new LinkedList<>();
        LinkedList 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 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 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、迭代器
        // 获取迭代器
        


//        b、增强for循环(foreach)
        


//        c、Lambda表达式(forEach)
        


//        d、for循环(for循环直接使用集合的方法删除,不会出现并发修改异常错误,但是也存在bug(会漏掉数据),不过有解决方案)
        

//        解决方案1:倒着删除
        
        


//         解决方案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循环遍历集合,做一些简单处理就可以解决了。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1041158.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号