[Java][I/O] 바이트기반의 보조 스트림

2022. 6. 23. 14:06JAVA/Language

1. FilterInputStream / FilterOutputStream

FilterInputStream / FilterOutputStream은 InputStream / OutputStream의 자손이면서 모든 보조 스트림의 조상입니다.

  • FilterInputStream의 자손 : BufferedInputStream, DataInputStream, LineNumberInputStream, PushBackInputStream 등
  • FilterOutputStream의 자손 : BufferedOutputStream, DataOutputStream, PrintStream 등

 

FilterInputStream / FilterOutputStream의 모든 메서드는 단순히 기반 스트림(InputStream / OutputStream)의 메서드를 그대로 호출할 뿐입니다. FilterInputStream / FilterOutputStream은 상속을 통해 원하는 작업을 수행하도록 읽고 쓰는 메서드를 오버라이딩해야 합니다.

 

2. BufferedInputStream / BufferedOutputStream

BufferedInputStream / BufferedOutputStream은 스트림의 입출력 효율을 높이기 위해 버퍼를 사용하는 보조스트림입니다. BufferedInputStream의 경우 입력소스로부터 버퍼 크기만큼의 데이터를 읽어다 자신의 내부 버퍼에 저장합니다. 그 다음 데이터를 읽기 위해서 read메서드가 호출되면 다시 버퍼크기만큼의 데이터를 읽어다 버퍼에 저장해놓습니다.

 

BufferedInputStream 생성자

BufferedInputStream(InputStream in, int size)
BufferedInputStream(InputStream in)
  • BufferedInputStream(InputStream in, int size) : 지정된 크기(byte 단위)의 버퍼를 갖는 BufferedInputStream 인스턴스를 생성합니다.
  • 버퍼의 크기를 초기화하지 않으면 기본값으로 8192byte 크기의 버퍼를 갖습니다.

BufferedOutputStream의 메서드 / 생성자

BufferedOutputStream(OutputStream out, int size)
BufferedOutputStream(OutputStream out)
flush()
close()
  • BufferedOutputStream(OutputStream out, int size) : 지정된 size 만큼의 버퍼를 갖는 BufferedOutputStream 인스턴스를 생성합니다.
  • flush()  : 버퍼의 모든 내용을 출력소스에 출력한 다음. 버퍼를 비웁니다.
  • close() : flush()를 호출해서 버퍼의 모든 내용을 출력소스에 출력하고, BufferedOutputStream 인스턴스가 사용하던 모든 자원을 반환합니다. 

BufferedOutputStream은 write메서드를 이용하여 출력이 BufferedOutputStream의 버퍼에 저장됩니다. 버퍼가 가득 차게되면 그때 버퍼의 모든 내용을 출력소스에 출력합니다. 그리고 버퍼를 비우고 다시 프로그램으로부터 출력을 저장할 준비를 합니다.

 

다음 예제는 1~9의 문자를 텍스트 파일에 출력하여 저장하는 예제입니다.

	@Order(1)
	@Test
	void bufferedOutputStreamWriteTest() {
		printMethodName("bufferedOutputStreamWriteTest");
		
		try {
			FileOutputStream fos = new FileOutputStream("./src/ch15/ex_03_BufferedInputStream_BufferedOutputStream/123.txt");
			// BufferedOutputStream의 버퍼 크기를 5로한다.
			BufferedOutputStream bos = new BufferedOutputStream(fos, 5);
			
			// 파일 123.txt에 1부터 9까지 출력
			for(int i = '1'; i <= '9'; i++) {
				bos.write(i);
			}
			
			fos.close();
		}catch (IOException e) {
			e.printStackTrace();
		}	
	}
123.txt 파일의 내용
12345

왜 123 텍스트 파일에는 "123456789"가 아닌 "12345"가 저장되었는가?

