前言
集合在Java中是非常重要,不仅在Java项目开发中高频使用,在面试中也经常出现集合相关的问题。本文主要给大家介绍一下ArrayList集合类。
一、ArrayList简介
ArrayList是实现了基于动态数组的数据结构,一个ArrayList集合是存储在一块连续的内存中。如图1.1。
1、定义一个ArrayList集合A,并插入对象(Obj01-Obj05)。
2、A.add(Obj06)会将Obj06添加到集合A的最后面。
3、A.add(3,Obj07)是将Obj07插曲到集合A第三个元素(Obj04)的前面,这时候就需要将Obj04、Obj05、Obj06分别向下移动一个位置供Obj07插入。
4、A.remove(Obj02)将Obj02删除,后面的对面都需要像前移一个位置。
从步骤3和4中可以看出,ArrayList在插入和删除元素的时候,必然导致在该位置后的所有元素需要像后或者向前移动,因此,其效率相对会比较低。但是ArrayList的查询效率很高,因为只要知道ArrayList在内存中的开始位置,通过计算就能准确的定位到某一个元素的位置。
图1.1
二、ArrayList 源码解析
ArrayList类属性
//ArrayList默认容量为10private static final int DEFAULT_CAPACITY = 10;//空数组,当传入的容量为0的时候使用,通过new ArrayList(0)创建时用的是这个空数组。private static final Object[] EMPTY_ELEMENTDATA = new Object[0];//空数组,这种是通过new ArrayList()创建时用的是这个空数组,与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(10)个元素。private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];//存储元素的数组,真正存放元素的地方,使用transient是为了不序列化这个字段。transient Object[] elementData;//ArrayList中真正存储元素的个数private int size;//ArrayList容量的最大值private static final int MAX_ARRAY_SIZE = 2147483639;
构造方法ArrayList(int var1)
ArrayList(int var1)方法是创建一个初始容量为var1的集合,var1是创建集合传入的初始容量,如果大于0就初始化elementData为对应大小,如果等于0就使用EMPTY_ELEMENTDATA空数组,如果小于0抛出异常。如new ArrayList(3),会创建一个初始容量为3的集合,那么初始容量为3是不是这个集合就只能存储3个元素呢?答案是否定的,初始化容量为3的集合是可以存储超过3个元素的,因为ArrayList可自动扩容。
public ArrayList(int var1) { if (var1 > 0) { // 如果传入的初始容量大于0,就新建一个数组存储元素 this.elementData = new Object[var1]; } else { if (var1 != 0) { //如果不大于0且不等于0,则抛出异常(因初始化容量不能小与0) throw new IllegalArgumentException("Illegal Capacity: " + var1); } //如果初始化容量等于0,则使用空数组 EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } }
构造方法ArrayList()
该构造方法没有传入初始的容量,则默认使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这种方法是我们比较常用的。
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}
构造方法ArrayList(Collection extends E> var1)
该方法是把初始化的时候把传入的集合的元素初始化到ArrayList中,如果传入的集合元素个数为0,则初始化EMPTY_ELEMENTDATA空数组。
public ArrayList(Collection extends E> var1) { //将传入的集合转换成数据,并赋值给elementData this.elementData = var1.toArray(); if ((this.size = this.elementData.length) != 0) { // 检查var1.toArray()返回的是不是Object[]类型,如果不是,重新拷贝成Object[].class类型 if (this.elementData.getClass() != Object[].class) { this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class); } } else { //如果传入的集合元素个数为0,则初始化EMPTY_ELEMENTDATA空数组。 this.elementData = EMPTY_ELEMENTDATA; } }
add(E var1)方法
在集合末尾添加一个新元素。
public boolean add(E var1) { //判断如果新增一个元素,会不会超过ArrayList的容量,若超过,则扩容 this.ensureCapacityInternal(this.size + 1); //将新的元素添加到集合的末尾 this.elementData[this.size++] = var1; return true; }
ensureCapacityInternal(int var1)方法
该方法用来判断var1有没有超过当前ArrayList容量的大小,如果超过了则自动扩容,从这个方法我们可以看到,当使用ArrayList()构造方法的时候,初始化为空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,但是当第一次使用Add方法往集合中添加元素的时候,集合会扩容为10。
private void ensureCapacityInternal(int var1) { //如果是空数组,则取var1和10两个值中的最大值 if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { var1 = Math.max(10, var1); } //将上一步取到的最大值,扩容ArrayList的大小 this.ensureExplicitCapacity(var1); }
ensureExplicitCapacity(int var1)方法
该方法判断var1与集合现在的元素个数的差,如果差值大于0,则表示现有集合的容量已经满足不了新增的元素,就会调用grow方法扩容。
private void ensureExplicitCapacity(int var1) { ++this.modCount; if (var1 - this.elementData.length > 0) { this.grow(var1); } }
grow(int var1)方法
private void grow(int var1) { //获取ArrayList现有的容量 int var2 = this.elementData.length; //将现有容量的值乘1.5倍(扩容以后的值) int var3 = var2 + (var2 >> 1); //判断扩容后的容量是否能满足需要的容量,如果不能则已需要的容量为准 if (var3 - var1 < 0) { var3 = var1; } // 如果新容量已经超过最大容量了,则使用最大容量 if (var3 - 2147483639 > 0) { var3 = hugeCapacity(var1); } // 以新容量拷贝出来一个新数组 this.elementData = Arrays.copyOf(this.elementData, var3); }
add(int n, E var2)方法
该方法是将元素var2插入到集合的第n个位置。
public void add(int var1, E var2) { //检查要插入的位置是否合法(插入的位置不能小于0且不能大于当前集合的容量) this.rangeCheckForAdd(var1); 判断如果新增一个元素,会不会超过ArrayList的容量,若超过,则扩容 this.ensureCapacityInternal(this.size + 1); //将要插入位置的之后的所以元素往后移动一个位置 System.arraycopy(this.elementData, var1, this.elementData, var1 + 1, this.size - var1); //将新的元素插入对应的位置 this.elementData[var1] = var2; ++this.size; }
remove(int var1)方法
删除var1位置的元素,集合中元素的位置是从0开始计算的,所以如果想删除第一个元素需用 remove(0)。
public E remove(int var1) { //检查要移除的元素位置是否合法(var1不能小于0且不能大于集合容量的最大值) this.rangeCheck(var1); ++this.modCount; //获取要删除的元素 Object var2 = this.elementData(var1); int var3 = this.size - var1 - 1; if (var3 > 0) { //将要删除元素之后的所有元素往前移动一个位置 System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var3); } //将最后的元素设置为null并将size减1 this.elementData[--this.size] = null; //返回删除的元素 return var2; }
remove(Object var1)
根据元素删除,从该方法的源码中,我们可以看到,如果集合中存在多个var1元素,remove方法只会按顺序找到第一个var1元素并删除,并不会把所有的var1元素都删除。
public boolean remove(Object var1) { int var2; //当var1为null时,循环遍历集合中的元素,找到一个null的元素并删除,返回true if (var1 == null) { for (var2 = 0; var2 < this.size; ++var2) { if (this.elementData[var2] == null) { this.fastRemove(var2); return true; } } } else { //当var1不为null时,循环遍历集合中的元素,找到一个var1的元素并删除,返回true for (var2 = 0; var2 < this.size; ++var2) { if (var1.equals(this.elementData[var2])) { this.fastRemove(var2); return true; } } } return false; }
fastRemove(int var1)方法
删除第var1个位置的元素。
private void fastRemove(int var1) { ++this.modCount; //如果var1不是最后一位,则将var1之后的元素依次往前移动一位 int var2 = this.size - var1 - 1; if (var2 > 0) { System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var2); } //将集合中最后的元素设置为null this.elementData[--this.size] = null; }
System.arraycopy()
System中提供了一个native静态方法arraycopy(),可以使用这个方法来实现数组之间的复制。arraycopy的参数如下:
arraycopy(Object src,int srcPos,Object dest,int destPos,int length);src表示源数组srcPos表示源数组要复制的起始位置desc表示目标数组destPos表示目标数据要复制的其实位置length表示要复制的长度
更多文章请关注微信公众号:IT鸡窝