Posts 학원 #44일차: 파일 입출력: byte stream
Post
Cancel

학원 #44일차: 파일 입출력: byte stream

데이터 출력

eomcs.java.io.ex04

int 입출력

1바이트만 입출력

1
2
3
FileOutputStream out = new FileOutputStream("temp/test3.data");
int money = 1_3456_7890; // = 0x080557d2
out.write(money); //항상 출력할 때는 맨 끝 1바이트만 출력한다.
  • 위의 코드를 실행하여 출력한 데이터를 InputStream.read()로 읽는다.

  • read()는 1바이트를 읽어 int 값으로 만든 후 리턴한다.

1
2
3
4
FileInputStream in = new FileInputStream("temp/test3.data");
int value = in.read(); // 실제 리턴한 값은 0xD2이다.
in.close();
System.out.printf("%1$x(%1$d)\n", value); // d2(210)

모든 바이트 입출력

1
2
3
4
5
6
7
FileOutputStream out = new FileOutputStream("temp/test3.data");
int money = 1_3456_7890; // = 0x080557d2

out.write(money >> 24); // 00000008|0557d2
out.write(money >> 16); // 00000805|57d2
out.write(money >> 8);  // 00080557|d2
out.write(money);       // 080557d2
  • 위의 코드를 실행하여 출력한 데이터를 read()로 읽는다.
  • 파일에서 4바이트를 읽어 4바이트 int 변수에 저장한다.
  • 읽은 바이트를 비트 이동 연산자로 값을 이동시킨 후 변수에 저장해야 한다.
1
2
3
4
5
6
int value = in.read() << 24;   // 00000008 => 08000000
value += (in.read() << 16);    // 00000005 => 00050000 +
value += (in.read() << 8);     // 00000057 => 00005700 +
value += in.read();            // 000000d2 => 000000d2 +
											       //             080557d2
System.out.printf("%x\n", value); //80557d2

long 입출력

모든 바이트 입출력

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FileOutputStream out = new FileOutputStream("temp/test3.data");
long money = 400_0000_0000_0000L; // = 0x00016bcc41e90000

// long 메모리의 모든 바이트를 출력하려면,
// 각 바이트를 맨 끝으로 이동한 후 write()로 출력한다.
// 왜? write()는 항상 변수의 마지막 1바이트만 출력하기 때문이다.
out.write((int)(money >> 56)); // 0000000000000000|016bcc41e90000
out.write((int)(money >> 48)); // 0000000000000001|6bcc41e90000
out.write((int)(money >> 40)); // 000000000000016b|cc41e90000
out.write((int)(money >> 32)); // 0000000000016bcc|41e90000
out.write((int)(money >> 24)); // 00000000016bcc41|e90000
out.write((int)(money >> 16)); // 000000016bcc41e9|0000
out.write((int)(money >> 8));  // 0000016bcc41e900|00
out.write((int)money);         // 00016bcc41e90000|
  • 위 코드를 실행하여 출력한 데이터를 read()로 읽는다.
  • 파일에서 8바이트를 읽어서 8바이트 long 변수에 저장한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
FileInputStream in = new FileInputStream("temp/test3.data");
// => 파일에 들어 있는 값 예: 00016bcc41e90000
long value = (long)in.read() << 56;   
value += (long)in.read() << 48;
value += (long)in.read() << 40;
value += (long)in.read() << 32;
value += (long)in.read() << 24;
value += (long)in.read() << 16;
value += (long)in.read() << 8;     
value += (long)in.read();            

in.close();
System.out.printf("%x\n", value);

String 입출력

str.getBytes(문자코드표)

  • 문자열을 지정한 문자 코드표에 따라 인코딩하여 바이트 배열을 만든다.
  • str.getBytes("UTF-8"): UCS2 문자 => UTF-8 문자
1
2
3
FileOutputStream out = new FileOutputStream("temp/test3.data");
String str = "AB가각간";
out.write(str.getBytes("UTF-8"));
  • 위의 코드의 실행 결과로 만든 파일을 읽는다.
  • 바이트 배열에 들어 있는 값을 사용하여 String 인스턴스를 만든다.
  • new String(바이트배열, 시작번호, 개수, 문자코드표)
1
2
3
4
5
FileInputStream in = new FileInputStream("temp/test3.data");
byte[] buf = new byte[100];
int count = in.read(buf);
String str = new String(buf, 0, count, "UTF-8");
System.out.printf("%s\n", str); //AB가각간

인스턴스 입출력

