为什么我的文件在一段时间未操作后会关闭?

12 投票
2 回答
806 浏览
提问于 2025-04-16 23:11

原始情况:

我现在正在开发的应用程序会接收来自另一个应用程序的通知,当某个特定文件有数据添加并准备好被读取时。目前我的代码大概是这样的:

class Foo(object):
    def __init__(self):
        self.myFile = open("data.txt", "r")
        self.myFile.seek(0, 2) #seeks to the end of the file

        self.mainWindow = JFrame("Foo",
                                 defaultCloseOperation = JFrame.EXIT_ON_CLOSE,
                                 size = (640, 480))
        self.btn = JButton("Check the file", actionPerformed=self.CheckFile)
        self.mainWindow.add(self.btn)
        self.mainWindow.visible = True

    def CheckFile(self, event):
        while True:
            line = self.myFile.readline()
            if not line:
                break
            print line

foo = Foo()

最终,CheckFile() 会在收到某个消息时被触发。现在,我是通过一个 JButton 来触发它的。

尽管在程序的其他地方没有对这个文件进行操作,而且我也没有用 with 来处理这个文件,但每当我尝试用 readline() 读取文件时,总是会出现 ValueError: I/O operation on closed file 的错误。

初步解决方案:

为了弄清楚文件到底是什么时候被关闭的,我把应用程序的代码改成了:

foo = Foo()
while True:
    if foo.myFile.closed == True:
        print "File is closed!"

但这样一来,问题就解决了!或者如果我改成:

foo = Foo()
foo.CheckFile()

那么最初的 CheckFile() 直接执行时是可以工作的。但是当我在大约 5 秒后点击按钮时,错误又出现了!

在把无限循环改成 pass 后,发现一切仍然正常,我得出的结论是,最开始在实例化一个 Foo 后,程序代码就结束了,foo 超出了作用域,因此 foo.myFile 也超出了作用域,文件被关闭了。尽管如此,Swing 仍然保持窗口打开,这导致我在尝试操作一个未打开的文件时出现错误。

我为什么还是困惑:

奇怪的是,如果 foo 已经超出了作用域,为什么 Swing 还能调用 foo.CheckFile() 呢?当我点击 JButton 时,难道不应该出现对象或方法不存在的错误,而是方法成功调用后在文件操作时出错吗?

我下一个想法是,也许当 JButton 尝试调用 foo.CheckFile() 时发现 foo 不再存在,它创建了一个新的 Foo,以某种方式跳过了它的 __init__ 方法,直接调用了 CheckFile()。但这似乎也不是情况。如果我修改 Foo.__init__ 让它接受一个参数,存储在 self.myNum 中,并在 CheckFile() 中打印出来,那么我在实例化初始对象时传入的值总是存在。这似乎表明 foo 根本没有超出作用域,这让我又回到了最开始的困惑!

编辑:整理了问题,添加了相关评论的信息,并删除了很多评论。

2 个回答

2

我觉得问题出在Java的内存分成了两种类型:堆内存和非堆内存。你的类实例foo会存储在堆内存里,而它的方法CheckFile则会加载到非堆内存的一个方法区里。当你的脚本执行完毕后,foo就没有其他地方再引用它了,所以它会被标记为可以被垃圾回收。而Swing界面仍然在引用CheckFile,所以它会被标记为正在使用中。我猜foo.myFile不是静态的,所以它也存储在堆内存里。至于Swing界面,只要窗口还开着并且在被窗口管理器更新,它就会被认为是正在使用的。


补充:我认为你用while True循环的解决方案是正确的。可以用这个循环来监控事件,当窗口关闭或者最后一行被读取时,退出循环,让程序结束。


补充2:另一种解决方案是让foo继承自JFrame,这样只要窗口还开着,Swing就会在它的主循环中保持对foo的引用。

4

* 初步的部分回答(添加到问题中) *

我想我刚刚搞明白了这个问题。在执行 foo = Foo() 之后,如果没有其他代码在运行,似乎这个对象就不再存在了,尽管应用程序仍然在运行,Swing窗口也在进行操作。

如果我这样做:

foo = Foo()
while True:
    pass

那么一切就会按照我预期的那样正常工作。

不过我还是有点困惑,为什么 foo.CheckFile() 能被调用。如果问题是 foo.myFile 超出了作用域并被关闭,那为什么 JButton 还能调用 foo.CheckFile() 呢?

也许其他人能给出更好的答案。

撰写回答