Chapter 15 자바 입출력

Ruinak·2021년 6월 22일
2

Java

목록 보기
15/15
post-thumbnail

15-1 자바 입출력과 스트림

  • 입출력은 프로그램의 가장 기본 기능이지만, 외부 저장 장치나 네트워크와 연동해야 하기 때문에 장치에 따라 다르게 구현해야 함
  • 자바는 장치에 따라 독립적이고 효율적인 입출력 기능을 제공함

스트림이란?

  • 자바에서 모든 입출력은 스트림(stream)을 통해 이루어짐
  • 스트림이란 네트워크에서 유래된 용어로 자료 흐름이 물의 흐름과 같다는 의미에서 사용됨
  • 입출력 장치는 매우 다양하기 때문에 장치에 따라 입출력 부분을 일일이 다르게 구현을 하면 프로그램 호환성이 떨어질 수 밖에 없음
  • 위와 같은 문제를 해결하기 위해 자바는 입출력 장치와 무관하고 일관성 있게 프로그램을 구현할 수 있도록 일종의 가상 통로인 스트림을 제공하는 것임
  • 자료를 읽어들이려는 소스(source)와 자료를 쓰려는 대상(target)에 따라 각각 다른 스트림을 제공함
  • 자바에서 입출력 기능을 사용하는 곳은 파일 디스크, 키보드, 모니터, 메모리 입출력, 네트워크 등이 있음
    - 이러한 곳에서 일어나는 모든 입출력 기능을 스트림 클래스로 제공함
  • 자바에서 자료를 입출력하려면 여러 스트림 클래스에 대해 알아야 하지만, 구현 방식이 서로 비슷하므로 크게 걱정할 필요가 없음

입력 스트림과 출력 스트림

  • 어떤 대상으로부터 자료를 읽어 들일 때 사용하는 스트림이 입력 스트림임
    - 어떤 동영상을 재생하기 위해 동영상 파일에서 자료를 읽을 때 입력 스트림을 사용함
    - 편집 화면에 사용자가 쓴 글을 파일에 저장할 때는 출력 스트림을 사용함
  • 스트림은 단방향으로 자료가 이동하기 때문에 입력과 출력을 동시에 할 수 없음
  • 입력 자료의 이동이 출력 자료의 이동과 한 스트림에서 동시에 일어날 수 없기 때문임
  • 일방 통행 외길에 차가 양방향으로 다닐 수 없는 것에 비유할 수 있음
  • 어떤 스트림이 있다고 하면 그 스트림은 입력 스트림이거나 출력 스트림임
  • 스트림의 이름을 보면 입력용인지 출력용인지 알 수 있음
  • InputStream이나 Reader로 끝나는 이름의 클래스는 입력 스트림임
  • OutputStream이나 Writer로 끝나는 이름의 클래스는 출력 스트림임

바이트 단위 스트림과 문자 단위 스트림

  • 자바의 스트림은 바이트(byte) 단위로 자료의 입출력이 이루어짐
  • 그림, 동영상, 음악 파일 등 대부분 파일은 바이트 단위로 읽거나 쓰면 됨
  • 자바에서 하나의 문자를 나타내는 char형은 2바이트이기 때문에 1바이트만 읽으면 한글 같은 문자는 깨짐
  • 입출력 중 가장 많이 사용하는 자료인 문자를 위해 문자 스트림을 별도로 제공함
    - 읽어들이는 자료형에 따라 바이트용과 문자용 스트림이 있음
  • 스트림 클래스의 이름이 Stream으로 끝나는 경우는 바이트 단위를 처리하는 스트림임
  • Reader나 Writer로 끝나는 이름은 문자를 위한 스트림 클래스임

기반 스트림과 보조 스트림

  • 어떤 스트림이 자료를 직접 읽거나 쓰는 기능을 제공하는 스트림인가, 아니면 자료를 직접 읽거나 쓰는 기능은 없이 다른 스트림에 부가 기능을 제공하는가에 따라 기반 스트림과 보조 스트림으로 구분할 수 있음
  • 기반 스트림은 읽어 들일 곳(소스)이나 써야 할 곳(대상)에서 직접 읽고 쓸 수 있으며 입출력 대상에 직접 연결되어 생성되는 스트림임
  • 보조 스트림은 직접 읽고 쓰는 기능은 없어서, 항상 다른 스트림을 포함하여 생성됨
  • 대부분 기반 스트림이 소스나 대상의 이름을 가지고 있지만, 보조 스트림 중에도 이름만 봐서 바로 알 수 없는 경우도 있으므로 많이 사용하는 클래스 위주로 기억해 두면 됨
  • 하나의 스트림 클래스는 세가지 분류로 나눌 수 있음
  • FileInputStream을 살펴보면 InputStream이니 입력 스트림이고, Stream이니 바이트 단위로 처리하며 File에 직접 읽고 쓰는 기반 스트림임
    - 즉 자바 스트림 클래스 이름에서 스트림 특성을 유추할 수 있음

