1. 자바에서의 입출력
1.1 입출력이란?
- Input & Ouput => 입출력
1.2 스트림(stream)
- 입출력을 수행하려면 (어느 한쪽에서 한 쪽으로 데이터를 전달하려면) 데이터를 전송할 수 있는 무언가가 필요하다.
- "스트림"이란 데이터를 운반하는데 사용되는 연결 통로이다.
- 스트림은 단방향 통신만 가능하기 때문에 입력, 출력을 동시에 처리할 수 없다.
- 스트림은 Queue와 같이 선입선출 구조로 되어있어서 먼저 입력된 데이터가 먼저 출력된다.
1.3 바이트기반 스트림 - InputStream, OutputStream
- 스트림은 바이트 단위로 데이터를 전송하며 입출력 대상에 따라 다음과 같은 스트림이 있다.
- InputStream: read() 오버라이드하고 OutputStream: write() 오버라이드
- 파일: FileInput(Output)Stream
- 메모리(byte)배열: ByteArrayInput(Output)Stream
- 프로세스(프로세스간의 통신): PipedInput(Output)Stream
- 오디오 장치: AudioInput(Output)Stream
1.4 보조 스트림
- 스트림의 기능을 보완하기 위해 보조 스트림이 등장했다.
- BufferedInputStream: 입력 성능 향상
- 스트림을 먼저 생성한 다음에 이를 이용해서 보조스트림을 생성한다.
- 보조 스트림은 실제 데이터를 주고 받는 스트림이 아니다. 스트림의 기능을 향상하거나 새로운 기능을 추가할 수 있다.
1.5 문자기반 스트림 - Reader, Writer
- 문자 데이터를 입출력 할 때는 문자기반 스트림을 사용한다.
- 바이트 기반 스트림은 입출력 단위가 모두 1byte 이다.
- 그런데 Java는 C언어와 다르게 char 타입이 2byte 이기 때문에 바이트 기반의 스트림으로 2byte인 문자를 처리하는데 어려움이 있다.
- 이를 보완하기 위한 것이 '문자 기반 스트림'
- InputStream -> Reader
- OutputStream -> Writer
2. 바이트기반 스트림
2.1 InputStream과 OutputStream
- 둘다 바이트 기반 스트림의 조상이다.
- 스트림의 종류에 따라서 mark()와 reset()을 사용해서 이미 읽은 데이터를 되돌려 읽을 수 있다.
2.2 ByteArrayInputStream과 ByteArrayOutputStream
- 메모리/ 바이트 배열에 데이터를 입출력하는데 사용되는 스트림이다.
Ex 1) 바이트 배열 스트림을 이용해서 inSrc를 outSrc로 복사
<hide/>
byte[] inSrc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
byte[] outSrc = null;
ByteArrayInputStream input = null;
ByteArrayOutputStream output = null;
input = new ByteArrayInputStream(inSrc);
output = new ByteArrayOutputStream();
int data = 0;
while((data = input.read()) != -1){
output.write(data);
}
outSrc = output.toByteArray();
System.out.println("Input source " + Arrays.toString(inSrc));
System.out.println("Output source " + Arrays.toString(outSrc));
- read, write 사용 방법
- input stream에 바이트 배열을 매개변수로 넣은 다음, read()를 통해 데이터를 읽어온다.
cf) 블로킹(blocking)이란?
- 어떤 작업이 완료될 때까지 프로그램의 실행이 일시 중지되는 상황을 의미한다.
- ex) 어떤 사용자가 데이터를 입력할 때까지 기다리는 경우도 블로킹에 해당한다.
- ex 1) 파일이나 네트워크에서 데이터를 읽거나 쓸 때, 데이터가 사용가능해질 때까지 프로그램이 블록(일시 정지)된다.
- ex 2) 동기화: 멀티스레드 프로그래밍에서 한 스레드가 다른 스레드를 기다려야하는 경우, 블락된다.
- 즉, 교착 상태는 블로킹에 포함된다.
- ex 3) 이벤트 대기: 이벤트 기반 프로그래밍에서 프로그램은 특정 이벤트가 발생할 때까지 기다릴 수 있다. Spring의 @EventListener 도 이에 해당한다.
Ex 3)
<hide/>
import java.io.*;
import java.util.Arrays;
public class IOEx3 {
public static void main(String[] args) {
byte [] inSrc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
byte [] outSrc = null;
byte [] temp = new byte[4];
ByteArrayInputStream input = null;
ByteArrayOutputStream output = null;
input = new ByteArrayInputStream(inSrc);
output = new ByteArrayOutputStream();
System.out.println("Input Source:" + Arrays.toString(inSrc));
try { // read나 write가 예외를 발생시킬 수 있어서 try - catch문으로 감싼다.
while (input.available() > 0) { // available()은 블락킹 없이 읽어올 수 있는 바이트의 수를 반환한다.
input.read(temp);
output.write(temp);
System.out.println("temp:" + Arrays.toString(temp));
outSrc = output.toByteArray();
printArrays(temp, outSrc);
}
}catch(IOException e) {}
}
static void printArrays(byte [] temp, byte [] outSrc) {
System.out.println("temp:" + Arrays.toString(temp));
System.out.println("Output Source:" + Arrays.toString(outSrc) );
}
}
- Ex 1) 에서는 read()를 사용했으나 이번에는 available() 사용
- available(): 블라킹(blocking) 없이 읽어올 수 있는 바이트의 수를 반환한다. 다시 말해, 블로킹 없이 즉시 액세스 가능한 데이터 양을 나타낸다.
- Output Source를 보면 뒤에 "6, 7"이 함께 출력된다.
- 그 이유는 마지막에 읽은 배열의 9번째와 10번째 요소 값인 8, 9만 출력해야하는데 temp에 남아있는 6, 7까지 출력했기 때문이다.
- 더 나은 성능을 위해 temp에 담긴 내용을 기존 내용에 덮어쓰기 때문이다.
- [4, 5, 6, 7] -- (8과 9을 읽고 난 후) --> [8, 9, 6, 7]이 된다.
- 해결 방법: read, write 수정
- 예제에서는 배열 내용 전체를 출력하지만 수정한 코드에서는 배열의 길이까지만 출력한다.
Note) 실행 결과 - 전
- 위의 read, write를 아래와 같이 수정한다.
- write(temp): 배열 temp 내용 전체를 출력한다.
- write(temp,0, len): 0번째 인덱스부터 len 개의 데이터를 출력한다.
int len = input.read(temp);
output.write(temp, 0, len);
Note) 실행 결과 - 후
2.3 FileInput
Stream과 FileOutputStream
- 파일에 입출력 하기 위한 스트림이다
Ex)
<hide/>
import java.io.*;
public class FileViewer {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(args[0]);
int data = 0;
while( (data = fis.read()) != - 1) {
char c = (char) data;
System.out.print(c);
}
}
}
Note) 커맨드 라인으로부터 입력받은 파일의 내용을 읽어서 그대로 화면에 출력하는 예제이다.
- read()의 반환값이 int형이지만, -1을 제외하고는 0~255가 정숫값이므로 char형으로 변환해도 손실되는 값 없다.
- 텍스트 파일을 다룰 때에는 FileInputStream이나 FileOutputStream보다는
-> 문자기반의 FileReader/FileWriter가 더욱 적합하다.
3. 바이트기반의 보조스트림
3.1 FilterInputStream과 FilterOutputStream
3.2 BufferedInputStream과 BufferedOutputStream
- 스트림의 효율을 높이기 위해 버퍼(바이트 배열)를 사용하는 보조스트림이다.
- 버퍼 크기는 입력 소스로부터 한 번에 가져올 수 있는 크기로 지정하면 좋다.
Ex) BufferOutputStream
<hide/>
import java.io.*;
public class BufferOutputStreamEx1 {
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream("123.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos, 5); // 버퍼 크기를 5로 한다.
for(int i ='1' ; i <= '9'; ++i) { // // 파일 123.txt에 1부터 9까지 출력한다.
bos.write(i);
}
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 위 파일을 실행하면 소스 루트에 "123.txt" 파일이 생성되고 파일 안에 "12345"라는 값을 조회할 수 있다.
- 크기가 5인 BufferedOutputStream을 이용해서 파일123.txt에 1부터 9까지 출력하는 예제
- 1~5까지만 출력이 되는데 버퍼에 남아있는 데이터가 출력되지 못한 상태로 프로그램이 종료되기 때문이다.
- fos.close() : 스트림을 닫는다.
- bos.close()와 같이 해서 close()를 호출해줘야 버퍼에 남은 모든 내용이 출력 된다.
- BufferedOutputStream의 close()는 기반 스트림인 FileOutputStream의 close()를 호출하기 때문에
- FileOutputStream의 close()를 따로 호출하지 않아도 된다.
Note) 실행 결과
3.3 DataInputStream과 DataOutputStream
- BufferedInputStream / BufferedOutputStream의 자손이며
- DataInputStreamsms DataInput인터페이스를 DataOutputStream는 DataOutput인터페이스를 각각 구현.
-> 데이터를 읽고 쓰는데 있어서 byte단위가 아닌 8가지 기본 자료형 단위로 읽고 쓸 수 있다.
Ex)
<hide/>
import java.io.*;
import java.util.Arrays;
public class DataOutputStreamEx1 {
public static void main(String[] args) {
ByteArrayOutputStream bos = null;
DataOutputStream dos = null;
byte [] result = null;
try {
bos = new ByteArrayOutputStream();
dos = new DataOutputStream(bos);
dos.writeInt(10);
dos.writeFloat(20.0f);
dos.writeBoolean(true);
result = bos.toByteArray();
String [] hex = new String[result.length];
for(int i = 0 ; i < result.length; ++i) {
if(result[i] < 0)
hex[i] = String.format("%02x", result[i] + 256);
else
hex[i] = String.format("%02x", result[i]);
}
System.out.println("10진수 : " + Arrays.toString(result));
System.out.println("16진수 : " + Arrays.toString(hex));
dos.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
Note) 실행 결과
- writeInt(10)
-> 첫번째 4byte 0 0 0 10, 00/ 00, 00, 0a
- writeFolat(20.0f)
-> 두번째 4byte 65 -96 0 0 / 41, a0, 00, 00
- writeBoolean(true)
-> 1/ 01출력
- DataArrayInput(Output)Stream을 사용하면 byte단위의 데이터 변환 및 조작이 가능하다.
-> 데이터를 변환할 필요도 없고 자리수를 세서 따질 필요 없어서 빠르고 편리.
Ex 3) DataOutputStreamEx3
<hide/>
public class DataOutputStreamEx3 {
public static void main(String[] args) {
int [] score = {100, 90, 95, 85, 50};
try{
FileOutputStream fos = new FileOutputStream("score.bat");
DataOutputStream dos = new DataOutputStream(fos);
for(int i = 0; i < score.length; ++i){
dos.writeInt(score[i]);
}
dos.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
Note) 실행 결과
- type 명령어를 이용하면 score.bat 파일 내용을 확인 가능
- 16진수 두 자리는 1byte
Ex) DataInputStreamEx2
<hide/>
import java.io.*;
public class DataInputStreamEx2 {
public static void main(String[] args) {
int sum = 0;
int score = 0;
FileInputStream fis = null;
DataInputStream dis = null;
try {
fis = new FileInputStream("score.dat");
dis = new DataInputStream(fis);
while(true) {
score = dis.readInt();
System.out.println(score);
sum += score;
}
}catch(EOFException e) {
System.out.println("점수의 총합은 " + sum + "입니다.");
}catch(IOException ie) {
ie.printStackTrace();
}finally {
try {
if ( dis != null)
dis.close();
}catch(IOException ie) {
ie.printStackTrace();
}
}
}
}
- DataInputStream의 readInt()와 같이 데이터를 읽는 메서드는 더 이상 읽을 데이터가 없으면 EOFException을 발생시킨다.
- 따라서 무한 반복문과 EOFException을 처리하는 catch문을 이용해서 데이터를 읽는다.
- EOFException(End Of FileException): 더 이상 읽을 데이터 없을 때 발생. 데이터를 읽는 동안 파일끝에 도달하여 읽을 데이터가 없는 경우 발생
- while문이 무한 반복문이므로 while이 아닌 finally절에서 스트림을 닫는다.
- dis.close()
- 참조변수 dis가 null이면 NullPoitnterException이 발생하므로 if문을 사용해서 null인지 체크 후, close() 호출
- close()는 IOException을 발생시킬 수 있으므로 try-catch로 감싸준다.
- try-with-resources 문을 이용하면 close() 자동 호출 된다.
Note) 실행 결과
3.4 SequenceInputStream
- 여러 개의 입력스트림을 연속적으로 연결해서 하나의 스트림으로부터 데이터를 읽는 것과 같이 처리할 수 있다.
Ex)
<hide/>
import java.io.*;
import java.util.*;
public class SequenceInputStreamEx {
public static void main(String[] args) {
byte [] arr1 = {0, 1, 2};
byte [] arr2 = {3, 4, 5};
byte [] arr3 = {6, 7, 8};
byte [] outSrc = null;
Vector v =new Vector();
v.add(new ByteArrayInputStream(arr1));
v.add(new ByteArrayInputStream(arr2));
v.add(new ByteArrayInputStream(arr3));
SequenceInputStream input = new SequenceInputStream(v.elements());
ByteArrayOutputStream output =new ByteArrayOutputStream();
int data = 0;
try {
while((data = input.read()) != -1) {
output.write(data);
}
}catch(IOException e) {}
outSrc = output.toByteArray();
System.out.println("Input Source1 : " + Arrays.toString(arr1));
System.out.println("Input Source2 : " + Arrays.toString(arr2));
System.out.println("Input Source3 : " + Arrays.toString(arr3));
System.out.println("Output Source : " + Arrays.toString(outSrc));
}
}
Note) 실행 결과
- 3개의 ByteArrayInputStream은 Vector와 SequenceInputStream을 이용해서 하나의 입력 스트림처럼 다룰 수 있다.
- Vector에 저장된 순서대로 입력된다.
3.5 PrintStream
- 데이터를 기반 스트림에 다양한 형태로 출력할 수 있는 print, println, printf와 같은 메서드를 오버로딩하여 제공
- 문자 기반 스트림의 역할
4. 문자기반 스트림
4.1 Reader와 Writer
- 문자 기반의 스트림 조상 -> Reader & Writer
- 바이트 기반 스트림의 조상 -> InputStream & OutputStream
4.2 FileReader와 FileWriter
- 파일로부터 텍스트 데이터를 읽고 파일에 쓰는데 사용된다.
4.3 PipedReader와 PipedWriter
- 스레드간에 데이터를 주고받을 때 사용된다.
- 입력, 출력 스트림을 하나의 스트림으로 연결해서 스트림을 주고 받는다.
- 스트림 생성 후, 한 쪽 스레드에서 connect()를 호출해서 입력 스트림과 출력 스트림을 연결한다.
Ex)
<hide/>
import java.io.*;
public class PipedReaderWriter {
public static void main(String[] args) {
InputThread inThread = new InputThread("InputThread");
OutputThread outThread = new OutputThread("OutputThread");
inThread.connect(outThread.getOutput());
inThread.start();
outThread.start();
}
}
class InputThread extends Thread{
PipedReader input = new PipedReader();
StringWriter sw = new StringWriter();
InputThread(String name ){
super(name); // Thread(String name);
}
public void run() {
try {
int data = 0;
while( (data = input.read()) != -1 ) {
sw.write(data);
}
System.out.println(getName() + "received : " + sw.toString());
}catch(IOException e) {}
}
public PipedReader getInput() {
return input;
}
public void connect(PipedWriter output) {
try {
input.connect(output);
}catch(IOException e) {}
}
}
class OutputThread extends Thread{
PipedWriter output = new PipedWriter ();
OutputThread(String name){
super(name); // Thread(String name);
}
public void run() {
try {
String msg="Hello";
System.out.println(getName()+ "sent : " + msg);
output.write(msg);
output.close();
}catch(IOException e) {}
}
public PipedWriter getOutput() {
return output;
}
public void connect(PipedReader input) {
try {
output.connect(input);
}catch(IOException e) {}
}
}
Note) 실행결과
- 두 쓰레드가 PipedThread/ PipedWriter를 이용해서 서로 메시지를 주고 받는다.
- 쓰레드를 시작하기 전에 둘을 서로 연결해야한다.
4.4 StringReader와 StringWriter
- StringBuffer g etBuffer(): StringWriter에 출력한 데이터가 저장된 StringBuffer를 반환
- String toString(): StringWriter에 출력된 문자열을 반환한다.
5. 문자기반의 보조스트림
5.1 BufferedReader와 BufferedWriter
- 버퍼를 이용해서 입출력의 효율을 높일 수 있도록 해주는 역할
Ex)
import java.io.*;
public class BufferedReaderEx1 {
public static void main(String[] args) {
try {
FileReader fr = new FileReader("BufferedReaderEx1.java");
BufferedReader br = new BufferedReader(fr);
String line = "";
for(int i = 1; (line = br.readLine()) != null; ++i) {
if(line.indexOf(";") != -1)
System.out.println(i + ":" + line);
}
br.close();
}catch( IOException E) {}
}
}
Note) 오류는 없는데 실행이 안 된다..
5.2 InputStreamReader와 OutputStreamWriter
- 바이트 기반 스트림을 문자기반 스트림으로 연결시켜준다.
- 바이트 기반 스트림의 데이터를 지정된 인코딩의 문자 데이터로 변환한다.
Ex)
<hide/>
import java.io.*;
public class InputStreamReaderEx {
public static void main(String[] args) {
String line = "";
try {
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
System.out.println("사용중인 OS의 인코딩 : " + isr.getEncoding());
do {
System.out.print("문장을 입력하세요. 마치시려면 q를 입력하세요. >");
line = br.readLine();
System.out.println("입력하신 문장 : " + line);
}while( !line.equalsIgnoreCase("q"));
System.out.println("프로그램을 종료합니다.");
}catch (IOException e) {}
}
}
Note) 실행 결과
- BufferedReader의 readLine()을 이용해서 사용자의 화면 입력을 라인 단위로 입력 받을 수 있다.
- BufferedReader와 InputStream을 연결하기 위해 InputStreamReader을 이용한다.
6. 표준입출력과 File
6.1 표준입출력 - System.in, System.out, System.err
1) 입력
- System.in.(read) : 콘솔로부터 데이터를 입력받는데 사용
- InputStreamReader reader = ...
- BufferedReader br = ...
- Scanner ...
2) 출력
- print/ printf/ println
- System.out: 콘솔로 데이터를 출력하는데 사용
- System.err: 콘솔로 데이터를 출력하는데 사용
- in, out, err은 System클래스에 선언된 클래스 변수(static 변수)이다.
Ex)
<hide/>
import java.io.*;
public class StandardIOEx1 {
public static void main(String[] args) {
try {
int input = 0;
while( (input = System.in.read()) != -1) {
System.out.println("input : " + input + " , (char)input :" + (char)input);
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
Note) 실행결과
- 실행하여 System.in.read()가 호출되면 코드 진행이 멈추고 콘솔에 커서가 깜빡 거린다.
- Enter키를 누르는 것은 두 개의 특수문자 \r과 \n이 입력된 것으로 간주된다.
-> \r: 캐리지 리턴(커서를 현재 라인의 첫번째 컬럼으로 이동시킨다.)
-> \n: 줄바꿈
6.2 표준입출력의 대상 변경 - setOut(), setErr(), setIn()
- setOut(): System.out의 출력을 지정된 PrintStream으로 변경
- setErr(): System.err의 출력을 지정한 PrintStream으로 변경
- setIn(): System.in의 입력을 지정한 InputStream으로 변경
Ex)
import java.io.*;
public class StandersIOEx3 {
public static void main(String[] args) {
PrintStream ps = null;
FileOutputStream fos = null;
try {
fos = new FileOutputStream("test.txt");
ps = new PrintStream(fos);
System.setOut(ps);
}catch(FileNotFoundException e) {
System.err.println("File not found.");
}
System.out.println("Hello by System.out");
System.out.println("Hello by System.err");
}
}
Note)
6.3 RandomAccessFile
- 자바에서는 입/출력이 각각 분리되어 별도록 작업하도록 설계된다.
- 이와 다르게 RandomAccessFiles는 하나의 클래스로 파일에 대한 입/출력을 모두 할 수 있도록 되어있다.
- 파일 어느 위치에서나 읽기 쓰기가 가능하다.
- 입출력 시에 작업이 수행되는 곳이 "파일 포인터"가 위치한 곳이 된다.
Ex)
<hide/>
import java.io.*;
public class RandomAccessFileEx1 {
public static void main(String[] args) {
try {
RandomAccessFile raf = new RandomAccessFile("test.dat", "rw");
System.out.println("파일 포인터의 위치 : " + raf.getFilePointer());
raf.writeInt(100);
System.out.println("파일 포인터의 위치 : " + raf.getFilePointer());
raf.writeLong(100L);
System.out.println("파일 포인터의 위치 : " + raf.getFilePointer());
}catch(IOException e) {
e.printStackTrace();
}
}
}
Note) 실행 결과
- 출력 작업이 수행될 때 파일 포인터의 위치가 어떻게 달라지는지 보여준다.
- int는 4byte이므로 writeInt()를 호출한 다음 파일 포인터 위치는 0->4로 바뀐다.
- long은 8byte이므로 writeLong()를 호출한 다음에서 포인터 위치가 4 -> 12로 바뀐다.
- rw: reading + writing
6.4 File
- 자바에서는 File클래스를 통해서 파일고 디렉토리를 다룰 수 있다.
- File 인스턴스는 파일 일 수도 있고 디렉토리일 수도 있다.
Ex)
<hide/>
import java.io.*;
import java.util.*;
import java.text.*;
public class FileEx4 {
public static void main(String[] args) {
String currDir = System.getProperty("user.dir");
File dir = new File(currDir);
File [] files = dir.listFiles();
for(int i = 0; i < files.length; ++i) {
File f = files[i];
String name = f.getName();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mma");
String attribute = "";
String size = "";
if(files[i].isDirectory()) {
attribute = "DIR";
}else {
size = f.length() + "";
attribute = f.canRead() ? "R" : " ";
attribute += f.canWrite() ? "W" : " ";
attribute += f.isHidden() ? "H" : " ";
}
System.out.printf("%s %3s %6s %s\n" , df.format(new Date(f.lastModified())), attribute ,size, name );
}
}
}
Note) 실행 결과
- 현재 디렉토리에 속한 파일, 이름, 크기 등 상세 정보를 보여준다.
Ex) FileEx8
<hide/>
import java.io.*;
public class FileEx8 {
static int deletedFiles = 0;
public static void main(String[] args) {
if(args.length != 1) {
System.out.println("USAGE : java FileEx8 Extension");
System.exit(0);
}
String currDir = System.getProperty("user.dir");
File dir = new File(currDir);
String ext = "." + args[0];
delete(dir, ext);
System.out.println(deletedFiles + "개의 파일이 삭제되었습니다.");
}
public static void delete(File dir , String ext) {
File [] files = dir.listFiles();
for(int i = 0; i < files.length; ++i ) {
if(files[i].isDirectory()) {
delete(files[i], ext);
}else {
String filename = files[i].getAbsolutePath();
if(filename.endsWith(ext)) {
System.out.println(filename);
if(files[i].delete()) {
System.out.println("- succesessfully deleted !");
++deletedFiles;
}else {
System.out.println("- delete failed!");
}
}
}
}
}
}
Note) 실행 결과
- 한글은 깨져서 나온다.
- 확장자가 .bat인 두 개의 파일이 정상적으로 삭제된다.
7. 직렬화(Serialization)
- 객체를 컴퓨터에 저장했다가 다음에 다시 꺼내거나 네트워트를 통해 컴퓨터 간에 서로 객체를 주고 받을 수 있다.
7.1 직렬화란?
Def) 객체를 데이터 스트림으로 만드는 것이다.
- 객체에 저장된 데이터를 스트림에 쓰기 위해 연속적인 데이터로 변환하는 것
- 역직렬화(deserialization): 스트림으로부터 데이터를 읽어서 객체를 만드는 것
7.2 ObjectInputStream, ObjectOutputStream
- ObjectOutputStream: 직렬화(스트림에 객체를 출력)할 때 사용한다. (OutputStream을 상속 받는다.)
- ObjectInputStream: 역직렬화(스트림으로부터 객체를 입력)에 사용 (InputStream을 상속 받는다. )
-> 각각 OutputStream, InputStream을 상속 받지만 기반 스트림이 필요한 보조스트림이다.
-> 객체 생성할 때, 직력화/ 역직렬화할 스트림을 지정해야 한다.
- defaultReadObject(), defaultWriteObject()는 자동 직렬화를 수행하는 메서드
7.3 직렬화가 가능한 클래스 만들기 - Serializable, transient
- 직렬화하고자 하는 클래스가 java.io.Serializable인터페이스를 구현하도록 한다.
- Serializable을 구현한 클래스를 상속 받는 경우에도 직렬화가 가능하다.
- 모든 클래스의 최고 조상인 Object는 Serializable을 구현하지 않았기 때문에 직렬화 불가능
- String 클래스를 들어가보면 Serializable를 구현하므로 직렬화 가능하다.
- 인스턴스 변수의 타입이 아닌 실제로 연결된 객체의 종류에 의해서 직렬화 가능 여부가 결정된다.
Object obj = new Object(); // 직렬화 불가능
Object obj = new String("abc"); // 직렬화 가능
- 직렬화 하고 싶은 클래스에 직렬화가 안 되는 객체(Object)에 대한 참조를 포함하고 있는 경우에 transient 키워드를 붙여서 해당 부분만 직렬화되지 않도록 정할 수 있다.
- transient 키워드를 붙이면 직렬화 대상에서 제외시킨다.
- password 처럼 보안상 직렬화되면 안되는 데이터에 transient를 적용할 수 있다.
- transient 를 붙이면 기본값으로 직렬화된다.
- 다시 말해, (직렬화된) 객체를 역직렬화했을 때, transient가 붙은 변수의 값들은 null이 된다.
Ex)
<hide/>
import java.io.*;
public class UserInfo2 {
String name;
String password;
SuperUserInfo(){
this("Unknown", "1111");
}
SuperUserInfo(String name, String password){
this.name = name;
this.password = password;
}
}
public class UserInfo2 extends SuperUserInfo implements java.io.Serializable{
int age;
public UserInfo2() {
this("Unknown", "1111", 0);
}
public UserInfo2(String name, String password, int age) {
super(name, password);
this.age = age;
}
public String toString() {
return "(" + name + ", " + password + ", " + age + ")";
}
private void writeObjact(ObjectOutputStream out) throws IOException {
out.writeUTF(name);
out.writeUTF(password);
out.defaultwriteObject();
}
private void readObjact(ObjectInputStream in) throws IOException, ClassNotFoundException{
name = in.readUTF();
password = in.readUTF();
in.defaultReadObject();
}
}
Note)
- 직렬화되지 않는 조상 SuperUserInfo로부터 상속받은 인스턴스 변수(name, password)에 대한 직렬화를 구현한다.
- 직렬화된 객체의 클래스에 writeObject(), readObject()를 추가해서 상속 받은 name, password가 직접 직렬화되도록한다.
- name, password의 타입이 String -> writeUTF(), readUTF()사용
- defaultWriteObject()는 UserInfo2클래스 자신에 정의된 인스턴스 변수 age의 직렬화를 수행한다.
7.4 직렬화 가능한 클래스의 버전관리
- 직렬화된 클래스를 역직렬화 했을 때, 직렬화 했을 때와 같은 클래스를 사용해야한다.
-> 클래스 이름이 같아도 내용이 변경된 경우는 실패하여 예외(InvalidClassException) 발생한다.
- static변수나 상수, transient가 붙은 인스턴스 변수가 추가되면 직렬화에 영향 없음.
0. 이클립스 자동완성
- 어제 이클립스에 자동완성을 설정했다.
- 스페이스바만 눌러도 자동으로 입력되는 일이 계속 일어났다.
- 간단한 설정으로 자동입력 문제를 해결할 수 있었다.
<자동완성 설정하기>
1) 이클립스 맨 위의 탭 중, Window > Preferences 클릭
2) Java > Editor > Content Assist
3) 맨 아래의 Auto Active를 체크하여 triggers에
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._
를 입력한다.
4) 자동입력 방지 -> Disable insertion triggers except 'Enter' 를 체크해준다.
'Java > Java의 정석' 카테고리의 다른 글
Chapter 16 네트워킹(Networking) (0) | 2023.03.02 |
---|---|
Chapter 13 스레드(Thread) (0) | 2023.03.01 |
Chapter 14 람다와 스트림 (0) | 2022.03.04 |
Chapter 12 지네릭스, 열거형, 에너테이션 (0) | 2022.03.02 |
Chapter 11 컬렉션 프레임웍(Collection Framework) (0) | 2022.03.02 |