지난 글 : [프로그래밍 언어/JAVA] - JAVA 입문 - 표준 입출력
이제 다양한 스트림의 종류와 사용법에 대해 알아보자. 이번 글에서 배우는 스트림은 입출력 기능을 구현하는 데 기본으로 알아야 하는 클래스와 메서드라고 한다. 우선 바이트 입출력 스트림부터 알아보자.
InputStream
바이트 단위로 읽는 스트림 중 최상위 스트림이다. InputStream은 추상 메서드를 포함한 추상 클래스로서 하위 스트림 클래스가 상속받아 각 클래스 역할에 맞게 추상 메서드 기능을 구현한다. 주로 사용하는 하위 클래스는 아래와 같다.
InputStream은 바이트 자료를 읽기 위해 아래 메서드를 제공한다.
read( ) 메서드의 반환형은 int다. 한 바이트를 읽어서 int에 저장한다. 한 바이트만 읽는 데 반환형이 int인 이유는 더 이상 읽어 들일 자료가 없는 경우에 정수 -1이 반환되기 때문이다. 파일에서 자료를 읽는 경우 파일의 끝에 도달하면 -1이 반환된다.
FileInputStream
FileInputStream은 InputStream 중 가장 많이 사용하며 파일에서 바이트 단위로 자료를 읽어 들일 때 사용하는 스트림 클래스다. 스트림을 사용하기 위해서 먼저 스트림 클래스를 생성해야 한다. FileInputStream의 생성자는 아래와 같다.
FileInputStream(String name) 생성자로 스트림을 생성하여 파일로부터 자료를 읽을 것이다. 코드는 아래와 같다.
이 코드는 첫 예제이므로 모든 예외 처리를 try-catch문으로 구현했다. 11행에서 FileInputStream("input.txt") 생성자로 input.txt 파일에 입력 스트림을 생성하려하나 input.txt 파일은 아직 존재하지 않는 상태다. FileInputStream은 읽으려는 파일이 없으면 FileNotFoundException 예외가 발생한다. 하여 11행을 수행하려다 FileNotFoundException의 상위 예외 클래스 IOException이 발생해 15행에서 catch된다. 그리고 finally 블록에서 열려있는 스트림을 닫기 위해 close( )를 호출하는데, 스트림이 생성되지 않았으므로 NullPointerException이 발생한다. NullPointerException은 처리하지 않을 때 컴파일 오류가 발생하는 예외가 아니므로 어떤 예외 클래스로 처리해야 할지 잘 모르는 경우 최상위 예외 클래스 Exception을 사용하면 된다. 수행 결과에서 알 수 있듯 예외 처리가 되어 프로그램이 중단되거나 멈춘 것이 아니라 end부분까지 출력되었다. 프로그램 수행을 중단시키지 않는 예외 처리의 중요성을 알 수 있다.
파일에서 자료 읽기
이제 실제 파일에서 자료를 읽어보자. 먼저 임의로 input.txt 파일을 만들어준다. 본 작성자는 책에 나와있는 대로 chapter15 프로젝트 파일을 생성 후 프로젝트 파일에 input.txt 파일을 생성했다.
input.txt 파일을 직접 열고 아래와 같이 작성한 후 저장한다.
ABC 세 개의 문자를 적었다. 이제 FileInputStreamTest1.java를 다시 실행해 출력 결과를 보자.
input.txt에 적혀 있는 ABC 세 개를 읽어 들여 바로 출력하니 알파벳의 아스키 코드 값이 적혀있다. System.out의 read( ) 메서드는 한 바이트씩 자료를 읽어 들이기 때문이다. 이를 A, B, C로 화면에 출력하려면 아래처럼 char 자료형으로 변환하면 된다.
파일 끝까지 읽기
바로 앞 코드에서는 input.txt에 문자가 세 개 포함된 것을 알고 있기에 read( ) 메서드를 세 번 호출해 파일에서 문자을 읽어 들였다. 하지만 파일에 내용이 얼마나 있는지 모르는 경우에는 파일의 끝에 도달할 때까지 반복해 읽어야 한다. 아래는 input.txt 파일을 끝까지 읽는 방식으로 FileInputStreamTest1.java를 바꾼 코드다. 이 코드는 try-with-resource문을 사용해 구현했다.
int read(byte[ ] b) 메서드로 읽기
자료를 read( ) 메서드로 한 바이트씩 읽는 것보다 배열을 사용해 한 번에 많이 읽으면 처리 속도가 빠르다. read(byte[ ] b) 메서드는 선언한 바이트 배열의 크기만큼 한 번에 자료를 읽고 읽어 들인 자료의 수를 반환한다. 그럼 바이트 배열을 생성하고 배열을 사용해 자료를 읽어보자. 이전 input.txt 파일과 유사하게 input2.txt 파일을 만들고 A~Z까지 알파벳을 적는다. 실제로는 더 큰 배열을 사용한다고 한다. 하지만 테스트(공부)를 위해 10바이트 크기 배열을 만들어 사용한다.
9행에서 크기 10인 바이트 배열을 생성하고 12행의 파일을 읽어 들이는 부분에 배열 bs를 매개변수로 넣는다. 그리고 읽어들인 반환 값이 -1이 아닐 때까지(파일의 끝에 도달할 때까지) 읽는다. 13행 for문을 사용해 bs 배열에 들어 있는 자료를 출력하고 몇 바이트 읽었는지 출력한다.
배열 크기는 10이고 26개 알파벳을 읽으므로 반복할 때마다 읽는 알파벳 개수는 10, 10, 6이다. 근데 출력 화면을 보면 마지막에 6바이트를 읽었는데 출력 값이 Z 이후 QRST가 더 출력되었다. 이유는 bs 배열을 보면 두 번째 읽을 때 K~T까지 10개 알파벳을 저장했다. 그리고 마지막으로 U~Z까지 저장할 때 새로 읽어 들인 6개 외에 남은 4개 공간에는 기존 자료가 남아 있다. 따라서 6개만 읽었는데 bs 전체를 출력하면 위와 같이 출력되는 것이다.
read(byte[ ] b) 메서드의 반환 값은 읽어 들인 자료의 바이트 수이다. 이를 사용하여 전체 배열을 출력하는 것이 아닌 바이트 수만큼, 즉 i 개수만큼 출력하도록 코드를 바꿔보자.
이처럼 메서드의 반환 값은 프로그래밍할 때 유용하게 쓰인다.
OutputStream
바이트 단위로 쓰는 스트림 중 최상위 스트림이다. 자료의 출력 대상에 따라 다른 스트림을 제공한다.
OutputStream에서 제공하는 메서드는 아래와 같다.
OutputStream을 상속받은 클래스 중 가장 많이 사용하는 FileOutputStream을 활용하는 코드를 구현해보자.
FileOutputStream
파일에 바이트 단위 자료를 출력하기 위해 사용하는 스트림이다. FileOutputStream을 생성하는 생성자는 아래와 같다.
생성자 매개변수로 전달한 파일이 경로에 없으면 FileOutputStream은 파일을 새로 생성한다. FileOutputStream을 사용해 파일에 자료를 쓸 때 기존 파일의 내용이 있더라도 처음부터 새로 쓸지(overwrite), 아니면 기존 내용 맨 뒤에 연결해서 쓸지(append) 여부를 FileOutputStream 생성자의 매개변수로 전달한다. 이 값이 append 변수다. 스트림 생성자에서 append 값은 디폴트가 false다. 기존에 쓰여 있는 내용이 있더라도 새로 쓴다. 기존 파일 내용에 이어서 써야 한다면 append 값을 반드시 true로 지정한다.
write( ) 메서드 사용하기
아래는 FileOutputStream을 생성하고 write( ) 메서드를 활용해 파일에 정수 값을 저장하는 코드다.
8행에서 output.txt 파일 이름으로 FileOutputStream을 생성한다. write( ) 메서드에 따라 파일에 값을 출력하고 스트림을 닫는다. 이클립스에서 현재 프로젝트(chapter15)를 선택하고 [refresh] 메뉴를 누르면 생성된 output.txt 파일이 보인다.
생성된 파일을 열면 아래와 같다.
FileOutputStream은 숫자를 해당 아스키 코드 값의 문자로 변환하여 저장한다.
위에서 실행한 FileOutputStreamTest를 한 번 더 실행하고 output.txt 파일을 보면 출력 결과가 이전과 같다. 기존의 ABC가 없어지고 새로운 ABC가 생긴 것이다. 만약 기존 자료에 이어서 출력하려면 생성자의 두 번째 매개변수에 true라고 쓴다.
write(byte[ ] b) 메서드 사용하기
출력도 입력과 마찬가지로 여러 자료를 한 번에 출력하면 효율적이고 실행 시간도 줄어든다. 따라서 바이트 배열을 활용해 출력할 수 있다. write(byte[ ] b) 메서드는 바이트 배열에 있는 자료를 한 번에 출력한다. 아래 코드를 살펴보자.
책에서는 자바 9부터 제공되는 향상된 try-with-resource문을 활용해 FileOutputStream("output2.txt", true) 생성자로 생성한 fos를 try( ) 안에 넣었지만 본 작성자는 자바 8 환경에서 학습 중이므로 위와 같이 작성했다.
위와 같은 방법으로 해당 프로젝트의 [refresh] 메뉴를 누르면 output2.txt 파일이 보인다. 이를 확인해보면 아래와 같다.
바이트 배열을 사용해 파일 출력 스트림을 생성할 때도 생성자의 두 번째 매개변수에 true를 쓰면 이미 쓰인 자료에 연결되어 출력된다.
write(byte[ ] b, int off, int len) 메서드 사용하기
write(byte[ ] b, int off, int len) 메서드는 배열의 전체 자료를 출력하지 않고 배열의 off 위치부터 len 길이만큼 출력한다. 예로 위에서 만든 bs 배열을 사용한다 할 때 write(bs, 2, 10)이라고 쓰면 bs의 두 번째 인덱스(세 번째 위치)부터 10개 바이트 자료만 출력한다. 배열 자료 중 일부를 출력할 때 사용할 수 있다. 아래 코드를 보자.
출력 파일 output3.txt를 확인하면 아래와 같다.
배열에 저장된 자료 중 세 번째 위치에 있는 C부터 L까지 10개 바이트가 출력되었다.
flush( ) 메서드와 close( ) 메서드
출력 스트림에서 flush( ) 메서드의 기능은 강제로 자료를 출력하는 것이다. write( ) 메서드로 값을 썼다 해도 바로 파일이나 네트워크로 전송되지 않고 출력을 위한 자료가 쌓이는 출력 버퍼에 어느 정도 자료가 모여야 출력된다. 따라서 자료의 양이 출력할 만큼 많이 않으면 write( ) 메서드로 출력했어도 파일에 쓰이지 않거나 전송되지 않을 수 있다. 이런 경우 flush( ) 메서드를 호출한다. 출력 스트림의 close( ) 메서드 안에서 flush( ) 메서드를 호출하여 출력 버퍼가 비워지면서 남아 있는 자료가 모두 출력된다.
참고 서적 : 자바 프로그래밍 입문 - 박은종
'프로그래밍 언어 > JAVA' 카테고리의 다른 글
JAVA 입문 - 보조 스트림 (0) | 2022.06.18 |
---|---|
JAVA 입문 - 문자 단위 스트림 (0) | 2022.06.16 |
JAVA 입문 - 표준 입출력 (0) | 2022.06.14 |
JAVA 입문 - 자바 입출력과 스트림 (0) | 2022.06.13 |
JAVA 입문 - 사용자 정의 예외 (0) | 2022.06.12 |