Java List 源码解析——从基础到深度剖析

ops/2025/1/8 0:05:31/

Java List 源码解析——从基础到深度剖析

Java 集合框架中的 List 接口是开发中最常用的组件之一。无论是对数据的有序管理,还是对元素的高效访问,List 都发挥着重要作用。在这篇博客中,我们将从 List 的设计出发,逐步深入解析其主要实现类的源码,帮助你全面理解其工作原理和性能特点。


1. List 简介

1.1 什么是 List?

List 是 Java 集合框架(Java Collections Framework)中的一个接口,继承自 Collection 接口。它代表了一个有序的、可重复的元素集合。
例如:[A, B, C, A] 是一个合法的 List

1.2 List 的主要实现类

在实际开发中,List 有以下几个常用实现类:

  1. ArrayList:基于动态数组实现,适用于频繁随机访问的场景。
  2. LinkedList:基于双向链表实现,适用于频繁插入和删除的场景。
  3. CopyOnWriteArrayList:线程安全,适用于多读少写的并发场景。

2. List 接口设计

2.1 接口的定义

以下是 List 接口的核心定义,它扩展了 Collection 接口,增加了一些操作有序集合的功能:

java">public interface List<E> extends Collection<E> {void add(int index, E element);E get(int index);E remove(int index);int indexOf(Object o);int lastIndexOf(Object o);List<E> subList(int fromIndex, int toIndex);// 其他方法省略
}

2.2 特点

  • 有序性List 会按照元素插入的顺序进行存储。
  • 允许重复List 允许存储重复的元素。
  • 随机访问:可以通过索引直接访问元素。

3. ArrayList 源码解析

ArrayList 是最常用的 List 实现类,其底层基于动态数组。以下是它的详细分析。

3.1 数据结构

ArrayList 使用一个动态数组(Object[] elementData)来存储元素。容量不足时,会进行扩容操作。

java">// ArrayList 的核心属性
transient Object[] elementData;
private int size; // 当前 ArrayList 的大小

3.2 核心方法解析

add(E e) 方法

add() 方法是向列表中添加元素的核心方法。以下是其简化的源码:

java">public boolean add(E e) {ensureCapacityInternal(size + 1); // 确保容量足够elementData[size++] = e;         // 添加元素return true;
}
  1. 容量检查
    调用 ensureCapacityInternal(size + 1) 方法,确保数组有足够的容量来存储新元素。如果容量不足,会触发扩容。

  2. 扩容机制 grow() 方法用于扩容,扩容逻辑如下:

    java">private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容为原来的 1.5 倍if (newCapacity - minCapacity < 0)newCapacity = minCapacity;elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
get(int index) 方法

通过索引访问元素,复杂度为 O(1):

java">public E get(int index) {rangeCheck(index); // 检查索引是否越界return elementData[index];
}

4. LinkedList 源码解析

LinkedList 是基于双向链表实现的 List,更适合频繁的插入和删除操作。

4.1 数据结构

LinkedList 使用内部的 Node 类表示链表节点:

java">private static class Node<E> {E item;       // 当前节点存储的元素Node<E> next; // 指向下一个节点Node<E> prev; // 指向前一个节点Node(Node<E> prev, E element, Node<E> next) {this.item = element;this.next = next;this.prev = prev;}
}

4.2 核心方法解析

add(E e) 方法

add() 方法会在链表尾部插入新元素:

java">public boolean add(E e) {linkLast(e); // 调用辅助方法return true;
}void linkLast(E e) {final Node<E> l = last;                  // 保存当前链表的最后一个节点final Node<E> newNode = new Node<>(l, e, null);last = newNode;                          // 更新最后一个节点if (l == null)                           // 如果链表为空first = newNode;                     // 更新第一个节点elsel.next = newNode;                    // 否则更新前节点的 nextsize++;
}
get(int index) 方法

get() 方法通过遍历链表找到目标节点,复杂度为 O(n):

java">Node<E> node(int index) {if (index < (size >> 1)) {Node<E> x = first; // 从头遍历for (int i = 0; i < index; i++)x = x.next;return x;} else {Node<E> x = last; // 从尾遍历for (int i = size - 1; i > index; i--)x = x.prev;return x;}
}

5. CopyOnWriteArrayList 源码解析

