概述
ArrayList是java对动态数组的功能实现方式。
预备知识
数组原理: 数组在内存中是连续分配的,这意味着数组中的元素在内存中彼此相邻。这种内存布局使得数组支持通过索引快速访问元素。
优点:
1、快速随机访问:数组支持通过索引在常数时间内访问元素,因为可以直接计算出元素在内存中的地址。(计算公式:memoryAddress = baseAddress + index * elementSize)
2、内存效率: 数组的内存分配是连续的,不需要额外的指针或链接信息,因此相对于某些数据结构(如链表)来说,数组在内存上的开销较小。
3、预知大小: 因为数组在创建时需要指定大小,所以在需要一次性存储一定数量的元素时,数组是一种合适的选择。
缺点:
1、大小固定:数组的大小在创建时就确定了,后续无法动态改变。如果需要动态调整大小,就需要创建一个新的数组,并将元素从旧数组复制到新数组中,这可能会导致性能开销。
2、插入和删除开销大:在数组中插入和删除元素可能需要移动其他元素,特别是在中间位置进行插入和删除操作。这会导致时间复杂度为 O(n)。
3、浪费空间: 如果数组大小过大,而实际存储的元素较少,就会浪费内存空间。
4、不支持异构元素: 数组要求所有元素类型相同,无法直接存储不同类型的数据。
关键知识点
扩容(system.copy):
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
这个方法的底层实现是通过 JNI(Java Native Interface)调用本地代码来完成的。在不同的操作系统和硬件平台上,底层的实现方式会有所不同。通常情况下,操作系统提供了对内存的直接访问机制,System.arraycopy 方法会利用这些机制来实现高效的数组复制。在具体实现中,System.arraycopy 方法可能会使用一些底层的内存操作指令来实现复制操作,例如在 x86 架构上可以使用 mov 指令来快速移动内存数据。这样做可以使数组复制的过程变得非常高效,减少了不必要的中间步骤。
适用场景及使用注意点
适应场景: 读多写少。
使用注意点:
1 预判业务情况: 指定容量避免扩容。
2 使用泛型: 始终在声明 ArrayList 时使用泛型,以保证类型安全。避免在遍历时出现类型转换问题。
3 避免频繁的删除。
4 考虑性能: 大数据量时考虑其他数据结构代替。
5 批量操作: ArrayList 提供了 addAll、removeAll、retainAll 等方法,可以用来执行批量操作,避免多次循环。
从计算机硬件的角度理解ArryList性能访问快的原因
1 高效的内存访问 :
当你要访问 ArrayList 中的某个元素时,计算机可以直接根据元素的索引计算出在内存中的物理地址,然后通过内存总线直接读取这个地址上的数据,这个过程非常高效。这也被称为“直接寻址”。
2 硬件缓存友好: 计算机内部有多级缓存,其中 L1 缓存是最快的,L2、L3 缓存速度逐级递减。连续存储的数据可以更好地利用缓存,因为一旦访问了一个地址,它周围的数据很可能会被加载到缓存中,从而加速后续的访问。
3 预取机制: 当你访问了一个内存地址时,计算机很可能会预测你接下来会访问哪些地址,然后提前将这些地址上的数据加载到缓存中。连续存储可以更好地利用这种预取机制,因为它们之间的地址关系更紧密。
关联的数据结构
并发安全:CopyOnWriteArrayList,Collections.synchronizedList()。