Java/Java의 정석

Chapter 15 입출력 I/O

계란💕 2022. 3. 7. 12:26

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은 VectorSequenceInputStream을 이용해서 하나의 입력 스트림처럼 다룰 수 있다.

  - 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' 를 체크해준다.