作者
Patrik Nyblom <pan(at)erlang(dot)org> , Fredrik Svahn <Fredrik(dot)Svahn(at)gmail>
状态
草案
类型
标准跟踪
创建时间
2010-09-29
Erlang 版本
OTP_R14B
取代
9

EEP 35: 二进制字符串模块 #

摘要 #

此 EEP 包含关于 binary_string 模块的已开发建议,该模块最初在 EEP 9 中提出。但模块名称现在已更改为 bstring

EEP 9 提出了多个模块,并部分被后续的 EEP(即 EEP 11EEP 31)所取代,但仍然包含尚未实现的宝贵建议。因此,EEP 9 中建议的最后一个剩余模块将在此单独的 EEP 中出现。这是在与 EEP 9 的原始作者达成一致意见后进行的。

建议 bstring 模块包含用于方便地操作存储在二进制文件中的文本数据(即二进制字符串)的函数。它在某种程度上类似于 string 模块(它是面向列表的),但不应简单地将其视为二进制文件的 string 模块。

建议的模块处理 Erlang 标准字符编码(即 ISO-Latin-1 和 UTF-8)中的二进制字符编码。

动机 #

文本字符串在 Erlang 中传统上表示为整数列表。虽然这很方便,并且或多或少地内置于语言的语法中(即 “ABC” 是 [$A,$B,$C] 的语法糖),但通常需要更紧凑的表示形式。此外,在某些情况下,二进制文件在算法复杂性方面比列表更有效率(尤其是在 ISO-Latin-1 的固定字符宽度情况下)。

最近,标准库中添加了更多模块来帮助使用二进制文件作为文本字符串,既表示 ISO-Latin-1 字符又表示以 UTF-8 编码的 Unicode 字符串。最值得注意的是 re 库,还有 unicode 模块是 stdlib 中相当新的补充,这将使程序员在操作二进制编码的字符串时更加轻松。此外,还存在一个用于在面向字节的二进制文件中进行快速搜索和替换的模块(模块 binary),但库中还没有传统的字符串操作模块。为了方便使用二进制编码的字符串,需要这样一个模块。

基本原理 #

用于列表的文本导向操作的模块 string 在标准库中存在了很长时间,以至于大多数程序员都不记得没有它的时代。据说它最初是两个不同的字符串模块的合并,由两个不同的程序员编写和设计,他们的目标可能略有不同,并且对函数命名的看法肯定略有不同。虽然有时因重复的功能和不一致的函数命名而受到批评,但在 Erlang/OTP 的整个生命周期中,该模块仍然很有用。所使用的字符串表示形式也经受住了 Unicode 的发展。

值得注意的是,string 模块中实际依赖于语言或区域的唯一函数是后来添加到该模块中的函数。这些函数(如 to_upperto_lowerto_integerto_float),或它们的二进制等效项,不是我为 bstring 建议的模块接口的一部分,原因很简单,它们需要 Erlang 中尚未存在的语言支持。未来的 EEP 可能会建议这种语言支持(即某种“区域设置”支持),但那是本 EEP 未涵盖的未来工作。

