Java Scanner – Java扫描器

最后修改: 2019年 12月 21日

中文/混合/英文(键盘快捷键:t)

1. Overview of Scanner

1.扫描器的概述

In this quick tutorial, we’ll illustrate how to use the Java Scanner class – to read input, find and skip patterns with different delimiters.

在这个快速教程中,我们将说明如何使用Java Scanner类 – 读取输入,找到并跳过具有不同分隔符的模式。

2. Scan a File

2.扫描一个文件

First – let’s see how to read a file using Scanner.

首先–让我们看看如何使用Scanner读取一个文件。

In the following example – we read a file contains “Hello world” into tokens:

在下面的例子中–我们将一个包含”Hello world“的文件读成tokens。

@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();
}

Note that the next() method returns the next String token here.

注意,next()方法在这里返回下一个String标记。

Also, note how we’re closing the scanner when we’re done using it.

另外,注意我们在使用完扫描仪后是如何关闭它的。

3. Convert InputStream to String

3.将InputStream转换为String

Next – let’s see how to convert an InputStream into a String using a Scanner:

接下来 – 让我们看看如何使用ScannerInputStream转换为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();
}

Similar to the previous example, we used the Scanner to tokenize the entire stream from the beginning to the next regex “A” – which matches the full input.

与之前的例子类似,我们使用Scanner对整个流进行标记,从开始到下一个词组 “A”–匹配全部输入。

4. Scanner vs. BufferedReader

4.ScannerBufferedReader

Now – let’s discuss the difference between Scanner and BufferedReader – we generally use:

现在–让我们讨论一下ScannerBufferedReader之间的区别–我们一般使用。

  • BufferedReader when we want to read the input into lines
  • Scanner to read the input into tokens

In the following example – we’re reading a file into lines using BufferedReader:

在下面的例子中–我们正在使用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();
}

Now, let’s use Scanner to read the same file into tokens:

现在,让我们用Scanner将同一个文件读成tokens。

@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();
}

Note how we’re using the Scanner nextLine() API – to read the entire line.

注意我们是如何使用Scanner nextLine() API的 – 读取整行

5. Scan Input From Console Using New Scanner(System.in)

5.使用New Scanner(System.in)扫描控制台的输入

Next – let’s see how to read input from the Console using a Scanner instance:

接下来–让我们看看如何使用一个Scanner实例从控制台读取输入。

@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();
}

Note that we used System.setIn(…) to simulate some input coming from the Console.

注意,我们使用了System.setIn(…)来模拟一些来自控制台的输入。

5.1. nextLine() API

5.1.nextLine() API

This method simply returns the string at the current line:

这个方法只是返回当前行的字符串。

scanner.nextLine();

This reads the content of the current line and returns it except for any line separator at the end – in this case – the new line character.

读取当前行的内容并返回,但末尾的任何行分隔符除外–本例中是新行字符。

After reading the content, Scanner sets its position to the start of the next line. The important point to remember is that the nextLine() API consumes the line separator and moves the position of the Scanner to the next line.

读取内容后,Scanner将其位置设置为下一行的开始。需要记住的是,nextLine() API会消耗行的分隔符,并将Scanner的位置移到下一行

So the next time if we read through Scanner we’ll be reading from the start of the next line.

所以下一次如果我们通过Scanner读取,我们将从下一行的开始读取。

5.2. nextInt() API

5.2.nextInt() API

This method scans the next token of the input as an int:

该方法将输入的下一个标记扫描为int:

scanner.nextInt();

The API reads the integer token available next.

API接下来会读取可用的整数令牌。

In this case, if the next token is an integer and after the integer, there is a line separator, always remember that nextInt() will not consume the line separator. Instead, the position of the scanner will be the line separator itself.

在这种情况下,如果下一个标记是一个整数,并且在整数之后有一个行分隔符,一定要记住,nextInt() 不会消耗行分隔符。相反,扫描器的位置将是行分隔符本身

So if we have a series of operations, where the first operation is a scanner.nextInt() and then scanner.nextLine() and as an input if we provide an integer and press line break, both the operations will be executed.

因此,如果我们有一系列的操作,其中第一个操作是scanner.nextInt(),然后是scanner.nextLine(),作为输入如果我们提供一个整数并按下换行键,两个操作都将被执行。

The nextInt() API will consume the integer and the nextLine() API will consume the line separator and will place Scanner to the starting of the next line.

nextInt() API将消耗整数,nextLine() API将消耗行的分隔符,并将Scanner放置到下一行的起点。

6. Validate Input

6.验证输入

Now – let’s see how to validate input using a Scanner. In the following example – we use the Scanner method hasNextInt() to check if the input is an integer value:

现在 – 让我们看看如何使用Scanner来验证输入。在下面的例子中,我们使用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. Scan a String

7.扫描一个字符串

Next – let’s see how to scan a String using Scanner:

接下来–让我们看看如何使用Scanner来扫描一个String

@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();
}

Note: The method nextInt(16) reads the next token as a hexadecimal integer value.

注意:方法nextInt(16)将下一个标记读为十六进制的整数值。

8. Find Pattern

8.寻找模式

