集合系列(二) -List接口详解

一、List简介

List 的数据结构就是一个序列,存储内容时直接在内存中开辟一块连续的空间,然后将空间地址与索引对应。

以下是List集合简易架构图

由图中的继承关系,可以知道,ArrayList、LinkedList、Vector、Stack都是List的四个实现类。

  • AbstractCollection 是一个抽象类,它唯一实现Collection接口的类。AbstractCollection主要实现了toArray()、toArray(T[] a)、remove()等方法。
  • AbstractList 也是一个抽象类,它继承于AbstractCollection。AbstractList实现List接口中除size()、get(int location)之外的函数,比如特定迭代器ListIterator。
  • AbstractSequentialList 是一个抽象类,它继承于AbstractList。AbstractSequentialList 实现了“链表中,根据index索引值操作链表的全部函数”。
  • ArrayList 是一个动态数组,它由数组实现。随机访问效率高,随机插入、随机删除效率低。
  • LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率高。
  • Vector 也是一个动态数组,和ArrayList一样,也是由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。
  • Stack 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。

下面对各个实现类进行方法剖析!

二、ArrayList

ArrayList实现了List接口,也是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现。
除该类未实现同步外,其余跟Vector大致相同。

在Java1.5之后,集合还提供了泛型,泛型只是编译器提供的语法糖,方便编程,对程序不会有实质的影响。因为所有的类都默认继承至Object,所以这里的数组是一个Object数组,以便能够容纳任何类型的对象。

常用方法介绍

2.1、get方法

get()方法同样很简单,先判断传入的下标是否越界,再获取指定元素。

public E get(int index) {rangeCheck(index);return elementData(index);
}/*** 检查传入的index是否越界*/
private void rangeCheck(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
2.2、set方法

set()方法也非常简单,直接对数组的指定位置赋值即可。

public E set(int index, E element) {rangeCheck(index);E oldValue = elementData(index);elementData[index] = element;return oldValue;
}
2.3、add方法

ArrayList添加元素有两个方法,一个是add(E e),另一个是add(int index, E e)。
这两个方法都是向容器中添加新元素,可能会出现容量(capacity)不足,因此在添加元素之前,都需要进行剩余空间检查,如果需要则自动扩容。扩容操作最终是通过grow()方法完成的。

grow方法实现

private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);//原来的1.5倍if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);
}

添加元素还有另外一个addAll()方法,addAll()方法能够一次添加多个元素,根据位置不同也有两个方法,一个是在末尾添加的addAll(Collection<? extends E> c)方法,一个是从指定位置开始插入的addAll(int index, Collection<? extends E> c)方法。

**不同点:addAll()的时间复杂度不仅跟插入元素的多少有关,也跟插入的位置相关,时间复杂度是线性增长!
**

2.4、remove方法

remove()方法也有两个版本,一个是remove(int index)删除指定位置的元素;另一个是remove(Object o),通过o.equals(elementData[index])来删除第一个满足的元素。

需要将删除点之后的元素向前移动一个位置。需要注意的是为了让GC起作用,必须显式的为最后一个位置赋null值。

  • remove(int 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; //赋null值,方便GC回收return oldValue;
}
  • remove(Object o)方法
public boolean remove(Object o) {if (o == null) {for (int index = 0; index < size; index++)if (elementData[index] == null) {fastRemove(index);return true;}} else {for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}return false;
}

三、LinkedList

在上篇文章中,我们知道LinkedList同时实现了List接口和Deque接口,也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(Stack)。

LinkedList底层通过双向链表实现,通过firstlast引用分别指向链表的第一个和最后一个元素,注意这里没有所谓的哑元(某个参数如果在子程序或函数中没有用到,那就被称为哑元),当链表为空的时候firstlast都指向null。

public class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{/**容量*/transient int size = 0;/**链表第一个元素*/transient Node<E> first;/**链表最后一个元素*/transient Node<E> last;......
}
/*** 内部类Node*/
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;}
}

常用方法介绍

3.1、get方法

get()方法同样很简单,先判断传入的下标是否越界,再获取指定元素。

public E get(int index) {checkElementIndex(index);return node(index).item;
}/*** 检查传入的index是否越界*/
private void checkElementIndex(int index) {if (!isElementIndex(index))throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
3.2、set方法

set(int index, E element)方法将指定下标处的元素修改成指定值,也是先通过node(int index)找到对应下表元素的引用,然后修改Node中item的值。

public E set(int index, E element) {checkElementIndex(index);Node<E> x = node(index);E oldVal = x.item;x.item = element;return oldVal;
}
3.3、add方法

同样的,add()方法有两方法,一个是add(E e),另一个是add(int index, E element)。

  • add(E e)方法

该方法在LinkedList的末尾插入元素,因为有last指向链表末尾,在末尾插入元素的花费是常数时间,只需要简单修改几个相关引用即可。

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++;
}
  • add(int index, E element)方法

