"Simple" is "Best"

開発時に詰まったところや 調べた結果日本語での情報が無かったり古かったりした場合に自分用のメモとして中学生(?)が更新していくブログ

Fileを入出力するためのクラスを作ってみる

無事忌々しいテストも終わったもんだから

テスト期間中に組んだFileの入出力クラスをメモがてら紹介する

 

成り立ち

Javaから外部資源としてFileを読み書きするには

少々長ったらしい文を書かなくてはいけない

昔、Androidのクイズアプリを作った際に

CSVで問題データを保持させようということで

そのときに初めてファイルの入出力をいじった

非常に書きにくい

特に当時はリファレンスを見るという力も無かったため

色んな情報に左右されながら長ったらしいコードを書いていた

いま計画中のアプリでもCSVを使おうと思っているのでその前準備

 

何をするクラスか

大体Javaでファイルを扱うときは(ファイル管理ソフトでも作らない限りは)

新規作成・書き込み・読み込みこのぐらいができれば十分であろう

ついでにファイル削除もつけて、計4機能

こいつらを実装させていく

基本的にError処理は利用者任せにするため、全てthrow

 

使う機能

機能というと語弊があるかもしれないが気にせずに

今回のクラスはnioPathsを使ってファイルを操作する

ファイルの読みはBufferedReader、書きはPrintWriterを使う

 

とりあえずフィールド変数

今回使用する変数の方々

 


    
    /**
     * ファイルの生成、削除、名前変更を担当
     * */
    private Path path;
    /**
     * 文字コード設定のための定数
     * */
    private static final Charset UTF_8 = Charset.forName("UTF-8");

    private static final String BR = System.lineSeparator();
    private static final Pattern p = Pattern.compile(BR);

    /**
     * 読み込み用
     * */
    private BufferedReader reader;
    /**
     * 書き込み用
     * */
    private PrintWriter writer;
    private BufferedWriter bufferedWriter;

    /**
     * 読み込みの際に文字列連結高速化
     * */
    private StringBuilder content;

今回はちょっとJavaDocも書いてみた

ちょっと変わったところがこの二行

    private static final String BR = System.lineSeparator();
    private static final Pattern p = Pattern.compile(BR);

一行目は改行コードを取っているのだが

その改行コードがOSによって異なっているので

OSから改行コードをもらってきている

そのOS依存の改行コードを使ってパターンを作っているのが二行目

あとでこれを使ってゴニョゴニョする

今気づいたがあまりこれは意味がない・・・

 

続いてコンストラクタ


       /**
     * パスが文字列で与えられたときのコンストラクタ
     * @param pathName 操作したいファイルのフルパスを渡す
     * */
    public FileManager(String pathName) throws IOException {
        path = Paths.get(pathName);
        init(true);
    }

    /**
     * File型が与えられたときのコンストラクタ
     * @param file 操作したいファイルのFile型を渡す
     * */
    public FileManager(File file) throws IOException {
        path = file.toPath();
        init(true);
    }

    /**
     * Path型が与えられたときのコンストラクタ
     * @param path 操作したいファイルのPath型を渡す
     * */
    public FileManager(Path path) throws IOException {
        this.path = path;
        init(true);
}

3種類の型に対応できるようにした

今頃Fileという感じもするが

古いAPIだと返り値がFileということがあるらしい

init()の内容はその名の通り各フィールド変数の初期化


        private void init() throws IOException {
        bufferedWriter = Files.newBufferedWriter( path, UTF_8, StandardOpenOption.APPEND);
        writer = new PrintWriter(bufferedWriter, true);
        reader = Files.newBufferedReader( path, UTF_8 );
        content = new StringBuilder();
}

ここで注意しておきたいのが

bufferedWriter = Files.newBufferedWriter( path, UTF_8, StandardOpenOption.APPEND);

StandardOpenOption.APPENDをしっかりつけないと

実行時にファイルがまっさらになる

ほかは特に注意点なし

bufferedWriter = Files.newBufferedWriter( path, UTF_8, StandardOpenOption.APPEND);
reader = Files.newBufferedReader( path, UTF_8 );

こいつらは省エネな書き方

File file = new File(file_name);
BufferedWriter bw = new BufferedWriter(new FileWriter(file));

これが普通の書き方

