LinkedList源代码阅读(1)

        在实际开发中,我们在实现List接口来创建集合的时候,并非只能通过数组(ArrayList)来实现,我们还可以通过链表(LinkedList)来创建一个集合。在LinkedList中,他内部的每一个元素都指向下一个元素,每一个节点都是一个单独的存储空间。

链表结构如图所示:

 1、类结构

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

由上述代码可以知道

(1)LinkedList同时实现了List接口和Queue接口,所以它既是一个双向链表,也是一个双端队列。允许添加任何元素,包括null元素;

(2)LinkedList的所有方法的实现都体现了双链表的特性,所有有关索引的操作都自动从LinkedList的开头或结尾开始遍历;

(3)其内部由若干个Node对象组成;

(4)LinkedList和ArrayList最大的区别在于前者不存在扩容因为采用链表结构,每次添加元素,都会创建新的Node节点并分配空间,所以不存在扩容;

(5)适合数据频繁添加和删除操作,写多读少的场景。

2、成员变量

transient int size = 0; // 表示元素个数

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

transient Node<E> last; // 尾节点

(1)加上transient关键字的作用时保证该属性不会被序列化;

(2)分别定义了链表中元素的个数以及头节点、尾节点;

头节点的固定引用(方便节点的创建、添加和链表的查找遍历)链表必须先创建添加头节点,才能创建添加第二个节点,以此类推查找遍历同理,链表是顺序访问列表,只能从开头或者末尾开始挨个访问所以头节点不可能匿名,需要一个固定引用方便后续的使用

3、构造方法

public LinkedList() {
    }


public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

(1)LinkedList中定义了两个构造方法,分别无参构造方法和有参构造方法;

(2)无参构造方法用来对LinkedList进行初始化;

(3)有参构造方法可以直接传入一个一个集合对象,调用addAll()方法直接存在LinkedList中。

节点的顺序需要与指定集合c的元素顺序完全一致

4、主要方法

(1)linkFirst(E e)

    //创建一个包含元素e的新节点
    //并链接至链表的首部(linkFirst),这个节点就是链表的新首节点
    //方法存在的意义:LinkedList同时implement了接口Queue和Deque
    private void linkFirst(E e) {
       //假设链表不为空,获取并传递首节点的引用
       //传递后,引用f和first同时指向首节点
       //这一步实际上也是复制首节点的引用
       final Node<E> f = first;
       //分别提供以下参数创建一个节点对象newNode:
       //①、上一个节点的引用null
       //②、元素e
       //③、下一个节点的引用f
       //newNode其实就是新的首节点,两点原因:
       //①、在链表中,只有首节点的上一个节点引用为null
       //②、原首节点f成为newNode的下一个节点
       //③、节点关系的维护放在了构造函数中(参考截图)
       final Node<E> newNode = new Node<>(null, e, f);
       //将newNode赋值给first,保证first永远都是首节点的引用
       first = newNode;
       //上面的代码只初始化了first,并未初始化last
       //如果首节点f为null,说明链表为空
       //所以新创建的节点newNode既是首节点也是尾节点
       if (f == null)
          //初始化last
          last = newNode;
       else
          //如果首节点f不为空
          //需要考虑两方面:元素和关系
          //元素由客户端程序员添加
          //这里只需考虑关系
          //首节点f的prev肯定为null
          //现在创建了新的首节点newNode
          //f自然就是第二节点
          //所以需要通过代码表示:
          //f的上一个节点是newNode
          //以此保证各个节点的关系正确
          f.prev = newNode;
       //新增一个节点,长度+1
       size++;
       //继承自抽象类AbstractList,保证快速失败机制的正常执行
       //新增节点是结构性修改
       modCount++;
    }

(2)linkLast(E e):

    //创建一个包含元素e的新节点,并链接至链表的尾部(linkLast)
    //这个节点就是链表的新尾节点
    //方法存在的意义:LinkedList同时implement了接口Queue和Deque
    //实现它们的方法时可以有效避免代码重复 
    void linkLast(E e) {
       //假设链表不为空,获取并传递尾节点的引用
       //传递后,引用l和last同时指向尾节点
       //这一步实际上也是复制尾节点的引用
       final Node<E> l = last;
       //创建一个节点对象newNode://①、上一个节点的引用l②、元素e
       //③、下一个节点的引用null
       //newNode其实就是新的尾节点,两点原因:
       //①、在链表中,只有尾节点的下一个节点引用为null
       //②、原尾节点l成为newNode的上一个节点
       final Node<E> newNode = new Node<>(l, e, null);
       //将newNode赋值给last,保证last永远都是尾节点的引用
       last = newNode;
       //上面的代码只初始化了last,并未初始化first
       //如果尾节点l为null,说明链表为空
       //所以新创建的节点newNode既是尾节点也是首节点
       if (l == null)
          //初始化first
          first = newNode;
       else
          //如果尾节点l不为空
          //需要考虑两方面:元素和关系
          //元素由客户端程序员添加
          //这里只需考虑关系
          //尾节点l的next肯定为null
          //现在创建了新的尾节点newNode
          //l自然就是倒数第二节点
          //所以需要通过代码表示:
          //l的下一个节点是newNode
          //以此保证各个节点的关系正确
          l.next = newNode;
       //新增一个节点,长度+1
       size++;
       //继承自抽象类AbstractList,保证快速失败机制的正常执行
       modCount++;
    }

5、常用方法

(1)boolean add(E e):添加新元素至链表尾部,默认调用linkLast()添加到尾部;

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

(2)void addFirst(E e):添加新元素至链表头部

public void addFirst(E e) {
        linkFirst(e);
    }

(3)void addLast(E e):添加新元素至链表尾部

public void addLast(E e) {
        linkLast(e);
    }

(4)E get(int index):遍历链表,找到指定位置的元素

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

(5)E getFirst():获取链表中的头元素

public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

(6)E getLast():获取链表中的尾元素

public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

(7)E remove():删除链表中的头元素

public E remove() {
        return removeFirst();
    }

(8)boolean remove(Object o):删除指定内容的元素

public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

(9) E remove(int index):删除指定位置的元素

public E remove(int index) {
        checkElementIndex(index);//检查index是否合法
        return unlink(node(index));
    }

 (10)E removeFirst():删除链表中的头元素

public void addFirst(E e) {
        linkFirst(e);
    }

 (11)E removeLast():删除链表中的尾元素

public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }

(12)default void sort(Comparator c):按照Comparator比较器,将链表中的所有元素进行排序

@SuppressWarnings({"unchecked", "rawtypes"})
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

(13)T[] toArray(T[] a):将链表转换为数组

<T> T[] toArray(T[] a);

6、ArrayList和LinkedList的区别

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农叮叮车

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值