LinkedList底层实现

一、什么是LinkedList

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

根据源码我们可以看出:

  • ①LinkedList继承了AbstractSequentialList,即按次序的List,也就是说不像ArrayList一样具有随机访问元素的能力,也就是说LinkedList支持按顺序访问列表中元素的操作。
  • ②实现了List接口,可进行列表相关操作
  • ③实现了Deque接口,可以将LinkedList当作双端队列进行使用
  • ④实现Clonalbe接口,即覆盖了clone()方法,可以进行克隆操作
  • ⑤实现了java.io.Serializable接口,可以进行序列化,即支持网络传输

LinkedList的本质是双向链表,与 ArrayList 相比,LinkedList 的增加和删除对操作效率更高,而查找和修改的操作效率较低。

二、LinkedList的创建

1、成员变量及数据结构

①transient int size = 0; //LinkedList存放元素个数

②first是双向链表的表头,last指向双向链表的最后一个元素
transient Node first;
transient Node last;

③Node数据结构

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

存放数据元素,prev指向上一个节点,next指向下一个节点,item为元素值

2、first和last为什么要用transist修饰?

在ArrayList中存放元素的数据结构elementData使用transisit修饰是为了节省空间,防止扩容后数组中的空数据也被序列化。
而LinkedList使用transist修饰是因为LinkedList中使用双向链表保存数据,结点中保存前驱和后继的引用。但是序列化之后前序结点和后继结点的地址都变了,所以我们应该将数据传输过去再在另一边链接为新的链表。

LinkedList的序列化与反序列化方法:

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException {
    // Write out any hidden serialization magic
    s.defaultWriteObject();

    // Write out size
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (Node<E> x = first; x != null; x = x.next)
        s.writeObject(x.item);
}

@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in any hidden serialization magic
    s.defaultReadObject();

    // Read in size
    int size = s.readInt();

    // Read in all elements in the proper order.
    for (int i = 0; i < size; i++)
        linkLast((E)s.readObject());
}

3、LinkedList的创建

LinkedList list = new LinkedList(); // 普通创建方法
或者
LinkedList list = new LinkedList(Collection<? extends E> c); // 使用集合创建链表

使用集合创建链表步骤

  • ①调用addAll( c )方法,而addAll( c )底层调用重写方法addAll(size, c)
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

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

    Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew == 0)
        return false;

    Node<E> pred, succ;
    if (index == size) {
        succ = null;
        pred = last;
    } else {
        succ = node(index);
        pred = succ.prev;
    }

    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        pred = newNode;
    }

    if (succ == null) {
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;
    }

    size += numNew;
    modCount++;
    return true;
}
  • ②addAll方法首先通过checkPositionIndex(index);判断index是否越界,如果越界报越界异常。
  • ③如果Collection为空直接return false
  • ④如果Collection不为空,定义两个指针pred,和succ;如果index等于size,那么说明是在链表末尾插入Collection,使得pred指向last
  • ⑤如果index不等于size,那么一定小于size,使用node(index)方法获得index处的node,是pred指向该node的上一个节点
  • ⑥遍历Collection中的元素,将元素一个个插入进LinkedList中。
    在这里插入图片描述
    在这里插入图片描述
  • ⑦最后,若succ为空,说明是在链表尾部插入的,更新last,若不为空,则是在链表中间插入的,执行操作,补全链表

三、LinkedList集合的各种插入删除操作

在前面我们说过,LinkedList可以当作队列也能作为栈,所以linkedList中有许多插入删除的方法。

链表尾部插入数据操作

方法返回值
public boolean add(E e)调用linkLast()方法,调用完返回true
public void addLast(E e)调用linkLast()方法,无返回值
public boolean offer(E e)调用add()方法,返回是否插入成功
public boolean offerLast(E e)调用addLast()方法,调用完返回true

链表头部插入数据操作

方法返回值
public void addFirst(E e)调用linkFirst()方法
public boolean offerFirst(E e)调用addFirst()方法,调用完返回true
public void push(E e)调用addFirst()方法

