在讲述字符串的奇技淫巧之前,我们需要先了解 V8 对于 JavaScript String 的表达。
在 V8 中字符串有如下 5 种表达模式:
- SeqString
- 在 V8 堆内使用(类似数组的)连续空间存储字符串。
- 实际数据存储时分为 OneByte、TwoByte(Unicode)两类。
- ConsString(first, second)
- 在字符串拼接时,采用树形结构表达拼接后(first + second)的字符串。
- SliceString(parent, offset)
- 在字符串切割时,采用 offset 与 [length] 表达父字符串(parent)的一部分。
- ThinString(actual)
- 直接引用另一个字符串对象(actual)。
- 在多数情况下可以被认为与 ConsString(actual, empty_string) 等价。
ExternalString
- 代表了产生在 V8 堆外的字符串资源。
- 实际数据表达时分为 OneByte、TwoByte(Unicode)两类。 ThinString 需要开启 flag: —thin_strings 在老版本中最大字符串长度均为 ~268.4M ,而在 V8 新版本(≥ 6.2.4)中最大长度限制增加到 ~1.073B(64 位平台):
32 位平台, (仍不变)
- 64 位平台, (比起 32 位平台增加了 4 倍)
64 位平台下最大长度:由 kMaxLength (也就是 int32)可以表达的最大正数值 > ,减去 1 bit(TwoByteString 表达需占用 2 个字节),再减去 kHeaderSize = 24 得到。
所有的表达模式均有 [length] 属性(由基类 String 定义)记录了字符串中的字符数(UTF-16 单元数),但只有 SeqString 真正存储了字符数据,而 ConsString 、SliceString 、ThinString 均为其它表达模式的引用(不存储字符数据)。
在实际应用过程中,由于经常发生字符串拼接操作,故 ConsString 经常会被用来表达相应的字符串。 字符串拼接时如果以传统方式(如 SeqString)存储,拼接操作的时间复杂度为 O(n) ,采用 > 绳索结构[Rope Structure] (也就是 ConsString 所采用的数据结构)可以减少拼接所花费的时间。 为了便于理解,我们来看个例子:
1、创建 2 个英文字符串并拼接它们:var hello = "hello";
var world = "world";
var hello_world = hello + world;
由于 hello 与 world 中所有字符均为单字节字符,故 V8 采用 SeqOneByteString 来表达它们。
为了提升短字符串拼接后的访问性能,V8 在这里(hello_world)并不会生成 ConsString 而是直接将 hello 与 world 中的字符拷贝到新建的 SeqOneByteString 中来存储它们。 V8 定义最小 ConsString 长度为 kMinLength = 13 个字符。
2、创建 1 个中文字符串并与步骤 1 生成的字符串(hello_world)拼接:
var chinese = "你好世界";
var mixed = hello_world + chinese;
由于 chinese 中存在有双字节字符,故 V8 采用 SeqTwoByteString 来表达它。
拼接后字符串(mixed)超过了最小 ConsString 长度限制,
生成 mixed = ConsString(hello_world, chinese) 这样的树形结构。
3、将步骤 2 生成字符串(mixed)进行切割:
var sliced = mixed.slice(1);
由于切割后字符串(sliced)超过了最小 SliceString 长度限制,
生成 sliced = SliceString(mixed, 1) 这样的空间结构。
V8 定义最小 SliceString 长度也为 kMinLength = 13 个字符。
在这里我们可以看到,只有 SeqOneByteString 与 SeqTwoByteString 实例存储实际的字符,而 SliceString 与 ConsString 实例内部其实只是其它表达模式的引用(不存储字符数据)。
需要注意的是,在 SliceString 场景中被切割掉的字符并不会被 V8 GC 回收。
也就是说,例子中 SeqOneByteString 实例中存储的 “h” 字符并不会被 V8 GC 回收。
总结一下:
- 字符串在 V8 中采用何种模式表达取决于此字符串的构造方式(Seq 还是 Cons 等)。
- 字符串拼接时,比起重新分配一个空间进行存储,采用树形结构(ConsString)表达更加高效。