ファイル操作と入出力2

<概要>もっとファイル操作について学びます。Java言語では簡単にファイル操作が行えることを実感して下さい。

例題18-2-1 ディレクトリの一覧を得る。

○プログラム

import java.io.*;

public class ListDir1{
  public static void main(String[] args){
    if(args.length!=1){
      System.out.println("使用法:java ListDir1 ディレクトリ名");
      System.out.println("例:java ListDir1 doc");
      System.exit(0);
    }
    String dirname=args[0];
    File dir=new File(dirname);
    String[] dirlist=dir.list();
    for(int i=0;i<dirlist.length;i++){
      System.out.println(dirlist[i]);
    }
  }
}


○実行例

D:\atsushi\Java\List18-6>java ListDir1 doc
file.txt
dir
picture.bmp

○解説

11. File dir=new File(dirname);

public File(String pathname)  //File クラス ← java.io パッケージ
 指定されたパス名文字列を抽象パス名に変換して、新しい File のインスタンスを生成します。
 指定された文字列が空の文字列の場合、結果は空の抽象パス名になります。
パラメータ: pathname - パス名文字列
例外: NullPointerException - pathname 引数が null の場合

12. String[] dirlist=dir.list();

public String[] list()  //File クラス ← java.io パッケージ
 この抽象パス名が示すディレクトリにあるファイルおよびディレクトリを示す文字列の配列を返します。
 この抽象パス名がディレクトリを示さない場合、このメソッドは null を返します。
 ディレクトリを示す場合は、文字列の配列が返されます。文字列は、
 ディレクトリ内の各ファイルまたはディレクトリごとに 1 つです。
 そのディレクトリ自体およびその親のディレクトリを示す名前は結果に含まれません。
 各文字列は、絶対パスではなくファイル名です。
 結果の配列の名前文字列は特定の順序にはなりません。アルファベット順になるわけではありません。
戻り値: この抽象パス名が示すディレクトリにあるファイルおよびディレクトリを示す文字列の配列。
 ディレクトリが空の場合、配列は空になる。
 この抽象パス名がディレクトリを示さない場合、または入出力エラーが発生した場合は null
例外: SecurityException - セキュリティマネージャが存在する場合に、
 セキュリティマネージャの SecurityManager.checkRead(java.lang.String) メソッドが
 ディレクトリへの読み込みアクセスを許可しないとき

実行例のフォルダ doc 以下の構成は次の通りです。

doc
├file.txt
├picture.bmp
└dir
 └dir-file.txt

doc フォルダ自体は List18-6 フォルダに納められています。

例題18-2 ディレクトリを作成する。

○プログラム

import java.io.*;

public class MakeDir1{
  public static void main(String[] args){
    if(args.length!=1){
      System.out.println("使用法:java MakdDir1 ディレクトリ名");
      System.out.println("例:java MakeDir1 doc");
      System.exit(0);
    }
    String dirname=args[0];
    File dir=new File(dirname);
    if(dir.mkdirs()){
      System.out.println(dirname+"を作成しました。");
      System.out.println("絶対パスは"+dir.getAbsolutePath()+"です。");
    }else{
      System.out.println(dirname+"を作成できませんでした。");
    }
  }
}


○実行例

D:\atsushi\Java\List18-7>java MakeDir1 doc
docを作成しました。
絶対パスはD:\atsushi\Java\List18-7\docです。

D:\atsushi\Java\List18-7>java MakeDir1 doc
docを作成できませんでした。

D:\atsushi\Java\List18-7>java MakeDir1 folder1\folder2\folder3
folder1\folder2\folder3を作成しました。
絶対パスはD:\atsushi\Java\List18-7\folder1\folder2\folder3です。

D:\atsushi\Java\List18-7>

○解説

