스트림이란 연속적인 데이터의 흐름을 말한다. 자바 입출력 스트림은 응용프로그램과 입출력 장치를 연결하는 역할을 한다. 입력 스트림은 키보드 장치를 제어하여 키 입력을 응용 프로그램에 전달하고, 연결된 출력 스트림에 출력하면, 출력 장치를 제어하여 장치에 출력한다. 자바 응용 프로그램이 장치를 직접 제어하지 않고 입출력 스트림 객체와 연결함으로써 데이터 입출력이 쉬워진다.
스트림의 한 쪽 끝에는 자바 응용 프로그램이, 다른 쪽에는 입출력 장치가 연결된다. 입출력 스트림이 입출력 장치를 제어하고 실질적인 입출력을 담당한다.
스트림은 단방향으로 흐르며 각각의 역할을 수행한다. 입력 스트림은 입력 장치에서 응용 프로그램으로 데이터를 전송하고 출력 스트림은 응용 프로그램으로부터 받은 데이터를 출력 장치로 전송한다.
이 때, 기본 단위는 바이트와 문자이다. 바이트 스트림과 문자 스트림이 있다.
스트림은 FIFO 방식을 따른다.
io 관련 클래스의 경로는 java.io로 시작하며 클래스를 사용하려면 import java.io.*;을 작성해 가져와야 한다.
자바 입출력 스트림은 바이트 스트림과 문자 스트림이 있다. 바이트 스트림은 바이트 단위로 데이터를 다루며 문자 스트림은 2바이트 char 타입 데이터를 다룬다. 문자가 아닌 데이터가 입출력되는 경우 오류가 발생할 수 있다.
java.io 패키지에는 바이트 단위, 문자 단위로 스트림 입출력을 하기 위한 클래스가 포함되어 있다.
바이트 스트림 관련 클래스로는 InputStream/OutputStream 클래스를 상속받은 클래스들이 있으며 문자 스트림 관련 클래스로는 Reader/Writer 클래스를 상속받은 클래스들이 있다.
텍스트 파일, 자바 소스 파일과 같이 문자로만 이루어진 파일의 경우와 다르게 이미지, 비디오 등의 바이너리 파일의 경우 바이트 스트림 클래스만 사용 가능하다.


