Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【深度揭秘】为什么很多语言的数组下标是从0开始的?

【深度揭秘】为什么很多语言的数组下标是从0开始的?

作者头像
吴延宝
发布于 2020-09-08 03:25:27
发布于 2020-09-08 03:25:27
1.4K10
代码可运行
举报
运行总次数:0
代码可运行

首先,恭喜你,能够点进来看的,已经领先60%的开发者了。 因为很多人看到标题可能觉得数组从0开始这不本来就这样吗?有什么看头,索性看都不会看,但是你点进来了,说明你还是保持了好奇心的,是具备成为专家的潜力的,这对技术行业来说非常重要。

很多的编程语言数组都是从0开始的,这已经是常识了。但是你是否好奇的想过,为什么呢?按照正常人的思维不都是从1开始的吗?

所以,我们带着这个疑问往下看。

数组的随机访问

尽管大家都知道了什么是数组,但是还是用官方的术语描述一下:数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。

我们可以抓住里面的几个重点词汇,来充分理解数组这种结构。

1、线性表,就是数据的排列从前到后顺序排列,就像一条线,像队列、栈列表、数组等都是线性表结构。

当然有线性表结构就有非线性表结构

2、连续内存空间和相同的数据类型。为啥数据访问一个数据效率非常高?那是因为数组的定义将数组这种结构定好了规矩,线性连续给了我们快速随机访问的机会。但是同时也带来了不好的地方,如果我们向其中插入或者删除一条数据是比较费劲的。

来看看数组是怎么实现随机访问的?

假设有这么一个数组: java int[] a = new int[10]; 操作系统给分配了一块连续的内存空间,假设为1000~1039,那么内存的首地址就是base_add = 1000

如果你去走访亲戚,你需要知道的是什么?亲戚家的地址吧(具体到门牌号),内存也一样,我们想读取内存里面的数据,操作系统也是通过内存的地址来访问的,那么问题来了,内存的地址是怎么知道呢?

这就涉及到操作系统的寻址,比如我想获取a[2]的值,那么操作系统先会根据下面的公式计算对应内存的地址:

a[2]的地址 = base_add + 2 * data_unit_size

dataunitsize表示该数据类型每个元素的大小,当前是int类型为4个字节,所以算出来a[2]的地址就是1008

那是不是可以说数组的查找的时间复杂度就是O(1)?当然不是了,正常情况下我们查找数可不是通过下标来查找的,我们是通过值来查找的,即便是二分查找时间复杂度也是O(logn)

删除和插入怎么就低效了

1、插入操作 假设我们要在长度为n的数组的第k个位置插入一个数据,我们就要讲第k~n的数往后挪,同理如果在最后插入就不需要挪位置,如果在第一个位置插入就要挪n个数,所以平均时间复杂度就是:(1+2+3+...+n)/n=O(n)

当然,如果不要求插入后顺序还保持原来一样,有个讨巧的插入方法就是讲第K个元素放到最后,将待插入的数放到第K个位置。

举个例子,假设数组 a[10]中存储了如下 5 个元素:a,b,c,d,e。

我们现在需要将元素 x 插入到第 3 个位置。我们只需要将 c 放入到 a[5],将 a[2]赋值为 x 即可。最后,数组中的元素如下:a,b,x,d,e,c。

2、删除操作.

其实和插入操作是相似的,当我们对长度为n的数组的第K个数组进行删除时,需要对后面的数据进行向前搬移操作,同理,时间复杂度和插入一样也是O(n),这里就不详细介绍了。

当然,在不考虑维持连续性的特殊情况下,为了提高删除的效率,没必要每次删除立即进行搬移操作,不然如果连续删除数据,就要连续进行多次的搬移。比较讨巧的办法是将待删除的元素进行标记,实际未做删除,等等内存不足的时候,将这些标记的数据统一进行删除操作。这样就会大大减少删除操作带来的大量数据搬移操作。

灾难!数组越界啦

对于Java来说发生数据越界的时候会抛出异常,但是对于有些语言比如C语言发生数组越界的时候并不会给你异常提示,比如下面这段代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int i = 0; int arr[3] = {0};
for(;i<=3;i++) {
  arr[i] = 0;
  System.out.println("test");  
}

