LinkedList源码系列(1)

//: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 GC
			tmp.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;
		else
			l.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 != null
		final T item = f.data;
		final Node<T> next = f.next;
		f.data = null;
		f.next = null;	// help GC
		first = next;
		if(next == null)      //也就是List只有一个元素的时候
			last = null;
		else
			next.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 = l
		final T item = l.data;
		final Node<T> prev = l.prev;
		l.data = null;
		l.prev = null;
		last = prev;	 
		if(prev == null)      //也就是List只有一个元素的时候
			first = null;
		else
			prev.next = null;	//新的last node
		modCount++;
		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 != null
		final 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的的length
		if(newNum == 0)
			return false;
		Node<T> pre,succ;    //pre相当于新添加进来的Collection的first
		if(index == size){     //往List的末端加入Collection,也就是index == size
			pre = 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->newListLastElement
			last = 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 != null
		final 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;
		else
		pred.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;
		}
	}
这里面有一点折半的思想,因为是双链表嘛,所以也可以从后往前遍历。。。  更有效率~~~


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值