Java集合学习笔记(四)—— LinkedList源码学习(jdk1.8)

LinkedList是什么

  • ArrayList类实现了可变的数组,保存所有元素,可根据索引位置对集合快速随机访问;但向指定的索引位置增删对象的速度较慢。
  • LinkedList类采用双向链表结构保存对象,允许null存在。每个元素分配的空间不必连续,向集合中增删对象效率高;但随机访问速度较慢。

在这里插入图片描述

1 LinkedList的继承关系

LinkedList 继承自 AbstractSequentialList 接口,同时了还实现了 Deque, Queue 接口,可以用来模拟堆栈,队列,双端队列结构。
在这里插入图片描述

2 数据结构 成员变量

在这里插入图片描述
源码

// 内部类
	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;
	    }
	}
// 成员变量	
	 //链表长度
    transient int size = 0;
    transient Node<E> first; //指向第一个节点的指针。
    transient Node<E> last; //指向最后一个节点的指针。
    public LinkedList(Collection<? extends E> c) { //构造方法
	    this();
	    addAll(c); //添加元素
	}
  • 使用双向链表实现.
  • 链表的每个节点使用内部类Node表示.
  • 通过first和last分别指向链表的第一个和最后一个元素.

3 构造方法

链表结构,初始化不需要指定长度。
在这里插入图片描述

	/**
     * 构造一个空列表。
     */
    public LinkedList() {
    }

    /**
     * 构造一个包含指定集合的​​元素的列表,其顺序由集合的迭代器返回。
     * @param  c 将其元素放入此列表的集合
     * @throws NullPointerException 如果指定的集合为null
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

4 特点

  • 元素可以为 null
  • 实现了Queue、Deque接口,可以用来模拟堆栈,队列,双端队列结构
  • 可以包含重复的元素
  • 非线程安全
  • 维护了元素插入时的顺序
  • 适合插入和删除操作 只用改变一下前后节点引用

常用方法

1 增

offer()add()peek()
在这里插入图片描述
在这里插入图片描述

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

offer()add()底层逻辑一致

1.1 追加尾部

add()addLatst()方法从尾部开始追加

	public boolean add(E e) {
	        linkLast(e);  // 将元素添加到链表末尾
	        return true;
	}

	//将e链接为最后一个元素
    void linkLast(E e) {
        final Node<E> l = last; // last 指向最后一个节点,在没有添加元素时为 null
        final Node<E> newNode = new Node<>(l, e, null); // 创建一个新节点,l是前节点,e是当前节点的值,后节点是null
        last = newNode;  // 新建的节点放在尾部
        if (l == null) 
            first = newNode; // 如果链表为空,头部和尾部是同一个节点,都是新建的节点
        if (l == null)
        else 
            l.next = newNode; // 如果链表不为空,将旧的尾节点的下一节点指向新添加的节点
        size++; //大小更改
        modCount++; //版本更改
    }

1.2 追加头部

addFirst()方法从头部开始追加

	public void addFirst(E e) {
        linkFirst(e);
    }
	// 从头部追加
    private void linkFirst(E e) {
        final Node<E> f = first; //指向头节点,赋值给f
        final Node<E> newNode = new Node<>(null, e, f); // 创建一个新节点,null是前节点,e是当前节点的值,后节点是f
        first = newNode; // 新建的节点放在头部
        if (f == null)
            last = newNode; // 如果链表为空,头部和尾部是同一个节点,都是新建的节点
        else
            f.prev = newNode;
        size++; //大小更改
        modCount++; //版本更改
    }

1.3 指定位置插入

add(int index, E element)addAll(int index, Collection<? extends E> c)方法从头部开始追加

	public void add(int index, E element) {
        checkPositionIndex(index);  // 检查插入位置的索引的合理性

        if (index == size)
            linkLast(element); // 插入的情况是尾部插入的情况:调用linkLast()
        else
            //在node(index)节点之前插入
            linkBefore(element, node(index)); // 插入的情况是非尾部插入的情况(中间插入):inkBefore()
    }
    
    // 检查插入位置的索引的合理性
    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }
    
	// 中间插入
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;  // 得到插入位置元素的前继节点
        final Node<E> newNode = new Node<>(pred, e, succ);  //创建一个新节点,pred是前节点,e是当前节点的值,后节点是succ
        succ.prev = newNode;  // 更新插入位置(succ)的前置节点为新节点
        if (pred == null)	       
            first = newNode;  // 如果pred为null,说明该节点插入在头节点之前,要重置first头节点 
        else	        
            pred.next = newNode; // 如果pred不为null,那么直接将pred的后继指针指向newNode即可
        size++; //大小更改
        modCount++; //版本更改
    }

addAll(int index, Collection<? extends E> c)

   public boolean addAll(int index, Collection<? extends E> c) {	    
        checkPositionIndex(index); // 1.添加位置的下标的合理性检查
	    
        Object[] a = c.toArray();  // 2.将集合转换为Object[]数组对象
        int numNew = a.length;
        if (numNew == 0)
            return false;
            
        Node<E> pred, succ;  // 3.得到插入位置的前继节点和后继节点         
        if (index == size) { // 特殊情况:当索引index=size时,通过node(index)找不到节点,超过范围    
	        // 从尾部添加的情况:前继节点是原来的last节点;后继节点是null
            succ = null;
            pred = last;
        } else {
           // 从指定位置(非尾部)添加的情况:前继节点就是index位置的节点,后继节点是index位置的节点的前一个节点
            succ = node(index); //找到第index个节点
            pred = succ.prev;
        }
				
        for (Object o : a) { // 4.遍历数据,将数据插入
            @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; // 如果是从尾部开始插入的,则把last置为最后一个插入的元素
        } else {
            pred.next = succ; // 如果不是从尾部插入的,则把尾部的数据和之前的节点连起来
            succ.prev = pred;
        }

        size += numNew;  // 链表大小+num
        modCount++;  // 修改次数加1
        return true;
    }

2 删

remove()poll()
在这里插入图片描述
在这里插入图片描述

	// remove()   
	public E remove() {
	  return removeFirst();
	}
	public E removeFirst() {
	  final Node<E> f = first;
	  if (f == null)
	    throw new NoSuchElementException();
	  return unlinkFirst(f);
	}
	public boolean removeFirstOccurrence(Object o) {
        return remove(o);
    }
	
	// poll(e)
	public E poll() {
	  final Node<E> f = first;
	  return (f == null) ? null : unlinkFirst(f); // 链表为空时,poll返回null
	}

几种删除方法底层逻辑一致,都是通过unlinkFirst()实现的,区别在于空链表时,remove()抛出异常,poll()返回null值。

2.1 删除尾部

removeLast()pollLast() 方法从尾部开始删除

	public E removeLast() { //从此列表中删除并返回最后一个元素。
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }

    private E unlinkLast(Node<E> l) { // 取消链接非空的最后一个节点l。
        // assert l == last && l != null;
        final E element = l.item; // 得到指定节点的值
        final Node<E> prev = l.prev; // 得到指定节点的前继节点
        l.item = null; // 删除指定节点内容
        l.prev = null; // 删除指定节点的前继节点,置为null
        last = prev; // 链表的尾节点设置为指定节点的前继节点
        if (prev == null) // 如果prev为null,则表示删除的是头部节点,否则就不是头部节点
            first = null; // 链表头部节点置空
        else
            prev.next = null; // 否则,指定节点的前继节点指向空
        size--; // 数量减1
        modCount++;
        return element;
    }

2.2 删除头部

remove()removeFirst()poll()pollFirst() 方法从头部开始删除

	public E removeFirst() { //从此列表中删除并返回第一个元素。
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    
    private E unlinkFirst(Node<E> f) { // 取消链接非空的第一个节点f。
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

2.3 删除指定元素

remove(int index)remove(Object o)removeFirstOccurrence(Object o)removeLastOccurrence(Object o)方法删除指定元素,通过unlink()方法完成删除操作。

unlink()unlinkFirst()类似,区别在于unlink()可以删除中间节点,unlinkFirst()只删除头节点。

remove(int index) :根据索引删除

    public E remove(int index) { //获取并移除此列表的头(第一个元素)。
        checkElementIndex(index); // 移除元素索引的合理性检查
        return unlink(node(index));
    }
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item; // 得到指定节点的值
        final Node<E> next = x.next; // 得到指定节点的后继节点
        final Node<E> prev = x.prev; // 得到指定节点的前继节点

        if (prev == null) { // 如果prev为null表示删除是头节点,否则就不是头节点
            first = next; 
        } else {
            prev.next = next;
            x.prev = null; // 指定节点的前继节点置空
        }

        if (next == null) { // 如果next为null,则表示删除的是尾部节点,否则就不是尾部节点
            last = prev;
        } else {
            next.prev = prev;
            x.next = null; // 指定节点的后继节点置空
        }

        x.item = null; // 置空需删除的指定节点的值
        size--; // 数量减1
        modCount++;
        return element;
    }    

remove(Object o)removeFirstOccurrence(Object o)底层逻辑一致,从头部开始寻找指定元素,并移除:

    public boolean removeFirstOccurrence(Object o) {
        return remove(o);
    }
	public boolean remove(Object o) {
        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;
    }

removeLastOccurrence(Object 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;
    }

2.4 全删

clear():清空链表。

    public void clear() {  // 清空链表
        // 清除节点之间的所有链接是“不必要的”,但是:如果丢弃的节点居住了多个世代,即使有一个可达的Iterator,也可以确保释放内存,这有助于世代GC
        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++;
    }

3 查

在这里插入图片描述
在这里插入图片描述

3.1 通过索引 get() 随机访问

get(int index):获取指定位置上的元素
element():获取但不移除此列表的头(第一个元素)
getFirst():获取第一个元素(最早添加的元素)
getLast():获取最后一个元素(最晚添加的元素)

	public E get(int index) {
        checkElementIndex(index); // 元素下标的合理性检查
        return node(index).item; // node(index)真正查询匹配元素并返回
    }
	/**
     * 返回指定元素索引处的(非空)节点。
     */
    //时间复杂度为n/2
    Node<E> node(int index) {
        // assert isElementIndex(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;
        }
    }

	//得到头部元素
	public E element() {
	  return getFirst();
	}
	
	//得到头部元素
	public E getFirst() {
	  final Node<E> f = first;
	  if (f == null)
	    throw new NoSuchElementException();
	  return f.item;
	}
			
    //得到尾部元素
    public E getLast() { 
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

3.2 通过索引 peek() 访问

get()源码差不多,底层逻辑一致,区别在于peek() 能查到并返回null,而get()会抛出异常。

	// 返回头元素,并且不删除。如果不存在,返回null
   public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
    
    // 返回头元素,并且不删除。如果不存在,返回null
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }
     
    // 返回尾元素,如果为null,则就返回null
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

