从面试角度分析ArrayList源码

开发 前端
ArrayList提供两种删除元素的方法,可以通过索引和元素进行删除。两种删除大同小异,删除元素后,将后面的元素一次向前移动。

 [[357376]]

ArrayList类图如下:

ArrayList的底层是由数组实现的,数组的特点是固定大小,而ArrayList实现了动态扩容。

ArrayList部分变量如下,在下面的分析中会用到这些变量。

  1. /** 
  2.  * 默认容量 
  3.  */ 
  4. private static final int DEFAULT_CAPACITY = 10; 
  5. /** 
  6.  * 空的对象数组 
  7.  */ 
  8. private static final Object[] EMPTY_ELEMENTDATA = {}; 
  9. /** 
  10.  * 无参构造器创建的空数组 
  11.  */ 
  12. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 
  13. /** 
  14.  * 存放数据的数组的缓存变量 
  15.  */ 
  16. transient Object[] elementData; 
  17. /** 
  18.  * 元素数量 
  19.  */ 
  20. private int size

一 初始化ArrayList

初始化ArrayList一般会使用以下两个构造器

1.1 无参构造器

初始化ArrayList的时候如果不指定大小,则会创建一个空数组。

  1. public ArrayList() { 
  2.     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 

1.2 指定数组大小的构造器

创建一个预估大小的数组,指定大小后只是指定了数组初始值的大小,不影响后面扩容,指定的好处就是可以节省内存及时间上的开销。

  1. public ArrayList(int initialCapacity) { 
  2.     if (initialCapacity > 0) { 
  3.         this.elementData = new Object[initialCapacity]; 
  4.     } else if (initialCapacity == 0) { 
  5.         this.elementData = EMPTY_ELEMENTDATA; 
  6.     } else { 
  7.         throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity); 
  8.     } 

二 添加元素、动态扩容

ArrayList.add(E e)源码:

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

add()中elementData[size++] = e很好理解,就是将元素插入第size个位置,然后将size++,我们重点来看看ensureCapacityInternal(size + 1)方法;

  1. private void ensureCapacityInternal(int minCapacity) { 
  2.     if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 
  3.         minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 
  4.     } 
  5.     ensureExplicitCapacity(minCapacity); 

ensureCapacityInternal()方法中判断缓存变量elementData是否为空,也就是判断是否是第一次添加元素,如果是第一次添加元素,则设置初始化大小为默认容量10,否则为传入的参数。这个方法的目的就是获取初始化数组容量。获取到初始化容量后调用ensureExplicitCapacity(minCapacity)方法;

  1. private void ensureExplicitCapacity(int minCapacity) { 
  2.     modCount++; 
  3.  
  4.     // overflow-conscious code 
  5.     if (minCapacity - elementData.length > 0) 
  6.         grow(minCapacity); 

ensureExplicitCapacity(minCapacity)方法用来判断是否需要扩容,假如第一次添加元素,minCapacity为10,elementData容量为0,那么就需要去扩容。调用grow(minCapacity)方法。

  1. // 数组的最大容量 
  2. private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 
  3.  
  4. private void grow(int minCapacity) { 
  5.     // overflow-conscious code 
  6.     int oldCapacity = elementData.length; 
  7.     // 扩容大小为原来数组长度的1.5倍 
  8.     int newCapacity = oldCapacity + (oldCapacity >> 1); 
  9.     // 扩容容量比需要扩容的长度小,则使用需要扩容的容量 
  10.     if (newCapacity - minCapacity < 0) 
  11.         newCapacity = minCapacity; 
  12.     // 扩容容量比最大数组长度大,则使用最大整数长度 
  13.     if (newCapacity - MAX_ARRAY_SIZE > 0) 
  14.         newCapacity = hugeCapacity(minCapacity); 
  15.     // minCapacity is usually close to size, so this is a win: 
  16.     elementData = Arrays.copyOf(elementData, newCapacity); 

grow(minCapacity)方法对数组进行扩容,扩容大小为原数组的1.5倍,如果计算出的扩容容量比需要的容量小,则扩容大小为需要的容量,如果扩容容量比数组最大容量大,则调用hugeCapacity(minCapacity)方法,将数组扩容为整数的最大长度,然后将elemetData数组指向新扩容的内存空间并将元素复制到新空间。

当需要的集合容量特别大时,扩容1.5倍就会非常消耗空间,因此建议初始化时预估一个容量大小。

三 删除元素

ArrayList提供两种删除元素的方法,可以通过索引和元素进行删除。两种删除大同小异,删除元素后,将后面的元素一次向前移动。

ArrayList.remove(int index)源码:

  1. public E remove(int index) { 
  2.     rangeCheck(index); 
  3.  
  4.     modCount++; 
  5.     E oldValue = elementData(index); 
  6.  
  7.     int numMoved = size - index - 1; 
  8.     if (numMoved > 0) 
  9.         System.arraycopy(elementData, index+1, elementData, index
  10.                          numMoved); 
  11.     elementData[--size] = null; // clear to let GC do its work 
  12.  
  13.     return oldValue; 

删除元素时,首先会判断索引是否大于ArrayList的大小,如果索引范围正确,则将索引位置的下一个元素赋值到索引位置,将ArrayList的大小-1,最后返回移除的元素。操作图如下,假如我要移除索引为1的元素:

四 总结

ArrayList底层是数组实现的,可以进行动态扩容,扩容大小为原来的1.5倍,虽然可以通过动态扩容,但是数组非常大时会特别浪费空间,因此建议初始化时预估数组大小。ArrayList允许插入重复值和空值。ArrayList实现了RandomAccess接口,支持快速随机访问,就是可以通过索引快速查到某个元素,因此遍历时使用for循环的方式效率更高。ArrayList是线程不安全的,可以通过Collections.synchronizedList将其转变为线程安全的集合,不过一般不会使用,Vector和CopyOnWriteArrayList是线程安全的,Vector性能一般,逐渐被CopyOnWriteArrayList取代了。

本文转载自微信公众号「Java旅途」,可以通过以下二维码关注。转载本文请联系Java旅途公众号。

 

责任编辑:武晓燕 来源: Java旅途
相关推荐

2020-12-17 08:03:57

LinkedList面试源码

2021-07-20 10:26:53

源码底层ArrayList

2022-03-08 11:29:06

Linux进程系统

2018-10-22 14:28:26

面试官数据公司

2021-03-15 18:47:25

日志开发源码

2021-10-26 10:22:27

ArrayList阿里云

2023-03-13 07:43:51

PHP类型转换

2018-04-27 14:46:07

面试简历程序员

2014-05-12 10:06:15

面试测试文化360

2022-09-27 18:56:28

ArrayList数组源代码

2016-11-04 10:30:17

微信小程序

2021-09-10 00:34:22

Java 线程启动

2023-04-14 08:39:01

AQS方法JDK5

2017-09-25 16:21:30

Spark on yacluster模式

2021-05-28 11:54:41

区块链数据分析密码学角度

2017-04-13 09:45:03

大数据新媒体VC

2021-12-09 08:31:01

ReentrantLoAQS

2020-02-04 09:53:05

数据安全数据泄漏信息安全

2022-04-14 07:51:21

MySQL数据库架构

2019-04-28 16:10:50

设计Redux前端
点赞
收藏

51CTO技术栈公众号