LinkedList源码学习

一.LinkedList与ArrayList的异同

1.相同点:

  • ArrayList、LinkedList都是单列集合中List接口的实现类,直接或间接继承了AbstractList类
  • 他们都是存取、允许重复、可有多个null值,插入有序
  • 都是线程不安全的

2.不同点:

  • ArrayList的实现是基于动态数组,在内存中分配连续的空间

    LinkedList的底层数据结构是双向链表

  • ArrayList查询快,增删慢

    LinkedList查询慢、增删快

  • LinkedList继承了Deque接口,所以LinkedList还实现了Deque接口的特有方法。

  • 在增加操作时,ArrayList通过System.arraycopy完成批量增加的
    LinkedList通过依次加入结点做到的

二.源码部分

1.基本属性

**AbstractSequentialList 继承自 AbstractList,是 LinkedList 的父类,是 List 接口 的简化版实现。简化在 AbstractSequentialList 只支持按次序访问,而不像 AbstractList 那样支持随机访问。

List 的数据结构就是一个序列,存储内容时直接在内存中开辟一块连续的空间,然后将空间地址与索引对应。

Deque 表示双端队列。双端队列是在两端都可以进行插入和删除的队列。Deque是一个比Stack和Queue功能更强大的接口,它同时实现了栈和队列的功能。ArrayDeque和LinkeList实现了Deque接口。

Cloneable接口 是一个标记接口,也就是没有任何内容

