Java集合框架-Vector的源码解析

451 阅读11分钟

一、前言

站在巨人的肩膀上,本系列的Java集合框架文章参考了skywang12345——Java 集合系列,真心讲的很好,可以参考。但是不足的是,时间过于长久了,大佬的文章是基于JDK1.6.0_45,对于现在来说至少都用JDK 8.0以上了,而且JDK 6.0与JDK 8.0中集合框架的改动挺大的,所以本系列的文章是基于JDK_1.8.0_161进行说明的。

二、介绍

我们先来看看Vector的类定义,以及继承关系:

java.util.Collection<E>
    -> java.util.AbstractCollection<E>
        -> java.util.AbstractList<E>
            -> java.util.Vector<E>

public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • Vector 是矢量队列,它是JDK1.0版本添加的类。继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口。
  • Vector 继承了AbstractList,实现了List;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能。
  • Vector 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在Vector中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
  • Vector 实现了Cloneable接口,即实现clone()函数。它能被克隆。
  • 和ArrayList不同,Vector中的操作是线程安全的。

再来从整体看一下Vector与Collection关系,如下图: Vector的数据结构和ArrayList差不多,它包含了3个成员变量:elementData , elementCount, capacityIncrement。
(01) elementData 是"Object[]类型的数组",它保存了添加到Vector中的元素。elementData是个动态数组,如果初始化Vector时,没指定动态数组的大小,则使用默认大小10。随着Vector中元素的增加,Vector的容量也会动态增长,capacityIncrement是与容量增长相关的增长系数,具体的增长方式,请参考源码分析中的ensureCapacity()函数。
(02) elementCount 是动态数组的实际大小。
(03) capacityIncrement 是动态数组的增长系数。如果在创建Vector时,指定了capacityIncrement的大小;则每次当Vector中动态数组容量增加时,增加的大小都是capacityIncrement。

三、解析

对源码的解析,我个人喜欢从方法的使用上去看,而不是把源码所有的方法从头到尾的都去看一遍,除非是那种比较短的源码还行,如果是上千行代码的话,我觉得会很崩溃的。这样不仅学习效率底下,而且看源码本身就是一件非常枯燥的事情,容易看着看着就不想看下去了(大佬忽略),非常打击学习源码的兴趣。

1、首先我们来看看它的成员变量

//存放元素的数组
protected Object[] elementData;
//数组的大小长度
protected int elementCount;
//容量增量(即当Vector中数组长度不足以容纳新的元素时,就会增加此变量的容量大小)
protected int capacityIncrement;

2、接着来看看它的构造方法,Vector它有4个构造方法。

//默认的构造方法
public Vector() {
	//调用了第二个构造方法,默认传递一个10当作Vector的初始容量
	this(10);
}

//传入一个初始容量构造Vector
public Vector(int initialCapacity) {
	//调用第三个构造方法,并把扩容增量设置为0.传递过去
	this(initialCapacity, 0);
}

//通过传入一个初始容量和扩容增量来构造一个Vector
public Vector(int initialCapacity, int capacityIncrement) {
	super();
	if (initialCapacity < 0)
		throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    //创建一个新的数组,并赋值给elementData
	this.elementData = new Object[initialCapacity];
    //设置扩容增量
	this.capacityIncrement = capacityIncrement;
}

//通过传入一个集合来构造Vector
public Vector(Collection<? extends E> c) {
	//先把集合转化成数组后,赋值给elementData
	elementData = c.toArray();
	elementCount = elementData.length;
	// c.toArray might (incorrectly) not return Object[] (see 6260652)
    //如果集合不是Object类型的,那么就把elementData转化成Object类型
	if (elementData.getClass() != Object[].class)
		elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}

3、当构造一个空的Vector后,我们肯定需要添加数据元素,那就去看看添加元素方法。添加方法有2组:add(E e)和add(int index, E element)和addElement(E obj)、addAll(int index, Collection<? extends E> c)和addAll(Collection<? extends E> c)。先来看第一组:

//添加元素
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);
}

//增长容量
private void grow(int minCapacity) {
	//先获取旧容量——当前数组的长度
	int oldCapacity = elementData.length;
	//1.当有设置容量增量时,新容量=旧容量+容量增量
	//2.当没有设置容量增量时,新容量=旧容量+旧容量(即扩充的容量为原来容量的2倍)
	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);
}

//添加元素到具体的索引处
public void add(int index, E element) {
	insertElementAt(element, index);
}

//插入元素到特定的位置
public synchronized void insertElementAt(E obj, int index) {
	modCount++;
	if (index > elementCount) {
		throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount);
	}
	//确保容量是否充足
	ensureCapacityHelper(elementCount + 1);
	//对index索引后面的数据进行拷贝
	System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
	//添加到index索引的位置
	elementData[index] = obj;
	//更新数组中真实元素长度
	elementCount++;
}

//这个方法和add(E e)方法添加逻辑是完全一样的,区别只是add(E e)方法是有返回值的,此方法没有返回值
public synchronized void addElement(E obj) {
	modCount++;
	ensureCapacityHelper(elementCount + 1);
	elementData[elementCount++] = obj;
}

这里用add(E e)方法做具体的说明。
(1)首先更新了修改次数,接着调用了ensureCapacityHelper确保数组容量的帮助类,并把数组长度+1传入;
(2)进入ensureCapacityHelper方法,判断了数组最小容量和数组长度的大小,如果最小容量大于数组长度,才会进行扩容操作,否则就不需要扩容。再说的直白一点,当调用了无参数的构造方法创建Vector后,默认会创建长度为10的一个动态数组,第一次添加数据,此时这里的数组长度就等于10,而此时的elementCount任然是0,所以就不会进入grow方法,也就不会进行扩容操作;当添加到第11个元素时,传入到ensureCapacityHelper方法中参数值就是11,此时就会进入grow方法进行扩容了。
(3)进入grow方法,先获取了旧容量,也就是数组长度;然后当调用了构造方法设置了增量容量后,新的扩容量就等于旧容量加上增量容量,否则新容量就等于旧容量的2倍;最后根据新的容量对数组的数据进行拷贝;
(4)把元素添加到数组最末尾,并更新数组真实元素长度。

接着看第2组:

//添加一个集合
public synchronized boolean addAll(Collection<? extends E> c) {
	modCount++;
	//先把集合转化成数组
	Object[] a = c.toArray();
	//获取数组的长度
	int numNew = a.length;
	//确保容量是否充足
	ensureCapacityHelper(elementCount + numNew);
	//进行数据的拷贝
	System.arraycopy(a, 0, elementData, elementCount, numNew);
	//更新真实数据长度
	elementCount += numNew;
	//如果集合不为空返回true,否则返回false
	return numNew != 0;
}

//把集合添加到特定索引位置
public synchronized boolean addAll(int index, Collection<? extends E> c) {
	modCount++;
	if (index < 0 || index > elementCount)
		throw new ArrayIndexOutOfBoundsException(index);

	//转化成数组
	Object[] a = c.toArray();
	//获取数组长度
	int numNew = a.length;
	//确保容量是否能够容纳集合元素
	ensureCapacityHelper(elementCount + numNew);

	int numMoved = elementCount - index;
	if (numMoved > 0)
		//先给集合数据腾位置,把原来index后的数据往后移动numNew个长度
		System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
	//把集合中的数据拷贝到数组中来
	System.arraycopy(a, 0, elementData, index, numNew);
	//更新真实数组长度
	elementCount += numNew;
	//如果集合不为空返回true,否则返回false
	return numNew != 0;
}

4、继续看看删除方法,删除元素的方法有很多。大致可以分为3组:remove(Object o)和remove(int index)、removeAll(Collection<?> c)和removeAllElements()、removeElement(Object obj)和removeElementAt(int index)和removeRange(int fromIndex, int toIndex)。首先来看看第一组:

//删除指定元素
public boolean remove(Object o) {
	return removeElement(o);
}

//移除元素
public synchronized boolean removeElement(Object obj) {
	modCount++;
	//根据元素获取下标索引
	int i = indexOf(obj);
	if (i >= 0) {
		//删除指定下标索引的元素
		removeElementAt(i);
		return true;
	}
	//若未找到元素值时,indexOf会返回-1,则就会返回false
	return false;
}

//根据元素定位下标索引
public int indexOf(Object o) {
	return indexOf(o, 0);
}

//从指定下标索引开始,查询指定元素的下标索引
public synchronized int indexOf(Object o, int index) {
	//如果为null,说明Vector可以添加null元素
	if (o == null) {
		//从指定下标开始遍历
		for (int i = index ; i < elementCount ; i++)
			if (elementData[i]==null)
		return i;
	} else {
		for (int i = index ; i < elementCount ; i++)
			if (o.equals(elementData[i]))
				return i;
	}
	//未找到,直接返回-1
	return -1;
}

//根据索引删除元素
public synchronized void removeElementAt(int index) {
	modCount++;
	//判断下标的合法性
	if (index >= elementCount) {
		throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
	}else if (index < 0) {
		throw new ArrayIndexOutOfBoundsException(index);
	}
	int j = elementCount - index - 1;
	//如果移除的下标不是最后一个元素的话,就先对数组进行拷贝,把指定索引的下标元素移动到数组的最后
	if (j > 0) {
		System.arraycopy(elementData, index + 1, elementData, index, j);
	}
	//更新数组真实元素大小
	elementCount--;
	//把数组末尾的元素设置成null
	elementData[elementCount] = null; /* to let gc do its work */
}

//删除指定下标索引的元素,并返回元素值
public synchronized E remove(int index) {
	modCount++;
	if (index >= elementCount)
		throw new ArrayIndexOutOfBoundsException(index);
	//先获取索引对应的元素值
	E oldValue = elementData(index);

	int numMoved = elementCount - index - 1;
	//先对数组进行拷贝,把指定索引的下标元素移动到数组的最后
	if (numMoved > 0)
		System.arraycopy(elementData, index+1, elementData, index,numMoved);
	//把数组末尾的元素设置成null,并更新数组真实元素大小
	elementData[--elementCount] = null; // Let gc do its work
	return oldValue;
}

//根据下标索引,返回元素值
E elementData(int index) {
	return (E) elementData[index];
}

这里用remove(Object o)方法来做具体说明。
(1)直接调用removeElement方法。进入removeElement方法,更新修改次数,并根据元素值返回对应的下标索引;
(2)当没有找到了元素,indexOf会返回-1,此时就返回false,表示删除元素失败;
(3)当找到了元素时,就会返回对应的下标,调用removeElementAt方法;
(4)进入removeElementAt方法,先判断索引值是否合法;再判断索引是否时数组的最后一个元素,如果是最后一个的话,不会进入if语句,更新真实数组大小,把数组的最后一个元素设置成null,是的GC进行垃圾回收;
(5)如果不是最后一个元素的话,会对数组进行拷贝移动,把指定下标之后的元素整体移动,把指定的下标移到数组的最末尾,接着就进行相关操作。

接着来看第二组删除方法:

//删除指定集合元素
public synchronized boolean removeAll(Collection<?> c) {
	//调用了父类AbstractCollection的方法
	return super.removeAll(c);
}

//对集合进行迭代删除
public boolean removeAll(Collection<?> c) {
	//这里要求集合元素中不能有null,否则会抛出空指针异常
	Objects.requireNonNull(c);
	boolean modified = false;
	Iterator<?> it = iterator();
    //进行迭代删除
	while (it.hasNext()) {
		if (c.contains(it.next())) {
			it.remove();
			modified = true;
		}
	}
	return modified;
}

