Java 集合学习笔记:LinkedList

Posted 笑虾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 集合学习笔记:LinkedList相关的知识,希望对你有一定的参考价值。

Java 集合学习笔记:LinkedList

UML

简介

List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列双端队列

此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。

所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。

注意,此实现不是同步的。如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须 保持外部同步。(结构修改指添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法来“包装”该列表。最好在创建时完成这一操作,以防止对列表进行意外的不同步访问,如下所示:

List list = Collections.synchronizedList(new LinkedList(...));

此类的 iterator 和 listIterator 方法返回的迭代器是快速失败 的:在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。

注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何硬性保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。

阅读源码

访问修饰符&返回类型方法描述
booleanadd(E e)将指定元素添加到此列表的结尾。
voidadd(int index, E element)在此列表中指定的位置插入指定的元素。
booleanaddAll(Collection<? extends E> c)添加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序。
booleanaddAll(int index, Collection<? extends E> c)将指定 collection 中的所有元素从指定位置开始插入此列表。
voidaddFirst(E e)将指定元素插入此列表的开头。
voidaddLast(E e)将指定元素添加到此列表的结尾。
booleanoffer(E e)将指定元素添加到此列表的末尾(最后一个元素)。
直接调 add(E e)
booleanofferFirst(E e)在此列表的开头插入指定的元素。
内部调 addFirst(e) 成功返回 true
booleanofferLast(E e)在此列表末尾插入指定的元素。
内部调 addLast(e) 成功返回 true
voidpush(E e)将元素推入此列表所表示的堆栈。
直接调 addFirst(e)

boolean add(E e)

  • test
@Test
public void addTest()
    LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "D", "E"));
    list.add("XYZ");
    log.info(list.toString()); // [A, B, C, D, E, XYZ]

  • add(E e):将指定元素添加到此列表的结尾。
public boolean add(E e) 
    linkLast(e);
    return true;

  • linkLast(E e):链接 e 作为最后一个元素。
    void linkLast(E e) 
    	// 临时缓存末尾节点
        final Node<E> l = last; 
        // 创建新节点,亲节点插在末尾,所以它后面为 null
        final Node<E> newNode = new Node<>(l, e, null); 
        // 更新末尾节点指针变量
        last = newNode;
        // 如原末尾节点就是 null 说明,列表是空的。头节点也指向新节点。
        // 否则:原节点的上一个节点更新 next 指向新节点
        if (l == null)
            first = newNode; 
        else
            l.next = newNode; 
        size++; // 插入节点成功,长度++
        modCount++; // 插入节点成功,修改计数++
    
  • Node<E>:节点类,私有。
    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;
        
    

void add(int index, E element)

  • test
