概要
Java 1.5より導入されたjava.util.Scannerクラスのおさらいメモです。
Scannerクラスの使用例では標準入力(System.in)を使う例が数多く取り上げられていますが、この記事では標準入力は扱わずテキストファイルを読むおさらいが中心になります。またScannerクラスのすべてのAPIは取り上げません。
環境
- Windows 10 Professional
- OpenJDK 10.0.1
参考
- [Oracle JDK 9ドキュメント] (https://docs.oracle.com/javase/jp/9/index.html)
- [Oracle JDK 10ドキュメント] (https://docs.oracle.com/javase/jp/10/index.html)
- [Scanner (Java Se 10 & JDK 10)] (https://docs.oracle.com/javase/jp/10/docs/api/java/util/Scanner.html)
インスタンスの生成方法
Fileから
public Scanner(File source) throws FileNotFoundException
public Scanner(File source, String charsetName) throws FileNotFoundException
コード例
Path in = Paths.get("path/to/sample.in");
Scanner scanner = new Scanner(in.toFile());
try (scanner) {
// ...省略...
}
InputStreamから
public Scanner(InputStream source)
public Scanner(InputStream source, String charsetName)
コード例
InputStream in = Files.newInputStream(Paths.get("path/to/sample.in"));
Scanner scanner = new Scanner(in);
try (scanner) {
// ...省略...
}
JavaDocに記載があるようにScannerの入力リソースがCloseableインタフェースを実装しているとScannerを閉じるときに、そのリソースも閉じられるためtry句に含める必要はありません。
Scannerが閉じられる場合、その入力ソースがCloseableインタフェースを実装していると、そのソースも閉じられます。
Pathから
public Scanner(Path source) throws IOException
public Scanner(Path source, String charsetName) throws IOException
コード例
Path in = Paths.get("path", "to", "sample.in");
Scanner scanner = new Scanner(in);
try (scanner) {
// ...省略...
}
Stringから
public Scanner(String source)
コード例
String in = "apple banana cherry durian elderberry";
Scanner scanner = new Scanner(in);
try (scanner) {
// ...省略...
}
Readableから
public Scanner(Readable source)
コード例
Readable in = new FileReader(new File("path/to/sample.in"));
Scanner scanner = new Scanner(in);
try (scanner) {
// ...省略...
}
テキストファイルを読み取る
サンプルファイル
日本郵便株式会社のサイトよりダウンロードできる郵便番号データを利用しました。
この記事のコード例ではこのカンマ区切りのテキストを扱いますが、通常はopencsvなどのライブラリを使用すると思います。
32343,"69917","6991701","シマネケン","ニタグンオクイズモチョウ","カメダケ","島根県","仁多郡奥出雲町","亀嵩",0,0,0,0,0,0
32343,"69915","6991515","シマネケン","ニタグンオクイズモチョウ","カモクラ","島根県","仁多郡奥出雲町","鴨倉",0,0,0,0,0,0
32343,"69915","6991514","シマネケン","ニタグンオクイズモチョウ","カワチ","島根県","仁多郡奥出雲町","河内",0,0,0,0,0,0
トークンの区切り文字
デフォルトのトークンの区切り文字は空白文字です。ただしこの場合の空白文字とはJavaの基準に従った空白(Character.isWhitespaceがtrueを返す文字)になり、半角スペース以外にも例えば以下のようなものが区切り文字として認識されます。
出力結果
戻り値がtrueの文字はJava基準の空白。
Character.isWhitespace(' '); // 半角スペース
// → true
Character.isWhitespace('\u0020'); // 半角スペース
// → true
Character.isWhitespace(' '); // 全角スペース
// → true
Character.isWhitespace('\t'); // タブ
// → true
Character.isWhitespace('\n'); // 改行
// → true
Character.isWhitespace('\f'); // フォームフィード
// → true
Character.isWhitespace('\r'); // 復帰
// → true
Character.isWhitespace('\u001C'); // ファイル区切り文字
// → true
Character.isWhitespace('\u001D'); // グループ区切り文字
// → true
Character.isWhitespace('\u001E'); // レコード区切り文字
// → true
Character.isWhitespace('\u001F'); // ユニット区切り文字
// → true
Character.isWhitespace('\u00a0'); // ノーブレークスペース. いわゆる
// → false
Character.isWhitespace('a');
// → false
Character.isWhitespace('あ');
// → false
区切り文字のマッチングに使用するPatternの確認
public Pattern delimiter()
コード例
scanner.delimiter().pattern();
出力結果
\p{javaWhitespace}+
区切り文字のマッチングに使用するPatternを指定する
useDelimiterメソッドで区切り文字に任意のパターンを指定します。
public Scanner useDelimiter(Pattern pattern)
public Scanner useDelimiter(String pattern)
コード例
String in = "apple : banana : cherry : durian : elderberry";
Scanner scanner = new Scanner(in);
try (scanner) {
scanner.useDelimiter("\\s*:\\s*");
while (scanner.hasNext()) {
System.out.println("[" + scanner.next() + "]");
}
}
出力結果
[apple]
[banana]
[cherry]
[durian]
[elderberry]
テキストファイルから1行ずつ読む
hasNextLineメソッドとnextLineメソッドを使用します。
hasNextLineメソッドはスキャナにまだ入力行がある場合にtrueを返します。またnextLineメソッドはスキャナの現在の位置から行末までの内容を返し、スキャナの位置を次行の先頭に移動させます。
public boolean hasNextLine()
public String nextLine()
コード例
File in = new File("path/to/sample.in");
Scanner scanner = new Scanner(in);
try (scanner) {
int counter = 0;
while (scanner.hasNextLine()) {
System.out.println(String.format("%2d: %s", ++counter, scanner.nextLine()));
}
}
出力結果
1: 32343,"69917","6991701","シマネケン","ニタグンオクイズモチョウ","カメダケ","島根県","仁多郡奥出雲町","亀嵩",0,0,0,0,0,0
2: 32343,"69915","6991515","シマネケン","ニタグンオクイズモチョウ","カモクラ","島根県","仁多郡奥出雲町","鴨倉",0,0,0,0,0,0
3: 32343,"69915","6991514","シマネケン","ニタグンオクイズモチョウ","カワチ","島根県","仁多郡奥出雲町","河内",0,0,0,0,0,0
テキストファイルからトークン単位で読む
hasNextメソッドはスキャナの入力に別のトークンがある場合にtrueを返します。またnextメソッドはスキャナの現在の位置からのトークンを返し、スキャナの位置を次の区切り文字の位置に移動させます。
デフォルトのトークンの区切り文字のホワイトスペース(半角、全角)、タブ、改行コードなどですが、このサンプルデータではトークンの区切り文字はカンマなのでuseDelimiterメソッドで明示的に指定し、また改行コードもトークンの区切り文字として指定する必要があります。
public boolean hasNext()
public String next()
コード例
File in = new File("path/to/sample.in");
Scanner scanner = new Scanner(in);
try (scanner) {
scanner.useDelimiter(",|\n");
int counter = 0;
while (scanner.hasNext()) {
System.out.println(String.format("%2d: %s", ++counter, scanner.next()));
}
}
出力結果
1: 32343
2: "69917"
3: "6991701"
4: "シマネケン"
5: "ニタグンオクイズモチョウ"
6: "カメダケ"
7: "島根県"
8: "仁多郡奥出雲町"
9: "亀嵩"
10: 0
11: 0
12: 0
13: 0
14: 0
15: 0
16: 32343
17: "69915"
18: "6991515"
19: "シマネケン"
20: "ニタグンオクイズモチョウ"
21: "カモクラ"
22: "島根県"
23: "仁多郡奥出雲町"
24: "鴨倉"
25: 0
26: 0
27: 0
28: 0
29: 0
30: 0
31: 32343
32: "69915"
33: "6991514"
34: "シマネケン"
35: "ニタグンオクイズモチョウ"
36: "カワチ"
37: "島根県"
38: "仁多郡奥出雲町"
39: "河内"
40: 0
41: 0
42: 0
43: 0
44: 0
45: 0
nextとnextLineの併用
nextメソッドで任意の位置のトークンを読み、nextLineメソッドで行末までのデータを読み飛ばすということもできます。
コード例
File in = new File("path/to/sample.in");
Scanner scanner = new Scanner(in);
try (scanner) {
scanner.useDelimiter(",");
int counter = 0;
while (scanner.hasNextLine()) {
int code = scanner.nextInt(); // 全国地方公共団体コード
String zip5 = scanner.next(); // 郵便番号(5桁)
String zip7 = scanner.next(); // 郵便番号(7桁)
scanner.next(); // skip 都道府県名 半角カタカナ
scanner.next(); // skip 市区町村名 半角カタカナ
scanner.next(); // skip 町域名 半角カタカナ
String prefectures = scanner.next(); // 都道府県名
String city = scanner.next(); // 市区町村名
String townArea = scanner.next(); // 町域名
System.out.println(String.format("%2d: %d %s %s %s %s %s", ++counter, code, zip5, zip7, prefectures, city, townArea));
scanner.nextLine(); // next line
}
}
出力結果
1: 32343 "69917" "6991701" "島根県" "仁多郡奥出雲町" "亀嵩"
2: 32343 "69915" "6991515" "島根県" "仁多郡奥出雲町" "鴨倉"
3: 32343 "69915" "6991514" "島根県" "仁多郡奥出雲町" "河内"
findInLineでパターン検索する
findInLineメソッドに指定した検索パターンに一致する文字列を、スキャナの現在位置から行末までの間で検索します。
パターンに一致する文字列が見つからなければnullを返します。
public String findInLine(String pattern)
public String findInLine(Pattern pattern)
コード例
File in = new File("path/to/sample.in");
Scanner scanner = new Scanner(in);
try (scanner) {
int counter = 0;
while (scanner.hasNextLine()) {
String find = scanner.findInLine("69915[0-9]{2}");
System.out.println(String.format("%2d: %s", ++counter, find));
scanner.nextLine(); // next line
}
}
出力結果
1: null
2: 6991515
3: 6991514
Java 9で追加されたAPI
tokens
トークンのストリームを返します。
public Stream<String> tokens()
コード例
String in = "apple banana cherry durian elderberry";
Scanner scanner = new Scanner(in);
try (scanner) {
final List<String> fruits = scanner.tokens()
.map(String::toUpperCase)
.collect(Collectors.toUnmodifiableList());
System.out.println(fruits);
}
出力結果
[APPLE, BANANA, CHERRY, DURIAN, ELDERBERRY]
findAll
スキャナからパターンマッチのストリームを返します。
public Stream<MatchResult> findAll(Pattern pattern)
public Stream<MatchResult> findAll(String patString)
コード例
File in = new File("path/to/sample.in");
Scanner scanner = new Scanner(in);
try (scanner) {
List<String> list = scanner.findAll("\"[0-9]{5,}\"")
.map(MatchResult::group)
.collect(Collectors.toUnmodifiableList());
System.out.println(list);
}
出力結果
["69917", "6991701", "69915", "6991515", "69915", "6991514"]
Java 10で追加されたAPI
新しいコンストラクタ
第2引数にCharsetを取る新しいコンストラクタが追加されています。コード例は省略します。
public Scanner(InputStream source, Charset charset)
public Scanner(File source, Charset charset) throws IOException
public Scanner(Path source, Charset charset) throws IOException
public Scanner(ReadableByteChannel source, Charset charset)
その他のおさらいメモ
- [Java NIO2のおさらいメモ] (https://qiita.com/rubytomato@github/items/6880eab7d9c76524d112)
- 2017年08月15日
- [クラス java.util.Objectsのおさらいメモ] (https://qiita.com/rubytomato@github/items/ba38877ed5a00dd24f16)
- 2017年08月25日
- [Java Collections Frameworkのおさらいメモ] (https://qiita.com/rubytomato@github/items/554095ae21a2c36f131a)
- 2017年08月30日
- [パッケージ java.time.temporal のおさらいメモ] (https://qiita.com/rubytomato@github/items/e9325dd46aa11f1b8e2e)
- 2018年01月30日
- [クラス java.util.Optionalのおさらいメモ] (https://qiita.com/rubytomato@github/items/92ac7944c830e54aa03d)
- 2018年03月22日