1. 概述
在日常的Java编程中,接收和解析用户输入是一项常见的任务。处理包含空格的输入有时可能会有些棘手。本教程将探讨如何使用Scanner
类来获取Java中的带有空格的字符串输入。
2. 问题介绍
如常,我们通过一个简单示例来理解问题所在。
假设我们的扫描器接收两行文本,第一行是一个人的名字,第二行简短地描述这个人:
String input = new StringBuilder().append("Michael Jackson\n")
.append("He was the 'King of Pop'.\n")
.toString();
Scanner sc = new Scanner(input);
为了简化,我们将使用Scanner
对象处理字符串,并使用单元测试断言来验证结果是否符合预期。通常,我们会使用Scanner.next()
方法来读取下一个令牌。
接下来,尝试从扫描器对象读取两个令牌:
String name = sc.next();
String description = sc.next();
assertEquals("Michael", name);
assertEquals("Jackson", description);
如果运行测试,它会通过。显然,Scanner
并没有智能地理解我们的需求。相反,它默认使用包括空格和换行符在内的空白作为分隔符来读取令牌。因此,我们得到的是“Michael”,而不是“Michael Jackson”。
实际上,这个例子只展示了处理包含空格值的一个场景。可能有以下两种情况:
- 每行一个值(如“Michael Jackson”示例所示)
- 值由特殊分隔符分隔
接下来,我们将探讨如何从Scanner
对象读取包含空格的值。当然,我们会涵盖这两种情况。
3. 每行一个值
首先,让我们更深入地了解“每行一个值”的场景。在这一节中,我们仍以之前的“Michael Jackson”示例作为输入。
3.1. 使用nextLine()
方法
由于我们想从扫描器中读取整个行作为值,Scanner
的nextLine()
方法是一个不错的选择。**nextLine()
方法从当前位置读取到下一行结束:**
Scanner sc = new Scanner(input);
String name = sc.nextLine();
String description = sc.nextLine();
assertEquals("Michael Jackson", name);
assertEquals("He was the 'King of Pop'.", description);
正如上面的代码所示,nextLine()
方法直接解决了问题。
3.2. 使用\n
作为分隔符
我们之前提到,Scanner
默认将空格和换行符视为分隔符。如果我们告诉Scanner
只接受换行符作为分隔符,我们仍然可以使用next()
方法来阅读一行作为令牌。让我们创建一个测试来验证这一点:
Scanner sc = new Scanner(input);
sc.useDelimiter("\\n");
String name = sc.next();
String description = sc.next();
assertEquals("Michael Jackson", name);
assertEquals("He was the 'King of Pop'.", description);
如我们所见,useDelimiter()
方法是解决这个问题的关键。
4. 由特殊分隔符分隔的值
有时,我们的输入具有预定义的格式。例如,逗号和空格分隔了三个伟大艺术家的名字:“Michael Jackson, Whitney Houston, John Lennon”。
接下来,看看在这种情况下如何读取期望的值。
4.1. 使用String.split()
方法
解决这个问题的第一个想法仍然是使用nextLine()
读取整行。然后,我们可以将分隔符模式传递给方便的String.split()
方法,以将值存储在一个数组中:
String input = "Michael Jackson, Whitney Houston, John Lennon\n";
Scanner sc = new Scanner(input);
String[] names = sc.nextLine().split(", ");
assertArrayEquals(new String[] { "Michael Jackson", "Whitney Houston", "John Lennon" }, names);
上述测试显示,我们已经正确地将三个名字存储在一个字符串数组中。
4.2. 自定义分隔符
使用split()
和分隔符模式的方法可以处理具有自定义分隔符的值。然而,由于Java中的数组具有固定大小,如果扫描器输入有多行,合并数组可能会很慢。通常,我们在Java中会使用列表(lists)而非数组。因此,接下来我们将调整Scanner
的分隔符,使用Scanner
的next()
方法将名字存储在列表中。
我们已经学会了使用useDelimiter()
方法设置自定义分隔符模式。由于这个输入示例的分隔符是逗号和空格,一些人可能会想到使用useDelimiter(", ")
。
那么,接下来我们向输入添加一个更多名字,看看这个想法是否按预期工作:
String input = new StringBuilder().append("Michael Jackson, Whitney Houston, John Lennon\n")
.append("Elvis Presley\n")
.toString();
Scanner sc = new Scanner(input);
sc.useDelimiter(", ");
List<String> names = new ArrayList<>();
while (sc.hasNext()) {
names.add(sc.next());
}
assertEquals(Lists.newArrayList("Michael Jackson", "Whitney Houston", "John Lennon", "Elvis Presley"), names);
当我们运行测试时,它失败了。真是出乎意料!让我们通过几个断言来看看列表中有什么:
assertEquals(3, names.size());
assertEquals("John Lennon\nElvis Presley\n", names.get(2));
我们可以看到,结果列表中有三个元素而不是四个。而且第三个元素是"John Lennon\nElvis Presley\n"
。这是因为我们设置了“, ”作为分隔符,然后换行符成为了令牌的一部分。所以**next()
方法会将换行符视为令牌中的普通字符**。
现在我们理解了问题的原因。然后很容易解决——我们必须将\n
添加到分隔符模式中:
Scanner sc = new Scanner(input);
sc.useDelimiter(", |\\n");
List<String> names = new ArrayList<>();
while (sc.hasNext()) {
names.add(sc.next());
}
assertEquals(Lists.newArrayList("Michael Jackson", "Whitney Houston", "John Lennon", "Elvis Presley"), names);
这次,测试通过了。
5. 总结
在这篇文章中,我们通过实例学习了如何从Scanner
中读取包含空格的值。文章涵盖了两种情况,并探讨了解决问题的不同方法。
如往常一样,这里展示的所有代码片段可在GitHub上找到。