3.3 indexOf 直接查找访问

	// 返回元素在链表中的索引,如果不存在则返回-1
	public int indexOf(Object o) {
        int index = 0;
        if (o == null) { // 返回元素在链表中的索引,如果不存在则返回-1
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else { // 返回元素在链表中的索引,如果不存在则返回-1
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }

    public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }

4 改

4.1 转化成数组

在这里插入图片描述

    /**
     * 以正确的顺序(从第一个元素到最后一个元素)返回包含此列表中所有元素的数组。
     *
     * <p>返回的数组将是“安全的”,因为此列表不保留对其的引用。(换句话说,此方法必须分配一个新数组)。 因此,调用者可以自由修改返回的数组。
     *
     * <p>此方法充当基于数组的API和基于集合的API之间的桥梁。
     */
    public Object[] toArray() {
        Object[] result = new Object[size];
        int i = 0;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item; //按顺序存入列表节点值
        return result;
    }
    
    /**
     * 返回一个数组,该数组按正确的顺序包含此列表中的所有元素(从第一个元素到最后一个元素); 返回数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则将其返回。 否则,将使用指定数组的运行时类型和此列表的大小分配一个新数组。
     */
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
         	//为什么用到反射==》泛型类在运行时无法new实例化,只能在运行时得到class对象
            a = (T[])java.lang.reflect.Array.newInstance(
                                a.getClass().getComponentType(), size);
        int i = 0;
        Object[] result = a;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;

        if (a.length > size)
            a[size] = null;

        return a;
    }

