자바의 입출력(I/O)와 스트림

June Lee·2021년 2월 4일
3

Java

목록 보기
13/23

스트림이란 데이터가 이동하는 통로이다. 이제까지 콘솔을 통한 출력은 System.out.println()을, 입력은 Scanner 객체를 당연하게 써왔는데, 사실 이것은 내부적으로 살펴봤을 때 스트림을 이용한 것이다.

System.out.println()의 경우 인자값으로 PrintStream(데이터를 적절한 문자로 출력하는 문자 기반 스트림의 역할을 수행)을 받는 메서드이다. 그리고 Scanner 클래스는 생성 시 System.in을 받았는데, 이게 바로 InputStream이다.

스트림을 다루는 클래스는 정말 정말 많은데,
이름을 통해 이 클래스의 역할을 짐작해볼 수 있다.

1. Input, Output 

2. Reader & Writer - 문자 스트림
InputStream & OutputStream - 바이트 스트림 (입출력 단위가 1byte)

3. nodeStream(기반 스트림) - 리소스에 직접적으로 연결되는 스트림
filterStream(보조 스트림) - 다른 스트림을 거쳐서 리소스와 연결되는 스트림

입출력으로 다루기 좋은 대표적인 예시인 파일 입출력은 FileInputStreamFileOutputStream을 사용한다. 그렇다면 이 스트림들은 바이트 데이터를 다루며, 각각 input과 output을 담당한다는 것을 알 수 있다.

이때 입출력에는 byte, char 배열 등을 사용하여 배열의 크기만큼 읽어오고, 배열의 크기만큼 써주는 것 또한 가능하다.

int flag = 0; 
byte[] arr = new byte[500];

inputStream = new FileInputStream("읽어올 파일 경로");
outputStream = new FileOutputStream("쓸 파일 경로");

while((flag = inputStream.read(arr)) != -1) {
	outputStream.write(arr);
	Arrays.fill(arr, (byte)0);
}

이때 flag의 경우 array로 읽을 때는 읽은 크기를 리턴하고, 한 글자씩 읽을 때는 읽은 데이터의 아스키 코드값을 리턴한다. 더 이상 읽을 게 없으면 -1을 리턴한다.

<그 외의 바이트 기반 스트림>
ByteArrayInputStream, ByteArrayOutputStream - 메모리(byte 배열)
PipedInputStream, PipedOutputStream - 프로세스(프로세스 간의 통신)
AudioInputStream, AudioOutputStream - 오디오 장치

byte[] inSrc = {0, 1, 2, 3};
byte[] outSrc = null;
input = new ByteArrayInputStream(inSrc);
output = new ByteArrayOutputStream();

int data = 0;

while((data = input.read()) != -1){
	output.write(data);
}
System.out.println(Arrays.toString(output.toByteArray()));
// 이렇게 파일이 아닌 출력도 가능

기반 스트림과 보조 스트림

보조 스트림은 직접적으로 리소스와 연결되지는 않지만, 다른 스트림에 연결되어 성능 향상을 돕는다.
예를 들어,

BufferedReader br = null;
BufferedWriter bw = null;
// Scanner 클래스가 추가되기 전에는 이렇게 받아야 했음
br = new BufferedReader(new InputStreamReader(System.in));
bw = new BufferedWriter(new FileWriter("output.txt"));

위와 같은 코드가 있다면, System.in이라는 InputStream(바이트를 다룸)이 InputStreamReader(문자를 다룸)에 연결되고, 이것이 마지막으로 BufferedReader(문자를 다루며 버퍼를 사용하는 스트림)과 연결된 것이다.
이렇게 하면, 바이트로 받은 리소스를 문자로 바꿔서 한 줄 씩 읽어올 수 있다.

String str = br.readLine();

여기에 String을 읽어서 리턴하는 메소드인 readLine을 붙이면 콘솔에 입력한 내용을 String에 저장할 수 있고,

bw.write(str);