因此,无论如何受到批评,字符串模块对于操作列表非常有用,并且对于二进制字符串也需要相同的功能。虽然很多功能会相似,但在实现用于操作二进制文件中编码的字符串的模块时,有一些主要问题需要考虑

  • Unicode - 二进制文件可以具有不同的编码。以 UTF-8 编码的字符可能占用多个(最多四个)字节位置,即使是相同的字符在 ISO-Latin-1 和 UTF-8 中也可能具有不同的编码(所有代码点从 128 到 255)。函数需要明确地知道字符编码。编码信息不存在于二进制文件中。

  • 混合字符编码 - 由于字符可以以不同的方式编码,因此同一程序中的两个字符串可能具有不同的编码。应在整个模块中一致地解决为函数提供非均匀字符串编码数据的问题,并且应在适用的情况下选择返回的编码。

  • 默认字符编码 - 由于函数将使用额外的参数来指定编码,因此一致的默认值可能很有用。选择默认值并不完全简单,因为传统上使用 ISO-Latin-1,而未来建议使用 UTF-8。

  • 语言 - Erlang 没有“区域设置”或首选数字格式的概念。通用的字符串模块不能假定特定的字母大小写概念,也不能假定特定的数字编码格式(对于浮点数尤其如此)。

  • 单词分隔符 - 空格字符当然不是文本数据(在任何语言中)的唯一单词分隔符。单词由空格分隔的概念对相关语言施加了限制。

  • 从左到右或从右到左 - 诸如左或右之类的概念来表示字符串的开头或结尾肯定不是语言无关的。虽然语言中的字符串具有开头和结尾,但该开头和结尾可以放置在图形表示的左侧、右侧,甚至顶部、底部或中心。字符串操作模块不应使用暗示从左到右的脚本或任何其他类型脚本的命名。

  • 命名和重复的功能 - 原始的 string 模块被指责为命名和功能略有不一致。实际上,唯一重复的函数是 substrsub_string。可能需要清理一些接口。

  • 面向字节与面向字符的返回值 - 在处理 Unicode 数据时,一个字符可能占用多个字节,因此,例如,计算字符串中的字符数并不能很好地说明字符串的实际大小(以字节为单位)。此外,稍后处理二进制文件可能需要面向字节的字符串操作,而不是面向字符的操作(即,您想使用 binary 模块或位语法来操作字符串),而字符实际上是构成字符串的元素,而不是字节。您需要两者。

  • 新的或替换的功能 - 从多个来源提出了新的功能,最值得注意的是 EEP 9。例如,EEP 9 中建议的函数 split

    string:tokens/2

    非常相似。例如,我们是否应该保留 tokens

我将在下面解决不同的问题。

Unicode #

该接口必须支持 ISO-Latin-1 和 UTF-8。unicode 模块甚至支持更多的编码,但 Erlang/OTP 对所有“内部”接口使用 UTF-8,并且 UTF-8 是二进制 Unicode 字符串的预期编码。即使 UTF-8 与 7 位 ASCII 范围内的 ISO-Latin-1 兼容,代码点在 128 到 255 之间的字符在“纯” ISO-Latin-1 编码和 UTF-8 中的编码方式也不同。这意味着 bstring 模块中的所有函数都需要将实际编码作为一个或多个额外参数。

可以发明一种更抽象的二进制字符串格式,其中数据例如表示为一个元组,其中将字符串和编码打包在一起。然而,没有其他模块支持这样的字符串结构,而且我认为这并没有真正增加任何东西,无论是功能还是可读性。考虑以下代码

bstring:tokens(Bin,latin1,[$ ,$\n])

bstring:tokens({Bin,latin1}, [$ ,$\n]).

甚至

