Java 中的 ArrayList 和 LinkedList

简介: 列表是一种数据结构,为了方便理解,我们可以认为它是动态的数组。众所周知,数组的大小在定义的时候就固定了,不可改变,那么它就无法存储超过它容量的数据,而列表的容量是无限大的,因为它可以随着其存储内容的大小进行动态的变化(包括容量扩增和缩小),这和 java.util.Vector 很像,但又不完全相同。Java 中对列表的实现有两种,ArrayList 和 LinkedList。

一、Java 中的列表

1.1 介绍

列表是一种数据结构,为了方便理解,我们可以认为它是动态的数组。众所周知,数组的大小在定义的时候就固定了,不可改变,那么它就无法存储超过它容量的数据,而列表的容量是无限大的,因为它可以随着其存储内容的大小进行动态的变化(包括容量扩增和缩小),这和 java.util.Vector 很像,但又不完全相同。

Java 中对列表的实现有两种,ArrayList 和 LinkedList。

1.2 继承结构

Java 的整个集合框架继承比较复杂,这里只画出和 ArrayList、LinkedList 相关的部分。

说明:灰色是接口(Interface),黄色是抽象类(AbstractClass),橙色是实现类(Class);虚线是实现(inplements),实线是继承(extends)。

Java 列表继承结构图image.gif编辑

由上面可见,ArrayList 和 LinkedList 功能应该是类似的,只不过内部实现方式不同,各有优缺点,各有各的适用场景。它们都位于 java.util 中。

二、ArrayList

2.1 介绍

ArrayList 是 Java 中列表的一种实现,从类名中我们可以看出,它的底层是用数组实现的,因此 ArrayList 本质上就是一个数组队列,提供了添加、删除和修改等基本方法。

2.2 方法

下面给出类 ArrayList 的常用方法。

方法 描述
boolean add(E element) 将 element 添加到 ArrayList 的末尾,添加成功则返回 true
void add(int index, E element) 将 elemnet 插入到索引为 index 的位置
boolean addAll(Collection c) 将集合 c 中的所有元素添加到 ArrayList 的末尾,成功则返回 true
boolean addAll(int index, Collection c) 将集合 c 中的所有元素插入到索引为 index 的位置,成功则返回 true
void clear() 清除 ArrayList 的所有元素
Object clone() 返回 ArrayList 的浅拷贝
boolean contains(Object o) 判断 ArrayList 是否含有对象 o
boolean containsAll(Collection c) 判断 ArrayList 是否含有集合 c 中的全部对象 o
E get(int index) 返回索引值为 index 的元素
int indexOf(Object o) 返回对象 o 在 ArrayList 中最先出现的索引,不存在则返回 -1
int lastIndexOf(Object o) 返回对象 o 在 ArrayList 中最后出现的索引,不存在则返回 -1
boolean retainAll(Collection c) 保留 ArrayList 中与集合 c 的交叉元素,成功则返回 true
boolean removeAll(Collection c) 移除 ArrayList 中与集合 c 的交叉元素,成功则返回 true
boolean remove(Object o) 移除 ArrayList 中最先出现的一个对象 o
E remove(int index) 移除索引为 index 的元素并返回该元素
int size() 返回 ArrayList 的元素个数
boolean isEmpty() 判断 ArrayList 是否为空
List<E> subList(int fromIndex, int toIndex) 返回 ArrayList 索引从 fromIndex 开始,toIndex 结尾的部分,不包含索引为 toIndex 的元素
E set(int index, E element) 将 ArrayList 索引为 index 的元素替换为 element,并返回被替换的元素
void sort(Comparator c) 根据比较接口 c 来对 ArrayList 进行排序
Object[] toArray() 返回将 ArrayList 转换后的 Object 类型的数组,元素类型没有改变
T[] toArray(T[] a) 返回将 ArrayList 转换后的 T 类型数组
String toString() 将 ArrayList 转换成 String 类型
void ensureCapacity(int minCapacity) 将 ArrayList 的容量大小设置为 minCapacity
void trimToSize() 将 ArrayList 的容量大小设置为当前元素个数
void replaceAll(UnaryOperator<E> operator) 将 ArrayList 的每个元素都执行操作 operator,并替换将返回值替换原来的元素
boolean removeIf(Predicate<E> filter) 移除 ArrayList 中符合过滤器 filter 的元素,如果成功则返回 true
void forEach(Comsumer<E> action) 对 ArrayList 的每个元素都执行操作 action

