S-JIS[2011-07-30/2015-12-12] 変更履歴

ファイル・ディレクトリ操作2

Javaのファイル操作クラスは、JDK1.7(Java7)で新しいものが導入された。(通称NIO2(New I/O 2)。自分はニオツーって呼んでる)
旧ファイル操作

Filesクラスでファイルのコピーや移動などを行うことが出来る。
また、ファイルやディレクトリーの場所を表すPathというインターフェースが新たに設けられた。


Path

java.nio.file.Pathは、ファイルやディレクトリーの場所を表すインターフェース。
従来はjava.io.Fileクラスがその役割だった。

import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
	FileSystem fs = FileSystems.getDefault();
	Path path1 = fs.getPath("C:/temp/zzz.txt");
	Path path2 = fs.getPath("C:/temp/", "zzz.txt");
	Path path3 = fs.getPath("C:", "temp", "zzz.txt");

(→FileSystemsを使ってzipファイルを読み書きする方法


Pathsを使うことも出来る。[2014-03-21]

import java.nio.file.Path;
import java.nio.file.Paths;
	Path path = Paths.get("C:", "temp", "zzz.txt");

Pathを生成するのにFileを使うことも出来る。

import java.io.File;
import java.nio.file.Path;
	Path path = new File("C:/temp/zzz.txt").toPath();

//	File file = path.toFile();

Files

Filesクラスにファイルを扱うメソッドが色々用意されている。
Filesクラスでは、ファイルやディレクトリーの指定をPathで行う。

import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;

ファイル操作系

説明 備考
ファイルコピー Path src = ;
Path dst = ;
Path r = Files.copy(src, dst, StandardCopyOption.COPY_ATTRIBUTES);
ファイルをコピーする。
正常に終了すると、コピー先ファイルのPathが返ってくる。
コピー元ファイルが無ければNoSuchFileException
コピー先ファイルが存在していたらFileAlreadyExistsException
が発生する。
JDK1.6以前は、自分でコピーロジックを書く必要があった。
チャネルを使ってコピーする方法
Path src = ;
Path dst = ;
Files.copy(src, dst, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
コピーオプションは可変長引数になっており、複数のオプションを指定できる。
CopyOption
REPLACE_EXISTING コピー先ファイルが存在していたら(エラーにせず)上書きする。
COPY_ATTRIBUTES コピー元ファイルの属性(作成日時とか)もコピーする。
ATOMIC_MOVE OSの機能を使ってアトミックに移動する。
copy()では使えない。
NOFOLLOW_LINKS シンボリックリンクをフォローしない。
ファイル移動 Path src = ;
Path dst = ;
Path r = Files.move(src, dst, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
ファイルを移動する。 File#renameTo()
ファイルからストリームへ出力 try (OutputStream os = ) {
  Path src = ;
  long r = Files.copy(src, os);
}
ファイルの内容をOutputStreamへ出力する。
戻り値はファイルサイズ。
 
ストリームからファイルへ出力 try (InputStream is = ) {
  Path dst = ;
  long r = Files.copy(is, dst, StandardCopyOption.REPLACE_EXISTING);
}
InputStreamからファイルへ出力する。
戻り値はファイルサイズ。
 
バイナリファイルの読み込み Path src = ;
try (InputStream is = Files.newInputStream(src, StandardOpenOption.READ,…)) {
  //isを使った読み込み is.read()
}
ファイルを読み込む。  
Path src = ;
byte[] bytes = Files.readAllBytes(src);
ファイル内の全データをバイト配列に読み込む。 JDK1.6以前で全データを読み込む方法
バイナリファイルの書き込み Path dst = ;
try (OutputStream os = Files.newOutputStream(dst, StandardOpenOption.WRITE,…)) {
  //osを使った書き込み os.write()
}
ファイルへ書き込む。
(このメソッドでは出力先ファイルが既に存在していてもエラーにならない模様)
 
byte[] bytes = 〜;
Path dst = ;
Path r = Files.write(dst, bytes, StandardOpenOption.WRITE,…);
バイト配列の全データをファイルに書き込む。
戻り値は出力先ファイル名。
(このメソッドでは出力先ファイルが既に存在していてもエラーにならない模様)
JDK1.6以前で全データを書き込む方法
テキストファイルの読み込み Path src = ;
try (BufferedReader br = Files.newBufferedReader(src, Charset.forName("MS932"))) {
  //brを使った読み込み br.readLine()
}
テキストファイルを読み込む。  
Path src = ;
try (BufferedReader br = Files.newBufferedReader(src)) {
  //brを使った読み込み br.readLine()
}
UTF8の場合はCharsetを省略できる。(JDK1.8以降)[2014-04-29]  
Path src = ;
List<String> list = Files.readAllLines(src, Charset.forName("MS932"));
テキストファイルの全行を読み込む。 JDK1.6以前で全テキストを読み込む方法
Path src = ;
List<String> list = Files.readAllLines(src);
UTF8の場合はCharsetを省略できる。(JDK1.8以降)[2014-04-29]  
Path src = ;
try (Stream<String> stream = Files.lines(src, Charset.forName("MS932"))) {
  stream.forEach(System.out::println);
}
テキストファイルの全行をStreamとして読み込む。(JDK1.8以降)[2014-04-29] BufferedReaderのlines()
Path src = ;
try (Stream<String> stream = Files.lines(src)) {
  〜
}
UTF8の場合はCharsetを省略できる。[2014-04-29]  
テキストファイルの書き込み Path dst = ;
try (BufferedWriter bw = Files.newBufferedWriter(dst, Charset.forName("MS932"), StandardOpenOption.WRITE,…)) {
  //bwを使った書き込み bw.write()
}
テキストをファイルへ書き込む。
(このメソッドでは出力先ファイルが既に存在していてもエラーにならない模様)
 
Path dst = ;
try (BufferedWriter bw = Files.newBufferedWriter(dst, StandardOpenOption.WRITE,…)) {
  //bwを使った書き込み bw.write()
}
UTF8の場合はCharsetを省略できる。(JDK1.8以降)[2014-04-29]  
List<String> list = 〜;
Path dst = ;
Path r = Files.write(dst, list, Charset.forName("MS932"), StandardOpenOption.WRITE,…);
全テキストをファイルへ書き込む。
戻り値は出力先ファイル名。
(このメソッドでは出力先ファイルが既に存在していてもエラーにならない模様)
JDK1.6以前で全テキストを書き込む方法
List<String> list = 〜;
Path dst = ;
Path r = Files.write(dst, list, StandardOpenOption.WRITE,…);
UTF8の場合はCharsetを省略できる。(JDK1.8以降)[2014-04-29]  
OpenOption
StandardOpenOption READ 読み込む為にオープンする。
WRITE 書き込む為にオープンする。
APPEND 追記する為にオープンする。
TRUNCATE_EXISTING WRITEでのオープン時に、ファイルが既に存在していたら中身をクリアする。READでは無効。
CREATE ファイルが無かったら作成する。
CREATE_NEW ファイルを作成する。既に存在していたらエラー。
DELETE_ON_CLOSE クローズ時にファイルを削除する。(ベストエフォートであり、即時に削除されるわけではない)
SPARSE  
SYNC データまたは属性を更新する度にストレージデバイスに対して同期をとる。
DSYNC データを更新する度にストレージデバイスに対して同期をとる。
LinkOption NOFOLLOW_LINKS シンボリックリンクをフォローしない。

newOutputStream(およびnewBufferedWriter)メソッドでは、(CREATEだけ指定して)TRUNCATE_EXISTINGを指定しなかった場合、既存ファイルの内容が残るので要注意![2015-05-05]
例えば、既存ファイルの内容が「abcdef」で、新しく「123」を書き込んだとすると、「123def」になってしまう!
newOutputStream(およびnewBufferedWriter)メソッドでは、OpenOptionを何も指定しないとCREATETRUNCATE_EXISTINGWRITEが指定された扱いになるので、むしろ何も指定しない方が安全かも(苦笑)


ディレクトリー操作系

説明 備考
ディレクトリー作成
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;

Path dir = ;
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-----");
FileAttribute<Set<PosixFilePermission>> attrs = PosixFilePermissions.asFileAttribute(perms);
Path r = Files.createDirectories(dir, attrs);

ディレクトリーを作成する。

Windowsでは(POSIX準拠じゃないので)属性の指定は出来ない。
(Windowsで使おうとすると例外が発生する)
File#mkdirs()
Path dir = ;
Path r = Files.createDirectories(dir);
ディレクトリーを作成する。
戻り値は作成されたディレクトリー名。
Path dir = ;
Path r = Files.createDirectory(dir, attrs);
ディレクトリーを作成する。
親ディレクトリーが存在しない場合はNoSuchFileExceptionが発生する。
File#mkdir()
ファイル作成 Path dst = ;
Path r = Files.createFile(dst, attrs);
空のファイルを作成する。
既に存在している場合はFileAlreadyExistsExceptionが発生する。
File#createNewFile()
リンク作成 Path link = ;
Path src = ;
Path r = Files.createLink(link, src);
リンクを作成する。
戻り値は作成されたリンク名。
既に存在している場合はFileAlreadyExistsExceptionが発生する。
 
シンボリックリンク作成 Path link = ;
Path src = ;
Path r = Files.createSymbolicLink(link, src, attrs);
シンボリックリンクを作成する。
Windowsは対応していない。
 
テンポラリーディレクトリー作成 createTempDirectory    
テンポラリーファイル作成 createTempFile   File#createTempFile()
ファイル・ディレクトリー削除 Path dst = ;
Files.delete(dst);
ファイル・ディレクトリーを削除する。戻り値は無し。
削除対象が無い場合はNoSuchFileExceptionが発生する。
ディレクトリーの場合、中が空でないとDirectoryNotEmptyExceptionが発生する。
File#delete()
Path dst = ;
boolean r = Files.deleteIfExists(dst);
ファイル・ディレクトリーが存在する場合だけ削除する。
削除した場合はtrue、削除対象が無かった場合はfalseが返る。
ディレクトリーの場合、中が空でないとDirectoryNotEmptyExceptionが発生する。
 
ファイル・ディレクトリー一覧取得
import java.nio.file.DirectoryStream;
Path dir = ;
try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) {
  for (Path path : ds) {
    System.out.println(path);
  }
}
指定されたパス直下のファイルやディレクトリー一覧を取得する。[2014-04-29]  
Path dir = ;
try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir, "*.txt")) {
  for (Path path : ds) {
    System.out.println(path);
  }
}
第2引数でファイル名のパターンを指定することが出来る。[2014-04-29]  
Path dir = ;
try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir, new DirectoryStream.Filter<Path>() {
  @Override
  public boolean accept(Path entry) throws IOException {
    String name = entry.getFileName().toString();
    return name.endsWith(".java") || name.endsWith(".class");
  }
})) {
  for (Path path : ds) {
    System.out.println(path);
  }
}
フィルタークラスを渡せば、複雑なパターンにも対応できる。[2014-04-29]  
Path dir = ;
try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir, entry -> {
  String name = entry.getFileName().toString();
  return name.endsWith(".java") || name.endsWith(".class");
})) {
  for (Path path : ds) {
    System.out.println(path);
  }
}
このフィルターは関数型インターフェースなので、
JDK1.8以降ならラムダ式で書ける。[2014-04-29]
 