12. if(dir.mkdirs()){

このプログラムで使用している mkdirs メソッドは、必要な親ディレクトリまで含めて作成してくれます。
既にそのディレクトリが存在する場合は作成できません。

親ディレクトリの作成を行わない mkdir メソッドもあります。
mkdir メソッドの場合、ディレクトリの指定に存在しない親ディレクトリを含めることは出来ません。

public boolean mkdirs()  //File クラス ← java.io パッケージ
 この抽象パス名が示すディレクトリを生成します。
 存在していないが必要な親ディレクトリも一緒に作成されます。
 このオペレーションが失敗した場合でも、いくつかの必要な親ディレクトリの生成には成功した場合があります。
戻り値: 必要なすべての親ディレクトリを含めてディレクトリが生成された場合は true、そうでない場合は false
例外: SecurityException - セキュリティマネージャが存在する場合に、
 セキュリティマネージャの SecurityManager.checkWrite(java.lang.String) メソッドが
 指定されたディレクトリおよび必要なすべての親ディレクトリの生成を許可しないとき

public boolean mkdir()  //File クラス ← java.io パッケージ
 この抽象パス名が示すディレクトリを生成します。
戻り値: ディレクトリが生成された場合は true、そうでない場合は false
例外: SecurityException - セキュリティマネージャが存在する場合に、
 セキュリティマネージャの SecurityManager.checkWrite(java.lang.String) メソッドが
 指定されたディレクトリの生成を許可しないとき

実行例における、実行後のフォルダ構成は次の通りです。

List18-7
├MakeDir1.java
├MakeDir1.class
├doc
└folder1
 └folder2
  └folder3

*=*=*=*=*=*=*=*=*=*= 文字ストリームとバイトストリーム *=*=*=*=*=*=*=*=*=*=

文字ストリーム(キャラクタストリーム)とは、16ビットの文字単位でストリームを扱う為の機能です。
バイトストリームとは、8ビットのバイト単位でストリームを扱う為の機能です。
java.io パッケージの Reader 、Writer (及びそのサブクラス)は、文字ストリームを扱うクラスです。
取り扱うデータは文字や文字列です。
Reader 、Writer は抽象クラスですので、実際に使うのはそのサブクラス
(例えば、FileReader 、BufferedReader 、BufferedWriter )になります。
これに対して、java.io パッケージの InputStream 、OutputStream は、バイトストリームを扱うクラスです。
取り扱うデータはバイトやバイト配列です。
InputStream 、OutputStream も抽象クラスですから、実際に使うのはそのサブクラス
(例えば、FileInputStream 、BufferedInputStream 、FileOutputStream)になります。

Java内部で文字を扱うとき、それらは全て Unicode (16ビットの文字コード)です。
しかし、扱いたいファイルの文字コードは、シフトJISコード( Shift_JIS )かも知れませんし、
日本語EUCコード( EUC_JP )かも知れません。
あるいはJISコードかも知れません。
いずれにしても、文字ストリームを扱う Reader や Writer を使えば、
入出力を行うと同時に、文字コードの変換も行うことが出来ます。
Reader や Writer (文字ストリーム関連クラス)を使うことで、
文字コードに依存しないプログラムが書きやすくなるのです。

文字ストリーム入力用のクラス InputStreamReader は、次のように使います。

InputStreamReader reader=new InputStreamReader(System.in,"Shift_JIS");
InputStreamReader reader=new InputStreamReader(System.in);


public class InputStreamReader extends Reader  //java.io パッケージ
 InputStreamReader はバイトストリームから文字ストリームへの橋渡しの役目を持ち、
 バイトデータを読み込んで、指定されたcharset を使用して文字に変換します。
 使用される文字エンコーディングは、名前で指定するか、明示的に渡すか、
 またはプラットフォームのデフォルトの文字エンコーディングをそのまま使うこともできます。

public InputStreamReader(InputStream in,String charsetName)
throws UnsupportedEncodingException
  //InputStreamReader クラス ← java.io パッケージ
 指定された文字エンコーディングを使う InputStreamReader を作成します。
パラメータ: in - InputStream , charsetName - サポートされる charset の名前
例外: UnsupportedEncodingException - 指定された文字エンコーディングがサポートされていない場合

public InputStreamReader(InputStream in)  //InputStreamReader クラス ← java.io パッケージ
 デフォルトの文字エンコーディングを使う InputStreamReader を作成します。

バッファリング機能を付けるには、次のようにします。

BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));

また、文字ストリーム出力用のクラス OutputStreamWriter は、次のように使います。

OutputStreamWriter writer=new OutputStreamWriter(System.out,"EUC-JP");
OutputStreamWriter writer=new OutputStreamWriter(System.out);


public class OutputStreamWriter extends Writer  //java.io パッケージ
 OutputStreamWriter は、文字ストリームからバイトストリームへの橋渡しの役目を持ちます。
 バイトストリームに書き込まれた文字は、指定された charset を使用してバイトに符号化されます。
 使用する文字エンコーディングは、名前で指定するか、明示的に渡すか、
 またはプラットフォームのデフォルトの文字エンコーディングをそのまま使うこともできます。

public OutputStreamWriter(OutputStream out,String charsetName)
throws UnsupportedEncodingException
  //OutputStreamWriter クラス ← java.io パッケージ
 指定された文字エンコーディングを使う OutputStreamWriter を作成します。
パラメータ: out - OutputStream
charsetName - サポートされる charset の名前
例外: UnsupportedEncodingException - 指定された文字エンコーディングがサポートされていない場合

public OutputStreamWriter(OutputStream out)  //OutputStreamWriter クラス ← java.io パッケージ
 デフォルトの文字エンコーディングを使う OutputStreamWriter を作成します。

バッファリング機能を付けるには、次のようにします。

BufferedWriter writer=new BufferedWriter(new OutpuStreamWriter(System.out,"EUC-JP"));

例題18-2-3 指定したファイルの内容を16進数で表示する。

○プログラム

長いので別窓でご用意致しました( click here )。
一部ややこしいので、解説を読みながらご覧下さい。
解説が長いのはプログラムが難しいから、ではないのでご安心を。

○実行例

D:\atsushi\Java\List18-8>java FileDump1 FileDump1.class
00000000:CA FE BA BE 00 00 00 2E - 00 6E 0A 00 24 00 31 09
00000010:00 32 00 33 08 00 34 0A - 00 35 00 36 0A 00 32 00
00000020:37 07 00 38 07 00 39 07 - 00 3A 0A 00 08 00 3B 0A
……省略……
00000710:00 00 00 01 00 28 00 00 - 00 1A 00 06 00 00 00 32
00000720:00 02 00 33 00 06 00 34 - 00 0C 00 36 00 12 00 37
00000730:00 2C 00 39 00 01 00 2F - 00 00 00 02 00 30 EOF

D:\atsushi\Java\List18-8>

○解説

11. in=new DataInputStream(new BufferedInputStream(new FileInputStream(args[0])));

DataInputStream は、byte 、short 、int など様々な型で読み込む為のバイトストリームです
(InputStream のサブクラス)。

java.lang.Object
 |
 +--java.io.InputStream
  |
  +--java.io.FilterInputStream
   |
   +--java.io.DataInputStream

public class DataInputStream extends FilterInputStream implements DataInput
  //java.io パッケージ

public DataInputStream(InputStream in)  //DataInputStream クラス ← java.io パッケージ
 FilterInputStream を作成し、その引数である入力ストリーム in をあとで使用できるように保存します。
 内部バッファ配列が作成され、buf に格納されます。
パラメータ: in - 入力ストリーム

BufferedInputStream は、バッファリングしながら読み込む為のバイトストリームです。

java.lang.Object
 |
 +--java.io.InputStream
  |
  +--java.io.FilterInputStream
   |
   +--java.io.BufferedInputStream

public class BufferedInputStream extends FilterInputStream
  //java.io パッケージ

