ファイルから文字列を読んでListに格納

ファイルから文字列を行ごとに取り出す

初心者向けのJavaの解説を書き始めたのは2003年。Javaのバージョンは1.4でした。ファイルからの入力で次のやり方を紹介しました。

もちろんFileReaderとBufferedReaderを使うものも扱いましたが、文字コードを指定できるのはこれです。

FileRead01.java

package iotest;
import java.io.*;
public class FileRead01 {
    public static void main(String[] args){
        String fname =args[0];
        int ct = 0;
        try {
            FileInputStream is  = new FileInputStream(fname);
            InputStreamReader in = new InputStreamReader(is,"UTF-8");
            BufferedReader inb = new BufferedReader(in);
            String line;
            ct = 0;
            while ((line = inb.readLine()) != null) {
                ct++;
                System.out.println(line);
            }
            System.out.println("以上" + ct + "行");
            inb.close();
            in.close();
            is.close();
        }
        catch (IOException e) {
            System.err.println( fname + " がないのでは?" );
            System.err.println( e);
        }
    }
}

例外が起こった時にcloseされないのでfainally節を書いて...というのは最近見かけるようになりました。これに言及すると長くなりそうなので後回しにして、try-with-resources文 や Files.readAllLines()メソッドを使ってみます。

Listに読み込む

いままでのやり方でListに読んでみます。

FileRead02.java

package iotest;
import java.io.*;
import java.util.LinkedList;
public class FileRead02 {
    public static void main(String[] args){
        String fname =args[0];
        LinkedList<String> list = new LinkedList<>();
        try {
            FileInputStream is  = new FileInputStream(fname);
            InputStreamReader in = new InputStreamReader(is,"UTF-8");
            BufferedReader inb = new BufferedReader(in);
            String line;
            while ((line = inb.readLine()) != null) {
                list.add(line);
            }
            inb.close();
            in.close();
            is.close();
        }
        catch (IOException e) {
            System.err.println( e);
        }
        for (String line : list) {
             System.out.println(line);
        }
        System.out.println("以上" + list.size() + "行");
    }
}

try-with-resources文でclose()を省く

Java SE 7 以降 try−with−resources文が使えるようになったということです。tryの後ろに( )が加わって、そこに書くのがresourceということらしいのですが、resourceという記述はプログラムには出現しません。

ListやMapに型変数をつけなければならなくなったのも、Java SE 7 以降 ですが、こちらはコンパイルが通らないので勉強しましたが、 try−with−resources文は意識していなかったですね。

Resource1.java

package iotest;
import java.io.*;
import java.util.LinkedList;
public class Resource1 {
    public static void main(String[] args){
        String fname =args[0];
        LinkedList<String> list = new LinkedList<>();
        try ( FileInputStream is  = new FileInputStream(fname);
              InputStreamReader in = new InputStreamReader(is,"UTF-8");
              BufferedReader inb = new BufferedReader(in);       ){
            String line;
            while ((line = inb.readLine()) != null) {
                list.add(line);
            }
        }
        catch (IOException e) {
            System.err.println( e);
        }
        for (String line : list) {
             System.out.println(line);
        }
        System.out.println("以上" + list.size() + "行");
    }
}

Files.readAllLines()メソッド

ファイルを読んでList<String>として返します。close()は不要ですが、tryは必要です。

ファイルのデータが少ない時には便利です。

ReadAll1.java

package iotest;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.nio.charset.StandardCharsets;
public class ReadAll1 {
    public static void main(String[] args){
        String fname =args[0];
        List<String> list = null; //Collections.emptyList();
        Path path = Paths.get(fname);
        try {
            list = Files.readAllLines(path, StandardCharsets.UTF_8 );
        }
        catch (IOException e) {
            System.err.println( e);
        }
        for (String line : list) {
             System.out.println(line);
        }
        System.out.println("以上" + list.size() + "行");
        
       }
}

Listの初期化については

List<String> lines = Collections.emptyList();

という書き方をする例もありました。Listはインターフェースなので new List<>()というわけにはいかないのですね。でも、どうせFiles.readAllLines()で代入されてしまいますし、拡張for文でnullのままでも文句は言われません。

public static final <T> List<T> emptyList() なので、emptyList()にはaddできません。

readAllLines()の周りは見慣れないものが多くあります。

