Java 集合框架系列四:JDK 1.8 ArrayList 详解

- 日理万妓 2022-12-05 00:55 248阅读 0赞

ArrayList 继承关系

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XG2c3R4h-1599553042477)(G:\\学习记录\\Java 集合框架\\images\\微信截图\_20200830201146.png)\]
ArrayList 是 JDK 1.2 提供的 List 实现类,继承了 AbstractList 抽象类,实现了 Cloneable、Serializable、RandomAccess、List 接口。

  1. public class ArrayList<E> extends AbstractList<E>
  2. implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
  3. }

ArrayList 实现了 Cloneable 接口说明 ArrayList 是可以拷贝的,看到 ArrayList 重写了 Object#clone 方法,ArrayList 的默认实现是浅拷贝,但是拷贝不会复制元素本身,只会复制元素的引用。拷贝后的 ArrayList 和原 ArrayList 的引用不相同,但是都持有了对相同元素的引用。

  1. public Object clone() {
  2. try {
  3. ArrayList<?> v = (ArrayList<?>) super.clone();
  4. v.elementData = Arrays.copyOf(elementData, size);
  5. v.modCount = 0;
  6. return v;
  7. } catch (CloneNotSupportedException e) {
  8. // this shouldn't happen, since we are Cloneable
  9. throw new InternalError(e);
  10. }
  11. }

ArrayList 实现了 RandomAccess 表示 ArrayList 的元素是允许随机访问的。所以 ArrarList 允许通过索引访问元素,访问速度是很快。

类文档解读

老规矩,先通过 ArrayList 的类文档了解一下能得到哪些信息。

  • ArrayList 是大小可变的有序列表,内部是一个容量可以动态增长的 Object 类型的数组。因其底层数据结构是数组,所以它是占据一块连续的内存空间。可以根据下标以 O(1) 的时间复杂度读写元素,因此时间效率很高。但是数组也有缺点,对数组进行添加和删除元素时由于需要移动元素,所以添加和删除元素的复杂度是 O(n),ArrayList 空间效率不高。
  • 由于数组的容量有限,所以在容量不满足容纳新元素时会发生扩容操作,由于扩容需要大量移动数组的数据,所以这是很耗费性能的地方,建议在构建 ArrayList 时指定合适的集合容量以减少扩容次数,或者在添加大量元素前手动调用 java.util.ArrayList#ensureCapacity 方法扩容,注意该方法是 ArrayList 的 API,所以可能需要强转操作。
  • ArrayList 允许添加 null 元素,而且 ArrayList 是允许重复数据的,所以 ArrayList 可能存在多个 null 元素。
  • ArrayList 是非线程安全的,如果希望使用线程安全的 List 可以通过List list = Collections.synchronizedList(new ArrayList(...));方法获取线程安全的 List。
  • ArrayList 返回的迭代器是 fail-fast 策略的,如果在迭代过程中存在线程对 ArrayList 进行结构修改会抛出 ConcurrentModificationException。

ArrayList API

成员变量

  1. // 默认容量
  2. private static final int DEFAULT_CAPACITY = 10;
  3. // 用于默认大小的共享空数组实例,当用户指定初始容量是0时使用
  4. private static final Object[] EMPTY_ELEMENTDATA = { };
  5. // 用于默认大小的共享空数组实例 需要将其与 EMPTY_ELEMENTDATA 区分开以了解添加第一个元素时要扩容的大小
  6. // 在调用无参构造方法构建 ArrayList 时使用
  7. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = { };
  8. // 存储 ArrayList 元素的数组缓冲区,数组的长度是 ArrayList 的容量,
  9. // 任何一个 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空数组
  10. // 在添加一个元素时会将数组的容量扩展为 DEFAULT_CAPACITY
  11. transient Object[] elementData;
  12. // 元素的数量 不一定和 elementData 的长度相等
  13. private int size;
  14. // 最大容量
  15. private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

