【Java】 —— 数据结构与集合源码:Vector、LinkedList在JDK8中的源码剖析

news2024/11/25 10:31:16

目录

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元素的个数增1
    elementData[elementCount++] = e;
    return true;
}

private void ensureCapacityHelper(int minCapacity) {
     //看是否超过了当前数组的容量
    if (minCapacity - elementData.length > 0)
        grow(minCapacity); //扩容
}

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length; //获取目前数组的长度
    //如果capacityIncrement增量是0,新容量 = oldCapacity的2倍
    //如果capacityIncrement增量是不是0,新容量 = oldCapacity + capacityIncrement增量;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    //如果按照上面计算的新容量还不够,就按照你指定的需要的最小容量来扩容minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //如果新容量超过了最大数组限制,那么单独处理
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    //把旧数组中的数据复制到新数组中,新数组的长度为newCapacity
    elementData = 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);
    else
        linkBefore(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;
    else
        pred.next = newNode;//原来[index]位置的前一个结点的next指向新结点
    size++;
    modCount++;
}

//方法:remove()相关方法
public boolean remove(Object o) {
    //分o是否为空两种情况
    if (o == null) {
        //找到o对应的结点x
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);//删除x结点
                return true;
            }
        }
    } else {
        //找到o对应的结点x
        for (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)这个构造器,避免底层的扩容、复制数组的操作。

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

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

相关文章

2024ccna考试时间?新手小白看这些就够了

2024年想要考取ccna证书的新手小白们&#xff0c;是不是正在为考试时间而烦恼呀&#xff0c;其实ccna的考试时间其实非常灵活&#xff0c;并不需要像其他考试那样死记硬背固定的日期。那么小编马上就给大家说说2024ccna考试时间&#xff0c;并且附带一些考试内容&#xff0c;让…

LeetCode[简单] 70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 思路 利用滚动数组 public class Solution {public int ClimbStairs(int n) { //滚动数组int f0 0, f1 0, f2 1;for(int i 1; i < n; i){…

拱坝与重力坝:结构特性与应用差异的深度解析

在水利工程领域&#xff0c;坝体结构的选择对于工程的稳定性、安全性以及经济效益具有至关重要的影响。拱坝与重力坝作为两种主要的坝型&#xff0c;各自具有独特的结构特性和应用场景。本文旨在深入探讨拱坝与重力坝的区别&#xff0c;从工程特点、坝体结构型式、材料选用、水…

国内如何下载谷歌浏览器(chrome浏览器)历史版本和chromedriver驱动,长期更新,建议收藏

众所周知&#xff0c;google是一直被国内屏蔽的&#xff0c;有时候想要下载个chrome浏览器都要去外网&#xff0c;或者到处去搜索才能下载到。因为下载chrome浏览器的这个网址&#xff1a;google.com/chrome/ 在国内是一直被屏蔽掉的。 今天主要讲解的是国内ChromeDriver 的下…

ET实现游戏中的红点提示系统(服务端)

目录 ☝&#x1f913;前言 ☝&#x1f913;一、实现思路 ☝&#x1f913;二、实现 &#x1f920;2.1 定义红点组件 &#x1f920;2.2 定义Proto消息体 &#x1f920;2.3 RedPointComponentSystem ☝&#x1f913;难点 ☝&#x1f913;前言 当我们闲来无事时打开农药想消…

数据结构-5.3.二叉树的定义和基本术语

一.二叉树的基本概念&#xff1a; 树是一种递归定义的数据结构&#xff0c;因此二叉树是递归定义的数据结构。 二.二叉树的五种状态&#xff1a; 三.几个特殊的二叉树&#xff1a; 1.满二叉树&#xff1a;结点总数就是通过等比数列公式求出来的&#xff0c;首项为1即根节点&a…

【网络协议】TCP协议常用机制——延迟应答、捎带应答、面向字节流、异常处理,保姆级详解,建议收藏

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;计算机网络那些事 前几篇文章&#xff0c;博主带大家梳理了一下TCP协议的几个核心机制&#xff0c;比如保证可靠性的 确认应答、超时重传 机制&#xff0c;和提高传输效率的 滑动窗口及其相关优化机…

C++ Builder XE12关于KonopkaControls与TMS VCL UI Pack组件的安装

1、先打开open project&#xff0c;选中安装的组件工程&#xff0c;并打开。 2、在option中设置 3、点击编译并进行安装install

洞察AI趋势:智享AI直播,打造专属你的数字化直播AIGC系统!

洞察AI趋势&#xff1a;智享AI直播&#xff0c;打造专属你的数字化直播AIGC系统&#xff01; 在当今这个日新月异的数字时代&#xff0c;人工智能&#xff08;AI&#xff09;已不再是遥不可及的未来科技&#xff0c;而是正深刻改变着我们生活、工作的每一个角落。其中&#xf…

幽默视频下载网站推荐

在快节奏的生活中&#xff0c;搞笑视频无疑是缓解压力的良药&#xff0c;不论是自制搞笑视频还是寻找素材来增添创作的趣味性&#xff0c;找到合适的视频素材至关重要。幸运的是&#xff0c;网络上有许多优秀的网站能够满足这一需求。以下是8个适合下载幽默搞笑视频素材的网站&…

华为 静态路由和bfd 侦测的实验

实验要求 sw1 上业务地址192.168.1.1/24 SW3 业务地址192.168.2.1/24 正常情况下走主链路&#xff0c;不正常的情况下走备份链路 2 配置 这是基本地址配置 开启了bfd 本端地址为 10.1.1.1 对端地址是10.1.1.2 关键是discrimination 分辨参数 …

静态路由和nqa 联动实验

nqa 配置 1 test 断端口 很明显是切换到备机上了

Word中如何删除表格下一页的空白页

Reference&#xff1a; [1] Word空白页怎么都删除不掉&#xff1f;用这6个方法随便删&#xff01; - 知乎 (zhihu.com)

前端埋点学习

前端埋点 前端数据埋点是在前端页面中通过代码的方式手机用户行为数据和页面性能的过程&#xff0c;通过在页面中插入指定的代码&#xff0c;实现实时监控用户在页面上的操作行为。 通常包括一下事件 定义事件: 定义需要手机的数据事件&#xff0c;如点击&#xff0c;浏览等添…

基于Docker的FRP内网穿透部署

服务器搭建&#xff08;FRPS&#xff09; 创建配置文件 # 创建存放目录 sudo mkdir /etc/frp # 创建frps.ini文件 nano /etc/frp/frps.ini frps.ini内容如下&#xff1a; [common] # 监听端口 bind_port 7000 # 面板端口 dashboard_port 7500 # 登录面板账号设置 dashboa…

【进阶OpenCV】 (9)--摄像头操作--->答题卡识别改分项目

文章目录 项目&#xff1a;答题卡识别改分1. 图片预处理2. 描绘轮廓3. 轮廓近似4. 透视变换5. 阈值处理6. 找每一个圆圈轮廓7. 将每一个圆圈轮廓排序8. 找寻所填答案&#xff0c;比对正确答案8.1 思路8.2 图解8.3 代码体现 9. 计算正确率 总结 项目&#xff1a;答题卡识别改分 …

数论与同余 - 离散数学系列(七)

目录 1. 整数的性质 整除与因数 最大公约数与最小公倍数 2. 欧几里得算法 算法步骤 3. 模运算与同余 模运算 同余关系 同余的性质 4. 数论在密码学中的应用 RSA 加密算法 5. 实际应用场景 1. 数字签名 2. 哈希函数与数据完整性 3. 密钥交换 6. 例题与练习 例题…

Java:方法详解

目录 一.什么是方法(method) 二.方法定义 三.方法中实参和形参的关系 四.方法重载 五.递归 一.什么是方法(method) 方法就是一个代码片段&#xff0c;再C语言中我们曾经学过一个类似的方式——函数&#xff0c;他们都是将具有独立功能的代码组织成一个整体&#xff0c;形成…

论文阅读 BLIP-2

Bootstrapping Language-Image Pre-training with Frozen Image Encoders and Large Language Models 使用冻结的图像编码器和大型语言模型进行语言-图像预训练的引导 BLIP-2 通过一个轻量级的查询变换器弥合了模态之间的差距。 Querying Transformer 第一阶段通过冻结的图像编…

高纯PGME和PGMEA市场规模:2023年全球市场规模为6.43亿美元

研究对象&#xff1a;高纯PGME和PGMEA行业 高纯PGME&#xff08;丙二醇甲醚&#xff09;和PGMEA&#xff08;丙二醇甲醚醋酸酯&#xff09;是重要的有机溶剂&#xff0c;广泛应用于半导体和面板制造领域&#xff0c;尤其是在光刻胶和清洗剂中。高纯度的PGME和PGMEA对于确保电子…