第6講 ファイル

この講で利用するプログラム

第6講 ファイルのサブセクション

ファイルを扱う型

File 型

Java でファイルを扱うときは File 型を利用します. 名前はFile型ですが,ファイルもディレクトリも同じ型として扱います.

なお,File型を利用するときは,java.io.File のインポートが必要です.File型の 実体を作成するにはファイル名,もしくはディレクトリ名を文字列(String型)で渡しましょう.

File dir1  = new File(".");
File dir2  = new File("../path/to/some/dir");
File file1 = new File("ファイル名");
File file2 = new File("../lesson01/BigAndSmall.java");

上記のように,カレントディレクトリを元に,パスの指定も可能です. 指定したファイルが存在しない場合でも問題なく実体を作成できます. ファイルが存在しているかは,ls コマンドに必要な File 型のメソッド にも載せているように File型変数に対して,existsメソッドを呼び出すことで確認できます.

例題 1. ls コマンドの作成

ここでは,lsコマンドに相当するプログラムを作成しましょう.lsコマンドは コマンドライン引数に渡された情報によって次のように挙動が変わります.

  • ファイル名が渡されると,そのファイルの名前が出力されます.
  • ディレクトリ名が渡されると,そのディレクトリ内部のファイル,ディレクトリの一覧が出力されます.
  • 何も渡されなかった場合,カレントディレクトリのファイル,ディレクトリの一覧が出力されます.
  • 指定されたファイル,もしくはディレクトリが存在しない場合,その旨を出力する.

ls コマンドの出力例

$ java ListFiles # 何も指定されない場合,現在ディレクトリを表示する.
Complex.java            Factorial2.java
Fibonacci4.java         SquareRoot.java
ComplexCalculator.java  Fibonacci2.java
GrandTotal2.java        CubicRoot.java
Fibonacci3.java         Matrix.java
$ ls Complex.java
Complex.java
$ java ListFiles .. # ディレクトリを指定した場合,中身を表示する.
lesson01/     lesson02/     lesson03/     lesson04/
lesson05/     lesson06/     lesson07/
$ java ListFiles ../lesson01
Adder.java             EvenPrinter.java
LeapYear.java          OddPrinter2.java
BackSlashPrinter.java  EvenPrinter2.java
Multiplication.java    PositiveChecker.java
BigAndSmall.java       GrandTotal.java
OddPrinter.java        XPrinter.java
$ java ListFiles ../lesson00 # 存在しない場合
ls: ../lesson00: No such file or directory

表示の順序,改行の位置は必ずしも同じでなくても構いません.

ls コマンドに必要な File 型のメソッド

では,lsコマンドを作成していきましょう.lsコマンドを 作成するために,File型の次の3つ(4つ)メソッドが利用できます.File型の 変数 file に対してメソッドを呼び出すと思ってください.

  • ファイル,ディレクトリの名前を取得する.
    • file.getName()
      • ファイル,もしくはディレクトリ名がString型として返る.
  • ファイルとディレクトリを区別する.
    • file.isDirectory()
      • ディレクトリならば,Boolean型の trueが返る.
      • ディレクトリでないなら(ファイルであれば),Boolean型の falseが返る.
    • file.isFile()
      • 一般ファイルならば,Boolean型の trueが返る.
      • 一般ファイルでないなら(ディレクトリであれば),Boolean型の falseが返る.
      • isDirectoryの判定と,isFileの判定のどちらかを利用すれば良いでしょう.
  • ファイルとディレクトリの存在を確認する.
    • file.exists()
      • ファイル,もしくはディレクトリが存在すれば,Boolean型のtrueが返る.存在しなければfalseが返る.
  • ディレクトリ内のファイル,ディレクトリの一覧を取得する.
    • file.listFiles()
      • File型の配列(File[])が返る.
      • file.isDirectory()trueを返さない場合,listFilesメソッドはnullを返します.

ls コマンドの作成

さて,ls コマンドに必要な File 型のメソッドにある 4つのメソッドを利用して,lsコマンドを作成しましょう. 次のコードを元に作成してください.以下のコードの....の部分に適切な命令を書きましょう.

