《一步一步看源码:LinkedList》容器源码系列之二

本文详细介绍了LinkedList数据结构,包括其底层实现的双向链表以及add、get、remove等核心方法的工作原理。通过实例解析了节点的插入、查找和删除过程,展示了LinkedList在数据操作中的具体应用。
摘要由CSDN通过智能技术生成

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;    //前置结点指针
        }
    }

单向链表:只有next,作为后置结点指针,指向下个结点。

双向链表:next和prev都有,一个指针指向后置结点,另一个指向前置结点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cmhPqOw0-1624426661183)(容器源码/链表数据结构.png)]

Add方法:
  • 将传入元素传递给linkLast方法:
    public boolean add(E e) {
        linkLast(e);
        return true; //插入无需判断越界,所以直接返回true
    }
  • 进入linkLast方法:
    • node是什么,前面刚开始介绍了。
    • last也是一个Node类,初始值为空,表示上个结点:transient Node last;
    • fist也是一个Node类,初始值为空,表示第一个结点:transient Node first;
    void linkLast(E e) {
        final Node<E> l = last; //l表示上个结点,如果为第一个则为null
        final Node<E> newNode = new Node<>(l, e, null); 
        //传入一个新的结点后,前置结点就是链表以前的最后一个结点,后置结点为空
        last = newNode; //最后一个结点变为新的结点
        if (l == null) //如果l为空说明现在传入的是链表的第一个结点
            first = newNode; //第一个结点变为新传入的结点
        else  //否则上个结点的下置结点变为当前传入的新节点
            l.next = newNode;
        size++;  //大小+1
        modCount++;   //统计进行了多少次add和remove
    }

​第一个元素插入:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RoJeP5ny-1624426661185)(容器源码/image-20210620114419054.png)]

往后的元素插入:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Mejyw3I-1624426661186)(容器源码/image-20210620114902616.png)]

Get方法
  • 进入方法内部,主要先判断长度有没有越界,没有就取值出来。
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}
  • 进入checkElementIndex方法内部:判断传入的index是否超过固有长度。
    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

  • 再进入isElementIndex:发现是拿add方法中的size和index。
   private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
Remove方法:
  • 进入方法内部,和get方法一样先判断是否越界,这里不再赘述。
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));//传入索引查出结点,而后把结点放入unlink方法中
    }
  • 进入node方法,查看取出结点过程。
    • index < (size >> 1):判断索引是大于还是小于长度的一半。
      • 大于就从头遍历,找到目标结点的上一个结点的后置节点指针,根据指针得到对应结点。
      • 小于就从尾遍历,找到目标结点的下一个结点的前置节点指针,根据指针得到对应结点。
    Node<E> node(int index) {
        // assert isElementIndex(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;
        }
    }
  • 进入unlink方法:有点多,但是不要怕,看注释,一步步都写好了。
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
		//刚开始把传入的结点全部取出
        if (prev == null) { //如果前置结点指针为空,说明这个是第一个结点
            first = next;  //把首结点标记给到下一个结点
        } else {			//否则, 说明不是首结点
            prev.next = next; //把前置结点的后置指针指向删除的结点的下个结点
            x.prev = null; //把要删除结点的前置指针置为空
        }

        if (next == null) { //如果要删除的后置结点指针为空,说明最后一个结点
            last = prev; //把尾结点的标记给到下一个结点
        } else { //否则,说明不是尾结点
            next.prev = prev;//把后置结点的前置指针指向要删除的结点的上个结点
            x.next = null;//把要删除结点的后置指针置为空
        }
        x.item = null; //把要删除结点的值置为空
        size--; //长度减少1
        modCount++; //操作次数+1,remove和add都会增加modCount的次数
        return element; //返回这个结点的值,以置空
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值