2.动态数组--ArrayList的简单实现

1 数据结构

1 数据结构概述

​ 数据结构是计算机存储,组织数据的方式。

2 常见的数据结构
  • 线性结构:数组,链表,栈,队列,哈希表(用到了数组,暂定为线性表)。
    • 线性表:具有n个相同类型元素的有限序列。
  • 树形结构:二叉树,avl树,红黑树,B树,堆,Trie,哈夫曼树,并查集。
  • 图形结构:邻接矩阵,邻接表。

2 数组

​ 数组一种顺序存储的线性表,所有的元素的内存地址是连续的。

例如:int[ ] array=new int[ ]{11,22,33};

缺点:很多编程语言中,数组无法动态的修改容量。但是实际的开发过程中我们总是希望数组的长度是可以发生变化的。因此我们需要自己定义一个动态数组。

3 自定义动态数组

​ 仿照ArrayList的的实现,简单实现增删改查。

1 我们需要实现的方法

在这里插入图片描述

2 代码逐步实现
1 首先定义成员变量
/**
 * 记录元素的数量
 */
private int size = 0;
/**
 * 存放元素
 */
private int[] elements;
/**
 * 我们要求如果传入容量小于10,就默认创建长度为10的数组
 */
private static final int DEFAULT_CAPACITY = 10;
/**
 * 查找的元素不存在的事后返回该常量
 */
private static final int ELEMENT_NOT_FOUND = -1;
2 构造器的实现:需要对数组进行初始化。
public DynamicArray2(int capacity) {
    //如果传入容量小于10,则创建一个容量为10数组
    capacity = (capacity < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capacity;
    elements =new int[capacity];
}

public DynamicArray2() {
    //初始化参数
    this(DEFAULT_CAPACITY);
}
3 一些方法的实现

​ size,isEmpty,get,set,indexOf,contains,clear,rangeCheck,rangeCheckAdd,showInfo,finalize,toString,add,remove,outOfBounds。

/**
 * 返回集合中元素的个数
 * @return :元素的个数
 */
public int size() {
    return size;
}

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

/**
* 获取指定下标的元素
* @param index :指定的下标
* @return :指定下标的元素
*/
public int get(int index) {
    rangeCheck(index);
    return elements[index];
}

/**
* 设置指定位置的元素,并返回改位置的就元素
* @param index   :指定下标
* @param element :替换的元素
* @return :被替换的元素
*/
public int set(int index, int element) {
    rangeCheck(index);
    int old = elements[index];
    elements[index] = element;
    return old;
}

/**
* 查看元素的索引
*
* @param element :查找的元素
* @return :返回第一次出现指定元素的下标
*/
public int indexOf(int element) {
    for (int i = 0; i < size; i++) {
        if (element==elements[i]) {
            return i;
        }
    }
    return ELEMENT_NOT_FOUND;
}
/**
* 是否包含某值
*
* @param element :元素值
*/
public boolean contains(int element) {
    return indexOf(element) != ELEMENT_NOT_FOUND;
}

/**
* 清除集合
*/
public void clear() {
   size = 0;
}

private void outOfBounds(int index) {
    throw new IndexOutOfBoundsException("index:" + index + ",size:" + size);
}

private void rangeCheck(int index) {
    if (index < 0 || index >= size) {
        //抛出数组下标越界异常
        outOfBounds(index);
    }
}

private void rangeCheckAdd(int index) {
    if (index < 0 || index > size) {
        //抛出数组下标越界异常
        outOfBounds(index);
    }
}

public void showInfo() {
   System.out.println(elements.length);
}

@Override
public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("size=").append(size).append(",[");
    for (int i = 0; i < size - 1; i++) {
        builder.append(elements[i] + ",");
    }
    if (size>0){
        builder.append(elements[size - 1] + "]");
    }else {
        builder.append("]");
    }
    return builder.toString();
}

/**
* 向动态数组中添加元素
*
* @param element :待添加的元素
*/
public void (int element) {
    add(size, element);
}