// import文を書く.
....
public class ListFiles{
  void run(String[] args){
    for(String arg: args){
      File thisFile = // ....
          // argを元に,File型の変数を作成する.
      this.listFile(thisFile);
    }
    if(args.length == 0){
      File currentDirectory = // ...
        // カレントディレクトリ(".")を表す File 型の実体を作成する.
      this.listFile(currentDirectory);
    }
  }
  void listFile(File target){
    if(this.isExist(target)){
      if(....){ // 引数に与えられた target がディレクトリの場合
        this.listFilesInDirectory(target);
      }
      else{
        System.out.printf("%s%n", ....);
            // 引数のファイルの名前を出力する.
      }
    }
  }
  void listFilesInDirectory(File dir){
    // 引数に受け取ったディレクトリの中身一覧を取得する.
    File[] files = ....
    // for文で files を繰り返す.
    for(....){
      // 配列の各要素であるファイルの名前を出力する.
    }
  }
  Boolean isExist(File target){
    Boolean exists = // .....
        // target が指し示すファイルが存在するかを確認する.
        // File型の existsメソッドを利用する.
    if(!exists){ // ファイルが存在しない場合
      // 指定されたファイル名は存在しない旨を出力する.
      System.out.printf("ListFiles: %s: No such file or directory%n",
          target.getName());
    }
    return exists;
  }
  // mainメソッドは省略
}
// import文を書く.
import java.io.File;

public class ListFiles{
    void run(String[] args){
        for(String arg: args){
            File thisFile = new File(arg); // argを元に,File型の変数を作成する.
            this.listFile(thisFile);
        }
        if(args.length == 0){
            this.listFile(new File("."));
        }
    }
    void listFile(File target){
        if(this.isExist(target)){
            if(target.isDirectory()){ // 引数のファイルがディレクトリの場合
                this.listFilesInDirectory(target);
            }
            else{
                System.out.printf("%s%n", target.getName()); // 引数のファイルの名前を出力する.
            }
        }
    }
    void listFilesInDirectory(File dir){
        // 引数に受け取ったディレクトリが持つファイル,ディレクトリの一覧を取得する.
        File[] files = dir.listFiles();
        // for文で files を繰り返す.
        for(File file: files){
            System.out.printf("%s%n", file.getName());
            // 配列の各要素であるファイルの名前を出力する.
        }
    }
    Boolean isExist(File target){
        if(!target.exists()){ // ファイルが存在しない場合
            // 指定されたファイル名は存在しない旨を出力する.
            System.out.printf("ListFiles: %s: No such file or directory%n", target.getName());
        }
        return target.exists(); // ファイルが存在するかを返す(if文の条件の反転).
    }
    public static void main(String[] args){
        ListFiles ls = new ListFiles();
        ls.run(args);
    }
}

例題 2. File の情報を出力するプログラム.

概要

ファイルの情報を出力するプログラムを作成しましょう. コマンドライン引数で指定されたファイルの次の情報を取得して出力してください.

  • ファイルの権限
    • ls -lで表示される rwx のこと.
      • rwxはそれぞれ,読み込み権限,書き込み権限,実行権限を表します.権限があれば文字が表示され,権限がなければ-で表されます.
      • r--であれば,内容は読めるが,書き込み,実行が不可能であることを表します.
  • 絶対パス
  • 相対パス(コマンドラインで指定されたパス)
  • ファイル名(ディレクトリ名を含まない名前)
  • 隠しファイルか否か.
  • 最終更新日
  • ファイルの長さ.

情報取得のために必要なメソッド.

以下のメソッドで必要な情報が取得できます.これらのメソッドも全て,File型が持ちます. 以下の説明では,ls コマンドに必要な File 型のメソッド と同じく,File型の変数 file に対してメソッドを呼び出すと思ってください.

  • ファイルの権限を取得する.
    • file.canRead(): ファイルが存在し,読み込み権限があればBoolean型のtrueを返します.
    • file.canWrite(): ファイルが存在し,書き込み権限があればBoolean型のtrueを返します.
    • file.canExecute(): ファイルが存在し,実行権限があればBoolean型のtrueを返します.
  • 絶対パス
    • file.getAbsolutePath(): この実体が表すファイルの絶対パスをString型で返します.
  • 相対パス
    • file.getPath(): この実態が表すファイルのパスをString型で返します.実体を作成するときに渡した文字列がそのまま返されます.
  • ファイル名
  • 隠しファイルか否か
    • file.isHidden(): ファイルが隠しファイルであれば,Boolean型の trueを返します. macOS であれば,.から始まるファイルは隠しファイルです.
  • 最終更新日
    • file.lastModified(): 最終更新日をLong型で返します. 日付(Date)型に変換するには,Date date = new Date(file.lastModified())としてください.
  • ファイルの長さ
    • file.length(): ファイルの長さをLong型で返します.

