在readFile之后Haskell文件会自动关闭吗?

时间:2012-12-13 21:08:36

标签: haskell io lazy-evaluation

我想使用Haskell函数

readFile :: FilePath -> IO String

将文件内容读入字符串。在the documentation中,我读到“文件是按需读取的,与getContents一样。”

我不确定我完全理解这一点。例如,假设我写了

s <- readFile "t.txt"

执行此操作时:

  • 文件已打开。
  • s中的字符实际上是从文件中读取的(但不是很快)它们需要评估某些表达式(例如,如果我评估length s 所有的内容将读取文件并关闭文件。
  • 一旦读取了最后一个字符,与readFile的此调用关联的文件句柄将自动关闭。

我的第三个陈述是否正确?那么,我可以在不关闭文件句柄的情况下调用readFile吗?只要我没有消耗(访问)整个结果字符串,句柄是否会保持打开状态?

修改

以下是有关我的疑虑的更多信息。假设我有以下内容:

foo :: String -> IO String
foo filename = do
                  s <- readFile "t.txt"
                  putStrLn "File has been read."
                  return s

执行putStrLn时,我会(直观地)期待

  1. s包含文件t.txt
  2. 的全部内容
  3. 用于读取文件的句柄已关闭。
  4. 如果不是这样的话:

    • 执行sputStrLn包含哪些内容?
    • 执行putStrLn时文件句柄处于什么状态?
    • 如果putStrLn执行时s不包含文件的全部内容,实际上何时会读取此内容,何时关闭文件?

2 个答案:

答案 0 :(得分:8)

  

我的第三个陈述是否正确?

不完全,文件未关闭“一旦读完最后一个字符”,至少通常不会,它会在读取过程中处于半闭状态时徘徊一段时间,IO- manager / runtime将在下次执行此类操作时将其关闭。如果您正在快速打开和读取文件,那么如果操作系统限制不是太高,那么延迟可能会导致您用完文件句柄。

对于大多数用例(在我有限的经验中),文件句柄的关闭足够及时。 [有些人不同意并认为懒惰的IO在所有情况下都极其危险。它肯定有陷阱,但IMO的危险往往被夸大了。]

  

那么,我可以在不关闭文件句柄的情况下调用readFile吗?

是的,当您使用readFile时,文件句柄会在完全读取文件内容时或在注意到文件句柄不再被引用时自动关闭。

  

只要我没有消耗(访问)整个结果字符串,句柄是否会保持打开状态?

不完全是,readFile将文件句柄置于半封闭状态,如hGetContents的文档中所述:

  

计算hGetContents hdl返回与hdl管理的频道或文件的未读部分对应的字符列表,该部分被置于中间状态,半封闭。在此状态下,hdl实际上已关闭,但项目会根据需要从hdl读取并累积在hGetContents hdl.

返回的特殊列表中

foo :: String -> IO String
foo filename = do
              s <- readFile "t.txt"
              putStrLn "File has been read."
              return s

啊,这是另一端懒惰IO的陷阱之一。这里文件在读取内容之前关闭。当foo返回时,不再引用文件句柄,然后关闭。 foo s结果的使用者将发现s是一个空字符串,因为当hGetContents尝试实际从文件中读取时,句柄已经关闭。 < / p>

我将readFile的行为与

的行为混为一谈
bracket (openFile file ReadMode) hClose hGetContents

那里。 readFile仅在s不再引用后才关闭文件句柄,因此它在此处的行为正确。

  

执行putStrLn时,我会(直观地)期待

     
      
  1. s包含文件t.txt
  2. 的全部内容   
  3. 用于读取文件的句柄已关闭。
  4.   

不,s不包含任何内容,但可以从文件句柄中获取一些字符。文件句柄是半关闭的,但未关闭。当文件内容被完全读取或s超出范围时,它将被关闭。

  

如果不是这样的话:

     
      
  • 执行sputStrLn包含哪些内容?
  •   
  • 执行putStrLn时文件句柄处于什么状态?
  •   
  • 如果putStrLn执行时s不包含文件的全部内容,实际上何时会读取此内容,何时关闭文件?
  •   

前两个问题已得到解答,第三个问题的答案是“当内容被消耗时将读取文件”,并且当读取整个内容或不再引用内容时它将被关闭。

这与上面的bracket调用不同 - bracket保证最终操作,这里hClose即使其他操作抛出异常也会运行,因此使用它经常被推荐。但是,hClosebracket返回时运行,然后hGetContents无法从现在真正关闭的文件句柄中获取任何内容。但是,如果发生异常,readFile不一定会关闭文件句柄。

这是懒惰IO的危险或怪癖之一,在要求内容之前不会读取文件,如果你错误地使用了懒惰的IO,那就太晚了,你没有得到任何内容。

很多(甚​​至是大多数)陷入这种或那种情况的陷阱,但是在被它咬了之后,很快就会知道IO何时需要非懒惰并在那些情况下非懒惰地进行。

备选方案(迭代器,枚举器,管道,管道......)避免了这些陷阱[除非实施者犯了错误],但在懒惰IO非常好的情况下使用它们要好得多。另一方面,他们对待不太需要懒惰的案例。

答案 1 :(得分:5)

  

执行putStrLn时,我会(直观地)期望s包含文件t.txt的全部内容,

你需要考虑一下你在这里使用懒惰IO的事实。从文件中读取只会创建一个无差别的字符串计算,如果以后需要,则会读取该文件。

通过使用惰性IO,您可以推迟IO,直到需要该值。

一旦读取了文件的最后一个字符,或者删除了对打开文件的所有引用(例如,您的s值),垃圾收集器将关闭您打开的文件。