栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java面试专项---集合专题二(Vector)---从源码的角度深入了解Vector集合

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java面试专项---集合专题二(Vector)---从源码的角度深入了解Vector集合

目录

1.Vector类的成员变量的含义

2.使用不同构造方法创建Vector实例对应的不同扩容行为

3.为什么Vector是线程安全的,而ArrayList、LinkedList是线程不安全的?


首先贴出Vector源码(源码出自jdk1.8.0_191)

1.Vector类的成员变量的含义

elementData:用于存储数据的数组,Vector集合中包含的数据个数就是该数组的长度capacityIncrement:每次Vector数组容量不够时,一次扩容的容量。而且仅当capacityIncrement为正数的时候,我们自定义的每次扩容数量才生效。若capacityIncrement不指定(不指定默认为0),指定为0,甚至指定为负数,那么我们自定义的每次扩容数量不会生效,换言之每当容量不够时,扩容后的容量是原容量的2倍(每次扩容100%)。如果我说的不形象那么请继续往下看,后面会以简单的例子详细解释这个结论。

elementCount:该变量的含义很抽象,但是该变量的值很有实际意义。我们看注释解释的:索引0~索引elementCount-1的位置是实际存储数据的位置。其实该索引范围也是对集合操作的合理索引范围,超过该索引范围的任何操作(增删改查)都会抛出异常或者错误。为什么这样说呢?请看下面一个例子,不过在看这个例子之前,我们先来看一下Vector类的构造方法

public Vector() {
        this(10);
    }
public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
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(Collection c) {
        elementData = c.toArray();
        elementCount = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
    }

可以看出Vector类给出了4个构造方法,但实际上只有两个构造方法。无论是调用无参数构造方法

public Vector()还是调用包含一个形参的构造方法public Vector(int initialCapacity),最后都是调用了public Vector(int initialCapacity, int capacityIncrement)构造方法。不同的是,调用无参数构造方法public Vector()最终会指定elementData数组容量为10;capacityIncrement=0,Vector容量不够的时候,只会2倍2倍地扩容。调用包含一个形参的构造方法public Vector(int initialCapacity)允许我们在初始化Vector的时候规定它的初始容量,Vector容量不够的时候,也只会2倍2倍地扩容。调用public Vector(int initialCapacity, int capacityIncrement) 构造方法不仅允许我们在初始化Vector的时候规定它的初始容量。Vector容量不够的时候,我们还可以规定每次增加几个容量,而不像前面的两个构造方法那么死板的2倍扩容方式。

看完了构造方法,我们来看一下之前提到的例子

  Vector vector=new Vector<>(5);
        for(int i=1;i<=3;i++){
            vector.add(i);
            System.out.print("第"+i+"次添加元素时,vector.size()="+vector.size()+"t");
            System.out.println("vector.capacity()="+vector.capacity());
        }
        System.out.println(vector.get(4));
        vector.add(4,4);

 首先我们明确size()方法返回是Vector集合中有几个元素,而capacity()方法返回的是当前Vector集合的容量(即当前集合最多能装多少个元素)

我们来看控制台异常信息

 Vector vector=new Vector<>(5)这个语句指定了elementData数组容量为5,那么我们为什么还是不能在索引为4的位置进行相关操作呢?我们来看一下

原来是索引值不在0~elementCount-1的范围之内 

 所以说正如官方对成员变量elementCount注释解释的那样:索引0~索引elementCount-1的位置是实际存储数据的位置。该索引范围也是对集合操作的合理索引范围,超过该索引范围的任何操作(增删改查)都会抛出异常或者错误。

2.使用不同构造方法创建Vector实例对应的不同扩容行为

我们再看下面这个例子

public static void main(String[] args) {
        Vector vector=new Vector<>();
        for(int i=1;i<=11;i++){
            vector.add(i);
            System.out.print("第"+i+"次添加元素时,vector.size()="+vector.size()+"t");
            System.out.println("vector.capacity()="+vector.capacity());
        }
    }

 可见,调用无参数构造方法public Vector(),初试容量为10,Vector容量不够的时候,2倍2倍地扩容。

