18.1. IO 패키지 소개
자바에서 데이터는 스트림(Stream)을 통해 입출력되므로 스트림의 특징을 잘 이해해야 한다. 스트림은 단일 방향으로 연속적으로 흘러가는 것을 말하는데, 데이터는 역시 출발지에서 나와 도착지로 들어간다.
18.2. 입력 스트림과 출력 스트림
프로그램이 데이터를 입력받을 때에는 입력 스트림(InputStream)이라고 부르고, 프로그램이 데이터를 보낼 때에는 출력 스트림(OutputStream)이라고 부른다. 입력 스트림의 출발지는 키보드, 파일, 네트워크상의 프로그램이 될 수 있고, 출력 스트림의 도착지는 모니터, 파일, 네트워크상의 프로그램이 될 수 있다.
*해당 스트림은 프로그램을 기준으로 나타난다는 것을 알아두자.
자바의 기본적인 데이터 입출력(IO : Input/Output) API는 java.io 패키지에서 제공하고 있다.
java.io 패키지에는 파일 시스템의 정보를 얻기 위한 File 클래스와 데이터를 입출력하기 위한 다양한 입출력 스트림 클래스를 제공하고 있다.
java.io 패키지의 주요 클래스 | 설명 |
File | 파일 시스템의 파일 정보를 얻기 위한 클래스 |
Console | 콘솔로부터 문자를 입출력하기 위한 클래스 |
InputStream/OutputStream | 바이트 단위 입출력을 위한 최상위 입출력 스트림 클래스 |
FileInputStream/FileOutputStream DataInputStream/DataOutputStream ObjectInputStream/ObjectOutputStream PrintStream BufferedInputStream/BufferedOutputStream |
바이트 단위 입출력을 위한 하위 스트림 클래스 |
Reader/Writer | 문자 단위 입출력을 위한 최상위 입출력 스트림 클래스 |
FileReader/FileWriter InputStreamReader/OutputStreamWriter PrintWriter BufferedReader/BufferedWriter |
문자 단위 입출력을 위한 하위 스트림 클래스 |
스트림 클래스 종류
- 바이트(byte) 기반 스트림 : 그림, 멀티미디어, 문자 등 모든 종류의 데이터를 받고 보낼 수 있다.
- 문자(character) 기반 스트림 : 오로지 무자만 받고 보낼 수 있도록 특화되어 있다.
구분 | 바이트 기반 스트림 | 문자 기반 스트림 | ||
입력 스트림 | 출력 스트림 | 입력 스트림 | 출력 스트림 | |
최상위 클래스 | InputStream | OutputStream | Reader | Writer |
하위 클래스(예) | XXXInputStream (FileInputStream) |
XXXOutputStream (FileOutputStream) |
XXXReader (FileReader) |
XXXWriter |
18.2.1. InputStream
InputStream은 바이트 기반 입력 스트림의 최상위 클래스로 추상 클래스이다.
리턴 타입 | 메소드 | 설명 |
int | read() | 입력 스트림으로부터 1바이트를 읽고 바이트를 리턴한다. |
int | read(byte[] b) | 입력 스트림으로부터 읽은 바이트들을 매개값으로 주어진 바이트 배열 b에 저장하고 실제로 읽은 바이트 수를 리턴한다. 많은 양의 바이트를 읽을 때 효율적이다. |
int | read(byte[] b, int off, int len) | 입력 스트림으로부터 len개의 바이트만큼 일고 매개값으로 주어진 바이트 배열 b[off]부터 len개까지 저장한다. 그리고 실제 읽은 바이트 수인 len개를 리턴한다. 만약 len개를 모두 읽지 못하면 실제로 읽은 바이트 수를 리턴한다. |
void | close() | 사용한 시스템 자원을 반납하고 입력 스트림을 닫는다. |
18.2.2. OutputStream
OutputStream은 바이트 기반 출력 스트림의 최상위 클래스로 추상 클래스이다.
리턴 타입 | 메소드 | 설명 |
void | write(int b) | 출력 스트림으로 1바이트를 보낸다(b의 끝 1바이트). |
void | write(byte[] b) | 출력 스트림으로 주어진 바이트 배열 b의 모든 바이트를 보낸다. |
void | write(byte[] b, int off, int len) | 출력 스트림으로 주어진 바이트 배열 b[off]부터 len개까지의 바이트를 보낸다. |
void | flush() | 버퍼에 잔루하는 모든 바이트를 출력한다. |
void | close() | 사용한 시스템 자원을 반남하고 출력 스트림을 닫는다. |
18.2.3. Reader
Reader는 문자 기반 입력 스트림의 최상의 클래스로 추상 클래스이다.
메소드 | 설명 | |
int | read() | 입력 스트림으로부터 한 개의 문자를 읽고 리턴한다. |
int | read(char[] cbuf) | 입력 스트림으로부터 읽은 문자들을 매개값으로 주어진 문자 배열 cbuf에 저장하고 실제로 읽은 문자 수를 리턴한다. |
int | read(char[] cbuf, int off, int len) | 입력 스트림으로부터 len개의 문자를 읽고 매개값으로 주어진 문자 배열 cbuf[off]부터 len까지 저장한다. 그리고 실제로 읽은 문자 수 인 len개를 리턴한다. |
void | close() | 사용한 시스템 자원을 바납하고 입력 스트림을 닫는다. |
18.2.4. Writer
Writer는 문자 기반 출력 스트림의 최상위 클래스로 추상 클래스이다.
리턴 타입 | 메소드 | 설명 |
void | write(int e) | 출력 스트림으로 주어진 한 문자를 보낸다(c의 끝 2바이트). |
void | write(char[] cbuf) | 출력 스트림으로 주어진 문자 배열 cbuf의 모든 문자를 보낸다. |
void | write(char[] cbuf, int off, int len) | 출력 스트림으로 주어진 문자 배열 cbuf[off]부터 len개까지의 문자를 보낸다. |
void | Write(String str) | 출력 스트림으로 주어진 문자열을 전부 보낸다. |
void | write(String str, int off, int len) | 출력 스트림으로 주어진 문자열 off 순번부터 len개까지의 문자를 보낸다. |
void | flush() | 버퍼에 잔류하는 모든 문자열을 출력한다. |
void | close() | 사용한 시스템 자원을 반남하고 출력 스트림을 닫는다. |
18.3. 콘솔 입출력
콘솔(Console)은 시스템을 사용하기 위해 키보드로 입력을 받고 화면으로 출력하는 소프트웨어를 말한다. 유닉스나 리눅스 운영체제는 터미널(terminal)에 해당하고, Windows 운영체제는 명령 프롬프트에 해당한다.
18.3.1. System.in 필드
자바는 프로그램이 콘솔로부터 데이터를 입력받을 수 있도록 System 클래스의 in 정적 필드를 제공하고 있다. System.in은 InputStream 타입의 필드이므로 다음과 같이 InputStream 변수로 참조 가능하다.
InputStream is = System.in;
*키보드로부터 어떤 키가 입력되었는지 확인하려면 InputStream의 read() 메소드로 한 바이트를 읽으면 된다. 리턴된 int 값에는 십진수 아스키 코드(Ascii Code)가 들어 있다.
int asciiCode = is.read();
18.3.2. System.out 필드
콘솔에서 입력된 데이터를 System.in으로 읽었다면, 반대로 콘솔로 데이터를 출력하기 위해서는 System 클래스의 out 정적 필드를 사용한다.
OutputStream os = System.out;
18.3.3. Console 클래스
자바 6부터는 콘솔에서 입력받은 문자열을 쉽게 읽을 수 있도록 java.io.Console 클래스를 제공하고 있다.
Console console = System.console();
리턴 타입 | 메소드 | 설명 |
String | readLine() | Enter키를 입력하기 전의 모든 문자열을 읽음 |
char[] | readPassword() | 키보드 입력 문자를 콘솔에 보여주지 않고 문자열을 읽음 |
18.3.4. Scanner 클래스
Console 클래스는 콘솔로부터 문자열은 읽을 수 있지만 기본 타입(정수, 실수) 값을 바로 읽을 수는 없다. java.io 패키지의 클래스는 아니지만, java.util 패키지의 Scanner 클래스를 이용하면 콘솔로부터 기본 타입의 값을 바로 읽을 수 있다.
Scanner scanner = new Scanner(System.in);
리턴 타입 | 메소드 | 설명 |
boolean | nextBoolean() | boolean(true/false) 값을 읽는다. |
byte | nextByte() | byte 값을 읽는다. |
short | nextShort() | short 값을 읽는다. |
int | nextInt() | int 값을 읽는다. |
long | nextLong() | long 값을 읽는다. |
float | nextFloat() | float 값을 읽는다. |
double | nextDouble() | double 값을 읽는다. |
String | nextLine() | String 값을 읽는다. |
18.4. 파일 입출력
18.4.1. File 클래스
IO 패키지(java.io)에서 제공하는 File 클래스는 파일 크기, 파일 속성, 파일 이름 등의 정보를 얻어내는 기능과 파일 생성 및 삭제 기능을 제공하고 있다.
특정 경로에 파일 생성 예제
File file = new File("경로/파일명");
디렉토리 또는 파일이 파일 시스템에 존재한다면 true를 리턴하고 존재하지 않는다면 false를 리턴한다.
boolean isExist = file.exists();
exists() 메소드의 리턴값이 false라면 사용가능한 메소드
리턴 타입 | 메소드 | 설명 |
boolean | createNewFile() | 새로운 파일을 생성 |
boolean | mkdir() | 새로운 디렉토리를 생성 |
boolean | mkdirs() | 경로상에 없는 모든 디렉토리를 생성 |
boolean | delete() | 파일 또는 디렉토리를 삭제 |
리턴값이 true가 나왔을 경우 정보 얻는 메소드
리턴 타입 | 메소드 | 설명 |
boolean | canExecute() | 실행할 수 잇는 파일인지 여부 |
boolean | canRead() | 읽을 수 있는 파일인지 여부 |
boolean | canWrite() | 수정 및 저장할 수 있는 파일인지 여부 |
String | getName() | 파일의 이름을 리턴 |
String | getParent() | 부모 디렉토리를 리턴 |
File | getParentFile() | 부모 디렉토리를 File 객체로 생성 후 리턴 |
String | getPath() | 전체 경로를 리턴 |
boolean | isDirectory() | 디렉토리인지 여부 |
boolean | isFile() | 파일인지 여부 |
boolean | isHidden | 숨김 파일인지 여부 |
long | lastModified() | 마지막 수정 날짜 및 시간을 리턴 |
long | length() | 파일의 크기를 리턴 |
String[] | list() | 디렉토리에 포함된 파일 및 서브디렉토리 목록 중에 FilenameFilter에 맞는 것만 String 배열로 리턴 |
String[] | list(FilenameFilter filter) | 디렉토리에 포함된 파일 및 서브디렉토리 목록 중에 FilenameFilter에 맞는 것만 String 배열로 리턴 |
File[] | listFile() | 디렉토리에 포함된 파일 및 서브 디렉토리 목록 전부를 File 배열로 리턴 |
File[] | listFiles(FilenameFilter filter) | 디렉토리에 포함된 파일 및 서브디렉토리 목록 중에 FilenameFilter에 맞는 것만 File 배열로 리턴 |
18.4.2. FileInputStream
FileInputStream 클래스는 파일로부터 바이트 단위로 읽어들일 때 사용하는 바이트 기반 입력 스트림이다. 바이트 단위로 일기 때문에 그림, 오디오, 비디오, 텍스트 파일 등 모든 종류의 파일을 읽을 수 있다.
//방법1
FileInputStraem fis = new FileInputStream("파일경로/파일명");
//방법2
File file = new File("파일경로/파일명");
FileInputStream fis = new FileInputStream(file);
18.4.3. FileOutputStream
FileOutputStream은 바이트 단위로 데이터를 파일에 저장할 때 사용하는 바이트 기반 출력 스트림이다.
//방법1
FileOutputStream fos = new FileOutputStream("파일경로/파일명");
//방법2
File file = new File("파일경로/파일명");
FileOutputStream fos = new FileOutputStream(file);
18.4.4. FileReader
FileReader 클래스는 텍스트 파일을 프로그램으로 읽어들일 때 사용하는 문자 기반 스트림이다.
//방법1
FileReader fr = new FileReader("파일경로/파일명");
//방법2
File file = new File("파일경로/파일명");
FileReader fr = new FileReader(file);
18.4.5. FileWriter
FileWriter는 텍스트 데이터를 파일에 저장할 때 사용하는 문자 기반 스트림이다.
//방법1
FileWriter fw = new FileWriter("파일경로/파일명");
//방법2
File file = new File("파일경로/파일명");
FileWriter fw = new FileWriter(file);
18.5. 보조 스트림
보조 스트림이란 다른 스트림과 연결되어 여러 가지 편리한 기능을 제공해주는 스트림을 말한다. 보조 스트림을 필터(filter) 스트림이라고도 하는데, 이는 보조 스트림의 일부가 FilterInputStream, FilterOutStream의 하위 클래스이기 때문이다.
보조스트림 변수 = new 보조스트림(연결스트림)
InputStream is = System.in;
InputStreamReader reader = new InputSteamReader(is);
18.5.1. 문자 변환 보조 스트림
소스 스트림이 바이트 기반 스트림(InputStream, OutputStream, FileInputStream, FileOutputStream)이면서 입출력 데이터가 문자라면 Reader와 Writer로 변환해서 사용하는 것을 고려해야 한다. 그 이유는 Reader와 Writer는 문자 단위로 입출력하기 때문에 바이트 기반 스트림보다는 편리하고, 문자셋의 종류를 지정할 수 있기 때문에 다양한 문자를 입출력할 수 있다.
InputStreamReader
InputStreanReader는 바이트 입력 스트림에 연결되어 문자 입력 스트림인 Reader로 변환시키는 보조 스트림이다.
Reader reader = new InputStreamReader(바이트입력스트림);
InputStream is = System.in;
Reader reader = new InputStreamReader(is);
OutputStreamWriter
OutputStreamWriter는 바이트 출력 스트림에 연결되어 문자 출력 스트림인 Writer로 변환시키는 보조 스트림이다.
Writer writer = new OutputStreamWriter(바이트출력스트림);
FileOutputStream fos = new FileOutputStream("파일경로/파일명");
Writer writer = new OutputStreamWriter(fos);
18.5.2. 성능 향상 보조 스트림
프로그램이 입출력 소스와 직접 작업하지 않고 중간에 메모리 버퍼(buffer)와 작업함으로써 실행 성능을 향상시킬 수 있다.
보조 스트림 중에서는 위와 같이 메모리 버퍼를 제공하여 프로그램의 실행 성능을 향상시키는 것들이 있다. 바이트 기반 스트림에는 BufferedInputStream, BufferedOutputStream이 있고, 문자 기반 스트림에는 BufferedReader, BuufferedWriter가 있다.
BufferedInputStream과 BufferedReader
BufferedInputStream은 바이트 입력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이고, BufferedReader는 문자 입력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이다. BufferedInputStream과 BufferedReader는 입력 소스로부터 자신의 내부 버퍼 크기만큼 데이터를 미리 읽고 버퍼에 저장해 둔다. 프로그램은 외부의 입력 소스로부터 직접 읽는 대신 버퍼러부터 읽음으로써 읽기 성능이 향상된다.
BufferedInputStream bis = new BufferedInputStream(바이트입력스트림);
BufferedReader br = new BufferedReader(문자입력스트림);
Enter키를 입력하기 전까지 콘솔에서 입력한 모든 문자열을 한꺼번에 얻는 방법
Reader reader = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(reader);
String inputStr = br.readLine();
BufferedOutputStream과 bufferedWriter
BufferedOutputStream은 바이트 출력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이고, BufferedWriter는 문자 출력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이다. BufferedOutputStream과 BufferedWriter는 프로그램에서 전송한 데이터를 내부 버퍼에 쌓아두었다가 버퍼가 꽉 차면, 버퍼의 모든 데이터를 한꺼번에 보낸다. 프로그램 입장에서 보면 직접 데이터를 보내는 것이 아니라, 메모리 버퍼로 데이터를 고속 전송하기 때문에 실행 성능이 향상되는 효과를 얻게 된다.
BufferedOutputStream bos = new BufferedOutputStream(바이트출력스트림);
BufferedWriter bw = new BufferedWriter(문자출력스트림);
18.5.3. 기본 타입 입출력 보조 스트림
바이트 스트림은 바이트 단위로 입출력하기 때문에 자바의 기본 데이터 타입인 boolean, char, short, int, long, float, double 단위로 입출력할 수 없다. 그러나 DataInputStream과 DataOutputStream 보조 스트림을 연결하면 기본 데이터 타입으로 입출력이 가능하다.
DataInputStream dis = new DataInputStream(바이트입력스트림);
DataOutputStream dos = new DataOutputStream(바이트출력스트림);
DataInputStream | DataOutputStream | ||
boolean | readBoolean() | void | writeBoolean(boolean v) |
byte | readByte() | void | writeByte(int v) |
char | readChar() | void | writeChar(int v) |
double | readDouble() | void | writeDouble(double v) |
float | readFloat() | void | writeFloat(float v) |
int | readInt() | void | WriteInt(int v) |
long | readLong() | void | writeLong(long v) |
short | readShort() | void | writeShort(int v) |
String | readUTF() | void | writeUTF(String str) |
18.5.4. 프린터 보조 스트림
PrintStream과 PrintWriter는 프린터와 유사하게 출력하는 print(), println() 메소드를 가지고 있는 보조 스트림이다.
PrintStream ps = new PrintStream(바이트출력스트림);
PrintWriter pw = new PrintStream(문자출력스트림);
PrintStream / PrintWriter | |||
void | print(boolean b) | void | println(boolean b) |
void | print(char c) | void | println(char c) |
void | print(double d) | void | println(double d) |
void | print(float f) | void | println(float f) |
void | print(int i) | void | println(int i) |
void | print(long l) | void | println(long l) |
void | print(Object obj) | void | println(Object obj) |
void | print(String s) | void | println(String s) |
void | println() |
18.5.5. 객체 입출력 보조 스트림
자바는 메모리에 생성된 객체를 파일 또는 네트워크로 출력할 수가 있다. 객체는 문자가 아니기 때문에 바이트 기반 스트림으로 출력해야한다. 객체를 출력하기 위해서는 객체의 데이터(필드값) 를 일렬로 늘어선 연속적인 바이트로 변경해야 하는데, 이것을 객체 직렬화(serialization)라고 한다. 반대로 파일에 저장되어 있거나 네트워크에서 전송된 객체를 읽을 수도 있는데, 입력 스트림으로부터 읽어들인 연속적인 바이트를 객체로 복원하는 것을 역직렬화(deserialization)라고 한다.
ObjectInputStream, ObjectOutputStream
자바는 객체를 입출력할 수 있는 두 개의 보조 스트림인 ObjectInputStream과 ObjectOutputStream을 제공한다.
ObjectInputStream ois = new ObjectInputStream(바이트입력스트림);
ObjectOutputStream oos = new ObjectOutputStream(바이트출력스트림);
ObjectOutputStream으로 객체를 직렬화하기 위해서는 writeObject() 메소드를 사용한다.
oos.writeObject(객체);
ObjectInputStream으로 역직렬화하기 위해서는 readObject() 메소드를 사용한다.
객체타입 변수 = (객체타입) ois.readObject();
직렬화가 가능한 클래스(Serializable)
자바는 Serializable 인터페이스를 구현한 클래스만 직렬화할 수 있도록 제한하고 있다. Serializable 인터페이스는 객체를 직렬화할 때 private 필드를 포함한 모든 필드를 바이트로 변환해도 좋다는 표시 역할을 한다.
public class XXX implements Serializabe { }
*필드 선언에 static 또는 transient가 붙어 있을 경우에는 직렬화가 되지 않는다.
serialVersionUID 필드
직렬화된 객체를 역직렬화할 때는 직렬화했을 때와 같은 클래스를 사용해야 한다. 클래스의 이름이 같더라도 클래스의 내용이 변경되면, 역직렬화는 실패하며 다음과 같은 예외가 발생한다.
java.io.InvalidClassException: XXX; local class incompatible: stream classdesc
serialVersionUID = -9130799490637978756, local class serialVersionUID = -1174725809595957294
위 예외의 내용은 직렬화할 대와 역직렬화할 때 사용된 클래스의 serialVersionUID가 다르다는 것이다.
만약 불가피하게 클래스의 수정이 필요하다면 클래스 작성 시 다음과 같이 serialVersionUID를 명시적으로 선언하면 된다.
public class XXX implements Serializable{
static final long serialVersionUID = 정수값;
...
}
클래스에 serialVersionUID가 명시적으로 선언되어 있으면 컴파일 시에 serialVersionUID 필드가 추가되지 않기 때문에 동일한 serialVersionUID 값을 유지할 수 있다.
writeObject()와 readObject() 메소드
두 클래스가 상속 관계에 있다고 가정해보자. 부모 클래스가 Serializable 인터페이스를 구현하고 있으면 자식 클래스는 Serializable 인터페이스를 구현하지 않아도 자식 객체를 직렬화하면 부모 필드 및 자식 필드가 모두 직렬화된다. 하지만 그 반대로 부모 클래스가 Serializable 인터페이스를 구현하고 있지 않고, 자식 클래스만 Serializable 인터페이스를 구현하고 있다면 자식 객체를 직렬화할 때 부모의 필드는 직렬화에서 제외한다. 이 경우 부모 클래스의 필드를 직렬화하고 싶다면 다음 두 가지 방법 중 하나를 선택해야 한다.
- 부모 클래스가 Serializable 인터페이스를 구현하도록 한다.
- 자식 클래스에서 writeObject()와 readObject() 메소드를 선언해서 부모 객체의 필드를 직접 출려시킨다.
'👨🏫Study > JAVA' 카테고리의 다른 글
[JAVA] NullPointerException 처리방법 (0) | 2022.03.30 |
---|---|
[JAVA] 18 - 2. 네트워킹 (0) | 2022.03.29 |
[JAVA] JavaFX (0) | 2022.03.25 |
[JAVA] 11 - 3 기본 API 클래스(Pattern, Arrays, Wrapper, Math, Date, Calendar, Format , LocalDate, Instant, DateTimeFormatter) (0) | 2022.03.23 |
[JAVA] 11 - 2 기본 API 클래스(Objects, System, Class, Reflection, String, Tokenizer, Builder) (0) | 2022.03.23 |
댓글