转载请注明出处:http://blog.csdn.net/hjf_huangjinfu/article/details/60607250
ArrayList 是一个基于数组(顺序表)来实现 List 接口的类。下面记录一些在 ArrayList 在实现细节上可以学习的知识点。
增加/插入操作
该系列操作主要涉及的方法有:
public boolean add(E e)
public void add(int index, E element)
public boolean addAll(Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c)
因为是基于数组实现,该系列方法主要考虑的就是 扩容 的问题,只要容量没问题,按需要进行数据平移,然后按索引赋值就行了。下面看一下它内部的自动扩容逻辑。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}
伪代码如下:
private void add() {
//计算目前容量是否够用,不够用就扩容
//计算最小容量需求
if (列表为空) {
最小容量 = 10(最低容量);
} else {
最小容量 = 当前容量 + 要添加的数据长度;
}
//扩容
if (最小容量 > 当前容量) {
//扩容策略
新容量 = 当前容量 * 1.5;
if (新容量 < 最小容量) {
新容量 = 最小容量;
}
if (新容量 > Integer.MAX_VALUE) {
新容量 = Integer.MAX_VALUE;
}
//复制数据
创建长度为 ‘新容量’ 的数组,并且把原数组复制过来;
}
//正常插入数据
正常插入数据,如果插入一个集合数据,采用数组赋值的方法来提高效率;
}
这里扩容还用了一点小技巧,就是使用移位 num >> 1 来代替 num / 2。
删除操作
该系列操作主要涉及的方法有:
public E remove(int index)
public boolean remove(Object o)
public boolean removeAll(Collection<?> c)
public boolean retainAll(Collection<?> c)
public void clear()
该系列方法中,单个删除没什么好说的,只是按索引删除,然后做数据平移就可以。主要是批量删除的方法,有一点小技巧,下面看一下:
public boolean removeAll(Collection<?> c) {
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
批量删除的实现原理就是,基于双索引(读写索引)来操作数组,也没有依赖辅助的数组,遇到不需要 remove 的元素,就向写索引指向的位置写入数据。
伪代码如下:
private void remove() {
读索引 = 0;
写索引 = 0;
for (; 读索引 < size; 读索引++) {
if (读索引指向的数据需要保留) {
把 读索引指向的数据 写入到 写索引指向的位置;
写索引++;
}
}
把从 写索引 开始 到最后的所有数据置为null;
}
下面示例演示了从 [A, B, C, D, E] 中 移除 [B, D] 的过程。
修改操作
该系列操作主要涉及的方法有:
public E set(int index, E element)
没什么好说的,就是检查索引边界,然后替换指定位置的数据。
查找操作
该系列操作主要涉及的方法有:
public E get(int index)
public int lastIndexOf(Object o)
public int indexOf(Object o)
public boolean contains(Object o)
没什么好说的,就是 按索引取值 或者 遍历匹配,只有在 lastIndexOf 中,换个方向遍历。
子列表
该系列操作主要涉及的方法有:
public List<E> subList(int fromIndex, int toIndex)
subList 为了减少内存使用的开销,内部原理是以offset 来控制 原来的List,所以对 sublist 修改,也会修改到原来的 list 。反之也成立。
其他
数组中 size 以外的数据一律置为 null,防止内存溢出。