为什么是2倍2倍地扩容呢?

第一步:调用无参数构造方法,该方法调用有参构造方法public Vector(int initialCapacity)
参入参数10
public Vector() {
        this(10);
    }


第二步:该方法内部调用public Vector(int initialCapacity, int capacityIncrement)构造方法
传入参数10、0
public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
第三步:该方法将elementData指定为容量为10的数组,扩容因子指定为0
public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
第四步:调用add方法,该方法内部调用ensureCapacityHelper方法检查是否有溢出风险
public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
第五步:如果minCapacity > elementData.length 即有溢出风险则调用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;
        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);
    }

我们重点来看一下grow函数,首先明确oldCapacity是当前集合中已有元素的数量。

int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);

如果capacityIncrement > 0,那么扩容后elementData数组容量newCapacity=oldCapacity+capacityIncrement.

若capacityIncrement <=0,那么扩容后elementData数组容量newCapacity=oldCapacity*2

下面我们再来看一个例子

 public static void main(String[] args) {
        Vector vector=new Vector<>(5,1);
        for(int i=1;i<=11;i++){
            vector.add(i);
            System.out.print("第"+i+"次添加元素时,vector.size()="+vector.size()+"t");
            System.out.println("vector.capacity()="+vector.capacity());
        }
    }

我们指定Vector初始容量为5,capacityIncrement=1看看控制台不是不我们之前分析的那样

果然。我们初始化时指定扩容因子 capacityIncrement=1,每次容量不够的时候都只增加1个容量。

如果我们指定capacityIncrement为负数呢?继续看下面的例子

   Vector vector=new Vector<>(7,-3);
        for(int i=1;i<=11;i++){
            vector.add(i);
            System.out.print("第"+i+"次添加元素时,vector.size()="+vector.size()+"t");
            System.out.println("vector.capacity()="+vector.capacity());
        }
    }

 与我们之前分析的结论一致

如果我们使用构造方法public Vector(Collection c)创建Vector后,初始容量与扩容行为是不是与之前有所不同呢?我们直接看例子

public class OutOfMermory {
    public static void main(String[] args) {
        Vector vector=new Vector<>(7,-3);
        for(int i=1;i<=11;i++){
            vector.add(i);
//            System.out.print("第"+i+"次添加元素时,vector.size()="+vector.size()+"t");
//            System.out.println("vector.capacity()="+vector.capacity());
        }
        Vectorvector1=new Vector<>(vector);
        System.out.println("vector1.size()="+vector1.size()+"t"+"vector1.capacity()="+vector1.capacity());
        vector1.add(12);
        System.out.println("vector1.size()="+vector1.size()+"t"+"vector1.capacity()="+vector1.capacity());
    }
}

可见在这种情况下,Vector初始容量为传入集合中包含的元素的个数,之后容量不够的时候,也是2倍2倍地扩容

3.为什么Vector是线程安全的,而ArrayList、LinkedList是线程不安全的?

先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。 List接口下面有两个实现,一个是ArrayList,另外一个是vector。 从源码的角度来看,因为Vector的方法前加了,synchronized 关键字,也就是同步的意思,因此Vector是线程安全的,而ArrayList类与LinkedList中的方法没有加锁.虽然线程不安全但是多线程访问该类效率明显高于Vector。 以向集合中添加元素为例,首先看一下ArrayList与Vector类中的add()方法源码

//arrayList类add方法源码
 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
//Vector类add方法源码
public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成: 

在elementData[size]数组位置添加元素
size++数组容量加1                                                                                                           

在单线程运行的情况下,如果 size = 0,添加一个元素后,此元素在位置 0,而且 Size=1; 
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意,我们假设的是添加一个元素是要两个步骤,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。 那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/826385.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号