Java Scanner资源管理:何时关闭与最佳实践
在Java开发中,Scanner 是处理输入流的常用工具,尤其适合解析基本类型和字符串。但它作为一个实现了 AutoCloseable 接口的资源类,如果使用不当,很容易引发资源泄露或程序异常。很多开发者尤其是初学者,经常搞不清什么时候该关闭 Scanner,什么时候不应该关闭。本文将结合实际场景,详细讲解 Scanner 的资源管理规则与最佳实践。
Scanner的资源特性
Scanner 本身并不直接持有系统底层资源,它的资源特性取决于它所包装的输入流。当它包装的是需要手动释放的资源(比如文件流、网络连接流)时,关闭 Scanner 会连带关闭底层的输入流;如果包装的是标准输入流 System.in,关闭 Scanner 同样会关闭 System.in,而标准输入流一旦关闭,后续再想从控制台读取输入就会直接抛出异常。
不同场景下的关闭策略
场景1:包装文件、网络等需要释放的资源流
当 Scanner 用来读取文件、网络连接等需要手动释放的资源时,必须在操作完成后关闭 Scanner,否则会导致底层资源无法释放,造成资源泄露。这种情况下推荐使用 try-with-resources 语法,它会自动帮我们关闭资源,不需要手动调用 close() 方法。
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ScannerFileExample {
public static void main(String[] args) {
// try-with-resources 自动管理Scanner资源,操作完成后自动关闭
try (Scanner scanner = new Scanner(new File("test.txt"))) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println("读取到内容:" + line);
}
} catch (FileNotFoundException e) {
System.out.println("文件未找到:" + e.getMessage());
}
// try块结束后,scanner会自动调用close(),底层文件流也会被关闭
}
}场景2:包装标准输入流System.in
标准输入流 System.in 是JVM启动时创建的全局资源,不需要也不应该被随意关闭。如果创建了包装 System.in 的 Scanner 并调用了 close() 方法,System.in 会被一并关闭,后续再使用新的 Scanner 包装 System.in 读取输入时,就会抛出 IllegalStateException,提示流已经关闭。
因此,对于包装 System.in 的 Scanner,通常不建议主动关闭,除非你明确知道程序后续不会再使用标准输入流。
import java.util.Scanner;
public class ScannerSystemInExample {
public static void main(String[] args) {
// 包装标准输入流的Scanner,不建议主动关闭
Scanner scanner = new Scanner(System.in);
System.out.println("请输入你的名字:");
String name = scanner.nextLine();
System.out.println("你好," + name);
// 错误示范:不要调用scanner.close(),否则System.in会被关闭
// scanner.close();
// 如果上面关闭了scanner,下面这行代码执行时会抛出异常
// Scanner newScanner = new Scanner(System.in);
// System.out.println("请再次输入:");
// System.out.println(newScanner.nextLine());
}
}场景3:包装字符串、字节数组等内存中的数据源
如果 Scanner 包装的是字符串(new Scanner("字符串内容"))或者字节数组等内存中的数据源,这些数据源本身不涉及系统底层资源,因此关闭 Scanner 与否影响不大。不过为了代码规范,还是建议在不再使用时调用 close() 方法,或者同样使用 try-with-resources 管理。
import java.util.Scanner;
public class ScannerStringExample {
public static void main(String[] args) {
String content = "Java Scanner 资源管理示例";
// 包装字符串的Scanner,关闭不影响其他资源,但建议规范关闭
try (Scanner scanner = new Scanner(content)) {
while (scanner.hasNext()) {
System.out.println("读取到单词:" + scanner.next());
}
}
// 自动关闭,无资源泄露风险
}
}最佳实践总结
- 当
Scanner包装文件、网络等需要手动释放的底层资源时,优先使用 try-with-resources 语法,避免手动关闭遗漏。 - 包装
System.in的Scanner不要主动调用close()方法,除非确定程序后续不会再用到标准输入流。 - 如果是在工具类或者长期运行的服务中创建包装
System.in的Scanner,建议将其作为单例使用,避免重复创建导致的不必要开销,同时也统一资源管理。 - 不要在多个
Scanner中包装同一个底层输入流,然后分别关闭,这样会导致第一个关闭的Scanner关闭底层流,后续其他Scanner无法使用。
常见错误示例与修正
下面是一个常见的错误案例:在读取完控制台输入后就关闭了 Scanner,导致后续输入失败。
import java.util.Scanner;
public class ScannerErrorExample {
public static void main(String[] args) {
Scanner scanner1 = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int num1 = scanner1.nextInt();
// 错误:关闭scanner1会连带关闭System.in
scanner1.close();
Scanner scanner2 = new Scanner(System.in);
System.out.println("请输入第二个数字:");
// 执行到这里会抛出异常,因为System.in已经被关闭
int num2 = scanner2.nextInt();
System.out.println("两个数字之和:" + (num1 + num2));
}
}修正后的代码:不主动关闭包装 System.in 的 Scanner,并且复用同一个 Scanner 实例。
import java.util.Scanner;
public class ScannerCorrectExample {
public static void main(String[] args) {
// 复用同一个Scanner实例,避免重复创建
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int num1 = scanner.nextInt();
System.out.println("请输入第二个数字:");
int num2 = scanner.nextInt();
System.out.println("两个数字之和:" + (num1 + num2));
// 程序结束前可以不关闭,或者如果确定不再使用,再关闭
// scanner.close();
}
}