18:ArrayList 集合 JDK1.7 及 JDK1.8 版本的 扩容机制 、Vector 扩容机制 解读 及它们之间的区别总结:

目录

说明:

ArrayList JDK1.7 版本底层扩容机制 实现源码解读:

ps:JDK1.7的ArrayList 的 ArrayList(Collection c) 构造器 源码解读:

ArrayList JDK1.8 版本底层扩容机制 实现源码解读:

ps:JDK1.8的ArrayList 的 ArrayList(Collection c) 构造器 源码解读:

Vector :

说明:

Vector 有且ArrayList没有的API基本使用:

 Vector 扩容机制 实现源码解读:

JDK1.7 、JDK1.8 的ArrayList 和 Vector 区别总结:

ArrayList 与 Vector 的相同点:

ArrayList 与 Vector 的不同点:


说明:

  1. ArrayList 、Vectory 的底层是通过 Object的 数组 来 实现的。
  2. 数组:在Java中是一种引用类型。在数据结构中,数组就是一种 紧密结构 之一的 一种数据结构!
  3. ArrayList 、Vector 底层的算法,就是 通过数组的 copy 来实现的。巧妙的结合了 很多编程思想在里面。
  4. 数组的表现形式:如图所示: 

数组的优缺点:

数组的优点:

    (1)相对于变量来说,数组可以存放大量的数据。
    (2)按照索引查询元素 速度快。
    (3)数组存储是:是有序的,可重复的。
    (4)按照索引遍历数组方便。数组定义简单。并且访问也方便。可以随机访问其中的元素。

数据的缺点:

    (1)一旦指定了长度,那么数组长度就被确定了,不可以更改,不适合动态存储。
    (2)只能存储一种数据类型的数据。
    (3)增加、删除元素效率慢。
    (4)官方 只提供了一个 length 属性能够获取数组中应该存储多少个元素,
        而 数组中实际元素的数量 是没有办法获取的,没有提供对应的方法 或者是属性 来获取。
    (5)数组的空间必须是连续的。造成数组在内存中分配空间时 必须找到一块连续的内存空间。
           所以说数组不可能定义得太大。因为内存中不可能有那么多 大的连续的内存空间来供数组使用。
             解决这个问题的办法 就是使用链表。

ArrayList JDK1.7 版本底层扩容机制 实现源码解读:

(源码和StringBuilder 以及 StringBuffer 及其相似)

(以JDK1.7.0_21为例:)

(JDK1.7.0_79 这是JDK1.7的最后一个小版本,源码和JDK1.8差不多,所以直接读 1.8)

无参构造器创建ArrayList对象:


ArrayList中两个重要的属性:

跟进:

step 1:

step 2:

调用add方法,查看ArrayList集合中的 扩容机制:此时 ArrayList 中 elementDate 数组的初始化长度 为 10:

跟进:

step  1 :

step 2:

 step 3:


再次添加执行触发数组的扩容:

step 1:

step 2

step 3:

step 4:

step 5:

step 6: (debug时不会进来)

step 7 :一直 return,return到没有执行的代码位置:

step 8:一路 结束方法弹栈消失 , 回到 add方法中:

 step 9: 添加元素完毕,add方法弹栈。回到调用 add 方法的地方。


ps:JDK1.7的ArrayList 的 ArrayList(Collection<? extends E> c) 构造器 源码解读:

