Java :: I/O :: 2. 바이트기반 스트림

김병철·2022년 9월 15일
0

Java

목록 보기
9/20

Java의 정석 3판을 보며 공부한 내용을 정리하였습니다.
남궁성님께 많이 배우고 있습니다.

2. 바이트기반 스트림

2.1 InputStream과 OutputStream

InputStream과 OutputStream은 모든 바이트기반 스트림의 조상이다.

# 메서드 종류

InputStream의 메서드

  • int available() : 스트림으로부터 읽어 올 수 있는 데이터의 크기 반환

  • void close() : 스트림을 닫음으로써 사용하고 있던 자원 반환

  • boolean markSupported() : 현재위치를 표시. 후에 reset()에 의해 표시한 위치로 다시 돌아갈 수 있다.
    (readlimit : 되돌아갈 수 있는 byte의 수)

  • abstract int read() : 1byte를 읽어 온다(0~255). 더 이상 읽어 올 데이터가 없으면 '-1'반환.
    (abstract메서드라서 InputStream의 자손들은 자신의 상황에 알맞게 구현해야 한다.)

  • int read(byte[] b) : 배열 b의 크기만큼 읽어서, 배열 b의 지정된 위치(off)부터 저장.
    (실제로 읽어올 수 있는 데이터가 len개보다 적을 수 있다.)

  • int read(byte[] b, int off, int len) : 최대 len개의 byte를 읽어서, 배열 b의 지정된 위치(off)부터 저장한다.
    (실제로 읽어 올 수 있는 데이터가 len개보다 적을 수 있다.)

  • void reset() : 스트림에서의 위치를 마지막으로 mark()이 호출되었던 위치로 되돌린다.

  • long skip(long n) : 스트림에서 주어진 길이(n)만큼을 건너뛴다.

OutputStream의 메서드

  • void close() : 입력소스를 닫음으로써 사용하고 있던 자원을 반환

  • void flush() : 스트림의 버퍼에 있는 모든 내용을 출력소스에 쓴다.
    (버퍼가 있는 출력스트림의 경우에만 의미가 있다. OutputStream에 정의된 flush()는 아무 일도 하지 않는다.)

  • abstract void write (int b) : 주어진 값을 출력소스에 쓴다.

  • void write (byte[] b) : 주어진 배열 b에 저장된 모든 내용을 출력소스에 쓴다.

  • void write (byte[] b, int off, int len) : 주어진 배열 b에 저장된 내용 중에서 off번째부터 len개 만큼만을 읽어서 출력소스에 쓴다.

프로그램이 종료될 때 사용하고 닫지 않은 스트림을 JVM이 자동적으로 닫아 준다.
하지만, 스트림을 사용하고 난 후에는 반드시 close()를 호출해서 닫아줘야 한다.
단, ByteArrayInputStream과 같이 메모리를 사용하는 스트림과 System.in, System.out과 같은 표준입출력 스트림은 닫지 않아도 된다.

2.2 ByteArrayInputStream과 ByteArrayOutputStream

ByteArrayInputStream / ByteArrayOutputStream은 메모리, 즉 바이트배열에 데이터를 입출력 하는데 사용되는 스트림이다.
주로 다른 곳에 입출력하기 전에 데이터를 임시로 바이트배열에 담아서 변환 등의 작업을 하는데 사용된다.


# ByteArrayInput/OutputStream을 이용해서 바이트배열 inSrc의 데이터를 outSrc로 복사하는 예제

import java.io.*;
import java.util.Arrays;

