比较JavaScript中的数据结构(数组与对象)
大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】。
在编程中,如果你想继续深入,数据结构是我们必须要懂的一块, 学习/理解数据结构的动机可能会有所不同,一方面可能是为了面试,一方面可能单单是为了提高自己的技能或者是项目需要。无论动机是什么,如果不知道什么是数组结构及何时使用应用字们,那学数据结构是一项繁琐且无趣的过程 ?
这篇文章讨论了什么时候使用它们。在本文中,我们将学习数组和对象。我们将尝试通过使用Big O notation来理解何时选择一种数据结构。
Big O notation 大零符号一般用于描述算法的复杂程度,比如执行的时间或占用内存(磁盘)的空间等,特指最坏时的情形。
数组
数组是使用最广泛的数据结构之一。 数组中的数据以有序的方式进行结构化,即数组中的第一个元素存储在索引0
中,第二个元素存储在索引1
中,依此类推。 JavaScript为我们提供了一些内置的数据结构,数组就是其中之一 ?
在JavaScript中,定义数组最简单的方法是:
let arr = []
上面的代码行创建了一个动态数组(长度未知),为了了解如何将数组的元素存储在内存中,我们来看一个示例:
let arr = ['John', 'Lily', 'William', 'Cindy']
在上面的示例中,我们创建一个包含一些人名的数组。 内存中的名称按以下方式存储:
为了理解数组是如何工作的,我们需要执行一些操作:
添加元素:
在JavaScript数组中,我们有不同方式在数组结尾,开关以及特定索引处添加元素。
在数组的末尾添加一个元素:
JavaScript 中的数组有一个默认属性 length,它表示数组的长度。除了length属性外,JS还提供了 push() 方法。 使用这个方法,我们可以直接在最后添加一个元素。
arr.push('Jake')
那么这个命令的复杂度是多少呢?我们知道,在默认情况下,JS提供了length
属性,push()
相当于使用以下命令:
arr[arr.length - 1] = 'Jake'
因为我们总是可以访问数组的长度属性,所以无论数组有多大,在末尾添加一个元素的复杂度总是O(1) ?。
在数组的开头添加一个元素:
对于此操作,JavaScript提供了一个称为unshift()
的默认方法,此方法将元素添加到数组的开头。
let arr = ['John', 'Lily', 'William', 'Cindy']
arr.unshift('Robert')
console.log(arr) // [ 'Robert', 'John', 'Lily', 'William', 'Cindy' ]
unshift
方法复杂度好像和push
方法的复杂度一样:O(1)
,因为我们只是在前面添加一个元素。 事实并非如此,让我们看一下使用unshift
方法时会发生什么:
在上图中,当我们使用unshift
方法时,所有元素的索引应该增加1。这里我们的数组个数比较少,看不出存在的问题。想象一下使用一个相当长的数组,然后,使用unshift
这样的方法会导致延迟,因为我们必须移动数组中每个元素的索引。因此,unshift
操作的复杂度为O(n) ?。
如果要处理较大长度的数组,请明智地使用unshift
方法。在特定索引处添加元素,我们可以 splice()
方法,它的语法如下:
splice(startingIndex, deleteCount, elementToBeInserted)
因为我们要添加一个元素,所以deleteCount
将为0
。例如, 我们想要在数组索引为2的地方新加一个元素,可以这么用:
let arr = ['John', 'Lily', 'William', 'Cindy']
arr.splice(2, 0, 'Janice')
console.log(arr)
// [ 'John', 'Lily', 'Janice', 'William', 'Cindy' ]
你觉得这个操作的复杂性是多少?在上面的操作中,我们在索引2
处添加了元素,因此,在索引2
之后的所有后续元素都必须增加或移动1(包括之前在索引2处的元素)。
可以观察到,我们不是在移动或递增所有元素的索引,而是在索引2
之后递增元素的索引。这是否意味着该操作的复杂度为 `O(n/2)? 不是 ?。 根据Big O规则,常量可以从复杂性中删除,而且,我们应该考虑最坏的情况。 因此,该操作的复杂度为O(n) ?。
删除元素:
就像添加元素一样,删除元素可以在不同的位置完成,在末尾、开始和特定索引处。
在数组的末尾删除一个元素:
像 push( )
一样,JavaScript提供了一个默认方法pop()
,用于删除/删除数组末尾的元素。
let arr = ['Janice', 'Gillian', 'Harvey', 'Tom']
arr.pop()
console.log(arr)
// [ 'Janice', 'Gillian', 'Harvey' ]
arr.pop()
console.log(arr)
// [ 'Janice', 'Gillian' ]
该操作的复杂度为O(1)。因为,无论数组有多大,删除最后一个元素都不需要改变数组中任何元素的索引。
在数组的开头删除一个元素:
JavaScript 提供了一个默认方法shift()
的默认方法,此方法删除数组的第一个元素。
let arr = ['John', 'Lily','William','Cindy']
arr.shift()
console.log(arr) // ['Lily','William','Cindy']
arr.shift()
console.log(arr);// ['William','Cindy']
从上面我们很容易可以看出 shift()
操作的复杂度为O(n) ,因为删除第一个元素后,我们必须将所有元素的索引移位或减量1
。
在特定索引处删除:
对于此操作,我们再次使用splice()
方法,不过这一次,我们只使用前两个参数,因为我们不打算在该索引处添加新元素。
let arr = ['Apple', 'Orange', 'Pear', 'Banana','Watermelon']
arr.splice(2,1)
console.log(arr) // ['Apple', 'Orange', 'Banana','Watermelon']
与用splice
添加元素操作类似,在此操作中,我们将递减或移动索引2
之后的元素索引,所以复杂度是O(n)。
查找元素:
查找只是访问数组的一个元素,我们可以通过使用方括号符号(例如: arr[4]
)来访问数组的元素。
你认为这个操作的复杂性是什么? 我们通过一个例子来演示一下:
let fruits = ['Apple', 'Orange', 'Pear']
前面我们已经看到,数组的所有元素都按顺序存储,并且始终分组在一起。 因此,如果执行fruits[1],它将告诉计算机找到名为fruits
的数组并获取第二个元素(数组从索引0
开始)。
由于它们是按顺序存储的,因此计算机不必查看整个内存即可找到该元素,因为所有元素按顺序分组在一起,因此它可以直接在fruits
数组内部查看。 因此,数组中的查找操作的复杂度为 O(1)。
我们已经完成了对数组的基本操作,我们先来小结一下什么时候可以使用数组:
当你要执行像push()
(在末尾添加元素)和pop()
(从末尾删除元素)这样的操作时,数组是合适的,因为这些操作的复杂度是O(1)
。
除此之外,查找操作可以在数组中非常快地执行。
使用数组时,执行诸如在特定索引处或在开头添加/删除元素之类的操作可能会非常慢,因为它们的复杂度为O(n)。
对象
像数组一样,对象也是最常用的数据结构之一。 对象是一种哈希表,允许我们存储键值对,而不是像在数组中看到的那样将值存储在编号索引处。
定义对象的最简单方法是:
let obj1 = {}
事例:
let student = {
name: 'Vivek',
age: 13,
class: 8
}
来看一下上面的对象是如何存储在内存中的:
可以看到,对象的键-值对是随机存储的,不像数组中所有元素都存储在一起。这也是数组与对象的主要区别,在对象中,键-值对随机存储在内存中。
我们还看到有一个哈希函数(hash function)。 那么这个哈希函数做什么呢? 哈希函数从对象中获取每个键,并生成一个哈希值,然后将此哈希值转换为地址空间
,在该地址空间中存储键值对。
例如,如果我们向学生对象添加以下键值对:
student.rollNumber = 322
rollNumber
键通过哈希函数,然后转换为存储键和值的地址空间。现在我们已经对对象如何存储在内存有了基本的了解,让我们来执行一些操作。
添加
对于对象,我们没有单独的方法将元素添加到前面或后面,因为所有的键-值对都是随机存储的。只有一个操作是向对象添加一个新的键值对。
事例:
student.parentName = 'Narendra Singh Bisht'
从上图中我们可以得出结论,这个操作的复杂性总是O(1),因为我们不需要改变任何索引或操作对象本身,我们可以直接添加一个键-值对,它被存储在一个随机的地址空间。
删除
与添加元素一样,对象的删除操作非常简单,复杂度为O(1)。因为,我们不必在删除时更改或操作对象。
delete student.parentName
查找
查找的复杂度O(1) ,因为在这里,我们也只是借助键来访问值。访问对象中的值的一种方法:
student.class
在对象中添加,删除和查找的复杂度为O(1)???那么我们可以得出结论,我们应该每次都使用对象而不是数组吗? 答案是不。 尽管对象很棒,但是在使用对象时需要考虑一些小的情况,就是哈希碰撞(Hash Collisions)。 在使用对象时,并非始终应处理此情况,但了解该情况有助于我们更好地理解对象。
那么什么是哈希碰撞?
当我们定义一个对象时,我们的计算机会在内存中为该对象分配一些空间。 我们需要记住,我们内存中的空间是有限的,因此有可能两个或更多键值对可能具有相同的地址空间,这种情况称为哈希碰撞
。 为了更好地理解它,我们看一个例子:
假设为下面的对象分配了5块空间
我们观察到两个键值对存储在相同的地址空间中。 怎么会这样?当哈希函数返回一个哈希值,该哈希值转换为多个键的相同地址空间时,就会发生这种情况。 因此,多个 key
被映射到相同的地址空间。 由于哈希碰撞,添加和访问对象值的复杂度为O(n) ,因为要访问特定值,我们可能必须遍历各种键值对。
哈希碰撞并不是我们每次使用对象时都需要处理的东西。 这只是一个特殊的情况,该情况也说明了对象不是完美的数据结构。
除了*哈希碰撞,使用对象时还必须注意另一种情况。 JS 为我们提供了一个内置的keys()
方法,用于遍历对象的键。
我们可以将此方法应用于任何对象,例如:object1.keys()
。 keys()
方法遍历对象并返回所有键。 尽管此方法看起来很简单,但我们需要了解对象中的键值对是随机存储在内存中的,因此,遍历对象的过程变得较慢,这与遍历按顺序将它们分组在一起的数组不同。
总结一下,当我们想执行诸如添加,删除和访问元素之类的操作时,可以使用对象,但是在使用对象时,我们需要谨慎地遍历对象,因为这可能很耗时。 除了进行遍历外,我们还应该理解,有时由于哈希碰撞,访问对象操作的复杂度可能会变为O(n)。
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
- Linux下smokeping网络监控环境部署记录
- Linux下的rsyslog系统日志梳理(用户操作记录审计)
- 数据结构之数组封装
- Centos下内网NDS主从环境部署记录
- 一搜解决,微信的这个功能厉害了!
- Saltstack自动化操作记录(2)-配置使用
- Saltstack自动化操作记录(1)-环境部署
- CentOS源码编译安装Nginx和tcp_proxy module
- 介绍一个MonoTouch开发的伦敦官方城市指南应用
- 虾说区块链-55-《精通比特币》笔记十
- SignalR QuickStart
- Node.js入门学习笔记-IDE选择/配置之WebStorm(windows)
- Captcha插件后门分析和修复
- log4net.SignalR - 日志即时发送客户端页面
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android Activity 活动的生命周期
- Android学习笔记,不断更新
- cJSON,c语言的JSON库!
- 自己动手实现4大免费聊天机器人:小冰、图灵、腾讯、青云客
- Android Spinner下拉框的基本使用
- hadoop本地运行的两个案例。官方Grep案例、官方WordCount案例。
- 腾讯智能闲聊机器人详细开发教程
- 用PyTorch实现MNIST手写数字识别(非常详细)
- 手把手教你从零开始用Java写爬虫
- STM32 cjson的GBK/UTF-8/UNICODE转换、显示中文、GBK字库
- 都说Linux很重要,你会几个Linux命令?来看看这道面试题目。
- 使用VisualGDB将Keil项目导入VisualStudio
- 小白学图像 | Group Normalization详解+PyTorch代码
- 使用VisualGDB开发Keil MDK-ARM项目
- 保姆级教程:还愁不会搭建伪分布式吗?(其实很简单)