【Scala】Vector内部结构与内存共享原理

Scala不可变集合

Scala不可变集合的设计目标是提供高效又安全的实现。这些集合中的大部分都是用高级技巧来在集合的不同版本之间“共享”内存。其中较长使用到的是Vector和List。
在一般的编程任务中,不可变集合有很多超出可变集合的优点。尤其重要的一点是不可变集合可以在多线程之中共享而无需加锁。

Vector内部结构

Scala的Vector实现为一组嵌套数组,在分割和连接时非常有效率。适用于大部分通用算法,因为它有高效的下标计算能力,以及能够在使用像+:和++方法时共享大部分内部结构的能力。
Vector采用分支系数为32的树状数据结构,分支因子是每个父节点允许拥有的最大子节点的数目。其随机访问(搜索或修改)复杂度是log_32(N),使用32位整数下标时在JVM上是个效率不错的小常量,即使对很大的N来说都近似一个常量。
Vector是个由元素的下标组成的前缀树(trie),前缀树是给定路径上的所有子节点功用某种形式的公共键值。我们可以根据任何下标的二进制形式得到查找路径,实现高效的元素查找。

Vector复制过程中的结构共享原理

在实际应用中,为了保持变量的不可变性,对有用的集合进行复制通常是必要的。假设有一个包含100 000个元素的Vector,需要得到一个副本,并替换掉原Vector的第8个元素,此时如果构造一个全新的100 000个元素的Vector将会是极其低效的。
为了兼顾高效和不可变性,可以通过共享原始Vector中的不变部分,而以某种方式表示变化部分,那么就可以高效地“创建”新Vector了。这种思想称之为结构共享

如果其他线程中的代码正在对原始Vector做其他不同的操作,对原始的Vector的复制不会影响该操作,因为原Vector没有被修改。这样,只要对旧版本有一个或多个引用,就可以创建一个Vector的“历史”版本。直到对旧版本的引用消失为止,旧版本才会被垃圾回收。

下面的图示解释了创建并修改副本过程中结构共享的原理:
使用#1来引用原树的根节点:



现在假设要在2和3之间插入2.5,要创建一个新的副本,我们并不需要修改原来的树结构,而是创建新树。
值得注意的是,原来的树(#1)仍然存在,但我们又创建了新的根(#2)和新的节点。创建新的树共享重用了原来的大部分节点,这样有助于降低修改集合的开销。


Vector查找原理

Scala的Vector集合非常类似于一个分支系数为32的下标前缀树。关键区别在于Vector用一个数组来表示分支。这使整个结构变成数组的数组(嵌套数组)。
下图是分支系数为2的二进制Vector:


其中有三个基本素组:display0、display1和display2.这些数组代表原始前缀树的深度。每个显示元素(display element)都是一个更深一层的的嵌套数组(display0是元素的数组,display1是数组的数组,display2是数组的数组的数组)。查找集合元素的步骤是先判断其深度,然后用跟前缀树一样的方式确定元素所在的数组。比如找数字4,其深度为2,所以先选择display2数组,4的二进制形式100,所以外层数组是下标为1的位置上,中层数组下标为0,最后4就位于结果数组的下标0的位置上。

二进制前缀树根据下标随机取值的复杂度是log_2(n),Scala的Vector的分支系数为32,那么访问任何元素的时间复杂度是log_32(n),对32位的下标也就大约是7,对64位大约是13.而对于较小的集合,排序的开销也会降低,所以访问速度会更快。所以随机访问的时间复杂度与前缀树的大小成正比。

小结

Scala的Vector为32分支,这除了带来查找时间和修改时间可以随集合大小伸缩外,它还提供了不错的缓存一致性,因为集合里相近的元素有很大可能位于同一个内存数组里。其高效结合不可变所带来的线程安全使之成为库里最强大的有序集合。
Scala的序列类型中Vector和List数据结构都是很常用的,Vector的所有操作都是O(1)(常数时间),而List对于那些需要访问头部以为元素的操作都需要O(n)操作,所以只在频繁执行头尾分解的情况下,推荐使用List。

转载请注明作者Jason Ding及其出处
jasonding.top
Github博客主页(http://blog.jasonding.top/)
CSDN博客(http://blog.csdn.net/jasonding1354)
简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354进入我的博客主页

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • scala学习笔记 第2章 变量和数据类型 基本数据 scala的核心数据为四种 :字面量、值、变量、类型 值使...
    485b1aca799e阅读 2,116评论 0 1
  • 53.计算字符 在字符串中获取字符值的数量, 可以使用字符串字符属性中的计数属性: let unusualMena...
    无沣阅读 1,081评论 0 4
  • 在平时使用集合的时候,我们经常会选择 Scala 中通用的集合,例如:Seq、Map、List等等,有的时候选择「...
    ShawRain阅读 1,090评论 0 0
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • 过有序生活 孩子第二个30天目标:学会自己安排时间 妈妈第二个30天目标:吃饭细嚼慢咽 加油(潘也齐+7)践行打卡...
    chenlan_c55e阅读 150评论 0 0