readAllLines(java.nio.file.Path, java.nio.charset.Charset)
  readAllLines()は java.nio.file.Filesのstaticメソッド
  java.nio.file.Path はインターフェース 
  java.nio.file.Paths は Path を返す static メソッド群
  java.nio.charset.Charset はクラス
  java.nio.charset.StandardCharsets は 標準 Charsets 用の定数定義

StandardCharsets.UTF_8 は長く、importも必要でお得感がありませんが、

import java.nio.charset.StandardCharsets;
list = Files.readAllLines(path, StandardCharsets.UTF_8 );

の他に

import static java.nio.charset.StandardCharsets.*;
list = Files.readAllLines(path, UTF_8 );

という使い方もできます。

Charsetについて

InputStreamReader でCharsetの指定は、Charsetでも文字列でもできます。

InputStreamReader(InputStream in, Charset cs)
InputStreamReader(InputStream in, String charsetName)

"UTF-8"や"windoes-31j"は String charsetName での指定でした。

readAllLines() への指定は Charsetに限られます

java.nio.charset.StandardCharsetsに定数として用意されていますが、限られています。

ISO_8859_1
US_ASCII
UTF_16
UTF_16BE
UTF_16LE
UTF_8

だけです。windoes-31jを使うときには

Charset.forName("Windows-31j")

とする必要があります(大文字小文字は無視)。この文字列がAPI仕様で記述が見つからず、次のメソッドで調べてみました。

static SortedMap<String,Charset> 	availableCharsets()

具体的には

System.out.println(java.nio.charset.Charset.availableCharsets().keySet());

[Big5, Big5-HKSCS, EUC-JP, EUC-KR, GB18030, GB2312, GBK, IBM-Thai, IBM00858, IBM01140, IBM01141, IBM01142, IBM01143, IBM01144, IBM01145, IBM01146, IBM01147, IBM01148, IBM01149, IBM037, IBM1026, IBM1047, IBM273, IBM277, IBM278, IBM280, IBM284, IBM285, IBM290, IBM297, IBM420, IBM424, IBM437, IBM500, IBM775, IBM850, IBM852, IBM855, IBM857, IBM860, IBM861, IBM862, IBM863, IBM864, IBM865, IBM866, IBM868, IBM869, IBM870, IBM871, IBM918, ISO-2022-CN, ISO-2022-JP, ISO-2022-JP-2, ISO-2022-KR, ISO-8859-1, ISO-8859-13, ISO-8859-15, ISO-8859-2, ISO-8859-3, ISO-8859-4, ISO-8859-5, ISO-8859-6, ISO-8859-7, ISO-8859-8, ISO-8859-9, JIS_X0201, JIS_X0212-1990, KOI8-R, KOI8-U, Shift_JIS, TIS-620, US-ASCII, UTF-16, UTF-16BE, UTF-16LE, UTF-32, UTF-32BE, UTF-32LE, UTF-8, windows-1250, windows-1251, windows-1252, windows-1253, windows-1254, windows-1255, windows-1256, windows-1257, windows-1258, windows-31j, x-Big5-HKSCS-2001, x-Big5-Solaris, x-COMPOUND_TEXT, x-euc-jp-linux, x-EUC-TW, x-eucJP-Open, x-IBM1006, x-IBM1025, x-IBM1046, x-IBM1097, x-IBM1098, x-IBM1112, x-IBM1122, x-IBM1123, x-IBM1124, x-IBM1364, x-IBM1381, x-IBM1383, x-IBM300, x-IBM33722, x-IBM737, x-IBM833, x-IBM834, x-IBM856, x-IBM874, x-IBM875, x-IBM921, x-IBM922, x-IBM930, x-IBM933, x-IBM935, x-IBM937, x-IBM939, x-IBM942, x-IBM942C, x-IBM943, x-IBM943C, x-IBM948, x-IBM949, x-IBM949C, x-IBM950, x-IBM964, x-IBM970, x-ISCII91, x-ISO-2022-CN-CNS, x-ISO-2022-CN-GB, x-iso-8859-11, x-JIS0208, x-JISAutoDetect, x-Johab, x-MacArabic, x-MacCentralEurope, x-MacCroatian, x-MacCyrillic, x-MacDingbat, x-MacGreek, x-MacHebrew, x-MacIceland, x-MacRoman, x-MacRomania, x-MacSymbol, x-MacThai, x-MacTurkish, x-MacUkraine, x-MS932_0213, x-MS950-HKSCS, x-MS950-HKSCS-XP, x-mswin-936, x-PCK, x-SJIS_0213, x-UTF-16LE-BOM, X-UTF-32BE-BOM, X-UTF-32LE-BOM, x-windows-50220, x-windows-50221, x-windows-874, x-windows-949, x-windows-950, x-windows-iso2022jp]