을 해주면 해당 문자열을 받아서 "output.txt"라는 파일에 바이트 데이터로 바꿔서 쓸 수 있다.

br = new BufferedReader(new InputStreamReader(System.in));
bw = new BufferedWriter(new FileWriter("output.txt"));
			
while((str = br.readLine()) != null) {
	str += "\n";
	bw.write(str);
}

BufferedReader(혹은 InputStream)을 사용하면, read() 메서드를 호출했을 때 입력 소스로부터 데이터를 읽어서 버퍼에 저장한다. 입력 소스에서 데이터를 버퍼 크기 만큼 가져와 버퍼에 저장해두고, 버퍼로부터 읽으면, 외부의 입력소스를 바로 읽는 것보다 작업이 훨씬 빨라진다.

한편, BufferedWriter(혹은 OutputStream)을 사용하면, write() 메서드를 호출했을 때 출력이 버퍼에 저장된다. 그리고 이 경우에는 버퍼가 가득 찼을 때에만 모든 내용을 출력 소스에 출력하고, 마지막 출력 부분이 출력 소스에 쓰이지 못하고 버퍼에 남아있는 채로 프로그램이 종료될 수 있기 때문에, 작업을 마친 후 close() 혹은 flush()를 호출해서 모든 내용이 출력되도록 해줘야한다.

여러 스트림을 연결해서 사용하는 경우, 마지막에 연결한 Buffered 스트림을 닫아줘야 앞부분에 연결된 스트림의 close() 메서드도 자동으로 호출되어 함께 닫힌다.

표준 입출력(Standard I/O)

표준 입출력은 콘솔을 통한 데이터의 입력과 콘솔로의 출력을 의미한다. 자바에서는 이를 위한 3가지 입출력 스트림을 자바 어플리케이션의 실행과 동시에 자동으로 생성해주기 때문에, 별도로 스트림을 생성하는 코드 없이도 아래의 세 가지 스트림을 사용 가능하다.

- System.in
- System.out
- System.err : System.out 과 같은 역할 + 입출력을 콘솔 이외 다른 대상으로 변경하는 것이 가능 

객체 입출력 (ObjectInput/OutputStream)

객체를 바이트 단위로 읽고 쓰기 위한 보조 스트림이다. FileInput/OutputStream에 연결해서 사용한다.

직렬화(Serialize)와 역직렬화(Deserialize)

외부 시스템에 저장된 객체를 보조 기억 장치로부터 자바 시스템 내부로 불러올 때, 혹은 그 반대의 경우 직렬화/역직렬화 과정을 거쳐야한다.

바이트 형태로 데이터 변환: 직렬화
바이트로 변환된 데이터를 다시 객체로 변환: 역직렬화

Serializable 인터페이스를 상속받은 클래스는 직렬화될 수 있는 자격을 가진다.

// 직렬화
m = new Member("hong", 20);
ObjectOutputStream oos = null;
oos = new ObjectOutputStream(new FileOutputStream("object.ser")); // 두번째 인자로 true를 주면 덮어쓰기
oos.writeObject(m);

// 반직렬화
ObjectInputStream ols = null;
ols = new ObjectInputStream(new FileInputStream("object.ser"));
m = (Member) ols.readObject();

일반적으로 웹에서는 문자열을 CSV, JSON 형식으로 직렬화하는 경우가 많다.

// csv
홍길동,25,학생
// JSON
  { 
    name: "홍길동",
    age: 25,
    job: "학생"
  }

이에 반해 자바 직렬화는 자바에서 실제로 쓰이는 객체 형태로 변환하여 직렬화와 반직렬화를 거친 후에도 데이터 이용이 용이하기 때문에 자바 시스템 간의 데이터 교환에서 많이 쓰인다. 예를 들어, 자바 시스템에서 퍼포먼스를 위해 캐시 라이브러리를 이용할 경우가 많은데, 이때 자바 직렬화를 이용해 캐시에 객체를 저장하기도 한다.

profile
📝 dev wiki

0개의 댓글