13일차 Java [ 데이터 입출력 ]
바이트 스트림
1) 입력 스트림 -> ~~InputStream으로 끝남
2) 출력 스트림 -> ~~OutputStream으로 끝남
문자 스트림
1) 입력 스트림 -> ~~Reader
2) 출력 스트림 -> ~~Writer
-------------------------------------------------------------------------------------------
1. 파일생성 (출력)
- OutputStream ( 바이트단위 파일생성 )
- Writer ( 문자단위 파일생성 )
2. 파일에 값 저장
- Write 메소드
3. 파일 읽어들임
- InputStream ( 바이트단위로 읽어들임 )
- Reader ( 문자단위로 읽어들임 )
4. 파일닫기 ( close() )
---------------------------------------------------------------------------------------------
주인공 장식 -> Construntor의 매개변수를 보고 매개변수 안에
~OutputStream, ~InputStream, ~Reader, ~Writer가 보이면 장식, 없으면 주인공 !
-> 주인공은 혼자서 파일 생성 가능 ( = 기반 클래스 )
-> 장식은 혼자서 파일 생성 불가능 ( = 주인공을 기반으로 해서 파일 생성 가능 )
[ p. 784 바이트 배열로 출력 ]
import java.io.*;
public class WriteExample {
public static void main(String[] args) {
try {
//데이터 도착지를 test2.db 파일로 하는 바이트 출력 스트림 생성
OutputStream os = new FileOutputStream("C:/Temp/test2.db");
byte[] array = { 10, 20, 30 };
//배열의 모든 바이트 출력
os.write(array);
//내부 버퍼에 잔류하는 바이트를 출력하고 버퍼를 비움
os.flush();
//출력 스트림을 닫아서 사용한 메모리 해제
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
[ p.790 바이트 배열로 읽기 ]
import java.io.*;
public class ReadExample {
public static void main(String[] args) {
try {
//데이터 출발지를 test2.db로 하는 입력 스트림 생성
InputStream is = new FileInputStream ("C:/Temp/test2.db");
byte []data = new byte[100];
while(true) {
//최대 100byte를 읽고 읽은 바이트는 data 저장, 읽은 수는 리턴
int num = is.read(data);
//파일 끝에 도달했을 경우
if(num == -1) break;
//읽은 바이트를 출력
for(int i=0; i<num ; i++) {
System.out.println(data[i]);
}
}
//배열의 모든 바이트를 출력
is.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
[ BufferedWriter ]
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("b.txt"); //b.txt파일 생성 됨
BufferedWriter out = new BufferedWriter(fw);
out.write("안녕안녕~"); //b.txt에 안녕안녕~ 저장
out.newLine(); //줄바꾸는작업
out.write("오늘은 금요일~");
out.newLine();
out.write("신나는 금요일~");
out.close();
}
}
[ BufferedReader ]
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Test {
public static void main(String[] args) {
//FileReader fr = new FileReader("b.txt");
//BufferedReader in = new BufferedReader(f);
try {
BufferedReader in = new BufferedReader(new FileReader("b.txt"));
String str;
while(true) {
try {
str=in.readLine();
if(str==null) {
break;
}
System.out.println(str);
}
catch(Exception e) { }
}
try {
in.close();
} catch(IOException e) { }
} catch (FileNotFoundException e) { }
}
}
[ 입출력 스트림 ]
데이터는 키보드를 통해 입력될 수도 있고, 파일 또는 프로그럄으로부터 입력될 수도 있다. 반대로 데이터는 모니터로 출력될 수도 있고, 파일에 저장되거나 다른 프로그램으로 전송될 수 있다. 이것을 총칭해서 데이터 입출력이라고 한다. 자바는 입력 스트림과 출력 스트림을 통해 데이터를 입출력한다. 스트림 ( Stream )은 단방향으로 데이터가 흐르는 것을 말하는데, 다음 그림과 같이 데이터는 출발지에서 나와 도착지로 흘러 들어간다.
프로그램을 기준으로 데이터가 들어오면 입력 스트림, 데이터가 나가면 출력 스트림이 된다. 프로그램이 다른 프로그램과 데이터를 교환하려면 양쪽 모두 입력 스트림과 출력 스트림이 필요하다.
어떤 데이터를 입출력하느냐에 따라 스트림은 다음 두 종류로 구분할 수 있다.
바이트 스트림 : 그림, 멀티미디어, 문자 등 모든 종류의 데이터를 입출력할 때 사용
문자 스트림 : 문자만 입출력할 때 사용
자바는 데이터 입출력과 관련된 라이브러리를 java.io 패키지에서 제공하고 있다. java.io 패키지는 바이트 스트림과 문자 스트림을 다음과 같이 이름으로 구분해서 제공한다.
구분 | 바이트스트림 | 문자스트림 | ||
입력 스트림 | 출력 스트림 | 입력 스트림 | 출력 스트림 | |
최상위 클래스 | InputStream | OutputStream | Reader | Writer |
하위 클래스 (예) | XXXInputStream (FileInputStream) |
XXXOutputStream (FileOutputStream) |
XXXReader (FileReader) |
XXXWriter (FileWriter) |
바이트 입출력 스트림의 최상의 클래스는 InputStream과 OutputStream이다. 이 클래스를 상속받는 자식 클래스에는 접미사로 InputStream 또는 OutputStream이 붙는다. 예를 들어 이미지와 같은 바이너리 파일의 입출력 스트림 클래스는 FileInputStream과 FileOutputStream이다.
문자 입출력 스트림의 최상위 클래스는 Reader와 Writer이다. 이 클래스를 상속받는 하위 클래스에는 접미사로 Reader 또는 Writer가 붙는다. 예를 들어 텍스트 파일의 입출력 스트림 클래스는 FileReader와 FileWriter이다.
[ 바이트 출력 스트림 ]
OutputStream은 바이트 출력 스트림의 최상위 클래스로 추상 클래스이다. 모든 바이트 출력 스트림 클래스는 이 OutputStream 클래스르 상속받아서 만들어진다. ( FileOutputStream 제일 많이 사용 )
OutputStream 클래스에는 모든 바이트 출력 스트림이 기본적으로 가져야 할 메소드가 정의되어있다. 다음은 OutputStream 클래스의 주요 메소드이다.
리턴 타입 | 메소드 | 설명 |
void | write(int b) | 1byte를 출력 |
void | write(byte[] b) | 매개값으로 주어진 배열 b의 모든 바이트를 출력 |
void | write(byte[] b, int off, int len) | 매개값으로 주어진 배열 b[off]부터 len개의 바이트를 출력 |
void | flush() | 출력 버퍼에 잔류하는 모든 바이트를 출력 |
void | close() | 출력 스트림을 닫고 사용 메모리 해제 |
※ 1 바이트 출력 : write (int b) 메소드는 매개값 in (4byte)에서 끝 1byte만 출력한다. 매개변수가 int타입이므로 4byte 모두를 보내는 것은 아니다. ( 잘 사용되지 않음 )
※ 바이트 배열 출력 ( 많이 사용 됨 ) : 일반적으로 1 바이트를 출력하는 경우는 드물고, 보통 바이트 배열을 통째로 출력하는 경우가 많다. write(byte[] b) 메소드는 매개값으로 주어진 배열의 모든 바이트를 출력한다.
만약 배열의 일부분을 출력하고 싶다면 write(byte[] b, int off, int len) 메소드를 사용하면된다. 이 메소드는 b[off]부터 len개의 바이트를 출력한다.
[ 바이트 입력 스트림 ]
InputStream은 바이트 입력 스트림의 최상위 클래스로, 추상 클래스이다. 모든 바이트 입력 스트림은 InputStream 클래스를 상속 받아 만들어진다. ( FileInputStream 제일 많이 사용 )
InputStream 클래스에는 바이트 입력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다. 다음은 InputStream 클래스의 주요 메소드이다.
리턴 타입 | 메소드 | 설명 |
int | read() | 1byte를 읽은 후 읽은 바이트를 리턴 |
int | read(byte[] b) | 읽은 바이트를 매개값으로 주어진 배열에 저장 후 읽은 바이트 수를 리턴 |
void | close() | 입력 스트림을 닫고 사용 메모리 해 |
※ 1 바이트 읽기 : read() 메소드는 입력 스트림으로 부터 1byte를 읽고 int(4byte) 타입으로 리턴한다. 따라서 리턴된 4byte 중 끝 1byte에만 데이터가 들어 있다. 예를 들어 입력 스트림에서 5개의 바이트가 들어온다면 다음과 같이 read() 메소드로 1byte씩 5번 읽을 수 있다. ( 잘 사용되지 않음 )
더 이상 입력 스트림으로부터 바이트를 읽을 수 없다면 read() 메소드는 -1을 리턴하는데, 이곳을 이용하면 읽을 수 있는 마지막 바이트까지 반복해서 읽을 수 있다.
InputStream is = ...;
while(true) {
int data = is.read(); // 1바이트를 읽고 리턴
if (data==-1) break; //-1을 리턴했을 경우 while문 종료
}
FileInputStream 생정자는 주어진 파일이 존재하지 않을 경우 FileNotFoundException을 발생시킨다. 그리고 read(), close() 메소드에서 IOException이 발생할 수 있으므로 두 가지 예외를 모두 처리해야 한다.
※ 바이트 배열로 읽기 ( 많이 사용 됨 ) : read(byte[] b) 메소드는 입력 스트림으로부터 주어진 배열의 길이만큼 바이트를 읽고 배열에 저장한 다음 읽은 바이트 수를 리턴한다. 예를 들어 입력 스트림에 5개의 바이트가 들어오면 다음과 같이 길이 3인 배열로 두 번 읽을 수 있다.
read(byte[] b) 역시 입력 스트림으로부터 바이트를 더 이상 읽을 수 없다면 -1을 리턴하는데, 이것을 이용하면 읽을 수 있는 마지막 바이트가지 반복해서 읽을 수 있다.
InputStream is = ...;
vyte[] data = new byte[100];
while(true) {
int num = is.read(data); // 최대 100byte를 읽고, 읽은 바이트는 배열 data 저장, 읽은 수는 리턴
if (num == -1) break; // -1을 리턴하면 while문 종료
}
많은 양의 바이트를 읽을 때는 read(byte[] b) 메소드를 사용하는 것이 좋다. 입력 스트림으로부터 100개의 바이트가 들어온다면 read() 메소드는 100번을 반복해서 읽어야 하지만, read(byte[] b) 메소드는 한 번 읽을 때 배열의 길이만큼 읽기 때문에 읽는 횟수가 현저히 줄어든다.
Java9 부터 좀 더 편리하게 입력 스트림에서 출력 스트림으로 바이트를 복사하는 transferTo() 메소드가 InputStream에 추가되었다.
is.transferTo(os);
[ 문자 입출력 스트림 ]
바이트 입출력 스트림인 InputStream과 OutputStream에 대응하는 문자 입출력 스트림으로 Reader와 Writer가 있다. 입출력 되는 단위가 문자인 것을 제외하면 바이트 입출력 스트림과 사용 방법은 동일하다.
※ 문자 출력 : Writer는 문자 출력 스트림의 최상위 클래스로, 추상클래스이다. 모든 문자 출력 스트림 클래스는 Writer 클래스를 상속 받아서 만들어진다.
Writer 클래스에는 모든 문자 출력 스트림이 기본적으로 가져야할 메소드가 정의되어 있다. Writer 클래스의 주요 메소드는 다음과 같다.
리턴 타입 | 메소드 | 설명 |
void | write(int c) | 매개값으로 주어진 한 문자를 출력 |
void | write(char[] cbuf) | 매개값으로 주어진 배열의 모든 문자를 출력 |
void | write(char[] cbuf, int off, int len) | 매개값으로 주어진 배열에서 cbuf[off]부터 len개까지의 문자를 출 |
void | write(String str) | 매개값으로 주어진 문자열을 출력 |
void | write(String str, int of, int len) | 매개값으로 주어진 문자열에서 off 순번부터 len까지의 문자를 출력 |
void | flush() | 버퍼에 잔류하는 모든 문자를 출력 |
void | close() | 출력 스트림을 닫고 사용 메모리를 해제 |
Writer는 OutputStream과 사용 방법은 동일하지만, 출력 단위가 문자(char)이다. 그리고 문자열을 출력하는 write(String str) 메소드를 추가로 제공한다.
※ 문자 읽기 : Reader는 문자 입력 스트림의 최상위 클래스로, 추상 클래스이다. 모든 문자 입력 스트림 클래스는 Reader 클래스를 상속받아서 만들어진다.
Reader 클래스에는 문자 입력 스트립이 기본적으로 가져야 할 메소드가 정의되어 있다. 다음은 Reader 클래스의 주요 메소드이다.
메소드 | 설명 | |
int | read() | 1개의 문자를 읽고 리턴 |
int | read(char[] cbuf) | 읽은 문자들을 매개값으로 주어진 문자 배열에 저장하고 읽은 문자 수를 리턴 |
void | close() | 입력 스트림을 닫고, 사용 메모리 해제 |
Reader는 InputStream과 사용 방법은 동일하지만, 출력 단위가 문자 ( char )이다.
[ 보조 스트림 ]
보조 스트림이란 다른 스트림과 연결되어 여러 가지 편리한 기능을 제공해주는 스트림을 말한다. 보조 스트림은 자체적으로 입출력을 수행할 수 없기 때문에 입출력 소스로부터 직접 생성된 입출력 스트림에 연결해서 사용해야 한다.
입출력 스트림에 보조 스트림을 연결하려면 보조 스트림을 생성할 때 생성자 매개값으로 입출력 스트림을 제공하면 된다.
보조 스트림 변수 = new 보조스트림(입출력스트림) ;
예를 들어 바이트 입력 스트림인 FileInputStream에 InputStreamReader 보조 스트림을 연결하는 코드는 다음과 같다.
InputStream is = new FileInputStream("...");
InputStreamReader reader = new InputStreamReader ( is );
보조 스트림은 또 다른 보조 스트림과 연결되어 스트림 체인으로 구성할 수 있다.
예를 들어 문자 변환 보조 스트림인 InputStreamReader와 BufferReader 보조 스트림을 연결하는 코드는 다음과 같다.
자주 사용 ☆★
InputStream is = new FileInputStream("...");
InputStreamReader reader = new InputStreamReader( is );
BufferReader br = new BufferReader( reader );
자주 사용되는 보조 스트림은 다음과 같다. ☆★
보조 스트림 | 기능 |
InputStreamReader | 바이트 스트림을 문자 스트림으로 변환 |
Buffered InputStream, BufferedoutputStream, BufferedReader, BufferedWriter |
입출력 성능 향상 |
DataInputStream, DataOutputStream | 기본 타입 데이터 입출력 |
PrintStream, PrintWriter | 줄바꿈 처리 및 형식화된 문자열 출력 |
ObjectInputStream, ObjectOutputStream | 객체 입출력 |
[ 문자 변환 스트림 ]
바이트 스트림 ( InputStream, OutputStream )에서 입출력할 데이터가 문자라면 문자 스트림 (Reader와 Writer )로 변환해서 사용하는 것이 좋다. 그 이유는 문자로 바로 입출력하는 편리함이 있고, 문자셋의 종류를 지정할 수 있기 때문이다.
※ InputStream을 Reader로 변환 ☆★: InputStream을 Reader로 변환하려면 InputStreamReader 보조 스트림을 연결하면 된다. 바이트를 -> 문자로 , 보조 스트림 ( 많이 사용 됨 )
다음은 InputStream을 Reader로 변환하는 코드를 보여준다.
InputStream is = new FileInputStream("C:/Temp/test.txt");
Reader reader = new InputStream(is);
※ FileReader의 원리
FileInputStream에 InputStreamReader를 연결하지 않고 FileReader를 직접 생성할 수도 있다. FileReader는InputStreamReader의 자식 클래스이다. 이것은 FileReader가 내부적으로 FileInputStream에 InputStreamReader 보조 스트림을 연결한 것이라고 볼 수 있다.
※ OutputStream을 Writer로 변환 : OutputStream을 Writer로 변환하려면 OutputStreamWriter 보조 스트림을 연결하면 된다. 문자를 -> 바이트로 , 보조 스트림
다음은 OutputStream을 Writer로 변환하는 코드를 보여준다.
OutputStream os = new FileOutputStream("C:/Temp/test.txt");
Writer writer = new OutputStreamWriter(os);
※ FileWriter의 원리
FileOutputStream에 OutputStreamWriter를 연결하지 않고 FileWriter를 직접 생성할 수 있다. FileWriter는OutputStream의 자식 클래스이다. 이것은 FileWriter가 내부적으로 FileOutputStream에 OutputStreamWriter 보조 스트림을 연결한 것이라고 볼 수 있다.
[ 성능 향상 스트림 ]
CPU와 메모리가 아무리 뛰어나도 하드 디스크의 입출력이 늦어지면 프로그램의 실행 성능은 하드 디스크의 처리 속도에 맞춰진다. 네트워크로 데이터를 전송할 떄도 느린 네트워크 환경이라면 컴퓨터 사양이 아무리 좋아도 메신저와 게임의 속도는 느릴 수 밖에 없다. 이 문제에 대한 완전한 해결책은 될 수 없지만, 프로그램이 입출력 소스와 직접 작업하지 않고 중간에 메모리 버퍼 ( buffer )와 작업함으로써 실행 성능을 향상시킬 수 있다. 출력 스트림의 경우 직접 하드 디스크에 데이터를 보내지 않고 메모리 버퍼에 데이터를 보냄으로써 출력 속도를 향상시킬 수 있다. 버퍼는 데이터가 쌓이기를 기다렸다가 꽉 차게 되면 데이터를 한꺼번에 하드 디스크로 보냄으로써 출력 횟수를 줄여준다.
입력 스트림에서도 버퍼를 사용하면 읽기 성능이 좋아진다. 하드 디스크로부터 직접 읽는 것 보다는 메모리 버퍼로부터 읽는 것이 빠르다.
위와 같이 메모리 버퍼를 제공하여 프로그램의 실행 성능을 향상시키는 보조 스트림이 있다. 바이트 스트림에는 BufferedInputStream, BufferedInputStream 이 있고 문자 스트림에는 BufferedReader, BufferedWriter가 있다. 보조 스트림을 연결하는 방법은 다음과 같다.
BufferedInputStream bis = new BufferedInputStream ( 바이트 입력 스트림 );
BufferedInputStream bis = new BufferedInputStream ( 바이트 출력 스트림 );
BufferedReader br = new BufferedReader ( 문자 입력 스트림 );
BufferedWriter bw = new BufferedWriter ( 문자 출력 스트림 );
문자 입력 스트림 Reader에 BufferedReader를 연결하면 성능 향상뿐만 아니라 좋은 점이 한가지 더 있는데, 행 단위로 문자열을 읽는 매우 편리한 readLine() 메소드를 제공한다는 것이다. 다음은 문자 파일을 행 단위로 읽는 코드를 보여준다.
BufferedReader br = new BufferedReader ( new FileReader("..."));
while(true) {
String str = br.readLine(); // 파일에서 한 행씩 읽음
if(str === null ) break; // 더 이상 읽을 행이 없을 경우(파일 끝) while 문 종료
}
[ 기본 타입 스트림 ]
바이트 스트림에 DataInputStream과 DataOutputStream 보조 스트림을 연결하면 기본 타입인 boolean, char, short, int, long, float, double 값을 입출력할 수 있다.
다음은 DataInputStream과 DataOutputStream 보조 스트림을 연결하는 코드이다.
DataInputStream dis = new DataInputStream ( 바이트 입력 스트림 ); // (바이트입력스트림)이 주인공 객체
DataOutputStream dos = new DataOutputStream ( 바이트 출력 스트림 );
다음은 DataInputStream과 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 | writeUFT(String str) |
이 메소드를 사용해 입출력할 때 한 가지 주의할 점이 있다. 데이터 타입의 크기가 모두 다르므로 DataOutputStream으로 출력한 데터를 다시 DataInputStream으로 읽어 올 때에는 출력한 순서와 동일한 순서로 읽어야 한다는 것이다. 예를 들어 출력할 때의 순서가 int -> Boolean -> double 이라면 읽을 때 순서도 int -> Boolean -> double 이어야 한다.
[ 프린트 스트림 ]
PrintStream과 PrintWriter는 프린터와 유사하게 출력하는 print(), println(), printf() 메소드를 가지고 있는 보조 스트림이다. 지금까지 우리는 콘솔에 출력하기 위해 System.out.println()을 사용하였는데, 그 이유는 out이 PrintStream 타입이기 때문이다.
PrintStream은 바이트 출력 스트림과 연결되고, PrintWriter는 문자 출력 스트림과 연결된다.
PrintStream ps - new PrintStream(바이트 출력 스트림);
PrintWriter pw = new PrintWriter(문자 출력 스트림);
PrintStream과 PrintWriter는 거의 같은 메소드를 가지고 있다. println() 메소드는 출력할 데이터 끝에 줄바꿈 문자인 '\n'을 더 추가시키기 떄문에 콘솔이나 파일에서 줄바꿈이 일어난다. 그러나 print() 메소드는 줄바꿈 없이 계속해서 문자를 출력시킨다. printin()과 print() 메소드는 출력할 데이터 타입에 따라 다음과 같이 재정의 된다.
PrintStream / PrintWriter | |||
void | print(boolean b) | void | println(boolean b) |
void | print(char c) | void | printin(char c) |
void | print(double d) | void | printin(double d) |
void | print(float f) | void | printin(float f) |
void | print(int i) | void | printin(int i) |
void | print(long l) | void | printin(long l) |
void | print(Object obj) | void | printin(Object obj) |
void | print(String s) | void | printin(String s) printin() |
printf() 메소드는 형식화된 문자열 ( format string )을 출력한다.
[ 객체 스트림 ]
자바는 메모리에 생성된 객체를 파일 또는 네트워크로 출력할 수 있다. 객체를 출력하려면 필드값을 일렬로 늘어선 바이트로 변경해야 하는데, 이것을 직렬화 ( serialization )라고 한다. 반대로 직렬화된 바이트를 객체의 필드값으로 복원하는 것을 역질렬화 ( deserialization ) 라고 한다. ObjectInputStream과 OutputStream은 객체를 입출력할 수 있는 보조 스트림이다. ObjectOutputStream은 바이트 출력 스트림과 연결되어 객체를 직렬화하고, ObjectInputStream은 바이트 입력 스트림과 연결되어 객체로 복원하는 역직렬화를 한다.
다음은 ObjectIntputStream과 ObjectOutputStream 보조 스트림을 연결하는 코드이다.
ObjectInputStream ois = new ObjectinputStream(바이트 입력 스트림);
ObjectOutputStream oos = new ObjectOutputStream(바이트 출력 스트림);
ObjectOutputStream으로 객체를 직렬화하기 위해서는 writeObject() 메소드를 사용한다.
oos.writeObject(객체);
반대로 ObjectInputStream의 readObject() 메소드를 읽은 바이트를 역직렬화해서 객체로 생성한다. readObject() 메소드의 리턴 타입은 Object이므로 구체적인 타입으로 강제 타입 변환해야 한다.
객체타입 변수 = (객체타입) ois.readObject();
[ ObjectOutputStream ObjectInputStream ]
import java.io.*;
class Rec implements Serializable{
int a,b;
Rec(int a, int b){
this.a=a;
this.b=b;
}
void show() {
System.out.println(a+" "+b);
}
}
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//역직렬화
ObjectInputStream in=new ObjectInputStream(new FileInputStream("obj.dat"));
Rec r1=(Rec)in.readObject();
Rec r2=(Rec)in.readObject();
String r3=(String)in.readObject();
r1.show();
r2.show();
System.out.println(r3);
/*ObjectOutputStream os=null;
os=new ObjectOutputStream(new FileOutputStream("obj.dat"));
//Rec r=new Rec(4,7);
os.writeObject(new Rec(4,7));
os.writeObject(new Rec(8,5));
os.writeObject(new String("glgl"));
os.close();*/
}
}
※ Serializable 인터페이스 : 이전 예제를 보면 Member와 Product는 Serializable 인터페이스를 구현하고 있다. 자바는 Serializable 인터페이스를 구현한 클래스만 직렬화할 수 있도록 제한한다. Serializable 인터페이스는 멤버가 없는 빈 인터페이스이지만, 객체를 직렬화할 수 있다고 표시하는 역할을 한다. 객체가 직렬화될 때 인스턴스 필드값은 직렬화 대상이지만 정적 필드값과 transient로 선언된 필드 값은 직렬화에서 제외되므로 출력되지 않는다.
public class XXX implements Serializable {
public int field1; -> 직렬화 일렬로 늘어선 바이트 데이터
protected int field2; -> field1 field2 field3 field4
int field3; ->
private int field4; ->
public static int field5; //정적 필드는 직렬화에서 제외
transient int field6; //transient로 선언된 필드는 직렬화에서 제외
}
※ serialVersionUID 필드 : 직렬화할 때 사용된 클래스와 역직렬화할 때 사용된 클래스는 기본적으로 동일한 클래스여야 한다. 만약 클래스의 이름이 같더라도 클래스의 내용이 다르면 역직렬화에 실패한다. 다음 코드를 보자. 왼쪽 Member 클래스로 생성한 객체를 직렬화하면 오른쪽 Member 클래스로 역직렬화할 수 없다. 그 이유는 오른쪽 Member 클래스에는 field3이 있기 때문이다.
클래스의 내용이 다르다 할지라도 직렬화된 필드를 공통으로 포함하고 있다면 역직렬화할 수 있는 방법이 있다. 두 클래스가 동일한 serialVersio
nUID 상수값을 가지고 있으면 된다.
serialVersionUID의 값은 개발자가 임의로 줄 수 있지만 가능하다면 클래스마다 다른 유일한 값을 갖도록 하는 것이 좋다. 이클립스는 serialVersionUID 필드를 자동 생성하는 기능을 제공한다. 먼저 클래스에 implements Serializable을 붙인 다음, 마우스를 클래스 이름에 갖다 대면 다음과 같이 ADD generated serial version ID 링크가 나온다. 이것을 클릭하면 자동으로 serialVersionUID 필드가 소스에 추가된다.
[ File과 Files 클래스 ] ( 잘 사용 x )
java.io 패키지와 java.nio.file 패키지는 파일과 디렉토리 정보를 가지고 있는 File과 Files 클래스를 제공한다. Files는 File을 개선한 클래스로, 좀 더 많은 기능을 가지고 있다.
※ File 클래스 : File클래스로부터 File 객체를 생성하는 방법은 다음과 같다.
File file = new File("경로")
경로 구분자는 운영체제마다 조금씩 다르다. 윈도우에서는 \\또는 /를 둘다 사용할 수 있고, 맥 OS 및 리눅스에서는 /를 사용한다. 다음은 윈도우에서 File 객체를 생성하는 코드이다.
File file = new File("C:/Temp/file.txt");
File file = new File("C:\\Temp\\file.txt");
File 객체를 생성했다고 해서 파일이나 디렉토리가 생성되는 것은 아니다. 그리고 경로에서 실제 파일이나 디렉토리가 없더라도 예외가 발생하지 않는다. 파일이나 디렉토리가 실제 있는지 확인하고 싶다면 File 객체를 생성하고 나서 exists() 메소드를 호출해보면 된다.
boolean isExist = file.exists(); //파일이나 폴더가 존재한다면 true를 리턴
exists() 메소드가 false를 리턴할 경우, 다음 메소드로 파일 또는 폴더를 생성할 수 있다.
리턴 타입 | 메소드 | 설명 |
boolean | createNewFile() | 새로운 파일을 생성 |
boolean | mkdir() | 새로운 디렉토리를 생성 |
boolean | mkdirs() | 경로상에 없는 모든 디렉토리를 생성 |
exists() 메소드가 true를 리턴할 경우, 다음 메소드로 파일 또는 폴더를 생성할 수 있다.
리턴 타입 | 메소드 | 설명 |
boolean | delete() | 파일 또는 디렉토리 삭제 |
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() | 디렉토리에 포함된 파일 및 서브 디렉토리 목록 전부를 String 배열로 리턴 |
String[] | list(FilenameFilter filter) | 디렉토리에 포함된 파일 및 서브 디렉토리 목록 중에 FilenameFilter에 맞는 것만 String 배앨로 리턴 |
File[] | listFiled() | 디렉토리에 포함된 파일 및 서브 디렉토리 목록 전부를 File 배열로 리턴 |
File[] | listFiles(FilenameFilter filter) | 디렉토리에 포함된 파일 및 서브 디렉토리 목록 중에 FilenameFilter에 맞는 것만 File 배열로 리턴 |
※ Files 클래스 : Files 클래스는 정적 메소드로 구성되어 있기 때문에 File 클래스처럼 객체로 만들 필요가 없다. Files의 정적 메소드는 운영체제의 파일 시스템에게 파일 작업을 수행하도록 위임한다. 다음은 Files 클래스가 가지고 있는 정적 메소드를 기능벼롤 정리한 표이다.
기능 | 관련 메소드 |
복사 | copy |
생성 | createDirectories, createDirectory, createFile, createLink, createSymbolicLink, createTempDirectory, createTempFile |
이동 | move |
삭제 | delete, deleteExists |
존재, 검색, 비교 | exists, notExists, find, mismatch |
속성 | getLastModifiedTime, getOwner, getPosixFilePermission, isDirectory, isExecutable, isHidden, isReadable, isSymbolicLink, isWritable, size, setAttribute, setLastModifiedTime, setOwner, setPosixFilePermissions, probeContentType |
디렉토리 탐색 | list, newDirectoryStream, walk |
데이터 입출력 | newInputStream, newOutputStram, newBufferedReader, newBufferedWriter, readAllBytes, lines, readAllLines, readString, readSymbolicLink, write, writeString |
이 메소드들은 매개값으로 Path 객체를 받는다. Path 객체는 파일이나 디렉토리를 찾기 위한 경로 정보를 가지고 있는데, 정적 메소드인 get() 메소드로 다음과 같이 얻을 수 있다.
Path path = Paths.get(String first, String ... more)
get() 메소드의 매개값은 파일 경로인데, 전체 경로를 한꺼번에 지정해도 좋고 상위 디렉토리와 하위 디렉토리를 나열해서 지정해도 좋다. 다음은 "C:/Temp/dir/file.txt"경로를 이용해서 Path 객체를 얻는 방법을 보여준다.
Path path = Paths.get("C:/Temp/dir/file.txt")
Path path = Paths.get("C:/Temp/dir", "file.txt")
Path path = Paths.get("C:", "Temp", "dir", f"ile.txt")
파일 경로는 절대 경로와 상대 경로를 모두 사용할 수 있다. 만약 현재 디렉토리 위치가 "C:\Temp"일 경우 "C:\Temp\dir\file.txt"는 다음과 같이 지정이 가능하다.
Path path = Paths.get("dir/file.txt")
Path path = Paths.get("./dir/file.txt")
현재 위치가 "C:\Temp\dir1"이라면 "C:\Temp\dir2\file.txt"는 다음과 같이 지정이 가능하다.
.이 현재 디렉토리라면 .. 은 상위 디렉토리를 말한다.
Path path = Paths.get("../dir2/file.txt")
probeContentType() 메소드는 파일 확장명에 따른 파일 유형을 리턴한다. 예를 들어 .txt 파일은 text/plain으로, jpg파일은 image/jpeg로 리턴한다. Files는 입출력 스트림을 사용하지 않아도 파일의 데이터를 쉽게 읽고 쓸 수 있다. writeString() 메소드는 문자열을 파일에 저장하고, readString() 메소드는 텍스트 파일의 내용을 전부 읽고 String으로 리턴한다.
[ 복습 연습 ]
Q. 콘솔창에 입력한 문자열 파일에 저장
import java.io.*;
public class Test {
public static void main(String[] args) {
//콘솔창에 문자를 입력해서 입력한 문자를
//파일에 저장
BufferedReader bu=null; //입력하기 위한 문자형 입력 스트림
PrintWriter pr=null; //출력하기 위한 문자형 출력 스트림
try {
InputStreamReader in= new InputStreamReader(System.in);
bu=new BufferedReader(in);
//콘솔에 입력!
//BufferedReader bu=new BufferedReader(new InputStreamReader(System.in));
FileWriter fw= new FileWriter("d.txt");
//d.txt에 저장
pr=new PrintWriter(fw);
String str=null;
while((str=bu.readLine())!=null) {
pr.println(str);
}
bu.close();
}catch(Exception e) {}
finally {
pr.close();
}
}
}