【Java】 —— 数据结构与集合源码:Vector、LinkedList在JDK8中的源码剖析
目录
7.2.4 Vector部分源码分析
7.3 链表LinkedList
7.3.1 链表与动态数组的区别
7.3.2 LinkedList源码分析
启示与开发建议
7.2.4 Vector部分源码分析
jdk1.8.0_271中:
//属性
protected Object[] elementData;
protected int elementCount;//构造器
public Vector() {this(10); //指定初始容量initialCapacity为10
}public Vector(int initialCapacity) {this(initialCapacity, 0); //指定capacityIncrement增量为0
}public Vector(int initialCapacity, int capacityIncrement) {super();//判断了形参初始容量initialCapacity的合法性if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);//创建了一个Object[]类型的数组this.elementData = new Object[initialCapacity];//增量,默认是0,如果是0,后面就按照2倍增加,如果不是0,后面就按照你指定的增量进行增量this.capacityIncrement = capacityIncrement;
}//方法:add()相关方法
//synchronized意味着线程安全的
public synchronized boolean add(E e) {modCount++;//看是否需要扩容ensureCapacityHelper(elementCount + 1);//把新的元素存入[elementCount],存入后,elementCount元素的个数增1elementData[elementCount++] = e;return true;
}private void ensureCapacityHelper(int minCapacity) {//看是否超过了当前数组的容量if (minCapacity - elementData.length > 0)grow(minCapacity); //扩容
}private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length; //获取目前数组的长度//如果capacityIncrement增量是0,新容量 = oldCapacity的2倍//如果capacityIncrement增量是不是0,新容量 = oldCapacity + capacityIncrement增量;int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);//如果按照上面计算的新容量还不够,就按照你指定的需要的最小容量来扩容minCapacityif (newCapacity - minCapacity < 0)newCapacity = minCapacity;//如果新容量超过了最大数组限制,那么单独处理if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);//把旧数组中的数据复制到新数组中,新数组的长度为newCapacityelementData = Arrays.copyOf(elementData, newCapacity);
}//方法:remove()相关方法
public boolean remove(Object o) {return removeElement(o);
}
public synchronized boolean removeElement(Object obj) {modCount++;//查找obj在当前Vector中的下标int i = indexOf(obj);//如果i>=0,说明存在,删除[i]位置的元素if (i >= 0) {removeElementAt(i);return true;}return false;
}//方法:indexOf()
public int indexOf(Object o) {return indexOf(o, 0);
}
public synchronized int indexOf(Object o, int index) {if (o == null) {//要查找的元素是null值for (int i = index ; i < elementCount ; i++)if (elementData[i]==null)//如果是null值,用==null判断return i;} else {//要查找的元素是非null值for (int i = index ; i < elementCount ; i++)if (o.equals(elementData[i]))//如果是非null值,用equals判断return i;}return -1;
}//方法:removeElementAt()
public synchronized void removeElementAt(int index) {modCount++;//判断下标的合法性if (index >= elementCount) {throw new ArrayIndexOutOfBoundsException(index + " >= " +elementCount);}else if (index < 0) {throw new ArrayIndexOutOfBoundsException(index);}//j是要移动的元素的个数int j = elementCount - index - 1;//如果需要移动元素,就调用System.arraycopy进行移动if (j > 0) {//把index+1位置以及后面的元素往前移动//index+1的位置的元素移动到index位置,依次类推//一共移动j个System.arraycopy(elementData, index + 1, elementData, index, j);}//元素的总个数减少elementCount--;//将elementData[elementCount]这个位置置空,用来添加新元素,位置的元素等着被GC回收elementData[elementCount] = null; /* to let gc do its work */
}
7.3 链表LinkedList
Java中有双链表的实现:LinkedList,它是List接口的实现类。
LinkedList是一个双向链表
,如图所示:
7.3.1 链表与动态数组的区别
动态数组底层的物理结构是数组,因此根据索引访问的效率非常高。但是非末尾位置的插入和删除效率不高,因为涉及到移动元素。另外添加操作时涉及到扩容问题,就会增加时空消耗。
链表底层的物理结构是链表,因此根据索引访问的效率不高,即查找元素慢。但是插入和删除不需要移动元素,只需要修改前后元素的指向关系即可,所以插入、删除元素快。而且链表的添加不会涉及到扩容问题。
7.3.2 LinkedList源码分析
jdk1.8.0_271中:
//属性
transient Node<E> first; //记录第一个结点的位置
transient Node<E> last; //记录当前链表的尾元素
transient int size = 0; //记录最后一个结点的位置//构造器
public LinkedList() {
}//方法:add()相关方法
public boolean add(E e) {linkLast(e); //默认把新元素链接到链表尾部return true;
}void linkLast(E e) {final Node<E> l = last; //用 l 记录原来的最后一个结点//创建新结点final Node<E> newNode = new Node<>(l, e, null);//现在的新结点是最后一个结点了last = newNode;//如果l==null,说明原来的链表是空的if (l == null)//那么新结点同时也是第一个结点first = newNode;else//否则把新结点链接到原来的最后一个结点的next中l.next = newNode;//元素个数增加size++;//修改次数增加modCount++;
}//其中,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;}
}
//方法:获取get()相关方法
public E get(int index) {checkElementIndex(index);return node(index).item;
} //方法:插入add()相关方法
public void add(int index, E element) {checkPositionIndex(index);//检查index范围if (index == size)//如果index==size,连接到当前链表的尾部linkLast(element);elselinkBefore(element, node(index));
}Node<E> node(int index) {// assert isElementIndex(index);/*index < (size >> 1)采用二分思想,先将index与长度size的一半比较,如果index<size/2,就只从位置0往后遍历到位置index处,而如果index>size/2,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历。*///如果index<size/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;}
}//把新结点插入到[index]位置的结点succ前面
void linkBefore(E e, Node<E> succ) {//succ是[index]位置对应的结点// assert succ != null;final Node<E> pred = succ.prev; //[index]位置的前一个结点//新结点的prev是原来[index]位置的前一个结点//新结点的next是原来[index]位置的结点final Node<E> newNode = new Node<>(pred, e, succ);//[index]位置对应的结点的prev指向新结点succ.prev = newNode;//如果原来[index]位置对应的结点是第一个结点,那么现在新结点是第一个结点if (pred == null)first = newNode;elsepred.next = newNode;//原来[index]位置的前一个结点的next指向新结点size++;modCount++;
}//方法:remove()相关方法
public boolean remove(Object o) {//分o是否为空两种情况if (o == null) {//找到o对应的结点xfor (Node<E> x = first; x != null; x = x.next) {if (x.item == null) {unlink(x);//删除x结点return true;}}} else {//找到o对应的结点xfor (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item)) {unlink(x);//删除x结点return true;}}}return false;
}
E unlink(Node<E> x) {//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 {//被删除结点不是第一个结点//被删除结点的上一个结点的next指向被删除结点的下一个结点prev.next = next;//断开被删除结点与上一个结点的链接x.prev = null;//使得GC回收}//如果被删除结点的后面没有结点,说明被删除结点是最后一个结点if (next == null) {//那么被删除结点的上一个结点变为最后一个结点last = prev;} else {//被删除结点不是最后一个结点//被删除结点的下一个结点的prev执行被删除结点的上一个结点next.prev = prev;//断开被删除结点与下一个结点的连接x.next = null;//使得GC回收}//把被删除结点的数据也置空,使得GC回收x.item = null;//元素个数减少size--;//修改次数增加modCount++;//返回被删除结点的数据return element;
}public E remove(int index) { //index是要删除元素的索引位置checkElementIndex(index);return unlink(node(index));
}
启示与开发建议
1、Vector基本不使用了
2、ArrayList底层使用数组结构:查找和添加(尾部添加)操作效率高,时间复杂度为O(1)。
删除和插入操作效率低,时间复杂度 为O(n)。
LinkedList底层使用双向链表结构:删除和插入操作效率高,时间复杂度为O(1)。
查找和添加(尾部添加)操作效率高,时间复杂度为O(n)(有可能添加操作是O(1))
3、在选择了ArrayList的前提下:new ArrayList():底层创建长度为10的数组。
如果开发中,大体确认数组的长度,则推荐使用ArrayList(int capacity)这个构造器,避免底层的扩容、复制数组的操作。
相关文章:

