LinkedList源码分析

LinkedList

概述

LinkedList实现的接口List和Deque,实现了几乎所有的LIst接口操作,其中保存的数据也可以是null。在其内部也不有实现线程安全的方法,所以其也是线程不安全的,跟ArrayList一样,多线程编程时要考虑同步的问题。其定义了一个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;
    }
}

成员变量

// 存储的元素个数
transient int size = 0;
// 指向双端链表的开头节点
transient Node<E> first;
// 指向双端链表的链尾节点
transient Node<E> last;

构造函数

其构造函数一个是空实现,一个是用另外一个集合进行初始化。

public LinkedList() {
}
​
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

addAll() 方法

public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);// 初始时size为0
}
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;
    if (index == 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)// 如果前驱是null,则说明要插入的node是队列的第一个
            first = newNode;
        else// 
            pred.next = newNode;
        pred = newNode;// 将当前node设置为前驱,为下一次循环做准备
    }
​
    // 将新元素接入队列中
    if (succ == null) {
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;
    }
​
    size += numNew;// 设置size
    modCount++;
    return true;
}
​
private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
​
private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}
​
Node<E> node(int index) {
    if (index < (size >> 1)) {// index在队列的前一半中
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {// index 在队列的后一半中
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

由上面的代码摘录可以看出,插入新元素时没有像ArrayList和Vector那样对内存进行复制,这样就提高了添加删除的执行效率,但是由于链表实现,需要进行寻址,在查找时不如ArrayList和Vector好样连续内存块效率高。

常用方法

添加新元素

public boolean add(E e) {
    linkLast(e);
    return true;
}
void linkLast(E e) {
    final Node<E> l = last;
    //以前驱为添加前最后一个节点,后继为null创建一个新节点
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;// 链尾节点设置为新节点
    if (l == null)// 如果原链尾节点为null,说明原链表为中无数据,新表头节点设置为新节点
        first = newNode;
    else
        l.next = newNode;
    size++;// 元素个数加1
    modCount++;
}

add方法将新元素链接到链表的末尾,下面是一个将新元素插入到指定位置的重载add方法:

public void add(int index, E element) {
    checkPositionIndex(index);// 检查index是否在有效位置
​
    if (index == size)
        linkLast(element);// 插入到链表尾
    else
        //先用node方法查找到index位置的节点,将新元素插入到此位置,此位置上的原节点做为新元素的后继节点
        linkBefore(element, node(index));
}
​
private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))// 指定插入位置无效,抛出异常
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
​
private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;// 在链表头和尾之间(包括头尾)算是有效位置
}
​
void linkBefore(E e, Node<E> succ) {
    final Node<E> pred = succ.prev;// index位置上节点的前驱节点
    // 创建新节点,新节点的前驱后继分别是,index位置的前驱和index位置上的节点
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

节点的删除

public E removeFirst() {
    final Node<E> f = first;
    if (f == null)// first为null,说明链表中无元素
        throw new NoSuchElementException();
    return unlinkFirst(f);// 将第一个节点从链表中移除
}
​
private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;// 第一个节点的后继节点
    // 将第一节点的内容和后继置null
    f.item = null;
    f.next = null; // help GC
    first = next;// 将表头设置后原第一节点的一后继节点
    if (next == null)// 如果后继为null,说明移除表头后,链表中已经元素了,将链尾置null
        last = null;
    else
        next.prev = null;//做为新的表头,其前驱应该为null
    size--;// 元素数减1
    modCount++;
    return element;
}

改变指定位置元素的值

public E set(int index, E element) {
    checkElementIndex(index);// 检查index是否合法
    Node<E> x = node(index);// 查找到index位置上的节点
    E oldVal = x.item;
    x.item = element;// 将节点上的元素设置为新元素
    return oldVal;
}

其它方法

    public boolean offer(E e)// 在链表尾添加新元素
    public boolean offerFirst(E e) // 在链表头添加新元素
    public boolean offerLast(E e)// 在链表尾添加新元素
​
    public E peek()// 获取表头元素,但不删除
    public E peekFirst()// 获取表头元素,但不删除
    public E peekLast()// 获取表尾元素,但不删除
​
    public E poll()// 获取表头元素,并删除之
    public E pollFirst()// 获取表头元素,并删除之
    public E pollLast()// 获取表尾元素,并删除之

总结

LinkedList是双向链表实现,其所有操作都是在双向链表的基本操作,总结下来其有如下特性

  • 实现了列表和双端队列几乎所有的接口,容量无限,只要内存够大。

  • 内部俱数据可以重复,可以为null

  • 不是线程安全的类

  • 效率上查询相较ArrayList慢,添加删除相对较快

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值