数组及数组方法原理学习---笔记2

161 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

1.数组的方法

public class Array<E> {
    private E[] data;//数组
    private int size;//数组长度

    public Array(int capacity) {//构造函数,传入数组的容量capacity构造Array
        data = (E[])new Object[capacity];
        size = 0;
    }

    public Array() {//无参数的构造函数,默认数组的容量capacity=10
        this(10);
    }
}

1. 创建增删改查方法

  • 自己创建数组无非就是数组的 增删改查 方法的创建,即 add,remove,set,get 方法。
  • add方法:在指定索引位置插入数字,就相当于把指定索引处后的值往后移一位,即把值赋给下一个索引,一层for循环就可以,同时不要忘记size++,然后再在指定索引处赋值。
  • remove方法:在指定索引位置删除数字,就相当于把指定索引处后的值往前移一位,即把值赋给上一个索引,一层for循环就可以,同时不要忘记size–。这里有所不同,即int[size]的值需不需要删除,其实这里删不删除并不影响数组的逻辑和功能实现,但本着严谨的态度,将int[size]赋值为null,节省一点内存空间。


    public E remove(int index) {
        //从数组中删除index位置的元素,返回删除的元素  时间复杂度:O(n/2)=>O(n)
        if(index < 0 || index >= size) {
            throw new IllegalArgumentException("Remove failed. Index is illegal.");
        }
        E ret = data[index];
        for(int i = index + 1; i < size; i++) {
            data[i - 1] = data[i];
        }
        size--;
        data[size] = null;//loitering objects != memory leak
        if(size == data.length / 4 && data.length / 2 != 0) {
            resize(data.length / 2);
        }
        return ret;
    }

    public E removeFirst() {//时间复杂度:O(n)
        return remove(0);
    }

    public E removeLast() {//时间复杂度:O(1)
        return remove(size - 1);
    }

    public void removeElement(E e) {//从数组中删除元素e
        int index = find(e);
        if(index != -1) {
            remove(index);
        }
    }

2. 获取数组元素



    public E get(int index) {
         //获取index索引位置的元素  时间复杂度:O(1)
        if(index < 0 || index >= size) {
            throw new IllegalArgumentException("Get failed. Index is illegal.");
        }
        return data[index];
    }

    void set(int index, E e) {
        //修改index索引位置的元素  时间复杂度:O(1)
        if(index < 0 || index >= size) {
            throw new IllegalArgumentException("Set failed. Index is illegal.");
        }
        data[index] = e;
    }

    public boolean contains(E e) {
        //查找数组中是否有元素e  时间复杂度:O(n)
        for(int i = 0; i < size; i++) {
            if(data[i] == e) {
                return true;
            }
        }
        return false;
    }

    public int find(E e) {
        //查找数组中元素e所在的索引,如果不存在元素e,返回-1  时间复杂度:O(n)
        for(int i = 0; i < size; i++) {
            if(data[i].equals(e)) {
                return i;
            }
        }
        return -1;
    }

2.泛型和动态数组的实现

1.泛型

在实际运用中,我们不能只是简单的用整数型数组来表示,这里就要引出泛型的概念。

  • 泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
  • 顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
  • 泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
  • 因为不能直接定义泛型,所以可以通过定义Object的方式来强转为泛型
  • 定义了泛型之后就可以接收的多种类型,因为泛型把具体的参数类型泛化,模糊化,使得传参可以有更多种的选择。

2.动态数组

动态数组的实现即实现了数组的扩容。扩容的过程是,是否超出了默认的数组长度,超出则执行扩容方法

  • 扩容是新建一个数组,新建数组的长度为原数组的2倍(ArrayList为1.5倍,通过位运算来计算),接着把原数组的值通过for循环来赋值到新数组,在接着把原数组的引用指向新数组。
  • 在增加和删除的时候进行扩容和缩容(新建一个为原数组长度1/2的新数组)
  • 在数组缩容的问题上,为了防止复杂度的震荡,数组在长度边缘疯狂的试探,频繁的进行扩容和缩容。(即数组长度为10,增加一个元素扩大为20,之后紧接着又删除一个元素,又缩容为10)。
  • 所以缩容的时候判断数组实际长度为1/4时才进行缩容,就可以减少疯狂的试探,从而防止方法的时间复杂度一直为O(n)。