FileIOStream 사용

인스턴스의 값을 출력하는 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) throws Exception {
  FileOutputStream out = new FileOutputStream("temp/test4.data");
  
  Member member = new Member();
  member.name = "AB가각간";
  member.age = 27;
  member.gender = true;
  
  // 인스턴스의 값을 출력하라!
  // 이름 출력
  byte[] bytes = member.name.getBytes("UTF-8");
  out.write(bytes.length);
  out.write(bytes);
  
  // 나이 출력
  out.write(member.age >> 24);
  out.write(member.age >> 16);
  out.write(member.age >> 8);
  out.write(member.age);
  
  // 성별 출력
  out.write(member.gender ? 1 : 0);
}

파일의 데이터를 읽어 인스턴스로 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) throws Exception {
  FileInputStream in = new FileInputStream("temp/test4.data");
  
  Member member = new Member();
  
  // 이름(String) 읽기
  int size = in.read(); // 이름이 저장될 바이트 배열의 수
  byte[] buf = new byte[size];
  in.read(buf); // 이름 배열 개수 만큼 바이트를 읽어 배열에 저장
  member.name = new String(buf, "UTF-8");
  
  // 나이(int) 읽기
  member.age = in.read() << 24;
  member.age += in.read() << 16;
  member.age += in.read() << 8;
  member.age += in.read();
  
  // 성별(boolean) 읽기
  member.gender = (in.read() == 1);
}

FileInput/OutputStream 객체를 생성해서 메서드를 사용해도 읽거나 출력할 데이터의 타입에 따라 다른 방식으로 처리를 해야 한다. FileInput/OutputStream 클래스를 상속받고 처리할 데이터의 타입에 따라 메서드를 추가한 클래스(DataFileInputStream)를 만들어보자.

상속 관계로 기능 확장

DataFileIOStream 구현 (상속)

DataFileOutputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class DataFileOutputStream extends FileOutputStream {
  public DataOutputStream(String filename) throws Exception {
    super(filename);
  }
  
  public void writeUTF(String str) throws Exception {
    // 상속 받은 write() 메서드를 사용하여 문자열 출력
    byte[] bytes = str.getBytes("UTF-8");
    this.write(bytes.length);
    this.write(bytes);
  }
  
  public void writeInt(int value) throws Exception {
    // 상속 받은 write() 메서드를 사용하여 int 값 출력
    this.write(value >> 24);
    this.write(value >> 16);
    this.write(value >> 8);
    this.write(value);
  }
  
  public void writeLong(int value) throws Exception {
    // 상속 받은 write() 메서드를 사용하여 long 값 출력
    this.write((int)(value >> 56));
    this.write((int)(value >> 48));
    this.write((int)(value >> 40));
    this.write((int)(value >> 32));
    this.write((int)(value >> 24));
    this.write((int)(value >> 16));
    this.write((int)(value >> 8));
    this.write((int)value);
  }
  
  public void writeBoolean(boolean b) throws Exception {
    // 상속 받은 write() 메서드를 사용하여 boolean 값 출력
    this.write(b ? 1 : 0);
  }
}

DataFileOutputStream을 이용하여 객체 출력

1
2
3
4
5
6
7
8
9
10
11
DataFileOutputStream out = new DataFileOutputStream("temp/test.data");

Member member = new Member();
member.name = "AB가각간";
member.age = 27;
member.gender = true;

out.writeUTF(member.name);
out.writeInt(member.age);
out.writeBoolean(member.gender);
out.close();

DataFileInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class DataFileInputStream extends FileInputStream {
  public DataFileInputStream(String filename) throws Exception {
    super(filename);
  }
  
  public String readUTF() throws Exception {
    int size = this.read();
    byte[] bytes = new byte[size];
    this.read(bytes); //이름 배열 개수만큼 바이트를 읽어 배열에 저장
    return new String(bytes, "UTF-8");
  }
  
  public int readInt() throws Exception {
    int value = 0;
    
    value = this.read() << 24;
    value += this.read() << 16;
    value += this.read() << 8;
    value += this.read();
    
    return value;
  }
  
  public long readLong() throws Exception {
    long value = 0;
    value += (long) this.read() << 56;
    value += (long) this.read() << 48;
    value += (long) this.read() << 40;
    value += (long) this.read() << 32;
    value += (long) this.read() << 24;
    value += (long) this.read() << 16;
    value += (long) this.read() << 8;
    value += this.read();
    return value;
  }
  
  public boolean readBoolean() throws Exception {
    return this.read() == 1;
  }
}

