java 集合modcount_源码|jdk源码之LinkedList与modCount字段

链表是对上一篇博文所说的顺序表的一种实现。

与ArrayList思路截然不同,链表的实现思路是:

不同元素实际上是存储在离散的内存空间中的。

每一个元素都有一个指针指向下一个元素,这样整个离散的空间就被“串”成了一个有顺序的表。

从链表的概念来讲,它可以算是一种递归的数据结构,因为链表拿掉第一个元素剩下的部分,依然构成一个链表。

时间空间复杂度

通过索引定位其中的一个元素。由于不能像ArrayList那样直接通过计算内存地址偏移量来定位元素,只能从第一个元素开始顺藤摸瓜来数,因此为O(n)。

插入元素。实际上插入元素需要看情况:

如果指定链表中某个元素将其插之其后,那么首先得找出该元素对应的节点,还是O(n)。

如果能够直接指定节点往其后插入(如通过迭代器),那么仅仅需要移动指针即可完成,O(1)。

移除元素。移除和插入类似,得看提供的参数是什么。如果提供的是元素所在的节点,那么也只需要O(1)。

LinkedList的继承结构

f18af3fdc223a6e1381716ac87470af6.png

首先继承结构和ArrayList类似,实现了List接口。

但是,它继承的是继承了AbstractList类的AbstractSequentialList类,

这个类的作用也是给List中的部分函数提供默认实现,只是这个类对链表这种List的实现提供了更多贴合的默认函数实现。

还有可以注意到,LinkedList实现了Deque接口,这也很显然,链表这种结构天然就适合当做双端队列使用。

LinkedList源码分析

节点定义

先来看链表的节点定义:

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;

}

}

可以看到,链表节点除了保存数据外,还需要保存指向前后节点的指针。

这里,链表即有后继指针也有前驱指针,因此这是一个双向链表。

一组节点之间按顺序用指针指起来,就形成了链表的链状结构。

属性和构造函数

transient int size = 0;

transient Node first;

transient Node last;

三个属性,first和last分别指向链条的首节点和尾节点。

这样有个好处,就是链表即可以使用头插法也可以采用尾插法。

size属性跟踪了链表的元素个数。虽然说遍历一遍链表也能统计到元素个数,

但是那是O(n)的费时操作。

因此,我们可以发现链表的size方法是O(1)的时间复杂度。

public LinkedList() {

}

LinkedList的代码很简单,构造函数空空如也。

空表中,first和last字段都为null。

get和set方法

public E get(int index) {

checkElementIndex(index);

return node(index).item;

}

public E set(int index, E element) {

checkElementIndex(index);

Node x = node(index);

E oldVal = x.item;

x.item = element;

return oldVal;

}

Node node(int index) {

// assert isElementIndex(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;

}

}

get和set的思路都是先根据索引定位到链表节点,然后获得或设置节点中的数据,这抽象出了node函数,根据索引找到链表节点。

node的思路也很显然,遍历一遍即可得到。

这里做了一点优化,我们可以发现LinkedList的实现是一个双向链表,并且LinkedList持有了头尾指针。

那么,根据索引和size就可以知道该节点是在链表的前半部分还是后半部分,

从而决定从头节点开始遍历还是从尾节点开始遍历,这样最多遍历 N / 2次即可找到。

添加/删除

public boolean add(E e) {

linkLast(e);

return true;

}

public void add(int index, E element) {

checkPositionIndex(index);

if (index == size)

linkLast(element);

else

linkBefore(element, node(index));

}

void linkLast(E e) {

final Node l = last;

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

last = newNode;

if (l == null)

first = newNode;

else

l.next = newNode;

size++;

modCount++;

}

void linkBefore(E e, Node succ) {

final Node pred = succ.prev;

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

succ.prev = newNode;

if (pred == null)

first = newNode;

else

pred.next = newNode;

size++;

modCount++;

}

添加/删除的思路都类似,删除的代码就不贴了。如果能够提供需要被操作的节点,就能直接移动下指针,O(1)完成。否则就需要遍历找到这个节点再操作。

需要关注两点:

有的操作是操作头指针,有的操作是操作尾指针。但是不管操作哪一个,都需要维护另外一个指针及size的值。

如果是删除,删除后及时把相关节点的item字段置为null,以帮助gc能更快的释放内存。

modCount字段分析

之前阅读ArrayList的代码时发现了modCount这一字段,它是定义在AbstractList类中的。之前不知道它起到什么作用,这次给弄明白了。

迭代器

迭代器迭代中表被修改

考虑以下这段代码:

List list = new LinkedList<>();

Iterator it = list.listIterator();

list.add(1);

it.next();

在迭代器创建之后,对表进行了修改。这时候如果操作迭代器,则会得到异常java.util.ConcurrentModificationException。

这样设计是因为,迭代器代表表中某个元素的位置,内部会存储某些能够代表该位置的信息。当表发生改变时,该信息的含义可能会发生变化,这时操作迭代器就可能会造成不可预料的事情。

因此,果断抛异常阻止,是最好的方法。

