Java集合——LinkList源码

一、LinkedList数据结构
LinkdList底层是由双向链表构成的,并且头结点不存放数据。
LinkedList继承了AbstractSequentialList抽象类,由AbstractSequentialList继承了Abstract类。LinkedL实现了Deque接口,Deque接口继承了Queue接口。

Node结点
因为是双向链表,每个结点都有前驱结点和后继结点。Node结点一共有三个属性:item代表结点值,prev代表结点的前一个结点,next代表结点的后一个结点。结点的定义如下:

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

在LinkedL中定义了两个变量分别来指向链表的第一个结点和最后一个结点

transient Node<E> first;//第一个结点
transient Node<E> last;//最后一个结点

存储链表的长度

transient int size = 0;

二、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);//建立结点对象,前一个结点为原链表的最后一个结点,后一个结点为null,中间item是要添加的结点
        last = newNode;
        if (l == null)//最后一个结点为null,表示链表为空,添加的结点则是链表中的第一个结点
            first = newNode;//让原链表的第一个结点为新添加的链表
        else
            l.next = newNode;//链表不为空,则让原链表的最后一个结点指向新添加的结点
        size++;//链表长度自增
        modCount++;
    }

总结:
LinkeList中的添加操作:每添加一个对象都会被放到新建的Node对象中。add()操作,首先通过l==null来判断原链表中是否有结点,若没有把新建的结点放入原链表中的第一个结点,否则让原链表最后一个结点的下一个指向新建的结点,链表长度自增。

三、addAll()操作

 public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);//检查Index是否越界,越界抛出异常
        Object[] a = c.toArray();//把集合对象转为数组
        int numNew = a.length;//统计插入结点的个数
        if (numNew == 0)//插入结点的个数为0,即每插入元素
            return false;

        Node<E> pred, succ;//声明pred和succ两个Node对象,表示要插入结点的前一个结点和后一个结点
        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);//new新的结点对象
            if (pred == null)//插入位置的前一个结点为null,表示插入的结点是第一个结点
                first = newNode;//将插入的结点赋给第一个结点
            else
                pred.next = newNode;//否则将新结点赋给插入位置前一个结点的下一个结点
            pred = newNode;
        }

        if (succ == null) {//在结尾添加
            last = pred;//将最后一个元素赋给对象pred
        } else {
            pred.next = succ;//将集合元素的最后一个节点对象的next指针指向原index位置上的Node对象
            succ.prev = pred;//将原index位置上的pred指针对象指向集合的最后一个对象
        }

        size += numNew;//链表长度增加
        modCount++;
        return true;
 }

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

        if (index < (size >> 1)) {//判断index是否小于size的一半,如果少于一半,从第一个结点开始遍历
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;//从第一个结点开始,依次将后一个结点赋值给x
            return x;
        } else {//否则,从最后一个结点开始遍历
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;//从最后一个结点开始,依次将前一个结点赋值给x
            return x;
        }
    }
 private void checkPositionIndex(int index) {//检查index是否越界
        if (!isPositionIndex(index))//越界,抛出异常
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
 }
private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
 }

总结:
添加指定集合中的所有元素。通过插入位置的前一个结点和后一个结点指针的指向来进行插入,不需要移动插入位置后面的元素。因为插入的位置不同,所进行的操作不同,所以必须对插入位置进行判断,然后再进行相应操作

四、删除操作
(1)remove()操作:移出此列表的第一个元素

 public E remove() {
        return removeFirst();
 }
 public E removeFirst() {
        final Node<E> f = first;//声明f对象,指向第一个元素
        if (f == null)//如果第一个元素weinull,表示链表为空,抛出异常
            throw new NoSuchElementException();
        return unlinkFirst(f);
 }
private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;//获得第一个结点值
        final Node<E> next = f.next;//第一个结点的下一个结点
        f.item = null;//让第一个结点值为null,即删除第一个结点
        f.next = null; // help GC
        first = next;//使指向第一个结点的指针指向第一个结点的下一个结点即作为删除后的新的第一个结点
        if (next == null)//如果下一个结点为空,即链表中只有一个结点
            last = null;
        else
            next.prev = null;//下一个结点的前一个指针为null
        size--;
        modCount++;
        return element;
    }

(2)remove(int index)操作:删除指定位置的元素

public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
 }
 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--;
        modCount++;
        return element;
    }

总结:
LinkedList的删除操作主要是判断删除元素的位置。利用删除结点的前一个结点和删除结点的后一个结点删除要删除的结点。如果要删除的结点是第一个是第一个元素,是一种操作,要删除的结点是最后一个元素,是另一种操作,要删除的结点是中间的元素,又是一种操作。通过判断要删除元素的位置,通过改变删除元素前一个结点和后一个结点的指向进行相应的删除操作。

五、修该操作set(int index,E element)

public E set(int index, E element) {
        checkElementIndex(index);//检查索引是否越界,越界抛出异常
        Node<E> x = node(index);//获取index位置上的结点对象
        E oldVal = x.item;
        x.item = element;//将行的元素赋值给index位置上的元素
        return oldVal;
 }

总结:
LinkedList中的数据保存在结点对象的item属性中

六、查询操作 get(int index)

public E get(int index) {
        checkElementIndex(index);检查索引是否越界,越界抛出异常
        return node(index).item;//返回index位置上的结点对象的item属性的值
}

总结:
LinkedList的get()方法通过Node()方法,判断index是在链表的前半部分还是后半部分,快速遍历来获取index位置上的元素值

LinkedList排列有序,可重复,底层使用双向链表数据结构,查询速度慢,增删操作块,add()和remove()方法块,线程不安全。

LinkedList和ArrayList的区别:
①ArrayList底层结构是动态数组,LinkedList底层是双向链表;
②当随机访问时(get()和set()操作),ArrayList效率比LinkedList效率高,LinkedList是线性存储结构,指针需要从前往后移动查找;
③当对数据进行增加删除操作时(remove()和add()操作),LinkedList比ArrayList效率高,因为ArrayList需要移动数据;
④LinkedList比ArrayList更占内存,因为LinkedList的结点除了存储数据外,还存储两个指针;
⑤从利用效率来看,ArrayList自由行较低,因为需要手动设置固定大小的容量,但是使用比较方便,直接通过下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是不便于使用;
⑥如果涉及到堆、栈、队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值