15-2 표준 입출력

  • 자바에서는 화면에 출력하고 입력받는 표준 입출력 클래스를 미리 정의해 둠
  • 이 클래스는 프로그램이 시작될 때 생성되므로 따로 만들 필요가 없음
  • 화면 출력을 위해 사용한 System.out은 표준 출력을 위한 객체임
  • 표준 입출력은 콘솔 화면에 입출력된다고 해서 콘솔 입출력이라고도 함
  • 표준 입출력을 위한 System 클래스가 가지는 세 개의 변수 : out, in, err
  • System.out은 표준 출력용, System.in은 표준 입력용 스트림임
  • 빨간색으로 오류 메시지를 출력할 때는 System.err를 사용함
  • out, in, err 모두 정적(static) 변수임
  • 지금까지 System 클래스를 생성하지 않고도 System.out을 사용할 수 있었던 이유는 out 변수가 System 클래스의 정적 변수이기 때문임

System.in으로 화면에서 문자 입력받기

예제 15-1 문자 하나를 입력받기

  • 프로그램을 실행하면 Console에서 입력을 받기 위해 커서가 기다리고 있음
  • A라고 알파벳을 쓰고 Enter키를 누르면 입력한 값이 변수 i에 들어감
  • i는 4바이트지만 System.in은 바이트 단위로 읽어 들이는 InputStream이므로 1바이트만 읽음
  • 읽어 들인 1바이트를 출력하면 문자에 대한 숫자 값, 즉 아스키 값을 출력함
  • 읽어 들일 때 사용한 InputStream의 read( ) 메서드는 한 바이트만을 읽어 들임

예제 15-2 문자 여러 개를 입력받기

  • while문에서 read( ) 메서드를 이용해 한 바이트씩 읽어 들임
  • Enter에 해당하는 '\n' 값이 입력될 때까지 반복 수행 됨
  • Enter가 입력되면 읽어 들인 내용을 화면에 출력함

그 외 입력 클래스

Scanner 클래스

  • Scanner 클래스는 java.util 패키지에 있는 입력 클래스임
  • Scanner 클래스는 문자뿐 아니라 정수, 실수 등 다른 자료형도 읽을 수 있음
  • 콘솔 화면뿐 아니라 파일이나 문자열을 생성자의 매개변수로 받아 자료를 읽어 올 수 있음
  • 여러 대상에서 자료를 읽는 Scanner 클래스의 생성자는 굉장히 다양함
  • Scanner scanner = new Scanner(System.in)처럼 사용하면 표준 입력으로부터 자료를 읽어 들이는 기능을 사용할 수 있음
  • Scanner 클래스는 System.in으로 입력받는 것보다 다양한 메서드를 활용할 수 있기 때문에 자주 사용하는 클래스임

예제 15-3 Scanner 테스트하기

  • 7행에서 표준 입력을 매개변수로 Scanner 클래스를 생성함
  • 이름과 직업은 문자열이므로 nextLine( ) 메서드로 입력 받고, 사번은 정수이므로 nextInt( ) 메서드를 사용함
  • 표준 입력 System.in을 사용하면 바이트 단위 자료만 처리할 수 있어 한글 같은 경우 보조 스트림을 추가로 사용해야 하는데, Scanner는 다양한 자료형을 입력할 수 있어 많이 활용함

Console 클래스

  • System.in을 사용하지 않고 간단히 콘솔 내용을 읽을 수 있는 Console클래스도 있음
  • 직접 콘솔 창에서 자료를 입력받을 때 이 클래스를 사용하는데 이클립스와는 연동되지 않음

예제 15-4 Console 테스트하기

  • cd 명령어를 이용해서 이동
  • 패키지의 상위폴더에서 패키지 이름까지 포함한 전체 클래스 이름을 써야함
  • readLine( ) 메서드를 사용해 이름과 직업을 문자열로 입력받고 realPassword( ) 메서드를 사용해 비밀번호를 char[ ] 배열로 입력받음
  • 비밀번호는 입력할때 화면에 나타나지 않음
  • Console 클래스에서는 Scanner와 마찬가지로 한글도 읽을 수 있음

