package com.lkj;
public class Array<E>
{
private E[] data;//数组容量capacity就是数组的length:data.length
private int size;//数组中有效元素的个数
//初始化数组的构造函数
public Array(int capacity)
{
//初始化数组容量为capacity,数组中有效元素个数为0
// data = new E[capacity];
/**
* 注意,java不支持直接:new E[capacity] 这样new一个E泛型类型的数组。
* 我们必须先new一个Object类型的数组,然后再将其转换为E类型的数组
*/
data = (E[])new Object[capacity];//注意,是转换为E类型的数组:E[] , 而不是转换为E类型
size = 0;
}
// 无参数的构造函数,如果用户不指定,默认数组的容量capacity=10
public Array()
{
//调用上面的有参构造函数初始化
this(10);
}
//--------------------------------------------------------------------------------- 获取数组容量,元素个数,判断数组是否为空
// 获取数组中的元素个数
public int getSize()
{
return size;
}
// 获取数组的容量
public int getCapacity()
{
return data.length;
}
// 返回数组是否为空
public boolean isEmpty()
{
return size == 0;
}
//---------------------------------------------------------------------------------------添加元素
/**
* add()、addFirst()方法时间复杂度为O(n),addLast()时间复杂度为O(1);
* 综合来说,数组添加操作是O(n)级别的算法。——我们通常按照最坏的情况来计算时间复杂度。
*
* 扩容的时间复杂度是O(n)——原先有多少个元素就要赋值多少次。
*/
//向所有元素后添加一个新元素(既在数组的末尾添加一个新的元素)
public void addLast(E e)
{
// //添加元素之前判断数组元素个数是否已经等于数组容量,等于则不可再添加(既数组已经放满了)
// if(size == data.length)
// {
// throw new IllegalArgumentException("AddLast fail . Array is full.");
// }
// data[size] = e;
// size++;
//addLast方法可以复用add方法,add方法自然会检查数组是否放满或者插入位置是否不正确
add(size , e);//向末尾添加元素,既在size位置添加
}
// 在所有元素前添加一个新元素(既在数组的0角标添加元素)
public void addFirst(E e)
{
add(0,e);
}
// 在index索引的位置插入一个新元素e
public void add(int index , E e)
{
//首先同样判断数组是否已经放满
if(size == data.length)
{
// throw new IllegalArgumentException("Add fail . Array is full.");
/**
* 当数组元素放满之后,我们不抛出异常,而是进行扩容,将整个数组的容量扩容2倍。
* 其实就是生成一个新的数组,将原来数组的元素存储到新数组中,并将原来数组的引用指向新的数组。
* 原先数组的地址没有引用指向,就会被JVM自动垃圾回收.
*
* 这样操作后,我们的数组从可以根据需求自动扩容
*/
resize(2*size);//使用resize函数实现扩容2倍,这样就可以继续添加元素。
}
//其次,判断index插入的位置是否不合法:index<0或者index>size ,插入的位置不合法(可以插入size位置,既插入数组当前元素的后边)
if(index<0 || index>size)
{
throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size.");
}
//最后,将index索引之后的元素向后移动一位。
for(int i=size-1 ; i>=index ; i--)
{//将数据后移一位。其实只是将i位置的元素复制一份放到i+1,而i的元素还存在,但是这个时候我们可以覆盖这个元素
data[i+1] = data[i];
}
/*
如果index=size,那么不会进入循环,直接将size位置的数据设置为e
*/
data[index] = e;//数据移动完毕后,将索引为index的位置的数据设置
size++;//最后,数组长度记得加1
}
//-----------------------------------------------------------------------------------------获取、设置元素
/**
* 如果知道元素的索引:
* set(int index) 修改操作的时间复杂度是O(1)——数组支持随机访问
* get(int index) 获取操作的时间复杂度是O(1),
*
* 如果不知道要删除元素的索引,通过contains(e)方法判断数组是否存在这个元素,这个方法时间复杂度为O(n),
* 再通过find(e)方法获取数组的元素的索引,这个方法的时间复杂度为O(n)
*/
// 获取index索引位置的元素
public E get(int index)
{
//判断输入的索引是否正确
if(index < 0 || index >= size)
throw new IllegalArgumentException("Get failed. Index is illegal.");
//这里相当于对data数组进行隐藏,用户只能通过get方法来获取data这个静态数组某一个索引对应的元素,而不能直接获得data数组(data设置为private,用户无法获取data)
//这样做的好处:可以对用户传入的索引进行判断,避免索引错误。比如数组中有的空间没有存储元素,那么通过get方法用户无法查询data数组没有使用的空间。
//如果直接使用data[索引]的方式,有可能会出现用户输入索引错误的问题。
return data[index];
}
//获取数组最后一个元素
public E getLast()
{
//我们这里不使用data[size-1],这是因为size可能等于0,使用data[size-1]可能造成索引为负数。
//而使用get(size-1),这个方法或判断索引是否合法,不合法会抛出异常
return get(size-1);
}
//获取数组第一个元素
public E getFirst()
{//同样,如果数组为null,data[0]会出错,get(0)则会判断索引是否合法
return get(0);
}
// 修改index索引位置的元素为e
public void set(int index , E e)
{
//判断输入的索引是否正确
if(index < 0 || index >= size)
throw new IllegalArgumentException("Set failed. Index is illegal.");
data[index] = e;
}
//----------------------------------------------------------------------------------------查找元素
/**
* contains(e) 方法时间复杂度为O(n),
* find(e)方法的时间复杂度为O(n)
*/
// 查找数组中是否有元素e
public boolean contains(E e)
{
for (int i = 0; i < size; i++)
{
//e是类类型,如果要进行值的比较,应该使用equals,使用==比较的是地址
if(data[i].equals(e))
return true;
}
return false;
}
// 查找数组中元素e所在的索引,如果不存在元素e,则返回-1
public int find(E e)
{
for (int i = 0; i < size; i++)
{
if(data[i].equals(e))
return i;
}
return -1;
}
//----------------------------------------------------------------------------------------删除元素
/**
* remove()、removeFirst()方法时间复杂度为O(n),removeLast()时间复杂度为O(1);
* 综合来说,数组删除操作是O(n)级别的算法。——我们通常按照最坏的情况来计算时间复杂度。
*
* 缩容的时间复杂度是O(n)——缩容同样要将原来数组的值赋予新的数组
*/
// 从数组中删除index位置的元素, 返回删除的元素
public E remove(int index)
{
//首先,判断要删除的index 是否正确
if(index < 0 || index >= size)
{
throw new IllegalArgumentException("Remove failed. Index is illegal.");
}
E ret = data[index];//将要删除位置的元素值存储
//将后面的元素逐渐覆盖它之前的一个元素
for(int i = index+1 ; i<= size -1 ; i++)
{
data[i-1] = data[i];
}
//如果要删除的index是最后一个size-1,不会进入循环,而是直接将size-1,将最后一个元素删除
size--;
/**
* 我们在删除的时候,其实size位置的元素还存在,只不过我们无法访问到size位置的元素。
* 当我们的元素是类类型的时候,数组存储的是类类型对象的引用的,删除后data[size]还指向引用的位置,此时jvm的垃圾回收机制不会自动将其回收。
* 因此,我们在删除后将size位置的元素置为null,这样删除后size位置的引用就会被JVM回收
*/
data[size] = null;//loitering objects != memory leak ,lortering objexts不等于内存泄漏
/**
* 当我们的数组删除到使得元素个数等于原来数组容量的一半的时候,我们需要简化数组容量缩小到原来的一半。
* 当我们减少元素到一定程度后,数组空间自动缩小,节省内存。
*
* 这样操作后,我们的数组从可以根据需求自动缩小容量,以节省空间
*/
// if(size == data.length/2)
//采用Lazy的策略,等到元素个数为数组容量1/4的时候再进行缩容,缩容为原来的一半,防止add胡remove的时候出现时间复杂度的震荡
if(size == data.length/4 && data.length/2 != 0)
{
/**
* 当size=0,此时data.length<4,有可能data.length=1,data.length/2可能等于0,
* (比如初始定义data.length=1,size=1,减少一个size=0=data.length/4,此时data.length/2=0)
* 但是我们在new一个数组空间的时候,不能new一个长度为0的数组,必须再添加一个条件:data.length/2 != 0,
* 当数组容量为1的时候,不再进行缩容。
*/
resize(data.length/2);
}
return ret;
}
// 从数组中删除第一个元素, 返回删除的元素
public E removeFirst()
{
return remove(0);
}
// 从数组中删除最后一个元素, 返回删除的元素
public E removeLast()
{
return remove(size-1);
}
// 从数组中删除元素e——我们当前的数组可以存储重复的元素,这个removeElement方法只删除了数组红最前面的一个e元素
public void removeElement(E e)
{
//首先通过find方法找到元素e的索引
int index = find(e);
//如果返回的index不是-1,说明数组中存在元素e
if(index != -1)//必须先判断数组中存在元素e
{
//调用remove方法删除这个元素
remove(index);
}
}
//----------------------------------------------------------------------------------------toString方法
//重写数组的toString方法
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
/*
format(String format, Object... args) ,静态方法,使用指定的格式字符串和参数返回一个格式化字符串。
添加这个字符串来反应数组的容量,元素个数等基本信息
*/
sb.append(String.format("Array: size = %d , capacity = %d\n" , size , data.length));
sb.append("[");
for (int i = 0; i < size; i++)
{
sb.append(data[i]);
if(i != size-1)
sb.append(",");
}
sb.append("]");
return sb.toString();
}
//-----------------------------------------------------------------------------------------扩容数组
/**
* 改变的时间复杂度是O(n)——原先数组有多少个元素就要赋值多少次。
*/
// 将数组空间的容量变成newCapacity大小(注意,这个方法是private的,用户无法调用,只有需要扩容的时候系统才能调用)
private void resize(int newCapacity)
{
//创建一个E类型的,容量是newCapacity的新的数组
E[] newData = (E[])new Object[newCapacity];//同样,无法直接创建E类型的数组,只能此案创建Object类型的数组,再将其转换为E[]类型
//将原来data数组的元素存储到newData数组
for (int i = 0; i < size ; i++)
{
newData[i] = data[i];
}
//最后,将原来data数组数组的引用指向新数组newData的地址。
//这样,我们原来的data就指向新的地址,且数组的容量扩充2倍,这样添加元素的时候就可以向里面添加元素。
data = newData;
/**
* 使用resize方法,我们的数组从静态数组(容量固定),转变为动态数组(可以根据需求自动扩容或者缩小)。
* 这样用户便不需要考虑内存大小问题,直接创建一个默认数组往里面存储数据,当空间不够的时候数组会自动扩容,当空间过多的时候内存会自动缩容
*/
}
}