集合
/*
for (Integer integer : arrayList){
System.out.println(arrayList.remove(0));
break;
}
AbstractCollection<E> 实现了 Collection<E> 部分方法,还有两个方法,如size()和iterator() 还没有被实现
将让子类去实现。
AbstractList<E> 继承了 AbstractCollection<E> 实现了 List<E> 抽象类中的部分方法,还有一个方法
size() 没有被实现
List<E> 接口 继承了 Collection<E> 自身增加了部分方法,如set。
ArrayList<E> 继承了 AbstractList<E> 实现了 List<E>
Collection<E> 是最基层的抽象,给出基本的集合操作方法,之后在这基础上进行完善。
问:为什么AbstractList<E> 继承了 AbstractCollection<E> 实现了 List<E> ,
ArrayList<E> 继承了 AbstractList<E> 实现了 List<E>也要如此?
回答:这种模式叫模板方法设计模式,部分方法已经被实现,没有被实现的方法后续已经被实现,只需要根据后续实现
之前的方法,你必须这样实现,只能这样实现。减少了代码量的同时捋清的代码逻辑。为什么父类实现了List<E>
子类还要继续实现List<E>这层关系?因为ArrayList<E> 只有AbstractList<E>这层关系的话,无法拿到List<E> 中
的抽象方法。为了实现动态代理。
简单来说:都是融合,但是都没有实现完善,再继承,再实现,再继承,直到最终完善。 ArrayList<E> 就是这样实现的。
LinkedList<E> 和 ArrayList<E> 实现的方法是一样的,但是实现的原理不一样。这就是模板模式。
ArrayList<E>:
构造一个数组并将数组赋值给elementData
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
构造一个空的对象数组:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
在数据使用add方法的时候使用了:
ensureCapacityInternal(size + 1);
将确保list内部容器也就是数组是否多余的空间存储。
如果保存列表对象的数组为初始分配的数组也就是数组长度为零的数组,那么就进行分配初始数组。
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //用来记录数组的修改次数
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
如果最小容量 - elementData.length 大于 0 才会进行增长,否则没必要增长。
之后会进入grow()方法体:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; // 0
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
关键代码: elementData = Arrays.copyOf(elementData, newCapacity);
如果满足数组增加的条件,那么通过arrays这个工具类对数组进行复制,大小为新的容量。
由上可知,我们在创建数组时最好定义一个初始化数组容量,避免在add操作时消耗cpu,为什么呢?
在数组进行复制的时候消耗是非常大的。
int newCapacity = oldCapacity + (oldCapacity >> 1);
增长因子: 0.5,二分之一。
为什么arraylist 删除慢,通过底层源码可以发现,操作删除数组将删除位置以后的数据进行了复制,复制到前面去,之后
将最后一位设置为null,让最后一位没有引用,之后GC会对最后一位进行回收。
为什么发现阿里的开发规范中写道:推荐创建arraylist的时候,推荐初始化长度,因为如果不够底层会自动对数组长度扩容
50%,每次不够就扩容50%,扩容是通过复制进行的,复制特别影响效率,有效的减少扩容次数,可以提高执行效率。
代码示范:
final int count = 20 * 100000;
List<Integer> list = new ArrayList<>();
long begin = System.currentTimeMillis();
for(int i = 0; i < count ; i++) {
list.add(i);
}
System.out.println("没有设置ArrayList初始容量: " + (System.currentTimeMillis() - begin) + " ms");
list = new ArrayList<>(count);
begin = System.currentTimeMillis();
for(int i = 0; i < count ; i++) {
list.add(i);
}
System.out.println("设置了ArrayList初始容量: " + (System.currentTimeMillis() - begin) + " ms");
执行结果:
没有设置ArrayList初始容量: 110 ms
设置了ArrayList初始容量: 20 ms
可以较为明显的看出两者的执行效率差距。这就是因为没有初始容量,会多次执行:
elementData = Arrays.copyOf(elementData, newCapacity);
这样就会很影响效率的代码。
clear():
循环数组。全部引用变为null,让GC全部回收。
还有一个重写的clear():
其中使用到了迭代器。
*/