Input과 Output의 약자, 컴퓨터 내부 또는 외부 장치와 프로그램 간의 데이터를 주고 받는 것
장치와 입출력을 위해서는 하드웨어 장치에 직접 접근이 필요한데 다양한 매체에 존재하는 데이터들을 사용하기 위해 입출력 데이터를 처리할 공통적인 방법으로 스트림 이용

파일 시스템의 파일을 표현하는 클래스
파일 크기, 파일 속성, 파일 이름 등의 정보와 파일 생성 및 삭제 기능 제공



입출력 장치에서 데이터를 읽고 쓰기 위해서 자바에서 제공하는 클래스
모든 스트림은 단방향이며 각각의 장치마다 연결할 수 있는 스트림 존재
하나의 스트림으로 입출력을 동시에 수행할 수 없으므로 동시에 수행하려면 2개의 스트림 필요


바이트 기반 입력 스트림의 최상위 클래스로 추상클래스

파일을 바이트 단위로 읽을 때 사용
그림, 오디오, 비디오, 텍스트 파일 등 모든 종류의 파일 읽기 가능
InputStream의 하위 클래스로 InputStream과 사용 방법 동일
바이트 기반 출력 스트림의 최상위 클래스로 추상클래스임

파일을 바이트 단위로 저장할 때 사용
그림, 오디오, 비디오, 텍스트 파일 등 모든 종류의 데이터를 파일로 저장
OutputStream의 하위 클래스로 OutputStream과 사용 방법 동일
만약 파일이 존재하지 않으면 자동으로 생성하지만 이미 파일이 존재하는 경우 파일을 덮어쓰는 단점이 있음
FileOutputStream fos = new FileOutputStream("C:/data/test.txt");
만일 기존 파일에 이어서 계속 작성하고 싶다면 아래 예제처럼 객체 생성 시 가능
FileOutputStream fos = new FileOutputStream("C:/data/test.txt", true);
문자 기반 입력 스트림의 최상위 클래스로 추상클래스임

문자 단위로 텍스트 기반 파일을 읽을 때 사용
텍스트가 아닌 그림, 오디오, 비디오 등의 파일은 읽기 불가능
Reader의 하위 클래스로 Reader와 사용 방법 동일
FileReader fr = new FileReader("C:/dev/test.txt");
FileReader fr = new FileReader(new File("C:/dev/test.txt"));
문자 기반 출력 스트림의 최상위 클래스로 추상클래스임

문자 단위로 텍스트 기반 파일을 쓸(저장할) 때 사용
텍스트가 아닌 그림, 오디오, 비디오 등의 파일은 저장 불가능
Writer의 하위 클래스로 Writer와 사용 방법 동일
만약 파일이 존재하지 않으면 자동으로 생성하지만 이미 파일이 존재하는 경우 파일을 덮어쓰는 단점이 있음
FileWriter fw = new FileWriter("C:/data/test.txt");
만일 기존 파일에 이어서 계속 작성하고 싶다면 아래 예제처럼 객체 생성 시 가능
FileWriter fw = new FileWriter("C:/data/test.txt", true);
스트림의 기능을 향상시키거나 새로운 기능을 추가하기 위해 사용
보조 스트림은 실제 데이터를 주고 받는 스트림이 아니기 때문에 입출력 처리 불가능
기반 스트림을 먼저 생성한 후 이를 이용하여 보조 스트림 생성

문자 변환(InputStreamReader/OutputStreamWriter),
입출력 성능(BufferedInputStream/BufferedOutputStream),
기본 데이터 타입 출력(DataInputStream, DataOutputStream),
객체 입출력(ObjectInputStream/ObjectOutputStream) 등의 기능을 제공하는 보조스트림이 있음

느린 속도로 인해 입출력 성능에 영향을 미치는 입출력 소스를 이용하는 경우 사용
입출력 소스와 직접 작업하지 않고 버퍼에 데이터를 보아 한꺼번에 작업을 하여 실행 성능 향상
(입출력 횟수 줄임)

기본 자료형 별 데이터 읽고 쓰기가 가능하도록 기능 제공
단, 입력된 자료형의 순서와 출력될 자료형의 순서 일치

