1、ArrayList-底层使用数组实现
/**
* 默认初始容量大小为10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 如果自定义容量为0,则会默认用它来初始化ArrayList,或者用于空数组替换
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 如果没有自定义容量,则会使用它来初始化ArrayList,或者用于空数组比对。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 实际ArrayList的大小/集合中元素的个数
*/
private int size;
/**
存储ArrayList元素的数组缓冲区。ArrayList的容量是这个数组缓冲区的长度。任何elementData==DEFAULTCAPACITY_empty_elementData的空ArrayList,当添加第一个元素时,将扩展到默认的容量。
*/
transient Object[] elementData;
2、ArrayList-构造方法
1、ArrayList提供了三种方式的构造器:
/**
* 构造一个初始容量为10的空列表。
*/
public ArrayList() {
//如果没有传入初始化容量,则使用空数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
//使用这个数组是在添加第一个元素的时候就会扩容到默认大小10
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造具有指定初始容量的空列表。
* 这就是ArrayList底层用到的数组
* 非私有,用于简化嵌套类访问
* transient 在已经实现序列化的类中,不允许某变量序列化(让某些被修饰的变量不参加序列化。)。
*
* @param 列表的初始容量
* @throws 如果指定的初始容量为负数,则为IllegalArgumentException
*/
public ArrayList(int initialCapacity) {
//如果传入的初始容量大于0,则新建一个数组来存储元素
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果传入的初始容量等于0,则使用空数组EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
/**
* 构造一个包含指定集合元素的列表,其顺序由集合的迭代器返回
*
* @param 要将其元素放入此列表中的集合
* @throws 如果指定的集合为null,则发生NullPointerException
*/
public ArrayList(Collection<? extends E> c) {
//.toArray();集合转数组
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//检查c.toArray()返回值是不是Object[]类型,如果不是,重新拷贝成Object[].class类型
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
//如果c是空集合,则初始化为空数组EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
}
}
3、ArrayList-存储元素方法
1、ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加元素的方法。
/**
* 用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。
*
* @param 要替换的元素的索引
* @param 要存储在指定位置的元素元素
* @return 以前位于指定位置的元素
* @throws 数组下标越界异常
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
/**
* 将指定的元素追加到此列表的末尾。时间复杂度为O(1)
*
* @param 要附加到此列表的元素
* @return 元素添加成功,返回true
*/
public boolean add(E e) {
//检查是否需要扩容
//size+1???
//1.如果集合添加元素成功后,集合中的实际元素个数。 2.为了确保扩容不会出现错误。
//如果默认大小是0,则0 + 0 » 1还是0。 如果size是1,则1 + 1 » 1还是1
//jdk1.8版本以后,ArrayList中的扩容放在add()方法中。之前放在构造方法中。
//默认所以ArrayList arrayList = new ArrayList();size应该是0。所以,size+ 1对扩容来讲很必要。
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* 在此列表的指定位置插入指定的元素。将当前位于该位置的元素(如果有)和任何后续元素向右移动(在其索引中添加一个元素)
* 时间复杂度O(n)
* @param 要插入指定元素的索引
* @param 要插入的元素
* @throws 数组下标越界异常
*/
public void add(int index, E element) {
//检查是否越界
rangeCheckForAdd(index);
//如果数组长度不足,将进行扩容。
ensureCapacityInternal(size + 1); // Increments modCount!!
//.arraycopy():
// 将 elementData中从Index位置开始、长度为size-index的元素,
// 拷贝到从下标为index+1位置开始的新的elementData数组中。
// 即将当前位于该位置的元素以及所有后续元素右移一个位置。
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//将元素插入到index的位置
elementData[index] = element;
size++;
}
/**
* .arraycopy();
1.Object src:原数组
2.int srcPos:从元数据的起始位置开始
3.Object dest:目标数组
4.int destPos:目标数组的开始起始位置
5.int length:要复制的数组的长度
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
/**
* 将指定集合中的所有元素追加到此列表,按照指定集合的迭代器。
* @param 包含要添加到此列表的元素的c集合
* @return 添加成功则返回true
* @throws 如果追加的集合是空的,则抛出空指针异常
*/
public boolean addAll(Collection<? extends E> c) {
//将集合转为数组
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
//如果c不为空就返回true,否则返回false。
return numNew != 0;
}
/**
* 从指定的位置开始,将指定collection中的所有元素插入到此列表中。
*
* @param 从指定集合插入第一个元素的索引
* @param 包含要添加到此列表的元素的c集合
* @return 添加成功则返回true
* @throws 数组下标越界异常
* @throws 如果追加的集合是空的,则抛出空指针异常
*/
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
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;
}
4、ArrayList-获取元素方法
1、ArrayList提供了get(int index);获取元素的方法。
//返回此列表中指定位置上的元素。 时间复杂度O(1)
public E get(int index) {
//检查元素是否越界
rangeCheck(index);
checkForComodification();
//返回数组index位置的元素
return ArrayList.this.elementData(offset + index);
}
5、ArrayList-删除元素方法
1、ArrayList提供了根据下标或者指定对象两种方式的删除功能。
2、[!!!]从数组中移除元素的操作,也会导致被移除的元素以后的所有元素的向左移动一个位置。
/**
* 移除此列表中指定位置上的元素。 时间复杂度为O(n)
*
* @param 索引要删除的元素的索引
* @return 从列表中删除的元素
* @throws 数组下标越界异常
*/
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
// 如果index不是最后一位,则将index之后的元素往前挪一位
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将最后一个元素删除,帮助GC
elementData[--size] = null; // clear to let GC do its work
// 返回旧值
return oldValue;
}
/**
* 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。
* 时间复杂度为O(n)
*/
public boolean remove(Object o) {
//由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。
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)快速删除相对于remove(int index)少了检查索引越界的操作。
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
// 如果index不是最后一位,则将index之后的元素往前挪一位
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将最后一个元素删除,帮助GC
elementData[--size] = null; // clear to let GC do its work
}
6、ArrayList-数组容量调整方法
1、每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。
2、数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。
3、[!!!]在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。
/**
* 增加这个<tt>ArrayList</tt>实例的容量
* minCapacity = size+1
* @param 期望的最小容量
*/
public void ensureCapacity(int minCapacity) {
//如果元素数据不为空,则初始化为0,否则为默认初始容量大小:为10
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
//比较期望的最小容量和最小扩容值,来判断是否需要进行计算扩容
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
// 存储元素相关方法中调用该方法,比如:.add()中
// minCapacity = size+1
// 确保内部容量
private void ensureCapacityInternal(int minCapacity) {
//如果是空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则初始化为默认大小10.
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//确保许可容量
ensureExplicitCapacity(minCapacity);
}
//确保许可容量
private void ensureExplicitCapacity(int minCapacity) {
//modCount不用理它,它用来计算修改次数
//ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//扩容,增加容量
grow(minCapacity);
}
/**
* 增加容量,以确保它至少可以容纳minimum capacity参数指定的元素数。
* minCapacity=size+1
* 假如不size+1处理,如果默认大小是0,则0 + 0 » 1还是0。 如果size是1,则1 + 1 » 1还是1。
* 默认所以ArrayList arrayList = new ArrayList();size应该是0。因为size在只会调用add()方法时才会自增。
* @param 最小容量所需的最小容量
*/
private void grow(int minCapacity) {
// overflow-conscious code
//计算元素数据的大小
int oldCapacity = elementData.length;
//默认扩容1.5倍
//表示将oldCapacity右移一位(位运算),相当于除2.再加上1,相当于新容量扩容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:
//以新容量拷贝出来一个新的数组赋值给elementData
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 如果新容量大于最大容量,则新容量为 Integer.MAX_VALUE;否则为 Integer.MAX_VALUE - 8
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/**
* 将这个集合实例的容量调整为列表的当前大小(将elementData的数组设置为ArrayList实际的容量,动态增长的多余容量被删除了。)。
* 应用程序可以使用此操作最小化<tt>ArrayList</tt>实例的存储。
* 将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize方法来实现。
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
7、ArrayList-集合运算方法
1、 并集:.addAll(Collection<? extends E> c) :按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾
交集:.retainAll(Collection<?> c): 仅保留此列表中包含在指定集合中的元素。
差集:.removeAll(Collection<?> c) :从此列表中删除指定集合中包含的所有元素。
无重复并集:list2.removeAll(list1);list1.addAll(list2);
1-1、求两个集合的交集:list1.retainAll(list2);
//获取两个集合的交集
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
// 调用批量删除方法,这时complement传入true,表示删除不包含在c中的元素
return batchRemove(c, true);
}
//具体操作集合的方法,当complement为false时表示删除c中包含的元素、为true时表示删除c中不包含的元素
private boolean batchRemove(Collection<?> c, boolean complement) {
//获得当前对象的所有元素,final修改
final Object[] elementData = this.elementData;
// 使用读写两个指针同时遍历数组
// 读指针每次自增1,写指针放入元素的时候才加1
int r = 0, w = 0;
// 设置标记位
boolean modified = false;
try {
// 遍历整个数组,如果c中包含该元素,则把该元素放到写指针的位置(以complement为准)
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];//存在则直接保存
} finally {
// 正常来说r最后是等于size的,除非c.contains()抛出了异常
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
// 如果c.contains()抛出了异常,则把未读的元素都拷贝到写指针之后
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;//当前集合大小
}
if (w != size) {
//将写指针之后的元素置为空,帮助GC
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
//新大小等于写指针的位置(因为每写一次写指针就加1,所以新大小正好等于写指针的位置)
modCount += size - w;
size = w;//交集大小
modified = true;
}
}
return modified;
}
1-2、求两个集合的差集:list1.removeAll(list2);
//只保留当前集合中不在c中的元素,不保留在c中不在当前集体中的元素。
public boolean removeAll(Collection<?> c) {
// 集合c不能为空
Objects.requireNonNull(c);
// 同样调用批量删除方法,这时complement传入false,表示删除包含在c中的元素
return batchRemove(c, false);
}
// 删除指定范围的元素
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// 同样是利用copy去操作的 计算出位置直接赋值
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
1-3、求两个集合的并集:list1.addAll(list2);
见.addAll();方法
8、ArrayList-集合源码分析总结
1、ArrayList-集合源码分析总结
1-1、ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,ArrayList不会进行缩容;
1-2、ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);
1-3、ArrayList添加元素到尾部极快,平均时间复杂度为O(1);
1-4、ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n);
1-5、ArrayList从尾部删除元素极快,时间复杂度为O(1);
1-6、ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为O(n);
1-7、ArrayList支持求并集,调用addAll(Collection c)方法即可;
1-8、ArrayList支持求交集,调用retainAll(Collection c)方法即可;
1-9、ArrayList支持求单向差集,调用removeAll(Collection c)方法即可;