public BufferedInputStream(InputStream in)  //BufferedInputStream クラス ← java.io パッケージ
 BufferedInputStream を作成し、その引数である入力ストリーム in をあとで使用できるように保存します。
 内部バッファ配列が作成され、buf に格納されます。
パラメータ: in - 基本となる入力ストリーム

FileInputStream は、ファイルから読み込む為のバイトストリームです。

java.lang.Object
 |
 +--java.io.InputStream
  |
  +--java.io.FileInputStream

public class FileInputStream extends InputStream
  //java.io パッケージ
 ファイルシステムのファイルから入力バイトを取得します。どのファイルが有効かはホスト環境に依存します。
 FileInputStream は、生のバイト (イメージデータなど) のストリームを読み込むときに使用します。
 文字のストリームを読み込むときは、FileReader を使用してください。

public FileInputStream(String name)
throws FileNotFoundException
  //FileInputStream クラス ← java.io パッケージ
 ファイルシステムの実際のファイル (パス名 name により指定) に接続することにより、FileInputStream を作成します。
 このファイルの接続を表すために、新しい FileDescriptor オブジェクトが生成されます。
パラメータ: name - システム依存のファイル名
例外: FileNotFoundException - ファイルが存在しないか、普通のファイルではなくディレクトリであるか、
 またはなんらかの理由で開くことができない場合
 SecurityException - セキュリティマネージャが存在し、
 checkRead メソッドがファイルへの読み込みアクセスを拒否する場合

12. int offset=0;

offset は、出力したバイト数を表します。
出力結果の体裁を整える為に使います。

14. byte b=in.readByte();

public final byte readByte() throws IOException  //DataInputStream クラス ← java.io パッケージ
 1 バイトの入力データを読み込んで返します。
 読み込み対象バイトは -128127 の範囲 (上下限値を含む) の符号付きの値として処理されます。
定義:インタフェース DataInput 内の readByte
戻り値: 符号付き 8 ビット byte と見なされる入力ストリームの次のバイト
例外: EOFException - 入力ストリームが終わりに達した場合
 IOException - 入出力エラーが発生した場合

入力ストリームが終わりに達した場合、例外 EOFException が投げられます。
このプログラムでは終了判定を行わず、この例外により無限ループを抜けます。
入力ストリームが終わりに達することは異常なことではありませんから、
例外は必ずしもエラーではない、と言えるでしょう。

15. if(offset%16==0){
16.   System.out.print(intToHexString(offset)+":");

intToHexString は、自作メソッドです。
int 型整数(32ビット)を8桁の16進数を表す文字列に変換して返します。

offset は、出力したバイト数を表しています。
出力は16バイトごとに改行します。
プログラム開始直後なら出力したバイト数を表す値 offset=0 より、
00000000: を出力し、
出力したバイト数が16個なら offset=16 より、
00000010: を出力します。
16進数 00000010 は、10進数 16 ですね。
ただし、ここでは改行しない点に注意して下さい。

17. }else if(offset%8==0){
18.   System.out.print(" - ");
19. }else{
20.   System.out.print(" ");
21. }

出力したバイト数が8個なら区切り" - "を出力し、
16個でも8個でもなければ、空白" "を出力します。
出力の際に改行しないので、空白が無いとバイト毎の区切りが一目で分からなくなってしまうからです。

22. System.out.print(byteToHexString(b));
23. if(offset%16==15){
24.   System.out.println("");
25. }
26. offset++;

出力したバイト数、区切り、空白のいずれかを出力した後に、
読み込んだ1バイトデータを16進数に変換して出力します。

byteToHexString は、自作メソッドです。
byte 型整数(8ビット)を2桁の16進数を表す文字列に変換して返します。

ここで offset=15 ならば、直前に出力したバイトを足して16個のバイトを出力したことになるので改行します。
全ての処理が終了した後に offset の値をインクリメントします。

28. }catch(FileNotFoundException e){
29.   System.out.println(args[0]+"が見つかりません。");
30. }catch(EOFException e){
31.   System.out.println(" EOF");
32. }catch(IOException e){
33.   System.out.println(" "+e);
34. }