最初に紹介したほうが個人的には好き

 

新規作成・削除

難しい処理は書くことなく、ほぼワンライン

無事に作成・削除できたかを知らせるboolean型の返り値を設定してある


    /**
     * ファイルを新規作成するためのメソッド
     * @return 作成に成功すればTrue, 既にファイルが存在する場合はFalse
     * @throws IOException 入出力エラーが発生した場合
     * */
    public boolean create() throws IOException {
        return path.toFile().createNewFile();
    }

    /**
     * ファイルを削除するためのメソッド
     * @return 削除に成功すればTrue, 失敗すればFalse
     * @throws IOException 入出力エラーが発生した場合
     * */
    public boolean delete() throws IOException {
        Files.delete(path);
        return !( exists() );
    }

説明はDocに書いてある通り

exists()っていうのは

ファイルが存在しているかどうかを調べるメソッド

以下の通りワンライン

public boolean exists() {
   return Files.exists(path);
}

外からでも有無を確認できるようにpublicにしてある

 

ここからが本番、読み込み

ここからがようやく本番といった感じ

いかにに無駄なく素早く処理するか

素人ながら色々工夫をして書いた


        /**
     * ファイルの中身を読み込むためのメソッド
     * @return ファイルの中身の文字列
     * @throws IOException 入出力エラーが発生した場合
     * @throws NullPointerException ファイルが空だった場合
     * */
    public String read() throws IOException, NullPointerException {
        if (exists() && Files.isReadable(path)) {
            String buffer;
            do {
                buffer = reader.readLine();
                content.append( buffer ).append( BR );
            } while (buffer != null);
            return content.toString();
        } else if ( !exists() ) {
            System.err.println("Couldn't find File");
        } else if ( !Files.isWritable(path) ) {
            System.err.println("Have no authority to read");
        }
        return null;
}

最初のif文で存在しているか&読み込み権限があるかを見ている

あとは定型文

これを何回も書きたくないからこのクラスを作ったようなもの

if文でboolen型を!をつけてFalseの場合に反転させているが

== falseで書いたほうが読みやすい気はする

 

仕上げの書き込み

最後の難関

    /**
     * ファイルに書き込むためのメソッド
     * @param text 書き込みたい内容
     * @throws IOException 入出力エラーが発生した場合
     * */
    public void write(String text) throws IOException {
        if (exists() && Files.isWritable(path)) {
            if (p.matcher(text).find()) {
                String[] buffer = text.split(BR, 0);
                for (String line : buffer) {
                    writer.println(line);
                }
            } else {
                writer.write(text);
            }
            writer.flush();
        } else if ( !exists() ) {
            System.err.println("Couldn't find File");
        } else if ( !Files.isWritable(path) ) {
            System.err.println("Have no authority to write");
        }
    }

ここで出てくるPattern

もし改行がある場合はそれを反映させ、ない場合はそのまま書き込む

この改行を楽にするためにPrintWriterを使用している

とは言えどもBufferedWriterにもnewLine()なんてのがあるので

素直にBufferedWriterを使えばよかった気もする

 

忘れられがちなClose

読み書きのメソッドを見て、close()がないことに気づかれただろう

忘れていたわけではなく、あえて書かなかったのだ

別にメソッドとして定義している

public void close_r() throws IOException {
  reader.close();
}

public void close_w() throws IOException {
   writer.close();
}

案の定のワンライン

なぜ読み書きの処理に埋め込まずに別途定義しているのか

それはそれぞれのメソッドがエラーを利用者に委ねているため

本来close()をいうのはfinallyの中に書かなくてはいけない

しかし、エラーを投げてしまっているため

finallyもクソもないのである

そのため、苦肉の策として別途定義することで

利用者側にfinallyでしっかり閉めてもらうことができる

私自身、かなり嫌々この方式を取った

もし何か良い策があったら連絡下さい

 

最後に

作ってから気づく実際に使えるのか感

実践で使ったことがないので何とも言えないが

あまり使える気がしない

今度使った後に感想でも書くこととしよう

 

このコードはGithubに上げてある

以下のリンクからすべてを見ることができる

github.com

Github上では、今回紹介した以外のメソッドも幾つか書いてあるので

気になる方はぜひご一読を

 

以上

次はCSVのパーサーでも自作したい