文章目录
1.1 ArrayList简介
- ArrayList是可以动态增长和缩减的索引序列,是基于数组实现的List类
- 该类封装了一个动态再分配的Object[]数组,每一个类的对象都有一个Capacity属性,表示所封装的Object[]数组的长度,当想ArrayList中添加元素的时候,此属性自动增加。
- ArrayList和Vector相似,但是Vector是线程安全的,ArrayList是线程不安全的,当多个线程访问同一个ArrayList的时候,程序需要手动保持线程的同步性。
1.1.1 ArrayList的结构
ArrayList的底层结构就是一个数组,数组元素类型为Object,可存放任意数据类型的数据,我们创建一个ArrayList以后对其进行操作的时候,底层都是在对数组进行操作。
1.1.2 ArrayList的继承结构
在IDEA中进入到ArrayList,按快捷键F4
就可以直接看到相关类的继承结构,按住Alt+7
就可以看见当前类的所有的方法。
可以看到:
上面提出的可以看到,ArrayList继承AbstractList,AbstractList继承AbstractCollection。
这里使用了先继承一个抽象的AbstractList类,然后实现List接口的方法,好处在于:抽象类中有抽象的方法和不抽象可以具体实现的方法,而接口中只有抽象的方法,所以让AbstractList去实现List中一些通用的方法,ArrayList就直接继承父类AbstractList,可以拿到这些通用的方法,然后自己实现一些特有的方法,代码更简洁。
ArrayList中再一次实现了List接口,这个网上的答案是说这个就是个作者在当时编写的时候思路上出现的一个小错误,因为对类的使用没有什么影响,所以就保留下来了。
ArrayList还实现了RandomAccess,API的解释为:List实现使用的标记界面,表明它们支持快速(通常为恒定时间)随机访问。 此接口的主要目的是允许通用算法更改其行为,以便在应用于随机访问列表或顺序访问列表时提供良好的性能。提高随机读写的效率,API中说实现了这个接口,直接for循环速度是更快。
实现Cloneable接口,可以根据需要重写Object.clone()方法来实现复制
实现Serializable接口,表明ArrayList是可以序列化:序列化是一个用于将对象状态转换为字节流的过程,可以将其保存到磁盘文件中或通过网络发送到任何其他程序;从字节流创建对象的相反的过程称为反序列化。
1.2 类中的属性
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; // non-private to simplify nested class access
//实际元素大小,默认为0
private int size;
//最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}
1.3 类中的构造方法
ArrayList有三个构造方法:
ArrayList()
、ArrayList(int)
、ArrayList(Collection <? extends E>)
/**
* Constructs an empty list with an initial capacity of ten.
* 构造一个空的集合,默认初始容量为10
*/
public ArrayList() {
//super();这里有默认调用父类的无参构造方法
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
// private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//源码中说的是这个是为了区分EMPTY_ELEMENTDATA,以便在添加内容的时候知道需要扩容多少
//网上说是为了确定第一次初始化是使用有参的构造还是无参的构造,有参的构造可以知道容量,
//无参的构造是默认的容量大小
}
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
//默认调用父类的空的构造方法
if (initialCapacity > 0) {
//如果初始容量大于0,初始化
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//等于0的话就直接初始化为EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
} else {
//小于0的话就是抛出一个异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
//转化为数组
elementData = c.toArray();
//判断数组中的元素个数
if ((size = elementData.length) != 0) {
//如果元素类型不为Object[].class,需要改造
if (elementData.getClass() != Object[].class)
//类型不匹配的时候,需要将其赋值以达到类型相等的目的
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList的构造方法目的就是为了初始化一下储存数据的容器,本质上是数组,在这叫elementData
1.4 核心方法
add(E)
、add(int,E)
、add(E,Object[],int)
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
modCount++;//这是修改次数,所有线程不安全的集合类基本都有
add(e, elementData, size);
return true;
}
/**
* This helper method split out from add(E) to keep method
* bytecode size under 35 (the -XX:MaxInlineSize default value),
* which helps when add(E) is called in a C1-compiled loop.
*/
private void add(E e, Object[] elementData, int s) {
//长度等于数组的长度
if (s == elementData.length)
//经历扩容
elementData = grow();
elementData[s] = e;
size = s + 1;
}
private Object[] grow(int minCapacity) {
//这里涉及一个数组的拷贝,到一个新的容量
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private Object[] grow() {
//因为要添加一个元素,所以要size+1,然后调用上面的方法
return grow(size + 1);
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
//旧的长度
int oldCapacity = elementData.length;
//新长度+新长度/2相当于扩容1.5倍oldCapacity
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新的容量减去最小增加的容量小于等于0,表示oldCapacity=0,newCapacity=0
if (newCapacity - minCapacity <= 0) {
//如果新容量和老容量都为0,判断数组长度是否与默认初始化的长度相同,是的话,
//比较minCapacity与默认长度大小,取大的
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow 溢出
throw new OutOfMemoryError();//报错
return minCapacity;//返回最小的minCapacity
}
//如果扩容成功,判断新容量与所能承受的最大size对比
//小的话,则直接返回扩容的容量,否则进入hugeCapacity继续判断
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow 溢出直接报错
throw new OutOfMemoryError();
//这里就是一个赋最大值的操作 MAX_VALUE = 0x7fffffff
//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
return (minCapacity > MAX_ARRAY_SIZE)
? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;
}
指定位置插入一个元素
public void add(int index, E element) {
//检查下标
rangeCheckForAdd(index);
modCount++;//修改次数+1
final int s;
Object[] elementData;
//跟上面的判断类似,判断size与elementData是否相等,相等的话表明空间用完,需要扩容
if ((s = size) == (elementData = this.elementData).length)
//于是扩容
elementData = grow();
//数组的拷贝,将多出来的那一部分进行拷贝
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
//然后将需要存入的数据放到这个位置
elementData[index] = element;
//同时size+1,前面s=size
size = s + 1;
}
/**
* A version of rangeCheck used by add and addAll.
*/
private void rangeCheckForAdd(int index) {
//对下标的有效性进行检查
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
图片来自:https://i-blog.csdnimg.cn/blog_migrate/284cd039587f55248003e87c90f532da.png
其中的ensureCapacityInternal与ensureExplicitCapacity在我看的jdk11中直接去掉了直接就是上述的判断是否容量已经用尽,等效。
1.5 删除方法
remove(int)
删除指定位置上的元素
public E remove(int index) {
//检查下标是否有效
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
/**
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(Object[] es, int i) {
modCount++;//修改次数+1
final int newSize;
//判断newSize表示最大的下标
if ((newSize = size - 1) > i)
//如果大于的话,直接就赋值需要删除的后面的元素填充到原来删除的位置
System.arraycopy(es, i + 1, es, i, newSize - i);
//就算是上面进行了操作,由于最后一个数是原来就存在的,所以设为null
//如果压根就没有执行上面的操作,表明删除的数的下标就是newSize,直接置为null即可。
es[size = newSize] = null;//置为null可以让gc(垃圾回收机制)尽快的回收
}
//这里说一下arrayCopy的用法
* @param src the source array.
* @param srcPos starting position in the source array.
* @param dest the destination array.
* @param destPos starting position in the destination data.
* @param length the number of array elements to be copied.
@HotSpotIntrinsicCandidate
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
//这个方法的意思就是:加入数组中有1 2 3 4 5,现在要删除3,此时的下标为2,使用arrayCopy完成,数组对象的从下标2+1,也就是4这个数这里开始复制,长度为总的下标长度-3的下标2,也就是2,复制的数为4 5,插入的位置在2,长度为2,则此时数组变为1 2 4 5 5,也就是前面是选中需要复制的数组的起点与长度,后面是需要复制到的数组的位置的起点与长度
remove(Object)
删除一个特定的对象,此方法可以表明ArrayList可以存放null值
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
//使用了一个标签,可以确保中断的范围
found: {
if (o == null) {
for (; i < size; i++)
//如果元素为null,删除
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
//如果元素与要删除的元素相同,删除
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
clear()
清除所有的元素
public void clear() {
modCount++;//修改次数+1
final Object[] es = elementData;
//直接将所有的值置为null
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;
}
removeAll()
批量删除
public boolean removeAll(Collection<?> c) {
return batchRemove(c, false, 0, size);
}
//这个用来判定是否有交集
//仅保留此列表中包含在指定集合中的元素。换句话说,从该列表中删除未包含在指定集合中的所有元素。
public boolean retainAll(Collection<?> c) {
return batchRemove(c, true, 0, size);
}
//上述两个方法都是调用batchRemove,不过删除的话complement=false,判定交集complement=true
//这里千万要注意,retainAll方法,只要数组发生改变就返回true,否则返回false
//我们只能够依据retainAll处理之后,看看响应的集合size是否为0,为0的话证明有交集,否则表示无交集
//retainAll方法的返回值对于判断是否有交集一点作用都没有,只能依靠处理后的size是否为0判断
boolean batchRemove(Collection<?> c, boolean complement,
final int from, final int end) {
Objects.requireNonNull(c);//判断元素非空
final Object[] es = elementData;
int r;
// Optimize for initial run of survivors
for (r = from;; r++) {
//如果到了最后一个元素,返回false
if (r == end)
return false;
//删除的话,complement=false,这就保证了这里所有的全是true!=false
//直接终止
if (c.contains(es[r]) != complement)
break;
}
//w=r++,这里r++,主要是通过上面的判断知道r位置上的数已经被包含了
int w = r++;
try {
//遍历,如果c中不包含这个数,由于是删除所有,所有都不会成立,直接finally
for (Object e; r < end; r++)
if (c.contains(e = es[r]) == complement)
es[w++] = e;
} catch (Throwable ex) {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
System.arraycopy(es, r, es, w, end - r);
w += end - r;
throw ex;
} finally {
//这是修改次数
modCount += end - w;
//这个方法主要就是将后面的全部都置为null
shiftTailOverGap(es, w, end);
}
return true;
}
/** Erases the gap from lo to hi, by sliding down following elements. */
private void shiftTailOverGap(Object[] es, int lo, int hi) {
//从hi开始,size-hi长度的数,复制到lo开始,size-hi长度位置
//这个操作把不是交集的元素都赋值到最后的位置上
System.arraycopy(es, hi, es, lo, size - hi);
//把不属于交集的都置为null
for (int to = size, i = (size -= hi - lo); i < to; i++)
es[i] = null;
}
1.6 Set()方法
Set(int, E)
替换某个指定位置上的值
public E set(int index, E element) {
//检查下标边界
Objects.checkIndex(index, size);
//取出旧元素
E oldValue = elementData(index);
//赋值成新元素
elementData[index] = element;
//返回旧值
return oldValue;
}
1.7 indexOf()方法
indexOf(Object)
返回特定元素第一次出现的下标位置,lastIndexOf(Object)
从尾部开始找
public int indexOf(Object o) {
return indexOfRange(o, 0, size);
}
int indexOfRange(Object o, int start, int end) {
//取出数组中的元素
Object[] es = elementData;
if (o == null) {
//如果对象为空的话,就遍历知道此位置为空,返回下标
for (int i = start; i < end; i++) {
if (es[i] == null) {
return i;
}
}
} else {
//不为空则判断值是否相等,返回下标
for (int i = start; i < end; i++) {
if (o.equals(es[i])) {
return i;
}
}
}
//如果没有找到,返回-1
return -1;
}
1.8 get()方法
get()
返回指定下标的那个元素
public E get(int index) {
//检查下界
Objects.checkIndex(index, size);
return elementData(index);
}
@SuppressWarnings("unchecked")
E elementData(int index) {
//取出特定元素,强转
return (E) elementData[index];
}
1.9 总结
- ArrayList可以存放null元素
- ArrayList本质上就是一个elementData的数组
- ArrayList区别于数组的地方就在于,ArrayList可以动态的扩容,关键实现在grow()方法
- ArrayList中的removeAll方法与clear()方法的主要区别在于,removeAll()方法可以批量删除指定位置上的元素,但clear()是删除所有的元素
- ArrayList本质是数组,查询方面会很快,但是在插入删除的时候效率就很低,每插入一个元素需要移动许多数据
- ArrayList实现了RandomAccess,所以遍历它的时候建议使用for循环
- ArrayList中的retainAll()方法,用来判断是否有交集,但是需要注意,它所返回的true和false作用不大,主要是看执行了此方法以后,相应的list集合的size是否置0,置0的话表示无交集,否则表示有交集。