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로 복사
- read, write 사용 방법
- input stream에 바이트 배열을 매개변수로 넣은 다음, read()를 통해 데이터를 읽어온다.
cf) 블로킹(blocking)이란?
- 어떤 작업이 완료될 때까지 프로그램의 실행이 일시 중지되는 상황을 의미한다.
- ex) 어떤 사용자가 데이터를 입력할 때까지 기다리는 경우도 블로킹에 해당한다.
- ex 1) 파일이나 네트워크에서 데이터를 읽거나 쓸 때, 데이터가 사용가능해질 때까지 프로그램이 블록(일시 정지)된다.
- ex 2) 동기화: 멀티스레드 프로그래밍에서 한 스레드가 다른 스레드를 기다려야하는 경우, 블락된다.
- 즉, 교착 상태는 블로킹에 포함된다.
- ex 3) 이벤트 대기: 이벤트 기반 프로그래밍에서 프로그램은 특정 이벤트가 발생할 때까지 기다릴 수 있다. Spring의 @EventListener 도 이에 해당한다.
Ex 3)
- 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)
Note) 커맨드 라인으로부터 입력받은 파일의 내용을 읽어서 그대로 화면에 출력하는 예제이다.
- read()의 반환값이 int형이지만, -1을 제외하고는 0~255가 정숫값이므로 char형으로 변환해도 손실되는 값 없다.
- 텍스트 파일을 다룰 때에는 FileInputStream이나 FileOutputStream보다는
-> 문자기반의 FileReader/FileWriter가 더욱 적합하다.
3. 바이트기반의 보조스트림
3.1 FilterInputStream과 FilterOutputStream
3.2 BufferedInputStream과 BufferedOutputStream
- 스트림의 효율을 높이기 위해 버퍼(바이트 배열)를 사용하는 보조스트림이다.
- 버퍼 크기는 입력 소스로부터 한 번에 가져올 수 있는 크기로 지정하면 좋다.
Ex) BufferOutputStream
- 위 파일을 실행하면 소스 루트에 "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)
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
Note) 실행 결과
- type 명령어를 이용하면 score.bat 파일 내용을 확인 가능
- 16진수 두 자리는 1byte

Ex) DataInputStreamEx2
- 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)
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)
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)
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)
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)
Note) 실행 결과

- 출력 작업이 수행될 때 파일 포인터의 위치가 어떻게 달라지는지 보여준다.
- int는 4byte이므로 writeInt()를 호출한 다음 파일 포인터 위치는 0->4로 바뀐다.
- long은 8byte이므로 writeLong()를 호출한 다음에서 포인터 위치가 4 -> 12로 바뀐다.
- rw: reading + writing
6.4 File
- 자바에서는 File클래스를 통해서 파일고 디렉토리를 다룰 수 있다.
- File 인스턴스는 파일 일 수도 있고 디렉토리일 수도 있다.
Ex)
Note) 실행 결과

- 현재 디렉토리에 속한 파일, 이름, 크기 등 상세 정보를 보여준다.
Ex) FileEx8
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)
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 |