Java 11 Gold 取得に向けた学習記録
IO(入出力)操作
java.io
パッケージ
File
File file = new File("a.txt");
File dir = new File("a");
// カレントディレクトリ
File dir = new File(".");
exists()
File file = new File("a.txt");
System.out.println(file.exists());
// >> false
createNewFile()
try {
File file = new File("a.txt");
file.createNewFile();
} catch (IOException e) {
}
mkdirs()
File dir = new File("a");
dir.mkdirs();
isDirectory()
File dir = new File("a");
System.out.println(dir.isDirectory());
// >> false
dir.mkdirs();
System.out.println(dir.isDirectory());
// >> true
getAbsoluteFile()
File
オブジェクトは本質的には「パス」を扱うクラスなので、実際のファイルの有無は関係ない
File file = new File("a.txt");
System.out.println(file.getAbsoluteFile());
// >> /Users/asahinakei/IdeaProjects/Qiita/a.txt
list()
返される型がString[]
。
File dir = new File(".");
String[] files = dir.list(); // String[]
for (String file : files) {
System.out.println(file);
}
// >> a
// >> gradle
// >> gradlew
// >> build.gradle
// >> .gradle
// >> build
// >> gradlew.bat
// >> settings.gradle
// >> .idea
// >> src
listFiles()
返される型がFile[]
。
File dir = new File(".");
File[] files = dir.listFiles(); // File[]
for (File file : files) {
System.out.println(file.getName());
}
// >> a
// >> gradle
// >> gradlew
// >> build.gradle
// >> .gradle
// >> build
// >> gradlew.bat
// >> settings.gradle
// >> .idea
// >> src
java.nio.file
パッケージ
nio = New IO
java.io.File
クラスには、UNIX系OSにおけるシンボリックリンクやファイル属性が適切に扱えないなどの課題があったことから、Java 7ではjava.nio.file
パッケージが導入された。
java.nio.file
パッケージに含まれるPath
インターフェースとFiles
クラスを利用することで、java.io.File
クラスが適切に扱うことのできなかったファイル属性を簡単に扱ったり、ファイル削除、移動などの高度な操作を簡単に行うことができる。
-
Path
: インターフェース -
Paths
:Path
インターフェースの実装クラスを生成する factory メソッドが定義されたクラス -
Files
:java.io.File
クラスの改良版
Path
/ Paths
Path path = Paths.get("a.txt");
パスを可変長引数によって表現することができる
Path path = Paths.get("aDir", "bDir", "c.txt");
Path
はFile
へ変換することができる。
Path path = Paths.get("a.txt");
File file = path.toFile();
Path
インターフェースの実装は、java.io.File
クラスからも取得することができる。
File file = new File("a.txt");
Path path = file.toPath();
Files
Files
クラスは多くの場合、Files
クラスのstatic
メソッドに対してPath
インターフェースの実装を渡すと言う形で利用する。
exists()
Path path = Paths.get("a.txt");
System.out.println(Files.exists(path));
// >> false
createFile()
作成しようとしているファイルがすでに存在する場合、FileAlreadyExistsException
が発生する。
try {
Path path = Paths.get("a.txt");
Files.createFile(path);
} catch (IOException e) {
}
delete()
削除しようとしているファイルが存在しない場合、NoSuchFileException
が発生する。
try {
Path path = Paths.get("a.txt");
Files.delete(path);
} catch (IOException e) {
}
getPosixFilePermissions()
POSIX = Portable Operation System Interface for UNIX
try {
Path path = Paths.get("a.txt");
System.out.println(Files.getPosixFilePermissions(path));
} catch (IOException e) {
}
// >> [OWNER_READ, OTHERS_READ, OWNER_WRITE, GROUP_READ]
createDirectory()
親ディレクトリの存在を前提にしているため、ディレクトリa
が存在しない場合、NoSuchFileException
が発生する。
また作成対象のディレクトリがまだ存在していないことを前提としているため、c
ディレクトリがすでに存在した場合にも、FileAlreadyExistsException
が発生する。
try {
Path path = Paths.get("a", "b", "c");
Files.createDirectory(path);
} catch (IOException e) {
}
createDirectories()
親ディレクトリ、作成対象ディレクトリの存在を前提としていないため、指定パスの中で存在しないディレクトリがあったとしても、親ディレクトリも含めて作成することができる。
try {
Path path = Paths.get("a", "b", "c");
Files.createDirectories(path);
} catch (IOException e) {
}
resolve()
Path
を連結することで、複数のファイルが同じディレクトリにある場合などに共通パス部分とファイル固有のパス部分を分割することができる。
Path dir = Paths.get("a", "b", "c");
Path file1 = dir.resolve(Paths.get("d.txt"));
Path file2 = dir.resolve(Paths.get("e.txt"));
copy()
try {
Path original = Paths.get("a.txt");
Path copy = Paths.get("a_copy.txt");
Files.copy(original, copy);
} catch (IOException e) {
}
move()
try {
Path source = Paths.get("a" + "b" + "c.txt");
Path destination = Paths.get("a" + "c.txt");
Files.move(source, destination);
} catch (IOException e) {
}
walk()
ファイルツリーを再帰的にトラバース(traverse=横切る)するための便利メソッド。
Path dir = Paths.get("a");
try (Stream<Path> stream = Files.walk(dir)) {
stream.forEach((path) -> {
System.out.println(path.getFileName());
});
} catch (IOException e) {
}
walkFileTree()
ファイルツリーを再帰的にトラバースする際に、トラバースを細かく制御することができる高機能なメソッド。
FileVisitor
インターフェースの実装を渡す必要がある。
FileVisitor
FileVisitor
インターフェースは、walkFileTree()
への引数として利用され、以下の4つの抽象メソッドが定義されている。それぞれのメソッドは各々決められたタイミングでFiles
によって実行される。
事前にコールバック関数を描くメソッドに仕込んでおき、コールバックのセットをwalkFileTree()
へ渡すイメージ。
// 一部省略しています
public interface FileVisitor<T> {
// ディレクトリに入るとき
FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs);
// ファイルに到達したとき
FileVisitResult visitFile(T file, BasicFileAttributes attrs);
// ディレクトリから出るとき
FileVisitResult postVisitDirectory(T dir, IOException exc);
// 処理に失敗したとき
FileVisitResult visitFileFailed(T file, IOException exc);
}
FileVisitResult
処理終了後の動作はenumのFileVisitResult
を返すことで制御ができる。
public enum FileVisitResult {
// トラバースを続行する
CONTINUE,
// トラバースを終了する
TERMINATE,
// このファイルまたはディレクトリの兄弟を visit せずにトラバースを続行する
SKIP_SIBLINGS,
// このディレクトリ内を visit せずにトラバースを続行する
SKIP_SUBTREE,
}
SimpleFileVisitor
FileVisitor
インターフェースには4つのメソッド定義があるため、本来は4つとも実装しなければならないがSimpleFileVisitor
クラスを使用することで、その手間を省くことができる。
SimpleFileVisitor
クラスはすでにFileVisitor
インターフェースを実装しているため、自分がカスタマイズしたいメソッドのみオーバーライドすることができる。
try {
Path dir = Paths.get("a");
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
// visitFile()だけをオーバーライドする
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("hello world");
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
}
ストリーム
java.io
パッケージから提供されるクラスは、入出力を「文字」として扱うのか「バイナリデータ(010111011
みたいな2進数表現)」として扱うのかによって使い分ける。
- 文字データを扱うストリーム
-
input :
Reader
▶️FileReader
/BufferedReader
-
output :
Writer
▶️FileWriter
/BufferedWriter
-
input :
- バイナリデータを扱うストリーム
-
input :
InputStream
▶️FileInputStream
/BufferedInputStream
-
output :
OutputStream
▶️FileOutputStream
/BufferedOutputStream
-
input :
ここでのInputStream
とOutputStream
はJava 1(JDK 1.0)のもので、Java 8 から導入されたストリームAPIとは別のものであることに注意する。
またAutoCloseable
インターフェースやCloseable
インターフェースを実装したクラスをインスタンス化する場合、try-finally
構文で明示的にclose()
するか、もしくはtry-with-resource
構文で自動的にclose()
する必要があるため注意する。
AutoCloseable
、Closeable
インターフェースの実装クラスをインスタンス化する場合、クローズ処理が必要
文字データを扱うストリーム
FileReader
// close が必要
try (FileReader reader = new FileReader("a.txt")) {
} catch (IOException e) {
}
read()
read()
はファイルの終端に到達すると -1
を返す
try (FileReader reader = new FileReader("a.txt")) {
int i = 0;
// read() はファイルの終端に到達すると -1 を返す
while ((i = reader.read()) != -1) {
char c = (char) i;
System.out.print(c);
}
} catch (IOException e) {
}
// >> hello world
// >> hello world
// >> hello world
// >> hello world
// >> hello world
BufferedReader
前述のread()
では、1文字づつファイルへのアクセスを行っている。一般的に、IO(Input/Output)処理はプログラムの実行と比較して大変重たい処理になるため、バッファを使用してファイルシステムへのアクセスを最小限に抑える方が望ましい。
バッファを使用することで入出力内容を一時的にバッファに貯めておくことができるため、ファイルシステムへのアクセス回数が大幅に減りパフォーマンスが向上する。
java.io
パッケージの場合
// close が必要
try (FileReader fileReader = new FileReader("a.txt");
// 生成時に FileReader を渡す必要がある
BufferedReader bufferReader = new BufferedReader(fileReader)) {
} catch (IOException e) {
}
BufferedReader
クラスは、コンストラクがReader
クラスを受け取るようになっている。また、BufferedReader
クラスはReader
クラスを継承しているため、どちらもReader
型として扱うことができる。コンストラクタが受け取ったReader
クラスは内部で使用されていて、一部のメソッドがオーバーライドされることで、Reader
クラスの機能が拡張されている。このようにオブジェクトを別のオブジェクトでラップすることによって機能を拡張するデザインパターンをDecoratorパターンと言う。
java.nio.file
パッケージの場合
Path path = Paths.get("a.txt");
try (BufferedReader bufferReader = Files.newBufferedReader(path)) {
} catch (IOException e) {
}
readLine()
readLine()
はファイルの終端に到達すると null
を返す
try (FileReader fileReader = new FileReader("a.txt");
BufferedReader bufferReader = new BufferedReader(fileReader)) {
String line = null;
// readLine() はファイルの終端に到達すると null を返す
while ((line = bufferReader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
}
// >> hello world
// >> hello world
// >> hello world
// >> hello world
// >> hello world
FileWriter
FileWriter
をインスタンス化する際、指定ファイルが存在しなければその時点でファイルが作成される。
// ファイルが存在しない場合、この時点で新規作成される(close が必要)
try (FileWriter writer = new FileWriter("a.txt")) {
} catch (IOException e) {
}
write()
write()
はデフォルトでは上書きモードで書き込みを行う。
// デフォルトでは上書きモード
try (FileWriter writer = new FileWriter("a.txt")) {
writer.write("hello world");
} catch (IOException e) {
}
追記モードで書き込みを行いたい場合は、FileWriter
のインスタンス化の際に設定を行う。
// 追記モード
try (FileWriter writer = new FileWriter("a.txt", true)) {
writer.write("hello world");
} catch (IOException e) {
}
BufferedWriter
java.io
パッケージの場合
try (FileWriter fileWriter = new FileWriter("a.txt");
// 生成時に FileWriter を渡す必要がある(close が必要)
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) {
} catch (IOException e) {
}
java.nio.file
パッケージの場合
Path path = Paths.get("a.txt");
try (BufferedWriter bufferWriter = Files.newBufferedWriter(path)) {
} catch (IOException e) {
}
Path path = Paths.get("a.txt");
try (BufferedWriter bufferWriter = Files.newBufferedWriter(path, StandardOpenOption.APPEND)) {
} catch (IOException e) {
}
write()
try (FileWriter fileWriter = new FileWriter("a.txt");
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) {
for (int i = 0; i < 5; i++) {
bufferedWriter.write("hello world");
bufferedWriter.newLine(); // 改行
}
// メモリ(上のバッファ)とファイルが同期される
bufferedWriter.flush();
} catch (IOException e) {
}
バイナリデータを扱うストリーム
FileInputStream
/ BufferedInputStream
// close が必要
try (FileInputStream fileInputStream = new FileInputStream("a.png");
// 生成時に fileInputStream を渡す必要がある
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream)) {
} catch (IOException e) {
}
FileOutputStream
/ BufferedOutputStream
// close が必要
try (FileOutputStream fileOutputStream = new FileOutputStream("a.png");
// 生成時に fileOutputStream を渡す必要がある
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream)) {
} catch (IOException e) {
}
コンソール
コンソールからの入力を受け付けたい場合、java.lang.System
クラスを使用する。ただし、この方法は後述するjava.io.Console
クラスによってさらに簡略化することができる。
System
を使う方法
System
クラスを利用する場合、バイナリデータを扱うストリームを文字を扱うストリームに変換する処理が必要になる。
この変換はブリッジと呼ばれ、java.io.InputStreamReader
クラスによって行うことができる。InputStreamReader
クラスはバイナリデータ(InputStream
)を文字エンコーディングを使用して文字データ(Reader
)にデコードすることができる。
この辺りが非常にややこしいので再掲。
- 文字データを扱うストリーム
- input :Reader
▶️FileReader
/BufferedReader
- output :Writer
▶️FileWriter
/BufferedWriter
- バイナリデータを扱うストリーム
- input :InputStream
▶️FileInputStream
/BufferedInputStream
- output :OutputStream
▶️FileOutputStream
/BufferedOutputStream
// 通常 InputStream ▶️ BufferedInputStream
// 変換時 InputStream ▶️ 【InputStreamReader】 ▶️ BufferedReader
try (InputStream inputStream = System.in;
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferReader = new BufferedReader(inputStreamReader)) {
String input = bufferReader.readLine();
System.out.println(input);
} catch (IOException e) {
}
// コンソールが入力を待つ
// 「hello world」とコンソールへ入力してEnter
// >> hello world
Console
を使う方法
Console
はコンソールからの入出力をサポートするクラスなので、IDEのツールからプログラムを実行した場合にはSystem.console()
がnull
を返すことがあるため注意。
Console console = System.console();
if (console != null) {
String input = console.readLine();
System.out.println(input);
}
// コンソールが入力を待つ
// 「hello world」とコンソールへ入力してEnter
// >> hello world
Scanner
を使う場合
標準入力(System.in
)からのデータの読み取りをサポートするクラス。
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
System.out.println(input);
// コンソールが入力を待つ
// 「hello world」とコンソールへ入力してEnter
// >> hello world
FileReader
vs InputStream
テキストファイルを読み込む場合、FileReader
を使用すべきなのか、InputStream
を使用すべきなのか。
FileReader
記述が簡単な一方で、プラットフォームのデフォルトエンコーディングを使用するため、特定のエンコーディングを指定する場面に不向き。
InputStream
エンコーディングを指定して読み取りを行うことができる。