4.2 直接替换

在这里插入图片描述

    public E set(int index, E element) { // 用指定的元素替换此列表中指定位置的元素。
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item; // 将指定位置的元素值赋给oldVal
        x.item = element;
        return oldVal;
    }

队列操作

具体的操作分别对应在查询、增、删操作中,详见本文增、删、查操作具体内容。
在这里插入图片描述
在这里插入图片描述

	//取队列头节点,但不删除
	//若first为空时,不抛出错误
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

    //若first为空时,抛出错误
    public E element() {
        return getFirst();
    }

    //出队列(头删)
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

	//入队列(尾插)
    public boolean offer(E e) {
        return add(e);
    }

栈操作

在这里插入图片描述
出入栈操作:

	//入栈,头部插入
    public void push(E e) {
        addFirst(e); // addFirst()方法详见增加操作1.2追加头部
    }

    //出栈,头部删除
    public E pop() {
        return removeFirst(); //removeFirst()详见删除操作2.2 删除头部
    }

迭代器实现

深入理解Java中的迭代器
关于快速报错fail-fast想说的之fail-fast的实现原理(一)
在这里插入图片描述
ListIterator接口相较于传统Iterator的区别在于:ListIterator可以实现双向的迭代访问
ListItr实现ListIterator接口:

	//从列表中的指定位置开始(按正确顺序)返回此列表中元素的列表迭代器。
    public ListIterator<E> listIterator(int index) {
        checkPositionIndex(index); // 检查索引合理性
        return new ListItr(index);
    }

    private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned; // 上一次返回的next或previos 的节点
        private Node<E> next; //下一个节点
        private int nextIndex;
        private int expectedModCount = modCount; //expectedModCount:期望版本号;modCount:当前版本号

        ListItr(int index) {
            // assert isPositionIndex(index);
            next = (index == size) ? null : node(index); // 当前节点赋值给next 
            nextIndex = index;
        }

