当前位置: 首页 > news >正文

Java 集合Collection—List

ArrayList

ArrayList 就是 Java 集合框架里最常用的 动态数组,查询快、增删慢(中间位置)、自动扩容、非线程安全

创建(3 种)

// 1. 空构造(初始容量 10)
List<String> list = new ArrayList<>();// 2. 指定容量(避免多次扩容)
List<String> list = new ArrayList<>(100);// 3. 通过已有集合快速构造
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));

基本 CRUD

list.add("A");                // 尾插
list.add(0, "头");            // 按下标插入
list.get(0);                  // 查
list.set(1, "B");             // 改
list.remove("A");             // 按对象删,返回 boolean
list.remove(0);               // 按下标删,返回被删元素
list.size();                  // 元素个数
list.isEmpty();               // 是否空
list.clear();                 // 清空

遍历(4 样)

// 1. for-each(最简洁)
for (String s : list) System.out.println(s);// 2. 下标遍历(需要索引时)
for (int i = 0; i < list.size(); i++) System.out.println(list.get(i));// 3. 迭代器(遍历中删除安全)
Iterator<String> it = list.iterator();
while (it.hasNext()) {if (it.next().equals("A")) it.remove();
}// 4. Lambda(Java 8+)
list.forEach(System.out::println);

常见批量/工具操作

// 排序
list.sort(String::compareTo);          // Java 8+
Collections.sort(list);                // 旧写法// 洗牌
Collections.shuffle(list);// 反转
Collections.reverse(list);// 转数组
String[] arr = list.toArray(new String[0]);// 去重(借助 Set)
List<String> noDup = new ArrayList<>(new LinkedHashSet<>(list));

容量与性能提示

  • 默认初始容量 10,扩容 ×1.5(old + (old >> 1)),频繁扩容影响性能。
    可预估大小时直接 new ArrayList<>(size)

  • 中间插入/删除 = 数组拷贝,复杂度 O(n);只追加或随机读用 ArrayList 最划算。

  • 线程安全替代

List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 或者直接用 CopyOnWriteArrayList(读多写少场景)

一行代码记忆

new ArrayList<>(容量)  // 创
add/get/set/remove     // 增查改删
forEach + iterator       // 遍历安全
sort/shuffle/reverse     // 工具

ArrayList扩容

ArrayList 扩容 = 旧容量 1.5 倍,若仍不够则直接按需分配,最后 Arrays.copyOf 一次性拷贝

// 位于 java.util.ArrayList 中
private void grow(int minCapacity) {   // minCapacity = 当前所需最小容量int oldCapacity = elementData.length;          // 旧容量int newCapacity = oldCapacity + (oldCapacity >> 1); // 关键1:新容量 = 旧容量 + 旧容量/2  → 1.5 倍if (newCapacity - minCapacity < 0)               // 关键2:如果 1.5 倍还不够,就直接用所需容量newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)            // 超过 JVM 允许最大数组长度时做极限处理newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity); // 真正申请新数组并拷贝
}

使用时机

public boolean add(E e) {ensureCapacityInternal(size + 1);  // 每次 add 先保证容量够用elementData[size++] = e;return true;
}private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 空构造第一次 addminCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 默认初始容量 10}ensureExplicitCapacity(minCapacity);
}private void ensureExplicitCapacity(int minCapacity) {modCount++;if (minCapacity - elementData.length > 0)  // 容量不足才触发 growgrow(minCapacity);
}

LinkedList

LinkedList双向链表 实现的 ListDeque,插入/删除头尾快、随机访问慢,非线程安全。

创建

LinkedList<String> list = new LinkedList<>();           // 空链表
LinkedList<Integer> list2 = new LinkedList<>(Arrays.asList(1,2,3)); // 带初值

List 接口常用(随机访问性能差)

list.add("A");              // 尾插
list.add(1, "B");           // 按下标插入(需遍历到节点,O(n))
list.get(2);                // 按索引查(O(n))
list.set(0, "AA");          // 改(O(n))
list.remove("A");           // 按对象删
list.remove(0);             // 按下标删
list.size();                // 元素个数
list.clear();               // 清空

Deque 接口常用(头/尾操作 O(1))

