【问题标题】:Haskell read/write binary files complete working exampleHaskell 读/写二进制文件完整的工作示例
【发布时间】:2015-11-22 02:32:23
【问题描述】:

我希望有人给出一个完整的工作代码,允许在 Haskell 中执行以下操作:

读取一个非常大的 32 位序列(超过 10 亿个元素) 将二进制文件中的 int 值放入适当的容器中(例如 当然不是列表,因为性能问题)并将每个数字加倍 如果小于 1000(十进制),然后写入生成的 32 位 int 值到另一个二进制文件。我可能不想阅读全文 内存中二进制文件的内容。我想读一本 前一个之后的块。

我很困惑,因为我找不到关于此的文档。 Data.Binary、ByteString、Word8 等等,它只会增加混乱。对于 C/C++ 中的此类问题,有相当直接的解决方案。取一个所需大小的数组(例如无符号整数),并使用读/写库调用并完成它。在 Haskell 中,这似乎并不容易,至少对我来说是这样。

如果您的解决方案使用主流 Haskell (> GHC 7.10) 可用的最佳标准 包而不是一些晦涩/过时的包,我将不胜感激。

我从这些页面阅读

https://wiki.haskell.org/Binary_IO

https://wiki.haskell.org/Dealing_with_binary_data

【问题讨论】:

    标签: haskell io binaryfiles


    【解决方案1】:

    在 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
    

    【讨论】:

      【解决方案2】:

      如果您正在执行二进制 I/O,您几乎肯定希望ByteString 用于实际的输入/输出部分。看看它提供的hGethPut 函数。 (或者,如果您只需要严格的线性访问,您可以尝试使用惰性 I/O,但很容易出错。)

      当然,字节串只是一个字节数组;你的下一个问题是将这些字节解释为字符/整数/双精度/它们应该是什么。有几个包,但Data.Binary 似乎是最主流的。

      binary 的文档似乎希望引导您使用 Binary 类,您可以在其中编写代码来序列化和反序列化整个对象。但是您可以使用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 整数),执行您指定的计算,然后再次写回结果。

      显然,如果您“真正地”执行此操作,则需要 EOF 检查等。

      【讨论】:

      • 6 个库已导入! “..youd 想要 EOF 检查..” - 我想知道:EOF 检查是否需要其他一些包,如 Data.Binary.EOF 或一些异常 Monad?并且只是添加简单的EOF检查需要添加另外100行代码?我希望它不是那么可怕。我正在尝试添加 EOF 检查,一旦能够完成该壮举,我会将您的解决方案标记为已接受。遗憾的是,官方文档没有提供一个工作示例。
      • 还有一点,这是标准解决方案吗?因为我在某处读到,如果你想在 Haskell 中做任何严肃的 IO,你不应该使用 System.IO,因为它不可靠。
      • 事实证明,在这个特定示例中实际上不需要导入 Data.Word。如果您停止使用replicateM,也可以删除Control.Monad。您可以使用hIsEOF 检查EOF,它位于System.IO(已导入)中。这只是一个函数调用。哦,我不知道你从哪里得到System.IO 是“不可靠”的印象。也许谁说的是在该模块的特定功能?
      • (1) "已导入 6 个库!" - 六个模块,但只有三个库/包:basebytestringbinary。所有这些都与 GHC 捆绑在一起,因此它们与您将获得的一样标准。在任何情况下,小模块和功能从小库中分离出来是 Haskell 中的常态而不是例外,即使对于诸如 bytestring 提供的基本功能也是如此。 (2) 在 EOF 上,除了hIsEOF,您可能还会发现Control.Exception 有用。它以方便的形式提供了熟悉的工具,例如trycatchfinally
      • @mntk123 replicateM 1000 表示“读取 1000 个块”。而chunk_size 是一个块中有多少字节。这些数字并不特别。我只是选择了示例值。实际上,您可能不会真正的 1000 个块,您会编写一个实际检查 EOF 或其他内容的循环。
      【解决方案3】:

      这是一个循环,每次处理来自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

      【讨论】:

      • hGetLine 似乎读了一行。我希望阅读的文件可能只有一行十亿个整数背靠背放置。也就是说newline如果出现,其实是数据值的一部分(整数)
      • 我说:hGetLine换成4个字节的东西,你应该可以在我提供的链接中找到这样的功能。
      • 对不起,但这正是我感到困惑的地方。 :( 你能修改你的代码让它做些神奇的事情吗?
      • 试试那个页面上列出的hGet函数怎么样。
      猜你喜欢
      • 2013-02-09
      • 2012-01-26
      • 2018-07-26
      • 2016-06-14
      • 2021-06-03
      • 1970-01-01
      • 1970-01-01
      • 2016-03-30
      相关资源
      最近更新 更多