如何在恒定内存的情况下将多个文件作为一个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)
这样,每个文件将只在需要其内容时打开,而之前读取的文件可能已经关闭。由于无法保证关闭句柄的时间,因此仍有可能遇到资源限制。
或者,您可以使用您最喜欢的iteratee
、enumerator
、conduit
或任何能够系统地解决问题的软件包。它们中的每一个相对于其他的都有自己的优点和缺点,如果编码正确,就消除了意外达到资源限制的可能性。
我假设您使用的是惰性字节字符串(来自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
(注意:我没有时间测试代码,但阅读文档表明这应该有效)