💁♀️ 입출력(IO)이란,
Input과 Output의 약자로 컴퓨터 내부 또는 외부 장치와 프로그램 간의 데이터를 주고 받는 것
장치와 입출력을 위해서는 하드웨어 장치에 직접 접근이 필요한데 다양한 매체에 존재하는 데이터들을 사용하기 위해 입출력 데이터를 처리 할 공통적인 방법으로 스트림을 이용
💁♀️ 스트림(Stream)이란,
입출력 장치에서 데이터를 읽고 쓰기 위해서 자바에서 제공하는 클래스
- 모든 스트림은 단방향이며 각각의 장치마다 연결할 수 있는 스트림 존재
- 하나의 스트림으로 입출력을 동시에 수행할 수 없으므로 동시에 수행하려면 2개의 스트림이 필요
📍 Stream 최상위 클래스의 종류
1) InputStream
2) OutputStream
3) Reader
4) Writer
read() : 입력 스트림으로부터 1바이트를 읽고 읽은 바이트 리턴
close() : 사용한 시스템 자원 반납 후 입력 스트림을 닫음
write(int b) : 출력 스트림으로 1바이트를 보냄
flush() : 버퍼에 잔류하는 모든 바이트 출력
close() : 사용한 시스템 자원 반납 후 출력 스트림을 닫음
read() : 입력 스트림으로부터 1바이트를 읽고 읽은 바이트 리턴
close() : 사용한 시스템 자원 반납 후 입력 스트림을 닫음
write(int b) : 출력 스트림으로 1바이트를 보냄
write(String str) : 출력 스트림에 매개 값으로 주어진 문자열을 보냄
flush() : 버퍼에 잔류하는 모든 바이트 출력
close() : 사용한 시스템 자원 반납 후 출력 스트림을 닫음
💁♀️ 기반 스트림이란,
기반 스트림(기본 스트림)은 외부 데이터에 직접 연결이 되는 스트림
💁 File Class란,
파일 처리를 수행하는 대표적인 클래스
대상 파일에 대한 정보로 인스턴스를 생성하고 파일의 생성, 삭제 등의 처리를 수행하는 기능을 제공
// 파일 클래스를 이용해서 인스턴스를 생성
>>> 대상 파일이 존재하지 않아도 인스턴스를 생성할 수 있음
File file = new File("src/com/greedy/section01/file/test.txt"); >>> 아직 파일 생성X
try {
// createNewFile()을 통해 파일을 생성할 수 있고 성공 실패 여부를 boolean으로 반환
boolean createSuccess = file.createNewFile();
>>> 처음에 Unhandled exception type IOException 에러 발생
>>> File 메소드가 Exception을 throw 하고 있기 때문
>>> 따라서 try&catch로 처리해야함
>>> 최초 실행 시 새롭게 파일이 만들어지므로 true가 반환되고
>>> 파일이 한번 생성되고 난 이후는 새롭게 파일을 만들지 않기 때문에 false를 반환
System.out.println(createSuccess);
>>> 최초 실행 시 true 출력. 왼쪽 file 패키지를 Refresh하면 test.txt 파일이 생성되어있음
} catch (IOException e) {
e.printStackTrace();
}
// 생성한 파일의 정보
System.out.println("파일의 크기 : " + file.length() + "byte");
System.out.println("파일의 경로 : " + file.getPath());
System.out.println("현재 파일의 상위 경로 : " + file.getParent());
System.out.println("파일의 절대 경로 : " + file.getAbsolutePath());
>>> '절대 경로'란, 최상위 루트 위치부터의 경로를 의미
// 파일 삭제 : 삭제 후 성공 실패 여부를 boolean으로 반환
boolean deleteSuccess = file.delete();
System.out.println(deleteSuccess);
>>> 최초 실행 시 true 출력. 왼쪽 file 패키지를 Refresh하면 test.txt 파일이 사라져있음
💻 Mini Console
true
파일의 크기 : 0byte
파일의 경로 : src\com\greedy\section01\file\test.txt
현재 파일의 상위 경로 : src\com\greedy\section01\file
파일의 절대 경로 : D:\java\workspace\joys_workspace_lecture\joyschap14-io\src\com\greedy\section01\file\test.txt
true
📍 File Stream의 종류
1) FileInputStream
2) FileOutputStream
3) FileReader
4) FileWriter
📌 Ref.
* 자바 프로그램과 연결되는 외부 데이터의 타입이 무엇인지는 클래스의 이름을 보고 유추 가능
InputStream/OutputStream, Reader/Writer를 빼고 남은 단어가 '외부 데이터의 타입'
* ex) FileInputStream : 외부 데이터인 File로부터 1바이트 단위로 데이터를 읽어오는 입력 스트림
외부 데이터인 File로부터 1바이트 단위로 데이터를 읽어오는 입력 스트림
FileInputStream fin = null;
>>> finally 블럭을 위해 try 블럭 밖에서 null로 값 초기화
try {
>>> 대상 파일이 존재하지 않는 경우 발생하는 FileNotFoundException에 대해 핸들링 해야함. try&catch 블럭 처리
>>> 인스턴스만 생성한 후 실행하면 FileNotFoundException이 발생하며,
>>> 파일을 직접 생성한 후 실행하면 예외가 발생X. 스트림 인스턴스가 정상적으로 생성된 것
fin = new FileInputStream("src/com/greedy/section02/stream/testInputStream.txt");
int value;
>>> read()는 throws IOException이므로 add catch clause to surrounding try를 눌러서 catch 블럭 하단에 추가
>>> read() : 파일에 기록된 값을 1byte씩 순차적으로 읽어서 반환하고 value 변수에 담음.
더 이상 읽어올 데이터가 없는 경우 -1 반환
while((value = fin.read()) != -1) { >>> != -1 : while문을 빠져나오기 위한 장치
>>> read()는 더이상 반환할 것이 없을 때 -1을 반환하기 때문
// 값을 정수로 읽어오기
System.out.println(value);
// 문자로 읽어오기
System.out.println((char)value);
}
>>> 1byte씩 읽어오는 것은 대부분 비효율적. byte 배열을 이용해서 한 번에 데이터를 읽어올 수 있음
// 파일의 길이만큼의 byte 배열 만들기
int fileSize = (int) new File("src/com/greedy/section02/stream/testInputStream.txt").length();
byte[] bar = new byte[fileSize];
>>> 생성해둔 byte 배열을 read() 메소드의 인자로 넣어주면 파일의 내용을 읽어 byte 배열에 기록
fin.read(bar); // byte 배열을 인자로 전달
for(int i = 0; i < bar.length; i++) {
System.out.println((char)bar[i]);
}
} catch (FileNotFoundException e) {
e.printStackTrace(); >>> 출력 시점에 파일이 없을 경우, '지정된 파일을 찾을 수 없습니다.' 라는 오류가 뜸
} catch (IOException e) {
e.printStackTrace();
} finally {
>>> fin 인스턴스가 null이 아닌 경우 자원 반납을 해야함
if(fin != null) {
try {
>>> 자원을 반납하는 경우에도 IOException을 핸들링 해야함
>>> IOException : 이미 자원이 반납된 경우 발생하는 Exception
fin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
abcde
💻 Mini Console
a
b
c
d
e
프로그램의 데이터를 외부 데이터인 File로 1바이트씩 내보내는 출력 스트림
FileOutputStream fos = null;
try {
>>> FileNotFoundException을 핸들링 해야하는 것은 입력 스트림과 동일하지만,
>>> 파일이 없는 상태에서 실행해도 예외가 발생하지않고 인스턴스가 잘 생성됨
>>> '출력 스트림'의 경우, 대상 파일이 존재하지 않으면 파일을 자동으로 생성해줌 (예외 발생 X)
>>> But, FileNotFoundException이 핸들링 하는 부분은 경로. 존재하지 않는 경로까지 만들어주지는 않음
// 파일과 연결되는 fos 객체 생성
fos = new FileOutputStream("src/com/greedy/section02/stream/testOuputStream.txt", *true);
>>> 실행을 할 때마다 testOuputStream.txt에 아래의 문자들이 계속 추가(이어쓰기)됨
>>> write 메소드는 IOException을 핸들링 해야함
fos.write(97);
// byte 배열을 이용해서 한 번에 기록하기 (10 = 개행문자)
byte[] bar = new byte[] {98, 99, 100, 101, 102, 10, 103, 104, 105};
fos.write(bar);
// bar 배열의 1번 인덱스부터 3의 길이만큼 파일에 내보내기
fos.write(bar, 1, 3); // 100, 101, 102 -> cde 출력
} catch (FileNotFoundException e) {
>>> 경로의 범위가 잘못되면 예외('지정된 경로를 찾을 수 없습니다')가 발생하지만,
>>> 경로는 맞지만 파일이 없는 경우는 에러가 발생되지 않고 파일을 만들어줌
e.printStackTrace();
} catch (IOException e) { // write()을 위한 예외처리
e.printStackTrace();
} finally { // 다 사용하고나서 자원 반납
if(fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
📌 Ref.
* true : 생성자의 두 번째 인자로 true를 전달하면 파일 '이어쓰기',
전달하지 않으면(or false 전달) 파일 '덮어쓰기'
abcdef
ghicdeabcdef
ghicdeabcdef
ghicde
// true로 인해, 실행할 때 마다
// abcdef
// ghicde 가 계속 추가됨
외부 데이터 File로부터 문자 단위로 데이터를 읽어오는 입력 스트림
FileReader fr = null;
try {
>>> 파일이 존재하지 않는 경우 FileNotFoundException이 발생하므로 파일을 추가해서
>>> 정상적으로 스트림이 생성되도록 함
fr = new FileReader("src/com/greedy/section02/stream/testReader.txt");
int value;
>>> 모든 문자는 그와 대응되는 숫자가 있기 때문에 int 타입으로 값을 저장
while((value = fr.read()) != -1) {
>>> read()는 IOException에 대한 처리가 필요함
// 문자 단위로 파일을 읽어옴
System.out.println((char)value);
}
>>> byte가 아닌 char 배열로 읽어오는 기능을 제공
// 해당 파일의 길이만큼의 carr 배열 생성
char[] carr = new char[(int) new File("src/com/greedy/section02/stream/testReader.txt").length()];
// read()로 배열 읽음
fr.read(carr);
// 배열 출력
for(int i = 0; i < carr.length; i++) {
System.out.println(carr[i]);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) { // read()의 예외 처리 중
e.printStackTrace();
} finally {
if(fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
안녕하세요
💻 Mini Console
안
녕
하
세
요
프로그램의 데이터를 외부 데이터인 File로 한 문자씩 내보내는 출력 스트림
FileWriter fw = null;
try {
>>> 출력 스트림의 경우 대상 파일이 존재하지 않으면 파일을 자동으로 생성하나 경로가 존재하지않는 경우는 예외 발생
>>> 두 번째 인자로 true 전달 시, 이어쓰기
fw = new FileWriter("src/com/greedy/section02/stream/testWriter.txt"/*, true*/);
fw.write(97);
>>> testWriter.txt에 a가 내보내기되지 않음
>>> 문자 단위 출력도 내부 버퍼를 사용하므로 쌓여있는 데이터를 flush()로 내보내줘야
>>> 최종적으로 파일에 출력.
>>> 또는 close()로 자원을 반납하면 반납 전에 flush() 메소드가 내부적으로 호출되므로 파일에 출력
fw.flush();
>>> testWriter.txt에 a가 내보내짐
// 문자 기반 스트림은 직접 char 자료형으로 내보내기 가능
fw.write('A');
// 또는 char 배열도 가능
fw.write(new char[] {'a', 'b', 'c', 'd', 'e'});
// 또는 String도 가능
fw.write("월요일 싫어 치즈 좋아");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fw != null) {
try {
fw.close();
>>> 닫기 전에 버퍼에 쌓여있는 데이터를 내보내기함
>>> flush를 호출하지 않더라도 닫히면서 쌓여있던 값이 내보내기됨 => a 출력
} catch (IOException e) {
e.printStackTrace();
}
}
}
aAabcde월요일 싫어 치즈 좋아
💁♀️ 필터 스트림이란,
필터 스트림(보조 스트림)은 외부 데이터에 직접 연결하는 것이 아니라 기본 스트림에 추가로 사용할 수 있는 스트림
- 주로 성능을 향상시키는 목적으로 사용하며 생성자를 보면 구분이 가능
- 생성자 쪽에 매개변수로 다른 스트림을 이용하는 클래스는 필터 스트림이라고 볼 수 있음
📍 Filter Stream의 종류
1) 성능 향상 보조 스트림
2) 형변환 보조 스트림
3) 기본 데이터 타입 보조 스트림
4) 객체 입출력 보조 스트림
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter("src/com/greedy/section03/filterstream/testBuffered.txt"));
// 보조 : bw = new BufferedWriter / 기반 : new FileWriter("src/com/greedy/section03/filterstream/testBuffered.txt")
>>> 서로 데이터 타입 맞춰줘야함 (Writer)
bw.write("오늘의 점심은?\n");
bw.write("칼국수\n");
>>> 버퍼를 이용하는 경우 버퍼가 가득 차면 자동으로 내보내기를 하지만 버퍼가 차지않은 상태에서는
>>> flush()를 호출하여 강제 내보내기를 해야함 (flush()를 해주면 파일에 기록됨)
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(bw != null) {
try {
>>> 사용한 보조 스트림만 close()하면 내부적으로 기반 스트림도 close()됨
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
>>> 버퍼에 미리 읽어온 뒤 한 줄 단위로 읽어들이는 기능을 제공하며,
>>> 기본 스트림보다 성능을 개선시킨 보조 스트림
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("src/com/greedy/section03/filterstream/testBuffered.txt"));
>>> BufferedReader는 readLine() 메소드를 추가로 제공
>>> 버퍼의 한 줄을 읽어와서 문자열로 반환
String temp;
while((temp = br.readLine()) != null) {
System.out.println(temp); // 버퍼를 한 줄씩 읽어와서 콘솔창에 출력
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) { // readLine()의 예외 처리 중
e.printStackTrace();
} finally {
if(br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
오늘의 점심은?
칼국수
💻 Mini Console
오늘의 점심은?
칼국수
📌 Ref.
* readLine()은 IOException을 핸들링 해야함
* readLine()은 스트링 값을 읽어오므로 반환할 값이 없을 때, -1가 아닌 null을 반환
InputStreamReader
OutputStreamWriter
📌 Ref. 표준 스트림
* 콘솔이나 키보드 같은 표준 입력 장치로부터 데이터를 입출력하기위한 스트림을 '표준 스트림 형태'로 제공
* System 클래스의 필드 in, out, err가 대상 데이터의 스트림을 의미
* System.in (InputStream) : 콘솔로부터 데이터를 입력 받기
* System.out (PrintStream) : 콘솔로 데이터를 출력하기
* System.err (PrintStream) : 콘솔로 데이터를 출력하기 (빨간색)
* 자주 사용되는 자원에 대해 미리 스트림을 생성해두었기 때문에 개발자가 별도로 스트림을 생성하지 않아도 됨
>>> Buffer를 이용해서 성능을 향상시키고 싶은 경우, '형변환 보조 스트림'을 사용
// BufferedReader br = new BufferedReader(System.in);
>>> inputStream(System.in)을 직접적인 인자로 넣을 수 없음 (지원X)
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
System.out.print("문자열 입력 : ");
String value = br.readLine();
>>> 개행(Enter)전까지의 한 줄을 읽어오기
>>> 스캐너와 동일하게 사용되지만 데이터 타입을 입력하지 않고 readLine()만으로 출력 가능
System.out.println("value : " + value);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
>>> 출력을 위한 것도 동일한 방식
>>> try-with-resource 문으로 작성
// BufferedWriter bw = 버퍼 보조 스트림(형변환 보조 스트림(표준 출력 스트림_기반 스트림));
// BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
>>> try 옆에 소괄호를 만든 후, 이 식을 그대로 넣어주면 try-with-resource 문
=> finally 및 close() 구문을 작성하지 않아도 close됨
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));){
bw.write("Joy with Happy"); >>> write() : 콘솔로 내보내는 역할
} catch (IOException e) {
e.printStackTrace();
}
💻 Mini Console
문자열 입력 : 내일은 화요일 :)
value : 내일은 화요일 :)
Joy with Happy
DataInputStream
DataOutputStream
// 데이터형별로 파일에 기록하는 DataOutputStream 인스턴스 생성
try (DataOutputStream dout
= new DataOutputStream(new FileOutputStream("src/com/greedy/section03/filterstream/score.txt"))) {
// 파일에 자료형별로 기록
dout.writeUTF("조효연"); >>> String은 writeStirng이 아닌 writeUTF
dout.writeInt(100); >>> Int는 눈에 보이지 않음 (다른 것으로 출력됨)
dout.writeChar('A');
dout.writeUTF("허수민");
dout.writeInt(85);
dout.writeChar('B');
dout.writeUTF("허치즈");
dout.writeInt(79);
dout.writeChar('C');
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} >>> try-with-resource로 인해 Finally 및 close()구문을 작성하지않아도 자동 close
// 데이터형 별로 읽어오는 DataInputStream 인스턴스 생성
try (DataInputStream din
= new DataInputStream(new FileInputStream("src/com/greedy/section03/filterstream/score.txt"))) {
>>> 무한 루프로 읽어들이게 되면 파일에 더 이상 읽을 것이 없을 때, EOFException이 발생한다.
>>> EOF(End Of File)Exception : 더이상 읽어올 것이 없음
>>> catch 블럭에 EOFException을 핸들링하는 코드를 추가하면됨
while(true) {
>>> 파일에 기록한 순서대로 읽어들이지 않는 경우 에러가 발생하거나 올바르지 않은 값을 읽어옴
System.out.println(din.readUTF());
System.out.println(din.readInt());
System.out.println(din.readChar());
>>> 이 식이 readInt() 위로 올라가면 출력문이 비정상적
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (EOFException e) { >>> EOFException이 IOException보다 하위 타입이므로 IOException보다 위에 있어야함
>>> (아래에 있으면 unreachable 코드)
System.out.println("파일 읽기를 완료했습니다.");
} catch (IOException e) {
e.printStackTrace();
}
조효연 d A 허수민 U B 허치즈 O C
💻 Mini Console
조효연
100
A
허수민
85
B
허치즈
79
C
파일 읽기를 완료했습니다.
ObjectInputStream
ObjectOutputStream
>>> 직렬화 대상 클래스에 Serializable 인터페이스만 구현하면 직렬화가 필요한 경우 데이터 직렬화 처리가 수행됨
public class MemberDTO implements Serializable {
>>> MemberDTO에 커서를 가져다 대어 'Add generated serial version ID'를 누르면
>>> 시리얼 버전 아이디 생성 => UID가 fix되어 변경 사항이 발생하더라도 역직렬화가 가능
>>> static : 모든 객체가 공유하는 공간
private static final long serialVersionUID = -1852199523260481785L;
private String id;
private String pwd;
private int age;
private char gender;
>>> 직렬화 대상에서 제외하고 싶은 필드의 경우 transient 키워드를 이용할 수 있음
private transient double point; // 0.0 출력
public MemberDTO() {}
public MemberDTO(String id, String pwd, int age, char gender, double point) {
super();
this.id = id;
this.pwd = pwd;
this.age = age;
this.gender = gender;
this.point = point;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public double getPoint() {
return point;
}
public void setPoint(double point) {
this.point = point;
}
@Override
public String toString() {
return "MemberDTO [id = " + id + ", pwd = " + pwd + ", age = " + age + ", gender = " + gender + ", point = " + point
+ "]";
}
}
📌 Ref.
* SerialVersionUID를 작성하지 않았을 경우, JVM이 자동으로 SerialVersionUID를 생성해서
동작시키므로 오류가 발생하지 않았음. 다만 '추후에 Class에 변경 사항이 발생하면' 다시
자동으로 또 다른 SerialVersionUID를 생성하게 되므로 이전에 출력했던
SerialVersionUID와 불일치하여, 읽어올 때 역직렬화에 실패하는 상황(InvaildCalssException)이
생길 수 있음. 따라서 '명시적으로 작성'해두는 것이 좋음
MemberDTO[] outputMembers = {
new MemberDTO("user01", "pass01", 26, '여', 1250.7),
new MemberDTO("user02", "pass02", 30, '남', 1212.9),
new MemberDTO("user03", "pass03", 35, '여', 1243.6)
};
try (ObjectOutputStream oos
= new ObjectOutputStream(new FileOutputStream("src/com/greedy/section03/filterstream/testObjectStream.txt"))){
for(int i = 0; i < outputMembers.length; i++) {
// 직렬화 중
oos.writeObject(outputMembers[i]);
>>> writeObject : 객체를 출력하는 메소드 (파일 쪽으로 내보내기)
} >>> 직렬화 후, 실행하면 예외가 발생하지않고 왼쪽의 filterstream을
>>> Refresh하면 testObjectStream.txt 파일이 생성되어있음
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
📌 Ref.
* 처음에 실행하면 java.io.NotSerializableException 발생.
이는 '직렬화 처리를 해주지 않아서 발생'하는 예외
🙋 잠깐 ! 직렬화와 역직렬화가 뭔가요?
- 직렬화
: 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부에서도 사용할 수 있도록 byte 형태로 데이터를 변환하는 기술- 역직렬화
: 바이트로 변환 된 데이터를 다시 객체로 변환하는 기술
MemberDTO[] inputMembers = new MemberDTO[3];
>>> outputMembers에서 내보낸 파일을 inputMembers에 담아 읽어옴
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("src/com/greedy/section03/filterstream/testObjectStream.txt"))) {
for(int i = 0; i < inputMembers.length; i++) {
// 역직렬화 중
inputMembers[i] = (MemberDTO) ois.readObject();
>>> readObject()를 통해 객체를 읽어서 inputMembers[i]에 저장할 것
>>> *MemberDTO로 다운캐스팅
>>> readObject()는 ClassNotFoundException 발생. 핸들링 해야함
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
>>> readObject()로 읽어온 Object를 역직렬화 할 수 있는 Class가 없을 때 ClassNotFoundException이 발생하기 때문에 핸들링 해야함
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 파일에서 읽어온 객체 결과 출력
for(MemberDTO member : inputMembers) {
System.out.println(member);
}
📌 Ref.
* MemberDTO로 다운캐스팅 하는 이유 ?
inputMembers[i]는 MemberDTO 클래스 타입인 반면, readObject()는 Object 클래스 타입이기 때문에
강제 타입 형변환을 해야함
��