删除并返回链表尾部的数据

方法返回值
public E removeLast()判断最后一个元素是否存在,存在调用unlinkLast()方法,不存在抛NoSuchElementException()异常,并返回删除元素的值。

删除并返回链表头部的数据

方法返回值
public E poll()判断first是否为空,不为空的话调用unlinkFirst()方法,返回值为删除节点的数据
public E remove()调用removeFirst()方法
public E removeFirst()判断第一个元素是否存在,存在调用unlinkLast()方法,不存在抛NoSuchElementException()异常,并返回删除元素的值。
public E pop()调用removeFirst()方法

获取第一个元素的值

方法返回值
public E element()调用getFirst()方法,返回链表first节点的元素值
public E getFirst()判断first节点是否为空,为空的话抛出NoSuchElementException()异常,否则返回first的元素值
public E peek()first为空返回null,否则返回first的元素值
public E peekFirst()与peek()方法一样

获取最后一个元素的值

方法返回值
public E getLast()判断last节点是否为空,为空的话抛出NoSuchElementException()异常,否则返回last的元素值
public E peekLast()last为空返回null,否则返回last的元素值

在任意位置插入数据:add(int index, E element)
删除任意位置数据:remove(int index)
获取任意位置的数据 : get(int index)

特别注意,作为如果使用linkedList作为栈,那么以链表头部作为栈进出的位置。

四、LinkedList的遍历

在ArrayList中已经讲过,LinkedList不支持快速随机访问,所以遍历时采用迭代器遍历更快一些
要理解这个,首先要明白linkedList是一个链表,不支持随机访问,所以linkedList要得到一个元素,即get(i)的时间复杂度时o(n),所以使用以下的遍历方式,时间复杂度就会变为o(n^2)了

for (int i=0; i<size; i++) {
    list.get(i);        
}

而我们可以看到linkedList实现的iterator迭代器就是一个一个从first开始遍历到index大于size为止,所以获取数据的时间复杂度为o(1)
在这里插入图片描述
所以使用LinkedList遍历推荐使用iterator迭代器

List<Integer> list = new LinkedList<>();
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
    iterator.next();
}

五、为什么官方推荐使用LinkedList作为栈

当初 JDK1.0 在开发时,可能为了快速的推出一些基本的数据结构操作,所以推出了一些比较粗糙的类。比如,Vector、Stack、Hashtable等。这些类在之前的几个版本中,性能还不怎么好,而且其中的一些方法加上了 synchronized 关键字,进一步影响了效率!

基于 Vector 实现的栈 Stack。底层实际上还是数组,所以还是存在需要扩容。Vector 是由数组实现的集合类,它包含了大量集合处理的方法。而 Stack 之所以继承 Vector,是为了复用 Vector 中的方法,来实现进栈(push)、出栈(pop)等操作。这里就是 Stack 设计不好的地方,既然只是为了实现栈,不用链表来单独实现,而是为了复用简单的方法而迫使它继承 Vector,Stack 和 Vector 本来是毫无关系的。这使得 Stack 在基于数组实现上效率受影响,另外因为继承 Vector 类,Stack 可以复用 Vector 大量方法,这使得 Stack 在设计上不严谨。

Java 提供了 Deuqe。Deque 是继承自 Queue,而 Stack 是继承自 Vector。Java 中的 Deuqe,即“double ended queue”的缩写,是 Java 中的双端队列集合类型。Deque 具备普通队列 FIFO 的功能,同时它也具备了 Stack 的 LIFO 功能,并且保留了 push 和 pop 函数,所以使用起来应该是一点障碍都没有。而LinkedList实现了Deque接口,可以作为栈来使用,至于为什么不使用ArrayDeque则和Vector一样,当动态扩容时同样需要将数据拷贝到新数组中。

总结:在java.util.stack中,栈的底层是使用数组来实现的,数组初始大小为10。每当元素个数超出数组容量就扩展为原来的2倍,将原数组中的元素拷贝到新数组中,随着数组元素的增多,这种开销也越大。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值