Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。
Set和List继承于它。
Set中不能包含重复的元素,也没有顺序来存放。
注意:集合顺序分为:添加顺序和字典顺序
所以Set集合并不是绝对无序的,部分集合如HashSet,TreeSet就会根据字典顺序排序,也就成有序的,而LinkedHashSet,CopyOnWriteArraySet就不会根据字典顺序排序,而是根据时间顺序排列。
List可以包含重复元素,是一个有序集合(这里说的有序是指添加顺序)。
Map是另一个接口,它和Collection接口没有关系。Map包含了Key-Value键值对,同一个Map里Key不能重复,而不同Key的Value是可以相同的。
ArrayList的源码
一、成员变量
ArrayList的成员变量:
private static final int DEFAULT_CAPACITY = 10; //默认长度10
private static final Object[] EMPTY_ELEMENTDATA = new Object[0]; //空元素数据数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0]; //默认容量空元素数据数组
transient Object[] elementData; //Object数组
private int size; //elementData数组里面包含的数据长度。
private static final int MAX_ARRAY_SIZE = 2147483639; //最大数组大小
数组用来存储我们的数据。也就是说,我们代码中的add的数据都会放在这个数组里面。
那么由此我们可知,ArrayList内部是由数组实现。
二、构造函数
ArrayList的构造方法:
public ArrayList(int var1) {
if (var1 > 0) {
this.elementData = new Object[var1];
} else {
if (var1 != 0) {
throw new IllegalArgumentException("Illegal Capacity: " + var1);
}
this.elementData = EMPTY_ELEMENTDATA;
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> var1) {
this.elementData = var1.toArray();
if ((this.size = this.elementData.length) != 0) {
if (this.elementData.getClass() != Object[].class) {
this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class);
}
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
我们看到主要有三个构造方法:
(1)第一个构造方法需要传入一个int类型的变量。
表示我们实例化一个ArrayList的时候想让ArrayList的初始化长度为多少。
然后如果该变量大于0,那么new一个长度为传入值的对象数组。
如果传入为0,那么等于EMPTY_ELEMENTDATA。这个变量我们上面讲过,就是实例化一个对象数组,内容为空。
如果小于0,那么抛出异常。
(2)第二个构造方法是无参构造方法,直接等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
(3)第三个构造方法则我们另外一种常见的使用方法,
比如处理化AList的时候想把BList的值传给AList。
那么使用如下代码:
首先调用了var1.toArray()方法将我们传入的集合元素变成一个数组来赋值给elementData数组。
然后判断elementData数组里面是否有数据元素:
1.如果有,那么再判断elementData数组类型是否为Object,不是的话,转为Object类型。
2.如果没有元素,那么直接赋值为EMPTY_ELEMENTDATA。
至此三个构造方法就已经分析完了,基本上没有什么难度。
三,常见方法
ArrayList的常见方法:
(1)size()
public int size() {
return this.size;
}
就是将elementData数组中元素个数返回。
(2)isEmpty()
public boolean isEmpty() {
return this.size == 0;
}
就是判断sizes是否等于0,即elementData数组中是否有元素。
(3)add()
第一个add方法:
public boolean add(E var1) {
this.ensureCapacityInternal(this.size + 1);
this.elementData[this.size++] = var1;
return true;
}
第一行是:判断数组大小,扩容
第二行是:常见的数组赋值,将下标为size处的数组元素赋值为var1,然后size自加1。
//计算容量
private static int calculateCapacity(Object[] var0, int var1) {
return var0 == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ? Math.max(10, var1) : var1;
}
//确保(elementData)内部容量
private void ensureCapacityInternal(int var1) {
this.ensureExplicitCapacity(calculateCapacity(this.elementData, var1));
}
//确保扩展容量
private void ensureExplicitCapacity(int var1) {
++this.modCount;
if (var1 - this.elementData.length > 0) {
this.grow(var1);
}
}
//扩容
private void grow(int var1) {
int var2 = this.elementData.length;
//扩大为原来的1.5倍,计算速度上讲,移位运算要比算术运算快
int var3 = var2 + (var2 >> 1);
if (var3 - var1 < 0) {
var3 = var1;
}
if (var3 - 2147483639 > 0) {
var3 = hugeCapacity(var1);
}
this.elementData = Arrays.copyOf(this.elementData, var3);
}
private static int hugeCapacity(int var0) {
if (var0 < 0) {
throw new OutOfMemoryError();
} else {
return var0 > 2147483639 ? 2147483647 : 2147483639;
}
}
第二个add方法:
将指定的下标处元素赋值为我们设定的值
public void add(int var1, E var2) {
//检查指定下标范围
this.rangeCheckForAdd(var1);
//确认数组长度是否足够
this.ensureCapacityInternal(this.size + 1);
//数组拷贝,将我们要插入的位置开始的元素全部往后移了一个位置,然后把值插入到指定的位置。插入到指定位置,指定位置的旧值会往后移,并不会被覆盖。
System.arraycopy(this.elementData, var1, this.elementData, var1 + 1, this.size - var1);
this.elementData[var1] = var2;
++this.size;
}
//检查指定的下标索引是否比elementData中拥有元素的数量大或者小于0,有问题则抛出异常。
private void rangeCheckForAdd(int var1) {
if (var1 > this.size || var1 < 0) {
throw new IndexOutOfBoundsException(this.outOfBoundsMsg(var1));
}
}
//src就是:源数组
//srcPos表明:从源数组的下标多少开始复制
//dest就是:目标数组
//destPos表明:复制源数组的数据到从目标数组的下标开始存放
//length就是:打算复制多少个源数组的值
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
(4)clear()
public void clear() {
++this.modCount;
for(int var1 = 0; var1 < this.size; ++var1) {
this.elementData[var1] = null;
}
this.size = 0;
}
首先modCount自加,表示我们对list进行了操作次数。
然后for循环置空(null)即可。
最后设置size等于0。
(5)remove()
第一个remove方法:
移除指定下标的元素
public E remove(int var1) {
//检查下标是否有效
this.rangeCheck(var1);
//操作次数加1
++this.modCount;
//将指定下标的元素取出
Object var2 = this.elementData(var1);
//计算出需要移动多少个元素,指的是从删除位置往后的元素,不包括删除位置的元素。
int var3 = this.size - var1 - 1;
if (var3 > 0) {
//如果个数大于0,那么调用 System.arraycopy()方法将删除位置后的一个元素开始到最后的元素往前移动一个位置。
System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var3);
}
//然后将size立马自减,然后将最后一个位置置为null(因为元素往前移动一位,那么最后一个元素往前移后,原来的最后一个位置值还存在没有被覆盖)。
this.elementData[--this.size] = null;
//最后返回旧的删除位置的元素值。
return var2;
}
第二个remove()方法:
直接移除掉某个元素
整个函数返回类型为boolean,true表示有这个对象删除成功。没有表示数组里没有这个对象,没有进行删除操作。
public boolean remove(Object var1) {
int var2;
//首先判断我们传入的object是否为空,如果为空,那么就for循环找到数组中值为null的元素,调用fastRemove()方法
if (var1 == null) {
for(var2 = 0; var2 < this.size; ++var2) {
if (this.elementData[var2] == null) {
this.fastRemove(var2);
return true;
}
}
} else {
//remove()中如果传入的对象不为null,那么就是for循环找到这个值然后移除即可。
for(var2 = 0; var2 < this.size; ++var2) {
if (var1.equals(this.elementData[var2])) {
this.fastRemove(var2);
return true;
}
}
}
return false;
}
//这就是第一个remove()方法的简化版,取消了越界检查,并且设置返回类型为void,不再返回删除的旧值。
private void fastRemove(int var1) {
++this.modCount;
int var2 = this.size - var1 - 1;
if (var2 > 0) {
System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var2);
}
this.elementData[--this.size] = null;
}
(6)contains()
查询当前ArrayList是否包含某个对象
public boolean contains(Object var1) {
return this.indexOf(var1) >= 0;
}
public int indexOf(Object var1) {
int var2;
//首先是对传入对象的判空。如果对象为空,还是一样的,for循环来查找elementData中第一个为null的元素,然后返回下标。
if (var1 == null) {
for(var2 = 0; var2 < this.size; ++var2) {
if (this.elementData[var2] == null) {
return var2;
}
}
} else {
//如果传入对象不为空,那么一样for循环查找第一个匹配元素,然后返回第一个匹配元素的下标。
for(var2 = 0; var2 < this.size; ++var2) {
if (var1.equals(this.elementData[var2])) {
return var2;
}
}
}
//如果都找不到,那么就返回-1。
return -1;
}
(7)get()
进行一个下标的越界判断,然后返回elementData[index]元素。
(8)set()
其实set(int index, E element)和add(int index, E element)方法很相似。
只是set是将指定位置的值直接覆盖掉。
add()则是将指定位置开始的元素往后全部后移一位,旧值不会被覆盖掉。
总结
1. ArrayLIst内部是由【数组】实现的。而且在存放数据的数组长度不够时,会进行扩容(即增加数组长度),在Java8中是默认扩展为原来的1.5倍
int var3 = var2 + (var2 >> 1);
2. 既然是数组:
优点:查找某个元素很快,可以通过下标查找元素,查找效率高。
缺点:每次删除,都会进行大量的数组元素移动,复制新的数组等。
增加元素如果长度不够,还要进行扩容。
因此添加删除效率低。
如果我们在实际开发中能够清楚知道我们的数据量,建议创建ArrayList的时候指定长度,这样无需频繁增加数据时不断进行扩容。