我正在尝试在Haskell中编写类似hexdump的程序。我编写了以下程序,我很高兴它可以工作并提供所需的输出,但它非常慢且效率低。它改编自this answer中的程序。
我使用sample file运行程序,处理少于1MB的文件大约需要1分钟。标准的Linux hexdump程序可以在不到一秒的时间内完成工作。我想在程序中执行的操作是read-> process->在bytestring中写入所有单个字节。
这是一个问题 - 如何有效地读取/处理/写入字节串(逐字节,即不使用任何其他函数,如getWord32le
,如果需要的话)?我想对每个单独的字节进行算术和逻辑运算,不一定在Word32le
或像这样的一组字节。我没有找到像Byte这样的数据类型。
无论如何,这是我写的代码,它在ghci(版本7.4)上成功运行 -
module Main where
import Data.Time.Clock
import Data.Char
import qualified Data.ByteString.Lazy as BIN
import Data.ByteString.Lazy.Char8
import Data.Binary.Get
import Data.Binary.Put
import System.IO
import Numeric (showHex, showIntAtBase)
main = do
let infile = "rose_rosebud_flower.jpg"
let outfile = "rose_rosebud_flower.hex"
h_in <- openFile infile ReadMode
System.IO.putStrLn "before time: "
t1 <- getCurrentTime >>= return . utctDayTime
System.IO.putStrLn $ (show t1)
process_file h_in outfile
System.IO.putStrLn "after time: "
t2 <- getCurrentTime >>= return . utctDayTime
System.IO.putStrLn $ (show t2)
hClose h_in
process_file h_in outfile = do
eof <- hIsEOF h_in
if eof
then return ()
else do bin1 <- BIN.hGet h_in 1
let str = (Data.ByteString.Lazy.Char8.unpack) bin1
let hexchar = getHex str
System.IO.appendFile outfile hexchar
process_file h_in outfile
getHex (b:[]) = (tohex $ ord b) ++ " "
getHex _ = "ERR "
tohex d = showHex d ""
当我在ghci上运行时,我得到了
*Main> main
before time:
23254.13701s
after time:
23313.381806s
请提供修改后的(但工作完整的)代码作为答案,而不仅仅是某些功能的名称列表。此外,不提供使用jpeg或其他图像处理库的解决方案,因为我对图像处理不感兴趣。我使用jpeg图像作为示例非文本文件。我只想逐字节处理数据。也不要提供其他站点的链接(特别是Haskell站点上的文档(或缺少它))。我无法理解bytestring的文档以及在Haskell网站上编写的许多其他软件包,他们的文档(在大多数情况下只是在页面上收集的类型签名)似乎只适用于已经理解了大部分内容的专家。如果我可以通过阅读他们的文档甚至广告(现实世界的haskell)RWH书来找出解决方案,我首先不会问这个问题。
对于看似咆哮感到抱歉,但与其他许多语言相比,Haskell的使用体验令人沮丧,特别是当使用小型完整工作示例
答案 0 :(得分:10)
您的示例代码一次读取一个字节。这几乎可以保证很慢。更好的是,它读取1字节ByteString
,然后立即将其转换为列表,否定ByteString
的所有好处。最重要的是,它通过打开文件,附加单个字符,然后再次关闭文件的稍微奇怪的方法写入输出文件。因此,对于写出的每个单独的十六进制字符,文件必须完全打开,缠绕到最后,附加一个字符,然后刷新到磁盘并再次关闭。
我并不是100%肯定你在这里想要实现的目标(即,试图了解其中的东西是如何工作的,而不是试图让特定的程序工作),所以我不确定如何最好地回答你的问题。
如果这是您第一次涉足Haskell,从以I / O为中心的事情开始可能是一个坏主意。在担心如何进行高性能I / O之前,最好先学习其余的语言。那就是说,让我试着回答你的实际问题......
首先,没有名为&#34; byte&#34;的类型。您正在寻找的类型称为Word8
(如果您想要无符号 8位整数)或Int8
(如果您想要签名< / em> 8位整数 - 你可能不会这样做。还有Word16
,Word32
,Word64
等类型;你需要导入Data.Word
来获取它们。同样,Int16
,Int32
和Int64
位于Data.Int
。 Int
和Integer
类型会自动导入,因此您不需要为这些类型执行任何特殊操作。
ByteString
基本上是一个字节数组。另一方面,[Word8]
是单个链接的单个字节列表,可能会或可能不会被计算 - 效率低得多,但更灵活。
如果你想要做的字面 all 是将变换应用于每个字节,与任何其他字节无关,那么ByteString
包提供了一个map
函数,做到这一点:
map :: (Word8 -> Word8) -> ByteString -> ByteString
如果只是想要从一个文件中读取并写入另一个文件,则可以使用所谓的&#34;懒惰的I / O&#34;来实现。这是一个整洁的闪避,图书馆为您处理所有的I / O块。虽然它有一些讨厌的陷阱;基本上围绕它很难确切知道什么时候输入文件将被关闭。对于简单的情况,这并不重要。对于更复杂的情况,确实如此。
那它是如何运作的?好吧,ByteString
库有一个函数
readFile :: FilePath -> IO ByteString
它看起来像它将整个文件读入内存中的巨型ByteString
。但它并没有。这是一个技巧。实际上它只是检查文件是否存在,并打开它进行读取。当您尝试使用 ByteString
时,在后台将文件在处理时无形地读入内存。这意味着你可以这样做:
main = do
bin <- readFile "in_file"
writeFile "out_file" (map my_function bin)
这将读取in_file
,将my_function
应用于文件的每个字节,并将结果保存到out_file
,自动以足够大的块进行I / O以提供良好的性能,但从不在RAM中同时保存多个块。 (my_function
部分必须具有类型Word8 -> Word8
。)因此,编写起来非常简单,并且应该非常快。
如果您不想阅读整个文件,或想要以随机顺序访问该文件,或者任何类似的复杂文件,事情会变得很有趣。我告诉我要查看pipes
库,但我个人从未使用它。
为了一个完整的工作范例:
module Main where
import Data.Word
import qualified Data.ByteString.Lazy as BIN
import Numeric
main = do
bin <- BIN.readFile "in_file"
BIN.writeFile "out_file" (BIN.concatMap my_function bin)
my_function :: Word8 -> BIN.ByteString
my_function b =
case showHex b "" of
c1:c2:_ -> BIN.pack [fromIntegral $ fromEnum $ c1 , fromIntegral $ fromEnum $ c2] -- Get first two chars in hex string, convert Char to Word8.
c2:_ -> BIN.pack [fromIntegral $ fromEnum $ '0', fromIntegral $ fromEnum $ c2] -- Only one digit. Assume first digit is zeor.
请注意,因为一个字节变为两个十六进制数字,所以我使用了ByteString
版concatMap
,这允许{ {1}}返回整个my_function
而不是单个字节。