Java Collecttions - LinkedList 源码分析

Java Collecttions - LinkedList 源码分析

链表表示多个结点,用某种关系关联起来。前一个结点要拥有下个结点的引用。拿到这个引用之后,就可以访问下一个结点,进而遍历完整个List。
首先,链表中要定义“结点”,告诉大家什么才算是列表中的一个结点。然后将结点串起来。
其次,要提供整个列表的添加、删除、查找方法。

链表有很多种实现方法,用数组(ArrayList)和指针(LinkedList)都可以。Java里没有指针,用的是引用(reference)。本质上来说,只要能知道结点的位置关系就行,具体的实现形式可以有很多。但是不同的实现形式的链表,其添加、删除、查找时表示出的特性也就不同。

JDK里的LinkedList有以下的特点:
1. 不是线程安全的。
2. 保留了头结点和最后一个结点。
3. 是双向列表,有指向下个结点和上个结点的指针。
4. 可以用于堆栈(stack)、队列(queue)、双向队列(deque)。

类的定义

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

    //队列中存储元素的数量
    transient int size = 0;

    //第一个结点
    transient Node<E> first;

    //最后一个结点
    transient Node<E> last;

Node类里定义了一个泛型元素和指向前后Node的引用。说明这里定义的结点是可以存储任何类型数据的,而且用到它的链表也会是一个双向链表。

private static class Node<E> {
   E item;
   Node<E> next;
   Node<E> prev;

   Node(Node<E> prev, E element, Node<E> next) {
       this.item = element;
       this.next = next;
       this.prev = prev;
   }
}

添加元素

有几类add方法向链表中的不同位置添加元素。
最简单的插入方法,会向链表尾插入元素。

//向链表的最后添加元素。这个方法等价于addLast
public boolean add(E e) {
   linkLast(e);
   return true;
}

/**
     * Links e as last element.
     * 将e添加到链表的末尾
     * 要注意一个临界情况,链表为空和不为空。
*/
void linkLast(E e) {
   final Node<E> l = last; //l作为中间变量,目的是记录之前的链表尾结点。
   final Node<E> newNode = new Node<>(l, e, null);
   last = newNode;
   if (l == null)
       first = newNode;
   else
       l.next = newNode;
   size++;
   modCount++;
}

向中间某个位置插入元素。

//在index元素的后面插入元素
//要考虑index为负数、超过size大小的情况
public void add(int index, E element) {
   checkPositionIndex(index);

   if (index == size)
       linkLast(element);       //直接调用插入到链表尾就可以
   else
       linkBefore(element, node(index));
}

//如果位置小于0或者大于size,就要抛一个异常
private void checkPositionIndex(int index) {
   if (!isPositionIndex(index))
       throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

/**
* Returns the (non-null) Node at the specified element index.
* 返回指定位置的元素,这里不需要再对index的范围做校验了,由其他方法来保障index的范围正确性。重复校验的话,代码比较臃肿。
*/
Node<E> node(int index) {
   // assert isElementIndex(index);

    //使用位移符号效率比较高。根据index位置的同确定从链表头查找还是链表尾查找。
   if (index < (size >> 1)) {
       Node<E> x = first;
       for (int i = 0; i < index; i++)
           x = x.next;
       return x;
   } else {
       Node<E> x = last;
       for (int i = size - 1; i > index; i--)
           x = x.prev;
       return x;
   }
}

/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node<E> succ) {
   // assert succ != null;
   final Node<E> pred = succ.prev;
   final Node<E> newNode = new Node<>(pred, e, succ);
   succ.prev = newNode;
   if (pred == null)
       first = newNode;
   else
       pred.next = newNode;
   size++;
   modCount++;
}

