ArrayList数组和LinkedList双向链表效率问题的比较--两个方面:访问元素和插入元素

ArrayList数组 物理结构连续,并且有自己的下标,因而访问起来效率高,但也因其物理结构连续,插入元素时插入位置后的元素都要后移一位,删除同理,故而添加/删除元素的效率不高

LinkedList双向链表,列表中的每个节点都包含了对前一个和后一个元素的引用.访问效率低于ArrayList数组,但添加/删除元素只需要改变前后两个节点,故添加/删除元素的效率高于ArrayList数组.
—补充:LinkedList双向链表的特点是端口和尾端都能进能出,所以从端口和尾端无论是访问还是添加/删除,效率都比从中间开始的高

总结:查询用ArrayList,添加/删除用LinkedList

代码验证:

public class Num1ArrayVSLinked {
    public static void main(String[] args) {
 //ArrayList数组和LinkedList双向链表效率问题的比较--两个方面:取元素,插入元素
        //1.取元素
        ArrayList<String> arr = new ArrayList<>();
        LinkedList<String> lin = new LinkedList<>();
        for (int i = 0; i < 10000000; i++) {
            arr.add(i, "");
            lin.add(i, "");
        }
        //1.1 从列表的头部开始访问
        long time1 = System.currentTimeMillis();
        arr.get(10);
        long time2 = System.currentTimeMillis();
        System.out.println("ArrayList从列表的头部开始访问的时间:" + (time2 - time1));//0
        long time3 = System.currentTimeMillis();
        lin.get(10);
        long time4 = System.currentTimeMillis();
        System.out.println("LinkedList从列表的头部开始访问的时间:" + (time4 - time3));//0
        //1.2 从列表的中间开始访问
        long time5 = System.currentTimeMillis();
        arr.get(5000000);
        long time6 = System.currentTimeMillis();
        System.out.println("ArrayList从列表的中间开始访问的时间:" + (time6 - time5));//0
        long time7 = System.currentTimeMillis();
        lin.get(5000000);
        long time8 = System.currentTimeMillis();
        System.out.println("LinkedList从列表的中间开始访问的时间:" + (time8 - time7));//31
        //1.3 从列表的尾部开始访问
        long time9 = System.currentTimeMillis();
        arr.get(9999999);
        long time10 = System.currentTimeMillis();
        System.out.println("ArrayList从列表的尾部开始访问的时间:" + (time10 - time9));//0
        long time11 = System.currentTimeMillis();
        lin.get(9999999);
        long time12 = System.currentTimeMillis();
        System.out.println("LinkedList从列表的尾部开始访问的时间:" + (time12 - time11));//0

        //2.插入元素
        //2.1 从列表的头部开始插入
        long time01 = System.currentTimeMillis();
        arr.add(10,"3");
        long time02 = System.currentTimeMillis();
        System.out.println("ArrayList从列表的头部开始插入的时间:" + (time02 - time01));//547
        long time03 = System.currentTimeMillis();
        lin.add(10,"3");
        long time04 = System.currentTimeMillis();
        System.out.println("LinkedList从列表的头部开始插入的时间:" + (time04 - time03));//0
        //2.2 从列表的中间开始插入
        long time05 = System.currentTimeMillis();
        arr.add(5000000,"3");
        long time06 = System.currentTimeMillis();
        System.out.println("ArrayList从列表的中间开始插入的时间:" + (time06 - time05));//46
        long time07 = System.currentTimeMillis();
        lin.add(5000000,"3");
        long time08 = System.currentTimeMillis();
        System.out.println("LinkedList从列表的中间开始插入的时间:" + (time08 - time07));//47
        //2.3 从列表的尾部开始插入
        long time09 = System.currentTimeMillis();
        arr.add(9999999,"3");
        long time010 = System.currentTimeMillis();
        System.out.println("ArrayList从列表的尾部开始插入的时间:" + (time010 - time09));//0
        long time011 = System.currentTimeMillis();
        lin.add(9999999,"3");
        long time012 = System.currentTimeMillis();
        System.out.println("LinkedList从列表的尾部开始插入的时间:" + (time012 - time011));//0
    }
}


