Java中List接口常见的实现类
目录
ArrayList实现类
数据存储
构造器
成员方法:CRUD
Vector实现类
数据存储
构造器方法
成员方法
LinkedList实现类
数据存储
构造器方法
成员方法CRUD
List总结
ArrayList:数组实现,随机访问速度快,增删慢,轻量级;(线程不安全)
LinkedList:双向链表实现,增删快,随机访问慢 (线程不安全)
Vector:数组实现,重量级 (线程安全、使用少)
ArrayList实现类
List list=new ArrayList();
类定义:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess,
Cloneable, java.io.Serializable
数据存储
transient Object[] elementData; //底层存储实现采用的是数组,当存储数据元素超过数组长度时会进行扩容处理。
构造器
1、public ArrayList() { //ArrayList默认实现中,针对元素存储的数组赋值为空数组,实际上这是一种优化策略,避免饿汉模式的缺陷,避免额外的空间浪费。
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
其中常量定义为
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //空数组
2、public ArrayList(int initialCapacity) { //参数为初始化容积,不是元素个数size。
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity]; //构建指定长度的数组
} else if (initialCapacity == 0) { //如果初始化容积为0则为空数组
this.elementData = EMPTY_ELEMENTDATA;
} else { //否则抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
成员方法:CRUD
1、public boolean add(E e)
public boolean add(E e) {modCount++;
//一个用于实现fail-fast异常的参数,记录的是修改次数,在迭代器中需要使用这个值,只要修改了结构则+1add(e, elementData, size);
//参数1为需要插入的数据,参数2为正在使用的数组,参数3为当前元素个数return true;
//除非上一步出现异常,否则返回true表示插入成功
}
private void add(E e, Object[] elementData, int s) {if (s == elementData.length) //如果当前数组长度等于元素个数则扩容处理elementData = grow();elementData[s] = e; //在指定位置存储元素size = s + 1; //元素个数+1
}
private Object[] grow() {return grow(size + 1); //新容器为元素个数+1
}
private Object[] grow(int minCapacity) {
//参数size+1实际上是所需要的最小容积,不是扩容的目标大小int oldCapacity = elementData.length;
//获取当前数组的长度if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, oldCapacity >> 1 );
//获取最新容积大小值,参数1为当前容积值,参数2为最小扩容值【size+1-数组长度】,参数3为老容积值的1/2整除return elementData = Arrays.copyOf(elementData, newCapacity);
//执行老数组的元素数据拷贝,并将扩容后的新数组赋值给属性以替换老数组} else { //如果容积值<=0则创建10个长的数组用于存储元素return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];}
}
ArraySupport工具类
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
//新容器=老容积+max(最小扩容值1,老容积的1/2),扩容为+50%*原始容积if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {return prefLength;
//扩容值在0到Integer.MAX_VALUE - 8则正常使用该扩容值} else {
//如果扩容目标长度值大于Integer.MAX_VALUE - 8:如果原始长度+1小于0 越int界则异常中断;如果原始长度+1在Integer.MAX_VALUE - 8范围内则每次扩容到Integer.MAX_VALUE - 8;如果原始长度+1大于Integer.MAX_VALUE - 8时,则每次扩容目标值为原始长度+1return hugeLength(oldLength, minGrowth); //原始容积 最小增长值1}
}
private static int hugeLength(int oldLength, int minGrowth) {int minLength = oldLength + minGrowth; //原始长度+1if (minLength < 0) { //最小长度值<0 则数据溢出,抛出异常Error中断运行throw new OutOfMemoryError("Required array length " + oldLength + " + " + minGrowth + " is too large");} else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {return SOFT_MAX_ARRAY_LENGTH;} else {return minLength;}
}
结论:添加数据时当存储数据的数组长度不足时,数组会自动变长,变长的比例为50%
2、remove方法定义
2.1 public boolean remove(Object o) //按照元素进行删除
2.2 public E remove(int index) //按照指定下表进行删除,并返回被删除的元素
public E remove(int index) {Objects.checkIndex(index, size);
//调用工具类Objects中的方法进行索引需要的合法性检查,如果不合法[0,size)之间,否则IndexOutOfBoundsExceptionfinal Object[] es = elementData;
//缓存数组变量,如果需要2个不同对象但是内容相同的数组则需要clone处理,=只是将存放数据的数组地址赋值给新变量,不管使用elementData变量还是使用es变量都是在操作同一个数组E oldValue = (E) es[index];
//从数组中获取指定index位置上的数据fastRemove(es, index);
//采用System.arraycopy将指定位置上的数据进行覆盖return oldValue;
}
private void fastRemove(Object[] es, int i) {modCount++; //修改次数+1final int newSize;if ((newSize = size - 1) > i) System.arraycopy(es, i + 1, es, i, newSize - i);es[size = newSize] = null;
}
结论:使用数组元素移动的方式实现元素的删除 System.arraycopy(es, i + 1, es, i, newSize - i);
注意:这里没有变小容积
修改元素个数时会有modCount的修改--快速失败
修改次数定义在AbstractList抽象类中
protected transient int modCount = 0;
public Iterator<E> iterator() {return listIterator(); // ---new 迭代器
}
//在抽象类中采用内部类的方式提供了迭代器的实现类
private class Itr implements Iterator<E>
属性int expectedModCount = modCount; //当创建迭代器对象时会记录当前的修改次数
public E next() {
//调用迭代的next方法获取下一个元素时,会针对当前集合的修改次数和缓存的修改次数进行比较,如果不相等则抛出ConcurrentModificationException异常checkForComodification();// ... ...}
final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();
}
3、get方法的实现
结论:首先要求index应该在[0,size-1]的范围内,否则异常
如果index正确则按照下标从数组中获取元素
public E get(int index) {Objects.checkIndex(index, size);return elementData(index);
}
E elementData(int index) {return (E) elementData[index];
}
Vector实现类
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess,Cloneable, java.io.Serializable
从JDK1.0开始就提供的一个List的实现类,属于较老的不推荐使用的实现类,建议优先考虑ArrayList,因为两者的实现方式基本一致,而且ArrayList性能优于Vector.
数据存储
protected Object[] elementData; //底层实现仍旧采用的是数组
构造器方法
1、public Vector()
public Vector() {this(10); //无参构建Vector对象时默认初始化容积为10,默认容积增长的步长值为0
}
2、public Vector(int initialCapacity)
public Vector(int initialCapacity) {this(initialCapacity, 0);
}
3、public Vector(int initialCapacity, int capacityIncrement)
public Vector(int initialCapacity, int capacityIncrement) {super();if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);this.elementData = new Object[initialCapacity];
//直接创建的就是指定长度的数组,并没有支持延迟创建数组的操作this.capacityIncrement = capacityIncrement;
}
成员方法
绝大部分的成员方法前有synchronized关键字,线程安全,属于重量级
1、public synchronized boolean add(E e)
public synchronized boolean add(E e) {modCount++; //修改次数+1,快死异常add(e, elementData, elementCount);
//参数1为需要添加的数据,参数2为原始数组,参数3为当前元素个数sizereturn true;
}
private void add(E e, Object[] elementData, int s) {if (s == elementData.length) //当元素个数和数组长度一致时进行扩容处理growelementData = grow();elementData[s] = e; //扩容后的数组存储元素elementCount = s + 1; //元素个数+1
}
private Object[] grow() {return grow(elementCount + 1); //参数为当前元素个数+1作为所需要的最小容积值
}
private Object[] grow(int minCapacity) {int oldCapacity = elementData.length; //获取当前数组长度int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, capacityIncrement > 0 ? capacityIncrement : oldCapacity);
//获取扩容后的新数组最佳长度值。如果设定了扩容步长值,则使用构建Vector对象时所设置的扩容步长值进行长度的递增【定值】;如果没有设置,则扩容步长值默认为0则新长度为原始长度的2倍,扩容+100%原始长度return elementData = Arrays.copyOf(elementData, newCapacity);
}
LinkedList实现类
public class LinkedList<E> extends AbstialLtractSequenist<E> implements List<E>,
Deque<E>, Cloneable, java.io.Serializable
实现了Deque双向队列接口
数据存储
transient Node<E> first; //头指针
transient Node<E> last; //尾指针
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;}
}
构造器方法
public LinkedList() {}public LinkedList(Collection<? extends E> c) {this();addAll(c);}
成员方法CRUD
1、public boolean add(E e)
public boolean add(E e) {linkLast(e); //在链表的默认添加新元素Node类型,其中包含存储的数据ereturn true;
}
void linkLast(E e) {final Node<E> l = last; //获取尾指针final Node<E> newNode = new Node<>(l, e, null); //构建链表中的元素对象Node,其中包含数据elast = newNode; //原始尾指针指向新元素if (l == null) //如果尾指针为null则表示链表中没有数据,则将头和尾指向同一个元素first = newNode;else //如果尾指针不为null,则将新元素添加到尾指针(Node元素的后续指针)之后l.next = newNode;size++; //元素个数+1modCount++; //修改次数+1
}
2、指定位置index序号添加元素
public void add(int index, E element) {checkPositionIndex(index); index >= 0 && index <= sizeif (index == size) //如果插入元素的位置等于元素个数则插入到末尾linkLast(element);else //否则在指定位置之前插入元素linkBefore(element, node(index)); //node()用于获取指定位置的节点元素
}
Node<E> node(int index) {
//按照索引序号查找指定位置上的元素时,首先判断序号距离头部近还是尾部近,如果距离头部近则从头指针指向的元素开始查找,如果距离尾部较劲则从后向前查找元素。可以减少1/2个数的元素查找if (index < (size >> 1)) {Node<E> x = first;for (int i = 0; i < index; i++)x = x.next;return x;} else {Node<E> x = last;for (int i = size - 1; i > index; i--)x = x.prev;return x;}
}
3、public boolean remove(Object o)
public boolean remove(Object o) {
//删除定义元素。如果需要删除的元素o则从头指针开始遍历整个链表,如果Node节点对象中的数据为o【equals】则执行删除操作。删除第一个值为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;
}
4、public E remove(int index)
public E remove(int index) {
//按照序号删除指定位置上的节点checkElementIndex(index);
// index >= 0 && index < sizereturn unlink(node(index));
//首先调用node()方法获取指定位置上的Node对象,然后删除指定的节点
}
总结:双向链表实现,由于没有数据移动的问题所以号称增删快【增删时还需要查找元素,所以实际上差不多O(n)】,随机访问慢 (线程不安全)。没有扩容问题,按需开辟空间,没有空间浪费。
List总结
ArrayList | LinkedList | Vector | |
实现方式 | 数组,按照索引下标访问速度快O(1),但是当删除添加元素时会导致元素的移动,速度慢O(n) | 双向链表,按照索引下标访问速度慢O(n),但是删除添加元素速度快O(1) | 数组,按照索引下标访问速度快O(1),但是当删除添加元素时会导致元素的移动,速度慢O(n) |
是否同步 | 不同步,线程不安全,但是并发高,访问效率高 | 不同步,线程不安全,但是并发高,访问效率高 | 同步,所以线程安全,但是并发低,访问效率低 |
如何选择 | 经常需要快速访问,较少在中间增加删除元素时使用;如果多线程访问,则需要自行编程解决线程安全问题 | 经常需要在内部增删元素,但是很少需要通过索引快速访问时使用;如果多线程访问,则需要自行编程解决线程安全问题 | 一般不使用,如果在多线程访问时可以考虑使用 |
相关文章:

Java中List接口常见的实现类
目录 ArrayList实现类 数据存储 构造器 成员方法:CRUD Vector实现类 数据存储 构造器方法 成员方法 LinkedList实现类 数据存储 构造器方法 成员方法CRUD List总结 ArrayList:数组实现,随机访问速度快,增删慢&#x…...

SPI通信
SPI通信: 四根通信线:SCK,MOSI,MISO,SS(从机选择线) 同步时钟,全双工 支持总线挂载多个设备,一主多从 SPI相对IIC传输更快,最简单,最快速 SPI没有接收和应答机制,发送就发…...

【动态规划】【数论】【区间合并】3041. 修改数组后最大化数组中的连续元素数目
作者推荐 视频算法专题 本文涉及知识点 动态规划汇总 数论 区间合并 LeetCode3041. 修改数组后最大化数组中的连续元素数目 给你一个下标从 0 开始只包含 正 整数的数组 nums 。 一开始,你可以将数组中 任意数量 元素增加 至多 1 。 修改后,你可以从…...

字节后端实习 一面凉经
心脏和字节永远都在跳动 深圳还有没有大厂招后端日常实习生啊,求捞~(boss小公司也不理我) 很纠结要不要干脆直接面暑期实习,又怕因为没有后端实习经历,面不到大厂实习。死锁了...