代码示例:

importjava.util.*;
classTest {
publicstaticvoidmain(String[] args) {
ArrayList<String>arrayList=newArrayList<>();
arrayList.add("ArrayList"); // 增加元素arrayList.add(0, "Java"); // 插入元素for (Strings:arrayList) System.out.println(s); // 打印元素arrayList.replaceAll(String::toUpperCase); // 替换为大写arrayList.forEach(System.out::println); // 打印元素System.out.println(arrayList); // Output: [JAVA, ARRAYLIST]    }
}

image.gif

2.3 扩容机制

ArrayList 的底层实现是数组,但数组是定长,而 ArrayList 的动态大小的,因此 ArrayList 需要在容量不够的时候进行扩容。当容量不够的时候,ArrayList 会重新开一个原来 1.5 倍大小的新数组,并将原来的数据都搬到新的数组中去,以此来完成扩容的操作。

但这里有个问题,扩容之后,ArrayList 的数组会有很多空的部分,虽然只是 1.5 倍,看起来不是很多,但在原来的数组就特别大的情况下,这 1.5 倍就显得格外多了,太浪费了,而且可能导致机器内存不足。因此 ArrayList 就设计了一些方法来解决这个问题,比如方法 ensureCapacity,它可以一次性直接将容量设置到指定的大小,防止在某些情况自动扩容导致内存不足,而方法  trimToSize 可以将容量大小立即设置为当前元素的数量,这样数组中就不会有多余的部分了。

2.4 性能分析

因为 ArrayList 是用数组实现的,因此在遍历元素上非常有优势,但由于数组定长,扩容时需要新开辟内存空间,同时还要搬运原来的数据到新的数组中,效率较低,因此 ArrayList 在增减元素这方面就稍微慢了一点。

三、LinkedList

3.1 介绍

LinkedList 的底层是用链表实现的,链表是一种特殊的数据结构,主要分为单向链表和双向链表。除此之外,其余的都和 ArrayList 比较相像。

对于单向链表,每个节点都指向下一个节点的内存地址,而对于双向链表,每个节点都有两个分别指向前一个节点和后一个节点内存地址的值。LinkedList 的底层就是双向链表。

链表的结构image.gif编辑

3.2 具体方法

下面具体描述 LinkedList 的常用方法。

方法 描述
boolean add(E e) 向 LinkedList 的末尾添加元素 e,成功则返回 true
void add(int index, E element) 向索引为 index 的位置添加元素 element
boolean addAll(Collection c) 将集合 c 中的全部元素添加到 LinkedList 的末尾,成功则返回 true
void addAll(int index, Collection c) 将集合 c 中的全部元素添加到 LinkedList 中索引为 index 的位置
void addFirst(E e) 将元素 e 添加到 LinkedList 的最前端
void addLast(E e) 将元素 e 添加到 LinkedList 的最后端
boolean offer(E e) 将元素 e 添加到 LinkedList 的末尾,成功则返回 true
boolean offerFirst(E e) 将元素 e 添加到 LinkedList 的最前端,成功则返回 true
boolean offerLast(E e) 将元素 e 添加到 LinkedList 的最后端,成功则返回 true
void clear() 清空 LinkedList 的全部元素
E removeFirst() 移除 LinkedList 中最前端的元素,并返回这个元素
E removeLast() 移除 LinkedList 中最后端的元素,并返回这个元素
boolean remove(Object o) 移除 LinkedList 中最先出现的对象 o,成功则返回 true
E remove(int index) 移除 LinkedList 中索引为 index 的元素
E poll() 移除 LinkedList 中最前端的元素,并返回这个元素
E remove() 移除 LinkedList 中最前端的元素,并返回这个元素
boolean contains(Object o) 判断 LinkedList 是否包含对象 o
E get(int index) 返回 LinkedList 中索引为 index 的元素
E getFirst() 返回 LinkedList 中最前端的元素
E getLast() 返回 LinkedList 中最后端的元素
int indexOf(Object o) 返回 LinkedList 中最先出现对象 o 的索引,若没有则返回 -1
int lastIndexOf(Object o) 返回 LinkedList 中最后出现对象 o 的索引,若没有则返回 -1
E element() 返回 LinkedList 的第一个元素
E peek() 返回 LinkedList 的第一个元素
E peekFirst() 返回 LinkedList 的头部元素
E peekLast() 返回 LinkedList 的尾部元素
E set(int index, E element) 将 LinkedList 中索引为 index 的元素替换为元素 element,并返回被替换的元素
Object clone() 返回 LinkedList 的浅拷贝
Iterator descendingIterator() 返回 LinkedList 的倒序迭代器
int size() 返回 LinkedList 的元素个数
ListIterator listIterator() 返回 LinkedList 的顺序迭代器
ListIterator listIterator(int index) 返回 LinkedList 从索引为 index 的位置开始的顺序迭代器
Object[] toArray() 返回将 LinkedList 转换后的 Object 类型的数组,元素类型没有改变
T[] toArray(T[] a) 返回将 LinkedList 转换后的 T 类型数组
String toString() 将 LinkedList 转换成 String 类型

代码示例:

importjava.util.*;
classTest {
publicstaticvoidmain(String[] args) {
LinkedList<String>linkedList=newLinkedList<>();
linkedList.add("LinkedList"); // 增加元素linkedList.add(0, "Java"); // 插入元素for (Strings:linkedList) System.out.println(s); // 打印元素linkedList.replaceAll(String::toUpperCase); // 替换为大写linkedList.forEach(System.out::println); // 打印元素System.out.println(linkedList); // Output: [JAVA, LINKEDLIST]    }
}

image.gif

3.3 性能分析

LinkedList 的底层是用链表这种数据结构实现的,而链表每个节点之间是通过内存地址来联系的,因此它不能像数组那样直接访问到某一索引处的元素,只能从头开始遍历,直到找到该元素,所以 LinkedList 在遍历上性能偏低。但正因为它是由链表实现的,不需要像 ArrayList 那样对数组进行 1.5 倍大小的扩容,每次增加元素只需要扩增一个节点就行了,因此 LinkedList 在增减元素上性能较好。

四、二者的联系和区别

4.1 联系

ArrayList 和 LinkedList 都是用来存储数据的线性结构,都属于 Java 中的集合框架,都实现了 List 接口的功能。

4.2 区别

ArrayList 和 LinkedList 各有优缺点,适用场景不一样。

    • ArrayList 的底层数据结构是数组;LinkedList 的底层数据结构是链表;
    • ArrayList 在遍历元素上性能较优,而在增减元素上性能较差;LinkedList 在遍历元素上性能较差,而在增减元素上性能较优;
    • 一般情况下,由于 ArrayList 的 1.5 倍扩容机制,因此 ArrayList 比 LinkedList 更占用内存空间;相同容量情况下,由于 LinkedList 每个节点需要记住前后节点的地址,因此 LinkedList 比 ArrayList 更加占用内存空间;

    查找和修改需要较高的遍历性能,而增加和减少需要较高的增减性能。因此,若需要频繁地查找和修改元素,且一般只在末尾增减元素的情况下,应该采用 ArrayList;在需要频繁增减开头和中间部分元素的情况下,应该采用 LinkedList。

    目录
    相关文章
    |
    10月前
    |
    存储 Java 索引
    用Java语言实现一个自定义的ArrayList类
    自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
    416 4
    |
    11月前
    |
    缓存 Java 开发者
    Java 开发者必看!ArrayList 和 LinkedList 的性能厮杀:选错一次,代码慢成蜗牛
    本文深入解析了 Java 中 ArrayList 和 LinkedList 的性能差异,揭示了它们在不同操作下的表现。通过对比随机访问、插入、删除等操作的效率,指出 ArrayList 在多数场景下更高效,而 LinkedList 仅在特定情况下表现优异。文章强调选择合适容器对程序性能的重要性,并提供了实用的选择法则。
    455 3
    |
    Java 索引
    Java ArrayList中的常见删除操作及方法详解。
    通过这些方法,Java `ArrayList` 提供了灵活而强大的操作来处理元素的移除,这些方法能够满足不同场景下的需求。
    830 30
    |
    人工智能 安全 JavaScript
    Java ArrayList:动态数组
    本文探讨Java中的数组,对比C/C++、JS/PHP/Python等语言的数组特性。文章分析了Java数组的定义、创建方式及其规范,指出其优缺点。Java数组作为引用类型,在堆上分配内存,支持动态大小,避免了C/C++中裸数组的常见问题(如越界访问)。然而,Java数组也存在性能瓶颈和设计缺陷,例如运行时的安全检查影响速度,无法创建超大数组或泛型数组,且多线程场景下缺乏同步机制。作者建议在实际开发中用集合替代数组以规避这些问题。
    351 1
    |
    Java
    Java LinkedList集合的深度剖析
    总的来说,我希望像说故事一样讲解Java LinkedList集合的使用和实现原理,让有些许枯燥的编程知识变得趣味盎然。在这个“公交车”故事中,你不仅熟悉了LinkedList集合的实现和使用,而且还更深入地理解了数据结构中的链表。链表可能会因为插入和删除的便利性而被选用,虽然它的查找效率并不高,但是在很多场景中仍然十分有效。这就像公交车,虽然它速度不快,但却是城市出行的重要工具。
    185 8
    |
    Java 索引 容器
    Java ArrayList扩容的原理
    Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
    762 4
    Java ArrayList扩容的原理
    |
    存储 Java 索引
    Java中的数据结构:ArrayList和LinkedList的比较
    【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
    |
    安全 Java 程序员
    Java集合之战:ArrayList vs LinkedList,谁才是你的最佳选择?
    本文介绍了 Java 中常用的两个集合类 ArrayList 和 LinkedList,分析了它们的底层实现、特点及适用场景。ArrayList 基于数组,适合频繁查询;LinkedList 基于链表,适合频繁增删。文章还讨论了如何实现线程安全,推荐使用 CopyOnWriteArrayList 来提升性能。希望帮助读者选择合适的数据结构,写出更高效的代码。
    1161 3
    |
    算法 Java 测试技术
    数据结构 —— Java自定义代码实现顺序表,包含测试用例以及ArrayList的使用以及相关算法题
    文章详细介绍了如何用Java自定义实现一个顺序表类,包括插入、删除、获取数据元素、求数据个数等功能,并对顺序表进行了测试,最后还提及了Java中自带的顺序表实现类ArrayList。
    570 0
    |
    存储 Java 索引
    Java LinkedList详解
    `LinkedList`是Java集合框架中的一个重要类,实现了`List`、`Deque`和`Cloneable`接口。它基于双向链表,支持动态扩展,允许重复元素。虽然通过索引访问元素的时间复杂度为O(n),但在插入和删除操作上表现优异,时间复杂度为O(1)。常用操作包括创建、添加、获取、删除和查找元素,以及使用迭代器遍历。适用于频繁插入和删除的场景,如队列和栈的实现。
    614 6