LinkedList源码解析

 一、简介
LinkedList类声明以下:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
能够发现 LinkedList继承了 AbstractSequentialList抽象类,而不是像 ArrayList和 Vector那样实现 AbstractList,实际上,java类库中只有 LinkedList继承了这个抽象类,正如其名,它提供了对序列的连续访问的抽象:
LinkedList的底层是 Deque双向链表,实现了 Deque接口,而 Deque接口继承于 Queue接口,所以,在java中,若是要实现队列,通常都使用 LinkedList来实现。
 
Node结点
Node节点 一共有三个属性:item表明节点值,prev表明节点的前一个节点,next表明节点的后一个节点。
LinkedList中关于链表结点的定义以下:
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;
        }
    }
每一个结点都有一个前驱和后继结点,而且在 LinkedList中也定义了两个变量分别指向链表中的第一个和最后一个结点:
transient Node<E> first;
transient Node<E> last;

 

二、添加(add)操做
public boolean add(E e) {
    linkLast(e);
    return true;
}

void linkLast(E e) {
    final Node<E> l = last;//链表最后一个节点
    final Node<E> newNode = new Node<>(l, e, null);//创建节点对象,前一个(perv属性)是last,后一个(next属性)是null,中间item数据是输入的参数e
    last = newNode;
    if (l == null)
        first = newNode; //若是最后一个节点 为null表示链表为空,则添加数据时将新加的数据做为第一个节点
    else
        l.next = newNode; //若是链表不为空则将最后一个链表的next属性指向新添加的节点
    size++; //链表长度自增1
    modCount++;
}

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;
    }
}
总结:
LInkedList添加操做时每一个新添加的对象都会被放到新建的Node对象中,Node对象中包含加入的对象和先后指针属性(pred和next,pred和next其实都是Node对象,直接存放的就是当前对象的前一个节点对象和后一个节点对象,最后一个及节点的next值为null),而后将原来最后一个last节点的next指针指向新加入的对象,便可
 
addAll操做:
public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index);//判断index是否越界,越界则抛出异常
    Object[] a = c.toArray();
    int numNew = a.length;//要插入的集合的长度
    if (numNew == 0)
        return false;
    Node<E> pred, succ;//声明pred和succ两个Node对象,用于标识要插入元素的前一个节点和最后一个节点
    if (index == size) { //若是size等于原数组长度则表示在结尾添加
        succ = null;
        pred = last;
    } else {
        succ = node(index);//index位置上的Node对象
        pred = succ.prev;
    }
    for (Object o : a) { //遍历要插入的集合
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);
        if (pred == null)
            first = newNode; //若是要插入的位置的前一个节点为null表示是第一个节点,则直接将newNode赋给第一个节点
        else
            pred.next = newNode; //将要插入的集合元素节点对象赋给此位置原节点对象的前一个对象的后一个,即更改前一个节点对象的next指针指到新插入的节点上
        pred = newNode;//更改指向后将新节点对象赋给pred做为下次循环中新插入节点的前一个对象节点,依次循环
    }
    //此时pred表明集合元素的插入完后的最后一个节点对象
    if (succ == null) { //结尾添加的话在添加完集合元素后将最后一个集合的节点对象pred做为last
        last = pred;
    } else {
        pred.next = succ;//将集合元素的最后一个节点对象的next指针指向原index位置上的Node对象
        succ.prev = pred;//将原index位置上的pred指针对象指向集合的最后一个对象
    }
    size += numNew;
    modCount++;
    return true;
}

Node<E> node(int index) {
    if (index < (size >> 1)) { //判断index是否小于size的一半,若是小于则从头遍历节点,不然从结尾遍历节点
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next; //从first第一个节点开始,依次将后一个节点赋给x
        return x; //返回index位置上的Node对象,下同理
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}
总结:
LinkedList在某个位置插入元素是经过将原位置节点的前一个节点的后一个指针指向新插入的元素,而后将原位置的节点的前一个指针指向新元素来实现的,至关于插入元素后后面的元素后移了,可是不是像ArrayList那样将全部插入位置后面的元素都后移,此处只是改变其先后节点的指向
 
三、删除操做
public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

E unlink(Node<E> x) {
    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;//若是next为null表示是最后一个元素,直接将pred节点赋给last
    } else {
        next.prev = prev;
        x.next = null;//将传入节点的后一个节点置空,使其后一个节点指针不指向任何元素
    }
    x.item = null;//将传入的节点对象上的对象置空,也就是整个Node对象中的属性都为空了,后面会被GC回收
    size--;
    modCount++;
    return element;
}
总结:
LinkedList删除操做是经过将index位置上的前一个节点的next执行index位置的后一个节点,同时将后一个节点的pred指向前一个节点,而后将index位置上的Node节点对象属性都置空来实现的,置空后的Node对象会被GC垃圾回收期回收掉。
 
四、修改操做
public E set(int index, E element) {
    checkElementIndex(index);//检查索引越界状况
    Node<E> x = node(index);//获取index位置上的节点对象
    E oldVal = x.item;
    x.item = element;//将新插入的元素直接赋给此位置上节点对象的item属性便可
    return oldVal;
}
总结:
LinkedList中的实际数据保存在节点对象的item属性中
 
五、查询操做
public E get(int index) {
    checkElementIndex(index);//检查索引越界状况
    return node(index).item;//返回该index位置上的节点对象的item属性,即该位置上的实际值
}
总结:
插叙操做是经过node方法实现的,而node方法是经过先判断index位置是在整个链表的前一半仍是后一半来遍历前一半或者后一半元素来获取index位置上的元素
 
六、LinkedList和ArrayList的大体区别:
一、ArrayList继承于 AbstractList, LinkedList继承于 AbstractSequentialList;
二、ArrayList基于数组, LinkedList基于双向链表,对于随机访问, ArrayList比较占优点,LinkedList插入、删除元素比较快,若是只要调整指针的指向那么时间复杂度是O(1),可是若是针对特定位置须要遍历时,时间复杂度是O(n),也就是LinkedList在随机访问元素的话比较慢;
三、LinkedList没有实现本身的 Iterator,可是有 ListIterator和 DescendingIterator;
四、LinkedList须要更多的内存,由于 ArrayList的每一个索引的位置是实际的数据,而 LinkedList中的每一个节点中存储的是实际的数据和先后节点的位置;
五、ArrayList 和 LinkedList都是非同步的集合。
六、和ArrayList同样,LinkedList也是非线程安全的,只有在单线程下才可使用。为了防止非同步访问,能够采用以下方式建立LinkedList:List list= Collections.synchronizedList(new LinkedList());
七、LinkedList基于双向链表实现,元素均可觉得null。
相关文章
相关标签/搜索