DataFileInputStream으로 객체 읽기

1
2
3
4
5
6
7
8
9
DataFileInputStream in = new DataFileInputStream("temp/test");
Member member = null;
member = new Member();

member.name = in.readUTF();
member.age = in.readInt();
member.gender = in.readBoolean();
in.close();
System.out.printf("%s\n", member);

BufferedFileIOStream 구현 (상속)

버퍼를 사용하는 이유

버퍼를 사용하지 않고 파일을 복사하는 시간을 측정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 파일 복사 및 시간 측정
FileInputStream in = new FileInputStream("temp/test.pdf");
FileOutputStream out = new FileOutputStream("temp/test_2.pdf")

int b;

long startTime = System.currentTimeMillis();

int callCount = 0;

while ((b = in.read()) != -1) {
  out.write(b);
}

long endTime = System.currentTimeMillis();

System.out.println(endTime - startTime);
System.out.pintln(callCount);

in.close();

버퍼를 사용하여 파일을 복사하고 시간을 측정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 파일 복사 및 시간 측정
FileInputStream in = new FileInputStream("temp/test.pdf");
FileOutputStream out = new FileOutputStream("temp/test_2.pdf")

byte[] buf = new byte[8192];
int len = 0;

long startTime = System.currentTimeMillis();

while ((len = in.read(buf)) != -1) {
  out.write(buf);
}

long endTime = System.currentTimeMillis();

System.out.println(endTime - startTime);
System.out.pintln(callCount);

in.close();

버퍼를 사용했을 때가 사용하지 않았을 때보다 훨씬 빨랐다.

  • 데이터 읽기 시간 = average seek time + data transfer time

  • 퀀텀 HDD의 경우

    • average seek time = 0.0105초
    • data transfer time = 0.00000005 초 / 1 byte
  • 1000 바이트를 읽을 때

    • 1바이트를 1000번 읽는 경우
      • 0.01050005초 * 1000 byte = 10.50005초
    • 1000바이트 1번 읽는 경우
      • 0.0105 * 0.00000005초 * 1000 byte = 0.01055초
  • 1바이트를 여러 번 읽을 경우 매번 바이트의 위치를 찾아야 하기 때문에 평균 탐색시간이 누적되어 한 번에 1000 바이트를 읽는 것보다 시간이 오래 걸린다.

  • 그러면 RAM에 배열의 크기를 크게 잡으면 좋지 않을까?

    PC처럼 소수의 프로그램이 동시에 실행될 때는 상관 없지만, 서버처럼 데이터를 읽는 코드가 동시에 수천 개에서 수십만 개일 때는 아무리 작은 크기의 메모리라도 매우 많아지게 된다. 그래서 보통 8k 정도의 버퍼 크기를 유지하고 있다.

  • 물론 프로그램의 용도나 목적에 따라 버퍼의 크기가 이보다 더 작아지거나 커질 수 있다.

버퍼를 사용하여 데이터를 읽고 쓰는 일을 하는 클래스를 따로 정의하자.

BufferedFileOutputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class BufferedFileOutputStream extends FileOutputStream {
  byte[] buf = new byte[8196];
  int cursor;
  
  public BufferedFileOutputStream(String filename) throws Exception {
    super(filename);
  }
  
  @Override
  public void write(int b) throws IOException {
    if (cursor == buf.length) { // 버퍼가 다 차면
      super.write(buf); // 버퍼에 들어 있는 데이터를 한 번에 출력한다.
      cursor = 0; // 다시 커서를 초기화시킨다.
    }
    
    // 1바이트 출력하라고 하면 일단 버퍼에 저장할 것이다.
    buf[cursor++] = (byte) b;
  }
  
  @Override
  public void write(byte[] buf) throws IOException {
    for (byte b : buf) {
      this.write(b & 0x000000ff);
    }
  }
  
  @Override
  public void close() throws IOException {
    this.flush();
    super.close();
  }
  
  @Override
  public void flush() throws IOException {
    if (cursor > 0) {
      this.write(buf, 0, cursor);
      cursor = 0;
    }
  }
}

BufferedFileInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class BufferedFileInputStream extends FileInputStream {
  byte[] buf = new byte[8192];
  int size; // 배열에 저장되어 있는 바이트 수
  int cursor; // 바이트 읽은 배열의 위치
  
  public BufferedInputStream(String filename) throws Exception {
    super(filename);
  }
  
  @Override
  public int read() throws IOException {
    if (cursor == size) { // 버퍼에 저장되어 잇는 데이터를 모두 읽었다는 의미
      if ((size = super.read(buf)) == -1) { // 파일에서 데이터를 읽으려고 했는데 데이터가 없다.
        return -1;
      }
      cursor = 0;
    }
    return buf[cursor++] & 0x000000ff;
  }
  
  @Override
  public int read(byte[] buf) throws IOException {
    int i = 0;
    for (; i < buf.length; i++) {
      // 1바이트를 읽어서 파라미터로 받은 바이트 배열에 채운다.
      int b = this.read();
      if (b == -1)
        // 바이트 배열을 다 채우기도 전에 읽을 데이터가 없다면 읽기를 멈춘다.
        break;
      buf[i] = (byte) b;
    }
    return i; // 지금까지 읽은 데이터의 수를 리턴한다.
  }
}

BufferedFileIOStream을 사용하여 파일 복사 및 측정을 해보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BufferedFileInputStream in = new BufferedFileInputStream("temp/jls11.pdf");
BufferedFileOutputStream out = new BufferedFileOutputStream("temp/jls11_4.pdf");

int b;

long startTime = System.currentTimeMillis();

while((b = in.read()) != -1) {
  out.write(b);
}

long endTime = System.currentTimeMillis();

System.out.println(endTiem - startTime);

in.close();
out.close();

메모리에서 데이터 읽기

보통 바이트 배열에서 데이터를 읽을 때 인덱스를 가지고 직접 바이트 배열의 값을 읽는다. 그런데 바이트 배열도 그냥 데이터 저장소로 간주하고 read()를 사용해서 읽을 수 있다면, 개발자는 파일이 되었든 메모리가 되었든 read() 메서드 호출이라는 일관된 방법으로 데이터를 읽을 수 있기 때문에 유지보수가 편하게 된다. 즉 데이터가 저장된 장소에 상관없이 데이터를 읽을 때는 read()를 호출한다는 일관성 있는 규칙이 생기게 되는 것이다. 규칙이 있다는 것은 코딩을 매우 편하게 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
byte[] buf = {0x0b, 0x41, 0x42, (byte) 0xea, (byte) 0xb0, (byte) 0x80, (byte) 0xea, (byte) 0xb0,
              (byte) 0x81, (byte) 0xea, (byte) 0xb0, (byte) 0x84, 0x00, 0x00, 0x00, 0x1b, 0x01};

// 바이트 배열에서 데이터를 읽는 도구
ByteArrayInputStream = new ByteArrayInputStream(buf);

int b;

while (true) {
  b = in.read();
  if (b == -1)
    break;
  System.out.printf("%x ", b);
}
System.out.println();
in.close();

읽은 데이터를 객체에 저장해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Exam0210 {

  public static void main(String[] args) throws Exception {
    byte[] buf = {
        0x0b, 0x41, 0x42, (byte) 0xea, (byte) 0xb0, (byte) 0x80, (byte) 0xea, (byte) 0xb0,
        (byte) 0x81, (byte) 0xea, (byte) 0xb0, (byte) 0x84, 0x00, 0x00, 0x00, 0x1b, 0x01};

    ByteArrayInputStream in = new ByteArrayInputStream(buf);

    Member member = new Member();
    // byte[] => 0b 41 42 ea b0 80 ea b0 81 ea b0 84 00 00 00 1b 01
    //
    // 1) 문자열 읽기
    // - 문자열의 바이트의 크기를 먼저 읽는다.
    int size = in.read();

    // - 읽은 문자열을 저장할 바이트 배열을 준비한다.
    byte[] bytes = new byte[size];

    // - 데이터를 읽어 바이트 배열에 저장한다.
    in.read(bytes);

    // - 바이트 배열에 저장된 문자 코드를 String 객체로 만든다.
    member.name = new String(bytes, "UTF-8");

    // 2) int 값 읽는다.
    member.age = in.read() << 24;
    member.age += in.read() << 16;
    member.age += in.read() << 8;
    member.age += in.read();

    // 3) boolean 값을 읽는다.
    if (in.read() == 1)
      member.gender = true;
    else
      member.gender = false;

    in.close();

    System.out.println(member);

  }

}

