List操作的一些常见问题

文章目录

  • 阿里巴巴开发手册强制规约:
  • 1. Arrays.asList转换基本类型数组
  • 2. Arrays.asList返回的List不支持增删操作
  • 3. 对原始数组的修改会影响到我们获得的那个List
  • 4. ArrayList.subList强转ArrayList导致异常
  • 5. ArrayList中的subList切片造成OOM
  • 6.Copy-On-Write 是什么?
  • 7.CopyOnWriteArrayList介绍
  • 8.CopyOnWriteArrayList简单源码解读
  • 9.CopyOnWriteArrayList优缺点
  • 10.CopyOnWriteArrayList使用场景

在这里插入图片描述

阿里巴巴开发手册强制规约:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1. Arrays.asList转换基本类型数组

在实际的业务开发中,我们通常会进行数组转List的操作,通常我们会使用Arrays.asList来进行转换,但是在转换基本类型的数组的时候,却出现转换的结果和我们想象的不一致。

import java.util.Arrays;
import java.util.List;

/**
 * Arrays.asList数组常见问题
 * @author 百里
 */
public class BaiLiTestDemo {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        List list = Arrays.asList(arr);
        System.out.println("list.size:" + list.size());
        for (int i = 0; i < list.size(); i++) {
            System.out.println("循环打印:" + list.get(i));
        }
    }
}

观察下asList的实现,可以看到是入参是使用的是泛型,所以会将{1, 2, 3}三个整数放入一个泛型列表中返回。

public static List asList(T... a) {
    return new ArrayList<>(a); 
}

在这里插入图片描述

那我们该如何解决呢?只需要在声明数组的时候,声明类型改为包装类型。

import java.util.Arrays;
import java.util.List;

/**
 * Arrays.asList数组常见问题
 * @author 百里
 */
public class BaiLiTestDemo {
    public static void main(String[] args) {
        Integer[] arr = {1, 2, 3};
        List list = Arrays.asList(arr);
        System.out.println("list.size:" + list.size());//size = 3
        for (int i = 0; i < list.size(); i++) {
            System.out.println("循环打印:" + list.get(i));
        }
    }
}

这就是第一个坑了,然而Arrays.asList不止这一个需要注意的问题,我们继续往下看:

2. Arrays.asList返回的List不支持增删操作

我们接着上面的demo,增加list加减的逻辑,运行demo会提示UnsupportedOperationException:

import java.util.Arrays;
import java.util.List;

/**
 * Arrays.asList数组常见问题
 * @author 百里
 */
public class BaiLiTestDemo {
    public static void main(String[] args) {
        Integer[] arr = {1, 2, 3};
        List list = Arrays.asList(arr);
        System.out.println("list.size:" + list.size());
        list.add(4);
    }
}

为什么会这样?我们看下asList的实现,它返回的ArrayList是Arrays的内部类,而不是我们通常使用的java.util.ArrayList:
在这里插入图片描述

可以看到内部类中的ArrayList没有add()与remove(),那我们怎么可以使用增减方法呢,继续往下看:
在这里插入图片描述

可以看到ArrayList继承了AbstractList类,我们观察AbstractList类的add()与remove():
在这里插入图片描述

现在是不是就理解Arrays.asList返回的List不支持增删操作了。

3. 对原始数组的修改会影响到我们获得的那个List

基于第一个demo我们继续改造,修改原arr[0]=10,这个时候打印Arrays.asList返回的list值也发生了改变:

import java.util.Arrays;
import java.util.List;

/**
 * Arrays.asList数组常见问题
 * @author 百里
 */
public class BaiLiTestDemo {
    public static void main(String[] args) {
        Integer[] arr = {1, 2, 3};
        List list = Arrays.asList(arr);
        System.out.println("list.size:" + list.size());
        arr[0] = 10;//修改原数组
        for (int i = 0; i < list.size(); i++) {
            System.out.println("循环打印:" + list.get(i));
        }
    }
}

为什么呢?观察ArrayList的实现,可以知道asList创建了 ArrayList,但它直接引用原本的数据组对象。所以只要原本的数组对象一发生变化,List也跟着变化。
在这里插入图片描述
解决方案:new一个新的ArrayList装Arrays.asList返回数据。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Arrays.asList数组常见问题
 * @author 百里
 */
