【面经总结】Java集合 - List

ArrayList

要点

实现机制

数组

扩容机制

初始容量为空列表,第一次插入后扩容成默认大小 10。

添加元素时如果已满,会自动扩容为原始大小的 1.5 倍。

类定义

java 复制代码
// 类定义
public class ArrayList<E> 
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  1. 实现了 RandomAccess 接口,支持随机访问。

RandomAccess 是一个标志接口,说明该类支持快速随机访问

  1. 实现了 Cloneable 接口,默认为浅拷贝。
  2. 实现了 Serializable 接口,支持序列化。
  3. 非线程安全:可以使用 Collections.synchronizedList() 包装成线程安全的

数据结构

  1. elementData:对象数组(用于存数据)
  2. size:当前数组长度
  3. DEFAULT_CAPACITY:默认大小
java 复制代码
// 默认初始化容量
private static final int DEFAULT_CAPACITY = 10;

// 对象数组
transient Object[] elementData;

// 数组长度
private int size;

构造方法

  1. 无参构造:默认初始大小(10)
  2. 指定初始大小构造:减少数组的扩容次数,提高性能
java 复制代码
public ArrayList() {
    // 创建一个空数组
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        // 根据初始化值创建数组大小
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 初始化值为 0 时,创建一个空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    }
}

访问元素

通过下标获取,复杂度 O(1)

java 复制代码
// 获取第 index 个元素
public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}

E elementData(int index) {
    return (E) elementData[index];
}

添加元素

  1. 尾部添加:直接放在数组最后
  2. 任意位置添加:向后复制后半段来腾出当前位置

默认大小为 10,超过数组大小会触发扩容 1.5 倍。

java 复制代码
// 添加元素到数组末尾
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

// 添加元素到任意位置
public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}

ArrayList 的扩容机制:

java 复制代码
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // new = old * 1.5
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    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);
}

删除元素

删掉当前位置元素,将后半段向前复制

java 复制代码
public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

Fail-Fast 机制

使用 modCount 来记录结构发生变化的次数,用来避免并发修改异常。

LinkedList

要点

实现机制

基于双向链表:顺序访问会非常高效,而随机访问效率比较低。

类定义

java 复制代码
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  1. 实现了 Deque 接口,也可以被当作队列 Queue 或双端队列 Deque进行操作
  2. 实现了 Cloneable 接口,默认为浅拷贝。
  3. 实现了 Serializable 接口,支持序列化。
  4. 非线程安全:可以使用 Collections.synchronizedList() 包装成线程安全的

数据结构

  1. size:数组长度
  2. first、last:双向链表头尾节点

Node:链表的节点

java 复制代码
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
    // ...
}

// 链表长度
transient int size = 0;
// 链表头节点
transient Node<E> first;
// 链表尾节点
transient Node<E> last;

序列化

访问元素

通过 size 和 index 判断 Node 是在前半段还是后半段,再遍历链表。时间复杂度 O(n)

java 复制代码
public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
}

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;
    }
}

添加元素

  1. add、addLast:尾插
  2. addFirst:头插
  3. add(index, item):指定位置插入
java 复制代码
private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null) last = newNode;
    else f.prev = newNode;
    size++;
    modCount++;
}

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++;
}

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);
    succ.prev = newNode;
    if (pred == null) first = newNode;
    else pred.next = newNode;
    size++;
    modCount++;
}

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

public void add(int index, E element) {
    checkPositionIndex(index);
    if (index == size) linkLast(element);
    else linkBefore(element, node(index));
}

public void addFirst(E e) {
    linkFirst(e);
}

public void addLast(E e) {
    linkLast(e);
}

删除元素

遍历找到要删除的元素节点,然后调用 unlink 方法删除节点

  • 前驱节点指向后继,否则更新头指针;
  • 后继节点指向前驱,否则更新尾指针。
java 复制代码
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;
}

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) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}

Vector

基于数组实现,特性与ArrayList类似。(不再推荐使用)

线程安全:使用线程同步能力,多线程互斥写入Vector。

全部操作方法都加的有 synchronized 关键字,性能雪崩。

https://blog.csdn.net/weixin_44688973/article/details/119732347

List

Arrays.asList()

  1. 不能转换基本类型的数组:数组会被当成一个对象

  2. 返回的 List 不能增删:返回的不是正常的 ArrayList,没有重写 add 和 remove 方法

  3. 原始数组的修改会影响 List:转换后直接复用了原始的数组

  4. 不能直接使用 Arrays.asList() 来转换基本类型数组。

Arrays.asList() 方法传入的是一个泛型 T 的可变参数,会导致数组整体作为了一个对象成为了 T

java 复制代码
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}
  1. 返回的 List 不支持增删操作

Arrays.asList() 返回的 List 并不是的 java.util.ArrayList,而是 Arrays 的内部类 ArrayList。没有重写 add 和 remove 方法。

  1. 对原始数组的修改会影响到我们获得的那个 List

Arrays.asList() 转换后直接复用了原始的数组

List.subList()

用途:截取集合中的一部分

问题:

  1. subList 直接引用了原始的 List,而不是一个新的 List,操作会相互影响
  2. 如果原 List 在 subList 操作期间发生了结构修改(增删操作),操作 subList 会抛异常

解决:

  1. 使用新的集合 new ArrayList
  2. 使用 stream 流的 limit 进行操作
相关推荐
亚马逊云开发者2 分钟前
RAG 向量存储月费 800 刀?S3 Vectors 直接砍到 100 出头
java
2401_8955213412 分钟前
springboot集成onlyoffice(部署+开发)
java·spring boot·后端
zlpzlpzyd13 分钟前
groovy学习
java·jvm·学习
程序员小假22 分钟前
你分得清 Prompt、Agent、Function Call、Skill、MCP 吗?
java·后端
xuboyok225 分钟前
【Spring Boot】统一数据返回
java·spring boot·后端
亚马逊云开发者28 分钟前
你的 AI Agent 只有鱼的记忆?聊聊 Agent 记忆管理的正确姿势
java
燕山罗成1 小时前
JAVA多线程基础
java·开发语言
予枫的编程笔记1 小时前
【面试专栏|Java并发编程】拆解Java线程生命周期:从新建到终止,再讲清进程与线程的核心差异
java·多线程·java基础·java面试·进程与线程·面试干货·java线程生命周期
Yvonne爱编码1 小时前
JAVA数据结构 DAY7-二叉树
java·开发语言·数据结构
程序媛徐师姐1 小时前
Java基于微信小程序的球馆预约系统,附源码+文档说明
java·微信小程序·球馆预约系统小程序·jav球馆预约系统小程序·java球馆预约微信小程序·球馆预约微信小程序·java球馆预约系统