Java集合(五)LinkedList底层扩容源码分析

news2024/11/17 1:46:02

LinkedList的全面说明:

(1)LinkedList底层实现了双向链表和双端队列特点

(2)可以添加任意元素(元素可以重复),包括null.

(3)线程不安全,没有实现同步

LinkedList的底层操作机制:

双向链表的创建:

(1)LinkedList底层维护了一个双向链表

size记录了这个链表里面有多少个元素。

(2)LinkedList中维护了两个属性first和last分别指向首节点和尾节点

相当于底层维护了一个链表,链表存在三个元素。它的属性first直接指向了我们维护的链表的第一个元素,last属性指向链表最后的。而链表所存有的对象的属性为node。Node为内部类

 (3)每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双向链表

(4)所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。进行删除元素时,我们只需要让所删除的后一个节点的prev指向所删除元素的前面元素的prev的节点,让所删除的前一个节点的next指向删除元素的后一个节点的next。

(5)模拟一个简单的双向链表

 我们通过代码来实现双向链表:
 

package com.rgf.list;
@SuppressWarnings({"all"})
public class LinkedList01 {
    public static void main(String[] args) {
        //模拟一个简单的双向链表:
        Node jack = new Node("jack");
        Node jdbc = new Node("jdbc");
        Node java = new Node("java");

        //连接三个结点,形成双向链表
        //实现jack-->jdbc-->java
        jack.next = jdbc;
        jdbc.next = java;
        //实现java-->jdbc-->jack
        java.pre = jdbc;
        jdbc.pre = jack;
        System.out.println("====从头到尾进行遍历=====");
        Node first = jack;  //first引用指向jack,就是双向链表的头结点
        Node last = java;   //让last引用指向java,就是双向链表的尾结点

        //演示,从头到尾进行遍历:
        while (true) {
            if (first == null) {
                break;
            }
            //输出first 信息
            System.out.println(first);
            first = first.next;
        }
        System.out.println("=====从尾到头进行遍历======");
        //演示,从尾到头进行遍历:
        while (true) {
            if (last == null) {
                break;
            }
            //输出last信息
            System.out.println(last);
            last = last.pre;
        }
        //演示:链表的添加对象/数据,是多么的方便
        //要求:在jack和jdbc之间插入一个对象,叫rgf
        //1.先创建一个Node结点,name叫rgf
        Node rgf = new Node("rgf");
        //下面就把smith加入到双向链表了
        jack.next = rgf;
        rgf.next = jdbc;
        jdbc.pre = rgf;
        rgf.pre = jack;
        System.out.println("====从头到尾进行遍历=====");
        //让first再次指向第一个人
        first = jack;  //first引用指向jack,就是双向链表的头结点
        last = java;   //让last引用指向java,就是双向链表的尾结点
        //演示,从头到尾进行遍历:
        while (true) {
            if (first == null) {
                break;
            }
            //输出first 信息
            System.out.println(first);
            first = first.next;
        }
        System.out.println("=====从尾到头进行遍历======");
        //演示,从尾到头进行遍历:
        while (true) {
            if (last == null) {
                break;
            }
            //输出last信息
            System.out.println(last);
            last = last.pre;
        }

    }

    //定义一个Node类,Node对象,Node表示双向链表的一个结点
    static class Node {
        public Object item;  //真正存放数据的地方
        public Node next;  //指向后一个结点
        public Node pre;   //指向前一个结点

        public Node(Object name) {
            this.item = name;
        }

        @Override
        public String toString() {
            return "Node name=" + item;
        }
    }
}

运行界面如下所示:

 LinkedList底层源码分析:

 LinkedList的增删改查案例:

LinkedList.add的实现代码区如下所示:

LinkedList.add的实现代码区如下所示:

package com.rgf.list;

import java.util.LinkedList;

public class LinkedListCRUD {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        linkedList.add(1);

        System.out.println("linkedList="+linkedList);
    }
}

