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,我将不胜感激]个。
我阅读了这些页面
这是一个循环,一次处理一行 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 部分。查看它提供的 hGet
和 hPut
函数。 (或者,如果你只需要严格的线性访问,你可以尝试使用惰性I/O,但很容易出错。)
当然,字节串只是一个字节数组;您的下一个问题是将这些字节解释为字符/整数/双精度数/它们应该是的任何其他内容。有几个软件包,但 Data.Binary
似乎是最主流的一个。
binary
的文档似乎想引导您使用 Binary
class,您可以在其中编写代码来序列化和反序列化整个对象。但是您可以使用Data.Binary.Get
和Data.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
我希望有人能提供完整的工作代码,允许在 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,我将不胜感激]个。
我阅读了这些页面
这是一个循环,一次处理一行 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 部分。查看它提供的 hGet
和 hPut
函数。 (或者,如果你只需要严格的线性访问,你可以尝试使用惰性I/O,但很容易出错。)
当然,字节串只是一个字节数组;您的下一个问题是将这些字节解释为字符/整数/双精度数/它们应该是的任何其他内容。有几个软件包,但 Data.Binary
似乎是最主流的一个。
binary
的文档似乎想引导您使用 Binary
class,您可以在其中编写代码来序列化和反序列化整个对象。但是您可以使用Data.Binary.Get
和Data.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