Java 使用扫描仪时出现资源泄漏警告

Java 使用扫描仪时出现资源泄漏警告,java,Java,我正在观看一些java教程,并想知道使用Scanner时的资源泄漏警告 我知道我可以关闭它,但视频中的人即使使用完全相同的代码也没有收到警告,为什么 Scanner input= new Scanner(System.in); System.out.print("Enter a line of text: "); String line = input.nextLine(); System.out.println("

我正在观看一些java教程,并想知道使用Scanner时的资源泄漏警告

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

    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将关闭底层流系统。就其本身而言,您不希望这样做:这不是您的责任,事实上会积极地损害事物;现在你再也不能从sysin中读取了

问题是,IDE不能真正知道这一点。潜在的问题是System.in在很多方面都是设计极其糟糕的API,但是[A]它已经有30年的历史了,那时要知道这一点要困难得多;我们现在知道这一点是因为事后诸葛亮,[B]oracle还没有着手制作第二个版本的sysin/out/err API,而且它也没有被提上议事日程

这就给IDE带来了麻烦:设置一些使用资源的模式和规则相对容易,这样“你创建的过滤器围绕着你没有创建的资源”就不会有任何问题,但是如果不编写一个小框架,你就不能将它们与sysin/err/out一起使用,这对新手来说要求有点高。假设告诉那些在java编码中迈出第一步的人先下载一些第三方库来清理sysin/out/err交互,这也不是一个好主意

因此,我们处于困境。IDE不应该对此发出警告,但他们很难发现这是一个异国情调的场景,在这个场景中,您有一个您制作的资源,但您不需要关闭,事实上,不应该关闭

您可以关闭“未关闭资源”的设置,这无疑是视频教程所做的,但这是一个有用的警告。只是被这个愚蠢的、毫无意义的老API所阻碍

一些深入的解释 有资源。这些都是实现自动关闭的东西,而且有很多。让我们关注那些表示I/O的类型:层次结构中的顶级类型是Writer、Reader、InputStream和OutputStream,让我们称它们为WRIO。它们都是自动关闭的,而且大多数都是错误的?所有人都抱怨这些资源未关闭。然而,这过于简单化了

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

实际资源它们直接代表了一个基于操作系统的基本概念,如果不关闭它们,就会导致某些资源的匮乏,也称为“泄漏”。新的FileInputStream、socket.getInputStream-等等,它们代表实际的资源 虚拟资源-它们的行为类似于一种资源,但实际上并不代表一种您可以饿死的资源,这种资源还没有被垃圾收集器修复。新ByteArrayInputStream,将StringBuilder转变为读者,等等。 过滤器-这些过滤器环绕资源并“在传输中”对其进行修改。这样的过滤器本身不会捕获任何不可用的资源。如果关闭它们,它们也会对它们所包装的对象调用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));
这是一个真实的资源,由一个过滤器WRIO InputStreamReader包装,然后该过滤器由另一个过滤器WRIO包装:那么资源泄漏都是关于raw的,如果您无法安全地关闭br,那就不是资源泄漏。如果在不关闭/刷新的情况下关闭raw,则可能是一个错误br首先,缓冲区中的大量字节不会被写入,但不会导致资源泄漏。关于无法关闭br的IDE警告是错误的,但也不是太有害,因为您可以在其周围的资源中进行尝试,这也保证了“由于未能清除缓冲过滤器WRIO而导致的错误”不再发生

然后是问题案例:

制作一个过滤器WRIO,它围绕着一个您没有制作的资源,并且没有关闭的责任:您不应该主动关闭这些过滤器WRIO,因为这将最终关闭底层资源,而您不希望这样做

在这里,IDE警告是非常糟糕和恼人的,但它非常重要 IDE很难实现这一点

设计方案 通常情况下,您可以通过从不进入该场景来解决此问题。例如,System.in应该有更好的API;此API看起来像:

try (Scanner s = System.newStandardIn()) {
   // use scanner here
}
并且具有关闭s不关闭系统的特性。它本身几乎什么也不做;设置一个布尔标志,以便在执行任何进一步的读取调用时抛出异常,或者甚至可能什么都不做。现在IDE警告充其量是过于热情了,但是听从它的建议并安全地关闭扫描器现在不再积极地在代码中引入bug

不幸的是,那个好的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警告,也没有bug。

因为代码编辑器中显示的警告取决于您使用的IDE和配置方式,而编译期间可能得到的警告取决于您用于编译的参数。我们都使用Eclipse,所以我想我们有不同的设置,谢谢您的回答!在Eclipse中,您可以在Java->Compiler->Errors/Warnings下的首选项中进行此配置。我认为默认情况下资源泄漏问题设置为警告,但您也可以告诉eclipse忽略它们,甚至让它们产生一个硬错误。很高兴知道这一点,再次感谢,我非常感谢!tldr至原因:您应该关闭打开的资源scanner.close-或者更好,请尝试使用资源。但是,在这种特定情况下,不应关闭它,而应抑制警告。关闭扫描仪将关闭基础资源,不应关闭System.in。在其他情况下,例如,当你从一个文件中读取时,你应该关闭它。非常广泛的答案,包含大量的见解和细节,做得好!这是非常翔实的,非常感谢你花时间写这篇文章!