单列集合(Collection)之 List:LinkedList

        区别于 ArrayList 和 Vecter ,LinkedList 的数据结构是 双向链表,而不是数组。

        链表也有单链表双链表和循环链表之分,它与 数组 的区别就是数据存储地址不是连续,需要通过存储下一个数据的引用而关联起来。我们来看看下面这张图:

单链表

        因为元素存储的地址不是连续的,一个 node 为一个元素。单链表里元素(node) 分为两部分,数据域(存储value部分)指针域(存储引用对象部分) ,所以每一个 node 都会把下一个 元素(node) 的引用存放在 指针域 里,这样就可以把元素有序的串联起来。而当 指针域 里是 null 时,说明这个元素(node)是最后一个。

双向链表

        上面我们了解了单链表后,那双链表又是如何的呢?我们看下面这个图:

        相比单链表,双链表多了一个指针域。多出来的这个指针域是用来存放上一个元素(node)的引用。这样我们看到不单可以找下一个元素(node)也可以找上一个元素(node),是双向的。如果元素(node)的 perv 是 null ,当前元素(node)就是第一个(first),next 是 null 说明就是最后一个(last)元素(node)。

        LinkedList 采用链表的方式实现,每个元素(node) 存储地址都不是连续性,所以也不需要考虑空间开辟(初始化容量)问题。有空间就存入,只需要记录前后元素(node)的地址就能串联起来。也应为是链表,元素(node)存储地址不是连续的,不可以像数组那样通过下标随机去访问元素。只能依次遍历的去寻找元素(node)。

因为 JAVA 没有指针的概念,所以指针域里存入的是 元素(node)的引用对象。

        接下来我们看看 LinkedList 是如何实现的。 

/*** LinkedList 类部分代码 ***/

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    // 元素(node)的数量
    transient int size = 0;

    /**
     * 指向第一个节点指针
     */
    transient Node<E> first;

    /**
     * 指向最后节点指针
     */
    transient Node<E> last;

    /**
     * 参数为空的构造函数
     */
    public LinkedList() {
    }

    /***
     参数为集合类型的构造函数
     ***/
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    
    /***
        内部的 Node 类
    ***/
    private static class Node<E> {
        // 元素的值(数据域)
        E item;
        // 下一个元素(node):指针域
        Node<E> next;
        // 上一个元素(node):指针域
        Node<E> prev;
        
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

}

        我们看到上面又两个构造函数提供我们创建 LinkedList 集合,一个有参的一个无参的。first 变量的类型是一个 Node 这个类是 LinkedList 的一个内部类,我们看这个类也有个构造函数会初始化一个节点。 

添加元素(node):

/*** 例子代码 1 ***/

/ 使用无参的构造函数创建 LinkedList 集合
LinkedList<String> objects = new LinkedList<>();
// 使用add 方法添加一个元素(node)
objects.add("1");

        那 add 方法如何在链表添加元素的(node)? 

/*** LinkedList 类部分代码 ***/

/*** 在末端添加一个元素(node) ***/
public boolean add(E e) {
        // 调用 linkLast 方法在链接最后添加元素
        linkLast(e);
        return true;
    }

/*** 链接最后添加元素 ***/
void linkLast(E e) {
        // 把最后一个元素(last) 赋值给 l 变量
        final Node<E> l = last;
        // 新创建一个元素(node),l 赋值给 prev ,e 赋值给 item,null 赋值next。
        final Node<E> newNode = new Node<>(l, e, null);
        // newNode 赋值给 last 记录为最后一个元素(node)
        last = newNode;
        // l 等于 null,说明链表里没有元素(node),把 newNode 设置为第一个节点
        if (l == null)
            first = newNode;
        else
            // l 不等于 null 说明集合不是空的,则把 newNode 赋值给 l 的 next,能让上一个元素(node)指向下一个元素(node)
            l.next = newNode;
        // 集合元素 + 1
        size++;
        // 修改次数 + 1
        modCount++;
    }

