1.基本图示
imagepublic class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
2. 基本介绍
- 底层使用数组,同时是动态数组
- 由于数组是 Object 类型的,因此可以添加元素 null,可以添加相同元素
- ArrayList 中的操作是线程不安全的
- 实现了 RandomAccess 接口,即可以使用随机访问,通过索引访问效率更高
3.基本参数
数组初始容量为 10
private static final int DEFAULT_CAPACITY = 10;
默认的空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
用于存放集合的数组,不是放入10条数据,该数组长度就为10,它会自动扩容以达到最适合的一个容量。因为是 Object 类,因此允许为 null
transient Object[] elementData;
数组中当前元素的长度
private int size;
4.初始化方法
如果在初始化时候不指定参数,则会初始化一个空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
如果在初始化的时候指定初始容量,如果初始容量大于0,则初始化 elementData 数组,数组容量即为 initialCapacity;如果 initialCapacity 是 0,初始化空数组;如果小于 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) {
//将集合转为数组对象后赋值给 elementData 数组
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray 如果不是 Object 类
if (elementData.getClass() != Object[].class)
// 使用 Arrays.copyOf 创建一个新的数组
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果长度等于 0,初始化空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
5.添加元素和数组扩容
如果直接添加元素,会先调整一次数组的容量,注意参数是当前已存入数组的数据长度+1
public boolean add(E e) {
// 调用调整数组容量的方法,参数为(当前数组容量+1)
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
// 在指定位置插入一个值
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
// 在数组插入的位置,所有元素向后移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 在指定的位置插入值
elementData[index] = element;
size++;
}
// 插入一个集合
public boolean addAll(Collection<? extends E> c) {
// 将集合转为数组对象后赋值给 Object 数组 a
Object[] a = c.toArray();
int numNew = a.length;
// 确保当前 elementData 数组的容量充足
ensureCapacityInternal(size + numNew);
// 将集合数组直接放到 elementData 数组的后面
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
// 数组不为 0 才返回 true
return numNew != 0;
}
// 在指定的位置插入一个集合
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);
//原 elementData 数组需要指定移动的
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
ensureCapacityInternal 方法里面嵌套了两层方法,首先调用 calculateCapacity 方法计算数组所需要的最小容量 minCapacity,然后再调用 ensureExplicitCapacity 方法来判断是否需要对当前数组进行扩容。注意,minCapacity 是当前已存入的数据长度+1
elementData 数组不是插入多少数据就长度为多少,而是根据插入的数据长度来自动扩充数组长度的(后面会讲到)
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果 elementData 数组为空(构造函数不传入参数的情况),会先返回10,即最小容量为10(也是 ArrayList 的默认长度)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 如果不为空,直接返回当前已存入的数据长度+1
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果已经存入数组的长度+1 比 elementData数组的长度 大了,那么就会对 elementData 数组进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
如果不传入任何参数,那么 ArrayList 的默认长度便是 10,即 elementData 数组的长度为 10,此时当插入第 11 个值时,会对 elementData 数组长度进行 1.5 倍扩容变成 15,以此类推,插入第 16 个值时,elementData 长度变为 15*1.5=22...
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 将 elementData 数组的长度扩大为原来的 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果扩容后的elementData数组长度还是比当前已存数组长度要小,则还是使用原来的值
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 返回一个扩容后或者容量改变后的 elementData 数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
6.删除元素
删除元素和增加元素基本是一样的,只是不用扩容罢了
public E remove(int index) {
rangeCheck(index);
modCount++;
// 保存需要删除的位置值,同时用于最后的返回
E oldValue = 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;
}
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 调用快速删除方法
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
// 调用快速删除方法
fastRemove(index);
return true;
}
}
return false;
}
// 快速删除的方法,和之前删除方法一致
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
}
// 全部清空的方法
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
7.修改、获取元素
直接获取指定数组下标的元素
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
8.其他方法
retainAll 用于检测两个集合是否存在相同的数据,即交集,因为无论是否有交集,该方法都会返回 true(虽然很奇怪,但确实是这样),我们可以用另外一种方式来判断,具体可以去看:https://www.cnblogs.com/lyajs/p/5737410.html
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
// r 用于遍历 elementData 数组, w 用于存放更新后(把交集数据从后往前移动)的最后一个位置
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
// r 从前往后遍历,如果 r 指向的位置的值在 c 中有,则往前覆盖;如果没有,则 r 继续往后遍历
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
//当出现异常(如 r 的位置超过了 elementData 的容量)
if (r != size) {
// 直接将后面的移到前面
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
// 如果 w!=size 说明进行了覆盖操作,此时 w 前面的都是交集,把 w 后面的数据全部删除
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;
}
// 调用 indexOf 方法来判断
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
// 返回对应元素在集合中的位置
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
// 只有该元素出现一次,就返回当前元素的位置
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
// 返回在集合中最后一次出现该元素的位置
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
// 从尾部开始遍历
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
9.迭代方式
(1) 迭代器方式
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
(2) 随机访问,通过索引值去遍历
for(int i=0; i<list.size(); i++) {
System.out.println(list.get(i));
}
(3) 通过 for 循环
for (String str: list) {
System.out.println(str);
}
其中,随机访问效率最高,由于 ArrayList 继承自 RandomAccess, 而且它的迭代器都是基于 ArrayList 的方法和数组直接操作,所以遍历时 get 的效率要 >= 迭代器
10.参考
https://blog.csdn.net/u011240877/article/details/52853989
http://www.cnblogs.com/skywang12345/p/3308556.html#a4
https://blog.csdn.net/wbin233/article/details/70195112
网友评论