之后,我们进行debug:

 点进去之后如下所示:当前只是做了一个初始化:

 这时我们进行LinkedList的界面查看如下所示:‘

  之后进入之后我们先进行装箱:

 之后退出来再进入如下所示:

 我们进入了add方法,调用了linkLast,我们只有一个结点,即头结点和尾节点都指向同一个结点。

我们再进行下一步,进入如下所示:

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;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

 我们刚开始所存入的值为1,此时last为null,我们继续往下走的时候,所存放数据的地方,l为空,e=1,next也为空,此时我们的新节点里面有前面为null,存放的值为1,后面的next也为null。last指向这个新节点:newNode,进入if语句后,l为空,所以first也指向newNode,所以这个时候,这个结点,三个(last、first、newNode)都指向他。

我们发现此时的为:

 我们全部运行完毕之后,点进去如下所示:

 我们发现first和last的地址都是513。

我们再此基础上进行添加一行代码,如下所示:

package com.rgf.list;

import java.util.LinkedList;

public class LinkedListCRUD {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        linkedList.add(2);
       
        System.out.println("linkedList="+linkedList);
    }
}

我们进行查看源码如下所示:

 我们发现还是要调用linkLast(e),我们继续进行下一步: 

我们进行理解如下所示: 

 我们运行完毕之后如下所示:

 LinkedList删除结点的代码演示如下所示:

 remove有三种删除方式:remove(),默认为删除第一个。尾入头出

remove(int index)最其中最复杂的,因为他为删除中间的结点比较麻烦

remove(Object o)对链表里面的某一个对象进行比较,然后删除指定的对象。

我们进行代码设计如下所示:

package com.rgf.list;

import java.util.LinkedList;

public class LinkedListCRUD {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        linkedList.add(2);

        System.out.println("linkedList="+linkedList);
        //演示删除结点的源码
        linkedList.remove();  //这里默认删除的为第一个
        System.out.println("linkedList="+linkedList);
    }
}

我们下来进行debug的进行判断:

我们进入remove进行查看: 

 我们再进行下一步进入源码:

 public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

我们发现此时f指向first,指向我们双向列表的第一个结点。而f是不等于空的,防止我们再删除的时候是删除一个空链表的。否则会抛出异常的。

我们真正是靠如下方法进行删除的:

private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

我们进行理解如下所示:

  LinkedList修改结点的代码演示如下所示:

 linkedList.set(1,999);
        System.out.println("linkedList="+linkedList);

我们进行打开断点如下所示:

 我们先进行自由装箱:

我们退出继续进行下一步,我们进入set的方法:

 

我们进入checkElementIndex方法,点击下一步之后如下所示:

 我们进入如下所示:

判断完毕之后我们进入下一行代码:

Node<E> x = node(index);

我们继续进行下一步,之后我们点击进入真实的判断方法: 

 Node<E> node(int index) {
        // assert isElementIndex(index);

        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;
        }
    }

 LinkedList删除结点的代码演示如下所示:

 //演示得到某个结点的源码
        //get(1)是得到双向链表的第二个对象
        Object o = linkedList.get(1);
        System.out.println(o);

我们进入get方法源码如下所示:

 之后我们进行判断checkElementIndex:

 我们再进入isElementIndex方法:

 返回之后,我们进入改行代码:

继续进行下一步之后如下所示:

 Node<E> node(int index) {
        // assert isElementIndex(index);

        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;
        }
    }

 我们下来进行ArrayList与LinkedList的比较: 

底层结构增删的效率改查的效率
ArrayList可变数组

较低

数组扩容

较高
LinkedList双向链表较高,通过链表追加较低

如何选择ArrayList和LinkedList:

(1)如果我们改查的操作多,选择ArrayList

(2)如果我们增删的操作多,选择LinkedList

(3)一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList

(4)在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另一个模块使用的是LinkedList.,也就是说,要根据业务来进行合理选择。

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

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

相关文章

