せっかくJava8の時代(いつの話ですか!)になったのに、未だにnew File("xxxx")なんてコードをよく見かけます。
何故!!!!!!!!!!
ということで、Javaによる最新(いつの話ですか!)ファイルI/Oのご紹介です。
java.nio.fileパッケージには、ファイルやディレクトリを操作する便利なクラスやインターフェイスが用意されています。
その中でも、ベースとなるのが多分Pathインターフェイスでしょう。
下記のようにして作成します。
下記のような記述でも作成できます。
上記の、path1とpath2は同じファイルを表現するので、equalsメソッドで比較するとtrueが返ります。
但し、下記のような記述では注意が必要です。
Windowsの環境では、問題なくファイルパスと認識されるのですが、私の環境(Linuxのデスクトップ)では単一のファイル(名前)と認識されてしまいます。
逆に、Windows環境で"/home/foo/currentDir/java.txt"を引数に作成した場合は、正しいファイルパスとして認識されます。
このクラスには、ファイルパスを操作する便利な機能が様々実装されています。基本的にはJavaDocを参照していただくとして、よく使うと思われるいくつかを紹介します。
上記コードは、私の環境では下記のように出力します(カレントディレクトリが/home/foo/currentDirだとすると)。
java.txt /home/foo/currentDir/java.txt
Windows環境では下記のように出力します(カレントディレクトリがC:\home\foo\currentDirだとすると)。
java.txt C:\home\foo\currentDir\java.txt
上記コードは、下記のように出力します(カレントディレクトリが/home/foo/currentDirだとすると)。
/home/foo/currentDir
上記コードは、下記のように出力します(カレントディレクトリが/home/foo/currentDirだとすると)。
/home/foo/currentDir/c.txt
上記コードは、下記のように出力します(カレントディレクトリが/home/foo/currentDirだとすると)。
/home/foo/currentDir/c.txt
(3)と同じことを、わざわざ親ディレクトリを取得しないで実現できます。
上記コードは、下記のように出力します。
java.txt java.txt
ありますよ。
論理パスではなく、実際のファイルやディレクトリを操作するためには、java.nio.file.Filesクラスを使います。
このクラスには、ファイルやディレクトリを操作/参照する便利な機能が様々実装されています。こちらも基本的にはJavaDocを参照していただくとして、よく使うと思われるいくつかを紹介します。
pathに該当するファイルやディレクトリが存在すれば、trueが返ります。
FileTimeクラスの文字列表現は、UTCのISO 8601形式となります。
2020-01-30T08:10:51.365231Z
少々本題からは外れますが、UTCでは使い難いのでFileTimeをローカル日付に変換する方法です。
(2)のソースの続きです。
2020/01/30 17:10:51
DateTimeFormatter#ofPatternが返すインスタンスはスレッドセーフなので、SimpleDateFormatの様に使う度に作成する必要はありません。
だいぶ本題から外れますが、和暦で表示する方法です。
LocalDateTimeを取得する箇所までは、(3)と同じです。
和暦で表示する場合は、java.time.chrono.JapaneseDateを使います。
3行目で、LocalDateTimeからJapaneseDateに変換しています。
但し、JapaneseDateは日付しか保持しないので、時分秒までフォーマットしようとすると、例外がスローされます。
このため、時刻はLocalDateTimeから取得します。
令和2年1月30日 17時10分51秒
令和に対応していないバージョンのJDKを使った場合は、当然「令和」とは表示されません。
ファイルの読み込みは、このクラスのおかげでとても簡単になりました。ワンステップです。
読み込んだサイズ:57バイト
こちらも、ワンステップでリストに格納してくれるので、とても簡単です。
このファイルは、日本語です SJISでエンコードされています
もちろん文字コードも指定できます。
文字コードを指定しない場合(Files#readAllLinesの第ニ引数がないバージョン)は、ファイルの内容がUTF-8としてデコードされます。
環境依存ではなくなったということですね。
またまた本題からはそれますが、Charsetのおかげで、地味にマルチバイト文字のエンコード/デコードが楽になりました。
String#getBytes(String)は例外をスローするのでtry〜catchで囲わざるを得ず、テストでカバレッジを100%にしたいがために、不自然なコーディングを余儀なくされたものでした。
しかし、Charset#forNameはランタイム例外しかスローしないので、無駄なtry〜catchも必要なくなりました。
まあ、String#getBytes(Charset)はJDK1.6からのサポートなので、今更何をって感じですかね。
おっと、一気にJava8っぽくなってきました。
関数型インターフェイスにアロー演算子、ラムダ式の登場です。
関数型プログラミングってやつですね。CPUのマルチコア化が進んで注目されているパラダイムです。オブジェクト指向と比べて...。おっと、このあたりは沼なので、さらっとこの程度で(^_^;
上記コードは関数型インターフェイスを強調するためにConsumerを登場させていますが、下記のようにも記述できます(こちらのほうが一般的でしょうか)。
Files#linesが返すStreamは、Files#readAllLinesと違い(関数型プログラミングなので)遅延評価されます。
上限が不明な巨大ファイルを操作するような場合、Files#readAllLinesは危険で使えませんが、Files#linesを使えば一定サイズのバッファリングで処理される(筈)ので、巨大なメモリを用意しなくても大丈夫です。
注意が必要なのは、エラーが発生するタイミングです。
JavaDocにも記述されていますが、Files#linesがスローするIOExceptionは、ファイルオープン時のエラーです。
リード中のエラーは、Streamの評価中にUncheckedIOExceptionとしてスローされます。
上記コード上では、4行目に当たります。そのため、7行目でキャッチしている例外をExceptionとしています。
また、全ての処理が終了した時点でStreamのクローズも必要です(特にサーバの処理では、ガベージコレクションに任せると痛い目にあいます)。
ファイルの書き込みも、このクラスのおかげでとても簡単になりました。ワンステップです。
現在のサイズ:7バイト
こちらも、ワンステップでリストから書き込んでくれるので、とても簡単です。
現在のサイズ:56バイト
書き込む時に、環境標準の改行コードが各行の終端に付加されます。
Files#writeは、第4引数以降(Charsetを指定する場合。Charsetを指定しない場合は第3引数以降)に、ファイルをオープンする際のオプションを指定できます。
オプションを指定しない場合、指定のファイルが存在しなければ作成し、存在した場合はオープン時に、サイズ0となります。
先程はListを渡しましたが、Files#writeの第2引数はjava.lang.Iterableなので、事象が発生した時点で随時書き込むということも可能です。
hasNextの中で、事象の発生か終了を待って値を返すようにすれば、随時書き込みの完成です。
以下のようなディレクトリ構成を前提として説明します。
${user.home}/
+tmp/
+test/
+sub_dir_01/
| +sub_dir_text.txt
+sub_dir_02/
| +sub_dir_text.txt
| +sub_dir_html.htm
| +sub_dir_html.html
+text_file_01.txt
+text_file_02.txt
+html_file_01.html
${user.home}/tmp/test/text_file_01.txt
${user.home}/tmp/test/text_file_02.txt
${user.home}/tmp/test/html_file_01.html
${user.home}/tmp/test/sub_dir_01
${user.home}/tmp/test/sub_dir_02
指定のパス(この場合は${user.home}/tmp/test)の直下にあるファイルとディレクトリを一覧します。
${user.home}/tmp/test
${user.home}/tmp/test/text_file_01.txt
${user.home}/tmp/test/text_file_02.txt
${user.home}/tmp/test/html_file_01.html
${user.home}/tmp/test/sub_dir_01
${user.home}/tmp/test/sub_dir_01/sub_dir_text.txt
${user.home}/tmp/test/sub_dir_02
${user.home}/tmp/test/sub_dir_02/sub_dir_text.txt
${user.home}/tmp/test/sub_dir_02/sub_dir_html.html
${user.home}/tmp/test/sub_dir_02/sub_dir_html.htm
指定のパスを含んだ配下にある全てのファイルとディレクトリを一覧します。
${user.home}/tmp/test/text_file_01.txt
${user.home}/tmp/test/text_file_02.txt
複数の拡張子を指定する場合は、下記のように指定します。
${user.home}/tmp/test/sub_dir_02/sub_dir_html.html
${user.home}/tmp/test/sub_dir_02/sub_dir_html.htm