该方法是在指定下表处插入元素,需要先通过线性查找找到具体位置,然后修改相关引用完成插入操作。

具体分成两步,1.先根据index找到要插入的位置;2.修改引用,完成插入操作。

public void add(int index, E element) {checkPositionIndex(index);if (index == size)//调用add方法,直接在末尾添加元素linkLast(element);else//根据index找到要插入的位置linkBefore(element, node(index));
}/*** 插入位置*/
void linkBefore(E e, Node<E> succ) {// assert succ != null;final Node<E> pred = succ.prev;final Node<E> newNode = new Node<>(pred, e, succ);succ.prev = newNode;if (pred == null)first = newNode;elsepred.next = newNode;size++;modCount++;
}

同样的,添加元素还有另外一个addAll()方法,addAll()方法能够一次添加多个元素,根据位置不同也有两个方法,一个是在末尾添加的addAll(Collection<? extends E> c)方法,另一个是从指定位置开始插入的addAll(int index, Collection<? extends E> c)方法。

里面也for循环添加元素,addAll()的时间复杂度不仅跟插入元素的多少有关,也跟插入的位置相关,时间复杂度是线性增长!

3.4、remove方法

同样的,remove()方法也有两个方法,一个是删除指定下标处的元素remove(int index),另一个是删除跟指定元素相等的第一个元素remove(Object o)。

两个删除操作都是,1.先找到要删除元素的引用;2.修改相关引用,完成删除操作

  • remove(int index)方法

通过下表,找到对应的节点,然后将其删除

public E remove(int index) {checkElementIndex(index);return unlink(node(index));
}
  • remove(Object o)方法

通过equals判断找到对应的节点,然后将其删除

public boolean remove(Object o) {if (o == null) {for (Node<E> x = first; x != null; x = x.next) {if (x.item == null) {unlink(x);return true;}}} else {for (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item)) {unlink(x);return true;}}}return false;
}

删除操作都是通过unlink(Node<E> x)方法完成的。这里需要考虑删除元素是第一个或者最后一个时的边界情况。

/*** 删除一个Node节点方法*/
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;size--;modCount++;return element;
}

四、Vector

Vector类属于一个挽救的子类,早在jdk1.0的时候,就已经存在此类,但是到了jdk1.2之后重点强调了集合的概念,所以,先后定义了很多新的接口,比如ArrayList、LinkedList,但考虑到早期大部分已经习惯使用Vector类,所以,为了兼容性,java的设计者,就让Vector多实现了一个List接口,这才将其保留下来。

在使用方面,Vector的getsetaddremove方法实现,与ArrayList基本相同,不同的是Vector在方法上加了线程同步锁synchronized,所以,执行效率方面,会比较慢!

4.1、get方法
public synchronized E get(int index) {if (index >= elementCount)throw new ArrayIndexOutOfBoundsException(index);return elementData(index);
}
4.2、set方法
public synchronized E set(int index, E element) {if (index >= elementCount)throw new ArrayIndexOutOfBoundsException(index);E oldValue = elementData(index);elementData[index] = element;return oldValue;
}
4.3、add方法
public synchronized boolean add(E e) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = e;return true;
}
4.4、remove方法
public synchronized boolean removeElement(Object obj) {modCount++;int i = indexOf(obj);if (i >= 0) {removeElementAt(i);return true;}return false;
}

五、Stack

在 Java 中 Stack 类表示后进先出(LIFO)的对象堆栈。栈是一种非常常见的数据结构,它采用典型的先进后出的操作方式完成的;在现实生活中,手枪弹夹的子弹就是一个典型的后进先出的结构。

在使用方面,主要方法有push peek pop

5.1、push方法

push方法表示,向栈中添加元素

public E push(E item) {addElement(item);return item;
}
5.2、peek方法

peek方法表示,查看栈顶部的对象,但不从栈中移除它

public synchronized E peek() {int     len = size();if (len == 0)throw new EmptyStackException();return elementAt(len - 1);
}
5.3、pop方法

pop方法表示,移除元素,并将要移除的元素方法

  public synchronized E pop() {E       obj;int     len = size();obj = peek();removeElementAt(len - 1);return obj;
}

关于 Java 中 Stack 类,有很多的质疑声,栈更适合用队列结构来实现,这使得Stack在设计上不严谨,因此,官方推荐使用Deque下的类来是实现栈!

