什么是数组?
数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
线性表
线性表就是数据排成像一条线一样的结构。
常见的线性表结构:数组,链表、队列、栈等。
非线性表结构:二叉树、堆、图等。连续的内存空间和相同类型的数据
优点:这两个限制使得具有随机访问的特性
缺点:删除、插入数据效率低
数组怎么根据下标随机访问的?
通过寻址公式,计算出该元素存储的内存地址:
a[i]_address = base_address + i * data_type_size
为何数组插入和删除低效?
插入:
若想往 int[n] 的第 k 个位置插入数据,需要将 k~n 位置上的元素往后移。
最好情况时间复杂度 O(1)
最坏情况时间复杂度为 O(n)
平均时间复杂度为 O(n)
如果数组中的数据不是有序的,也就是无规律的情况下,可以直接把第 k 个位置上的数据移到最后,然后将插入的数据直接放在第 k 个位置上。这样时间复杂度就为 O(1) 了。
删除:
与插入类似,为了保持内存的连续性。
最好情况时间复杂度 O(1)
最坏情况时间复杂度为 O(n)
平均时间复杂度为 O(n)
提高效率:将多次删除操作集中在一起执行,可以先记录已经删除的数据,但是不进行数据迁移,而仅仅是记录,当发现没有更多空间存储时,再执行真正的删除操作。这也是 JVM 标记清除垃圾回收算法的核心思想。
数组访问越界问题
C语言中的数组越界是一种未决行为,一般比较难发现的逻辑错误。在 C 语言中,只要不是访问受限的内存,所有的内存空间都是可以自由访问的,越界后访问到的是下一个地址上的数据。而Java 会有越界检查,越界后会抛出异常。
用数组还是容器?
数组先指定了空间大小,容器如 ArrayList 可以动态扩容。
- 希望存储基本类型数据,可以用数组
- 事先知道数据大小,并且操作简单,可以用数组
- 直观表示多维,可以用数组
- 业务开发,使用容器足够,开发框架,追求性能,首选数组。
为什么数组要从 0 开始编号?
由于数组是通过寻址公式,计算出该元素存储的内存地址:
a[i]_address = base_address + i * data_type_size
如果数组是从 1 开始计数,那么就会变成:
a[i]_address = base_address + (i-1)* data_type_size
对于CPU来说,多了一次减法的指令。
当然,还有一定的历史原因,比如让使用 C 语言的程序员更方便地迁移到使用 JAVA 等其他语言上。
JVM标记清除垃圾回收算法
先标记出所有存活的对象,在标记完成后统一回收所有没被标记的对象。
缺点:
- 每次进行垃圾回收时,会暂停当前用户程序的运行
- 垃圾回收器需要间隔性的检查,并且标记和清除的过程相对较慢,要两次扫描。
- 在标记清除之后可能会产生大量内存碎片,导致一旦需要为大对象分配空间时,由于找不到足够大的内存空间,而不得以引发另外一次GC过程
二维数组的的内存寻址公式
- 对于一维数组:a[i]_address = base_address + (i) * data_type_size
- 二维数组如果是 mn,那么a[i][j] _address = base_address + ( in + j) * data_type_size。
以上内容总结自极客时间的专栏《数据结构与算法之美》第 05 讲。