显然定义的是长度为3的数组,但是循环条件是<=,所以会访问到数组外面的内存,而a[3]的地址刚好是存储i的内存,所以当循环到a[3]时又赋值为0,相当于i=0;所以这个循环永远结束不了,“hello world”会一直打印。

所以,对于C语言来说,如果没控制好下标,发生数组越界会出现莫名其妙的逻辑问题,还很难调试。这也是很多病毒利用数组越界来非法访问内存来攻击系统。

各种容器满天飞,还需要数组?

对于Java开发者来说,ArrayList再熟悉不过了,它为我们封装好了各种API来操作,比使用数组方便的多,而且是支持动态扩容的,因为数组是要提前订好大小的,当大小不满足的时候,需要重新定义大的数组进行复制操作,这显然很不方便,而容器类是内部有动态的分配的机制,当大小不够的时候自动的扩容,当然这也是非常耗性能的。如果能确定数据的大小,提前指定容器的大小更好。

那是不是意味着数组没有存在的必要,那也不是的,比如在下面的情况:

  • ArrayList是不能存储基本数据类型的,需要使用他们对应的装箱类,而拆箱和装箱显然都是非常耗性能的,如果特别关注性能,又需要使用基本数据类型,使用数组比使用ArrayList性能更好
  • 定义多维数组时,使用数组更加的直观
  • 如果数据大小事先知道,而对数据的操作比较简单。用不到ArrayList的大多API,这时候可以优先使用数组

小结:对于上层业务开发者,由于业务变化大,操作数据变化频繁,使用容器更加方便,牺牲一点性能对系统的整体功能影响不大。但是如果是做比较偏底层的开发就需要关注性能了,性能一丁点的提升,影响也是很广泛的,所以选择数组比较合适。

回到主题

为什么数组从0开始呢? 从数组存储的内存模型来看,下标比较确切的定义是“偏移”,如果用a来表示数组的首地址,那么a[0]就表示偏移为0的位置。a[x]就表示偏移x个类型大小(int 4个字节)的的位置。java a[x]_address = base_address + x * data_type_size;

但是如果从1开始计数呢,那么寻址公式就变成: java a[x]_address = base_address + (x-1) * data_type_size; 显然要多运算减一的操作,对于数组数据结构的定义是偏基础库的,对于性能要求当然是要追求极致的,多一步和少一步运算都是非常重要的参考点,所以为了更好的性能选择从0开始而不是从1开始。

当然也有历史因素,因为最早的C语言设计者使用从0开始的,所以后面的语言都延续了这一做法,这样能减少程序员学习语言的成本。当然也有一些不是从0开始的语言,这里就不举例了,感兴趣的同学可以自行去搜索一下。

总结

数组这种数据结构对于随机访问的效率特别高,但是对于插入和删除的效率就比较低了,对于业务开发者来说使用容器类比较省时实力,而对于偏底层开发者来说选择性能更好的数组就更合适了。

思考