bstring:tokens(#bstring{data = Bin, encoding = latin1}, [$ ,$\n]).

相比,在许多情况下,需要在调用时添加额外的信息,使得代码的可读性或编写的简单性不如使用单独的额外参数。考虑一下,如果我们有一个编码的默认值。代码

f(Data) ->
    bstring:tokens(Data,[$ ,$\n]).

无论如何都不会指示 Data 应该是具有默认编码的二进制文件,还是某种指示实际字符串及其编码的复杂数据结构。

我认为编码的额外参数是直接而简单的,并且在使用其他模块(即 rebinaryfile 等)中的二进制字符串时,它使编程更容易。我认为我们根本不应该为此模块使用特殊的字符串数据类型,字符编码应作为单独的参数提供。

混合字符编码 #

为了简化字符编码之间的转换,我认为该接口应该接受不同的参数和返回值使用不同的编码。这使得可以动态转换,并让函数决定所提供参数和返回值的最有效的字符转换路径。

这种方法的缺点是一些函数将使用许多参数来指示不同的字符编码,例如,字符串连接例程可能如下所示

concat(BString1, Encoding1, BString2, Encoding2, Encoding3) -> BString3

像这样调用

US = bstring:concat(SA,latin1, SB, latin1, unicode),

这可能看起来有点笨拙。另一方面,转换是动态进行的,您无需显式调用 unicode 模块来转换结果。

我认为隐式转换非常有用,值得使用额外的参数。例如,如果没有它,concat 函数或多或少会毫无用处,如果禁止转换,位语法会更容易使用。

默认字符编码 #

选择默认字符编码并非显而易见。尽管 ISO-Latin-1 是 Erlang 中的默认编码(例如 «“korvsmörgås”» 会生成一个 ISO-Latin-1 编码的二进制字符串),但 UTF-8 的使用预计在未来会增长。

尽管选择 UTF-8 作为默认编码很诱人,但我认为我们应该坚持使用 ISO-Latin-1 作为默认编码,即使对于此模块也是如此。原因有以下几点:

  • 我们不需要在添加到标准库的每个模块中都强制推行新标准。一致性无疑会增加价值,并且位语法、源代码编码以及像 io:format 例程之类的东西都以 ISO-Latin-1 作为默认编码。不要让这个模块与其他模块不一致。

  • string 模块通常用于操作任意整数列表,并不总是真正表示文本数据。同样,如果使用 ISO-latin-1 版本,bstring 也可能用于操作任意字节块。ISO-Latin-1 实际上是未解释的原始字节,因此任何二进制数据都可以在面向 ISO-Latin-1 的例程中处理。使用 UTF-8 编码作为默认编码会将默认函数的使用范围缩小到仅处理真正的文本数据。

  • 纯 ISO-Latin-1 函数实现将是最有效的,因为根本不需要数据检查。任何字节值在任何版本中都是可接受的。即使某些函数期望 ISO-Latin-1 数据,它们也适用于 UTF-8 字符串。ISO-Latin-1 版本和 UTF-8 版本之间的区别仅在于输入数据控制。如果提供给例如 bstring:concat 的数据已经检查过 UTF-8 的正确性,那么该函数的简单 ISO-Latin-1 版本既更有效率,又能保证给出与输入一样正确的输出。

      CorrectUtf8_1 = give_me_good_string(),
      CorrectUtf8_2 = give_me_another_good_string(),
      CorrectUtf8_3 = bstring:concat(CorrectUtf8_1, latin1, CorrectUtf8_2, latin1, latin1),
      ...
    

    简而言之,ISO-Latin-1 版本的函数比纯 UTF-8 版本更通用,效率也更高。

  • 可以轻松编写一个提供纯 UTF-8 接口的包装模块。对于 UTF-8 包装器来说,通过包装器的开销将相对较低,因为模块中 UTF-8 字符串的字符解码/编码开销会相当高。简而言之,与检查数据 UTF-8 正确性的成本相比,包装器的成本非常低。

    我实际上建议使用一个模块 ubstring,它具有 bstring 接口中隐含默认编码的部分,但区别在于期望使用 UTF-8。例如,函数 ubstring:tokens/2 如下所示:

      tokens(S,L) -> bstring:tokens(S,unicode,L).
    

    很简单。

总而言之,我认为所有函数都应该存在一个没有提供编码且期望使用 ISO-Latin-1 编码数据的版本。

语言 #

尽管 Unicode 字符可以用来表达大多数已知、现存和已灭绝的文字,但语言和地区知识是完全不同的概念。字符串接口通常会对字符串施加特定于语言的属性,例如从左到右的写入方向、由空格分隔的字符组构成的单词概念、表示数字和小数点的方式等等。由于 Erlang (目前) 没有一种方法可以指定字符串的此类特定于语言或地区的属性,因此该接口不应包含与语言相关的功能。string 模块最初不包含此类函数(除了字符对齐函数被命名为 leftright 之外),但不幸的是,后来添加了像 to_floatto_upper 这样的函数。

我认为在 string 模块中使用与语言相关的函数是一个错误,我不想再次犯同样的错误。因此,我在 bstring 中没有包含此类函数或名称。

我更建议将“区域设置”功能作为未来 EEP 的主题。对于那些认为这很简单的人,请尝试为所有欧洲语言编写一个正确的 to_upper 函数,确保它在所有可以运行 Erlang 的平台上工作……也许不是火箭科学,但需要大量的元数据。这些数据并不总是在底层操作系统中可用,但可能需要与 Erlang/OTP 一起分发以实现一致的功能。绝对值得单独编写一个 EEP。

单词分隔符 #

在与语言无关性相关联时,我认为我们应该放弃单词作为一组由空格分隔的字符的概念。“标记”这个词更通用,并且不像那样表示语言结构。string 模块使用 ASCII 空格字符作为默认的单词分隔符,我认为应该在 bstring 中删除它。无论应该分隔标记的内容都应该提供,可能作为替代方案。因此,我建议使用函数 bstring:num_tokensbstring:nth_token 来实现 string:wordsstring:sub_word 的功能。

EEP 9 中所述,我建议使用一个新函数 split 来处理标记的多字符分隔符的情况。将 splitjoin 组合在一起也可以方便地创建一个 replace 函数。

从左到右或从右到左 #

如前所述,我认为不应该在接口中暗示图形表示的方向,因此我建议使用诸如 leading 和 trailing (表示二进制中的前导和尾随字符)之类的概念,而不是任何方向概念。我还认为对齐字符串(如 strings:right 等)可以在一个函数 align 中解决,如果需要实现的话,可以使用原子 leadingtrailingcenter 中的一个作为参数。

命名和重复功能 #

我绝对不认为我们应该将 string 中的所有接口都复制到 bstring 中。特别是别名的接口不应带到 bstring 模块。然而,string 模块中的大多数函数都有简短且相当描述性的名称,通常与其他语言中的名称相似。我认为对于从字符串末尾向开头工作的函数使用 r 前缀是一个不错的选择,对于补码使用 c 也是一个不错的选择。

面向字节的值与面向字符的返回值 #

string 中的某些函数(当然很有用)会返回表示字符位置的数字。相同的函数绝对应该存在于 bstring 模块中,并且返回值绝对应该是面向字符的。然而,字节偏移量绝对有用,例如,如果我们使用像 span 这样的函数来查找不在一组字符中的第一个字符,我们可能也需要第一个字符的字节偏移量。

我建议添加一些返回字节偏移量的接口,或者像 binary 模块和 re 中使用的 part(),以处理在某些情况下对字节偏移量和长度的需求。函数名称的 b 后缀可以表示此类功能,因此 bstring:span 返回一个字符位置,而 bstring:spanb 返回一个字节位置,并且 btring:str 返回一个字符位置,而 bstring:strb 返回一个 part()。尽管这最终会在接口中产生更多函数,但在选项列表中使用返回类型更改选项不是正确的方法(我知道,我在 re 中有它们,但总的来说这不是一个好主意……)。

新增或替换功能 #

在编写通用字符串模块时,可以添加的新功能(或多或少是深奥的功能)没有尽头。我认为,至少在初始实现中,我们应该坚持 EEP 9 中概述的功能,即扩展 str 及其友元以可选地接受要搜索的替代字符串列表,添加一个函数 split 来处理多字符分隔符(与函数 tokens 中的单字符分隔符相反)和一个替换函数,我认为应该像在其他模块中那样命名为 replace

然而,使用 binary 模块的预编译匹配不是一个好主意,因为 binary 模块没有字符编码的概念。搜索字符串需要以定义的字符编码给出,并且在进行高效搜索时需要知道“大海捞针”和“针”的编码。因此,没有预编译的搜索表达式。

建议的手册页摘录 #

如上所述,我更喜欢将 bstring 作为二进制字符串模块的名称,而不是最初建议的更冗长的名称 binary_string。在模块 bstring 中,我建议使用以下接口,以 OTP 手册页的形式表示。

数据类型 #

encoding() = latin1 | unicode | utf8
    - The encoding of characters in the binary data, both input and output
bstring()
    - Binary with characters encoded either in ISO-Latin-1 or UTF-8
unicode_char() = non_negative_integer()
    - An integer representing a valid unicode codepoint
non_negative_integer()
    - An integer >= 0

导出 #

align(BString, Alignment, Number, Char) -> Result #

align(BString, Encoding, Alignment, Number, Char) -> Result #

类型

BString = Result = bstring()
Encoding = encoding()
Alignment = leading | trailing | center
Number = non_negative_integer()
Char = unicode_char()

根据 Alignment 参数,在长度为 Number 个字符的 Result 中对齐 BString 中的字符。通过在二进制字符串的开头或结尾(或两者)插入字符 Char 来完成对齐。

生成的二进制字符串将正好包含 Number 个字符,如果字符串包含的字符多于 Number,则字符串将被截断 - 如果 Alignmentleading,则在末尾截断,如果 Alignmenttrailing,则在开头截断,如果 Alignmentcenter,则在两端截断。如果 Encodingunicode,则 Result 可能包含比 Number 更多的字节,因为一个字符可能需要多个字节。

示例

> bstring:align(<<"Hello">>, latin1, center, 10, $.).
<<"..Hello...">>

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果 BString 不包含根据 Encoding 参数编码的字符,则会引发 badarg 异常,EncodingAlignment 具有无效值,字符 Char 无法在给定为 Encoding 的字符编码中编码,或者任何参数的类型错误。

chr(BString, Character) -> Position #

chr(BString, Encoding, Character) -> Position #

rchr(BString, Character) -> Position #

rchr(BString, Encoding, Character) -> Position #

类型

BString = bstring()
Encoding = encoding()
Character = unicode_char()
Position = integer()

返回 BString 中第一次/最后一次出现 Character 的字符位置(从零开始)。 如果 Character 未出现,则返回 -1

请注意,字符位置与字节位置不同。 使用 chrbrchrb 函数获取字节位置。

如果 Character 无法以指定的编码表示,这不是错误,只会返回 -1 作为返回值。

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果 BString 的搜索部分不包含根据 Encoding 参数编码的字符,或者 Encoding 具有无效值,或者任何参数的类型错误,则会引发 badarg 异常。

chrb(BString, Character) -> {BytePosition, ByteLength} #

chrb(BString, Encoding, Character) -> {BytePosition, ByteLength} #

rchrb(BString, Character) -> {BytePosition, ByteLength} #

rchrb(BString, Encoding, Character) -> {BytePosition, ByteLength} #

类型

BString = bstring()
Encoding = encoding()
Character = unicode_char()
BytePosition = integer()
ByteLength = non_negative_integer()

chrrchr 的功能类似,但返回字符的字节位置和字节长度。

如果未找到字符,则返回 {-1,0}

concat(BString1, BString2) -> BString3 #

concat(BString1, Encoding1, BString2, Encoding2, Encoding3) -> BString3 #

类型

BString1 = BString2 = BString3 = bstring()
Encoding1 = Encoding2 = Encoding3 = encoding()

连接两个二进制字符串以形成新的字符串。 以 Encoding3 给定的编码返回新的二进制字符串。

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果 BString1Bstring2 不包含根据 Encoding1Encoding2 参数编码的字符,或者编码参数具有无效值,或者输入参数中的代码点无法在输出编码中表示,或者任何参数的类型错误,则会引发 badarg 异常。

equal(BString1, BString2) -> bool() #

equal(BString1, Encoding1, BString2, Encoding2) -> bool() #

类型

BString1 = BString2 = bstring()
Encoding1 = Encoding2 = encoding()

测试两个二进制字符串是否相等。 如果相等,则返回 true,否则返回 false

Encoding1BString1 的编码,Encoding2BString2 的编码。

请注意,字符串可以具有不同的编码,并且比较的是字符串中编码的字符值。 只要二进制字符串相等,就会扫描这些字符串,这意味着如果函数返回 true,则两个字符串都已正确编码,而返回值 false 并不能保证两个二进制字符串都已正确编码。 如果在比较字符串时确定了错误的编码,则会引发异常,而不是在未检查的字符串部分包含编码错误时引发异常。

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果在比较期间遇到根据编码参数错误编码的字符,或者编码参数具有无效值,或者任何参数的类型错误,则会引发 badarg 异常。

join(BStringList, Separator) -> Result #

join(BStringList, BStringListEncoding, Separator, SeparatorEncoding, ResultEncoding) -> Result #

类型

BStringList = [bstring()]
BStringListEncoding = SeparatorEncoding = ResultEncoding = encoding()
Separator = bstring()
Result = bstring()

返回一个二进制字符串,其中 BStringList 的元素由 Seperator 中的二进制字符串分隔。

BStringList 中的所有二进制字符串应具有相同的编码(作为 BStringListEncoding 给出)。 但是,Separator 可以具有不同的编码(作为 SeparatorEncoding 给出),Result 也可以具有不同的编码(作为 ResultEncoding 给出)。

示例

> bstring:join([<<"one">>, <<"two">>, <<"three">>], latin1, <<", ">>, latin1, latin1).
<<"one, two, three">>

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果 BStringListSeparator 中的二进制字符串不包含分别根据 BStringListEncodingSeparatorEncoding 参数编码的字符,或者编码参数具有无效值,或者输入参数中的代码点无法在输出编码 ResultEncoding 中表示,或者任何参数的类型错误,则会引发 badarg 异常。

len(BString) -> Length #

len(BString, Encoding) -> Length #

类型

BString = bstring()
Encoding = encoding()
Length = non_negative_integer()

返回二进制字符串中的字符数。

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果 BString 不包含根据 Encoding 参数编码的字符,或者 Encoding 具有无效值,或者任何参数的类型错误,则会引发 badarg 异常。

nth_token(BString, N, CharList) -> Result #

nth_token(BString, Encoding, N, CharList) -> Result #

类型

BString = Result = bstring()
Encoding = encoding()
CharList = [ unicode_char() ]
N = non_negative_integer()

返回 BString 中第 N 个标记(从零开始)。 标记由 CharList 中的字符分隔。

返回的标记将具有与 BString 相同的编码。

例如

> bstring:nth_token(<<" Hello old boy !">>,latin1,3,[$o, $ ]).
<<"ld b">>

CharList 被视为字符的 *集合*,顺序无关紧要。 CharList 中给出的无法由 Encoding 表示的代码点不是错误。

N 的值 >= BString 中标记的数量将导致返回空二进制字符串 <<>>

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果 BString 不包含根据 Encoding 参数编码的字符,或者 Encoding 具有无效值,或者任何参数的类型错误,则会引发 badarg 异常。

num_tokens(BString, CharList) -> Count #

num_tokens(BString, Encoding, CharList) -> Count #

类型

BString = bstring()
Encoding = encoding()
CharList = [ unicode_char() ]
Count = non_negative_integer()

返回 String 中标记的数量,这些标记由 CharList 中的字符分隔。

结果与 length(bstring:tokens(BString,Encoding,CharList)) 的结果相同,但避免了构建结果。

例如

> num_tokens(<<" Hello old boy!">>, latin1, [$o, $ ]).
4

CharList 被视为字符的 *集合*,顺序无关紧要。 CharList 中给出的无法由 Encoding 表示的代码点不是错误。

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果 BString 不包含根据 Encoding 参数编码的字符,或者 Encoding 具有无效值,或者任何参数的类型错误,则会引发 badarg 异常。

span(BString, Chars) -> Length #

span(BString, Encoding, Chars) -> Length #

rspan(BString, Chars) -> Length #

rspan(BString, Encoding, Chars) -> Length #

cspan(BString, Chars) -> Length #

cspan(BString, Encoding, Chars) -> Length #

rcspan(BString, Chars) -> Length #

rcspan(BString, Encoding, Chars) -> Length #

类型

BString = bstring()
Encoding = encoding()
Chars = [ integer() ]
Length = non_negative_integer()

返回 BString 的最大初始(span 和 cspan)或尾随(rspan 和 rcspan)段的长度(以字符为单位),该段完全由来自(span 和 rspan)或不来自(cspan 和 rcspan)Chars 的字符组成。

Chars 被视为字符的 *集合*,顺序无关紧要。 Char 中给出的无法由 Encoding 表示的代码点不是错误。

例如

> bstring:span(<<"\t    abcdef">>,latin1," \t").
5
> bstring:cspan((<<"\t    abcdef">>,latin1, " \t").
0

Chars 中无法由 Encoding 表示的代码点不被视为错误。

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果 BString 的搜索部分不包含根据 Encoding 参数编码的字符,或者 Encoding 具有无效值,或者任何参数的类型错误,则会引发 badarg 异常。

spanb(BString, Chars) -> ByteLength #

spanb(BString, Encoding, Chars) -> ByteLength #

rspanb(BString, Chars) -> ByteLength #

rspanb(BString, Encoding, Chars) -> ByteLength #

cspanb(BString, Chars) -> ByteLength #

cspanb(BString, Encoding, Chars) -> ByteLength #

rcspanb(BString, Chars) -> ByteLength #

rcspanb(BString, Encoding, Chars) -> ByteLength #

类型

BString = bstring()
Encoding = encoding()
Chars = [ integer() ]
ByteLength = non_negative_integer()

其工作方式与函数 spanrspancspanrcspan 完全相同,但返回字节数而不是字符数。

split(BString, Separators, Where) -> Tokens #

split(BString, Encoding, Separators, SepEncoding, Where, ReturnEncoding) -> Tokens #

类型

String = bstring()
Encoding = SepEncoding = ReturnEncoding = encoding()
Separators = [ bstring() ]
Where = first | last | all
Tokens = [bstring()]

返回 BString 中由 Separators 中的二进制字符串分隔的标记列表。

返回的 Tokens 根据 ReturnEncoding 进行编码。

示例

> bstring:split(<<"abc defxxghix jkl">>, latin1, [<<"x">>,<<" ">>],all,latin1).
[<<"abc">>, <<"def">>, <<"ghi">>, <<"jkl">>]

Separators 被视为二进制字符串的 *集合*,顺序无关紧要。 Separators 中给出的无法由 Encoding 表示的代码点不是错误。

Where 参数指定要在哪个 Separators 出现时拆分二进制字符串,是在 first 次出现时,last 次出现时,还是在 all 次出现时拆分,在 all 情况下,Tokens 可能是任意长的列表。

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果 BStringSeparators 不包含分别根据 EncodingSepEncoding 参数编码的字符,或者生成的标记无法在 ReturnEncoding 中编码,或者 Encoding 具有无效值,或者任何参数的类型错误,则会引发 badarg 异常。

str(BString, SubBStrings) -> Position #

str(BString, Encoding, SubBStrings, SubEnc) -> Position #

rstr(BString, SubBStrings) -> Position #

rstr(BString, Encoding, SubBStrings, SubEnc) -> Position #

类型

BString = bstring()
SubBString = bstring() | [ bstring() ]
Encoding = SubEnc = encoding()
Position = integer()

返回 BString 中第一次/最后一次出现任何 SubBStrings 的字符位置(从零开始)。 如果 SubBString 不存在于 BString 中,则返回 -1

请注意,Character 位置与字节位置不同。 使用 strbrstrb 函数获取字节位置。

BStringSubBStrings 的编码不必相同,但是 SubBStrings 中的所有字符串都需要具有相同的编码。

如果 SubBString 中的代码点无法以 BString 的编码表示,则这不是错误,但始终会导致返回值 -1。

示例

> bstring:str(<<" Hello Hello World World ">>,latin1,<<"Hello World">>,latin1).
7

请注意,如果两个编码相同,并且要使用相同的 SubBStrings 执行重复搜索,则使用带有原始二进制数据上的预编译模式的 binary:match/{2,3} 函数效率更高。

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果 BStringSubBString 的搜索部分不包含根据 EncodingSubEnc 参数编码的字符,或者 Encoding 具有无效值,或者任何参数的类型错误,则会引发 badarg 异常。

strb(BString, SubBStrings) -> {BytePosition, ByteLength} #

strb(BString, Encoding, SubBStrings, SubEnc) -> {BytePosition, ByteLength} #

rstrb(BString, SubBStrings) -> {BytePosition, ByteLength} #

rstrb(BString, Encoding, SubBStrings, SubEnc) -> {BytePosition, ByteLength} #

类型

BString = bstring()
SubBString = bstring() | [ bstring() ]
Encoding = SubEnc = encoding()
BytePosition = integer()
ByteLength = non_negative_integer()

功能与 strrstr 分别类似,但返回找到的子字符串的字节位置和字节长度。

请注意,ByteLength 是找到的子字符串在 BString 中的长度,与 SubBStrings 中的编码无关。因此,根据二进制字符串的编码,ByteLength 可能大于或小于 byte_size(SubBString)

如果未找到子字符串,则返回 {-1,0}

strip(BString, Which, CharList) -> Result #

strip(BString, Encoding, Which, CharList) -> Result #

类型

BString = Result = bstring()
Encoding = encoding()
Which = leading | trailing | both
CharList = [ unicode_char() ]

从二进制字符串 BString 中删除属于 CharList 指示的集合的开头(Which = leading)、结尾(Which = trailing)或开头和结尾(Which = both)的字符。

这本质上与使用 spanb 和/或 rspanb 结合位语法来删除字符相同。

示例

> bstring:strip(<<"...He.llo.....">>, latin1, both, [$.]).
<<"He.llo">>

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果 BString 的扫描部分不包含根据 Encoding 参数编码的字符、EncodingWhich 具有无效值,或者任何参数类型错误,则会引发 badarg 异常。

replace(BString, Separators, Replacement, Where) -> Result #

replace(BString, Encoding, Separators, SeparatorsEncoding, Replacement, ReplacementEncoding, Where, ResultEncoding) -> Result #

类型

BString = bstring()
Encoding = SeparatorsEncoding = ReplacementEncoding, ResultEncoding = encoding()
Separators = [ bstring() ]
Replacement = bstring()
Where = first | last | all
Result = bstring()

产生与以下代码相同的结果:

bstring:join(bstring:split(BString,Encoding,Separators,SeparatorsEncoding,Where,
                           unicode),
             unicode,Replacement,ReplacementEncoding,ResultEncoding)

但开销更少。

substr(BString, Start, Length) -> SubBString #

substr(BString, Encoding, Start, Length) -> SubBString #

类型

BString = SubBString = bstring()
Encoding = bstring()
Start = integer()
Length = non_negative_integer() | infinity

返回 String 的子字符串,从基于零的字符位置 Start 开始,到二进制字符串的末尾(如果 Lengthinfinity)或直到(但不包括)字符位置 Start+Length(如果 Length 为非负整数)。

返回的 SubBString 将与 BString 具有相同的编码。

示例

> bstring:substr(<<"Hello World">>, latin1, 3, 5).
<<"lo Wo">>

Start 的负值表示从 BString末尾开始的 abs(Start) 个字符,因此 -1 是二进制字符串中的最后一个字符位置。

示例

> bstring:substr(<<"Hello World">>, latin1, -1, 3).
<<"rld">>

由于确定 UTF-8 编码的二进制字符串的真实长度非常耗时 (O(N),其中 N 是二进制字符串中的字节数),因此该函数对于字符串之外给定的位置(包括 StartLength)非常宽容。在任一方向上位于字符串之外的字符位置都会被折叠为空二进制字符串。

示例

> bstring:substr(<<"01234">>, latin1, 5, 5).
<<>>
> bstring:substr(<<"01234">>, latin1, 4, 5).
<<"4">>
> bstring:substr(<<"01234">>, latin1, -5, 100).
<<"01234">>
> bstring:substr(<<"01234">>, latin1, -6, 1).
<<>>
> bstring:substr(<<"01234">>, latin1, -6, 2).
<<"0">>

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果 BString 的搜索部分不包含根据 Encoding 参数编码的字符,或者 Encoding 具有无效值,或者任何参数的类型错误,则会引发 badarg 异常。

tokens(BString, SeparatorList) -> Tokens #

tokens(BString, Encoding, SeparatorList) -> Tokens #

类型

String = bstring()
Encoding = encoding
SeparatorList = [ non_negative_integer() ]
Tokens = [bstring()]

返回 BString 中由 SeparatorList 中的字符分隔的标记列表。

返回的 Tokens 以与 BString 相同的字符编码进行编码。

示例

> bstring:tokens(<<"abc defxxghix jkl">>, latin1, [$x,$ ]).
[<<"abc">>, <<"def">>, <<"ghi">>, <<"jkl">>]

SeparatorList 应被视为字符的集合,顺序并不重要。SeparatorList 中给出的无法由 Encoding 表示的代码点不是错误。

如果未给出编码,则假定为 latin1,这意味着不解释二进制字符串中的字节。

如果 BString 的搜索部分不包含根据 Encoding 参数编码的字符,或者 Encoding 具有无效值,或者任何参数的类型错误,则会引发 badarg 异常。

性能 #

此模块可以,并且可能应该完全在 Erlang 中实现,不需要 BIF 或 NIF。可以利用 binaryunicode 模块来加快转换和数据检查。Unicode 版本肯定比 ISO-Latin-1 版本慢,因为字符编码、解码和检查必然会产生开销。

建议的包装器 ubstring 与将所有编码参数设置为 unicodebstring 相比,不应产生任何显着的成本。

此想法旨在使使用二进制文件的字符串操作更加方便,因为它对系统内存方面具有很大的积极影响。与面向列表的字符串相比,提高速度不是目标,尽管它很可能是一种副作用。

参考实现 #

未进行特定的参考实现,但代码将在开发期间在 GitHub 上提供。

版权 #

本文档根据 知识共享署名 3.0 许可协议 获得许可。