动态数组原理【Java实现】(六)

前言

接下来咱们进入集合学习,看过不少文章一上来就是讲解原理感受会特别枯燥,任何成熟解决方案的出现都是为了解决问题,若经过实际问题引入而后再来说解原理想必学起来一定事半功倍,从我写博客的那一天起,我就在思考如何经过通俗易懂的话让看到文章的童鞋立马能明白我讲解的什么,即便文章很长如果层层递进定不会感到枯燥乏味,因此我脑海里一直在高度不停旋转着去找合适的例子。关于集合学习将分为例子引入、源码分析、数据结构分析三个部分来进行阐述。数组

集合入门

咱们以一个例子来进行集合入门讲解,上学时咱们上体育课,首先体育老师会叫咱们多少多少同窗站成一对来进行报名,而后自由解散休息,哈哈。体育老师要求咱们站成一对,这个时候就好对数组进行数据存储,咱们假设体育老师须要5我的站成一对,因此这也就对应数组初始化的容量,有了容量以后,接下来则是在所要求的地方站到指定位置,这就比如往数组里添加元素,好了接下来咱们以Java语言来实现这一要求。首先咱们定义一个排队类,依据面向对象封装思想,咱们对外提供操做方法,因此在此类中定义私有的数组,而后定义集合中元素个数属性,以下:数据结构

public class QueueDemo {

    private int Size;

    private Integer[] Elements;

}

依据咱们所分析,咱们按照5人站成一对,因此当咱们初始化排队类时就初始化数组容量,也就是说咱们在构造函数中初始化容量,以下:app

public class QueueDemo {

    // 数组大小
    private int Size;

    // 数组
    private Integer[] Elements;
    
    // 初始化数组容量
    public QueueDemo(int capacity) {
        Elements = new Integer[capacity];
    }

}

咱们初始化容量后呢,接下来则是对应同窗开始排队,此时也就是对应往数组里添加元素,因此咱们封装一个添加方法,每添加一个元素则数组大小则递增1,以下:ide

    // 添加元素
    public void Add(Integer element) {
        Elements[Size] = element;
        Size++;
    }

对应每一步操做,咱们都遍历打印出数组中元素,因此接下来咱们重写toString方法,以下:函数

  // 重写toString打印元素
    @Override
    public String toString() {
        int length = Elements.length;
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < length; i++) {
            sb.append(Elements[i]);
            if (i != length - 1) {
                sb.append(",");
            }
        }
        return sb.toString();
    }

咱们完成了同窗排队第一步,接下来咱们实例化上述排队类并添加元素(咱们将元素看作时排队时同窗们的姓名),最后打印元素,以下:源码分析

public class Main {

    public static void main(String[] args) {
        QueueDemo demo = new QueueDemo(5);
        demo.Add(1);
        demo.Add(2);
        demo.Add(3);
        demo.Add(4);
        demo.Add(5);
        System.out.println(demo);
    }
}

当同窗们排成一队后,体育老师发现排队的同窗高矮不一,而后将同窗与同窗之间按照高矮进行调换,这也就对应着咱们须要封装插入元素的方法,由于咱们初始化数组容量为5,当咱们在指定索引插入一个元素时,再打印元素必然抛出数组异常,也就是涉及到数组容量扩容,咱们暂且定义在指定索引插入元素的方法,以下:性能

public void Insert(int index, Integer element) {
        
}

当按照高矮排好队后,体育老师认为一列只需排4我的,剩余一我的到其余对去,这也就对应删除数组中的元素,一样咱们定义删除方法,当咱们删除数组中元素时,须要将删除的元素后的元素都往前移一位,同时将最后一位置为空,数组大小也减小一位,以下:学习

// 删除元素
public void Remove(Integer element) {
        int index = GetIndex(element);
        for (int i = index; i < Elements.length - 1; i++) {
            Elements[i] = Elements[i + 1];
        }
        Elements[Size - 1] = null;
        Size--;
}

接下来咱们再来删除并打印元素,以下:ui

        //删除元素
        demo.Remove(3);
        System.out.println(demo);

 由于咱们将数组最后一位元素置为空,因此在打印时应删除,咱们继续改造重写的toString方法,以下:this

    @Override
    public String toString() {
        int length = Elements.length;
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < length; i++) {
            if (Elements[i] == null) {
                continue; }
            sb.append(Elements[i]);
            if (i != length - 1) {
                sb.append(",");
            }
        }
        if (sb.charAt(sb.length() - 1) == ',') {
            sb.delete(sb.length() - 1, sb.length()); }
        sb.append("]");
        return sb.toString();
    }

 接下来体育老师要求报数,好比根据某个同窗的姓名即元素报出本身所在的第几位(也就对应数组中的索引),因此此时咱们再封装一个获取指定元素的索引方法,以下:

// 获取指定元素索引
public int GetIndex(Integer element) {
        for (int i = 0; i < Elements.length - 1; i++) {
            if (!Elements[i].equals(element)) {
                continue;
            }
            return i;
        }
        throw new RuntimeException("未找到");
}

而后咱们尝试获取4号同窗所排队的位置是,以下:

System.out.println("4号同窗所在的位置是 :" + demo.GetIndex(4));

到了这里咱们完成了排队的基本要求,可是还远远不够,好比咱们是自定义初始化容量,这里咱们指定为5,通过删除操做后,最终数组中存在4个元素,要是咱们再往数组中添加至少2个以上元素,此时打印数组元素将抛出异常,因此这里为了解决这个问题咱们对数组进行自动扩容,也就是对添加方法进行改造,当添加元素时咱们须要判断是否已经超过数组容量,若超过,咱们将数组容量扩大到现有数组容量的2倍,那么咱们应该怎么判断呢?咱们经过数组大小和数组容量进行判断,以下: 

    public void Add(Integer element) {
        if (Size >= Elements.length) {
            Elements = Arrays.copyOf(Elements, 2 * Elements.length); }
        Elements[Size] = element;
        Size++;
    }
public static void main(String[] args) {
        QueueDemo demo = new QueueDemo(5);
        demo.Add(1);
        demo.Add(2);
        demo.Add(3);
        demo.Add(4);
        demo.Add(5);
        System.out.println(demo);
        //删除元素
        demo.Remove(3);
        System.out.println(demo);
        System.out.println("4号同窗所在的位置是 :" + demo.GetIndex(4));

        demo.Add(6);
        demo.Add(7);
        System.out.println(demo);
}

在排队时咱们给定人数为5,也就说数组初始化容量为5,这还不够灵活,若是体育老师已经明确规定一列必须站几个,咱们直接就能接受到体育老师规定的信号,这就像明确指定了数组的初始化容量,这样一来既能保证不会抛出异常,同时也不会影响当添加和插入元素时扩容时所带来的性能开销,若是未明确规定一列站几个,咱们也能够默认初始化容量,如此最灵活,一切都未变且性能最佳,以下咱们定义一个默认初始化容量并改造排队列构造函数,以下:

    private int DEFAULT_CAPACITY = 10;

    public QueueDemo() {
        Elements = new Integer[DEFAULT_CAPACITY];
    }

    public QueueDemo(int capacity) {
        Elements = new Integer[capacity <= 0 ? DEFAULT_CAPACITY : capacity];
    }

到了这里咱们还未实如今指定索引位置插入元素的Insert方法,既然要插入指定索引位置,首先咱们必须检查指定索引位置是否超过数组大小,而后将指定索引后的元素向后移动一位,最后留出指定索引位置进行插入,以下:

public void Insert(int index, Integer element) {
        if (Size <= index || index < 0) {
            throw new RuntimeException("超出数组边界");
        }
        System.arraycopy(Elements, index, Elements, index + 1,
                Size - index);
        System.out.println(this);
        Elements[index] = element;
        Size++;
 }
        QueueDemo demo = new QueueDemo();
        demo.Add(1);
        demo.Add(2);
        demo.Add(3);
        demo.Add(4);
        demo.Add(5);
        System.out.println(demo);
        //删除元素
        demo.Remove(3);
        System.out.println(demo);
        System.out.println("4号同窗所在的位置是 :" + demo.GetIndex(4));

        demo.Add(6);
        demo.Add(7);
        System.out.println(demo);

        //插入元素
        demo.Insert(2, 20);
        System.out.println(demo);

如上咱们首先检查指定索引是否小于0或者是否超出数组大小,不然抛出异常,而后这里咱们经过内置提供的方法,从指定索引位置后的元素进行复制即Index+1,最后复制元素的长度为Size-Index。此时指定索引位置数据仍为4,最后咱们将指定索引位置的值经过咱们要插入的值进行替换。

总结

如上则是咱们实现比较完整的排队需求,固然还有一些参数检查的小问题,看到这里想必不少童鞋就已经清楚知道了,其实咱们实现的就是Java中的集合,有了本节课的基础,下节课咱们进行ArrayList源码分析将驾轻就熟,感谢阅读,咱们下节见。

相关文章
相关标签/搜索