9

我有三个问题。

为了解释,我正在审查某人的代码,并注意到BufferedReaders 有时没有被关闭。通常,Eclipse 会发出警告,指出这是一个潜在的内存泄漏(我修复了它)。但是,在 Callable 内部类中,没有警告。

class outerClass {
    ...
    public void someMethod() {
        Future<Integer> future = outputThreadPool.submit(new innerClass(this.myProcess.getInputStream(), threadName));
        ...
    }

    class innerClass implements Callable<Integer> {
        private final InputStream stream;
        private final String prepend;

        innerClass(InputStream stream, String prepend) {
            this.stream = stream;
            this.prepend = prepend;
        }

        @Override
        public Integer call() {
            BufferedReader stdOut = new BufferedReader(new InputStreamReader(stream));
            String output = null;
            try {
                while ((output = stdOut.readLine()) != null) {
                    log.info("[" + prepend + "] " + output);
                }

            } catch (IOException ignore) {
            // I have no idea why we're ignoring this... :-|        
            }
            return 0;   
        }
    }
}

编写代码的人都是经验丰富的 Java 开发人员,所以我的第一个想法是这是故意的……但可能是他们在编写代码时很着急而忽略了它。

我的问题是:

  1. 为什么 Eclipse 没有突出显示这一点(可以通过以下问题的答案来回答)?

  2. 如果它在 call() 方法中关闭,可能发生的最坏情况是什么?(我想不出一个很好的理由......我一直在寻找一段时间......但也许是故意不关闭 BufferedReader)

  3. 如果 BufferedReader没有在内部类中关闭,可能发生的最坏情况是什么?

4

5 回答 5

7

我会说,因为他们是BufferedReader围绕给定创建一个InputStream,所以代码是安全的,不调用close(). 调用的代码close()应该始终是创建流并使用 try/finally 完成的代码。

public static void read(String str) throws IOException {
    FileInputStream stream = null
    try {
        stream = new FileInputStream(str);
        readStreamToConsole(stream);
    } finally {
        if (stream != null)
            stream.close();
    }
}

private static void readStreamToConsole(InputStream stream) {
    BufferedReader stdOut = new BufferedReader(new InputStreamReader(stream));
    String output = null;
    while ((output = stdOut.readLine()) != null)
        System.out.println(output);
}

另一个注意事项:您的代码似乎正在记录其他进程的输出。无论如何,您可能无法关闭流。如果不自己进行测试,我不确定如果您关闭来自另一个进程的流会发生什么。

哦,这IOException不太可能发生,因为流来自另一个进程。除非发生一些不可恢复的错误,否则这不太可能发生。不过,以某种方式记录异常仍然不是一个坏主意。


编辑以解决您对混合答案的评论:

让我们使用一个输出流,BufferedWriter这次作为一个例子:

private static final String NEWLINE = System.getProperty("line.separator");

public static void main(String[] args) throws IOException {
    String file = "foo/bar.txt";
    FileOutputStream stream = null;
    try {
        stream = new FileOutputStream(file);
        writeLine(stream, "Line 1");
        writeLine(stream, "Line 2");
    } finally {
        if (stream != null)
            stream.close();
    }
}

private static void writeLine(OutputStream stream, String line) throws IOException {
    BufferedWriter writer = new BufferedWriter(new InputStreamWriter(stream));
    writer.write(line + NEWLINE);
}

这行得通。writeLine 方法用作创建writer和实际写入line文件的委托。当然,这个逻辑可能更复杂一些,比如把一个对象变成 aString并写出来。这也使该main方法更易于阅读。

现在,如果我们关闭 BufferedWriter 会怎样?

private static void writeLine(OutputStream stream, String line) throws IOException {
    BufferedWriter writer = null;
    try {
        writer = new BufferedWriter(new InputStreamWriter(stream));
        writer.write(line + NEWLINE);
    } finally {
        if (writer != null)
            writer.close();
    }
}

尝试用它运行它,每次第二次writeLine调用都会失败。始终在创建流的位置而不是通过的位置关闭流是一种很好的做法。最初可能没问题,但稍后尝试更改该代码可能会导致错误。如果我开始时只writeLine使用错误的方法进行了 1 次调用,而其他人想要添加第二次调用,他们将不得不重构代码以便writeLine无论如何都不会关闭流。获得近距离的快乐可能会导致头痛。

另请注意,从技术上讲,这BufferedWriter不是系统资源的实际句柄,FileOutputStream而是,所以无论如何您都应该关闭实际资源。

所以,经验法则:只在你创建它们的地方关闭你的流,并且总是在 try/finally 块(或 Java 7 很棒的try/resource 块,它为你关闭)中进行创建和关闭。

于 2012-08-30T18:50:38.503 回答
2

BufferedReader在这种情况下,您可能不想关闭。传递给构造函数的InputStream是可能与系统资源相关联的对象。theBufferedReader和 theInputStreamReader只是围绕它的包装。关闭BufferedReader也会关闭InputStream,这可能不是调用者想要的。

于 2012-08-30T18:53:02.820 回答
1

BuffereddReader关闭()

关闭此流并释放与其关联的任何系统资源。如果流已经关闭,则调用此方法无效。

因此,如果您不关闭(),系统资源可能仍与阅读器相关联,这可能会导致内存泄漏。

为什么 eclipse 不高亮:如果忽略调用 close() 不是编译时错误,所以 eclipse 不高亮。

于 2012-08-30T18:38:03.550 回答
1

无论您在哪里打开流,都应该在 finally 块中关闭它,并且测试流也是一个好习惯null,因为如果文件不存在,流将是null,异常将被抛出(FileNotFoundException)但 finally博克完成了。即调用方法的内容应该是:

   BufferedReader stdOut = null;
   String output = null;
        try {
            stdOut = new BufferedReader(new InputStreamReader(stream));
            while ((output = stdOut.readLine()) != null) {
                log.info("[" + prepend + "] " + output);
            }
        } catch (FileNotFoundException ex) {
            log.warn("Unable to open nonexisten file " + whichOne);  
        } catch (IOException ex) {
            log.warn("Unable to read from stream");  
        } finally {
            if (stdOut != null) {
                try {
                   stdOut.close();
                } catch (IOException e) {
                    log.warn("Unable to close the stream");
                }
            }
        }
        return 0;

或者,如果您被允许使用 Java 7,您可以AutoCloseable为此目的使用接口和新语言结构的优势。请参阅http://www.oracle.com/technetwork/articles/java/trywithresources-401775.html

于 2012-08-30T18:51:31.593 回答
1
  1. 不突出显示,这是真的,因为可以在调用方法之外的其他地方关闭流。

  2. 如果它在调用方法中关闭,其他线程可能正在使用它。

  3. 没有BufferdRreader什么,但如果你失去对流的引用并且无法关闭它,这会导致内存泄漏。

于 2012-08-30T18:56:59.093 回答