模組 BytesLabels

module BytesLabels: sig .. end

位元組序列操作。

位元組序列是一種可變的資料結構,包含固定長度的位元組序列。每個位元組都可以透過索引在常數時間內進行讀取或寫入。

給定一個長度為 l 的位元組序列 s,我們可以透過其在序列中的索引來存取 s 的每個 l 位元組。索引從 0 開始,如果索引落在 [0...l-1](包含)的範圍內,我們就稱該索引在 s 中有效。位置是指兩個位元組之間或序列開始或結束的點。如果位置落在 [0...l](包含)的範圍內,我們稱該位置在 s 中有效。請注意,索引 n 處的位元組位於位置 nn+1 之間。

如果 len >= 0startstart+lens 中是有效的位置,則稱兩個參數 startlen 指定 s 的有效範圍。

位元組序列可以就地修改,例如透過下面描述的 setblit 函式。另請參閱字串(模組 String),它們幾乎是相同的資料結構,但無法就地修改。

位元組由 OCaml 類型 char 表示。

此模組的標記版本可以使用 StdLabels 模組中描述的方式使用。


val length : bytes -> int

傳回參數的長度(位元組數)。

val get : bytes -> int -> char

get s n 傳回參數 s 中索引 n 處的位元組。

val set : bytes -> int -> char -> unit

set s n c 就地修改 s,將索引 n 處的位元組替換為 c

val create : int -> bytes

create n 傳回一個長度為 n 的新位元組序列。該序列未初始化,且包含任意位元組。

val make : int -> char -> bytes

make n c 傳回一個長度為 n 的新位元組序列,並用位元組 c 填滿。

val init : int -> f:(int -> char) -> bytes

init n f 傳回一個長度為 n 的新位元組序列,其中字元 i 初始化為 f i 的結果(按遞增索引順序)。

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 的新位元組序列,其中包含 s 的子序列,該子序列從位置 pos 開始,長度為 len

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 個未初始化的位元組。如果 leftright 為負數,則會從 s 的對應側移除(而非附加)位元組。

val fill : bytes -> pos:int -> len:int -> char -> unit

fill s ~pos ~len c 就地修改 s,從 pos 開始,將 len 個字元替換為 c

val blit : src:bytes -> src_pos:int -> dst:bytes -> dst_pos:int -> len:int -> unit

blit ~src ~src_pos ~dst ~dst_pos ~len 從位元組序列 src 的索引 src_pos 開始複製 len 個位元組到位元組序列 dst 的索引 dst_pos 開始的位置。即使 srcdst 是相同的位元組序列,且來源和目標間隔重疊,它也能正常運作。

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 從字串 src 的索引 src_pos 開始複製 len 個位元組到位元組序列 dst 的索引 dst_pos 開始的位置。

val concat : sep:bytes -> bytes list -> bytes

concat ~sep sl 連接位元組序列列表 sl,在每個之間插入分隔符位元組序列 sep,並以新的位元組序列傳回結果。

val cat : bytes -> bytes -> bytes

cat s1 s2 連接 s1s2,並以新的位元組序列傳回結果。

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 ss 的每個字元及其索引(按遞增索引順序)呼叫 f,並將結果位元組儲存在新的序列中,該序列會以結果傳回。

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)),其中 ns 的長度。

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) ...)),其中 ns 的長度。

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) 之外的字元以及反斜線和雙引號都會被跳脫。

val index : bytes -> char -> int

index s c 傳回位元組 cs 中第一次出現的索引。

val index_opt : bytes -> char -> int option

index_opt s c 傳回位元組 cs 中第一次出現的索引,如果 c 未在 s 中出現,則傳回 None

val rindex : bytes -> char -> int

rindex s c 傳回位元組 cs 中最後一次出現的索引。

val rindex_opt : bytes -> char -> int option

rindex_opt s c 傳回位元組 cs 中最後一次出現的索引,如果 c 未在 s 中出現,則傳回 None

val index_from : bytes -> int -> char -> int

index_from s i c 傳回位置 i 之後位元組 cs 中第一次出現的索引。index s c 等效於 index_from s 0 c

val index_from_opt : bytes -> int -> char -> int option

index_from_opt s i c 傳回位置 i 之後位元組 cs 中第一次出現的索引,如果 c 未在 s 中位置 i 之後出現,則傳回 Noneindex_opt s c 等效於 index_from_opt s 0 c

val rindex_from : bytes -> int -> char -> int

rindex_from s i c 傳回位元組 cs 中,位置 i+1 之前最後一次出現的索引。rindex s c 等同於 rindex_from s (length s - 1) c

val rindex_from_opt : bytes -> int -> char -> int option

rindex_from_opt s i c 傳回位元組 cs 中,位置 i+1 之前最後一次出現的索引,如果 c 在位置 i+1 之前未在 s 中出現,則傳回 Nonerindex_opt s c 等同於 rindex_from s (length s - 1) c

