您的位置:首页 > 编程语言 > Java开发

LinkedList源码解析(JDK1.8)

2019-07-24 17:09 549 查看

文档头

  • LinkedList是List和Deque接口的双向链表实现,实现所有列表操作,允许null。
  • 有关index的操作都会遍历链表,只是从头走或从尾遍历的区别
  • 是不同步,需要外部同步操作或 Collections.synchronizedList(new LinkedList(...));
  • iterator也是fast-fail类型的,iterator生成后,除了iterator自己的操作,其它对实例的结构性修改都会报ConcurrentModificationException。

变量定义

  • Node类  
    [code]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会记录上一个、下一个节点的引用,以及自身值。

  • [code]transient int size = 0;

    链表长度

  • [code]    transient Node<E> first;

    表头

  • [code]transient Node<E> last;

    表尾

节点的插入、删除

  • linkFist(E e)  将e链到表头   
    [code]    private void linkFirst(E e) {
    final Node<E> f = first; // 获取原first
    final Node<E> newNode = new Node<>(null, e, f); // 新建节点, prev为null,新节点为
    //头,prev指定为null,,next为原first。
    first = newNode; // 指定first为新节点
    if (f == null) // 如果原first为空,size=0,新节点就是第一个节点,尾也是新节点
    last = newNode;
    else
    f.prev = newNode; // 否则原first的prev指向新新节点
    size++;
    modCount++;
    }

     

  • linkLast(E e) 将e链到表尾 

    [code] void linkLast(E e) {
    final Node<E> l = last; // 获取原last
    final Node<E> newNode = new Node<>(l, e, null); // 创建新node,next为null,prev为
    // 原last
    last = newNode;
    if (l == null) // 原last为null时,原来没有元素, 这是第一个,所以first也为node。
    first = newNode;
    else
    l.next = newNode;  // 否则原last的next指向现last。
    size++;
    modCount++;
    }

     

  • linkBefore(E e, Node succ) 将e链接到succ节点前 , 要求succ节点非空   

    [code]void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;  // 获取succ前一个节点
    final Node<E> newNode = new Node<>(pred, e, succ); // 创建新节点,prev为succ前,
    // next为succ
    succ.prev = newNode; // succ的prev指向新节点, 将succ链到新节点后。
    if (pred == null) // 如果succ原来的前一个节点为null,说明succ原来是first节点,那么
    // 现在的first就是新节点
    first = newNode;
    else // succ原来前一个不为null, 将新节点链到其后。
    pred.next = newNode;
    size++;
    modCount++;
    }

      

  • unlinkFirst、unlinkLast 分别移除first、last元素,也只是对节点的prev、next修改就可以实现,无需遍历集合
  • unlink(Node<E> e)  移除一个非空节点, 可以是头是尾 
    [code]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) { // 如果前一个为null,说明节点原来是first,移除后的first为节点
    // 的next
    first = next;
    } else { // 前一个不为null, 移除节点后, 前一个的next应该指向节点的next。
    prev.next = next;
    x.prev = null;
    }
    if (next == null) { // 后一个节点为null, 节点为原来的last, 现在的last为节点的前
    // 一个
    last = prev;
    } else { // 否则, 后一个节点的prev,指向节点的前一个。
    next.prev = prev;
    x.next = null;
    }
    x.item = null;
    size--;
    modCount++;
    return element;
    }

     

  • node(int index) 获取index处的节点 

    [code]Node<E> node(int index) {
    // assert isElementIndex(index);
    
    if (index < (size >> 1)) { // 如果index < (size/2)时,从头开始遍历
    Node<E> x = first;
    for (int i = 0; i < index; i++)
    x = x.next;
    return x;
    } else { // index > (size/2)时,从尾开始遍历。
    Node<E> x = last;
    for (int i = size - 1; i > index; i--)
    x = x.prev;
    return x;
    }
    }

     node方法主要用作内部增删之前,找到index处的节点方便操作,最坏会遍历sise/2个元素。

public方法

public方法都是前面link、unlink、node方法的封装,前面看懂了就很容易。

  • [code] getFirst()、getLast()
    [code]public E getFirst() {
    final Node<E> f = first;
    if (f == null)
    throw new NoSuchElementException();
    return f.item;
    }
    public E getLast() {
    final Node<E> l = last;
    if (l == null)
    throw new NoSuchElementException();
    return l.item;
    }

    first不为null,返回first、last值

  • [code]removeFirst()、removeLast()
    [code]    public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
    throw new NoSuchElementException();
    return unlinkFirst(f);
    }
    public E removeLast() {
    final Node<E> l = last;
    if (l == null)
    throw new NoSuchElementException();
    return unlinkLast(l);
    }

    判断不为null,分别调用unlinkFirst、unlinkLast。

  • addFirst()、addLast() 

    [code]    public void addFirst(E e) {
    linkFirst(e);
    }
    public void addLast(E e) {
    linkLast(e);
    }

    直接调用

  • [code]indexOf(Object o)
[code]    public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}

      IndexOf方法直接从表头开始遍历,找到第一个equals匹配的即返回。最差会遍历到表尾,也就是size个元素。

  • [code]contains(Object o)
[code] public boolean contains(Object o) {
return indexOf(o) != -1;
}

    contains方法调用了indexOf(int index),因此也可能遍历全表。

  • [code] get(int index) 、 set(int index, E element)
    [code] public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
    }
    public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
    }

    get、set通过node(index)查找index处元素,因此会根据index判断从头或是从尾遍历,最多遍历size/2个元素。

    3ff7

总结

  • LinkedList由Node构成,是双向链表结构
  • LinkedList对头、尾的增删比较高效;对链表中间对象的插入需要先遍历并找到处对象,然后执行引用的修改
  • LinkedList contains(o)、indexOf(o)会从头遍历链表,最多遍历全表; get(index)、set(index,e)时,会根据index判断从头还是从尾开始,最差遍历size/2元素。

LinkedList和ArrayList的区别

  • ArrayList是基于数组实现,通过新建数组复制元素来扩容;LinkedList是基于双向链表实现,没有容量这个概念,增删元素后直接修改size。
  • 从头插入时, LinkedList非常快,只需要新建节点然后改变原first节点的prev,,而ArrayList需要将所有元素后移,因此开销会比较大; 尾部插入时,LinkedList同理很快,,ArrayList容量足够时,只需将新节点存入数组的size位置,也挺快的。中间插入时,LinkedList需要有找到index处元素的开销,这个开销是不一定的;ArrayList需要将index处往后的元素集体移位,开销也不一定。 
  • get(index)、set(index, e)时,因为ArrayList基于数组,查找效率是常量级的;LinkedList需要找到index处元素。  这里ArrayList肯定比LinkedList快。
  • 遍历时, ArrayList支持随机快速访问,可以用for(int i; i<size;i++)循环,这样的效率非常高;LinkedList虽然也可以用fori,或者迭代器,但遍历效率不如ArrayList。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: