【面经总结】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 分钟前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生1 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功1 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2341 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨1 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程2 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man3 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang