在面试过程中,会遇到ArrayList实现原理是怎么样,在什么情况会对集合进行扩容等问题;ArrayList相对于其它的集合是比较简单的了。在后面会将手写的ArrayList代码附上,在这之前我们需要了解ArrayList一些主要的成员变量以及原理。
1、主要的成员变量解释
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
- DEFAULT_CAPACITY :集合默认的初始大小
- EMPTY_ELEMENTDATA: 默认一个空的数组对象;在调用有参的ArrayList时会使用的,比如new ArrayList(int initialCapacity)时,initialCapacity等于0的时候,就会赋值给elementData了。
- DEFAULTCAPACITY_EMPTY_ELEMENTDATA :默认一个空的数组对象,从使用程度来看,主要是区分在集合实例化的方式不一样,在使用默认无参的构造方法时,会将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给elementData;这个跟EMPTY_ELEMENTDATA都是一个空的数组对象,不是很明白为什么要搞两个空的数组对象
- elementData :元素存放的位置;从这里可以看出ArrayList实际是用数组存储元素的
- size :集合存放的元素大小
2、集合扩容原理
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这是大家熟悉的ArrayList对象中的add方法;在这个方法中会分一下步骤来进行:
- 计算当前最新的容量;会拿当前即将要添加的元素下标去ensureCapacityInternal(minCapacity)方法中做数组扩容判断。我们去看跟数组扩容相关的方法,不必要的代码就直接忽略
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
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);
}
在ensureExplicitCapacity方法中"minCapacity - elementData.length"这段代码才是扩容的条件。当计算出来的最新下标值减去当前数组的长度大于0时,就满足扩容条件了。
而在grow方法中,就是进行扩容的逻辑int newCapacity = oldCapacity + (oldCapacity >> 1);主要代码,这一段代码就是决定数组要扩容到多少,简单来说就是:当前的数组长度+(当前数组长度右移1位);结合前面说的DEFAULT_CAPACITY变量,集合默认的数组大小是10,对号入座之后应该是:int newCapacity = 10 + (10 >> 1) 最终结果是15,最后通过Arrays.copyOf对数组进行拷贝扩容。在Arrays.copyOf方法中底层实际是调用管理System.arraycopy方法的,这里不继续讲解,就当是额外的话题。最终将扩容后的数组赋值给elementData,最后通过elementData[size++] = e;将元素存放到数组中。
总结:ArrayList实际是用数组存储元素,这也决定的了对随机获取元素时,效率是很高的;但是ArrayList对元素的增删改性能损耗是比较大的因为增加或者删除元素,都需要移动集合里面的元素,因此ArrayList不适合频繁的增删改操作,比较适合元素的获取。
以上就是对ArrayList扩容的讲解,其它的方法也相对简单,这里就直接忽略了,有兴趣的同学可以自行去研究。接下来就是依葫芦画瓢,简单自己实现一个ArrayList的代码编写了。
3、手写ArrayList
import java.util.Arrays;
/**
* @Description: 自定义ArrayList
* @CreatedDate: 2018/12/10 15:17
* @Author:
*/
public class CustomArrayList<E> {
/**
* 初始容量大小
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 默认一个空的数组
*/
private static final Object[] DEFAULT_EMPTY_ELEMENTDATA = {};
/**
* 数组最大的一个临界值
*/
private static final int MAX_SIZE = Integer.MAX_VALUE - 8;
/**
* 实际存放的元素值
*/
private Object[] elementData;
/**
* 集合大小
*/
private int size;
public CustomArrayList() {
this.elementData = DEFAULT_EMPTY_ELEMENTDATA;
}
/**
* @Description: 添加元素
* @Author:
* @CreatedDate: 2018/12/10 15:58
* @param
* @return
*/
public boolean add(E e) {
//计算容量,是否需要扩容
checkExpansionCapacity(size + 1);
elementData[size++] = e;
return true;
}
/**
* @Description: 获取集合元素:获取元素之前,先检查是否会出现数组下标越界
* @Author:
* @CreatedDate: 2018/12/10 16:57
* @param
* @return
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
/**
* @Description: 根据下标删除元素
* @Author:
* @CreatedDate: 2018/12/10 16:57
* @param
* @return
*/
public E remove(int index) {
rangeCheck(index);
E oldValue = elementData(index);
resetEmpty(index);
return oldValue;
}
/**
* @Description: 根据对象删除元素
* @Author:
* @CreatedDate: 2018/12/10 18:10
* @param
* @return
*/
public boolean remove(E e) {
if (e == null) {
//null,则查询是否有null的数据,有则删除
for (int i =0 ;i <elementData.length; i++) {
if (elementData[i] == null) {
resetEmpty(i);
return true;
}
}
}else{
for (int i =0 ;i <elementData.length; i++) {
if (e.equals(elementData[i])) {
resetEmpty(i);
return true;
}
}
}
return false;
}
/**
* @Description: 集合大小
* @Author:
* @CreatedDate: 2018/12/10 16:57
* @param
* @return
*/
public int size(){
return this.size;
}
/**
* @Description: 删除某一个元素 or 将某一个元素置空
* System.arraycopy方法参数解释:
* Object src : 原数组
* int srcPos : 从元数据的起始位置开始
* Object dest : 目标数组
* int destPos : 目标数组的开始起始位置
* int length : 要copy的数组的长度
*
* @Author:
* @CreatedDate: 2018/12/10 18:17
* @param
* @return
*/
private void resetEmpty(int index){
int movedIndex = size - index - 1;
//利用底层提供的数组拷贝api来实现拷贝功能
if (movedIndex > 0)
System.arraycopy(elementData, index + 1, elementData, index, movedIndex);
elementData[--size] = null;
}
/**
* @Description: 获取数组中元素
* @Author:
* @CreatedDate: 2018/12/10 16:57
* @param
* @return
*/
private E elementData(int index) {
return (E) this.elementData[index];
}
/**
* @Description: 范围检查
* @Author:
* @CreatedDate: 2018/12/10 16:37
* @param
* @return
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException("index:" + index + ", size:" + size);
}
/**
* @Description: 检查数组是否需要扩容
* @Author:
* @CreatedDate: 2018/12/10 16:05
* @param
* @return
*/
private void checkExpansionCapacity(int minCapacity){
int capacity = calculateCapacity(elementData, minCapacity);
if (capacity - elementData.length > 0)
expansionCapacity(capacity);
}
/**
* @Description: 扩容数组:
* @Author:
* @CreatedDate: 2018/12/10 16:05
* @param
* @return
*/
private void expansionCapacity(int minCapacity){
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_SIZE > 0)
newCapacity = MAX_SIZE;
//进行数组扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* @Description: 计算容量
* @Author:
* @CreatedDate: 2018/12/10 15:46
* @param
* @return
*/
private int calculateCapacity(Object[] elementData, int minCapacity){
if (elementData.getClass() == DEFAULT_EMPTY_ELEMENTDATA.getClass()) {
//一开始数组是空的,则计算出最大的容量值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
}
public class ArrayListDemo {
public static void main(String[] args) {
CustomArrayList<String> list = new CustomArrayList<String>();
list.add("test1");
list.add("test2");
list.add("test3");
list.add("test4");
list.add("test5");
for (int i= 0; i< 8; i ++ ) {
if(i == 5) {
System.out.println("start...");
}
list.add("demo_" + i);
}
System.out.println(list.size());
list.remove(0);
list.remove("test2");
String result = list.get(1);
System.out.println(result);
System.out.println(list.size());
}
}
以上内容如有问题,希望可以留言给我,我可以及时纠正。很高兴与您相遇,希望对阅读完后对您有所帮助;
学习永无止境,不进则退~!