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 = 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优缺点

优点:

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

缺点:

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

10.CopyOnWriteArrayList使用场景

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言——从键盘输人一个表示年份的整数,判断该年份是否为闰年,并显示判断结果。

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int year 0;printf("请输入年份&#xff1a;");scanf("%d",&year);if((year%4 0) && (year%100!0) || (year%400 0)){printf("%d是闰年\n",year);}else{p…

QML Label 属性 pyside6

基本属性 text: 显示的文本内容 Label {text: "Hello, World!" }color: 文本颜色 Label {color: "blue" }font: 字体设置&#xff0c;包括 family、pointSize、weight 和 style Label {font.family: "Arial"font.pointSize: 20font.bold: tr…

set与map

set与map 一、序列式容器与关联式容器二、pair1、键值对2、作用3、构造函数4、make_pair&#xff08;1&#xff09;构造函数&#xff08;2&#xff09;作用 5、代码6、运行结果 三、set1、概念2、代码3、运行结果4、说明 四、multiset1、与set的关系2、代码3、运行结果 五、map…

【新手解答1】深入探索 C 语言:变量名、形参 + 主调函数、被调函数 + 类和对象 + 源文件(.c 文件)、头文件(.h 文件)+ 库

C语言的相关问题解答 写在最前面目录 问题1变量名与变量的关系与区别变量和数据类型形参&#xff08;形式参数&#xff09;的概念 问题2解析&#xff1a;主调函数和被调函数延伸解析&#xff1a;主调函数对于多文件程序的理解总结 问题3类和对象变量和数据类型变量是否为抽象的…

ArcGIS中基于人口数据计算人口密度的方法

文章目录 一、密度分析原理二、点密度分析三、线密度分析四、核密度分析一、密度分析原理 密度分析是指根据输入的要素数据集计算整个区域的数据聚集状况,从而产生一个联系的密度表面。通过密度计算,将每个采样点的值散步到整个研究区域,并获得输出栅格中每个像元的密度值。…

嵌入式八股 | 校招秋招 | 笔试面试 | 精选题目

欢迎关注微信公众号【赛博二哈】获取八股PDF 并加入嵌入式求职交流群。提供简历模板、学习路线、岗位整理等 欢迎加入知识星球【嵌入式求职星球】获取完整嵌入式八股。 提供简历修改、项目推荐、求职规划答疑。另有各城市、公司岗位、笔面难题、offer选择、薪资爆料等 嵌入式…

分布式数据恢复-hbase+hive分布式存储误删除如何恢复数据?

hbasehive分布式存储数据恢复环境&#xff1a; 16台某品牌R730XD服务器节点&#xff0c;每台物理服务器节点上有数台虚拟机&#xff0c;虚拟机上配置的分布式&#xff0c;上层部署hbase数据库hive数据仓库。 hbasehive分布式存储故障&初检&#xff1a; 数据库文件被误删除…

十八、初识elasticsearsh (索引)

目录 一、Elasticsearch的介绍&#xff1a; 二、正向索引和倒排索引 1、正向索引 2、倒排索引 3、索引(index) :相同类型的文档的集合 4、映射(mapping):索引中文档的字段约束信息&#xff0c;类似表的结构约束 一、Elasticsearch的介绍&#xff1a; Elasticsearch是一个…

企业数字化转型的作用是什么?_光点科技

在当今快速变化的商业环境中&#xff0c;数字化转型已成为企业发展的重要策略。企业数字化转型指的是利用数字技术改造传统业务模式和管理方式&#xff0c;以提升效率、增强竞争力和创造新的增长机会。 提升运营效率&#xff1a;数字化转型通过引入自动化工具和智能系统&#x…

JVM——产生内存溢出原因

目录 1.产生内存溢出原因一 &#xff1a;代码中的内存泄漏1.案例1&#xff1a;equals()和hashCode()导致的内存泄漏问题&#xff1a;**正常情况**&#xff1a;**异常情况&#xff1a;**解决方案&#xff1a; 2.案例2&#xff1a;内部类引用外部类问题&#xff1a;解决方案&…

计算机网络——网络可靠性及网络出口配置

1. 前言&#xff1a; 学习目标&#xff1a; 1.了解链路聚合的作用 2. 了解ACL的工作原理 3. 了解NAT的工作原理和配置 2. 网络可靠性方案 网络可靠性是指网络在面对各种异常情况或故障时&#xff0c;能够维持正常运行和提供服务的能力。这包括防止网络中断、减小数据丢失的可能…

深入Python元编程:了解声明与初始化定制元类

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 简介 在Python中&#xff0c;元编程是指在运行时创建或定制类的编程。元类是Python中最强大的元编程工具之一&#xff0c;允许您控制类的创建过程。元类是类的类&#xff0c;它控制类的实例化&#xff0c;允许您…

详解Python中print和return的作用及区别

文章目录 关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面试资料六、Python兼职渠道 print的作用是输出数据到控制端,就是打印在你能看到的…

C51--LCD1602显示屏

LCD602显示&#xff1a; 1、概述 LCD602是一种工业字符型液晶&#xff0c;能够同时显示16x02&#xff0c;即32字符&#xff08;16列&#xff0c;2行&#xff09; 2、引脚&#xff1a; VSS&#xff1a;电源地VDD&#xff1a;电源正极——5V电源VO&#xff1a; 液晶显示偏压 …

给企业做公众号运营你都有哪些宝贵经验?

运营企业公众号需要长期的坚持和不断的创新&#xff0c;如何运营好一个企业公众号&#xff0c;使其成为企业与受众互动、传递价值、提升品牌形象的平台&#xff0c;是许多企业所面临的挑战。但只要不断学习&#xff0c;总结经验&#xff0c;就一定能够找到适合自己企业的公众号…

具有“真实触感”的动捕数据手套mhand pro,提供更精确的动作捕捉

随着人工智能的普及和万物互联&#xff0c;vr虚拟技术备受关注&#xff0c;为了更加真实的虚拟现实交互体验&#xff0c;动捕数据手套的使用逐渐普及&#xff0c;vr手套可以实时采集各手指关节运动数据&#xff0c;使用动捕数据手套可以在虚拟现实的场景中实现对真实手部运动的…

竞赛选题 题目:基于机器视觉的图像矫正 (以车牌识别为例) - 图像畸变校正

文章目录 0 简介1 思路简介1.1 车牌定位1.2 畸变校正 2 代码实现2.1 车牌定位2.1.1 通过颜色特征选定可疑区域2.1.2 寻找车牌外围轮廓2.1.3 车牌区域定位 2.2 畸变校正2.2.1 畸变后车牌顶点定位2.2.2 校正 7 最后 0 简介 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享…

巧妙之中见真章:深入解析常用的创建型设计模式

设计模式之创建型设计模式详解 一、设计模式是什么&#xff1f;二、模板方法2.1、代码结构2.2、符合的设计原则2.3、如何扩展代码2.4、小结 三、观察者模式3.1、代码结构3.2、符合的设计原则3.3、如何扩展代码3.4、小结 四、策略模式4.1、代码结构4.2、符合的设计原则4.3、如何…

模拟火车订票系统---python序列

if __name__ __main__:#创建车辆信息列表list["车次","出发站-到达站","出发时间","到达时间","历时","余票"]trainNumber[T40,T298,Z158,Z62]address[长春-北京,长春-北京,长春-北京,长春-北京]getTime[00:12,0…

3种在ArcGIS Pro中制作山体阴影的方法

山体阴影可以更直观的展现地貌特点&#xff0c;表达真实的地形&#xff0c;这里为大家介绍一下在ArcGIS Pro中制作山体阴影的方法&#xff0c;希望能对你有所帮助。 数据来源 本教程所使用的数据是从水经微图中下载的DEM数据&#xff0c;除了DEM数据&#xff0c;常见的GIS数据…