- ArrayList
- 基于数组实现,是一个动态数组,扩容机制请看下文
- 非线程安全
- 实现了Serializable接口,支持序列化,能够通过序列化传输,
- 实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问
- 实现了Cloneable接口,能被克隆。
- ArrayList 扩容机制(基于JDK1.8.0_92)
- 如果未定义初始大小,则容量为0,第一次扩容时直接扩容为10,之后每次扩容增加当前容量的一半容量;
- 如果定义初始大小,之后每次扩容增加当前容量的一半容量;
public static void main(String[] args) {
List list = new ArrayList();
System.out.println(ListDemo.getArrayListCapacity(list));//容量为0
for(int i = 1;i<=1;i++){
list.add(i);
}
System.out.println(ListDemo.getArrayListCapacity(list));//添加1个元素后,总元素1,容量扩容为10
for(int i = 1;i<=10;i++){
list.add(i);
}
System.out.println(ListDemo.getArrayListCapacity(list));//再添加10个元素后,总元素11,容量扩容为15
for(int i = 1;i<=5;i++){
list.add(i);
}
System.out.println(ListDemo.getArrayListCapacity(list));//再添加5个元素后,总元素16,容量扩容为22
for(int i = 1;i<=7;i++){
list.add(i);
}
System.out.println(ListDemo.getArrayListCapacity(list));//再添加7个元素后,总元素23,容量扩容为33
}
输出结果:
0
10
15
22
33
结论:由输出结果可知,如果未定义初始大小,则容量为0,第一次扩容时直接扩容为10,之后每次扩容增加当前容量的一半容量;
public static void main(String[] args) {
List list = new ArrayList(1);
System.out.println(ListDemo.getArrayListCapacity(list));//容量为1
for(int i = 1;i<=2;i++){
list.add(i);
}
System.out.println(ListDemo.getArrayListCapacity(list));//添加2个元素后,总元素2,容量扩容为2
for(int i = 1;i<=1;i++){
list.add(i);
}
System.out.println(ListDemo.getArrayListCapacity(list));//再添加1个元素后,总元素3,容量扩容为3
for(int i = 1;i<=1;i++){
list.add(i);
}
System.out.println(ListDemo.getArrayListCapacity(list));//再添加1个元素后,总元素4,容量扩容为4
for(int i = 1;i<=1;i++){
list.add(i);
}
System.out.println(ListDemo.getArrayListCapacity(list));//再添加1个元素后,总元素5,容量扩容为6
for(int i = 1;i<=2;i++){
list.add(i);
}
System.out.println(ListDemo.getArrayListCapacity(list));//再添加2个元素后,总元素7,容量扩容为9
}
输出结果:
1
2
3
4
6
9
结论:由输出结果可知,如果定义初始大小,之后每次扩容增加当前容量的一半容量;
// 获取ArrayList容量
public static int getArrayListCapacity(List arrayList) {
try {
Field field = ArrayList.class.getDeclaredField("elementData");
field.setAccessible(true);
Object[] objects = (Object[])field.get(arrayList);
return objects.length;
} catch (NoSuchFieldException e) {
e.printStackTrace();
return -1;
} catch (IllegalAccessException e) {
e.printStackTrace();
return -1;
}
}
- ArrayList 实现了Serializable接口,为什么它的elementData属性用 transient修饰?
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
- 用transient修饰的属性不会被序列化
- ArrayList在序列化的时候会调用它的writeObject()方法,这个方法是根据其size将elementData中的元素写进ObjectOutputStream,;反序列化时调用readObject()方法,从ObjectInputStream获取size和element,再恢复到elementData。
- 原因是elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。
- ArrayList 添加、删除元素
- ArrayList 每次add元素时都会调用 ensureCapacityInternal(size + 1); 检查数组容量是否够用,如果不够用就进行扩容
- ArrayList 扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。
- ArrayList 新增、删除元素时会进行元素的移位
- ArrayList的实现中大量地调用了Arrays.copyof()和System.arraycopy()方法(Arrays.copyof()内部会创建一个新的数组,然后调用System.arraycopy()进行数组复制,System.arraycopy()是一个native方法,由C语言实现)
- 常用方法
// 数组修身,即将list长度修为其size大小的长度
// 原理 利用Arrays.copyOf(elementData, size);又copy到一个新的数组中
trimToSize()
// 转为静态数组toArray
// 原理 利用Arrays.copyOf(elementData, size);又copy到一个新的数组中
toArray()
- ArrayList 实现线程安全的方法
// 1、使用Collections类中提供的静态工厂方法创建的同步容器类
// 注:此方法并非绝对同步,还会存在线程安全问题
List list = Collections.synchronizedList(new ArrayList());
- ArrayList 和 Vector
- 都采用数组方式存储数据
- vector 是线程安全的,会影响性能
- 如果不考虑到线程的安全因素,ArrayList 效率比较高。
- Vector 默认容量为10,默认扩容为当前容量的1倍;也可以根据构造方法指定容量和扩容倍数
- 写一段代码在遍历 ArrayList 时移除一个元素
//使用迭代器
Iterator it= list.iterator();
while(it.hasNext()) {
if(...) {
it.remove();
}
}
//使用lanbda表达式
list.removeIf(integer -> integer == 2);
列子:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(6);
for(int i = 1;i<=6;i++){
list.add(i);
}
list.forEach(integer -> {
System.out.println(integer);
});
System.out.println("删除元素后:");
list.removeIf(integer -> integer == 2);
list.forEach(integer -> {
System.out.println(integer);
});
}
输出结果:
1
2
3
4
5
6
删除元素后:
1
3
4
5
6
- 简单集合关系图