ファイル入出力についてみていきます。ファイルへのアクセス方法は対象となるファイルによりバイトストリーム(バイナリファイル)と文字ストリーム(テキストファイル)に大別されます。「ストリーム」とは「データの流れ」を表現する用語です。
ファイル入出力の前に、まずファイルやディレクトリの取り扱いをみていきます。Javaでは「java.io.File」クラスによってファイルやディレクトリを1つのオブジェクトとして取り扱っています。「java.io.File」の詳細についてはJDK API仕様に譲るとして、ここでは簡単な使用例で確認してみます。
・サンプルソース(Sample1601.java)
import java.io.File; import java.io.IOException; public class Sample1601 { public static void main(String[] args) throws IOException { // ファイルの作成 File fileObject1 = new File("C:\\dev\\java\\newFile1.txt"); //← "\"は"\\"とする必要があります。 fileObject1.createNewFile(); // ディレクトリの作成 File dirObject = new File("C:\\dev\\java\\newDir"); dirObject.mkdir(); // 作成したディレクトリの中にファイルを作成 File fileObject2 = new File(dirObject, "newFile2.txt"); fileObject2.createNewFile(); // ファイルの存在チェック if (fileObject1.exists()) { System.out.println(fileObject1.getName() + "は存在します。"); if (fileObject1.isFile()) System.out.println(fileObject1.getName() + "はファイルです。"); } else { System.out.println(fileObject1.getName() + "は存在しません。"); } // ディレクトリの存在チェックおよびその内部参照 if (dirObject.exists()) { System.out.println(dirObject.getName() + "は存在します。"); if (dirObject.isDirectory()) { System.out.println(dirObject.getName() + "はディレクトリです。"); System.out.println(dirObject.getName() + "の中身は次のとおりです。"); String[] ary = dirObject.list(); for (int i = 0; i < ary.length; i++) { System.out.println(ary[i]); } } } else { System.out.println(dirObject.getName() + "は存在しません。"); } } }
・実行結果
C:\dev\java>javac Sample1601.java [Enter] C:\dev\java>java Sample1601 [Enter] newFile1.txtは存在します。 newFile1.txtはファイルです。 newDirは存在します。 newDirはディレクトリです。 newDirの中身は次のとおりです。 newFile2.txt C:\dev\java>dir ← 実際のディレクトリ内を確認。 ドライブ C のボリューム ラベルは Local Disk です ボリューム シリアル番号は 001A-E4E3 です C:\dev\java のディレクトリ 2008/03/16 23:58 <DIR> . 2008/03/16 23:58 <DIR> .. 2008/03/16 23:58 <DIR> newDir ← ディレクトリが作成されている。 2008/03/16 23:58 0 newFile1.txt ← ファイルが作成されている。 2008/03/16 23:58 1,473 Sample1601.class 2008/03/16 23:53 1,637 Sample1601.java 3 個のファイル 3,110 バイト 3 個のディレクトリ 52,261,048,320 バイトの空き領域 C:\dev\java>dir newDir ← 作成したディレクトリ内を確認。 ドライブ C のボリューム ラベルは Local Disk です ボリューム シリアル番号は 001A-E4E3 です C:\dev\java\newDir のディレクトリ 2008/03/16 23:58 <DIR> . 2008/03/16 23:58 <DIR> .. 2008/03/16 23:58 0 newFile2.txt ← ファイルが作成されている。 1 個のファイル 0 バイト 2 個のディレクトリ 52,261,048,320 バイトの空き領域
Javaにはファイル入力関連のAPIが多く用意されていますが、ここでは主要である次のクラスを使用します。読み込み方としてバイト毎に行う方法(バイトストリーム)と文字ごとに行う方法(文字ストリーム)の2種類が存在します。
クラス名 | 処理 |
FileInputStream | 1バイト単位でバイトコードを読み込むときに使用します。 |
BufferedInputStream | 1バイト単位ではなく、一度に多数のバイトを読み込み(バッファリング)、処理効率を上げるためのクラスです。 |
BufferedReader | 1バイト単位で読み込み及び文字変換を行うのではなく、一度に多数のバイトを読み込み(バッファリング)、処理効率を上げるためのクラスです。 |
InputStreamReader | 1バイト単位でバイトコードを読み込んで文字に変換するためのクラスです。文字エンコーディングを省略した場合はプラットフォームのデフォルトの文字エンコードが使用されます。 |
FileReader | テキストを読み込むための簡易クラスです。プラットフォームのデフォルトの文字エンコードが使用されます。 |
ファイル(Sample1601.txt)からバイトコードとしてデータを読み込む方法を示します。読み込み方として、バイト単位で読み込む方法と読み込み処理の効率を図るために複数バイトを一度に読み込む方法(バッファリング)があります。
・サンプルテキスト(c:\dev\java\Sample1601.txt)
abcあいう
・1バイト単位の読み込みサンプルソース(Sample1602.java)
import java.io.FileInputStream; import java.io.IOException; class Sample1602 { public static void main(String args[]) throws IOException { String inputFile = "c:\\dev\\java\\Sample1601.txt"; FileInputStream fis = new FileInputStream(inputFile); // 読み込み処理。 int code; while ((code = fis.read()) != -1) { System.out.print(Integer.toHexString(code)); } fis.close(); // ストリーム使用後はクローズ処理を行います。 } }
・実行結果
C:\dev\java>javac Sample1602.java [Enter] C:\dev\java>java Sample1602 [Enter] 61626382a082a282a4
「a」、「b」、「c」はASCIIコードで「61」、「62」、「63」、「あ」、「い」、「う」はシフトJISコードで「82a0」、「82a2」、「82a4」で現されます。
・バッファリングによるの読み込みサンプルソース(Sample1603.java)
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
class Sample1603 {
public static void main(String args[]) throws IOException {
String inputFile = "c:\\dev\\java\\Sample1601.txt";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(inputFile));
int code;
while ((code = bis.read()) != -1) {
System.out.print(Integer.toHexString(code));
}
bis.close();
}
}
・実行結果
C:\dev\java>javac Sample1603.java [Enter] C:\dev\java>java Sample1603 [Enter] 61626382a082a282a4
ファイル(Sample1601.txt)から1文字単位で読み込む方法とバッファリングによる読み込み方法を示します。
・1文字単位の読み込みサンプルソース(Sample1604.java)
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
class Sample1604 {
public static void main(String[] args) throws IOException {
String inputFile = "c:\\dev\\java\\Sample1601.txt";
InputStreamReader isr = new InputStreamReader(new FileInputStream(inputFile), "SJIS");
int data;
while ((data = isr.read()) != -1) {
System.out.print((char)data);
}
isr.close();
}
}
上記のサンプルでは文字エンコードとして「SJIS」を使用しましたが、その他のサポートされているエンコーディングについてはJDKドキュメントを参照してください。
また、エンコーディングをプラットフォームのデフォルトのものを使用する場合には「FileReader」クラスで次のように簡略化できます。
・サンプルSample1604.javaの簡略版(Sample1605.java)
import java.io.FileReader;
import java.io.IOException;
class Sample1605 {
public static void main(String[] args) throws IOException {
String inputFile = "c:\\dev\\java\\Sample1601.txt";
FileReader fr = new FileReader(inputFile);
int data;
while ((data = fr.read()) != -1) {
System.out.print((char)data);
}
fr.close();
}
}
・実行結果
C:\dev\java>javac Sample1604.java [Enter] C:\dev\java>java Sample1604 [Enter] abcあいう C:\dev\java>javac Sample1605.java [Enter] C:\dev\java>java Sample1605 [Enter] abcあいう
・バッファリングによる読み込みサンプルソース(Sample1606.java)
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
class Sample1606 {
public static void main(String[] args) throws IOException {
String inputFile = "c:\\dev\\java\\Sample1601.txt";
BufferedReader br = new BufferedReader(new FileReader(inputFile));
int data;
while ((data = br.read()) != -1) {
System.out.print((char)data);
}
br.close();
}
}
・実行結果
C:\dev\java>javac Sample1606.java [Enter] C:\dev\java>java Sample1606 [Enter] abcあいう
次のプログラム(5kbのデータ読み込み処理)を用いてバッファリングによる処理効率を比較してみます。
・入力処理計測用サンプルソース(Sample1607.java)
import java.io.*; import java.lang.reflect.Method; class Sample1607 { static String inputFile = "c:\\dev\\java\\Sample1602.txt"; public static void main(String args[]) { takeMeasures("byteRead"); // 1バイト単位でのバイトコードの読み込み処理。 takeMeasures("byteBufferedRead"); // バッファリングによるバイトコードの読み込み処理。 takeMeasures("textRead"); // 1文字単位での文字の読み込み処理。 takeMeasures("textBufferedRead"); // バッファリングによる文字の読み込み処理。 } // 1バイト単位でのバイトコードの読み込み処理。 public static void byteRead() throws IOException { FileInputStream fis = new FileInputStream(inputFile); int code; while ((code = fis.read()) != -1) { Integer.toHexString(code); } fis.close(); } // バッファリングによるバイトコードの読み込み処理。 public static void byteBufferedRead() throws IOException { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(inputFile)); int code; while ((code = bis.read()) != -1) { Integer.toHexString(code); } bis.close(); } // 1文字単位での文字の読み込み処理。 public static void textRead() throws IOException { InputStreamReader isr = new InputStreamReader(new FileInputStream(inputFile), "SJIS"); int data; while ((data = isr.read()) != -1) { Character.toChars(data); } isr.close(); } // バッファリングによる文字の読み込み処理。 public static void textBufferedRead() throws IOException { BufferedReader br = new BufferedReader(new FileReader(inputFile)); int data; while ((data = br.read()) != -1) { Character.toChars(data); } br.close(); } // 計測処理。 private static void takeMeasures(String readPattern) { try { Method method = Class.forName("Sample1607").getMethod(readPattern); // リファクタリングを使用。 long startTime = System.currentTimeMillis(); method.invoke(null); // メソッドの実行。 long endTime = System.currentTimeMillis(); System.out.println(readPattern + " ReadTime : " + (endTime - startTime) + " msec"); } catch (Exception e) { e.printStackTrace(); } } }
・実行結果
C:\dev\java>dir [Enter] ドライブ C のボリューム ラベルは Local Disk です ボリューム シリアル番号は 001A-E4E3 です C:\dev\java のディレクトリ 2008/03/17 00:31 <DIR> . 2008/03/17 00:31 <DIR> .. 2008/03/17 00:30 5,000 Sample1602.txt ← 5kbのファイルで読み込み調査を行う。 2008/03/17 00:31 2,443 Sample1607.java 2 個のファイル 7,443 バイト 2 個のディレクトリ 52,261,150,720 バイトの空き領域 C:\dev\java>javac Sample1607.java [Enter] C:\dev\java>java Sample1607 [Enter] 計測を3回行う。1回目 byteRead ReadTime : 31 msec byteBufferedRead ReadTime : 16 msec textRead ReadTime : 16 msec textBufferedRead ReadTime : 0 msec C:\dev\java>java Sample1607 [Enter] 2回目 byteRead ReadTime : 16 msec byteBufferedRead ReadTime : 0 msec textRead ReadTime : 31 msec textBufferedRead ReadTime : 0 msec C:\dev\java>java Sample1607 [Enter] 3回目 byteRead ReadTime : 16 msec byteBufferedRead ReadTime : 0 msec textRead ReadTime : 31 msec textBufferedRead ReadTime : 0 msec
上記結果からバッファリングの効果が得られていることが判ります。
尚、計測値についてはシステムにより異なります。(計測マシンのスペック CPU:Pentium Dual-Core1.6GHz Mem:1.5GB OS:Win-XP Pro)
Javaにはファイル出力関連のAPIが多く用意されていますが、ここでは主要である次のクラスを使用します。書き込み方としてバイト毎に行う(バイトストリーム)方法と文字ごとに行う(文字ストリーム)方法の2種類が存在します。
クラス名 | 処理 |
FileOutputStream | 1バイト単位でバイトコードを書き込むときに使用します。 |
BufferedOutputStream | 1バイト単位ではなく、一度に多数のバイトを書き込み(バッファリング)、処理効率を上げるためのクラスです。 |
BufferedWriter | 1文字単位ではなく、一度に多数の文字を書き込み(バッファリング)、処理効率を上げるためのクラスです。 |
OutputStreamWriter | 指定された文字エンコーディングに従いて1文字単位で書き込みをためのクラスです。文字エンコーディングを省略した場合はプラットフォームのデフォルトの文字エンコードが使用されます。 |
FileWriter | テキストを書き込むための簡易クラスです。プラットフォームのデフォルトの文字エンコードが使用されます。 |
ファイル(Sample1603.txt)にバイトコードとしてデータを書き込む方法を示します。書き込み方として、バイト単位で書き込む方法と書き込み処理の効率を図るために複数バイトを一度に書き込む(バッファリング)方法があります。
・1バイト単位の書き込みサンプルソース(Sample1608.java)
import java.io.FileOutputStream; import java.io.IOException; class Sample1608 { public static void main(String args[]) throws IOException { String outputFile = "c:\\dev\\java\\Sample1603.txt"; // ファイルが存在しなければ作成されます。 FileOutputStream fos = new FileOutputStream(outputFile); // 第二引数を省略時はファイルを上書きします。 // FileOutputStream fos = new FileOutputStream(outputFile, true); // 第二引数がtrueの時ファイルに追記します。 int[] code = {97, 98, 99, 130, 160, 130, 162, 130, 164}; //「abcあいう」のコードを10進数で表した値です。 // 読み込み処理。 for(int i=0; i<code.length; i++) { fos.write(code[i]); } fos.close(); // ストリーム使用後はクローズ処理を行います。 System.out.println("出力しました。"); } }
・実行結果
C:\dev\java>javac Sample1608.java [Enter] C:\dev\java>java Sample1608 [Enter] 出力しました。 C:\dev\java>dir [Enter] ドライブ C のボリューム ラベルは Local Disk です ボリューム シリアル番号は 001A-E4E3 です C:\dev\java のディレクトリ 2008/03/17 00:55 <DIR> . 2008/03/17 00:55 <DIR> .. 2008/03/17 00:55 9 Sample1603.txt ← ファイルが作成されている。 2008/03/17 00:55 725 Sample1608.class 2008/03/17 00:54 837 Sample1608.java 3 個のファイル 1,571 バイト 2 個のディレクトリ 52,259,086,336 バイトの空き領域
・実行後のファイル(Sample1603.txt)
abcあいう
・バッファリングによるの書き込みサンプルソース(Sample1609.java)
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
class Sample1609 {
public static void main(String args[]) throws IOException {
String outputFile = "c:\\dev\\java\\Sample1603.txt";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outputFile));
int[] code = {97, 98, 99, 130, 160, 130, 162, 130, 164};
for(int i=0; i<code.length; i++) {
bos.write(code[i]);
}
bos.close();
System.out.println("出力しました。");
}
}
・実行結果
C:\dev\java>javac Sample1609.java [Enter] C:\dev\java>java Sample1609 [Enter] 出力しました。
・実行後のファイル(Sample1603.txt)
abcあいう
ファイル(Sample1603.txt)に1文字単位で書き込む方法とバッファリングによる書き込み方法を示します。
・1文字単位の書き込みサンプルソース(Sample1610.java)
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
class Sample1610 {
public static void main(String args[]) throws IOException {
String outputFile = "c:\\dev\\java\\Sample1603.txt";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(outputFile), "SJIS");
osw.write("abcあいう");
osw.close();
System.out.println("出力しました。");
}
}
上記のサンプルでは文字エンコードとして「SJIS」を使用しましたが、その他のサポートされているエンコーディングについてはJDKドキュメントを参照してください。
また、エンコーディングをプラットフォームのデフォルトのものを使用する場合には「FileWriter」クラスで次のように簡略化できます。
・サンプルSample1610.javaの簡略版(Sample1611.java)
import java.io.FileWriter; import java.io.IOException; class Sample1611 { public static void main(String args[]) throws IOException { String outputFile = "c:\\dev\\java\\Sample1603.txt"; FileWriter fw = new FileWriter(outputFile); fw.write("abcあいう"); fw.write(System.getProperty("line.separator")); // 行区切り文字(改行)するとき。 fw.close(); System.out.println("出力しました。"); } }
・実行結果
C:\dev\java>javac Sample1610.java [Enter] C:\dev\java>java Sample1610 [Enter] 出力しました。 C:\dev\java>javac Sample1611.java [Enter] C:\dev\java>java Sample1611 [Enter] 出力しました。
・実行後のファイル(Sample1603.txt)
abcあいう
・バッファリングによる読み込みサンプルソース(Sample1612.java)
import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; class Sample1612 { public static void main(String args[]) throws IOException { String outputFile = "c:\\dev\\java\\Sample1603.txt"; BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile)); bw.write("abcあいう"); bw.newLine(); //「BufferedWriter」には行区切り文字(改行)のメソッドが用意されています。 bw.close(); System.out.println("出力しました。"); } }
・実行結果
C:\dev\java>javac Sample1612.java [Enter] C:\dev\java>java Sample1612 [Enter] 出力しました。
・実行後のファイル(Sample1603.txt)
abcあいう
次のプログラム(5kbのデータ書き込み処理)を用いてバッファリングによる処理効率を比較してみます。
・出力処理計測用サンプルソース(Sample1613.java)
import java.io.*; import java.lang.reflect.Method; class Sample1613 { static String outputFile = "c:\\dev\\java\\Sample1603.txt"; public static void main(String args[]) { takeMeasures("byteWrite"); // 1バイト単位でのバイトコードの書き込み処理。 takeMeasures("byteBufferedWrite"); // バッファリングによるバイトコードの書き込み処理。 takeMeasures("textWrite"); // 1文字単位での文字の書き込み処理。 takeMeasures("textBufferedWrite"); // バッファリングによる文字の書き込み処理。 } // 1バイト単位でのバイトコードの書き込み処理。 public static void byteWrite() throws IOException { FileOutputStream fos = new FileOutputStream(outputFile); for(int i=0; i<50000; i++) { fos.write(97); } fos.close(); } // バッファリングによるバイトコードの書き込み処理。 public static void byteBufferedWrite() throws IOException { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outputFile)); for(int i=0; i<50000; i++) { bos.write(97); } bos.close(); } // 1文字単位での文字の書き込み処理。 public static void textWrite() throws IOException { FileWriter fw = new FileWriter(outputFile); for(int i=0; i<50000; i++) { fw.write("a"); } fw.close(); } // バッファリングによる文字の書き込み処理。 public static void textBufferedWrite() throws IOException { BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile)); for(int i=0; i<50000; i++) { bw.write("a"); } bw.close(); } // 計測処理。 private static void takeMeasures(String writePattern) { try { Method method = Class.forName("Sample1613").getMethod(writePattern); // リファクタリングを使用。 long startTime = System.currentTimeMillis(); method.invoke(null); // メソッドの実行。 long endTime = System.currentTimeMillis(); System.out.println(writePattern + " WriteTime : " + (endTime - startTime) + " msec"); } catch (Exception e) { e.printStackTrace(); } } }
・実行出力結果
C:\dev\java>javac Sample1613.java [Enter] C:\dev\java>java Sample1613 [Enter] 計測を3回行う。1回目 byteWrite WriteTime : 156 msec byteBufferedWrite WriteTime : 16 msec textWrite WriteTime : 47 msec textBufferedWrite WriteTime : 15 msec C:\dev\java>java Sample1613 [Enter] 2回目 byteWrite WriteTime : 156 msec byteBufferedWrite WriteTime : 0 msec textWrite WriteTime : 47 msec textBufferedWrite WriteTime : 15 msec C:\dev\java>java Sample1613 [Enter] 3回目 byteWrite WriteTime : 156 msec byteBufferedWrite WriteTime : 16 msec textWrite WriteTime : 47 msec textBufferedWrite WriteTime : 15 msec C:\dev\java>dir ドライブ C のボリューム ラベルは Local Disk です ボリューム シリアル番号は 001A-E4E3 です C:\dev\java のディレクトリ 2008/03/17 01:28 <DIR> . 2008/03/17 01:28 <DIR> .. 2008/03/17 01:27 50,000 Sample1603.txt ← 5kbのデータ出力が完了している。 2008/03/17 01:26 2,175 Sample1613.class 2008/03/17 01:26 2,273 Sample1613.java 3 個のファイル 54,448 バイト 2 個のディレクトリ 52,258,926,592 バイトの空き領域
上記結果からバッファリングの効果が得られていることが判ります。
尚、計測値についてはシステムにより異なります。(計測マシンのスペック CPU:Pentium Dual-Core1.6GHz Mem:1.5GB OS:Win-XP Pro)