Java 応用編 7日目

この章では、I/Oストリームについて取り上げます

I/Oストリームについて

Javaにおけるストリームとは、プログラムと入出力先(ファイル、ネットワーク、コンソールなど)との間でデータをやり取りする流れを抽象化した概念です
ストリームを利用することで、異なる入出力先に対しても統一的な方法でデータの読み書きを行うことができます

ストリームには、大きく分けてバイトストリームと文字ストリームの2種類があります

  1. バイトストリーム
    バイトストリームは、8ビット単位のバイトデータを扱うためのストリームです
    主にバイナリファイルの読み書きや、ネットワーク通信におけるデータの送受信などに使用されます
  2. 文字ストリーム
    文字ストリームは、文字データを扱うためのストリームです
    テキストファイルの読み書きや、文字列の処理などに使用されます

また、ストリームには、データの流れの方向によって入力ストリームと出力ストリームがあります

キーボードからの入力

キーボードからの入力を読み取るには、入力ストリームのScannerクラスを利用します

文字列を取得する

キーボードから文字列を取得する、サンプルプログラムを以下に示します

import java.util.Scanner;

public class Sample{
	public static void main(String[] args) {
		System.out.print("あなたの名前を入力してください:");
		Scanner scanner = new Scanner(System.in);
		String name  = scanner.nextLine();
		System.out.println("こんにちは。" + name + "さん。");
        scanner.close();
    }
}
実行結果
あなたの名前を入力してください:Taro Yamada
こんにちは。Taro Yamadaさん。

このプログラムでは、Scannerクラスを使って標準入力から名前を受け取ります

import java.util.Scanner;

Scannerクラスを使うために必要な宣言文です
プログラムの先頭で定義します
Scannerクラスはキーボードから入力された文字や数字を簡単に読み込むことができるメソッドがそろっています

Scanner scanner = new Scanner(System.in);
String name  = scanner.nextLine();

Scannerクラスのインスタンスを作成し、nextLineメソッドを呼び出します
キーボードからEnterが入力されるまで一時停止状態になり、Enterが入力されると入力された文字列を受け取り、nameに代入します

scanner.close();

Scanner オブジェクトが使用しているリソースを解放します
リソースの解放や予期せぬ問題の回避のために、できる限り呼び出すことが推奨されます
Java7以降では、try-catch を使うと、Scanner オブジェクトが try ブロックを抜ける際に、自動的に close() メソッドが呼び出されます

以下は、カンマ区切りの複数の文字列を取得するサンプルプログラムです

import java.util.Scanner;

public class Sample{
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);

        System.out.println("カンマ区切りの文字列を入力してください:");
        String input = scanner.nextLine();

        // カンマで文字列を分割
        String[] values = input.split(",");

        // 分割された文字列を表示
        System.out.println("入力された値:");
        for (int i=0;i<values.length;i++) {{
            System.out.println(values[i].trim()); // trim()で空白を取り除く
        }

        scanner.close();
    }
}
カンマ区切りの文字列を入力してください:
apple,banana,orange
入力された値:
apple
banana
orange

入力された文字列を受け取り、split関数を使い文字列を分割し配列valuesに格納します

String[] values = input.split(",");

数字列を取得する

キーボードから一つの整数を受け取るサンプルプログラムを以下に示します

import java.util.Scanner;

public class Sample{
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);

		System.out.print("繰り返しの回数を入力してください:");
		int num = scanner.nextInt();
		System.out.println(num + "回繰り返します");

    scanner.close();
  }
}
繰り返しの回数を入力してください:10
10回繰り返します

nextInt()メソッドを使用しています

int num = scanner.nextInt();

※double型の値を一つ受け取りたいときは、nextDouble()nextFloat()メソッドなども用意されています

カンマ区切りの数値を取得する場合は、基本的な処理の流れは同じですが、文字列として読み込んだ後、数値に変換する処理が必要になります
以下にサンプルプログラムを示します

import java.util.Scanner;