객체를 파일 또는 네트워크로 입출력 할 수 있는 기능 제공
단, 객체는 문자가 아니므로 바이트 기반 스트림으로 데이터를 변경해주는 직렬화 필수

Serializable 인터페이스를 implements하여 구현
객체 직렬화 시 private 필드를 포함한 모든 필드를 바이트로 변환하지만
transient키워드를 사용한 필드는 직렬화에서 제외
직렬화된 객체를 역직렬화할 때는 직렬화 했을 때와 같은 클래스 사용
단, 클래스 이름이 같더라도 클래스 내용이 변경된 경우 역직렬화 실패
직렬화한 클래스와 같은 클래스임을 알려주는 식별자 역할로 컴파일 시 JVM이 자동으로 serialViersionUID 정적 필드를 추가해줘 별로도 작성하지 않아도 오류는 나지 않지만 자동 생성 시 역직렬화에서 예상하지 못한 InvalidClassException을 유발할 수 있어 명시 권장
private static final long serialVersionUID = -6423919775137290062L;
소스 스트림이 바이트 기반 스트림이지만 데이터가 문자일 경우 사용
Reader와 Writer는 문자 단위로 입출력을 하기 때문에 데이터가 문자인 경우
바이트 기반 스트림보다 편리하게 사용 가능