FileNotFoundException は、FileInputStream コンストラクタが投げます。
EOFException は、readByte メソッドが投げます。
IOException は、readByte メソッドが投げます。

例外 EOFException が投げられるのは、入力ストリームの終わりに達した場合です。
したがって、ここに終了処理を書きます。
このプログラムでは、入力の終わりを表す記号 EOF を出力して改行するようにしてみました。

36. if(in!=null){
37.   in.close();
38. }

DataInputStream クラスの変数 in が null でなければ(初期化に成功していれば)、
FileInputStream コンストラクタがオープンした入力ストリームを閉じます。
close メソッドは FilterInpuStream クラスで定義されていますが、
DataInputStream クラスはこれを継承しているので使うことが出来ます。

public class FilterInputStream extends InputStream  //java.io パッケージ
 ほかの入力ストリームを格納し、それをデータの基本的なソースとして使用して、
 データを途中で変換したり、追加機能を提供したりします。

public void close() throws IOException  //FilterInputStream クラス ← java.io パッケージ
 入力ストリームを閉じて、このストリームと関連するすべてのシステムリソースを解放します。
 このメソッドは単純に in.close() を実行します。
オーバーライド: クラス InputStream 内の close
例外: IOException - 入出力エラーが発生した場合

43. public static String intToHexString(int n){
44.   return byteToHexString((byte)(n>>>24))
45.   +byteToHexString((byte)(n>>>16))
46.   +byteToHexString((byte)(n>>>8))
47.   +byteToHexString((byte)n);
48. }

intToHexString メソッドは、int型データ(32ビット)をバイト(8ビット)毎に分け、
それらを byteToHexString メソッドに渡します。
そして、それらの戻り値(2桁の16進数)を連結した文字列を返します。

>>> 演算子は、符号無しの右シフト(最上位ビットは常に0) を行います。
シフトした後、byte 型にキャストするのだから、通常の右シフト >> 演算子でも同じですけどね。

49. public static String byteToHexString(byte b){
50.   int n=(int)b;
51.   if(n<0){
52.     n+=256;
53.   }
54.   if(n<16){
55.     return "0"+Integer.toHexString(n).toUpperCase();
56.   }else{
57.     return Integer.toHexString(n).toUpperCase();
58.   }
59. }

先ず、Interger クラスのメソッドを使う為に int 型にキャストします。

public final class Integer extends Number implements Comparable  //java.lang パッケージ
 int を String に、String を int に変換する各種メソッドや、
 int の処理時に役立つ定数およびメソッドも提供します。

public static String toHexString(int i)  //Integer クラス ← java.lang パッケージ
 整数の引数の文字列表現を、基数 16 の符号なし整数として返します。
 引数が負の数の場合は、符号なし int 値は引数に 2の32乗 を加算した値になります。
 そうでない場合は、引数に等しい値になります。
 この値は、16 進数表記 (基数 16) の ASCII 文字列による数字となります。
 前に 0 は付きません。
 符号なしの絶対値がゼロの場合は、単一のゼロ文字 '0' ('\u0030') で表されます。
 そうでない場合は、符号なしの絶対値を表す文字の 1 文字目はゼロではありません。
 大文字を使いたい場合は、生成された文字列に対して String.toUpperCase() メソッドを呼び出すことができます。
パラメータ: i - 文字列に変換する整数
戻り値: 16 進数 (基数 16) の引数で表される符号なし整数値の文字列表現

51. if(n<0){
52.   n+=256;
53. }

ここが一番分かり難いところだと思うのですが、
byte 型から int 型へキャストした際に生じた差異を吸収する為にあります。

例えば、byte 型の -1 を2の補数で表してみましょう。

1000 0001 //-1
1111 1110 //ビット反転
1111 1111 //ビット反転した結果+1

最上位ビットは符号ビットなので、ビット反転しません。

