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。

    目录
    相关文章
    |
    2月前
    |
    存储 算法 Java
    Arraylist 在 Java 中能容纳多少个元素?
    【8月更文挑战第23天】
    85 0
    |
    2月前
    |
    Java
    |
    1月前
    |
    Java
    java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
    本文介绍了Java中抽象类和抽象方法的使用,以及ArrayList的基本操作,包括添加、获取、删除元素和判断列表是否为空。
    21 2
    java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
    |
    23天前
    |
    存储 Java 索引
    Java LinkedList详解
    `LinkedList`是Java集合框架中的一个重要类,实现了`List`、`Deque`和`Cloneable`接口。它基于双向链表,支持动态扩展,允许重复元素。虽然通过索引访问元素的时间复杂度为O(n),但在插入和删除操作上表现优异,时间复杂度为O(1)。常用操作包括创建、添加、获取、删除和查找元素,以及使用迭代器遍历。适用于频繁插入和删除的场景,如队列和栈的实现。
    |
    18天前
    |
    算法 Java 测试技术
    数据结构 —— Java自定义代码实现顺序表,包含测试用例以及ArrayList的使用以及相关算法题
    文章详细介绍了如何用Java自定义实现一个顺序表类,包括插入、删除、获取数据元素、求数据个数等功能,并对顺序表进行了测试,最后还提及了Java中自带的顺序表实现类ArrayList。
    12 0
    |
    2月前
    |
    存储 Java
    Java中ArrayList 元素的排序
    本文提供了Java中根据`ArrayList`元素的某个属性进行排序的示例代码,包括实现`Comparable`接口和重载`compareTo`方法,然后使用`Collections.sort`方法进行排序。
    |
    2月前
    |
    Java
    如何在 Java 中使 Arraylist 匿名?
    【8月更文挑战第23天】
    39 0
    |
    2月前
    |
    存储 Java 编译器
    |
    2月前
    |
    存储 Java API
    |
    2月前
    |
    存储 Java API
    如何在 Java 中为 ArrayList 分配值?
    【8月更文挑战第23天】
    49 0