ArrayList
ArrayList是由数组支持实现的,内部封装了一个数组,下面介绍一下ArrayList内部的一些属性(其中包含三个数组,若暂时不理解可以继续向下看构造函数的实现,会有助于理解这三个数组的用途)
/**
* 默认长度
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 长度为0的空数组,用于对该集合中elementData初始化
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 长度为0的空数组,用于对该集合中elementData初始化。
* 该空数组与EMPTY_ELEMENTDATA的区别是,如果用这个数组初始化,那么在第一次添加数据的时* 候,数组长度会被扩展到10
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 用于存放集合元素的数组
* 非private权限,便于嵌套类访问
*/
transient Object[] elementData;
/**
* 集合中的元素个数
*/
private int size;
之前说过,Collection集合必须包含两个类型的构造函数(参考java集合类),ArrayList中有三种构造器,我们来分别看一下这三种构造器的内部实现
/**
* 第一个构造器
* 初始化一个指定长度的数组,用于以后存放集合元素,若指定的长度为0,则用
* EMPTY_ELEMENTDATA去初始化,创建一个长度为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);
}
}
/**
* 第二个构造器
* 初始化一个长度为默认长度(10)的数组,这里用DEFAULTCAPACITY_EMPTY_ELEMENTDATA去初* 始化,上面有介绍,这个数组也是一个长度为0的空数组,但是当存入第一个元素时,长度会被自动* 扩展到默认长度(10),这也是和EMPTY_ELEMENTDATA的主要区别
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 第三个构造器
* 此构造器用于复制一个集合
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
看完了三种构造器的源码,应该对ArrayList中的属性有了进一步的了解,终于明白了定义的这三个空数组的用处了。
接下来我们继续看一下ArrayList的扩容操作,虽然我们平时很少用到或没有用到过,但是理解一下这个方法会更好的理解ArrayList这个类
/**
* 此方法在向集合中添加大量元素前使用,以确保容量
* 此处需配合add源码来理解,因为进行add操作时,会调用ensureCapacityInternal来保证容量* 足够插入数据,由于默认大小是10,所以当大批量插入数据时会有频繁的扩容操作,所以我们需要 * 调用此方法来一次性扩容到我们需要的那么大的容量,避免扩容操作过于频繁,提高性能
*
* minCapacity:所需的最小容量
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)? 0
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
/**
* 此操作在ArrayList的add*()操作中会被默认调用,以保证集合内数组的容量
*
* minCapacity:所需的最小容量
*/
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//这个属性稍后会做介绍
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 此属性用来表示要分配的数组的最大大小。
* 一些虚拟机会在数组中存一些头部关键字,所以尝试分配较大数组可能会导致内存溢出
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 扩容容量计算
*/
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
/*
*扩容操作
*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
上述介绍了两种扩容操作ensureCapacity和ensureCapacityInternal,前者是我们在进行大量插入元素操作时使用(提升性能),后者是ArrayList执行添加操作时默认调用,上面的介绍还留了一个关键点没有讲,就是modCount的来源和用途
modCount:此属性定义在AbstractList抽象类中,顾名思义,就是修改次数的意思,任何对集合的修改都会使这个值加1,用在非线程安全的方法中。集合都是用迭代器(iterator)迭代访问的,那么迭代器初始化的时候会得到这个值,在迭代访问过程中,如果集合中的这个值和迭代器初始化时得到的不一样,那么就会立刻抛出ConcurrentModificationException,避免其他线程对其修改造成的影响对此线程不可见,这也就是所谓的快速失败(fail-fast)机制,所以建议大家在使用非线程安全的集合类的时候,使用迭代器访问
下面我们来看一下”克隆”这个操作:
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
这里用到了super.clone(),这个是Object类中的方法,所以这里的clone是浅拷贝,有以下特点:
public static void main(String[] args) {
ArrayList collection = new ArrayList<>();
collection.add(null);
collection.add("1");
collection.add("3");
collection.add("2");
collection.add("2");
Object collection_1 = collection.clone();
System.out.println(collection == collection_1);
System.out.println(collection.getClass()== collection_1.getClass());
System.out.println(collection.equals(collection_1));
}
此处输出的结果是false,true,true
下面看一下比较重要的一个操作,就是取元素,这里有两个方法
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
....
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
get()方法用的其实就是elementData方法,不过前面加上了一个数组边界的判断,细心的人可以发现,rangeCheck中只有一个条件:if (index >= size),此条件可以避免输入的参数超过数组长度,那么如果输入参数小于0呢?这个会由数组Arrays类去判断下标小于0,则会抛出java.lang.ArrayIndexOutOfBoundsException: -xxx
接下来是一个常用的操作,添加元素
public boolean add(E e) {
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++;
}
可以发现,无论是直接插入,还是在指定位置插入(先将要插入位置及后面的元素向后移动,再用新值覆盖要插入的位置),都会先执行以下ensureCapacityInternal,在前面有提到过这个操作的作用和用途,这里不再赘述,可以看上面关于扩容操作的介绍
CRUD操作我们看完了增和查,下面来看一下改和删
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
修改操作没什么可说的,它也会进行边界判断,修改后会将原值返回
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;
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;
}
删除操作常用的有以上两个,一个是按索引删除,一个是按元素删除。按索引删除会返回被删除元素的值,按元素删除用到了另一个删除方法fastRemove,两种删除操作都用到modCount这个属性,来保证线程安全,并且删除之后,数组元素都会移动位置
ArrayList中有一个内部类subList,该类实际上只是父类list的一个视图,任何对该类元素的修改都会影响父类中的元素,同时,由于快速失败机制,也不允许对已经形成视图(sublist)的list进行add和remove操作
举个例子:
List list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
list.add("2");
System.out.println(list);
List subList = list.subList(1,3);//前面包含、后面不包含
System.out.println(subList);
//list.add("10");
list.remove(3);
System.out.println(subList);
System.out.println(list);
输出结果是:
[1, 2, 3, 2]
[2, 3]
同时会抛出java.util.ConcurrentModificationException
但是可以进行set操作
List list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
list.add("2");
System.out.println(list);
List subList = list.subList(1,3);//前面包含、后面不包含
System.out.println(subList);
list.set(1,10);
System.out.println(subList);
System.out.println(list);
输出结果是:
[1, 2, 3, 2]
[2, 3]
[10, 3]
[1, 10, 3, 2]
均为个人理解,欢迎批评指正,谢谢