public class Ex15_1 {
	public static void main(String[] args) {
		byte[] inSrc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
		byte[] outSrc = null;
		
		ByteArrayInputStream input = null;
		ByteArrayOutputStream output = null;
		
		input = new ByteArrayInputStream(inSrc);
		output = new ByteArrayOutputStream();
		
		int data = 0;
		
		while((data = input.read()) != -1) {	// read()해서 data에 넣고 그 값이 -1인지 확인.
			output.write(data);		// void write(int b)
		}
		
		outSrc = output.toByteArray();		// 스트림의 내용을 byte배열로 반환
		
		System.out.println("Input Source :" + Arrays.toString(inSrc));
		System.out.println("Output Source :" + Arrays.toString(outSrc));
	}
}
  • 출력 결과 :
Input Source :[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Output Source :[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

바이트배열은 사용하는 자원이 메모리밖에 없어서 가비지컬렉터에 의해 자동적으로 자원을 반환하므로 close()로 스트림을 닫지 않아도 된다.
read()와 write(int b)를 사용해서 한 번에 1byte만 읽고 쓰므로 작업 효율이 떨어진다.


# int read(byte[] b, int off, int len)와 void write(byte[], int off, int len)을 사용한 입출력 예제

import java.io.*;
import java.util.Arrays;

public class Ex15_2 {
	public static void main(String[] args) {
		byte[] inSrc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
		byte[] outSrc = null;
		byte[] temp = new byte[10];
		
		ByteArrayInputStream input = null;
		ByteArrayOutputStream output = null;
		
		input  = new ByteArrayInputStream(inSrc);
		output = new ByteArrayOutputStream();
		
		input.read(temp, 0, temp.length);	// 읽어 온 데이터를 배열 temp에 담는다.
		output.write(temp, 5, 5);			// temp[5]부터 5개의 데이터를 write한다.

		outSrc = output.toByteArray();
		
		System.out.println("Input Source  : " + Arrays.toString(inSrc));
		System.out.println("temp          : " + Arrays.toString(temp));
		System.out.println("Output Source : " + Arrays.toString(outSrc));
	}
}
  • 출력 결과 :
Input Source  : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
temp          : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Output Source : [5, 6, 7, 8, 9]

앞선 예제와 달리 byte배열을 사용해서 한 번에 배열의 크기만큼 읽고 썼다.
배열을 이용한 입출력은 작업의 효율을 증가시키므로 가능하면 대상에 따라 알맞은 크기의 배열을 사용하자.


# 앞선 예제에서 available()사용하고 복사하는 배열의 크기가 다를 경우 예제

import java.io.*;
import java.util.Arrays;

public class Ex15_3 {

	public static void main(String[] args) {
		byte[] inSrc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
		byte[] outSrc = null;
		byte[] temp = new byte[4];		// 이전 예제와 다르게 배열크기를 4로 지정
		
		ByteArrayInputStream input = null;
		ByteArrayOutputStream output = null;
		
		input  = new ByteArrayInputStream(inSrc);
		output = new ByteArrayOutputStream();
		
		System.out.println("Input Source  : " + Arrays.toString(inSrc));
		
		try {
			while(input.available() > 0) {
				input.read(temp);
				output.write(temp);
//				System.out.println("temp : "+ Arrays.toString(temp));
				outSrc = output.toByteArray();
				printArrays(temp, outSrc);
			}
		}catch(IOException e) {
			
		}
	}// main
	
	static void printArrays(byte[] temp, byte[] outSrc) {
		System.out.println("temp          : " + Arrays.toString(temp));
		System.out.println("Output Source : " + Arrays.toString(outSrc));
	}
}
  • 출력 결과 :
Input Source  : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
temp          : [0, 1, 2, 3]
Output Source : [0, 1, 2, 3]
temp          : [4, 5, 6, 7]
Output Source : [0, 1, 2, 3, 4, 5, 6, 7]
temp          : [8, 9, 6, 7]		// 뒤에 6, 7이 잘못포함됨
Output Source : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6, 7]

read()나 write()이 IOException을 발생시킬 수 있어서 try-catch문으로 감싸주었다.

available()은 블락킹(blocking) 없이 읽어 올 수 있는 바이트의 수를 반환한다.

마지막 복사에서 배열의 8과 9만 출력해야 하는데, temp에 남아있던 6, 7까지 출력된 모습이다.

temp의 내용을 지우고 다시 쓴 것이 아니라 덮어쓰기를 했기 때문이다.

  • 블락킹(blocking) : 데이터를 읽어 올 때 데이터를 기다리기 위해 멈춰있는 것을 뜻한다.

앞선 예제에서 temp를 수정하려면 다음과 같이 바꾸면 된다.

// 변경 전
while(input.available() > 0) {
	input.read(temp);
    output.write(temp);
}

// 변경 후
while(input.available() > 0) {
	int len = input.read(temp);		// 읽어 온 데이터의 개수 반환
    output.write(temp, 0, len);		// 읽어 온 만큼만 write
}
  • 변경 후 출력 :
Input Source  : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
temp          : [0, 1, 2, 3]
Output Source : [0, 1, 2, 3]
temp          : [4, 5, 6, 7]
Output Source : [0, 1, 2, 3, 4, 5, 6, 7]
temp          : [8, 9, 6, 7]
Output Source : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]	// 6, 7이 빠짐.

