java实现的LinkedLilst
Posted 陈东
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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的主要内容,如果未能解决你的问题,请参考以下文章
LockSupport.java 中的 FIFO 互斥代码片段
ASP.net MVC 代码片段问题中的 Jqgrid 实现
《java精品毕设》基于javaweb宠物领养平台管理系统(源码+毕设论文+sql):主要实现:个人中心,信息修改,填写领养信息,交流论坛,新闻,寄养信息,公告,宠物领养信息,我的寄养信息等(代码片段