首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件开发 >

源码阅览之LinkedList

2012-08-01 
源码阅读之LinkedListLinkedList类似C语言的双向链表,但是java中没有指针如何实现呢,看完LinkedList   你

源码阅读之LinkedList
LinkedList类似C语言的双向链表,但是java中没有指针如何实现呢,看完LinkedList
  你将对java中的引用类型有更深入的理解。LindedList的声明如下:
  

public class LinkedList extends AbstractSequentialList implements List, Cloneable, java.io.Serializable


  下面分析一下这个链表的实现,这里只重点分析某些方法。
  
private transient Entry header = new Entry(null, null, null);  private transient int size = 0;  public LinkedList() {  header.next = header.previous = header;//previous 和 next都指向自身  }

  header相当于C中的头指针,而且这个头指针不是链表的元素,有关Entry将在下面介绍。
 
 public LinkedList(Collection c) {  this();  addAll(c);  }  public Object getFirst() {  if (size==0)  throw new NoSuchElementException();   return header.next.element;  }

  头指针的下一个元素就是第一个元素
  public Object getLast() {
  if (size==0)
  throw new NoSuchElementException();
  return header.previous.element;
  }
  头指针的前一个当然就是最后一个了
 
 public Object removeFirst() {  Object first = header.next.element;  remove(header.next);  return first;  }  public Object removeLast() {  Object last = header.previous.element;  remove(header.previous);  return last;  }  public void addFirst(Object o) {  addBefore(o, header.next);  }  public void addLast(Object o) {  addBefore(o, header);  }

  这个方法在链表末尾插入新的元素,功能和add方法一样,这个方法完全是为了对称性(因为有addFirst)
 
 public boolean contains(Object o) {  return indexOf(o) != -1;  }  public int size() {  return size;  }  public boolean add(Object o) {  addBefore(o, header);  return true;  }  public boolean remove(Object o) {  if (o==null) {  for (Entry e = header.next; e != header; e = e.next) {  if (e.element==null) {  remove(e);  return true;  }  }  } else {  for (Entry e = header.next; e != header; e = e.next) {  if (o.equals(e.element)) {  remove(e);  return true;  }  }  }  return false;  }

  用过C的人应该感到很熟悉了,这里就是通过指针后移来查找相同的元素,注意这里最多只删除一个
  元素,符合List接口中的说明。
  
public boolean addAll(Collection c) {  return addAll(size, c);  }  public boolean addAll(int index, Collection c) {  int numNew = c.size();  if (numNew==0)  return false;  modCount++;  Entry successor = (index==size ? header : entry(index));  Entry predecessor = successor.previous;  Iterator it = c.iterator();  for (int i=0; i       Entry e = new Entry(it.next(), successor, predecessor);  predecessor.next = e;  predecessor = e;  }  successor.previous = predecessor;  size += numNew;  return true;  }

  这里又看到熟悉的面孔了,在数据结构里面的链表中插入元素不就是这样吗?
  successor代表后一个指针,就是说插入的数据在他的前面,predecessor代表前一个指针,也就是要插入数据之前的指针。如果对数据结构比较了解的话,应该比较容易理解,这里我可以把代码改一下,但是不能算是优化:
 
 for (int i=0; i       Entry e = new Entry(it.next(), null, predecessor);  predecessor.next = e;  predecessor = e;  }  predecessor.next = successor;    successor.previous = predecessor;

  这样修改和原来是一样的,如果Entry有一个这样的构造函数Entry(Object element,Entry previous)那就
  好了,那就可以用修改后的代码优化了(并没有多大的价值)。如果可以看明白为什么可以这样修改,那就完全理解了,如果对这种数据结构不熟悉的话,可以画一个链表,然后按代码执行修改你的链表,这个方法很有效的。
  
public void clear() {  modCount++;  header.next = header.previous = header;  size = 0;  }  public Object get(int index) {  return entry(index).element;  }   public Object set(int index, Object element) {  Entry e = entry(index);  Object oldVal = e.element;  e.element = element;  return oldVal;  }  public void add(int index, Object element) {  addBefore(element, (index==size ? header : entry(index)));  }  public Object remove(int index) {  Entry e = entry(index);  remove(e);  return e.element;  }  private Entry entry(int index) {  if (index < 0 || index >= size)  throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);  Entry e = header;  if (index < (size >> 1)) {  for (int i = 0; i <= index; i++)  e = e.next;  } else {  for (int i = size; i > index; i--)  e = e.previous;  }  return e;  }

  这个方法返回一个Entry,这里注意注意当index为0时返回的是head.next,不可能返回head。因为index>=0而且
   所以循环语句至少要执行一次。这里指针移动进行了优化,因为是一个双向链表,可以朝不同方向移动,size>>2相当于size=size/2
  
public int indexOf(Object o) {  int index = 0;  if (o==null) {  for (Entry e = header.next; e != header; e = e.next) {  if (e.element==null)  return index;  index++;  }  } else {  for (Entry e = header.next; e != header; e = e.next) {  if (o.equals(e.element))  return index;  index++;  }  }  return -1;  }

  这里唯一注意的就是index++的位置
  
public int lastIndexOf(Object o) {  int index = size;  if (o==null) {  for (Entry e = header.previous; e != header; e = e.previous) {  index--;  if (e.element==null)  return index;  }  } else {  for (Entry e = header.previous; e != header; e = e.previous) {  index--;  if (o.equals(e.element))  return index;  }  }  return -1;  }

  注意index--的位置
 
 public ListIterator listIterator(int index) {  return new ListItr(index);  }

  以下是一个私有内部类
  
