前言
ArrayList是我们非常常用的数据存储类,在这篇文章里我们就了解一下ArrayList的内部数据结构,从最开始构造函数说起
构造
ArrayList<Object> arrayList1 = new ArrayList<>();
ArrayList<Object> arrayList2 = new ArrayList<>(10);
ArrayList<Object> arrayList3 = new ArrayList<>(arrayList1);
这里是ArrayList的三个构造函数我们一个个往下看它对源码
首先贴上空参构造的源码
transient Object[] elementData; // non-private to simplify nested class access
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
从空参构造上可以看出ArrayList中是使用数组对数据进行存储,只是这里因为我们没有添加数据所有是使用一个length为0的数组
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);
}
}
这是单参数构造,需要传入容器长度,会根据这个长度创建了一个数组对数据进行存储,当然这个长度不是固定的,当达到最大容量是对进行扩容后面我们会看到.
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
//注释1
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
//注释2
elementData = a;
} else {
//注释3
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
注释1: 将arrayList的size变量置为传递的数组长度,这样之后在添加数据就是从这个size++;
注释2: 如果传递的是ArrayList就直接将传递的这个arrayList中的数组赋值给当前arrayList
注释3: 是将传进来Collection中的数组中的数据copy到一个新的数据并且将这个新的数据指向当前ArrayList中的elementData
接下来我们看下ArrayList的增删改查
增加
public boolean add(E e) {
//这个变量主要作用为在迭代器中增删
//使其不抛出异常不是我们这次要掌握的重点可以暂时忽略
modCount++;
add(e, elementData, size);
return true;
}
//真正添加
private void add(E e, Object[] elementData, int s) {
//当s == elementData.length说明现在的数据已经存储满了需要扩容
if (s == elementData.length)
elementData = grow();
//添加数据到最后一位
elementData[s] = e;
//存储完数据,维护的size+1
size = s + 1;
}
//扩容
private Object[] grow() {
return grow(size + 1);
}
//扩容的具体实现
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
//注释1
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//注释2
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
//注释3
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
//注释4
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
我们这里重点看一下扩容这个函数干了些什么
注释1: 判断当前是不是以空参数构造构建的ArrayList并且是还没有添加过数据的如果是的则回执行到注释4
注释2: 这里是算出需要扩容到什么长度,这个扩容规则是至少将容量扩大1,通常情况是扩容到之前容量的1.5倍
注释3: 这里是将之前数组的中的数据copy新的数组中并且指向新的数组
注释4: 这一行代码是将arrayList中的数组扩容到10的长度
指定位置添加数据也是很常用的方法,直接上源码
public void add(int index, E element) {
//检查需要插入的位置是否合法
//如果当前size = 10,而要插入的数据位置为11这时候就是不合法的需要抛出异常
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
//判断是否需要扩容
//加入ArrayList的Size = 10, 这个函数中的index也等于 10 这时候就需要扩容
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
//注释1
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
//将数据添加到指定位置
elementData[index] = element;
size = s + 1;
}
这里重点看下注释1
指定位置插入数据,机智的小伙伴就会想到原来这个位置的数据是不是被覆盖了,看到这行代码相信大家心里就有数了
这里会把以传入的下标为开始,size为结束的数据后往后移动一个位置使得index位置空出来让新的数据进行插入.
删除
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
删除有两个方法,方法1为指定对象删除,通过遍历的方式比较对象是否是相等如果相等就将这个位置的数据进行置null操作并且将之后的数据相前移动一位,且将最后一个数据设为null且size -1
方法二则是直接根据下标将对应位置设置为null,并且将后续的数据向前移动一位,且将最后一个数据设为null且size -1
修改
public E set(int index, E element) {
Objects.checkIndex(index, size);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
修改则更加简单直接将指定位置的元素设置为新的元素不需要移动别的元素
查询
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
查询操作则是先判断是要取出的数据下标是否合法,如果超出了size -1就抛出
outOfBoundsCheckIndex,合法就直接通过下标取出数据
总结
在源码中我们看到了ArrayList中增删改查中做的各种操作,我们会发现对数据进行中间插入或者是删除都会触发数据位移的操作,添加大量的数据会使ArrayList进行频繁的扩容操作这个多少会对性能有所影响,所以在这些场景中应该尽量避免使用ArrayList