8

我想使用 Haskell 函数

readFile :: FilePath -> IO String

将文件的内容读入字符串。在文档中,我读到“文件是根据需要延迟读取的,就像 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. 用于读取文件的句柄已关闭。

如果不是这种情况:

  • s执行时包含什么putStrLn
  • 执行时文件句柄处于什么状态putStrLn
  • 如果 when putStrLnis executeds不包含文件的全部内容,什么时候会真正读取这个内容,什么时候会关闭文件?
4

2 回答 2

9

我的第三个陈述正确吗?

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

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

那么,我可以自己调用readFile而不关闭文件句柄吗?

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

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

不完全是,readFile将文件句柄置于半关闭状态,在文档中进行了描述hGetContents

计算hGetContents hdl返回对应于 管理的通道或文件的未读部分的字符列表,将hdl其置于中间状态,半关闭。在这种状态下,实际上是关闭的,但项目会按需hdl读取并累积在由返回的特殊列表中hdlhGetContents hdl.


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

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

readFile我将 的行为与的行为混淆了

bracket (openFile file ReadMode) hClose hGetContents

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

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

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

不,s除了可能从文件句柄中获取一些字符的配方外,还没有任何内容。文件句柄是半封闭的,但不是封闭的。s当文件内容被完全读取或超出范围时,它将关闭。

如果不是这种情况:

  • s执行时包含什么putStrLn
  • 执行时文件句柄处于什么状态putStrLn
  • 如果 when putStrLnis executeds不包含文件的全部内容,什么时候会真正读取这个内容,什么时候会关闭文件?

前两个问题已经回答了,第三个问题的答案是“内容被消费时会读取文件”,当内容全部被读取或不再被引用时会关闭。

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

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

这是一个很多人(甚至大多数人)一次又一次陷入的陷阱,但是在被它咬伤之后,人们很快就会知道什么时候 IO 需要非懒惰并在这些情况下非懒惰地去做。

替代方案(迭代器、枚举器、管道、管道......)避免了这些陷阱[除非实现者犯了错误],但在惰性 IO 完全没问题的情况下使用起来就不太好用了。另一方面,他们更好地处理不希望懒惰的情况。

于 2012-12-13T21:27:57.393 回答
5

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

您需要考虑一下您在这里使用惰性 IO 的事实。从文件中读取只会创建一个未经评估的字符串计算,如果以后需要,它将读取文件。

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

Once the last character of your file has been read, or all references to the open file are dropped (e.g. your s value), your open file will be closed by the garbage collector.

于 2012-12-13T21:54:40.440 回答