LinkedList源码学习第一天

目录

1,LinkedList简介

2,类图

3,属性

4,构造方法

5,添加方法

1,添加单个元素add(E e) 

2,插入单个元素到指定位置add(int index, E element)

3,在表头插入数据addFirst(),push()在表尾插入数据addLast(),offer()

4,添加多个元素addAll(Collection c)

6,链表扩容

7,删除元素

1. 移除单个元素remove(int index)

2,删除首个为o的元素remove(Object o) 

3,删除链表首个为o的元素removeFirstOccurrence(Object o)

4,删除链表最后为o的元素removeLastOccurrence(Object o)

5,删除首个节点remove() 

6,删除尾结点removeLast()

7,pool方法里面有移除头尾操作

8,移除多个元素removeAll(Collection c)

9,移除集合c中不存在的元素retainAll(Collection c)

1,LinkedList简介

LinkedList ,基于节点实现的双向链表的 List ,每个节点都指向前一个和后一个节点从而形成链表。相比 ArrayList 来说,我们日常开发使用 LinkedList 相对比较少。

2,类图

LinkedList 实现的接口、继承的抽象类,如下图所示:

 如下 3 个接口是 ArrayList 一致的:

  • List接口
  • Serializable接口
  • Cloneable接口

如下1个接口是少于ArrayList的:

RandomAccess接口,LinkedList不同于ArrayList的很大一点,不支持随机访问

如下一个接口是多于ArrayList的:

Deque接口,提供双端队列的功能,LinkedList 支持快速的在头尾添加元素和读取元素,所以很容易实现该特性。

继承了 AbstractSequentialList 抽象类,它是 AbstractList 的子类,实现了只能连续访问“数据存储”(例如说链表)的 #get(int index)#add(int index, E element) 等等随机操作的方法。可能这样表述有点抽象,点到AbstractSequentialList  抽象类中看看这几个方法,基于迭代器顺序遍历后,从而实现后续的操作。

  • 但是呢,LinkedList 和 ArrayList 多是一个有点“脾气”的小伙子,都为了结合自身的特性,更加高效的实现,都选择了重写了 AbstractSequentialList 的方法,嘿嘿。
  • 不过一般情况下,对于支持随机访问数据的继承 AbstractList 抽象类,不支持的继承 AbstractSequentialList 抽象类。

3,属性

LinkedList 一共有 3 个属性。如下图所示:

  • transient int size = 0;
  • transient Node<E> first;
  • transient Node<E> last;

 代码如下所示:

 transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

firstlast属性:链表的头尾指针.

  • 在初始的时候,firstlast指向null,因为此时没有Node节点,在添加完首个节点后,创建对应的Node节点node1,前后指向null , 此时firstlast指向Node节点
  • 若继续添加一个元素后,创建对应的Node节点node2, prev = node1 和 next = null ,而 node1  prev = null 和 next = node2 。此时,first 保持不变,指向 node1 last 发生改变,指向 node2 
  • size 属性:链表的节点数量。通过它进行计数,避免每次需要 List 大小时,需要从头到尾的遍历。

对应代码如下:

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

      /**
     * 链表大小
     */
    transient int size = 0;

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

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

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

4,构造方法

LinkedList 一共有两个构造方法,我们分别来看看。代码如下:

        public LinkedList() {
    }


        public LinkedList(Collection<? extends E> c) {
        this();
        // 添加 c 到链表中
        addAll(c);
    }

相比 ArrayList 来说,因为没有容量一说,所以不需要提供 #ArrayList(int initialCapacity) 这样的构造方法。

5,添加方法

1,添加单个元素add(E e) 

add(E e) 方法,顺序添加单个元素到链表。代码如下:

相比 ArrayList 来说,无需考虑容量不够时的扩容

public boolean add(E e) {
    // <X> 添加末尾
    linkLast(e);
    return true;
}


void linkLast(E e) {
         // <1> 记录原 last 节点
        final Node<E> l = last;
        // 第一个参数表示,newNode 的前一个节点为 l 。
        // 第二个参数表示,e 为元素。
        // 第三个参数表示,newNode 的后一个节点为 null 。

        final Node<E> newNode = new Node<>(l, e, null);
         // <3> last 指向新节点
        last = newNode;
         // <4.1> 如果原 last 为 null ,说明 first 也为空,则 first 也指向新节点
        if (l == null)
         // <4.2> 如果原 last 非 null ,说明 first 也非空,则原 last 的 next 指向新节点。
            first = newNode;
        else
            l.next = newNode;
        size++;
        // <6> 增加数组修改次数
        modCount++;
    }

