LinkedList源码阅读,构造函数,add方法,get方法等

本文详细解析了Java LinkedList的源码,包括无参构造函数、add方法、get方法、remove方法和set方法的工作原理。通过分析,展示了LinkedList作为双向链表在插入和删除操作上的优势,以及在随机访问上的不足。同时,文章强调了链表和数组在特性上的对比,帮助读者更好地理解LinkedList的内部实现。
摘要由CSDN通过智能技术生成

后续我会陆陆续续更新java源码的一些阅读。大家如果觉得对自己有用就点个关注吧。
文中描述如有问题,欢迎留言指证。

引言

本文中的例子我尽量写的简单,避免一些我平时查资料时一些例子中出现的大量无用代码,产生让人阅读不下去的感觉

LinkedList

注意:如果有小伙伴不知道链表是啥,请自行百度一下,再看这篇文章,毕竟这篇文章主要是带大家进行源码阅读.
描述:其实LinkList就是链表.(外带一句ArrayList本质就是数组),其实链表也有很多种,单向链表,双向链表,单向循环链表,双向循环链表,等等,LinkedList就是一个双向链表!

链表的特点

  1. 链表随机访问很慢,因为每次访问一个变量都需要循环,从一个元素开始比遍历
  2. 插入和删除很容易,因为链表不涉及到移位,只需要修改节点指针的指向

数组的特点:

  1. 数组存储在内存中是连续的.
  2. 由于是连续的所以插入和删除效率就会比较慢(相对链表等)因为插入一个数值,后面的元素都要依次往后移,删除的话后面的元素都会依次往前移.
  3. 随意访问数据很快,因为数组是连续的,知道每一个数据的内存地址,可以直接找到给地址的数据。

类的参数

我将只展现类的属性,将所有没必要的方法,全部删除,这样也可以更直观

LinkedList展示

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
    //链表的长度,你如果调用Size()方法,他就把这个参数返给你
	transient int size = 0;
	
	//链表的头结点
    transient Node<E> first;

	//链表的尾节点
    transient Node<E> last;

	//无参构造函数,啥都没做
    public LinkedList() {
    }
	//这个有参构造函数也只是创建完对象,调用addAll方法没什么好说的
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
}

Node节点类展示

标准双向链表节点的数据结构,这你要看着一点没有印象,赶紧去看链表去.

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 LinkedList() {
    }

对你看的没错就是一个空方法,这就意味着当我们new LinkedList(),只是创建了一个对象没有其他操作,并且其他有参构造方法先不看,因为无非就相当于创建了这个对象然后调用一个这个对象的方法(不要有疑问,我连他的父类都看了,全是空方法)这个我们就可以不看了哈哈哈!!!

add方法

第一部分代码

    public boolean add(E e) {
        linkLast(e);
        return true;
    }

这一部分没什么,继续进入方法

第二部分代码

void linkLast(E e) {
		//获取当前尾节点,如果添加的是第一个元素l拿到的就是null
        final Node<E> l = last;
        //将添加元素e打包成节点对象,这个节点对象头指针,
        //指向尾节点,尾指针指向null
        final Node<E> newNode = new Node<>(l, e, null);
        //将新添加的节点设置成尾节点,因为链表添加都是在尾节点的
        //后面再加一个节点,这样新加的节点就成为了,新的尾结点
        last = newNode;
        //判断再添加新元素之前尾结点是不是空,如果是空将头结点也设置成新节
        //这个时候链表中头结点和尾节点都是同一个节点
        if (l == null)
            first = newNode;
        //如果不是空则建原来的尾节点的尾指针,指向新节点
        else
            l.next = newNode;
        //链表长度加1
        size++;
        //对链表的操作次数加1
        modCount++;
    }

其实注释写了这么多.如果你会链表你会接受的很透彻,标准的双向链表的增操作,就是将新加入的元素连接到链表尾部.在这里插入图片描述
注意重点
你发没发现这里面不涉及扩容的操作,它可以无限的在尾结点添加元素,这也是插入操作为什么LinkedList比ArrayList快的原因之一(啥呀!你不懂ArrayList 原理那么请看我的另一篇文章ArrayList的构造函数和Add方法)

get方法

第一部分代码

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

依然没什么可以看的,我们来进入方法

第二部分代码

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

这个方法只是校验你获取的元素下标是否越界,比如你list.get(-1)或者list.get(10000000) 这样就会报错,我们继续看

第三部分代码

Node<E> node(int index) {
		//size >> 1 二进制运算右移以为,就相当于10进制中
		//除以2取整型的结果,注意不带小数,是取整型,1/2 = 0的那种
		//判断要获取元素的下标,在前半部分还是后半部分
        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;
        }
    }

这块你仔细看也不是很难看懂.有点像极简版的二分查找.哈哈(可能不准确,但是有点那味),意思就是如果你想获取的元素在链表长度的后半部分,就从后往前遍历,如果在前半部分就从前往后遍历,这样可以减少遍历次数.
又一个重点
LinkedList(链表)想获取元素需要遍历,而ArrayList不需要,所以对于数据的访问,ArrayList的性能更好

remove(int a)方法

第一部分代码

    public E remove(int index) {
    	//校验下标是否越界的,如果越界报错
        checkElementIndex(index);
        return unlink(node(index));
    }

没啥说的,进入第二部分

第二部分代码

注意:这里面有一个node(index)方法,我们在get方法第三部分代码讲过了,他的功能就是获取到指定下标的节点

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;
            //细节:这块他把需要删除元素的尾指针致null了
            x.prev = null;
        }
		
		//判断是不是尾指针,如果是直接将链表中的尾节点替换成
		//删除节点的上一个节点
        if (next == null) {
            last = prev;
        } else {
        	//如果并不是将删除节点的下一个节点头指针指向删除节点的
        	//下一个节点,就完成了操作
            next.prev = prev;
            x.next = null;
        }
		//将删除节点的各个属性彻底置空
        x.item = null;
        //长度减1
        size--;
        modCount++;
        //返回已删除的节点
        return element;
    }

这块操作我们直接看图,可能更透彻一点.
在这里插入图片描述

set方法

第一部分代码

    public E set(int index, E element) {
    	//校验下标是否越界
        checkElementIndex(index);
        //获取需要修改的节点
        Node<E> x = node(index);
        E oldVal = x.item;
        //将新节点替换进去
        x.item = element;
      	//返回旧节点
        return oldVal;
    }

node(int index)这个方法我们在get方法第三部分代码讲过了,然后更新节点内容就行,不用对指针指向进行操作

总结:阅读这些代码的关键就是你需要了解链表的增删改查,之后,这些代码就好理解多了,我也是越写越纳闷,这篇文章没有讲链表增删改查,但是你要是会了增删改查,可能也就没必要读这篇文章了.我这写的劲劲的,到底是写给谁看的.擦!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值