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...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...
协议转换利器,profinet转ethercat网关的两大派系,各有千秋
随着工业以太网的发展,其高效、便捷、协议开放、易于冗余等诸多优点,被越来越多的工业现场所采用。西门子SIMATIC S7-1200/1500系列PLC集成有Profinet接口,具有实时性、开放性,使用TCP/IP和IT标准,符合基于工业以太网的…...
stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
sshd代码修改banner
sshd服务连接之后会收到字符串: SSH-2.0-OpenSSH_9.5 容易被hacker识别此服务为sshd服务。 是否可以通过修改此banner达到让人无法识别此服务的目的呢? 不能。因为这是写的SSH的协议中的。 也就是协议规定了banner必须这么写。 SSH- 开头,…...
Windows 下端口占用排查与释放全攻略
Windows 下端口占用排查与释放全攻略 在开发和运维过程中,经常会遇到端口被占用的问题(如 8080、3306 等常用端口)。本文将详细介绍如何通过命令行和图形化界面快速定位并释放被占用的端口,帮助你高效解决此类问题。 一、准…...

