【数据结构】数组的二次封装(Java实现)

2019-01-09  本文已影响6人  猫留下你走吧

前言

数据结构很重要,因为大学起步晚。上数据结构的时候听是听得懂,但是怎么都写不出来。网上代码也不是很实用,也就是一套代码不可以复用,换个数据类型就不支持了。踏入企业才知道数据结构还是挺重要的,于是重新开始学习数据结构......把自己的学习记下来,然后日后能够复习的时候我想起我写过一篇关于数据结构的文章,我可以重新过一遍。同时,也能够帮助大家学习。

概要说明

Java原生的数组都很不好用,因为其初始化就要指明整个数组的长度,而且还不能修改。但我们希望数组的长度可以随着我们业务所需的数据量的大小,动态变化。而不是每次要想预估可能的数据量,因为凡事都有万一!而且实际发现可能用不了这么多的长度,也在浪费内存为数组开辟的那么大空间。
因此,我们希望再此基础上,进行二次封装,做一个可以动态扩、缩容的数组,且能够不断复用。

使用泛型

在复用性上,我们希望支持泛型。泛型可以放入任何数据类型的数据。当然,整个所谓的任何是不支持基本数据类型(int、double、float等)。估计对Java了解不深的人会跳起来了,你这不是坑人吗,我刚学可就是用这些数据类型,你告诉我不支持,我还学什么。取关!!!
其实,泛型虽然不支持基本数据类型,但可以通过“曲线救国”,使用包装类型。因为基本数据类型没有null概念,所以Java就提出了包装类。为什么要使用包装类,因为它太需要了:某A同学数据结构考了0分,B同学他缺考了,得了0分。老师认为B缺考性质恶劣,要让他没有补考机会。他就可以让B同学的分数为null。它就可以用double的升级版Double来记录分数区分他们。
基本数据类型对应的包装类型很简单,大部分是首字母大写就是它对应的包装类型。其实就是封装了基本数据类型为对象。记住一点:String它不是基本数据类型,从它的大写开头就知道了......
说了这么多,其实也是让自己加深对泛型和包装类的理解。

概要设计

需求

肯定要实现的功能:增、删、改、查
因为要实现动态扩、缩容:我要知道当前数组的容量多大了,还要知道我用了多少长度。每次扩、缩多少长度。
可能还要知道数组是不是为空?
我的业务场景只是每次向第一个或者最后一个坑位插数据,每次还要去判断插满了没,太复杂了。我希望封装起来。

整理
void add(int index,T value);
T remove(int index,T value);
void set(int index,T value);
T get(int index);
void resize(int capacity);
int size();
int capacity();
boolean isEmpty();
拓展
void addFirst(T value);
void addLast(T value);
T removeFirst()
T removeLast();
boolean contains(T value);
int find(T value);

实现代码

如何实现思路都是数据结构的书写的,不具体详细概述。具体有些在代码中也有注释,细心体会~

package com.example;

/**
 * 自定义数组(二次封装) - Java实现
 * 功能说明:
 * 1.根据数组自动扩/缩容
 * 2.封装常用数组操作
 *
 * @param <T> 对象类型
 */
public class Array<T> {

    //数据
    private T[] data;
    //数组大小
    private int size;

    /**
     * 无参构造方法
     * 初始化使用默认数组容量
     */
    public Array() {
        this(10);
    }

    /**
     * 有参构造方法
     * 初始化自定义数组容量
     *
     * @param capacity 容量值
     */
    public Array(int capacity) {
        this.data = (T[]) new Object[capacity];
        this.size = 0;
    }

    /**
     * 返回当前数组长度
     *
     * @return size
     */
    public int size() {
        return size;
    }

    /**
     * 返回当前数组是否为空
     *
     * @return true or false
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 获取数组容量
     *
     * @return 容量
     */
    public int capacity() {
        return data.length;
    }

    /**
     * 在指定索引添加值
     * 设计思路:
     * 1.判断索引的合法性
     * 2.判断是否空间已满,满了自动扩容
     * 3.将该索引之后数据往后挪一位
     * 4.放入数据
     * 5.数组长度+1
     *
     * @param index 索引
     * @param value 值
     */
    public void add(int index, T value) {
        if (index < 0 || index >= data.length)
            throw new IllegalArgumentException("add fail. index is illegal.");
        if (size == data.length)
            resize((int) (1.5 * size));
        for (int i = size - 1; i >= index; i--)
            data[i + 1] = data[i];
        data[index] = value;
        size++;
    }

    /**
     * 数组尾部添加值
     *
     * @param value 值
     */
    public void addLast(T value) {
        add(size, value);
    }

    /**
     * 数组头部添加值
     *
     * @param value 值
     */
    public void addFirst(T value) {
        add(0, value);
    }

    /**
     * 数组是否包含该值,包含则返回true,否则返回false
     *
     * @param value 值
     * @return true or false
     */
    public boolean contains(T value) {
        return find(value) != -1;
    }

    /**
     * 值第一次出现时的索引位值,如果没有该值返回-1
     *
     * @param value 值
     * @return index
     */
    public int find(T value) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(value)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 修改某个索引的值
     * 设计思路:
     * 1.判断索引的合法性
     * 2.修改
     *
     * @param index 索引
     * @param value 值
     */
    public void set(int index, T value) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("set value fail. index is illegal.");
        data[index] = value;
    }

    /**
     * 移除某个索引下的值
     * 设计思路:
     * 1.判断索引的合法性
     * 2.将索引之后的数组往前挪一位(覆盖)
     * 可选优化:将data[size-1] = null,垃圾回收机制会将其回收
     * 4.动态缩减容量
     * 3.数组长度-1
     *
     * @param index 索引
     */
    public void remove(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("remove fail. index is illegal.");
        for (int i = index + 1; i < size; i++)
            data[i - 1] = data[i];
        data[size - 1] = null;
        if (size == (capacity() / 2))
            resize(capacity() / 2);
        size--;
    }

    /**
     * 移除数组的第一个值
     */
    public void removeFirst() {
        remove(0);
    }

    /**
     * 移除数组的最后一个值
     */
    public void removeLast() {
        remove(size - 1);
    }

    /**
     * 删除指定元素
     *
     * @param value 值
     */
    public void removetValue(T value) {
        int index = find(value);
        if (index != -1)
            remove(index);
    }

    /**
     * 动态扩容
     * 扩容操作对外部来说应该是无感知的,因此将其私有
     */
    private void resize(int capacity) {
        T[] newData = (T[]) new Object[capacity];
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

}

说明

动态扩所容的实现原理很简单,其实就是重新创建一个数组,将原来的数据拷贝过去。在将指针指向新数组。
很多人会觉得很low,其实封装这种事情,就是为了掩藏low代码,屏蔽其中的细节。但有一个意识很重要:实现方式low不等于性能不好。如果有更好的思路去减少复杂度,是不错的选择!

功能验证

创建一个类和main方法:

package com.example;

public class Application {
    
    class Student {
        
        private  String name;
        
        private Integer age;

        public Student(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
    }

    public static void main(String[] args) {
        Array<String> array = new Array<>(100);
        array.addLast("张三");
        array.addLast("李四");
        System.out.println(array.removeLast());
        //复用示例
        Array<Student> array1 = new Array<>(50);
        //todo
    }
    
}

验证写的比较少,就是意思一下~博客写的比较累,验证已经没有动力写了,偷懒一下~自己动手试试

上一篇下一篇

猜你喜欢

热点阅读