//删除Vector中的全部元素
public synchronized void removeAllElements() {
	modCount++;
	//遍历整个数组,把全部元素置为null
	for (int i = 0; i < elementCount; i++)
		elementData[i] = null;

	//设置真实元素大小为0
	elementCount = 0;
}

第三组删除方法中前两个已经再第一组中说明不再赘述

//根据范围进行删除
protected synchronized void removeRange(int fromIndex, int toIndex) {
	modCount++;
	int numMoved = elementCount - toIndex;
    //把需要删除的范围进行拷贝,把待删除的元素拷贝到末尾
	System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);

	// Let gc do its work
	int newElementCount = elementCount - (toIndex - fromIndex);
    //迭代数组,把末尾元素设置为null,并更新真实数组大小
	while (elementCount != newElementCount)
		elementData[--elementCount] = null;
}

5、继续了解Vector获取元素的get操作和修改元素的set操作。

//通过下标索引,获取元素值
public synchronized E get(int index) {
	if (index >= elementCount)
		throw new ArrayIndexOutOfBoundsException(index);

	return elementData(index);
}

//根据索引返回元素值
E elementData(int index) {
	return (E) elementData[index];
}

//更新特定索引下的元素值
public synchronized E set(int index, E element) {
	if (index >= elementCount)
		throw new ArrayIndexOutOfBoundsException(index);
	
    //先获取旧元素值
	E oldValue = elementData(index);
    //更新元素值
	elementData[index] = element;
    //返回旧的元素值
	return oldValue;
}

//直接覆盖特定索引下的元素值
public synchronized void setElementAt(E obj, int index) {
	if (index >= elementCount) {
		throw new ArrayIndexOutOfBoundsException(index + " >= " +elementCount);
	}
	elementData[index] = obj;
}

6、Vector其他常用的方法。

//清空Vector中的全部元素
public void clear() {
	removeAllElements();
}

//是否包含某个元素
public boolean contains(Object o) {
	//若存在则返回对应的索引,返回true;不存在返回索引为-1,返回false
	return indexOf(o, 0) >= 0;
}

//返回指定索引的元素值
public synchronized E elementAt(int index) {
	if (index >= elementCount) {
		throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
	}

	return elementData(index);
}

//获取Vector的第一个元素值
public synchronized E firstElement() {
	if (elementCount == 0) {
		throw new NoSuchElementException();
	}
	return elementData(0);
}

//获取Vector的最后一个元素值
public synchronized E lastElement() {
	if (elementCount == 0) {
		throw new NoSuchElementException();
	}
	return elementData(elementCount - 1);
}

//判断Vector是否为空
public synchronized boolean isEmpty() {
	return elementCount == 0;
}

//返回Vector长度
public synchronized int size() {
	return elementCount;
}

//把Vector转化成Object类型的数组
public synchronized Object[] toArray() {
	//进行数组的拷贝
	return Arrays.copyOf(elementData, elementCount);
}

//根据传入的数组,把Vector转化成特定类型的数组
public synchronized <T> T[] toArray(T[] a) {
	if (a.length < elementCount)
		return (T[]) Arrays.copyOf(elementData, elementCount, a.getClass());

	System.arraycopy(elementData, 0, a, 0, elementCount);
    //如果数组长度大于真实数组大小,后面的元素全部设置为null
	if (a.length > elementCount)
		a[elementCount] = null;

	return a;
}

以上就是我们在使用Vector会经常使用到的方法,其他方法就不再说明了,可以自己去看看。

四、总结

  • Vector实际上是通过一个数组去保存数据的。当我们构造Vecotr时;若使用默认构造函数,则Vector的默认容量大小是10。
  • 当Vector容量不足以容纳全部元素时,Vector的容量会增加。若容量增量大于0时,则将容量的值增加“容量增量”;否则,将容量大小增加一倍。
  • Vector的克隆函数,即是将全部元素克隆到一个数组中。
  • Vector是线程安全的。

五、参考

Java 集合系列06之 Vector详细介绍(源码解析)和使用示例