File の情報を出力するプログラムの作成.

// import文を書く.
....
public class FileInfo{
  void run(String[] args){
    for(String arg: args){
      File thisFile = .... // argを元に,File型の変数を作成する.
      this.showFileInfo(thisFile);
    }
  }
  void showFileInfo(File target){
    if(....){ // ファイルが存在する場合.
      this.showInfo(target);
    }
    else{
      // 指定されたファイル名は存在しない旨を出力する.
      System.out.printf("FileInfo: %s: No such file or directory%n", ....);
    }
  }
  void showInfo(File target){
    System.out.printf(
      "%s %6d %s %s (%s) %s%n",
      getMode(target),
      ...., // ファイルの長さを指定する.
      ...., // ファイルの最終更新日を Date 型で指定する.
      ...., // ファイルの相対パスを指定する.
      ...., // ファイルの絶対パスを指定する.
      getHidden(target)
    );
  }
  String getHidden(File file){
    if(....){
      return "隠しファイル";
    }
    return "";
  }
  String getMode(File file){
    String rwx = "";
    // 読み込み権限があるか確認する.
    if(....) rwx = rwx + "r";
    else     rwx = rwx + "-";

    // 書き込み権限があるか確認する.
    if(....) rwx = rwx + "w";
    else     rwx = rwx + "-";

    // 実行権限があるか確認する.
    if(....) rwx = rwx + "x";
    else     rwx = rwx + "-";

    return rwx;
  }
  public static void main(String[] args){
    FileInfo info = new FileInfo();
    info.run(args);
  }
}

実行例

$ java FileInfo FileInfo.java
rw-   1617 Fri May 12 15:43:20 JST 2017 FileInfo.java (/Users/tamada/ksuap/lesson07/FileInfo.java)
$ java FileInfo ListFiles.java ~/.bashrc /usr/bin/java
rw-   1621 Fri May 12 15:32:17 JST 2017 ListFiles.java (/Users/tamada/ksuap/lesson07/ListFiles.java)
rw-    578 Fri May 12 13:27:36 JST 2017 /Users/tamada/.bashrc (/Users/tamada/.bashrc) 隠しファイル
r-x  58560 Wed Sep 14 09:55:30 JST 2017 /usr/bin/java (/usr/bin/java)
// import文を書く.
import java.io.File;
import java.util.Date;

public class FileInfo{
    void run(String[] args){
        for(String arg: args){
            File thisFile = new File(arg); // argを元に,File型の変数を作成する.
            this.showFileInfo(thisFile);
        }
    }
    void showFileInfo(File target){
        if(target.exists()){ // ファイルが存在する場合.
            this.showInfo(target);
        }
        else{
            // 指定されたファイル名は存在しない旨を出力する.
            System.out.printf("FileInfo: %s: No such file or directory%n", target.getName());
        }
    }
    void showInfo(File target){
        System.out.printf(
            "%s %d %s %s (%s) %s%n",
            getMode(target),
            target.length(), // ファイルの長さを指定する.
            new Date(target.lastModified()), // ファイルの最終更新日を Date 型で指定する.
            target.getPath(), // ファイルの相対パスを指定する.
            target.getAbsolutePath(), // ファイルの絶対パスを指定する.
            getHidden(target)
        );
    }
    String getHidden(File file){
        if(file.isHidden()){
            return "隠しファイル";
        }
        return "";
    }
    String getMode(File file){
        String rwx = "";
        // 読み込み権限があるか確認する.
        if(file.canRead())    rwx = rwx + "r";
        else                  rwx = rwx + "-";

        // 書き込み権限があるか確認する.
        if(file.canWrite())   rwx = rwx + "w";
        else                  rwx = rwx + "-";

        // 実行権限があるか確認する.
        if(file.canExecute()) rwx = rwx + "x";
        else                  rwx = rwx + "-";

        return rwx;
    }
    public static void main(String[] args){
        FileInfo info = new FileInfo();
        info.run(args);
    }
}

例題 3. tree コマンド

概要

