📂 목차
📚 본문
Java IO 심화
해당 패키지는 기본적으로 실습을 많이 하여야 익숙해지는 듯하니, 다양한 입출력들을 연습해보길 바란다.
우선 다음 흐름을 가져가자.
InputStream -> Reader -> System -> Writer -> OutputStream
InputStream(Byte Stream)
이전 포스트에서 스트림 자체는 byte 씩 읽어오는 것이라고 했다.
입력 스트림은 Java 애플리케이션에서 소스로부터 데이터를 읽는 데 사용되고 데이터는 파일, 배열, 주변 장치 또는 소켓 등 무엇이든 될 수 있다.
Java 에서 java.io.InputStream 클래스 는 모든 Java IO 입력 스트림의 기본 클래스이며 하위 클래스는 다음과 같이 있다.
AudioInputStream
ByteArrayInputStream
FileInputStream
FilterInputStream
InputStream
ObjectInputStream
PipedInputStream
SequenceInputStream
StringBufferInputStream
다 쓰진 않는다. 자주 쓰일거 같은 것들을 선택하여 쓰면 된다.
FileInputStream
당연하게도 File 을 입력 시킬 수 있다. 파일은 경로를 통해 설정할 수 있고, 설정 시에 소스가 정해진다.
다음 생성자들을 통해 소스를 설정할 수 있다.
생성자
FileInputStream(File file)
FileInputStream(String name)
FileInputStream(FileDescriptor fdObj)
소스가 정해졌으면 stream 으로 데이터가 흐르도록 초기화가 끝난 것이고,
주요 메서드
read()
: 1 byte 읽기read(byte[] b)
: b에 읽은 값들 저장read(byte[] b, int off, int len)
: off 부터 len 길이 만큼의 byte 를 저장
등을 통해 읽어들일 수 있다. 해당 class 는 @Closable
하기에 try-with-resources 문을 통해 자동으로 close()
메서드를 실행시켜준다.
FilterInputStream
해당 클래스는 추상 클래스이고 protected 로 생성자가 보호되고 있다.
해당 클래스의 하위 클래스가 또 있는데, 다음 두 클래스가 자주 사용되는 듯하다.
BufferedInputStream
생성자로는 다음으로 초기화 가능하다.
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)
: size 는 버퍼사이즈이다.
기본적으로 상위클래스인 FilterInputStream
이 read()
세가지 메서드를 구현하기에 다 사용 가능하다.
DataInputStream
마찬가지로 DataInputStream(InputStream in)
을 통해 초기화를 하며,
특이하게도 여기에는 DataInput
이라는 인터페이스를 구현하고 있음을 볼 수 있다.
DataInput 인터페이스를 구현하면 이는 binary stream 에서 바이트 단위로 데이터를 읽어와서
그것을 자바의 기본 자료형으로 재구성 할 수 있게 해준다. 따라서 우리는 DataInputStream
으로
읽어오기만 하더라도 자동으로 자바의 primitives 로 변환시켜준다는 것이다.
예시를 보자.
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class DataInputExample {
public static void main(String[] args) {
try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {
int number = dis.readInt(); // 4바이트 정수 읽기
double price = dis.readDouble(); // 8바이트 실수 읽기
boolean flag = dis.readBoolean(); // 1바이트 불리언 읽기
String text = dis.readUTF(); // Modified UTF-8 문자열 읽기
System.out.println("number = " + number);
System.out.println("price = " + price);
System.out.println("flag = " + flag);
System.out.println("text = " + text);
} catch (IOException e) {
e.printStackTrace();
}
}
}
위는 메서드들을 통해 쉽게 캐스팅을 하지 않고도 값을 읽어올 수 있음을 볼 수 있다.
readBoolean()
readByte()
readChar()
readDouble()
readFloat()
readFully(byte[] b)
readFully(byte[] b, int off, int len)
readInt()
readLine()
readLong()
readShort()
readUnsignedByte()
readUnsignedShort()
readUTF()
skipBytes()
기능들은 함수명 그대로이기에 설명은 생략한다.
ObjectInputStream
자바 객체를 읽어오는 스트림이며, 바이트 단위 스트림을 기반으로 파일, 네트워크 등에서 직렬화(serialized) 된 객체를 복원(deserialize) 하는 객체이다.
primitive
로 읽어오고 싶다면 FilterInputStream
의 DataInputStream
을, Object
로 읽어오고 싶다면 ObjectInputStream
을 쓰면 되겠다.
readObject()
: 객체를 읽어들인다. 이때 반환은 Object 이므로 명시적 형변환이 필요하다.
여기서 모든 InputStream 은 안에 InputStream 을 넣어 초기화를 할 수 있는 생성자가 있다는 것을 저번에 posting 으로 썼었는데, 이를 통해 기능들을 늘릴 수 있다고 말했었다. 따라서 ObjectInputStream 은 기본 생성자가 protected 라서 생성자 안에 InputStream 을 무조건 넣어야 함을 볼 수 있는데, 이는 ObjectInputStream 혼자서 초기화가 되는게 아님을 볼 수 있다.
Reader(Char Stream)
Abstract class for reading character streams. The only methods that a subclass must implement are
read(char[], int, int)
andclose()
. Most subclasses, however, will override some of the methods defined here in order to provide higher efficiency, additional functionality, or both.
추상 클래스이며, 문자열 흐름을 읽어오기 위해 쓰는 메서드이다. 즉 Char Stream 이라고 보면 된다.
주요 메서드
read(char[] cbuf)
read(char[] cbuf, int off, int len)
read(CharBuffer target)
주요 하위 클래스
InputStreamReader
BufferedReader
FileReader
주요 예시들을 보자.
Character 를 맞는 decording method 설정해서 가져오기
public class InputStreamReaderExample {
public static void main(String[] args) {
try (
FileInputStream fis = new FileInputStream("example.txt"); // 바이트 스트림
InputStreamReader isr = new InputStreamReader(fis, "UTF-8"); // 문자 변환
BufferedReader br = new BufferedReader(isr) // 한 줄씩 읽기 편하게
) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
File을 읽기 위한 BufferedReader 의 readLine 기능을 활용
public class BufferedReaderExample {
public static void main(String[] args) {
try (
FileReader fr = new FileReader("example.txt"); // 문자 스트림
BufferedReader br = new BufferedReader(fr) // 버퍼링 + 편리한 readLine()
) {
String line;
while ((line = br.readLine()) != null) { // 한 줄씩 읽기
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
OutputStream
주요 함수는 다음과 같다.
- flush()
- write(byte[] b)
- write(byte[] b, int off, int len)
Flushable
Flushable 은 플러시될 수 있는 클래스의 대상이다. flush 메서드는 버퍼링된 출력을 기본 스트림에 쓰기 위해 호출된다.
Writer
Chaining Method
- append(char c): 단일 문자 추가
- append(CharSequence csq): 문자 시퀀스 전체 추가
- append(CharSequence csq, int start, int end): 문자 시퀀스 일부 추가
반환값이 자기 자신이라 체이닝 가능
- write(int c): 단일 문자 스트림에 쓰기
- write(char[] cbuf): 문자 배열 전체 스트림에 쓰기
- write(char[], int off, int len)
- write(String str): 문자열 전체 스트림에 쓰기
- write(String str, int off, int len)
PrintWriter
우리가 알고 있는 일반적인 print 기능이 들어가 있는 writer 이다. 필요하면 찾아서 쓰자.
BufferedWriter
단일 문자, 배열, 문자열 등을 효율적으로 읽고 쓸 수 있는 클래스이다.
자바에서는 Writer 클래스가 문자를 출력 스트림에 바로 보내는 역할을 하는데, write() 를 호출하면 곧바로 해당 문자들이 기본 스트림(File, OutputStream) 으로 전달되게 된다.
하지만 이렇게 바로 쓰면 특히 파일이나 네트워크 등에 비용이 큰 출력 작업에서는 굉장히 비효율적일 수 있다. 매번 writer() 가 호출될 때마다 문자를 byte 로 변환시키고 출력 스트림에 전송시켜야 하기에 이를 방지하고자 다음처럼 BufferedWriter 의 기능을 가지면서 일정량의 데이터가 모일 때 한 번에 출력하도록 하면 좋다.
import java.io.*;
public class BufferedWriterExample {
public static void main(String[] args) {
try (
PrintWriter out = new PrintWriter(
new BufferedWriter(
new FileWriter("foo.out")
)
)
) {
out.println("Hello, BufferedWriter!");
out.println("이제 출력이 훨씬 효율적입니다.");
} catch (IOException e) {
e.printStackTrace();
}
}
}