此 EEP 建议添加两个二进制辅助库,其中包含用于时间关键活动(例如搜索和拆分 Erlang 二进制数据)的内置函数,以及用于二进制数据常见操作的库函数。此 EEP 还建议添加一个使用内置函数的正则表达式库。
对于列表数据类型,有一个辅助库提供用于常见操作(例如搜索和拆分列表)的函数。此 EEP 建议为二进制数据创建类似的一组库函数。许多建议的函数都基于 erlang-questions 邮件列表中有关二进制数据的问题的答案,例如“如何将数字转换为二进制数据?”。
由于二进制数据通常用于处理大量数据的对时间要求很高的活动,因此建议将一些二进制数据操作实现为内置函数(BIF)。
特别是,社区似乎对用于搜索二进制数据的高效正则表达式实现非常感兴趣。此外,为了在搜索和拆分二进制数据时获得最佳性能,建议正则表达式搜索函数补充一个用于简单搜索的高性能函数,例如定位和拆分换行符上的二进制数据。测试表明,对于此类目的,例如 Boyer-Moore 算法可能比正则表达式算法快得多。
在审查 EEP 时,很明显也强烈需要对二进制数据进行字符串操作,以获得更好的性能。
单独发送给 OTP 团队的参考实现指示了与例如当前用于搜索列表的正则表达式模块相比,预期的性能改进。一些结果在此 EEP 的末尾提供。
此 EEP 建议添加两个新模块:一个名为 binary 的模块和一个名为 binary_string 的模块。
此 EEP 还建议一个新的正则表达式库,该库基于 Perl 兼容正则表达式 (PCRE)。该库应能够对 binary_string 和 string 进行操作。
最后,应将以下函数添加到 erlang 模块
binary_to_atom(Binary) -> Atom
atom_to_binary(Atom) -> Binary
binary_to_existing_atom(Binary) -> Atom
目前,以下内容不包含在 EEP 中
binary_string 模块应基于当前的 string 模块,但应在二进制数据表示的字符串上操作,而不是当前在列表表示的字符串上操作的 strings 模块。
除了在二进制数据上操作外,binary_string 的接口应与 string 的接口相同,但以下例外情况除外
应修改 str/2 和 rstr/2 以可选地接受二进制列表或 MatchSpec(例如 binary:match_compile/2 返回的 MatchSpec)作为第二个参数。如果 Keys 参数对应于多个键,则该函数应返回一个元组,指示匹配的键和匹配的索引,即
str(Binary, Keys) -> Return
rstr(Binary, Keys) -> Return
Binary = binary()
Keys = Key | [ Key ] | MatchSpec
Key = string() | binary()
MatchSpec = tuple() as returned by binary:match_compile/1
Return = Index | {NeedleNumber, Index}
Index = integer()
str/rstr 应使用高效的算法(例如 Boyer-Moore、Aho-Corasick 或类似算法)实现为内置函数。通常,该函数可以基于 binary:match/2 构建。
应添加一个新函数 split。它应与 tokens/2 的行为方式相同,但采用分隔符二进制数据/字符串列表,而不是分隔符字符列表。
split(Binary, SplitKeys) -> List
Binary = binary()
SplitKeys = Key | [ Key ] | MatchSpec
Key = string() | binary()
MatchSpec = tuple() as returned by binary:match_compile/1
List = [ binary() ]
根据 SplitKeys 二进制数据中指定的模式,将二进制数据拆分为二进制数据列表。
示例
> binary_string:split(<<"cat and dog">>, <<"and">>).
[<<"cat ">>, <<" dog">>]
> binary_string:split(<<"cat and dog">>, "and").
[<<"cat ">>, <<" dog">>]
> binary_string:split(<<"cat and dog">>,["a","n",<<"d">>]).
[<<"c">>,<<"t ">>,<<" ">>,<<"og">>]
结果列表应与 regexp:split/2 的结果列表相同(特殊字符如 “*”、“.”、“^” 等的明显例外)。
请注意,第三个示例应与 binary_string:tokens(«“cat and dog”», “and”) 的结果相同。
应添加新函数 substitute 和 globally_substitute。
3.1. substitute/3
substitute(OldBinary, Key, Replacement)-> NewBinary
OldBinary, NewBinary, Replacement = binary()
Keys = binary() | [ binary() ] | MatchSpec
MatchSpec = tuple() as returned by binary:match_compile/1
通过将 OldBinary 中 Keys 中的任何二进制数据的首次出现替换为 Replacement 二进制数据,从 OldBinary 创建二进制数据 NewBinary。
Replacement 二进制数据的大小不必与匹配的 Key 的大小相同。
示例
> binary_string:substitute(<<"cat anf dog">>,<<"anf">>,<<"and">>).
[<<"cat and dog">>]
3.2. globally_substitute/3
globally_substitute(OldBinary, Key, Replacement)-> NewBinary
OldBinary, NewBinary, Replacement = binary()
Keys = binary() | [ binary() ] | MatchSpec
MatchSpec = tuple() as returned by binary:match_compile/1
与 substitute 相同,只不过 OldBinary 中子二进制数据的所有非重叠出现都被 Replacement 二进制数据替换。
建议将相同的函数也添加到 string 模块,但这超出了此 EEP 的范围。
binary 模块的接口应具有以下导出的函数(请注意,某些函数与 binary_string 中的函数故意相同,因为人们认为它们对于字符串和二进制数据操作都很有用)
match(Binary, Keys) -> Return
match(Binary, Keys, {StartIndex, EndIndex}) -> Return
Binary = binary()
Keys = binary() | [ binary() ] | MatchSpec
MatchSpec = tuple() as returned by binary:match_compile/1
StartIndex = EndIndex = integer()
Return = Index | {KeyNumber, Index}
Index = KeyNumber = integer()
返回 Keys 中第一个匹配的二进制数据在 Binary 中的首次出现位置,如果未匹配则返回 0。如果给出键列表,则该函数将返回一个元组,其中包含匹配的 Key 的 KeyNumber 以及在 Binary 中找到它的位置。
已经讨论过该函数是应该返回匹配的 Key 还是返回 KeyNumber。返回 KeyNumber 应该稍微高效一些,并且由于如果需要,可以通过 lists:nth(KeyNumber, Keys) 轻松检索匹配的键,因此建议该函数返回 KeyNumber。
Binary 从 StartIndex 搜索到 EndIndex。如果未指定 StartIndex 和 EndIndex,则默认值是从头到尾搜索 Binary。
示例
> binary:match(<<1,2,3,0,0,0,4>>, <<0,0,0>>).
4
> binary:match(<<1,2,255,0,0,0,4,255>>, [<<0,0,0>>, <<255>>]).
{2, 3}
实现建议:应使用例如 Boyer-Moore、Aho-Corasick 或类似的高效算法实现为一个或多个 BIF。
matches(Binary, Keys) -> Return
matches(Binary, Keys, {StartIndex, EndIndex}) -> Return
Binary = binary()
Keys = binary() | [ binary() ] | MatchSpec
MatchSpec = tuple() as returned by binary:match_compile/1
StartIndex = EndIndex = integer()
Return = [ Index ] | [ {KeyNumber, Index} ]
Index = KeyNumber = integer()
查找 Haystack 中 Keys 的所有匹配项。返回键或多个键的所有非重叠出现的索引列表。
split(Binary, SplitKeys) -> List
split(Binary, SplitKeys, {StartIndex, EndIndex}) -> List
Binary = binary()
SplitKeys = binary() | [ binary() ] | MatchSpec
MatchSpec = tuple() as returned by binary:match_compile/1
StartIndex = EndIndex = integer()
List = [ binary() ]
根据 SplitKeys 中指定的模式,将 Binary 拆分为二进制数据列表。
示例
> binary:split(<<1,255,4,0,0,0,2,3>>, <<0,0,0>>).
[<<1,255,4>>, <<2,3>>]
> binary:split(<<0,1,0,0,4,255,255,9>>, [<<0,0>>, <<255,255>>]).
[<<0,1>>,<<4>>,<<9>>]
结果列表基本上应与 regexp:split/2 的结果列表相同(特殊字符如 “*”、“.”、“^” 等的明显例外)。
List 中的二进制数据都是 Binary 的子二进制数据,这意味着 Binary 中的数据实际上没有复制到新的二进制数据。
substitute(OldBinary, Key, Replacement)-> NewBinary
OldBinary, NewBinary, Replacement = binary()
Keys = binary() | [ binary() ] | MatchSpec
MatchSpec = tuple() as returned by binary:match_compile/1
通过将 OldBinary 中 Keys 中的任何二进制数据的首次出现替换为 Replacement 二进制数据,从 OldBinary 创建二进制数据 NewBinary。
Replacement 二进制数据的大小不必与匹配的 Key 的大小相同。
globally_substitute(OldBinary, Key, Replacement)-> NewBinary
OldBinary, NewBinary, Replacement = binary()
Keys = binary() | [ binary() ] | MatchSpec
MatchSpec = tuple() as returned by binary:match_compile/1
与 substitute 相同,只不过 OldBinary 中子二进制数据的所有非重叠出现都被 Replacement 二进制数据替换。
match_compile(Keys) -> MatchSpec
Keys = binary() | [ binary() ]
MatchSpec = tuple()
构建一个内部结构,表示一个或多个搜索键。如果使用相同的搜索关键字执行多次搜索,则 MatchSpec 结构可用于加速使用 binary:match/2 或 binary_string:str/2 的搜索。
binary:from_unsigned(Integer)-> Binary
binary:to_unsigned(Binary)-> Integer
将正整数转换为二进制数据类型格式的最小可能表示,反之亦然。
示例
> binary:from_unsigned(11111111).
<<169,138,199>>
> binary:to_unsigned(<<169,138,199>>).
11111111
first(Binary1)-> Binary2
first(SizeBytes, Binary1)-> Binary2
返回 Binary1 中第一个字节或 SizeBytes 个字节的子二进制数据。
示例
> binary:first(2, <<"abc">>).
<<"ab">>
last(Binary1)-> Binary2.
last(SizeBytes, Binary1)-> Binary2
返回 Binary1 中最后一个字节或 SizeBytes 个字节的子二进制数据。
示例
> binary:last(2, <<"abc">>).
<<"bc">>
nth(N, Binary) -> Value
N = integer(), 1 =< N =< size(Binary)
Value = integer()
从 Binary 中提取位置 N 处的字节。与以下内容相同:
T = N-1,
<<_:T/binary, Value:Size/binary, _/binary>> = Binary,
Value.
尽管此函数编写起来稍微简短且容易。
extract(N, Size, Binary) -> SubBinary
N = integer(), 1 =< N =< size(Binary)
Size = integer()
SubBinary = subbinary()
从 Binary 中返回大小为 Size 并从位置 N 开始的子二进制数据。在此操作中不复制数据。
已经讨论过是否应该有一个函数用于复制二进制数据的一部分,而不是获取子二进制数据。这将使得可以获得二进制数据的一小部分,并让其余部分被垃圾回收。由于可以通过将提取的部分转换为列表,然后再次转换回二进制数据来实现相同的结果,并且它是一个非常特殊的操作,可能会使新用户感到困惑,因此在此阶段将其排除在外。
在与设计人员交谈时,许多人似乎更喜欢为此函数使用名称 extract 而不是 subbinary。
duplicate(N, Byte)-> Binary
类似于 lists:duplicate/2
。创建一个新的二进制数据,由重复 N 次的字节组成。
示例
> binary:duplicate(5, $a).
<<"aaaaa">>
建议添加一个新的基于内置函数的正则表达式库。它应具有以下接口函数(模块的名称待定,出于向后兼容的原因,它可能应该与旧的 regexp 模块并行存在)
在第一轮反馈期间,有人建议最终实现应该是基于 Perl 兼容正则表达式 (PCRE) 库的内置函数。它经过优化、支持良好,并且或多或少被认为是今天的标准。它用于许多著名的产品和项目中,例如 Apple 的 Safari、Apache、KDE、PHP、Postfix 和 Nmap。
建议该模块具有以下导出的函数
compile(Regex) -> MatchSpec
Regex = string()
MatchSpec = tuple()
构建一个内部结构,表示一个或多个搜索键。如果使用相同的搜索关键字执行多次搜索,则 MatchSpec 结构可用于加速搜索。
match(BinOrString, RegExp)-> Return
match(BinOrString, RegExp, {StartIndex, EndIndex})-> Return
BinOrString = binary() | string()
RegExp = string() | MatchSpec
MatchSpec = tuple() as returned by match_compile/1
StartIndex = EndIndex = integer()
Return = 0 | {Start, Length, [CapturedPatterns]}
在 BinOrString 中找到正则表达式 RegExp 的第一个最长匹配项。此函数搜索最长的可能匹配项,如果存在多个长度相同的表达式,则返回找到的第一个匹配项。
该函数支持模式捕获。捕获的模式(如果有)在 Return 元组的列表中返回。
示例
> binary:regex_match(<<"abcde">>, "b?cd").
{2,3,[]}
> binary:regex_match(<<"127.0.0.1">>, "(\d*)\.(\d*)\.").
{1,6,[<<"127">>, <<"0">>]}
未决问题
处理编码。
matches(BinOrString, RegExp)-> Replacementeturn matches(BinOrString, RegExp, {StartIndex, EndIndex})-> Return
BinOrString = binary() | string()
RegExp = string() | MatchSpec
MatchSpec = tuple() as returned by match_compile/1
StartIndex = EndIndex = integer()
Return = 0 | [ {Start, Length, [CapturedPatterns]} ]
在 BinOrString 中查找正则表达式 RegExp 的所有匹配项。
示例
> binary:regex_matches(<<"aaa">>, "a").
[{1,1,[]},{2,1,[]},{3,1,[]}]
sub(BinOrString, RegExp, Replacement)-> NewStringOrBinary
BinOrString = NewStringOrBinary = binary() | string()
RegExp = string() | MatchSpec
MatchSpec = tuple() as returned by match_compile/1
Replacement = string()
将 BinOrString 中与 RegExp 匹配的子字符串或子二进制数据的首次出现替换为 Replacement。Replacement 字符串中的 & 被 BinOrString 的匹配子字符串或子二进制数据替换。\& 将文字 & 放入替换字符串或二进制数据中。NewStringOrBinary 的类型将与 BinOrString 的类型相同。
gsub(BinOrString, RegExp, Replacement)-> Binary2
与 sub 相同,只不过 BinOrString 中与 RegExp 匹配的子字符串或子二进制数据的所有非重叠出现都被字符串 Replacement 替换。
split(BinOrString, RegExp) -> List
split(BinOrString, RegExp, {StartIndex, EndIndex}) -> List
BinOrString = binary() | string()
RegExp = string() | MatchSpec
MatchSpec = tuple() as returned by match_compile/1
StartIndex = EndIndex = integer()
List = [ binary() ]
根据 RegExp 中指定的模式,将 Binary 拆分为二进制数据列表。
结果列表基本上应与 regexp:split/2 的结果相同。
使用参考实现对认为最重要的函数进行了性能测量。以下是一些示例:
在一个约1 Mb的二进制数据中搜索不存在的1字节和3字节二进制数据。请注意,由于O(n/m)的算法,`binary:match/2`函数随着“针”(needle)的长度增加而变得更快。所有时间单位均为微秒。
Search for: 1 byte 3 bytes
---------------------------------------
binary:match/2: 17598 6045
binary:regex_first/2: 47299 46701
string:str/2: 68969 69637
regexp:first_match/2: 460858 887485
在一个约1 Mb的二进制数据中以换行符分割。这个特殊的二进制数据平均每60个字符包含一个换行符。
binary:split/2: 89142 microseconds
regexp:split/2: 564911 microseconds
来自计算机语言基准测试的Regex-DNA基准测试。
prototype regexp bif: 1.9 seconds
regexp module in R12B: 99.1 seconds
在计算机语言基准测试的示例中,与其他算法(如参考实现中的算法或特别是TCL中的算法)相比,PCRE的性能略低。但这并不一定意味着对于所有类型的模式都是如此。
已经向OTP团队提供了参考实现。
本文档根据知识共享许可协议授权。