【Java】 —— 数据结构与集合源码:Vector、LinkedList在JDK8中的源码剖析
目录 7.2.4 Vector部分源码分析 7.3 链表LinkedList 7.3.1 链表与动态数组的区别 7.3.2 LinkedList源码分析 启示与开发建议 7.2.4 Vector部分源码分析 jdk1.8.0_271中: //属性 protected Object[] elementData; protected int elementCount;//构造器 public …...

YOLOv5改进——添加SimAM注意力机制
目录 一、SimAM注意力机制核心代码 二、修改common.py 三、修改yolo.py 三、建立yaml文件 四、验证 一、SimAM注意力机制核心代码 在models文件夹下新建modules文件夹,在modules文件夹下新建一个py文件。这里为simam.py。复制以下代码到文件里面。 import…...

SQL 自学:表别名的运用与对被联结表使用聚集函数
一、表别名的概念与作用 (一)表别名的定义 表别名是为表指定的临时名称,在 SQL 查询中使用别名可以简化表名,提高代码的可读性和可维护性。当表名较长或在复杂的查询中多次引用表时,使用表别名可以避免重复输入冗长的…...

jmeter学习(2)变量
1)用户定义的变量 路径:添加-》配置元件-》用户定义的变量 用户定义的变量是全局变量,可以跨线程组被调用,但在启动运行时获取一次值,在运行过程中不再动态获取值。 注意的是,如果在某个线程组定义了全…...