15-3 바이트 단위 스트림

  • 다양한 스트림의 종류와 사용 방법에 대해 자세히 알아보겠음
  • 여기에서 설명하는 스트림은 입출력 기능을 구현하는데 기본으로 알아야 하는 클래스와 메서드임
  • 기본 사용법을 익히고 나중에 프로그램을 개발할 때 원하는 기능의 클래스를 잘 찾아서 사용할 수 있으면 됨(클래스를 모두 외워야 하는 건 아님)

InputStream

  • 바이트 단위로 읽는 스트림 중 최상위 스트림임
  • InputStream은 추상 메서드를 포함한 추상 클래스로서 하위 스트림 클래스가 상속받아 각 클래스 역할에 맞게 추상 메서드 기능을 구현함
  • InputStream의 하위 클래스

  • InputStream이 바이트 자료를 읽기 위해 제공하는 메서드

  • read( ) 메서드의 반환형은 int이며, 한 바이트를 읽어서 int에 저장함
  • 한 바이트만 읽는데 반환형이 int인 이유는 더 이상 읽어들일 자료가 없는 경우에 정수 -1이 반환되기 때문임
  • 파일에서 자료를 읽는 경우 파일의 끝에 도달하면 -1이 반환됨

FileInputStream

  • InputStream 중 가장 많이 사용하는 클래스
  • 파일에서 바이트 단위로 자료를 읽어 들일 때 사용하는 스트림 클래스
  • 스트림을 사용하기 위해서는 먼저 스트림 클래스를 생성해야 함
  • FileInputStream 클래스의 생성자

예제 15-5 FileInputStream 사용하기

  • 첫 예제이므로 모든 예외 처리를 try-catch문으로 구현함
  • try-catch문을 보면 파일 스트림을 생성하고 활용할 때 어떤 예외가 발생하고 어떻게 처리해야 하는지 알 수 있음
  • 11행에서 FileInputStream("input.txt") 생성자로 input.txt 파일에 입력 스트림을 생성하려고 하지만, 파일은 아직 존재하지 않는 상태임
  • FileInputStream은 읽으려는 파일이 없으면 FileNotFoundException 예외가 발생함
    - 따라서 11행을 수행하려다가 IOException(FileNotFoundException의 상위 예외 클래스)이 발생하여 15행에서 catch됨
  • finally 블럭에서 열려 있는 스트림을 닫기 위해 close( )를 호출하는데, 스트림이 생성되지 않았으므로 NullPointerException이 발생함
  • NullPointerException은 처리하지 않을 때 컴파일 오류가 발생하는 예외가 아니므로 어떤 예외 클래스로 처리해야 할지 잘 모르는 경우 최상위 예외 클래스 Exception을 사용하면 됨
  • 예외 처리가 되어 프로그램이 중단되거나 멈춘 것이 아니라 end 부분까지 출력되었다는 것은 프로그램 수행을 중단시키지 않는 예외 처리의 중요성을 알 수 있는 부분임

파일에서 자료 읽기

  • 프로젝트 우클릭 - New - File
  • 이름에 input.txt 입력하고 Finish
  • 내용으로 ABC를 작성하고 저장
  • FileInputStreamTest1 실행시 각 알파벳의 아스키 코드 값이 출력됨

파일 끝까지 읽기

  • 파일에 내용이 얼만큼 있는지 모르는 경우에는 파일의 끝에 도달할 때까지 반복해서 읽어야 함

예제 15-6 파일 끝까지 읽기

  • read( ) 메서드로 파일을 읽는 경우 파일의 끝에 도달하면 -1을 반환함
  • read( ) 메서드로 읽어 들여 저장한 i값이 -1이 아닌 한 while문이 계속 수행됨

int read(byte[ ] b) 메서드로 읽기

  • 자료를 read( ) 메서드로 한 바이트씩 읽는 것보다 배열을 사용하여 한꺼번에 많이 읽으면 처리 속도가 훨씬 빠름
  • int read(byte[ ] b) 메서드는 선언한 바이트 배열의 크기만큼 한꺼번에 자료를 읽고, 읽어 들인 자료의 수를 반환함

예제 15-7 byte 배열로 읽기

  • 9행에서 크기 10인 바이트 배열을 생성하고 12행의 파일을 읽어 들이는 부분에 배열 bs를 매개 변수로 넣음
  • 읽어들인 반환 값이 -1일 때까지, 즉 파일의 끝에 도달할 때까지 읽음
  • 13행 향상된 for문을 사용하여 bs 배열에 들어 있는 자료를 출력하고 몇 바이트를 읽었는지 출력함
  • 배열 크기는 10이고 26개 알파벳을 읽으므로 반복할 때마다 읽는 알파벳 갯수는 10, 10, 6임
  • 출력 화면을 보면 마지막에 6바이트를 읽었지만 출력 값은 Z이후에 QRST가 더 출력됨
    - 기본 자료가 남아 있어서 출력됨
  • 전체 배열을 출력하는 것이 아닌 바이트 수만큼 출력하도록 코드를 바꾸면 남아 있는 자료 없이 출력됨