public void add(int index, int element) {
    //注意此处的参数判断:index和size的大小允许相等。
    rangeCheckAdd(index);
    /*for (int i = size; i >index; i--) {
            elements[i]=elements[i-1];
        }*/
    for (int i = size - 1; i >= index; i--) {
        elements[i + 1] = elements[i];
    }
    elements[index] = element;
    showInfo();
    size++;
}

 /**
* 删除指定位置处的元素
*
* @param index : 需要被删除的下标
* @return :     被删除指定位置的元素
*/
public int remove(int index) {
    rangeCheck(index);
    int oldElement = elements[index];
    //将数组中的元素进行前移覆盖即可。
    for (int i = index + 1; i < size; i++) {
        //可以保证下标不会出现越界的情况
        elements[i - 1] = elements[i];
    }
    return oldElement;
}
4 扩容的实现

​ 扩容:当我们需要进行扩容的时候,因为底层是数组,不可能使用拼接的方式,因此我们需要申请一块连续的地址空间,并将当前数组中的元素复制到另外的数组中去。

public void ensureCapacity(int capacity) {
    int oldCapacity = elements.length;
    if (oldCapacity >= capacity) {
        return;
    }
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    int[] newElements = new int[newCapacity];
    //elements=Arrays.copyOf(elements,newCapacity);
    //对数组进行复制.这里采用这种方式当数据量比较大的时候,可以采用系统级别的拷贝方式,例如System.arraycopy();
    for (int i = 0; i < size; i++) {
        newElements[i] = elements[i];
    }
    elements = newElements;
}

因为我们添加了扩容的方法,所以add方法需要进行简单的修改

public void add(int index, int element) {
   /* if (element==null){
        return;
    }*/
    //注意此处的参数判断:index和size的大小允许相等。
    rangeCheckAdd(index);
    /*for (int i = size; i >index; i--) {
        elements[i]=elements[i-1];
    }*/
    ensureCapacity(size + 1);
    for (int i = size - 1; i >= index; i--) {
        elements[i + 1] = elements[i];
    }
    elements[index] = element;
    showInfo();
    size++;
}
5 存储对象的实现

分析:因为我们底层存储数据的数组是int类型,所以只能存储int类型数据,为了可以存储对象,需要进行修改,考虑使用泛型。

package day01;

public class DynamicArray<E> {
    /**
     * 记录元素的数量
     */
    private int size = 0;
    /**
     * 存放元素
     */
    private E[] elements;
    private static final int DEFAULT_CAPACITY = 10;
    private static final int ELEMENT_NOT_FOUND = -1;

