zoukankan      html  css  js  c++  java
  • 关于ArrayList的两个问题

    最近在研究ArrayList,开始就发现了两个问题:

      1. ArrayList默认的初始容量大小?

      2. ArrayList的插入速度比LinkedList的慢?


    背景:

      JDK 1.8


    1. ArrayList默认的初始容量大小?

    看源码

    /**
      * Constructs an empty list with an initial capacity of ten.
      */
    public ArrayList() {
      this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    /**
      * Shared empty array instance used for default sized empty instances. We
      * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
      * first element is added.
      */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}

    第一张图片注释的意思是:

      Constructs an empty list with an initial capacity of ten.

      构造一个初始容量为10的空列表。

    但是这里,默认构造方法只干了一件事,就是将elementData初始化为一个空数组。

    那么,为什么说它的默认初始容量是10呢?

    ArrayList有三个构造方法:

    1. public ArrayList(int initialCapacity) {...}
    
    2. public ArrayList() {...}
    
    3. public ArrayList(Collection<? extends E> c) {...}

    第三个暂且不说。第一个是指定ArrayList的初始化容量,第二个则是默认的构造方法。

    如果不指定ArrayList的初始化容量,而使用默认构造方法去实例化ArrayList,则ArrayList会提供一个默认的容量大小,即为10。

        /**
         * Default initial capacity.
         */
        private static final int DEFAULT_CAPACITY = 10;

    但是,上面也说了 -- 默认构造方法只干了一件事,就是将elementData初始化为一个空数组。

    那么,什么时候才会用到这个 DEFAULT_CAPACITY  常量呢?

    答案是 -- 第一次添加元素时

    执行add()方法最终会调用到这个grow()方法,也就是数组扩容的方法。

    也就是说,第一次添加元素时,newCapacity的大小其实就是DEFAULT_CAPACITY (10)

     1 private void grow(int minCapacity) {
     2   // overflow-conscious code
     3     int oldCapacity = elementData.length;
     4     int newCapacity = oldCapacity + (oldCapacity >> 1);
     5     if (newCapacity - minCapacity < 0)
     6         newCapacity = minCapacity;
     7     if (newCapacity - MAX_ARRAY_SIZE > 0)
     8         newCapacity = hugeCapacity(minCapacity);
     9     // minCapacity is usually close to size, so this is a win:
    10     elementData = Arrays.copyOf(elementData, newCapacity);
    11 }

    这个和JDK 1.6 的版本是不一样的。1.6 的版本会直接初始化一个数组容量为10的数组 。

    还是看看源码吧。

    JDK 1.6 的

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this(10);
    }
    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param   initialCapacity   the initial capacity of the list
     * @exception IllegalArgumentException if the specified initial capacity
     *            is negative
     */
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    JDK 1.7 的

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }
    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    小结:

      1. ArrayList的默认初始容量的确是10;

      2. 使用默认构造方法实例化ArrayList,只初始化了一个空数组;

      3. 第一次调用add()方法时,数组才会被扩容,数组大小为默认的初始容量--10(2是前提)

    补充:

      我的考证仅限于1.6 到 1.8,其他版本可能还会有所不同


    2. ArrayList的插入速度比LinkedList的慢?

    add方法有两种:

    (1)一种是直接在数组最后面插入

    1 public boolean add(E e) {
    2   ensureCapacityInternal(size + 1);  // Increments modCount!!
    3     elementData[size++] = e;
    4     return true;
    5 }

    (2)另一种是指定位置插入

    1 public void add(int index, E element) {
    2     rangeCheckForAdd(index);
    3 
    4     ensureCapacityInternal(size + 1);  // Increments modCount!!
    5     System.arraycopy(elementData, index, elementData, index + 1, size - index);
    6     elementData[index] = element;
    7     size++;
    8 }

    说ArrayList的插入速度比LinkedList的慢是因为:

      ArrayList按序索引元素,所以插入新元素时涉及到其他已存在元素在数组中的前后移动,导致速度慢。

    此刻,给一个条件,只在数组末尾插入元素,即仅使用add(E e)方法,不使用add(int index, E element)方法,那么结果会如何?

    分析一下:

      只在末尾插入元素 == 不会导致中间已存在元素的变动。

    也就是说:

      这个时候,使用ArrayList的插入速度应该更快。

    当然,也不能胡说,来一个小实验验证一下: 

     1   public static void main(String[] args) {
     2         long before = System.currentTimeMillis();
     3         List list  = new ArrayList();
     4         //List list  = new LinkedList();
     5         for (int i = 0; i < 1000000; i++) {
     6             list.add(i);
     7         }
     8         long after = System.currentTimeMillis();
     9         System.out.println("插入一百万个元素,ArrayList需要时间:" + (after - before));
    10         //System.out.println("插入一百万个元素,LinkedList需要时间:" + (after - before));
    11     }

    各重复5次,结果:

    ArrayList:
      插入一百万个元素,ArrayList需要时间:28
      插入一百万个元素,ArrayList需要时间:32
      插入一百万个元素,ArrayList需要时间:63
      插入一百万个元素,ArrayList需要时间:31
      插入一百万个元素,ArrayList需要时间:32
    
    LinkedList
      插入一百万个元素,LinkedList需要时间:132
      插入一百万个元素,LinkedList需要时间:128
       插入一百万个元素,LinkedList需要时间:133
       插入一百万个元素,LinkedList需要时间:149
       插入一百万个元素,LinkedList需要时间:149   

    所以:

      应该改为 -- 随机插入元素时,ArrayList的速度比LinkedList的慢。

    并且:

      删除元素的道理也是一样的,所以应该是 -- 随机增删元素时,ArrayList的速度比LinkedList的慢。


  • 相关阅读:
    解决首次在eclipse中使用maven构建hadoop等项目时报Missing artifact sun.jdk:tools:jar:1.5.0的问题
    分享eclipse自动生成java注释方法
    Android篇Styles和Themes常见用法可能疑点小结
    常用文件的MIME类型
    C#中如何将字符串转换byte[],同时如何将byte[]换成字符串
    TransactSQL语句进行导入导出[转]
    随机索引生成
    C#使用ZLIB对字符串进行压缩
    C#对XML文件的读操作
    SQL语句删除数据库重复记录的方法
  • 原文地址:https://www.cnblogs.com/shadowdoor/p/9203926.html
Copyright © 2011-2022 走看看