六、总结

  • ArrayList(动态数组结构),查询快(随意访问或顺序访问),增删慢,但在末尾插入,速度与LinkedList相差无几!
  • LinkedList(双向链表结构),查询慢,增删快!
  • Vector(动态数组结构),相比ArrayList都慢,被ArrayList替代,基本不在使用。优势是线程安全(函数都是synchronized),如果需要在多线程下使用,推荐使用并发容器中的工具类来操作,效率高!
  • Stack(栈结构)继承于Vector,数据是先进后出,基本不在使用,如果要实现栈,推荐使用Deque下的ArrayDeque,效率比Stack高!

七、参考

1、JDK1.7&JDK1.8 源码

2、CarpenterLee - Java集合分析

3、博客园 - 朽木 - ArrayList、LinkedList、Vector、Stack的比较

八、写到最后

最近无意间获得一份阿里大佬写的技术笔记,内容涵盖 Spring、Spring Boot/Cloud、Dubbo、JVM、集合、多线程、JPA、MyBatis、MySQL 等技术知识。需要的小伙伴可以点击如下链接获取,资源地址:技术资料笔记。

不会有人刷到这里还想白嫖吧?点赞对我真的非常重要!在线求赞。加个关注我会非常感激!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/748306.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件设计和体系结构

软件设计和体系结构 一、引言 软件 定义&#xff1a;一系列按照特定顺序组织的计算机数据、指令的集合 特点&#xff1a; 软件不是生产制造&#xff0c;是设计开发软件不会磨损和老化软件需要根据实际情况进行定制开发 软件设计的基本原则 抽象方法 过程抽象&#xff1a;是指…

进程和ELF文件

程序的二进制格式&#xff1a; 关联知识&#xff1a;GCC编译原理与使用-CSDN博客 linux二进制程序的严格格式&#xff1a;ELF&#xff08;Executeable and Linkable format&#xff09; 一 预处理&#xff0c;编译&#xff0c;汇编得到的.o文件&#xff0c;就是ELF的第一种类…

Altium Designer快速入门及项目实战教程之层次原理图PCB设计(七)

一、简介 层次PCB设计是电子工程领域中一项至关重要的任务&#xff0c;它涉及到电路板的各个层面的功能分配和布局规划。这一设计过程的关键在于如何合理安排每一层的功能和布局&#xff0c;以确保电路板的性能达到最佳状态&#xff0c;并同时保证其可靠性。 首先&#xff0c…

数据结构 第3章:栈与队列

文章目录 1. 栈1.1 栈的基本概念1.2 栈的基本操作1.3 栈的顺序存储实现1.4 栈的链式存储实现 2. 队列2.1 队列的基本概念2.2 队列的基本操作2.3. 队列的顺序存储实现2.4 队列的链式存储实现2.5 双端队列 3. 栈与队列的应用3.1 栈在括号匹配中的应用3.2 栈在表达式求值中的应用3…

6.JavaWebJDBC连接池、JDBCTemplate

目录 导语&#xff1a; 一、JDBC连接池概述 常见JDBC连接池&#xff1a; JDBC连接池的优点 二、JDBCTemplate介绍 JDBCTemplate的核心组件 JDBCTemplate的使用方法 JDBCTemplate的优点 结语&#xff1a; ​​​​​​​ 导语&#xff1a; 在Java数据库编程中&#xf…

macOS Monterey 12.7.4 (21H1123) Boot ISO 原版可引导镜像下载

macOS Monterey 12.7.4 (21H1123) Boot ISO 原版可引导镜像下载 3 月 8 日凌晨&#xff0c;macOS Sonoma 14.4 发布&#xff0c;同时带来了 macOS Ventru 13.6.5 和 macOS Monterey 12.7.4 安全更新。 本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Applications&#xf…

Hive-技术补充-初识ANTLR

一、背景 要清晰的理解一条Hql是如何编译成MapReduce任务的&#xff0c;就必须要学习ANTLR。下面是ANTLR的官方网址&#xff0c;下面让我们一起来跟着官网学习吧&#xff0c;在学习的过程中我参考了《antlr4权威指南》&#xff0c;你也可以读下这本书&#xff0c;一定会对你有…

在idea中配置tomcat服务器,部署一个项目(下载教程加链接)

第一步&#xff1a;把Tomcat下载好 ww​​​​​​​Apache Tomcat - Welcome! 链接如上&#xff1a;进去后在左边找到Tomcat8点击进去后 找到图下内容 第二步&#xff1a; 打开这个文件点击bin进去 会出现一个黑色框框&#xff0c;也就是服务器 完成后就可以在浏览器输入…

【C++】实现红黑树

