LinkedList源码系列(1)
来源:互联网 发布:surpac软件 编辑:程序博客网 时间:2024/05/17 04:47
//:judy/List
首先我们知道LinkedList的ADT(抽象数据类型)其实是链表,而链表的优缺点想必大家都知道。。 在此不用赘述
今天我们主要讲的是LinkedLIst 类和 Node类 以及ListIterator类用泛型的设计,以及核心常用的源码:
先来看下Collection接口,List接口,ListIterator接口的主要接口
public interface Collection<T> extends Iterable<T> {int size();boolean isEmpty();void clear();boolean contains(T x);boolean add(T x);boolean remove(T x);//实现Iterable接口可以拥有增强for循环java.util.Iterator<T> iterator();}
public interface List<T> extends Collection<T>{T get(int idx);T set(int idx,T newVal);void add(int idx,T x);void remove(int idx);ListIterator<T> listIterator(int pos);}
public interface Iterator<T>{ boolean hasNext(); T next(); void remove();}
想使用增强for循环就必须实现Iterator接口,覆盖Iterator()方法,返回一个迭代器,这也就是为什么作为java Collections framework的根类Collection为什么要实现Iterable接口。下面看Linkedlist的设计
<pre name="code" class="java">public class MyLinkedList<T> implements Iterable<T>{private int size;private int modCount;//帮助迭代器检测集合的变化private Node<T> first;<span style="white-space:pre"></span>//总是指向List第一个元素private Node<T> last;<span style="white-space:pre"></span>//总是指向List的最后一元素private static class Node<T>{public Node(T val,Node<T> prev,Node<T> next ){this.data = val;this.prev = prev;this.next = next;}public T data;public Node<T> prev;public Node<T> next;}public Iterator<T> iterator(int index) {checkPositionIndex(index);return new LinkedListIterator(index);}private class LinkedListIterator implements java.util.ListIterator<T>{private Node<T> lastReturned = null;private Node<T> next ;private int nextIndex; private int expectedModCount = modCount;public LinkedListIterator(){}public LinkedListIterator(int index){// assert checkPositionIndex(index)next = (index==size)?null:getNode(index);nextIndex = index;} }}
看到这里,我们肯定有个疑问为什么把Node类设计成嵌套类,而把LinkedListIterator设计成内部类,因为根据它们各自的定义我们可以知道,嵌套类它是外部类所产生所有的实例对象所共享的,也是就是静态的,而内部类却是和每个实例对象相关联的,并且在LinkedList中我们也可以自己想象一下,结点是干嘛的,迭代器又是要干嘛的,结点很明显是共享的,用来保护每个元素结点之间属于线性关系,1:1,而迭代器就是每个实例对象所独有的,用来迭代本链表中的元素,而迭代器中的remove方法比起集合的remove的方法也是有优点的:Iterator的remove方法在删除项时,已经确定了项的位置,而Collection的remove()方法则需要先找出要删除项的位置,这样更更高效。但是已经确定在使用Iterator时就不能使用Collection中的clear,remove,add等改变集合结构的方法,否则Iterator就不合法,会抛出一个ConcurrentModificationException异常。而ModCount就是用来检测集合变化的手段!
打字好累还是直接看源码吧。。。首先向伟大的JAVA之母Josh Bloch致敬!
public void clear()的源码:
<pre name="code" class="java"> /** * Removes all of the elements from this list. * The list will be empty after this call returns. */public void clear(){for(Node<T> tmp = first;tmp!=null;){Node<T> next = tmp.next;tmp.data = null;//help GCtmp.prev = null;tmp.next = null;tmp = next;}first = last = null;size = 0 ;modCount++;}个人感觉这个源码蕴含的思想挺重要的,许多人在之前写这个clear的时候都是如下:
public void clear(){first = last = null;size = 0 ;modCount++;}认为Java虚拟机会帮助我们释放在堆里面创建的对象,但是你看源代码很明显有一个循环遍历LinkedList的每个节点,并且把结点的 data,prev,next对象的引用置null,这样做可以帮助GC,下面是作者对它的描述:
Description:Clearing all of the links between nodes is "unnecessary", but:
helps a generational GC if the discarded nodes inhabit more than one generation is sure to free memory even if there is a reachable Iterator这句话的翻译就是: 清除所有节点之间的链接是“不必要的”,但:帮助一代垃圾收集,如果被丢弃的节点居住一代以上即使有一个可到达的迭代器,也确保释放内存(ps:本人的英语very poor,不对别打我!!!)其实也就是为什么不把每个结点置null,而把结点的 data,prev,next对象的引用置nul的原因,但是为什么又能帮助到GC呢,那就要引出GC的工作机制:
在<<Thinking in java>>中的Bruce Eckel如是说,在其他系统中的垃圾回收机制采用的是一种简单但速度很低的“引用计数”技术,它的工作原理就是每一个对象都含有一个引用计数器,当引用连接至对象时引用计数加一,当引用离开作用域或置null时,引用计数减一,虽然管理引用计数的开销不大,但是这项工作是在整个程序的生命周期持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为0时,立即释放其占有空间的资源。但这种方法有个缺陷,就是对象之间存在循环引用,出现“对象应该被释放,但是其引用计数不为0”,对垃圾回收器来说,定位这样的交互自引用对象组需要的工作量极大,所以说引用计数只是用来说明垃圾收集器的工作方式,并未真正在哪个java虚拟机中实现。
在一些更快的模式中,它们并非基于引用计数的计数,它们的思想是:对任何“活”的对象,一定能最终追踪到其存活在堆栈或静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。由此,从堆栈和静态存储区开始遍历所有引用就能找到所有活着的对象。对于发现的每个引用,必须追踪它所引用的对象,然后是此对象的所有引用,如此反复进行,直到“根源于堆栈和静态存储区的引用”,所形成的网络全部被访问为止。你所访问过的对象必须是“活”的。这就解决了“交互自引用的对象数组”问题。
在这种情况下,java虚拟机采用的是自适应的垃圾回收技术。如何处理找到的存活对象,取决于不同的java虚拟机实现。这里有两种方法:第一种是“停止--复制”。显然这意味着先暂停程序的运行,然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全是垃圾。对象复制到新堆是一个挨着一个紧密排列,然后堆指针可以像一个传送带一样简单的移动到尚未分配的区域,高效的分配空间。但是对于"复制式的回收器"来说效率是非常低的。
问题一:需要维护两个堆,对此java虚拟机的解决方法是需要从堆中分配几块较大的内存,而复制的动作就发生在这几块较大的内存之间。
问题二:程序进入稳定状态之后,可能只会产生少量垃圾,甚至没有垃圾,尽管如此,复制式回收器仍然会经所有的内存从一处复制到另一处,这很浪费。为避免这一情况,一些java虚拟机会进行检查:要是没有垃圾产生,就会转到另一种工作模式,即标记--清扫。
标记-清扫的思想是:同样从堆栈和静态存储区出发,遍历所有的应用,进而找出所有存活的对象,给对象一个标记,但是这个过程不会回收任何对象,只有全部的标记动作完成时,清理动作才会开始。,没有被标记的对象将被释放,所以剩下的堆空间是不连续的。垃圾回收器要是需要得到连续空间就必须重新整理剩下的对象。
在这里讨论的java的虚拟机中,内存分配以较大的“块”为单位。如果对象比较大,那么将占有单独的块。严格来说,“停止-复制”要求在释放旧有对象之前,必须把所有存活的对象从旧堆复制到新堆。有了块之后垃圾回收器再回收的时候就可以往废弃的块里拷贝对象,每个块都有相应的代数来判断对象是否存活,块在某处被引用,其代数会加一。垃圾回收器会定期进行完整的清理动作--大型对象仍然不会被复制(只是代数增加),内涵小型对象的那些块则被复制整理。java虚拟机会进行监视,如果所有的对象都很稳定,垃圾回收器的效率很低,就切换到标记-清扫的方式。同样,虚拟机也会对标记-清扫进行监视,要是堆空间出现很多碎片,就会切换回“停止-复制”的方式。这就是自适应技术。
总的来说就是“自适应,分块的,停止-复制,标记-清扫”式垃圾回收器!!!
综上所述,帮助GC回收是很有必要的~~
linkFirst(T e)的源码:
/** * Links e as first element. */private void linkFirst(T e){final Node<T> f = first;final Node<T> newNode = new Node<T>(e, null, f); // newNode.e = e; newNode.prev = null; newNode.next = f;first = newNode;if(f == null){last = newNode; }else{ f.prev = newNode; }modCount++;size++;}linkFirst的代码是往双链表的头加元素,也就是deQue在队头的进队操作offerFirst(T e)addFirst(T e)内部封装的其实就是linkFirst(T e):
/** * Inserts the specified element at the front of this list. * * @param e the element to insert * @return {@code true} (as specified by {@link Deque#offerFirst}) * @since 1.6 */public boolean offerFirst(T e){addFirst(e);return true;}void linkLast(T e)的源码:
/** * Links e as last element. */void linkLast(T e){final Node<T> l = last;final Node<T> newNode = new Node<T>(e, l, null); // newNode.e = e; newNode.prev = l; newNode.next = null;last = newNode;if(l == null)first = newNode;elsel.next = newNode;modCount++;size++;}其实就是往List的高端添加元素,还是deQue的队尾进队操作。。
/** * Inserts the specified element at the end of this list. * * @param e the element to insert * @return {@code true} (as specified by {@link Deque#offerLast}) * @since 1.6 */public boolean offerLast(T e){addLast(e);return true;}T unlinkFirst(Node<T> f)的源码:
/** * Unlinks non-null first node f. */private T unlinkFirst(Node<T> f){//assert f = first && f != nullfinal T item = f.data;final Node<T> next = f.next;f.data = null;f.next = null;// help GCfirst = next;if(next == null) //也就是List只有一个元素的时候last = null;elsenext.prev = null; //让原先的第二个元素的prev->null ,成为新的头元素modCount++;size--;return item;}也是deQue的pollFirst()队头的出队操作。。。。
/** * Retrieves and removes the first element of this list, * or returns {@code null} if this list is empty. * * @return the first element of this list, or {@code null} if * this list is empty * @since 1.6 */public T pollFirst(){final Node<T> f = first;return (f == null)? null:unlinkFirst(f);}T unlinkLast(T e)的源码:
/** * Unlinks non-null last node l. */private T unlinkLast(Node<T> l){//assert l!=null && last = lfinal T item = l.data;final Node<T> prev = l.prev;l.data = null;l.prev = null;last = prev; if(prev == null) //也就是List只有一个元素的时候first = null;elseprev.next = null;//新的last nodemodCount++;size--;return item;}deQue操作:
/** * Retrieves and removes the last element of this list, * or returns {@code null} if this list is empty. * * @return the last element of this list, or {@code null} if * this list is empty * @since 1.6 */public T pollLast(){final Node<T> l = last;return (l == null)? null:unlinkLast(l);}T unlink(Node<T> ):
/** * Unlinks non-null node x. */private T unlink(Node<T> x){//assert x != nullfinal T item = x.data;final Node<T> prev = x.prev;final Node<T> next = x.next;if(prev == null)<span style="white-space:pre"></span>//如果删除的是头结点的情况first = next;else{prev.next = next;x.prev = null;<span style="white-space:pre"></span>//help GC<span style="white-space:pre"></span>}if(next == null)<span style="white-space:pre"></span>//删除尾结点的情况last = prev;else{next.prev = prev;x.next = null;<span style="white-space:pre"></span>//help GC}x.data = null;modCount++;size--;return item;}这个分离链表任意结点的代码是非常basic and important ,所以务必熟悉。。。
boolean remove(Object o):
/** * Removes the first occurrence of the specified element from this list, * if it is present. If this list does not contain the element, it is * unchanged. More formally(正规的), removes the element with the lowest index * {@code i} such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt> * (if such an element exists). Returns {@code true} if this list * contained the specified element (or equivalently, if this list * changed as a result of the call). * * @param o element to be removed from this list, if present * @return {@code true} if this list contained the specified element */public boolean remove(Object o){if(o == null){for(Node<T> x = first; x != null; x = x.next){if(x.data == null){this.unlink(x);return true;}}}else{for(Node<T> x = first; x != null; x = x.next){if(o.equals(x.data)){this.unlink(x);return true;}}}return false;}这段代码的核心就是 o == null ?get(i).data = null : o.equals(get(i).data) ,其实思想就是当参数传递对象在null的情况下也可以遍历双链表找到Node.data == null的情况这也是必须考虑的,其次就是正常情况用equals比较对象,最后如果还没匹配就返回fasle~~~
boolean addAll(int index,Collection<? extends T> coll):
/** * Inserts all of the elements in the specified collection into this * list, starting at the specified position. Shifts(移位) the element * currently at that position (if any) and any subsequent(随后的) elements to * the right (increases their indices). The new elements will appear(呈现) * in the list in the order(规则) that they are returned by the * specified collection's iterator. * * @param index index at which to insert the first element * from the specified collection * @param c collection containing elements to be added to this list * @return {@code true} if this list changed as a result of the call * @throws IndexOutOfBoundsException {@inheritDoc} * @throws NullPointerException if the specified collection is null */public boolean addAll(int index, Collection<? extends T> coll){checkPositionIndex(index); //后面再说Object[] a = coll.toArray();int newNum = a.length; //先检查原先coll的的lengthif(newNum == 0)return false;Node<T> pre,succ; //pre相当于新添加进来的Collection的firstif(index == size){ //往List的末端加入Collection,也就是index == sizepre = last;succ = null;}else{<span style="white-space:pre"></span>succ = getNode(index); //succ指向索引为index的结点pre = succ.prev;<span style="white-space:pre"></span>//pre->succ之前的结点}for(Object o : a){@SuppressWarnings("unchecked")T e = (T)o;Node<T> newNode = new Node<T>(e, pre, null); // newNode.data = e; newNode.pre = pre; newNode.next = null;if(pre == null){<span style="white-space:pre"></span>//如果输入的index为0的情况下first = newNode;}else{pre.next = newNode;}pre = newNode;<span style="white-space:pre"></span>//不断往pre之后添加newNode,pre也不停地后移}if(succ == null){<span style="white-space:pre"></span>//index == size 的时候,pre->newListLastElementlast = pre;}else{<span style="white-space:pre"></span>//在index处添加newList的情况pre.next = succ;<span style="white-space:pre"></span>succ.prev =pre;}modCount++;size += newNum;return true;}
void linkBefore(T e,Node<T> p):
/** * Inserts element e before non-null Node p. */private void linkBefore(T e,Node<T> p){//assert p != nullfinal Node<T> pred = p.prev;final Node<T> newNode = new Node<T>(e, pred, p); //newNode.data = e; newNode.prev = pred; newNode.next = p;p.prev = newNode;if(pred==null) //往List的低端插入newNode时first = newNode;elsepred.next = newNode;modCount++;size++;}下面介绍的就是比较健壮的参数检查机制:
/** * Tells if the argument is the index of an existing element. */private boolean isElementIndex(int index){return index >= 0 && index < size;} /** * Tells if the argument is the index of a valid(有效的) position for an * iterator or an add operation. */private boolean isPositionIndex(int index){return index >= 0 && index <=size;} /** * Constructs an IndexOutOfBoundsException detail(详情) message. * Of the many possible refactorings of the error handling(处理) code, * this "outlining" performs(执行) best with both server and client VMs. */private String outOfBoundsMsg(int index){return "index:"+index+",size="+size;}private void checkElementIndex(int index){if(!isElementIndex(index))throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}private void checkPositionIndex(int index){if(!isPositionIndex(index))throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}上面的isElementIndex是对结点的索引属于[0,size-1),而IsPositionIndex是对位置的索引,需要用到index == size这个位置,比如addAll(),所以其范围为[0,size].否则就抛出IndexOutofBoundsException异常!!!
Node<T> getNode(int index):
/** * Returns the (non-null) Node at the specified element index. */private Node<T> getNode(int idx){// assert isElementIndex(idx)checkElementIndex(idx);if(idx < (size >> 1)){Node<T> p = first;for(int i = 0 ;i<idx;i++){p = p.next;}return p;}else{Node<T> p = last;for(int i = size-1;i > idx;i--){p = p.prev;}return p;}}这里面有一点折半的思想,因为是双链表嘛,所以也可以从后往前遍历。。。 更有效率~~~
- LinkedList源码系列(1)
- LinkedList源码系列(2)
- JDK源码系列(1)----LinkedList 源码分析
- java源码阅读系列-LinkedList
- LinkedList源码(1)
- JDK源码学习系列05----LinkedList
- Java集合系列之LinkedList源码分析
- Java集合系列之LinkedList源码分析
- JAVA集合源码分析系列:LinkedList源码分析
- JAVA集合源码分析系列:LinkedList源码分析
- LinkedList源码
- LinkedList源码
- LinkedList源码
- LinkedList源码
- .NET Framework源码研究系列之---ArrayList与LinkedList
- 深入Java集合源码学习系列:LinkedList的实现原理
- JDK1.8源码阅读系列之二:LinkedList
- Java 集合系列05之 LinkedList源码解析
- (三十四)signed和unsigned的用法
- sql excel
- 周末“干活”之 Mesos Meetup
- spring mybatis mvc cache 缓存 二级缓存
- meta-info配置
- LinkedList源码系列(1)
- double输出
- Spring @Resource、@Autowired、@Qualifier的注解注入及区别
- activemq的几种基本通信方式总结
- 利用htmlunit在YouTube上抓取视频连接地址
- PHP 匹配中文(UTF-8)
- 带你了解Fresco
- 我的Android进阶之旅------>Android Service学习之AIDL, Parcelable和远程服务实例
- (三十五)帮贴吧人解答的一道题