您好,我对将数据从String
编码到ByteString
所需的所有Haskell模块有点困惑,以实现高效写入。
我不明白您如何将Data.ByteString.Lazy
转换为Data.ByteString.Char8
,反之亦然。
我需要知道什么?因为我无法获得所有这些可能的用法组合....Data.ByteString
,Data.ByteString.Lazy
,Data.ByteString.Char8
,然后是Data.Text
.....我需要什么才能轻松有效地将字符串写入文件,反之亦然?(使用正确的编码)
P.S目前正在阅读现实世界的 Haskell 和我对所有这些模块都感到非常困惑。
这是路线图的一个镜头。
字符串和文本
你可能知道,HaskellString
类型只是[Char]
的类型同义词,其中Char
是可以表示单个Unicode码位的数据类型。 这使得String
成为表示文本数据的完美数据类型,除了一个小问题 - 作为盒装Char
值的链接列表 - 它有可能非常低效。
text
包中的Text
数据类型解决了此问题。Text
也像String
一样,是Char
值列表的表示,但它不是使用实际的Haskell列表,而是使用时间和空间有效的表示。 当您需要高效处理文本(Unicode)数据时,它应该是String
的首选替代品。
与标准Haskell库中的许多其他数据类型一样,它有惰性和严格的变体。 两个变体具有相同的名称Text
,但它们包含在单独的模块中,因此您可以执行以下操作:
import qualified Data.Text as TS
import qualified Data.Text.Lazy as TL
如果您需要在同一程序中同时使用TS.Text
和TL.Text
变体。
变体之间的确切区别在 Data.Text 的文档中进行了描述。 简而言之,您应该默认使用严格版本。 您仅在两种情况下使用惰性版本。 首先,如果您打算一次处理一点点大的Text
值,将其视为文本"流"而不是"字符串",那么惰性版本是一个不错的选择。 (例如,读取大型 CSV 数字文件的程序可能会将该文件读取为长延迟Text
流,并将结果存储在有效的数字类型中,例如未装箱的Double
值Vector
,以避免将整个输入文本保留在内存中。 其次,如果你要从许多小块构建一个大的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 ()
您可以判断这些函数正在自动执行编码和解码,因为它们返回并接受Text
值,而不是ByteString
值。 使用的默认编码将取决于操作系统及其配置。 在典型的现代Linux发行版上,它将是UTF-8。
或者,您可以使用bytestring
包中的函数从文件中读取或写入原始字节(同样,延迟或严格版本,具体取决于模块):
readFile :: FilePath -> IO ByteString
writeFile :: FilePath -> ByteString -> IO ()
它们与text
版本具有相同的名称,但您可以看到它们正在处理原始字节,因为它们返回并接受ByteString
参数。 在这种情况下,如果要将这些ByteString
用作文本数据,则需要自己解码或编码它们。 例如,如果ByteString
表示文本的 UTF-8 编码版本,则Data.Text.Encoding
(对于严格版本)或Data.Text.Lazy.Encoding
(对于惰性版本)中的这些函数就是您要查找的:
decodeUtf8 :: ByteString -> Text
encodeUtf8 :: Text -> ByteString
字符 8 模块
现在,Data.ByteString.Char8
和Data.ByteString.Lazy.Char8
中的模块是一个特例。 当纯 ASCII 文本使用几种"ASCII 保留"编码方案(包括 ASCII 本身、Latin-1 和其他 Latin-x 编码以及 UTF-8)之一进行编码时,事实证明,编码ByteString
只是 Unicode 码位 0 到 127 的简单每字符一个字节的编码。 稍微一般地说,当文本以 Latin-1 编码时,编码ByteString
只是 Unicode 代码点 0 到 255 的简单每字符一个字节的编码。 在这些情况下,并且仅在这些情况下,这些模块中的函数可以安全地用于绕过显式编码和解码步骤,只需将字节字符串直接视为 ASCII 和/或拉丁语 1 文本,即可自动将单个字节转换为 unicodeChar
值并返回。
由于这些函数仅在特殊情况下有效,因此除在专用应用程序中外,通常应避免使用它们。
此外,正如评论中提到的,这些Char8
模块中的ByteString
变体与普通的严格和懒惰的ByteString
变体没有任何不同;这些模块中的函数只是将它们视为Char
值的字符串,而不是Word8
值 - 数据类型是相同的, 只是功能界面不同。
总体战略
因此,如果您使用的是纯文本和操作系统的默认编码,只需使用Data.Text
中的严格Text
数据类型和Data.Text.IO
中的(高效)IO 函数。 您可以使用惰性变体进行流处理或从小片段构建大字符串,也可以使用Data.Text.Read
进行一些简单的解析。
在大多数情况下,您应该能够避免使用String
,但是如果您发现需要来回转换,那么Data.Text
(或Data.Text.Lazy
)中的这些转换函数将很有用:
pack :: String -> Text
unpack :: Text -> String
如果您需要对编码进行更多控制,您仍然希望在整个程序中使用Text
,除了在读取或写入文件的"边缘"处。 在这些边缘,使用Data.ByteString
(或Data.ByteString.Lazy
)中的 I/O 函数以及来自Data.Text.Encoding
或Data.Text.Lazy.Encoding
的编码/解码函数。
如果您发现需要混合使用严格变体和懒惰变体,请注意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 包中的Text
。
如果不需要一次将所有数据读入内存,请使用该模块的Lazy
版本。否则,整个数据将加载到单个数据结构中。
何时使用Data.ByteString
或Data.ByteString.Char8
取决于您希望该字节串表示的内容:字节序列或 8 位字符序列。ByteString
是一种数据结构,可用于存储字节序列(每种类型:Word8
)或 8 位字符序列(每种类型:Char
)。只有一种ByteString
类型。
因为通常我们可能会处理二进制数据与基于字符的数据混合,所以如果我们在不同的模块中分别保留字节和字符的操作会很方便;这样,当我们需要处理基于字符的数据时,只需使用 Char8 模块中的操作。