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

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

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 接口,增加了一些操作有序集合的功能:

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)来存储元素。容量不足时,会进行扩容操作。

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

3.2 核心方法解析

add(E e) 方法

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

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

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

    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):

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

4. LinkedList 源码解析

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

4.1 数据结构

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

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() 方法会在链表尾部插入新元素:

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;                     // 更新第一个节点
    else
        l.next = newNode;                    // 否则更新前节点的 next
    size++;
}
get(int index) 方法

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

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) 方法
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.kler.cn/a/461884.html

相关文章:

  • 01、Docker学习,第一天:简单入门与安装
  • Spring boot接入xxl-job
  • SQL Server 数据库 忘记密码
  • LabVIEW在反馈控制时如何解决带约束的控制问题
  • Python深度学习GRU、LSTM 、BiLSTM-CNN神经网络空气质量指数AQI时间序列预测及机器学习分析|数据分享...
  • 期末速成C++【大题汇总完】
  • Postman[2] 入门——界面介绍
  • 赛博周刊·2024年度工具精选(图片设计类)
  • 基于STM32的智能床垫控制系统的Proteus仿真
  • 直流开关电源技术及应用二
  • 麒麟信安云在长沙某银行的应用入选“云建设与应用领航计划(2024)”,打造湖湘金融云化升级优质范本
  • python ai ReAct 代理(ReAct Agent)
  • ulimit命令与nginx的联系
  • 【Linux报告】实训六 重置超级用户密码
  • Docker--Docker Network(网络)
  • Java:认识异常(超详解)
  • Springboot项目:使用MockMvc测试get和post接口(含单个和多个请求参数场景)
  • datax与sqoop的优缺点?
  • 安全见闻(一)
  • 深度学习camp-ResNeXt-50实战解析
  • ffmpeg指令
  • Axure PR 9 Banner 轮播图 设计交互
  • Ps:创建数据驱动的图像
  • 瑞芯微(RK)平台调试MIPI屏幕
  • 力扣2110股票平滑下跌阶段的数目
  • excel操作