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。

    目录
    相关文章
    |
    3天前
    |
    Java API
    |
    11天前
    |
    存储 算法 Java
    14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
    14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
    30 2
    14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
    |
    1天前
    |
    SQL Java Apache
    实时计算 Flink版操作报错合集之使用parquet时,怎么解决报错:无法访问到java.uti.Arrays$ArrayList类的私有字段
    在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
    |
    1月前
    |
    存储 Java 编译器
    Java中ArrayList的常用函数
    确切地说,`ArrayList` 提供的这些方法构成了一套强大并且灵活的工具集,可以满足各种程序设计情况中的需求。例如,通过使用 `iterator()`方法,开发者可以在不知道集合大小的情况下遍历集合中全部或部分元素;而 `sort()`方法则能够对集合中的元素进行排序。这些函数在日常的Java编程中极其常见且重要,掌握它们对于进行集合操作和数据处理来说是基础且必须的。
    17 2
    Java中ArrayList的常用函数
    |
    21天前
    |
    存储 Java 索引
    Java ArrayList操作指南:如何移除并返回第一个元素
    通过上述方法,你可以方便地从Java的 `ArrayList` 中移除并返回第一个元素。这种操作在日常编程中非常常见,是处理列表时的基本技能之一。希望这篇指南能帮助你更好地理解和运用Java的 `ArrayList`。
    24 4
    |
    20天前
    |
    存储 Java 索引
    Java ArrayList操作指南:如何移除并返回第一个元素
    通过上述方法,你可以方便地从Java的 `ArrayList` 中移除并返回第一个元素。这种操作在日常编程中非常常见,是处理列表时的基本技能之一。希望这篇指南能帮助你更好地理解和运用Java的 `ArrayList`。
    23 2
    |
    5天前
    |
    存储 SQL 搜索推荐
    一天十道Java面试题----第一天(面向对象-------》ArrayList和LinkedList)
    这篇文章是关于Java面试的笔记,涵盖了面向对象、JDK/JRE/JVM的区别、`==`和`equals`、`final`关键字、`String`、`StringBuffer`和`StringBuilder`的区别、重载与重写、接口与抽象类、`List`与`Set`、`hashcode`与`equals`以及`ArrayList`和`LinkedList`的对比等十个主题。
    |
    1月前
    |
    存储 安全 Java
    详解Java中集合的List接口实现的ArrayList方法 | Set接口实现的HashSet方法
    详解Java中集合的List接口实现的ArrayList方法 | Set接口实现的HashSet方法
    |
    1月前
    |
    设计模式 并行计算 安全
    Java面试题: 如何使用装饰器模式来增强ConcurrentHashMap的功能?在什么情况下应该使用CopyOnWriteArrayList而不是ArrayList?
    Java面试题: 如何使用装饰器模式来增强ConcurrentHashMap的功能?在什么情况下应该使用CopyOnWriteArrayList而不是ArrayList?
    20 0
    |
    3月前
    |
    存储 Java
    【JAVA学习之路 | 进阶篇】ArrayList,Vector,LinkedList内存解析
    【JAVA学习之路 | 进阶篇】ArrayList,Vector,LinkedList内存解析