/**
* 将一个实现了Collection接口的类添加到链表的index位置中。
* 
*/
public boolean addAll(int index, Collection<? extends E> c) {
   checkPositionIndex(index);
    //先把c转成一个数组
   Object[] a = c.toArray();
   int numNew = a.length;
   if (numNew == 0)
       return false;
    //初始化一下要插入的位置
   Node<E> pred, succ;
   if (index == size) {
       succ = null;
       pred = last;
   } else {
       succ = node(index);
       pred = succ.prev;
   }
    //思想是把c插入到链表中间,形成一个分支。prev指向的一直是这个分支的结尾,succ始终指向插入的结点。最后的时候把prev和succ连起来就可以了。
   for (Object o : a) {
       @SuppressWarnings("unchecked") E e = (E) o;
       Node<E> newNode = new Node<>(pred, e, null);
       if (pred == null)
           first = newNode;
       else
           pred.next = newNode;
       pred = newNode;
   }

   if (succ == null) {
       last = pred;
   } else {
       pred.next = succ;
       succ.prev = pred;
   }

   size += numNew;
   modCount++;
   return true;
}

移除元素

移除第一个结点。移除的时候要返回第一个结点中元素的值,这样方便用于栈的push和pop。

//移除第一个元素
public E removeFirst() {
   final Node<E> f = first;
   if (f == null)
       throw new NoSuchElementException();
   return unlinkFirst(f);
}

//移除第一个非null的元素
private E unlinkFirst(Node<E> f) {
   // assert f == first && f != null;
   final E element = f.item;
   final Node<E> next = f.next;
   f.item = null;
   f.next = null; // help GC。把第一个节点中的元素都清空,这样方便垃圾回收
   first = next;
   //考虑到了first等于last这种临界情况。
   if (next == null)
       last = null;
   else
       next.prev = null;
   size--;
   modCount++;
   return element;
}

从列表中移除一个Object,判断Object相等的依据是equals方法。当需要比较两个对象时,我们都要考虑到对象的equals方法。

public boolean remove(Object o) {
   if (o == null) {
       for (Node<E> x = first; x != null; x = x.next) {
           if (x.item == null) {
               unlink(x);
               return true;
           }
       }
   } else {
       for (Node<E> x = first; x != null; x = x.next) {
           if (o.equals(x.item)) {
               unlink(x);
               return true;
           }
       }
   }
   return false;
}

将移除第一个结点、最后一个结点、任意一个结点的方法提炼出来,这样代码更加简洁。

//删除某个确定的结点。要注意判断临界条件、清空它本身的内容。
E unlink(Node<E> x) {
   // assert x != null;
   final E element = x.item;
   final Node<E> next = x.next;
   final Node<E> prev = x.prev;

   if (prev == null) {
       first = next;
   } else {
       prev.next = next;
       x.prev = null;
   }

   if (next == null) {
       last = prev;
   } else {
       next.prev = prev;
       x.next = null;
   }

   x.item = null;
   size--;
   modCount++;
   return element;
}

清除整个链表
要将整个链表中结点的内容都清空,头尾结点也清空。这样有利于GC。

public void clear() {
   // 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
   for (Node<E> x = first; x != null; ) {
       Node<E> next = x.next;
       x.item = null;
       x.next = null;
       x.prev = null;
       x = next;
   }
   first = last = null;
   size = 0;
   modCount++;
}

获取结点

使用的是node方法,用遍历的方式找到位置为index的结点,然后返回。使用了一个技巧,如果index

Node<E> node(int index) {
   // assert isElementIndex(index);

   if (index < (size >> 1)) {
       Node<E> x = first;
       for (int i = 0; i < index; i++)
           x = x.next;
       return x;
   } else {
       Node<E> x = last;
       for (int i = size - 1; i > index; i--)
           x = x.prev;
       return x;
   }
}

其他方法

pop和push:出栈和入栈,LinkedList是以头结点为栈顶的
peek:取第一个元素,不删除它
poll:取第一个元素,并且要队列中删除它

性能

获取某个结点时需要遍历整个列表,时间为O(n)
明确知道某个结点,需要添加和删除时,时间为O(1)
容量几乎是没有限制的
适合用做栈和队列,而ArrayList只适合用做栈,因为它添加和删除头结点时,要复制后面的结点。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值