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

Java 集合体系 —— List 篇

在Java集合框架中,List接口是最常用的数据结构之一,它代表一个有序的集合,允许元素重复且可以通过索引访问。本文将深入解析List接口的三个主要实现类——ArrayList、LinkedList和Vector,从底层结构、核心操作、源码实现到适用场景进行全面对比分析,帮助开发者在实际开发中做出更合适的选择。

一、List接口概述

List接口继承自Collection接口,定义了一组有序集合的操作规范,其核心特点包括:

  • 允许存储重复元素
  • 维护元素的插入顺序
  • 支持通过索引(int类型)访问元素
  • 提供了丰富的增删改查方法

List接口的主要方法包括:

  • add(E e):在末尾添加元素
  • get(int index):获取指定索引的元素
  • set(int index, E element):替换指定索引的元素
  • remove(int index):删除指定索引的元素
  • indexOf(Object o):返回元素首次出现的索引

ArrayList、LinkedList和Vector作为List接口的具体实现,在数据结构、性能特性和线程安全性上各有不同,下面分别进行详细分析。

二、ArrayList详解与源码分析

1. 底层数据结构

ArrayList是基于动态数组实现的List,其底层维护了一个Object类型的数组(transient Object[] elementData),默认初始容量为10。当元素数量超过当前容量时,会自动进行扩容操作。

2. 核心源码分析

构造方法
// 默认构造方法,初始化为空数组,首次添加元素时会扩容到10
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}// 指定初始容量的构造方法
public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);}
}
添加元素
public boolean add(E e) {// 确保容量足够,不足则扩容ensureCapacityInternal(size + 1);  // Increments modCount!!elementData[size++] = e;return true;
}// 扩容核心逻辑
private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;// 新容量为旧容量的1.5倍int newCapacity = oldCapacity + (oldCapacity >> 1);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// 复制元素到新数组elementData = Arrays.copyOf(elementData, newCapacity);
}
获取元素
public E get(int index) {// 检查索引是否越界rangeCheck(index);return elementData(index);
}// 直接通过数组索引访问,时间复杂度O(1)
E elementData(int index) {return (E) elementData[index];
}
删除元素
public E remove(int index) {rangeCheck(index);modCount++;E oldValue = elementData(index);// 计算需要移动的元素数量int numMoved = size - index - 1;if (numMoved > 0)// 移动元素填补空位System.arraycopy(elementData, index+1, elementData, index, numMoved);elementData[--size] = null; // 帮助GC回收return oldValue;
}

3. 性能特点

  • 随机访问:通过索引直接访问元素,时间复杂度为O(1),性能优异
  • 添加元素:尾部添加(add(E e))时间复杂度为O(1),但扩容时需要复制数组,最坏情况为O(n)
  • 插入/删除元素:需要移动大量元素,时间复杂度为O(n)
  • 内存占用:连续内存空间,存在一定的空间浪费(为了避免频繁扩容)

三、LinkedList详解与源码分析

1. 底层数据结构

LinkedList基于双向链表实现,每个节点(Node)包含三个部分:

  • 存储的元素(item)
  • 前驱节点引用(prev)
  • 后继节点引用(next)

其内部维护了两个指针:

  • transient Node<E> first:指向头节点
  • transient Node<E> last:指向尾节点

2. 核心源码分析

节点结构
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;}
}
添加元素
// 尾部添加元素
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;size++;modCount++;
}// 指定位置插入元素
public void add(int index, E element) {checkPositionIndex(index);if (index == size)linkLast(element);elselinkBefore(element, node(index));
}
获取元素
public E get(int index) {checkElementIndex(index);return node(index).item;
}// 查找指定索引的节点,时间复杂度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;}
}
删除元素
public E remove(int index) {checkElementIndex(index);return unlink(node(index));
}E unlink(Node<E> x) {// assert x != null;final E element = x.item;final Node<E> next = x.next;final Node<E> prev = x.prev;if (prev == null) {first = next;} else {prev.next = next;x.prev = null;}if (next == null) {last = prev;} else {next.prev = prev;x.next = null;}x.item = null; // 帮助GCsize--;modCount++;return element;
}

3. 性能特点

  • 随机访问:需要遍历链表,时间复杂度为O(n),性能较差
  • 添加/删除元素:只需修改节点引用,时间复杂度为O(1)(已知节点位置时)
  • 内存占用:每个元素需要额外存储前后节点引用,内存开销较大
  • 迭代性能:迭代器遍历性能好,但随机访问性能差

四、Vector详解与源码分析

1. 底层数据结构

Vector与ArrayList类似,也是基于动态数组实现,但它是线程安全的集合。其底层同样维护了一个Object数组(protected Object[] elementData),默认初始容量为10。

