5

如何使用恒定内存懒惰地将多个文件作为单个 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不应用它们的情况下替换它们的方法。

4

2 回答 2

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)

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

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

于 2012-10-01T14:24:04.840 回答
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

(注意:我没有时间测试代码,但是阅读文档说这应该可以)

于 2012-10-01T14:22:37.083 回答