2-ArrayList底层结构和源码分析
2-ArrayList底层结构和源码分析
介绍汇总:
- ArrayList的注意事项
- ArrayList的运行重要步骤
- 补充
1-ArrayList的注意事项
- ArrayList 允许添加所有的元素,包括 null ,而且还可以多个 null 。
- ArrayList 是由数组来实现数据存储的。
- ArrayList 基本等同于 Vector ,除了 ArrayList 是线程不安全(执行效率高)看源码。在多线程情况下,不建议使用 ArrayList。
// 此为 ArrayList 的 插入数据的方法
// 该方法并没有关键字 synchronized 修饰,可以表明 ArrayList 是线程不安全的
// synchronized 大概是加锁的意思
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
2-ArrayList的运行重要步骤
- ArrayList 中维护了一个 Object 类型的数组 elementData 。(debug 看源码
transient Object[] elementData;
// 先来解释 transient 关键字
/*
在Java中,transient关键字是一个变量修饰符,用来表示一个字段不应该被序列化。
当一个对象被序列化时(比如,通过ObjectOutputStream),对象的所有变量都会被写入到序列化文件中,
除了那些用transient修饰的变量。这样,当对象被反序列化时(比如,通过ObjectInputStream),
transient变量将不会被恢复,它们的值将会是类型的默认值(比如,int的默认值是0,
对象的默认值是null)。
*/
/*
transient关键字主要用于以下情况:
敏感信息:如果对象的某个字段包含敏感信息,如密码或个人身份识别信息,那么应该使用transient修饰该字段,以防止这些信息被序列化到文件中。
无法序列化的对象:如果对象的某个字段引用了另一个无法序列化的对象,那么这个字段也应该用transient修饰,否则序列化过程将抛出NotSerializableException异常。
派生字段:如果对象的某个字段的值可以从其他字段派生出来,那么该字段可以用transient修饰,以节省序列化和反序列化的时间和空间。
*/
// 解释一下 elementData 的作用
/*
在 Java 的 ArrayList 类中,transient Object[] elementData; 是一个非常重要的成员变量,它用于存储 ArrayList 中的元素。
ArrayList 是一个动态数组,能够根据需要自动扩容以存储更多的元素,而 elementData 就是用来实际存储这些元素的数组。
transient 关键字在这里的作用是阻止 elementData 数组被自动序列化。
由于 ArrayList 实现了 Serializable 接口,它支持序列化操作,
但是 elementData 数组中的元素可能包含不应该被序列化的数据,
或者数组本身的大小(容量)可能远大于实际存储的元素数量,导致序列化后的数据包含大量无用信息。
因此,ArrayList 提供了两个私有方法 writeObject(java.io.ObjectOutputStream s) 和 readObject(java.io.ObjectInputStream s) 来控制序列化和反序列化的过程。
在序列化时,writeObject 方法会先检查 elementData 数组的大小,如果它大于实际存储的元素数量,那么就会创建一个新的数组,只包含实际存储的元素,并将这个新数组序列化。
在反序列化时,readObject 方法会负责创建一个新的 ArrayList 实例,并恢复其元素。
总之,transient Object[] elementData; 在 ArrayList 中的作用是作为存储元素的数组,
而 transient 关键字则用于控制这个数组的序列化行为,确保序列化后的数据不包含无用信息。
*/
-
当创建 ArrayList 对象是,若使用的是无参构造器,则初始的 elementData 容量为 0, 第一次添加,则扩容 elementData 为 10,若再次扩容,则扩容 elementData 为 1.5 倍(newCapacity = oldCapacity + (oldCapacity >> 1))。
-
若使用指定大小的构造器,则初始 elementData 容量为指定大小,若再次扩容,则直接扩容 elementData 为 1.5 倍。
-
ArrayList 的扩容过程
注:这些流程图使用 debug 来完成。
// 扩容流程代码
ArrayList arrayList = new ArrayList();
// ArrayList arrayList = new ArrayList(8);
for (int i = 1 ; i <= 10 ; i ++) {
arrayList.add(i) ;
}
for (int i = 11 ; i <= 15 ; i ++) {
arrayList.add(i) ;
}
arrayList.add(100) ;
arrayList.add(200) ;
arrayList.add(null) ;
for (Object object : arrayList) {
System.out.println(object);
}
- 注意
ArrayList 的无参构造器与指定大小的有参构造器,区别主要是刚开始初始化内存数组的不一样.
// 无参构造器
public ArrayList() {
this.elementData = 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);
}
}
3-补充
// 指定数组列表的大小为 0 的话,数组列表内部
// 会将 elementData = EMPTY_ELEMENTDATA
// 区别于 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
// 从源码中看,虽然都是空数组,但是他们扩容时不同
// DEFAULTCAPACITY_EMPTY_ELEMENTDATA 第一次扩会扩成容量为 10 ,之后就是其 1.5 倍扩
// EMPTY_ELEMENTDATA 首次及第二次扩都以 minCapcity 来扩,之后可能都是其 1.5 倍扩
ArrayList arrayList = new ArrayList(0);
for (int i = 1 ; i <= 10 ; i ++) {
arrayList.add(i) ;
}
for (int i = 11 ; i <= 15 ; i ++) {
arrayList.add(i) ;
}
arrayList.add(100) ;
arrayList.add(200) ;
arrayList.add(null) ;
for (Object object : arrayList) {
System.out.println(object);
}