构造方法

  1. // 无参构造方法,会将 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 静态变量赋值给 elementData。
  2. public ArrayList() {
  3. this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  4. }
  5. // 指定初始容量
  6. public ArrayList(int initialCapacity) {
  7. if (initialCapacity > 0) {
  8. // 指定的初始容量 > 0 则初始化 elementData 数组
  9. this.elementData = new Object[initialCapacity];
  10. } else if (initialCapacity == 0) {
  11. // EMPTY_ELEMENTDATA 表示用户指定初始容量是 0
  12. this.elementData = EMPTY_ELEMENTDATA;
  13. } else {
  14. // < 0 抛出异常
  15. throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
  16. }
  17. }
  18. // 将指定的 Collection 中的元素添加到当前 ArrayList
  19. public ArrayList(Collection<? extends E> c) {
  20. // 转换为数组 如果 c 是 null 则抛出 NullPointerException
  21. elementData = c.toArray();
  22. if ((size = elementData.length) != 0) {
  23. // c.toArray might (incorrectly) not return Object[] (see 6260652)
  24. if (elementData.getClass() != Object[].class)
  25. elementData = Arrays.copyOf(elementData, size, Object[].class);
  26. } else {
  27. // 如果数组的长度是0,则将 EMPTY_ELEMENTDATA 赋给 elementData
  28. this.elementData = EMPTY_ELEMENTDATA;
  29. }
  30. }

ArrayList#size

  1. public int size() {
  2. return size;
  3. }

ArrayList#isEmpty

  1. public boolean isEmpty() {
  2. return size == 0;
  3. }

ArrayList#add(E)

在列表的末尾添加一个新元素。

  1. public boolean add(E e) {
  2. // 判断当前数组是否能够容纳新元素,传递 size + 1
  3. ensureCapacityInternal(size + 1); // Increments modCount!!
  4. // 将数组的 size++ 赋值且长度 +1
  5. elementData[size++] = e;
  6. return true;
  7. }
  8. private void ensureCapacityInternal(int minCapacity) {
  9. // 如果 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,即通过无参构造方法创建的 ArrayList 实例
  10. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  11. // 从 DEFAULT_CAPACITY 和 minCapacity 找一个最大值作为容量
  12. minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  13. }
  14. ensureExplicitCapacity(minCapacity);
  15. }
  16. private void ensureExplicitCapacity(int minCapacity) {
  17. // 对修改计算器 + 1
  18. modCount++;
  19. // 如果当前需要的容量大于 elementData 数组长度需要扩容
  20. if (minCapacity - elementData.length > 0)
  21. grow(minCapacity);
  22. }
  23. // 扩容
  24. private void grow(int minCapacity) {
  25. // 当前 elementData 数组的长度
  26. int oldCapacity = elementData.length;
  27. // 新数组的长度: oldCapacity >> 1 是当前数组长度的一半 如 10 就是 5,5 就是 2,然后加上当前数组的长度,也就是 1.5 倍
  28. int newCapacity = oldCapacity + (oldCapacity >> 1);
  29. // 如果新数组的长度仍然小于 minCapacity 则使用 minCapacity(需要的长度) 作为新数组的长度,者意味着下次添加元素要再次扩容
  30. if (newCapacity - minCapacity < 0)
  31. newCapacity = minCapacity;
  32. // 如果新数组长度大于 MAX_ARRAY_SIZE 需要重新选取数组长度 Integer.MAX_VALUE OR MAX_ARRAY_SIZE OR 抛出 OutOfMemoryError
  33. if (newCapacity - MAX_ARRAY_SIZE > 0)
  34. newCapacity = hugeCapacity(minCapacity);
  35. // 创建新数组并将原数组元素复制,最后赋值给 elementData
  36. elementData = Arrays.copyOf(elementData, newCapacity);
  37. }
  38. private static int hugeCapacity(int minCapacity) {
  39. if (minCapacity < 0) // overflow
  40. throw new OutOfMemoryError();
  41. return (minCapacity > MAX_ARRAY_SIZE) ?
  42. Integer.MAX_VALUE :
  43. MAX_ARRAY_SIZE;
  44. }

ArrayList#add(int, E)

