源码分析:逐行分析LinkedList

概述:底层数据结构是链表 双向链表 非同步的
LinkedList是一个基于链表实现的双向队列 所以链表也是双向链表

特性:查改慢增删快、非线程安全、效率高

注意:

  • 除了基础的List接口的功能外 在LinkedList中还维护了 头尾两个对象 可以直接对头尾进行操作
    而LinkedList也继承了队列的接口 头尾对象是根据队列的接口维护的 队列接口中还提供了对头尾进行操作的方法
    除了头尾操作外push、pop、poll....等方法也都是有实现 不过这里是作为List接口下的LinkedList来介绍 关于队列的用法 后面可能会补充吧
  • 由于是双向链表所以 LinkedList 不能随机访问 因为链表结构决定LinkedList只能通过节点的信息去循环查找(没有索引所以不能直接找到节点)

优势:
增删比较快 时间复杂度是O(1)
因为不管是头、尾、中间那个地方操作 都影响的节点最多就是前后两个节点
不过需要注意的是 LinkedList 在删除前需要找到需要删除的对象 在指定位置添加前也需要找到指定位置的元素
而查询的过程需要循环查找 所以并不是说LinkedList的增删就一定快

缺点:
查改比较慢 时间复杂度是O(n)
LinkedList不能随机访问 因为他没有索引 不能根据索引直接找到数据 必须通过循环查找

画图解析:

LinkedList结构:

堆中的结构:

源码分析:

构造函数;

// LinkedList的两个构造函数没什么好说的 比较简单
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
    this();// 调用无参构造
    addAll(c);// 把传进来的collection参数添加进链表对象
}

// 构造函数没啥好说的  说说LinkedList的属性
// 链表的逻辑长度(跟数组不同逻辑长度就是实际长度)
transient int size = 0;
// 头节点对象
transient Node<E> first;
// 尾节点对象
transient Node<E> last;
// 这个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;
    }
}

添加方法:

// 直接添加 相当于尾插入
public boolean add(E e) {
    linkLast(e);// 直接调了尾插入
    return true;
}
// 头插入
public void addFirst(E e) {
    linkFirst(e);// 直接调了头插入
}
// 尾插入
public void addLast(E e) {
    linkLast(e);// 直接调了尾插入
}
// 指定位置插入 并不是说是中间插入 
// 因为如果指定的位置是最后一个 实际就相当于是尾插入
// 不过不能进行头插入 会抛出异常:IndexOutOfBoundsException
public void add(int index, E element) {
    checkPositionIndex(index);// 指定的位置 必须大于零 小于链表的size
    //index >= 0 && index <= size
    if (index == size)// 判断指定位置和逻辑长度
        linkLast(element);// 相等直接调用尾插入
    else
        linkBefore(element, node(index));// 不相等 说明为中间插入
}
// 根据指定位置取节点对象
Node<E> node(int index) {// LinkedList中取元素 除了直接取头尾节点 都是通过这个方法循环取指定位置的节点
    // 性能相对ArrayList的直接根据索引取来说 是比较慢的
    if (index < (size >> 1)) {
        Node<E> x = first;// 先赋头节点的值
        for (int i = 0; i < index; i++)// 循环取到index位置的节点对象
            x = x.next;
        return x;
    } else {// 逻辑同上
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

void linkLast(E e) {
    final Node<E> l = last;// 先记录原来的尾节点
    final Node<E> newNode = new Node<>(l, e, null);// 把当前传进来的元素包装成节点
    last = newNode;// 把当前节点 设置为新的尾节点
    if (l == null)// 原来的尾节点不存在
        first = newNode;// 把头节点也指向当前节点
    else// 原来的尾节点存在
        l.next = newNode;// 把原尾节点的下一节点引用设置为新尾节点
    size++;// 逻辑长度加1
    modCount++;// 记录修改次数
}
private void linkFirst(E e) {// 头插入的逻辑与尾插入的逻辑 基本一直
    final Node<E> f = first; 
    final Node<E> newNode = new Node<>(null, e, f); 
    first = newNode;
    if (f == null)
        last = newNode; 
    else
        f.prev = newNode;
    size++;
    modCount++;
}
// 中间插入
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++;
}

删除方法:

// 删除指定位置
public E remove(int index) {
    checkElementIndex(index);// 校验index 保证是正确数据 错误数据抛出异常:IndexOutOfBoundsException
    // 校验规则:index >= 0 && index < size
    return unlink(node(index));// 校验通过 直接调用nulink(node方法在上面说了 不再重复)
}
// 删除指定对象 需要equals方法支持
public boolean remove(Object o) {// 参数o是需要删除的对象
    if (o == null) {// 如果需要删除的对象是null
        for (Node<E> x = first; x != null; x = x.next) {// 从头节点开始 取下一节点 一直取到下一节点是null
            if (x.item == null) {// 当前节点元素为null 直接调用unlink移除元素
                unlink(x);
                return true;
            }
        }
    } else {// 逻辑基本同上 唯一不同是判断对象相同的手段 这里用的是equals方法
        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) {// 参数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 = next;// 直接将LinkedList中的头节点对象设置为 需要移除节点的下一节点
    } else {// 如果不是头节点
        prev.next = next;// 设置上一节点的下一节点为需要移除节点的下一节点
        x.prev = null;// 置空需要移除节点的上一节点
    }

    //处理next的逻辑同上面的prev 不再重复
    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;// 最后置空需要移除节点的元素本身 (到此为止 链表中已经不存在对item的引用 GC会在何时的时候处理掉item)
    size--;// 逻辑长度更新
    modCount++;// 操作记录加一
    return element;// 返回之前保存的item的引用
}

查改方法:

// 查改放在一起是因为逻辑比较简单 这里简单提下
// 取指定位置的元素
public E get(int index) {
    checkElementIndex(index);// 这个校验在删除的时候提过 不在重复
    return node(index).item;// 通过node方法取到 节点对象 返回节点中的元素
}
public E getFirst() {// 直接取头节点
    final Node<E> f = first;
    if (f == null)// 头节点为空抛出异常
        throw new NoSuchElementException();
    return f.item;
}
public E getFirst() {// 直接取尾节点
    final Node<E> f = first;
    if (f == null)// 尾节点为空抛出异常
        throw new NoSuchElementException();
    return f.item;
}

// 修改方法 修改指定位置的元素
public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}

总结一下:
LinkedList就是一个实现了双向链表双向对列结构的容器
并对这个双向链表结构的数据实现了:插入、删除、修改、查询、序列化等一系列操作的包装

posted @ 2021-08-05 15:22  熏晴微穗  阅读(45)  评论(1编辑  收藏  举报