如何使用常量内存懒惰地将多个文件作为单个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
而不应用它们的方法。
答案 0 :(得分:7)
如何使用常量内存懒惰地将多个文件作为单个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
或任何以系统方式解决问题的软件包。它们中的每一个都有其自身的优点和缺点,如果编码正确,则消除了意外达到资源限制的可能性。
答案 1 :(得分:1)
我假设您正在使用延迟字节字符串(来自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
(注意:我没有时间测试代码,但阅读文档说这应该有效)