- 底层实现是数组(没啥好说的这个都知道)
成员变量
/**
* 默认容量大小
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空数组,如果传入的容量为0时使用
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 空数组,传传入容量时使用,添加第一个元素的时候会重新初始为默认容量大小
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储元素的数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* ArrayList的最大值
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 集合中元素的个数
*/
private int size;
构造方法
/**
* 带有初始容量的构造方法
*/
public ArrayList(int initialCapacity) {
// 按照初始值创建集合的大小
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
// 传入0用null的
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
/**
* 直接创建原来的数组
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 传入集合的
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
// 如果不是null数组
if ((size = elementData.length) != 0) {
// 检查c.toArray()返回的是不是Object[]类型,如果不是,重新拷贝成Object[].class类型 这就代表泛型没啥用
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
//如果是null数组
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
添加元素
/**
* 添加元素
*/
public boolean add(E e) {
// 修改次数加一 用于快速失败机制 什么是快速失败
modCount++;
// 添加元素
add(e, elementData, size);
return true;
}
/**
* 添加元素
* e:要添加的元素
* elementData:当前存放的数组的大小
* s:当前数组的长度
*/
private void add(E e, Object[] elementData, int s) {
// 如果当前数size的大小等于数组的长度
if (s == elementData.length)
// 扩容
elementData = grow();
// 设置size的位置是当前元素
elementData[s] = e;
//把size+1
size = s + 1;
}
- 总结:
- (1) 如果当前size的大小等于数组的长度 进行扩容处理
- (2) 并且设置,并且赋值 数组[数组当前长度] = 要添加的值 (巧妙运用了数组的长度 跟 坐标的关系)
- (3) 把当前size值减1
按照指定位置添加元素
public void add(int index, E element) {
/*越界检查*/
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
/*检查是否扩容*/
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
/*将index及其之后的元素往后挪一位,则index位置处就空出来了*/
System.arraycopy(elementData, index, elementData, index + 1, s - index);
/*设置当前index的元素*/
elementData[index] = element;
size = s + 1;
}
好玩的来了嘿嘿!角标越界方法
/**
* 越界检查
*/
private void rangeCheckForAdd(int index) {
/*size 是当前元素的数量 也就是说按照索引设置值
最多只能放到已经有的元素个数的位置*/
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
- 也就是说按照索引存放元素的时候 下标只能取小于等于元素数量的值(也就是) 设置的下标一定要小于等于size否则会报角标越界异常算不算是一个坑呢?哈哈
- 例子1
- 运行结果
扩容grow()
/**
* 扩容往下走
* @return
*/
private Object[] grow() {
// 扩容当前size+1
return grow(size + 1);
}
- 总结:
- 因为要增加一个元素,所以说当前数组的长度+1(这个时候还没增加)
/**
* 扩容往下走
* @param minCapacity
* @return
*/
private Object[] grow(int minCapacity) {
// Arrays.copyOf 创建新数组
return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
}
- 这里运用了Arrays.copyOf方法copy出一个新的数组 并且通过 newCapacity(minCapacity) 来计算新数组的长度
/**
* 真正扩容的方法
* @return
*/
private int newCapacity(int minCapacity) {
// 老的数组的长度
int oldCapacity = elementData.length;
// 薪的数组的长度=老数组的长度+老数组的长度/2
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果扩容之后还是不满足 什么情况之下会不足?
// 第一种 初始化 oldCapacity 是0
// 第二种 putAll 放了一个集合但是它放置的长度比较大
if (newCapacity - minCapacity <= 0) {
// 如果数组没有被初始化过
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// 返回默认容量和要扩充的空间中的的最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 如果不是初始化 而且容量不足 那么就返回他要扩容的这个大小
// 1 记住去了问面试官这个问题 这块就证明 Arraylist的集合的容量 不一定是5的倍数
return minCapacity;
}
//返回薪数组的长度
return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity :
hugeCapacity(minCapacity);
}
- 总结
- (1) 新数组的长度=老数组的长度+老数组的长度/2 也就是扩容1.5倍
- (2) 如果扩容之后还是不满足 什么情况之下会不足?
- 第一种 初始化 oldCapacity 是0或者putAll 放了一个集合但是它放置的长度比较大
- (2-1)如果没经过初始化 这里分放置一个元素 和放置多个元素(putAll) 所以就取(默认容量10 与 要扩容的大小的最大值) 返回这个长度作为新数组的长度
- (2-2)如果经历了初始化但是扩容之后的容量还是放不下,那么就取这个最大值 ,返回这个长度作为新数组的长度
- 这里就表明虽然说默认是10 扩容为1.5 倍 但是容量不一定是5的倍数 因为putAll操作存在扩容完毕之后存不下直 接取当前要扩容的值的情况
- (3)扩容之后能满足,返回扩容之后的容量作为新数组的长度
一次性添加多个元素addAll()方法
/**
* 一次性添加多个元素
* @param c
* @return
*/
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
modCount++;
// 取出薪数组的长度
int numNew = a.length;
if (numNew == 0)
return false;
Object[] elementData;
final int s;
// 如果要放置的元素 大于目前数组还闲置的位置
if (numNew > (elementData = this.elementData).length - (s = size))
// 扩容
elementData = grow(s + numNew);
// arraycopy 创建新数组
System.arraycopy(a, 0, elementData, s, numNew);
// 设置当前元素的数量
size = s + numNew;
return true;
}
- 跟put方法差不多有差别的地方有三点
- (1) 根据 如果要放置的元素 大于目前数组还闲置的位置来判断是够扩容
- (2) 扩容的数量不一致
- (3) 多了用 System.arraycopy copy的是两个数组来放置要放置的元素集
通过索引获取元素
/**
* 通过索引获取元素中的位置
*/
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
/**
* 通过索引获取元素中的位置
*/
public E get(int index) {
return elementData(index);
}
/**
* 按照索引位置返回旧值
*/
public E set(int index, E element) {
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
- 比较简单没啥可说的
通过索引删除元素
/**
* 按照索引删除的话就直接删除就好了 并且返回旧的值
*/
public E remove(int index) {
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;
// 如果index不是最后一位 则将index之后的元素向前挪一位
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
// 将最后一个元素至null
es[size = newSize] = null;
}
- 总结:删除元素的核心就是 System.arraycopy(es, i + 1, es, i, newSize - i);的删除操作
- 如果index不是最后一位 则将index之后的所有元素向前挪一位,如果是最后一位直接置null
通过元素删除集合中元素
/**
* 按照元素进行删除操作
*/
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
/**
* 循环遍历确定元素索引的位置
*/
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
/**
* 按照索引去删除元素
*/
fastRemove(es, i);
return true;
}
- 总结:
- (1) 循环遍历确定索引的位置
- (2) 再通过索引去删除元素
关于ArrayList的一些总结
- (1)按照索引位置进行访问效率很高
- (2)添加元素的效率还算可以(扩容采用copyof效率也还可以)
- (3)按照索引删除元素效率比较低 因为要移动元素
- (4) 按照元素内容删除元素效率更低,因为要确定索引的位置,然后再通过索引删除元素,之后再移动元素
- (5) 按照索引位置添加元素,效率比较低因为要移动元素,还tmmp的有个坑
一声坏笑下次我要问面试官俩问题
- 既然默认的容量是10,每次扩容按照1.5 ,那集合容量第一次扩容的大小就一定是5的倍数15嘛?
-
ArrayList<Integer> arrrrayList = new ArrayList(); arrrrayList.add(1,1);会发生啥?