Haskell 中的编码和高效 IO
Encoding and efficient IO in Haskell
你好,我对从 String
到 ByteString
编码数据所需的所有 Haskell 模块感到有点困惑,以实现高效写入。
我不明白你如何将 Data.ByteString.Lazy
转换为 Data.ByteString.Char8
,反之亦然。
我需要知道什么?因为我无法获得所有这些可能的用法组合....
Data.ByteString
,Data.ByteString.Lazy
,Data.ByteString.Char8
,然后是 Data.Text
.....我需要什么才能轻松高效地将字符串写入文件,反之亦然? (使用适当的编码)
P.S 目前正在阅读 Real World Haskell 我对所有这些模块感到很困惑。
这是路线图的截图。
字符串和文本
您可能知道,Haskell String
类型只是 [Char]
的类型同义词,其中 Char
是可以表示单个数据类型的数据类型Unicode 代码点。这使得 String
成为表示文本数据的完美数据类型,除了一个小问题——作为装箱 Char
值的链接列表——它可能效率极低。
text
包中的 Text
数据类型解决了这个问题。 Text
也像 String
一样表示 Char
值的列表,但它不是使用实际的 Haskell 列表,而是使用时间和 space-高效表示。当您需要高效地处理文本 (Unicode) 数据时,它应该是 String
的首选替代品。
与标准 Haskell 库中的许多其他数据类型一样,它有惰性变体和严格变体。两个变体具有相同的名称 Text
,但它们包含在不同的模块中,因此您可以这样做:
import qualified Data.Text as TS
import qualified Data.Text.Lazy as TL
如果您需要在同一程序中同时使用 TS.Text
和 TL.Text
变体。
documentation for Data.Text 中描述了变体之间的确切区别。简而言之,您应该默认使用严格版本。您只在两种情况下使用惰性版本。首先,如果您打算一次处理一个较大的 Text
值,将其更像是文本 "stream" 而不是 "string",那么惰性版本是好的选择。 (例如,读取一个巨大的 CSV 数字文件的程序可能会将该文件读取为一个长惰性 Text
流,并将结果存储在一个有效的数字类型中,例如 Vector
的未装箱 Double
值以避免将整个输入文本保留在内存中。)其次,如果您从许多小片段构建一个大的 Text
字符串,那么您不想使用严格版本,因为它们的不变性意味着每当您添加内容时都需要复制它们。相反,您希望将惰性变体与 Data.Text.Lazy.Builder
.
中的函数一起使用
字节串
另一方面,bytestring
包中的 ByteString
数据类型是字节列表的有效表示。就像 Text
是 [Char]
的高效版本一样,您应该将 ByteString
视为 [Word8]
的高效版本,其中 Word8
是 Haskell 类型,表示值为 0-255 的单个无符号数据字节。等效地,您可以将 ByteString
视为代表一块内存或一块要从文件中读取或写入文件的数据块,准确地说是一个字节接一个字节。它还具有懒惰和严格的风格:
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
使用变体的注意事项与Text
.
类似
读取和写入文件
在 Haskell 程序中,通常在内部将 Unicode 字符串表示为 String
或 Text
值。但是,要从文件中读取它们或将它们写入文件,需要将它们编码为字节序列并从中解码。
处理此问题的最简单方法是使用 Haskell 自动处理编码和解码的函数。您可能知道,Prelude
中已经有两个函数可以直接读写字符串:
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
此外,text
中还有readFile
和writeFile
函数可以做到这一点。您可以在 Data.Text.IO
和 Data.Text.Lazy.IO
中找到版本。它们似乎具有相同的签名,但一个在严格 Text
类型上运行,另一个在惰性 Text
类型上运行:
readFile :: FilePath -> IO Text
writeFile :: FilePath -> Text -> IO ()
您可以看出这些函数正在自动进行编码和解码,因为它们 return 并接受 Text
值,而不是 ByteString
值。使用的默认编码将取决于操作系统及其配置。在典型的现代 Linux 发行版中,它将是 UTF-8。
或者,您可以使用 bytestring
包中的函数从文件读取或写入原始字节(同样,惰性或严格版本,具体取决于模块):
readFile :: FilePath -> IO ByteString
writeFile :: FilePath -> ByteString -> IO ()
它们与 text
版本具有相同的名称,但您可以看到它们正在处理原始字节,因为它们 return 并接受 ByteString
参数。在这种情况下,如果您想将这些 ByteString
用作文本数据,则需要自己对它们进行解码或编码。例如,如果 ByteString
表示文本的 UTF-8 编码版本,那么来自 Data.Text.Encoding
(对于严格版本)或 Data.Text.Lazy.Encoding
(对于惰性版本)的这些函数就是您要的寻找:
decodeUtf8 :: ByteString -> Text
encodeUtf8 :: Text -> ByteString
Char8 模块
现在,Data.ByteString.Char8
和Data.ByteString.Lazy.Char8
中的模块是特例。当使用几种 "ASCII-preserving" 编码方案(包括 ASCII 本身、Latin-1 和其他 Latin-x 编码以及 UTF-8)中的一种对纯 ASCII 文本进行编码时,结果是编码的 ByteString
只是 Unicode 代码点 0 到 127 的一个简单的每个字符一个字节的编码。更一般地说,当文本以 Latin-1 编码时,编码的 ByteString
只是一个简单的一个字节- Unicode 代码点 0 到 255 的每个字符编码。在这些情况下,并且仅在这些情况下,可以安全地使用这些模块中的函数来绕过显式编码和解码步骤,并将字节字符串视为 ASCII and/or Latin-1 文本直接通过自动将单个字节转换为 unicode Char
值并返回。
因为这些函数只在特定情况下有效,所以通常应避免使用它们,除非是在专门的应用程序中。
此外,正如评论中提到的,这些 Char8
模块中的 ByteString
变体与普通的严格和惰性 ByteString
变体没有任何不同;这些模块中的 函数 将它们视为 Char
值而不是 Word8
值的字符串——数据类型相同,只是功能界面不同
总攻略
因此,如果您使用纯文本和操作系统的默认编码,只需使用来自 Data.Text
的严格 Text
数据类型和来自 [=] 的(高效)IO 函数54=]。您可以使用惰性变体进行流处理或从小片段构建大字符串,并且可以使用 Data.Text.Read
进行一些简单的解析。
你应该可以在大多数情况下完全避免使用String
,但是如果你发现你需要来回转换,那么Data.Text
(或[=83]中的这些转换函数=]) 会有用:
pack :: String -> Text
unpack :: Text -> String
如果您需要对编码进行更多控制,您仍然希望在整个程序中使用 Text
,但在 "edges" 处除外读取或写入文件。在这些边上,使用 Data.ByteString
(或 Data.ByteString.Lazy
)中的 I/O 函数,以及 Data.Text.Encoding
或 Data.Text.Lazy.Encoding
中的 encoding/decoding 函数。
如果您发现需要混合使用严格和惰性变体,请注意 Data.Text.Lazy
包含:
toStrict :: TL.Text -> TS.Text -- convert lazy to strict
fromStrict :: TS.Text -> TL.Text -- vice versa
和 Data.ByteString.Lazy
包含 ByteString
值的相应函数:
toStrict :: BL.ByteString -> BS.ByteString
fromStrict :: BS.ByteString -> BL.ByteString
这取决于您要处理的数据类型以及您打算如何处理这些数据。
如果您要处理 Unicode 字符串,请使用文本包中的 Text
。
如果您不需要一次将所有数据读入内存,请使用该模块的 Lazy
版本。否则,整个数据将加载到一个数据结构中。
何时使用 Data.ByteString
或 Data.ByteString.Char8
取决于您希望字节串表示什么:字节序列或 8 位字符序列。 ByteString
是一种数据结构,可用于存储字节序列,每个字节的类型为:Word8
或 8 位字符的序列,每个字符的类型为:Char
。只有一种 ByteString
类型。
因为我们经常处理混合了基于字符的数据的二进制数据,所以如果我们在不同的模块中分别对字节和字符进行操作会很方便;这样当我们需要处理基于字符的数据时,只需使用 Char8 模块中的操作即可。
你好,我对从 String
到 ByteString
编码数据所需的所有 Haskell 模块感到有点困惑,以实现高效写入。
我不明白你如何将 Data.ByteString.Lazy
转换为 Data.ByteString.Char8
,反之亦然。
我需要知道什么?因为我无法获得所有这些可能的用法组合....
Data.ByteString
,Data.ByteString.Lazy
,Data.ByteString.Char8
,然后是 Data.Text
.....我需要什么才能轻松高效地将字符串写入文件,反之亦然? (使用适当的编码)
P.S 目前正在阅读 Real World Haskell 我对所有这些模块感到很困惑。
这是路线图的截图。
字符串和文本
您可能知道,Haskell String
类型只是 [Char]
的类型同义词,其中 Char
是可以表示单个数据类型的数据类型Unicode 代码点。这使得 String
成为表示文本数据的完美数据类型,除了一个小问题——作为装箱 Char
值的链接列表——它可能效率极低。
text
包中的 Text
数据类型解决了这个问题。 Text
也像 String
一样表示 Char
值的列表,但它不是使用实际的 Haskell 列表,而是使用时间和 space-高效表示。当您需要高效地处理文本 (Unicode) 数据时,它应该是 String
的首选替代品。
与标准 Haskell 库中的许多其他数据类型一样,它有惰性变体和严格变体。两个变体具有相同的名称 Text
,但它们包含在不同的模块中,因此您可以这样做:
import qualified Data.Text as TS
import qualified Data.Text.Lazy as TL
如果您需要在同一程序中同时使用 TS.Text
和 TL.Text
变体。
documentation for Data.Text 中描述了变体之间的确切区别。简而言之,您应该默认使用严格版本。您只在两种情况下使用惰性版本。首先,如果您打算一次处理一个较大的 Text
值,将其更像是文本 "stream" 而不是 "string",那么惰性版本是好的选择。 (例如,读取一个巨大的 CSV 数字文件的程序可能会将该文件读取为一个长惰性 Text
流,并将结果存储在一个有效的数字类型中,例如 Vector
的未装箱 Double
值以避免将整个输入文本保留在内存中。)其次,如果您从许多小片段构建一个大的 Text
字符串,那么您不想使用严格版本,因为它们的不变性意味着每当您添加内容时都需要复制它们。相反,您希望将惰性变体与 Data.Text.Lazy.Builder
.
字节串
另一方面,bytestring
包中的 ByteString
数据类型是字节列表的有效表示。就像 Text
是 [Char]
的高效版本一样,您应该将 ByteString
视为 [Word8]
的高效版本,其中 Word8
是 Haskell 类型,表示值为 0-255 的单个无符号数据字节。等效地,您可以将 ByteString
视为代表一块内存或一块要从文件中读取或写入文件的数据块,准确地说是一个字节接一个字节。它还具有懒惰和严格的风格:
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
使用变体的注意事项与Text
.
读取和写入文件
在 Haskell 程序中,通常在内部将 Unicode 字符串表示为 String
或 Text
值。但是,要从文件中读取它们或将它们写入文件,需要将它们编码为字节序列并从中解码。
处理此问题的最简单方法是使用 Haskell 自动处理编码和解码的函数。您可能知道,Prelude
中已经有两个函数可以直接读写字符串:
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
此外,text
中还有readFile
和writeFile
函数可以做到这一点。您可以在 Data.Text.IO
和 Data.Text.Lazy.IO
中找到版本。它们似乎具有相同的签名,但一个在严格 Text
类型上运行,另一个在惰性 Text
类型上运行:
readFile :: FilePath -> IO Text
writeFile :: FilePath -> Text -> IO ()
您可以看出这些函数正在自动进行编码和解码,因为它们 return 并接受 Text
值,而不是 ByteString
值。使用的默认编码将取决于操作系统及其配置。在典型的现代 Linux 发行版中,它将是 UTF-8。
或者,您可以使用 bytestring
包中的函数从文件读取或写入原始字节(同样,惰性或严格版本,具体取决于模块):
readFile :: FilePath -> IO ByteString
writeFile :: FilePath -> ByteString -> IO ()
它们与 text
版本具有相同的名称,但您可以看到它们正在处理原始字节,因为它们 return 并接受 ByteString
参数。在这种情况下,如果您想将这些 ByteString
用作文本数据,则需要自己对它们进行解码或编码。例如,如果 ByteString
表示文本的 UTF-8 编码版本,那么来自 Data.Text.Encoding
(对于严格版本)或 Data.Text.Lazy.Encoding
(对于惰性版本)的这些函数就是您要的寻找:
decodeUtf8 :: ByteString -> Text
encodeUtf8 :: Text -> ByteString
Char8 模块
现在,Data.ByteString.Char8
和Data.ByteString.Lazy.Char8
中的模块是特例。当使用几种 "ASCII-preserving" 编码方案(包括 ASCII 本身、Latin-1 和其他 Latin-x 编码以及 UTF-8)中的一种对纯 ASCII 文本进行编码时,结果是编码的 ByteString
只是 Unicode 代码点 0 到 127 的一个简单的每个字符一个字节的编码。更一般地说,当文本以 Latin-1 编码时,编码的 ByteString
只是一个简单的一个字节- Unicode 代码点 0 到 255 的每个字符编码。在这些情况下,并且仅在这些情况下,可以安全地使用这些模块中的函数来绕过显式编码和解码步骤,并将字节字符串视为 ASCII and/or Latin-1 文本直接通过自动将单个字节转换为 unicode Char
值并返回。
因为这些函数只在特定情况下有效,所以通常应避免使用它们,除非是在专门的应用程序中。
此外,正如评论中提到的,这些 Char8
模块中的 ByteString
变体与普通的严格和惰性 ByteString
变体没有任何不同;这些模块中的 函数 将它们视为 Char
值而不是 Word8
值的字符串——数据类型相同,只是功能界面不同
总攻略
因此,如果您使用纯文本和操作系统的默认编码,只需使用来自 Data.Text
的严格 Text
数据类型和来自 [=] 的(高效)IO 函数54=]。您可以使用惰性变体进行流处理或从小片段构建大字符串,并且可以使用 Data.Text.Read
进行一些简单的解析。
你应该可以在大多数情况下完全避免使用String
,但是如果你发现你需要来回转换,那么Data.Text
(或[=83]中的这些转换函数=]) 会有用:
pack :: String -> Text
unpack :: Text -> String
如果您需要对编码进行更多控制,您仍然希望在整个程序中使用 Text
,但在 "edges" 处除外读取或写入文件。在这些边上,使用 Data.ByteString
(或 Data.ByteString.Lazy
)中的 I/O 函数,以及 Data.Text.Encoding
或 Data.Text.Lazy.Encoding
中的 encoding/decoding 函数。
如果您发现需要混合使用严格和惰性变体,请注意 Data.Text.Lazy
包含:
toStrict :: TL.Text -> TS.Text -- convert lazy to strict
fromStrict :: TS.Text -> TL.Text -- vice versa
和 Data.ByteString.Lazy
包含 ByteString
值的相应函数:
toStrict :: BL.ByteString -> BS.ByteString
fromStrict :: BS.ByteString -> BL.ByteString
这取决于您要处理的数据类型以及您打算如何处理这些数据。
如果您要处理 Unicode 字符串,请使用文本包中的 Text
。
如果您不需要一次将所有数据读入内存,请使用该模块的 Lazy
版本。否则,整个数据将加载到一个数据结构中。
何时使用 Data.ByteString
或 Data.ByteString.Char8
取决于您希望字节串表示什么:字节序列或 8 位字符序列。 ByteString
是一种数据结构,可用于存储字节序列,每个字节的类型为:Word8
或 8 位字符的序列,每个字符的类型为:Char
。只有一种 ByteString
类型。
因为我们经常处理混合了基于字符的数据的二进制数据,所以如果我们在不同的模块中分别对字节和字符进行操作会很方便;这样当我们需要处理基于字符的数据时,只需使用 Char8 模块中的操作即可。