前言

今天我们通过分析LinkedList的源码,来学习一下它内部是如何添加、查询以及删除元素的。同时在阅读源码时,也要思考以下几个问题。

LinkedList的底层数据结构是什么?

与ArrayList有什么区别?

LinkedList是线程安全的吗?

继承关系

public class LinkedList

extends AbstractSequentialList

implements List, Deque, Cloneable, java.io.Serializable

{

//......

}

复制代码

首先我们来看一下LinkedList的继承关系,可以看出它继承自AbstractSequentialList。并且实现List、Deque、Cloneable以及Serializable接口,然后我们再来看一下它的核心字段。

核心字段

//表示LinkedList内部的元素数量

transient int size = 0;

//表示头结点

transient Node first;

//表示尾节点

transient Node last;

复制代码

从源码中可以看出LinkedList中存在两个特殊的节点,分别是头结点和尾节点。那我们是不是就能猜到LinkedList底层结构是一个双向循环链表?到底是不是,我们慢慢分析。

构造方法

//创建了一个空列表

public LinkedList() {

}

//这个构造方法传入一个集合,然后将该集合的元素全部添加的链表中

public LinkedList(Collection extends E> c) {

this();

addAll(c);

}

复制代码

可以看出构造方法比较简单,我们再来看一下Node类。

Node

private static class Node {

E item; //元素

Node next; //后节点

Node prev; //前节点

Node(Node prev, E element, Node next) {

this.item = element;

this.next = next;

this.prev = prev;

}

}

复制代码

Node节点类是LinkedList中一个私有的静态内部类,包含元素、前节点和后节点。

添加

添加一个节点

public boolean add(E e) {

linkLast(e);

return true;

}

void linkLast(E e) {

//l为尾节点

final Node l = last;

//创建一个以l节点为前节点,数据为e,后节点为null的Node节点,此时newNode为尾节点

final Node newNode = new Node<>(l, e, null);

//更新尾节点

last = newNode;

if (l == null)

first = newNode;

else

l.next = newNode;

size++;

modCount++;

}

复制代码

从源码中可以看出LinkedList添加一个元素是从链表尾部进行添加,添加步骤如下:

将尾节点赋值l节点

创建一个以l节点为前节点,数据为e,后节点为null的Node节点

更新尾节点

判断l节点是否为空

size++、modCount++

通过指定index添加

public void add(int index, E element) {

//判断index是否越界

checkPositionIndex(index);

if (index == size)

//此时表示在链表尾部添加

linkLast(element);

else

linkBefore(element, node(index));

}

void linkBefore(E e, Node succ) {

// succ表示index对应的节点,pred表示succ前节点

final Node pred = succ.prev;

//newNode的前节点为pred,后节点为succ

final Node newNode = new Node<>(pred, e, succ);

//succ的前节点为newNode

succ.prev = newNode;

//如果pred为null,说明succ原来是头结点,而现在succ的前节点为newNode,所以现在头结点是newNode

if (pred == null)

first = newNode;

else

//pred的后节点为newNode

pred.next = newNode;

size++;

modCount++;

}

复制代码

修改

首先我们来看通过指定索引来修改Node数据,源码如下

public E set(int index, E element) {

//检查是否数组越界

checkElementIndex(index);

//通过node方法来获得对应得Node节点

Node x = node(index);

//保存旧数据

E oldVal = x.item;

//赋值新数据

x.item = element;

//返回旧数据

return oldVal;

}

复制代码

可以看出修改数据主要分为以下几步:

获得index对应的Node节点

保存Node节点旧数据

赋值新数据

获取

public E get(int index) {

checkElementIndex(index);

return node(index).item;

}

复制代码

可以看出get()方法内部只有两行代码,我们分别来看一下都是做了什么操作。

checkElementIndex(index)

private void checkElementIndex(int index) {

if (!isElementIndex(index))

throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

private boolean isElementIndex(int index) {

return index >= 0 && index < size;

}

复制代码

可以看出checkElementIndex()方法主要是来判断index是否数组越界,如果越界就抛出对应的异常。

node(index)

Node node(int index) {

if (index < (size >> 1)) {

Node x = first;

for (int i = 0; i < index; i++)

x = x.next;

return x;

} else {

Node x = last;

for (int i = size - 1; i > index; i--)

x = x.prev;

return x;

}

}

复制代码

可以看出node()方法通过判断index是处于前半段还是后半段,来查找对应的Node节点。通过折半查找提升了一定的效率。

删除

删除指定索引对应的节点

public E remove(int index) {

checkElementIndex(index);

//传入index对应的Node节点

return unlink(node(index));

}

E unlink(Node x) {

//获取Node节点数据

final E element = x.item;

//获取后节点

final Node next = x.next;

//获取前节点

final Node 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;

}

//将节点数据置为null

x.item = null;

size--;

modCount++;

return element;

}

复制代码

然后我们再来简单看下LinkedList中其他remove()方法,具体如下:

//删除第一个节点数据

public E removeFirst() {

final Node f = first;

if (f == null)

throw new NoSuchElementException();

return unlinkFirst(f);

}

//删除最后一个节点数据

public E removeLast() {

final Node l = last;

if (l == null)

throw new NoSuchElementException();

return unlinkLast(l);

}

