动态数组原理【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 ...
随机推荐
- LeetCode529. 扫雷游戏 Python3 DFS+BFS+注释
https://leetcode-cn.com/problems/minesweeper/solution/python3-dfsbfszhu-shi-by-xxd630/ 规则: 'M' 代表一个未 ...
- 基于C# WPF框架的贪吃蛇
游戏开始界面 游戏开始 共有两条蛇,吃到红色食物加1分,吃到绿色毒食物减1分,知道0不减: 碰到墙壁游戏结束,碰到对方游戏结束,碰到自己游戏结束 此游戏通过Canvas画布布局,通过C#代码实现 游戏 ...
- [ASP.NET Core 3框架揭秘] 文件系统[4]:程序集内嵌文件系统
一个物理文件可以直接作为资源内嵌到编译生成的程序集中.借助于EmbeddedFileProvider,我们可以采用统一的编程方式来读取内嵌的资源文件,该类型定义在 "Microsoft.Ex ...
- 【CV现状-2】三维感知
#磨染的初心--计算机视觉的现状 [这一系列文章是关于计算机视觉的反思,希望能引起一些人的共鸣.可以随意传播,随意喷.所涉及的内容过多,将按如下内容划分章节.已经完成的会逐渐加上链接.] 缘起 三维感 ...
- 因特尔CPU上TM和R标识的区别
TM是英文trademark的缩写,TM标志并非对商标起到保护作用,它与R不同,TM表示的是该商标已经向国家商标局提出申请,并且国家商标局也已经下发了<受理通知书>,进入了异议期,这样就可 ...
- iOS核心动画高级技巧-5
9. 图层时间 图层时间 时间和空间最大的区别在于,时间不能被复用 -- 弗斯特梅里克 在上面两章中,我们探讨了可以用CAAnimation和它的子类实现的多种图层动画.动画的发生是需要持续一段时间的 ...
- 2019年Java面试题基础系列228道(2)
21.描述一下 JVM 加载 class 文件的原理机制? JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的类加载器是一个重要的 Java 运行时系统组件,它 ...
- Android Studio中的AndroidManifest.xml文件分析
一.关于AndroidManifest.xml AndroidManifest.xml清单文件是每个Android程序中必须的文件,它是整个Android程序的全局描述文件,除了能声明程序中的Acti ...
- Python语法规则
Python基本语法 Python的语法相对比C,C++,Java更加简洁,比较符合人的正常思维.本篇介绍Python的基本语法,通过本篇文章你可以学到以下内容. 掌握Python的基本语法 识别Py ...
- linux查看磁盘及文件夹大小命令
https://www.runoob.com/w3cnote/linux-view-disk-space.html 1.使用lsof查看已删除但未释放的文件 lsof -n | grep delete ...