막연하게만 사용하던 I/O를 조금 더 이해해보고자 작성한 글입니다.
기본적으로 I/O는 O/S 레벨에서 이루어지고 byte를 다룬다는 것을 생각하면 좋을 것 같습니다.
글 작성에 참고한 영상입니다.
Java IO - Output Streams by Kody Simpson
데이터를 Byte로 변경하여 목적지로 보내는 역할을 한다.
크게 1개의 Byte를 사용하는 것과 Byte[ ]을 사용하는 경우로 나누어서 볼 수 있겠다.
class TestOuputStream {
public static void main(String[] args) {
byte b = 75;
OutputStream out = System.out;
out.write(b);
out.flush();
// 콘솔에 K 출력
}
}
위 코드는 아래와 같이 실행된다.
class TestOuputStream {
public static void main(String[] args) {
// 1.byte는 signed 8bit의 데이터를 갖는다.
// 따라서 b의 실제 데이터는 75를 2진수로 변환한 '01001011'이다.
byte b = 75;
OutputStream out = System.out;
// 2.write에 1byte인 '01001011' 전달한다.
out.write(b);
// 3.버퍼에 들어있는 내용을 강제로 목적지로 보낸다.
out.flush();
// 4.터미널에서 전달받은 출력스트림을 인코딩한다.
// 5.해당 결과를 기반으로 unicode에서 매치되는 글자를 찾는다.
// 6.해당 글자를 console에 보여준다.
}
}
UTF - 8
가변길이 인코딩 방식으로 문자를 1byte에서 4byte로 인코딩한다.
'01001011'은 0으로 시작하기때문에 1Byte 데이터임을 알 수 있다.
'01001011'를 Unicode 코드포인트로 변경하면 U+004B이다.
( 01001011 => 0100 1011 => 4B => U+004B )
U+004B는 K를 의미
class TestOuputStream {
public static void main(String[] args) {
int i = 75;
OutputStream out = System.out;
out.write(b);
out.flush();
// 콘솔에 K 출력
}
}
위 코드는 아래와 같이 실행된다.
class TestOuputStream {
public static void main(String[] args) {
// 1. int는 signed 32bit의 데이터를 갖는다.
int i = 75;
OutputStream out = System.out;
// 2. write(int a)의 경우는 32bit 중 하위 8bit만 사용한다.
// 공식 문서 참고
out.write(b);
out.flush();
// 3. 이후 진행은 byte와 동일
}
}
class TestOuputStream {
public static void main(String[] args) {
char c = 75;
char c1 = 'K';
OutputStream out = System.out;
out.write(c);
out.write(c1);
out.flush();
// 콘솔에 K 출력
}
}
위 코드는 아래와 같이 실행된다.
class TestOuputStream {
public static void main(String[] args) {
// 1. char은 unsigned 16bit 데이터 타입
// 기본적으로 unicode의 codepoint를 저장하기 위해 고안된 타입
// 컴파일 타임에 숫자인지 문자 리터럴인지 확인
// 숫자의 경우 해당 숫자를 16bit 데이터로 할당
// 00000000 01001011 저장
char c = 75;
// 문자리터럴의 경우, 해당 문자열을 unicode에서 찾고
// 해당 코드포인트를 16bit로 변경하여 할당
// U+004B => 0100 1011 => 00000000 01001011 저장
char c1 = 'K';
OutputStream out = System.out;
// write()는 int 혹은 byte[]을 입력으로 받는다.
// char 타입을 write에 넣으면 자동으로 int로 변경된다.
// 16bit char이 32bit int로 변경되고, 하위 8bit만 사용된다.
out.write(c);
out.write(c1);
out.flush();
}
}
Type Promotion
자바는 기본적으로 데이터의 손실이 발생하지 않는다면, 더 적은 크기의 데이터 타입은 더 큰 크기의 데이터타입으로 변경이 가능하다.
1. char => int (o)
char c = 'K' (00000000 01001011); int i = c (00000000 000000000 00000000 01001011);
2. int => float (o)
데이터의 손실이 없기때문에 변환된다.
int i = 3; (00000000 000000000 00000000 01001011) float f = i; (0 10000000 10000000000000000000000)
3. float => int (x)
int로 변경할 경우, 소수점이 사라지는데 이는 데이터 손실을 의미한다.
따라서 개발자가 데이터 손실을 인지한다는 것을 의미하는 casting을 해줘야 사용가능하다.float f = 3.3; int i = f; // x int i2 = (int) f; // o
class TestOutputStream {
public static void main(String[] args) {
byte[] b = new byte[4];
for (int i = 0; i < b.length; i++) {
int character = 65 + i;
b[i] = (byte) character;
}
OutputStream out = System.out;
out.write(b);
out.flush();
// 콘솔에 ABCD 출력
}
}
위 코드는 아래와 같이 실행된다.
class TestOutputStream {
public static void main(String[] args) {
// 1. 보통은 InputStream을 통해 전달받은 byte배열을 사용
// 테스트를 위한 byte배열 생성
byte[] b = new byte[4];
// 2. byte배열에 65 ~ 68 의 byte를 넣는다.
for (int i = 0; i < b.length; i++) {
int character = 65 + i;
b[i] = (byte) character;
}
OutputStream out = System.out;
// 3. byte[]을 받는 경우, 해당 배열을 순회하면서 하나씩 write를 수행
// 콘솔에 ABCD 출력
out.write(b);
out.flush();
}
}
class TestOutputStream {
public static void main(String[] args) {
byte[] b = new byte[4];
for (int i = 0; i < b.length; i++) {
int character = 65 + i;
b[i] = (byte) character;
}
OutputStream out = System.out;
out.write(b, 1, 2);
out.flush();
// 콘솔에 BC 출력
}
}
위 코드는 아래와 같이 실행된다.
class TestOutputStream {
public static void main(String[] args) {
byte[] b = new byte[4];
for (int i = 0; i < b.length; i++) {
int character = 65 + i;
b[i] = (byte) character;
}
OutputStream out = System.out;
// 위 코드와 모두 동일하고,
// 2번째 인자는 시작 index, 3번째 인자는 길이이다.
// byte 배열의 1번 인덱스부터 2개를 보여준다는 의미
out.write(b, 1, 2);
out.flush();
}
}