常见面试题02-ArrayList扩容机制详解

3 篇文章 0 订阅
1 篇文章 0 订阅

常见面试题02-ArrayList

面试题:

ArrayList底层实现是什么?默认长度是什么,说一下它的扩容机制

进入ArrayList类源码进行分析

  分析思路:
1.查看arraylist类的介绍
2.理解arraylist类的属性含义
3.理解arraylist的构造方法
4..理解arraylist中比较重要的方法机制(如add、grow)是如何实现的

————————————————————————————————————————
分析步骤1:
1.查看arraylist类的介绍

下面截取、整理部分arraylist类的介绍(为方便阅读,高亮显示)

Arraylist类的介绍:


1.Resizable-array implementation of the <tt>List</tt> interface.

2.This class is roughly equivalent to <tt>Vector</tt>, except that it is unsynchronized.

3.>Each <tt>ArrayList</tt> instance has a <i>capacity</i>.  The capacity is
  the size of the array used to store the elements in the list.  It is always
  at least as large as the list size.  As elements are added to an ArrayList,
  its capacity grows automatically.  The details of the growth policy are not
  specified beyond the fact that adding an element has constant amortized
  time cost.
  
  4.Note that this implementation is not synchronized.</strong>
 If multiple threads access an <tt>ArrayList</tt> instance concurrently,
 and at least one of the threads modifies the list structurally, it
 <i>must</i> be synchronized externally
 
  5.{@link Collections#synchronizedList Collections.synchronizedList}
 method.  This is best done at creation time, to prevent accidental
 unsynchronized access to the list:<pre>
 List list = Collections.synchronizedList(new ArrayList(...));</pre>

汇总信息:(标号与代码注释一致)
1.arraylist是List接口的实现

2.arraylist类基本与vector相等,只是arraylist是非同步的(非线程安全的)

3.arraylist有自动扩容机制,它至少会保障这个数组的最小容量和你的数组大小一致,但是arraylist的扩容机制并不是完全确定的,而且每次添加一个元素会消耗特定不变的缓冲时间

4.这个arraylist实现并不是同步的,如果多线程进入一个arraylist实例,并且至少有一个线程结构性(比如对它进行了删除之类的操作)地修改了这个list,那么就必须在外部将它同步化

5.这里提供了一个将arraylist同步化的方法,并且最好在创建的时候便这样做,以防对这个list的非同步访问,即利用Collections工具类:
List list = Collections.synchronizedList(new ArrayList(…));

————————————————————————————————————————
分析步骤2:
2.理解arraylist类的属性含义

 /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

文档注释告诉我们几个信息:
1.elementData,对象数组,被称作对象的数组存储区,也就是存放一个个对象的位置
2.这个对象的数组存储区的长度也就是arraylist对象的容量
3.任何空的arraylist对象会在第一次添加元素的时候扩展到DEFAULT_CAPACITY (默认容量)

OK,接下来我们再看DEFAULT_CAPACITY 属性

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

信息:arraylist存在一个默认容量(对象的数组存储区的容量),默认容量为10

   /**
     * The size of the ArrayList (the number of elements it contains).
     */
    private int size;

信息:size属性,也就是在对象的数组存储区(elementData)中实际存储有多少个元素

 /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] 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 = {};

两个属性:EMPTY_ELEMENTDATA 、DEFAULTCAPACITY_EMPTY_ELEMENTDATA
可以看到两个都是空的对象数组,那么它们的区别在于哪里?

文档注释这样说的:
EMPTY_ELEMENTDATA ,是空的数组实例,用于空的arraylist对象实例
DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,是空的数组实例,用于默认大小的、空的arraylist实例。我们创建这个空数组实例与EMPTY_ELEMENTDATA 区分,从而知道我们在第一个元素添加的时候我们应该扩容多少

还是有些模糊,但我们知道两者的区别与构造、添加元素以及扩容机制有关
————————————————————————————————————————
分析步骤3:
查看构造方法

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

Arraylist提供了三个构造方法:

1.先看无参构造:
无参构造,我们会一个空的数组对象——>DEFAULTCAPACITY_EMPTY_ELEMENTDATA
这点很重要,注意:
我们得到的是一个空的对象数组,而不是一个默认容量为10的对象数组

2.看有参构造:
第一种:传int参数
如果这个初始化参数大于0,那么就会设置对象大的数组存储区的容量大小与初始化参数相等
如果初始化参数等于0,那就返回EMPTY_ELEMENTDATA的空数组