스트림을 서로 연결할 수 있다. 다음은 표준 입력 스트림인 System.in과 InputStreamReader 스트림 객체를 연결해 키보드로부터 문자를 입력받는 코드이다.
InputStreamReader rd = new InputStreamReader(System.in); //문자 입력 스트림과 표준 입력 스트림 연결
int c = rd.read(); //입력 스트림으로부터 키 입력
두 스트림 rd와 System.in이 연결되면 System.in은 사용자의 키 입력을 받아 바이트 스트림으로 내보내며 rd는 바이트 스트림을 문자로 구성해 응용 프로그램에 전달한다.
문자 스트림은 2바이트 유니코드 문자를 입출력하는 스트림이다. 바이너리 데이터는 처리할 수 없다. 문자 입력 스트림은 바이트를 전달받고 이를 문자로 변환한다.
InputStreamReader는 스트림에 입력되는 바이트 데이터를 문자 집합을 통해 문자로 변환한다. 이 때 InputStreamReader 생성자에 문자 집합을 지정해주어야 하는데, 읽어들인 바이트들이 문자 집합에 속하지 않는 경우 이 문자는 해독할 수 없는 것으로 처리된다.
다음과 같이 바이트 파일 입력 스트림을 생성한 뒤 InputStreamReader 객체를 생성한다.
FileInputStream fin = new FileInputStream("c:\\text.txt");
InputStreamReader in = new InputStreamReader(fin, "MS949");
생성자 InputStreamReader의 두 번째 매개변수에는 fin으로부터 읽어들인 바이트들을 문자로 인코딩하기 위한 문자 집합을 지정한다. 예시에서는 윈도우에서 다폴트로 이용하는 문자 집합 MS949를 지정하였다.
파일을 읽기 위해서는 in 스트림이 필요하다. in.read()는 fin이 파일로부터 필요한 바이트들을 읽도록 하고, 읽어들인 바이트를 MS949 문자 집합에 정의된 문자인지 찾아 한글 문자를 리턴한다. 문자 집합에 없는 경우 비정상적인 값을 리턴한다.
즉, InputStreamReader를 통해 텍스트 파일을 읽으려면 FileInputStream을 생성해 바이트 스트림을 읽고 InputStreamReader을 연결하여 문자 집합을 지정한다. 여기에 in 스트림을 연결하여 파일을 읽는 과정을 거친다.
FileReader 클래스를 이용해 파일을 읽으려면 우선 파일 입력 스트림을 생성해야 한다.
다음은 FileReader로 파일 입력 스트림을 생성하고 텍스트 파일을 연결하는 예시이다. FileReader는 생성자에 전달된 파일을 찾아 열고 스트림과 연결한다.
FileReader fin = new FileReader("c:\\text.txt");
read()는 연결된 파일로부터 문자 하나를 읽어 리턴하며 EOF을 만나는 경우 -1을 리턴한다. 다음은 파일 전체를 읽어 화면에 출력하는 코드이다.
int c;
while((c = fin.read()) != -1){ //하나의 문자를 c에 읽어들임. 파일 끝까지 반복
System.out.print((char)c); //문자 c 화면에 출력
}
파일을 한 문자가 아닌 한 블록씩 읽으려면 read()에 버퍼를 전달하면 된다.
char [] buf = new char [1024];
int n = fin.read(buf); //한 번에 1024개 문자를 읽어 buf[]에 저장하고 읽은 문자 수 리턴
읽은 문자의 개수가 1024보다 적은 경우 실제로 읽은 문자의 개수를 리턴한다. 따라서 n이 버퍼 크기보다 작은 경우, 파일을 끝까지 읽은 것으로 해석할 수 있다.
스트림을 닫을 때 close() 메소드를 호출한다.
fin.close();
FileWriter를 이용해 텍스트 파일을 작성하는 경우 다음과 같이 출력 스트림을 생성한다.
FileWriter fout = new FileWriter("c:\\text.txt");
FileWriter의 생성자에 전달된 파일을 열어 스트림과 연결한다. 파일이 없는 경우 새 파일을 생성하며 파일이 존재하는 경우 파일 내용을 전부 지우고 새로 작성한다.
FileWriter의 write() 메소드를 사용하면 문자 단위로 파일에 저장할 수 있다. 다음과 같이 작성한다.
fout.write('A'); //문자 'A'를 파일에 저장
fout.write("ABCDEFG"); //문자열 "ABCDEFG"를 파일에 저장
블록 단위로 문자를 저장하려면 다음과 같이 작성하면 된다.
char [] buf = new char [1024];
fout.write(buf, 0, buf.length);
파일 쓰기를 마친 후 close()를 호출하여 스트림을 닫는다.
fout.close();
스트림을 닫으면 연결된 파일도 닫힌다.
파일 입출력 실행 중 다음의 예외가 발생할 수 있다.
파일 경로명이 틀린 경우
FileReader 생성자는 FileNotFoundException 예외 발생시킴
파일 읽기/쓰기/닫기 중 입출력 오류가 발생한 경우
read(), write(), close() 메소드는 IOException 예외 발생시킴
따라서 파일 입출력 코드에 try-catch 블록을 작성해주어야 한다.
try{
FileReader fin = new FileReader("c:\\text.txt");
...
int c = fin.read();
...
fin.close;
}catch(FileNotFoundException e){
System.out.println("파일 열기 실패");
}catch(IOException e){
System.out.println("입출력 오류");
}
바이트 스트림은 바이트 단위의 바이너리 데이터가 흐르는 스트림이다. 이미지, 동영상 파일 입출력과 문자 텍스트 파일 입출력에 이용된다.
대표적인 바이트 스트림 클래스로는 바이트 입출력 처리를 위한 공통 기능을 가진 추상 클래스 InputStream/OutputStream가 있다. InputStream/OutputStream 슈퍼 클래스를 상속한 클래스 FileInputStream/FileOutputStream은 파일 입출력을 위한 클래스로 파일로부터 바이너리 데이터를 읽거나 파일에 바이너리 데이터를 저장할 수 있다. DataInputSteam/DataOutputStream은 자바 기본 타입의 값과 문자열을 바이너리 형태로 입출력한다.
프로그램 내 변수나 배열에 들어있는 바이너리 값을 파일에 저장해야 하는 경우 바이트 스트림 파일쓰기를 한다.
파일 출력 스트림은 다음과 같이 생성한다.
FileOutputStream fout = new FileOutputStream("c:\\text.txt");
생성자에 전달 된 파일을 생성하고 연결한다. 파일이 이미 존재하는 경우 내용을 전부 삭제하고 스트림에 연결한다. 쓰기가 이루어지면 c:\text.txt 파일은 바이너리 파일이 된다.
배열을 파일에 작성하는 경우를 생각해보자. write() 메소드를 이용해 다음과 같이 한 바이트 씩 배열 데이터를 저장할 수 있다.
byte a[] = {5, 4, 3, 2, 1};
for(int i = 0; i < a.length; i++){
fout.write(a[i]);
}
//또는
fout.write(a);
데이터 작성 후 스트림을 닫으려면 다음과 같이 작성한다.
fout.close();
FileOutputStream 클래스의 flush() 함수 호출 시 출력 스트림에 남아있는 바이너리 데이터를 모두 출력한다.
FileInputStream은 바이트 스트림으로 파일을 읽는 클래스이다. 다음과 같이 작성하면 파일과 연결되어 파일로부터 바이너리 값을 읽어들이는 바이트 스트림이 생성된다.
FileInputStream fin = new FileInputStream("c:\\text.txt");
c:\text.txt 파일을 찾아 열고 파일을 연결한 스트림 fin을 생성한다.
read() 메소드는 파일 스트림으로부터 한 바이트를 읽어 반환한다. 다음은 파일에 저장된 바이트들을 읽어 배열에 저장하는 예시이다.
byte a[] = new byte [6];
int i = 0, c;
while((c = fin.read()) != -1){
a[i] = (byte)c;
i++;
}
//또는
fin.read(a);
스트림의 사용이 끝난 경우 close() 메소드를 호출해 스트림을 닫으면 된다.
fin.close();
available() 메소드는 입력 스트림에서 현재 읽을 수 있는 바이트의 수를 리턴한다.
입출력 스트림이 입출력 장치와 프로그램 사이에서 데이터를 전송하는 경우 운영체제 API를 호출한다. 운영체제 API가 호출되는 경우 하드 디스크, 네트워크 장치가 작동되어 시스템 효율이 떨어지고 입출력이 잦아진다. 따라서 스트림에 버퍼를 두고 일시적으로 데이터를 저장한 뒤 한 번에 파일에 입출력한다면 운영체제 API의 호출 빈도를 줄일 수 있어 효율이 올라간다.
버퍼를 가진 스트림을 버퍼 스트림이라 부르며 버퍼 입력 스트림은 입력 장치로부터 입력된 데이터를 버퍼에 모아 한 번에 프로그램으로 입력하고 버퍼 출력 스트림은 프로그램에서 출력한 데이터를 버퍼에 모아 한 번에 출력 장치에 출력한다.
버퍼 스트림 또한 바이트 버퍼 스트림과 문자 버퍼 스트림으로 나뉜다. 바이트 버퍼 스트림의 경우 BufferedInputStream/BufferedOuputStream의 입출력 클래스를 가지며 문자 버퍼 스트림의 경우 BufferedReader/BufferedWriter 입출력 클래스를 가진다.
버퍼 스트림 클래스의 생성자에 스트림과 정수를 전달하면 해당 바이트 수의 크기를 가진 버퍼 스트림이 생성된다. 정수는 생략 가능하며 크기를 기재하지 않은 경우 디폴트 크기로 지정된다.
화면에 출력을 하기 위한 버퍼 출력 스트림을 생성하려면 다음과 같이 작성한다.
BufferedOutputStream bout = new BufferedOutputStream(System.out, 20);
표준 출력 스트림 System.out에 연결하여 화면에 출력하는 20바이트 크기의 버퍼를 가진 버퍼 스트림을 생성한다.
파일을 읽어 버퍼 출력 스트림을 통해 화면에 출력하려면 다음고 같이 작성한다.
FileReader fin = new FileReader("c:\\windows\\system.ini");
int c;
while((c = fin.read()) != -1){
bout.write((char)c);
}
파일의 끝을 만날 때까지 문자를 하나씩 읽어 버퍼 출력 스트림에 작성한다. 출력 스트림과 연결된 화면에 출력된다.
버퍼 스트림은 버퍼가 가득 찬 경우에만 출력된다. 버퍼가 가득 차지 않은 상태에서 버퍼의 데이터를 출력 장치로 내보내려면 flush() 메소드를 사용한다.
버퍼 스트림의 사용을 마친 후 close() 메소드를 이용해 닫아주어야 한다.
bout.close(); //버퍼 스트림 닫기
fin.close(); //파일 입력 스트림 닫기
File 클래스는 파일, 디렉토리에 대한 속성 정보를 제공하고 파일 삭제, 디렉토리 생성, 파일 이름 변경 등 파일 관리 작업을 지원한다. File 클래스의 경로명은 java.io.File이다. File 클래스에는 파일 입출력 기능이 없으므로 FileInputStream/FileOutputStream, FileReader/FileWriter 파일 입출력 클래스를 이용해야 한다.
File 클래스의 생성자를 이용해 객체를 생성한다. c:\Temp\test.txt 파일의 File 객체는 다음과 같은 방법으로 생성할 수 있다.
File f = new File("c:\\Temp\\test.txt");
//또는
File f = new File("c:\\Temp", "test.txt"); //디렉토리와 파일명을 나누어 전달
length() 메소드는 파일이나 디렉토리의 크기를 리턴한다.
getName()은 파일명, getPath()는 완전경로명, getParent()은 부모 디렉토리를 문자열로 리턴한다.
isFile()와 isDirectory()는 경로명이 파일인지 디렉토리인지에 따라 true/false를 리턴한다.
list()는 파일과 서브 디렉토리 경로명을 문자열 배열로 리턴하며 listFiles는 파일과 서브 디렉토리 경로명을 File[] 배열로 리턴한다.