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

Gio*_*gio 7 io haskell lazy-evaluation

我想使用Haskell函数

readFile :: FilePath -> IO String
Run Code Online (Sandbox Code Playgroud)

将文件的内容读入字符串.在文档中,我读到"文件是按需读取的,与getContents一样."

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

s <- readFile "t.txt"
Run Code Online (Sandbox Code Playgroud)

执行此操作时:

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

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

编辑

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

foo :: String -> IO String
foo filename = do
                  s <- readFile "t.txt"
                  putStrLn "File has been read."
                  return s
Run Code Online (Sandbox Code Playgroud)

putStrLn执行时,我会(直觉地)期望

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

如果不是这样的话:

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

Dan*_*her 8

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

不完全是,文件没有关闭"一旦读完最后一个字符",至少通常不会,它会在读取片刻期间处于半封闭状态,IO管理器/运行时将在下次执行此类操作时关闭它.如果您正在快速打开和读取文件,那么如果操作系统限制不是太高,那么延迟可能会导致文件句柄用完.

但是,对于大多数用例(在我有限的经验中),文件句柄的关闭足够及时.[有些人不同意并认为懒惰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
Run Code Online (Sandbox Code Playgroud)

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

我混淆了readFile那种行为

bracket (openFile file ReadMode) hClose hGetContents
Run Code Online (Sandbox Code Playgroud)

那里.readFile仅在s不再引用后关闭文件句柄,因此它在此处按预期正常运行.

putStrLn执行时,我会(直觉地)期望

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

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

如果不是这样的话:

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

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

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

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

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

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


Don*_*art 5

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

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

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

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