手写泛型设计的动态数组并进行原理分析
在每一个编程语言里,都离不开数组这个最基本的数据结构,那么今天我就来给大家从简单编写一个自己的动态数组到每一步的思维分析,进入一个奇妙的数据结构世界。
动态数组的设计与逻辑分析
首先对于整个初始设计进行阐述:1、设计成泛型类;2、两个必有私有属性:①接收T类型的数组data;②记录数组data大小的size,方便容量控制以及扩容;3、两个构造方法,一个是默认构造方法(复用有一个形参的构造方法),初始容量是10,由于泛型java不支持直接设置为数组,所以需要创建为object[],在进行转换T[];4、获取数组中以存放元素个数方法getSize() ;5、获取数组容量getCapacity();6、返回数组是否为空 isEmpty()
public class ArrayDemo<T> {
private T[] data;
private int size;
//构造函数,传入数组的容量capacity构造Array
public ArrayDemo(int capacity) {
data = (T[]) new Object[capacity];
size = 0;
}
//对原有构造方法进行复用,默认数组大小capacity=10
public ArrayDemo() {
this(10);
}
//获取数组中以存放元素个数
public int getSize() {
return size;
}
//获取数组容量
public int getCapacity() {
return data.length;
}
//返回数组是否为空
public boolean isEmpty() {
return size == 0;
}
}
ArrayDemo增加添加方法并进行深入分析
//在第index个位置插入一个新元素e
public void add(int index, T e) {
//校验index
rangeCheckForAdd(index);
//检测是否超过容限,并进行扩容
ensureCapacity();
//将index下标后面的元素到每一个都赋值下一个位置上
for (int i = size - 1; i >= index; i--)
data[i + 1] = data[i];
data[index] = e;
size++;
}
private void ensureCapacity() {
if (size == data.length)
resize(2 * data.length);
}
private void resize(int newCapacity) {
T[] newData = (T[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
private void rangeCheckForAdd(int index) {
if (index < 0 || index > size)
throw new IllegalArgumentException("AddList failed,Require index >=0 and index <=size");
}
首先向data容器中添加元素,需要先判断index下标是否合格,index不能小于0且不能大于size大小,必须在0到size之间,有问题抛出异常;之后没有问题进行容限校验,如果data数组已经满了,就进行扩容,扩容的大小是原来得两倍,将原来的元素放入新的容器,并将data指向新的数组地址;
之后就是将index下标后面的元素到每一个都赋值下一个位置上,相应indexw位置的元素也会后移,当然index位置还有原来的元素,所以,循环条件for (int i = size - 1; i >= index; i–),(目前是单向链表形式)初始条件为数组的最后一值开始,判断条件为i=index为止,从index下表开始的元素全部后移一个位置,过程如图:
之后就是值赋值到data[index]位置,以及size++;当然下标为1的位置在没有放入77之前也是有值的,为88,只是这个值已经安全的进行和后移,所以77只是将下标为1位置的88覆盖了而已。
ArrayDemo增加get和set方法以及find(查找元素所在的位置)、contains(数组是否包含元素)
//数组是否包含元素
public boolean contains(T e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e))
return true;
}
return false;
}
//查找元素所在的位置
public int find(T e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e))
return i;
}
return -1;
}
//获取index索引位置的元素
public T get(int index) {
//校验index
rangeCheckForAdd(index);
return data[index];
}
public void set(int index, T e) {
//校验index
rangeCheckForAdd(index);
data[index] = e;
}
get方法以及set方法基本都是对于下标进行判断,没有问题进行获取返回或者是对下标位置的值进行覆盖;contains是否包含元素和find查找元素所在的位置都是进行循环遍历找到对应的元素,返回boolean值或者是下标位置,当然这个比较的时候使用的equels方法,可能有缺陷,有待优化。
ArrayDemo增加删除方法remove、removeFirst、removeLast、removeElement
首先1、分析remove方法,代码如下
//从数组中删除index位置的元素,返回删除的元素
public T remove(int index) {
//校验index
rangeCheckForAdd(index);
T ret = (T) data[index];
for (int i = index + 1; i < size; i++) {
data[i - 1] = data[i];
}
size--;
data[size] = null;
if (size == data.length / 2) {
resize(data.length / 2);
}
return ret;
}
第一步:对于传入的下标index进行校验;第二步:获取到下标位置的值;第三步:就是对要删除位置下标位置的元素进行覆盖, for (int i = index + 1; i < size; i++) ,初始位置从下标的后一个开始,覆盖掉index位置的元素,以此类推,后面的元素以次往前覆盖一个位置,过程如图:
之后size–,将最后一个位置(下标为4的)100设置为null,当然,如果在删除过程中data里面的元素少于原来扩容后的数组大小的二分之一,那么就进行动态减容,大小大约为原来的二分之一(当然这里只是一种简单的设计,可能会有性能问题,这里暂不考虑,只是为理解数组思想而写),之后就将删除的元素返回。
2、接下来就是复用romove了:removeFirst()、removeLast()
//从数组中删除第一个元素,返回删除的元素
public T removeFirst() {
return remove(0);
}
//从数组中删除最后一个元素,返回删除的元素
public T removeLast() {
return remove(size - 1);
}
这两个方法比较简单,我就不做过多的分析了,当然需要提醒一点就是,数组的位置是从0到size-1.
3、removeElement方法
//从数组中删除元素e
public void removeElement(T e) {
int index = find(e);
if (index != -1)
remove(index);
}
这个方法对find方法和remove方法进行复用,获取元素的下标位置,如果不是-1,并通过获取到的下标进行删除。
写博不易,欢迎添加关注本博客