자바 I/O 마스터하기: 파일, 스트림, 입출력 기초
java.io 패키지에는 자바에서 입력과 출력(I/O)을 수행하는 데 필요한 거의 모든 클래스가 들어 있습니다. 이 모든 스트림은 입력 소스와 출력 대상을 나타냅니다. java.io 패키지의 스트림은 기본 타입, 객체, 지역화된 문자 등 다양한 데이터를 지원합니다.
스트림(Stream)
스트림은 데이터의 연속적인 흐름으로 정의할 수 있습니다. 스트림에는 두 가지 종류가 있습니다.
- InputStream - 소스에서 데이터를 읽는 데 사용
- OutputStream - 대상으로 데이터를 쓰는 데 사용
자바는 파일 및 네트워크 관련 I/O에 대해 강력하면서도 유연한 지원을 제공하지만, 이 튜토리얼에서는 스트림과 I/O의 가장 기본적인 기능만 다룹니다. 가장 흔히 사용되는 예제들을 하나씩 살펴보겠습니다.
바이트 스트림(Byte Streams)
자바 바이트 스트림은 8비트 바이트 단위의 입출력을 수행합니다. 바이트 스트림 관련 클래스는 많지만 가장 자주 사용되는 클래스는 FileInputStream과 FileOutputStream입니다. 다음 예제는 이 두 클래스를 사용하여 입력 파일을 출력 파일로 복사합니다.
예제
import java.io.*;
public class CopyFile {
public static void main(String args[]) throws IOException {
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("input.txt");
out = new FileOutputStream("output.txt");
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
다음 내용을 가진 input.txt 파일이 있다고 가정해 보겠습니다.
This is test for copy file.
위 프로그램을 컴파일하고 실행하면 output.txt 파일이 생성되며 input.txt와 동일한 내용이 들어갑니다.
$ javac CopyFile.java
$ java CopyFile
문자 스트림(Character Streams)
자바 바이트 스트림이 8비트 바이트 입출력을 수행하는 반면, 자바 문자 스트림은 16비트 유니코드 입출력을 수행합니다. 문자 스트림 관련 클래스도 많지만 가장 자주 쓰이는 것은 FileReader와 FileWriter입니다. 내부적으로 FileReader는 FileInputStream을, FileWriter는 FileOutputStream을 사용하지만, 주요 차이점은 FileReader는 한 번에 두 바이트를 읽고 FileWriter는 한 번에 두 바이트를 쓴다는 점입니다.
유니코드 문자가 있는 입력 파일을 출력 파일로 복사하는 예제를 문자 스트림으로 다시 작성해 보겠습니다.
예제
import java.io.*;
public class CopyFile {
public static void main(String args[]) throws IOException {
FileReader in = null;
FileWriter out = null;
try {
in = new FileReader("input.txt");
out = new FileWriter("output.txt");
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
표준 스트림(Standard Streams)
모든 프로그래밍 언어는 표준 I/O를 지원하여 사용자 프로그램이 키보드에서 입력을 받고 화면에 출력을 내보낼 수 있게 합니다. C나 C++을 아신다면 STDIN, STDOUT, STDERR라는 세 표준 장치를 아실 것입니다. 자바도 마찬가지로 다음 세 표준 스트림을 제공합니다.
- 표준 입력(Standard Input) - 사용자 프로그램에 데이터를 공급하며, 보통 키보드를 표준 입력 스트림으로 사용하고 System.in으로 표현
- 표준 출력(Standard Output) - 사용자 프로그램이 생성한 데이터를 출력하며, 보통 모니터를 표준 출력 스트림으로 사용하고 System.out으로 표현
- 표준 에러(Standard Error) - 사용자 프로그램이 생성한 에러 데이터를 출력하며, 보통 모니터를 표준 에러 스트림으로 사용하고 System.err으로 표현
사용자가 'q'를 입력할 때까지 표준 입력 스트림을 읽는 InputStreamReader를 생성하는 간단한 프로그램입니다.
예제
import java.io.*;
public class ReadConsole {
public static void main(String args[]) throws IOException {
InputStreamReader cin = null;
try {
cin = new InputStreamReader(System.in);
System.out.println("Enter characters, 'q' to quit.");
char c;
do {
c = (char) cin.read();
System.out.print(c);
} while (c != 'q');
} finally {
if (cin != null) {
cin.close();
}
}
}
}
위 코드를 ReadConsole.java에 저장하고 컴파일/실행하면 'q'를 누를 때까지 입력한 문자를 그대로 출력합니다.
$ javac ReadConsole.java
$ java ReadConsole
Enter characters, 'q' to quit.
1 1 e e q q
파일 읽기와 쓰기
앞서 설명했듯 스트림은 데이터의 시퀀스입니다. InputStream은 소스에서 데이터를 읽고, OutputStream은 대상으로 데이터를 씁니다.
입출력 스트림을 다루는 클래스 계층 구조는 다음과 같습니다.
중요한 두 스트림은 FileInputStream과 FileOutputStream으로, 이 튜토리얼에서 다룹니다.
FileInputStream
파일에서 데이터를 읽는 데 사용되는 스트림입니다. new 키워드로 객체를 생성하며 여러 생성자가 있습니다.
파일 이름을 문자열로 받아 입력 스트림 객체를 생성하는 생성자:
InputStream f = new FileInputStream("C:/java/hello");
파일 객체를 받아 입력 스트림 객체를 생성하는 생성자:
File f = new File("C:/java/hello");
InputStream f = new FileInputStream(f);
InputStream 객체를 얻으면 스트림을 읽거나 다른 연산을 수행하는 데 사용할 수 있는 여러 도우미 메서드들이 있습니다.
| 번호 |
메서드 및 설명 |
| 1 | public void close() throws IOException - 파일 출력 스트림을 닫음. 파일과 관련된 시스템 자원 해제. IOException 발생 가능 |
| 2 | protected void finalize() throws IOException - 파일과의 연결 정리. 이 스트림에 대한 참조가 더 없을 때 close 메서드 호출 보장. IOException 발생 가능 |
| 3 | public int read(int r) throws IOException - InputStream에서 지정된 바이트 데이터 읽음. int 반환. 다음 바이트 데이터 반환, 파일이 끝이면 -1 반환 |
| 4 | public int read(byte[] r) throws IOException - 입력 스트림에서 r.length 바이트를 배열로 읽음. 읽은 총 바이트 수 반환. 파일이 끝이면 -1 반환 |
| 5 | public int available() throws IOException - 이 파일 입력 스트림에서 읽을 수 있는 바이트 수 반환. int 반환 |
기타 중요한 입력 스트림:
- ByteArrayInputStream
- DataInputStream
FileOutputStream
FileOutputStream은 파일을 생성하고 데이터를 쓰는 데 사용됩니다. 파일이 없으면 출력용으로 열기 전에 파일을 생성합니다.
FileOutputStream 객체를 생성하는 두 가지 생성자:
파일 이름을 문자열로 받아 출력 스트림 객체 생성:
OutputStream f = new FileOutputStream("C:/java/hello")
파일 객체를 받아 출력 스트림 객체 생성:
File f = new File("C:/java/hello");
OutputStream f = new FileOutputStream(f);
OutputStream 객체를 얻으면 스트림에 쓰거나 다른 연산을 수행하는 도우미 메서드들을 사용할 수 있습니다.
| 번호 |
메서드 및 설명 |
| 1 | public void close() throws IOException - 파일 출력 스트림 닫음. 파일 관련 시스템 자원 해제. IOException 발생 가능 |
| 2 | protected void finalize() throws IOException - 파일과의 연결 정리. 이 스트림에 대한 참조가 더 없을 때 close 메서드 호출 보장. IOException 발생 가능 |
| 3 | public void write(int w) throws IOException - 지정된 바이트를 출력 스트림에 씀 |
| 4 | public void write(byte[] w) - 지정된 바이트 배열에서 w.length 바이트를 OutputStream에 씀 |
기타 중요한 출력 스트림:
- ByteArrayOutputStream
- DataOutputStream
예제: InputStream과 OutputStream 시연
import java.io.*;
public class FileStreamTest {
public static void main(String args[]) {
try {
byte bWrite[] = {11, 21, 3, 40, 5};
OutputStream os = new FileOutputStream("test.txt");
for (int x = 0; x < bWrite.length; x++) {
os.write(bWrite[x]); // 바이트 쓰기
}
os.close();
InputStream is = new FileInputStream("test.txt");
int size = is.available();
for (int i = 0; i < size; i++) {
System.out.print((char) is.read() + " ");
}
is.close();
} catch (IOException e) {
System.out.print("Exception");
}
}
}
위 코드는 test.txt 파일을 생성하고 주어진 숫자들을 바이너리 형식으로 씁니다. 같은 내용이 표준 출력에도 나타납니다.
파일 탐색과 I/O
파일 탐색과 I/O의 기초를 알아보기 위해 다음 클래스들을 살펴보겠습니다.
- File 클래스
- FileReader 클래스
- FileWriter 클래스
자바의 디렉터리
디렉터리는 다른 파일들과 디렉터리들의 목록을 포함할 수 있는 File입니다. File 객체를 사용하여 디렉터리를 생성하고, 디렉터리 내의 파일들을 나열할 수 있습니다. 자세한 내용은 File 객체에서 호출할 수 있는 모든 메서드 목록과 디렉터리 관련 메서드들을 확인하세요.
디렉터리 생성
디렉터리 생성을 위한 두 가지 유용한 File 유틸리티 메서드:
- mkdir() - 디렉터리를 생성하며, 성공 시 true, 실패 시 false 반환. 실패는 File 객체에 지정된 경로가 이미 존재하거나 전체 경로가 아직 존재하지 않아 디렉터리를 생성할 수 없음을 의미
- mkdirs() - 디렉터리와 그 디렉터리의 모든 상위 디렉터리를 생성
예제: "/tmp/user/java/bin" 디렉터리 생성
import java.io.File;
public class CreateDir {
public static void main(String args[]) {
String dirname = "/tmp/user/java/bin";
File d = new File(dirname);
// 디렉터리 생성
d.mkdirs();
}
}
위 코드를 컴파일하고 실행하여 "/tmp/user/java/bin"을 생성하세요.
참고 - 자바는 UNIX와 Windows의 경로 구분자를 관례에 따라 자동으로 처리합니다. Windows 버전 자바에서 슬래시(/)를 사용해도 경로가 올바르게 해결됩니다.
디렉터리 나열
File 객체의 list() 메서드를 사용하여 디렉터리 내의 모든 파일과 디렉터리를 나열할 수 있습니다.
예제
import java.io.File;
public class ReadDir {
public static void main(String[] args) {
File file = null;
String[] paths;
try {
// 새 파일 객체 생성
file = new File("/tmp");
// 파일 및 디렉터리 경로 배열
paths = file.list();
// 경로 배열의 각 이름에 대해
for (String path : paths) {
// 파일명과 디렉터리명 출력
System.out.println(path);
}
} catch (Exception e) {
// 에러 발생 시
e.printStackTrace();
}
}
}
내 /tmp 디렉터리에 있는 디렉터리와 파일에 따라 다음 결과가 나옵니다.
실행 결과
test1.txt
test2.txt
ReadDir.java
ReadDir.class