目录 一、认识红黑树1.1 概念1.2 定义 二、实现红黑树2.1 插入2.2 与AVL树对比 一、认识红黑树 1.1 概念 红黑树是一个二叉搜索树&#xff0c;与AVL树相比&#xff0c;红黑树不再使用平衡因子来控制树的左右子树高度差&#xff0c;而是用颜色来控制平衡&#xff0c;颜色为红色…

Keil笔记(缘更)

Keil 一、使用Keil时可能会出现的问题1.Project框不见了2.添加文件时找不到3.交换文件位置4.main.c测试报1 warning 二、STLINK点灯操作1.配置寄存器进行点灯2.使用库函数进行点灯 3.GPIO1.LED闪烁 一、使用Keil时可能会出现的问题 1.Project框不见了 view->Project Windo…

Naive Ui Admin:企业级中后台项目开箱即用框架/让你少写一些代码

欢迎加入我们的前端组件学习交流群&#xff0c;可添加群主微信&#xff0c;审核通过后入群。 Naive Ui Admin&#xff1a;企业级中后台项目开箱即用框架/让你少写一些代码 在数字化时代&#xff0c;中后台系统对于企业的运营至关重要。然而&#xff0c;构建这样的系统往往需要…

Unity URP 如何写基础的曲面细分着色器

左边是默认Cube在网格模式下经过曲面细分的结果&#xff0c;右边是原状态。 曲面细分着色器在顶点着色器、几何着色器之后&#xff0c;像素着色器之前。 它的作用时根据配置信息生成额外的顶点以切割原本的面片。 关于这部分有一个详细的英文教程&#xff0c;感兴趣可以看一…

AtomoVideo:AIGC赋能下的电商视频动效生成

✍&#x1f3fb; 本文作者&#xff1a;凌潼、依竹、桅桔、逾溪 1. 概述 当今电商领域&#xff0c;内容营销的形式正日趋多样化&#xff0c;视频内容以其生动鲜明的视觉体验和迅捷高效的信息传播能力&#xff0c;为商家创造了新的机遇。消费者对视频内容的偏好驱动了视频创意供给…

Redis部署方式(三)主从模式

在前面单机版的基础上&#xff0c;41为主&#xff0c;30为从。 一、主从搭建 1、主Redis安装 41机器redis主要配置 requirepass redis#!_41 bind 0.0.0.0 port 6379 daemonize yes 2、从redis安装 30机器redis主要配置 requirepass redis#!_30 bind 0.0.0.0 port 6380 da…

python 如何使用 NLPchina 开源sql插件,提供代码

分享一段使用python&#xff0c;通过使用发送post请求的方式&#xff0c;来从es集群中获取数据。不用使用 elasticsearh&#xff0c;仅需要导入request和json包即可。 开源sql插件官方 文档 GitHub - NLPchina/elasticsearch-sql: Use SQL to query Elasticsearch 示例代码 调…

JavaScript中的事件模型(详细案例代码)

文章目录 一、事件与事件流二、事件模型原始事件模型特性 标准事件模型特性 IE事件模型 一、事件与事件流 javascript中的事件&#xff0c;可以理解就是在HTML文档或者浏览器中发生的一种交互操作&#xff0c;使得网页具备互动性&#xff0c; 常见的有加载事件、鼠标事件、自定…

js实现扫描线填色算法使用canvas展示

算法原理 扫描线填色算法的基本思想是&#xff1a;用水平扫描线从上到下扫描由点线段构成的多段构成的多边形。每根扫描线与多边形各边产生一系列交点。将这些交点按照x坐标进行分类&#xff0c;将分类后的交点成对取出&#xff0c;作为两个端点&#xff0c;以所填的色彩画水平…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《适应分布式资源渗透率提高的配电网网元规划方法》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

【QT 5 +Linux下qt软件点击.sh脚本运行+Dconf编辑器+学习他人文章+番外篇:点击脚本运行软件】

【QT 5 Linux下qt软件点击.sh脚本运行Dconf编辑器学习他人文章番外篇&#xff1a;点击脚本运行软件】 1、前言2、实验环境3、自我学习总结-本篇总结1、说明&#xff1a;代替qt的快捷方式2、适用性更广3、了解工具&#xff1a;Dconf编辑器注意事项&#xff1a; 4、参考链接-感谢…

PCM和I2S区别

I2S和PCM接口都是数字音频接口&#xff0c;而所见的蓝牙到cpu以及codec的音频接口都是用PCM接口&#xff0c;是不是两个接口有各自不同的应用呢&#xff1f;先来看下概念。 PCM&#xff08;PCM-clock、PCM-sync、PCM-in、PCM-out&#xff09;脉冲编码调制&#xff0c;模拟语音信…