Haskell read/write 二进制文件完整工作示例

Haskell read/write binary files complete working example

我希望有人能提供完整的工作代码,允许在 Haskell 中执行以下操作:

Read a very large sequence (more than 1 billion elements) of 32-bit int values from a binary file into an appropriate container (e.g. certainly not a list, for performance issues) and doubling each number if it's less than 1000 (decimal) and then write the resulting 32-bit int values to another binary file. I may not want to read the entire contents of the binary file in the memory at once. I want to read one chunk after the previous.

我很困惑,因为我找不到关于这方面的文档。 Data.Binary、ByteString、Word8 等等,这只会增加混乱。 C/C++ 中有针对此类问题的非常直接的解决方案。取一个所需大小的数组(例如 unsigned int 数组),然后使用 read/write 库调用并完成它。在 Haskell 这似乎并不那么容易,至少对我来说是这样。

如果您的解决方案使用主流 Haskell(> GHC 7.10)可用的​​最佳 标准 软件包而不是某些 obscure/obsolete,我将不胜感激]个。

我阅读了这些页面

https://wiki.haskell.org/Binary_IO

https://wiki.haskell.org/Dealing_with_binary_data

这是一个循环,一次处理一行 stdin:

import System.IO

loop = do b <- hIsEOF stdin
          if b then return ()
               else do str <- hGetLine stdin
                       let str' = ...process str...
                       hPutStrLn stdout str'

现在只需将 hGetLine 替换为读取 4 个字节的内容,等等

这是 Data.ByteString 的 I/O 部分:

https://hackage.haskell.org/package/bytestring-0.10.6.0/docs/Data-ByteString.html#g:29

如果你正在做 binary I/O,你几乎肯定想要 ByteString 作为实际的 input/output 部分。查看它提供的 hGethPut 函数。 (或者,如果你只需要严格的线性访问,你可以尝试使用惰性I/O,但很容易出错。)

当然,字节串只是一个字节数组;您的下一个问题是将这些字节解释为字符/整数/双精度数/它们应该是的任何其他内容。有几个软件包,但 Data.Binary 似乎是最主流的一个。

binary 的文档似乎想引导您使用 Binary class,您可以在其中编写代码来序列化和反序列化整个对象。但是您可以使用Data.Binary.GetData.Binary.Put中的函数来处理单个项目。在那里你会找到诸如getWord32be(get Word32 big-endian)等函数。

我现在没有时间写一个工作代码示例,但基本上看看我上面提到的函数并忽略其他一切,你应该有所了解。

现在有工作代码:

module Main where

import Data.Word
import qualified Data.ByteString.Lazy as BIN
import Data.Binary.Get
import Data.Binary.Put
import Control.Monad
import System.IO

main = do
  h_in  <- openFile "Foo.bin" ReadMode
  h_out <- openFile "Bar.bin" WriteMode
  replicateM 1000 (process_chunk h_in h_out)
  hClose h_in
  hClose h_out

chunk_size = 1000
int_size = 4

process_chunk h_in h_out = do
  bin1 <- BIN.hGet h_in chunk_size
  let ints1 = runGet (replicateM (chunk_size `div` int_size) getWord32le) bin1
  let ints2 = map (\ x -> if x < 1000 then 2*x else x) ints1
  let bin2 = runPut (mapM_ putWord32le ints2)
  BIN.hPut h_out bin2

我相信这可以满足您的要求。它读取 1000 个 chunk_size 字节块,将每个块转换为 Word32 的列表(因此内存中一次只有 chunk_size / 4 个整数),进行您指定的计算,然后写入结果又出来了。

显然,如果您这样做 "for real",您将需要 EOF 检查等。

在 Haskell 中使用二进制 I/O 的最佳方法是使用字节串。惰性字节串提供缓冲 I/O,所以你甚至不需要关心它。

下面的代码假定块大小是 32 位的倍数(它是)。

module Main where

import Data.Word
import Control.Monad
import Data.Binary.Get
import Data.Binary.Put
import qualified Data.ByteString.Lazy as BS
import qualified Data.ByteString as BStrict

-- Convert one bytestring chunk to the list of integers
-- and append the result of conversion of the later chunks.
-- It actually appends only closure which will evaluate next
-- block of numbers on demand.
toNumbers :: BStrict.ByteString -> [Word32] -> [Word32]
toNumbers chunk rest = chunkNumbers ++ rest
    where
    getNumberList = replicateM (BStrict.length chunk `div` 4) getWord32le
    chunkNumbers = runGet getNumberList (BS.fromStrict chunk)

main :: IO()
main = do
    -- every operation below is done lazily, consuming input as necessary
    input <- BS.readFile "in.dat"
    let inNumbers = BS.foldrChunks toNumbers [] input
    let outNumbers = map (\x -> if x < 1000 then 2*x else x) inNumbers
    let output = runPut (mapM_ putWord32le outNumbers)
    -- There lazy bytestring output is evaluated and saved chunk
    -- by chunk, pulling data from input file, decoding, processing
    -- and encoding it back one chunk at a time
    BS.writeFile "out.dat" output