반복문이 끝났을때 버퍼에는 "6789"가 저장되었고 BufferedOutputStream의 기반스트림인 FileOutputStream 인스턴스(fos)가 close를 호출해서 버퍼에 있는 내용을 출력하지 못하였기 때문입니다. 이 문제를 해결하기 위해서는 FileOutStream 인스턴스를 close 하는 것이 아닌 보조 스트림인 BufferedOutputStream의 인스턴스를 close 해야합니다. bos 인스턴스의 close하는 과정주에서 flush()를 호출하여 버퍼에 있는 내용을 출력을 한다음에 비우게 되고 기반 스트림인 FileOutputStream을 종료하게 됩니다.

 

다음은 BufferedInputStream의 조상 클래스인 FilterOutputStream 클래스의 close 메서드 일부입니다.

public class FilterOutputStream extends OutputStream{
	protected OutputStream out;
    public FilterOutputStream(OutputStrema out){
    	this.out = out;
    }
    
    ...
    
    public void close() throws IOException{
    	try{
        	flush();
        }catch(IOException ignored){}
        out.close(); // 기반 스트림의 close()를 호출한다.
    }
}

위와 같이 보조스트림을 사용한 경에는 기반 스트림의 close()나 flush()를 호출할 필요없이 보조스트림의 close()를 호출하기만 하면 됩니다.

 

3. DataInputStream / DataOutputStream

DataInputStream / DataOutputStream도 각각 FilterInputStream / FilterOutputStream의 자손이며 DataInputStream은 DataInput 인터페이스를, DataOutputStream은 DataOutput 인터페이스를 구현했기 때문에 데이터를 읽고 쓰는데 있어서 byte 단위가 아닌, 8가지 기본 자료형(int, float, char 등)의 단위로 읽고 쓸수 있다는 장점이 있습니다.

 

DataInputStream 메서드 / 생성자

// 생성자
DataInputStream(InputStream in)

// 각 타입에 맞게 값을 읽음. 더이상 읽을 값이 없으면 EOFException을 발생시킴
boolean readBoolean()
byte    readByte()
char    readChar()
short   readShort()
int     readInt()
long    readLong()
float   readFloat()
double  readDouble()
int     readUnsignedByte()
int     readUnsignedShort()

void readFully(byte[] b)
void readFully(byte[] b, int off, int len)

String readUTF()
static String readUTF(DataInput in)
int skipBytes(int n)

 

DataOutputStream의 생성자 / 메서드

// 생성자
DataOutputStream(OutputStream out)

// 각 자료형에 알맞는 값을 출력함
void writeBoolean(boolean b)
void writeByte(byte b)
void writeChar(int c)
void writeShort(int s)
void writeInt(int i)
void writeLong(long l)
void writeFloat(float f)
void writeDouble(dobule d)

void writeUTF(String s)
void writeChars(String s)
int  size()

 

다음 예제는 DataOutputStream을 기반으로 인스턴스를 생성한 후 sample.dat 파일에 값들을 출력하고 읽는 예제입니다.

	@Order(1)
	@Test
	void writeIntFloatBooleanMethodTest() {
		printMethodName("writeIntFloatBooleanMethodTest");
		
		FileOutputStream fos = null;
		DataOutputStream dos = null;
		
		try {
			fos = new FileOutputStream("./src/ch15/ex_04_DataInputStream_DataOutputStream/sample.dat");
			dos = new DataOutputStream(fos);
			
			dos.writeInt(10);
			dos.writeFloat(20.0f);
			dos.writeBoolean(true);
			
			dos.close();
		}catch (IOException e) {
			e.printStackTrace();
		}
		
		FileInputStream fis = null;
		DataInputStream dis = null;
		
		try {
			fis = new FileInputStream("./src/ch15/ex_04_DataInputStream_DataOutputStream/sample.dat");
			dis = new DataInputStream(fis);
			
			int   a   = dis.readInt();
			float b   = dis.readFloat();
			boolean c = dis.readBoolean();
			
			System.out.println(a); // 10
			System.out.println(b); // 20.0
			System.out.println(c); // true
			
			dis.close();
		}catch (IOException e) {
			e.printStackTrace();
		}
	}

 

