基础知识:
1,ArrayList 是 java 集合框架中比较常用的数据结构,**继承自 AbstractList,实现了 List 接口**。
2,底层采用数组实现。
3,ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
特点:
1,值可重复;
2,插入元素有序;
3,允许存在null值;
4,查询效率高。
源码分析
初始化时定义的部分参数:
//初始化默认集合个数
private static final int DEFAULT_CAPACITY = 10;
//集合元素个数
private int size;
add():(初始化)
public boolean add(E e) {
//新创建的ArrayList在添加第一个值时,会先进行数组初始化操作,初始化完成后,会将第一个值添加到数组。
//此时size为0,所以size+1 = 1;
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//初始化时elementData集合为空,minCapacity =1;
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//此时elementData为空,minCapacity =1,下列if判断成立
//DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为新建的数组,为空
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//此时返回最大的数;
//DEFAULT_CAPACITY 为默认值10 ,minCapacity =1;
//最终返回10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
//此时 calculateCapacity(elementData, minCapacity) 这个方法最后返回的值为10
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
//add操作次数+1
modCount++;
//此时 minCapacity =10 ,elementData.length =0,下列if判断成立
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
//oldCapacity = 0
int oldCapacity = elementData.length;
// 右移1位 4 >> 1 = 4/2 = 2
// 左移1位 4 << 1 = 4*2 = 8
//newCapacity = oldCapacity + oldCapacity/2
//此时 newCapacity = 0,minCapacity = 10
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
//此时 newCapacity = 10
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//数组拷贝,此处为数组的初始化,新数组容量为10
elementData = Arrays.copyOf(elementData, newCapacity);
}
public boolean add(E e) {
ensureCapacityInternal(size + 1);
// 数组初始化完成后,此时数组长度为10,开始将第一个值插入
//此时size=0,所以 elementData[0] =e, size++
elementData[size++] = e;
//此时ArrayList添加第一个值的操作完成。
return true;
}
add() (扩容)
public boolean add(E e) {
//此时,假设size长度为10,size+1 = 11;
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//elementData.length = 10,minCapacity =11
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//此处elementData不为空,if条件判断不成立,返回minCapacity =11
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//此时 minCapacity =11,elementData.length = 10,条件成立
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
//此时minCapacity = 11
//oldCapacity = 10
int oldCapacity = elementData.length;
//newCapacity = oldCapacity + oldCapacity/2 = 15
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 最后进行数组拷贝
elementData = Arrays.copyOf(elementData, newCapacity);
}
get(index):
public E get(int index) {
//判断是否越界
rangeCheck(index);
//然后返回下标
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
//如果越界抛出异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
remove(index):
public E remove(int index) {
//判断是否越界
rangeCheck(index);
//删除次数+1
modCount++;
//获取到要删除下标的值
E oldValue = elementData(index);
//计算要移动的个数(比如有5个元素,要删除第4个,则 numMoved = 5 - 3 -1 = 1,要移动一格 )
int numMoved = size - index - 1;
if (numMoved > 0)
//此次为浅拷贝,将index+1的值拷贝到index上,此时,原先最后一个元素依然存在,数组只做了单纯的复制操作
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//此处将最后一个值置位null,下次垃圾回收时将会被回收
// clear to let GC do its work
elementData[--size] = null;
//返回删除的值
return oldValue;
}
ArrayList的一个坑
边遍历边删除元素(错误)
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("2");
list.add("3");
// for循环遍历删除
for (int i =0;i<list.size();i++){
if("2".equals(list.get(i))){
list.remove(i);
}
}
System.out.println(list);
}
//打印结果
1
2
3
第二个2没有被删除,原因是for循环时,指针会向下走,而ArrayList删除元素后,之后元素位置会向前移动,结果会导致部分元素无法被删除。
解决方案:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("2");
list.add("3");
for (int i =0;i<list.size();i++){
if("2".equals(list.get(i))){
list.remove(i);
//每次删除后,将指针向前拨一位即可解决问题
i--;
}
}
System.out.println(list);
}
打印结果:
1
3
第二种方式为迭代器遍历,这种方式比较推荐:
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("2");
list.add("3");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
if ("2".equals(iterator.next())){
iterator.remove();
}
}
System.out.println(list);
思考题:
1,ArrayList增删是怎么做的?为什么会慢?
答:在新增时需要先进行数组长度的校验,如果长度不够,会进行扩容。扩容时才用了位运算,右移一位。
扩容完成后,会进行数组的拷贝。如果数组大的话,拷贝的速度是很慢的。
所以慢的原因主要有两点:1,数组扩容;2,数组拷贝;
2,ArralList(int initialCaacity)会不会初始化数组大小?
答:会初始化数组大小,但是List大小没有变化,因为List的大小是返回size的,只有当执行add()方法的时候才会改变List的大小。
ArrayList<String> list = new ArrayList<>(10);
System.out.println(list.size());
list.add("q");
System.out.println(list.size());
//打印
0
1
Process finished with exit code 0
还有一个点:
如果只是初始化了数组,没有改变list的大小,那么此时执行list,set()方法会抛出数组越界异常,因为list.size()=0.
ArrayList<String> list = new ArrayList<>(10);
System.out.println(list.size());
list.set(1,"1");
//打印
0
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 1, Size: 0
3,ArrayList增删速度一定慢吗?
不一定,增删速度主要取决于增删元素与数组末尾的位置,位置越近,速度越快。
如果元素增删是在数组最后一个元素,此时不涉及到其它元素的拷贝,那么速度就很快。
如果元素增删是在头部,那么要拷贝的元素会很多,速度就会变慢,特别是数组特别大的时候。
4,ArrayList的遍历和LinkedList的遍历性能比较?
答:ArrayList的遍历速度要快于LinkedList的遍历速度。
主要原因是ArrayList内存的连续性。它存储时占用一片连续的存储空间,可以大幅度降低读取内存的性能开销。
5,ArrayList如何优化?
答:性能损耗的点主要有两方面:1,数组扩容;2,数组拷贝;
优化时可以从这两方面入手。扩容方面,尽量给一个合理的初始化值,减少扩容次数。数组拷贝方面:可以重写add方法,使用尾插的方式,减少数组拷贝次数。
版权声明:本文为m0_37688047原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。