商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Java中的各种list有什么区别及list和set区别详析

Java中的各种list有什么区别及list和set区别详析

  发布于2026-06-02 阅读(0)

扫一扫,手机访问

Ja va 中 List 的几种实现

List 是 Ja va 集合框架中一个非常核心的接口,它代表着有序、可重复的元素集合。说到有序,就好比排队,谁先来谁就排在前面,元素的位置由插入顺序决定。而可重复,则意味着你可以把同一个对象往里放多次。

Ja va中的各种list有什么区别及list和set区别详析

在实践中,我们最常遇到的 List 实现有这么几个:ArrayListLinkedListVectorStack,以及并发场景下的 CopyOnWriteArrayList。每个都有自己独特的脾气和适用场景,搞清楚它们之间的区别,是写出高性能代码的基础。

ArrayList

底层结构

ArrayList 的底层,是一个动态数组。它初始化时的默认容量是 10,但这不是固定的。当元素数量超过当前容量时,它会触发扩容机制。扩容规则很简单:
新容量 = 旧容量 + 旧容量 / 2,也就是 1.5 倍。例如容量 10 的数组,扩一次变成 15,再扩一次变成 22,以此类推。

时间复杂度

操作复杂度说明
随机访问 get(i)O(1)直接通过数组下标访问
尾部 addO(1) 均摊扩容时退化为 O(n)
中间插入/删除O(n)需要移动后续所有元素

线程安全

ArrayList 是非线程安全的。如果多个线程同时对它进行结构修改(比如一个线程在遍历,另一个在添加/删除),很容易出问题。解决方案通常用 Collections.synchronizedList 进行包装,或者直接上并发包里的 CopyOnWriteArrayList

LinkedList

底层结构

LinkedList 的底层是双向链表。每个节点由三部分组成:前驱指针(prev)、存储的数据(item)、后继指针(next)。这意味着它的内存是分散的,不连续。

时间复杂度

操作复杂度说明
get(i)O(n)需要从头或尾遍历到指定位置
头/尾插入删除O(1)只需要修改指针指向
中间插入O(n)先要找到那个位置

Vector

底层结构

Vector 的底层也是动态数组,这一点和 ArrayList 几乎一样。但它有一个关键区别:线程安全。

关键区别

Vector 的所有方法都加了 synchronized,这意味着它在多线程环境下是安全的。

问题

不过,这种锁粒度太粗了,性能非常差。实际项目中,几乎没有理由再去使用 Vector,它已经被 JCF 体系淘汰了。能用 ArrayList 或者并发集合解决的,坚决不用 Vector。

Stack

继承关系

Stack extends Vector

特点

Stack 是典型的后进先出(LIFO)结构。因为它继承了 Vector,所以也是线程安全的。但同样的,由于 Vector 本身的问题,Stack 也属于“已淘汰”的组件。更推荐用 Deque 接口的实现类(比如 ArrayDeque)来模拟栈的行为。

CopyOnWriteArrayList(并发重点)

底层思想

CopyOnWriteArrayList 是并发包下的一个利器,它的核心思想是“写时复制”。

特点

方面说明
线程安全
读性能非常高
写性能较差(每次写都要复制整个底层数组)
迭代不会抛 ConcurrentModificationException

它的读操作完全不加锁,读的是快照数组,所以读性能极好。但写的代价也很高——每次修改(add、set、remove)都会先复制一份新数组,修改完后再将引用指向新数组。因此它非常适合“读多写少”的并发场景,比如缓存配置、黑白名单等。

对比总结

实现底层线程安全适合场景
ArrayList动态数组随机查询多、数据变化不频繁
LinkedList双向链表频繁的头尾插入删除操作
Vector动态数组已淘汰,不建议使用
Stack已淘汰,用 Deque 代替
CopyOnWriteArrayList数组复制高并发读多写少场景

面试常见问题

Q1:ArrayList 和 LinkedList 区别?

ArrayList 底层是数组,查询快(O(1)),插入慢(O(n));
LinkedList 底层是链表,头尾操作快(O(1)),随机访问慢(O(n))。

Q2:为什么 ArrayList 不是线程安全?

在并发环境下,多个线程同时调用 add / remove 可能导致:
扩容时数据丢失、元素覆盖、操作被中断导致数据不一致。

Q3:CopyOnWriteArrayList 为什么读快?

读操作没有加锁,始终读取的是当前底层数组的一个稳定快照,所以不会出现并发修改异常(ConcurrentModificationException)。

Q4:为什么不推荐 Vector / Stack?

因为它们的 synchronized 锁粒度太大,性能差,且其他并发容器(如 CopyOnWriteArrayList、ConcurrentLinkedDeque)提供了更好的替代方案。

总结

ArrayList 查得快,LinkedList 头尾插得快,
并发专用 CopyOnWrite,Vector 和 Stack 让它歇。

一、关于List和Set

List vs Set

对比点ListSet
是否有序✅ 有序(按插入顺序)❌ 大多无序(LinkedHashSet 例外)
是否允许重复✅ 允许❌ 不允许
是否有下标✅ 有(get(i)❌ 没有
常见实现ArrayListLinkedListHashSetLinkedHashSetTreeSet
典型用途保存“有顺序、可重复”的数据保存“去重”的数据

一句话记忆:

List = 有顺序 + 可重复
Set = 去重

二、List

什么是List?

  • 可以理解为数组的升级版。
  • 能按顺序存放元素。
  • 能存储重复的元素。
  • 能通过下标(索引)直接访问。
List list = new ArrayList<>();
list.add(10);
list.add(10);
list.add(20);

System.out.println(list); // [10, 10, 20]
System.out.println(list.get(1)); // 10

三、Set

什么是Set?

  • 天生自带去重光环。
  • 它关心插入顺序?不一定,要看具体实现(HashSet 无序,LinkedHashSet 保持顺序,TreeSet 按自然顺序排序)。
  • 没有下标,不能用 get(i) 来访问元素。
Set set = new HashSet<>();
set.add(10);
set.add(10);
set.add(20);

System.out.println(set); // [10, 20]

Set 是怎么判断“重复”的?

依赖两个方法:hashCode() + equals()

  1. 先计算元素的 hashCode 值,定位到哈希桶。
  2. 再用 equals 方法比较该桶内是否已有相同元素。

四、List vs Set 核心区别

List 和 Set 都是 Collection 的子接口,核心区别在于:是否允许重复、是否有顺序、是否有下标。List 适合按顺序存储可重复数据,Set 天生去重、无下标,完全依赖哈希值判断唯一性。

五、一句话总结

List:顺序 + 重复 + 下标
Set:去重 + 无下标 + 基于 equals / hashCode

本文转载于:https://www.jb51.net/program/3650152jb.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注