ArrayList、Vector、Stack

类继承

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
	...
}
  1. Serializable:支持序列化(标记作用,内部无方法)
    • 序列化是把内存对象转成别的东西
    • 反序列化是把别的东西转成内存对象
    • 比如一个对象序列化后存入本地文件,反序列化后又成为一个对象
    • 再比如前端请求传入的是字符串,后端接收到后是一个对象
      这个比喻不恰当,因为 json 不需要序列化,java类不实现序列化接口也可以,本质上后端是结构映射而非类型还原
  2. Cloneable:支持克隆(标记作用,内部无方法)
    • Object 在调用 clone() 方法时 JVM 会检测类是否实现了 Cloneable 接口,如果没有会报错
    • Object.clone() 方法是浅拷贝,所以 Arraylist 自己重写过 clone 方法
  3. RandomAccess:支持随机访问(标记作用,内部无方法)
    • 哪种数据结构最适合随机访问?当然是数组了,所以链表实现的 LinkedList 就没实现 RandomAccess 接口
    • 作用就是允许算法根据此标记选择最优实现
      比如 Collestions.binarySearch() 方法里就有判断,如果实现了 RandomAccess 就用数组的方式直接使用下标访问,不然就链表方式顺序访问
  4. List:定义列表的基本操作(接口用来定义行为,实现类具体实现)
    1. List 继承了 Collection 接口,Collection 继承了 Iterable 接口
    2. Collection、Iterable 也是定义了一些行为,比如迭代器、转数组等
  5. AbstractList:父接口的一些默认实现,避免重复编写功能,比如 indexOf()lastIndexOf()

类成员

类属性

// 数组默认容量(首次添加元素时才真正分配)
private static final int DEFAULT_CAPACITY = 10;

// 这俩货都是空数组,在显示表达和资源优化上有区别(在下面配合构造方法来解释更易理解)
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 真正存放数据的数组(transient:在序列化时会忽略 transient 标注的字段,ArrayList 自己实现了 readObject 和 writeObject 方法来序列化)
transient Object[] elementData; // non-private to simplify nested class access

// 元素个数
private int size;

// 修改次数
protected transient int modCount = 0;

内部类

// 基础的迭代器,只有移除和顺序遍历
private class Itr implements Iterator<E> {
    
    @SuppressWarnings("unchecked")
    public E next() {}

    public void remove() {}

}

// 增强的迭代器具有 Itr 的功能,增强了逆序遍历的能力,除了移除还能添加和修改元素
private class ListItr extends Itr implements ListIterator<E> {

    @SuppressWarnings("unchecked")
    public E previous() {}

    public void set(E e) {}

}

// 调用 sublist() 获取 ArrayList 中的一部分(子列表),操作这个子列表 JVM 层面有一些优化处理
private static class SubList<E> extends AbstractList<E> implements RandomAccess {
  
}

// 对 ArrayList 进行高效遍历和拆分操作,JDK8 引入
// 调用 spliterator() 这个方法获取 ArrayListSpliterator 对象
// 并行流 parallelStream() 内部也会调用 spliterator() 获取 ArrayListSpliterator
final class ArrayListSpliterator implements Spliterator<E> {

}

构造函数

// 不带参数,使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 硬性指定长度
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) { // 大于 0,创建指定长度的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) { // 如果指定 0,使用 EMPTY_ELEMENTDATA
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    }
}

// 传入一个列表
public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        // 正常添加
    } else {
        // 如果传入的是 null,使用 EMPTY_ELEMENTDATA
        elementData = EMPTY_ELEMENTDATA;
    }
}
  1. EMPTY_ELEMENTDATA

    new ArrayList(0);  // 显式指定初始容量为0
    new ArrayList(collection);  // 当传入空集合时
    

    带了参数表示要精确所需大小,不需要预分配的空间,说多少就多少。尊重用户的选择

  2. DEFAULTCAPACITY_EMPTY_ELEMENTDATA

    new ArrayList();  // 无参构造器使用
    

    不带参数,那就使用默认的长度

添加元素

public boolean add(E e) {
    modCount++;
    add(e, elementData, size); // 调用这个方法来完成添加
    return true;
}

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)  // 扩容
        elementData = grow();
    elementData[s] = e; // s 是元素个数(如果数组目前已经存在 2 个元素的话,就应该在下标是 2 的位置插入)
    size = s + 1;
}

扩容

private Object[] grow() {
    return grow(size + 1); // size 是当前元素个数,数组最小长度就要+1(参数形参翻译过来也就是最小容量)
}


