arrayList 底层其实就是数组。今天有时间手写一个arrayList 加深对集合的理解。废话不多说,直接手写:
1.定义集合接口泛型:MyArrayListInterface
package collection.list;
/**
* 定义接口泛型
*
* @param <E>
*/
public interface MyArrayListInterface<E> {
/**
* 新增
*/
boolean add(E object);
/**
* 根据下标新增
*/
boolean add(int index, E object);
/**
* 更新下标
*/
boolean set(int index ,E object);
/**
* 根据下标删除
*/
Object remove(int index);
/**
* 集合元素大小
*/
int size();
/**
* 根据下标查询元素
*/
E get(int index);
/**
*
* 判断集合是否为空
*/
boolean isEmpty();
}
2.实现这个集合接口:MyArrayList
package collection.list;
import java.util.Arrays;
/**
* 手写arrayList
* 底层是数组实现的,线程不安全
*/
public class MyArrayList<E> implements MyArrayListInterface<E> {
//底层数组,来存放数据
private Object[] elementData;
//默认数组容量10
private static final int DEFAULT_CAPACITY = 10;
//记录实际ArrayList大小,这里大小其实是"有数据"的组数大小
private int size;
//默认初始化容量为10
public MyArrayList() {
this(DEFAULT_CAPACITY);
}
//初始化new 构建的时候,ArryList指定数组初始的容量
public MyArrayList(int initialCapacity) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("初始化容量不能小于0");
}
//数据大小初始化
elementData = new Object[initialCapacity];
}
/**
* 添加集合元素
*
* @param object
* @return
*/
public boolean add(E object) {
//添加元素的时候,先要检查一下。数组实际存放元素大小是否大于数组定义的大小,如果是就扩容
ensureCapacityInternal(size + 1);
//赋值
elementData[size++] = object;
return true;
}
/**
* 检查元素大小,是否需要扩容
*
* @param minCapacity
* @return
*/
public void ensureCapacityInternal(int minCapacity) {
//实际元素大小>= 定义的数组大小,这个时候需要扩容,每次扩容为之前的1.5倍大小。
if (size >= elementData.length) {
int oldCapcity = elementData.length;
int newCapcity = oldCapcity + oldCapcity >> 1;//1+ 1/2
if (newCapcity < minCapacity) { //如果扩容定义容量之后,还是小于实际要存放的容量。即扩容容量为实际容量
newCapcity = minCapacity;
}
elementData = Arrays.copyOf(elementData, newCapcity);//将老数组copy出一个新数组。newCapcity 是复制长度
}
}
/**
* 指定位置插入元素,位置索引必须有实际用过的元素
*
* @param index
* @param object
* @return
*/
public boolean add(int index, E object) {//index 一定是实际元素的index
rangeCheck(index);//检查数组索引是否小标越界
ensureCapacityInternal(size + 1);//是否需要扩容
//各个参数说明:
// 1.数组1 2.源数组1的开始复制索引位置 3.目标数组2 4.目标数组的开始被覆盖的索引位置 5.源数组复制长度
//注意
// 源的起始位置+长度不能超过末尾
// 目标起始位置+长度不能超过末尾
// 且所有的参数不能为负数
//实际就是复制一个新数组,中间留个空位置,给将要插入的原始
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = object;
return true;
}
/**
* 根据下标替换更新
*
* @param index
* @param object
* @return
*/
public boolean set(int index, E object) {
rangeCheck(index);//检查数组索引是否小标越界
elementData[index] = object;
return true;
}
/**
* 删除元素
*
* @param index
* @return
*/
public E remove(int index) {
//下标是否越界
rangeCheck(index);
//1.先查询一下要删除的元素
E e = get(index);
//删除元素
//先计算要删除元素的index 后面的元素长度
int numMoved = size - index - 1;
if (numMoved > 0) { //numMoved = 0 说明移除的就是最后一个元素
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
//将最后一个元素为为空,并将长度-1
elementData[--size] = null;//只能remove 能让元素变成未使用状态
return e;
}
/**
* 返回元素大小
*
* @return
*/
public int size() {
return size;
}
/**
* 根据下标取元素
*
* @param index
* @return
*/
public E get(int index) {
return (E) elementData[index];
}
/**
* 检查索引是否下标越界
*/
public void rangeCheck(int index) {
if (index > size || index < 0) { //说明这个index 只能对有数据(元素用过)的index 进行指定插入。
throw new IndexOutOfBoundsException("下标越界了");
}
}
/**
* 判断元素是否为空
*/
public boolean isEmpty() {
return size == 0;
}
}
3.重点总结
一、2个重要的方法:
- Arrays.copyOf(elementData, newCapcity)
- System.arraycopy(elementData, index, elementData, index + 1, size - index);
用于数组扩容时和数组的移动,数组复制。
二、新增和删除的逻辑:
1.新增:先判断是否越界下标 --> 再判断是否需要扩容 --> 再移动数组实现复制。
2.删除:先判断是否越界下标–>再移动数组实现复制–>将元素变空。
三、需要注意的点:
1.index 是实际用过的数组元素的下标
2.size 是实际用过的数组元素的大小