1. 概述

在Java中,我们通常会从一个Scanner对象读取输入。此外,这些输入常常会被存储在一个数组中,以便进行进一步处理。

在这篇教程中,我们将探讨如何在Java中将Scanner的输入保存到数组中。

2. 问题介绍

当我们把Scanner的输入作为数组存储时,可能有三种情况:

  • 单行输入 - 每个令牌是一个数组元素。
  • 多行输入 - 每行是一个数组元素。
  • 多行输入 - 每行的每个令牌是一个数组元素。

本教程将讨论这些情况。为了简化,我们将通过字符串喂给Scanner对象,并使用单元测试断言来验证是否得到预期的结果。

3. 单行输入:每个令牌一个数组元素

首先,假设Scanner对象有以下输入:

String input = "Java Kotlin Ruby Python Go\n";

如图所示,输入行有五个令牌。因此,结果数组应该看起来像这样:

String[] expected = new String[] { "Java", "Kotlin", "Ruby", "Python", "Go" };

Java Scanner的默认分隔符是空格。而且,Scanner.next()方法允许我们从输入中读取下一个令牌。因此,我们可以使用循环调用next()方法来填充数组:

Scanner scanner1 = new Scanner(input);
String[] result1 = new String[5];
int i = 0;
while (i < result1.length) {
    result1[i] = scanner1.next();
    i++;
}
assertArrayEquals(expected, result1);

细心的读者可能会注意到,这个解决方案只适用于行中有五个令牌的情况,因为我们定义了数组的容量(5)。这是因为Java数组是固定的大小,我们在创建它时需要设置其大小。

显然,当不知道输入中有多少元素时,这种方法不太方便。

另一种选择是,我们可以使用Scanner.nextLine()方法读取整个输入行,并使用split()方法将行分割成数组

Scanner scanner2 = new Scanner(input);
String[] result2 = scanner2.nextLine().split("\\s+");
assertArrayEquals(expected, result2);

4. 多行输入:每行一个数组元素

接下来,构建多行输入:

String input = new StringBuilder().append("Baeldung Java\n")
  .append("Baeldung Kotlin\n")
  .append("Baeldung Linux\n")
  .toString();

然后,我们期望输入中的每一行都成为结果数组的一个元素:

String[] expected = new String[] {"Baeldung Java", "Baeldung Kotlin", "Baeldung Linux"};

我们可以使用Scanner.nextLine()读取输入行,并在循环中填充数组

String[] result = new String[3];
Scanner scanner = new Scanner(input);
int i = 0;
while (i < result.length) {
    result[i] = scanner.nextLine();
    i++;
}
assertArrayEquals(expected, result);

同样,我们在代码中声明数组对象时设置了大小(3)。

由于List具有动态大小,如果我们不知道输入中有多少行,我们可以使用List来存储行:

List<String> expected = Lists.newArrayList("Baeldung Java", "Baeldung Kotlin", "Baeldung Linux");
List<String> result = new ArrayList<>();
Scanner scanner = new Scanner(input);
while (scanner.hasNextLine()) {
    result.add(scanner.nextLine());
}
assertEquals(expected, result);

当然,如果最终结果必须是数组,我们可以轻松地将结果List转换为数组

5. 多行输入:每个令牌一个数组元素

现在,让我们看看如何将多行输入的令牌存储在数组中。假设我们有以下两行输入:

String input = new StringBuilder().append("Linux Windows MacOS\n")
  .append("Java Kotlin Python Go\n")
  .toString();

并且我们希望有一个包含这些元素的数组:

String[] expected = new String[] { "Linux", "Windows", "MacOS", "Java", "Kotlin", "Python", "Go" };

为了解决这个问题,我们可以使用Scanner.nextLine()读取每一行,并对每一行进行split()以将其转换为数组。同时,我们需要将这个数组合并到最终的结果数组中

接下来,我们来看看它是如何工作的:

String[] result = new String[] {};

Scanner scanner = new Scanner(input);
while (scanner.hasNextLine()) {
    String[] lineInArray = scanner.nextLine().split("\\s+");
    result = ObjectArrays.concat(result, lineInArray, String.class);
}
assertArrayEquals(expected, result);

在上面的测试中,我们使用了GuavaObjectArrays.concat()方法来连接两个数组,以简化操作。

然而,连接数组会产生一个新的数组,并将两个数组复制到新数组中。因此,这种做法在大量输入行的情况下可能会很慢

为了获得更好的性能,我们可以使用List来存储令牌:

List<String> expected = Lists.newArrayList("Linux", "Windows", "MacOS", "Java", "Kotlin", "Python", "Go");

List<String> result = new ArrayList<>();
Scanner scanner = new Scanner(input);
while (scanner.hasNext()) {
    result.add(scanner.next());
}
assertEquals(expected, result);

再次强调,如果需要,我们可以将列表结果转换为数组。

6. 数组与List的比较

在解决我们的问题的过程中,我们可能已经意识到将输入存储到List中比数组更灵活。除了动态大小属性外,List相对于数组还有其他优势,例如:

  • 添加和删除元素更加方便
  • 类型安全
  • 提供方便的内置操作,如indexOf(), contains(), 等等
  • 有多种实现,如ArrayListLinkedList, 等等

因此,在Java项目中,我们应该考虑使用List而不是数组

7. 总结

在这篇文章中,我们学习了如何将Scanner的输入保存到数组中。

我们还讨论了三种不同的情况,并通过示例探索了每种解决方案。

如往常一样,这里展示的所有代码片段都在GitHub上可用。