// 头
list.addFirst("head");      // 头部插入
String h = list.removeFirst(); // 头部删除并返回
String peek = list.peekFirst(); // 偷看头// 尾
list.addLast("tail");       // 尾插(等价 add)
String t = list.removeLast();
String peekTail = list.peekLast();// 队列模式
list.offer("task");         // 尾插
String task = list.poll();  // 头删,空返回 null
String element = list.element(); // 头查,空抛异常

遍历(4 样)

// 1. for-each(最常用)
for(String s : list) System.out.println(s);// 2. 下标(慢,O(n^2) 总耗时)
for (int i = 0; i < list.size(); i++) ...// 3. 迭代器(遍历时删安全)
Iterator<String> it = list.iterator();
while(it.hasNext()) if (it.next().equals("A")) it.remove();// 4. Lambda
list.forEach(System.out::println);

栈/队列 一行用法

// 当栈用
list.push("A");      // 压栈
String top = list.pop(); // 出栈// 当队列用
list.offer("job");
String job = list.poll();

性能 & 使用提示

  • 随机访问 get(index) 需遍历,复杂度 O(n);大量按位置读写请用 ArrayList

  • 头/尾增删 O(1),适合 队列、栈 场景。

  • 需要线程安全时

Deque<String> syncDeque = Collections.synchronizedDeque(new LinkedList<>());
// 或 ConcurrentLinkedQueue / LinkedBlockingDeque

口诀背下来

LinkedList = 双向链
push/pop 当栈用
addFirst/removeFirst 当队列
随机 get 慢成狗

List如何线程安全

CopyOnWriteArrayList —— 读多写少无敌手

  • 原理:写时复制整个数组;读操作无锁。

  • 适用:遍历远多于修改,且数据量不大(复制成本可接受)。

List<String> list = new CopyOnWriteArrayList<>();

缺点:写频繁或数组巨大时 GC 压力大。

Collections.synchronizedList(new ArrayList<>()) —— 通用兜底

  • 原理:对每个方法加 synchronized (mutex),读写都串行。

  • 适用:写操作不少、数据量中等、需要随机访问。

List<String> list = Collections.synchronizedList(new ArrayList<>());

注意:

  • 迭代必须手动加锁,否则抛 ConcurrentModificationException

synchronized (list) {for (String s : list) { ... }
}

Vector —— 历史遗产,别再用

synchronizedList 类似,但所有方法直接加 synchronized,性能差、扩展性差;官方已不推荐使用。

手动外部锁 —— 特殊需求

自己控制锁粒度,例如读写锁 ReentrantReadWriteLock

ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
List<String> list = new ArrayList<>();// 读
rwl.readLock().lock();
try { ... } finally { rwl.readLock().unlock(); }// 写
rwl.writeLock().lock();
try { list.add("x"); } finally { rwl.writeLock().unlock(); }

复杂度最高,只有需要 细粒度控制批量读写 时才考虑。

选型口诀
“读远多于写” → CopyOnWriteArrayList
“读写差不多” → Collections.synchronizedList
“不要碰 Vector” → 历史遗迹
“极致性能” → 手动读写锁

CopyOnWriteArrayList写时加锁复制

同一时刻只能有一条线程写,读线程完全无锁
写操作先 加锁 → 复制数组 → 修改副本 → 原子替换引用,最后释放锁。

set(int index, E element) —— 替换指定位置元素

