보조 스트림은 기반스트림에 연결해서 계속적으로 사용할 수 있는 스트림으로, 편리한 기능을 제공하는 스트림들이다.
바이트 단위 스트림은 한글 문자같은 2바이트 문자를 못 읽는다. 그런데 inputStreamReader 와 같은 보조 스트림을 사용하면 바이트 단위 자료를 문자로 변환하는 기능을 제공한다.
Bufferedinput & output Stream 보조 스트림을 활용하면 용량이 큰 자료에 접근할 때 입출력이 많이 오가면서(버퍼 크기 때문에) 속도가 느려지는데, 한번에 큰 버퍼를 제공하면서 성능을 향상시키는 효과를 발휘한다.
InputStreamReader 예제
package main;
import java.io.FileInputStream;
import java.io.InputStreamReader;
public class Ex10 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("src/reader.txt");
InputStreamReader isr = new InputStreamReader(fis);
int i;
while (true) {
i = isr.read();
if(i == -1) break;
System.out.println((char) i);
}
} catch (Exception e) {
System.out.println(e);
}
}
}
실행 결과를 보면 바이트 단위 스트림인 FileInputStream 인데도 불구하고 한글을 정상적으로 읽어오는 것을 볼 수 있다.
FileInputStream & FileOutputStream + 보조 스트림 예제 (성능향상편)
기반 스트림만 활용한 경우 (비효율 발생)
우선 a 파일에 엄청나게 길게 A 를 입력했습니다. (대략 2천개..?)
기반 스트림만 사용하여 파일 해당 파일을 읽어서 다른 파일로 복사하는 시간을 측정해보겠습니다.
package main;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Ex11 {
public static void main(String[] args) {
long start = 0;
long end = 0;
try {
FileInputStream fis = new FileInputStream("src/a.txt");
FileOutputStream fos = new FileOutputStream("src/copy.txt");
start = System.currentTimeMillis(); // 복사 시작 시간
int i;
while ((i = fis.read()) != -1) { // 파일에서 한 글자씩 읽어가면서 복사시작
fos.write(i);
}
end = System.currentTimeMillis(); // 복사 종료 시간
} catch (IOException e) {
System.out.println(e);
}
System.out.println("파일 복사하는 데 " + (end - start) + " ms 소요되었습니다.");
}
}
우선 위 코드에서 낯선 코드가 currentTimesMillis( ) 인데, 이 값은 현재 시간을 밀리 세컨드를 기준으로 반환한다. 여기서 시간은 우리가 흔히 보는 12:00:10 이런 식으로 출력되는 것이 아니라 15468454861686... 이런식으로 현재 시간을 표현한다. 값이 커서 반환을 long 타입을 하므로 우리도 long 타입 변수에 반환 값을 담아주어야 한다.
그 다음 파일을 읽어서 복사 파일에 쓰는 과정을 반복하는데 총 약 22 초가 걸린 것을 확인할 수 있다. 굉장히 오래 걸린 것을 확인할 수 있는데, 1 byte 씩 읽어서 옮기기 때문에 동작 과정이 너무나 많아짐에 따라서 생긴 현상이다.
보조 스트림 활용한 경우 (비효율 제거)
package main;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Ex12 {
public static void main(String[] args) {
long start = 0;
long end = 0;
try {
FileInputStream fis = new FileInputStream("src/a.txt"); // 기반 스트림 생성
FileOutputStream fos = new FileOutputStream("src/copy2.txt");
BufferedInputStream bis = new BufferedInputStream(fis); // 버퍼 보조 스트림 생성
BufferedOutputStream bos = new BufferedOutputStream(fos); // 버퍼 보조 스트림 생성
start = System.currentTimeMillis();
int i;
while ((i = bis.read()) != -1) { // 8kb 씩 읽어서 복사한다.
bos.write(i);
}
end = System.currentTimeMillis();
} catch (IOException e) {
System.out.println(e);
}
System.out.println(end - start + "ms 초 걸렸습니다.");
}
}
0.31 초가 걸렸다. 0.31초!! 기반 스트림을 활용했을 때에 대비해서 몇 배가 빨라진 것인가? 엄청난 효율의 비약적인 상승이다.
한번에 가져오는 버퍼의 크기가 8kb 로 커짐에 따라서 속도가 엄청 빨라진 것을 확인할 수 있다.
File 예제
이번에는 File 클래스를 활용해보자.
package main;
import java.io.File;
import java.io.IOException;
public class Ex13 {
public static void main(String[] args) {
File file = new File("C:\\atemp\\new.txt");
try {
file.createNewFile(); // 실제 파일 생성
} catch (IOException e) {
System.out.println(e);
}
System.out.println(file.isFile());
System.out.println(file.isDirectory());
System.out.println(file.getName());
System.out.println(file.getAbsolutePath());
System.out.println(file.getPath());
System.out.println(file.canRead());
System.out.println(file.canWrite());
file.delete(); // 파일 삭제
}
}
File 클래스를 활용하여 내가 원하는 확장자의 파일을 생성하고 생성한 파일의 이름, 주소, 상태 등 다양한 속성을 확인할 수 있는 것을 볼 수 있다.
또한 file.delete( ) 를 활용하여 파일을 삭제해줄 수 있다.
package main;
import java.io.File;
public class Ex13 {
public static void main(String[] args) {
File dir = new File("C:\\atemp");
File[] files = dir.listFiles(); // 폴더 밑에 있는 파일 목록
for (int i = 0; i<files.length; i++) {
System.out.println(files[i]);
}
}
}
listFiles( ) 메서드를 사용하면 디렉터리에 있는 파일들을 리스트에 담아서 사용할 수도 있다. 매우 유용하겠다.
Quiz 4
한 폴더에 있는 파일의 목록을 읽어서 텍스트 파일에 출력하세요. (quiz4.txt 에 출력)
package quiz;
import java.io.File;
import java.io.FileWriter;
public class Quiz4 {
public static void main(String[] args) {
File file = new File("C:\\atemp\\quiz4.txt");
File dir = new File("C:\\atemp");
File[] files = dir.listFiles();
try {
file.createNewFile();
FileWriter fw = new FileWriter(file);
for (int i = 0; i < files.length; i++) {
fw.write(files[i].getAbsolutePath() + "\n");
}
fw.flush();
} catch (Exception e) {
System.out.println(e);
}
}
}
File 로 생성할 파일을 지정을 한다.
dir 로 하위 폴더를 파악할 폴더를 지정한다.
File[ ] files = dir.listFiles( ) 로 dir 하위에 있는 리스트를 갖게 되었다.
그 아래 코드에서 중요한 점이 하나 있다.
FileWriter fw = new FileWriter(file);
이 구문에서 사실 매개 변수가 숨겨져 있는 것이 하나 있다.
FileWriter fw = new FileWriter(file, false);
바로 이것인데, 두 번째 매개변수는 파일에 덮어씌기를 하겠다는 것을 의미한다. default 값이므로 true 를 주게 되면 파일에 덮어쓰는 것이 아니라 이어서 쓰게 된다. 이런 내용을 숙지하면 다음과 같은 코드도 같은 동작을 하게 된다는 것을 알 수 있다.
package quiz;
import java.io.File;
import java.io.FileWriter;
public class Quiz4 {
public static void main(String[] args) {
File file = new File("C:\\atemp\\quiz4.txt");
File dir = new File("C:\\atemp");
File[] files = dir.listFiles();
try {
file.createNewFile();
for (int i = 0; i < files.length; i++) {
FileWriter fw = new FileWriter(file, true);
fw.write(files[i].getAbsolutePath() + "\n");
fw.flush();
}
} catch (Exception e) {
System.out.println(e);
}
}
}
똑같은 결과가 출력되는 것을 확인할 수 있다.
Quiz5 하위 모든 파일 출력하기 (함수와 스택 메모리 특징 확인 가능)
Q. 특정 폴더에 있는 모든 하위 파일의 목록을 읽고자 한다. 하위의 하위의 하위 파일들도 확인을 하는 것을 의미한다. 즉, 디렉토리면 내부로 들어가서 파일 목록을 읽고 등등 반복을 한다는 것을 의미하는 프로그램을 작성하시오.
package quiz;
import java.io.File;
import java.io.FileWriter;
public class Quiz5 {
public static void main(String[] args) {
File dir1 = new File("C:\\atemp");
findDir(dir1);
System.out.println("=== 프로그램이 정상 종료됩니다. ===");
}
public static void findDir(File dir) {
File file = new File("C:\\atemp\\quiz5.txt");
File[] files = dir.listFiles();
try {
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
File newdir = files[i];
System.out.println("새로운 하위 디렉터리 " + newdir + "을 찾았습니다.");
findDir(newdir);
} else {
for (int j = 0; j < files.length; j++) {
FileWriter fw = new FileWriter(file, true);
fw.write(files[j].getAbsolutePath() + "\n");
fw.flush();
}
}
}
} catch (Exception e) {
System.out.println(e);
}
}
}
위 코드를 보면 재귀함수를 사용한 것을 볼 수 있다. 즉, if 문을 통해서 files List 에 담긴 것이 디렉터리라면 새로운 해당 files[i] 를 새로운 file 객체 변수에 담고 함수를 다시 호출하는 것이다. 그리고 파일일 때 결과들을 텍스트 파일에 작성하도록 코드를 구성하였다. 여기서 중요한 점은 이전 퀴즈와 마찬가지로 FileWriter 를 사용할 때 매개변수를 2개를 주는데 두 번째 변수를 true 를 줘서 덮어쓰기가 아니라 이어쓰기로 만들어줘야 한다.
출력 값을 보면 모든 하위 디렉터리에 담긴 파일들을 잘 가져온 것을 확인할 수 있다. 여기서 유의 깊게 보아야 하는 것은 출력 결과인데, 함수의 동작 과정인 스택 메모리의 FILO 의 특징이 잘 보인다.
C:\atemp\ai\packages\AAM\IPC
위 경로가
이렇게 구성되어 있는데, 더이상 하위 폴더가 없다. 따라서 이 디렉터리에 담긴 파일들은 file write 가 우선 실행된다. 그 다음 AAM 에 다른 하위 폴더나 파일이 없으므로 함수가 곧바로 종료되고 스택 메모리 다음에 쌓여 있던
새로운 하위 디렉터리 C:\atemp\ai\packages을 찾았습니다.
이 함수가 이어서 실행된다. 그러면 다음 폴더는 ADC 가 된다. 이런 식이었기 때문에 출력 결과과 위의 화면과 같았던 것이다.