倒计时37天
复习1001. 马走日问题: 1.P1002 [NOIP2002 普及组] 过河卒 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) //日常碎碎念:谁懂啊,dev突然不能用了,也不知道是哪里出了问题下了五六次都不能用,,,找远程安…...

【计算机考研】考408,还是不考408性价比高?
首先综合考虑,如果其他科目并不是很优秀,需要我们花一定的时间去复习,408的性价比就不高,各个科目的时间互相挤压,如果备考时间不充裕,考虑其他专业课也未尝不可。 复习408本来就是费力不讨好的事情 不同…...

测试入门篇
测试: 这里写目录标题 测试:基础概念:BUG:创建一个合理的bug:bug 的级别:跟开发争执如何解决: 测试用例:编写测试用例的万能公式:案例: 登录功能的测试:设计测试用例的方法: 进阶篇(主要介绍测试方法):自动化测试:自动化测试的分类:selenium( web 自动化测试工具 )环境部署:什么…...

b站小土堆pytorch学习记录—— P25-P26 网络模型的使用和修改、保存和读取
文章目录 一、修改1.方法2.代码 二、保存和读取1.方法2.代码(1)保存(2)加载 3.陷阱 一、修改 1.方法 add_module(name: str, module: Module) -> None name 是要添加的子模块的名称。 module 是要添加的子模块。 调用 add_m…...

[数据结构]OJ用队列实现栈
225. 用队列实现栈 - 力扣(LeetCode) 官方题解:https://leetcode.cn/problems/implement-stack-using-queues/solutions/432204/yong-dui-lie-shi-xian-zhan-by-leetcode-solution/ 首先我们要知道 栈是一种后进先出的数据结构,…...

「优选算法刷题」:最长回文子串
一、题目 给你一个字符串 s,找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。 示例 1: 输入:s "babad" 输出:"bab" 解释:"aba"…...

Java项目:41 springboot大学生入学审核系统的设计与实现010
作者主页:舒克日记 简介:Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本大学生入学审核系统管理员和学生。 管理员功能有个人中心,学生管理,学籍信息管理,入学办理管理等。 学生功能有…...

【数据结构与算法】常见排序算法(Sorting Algorithm)
文章目录 相关概念1. 冒泡排序(Bubble Sort)2. 直接插入排序(Insertion Sort)3. 希尔排序(Shell Sort)4. 直接选择排序(Selection Sort)5. 堆排序(Heap Sort)…...

Unity3D学习之XLua实践——背包系统
文章目录 1 前言2 新建工程导入必要资源2.1 AB包设置2.2 C# 脚本2.3 VSCode 的环境搭建 3 面板拼凑3.1 主面板拼凑3.2 背包面板拼凑3.3 格子复合组件拼凑3.4 常用类别名准备3.5 数据准备3.5.1 图集准备3.5.2 json3.5.3 打AB包 4 Lua读取json表及准备玩家数据5 主面板逻辑6 背包…...

前端技术研究越深入,越觉得技术不是决定录用唯一条件。
一、拒绝抬杠 我说技能不是唯一条件,不是说技能不重要,招聘前端条件是1X,其中1是技能,X是其他条件。 如果X条件很优秀,1这个条件可以降格为0.8、0.5,甚至更低。 有人就抬杠,那为啥不招聘清洁工来干前端&…...

vue组件的重新渲染的问题
目录 1.方式1 2.方式2 1.方式1 修改组件上的key属性 Vue是通过diffing算法比较虚拟DOM和真实DOM,来判断新旧 DOM 的变化。key是虚拟DOM对象的标识,在更新显示时key表示着DOM的唯一性。 DOM是否变化的核心是通过判断新旧DOM的key值是否变化,…...

opengl 学习(二)-----你好,三角形
你好,三角形 分类demo效果解析 分类 opengl c demo #include "glad/glad.h" #include "glfw3.h" #include <iostream> #include <cmath> #include <vector>using namespace std;/** * 在学习此节之前,建议将这…...

mongodb4.2升级到5.0版本,升级到6.0版本, 升级到7.0版本案例
今天一客户想把自己当前使用的mongodb数据库4.2版本升级到7.0版本。难道mongodb能直接跳跃升级吗? 经过几经查找资料,貌似真不行呀。确定升级流程如下: 还得从mongo4.2升级到5.0。其次再从5.0升级到6.0。最后再从6.0升级到7.0。 开始升级之前将数据进行备份 这一步…...

CPU处理器模式与异常
ARM架构中的Exception Level(EL) 在ARM架构中,Exception Level(EL)是一个关键概念,它表示了处理器当前处理异常或中断的层次。ARMv8-A架构定义了四个Exception Levels:EL0、EL1、EL2和EL3&…...

Day 53 |● 1143.最长公共子序列 ● 1035.不相交的线 ● 53. 最大子序和
1143.最长公共子序列 class Solution { public:int longestCommonSubsequence(string text1, string text2) {vector<vector<int>> dp(text1.size()1,vector<int>(text2.size()1,0));int res 0;for(int i 1; i < text1.size(); i){for(int j 1; j <…...

ant-desgin charts双轴图DualAxes,柱状图无法立即显示,并且只有在调整页面大小(放大或缩小)后才开始显示
摘要 双轴图表中,柱状图无法立即显示,并且只有在调整页面大小(放大或缩小)后才开始显示 官方示例代码 在直接复制,替换为个人数据时,出现柱状图无法显示问题 const config {data: [data, data],xFiel…...

获取别人店铺的所有商品API接口
使用淘宝淘口令接口的步骤通常包括: 注册成为淘宝开放平台的开发者:在淘宝开放平台网站上注册账号并完成认证。 创建应用以获取API密钥:在您的开发者控制台中创建一个应用,并获取用于API调用的密钥,如Client ID和Clie…...

成都正信:亲戚借了钱一直不还怎么委婉的说
在中国传统文化中,亲情关系往往被视为最为重要和敏感的部分。当亲戚间发生借贷时,若出现拖欠不还的情形,处理起来尤为棘手。面对这样的尴尬局面,采取委婉而有效的沟通方式至关重要。 张华最近就遇到了这样的困扰。他的表弟去年因急…...

Truenas入门级教程
Truenas入门教程 前言:系统相关配置 采用I3 4160,采用了2块500G的硬盘,内存为8G,两个网卡只用了其中一个,系统安装的是core版本 硬件采用DELL3020MT机箱,自带3个SATA网口,后期网口不够&#…...

窗口函数dense() over(条件)
力扣题目连接 https://leetcode.cn/problems/rank-scores/ 在 SQL 中,DENSE_RANK() 是一个窗口函数,用于计算结果集中每行的稠密排名(dense rank)。DENSE_RANK() 函数会为具有相同排序字段值的行分配相同的排名,但不会…...

蓝牙APP开发实现汽车遥控钥匙解锁汽车智能时代
在现代社会,随着科技的不断发展,汽车已经不再是简单的交通工具,而是与智能科技紧密相连的载体。其中,通过开发APP蓝牙程序实现汽车遥控钥匙成为了一种趋势,为车主带来了便捷与安全的体验。虎克技术公司作为行业领先者&…...

第三天 Kubernetes进阶实践
第三天 Kubernetes进阶实践 本章介绍Kubernetes的进阶内容,包含Kubernetes集群调度、CNI插件、认证授权安全体系、分布式存储的对接、Helm的使用等,让学员可以更加深入的学习Kubernetes的核心内容。 ETCD数据的访问 kube-scheduler调度策略实践 预选与…...

redis小结
1.mysql是数据库,redis是数据库,那么什么时候使用应该使用哪种数据库? redis做缓存是为了缓解mysql的压力,在数据库表数据量上千万,并且访问频繁时,mysql压力增大,在有索引的情况下依旧效果不佳,需要使用…...

PHP伪协议详解
PHP伪协议详解 一、前言1.什么是PHP伪协议?2.什么时候用PHP伪协议? 二、常见的php伪协议php://inputphp://filterzip://与bzip2://与zlib://协议data://phar:// 一、前言 1.什么是PHP伪协议? PHP伪协议是PHP自己支持的一种协议与封装协议,…...

进程:守护进程
一、守护进程的概念 守护进程是脱离于终端控制,且运行在后端的进程。(孤儿进程)守护进程不会将信息显示在任何终端上影响前端的操作,也不会被终端产生的任何信息打断,例如(ctrlc).守护进程独立…...

千里马平台项目管理理念
软件项目的成功和失败和技术关系不大,尤其是应用型软件,不可能有技术难关卡死了项目,大部分问题还是出现在项目管理层面。公司的业务形态是帮助客户构建自己的信息化生态圈,项目管理咨询也是其中的核心内容。 我们的软件项目管理理…...