보조 스트림
은 스트림의 기능을 보완하기 위해 다른 스트림과 연결되어 편의 기능을 제공해주는 스트림을 의미합니다. 보조 스트림
은 실제로 입출력을 하는 스트림은 아니기 때문에 데이터를 입출력할 수는 없습니다. 또한 보조 스트림
은 여러 보조 스트림과 연결되어 체인을 구성할 수 있습니다.
다음 코드는 txt 파일을 읽기 위해 FileInputStream
을 사용하는 경우 입력 성능을 높이기 위해 버퍼를 이용하는 BufferedInputStream
을 사용하는 코드입니다.
InputStream is = new FileInputStream("text.txt"); //기반 스트림 FileInputStream
BufferedInputStream bis = new BufferedInputStream(is); //보조 스트림 BufferedInputStream
bis.read(); //보조 스트림으로부터 데이터 read
위 코드에서 실제 입력은 FileInputStream
이 수행하고, BufferedInputStream
은 입력 버퍼만을 제공합니다.
주로 사용되는 보조 스트림을 나열하면 다음과 같습니다.
보조 스트림 | 기능 |
---|---|
InputStreamReader | 바이트 스트림을 문자 스트림으로 변환 |
BufferedInputStream, BufferedOutputStream BufferedReader, BufferedWriter | 입출력 성능 향상 |
DataInputStream, DataOutputStream | 기본형 데이터 입출력 |
PrintStream, PrintWriter | 개행, 형식화된 문자열 출력 |
ObjectInputStream, ObjectOutputStream | 객체 입출력 |
바이트 스트림의 입출력 데이터가 문자라면 문자 스트림으로 변환하고 사용하는 것이 효율적입니다. 문자를 바로 입출력하거나 문자셋 등을 이용할 수 있다는 장점이 있습니다.
다음은 각각 파일 입출력에서 바이트 입력 스트림을 문자 입력 스트림으로, 바이트 출력 스트림을 문자 출력 스트림으로 변환하는 코드입니다.
InputStream is = new FileStream("파일 경로");
Reader reader = new InputStreamReader(is);
OutputStream os = new FileOutputStream("파일 경로");
Writer writer = new OutputStreamWriter(os);
저장 장치의 속도, 네트워크의 속도에 따라서 CPU, 메모리의 성능과는 상관없이 느린 쪽으로 입출력 처리 속도가 맞춰지게 됩니다. 따라서 이런 경우 직접 입출력을 하기보다 버퍼
를 이용해서 어느정도 실행 성능을 향상시킬 수 있습니다.
버퍼
는 데이터가 쌓이기를 기다리다가 데이터가 모두 차게되면 한 번에 목적지로 데이터를 전송하게 됩니다. 그렇기 때문에 출력에서는 출력 횟수를 줄여주고, 입력에서는 읽기 횟수를 줄여줘서 실행 성능 향상을 얻을 수 있게 됩니다.
버퍼는 바이트 입출력 스트림은 BufferedInputStream, BufferedOutputStream
을 사용하고 문자 입출력 스트림은 BufferedReader, BufferedWriter
를 사용합니다.
boolean, char, short, int, long, float, double
과 같은 기본형 데이터를 입출력하고 싶다면 DataOutputStream, DataInputStream
을 사용합니다.
두 스트림 클래스는 기본형 입출력을 위해 readXxxxx(), writeXxxxx()
메소드를 제공하고 있습니다. 이 메소드를 사용할 때 주의할 점이 있는데요. 기본형 데이터의 byte 크기가 모두 다르므로 출력한 데이터를 읽을 때 동일한 순서로 읽어야한다는 점입니다.
프린트 스트림인 PrintStream, PrintWriter
는 print(), println(), println()
메소드를 가지고 있는 보조 스트림 클래스입니다.
즉, 우리가 그동안 써왔던 println()
과 같은 프린트 메소드는 이 프린트 스트림에 속하던 메소드였었습니다.
System.out.print()
를 처음 소개드릴 때, System
클래스가 out
을 호출하는데, 이 out
이 바로 PrintStream
의 인스턴스라고 설명드렸었습니다. 그리고 PrintStream
의 print()
메소드를 통해 콘솔에 출력하는 것이 코드의 동작이었습니다. 무려 80번의 포스트 만에 떡밥을 회수했네요.
당시의 설명인데요. 이제는 이 코드를 이해하실 수 있으시겠죠?
ObjectInputStream, OnjectOutputStream
은 객체를 입출력할 수 있는 보조 스트림입니다.
자바에서는 생성된 객체를 파일이나 네트워크로 출력할 수 있습니다. 객체를 출력하기 위해서는 필드를 일렬로 늘어선 바이트로 변환해야하는데요. 이 과정을 직렬화(serialization)
이라고 하고, 반대로 직렬화된 바이트를 객체의 필드값으로 다시 변환하는 것을 역직렬화(deserialization)
이라고 합니다.
그래서 ObjectOutputStream
은 바이트 출력 스트림에 보조되어 객체를 직렬화하고, ObjectInputStreaem
은 바이트 입력 스트림에 보조되어 바이트를 역직렬화합니다.
다음 코드는 ObjectInputStream, OnjectOutputStream
를 바이트 입출력 스트림과 연결하고 직렬화하고 역직렬화를 수행하는 코드입니다.
ObjectInputStream ois = new ObjectInputStream(바이트 입력 스트림);
ObjectOutputStream oos = new ObjectOutputStream(바이트 출력 스트림);
oos.writeObject(객체); //직렬화
객체타입 변수명 = (객체타입) ois.readObject(); //역직렬화
자바는 Serializable 인터페이스
를 구현하고 있는 객체만 직렬화가 가능하도록 제한합니다. Serializable 인터페이스
내용이 없는 빈 인터페이스로 이 객체가 직렬화가 가능하다고 표시하는 정도의 용도로만 사용됩니다.
필드를 직렬화할 때 정적(static) 필드와 transient
로 선언된 키워드를 제외한 필드만 직렬화를 수행합니다.
transient
키워드는 필드에서 사용되며, 해당 키워드가 붙은 필드는 직렬화 대상에서 제외합니다.
직렬화에 사용된 클래스와 역직렬화에 사용되는 클래스는 내용이 동일해야합니다. 이름이 같아도 내용이 다르면 역직렬화가 불가능합니다. 만약 내용이 달라도 직렬화된 필드를 가지고 있다면 serialVersionUID
상수를 통해 역직렬화를 가능하게 만들 수 있습니다.
serialVersionUID
의 값이 같다면 내용이 조금 다르더라도 역직렬화가 가능해집니다.