为什么 PrintStream.close() 最终被调用两次?

发布于 2024-07-19 23:04:54 字数 784 浏览 7 评论 0原文

令我惊讶的是,以下代码打印出“Close”两次。 通过调试器运行,似乎 MyPrintStream.close() 调用 super.close(),最终再次调用 MyPrintStream.close()

    
import java.io.*;

public class PrintTest
{
    static class MyPrintStream extends PrintStream
    {
        MyPrintStream(OutputStream os)
        {
            super(os);
        }

        @Override
        public void close()
        {
            System.out.println("Close");
            super.close();
        }
    }

    public static void main(String[] args) throws IOException
    {
        PrintStream ps = new MyPrintStream(new FileOutputStream(File.createTempFile("temp", "file")));
        ps.println("Hello");
        ps.close();
    }
}

为什么会发生这种情况? 我不应该扩展 PrintStream 吗?

Somewhat to my surprise, the following code prints out "Close" twice. Running through the debugger, it seems MyPrintStream.close() calls super.close(), which ends up calling MyPrintStream.close() again.

    
import java.io.*;

public class PrintTest
{
    static class MyPrintStream extends PrintStream
    {
        MyPrintStream(OutputStream os)
        {
            super(os);
        }

        @Override
        public void close()
        {
            System.out.println("Close");
            super.close();
        }
    }

    public static void main(String[] args) throws IOException
    {
        PrintStream ps = new MyPrintStream(new FileOutputStream(File.createTempFile("temp", "file")));
        ps.println("Hello");
        ps.close();
    }
}

Why is this happening? Should I not be extending PrintStream?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

GRAY°灰色天空 2024-07-26 23:04:54

如果您在调试器中查看代码并在 close() 方法中设置断点,它将显示正在调用您的 close( ) 方法:

  1. 您的主要方法
  2. sun.nio.cs.StreamEncoder$CharsetSE.implClose() 第 431 行

后者的完整堆栈跟踪如下所示:

PrintTest$MyPrintStream.close() line: 20    
sun.nio.cs.StreamEncoder$CharsetSE.implClose() line: 431 [local variables unavailable]  
sun.nio.cs.StreamEncoder$CharsetSE(sun.nio.cs.StreamEncoder).close() line: 160 [local variables unavailable]    
java.io.OutputStreamWriter.close() line: 222 [local variables unavailable]  
java.io.BufferedWriter.close() line: 250 [local variables unavailable]  
PrintTest$MyPrintStream(java.io.PrintStream).close() line: 307  
PrintTest$MyPrintStream.close() line: 20    
PrintTest.main(java.lang.String[]) line: 27 