次に、int 型の -1 を2の補数で表してみましょう。

1000 0000 0000 0000 0000 0000 0000 0001 //-1
1111 1111 1111 1111 1111 1111 1111 1110 //ビット反転
1111 1111 1111 1111 1111 1111 1111 1111 //ビット反転した結果+1

byte 型の -1 を敢えて 32ビットで表現するならば次のようになります。
0000 0000 0000 0000 0000 0000 1111 1111
本来同じでなければならない int 型の -1 と違う結果になってしまいました。

ところで、違うとは何が違うのでしょうか?
10進数で考えるならばどちらも同じ値です。
しかし、2進数に変換した場合のビット並びが違います。
最終的に10進数で扱うならば、ビット並びが違うのは、
型によるビット数の違いとして吸収されますが、
最終的に10進数以外で扱うのならば、
ビット並びが違うということは、数が違うということになります。

それでは次に、-1+256=255 を2進数で表してみましょう。

0000 0000 0000 0000 0000 0000 1111 1111 //255

これは byte 型の -1 と同じビット並びになっていることが分かりますね。
10進数の値は違うけれど、ビット並びは同じになりました。

これを16進数に変換すると 000000FF ですが、
このプログラムで Integer.toHexString メソッドに渡しているのは10進数の 255 です。
したがって、255 の16進数表現 FF が文字列として返ってきます。
もともとの値は8ビットでしたから、
これで本来欲しかった8ビットの -1 の16進数表現が得られたことになります。
そして、byteToHexString メソッドを呼び出したメソッド System.out.print メソッドが
FF という文字列を出力することになるのです。

ふー、疲れた(汗)。
私自身理解するのに相当悩みましたが、
私の丁寧な解説で理解できたことでしょう(笑)!

54. if(n<16){
55.   return "0"+Integer.toHexString(n).toUpperCase();
56. }else{
57.   return Integer.toHexString(n).toUpperCase();
58. }

10進数16未満の値を16進数に変換すると1桁になってしまいます。
バイト単位、つまり16進数2桁ずつ出力したいので、1桁では体裁が崩れてしまいます。
体裁を保つ為、このプログラムでは先頭に 0 を付け加えています。

toUpperCase は、String クラスのメソッドです。

public final class String extends Object
implements Serializable, Comparable, CharSequence
  //java.lang パッケージ
 String クラスは文字列を表します。
 Java プログラム内の "abc" などのリテラル文字列はすべて、このクラスのインスタンスとして実行されます。

public String toUpperCase(Locale locale)  //String クラス ← java.lang パッケージ
 指定された Locale の規則を使用して、この String 内のすべての文字列を大文字に変換します。
パラメータ: locale - このロケールの大文字小文字変換規則を使用する
戻り値: 大文字に変換された String

さて、解説が長くなってしまいましたが、実行例をご覧下さい。
FileDump1.class の内容を16進数で表示したものです。
Java のクラスファイル( .class )は必ず CA FE BA BE (Cafe babe)で始まります。
偶然か必然かは分かりませんが、あのロゴを思い浮かべるとニヤリとさせられますね(笑)。

Decorator パターン

FileInputStream は、ファイルから読み込む為のクラスです。
つまり、読み込みの対象を定めています。
BufferedInputStream は、バッファリングしながら読み込む為のクラスです。
つまり、読み込みの効率化を考えている。
でも、読み込みの対象については何も定めていない。
DataInputStream は、様々な型で読み込む為のクラスです。
でも、読み込みの対象や、効率化については何も定めていない。
この三つを重ねることで、私たちは「ファイルから、バッファリングしつつ、様々な型で」読み込むことが出来る
入力ストリームを得ることが出来ます。
組み合わせを変えれば、また違った機能を持つ入力ストリームを得ることが出来ます。
このように、柔軟な複合機能を持ったインスタンス作成を可能にするクラス設計を
Decorator パターンと呼ぶことがあります。
Decorator とは装飾者という意味です。


戻る / ホーム