动态数组原理【Java实现】(六)
前言
接下来我们进入集合学习,看过很多文章一上来就是讲解原理感觉会特别枯燥,任何成熟解决方案的出现都是为了解决问题,若通过实际问题引入然后再来讲解原理想必学起来必定事半功倍,从我写博客的那一天起,我就在思考如何通过通俗易懂的话让看到文章的童鞋立马能明白我讲解的什么,即使文章很长若是层层递进定不会感到枯燥乏味,所以我脑海里一直在高度不停旋转着去找合适的例子。关于集合学习将分为例子引入、源码分析、数据结构分析三个部分来进行阐述。
集合入门
我们以一个例子来进行集合入门讲解,上学时我们上体育课,首先体育老师会叫我们多少多少同学站成一对来进行报名,然后自由解散休息,哈哈。体育老师要求我们站成一对,这个时候就好对数组进行数据存储,我们假设体育老师需要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实现】(六)的更多相关文章
- 动手编写—动态数组(Java实现)
目录 数组基础回顾 自定义动态数组 动态数组的设计 抽象父类接口设计 抽象父类设计 动态数组之DynamicArray 补充数组缩容 全局的关系图 声明 数组基础回顾 1.数组是一种常见的数据结构,用 ...
- ArrayList实现动态数组原理
addAll方法和申请数组大小函数 public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); ...
- Java动态数组
其中java动态数组: Java动态数组是一种可以任意伸缩数组长度的对象,在Java中比较常用的是ArrayList,ArrayList是javaAPI中自带的java.util.ArrayList. ...
- java动态数组笔记
动态数组: 在java.lang.reflect包下提供了Array类,包括一系列static方法,通过这些方法可动态的创建数组.给元素赋值.取出元素值等等 //理解数组引用——下面定义的objs数组 ...
- 常用数据结构-线性表及Java 动态数组 深究
[Java心得总结六]Java容器中——Collection在前面自己总结的一篇博文中对Collection的框架结构做了整理,这里深究一下Java中list的实现方式 1.动态数组 In compu ...
- 一篇文章让你了解动态数组的数据结构的实现过程(Java 实现)
目录 数组基础简单回顾 二次封装数组类设计 基本设计 向数组中添加元素 在数组中查询元素和修改元素 数组中的包含.搜索和删除元素 使用泛型使该类更加通用(能够存放 "任意" 数据类 ...
- 算法入门 - 动态数组的实现(Java版本)
静态数组 Java中最基本的数组大家肯定不会陌生: int[] array = new int[6]; for (int i = 0; i < array.length; i++){ array ...
- java动态代理原理
我们经常会用到Java的动态代理技术, 虽然会使用, 但是自己对其中的原理却不是很了解.比如代理对象是如何产生的, InvocationHandler的invoke方法是如何调用的?今天就来深究下Ja ...
- 动态代理 原理简析(java. 动态编译,动态代理)
动态代理: 1.动态编译 JavaCompiler.CompilationTask 动态编译想理解自己查API文档 2.反射被代理类 主要使用Method.invoke(Object o,Object ...
随机推荐
- MySql数据库之常用数据类型及常用约束简述
本文呢,主要给大家简述一下数据库中常用的几种数据类型以及约束. 1.数据类型 数据类型,是指数据表中可以存储的数据的种类. 数据库中常用的数据类型有: 1.整型:int.bit 2.小数:decima ...
- Golang 入门系列(十二)ORM框架gorm
之前在已经介绍了用的github.com/go-sql-driver/mysql 访问数据库,不太了解的可以看看之前的文章 https://www.cnblogs.com/zhangweizhong/ ...
- 原生js复制粘贴上传图片前后台代码,兼容firebox,chrome, ie11,亲测有效
需求:粘贴上传图片,截图工具,右键粘贴,或者ctrl+v粘贴 方法1:可直接套用富文本框的图片上传功能,完成复制粘贴 缺点:麻烦,样式难控制 方法2:用原生js完成,以下案例基于此,样式请自己动手调整 ...
- Android 基于ksoap2的webservice请求的学习
[学习阶段] WebService网络请求? 其实我也是第一次遇到,之所以有这个需要是因为一些与 ERP 相关的业务,需要用到这样的一个请求方式. 开始学习WebService ①当然是百度搜索,这里 ...
- java 获取当前年份 月份,当月第一天和最后一天
获取当前年份 月份,当月第一天和最后一天,工作中会经常用到,下面是代码: package basic.day01; import java.text.SimpleDateFormat; import ...
- vue学习笔记(十)路由
前言 在上一篇博客vue学习笔记(九)vue-cli中的组件通信内容中,我们学习组件通信的相关内容和进行了一些组件通信的小练习,相信大家已经掌握了vue-cli中的组件通信,而本篇博客将会带你更上一层 ...
- 【SVN搭建】搭建SVN服务
1.安装 # yum -y install subversion 2.配置 创建仓库 我们这里在/opt下建立一个名为svn的仓库(repository),所有代码都可以放在这个下面,创建成功后在sv ...
- C语言程序设计100例之(11):求质数
例11 求质数 问题描述 质数是指除了有1和自身作为约数外,不再有其他约数的数.比如:3.5.7是质数.而9不是质数,因为它还有约数3. 编写程序求给定区间中的所有质数. 输入格式 两个整数a和b, ...
- 一起学Spring之基础篇
本文主要讲解Spring的基础环境搭建以及演变由来,仅供学习分享使用,如有不足之处,还请指正. 什么是Spring ? Spring是一个开源框架,用来处理业务逻辑层和其他层之间的耦合问题.因此Spr ...
- jvm虚拟机笔记<三> 类文件结构与类加载机制
java虚拟机具有语言无关系,它只和“class文件“这种特定的二进制文件格式绑定. 不同语言的编译器将对应的程序编译成字节码文件(*.class),送给jvm执行. class文件本质上就是一张表, ...