public class Sample{
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);

        System.out.println("カンマ区切りの数値を入力してください:");
        String input = scanner.nextLine();

        // カンマで文字列を分割
        String[] numberStrings = input.split(",");

        // 数値に変換して配列に格納
        int[] numbers = new int[numberStrings.length];
        for (int i = 0; i < numberStrings.length; i++) {
            try {
                numbers[i] = Integer.parseInt(numberStrings[i].trim());
            } catch (NumberFormatException e) {
                System.err.println("'" + numberStrings[i].trim() + "' は数値に変換できません。");
                // 必要に応じて、エラー処理を行うか、プログラムを終了する
                return; // ここではプログラムを終了する例
            }
        }

        // 数値を表示
        System.out.println("入力された数値:");
        for (int number : numbers) {
            System.out.println(number);
        }

        scanner.close();
    }
}		
カンマ区切りの数値を入力してください:
1,2,a
'a' は数値に変換できません。
int[] numbers = new int[numberStrings.length]; 

分割された文字列の数だけint型の数値を格納する配列を作成します

for (int i = 0; i < numberStrings.length; i++) 

forループを使って、numberStrings配列の各要素を順番に処理します

Integer.parseInt(numberStrings[i].trim()); 

文字列をint型の数値に変換します
trim()で空白を取り除くことで、”10 , 20″ のような入力にも対応できます

