List操作的一些常见问题
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 = 3for (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.ArrayListat 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.ConcurrentModificationExceptionat 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);//将引用指向新数组 1return 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优缺点
优点:
- 线程安全。CopyOnWriteArrayList是线程安全的,由于写操作对原数据进行复制,因此写操作不会影响读操作,读操作可以不加锁,降低了并发冲突的概率。
- 不会抛出ConcurrentModificationException异常。由于读操作遍历的是不变的数组副本,因此不会抛出ConcurrentModificationException异常。
缺点:
- 写操作性能较低。由于每一次写操作都需要将元素复制一份,因此写操作的性能较低。
- 内存占用增加。由于每次写操作都需要创建一个新的数组副本,因此内存占用会增加,特别是当集合中有大量数据时,内存占用较高。
- 数据一致性问题。由于读操作遍历的是不变的数组副本,因此在对数组执行写操作期间,读操作可能读取到旧的数组数据,这就涉及到数据一致性问题。
10.CopyOnWriteArrayList使用场景
- 读多写少。为什么?因为写的时候会复制新集合
- 集合不大。为什么?因为写的时候会复制新集合
- 实时性要求不高。为什么,因为有可能会读取到旧的集合数据
相关文章:
List操作的一些常见问题
1. Arrays.asList转换基本类型数组 在实际的业务开发中,我们通常会进行数组转List的操作,通常我们会使用Arrays.asList来进行转换,但是在转换基本类型的数组的时候,却出现转换的结果和我们想象的不一致。 import java.util.Arra…...
如何使用Java和RabbitMQ实现延迟队列?
前言 今天我们使用Java和RabbitMQ实现消息队列的延迟功能。 前期准备,需要安装好docker、docker-compose的运行环境。 需要安装RabbitMQ的可以看下面这篇文章。 如何使用PHP和RabbitMQ实现消息队列?-CSDN博客 今天讲的是依赖RabbitMQ的延迟插件实现…...
AI论文速读 | TF-LLM:基于大语言模型可解释性的交通预测
论文标题: Explainable Traffic Flow Prediction with Large Language Models 作者:Xusen Guo, Qiming Zhang, Mingxing Peng, Meixin Zhu(朱美新)*, Hao (Frank)Yang(杨昊) 机构:香港科技大学(广州),约翰…...
智慧矿山视频智能监控与安全监管方案
一、行业背景 随着全球能源需求的日益增长,矿业行业作为国民经济的重要支柱,其发展日益受到广泛关注。然而,传统矿山管理模式的局限性逐渐显现,如生产安全、人员监管、风险预警等方面的问题日益突出。因此,智慧矿山智…...
2024春算法训练4——函数与递归题解
一、前言 感觉这次的题目都很好,但是E题....(我太菜了想不到),别人的题解都上百行了,晕; 二、题解 A-[NOIP2010]数字统计_2024春算法训练4——函数与递归 (nowcoder.com) 这种题目有两种做法:…...
【C++】C++知识点复习
牛客cpp:牛客网在线编程 2024年4月10日:BC1—>BC8 BC4:浮点数精度保留 问题:不加入fixed输入0.359813,最后得到0.36,并不是强制保留0.360。这种写法会保留小数点后三位精度,但是最后输出会省略掉最后…...
SpringBoot+Vue,轻松实现网页版人脸登录与精准识别
目录 1、技术介绍 2、技术原理 2.1、人脸检测 ①参考模板法 ②人脸规则法 2.2、人脸跟踪 2.3、人脸比对 ①特征向量法 ②面纹模板法 识别过程 案例 一、springboot后端项目 1,拉取项目后,导入相关依赖jar包 2,执行sql文件夹下面…...
深入浅出 -- 系统架构之垂直架构
当业务复杂度增加、访问量逐渐增大出现高并发时,单体架构无法满足需求,可以根据业务功能对系统进行拆分,以提高访问效率。 垂直架构介绍 1.垂直架构一般是因为单体架构太过于庞大而进行的拆分,拆分后各个系统应满足独立运行互相不…...
深入浅出 -- 系统架构之微服务架构选型参考图
技术选型架构图 是一个用于展示项目中所采用的各种技术和组件之间关系的图表。 它通常包括以下几个部分: 1. 项目名称和描述:简要介绍项目的背景和目标。 2. 技术栈:列出项目中使用的主要技术和工具,如编程语言、框架、数据库…...
Java 使用 ant.jar 执行 SQL 脚本文件
Java 使用 ant.jar 执行 SQL 脚本文件,很简单。 在 pom.xml 中导入 ant 依赖 <dependency><groupId>org.apache.ant</groupId><artifactId>ant</artifactId><version>1.10.11</version> </dependency>sql 脚本文件…...
【随笔】Git 高级篇 -- 快速定位分支 ^|~(二十三)
💌 所属专栏:【Git】 😀 作 者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! 💖 欢迎大…...
git环境切换
文章目录 一. 操作步骤:1.查看全局设置3.Git 切换本地git设置4.切换仓库并推送 一. 操作步骤: 1.查看全局设置 $ Git config --global --list credential.https://codeup.aliyun.com.providergeneric user.namebiejiahao user.emailxxxxxxxxqq.com3.Gi…...
hyperf websocket
composer require hyperf/websocket-server 配置 Server 修改 config/autoload/server.php,增加以下配置。 <?phpreturn [servers > [[name > ws,type > Server::SERVER_WEBSOCKET,host > 0.0.0.0,port > 9502,sock_type > SWOOLE_SOCK_TCP…...
用Echarts词云数据可视化热词表白
目录 1、使用前准备 2、准备工作 3、盒子搭建 4、整体展现 1、使用前准备 找到表白对象(重中之重!),不要一见钟情(个人觉得:一见钟情属于见色起意!),因为数据可视化需…...
VUE 实现路由的基本原理
路由 基本概念 在前端技术早期,所有页面的跳转通过更改url,浏览器页面刷新获取新的页面内容,这种粗糙的交互方式,一直等待优化。 后来,改变发生了——Ajax 出现了,它允许人们在不刷新页面的情况下发起请求࿰…...
Android 11 添加系统属性
在初识Android 属性一文中提到,系统会默认加载以下文件 /system/etc/prop.default /system/build.prop /system_ext/build.prop /vendor/default.prop /vendor/build.prop /odm/etc/build.prop /product/build.prop /factory/factory.prop要弄清楚我们应该在哪里添…...
docker 创建容器过程
结合下图,本文讨论docker 创建容器过程: START└── [用户通过Docker Client发出指令]└── (1) docker run 或 docker create 命令├── (2) Docker Client与Docker Daemon建立通信连接└── (3) Docker Daemon接收到创建容器请求├── (4) 检查…...
OSI七层网络攻击行为及防范手段
2020年3月3日,360安全大脑披露美国中央情报局攻击组织(APT-C-39)对我国大型互联网公司、政府部门及相关企业进行长达11年的网络攻击渗透,该组织所使用的网络武器和CIA“Vault7”项目中的网络武器完全吻合。如今随着互联网技术的蓬…...
第100+5步 ChatGPT文献复现:ARIMAX预测肺结核 vol. 5
基于WIN10的64位系统演示 一、写在前面 我们继续往下看,首先例行回顾文章: 《PLoS One》杂志的2023年一篇题目为《A comparative study of three models to analyze the impact of air pollutants on the number of pulmonary tuberculosis cases in …...
论文| Convolutional Neural Network-based Place Recognition - 2014
2014-Convolutional Neural Network-based Place Recognition...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...

