Last Modified : 2011.01.02

스트림

자바에서 입출력은 스트림을 통해서 이루어진다.
영어사전에 보면 stream 의 뜻으로 실개천이라고 되어 있는데 그대로 이해하면 되겠다.

자바 프로그램을 기준으로 스트림의 방향이 자바 프로그램안으로 향하면 입력스트림,
스트림의 방향이 자바 프로그램 밖으로 향하면 출력스트림이라 한다.
입력스트림의 경우 근원지(Source), 출력스트림의 경우 목적지(Destination)가 있어야 한다.
근원지나 목적지의 형태는 생각보다 다양하다.
예를 들면, 목적지의 경우 일반적으로 떠올리는 파일외에 콘솔화면, 소켓, 웹브라우저가 될 수 있다.

자바에서 스트림 관련 클래스가 워낙 많다.
스트림으로 전달되는 데이터가 문자인지 바이트인지로 나뉘고
위에서 살펴듯이 입력스트림과 출력스트림으로 나뉜다.
그리고 실제로 스트림을 만드는 것과 스트림의 성능에 도움을 주는 것들로 나뉜다.

이번 글에서는 충분하지는 않더라도 스트림을 간단히 소개하는 정도의 예제를 다룰 것이다.

InputTest.java

package net.java_school.stream;

import java.io.*;

public class InputTest {
	public static void main(String[] args) throws IOException {
		InputStreamReader isr = new InputStreamReader(System.in);
		BufferedReader br = new BufferedReader(isr);
		String input = br.readLine();
		System.out.println("입력 : " + input); 
	}
}

소스에서는 먼저 키보드(표준 입력기구)를 근원지(소스)로 하는 InputStreamReader 을 생성한다.
그 다음 입출력의 성능을 위해 버퍼 기능을 가진 BufferedReader객체의 생성자에 InputStreamReader레퍼런스를 인자로 전달한다.
이 예제는 자바 은행이라는 실습 예제에 쓰이는 것이다.
BufferedReader의 readLine() 메소드는 사용자가 엔터키를 칠때까지의 문자열을 반환한다.
이 시점에서 BufferedReader를 API문서에서 찾아보기 바란다.

이번 예제는 문자 데이터를 파일에 출력하는 것이다.

OutputTest.java

package net.java_school.stream;

import java.io.*;

public class OutputTest {
	public static void main(String[] args) {
		FileWriter fw = null;
		try {
			fw = new FileWriter("C:/output.txt", true);
			fw.write("테스트");
			fw.flush();
		} catch(IOException e) {
			e.printStackTrace();
		} finally {
			try {
				fw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

FileWriter클래스는 문자 데이터를 파일에 출력할 때 사용된다.
FileWriter클래스의 생성자의 첫번째 인자는 목적지이고, 두번째 인자는 파일에 있는 기존 내용을 그대로 둘지 여부를 결정하는 플래그이다. true면 기존 내용을 그대로 둔다
입력스트림에서와 달리 출력일 경우 제대로 출력스트림을 닫아주는 것이 중요하다.
소스에서 15라인의 fw.close() 가 그것이다.
이 예제는 "로깅(Logging)"의 첫번째 예제에서 쓰이는 코드이다.

다음 예제는 객체스트림에 관한 예제이다.
객체를 파일이나 네트워크을 통해 전달하려면 직렬화 과정이 필요하다.
파일이나 네트워크를 통해 전달된 객체스트림으로부터 다시 객체를 만들어 내려면 역직렬화 과정이 필요하다.

다음 예제는 객체를 파일에 저장하고 다시 파일에서 저장된 객체정보로부터 객체를 생성하는 예제이다.

Address.java

package net.java_school.serial;

import java.io.Serializable;

public class Address implements Serializable {

	private static final long serialVersionUID = -201008311558L;
	private String mobile;
	private String address;
	
	public String getMobile() {
		return mobile;
	}
	public void setMobile(String mobile) {
		this.mobile = mobile;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	
}

Address객체가 직렬화되어 스트림을 타고 파일에 저장될 것이라면
Address클래스는 반드시 Serializable인터페이스를 구현한다고 선언해야 한다.

public class Address implements Serializable

그런데 이 인터페이스에는 구현할 메소드가 없다.
이것은 이 클래스로부터 생성된 객체는 직렬화 대상이라는 것을 버추얼 머신에게 알리는 선언적 의미의 인터페이스이다.

자바의 기본 자료형이나 String그리고 Collection관련 클래스는 원래 직렬화가 가능하도록 되어 있다.
하지만 우리가 만드는 클래스는 Serializable인터페이스를 구현한다고 선언을 해야 직렬화 할 수 있다.

Backup.java

package net.java_school.serial;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Backup {
	public static void main(String[] args) {
		ObjectOutputStream out = null;
		try {
			out = new ObjectOutputStream(new FileOutputStream("address.txt"));
			Address addr = new Address();
			addr.setMobile("010-1234-5678");
			addr.setAddress("서울 서초구");
			out.writeObject(addr);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

백업(Backup)은 말 그대로 Address객체를 하나 생성하고 이것을 파일에 저장하는 클래스이다.
파일에 저장하기에 FileOutputStream이 쓰였고 객체스트림을 만들기 위해 ObjectOutputStream이 쓰였다.

Recovery.java

package net.java_school.serial;

import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Recovery {
	public static void main(String[] args) {
		ObjectInputStream in = null;
		try {
			in = new ObjectInputStream(new FileInputStream("address.txt"));
			while(true) {
				Address addr = (Address) in.readObject();
				System.out.println(addr.getMobile());
				System.out.println(addr.getAddress());
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (EOFException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			try {
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

복구(Recovery)는 말그대로 address.txt파일에 저장된 객체 상태로 부터 객체를 메모리에 올리는 프로그램이다.
파일로부터의 입력스트림이기에 FileInputStream이 쓰였고 객체스트림이기에 ObjectInputStream 이 쓰였다.
그리고 확인을 위해 표준 출력 메소드를 이용했습니다.
직렬화는 RMI 기본 개념을 이해하기 위해서도 필요하다.