Web高级 JavaScript中的数据结构
复杂度分析
- 大O复杂度表示法
常见的有O(1), O(n), O(logn), O(nlogn)
时间复杂度除了大O表示法外,还有以下情况 - 最好情况时间复杂度
- 最坏情况时间复杂度
- 平均情况时间复杂度
- 均摊时间复杂度
代码执行效率分析
大多数情况下,代码执行的效率可以采用时间复杂度分析,但是大O表示法通常会省略常数。
但是在工业级实现中,真正的执行效率通常会考虑多方面:
- 时间复杂度
- 空间复杂度
- 缓存友好
- 指令条数
- 性能退化(如哈希冲突解决方案等)
- 等
以上是理论层级,对于前端开发来说,如果你开发的是基础库会被广泛使用时,只是基于理论的分析是不够的。
此时就可以借助一些基准库来对你的模块进行测试: benchmark.js
数据结构
1. 数组
- 特点: 连续内存,支持下标访问,缓存友好,时间复杂度O(1)
- 深入: 从JS继承链可以知道在JS中的Array数组继承于Object,在某些情况下数组会退化为哈希表结构[Dictionary]。
- 思考: 考虑如下情况,浏览器会怎样存储该数组?
let myArray = [10000];
myArray[0] = 0;
myArray[9999] = 1;
2. 链表
- 特点: 非连续内存,查找元素时间复杂度最坏情况O(n),最好情况O(1)。
- 单向链表
- 双向链表
- 深入: 判断链表是否有环形?
- 使用一个步进为1,和一个额外的步进为2的参数,如果在遍历完之前相遇,则有环。
3. 栈
- 特点: 先进后出,后进先出,底层结构可以为数组或链表。
- 场景: 函数调用时对当前执行环境进行入栈操作,调用完成后出栈恢复调用前上下文。
- 深入: 递归调用时连续入栈操作,如果递归层级过深造成堆栈溢出。
比如AngularJS中的脏值检查嵌套深度上线为10。 - 思考: 浏览器的前进后退功能可否用栈实现?
使前进栈和后退栈的双栈结构
4. 队列
- 特点: 先进先出,底层结构可以为数组或链表。
- 无阻塞队列
基于链表 - 阻塞队列
基于数组的循环队列
深入: JS中的Macro队列和Micro队列
5. 跳表
底层为链表,为了增加查找效率,在链表基础上,以(2/3/4/../n)个节点增加一层查找链表
特点: '支持区间查找',支持动态数据结构,需要额外的存储空间来存储索引链节点
时间复杂度: 基于链表的查找时间复杂度由跳表层高决定
场景: 如Redis中的有序集合
6. 散列表
- 底层为数组结构,使用散列函数计算key将其映射到数组中的下标位置。
- 散列函数要求:
- 散列值为非负整数
- key1 = key2, 则 hash(key1) = hash(key2)
- key1 != key2, 则 hash(key1) != hash(key2)
- 常见的哈希算法有: MD5, SHA, CRC等。
- 散列冲突:
- 开放寻址
线性探测:散列冲突时,依次往后查找空闲
双重散列: 使用多个散列函数,如果第一个散列值被占用,则用第二个散列值 - 链表法
散列冲突时,在一级数据后链接一个链表结构。该链表可以是单/双链,或树形链表
- 装载因子: 整个数组中已经被使用的位置/总位置
- 动态扩容: 当装载因子达到某个限定值(如:0.75)时,对整个底层数组进行扩容
注: 工业级实现中,动态扩容并不是一次完成的。
一次完成的动态扩容会造成性能瓶颈,一般是在装载因子达到设定值时,申请一个新的数组。在后续的每次操作时,将一个数据从原数组搬到新数组,直到原数组为空。
JavaScript 对象数据结构
- 我在另一篇文章中有将到JS中的对象在底层存储的时候会有好几种模式,下面我们结合源码来详细分析一下
//https://github.com/v8/v8/blob/master/src/objects/js-objects.h
// 我们先来看JS中object的定义,继承自JSReceiver
// Line 278
class JSObject : public JSReceiver {
//省略...
}
// Line 26
// 接下来再看,JSReceiver继承自HeapObject,并且有几个重要属性
// JSReceiver includes types on which properties can be defined, i.e.,
// JSObject and JSProxy.
class JSReceiver : public HeapObject {
public:
NEVER_READ_ONLY_SPACE
// Returns true if there is no slow (ie, dictionary) backing store.
// 是否有快速属性模式
inline bool HasFastProperties() const;
// Returns the properties array backing store if it exists.
// Otherwise, returns an empty_property_array when there's a Smi (hash code) or an empty_fixed_array for a fast properties map.
// 属性数组
inline PropertyArray property_array() const;
// Gets slow properties for non-global objects.
// 字典属性
inline NameDictionary property_dictionary() const;
// Sets the properties backing store and makes sure any existing hash is moved
// to the new properties store. To clear out the properties store, pass in the
// empty_fixed_array(), the hash will be maintained in this case as well.
void SetProperties(HeapObject properties);
// There are five possible values for the properties offset.
// 1) EmptyFixedArray/EmptyPropertyDictionary - This is the standard
// placeholder.
//
// 2) Smi - This is the hash code of the object.
//
// 3) PropertyArray - This is similar to a FixedArray but stores
// the hash code of the object in its length field. This is a fast
// backing store.
//
// 4) NameDictionary - This is the dictionary-mode backing store.
//
// 4) GlobalDictionary - This is the backing store for the
// GlobalObject.
// 初始化属性
inline void initialize_properties();
- 由上可知对象有快速属性和字典属性两种模式,快速属性由数组存储,字典属性采用hash表存储。
- 快速属性这里不深入,我们接下来看看NameDictionary的底层结构
// https://github.com/v8/v8/blob/master/src/objects/dictionary.h
// 我们先来看看继承链
// Line 202
class V8_EXPORT_PRIVATE NameDictionary : public BaseNameDictionary<NameDictionary, NameDictionaryShape>{}
// Line 128
class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE) BaseNameDictionary : public Dictionary<Derived, Shape> {}
// Line 26
class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE) Dictionary : public HashTable<Derived, Shape> {}
// 由上可知继承自HashTable,我们来看HashTable的定义
// https://github.com/v8/v8/blob/master/src/objects/hash-table.h
// 并且在文件开头的注释已经很详细了
// HashTable is a subclass of FixedArray that implements a hash table that uses open addressing and quadratic probing.
*重要: hash表使用数组为基础数据,并在其上实现了开放寻址和二次探测
// In order for the quadratic probing to work, elements that have not yet been used and elements that have been deleted are distinguished.
// Probing continues when deleted elements are encountered and stops when unused elements are encountered.
* 为了使二次探测工作正常,未使用/被删除的元素将被标记删除而不是直接删除
// - Elements with key == undefined have not been used yet.
// - Elements with key == the_hole have been deleted.
// 以下会被使用的hash表派生类
// Line 292
template <typename Derived, typename Shape>
class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE) ObjectHashTableBase
: public HashTable<Derived, Shape> {}
接下来我们看看V8在实现hash表时的几个重要行为和参数
// https://github.com/v8/v8/blob/master/src/objects/objects.cc
1. 扩容
// Line 7590
// 下面源代码是新增一个元素后的逻辑
ObjectHashTableBase<Derived, Shape>::Put(Isolate* isolate, Handle<Derived> table, Handle<Object> key, Handle<Object> value, int32_t hash) {
int entry = table->FindEntry(roots, key, hash);
// Key is already in table, just overwrite value.
// key已经存在,覆盖值
if (entry != kNotFound) {
table->set(Derived::EntryToValueIndex(entry), *value);
return table;
}
// 如果33%以上元素都被删除了,考虑重新hash
// Rehash if more than 33% of the entries are deleted entries.
// TODO(jochen): Consider to shrink the fixed array in place.
if ((table->NumberOfDeletedElements() << 1) > table->NumberOfElements()) {
table->Rehash(roots);
}
// If we're out of luck, we didn't get a GC recently, and so rehashing isn't enough to avoid a crash.
// 如果没有足够的预估空位,按二倍大小进行重新hash
if (!table->HasSufficientCapacityToAdd(1)) {
int nof = table->NumberOfElements() + 1;
int capacity = ObjectHashTable::ComputeCapacity(nof * 2);
if (capacity > ObjectHashTable::kMaxCapacity) {
for (size_t i = 0; i < 2; ++i) {
isolate->heap()->CollectAllGarbage(
Heap::kNoGCFlags, GarbageCollectionReason::kFullHashtable);
}
table->Rehash(roots);
}
}
// Line 6583.
// 下面是计算预估空位的逻辑
HashTable<Derived, Shape>::EnsureCapacity(Isolate* isolate, Handle<Derived> table, int n, AllocationType allocation) {
if (table->HasSufficientCapacityToAdd(n)) return table;
int capacity = table->Capacity();
int new_nof = table->NumberOfElements() + n;
const int kMinCapacityForPretenure = 256;
bool should_pretenure = allocation == AllocationType::kOld ||
((capacity > kMinCapacityForPretenure) &&
!Heap::InYoungGeneration(*table));
Handle<Derived> new_table = HashTable::New(
isolate, new_nof,
should_pretenure ? AllocationType::kOld : AllocationType::kYoung);
table->Rehash(ReadOnlyRoots(isolate), *new_table);
return new_table;
}
2. 收缩
// Line 6622
HashTable<Derived, Shape>::Shrink(Isolate* isolate,
Handle<Derived> table,
int additionalCapacity) {
int capacity = table->Capacity();
int nof = table->NumberOfElements();
// Shrink to fit the number of elements if only a quarter of the capacity is filled with elements.
// 当只有1/4的装载量时,进行收缩
if (nof > (capacity >> 2)) return table;
// Allocate a new dictionary with room for at least the current number of
// elements + {additionalCapacity}. The allocation method will make sure that
// there is extra room in the dictionary for additions. Don't go lower than
// room for {kMinShrinkCapacity} elements.
int at_least_room_for = nof + additionalCapacity;
int new_capacity = ComputeCapacity(at_least_room_for);
if (new_capacity < Derived::kMinShrinkCapacity) return table;
if (new_capacity == capacity) return table;
const int kMinCapacityForPretenure = 256;
bool pretenure = (at_least_room_for > kMinCapacityForPretenure) &&
!Heap::InYoungGeneration(*table);
Handle<Derived> new_table =
HashTable::New(isolate, new_capacity,
pretenure ? AllocationType::kOld : AllocationType::kYoung,
USE_CUSTOM_MINIMUM_CAPACITY);
table->Rehash(ReadOnlyRoots(isolate), *new_table);
return new_table;
}
Web高级 JavaScript中的数据结构的更多相关文章
- Web高级 JavaScript中的算法
算法 排序算法 稳定排序 待排序序列中相等元素在排序完成后,原有先后顺序不变. 非稳定排序 有序度 待排序序列中有序关系的元素对个数. 逆序度 1. 插入排序 遍历有序数组,对比待插入的元素大小,找到 ...
- JavaScript中的数据结构及实战系列
本系列主要是讲解JavaScript中的数据结构及在实际项目中遇到的地方 JavaScript中的数据结构及实战系列(1):队列 JavaScript中的数据结构及实战系列(2):栈
- javascript中的数据结构
Javascript中的关键字 abstract continue finally instanceof private this boolean ...
- JavaScript中常见数据结构
数据结构 栈:一种遵从先进后出 (LIFO) 原则的有序集合:新添加的或待删除的元素都保存在栈的末尾,称作栈顶,另一端为栈底.在栈里,新元素都靠近栈顶,旧元素都接近栈底. 队列:与上相反,一种遵循先进 ...
- JavaScript中的数据结构及实战系列(1):队列
开题 张三丰教无忌太极剑法: 还记得吗? 全都记得. 现在呢? 已经忘却了一小半. 啊,已经忘了一大半. 不坏不坏,忘得真快,那么现在呢? 已经全都忘了,忘得干干净净. 好了,你上吧. 长时间写前端代 ...
- JavaScript中的数据结构及实战系列(2):栈
开题: 不冒任何险,什么都不做,什么也不会有,什么也不是. 本文目录 栈介绍: JavaScript实现栈: 栈的应用: 栈介绍: 和队列一样,栈也是一种表结构,但是和队列的"先进先出&qu ...
- 在Object-C中学习数据结构与算法之排序算法
笔者在学习数据结构与算法时,尝试着将排序算法以动画的形式呈现出来更加方便理解记忆,本文配合Demo 在Object-C中学习数据结构与算法之排序算法阅读更佳. 目录 选择排序 冒泡排序 插入排序 快速 ...
- 一篇文章把你带入到JavaScript中的闭包与高级函数
在JavaScript中,函数是一等公民.JavaScript是一门面向对象的编程语言,但是同时也有很多函数式编程的特性,如Lambda表达式,闭包,高阶函数等,函数式编程时一种编程范式. funct ...
- 掌握javascript中的最基础数据结构-----数组
这是一篇<数据结构与算法javascript描述>的读书笔记.主要梳理了关于数组的知识.部分内容及源码来自原作. 书中第一章介绍了如何配置javascript运行环境:javascript ...
随机推荐
- 文本数据挖掘 Matrix67: The Aha Moments
转自:http://www.matrix67.com/blog/archives/5044 互联网时代的社会语言学:基于SNS的文本数据挖掘 今年上半年,我在人人网实习了一段时间,期间得到了很多宝贵的 ...
- L3-016. 二叉搜索树的结构
二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值:若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值:它的左.右子树也分别 ...
- C#基础知识之System.AppDomain类
进程是存在独立的内存和资源的,但是AppDomain仅仅是逻辑上的一种抽象.一个process可以存在多个AppDomain.各个AppDomain之间的数据时相互独立的.一个线程可以穿梭多个AppD ...
- 七夕-心形表白-简单css代码
今天你要和谁过? 今天你要怎么过?? 今天去哪里吃??? 公司的三连问,对于一些单身狗有点招架不住啊. 在此送上一个薄礼,来安慰下受伤的心灵... 确定是安慰不是连环打击嘛..... 回答:确定! 来 ...
- BZOJ 3319: 黑白树 并查集 + 离线 + 思维
Description 给定一棵树,边的颜色为黑或白,初始时全部为白色.维护两个操作: 1.查询u到根路径上的第一条黑色边的标号. 2.将u到v 路径上的所有边的颜色设为黑色. Notice:这 ...
- POJ 1252 Euro Efficiency ( 完全背包变形 && 物品重量为负 )
题意 : 给出 6 枚硬币的面值,然后要求求出对于 1~100 要用所给硬币凑出这 100 个面值且要求所用的硬币数都是最少的,问你最后使用硬币的平均个数以及对于单个面值所用硬币的最大数. 分析 : ...
- Socket通信前必须考虑的几件事
如何处理I/O?是让程序阻塞等待响应,还是在后台处理这些事?这是软件设计的关键因素.阻塞式的I/O操作会让程序架构难以扩展,而后台处理I/O也是比较困难的. 如何处理那些临时的.来去自由的组件?我 ...
- RESTful风格编程
参考文档:http://blog.didispace.com/springbootrestfulapi/ https://www.jianshu.com/p/91600da4df95 *)RESTfu ...
- apache cgi 程序: End of script output before headers
测试linux Apache cgi程序: #include <stdio.h> int main(){ printf("abc"); ; } 目录:/var/www/ ...
- The Linux usage model for device tree data
Linux and the Device Tree The Linux usage model for device tree data Author: Grant Likely grant.like ...