5、数组:为什么很多编程语言中数组都从0开始编号?

如何实现随机访问?
  • 数组是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
线性表
  • 线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。
非线性表
  • 在非线性表中,数据之间并不是简单的前后关系。
连续的内存空间和相同类型的数据。
  • int[] a = new int[10],计算机分配了一块连续内存空间1000~1039。
  • 计算机通过地址来访问内存中的数据,随机访问数组中的某个元素时,会使用以下的寻址公式:
a[i]_address = base_address + i * data_type_size
  • 数组支持随机访问,根据下标随机访问的时间复杂度为O(1)。
低效的“插入”和“删除”
  • 如果数组中的数据是有序的,我们在某个位置插入一个新的元素时,就必须按照刚才的方法搬移k之后的数据。

  • 如果数组中存储的数据并没有任何规律,数组只是被当作一个存储数据的集合。在这种情况下,如果要将某个元素插入到第k个位置,为了避免大规模的数据搬移,直接将第k位的数据搬移到数组元素的最后,把新的元素直接放入第k个位置。

  • 和插入类似,如果删除数组末尾的数据,则最后情况时间复杂度为O(1);如果删除开头的数据,则最坏情况时间复杂度为O(n);平均情况实际复杂度也为O(n)。

  • 数组a[10]中存储了8个元素:a,b,c,d,e,f,g,h。现在,我们要依次删除a,b,c三个元素。

  • 为了避免d,e,f,g,h这几个数据会被搬移三次,我们可以先记录下已经删除的数据,每次的删除操作并不是真正的搬移数据,只是记录数据已经被删除。

  • 当数组没有更多空间存储数据时,我们再触发执行一次真正的删除操作,这样就大大减少了删除操作导致的数据搬移。

这就是JVM标记清楚垃圾回收算法的核心思想。
警惕数组的访问越界问题
  • 数组越界在C语言中是一种未决行为,并没有规定数组访问越界时编译器应该如何处理。
容器是否完全替代数组?
  • ArrayList最大的优势就是可以将很多数组操作的细节封装起来,而且支持动态扩容。
  • 如果事先能确定需要存储的数据大小,最好在创建ArrayList的时候事先指定数据大小。
经验
  • 1、ArrayList无法存储基本类型,比如int、long,需要封装为Integer、Long类,而Autoboxing、Unboxing则有一定的性能消耗,所有如果特别关注性能,或者希望使用基本类型,就可以选用数组。
  • 2、如果数据大小事先已知,并且对数据的操作非常简单,也可以直接使用数组。
  • 3、当要表示多维数组时,用数组往往会更加直观。比如Object[][] array;而用容器的话则需要这样定义:ArrayList array。
  • 4、对于业务开发,直接使用容器就足够了,毕竟损耗一丢丢性能,完全不会影响到系统整体的性能。但如果你是做一些非常底层的开发,比如开发网络框架,性能的优化需要做到极致,这个时候数组就会优于容器,成为首选。
为什么大多数编程语言中,数组要从0开始编号,而不是从1开始呢?
  • 从数组存储的内存模型上来看,“下标”最确切的定义应该是偏移(offset)。如果用a来表示数组的首地址,a[0]就是偏移为0的地址,也就是首地址。
a[k]_address = base_address + k * type_size
a[k]_address = base_address + (k-1)*type_size
  • 从1开始编号,每次随机访问数组元素都多了一次减法运算,对于CPU来说,就是多了一次减法指令。
posted @ 2018-11-26 23:55  _sanjun  阅读(453)  评论(0编辑  收藏  举报