7. 입출력 프로그래밍
이번 강좌에서는 입출력 프로그래밍의 주요 개념과 java.io 및 java.nio 패키지의 주요 클래스들을 사용한 기본적인 입출력 프로그램 개발방법을 배웁니다.
이 강의를 통해 입출력 프로그램이 무엇인지 이해하고 자바 에서 기본적인 입출력 프로그램을 구현할 수 있게 됩니다.
01: 자바 I/O 개요
1) 입출력 구현 사례
입출력은 컴퓨터 프로그램에 꼭 필요한 요소 입니다. 일반적으로 모든 프로그램은 어떠한 형태로든 데이터를 입력받고 처리하고 출력하는 구조로 되어 있습니다.
조이스틱이나 터치스크린을 통해 입력을 받고 프로그램에서 주인공을 움직이거나 아이템을 사용하도록 처리하고 결과를 그래픽 화면으로 보여주는 형태 입니다.
바코드리더기로 부터 상품의 바코드를 입력 받아 해당 상품을 조회하고 상품 정보를 화면에 보여줍니다.
입력과 출력은 컴퓨터와 연결된 장치(Device)를 통해 이루어지며 대표적인 입출력 장치는 다음과 같습니다.
입력장치
- 키보드, 마우스
- 파일
- 네트워크
- 조이스틱, 바코드리더 등 컴퓨터에 연결된 장치
- 컴퓨터에 연결된 체중계, 혈압계 등 측정 장치
출력장치
- 모니터, 프린터, 스피커
- 파일
- 네트워크
이러한 입출력 장치들은 컴퓨터와는 유선 혹은 무선으로 연결될 수 있습니다. 대표적인 유선 인터페이스는 USB 이며 전통적인 장치들은 시리얼 포트를 사용합니다. 무선의 경우 블루투스가 대표적이며 이들 장치를 컴퓨터에서 인식하고 사용가능하게 하는 것은 운영체제와 장치드라이버가 담당하는 것이고 프로그램에서 직접적으로 해당장치에 연결되는 프로그램을 작성할 필요가 없습니다.
자바 프로그램의 경우 입출력 장치의 종류와 상관없이 해당 장치와 연결되는 스트림만 확보한다면 동일한 방식으로 입출력 프로그램을 작성할 수 있습니다.
2) 자바 IO
자바에서는 앞에서 언급한것 처럼 특정 장치와의 연결을 직접 담당하지 않고 스트림을 통해 입출력을 처리 합니다. 기본적으로 java.io
패키지의 클래스들을 사용합니다. 전통적으로 자바의 기본 입출력 프로그래밍 방법이며 연결되는 클라이언트가 적고 대용량의 데이터를 순차적으로 처리하는 유형의 프로그램에 적합 합니다.
다음은 자바 스트림의 주요 특징 입니다.
- 스트림은 입출력 장치와 자바 프로그램간의 연결 통로임.
- 단방향 이므로 입력 스트림과 출력 스트림을 별도로 사용해야 함.
- 연속된 데이터의 흐름으로 입출력 진행시 다른작업을 할 수 없는 블로킹(Blocking)상태가 됨.
- 입출력 대상을 변경하기 편하며 동일한 프로그램 구조를 유지할 수 있음.
- 문자 스트림과 바이트 스트림으로 구분.
스트림은 데이터 전달 방식에 따라 바이트 스트림과 문자 스트림으로 나뉩니다.
바이트 스트림(Byte Stream)
- binary 데이터를 입출력하는 스트림.
- 데이터는 1바이트 단위로 처리됨.
- 이미지, 동영상등을 송수신할 때 주도 사용.
- 주요 라이브러리는 *InputStream, *OutputStream 형태의 클래스 이름을 사용.
문자 스트림(Character Stream)
- text 데이터를 입출력하는 스트림.
- 데이터는 2바이트 단위로 처리됨.
- 일반적인 텍스트 및 JSON, HTML 등을 송수신할 때 주로 사용.
- 주요 라이브러리는 *Reader, *Writer 형태의 클래스 이름을 사용.
보조 스트림
보조 스트림은 FilterInputStream
과 FilterOutputStream
을 상속받는 클래스들로 기본 스트림과 결합하여 특정 상황에서 보다 편리하게 사용할 수 있습니다.
BufferedInputStream/BufferedOutputStream
: 버퍼를 사용해 입출력 효율과 편의를 위해 사용BufferedReader/BufferedWriter
: 라인단위의 입출력이 편리함.InputStreamReader/OutputStreamReader
: 바이트 스트림을 문자 스트림처럼 쓸 수 있도록하며 문자 인코딩 변환을 지원함.DataInputStream/DataOutputStream
: 자바 원시자료형 데이터 처리에 적합.
다양한 스트림 클래스중 사용 목적에 맞는 적절한 클래스를 잘 선택해서 사용하는 것이 중요 합니다.
자바 프로그램에서 입출력 스트림을 생성하는 방법은 다음과 같습니다.
InputStream in = System.in;
OutputStream out = System.out;
int input = in.read();
out.write(input);
out.close();
- System 클래스 변수 in 과 out 은 각각 InputStream 과 OutputStream 클래스의 인스턴스로 키보드와 콘솔로 연결되어 있다.
- 입력이나 출력 대상을 바꾸려면 System.in/out 대신 파일 혹은 네트워크 연결을 통해 스트림 객체를 생성 하면 된다.
- 단, InputStream 이나 OutputStream 은 최상위 클래스로 제공되는 기능이 제한적이고 실제 사용에는 불편하므로 Buffered 계열을 사용하는 것이 좋다.
- 위 코드는 바이트 스트림이므로 숫자나 영문 입력 및 출력은 가능하지만 2바이트로 구성된 한글 입력은 제대로 처리할 수 없다.
- read() 메서드는 스트림으로 부터 1바이트만 읽어 오기 때문에 여러 데이터를 한번에 입력받기 위해서는 별도의 작업이 필요하다.
» 실습: 자바 스트림 기본 예제
실습개요
키보드와 콘솔을 사용한 기본 입출력 예제 입니다. 바이트 스트림의 기본적인 특징을 이해하기 위해 1바이트만 처리하는 경우와 여러 바이트를 처리하는 경우를 함께 살펴 봅니다.
소스코드
public class BasicIOTest {
public static void main(String[] args) {
InputStream in = System.in;
OutputStream out = System.out;
int input;
try {
System.out.print("## Input 1~3 character: ");
// input 1byte from keyboard
input = in.read();
System.out.println("## 1byte read and print");
System.out.println(input);
out.write(input);
//out.flush();
/*
// input 3byte from keyboard
byte[] b = new byte[3];
in.read(b);
System.out.println("## 3byte read and print");
out.write(b);
*/
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
실행결과
## Input 1~3 character: A
## 1byte read and print
65
A
## Input 1~3 character: 황
## 1byte read and print
237
�
## Input 1~3 character: ABC
## 3byte read and print
ABC
## Input 1~3 character: 홍
## 3byte read and print
홍
- 첫번째 실행은 기본 read() 메서드를 사용한 결과로 여러 글자를 입력해도 처음 하나만 출력
- 영문의 경우 System.out.prinltn()으로 출력시 아스키 코드 값이 출력됨
- 한글의 경우 출력되지 않음
- 두번째와 세번째 실행은 첫번째 실행 블럭을 주석처리하고 원래 주석처리된 코드의 주석을 제거하고 다시 실행
- 세글자 까지 입력 내용이 출력되며 한글의 경우에도 정상 출력됨(UTF-8 은 3바이트)
- 입력내용을 모두 처리하려면 반복해서 읽도록 해야 하므로 처리가 불편
3) 자바 NIO
스트림 기반의 자바 IO는 구조적으로 다소 느리며 블로킹 기반으로 동시 작업을 위해서는 스레드를 생성해야 하는데 대규모의 클라이언트 접속이 이루어지는 서비스에서는 스레드로 인한 부하가 발생해 성능에 문제가 발생하게 됩니다.
자바 NIO는 New IO 혹은 Non-blocking IO 라는 의미를 가지며 비동기 넌블로킹 처리가 가능 합니다. 스트림이 아닌 채널(Channel)을 사용하며 동시에 많은 접속이 이루어지며 단일 작업에 입출력 처리가 오래걸리지 않은 유형의 프로그램 개발에 적합 합니다.
버퍼(Buffer)
- 프로그램에서 관리하는 것이 아닌 OS 커널의 시스템 메모리를 직접 사용하는 Buffer 클래스
채널(Channel)
- 입출력이 동시에 가능한 양방향 입출력 클래스
- Native IO, Scatter/Gather(운영체제 최적화된 로직에 따라 버퍼 관리) 구현으로 효율적인 IO 처리
셀렉터(Selector)
- 네트워크 프로그래밍의 효율을 높여줌
- 클라이언트당 쓰레드 하나를 생성해야 하는 기존 IO를 개선하는 Reactor 패턴의 구현체
NIO에서 사용하는 채널은 다음과 같이 4종류가 있습니다.
- FileChannel: 파일 입출력을 위해 사용
- DatagramChannel: 네트워크를 통해 UDP 통신을 위해 사용
- SocketChannel: 네트워크를 통해 TCP 통신을 위해 사용
- ServerSocketChannel: 서버 연결을 지원하기 위해 사용
Path 를 이용해 파일을 다루는 방법은 다음과 같습니다.
Path path = Paths.get("/tmp/testfile.txt");
try(BufferedWriter writer = Files.newBufferedWriter(path, Charset.forName("UTF-8"))){
writer.write("To be, or not to be. That is the question.");
}catch(IOException ex){
ex.printStackTrace();
}
- Path 클래스를 사용해 파일에 대한 참조를 생성
- BufferedWriter 클래스를 이용해
FileChannel 을 이용하는 경우 다음과 같이 구현할 수 있습니다.
FileInputStream fis = new FileInputStream("c:/tmp/testfile.txt");
FileChannel fileChannel2 = fis.getChannel();
ByteBuffer buffer = ByteBuffer.allocateDirect(data.length());
Charset charset = Charset.forName("UTF-8");
buffer = charset.encode("Hello World");
int byteCnt = fileChannel.write(buffer);
fileChannel.close();
- FileInputStream 으로 파일 채널 생성
- 채널에 사용할 ByteBuffer 생성
- FileChannel.write()로 데이터 출력
파일 입출력과 관련된 보다 자세한 사항은 다음장에서 자세히 다루게 됩니다.
02: 파일 입출력
파일 입출력은 모든 입출력에서 가장 기본이 됩니다. 여기서는 자바에서 파일을 다루는 기본적인 사항들을 살펴보고 java.io 및 java.nio 패키지의 클래스들을 사용해 다양한 파일 입출력 프로그램 구현을 알아봅니다.
1) 파일과 디렉토리(File and Directory)
파일
파일은 컴퓨터에서 가장 기본이 되는 입출력 대상입니다.
- 파일은 디렉토리안에 존재할 수 있으며 일반적으로 확장자를 가짐.
- MS 오피스 프로그램의 경우 키보드나 마우스로 입력한 내용을 .pptx, .xlsx, docx 와 같은 확장자의 파일로 저장하고 파일로 부터 내용을 불러옴.
- 게임 프로그램의 경우 마지막 성공 스테이지 라든가 현재 게임의 진행상황등이 파일에 저장이 되고 게임을 시작할때 해당내용을 불러옴.
디렉토리
디렉토리는 파일들을 관리하기 위한 구조로 일종의 트리(Tree)형태로 구성된 방(room)의 개념 입니다.
- 유닉스 계열의 경우(Linux, MacOS, Android, iOS 등) 최상위 디렉토리는 보통 루트(root)디렉토리 라고 하며
/
로 표시. - 윈도우의 경우
C:\
가 루트가 됨. - 디렉토리는 폴더라고도 함.
권한관리
대부분의 운영체제는 파일마다 권한 설정을 통해 일반 사용자와 시스템 관리를 위한 root 사용자를 구분하고 있습니다. 물론 하나의 운영체제를 여러명이 동시에 사용하는 유닉스 계열에서는 파일의 권한 관리는 매우 중요 합니다.
- 기본적인 권한은 소유자, 그룹, 공개로 구성.
- 각 사용자 유형에 대해 r(읽기), w(쓰기), x(실행) 권한을 부여
- rwx r-x r-x 라는 권한이 있다면 소유자는 읽기,쓰기,실행이 모두 가능하고 나머지는 읽기와 실행만 가능.
- 시스템에서 관리하는 파일은 root 소유로 두어 일반 사용자의 접근을 제한.
랜덤 엑세스(Random Access)
입출력은 기본적으로 순차적으로 처리되는것을 원칙으로 합니다. 따라서 파일의 경우에도 순차적으로 내용에 접근하게 되는데 파일의 경우 내용이 고정되어 있기 때문에 중간에 있는 데이터만 필요한 경우 특정 위치를 바로 접근할 방법이 필요하게 됩니다.
- RandomAccessFile 이나 SeakableByteChannel 인터페이스를 구현한 클래스들을 사용.
- 일기와 쓰기가 함께 이루어지는 파일의 경우 정책 수립을 잘 해야 함.
텍스트 파일과 바이너리 파일
파일의 내용이 일반 텍스트로 이루어진 파일을 텍스트 파일이라고 하며 이진 형식으로 이루어진 파일을 바이너리 파일이라고 합니다. 입출력시 파일의 내용에 따라 처리가 달라지므로 주의해야 합니다.
- html, css, .java 등은 텍스트 파일.
- 텍스트 파일은 메모장등 간단한 편집 프로그램으로 내용 확인이 가능.
- .exe, .cmo, .dll, .zip 등은 바이너리 파일.
- 바이너리 파일은 내용을 바로 확인할 수 없으며 파일 규칙을 이해하는 프로그램에 의해 처리가 가능.
2) java.io 패키지를 이용한 파일 입출력
파일 자체는 File 클래스를 통해 핸들링 할 수 있으며 이를 통해 다양한 정보를 참조할 수 있습니다. File 클래스를 무조건 사용해야 하는 것은 아니며 FileReader 등의 입력 스트림을 통해서도 경로 기반으로 파일을 처리할 수 있습니다.
java.io.File
파일과 디렉토리 자체에 대한 정보를 제공하는 클래스로 파일이나 디렉토리 관리나 파일을 생성하는데 가장 기본이 되는 클래스 입니다.
기본적으로 제공하는 메서드 기능은 다음과 같습니다. java.nio.file 패키지에는 보다 다양한 파일 관련 클래스들이 있으며 향상된 기능을 제공하고 있습니다.
- 파일의 경로, 권한 관련 정보
- 하드 디스크의 용량 관련 정보
- 파일의 수정일자, 크기 등 정보
- java.nio.file.Path 로 변환
- 파일, 디렉토리의 생성, 삭제
API 사용에 대한 자세한 사항은 자바 API 문서를 참고하기 바랍니다.
기본 사용법은 다음과 같습니다.
Java IO 파일 입출력
텍스트 파일인 경우 문자 스트림 클래스들을 사용하면 되고 바이너리 파일인 경우 바이트스트림을 기본적으로 사용합니다. 또한 입출력 효율을 위해 Buffered 계열의 보조 스트림을 함께 사용하는 것이 좋습니다.
정해진 경로를 기반으로 파일을 생성하고 파일로 부터 입력 스트림과 출력 스트림을 확보한 다음 읽기/쓰기 작업을 수행하면 됩니다.
텍스트 파일의 경우 다음과 같이 코드를 작성할 수 있습니다.
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
String s;
while((s = br.readLine()) != null ) {
bw.write(s+"\n");
}
- 이클립스에서 실행시 특별히 경로를 지정하지 않으면 프로젝트 폴더를 기준으로 함.
- File 클래스를 사용하지 않고도 파일 생성 및 참조 가능.
이진 파일의 경우 바이트스트림을 사용해야 하며 다음과 같이 코드를 작성할 수 있습니다.
BufferedInputStream is = new BufferedInputStream(new FileInputStream("a.jpg"));
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream("b.jpg"));
byte[] buffer = new byte[16384];
while (is.read(buffer) != -1) {
os.write(buffer);
}
- 16KB 의 버퍼 크기로 파일 데이터를 읽어 처리
- 파일 내용이 매우 짧은 경우 readAllBytes 로 모든 내용을 바이트 배열로 읽어올 수 있음
» 실습: 자바 IO 활용 예제
실습개요
java.io 패키지의 클래스를 이용해 간단한 메모장 프로그램을 만들어 봅니다.
소스코드
public class BinaryFileTest {
public static void main(String[] args) {
try {
BufferedInputStream is = new BufferedInputStream(new FileInputStream("a.jpg"));
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream("b.jpg"));
byte[] buffer = new byte[16384];
while (is.read(buffer) != -1) {
os.write(buffer);
}
is.close();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
실행결과
- a.jpg 는 프로젝트 최상위 플더에 위치해 있어야 함. 별도 폴더 지정 가능(c:/Temp 등)
- 동일 파일이 복사되었는지 확인.
참고 자료
- 오라클 자바 홈페이지: http://java.oracle.com
- Introduction to Java Programming-IBM : https://www.ibm.com/developerworks
- Java Tutorial for Complete Beginners: https://www.udemy.com