LinkedList源码解析

LinkedList集合是平时工作中常用的集合之一,它的增删操作效率高,但是相对它的查询效率就不如ArrayList集合了。对于想要了解ArrayList集合的朋友可以看我另外一篇博客ArrayList源码解析,本文只针对LinkedList集合的源码讲解分析。

LinkedList的基本信息属性

// 列表中的元素个数
transient int size = 0;

// 列表中第一个元素
transient Node<E> first;

// 列表中最后一个元素
transient Node<E> last;

// 记录列表内部结构改变次数
protected transient int modCount = 0;

Node内部类

private static class Node<E> {
    E item; // 结点中的元素
    // 以下两个属性说明,LinkedList的内部结构属于双向链表
    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类方法

对于LinkedList类中的方法,如果你熟悉链表这种数据结构的话,那么以下方法的源码对于你来说肯定十分简单。但是如果看完了以下方法的源码分析,还是觉得不能理解的话,那么我建议你接下来去了解下数据结构中的链表,之后再来看这篇博客。到那个时候相信这些代码对你来说不足为惧了。

  • add——添加结点
public boolean add(E e) {
    linkLast(e); // 往列表末尾添加结点
    return true;
}

void linkLast(E e) {
    final Node<E> l = last; // 获取末尾结点
    final Node<E> newNode = new Node<>(l, e, null); // 创建结点,其内部元素为e,前任结点为last
    last = newNode; // 将新添加的结点作为last末尾结点
    if (l == null) // 若旧的末尾节点为空,说明此次添加的结点为列表的第一个结点
        first = newNode; // 因此first结点也是新添加的节点
    else
        l.next = newNode; // 否则,旧的末尾节点的后继结点为新添加的结点
    size++;
    modCount++;
}
  • add——在指定索引位置插入结点
public void add(int index, E element) {
    checkPositionIndex(index); // 检查索引的合法性
    if (index == size) // 指定索引等于size,说明在末尾添加节点
        linkLast(element); // 在列表末尾添加结点,详情看上个方法讲解
    else
        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; // 指定索引大于0,而且小于列表结点数量,说明合法;否则非法
}

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

void linkBefore(E e, Node<E> succ) {
    final Node<E> pred = succ.prev; // 获取插入指定位置结点的前任结点
    final Node<E> newNode = new Node<>(pred, e, succ); // 创建新结点,其内部元素为e、前任结点为插入指定位置结点点的前任结点、后继结点为插入指定位置结点
    succ.prev = newNode; // 插入指定位置结点的前任结点指向新加入的结点
    if (pred == null) // 如果插入指定位置的节点的前任结点为空,说明插入位置为头结点
        first = newNode; // 新加入的节点为头结点
    else
        pred.next = newNode; // 否则,插入指定位置结点的前任结点的后继结点指向新加入的节点
    size++;
    modCount++;
}
  • addAll——将指定集合中的全部元素添加进列表
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c); // 在末尾添加结点
}

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;
    if (index == size) { // 指定索引等于size,意味着在列表末尾添加结点
        succ = null;
        pred = last;
    } else { // 否则,在指定位置添加结点
        succ = node(index); 
        pred = succ.prev;
    }

    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null); // 创建结点,其内部元素为e,前任结点为pred,后继结点为空
        if (pred == null) // pred结点为空,说明列表为空,而newNode为第一个加入的结点
            first = newNode; // 指定first结点
        else // 否则,继续往插入位置添加结点
            pred.next = newNode; 
        pred = newNode;
    }

    if (succ == null) { // succ为空,说明此次添加是在列表末尾添加结点,则重新指定末尾结点
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;
    }

    size += numNew;
    modCount++;
    return true;
}
  • addFirst——添加头结点
public void addFirst(E e) {
    linkFirst(e);
}

private void linkFirst(E e) {
    final Node<E> f = first; 
    final Node<E> newNode = new Node<>(null, e, f); // 创建节点,其内部元素为e,前任结点为空,后继结点为头结点
    first = newNode; // 指定新结点为头结点
    if (f == null) // 头结点为空,说明列表为空,新节点为第一个插入的节点
        last = newNode; // 同时指定为末尾节点
    else
        f.prev = newNode;
    size++;
    modCount++;
}
  • addLast——添加末尾节点
public void addLast(E e) {
    linkLast(e);
}

void linkLast(E e) {
    final Node<E> l = last; 
    final Node<E> newNode = new Node<>(l, e, null); // 创建新结点,其内部元素为e,前任结点为末尾节点,后继结点为空
    last = newNode;
    if (l == null) // 末尾结点为空,说明列表为空,新节点为第一个插入的节点
        first = newNode; // 同时指定为末尾节点
    else
        l.next = newNode;
    size++;
    modCount++;
}
  • contains——列表是否包含指定元素
public boolean contains(Object o) {
    return indexOf(o) != -1;
}

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 { // 从头往后寻找,找到第一个与指定元素equals比较相等的元素,返回其所在位置的索引
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1; // 找不到,则返回-1
}
  • remove——删除头结点
public E remove() {
    return removeFirst();
}

public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null; // 头结点内部元素置空
    f.next = null; // 头结点的前任结点置空
    first = next; // 原第二结点作为头结点
    if (next == null) // 原第二结点为空,说明列表只有一个头结点
        last = null; // 移除后头结点后,列表为空,末尾节点指定为空
    else
        next.prev = null; // 否则,原第二结点的前任结点指定为空,与原头结点断绝关系
    size--;
    modCount++;
    return element;
}

到此,LinkedList的源码解析就结束了。当然,LInkedList类中的方法不止如此,但是其他方法说到底也就是结点指针的变换,所以继续讲解其他方法也是炒冷饭而已,意义不大。而以上内容如果你都明白了,那么剩下的方法,直接去看源码也一定能够明白。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值