ex06
버퍼 사용하기
ㄴ 버퍼 사용 전/후 성능 비교
포맷
물리섹터를 몇개의 단위로 묶을 것인가
정보를 어떻게 관리할 것인가
* 데이터를 읽는 시간이란 seek time 과 read time 을 합친 만큼을 말함
ex06
Exam0110.java
// 버퍼 사용 전 - 데이터 읽는데 걸린 시간 측정
package com.eomcs.io.ex06;
import java.io.FileInputStream;
public class Exam0110 {
public static void main(String[] args) throws Exception {
FileInputStream in = new FileInputStream("temp/jls17.pdf");
int b;
long startTime = System.currentTimeMillis(); // 밀리초
int callCount = 0;
while ((b = in.read()) != -1) {
callCount++; // 파일을 끝까지 읽는다.
}
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
System.out.println(callCount);
in.close();
}
}
Exam0120.java
// 버퍼 사용 후 - 데이터 읽는데 걸린 시간 측정
package com.eomcs.io.ex06;
import java.io.FileInputStream;
public class Exam0120 {
public static void main(String[] args) throws Exception {
FileInputStream in = new FileInputStream("temp/jls17.pdf");
byte[] buf = new byte[8192]; // 보통 8KB 정도 메모리를 준비한다.
int len = 0;
long startTime = System.currentTimeMillis(); // 밀리초
int callCount = 0;
while ((len = in.read(buf)) != -1) {
System.out.printf("읽은 바이트 수: %d\n", len);
callCount++; // 파일을 끝까지 읽는다.
}
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
System.out.println(callCount);
in.close();
}
}
ㄴ 한 번 읽을 때 8192 바이트씩 읽음
ㄴ 시간이 확연히 줄어들은 것을 알 수 있음
=> 데이터를 많이씩 읽는 것이 좋음
테스트
MyFileInputStream.java
MyFileInputStream.java
MyFileInputStream.java
package com.eomcs.io.ex06;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MyFileInputStream extends FileInputStream {
byte[] buf = new byte[8192]; // 8KB
int length; // 버퍼에 저장한 바이트 수
int pos; // 읽을 바이트의 인덱스
public MyFileInputStream(String name) throws FileNotFoundException {
super(name);
}
@Override
public int read() throws IOException {
if (pos == length) { // 버퍼의 데이터를 다 읽었다면,
length = this.read(buf); // 버퍼 크기만큼 파일에서 데이터를 읽어 들인다.
pos = 0; // 버퍼 시작부터 읽을 수 있도록 위치를 0으로 설정한다.
}
return buf[pos++];
}
}
Exam0130.java
// BufferedFileInputStream 사용 전 - 데이터 읽는데 걸린 시간 측정
package com.eomcs.io.ex06;
public class Exam0130 {
public static void main(String[] args) throws Exception {
MyFileInputStream in = new MyFileInputStream("temp/jls17.pdf");
int b;
long startTime = System.currentTimeMillis(); // 밀리초
int callCount = 0;
while ((b = in.read()) != -1)
callCount++; // 파일을 끝까지 읽는다.
// => BufferedFileInputStream의 read() 메서드는
// FileInputStream에서 상속 받은 메서드를 이용하여
// 바이트 배열로 데이터를 왕창 가져온 다음
// 그 배열에서 1바이트를 리턴한다.
// => 그 이후에는 바이트 배열의 데이터가 떨어질 때까지
/// 계속 바이트 배열에서 값을 꺼내 리턴해 준다.
// => 바이트 배열의 값이 떨어지면
// 다시 바이트 배열 단위로 데이터를 읽어 온다.
// 그래서 1바이트 씩 읽더라도 그렇게 속도가 떨어지지 않는 것이다.
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
System.out.println(callCount);
in.close();
}
}
=>
MyFileInputStream.java
package com.eomcs.io.ex06;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MyFileInputStream extends FileInputStream {
byte[] buf = new byte[8192]; // 8KB
int length; // 버퍼에 저장한 바이트 수
int pos; // 읽을 바이트의 인덱스
public MyFileInputStream(String name) throws FileNotFoundException {
super(name);
}
@Override
public int read() throws IOException {
if (pos == length) { // 버퍼의 데이터를 다 읽었다면,
length = this.read(buf); // 버퍼 크기만큼 파일에서 데이터를 읽어 들인다.
if (length == -1) {
length = 0;
return -1;
}
pos = 0; // 버퍼 시작부터 읽을 수 있도록 위치를 0으로 설정한다.
System.out.println(length + " 바이트 읽었음!");
}
return buf[pos++] & 0x000000ff; // 4바이트 int값으로 바꿔줌
}
}
Exam0130.java
// BufferedFileInputStream 사용 후 - 데이터 읽는데 걸린 시간 측정
package com.eomcs.io.ex06;
public class Exam0130 {
public static void main(String[] args) throws Exception {
BufferedFileInputStream in = new BufferedFileInputStream("temp/jls17.pdf");
int b;
long startTime = System.currentTimeMillis(); // 밀리초
int callCount = 0;
while ((b = in.read()) != -1)
callCount++; // 파일을 끝까지 읽는다.
// => BufferedFileInputStream의 read() 메서드는
// FileInputStream에서 상속 받은 메서드를 이용하여
// 바이트 배열로 데이터를 왕창 가져온 다음
// 그 배열에서 1바이트를 리턴한다.
// => 그 이후에는 바이트 배열의 데이터가 떨어질 때까지
/// 계속 바이트 배열에서 값을 꺼내 리턴해 준다.
// => 바이트 배열의 값이 떨어지면
// 다시 바이트 배열 단위로 데이터를 읽어 온다.
// 그래서 1바이트 씩 읽더라도 그렇게 속도가 떨어지지 않는 것이다.
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
System.out.println(callCount);
in.close();
}
}
=> 속도 향상됨
ㄴ BufferedFileInputStream.java 참고하자
BufferedFileInputStream.java
package com.eomcs.io.ex06;
import java.io.FileInputStream;
import java.io.IOException;
public class BufferedFileInputStream extends FileInputStream {
byte[] buf = new byte[8192];
int size; // 배열에 저장되어 있는 바이트의 수
int cursor; // 바이트 읽은 배열의 위치
public BufferedFileInputStream(String filename) throws Exception {
super(filename);
}
// 파일에서 버퍼로 왕창 읽어 온 횟수
int readCount = 0;
// 버퍼를 사용하는 서브 클래스의 특징에 맞춰서
// 상속 받은 메서드를 재정의 한다.
@Override
public int read() throws IOException {
if (size == -1 || cursor == size) { // 바이트 배열에 저장되어 있는 데이터를 모두 읽었다면,
if ((size = super.read(buf)) == -1) { // 다시 파일에서 바이트 배열로 데이터를 왕창 읽어 온다.
return -1;
}
readCount++;
System.out.printf("==>버퍼로 왕창 읽었음! - %d 번째\n", readCount);
cursor = 0;
}
return buf[cursor++] & 0x000000ff;
// 위의 리턴 문장은 컴파일 할 때 아래의 문장으로 바뀐다.
// int temp;
// temp = buf[cursor];
// cursor++;
// return temp & 0x000000ff;
}
}
Exam0210.java
package com.eomcs.io.ex06;
import java.io.FileOutputStream;
public class Exam0210 {
public static void main(String[] args) throws Exception {
FileOutputStream out = new FileOutputStream("temp/data.bin");
System.out.println("데이터 쓰는 중...");
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
out.write(0x55);
}
long end = System.currentTimeMillis();
out.close();
System.out.println("출력 완료!");
System.out.printf("경과된 시간: %d\n", end - start);
}
}
Exam220.java
package com.eomcs.io.ex06;
import java.io.FileOutputStream;
public class Exam0220 {
public static void main(String[] args) throws Exception {
FileOutputStream out = new FileOutputStream("temp/data.bin");
System.out.println("데이터 쓰는 중...");
long start = System.currentTimeMillis();
byte[] buf = new byte[8192];
int size = 0;
for (int i = 0; i < 1000000; i++) {
// 일단 바이트 버퍼에 저장한다.
buf[size++] = 0x55;
if (size >= buf.length) {
out.write(buf); // 버퍼가 꽉 차면 파일로 내보낸다.
size = 0; // 다시 버퍼를 쓸 수 있도록 size를 0으로 초기화 한다.
}
}
// 마지막으로 버퍼에 남아 있는 바이트를 출력한다.
out.write(buf, 0, size);
long end = System.currentTimeMillis();
out.close();
System.out.println("출력 완료!");
System.out.printf("경과된 시간: %d\n", end - start);
}
}
ㄴ 버퍼가 꽉차면 그제서야 왕창 바이트 배열을 출력함
Exam0230.java
package com.eomcs.io.ex06;
public class Exam0230 {
public static void main(String[] args) throws Exception {
BufferedFileOutputStream out = new BufferedFileOutputStream("temp/data.bin");
System.out.println("데이터 쓰는 중...");
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
out.write(0x55);
// BufferedFileOutputStream은
// 내부적으로 1바이트를 파일로 바로 출력하지 않고
// 일단 byte[] 배열에 저장한다.
// 바이트 배열이 꽉 찼을 때 파일로 출력한다.
// 그래서 1바이트씩 파일로 바로 출력하는 것 보다 더 빠르다.
}
// BufferedFileOutputStream은 바이트 배열이 꽉 찼을 때만 파일로 출력하기 때문에
// 바이트 배열에 데이터가 남아 있을 경우 강제적으로 출력해야 한다.
out.flush(); // 버퍼에 남아 있는 것을 방출한다.
long end = System.currentTimeMillis();
out.close();
System.out.println("출력 완료!");
System.out.printf("경과된 시간: %d\n", end - start);
}
}
ㄴ BufferedFileOutputStream 이용
ㄴ flush() 이용하여 버퍼에 남아 있는 것을 방출
BufferedFileOutputStream.java
package com.eomcs.io.ex06;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedFileOutputStream extends FileOutputStream {
byte[] buf = new byte[8192];
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 flush() throws IOException {
if (cursor > 0) {
this.write(buf, 0, cursor);
cursor = 0;
}
}
// 항상 입출력 스트림을 사용한 다음에는 자원 해제를 위해 close()를 호출해야 한다.
// close()가 호출될 때 버퍼의 잔여 데이터를 내보내도록 상속 받은 메서드를 재정의 한다.
@Override
public void close() throws IOException {
this.flush();
super.close();
}
}
ㄴ 버퍼에 쌓아두다가 버퍼가 꽉차면 출력하는 것
ㄴ 버퍼의 잔여 데이터가 남아 있을 수 있으므로 상속받은 flush() 메서드를 통해 잔여 데이터를 강제로 출력하도록 함
=> close() 에도 flush() 메서드 이용됨
Exam0310.java
// 버퍼 사용 전 - 파일 복사 및 시간 측정
package com.eomcs.io.ex06;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Exam0310 {
public static void main(String[] args) throws Exception {
FileInputStream in = new FileInputStream("temp/jls17.pdf");
FileOutputStream out = new FileOutputStream("temp/jls17_2.pdf");
int b;
long startTime = System.currentTimeMillis(); // 밀리초
int count = 0;
while ((b = in.read()) != -1) {
out.write(b);
count++;
}
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
System.out.println(count);
in.close();
out.close();
}
}
ㄴ 파일을 읽을 때 마다 쓴다면 시간이 너무 오래걸림
Exam0320.java
// 버퍼 사용 후 - 파일 복사 및 시간 측정
package com.eomcs.io.ex06;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Exam0320 {
public static void main(String[] args) throws Exception {
FileInputStream in = new FileInputStream("temp/jls17.pdf");
FileOutputStream out = new FileOutputStream("temp/jls17_3.pdf");
byte[] buf = new byte[8192]; // 보통 8KB 정도 메모리를 준비한다.
int len = 0;
long startTime = System.currentTimeMillis(); // 밀리초
int count = 0;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
count++;
}
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
System.out.println(count);
in.close();
out.close();
}
}
// 데이터 읽기 시간 = average seek time + data transfer time
// 퀀텀 HDD 예)
// average seek time = 0.0105 초
// data trasfer time = 0.00000005 초 / 1 byte
//
// 1000 바이트를 읽을 때
// 1) 1바이트를 1000 번 읽는 경우
// 0.01050005초 * 1000 byte = 10.50005초
// 2) 1000바이트 1번 읽는 경우
// 0.0105 * 0.00000005초 * 1000 byte = 0.01055초
// 1바이트를 여러 번 읽을 경우 매번 바이트의 위치를 찾아야 하기 때문에
// 평균 탐색시간이 누적되어 한 번에 1000 바이트를 읽는 것 보다 시간이 오래 걸린다.
//
// 그러면 RAM에 배열의 크기를 크게 잡으면 좋지 않겠는가?
// => PC 처럼 소수의 프로그램이 동시에 실행될 때는 상관없지만,
// 서버처럼 데이터를 읽는 코드가 동시에 수천개에서 수십만개일 때는
// 아무리 작은 크기의 메모리라도 매우 많아지게 된다.
// 그래서 보통 8KB 정도의 버퍼 크기를 유지하고 있다.
// => 물론 프로그램의 용도나 목적에 따라 버퍼의 크기가 이보다 더 작아지거나
// 커질 수 있다.
ㄴ 8KB 메모리 버퍼 준비
ㄴ 버퍼 사용 후 속도 향상
Exam0330.java
// BufferedFileInputStream과 BufferedFileOutputStream을 사용하여 파일 복사 및 시간 측정
package com.eomcs.io.ex06;
public class Exam0330 {
public static void main(String[] args) throws Exception {
BufferedFileInputStream in = new BufferedFileInputStream("temp/jls17.pdf");
BufferedFileOutputStream out = new BufferedFileOutputStream("temp/jls11_4.pdf");
int b;
long startTime = System.currentTimeMillis(); // 밀리초
int count = 0;
while ((b = in.read()) != -1) {
out.write(b);
count++;
}
// 아직 파일로 출력되지 않고 버퍼 남아 있는 데이터를
// 마무리로 출력한다.
// out.flush();
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
System.out.println(count);
in.close();
out.close();
}
}
ㄴ BufferedFileInputStream 사용시 속도 향상
=> 버퍼를 도입하자!