CopyOnWriteArrayList 是一种线程安全的 List,它通过写时复制(Copy-On-Write)实现线程安全。

5.1 数据结构

CopyOnWriteArrayList 的底层也是一个数组,但每次写操作都会复制整个数组。

5.2 核心方法解析

add(E e) 方法
java">public boolean add(E e) {synchronized (lock) { // 加锁保证线程安全Object[] elements = getArray(); // 获取当前数组int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len + 1); // 复制数组newElements[len] = e;setArray(newElements); // 替换原数组return true;}
}
优缺点
  • 优点:读操作无需加锁,读性能高。
  • 缺点:写操作的成本较高,尤其是数组很大时。

6. ArrayList vs LinkedList

特性ArrayListLinkedList
底层实现动态数组双向链表
随机访问效率高 (O(1))低 (O(n))
插入/删除效率低 (O(n))高 (O(1))
内存使用相对节省占用更多

7. 总结与实践

  • 如果需要高效的随机访问,选择 ArrayList
  • 如果需要频繁插入或删除元素,选择 LinkedList
  • 在多读少写的并发场景中,选择 CopyOnWriteArrayList

希望通过本文,你对 List 接口及其实现类有了更深刻的理解。欢迎在实践中验证这些知识,并结合实际需求选择合适的实现类!


http://www.ppmy.cn/ops/147884.html

相关文章

音视频入门基础:MPEG2-PS专题(4)——FFmpeg源码中,判断某文件是否为PS文件的实现

一、引言 通过FFmpeg命令&#xff1a; ./ffmpeg -i XXX.ps 可以判断出某个文件是否为PS文件&#xff1a; 所以FFmpeg是怎样判断出某个文件是否为PS文件呢&#xff1f;它内部其实是通过mpegps_probe函数来判断的。从《FFmpeg源码&#xff1a;av_probe_input_format3函数和AVI…

《解析 MXNet 的 C++版本在分布式训练中的机遇与挑战》

在深度学习的广袤领域中&#xff0c;分布式训练已成为应对大规模数据和复杂模型训练需求的关键手段。MXNet 作为一款备受瞩目的深度学习框架&#xff0c;其 C版本在分布式训练方面展现出独特的魅力&#xff0c;同时也面临着诸多挑战。深入探究这些优势与挑战&#xff0c;对于推…

django vue3实现大文件分段续传(断点续传)

前端环境准备及目录结构&#xff1a; npm create vue 并取名为big-file-upload-fontend 通过 npm i 安装以下内容"dependencies": {"axios": "^1.7.9","element-plus": "^2.9.1","js-sha256": "^0.11.0&quo…

Linux(Centos 7.6)命令详解:ls

1.命令作用 列出目录内容(list directory contents) 2.命令语法 Usage: ls [OPTION]... [FILE]... 3.参数详解 OPTION: -l&#xff0c;long list 使用长列表格式-a&#xff0c;all 不忽略.开头的条目&#xff08;打印所有条目&#xff0c;包括.开头的隐藏条目&#xff09…

Unity3D仿星露谷物语开发13之角色感知道具

1、目标 在Scene中创建道具&#xff0c;角色靠近道具能够自动获取道具的信息。 ps&#xff1a;unity核心用法&#xff1a; SerializeField&#xff1a;序列化某一个字段Create -> Prefab Variant得到衍生预制体。SingletonMonobehaviour&#xff1a;单例模式类&#xff0…

【NLP高频面题 - Transformer篇】Transformer的输入中为什么要添加位置编码?

Transformer的输入中为什么要添加位置编码&#xff1f; 重要性&#xff1a;★★★ Transformer 将句子中的所有词并行地输入到神经网络中。并行输入有助于缩短训练时间&#xff0c;同时有利于学习长期依赖。不过&#xff0c;并行地将词送入 Transformer&#xff0c;却不保留词…

基于海思soc的智能产品开发(camera sensor的两种接口)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 对于嵌入式开发设备来说&#xff0c;除了图像显示&#xff0c;图像输入也是很重要的一部分。说到图像输入&#xff0c;就不得不提到camera。目前ca…

代码段中使用数据、栈

代码段中使用数据 改进之后 代码段中使用栈 在数据段中专门空出一段&#xff0c;作为栈 将数据、代码、栈放入不同段中