public E set(int index, E element) {synchronized (lock) {              // ① 全局写锁,保证只有一条线程进入Object[] es = getArray();      // ② 拿到当前 volatile 数组快照(读无锁)E oldValue = elementAt(es, index); // ③ 取旧值if (oldValue != element) {     // ④ 值真正变化才复制es = es.clone();           // ⑤ 整表复制一份新数组es[index] = element;       // ⑥ 在新数组上修改}// ⑦ 即使旧值相同,也做一次 volatile 写,保证“写-写”顺序语义setArray(es);                  // ⑧ 原子替换数组引用,读线程立即可见return oldValue;               // ⑨ 返回旧值}                                    // ⑩ 解锁,写完成
}

add(E e) —— 尾部追加元素

public boolean add(E e) {synchronized (lock) {              // ① 写锁Object[] es = getArray();      // ② 当前数组int len = es.length;           // ③ 旧长度es = Arrays.copyOf(es, len + 1); // ④ 复制并扩容 1 位es[len] = e;                   // ⑤ 在新数组尾部放入新元素setArray(es);                  // ⑥ volatile 替换引用,读线程无感知切换return true;                   // ⑦ 成功}                                    // ⑧ 解锁
}
要点说明
锁范围仅写操作加 synchronized (lock),读操作(get()/iterator())完全不抢锁,实现 读多写少 的高并发。
复制数组任何修改都先 clone/copyOf 生成新数组,旧数组在迭代器手里仍是快照,遍历不会抛 ConcurrentModificationException。
volatile 替换setArray(es) 把新数组赋给 volatile Object[] array;,保证 写-读 的可见性;读线程下次 getArray() 立刻看到新数据。
最小修改set 方法里若旧值相同,仍执行一次 volatile 写,是为了维持“写-写” happens-before 语义(框架级需求)。

Arrays.copyOf

Collections.synchronizedList

Collections.synchronizedList的原理是在原有的List上套一层synchronized

Collections.synchronizedList 的实现原理就是 “装饰器模式 + 同步代码块”
它并不改动原 List 的内部结构,而是把 所有访问方法 包上一层 synchronized (mutex),让 同一时刻只有一个线程能读写

下面给出源码级注释,一眼看懂。

工厂方法入口

public static <T> List<T> synchronizedList(List<T> list) {return (list instanceof RandomAccess ?new SynchronizedRandomAccessList<>(list) :   // ArrayList 等new SynchronizedList<>(list));               // LinkedList 等
}

→ 根据是否支持随机访问,选两个装饰子类,但核心逻辑一样。

装饰类结构(以 SynchronizedList 为例)

static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> {private static final long serialVersionUID = -7754090372962977524L;final List<E> list;           // 被装饰的原始 ListSynchronizedList(List<E> list) {super(list);              // 父类把 list 保存为 this.cthis.list = list;}...
}

方法级同步 —— 所有操作包 synchronized (mutex)

public E get(int index) {synchronized (mutex) {        // 同一时刻只有一个线程能读return list.get(index);}
}public void add(int index, E element) {synchronized (mutex) {        // 写也串行list.add(index, element);}
}public Iterator<E> iterator() {return list.iterator();       // ⚠️ 注意:迭代器本身 **不同步**!
}
  • mutex 在父类 SynchronizedCollection 里就是 当前装饰器实例 (this)。

  • 因此 读写互斥、写写互斥、读读也互斥 —— 完全串行。

迭代器 不同步 —— 必须手动加锁

List<Foo> syncList = Collections.synchronizedList(new ArrayList<>());
...
synchronized (syncList) {        // 整个遍历区间加锁for (Foo f : syncList) {// do something}
}

否则在多线程环境下会抛 ConcurrentModificationException

与 Vector 对比

维度synchronizedListVector
锁粒度装饰器方法级 synchronized (mutex)方法声明 synchronized
底层实现复用现有 ArrayList/LinkedList原生同步数组
迭代器需外部手动同步需外部手动同步
扩展性可装饰任意 List 实现固定实现

一句话总结
Collections.synchronizedList 就是 给原 List 套一层“方法级同步壳”
所有读写操作串行化迭代器必须额外手动加锁,简单但并发度低,适合 写少读少快速兜底 场景。

LinkedList和ArrayList使用场景

查询多、尾部增删多 → ArrayList;插入删除多、当队列/栈用 → LinkedList
下面把场景、量化数据、代码示例一次给全,面试直接背。

一、底层决定性格

维度ArrayListLinkedList
数据结构可扩容 Object[]双向 链表(Node<E>
随机访问O(1)O(n)
头/尾插入删除O(1) 仅尾插;中间 O(n)O(1) 头尾;中间定位 O(n)
内存占用连续数组,1 倍指针每个元素 3 个指针(prev、next、item)≈ 3 倍
缓存友好连续内存,命中高节点分散,缓存行失效

二、量化场景阈值(经验值)

  1. 随机访问次数 > 1 000 / 每 1 次插入 → 用 ArrayList
    实测 10w 次 get(i)ArrayList ≈ 1 ms,LinkedList ≈ 200 ms

  2. 头/尾插入比例 > 30% 且几乎不查询 → LinkedList 开始反超
    10w 次头插:LinkedList ≈ 3 ms,ArrayList ≈ 200 ms(批量 add(0, e) 需整体拷贝)

三、典型业务场景对照

场景推荐原因
数据库结果集只读遍历ArrayList大量 get(i)
消息队列、栈、双端队列LinkedList头尾 O(1),可当队列/栈
中间频繁插入/删除(>30%)且数据量大LinkedList无需移动元素
缓存、随机查询密集ArrayList连续内存+O(1) 访问
Android RecyclerView 数据源ArrayList随机 get 多,内存紧凑

文章转载自:

http://ObwVM449.wwznd.cn
http://128nQT2k.wwznd.cn
http://t39yJbtQ.wwznd.cn
http://AcKqxf95.wwznd.cn
http://ifF5lPIa.wwznd.cn
http://F20kuSXO.wwznd.cn
http://4v4fhFEu.wwznd.cn
http://l99dNWFQ.wwznd.cn
http://xKe5fEz9.wwznd.cn
http://0HWFob8h.wwznd.cn
http://YCS7FRng.wwznd.cn
http://vrr2xP0V.wwznd.cn
http://UHlrAIOi.wwznd.cn
http://Ik4Xz3VJ.wwznd.cn
http://EYx8dyym.wwznd.cn
http://epsFOhca.wwznd.cn
http://84jXCdGU.wwznd.cn
http://VaoVDvwh.wwznd.cn
http://lp9ZnNRA.wwznd.cn
http://mhs5aLoN.wwznd.cn
http://CgEzYaVI.wwznd.cn
http://cyYvCT7a.wwznd.cn
http://WS3FqF9M.wwznd.cn
http://IRNbrQ9s.wwznd.cn
http://3TRI6ydW.wwznd.cn
http://88cfYSvG.wwznd.cn
http://OP3FsoWT.wwznd.cn
http://nyoKS0Il.wwznd.cn
http://IJORR6RU.wwznd.cn
http://9HZbr2HX.wwznd.cn
http://www.dtcms.com/a/373318.html

相关文章:

  • leetcode9(跳跃游戏)
  • 在UnionTech OS Server 20 (统信UOS服务器版) 上离线安装PostgreSQL (pgsql) 数据库
  • Azure Logic App 与 Azure Function 对比分析
  • 房屋安全鉴定注意事项
  • 【Go】:mac 环境下GoFrame安装开发工具 gf-cli——gf_darwin_arm64
  • 知识竞赛活动舞台道具全面指南
  • Linux《进程信号(下)》
  • 力扣.1054距离相等的条形码力扣767.重构字符串力扣47.全排列II力扣980.不同路径III力扣509.斐波那契数列(记忆化搜索)
  • 区块链:重构企业数字化的信任核心与创新动力
  • 【系统架构设计师(22)】面向服务的软件架构风格
  • Google Play账户与App突遭封禁?紧急应对与快速重构上架策略
  • 操作系统进程/线程的状态与转换
  • 保姆级教程 | travis-Linux版本安装编译
  • 【HarmonyOS 6】Install Failed: error: failed to install bundle.code:9568322
  • STM32精准控制水流
  • Failed to connect to github.com port 443 after 21s
  • 视频画质差怎么办?AI优化视频清晰度技术原理与实战应用
  • comfyUI 暴露网络restful http接口
  • Python程序使用了Ffmpeg,结束程序后,文件夹中仍然生成音频、视频文件
  • 【CFA三级笔记】资产配置:第二章 资本市场预期(预测资产收益)
  • CSS3核心技术
  • Redis 发布订阅模式:轻量级消息系统实战指南
  • 简单粗暴的Linux入门以及基础命令
  • SME-Econometrics
  • ActiveMQ、RocketMQ、RabbitMQ、Kafka 的全面对比分析
  • 无人机方案如何让桥梁监测更安全、更智能?融合RTK与超高分辨率成像,优于毫米精度
  • 嵌入式 - ARM1
  • 零基础入门AI:Transformer详解(自注意力机制、前馈神经网络等)
  • 小红书获取用户作品列表API接口操作指南
  • MySQL——事务、MVCC