module BytesLabels:sig
..end
字节序列操作。
字节序列是一种可变数据结构,包含固定长度的字节序列。每个字节都可以通过其索引以恒定时间进行读取或写入。
给定长度为 l
的字节序列 s
,我们可以通过其在序列中的索引访问 s
的每个 l
字节。索引从 0
开始,如果索引在 [0...l-1]
(含)范围内,则称其在 s
中有效。位置是两个字节之间或序列的开头或结尾的点。如果位置在 [0...l]
(含)范围内,则称其在 s
中有效。请注意,索引 n
处的字节位于位置 n
和 n+1
之间。
如果 len >= 0
且 start
和 start+len
是 s
中有效的位置,则称两个参数 start
和 len
指定了 s
的有效范围。
字节序列可以在原地修改,例如通过下面描述的 set
和 blit
函数。另请参见字符串(模块 String
),它们几乎是相同的数据结构,但不能在原地修改。
字节由 OCaml 类型 char
表示。
此模块的带标签版本可以按照 StdLabels
模块中所述的方式使用。
val length : bytes -> int
返回参数的长度(字节数)。
val get : bytes -> int -> char
get s n
返回参数 s
中索引 n
处的字节。
Invalid_argument
如果 n
不是 s
中的有效索引。val set : bytes -> int -> char -> unit
set s n c
在原地修改 s
,将索引 n
处的字节替换为 c
。
Invalid_argument
如果 n
不是 s
中的有效索引。val create : int -> bytes
create n
返回一个新的长度为 n
的字节序列。该序列未初始化,包含任意字节。
Invalid_argument
如果 n < 0
或 n >
Sys.max_string_length
。val make : int -> char -> bytes
make n c
返回一个新的长度为 n
的字节序列,用字节 c
填充。
Invalid_argument
如果 n < 0
或 n >
Sys.max_string_length
。val init : int -> f:(int -> char) -> bytes
init n f
返回一个新的长度为 n
的字节序列,其中字符 i
初始化为 f i
的结果(按索引递增顺序)。
Invalid_argument
如果 n < 0
或 n >
Sys.max_string_length
。val empty : bytes
大小为 0 的字节序列。
val copy : bytes -> bytes
返回一个包含与参数相同字节的新字节序列。
val of_string : string -> bytes
返回一个包含与给定字符串相同字节的新字节序列。
val to_string : bytes -> string
返回一个包含与给定字节序列相同字节的新字符串。
val sub : bytes -> pos:int -> len:int -> bytes
sub s ~pos ~len
返回一个新的长度为 len
的字节序列,包含从位置 pos
开始、长度为 len
的 s
的子序列。
Invalid_argument
如果 pos
和 len
没有指定 s
的有效范围。val sub_string : bytes -> pos:int -> len:int -> string
与 BytesLabels.sub
相同,但返回字符串而不是字节序列。
val extend : bytes -> left:int -> right:int -> bytes
extend s ~left ~right
返回一个新的字节序列,其中包含 s
的字节,并在其前面添加 left
个未初始化的字节,并在其后面添加 right
个未初始化的字节。如果 left
或 right
为负数,则从 s
的相应侧移除字节(而不是附加)。
Invalid_argument
如果结果长度为负或超过 Sys.max_string_length
个字节。val fill : bytes -> pos:int -> len:int -> char -> unit
fill s ~pos ~len c
在原地修改 s
,用 c
替换 len
个字符,从 pos
开始。
Invalid_argument
如果 pos
和 len
没有指定 s
的有效范围。val blit : src:bytes -> src_pos:int -> dst:bytes -> dst_pos:int -> len:int -> unit
blit ~src ~src_pos ~dst ~dst_pos ~len
将 len
个字节从字节序列 src
(从索引 src_pos
开始)复制到字节序列 dst
(从索引 dst_pos
开始)。即使 src
和 dst
是同一个字节序列,并且源区间和目标区间重叠,它也能正常工作。
Invalid_argument
如果 src_pos
和 len
没有指定 src
的有效范围,或者 dst_pos
和 len
没有指定 dst
的有效范围。val blit_string : src:string -> src_pos:int -> dst:bytes -> dst_pos:int -> len:int -> unit
blit_string ~src ~src_pos ~dst ~dst_pos ~len
将 len
个字节从字符串 src
(从索引 src_pos
开始)复制到字节序列 dst
(从索引 dst_pos
开始)。
Invalid_argument
如果 src_pos
和 len
没有指定 src
的有效范围,或者 dst_pos
和 len
没有指定 dst
的有效范围。val concat : sep:bytes -> bytes list -> bytes
concat ~sep sl
连接字节序列列表 sl
,在每个序列之间插入分隔符字节序列 sep
,并将结果作为新的字节序列返回。
Invalid_argument
如果结果长度超过 Sys.max_string_length
个字节。val cat : bytes -> bytes -> bytes
cat s1 s2
连接 s1
和 s2
,并将结果作为新的字节序列返回。
Invalid_argument
如果结果长度超过 Sys.max_string_length
个字节。val iter : f:(char -> unit) -> bytes -> unit
iter ~f s
依次将函数 f
应用于 s
的所有字节。它等价于 f (get s 0); f (get s 1); ...; f (get s
。
(length s - 1)); ()
val iteri : f:(int -> char -> unit) -> bytes -> unit
与 BytesLabels.iter
相同,但函数将字节的索引作为第一个参数,将字节本身作为第二个参数。
val map : f:(char -> char) -> bytes -> bytes
map ~f s
依次将函数 f
应用于 s
的所有字节(按索引递增顺序),并将结果字节存储在一个新的序列中,该序列作为结果返回。
val mapi : f:(int -> char -> char) -> bytes -> bytes
mapi ~f s
调用 f
处理 s
的每个字符及其索引(按索引递增顺序),并将结果字节存储在一个新的序列中,该序列作为结果返回。
val fold_left : f:('acc -> char -> 'acc) -> init:'acc -> bytes -> 'acc
fold_left f x s
计算 f (... (f (f x (get s 0)) (get s 1)) ...) (get s (n-1))
,其中 n
是 s
的长度。
val fold_right : f:(char -> 'acc -> 'acc) -> bytes -> init:'acc -> 'acc
fold_right f s x
计算 f (get s 0) (f (get s 1) ( ... (f (get s (n-1)) x) ...))
,其中 n
是 s
的长度。
val for_all : f:(char -> bool) -> bytes -> bool
for_all p s
检查 s
中的所有字符是否都满足谓词 p
。
val exists : f:(char -> bool) -> bytes -> bool
exists p s
检查 s
中至少一个字符是否满足谓词 p
。
val trim : bytes -> bytes
返回参数的副本,不包含前导和尾随空格。被视为空格的字节是 ASCII 字符 ' '
、'\012'
、'\n'
、'\r'
和 '\t'
。
val escaped : bytes -> bytes
返回参数的副本,其中特殊字符由转义序列表示,遵循 OCaml 的词法约定。所有 ASCII 可打印范围(32..126)之外的字符都将被转义,反斜杠和双引号也是如此。
Invalid_argument
如果结果长度超过 Sys.max_string_length
个字节。val index : bytes -> char -> int
index s c
返回 s
中字节 c
的第一次出现的索引。
Not_found
如果 c
没有出现在 s
中。val index_opt : bytes -> char -> int option
index_opt s c
返回 s
中字节 c
的第一次出现的索引,如果 c
没有出现在 s
中,则返回 None
。
val rindex : bytes -> char -> int
rindex s c
返回 s
中字节 c
的最后一次出现的索引。
Not_found
如果 c
没有出现在 s
中。val rindex_opt : bytes -> char -> int option
rindex_opt s c
返回 s
中字节 c
的最后一次出现的索引,如果 c
没有出现在 s
中,则返回 None
。
val index_from : bytes -> int -> char -> int
index_from s i c
返回 s
中位置 i
之后字节 c
的第一次出现的索引。index s c
等价于 index_from s 0 c
。
Invalid_argument
如果 i
不是 s
中的有效位置。Not_found
如果 c
没有出现在位置 i
之后 s
中。val index_from_opt : bytes -> int -> char -> int option
index_from_opt s i c
返回 s
中位置 i
之后字节 c
的第一次出现的索引,如果 c
没有出现在位置 i
之后 s
中,则返回 None
。index_opt s c
等价于 index_from_opt s 0 c
。
Invalid_argument
如果 i
不是 s
中的有效位置。val rindex_from : bytes -> int -> char -> int
rindex_from s i c
返回 s
中位置 i+1
之前字节 c
的最后一次出现的索引。rindex s c
等价于 rindex_from s (length s - 1) c
。
Invalid_argument
如果 i+1
不是 s
中的有效位置。Not_found
如果 c
没有出现在位置 i+1
之前 s
中。val rindex_from_opt : bytes -> int -> char -> int option
rindex_from_opt s i c
返回 s
中位置 i+1
之前字节 c
的最后一次出现的索引,如果 c
没有出现在位置 i+1
之前 s
中,则返回 None
。rindex_opt s c
等价于 rindex_from s (length s - 1) c
。
Invalid_argument
如果 i+1
不是 s
中的有效位置。val contains : bytes -> char -> bool
contains s c
测试字节 c
是否出现在 s
中。
val contains_from : bytes -> int -> char -> bool
contains_from s start c
测试字节 c
是否出现在位置 start
之后 s
中。contains s c
等价于 contains_from
。
s 0 c
Invalid_argument
如果 start
不是 s
中的有效位置。val rcontains_from : bytes -> int -> char -> bool
rcontains_from s stop c
测试字节 c
是否出现在位置 stop+1
之前 s
中。
stop < 0
或 stop+1
不是 s
中的有效位置,则引发 Invalid_argument
异常。val uppercase_ascii : bytes -> bytes
返回参数的副本,其中所有小写字母都转换为大写字母,使用 US-ASCII 字符集。
val lowercase_ascii : bytes -> bytes
返回参数的副本,其中所有大写字母都转换为小写字母,使用 US-ASCII 字符集。
val capitalize_ascii : bytes -> bytes
返回参数的副本,其中第一个字符设置为大写,使用 US-ASCII 字符集。
val uncapitalize_ascii : bytes -> bytes
返回参数的副本,其中第一个字符设置为小写,使用 US-ASCII 字符集。
typet =
bytes
字节序列类型的别名。
val compare : t -> t -> int
val equal : t -> t -> bool
字节序列的相等函数。
val starts_with : prefix:bytes -> bytes -> bool
starts_with
~prefix s
当且仅当 s
以 prefix
开头时为 true
。
val ends_with : suffix:bytes -> bytes -> bool
ends_with
~suffix s
当且仅当 s
以 suffix
结尾时为 true
。
本节描述了 bytes
和 string
之间不安全的低级转换函数。它们不会复制内部数据;如果使用不当,它们可能会破坏由 -safe-string
选项提供的字符串上的不变性。它们可供专家库作者使用,但在大多数情况下,您应该使用始终正确的 BytesLabels.to_string
和 BytesLabels.of_string
代替。
val unsafe_to_string : bytes -> string
不安全地将字节序列转换为字符串。
为了推断 unsafe_to_string
的使用,考虑“所有权”规则很方便。操作某些数据的代码段“拥有”它;有几种不相交的所有权模式,包括
唯一所有权是线性的:将数据传递给另一段代码意味着放弃所有权(我们无法再次写入数据)。唯一所有者可以决定使数据共享(放弃对其的修改权限),但共享数据不能再次成为唯一拥有的。
unsafe_to_string s
只能在调用者拥有字节序列 s
时使用——无论是唯一拥有还是作为共享的不可变数据。调用者放弃对 s
的所有权,并获得对返回字符串的所有权。
有两个有效的用例符合此所有权规则
1. 通过初始化和修改在初始化后不再更改的字节序列来创建字符串。
let string_init len f : string =
let s = Bytes.create len in
for i = 0 to len - 1 do Bytes.set s i (f i) done;
Bytes.unsafe_to_string s
此函数是安全的,因为在调用 unsafe_to_string
后,将不再访问或修改字节序列 s
。 string_init
代码放弃对 s
的所有权,并将结果字符串的所有权返回给其调用者。
请注意,如果将 s
作为附加参数传递给函数 f
,则它是不安全的,因为它可以通过这种方式逃逸并在将来被修改—— string_init
会放弃对 s
的所有权以将其传递给 f
,并且无法安全地调用 unsafe_to_string
。
我们提供了 String.init
、String.map
和 String.mapi
函数来涵盖构建新字符串的大多数情况。只要适用,您应该优先选择这些函数而不是 to_string
或 unsafe_to_string
。
2. 将字节序列的所有权临时授予期望唯一拥有字符串并返回所有权的函数,以便我们可以在调用结束后再次修改序列。
let bytes_length (s : bytes) =
String.length (Bytes.unsafe_to_string s)
在此用例中,我们不保证在调用 bytes_length s
后 s
将永远不会被修改。 String.length
函数临时借用字节序列的唯一所有权(并将其视为 string
),但将此所有权返回给调用者,调用者可以假设在调用后 s
仍然是有效的字节序列。请注意,这仅在知道 String.length
不会捕获其参数时才正确——它可能通过诸如记忆化组合器之类的侧信道逃逸。
在借用字符串期间,调用者不得修改 s
(它已暂时放弃所有权)。这会影响并发程序,还会影响高阶函数:如果 String.length
返回一个稍后要调用的闭包,则在完全应用此闭包并返回所有权之前,不应修改 s
。
val unsafe_of_string : string -> bytes
不安全地将共享字符串转换为不应修改的字节序列。
使 unsafe_to_string
正确的相同所有权规则也适用于 unsafe_of_string
:如果您是 string
值的所有者,并且您将以相同的模式拥有返回的 bytes
,则可以使用它。
在实践中,字符串值的唯一所有权非常难以正确推断。您应该始终假设字符串是共享的,而不是唯一拥有的。
例如,字符串文字由编译器隐式共享,因此您永远不会唯一拥有它们。
let incorrect = Bytes.unsafe_of_string "hello"
let s = Bytes.of_string "hello"
第一个声明是不正确的,因为字符串文字 "hello"
可能由编译器与程序的其他部分共享,并且修改 incorrect
是一个错误。您必须始终使用第二个版本,它执行复制,因此是正确的。
假设不是字符串文字,而是(部分)由字符串文字构建的字符串的唯一所有权也是不正确的。例如,修改 unsafe_of_string ("foo" ^ s)
可能会修改共享字符串 "foo"
——假设字符串的绳索状表示。更一般地说,操作字符串的函数将假设共享所有权,它们不会保留唯一所有权。因此,假设 unsafe_of_string
结果的唯一所有权是不正确的。
我们有理由相信唯一安全的情况是,如果生成的 bytes
是共享的——用作不可变的字节序列。这可能对增量迁移操作不可变字节序列(例如 Marshal.from_bytes
)的低级程序并且以前为此目的使用 string
类型很有用。
val split_on_char : sep:char -> bytes -> bytes list
split_on_char sep s
返回 s
的所有(可能为空)子序列的列表,这些子序列由 sep
字符分隔。如果 s
为空,则结果为单例列表 [empty]
。
函数的输出由以下不变式指定
sep
作为分隔符连接其元素将返回等于输入的字节序列 (Bytes.concat (Bytes.make 1 sep)
(Bytes.split_on_char sep s) = s
)。sep
字符。val to_seq : t -> char Seq.t
以递增的索引顺序迭代字符串。迭代期间对字符串的修改将反映在序列中。
val to_seqi : t -> (int * char) Seq.t
以递增顺序迭代字符串,沿字符产生索引
val of_seq : char Seq.t -> t
从生成器创建字符串
val get_utf_8_uchar : t -> int -> Uchar.utf_decode
get_utf_8_uchar b i
解码 b
中索引 i
处的 UTF-8 字符。
val set_utf_8_uchar : t -> int -> Uchar.t -> int
set_utf_8_uchar b i u
将 u
编码为 UTF-8 并将其写入 b
中索引 i
处,并返回从 i
开始写入的字节数 n
。如果 n
为 0
,则表示在 i
处没有足够的空间来编码 u
,并且 b
保持不变。否则,可以在 i + n
处编码新字符。
val is_valid_utf_8 : t -> bool
is_valid_utf_8 b
当且仅当 b
包含有效的 UTF-8 数据时为 true
。
val get_utf_16be_uchar : t -> int -> Uchar.utf_decode
get_utf_16be_uchar b i
解码 b
中索引 i
处的 UTF-16BE 字符。
val set_utf_16be_uchar : t -> int -> Uchar.t -> int
set_utf_16be_uchar b i u
将 u
编码为 UTF-16BE 并将其写入 b
中索引 i
处,并返回从 i
开始写入的字节数 n
。如果 n
为 0
,则表示在 i
处没有足够的空间来编码 u
,并且 b
保持不变。否则,可以在 i + n
处编码新字符。
val is_valid_utf_16be : t -> bool
is_valid_utf_16be b
当且仅当 b
包含有效的 UTF-16BE 数据时为 true
。
val get_utf_16le_uchar : t -> int -> Uchar.utf_decode
get_utf_16le_uchar b i
解码 b
中索引 i
处的 UTF-16LE 字符。
val set_utf_16le_uchar : t -> int -> Uchar.t -> int
set_utf_16le_uchar b i u
将 u
编码为 UTF-16LE 并将其写入 b
中索引 i
处,并返回从 i
开始写入的字节数 n
。如果 n
为 0
,则表示在 i
处没有足够的空间来编码 u
,并且 b
保持不变。否则,可以在 i + n
处编码新字符。
val is_valid_utf_16le : t -> bool
is_valid_utf_16le b
当且仅当 b
包含有效的 UTF-16LE 数据时为 true
。
本节中的函数将整数二进制编码和解码到字节序列中。
以下所有函数在索引 i
处解码或编码整数所需的空格不可用时引发 Invalid_argument
。
小端 (resp. 大端) 编码意味着最低 (resp. 最高) 有效字节首先存储。大端也称为网络字节顺序。本机端编码根据 Sys.big_endian
既可以是小端也可以是大端。
32 位和 64 位整数分别由 int32
和 int64
类型表示,可以将其解释为有符号或无符号数字。
8 位和 16 位整数由 int
类型表示,该类型比二进制编码具有更多位。这些额外的位处理如下
int
值表示的有符号 (resp. 无符号) 8 位或 16 位整数的函数对其结果进行符号扩展 (resp. 零扩展)。int
值表示的 8 位或 16 位整数的函数将其输入截断为其最低有效字节。val get_uint8 : bytes -> int -> int
get_uint8 b i
是从字节索引 i
开始的 b
的无符号 8 位整数。
val get_int8 : bytes -> int -> int
get_int8 b i
表示从字节索引 i
开始的 b
的带符号 8 位整数。
val get_uint16_ne : bytes -> int -> int
get_uint16_ne b i
表示从字节索引 i
开始的 b
的本机字节序无符号 16 位整数。
val get_uint16_be : bytes -> int -> int
get_uint16_be b i
表示从字节索引 i
开始的 b
的大端字节序无符号 16 位整数。
val get_uint16_le : bytes -> int -> int
get_uint16_le b i
表示从字节索引 i
开始的 b
的小端字节序无符号 16 位整数。
val get_int16_ne : bytes -> int -> int
get_int16_ne b i
表示从字节索引 i
开始的 b
的本机字节序带符号 16 位整数。
val get_int16_be : bytes -> int -> int
get_int16_be b i
表示从字节索引 i
开始的 b
的大端字节序带符号 16 位整数。
val get_int16_le : bytes -> int -> int
get_int16_le b i
表示从字节索引 i
开始的 b
的小端字节序带符号 16 位整数。
val get_int32_ne : bytes -> int -> int32
get_int32_ne b i
表示从字节索引 i
开始的 b
的本机字节序 32 位整数。
val get_int32_be : bytes -> int -> int32
get_int32_be b i
表示从字节索引 i
开始的 b
的大端字节序 32 位整数。
val get_int32_le : bytes -> int -> int32
get_int32_le b i
表示从字节索引 i
开始的 b
的小端字节序 32 位整数。
val get_int64_ne : bytes -> int -> int64
get_int64_ne b i
表示从字节索引 i
开始的 b
的本机字节序 64 位整数。
val get_int64_be : bytes -> int -> int64
get_int64_be b i
表示从字节索引 i
开始的 b
的大端字节序 64 位整数。
val get_int64_le : bytes -> int -> int64
get_int64_le b i
表示从字节索引 i
开始的 b
的小端字节序 64 位整数。
val set_uint8 : bytes -> int -> int -> unit
set_uint8 b i v
将从字节索引 i
开始的 b
的无符号 8 位整数设置为 v
。
val set_int8 : bytes -> int -> int -> unit
set_int8 b i v
将从字节索引 i
开始的 b
的带符号 8 位整数设置为 v
。
val set_uint16_ne : bytes -> int -> int -> unit
set_uint16_ne b i v
将从字节索引 i
开始的 b
的本机字节序无符号 16 位整数设置为 v
。
val set_uint16_be : bytes -> int -> int -> unit
set_uint16_be b i v
将从字节索引 i
开始的 b
的大端字节序无符号 16 位整数设置为 v
。
val set_uint16_le : bytes -> int -> int -> unit
set_uint16_le b i v
将从字节索引 i
开始的 b
的小端字节序无符号 16 位整数设置为 v
。
val set_int16_ne : bytes -> int -> int -> unit
set_int16_ne b i v
将从字节索引 i
开始的 b
的本机字节序带符号 16 位整数设置为 v
。
val set_int16_be : bytes -> int -> int -> unit
set_int16_be b i v
将从字节索引 i
开始的 b
的大端字节序带符号 16 位整数设置为 v
。
val set_int16_le : bytes -> int -> int -> unit
set_int16_le b i v
将从字节索引 i
开始的 b
的小端字节序带符号 16 位整数设置为 v
。
val set_int32_ne : bytes -> int -> int32 -> unit
set_int32_ne b i v
将从字节索引 i
开始的 b
的本机字节序 32 位整数设置为 v
。
val set_int32_be : bytes -> int -> int32 -> unit
set_int32_be b i v
将从字节索引 i
开始的 b
的大端字节序 32 位整数设置为 v
。
val set_int32_le : bytes -> int -> int32 -> unit
set_int32_le b i v
将从字节索引 i
开始的 b
的小端字节序 32 位整数设置为 v
。
val set_int64_ne : bytes -> int -> int64 -> unit
set_int64_ne b i v
将从字节索引 i
开始的 b
的本机字节序 64 位整数设置为 v
。
val set_int64_be : bytes -> int -> int64 -> unit
set_int64_be b i v
将从字节索引 i
开始的 b
的大端字节序 64 位整数设置为 v
。
val set_int64_le : bytes -> int -> int64 -> unit
set_int64_le b i v
将从字节索引 i
开始的 b
的小端字节序 64 位整数设置为 v
。
当从多个域并发访问字节序列时,必须小心:访问字节序列永远不会导致程序崩溃,但未同步的访问可能会产生令人意外的(非顺序一致的)结果。
每个访问多个字节的字节序列操作都不是原子的。这包括迭代和扫描。
例如,考虑以下程序
let size = 100_000_000
let b = Bytes.make size ' '
let update b f () =
Bytes.iteri (fun i x -> Bytes.set b i (Char.chr (f (Char.code x)))) b
let d1 = Domain.spawn (update b (fun x -> x + 1))
let d2 = Domain.spawn (update b (fun x -> 2 * x + 1))
let () = Domain.join d1; Domain.join d2
字节序列 b
可能包含 '!'
、'A'
、'B'
和 'C'
值的非确定性混合。
执行此代码后,序列 b
的每个字节要么是 '!'
、'A'
、'B'
,要么是 'C'
。如果需要原子性,则用户必须实现自己的同步(例如,使用 Mutex.t
)。
如果两个域仅访问字节序列的不同部分,则观察到的行为等效于来自这两个域的操作的某些顺序交错。
当两个域访问同一个字节且未进行同步,并且至少一次访问是写操作时,就会发生数据竞争。在没有数据竞争的情况下,观察到的行为等效于来自不同域的操作的某些顺序交错。
只要有可能,应通过使用同步来协调对序列元素的访问,从而避免数据竞争。
实际上,在存在数据竞争的情况下,程序不会崩溃,但观察到的行为可能不等效于来自不同域的操作的任何顺序交错。然而,即使在存在数据竞争的情况下,读取操作也会返回对该位置的某个先前写入的值。
另一个微妙之处是,如果数据竞争涉及对同一位置的混合大小的写入和读取,则域观察这些写入和读取的顺序未指定。例如,以下代码按顺序将一个 32 位整数和一个 char
写入到相同的索引
let b = Bytes.make 10 '\000'
let d1 = Domain.spawn (fun () -> Bytes.set_int32_ne b 0 100; b.[0] <- 'd' )
在这种情况下,观察到将 'd' 写入 b.0
的域并不保证也会观察到对索引 1
、2
或 3
的写入。