使用扫描程序时出现资源泄漏警告



我正在观看一些Java教程,想知道使用扫描仪时的资源泄漏警告。

我知道我可以关闭它,但是视频中的人即使使用完全相同的代码也没有收到警告,为什么会这样?

Scanner input= new Scanner(System.in);

System.out.print("Enter a line of text: ");
String line = input.nextLine();

System.out.println("You entered: " + line);

//input.close();

实际问题

这很复杂。一般规则是,无论谁创建资源,都必须安全地关闭它,并且由于您的代码生成了扫描程序,因此 IDE 会告诉您:嘿,您应该关闭它。

问题是,关闭该扫描程序在这里是错误的:扫描程序环绕System.in这不是您创建的资源,但scanner.close()会关闭底层流(System.in本身),您不希望这样:这不是您的责任,实际上会主动伤害事物; 现在您再也无法从 sysin 读取

。问题是,IDE 无法真正知道这一点。根本问题是System.in在很多方面都是设计得非常糟糕的 API,但 [A] 它已有 30 年的历史,当时要知道这一点要困难得多;由于事后诸觉,我们现在知道了,[B] Oracle 还没有开始制作 sysin/out/err API 的第二个版本,而且它在议程上并不重要。

这给 IDE 带来了麻烦:设置一些使用资源的模式和规则相对容易,这样你就不会遇到任何问题,这个"你创建的过滤器环绕着你没有创建的资源",但你不能在不编写一点框架的情况下将它们与 sysin/err/out 一起使用,这对新手来说有点苛刻。告诉那些迈出 Java 编码第一步的人首先去下载一些清理 sysin/out/err 交互的第三方库,这大概也不是一个好主意。

因此,我们陷入了困境。IDE 不应该对此发出警告,但他们很难检测到这是一个异国情调的场景,您有一个您制作的资源,但您不需要关闭,实际上也不应该关闭。

您可以关闭"未关闭的资源"的设置,毫无疑问,视频教程确实如此,但这是一个有用的警告。只。。受到这个愚蠢的旧API的阻碍,不再有意义。

一些深入的解释

有资源。这些是实现AutoClosable的东西,而且有很多。让我们关注那些表示 I/O 事物的事物:层次结构中的顶级类型是 Writer、Reader、InputStream 和 OutputStream(我们称它们为所有 WRIO)。它们都是可自动关闭的,大多数 IDE(不正确?)都抱怨这些资源未关闭。然而,这过于简单化了。

您可以将所有WRIO的世界分为:

  • 实际资源(它们直接表示基于操作系统的底层概念,如果不关闭它们,则会导致某些资源匮乏,也称为"泄漏")。new FileInputStreamsocket.getInputStream等,这些代表实际资源
  • 虚拟资源 - 它们的作用类似于资源,但实际上并不代表垃圾回收器尚未修复的可以饿死的资源。new ByteArrayInputStream,将字符串生成器转换为读取器等。
  • 过滤器 - 这些过滤器环绕资源并"在传输中"修改它。此类过滤器本身不会捕获任何饥饿的资源。如果你close()它们,它们也会调用它们包装的东西的关闭。扫描仪是一个过滤器。

关闭它们的规则归结为:

  • 实际资源 - 必须由制造它们的人安全关闭。如果您未能执行此操作,则保证发出 IDE 警告。请注意,您没有制作System.in,因此不适用于那里。
  • 虚拟资源 - 您可以关闭它们,但您不必这样做。如果 IDE 警告它们,请围绕它抛出一个资源尝试,这很烦人,但不太难解决。
  • 过滤器 - 棘手。

过滤器的问题

如果它是提供给你的过滤器,目的是关闭它:

BufferedReader br = Files.newBufferedReader(somePath);

那么关闭br失败就是资源泄漏;IDE 警告是有保证的。

如果它是你做的一个过滤器,包裹在你也做的WRIO上:

InputStream raw = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(raw, StandardCharsets.UTF_8));

(这是 1 个真正的资源,由过滤器 WRIO(输入流读取器)包装,然后该过滤器由另一个过滤器 WRIO 包装): 那么资源泄漏就是关于raw的,如果你未能安全地关闭 br,那就没有资源泄漏。这可能是一个错误(如果您在不先关闭/刷新 br 的情况下关闭原始文件,则缓冲区中的一堆字节不会被写出),但不是资源泄漏。关于无法关闭br的 IDE 警告是错误的,但不会太有害,因为您可以围绕它抛出尝试资源,这也顺便保证了"由于未能冲洗缓冲过滤器 WRIO 而导致的错误"不会再发生。

然后,有问题的情况:

创建一个过滤器WRIO,它包裹着你没有做的资源,也没有责任关闭:你应该主动不要关闭这些过滤器WRIO,因为这最终会关闭底层资源,而你不希望这样。

在这里,IDE 警告是有害和烦人的,但 IDE 很难意识到这一点。

设计解决方案

通常,您可以通过永远不进入该场景来解决此问题。例如,System.in 应该有更好的 API;此 API 如下所示:

try (Scanner s = System.newStandardIn()) {
// use scanner here
}

并且具有关闭 s不会关闭本身 System.in 的属性(它几乎什么都不做;设置一个布尔标志以在完成任何进一步读取调用时抛出异常,或者甚至可能什么都不做)。现在,IDE 警告充其量是过于热心,但听从它的建议并安全地关闭扫描程序现在不再主动在代码中引入错误。

不幸的是,那个不错的 API 还不存在(还?因此,我们陷入了这种烦人的场景,即由于糟糕的 API 设计,有用的 IDE 警告系统会主动误导您。如果你真的想,你可以写它:

public static Scanner newStandardIn() {
Scanner s = new Scanner(System.in) {
@Override public void close() {}
};
// hey, while we're here, lets fix
// another annoying wart!
s.useDelimiter("r?n");
return s;
}

现在,您可以通过遵循其建议来注意这些警告:

public static void main(String[] args) {
String name;
int age;
try (Scanner s = newStandardIn()) {
System.out.print("What is your name: ");
// use next() to read entire lines -
// that useDelimiter fix made this possible
name = s.next();
System.out.print("What is your age: ");
age = s.nextInt();
}
// use name and age here
}

没有IDE警告,也没有错误。

最新更新