OutputStream

  • 바이트 단위로 쓰는 스트림 중 최상위 스트림임
  • 자료의 출력 대상에 따라 다른 스트림을 제공함

  • OutputStream에서 제공하는 메서드

FileOutputStream

  • 파일에 바이트 단위 자료를 출력하기 위해 사용하는 스트림
  • FileOutputStream을 생성하는 생성자

  • 생성자 매개변수로 전달할 파일이 경로에 없으면 FileOutputStream은 파일을 새로 생성함
  • FileOutputStream을 사용해 파일에 자료를 쓸 때 기존 파일의 내용이 있더라로 처음부터 새로 쓸지(overwrite), 아니면 기존 내용 맨 뒤에 연결해서 쓸 것인지(append) 여부를 FileOutputStream 생성자의 매개변수로 전달함
  • 스트림 생성자에서 append 값은 디폴트가 false이므로, 기존 파일 내용에 이어서 써야 한다면 append 값을 반드시 true로 지정해야 함

write( ) 메서드 사용하기

예제 15-8 파일에 한 바이트씩 출력하기

  • 8행에서 output.txt 파일 이름으로 FileOutputStream을 생성함
  • write( ) 메서드에 따라 파일에 값을 출력하고(쓰고) 스트림을 닫음
  • 새로 생성된 output.txt 파일을 볼 수 있음
  • 숫자 65, 66, 67에 해당하는 문자 A, B, C가 쓰여져 있음
  • FileOutputStream은 숫자를 해당 아스키 코드 값의 문자로 변환하여 저장함
  • FileOutputStreamTest를 한 번 더 실행해도 output.txt는 변화하지 않음
  • 생성자의 두 번째 매개변수에 true를 쓰고 실행을 해보면
  • ABC가 이어서 쓰이는 것을 알 수 있음

write(byte[ ] b) 메서드 사용하기

  • 출력도 입력과 마찬가지로 여러 자료를 한꺼번에 출력하면 효율적일뿐더러 실행 시간도 줄어듬
  • write(byte[ ] b) 메서드는 바이트 배열에 있는 자료를 한꺼번에 출력함

예제 15-9 파일에 바이트 배열로 출력하기(1)

write(byte[ ] b, int off, int len) 메서드 사용하기

  • write(byte[ ] b, int off, int len) 메서드는 배열의 전체 자료를 출력하지 않고 배열의 off 위치부터 len 길이만큼 출력함
  • 예로 앞 예제에서 만든 bs를 사용한다고 할 때 writ(bs, 2, 10)이라고 쓰면 bs 배열의 두 번째 인덱스, 즉 세번째 위치부터 10개 바이트 자료만 출력함
    - 즉 배열 자료 중 일부를 출력할 때 사용할 수 있음

예제 15-10 파일에 바이트 배열로 출력하기(2)

  • FileOutputTest2 예제에서 write( ) 메서드만 바꿈
  • fos.write(bs, 2, 10)를 사용하여 두 번째 인덱스(세 번째 위치)부터 10개를 출력함

flush( ) 메서드와 close( ) 메서드

  • 출력 스트림에서 flush( ) 메서드의 기능은 강제로 자료를 출력하는 것임
  • write( ) 메서드로 값을 썻다고 해도 바로 파일이나 네트워크로 전송되지 않고 출력을 위한 자료가 쌓이는 출력 버퍼에 어느 정도 자료가 모여야 출력됨
    - 자료의 양이 출력할 만큼 많지 않으면 write( ) 메서드로 출력했어도 파일에 쓰이지 않거나 전송되지 않을 수 있음
    - 이런 경우에 flush( ) 메서드를 호출함
  • 출력 스트림의 close( ) 메서드 안에서 flush ( ) 메서드를 호출하여 출력 버퍼가 비워지면서 남아 있는 자료가 모두 출력됨

바로바로 전송할 채팅 메세지 같은 경우는 flush( ) 메서드를 호출하는 것이 좋습니다.

15-4 문자 단위 스트림

Reader

  • 문자 단위로 읽는 스트림 중 최상위 스트림으로 주로 사용하는 하위 클래스

  • 자료를 읽는 메서드를 제공

FileReader

  • FileReader를 생성하는데 사용하는 생성자

  • FileInputStream과 마찬가지로 읽으려는 파일이 없으면 FileNotFoundException이 발생함
  • 문자를 입출력할 때는 문자 스트림을 사용해야함

