计算机系统应用教程网站

网站首页 > 技术文章 正文

聊一聊Arraylist的扩容机制

btikc 2025-02-28 15:00:07 技术文章 3 ℃ 0 评论

1 死记硬背

1 ArrayList底层是数组elementData,用于存放插入的数据,初始大小是0,当有数据插入时,默认大小DEFAULT_CAPACITY = 10。

2 当ArrayList中的元素数量达到当前容量时,ArrayList会自动增加其容量,大小一般为原来数组的1.5倍。

2 简介

ArrayList是Java集合框架中的一个重要的类,它继承于AbstractList,实现了List接口,是一个长度可变的集合,提供了增删改查的功能。集合中允许null的存在。ArrayList类还是实现了RandomAccess接口,可以对元素进行快速访问。实现了Serializable接口,说明ArrayList可以被序列化,还有Cloneable接口,可以被复制。和Vector不同的是,ArrayList不是线程安全的。

3 三个构造器

// 1 无参构造器
public ArrayList() {
  this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 2 初始化大小的构造器
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);
  }
}
// 3 包含初始化元素的构造器
public ArrayList(Collection 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;
  }
}

4 扩容机制

1 先上代码

public static void main(String[] args) {
  ArrayList list = new ArrayList<>();
  list.add("面试题解析");
  System.out.println(list.size());
}

2 代码详解

ArrayList list = new ArrayList<>();

这个代码是创建了一个ArrayList的对象list,而扩容机制发生在list.add的方法里,我们看下add方法的源码。

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

此时的E e你可以理解成我们代码里传的字符串“面试题解析”,而此时的size是0,所以我们要去看下ensureCapacityInternal这个方法的源码。

private void ensureCapacityInternal(int minCapacity) {
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  }

  ensureExplicitCapacity(minCapacity);
}

此时的minCapacity可以理解成是1,因为是上面的size+1传过来的,而size是0,所以minCapacity就是1了,因为初始化的时候elementData设置成了
DEFAULTCAPACITY_EMPTY_ELEMENTDATA,所以就会走minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);这行代码,DEFAULT_CAPACITY是10,所以minCapacity就变成了10,接下来走方法ensureExplicitCapacity,我们也看下源码。

private void ensureExplicitCapacity(int minCapacity) {
  modCount++;

  // overflow-conscious code
  if (minCapacity - elementData.length > 0)
    grow(minCapacity);
}

此时的minCapaticy为10,而modCount是指当前集合被修改的次数,主要包括添加和删除,此时modeCount的值为0,当执行了一次add操作后,就变成了1,minCapacity是指要求的最小的容量,而elementData是指已经有的元素数组,当要求的最小的容量大于已经有的数组大小时,就会执行扩容操作,也就是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);
}

在grow方法中,首先我们会计算老的数组的容量为oldCapacity,本例中为0,而扩容后的容量为oldCapacity的1.5倍,为啥是1.5倍呢?看下代码:int newCapacity = oldCapacity + (oldCapacity >> 1);新容量newCapacity是老容量加上老容量右移一位的容量,而右移一位就相当于原来的容量除以2,即0.5倍,再加上前面的oldCapacity,就是1.5倍的oldCapacity,也是文章刚开始种死记硬背中说的1.5倍。一般这种情况就满足扩容需求了,但是仍然有两种情况比较特殊。

第一种就是:newCapacity - minCapacity < 0,即扩容后的容量仍然不能满足最低容量的需求,则newCapacity的大小就以minCapacity为准,这种情况是大于1.5倍的扩容的。

第二种就是:newCapacity - MAX_ARRAY_SIZE > 0,即新扩容后的大小大于2147483639,则会走新的方法hugeCapacity。

private static int hugeCapacity(int minCapacity) {
  if (minCapacity < 0) // overflow
    throw new OutOfMemoryError();
  return (minCapacity > MAX_ARRAY_SIZE) ?
    Integer.MAX_VALUE :
  MAX_ARRAY_SIZE;
}

当minCapacity小于0时,则会报OOM异常,当大于MAX_ARRAY_SIZE,最后的容量大小就等于2147483647,否则的话就等于2147483639。

附上扩容机制的时序图,希望大家能更清楚地掌握扩容机制。

总结

ArrayList的扩容机制主要是由于在添加元素时,数组的容量不够导致扩容,所以我们在创建ArrayList的时候,尽可能地指定大小,然后再进行添加操作,这样就可以避免后期的操作频繁地发生扩容而导致性能下降,从而可以提升代码的性能。如果本文讲的不够详细,有不清楚的地方,欢迎留言,都会一一回复,感谢支持。

【温馨提示】

点赞+收藏文章,关注我并私信回复【面试题解析】,即可100%免费领取楼主的所有面试题资料!

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表