4. SequenceInputStream

SequenceInputStream은 여러 개의 입력 스트림을 연속적으로 연결해서 하나의 스트림으로부터 데이터를 읽는 것과 같이 처리할 수 있도록 도와줍니다.

 

SequenceInputStream의 생성자

SequenceInputStream(Enumeration e)
SequenceInputStream(InputStream s1, InputStream s2)

 

다음 예제는 여러개의 ByteArrayInputStream들을 하나의 스트림으로 통합하여 ByteArrayOutputStream을 통해서 출력하는 예제입니다.

	@Order(1)
	@Test
	void sequenceInputStreamTest() {
		byte[] arr1   = {0, 1, 2};
		byte[] arr2   = {3, 4, 5};
		byte[] arr3   = {6, 7, 8};
		byte[] outSrc = null;
		
		Vector<InputStream> v = new Vector<InputStream>(); 
		v.add(new ByteArrayInputStream(arr1));
		v.add(new ByteArrayInputStream(arr2));
		v.add(new ByteArrayInputStream(arr3));
		
		SequenceInputStream   input  = new SequenceInputStream(v.elements());
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		
		int data = 0;
		
		try {
			while((data = input.read()) != -1) {
				output.write(data);
			}
		}catch (IOException e) {
			e.printStackTrace();
		}
		
		outSrc = output.toByteArray();
		
		System.out.println("Input Source1 : " + Arrays.toString(arr1));
		System.out.println("Input Source2 : " + Arrays.toString(arr2));
		System.out.println("Input Source3 : " + Arrays.toString(arr3));
		System.out.println("Output Source : " + Arrays.toString(outSrc));
	}
Input Source1 : [0, 1, 2]
Input Source2 : [3, 4, 5]
Input Source3 : [6, 7, 8]
Output Source : [0, 1, 2, 3, 4, 5, 6, 7, 8]

 

5. PrintStream

PrintStream은 데이터를 기반스트림에 다양한 형태로 출력할 수 있는 print, println, printf와 같은 메서드를 오버로딩하여 제공합니다. PrintStream 인스턴스는 System 클래스의 out 필드멤버를 통해 접근할 수 있습니다. 그래서 System.out.println()과 같은 형태로 출력할 수 있습니다.

 

PrintStream의 생성자 / 메서드

// 생성자
PrintStream(File file)
PrintStream(File file, String csn)
PrintStream(OutputStream out)
PrintStream(OutputStream out, boolean autoFlush)
PrintStream(OutputStream out, boolean autoFlush, String encoding)
PrintStream(String fileName)
PrintStream(String fileName, String csn)

boolean checkError() : 스트림을 flush하고 에러가 발생했는지를 알려줌

void print(기본데이터타입 val)
void print(char[] c)
void print(Object o)
void print(String s)
void println(기본데이터타입)
void println(char[] c)
void println(Object o)
void println(String s)

void println()

PrintStream printf(String format, Object... args) : 정형화된 출력을 가능하게함
protected void setError() : 작업중에 오류가 발생했음을 알림

 