try-catchブロックは、Integer.parseInt()で数値に変換できない文字列が入力された場合の処理を行います

} catch (NumberFormatException e) {

NumberFormatExceptionは、数値に変換できない文字列が渡されたときに発生する例外です
catchブロックでは、エラーメッセージを表示し、ここではプログラムを終了しています

【演習問題】

  1. キーボードから⽂字列を⼊⼒後、受け取った⽂字列とその⻑さを表⽰するプログラムを作成してみましょう

バイトストリーム

バイトストリームはInputStream/OutputStreamを親クラスとしています

FileOutputStream

ファイルへバイトストリームを書き込みためのクラスです

書き込みは以下の手順で行います

1. FileOutputStreamのインスタンス化を実行します
・コンストラクタにファイルパスを指定します
・追記モードで開く場合は、第2引数にtrueを指定します

FileOutputStream obj = new FileOutputStream(ファイル名) ;
FileOutputStream obj = new FileOutputStream(ファイル名,true); 

2. write()メソッドを使ってデータを書き込みます
・バイト配列、またはバイト単位でデータを書き込むことができます

obj.write(int b); // 指定されたバイトをこのファイル出力ストリームに書き込みます
obj.write(byte[] b); // 指定されたバイト配列のb.lengthバイトをこのファイル出力ストリームに書き込みます

3. close()メソッドでストリームを閉じます

FileInputStream

ファイルからバイトストリームを読み込みためのクラスです

読み込みは以下の手順で行います

1.FileInputStreamのインスタンス化を実行します
・コンストラクタにファイルパスを指定します

FileInputStream obj = new FileInputStream(ファイル名)

2. read()メソッドを使ってデータを読み込みます
・バイト配列に読み込む、またはバイト単位で読み込むことができます
・ファイルの終端に達すると-1を返します

int ret = obj.read();

データのバイトを読み込みます
戻り値は、次のバイトで、ファイルの終わりに達した場合は-1を返します

3.close()メソッドでストリームを閉じます

obj.close()

FileInputStream、FileInputStreamを使い、バイナリファイルをコピーするサンプルプログラムを以下に示します

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileInput {

	public static void main(String[] args) {
		FileInputStream fis = null;
		FileOutputStream fos = null;
		
		try {
			fis = new FileInputStream("./star.png");
			fos = new FileOutputStream("./star_copy.png");
			
			byte[] buffer = new byte[1024]; // バッファサイズは適宜調整
      int bytesRead;

      while ((bytesRead = fis.read(buffer)) !-1) {
            fos.write(buffer, 0, bytesRead);
      }

      System.out.println("バイナリファイルのコピーが完了しました。");
    } catch(IOException e) {
			e.printStackTrace();
		}finally {
			if(fis!null) {
				try {
              fis.close();
        } catch (IOException e) {
              e.printStackTrace();
        }				
			}
			if(fos!null) {
				try {
             fos.close();
        } catch (IOException e) {
             e.printStackTrace();
        }				
			}
		}
	}
}
実行結果
バイナリファイルのコピーが完了しました。

FileInputStreamとFileOutputStreamのインスタンス化を実行します

fis = new FileInputStream("./star.png");

コピー元のファイルを読み込むためのFileInputStreamを作成します

fos = new FileOutputStream("./star_copy.png");

コピー先のファイルに書き込むためのFileOutputStreamを作成します

バッファを使用してファイルの読み書きを実行します

byte[] buffer = new byte[1024];

データを一時的に保存するバッファを作成します。バッファサイズは任意の値に調整できます

while ((bytesRead = fis.read(buffer)) != -1) 

ファイルをバッファに読み込みます
fis.read(buffer) は、読み込んだバイト数を返し、ファイルの終端に達すると-1を返します

fos.write(buffer, 0, bytesRead); 
バッファに読み込んだデータをファイルに書き込みます

try-catch ブロックで、IOExceptionが発生した場合にエラーメッセージを表示します
finally ブロックで、FileInputStreamとFileOutputStreamを確実にクローズします
close() メソッドはIOExceptionをスローする可能性があるため、ここでもtry-catchブロックで囲みます
finallyブロックで明示的にclose()メソッドを呼び出す必要があります

コピー元のファイルが存在しない場合や、コピー先に書き込み権限がない場合は、IOExceptionが発生します

try-with-resources文

Java SE7以降では、try-with-resources文が実装され文の終わりで各リソースが確実に閉じられるようになりました
try-with-resources文を使用すると、finallyブロックでのclose()処理を省略できます
コードは次のようになります

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileInput {
	public static void main(String[] args) {
   		try (FileInputStream fis = new FileInputStream("./star.png");
		     FileOutputStream fos = new FileOutputStream("./star_copy.png")) {
			
			      byte[] buffer = new byte[1024]; // バッファサイズは適宜調整
            int bytesRead;

            while ((bytesRead = fis.read(buffer)) !-1) {
                fos.write(buffer, 0, bytesRead);
            }

            System.out.println("バイナリファイルのコピーが完了しました。");
        } catch(IOException e) {
			e.printStackTrace();
		}
	}
}

try-catch-resourcesを使用する方が、コードが簡潔になり、リソースのクローズ忘れを防ぐことができるため、推奨されます

文字ストリーム

文字ストリームは、Reader/Writerクラスがすべての親クラスになっています

FileWriterとFileReaderは、Javaでテキストファイルを読み書きするための基本的なクラスで、文字単位で読み書きを実行います

FileWriter

FileWriterは、テキストファイルを書き込むためのクラスです

以下の手順で書き込みを行います

1. FileWriterのインスタンス化を実行します
・コンストラクタにファイルパスを指定します
・追記モードで開く場合は、第2引数にtrueを指定します

FileWriter fw = new FileWriter(filePath);
FileWriter fw = new FileWriter(filePath,true);

2. writeメソッドを使い文字列の書き込みを行います
・引数に文字列を渡します

fw.write(String s); 

文字列をファイルに書き込みます
改行する場合は\n を指定します

3. finally ブロックで、FileWriterを確実にクローズします

fw.close();

なお、出力されるテキストファイルの文字コードはデフォルトの文字コードが使用されます
デフォルトの文字コードは以下のように確認できます

System.out.println(System.getProperty("file.encoding"));

FileReader

FileReaderは、テキストファイルを読み込むためのクラスです

以下の手順で読み込みを行います
1. FileReaderのインスタンス化を実行します
・コンストラクタにファイルパスを指定します

FileReader fr = new FileReader(filePath); 

指定したファイルパスから読み込むためのFileReaderを作成します

2. readメソッドを使い、文字単位で読み込みを実行します

int dat = fr.read();

1文字ずつ読み込み、その文字コードをint型で返します。ファイルの終端に達すると-1を返します

 int count = fr.read(char[] c);

引数cにデータがコピーされ、戻り値は読み取られた文字数です。ファイルの終端に達すると-1を返します

3. finally ブロックで、FileReaderを確実にクローズします

テキストファイルをコピーするサンプルプログラムを以下に示します

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileInput {

	public static void main(String[] args) {
       try (FileReader fr = new FileReader("source.txt");
              FileWriter fw = new FileWriter("source_copy.txt");){
            
            int c;
            while ((c = fr.read()) !-1) {
                fw.write(c); // 1文字ずつ書き込む
            }

            System.out.println("テキストファイルのコピーが完了しました。");

        } catch (IOException e) {
            System.err.println("エラーが発生しました: " + e.getMessage());

        } 
    }
}
実行結果
テキストファイルのコピーが完了しました。

try-with-resource文中でインスタンス化を実行します

 try (FileReader fr = new FileReader("source.txt");
      FileWriter fw = new FileWriter("source_copy.txt");){

FileReader fr = new FileReader(sourceFilePath); コピー元のファイルを読み込むためのFileReaderを作成します
FileWriter fw = new FileWriter(destinationFilePath); コピー先のファイルに書き込むためのFileWriterを作成します

文字単位での読み書きを実行します

while ((c = fr.read()) != -1) {
    fw.write(c); // 1文字ずつ書き込む
}

fr.read() は、1文字ずつ読み込み、その文字コードをint型で返し、ファイルの終端に達すると-1を返します
fw.write(c); 読み込んだ文字コードをファイルに書き込みます

例外処理を実装します

 catch (IOException e) {
    System.err.println("エラーが発生しました: " + e.getMessage());
 } 

try-catch ブロックで、IOExceptionが発生した場合にエラーメッセージを表示します

バッファストリーム

上述したサンプルはすべて1バイトあるいは1文字ずつ処理をしているため大きいファイルを扱う場合には効率が悪く処理が遅くなることがあります
そこで、Javaではバッファストリームを実装し、高速化を図っています
バイトストリームには、BufferedInputStream/BufferedOutputStream、文字ストリームにはBufferdReader、BufferdWriterクラスがあります

ここでは、BufferdReader、BufferdWriterを使いテキストファイルをコピーするプログラムを紹介します

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class FileInput {
	public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("source.txt"));
             BufferedWriter bw = new BufferedWriter(new FileWriter("source_copy.txt"));){
            
            String line;
            while ((line = br.readLine()) !null) {
            	bw.write(line);
                bw.newLine(); // 1行ずつ書き込む
            }

            System.out.println("テキストファイルのコピーが完了しました。");

        } catch (IOException e) {
            System.err.println("エラーが発生しました: " + e.getMessage());

        } 
    }
}
実行結果
テキストファイルのコピーが完了しました。

