ArraylList的扩容机制和使用ensureCapacity()方法提高性能

24 篇文章 0 订阅
15 篇文章 0 订阅

ArrayList的扩容规则是变成原来最大容量的1.5倍+1

具体为什么,现在看一下源码:

[java]  view plain  copy
  1. public boolean add(E e) {  
  2.        ensureCapacityInternal(size + 1);  // Increments modCount!!  
  3.        elementData[size++] = e;  
  4.        return true;  
  5.    }  
ensureCapacityInternal是判断是否要扩容的方法,下面是源码:

[java]  view plain  copy
  1. private void ensureCapacityInternal(int minCapacity) {  
  2.         if (elementData == EMPTY_ELEMENTDATA) {  
  3.             minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);  
  4.         }  
  5.   
  6.         ensureExplicitCapacity(minCapacity);  
  7.     }  

[java]  view plain  copy
  1. private transient Object[] elementData;  
elementData是存放元素的对象数组

[java]  view plain  copy
  1. private static final Object[] EMPTY_ELEMENTDATA = {};  
EMPTY_ELEMENTDATA是空数组,表示现在ArrayList是空的

ensureCapacityInternal中首先是判断现在的ArrayList是不是空的,如果是空的,minCapacity就取默认的容量和传入的参数minCapacity中的大值

然后调用ensureExplicitCapacity方法,

[java]  view plain  copy
  1. private void ensureExplicitCapacity(int minCapacity) {  
  2.       modCount++;  
  3.   
  4.       // overflow-conscious code  
  5.       if (minCapacity - elementData.length > 0)  
  6.           grow(minCapacity);  
  7.   }  

modCount是fail fast机制,在jdk1.6之前都是有volatile来修饰的,尽可能的让并发访问非安全的集合对象时尽快的失败抛出异常,让程序员修改代码。

在jdk1.7中去掉了volatile修饰,因为感觉没有必要为非线程安全集合浪费效率,在jdk1.5开始就提供了线程安全的集合类,在多线程环境下就应该使用线程安全的集合。

接着看,如果minCapacity的值大于add数据之前的大小,就调用grow方法,进行扩容,否则什么也不做。

下面是具体看出来扩容机制的大小增长规则了:

[java]  view plain  copy
  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.    }  
注意:这里传过来的minCapcatiy的值是size+1,能够实现grow方法调用就肯定是(size+1)>elementData.length的情况,所以size就是初始最大容量或上一次扩容后达到的最大容量,所以才会进行扩容。

newCapacity=oldCapacity+(oldCapacity>>1),这里就是扩容大小确定的地方,相当于新的最大容量是 size+1+size/2 相当于原来的1.5倍然后加1

==============================================================================================================================

这样每次size达到容量后,再插入数据就会造成扩容,默认的容量大小是10,如果我们现在连续插入17的对象,就会扩容两次,第一次是在插入第11个对象时,第二次是在插入第17个对象时扩容,这样会频繁进行数组的拷贝,效率影响很大。那么我们在插入数据数量已知的情况,可以调用ensureCapacity方法。

下面是方法源码:

[java]  view plain  copy
  1. public void ensureCapacity(int minCapacity) {  
  2.         int minExpand = (elementData != EMPTY_ELEMENTDATA)  
  3.             // any size if real element table  
  4.             ? 0  
  5.             // larger than default for empty table. It's already supposed to be  
  6.             // at default size.  
  7.             : DEFAULT_CAPACITY;  
  8.   
  9.         if (minCapacity > minExpand) {  
  10.             ensureExplicitCapacity(minCapacity);  
  11.         }  
  12.     }  
为什么要先判断先判断elementData!=EMPTY_ELEMENTDATA呢?现在看一下ArrayList的无参构造函数

[java]  view plain  copy
  1. public ArrayList() {  
  2.        super();  
  3.        this.elementData = EMPTY_ELEMENTDATA;  
  4.    }  
这里看出来ArrayList在没有指定初始化容量的时候elementData就是一个空对象数组,没有任何对象元素,也就是容量为0,没有分配空间。

可以再看一下add方法中,调用的ensureCapacityInternal方法中判断elementData == EMPTY_ELEMENTDATA 是否为空,如果为空,minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);,minCapacity设置为其中的大值,在默认是空的时候,初次调用add方法传入的参数是1,也就是这里的minCapacity就是1,现在minCapcity变成了DEFAULT_CAPACITY 也就是10,下面会调用grow方法,然后执行 elementData = Arrays.copyOf(elementData, 10);下面注意拷贝方法:

[java]  view plain  copy
  1. public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {  
  2.        T[] copy = ((Object)newType == (Object)Object[].class)  
  3.            ? (T[]) new Object[newLength]  
  4.            : (T[]) Array.newInstance(newType.getComponentType(), newLength);  
  5.        System.arraycopy(original, 0, copy, 0,  
  6.                         Math.min(original.length, newLength));  
  7.        return copy;  
  8.    }  
执行了第一条语句后,copy=new Objecet[10]了,返回的结果数组大小就是10了, ArrayList相当于在没指定initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10个对象空间。

上面分析过程也包含了扩容的步骤了。

当我们已经确定了要插入的对象的数目(并不是在创建ArrayList之前就知道有多少对象要插入的情况),就应该首先调用ensureCapacity来一次性扩容到可以容得下要插入的对象个数,这样就避免的多次进行数组拷贝的情况,提高了效率,算是优化吧
当然,我们在创建ArrayList之前就知道有多少对象要插入就使用有参构造。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值