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;都需求…

emacs php 配置文件,如何配置emacs进行正确的PHP开发?

我使用web模式(http://web-mode.org/)混合HTML / PHP文件和php模式为纯PHP文件.最新版本的php-mode还推荐使用混合HTML / PHP文件的Web模式&#xff1a;https://github.com/ejmr/php-mode#avoid-html-template-compatibility.不同于其他模式,如mmm模式,mumamo或多网络模式,尝试…

php 5.3.9 漏洞,PHP-5.3.9远程执行任意代码漏洞(CVE-2012-0830) 详解

这个新的修复方法初衷是好的, 但是却带来一个严重的问题(5.3.10中已经修复), 这个问题最初是由Stefan Esser发现的. 请看之前(5.3.9)最终的修复方案(php_register_variable_ex):代码如下while (1) {if (zend_symtable_find(symtable1, escaped_index, index_len 1, (void **) …

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…

php 正则替换 ubb,php实现过滤UBB代码的类

本文实例讲述了php实现过滤UBB代码的类。分享给大家供大家参考。具体如下&#xff1a;PHP代码如下&#xff1a;class Day{function ubb($Text) { /// UBB代码转换//$Texthtmlspecialchars($Text);//$Textereg_replace("\r\n","",$Text);$Textereg_rep…

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…

java建立线性表的链式结构,数据结构学习----线性表的链式表示(Java实现)

线性表接口LList&#xff1a;package com.clarck.datastructure.linked;/*** 线性表接口LList&#xff0c;描述线性表抽象数据类型&#xff0c;泛型参数T表示数据元素的数据类型** author clarck**/public interface LList {/*** 判断线性表是否空* return*/boolean isEmpty();…

php prepare 批量,PreparedStatement批处理

PreparedStatement批量更新关键代码 无 import java.sql.Connection;import java.sql.PreparedStatement; //...String sql "insert into employee (name, city, phone) values (?, ?, ?)";Connection connection new getConnection();PreparedStatement pPrepa…

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

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

java repaint 重画图形,学习笔记:WINDOWS的图形重绘基础

OnPaint()与OnDraw()的区别&#xff1a;OnPaint是WM_PAINT的消息响应函数&#xff0c;在MFC的基类里OnPaint函数调用了OnDraw()函数。OnPaint函数另外还调用了OnPrepareDC()函数。如果在窗口子类覆盖了OnPaint函数&#xff0c;当MFC调用我们重写的OnPaint函数时&#xff0c;就调…

php定义数据表类,phpwind中的数据库操作类

phpwind中的数据库操作类2021-01-22 20:12:15141/*来源&#xff1a;phpwind.net*/ClassDB{var$query_num0;functionDB($dbhost,$dbuser,$dbpw,$dbname,$pconnect0){$this->connect($dbhost,$dbuser,$dbpw,$dbname,$pconnect);}functionconnect($dbhost,$dbuser,$dbpw,$dbnam…

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

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

基于matlab的传热学虚拟实验开发,基于MATLAB的传热学课程虚拟实验软件的开发

215教育现代化2018 年 12 月第 49 期 教育信息技术 基于 MATLAB 的传热学课程虚拟实验软件的开发 周永利&#xff0c;李友荣&#xff0c;石万元&#xff0c;张力元&#xff0c;杨晨&#xff0c;卞煜&#xff0c;王国强&#xff0c;李俊&#xff0c;包键 ( 重庆大学 低品位能源利…

java做 binggo,Linux启动与停止spring boot工程的脚本示例

在springboot项目启动有三种方式&#xff1a;1、运行主方法程序2、使用命令mvn spring-boot:run 在命令行运行3、使用 mvn packpage打包位jar文件以后&#xff0c;使用java -jar yourapp.jar命令行运行一般我们在开发的时候经常使用的是前面两种运行方式&#xff0c;在部署实施…

php计划任务 框架,计划任务的使用 ThinkCMF内容管理框架,做最简约的ThinkPHP开源软件...

1、先不管是是否是独立分组&#xff0c;必须在Application\common\项目名下的Conf文件夹内创建2个文件一个是tags.php(项目默认有&#xff0c;直接加入需要执行的代码即可) 一个是 crons.php。注意这两个文件名为thinkphp标准文件名&#xff0c;不可以改变tages.php内容是&…

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…