바이트 배열의 값을 읽어서 String이나 int, boolean 값으로 만들려면 위와 같이 비트이동 연산 등을 수행해야 한다. 항상 이런 식으로 코딩을 해야 한다면 읽어들일 항목이 많을 경우 코드가 매우 복잡해질 것이다. ByteArrayInputStream을 상속 받아 DataInputStream을 만들어 이런 기능을 수행하는 메서드를 추가하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class DataByteArrayInputStream extends ByteArrayInputStream {

  public DataByteArrayInputStream(byte[] buf) {
    super(buf);
  }

  public String readUTF() throws Exception {
    // 상속 받은 read() 메서드를 사용하여 문자열 출력
    int size = this.read();
    byte[] bytes = new byte[size];
    this.read(bytes); // 배열 개수 만큼 바이트를 읽어 배열에 저장한다.
    return new String(bytes, "UTF-8");
  }

  public int readInt() throws Exception {
    // 상속 받은 read() 메서드를 사용하여 int 값 출력
    int value = 0;

    value = this.read() << 24;
    value += this.read() << 16;
    value += this.read() << 8;
    value += this.read();
    return value;
  }

  public long readLong() throws Exception {
    // 상속 받은 read() 메서드를 사용하여 long 값 출력
    long value = 0;
    value += (long) this.read() << 56;
    value += (long) this.read() << 48;
    value += (long) this.read() << 40;
    value += (long) this.read() << 32;
    value += (long) this.read() << 24;
    value += (long) this.read() << 16;
    value += (long) this.read() << 8;
    value += this.read();
    return value;
  }

  public boolean readBoolean() throws Exception {
    // 상속 받은 read() 메서드를 사용하여 boolean 값 출력
    if (this.read() == 1)
      return true;
    else
      return false;
  }
}

DataByteArrayInputStream을 사용하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
byte[] buf = {0x0b, 0x41, 0x42, (byte) 0xea, (byte) 0xb0, (byte) 0x80, (byte) 0xea, (byte) 0xb0,
              (byte) 0x81, (byte) 0xea, (byte) 0xb0, (byte) 0x84, 0x00, 0x00, 0x00, 0x1b, 0x01};

DataByteArrayInputStream in = new DataByteArrayInputStream(buf);

Member member = new Member();

member.name = in.readUTF();
member.age = in.readInt();
member.gender = in.readBoolean();

in.close();

System.out.println(member);

그런데 DataByteArrayInputStream은 위에서 만든 DataFileInputStream 클래스와 생성자 빼고 나머지 코드가 모두 같다. 그러나 안타깝게도 DataFileInputStream의 코드를 복사하지 않고 재사용하는 방법은 없다. 이것이 상속으로 기능을 확장했을 때의 한계이다.

상속 관계가 아니라 포함 관계로 변경하자. 바이트 값을 읽어 String, int, boolean 값으로 바꾸는 코드를 장신구(decorator)처럼 붙였다 뗐다 할 수 있게 만드는 것이다.

포함 관계로 기능 확장

상속 관계를 포함 관계로 변경하면 코드가 중복되지 않고, 장식품처럼 여러 스트림 클래스에 붙여서 사용할 수 있다. 즉 재사용성이 높고 유지보수가 쉬워진다.

이렇게 만든 클래스는 실제 데이터가 저장된 장소로부터 데이터를 읽는 일을 하지 않는다. 단지 읽어 온 데이터를 중간에서 가공 처리하여 리턴하는 일을 한다. 이런 류의 클래스를 데이터 처리 스트림 클래스(data processing stream class)라고 한다.

BufferedInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class BufferedInputStream {
  InputStream in;
  
  byte[] buf = new byte[8192];
  int size;
  int cursor;
  
  public BufferedInputStream(InputStream in) {
    this.in = in;
  }
  
  public int read() throws IOException {
    if (cursor == size) { // 버퍼에 저장되어 있는 데이터를 모두 읽었다.
      if ((size == in.read(buf)) == -1) {
        return -1;
      }
      cursor = 0;
    }
    return buf[cursor++] & 0x000000ff;
  }
  
  public int read(byte[] buf) throws IOException {
    int i = 0;
    for (; i < buf.length; i++) {
      // 1바이트를 읽어서 파라미터로 받은 바이트 배열에 채운다.
      int b = this.read();
      if (b == -1) {
        // 바이트 배열을 다 채우기도 전에 읽을 데이터가 없다면 읽기를 멈춘다.
        break;
      }
      buf[i] = (byte) b;
    }
    return i; // 지금까지 읽은 데이터의 수를 리턴한다.
  }
}

BufferedInputStream, BufferedOutputStream 사용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
FileInputStream in = new FileInputStream("temp/jls11.pdf");