예제 15-11 FileReader로 읽기

  • 문자 스트림 FileReader로 읽으면 한글이 제대로 읽히는 것을 알 수 있음
  • Reader 클래스는 문자를 처리할 때 사용하는 클래스임
  • read(char[ ] buf) 메서드와 read(char[ ] buf, int off, int len) 메서드의 내용은 FileInputStream과 유사함

Writer

  • 문자 단위로 출력하는 스트림 중 최상위 스트림으로 주로 사용하는 하위 클래스

  • 자료를 읽는 메서드를 제공

FileWriter

  • 다른 스트림 클래스와 마찬가지로 생성자를 사용하여 스트림을 생성함
  • FileOutputStream과 마찬가지로 출력 파일이 존재하지 않으면 파일을 생성함
  • FileWriter를 생성하는데 사용하는 생성자

예제 15-12 FileWriter로 쓰기

15-5 보조 스트림

보조 스트림이란?

  • 보조 스트림은 입출력 대상이 되는 파일이나 네트워크에 직접 쓰거나 읽는 기능은 없음
    - 말 그대로 보조 기능을 추가하는 스트림임
    - 보조 기능은 여러 스트림에 추가할 수 있음
  • 예시
    - 에스프레소에 우유를 넣으면 카페라떼가 됨
    - 에스프레소에 모카시럽을 넣으면 모카 커피가 됨
    - 모카 커피에 휘핑크림을 올리면 휘핑크림이 올라간 모카 커피가 됨
  • 우유, 모카 시럽, 휘핑크림은 커피가 아니며 커피의 맛을 좋게 만드는 보조 요소임

  • 자바의 기반 스트림은 커피, 보조 스트림은 우유, 모카 시럽, 휘핑 크림과 비슷한 개념으로 이해할 수 있음

  • 보조 스트림은 다른 말로 Wrapper 스트림이라고도 함
    - 다른 스트림을 감싸고 있다는 의미임
    - 스스로는 입출력 기능이 없기 때문에 생성장의 매개변수로 다른 스트림을 받게되면 자신이 감싸고 있는 스트림이 읽거나 쓰는 기능을 수행할 때 보조 기능을 추가함

FilterInputStream과 FilterOutputStream

  • FilterInputStream과 FilterOutputStream은 보조 스트림의 상위 클래스임
  • 모든 보조 스트림은 FilterInputStream이나 FilterOutputStream을 상속받음
  • 보조 스트림은 자료 입출력을 직접 할 수 없기 때문에 다른 기반 스트림을 포함함
  • FilterInputStream과 FilterOutputStream의 생성자

  • 두 클래스 모두 다른 생성자는 제공하지 않음
  • FilterInputStream과 FilterOutputStream를 상속받은 보조 클래스도 상위 클래스에 디폴트 생성자가 없으므로 다른 스트림을 매개변수로 받아 상위 클래스를 호출해야 함
  • FilterInputStream과 FilterOutputStream을 직접 생성하여 사용하는 경우는 거의 없고 이를 상속한 하위 클래스를 프로그램에서 많이 사용함
  • 보조 스트림의 생성자에 항상 기반 스트림만 매개변수로 전달되는 것은 아님
    - 때로는 또 다른 보조 스트림을 매개변수로 전달받을 수도 있으며, 이때 전달되는 또 다른 보조 스트림은 내부적으로 기반 스트림을 포함하고 있음

InputStreamReader와 OutputStreamWriter

  • 바이트 단위로 자료를 읽으면 한글 같은 문자는 깨지므로, 문자는 Reader나 Writer에서 상속받은 스트림을 사용해서 자료를 읽거나 써야함
  • 하지만 바이트 자료만 입력되는 스트림도 있음
    - 대표적으로 표준 입출력 System.in 스트림이 해당됨
    - 네트워크에서 소켓이나 인터넷이 연결되었을 때 읽거나 쓰는 스트림은 바이트 단위인 InputStream과 OutputStream임
  • 이렇게 생성된 바이트 스트림을 문자로 변환해주는 보조 스트림이 InputStreamReader와 OutputStreamWriter임
  • 보조 스트림은 입출력 기능이 없으므로 다른 입출력 스트림을 포함함
  • InputStreamReader의 생성자

  • InputStreamReader 생성자의 매개변수로 바이트 스트림과 문자 세트를 매개변수로 지정할 수 있음
  • 문자 세트란 문자를 표현하는 인코딩 방식임
  • 바이트 자료가 문자로 변환될 때 지정된 문자 세트가 적용됨
  • 적용할 문자 세트를 명시하지 않으면 시스템이 기본으로 사용하는 문자세트가 적용됨

