LoginSignup
28
27

More than 5 years have passed since last update.

ファイルを読み込むメソッドの引数はInputStreamが良い、と思う。

Last updated at Posted at 2015-08-04

はじめに

ファイルを読み込むメソッドを作成しようとする。引数として考えるのが、
・ファイル名のString型
・ファイルのFile型
が考えられるが、InputStreamが良いのではないか、と結論付けた。
理由としては「テストしやすい」からである。
StringやFileだと、どうしてもファイルの場所を指定するので、ファイル構造を変更すると、ファイルの場所をすべて書き換えないといけない。そこでテストに使用するファイルはgetResourceAsStream()で指定し、テストクラスと同じ場所に置いてしまうのが、一番柔軟性が高いと判断した。

以下、テスト方法や実際の実装方法をまとめた。

実際のソース

ということで、テスト対象のメソッド。引数はInputStreamにしてます。

InputStreamMethod.java
/**
 * 一行で「グループ名:メンバー1,メンバー2,メンバー3」という形式のファイルを読み込む。<br/>
 * メンバーをキーにして、どのグループに所属しているか確認できるMapを返す。
 * @param inputStream 読み込みファイル
 * @return メンバーをキーにして、どのグループに所属しているか確認できるMap
 */
public Map<String, String> read(InputStream inputStream){
    try(BufferedReader reader = new BufferedReader(
            new InputStreamReader(inputStream, Charset.forName("UTF-8")))){

        Map<String, String> result = new HashMap<>();

        String line;
        while((line = reader.readLine()) != null){

            // グループ名
            String group = line.substring(0, line.indexOf(":"));

            // メンバー名
            for (String member: line.substring(group.length() + 1).split(",")){
                result.put(member, group);
            }
        }

        return result;

    }catch (IOException | IllegalArgumentException | StringIndexOutOfBoundsException e) {
        // 複数例外catchを使用。初めて使うw

        System.err.println("グループの設定ファイルがおかしい");
        return null;
    }
}

さて、「いざファイルを読み込ませよう!」と思う前に、不安だから一行だけの読み込ませてみるか。でも、一行のファイルを作るのはめんどくさいから、ByteArrayInputStreamで引数に渡すInputStreamをテストソース内で作成しよう。

InputStreamMethodTest.java
@Test
public void read1行(){

    // 一行お試し。メモリー上にストリームを作成して、テストを実施する
    ByteArrayInputStream stream = new ByteArrayInputStream("東北:青森県,岩手県,秋田県,宮城県,山形県,福島県".getBytes());

    InputStreamMethod target = new InputStreamMethod();
    Map<String,String> result = target.read(stream);

    assertThat(result.get("青森県"), is("東北"));
    assertThat(result.get("秋田県"), is("東北"));
    assertThat(result.get("福島県"), is("東北"));
}

複数行だと、どうかな?

InputStreamMethodTest.java
@Test
public void read3行(){
    StringBuffer sb = new StringBuffer();
    sb.append("北海道:北海道").append(System.lineSeparator());
    sb.append("東北:青森県,岩手県,秋田県,宮城県,山形県,福島県").append(System.lineSeparator());
    sb.append("関東:茨城県,栃木県,群馬県,埼玉県,千葉県,東京都,神奈川県").append(System.lineSeparator());

    ByteArrayInputStream stream = new ByteArrayInputStream(sb.toString().getBytes());

    InputStreamMethod target = new InputStreamMethod();
    Map<String,String> result = target.read(stream);

    assertThat(result.get("北海道"), is("北海道"));
    assertThat(result.get("青森県"), is("東北"));
    assertThat(result.get("神奈川県"), is("関東"));
}

よし、大丈夫そうだ。じゃあ、実際のファイルを読ませるか。

InputStreamMethodTest.java
@Test
public void read() {

    InputStreamMethod target = new InputStreamMethod();
    Map<String,String> result = target.read(getClass().getResourceAsStream("InputStreamMethodTest.txt"));

    assertThat(result.get("北海道"), is("北海道"));
    assertThat(result.get("青森県"), is("東北"));
    assertThat(result.get("秋田県"), is("東北"));
    assertThat(result.get("福島県"), is("東北"));
    assertThat(result.get("神奈川県"), is("関東"));
    assertThat(result.get("鳥取県"), is("中国"));
    assertThat(result.get("徳島県"), is("四国"));
    assertThat(result.get("沖縄県"), is("九州"));

    assertThat(result.get("うどん県"), is(nullValue()));
}

テストで読み込むテキストファイル。InputStreamMethodTest.javaと同じ場所に配置するのがミソです。

InputStreamMethodTest.txt
北海道:北海道
東北:青森県,岩手県,秋田県,宮城県,山形県,福島県
関東:茨城県,栃木県,群馬県,埼玉県,千葉県,東京都,神奈川県
中部:山梨県,長野県,新潟県,富山県,石川県,福井県,静岡県,愛知県,岐阜県
近畿:三重県,滋賀県,京都府,大阪府,兵庫県,奈良県,和歌山県
中国:鳥取県,島根県,岡山県,広島県,山口県
四国:香川県,愛媛県,徳島県,高知県
九州:福岡県,佐賀県,長崎県,熊本県,大分県,宮崎県,鹿児島県,沖縄県

ファイルのフォーマットがおかしかったときのテストもしておこう。