// 从头到尾的迭代,获取当前节点的下一节点
        public boolean hasNext() { // 判断还有没有下一个元素
            return nextIndex < size;
        }

        public E next() { // 获取下一个元素
            checkForComodification(); // 检查版本号
            if (!hasNext())
                throw new NoSuchElementException();

            lastReturned = next; // lastReturned赋值为当前节点
            next = next.next; // next赋值下一个节点,为下次迭代做准备
            nextIndex++;
            return lastReturned.item; //返回此次所需返回的节点的值
        }

// 从尾到头的迭代,获取当前节点的上一节点
        public boolean hasPrevious() { // 判断还有没有上一个元素
            return nextIndex > 0;
        }

        public E previous() { // 获取上一个元素
            checkForComodification(); // 检查版本号
            if (!hasPrevious())
                throw new NoSuchElementException();

			// next为空,说明是第一次迭代,取尾节点(last)
			// next不为空,说明已经发生过迭代了,取前一个节点即可(next.prev)
            lastReturned = next = (next == null) ? last : next.prev;
            nextIndex--;
            return lastReturned.item;
        }

        public int nextIndex() {
            return nextIndex;
        }

        public int previousIndex() {
            return nextIndex - 1;
        }

        public void remove() {
            checkForComodification();
            if (lastReturned == null) // 未检查是否还有节点就remove,lastReturned返回初始null值
                throw new IllegalStateException();

            Node<E> lastNext = lastReturned.next;
            unlink(lastReturned); // 删除当前节点
            // 想要满足next == lastReturned,只有一种情况,就是目前是以从尾到头的方式进行迭代的,这时候由于lastReturned节点删除了,会被GC回收,就需要把next指向lastReturned.next位置,这样进行下一次previous时,才可以找到lastReturned.prev
            if (next == lastReturned) // 从尾到头迭代时,lastReturned被删除回收时发生
                next = lastNext;
            else
                nextIndex--;
            lastReturned = null;
            expectedModCount++;
        }

        public void set(E e) {
            if (lastReturned == null)
                throw new IllegalStateException();
            checkForComodification();
            lastReturned.item = e;
        }

        public void add(E e) {
            checkForComodification();
            lastReturned = null;
            if (next == null)
                linkLast(e);
            else
                linkBefore(e, next);
            nextIndex++;
            expectedModCount++;
        }

        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            while (modCount == expectedModCount && nextIndex < size) {
                action.accept(next.item);
                lastReturned = next;
                next = next.next;
                nextIndex++;
            }
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

总结

LinkedList和ArrayList的区别

结构

  • ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表结构。
  • LinkedList的占用的内存空间较大,需要创建node对象,需要建立前后关联
  • LinkedList集合不支持 高效的随机随机访问(Random Access),因为可能产生二次项的行为,而ArrayList支持随机访问且速度较快。

速度 :

  • LinkedList做插入、删除的时候,慢在寻址,快在只需要改变前后Entry的引用地址,故而,LinkedList修改删除较快,查询较慢。
  • ArrayList做插入、删除的时候,慢在数组元素的批量copy,快在寻址,故而,ArrayList修改删除较慢,查询较快。

线程安全性
ArrayList安全,LinkedList不安全 https://blog.csdn.net/qq_36966591/article/details/88820769

选择

一般,查询用ArrayList,增删用LinkedList。另外:

1、如果十分确定插入、删除的元素是在前半段,使用LinkedList

2、如果十分确定插入、删除的元素后半段,使用ArrayList

3、如果上面的两点不确定,建议使用LinkedList

说明:其一、LinkedList整体插入、删除的执行效率比较稳定,没有ArrayList这种越往后越快的情况;其二、插入元素的时候,弄得不好ArrayList就要进行一次扩容,而ArrayList底层数组扩容是一个既消耗时间又消耗空间的操作,所以综合来看就知道选择哪个类型的list。

LinkedList和ArrayList的比较

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值