最近想把学到的知识汇总起来整理一遍,方便日后升职加薪,于是想了想还是从Java基础开始搞起吧~
我们先来梳理下Java集合中几个重要的接口:
java.lang.Iterable接口
List,Set,Queue集合的上层都需要继承java.lang.Iterable接口,实现该接口的类具备两种能力,一种是forEach(Consumer<? super T> action) 方法和spliterator方法
java.util.Collection接口
间接实现java.util.Collection接口的类都是可以按照某种逻辑结构存储一组数据的集合;
java.util.AbstractList抽象类
根据list集合在各维度上的表现出来的工作特点,可以对其进行分类,分类标准可以说有三种:
- 根据是否支持随机访问的特点进行分类, 分为支持随机访问的list集合和不支持随机访问的list集合。
- 根据是否具有数据的可修改权限进行分类,可以分为可修改的list集合和不可修改的list集合。
- 根据集合的容量是否可变进行分类,可以分为大小可变的list集合和大小不可变的list集合。
java.util.AbstractList抽象类提供了一种能力,各种具体的list集合只需要根据自身情况重写java.util.AbstractList抽象类中的不同方法就可以定制化自己的工作特点。例如,set(int)方法的功能是替换指定索引位上的数据对象,如果当前List集合不支持修改,则一定会抛出UnsupportedOperationException异常;对于大小可变的类,只需要重写add(int, E)方法和remove(int)方法;如果开发人员不需要实现支持随机访问的List集合,则可以使其继承java.util.AbstractSequentialList抽象类。
java.util.RandomAccess接口
是一种标识接口。标识接口通常不需要实现任何方法。主要用于向调用者表示这些list集合支持集合中数据对象的随机访问。其他标识接口如java.lang.Cloneable接口和java.io.Serializable接口;
java.util.ArrayList,java.util.Vector,java.util.concurrent.CopyOnWriteArrayList都实现了java.util.RandomAccess接口,另外我们常用的三方类如JSONArray类也实现了RandomAccess接口;
如果类实现了java.util.RandomAccess接口,表示支持随机访问,例如ArrayList是支持随机访问的,LinkedList是不支持随机访问的,其set方法实现机制完全不同,代码如下:
ArrayList
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
LinkedList
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
因为如此,所以我们再看Collections中的fill方法,其针对是否实现RandomAccess接口逻辑是不相同的
public static <T> void fill(List<? super T> list, T obj) {
int size = list.size();
// FILL_THRESHOLD 为 20
if (size < FILL_THRESHOLD || list instanceof RandomAccess) {
for (int i=0; i<size; i++)
list.set(i, obj);
} else {
ListIterator<? super T> itr = list.listIterator();
for (int i=0; i<size; i++) {
itr.next();
itr.set(obj);
}
}
}
针对于不支持随机访问的集合使用迭代器的操作,从而避免不必要的索引位置查询操作,在每次调用next()方法时,迭代器都可以基于上次操作的索引位继续寻找下一个索引位,而不需要重新从第一个索引位进行查询;
Vector集合
Vector集合的工作特性除了支持数据对象的随机访问,还有集合的大小可变,保证线程安全的运行环境;在Vector中有三个关键的变量:
// 保存元素的数组,可以扩展,当数组容量大于当前已写入的数据时,存null,初始化大小由构造方法中的initialCapacity决定,initialCapacity参数默认值为10
protected Object[] elementData;
// 主要用于记录当前Vector集合中的数据对象数量
protected int elementCount;
// 支持扩容操作,capacityIncrement表示扩容大小,当小于或者等于0时,2倍扩容
protected int capacityIncrement;
Vector集合的扩容场景分析
vector扩容场景分为以下几种:
- 集合在初始化时会进行扩容操作;
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector() {
// 默认初始化容量为10
this(10);
}
在集合初始化的时候,如果没有指定initialCapacity,那么初始化容量为10, 如果没有指定capacityIncrement,那么在随后的每次扩容中都是扩容前容量的2倍。
- 当数据对象数量elementCount大于最大容量capacity时,也会进行扩容操作;我们看Vector的add(E)方法;
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
当集合中的对象数据数量elementCount +1 的值大于当前elementData数组容量的值时,会进行扩容操作;
- 指定容量大小的时候,也会进行扩容
public synchronized void setSize(int newSize) {
modCount++;
if (newSize > elementCount) {
ensureCapacityHelper(newSize);
} else {
for (int i = newSize ; i < elementCount ; i++) {
elementData[i] = null;
}
}
elementCount = newSize;
}
如果设置的容量值大于当前Vector集合的容量值,则会进行扩容,否则的话,多余的数据将会被丢弃掉;
Vector集合的扩容原理
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
我们重点讲一下Arrays.copyOf()
该方法主要用于将原始数组(original)复制为新的数组,后者的长度为指定的新的长度(newLength);那么将会出现以下四种情况
- 指定的长度小于当前数组的长度:原始数组无法被复制的部分将会被抛弃
- 指定的长度大于或者等于原始数组的长度:原始数组会一次复制到新的数组,多余的部分会被赋值为null(如果是基本类型,则会被赋值为默认值,如int为0)
- 指定的长度小于0:抛出异常NegativeArraySizeException
- 指定的长度等于0:等同于指定长度小于当前数组长度,返回一个空数组
Vector集合修改方法
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
从源码中我们可以看出index必须要小于elementCount,返回值为替换前该索引位置上的值,新的值可以是null;
Vector集合删除方法
public synchronized void removeElementAt(int index) {
modCount++;
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
// j代表的是elementData要移动的长度
int j = elementCount - index - 1;
// j=0时表示的是要移除的是当前集合的最后一位
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
// elementData数据对象-1
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}
这段代码表示的意思其实是:从指定的索引位置起,将后续数组依次向前挪动1位;