1、ArrayList 简介
1)ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类。
2)该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加。
如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能。
3)ArrayList的用法和Vector向类似,但是Vector是一个较老的集合,具有很多缺点,不建议使用。
另外,ArrayList和Vector的区别是:ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性,而Vector则是线程安全的。
2、ArrayList 成员变量
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable,
java.io.Serializable{
private static final long serialVersionUID = 8683452581122892189L;
// 默认容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组,用于空实例
private static final Object[] EMPTY_ELEMENTDATA = {};
// 一个空对象,如果使用默认构造函数创建,则默认对象内容默认是该值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 当前数据对象存放地方,当前对象不参与序列化
transient Object[] elementData;
// 当前数组长度
private int size;
// 数组最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}
之分析了一部分方法,其他不重要的就不做分析了!!!
3、构造方法
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) { //如果初始化容量为0,则用特殊的空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
// 此时我们创建的ArrayList对象中的elementData中的长度是1,size是0
// 当进行第一次add的时候,elementData将会变成默认的长度:10.
public ArrayList() {
// 没容量,则用默认的空数组,但实际上是10.后面的calculateCapacity有体现
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
重点掌握:
- 初始空时,调用 add() 方法时是怎么扩容的
- 正常情况下,调用 add() 是怎么扩容的
- 删除中间的元素是怎么完成的?将index后面的元素向前移动1步
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length):
src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,length表示要复制的长度。
4、核心方法
4.1、add方法(4个)
(1)add(E e)
public boolean add(E e) {
// 确定内部容量是否够了,size是数组中数据的个数,因为要添加一个元素,所以size+1,
// 先判断size+1的这个个数数组能否放得下,就在这个方法中去判断是否数组.length是否够用了
ensureCapacityInternal(size + 1); // Increments modCount!!
// 在数据中正确的位置上放上元素e,并且size++
elementData[size++] = e;
return true;
}
ensureCapacityInternal(int minCapacity)
确定容量,不够则扩容,包含了modCount++
如果初始时为空,则返回的是 DEFAULT_CAPACITY=10,而不是 minCapacity = size+1=1
// 确定内部容量的方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
calculateCapacity(Object[] elementData, int minCapacity)
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 判断初始化的elementData是不是空的数组,也就是没有长度
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity); //返回两者间的较大者
}
return minCapacity;
}
ensureExplicitCapacity(int minCapacity)
// 确认实际的容量,这个方法就是真正的判断elementData是否够用
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //修改次数+1
// 预计元素添加之后,超过容量
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
grow(int minCapacity) 扩容
newCapacity = max(minCapacity, 3/2 * oldCapacity)
如果 minCapacity > MAX_ARRAY_SIZE,则 minCapacity = Integer.MAX_VALUE
// 数组扩容
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 新容量等于 = 3/2 * 就容量
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 新容量取 newCapacity和minCapacity中的较大者
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量大于最大容量,则 newCapacity取Integer.MAX_VALUE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// copyOf 第1个参数:旧数组 第2个参数:新数组的大小 默认第三个参数是就数组的大小
// 就是将就数组的内容复制到一个大小为newCapacity的新数组中,并将新数组赋给elementData
elementData = Arrays.copyOf(elementData, newCapacity);
}
hugeCapacity(int minCapacity)
// 取Integer.MAX_VALUE和MAX_ARRAY_SIZE中的较大者
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) /
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
(2)add(int index, E element)
// 在特定位置添加元素,也就是插入元素
public void add(int index, E element) {
// 检查index也就是插入的位置是否合理
rangeCheckForAdd(index);
// 判断是否数组.length是否够用
ensureCapacityInternal(size + 1); // Increments modCount!!
// 这个方法就是用来在插入元素之后,要将index之后的元素都往后移一位
System.arraycopy(elementData, index, elementData, index + 1, size - index);
// 在目标位置上存放元素
elementData[index] = element;
// size增加1
size++;
}
rangeCheckForAdd(int index)
检查 index 是否在 数组的下表范围内
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
总结
正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值。
当我们调用add方法时,实际上的函数调用如下:
说明:初始化lists大小为0,调用的ArrayList()型构造函数,那么在调用lists.add(8)方法时,会经过怎样的步骤呢?下图给出了该程序执行过程和最初与最后的elementData的大小。
说明:我们可以看到,在add方法之前开始elementData = {};调用add方法时会继续调用,直至grow时参数minCapacity=10,oldCapacity=0,则newCapacity = minCapacity‘最后elementData的大小变为10,之后再返回到add函数,把8放在elementData[0]中。
(3)addAll(Collection<? extends E> c)
将集合c中元素输出到数组,利用 arraycopy() 将旧数组数据复制到新数组中
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray(); //将集合c中的元素复制到数组a中
int numNew = a.length;
ensureCapacityInternal(size + numNew); // 确定新容量(包含modCount++,扩容等)
// 将旧数组中的元素复制到新数组
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
(4)addAll(int index, Collection<? extends E> c)
和上面基本一样
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index); //检查index是否符合要求
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // 确定新容量,修改modCount
int numMoved = size - index;
// 将集合c中复制到list指定位置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.2、删除方法
比添加元素add简单,因为不会关系到数组扩容
实现主要是:快慢指针 和 System.arraycopy()
(1)remove(int index)
删除指定index处的元素
public E remove(int index) {
rangeCheck(index); //检查index是否符合要求
modCount++; // 修改次数+1
E oldValue = elementData(index); // index处的元素值
int numMoved = size - index - 1;
if (numMoved > 0)
// 将index后的元素前移1步
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // 将原来结尾处置空,方便GC
return oldValue;
}
(2)remove(Object o)
删除list中的元素o,实际的删除动作发生在 fastRemove(index):将index后面的元素向前移到1步。和上面的remove(int)原理基本一样,只是多了一个确定o的index
public boolean remove(Object o) {
// 如果o==null,也要删除对应的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);
return true;
}
}
return false;
}
fastRemove(int index)
private void fastRemove(int index) {
// 修改次数+1
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
// 将index后面的元素向前移到1步
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work
}
(3)removeAll(Collection<?> c)
实际上是 调用的批量移除:batchRemove
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c); // 检查c非空
return batchRemove(c, false); //批量移除
}
batchRemove(Collection<?> c, boolean complement)
批量移除
complement = true时,表示将集合c中的元素保留,将list俩民不在集合c中的元素删除
complement = false时,表示将集合c中的元素删除
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
// r表示工作指针(快指针)
// w表示最新的保留元素的指针(慢指针)
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
// 如果元素满足条件(不在集合c,或者在集合c)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// 说明前面出现了异常,为了保证r后面的元素不受影响。将r到size处的元素复制到w后面。
if (r != size) {
// 将elementData中的从r开始的长度为size-r的元素 复制到 elementData从w开始处
System.arraycopy(elementData, r, elementData, w, size - r);
w += size - r; //更新w的位置,w现在所处位置就是结果的位置
// 最后的结果是0~w+(size-r),0~w是正常处理的结果,w~w+(size-r)是还未处理的部分
}
// 说明,list至少有一个元素满足条件要删除(满指针没到达底部),那么需要清理被删除的部分
// 注意到这里,及格过了上面的if之后,0~w是需要保存的结果,而w~size是需要置空的部分
if (w != size) {
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w; //更新modCount
size = w; //更新size
modified = true; //被修改过
}
}
return modified;
}
(4)removeIf(Predicate<? super E> filter)
Bitset中主要存储的是二进制位,做的也都是位运算,每一位只用来存储0,1值,主要用于对数据的标记。Bitset的基本原理是,用1位来表示一个数据是否出现过,0为没有出现过,1表示出现过。使用的时候可以根据某一个位是否为0表示此数是否出现过。JDK中的BitSet集合对是布隆过滤器中经常使用的数据结构Bitmap的相对简单的实现。BitSet采用了Bitmap的算法思想。
Java的BitSet使用一个Long(一共64位)的数组中的没一位(bit)是否为
1
来表示当前Index的数存在不
实现要点:
(1)通过快慢指针进行移除,之后将后面的位置置空
(2)通过BitSet来讲需要移除的位置标注,如果其他线程在此期间修改了list,则可以保证集合没有修改。(因为此阶段只是标注了,还未修改list)
public boolean removeIf(Predicate<? super E> filter) {
// filter非空
Objects.requireNonNull(filter);
// 找出要删除的元素
// 在此阶段从筛选器引发的任何异常都将使集合保持不变
int removeCount = 0; //记录需要移除的元素个数
final BitSet removeSet = new BitSet(size);
final int expectedModCount = modCount; //保存当前的modCount
final int size = this.size;
// 如果modCount != expectedModCount,说明有其他线程修改了list
for (int i=0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
final E element = (E) elementData[i];
if (filter.test(element)) { //元素满足过滤器条件
removeSet.set(i); //BitSet将对应index设置为1,表示之后需要删除
removeCount++; // 需要移除的元素的个数
}
}
// 如果统计期间其他线程修改了list,则抛出异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
// 到这里,说明上面没有发生异常,统计正常!!!
// 将剩余的元素移到移除元素留下的空间上
final boolean anyToRemove = removeCount > 0;
// 如果有需要移除的元素
if (anyToRemove) {
final int newSize = size - removeCount; //更新size
// i是快指针,j是满指针
for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
// 根据bit位来判断是否移除
i = removeSet.nextClearBit(i);
elementData[j] = elementData[i];
}
// 将余下的位置置空
for (int k=newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work
}
this.size = newSize; //更新size
// 如果次期间有其他线程修改list,则抛出异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++; //修改次数+1
}
return anyToRemove;
}
(5)removeRange(int fromIndex, int toIndex)
讲 [fromIndex, toIndex] 范围内的元素移除
protected void removeRange(int fromIndex, int toIndex) {
modCount++; //修改次数+1
int numMoved = size - toIndex; //需要移除的元素个数
// 通过arraycopy()进行移除
System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved);
// 讲余下部分置空
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
(6)retainAll(Collection<?> c)
讲不在集合c中的元素删除,和前面的方法基本一样。只是调用 batchRemove()时的参数不一样
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
5、其他方法(只讲一部分)
像 clear() 、isEmpty()、iterator() 、listIterator() 、size()、toArray()等简单的方法就不说了
(1)contains(Object o)
实际上就是调用了 indexOf(object o)
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
(2)get(int index)
实际上就是调用了 elementData(int)
public E get(int index) {
rangeCheck(index); // 检查index的合法性
return elementData(index);
}
elementData(int index)
E elementData(int index) {
return (E) elementData[index];
}
(3)set(int index, E element)
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
(4)lastIndexOf(Object o)
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;
}
(5)replaceAll(UnaryOperator<E> operator)
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final int expectedModCount = modCount;
final int size = this.size;
// 就是一个一个替换
for (int i=0; modCount == expectedModCount && i < size; i++) {
elementData[i] = operator.apply((E) elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
(6)sort(Comparator<? super E> c)
实际上是调用的 Arrays.sort()
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
(7)trimToSize()
将此ArrayList实例的容量 修剪 为列表的当前大小。应用程序可以使用此操作最小化ArrayList实例的存储。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0) ?
EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
}
}
5、内部类
不做讲解
6、总结
1)arrayList可以存放null。
2)arrayList本质上就是一个elementData数组。
3)arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法。
4)arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全是删除集合中的元素。
5)arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
6)arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。