ArrayList 底层实现原理

本文基于 JDK 11 讲解;©CopyRight 思思不羡仙;Date: 2021-6

1. 成员变量

  • private static final int DEFAULT_CAPACITY = 10; 构造函数数值缺省时默认大小

  • private static final Object[] EMPTY_ELEMENTDATA = {}; 当构造函数指出大小为0时将其赋值给 elemetData 数组

  • private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 当构造函数未指出大小时将其赋值给 elemetData 数组

  • transient Object[] elemetData; ArrayList 底层数据结构(指定不被序列化的对象数组)

  • private int size; 实际ArrayList中存放的元素的个数,默认为0个元素

  • private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE – 8; 对象数组的最大数组容量为 Integer.MAX_VALUE – 8

2. 构造方法

无参构造

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

此处将 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值给了 elementData,为何不使用 EMPTY_ELEMENTDATA ,原因在于后续为了区别第一次调用 add 方法进行数组扩增时的大小,后文将详细解释

有参构造

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);
	}
}

此处的 initialCapacity 为期望设置的 ArrayList 数组大小,由于是用户输入,故需要判断其值是否符合要求,若大于0则直接创建容量为 initialCapacity 的新数组,如果等于0将 EMPTY_ELEMENTDATA 赋值给 elementData ,若小于0则直接抛出异常

3. 添加元素

// 公有add方法
public boolean add(E e) {
	modCount++;
	add(e, elementData, size);
	return true;
}

// 私有add方法
private void add(E e, Object[] elementData, int s) {
	if (s == elementData.length){
        elementData = grow();
    }
	elementData[s] = e;
	size = s + 1;
}

​ 此处不难发现,用户调用公有add方法时主要完成了三件事,将 modCount 增加一(迭代时,将 expectedModCount 设为 modCount,若迭代过程中对集合进行了操作,易知 expectedModCount 与 modCount 数值不等,则会抛出 ConcurrentModificationException 异常),在私有 add 方法中,首先判断长度是否与实际大小相同,如果相同则调用 grow 方法进行扩增,否则将 elementData 最后一个空位置填入需要添加的元素,并将实际大小加一

4. 扩容机制

private Object[] grow(int minCapacity) {
	return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
}

private Object[] grow() {
	return grow(size + 1);
}

​ 书接上回,当实际大小与对象数组大小相同时调用扩增方法 grow 后,grow 方法会调用 newCapacity 方法进行扩增,并传入最小容量为 size + 1,并调用Arrays.copyOf 方法将原数组复制至扩增的新数组

private int newCapacity(int minCapacity) {
	int oldCapacity = elementData.length;
	int newCapacity = oldCapacity + (oldCapacity >> 1);
	if (newCapacity - minCapacity <= 0) {
       if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
           return Math.max(DEFAULT_CAPACITY, minCapacity);
       }  
       if (minCapacity < 0){
           throw new OutOfMemoryError();
       }      
       return minCapacity;
    }
    return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity);
}

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

说明:二进制下使用 >>(右移运算符)相当于十进制除以 2

此处的 newCapacity 方法将新容量设为(还未扩增)原对象数组长度的 1.5 倍后进行了三个判断,我们逐个说明:

  • 判断扩增是否足够?
    • 是:进行下一步操作
    • 否:是否超出最大值 MAX_ARRAY_SIZE,如果没有则直接赋值为所需容量,如果超出则调用 hugeCapacity 方法
  • 判断是否为初始化还未添加元素的数组?
    • 此处回答了为何需要单独设立两个空的对象数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATAEMPTY_ELEMENTDATA,如果 elementData 为前者的地址值,则说明是调用的无参构造方法,第一次扩增若放入的数据小于10,则直接扩增为10,反之扩增至所需容量最小值(此处不一定是 grow方法调用的该方法,也可以是一次添加多个元素的方法,故这样写提高通用性),如果是指定了大小的并且小于指定大小,则直接扩增至所需容量最小值
  • 判断最小容量是否小于 0 ?
    • 是:说明内存溢出了(请自行了解),抛出 OutOfMemoryError 异常
    • 否:返回最小容量

5. 查找元素

indexOf(Object o) 方法

public int indexOf(Object o) {
	return indexOfRange(o, 0, size);
}

int indexOfRange(Object o, int start, int end) {
	Object[] es = elementData;
	if (o == null) {
		for (int i = start; i < end; i++) {
			if (es[i] == null) {
				return i;
			}
        }
    } else {
		for (int i = start; i < end; i++) {
			if (o.equals(es[i])) {
				return i;
			}
		}
	}
    return -1;
}

此方法会调用 indexOfRange 方法来查找 Object o 的位置,分两种情况:

  1. o 为 null ,则直接与 null 比较
  2. o 不为 null ,则调用 o 的 equals 方法

直到找到后返回元素位置,否则返回 -1

get(int index) 方法

public E get(int index) {
	Objects.checkIndex(index, size);
    return elementData(index);
}

此方法返回指定 List 下标的对象,首先先检查传入 index 数值是否在 [0,size) 中(Objects.checkIndex 方法做的就是这件事),如果不抛出异常 IndexOutOfBoundsException 则返回数组的第 index 位元素

6. 删除元素

public E remove(int index) {
	Objects.checkIndex(index, size);
	final Object[] es = elementData;
	@SuppressWarnings("unchecked") E oldValue = (E) es[index];
	fastRemove(es, index);
	return oldValue;
}

private void fastRemove(Object[] es, int i) {
	modCount++;
	final int newSize;
	if ((newSize = size - 1) > i){
        System.arraycopy(es, i + 1, es, i, newSize - i);
    }
	es[size = newSize] = null;
}

执行 remove 方法时,首先检查下标是否正确,如果正确,则调用 fastRemove 方法进行删除操作,fastRemove 方法的思路就是将需要删除的后面的元素全部向前移动一个元素位置,然后将最后一个元素(等于前一个元素)赋值为 null

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值