MicroBlaze系列教程(1):AXI_GPIO的使用

文章目录 @[toc]简介常用函数使用示例参考资料工程下载本文是Xilinx MicroBlaze系列教程的第1篇文章。 简介 AXI GPIO是基于AXI-lite总线的一个通用输入输出IP核,可配置为一个或两个通道,每个通道32位,每一位可以通过SDK动态配置成输入或输出方向,支持中断请求,配合中断控…

计算机网络第三章 传输层

本文部分图片&#xff08;PPT截图&#xff09;来自中科大计算机网络top down3.0 目录[TOC]3.1 概述传输层TCP和UDP协议可以在IP协议主机到主机通信的基础上&#xff0c;实现进程到进程之间的通信&#xff08;利用端口号&#xff09;真正实现端到端的通信【通过多路复用于解复用…

b站黑马Vue2后台管理项目笔记——(3)用户列表

说明&#xff1a; 此项目中使用的是本地SQL数据库&#xff0c;Vue2。 其他功能请见本人后续的其他相关文章。 本文内容实现的最终效果如下图&#xff1a; 三.用户列表的开发 目标效果&#xff1a; 点击二级菜单——用户列表&#xff0c;在右侧展示用户列表对应的内容&#xf…

羊了个羊,低配纯前端实现,代码开源

各位朋友们大家好&#xff0c;我是克隆窝。 我属实被“羊了个羊”气到了&#xff0c;就是通不过第二关&#xff0c;迫不得已自己弄了个网页版的“鱼了个鱼” 游戏的玩法非常简单&#xff0c;类似 “消消乐”&#xff0c;从一堆方块中找到相同图案的 3 个方块并消除即可。 文末…

火山引擎边缘计算在云边协同方面的探索与实践

作者&#xff1a;杜怀宇近期&#xff0c;由边缘计算产业联盟&#xff08;ECC&#xff09;主办的2022边缘计算产业峰会&#xff08;ECIS2022&#xff09;以云端直播形式成功举办&#xff0c;峰会以“边云智联 助力行业数字化转型”为主题&#xff0c;汇聚来自全球的商业领袖、国…

JavaScript基础复盘1

JS有三种书写位置&#xff0c;分别为行内&#xff0c;内嵌和外部。 注意&#xff0c;引用外部JS文件&#xff0c;script双标签内部不可以写代码了。 JavaScript输入输出语句 变量 变量的使用 变量再使用时分为两步&#xff1a;1.声明变量 2.赋值 实现用JavaScript接受用户输入…

最近公共祖先

最近公共祖先&#xff08;Lowest Common Ancestor&#xff0c;LCA&#xff09; 指两个点的公共祖先中&#xff0c;离根最远/深度最深的 性质&#xff1a; 1.LCA({u})uLCA\left(\left\{u\right\}\right) uLCA({u})u 2.若uuu是vvv的祖先&#xff0c;当且仅当LCA(u,v)uLCA\left(…

[Lua实战]Skynet-1.如何启动(linux环境启动)[开箱可用]

Skynet-如何启动1.依赖环境:可登录&联网的linux(Centos7)系统(可以是虚拟机)2.yum安装依赖库3.git clone skynet项目4.编译skynet4.1有可能遇到的错误(升级gcc到4.9以上即可解决):5.测试运行skynet6.运行结果最近用到了lua,想了解下云风大神的skynet,在网上看了半天也没入门…

Spire.Pdf for Java v9.1.4 Patcher

Spire.PDF for Java是一种 PDF API&#xff0c;它使 Java 应用程序无需使用第三方SDK 即可读取、写入和保存 PDF 文档。使用这个 Java PDF 组件&#xff0c;开发人员和程序员可以实现丰富的功能&#xff0c;从头开始创建 PDF 文件或完全在 Java 应用程序&#xff08;J2SE 和 J2…

leetcode刷题记录总结-7.二叉树

