LinkedList源码剖析

1、实现原理、内部组成

我们知道,ArrayList内部是数组,元素在内存中是连续存放的,但LinkedList不是。

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

LinkedList内部组成就是如下三个实例变量:

  //LinkedList的所有public方法内部操作都是这三个实例变量
    transient int size = 0;  //表示链表长度,默认为0

    transient Node<E> first; //指向头节点

    transient Node<E> last;//指向尾结点

LinkedList的所有public方法操作都是这三个实例变量

2、add方法

 public boolean add(E e) {
        linkLast(e);//在链表的尾部添加元素
        return true;
    }

void linkLast(E e) {
        final Node<E> l = last;//l指向原来的尾部节点
        final Node<E> newNode = new Node<>(l, e, null);//创建一个新的节点
        last = newNode;//修改尾部节点,指向新的最后节点newNode
        if (l == null) //修改前节点的后向连接,如果原来链表为空,则让头节点指向新节点
            first = newNode;
        else
            l.next = newNode;//否则让前一个节点的next指向新节点
        size++;//增加链表大小
        modCount++;//modCount的目的与ArrayList是一样的,记录修改次数,便于迭代中间检测结构性变化
    }


可以看出,与ArrayList不同,LinkedList的内存是按需分配,不需要预先分配多余内存,添加元素只需要分配新元素的空间,然后调节几个连接即可

3、get方法

 //根据索引访问元素get(index)
    public E get(int index) {
        checkElementIndex(index);//检查索引位置的有效性,如果无效,则抛出异常
        return node(index).item;//如果index有效,则调用node方法查找对应的节点,其
    }


 Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) { //size>>1等于size/2,如果索引位置在前半部分(index<(size>>1)),则从头节点开始查找,否则从尾及诶按开始查找。
            Node<E> x = first;
            for (int i = 0; i < index; i++)//for循环,复杂度为O(n)
                x = x.next;
            return x;
        } else {
            Node<E> x = last; //可以看出,与ArrayList明显不同,ArrayList中数组元素可以连续存放,可以根据索引直接定位,而在LinkedList中,则必须从头或尾顺着连接查找,效率较低
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

4、indexOf方法

代码很简单,从头节点顺着连接往后找,如果要找的是null,则找到第一个item为null的节点,否则使用equals方法进行比较

 public intf indexOf(Object o) {
        int index = 0;
        if (o == null) {//如果要找的是null.则找到第一个Item为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;
    }

5、node(index)

 Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) { //size>>1等于size/2,如果索引位置在前半部分(index<(size>>1)),则从头节点开始查找,否则从尾及诶按开始查找。
            Node<E> x = first;
            for (int i = 0; i < index; i++)//for循环,复杂度为O(n)
                x = x.next;
            return x;
        } else {
            Node<E> x = last; //可以看出,与ArrayList明显不同,ArrayList中数组元素可以连续存放,可以根据索引直接定位,而在LinkedList中,则必须从头或尾顺着连接查找,效率较低
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

5、在中间插入元素

public void add(int index, E element) {
        checkPositionIndex(index);//

        if (index == size)//若index为size,添加到最后面
            linkLast(element);
        else     //一般情况,插入到Index对应节点的前面
            linkBefore(element, node(index));//node(index)找到该节点,遍历
    }


 void linkLast(E e) {
        final Node<E> l = last;//l指向原来的尾部节点
        final Node<E> newNode = new Node<>(l, e, null);//创建一个新的节点
        last = newNode;//修改尾部节点,指向新的最后节点newNode
        if (l == null) //修改前节点的后向连接,如果原来链表为空,则让头节点指向新节点
            first = newNode;
        else
            l.next = newNode;//否则让前一个节点的next指向新节点
        size++;//增加链表大小
        modCount++;//modCount的目的与ArrayList是一样的,记录修改次数,便于迭代中间检测结构性变化
    }

void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev; //succ 表示后继节点,变量pred表示前驱节点
                                        //目的是在pred和success中间插入一个节点
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;//后继的前驱节点指向新节点
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;//增加长度
        modCount++;//增加修改长度
    }




7、删除元素remove

删除x节点,基本思想就是让x的前驱和后继直接连接起来,next是x的后继,prev是x的前驱

 public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));//node(index)查找Index索引位置处的节点

    }

   //删除x节点,基本思想就是让x的前驱和后继直接连接起来,next是x的后继,prev是x的前驱
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item; //待返回节点的值
        final Node<E> next = x.next;//x的后继节点
        final Node<E> prev = x.prev;//x的前驱节点

        //让x的前驱的后继指向x的后继
        //如果x没有前驱,说明删除的是头结点,则修改头街点指向x的后继
        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--;
        modCount++;//修改次数+1
        return element;
    }

8、LinkedList特点分析

用法上,LinkedList是一个List,但也实现了Deque接口,可以作为队列双端对哦咧使用。

实现原理上,LinkedList内部是一个双向链表,并维护了长度,头结点,尾结点,这决定了他有如下特点:

  • 按需要分配空间,不需要预先分配很多空间
  • 不可以随机访问,按照索引位置访问效率比较低,必须葱油或尾顺着链表查找,效率为O(N/2)
  • 不管列表是否已经排序,只要按照内容查找元素,效率都比较低,必须逐个比较,效率为O(N)
  • 在两端添加、删除元素的效率很高,为O(1)
  • 在中间插入、删除元素,需要先定位,效率比较低,为O(N)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值