LinkedList 源码分析

1. 前言

LinkedList 解析本文将分为如下几部分:

  1. 类结构
  2. 存储结构
  3. 常用方法及执行效率
  4. 线程安全
  5. ArrayList 优缺点
  6. 使用场景

注:本文大部分分析都围绕着源码,源码基于 JDK 1.8


2. 类结构

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

AbstractSequentialList:其内部的代码是用于减少重复性。相较于 AbstractList,此抽象类适用于没有随机访问性的 List
ListList 的标准规范。
Deque:此接口继承自 Queue,在其基础上扩展了双端队列的标准。
Cloneable:赋予了 LinkedList 对象拷贝功能
Serializable:赋予 LinkedList 序列化功能。


2. 存储结构

public class LinkedLise {
	transient Node<E> first;
	
	transient Node<E> last;

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

firstlast两个属性是用于存储数据,其类型为 Node

Node 的结构也很直白,大致如下图:
在这里插入图片描述

注意

  • 当Node作为根节点时,Node的 prevnull,因为根节点无前驱节点!
  • 当Node作为尾节点时,Node的 nextnull,因为尾节点无后继节点!

3. 常用方法及执行效率

3.1 无参构造

public LinkedList() {}

emm,啥也没有。。。无意水了一下🌚


3.2 add()

public boolean add(E e) {
	// 见下文 3.2.1
    linkLast(e);
    return true;
}

3.2.1 linkLast()

void linkLast(E e) {
	// 此行代码无实际意义
    final Node<E> l = last;
    
    /*
		新建节点,调用 Node 类型的全参构造。
		Node(Node<E> prev, E element, Node<E> next)
		new Node<>(l, e, null),将 last 作为新节点的前驱节点,e 是数据,后继节点为 null 前文已经介绍过了,此处不再叙述。
     */
    final Node<E> newNode = new Node<>(l, e, null);
    
    // 将 last 属性更新为新增节点(newNode)
    last = newNode;
    
    /*
    	需要注意的是,当 last 为 null 时,此时 last 中是一个节点都没有的。
    	也就直接证明了该节点为第一个节点,也是 LinkedList 根(root)节点。
    	反之,当 last 中存在节点时,将新增节点作为后继节点插入。
     */
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    
    // LinkedList 的元素个数 + 1
    size++;
    
    /*
		该变量主要是用于记录结构变化的次数(添加数据,删除数据,调整数组大小等)。
		其该变量作用体现在迭代时,比较前后的 modCount 是否变化。
		如果 modeCount 前后不一致,则抛出ConcurrentModificationException(并发修改异常)!
     */
    modCount++;
}

3.3 remove()

public boolean remove(Object o) {
	// 如果入参 o 为空,那么将会删除该 LinkedList 第一个为 null 的元素
    if (o == null) {
    	// 遍历所有元素
        for (Node<E> x = first; x != null; x = x.next) {
        	// 如果该元素等于 null,则 if 语句条件成立,执行 if 代码块
            if (x.item == null) {
            	// 详见下文 3.2.1 unlink() 解析
                unlink(x);
                return true;
            }
        }
    } else {
    	// 遍历所有元素
        for (Node<E> x = first; x != null; x = x.next) {
        	// 使用 equals 判断两者相等性,如果相等则删除
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

时间复杂度 O(n)


3.2.1 unlink();

E unlink(Node<E> x) {
	// 取出被删除节点的值
    final E element = x.item;
    // 被删除节点的下一个元素
    final Node<E> next = x.next;
    // 被删除节点的上一个元素
    final Node<E> prev = x.prev;

	// 因为根节点没有前驱,所以当删除的节点为根节点时,if 语句成立,执行代码块
	// 反之,删除的不是根(首)节点
    if (prev == null) {
    	// 更新根(首)节点,将根节点替换为删除元素的下一个元素
        first = next;
    } else {
    	// x.prev.next 改变引用(不再指向被删除元素)
        prev.next = next;
        // 将元素指向置空
        x.prev = null;
    }

	// 如果删除的是尾节点,则尾节点没有后继,if 条件成立,执行 if 代码块
	// 反之,删除的不是尾节点
    if (next == null) {
    	// 更新尾节点,将尾节点替换为删除元素的上一个元素
        last = prev;
    } else {
	    // x.next.prev 改变引用(不再指向被删除元素)
        next.prev = prev;
        // 将元素指向置空
        x.next = null;
    }

	// 将元素值置空
    x.item = null;
    
    // LinkedList 元素个数 - 1
    size--;
    
    // 上文讲过,可以去看看
    modCount++;
    
    // 返回元素值
    return element;
}

3.4 set()

public E set(int index, E element) {
	// 详见下文 3.4.1 checkElementIndex()
    checkElementIndex(index);
    
    // 详见下文 3.4.2 node()
    Node<E> x = node(index);
	
	// 取出旧值保存,用作返回值
    E oldVal = x.item;
    
    // 替换节点值
    x.item = element;
    
    return oldVal;
}

3.4.1 checkElementIndex()

private void checkElementIndex(int index) {
	// 如果给定索引无效,则抛出索引越界异常
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
3.4.1.1 isElementIndex()
private boolean isElementIndex(int index) {
	// 判断给定索引是否有效
    return index >= 0 && index < size;
}

3.4.2 node()

// 获取指定节点(元素)
Node<E> node(int index) {
	// 采用了对半查找
	// 如果 index 小于长度的一半(size >> 1),那么 if 条件成立,执行 if 代码块
	// 反之
    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;
    }
}

3.5 get()

public E get(int index) {
	// 详见 3.4.1 checkElementIndex()
    checkElementIndex(index);
    
	// 详见 3.4.2 node()
    return node(index).item;
}

3.5 indexOf()

简单明了,此方法不解析

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

3.6 其它方法

由于 LinkedList 实现了 Deque 接口,所以 LinkedList 支持双端队列操作。
具体的操作方法可以查阅 LinkedList 源码。


4. 线程安全

在上述方法中,并没有见到如 synchronizedReentrantLock 等来维护线程安全的操作,也就直接证明了该方法是非线程安全的。


5. LinkedList 优缺点

优点:

  • 适用于高效的增删操作
  • 因实现了双端队列,具有更高灵活性

缺点:

  • 因为存储结构影响,获取指定元素的时间复杂度为O(n)
  • 没有随机访问特性

6. 使用场景

  1. 需要经常增删
  2. 元素访问不频繁
  3. 对元素顺序有要求
  4. 有双端队列特性的需求

至此,文章到此就结束了。
博主水平有限,博文有错误的地方可以私信或评论指出。
Bye.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值