简单易懂的ArrayList源码分析

Posted 空指针异常

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单易懂的ArrayList源码分析相关的知识,希望对你有一定的参考价值。

ArrayList是工作中使用频率很高的数组集合类,让我们来看看它的实现原理是什么。

ArrayList关键属性

 //默认初始化容量大小 private static final int DEFAULT_CAPACITY = 10;
//空数组实例 private static final Object[] EMPTY_ELEMENTDATA = {};
//默认初始化大小的空实例,与EMPTY_ELEMENTDATA区分出来 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//ArrayList使用这个属性,Object[]数组存储它的数据 transient Object[] elementData; // non-private to simplify nested class access
//数组实际存储的元素个数 private int size;

ArrayList的本质是一个Object[]数组。

ArrayList常用方法

首先,我们使用无参构造函数声明一个ArrayList。

List<String> list = new ArrayList();         //本质就是声明一个空数组。效果如同Object[] elementData = {};

public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

接着,使用add方法往集合中添加元素。

list.add("a");

添加一个元素需要以下步骤:

1. 计算数组所需的最小长度,应该是原数组的长度+1。如果是一个空数组,则默认取10跟原数组的长度+1中两者较大的那个。

2. 判断所需的最小长度是否大于现在的数组长度, 如果大于,需要扩充数组。

3. 创建一个新数组,大小是oldCapacity + (oldCapacity >> 1)。然后将原数组存储的内容复制到新数组,并将新数组赋值给elementData属性。

4. 往数组最后位置添加元素:elementData[size++] = e。

 
public boolean add(E e) { //先校验原有大小再加一个元素数组能否容纳下去,如果不能,创建一个更大的数组,并把原数组的内容复制进去 ensureCapacityInternal(size + 1); //将元素添加到数组的最后一个位置 elementData[size++] = e; return true;}

private void ensureCapacityInternal(int minCapacity) { //数组最小容量是原数组元素个数+1 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}

private static int calculateCapacity(Object[] elementData, int minCapacity) { //如果数组里面还没有任何元素,则取【默认初始化大小10】,【最小容量】两者较大的那个 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity;}
private void ensureExplicitCapacity(int minCapacity) { modCount++;
// 如果所需的最小容量大于数组现在的大小,则需要对数组进行扩充 if (minCapacity - elementData.length > 0) grow(minCapacity);}
//扩充数组大小private void grow(int minCapacity) { //旧数组大小 int oldCapacity = elementData.length; //新数组大小=旧数组大小+旧数组大小/2, 即每次扩充一半大小。 int newCapacity = oldCapacity + (oldCapacity >> 1); //增大一半之后的大小如果小于需要的最小容量,则直接取最小容量的大小。 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //判断新的容量有没有超过最大值,因为数组的下标是整形,而整形的最大值是Integer.MAX_VALUE, 所以数组有大小限制 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //创建一个新数组,并把原数组的内容复制过去 elementData = Arrays.copyOf(elementData, newCapacity);}

如果我们希望将元素添加到数组指定位置,则使用

list.add(1, "b");

同add(E e)方法一样,首先校验数组的大小是否够用,如果不够用则扩大数组。

接着,将index之后的元素,复制到index+1的位置,即index之后的元素每个向后移动一位。

最后在index位置添加元素:elementData[index] = element

该方法涉及到迁移元素,需要将index位置之后的元素往后移动一位,空出index位置来插入元素,所以效率会低点。频繁往数组中间插入元素的集合,应该使用LinkedList。

public void add(int index, E element) { rangeCheckForAdd(index);
//判断数组大小是否满足,不够则扩大数组 ensureCapacityInternal(size + 1); //index之后的元素,往后移动一位,空出index处的位置 System.arraycopy(elementData, index, elementData, index + 1,size - index); //将元素添加到index位置 elementData[index] = element; size++;}

添加元素之后,我们希望可以找出某个元素在数组中的位置,则使用

list.indexOf("b");

 
public int indexOf(Object o) { //遍历数组,逐个比较,找到相等的元素之后返回对应的下标 if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1;}

想要根据下标获取数组元素,则使用

list.get(i);    本质就是:   数组变量[index]

 
public E get(int index) { rangeCheck(index);
return elementData(index);}

E elementData(int index) { return (E) elementData[index];}

如果我们想删除某个元素,可以使用

list.remove("b");

同indexof方法,remove会先遍历数组,找到元素所在的位置index。然后将index之后的所有元素往前移动一位,这样子index位置就被覆盖了。再将最后那个多余的元素设置为null,size减1。

public boolean remove(Object o) { //遍历数组,一一对比找到元素所在位置index if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false;}
private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; //将数组index位置后面的元素往前移动一位 if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); //清空最后一位,并将数组大小减1 elementData[--size] = null; // clear to let GC do its work}

想要添加一个集合,使用

list.addAll();   

addAll会将要添加的元素复制到elementData最后一个元素之后。

 public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; //把旧数组大小跟新数组大小相加,校验新大小是否能满足存储,不满足则扩充数组 ensureCapacityInternal(size + numNew); // Increments modCount //将要添加的集合复制到原数组最后位置 System.arraycopy(a, 0, elementData, size, numNew); //变更size size += numNew; return numNew != 0; }

最后,我们可以使用clear来清空数组的元素

list.clear();

clear会遍历数组的每个元素,将每个元素设置为null,并将size置为0

public void clear() { modCount++;
// 遍历数组,将每个元素设置为null,并将size设为0 for (int i = 0; i < size; i++) elementData[i] = null;
size = 0;}


以上是关于简单易懂的ArrayList源码分析的主要内容,如果未能解决你的问题,请参考以下文章

史上最浅显易懂的RxJava入门教程

容器_JDK源码分析_自己简单实现ArrayList容器

ArrayList源码分析超详细

java集合系列之ArrayList源码分析

JAVA——底层源码阅读——集合ArrayList的实现底层源码分析

Java源码解读系列:ArrayList