문자 세트는 각 문자가 가지는 고유 값이 어떤 값으로 이루졌는가에 따라 다릅니다. 대표적으로 자바에서 사용하는 UTF-16 문자 세트가 있는데 이는 유니코드를 나타내는 문자 세트 중 하나입니다.

  • InputStreamReader의 모든 생성자는 InputStream, 즉 바이트 단위로 읽어 들이는 스트림을 매개변수로 받음
  • 생성자에서 매개변수로 받은 InputStream이 자료를 읽으면 InputStreamReader가 읽은 바이트 자료를 변환해줌

예제 15-13 InputStreamReader 사용하기

  • 9행을 보면 InputStreamReader(보조 스트림)이 FileInputStream(기반 스트림)을 매개변수로 받아 생성됨
  • FileInputStream은 바이트 단위로 자료를 읽기 때문에 reader.txt에 쓰여 있는 한글 '안녕하세요'를 읽을 수 없음
  • InputStreamReader는 파일 스트림이 바이트 단위로 읽어 들인 내용을 문자로 변환해 주는 역할을 함
  • 사실 파일에서 문자를 읽는 경우는 위와 같이 InputStreamReader으로 변환할 필요 없이 FileReader로 바로 읽으면 됨
  • 표준 입출력 스트림 System.in과 System.out은 모두 바이트 스트림임
  • System.in은 콘솔 화면에서 한글을 읽으려면 InputStreamReader를 사용해야 함
  • Scanner 클래스는 이런 변환이 필요 없어 콘솔 입력에 많이 쓰임
  • 네트워크에서 쓰이는 클래스는 스트림을 생성하면 InputStream이나 OutputStream으로 생성됨

Buffered 스트림

  • 입출력이 한 바이트나 문자 단위로 이루어지면 그만큼 프로그램 수행 속도가 느려집니다.
  • Buffered 스트림은 내부적으로 8,192바이트 크기의 배열을 가지고 있으며 이미 생성된 스트림에 배열 기능을 추가해 더 빠르게 입출력을 실행할 수 있는 버퍼링 기능을 제공함
    - 한 바이트나 한 문자 단위로 처리할 때보다 훨씬 빠르게 처리할 수 있음
  • 버퍼링 기능을 제공하는 스트림 클래스

  • 버퍼링 기능을 제공하는 스트림 역시 보조 스트림으로 다른 스트림을 포함하여 수행됨
  • BufferedInputStream의 생성자

  • BufferedInputStream은 보조 스트림이므로 생성자의 매개변수로 다른 InputStream을 가져야 함
  • BufferedOutputStream은 OutputStream을, BufferedReader는 Reader를, BufferedWriter는 Writer 클래스를 생성자의 매개변수로 받음
  • Buffered 스트림이 포함할 스트림이 입력 스트림인지 출력 스트림인지, 문자용인지 바이트용인지에 따라 그에 맞는 스트림을 사용함

예제 15-14 파일 복사하기

  • 콘솔을 보면 전체를 복사하는데 걸리는 시간은 79초임
  • FileInputStream은 바이트 단위로 자료를 읽는 스트림으로 한 바이트를 읽어서 변수 i에 저장하면 그 값을 다시 FileOutputStream을 통해 저장함
  • 한 바이트씩 읽고 쓰는 과정의 시간이 여러 바이트를 한꺼번에 읽고 쓰는 것보다 당연히 오래 걸림

예제 15-15 파일 복사하기(보조 스트림 Buffered 사용)

  • 콘솔을 보면 전체를 복사하는데 걸리는 시간은 0.17초임
  • 속도가 매우 빠름을 알 수 있음
  • Buffered 스트림은 멤버변수로 8,192 바이트 배열을 가지고 있음
    - 즉 한 번 자료를 읽을 때 8KB 정보를 한꺼번에 읽고 쓸 수 있으므로 1바이트씩 읽고 쓸 떄보다 훨씬 빠른 수행을 보장함
  • 배열 크기는 Buffered 스트림 생성자 매개변수로 지정할 수도 있음