1、通过数组的讨巧式删除方法,思考下JVM的标记清除算法? 2、上面讲到了一维数组的寻址方式,能推导下二维数组的寻址算法?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT烂笔头 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
1 条评论
热度
最新
抄袭狗
抄袭狗
回复回复1举报
推荐阅读
编辑精选文章
换一批
数组:为什么很多编程语言中数组都从0开始编号?
数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
Jingbin
2019/04/19
1K0
数组:为什么很多编程语言中数组都从0开始编号?
为什么很多编程语言中的数组都从0开始编号?——你真的了解数组吗?
数组是学习数据结构的开端。尽管数组看起来非常基础、简单,但是有多少人理解数组的精髓呢?
100000860378
2018/11/22
6140
数据结构与算法学习笔记之 从0编号的数组
数组看似简单,但掌握精髓的却没有多少;他既是编程语言中的数据类型,又是最基础的数据结构;
Dawnzhang
2018/10/18
7590
一文了解数组
数据结构算法入门系列的第二篇,这次介绍下数组, 数组是一个最基础而且常见的数据结构,几乎每种编程语言都有。
kbsc13
2019/10/24
5080
数组是如何随机访问元素?数组下标为什么从0开始,而不是1?
数组(Array)是一种线性表数据结构,它用一组连续的内存空间,来存储相同类型的数据。
搜云库技术团队
2019/10/17
6.6K0
数据结构01-数组
数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
WindCoder
2020/01/22
7390
4.线性表之数组
数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同。Java 语言中提供的数组是用来存储固定大小的同类型元素。
码哥字节
2020/04/07
3890
数组:面试中的疑难点
数组相信大家都不陌生,我们几乎每天都有用到数组,不管是直接由我们自己创建的,还是间接使用sdk内部提供的数据结构,底层都或多或少的离不开数据的使用。
Rouse
2021/01/12
4760
数组:面试中的疑难点
数据结构-数组
数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
acc8226
2022/05/17
3490
深入理解数组
数组是一种基本的线性表数据结构,它用一段连续的内存空间来存储一组具有相同类型的数据。
一行舟
2022/08/25
3320
AI_第一部分 数据结构与算法(4.线性表之数组相关)
第四阶段我们进行深度学习(AI),本部分(第一部分)主要是对底层的数据结构与算法部分进行详尽的讲解,通过本部分的学习主要达到以下两方面的效果:
python编程从入门到实践
2019/10/22
4760
AI_第一部分 数据结构与算法(4.线性表之数组相关)
【灵魂 | 数据结构与算法】线性表(数组&链表)原理详解 + 实战代码
数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。由于数组有连续的内存空间和相同类型的数据,内存访问机制 - 任意访问(随机访问)
计算机魔术师
2023/12/05
2680
重学数据结构和算法(一)之复杂度、数组、链表、栈、队列、图
最近学习了极客时间的《数据结构与算法之美]》很有收获,记录总结一下。 欢迎学习老师的专栏:数据结构与算法之美 代码地址:https://github.com/peiniwan/Arithmetic
六月的雨
2021/03/02
6050
重学数据结构和算法(一)之复杂度、数组、链表、栈、队列、图
Algorithms_基础数据结构(01)_线性表之数组&数组的应用案例分析
给你一个文件里面包含全国人民(14亿)的年龄数据(0~180),现在要你统计每一个年龄有多少人? 给定机器为 单台+2CPU+2G内存。不得使用现成的容器,比如map等。
小小工匠
2021/08/17
3540
什么是数组?
今天要介绍的主角就是-数组,数组也是数据呈线性排列的一种数据结构。与前一节中的链表不同,在数组中,访问数据十分简单,而添加和删除数据比较耗工夫。这和什么是数据结构那篇文章中讲到的姓名按拼音顺序排列的电话簿类似。
武培轩
2020/02/17
5140
数据结构与算法学习笔记
本文是王争老师的《算法与数据结构之美》的学习笔记,详细内容请看王争的专栏 。有不懂的地方指出来,我做修改。
全栈程序员站长
2022/07/23
7210
数据结构与算法学习笔记
为什么数组都是从0开始编号
为什么数组都是从 0 开始编号,首先先了解一下数组的概念。 数组 Array 是一种线性表数据结构,是一组连续的内存空间,用来存储一组具有相同类型的数据。数组具备以下特性:
s_在路上
2018/12/06
1.1K0
数据结构:数组内存模型
在计算机里,所有的数据结构本质上其实都可以归为两类:数组和链表。对于链表,我将会在第03 与第 04 讲中着重讲解。今天我将要和你一起探索数据结构中最基本的知识点——数组(Array)。
码农架构
2021/01/22
8430
数据结构:数组内存模型
算法笔记汇总精简版下载_算法与数据结构笔记
10个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树; 10个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态 规划、字符串匹配算法。
全栈程序员站长
2022/09/20
9390
ACM 选手带你玩转数组!
提到数组,想必臭宝们都不陌生。说不定已经昂起脑瓜抖着腿,小眼神儿斜着来一句:“就那个随随便便用脚趾甲就能学会的数组?”
编程文青李狗蛋
2022/01/06
2550
ACM 选手带你玩转数组!
推荐阅读
相关推荐
数组:为什么很多编程语言中数组都从0开始编号?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验