Haskell中的编码和高效IO

Ber*_*ian 0 encoding haskell bytestring

您好,我对编码数据所需的所有Haskell模块感到有点困惑,String以便ByteString进行高效写入.

我不明白你如何将a转换Data.ByteString.Lazy为a Data.ByteString.Char8,反之亦然.

我需要知道什么?因为我不能让用途的所有这些可能的组合...... Data.ByteString,Data.ByteString.Lazy,Data.ByteString.Char8,然后还有Data.Text.....我需要以轻松,高效地将字符串写入到文件和副-versa什么?(使用适当的编码)

PS目前阅读真实世界Haskell,我对所有这些模块感到非常困惑.

K. *_*uhr 5

这是路线图的一个镜头.

字符串和文本

您可能知道,Haskell String类型只是一个类型的同义词[Char],其中Char的数据类型可以表示单个Unicode代码点.这使得String完美的数据类型代表文本数据,除了小问题 - 作为盒装Char值的链接列表- 它可能非常低效.

包中的Text数据类型text解决了这个问题. Text也就像是String一个Char值列表的表示,但它不使用实际的Haskell列表,而是使用时间和空间有效的表示.String无论何时需要有效地处理文本(Unicode)数据,它都应该是您的首选替代品.

与标准Haskell库中的许多其他数据类型一样,它有懒惰和严格的变体.两种变体都具有相同的名称Text,但它们包含在单独的模块中,因此您可以执行以下操作:

import qualified Data.Text as TS
import qualified Data.Text.Lazy as TL
Run Code Online (Sandbox Code Playgroud)

如果你需要在同一个程序中使用这两个TS.TextTL.Text变体.

变体之间的确切差异在Data.Text文档中描述.简而言之,您应该默认使用严格版本.您只在两种情况下使用惰性版本.首先,如果您计划Text一次处理一个大的值,将其视为文本"流"而不是"字符串",则懒惰版本是一个不错的选择.(例如,读取大量CSV数字文件的程序可能会将该文件读取为长延迟Text流,并将结果存储为有效的数字类型Vector,如未装箱的Double值,以避免将整个输入文本保留在内存中.)其次,如果你是Text从很多小块中构建一个大字符串,那么你不想使用严格的版本,因为它们的不变性意味着它们需要在你添加东西时被复制.相反,你想要使用带有函数的惰性变体Data.Text.Lazy.Builder.

字节串

另一方面,ByteString来自bytestring包的数据类型是字节列表的有效表示.就像Text是一个高效版本一样[Char],您应该将其ByteString视为一个有效的版本[Word8],Word8Haskell类型在哪里表示一个值为0-255的单个无符号数据字节.同样地,您可以将a ByteString视为表示要从文件读取或写入文件的一块内存或一大块数据,精确地按字节为单位.它还有懒惰和严格的口味:

import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
Run Code Online (Sandbox Code Playgroud)

并且使用变体的注意事项类似于Text.

读写文件

在Haskell程序中,通常将Unicode字符串内部表示为String或者Text值.但是,要从文件中读取它们或将它们写入文件,需要将它们编码到字节序列中并从中进行解码.

处理此问题的最简单方法是使用Haskell函数自动处理编码和解码.您可能已经意识到,Prelude直接读取和写入字符串已经有两个函数:

readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
Run Code Online (Sandbox Code Playgroud)

此外,还有readFilewriteFile函数text是做到这一点.你可以在两个版本Data.Text.IOData.Text.Lazy.IO.它们看起来具有相同的签名,但是一个是在严格Text类型上运行而另一个是在惰性Text类型上运行:

readFile :: FilePath -> IO Text
writeFile :: FilePath -> Text -> IO ()
Run Code Online (Sandbox Code Playgroud)

您可以告诉这些函数自动进行编码和解码,因为它们返回并接受Text值,而不是ByteString值.使用的默认编码取决于操作系统及其配置.在典型的现代Linux发行版中,它将是UTF-8.

或者,您可以使用bytestring包中的函数从文件中读取或写入原始字节(同样,根据模块,可以是惰性版本或严格版本):

readFile :: FilePath -> IO ByteString
writeFile :: FilePath -> ByteString -> IO ()
Run Code Online (Sandbox Code Playgroud)

它们与text版本具有相同的名称,但您可以看到它们处理原始字节,因为它们返回并接受ByteString参数.在这种情况下,如果要将这些ByteStrings用作文本数据,则需要自己对其进行解码或编码.例如,如果ByteString代表文本的UTF-8编码版本,则从Data.Text.Encoding(对于严格版本)或Data.Text.Lazy.Encoding(对于懒惰版本)这些函数是您正在寻找的:

decodeUtf8 :: ByteString -> Text
encodeUtf8 :: Text -> ByteString
Run Code Online (Sandbox Code Playgroud)

Char8模块

现在,模块中的Data.ByteString.Char8Data.ByteString.Lazy.Char8是一个特例.当使用几种"ASCII保留"编码方案(包括ASCII本身,Latin-1和其他Latin-x编码以及UTF-8)中的一种编码纯ASCII文本时,结果证明编码ByteString只是一个简单的编码Unicode代码的每字符字节编码点0到127.稍微更普遍的是,当文本用Latin-1编码时,编码ByteString只是一个简单的单字节每字符编码的Unicode代码点0到255.在这些情况下,仅在这些情况下,这些模块中的函数可以安全地用于绕过显式编码和解码步骤,并直接通过自动转换单个字节将字节字符串视为ASCII和/或Latin-1文本unicode Char值和返回.

因为这些函数仅适用于特殊情况,所以除了专门的应用程序之外,通常应该避免使用它们.

另外,正如评论中提到的,ByteString这些Char8模块中的变体与普通严格和惰性ByteString变体没有任何不同; 只是将它们视为Char值的字符串而不是那些模块中Word8函数的值- 数据类型是相同的,只是函数接口是不同的.

一般战略

因此,如果您使用纯文本和操作系统的默认编码,只需使用来自的严格Text数据类型Data.Text和(高效)IO函数Data.Text.IO.您可以使用惰性变体进行流处理或从小块中构建大字符串,您可以使用Data.Text.Read一些简单的解析.

String在大多数情况下,您应该能够避免使用,但如果您发现需要来回转换,那么Data.Text(或Data.Text.Lazy)中的这些转换函数将非常有用:

pack :: String -> Text
unpack :: Text -> String
Run Code Online (Sandbox Code Playgroud)

如果您需要更多地控制编码,您仍然希望在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
Run Code Online (Sandbox Code Playgroud)

Data.ByteString.Lazy包含ByteString值的相应函数:

toStrict :: BL.ByteString -> BS.ByteString
fromStrict :: BS.ByteString -> BL.ByteString
Run Code Online (Sandbox Code Playgroud)