1. Scanner 概述
在本教程中,我们将介绍 Java 中的 Scanner 类,它用于读取输入、查找和跳过模式,支持多种分隔符。
2. 扫描文件内容
首先来看如何使用 Scanner
读取一个文件。
在下面的例子中,我们读取了一个包含 “Hello world” 的文件,并将其拆分为多个 token:
@Test
public void whenReadFileWithScanner_thenCorrect() throws IOException{
Scanner scanner = new Scanner(new File("test.txt"));
assertTrue(scanner.hasNext());
assertEquals("Hello", scanner.next());
assertEquals("world", scanner.next());
scanner.close();
}
注意:next()
方法会返回下一个字符串 token。
同时,记得在使用完毕后调用 scanner.close()
关闭资源。
3. 将 InputStream 转换为 String
接着来看如何使用 Scanner
将 InputStream
转换为 String
:
@Test
public void whenConvertInputStreamToString_thenConverted()
throws IOException {
String expectedValue = "Hello world";
FileInputStream inputStream
= new FileInputStream("test.txt");
Scanner scanner = new Scanner(inputStream);
scanner.useDelimiter("\\A");
String result = scanner.next();
assertEquals(expectedValue, result);
scanner.close();
}
这里我们使用了 useDelimiter("\\A")
来将整个输入流当作一个 token 读取,其中 \A
表示匹配整个输入的开始。
4. Scanner 与 BufferedReader 的区别
现在我们来对比一下 Scanner
和 BufferedReader
的区别:
✅ BufferedReader 通常用于按行读取输入
✅ Scanner 更适合按 token 读取输入
下面是使用 BufferedReader
按行读取文件的示例:
@Test
public void whenReadUsingBufferedReader_thenCorrect()
throws IOException {
String firstLine = "Hello world";
String secondLine = "Hi, John";
BufferedReader reader
= new BufferedReader(new FileReader("test.txt"));
String result = reader.readLine();
assertEquals(firstLine, result);
result = reader.readLine();
assertEquals(secondLine, result);
reader.close();
}
而使用 Scanner
按 token 读取同一个文件:
@Test
public void whenReadUsingScanner_thenCorrect()
throws IOException {
String firstLine = "Hello world";
FileInputStream inputStream
= new FileInputStream("test.txt");
Scanner scanner = new Scanner(inputStream);
String result = scanner.nextLine();
assertEquals(firstLine, result);
scanner.useDelimiter(", ");
assertEquals("Hi", scanner.next());
assertEquals("John", scanner.next());
scanner.close();
}
注意:我们使用了 nextLine()
方法来读取整行,而 next()
会根据分隔符拆分 token。
5. 从控制台读取输入
使用 new Scanner(System.in)
可以从控制台读取用户输入:
@Test
public void whenReadingInputFromConsole_thenCorrect() {
String input = "Hello";
InputStream stdin = System.in;
System.setIn(new ByteArrayInputStream(input.getBytes()));
Scanner scanner = new Scanner(System.in);
String result = scanner.next();
assertEquals(input, result);
System.setIn(stdin);
scanner.close();
}
我们使用 System.setIn(...)
模拟控制台输入。
5.1. nextLine() 方法
该方法读取当前行的所有内容(不包括行尾的换行符):
scanner.nextLine();
读取完后,Scanner
的位置会跳到下一行。⚠️ 重要:nextLine()
会消费掉换行符。
5.2. nextInt() 方法
此方法读取下一个整数 token:
scanner.nextInt();
⚠️ 注意:nextInt()
不会消费换行符。如果后面接着调用 nextLine()
,它会读取到前面留下的换行符,导致跳过用户输入。
6. 输入验证
使用 Scanner
的方法可以轻松验证输入是否为特定类型。例如,使用 hasNextInt()
检查输入是否为整数:
@Test
public void whenValidateInputUsingScanner_thenValidated()
throws IOException {
String input = "2000";
InputStream stdin = System.in;
System.setIn(new ByteArrayInputStream(input.getBytes()));
Scanner scanner = new Scanner(System.in);
boolean isIntInput = scanner.hasNextInt();
assertTrue(isIntInput);
System.setIn(stdin);
scanner.close();
}
7. 扫描字符串
Scanner
也可以直接扫描字符串:
@Test
public void whenScanString_thenCorrect()
throws IOException {
String input = "Hello 1 F 3.5";
Scanner scanner = new Scanner(input);
assertEquals("Hello", scanner.next());
assertEquals(1, scanner.nextInt());
assertEquals(15, scanner.nextInt(16)); // 读取十六进制
assertEquals(3.5, scanner.nextDouble(), 0.00000001);
scanner.close();
}
8. 查找模式
使用 findInLine()
可以在当前行查找匹配的模式:
@Test
public void whenFindPatternUsingScanner_thenFound() throws IOException {
String expectedValue = "world";
FileInputStream inputStream = new FileInputStream("test.txt");
Scanner scanner = new Scanner(inputStream);
String result = scanner.findInLine("wo..d");
assertEquals(expectedValue, result);
scanner.close();
}
也可以使用 findWithinHorizon()
在指定字符范围内查找:
@Test
public void whenFindPatternInHorizon_thenFound()
throws IOException {
String expectedValue = "world";
FileInputStream inputStream = new FileInputStream("test.txt");
Scanner scanner = new Scanner(inputStream);
String result = scanner.findWithinHorizon("wo..d", 5);
assertNull(result);
result = scanner.findWithinHorizon("wo..d", 100);
assertEquals(expectedValue, result);
scanner.close();
}
🔍 搜索范围是字符数,不是行数。
9. 跳过模式
使用 skip()
方法可以跳过匹配特定模式的 token:
@Test
public void whenSkipPatternUsingScanner_thenSkipped()
throws IOException {
FileInputStream inputStream = new FileInputStream("test.txt");
Scanner scanner = new Scanner(inputStream);
scanner.skip(".e.lo");
assertEquals("world", scanner.next());
scanner.close();
}
10. 修改 Scanner 分隔符
默认分隔符是空白字符,可以通过 useDelimiter()
修改:
@Test
public void whenChangeScannerDelimiter_thenChanged()
throws IOException {
String expectedValue = "Hello world";
String[] splited = expectedValue.split("o");
FileInputStream inputStream = new FileInputStream("test.txt");
Scanner scanner = new Scanner(inputStream);
scanner.useDelimiter("o");
assertEquals(splited[0], scanner.next());
assertEquals(splited[1], scanner.next());
assertEquals(splited[2], scanner.next());
scanner.close();
}
还可以使用多个分隔符:
@Test
public void whenReadWithScannerTwoDelimiters_thenCorrect()
throws IOException {
Scanner scanner = new Scanner(new File("test.txt"));
scanner.useDelimiter(",|-");
assertEquals("John", scanner.next());
assertEquals("Adam", scanner.next());
assertEquals("Tom", scanner.next());
scanner.close();
}
11. 处理 NoSuchElementException
11.1. 异常说明
NoSuchElementException
通常在尝试读取不存在的元素时抛出。
11.2. 复现异常
下面的例子展示了两个 Scanner
实例共享同一个 InputStream
时的问题:
public void givenClosingScanner_whenReading_thenThrowException() throws IOException {
final FileInputStream inputStream = new FileInputStream("src/test/resources/test_read.in");
final Scanner scanner = new Scanner(inputStream);
scanner.next();
scanner.close();
final Scanner scanner2 = new Scanner(inputStream);
scanner2.next();
scanner2.close();
}
运行时会抛出:
java.util.NoSuchElementException
11.3. 原因分析
当 Scanner
被关闭时,如果其输入源实现了 Closeable
,那么输入源也会被关闭。
11.4. 解决方案
✅ 避免多个 Scanner
共享同一个资源。推荐使用单个 Scanner
实例读取,最后再关闭。
12. nextLine() 与 nextXXX() 的坑
常见问题:nextInt()
等方法不会消费换行符,导致后续 nextLine()
被跳过:
Scanner scanner = new Scanner(System.in);
System.out.print("Enter your age: ");
int age = scanner.nextInt();
System.out.print("Enter your first name: ");
String firstName = scanner.nextLine(); // ❌ 被跳过
✅ 正确做法是插入一个 nextLine()
消费换行符:
int age = scanner.nextInt();
scanner.nextLine(); // 消费换行符
String firstName = scanner.nextLine(); // ✅ 正常读取
或者直接统一使用 nextLine()
并手动转换类型:
int age = Integer.parseInt(scanner.nextLine());
String firstName = scanner.nextLine();
或者使用 skip()
跳过换行符:
int age = scanner.nextInt();
scanner.skip("\\R"); // 跳过换行符
String firstName = scanner.nextLine();
13. 总结
在本教程中,我们通过多个示例讲解了 Java 中 Scanner
类的常见用法:
- ✅ 从文件、控制台或字符串中读取输入
- ✅ 查找和跳过特定模式
- ✅ 自定义分隔符
- ✅ 避免常见的坑,如
NoSuchElementException
和换行符问题
所有示例代码可在 GitHub 找到。