1.Vector
Vector和ArrayList很相似,其内部都是通过一个容量能动态增长的数组来实现的。不同点是Vector是线程安全的。因为其内部有很多同步代码块来保证线程安全。
Vector的大小是可以增加或者减小的,以便适应创建Vector后进行添加或者删除操作。
Vector有几个重要属性:
protected Object[ ] elementData; //底层数组
protected int elementCount; //数组有效元素个数
capacityIncrement //扩容时的增长系数
2.构造方法
Vector的构造方法一共有四个。
①创建一个空的Vector,并且指定了Vector的初始容量为10。
public Vector(){
this(10);
}
②创建一个空的Vector,并且指定了Vector的初始容量。
public Vector(int initalCapacity){
this(initalCapacity,0)
}
③创建一个空的Vector,并且指定了Vector的初始容量和扩容时的增长系数。
public Vector(int initalCapacity, int capacityIncrement){
super();
if(initalCapacity < 0)
throw new IllegalArgumentException("illegal capacity:" + initalCapacity);
this.elementData = new Object[initalCapacity];
this.capacityIncrement = capacityIncrement;
}
④根据其他集合来创建一个非空的Vector。
public Vector(Collection<? extends E> c) {
elementData = c.toArray();
elementCount = elementData.length;
if(elementData.getClass() != Object[ ].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[ ].class);
}
首先把其他集合转化为数组,然后复制粘贴到Vector里面。
3.增加元素
增加元素有两个主要的方法,第一个是在Vector尾部追加,第二个是在指定位置插入元素。
①在Vector尾部追加元素
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);//判断容量大小,若能装下就直接放进去,装不下就扩容
elementData[elementCount++] = e;
return true;
}
然后看一下ensureCapacityHelper是如何实现的:
private void ensureCapacityHelper(int minCapacity) {
if(minCapacity - elementData.length > 0)
grow(minCapacity);
}
所以真正扩容的方法是grow,那再进去看看:
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//capacityIncrement表示需要新增加的数据,如果大于0就扩充指定数量,否则就翻倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
//若进行扩容后,capacity仍然小,则新容量改为实例需要的minCapacity大小
if(newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新数组的长度比虚拟机能够提供给数组的最大存储空间还大,则将新数组长度更改为最大正数Integer.MAX_VALUE
if(newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//按照新容量newCapacity创建一个新数组,然后再将原数组中的内容复制到新数组中,并将elementData指向新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
扩容的时候要排除一些异常的情况,首先就是capacityIncrement(需要增加的数量)是否大于0,如果大于0直接增加这么多。然后发现增加了上面那些还不够,那就扩充为实际需要minCapacity的大小。最后发现还不够,就只能扩充到虚拟机能表示的数字最大值了。
②在指定位置增加元素
public void add(int index, E element){
insertElementAt(element, index);
}
public synchronized void insertElementAt(E obj, int index){
modCount++; //fail-fast机制
//判断index下标的合法性
if(index > elementCount){
throw new ArrayIndexOutOfBoundsException (index + ">" + elementCount);
}
ensureCapacityHelper(elementCount + 1);
第四步:数组拷贝,将index到末尾的元素拷贝到index+1到末尾的位置,将index的位置留出来
System.arrayCopy(elementData, index, elementData, index +1, elementCount - index);
elementData[index] = obj;
elementCount++;
}
4.删除元素
删除元素也有两种方法,第一个根据元素值来删除,第二个根据下表来删除元素。
①根据元素值来删除元素
public boolean remove(Object o) {
return removeElement(o);
}
删除元素其实是调用了removeElement()方法来删除元素的,进入这个方法内部看一下。
public synchronized boolean removeElement( Object obj) {
modCount++; //fail-fast机制
int i = indexOf(obj);//查找obj在数组中的下标
if(i >= 0){ //下标不小于0,说明Vector容器中含有这个元素
//调用removeElementAt方法删除元素
removeElementAt(i);
return true;
}
return false;
}
真正执行删除操作的是removeElementAt(i),再进入这个方法看看:
public synchronized void removeElementAt(int index) {
modCount++; //fail-fast机制
//index下标合法性检验
if(index >=elementCount){
throw new ArrayIndexOutOfBoundsException (index + ">=" + elementCount);
} else if(index < 0){
throw new ArrayIndexOutOfBoundsException (index);
}
//要移动的元素个数
int j = elementCount - index - 1;
if(j > 0){
//将index之后的元素向前移动一位
System.arrayCopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null;
}
可以看到,删除元素要移动大量的元素,时间效率肯定是不好的。毕竟Vector是通过数组来实现的,而不是通过链表。
②删除指定位置的元素
删除指定位置的元素就比较简单了,到指定的位置进行删除,然后把后面的元素进行移位。
public synchronized E remove(int index) {
modCount++; //fail-fast机制
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);
elementData[--elementCount] = null;
return oldValue;
}
5.更改元素
public synchronized void setElementAt(E obj, int index) {
if(index >= elementCount)
throw new ArrayIndexOutOfBoundsException (index);
elementData[index] = obj;
}
6.查找元素
查找元素有三个,第一个查询Vector容器中是否包含某个元素,第二个查询第一次出现指定元素的索引,第三个最后一次出现指定元素的索引。
①查询Vector容器中是否包含某个元素
public boolean contains(Object o) {
return indexOf(o, 0) >= 0;
}
查询Vector是否包含某个元素,其实是调用了第二个方法,那直接就看第二个。
②查询第一次出现的指定元素的索引
public synchronized int indexOf(Object o, int index) {
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;
}
}
return -1;
}
③查询最后一次出现的指定元素的索引
public synchronized int lastIndexOf(Object o, int index){
if(index >= elementCount)
throw new ArrayIndexOutOfBoundsException (index + ">=" + elementCount);
if(o == null) {
for(int i = index; i >= 0;i--) {
if(elementData[i] == null)
return i;
}
} else {
for(int i = index; i >= 0;i--) {
if(o.equals(elementData[i]))
return i;
}
}
return -1;
}
7.Vector特点
①线程安全
从Vector的构造方法还有增删改查的操作,可以发现,这些方法都有synchronized关键字,就是这个关键字为Vector容器提供了一个安全机制,保证了线程安全。
②Vector实际上是通过一个数组保存数据的。构造Vecotr时,使用默认构造函数,默认容量大小是10。
③当Vector容量不足以容纳全部元素时,Vector的容量会增加。若容量增加系数大于0,则将容量的值增加“容量增加系数”;否则,将容量大小增加一倍。
8.Vector与其他容器的区别
通过源码可以发现,Vector和ArrayList很相似,下面对比区分一下:
①ArrayList是线程非安全的,因为ArrayList中所有的方法都不是同步的,在并发下一定会出现线程安全问题。而Vector是线程安全的。
②Vector可以指定增长因子,如果该增长因子指定了,那么扩容的时候,每次新的数组大小会在原数组的大小基础上加上增长因子;如果不指定增长因子,那么就扩容到原数组大小的2倍。