第二种:传Collection
首先,将Collection转化成数组,赋值给elementData,如果size不为0,那么就判断elementData是不是属于Object数组(这是为了避免出现在集合转数组时可能存在不返回Object数组的情况)
如果size为0,那就返回EMPTY_ELEMENTDATA的空数组

分析:
其实到这里,EMPTY_ELEMENTDATA 、DEFAULTCAPACITY_EMPTY_ELEMENTDATA 两者的区别就有些眉目了:

EMPTY_ELEMENTDATA 就是我们在指定size为0的时候的空数组。

DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个默认的数组,当然它也是一个空数组,但我们没有去特别指定它的size为0,更确切地说,它处于一个默认的、无的状态,这两者存在概念性或者说设计理念上的区别,而这设计理念的具体体现在哪里,正如之前所提到的DEFAULTCAPACITY_EMPTY_ELEMENTDATA的文档注释所写,我们需要去看arraylist的扩容机制。

EMPTY_ELEMENTDATA ,是空的数组实例,用于空的arraylist对象实例
DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,是空的数组实例,用于默认大小的、空的arraylist实例
我们创建这个空数组实例与EMPTY_ELEMENTDATA 区分,从而知道我们在第一个元素添加的时候我们
应该扩容多少

————————————————————————————————————————
分析步骤4:
查看添加和关于扩容机制的方法

添加元素:

  public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
   private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
   private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

分析步骤4:查看添加add方法:
由上面的信息可知,在arraylist添加一个元素的时候,会执行这样的操作:

1.执行ensureCapacityInternal方法,判断这个数组缓存区是不是默认的空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,如果是,那么就会给到它一个默认容量10,然后执行去执行 ensureExplicitCapacity方法。

如果不是,就会以当前对象数组缓存区的size+1的大小作为参数去执行 ensureExplicitCapacity方法。

2.执行ensureExplicitCapacity方法
A.首先,它将修改次数modCount+1(这里没有对其深究,但可以猜测是为防止出现某些不当的修改操作导致的数据安全问题)
B.然后如果添加一个元素之后的容量大于了数组缓存区的容量,就会去执行grow扩容方法
(注1:这种情况只会在非默认数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA的情况下出现,因为如果原本是一个空数组,在添加一个元素后不可能大于所设置的10的默认容量)

————————————————————————————————————————
先进行一个小结
1.关于EMPTY_ELEMENTDATA 、DEFAULTCAPACITY_EMPTY_ELEMENTDATA,现在我们终于知道这两者的区别就正如文档注释所说,存在于两者分别确定了不同的扩容机制。

对于EMPTY_ELEMENTDATA,也就是经过由它初始化的、size为0的数组缓存区而言,不会在第一次添加元素后使它的容量扩展到默认的10的长度,而是会去执行grow的扩容方法。

对于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,可以看到ensureCapacityInternal方法中,首先用判断了elementData与DEFAULTCAPACITY_EMPTY_ELEMENTDATA是否一致,请特别注意,这里是用==进行判断的,而不是进行值的比较,也就是说,同样的空数组EMPTY_ELEMENTDATA是无法通过这个判断的,而这时,这个未被初始化过的、默认的、空的数组缓存区就会被扩展到10的容量长度
————————————————————————————————————————
分析步骤4:查看grow方法

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

信息获取:
由上面源码可知,grow的机制是比较简单的,大致如下:
1.在容量不足的情况下,newCapacity新的容量会扩展至oldCapacity 旧的容量的1.5倍
2.新增条件,虽然我们进行了扩容,但是扩容后的容量可能存在两种情况:
A.扩容容量太小,就让我们需要的容量赋值给这个newCapacity新的容量(这种情况应该会发生在调用addAll,也就是添加集合的情况下发生)
B.扩容容量太大,这里有一个MAX_ARRAY_SIZE,它的值为Integer.MAX_VALUE - 8,,如果我们扩容后的容量大小比它还大,那么就会调用hugeCapacity方法:
如果minCapacity得到了一个负数(也就是说这个值已经超过了Integer最大值),就会抛出一个“内存溢出”的异常
否则,就将你所需要的容量大小与MAX_ARRAY_SIZE比较,然后按照源码中的三目运算的方法在MAX_ARRAY_SIZE与Integer.MAX_VALUE间取得一个值。

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值