목차
- File 클래스
- 입출력(io)
- 스트림(stream)
3-1. 입력 스트림
3-2. 출력 스트림
1. File 클래스
- JDK 1.0버전부터 지원하는 API로 파일 처리를 수행하는 대표적인 클래스이다.
- 대상 파일에 대한 정보로 인스턴스를 생성한다. 이때 대상 파일이 존재하지 않아도 인스턴스 생성은 가능하다.
- 파일 생성, 삭제와 같은 기능들을 수행한다.
File file = new File("src/com/greedy/section01/file/test.txt");
- 파일의 위치이자 경로(pathname)를 String 문자열로 작성해 인스턴스를 생성하는 것이다.
try {
boolean createSuccess = file.createNewFile();
System.out.println("createSuccess : " + createSuccess);
} catch (IOException e) {
e.printStackTrace();
}
- createNewFile() 은 파일 생성 후 성공/실패 여부를 boolean형으로 반환한다.
- 최초 실행 시 새롭게 파일이 만들어지므로 true가 반환되지만, 파일이 한 번 생성되고 난 다음에는 새롭게 파일을 만들지 못하기 때문에 false가 반환된다.
- [생성]createNewFile() : 파일을 생성한다. 파일 생성의 성공/실패 여부를 boolean형으로 반환 받는다.
- [크기]length() : 파일 크기를 반환한다.
- [경로]getPath() : 파일 경로를 반환한다. "test.txt" 같은 파일명까지 포함된다.
- [상위경로]getParent() : 지정 파일의 상위 경로까지를 탐색해 반환한다. 이때는 파일명 제외한 범위만을 출력한다.
- [절대경로]getAbsolutePath() : 절대 경로를 반환한다. 절대 경로란 최상위 루트 위치부터를 일컫는다. "D:\User\......test.txt" 형태로 출력된다.
- [삭제]delete() : 파일을 삭제한다. 삭제 후 성공/실패 여부를 boolean형으로 반환한다.
2. 입출력(io)
- input과 output의 약자로, 컴퓨터 내부 또는 외부 장치와 프로그램간에 데이터 교환을 일컫는다.
File → Program → File
Network → Program → Network
Keyboard → Program → Monitor
- 따라서 입출력은 비단 파일에서만 사용되는 개념이 아니다.
- 하드웨어 장치에 직접 접근할 수 있어야 입출력 또한 이루어질 수 있을 테다. 따라서 다양한 매체에 존재하는 데이터들을 공통적으로 처리하고 사용할 수 있도록 하는 방법이 필요한데, 그게 바로 스트림(stream)이다.
- 즉, 스트림(stream)이라는 공통적인 방법으로 입출력 데이터를 처리한다.
- 입출력과 관련된 API는 java.io 패키지에서 제공하고 있다.
3. 스트림(stream)
- 입출력 장치에서 데이터를 읽고 쓰기 위해 자바에서 제공하고 있는 클래스이다.
- 모든 스트림은 단방향, 즉 오로지 한 방향으로만 흐른다. 하나의 스트림으로 입출력을 동시에 수행할 수 없다. 동시 시행을 위해서는 2개의 스트림이 필요하다.
- 각각의 장치마자 연결 가능한 스트림이 존재하기 마련이다.
- 여기서 스트림은 데이터 처리 방식에 따라 두 가지로 나누어 볼 수 있다.
먼저 바이트 단위 처리는 1 byte씩 취급한다.
문자 단위 처리는 2 byte 또는 3 byte로 작업 단위가 구성된다.
구분 | 바이트 기반 스트림 | 문자 기반 스트림 | ||
입력 스트림 | 출력스트림 | 입력 스트림 | 출력 스트림 | |
최상위 클래스 | InputStream | OutputStream | Reader | Writer |
하위 클래스 | ___InputStream | ___OutputStream | ___Reader | ___Writer |
메소드 인자 타입 | int, byte[] | int, char[] | int, char, char[], String |
❗ 최상위 클래스에 대해서는 구분 가능할 만큼 숙지해야 한다.
파일을 기반으로 1 byte씩 불러오는 스트림의 이름은 FileInputStream이라고 해석할 수 있어야 한다.
FileReader라는 클래스는 File이라는 외부 자원을 문자 단위로 입력해주는 스트림이라고 해석할 수 있어야 한다.
- 자바 프로그램과 연결되는 외부 데이터의 타입이 무엇인지는 클래스의 이름을 보고 유추가 가능하다.
- 스트림의 기능은 쉽게 말해 생성하고, 읽거나 쓰고, 닫는 것으로 이어진다.
❗ 기반 스트림 : 실제 데이터가 전송되는 스트림을 말한다.
❗ 보조 스트림 : 기반 스트림에 붙어 추가 기능을 제공하거나 성능 향상을 보여주는 스트림을 일컫는다.
3-1. 입력 스트림
- InputStream과 Reader는 외부로부터 데이터를 읽어오는 입력스트림이다.
- InputStream은 byte 기반 입력 스트림의 최상위 클래스이자 추상클래스이다.
- Reader는 문자 기반 입력 스트림의 최상위 클래스이자 추상클래스이다.
A. InputStream
△
|
FileInputStream
int, byte[] 취급
- 파일로부터 byte 단위로 읽어올 때 사용한다.
- 그림, 오디오, 비디오, 텍스트 파일 등 모든 종류의 파일 읽기가 가능하다.
- InputStream의 하위 클래스로, 상위 클래스인 InputStream의 메소드를 그대로 사용할 수 있다.
int value;
while((value = fin.read()) != -1) {
System.out.println(value); 값을 정수로 읽어온다. 97, 98...
System.out.println((char) value); 문자로 출력하고 싶은 경우 형변환
}
- 값을 하나만 읽어오면 안 되니까 반복문 while문에 담는다. 이때 조건식은 ((value = fin.read()) != -1)이다.
- read() : 정수형 int 데이터, 바이트 배열 byte[] 데이터 취급한다. 파일에 기록된 값을 순차적으로 읽어오고 더 이상 읽어올 데이터가 없는 경우 -1을 반환한다.
❗ 한글 값이 입력된 경우 한글이 깨져서 나온다. 한글은 글자당 3 byte이기 때문에 3 byte 데이터를 1 byte씩 읽어오면서 글자가 깨지게 되는 것이다. 알 수 없는 숫자와 기호로 출력된다...
int fileSize = (int) new File("src/com/greedy/section02/stream/testInputStream.txt").length();
byte[] bar = new byte[fileSize];
fin.read(bar);
for(int i=0; i < bar.length; i++) {
System.out.println((char) bar[i]);
}
- 전처럼 int value를 이용해 1 byte씩 읽어와야 하는 경우도 존재하지만, 대부분 비효율적이다. 따라서 이번 예시처럼 byte 배열을 이용해 한 번에 데이터를 읽어올 수 있겠다.
- 이번에는 read() 메소드 인자로 byte[] 배열이 들어가 있음을 알 수 있다.
- 주의할 점은 read()는 파일의 끝까지 한 번 다 읽어들였다면 더 이상 값을 반환하지 않는다.
위에서 1 byte씩 read()진행한 while문을 주석해야 이번 for문이 정상 동작한다.
- length() : 파일 길이를 반환하는 File.class의 length()이다. public long length()여서 원래는 long타입을 따르지만, 여기서는 fileSize를 우리가 직접 지정해 정수형으로도 충분히 처리할 수 있는 범위임을 알고 있기에 (int) 강제형변환 적용하였다. 배열 크기를 가늠하기 위해 미리 측정해 보는 과정이었다.
B. Reader
△
|
FileReader
int, char[] 취급
- 문자(character, 2 byte 혹은 3 byte) 단위로 작업한다.
- FileInputStream과 사용하는 방법이 거의 동일하다. 단, byte 단위가 아닌 character 단위로 읽어들인다는 것이 차이점이다.
- 따라서 글자 단위로 읽어오기 때문에 한글을 정상적으로 읽어올 수 있다.
- 텍스트 파일 읽기에만 쓰인다.
- Reader의 하위 클래스로 Reader의 메소드를 그대로 사용 가능하다.
FileReader fr = new FileReader("C:/data/test.txt");
FileReader fr = new FileReader(new File("C:/data/test.txt"));
- 인스턴스를 생성한다==특정 자원과의 통로(스트림)를 연다는 뜻이다. 편의를 위해 큰따옴표만으로도 표기 가능하지만, 아래처럼 쓰는 것이 좋다.
- 만약 파일이 존재하지 않으면 FileNotFoundException이 발생하므로 예외처리는 필수이다.
package com.greedy.section02.stream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Application2 {
public static void main(String[] args) {
/* FileReader */
/* FileInputStream과 사용하는 방법이 거의 동일하다.
* 단, byte 단위가 아닌 character 단위로 읽어들이는 부분이 차이점이다.
* 따라서 2바이트든 3바이트든 글자 단위로 읽어오기 때문에 한글을 정상적으로 읽어올 수 있다.
* */
FileReader fr = null;
try {
/* 파일이 존재하지 않는 경우 파일을 찾지 못한다는 예외가 발생한다.
* 파일을 추가해 정상적으로 스트림이 생성될 수 있도록 한다.
* */
/* 인스턴스를 생성한다==특정 자원과의 통로(스트림)를 연다. */
fr = new FileReader("src/com/greedy/section02/stream/testReader.txt");
/* 파일 내용을 하나씩 읽어오는 것도 동일하다.
* 한글 값을 입력해도 하나의 글자 단위로 읽힌다. */
// int value;
// while((value = fr.read()) != -1) {
// System.out.print((char) value);
// }
/* 한꺼번에 읽어오는 것도 가능하다.
* byte 배열로 읽어오면 한글은 깨지게 되므로
* char 배열로 내용을 읽어오는 기능을 제공하고 있다.
* */
/* length()를 int형으로 변환한 것이다. */
char[] carr = new char[(int) new File("src/com/greedy/section02/stream/testReader.txt").length()];
fr.read(carr);
for(int i=0; i < carr.length; i++) {
System.out.print(carr[i]);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3-2. 출력 스트림
- OutputStream과 Writer는 데이터를 내보내는 출력스트림이다.
- OutputStream은 byte 기반 출력 스트림의 최상위 클래스이자 추상클래스이다.
- Writer는 문자 기반 출력 스트림의 최상위 클래스이자 추상클래스이다.
❗ 출력 스트림인 OutputStream, Writer의 경우 대상 파일이 존재하지 않으면 파일을 자동으로 생성해준다.
앞서 배운 입력 스트림들처럼 FileNotFoundException을 핸들링해야 하는 것은 동일하지만, 파일이 존재하지 않는 상태에서 그냥 실행해도 예외가 발생하지 않고 인스턴스가 잘 생성된다.
여기서 알 수 있듯 FileNotFoundException이 핸들링 하는 부분은 '지정된 경로를 찾을 수 없습니다'라는 안내말처럼 그 경로(pathname)에 있다는 것이다.
A. OutputStream
△
|
FileOutputStream
int, byte[] 취급
- 그림, 오디오,비디오,텍스트 파일 등 모든 종류의 파일로 출력이 가능하다.
- OutputStream의 하위 클래스로 OutputStream의 메소드를 그대로 쓸 수 있다.
FileOutputStream fout = null;
try {
fout = new FileOutputStream("src/com/greedy/section02/stream/testOutputStream.txt"); //덮어쓰기
fout = new FileOutputStream("src/com/greedy/section02/stream/testOutputStream.txt", true); //이어쓰기
fout.write(97);
- 인스턴스 생성 시 두 번째 인자로 true를 전달하면 이어쓰기가 된다. 전달하지 않거나 그 반대인 false로 명시하면 덮어쓰기가 된다.
byte[] bar = new byte[] {98, 99, 100, 101, 102, 10, 103, 104, 105};
fout.write(bar);
- InputStream에서와 마찬가지로 byte[] 배열을 이용해서 한 번에 기록할 수도 있다.
- 여기서 숫자 10은 개행 문자이다.
fout.write(bar, 1, 3);
- bar의 1번 인덱스부터 3의 길이만큼 지정해 파일에 내보낼 수 있다.
} finally {
if(fout != null) { *인스턴스가 null이 아닌 경우 자원 반납
try {
fout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- try-catch 이후 finally 구문에서 close() 해야 한다. 이를 자원 반납이라고 한다.
❗ 자원 반납 close() 또는 flush()
if문으로 조건식을 세워 if(fout != null)처럼 인스턴스가 null이 아닌 경우에는 항상 자원 반납하도록 한다.
자원 해제를 하는 경우에도 IOException을 핸들링 해야 하기 때문이다. 이미 자원이 반납된 경우 발생하는 Exception이다.
자원 반납을 해야 하는 경우
A. 장기간 실행 중인 프로그램에서 스트림을 닫지 않는 경우 다양한 리소스에 누수(leak)가 발생한다.
B. 앞으로 배우게 될 버퍼(buffer)를 이용하게 되면, 마지막에 flush()로 버퍼에 있는 데이터를 강제 전송해야 한다.
만약 잔류 데이터가 남은 상황에서 스트림을 사용한다면 데드락(deadlock) 상태가 된다.
판단하기 어렵고 의도하지 않은 상황에서 이런 현상이 발생할 수 있으니 마지막에는 flush()를 무조건 실행해주는 것이 좋다.
한편, close() 메소드는 자원을 반납하며 flush()를 해주기 때문에 close()만 제대로 해주어도 된다.
try-with-resource 구문을 사용하면 끝에 close() 자동 처리되므로 그렇게 쓰는 것도 방법이다. 하단의 예시를 참고하자.
try (BufferedReader in = new BufferedReader(new FileReader("test.dat"))) {
String s;
while((s = in.readLine()) != null) {
System.out.println(s);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
B. Writer
△
|
FileWriter
int, char[], String str 취급
- FileReader와 마찬가지로 글자 단위로 데이터를 처리한다.
- Writer의 하위 클래스로 Writer의 메소드를 그대로 사용 가능하다.
- 텍스트 파일에 쓰인다.
❗ 1 byte가 아닌 문자 등의 특정 단위로 다룰 때는 값을 버퍼에 모았다가 내보내게 되어있다. 그 특성상 반드시 flush() 또는 close()처리를 해주어야 데이터를 출력할 수 있다.
FileWriter fw = new FileWriter("C:/data/test.txt"); //덮어쓰기
FileWriter fw = new FileWriter("C:/data/test.txt", true); //이어쓰기
- 인스턴스 생성 시 덮어쓰기, 이어쓰기 부분을 지정할 수 있다. 두 번째 인자에 true를 작성하면 이어쓰기 적용된다.
- 단, 덮어쓰는 경우 파일이 존재하지 않을 때는 자동 생성으로 이어지지만 이미 존재하는 파일인 때는 그 파일에 덮어쓰기 된다는 유의점이 따른다.
package com.reminder.stream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class TestWriter {
public static void main(String[] args) {
/* FileWriter */
/* 1. flush() 사용 */
FileWriter fileWriterA = null;
try {
fileWriterA = new FileWriter(new File("src/com/reminder/stream/testA.txt")); //덮어쓰기
// fileWriter = new FileWriter(new File("src/com/reminder/stream/testA.txt"), true); //이어쓰기
/* A. int 인자 */
fileWriterA.write(97);
fileWriterA.write(98);
fileWriterA.write(99);
/* B. char, char[] 인자 */
fileWriterA.write('d');
char[] carr = new char[] {'A', 'B', 'C', 'D'};
fileWriterA.write(carr);
/* C. String 인자 */
fileWriterA.write("안녕하세요");
/* 이처럼 write() 후 곧바로 run 하면 어떤 것도 내보내기 하지 않은 상황이 된다.
* FileWriter는 추상클래스이자 저보다 상위클래스인 Writer를 상속하고 있다.
* 따라서 Writer의 메소드 기능들을 그대로 사용할 수 있을뿐만 아니라
* 1 byte 단위가 아닌 문자(character, 2 byte 또는 3 byte) 단위로 출력한다는 특성까지 그대로 닮아있다.
* 여기서 문자는 출력 시에 버퍼에 모았다가 내보내게 되어있기에 flush() 또는 close() 메소드를 반드시 사용해야 한다.
* */
fileWriterA.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileWriterA.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/* 2. close() 사용 */
try(FileWriter fileWriterB = new FileWriter(new File("src/com/reminder/stream/testB.txt")/*, true*/)) {
fileWriterB.write("반갑습니다");
/* 이때는 flush() 또는 close() 메소드를 직접 작성하지 않았음에도 파일로 곧바로 출력된다. */
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.greedy.section02.stream;
import java.io.FileWriter;
import java.io.IOException;
public class Application4 {
public static void main(String[] args) {
/* FileWriter */
/* 프로그램의 데이터를 파일로 내보내기 위한 용도의 스트림이다.
* 1글자 단위로 데이터를 처리한다.
* */
FileWriter fw = null;
try {
/* FileNotFountException을 핸들링해야 하지만
* 실행해도 예외는 발생하지 않는다.
* Writer의 경우 대상 파일이 존재하지 않으면 파일을 자동으로 생성해준다.
* 경로가 존재하지 않는 경우에는 예외가 발생한다.
* */
/* 두 번째 인자로 true를 전달하면 이어쓰기가 된다.
* 인자를 전달하지 않으면(또는 false라고 명시하면) 덮어쓰기가 된다.
* */
// fw = new FileWriter("src/com/greedy/section02/stream/testWriter.txt");
fw = new FileWriter("src/com/greedy/section02/stream/testWriter.txt", true);
/* write() 메소드도 IOException을 핸들링해야 한다. */
fw.write(97); //아무 값도 생기지 않았다. flush() 또는 close() 처리해야 작성된다.
/* 문자 단위 출력도 내부 버퍼를 사용하므로 쌓여있는 데이터를 flush()로 내보내줘야
* 최종적으로 파일에 출력되는 모습을 확인할 수 있다. 또는 close()로 자원을 반납하면서 반납 전에
* flush()가 자동 호출되므로 파일에 출력까지 이어지는 모습을 확인할 수 있다.
* */
// fw.flush();
/* 문자 기반 스트림은 직접 char 자료형으로 내보내기도 가능하다 */
fw.write('A');
/* 혹은 char 배열도 가능하며
* 문자열도 가능하다. */
fw.write(new char[] {'a', 'p', 'p', 'l', 'e'});
fw.write("우리나라 대한민국");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
'Java' 카테고리의 다른 글
[JAVA/수업 과제 practice] HashMap 데이터 관리 프로그램 만들기 (0) | 2022.01.13 |
---|---|
[자바의 정석] Ch 7. 객체 지향 프로그래밍 II 강의 메모 (0) | 2022.01.13 |
[JAVA] 13-2. 예외처리 | finally | try-with-resource | 오버라이딩 (0) | 2022.01.12 |
[JAVA/수업 과제 practice] ArrayList 데이터 관리 프로그램 만들기 (0) | 2022.01.12 |
[JAVA] 13-1. 예외처리 | throws | try-catch | 사용자 정의 예외클래스 (0) | 2022.01.11 |