@Test
public void addIndexTest()
    LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "D", "E"));
    list.add(2, "XYZ");
    log.info(list.toString()); // [A, B, XYZ, C, D, E]

  • 插入流程
  1. 找到 index 对应的目标节点 R
  2. R 与它的前一节点 L 断开。
  3. 创建新节点 newNode。使 newNode.prev 指向 LnewNode.next 指向 R
  4. 更新R节点指针:R.prev 指向 newNode
  5. 更新L节点指针:
    5.1. 如果节点 L 为 null,表示我们插入在了头部,更新头节点指针 first = newNode
    5.2. 否则 L.next 指向 newNode
  • add(int index, E element)
    在列表的 index 处插入 element
    public void add(int index, E element) 
        checkPositionIndex(index); // 检查 index 如果越界就抛锅
        if (index == size) // 如果是末尾
            linkLast(element); // 直接连接
        else
        	// 否则 在非空 index 节点之前插入元素 element。
            linkBefore(element, node(index)); 
    

  • checkPositionIndex(int index)
    索引检测,如果越界就抛锅。
    private void checkPositionIndex(int index) 
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    
  • isPositionIndex(int index)
    索引在正常范围内,返回 true
    private boolean isPositionIndex(int index) 
    	// index = 0 表示头节点。
    	// index = size 表示尾节点的 next。也就是末尾追加元素的位置。
        return index >= 0 && index <= size;
    
  • linkLast(E e):将元素 e 作为最后一个节点,连接到链表。
  • node(int index) 按索引获取节点。返回指定元素索引处的(非空)Node。
    Node<E> node(int index) 
		// size / 2 找出中间值,index 小于它说明靠左,从头开始找,否则从末尾开始找。
        if (index < (size >> 1)) 
        	// 先拿到头节点
            Node<E> x = first;
            // 从前向后找,直到遇上 index 的前一个节点,取它的 next 返回。
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
         else 
        	// 先拿到尾节点
            Node<E> x = last;
            // 从后向前找,直到遇上 index 的后一个节点,取它的 prev 返回。
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        
    

  • linkBefore(E e, Node<E> succ):在非空节点 succ 之前插入元素 e
    void linkBefore(E e, Node<E> succ) 
        // 断言 succ != null; add 开头有检测过 index 有效,肯定能找到一个节点。
        // 获取【原节点 succ】的前一节点。
        final Node<E> pred = succ.prev; 
        // 创建存放【元素e】的新节点,同时指定其【前、后】指针。
        final Node<E> newNode = new Node<>(pred, e, succ);
        // 更新【原节点 succ】的 prev 指针,指向新节点。
        succ.prev = newNode;
        // 如果【原节点 succ】的 prev 为 null 说明它当时就是头节点,
        // 现在新节点插入在原节点前面,所以更新头节点指针,指向新插入的节点。
        if (pred == null)
            first = newNode;
        // pred.next 本来指向【原节点 succ】,现在要更新为指向新插入节点。
        else
            pred.next = newNode;
        // 插入成功,更新列表长度。
        size++;
        // 插入成功,更新修改计数。
        modCount++;
    

addAll(Collection<? extends E> c)

  • test
@Test
public void addAllTest()
    LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "D", "E"));
    list.addAll(Arrays.asList("1","2","3"));
    log.info(list.toString()); // [A, B, C, D, E, 1, 2, 3]

将集合 c 原样插入当前列表末尾。

    public boolean addAll(Collection<? extends E> c) 
    	// 尾节点的索引是 size - 1。在它后面插入就是 size
        return addAll(size, c);
    

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

  • test
@Test
public void addAllIndexTest()
    LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "D", "E"));
    list.addAll(2, Arrays.asList("1","2","3"));
    log.info(list.toString()); // [A, B, 1, 2, 3, C, D, E]

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

将指定集合 c 中的所有元素从指定位置 index 开始插入到此列表中。此列表中 index 及其后的所有元素,向后挪。

