여러 외부 자원과 동일한 방식으로 인터페이스 할 수 있도록 자바에서 제공하는 추상 클래스는?
외부 시스템과 데이터를 주고 받는 방법은 서로 다를 수 있기 때문에, 이에 따라 코드도 다 각각 짜야 한다면 코드가 복잡하고 변경도 많이 해야하는 문제가 생길 수 있음.
따라서, 자바에서는 InputStream, OutputStream이라는 기본 추상 클래스를 제공해 모든 외부 자원과 동일한 인터페이스로 통신을 할 수 있게 제공함.
기본 인터페이스 함수를 제공하며, byte 단위로 통신
InputStream에서 read()가 int를 반환하는 이유는?
byte는 부호가 있어 -128 ~ 127로 표현되어 음수 방식을 사용해야 하는 불편함이 있음.
EOF(End of File)을 표시해줘야 하는데, 이를 위한 한 자리를 byte에 담을 수 없음.
따라서, int를 사용한다면 -1을 추가로 표현 가능.
FileInputStream과 같은 클래스를 사용해서 1바이트씩 파일을 읽는 방식보다 성능을 개선할 수 있는 방법은 무엇이며, 주의해야 할 점은?
write()나 read()를 호출 할 때마다 OS의 시스템 콜을 통해 파일을 읽거나 쓰는 명령어를 전달하는데, 이러한 시스템 콜은 상대적으로 무거운 작업임.
따라서, 시스템 콜 호출 횟수를 줄이기 위해 읽고 쓰는 데이터를 버퍼에 일정량 모아서 한 번에 읽기/쓰기 작업을 하면 시스템 콜 횟수가 줄어 성능이 개선 됨.
주의 해야할 점은, 데이터를 버퍼에 넣고, 버퍼가 다 차면 쓰기 작업을 하기 때문에 마지막에 버퍼가 꽉 차지 않은 경우 우에 대해서도 마저 쓰기 작업을 하도록 추가 코드를 넣어 줘야 함.
버퍼의 사이즈는 몇으로 설정하는게 좋은가?
시스템 콜 횟수를 줄이기 위해서라면 버퍼의 사이즈는 크면 클 수록 좋다고 생각할 수 있음.
하지만 너무 많은 데이터를 한 번에 처리하려고 하다보면 메모리가 부족해 OutOfMemory 문제가 발생할 수 있음.
또한, 디스크나 파일 시스템에서 데이터를 읽고 쓰는 기본 단위가 보통 4KB 또는 8KB이기 때문에, 이 이상으로 버퍼 사이즈를 키워봤자 성능상 이점이 거의 발생하지 않음.
따라서, 버퍼의 사이즈는 이런 것을 고려해 4KB나 8KB 정도가 적당함.
버퍼를 사용한 스트림 읽기쓰기 클래스는?
FileOutputStream fos = new FileOutputStream(FILE_NAME);
BufferedOutputStream bos = new BufferedOutputStream(fos, BUFFER_SIZE);
long startTime = System.currentTimeMillis();
for (int i = 0; i < FILE_SIZE; i++) {
bos.write(1);
}
bos.close();
BufferedInputStream, BufferedOutputStream을 사용
write 함수가 불리면 BufferedOutputStream의 버퍼 안에 데이터를 채움
만약 버퍼가 가득 찬 경우, FileOutputStream으로 버퍼를 내보냄
flush() 함수를 사용하면 버퍼가 다 차지 않아도 내보낼 수 있으며, 스트림 close()시 내부 남아있는 데이터들은 자동으로 flush()가 호출 됨.
BufferedXXStream 클래스가 직접 버퍼를 넣어 구현한 스트림보다 성능이 느린 이유는
Bueffered 클래스는 멀티 스레드를 고려하여 만든 클래스라 모두 동기화 처리가 되어 있기 때문에 느려짐.
따라서, 성능이 더 중요하다면 직접 구현해서 버퍼를 다뤄주는게 더 좋음.
Byte가 아닌 String을 File에 입력하려면 어떻게 구현해야하는가?
String writeString = "ABC";
// 문자 -> byte UTF-8 인코딩
byte[] writeBytes = writeString.getBytes(UTF_8);
// 파일에 쓰기
FileOutputStream fos = new FileOutputStream(FILE_NAME);
fos.write(writeBytes);
fos.close();
Stream은 무조건 Byte로만 입출력을 관리함
따라서, String을 getBytes를 통해 Bytes로 변환해줘야 하며, 이때 어떤 문자 집합으로 인코딩할지 결정할 수 있음.
앞으로 어떤 구현체가 나오던지 결국엔 무조건 Byte로 입출력이 관리됨!
String을 자동으로 내부에서 자동으로 처리해서 Stream으로 보내는 방식에 대해 설명하시오
//OutputStreamWriter를 사용하는 방식
String writeString = "ABC";
// 파일에 쓰기
FileOutputStream fos = new FileOutputStream(FILE_NAME);
OutputStreamWriter osw = new OutputStreamWriter(fos, UTF_8);
osw.write(writeString);
osw.close();
//FileWriter를 사용하는 방식
String writeString = "ABC";
// 파일에 쓰기
FileWriter fw = new FileWriter(FILE_NAME, UTF_8);
fw.write(writeString);
fw.close();
//InputStreamReader를 사용하는 방식
FileInputStream fis = new FileInputStream(FILE_NAME);
InputStreamReader isr = new InputStreamReader(fis, UTF_8);
StringBuilder content = new StringBuilder();
int ch;
while ((ch = isr.read()) != -1) {
content.append((char) ch);
}
isr.close();
//FileReader를 사용하는 방식
StringBuilder content = new StringBuilder();
FileReader fr = new FileReader(FILE_NAME, UTF_8);
int ch;
while ((ch = fr.read()) != -1) {
content.append((char) ch);
}
fr.close();
writer, reader 클래스들은 기본적으로 끝에 Stream이 붙는 클래스와 다른게 parameter를 byte로 받지 않고 char나 String으로 받음.
그 이후, String이나 char를 bytes로 변환해서 사용하는 식으로 구현됨.
OutputStreamWriter를 사용하면 String을 write하면 내부에서 자동으로 이를 byte로 변환해서 stream으로 보냄.
FileWriter를 사용하면 OutputStreamWriter를 사용하는 것보다 더 간결하게 사용이 가능함.
FileWriter는 내부에 super(new FileOutputStream(fileName), charset) 와 같은 함수가 있어서 사실상 내부에 FileOutputStream을 만들어서 사용 해 결국 동일한 구조라 볼 수 있음.
Reader/Writer 클래스에서 Buffer를 사용하는 방식에 대해 설명하시오
String writeString = "ABC\n가나다";
// 파일에 쓰기
FileWriter fw = new FileWriter(FILE_NAME, UTF_8);
BufferedWriter bw = new BufferedWriter(fw, BUFFER_SIZE);
bw.write(writeString);
bw.close();
// 파일에서 읽기
StringBuilder content = new StringBuilder();
FileReader fr = new FileReader(FILE_NAME, UTF_8);
BufferedReader br = new BufferedReader(fr, BUFFER_SIZE);
String line;
while ((line = br.readLine()) != null) {
content.append(line).append("\n");
}
br.close();
Stream에서 사용하는 방식과 똑같이 Reader/Writer도 BufferedWriter와 같은 객체 생성해서 버퍼 사이즈를 지정하여 버퍼단위로 읽고 쓸 수 있음.
BufferedReader는 한 줄 단위로 읽는 기능인 readLine() 함수도 제공
단, 파일의 끝에 도달하면 String이 아니기 때문에 -1을 표현할 수 없어 null로 표현함.
PrintStream에 대해 설명하시오
FileOutputStream fos = new FileOutputStream("temp/print.txt");
PrintStream printStream = new PrintStream(fos);
printStream.println("hello java!");
printStream.println(10);
printStream.println(true);
printStream.printf("hello %s", "world");
printStream.close();
앞에서 언급한 클래스들과 다르게 PrintStream은 String, Byte 외에 Integer나 boolean과 같은 다른 타입도 쓸 수 있음.
이 println이 우리가 사용하던 System.out과 같으며, printStream의 인자로 System.out이 들어가면 콘솔에 출력함.
DataOutputStream에 대해 설명하시오
FileOutputStream fos = new FileOutputStream("temp/data.dat");
DataOutputStream dos = new DataOutputStream(fos);
dos.writeUTF("회원A");
dos.writeInt(20);
dos.writeDouble(10.5);
dos.writeBoolean(true);
dos.close();
FileInputStream fis = new FileInputStream("temp/data.dat");
DataInputStream dis = new DataInputStream(fis);
System.out.println(dis.readUTF());
System.out.println(dis.readInt());
System.out.println(dis.readDouble());
System.out.println(dis.readBoolean());
dis.close();
DataOutputStream을 사용하면 자바의 String, int, double, boolean 과 같은 데이터 형을 편리하게 다룰 수 있음.
타입별로 write/read를 할 수 있음.
다만, 꼭 저장한 순서대로 읽어야 함.
파일을 열어보면 우리가 알아보기 힘든 형태일 수 있는데, 그 이유는 UTF-8의 저장방식이 아니라 Int는 4바이트, boolean은 1바이트 이런식이기 때문임.