java实现的LinkedLilst
- package javabean.adt.List;
- import java.util.ConcurrentModificationException;
- import java.util.Iterator;
- import java.util.ListIterator;
- import java.util.NoSuchElementException;
- /**
- * 模拟 双链表
- * 类本身 包含着 到两端的链、表的大小以及一些方法,
- * 2:Node类,他可能是一个私有的嵌套类,一个节点包含数据以及到前一个节点链和到下一个节点的链。还有一些适当的构造方法
- * 3:LinkedListIterator类,该类抽象了位置的概念,是一个私有的类,并实现接口Iterator,它提供了方法next haseNext和remove的类
- * 由于迭代器类存储"当前节点"的引用,并且终端标记是一个合理的位置,因此它对于在表的终端创建一个额外的节点来表示终端标记是有意义的,
- * 更进一步 我们能在标最前端创建一个 额外的节点,逻辑上表示 表开始的标记,这些额外的节点 有时候叫做标记节点,特别的,在前端的节点有时候也叫做
- * 头结点,而在末端的节点有时候也就叫做尾节点,
- * 使用这些额外节点的优点在于,通过派出许多特殊的情况极大简化了编码,
- * 例如 :如果我们不是用头结点,那么删除第一个节点 就变成了特殊的情况,因为在删除其间我们必须重新调整链表到第一个节点的链,
- * 因为链表的第一个节点 到前一个节点的链是空的 第一个节点前面已经没有了任何的节点,同理可知删除最后一个节点那么
- * 如果我们不是用尾节点 那么 在删除最后一个节点的时候 倒数第二的节点 指向后一个节点的链 将必须设置为空 因为后面已经没有了节点
- */
- public class MyLInkedList<E> implements Iterable<E> {
- private int theSize;
- private int modCount = 0;
- //链表头结点
- private Node<E> beginMarker;
- //链表尾节点
- private Node<E> endMarker;
- public MyLInkedList() {
- }
- public void clear() {
- doClear();
- }
- private void doClear() {
- //头结点的下一个节点 就是尾节点 相当于链表中一个元素都没有 只有两个Node节点 头和尾
- beginMarker = new Node<E>(null, null, null);
- endMarker = new Node<E>(beginMarker, null, null);
- beginMarker.next = endMarker;
- theSize = 0;
- modCount++;
- }
- public int size() {
- return this.theSize;
- }
- public boolean isEmpty() {
- return this.size() - 0 == 0;
- }
- public boolean add(E e) {
- add(theSize, e);
- return true;
- }
- public void add(int index, E e) {
- checkPositionIndex(index);
- if (theSize == index)
- addLast(e);
- else
- addBefore(getNode(index), e);
- }
- /**
- * 此处是为了向链表的尾部 添加一个新的节点
- *
- * @param e
- */
- private void addLast(E e) {
- final Node<E> last = endMarker;
- final Node<E> newNode = new Node<E>(last, e, null);
- //新节点称为头节点
- endMarker = newNode;
- if (last == null) {
- //如果尾节点为null 代表当前的链表是一个空的链表 则新节点即为尾节点又是头结点
- beginMarker = newNode;
- } else {
- last.next = newNode;
- }
- theSize++;
- modCount++;
- }
- public E remove(int index) {
- checkElementIndex(index);
- return unlink(getNode(index));
- }
- private E unlink(Node<E> node) {
- //在没有添加checkElementIndex 这个方法的时候 本来想判断下 这个节点是不是为空的 (若当前的链表刚刚被创建 并且没有添加任何节点 那么 当前的头结点和尾节点都是null)
- //getNode 中我没有加任何的校验 就算当前链表为空 它取到的值 将会是null//但是在添加了checkElementIndex这个方法之后 在index<theSize
- //也就是 在 theSize为0时 是会抛出错误的,也是就是当前链表为空 不允许删除
- //所以程序执行到这里 这个node一定不会为null 放心大胆的用吧
- final E oldanaytype = node.anaytype;
- final Node<E> prev = node.prev;
- final Node<E> next = node.next;
- //如果node节点是头节点 只要将 头结点的下一个节点的 prev置为null就可以了
- if (prev == null) {
- beginMarker = next;
- } else {
- //如果node不是头结点 那么先将 node的前节点的next 指向 node的next节点 在将node下一个节点的 prev 指向 node的前一个节点
- //但是前提 node不是尾节点
- prev.next = next;
- //node的前节点的next已经有了新的指向 那么node 的prev 先置为null
- node.prev = null;
- }
- //如果是尾节点 那么只需要将尾节点的前一个节点 的next置为null 事实是若当前节点即是头结点 又是尾节点 那么 尾节点的前一个节点
- //是null prev.next是不可以被引用的 所以有种更符合逻辑的 写法 如果尾节点被删除 那么就是尾节点的前一个节点 就是尾节点
- //由于当前链表的成员变量 尾节点是允许为空的 同理上述头结点
- //如果不是尾节点
- if (next == null) {
- endMarker = prev;
- } else {
- next.prev = prev;
- //node的下一个节点 的prev也已经有了新的指向 node的next 置为null
- node.next = null;
- }
- //最后 node的数据域置为null
- node.anaytype = null;
- theSize--;
- modCount++;
- return oldanaytype;
- }
- public boolean remove(Object o) {
- if (o == null) {
- for (Node<E> node = beginMarker; node != null; node = node.next) {
- if (node.anaytype == null) {
- unlink(node);
- return true;
- }
- }
- } else {
- for (Node<E> node = beginMarker; node != null; node = node.next) {
- if (o.equals(node.anaytype)) {
- unlink(node);
- return true;
- }
- }
- }
- return false;
- }
- public E get(int index) {
- checkPositionIndex(index);
- return getNode(index).anaytype;
- }
- public E set(int index, E e) {
- this.checkPositionIndex(index);
- Node<E> node = getNode(index);
- E old = node.anaytype;
- node.anaytype = e;
- return old;
- }
- private boolean isPositionIndex(int index) {
- return index >= 0 && index <= theSize;
- }
- private void checkPositionIndex(int index) {
- if (!isPositionIndex(index))
- throw new IndexOutOfBoundsException(outOfBoundsMesg(index));
- }
- private String outOfBoundsMesg(int index) {
- return "索引位置:" + index + ",链表长度:" + theSize;
- }
- private void checkElementIndex(int index) {
- if (!this.isElementIndex(index))
- throw new IndexOutOfBoundsException(outOfBoundsMesg(index));
- }
- private boolean isElementIndex(int index) {
- return index >= 0 && index < theSize;
- }
- private Node<E> getNode(int index) {
- if (index < (theSize >> 1)) {
- Node<E> x = beginMarker;
- for (int i = 0; i < index; i++) {
- x = x.next;
- }
- return x;
- } else {
- Node<E> x = endMarker;
- for (int i = theSize - 1; i > index; i--) {
- x = x.prev;
- }
- return x;
- }
- }
- /**
- * @param prev :原来链表位置上的节点 现在要新插入的节点 要插入在这个节点之前
- * @param e
- */
- private void addBefore(Node<E> prev, E e) {
- final Node<E> p = prev.prev;
- final Node<E> newNode = new Node<E>(prev.prev, e, prev);
- //无论prev是不是头结点 在新的节点插入之后 prev一定是新节点的下一个节点
- prev.prev = newNode;
- if (p == null) {
- //发现prev的前一个节点 是null 代表 prev是头结点,那么只要把新节点变为头结点就可以了
- beginMarker = newNode;
- } else {
- //prev不是头节点 那么就要把 prev前一个节点中指向下一节点的链 替换成 新的节点
- p.next = newNode;
- }
- theSize++;
- modCount++;
- }
- @Override
- public Iterator<E> iterator() {
- return null;
- }
- private static class Node<E> {
- //实际存储的元素
- E anaytype;
- //该节点的前一个节点
- Node<E> prev;
- //该节点的下一个节点
- Node<E> next;
- Node(Node<E> prev, E anaytype, Node<E> next) {
- this.anaytype = anaytype;
- this.prev = prev;
- this.next = next;
- }
- }
- private class MyLinkedIterstor implements ListIterator<E> {
- /**
- * linkedlist的迭代器 与 arrlist的迭代器不同
- * ;linkedlist迭代器可以指定初始索引
- *
- * @return
- */
- private Node<E> oldNode;
- private Node<E> next;
- private int nextIdex;
- private int expectedModCount = modCount;
- MyLinkedIterstor(int index) {
- nextIdex = index;
- next = index == theSize ? endMarker : getNode(index);
- }
- @Override
- public boolean hasNext() {
- /**
- * 还是从前向后遍历
- */
- return nextIdex < theSize;
- }
- @Override
- public E next() {
- this.checkForComodification();
- if (!hasNext())
- throw new NoSuchElementException();
- oldNode = next;
- next = next.next;
- nextIdex++;
- return oldNode.anaytype;
- }
- private void checkForComodification() {
- if (!(expectedModCount == modCount))
- throw new ConcurrentModificationException();
- }
- @Override
- public boolean hasPrevious() {
- return nextIdex > 0;
- }
- @Override
- public E previous() {
- checkForComodification();
- if (!hasPrevious())
- throw new NoSuchElementException();
- /**
- * 分析下 node为null 的可能按照现在链表的赋值
- * 第一种 本身就是空链表
- * 第二种是 到了链表头部
- * 若是到了链表的头部那么 nexindex也不是吃素的
- * 倒数第二个节点的时候 nexindex = 2;
- * oldNode = 倒数第二个节点
- * next = 头结点
- * 然后 hasPrevious nextIndex>0;1
- * oldNdoe = 头结点
- * NEXT = NULL
- * Next =0;
- * 该方法是把向前遍历 所以 oldNode的值就是上一个next的值
- * = 的优先级是最低的
- */
- oldNode = next = (next == null) ? beginMarker : next.prev;
- nextIdex--;
- return oldNode.anaytype;
- }
- @Override
- public int nextIndex() {
- return nextIdex;
- }
- @Override
- public int previousIndex() {
- //作用是返回 下一个节点的索引
- return nextIdex - 1;
- }
- //删除的情况一般都是在使用迭代器 next 或者 previous 结束之后
- //上述操作之后 这个时候 删除的应该是旧节点 而不是新的节点 因为在上述两个方法之后
- // next已经被赋值成了新的节点 这个时候删除新节点恐怕不是很合适啊
- //这里面删除的应该是旧节点 也就是上一个next 应该删除的是旧Node对象
- @Override
- public void remove() {
- //删除linkedlist中的节点 那么modCount操作计数器肯定会增加为了不使迭代器违法 迭代器中的
- //expectedModCount肯定同步
- //先判断观察节点是否与linkedlist中观察节点是否一致
- checkForComodification();
- //因为unlink方法中我没有 判断传入节点是否是null的情况,
- //在方法中 有对传入变量的直接调用 所以不允许 节点存在Null值
- //oldNode什么时候会是null值呢?
- /*只有一种情况 那就是在使用 previous中 当previous 操作节点为头结点 那么在最后结束时 oldNode会被赋值为null
- 但是呢 那是在java1.8的 版本中 我的版本是不会有空值的
- 那么想一下 删除之后 会对迭代器中的现有节点有什么影响 删除老节点之后 新节点 所谓的新节点
- 因为存在不同的方向的遍历 所以 所谓的新节点 一律不可以取当前迭代器中的额nxet值 因为这个值我们是不知道怎么前一个节点还是后一个节点
- 按照正常的逻辑 删除节点之后一定要 将被删除节点的前一个节点指向被删除节点的链 变成 被删除节点的下一个节点,
- 然后将删除节点的后一个节点指向 被删除节点的链 替换成被删除节点的前一个节点
- 那么肯定会对迭代器 中的 oldNode 和 next 产生影响
- 那么会有什么影响呢
- 比如说 :现在正在使用向前遍历
- 那么 当前jdk1.8中 是 老节点和新节点是一致的
- 删除了老节点之后 其实相当于删除了 */
- if (oldNode == null)
- throw new IllegalStateException();
- unlink(oldNode);
- Node<E> lastNode = oldNode.next;
- if (oldNode == next)
- //若目前迭代器使用的遍历是向前在遍历 删除的当前遍历的节点 然后 被删除当前的节点的下一个节点 要赋值给next因为next被删除了
- //next是目前 迭代器 遍历到的节点 并且迭代器会根据next的值进行进一步向头结点方向或者向尾节点方向遍历
- //所以被删除next之后 unlink中 会把 当前节点删除 然后将当前节点的前一个节点指向被删除节点的链 更新为指向被删除节点的后一个节点
- //并将被删除节点的下一个节点指向被删除节点的链更新为 指向被删除节点前一个节点。所以关于节点内部我们不用关心了,我们只要把当前迭代器
- //中的next 与 oldNode的值更新好就ok了,那么现在整理下迭代器中这两个引用的值 要怎么变?
- //next与oldNode的引用的值是相等的 那么被删除后next的值应该变为 next的下一个节点 因为 next的下一个节点
- //中前指针指向的是next的前一个节点 所以我们需要将next、变为nxt的下一个节点,这样在下一次previous的时候 就可以根据
- //被删除节点的下一个节点的前指针 继续遍历
- next = lastNode;
- else
- //如果 现在是向后遍历的 那么被删除的节点被删掉 会影响当前的next节点与oldNdoe节点的值么
- //首先还是分析unlink中会帮助我完成了linkedList中节点中的替换 但是 迭代器中的next节点
- //next需要的是 当前节点的next节点 而每次next之后 其实next的值就不是当前遍历节点的值了 而是当前节点的下一个节点的值
- // 而oldNode才是当前的节点 所以删除当前的节点其实就是删除oldNode 并不会下一次的遍历产生任何影响
- //因为next遍历的条件就是nextIndex< theSize 删掉之后 theSize肯定要减1的为了同步thesize nextIndex肯定也是要--;
- //疑问?那么在向前遍历的时候 被删掉节点那么 linkedList 中代表节点数量的 theSize会减少的 那么为了同步 需不需要同步
- //为什么同步?向前遍历的时候 被删掉数据 为什么可以不需要next--呢 因为实际上在向前便利的时候我们可以想象一下
- //这就是逐渐抛弃后面的节点 虽然删除了节点 链表的theSize会相应的减少 如果这个时候在去向前遍历 这会产生线程问题
- //同时操作同一个nextIndex情况 本身这个linked'LIst就不具备线程安全的功能,
- nextIdex--;
- oldNode = null;
- expectedModCount++;
- }
- /**
- * 观察 迭代器与原集合之间增删改操作是否同步
- */
- public void checkModicationlistator() {
- if (expectedModCount != modCount)
- throw new ConcurrentModificationException("迭代器迭代时 不允许对原集合进行增删改操作");
- }
- /**
- * 该方法是为了在遍历的时候 进行重新赋值的 被赋值的节点 就是 oldNode 但是oldNode是可以被删除的 被删除后就是
- * 被赋值为null了
- *
- * @param e
- */
- @Override
- public void set(E e) {
- if (oldNode == null)
- throw new IllegalStateException();
- checkForComodification();
- oldNode.anaytype = e;
- }
- //这个方法的意义是什么呢 因为在迭代器 遍历的时候 是不允许LinkeLIst操作数据的 所以在这里加上这个add方法
- //实际上就是插入到linkedlist后面的
- @Override
- public void add(E e) {
- //要新增一个节点那么老的节点肯定就没有什么意义了 因为老节点也是根据next为标准的现在
- //插入新节点的位置就是在next节点之前 那么老节点很可能就不再是老节点了
- //
- checkModicationlistator();
- oldNode = null;
- /**
- * 为什么要判断next是否为null 呢因为 next 若不是空的 那么 我们就要将新节点插在next节点之前
- * 那么要插入在next之前 方法中实际上要更新next节点中的 previous属性 那么引用Null的实例 那么肯定是不行的
- * 所以一定要判断next是否为null
- * 那么next什么时候可能为null呢 迭代器中能为next赋值的地方只有三个 就是初始化的时候、next方法的时候
- * previous的时候,
- * 那么在上述三个地方什么时候有可能为赋值为空呢
- * 第一个构造器 也就是初始化的时候 只要链表不是空的 那么在使用构造器初始化迭代器的时候 也会校验该链表是不是没有数据
- * 如果校验失败 链表是一个空的链表那么 是不能初始化的 所以在第一个地方 构造器 中是不会被赋值为null的
- * 那么next中 next方法中每次都会将next 赋值为next的下一个节点 那么最后结束的的时候也就是遍历到尾节点的时候
- * 因为尾节点的next指针属性是null那么理所应当的next会被赋值为null
- * 所以第一种情况 就是next在已经遍历到了
- * 链表的尾节点 那么 会出现next被赋值为null
- * *
- * 那么previous 会不会出现next为null值得情况呢 初始情况不会出现那么就是 看看遍历到头结点的时候
- * 遍历到头结点的是时候 确实会出现next为null 但是 我已经在程序里控制了 在next为null的时候会把next赋值为头结点
- * 所以next为null的时候只有一种情况 那就是当前的next已经遍历到了尾节点的部分
- * 所以若是next为null的时候 也就是遍历到了尾节点 那么在插入节点的时候 就直接使用链表中的addLast方法就可以了
- * 如果next不为null说明 可以将节点插在next的前面那么就调用addBefore方法
- * */
- if (next == null)
- addLast(e);
- else
- addBefore(next,e);
- nextIdex++;
- modCount++;
- }
- }
- }
java实现的LinkedLilst的更多相关文章
- Spark案例分析
一.需求:计算网页访问量前三名 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /* ...
- 故障重现(内存篇2),JAVA内存不足导致频繁回收和swap引起的性能问题
背景起因: 记起以前的另一次也是关于内存的调优分享下 有个系统平时运行非常稳定运行(没经历过大并发考验),然而在一次活动后,人数并发一上来后,系统开始卡. 我按经验开始调优,在每个关键步骤的加入如 ...
- Elasticsearch之java的基本操作一
摘要 接触ElasticSearch已经有一段了.在这期间,遇到很多问题,但在最后自己的不断探索下解决了这些问题.看到网上或多或少的都有一些介绍ElasticSearch相关知识的文档,但个人觉得 ...
- 论:开发者信仰之“天下IT是一家“(Java .NET篇)
比尔盖茨公认的IT界领军人物,打造了辉煌一时的PC时代. 2008年,史蒂夫鲍尔默接替了盖茨的工作,成为微软公司的总裁. 2013年他与微软做了最后的道别. 2013年以后,我才真正看到了微软的变化. ...
- 故障重现, JAVA进程内存不够时突然挂掉模拟
背景,服务器上的一个JAVA服务进程突然挂掉,查看产生了崩溃日志,如下: # Set larger code cache with -XX:ReservedCodeCacheSize= # This ...
- 死磕内存篇 --- JAVA进程和linux内存间的大小关系
运行个JAVA 用sleep去hold住 package org.hjb.test; public class TestOnly { public static void main(String[] ...
- 【小程序分享篇 一 】开发了个JAVA小程序, 用于清除内存卡或者U盘里的垃圾文件非常有用
有一种场景, 手机内存卡空间被用光了,但又不知道哪个文件占用了太大,一个个文件夹去找又太麻烦,所以我开发了个小程序把手机所有文件(包括路径下所有层次子文件夹下的文件)进行一个排序,这样你就可以找出哪个 ...
- Java多线程基础学习(二)
9. 线程安全/共享变量——同步 当多个线程用到同一个变量时,在修改值时存在同时修改的可能性,而此时该变量只能被赋值一次.这就会导致出现“线程安全”问题,这个被多个线程共用的变量称之为“共享变量”. ...
- Java多线程基础学习(一)
1. 创建线程 1.1 通过构造函数:public Thread(Runnable target, String name){} 或:public Thread(Runnable target ...
随机推荐
- onReachBottom 注意事项
onReachBottom使用注意 可在pages.json里定义具体页面底部的触发距离onReachBottomDistance,比如设为50,那么滚动页面到距离底部50px时,就会触发onReac ...
- shiro 不使用加密 解决 org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.sh
测试本方法为失效的 报错: org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token ...
- CoordinatorLayout使用全解析
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u012124438/article/details/56701641 CoordinatorLayo ...
- react native tap切换页面卡顿
问题描述:做一个页面,左边是导航,每次点击一个菜单,右边立即显示出对应的视图,数据会重新过滤,使用setState 更新视图,会卡顿 解决办法: InteractionManager.runAfter ...
- RNN 与 LSTM 的原理详解
原文地址:https://blog.csdn.net/happyrocking/article/details/83657993 RNN(Recurrent Neural Network)是一类用于处 ...
- Oracle 性能之 Enq: CF - contention
Oracle 性能之 Enq: CF - contention Table of Contents 1. 原因 2. 解决问题 2.1. 针对持有锁进程类型处理 2.1.1. 查看持有锁会话的进程类型 ...
- spring 过滤器- 过滤登陆请求路径(过滤静态资源跳转到登陆页面)
public class LoginedFilter implements Filter { /** * 排除的地址 */ private Map<String, Boolean> ign ...
- flask 学习(二)
安装了flask扩展 以及flask-bootstrap 默认情况下,flask在template文件夹寻找模板. flask 加载的是Jinja2模板,该模板引擎在flask中由函数render_t ...
- C基础知识(4):指针--p=&a和*p=a的区别详解
对于*p,[p = &a]和[*p = a]的区别详解 (1) p=&a就是用a的地址对p赋值,&p不改变,变的是p (2) *p=a就是把p所指向的那一内存空间的值赋值为a, ...
- django中使用mysql数据库
django连接mysql 安装MySQL 1 linux: apt install mysql-server apt install mysql-client mysql_secure_instal ...