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.pswp.cn/news/533858.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/533858.shtml
英文地址,請注明出處:http://en.pswp.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;并經歷了采用電壓頻率協調控制、轉差頻率控制、矢量控制以及直接轉矩控制的發展過程。其…

python中標識符的命名規則_Python——標識符的命名規則

01 Python語言的特點 python的語言特點有很多&#xff0c;我們這里只講一點&#xff0c;python是一門面向對象的語言&#xff0c;即一切皆對象&#xff08;Linux中有一句是&#xff1a;一切皆文件&#xff09;&#xff0c;括號內的只是打個比方&#xff0c;不懂也沒事&#xff…

python內置對象是什么_Python內置對象類型之數字類型

Python中有6種內置對象類型整數、浮點數–Number字符串–String列表–List元組–Tuple字典–Dictionary集合–Set不可變類型&#xff1a;Number、String、Tuple可變類型&#xff1a;List、Dictionary、Set知識點&#xff1a;變量和對象的關系–引用變量的使用數字類型的常見使用…

oracle的exp和imp,oracle exp和imp

--1.exp和imp的輸入都是名字和值對&#xff1a;如&#xff1a;exp parameter_namevalue 或exp parameter_name(value1,value2,value3..)--2.exp和imp都支持helpy選項。命令格式為:exp/imp helpy--3.exp中的參數:參數名稱 默認值 含義 建議compress Y 不壓縮導出數據的內容 comp…

python案例實操_用案例實操學習Python ,培養編程邏輯思維

案例一&#xff1a; A、B、C、D、E 五人在某天夜里合伙去捕魚&#xff0c;到第二天凌晨時都疲憊不堪&#xff0c;于是各自找地方睡覺。 日上三桿 A 第一個醒來&#xff0c;他將魚分為五份&#xff0c;把多余的一條魚扔掉&#xff0c;拿走自己的一份。 B 第二個醒來&#xff0c;…

oracle錯誤1327,Oracle中的PGA監控報警分析(r11筆記第97天)

最近接到一個數據庫報警&#xff0c;讓我頗有些意外&#xff0c;這是一個PGA相關的報警。聽起來感覺是應用端的資源調用出了問題。報警內容大體如下&#xff1a;報警內容: PGA Alarm on alltest------------------------------------報警級別: PROBLEM------------------------…

python控制臺清屏_Python Shell 怎樣清屏?

啟動Python有兩種方式&#xff0c;分別為“Windows命令行窗口”和“IDLE” “命令行窗口”下可以通過如下兩種方法&#xff1a; 1. import subprocess subprocess.call("clear") # linux/mac subprocess.call("cls", shellTrue) # windows 執行完次命令后&…

python卸載opencv包_Ubuntu16.04卸載opencv2.4.9并安裝opencv3.2.0+contrib

本文為作者原創&#xff0c;轉載請注明出處(http://www.cnblogs.com/mar-q/)by 負赑屃需要用到opencv中的surf和sift算法&#xff0c;機器上只有opencv3.2&#xff0c;沒有擴展包&#xff0c;于是就去GitHub和官網看了下&#xff0c;沒想到8月剛出了3.3&#xff0c;一個激動就想…

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都可以作為日志提…

oracle樹狀排序,Oracle樹狀結構查詢

oracle用表的形式組織數據&#xff0c;某些數據還呈現樹狀結構&#xff0c;提供了對這些數據的組織、查詢等功能。在掃描樹結構表時&#xff0c;要依次訪問樹中的每一個節點&#xff0c;并且每個節點只能訪問一次&#xff0c;其步驟如下&#xff1a;1&#xff1a;從根節點開始2…

python numpy讀取數據_大神教你python 讀取文件并把矩陣轉成numpy的兩種方法

導讀 今天小編就為大家分享一篇python 讀取文件并把矩陣轉成numpy的兩種方法&#xff0c;具有很好的參考價值&#xff0c;希望對大家有所幫助。一起跟隨小編過來看看吧 在當前目錄下&#xff1a; 方法1&#xff1a; file open(‘filename) a file.read() b a.split(‘\n)#使用…

datagrid wpf 獲取選中_c# WPF DataGrid 獲取選中單元格信息

private void Button_Click(objectsender, RoutedEventArgs e){DataGridCell cell dg.GetCell(1, 2);TextBlock tb cell.Content asTextBlock;Console.WriteLine(tb.Text);}public static classDataGridExtension{/// ///獲取DataGrid控件單元格/// /// DataGrid控件/// 單元格…

redis哨兵高可用-源碼篇

前段時間寫過兩篇redis哨兵的文章,一篇是redis哨兵模式的搭建。另外一篇是redis哨兵主從切換的原理,。 當時寫的原理篇,是手動模擬主節點故障,然后查看主從切換的日志推算哨兵主從切換的流程。但是感覺這樣搞出來的流程太粗&#xff0c;忽略了很多細節&#xff0c;真正要搞明白…

python獲取網頁數據對電腦性能_【Python】網頁數據爬取實戰

由于網頁結構跟之前有變化&#xff0c;還不是很熟悉。代碼待完善&#xff0c;問題記錄&#xff1a;騰訊新聞二級網頁內容爬取有問題。鏈家網站頭文件沒有用到。爬取一條騰訊視頻的header內容&#xff0c;存入txt。要求&#xff1a;包含網頁鏈接包含title包含所有headers信息imp…

python集合去重_python集合去重

[python中對list去重的多種方法 怎么快速的對列表進行去重呢&#xff0c;去重之后原來的順序會不會改變呢&#xff1f; 1.以下的幾種情況結果是一樣的&#xff0c;去重之后順序會改變: i [1,2,3,3,4,2,3,4,5,6,1] news_i [] for id in i: if id not in news_i: news_i.append(…

linux進程pid分配規則,Linux進程pid分配法【轉】

一. 概述Android系統創建進程&#xff0c;最終的實現還是調用linux fork方法&#xff0c;對于linux系統每個進程都有唯一的 進程ID(值大于0)&#xff0c;也有pid上限&#xff0c;默認為32768。 pid可重復利用&#xff0c;當進程被殺后會回收該pid&#xff0c;以供后續的進程pid…