LinkedList和Java中的指针操作

LinkedList类似C语言的双向链表,但是java中没有指针如何实现呢,看完LinkedList
你将对java中的引用类型有更深入的理解.

LindedList的声明如下:

public class LinkedList extends AbstractSequentialList implements List, Cloneable, java.io.Serializable
有关AbstractSequentialList:http://blog.csdn.net/treeroot/archive/2004/09/18/108696.aspx
有关List: http://blog.csdn.net/treeroot/archive/2004/09/14/104638.aspx
有关Cloneable:http://blog.csdn.net/treeroot/archive/2004/09/07/96936.aspx

下面分析一下这个链表的实现,这里只重点分析某些方法。

private transient Entry header = new Entry(null, null, null);
private transient int size = 0;
public LinkedList() {
  header.next = header.previous = header;
}

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来提高性能。