文章目录零、二叉树理论二叉树的种类满二叉树完全二叉树二叉搜索树平衡二叉搜索树二叉树的存储方式二叉树的遍历方式二叉树的定义总结一、二叉树的遍历[144. 二叉树的前序遍历 ](https://leetcode.cn/problems/binary-tree-preorder-traversal/)题解递归实现迭代实现[94. 二叉树…

如何运营企业网站

企业网站的最终目的是给企业带来效益&#xff0c;树立企业形象。只要有这个目标定位&#xff0c;剩下的工作就是围绕这个定位去做和优化&#xff0c;米贸搜整理如下&#xff1a;1.增强被收录页面的重要性。收录页面的提升不仅仅是数量的提升&#xff0c;质量占据了很高的比重。…

网络安全协议

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 前言 本章将会进行网络安全协议的讲解 一.网络安全 1.什么是网络安全 网络安全&#xff…

导数的概念——“高等数学”

各位CSDN的uu们你们好呀&#xff0c;今天小雅兰的内容是导数的概念&#xff0c;其实在高中时期&#xff0c;我们就已经接触过导数了&#xff0c;但是那个时候学得并不是特别深入&#xff0c;依稀记得&#xff0c;我们当初的导数大题一般都是压轴题&#xff0c;很多学校每次讲解…

Oracle重写sql经典50题(代码)

上次发表的是写的时候遇到的问题&#xff0c;这次备份一下自己的代码&#xff08;很冗余,也不保证正确率&#xff09; mysql50题链接在此 Oracle重写sql经典50题创建数据库和表oracle数据库一个账户就一个库&#xff0c;不需要创建学生表 student创建学生表 student插入学生数据…

A-Star算法探索和实现(五)

本篇摘要在上一篇中我们对寻路的移动规则进行了制定&#xff0c;而在本篇我们将对最佳路径的查找方式进行优化&#xff0c;而这就会涉及到移动规则的检测改进、权值计算的改进、NextNode集的处理改进、寻路逻辑的改进&#xff0c;我们将从上述四个方面进行详细讲解。方案探讨&a…

3.堆排序和比较器

1. 堆 堆结构就是用数组实现的完全二叉树结构&#xff0c;对于结点i&#xff0c;左孩子2*i1、右孩子2*i2、父节点&#xff08;i-1&#xff09;/ 2。 完全二叉树中如果每棵子树的最大值都在顶部就是大根堆 完全二叉树中如果每棵子树的最小值都在顶部就是小根堆堆结构的heapInse…

【C++算法图解专栏】一篇文章带你入门二分算法

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为 0 基础刚入门数据结构与算法的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们一起交流~ &#x1f4da;专栏地址&#xff1a;https://blog.csdn.net/Newin…

步进式PID控制算法及仿真

在较大阶跃响应时&#xff0c;很容易产生超调。采用步进式积分分离PID控制&#xff0c;该方法不直接对阶跃信号进行响应&#xff0c;而是使输入指令信号一步一步地逼近所要求的阶跃信号&#xff0c;可使对象运行平稳&#xff0c;适用于高精度伺服系统的位置跟踪。在步进式PID控…

【数据手册】CH340G芯片使用介绍

1.概述 CH340是一系列USB总线适配器&#xff0c;它通过USB总线提供串行、并行或IrDA接口。CH340G集成电路提供通用的MODEM信号&#xff0c;允许将UART添加到计算机上&#xff0c;或将现有的UART设备转换为USB接口。 2.特征 全速USB接口&#xff0c;兼容USB 2.0接口。使用最小…

Android核心技术【SystemServer加载AMS】

启动流程 Init 初始化Linux 层&#xff0c;处理部分服务 挂载和创建系统文件 解析rc文件&#xff1a; rc 文件中有很多action 进入无限循环 执行action&#xff1a;zygote 进程就在这里启动 for循环去解析参数&#xff0c;根据rc 文件中的action 执行相应操作 检测并重启需要…