2

streaming-bytestring库在 打印大约 512 个字节后给出错误。

错误:

openBinaryFile: resource exhausted (Too many open files)

代码:

import           Control.Monad.Trans (lift, MonadIO)
import           Control.Monad.Trans.Resource (runResourceT, MonadResource, MonadUnliftIO, ResourceT, liftResourceT)
import qualified Data.ByteString.Streaming          as BSS
import qualified Data.ByteString.Streaming.Char8    as BSSC
import           System.TimeIt

main :: IO ()
main = timeIt $ runResourceT $ dump $ BSS.drop 24 $ BSS.readFile "filename"

dump :: MonadIO m => BSS.ByteString m r -> m ()
dump bs = do
    isEmpty <- BSS.null_ bs
    if isEmpty then return ()
    else do
        BSSC.putStr $ BSS.take 1 bs
        dump $ BSS.drop 1 bs
4

1 回答 1

2

使用流库时,重用有效的流通常是个坏主意。也就是说,您可以将类似drop或的函数应用于splitAt流,然后继续使用生成的流,或者您可以使用fold之类的函数将流作为一个整体来使用,这样您就可以留在基础 monad 中。但是你永远不应该将相同的流值应用于两个不同的函数。

遗憾的是,目前的 Haskell 类型系统无法在编译时强制执行该限制,它需要某种形式的线性类型。相反,它变成了用户的责任。

null_函数可能是流式字节字符串api 中的一个缺陷,因为它不会随结果返回新的流,给人的印象是流重用在整个 API 中是正常的。如果它有一个类似的签名会更好 null_ :: ByteString m r -> m (Bool, ByteString m r)

同样,不要将dropandtake与相同的流值一起使用。相反,使用splitAtoruncons并处理除法结果。

dump :: MonadIO m => BSS.ByteString m r -> m ()
dump bs = do
    mc <- BSSC.uncons bs -- bs is only used once
    case mc of
        Left _ -> return ()
        Right (c,rest) -> do liftIO $ putChar c
                             dump rest

所以,关于错误。正如@BobDalgleish 在评论中提到的那样,正在发生的事情是文件在null_被调用时打开(这是我们第一次从流中“要求”某些东西)。在递归调用中,我们再次传递原始bs值,因此它将再次打开文件,每次迭代一次,直到我们达到文件句柄限制。


就个人而言,我不喜欢使用ResourceT流媒体库。如果可能的话,我更喜欢使用回调打开文件,withFile然后使用回调创建和使用流。但有些事情这样更难。

于 2019-05-13T18:14:55.503 回答