初学者肯定有个疑问,为什么数组起始编号使用0开始的而不是1,这主要是历史原因
首C语言设计者从数组存储的内存模型来看,“下标”更确切的定义应该是“偏移 offset”,a[0]就是偏移为0的位置,自从他们开始使用0下标后,后面的Java等语言也效仿C语言,但也有一些语言的数组不是从0开始计数的,比如MATLAB,而且Python小老弟还说:我还支持负下标呢,那我骄傲了吗?
数组(Array)是一种线性表数据结构。它用一组连续的内存空间存储一组具有相同类型的数据。
解释一下定义中的名词:
线性表(Linear List),顾名思义就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前后两个方向,除了数组,链表、队列、栈等也是线性表结构。
与此相对的就是非线性表,他们的数据之间并不是简单的前后关系,比如二叉树、堆、图等。
**连续的内存空间和相同类型的数据:正因为这两个限制,数组才可以有“随机访问”**这个特性,但这两个限制也让数组的许多操作效率很低,比如数组中删除、插入一个数据,为了保证连续性,就要进行大量的数据搬移工作。
但是数组是如何根据下标实现随机访问数组元素的呢?
数组是适合查找操作,但是查找的时间复杂度并不是O(1),即使是排好序的数组用二分查找,时间复杂度也是O(logn),正确的表达是:数组支持随机访问,根据下标随机访问的时间复杂度是O(1)
说完了数组的“随机访问”,再聊聊数组的插入、删除为何低效
**插入:**假设一个数组长度为n,将一个数据插入到数组的第k个位置,为了把第k个位置腾出来,要把第k~n个位置都往后挪一位。所以最好情况是在尾部插入时间复杂度是O(1),最坏是头插入时间复杂度是O(n),平均时间复杂度是O(n)。
**删除:**要删除第k个位置,为了保证内存的连续性,要把第k~n个数据向前挪一位,情况和插入一样。
提问:在Java中会有数组越界的情况,在C语言中也会报错吗?
答案是不会报错,因为在C语言中只要不是访问受限的内存,所有的内存空间都是可以自由访问的,数组越界在C语言中是一种未决行为,并没有规定数组访问越界时编译器应该如何处理。因为访问数组的本质就是访问一段连续内存,只要数组通过偏移计算得到的内存地址是可用的,就不会报错的。
那我作为一个Java工程师,几乎每天用到最多的就是ArrayList,好像也不经常用数组啊,这两个有什么区别吗?
首先针对数组类型,许多语言都提供了容器类,比如Java中的ArrayList、C++STL中的vector。
ArrayList最大的优势就是将数组的许多操作细节封装起来,而且它还支持动态扩容。
因为数组本身定义的时候需要指定大小,如果插入数据时内存空间不够,需要重新分配一块更大的空间,将原来的数据复制过去再插入新的数据。
虽然ArrayList封装了许多细节,但有时候直接使用数组会更合适:
- Java的ArrayList无法存储基本数据类型,像int、long都要封装成Integer、Long,使用过程中会装箱、拆箱造成一定的性能损耗,所以如果对性能要求高或者想用基本类型,就去用数组
- 如果数据大小已知,并且数据的操作简单,用不到ArrayList提供的方法,就可以直接使用数组
- 使用多维数组的时候,用
int[][]
会比容器定义的ArrayList<ArrayList>
更加直观