/*** 指定位置添加一个元素(node); index:值得的位置; element: 添加的内容(值) ***/
public void add(int index, E element) {
        // 检查 index 是否在集合范围
        checkPositionIndex(index);
        // index 等于 size 说明是在链接最后添加元素(node),所以调用 linkLast 方法
        if (index == size)
            linkLast(element);
        else
            // index 不等于 size 说明是插入一个元素(node),所以调用 linkBefore 方法
            // node 方法传入 index 查找插入元素的位置,这里调用了下面的这个 node 方法
            linkBefore(element, node(index));
    }

/*** 获取 index 相应位置的元素(node),这里用于查找插入元素的位置 ***/
Node<E> node(int index) {
         // 这里使用二分法的方式快速查找元素插入的位置的元素(node)
         // index 小于 size 的一半就从集合的头开始找插入的位置; >>:相当于 size 除于 2
        if (index < (size >> 1)) {
            // 那第一个元素(node),这里因为不能像数组那样可以通过下标随机获取元素(node),所以要遍历去拿到插入位置的元素(node),并返回插入位置的元素(node)
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            // index 大于 size 就从集合的尾开始找插入的位置,并返回插入位置的元素(node)
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }


/*** 链接之前添加元素;e:添加的内容(值),succ:添加位置的元素(node)  ***/
void linkBefore(E e, Node<E> succ) {
        // 获取添加位置的元素(node)的前一个元素(node)
        final Node<E> pred = succ.prev;
        // 将原本添加位置元素(node)的前一个元素(node)和添加的内容(值)及添加位置的元素(node),生成一个新的元素(node)
        final Node<E> newNode = new Node<>(pred, e, succ);
        // 把新的元素(node),赋值给添加位置元素的前一个元素(node),完成添加位置元素(node)的后移操作
        succ.prev = newNode;
        // 判断添加元素(node)的前一个元素(node)是空的,就把新添加的元素(node)记录为第一个元素
        if (pred == null)
            first = newNode;
        else
            // 不是空的说明集合不是空的,就把添加位置的前一个元素(node),的下一个元素(node)设置为新的元素(node),完成前一个元素(node)对后一个元素的链接(node)
            pred.next = newNode;
        // 集合的元素总数 + 1
        size++;
        // 修改次数 + 1
        modCount++;
    }
概览图: 

        add(E e):

        add(int index, E element) :

获取元素(node):

了解了元素(node)的添加后,我们再看看获取元素(node)内容。

/*** 例子代码2 ***/

LinkedList<String> objects = new LinkedList<>();

objects.add("1");
objects.add("2");
objects.add("3");
        
objects.get(1);
/*** LinkedList 类部分代码 ***/

/*** 指定获取元素(node); index:获取第几个元素(node) ***/;
public E get(int index) {
        // 检查 index 是否在集合的范围内
        checkElementIndex(index);
        // 调用 node 方法获取相应元素(node),并.item 返回内容(值)
        return node(index).item;
    }

/*** 获取 index 相应位置的元素(node),和上面的 node 方法是同一个 ***/
Node<E> node(int index) {
         // 这里使用二分法的方式快速查找元素插入的位置的元素(node)
         // index 小于 size 的一半就从集合的头开始找插入的位置; >>:相当于 size 除于 2
        if (index < (size >> 1)) {
            // 那第一个元素(node),这里因为不能像数组那样可以通过下标随机获取元素(node),所以要遍历去拿到插入位置的元素(node),并返回插入位置的元素(node)
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            // index 大于 size 就从集合的尾开始找插入的位置,并返回插入位置的元素(node)
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

        因为链表存储元素(node)存储地址不是连续的,不可以像数组那样通过下标随机去访问元素。只能依次遍历的去寻找元素。

删除元素(node):

        那删除元素(node)呢?删除元素有以下几个方法:

clear()
/*** 例子代码3 ***/

LinkedList<String> objects = new LinkedList<>();

objects.add("1");
objects.add("2");
objects.add("3");
        
objects.clear();
/*** LinkedList 类部分代码 ***/

public void clear() {
        // Clearing all of the links between nodes is "unnecessary", but:
        // - helps a generational GC if the discarded nodes inhabit
        //   more than one generation
        // - is sure to free memory even if there is a reachable Iterator
        for (Node<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
        modCount++;
}

        通过遍历集合,把每个元素(node)之间的连接(引用),清除。只是清除了集合里的元素(node),集合对象再未被GC回收之前都可以重新给该集合添加新的元素(node):

  1. 把第一个元素(node) 赋值给 x 变量,通过给 x 里的数据域和指针域赋值为空,直到判断 x 为 null 说明已经删除了最后一个元素(node)
  2. 把记录第一个和最后一个节点的 first 和 last 变量也赋值为 null
  3. 集合元素总数赋值为 0 个
  4. 修改次数 + 1

这里为什么要把元素(node)之间的连接(引用)清除,而不是直接把集合赋值为 null?

        我们看上面的注释就大概能知道,这个和GC回收算法有些关系,在一些旧版本,GC判断当前对象是否为垃圾对象并可以被回收,是看这个对象是否有被引用,被引用了说明这个对象还是在使用着不能被回收。所以即使直接把集合赋值为 null,但是元素(node)之间还是相互连接(引用)着,不利于GC的回收。

        这里也是应该为了兼容旧版本而做的一些工作,方便GC进行更好的回收不用的对象提高性能。 

remove(): 
/*** 例子代码4 ***/

LinkedList<String> objects = new LinkedList<>();

objects.add("1");
objects.add("2");
objects.add("3");
        
objects.remove();
/*** LinkedList 类部分代码 ***/

/*** 删除元素(只删除第一个元素(node)),并返回删除的内容(值) ***/
public E remove() {
        // 删除第一个元素(node)
        return removeFirst();
}

/*** 删除第一个元素,并返回删除的内容(值) ***/
public E removeFirst() {
        // 获取当前第一个元素(node)赋值给 f
        final Node<E> f = first;
        // f 等于空,说明集合为空,则不能删除,并抛出异常
        if (f == null)
            throw new NoSuchElementException();
        // 删除集合的首个元素(node);参数 f:删除的元素(node)
        return unlinkFirst(f);
}

/*** 删除集合的首个元素(node);参数 f:删除的元素(node)***/
private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        // 获取删除元素(node)的内容(值),赋值给 element 变量
        final E element = f.item;
        // 获取删除元素(node)的下一个元素(node),赋值给 next 变量
        final Node<E> next = f.next;
        // 把删除元素(node)的数据域清空,赋值为 null
        f.item = null;
        // 把删除元素(node)的指向下一个元素(node)的指针域清空,赋值为 null
        f.next = null; // help GC
        // 把删除元素(node)的下一个元素(node)标记为第一个元素(node)
        first = next;
        // next 等于 null,说明删除的元素是集合里的最后一个元素,所以也把 last 赋值为 null
        if (next == null)
            last = null;
        else
            // 把删除的元素(node)与下一个元素(node)的之前引用清除
            next.prev = null;
        // 集合元素总数 - 1
        size--;
        // 修改次数 + 1
        modCount++;
        // 返回删除元素(node)的内容(值)
        return element;
    }

         当我们直接调用 remove 方法的时候并不会像 clear 方法那样删除所有的元素(node),而是删除集合的首个元素,并返回删除元素(node)的内容(值)。

概览图:

remove(int index)
/*** 例子代码5 ***/

LinkedList<String> objects = new LinkedList<>();

objects.add("1");
objects.add("2");
objects.add("3");
        
objects.remove(1);
/*** LinkedList 类部分代码 ***/

/*** 指定删除第几个元素,并返回删除元素(node)的内容(值);index:集合的第几个元素 ***/ 
public E remove(int index) {
        // 检查index 是否在集合范围内
        checkElementIndex(index);
        // 这里调用了两个方法,node方法通过 index 找到删除的元素(node),并传入 unlink 方法做删除操作
        return unlink(node(index));
}

/*** 获取 index 相应位置的元素(node),和上面的 node 方法是同一个 ***/
Node<E> node(int index) {
         // 这里使用二分法的方式快速查找元素插入的位置的元素(node)
         // index 小于 size 的一半就从集合的头开始找插入的位置; >>:相当于 size 除于 2
        if (index < (size >> 1)) {
            // 那第一个元素(node),这里因为不能像数组那样可以通过下标随机获取元素(node),所以要遍历去拿到插入位置的元素(node),并返回插入位置的元素(node)
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            // index 大于 size 就从集合的尾开始找插入的位置,并返回插入位置的元素(node)
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

/*** 删除指定的元素(node),返回删除元素(node)的内容(值);x:删除的元素(node)***/
E unlink(Node<E> x) { 
        // assert x != null;
        // 获取删除元素(node)的内容(值),赋值给 element
        final E element = x.item;
        // 获取删除元素(node)的后一个元素(node),赋值给 next
        final Node<E> next = x.next;
        // 获取删除元素(node)的前一个元素(node),赋值给 prev
        final Node<E> prev = x.prev;
        // perv 等于 null 说明是集合的首个元素(node)
        if (prev == null) {
            // 就把要删除元素(node)的下一个元素(next)标记为首个元素(first)
            first = next;
        } else {
            // prev 不等于 null,说明删除的不是集合的首个一个元素(node)
            // 把删除元素(node)的上一个元素(node)与删除元素(node)的下一个元素做连接,将 next 赋值给 prev 的 next
            prev.next = next;
            // 把删除元素的指向前一个元素(node)的指针域清空,解除删除元素(node)与上一个元素(node)的连接
            x.prev = null;
        }
        // next 等于 null ,说明删除的元素(node)是集合的最后一个
        if (next == null) {
            // 把删除元素(node)的上一个元素(node)标记为最后一个元素(node)
            last = prev;
        } else {
            // next 不等于 null ,说明删除的不是集合的最后一个元素(node)
            // 把删除元素(node)的下一个元素(node)与 删除元素(node)的上一个元素连接,将 prev 赋值给 next 的 prev
            next.prev = prev;
            // 把删除元素的指向后一个元素(node)的指针域清空,解除删除元素(node)与下一个元素(node)的连接
            x.next = null;
        }
        // 清空删除元素(node)的数据域
        x.item = null;
        // 集合元素总数 - 1
        size--;
        // 修改次数 + 1
        modCount++;
        // 返回删除元素(node)的内容(值)
        return element;
    }
概览图:

remove(Object o)
/*** 例子代码6 ***/

LinkedList<Integer> objects = new LinkedList<>();

objects.add(1);
objects.add(2);
objects.add(3);

Integer removeItem = 3;

objects.remove(removeItem);
/*** LinkedList 类部分代码 ***/

/*** 根据内容(值)删除元素(node),成功返回 true,失败返回 false; o:删除元素(node)的内容(值)***/
public boolean remove(Object o) {
        // 因为 LinkedList 的元素内容是可以为 null,所以这里判断要删除的内容是否是 null
        if (o == null) {
            // 通过遍历寻找内容(值)为 null 的元素(node)
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    // 将找到的元素(node),作为参数传入 unlink 方法进行删除
                    unlink(x);
                    // 完成删除,返回true
                    return true;
                }
            }
        } else {
            // 删除元素(node)的内容(值)不是 null
            // 也是通过遍历去对比集合里元素(node)的内容(值),来找到相应的元素(node)
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    // 将找到的元素(node),作为参数传入 unlink 方法进行删除
                    unlink(x);
                    // 完成删除,返回true
                    return true;
                }
            }
        }
        // 如果没有找到相应的内容的元素(node),删除失败返回 false
        return false;
    }

/*** 删除指定的元素(node),返回删除元素(node)的内容(值);x:删除的元素(node);和上面的 unlink 方法是同一个 ***/
E unlink(Node<E> x) { 
        // assert x != null;
        // 获取删除元素(node)的内容(值),赋值给 element
        final E element = x.item;
        // 获取删除元素(node)的后一个元素(node),赋值给 next
        final Node<E> next = x.next;
        // 获取删除元素(node)的前一个元素(node),赋值给 prev
        final Node<E> prev = x.prev;
        // perv 等于 null 说明是集合的首个元素(node)
        if (prev == null) {
            // 就把要删除元素(node)的下一个元素(next)标记为首个元素(first)
            first = next;
        } else {
            // prev 不等于 null,说明删除的不是集合的首个一个元素(node)
            // 把删除元素(node)的上一个元素(node)与删除元素(node)的下一个元素做连接,将 next 赋值给 prev 的 next
            prev.next = next;
            // 把删除元素的指向前一个元素(node)的指针域清空,解除删除元素(node)与上一个元素(node)的连接
            x.prev = null;
        }
        // next 等于 null ,说明删除的元素(node)是集合的最后一个
        if (next == null) {
            // 把删除元素(node)的上一个元素(node)标记为最后一个元素(node)
            last = prev;
        } else {
            // next 不等于 null ,说明删除的不是集合的最后一个元素(node)
            // 把删除元素(node)的下一个元素(node)与 删除元素(node)的上一个元素连接,将 prev 赋值给 next 的 prev
            next.prev = prev;
            // 把删除元素的指向后一个元素(node)的指针域清空,解除删除元素(node)与下一个元素(node)的连接
            x.next = null;
        }
        // 清空删除元素(node)的数据域
        x.item = null;
        // 集合元素总数 - 1
        size--;
        // 修改次数 + 1
        modCount++;
        // 返回删除元素(node)的内容(值)
        return element;
    }

        我们可以看到不管传入的是什么类型的参数,都会调用它们重写的 equal 方法与元素(node)的内容(值)比较来找到对应的元素(node)。

修改元素(node):

/*** 例子代码7 ***/

LinkedList<String> objects = new LinkedList<>();
objects.add("1");
objects.add("2");
objects.add("3");

objects.set(1,"6");
/*** LinkedList 类部分代码 ***/

/*** 修改指定位置元素(node)的内容(值),并返回旧元素(node)的内容;index:指定元素(node)的位置,element:修改的内容 ***/
public E set(int index, E element) {
        // 检查 index 是否在集合范围内
        checkElementIndex(index);
        // node方法通过 index 找到修改的元素(node)
        Node<E> x = node(index);
        // 修改前把旧元素(node)内容(值),赋值给 oldVal 变量存储,以便后续返回
        E oldVal = x.item;
        // 将新的内容(值),赋值给修改的元素(node)
        x.item = element;
        // 返回旧的内容
        return oldVal;
    }

/*** 获取 index 相应位置的元素(node),和上面的 node 方法是同一个 ***/
Node<E> node(int index) {
         // 这里使用二分法的方式快速查找元素插入的位置的元素(node)
         // index 小于 size 的一半就从集合的头开始找插入的位置; >>:相当于 size 除于 2
        if (index < (size >> 1)) {
            // 那第一个元素(node),这里因为不能像数组那样可以通过下标随机获取元素(node),所以要遍历去拿到插入位置的元素(node),并返回插入位置的元素(node)
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            // index 大于 size 就从集合的尾开始找插入的位置,并返回插入位置的元素(node)
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

 如有错误还请大家多多指出,让我们共同进步,谢谢。能帮助到你的话也请动动发财的小手帮忙点个赞,感谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值