newBufferedReader()も

newBufferedReader()も見つけたので使ってみます

package iotest;
import java.io.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedList;
import static java.nio.charset.StandardCharsets.*;
public class ReadAll2 {
    public static void main(String[] args){
        String fname =args[0];
        LinkedList<String> list = new LinkedList<>();
        // List<String> list = null; //Collections.emptyList();
        Path path = Paths.get(fname);
        try {
            BufferedReader inb = Files.newBufferedReader(path, UTF_8 );
            String line;
            while ((line = inb.readLine()) != null) {
                list.add(line);
            }
        }catch (IOException e) {
            System.err.println( e);
        }
        for (String line : list) {
             System.out.println(line);
        }
        System.out.println("以上" + list.size() + "行");
       }
}

ListをArrayListやLinkedListに

拡張for文で使えばListのままで使えますが、ArrayListやLinkedListにしたい場合もあるかも知れません。引数としてLinkedListを使うようになっている場面とか。そんな場合の対処です。

1. 新しい目的のListを生成する方法はどんな時でも使えます

targetMethod(new ArrayList<String>(list));

2. もし、listがArrayListのインスタンスとわかっている時はキャストできます

targetMethod((ArrayList<String>)list);

3. 確証が持てなければ、

if (list instanceof ArrayList){
   targetMethod((ArrayList<String>)list);
}else{
   targetMethod(new ArrayList<String>(list));
}

Files.readAllLines()で得られるListはArrayListでした。

こんな場合に備えて、メソッドを作るときには可能ならばListを引数にすれば良いという事ですね。

closeのためにfainallyを書いてという話

try-with-resources文 や Files.readAllLines()で、close()を排除できるので、過去の話ではあります。

2003年あたりでfinallyを扱った書籍や解説サイトなどを見た記憶がないのですが、確信はありません。try-with-resources文の解説で、昔はclose()をfinallyに書かなければならなかったけど...という記述を目にするようになりました。

多くの場合、IOExceptionはファイルがない場合で、その場合はcloseする必要がないし、開いた後の例外発生の場合でも、Javaのシステムが終了する時に処理をしてくれることが期待できますから、プログラム中でたくさんのファイルを開くのでなければ問題はないと考えられます。

でも礼儀正しくcloseしようとすれば、たしかにこれは面倒でした。

tryの中で BufferedReader inb= new .. のままではfinallyで inb.close(); とはできません。tryの前に BufferedReader inb; の宣言が必要になります。

BufferedReader inb;
try{
  inb = new BufferdReader...
}catch(IOException e){
  ...
}finally{
  inb.close();
}

さらに、「inbは初期化されていない可能性があります」といわれます。初期化しなければ参照型はnullになるはずだから=nullでは変わらないのでは...と思いながらも=nullとすると通ります。

加えて、そもそもopenしていないならcloseは必要ないので、そこも書いておきます

BufferedReader inb = null;
try{
  inb = new BufferdReader...
}catch(IOException e){
  ...
}finally{
  if(inb!=null) inb.close();
}

さらに、inb.close()で「例外IOExceptionは報告されません。」とコンパイラから注意を受けます。ここまで言及していない解説記事も多いので、コンパイラがより厳密に変わったのかも知れません。それに対処するにはこうなります。

BufferedReader inb = null;
try{
  inb = new BufferdReader...
}catch(IOException e){
  ...
}finally{
  if(inb!=null){
    try{
      inb.close();
    }catch(IOException e){
      ...
    }
  }
}

プログラムとして美しくないです。そもそもopen()でなくなったものに、close()をそのままあてがっているところから問題があるといえます。openしていないのにcloseするのは、Javaからプログラム言語の学習を始める人たちには違和感は大きいと思います。disposeとかremoveとかにするべきだったでしょう。