Vector总结及部分底层源码分析

Vector总结及部分底层源码分析

1. Vector继承的抽象类和实现的接口

  • Vector类实现的接口
    1. List接口:里面定义了List集合的基本接口,Vector进行了实现
    2. RandomAccess接口
    3. Cloneable接口
    4. Serializable:标记该类支持序列化
  • Vector继承了AbstractList抽象类

2. Vector底层的数据结构

  • Vector底层是基于数组实现的

    1. 特点:

      1. 数组在内存中是一个内存地址连续的,使用存储的元素也有序的,固定内存大小的内存空间,并且Vector可以存放重复的元素。允许存储 一个null或者多个null。
    2. 优点:

      1. 因为Vector在可能发送线程安全的方法上都添加了 synchronized 关键字进行了同步设置,所以Vector是线程安全的集合,在多线程的情况下可以直接使用。
      2. 因为使用了同步锁的原因在查询效率上不如ArrayList快,但是相比于链表而言查询效率还是快。
    3. 缺点:

      1. 当向Vector集合中存储的新的元素时,长度已经不够时,就会触发Vector底层的扩容机制。其实底层就是数组扩容。因为数组扩容需要把旧的数组元素复制到新的数组中,所以在数据量特别大的时候,扩容的效率不高
      2. 在指定位置添加和修改元素效率不高(尾部除外)。
        • 例如:在数组的尾部以外的地方插入一个元素,那么为例保证整个数组中所有元素的连续存储性,就必须向将从该位置到尾部的所有元素进行后移一位,腾出一个空的位置,再把新的元素进行插入。删除也是,删除了指定位置的之后需要把从该元素到尾部的所有元素向前移动一个位置。
      3. 虽然使用了 synchronized 保证线程安全,但是会损失掉一部分性能,所以目前已经基本弃用该集合。

3. Vector适用的场景

  1. 在多线程情况下,频繁对数据进行查找与修改的场景
  2. 但是为例将性能进行提升,可以创建线程安全ArrayList进行代替,使用Vector集合做的事情,使用ArrayList都可以做到,所以Vector基本已经不使用。
// 创建多线程环境下,线程安全的ArrayList集合,使用Collections集合工具类中的 synchronizedList() 方法
List<Object> list=Collections.synchronizedList(new ArrayList<Object>());

4. Vector底层源码分析

1. 四个构造函数

1. 无参构造函数

/**
 * 默认无参构造函数
 */
public Vector() {
    // 调用只有一个参数的构造函数,给参数一个定值10
    this(10);
}

2. 有一个参数的构造函数

/**
 * 有一个参数的构造函数
 * @param   initialCapacity  创建对象时如果使用默认的无参构造函数,默认大小为10
 */
public Vector(int initialCapacity) {
    // 调用有两个参数的构造函数
    this(initialCapacity, 0);
}

3. 有两个参数的构造函数

/**
 * 有两个参数的构造函数,第一个参数代表数组的初始化长度,第二个是数组需要进行扩容的的增量值
 * @param   initialCapacity     	 集合的初始化长度大小,如果创建集合时采用无参构造器,则默认为10
 * @param   capacityIncrement   	 Vector需要自动扩容时增加的容量值,不传递时默认为0		
 * @throws  IllegalArgumentException 发生异常时抛出异常
 */
public Vector(int initialCapacity, int capacityIncrement) {
    // 调用父类的构造函数
    super();
    // 判断传递进来的默认初始化集合长度大小的值是否小于0,如果小于则抛出异常
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    // 创建一个Object类型的数组,长度为传递进来的 initialCapacity ,用 elementData 接收
    this.elementData = new Object[initialCapacity];
    //  Vector需要自动扩容时增加的容量值,不传递时默认为0,在后面的扩容方法中会体现出来	
    this.capacityIncrement = capacityIncrement;
}
  • 变量说明:
// 存放集合的元素值,类型Object类型,可以存储任意类型的数据
protected Object[] elementData;

// 数组需要进行扩容的的增量值,只有在调用有两个参数的构造函数是才可以改变其大小
// 如果采用其他两个构造函数创建集合,则默认是0
protected int capacityIncrement;
  • Vector父类的构造函数
/**
 * Vector父类的构造函数,不做任何事情
 */
protected AbstractList() {}

4. 有参构造(传入一个Collection类型的集合)

/**
 * 在初始化的时候直接将一个集合传入,可以把传入集合的元素全部复制到创建的新集合中
 * @param c 					传入的集合
 * @throws NullPointerException   当传入的集合为空时,会抛出异常
 */
public Vector(Collection<? extends E> c) {
    elementData = c.toArray();
    elementCount = elementData.length;
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}

2. 添加一个元素的过程以及扩容机制

1. 调用对应类型的装箱方法

​ 在每次添加数据时,如果数据是基本数据类型,会先将基本数据类型进行装箱操作,把基本数据类型转换成对应的包装类型(引用数据类型)

// 例如:集合中存放Integer数据类型,在进行add操作时,会先进行装箱操作

/**
 * 将基本数据类转换为引用数据类型
 * @param  i 	传入的参数为一个基本型数据类型
 * @return 		返回的参数是一个基本数据类型的包装类(引用数据类型)
 */
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

2. 调用 add() 方法

/**
 * 添加元素方法,成功返回true,失败会抛出异常
 * @param e 			需要进行添加的元素值
 * @return <tt>true</tt> 添加成功返回true,添加过程中如果失败会抛出异常
 */
public synchronized boolean add(E e) {
    // 记录集合被修改的次数
    modCount++;
    // 判断是否需要扩容
    ensureCapacityHelper(elementCount + 1);
    // 将传递进来的元素添加到数组的末尾
    elementData[elementCount++] = e;
    return true;
}
  • 变量说明:
// 记录集合被修改的次数
protected transient int modCount = 0;

// 记录当前的vector集合中存储的实际元素个数
protected int elementCount;

3. 判断是否需要扩容 ensureCapacityHelper()

/**
 * 按照传递进来的具体值,判断是否需要进行扩容操作
 * @param minCapacity 在原来集合实际元素个数的数目上加1,代表添加元素之后数组的实际长度
 */
private void ensureCapacityHelper(int minCapacity) {
    // 当实际需要的数组长度 - 数组实际的长度大于0时,表示需要进行扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity); 	// 扩容的核心方法
}

4. 核心扩容方法 grow()

/**
 * 具体的扩容方法
 * @param minCapacity 经过判断之后存储数据的数组需要的容量大小
 */
private void grow(int minCapacity) {
    // 获取未扩容之前的数组长度
    int oldCapacity = elementData.length;
    // 判断 capacityIncrement 增量值是否大于0,如果大于0每次扩容大小是增量值,否则按原来长度的2倍扩容
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
    
    // 当扩容之后的长度小于实际需要的长度时,将实际需要的长度作为扩容之后的长度
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    
    // 当扩容之后的长度大于限定的最大长度时
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    
    // 使用数组的 copyOf 对数组进行扩容
    elementData = Arrays.copyOf(elementData, newCapacity);
}
  • 变量说明:
// 整数类型的最大值减8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

5. 当数组要求的扩容的长度超过限定的最大值时

/**
 * 具体的扩容方法
 * @param minCapacity 经过判断之后存储数据的数组需要的容量大小
 */
private static int hugeCapacity(int minCapacity) {
    // 判断是否是内存溢出
    if (minCapacity < 0) 
        throw new OutOfMemoryError();
    
    // 判断实际需要的数组长度是否大于设置的最大值,是则返回整数类型的最大值,否则返回设置的最大值
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
posted on 2021-07-03 22:31  秃头靓仔-001  阅读(253)  评论(0编辑  收藏  举报