// 기존의 FileInputStream에 버퍼 기능을 덧붙이기 위해서 상속 대신 포함하는 방식을 사용한다.
// 다음 BufferedInputStream 은 FileInputStream을 포함한다.
// 즉 파일에서 데이터를 읽을 때 FileInputStream을 사용한다.
BufferedInputStream in2 = new BufferedInputStream(in);

int b;

long startTime = System.currentTimeMillis(); // 밀리초

int callCount = 0;
while ((b = in2.read()) != -1)
  callCount++; // 파일을 끝까지 읽는다.

long endTime = System.currentTimeMillis();

System.out.println(endTime - startTime);
System.out.println(callCount);

in.close();

DataInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class DataInputStream {
  // FileInputStream 이나 ByteArrayInputStream 을 포함하는 레퍼런스
  InputStream in;

  public DataInputStream(InputStream in) {
    this.in = in;
  }

  public String readUTF() throws Exception {
    // 생성자에서 받은 InputStream 객체의 read() 메서드를 사용하여 문자열  값을 읽어서 리턴한다.
    int size = in.read();
    byte[] bytes = new byte[size];
    in.read(bytes); // 배열 개수 만큼 바이트를 읽어 배열에 저장한다.
    return new String(bytes, "UTF-8");
  }

  public int readInt() throws Exception {
    // 생성자에서 받은 InputStream 객체의 read() 메서드를 사용하여 int 값을 읽어서 리턴한다.
    int value = 0;

    value = in.read() << 24;
    value += in.read() << 16;
    value += in.read() << 8;
    value += in.read();
    return value;
  }

  public long readLong() throws Exception {
    // 생성자에서 받은 InputStream 객체의 read() 메서드를 사용하여 long 값을 읽어서 리턴한다.
    long value = 0;
    value += (long) in.read() << 56;
    value += (long) in.read() << 48;
    value += (long) in.read() << 40;
    value += (long) in.read() << 32;
    value += (long) in.read() << 24;
    value += (long) in.read() << 16;
    value += (long) in.read() << 8;
    value += in.read();
    return value;
  }

  public boolean readBoolean() throws Exception {
    // 상속 받은 read() 메서드를 사용하여 boolean 값 값을 읽어서 리턴한다.
    if (in.read() == 1)
      return true;
    else
      return false;
  }
}

현재 구조의 문제점: DataInputStream과 BufferedInputStream 사용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// FileInputStream
// - 파일 저장소에서 데이터를 읽는 일을 한다.
FileInputStream in = new FileInputStream("temp/test4.data");

// FileInputStream + BufferedInputStream
// - 버퍼를 이용하여 일정량의 데이터를 왕창 읽어온 다음에 바이트를 꺼낸다.
// - 읽기 속도를 높이는 일을 한다.
BufferedInputStream in2 = new BufferedInputStream(in);

// FileInputStream + BufferedInputStream + DataInputStream
// - 문자열이나 자바 기본 타입의 데이터를 좀 더 쉽게 읽기
// - 그러나 안타깝게도 이런 식으로 기능을 확장할 수 없다.
//   왜? 
//   - DataInputStream 생성자에는 InputStream 객체만 넘겨줄 수 있다.
//   - 즉 DataInputStream은 InputStream 객체에만 연결할 수 있다.
//   - BufferedInputStream은 InputStream 의 자식이 아니기 때문에 
//     DataInputStream에 연결할 수 없다.
DataInputStream in3 = new DataInputStream(in2); // 컴파일 오류!

// 만약 위의 코드처럼 DataInputStream을 BufferedInputStream에 연결할 수 있다면,
// 회원 정보를 읽을 때 버퍼를 사용하기 때문에 속도가 빠를텐데...
// 안타깝게도 현재의 클래스 구조로는 불가능하다!
// 
Member member = new Member();
member.name = in3.readUTF();
member.age = in3.

현재 구조는 DataInputStream과 BufferedInputStream이 InputStream객체를 파라미터로 받아 객체의 기능을 확장하고 있다. 그러나 BufferedInputStream과 DataInputStream을 동시에 사용할 수는 없는데, 두 클래스 모두 InputStream의 자손클래스가 아니라 파라미터로 받지 못하기 때문이다. 내일 수업에서는 BufferedIOStream과 DataIOStream이 IOStream을 상속하도록 변경할 것이다.

This post is licensed under CC BY 4.0 by the author.

학원 #43일차: 파일 입출력 API

[JS] 코어 자바스크립트 #1.1: 자바스크립트란?

Loading comments from Disqus ...