    public DynamicArray(int capacity) {
        capacity = (capacity < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capacity;
        elements = (E[]) new Object[capacity];
    }

    public DynamicArray() {
        //初始化参数
        this(DEFAULT_CAPACITY);
    }

    /**
     * 返回集合中元素的个数
     *
     * @return :元素的个数
     */
    public int size() {
        return size;
    }

    /**
     * 向动态数组中添加元素
     *
     * @param element :待添加的元素
     */
    public void add(E element) {
        add(size, element);
    }

    public void add(int index, E element) {
       /*
       可以设置是否可以存储null
       if (element==null){
            return;
        }*/
        //注意此处的参数判断:index和size的大小允许相等。
        rangeCheckAdd(index);
        /*for (int i = size; i >index; i--) {
            elements[i]=elements[i-1];
        }*/
        ensureCapacity(size + 1);
        for (int i = size - 1; i >= index; i--) {
            elements[i + 1] = elements[i];
        }
        elements[index] = element;
        showInfo();
        size++;
    }

    /**
     * 保证要有capacity的容量足够
     *
     * @param capacity: 当前数组中元素的size+1
     */
    public void ensureCapacity(int capacity) {
        int oldCapacity = elements.length;
        if (oldCapacity >= capacity) {
            return;
        }
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        E[] newElements = (E[]) new Object[newCapacity];
        //elements=Arrays.copyOf(elements,newCapacity);
        //对数组进行复制.这里采用这种方式当数据量比较大的时候,可以采用系统级别的拷贝方式,例如System.arraycopy();
        for (int i = 0; i < size; i++) {
            newElements[i] = elements[i];
        }
        elements = newElements;

    }

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

    /**
     * 获取指定下标的元素
     *
     * @param index :指定的下标
     * @return :指定下标的元素
     */
    public E get(int index) {
        rangeCheck(index);
        return elements[index];
    }


    /**
	 * 删除指定位置处的元素
 	 * @param index : 需要被删除的下标
	 * @return :     被删除指定位置的元素
 	 */
    public E remove(int index){
        rangeCheck(index);
        E oldElement=elements[index];
        //将数组中的元素进行前移覆盖即可。
        for (int i = index+1; i < size; i++) {
            //可以保证下标不会出现越界的情况
            elements[i-1]=elements[i];
        }
        size--;
        return oldElement;
    }
    /**
     * 设置指定位置的元素,并返回改位置的就元素
     *
     * @param index   :指定下标
     * @param element :替换的元素
     * @return :被替换的元素
     */
    public E set(int index, E element) {
        rangeCheck(index);
        E old = elements[index];
        elements[index] = element;
        return old;
    }

    /**
     * 查看元素的索引
     *
     * @param element :查找的元素
     * @return :返回第一次出现指定元素的下标
     */
    public int indexOf(E element){
        for (int i = 0; i < size; i++) {
            if (elements[i]==element){
                return i;
            }
        }
     	return ELEMENT_NOT_FOUND;
    }

    /**
     * 是否包含某值
     *
     * @param element :元素值
     * @return :是否包含某元素
     */
    public boolean contains(E element) {
        return indexOf(element) != ELEMENT_NOT_FOUND;
    }

    /**
     * 清除集合
     */
    public void clear(){
        size=0;
    }
    private void outOfBounds(int index) {
        throw new IndexOutOfBoundsException("index:" + index + ",size:" + size);
    }

    private void rangeCheck(int index) {
        if (index < 0 || index >= size) {
            //抛出数组下标越界异常
            outOfBounds(index);
        }
    }

    private void rangeCheckAdd(int index) {
        if (index < 0 || index > size) {
            //抛出数组下标越界异常
            outOfBounds(index);
        }
    }

    public void showInfo() {
        System.out.println(elements.length);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("size=").append(size).append(",[");
        for (int i = 0; i < size - 1; i++) {
            builder.append(elements[i] + ",");
        }
        if (size>0){
            builder.append(elements[size - 1] + "]");
        }else {
            builder.append("]");
        }
        return builder.toString();
    }
}
6 存储对象的一些优化

1 清空

​ 当存储的类型为基本数据类型的时候,直接size=0就可以了;但是当存储引用数据类型的时候,就不能这样进行处理。

对象数组的内存细节:
在这里插入图片描述

如果以现在的方式来看,当我们clear之后,当我们添加一个元素之后,其他应该被清空的对象依旧存在,会导致内存的浪费。

/**
 * 清除集合
 */
public void clear(){
    size=0;
}

因此需要进行修改,如下所示。修改之后,那些对象会因为没有被引用,被gc所回收==>循环利用的留下,其他的去掉。

public void clear(){
    for (int i = 0; i < size; i++) {
        elements[i]=null;
    }
    size=0;
}

回调函数来表明对象的死亡

//对象死亡之后的回调函数
@Override
protected void finalize() throws Throwable {
    super.finalize();
    System.out.println("person - finalized");
}

2 remove的修改

​ remove的方法也需要进行处理,假如我们remove一个元素之后,会导致数组的最后会存在垃圾对象,对于这些对象我们应该对其进行回收。

/**
 * 删除指定位置处的元素
 * @param index : 需要被删除的下标
 * @return :     被删除指定位置的元素
 */
public E remove(int index){
    rangeCheck(index);
    E oldElement=elements[index];
    //将数组中的元素进行前移覆盖即可。
    for (int i = index+1; i < size; i++) {
        //可以保证下标不会出现越界的情况
        elements[i-1]=elements[i];
    }
    size--;
    return oldElement;
}

处理之后:

public E remove(int index){
    rangeCheck(index);
    E oldElement=elements[index];
    //将数组中的元素进行前移覆盖即可。
    for (int i = index+1; i < size; i++) {
        //可以保证下标不会出现越界的情况
        elements[i-1]=elements[i];
    }
    elements[--size]=null;
    return oldElement;
}

3 indexOf的实现

对于对象的对比,一般是可以自定义是否相等的规则,所以不建议使用==

public int indexOf(E element){
    for (int i = 0; i < size; i++) {
        if (elements[i]==element){
            return i;
        }
    }
    return ELEMENT_NOT_FOUND;
}

修改之后,建议使用equals

public int indexOf(E element) {

    for (int i = 0; i < size; i++) {
        if (elements[i].equals(element)) {
            return i;
        }
    }
    return ELEMENT_NOT_FOUND;
}

4 Null值的处理

​ 是否允许存放空值,这个规则由我们来定,默认的情况下是允许存放空值的,如果不允许存放空值,那么我们可以进行如下的处理。

public void add(int index,E element){
    if (element==null){
        return;
    }
    //注意此处的参数判断:index和size的大小允许相等。
    rangeCheckAdd(index);
    ensureCapacity(size+1);
    for (int i = size-1; i>=index ; i--) {
        elements[i+1]=elements[i];
    }
    elements[index]=element;
    showInfo();
    size++;
}

但是我们在这里是允许存放空值,因此我们还需要对indexOf这个方法进行处理。

public int indexOf(E element) {
    if (element == null) {
        for (int i = 0; i < size; i++) {
            if (elements[i] == null) {
                return i;
            }
        }
    } else {
        for (int i = 0; i < size; i++) {
            if (element.equals(elements[i])) {
                return i;
            }
        }
    }
    return ELEMENT_NOT_FOUND;
}

如果在这里不对其进行处理,如果某个元素是空,会造成空指针异常的出现。

7 最终代码的实现
package day01;

public class DynamicArray<E> {
    /**
     * 记录元素的数量
     */
    private int size = 0;
    /**
     * 存放元素
     */
    private E[] elements;
    private static final int DEFAULT_CAPACITY = 10;
    private static final int ELEMENT_NOT_FOUND = -1;