实际上,这种迭代器迭代过程中表结构发生改变的情况,更经常发生在多线程的环境中。

记录表被修改的标记

这种机制的实现就需要记录表被修改,那么思路是使用状态字段modCount。

每当会修改表的操作执行时,都将此字段加1。使用者只需要前后对比该字段就知道中间这段时间表是否被修改。

如linkedList中的头插和尾插函数,就将modCount字段自增:

private void linkFirst(E e) {

final Node f = first;

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

first = newNode;

if (f == null)

last = newNode;

else

f.prev = newNode;

size++;

modCount++;

}

void linkLast(E e) {

final Node l = last;

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

last = newNode;

if (l == null)

first = newNode;

else

l.next = newNode;

size++;

modCount++;

}

迭代器

迭代器使用该字段来判断,

private class ListItr implements ListIterator {

private Node lastReturned;

private Node next;

private int nextIndex;

private int expectedModCount = modCount;

ListItr(int index) {

// assert isPositionIndex(index);

next = (index == size) ? null : node(index);

nextIndex = index;

}

public boolean hasNext() {

return nextIndex < size;

}

public E next() {

checkForComodification();

if (!hasNext())

throw new NoSuchElementException();

lastReturned = next;

next = next.next;

nextIndex++;

return lastReturned.item;

}

/* ... */

public void set(E e) {

if (lastReturned == null)

throw new IllegalStateException();

checkForComodification();

lastReturned.item = e;

}

final void checkForComodification() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

}

}

迭代器开始时记录下初始的值:

private int expectedModCount = modCount;

然后与现在的值对比判断是否被修改:

final void checkForComodification() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

}

这是一个内部类,隐式持有LinkedList的引用,能够直接访问到LinkedList中的modCount字段。

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

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

相关文章

idea 新建ssm java ee_IDEA搭建SSM项目实现增删改查

首先打开IDEA&#xff0c;File—>New—>Project创建项目选择左侧导航栏里的Maven&#xff0c;勾上勾&#xff0c;选择webapp按如下图进行填写创建完成后进入项目&#xff0c;右下角弹出的提示点击右边的Enable Auto-Import&#xff0c;自动配置连接数据库&#xff0c;我用…

php mail centos_centos怎么发送邮件

一、安装sendmail与mail1、安装sendmail&#xff1a;1) centos下可以安装命令&#xff1a;yum -y install sendmail2) 安装完后启动sendmail命令&#xff1a;service sendmail start2、安装mail安装命令&#xff1a;yum install -y mailx二、发送邮件1、通过文件内容发送发送命…

php文件的作用,php入口文件的作用-PHP问题

php入口文件的作用php入口文件能够完成主动加载性能。解析PHP入口文件的主动加载性能php的主动加载&#xff1a;正在php5之前&#xff0c;咱们要用某个类或类的办法&#xff0c;那必需include或许require&#xff0c;之后能力应用&#xff0c;每一次用一个类&#xff0c;都需求…

java中随机数边界问题,java 简单Dice问题(随机数的运用)