DataInpuStream과 DataOutputStream

  • 지금까지 살펴본 스트림은 사람이 읽고 쓰는 텍스트 형식의 자료를 다루었음
  • DataInpuStream과 DataOutputStream은 메모리에 저장된 0, 1 상태를 그대로 읽거나 씀
  • DataInpuStream과 DataOutputStream의 생성자

  • DataInpuStream의 각 자료형별 메서드로 자료형에 읽거나 쓸 때 사용할 수 있음

  • DataOutpuStream의 각 자료형별로 read( )에 대응되는 write( ) 메서드를 제공함

  • 자료형을 그대로 읽고 쓰는 스트림이기 때문에 같은 정수라도 자료형에 따라 다르게 처리함
    - 즉 writeByte(100)은 1바이트로 쓰인 100을 의미하지만, writeInt(100)은 4바이트로 쓰인 100을 의미함
  • 자료를 쓸 때 사용한 메서드와 같은 자료형의 메서드로 읽어야 함
    - 쓸때는 writeInt를 쓰고 readByte로 읽으면 서로 사용한 메모리 크기가 달라서 같은 값을 가져올 수 없음
    - 파일이든 네트워크든 자료를 쓸 때 사용한 메서드 순서대로 읽어야 함

예제 15-16 DataInputStream / DateOutputStream 테스트하기

  • 파일 스트림을 만들고 여기에 DataInputStream과 DateOutputStream 기능을 추가함
  • 기반 스트림에서 쓸 수 없던 각 자료형의 자료를 그대로 읽고 쓸 수 있음
  • 파일에 쓴 것과 동일한 순서, 동일한 메서드로 읽어야 함

15-6 직렬화

직렬화와 역직렬화

  • 클래스의 인스턴스가 생성되면 인스턴스의 상태, 즉 인스턴스 변수 값은 마치 생명체처럼 계속 변하게 됨
  • 인스턴스의 어느 순간 상태를 그대로 저장하거나 네트워크를 통해 전송할 일이 있을 수 있는데 이를 직렬화(serialization)이라고 함
  • 저장된 내용이나 전송받은 내용을 다시 복원하는 것을 역직렬화(deserialization)이라고 함
  • 직렬화란 인스턴스 내용을 연속 스트림으로 만드는 것임
  • 스트림으로 만들어야 파일에 쓸 수도 있고 네트워크로 전송할 수도 있음
  • 직렬화 과정에서 하는 일은 인스턴스 변수 값을 스트림으로 만드는 것임
  • 복잡한 과정일 수 있지만 보조스트림인 ObjectInputStream과 ObjectOutputStream을 사용하여 좀 더 쉽게 구현할 수 있음
  • ObjectInputStream과 ObjectOutputStream의 생성자

  • 저장할 파일이나 전송할 네트워크 등의 기반 스트림을 매개변수로 받아서 인스턴스 변수 값을 저장하거나 전송함

예제 15-17 직렬화 테스트하기

  • serial.out 파일을 열어 보면 읽을 수 없는 내용으로 저장되어 있음

Serializable 인터페이스

  • 프로그램을 실행하면 출력 화면처럼 오류가 발생함
  • 직렬화는 인스턴스 내용이 외부로 유출되는 것이므로 프로그래머가 직렬화를 하겠다는 의도를 표시해야 함
  • Person 클래스에 마커 인터페이스인(marker interface)인 Serializable 인터페이스를 추가함
  • Serializable 인터페이스는 이 클래스를 직렬화 하겠다는 의미로만 해석하면 됨
  • 처음 생성한 클래스 내용이 그대로 읽히고 복원된 것을 알 수 있음

transient 예약어

  • 직렬화 대상이 되는 클래스는 모든 인스턴스 변수가 직렬화되고 복원됨
  • 직렬화 될 수 없는 클래스(Socket 클래스는 직렬화 될 수 없음)가 인스턴스 변수로 있다거나 직렬화되고 싶지 않은 변수가 있을 수 있을 때 transient 예약어를 사용함
  • transient 예약어를 사용하면 해당 변수는 직렬화되고 복원되는 과정에서 제외됨
  • transient 예약어를 사용한 변수 정보는 그 자료형의 기본 값으로 저장됨
  • 객체 자료형인 경우에 null 값이 됨

serialVersionUID를 사용한 버전 관뢰

  • 객체를 역직렬화할 때, 직렬화할 때의 클래스와 상태가 다르면 오류가 발생함
  • 그 사이 클래스가 수정되었다거나 변경되었다면 역직렬화를 할 수 없기 때문임
  • 따라서 직렬화할 때 자동으로 serialVersionUID를 생성하여 정보를 저장함
  • 역직렬화할 때 serialVersionUID를 비교하는데 만약 클래스 내용이 변경되었다면 클래스 버전이 맞지 않는다는 오류가 발생함
  • 하지만 작은 변경에도 클래스 버전이 계속 바뀌면 네트워크로 서로 객체를 공유해서 일하는 경우에 매번 클래스를 새로 배포해야 하는 번거로움이 있음
    - 이런 경우 클래스의 버전 관리를 개발자가 할 수 있음
  • 자바에서 제공하는 자바 설치 경로의 bin\serialver.exe를 사용하면 serialVersionUID가 생성되고 이 정보를 클래스 파일에 적어주면 됨
    - 이클립스에서는 이 기능을 자동으로 제공함
  • 만약 직렬화의 대상이 되는 클래스 정보가 바뀌고 이를 공유해야 하는 경우에 버전 정보를 변경하면 됨