Now – let’s see how to find a Pattern using Scanner.

现在–让我们看看如何使用Scanner找到一个Pattern

In the following example – we use findInLine() to search for a token that matches the given Pattern in the entire input:

在下面的例子中–我们使用findInLine()在整个输入中搜索符合给定Pattern的标记。

@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();
}

We can also search for a Pattern in the specific domain using findWithinHorizon() as in the following example:

我们还可以使用findWithinHorizon()在特定的域中搜索Pattern,如下例所示。

@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();
}

Note that the search horizon is simply the number of characters within which the search is performed.

请注意,搜索范围只是进行搜索的字符数

9. Skip Pattern

9.跳过模式

Next – let’s see how to skip a Pattern in Scanner. We can skip tokens that match a specific pattern while reading the input using Scanner.

接下来 – 让我们看看如何在Scanner中跳过一个Pattern。在使用Scanner读取输入时,我们可以跳过符合特定模式的标记。

In the following example – we skip “Hello” token using the Scanner method skip():

在下面的例子中 – 我们使用Scanner方法skip()跳过”Hello“标记。

@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. Change Scanner Delimiter

10.改变扫描器分隔符

Finally – let’s see how to change the Scanner delimiter. In the following example – we change the default Scanner delimiter to “o“:

最后–让我们看看如何改变Scanner的分隔符。在下面的例子中–我们把默认的Scanner分隔符改为”o“。

@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();
}

We can also use multiple delimiters. In the following example – we use both commas “,” and dash”” as delimiters to scan a file contains “John,Adam-Tom“:

我们还可以使用多个定界符。在下面的例子中–我们同时使用逗号”,“和破折号”“作为定界符来扫描一个包含”John,Adam-Tom“的文件。

@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();
}

Note: The default Scanner delimiter is whitespace.

注意:默认的Scanner分隔符是空白。

11. Handling NoSuchElementException

11.处理NoSuchElementException

Before diving deep into the details, let’s try to understand what the exception really means.

在深入研究细节之前,让我们试着了解一下这个例外的真正含义。

According to the documentation, NoSuchElementException typically occurs when the requested element does not exist.

根据文档,NoSuchElementException通常在请求的元素不存在时发生

Basically, Scanner throws this exception when it fails to read data or fetch the next element.

基本上,Scanner在读取数据或获取下一个元素失败时抛出这个异常。

11.1. Reproducing the Exception

11.1.复制例外情况

Now that we know what causes Scanner to throw NoSuchElementException, let’s see how to reproduce it using a practical example.

现在我们知道是什么导致Scanner抛出NoSuchElementException,让我们看看如何用一个实际的例子来重现它。

To demonstrate a real-world use case, we’re going to read data from a FileInputStream twice using two different instances of the Scanner class:

为了演示一个真实的用例,我们将使用Scanner类的两个不同实例从FileInputStream中读取数据。

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();
}

Next, let’s run our test case and see what happens. Indeed, looking at the logs, the test case fails with NoSuchElementException:

接下来,让我们运行我们的测试案例,看看会发生什么。的确,查看日志,测试案例以NoSuchElementException失败。

java.util.NoSuchElementException
    at java.util.Scanner.throwFor(Scanner.java:862)
    at java.util.Scanner.next(Scanner.java:1371)
    at com.baeldung.scanner.JavaScannerUnitTest.givenClosingScanner_whenReading_thenThrowException(JavaScannerUnitTest.java:195)
...

11.2. Investigating the Cause

11.2.调查原因

Simply put, when a Scanner is closed, it will close its source, too, if the source implements the Closeable interface.

简单地说,当一个扫描器被关闭时,它也将关闭其源,如果源实现了Closeable接口的话

Basically, when we invoked scanner.close() in our test case, we actually closed the inputStream, too. So, the second Scanner failed to read the data because the source is not open.

基本上,当我们在测试案例中调用scanner.close()时,我们实际上也关闭了inputStream。所以,第二个Scanner没能读取数据,因为源头没有打开。

11.3. Fixing the Exception

11.3.修复异常情况

There’s only one way to fix the exception – to avoid using multiple Scanners to manipulate the same data source.

只有一个方法可以解决这个异常–避免使用多个Scanners来操作同一个数据源。

So, a good solution would be using a single Scanner instance to read data and close it at the very end.

因此,一个好的解决方案是使用一个Scanner实例来读取数据,并在最后关闭它。

12. Conclusion

12.结论

In this tutorial, we went over multiple real-world examples of using the Java Scanner.

在本教程中,我们讲述了使用Java Scanner的多个真实世界的例子。

We learned how to read input from a file, console, or String using Scanner. We also learned how to find and skip a pattern using Scanner — as well as how to change the Scanner delimiter.

我们学习了如何使用Scanner从文件、控制台或String中读取输入。我们还学习了如何使用Scanner查找和跳过一个模式–以及如何改变Scanner分隔符。

Finally, we explained how to handle NoSuchElementException exception.

最后,我们解释了如何处理NoSuchElementException exception.

The implementation of these examples can be found over on GitHub.

这些示例的实现可以在GitHub上找到over on GitHub.