什么是数组
看到这个问题时,想必答案已经在你脑中了吧.通俗的来说: 数组是用一组连续的内存空间,来存储一组具有相同类型的数据。 也可以说数组的作用是将相同类型的数据,存储在一组连续的内存空间中.
连续的内存空间:关键字连续,为什么需要连续的内存空间呢,难道不连续不可以吗?
根据《Java虚拟机规范》的规定,Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的,这点就像我们用磁盘空间去存储文件一样,并不要求每个文件都连续存放。但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的考虑,很可能会要求连续的内存空间。
正是因为连续的内存空间和相同类型的数据,才使得数据具有随机访问的特性,然而成也风云,败也风云.因为"风云"使得数据的很多操作都变得低效,例如对数据执行删除或插入操作时,为了保证连续性,就需要做大量的搬移工作.
如何创建
Java 中,使用[]来定义一个数组.常见的创建方式有以下三种
//1)使用长度定义数组
int[] arr = new int[3];
//2)使用数据直接定义数组
int[] arr = new int[]{1,2,3};
//3)使用数据直接定义
int[] arr = {1,2,3};
注意:数组一旦被定义,其长度便无法更改,如果需要更改,可定义一个新的数据,将原有数据copy过去即可
不同的数据类型在内存中存储的方式不同
- int、long、char等基本数据类型的数据
new申请的空间在堆上,arr存储在栈上。arr存储的是数组空间的首地址。
从图中来看,在Java中,基本数据类型数组还是符合数据结构中数组的定义的。数组中数据是相同类型的、并且存储在一片连续的内存空间中。
- 存储对象类型的数据
在Java中,对象数组中存储的是对象在内存中的地址,而非对象本身。对象本身在内存中并不是连续存储的,而是散落在各个地方的。
如何使用
赋值
int[] arr = new int[3];
arr[2] = 3;//给下标为2赋值为 3
arr[2] = 6;//如果再次赋值,原来的数据将被覆盖
插入
- 新建数组,对原数组扩容
- 将原数组数据赋值给新数组
- 将大于i的数据向后移动一位
- 赋值到k位置
如果数组只是作为一个存储功能的容器且里面的数据元素是无序的;为了避免大规模的数据搬移,可以直接将第 k 位的数据搬移到数组元素的最后,把新的元素直接放入第 k 个位置,反之只能采取移动数据元素的方式。
删除
跟插入数据的操作类似,为了内存的连续性,也需要搬移数据。
如果要删除多个数据时,难道我们每次删除都要进行数据搬移吗?就好比在生活中,当我们有想要扔掉的东西时,每次我们都会把东西拿出去吗?当然不是,实际上我们只是将它放到了垃圾通里面,而此时并没有消失,只是被"标记"成了垃圾。只有垃圾桶满了,才会清理垃圾桶。
我们可以利用这种思想,当要删除多个数据时,先将其"标记"(扔到垃圾桶),当标记完成后(垃圾桶满了),在执行删除操作,进行数据搬移(清理垃圾桶)。
有的舍友可能会说:"把东西扔到垃圾桶,那不是移动了位置吗?"
可以这样里理解:将东西扔到垃圾桶只是"标记"的一种实现方式,例如我们还可以在要扔弃的东西上写上"这是个垃圾".
熟悉JVM的舍友,可能会发现这不正是标记清除垃圾回收算法的思想吗?
JVM标记清除算法:
大多数主流虚拟机采用可达性分析算法来判断对象是否存活,在标记阶段,会遍历所有 GC ROOTS,将所有 GC ROOTS 可达的对象标记为存活。只有当标记工作完成后,清理工作才会开始。具体的内容后面会有专门文章介绍.
索引越界
创建数组时,就已经指定了数组长度.假如数组长度为k,数组的索引值是从0开始,那么索引的范围为 0--(k-1),
int[] arr = new int[3];
arr[3] = 6;//会抛出 java.lang.ArrayIndexOutOfBoundsException
应用场景
- 在平常的开发中大多数都是做业务开发,我们直接使用容器就足够了,因为它将对数据的的操作细节进行封装,使用起来非常方便.如果对性能要求很高的话,那么优先使用数组
- 开发中,如果已知数据大小,且没有复杂操作,那么也可以直接使用数组.