腾讯一面,被ArrayList遍历问题问住了

本文基于JDK-8u261源码分析

1 简介

ArrayList作为最基础的集合类,其底层是使用一个动态数组来实现的,这里“动态”的意思是可以动态扩容(虽然ArrayList可以动态扩容,但却不会动态缩容)。但是与HashMap不同的是,ArrayList使用的是1.5的扩容策略,而HashMap使用的是2的方式。还有一点与HashMap不同:ArrayList的默认初始容量为10,而HashMap为16。

有意思的一点是:在Java 7之前的版本中,ArrayList的无参构造器是在构造器阶段完成的初始化;而从Java 7开始,改为了在add方法中完成初始化,也就是所谓的延迟初始化。在HashMap中也有同样的设计思路。

另外,同HashMap一样,如果要存入一个很大的数据量并且事先知道要存入的这个数据量的固定值时,就可以往构造器里传入这个初始容量,以此来避免以后的频繁扩容。


2 构造器


/**
 * ArrayList:
 * 无参构造器
 */
public ArrayList() {
    //DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空实现“{}”,这里也就是在做初始化
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
 
/**
 * 有参构造器
 */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        //initialCapacity>0就按照这个容量来初始化数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        //EMPTY_ELEMENTDATA也是一个空实现“{}”,这里也是在做初始化
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        //如果initialCapacity为负数,则抛出异常
        throw new IllegalArgumentException("Illegal Capacity: " +
                initialCapacity);
    }
}

3 add方法

3.1 add(E e)

添加指定的元素:


/**
 * ArrayList:
 */
public boolean add(E e) {
    //查看是否需要扩容
    ensureCapacityInternal(size + 1);
    //size记录的是当前元素的个数,这里就直接往数组最后添加新的元素就行了,之后size再+1
    elementData[size++] = e;
    return true;
}
 
/**
 * 第6行代码处:
 */
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
 
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    /*
    minCapacity = size + 1
    之前说过,DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空实现“{}”,这里也就是在判断是不是调用的无参构造器
    并第一次调用到此处
     */
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        /*
        如果是的话就返回DEFAULT_CAPACITY(10)和size+1之间的较大者。也就是说,数组的最小容量是10
        这里有意思的一点是:调用new ArrayList<>()和new ArrayList<>(0)两个构造器会有不同的默认容量(在HashMap中
        也是如此)。也就是说无参构造器的初始容量为10,而传进容量为0的初始容量为1。同时这也就是为什么会有
        EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA这两个常量的存在,虽然它们的值都是“{}”
        原因就在于无参构造器和有参构造器完全就是两种不同的实现策略:如果你想要具体的初始容量,那么就调用有参构造器吧,
        即使传入的是0也是符合这种情况的;而如果你不在乎初始的容量是多少,那么就调用无参构造器就行了,这会给你默
        认为10的初始容量
         */
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //如果调用的是有参构造器,或者调用无参构造器但不是第一次进来,就直接返回size+1
    return minCapacity;
}
 
/**
 * 第16行代码处:
 */
private void ensureExplicitCapacity(int minCapacity) {
    //修改次数+1(快速失败机制)
    modCount++;
 
    /*
    如果+1后期望的容量比实际数组的容量还大,就需要扩容了(如果minCapacity也就是size+1后发生了数据溢出,
    那么minCapacity就变为了一个负数,并且是一个接近int最小值的数。而此时的elementData.length也会是一个接近
    int最大值的数,那么该if条件也有可能满足,此时会进入到grow方法中的hugeCapacity方法中抛出溢出错误)
     */
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
 
private void grow(int minCapacity) {
    //获取扩容前的旧数组容量
    int oldCapacity = elementData.length;
    //这里扩容后新数组的容量是采用旧数组容量*1.5的方式来实现的
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    /*
    如果新数组容量比+1后期望的容量还要小,此时把新数组容量修正为+1后期望的容量(对应于newCapacity为0或1的情况)
    这里以及后面的判断使用的都是“if (a - b < 0)”形式,而不是常规的“if (a < b)”形式是有原因的,
    原因就在于需要考虑数据溢出的情况:如果执行了*1.5的扩容策略后newCapacity发生了数据溢出,那么它就一样
    变为
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值