在Haskell中,我可以作为一个IO操作懒洋洋地读取n个文件吗



如何在恒定内存的情况下将多个文件作为一个ByteString惰性地读取?

readFiles :: [FilePath] -> IO ByteString

我目前有以下实现,但根据我从评测中看到的以及我的理解,我将以内存中文件的n-1结束。

readFiles = foldl1 joinIOStrings . map ByteString.readFile
where joinIOStrings ml mr = do
l <- ml
r <- mr
return $ l `ByteString.append` r

我知道这里的缺陷是我正在应用IO操作,然后重写它们,所以我认为我需要的是一种在不应用它们的情况下替换foldl1 joinIOStrings的方法。

如何在恒定内存的情况下将多个文件作为单个ByteString进行延迟读取?

如果您想要恒定的内存使用率,则需要Data.ByteString.Lazy。严格的ByteString不能延迟读取,并且需要O(sum of filesizes)内存。

对于数量不太多的文件,简单地将它们全部读取(D.B.L.readFile延迟读取)并将结果连接起来是很好的,

import qualified Data.ByteString.Lazy as L
readFiles :: [FilePath] -> IO L.ByteString
readFiles = fmap L.concat . mapM L.readFile

mapM L.readFile将打开文件,但仅在需要时读取每个文件的内容。

如果文件数量很大,以至于操作系统允许单个进程打开的文件句柄的限制可能会耗尽,那么您需要更复杂的东西。你可以制作你自己的懒惰版mapM

import System.IO.Unsafe (unsafeInterleaveIO)
mapM_lazy :: [IO a] -> IO [a]
mapM_lazy [] = return []
mapM_lazy (x:xs) = do
r <- x
rs <- unsafeInterleaveIO (mapM_lazy xs)
return (r:rs)

这样,每个文件将只在需要其内容时打开,而之前读取的文件可能已经关闭。由于无法保证关闭句柄的时间,因此仍有可能遇到资源限制。

或者,您可以使用您最喜欢的iterateeenumeratorconduit或任何能够系统地解决问题的软件包。它们中的每一个相对于其他的都有自己的优点和缺点,如果编码正确,就消除了意外达到资源限制的可能性。

我假设您使用的是惰性字节字符串(来自Data.ByteString.Lazy)。可能还有其他方法可以做到这一点,但有一种选择是简单地使用concat :: [ByteString] -> ByteString:

import Control.Monad
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as ByteString
readFiles :: [FilePath] -> IO ByteString
readFiles = fmap ByteString.concat . mapM ByteString.readFile

(注意:我没有时间测试代码,但阅读文档表明这应该有效)

最新更新