Java集合之LinkedList

目录

LinkedList简介

LinkedList数据结构

LinkedList源码解析

LinkedList和ArrayList的比较


public class LinkedList extends AbstractSequentialList implements List, Deque, Cloneable, java.io.Serializable‘

LinkedList简介

  1. LinkedList继承于AbstractSequentialList的双向链表,它可以被当做堆,栈,队列或者双端队列进行操作。
  2. LinkedList实现了List接口,可以对它进行队列操作
  3. LinkedList实现了Deque接口,可以对它进行双端队列操作
  4. LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
  5. LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

ps:LinkedList是非同步的

LinkedList构造函数

// 默认构造函数
LinkedList()

// 创建一个LinkedList,保护Collection中的全部元素。
LinkedList(Collection collection)

AbstractSequentialList简介:

public abstract class AbstractSequentialList extends AbstractList

其中的抽象方法为: public abstract ListIterator listIterator(int index);

而且AbstractSequentialList实现了get(int index)、set(int index, E element)、add(int index, E element) 和 remove(int index)这些函数,如果我们想创建一个AbstractSequentialList的实现类,只需要重写并提供 listIterator() 和 size() 方法的实现即可。如果要实现不可修改的列表,只需要修改ListIterator接口的hasNext、next、hasPrevious、previous 和 index 方法即可。

LinkedList本质是双向链表。

LinkedList数据结构

LinkedList有一个内部类

   private static class Node {//内部类,定义了一个结点的内部结构,包含前驱,后继,和该节点的元素
        E item;
        Node next;
        Node prev;

        Node(Node prev, E element, Node next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

2两个构造函数

public LinkedList() {//无参构造,创建一个空的链表
    }
public LinkedList(Collection c) {//包含“集合”的构造函数:创建一个包含“集合”的LinkedList
        this();
        addAll(c);
    }

LinkedList源码解析

LinkedList是通过双向链表来实现的,所以它的顺序访问效率会很高,随机访问效率会很低。

类的成员变量

public class LinkedList
    extends AbstractSequentialList
    implements List, Deque, Cloneable, java.io.Serializable
{
    // 实际元素个数
    transient int size = 0;
    // 头结点
    transient Node first;
    // 尾结点
    transient Node last;
}    //将成员变量定义为transient,意味着这些变量不会被序列化

add方法

 public boolean add(E e) {//双向链表
        // 添加到末尾
        linkLast(e);
        return true;
    }
void linkLast(E e) {
        final Node l = last;//首先将尾结点保存到l中,属性是final
        final Node newNode = new Node<>(l, e, null);//生成一个新节点,前驱为l,后继为null
        last = newNode;//将新创建的结点设为尾结点
        if (l == null)//如果原来的尾结点为空,那么新生成的结点为头结点,否则将原来结点的后继指向新节点
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

addAll方法有两个重载函数

public boolean addAll(int index, Collection c) {//从某一位置开始将集合中的元素添加进去
        checkPositionIndex(index);//检查索引值是否合法

        Object[] a = c.toArray();//将集合转化为数组
        int numNew = a.length;//获取几个中元素的个数
        if (numNew == 0)//如果集合中元素个数为零,那么添加失败
            return false;

        Node pred, succ;
        if (index == size) {//如果插入位置为链表末尾,则后继为null,前驱为尾结点
            succ = null;
            pred = last;
        } else {
            succ = node(index);//如果插入位置为其他位置,那么该位置上的原始结点为新调整的后继结点
            pred = succ.prev;//原始结点的前驱结点为新调整的前驱结点
        }

        for (Object o : a) {//遍历数组
            @SuppressWarnings("unchecked") E e = (E) o;
            Node newNode = new Node<>(pred, e, null);
            if (pred == null)
                first = newNode;//表示在第一个元素之前插入(索引为0的结点),新节点为头结点
            else
                pred.next = newNode;//否则将新节点插入到前驱结点的下一个结点
            pred = newNode;//每一次都将前驱结点设置为一次循环操作的新节点
        }

        if (succ == null) {//如果此时后继结点为空,说明到了最后一个结点,将该结点的前驱设为尾结点
            last = pred;
        } else {//否则将每一个结点的前驱和后继关联起来
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;//修改实际元素个数
        modCount++;
        return true;
    }

LinkedList的遍历方式

由此可见,遍历LinkedList时,使用removeFist()或removeLast()效率最高。但用它们遍历时,会删除原始数据;若单纯只读取,而不删除,应该使用第3种遍历方式。
无论如何,千万不要通过随机访问去遍历LinkedList!

LinkedList和ArrayList的比较

首先LinkedList在指定位置插入元素远远快于ArrayList,源码比较

LinkedList的插入动作:

 public void add(int index, E element) {
//首先检查插入位置是否合理
        checkPositionIndex(index);
//如果插入位置等于集合大小,则在集合后面追加
        if (index == size)
            linkLast(element);
        else//否则就在指定位置之前插入,即在节点index之前插入元素element
            linkBefore(element, node(index));
    }
//由于LinkedList原理是双向链表,所以有前驱结点和后继节点
void linkBefore(E e, Node succ) {
        // assert succ != null;
//首先获得succ的前驱结点存到pred中
        final Node pred = succ.prev;
//新建一个结点,其前驱结点是pred,后继节点是succ
        final Node newNode = new Node<>(pred, e, succ);
//将succ的前驱结点执行新生成的节点
        succ.prev = newNode;
//如果pred为null,那么就将头结点赋值为新生成的节点
        if (pred == null)
            first = newNode;
        else//否则将pred的后继节点指向新生成的节点
            pred.next = newNode;
        size++;
        modCount++;
    }
//node寻找中有一个位置判断,如果插入位置在前半段则从0索引开始找,如果插入位置在后半段则从size-1索引开始找
Node node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

从中,我们可以看出:通过add(int index, E element)向LinkedList插入元素时。先是在双向链表中找到要插入节点的位置index;找到之后,再插入一个新节点
双向链表查找index位置的节点时,有一个加速动作若index < 双向链表长度的1/2,则从前向后查找; 否则,从后向前查找

ArrayList的插入动作:

public void add(int index, E element) {
//评估插入元素位置是否合理
        rangeCheckForAdd(index);
//确保数组的容量有当前元素个数加1那么大
        ensureCapacityInternal(size + 1);  // Increments modCount!!
//数组复制
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;//index索引的元素为插入元素
        size++;
    }

通过源码我们可以了解到,在指定位置插入元素时,LinkedList是通过二分法查找元素的位置,从而将指定元素插入到索引之前,然后修改指针即可;而ArrayList则是通过数组的赋值来插入元素,如果数组元素过多,那么耗时会很长,所以不如LinkedList速度快。

然而在随机访问时LinkedList的速度却不如ArrayList快,原因是LinkedList这时还是要通过二分法来查找元素的索引,如果元素个数较多,则耗时较长;而ArrayList直接通过索引值就可以找到指定位置的元素。

 

你可能感兴趣的:(Java重温)