checkPositionIndex 索引检测,如果越界就抛锅。

	// 向 ----- 索引2处插入 ===
	// 断开为 -- 和 ---
	// 插入 -- === ---
	// 更新连接处各节点的 prev、next 指针
	// 完成插入 --===---
	// 插入的是集合 [===,+++,***] 就重复如上。
	// --===---
	// --===+++---
	// --===+++***---
    public boolean addAll(int index, Collection<? extends E> c) 
        checkPositionIndex(index);	// 索引检测,如果越界就抛锅。
		
        Object[] a = c.toArray();	// 获取集合 c 所有元素的数组。
        int numNew = a.length;		// 获取将插入的元素个数
        if (numNew == 0)			// 如果插入元素个数为 0 直接返回 false
            return false;
            
		// 声明两个临时变量,在插入过程中,用来临时保存前后节点。
		// pred=前节点(原本就在 index 节点前面,新节点来后,位置不变)
		// succ=后节点(插入前 index 位置的节点,被新节点往后挤了)
        Node<E> pred, succ;			
        // 如果 index == size 在尾节点后追加
        if (index == size) 
            succ = null;			// 后节点自然还是 null
            pred = last;			// 原列表的尾节点。
        // 否则在中间或头插入
         else 
            succ = node(index);		// index 所在节点,作为后节点。
            pred = succ.prev;		// index节点的 prev,作为前节点。
        
		// 遍历数组 a 逐个插入
        for (Object o : a) 
        	// 取出元素,强转类型,以符合当前列表。
            @SuppressWarnings("unchecked") E e = (E) o;
            // 创建存放元素的新节点:【前节点】上面已取得直接使,【后节点】留空,稍后处理。
            Node<E> newNode = new Node<>(pred, e, null);
            // 没人出头,我出头当大哥。
            if (pred == null)
                first = newNode;
            // 有带头大哥,就让大哥带新节点。新节点在创建时 prev 已经指向 pred ,至此双向连接完成。
            else
                pred.next = newNode;
            // 更新前节点变量。用这个的新节点作为后续节点的【前节点】
            pred = newNode;
        
		// 遍历插入完成后,处理尾节点。
		// 在插入前,插入位置就没有【后节点】,那最后一个插入的节点作为【尾节点】
        if (succ == null) 
            last = pred;
        // 否则:新插入的最后一个节点与 succ 连接。
         else 
            pred.next = succ;
            succ.prev = pred;
        
		// 更新列表长度 +上新插入的个数
        size += numNew;
        // 更新修改计数。与批量插入了多少个元素无关,计为修改了 1 次。
        modCount++;
        // 插入成功返回 true
        return true;
    
  • Object[] toArray()
    将当前表中的元素,按顺序复制到一个新数组并返回。此数组与原列表无引用关系,可以随便编辑修改数组(元素是传值址的,与原列表共享,修改元素内容,两边都会变的)。
    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;
    

void addFirst(E e)

  • test
@Test
public void addFirstTest()
    LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "D", "E"));
    list.addFirst("X");
    log.info(list.toString()); // [X, A, B, C, D, E]

  • void addFirst(E e)
    public void addFirst(E e) 
        linkFirst(e);
    

  • void linkFirst(E e) 将元素 e 作为头节点,连接到链表。
    private void linkFirst(E e) 
    	// 取出【头节点】,存入临时变量 f
        final Node<E> f = first;
        // 创建新节点,存放元素 e,并将其 next 指向 f (原来的头节点)
        final Node<E> newNode = new Node<>(null, e, f);
        // 更新当前链表的【头节点】指向【新节点】
        first = newNode;
        // 如果是空链表,更新当前链表的【尾节点】指向【新节点】
        if (f == null)
            last = newNode;
        // 否则:【原来的头节点】的 prev 指向【新节点】
        else
            f.prev = newNode;
        // 插入成功,更新列表长度。
        size++;
        // 插入成功,更新修改计数。
        modCount++;
    

void addLastTest()

  • test
@Test
public void addLastTest()
    LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "D", "E"));
    list.addLast("X");
    log.info(list.toString()); // [A, B, C, D, E, X]

  • void addLast(E e)
    public void addLast(E e) 
        linkLast(e);
    
  • linkLast(E e):将元素 e 作为最后一个节点,连接到链表。

访问修饰符&返回类型方法描述
voidclear()从此列表中移除所有元素。
Eremove()获取并移除此列表的头(第一个元素)。
底层调用 removeFirst() 并返回结果。
Eremove(int index)移除此列表中指定位置处的元素。
booleanremove(Object o)从此列表中移除首次出现的指定元素(如果存在)。
EremoveFirst()移除并返回此列表的第一个元素。
booleanremoveFirstOccurrence(Object o)从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。
直接调用remove(Object o) 并返回结果。
EremoveLast()移除并返回此列表的最后一个元素。
booleanremoveLastOccurrence(Object o)从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。
EJava集合源码学习笔记LinkedList分析

Java学习笔记5.2.2 List接口 - LinkedList类

java学习笔记

java学习笔记--类ArrayList和LinkedList的实现

学习笔记 07 --- JUC集合

Java集合类学习笔记2

(c)2006-2024 SYSTEM All Rights Reserved IT常识