[java]代码库/*** Dice Write a program that simulates rolling two dice using the following* steps: 1. Prompt the user for the number of sides for two dice. 2. “Roll” the* dice three times by generating a random number between 1 (inclusive) and the* number…

java单词测试,java单词 - 在线打字测试(dazi.kukuw.com)

java单词贡献者&#xff1a;15533470608类别&#xff1a;英文 时间&#xff1a;2018-08-04 22:32:16 收藏数&#xff1a;20 评分&#xff1a;0返回上页举报此文章请选择举报理由&#xff1a;广告/谣言/欺诈政治敏感色情/违法信息垃圾文章其他收藏到我的文章改错字public static…

java vector list,Java基础之:List——ArrayList Vector

Java基础之&#xff1a;List——ArrayList & VectorArrayList简单介绍ArrayList实现了List接口&#xff0c;底层是一个数组&#xff0c;并实现了可变的功能。底层属性(transient Object[] elementData;)在序列化时&#xff0c;忽略该属性。ArrayList实现了List接口&#xf…

钉钉 php 推送,微信模板推送,钉钉信息推送

上午的时候看到有朋友需要微信推送&#xff0c;正好我也需要&#xff0c;之前一直用 Server 酱的&#xff0c;但是最近用不了&#xff0c;想找一个替代品&#xff0c;一开始准备选择钉钉&#xff0c;除了打卡&#xff0c;我很少使用钉钉&#xff0c;邮件提醒是备用方案&#xf…

涡轮机叶片matlab强度分析论文,一种基于MATLAB及Pro_E的涡轮建模方法

自动化与控制与二一种基于&#xff2d;&#xff21;&#xff34;&#xff2c;&#xff21;&#xff22;及&#xff30;&#xff52;&#xff4f;&#xff0f;&#xff25;的涡轮建模方法王智明(中海油服油田技术事业部北京&#xff11;&#xff10;&#xff11;&#xff11;&am…

php按文章评论数排序,zblog获取分类文章排序按指定的时间排序、评论数量排序、浏览数量排序...

Zblog PHP在1.8版本的时候想要调用多个分类的文章&#xff0c;并且按照自己的需求去排序是很简单的事情&#xff0c;很多博友也利用这个方法进行最新文章排行、热门评论文章排行等等操作&#xff0c;现在随着ZblogPHP版本的升级&#xff0c;已经封装了数据库语句&#xff0c;导…

蚁群算法matlab vrp问题车辆限重,蚁群算法MATLAB解VRP问题

Excel exp12_3_2.xls内容&#xff1a;ANT_VRP函数&#xff1a;function [R_best,L_best,L_ave,Shortest_Route,Shortest_Length]ANT_VRP(D,Demand,Cap,iter_max,m,Alpha,Beta,Rho,Q)%% R_best 各代最佳路线%% L_best 各代最佳路线的长度%% L_ave 各代平均距离%% Shortest_Rout…

java线程6种状态转换,Java线程的生命周期和各种状态转换详解

在Java中&#xff0c;任何对象都有生命周期&#xff0c;线程也不例外&#xff0c;它也有自己的生命周期。当Thread对象创建完成时&#xff0c;线程的生命周期便开始了&#xff0c;当线程任务中代码正常执行完毕或者线程抛出一个未捕获的异常(Exception)或者错误(Error)时&#…

matlab里dcgain,制系统的时域分析

一个动态系统的性能常用典型输入作用下的响应来描述。响应是指零初始值条件下某种典型的输入函数作用下对象的响应&#xff0c;控制系统常用的输入函数为单位阶跃函数和脉冲激励函数(即冲激函数)。在MATLAB的控制系统工具箱中提供了求取这两种输入下系统响应的函数。一、时域分…

在oracle数据库中显示异常,Oracle数据库出现ORA-01034错误的解决方案

类型&#xff1a;数据库类大小&#xff1a;42.1M语言&#xff1a;中文 评分&#xff1a;5.0标签&#xff1a;立即下载使用Oracle数据库的朋友经常会碰到的错误ORA-3113 "end of fileon communication channel" 就是这样的一个&#xff0c;我们可以简单的把这个错误理…

oracle数据库内核,深入内核:Oracle数据库里SELECT操作Hang解析

崔华&#xff0c;网名 dbsnakeOracle ACE Director&#xff0c;ACOUG 核心专家编辑手记&#xff1a;感谢崔华授权我们独家转载其精品文章&#xff0c;也欢迎大家向“Oracle”社区投稿。我们都知道在 Oracle 数据库里是“读不阻塞写&#xff0c;写不阻塞读”&#xff0c;那么是否…

oracle 如何形成死锁,Oracle数据表中的死锁情况解决方法

在进行数据库管理的过程中,经常会出现数据表被用户的一些不合理操作而导致表被锁定的情况,以下主要介绍如何查找哪些表被哪个用户所锁定,以及如何解除锁定:1.查找被锁定的表:select object_name,session_id,os_user_name,oracle_username,process,locked_mode,statusfrom v$loc…

linux设备分层优点,Linux设备驱动的分层设计思想

代码清单8第2行获取platform_data&#xff0c;而platform_data实际上是定义GPIO按键硬件信息的数组&#xff0c;第31行的for循环工具这些信息申请GPIO并初始化中断&#xff0c;对于LDD6140电路板而言&#xff0c;这些信息如代码清单10。代码清单10 LDD6410开发板GPIO按键的plat…

linux 关闭桌面环境,Ubuntu 14.04上的Cinnamon桌面环境PPA被关闭

今天Cinnamon桌面环境的开发者宣布关闭Cinnamon桌面环境的PPA&#xff0c;这意味着以后在Ubuntu上安装Cinnamon桌面环境将变得很难。关于为什么要关闭PPA&#xff0c;Cinnamon PPA的维护者Gwendal Le Bihan做出了以下解释&#xff1a;“稳定的Cinnamon PPA将不再提供&#xff0…

genymotion linux 32,Ubuntu Linux 32bit - 不是Genymotion虚拟设备

因为4天我没有找到解决方案我的genymotion有问题 我正在使用Ubuntu 12.04 32位(architecure&#xff1a;i686)并安装android studio并将genymotion的插件放入其中succefully ......现在我的问题&#xff0c;当点击genymotion设备管理器&#xff0c;列表是空的&#xff0c;当我试…

grub linux rootfs,rootfs文件系统(笔记)(草稿)

文件系统简介文件系统就是个软件&#xff0c;帮用户来管理一些二进制的信息&#xff0c;管理外存上存储的这些二进制各种文件在内存中都是以二进制的形式来存在的&#xff0c;如果没有文件系统&#xff0c;用户就需要自己去决定这些二进制的东西是什么&#xff0c;需要自己去和…

linux 如何查看属性,linux 下查看系统属性

linux 下查看系统属性(2009-06-28 19:01:34)标签&#xff1a;linux杂谈分类&#xff1a;OSlinux下查看系统属性1、查看cpu信息查看所有cpu信息&#xff1a;cat /proc/cpuinfo查看cpu类型&#xff1a; grep "model name" /proc/cpuinfo2、查看内存信息&#xff1a;查看…