private class ListItr implements ListIterator {  private Entry lastReturned = header;  private Entry next;  //调用next()方法的节点  private int nextIndex;  private int expectedModCount = modCount;  ListItr(int index) {  if (index < 0 || index > size)  throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);  if (index < (size >> 1)) {  next = header.next;  for (nextIndex=0; nextIndex           next = next.next;  } else {  next = header;  for (nextIndex=size; nextIndex>index; nextIndex--)  next = next.previous;  }  }  public boolean hasNext() {  return nextIndex != size;  }  public Object next() {  checkForComodification();  if (nextIndex == size)  throw new NoSuchElementException();   lastReturned = next;  next = next.next;  nextIndex++;  return lastReturned.element;  }  public boolean hasPrevious() {  return nextIndex != 0;  }  public Object previous() {  if (nextIndex == 0)  throw new NoSuchElementException();    lastReturned = next = next.previous;  nextIndex--;  checkForComodification();  return lastReturned.element;  }  public int nextIndex() {  return nextIndex;  }  public int previousIndex() {  return nextIndex-1;  }  public void remove() {  checkForComodification();  try {  LinkedList.this.remove(lastReturned);  } catch (NoSuchElementException e) {  throw new IllegalStateException();  }  if (next==lastReturned)  //这里表示删除的是调用previous()返回的元素。  next = lastReturned.next; //next被删除,所以next要后移,索引不变。  else  nextIndex--;      //删除的是next.previous,所以索引要减1。        lastReturned = header;  //这里很重要:1.释放资源。2.不允许连续调用remove。  expectedModCount++;  }  public void set(Object o) {  if (lastReturned == header)  throw new IllegalStateException();  checkForComodification();  lastReturned.element = o;  }  public void add(Object o) {  checkForComodification();  lastReturned = header;  addBefore(o, next);  nextIndex++;  expectedModCount++;  }  final void checkForComodification() {  if (modCount != expectedModCount)  throw new ConcurrentModificationException();  }  }

  以下是Entry的定义:
  
private static class Entry {  Object element;  Entry next;  Entry previous;  Entry(Object element, Entry next, Entry previous) {  this.element = element;  this.next = next;  this.previous = previous;  }  }

  很简单,就是一个双向链表的接点,只有一个构造函数而已,没有其他方法。这里的next和previous不就是一个引用吗?其实不就是一个C里面的指针吗!不过C语言不会处理空指针,直接让操作系统处理了,Java确实抛出一个系统异常NullPointerException,根本不给他破坏系统的机会。
  
private Entry addBefore(Object o, Entry e) {  Entry newEntry = new Entry(o, e, e.previous);  newEntry.previous.next = newEntry;  newEntry.next.previous = newEntry;  size++;  modCount++;  return newEntry;  }  private void remove(Entry e) {  if (e == header)  throw new NoSuchElementException();   e.previous.next = e.next;  e.next.previous = e.previous;  size--;  modCount++;  }  public Object clone() {  LinkedList clone = null;  try {   clone = (LinkedList)super.clone();  } catch (CloneNotSupportedException e) {   throw new InternalError();  }  // Put clone into "virgin" state  clone.header = new Entry(null, null, null);  clone.header.next = clone.header.previous = clone.header;  clone.size = 0;  clone.modCount = 0;  // Initialize clone with our elements  for (Entry e = header.next; e != header; e = e.next)  clone.add(e.element);   return clone;  }  public Object[] toArray() {  Object[] result = new Object[size];  int i = 0;  for (Entry e = header.next; e != header; e = e.next)  result[i++] = e.element;  return result;  }  }  public Object[] toArray(Object a[]) {  if (a.length < size)  a = (Object[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);  int i = 0;  for (Entry e = header.next; e != header; e = e.next)  a[i++] = e.element;   if (a.length > size)  a[size] = null;   return a;  }  private static final long serialVersionUID = 876323262645176354L;  private synchronized void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {  // Write out any hidden serialization magic  s.defaultWriteObject();  // Write out size  s.writeInt(size);  // Write out all elements in the proper order.  for (Entry e = header.next; e != header; e = e.next)  s.writeObject(e.element);  }  private synchronized void readObject(java.io.ObjectInputStream s)    throws java.io.IOException,ClassNotFoundException {  // Read in any hidden serialization magic  s.defaultReadObject();  // Read in size  int size = s.readInt();  // Initialize header  header = new Entry(null, null, null);  header.next = header.previous = header;  // Read in all elements in the proper order.  for (int i=0; i       add(s.readObject());  }

  这里和ArrayList有一个区别就是size被声明为 transient的,因为这里调用的是add方法,这样
  size会自动增加,而在ArrayList是直接给数组赋值(效率更高)。
  这里比较一下ArrayList和LinkedList:
  1.ArrayList是基于数组,LinkedList基于链表实现。
  2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
  4.查找操作indexOf,lastIndexOf,contains等,两者差不多。
  这里只是理论上分析,事实上也不一定,比如ArrayList在末尾插入和删除数据就不设计到数据移动,不过还是有这么个建议:随机访问比较多的话一定要用ArrayList而不是LinkedList,如果需要频繁的插入和删除应该考虑用LinkedList来提高性能。
值得注意的是:当向ArrayList添加一个对象时,实际上就是将对象添加到了ArrayList底层维护的数组中;当向LinkedList添加一个对象时,实际上LinkedList会生成一个Entry对象,其结构为:
Entry
{
E element;
Entry<E> next;
Entry<E> previous;
}
其中element是我们向LinkedList中添加的元素,然后Entry又构造好了向前和向后的引用next和previous,最后将生成的Entry对象加入到链表中去。换句话说,LinkedList维护的是一个Entry对象

热点排行