最近研究了下Arraylist的底层实现,我在这里基于Arraylist底层的实现思想,这里基于对java的数组进行一个简单的封装,实现一个类似Arraylist的自定义的数组类。
1.封装数组类
在我们自定义的数组中要指定一个容量大小的变量capacity,一个数组实际大小的变量size,size也是指向数组中第一个没有元素的位置,首先自定义一个类,这里先指定数组存放的数据类型为int,后面会使用泛型进行优化
public class Array {
//因为数组的长度也就是我们这个数组的容量大小,所以这里就少维护一个变量
private int[] data;
//数组中的元素个数
private int size;
/**
* 用户在创建类的时候传入该数组的容量大小
* @param capacity
*/
public Array(int capacity){
data =new int[capacity];
size=0;
}
/**
* 如果用户在创建的时候不传入参数,则调用有参构造函数,并将容量默认设置为10
*/
public Array(){
this(10);
}
/**
* 查询数组的容量大小
* @return
*/
public int getCapacity(){
return data.length;
}
/**
* 获取数组中的元素个数
* @return
*/
public int getSize(){
return size;
}
}
2.实现向数组中添加元素
- 在数组的末尾添加元素:首先前面介绍过,size就是指向数组中第一个没有元素的位置,所以如果要想实现向数组中最后一个位置添加元素的操作,只需要向size的位置添加元素即可,在添加完后,将size的值就行+1的操作,需要注意的是在执行添加操作之前需要对数组的容量大小进行判断,如果数组中的元素个数等于数组的容量的时候需要抛出一个异常来提示用户,后面会进行动态扩容的优化。
public void addLast(int e){
if (size==data.length){
throw new RuntimeException("数组容量已满");
}
data[size]=e;
size++;
}
- 向数组中的指定位置插入元素:如果要向数组中指定的位置添加元素,我们需要将该索引后面的元素依次向后面移动一个位置,将需要插入的索引位置的数据让出来,为了不发生数组的覆盖,我们应该从数组中最后一个元素开始移动,直到将用户选择插入的索引位置的元素也移动到下一位时候停止,然后将用户传入的数据插入到该位置,将size++,还要对用户传入的索引值进行一下判断,该所以必须大于等于0,且不能大于size的值,否则数据会出现不连续的情况。
public void add(int index ,int e){
if (size==data.length){
throw new RuntimeException("数组容量已满");
}
if(index<0|| index>size){
throw new RuntimeException("索引值不符合要求");
}
for (int i=size-1;i>=index;i--){
data[i+1]=data[i];
}
data[index]=e;
size++;
}
定义了该方法后,前面所定义的addLast就可以直接复用该方法了,可以使代码更加简洁
public void addLast(int e){
add(size,e);
}
向数组的头部添加元素:实现思路和前面的相同只需要向数组中的第0个位置添加一个元素即可
public void addFirst(int e){
add(0,e);
}
3.实现数组的查询元素和修改元素
- 为了方便查看数组中的元素,我们先书写一个toString的方法
@Override
public String toString() {
StringBuffer stringBuffer=new StringBuffer();
stringBuffer.append("数组的元素的个数为"+size+"\r\n"+"数组的容量为"+data.length+"\r\n");
stringBuffer.append("[");
for (int i=0;i<size;i++){
stringBuffer.append(data[i]);
if (i!=size-1){
stringBuffer.append(",");
}
}
stringBuffer.append("]");
return stringBuffer.toString();
}
- 创建一个方法,用户传入索引值,就可以获取到该索引上的元素的值,首先要先对该索引进行判断,如果该索引小于0或者大于size值会抛出异常,索引符合要求时会从返回data中的该索引的元素
public int get(int index){
if (index<0||index>size){
throw new RuntimeException("索引值不符合要求");
}
return data[index];
}
- 修改指定索引上的元素,同样先对传入的索引值进行判断,然后将data对应传入的索引值上的元素修改为用户传入的值
/**
* 修改指定索引上的元素
* @param index
* @param e
*/
public void set(int index , int e){
if (index<0||index>=size){
throw new RuntimeException("索引值不符合要求");
}
data[index]=e;
}
- 传入一个元素,查看该元素是否存在于该数组中,如果存在返回true,如果不存在返回false,遍历data数组,将每个索引位置的元素取出和传入的元素进行对比,如果相等返回true,否则返回false
public boolean contain(int e){
for (int i=0;i<size;i++){
if (data[i]==e){
return true;
}
}
return false;
}
- 查询方法,传入一个元素,返回该元素所在位置的索引值,如果该元素不存在则返回-1,复用上面的contain方法,先对该元素进行判断是否存在于数组之中,如果存在则进行循环遍历,如果不存在则可以直接返回-1
public int find(int e){
boolean b = contain(e);
if (b){
for (int i=0;i<size;i++){
if (data[i]==e){
return i;
}
}
}
return -1;
}
4.实现删除元素
- 实现删除元素的功能,用户传入索引值,删除该索引值上的元素,该方法的实现和添加元素类似,对于添加元素来说是需要将要插入的索引位置后面的元素都向后面一次赋值,然后再将元素写入,对于删除来说就是讲要删除的索引位置之后的元素全部都向前面一个位置进行赋值操作,维护size变量,对size变量进行减一,并返回要删除的索引上的元素值
public int remove(int index){
if (index<0||index>=size){
throw new RuntimeException("索引值不符合规范");
}
int e=data[index];
for (int i=index+1;i<size;i++){
data[i-1]=data[i];
}
size--;
return e;
}
5.使用泛型对数组进行改良
因为我们前面定义的数组为int类型的,只能存放int类型的数组,存在一定得局限性,所以我们将该数组设置一个泛型,可以用来存放所有的元素
public class Array<E> {
//因为数组的长度也就是我们这个数组的容量大小,所以这里就少维护一个变量
private E[] data;
//数组中的元素个数
private int size;
/**
* 用户在创建类的时候传入该数组的容量大小
* @param capacity
*/
public Array(int capacity){
//因为java中不支持直接创建泛型的数组,所以先创建一个object类型的数组,然后再强转为泛型数组
data = (E[])new Object[capacity];
size=0;
}
/**
* 如果用户在创建的时候不传入参数,则调用有参构造函数,并将容量默认设置为10
*/
public Array(){
this(10);
}
/**
* 查询数组的容量大小
* @return
*/
public int getCapacity(){
return data.length;
}
/**
* 获取数组中的元素个数
* @return
*/
public int getSize(){
return size;
}
/**
* 向数组的尾部添加一个元素
* @param e
*/
public void addLast(E e){
add(size,e);
}
/**
* 向数组的头部添加一个元素
* @param e
*/
public void addFirst(E e){
add(0,e);
}
/**
* 向数组的任意位置添加一个元素
* @param index
* @param e
*/
public void add(int index ,E e){
if (size==data.length){
throw new RuntimeException("数组容量已满");
}
if(index<0|| index>size){
throw new RuntimeException("索引值不符合要求");
}
for (int i=size-1;i>=index;i--){
data[i+1]=data[i];
}
data[index]=e;
size++;
}
@Override
public String toString() {
StringBuffer stringBuffer=new StringBuffer();
stringBuffer.append("数组的元素的个数为"+size+"\r\n"+"数组的容量为"+data.length+"\r\n");
stringBuffer.append("[");
for (int i=0;i<size;i++){
stringBuffer.append(data[i]);
if (i!=size-1){
stringBuffer.append(",");
}
}
stringBuffer.append("]");
return stringBuffer.toString();
}
/**
* 获取指定索引上的元素
* @param index
* @return
*/
public E get(int index){
if (index<0||index>size){
throw new RuntimeException("索引值不符合要求");
}
return data[index];
}
/**
* 修改指定索引上的元素
* @param index
* @param e
*/
public void set(int index , E e){
if (index<0||index>=size){
throw new RuntimeException("索引值不符合要求");
}
data[index]=e;
}
/**
* 查询传入的元素在数组中的索引,如果该元素不存在则返回-1
* @param e
* @return
*/
public int find(E e){
boolean b = contain(e);
if (b){
for (int i=0;i<size;i++){
if (data[i].equals(e)){
return i;
}
}
}
return -1;
}
/**
* 查看数组中是否包含某个元素,如果包含返回true,否则返回false
* @param e
* @return
*/
public boolean contain(E e){
for (int i=0;i<size;i++){
if (data[i].equals(e)){
return true;
}
}
return false;
}
/**
* 删除指定索引上的元素
* @param index
* @return
*/
public E remove(int index){
if (index<0||index>=size){
throw new RuntimeException("索引值不符合规范");
}
E e=data[index];
for (int i=index+1;i<size;i++){
data[i-1]=data[i];
}
size--;
//在进行完上面的操作后数组中的size索引位置依然有值,将其设置为null进行垃圾回收
data[size]=null;
return e;
}
}
6.动态数组的实现
在我们之前设计的数组中,数组的容量是固定的,一旦用户添加元素达到容量上限的时候会抛出异常,这里我们要进行改良,当容量达到上限的时候对数组的容量进行扩充,这里采用ArrayList中的扩容因子,为0.5,就是说当数组容量达到上限时候,在创建一个新的数组,容量为原来的1.5倍,并将原数组中的元素遍历存储到新的数组中,并将原数组的引用指向新的数组,这样就实现了数组的扩容,同样,当用户在进行remove操作的时候,当数组中的元素个数为容量的四分之一的时候需要对数组进行缩容,将容量变为原来的一半。
private void resize(int newCapacity){
E[] newData=(E[])new Object[newCapacity];
for (int i=0;i<size;i++){
newData[i]=data[i];
}
data=newData;
}
7.完整代码
public class Array<E> {
//因为数组的长度也就是我们这个数组的容量大小,所以这里就少维护一个变量
private E[] data;
//数组中的元素个数
private int size;
/**
* 用户在创建类的时候传入该数组的容量大小
* @param capacity
*/
public Array(int capacity){
//因为java中不支持直接创建泛型的数组,所以先创建一个object类型的数组,然后再强转为泛型数组
data = (E[])new Object[capacity];
size=0;
}
/**
* 如果用户在创建的时候不传入参数,则调用有参构造函数,并将容量默认设置为10
*/
public Array(){
this(10);
}
/**
* 查询数组的容量大小
* @return
*/
public int getCapacity(){
return data.length;
}
/**
* 获取数组中的元素个数
* @return
*/
public int getSize(){
return size;
}
/**
* 向数组的尾部添加一个元素
* @param e
*/
public void addLast(E e){
add(size,e);
}
/**
* 向数组的头部添加一个元素
* @param e
*/
public void addFirst(E e){
add(0,e);
}
/**
* 向数组的任意位置添加一个元素
* @param index
* @param e
*/
public void add(int index ,E e){
if (size==data.length){
// throw new RuntimeException("数组容量已满");
resize(data.length/2+data.length);
}
if(index<0|| index>size){
throw new RuntimeException("索引值不符合要求");
}
for (int i=size-1;i>=index;i--){
data[i+1]=data[i];
}
data[index]=e;
size++;
}
@Override
public String toString() {
StringBuffer stringBuffer=new StringBuffer();
stringBuffer.append("数组的元素的个数为"+size+"\r\n"+"数组的容量为"+data.length+"\r\n");
stringBuffer.append("[");
for (int i=0;i<size;i++){
stringBuffer.append(data[i]);
if (i!=size-1){
stringBuffer.append(",");
}
}
stringBuffer.append("]");
return stringBuffer.toString();
}
/**
* 获取指定索引上的元素
* @param index
* @return
*/
public E get(int index){
if (index<0||index>size){
throw new RuntimeException("索引值不符合要求");
}
return data[index];
}
/**
* 修改指定索引上的元素
* @param index
* @param e
*/
public void set(int index , E e){
if (index<0||index>=size){
throw new RuntimeException("索引值不符合要求");
}
data[index]=e;
}
/**
* 查询传入的元素在数组中的索引,如果该元素不存在则返回-1
* @param e
* @return
*/
public int find(E e){
boolean b = contain(e);
if (b){
for (int i=0;i<size;i++){
if (data[i].equals(e)){
return i;
}
}
}
return -1;
}
/**
* 查看数组中是否包含某个元素,如果包含返回true,否则返回false
* @param e
* @return
*/
public boolean contain(E e){
for (int i=0;i<size;i++){
if (data[i].equals(e)){
return true;
}
}
return false;
}
/**
* 删除指定索引上的元素
* @param index
* @return
*/
public E remove(int index){
if (index<0||index>=size){
throw new RuntimeException("索引值不符合规范");
}
E e=data[index];
for (int i=index+1;i<size;i++){
data[i-1]=data[i];
}
size--;
//在进行完上面的操作后数组中的size索引位置依然有值,将其设置为null进行垃圾回收
data[size]=null;
if (size<=data.length/4){
resize(data.length/2);
}
return e;
}
/**
* 数组的扩容和缩容操作
* @param newCapacity
*/
private void resize(int newCapacity){
E[] newData=(E[])new Object[newCapacity];
for (int i=0;i<size;i++){
newData[i]=data[i];
}
data=newData;
}
}