public class BaiLiTestDemo {
    public static void main(String[] args) {
        Integer[] arr = {1, 2, 3};
        List list = new ArrayList<>(Arrays.asList(arr));
        arr[0] = 10;
        for (int i = 0; i < list.size(); i++) {
            System.out.println("循环打印:" + list.get(i));
        }
    }
}

4. ArrayList.subList强转ArrayList导致异常

当使用ArrayList.subList的返回list强转ArrayList时,会出现java.lang.ClassCastException,看以下代码:

import java.util.ArrayList;
import java.util.List;

/**
 * ArrayList.subList常见问题
 * @author 百里
 */
public class BaiLiArrayListDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>() {{
            add("one");
            add("two");
            add("three");
        }};
        ArrayList strings = (ArrayList) names.subList(0, 1);
        System.out.println(strings);
    }
}
Exception in thread "main" java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList
	at BaiLiArrayListDemo.main(BaiLiArrayListDemo.java:15)

同样的,我们看下sublist的实现:
在这里插入图片描述

可以看到SubList()实际上没有创建一个新的List,而是直接引用了原来的List,指定了元素的范围。并且返回的是一个内部类实现的SubList对象,该对象只是原始ArrayList的一个引用,而不是一个全新的ArrayList,因此无法直接将其强制转换为ArrayList类型。
由于是引用的原List,因此也会存在asList的问题,也就是针对subList进行增减数据,会影响原List的值。

import java.util.ArrayList;
import java.util.List;

/**
 * ArrayList.subList常见问题
 * @author 百里
 */
public class BaiLiArrayListDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>() {{
            add("one");
            add("two");
            add("three");
        }};
        List  strings = names.subList(0, 1);
        strings.add(0,"four");
        System.out.println(strings);//[four, one]
        System.out.println(names);//[four, one, two, three]
    }
}

需要注意修改原List-names的值会出导致strings的遍历、增加、删除产生ConcurrentModificationException异常。

import java.util.ArrayList;
import java.util.List;

/**
 * ArrayList.subList常见问题
 * @author 百里
 */
public class BaiLiArrayListDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>() {{
            add("one");
            add("two");
            add("three");
        }};
        List strings = names.subList(0, 1);
        names.add("four");
        System.out.println(strings);
        System.out.println(names);
    }
}
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231)
	at java.util.ArrayList$SubList.listIterator(ArrayList.java:1091)
	at java.util.AbstractList.listIterator(AbstractList.java:299)
	at java.util.ArrayList$SubList.iterator(ArrayList.java:1087)
	at java.util.AbstractCollection.toString(AbstractCollection.java:454)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at BaiLiArrayListDemo.main(BaiLiArrayListDemo.java:17)

上面问题的解决方案跟asList同样,直接new一个新的ArrayList装Arrays.subList返回数据就可以了。

import java.util.ArrayList;
import java.util.List;

/**
 * ArrayList.subList常见问题
 * @author 百里
 */
public class BaiLiArrayListDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>() {{
            add("one");
            add("two");
            add("three");
        }};
        List strings = new ArrayList<>(names.subList(0, 1));
        strings.add("four");
        System.out.println(strings);//[one, four]
        System.out.println(names);//[one, two, three]
    }
}

5. ArrayList中的subList切片造成OOM

subList所产生的List,其实是对原来List对象的引用,这个产生的List只是原来List对象的视图,也就是说虽然值切片获取了一小段数据,但是原来的List对象却得不到回收,如果这个原来的对象很大,就会出现OOM的情况。我们将VM参数调小:-Xms20m -Xmx40m

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * ArrayList.subList常见问题
 * @author 百里
 */
public class BaiLiArrayListDemo {
    public static void main(String[] args) {
        List data = new ArrayList<>();
        IntStream.range(0, 1000).forEach(i ->{
            List<Integer> collect = 
                    IntStream.range(0, 100000).boxed().
                            collect(Collectors.toList());
            data.add(collect.subList(0, 1));
        });
    }
}

出现OOM的原因:原数组无法被回收,会一直在内存中。
解决方案:new一个新的ArrayList接收subList返回。

6.Copy-On-Write 是什么?

