Java ArrayList 和 LinkedList

103 阅读4分钟

1. 底层实现

1.1 ArrayList

  • 底层数据结构ArrayList 底层是基于动态数组实现的。
  • 存储方式:数据连续存储在内存中。
  • 扩容机制:当数组容量不够时,ArrayList 会创建一个新的更大的数组(通常是原来大小的1.5倍),然后将旧数组中的元素复制到新数组中。 以下是源码分析 创建 ArrayList 的默认容量是 10。 如果我们不给定扩容参数的话,它会默认传递 size+1,也就是重载函数 grow()。容量要进行扩容,那就要确定扩容的大小,newCapacity 就是计算的大小,但是这个大小需要进一步的判断,因为扩容太大容易导致溢出。所以这个问题就交给了 ArraysSupport. newLength 函数来解决,它传递的参数是:oldCapacity (原来的容量)、minCapacity - oldCapacity(最小扩容减去原来的容量也就是 1)、oldCapacity>>1 (原来的容量的一半) 这里 minCapacity - oldCapacity 的默认是 1,但程序员是可以传递 minCapacity 的。 那我们进入到 newLength 函数中看看它是怎么判断的。首先 ArraysSupport 类中先定义了最大容量 SOFT_MAX_ARRAY_LENGTH = Integer.Max_Value - 8。然后 newLength 方法中会对新的容量进行计算。
     int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
     //这行代码就是扩容后的容量,minGrowth默认是1,但程序员调用grow时传递了minCapacity的话,就不一定了。prefGrowth是原来容量的一半,所以说默认是1.5倍扩容
    
    确认了新容量之后,就要进行判断容量是否溢出。没有溢出就进行扩容,如果溢出,还要调用 hugeLength 方法。 这里是 hungeLength 方法,传递的是原来的容量和最小扩容大小(默认是 1,程序员可以传递任意大小)。 方法中先对 oldLength 和 minGrowth 进行相加,判断是否小于 0(也就是判断是否溢出),如果溢出了就直接抛异常了。没有溢出的话直接给定最大容量了。 总结以下 ArrayList 的扩容机制,默认大小是 10,默认 1.5 倍扩容,当然程序员可以指定扩容量。如果进行扩容的时候溢出了,那么就直接扩容到最大容量 Integer. Max_Value - 8,为什么要-8? 在 JVM 中,所有对象(包括数组)都有一个对象头,用于存储一些元数据信息。对于数组来说,对象头包含数组的长度等信息。对象头的大小根据 JVM 的具体实现可能有所不同,但通常为 8 到 16 字节。

1.2 LinkedList

  • 底层数据结构LinkedList 底层是基于双向链表实现的。
  • 存储方式:每个元素存储在一个节点中,节点包含元素值和指向前后节点的指针。
  • 节点结构:每个节点都包含三个部分:前驱节点的引用、元素值、后继节点的引用。

2. 性能比较

2.1 访问元素

  • ArrayList:访问元素的时间复杂度是O(1),因为可以通过索引直接访问。
  • LinkedList:访问元素的时间复杂度是O(n),因为需要从头或尾开始遍历到指定位置。

2.2 添加元素

  • ArrayList
    • 在末尾添加元素的时间复杂度是O(1),平均情况下。
    • 在指定位置添加元素的时间复杂度是O(n),因为需要移动后面的元素以腾出空间。
  • LinkedList
    • 在末尾添加元素的时间复杂度是O(1),因为只需要修改指针。
    • 在指定位置添加元素的时间复杂度是O(n),因为需要遍历到指定位置。

2.3 删除元素

  • ArrayList
    • 删除末尾元素的时间复杂度是O(1)。
    • 删除指定位置元素的时间复杂度是O(n),因为需要移动后面的元素以填补空位。
  • LinkedList
    • 删除末尾或头部元素的时间复杂度是O(1)。
    • 删除指定位置元素的时间复杂度是O(n),因为需要遍历到指定位置。

2.4 内存消耗

  • ArrayList:由于底层是数组,内存利用率高,但在扩容时会有内存开销。
  • LinkedList:每个元素都需要额外的指针存储前驱和后继节点,内存开销较大。

3. 使用场景

  • ArrayList:适用于需要频繁访问元素、较少插入或删除操作的场景,例如:随机访问、遍历操作等。
  • LinkedList:适用于需要频繁插入或删除操作、较少访问元素的场景,例如:实现队列、双端队列等。