Externalizable 인터페이스

  • 직렬화 하는데 사용하는 또 다른 인터페이스로 Externalizable가 있음
  • Serializable 인터페이스는 자료를 읽고 쓰는 데 필요한 부분을 프로그래머가 따로 구현하지 않음
  • Externalizable 인터페이스는 프로그래머가 구현해야할 메서드가 있음
  • 객체의 직렬화와 역직렬화를 프로그래머가 세밀하게 제어하고자 할 때, 메서드에 그 내용을 구현함

예제 15-18 직렬화 테스트하기

  • Externalizable 인터페이스를 구현하면 writeExternal( ) 메서드와 readExternal( ) 메서드를 구현해야 함
  • 복원할 때 디폴트 생성자가 호출되므로 디폴트 생성자를 추가해주어야 함
  • 읽고 쓰는 내용은 프로그래머가 직접 구현함

15-7 그 외 입출력 스트림

File 클래스

  • File 클래스는 말 그대로 파일이라는 개념을 추상화한 클래스임
  • 파일에 대한 입출력은 지금까지 배운 스트림을 사용하여 수행함
  • File 클래스에 별도의 입출력 기능은 없지만 파일 자체의 경로나 정보를 알 수 있고 파일을 생성할 수도 있음
  • File 클래스의 주요 생성자

예제 15-19 File 클래스 테스트하기

  • File 클래스를 생성했다고 실제 파일이 생성되는 것은 아님
  • createNewFile( ) 메서드를 활용하여 파일을 생성함
  • File 클래스가 제공하는 메서드로 생성한 파일의 속성을 살펴볼 수 있음
  • 이렇게 생성한 파일은 FileInputStream과 같은 파일 입출력 기능을 제공하는 클래스의 생성자 매개변수로 사용할 수 있음

RandomAccessFile 클래스

  • RandomAccessFile은 입출력 클래스 중 유일하게 파일 입출력을 동시에 할수 있는 클래스임
  • 지금까지 배운 스트림은 처음부터 차례로 자료를 읽었지만 RandomAccessFile은 임의의 위치로 이동하여 자료를 읽을 수 있음
  • RandomAccessFile에는 파일 포인터가 있는데, 현재 이 파일의 어느 위치에서 읽고 쓰는지 그 위치를 가리키는 속성임
  • 스트림을 생성하지 않고 간단한게 파일에 자료를 쓰거나 읽을 때 사용하면 유용함
  • 파일 포인터가 이동하는 위치가 파일 자료를 읽거나 쓰이는 위치이므로 파일 포인터의 위치를 잘 생각하며 구현해야 함
  • RandomAccessFile은 임의의 위치에 읽거나 쓰느 ㄴ기능 외에도 다양한 자료형 값을 읽거나 쓸 수 있음

예제 15-20 RandomAccessFile 테스트하기(1)

  • 파일에 자료를 읽거나 쓰면 파일 포인터는 이동함
  • 처음 RandomAccessFile 클래스를 생성하면 파일 포인터의 위치는 맨 앞, 즉 0을 가리킴
  • 다음 자료를 읽거나 써야할 위치로 계속 이동하는 것이 파일 포인터임
  • RandomAccessFile를 생성할때 rw 모드를 사용함
    - 이 파일은 읽고 쓰기가 모두 가능함
  • 각 메서드가 호출됨으로써 이동한 파일 포인터의 값
  • 쓰기가 끝난 후 파일 포인터의 위치는 4 + 8 + 17 = 29임
    - 그런데 여기에서 read( ) 메서드를 호출하면 오류가 남
    - 파일 포인터 위치가 29에 있기 때문임
    - 우리가 읽어야 할 파일 위치는 맨 처음인 0부터임
  • 파일 포인터 위치를 이동해 주는 seek( ) 메서드를 활용하며 맨 처음으로 이동함

예제 15-21 RandomAccessFile 테스트하기(2)

  • 이제 자료를 읽기 전에 파일의 맨 처음으로 이동하여 처음부터 차례로 값을 읽어 올 수 있음
  • 자료를 읽어 올 때는 저장할 때 사용한 자료형에 맞는 메서드로 읽어야 함
profile
Nil Desperandum <절대 절망하지 마라>

0개의 댓글