2,插入单个元素到指定位置add(int index, E element)

插入单个元素到指定位置add(int index, E element) 方法, 。代码如下:

 public void add(int index, E element) {
        checkPositionIndex(index);  // 校验不要超过范围
        // 如果刚好等于链表大小,直接添加到尾部即可
        if (index == size)  
            linkLast(element);
        else
            //添加到第 index 的节点的前面
            linkBefore(element, node(index));
    }

linkBefore(E e, Node<E> succ) 方法,添加元素 e 到 succ 节点的前面。代码如下



 void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
         // 获得 succ 的前一个节点
        final Node<E> pred = succ.prev;
        // 创建新的节点 newNode
        final Node<E> newNode = new Node<>(pred, e, succ);
        //设置 succ 的前一个节点为新节点
        succ.prev = newNode;
        // 如果 pred 为 null ,说明 first 也为空,则 first 也指向新节点
        if (pred == null)
            first = newNode;
        // 如果 pred 非 null ,说明 first 也为空,则 pred 也指向新节点
        else
            pred.next = newNode;
        // 增加链表大小
        size++;
        // 增加数组修改次数
        modCount++;
    }

node(int index) 方法,获得第 index 个 Node 节点,会去判断是不是超过链表一半去决定是否使用倒序还是先序遍历

   Node<E> node(int index) {
        // assert isElementIndex(index);
// 如果 index 小于 size 的一半,就正序遍历,获得第 index 个节点
        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--)  如果 index 大于 size 的一半,就倒序遍历,获得第 index 个节点
                x = x.prev;
            return x;
        }
    }

3,在表头插入数据addFirst(),push()在表尾插入数据addLast(),offer()

因为 LinkedList 实现了 Deque 接口,所以它实现了 addFirst(E e) 和 addLast(E e) 方法,分别添加元素到链表的头尾。代码如下:

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



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

linkLast(E e) 方法,和 add(E e) 方法是一致的

addFirst(E e) 方法,调用 linkFirst(E e) 方法,添加元素到队头。代码如下

因为 LinkedList 实现了 Queue 接口,所以它实现了 push(E e) 和 offer(E e) 方法,添加元素到链表的头尾。就是上面2个方法代码如下:

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

public boolean offer(E e) {
        return add(e);
    }

4,添加多个元素addAll(Collection<? extends E> c)

addAll(Collection<? extends E> c) 方法,批量添加多个元素。代码如下:

public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }



public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);
        //将 c 转成 a 数组
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0) // 如果无添加元素,直接返回 false 数组未变更
            return false;
        // 获得第 index 位置的节点 succ ,和其前一个节点 pred
        Node<E> pred, succ;
        if (index == size) { // 如果 index 就是链表大小,那说明插入队尾,所以 succ 为 null ,pred 为 last
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.prev;
        }
        // 遍历 a 数组,添加到 pred 的后面
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;   // 创建新节点
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null) // 如果 pred 为 null ,说明 first 也为 null ,则直接将 first 指向新节点
                first = newNode;
            else    // pred 下一个指向新节点
                pred.next = newNode;
            pred = newNode;  // 修改 pred 指向新节点
        }
        //修改 succ 和 pred 的指向
        if (succ == null) { // 如果 succ 为 null ,说明插入队尾,则直接修改 last 指向最后一个 pred
            last = pred;
        } else {   // 如果 succ 非 null ,说明插入到 succ 的前面
            pred.next = succ;   //下一个指向 succ
            succ.prev = pred;   // succes 前一个指向 pred
        }
        //增加链表大小
        size += numNew;
        modCount++; //增加数组修改次数
        return true; //返回 true 数组有变更
    }

6,链表扩容

Linked没有扩容,因为添加元素都是通过Node的前后指定

7,删除元素

1. 移除单个元素remove(int index)

remove(int index) 方法,移除指定位置的元素,并返回该位置的原元素。代码如下:

public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index)); // 获得第 index 的 Node 节点,然后进行移除
    }
  • 首先,调用 node(int index) 方法,获得第 index 的 Node 节点。然后调用 unlink(Node<E> x) 方法,移除该节点。

