我们了解了[数组](Android回看——数组 - 简书 (jianshu.com)
),这就看看我们使用的数组结构:ArrayList。
ArrayList基本上是Android中最常使用的集合类型了,从名字也可以看出来它是一个数组列表,是用一个数组存储操作我们需要的数据。
基本参数
/**默认初始容量*/
private static final int DEFAULT_CAPACITY = 10;
/**用于空实例的共享空数组实例*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**共享的空数组实例,用于默认大小的空实例。 我们将此与EMPTY_ELEMENTDATA区别开来,以了解添加第一个元素时需要添加多少*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**存储元素的数组,在添加第一个元素时将会扩充至10*/
transient Object[] elementData;
/**ArrayList内的元素个数*/
private int size;
可以看到ArrayList内定义了两个空数组,和一个值为10的参数(注释表示初始容量,但其实是分情况的),数组长度不一定是等于size的,size是实际元素个数的数量。
构造函数
第一种:带初始容量的构造函数
/** * 构造一个具有指定初始容量的空列表 * * @param initialCapacity 列表的初始容量 */
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//如果初始容量大于0
this.elementData = new Object[initialCapacity];//elementData赋值为一个初始容量为initialCapacity的Object数组
} else if (initialCapacity == 0) {//如果初始容量等于0
this.elementData = EMPTY_ELEMENTDATA;//elementData赋值为空实例数组
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
第二种:不带参的构造函数
/** * Constructs an empty list with an initial capacity of ten. * 构造一个初始容量为10的空列表 */
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//赋值为已创建的空数组
}
注意,这里源码注释是初始值为10的空数组,实际上给的是一个之前定义的空数组,那这里?岂不是注释错了?
其实以前老版本的JDK是一个大小为10的空数组的,现在修改为空数组,注释却没有修改。像极了平时你修改了代码却不改注释,让同事看得一脸懵逼
第三种:带集合的构造函数
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//先转换为数组,再赋值给elementData
if ((size = elementData.length) != 0) {//size赋值为elementData的长度、并判断如果长度不为0
//如果出现elementData类型异常,拷贝一个新的elementData
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 替换为空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
这里可以看到,不管是哪一种构造函数,在没有设置初始容量的时候,或者设置的初始容量为0的时候,都给数据持有对象elementData赋值为了空数组。
添加
添加有几种方法,我们主要看添加一个元素
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
看起来其中的处理基本都在ensureCapacityInternal方法中,进去看看
计算容量
private void ensureCapacityInternal(int minCapacity) {
//如果当前数组等于空数组
if (elementData == *DEFAULTCAPACITY_EMPTY_ELEMENTDATA*) {
//最小容量等于默认初始容量(10)和传入的size+1中的最大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
此时就可以看到内部已经开始修改容量了,如果是空数组并且是第一次添加,最小容量就变成10,其他情况下在ensureExplicitCapacity中处理,minCapacity = size+1
让我们接着看ensureExplicitCapacity
ensureExplicitCapacity
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code //如果minCapacity大于当前的容量 if (minCapacity - elementData.length > 0) grow(minCapacity); }
这里modCount暂时不用管它,是一个对数组进行更改的技术,用于纠错;
grow方法,看名字也应该知道,它计算完新的容量后终于要开始扩容了
扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//定义一个新的容量,它的值为老值的1.5倍
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:
//数组等于copy的新数组,更改了它的容量
elementData = Arrays.*copyOf*(elementData, newCapacity);
}
//数组最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//int最大值
public static final int MAX_VALUE = 2147483647;
//返回最大值MAX_VALUE(因为上面以及判断了newCapacity>MAX_ARRAY_SIZE)
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
// overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
从grow方法中可以看到数组是如何扩容的,它是通过各种判断后获取了新的容量大小,然后copy出来的,也知道了数组容量的最大值是Int的最大值。
当这个方法调用完之后,再调用了
elementData[size++] = e;
实现了一个元素的添加。
其他添加方法
现在我们来看看其他的添加方法
public void add(int index, E element) {
//先对index做限制,超出限制抛出异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
//依然用了这个方法
ensureCapacityInternal(size + 1);
//将index之后的元素向后移动
System.*arraycopy*(elementData, index, elementData, index + 1, size - index);
//最后index位置放上我们要插入的元素
elementData[index] = element; size++;
}
删除
先看看第一种删除方式,删除某一位置元素:
机翻注释:移除此列表中指定位置的元素。 将任何后续元素向左移动(从它们的索引中减去一个)。
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
//拿到要删除的对象
E oldValue = (E) elementData[index];
//计算要移动的数据数量(也就是后面将要移动的数据)
int numMoved = size - index - 1;
if (numMoved > 0)
//拷贝移动数据
System.arraycopy(elementData, index+1, elementData, index,numMoved);
//将最后一位置空
elementData[--size] = null;
// clear to let GC do its work
//返回已删除的对象
return oldValue;
}
再看第二种删除方式,删除某一元素:
机翻注释:从此列表中删除第一次出现的指定元素(如果存在)。 如果列表不包含该元素,则它保持不变。 更正式地,删除具有最低索引i的元素,使得(o==null ? get(i)==null : o.equals(get(i))) (如果这样的元素存在)。 如果此列表包含指定的元素(或等效地,如果此列表因调用而更改),则返回true
public boolean remove(Object o) {
if (o == null) {
//如果元素是null,则删除从列表开始第一个找到的 == null的元素,返回true
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
//如果不为空,则删除从列表开始第一个找到的符合equals()函数的元素,返回true
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
//若没找到,返回false
return false;
}
我们再看看具体的删除逻辑是怎么样的,都在fastRemove(index)函数里面
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.*arraycopy*(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null;// clear to let GC do its work
}
逻辑操作和第一种删除方法是一样的,找到它的index位置进行删除、移动元素
到这里基本就结束了,为什么不讲更多的比如addAll(),clear()这些?
因为逻辑上基本是差不多的,懂了上面的,其他的方法看一眼就清楚了。
自己写一篇文章收获还是蛮多的,自己看一遍一会儿就看完了,懂也感觉自己懂了,但是写的时候发现其中细节地方还有不明白的,推荐有时间的也可以自己写一写,对自己帮助不少。
说完了ArrayList,下一个要不讲讲它兄弟HashMap或者离不开它的RecyclerView吧。不过时间不确定,因为本来我打算这篇文章连看源码+文章几个小时怎么也够了的,结果也硬生生拖了两个月写完2333
因为是从笔记里粘贴过来的,所以格式有点乱,以后会在这里写的;如有错误,欢迎指正