Copy-On-Write它是一种在计算机科学中常见的优化技术,主要应用于需要频繁读取但很少修改的数据结构上。
简单的说就是在计算机中就是当你想要对一块内存进行修改时,我们不在原有内存块中进行写操作,而是将内存拷贝一份,在新的内存中进行写操作,写完之后呢,就将指向原来内存指针指向新的内存,原来的内存就可以被回收掉了!
既然是一种优化策略,我们看一段代码:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
* @author 百里
*/
public class BaiLiIteratorTest {
    private static List<String> list = new ArrayList<>();

    public static void main(String[] args) {
        list.add("1");
        list.add("2");
        list.add("3");
        Iterator<String> iter = list.iterator();
        while (iter.hasNext()) {
            System.err.println(iter.next());
        }
        System.err.println(Arrays.toString(list.toArray()));
    }
}

上面的Demo在单线程下执行时没什么毛病,但是在多线程的环境中,就可能出异常,为什么呢?
因为多线程迭代时如果有其他线程对这个集合list进行增减元素,会抛出java.util.ConcurrentModificationException的异常。
我们以增加元素为例子,运行下面这Demo:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 并发迭代器问题示例代码
 * @author 百里
 */
public class BaiLiConcurrentIteratorTest {
    // 创建一个ArrayList对象
    private static List<String> list = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        // 给ArrayList添加三个元素:"1"、"2"和"3"
        list.add("1");
        list.add("2");
        list.add("3");

        // 开启线程池,提交10个线程用于在list尾部添加5个元素"121"
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            service.execute(() -> {
                for (int j = 0; j < 5; j++) {
                    list.add("121");
                }
            });
        }

        // 使用Iterator迭代器遍历list并输出元素值
        Iterator<String> iter = list.iterator();
        for (int i = 0; i < 10; i++) {
            service.execute(() -> {
                while (iter.hasNext()) {
                    System.err.println(iter.next());
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        service.shutdown();
    }
}

这里暴露的问题是什么呢?
● 多线程场景下迭代器遍历集合的读取操作和其他线程对集合进行写入操作会导致出现并发修改异常
解决方案:
● CopyOnWriteArrayList避免了多线程操作List线程不安全的问题

7.CopyOnWriteArrayList介绍

从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。
CopyOnWriteArrayList原理:
在写操作(add、remove等)时,不直接对原数据进行修改,而是先将原数据复制一份,然后在新复制的数据上执行写操作,最后将原数据引用指向新数据。这样做的好处是读操作(get、iterator等)可以不加锁,因为读取的数据始终是不变的。
接下来我们就看下源码怎么实现的。

8.CopyOnWriteArrayList简单源码解读

add()方法源码:

/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
    final ReentrantLock lock = this.lock;//重入锁
	lock.lock();//加锁啦
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);//拷贝新数组
        newElements[len] = e;
        setArray(newElements);//将引用指向新数组  1
        return true;
    } finally {
        lock.unlock();//解锁啦
    }
}

可以看到,CopyOnWriteArrayList中的写操作都需要先获取锁,然后再将当前的元素数组复制一份,并在新复制的元素数组上执行写操作,最后将数组引用指向新数组。

@SuppressWarnings("unchecked")
public E next() {
    if (! hasNext()) //是否存在下一个元素
        throw new NoSuchElementException(); //没有下一个元素,则会抛出NoSuchElementException异常
    //snapshot是一个类成员变量,它是在创建迭代器时通过复制集合内容而获得的一个数组。
    //cursor是另一个类成员变量,初始值为0,并在每次调用next()时自增1,表示当前返回元素的位置。
    return (E) snapshot[cursor++];
}

而读操作不需要加锁,直接返回当前的元素数组即可。
这种写时复制的机制保证了读操作的线程安全性,但是会牺牲一些写操作的性能,因为每次修改都需要复制一份数组。因此,适合读远多于写的场合。
所以我们将多线程Demo中的ArrayList改为CopyOnWriteArrayList,执行就不会报错啦!

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* 并发迭代器问题示例代码
* @author 百里
*/
public class BaiLiConcurrentIteratorTest {
    // 创建一个ArrayList对象
    private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        // 给ArrayList添加三个元素:"1"、"2"和"3"
        list.add("1");
        list.add("2");
        list.add("3");