BufferedReaderのインスタンスを作成します

BufferedReader br = new BufferedReader(new FileReader("source.txt")); 

ileReaderを引数として渡すことで、指定したファイルを読み込むためのBufferedReaderを作成します

BufferedWriterのインスタンスを作成します

BufferedWriter bw = new BufferedWriter(new FileWriter(source_copy.txt)); 

FileWriterを引数として渡すことで、指定したファイルに書き込むためのBufferedWriterを作成します

コピー元のファイルを1行ずつ読み込みます

while ((line = br.readLine()) != null) {

br.readLine() で、1行ずつ文字列として読み込みます
ファイルの終端に達するとnullを返します
whileループで、すべての行を読み込むまでループします

コピー先に出力します

bw.write("文字列");

文字列をバッファに書き込みます

bw.newLine();

改行コードをバッファに書き込みます

try-with-resources ステートメントを使用することで、BufferedReader、BufferedWriterを自動的にクローズします

I/Oストリームについては、以下のURLをご参照ください
URL:https://docs.oracle.com/cd/E26537_01/tutorial/essential/io/streams.html

【演習問題】

  1. それぞれのサンプルプログラムを実行してみましょう
  2. FileInputStream、FileOutputStreamを使ったサンプルもバッファストリームを使って書き換えてみましょう

コメント

この記事へのコメントはありません。

関連記事

Java 基本編 2日目

C言語 応用編 ~4日目~

Java 応用編 5日目

PAGE TOP