在此列表的指定位置插入指定的元素,将指定位置的后续元素向右移动。

  1. public void add(int index, E element) {
  2. // 检查是否越界
  3. rangeCheckForAdd(index);
  4. // 判断是否需要扩容,同 add(E)
  5. ensureCapacityInternal(size + 1); // Increments modCount!!
  6. // 将 index 后面的元素向后移动一位。
  7. System.arraycopy(elementData, index, elementData, index + 1,
  8. size - index);
  9. // 将元素插入到 index 位置
  10. elementData[index] = element;
  11. // 长度加 1
  12. size++;
  13. }
  14. private void rangeCheckForAdd(int index) {
  15. if (index > size || index < 0)
  16. throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  17. }

ArrayList#addAll(java.util.Collection<? extends E>)

按照指定集合的迭代器返回的顺序,将指定集合中的所有元素追加到此列表的末尾。此方法添加的是指定集合的元素引用。

  1. public boolean addAll(Collection<? extends E> c) {
  2. // 调 toArray 方法
  3. Object[] a = c.toArray();
  4. int numNew = a.length;
  5. // 判断当前集合长度 + 指定集合长度是否需要扩容
  6. ensureCapacityInternal(size + numNew); // Increments modCount
  7. // 将指定集合元素添加到当前集合末尾
  8. System.arraycopy(a, 0, elementData, size, numNew);
  9. // 长度增加
  10. size += numNew;
  11. return numNew != 0;
  12. }

ArrayList#addAll(int, java.util.Collection<? extends E>)

从指定位置开始,将指定集合中的所有元素插入此列表。将当前位于该位置的元素(如果有)和后续元素向右移动。新元素将按照指定集合的迭代器返回的顺序出现在列表中。

  1. public boolean addAll(int index, Collection<? extends E> c) {
  2. // 越界检查
  3. rangeCheckForAdd(index);
  4. Object[] a = c.toArray();
  5. int numNew = a.length;
  6. // 判断是否需要扩容
  7. ensureCapacityInternal(size + numNew); // Increments modCount
  8. // 判断需要移动的元素数量
  9. int numMoved = size - index;
  10. if (numMoved > 0)
  11. System.arraycopy(elementData, index, elementData, index + numNew,
  12. numMoved);
  13. System.arraycopy(a, 0, elementData, index, numNew);
  14. size += numNew;
  15. return numNew != 0;
  16. }

ArrayList#get

  1. public E get(int index) {
  2. rangeCheck(index);
  3. return elementData(index);
  4. }
  5. // 越界检查
  6. private void rangeCheck(int index) {
  7. if (index >= size)
  8. throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  9. }
  10. E elementData(int index) {
  11. // 通过数组下标访问
  12. return (E) elementData[index];
  13. }

ArrayList#set

  1. public E set(int index, E element) {
  2. // 越界检查
  3. rangeCheck(index);
  4. // 当前元素
  5. E oldValue = elementData(index);
  6. // 替换新元素
  7. elementData[index] = element;
  8. // 返回旧元素
  9. return oldValue;
  10. }

ArrayList#contains

  1. public boolean contains(Object o) {
  2. // 判断 indexOf 是否 > 0
  3. return indexOf(o) >= 0;
  4. }

ArrayList#indexOf

通过 for 循环正序遍历数组元素并通过元素的 equals 方法进行比较,找到就返回 index,找不到返回 -1。

  1. public int indexOf(Object o) {
  2. if (o == null) {
  3. for (int i = 0; i < size; i++)
  4. if (elementData[i]==null)
  5. return i;
  6. } else {
  7. for (int i = 0; i < size; i++)
  8. if (o.equals(elementData[i]))
  9. return i;
  10. }
  11. return -1;
  12. }

ArrayList#lastIndexOf

通过 for 循环逆序遍历数组元素并通过元素的 equals 方法进行比较,找到就返回 index,找不到返回 -1。

  1. public int lastIndexOf(Object o) {
  2. if (o == null) {
  3. for (int i = size-1; i >= 0; i--)
  4. if (elementData[i]==null)
  5. return i;
  6. } else {
  7. for (int i = size-1; i >= 0; i--)
  8. if (o.equals(elementData[i]))
  9. return i;
  10. }
  11. return -1;
  12. }