复制代码

总结

通过分析LinkedList的源码,我们可以知道LinkedList在插入和删除上有着比较大的优势,这也符合链表的特性。而且通过判断index在前半段还是后半段,使用折半查找的方法来获得对应的Node节点,提升了一定的效率。

参考资料

java linkedlist 更新_Java填坑系列之LinkedList相关推荐

  1. MySQL填坑系列--Linux平台下MySQL区分大小写问题

    问题引入 大家好,我是软件大盗(道),下面开始我们的<MySQL填坑系列>. 笔者最近又在MySQL的边缘试探,然后,试探着,试探着就报错了. 情景还原 书接上文,系统连接数据库时报错:找 ...

  2. Java填坑系列之SparseArray

    前言 今天我们来了解一下与HashMap类似的数据结构SparseArray,并分析下它的源码实现.在分析源码的过程中,我们带着以下几个问题来看. SparseArray底层数据结构是什么? Spar ...

  3. java游戏代码_Java与Kotlin系列文章之性能问题详解

    作者丨Jakub Anioła 译者丨姜雨生 策划丨田晓旭 随着对 Kotlin 越来越深入的了解,我发现市面上关于 Kotlin 方面,比较深入的资料几乎是 0,所以我决定,将 Kotlin 各个方 ...

  4. java 动态更新_java动态更新枚举类

    工作中遇到需要对枚举类的值进行动态更新 手动改不现实也不方便 现记录下来方便以后学习使用 1.在工程utils包中添加动态更新枚举类得工具类(根据自己得项目,放到指定位置调用就可以) 2.一开始陷入了 ...

  5. android小米定位,Android填坑系列:在小米系列等机型上放开定位权限后的定位请求弹框示例...

    背景 近期因实际项目需要,在特定操作下触发定位请求,取到用户位置及附近位置. 问题: 经初步选型,最终决定接入百度定位,按照百度定位SDK Android文档,接入过程相对顺利. 但随后发现,在小米系 ...

  6. java数组更新_java数组

    数组无论在哪种编程语言中都算是最重要的数据结构之一,同时不同语言的实现及处理也不尽相同.但凡写过一些程序的人都知道数组的价值及理解数组的重要性,与链表一道,数组成为了基本的数据结构.尽管Java提供了 ...

  7. java jlist 更新_java – 更新JList

    我现在已经创建了一个基于arraylist的JList,并且由defaultlistmodel填充.该列表将在人们连接到服务器时添加人员,但不会显示连接的人或连接后的人.所以,我必须更新JList. ...

  8. java正则表达式爬虫_Java简单爬虫系列(3)---正则表达式和Java正则API的使用

    上一篇内容写了如何请求资源,那么资源请求下载之后我们就要对它就行解析了,解析之前我们先熟悉一下正则表达式 正则表达式在平常使用时还是很广泛的,比如说表单输入验证,验证手机号邮箱之类,Java的字符串匹 ...

  9. java数组更新_java刷新数组到jList

    好吧,所以我有一个JList和内容提供了一个数组.我知道如何将元素添加到数组,但我想知道如何刷新JList ...或者甚至有可能吗?我试过谷歌. :\java刷新数组到jList import jav ...

最新文章

  1. 文件查找命令find的使用
  2. 单片机值得学吗?会单片机能找什么工作?
  3. 最长回文子串-三种DP实现
  4. html音频从10秒播放至30秒,基于Arduino制作SD卡音乐播放器
  5. java 编程工具_Java开发工具可以促进编程!
  6. 求你了,别再说Java对象都是在堆内存上分配空间的了!
  7. iPhone 5用户们,苹果又喊你更新了,不然可能会变砖!
  8. python基本词汇的特点_开课吧老师为你讲解 Python都有什么优点?
  9. Spark DataFrame入门详解
  10. 18. jQuery - 尺寸
  11. rabbitmq 一个生产者多个消费者_RabbitMQ入门学习系列(二),单生产者消费者
  12. jquery全国省市县三级联动
  13. junit4报测试类class not found
  14. linux内核源码分析之虚拟内存映射
  15. 跟锦数学200217 厦门大学2019年数学分析考研试题4 (解答见跟锦数学微信公众账号)...
  16. PEP 635 – Structural Pattern Matching: Motivation and Rationale
  17. 微软Kinect是怎么做到的
  18. 抢菜捡漏工具(PrintScreenCatchImg)
  19. CTF easycap Banmabanma
  20. 计算机锁屏打不开,电脑点锁屏锁不了怎么办

热门文章

  1. 决策树可视化案例python_Python决策树demo可视化
  2. 内存泄漏的原因及解决办法_内存泄漏的场景和解决办法
  3. linux ping策略打开_Linux Iptables允许或阻止ICMP ping请求
  4. vue中接受后台传过来的图片文件流blob前端进行展示实现方法
  5. 标准气压高度与修正海平面气压的区别
  6. @Autowired 与@Resource的区别
  7. springboot线程池使用
  8. Git实现从本地添加项目到远程仓库
  9. Swagger注解-@ApiImplicitParams 和 @ApiImplicitParam
  10. 红帽yum安装httpd出现错误(This system is not registered to Red Hat Subscription Management. You can use subs)