    public DynamicArray(int capacity) {
        capacity = (capacity < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capacity;

        elements = (E[]) new Object[capacity];
    }

    public DynamicArray() {
        //初始化参数
        this(DEFAULT_CAPACITY);
    }

    /**
     * 返回集合中元素的个数
     *
     * @return :元素的个数
     */
    public int size() {
        return size;
    }

    /**
     * 向动态数组中添加元素
     *
     * @param element :待添加的元素
     */
    public void add(E element) {
        add(size, element);
    }

    public void add(int index, E element) {
       /* if (element==null){
            return;
        }*/
        //注意此处的参数判断:index和size的大小允许相等。
        rangeCheckAdd(index);
        /*for (int i = size; i >index; i--) {
            elements[i]=elements[i-1];
        }*/
        ensureCapacity(size + 1);
        for (int i = size - 1; i >= index; i--) {
            elements[i + 1] = elements[i];
        }
        elements[index] = element;
        showInfo();
        size++;
    }

    /**
     * 保证要有capacity的容量足够
     *
     * @param capacity: 当前数组中元素的size+1
     */
    public void ensureCapacity(int capacity) {
        int oldCapacity = elements.length;
        if (oldCapacity >= capacity) {
            return;
        }
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        E[] newElements = (E[]) new Object[newCapacity];
        //elements=Arrays.copyOf(elements,newCapacity);
        //对数组进行复制.这里采用这种方式当数据量比较大的时候,可以采用系统级别的拷贝方式,例如System.arraycopy();
        for (int i = 0; i < size; i++) {
            newElements[i] = elements[i];
        }
        elements = newElements;

    }

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

    /**
     * 获取指定下标的元素
     *
     * @param index :指定的下标
     * @return :指定下标的元素
     */
    public E get(int index) {
        rangeCheck(index);
        return elements[index];
    }

    /**
     * 删除指定位置处的元素
     *
     * @param index : 需要被删除的下标
     * @return :     被删除指定位置的元素
     */
    public E remove(int index) {
        rangeCheck(index);
        E oldElement = elements[index];
        //将数组中的元素进行前移覆盖即可。
        for (int i = index + 1; i < size; i++) {
            //可以保证下标不会出现越界的情况
            elements[i - 1] = elements[i];
        }
        //注意此处的是先--再清空。
        elements[--size] = null;
        return oldElement;
    }

    /**
     * 设置指定位置的元素,并返回改位置的就元素
     *
     * @param index   :指定下标
     * @param element :替换的元素
     * @return :被替换的元素
     */
    public E set(int index, E element) {
        rangeCheck(index);
        E old = elements[index];
        elements[index] = element;
        return old;
    }

    /**
     * 查看元素的索引
     *
     * @param element :查找的元素
     * @return :返回第一次出现指定元素的下标
     */
    public int indexOf(E element) {
        if (element == null) {
            for (int i = 0; i < size; i++) {
                if (elements[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (element.equals(elements[i])) {
                    return i;
                }
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    /**
     * 是否包含某值
     *
     * @param element :元素值
     * @return :是否包含某元素
     */
    public boolean contains(E element) {
        return indexOf(element) != ELEMENT_NOT_FOUND;
    }

    /**
     * 清除集合
     */
    /*public void clear(){
        size=0;
    }*/
    public void clear() {
        for (int i = 0; i < size; i++) {
            elements[i] = null;
        }
        size = 0;
    }

    private void outOfBounds(int index) {
        throw new IndexOutOfBoundsException("index:" + index + ",size:" + size);
    }

    private void rangeCheck(int index) {
        if (index < 0 || index >= size) {
            //抛出数组下标越界异常
            outOfBounds(index);
        }
    }

    private void rangeCheckAdd(int index) {
        if (index < 0 || index > size) {
            //抛出数组下标越界异常
            outOfBounds(index);
        }
    }

    public void showInfo() {
        System.out.println(elements.length);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("size=").append(size).append(",[");
        for (int i = 0; i < size - 1; i++) {
            builder.append(elements[i] + ",");
        }
        if (size>0){
            builder.append(elements[size - 1] + "]");
        }else {
            builder.append("]");
        }
        return builder.toString();
    }

    //对象死亡之后的回调函数
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("person - finalized");
    }
}
int index) {
        if (index < 0 || index > size) {
            //抛出数组下标越界异常
            outOfBounds(index);
        }
    }

    public void showInfo() {
        System.out.println(elements.length);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("size=").append(size).append(",[");
        for (int i = 0; i < size - 1; i++) {
            builder.append(elements[i] + ",");
        }
        if (size>0){
            builder.append(elements[size - 1] + "]");
        }else {
            builder.append("]");
        }
        return builder.toString();
    }

    //对象死亡之后的回调函数
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("person - finalized");
    }
}
3 扩展
1 add的进一步优化:只是一个小细节
public void add(int index, E element) {
    /* if (element==null){
            return;
        }*/
    //注意此处的参数判断:index和size的大小允许相等。
    rangeCheckAdd(index);
    /*for (int i = size; i >index; i--) {
            elements[i]=elements[i-1];
        }*/
    ensureCapacity(size + 1);
    for (int i = size - 1; i >= index; i--) {
        elements[i + 1] = elements[i];
    }
    elements[index] = element;
    showInfo();
    size++;
}

对其中的for循环进行优化

可以从size开始,index+1结束,即代码如下

public void add(int index, E element) {
    /* if (element==null){
            return;
        }*/
    //注意此处的参数判断:index和size的大小允许相等。
    rangeCheckAdd(index);
    ensureCapacity(size + 1);
    for (int i = size; i >index; i--) {
            elements[i]=elements[i-1];
    }
    elements[index] = element;
    showInfo();
    size++;
}
2 添加一个remove方法

根据元素来删除集合中第一次出现的该元素

public E remove(E element){
    E remove = remove(indexOf(element));
    return remove;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值