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로 복사

java
열기
  • read, write 사용 방법
  • input stream에 바이트 배열을 매개변수로 넣은 다음, read()를 통해 데이터를 읽어온다. 

cf) 블로킹(blocking)이란?

  • 어떤 작업이 완료될 때까지 프로그램의 실행일시 중지되는 상황을 의미한다. 
  • ex) 어떤 사용자가 데이터를 입력할 때까지 기다리는 경우도 블로킹에 해당한다. 
  • ex 1) 파일이나 네트워크에서 데이터를 읽거나 쓸 때, 데이터가 사용가능해질 때까지 프로그램이 블록(일시 정지)된다. 
  • ex 2) 동기화: 멀티스레드 프로그래밍에서 한 스레드가 다른 스레드를 기다려야하는 경우,  블락된다.
    • 즉, 교착 상태블로킹에 포함된다.  
  • ex 3) 이벤트 대기: 이벤트 기반 프로그래밍에서 프로그램은 특정 이벤트가 발생할 때까지 기다릴 수 있다. Spring의 @EventListener 도 이에 해당한다.

 

  Ex 3)

java
열기

 

  • 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)

java
열기

 

  Note) 커맨드 라인으로부터 입력받은 파일의 내용을 읽어서 그대로 화면에 출력하는 예제이다.

  - read()의 반환값이 int형이지만, -1을 제외하고는 0~255가 정숫값이므로 char형으로 변환해도 손실되는 값 없다.

  - 텍스트 파일을 다룰 때에는  FileInputStream이나 FileOutputStream보다는

    -> 문자기반의 FileReader/FileWriter가 더욱 적합하다.

 

 

3. 바이트기반의 보조스트림

  3.1 FilterInputStream과 FilterOutputStream

  3.2 BufferedInputStream과 BufferedOutputStream

  • 스트림의 효율을 높이기 위해 버퍼(바이트 배열)를 사용하는 보조스트림이다. 
  • 버퍼 크기는 입력 소스로부터 한 번에 가져올 수 있는 크기로 지정하면 좋다. 

 

  Ex) BufferOutputStream 

java
열기
  • 위 파일을 실행하면 소스 루트에 "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)

java
열기

  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

java
열기

 

 

  Note) 실행 결과

  • type 명령어를 이용하면 score.bat 파일 내용을 확인 가능
  • 16진수 두 자리는 1byte

 

 

 

  Ex) DataInputStreamEx2

java
열기
  • 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)

java
열기

  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)

java
열기

  Note) 실행결과

  - 두 쓰레드가 PipedThread/ PipedWriter를 이용해서 서로 메시지를 주고 받는다. 

  - 쓰레드를 시작하기 전에 둘을 서로 연결해야한다.

 

  4.4 StringReader와 StringWriter

  - StringBuffer g etBuffer(): StringWriter에 출력한 데이터가 저장된 StringBuffer를 반환 

  - String toString(): StringWriter에 출력된 문자열을 반환한다.

 

 

5. 문자기반의 보조스트림

  5.1 BufferedReader와 BufferedWriter

  - 버퍼를 이용해서 입출력의 효율을 높일 수 있도록 해주는 역할

  Ex)

java
닫기
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)

java
열기

  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)

java
열기

  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)

java
닫기
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)

java
열기

  Note) 실행 결과

  • 출력 작업이 수행될 때 파일 포인터의 위치가 어떻게 달라지는지 보여준다.
  • int는 4byte이므로 writeInt()를 호출한 다음 파일 포인터 위치는 0->4로 바뀐다.
  • long은 8byte이므로 writeLong()를 호출한 다음에서 포인터 위치가 4 -> 12로 바뀐다. 
  • rw: reading + writing

 

  6.4 File

  • 자바에서는 File클래스를 통해서 파일고 디렉토리를 다룰 수 있다.
    • File 인스턴스는 파일 일 수도 있고 디렉토리일 수도 있다. 

 

  Ex)

java
열기

  Note) 실행 결과

  • 현재 디렉토리에 속한 파일, 이름, 크기 등 상세 정보를 보여준다. 

 

 

  Ex) FileEx8

java
열기

  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를 구현하므로 직렬화 가능하다. 
  • 인스턴스 변수의 타입이 아닌 실제로 연결된 객체의 종류에 의해서 직렬화 가능 여부가 결정된다. 
java
닫기
Object obj = new Object();	// 직렬화 불가능
Object obj = new String("abc");	// 직렬화 가능

 

 

  • 직렬화 하고 싶은 클래스에 직렬화가 안 되는 객체(Object)에 대한 참조를 포함하고 있는 경우에 transient 키워드를 붙여서 해당 부분만 직렬화되지 않도록 정할 수 있다.
  • transient 키워드를 붙이면 직렬화 대상에서 제외시킨다. 
  • password 처럼 보안상 직렬화되면 안되는 데이터에  transient를 적용할 수 있다. 
    • transient 를 붙이면 기본값으로 직렬화된다. 
    • 다시 말해, (직렬화된) 객체를 역직렬화했을 때, transient가 붙은 변수의 값들은 null이 된다. 

 

 

  Ex)

java
열기

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