Array 动态数组

Array 动态数组, 也就是Java内置ArrayLIst的底层实现,也是是栈和队列的底层实现方法之一。

什么是Array?

Array 也称动态数组,是一个线性以存储数据为目的数据结构,它具有以下几个重要特性:

  1. 具有索引,能够通过索引对Array对象进行访问, 时间复杂度为O(1)

  2. 一个Array对象里面只能存储一种数据类型, 比如全部存储 int 类型

  3. 能够动态地进行扩容,缩容操作

  4. 是Java内置ArrayList地底层实现, 也是栈和队列的底层实现

Array 图解表示

一个Array对象 可以用下图进行表示。

Array具有3个重要的成员属性, 分别是:

  1. data, 是Java 中的 一维数组

  2. size, 指向data中下一个可以存放数据的索引, 初始值为0

  3. capacity, 表示Array的容量, 也就是data的长度, 在扩容和缩容时动态改变

构造方法

根据上面所说,我们可以很轻易地写出Array的构造方法, 为了增加代码的复用性, 我们使用了泛型。

public class Array<E> {

    private E[] data;
    private int size;//有效的元素个数

    /**
     * 传入数组容量构造Array
     * @param capacity
     */
    public Array(int capacity){
        this.data =(E[]) new Object[capacity];
        this.size=0;
    }

    //若不传入, 则创建一个容量为10的Array对象
    public Array(){
        this(10);
    }

}

Array还应该有一些常用方法

获取有效元素个数

public  int getSize(){
    return this.size;
}

获取容积

public int getCapacity(){
    return this.data.length;
}

判断是否为空

public boolean isEmpty(){
    return  size==0;
}

通过索引, 获得元素

public E get(int index){
        if (index<0 || index >= this.size){
            throw new IllegalArgumentException("Add failed. Required index>=0 <=arr.size");
        }
        return data[index];
}

获得最后一个元素

public E getLast(){
    return  this.get(this.size-1);
}

获取第一个元素

public E getFirst(){
    return  this.get(0);
}

通过索引, 更改元素

public void set(int index, E e){
    if (index<0 || index >= this.size){
        throw new IllegalArgumentException("Add failed. Required index>=0 <=arr.size");
    }
    data[index] =e;
}

查询是否包含某个元素

public boolean contains(E e){
    for (int i = 0; i < this.size; i++) {
        if(data[i].equals(e)){
            return  true;
        }
    }
    return  false;
}

查询是否包含某个元素, 并返回索引,若不存在返回-1

public int find(E e){
    for (int i = 0; i < this.size; i++) {
        if(data[i].equals(e)){
            return  i;
        }
    }
    return  -1;
}

扩容, 缩容 resize 操作

当Array 对象容量已满, 想要继续向 Array 对象增加元素需要扩容

当Array 对象元素占容量的很少一部分时, 为节省内存空间,需要缩容

扩容缩容的操作非常简单, 首先构造一个新的 Java数组, 将原数组内的元素全部复制到新数组里面,组后 data指向新数组

//数组扩/缩容
private void resize(int newCapacity){
    E[] newData=(E[]) new Object[newCapacity];
    for (int i = 0; i < this.size; i++) {
        newData[i]=data[i];
    }
    this.data=newData;

}

向指定位置插入一个新元素

若Array容量已满,我们需要对内置的数组进行扩容, 我们选择扩大一倍容量,这只是一个经验值,也可以换成别的值

//在index位置插入一个新元素,index后面的元素需要后移
public void add(int index, E e){

    //若索引<0 或是 大于 size, 抛出异常。
    if (index<0 || index >this.size){
        throw new IllegalArgumentException("Add failed. Required index>=0 <=arr.size");
    }

    //数组已满,需要扩容
    if (this.size==this.data.length){
        this.resize(2* data.length);
    }
    
    //将index后面的元素一个个后移
    for (int i = size-1; i >=index ; i--) {
        data[i+1]=data[i];
    }
    
    //插入新元素
    this.data[index]=e;
    
    //更新有效元素个数
    this.size++;
}

在首尾插入元素, 只需要调用 add() 函数即可

public void addFirst(E e){
    add(0,e);
}

public void addLast(E e){
    this.add(this.size,e);
}

删除指定位置的元素并返回

//删除index位置的元素,返回删除的元素
    public E remove(int index){
        //索引越界,抛出异常
        if (index<0 || index > this.size){
            throw new IllegalArgumentException("Add failed. Required index>=0 <=arr.size");
        }

        //return的元素
        E ret=data[index];

        //将index之后的元素, 一个个向前移
        for(int i=index+1;i<size;i++){
            data[i-1]=data[i];
        }

        //更新有效元素个数
        size--;

        //减少了一个元素,将原来最后一个设为NULL
        data[size]=null;
        
        //动态减少空间
        if (size<= data.length/4 && data.length/2!=0 ){
            this.resize(data.length/2);
        }
        
        return  ret;
    }

这里特别注意,我们进行缩容的条件。当Array元素小于容积 1/4 时,我们只缩容1/2

这是为了防止resize操作引起的复杂度震荡。假设有这么一个场景, 需要频繁地加入一个元素,进行扩容两倍,随后立马删掉一个元素,又进行缩容至一半。如此反复调用 resize 需要反反复复地 new 一个新数组,非常消耗性能。所以我们把删除元素后resize的条件设置的苛刻一些,宁愿浪费一些空间,也不要引起复杂度的震荡。

删除元素不反回, 删除首尾元素, 都只要调用 remove()函数即可

public void removeElement(E e){
    int index=find(e);
    if (index!=-1){
        remove(index);
    }
}

public E removeLast(){
     return this.remove(size-1);
}

public E removeFirst(){
    return this.remove(0);
}

重写toString() 函数, 方便打印输出Array的容积,有效元素个数 和 里面的元素

@Override
public String toString() {

    StringBuilder res =new StringBuilder();
    
    res.append("Array: size="+ this.size+ " capacity="+this.data.length);
    res.append(" [");
    
    for (int i = 0; i < this.size; i++) {
        res.append(data[i]);
        if(i!=size-1){
            res.append(", ");
        }
    }
    
    res.append("]");
    
    return  res.toString();

}

至此我们完成了所有Array的功能模块

测试一下

编写了一个Array测试类

public class ArrayTest {

    public static void main(String[] args) {
        //泛型中只能放类
        Array<Integer> arr= new Array();
        for (int i = 0; i < 10; i++) {
            arr.addLast(i);
        }
        System.out.println(arr);

        //测试扩容功能
        arr.add(10,10);
        System.out.println(arr);

        //测试增加元素功能
        arr.add(1,100);
        System.out.println(arr);

        arr.addFirst(-1);
        System.out.println(arr);

        //测试删除元素功能
        arr.remove(2);
        System.out.println(arr);

        arr.removeElement(10);
        System.out.println(arr);

        //测试动态减小空间功能
        arr.removeElement(9);
        arr.removeElement(8);
        arr.removeElement(7);
        arr.removeElement(6);
        arr.removeElement(5);
        arr.removeElement(4);
        System.out.println(arr);
    }
}

输出结果

Array: size=10 capacity=10 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Array: size=11 capacity=20 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Array: size=12 capacity=20 [0, 100, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Array: size=13 capacity=20 [-1, 0, 100, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Array: size=12 capacity=20 [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Array: size=11 capacity=20 [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Array: size=5 capacity=10 [-1, 0, 1, 2, 3]

Last updated

Was this helpful?