printf() 메서드의 출력에 사용될 수 있는 옵션

	/**
	 * title : PrintStream 사용
	 * System 클래스의 out 멤버는 PrintStream 인스턴스임
	 * 
	 * printf() : 메서드에 형식화를 수행하여 출력할 수 있음
	 * 
	 * 메서드의 정수의 출력에 사용될 수 있는 옵션, 결과(int i=65)
	 * %d   : 10진수 , 65
	 * %o   : 8진수  , 101
	 * %x   : 16진수 , 41
	 * %c   : 문자   , A
	 * %s   : 문자열  , 65
	 * %5d  : 5자리 숫자. 빈자리는 공백으로 채운다. , '   '65
	 * %-5d : 5자리 숫자. 빈자리는 공백으로 채운다. (왼쪽 정렬), 65'   '
	 * %05d : 5자리 숫자. 빈자리는 0으로 채운다., 00065
	 * 
	 * 문자열의 출력에 사용될 수 있는 옵션, 결과(String str = "ABC")
	 * %s   : 문자열, ABC
	 * %5s  : 5자리 문자열. 빈자리는 공백으로 채운다., '  'ABC
	 * %-5s : 5자리 문자열. 빈자리는 공백으로 채운다., ABC'  '
	 * 
	 * 실수의 출력에 사용될 수 있는 옵션, 결과(float f = 1234.56789f)
	 * %e     : 지수형태표현 , 1.234568e+03
	 * %f     : 10진수    , 1234.56789
	 * %3.1f  : 출력될 자리수를 최소 3자리(소수점포함), 소수점 이하 1자리(2번째 자리에서 반올림), 1234.6
	 * %8.1f  : 소수점이상 최소 6자리, 소주점 이하 1자리.
	 * 출력될 자리수를 최소 8자리(소수점 포함)을 확보한다. 빈자리는 공백으로 채워진다.(오른쪽 정렬), '  '1234.6
	 * %08.1f : 소수점이상 최소 6자리. 소주점 이하 1자리
	 * 출력될 자리수를 최소 8자리(소수점포함)을 확보한다. 빈자리는 0으로 채워진다., 001234.6
	 * %-8.1f : 소수점이상 최소 6자리. 소수점 이하 1자리.
	 * 출력될 자리수를 최소 8자리(소수점포함)을 확보한다. 빈자리는 공백으로 채워진다.(왼쪽 정렬), 1234.6'  '
	 * 
	 * 특수문자를 출력하는 옵션
	 * \t     : 탭(tab)
	 * %n     : 줄바꿈 문자(new line)
	 * %%     : %
	 * 
	 * 날짜와 시간의 출력에 사용될 수 있는 옵션
	 * %tR, %tH:%tM     : 시분(24시간) , 21:05
	 * %tT, %tH:%tM:%tS : 시분초(24시간), 21:05:33
	 * %tD, %tm/%td/%ty : 월일년       , 11/16/15
	 * %tF, %tY-%tm-%td : 년월일       , 2015-11-16
	 * 
	 * '숫자$'를 옵션앞에 붙여줌으로써 출력된 매개변수를 지정해줄수 있음
	 */

 

다음은 printf 메서드를 사용한 예제입니다.

	@Order(1)
	@Test
	void printStreamTest() {
		int i   = 65;
		float f = 1234.56789f;
		Date d  = new Date();
		
		System.out.printf("문자 %c의 코드는 %d\n", i, i);
		System.out.printf("%d는 8진수로 %o, 16진수로 %x\n", i, i, i);
		System.out.printf("%3d%3d%3d\n", 100, 90, 80);
		System.out.println();
		
		System.out.printf("123456789012345678901234567890\n");
		System.out.printf("%s%-5s%5s%n", "123", "123", "123");
		System.out.println();
		
		System.out.printf("%-8.1f%8.1f %e%n", f, f, f);
		System.out.println();
		
		System.out.printf("오늘은 %tY년 %tm월 %td일입니다.\n", d, d, d);
		System.out.printf("오늘은 %tH시 %tM분 %tS초입니다.\n", d, d, d);
		System.out.printf("지금은 %1$tH시 %1$tM분 %1$tS초입니다.\n", d);
	}
문자 A의 코드는 65
65는 8진수로 101, 16진수로 41
100 90 80

123456789012345678901234567890
123123    123

1234.6    1234.6 1.234568e+03

오늘은 2022년 06월 23일입니다.
오늘은 14시 05분 46초입니다.
지금은 14시 05분 46초입니다.

 

References

source code : https://github.com/yonghwankim-dev/java_study/tree/main/ch15
[도서] Java의 정석, 남궁 성 지음