unlink(Node<E> x)方法,代码如下:

E unlink(Node<E> x) {
        // assert x != null;获得 x 的前后节点 prev、next
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
        //将 prev 的 next 指向下一个节点
        if (prev == null) {
            first = next; //如果 prev 为空,说明 first 被移除,则直接将 first 指向 next
        } else {    //如果 prev 非空
            prev.next = next; // prev 的 next 指向 next
            x.prev = null;   // x 的 pre 指向 null
        }
        //将 next 的 prev 指向上一个节点
        if (next == null) {//如果 next 为空,说明 last 被移除,则直接将 last 指向 prev
            last = prev;
        } else { //如果 next 非空
            next.prev = prev; // next 的 prev 指向 prev
            x.next = null; // x 的 next 指向 null
        }
        //将 x 的 item 设置为 null ,帮助 GC
        x.item = null;
        //减少链表大小
        size--;
        // 增加数组的修改次数
        modCount++;
        return element;
    }

2,删除首个为o的元素remove(Object o) 

remove(Object o) 方法,移除首个为 o 的元素,并返回是否移除到。代码如下:

public boolean remove(Object o) {
        if (o == null) {    // o 为 null 的情况
            for (Node<E> x = first; x != null; x = x.next) { // 顺序遍历,找到 null 的元素后,进行移除
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {// 顺序遍历,找到等于 o 的元素后,进行移除
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

3,删除链表首个为o的元素removeFirstOccurrence(Object o)

removeFirstOccurrence(Object o)删除链表首个为o代码如下:

就是去调用remove(Object o) 

public boolean removeFirstOccurrence(Object o) {
        return remove(o);
    }

4,删除链表最后为o的元素removeLastOccurrence(Object o)

removeLastOccurrence(Object o)删除链表最后为o的元素代码如下:

public boolean removeLastOccurrence(Object o) {  // 移除首个
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

5,删除首个节点remove() 

remove() 方法,移除链表首个节点。代码如下:

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

public E removeFirst() {
        final Node<E> f = first;
        if (f == null)  //如果链表为空,抛出 NoSuchElementException 异常
            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 的下一个节点
        f.item = null;  /// 设置 f 的 item 为 null ,帮助 GC
        f.next = null; // help GC  设置 f 的 next 为 null ,帮助 GC
        first = next; // 修改 fisrt 指向 next
        if (next == null)  // 如果链表只有一个元素,说明被移除后,队列就是空的,则 last 设置为 null
            last = null;
        else
            next.prev = null;
        size--;  // 链表大小减一
        modCount++; // 增加数组修改次数
        return element; 
    }

6,删除尾结点removeLast()

removeLast() 方法,移除链表最后一个节点。代码如下:

public E removeLast() {
        final Node<E> l = last;
        if (l == null)  // 如果链表为空,则抛出 NoSuchElementException 移除
            throw new NoSuchElementException();
        return unlinkLast(l);  // 移除链表的最后一个元素
    }
 private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev; // 获得 f 的上一个节点
        l.item = null; // 设置 l 的 item 为 null ,帮助 GC
        l.prev = null; // help GC设置 l 的 prev 为 null ,帮助 GC
        last = prev;  // 修改 last 指向 prev
        if (prev == null) // 如果链表只有一个元素,说明被移除后,队列就是空的,则 first 设置为 null
            first = null;
        else
            prev.next = null;
        size--;  // 链表大小减一
        modCount++; // 增加数组修改次数
        return element;
    }

7,pool方法里面有移除头尾操作

public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }


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


public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

8,移除多个元素removeAll(Collection<?> c)

removeAll(Collection<?> c) 方法,批量移除指定的多个元素。代码如下:

该方法是通过父类AbstractCollection来实现的通过迭代器遍历LinkedList判断c包含就删除

 public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;
        Iterator<?> it = iterator(); // 获得迭代器
        while (it.hasNext()) { // 通过迭代器遍历
            if (c.contains(it.next())) { // 如果 c 中存在该元素,则进行移除
                it.remove();
                modified = true; // 标记修改
            }
        }
        return modified;
    }

9,移除集合c中不存在的元素retainAll(Collection<?> c)

removeAll(Collection<?> c) 方法相反代码如下

public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            if (!c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ehdjsbs

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

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

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

打赏作者

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

抵扣说明:

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

余额充值