import java.util.stream.Stream;

Path dir = ;
try (Stream<Path> stream = Files.list(dir)) {
  stream
    .filter(entry -> entry.getFileName().toString().endsWith(".txt"))
    .forEach(System.out::println);
}

JDK1.8の場合、ディレクトリー直下の一覧を取得するならlist()の方が簡単。[2014-04-29]
(サブディレクトリーも含めたい場合はwalk()を使う)
 
ファイル・ディレクトリー階層取得
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

Path dir = ;
final List<Path> list = new ArrayList<>();
Files.walkFileTree(dir, new FileVisitor<Path>() {

  @Override
  public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
    return FileVisitResult.CONTINUE;
  }

  @Override
  public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
    if (file.getFileName().toString().endsWith(".txt")) {
      list.add(file);
    }
    return FileVisitResult.CONTINUE;
  }

  @Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
    return FileVisitResult.CONTINUE;
  }

  @Override
  public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
    return FileVisitResult.CONTINUE;
  }
});
for (Path path : list) {
  System.out.println(path);
}

サブディレクトリー内も含めてファイル・ディレクトリー一覧を取得する。[2014-04-29]

FileVisitorを渡すとファイルやディレクトリーのパスを受け取れるので、
別途List等にパスを保持していく。
各メソッドでFileVisitResult.TERMINATEを返すと、それ以上走査しない。
(FileVisitorのアダプタークラスとして、SimpleFileVisitorというクラスが用意されている。[2015-12-12]

なお、デフォルトではシンボリックリンクのリンク先は探索対象にならない。[2015-04-18]
シンボリックリンクを探索対象に含める方法

 
Path dir = ;
Set<FileVisitOption> options = EnumSet.noneOf(FileVisitOption.class);
int maxDepth = 3;
final List<Path> list = new ArrayList<>();
Files.walkFileTree(dir, options, maxDepth, new FileVisitor<Path>() {
  〜
}
サブディレクトリーの最大階層を指定して一覧を取得する。[2014-04-29]  
Path dir = ;
Set<FileVisitOption> options = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
int maxDepth = Integer.MAX_VALUE;
final List<Path> list = new ArrayList<>();
Files.walkFileTree(dir, options, maxDepth, new FileVisitor<Path>() {
  〜
}
UNIXのシンボリックリンクのリンク先も含めて探索する[2015-04-18]
(参考: opengl-8080さんのシンボリックリンクのフォルダも再帰対象にする
 
import java.util.stream.Stream;

Path dir = ;
int maxDepth = 3;
try (Stream<Path> stream = Files.find(dir, maxDepth,
(file, attrs) -> file.getFileName().toString().endsWith(".txt"))) {
  stream.forEach(System.out::println);
}

findメソッドの場合、最小限の判定用関数だけを渡せる。(JDK1.8以降)[2014-04-29]  
import java.util.stream.Stream;

Path dir = ;
try (Stream<Path> stream = Files.walk(dir)) {
  stream
    .filter(entry -> entry.getFileName().toString().endsWith(".txt"))
    .forEach(System.out::println);
}

JDK1.8の場合、サブディレクトリー内も含めた一覧取得はwalk()の方が簡単。[2014-04-29]

マルチスレッドで処理する例
 
Path dir = ;
int maxDepth = 3;
try (Stream<Path> stream = Files.walk(dir, maxDepth)) {
  〜
}
サブディレクトリーの最大階層を指定して一覧を取得する。[2014-04-29]

maxDepthに1を指定すると、list()と同等になる。
 
Path dir = ;
try (Stream<Path> stream = Files.walk(dir, FileVisitOption.FOLLOW_LINKS)) {
  〜
}
UNIXのシンボリックリンクのリンク先も含めて一覧を取得する。[2015-04-18]  
Path dir = ;
int maxDepth = Integer.MAX_VALUE;
try (Stream<Path> stream = Files.walk(dir, maxDepth, FileVisitOption.FOLLOW_LINKS)) {
  〜
}

属性取得・設定系

Path(ファイルやディレクトリー)の属性を取得・設定するメソッドもFilesのstaticメソッドとして用意されている。

  データのクラス 取得メソッド 設定メソッド 説明 備考
属性 Object getAttribute setAttribute    
FileAttributeView getFileAttributeView      
BasicFileAttributes readAttributes      
Map<String,Object> readAttributes      
ファイル権限 Set<PosixFilePermission> getPosixFilePermissions setPosixFilePermissions    
所有者 UserPrincipal getOwner setOwner    
シンボリックリンク boolean isSymbolicLink      
ディレクトリー boolean isDirectory     File#isDirectory()
  boolean isRegularFile     File#isFile()
最終更新時刻 FileTime getLastModifiedTime setLastModifiedTime   File#lastModified()
File#setLastModified()
ファイルサイズ long size   ファイルサイズ(バイト単位)  
ファイルの存在チェック boolean exists     File#exists()
boolean notExists      
読込可能 boolean isReadable     File#canRead()
File#setReadable()
書込可能 boolean isWritable     File#canWrite()
File#setWritable()
File#setReadOnly()
実行可能 boolean isExecutable     File#canExecute()
File#setExecutable()
隠しファイル boolean isHidden     File#isHidden()
ファイルタイプ String probeContentType      
  Path readSymbolicLink      
比較 boolean isSameFile   2つのPathが同じ場所を指しているかどうかをチェックする。  

AutoCloseableインターフェース

JDK1.7で(Closeableインターフェースと似たような)java.lang.AutoCloseableインターフェースが追加された。

AutoCloseableを実装しているクラスの場合、リソース付きtry文が使える。
CloseableもAutoCloseableを継承する形になっているので、Closeableを実装しているクラスは全てリソース付きtry文が使えることになる。
したがってJDK1.7のInputStreamやOutputStreamリソース付きtry文を使える。

Closeable(JDK1.5)の例 AutoCloseableの例
InputStream is = 〜;
try {
  //isを使った処理
} finally {
  is.close();
}
try (InputStream is = 〜) {

  //isを使った処理


}
InputStream is = 〜;
try {
  OutputStream os = 〜;
  try {
    //is,osを使った処理
  } finally {
    os.close();
  }
} finally {
  is.close();
}
try (InputStream is = 〜;
     OutputStream os = 〜) {


  //is,osを使った処理





}

ちなみに、Closeableで定義されているメソッドは「public void close() throws IOException」だが
AutoCloseableで定義されているメソッドは「public void close() throws Exception」であることに注意。
たぶん、入出力(I/O)でないリソースについても使用できるという意味なのだろう。

ただし、オーバーライドしたメソッド側では発生させる例外の種類を減らすことが出来るので、常にException全部が指定される(指定しなければならない)というわけではない。
例えばInputStreamは以下のような形で、close()にはIOExceptionしか指定していない。

public abstract class InputStream implements Closeable {

	// CloseableとAutoCloseableのclose()をオーバーライド
	public void close() throws IOException;
}

Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま