강의 : 김영한의 실전 자바 - 고급 2편, I/O, 네트워크, 리플렉션
3. I/O 기본 2
3-1. 문자 다루기1
스트림의 모든 데이터는 byte 단위를 사용한다.
byte가 아닌 문자를 스트림에 직접 전달할 수 없기 때문에, byte로 변환한 다음에 전달해야 한다.
String을 byte로 변환할 때는 String.getBytes(Charset)을 사용한다.
반대로 byte[]를 String으로 변환할 때는 new String(readBytes, Charset)을 사용한다.
public class ReaderWriterMainV1 {
public static void main(String[] args) throws IOException {
String writeString = "ABC";
// 문자 -> byte UTF-8 인코딩
byte[] writeBytes = writeString.getBytes(UTF_8);
System.out.println("write String: " + writeString);
System.out.println("write bytes: " + Arrays.toString(writeBytes));
// 파일에 쓰기
FileOutputStream fos = new FileOutputStream(FILE_NAME);
fos.write(writeBytes);
fos.close();
// 파일에서 읽기
FileInputStream fis = new FileInputStream(FILE_NAME);
byte[] readBytes = fis.readAllBytes();
fis.close();
// byte -> String UTF-8 디코딩
String readString = new String(readBytes, UTF_8);
System.out.println("read bytes: " + Arrays.toString(writeBytes));
System.out.println("read String: " + readString);
}
}
3-2. 문자 다루기2
byte를 String으로 String을 byte로 바꾸는 번거로운 과정을 OutputStreamWriter, InputStreamWriter는 대신해준다.
OutputStreamWriter는 문자를 입력 받고 받은 문자를 byte[]로 변환한다.
그리고 변환한 결과를 FileOutputStream의 write(byte[])에 전달해 파일에 데이터를 쓴다.
InputStreamReader는 int ch = read()를 제공하는데, char 형의 문자 하나를 받는다.
실제 타입은 int형 이므로 char형으로 캐스팅해서 사용하면 된다.
char형은 파일의 끝인 -1을 표현할 수 없어 int를 반환한다.
FileInputStream에서 byte[]를 읽고 InputStreamReader에서 읽은 byte[]를 char로 변경해서 반환한다.
public class ReaderWriterMainV2 {
public static void main(String[] args) throws IOException {
String writeString = "ABC";
System.out.println("write String: " + writeString);
// 파일에 쓰기
FileOutputStream fos = new FileOutputStream(FILE_NAME);
OutputStreamWriter osw = new OutputStreamWriter(fos, UTF_8);
osw.write(writeString);
osw.close();
// 파일에서 읽기
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();
System.out.println("read String: " + content);
}
}
3-3. 문자 다루기3 - Reader, Writer
자바는 byte를 다루는 I/O 클래스와 문자를 다루는 I/O 클래스를 나누었다.
byte를 다루는 클래스는 OutputStream, InputStream의 자식이다.
FileInput/OutputStream, ByteArrayInput/OutputStream, BUfferedInput/OutputStream 등이 있다.
문자를 다루는 클래스는 Reader, Writer의 자식이다.
FileReader, InputStreamReader, BufferedReader, FileWriter, OutputStreamWriter, BufferedWriter 등이 있다.
Reader, Writer를 사용하더라도 결국에 데이터는 byte로 쓰여진다.
3-3-1. FileReader, FileWriter
FileReader에서 데이터를 읽을 때 내부에서 FileInputStream을 사용해 데이터를 byte 단위로 읽어들인다.
그리고 UTF_8 같은 문자 집합을 사용해 byte[]를 char로 디코딩 한다.
FileWriter를 사용한 코드와 OutputStreamWriter를 사용한 코드는 유사하다.
OutputStreamWriter는 FileOutputStream을 직접 생성한 점이 다른데, FileWriter는 생성자 내부에서 FileOutputStream을 대신 생성해준다.
FileWriter는 FileOutputStream을 개발자 대신에 생성해 주는 것만 다르고 FileWriter는 OutputStreamWriter를 상속한다.
FileReader와 FileInputStream도 마찬가지이다.
참고로 문자 집합을 생략하면 시스템 기본 문자 집합이 사용된다.
public class ReaderWriterMainV3 {
public static void main(String[] args) throws IOException {
String writeString = "ABC";
System.out.println("write String: " + writeString);
// 파일에 쓰기
FileWriter fw = new FileWriter(FILE_NAME, UTF_8);
fw.write(writeString);
fw.close();
// 파일에서 읽기
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();
System.out.println("read String: " + content);
}
}
3-4. 문자 다루기4 - BufferedReader
BufferedOutputStream, BufferedInputStream과 같이 Reader, Writer에도 버퍼 보조 기능을 제공하는 BufferedReader, BufferedWriter 클래스가 있다.
문자를 다룰 때는 한 줄 단위로 다룰 때가 많은데, BufferedReader는 한 줄 단위로 문자를 읽는 기능도 추가로 제공한다.
br.readLine()
- 한 줄 단위로 문자를 읽고 String을 반환한다.
- 파일의 끝(EOF)에 도달하면 null을 반환한다.
public class ReaderWriterMainV4 {
private static final int BUFFER_SIZE = 8192;
public static void main(String[] args) throws IOException {
String writeString = "ABC\n가나다";
System.out.println("== Write String ==");
System.out.println(writeString);
// 파일에 쓰기
FileWriter fw = new FileWriter(FILE_NAME, StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(fw, BUFFER_SIZE);
bw.write(writeString);
bw.close();
// 파일에서 읽기
StringBuilder content = new StringBuilder();
FileReader fr = new FileReader(FILE_NAME, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(fr, BUFFER_SIZE);
String line;
while ((line = br.readLine()) != null) {
content.append(line).append("\n");
}
br.close();
System.out.println("== Read string ==");
System.out.println(content);
}
}
3-5. 기타 스트림
3-5-1. PrintStream
PrintStream은 System.out에서 사용되는 스트림이다.
PrintStream과 FileOutputStream을 조합하면 콘솔에 출력하듯이 파일에 출력할 수 있다.
public class PrintStreamEtcMain {
public static void main(String[] args) throws FileNotFoundException {
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();
}
}
3-5-2. DataOutputStream
DataOutputStream을 사용하면 자바의 String, int, double, boolean 같은 데이터 형을 편리하게 다룰 수 있다.
이 스트림을 사용할 때 주의할 점은 저장한 순서대로 읽어야 한다는 것이다.
저장한 data.dat를 열어보면 제대로 보이지 않는데, writeUTF()의 경우 UTF-8 형식으로 저장하지만
나머지 경우 문자가 아니라 각 타입에 맞는 byte 단위로 저장하기 때문이다.
예를 들어 int는 4byte를 묶어서 사용하고 해당 byte가 그대로 파일에 저장된다.
public class DataStreamEtcMain {
public static void main(String[] args) throws IOException {
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();
}
}