LinkedList源码分析及总结

Java LinkedList 源码分析及总结

1、LinkedList 集合简介

在这里插入图片描述
(1)LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的。
(2)LinkedList 实现 List 接口,能对它进行队列操作
(3)LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
(4)LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
(5)LinkedList 实现Cloneable 表明其可以调用clone()方法来返回实例的field-for-field拷贝
(6)实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
(7)LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法:
List list=Collections.synchronizedList(new LinkedList(…));

2、类的属性

先看LinkedList 底层双向链表结构,该类属于LinkedList 的一个静态内部类

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

LinkedList的关键属性

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

LinkedList的属性非常简单,一个头结点、一个尾结点、一个表示链表中实际元素个数的变量。注意,头结点、尾结点都有transient关键字修饰,这也意味着在序列化时该域是不会序列化的。

3、构造方法

(1)无参构造

 public LinkedList() {
    }

(2)带Collections有参构造


   //将集合c中的各个元素构建成LinkedList链表。
    public LinkedList(Collection<? extends E> c) {

     // 调用无参构造函数
        this();
        // 添加集合中所有的元素
        addAll(c);
}

4、LinkedList 常见方法

4.1 添加方法

(1)add(E e)

/**
     * 将元素添加到链表尾部
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    
    /**
     * 在表尾插入指定元素e
     */
    void linkLast(E e) {
        final Node<E> l = last;
        //新建节点newNode,节点的前指针指向l,后指针为null
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        //如果原来的尾结点为null,更新头指针,否则使原来的尾结点l的后置指针指向新的头结点newNode
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

(2)add(int index, E element)

/**
     * 在指定位置添加元素
     */
    public void add(int index, E element) {
        //检查索引是否处于[0-size]之间
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

()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) {
        //1:检查index范围是否在size之内
        checkPositionIndex(index);

        //2:toArray()方法把集合的数据存到对象数组中
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        //3:得到插入位置的前驱节点和后继节点
        Node<E> pred, succ;
        //如果插入位置为尾部,前驱节点为last,后继节点为null
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            //否则,调用node()方法得到后继节点,再得到前驱节点
            succ = node(index);
            pred = succ.prev;
        }

        // 4:遍历数据将数据插入
        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;
        }
        //如果插入位置在尾部,重置last节点
        if (succ == null) {
            last = pred;
        }//否则,将插入的链表与先前链表连接起来
        else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

(4)addFirst(E e)

/**
     * 在表头插入指定元素.
     */
    public void addFirst(E e) {
        linkFirst(e);
    }
    
    /**
     * 在表头添加指定元素e
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        //新建节点,节点的前指针指向null,后指针原来的头节点
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        //如果原来的头结点为null,更新尾指针,否则使原来的头结点f的前置指针指向新的头结点newNode
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

4.2 获取方法

(1)get(int index)

/**
     * 返回指定索引处的元素
     */
    public E get(int index) {
        //检查index范围是否在size之内
        checkElementIndex(index);
        //调用node(index)去找到index对应的node然后返回它的值
        return node(index).item;
    }
    
     /**
     * 返回在指定索引处的非空元素
     */
    Node<E> node(int 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--)
                x = x.prev;
            return x;
        }
    }

关键在于node()方法:

/**
     * Returns the (non-null) Node at the specified element index.
     */
//这里查询使用的是先从中间分一半查找
    Node<E> node(int index) {
        // assert isElementIndex(index);
//"<<":*2的几次方 “>>”:/2的几次方,例如:size<<1:size*2的1次方,
//这个if中就是查询前半部分
         if (index < (size >> 1)) {//index<size/2
            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--)
                x = x.prev;
            return x;
        }
    }

(2)获取头节点(index=0)数据方法

/**
     * 返回链表中的头结点的值.
     */
    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
    
    /**
     * 获取表头节点的值,头节点为空抛出异常
     */
    public E element() {
        return getFirst();
    }
    
     /**
     * 返回头节点的元素,如果链表为空则返回null
     */
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
    
    /**
     * 返回队列的头元素,如果头节点为空则返回空
     */
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }

区别:
getFirst(),element(),peek(),peekFirst() 这四个获取头结点方法的区别在于对链表为空时的处理,是抛出异常还是返回null。

4.3 删除方法

(1)remove(Object o) 删除指定元素

/**
     * 正向遍历链表,删除出现的第一个值为指定对象的节点
     */
    public boolean remove(Object o) {
        //LinkedList允许存放Null
        //如果删除对象为null
        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;
    }

(2)remove() ,removeFirst(),pop(): 删除头节点

 /**
     * 删除并返回栈头元素
     */
    public E pop() {
        return removeFirst();
    }
    
    /**
     * 删除并返回头节点,如果链表为空,抛出异常
     */
    public E remove() {
        return removeFirst();
    }
    
    /**
     *  删除并返回表头元素.
     */
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

(3)removeLast(),pollLast(): 删除尾节点

/**
     * 删除并返回表尾元素
     */
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
    
    /**
     * 删除并返回队列的最后个元素,如果尾节点为空,则返回null.
     */
    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

5、分析总结

要点:
  (1)linkedList本质上是一个双向链表,通过一个Node内部类实现的这种链表结构
  (2)能存储null值
  (3)LinkedList最大的好处在于头尾和已知节点的插入和删除时间复杂度都是o(1)。但是涉及到先确定位置再操作的情况,则时间复杂度会变为o(n)。
当然,每个节点都需要保留prev和next指针也是经常被吐槽是浪费了空间。
  (4)通过下标获取某个node 的时候,(add select),会根据index处于前半段还是后半段 进行一个折半,以提升查询效率
  (5)linkedList不光能当链表,还能当队列使用,这个就是因为实现了Deque接口。
 
  ArrayList和LinkedList的区别
  (1)ArrayList是实现了基于动态数组的数据结构,而LinkedList是基于链表的数据结构
  (2)对于随机访问的get和set,ArrayList要优于LinkedList,因为LinkedList基于指针的移动
  (3)当插入的数据量很小时,两者区别不太大,当插入的数据量大时,大约在容量的1/10之前,LinkedList会优于ArrayList,在其后就劣与ArrayList,且越靠近后面越差。所以个人觉得,一般首选用ArrayList,由于LinkedList可以实现栈、队列以及双端队列等数据结构,所以当特定需要时候,使用LinkedList,当然咯,数据量小的时候,两者差不多,视具体情况去选择使用;当数据量大的时候,如果只需要在靠前的部分插入或删除数据,那也可以选用LinkedList,反之选择ArrayList反而效率更高。

发布了19 篇原创文章 · 获赞 11 · 访问量 257

猜你喜欢

转载自blog.csdn.net/qqq3117004957/article/details/104530317