前言

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

集合入门

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

public class QueueDemo {

    private int Size;

    private Integer[] Elements;

}

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

public class QueueDemo {

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

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

    // 添加元素
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--;
}

接下来我们再来删除并打印元素,如下:

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

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

    @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源码分析将得心应手,感谢阅读,我们下节见。

动态数组原理【Java实现】(六)的更多相关文章

  1. 动手编写—动态数组(Java实现)

    目录 数组基础回顾 自定义动态数组 动态数组的设计 抽象父类接口设计 抽象父类设计 动态数组之DynamicArray 补充数组缩容 全局的关系图 声明 数组基础回顾 1.数组是一种常见的数据结构,用 ...

  2. ArrayList实现动态数组原理

    addAll方法和申请数组大小函数 public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); ...

  3. Java动态数组

    其中java动态数组: Java动态数组是一种可以任意伸缩数组长度的对象,在Java中比较常用的是ArrayList,ArrayList是javaAPI中自带的java.util.ArrayList. ...

  4. java动态数组笔记

    动态数组: 在java.lang.reflect包下提供了Array类,包括一系列static方法,通过这些方法可动态的创建数组.给元素赋值.取出元素值等等 //理解数组引用——下面定义的objs数组 ...

  5. 常用数据结构-线性表及Java 动态数组 深究

    [Java心得总结六]Java容器中——Collection在前面自己总结的一篇博文中对Collection的框架结构做了整理,这里深究一下Java中list的实现方式 1.动态数组 In compu ...

  6. 一篇文章让你了解动态数组的数据结构的实现过程(Java 实现)

    目录 数组基础简单回顾 二次封装数组类设计 基本设计 向数组中添加元素 在数组中查询元素和修改元素 数组中的包含.搜索和删除元素 使用泛型使该类更加通用(能够存放 "任意" 数据类 ...

  7. 算法入门 - 动态数组的实现(Java版本)

    静态数组 Java中最基本的数组大家肯定不会陌生: int[] array = new int[6]; for (int i = 0; i < array.length; i++){ array ...

  8. java动态代理原理

    我们经常会用到Java的动态代理技术, 虽然会使用, 但是自己对其中的原理却不是很了解.比如代理对象是如何产生的, InvocationHandler的invoke方法是如何调用的?今天就来深究下Ja ...

  9. 动态代理 原理简析(java. 动态编译,动态代理)

    动态代理: 1.动态编译 JavaCompiler.CompilationTask 动态编译想理解自己查API文档 2.反射被代理类 主要使用Method.invoke(Object o,Object ...

随机推荐

  1. linux指令-date

    1.在linux中要显示日期,则可以直接输入指令date 2.如果想以这样2016/12/26的方式输出呢,那就是,Y是年份,m是月份,d是日 date +%Y/%m/%d 3.如果要显示时间,则da ...

  2. Spring Boot微服务电商项目开发实战 --- 分布式开发要避的那些坑

    今天已经进入第七讲了,整个微服务架构的搭建工作也基本完成.那到目前为止究竟使用了那些技术及实现了什么功能呢?我们先回顾一下. 使用的技术:SpringBoot.Dubbo.Zookeeper.Redi ...

  3. c语言从入门到精通的几个阶段

    本文主要结合往期学员学习阶段,因材施教整理的几个阶段学习路线知识点: 1.初级教程 初级c语言入门教程比较适合零基础的小白,这个周期一般在22天,度过这个阶段的小白,基本上已经拥有了编程思维,且能开发 ...

  4. PHP实现微信扫码自动登陆与注册,参考实例

    微信开发已经是现在phper必须要掌握的一项基本的技术了,其实做过微信开发的都知道微信接口非常的强大做起来也非常的简单,这里我们一起来看一个微信自动登陆注册的例子. php 微信扫码 pc端自动登陆注 ...

  5. 使用 ASP.NET Core MVC 创建 Web API(六)

    使用 ASP.NET Core MVC 创建 Web API 使用 ASP.NET Core MVC 创建 Web API(一) 使用 ASP.NET Core MVC 创建 Web API(二) 使 ...

  6. SpringBoot系列-整合Mybatis(XML配置方式)

    目录 一.什么是 MyBatis? 二.整合方式 三.实战 四.测试 本文介绍下SpringBoot整合Mybatis(XML配置方式)的过程. 一.什么是 MyBatis? MyBatis 是一款优 ...

  7. 精通awk系列(15):awk数据类型和字面量

    回到: Linux系列文章 Shell系列文章 Awk系列文章 数据类型 gawk有两种基本的数据类型:数值和字符串.在gawk 4.2.0版本中,还支持第三种基本的数据类型:正则表达式类型. 数据是 ...

  8. python高阶函数——sorted排序算法

    python 内置的sorted()函数可以对一个list进行排序: >>> sorted([8,3,8,11,-2]) [-2, 3, 8, 8, 11] 既然说是高阶函数,那么它 ...

  9. centos7下安装客户端rabbitmq9.0

    下载目前最新rabbitmq客户端版本: wget https://github.com/alanxz/rabbitmq-c/archive/v0.9.0.tar.gz php扩展 : wget ht ...

  10. 分布式CAP与BASE理论

    参考: CAP和BASE理论 https://juejin.im/post/5d720e86f265da03cc08de74 https://github.com/changmingxie/tcc-t ...