遗憾的是,我无法告诉 为什么 StreamEncoder会回调到您的 PrintStream 中,因为我的 IDE 没有 sun.nio.cs.StreamEncoder 的源附件:( 顺便说一句,如果这很重要,这是 JDK 6。

顺便说一句,如果您问这个问题是因为您注意到 close() 方法中的自定义代码运行了两次,您确实应该检查 this.writing 是否。 close() 将其设置为 true (并且类的注释状态 /* 以避免递归关闭 */ )。

If you look at your code in a debugger and set a breakpoint in the close() method, it'll reveal the stacktraces of who is calling your close() method:

  1. your main method
  2. sun.nio.cs.StreamEncoder$CharsetSE.implClose() line 431

the complete stacktrace for the latter looks like:

PrintTest$MyPrintStream.close() line: 20    
sun.nio.cs.StreamEncoder$CharsetSE.implClose() line: 431 [local variables unavailable]  
sun.nio.cs.StreamEncoder$CharsetSE(sun.nio.cs.StreamEncoder).close() line: 160 [local variables unavailable]    
java.io.OutputStreamWriter.close() line: 222 [local variables unavailable]  
java.io.BufferedWriter.close() line: 250 [local variables unavailable]  
PrintTest$MyPrintStream(java.io.PrintStream).close() line: 307  
PrintTest$MyPrintStream.close() line: 20    
PrintTest.main(java.lang.String[]) line: 27 

Sadly though I can't tell why StreamEncoder would call back into your PrintStream though, as my IDE doesn't have a source attachment for sun.nio.cs.StreamEncoder :( This is JDK 6 btw, if that matters.

By the way, if you are asking this question because you noticed that custom code in your close() method is running twice, you should really be checking if this.closing. PrintStream.close() sets this to true (and the class's comments state /* To avoid recursive closing */ ).

橘和柠 2024-07-26 23:04:54

看一下PrintStream的源码。

它有两个对底层 Writer textOutcharOut 的引用,一个基于字符,一个基于文本(无论这意味着什么)。 此外,它还继承了对基于字节的 OutputStream 的第三个引用,称为 out

/**
 * Track both the text- and character-output streams, so that their buffers
 * can be flushed without flushing the entire stream.
 */
private BufferedWriter textOut;
private OutputStreamWriter charOut;

close() 方法中,它关闭所有这些(textOutcharOut 基本相同)。

 private boolean closing = false; /* To avoid recursive closing */

/**
 * Close the stream.  This is done by flushing the stream and then closing
 * the underlying output stream.
 *
 * @see        java.io.OutputStream#close()
 */
public void close() {
synchronized (this) {
    if (! closing) {
    closing = true;
    try {
        textOut.close();
        out.close();
    }
    catch (IOException x) {
        trouble = true;
    }
    textOut = null;
    charOut = null;
    out = null;
    }
}
}

现在,有趣的部分是 charOut 包含一个对 PrintStream 本身的引用(包装)(注意构造函数中的 init(new OutputStreamWriter(this)) ),

private void init(OutputStreamWriter osw) {
   this.charOut = osw;
   this.textOut = new BufferedWriter(osw);
}

/**
 * Create a new print stream.
 *
 * @param  out        The output stream to which values and objects will be
 *                    printed
 * @param  autoFlush  A boolean; if true, the output buffer will be flushed
 *                    whenever a byte array is written, one of the
 *                    <code>println</code> methods is invoked, or a newline
 *                    character or byte (<code>'\n'</code>) is written
 *
 * @see java.io.PrintWriter#PrintWriter(java.io.OutputStream, boolean)
 */
public PrintStream(OutputStream out, boolean autoFlush) {
this(autoFlush, out);
init(new OutputStreamWriter(this));
}

因此,调用 close( ) 将调用 charOut.close(),后者又调用原来的 close(),这就是为什么我们有关闭标志来缩短无限递归。

Take a look at PrintStream's source.

It has two references to the underlying Writer textOut and charOut, one character-base, and one text-based (whatever that means). Also, it inherits a third reference to the byte-based OutputStream, called out.

/**
 * Track both the text- and character-output streams, so that their buffers
 * can be flushed without flushing the entire stream.
 */
private BufferedWriter textOut;
private OutputStreamWriter charOut;

In the close() method it closes all of them (textOut is basically the same as charOut).

 private boolean closing = false; /* To avoid recursive closing */

/**
 * Close the stream.  This is done by flushing the stream and then closing
 * the underlying output stream.
 *
 * @see        java.io.OutputStream#close()
 */
public void close() {
synchronized (this) {
    if (! closing) {
    closing = true;
    try {
        textOut.close();
        out.close();
    }
    catch (IOException x) {
        trouble = true;
    }
    textOut = null;
    charOut = null;
    out = null;
    }
}
}

Now, the interesting part is that charOut contains a (wrapped) referenced to the PrintStream itself (note the init(new OutputStreamWriter(this)) in the constructor )

private void init(OutputStreamWriter osw) {
   this.charOut = osw;
   this.textOut = new BufferedWriter(osw);
}

/**
 * Create a new print stream.
 *
 * @param  out        The output stream to which values and objects will be
 *                    printed
 * @param  autoFlush  A boolean; if true, the output buffer will be flushed
 *                    whenever a byte array is written, one of the
 *                    <code>println</code> methods is invoked, or a newline
 *                    character or byte (<code>'\n'</code>) is written
 *
 * @see java.io.PrintWriter#PrintWriter(java.io.OutputStream, boolean)
 */
public PrintStream(OutputStream out, boolean autoFlush) {
this(autoFlush, out);
init(new OutputStreamWriter(this));
}

So, the call to close() will call charOut.close(), which in turn calls the original close() again, which is why we have the closing flag to cut short the infinite recursion.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文