Java集合【9】-- Vector源码解析
1.Vector介绍
Vector
和前面说的ArrayList
很是类似,这里说的也是1.8版本,它是一个队列,但是本质上底层也是数组实现的。同样继承AbstractList
,实现了List
,RandomAcess
,Cloneable
, java.io.Serializable
接口。具有以下特点:
- 提供随机访问的功能:实现
RandomAcess
接口,这个接口主要是为List
提供快速访问的功能,也就是通过元素的索引,可以快速访问到。 - 可克隆:实现了
Cloneable
接口 - 是一个支持新增,删除,修改,查询,遍历等功能。
- 可序列化和反序列化
- 容量不够,可以触发自动扩容
- **最大的特点是:线程安全的*,相当于线程安全的
ArrayList
。
定义源码如下:
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
}
2. 成员变量
底层是数组,增加元素,数组空间不够的时候,需要扩容。
- elementData:真正保存数据的数组
- elementCount:实际元素个数
- capacityIncrement:容量增加系数,就是扩容的时候增加的容量
- serialVersionUID:序列化id
// 真正保存数据的数组
protected Object[] elementData;
// 元素个数
protected int elementCount;
//容量增加系数
protected int capacityIncrement;
// 序列化id
private static final long serialVersionUID = -2767605614048989439L;
3. 构造函数
Vector
一共有四个构造函数:
- 指定容量和增长系数
- 指定容量
- 不指定,使用默认容量值10
- 指定集合初始化
1.指定容量和增长系数构造函数
public Vector(int initialCapacity, int capacityIncrement) {
super();
// 非法判断
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
// 初始化数组
this.elementData = new Object[initialCapacity];
// 指定增长系数
this.capacityIncrement = capacityIncrement;
}
2.指定初始化容量,增长系数默认为0
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
3.什么都不指定,默认给的容量是10:
public Vector() {
this(10);
}
4.指定集合初始化:
public Vector(Collection<? extends E> c) {
// 转换成为数组
Object[] a = c.toArray();
// 大小为数组的大小
elementCount = a.length;
// 如果是ArrayList,则直接复制
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
// 否则需要进行拷贝
elementData = Arrays.copyOf(a, elementCount, Object[].class);
}
}
4. 常用方法
4.1 增加
增加元素,默认是在最后添加,如果容量不够的时候会触发扩容机制。
public synchronized void addElement(E obj) {
// 修改次数增加
modCount++;
// 确保容量足够(如果需要,里面会有扩容,复制操作)
ensureCapacityHelper(elementCount + 1);
// 将新元素放在最后一个元素,个数增加
elementData[elementCount++] = obj;
}
那么它是如何确保容量的呢?
可以看到ensureCapacityHelper()
里面判断增加后的元素个数是否大于现在数组的长度,如果不满足,就需要扩容。调用grow()
函数扩容。
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 扩容,传入的是需要最小的容量
private void grow(int minCapacity) {
// overflow-conscious code
// 以前的容量
int oldCapacity = elementData.length;
// 现在的容量,是以前的容量加上扩展系数,如果扩展系数小于等于0,那么,就是以前的容量的两倍
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);
}
在指定的索引index,插入数据,实际上调用的是insertElementAt(element, index)
.
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);
// 拷贝数据,将后面的元素,往后移动一位
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
// 将实际的数据插入
elementData[index] = obj;
// 个数增加
elementCount++;
}
将一个集合所有元素添加进去:
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;
// 返回添加的数组是不是有数据
return numNew != 0;
}
指定index,插入一个集合,和前面不一样的地方在于复制之前,需要计算往后面移动多少位,不是用for循环去插入,而是一次性移动和写入。
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)
// 移动后面的元素,腾出位置给插入的元素
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 插入元素
System.arraycopy(a, 0, elementData, index, numNew);
// 更新个数
elementCount += numNew;
// 插入元素个数是否为0
return numNew != 0;
}
4.2 删除
删除指定元素
public boolean remove(Object o) {
return removeElement(o);
}
// 实际调用的是removeElement()
public synchronized boolean removeElement(Object obj) {
// 修改次数增加
modCount++;
// 获取第一个满足条件的元素缩影
int i = indexOf(obj);
// 索引如果满足条件
if (i >= 0) {
// 将索引为i的元素从数组中移除
removeElementAt(i);
return true;
}
return false;
}
// 操作数组删除元素
public synchronized void removeElementAt(int index) {
// 修改次数增加
modCount++;
// 是否合法
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
// index后面的元素个数
int j = elementCount - index - 1;
if (j > 0) {
// 往前面移动一位(复制,覆盖)
System.arraycopy(elementData, index + 1, elementData, index, j);
}
// 更新个数
elementCount--;
// 原来最后一个元素的位置置空
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;
// 如果移动个数大于0
if (numMoved > 0)
// 后面的元素往前面移动一位,赋值,覆盖
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 最后一个元素置空
elementData[--elementCount] = null; // Let gc do its work
// 返回旧的元素
return oldValue;
}
4.3 修改
下面两个set函数都是,修改索引为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;
}
4.4 查询
public synchronized E get(int index) {
// 合法判断
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
// 返回数组的元素
return elementData(index);
}
获取第一个元素:
public synchronized E firstElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(0);
}
获取最后一个元素:
public synchronized E lastElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(elementCount - 1);
}
E elementData(int index) {
return (E) elementData[index];
}
4.5 其他常用函数
将元素拷贝进数组中:
public synchronized void copyInto(Object[] anArray) {
System.arraycopy(elementData, 0, anArray, 0, elementCount);
}
手动缩容,其实就是将里面的数组复制到一个更小的数组,更新数组引用即可。
public synchronized void trimToSize() {
// 修改次数增加
modCount++;
// 获取数组的长度
int oldCapacity = elementData.length;
// 数组长度大于真实的容量,说明有可以缩容的空间
if (elementCount < oldCapacity) {
// 复制到新的数组
elementData = Arrays.copyOf(elementData, elementCount);
}
}
保证容量的函数,其实相当于手动扩容,参数是所需要的最小的容量,里面调用的ensureCapacityHelper()
在上面add()
函数解析的时候已经说过了,不再解析。
public synchronized void ensureCapacity(int minCapacity) {
if (minCapacity > 0) {
modCount++;
ensureCapacityHelper(minCapacity);
}
}
手动将元素个数设置为newSize,分为两种情况,一种是新的size比现在的size还要大,就是想到那个于指定容量扩容。另外一种是相当于缩容,但是这个缩容比较特殊,总的容量实际上没有变化,只是将里面多余的元素置为null。
public synchronized void setSize(int newSize) {
modCount++;
if (newSize > elementCount) {
// 扩容
ensureCapacityHelper(newSize);
} else {
for (int i = newSize ; i < elementCount ; i++) {
// 将超出个数的元素设置为null
elementData[i] = null;
}
}
elementCount = newSize;
}
获取容量:
public synchronized int capacity() {
return elementData.length;
}
获取里面真实的元素个数:
public synchronized int size() {
return elementCount;
}
容器是不是为空:
public synchronized boolean isEmpty() {
return elementCount == 0;
}
返回枚举类型的元素迭代器,这是一个有意思的方法,相当于用枚举包装了当前的元素,Enumeration
是一个接口,这个接口有两个方法,一个是hasMoreElements()
,表示是否有下一个元素。一个是nextElement()
,获取下一个元素。
public Enumeration<E> elements() {
return new Enumeration<E>() {
int count = 0;
// 重写方法,是否有下一个元素
public boolean hasMoreElements() {
return count < elementCount;
}
public E nextElement() {
// 同步
synchronized (Vector.this) {
if (count < elementCount) {
// 返回下一个元素
return elementData(count++);
}
}
throw new NoSuchElementException("Vector Enumeration");
}
};
}
是否包含某一个元素,其实里面是获取对象的索引,如果索引大于等于0,证明元素在里面,否则元素不在里面。
public boolean contains(Object o) {
return indexOf(o, 0) >= 0;
}
返回元素的索引,分为两种情况,一种是元素是null的情况,不能使用equals()
方法,另一种是非null,可以直接使用equals()
方法。
public int indexOf(Object o) {
return indexOf(o, 0);
}
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) {
return lastIndexOf(o, elementCount-1);
}
public synchronized int lastIndexOf(Object o, int index) {
if (index >= elementCount)
throw new IndexOutOfBoundsException(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;
}
拷贝元素,数组里面的元素其实拷贝的只是引用,如果修改新的Vector
里面的对象的属性,旧的也会被修改。
public synchronized Object clone() {
try {
@SuppressWarnings("unchecked")
Vector<E> v = (Vector<E>) super.clone();
v.elementData = Arrays.copyOf(elementData, elementCount);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
比如:
class Student {
public int age;
public String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
public class Test {
public static void main(String[] args) {
Vector<Student> vector1 = new Vector<>();
vector1.add(new Student(1,"sam"));
Vector<Student> vector2 = (Vector<Student>) vector1.clone();
vector2.get(0).name = "change name";
System.out.println(vector2);
System.out.println(vector1);
}
输出结果如下,可以看出其实两个集合里面的Student还是同一个对象。
[Student{age=1, name='change name', score=0}]
[Student{age=1, name='change name', score=0}]
将元素转换成为数组,原理也是一样,都是浅拷贝,拷贝的都是元素对象的引用。
public synchronized Object[] toArray() {
return Arrays.copyOf(elementData, elementCount);
}
指定数组类型的拷贝:
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);
if (a.length > elementCount)
a[elementCount] = null;
return a;
}
截取出某一段的元素集合,调用的是父类的方法
public synchronized List<E> subList(int fromIndex, int toIndex) {
return Collections.synchronizedList(super.subList(fromIndex, toIndex),
this);
}
移除某一段索引的元素,我们可以看到首先是将后面的元素往前面移动,覆盖掉前面的元素,然后将后面的元素坑位赋值为null。
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;
}
获取指定位置的迭代器:
Vector
和ArrayList
基本差不多,都是定义了三个迭代器:
Itr
:实现接口Iterator
,有简单的功能:判断是否有下一个元素,获取下一个元素,删除,遍历剩下的元素ListItr
:继承Itr
,实现ListIterator
,在Itr
的基础上有了更加丰富的功能。VectorSpliterator
:可以分割的迭代器,主要是为了分割以适应并行处理。和ArrayList
里面的ArrayListSpliterator
类似。
// 返回指定index位置的ListIterator
public synchronized ListIterator<E> listIterator(int index) {
if (index < 0 || index > elementCount)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
// 返回开始位置的ListIterator
public synchronized ListIterator<E> listIterator() {
return new ListItr(0);
}
// 返回Itr
public synchronized Iterator<E> iterator() {
return new Itr();
}
// 返回VectorSpliterator
public Spliterator<E> spliterator() {
return new VectorSpliterator<>(this, null, 0, -1, 0);
}
4.6 Lambda表达式相关的方法
- forEach:遍历处理
- removeIf:按照条件移除元素
- replaceAll:移除元素
- sort:排序
基本都是将行为当成参数传递到函数中进行处理,里面值得一提的是removeIf()
,里面是将过滤器传递进去,在里面我们可以看到使用了BitSet
,这个东西来保存了需要移除的元素的下标,统计完成之后,后面再取出来进行移除操作。那么这个BitSet
是什么呢???
Java集合【9】-- Vector源码解析的更多相关文章
- Java集合---Array类源码解析
Java集合---Array类源码解析 ---转自:牛奶.不加糖 一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Prim ...
- Java集合:LinkedList源码解析
Java集合---LinkedList源码解析 一.源码解析1. LinkedList类定义2.LinkedList数据结构原理3.私有属性4.构造方法5.元素添加add()及原理6.删除数据re ...
- Java集合之Vector源码分析
概述 Vector与ArrayLIst类似, 内部同样维护一个数组, Vector是线程安全的. 方法与ArrayList大体一致, 只是加上 synchronized 关键字, 保证线程安全, 下面 ...
- 【Java集合】HashSet源码解析以及HashSet与HashMap的区别
HashSet 前言 HashSet是一个不可重复且元素无序的集合.内部使用HashMap实现. 我们可以从HashSet源码的类注释中获取到如下信息: 底层基于HashMap实现,所以迭代过程中不能 ...
- Java集合---Arrays类源码解析
一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类型: ...
- java集合之HashMap源码解析
Map是java中的一种数据结构,围绕着Map接口,有一系列的实现类如Hashtable.HashMap.LinkedHashMap和TreeMap.而其中HashMap和Hashtable我们平常使 ...
- Java集合之LinkedList源码解析
LinkedList简介 LinkedList基于双向链表,即FIFO(先进先出)和FILO(先进后出)都是支持的,这样它可以作为堆栈,队列使用 继承AbstractSequentialList,该类 ...
- java集合之List源码解析
List是java重要的数据结构之一,我们经常接触到的有ArrayList.Vector和LinkedList三种,他们都继承来自java.util.Collection接口,类图如下 接下来,我们对 ...
- Vector源码解析
概要 学完ArrayList和LinkedList之后,我们接着学习Vector.学习方式还是和之前一样,先对Vector有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它.第1部分 Vec ...
- 死磕 java集合之DelayQueue源码分析
问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...
随机推荐
- [BZOJ 2287/POJ openjudge1009/Luogu P4141] 消失之物
题面: 传送门:http://poj.openjudge.cn/practice/1009/ Solution DP+DP 首先,我们可以很轻松地求出所有物品都要的情况下的选择方案数,一个简单的满背包 ...
- 公钥、私钥、SSL/TLS、会话密钥、DES
一,公钥私钥 1,公钥和私钥成对出现 2,公开的密钥叫公钥,只有自己知道的叫私钥 3,用公钥加密的数据只有对应的私钥可以解密 4,用私钥加密的数据只有对应的公钥可以解密 5,如果可以用公钥解密,则必然 ...
- CSP-S 2020模拟训练题1-信友队T1 四平方和
题意简述 \(n\)是正整数,其四个最小的因子分别为\(d_1,d_2,d_3,d_4\). 求对于所有的\(n \le m\)满足 \[d_1^2+d_2^2+d_3^2+d_4^2=n \] 的\ ...
- C# 全局唯一标识符 (GUID)
一 C# 全局唯一标识符 (GUID) Represents a globally unique identifier (GUID). To browse the .NET Framework so ...
- MFC的Static控件文字重叠问题
写个普通的MFC桌面程序,为了美化界面用了界面库,然后界面上的静态文本就出现了重叠的效果,就像下面这样: 但是窗口被刷新一下就好了,比如被遮挡后恢复的时候.在程序中可以手动加入以下代码: void C ...
- 消息队列--ActiveMQ集群部署
一.activeMQ主要的部署方式? 1,默认的单机部署(kahadb) activeMQ默认的存储单机模式,如果配置文件不做修改,则默认使用此模式.以本地的kahadb文件的方式进行存储,性能完全依 ...
- python_摘要_加密
import hashlib def get_md5(username,password): md5 = hashlib.md5(username.encode('utf-8')) # 加盐 md5. ...
- WIN10下安装python3.7.2出现“尝试创建C:\Users\XX\AppData\Roaming\Microsoft\Installer时出错”
WIN10下安装python3.7.2出现"尝试创建C:\Users\XX\AppData\Roaming\Microsoft\Installer时出错" 1.右键点击安装包以管理 ...
- 利用s3-test进行ceph的接口兼容性测试
前言 ceph的rgw能够提供一个兼容性的s3的接口,既然是兼容性,当然不可能是所有接口都会兼容,那么我们需要有一个工具来进行接口的验证以及测试,这个在其他测试工具里面有类似的posix接口验证工具, ...
- 关于JPA一对一,一对多(多对一),多对多的详解
一.@OneToOne关系映射 JPA使用@OneToOne来标注一对一的关系. 实体 People :用户. 实体 Address:家庭住址. People 和 Address 是一对一的关系. 这 ...