<think>好的,我现在需要为用户详细介绍Java中的LinkedList,包括其作为双向链表的特性,以及源码分析的各个部分:属性、构造方法、添加、修改、插入、删除元素。用户之前已经询问过链表的基础知识,现在可能想深入理解Java具体实现中的细节。 首先,用户提到LinkedList双向链表,我需要确认Java中的LinkedList确实是双向链表结构,并解释每个节点的结构,比如包含前驱后继指针。然后需要分析源码中的属性,比如头尾节点、大小等。 构造方法部分,可能需要查看LinkedList的两种构造方式:无参构造用集合初始化。需要说明默认构造方法如何初始化头尾节点,以及使用集合时如何添加元素。 添加元素方面,用户可能想知道add方法的实现,比如添加到末尾或指定位置。需要区分add(E e)add(int index, E e)的不同,以及它们如何遍历链表找到插入点。 修改元素涉及set方法,需要说明如何通过索引找到节点并替换其数据。要指出索引遍历的优化,比如根据位置选择从头或尾开始遍历,以提升效率插入元素可能添加元素中的指定位置添加是同一过程,需要确认源码中是否单独处理,或者是否属于add方法的一部分。可能需要详细描述节点插入时前驱后继指针的调整过程。 删除元素包括按索引删除元素删除,需要分析remove(int index)remove(Object o)的实现。要说明删除节点时如何调整相邻节点的指针,以及如何处理头尾节点的特殊情况。 另外,用户可能对时间复杂度感兴趣,比如添加到末尾是O(1),但插入到中间是O(n)。需要指出LinkedList在频繁插入删除时的效率优势,以及随机访问的劣势。 需要注意源码中的具体实现细节,比如节点类Entry或Node的定义,头尾指针的维护,以及size字段的作用。可能还需要提到迭代器的实现,比如如何支持快速失败机制。 最后,要确保解释清晰,结构分明,使用代码示例或伪代码帮助理解,同时对比ArrayList说明LinkedList的适用场景,如频繁插入删除时更高效,但随机访问较慢。可能还需要提到LinkedList实现了Deque接口,支持队列双端队列操作。</think>以下是针对 **Java `LinkedList`** 的源码级分析,包括其作为双向链表的实现原理及核心操作: --- ### **1. LinkedList 核心结构** ```java public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable { // 双向链表节点定义 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; // 尾节点 } ``` - **双向链表特性**:每个节点(`Node`)包含前驱(`prev`)后继(`next`)指针。 - **头尾指针**:通过 `first` `last` 快速访问链表的首尾节点。 - **非线程安全**:未同步,需外部保证线程安全。 --- ### **2. 构造方法** #### **无参构造** ```java public LinkedList() {} // 初始化空链表(first=last=null, size=0) ``` #### **用集合初始化** ```java public LinkedList(Collection<? extends E> c) { this(); addAll(c); // 遍历集合,依次添加元素链表尾部 } ``` --- ### **3. 添加元素** #### **添加到尾部(默认)** ```java public boolean add(E e) { linkLast(e); // 时间复杂度 O(1) return true; } void linkLast(E e) { final Node<E> l = last; // 原尾节点 final Node<E> newNode = new Node<>(l, e, null); // 新节点.prev=last, .next=null last = newNode; // 更新尾节点为新节点 if (l == null) // 原链表为空 first = newNode; // 头尾均指向新节点 else l.next = newNode; // 原尾节点.next 指向新节点 size++; } ``` #### **添加到指定位置** ```java public void add(int index, E element) { checkPositionIndex(index); // 检查索引是否合法 if (index == size) // 添加到尾部 linkLast(element); else // 插入到中间位置 linkBefore(element, node(index)); // 找到原位置节点,在其前插入新节点 } // 在指定节点 succ 前插入新节点 void linkBefore(E e, Node<E> succ) { final Node<E> pred = succ.prev; // 原前驱节点 final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; // 更新 succ 的前驱为新节点 if (pred == null) // 插入到头部 first = newNode; else pred.next = newNode; // 原前驱节点的后继指向新节点 size++; } ``` --- ### **4. 修改元素** ```java public E set(int index, E element) { checkElementIndex(index); // 检查索引是否合法 Node<E> x = node(index); // 获取原节点(时间复杂度 O(n/2)) E oldVal = x.item; x.item = element; // 直接替换数据域 return oldVal; } // 根据索引定位节点(优化遍历方向) 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; } } ``` --- ### **5. 删除元素** #### **按索引删除** ```java public E remove(int index) { checkElementIndex(index); return unlink(node(index)); // 找到节点并解除链接 } E unlink(Node<E> x) { final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; if (prev == null) // 删除头节点 first = next; else prev.next = next; // 前驱节点跳过x if (next == null) // 删除尾节点 last = prev; else next.prev = prev; // 后继节点跳过x x.item = null; // 帮助GC x.next = x.prev = null; size--; return element; } ``` #### **按元素删除** ```java public boolean remove(Object o) { if (o == null) { // 处理 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; } ``` --- ### **6. 核心操作时间复杂度** | 操作 | 时间复杂度 | 说明 | |----------------|------------|--------------------------------| | 增删头尾 | O(1) | 直接修改头尾指针 | | 增删中间元素 | O(n) | 需遍历到目标位置 | | 按索引访问 | O(n) | 需遍历链表 | | 按值查找 | O(n) | 需遍历链表 | --- ### **7. 适用场景** 1. **频繁增删**:如实现队列(`poll()`/`offer()`)或栈(`push()`/`pop()`)。 2. **双端操作**:因实现 `Deque` 接口,适合需要从头部尾部同时操作的场景。 3. **动态数据**:数据规模变化频繁时,无需预留内存空间。 --- ### **总结** - **优势**:动态内存、高效增删、支持双端操作。 - **劣势**:随机访问慢、内存开销较大(每个节点需两个指针)。 - **设计思想**:以空间换时间,通过指针操作避免数据位移。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值