InputStreamMethodTest.java
@Test
public void readファイルエラー(){

    ByteArrayInputStream stream = new ByteArrayInputStream("東北青森県,岩手県,秋田県,宮城県,山形県,福島県".getBytes());

    InputStreamMethod target = new InputStreamMethod();
    assertThat(target.read(stream), is(nullValue()));
}

では、肝心のメイン文を、と。

InputStreamMethod.java
public static void main(String[] args) throws FileNotFoundException {

    File file = new File("src/streamtest/InputStreamMethodTest.txt");
    if (! file.exists()){
        System.err.println("ファイルがありません:" + file.getAbsolutePath());
        System.exit(-1);
    }

    InputStreamMethod target = new InputStreamMethod();

    Map<String, String> map = target.read(new FileInputStream(file));
    map.keySet().stream()
    .map(key -> key +":" + map.get(key))
    .forEach(System.out::println);
}

ということで、完成。

ひとまず、まとめ

以前書いたソースの引数をStringにしていたが、Maven導入でフォルダ構成がちょっと変わったので、格好悪い感じになっています。どうやったら、うまくいくか、と考えたとき、InputStreamを引数にするのが柔軟性があるかなと思いました。実際業務でも書いてみましたが、いい感じに収まった気がします。

closeって入れ子でも呼ばれるのか

こう書いてみて、引数にとったInputStreamってちゃんとcloseしているのか、不安になりました。InputStreamMethod.readの内部でByteArrayInputStream streamを使用したInputStreamReaderをtryで囲んでいるが、この場合closeが呼ばれているか気になったのでテスト。呼ばれているようだ。

InputStreamMethodTest.java
@Test
public void close(){

    final StringBuilder sb = new StringBuilder();

    ByteArrayInputStream stream =
            new ByteArrayInputStream("東北:青森県,岩手県,秋田県,宮城県,山形県,福島県".getBytes())
            {
                @Override
                public void close() throws IOException{
                    sb.append("stream was closed");
                    super.close();
                }
            };

    InputStreamMethod target = new InputStreamMethod();
    Map<String,String> result = target.read(stream);

    assertThat(sb.toString(), is("stream was closed"));
}


@Test(expected = IOException.class)
public void closeFile() throws IOException {

    InputStream stream = getClass().getResourceAsStream("InputStreamMethodTest.txt");
    InputStreamMethod target = new InputStreamMethod();
    Map<String,String> result = target.read(stream);

    stream.read();
    org.junit.Assert.fail("ファイルをcloseした状態でreadしているので、IOExceptionが発生するはず");
}

ファイル出力もOutputStreamを引数に?

ついでにファイル出力も引数をOutputStreamにした方が良いかとやってみた。

OutputStreamMethodTest.java
@Test
public void write_1データ() throws IOException {

    OutputStreamMethod target = new OutputStreamMethod();
    target.put("北海道", "北海道");

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    target.write(out);

    assertThat(out.toString(), is("北海道:北海道"));
}

@Test
public void write_1行() throws IOException {

    OutputStreamMethod target = new OutputStreamMethod();
    target.put("青森県", "東北");
    target.put("岩手県", "東北");
    target.put("秋田県", "東北");

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    target.write(out);

    assertThat(out.toString(), is("東北:岩手県,秋田県,青森県"));
}

@Test
public void write_3行() throws IOException {

    OutputStreamMethod target = new OutputStreamMethod();
    target.put("北海道", "北海道");
    target.put("青森県", "東北");
    target.put("岩手県", "東北");
    target.put("秋田県", "東北");
    target.put("茨城県", "関東");
    target.put("栃木県", "関東");
    target.put("群馬県", "関東");
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    target.write(out);

    String[] expected = {
            "北海道:北海道"
            ,"東北:岩手県,秋田県,青森県"
            ,"関東:栃木県,群馬県,茨城県"
            };

    assertThat(out.toString(), is(String.join(System.lineSeparator(), expected)));
}
OutputStreamMethod.java
Map<String, String> map = new HashMap<>();

public String put(String key, String value ){
    return map.put(key, value);
}

public void write(OutputStream outputtStream) throws IOException{

    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputtStream));

    String[] groups =
            map.values().stream()
            .distinct()
            .sorted()
            .toArray(count->new String[count]);


    for (int i = 0; i < groups.length; i++){

        final String group = groups[i];

        String[] members =
                map.keySet().stream()
                .filter(p-> group.equals(map.get(p)))  // 同じグループのみでフィルタリングしてる
                .sorted()
                .toArray(count -> new String[count]);

        writer.write(group  + ":" + String.join(",", members));

        // 最終グループでないときは、改行を入れる
        if (i != groups.length -1){
            writer.newLine();
        }
    }

    writer.flush();
}

public static void main(String[] args) throws FileNotFoundException, IOException {
    File file = new File("src/streamtest/OutputStreamMethodTest.txt");
    if (file.exists()){
        System.err.println("ファイルがあります:" + file.getAbsolutePath());
        System.exit(-1);
    }

    OutputStreamMethod target = new OutputStreamMethod();
    target.put("北海道", "北海道");
    target.put("青森県", "東北");
    target.put("岩手県", "東北");
    target.put("秋田県", "東北");
    target.put("茨城県", "関東");
    target.put("栃木県", "関東");
    target.put("群馬県", "関東");

    target.write(new FileOutputStream(file));
}

よし。いけてる。

ソースは
https://github.com/xaatw0/quiita/tree/master/src/streamtest
に落ちてます。

28
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
27