Serializable接口 是启用其序列化功能的接口。实现java.io.Serializable 接口的类是可序列化的。没有实现此接口的类将不能使它们的任意状态被序列化或逆序列化。

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable {
    //transient关键字为类型修饰符,英文本意为“短暂的”,在对象序列化过程中,标记了transient的变量不会被序列化。
    transient int size;
    //头节点
    transient LinkedList.Node<E> first;
    //尾节点
    transient LinkedList.Node<E> last;
    //数字后面加上的L表示这是一个long值。 通过这种方式来解决不同的版本之间的串行话问题
    private static final long serialVersionUID = 876323262645176354L;

2.Node

//私有静态内部类
    private static class Node<E> {
        //节点值
        E item;
        //后继结点
        LinkedList.Node<E> next;
        //前驱结点
        LinkedList.Node<E> prev;
//有参构造方法
        Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

3.构造函数

    /**
     * 构造函数
     *1.无参构造函数LinkedList()
     * 2.有参构造函数
     * 2.1LinkedList(Collection<? extends E> c)
     * 2.2linkFirst(E e)
     */
    //无参构造,size为0
    public LinkedList() {
        this.size = 0;
    }
//将已有元素的集合Collection 的实例添加到 LinkedList 中,调用的是 addAll() 方法
    public LinkedList(Collection<? extends E> c) {
        this();
        this.addAll(c);
    }

4.add方法

  • boolean addAll(Collection<? extends E> c)
//将一个集合的所有元素添加到链表后面,返回是否成功,成功为 true,失败为 false。
    public boolean addAll(Collection<? extends E> c) {
        return this.addAll(this.size, c);
    }
  • boolean addAll(int index, Collection<? extends E> c)

    //将一个集合的所有元素添加到链表的指定位置后面,返回是否成功,成功为 true,失败为 false。
        public boolean addAll(int index, Collection<? extends E> c) {
            //调用方法,不正确则将抛出异常
            this.checkPositionIndex(index);
            //将list直接转为Object[] 数组
            Object[] a = c.toArray();
            //数组a的长度
            int numNew = a.length;
            //如果长度为0,则返回失败
            if (numNew == 0) {
                return false;
            } else {
                //要添加位置的前驱结点
                LinkedList.Node pred;
                //添加位置的结点,index号
                LinkedList.Node succ;
                //在链表后面插入结点
                if (index == this.size) {
                    succ = null;
                    pred = this.last;
                } else {
                    //node()方法返回index结点
                    succ = this.node(index);
                    //前驱结点则为succ的前驱结点
                    pred = succ.prev;
                }
    
                Object[] var7 = a;
                int var8 = a.length;
                //遍历a.length次数组
                for(int var9 = 0; var9 < var8; ++var9) {
                    //将数组中的值赋给o
                    Object o = var7[var9];
                    //通过有参构造函数的方法新建结点,前驱结点为:pred, 值为o, 后继结点为null
                    LinkedList.Node<E> newNode = new LinkedList.Node(pred, o, (LinkedList.Node)null);
                    //如果添加位置为第一个位置
                    if (pred == null) {
                        //直接将first指向新的结点
                        this.first = newNode;
                    } else {
                        //如果不为第一个位置,则将前驱结点的next指向新结点
                        pred.next = newNode;
                    }
                    //pred指向新的结点,等待下个结点顺序插入
                    pred = newNode;
                }
                //如果插入位置为尾节点
                if (succ == null) {
                    //将last指向目前链表的尾节点
                    this.last = pred;
                } else {
                    //将本来在index位置的结点添加在链表上
                    pred.next = succ;
                    succ.prev = pred;
                }
    
                //链表的大小加上集合的长度
                this.size += numNew;
                //增加链表修改次数
                ++this.modCount;
                return true;
            }
        }
    

5.remove方法

  • public E removeFirst()
//删除并返回第一个元素。
    public E removeFirst() {
        //取到第一个结点的
        LinkedList.Node<E> f = this.first;
        //如果为空,则抛出异常
        if (f == null) {
            throw new NoSuchElementException();
        } else {
            //删除第一个结点
            return this.unlinkFirst(f);
        }
    }
//删除首结点
    private E unlinkFirst(LinkedList.Node<E> f) {
        //先将数据保存起来
        E element = f.item;
        //先将首结点的后一个结点保存起来
        LinkedList.Node<E> next = f.next;
        //f = null
        f.item = null;
        f.next = null;
        //首结点变成要删除结点的后一个结点
        this.first = next;
        //如果只有一个结点,即首结点没有后继节点,则将
        if (next == null) {
            this.last = null;
        } else {
            //next结点则为新的首结点
            next.prev = null;
        }
        //size - 1
        --this.size;
        //增加链表修改次数
        ++this.modCount;
        //返回首结点的数值
        return element;
    }

public boolean remove(Object o)

//删除某一元素,返回是否成功,成功为 true,失败为 false。
    public boolean remove(Object o) {
        LinkedList.Node x;
        //如果实参为null,则将值为null的都遍历删除
        if (o == null) {
            for(x = this.first; x != null; x = x.next) {
                if (x.item == null) {
                    this.unlink(x);
                    return true;
                }
            }
        } else {
            for(x = this.first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    this.unlink(x);
                    return true;
                }
            }
        }

        return false;
    }
//删除参数结点
    E unlink(LinkedList.Node<E> x) {
        E element = x.item;
        //保存前驱和后继结点
        LinkedList.Node<E> next = x.next;
        LinkedList.Node<E> prev = x.prev;
        //如果要删除的结点为首结点
        if (prev == null) {
            this.first = next;
        } else {
            //若删除结点有后继结点,则将其指向要删除结点的后继结点
            prev.next = next;
            //将x设孔
            x.prev = null;
        }
//如若要删除的结点为最后一个结点
        if (next == null) {
            //last指针指向删除结点的前一个结点
            this.last = prev;
        } else {
            //否则,将要删除结点的后继结点的前驱结点指向要删除结点的前驱结点
            next.prev = prev;
            //将x设孔
            x.next = null;
        }
//将x设孔
        x.item = null;
        //size也 - 1
        --this.size;
        ++this.modCount;
        //返回被删除结点的数据
        return element;
    }
  • public E remove(int index)
//删除指定位置的元素。
    public E remove(int index) {
        //同样的index检查
        this.checkElementIndex(index);
        //先用node方法找到目标结点,传参到unlink方法中删除
        return this.unlink(this.node(index));
    }

6.get、set方法

  • public E get(int index)
//返回指定位置的元素。
    public E get(int index) {
        //与addall相同,添加指定位置时就检查是否在范围内
        this.checkElementIndex(index);
        //调用node方法,返回index结点的存储值
        return this.node(index).item;
    }

  • public E getFirst()
//返回第一个元素。
    public E getFirst() {
        //获取首结点
        LinkedList.Node<E> f = this.first;
        //若为空,则抛出异常
        if (f == null) {
            throw new NoSuchElementException();
        } else {
            //返回数据
            return f.item;
        }
    }
  • public E set(int index, E element)

    //设置指定位置的元素
        public E set(int index, E element) {
            //检查index是否合法
            this.checkElementIndex(index);
            //新建临时结点 x 存储index结点
            LinkedList.Node<E> x = this.node(index);
            //将旧的index结点的值保存起来
            E oldVal = x.item;
            //重新为其赋值
            x.item = element;
            //返回旧的值
            return oldVal;
        }
    

7.LinkedList.Node node(int index)方法

//为add,get等操作的提供了查找并返回结点的功能
LinkedList.Node<E> node(int index) {
        LinkedList.Node x;
        int i;
        //位移运算符大于小于操作
        //当要查找的index小于size的一半,x指向首结点
        if (index < this.size >> 1) {
            x = this.first;
//遍历index - 1次,找到index的位置
            for(i = 0; i < index; ++i) {
                x = x.next;
            }
//返回index结点
            return x;
        } else {
            //如果index的值在链表的后半部
            //x = 尾结点,从后面开始遍历
            x = this.last;
//遍历到index的前一个结点
            for(i = this.size - 1; i > index; --i) {
                x = x.prev;
            }

            return x;
        }
    }

三.总结

  • 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
  • 头尾和已知节点的插入和删除时间复杂度都是o(1)
  • 在增加,查找,删除操作的时候通过node(index)找到并返回目标结点
posted @ 2022-02-19 13:34  ftfty  阅读(15)  评论(0编辑  收藏  举报