public ArrayList(Collection<? extends E> c) {
       //step 1: 把入参集合转成 Object数组,让当前 初始化的集合对象的 elementData数组指向
        elementData = c.toArray();
        // step 2: 拿到 入参集合的长度 
        size = elementData.length;

        // c.toArray might (incorrectly) not return Object[] (see 6260652)

        // step 3: 
            //     如果 elementData数组的类型不是Object数组的类型 
            // 就进行 数组的拷贝,再让elementData指向 
           
        if (elementData.getClass() != Object[].class)
            // 发生这个 数组拷贝的动作 除非调用的 toArray 的返回值不是 Object 类型的。
            // 继承ArrayList 或父类、或实现父接口 把它的 toArray重写就 可以执行这个代码。
            // 比如看以下 测试 代码:👇
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

test:

private static class TestList<E> extends ArrayList<E>{
        @Override
        public String[] toArray() {
            return this.toArray(new String[0]);
        }
    }
        TestList<String> testList = new TestList<>();
        testList.add("第一");
        testList.add("第二");
        testList.add("第三");
        testList.add("第四");
        
        // 通过这种方式创建集合,如果正在实例化的集合 给定的 泛型数据类型 
        // 不是传入的集合 给定的 泛型类型的本类型的话。
        // 是它的父类的话,那么在实例化集合的时候,
        // 调用构造器的代码 new ArrayList<CharSequence> 泛型 也要指定才行!不然
        //   public ArrayList(Collection<? extends E> c) 的 Collection<? extends E> 
        //  的 泛型上限通配,不好检查是否编译通过(也可以理解为这里的自动推断类型失效)
        ArrayList<CharSequence> arrayList = new ArrayList<CharSequence>(testList);
        System.out.println("arrayList = " + arrayList);

debug 构造器  --->


ArrayList JDK1.8 版本底层扩容机制 实现源码解读:

说明:JDK1.8 版本后的 源码和 JDK1.7 的源码只有细微的差别。

通过ArrayList 无参构造器 看底层扩容机制:

step 1:JDK1.8版本后 源码中几个关键的属性:

 跟进:

step 1:

使用add方法,跟进:

step 1:

step 2:

step 3:

注:calculateCapatity只会在第一次添加元素的时候进入if分支。初始化长度为 10

step 4:

进入到 ensureExplicitCapacity(确保明确的容量方法)

step 5:

step 6:

step 7:

System.arraycopy 方法debug时不会进入:

step 8:一路return 到grow方法

step 9:grow执行结束弹栈消失,一路的方法弹栈消失,回到 add 方法

step 10:add 方法结束,回到调用 add 方法的地方。

step 11:,这和JDK1.7的机制是一样的。所以不再跟进。

在没有调用ArrayList的传入一个初始化容量的构造器和 当添加元素时 手动去 指定最小容量的时候,调用 ArrayList的无参构造器的初始化的方式 调用add方法时当数组容量不够时 扩容递增顺序就是: 10 -> 15 -> 22 - > 33 -> 49 -> 73 -> 109 -> 163 -> 244 -> 366 -> ……,每次新数组的长度扩容的倍数,都是  旧数组长度的 1.5 倍


ps:JDK1.8的ArrayList 的 ArrayList(Collection<? extends E> c) 构造器 源码解读:

public ArrayList(Collection<? extends E> c) {
        //step 1: 把当前传入建立的集合转成 Object 数组
        Object[] a = c.toArray();
        //step 2: 把数组的长度赋值给 当前正在实例化的集合对象。并且 size 不等于 0
        if ((size = a.length) != 0) {
            // step 2.1:如果传入进当前正在实例化集合的集合的运行时类型是 ArrayList 类型
            if (c.getClass() == ArrayList.class) {
                // step 2.1.1: 就让当前实例化的 集合对象的 elementData数组 指向 
                // 传入进来的ArrayList集合 的elementData 数组
                elementData = a;
            } else {
                // step 2.2: 如果不是,则进行数组的拷贝,再让elementData 指向
                // 在 Arrays.copyOf方法中创建的新数组!
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.
            // step 3: 如果传入进来的集合是没有元素的,
            // 即传入进来的集合的elementData的长度为0时,则当前实例化的集合对象的 
            // elementData 数组 指向 空数组,(长度为0)
            elementData = EMPTY_ELEMENTDATA;
        }
    }

 


Vector :

说明:

Vector 的使用和 ArrayList 的使用是一样的,且底层也是通过数组来实现的。只是相同的API  由ArrayList 实现的 换成了 Vector 来实现了而已。相同功能的方法名不一样。当然,Vector 也有自己有 且 ArrayList 没有的API。相同的就不做赘述了。不同的使用一下。不过 Vector 在我实际开发中,没用到过。但我被面试问到过。

Vector 有且ArrayList没有的API基本使用:

public static void vectorExclusivelyMethodUse() {
        Vector<String> vector = new Vector<>();
        vector.add("埃及胡夫金字塔");
        vector.add("巴比伦空中花园");
        vector.add("阿尔忒弥斯神庙");
        vector.add("奥林匹亚宙斯神像");
        vector.add("摩索拉斯陵墓");
        vector.add("罗德岛太阳神巨像");
        vector.add("亚历山大灯塔");
        vector.add("秦陵兵马俑");
        //void copyInto(Object[] anArray)
        //将此向量的组件复制到指定的数组中
        // 如果数组的类型不是 Vector 的泛型给定的数组类型或父类类型,
        // 则抛出 java.lang.ArrayStoreException
        CharSequence[] str = new String[vector.size()];
        vector.copyInto(str);
        System.out.println(Arrays.toString(str));

        // (和ArrayList 的  public E get(int index) 方法功能是一样的)
        // E elementAt(int index)

        //返回指定索引处的组件。
        //Enumeration<E> elements()
        //返回此向量的组件的枚举。
        // (此枚举非彼枚举,只是接口名的意思是:枚举)
        // 可用这种方法 遍历 Vector
        Enumeration<String> elements = vector.elements();
        while (elements.hasMoreElements()) {
            String element = elements.nextElement();
            System.out.println("element = " + element);
        }


        //void insertElementAt(E obj, int index)
        // 此方法和 ArrayList 的
        // public void add(int index, E element) 方法功能是一样的
        //在指定的index插入指定对象作为该向量中的一个 index 。

        vector.insertElementAt("万里长城", vector.size());
        System.out.println("vector = " + vector);


        //E lastElement()
        // 等同于 ArrayList的get方法的
        // list.get(list.size() -1) 方式调用
        //返回向量的最后一个组件

        // void setElementAt(E obj, int index)
        //  E set(int index, E element) , index 不能大于 最大索引。
        //  否则 抛出 java.lang.ArrayIndexOutOfBoundsException:
        //  用指定的元素替换此Vector中指定位置的元素。
        // 和 ArrayList的 public E set(int index, E element) 功能是一样的
        //设置在指定的组件 index此向量的要指定的对象。
        vector.setElementAt("吴哥窟", vector.size() - 2);
        System.out.println("vector = " + vector);
        vector.set(vector.size() - 1, "罗马斗兽场");
        System.out.println("vector = " + vector);

        //void setSize(int newSize)
        //设置此向量的大小。(如果长度小于 Vector的 size,长度后的元素将删除)
        vector.setSize(6);
        System.out.println("vector = " + vector);

        // int capacity()
        //返回此向量的当前容量。
        System.out.println("vector.capacity() = " + vector.capacity());
    }

result:

 Vector 扩容机制 实现源码解读:

使用无参构造器跟进:

Vector中三个重要的属性:

step 1:

step 2:

step 3:

 实例化完成:

往Vector集合中添加 11个元素,看第11个元素的扩容机制:

step 1:

step 2

step 3:

 step 4:方法弹栈消失。回到 add 方法中:

 Vector 其他两个 构造器:

public Vector(Collection<? extends E> c) {
        Object[] a = c.toArray();
        elementCount = a.length;
        // 1: 如果是 ArrayList 集合
        if (c.getClass() == ArrayList.class) {
            // 2: 则把 ArrayList集合的 elementData 赋值给 当前 Vectory实例的elementData指向。
            elementData = a;
        } else {
              // 3: 如果不是则 先数组拷贝。再赋值指向。
            elementData = Arrays.copyOf(a, elementCount, Object[].class);
        }
    }

 

public Vector(int initialCapacity, int capacityIncrement) {
        super();
        // 如果用户给的 初始化长度是 小于 0 
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        // 初始化的elementData的数组的长度为 用户指定的长度。
        this.elementData = new Object[initialCapacity];
        // 当前Vector实例的 容量增量是用户给的为准。
        this.capacityIncrement = capacityIncrement;
    }

// 如果使用此构造器实例化 Vector对象时。当集合中的元素的数量超过。elementData数组的容量时。则
// Vector 底层的扩容的新数组的长度每次都是  : 旧数组的容量 + 用户给定的容量增量。

 


JDK1.7 、JDK1.8 的ArrayList 和 Vector 区别总结:

  1. JDK1.7的ArrayList的 扩容机制源码。JDK1.7的源码是 ,在实例化这个 ArrayList 集合对象的时候。不管 调用者初始化完ArrayList对象后 继不继续 使不使用,在初始化对象的时候,都会创建一个长度为 10 的 Object 的数组(elementData),当调用者继续使用时 添加元素 到集合中的容量 达到 elementData的当前最大容量时。 就会进行 对 elementData 数组的 扩容。扩容的新数组的长度 为 就旧数组长度的 1.5 倍。
  2. JDK1.8的ArrayList的 扩容机制源码。JDK1.8的源码是,在实例化这个 ArrayList 集合对象的时候。不会去创建Object 的数组。底层是长度为0的Object 数组。只有在调用者第一次往集合中添加元素的时候,才会对 elementData 数组扩容 至长度为 10 的新数组。当调用者 添加的元素  到集合中的容量 达到 elementData的当前最大容量时。 就会进行 对 elementData 数组的 扩容。扩容的新数组的长度 为 就旧数组长度的 1.5 倍。(这和1.7的扩容机制是一样的)
  3. 只是它们两个版本 的源码最大的不同点是。 JDK1.7 的源码在 实例化 ArrayList 集合对象的时候,就会去创建 一个长度为 10 的Object 数组(elementData)。不管调用者实例化这个对象后要不要继续往集合中添加元素。在内存中都有个 长度为 10 的Object 数组。在一定的程度上,有点造成了资源的浪费。
  4. 而 JDK1.8 的源码在实例化 ArrayList 集合对象的时候,不会去创建 新数组,此时elementData数组的长度为0。 只有当 调用者确定往集合中添加元素时。第一次才会去对 elementData数组进行扩容(也就是创建一个长度为 10 的新数组让 elementData 指向)。JDK1.8 源码 改良后的 体现思想是为开发者 节省了内存。在调用 add 方法后才会去创建新数组。
  • ArrayList 与 Vector 的相同点:

    • 它们的底层都是通过 数组 来实现存储的。且 数组的类型都是Object。且数组的 变量名 是 elementData 。
    • 它们 的查询元素效率高。增、删元素效率低。
    • 它们的元素都是有序、可重复的。
    • 如果是使用无参构造器初始化ArrayList、Vector的实例的话:JDK1.7的 ArrayList 的 elementData 数组在是实例化 ArrayList对象的时候就已经创建好了,长度为 10。JDK1.8 的ArrayList 的 elementData 数组在实例化的时候没有创建。长度为0,在用户 第一次添加元素的时候,才会先创建 elmentData 数组。长度为 10。Vector 的elementData数组也是在实例化的时候就已经创建好了。长度为10。(Vector实例化和JDK1.7的ArrayList实例化的机制是一样的。)。它们在初始化elementData数组的共同点是。第一次初始化 elementData 数组的长度 都为 10!
  • ArrayList 与 Vector 的不同点:

    • 底层扩容机制不一样,在是否多线程的环境下。效率也不一样。
      • 当使用ArrayList时 添加的元素的数量大于 当前elementData数组的最大容量时,ArrayList 底层扩容 elementData数组的容量是 原数组容量 的1.5倍。 ArrayList 是线程不安全的、但是效率高。
      • 当使用Vector时,如果不是使用 Vector 的 有个有参构造器 可以 自定义初始化容量、和自定义初始化容量增量的构造器实例化 Vector的实例的 话。那么当 添加 的元素的数量大于 当前elementData数组的最大容量时。Vectory 底层扩容elementData数组的容量是 原数组的 2 倍。Vector 的 增加、修改、查询、删除 API都被 synchronized 关键字所修饰。说明 Vector 是线程安全的。但是 它的效率低。所以使用频率没有 ArrayList 高。

 


下一篇:Collection集合接口:List 集合子接口下的LinkedList的基本使用、模拟双向链表存储元素、LinkedList 源码阅读理解总结:

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值