        // 开启线程池,提交10个线程用于在list尾部添加5个元素"121"
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            service.execute(() -> {
                for (int j = 0; j < 5; j++) {
                    list.add("121");
                }
            });
        }

        // 使用Iterator迭代器遍历list并输出元素值
        Iterator<String> iter = list.iterator();
        for (int i = 0; i < 10; i++) {
            service.execute(() -> {
                while (iter.hasNext()) {
                    System.err.println(iter.next());
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        service.shutdown();
    }
}

9.CopyOnWriteArrayList优缺点

优点:

  1. 线程安全。CopyOnWriteArrayList是线程安全的,由于写操作对原数据进行复制,因此写操作不会影响读操作,读操作可以不加锁,降低了并发冲突的概率。
  2. 不会抛出ConcurrentModificationException异常。由于读操作遍历的是不变的数组副本,因此不会抛出ConcurrentModificationException异常。

缺点:

  1. 写操作性能较低。由于每一次写操作都需要将元素复制一份,因此写操作的性能较低。
  2. 内存占用增加。由于每次写操作都需要创建一个新的数组副本,因此内存占用会增加,特别是当集合中有大量数据时,内存占用较高。
  3. 数据一致性问题。由于读操作遍历的是不变的数组副本,因此在对数组执行写操作期间,读操作可能读取到旧的数组数据,这就涉及到数据一致性问题。

10.CopyOnWriteArrayList使用场景

● 读多写少。为什么?因为写的时候会复制新集合
● 集合不大。为什么?因为写的时候会复制新集合
● 实时性要求不高。为什么,因为有可能会读取到旧的集合数据

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/174389.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

上海亚商投顾:北证50指数大涨 机器人概念股掀涨停潮

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 三大指数昨日震荡反弹&#xff0c;黄白二线有所分化&#xff0c;题材热点轮动表现。北证50指数大涨超3%&#…

「MACOS限定」 如何将文件上传到GitHub仓库

介绍 本期讲解&#xff1a;如何在苹果电脑上上传文件到github远程仓库 注&#xff1a;写的很详细 方便我的朋友可以看懂操作步骤 第一步 在电脑上创建一个新目录&#xff08;文件夹&#xff09; 注&#xff1a;创建GitHub账号、新建github仓库、git下载的步骤这里就不过多赘…

股票统计信息(七)

7-统计信息 文章目录 7-统计信息一. 股票周级别统计信息二. 查询可支持的所有的股票资金类型三. 股票图形统计信息四. 查询当前用户自选表里面最近十天的交易信息五. 查看天/星期范围统计的历史记录六. 查看最近多少天某个属性的涨跌幅度值 一. 股票周级别统计信息 接口描述: …

使用Python处理ADC激光测距数据并绘制为图片(二)

使用Python处理ADC激光测距数据并绘制为图片 说明一、定义全局变量变二、保存和清空原始数据三、拆分原始数据为键值对四、获取标题、FigText、更新统计信息文件五、生成图片六、处理原始数据文件七、主函数入口八、测试结果 说明 1. 主要是将ADC激光测距叠加后的1024Byte数据绘…

【码神之路】【Golang】博客网站的搭建【学习笔记整理 持续更新...】

介绍 一个用原生GO开发的博客网站&#xff0c;涉及Golang Web开发、Web服务器搭建和HTTP请求处理、模板与静态资源处理等 技术栈 后端&#xff1a;Go、Go并发机制前端&#xff1a;HTML模版链接直达 Golang搭建博客网站的学习视频 注&#xff1a;这里我只记录我实质✅学习到…

PyCharm玩转ESP32

想必玩ESP32的童鞋都知道Thonny&#xff0c;当然学Python的童鞋用的更多的可能是PyCharm和VsCode Thonny和PyCharm的对比 对于PyCharm和VsCode今天不做比较&#xff0c;今天重点说一下用PyCharm玩转ESP32&#xff0c;在这之前我们先对比下Thonny和PyCharm的优缺点 1、使用Tho…

引迈-JNPF低代码项目技术栈介绍

从 2014 开始研发低代码前端渲染&#xff0c;到 2018 年开始研发后端低代码数据模型&#xff0c;发布了JNPF开发平台。 谨以此文针对 JNPF-JAVA-Cloud微服务 进行相关技术栈展示&#xff1a; 1. 项目前后端分离 前端采用Vue.js&#xff0c;这是一种流行的前端JavaScript框架&a…

webpack 配置

1、基础配置 // node js核心模塊 const path require(path) // 插件是需要引入使用的 const ESLintPlugin require(eslint-webpack-plugin) // 自动生成index.html const HtmlWebpackPlugin require(html-webpack-plugin); // 将css文件单独打包&#xff0c;在index.html中…

【开源】基于Vue.js的教学过程管理系统

项目编号&#xff1a; S 054 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S054&#xff0c;文末获取源码。} 项目编号&#xff1a;S054&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 教师端2.2 学生端2.3 微信小程序端2…

iOS越狱检测总结

文章目录 前言检测越狱文件私有目录检测检测越狱软件检测系统目录是否变为链接动态库检测环境变量检测系统调用检测指令集调用检测其他方式检测 前言 在之前的文章中&#xff0c;已经带大家一起制作了一个屏蔽越狱检测的Tweak。本文就和大家一起学习整理一下iOS系统中有哪些越…

scala的schema函数(算子)

在翻阅一些代码的时候&#xff0c;schema算子好像没碰到过&#xff0c;比较好奇structField这个类型&#xff0c;为什么可以直接用name参数&#xff0c;就翻阅了下资料&#xff1a; 在 Apache Spark 中&#xff0c;DataFrame 是一种分布式的数据集&#xff0c;它是以类似于关系…

电脑便签功能在哪里找?电脑桌面便签怎么添加?

很多上班族在使用电脑办公的时候&#xff0c;都需要随手记录工作事项&#xff0c;例如记录共同工作时的想法、会议笔记、常用工作资料、每天待办的工作任务等事项&#xff0c;这时候使用纸质的笔记本来记录工作&#xff0c;不仅不方便随时查看和使用&#xff0c;而且在修改、删…

基于SSM的校内互助交易平台设计

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

ppt录屏制作微课,轻松打造精品课程

微课作为一种新型的教学方式逐渐受到广大师生的欢迎。微课具有方便快捷、内容丰富、互动性强等特点&#xff0c;可以有效地帮助教师传达知识&#xff0c;提高学生的学习效果。其中&#xff0c;ppt录屏制作微课就是一种常见的方式。本文将介绍ppt录屏的使用方法&#xff0c;帮助…

Double 4 VR智能互动系统在轨道交通实训教学中的应用

Double 4 VR智能互动系统是一种集成了虚拟现实技术、人工智能和物联网技术的教学系统。计算机通过模拟真实的轨道交通环境&#xff0c;为学生提供了一个高度仿真的学习环境&#xff0c;帮助他们更好地理解和掌握轨道交通的相关知识和技能。 首先&#xff0c;Double 4 VR智能互动…

注解案例:山寨Junit与山寨JPA

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 上篇讲了什么是注解&am…

注册中心CAP架构剖析

Nacos 支持 AP 或 CP AP Nacos 通过临时节点实现 AP 架构&#xff0c;将服务列表放在内存中&#xff1b; CP Nacos 通过持久化节点实现 CP 架构&#xff0c;将服务列表放在文件中&#xff0c;并同步到内存&#xff0c;通过 Raft 协议算法实现&#xff1b; 通过配置 epheme…

集中签约 深算院YashanDB关键行业商用提速

11月17日&#xff0c;深圳计算科学研究院在2023深圳企业创新发展大会主论坛上&#xff0c;与金融、能源和交通等关键行业的7家标杆企业签约&#xff0c;签约总金额近亿元&#xff0c;标志着YashanDB在重点行业的商用落地迈入全面提速阶段。 会上&#xff0c;深圳计算科学研究院…

人工智能的时代---AI的影响

人工智能&#xff08;AI&#xff09;是当前科技领域的一个热门话题&#xff0c;它正在以前所未有的速度改变着我们的生活方式和工作方式。从智能家居到自动驾驶&#xff0c;从智能医疗到智能金融&#xff0c;人工智能正在渗透到我们生活的方方面面。在这篇文章中&#xff0c;我…

C语言基础---函数、数组

目录 一、函数 二、数组​ 一、函数 交换两个数&#xff1a; 发现这样并没有交换a和b的值&#xff0c;只是交换了x和y的值&#xff0c;这是因为&#xff1a; //当实参传递给形参的时候&#xff0c;形参是实参的一份临时拷贝 //对形参的修改不能改变实参 实参与形参是…