ArrayList#remove(int)

删除此列表中指定位置的元素。向左移动后续元素。

  1. public E remove(int index) {
  2. // 越界检查
  3. rangeCheck(index);
  4. // modCount + 1
  5. modCount++;
  6. // 取出需要删除的元素
  7. E oldValue = elementData(index);
  8. // 如果删除的元素不是最后一个就需要将索引后面的元素向左移动一个位置
  9. int numMoved = size - index - 1;
  10. if (numMoved > 0)
  11. System.arraycopy(elementData, index+1, elementData, index,numMoved);
  12. // 将最后一个元素设为 null
  13. elementData[--size] = null; // clear to let GC do its work
  14. // 返回需要删除的元素
  15. return oldValue;
  16. }

ArrayList#remove(java.lang.Object)

移除第一个 equals 为 true 的元素的 index。

  1. public boolean remove(Object o) {
  2. if (o == null) {
  3. for (int index = 0; index < size; index++)
  4. if (elementData[index] == null) {
  5. fastRemove(index);
  6. return true;
  7. }
  8. } else {
  9. for (int index = 0; index < size; index++)
  10. if (o.equals(elementData[index])) {
  11. fastRemove(index);
  12. return true;
  13. }
  14. }
  15. return false;
  16. }
  17. // 私有删除方法,跳过边界检查且不返回移除的值。
  18. private void fastRemove(int index) {
  19. modCount++;
  20. int numMoved = size - index - 1;
  21. if (numMoved > 0)
  22. System.arraycopy(elementData, index+1, elementData, index,
  23. numMoved);
  24. elementData[--size] = null; // clear to let GC do its work
  25. }

ArrayList#clear

  1. // 清空 ArrayList
  2. public void clear() {
  3. modCount++;
  4. // 循环
  5. for (int i = 0; i < size; i++)
  6. // 赋 null 以便 GC
  7. elementData[i] = null;
  8. size = 0;
  9. }

ArrayList#subList

该方法返回的是 SubList 对象,是 ArrayList 的一个内部类,该对象继承了 AbstractList,实现了 RandomAccess。

  1. public List<E> subList(int fromIndex, int toIndex) {
  2. subListRangeCheck(fromIndex, toIndex, size);
  3. return new SubList(this, 0, fromIndex, toIndex);
  4. }
  5. private class SubList extends AbstractList<E> implements RandomAccess {
  6. private final AbstractList<E> parent;
  7. private final int parentOffset;
  8. private final int offset;
  9. int size;
  10. SubList(AbstractList<E> parent,
  11. int offset, int fromIndex, int toIndex) {
  12. this.parent = parent;
  13. this.parentOffset = fromIndex;
  14. this.offset = offset + fromIndex;
  15. this.size = toIndex - fromIndex;
  16. this.modCount = ArrayList.this.modCount;
  17. }
  18. }

ArrayList#sort

  1. public void sort(Comparator<? super E> c) {
  2. final int expectedModCount = modCount;
  3. // 使用 Arrays.sort 对 elementData 排序
  4. Arrays.sort((E[]) elementData, 0, size, c);
  5. if (modCount != expectedModCount) {
  6. throw new ConcurrentModificationException();
  7. }
  8. // 此方法也会将 modCount + 1。
  9. modCount++;
  10. }

ArrayList#ensureCapacity

建议存放大量元素前先调此方法手动扩容,避免自动频繁扩容影响性能。

  1. public void ensureCapacity(int minCapacity) {
  2. int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
  3. // any size if not default element table
  4. ? 0
  5. // larger than default for default empty table. It's already
  6. // supposed to be at default size.
  7. : DEFAULT_CAPACITY;
  8. if (minCapacity > minExpand) {
  9. ensureExplicitCapacity(minCapacity);
  10. }
  11. }

Itr 和 ListIterator