次に,treeコマンドのように,ディレクトリのツリー構造を表示しましょう. クラス名は,TreeViewer としてください.

ヒント

  • ディレクトリの数とファイルの数を数える必要がありますので,フィールドにディレクトリ数,ファイル数を格納するInteger型の変数を宣言しましょう.
  • traverseメソッドを宣言しましょう.
    • File型の変数とインデントレベルを表す数値(Integer型)を引数として受け取ってください.
    • 返り値はありません(voidです).
    • インデントレベルに応じた空白を出力し,引数のファイルの名前(パスは含みません)を出力しましょう.
    • 引数で受け取ったFile型変数がファイルであった場合,ファイル数をインクリメントしてメソッドを終了してください (returnしてください).
    • 引数で受け取ったFile型変数がディレクトリであった場合,次の処理を行なってください.
      • ディレクトリ数をインクリメントしてください.
      • ディレクトリの中身を取得してください(listFiles).
      • 受け取ったFile型の配列の各要素に対して,traverseメソッドを呼び出してください.
        • その際,引数で受け取ったインデントレベルに +1 して呼び出してください.
        • 再帰呼び出しを行なっています.
      • ループが終了すれば,traverseメソッドも終了です.
  • runメソッドは次の処理を行なってください.
    • File型の変数を宣言してください.
    • コマンドライン引数の 0 番目の要素をFile型に変換してください.
    • 変換したFile型の実体を先ほど宣言したFile型変数に代入してください.
    • traverseメソッドを呼び出してください.
      • 第1引数は先ほど作成した File型の変数,インデントレベルは 0 としてください.
    • traverseメソッドの呼び出し後,ディレクトリ数,ファイル数を出力してください.

実行例

$ java TreeViewer prog
prog/
    lesson01
        Adder.java
        ... 途中省略
        XPrinter.java
    lesson02
        DateExample.class
        ... 途中省略
        StringBuilderExample.java
    lesson03
        ArgsSorter.class
        ... 途中省略
        TrapezoidalRulePi.java
    lesson04
        ArgsPrinter2.class
        ... 途中省略
        StatsValues.java
    lesson05
        Complex.java
        ... 途中省略
        SquareRoot.java
    lesson06
        Bound.class
        ... 途中省略
        ThrowingExercise2.java
    lesson07
        FileInfo.class
        ... 途中省略
        ListFiles.java

8 directories, 98 files

このようにディレクトリが存在しなくなるまでディレクトリを掘り進んでください. コマンドライン引数で渡す文字列は1つで良いです(複数の引数に対応する必要はありません).

import java.io.File;

public class TreeViewer{
    Integer directoryCount = 0;
    Integer fileCount = 0;
    void run(String[] args){
        traverse(new File(args[0]), 0);
        System.out.printf("%d directories, and %d files%n", directoryCount, fileCount);
    }
    void traverse(File file, Integer indent){
        for(Integer i = 0; i < indent; i++) System.out.print("    ");
        System.out.printf("%s%n", file.getName());
        if(file.isDirectory()){ // ディレクトリであれば,中身を確認する.
            File[] files = file.listFiles();
            Integer newIndent = indent + 1;
            for(File f: files){
                this.traverse(f, newIndent);
            }
            directoryCount++;
        }
        else{
            fileCount++;
        }
    }
    public static void main(String[] args){
        TreeViewer tree = new TreeViewer();
        tree.run(args);
    }
}

練習問題

1. ファイルを探すコマンド FileFinder

指定されたディレクトリ以下の特定の名前をもつファイルが存在するかを探索するプログラムを作成してください. 以下のように指定してください.

java FileFinder ファイル名 探索ディレクトリ

例題 3が参考になるでしょう.探索には,TreeViewertraverseと同じように 再帰呼び出しを行いましょう. ファイル名の一致を確認するには,値の一致性を確認しましょう

見つかった場合に,全てのパスを出力し,見つからなかったら,その旨を出力するようにしましょう. 見つかったら,とりあえず,結果を入れるListにパス(File型変数)を追加しましょう. そして,最後に Listの大きさを確認し,見つかったか,見つからなかったかを判断しましょう.

実行例

$ java FileFinder TreeViewer.java prog
prog/lesson07/TreeViewer.java
$ java FileFinder TreeViewer.java ../
../prog/lesson07/TreeViewer.java
../2019autumn/prog/07/TreeViewer.java
$ java FileFinder TreeViewer.notfound prog
TreeViewer.notfound: Not found.