【C#生态园】C#文件压缩库全面比较:选择最适合你的库
从核心功能到API概览:深度解析六大C#文件压缩库 前言 在软件开发过程中,文件的压缩和解压缩是一个常见的需求。针对C#开发者而言,选择合适的文件压缩库可以极大地简化开发工作。本文将介绍几个常用的C#文件压缩库,包括其核心功能…...

【测试】接口测试与接口自动化
壹、接口测试基础 一、接口测试概念 I、基础概念 是测试系统组件间接口的一种测试。 主要用于检测外部系统与系统间、内部子系统间的交互点;测试重点检查数据的交换、传递和控制管理过程,以及系统间的相互逻辑依赖关系。 内部接口调用相当于函数调用&am…...

Android设置边框圆角
在Android开发中,圆角设计十分常见,那么实现边框圆角有几种形式呢? 文章目录 设置圆角边框样式使用ClipToOutline进行裁切最后 设置圆角边框样式 常见的方式是在drawable文件夹下设置一个xml文件的边框样式,比如 <shape andro…...

SpringBoot项目打成jar包,在其他项目中引用
1、首先新建一个SpringBoot工程 记得要将Gradle换成Maven 2、新建一个要引用的方法 3、打包的时候要注意: ① 不能使用springboot项目自带的打包插件进行打包,下面是自带的: ②要换成传统项目的maven打包,如下图: 依…...

【音频可视化】通过canvas绘制音频波形图
前言 这两天写项目刚好遇到Ai对话相关的需求,需要录音功能,绘制录制波形图,写了一个函数用canvas实现可视化,保留分享一下,有需要的直接粘贴即可,使用时传入一个1024长的,0-255大小的Uint8Arra…...

解决github每次pull push输入密码问题
# 解决git pull/push每次都需要输入密码问题 git bash进入你的项目目录,输入: git config --global credential.helper store然后你会在你本地生成一个文本,上边记录你的账号和密码。配置项写入到 "C:\Users\用户名\ .gitconfig" …...

Java重修笔记 第六十四天 坦克大战(十四)IO 流 - 标准输入输出流、InputStreamReader 和 OutputStreamWriter
标准输入输出流 1. System.in 标准输入流 本质上是一个InputString,对应键盘,表示从键盘输入。 定义:public final static InputStream in null; 所以 Scanner scanner new Scanner(System.in); 会从键盘中获取数据 2. System.out 标准输…...

prctl的函数和pthread_self函数
1.prctl的函数原型如下: #include<sys/prctl.h> prctl(PR_SET_NAME, “process_name”);第一个参数是操作类型,指定PR_SET_NAME(对应数字15),即设置进程名; 第二个参数是进程名字符串,…...

Vim 命令行模式下的常用命令
Vim 命令行模式下的常用命令 文件操作: :w :保存当前文件。:w filename :将当前内容另存为指定的 filename 。:q :退出 Vim,如果文件有修改但未保存,会提示错误。:q! :强制退出 Vim,…...

【动态规划-最长递增子序列(LIS)】力扣2826. 将三个组排序
给你一个整数数组 nums 。nums 的每个元素是 1,2 或 3。在每次操作中,你可以删除 nums 中的一个元素。返回使 nums 成为 非递减 顺序所需操作数的 最小值。 示例 1: 输入:nums [2,1,3,2,1] 输出:3 解释: …...

Elastic Stack--16--ES三种分页策略
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 方式一:from size实现原理使用方式优缺点 方式二:scroll实现原理使用方式优缺点 方式三:search_after实现原理使用方式优缺点 三…...

[LeetCode] 315. 计算右侧小于当前元素的个数
题目描述: 给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。 题目链接: . - 力扣(LeetCode) 题目主要思路&a…...

【hot100-java】二叉树展开为链表
二叉树篇。 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* …...