Itr 和 ListIterator 是 ArrayList 的内部类,分别实现了 Iterator 和 ListIterator 接口。

  1. private class Itr implements Iterator<E> { }
  2. private class ListItr extends Itr implements ListIterator<E> { }

ArrayList 的遍历方式

  • 普通 for 循环

    for(int i = 0;i < list.size();++i){
    }

  • 增加 for 循环

    for(Str s:list){
    }

  • 迭代器遍历

    ListIterator it = list.listIterator();
    while(it.hasNext()){

    1. it.next();

    }

ArrayList 删除元素的方式

  1. List<String> list = new ArrayList<String>();
  2. list.add("jj");
  3. list.add("kk");
  4. list.add("kk");
  5. list.add("rr");
  6. list.add("kk");
  7. /* * 方式一 * 当kk元素没有连续存储时,就不能全部删除集合中的kk元素 因为每删除一次元素,集合长度变小, * 但是迭代的下标没变 */
  8. for (int i = 0; i < list.size(); i++) {
  9. System.out.println(i);
  10. if (list.get(i).equals("kk")) {
  11. list.remove(i);
  12. }
  13. }
  14. /* * 方式二 * 不管kk元素是否连续,都可以全部删除 */
  15. for (int i = 0; i < list.size(); i++) {
  16. if ("kk".equals(list.get(i))) {
  17. list.remove(i);
  18. --i;// 删除了元素,迭代的下标也跟着改变
  19. }
  20. }
  21. /* * 方式三 * 不管值为"c"的元素在list中是否连续,都可以把值为"c"的元素全部删除 需保证没有其他线程同时在修改 */
  22. Iterator<String> iterator = list.iterator();
  23. while (iterator.hasNext()) {
  24. String str = iterator.next();
  25. if ("kk".equals(str)) {
  26. iterator.remove();
  27. }
  28. }

其他实现方法

ArrayList不仅实现了List中定义的所有功能,还实现了clonewriteObjectreadObject等方法。这些方法都需要与存储的数据配合,否则结果将是错误的或者克隆得到的数据只是浅拷贝,或者数据本身不支持序列化等,这些我们定义数据时注意到即可。我们主要看下其在序列化时自定义了哪些东西。

  1. //这里就能解开我们的迷惑了,elementData被transient修饰,也就是不会参与序列化
  2. //这里我们看到数据是一个个写入的,并且将size也写入了进去
  3. private void writeObject(java.io.ObjectOutputStream s)
  4. throws java.io.IOException{
  5. // Write out element count, and any hidden stuff
  6. int expectedModCount = modCount;
  7. s.defaultWriteObject();
  8. // Write out size as capacity for behavioural compatibility with clone()
  9. s.writeInt(size);
  10. // Write out all elements in the proper order.
  11. for (int i=0; i<size; i++) {
  12. s.writeObject(elementData[i]);
  13. }
  14. //modCount的作用在此体现,如果序列化时进行了修改操作,就会抛出异常
  15. if (modCount != expectedModCount) {
  16. throw new ConcurrentModificationException();
  17. }
  18. }

readObject是一个相反的过程,就是把数据正确的恢复回来,并将elementData设置好即可。

  1. private void readObject(java.io.ObjectInputStream s)
  2. throws java.io.IOException, ClassNotFoundException {
  3. elementData = EMPTY_ELEMENTDATA;
  4. // Read in size, and any hidden stuff
  5. s.defaultReadObject();
  6. // Read in capacity
  7. s.readInt(); // ignored
  8. if (size > 0) {
  9. // be like clone(), allocate array based upon size not capacity
  10. ensureCapacityInternal(size);
  11. Object[] a = elementData;
  12. // Read in all elements in the proper order.
  13. for (int i=0; i<size; i++) {
  14. a[i] = s.readObject();
  15. }
  16. }
  17. }

对于 equalshashCode方法 ArrayList 使用的仍是 AbstractList 的实现并没有重写。

发表评论

表情:
评论列表 (有 0 条评论,248人围观)

还没有评论,来说两句吧...

相关阅读