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

原文地址:https://www.cnblogs.com/ChenD/p/9062836.html

时间: 2024-08-29 22:26:59

java实现的LinkedLilst的相关文章

Java多线程学习(吐血超详细总结)

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 目录(?)[-] 一扩展javalangThread类 二实现javalangRunnable接口 三Thread和Runnable的区别 四线程状态转换 五线程调度 六常用函数说明 使用方式 为什么要用join方法 七常见线程名词解释 八线程同步 九线程数据传递 本文主要讲了java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的一些线程函数用法.概述等. 首先讲一下进程和线程

Java TM 已被阻止,因为它已过时需要更新的解决方法

公司的堡垒机需要通过浏览器登陆,且该堡垒机的网站需要Java的支持,最近通过浏览器登陆之后总是提示"java TM 已被阻止,因为它已过时需要更新的解决方法"导致登陆之后不能操作, 但是操作系统中确实已经安装了比较新的JDK,安装的JDK版本是jdk-7u67-windows-i586,因为太烦人,所以决定搞清楚报错的原因,一劳永逸,彻底解决这个问题 准备工作:安装JDK,安装版本jdk-7u67-windows-i586.exe,因为机器的Eclipse还依赖64位的JDK,所以另安

Java四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor

介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? Java new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }).start(); 1 2 3 4 5 6 7 new Thread(new

由@NotNull 注解引出的关于Java空指针的控制(转)

Java 小技巧和在java应用避免NullPonintException的最佳方法 在java应用程序中,一个NullPonintException(空指针异常)是最好解决(问题)的方法.同时,空指针也是写健壮的顺畅运行的代码的关键.“预防好过治疗”这句话也同样适用于令人不爽的NullPonintException.通过应用防御性的编码技术和在遵守多个部分之间的约定,你可以再很大程度上避免NullPointException.下面的这些java小技巧可以最小化像!=null这种检查的代码.作为

Java注解(2)-注解处理器(运行时|RetentionPolicy.RUNTIME)

如果没有用来读取注解的工具,那注解将基本没有任何作用,它也不会比注释更有用.读取注解的工具叫作注解处理器.Java提供了两种方式来处理注解:第一种是利用运行时反射机制:另一种是使用Java提供的API来处理编译期的注解. 反射机制方式的注解处理器 仅当定义的注解的@Retention为RUNTIME时,才能够通过运行时的反射机制来处理注解.下面结合例子来说明这种方式的处理方法. Java中的反射API(如java.lang.Class.java.lang.reflect.Field等)都实现了接

jvm系列(一):java类的加载机制

java类的加载机制 原文:http://www.cnblogs.com/ityouknow/p/5603287.html 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构.类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口. 类加载器并不需要等到某个

Java注解(1)-注解基础

注解(Annotation)是在JAVA5中开始引入的,它为在代码中添加信息提供了一种新的方式.注解在一定程度上把元数据与源代码文件结合在一起,正如许多成熟的框架(Spring)所做的那样.那么,注解到底可以做什么呢? 1.注解的作用. 提供用来完整地描述程序所需要的信息,如编译期校验程序信息. 生成描述符文件,或生成新类的定义. 减轻编写"样板"代码(配置文件)的负担,可以使用注解自动生成. 更加干净易读的代码. 编译期类型检查. 2.Java提供的注解 Java5内置了一些原生的注

异常笔记--java编程思想

开一个新的系列,主要记一些琐碎的重要的知识点,把书读薄才是目的...特点: 代码少,概念多... 1. 基本概念 异常是在当前环境下无法获得必要的信息来解决这个问题,所以就需要从当前环境跳出,就是抛出异常.抛出异常后发生的几件事: 1.在堆上创建异常对象. 2.当前的执行路径中止                                          3. 当前环境抛出异常对象的引用.                                         4. 异常处理机制接

Java自学序言

Java自学序言 亲爱的自己和各位读者朋友: 您们好! 这是作者本人自学Java编程开发的一系列文章,不具有一定的权威性,也算是自己一个人的学习笔记和总结,希望自己通过博客的形式将我自己的学习效率得到提高.如自学的稳重存在不足或错误的地方希望广大的博客朋友们多多指教.本人在此不胜感激! 学习Java是一件很痛苦的事儿,我自己要想不断的去挑战一下自己,把自己大学所学的Java知识能够巩固起来.不断的去改正自己开发中的不足之处.如何来学习Java?如何来更好的掌握Java开发语言?这些都是我们要不断