ArrayList(数组)
ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除
三种构造方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final int DEFAULT_CAPACITY = 10; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this.elementData = EMPTY_ELEMENTDATA; } }
}
|
以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。
无参构造的 ArrayList 对象时,直接创建了长度是 10 的 Object[] 数组 elementData
扩容过程
在添加元素的时候
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; }
...
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }
...
private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) grow(minCapacity); }
|
当添加第 1 个元素时,elementData.length 为 0,因为执行了 ensureCapacityInternal() 方法 ,所以 minCapacity 此时为 10。此时,minCapacity - elementData.length > 0成立,所以会进入 grow(minCapacity) 方法;
当添加第 2 个元素时,minCapacity = 2,此时 elementData.length 在添加第一个元素后扩容成 10 了。此时,minCapacity - elementData.length > 0 不成立,所以不会执行 grow(minCapacity) 方法,既不会进行扩容。添加第 3、4、···直到第 10 个元素时,依然不会执行 grow 方法,数组容量都为 10;
当添加第 11 个元素时,minCapacity = 11,此时 minCapacity - elementData.length = 11 - 10 > 0 成立,因此便进行扩容操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity); }
...
private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
|
当向 ArrayList 中添加一个新元素时,ArrayList 会先检查当前数组中是否还有剩余的空间可以存储元素;
如果有剩余空间,则将元素添加到数组 elementData 的尾部,然后更新数组 elementData 中的元素数量 size 即可。
如果没有剩余空间,则底层通过 ArrayList 扩容的核心方法 grow(int minCapacity) 来进行扩容,具体步骤如下:
将数组 elementData 的长度设置为旧容量 oldCapacity;
然后取 oldCapacity 的大约 1.5 倍作为新容量 newCapacity,具体的计算方法是 newCapacity = oldCapacity + (oldCapacity << 1),这里通过使用移位运算符来加快计算速度;
通过 Arrays.copyOf() 方法来创建一个长度为 newCapacity 的数组,并且将旧数组中的元素复制到新数组中,复制完成后,ArrayList 就会开始使用新数组来存储元素,并更新数组的大小和元素数量
Vector
Vector 与 ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写 Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问 ArrayList 慢。
三种构造方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public Vector() { // 当前是无参构造方法 Vector vector = new Vector(); // 其实你不给参数,它默认就是10,它会去调用一个有参方法 this(10); } ... public Vector(int initialCapacity) { // 这句话好比如 Vector vector = new Vector(5); // 意思代表着如果你没有参数我默认就是10,如何调用有参方法 // 如果构造方法有值的话,则直接走当前方法 this(initialCapacity, 0); } ... public Vector(int initialCapacity, int capacityIncrement) { super(); // 判断当前的值是否为负数,则会抛出异常 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); /** * 第一个值代表当前数组初始化的大小 * 第二个值代表待会儿我们可以在扩容的那个地方说,它起到了关键 * 性作用 */ this.elementData = new Object[initialCapacity]; // 如果我们在刚刚开始构建Vector的时候你没有传入参数 // 或者说你只传了一个参数,在调用当前方法的时候,它是 // 默认传入0的 this.capacityIncrement = capacityIncrement; }
|
扩容过程
在添加元素的时候
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| // 可以看见该方法synchronized修饰,则是线程安全的 public synchronized boolean add(E e) { modCount++;// 修改次数 加加 // protected int elementCount; 则记录当前,每次加1 ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
...
private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code // 如果在无参构造方法在第一次进行添加的时候, // 此时的minCapacity 等于1 而elementDta.length等于10 // 1-10 = -9; -9不大于0 所以说不满足条件 if (minCapacity - elementData.length > 0) grow(minCapacity); }
|
经过多次添加的时候,满足条件,会触发扩容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| private void grow(int minCapacity) { // overflow-conscious code // 此时我们的elementData.length是等于10 int oldCapacity = elementData.length; /** * 这边 运用了一个三元运算符就行判断 * capacityIncrement 就是前面说的起到关键性作用的变量 * 假设,我们在创建Vector的时候是一个无参的构造器,或者说只有一个 * 参数,那么此时的capacityIncrement 等于0,前面提到过 默认传给 * 它的就是0,0不大于0所以结果为 oldCapacity + oldCapacity * 也就是10加上10 等于20,以2倍的速度进行增加 * 如果你是Vector vector = new Vector(5,5);这样创建 * 结果明显而知,5肯定大于0的 */ int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 = 2147483639 // 判断当前是否超出了一个最大值 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 调用了Arrays.copyOf方法, // 为什么调用Arrays.copyOf,为了就是保留原有的数据 // 把新的newCapacity赋值给它后再copy给原来的elementData // 则当前扩容完成 elementData = Arrays.copyOf(elementData, newCapacity); }
|
LinkedList
LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
可以参考算法 数组、链表和跳表