php tire树,Immutable.js源码之List 类型的详细解析(附示例)

本篇文章给大家带来的内容是关于Immutable.js源码之List 类型的详细解析(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

一、存储图解

我以下面这段代码为例子,画出这个List的存储结构:let myList = [];

for(let i=0;i<1100;i++) {

myList[i] = i;

}

debugger;//可以在这里打个断点调试

let immutableList = Immutable.List(myList)

debugger;

console.log(immutableList.set(1000, 'Remm'));

debugger;

console.log(immutableList.get(1000));

b33918e9b3bfa37d54c498fe13b99401.png

二、vector trie 的构建过程

我们用上面的代码为例子一步一步的解析。首先是把原生的list转换为inmutable的list 类型:export class List extends IndexedCollection {

// @pragma Construction

constructor(value) { // 此时的value就是上面的myList数组

const empty = emptyList();

if (value === null || value === undefined) {//判断是否为空

return empty;

}

if (isList(value)) {//判断是否已经是imutable的list类型

return value;

}

const iter = IndexedCollection(value);//序列化数组

const size = iter.size;

if (size === 0) {

return empty;

}

assertNotInfinite(size);

if (size > 0 && size < SIZE) { // 判断size是否超过32

return makeList(0, size, SHIFT, null, new VNode(iter.toArray()));

}

return empty.withMutations(list => {

list.setSize(size);

iter.forEach((v, i) => list.set(i, v));

});

}

。。。。。。

}

首先会创建一个空的listlet EMPTY_LIST;

export function emptyList() {

return EMPTY_LIST || (EMPTY_LIST = makeList(0, 0, SHIFT));

}

SHIFT的值为5,export const SHIFT = 5; // Resulted in best performance after ______?

再继续看makeList,可以清晰看到 List 的主要部分:function makeList(origin, capacity, level, root, tail, ownerID, hash) {

const list = Object.create(ListPrototype);

list.size = capacity - origin;// 数组的长度

list._origin = origin;// 数组的起始位置 一般是0

list._capacity = capacity;// 数组容量 等于 size

list._level = level;//树的深度,为0时是叶子结点。默认值是5,存储指数部分,用于方便位运算,增加一个深度,level值+5

list._root = root;// trie树实现

list._tail = tail;// 32个为一组,存放最后剩余的数据 其实就是 %32

list.__ownerID = ownerID;

list.__hash = hash;

list.__altered = false;

return list;

}

将传入的数据序列化// ArraySeq

iter = {

size: 数组的length,

_array: 传入数组的引用

}

判断size是否超过32,size > 0 && size < SIZE 这里 SIZE : export const SIZE = 1 << SHIFT;即 32。若没有超过32,所有数据都放在_tail中。

_root 和 _tail 里面的数据又有以下结构:// @VNode class

constructor(array, ownerID) {

this.array = array;

this.ownerID = ownerID;

}

可以这样调试查看:let myList = [];

for(let i=0;i<30;i++) {

myList[i] = i;

}

debugger;//可以在这里打个断点调试

console.log(Immutable.List(myList));

size如果超过32return empty.withMutations(list => {

list.setSize(size);//构建树的结构 主要是计算出树的深度

iter.forEach((v, i) => list.set(i, v));//填充好数据

});export function withMutations(fn) {

const mutable = this.asMutable();

fn(mutable);

return mutable.wasAltered() ? mutable.__ensureOwner(this.__ownerID) : this;

}

list.setSize(size)中有一个重要的方法setListBounds,下面我们主要看这个方法如何构建这颗树

这个方法最主要的作用是 确定 list的levelfunction setListBounds(list, begin, end) {

......

const newTailOffset = getTailOffset(newCapacity);

// New size might need creating a higher root.

// 是否需要增加数的深度 把 1 左移 newLevel + SHIFT 位 相当于 1 * 2 ^ (newLevel + SHIFT)

// 以 size为 1100 为例子 newTailOffset的值为1088 第一次 1088 > 2 ^ 10 树增加一层深度

// 第二次 1088 < 2 ^ 15 跳出循环 newLevel = 10

while (newTailOffset >= 1 << (newLevel + SHIFT)) {

newRoot = new VNode(

newRoot && newRoot.array.length ? [newRoot] : [],

owner

);

newLevel += SHIFT;

}

......

}function getTailOffset(size) {

// (1100 - 1) / 2^5 % 2^5 = 1088

return size < SIZE ? 0 : (((size - 1) >>> SHIFT) << SHIFT);

}

经过 list.setSize(size);构建好的结构

a42597d570c1e186e3346a4e185aa4ab.png

三、set 方法

listiter.forEach((v, i) => list.set(i, v));这里是将iter中的_array填充到

这里主要还是看看set方法如何设置数据set(index, value) {

return updateList(this, index, value);

}function updateList(list, index, value) {

......

if (index >= getTailOffset(list._capacity)) {

newTail = updateVNode(newTail, list.__ownerID, 0, index, value, didAlter);

} else {

newRoot = updateVNode(

newRoot,

list.__ownerID,

list._level,

index,

value,

didAlter

);

}

......

}function updateVNode(node, ownerID, level, index, value, didAlter) {

// 根据 index 和 level 计算 数据set的位置在哪

const idx = (index >>> level) & MASK;

// 利用递归 一步一步的寻找位置 直到找到最终的位置

if (level > 0) {

const lowerNode = node && node.array[idx];

const newLowerNode = updateVNode(

lowerNode,

ownerID,

level - SHIFT,

index,

value,

didAlter

);

......

// 把node节点的array复制一份生成一个新的节点newNode editableVNode函数见下面源码

newNode = editableVNode(node, ownerID);

// 回溯阶段将 子节点的引用赋值给自己

newNode.array[idx] = newLowerNode;

return newNode;

}

......

newNode = editableVNode(node, ownerID);

// 当递归到叶子节点 也就是level <= 0 将值放到这个位置

newNode.array[idx] = value;

......

return newNode;

}function editableVNode(node, ownerID) {

if (ownerID && node && ownerID === node.ownerID) {

return node;

}

return new VNode(node ? node.array.slice() : [], ownerID);

}

下面我们看看运行了一次set(0,0)的结果

70ff7bf7177f8dfa3b969da675836303.png

整个结构构建完之后

4e5e0322a9949daaa980ce4bcc68e37f.png

下面我们接着看刚刚我们构建的list set(1000, 'Remm'),其实所有的set的源码上面已经解析过了,我们再来温习一下。

调用上面的set方法,index=1000,value='Remm'。调用updateList,继而调用updateVNode。通过const idx = (index >>> level) & MASK;计算要寻找的节点的位置(在这个例子中,idx的值依次是0->31->8)。 不断的递归查找,当 level <= 0 到达递归的终止条件,其实就是达到树的叶子节点,此时通过newNode = editableVNode(node, ownerID);创建一个新的节点,然后 newNode.array[8] = 'Remm'。接着就是开始回溯,在回溯阶段,自己把自己克隆一个,newNode = editableVNode(node, ownerID);,注意这里克隆的只是引用,所以不是深拷贝。然后再将idx位置的更新了的子节点重新赋值,newNode.array[idx] = newLowerNode;,这样沿着路径一直返回,更新路径上的每个节点,最后得到一个新的根节点。

更新后的list:

ed2ee32eaf52289cb3ec5cc292e1b2cb.png

四、get 方法

了解完上面的list构建和set,我们再来看 immutableList.get(1000) 源码就是小菜一碟了。get(index, notSetValue) {

index = wrapIndex(this, index);

if (index >= 0 && index < this.size) {

index += this._origin;

const node = listNodeFor(this, index);

return node && node.array[index & MASK];

}

return notSetValue;

}function listNodeFor(list, rawIndex) {

if (rawIndex >= getTailOffset(list._capacity)) {

return list._tail;

}

if (rawIndex < 1 << (list._level + SHIFT)) {

let node = list._root;

let level = list._level;

while (node && level > 0) {

// 循环查找节点所在位置

node = node.array[(rawIndex >>> level) & MASK];

level -= SHIFT;

}

return node;

}

}

五、tire 树 的优点

来一张从网上盗来的图:

178d790964ddca0cb11be2089ae7cb4c.png

这种树的数据结构(tire 树),保证其拷贝引用的次数降到了最低,就是通过极端的方式,大大降低拷贝数量,一个拥有100万条属性的对象,浅拷贝需要赋值 99.9999万次,而在 tire 树中,根据其访问的深度,只有一个层级只需要拷贝 31 次,这个数字不随着对象属性的增加而增大。而随着层级的深入,会线性增加拷贝数量,但由于对象访问深度不会特别高,10 层已经几乎见不到了,因此最多拷贝300次,速度还是非常快的。

我上面所解析的情况有 构建、修改、查询。其实还有 添加 和 删除。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/533858.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nodejs missing script: dev_nodejs深入学习系列之v8基础篇

V8这个概念大家都不陌生了&#xff0c;那么你动手编译过V8源码吗&#xff1f;编译后有尝试去了解V8背后的一些概念吗&#xff1f;如果没有&#xff0c;那么也不用心慌&#xff0c;下文将跟大家一一解释这些东西。在编译V8之前我们先要了解一个东西-构建系统1、构建系统1.1、构建…

cmos存储器中存放了_天津大学姚建铨院士,张雅婷副教授JMCC:具有宽光谱调控特性的阻变存储器...

【引言】存储器是计算机中数据存放的主要介质。随着5G时代到来&#xff0c;带动人工智能、物联网、智慧城市等应用市场发展并向存储器提出多样化需求&#xff0c;加上传统存储器市场价格变化等因素&#xff0c;新型存储器将在市场发挥越来越重要的作用。因此具有存储密度更高&a…

matlab转差频率控制,转差频率控制的异步电机调速系统的研究

1 引言交流变频调速的方法是异步电机最有发展前途的调速方法。随着电力电子技术、计算机技术和自动控制技术的不断发展&#xff0c;交流电机变频调速已经逐步取代直流电机调速&#xff0c;并经历了采用电压频率协调控制、转差频率控制、矢量控制以及直接转矩控制的发展过程。其…

oracle错误1327,Oracle中的PGA监控报警分析(r11笔记第97天)

最近接到一个数据库报警&#xff0c;让我颇有些意外&#xff0c;这是一个PGA相关的报警。听起来感觉是应用端的资源调用出了问题。报警内容大体如下&#xff1a;报警内容: PGA Alarm on alltest------------------------------------报警级别: PROBLEM------------------------…

php函数内的循环,PHP 循环列出目录内容的函数代码

PHP 循环列出目录内容的函数代码复制代码 代码如下:function list_files($dir){if(is_dir($dir)){if($handle opendir($dir)){while(($file readdir($handle)) ! false){if($file ! "." && $file ! ".." && $file ! "Thumbs.db&quo…

python火柴人打架代码_python火柴人

广告关闭 腾讯云11.11云上盛惠 &#xff0c;精选热门产品助力上云&#xff0c;云服务器首年88元起&#xff0c;买的越多返的越多&#xff0c;最高返5000元&#xff01; 代码实现了一个火柴人&#xff0c;他开心时可以跳跃、可以舞蹈&#xff0c;不开心时可以躺地上... ?代码有…

spring boot admin 2.2 获取日志失败_SB实战20-Spring Boot的日志和报告

上篇我们学习了《SB实战19-Spring Boot的外部配置》&#xff0c;本篇我们学习Spring Boot的日志和报告。4 日志和报告4.1 日志日志是对应用运行时进行调试和分析的重要工具。Spring Boot使用SLF4J作为日志的API&#xff0c;Logback、Log4j2、Java Util Logging都可以作为日志提…

sqlserver date类型和字符串比较_基于SQL Server数据库搭建主从复制实现读写分离实战演练...

一、课程介绍读写分离(主从同步)从字面意思就可以理解&#xff0c;就是把对数据库的读操作和写操作分离开。读写分离在网站发展初期可以一定程度上缓解读写并发时产生锁的问题&#xff0c;将读写压力分担到多台服务器上。读写分离的基本原理是让主数据库处理事务性增、改、删操…

linux非标准头文件,Linux学习:unix的标准化的实现(Linux中各种限制-数据类型-各种标准化头文件介绍)...

作为Linux的前身&#xff0c;unix标准化是十分重要的。我在这里挑几个重要的点说明。1&#xff1a;Linux中各种限制。Linux中限制有编译时限制和运行时限制&#xff0c;另外有一些限制是由于我们的实现不同而不同&#xff0c;因此我们需要调用对应的函数获取对应的值不同。(eg&…

51单片机怎么显示当前时间_(进阶篇)51单片机之按键控制蜂鸣器、数码管、按键值移位显示...

一、实操演示- 按键控制蜂鸣器1、图文详细独立按键硬件电路蜂鸣器硬件电路2、连接方式&#xff1a;J20的第3号引脚连接到J7引脚&#xff0c;即P15连接J7。J29的第7、8号引脚连接到JP1的第1、2号引脚&#xff0c;即P31连接k1&#xff0c;P30连接k2。下载程序后&#xff0c;观察现…

linux怎么运行g77,Linux安装g77编译器的技巧

在Ubuntu10.10系统中&#xff0c;g77已经被gfortran完全替代了&#xff0c;但并不能完全兼容过去的g77&#xff0c;这样就不能使用一些用977编译的程序了。所以我们只能自己再安装g77了。今天华军小编给大家展示的是Linux安装g77编译器的技巧&#xff0c;精心挑选的内容希望大家…

vs使用未初始化的内存怎么解决_遇到C语言内存错误怎么办?一定要找准这六个原因...

一、没有为指针分配内存定义了指针变量&#xff0c;但是没有为指针分配内存&#xff0c;即指针没有指向一块合法的内存。浅显的例子就不举了&#xff0c;这里举几个比较隐蔽的例子。1、结构体成员指针未初始化struct student { char *name; int score; }stu,*pstu; int main() …

cad求和插件_黑科技 | 无BIM建模下平面CAD自动生成门窗表

如果你接到的施工图既不是用天正出的&#xff0c;也不是用revit出的&#xff0c;还得统计门窗表&#xff0c;那么你需要读完这篇文章。为了能够让自己和所有底层同行们从这项无脑又烧脑的机械劳动中解脱&#xff0c;C君近期利用茶余饭后的时间开发了一个小插件&#xff0c;可以…

linux数据库实例开机启动,linux下数据库实例开机自启动设置

linux下数据库实例开机自启动设置 1、修改/oratab [rootorg54 ~]# vi/etc/oratab --把N改为Y&#xff0c;如下提示 # This file is used by ORACLEutilities. It is created by root.sh # and updated by the Database ConfigurationAssistant when creating # a datablinux下数…

tensorboard ckpt pb 模型的输出节点_算法工程化系列——模型固化

摘要基于tensorflow训练的模型一般被保存为ckpt形式的文件&#xff0c;随着当前深度学习模型网络越来越大&#xff0c;对应模型也会非常大。当对外提供服务的时候&#xff0c;如果采用ckpt的形式&#xff0c;服务进程被调起来非常困难&#xff0c;且推理服务一般速度也较慢(会达…

深度linux内核升级,深度操作系统 2020.11.11 更新发布:内核升级

原标题&#xff1a;深度操作系统 2020.11.11 更新发布&#xff1a;内核升级IT之家11月11日消息 今日&#xff0c;深度操作系统宣布2020.11.11 更新现已发布。本次更新包括升级内核、Debian 10.6 仓库以及系统安全性更新。系统安全方面&#xff0c;本次更新修复了 Firefox-ESR 安…

python爬电影_使用Python多线程爬虫爬取电影天堂资源

最近花些时间学习了一下Python&#xff0c;并写了一个多线程的爬虫程序来获取电影天堂上资源的迅雷下载地址&#xff0c;代码已经上传到GitHub上了&#xff0c;需要的同学可以自行下载。刚开始学习python希望可以获得宝贵的意见。 先来简单介绍一下&#xff0c;网络爬虫的基本实…

sentinel 端口_Sentinel原理:控制台是如何获取到实时数据的

Sentinel 系列教程&#xff0c;现已上传到 github 和 gitee 中&#xff1a;GitHub&#xff1a;https://github.com/all4you/sentinel-tutorialGitee&#xff1a;https://gitee.com/all_4_you/sentinel-tutorialSentinel 能够被大家所认可&#xff0c;除了他自身的轻量级&#x…

python 最小二乘回归 高斯核_「机器学习」一文读懂线性回归、岭回归和Lasso回归...

点击上方蓝色字体&#xff0c;关注AI小白入门哟作者 | 文杰编辑 | yuquanle本文介绍线性回归模型&#xff0c;从梯度下降和最小二乘的角度来求解线性回归问题&#xff0c;以概率的方式解释了线性回归为什么采用平方损失&#xff0c;然后介绍了线性回归中常用的两种范数来解决过…

优先队列默认是小顶堆吗_一分钟带你读懂什么是堆?

堆其实就是一种特殊的队列——优先队列。 普通的队列游戏规则很简单&#xff1a;就是先进先出&#xff1b;但这种优先队列搞特殊&#xff0c;不是按照进队列的时间顺序&#xff0c;而是按照每个元素的优先级来比拼&#xff0c;优先级高的在堆顶。 这也很容易理解吧&#xff0c;…