在Haskell中读取和关闭文件

时间:2014-09-13 08:43:15

标签: xml haskell file-io

我是Haskell的初学者,目前我正在尝试解析xml文件列表。

要解析给定文件名中的xml文件,我使用以下函数

searchXML :: String -> IO News
searchXML file = do
    rsp <- readFile file
    let tags = parseTags rsp 
    return News { author   = get_val "createdBy" tags,
                  headline = get_val "headline" tags,
                  content  =  get_val "text" tags}
    where 
        extr a b c = drop 1 $ takeWhile (~/= TagClose a) $
                     dropWhile (~/= TagOpen a b) c
        get a b = extr "value" [] $ extr "property" [("name",a)] b
        get_val a b = fromTagText $ (get a b) !! 0 

调用xml文件列表

searchForKW :: String -> IO [News]
searchForKW keyword = do
xmlList <- simpleFind (\p -> takeExtension p == ".xml") "."
xml <- mapM searchXML xmlList
return $ filter (kwInNews keyword) xml
where
    kwInNews :: String -> News -> Bool
    kwInNews keyword (News {author=a,headline=b,content=c}) = isInfixOf keyword c

然而,这会导致openFile: resource exhausted (Too many open files)错误。所以我认为文件是打开阅读但没有关闭。我怎样才能解决这个问题?

PS:非常欢迎任何进一步的重构提示。

2 个答案:

答案 0 :(得分:2)

readFile函数因此而臭名昭着。它假装将整个文件读成一个巨大的字符串,但事实并非如此。只需打开文件进行阅读即可立即返回。该文件只有在以下情况下才会关闭:

  • 您的程序会检查字符串的最后一个字符
  • 您的程序删除对结果的所有引用,并且垃圾收集器运行

麻烦的是,Haskell很懒。它可能看起来就像您的代码立即处理整个字符串一样,但实际上它取决于您对该处理结果的处理方式。弄清楚这种事情可能相当棘手。 Haskell的整个是你的代码实际执行时应该无关紧要 - 但我们在这里,需要代码在特定时刻执行,因为现实世界的可观察事物只发生在代码运行。

实际上,readFile非常适合快速检查一些小例子如何运作。只要您想要控制文件何时打开/关闭或者您想要高性能(即处理大型 XML文件),您就要避免readFile

如果您知道文件较小/性能不重要,则可以手动openFilehGetLinehClose。这样你就知道文件何时关闭,因为你正在关闭它。您可能还想查看ByteString库;有一个类似于readFile的函数,它返回一个严格的ByteString(换句话说,它真的一次加载整个文件)。 ByteString类型的效率也比String类型高得多(但使用起来更加繁琐)。

答案 1 :(得分:0)

遵循@MathematicalOrchid的建议我使用Data.ByteString中的readFileData.ByteString.Char8.unpack函数将ByteString转换为String

import qualified Data.ByteString as Str 
import qualified Data.ByteString.Char8 as Char8

searchXML :: String -> IO News
searchXML file = do
    rsp <- Str.readFile file
    let get a = getVal a $ parseTags $ Char8.unpack rsp 
        auth  = get "createdBy"
        headl = get "headline"
        cont  = get "text"
    return News {author = auth, headline = headl, content = cont}
    where 
          extract tag attr = (drop 1) . (takeWhile (~/= TagClose tag))
                             . dropWhile (~/= TagOpen tag attr)
          getVal attr      = (fromTagText . safeHead . extract "value" []) 
                             . extract "property" [("name", attr)]
          safeHead (x:xs)  = x 
          safeHead []      = TagText " "