2.3 FileInputStream과 FileOutputStream

FileInputStream과 FileOutputStream은 파일에 입출력을 하기 위한 스트림이다.


FileInputStream과 FileOutputStream의 생성자

  • FileInputStream(String name) : 지정된 파일이름(name)을 가진 실제 파일과 연결된 FileInputStream을 생성한다.

  • FileInputStream(File file) : 파일의 이름이 String이 아닌 File인스턴스로 지정해줘야 하는 점을 제외하고 FileInputStream(String name)와 같다.

  • FileInputStream(FileDescriptor fdObj) : 파일 디스크립터(fdObj)로 FileInputStream을 생성한다.

  • FileOutputStream(String name) : 지정된 파일이름(name)을 가진 실제 파일과의 연결된 FileOutputStream을 생성

  • FileOutputStream(String name, boolean append) : 지정된 파일이름(name)을 가진 실제 파일과 연결된 FileOutputStream을 생성한다. 두번째 인자인 append를 true로 하면, 출력 시 기존의 파일내용의 마지막에 덧붙인다. false면 기존의 파일 내용을 덮어쓰게 된다.

  • FileOutputStream(File file) : 파일의 이름을 String이 아닌 File인스턴스로 지정해줘야 하는 점을 제외하고 FileOutputStream(String name)과 같다.

  • FileOutputStream(File file, boolean append) : 파일의 이름을 String이 아닌 File인스턴스로 지정해줘야 하는 점을 제외하고 FileOutputStream(String name, boolean append)과 같다.

  • FileOutputStream(FileDescriptor fdObj) : 파일 디스크립터(fdObj)로 FileOutputStream을 생성한다.


  • FileInputStream 을 사용한 예제

    'D:\Java\workspace\study\example\src\test.txt' 파일의 내용을 읽어온다.

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class Ex15_5 {

	public static void main(String[] args) throws IOException {
		File file = new File("d:\\java\\workspace\\study\\example\\src\\test.txt");
		FileInputStream fis = new FileInputStream(file);
		int data = 0;
		
		while((data=fis.read()) != -1) {
			char c = (char)data;
			System.out.print(c);
		}
	}
}
  • 출력 결과 :
abcdefg

  • FileOutputStream 을 사용한 예제

    'D:\Java\workspace\study\example\src\output.txt'으로 "HelloWorld!"를 출력한다.

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamRun {
	public static void main(String[] args) throws IOException {
		File file = new File("d:\\java\\workspace\\study\\example\\src\\output.txt");
		FileOutputStream fos = new FileOutputStream(file);
		
		String hi = "HelloWorld!";
		
		fos.write(hi.getBytes());
		
		fos.close();
	}
}
  • 출력 결과 :
profile
keep going on~

0개의 댓글