Haskell 中的编码和高效 IO

Encoding and efficient IO in Haskell

你好,我对从 StringByteString 编码数据所需的所有 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.TextTL.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 字符串表示为 StringText 值。但是,要从文件中读取它们或将它们写入文件,需要将它们编码为字节序列并从中解码。

处理此问题的最简单方法是使用 Haskell 自动处理编码和解码的函数。您可能知道,Prelude 中已经有两个函数可以直接读写字符串:

readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()

此外,text中还有readFilewriteFile函数可以做到这一点。您可以在 Data.Text.IOData.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.Char8Data.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.EncodingData.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.ByteStringData.ByteString.Char8 取决于您希望字节串表示什么:字节序列或 8 位字符序列。 ByteString 是一种数据结构,可用于存储字节序列,每个字节的类型为:Word8 或 8 位字符的序列,每个字符的类型为:Char。只有一种 ByteString 类型。

因为我们经常处理混合了基于字符的数据的二进制数据,所以如果我们在不同的模块中分别对字节和字符进行操作会很方便;这样当我们需要处理基于字符的数据时,只需使用 Char8 模块中的操作即可。