1. 特点

2. 内存结构图

3. 底层原理

4. 扩容方法

image-20220923161313175

这里需要注意一点,因为扩容操作涉及内存申请和数据搬移,是比较耗时的。所以,如果事先能确定需要存储的数据大小,最好 在创建 ArrayList 的时候事先指定数据大小

比如我们要从数据库中取出 10000 条数据放入 ArrayList。我们看下面这几行代码,你会发现,相比之下,事先指定数据大小可以省掉很多次内存申请和数据搬移操作。

4.1. jdk 区别

jdk8:ArrayList 中维护了 Object[] elementData,初始容量为 0. 第一次添加时,将初始 elementData 的容量为 10 再次添加时,如果容量足够,则不用扩容直接将新元素赋值到第一个空位上 如果容量不够,会扩容 1.5 倍

jdk7:ArrayList 中维护了 Object[] elementData,初始容量为 10. 添加时,如果容量足够,则不用扩容直接将新元素赋值到第一个空位上 如果容量不够,会扩容 1.5 倍

jdk7 和 jdk8: 区别:jdk7 相当于饿汉式,创建对象时,则初始容量为 10 jdk8 相当于懒汉式,创建对象时,并没有初始容量为 10,而在添加时才去初始容量为 10

5. system.arraycopy

6. Fail-Fast⭐️🔴

ArrayList 也采用了快速失败的机制,通过记录 modCount 参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

modCount 字段,表示集合结构性修改的次数。所谓结构性修改,指的是影响 List 大小的修改,所以 add 操作必然会改变 modCount 的值。

而在需要迭代时,获取到的迭代器会先读取 modCount 到自己的属性 expectedModCount 中,在迭代操作时,先执行 checkForComodification 方法确定是否有并发修改,如果有直接抛出并发修改异常,防止数据错乱。


7. Fast-Safe⭐️🔴

java.util 包下面的所有的集合类都是快速失败的,而 java.util.concurrent 包下面的所有的类都是安全失败的。快速失败的迭代器会抛出 ConcurrentModificationException 异常,而安全失败的迭代器永远不会抛出这样的异常。

8. 与数组比较

  1. Java ArrayList 无法存储基本类型,比如 int、long,需要封装为 Integer、Long 类,而 Autoboxing、Unboxing 则有一定的性能消耗,所以如果特别关注性能,或者希望使用基本类型,就可以选用数组。

  2. 如果数据大小事先已知,并且对数据的操作非常简单,用不到 ArrayList 提供的大部分方法,也可以直接使用数组。

  3. 还有一个是我个人的喜好,当要表示多维数组时,用数组往往会更加直观。比如 Object[][] array;而用容器的话则需要这样定义:ArrayList<ArrayList > array

我总结一下,对于业务开发,直接使用容器就足够了,省时省力。毕竟损耗一丢丢性能,完全不会影响到系统整体的性能。但如果你是做一些非常底层的开发,比如开发网络框架,性能的优化需要做到极致,这个时候数组就会优于容器,成为首选。

9. 与 Vector 比较

  1. ArrayList 创建时的大小为 0;当加入第一个元素时,进行第一次扩容时,默认容量大小 10。
  2. ArrayList 每次扩容都以当前数组大小的 1.5 倍去扩容。
  3. Vector 创建时的默认大小为 10。
  4. Vector 每次扩容都以当前数组大小的 2 倍去扩容。当指定了 capacityIncrement 之后,每次扩容仅在原先基础上增加 capacityIncrement 个单位空间。
  5. ArrayList 和 Vector 的 add、get、size 方法的复杂度都为 O(1),remove 方法的复杂度为 O(n)。
  6. ArrayList 是非线程安全的,Vector 是线程安全的。

10. 最佳实践

 假如元素的大小是固定的,而且能事先知道,我们就应该用 Array 而不是 ArrayList。
 有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算 hash 值或者是扩容。
 为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的 ClassCastException。
 使用 JDK 提供的不变类 (immutable class) 作为 Map 的键可以避免为我们自己的类实现 hashCode() 和 equals() 方法。
 编程的时候接口优于实现。
 底层的集合实际上是空的情况下,返回长度是 0 的集合或者是数组,不要返回 null。

11. 参考

11.1. 尚硅谷

https://blog.csdn.net/wz249863091/article/details/52853360

尚硅谷大数据共 50 阶段 - 有 ML/尚硅谷 - 第 04 阶段《集合》/day18/video/09 ArrayList 的底层和源码分析.mp4

11.2. 黑马

11.2.1. 视频

https://www.bilibili.com/video/BV1zr4y117JM?p=34&spm_id_from=pageDriver&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

11.2.2. 资料