2. ディレクトリを作成するコマンド mkdir

ディレクトリを作成するコマンド MakeDirectory を作成しましょう. 作成したいディレクトリのパスを持つFile型変数を作成し,その変数に対して,mkdir メソッドを呼び出すとディレクトリを作成できます.

実行例

$ java ListFiles ..
lesson01 lesson02 lesson03 lesson04 lesson05 lesson06 lesson07
$ java MakeDirectory ../lesson00
$ java ListFiles ..
lesson00 lesson01 lesson02 lesson03 lesson04 lesson05 lesson06 lesson07
$ java ListFiles ../lesson00
$ java MakeDirectory ../lesson00/not/exist/parent/dir
../lesson00/not/exist/parent/dir: could not make directory.

3. ディレクトリを作成するコマンド mkdir の改良

先ほどの MakeDirectory は途中のディレクトリが存在しないとき, ディレクトリの作成に失敗しました.そのような場合でもディレクトリが作成できるようにしましょう.

ディレクトリを作成するコマンド mkdirでは,mkdirメソッドを利用しました. ここでは,mkdirメソッドの代わりに,mkdirsメソッドを利用してください. これで,途中のディレクトリが存在しない場合でも作成してくれます.

クラス名を MakeDirectories としてください. なお,コマンドライン引数で複数の値を受け取れるようにしてください.

実行例

$ java MakeDirectories a/b/c/d/e/f
$ java ListFiles a
b

4. ファイル,ディレクトリを削除するコマンド remove

ファイル,ディレクトリを削除するコマンド Remover を作成してください. コマンドライン引数で受け取った複数のパスのファイルを削除するコマンドです. ディレクトリ内にファイルがあったとしても,それらも全て削除してください.

ただし,必要なファイルを削除しないように気をつけてください.

削除は,File型の変数に対して,deleteメソッドを呼び出してください. ただし,ディレクトリ内が空でなければ,deleteメソッドは失敗します. ディレクトリが空でない場合は,そのディレクトリ内部のファイル,ディレクトリを削除してから削除するようにしましょう. 再帰呼び出しを用いると実現できるでしょう.

実行例

$ java ListFiles a/b/c/d/e
f
$ java Remover a/b/c/d/e/f
$ java ListFiles a/b/c/d/e
$ java Remover a
$ java ListFiles a
ls: a: No such file or directory.

まとめ

まとめ

  • ファイルを扱うには,File 型を利用する
    • File 型を利用するには,import java.io.Fileが必要
    • ファイル,ディレクトリを同じ型で扱う.
    • File型が持つ各種メソッド
      • ls コマンドのために必要なメソッド情報取得のためのメソッド練習問題 2, 練習問題 3, 練習問題 4からのまとめ.
      • 全て,File型の変数 fileに対して呼び出すものとする.
      • ファイル,ディレクトリの名前を取得する: file.getName()
      • ファイル,ディレクトリを区別する: file.isDirectory()file.isFile()
      • ファイル,ディレクトリの存在を確認する: file.exists()
      • ディレクトリ内のファイル,ディレクトリ一覧を取得する: file.listFiles()
      • ファイルの権限を確認する: file.canRad()file.canWrite()file.canExecute()
      • ファイルの絶対パスを取得する: file.getAbsolutePath()
      • ファイルの相対パスを取得する; file.getPath()
      • 隠しファイルか否かを判定する: file.isHidden()
      • ファイルの長さを取得する: file.length()
      • ファイルの最終更新日時を取得する: file.lastModified()
        • Long型で返される.Date型として扱うには,new Date(file.lastModified())とする.
      • ディレクトリを作成する: file.mkdir()
        • file変数が表すパスのディレクトリを作成します.作成に成功すれば,trueを返し,そうでなければ falseを返します.
      • ディレクトリを作成する 2: file.mkdirs()
        • file変数が表すパスのディレクトリを作成します.存在していないものの,必要なディレクトリがあれば,一緒に作成されます.必要な全てのディレクトリが作成された場合は,trueを返し,そうでなければfalseを返します.
      • ファイル,ディレクトリを削除する: file.delete()
        • file変数が表すパスがディレクトリで,かつディレクトリの中身が空でなければ falseを返し,削除に失敗します.