如何在在 YOLOv3模型中添加Attention机制
在YOLOv3模型中添加Attention机制需要以下几个步骤: 1. 规定格式 当添加新的模块(如Attention机制模块)时,需要像定义[convolutional]、[maxpool]等层在cfg文件中的格式一样,对新模块进行格式规定。例如对于SE模块&a…...

单点登录Apereo CAS 7.1安装配置教程
笔者目前正在做一个单点登录的课题,历时较长总算摸到一些门路,其中的辛酸不易按下不表。截至本文发布,CAS的最新版本为7.1。由于涉及到课题内容,而且内容比较新,整理试验不容易,暂时只对VIP开放,后续课题完成后会完全开放,敬请谅解。 CAS项目区别 在CAS的项目选择上,…...

windows C++-移除界面工作线程(一)
本文档演示了如何使用并发运行时将 Microsoft 基础类 (MFC) 应用程序中由用户界面 (UI) 线程执行的工作移动到工作线程。 本文档还演示了如何提高冗长绘制操作的性能。 通过将阻塞性操作(例如,绘制)卸载到工作线程来从 UI 线程中移除工作&am…...

Qt小bug — LINK : fatal error LNK1158: 无法运行“rc.exe“
Qt小bug —— LINK :fatal error LNK1158:无法运行"rc.exe" 环境 Qt 5.14.2 MSVC 2015 x64 现象 解决 在电脑上找到rc.exe 和rcdll.dll (一般在C:\Program Files(x86)\Windows Kits*\bin\x64下面)拷贝到 C:\Qt\Qt5…...

c++小游戏
目录 狼人杀 走迷宫 炸弹人 贪吃蛇 飞翔的小鸟 跑酷 吃豆人 飞机大战 人生模拟器 坦克大战 修仙模拟器 搜集了一些小游戏,名字下是个人是个人喜欢度,可供参考~ 狼人杀 ❤❤❤❤ #include<bits/stdc.h> #include<cstdio> #incl…...

k8s为什么用Calico
Calico是一种开源的网络和安全解决方案,主要用于容器、虚拟机、宿主机之间的网络连接。 它支持Kubernetes、OpenShift、Docker EE、OpenStack等PaaS或IaaS平台,提供高效的网络通信和安全控制功能12。 Calico的核心组件包括Felix、etcd、BIRD等。F…...

HashMap 和 Hashtable 有什么区别?
HashMap和Hashtable都是Java中常用的存储键值对的集合类,它们都实现了Map接口,但二者之间存在一些显著的区别。以下是对HashMap和Hashtable区别的详细归纳: 一、线程安全性 HashMap:是非线程安全的,即多个线程可以同…...

【机器学习】深度学习、强化学习和深度强化学习?
深度学习、强化学习和深度强化学习是机器学习的三个重要子领域。它们有着各自独特的应用场景和研究目标,虽然都属于机器学习的范畴,但各自的实现方式和侧重点有所不同。 1. 深度学习(Deep Learning) 深度学习是一种基于神经网络的…...

fastadmin 多商户模式下侧边栏跳转路径BUG
记录:仅作自己项目记录,在一个域名下部署多套项目时,若不是多商户模式项目会出现跳转路径问题。 修改 \manystore\library\Auth.php 文件的 getSidebar 方法 // 1 改为: $v[url] isset($v[url]) && $v[url] ? $v[url]…...

java内置的四种函数式接口
供给型:Supplier 无入参,有返回值。 FunctionalInterface public interface Supplier<T> {T get();}消费型:Consumer 有入参,无返回值。 FunctionalInterface public interface Consumer<T> {void accept(T t);de…...

如何获取 uni-app 应用发布所需的证书、私钥与配置文件
引言 在开发和发布iOS应用时,开发者常常会面临一系列复杂的证书、私钥密码以及配置文件的管理问题。这些配置不仅影响到应用的开发调试,还决定了应用是否能够顺利通过审核并发布到App Store。对于使用uni-app进行开发的开发者来说,自动生成的…...

TCP网络通信——多线程
前面分别用多进程和多路复用完成了TCP网络通信,本文就来讲讲多线程的TCP通信。首先来了解一下线程的概念: 1、线程是进程的执行路线,它是进程内部的控制序列,或者说线程是进程的一部分(进程是一个资源单位,线程是执行单…...

【exp报错注入】
整数范围 最大整数 exp 函数介绍 报错盲注注入 payload分析 709C-ASCII 值就等于我们下面的 7091-1 ,C就是我们要猜的值,当我们猜测的值和ASCII码相等时,那么exp就不会出现报错,因为1-1还是等于709: 练习 id1 an…...