Vectory与ArrayListd的区别:
1.Vector是线程同步的,所以Vector线程安全,但ArrayList线程不安全。
2.Vector的性能要逊于ArrayList。
3.ArrayList在调用空参构造时,此时,会初始化一个长度为0的Object数组,只有当向ArrayList中添加元素时,才会扩容至长度为10的Object数组。而Vector在调用空参构造时,会直接初始化一个长度为10的Object数组,不会在添加元素时再扩容。
4.ArrayList中的元素是允许为null的,但Vector中不允许出现null元素。

2. 核心源码分析

线程安全实现

Vector的线程安全主要通过在方法上添加synchronized关键字实现:

// 添加元素,方法上有synchronized关键字
public synchronized boolean add(E e) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = e;return true;
}// 获取元素
public synchronized E get(int index) {if (index >= elementCount)throw new ArrayIndexOutOfBoundsException(index);return elementData(index);
}
扩容机制

与ArrayList不同,Vector默认扩容为原来的2倍:

private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;// capacityIncrement为扩容增量,默认为0,此时扩容为原来的2倍int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity);
}

3. 性能特点

  • 功能:与ArrayList基本一致,都提供动态数组的功能
  • 线程安全:所有方法都被synchronized修饰,保证线程安全
  • 性能:由于同步开销,在单线程环境下性能不如ArrayList
  • 扩容:默认扩容为原来的2倍,比ArrayList的1.5倍更激进

五、三种集合的对比与适用场景

特性ArrayListLinkedListVector
底层结构动态数组双向链表动态数组
线程安全
随机访问快(O(1))慢(O(n))快(O(1))
插入删除慢(O(n))快(O(1))慢(O(n))
内存占用较少(连续空间)较多(节点引用)较少(连续空间)
扩容机制1.5倍无需扩容2倍

适用场景选择建议:

  1. ArrayList

    • 适用于频繁随机访问元素的场景
    • 适合元素数量变化不大,或主要在尾部添加/删除元素的操作
    • 单线程环境下的首选List实现
  2. LinkedList

    • 适用于频繁在中间位置插入/删除元素的场景
    • 适合实现队列、栈等数据结构(LinkedList实现了Deque接口)
    • 元素数量不确定且经常变化的场景
  3. Vector

    • 适用于多线程环境下需要线程安全的场景
    • 但在实际开发中,更推荐使用Collections.synchronizedList(new ArrayList<>())替代
    • 对性能要求不高,且需要兼容旧代码的场景

六、总结

ArrayList、LinkedList和Vector作为List接口的三大实现类,各有其独特的设计和性能特性:

  • ArrayList以动态数组为基础,提供了高效的随机访问能力,适合读取操作频繁的场景
  • LinkedList基于双向链表实现,在插入和删除操作上表现优异,适合频繁修改的场景
  • Vector作为线程安全的动态数组,虽然保证了线程安全,但性能开销较大,在现代开发中已较少直接使用

在实际开发中,应根据具体的业务场景、操作类型和并发需求选择合适的List实现,以达到最优的性能表现。同时,需要注意的是,这三个集合类都不是线程安全的(除Vector外),在多线程环境下使用时需要额外的同步措施。

http://www.dtcms.com/a/418211.html

相关文章:

  • 操作系统应用开发(十一)RustDesk在线编译自己客户端——东方仙盟筑基期
  • 【mdBook】5.2 配置
  • 网站建设难么让网站快速收录
  • h5游戏网站建设游戏软件开发培训学校
  • 扩散模型-图像编辑【An Edit Friendly DDPM Noise Space: Inversion and Manipulations】
  • 开源 C# 快速开发(九)通讯--Tcp客户端
  • 大黄蜂云课堂vep格式加密视频录屏截图翻录转换为mp4
  • 【Python办公】批量图片转PDF工具
  • Python爬虫实战:获取北京市交管局最新车检信息与数据分析
  • ubuntu24.04 实现DLNA音频推送
  • 企业网站的建设规划网站建站前期准备工作
  • Docker搭建ESPIDF环境,程序下载
  • MQTT-物联网轻量级通信
  • eclipse复制项目后原项目名依然伴随值所复制的项目名
  • 微服务架构:从单机到分布式的革命性升级
  • 【ROS2学习笔记】话题通信篇:python话题订阅与发布
  • 【碎片化学习】SpringBoot服务的启动过程
  • 儿童网站模板 html做百度快照要先有网站吗
  • Games101 第六章 Shading(着色)
  • 电子电气架构 --- 智能座舱域环境感知和人机交互系统
  • 数字营销网站主页优化制作网页动画的软件
  • CSS详篇
  • Memblock-3
  • 大数据毕业设计选题推荐-基于大数据的全国饮品门店数据可视化分析系统-Hadoop-Spark-数据可视化-BigData
  • 【后端开发】golang部分中间件介绍(任务调度/服务治理/数据库/缓存/服务通信/流量治理)
  • 建设一个自己的网站需要多少钱站长统计官方网站
  • 烟台装修公司网站建设注册公司流程和费用时间
  • java设计模式:工厂方法 + 建造者模式
  • 3、Lombok进阶功能实战:Builder模式、异常处理与资源管理高级用法
  • Linux 内核开发 的核心知识点