class Student implements Serializable
package edu.kh.io.model.vo;
import java.io.Serializable;
// Serializable
// -> 직렬화가 될 수 있는 객체가 상속 받는 인터페이스
// --> ObjectOutputStream이 객체를 내보낼 때 해당 인터페이스를 상속 받은 객체인 경우에만 직렬화 작업 진행
// Serializable을 상속했지만 구현할 메소드가 없다
// == 마커 인터페이스(표시용도의 인터페이스) = 그냥 껍데기만 있는 느낌
public class Student implements Serializable{
private String name;
private int grade;
private int classRoom;
private int number;
private char gender;
public Student() {}
public Student(String name, int grade, int classRoom, int number, char gender) {
super();
this.name = name;
this.grade = grade;
this.classRoom = classRoom;
this.number = number;
this.gender = gender;
}
}
class IORun
package edu.kh.io.run;
import edu.kh.io.model.service.IOService;
public class IORun {
public static void main(String[] args) {
IOService service = new IOService();
service.output1();
service.output2();
service.input1();
service.input2();
service.objectOutput();
service.objectInput();
service.listOuptput();
service.listInput();
}
}
class IOService
package edu.kh.io.model.service;
import java.io.*;
import java.util.*;
import edu.kh.io.model.vo.Student;
public class IOService {
// IO
// Input(입력) : 외부 -> 내부로 데이터를 들여오는 것
// Ouput(출력) : 내부 -> 외부로 데이터를 내보내는 것
// Stream : 입/출력 통로 역할(데이터가 흘러가는 통로)
// 기본적으로 Stream은 단바양
// 1) 파일 출력(내부 == 프로그램, 외부 == 파일)
public void output1() {
FileOutputStream fos = null;
// FileOutputStream 객체 생성시 FileNotFoundException 예외가 발생할 가능성이 있음 -> 예외처리
try {
fos = new FileOutputStream("test1.txt");
// 현재 프로그램에서 test1.txt 파일로 출력하는 통로 객체 생성
// 파일에 "Hello" 내보내기
String str = "Hello";
for(int i=0; i<str.length(); i++) {
System.out.println(str.charAt(i));
// "Hello"를 한 문자씩 끊어서 파일로 출력하기
fos.write(str.charAt(i));
// write()는 IOException 가능성 있어서 try-catch 해야한다
}
} catch (IOException e) {
System.out.println("예외 발생");
e.printStackTrace(); // 예외 추적
} finally {
// 예외가 발생 하든 말든 무조건 수행
// 사용한 스트림 자원 반환(통로 없앰) --> 필수 작성!
// 프고르매메모리 관리 차원에서 항상 다쓰면 끊어줌
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

// 2) 파일 출력(문자 기반 스트림)
public void output2() {
FileWriter fw = null; // 프로그램 -> 파일로 쓰는 문자 기반 스트림
try {
fw = new FileWriter("test2.txt", true); // 외부 파일과 연결하는 스트림 객체 생성
// fw = new FileWriter("경로, 이어쓰기 옵션);
// -> byte 기반 스트림도 사용 가능한 옵션
String str = "안녕하세요. Hello / \n1234 !@#$%";
// fw.write(int c): 한 문자 씩 출력
// fw.wrtie(String s) : 한 줄 씩 출력
fw.write(str);
// 실행했는데 출력이 안된다..?
// -> 한 줄을 통째로 보내기 위해 "버퍼"를 이용하는데 아직 버퍼에 담겨 있음
// 이걸 강제로 밀어넣어서 버퍼를 비워야함
// close() 구문을 수행하면 통로에 남아있는 내용을 모두 내보내고 통로를 없앤다
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fw.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
// 3) 파일 입력 : 외부(파일) -> 내부(프로그램)으로 읽어보기
public void input1() {
FileInputStream fis = null; // 파일 -> 프로그램으로 읽어오는 바이트 기반 스트림
try {
fis = new FileInputStream("test1.txt");
// FileInputStream은 1byte씩만 읽어올 수 있다
while(true) {
int data = fis.read(); // 다음 1byte를 읽어오는데 정수형임
// 다음 내용이 없으면 -1 반환
if (data == -1){ // 다음 내용 없음 => 종료
break;
}
// 반복 종료 안됐으면 char로 강제 형변환 하여 문자로 출력
System.out.println((char)data);
}
} catch (IOException e) {
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

// 4) 파일 입력(문자 기반 스트림)
public void input2() {
FileReader fr = null;
try {
fr = new FileReader("test2.txt");
while(true) {
int data = fr.read(); // 다음 한 문자를 읽어옴. 없으면 -1
if(data == -1) {
break;
}
System.out.print((char)data);
}
} catch (IOException e) {
} finally {
}
}

// 5) 객체 출력 보조 스트림
public void objectOutput() {
// ObjectXXXStream : 객체를 파일 또는 네트워크를 통해 입/출력할 수 있는 스트림
// 직렬화, 역직렬화
// ObjectOutputStream
// -> 객체를 바이트 기반 스트림으로 출력할 수 있게 하는 스트림
// 조건 : 출력하려는 객체에 직렬화 가능 여부를 나타내는 Serializable 인터페이스를 상속 받아야 한다
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("object/Student.txt"));
// 내보낼 학생 객체 생성
Student std = new Student("홍길동", 3, 5, 7, '남');
// 학생 객체를 파일로 출력
oos.writeObject(std);
System.out.println("학생 출력 완료");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(oos != null) oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 6) 객체 입력 보조 스트림
public void objectInput() {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("Object/Student.txt"));
Student std = (Student) ois.readObject();
// readObject() : 직렬화된 객체 데이터를 읽어와, 역직렬화를 시켜 정상적인 객체 형태로 반환
System.out.println(std);
} catch (Exception e) {
} finally {
try {
if(ois!=null) ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 7) List에 Student 객체를 담아서 파일로 출력
public void listOuptput() {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("object/StudentList.ini"));
// 내보낼 학생 객체 생성
List<Student> list = new ArrayList<>();
list.add(new Student("홍길동", 1,1,1,'남'));
list.add(new Student("김길동", 2,2,2,'남'));
list.add(new Student("이길동", 3,3,3,'여'));
list.add(new Student("박길동", 1,2,3,'남'));
// 학생 객체를 파일로 출력
oos.writeObject(list);
// writeObject(객체) : 출력하려는 객체는 직렬화가 가능해야만 한다
// Serializable 인터페이스 구현 필수
// 컬렉션은 모두 직렬화가 가능하도록 Serializable 인터페이스 구형 O
// 단, 컬렉션에 저장하는 객체가 직렬화 가능하지 않다면 출력 안 된다
System.out.println("학생 출력 완료");
} catch (Exception e) {
} finally {
try {
if(oos!=null) oos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 8) List에 Student 객체를 파일로 담기
public void listInput() {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("object/StudentList.ini"));
List<Student> list = (List<Student>) ois.readObject();
for(Student std:list) {
System.out.println(std);
}
} catch (Exception e) {
} finally {
try {
ois.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
}