Loading

LinkedList集合底层原理

LinkedList集合特点及源码分析

LinkedList是List接口的实现类

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

继承了AbstractSequentialList类,实现了List、Deque双端队列、Cloneable克隆、Serializable序列化接口

特点:底层是双向链表、查询慢,增删快,线程不安全

LinkedList源码分析

Node是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;
    }
}

E :节点的值
Node next:指向上一个节点的引用
Node prev:指向下一个节点的引用

成员变量

transient int size = 0;//记录LinkedList的大小
transient Node<E> first;//头部节点
transient Node<E> last;//尾部节点

构造函数

1.无参构造
public LinkedList() {
}
2.集合类型构造函数
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);//传入size=0 
}

addAll方法是指定节点位置的后面插入集合

public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index);//检查元素是否合法

    Object[] a = c.toArray();//把集合转成数组
    int numNew = a.length;//获取数组大小
    if (numNew == 0)
        return false;

    Node<E> pred, succ;//创建两个节点pred、succ
    //如果是在元素尾部添加就会满足index == size,或者初始化数组的时候
    //第一种情况当构造方法创建的一个空的链表,首次添加的集合进来时候, pred、succ都为null
    //第二种情况当链表里面有节点,往尾部插入的时候,因为first指向前一个节点,last指向后一个节点,所以再添加一个节点时,succ指向后一个节点=null,pred指向前一个节点,也就是原来的尾部的节点last
    if (index == size) {
        succ = null;
        pred = last;
    } else {//情况三 这里是指定节点位置插入集合时才会满足这个条件,所以要先获取当前索引的节点
        succ = node(index);//根据节点索引获取当前节点succ
        pred = succ.prev;//获取当前节点的前一个节点pred
    }

    //遍历数据将数据插入
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;//类型转换
        Node<E> newNode = new Node<>(pred, e, null);//生成一个新节点  e为当前值,pred指向前一个节点
        //情况一是满足pred == null,创建了一个空链表
        if (pred == null)
            first = newNode;//newNode当作第一个节点,再把当前节点赋值给头节点
        else
            //否则就是在尾部添加一个节点, 把新节点作为前一个节点指向的下一个节点
            //或者在指定位置插入时,把当前节点作为前一个节点指向的下一个节点
            pred.next = newNode;
       //最后把newNode指向当前节点pred
       pred = newNode;
    }

//当构造方法创建的一个空的链表时候,succ为空,pred已经是当前节点newNode了,
//当链表里面有节点,往尾部插入的时候,最后一个节点就指向当前节点
    if (succ == null) {
        last = pred;
    } else { // 非空链表插入情况
        //在指定位置插入的时候,只需要变更当前节点的前后指向
        pred.next = succ;
        succ.prev = pred;
    }

    size += numNew;// 链表大小+num
    modCount++; // 修改次数加1
    return true;
}

查询方法

linkFirst(E e)方法
private void linkFirst(E e) {
    final Node<E> f = first;//获取首节点
    final Node<E> newNode = new Node<>(null, e, f);//生成头部节点 e为当前节点值  f为之前的首节点
    first = newNode;//因为是头部插入 那么赋值给头部
    if (f == null)//首节点为空 说明这里面是空的
        last = newNode;//新节点也是
    else//把之前首节点的上一个节点指向该节点
        f.prev = newNode;
    size++;//节点数量+1
    modCount++;//修改次数加1
}
linkLast(E e)方法
void linkLast(E e) {
    final Node<E> l = last;//获取尾部节点
    final Node<E> newNode = new Node<>(l, e, null);//新增一个尾部节点  l指向之前的尾部节点  e为当前节点 
    last = newNode;////因为是尾部插入 那么赋值给尾部
    if (l == null)//尾部节点为空 说明这里面是空的
        first = newNode;//把当前新增节点作为头部节点
    else////把之前尾部节点的下一个节点指向该节点
        l.next = newNode;
    size++;
    modCount++;
}
linkBefore(E e, Node succ) 方法

在某个节点钱面追加节点

void linkBefore(E e, Node<E> succ) {
    final Node<E> pred = succ.prev;//当前节点的上一个节点
    final Node<E> newNode = new Node<>(pred, e, succ);//生成新的节点
    succ.prev = newNode;//修改当前节点的上一个节点的指向为新节点
    if (pred == null)//接着判断一个succ的前一个节点是否为空,如果为空的话,需要把新节点设置为为头结点
        first = newNode;
    else//否则把前一个节点的下一个节点指向当前newNode
        pred.next = newNode;
    size++;
    modCount++;
}
node(int index)方法

对查找节点进行了优化

Node<E> node(int 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;
    }
}
getFirst()方法
public E getFirst() {
    final Node<E> f = first;//获取头部节点
    if (f == null)
        throw new NoSuchElementException();//没有这样的元素
    return f.item;
}
getLast()方法
public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

删除方法

unlinkFirst(Node f)方法

该方法是私有的

删除头部节点,并且修改下一个节点对头部的指向

private E unlinkFirst(Node<E> f) {
    final E element = f.item;//获取头部节点
    final Node<E> next = f.next;//获取头部节点的下一个节点
    f.item = null;//将头部节点置为null
    f.next = null;//头部节点的下一个节点置为null
    first = next;//将头部节点的下一个节点置为头部节点
    if (next == null)//头部节点的下一个节点为null 那么代表后面没有节点了
        last = null;//尾部节点为null
    else
        next.prev = null;//修改现在头部节点对上一个节点的指向
    size--;//数量-1
    modCount++;//操作+1
    return element;//返回当前头部节点
}
unlinkLast(Node l)方法

同理

private E unlinkLast(Node<E> l) {
    final E element = l.item;
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null; 
    last = prev;
    if (prev == null)
        first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
}
removeFirst()方法
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);//删除
}
removeLast()方法
public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);//同上
}
pop()方法
public E pop() {
    return removeFirst();//同上
}
poll()方法
public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);//同上
}

其他

push(E e)方法
public void push(E e) {
    addFirst(e);//同上
}

总结:

  • 插入、删除操作很快,只要修改前后指针就OK了,时间复杂度为O(1)
  • 访问比较慢,必须得从第一个元素开始遍历,时间复杂度为O(n)
posted @ 2021-10-30 17:22  炒焖煎糖板栗  阅读(132)  评论(0编辑  收藏  举报