一.ArrayList
先来看看ArrayList这个集合, 一般对ArrayList的理解是这样的
单列集合, 有序 可重复, 底层采用数组的方式实现, 可扩容
我们来解析ArrayList的源码
- 先看看ArrayList的成员变量
在ArrayList中大概存在5-6个成员变量, 但是对我们来说有用的就这两个
1.Object[] elementData 数组存储数据
2.int size 标识ArrayList大小(集合包含的元素个数)
由于ArrayList的源码过于复杂, 我们模拟一份代码, 便于理解
public class Demo_ArrayList<E> {
// 以Object数组的形式存储数据
private Object[] elementData;
// 标识Demo_ArrayList的长度
private int size;
}
- 然后看一下ArrayList的构造方法
空参构造仅仅做了一件事情
给elementData变量进行初始化
初始长度为0(不是10, 第一次扩容才是10)
int类型参数的构造比空参构造要复杂一些
还是给elementData变量进行初始化
不过初始长度根据参数创建
参数小于0就会抛出异常
Collection类型参数就更为复杂了
他的目的是将单列集合参数c复制一份, 然后转化成数组赋值给elementData变量
然后来生成模拟代码
Collection类型参数的构造比较复杂, 就不在模拟
public class Demo_ArrayList<E> {
// 以Object数组的形式存储数据
private Object[] elementData;
// 标识Demo_ArrayList的长度
private int size;
// 空参构造
public Demo_ArrayList() {
elementData = new Object[]{};
}
// int参数构造, 按照initSize的大小初始化elementData
public Demo_ArrayList(int initSize) {
if (initSize >= 0) {
elementData = new Object[initSize];
} else {
throw new IllegalArgumentException("非法容量, 容量不能小于0");
}
}
}
- 然后看一下ArrayList的add方法
这个是最常用的add方法,他由三行代码组成
第一行是调用扩容方法, 如果(size+1) 的大小满足扩容条件, 就会自动扩容(扩容后面再详细理解)
第二行就是为数组elementData赋值, 注意(size++)会在本行代码执行完成之后, 再进行自增, 所以参数e赋值到了elementData[size]上, 然后才会使长度加一
第三行表示添加成功了, 失败就会抛异常了
这个是向指定位置插入数据的add方法,他由五行代码组成
第一行是判断插入位置是否满足条件, 满足(index > size || index < 0)就会抛异常
第二行是调用扩容方法
第三行比较复杂, 他是使用System.arraycopy()方法将elementData数组从index位置分割开, 然后将后半段右移一位再覆盖回去, 这样elementData[index]位置便会空出来, 再给新添加的数据element赋值
这里可能不太好理解, 所以举个栗子
- 开始elementData数组是这样的 [1, 2, 3, 4, 5, null, null, null, null, null]
- 再索引为2处添加数据为10
- 然后elementData数组是就会这样 [1, 2, null, 3, 4, 5, null, null, null, null]
- 然后elementData数组赋值, [1, 2, 10, 3, 4, 5, null, null, null, null]
然后继续
第四行就是给elementData的index位置赋值
第五行是表示集合长度的size+1
模拟代码
import java.util.Arrays;
public class Demo_ArrayList<E> {
// 以Object数组的形式存储数据
private Object[] elementData;
// 标识Demo_ArrayList的长度
private int size;
// 空参构造
public Demo_ArrayList() {
elementData = new Object[]{};
}
// int参数构造, 按照initSize的大小初始化elementData
public Demo_ArrayList(int initSize) {
if (initSize >= 0) {
elementData = new Object[initSize];
} else {
throw new IllegalArgumentException("非法容量, 容量不能小于0");
}
}
public boolean add(E e){
// 扩容
grow(size + 1);
// 赋值
elementData[size++] = e;
return true;
}
public void add(int index, E e) {
// 判断
if (index > size || index < 0)
throw new IndexOutOfBoundsException("非法索引");
// 扩容
grow(size + 1);
// 从index位置向后平移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 赋值
elementData[index] = e;
// 长度加一
size ++;
}
//一会再写
private void grow(int i) {}
@Override
public String toString() {
return Arrays.toString(Arrays.copyOf(elementData,size));
}
}
下面来简单说说扩容机制的思路
首先点开ArrayList源码中add方法所调用的那个方法ensureCapacityInternal
他调用了ensureExplicitCapacity()方法
这两个方法就是为了判断是否扩容, 和minCapacity参数是否满足条件
grow()方法是正式扩容的方法
他会将老的容量elementData.length 进行如下算法扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
使用了位移运算符右移(容量的变化大概是0, 10, 15, 22)
4.然后看一下ArrayList的get方法
get方法十分简单
第一行判断index索引是否越界(index >= size)
第二行获取elementData[index]的值, 然后按照泛型强转, 返回
4.然后看一下ArrayList的set方法
set方法也不复杂
第一行还是判断index索引是否越界(index >= size)
第二行通过获取到旧的值按照泛型强转之后, 临时保存
第三行修改elementData[index]的值
第四行返回旧的值(为啥要返回旧的值呢?)
然后生成模拟代码
import java.util.Arrays;
public class Demo_ArrayList<E> {
// 以Object数组的形式存储数据
private Object[] elementData;
// 标识Demo_ArrayList的长度
private int size;
// 空参构造
public Demo_ArrayList() {
elementData = new Object[]{};
}
// int参数构造, 按照initSize的大小初始化elementData
public Demo_ArrayList(int initSize) {
if (initSize >= 0) {
elementData = new Object[initSize];
} else {
throw new IllegalArgumentException("非法容量, 容量不能小于0");
}
}
public boolean add(E e){
// 扩容
grow(size + 1);
// 赋值
elementData[size++] = e;
return true;
}
public void add(int index, E e) {
// 判断
if (index > size || index < 0)
throw new IndexOutOfBoundsException("索引越界");
// 扩容
grow(size + 1);
// 从index位置向后平移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 赋值
elementData[index] = e;
// 长度加一
size ++;
}
// 获取值
public E get(int index){
if (index >= size)
throw new IndexOutOfBoundsException("索引越界");
return (E)elementData[index];
}
// 设置值
public E set(int index, E e){
if (index >= size)
throw new IndexOutOfBoundsException("索引越界");
E oldValue = (E) elementData[index];
elementData[index] = e;
return oldValue;
}
// 扩容
private void grow(int minCapacity) {
// 判断是否满足条件
if (minCapacity > elementData.length){
// 如果预计的长度小于10, 则扩容到10
if (minCapacity < 10){
minCapacity = 10;
}
// 老的容量
int oldCapacity = elementData.length;
// 新的容量
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 保证新的容量大于预计长度
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
@Override
public String toString() {
return Arrays.toString(Arrays.copyOf(elementData,size));
}
}
5.然后看一下ArrayList的remove方法
通过索引删除数据, 有点复杂
第一行依旧是判断index索引是否越界(index >= size)
第二行modCount++ 不知道是做什么的
第三行依旧是获取对应index的老数据
第四行是计算出要截取的长度 = 集合长度 - (索引+ 1), 由于索引是从0开始的, 而集合长度size从1开始才有意义
第五行不太好理解, 他是使用System.arraycopy()方法将elementData数组从index + 1位置分割开, 然后将后半段左移一位再覆盖回去, 由于是先复制再覆盖的, 所以ArrayList的最后一个值是多余的
所以在第六行, 现将size减一, 然后在将ArrayList的最后一个值设置为null
最后一行, 返回老的值
同样举个栗子
- 开始elementData数组是这样的 [1, 2, 3, 4, 5, null, null, null, null, null]
- 再索引为2处删除数据
- 然后elementData数组是就会这样 [1, 2, 4, 5, 5, null, null, null, null, null]
- 然后删除最后一个, [1, 2, 4, 5, null, null, null, null, null, null]
通过对象删除数据, 思路就是遍历elementData数组, 匹配参数对应的索引, 然后再通过索引删除
其中参数ken可能为null, 但是ArrayList可以存储null, 所以必须分开处理
其中匹配成功调用的**fastRemove()**方法与上面的remove方法思路一致, 只不过是私有的(为啥要再写一遍一样的方法呢?)
最终模拟代码
import java.util.Arrays;
public class Demo_ArrayList<E> {
// 以Object数组的形式存储数据
private Object[] elementData;
// 标识Demo_ArrayList的长度
private int size;
// 空参构造
public Demo_ArrayList() {
elementData = new Object[]{};
}
// int参数构造, 按照initSize的大小初始化elementData
public Demo_ArrayList(int initSize) {
if (initSize >= 0) {
elementData = new Object[initSize];
} else {
throw new IllegalArgumentException("非法容量, 容量不能小于0");
}
}
public boolean add(E e){
// 扩容
grow(size + 1);
// 赋值
elementData[size++] = e;
return true;
}
public void add(int index, E e) {
// 判断
if (index > size || index < 0)
throw new IndexOutOfBoundsException("索引越界");
// 扩容
grow(size + 1);
// 从index位置向后平移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 赋值
elementData[index] = e;
// 长度加一
size ++;
}
// 获取值
public E get(int index){
if (index >= size)
throw new IndexOutOfBoundsException("索引越界");
return (E)elementData[index];
}
// 设置值
public E set(int index, E e){
if (index >= size)
throw new IndexOutOfBoundsException("索引越界");
E oldValue = (E) elementData[index];
elementData[index] = e;
return oldValue;
}
// 通过索引删除
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException("索引越界");
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
return oldValue;
}
// 通过对象删除
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
remove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
remove(index);
return true;
}
}
return false;
}
// 获取大小
public int size() {
return size;
}
// 扩容
private void grow(int minCapacity) {
// 判断是否满足条件
if (minCapacity > elementData.length){
// 如果预计的长度小于10, 则扩容到10
if (minCapacity < 10){
minCapacity = 10;
}
// 老的容量
int oldCapacity = elementData.length;
// 新的容量
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 保证新的容量大于预计长度
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
@Override
public String toString() {
return Arrays.toString(Arrays.copyOf(elementData,size));
}
}
测试代码
public static void main(String[] args) {
Demo_ArrayList<String> list = new Demo_ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
System.out.println(list.size() +":" + list);
list.set(2, "2");
list.set(4, "4");
System.out.println(list.size() +":" + list);
list.remove(1);
System.out.println(list.size() +":" + list);
list.remove("d");
System.out.println(list.size() +":" + list);
for (int i = 0; i < list.size(); i++){
System.out.println(list.get(i));
}
}
如果你已经看到这里, 那说明已经对ArrayList的的理解入门了