自定义一个简单的ArrayList
主要功能
通过java代码实现一个简单的ArrayList数据结构,可以进行增删改查,以及迭代器实现.
代码实现
package com.example.springboot01.util;
import org.thymeleaf.util.StringUtils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class MyArrayList<T> implements Iterable<T>{
// 容量
private int capacity = 3;
// 使用数组实现
private T[] arr = (T[])new Object[capacity];
// 元素个数
private int index = 0;
/**
* 新增
* @param t
*/
public void add(T t) {
if(index + 1 > capacity) { // 动态扩容
capacity = capacity * 2;
T[] arr2 = (T[])new Object[capacity];
for(int i=0; i<size(); i++) {
arr2[i] = arr[i];
}
arr = arr2;
}
arr[index] = t;
index++;
}
/**
* 查询
* @param i
* @return
*/
public T get(int i) {
return arr[i];
}
/**
* 修改
* @param l
* @param t
*/
public void set(int l, T t) {
if(l + 1 > size()) {
throw new ArrayIndexOutOfBoundsException("Index: " + l + ", Size: " + size());
}
else {
arr[l] = t;
}
}
/**
* 删除指定元素
* @param t
*/
public void remove(T t) {
int flag = -1;
for(int i=0; i < size(); i++) {
if(t instanceof String) {
if(StringUtils.equals(arr[i], t)) {
flag = i;
break;
}
}
else if(t instanceof Integer) {
if(arr[i] == t) {
flag = i;
break;
}
}
}
if(flag > -1) {
for(int i=flag; i < size(); i++) {
arr[i] = arr[i+1];
}
index--;
}
}
/**
* 按索引删除
* @param flag
*/
public void remove(int flag) {
if(flag < 0 || flag > size() - 1) {
throw new IndexOutOfBoundsException("Index: " + flag + ", Size: " + size());
}
else {
for(int i=flag; i < size(); i++) {
arr[i] = arr[i+1];
}
index--;
}
}
/**
* 获取元素个数
* @return
*/
public int size() {
return index;
}
/**
* 返回自定义的迭代器
* @return
*/
@Override
public Iterator<T> iterator() {
return new Itr();
}
/**
* 迭代器实现
*/
private class Itr implements Iterator<T> {
// 指针位置,初始值为-1,在0的前面,后面每次调用next()方法,往后移动一位
private int cursor = -1;
@Override
public boolean hasNext() {
if(cursor + 1 < size()) {
return true;
}
else {
return false;
}
}
@Override
public T next() {
// 这边缺少一个判断,cursor是否越界
return arr[++cursor];
}
@Override
public void remove() {
if(cursor < 0 || cursor > size() - 1) {
throw new IndexOutOfBoundsException("Index: " + cursor + ", Size: " + size());
}
else {
for(int i=cursor; i < size(); i++) {
arr[i] = arr[i+1];
}
cursor--; // 这是灵魂所在,在删除了一个元素之后,后面的元素会向前补位,所以指针也应该同步向前一步走,不然就有漏网之鱼
index--; // 容器中元素数量减一
}
}
}
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
for(int i=0; i<list.size(); i++) {
if("7".equals(list.get(i)) == true) {
list.remove(i);
i--;
}
//System.out.println("ArrayList: " + list.get(i));
}
for(int i=0; i<list.size(); i++) {
System.out.println("ArrayList: " + list.get(i));
}
MyArrayList<String> list2 = new MyArrayList<String>();
//System.out.println("capacity: " + list2.size());
list2.add("1");
list2.add("2");
list2.add("3");
list2.add("4");
//System.out.println("capacity: " + list2.size());
/* list2.add("4");
list2.add("5");
list2.add("6");
list2.add("7");*/
//System.out.println(list2.size());
//list2.remove("5");
//System.out.println(list2.size());
//list2.add("5");
//System.out.println(list2.size());
//list2.remove(0);
//System.out.println(list2.size());
Iterator<String> iterator = list2.iterator();
System.out.println(iterator.next());
System.out.println(iterator.next());
System.out.println(iterator.next());
for(int i=0; i<list2.size(); i++) {
if("7".equals(list2.get(i)) == false) {
list2.remove(i);
i--;
}
//System.out.println("MyArrayList: " + list2.get(i));
}
/* while(iterator.hasNext()) {
String item = iterator.next();
if("7".equals(item) == false) {
iterator.remove();
}
}*/
for(int i=0; i<list2.size(); i++) {
System.out.println("MyArrayList: " + list2.get(i));
}
}
}
性能测试
这边的性能测试权作参考,没有实用价值.GC工作时,会影响到时间的计算,我这边把GC设置为G1收集器,这样每次GC的时间都差不多,也很短,使用飞行记录jmc.exe测试平均100ms左右.如果使用jdk1.8默认的Parallel Scavenge + Parallel Old收集器,GC时间有长有短,长的将近3秒,短的30ms.
扩容倍数设置为2时,插入并查询一百万条数据大概耗时100ms,一千万条耗时800ms;
扩容倍数设置为3时,一百万条数据耗时50ms,一千万条耗时600ms.
扩容倍数设置为4时,一百万条数据耗时60ms,一千万条500ms
扩容倍数设置为10时,一千万调数据耗时400ms
扩容倍数设置为20时,一千万调数据耗时450ms
扩容倍数设置为100时,一千万调数据耗时450ms
扩容倍数再调大,耗时都没有明显减少,调整数组初始容量对耗时几乎没有影响,所以如果需要性能调优,可以适当减少扩容次数.
代码优化
package com.example.springboot01.util;
import org.junit.Test;
public class MyArrayList<T> {
// 容量
private int capacity = 10;
// 使用数组实现
private T[] arr = (T[])new Object[capacity];
// 元素个数
private int size = 0;
/**
* 新增
* @param t
*/
public void add(T t) {
grow();
arr[size++] = t;
}
/**
* 按索引删除
* @param flag
*/
public void remove(int flag) {
validIndex(flag);
// 可用System.arraycopy()代替,测试发现两者差不多
for(int i = flag; i < size() - 1; i++) {
arr[i] = arr[i+1];
}
// 容器中元素数量减一,并将最后位置的值置位null,以便GC回收
arr[--size] = null;
}
/**
* 删除指定元素
* @param t 指定元素
*/
public void remove(T t) {
int flag = -1;
for(int i=0; i < size(); i++) {
if(t instanceof String) {
if(equals(arr[i], t)) {
flag = i;
break;
}
}
else if(t instanceof Integer) {
if(arr[i] == t) {
flag = i;
break;
}
}
}
remove(flag);
}
/**
* 动态扩容
*/
public void grow() {
// 动态扩容,每次增加原先的一半容量
if((size + 1) > capacity) {
capacity = capacity + (capacity >> 1);
//capacity *= 3;
T[] arr2 = (T[])new Object[capacity];
// 可用System.arraycopy()替代,没有直接for循环快
for(int i=0; i<size(); i++) {
arr2[i] = arr[i];
}
arr = arr2;
}
}
/**
* 修改
* @param i 索引
* @param t 值
*/
public void set(int i, T t) {
validIndex(i);
arr[i] = t;
}
/**
* 查询
* @param i 索引
* @return 返回指定下标的元素
*/
public T get(int i) {
validIndex(i);
return arr[i];
}
/**
* 获取元素个数
* @return 容器中元素的个数
*/
public int size() {
return size;
}
/**
* 校验输入的索引
* @param i 索引id
*/
public void validIndex(int i) {
if(i < 0 || i > (size() - 1)) {
throw new ArrayIndexOutOfBoundsException(indexOutOfBoundsMsg(i));
}
}
/**
* 索引超出范围提示信息
* @param i 索引
* @return 错误提示
*/
public String indexOutOfBoundsMsg(int i) {
return "Index: " + i + ", Size: " + size();
}
/**
* 比较两个字符串是否相等
* @param first 第一个参数
* @param second 第二个参数
* @return 是否相等
*/
public boolean equals(Object first, Object second) {
if(first == null && second == null) {
return true;
}
else {
return (first == null || second == null)?false:first.toString().equals(second.toString());
}
}
/**
* 返回自定义的迭代器
* @return Itr对象
*/
//@Override
public Itr<T> iterator() {
return new Itr<T>();
}
/**
* 迭代器实现
*/
private class Itr<E> {
// 指针位置,初始值为-1,在0的前面,后面每次调用next()方法,往后移动一位
private int cursor = -1;
//@Override
public boolean hasNext() {
return (cursor + 1) < size();
}
//@Override
public E next() {
MyArrayList.this.validIndex(++cursor);
return (E)arr[cursor];
}
//@Override
public void remove() {
MyArrayList.this.validIndex(cursor);
// cursor--,这是灵魂所在,在删除了一个元素之后,后面的元素会向前补位,所以指针也应该同步向前一步走,不然就有漏网之鱼
MyArrayList.this.remove(cursor--);
}
}
@Test
public void test() {
long startTime = System.currentTimeMillis();
MyArrayList<Integer> list2 = new MyArrayList<>();
for(int i=1; i< 10000000; i++) {
list2.add(i);
}
System.out.println("cost1: " + (System.currentTimeMillis() - startTime) + "ms");
// 因为是内部类,所以引用时,需要在前面加上外部类
MyArrayList<Integer>.Itr<Integer> iterator = list2.iterator();
System.out.println("cost2: " + (System.currentTimeMillis() - startTime) + "ms");
while(iterator.hasNext()) {
Integer item = iterator.next();
/*if(7 != item) {
iterator.remove();
}*/
}
/*for(int i=0; i<list2.size(); i++) {
System.out.println("MyArrayList: " + list2.get(i));
}*/
System.out.println("cost3: " + (System.currentTimeMillis() - startTime) + "ms");
}
}
总结
1.如下声明泛型数组是不行的,会报Type parameter ‘T’ cannot be instantiated directly错误
private T[] arr = new T[capacity];
需要这样强制转换一下
private T[] arr = (T[])new Object[capacity];
2.数组的动态扩容是通过新建一个数组来进行的,然后拷贝元素,最后将新数组的引用地址赋值给老数组
arr = arr2;
3.删除指定元素前,需要对元素类型进行判断,才好进行下一步的匹配,删除元素后,后面的元素自动向前补位,容器中元素总数减一.
4.迭代器的实现是通过内部类Itr实现Iterator接口,并重写里面的hasNext(),next(),remove()等方法实现的.
5.迭代器里面的remove()方法成功执行后,需要将指针位置向前移动一位,不然会漏掉刚补位到前面的元素.
6.创建数组后,如果不赋值,虚拟机会自动初始化为对应类型的零值.如String的零值是null,int的零值是0等.
7.ArrayList初始容量是10,每次扩容增加原先容量的一半,即10=>15=>22=>33=>49=>73=>109这样.如果你要存储100个数据,ArrayList会扩容6次.
8.可以不实现Iterator接口,只是使用内部类Itr时需要在前面加上外部类MyArrayList.
9.性能测试和ArrayList差不多,甚至还要快一点.