源码:ArrayList & LinkedList

List 接口的实现类(Java 1.2+)

  1. 有序,有下标,可重复。
  2. 线程不安全,效率高。

ArrayList

ArrayList 基于数组存储元素。

成员变量

数据类型 说明
DEFAULT_CAPACITY int 默认初始容量(10
EMPTY_ELEMENTDATA Object[] 空数组
DEFAULTCAPACITY_EMPTY_ELEMENTDATA Object[] 默认容量的空数组
elementData Object[] 存储集合元素的数组
size int 当前存储的实际元素个数
(ArrayList 长度)

空数组对比

  • EMPTY_ELEMENTDATA:使用有参构造方法时使用,避免重复创建空数组。
  • DEFAULTCAPACITY_EMPTY_ELEMENTDATA:使用无参构造方法时使用,确保初始化容量。

无参构造函数

操作:仅将 elementData 设置默认容量空数组,没有其它操作。

首次调用 add() 时,进行初始化操作。

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

add()

// 1
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
// 2
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
// 3
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
// 4
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

首次调用

此时数组为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA

容量为 0

  1. add():添加元素。
    1. 传入当前需要的最小容量(当前元素个数 + 1),确保数组容量。
    2. 添加元素到数组末尾。
  2. ensureCapacityInternal():确保数组容量。
    1. 判断 elementData 是空数组,待初始化容量为 10
    2. 确保数组可用容量。
  3. ensureExplicitCapacity():确保数组可用容量。
    1. minCapacity 表示当前需要的最小容量,elementData.length 表示当前实际容量。
    2. 实际容量必须大于最小容量,否则需要扩容
  4. grow():数组扩容。
    1. 定义新容量 = 旧容量的 1.5(仅确定扩容容量)。
    2. 确保新容量 >= minCapacity(首次调用时 if 不成立,赋值新容量 = minCapacity)。
    3. 若新容量超过最大限制,进行特殊操作(避免 OOM 异常)。
    4. 将 element 实际扩容为新容量(首次调用 add() 时,到此才具有默认容量 10)。

非首次调用

  1. add()
  2. ensureCapacityInternal()
    1. 数组 elementData 非空,跳过。
    2. 确保数组可用容量。
  3. ensureExplicitCapacity():判断最小容量是否大于实际容量。
    • :扩容。
    • :返回。
  4. grow()
    1. 新容量 = 旧容量的 1.5 倍。
    2. 新容量必定大于最小容量,通常不会超过最大限制。
    3. 实际扩容。

LinkedList

LinkedList 基于双向链表存储元素,

定义内部类 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;
    }
}

成员变量

数据类型 含义
size int 当前存储的实际元素个数
(LinkedList 长度)
first Node<E> 头结点
last Node<E> 尾结点

添加元素

已知一个双向链表(头结点 first、尾结点 last),

向其尾插一个新结点(nNode)。

创建新结点 nNode:前驱指向 last,后继为空。

image

思路一

先判断链表是否为空,再移动尾指针。

  1. last != null(链表非空):将 last 后继指向 nNode,last 移动到 nNode。

    image

  2. last == null(链表为空):nNode 是唯一元素,first 和 last 指向 nNode。
    image

相应代码

// 创建新节点
Node nNode = new Node(last, data, null);

// 判断链表是否为空(通过 last)
if (last == null) {
    first = nNode;
} else {
    last.next = nNode;
}

// 移动尾指针
last = nNode;

思路二

先移动尾指针,再判断链表是否为空。

  1. 使用临时指针(tmp)保存 last,将 last 移动到 nNode。

  2. 判断 tmp 是否为空:

    1. tmp != null(链表非空):将 tmp 后继指向 nNode。

      image

    2. tmp == null(链表空):nNode 是唯一元素,first 指向 nNode。

      image

相应代码

// 创建新节点
Node nNode = new Node(last, data, null);

// 保存并移动尾指针
Node tmp = last;
last = nNode;

// 判断链表是否为空(通过 tmp)
if (tmp == null) {
    first = nNode;
} else {
    tmp.next = nNode;
}

add()

可以看出,add() 类似上述的思路二。

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

void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

分析:思路二的优势。

两种思路的主要区别在于,后者定义了局部变量(tmp)来保存 last

后续操作均基于 tmp,而不是直接操作 last

个人理解(基于 JVM 角度):提高性能

  1. 成员变量 last 位于堆中,局部常量位于运行时常量池(类似本地缓存)。
  2. 相比从堆中读取数据,从常量池中读取数据的效率更高。
posted @ 2022-04-20 10:56  Jaywee  阅读(27)  评论(0编辑  收藏  举报

👇