* 문자열을 byte stream 으로 출력하기
* file.encoding VM 프로퍼티
① 설정하는 방법
② 설정하지 않으면 OS의 기본 문자집합으로 설정
OS에 따라 file.encoding이 달라지지 않게 해주려면 UTF-8로 고정되게 해야한다.
// => UTF-8로 인코딩 하기
System.out.printf("file.encoding=%s\n", System.getProperty("file.encoding"));
byte[] bytes = str.getBytes("UTF-8"); // UCS2 ==> UTF-8
* UTF-8 byte -> String 객체
String str = new String(buf, 0, count);
// 바이트 배열이 어떤 문자집합으로 인코딩 된 것인지 알려주지 않으면,
// file.encoding에 설정된 문자집합으로 인코딩된 것으로 간주한다.
// Byte Stream - 텍스트 데이터 읽기 II
package com.eomcs.io.ex02;
import java.io.FileInputStream;
public class Exam0521 {
public static void main(String[] args) throws Exception {
// JVM 환경 변수 'file.encoding' 값
System.out.printf("file.encoding=%s\n", System.getProperty("file.encoding"));
FileInputStream in = new FileInputStream("sample/utf8.txt");
// 파일의 데이터를 한 번에 읽어보자.
byte[] buf = new byte[1000];
int count = in.read(buf); // <== 41 42 ea b0 80 ea b0 81 (AB가각)
in.close();
// 읽은 바이트 수를 출력해보자.
System.out.printf("읽은 바이트 수: %d\n", count);
// 읽은 바이트를 String 객체로 만들어보자.
// - 바이트 배열에 저장된 문자 코드를
// JVM이 사용하는 문자 집합(UCS2=UTF16BE)의 코드 값으로 변환한다.
// - 바이트 배열에 들어 있는 코드 값이 어떤 문자 집합의 값인지 알려주지 않는다면,
// JVM 환경 변수 file.encoding에 설정된 문자 집합으로 가정하고 변환을 수행한다.
String str = new String(buf, 0, count);
// 바이트 배열이 어떤 문자집합으로 인코딩 된 것인지 알려주지 않으면,
// file.encoding에 설정된 문자집합으로 인코딩된 것으로 간주한다.
System.out.println(str);
// 바이트가 어떤 문자 집합으로 인코딩 되었는지 알려주지 않는다면?
// String 객체를 생성할 때 다음의 규칙에 따라 변환한다.
// => JVM 환경 변수 'file.encoding' 에 설정된 문자집합으로 가정하고 UCS2 문자 코드로 변환한다.
}
}
// 1) 이클립스에서 실행 => 성공!
// - JVM 실행 옵션에 '-Dfile.encoding=UTF-8' 환경 변수가 자동으로 붙는다.
// - 그래서 String 클래스는 바이트 배열의 값을 UCS2로 바꿀 때 UTF-8 문자표를 사용하여 UCS2 로 변환한다.
// - 예1)
// utf8.txt => 41 42 ea b0 80 ea b0 81
// UCS2 => 0041 0042 ac00 ac01 <== 정상적으로 바뀐다.
// ) Linux/macOS 콘솔에서 실행 => 성공!
// 2) Windows 콘솔에서 실행 => 실패!
// - JVM을 실행할 때 file.encoding을 설정하지 않으면
// OS의 기본 문자집합으로 설정한다.
// - Windows의 기본 문자집합은 MS949 이다.
// - 따라서 file.encoding 값은 MS949가 된다.
// - 바이트 배열은 UTF-8로 인코딩 되었는데,
// MS949 문자표를 사용하여 UCS2로 변환하려 하니까
// 잘못된 문자로 변환되는 것이다.
// - 해결책?
// JVM을 실행할 때 file.encoding 옵션에 정확하게 해당 파일의 인코딩을 설정하라.
// 즉 utf8.txt 파일은 UTF-8로 인코딩 되었기 때문에
// '-Dfile.encoding=UTF-8' 옵션을 붙여서 실행해야 UCS2로 정상 변환된다.
* 27. Data I/O stream API - binary stream API 사용
* File I/O API를 이용하여 데이터를 바이너리 형식으로 입출력하기
1. ArrayList 패키지를 통해서 list에 저장해두고 그 값을 입출력하는 과정을 통해서 데이터를 관리할 수 있다.
2. save와 load 클래스를 만들고 Java의 FileInput, output Stream 패키지를 통해서
값을 파일로 저장하거나 파일에 저장된 값을 불러올 수 있다.
3. 여기서 한글로 출력하는 경우에는 반드시 바이트타입을 "UTF-8"로 설정해주어야한다.
또한 각 타입의 byte 값에 따라서 비트이동 연산자를 입력해주어야 하는데 이유는 다음과 같다.
Short
예) short 의 2byte 값을 저장하게되면 파일 입출력을 진행할 때 최상위에 값이 위치하지 않으면
"00"의 값이 불러와질 수 있기 때문에 비트 이동 연산자를 통해서 진행해주어야한다.
short의 2byte 중 값이 존재하는 byte가 최상위에 위치하려면
1byte 만큼을 이동시켜주어야하고 1 byte는 8 bit 이기 때문에,
Short length = in.read() << 8 ;
이렇게 int 를 비트이동 시켜주면 값이 존재하는 1 byte가 최상단에 위치하게된다.
Int
예) int 의 4byte 값을 저장하게되면 파일 입출력을 진행할 때 최상위에 값이 위치하지 않으면
마찬가지로 "00"의 값이 불러와질 수 있기 때문에 비트 이동 연산자를 해주어야한다.
int rength = in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read();
int는 4byte 이기 때문에 3byte 만큼을 비트이동 시켜주어야 값이 최상단에 위치할 수 있다.
때문에 3 byte = 24 bit 이고 24비트만큼을 이동시켜주어야하는데,
값이 존재할 수 있기때문에 and 조건이 아닌 or 조건을 통해서
24bit | 16bit | 8bit | 1bit 를 진행해주어야한다.
# saveMember
데이터를 저장할 size()를 생성하고 각 타입별 byte 배열의 값에 출력해준다.
private void saveMember() {
try {
FileOutputStream out = new FileOutputStream("member.data");
// 저장할 데이터의 개수를 먼저 출력한다.
int size = memberList.size();
out.write(size >> 8);
out.write(size);
for (Member member : memberList) {
int no = member.getNo();
out.write(no >> 24);
out.write(no >> 16);
out.write(no >> 8);
out.write(no);
byte[] bytes = member.getName().getBytes("UTF-8");
// 출력할 바이트의 개수를 2바이트로 표시한다.
out.write(bytes.length >> 8);
out.write(bytes.length);
// 문자열의 바이트를 출력한다.
out.write(bytes);
bytes = member.getEmail().getBytes("UTF-8");
out.write(bytes.length >> 8);
out.write(bytes.length);
out.write(bytes);
bytes = member.getPassword().getBytes("UTF-8");
out.write(bytes.length >> 8);
out.write(bytes.length);
out.write(bytes);
char gender = member.getGender();
out.write(gender >> 8);
out.write(gender);
}
out.close();
} catch (Exception e) {
System.out.println("회원 정보를 저장하는 중 오류 발생!");
}
}
# loadMember
저장된 data 파일의 바이트 코드를 읽고 UTF-8로 변환해주어야하는 경우
저장소 buf를 생성하고 0부터 length의 바이트 코드 값을 디코딩해서 각 타입별로 입력해준다.
private void loadMember() {
try {
FileInputStream in = new FileInputStream("member.data");
int size = in.read() << 8;
size |= in.read();
byte[] buf = new byte[1000];
for (int i = 0; i < size; i++) {
Member member = new Member();
member.setNo(in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read());
int length = in.read() << 8 | in.read();
in.read(buf, 0, length);
member.setName(new String(buf, 0, length, "UTF-8"));
length = in.read() << 8 | in.read();
in.read(buf, 0, length);
member.setEmail(new String(buf, 0, length, "UTF-8"));
length = in.read() << 8 | in.read();
in.read(buf, 0, length);
member.setPassword(new String(buf, 0, length, "UTF-8"));
member.setGender((char)(in.read() << 8 | in.read()));
memberList.add(member);
}
// 데이터를 로딩한 이후에 추가할 회원의 번호를 설정한다.
Member.userId = memberList.get(memberList.size() - 1).getNo() + 1;
in.close();
} catch (Exception e) {
System.out.println("회원 정보를 읽는 중 오류 발생!");
}
}
* 28. 상속을 이용하여 primitive type과 String 입출력 기능
항상 중요한 것 .. 공식문서에 명시된 클래스&메서드&변수의 이름을 활용할 것
지난 27번에서 입/출력의 비트이동 연산자가 중복되기 때문에 리팩토링 대상이다 .
DataOutput & input Stream 을 통해서 클래스로 분류하여 관리해주자.
# saveMember
private void saveMember() {
try {
DataOutputStream out = new DataOutputStream("member.data");
// 출력할 데이터의 개수를 먼저 출력한다.
out.writeShort(memberList.size());
for (Member member : memberList) {
out.writeInt(member.getNo());
out.writeUTF(member.getName());
out.writeUTF(member.getEmail());
out.writeUTF(member.getPassword());
out.writeChar(member.getGender());
}
System.out.println("member.data 파일 정보 저장 완료");
out.close();
} catch (Exception e) {
System.out.println("member.data 파일 정보를 저장하는 중 오류 발생!");
}
}
# loadMember
private void loadMember() {
try {
DataInputStream in = new DataInputStream("member.data");
System.out.println("member.data 파일 정보 읽기 성공 !");
int size = in.readShort(); // 8비트 이동 후 number에 저장
for (int i = 0; i < size; i++) {
Member member = new Member();
member.setNo(in.readInt());
member.setName(in.readUTF());
member.setEmail(in.readUTF());
member.setPassword(in.readUTF());
member.setGender(in.readChar());
memberList.add(member);
}
// 데이터를 로딩한 이후에 추가 할 회원의 번호를 설정한다.
Member.userId = memberList.get(memberList.size() - 1).getNo() + 1;
in.close();
} catch (Exception e) {
System.out.println("member.data 파일 정보를 읽는 중 오류 발생 !");
}
}
# InputStream
load 는 data 를 Input 하는 것이므로 각 타입에 맞게 변환해주어서
비트이동을 시켜주어야한다.
package bitcamp.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class DataInputStream extends FileInputStream {
public DataInputStream(String name) throws FileNotFoundException {
super(name);
}
public short readShort() throws IOException {
return (short) (this.read() << 8 | this.read());
}
public int readInt() throws IOException {
return this.read() << 24 | this.read() << 16 | this.read() << 8 | this.read();
}
public long readLong() throws IOException {
return (long) (this.read()) << 56 | (long) this.read() << 48 | (long) this.read() << 40
| (long) this.read() << 32 | (long) this.read() << 24 | (long) this.read() << 16
| (long) this.read() << 8 | this.read();
}
public char readChar() throws IOException {
return (char) (this.read() << 8 | this.read());
}
public String readUTF() throws IOException {
int length = this.read() << 8 | this.read();
byte[] buf = new byte[length];
this.read(buf);
return new String(buf, "UTF-8");
}
}
# outputStream
save 는 data 를 output 하는 것이므로 비트이동을 시켜주어야한다.
package bitcamp.io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataOutputStream extends FileOutputStream {
public DataOutputStream(String name) throws FileNotFoundException {
super(name);
}
public void writeShort(int v) throws IOException {
this.write(v >> 8);
this.write(v);
}
public void writeInt(int v) throws IOException {
this.write(v >> 24);
this.write(v >> 16);
this.write(v >> 8);
this.write(v);
}
public void writeLong(long v) throws IOException {
this.write((int) (v >> 56));
this.write((int) (v >> 48));
this.write((int) (v >> 40));
this.write((int) (v >> 32));
this.write((int) (v >> 24));
this.write((int) (v >> 16));
this.write((int) (v >> 8));
this.write((int) v);
}
public void writeChar(int v) throws IOException {
this.write(v >> 8);
this.write(v);
}
public void writeUTF(String str) throws IOException {
byte[] bytes = str.getBytes("UTF-8");
this.write(bytes.length >> 8);
this.write(bytes.length);
this.write(bytes);
}
}