val contains : bytes -> char -> bool

contains s c 測試位元組 c 是否在 s 中出現。

val contains_from : bytes -> int -> char -> bool

contains_from s start c 測試位元組 c 是否在 s 中,位置 start 之後出現。contains s c 等同於 contains_from
    s 0 c

val rcontains_from : bytes -> int -> char -> bool

rcontains_from s stop c 測試位元組 c 是否在 s 中,位置 stop+1 之前出現。

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 字元集設定為小寫。

type t = bytes 

位元組序列類型的別名。

val compare : t -> t -> int

位元組序列的比較函式,規格與 compare 相同。連同類型 t,此函式 compare 允許將模組 Bytes 作為函子 Set.MakeMap.Make 的引數傳遞。

val equal : t -> t -> bool

位元組序列的相等函式。

val starts_with : prefix:bytes -> bytes -> bool

starts_with ~prefix s 只有在 sprefix 開頭時才為 true

val ends_with : suffix:bytes -> bytes -> bool

ends_with ~suffix s 只有在 ssuffix 結尾時才為 true

不安全的轉換(供進階使用者使用)

本節描述 bytesstring 之間的不安全、低階轉換函式。它們不會複製內部資料;如果使用不當,它們可能會破壞 -safe-string 選項提供的字串上的不可變性不變量。它們適用於專家程式庫作者,但對於大多數用途,您應該使用始終正確的 BytesLabels.to_stringBytesLabels.of_string 來代替。

val unsafe_to_string : bytes -> string

將位元組序列不安全地轉換為字串。

為了推理 unsafe_to_string 的使用,可以方便地考慮「所有權」原則。一段操作某些資料的程式碼「擁有」它;存在幾種不相交的所有權模式,包括

  • 獨有所有權:可以存取和變更資料
  • 共享所有權:資料有多個擁有者,他們只能存取資料,不能變更它。

獨有所有權是線性的:將資料傳遞給另一段程式碼表示放棄所有權(我們無法再次寫入資料)。獨有擁有者可以決定讓資料變成共享(放棄對其的變更權),但是共享資料可能無法再次變成獨有所有。

只有在呼叫者擁有位元組序列 s 時,才能使用 unsafe_to_string 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 後,永遠不會存取或變更位元組序列 sstring_init 程式碼放棄對 s 的所有權,並將結果字串的所有權傳回給其呼叫者。

請注意,如果將 s 作為附加參數傳遞給函式 f,則是不安全的,因為它可能會以此方式逸出,並在未來被變更 -- string_init 會放棄對 s 的所有權以將其傳遞給 f,並且無法安全地呼叫 unsafe_to_string

我們提供了 String.initString.mapString.mapi 函式,以涵蓋大多數建立新字串的情況。在適用時,您應該優先使用這些函式,而不是 to_stringunsafe_to_string

2. 暫時將位元組序列的所有權提供給需要獨有擁有的字串並將所有權傳回的函式,以便我們可以在呼叫結束後再次變更序列。

let bytes_length (s : bytes) =
  String.length (Bytes.unsafe_to_string s)
   

在此用例中,我們不保證在呼叫 bytes_length s 後永遠不會變更 sString.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

從產生器建立字串

UTF 編碼器與驗證器

UTF-8

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 uu 以 UTF-8 編碼寫入 b 中索引為 i 的位置,並回傳從 i 開始寫入的位元組數量 n。如果 n0,則表示沒有足夠的空間在 i 編碼 u,且 b 將保持不變。否則,可以在 i + n 的位置編碼新的字元。

val is_valid_utf_8 : t -> bool

is_valid_utf_8 b 只有在 b 包含有效的 UTF-8 資料時才會回傳 true

UTF-16BE

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 uu 以 UTF-16BE 編碼寫入 b 中索引為 i 的位置,並回傳從 i 開始寫入的位元組數量 n。如果 n0,則表示沒有足夠的空間在 i 編碼 u,且 b 將保持不變。否則,可以在 i + n 的位置編碼新的字元。

val is_valid_utf_16be : t -> bool

is_valid_utf_16be b 只有在 b 包含有效的 UTF-16BE 資料時才會回傳 true

UTF-16LE

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 uu 以 UTF-16LE 編碼寫入 b 中索引為 i 的位置,並回傳從 i 開始寫入的位元組數量 n。如果 n0,則表示沒有足夠的空間在 i 編碼 u,且 b 將保持不變。否則,可以在 i + n 的位置編碼新的字元。

val is_valid_utf_16le : t -> bool

is_valid_utf_16le b 只有在 b 包含有效的 UTF-16LE 資料時才會回傳 true

整數的二進制編碼/解碼

本節中的函式會將整數編碼和解碼為二進制位元組序列。

如果索引 i 所需的空間不足以解碼或編碼整數,則以下所有函式都會引發 Invalid_argument 錯誤。