private Object[] grow(int minCapacity) {
    // 获取当前容量
    int oldCapacity = elementData.length;
    
    // 情况1:非首次扩容或非默认空数组
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 计算新容量
        int newCapacity = ArraysSupport.newLength(...);
        // 创建新数组并拷贝数据
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } 
    // 情况2:首次扩容且是默认空数组
    else {
        // 直接创建新数组(默认容量或指定最小容量)
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

新容量怎么计算来的,扩容多少?

// oldCapacity:当前数组长度(也就是元素个数,前提是长度=个数)
// minCapacity:最小增长量(调用 add() 导致的扩容就是1,调用 addAll() 导致的扩容可能 > 1)
// oldCapacity >> 1:右运算,旧容量的一半(推荐的增长量)
int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);


public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
  
	  // 推荐是原长度的一半,但是有可能原长度的一半不够( addAll 添加 1w 个元素),所以要取大的 max(最小增长, 首选增长)
    int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
  
    // SOFT_MAX_ARRAY_LENGTH 是个静态常量(Integer.MAX_VALUE - 8)为什么要减 8?
  	// Integer.MAX_VALUE 不同的 vm 规范不一致,避免超过真正 Integer 的最大值,保险起见设置一个软最大长度
    if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
        return prefLength;
    } else {
        // 新数组长度如果大于 SOFT_MAX_ARRAY_LENGTH,放弃1.5倍扩容策略,仅满足基本要求
        return hugeLength(oldLength, minGrowth);
    }
}

private static int hugeLength(int oldLength, int minGrowth) {
    // 原长度 + 最小增量。这个基本要求也满足不了就报错
    int minLength = oldLength + minGrowth;
    if (minLength < 0) { // 为什么要判断 < 0?Integer最大值+1后是负数
        throw new OutOfMemoryError("Required array length " + oldLength + " + " + minGrowth + " is too large");
    } else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
        return SOFT_MAX_ARRAY_LENGTH;
    } else {
        return minLength;
    }
}
  1. 默认扩容为原来长度的1.5倍,但不绝对
    1. 1.5 倍只是推荐值,当超出 Integer 最大值(软最大值)时扩容就不是 1.5 倍,是需要多少就扩多少
    2. 如果调用无参构造创建的列表,添加元素时第一次扩容(也是设置初始长度)扩容后是 10
  2. 数组最大长度不是 Integer 最大值,因为不同虚拟机 Integer 最大值可能不一致,所以减8(软最大值,最大值附近)

总结

  1. 基于数组实现
  2. 元素有序(添加顺序)、可以添加重复元素、允许元素为null、线程不安全
  3. 支持随机访问,实现了 RandomAccess 接口
    • 插入、删除元素时间复杂度 O(n),尾部插入 O(1) 但是可能扩容
    • 获取元素时间复杂度 O(1)
    • 插入、删除复杂换来获取时的便捷,所以适用于查询多的场景
  4. 扩容
    • 默认初始容量 10(无参构造时首次添加才分配)
    • 扩容公式:新容量 = max(最小需求, 旧容量 * 1.5) (扩容1.5不是绝对的)
    • 大容量处理:超过 Integer.MAX_VALUE - 8 抛出 OutOfMemoryError (扩容1.5不是绝对的)
    • 不会自动缩容,但可通过 trimToSize() 释放多余空间
  5. 两种迭代器
    • Itr:基础迭代器(单向,支持 remove
    • ListItr:增强迭代器(双向,支持 add/set/remove
  6. 内存与性能优化
    • new ArrayList() 使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,首次添加才分配默认容量
    • EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA 区分构造意图,以人为本,客户是上帝
    • transient Object[] elementData,序列化时忽略底层数组,自己实现了 readObject 和 writeObject 仅序列化有效元素

Vector

特性 ArrayList (JDK 1.2+) Vector (JDK 1.0, 遗留类)
线程安全 非线程安全 线程安全 (所有方法用 synchronized 修饰)
扩容机制 新容量 = max(最小需求, 旧容量 * 1.5) 新容量 = max(最小需求, 旧容量 * 2) (默认翻倍)
初始容量 10 (首次添加元素时分配) 10 (构造时直接分配)
Java 版本 JDK 1.2+ (现代集合框架) JDK 1.0 (遗留类,仍保留但已过时)

Stack

Stack 继承自 Vector,扩展了栈的特性(后进先出)

posted @ 2023-05-24 17:04  CyrusHuang  阅读(32)  评论(0)    收藏  举报