クラス java.util.Scannerのおさらいメモ

# 概要

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から

```text:signature
public Scanner​(File source) throws FileNotFoundException

public Scanner​(File source, String charsetName) throws FileNotFoundException
```

**コード例**

```java:example
Path in = Paths.get("path/to/sample.in");
Scanner scanner = new Scanner(in.toFile());
try (scanner) {
  // ...省略...
}
```

## InputStreamから

```text:signature
public Scanner​(InputStream source)

public Scanner​(InputStream source, String charsetName)
```

**コード例**

```java:example
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から

```text:signature
public Scanner​(Path source) throws IOException

public Scanner​(Path source, String charsetName) throws IOException
```

**コード例**

```java:example
Path in = Paths.get("path", "to", "sample.in");
Scanner scanner = new Scanner(in);
try (scanner) {
  // ...省略...
}
```

## Stringから

```text:signature
public Scanner​(String source)
```

**コード例**

```java:example
String in = "apple banana cherry durian elderberry";
Scanner scanner = new Scanner(in);
try (scanner) {
  // ...省略...
}
```

## Readableから

```text:signature
public Scanner​(Readable source)
```

**コード例**

```java:example
Readable in = new FileReader(new File("path/to/sample.in"));
Scanner scanner = new Scanner(in);
try (scanner) {
  // ...省略...
}
```

# テキストファイルを読み取る

**サンプルファイル**

日本郵便株式会社のサイトよりダウンロードできる郵便番号データを利用しました。
この記事のコード例ではこのカンマ区切りのテキストを扱いますが、通常はopencsvなどのライブラリを使用すると思います。

```text:sample.in
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基準の空白。

```java:output
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');  // ノーブレークスペース. いわゆる&nbsp;
// → false
Character.isWhitespace('a');
// → false
Character.isWhitespace('あ');
// → false
```

### 区切り文字のマッチングに使用するPatternの確認

```text:signature
public Pattern delimiter()
```

**コード例**

```java:example
scanner.delimiter().pattern();
```

出力結果

```text:output
\p{javaWhitespace}+
```

### 区切り文字のマッチングに使用するPatternを指定する

useDelimiterメソッドで区切り文字に任意のパターンを指定します。

```text:signature
public Scanner useDelimiter​(Pattern pattern)

public Scanner useDelimiter​(String pattern)
```

**コード例**

```java:example
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() + "]");
  }
}
```

出力結果

```text:output
[apple]
[banana]
[cherry]
[durian]
[elderberry]
```

## テキストファイルから1行ずつ読む

hasNextLineメソッドとnextLineメソッドを使用します。
hasNextLineメソッドはスキャナにまだ入力行がある場合にtrueを返します。またnextLineメソッドはスキャナの現在の位置から行末までの内容を返し、スキャナの位置を次行の先頭に移動させます。

```text:signature
public boolean hasNextLine()

public String nextLine()
```

**コード例**

```java:example
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()));
  }
}
```

出力結果

```text:output
 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メソッドで明示的に指定し、また改行コードもトークンの区切り文字として指定する必要があります。

```text:signature
public boolean hasNext()

public String next()
```

**コード例**

```java:example
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()));
  }
}
```

出力結果

```text:output
 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メソッドで行末までのデータを読み飛ばすということもできます。

**コード例**

```java:example
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
  }
}
```

出力結果

```text:output
 1: 32343 "69917" "6991701" "島根県" "仁多郡奥出雲町" "亀嵩"
 2: 32343 "69915" "6991515" "島根県" "仁多郡奥出雲町" "鴨倉"
 3: 32343 "69915" "6991514" "島根県" "仁多郡奥出雲町" "河内"
```

## findInLineでパターン検索する

findInLineメソッドに指定した検索パターンに一致する文字列を、スキャナの現在位置から行末までの間で検索します。
パターンに一致する文字列が見つからなければnullを返します。

```text:signature
public String findInLine​(String pattern)

public String findInLine​(Pattern pattern)
```

**コード例**

```java:exmaple
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
  }
}
```

出力結果

```text:output
 1: null
 2: 6991515
 3: 6991514
```

# Java 9で追加されたAPI

## tokens

トークンのストリームを返します。

```text:signature
public Stream<String> tokens()
```

**コード例**

```java:example
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);
}
```

出力結果

```text:output
[APPLE, BANANA, CHERRY, DURIAN, ELDERBERRY]
```

## findAll

スキャナからパターンマッチのストリームを返します。

```text:signature
public Stream<MatchResult> findAll​(Pattern pattern)

public Stream<MatchResult> findAll​(String patString)
```

**コード例**

```java:example
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);
}
```

出力結果

```text:output
["69917", "6991701", "69915", "6991515", "69915", "6991514"]
```


# Java 10で追加されたAPI

## 新しいコンストラクタ

第2引数にCharsetを取る新しいコンストラクタが追加されています。コード例は省略します。

```text:signature
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日