小端序(little-endian)(resp. 大端序 big-endian)編碼表示最不重要(resp. 最重要)的位元組會先儲存。大端序也稱為網路位元組順序。原生端序編碼是小端序或大端序,取決於 Sys.big_endian 的值。

32 位元和 64 位元整數由 int32int64 型別表示,可以解讀為有號或無號數字。

8 位元和 16 位元整數由 int 型別表示,其位元數多於二進制編碼。這些額外的位元處理方式如下:

val get_uint8 : bytes -> int -> int

get_uint8 b i 取得 b 中從位元組索引 i 開始的無號 8 位元整數。

val get_int8 : bytes -> int -> int

get_int8 b i 取得 b 中從位元組索引 i 開始的有號 8 位元整數。

val get_uint16_ne : bytes -> int -> int

get_uint16_ne b i 取得 b 中從位元組索引 i 開始的原生端序無號 16 位元整數。

val get_uint16_be : bytes -> int -> int

get_uint16_be b i 取得 b 中從位元組索引 i 開始的大端序無號 16 位元整數。

val get_uint16_le : bytes -> int -> int

get_uint16_le b i 取得 b 中從位元組索引 i 開始的小端序無號 16 位元整數。

val get_int16_ne : bytes -> int -> int

get_int16_ne b i 取得 b 中從位元組索引 i 開始的原生端序有號 16 位元整數。

val get_int16_be : bytes -> int -> int

get_int16_be b i 取得 b 中從位元組索引 i 開始的大端序有號 16 位元整數。

val get_int16_le : bytes -> int -> int

get_int16_le b i 取得 b 中從位元組索引 i 開始的小端序有號 16 位元整數。

val get_int32_ne : bytes -> int -> int32

get_int32_ne b i 取得 b 中從位元組索引 i 開始的原生端序 32 位元整數。

val get_int32_be : bytes -> int -> int32

get_int32_be b i 取得 b 中從位元組索引 i 開始的大端序 32 位元整數。

val get_int32_le : bytes -> int -> int32

get_int32_le b i 取得 b 中從位元組索引 i 開始的小端序 32 位元整數。

val get_int64_ne : bytes -> int -> int64

get_int64_ne b i 取得 b 中從位元組索引 i 開始的原生端序 64 位元整數。

val get_int64_be : bytes -> int -> int64

get_int64_be b i 取得 b 中從位元組索引 i 開始的大端序 64 位元整數。

val get_int64_le : bytes -> int -> int64

get_int64_le b i 取得 b 中從位元組索引 i 開始的小端序 64 位元整數。

val set_uint8 : bytes -> int -> int -> unit

set_uint8 b i vb 中從位元組索引 i 開始的無號 8 位元整數設定為 v

val set_int8 : bytes -> int -> int -> unit

set_int8 b i vb 中從位元組索引 i 開始的有號 8 位元整數設定為 v

val set_uint16_ne : bytes -> int -> int -> unit

set_uint16_ne b i vb 中從位元組索引 i 開始的原生端序無號 16 位元整數設定為 v

val set_uint16_be : bytes -> int -> int -> unit

set_uint16_be b i vb 中從位元組索引 i 開始的大端序無號 16 位元整數設定為 v

val set_uint16_le : bytes -> int -> int -> unit

set_uint16_le b i vb 中從位元組索引 i 開始的小端序無號 16 位元整數設定為 v

val set_int16_ne : bytes -> int -> int -> unit

set_int16_ne b i vb 中從位元組索引 i 開始的原生端序有號 16 位元整數設定為 v

val set_int16_be : bytes -> int -> int -> unit

set_int16_be b i vb 中從位元組索引 i 開始的大端序有號 16 位元整數設定為 v

val set_int16_le : bytes -> int -> int -> unit

set_int16_le b i vb 中從位元組索引 i 開始的小端序有號 16 位元整數設定為 v

val set_int32_ne : bytes -> int -> int32 -> unit

set_int32_ne b i vb 中從位元組索引 i 開始的原生端序 32 位元整數設定為 v

val set_int32_be : bytes -> int -> int32 -> unit

set_int32_be b i vb 中從位元組索引 i 開始的大端序 32 位元整數設定為 v

val set_int32_le : bytes -> int -> int32 -> unit

set_int32_le b i vb 中從位元組索引 i 開始的小端序 32 位元整數設定為 v

val set_int64_ne : bytes -> int -> int64 -> unit

set_int64_ne b i vb 中從位元組索引 i 開始的原生端序 64 位元整數設定為 v

val set_int64_be : bytes -> int -> int64 -> unit

set_int64_be b i vb 中從位元組索引 i 開始的大端序 64 位元整數設定為 v

val set_int64_le : bytes -> int -> int64 -> unit

set_int64_le b i vb 中從位元組索引 i 開始的小端序 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 的域不能保證也會觀察到寫入索引 123 的寫入。