数据结构之美-01-数组

192 阅读4分钟

数组是一种线性表数据结构。他用一组连续的内存空间来存储一组具有相同类型的数据。 计算机会给每个内存单元分配地址,计算机通过地址来访问内存中的数据。他会通过下面的寻址公式,计算出存储的内存地址:

a[i]_address = base_address + i * data_type_size

data_type_size表示数组中每个元素的大小。

数组的插入和删除是非常低效的。因为是连续的内存空间,如果要插入一个中间位置的话,需要把插入位置之后的数据往后面移动一位,因此这个是低效的,删除的时候也是相同的道理。

为了避免数据清除之后被重新移位置,可以先记录已经删除的数据,每次的删除操作并不是真正搬移数据,只是记录被删除,当数组的存储空间不足的时候,再来触发真正的删除操作,这样就可以减少移动数据空间造成的内存吃紧的问题,这也是jvm的标记清楚回收算法的核心。

容器能否完全替代数组?比如arraylist,arraylist的最大优势是可以将很多的数组操作的细节封装起来,并且支持动态扩容。使用数组的 时候需要先定义好长度,使用arraylist的时候就不用关心底层的扩容逻辑,在空间不足的时候回自动扩容1.5倍。有些时候数组会更好用一些: 1、ArrayList无法存储基本类型,需要封装成Integer,Long类型,这些在自动装箱拆箱的时候会有一定的性能消耗, 2、如果数组操作简单,大小也知道可以考虑用数组 3、多维数组的时候用数组比较直观。

为什么数组从0开始? 下面的公式是从0开始,要找k个的话: a[k]_address = base_address + k * type_size

如果从1开始,要找k个的话: a[k]_address = base_address + (k-1)*type_size

这样cpu每次都要做进行一次减操作,所以为了减少cpu的计算,数组选择从0开始。

思考题:基于数组的原理引出 JVM 的标记清除垃圾回收算法的核心理念说说你理解的jvm垃圾回收算法

jvm里面采用可达性分析来判断对象是否存活,什么是可达性分析,通过gcroot 来判断一个对象是否被引用,如果未被引用,则判断该对象是可回收的,并标记为可以回收,但是有一定的缺点,标记再清楚之后,会造成内存空间不连续,这样在内存空间不足的时候比利于整理出有用的空间,会造成资源浪费

二维数组的地址问题:

可以把二维数组中成员看作行和列两个方向构成的格子中的数据,三维数组成员看作长宽高构成的立体格子中的数据,但这只是有助于理解的形象表示,实质上数组在内存中是连续线性的。

那么对于二维数组 x[]来说,求x[i][j]的时候(不会考虑i j越界的情况),要到i的时候,一定走完了ia2的长度,在x[i][0]往后找j个长度就是x[i][j],所以会从初始地址增加 (ia2+j)个单位长度。 对于三维数组, x[][]来说,求x[i][j][k]的时候,要到i的时候,一定走完了ia2a3的长度,在x[i][0][0]往后找ja3个长度就是x[i][j][0],再往后找k个长度就是x[i][j][k],所以会从初始地址增加 (ia2a3+ja3+k)个单位长度。以此类推如下:

数组 为x ,an为某一维度长度 一维数组:(a1)x[i]_address = base_address + i * type_size 二维数组:(a1a2)x[i][j]_address = base_address + ( i * a2 + j ) * type_size 三位数组:(a1a2a3)x[i][j][k]_address = base_address + ( i * a2a3 + j * a3 + k ) * type_size 四位数组:(a1a2a3a4)x[i][j][k][l]_address = base_address + ( i * a2a3a4 + j a3a4 + k a4 + l ) * type_size 。。。。。。 n维数组:(a1a2a3*...an)x[i1][i2][i3]...[in] = base_address + ( i1 * a2a3...an + i2 * a3a4*...an + i3 * a4a5*...*an +......i(n-1) * an + in) * type_size