
여러 개의 입력스트림을 연속적으로 연결해서 하나의 스트림으로부터 데이터를 읽는 것과 같이 처리할 수 있도록 해줌
생성자를 제외하고 나머지 작업은 다른 입력스트림과 다르지 않다
큰 파일을 여러 개의 작은 파일로 나누었다가 하나의 파일로 합치는 것과 같은 작업을 수행할 때 좋다
SequenceInputStream은 다른 보조스트림들과는 달리 FilterInputStream의 자손이 아닌 InputStream을 바로 상속받아 구현

3개의 ByteArrayInputStream을 Vector와 SequenceInputStream을 이용해서 하나의 입력 스트림으로 다룬 예시
// Vector에 저장된 순서대로 입력됨
import java.io.*;
import java.util.*;
class Ex15_7{
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));
}
}
Input Source1 :[0, 1, 2]
Input Source2 :[3, 4, 5]
Input Source3 :[6, 7, 8]
Output Source :[0, 1, 2, 3, 4, 5, 6, 7, 8]
PrintStream은 데이터를 기반스트림에 다양한 형태로 출력할 수 있는 print, println, printf와 같은 메소드를 오버로딩하여 제공한다.
PrintStream은 데이터를 적절한 문자로 출력하는 것이기 때문에 문자기반 스트림의 역할을 수행한다.
JDK 1.1부터 PrintStream보다 향상된 기능의 문자기반 스트림인 PrintWriter가 추가되었으나 System.out이 PrintStream이다 보니 둘 다 사용하게 되었다.
PrintStream와 PrintWriter는 거의 같은 기능을 가지고 있지만 PrintWriter가 PrintStream에 비해 다양한 언어의 문자를 처리하는데 적합하기 때문에 가능하면 PrintWriter를 사용하는 것이 좋다.

print()나 println()을 이용해서 출력하는 중에 PrintStream의 기반스트림에서 IOException이 발생하면 checkError()를 통해 인지할 수 있다.
println()이나 print()는 예외를 던지지 않고 내부에서 처리하도록 정의되었는데, 그 이유는 println()과 같은 메소드가 자주 사용되기 때문이다.
바이트기반 스트림의 조상이 InputStream/OutputStream인 것과 같이 문자기반의 스트림에서는 Reader/Writer가 그와 같은 역할
Reader/Writer는 byte배열 대신 char배열을 사용한다는 것 외에는 InputStream/OutputStream의 메소드와 다르지 않다.


문자기반 스트림이라는 것이 단순히 2 byte로 스트림을 처리하는 것만이 아니라, 인코딩(encoding)기능 또한 포함되어 있다.
문자기반 스트림인 Reader/Writer, 그리고 그 자손들은 여러 종류의 인코딩과 자바에서 사용하는 유니코드(UTF-16)간의 변환을 자동적으로 처리해준다.
Reader는 특정 인코딩을 읽어서 유니코드로 변환하고 Writer는 유니코드를 특정 인코딩으로 변환하여 저장
FileInputStream,OutputStream과 사용법이 같음
파일로 부터 텍스트 데이터를 읽고, 파일을 쓰는데 사용
import java.io.*;
class Ex15_8{
public static void main(String[] args){
try{
String fileName = "test.txt";
FileInputStream fis = new FileInputStream(fileName);
FileReader fr = new FileReader(fileName);
int data = 0;
//FileInputStream을 이용해서 파일 내용을 읽어 화면 출력
while((data=fis.read())!=-1){
System.out.print((char)data);
}
System.out.println();
fis.close();
//FileReader를 이용해서 파일 내용을 읽어 화면 출력
while((data=fr.read())!=-1)
System.out.print((char)data);
System.out.println();
fr.close();
} catch(IOException e){
e.printStackTrace();
}
}
}
바이트 기반의 FileInputStream은 한글이 깨져서 출력되는 걸 볼 수 있음
파일의 공백을 모두 없애는 예제
import java.io.*;
class Ex15_9{
public static void main(String args[]){
try{
FileReader fr = new FileReader(args[0]);
FileWriter fw = new FileWriter(args[1]);
int data = 0;
while((data=fr.read())!=-1){
if(data!='\t\ && data! = '\n' && data!=' ' && data != '\r')
fw.write(data);
}
fr.close();
fw.close();
} catch(IOException e){
e.printStactTrace();
}
}
}
입출력 대상이 메모리인 스트림
StringWriter에 의해 출력되는 데이터는 내부의 StringBuffer에 저장
StringBuffer getBuffer() : StringWriter에 출력한 데이터가 저장된 StringBuffer를 반환
String toString() : StringWriter에 출력된(StringBuffer에 저장된) 문자열을 반환
import java.io.*;
class Ex15_10{
public static void main(String[] args){
String inputData = "ABCD";
StringReader input = new StringReader(inputData);
StringWriter output = new StringWriter();
int data = 0;
try{
while((data==input.read()) != -1){
output.write(data);
}
}catch(IOException e) { }
System.out.println("Input Data: " + inputData);
System.out.println("Output Data: " + Output.toString());
}
}
Input Data : ABCD
Output Data : ABCD
라인 단위로 읽게 해주는 메서드줄 바꿈을 해주는 메서드// 주석 또한 프린트 하는 것을 볼 수 있다
import java.io.*;
class Ex15_11{
public static void main(String[] args){
try{
FileReader fr = new FileReader("Ex15_11.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) {}
}
}
바이트기반 스트림의 데이터를 지정된 인코딩의 문자데이터로 변환하는 작업


다른 언어로 작성된 파일을 한글 윈도우에서 읽어올 때 InputStreamReader(InputStream in, String encoding)를 이용해서 인코딩이 중국어로 되어있다는 것을 지정해주어야 파일의 내용이 깨지지 않고 정상적으로 보일 것
package ch15;
import java.io.*;
class Ex15_12 {
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"));
// br.close(); // System.in과 같은 표준 입출력은 닫지 않아도 된다.
System.out.println("프로그램을 종료합니다. ");
} catch(IOException e) {}
}
}
BufferedReader와 InputStream인 System.in을 연결하기 위해 InputStreamReader 사용
-> 현재는 Scanner 주로 사용
콘솔을 통한 데이터 입력과 콘솔로의 데이터 출력을 의미
자바에서는 표준 입출력(standard I/O)을 위해 3가지 입출력 스트림인 System.in, System.out, System.err을 제공하는데, 이들은 자바 어플리케이션의 실행과 동시에 사용할 수 있께 자동적으로 생성되기 때문에 개발자가 별도로 스트림을 생성하는 코드를 작성하지 않고도 사용이 가능
System.in : 콘솔로부터 데이터를 입력받는데 사용System.out : 콘솔로 데이터를 출력하는데 사용System.err : 콘솔로 데이터를 출력하는데 사용
System클래스의 소스에서 알 수 있듯이 in, out, err는 System클래스에 선언된 클래스 변수(static 변수)이다.
선언부분은 PrintStream이지만 실제로는 버퍼를 이용하는 BufferedInputStream과 BufferedOutputStream의 인스턴스를 사용한다.
public final class System{
public final static InputStream in = nullInputStream();
public final static PrintStream out = nullPrintStream();
public final static PrintStream err = nullPrintStream();
...
}
초기에는 System.in, System.out, System.err의 입출력대상이 콘솔이지만 setIn(), setOut(), setErr()를 사용하면 입출력을 콘솔 이외에 다른 입출력 대상으로 변경하는 것이 가능

import java.io.*;
class Ex15_14{
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) // System.out의 출력 대상을 test.txt파일로 변경
} catch(FileNotfoundException e){
System.out.println("File not found.");
}
System.out.println("Hello by System.out");
System.err.println("Hello by System.err");
}
}
System.out을 이용한 출력은 모두 test.txt파일에 저장
파일은 기본적이면서도 가장 많이 사용되는 입출력 대상
자바에서는 File 클래스를 통해서 파일과 디렉토리를 다룰 수 있음


import java.io.*;
class FileEx1 {
public static void main(String[] args) throws IOException
{
File f = new File("c:\\jdk1.8\\work\\ch15\\FileEx1.java");
String fileName = f.getName();
int pos = fileName.lastIndexOf(".");
System.out.println("경로를 제외한 파일이름 - " + f.getName());
System.out.println("확장자를 제외한 파일이름 - " + fileName.substring(0,pos));
System.out.println("확장자 - " + fileName.substring(pos+1));
System.out.println("경로를 포함한 파일이름 - " + f.getPath());
System.out.println("파일의 절대경로 - " + f.getAbsolutePath());
System.out.println("파일의 정규경로 - " + f.getCanonicalPath());
System.out.println("파일이 속해 있는 디렉토리 - " + f.getParent());
System.out.println();
System.out.println("File.pathSeparator - " + File.pathSeparator);
System.out.println("File.pathSeparatorChar - " + File.pathSeparatorChar);
System.out.println("File.separator - " + File.separator);
System.out.println("File.separatorChar - " + File.separatorChar);
System.out.println();
System.out.println("user.dir=" + System.getProperty("user.dir"));
System.out.println("sun.boot.class.path=" + System.getProperty("sun.boot.class.path"));
}
}
경로를 제외한 파일이름 - FileEx1.java
확장자를 제외한 파일이름 - FileEx1
확장자 - java
경로를 포함한 파일이름 - c:\jdk1.8\work\ch15\FileEx1.java
파일의 절대경로(루트부터 파일의 전체 경로)- c:\jdk1.8\work\ch15\FileEx1.java
파일의 정규경로(.을 포함하지 않는 경로) - C:\jdk1.8\work\ch15\FileEx1.java
파일이 속해 있는 디렉토리 - c:\jdk1.8\work\ch15
File.pathSeparator - ;
File.pathSeparatorChar - ;
File.separator - \
File.separatorChar - \
user.dir=C:\Users\appti\eclipse-workspace\ch15
sun.boot.class.path=null
File 인스턴스를 생성했다고 해서 파일이나 디렉토리가 생성되는 것은 아니므로 새로운 파일을 생성할 때에는 주의
1. 이미 존재하는 파일을 참조할 때 :
File f = new File("c:\\jdk1.8\\work\\ch15", "FileEx1.java");
2. 기존에 없는 파일을 새로 생성할 때 :
File f = new File("c:\\jdk1.8\\work\\ch15", "NewFile.java");
f.craeteNewFile();
지정한 디렉토리에 포함된 파일과 디렉토리 목록을 보여주는 예제
import java.io.*;
class Ex15_16{
public static void main(String[] args){
if(args.length != 1){
System.out.println("USAGE : java Ex15_16 DIRECTORY");
System.exit(0);
}
File f = new File(args[0]);
if(!f.exist() || !f.isDirectory()){
System.out.println("유효하지 않은 디렉토리입니다.");
System.exit(0);
}
File[] files = f.listFiles();
for(int i = 0; i< files.length; i++){
String fileName = files[i].getName();
System.out.println(
files[i].isDirectory()? "[" + fileName + "]" : fileName);
}
}
}
지정된 디렉토리에서 지정된 확장자를 가진 파일을 delete()를 호출하여 삭제
import java.io.*;
class Ex15_17{
static int deletedFiles = 0;
public static void main(String[] args){
if(args.length != 1){
System.out.println("USAGE : java Ex15_17 DIRECTORY");
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.print(filename);
if(files[i].delete()){
System.out.println(" - 삭제 성공");
deletedFiles++;
} else
System.out.println(" - 삭제 실패");
}
}
}
}
}
직렬화(Serialization)란 객체를 데이터 스트림으로 만드는 것을 의미
즉 객체에 저장된 데이터를 스트림에 쓰기(write)위해 연속적인(seiral) 데이터로 변환하는 것을 의미한다.
반대로 스트림으로부터 데이터를 읽어서 객체를 만드는 것을 역직렬화(deserialization)라고 한다.

객체를 저장하거나 전송할때 필수!
인스턴스변수의 집합이다.클래스변수나 메소드가 포함되지 않는다.인스턴스 변수들로만 구성되어 있다.인스턴스변수는 인스턴스마다 다른 값을 가질 수 있어야 하기 때문에 별도의 메모리공간이 필요하지만 메소드는 변하지 않으므로 메모리를 낭비해 가면서 인스턴스마다 같은 내용의 코드(메소드)를 포함시킬 이유가 없다

객체를 저장한다는 것은 바로 객체의 모든 인스턴스변수의 값을 저장한다는 것과 같은 의미
어떤 객체를 저장하고자 한다면 현재 객체의 모든 인스턴스변수의 값을 저장하기만 하면 됨
그리고 저장했던 객체를 다시 생성하려면, 객체를 생성한 후에 저장했던 값을 읽어서 생성한 객체의 인스턴스변수에 저장하면 된다.
클래스에 정의된 인스턴스변수가 단순히 기본형일 때는 인스턴스변수의 값을 저장하는 일이 간단하지만, 인스턴스변수의 타입이 참조형일 때에는 간단하지 않다.
이러한 문제는 객체를 직렬화/역직렬화 할 수 있는 ObjectInputStream과 ObjectOutputStream의 사용법을 알기만 하면 된다.
여기서 두 객체가 동일한지 판단하는 기준은 두 객체의 인스턴스변수의 값들이 같고 다름이다.
직렬화(스트림에 객체를 출력)에는 ObjectOutputStream을 사용하고 역직렬화(스트림으로부터 객체를 입력)에는 ObjectInputStream을 사용
ObjectInputStream/ObjectOutputStream은 각각 InputStream/OutputStream을 직접 상속받지만 기반스트림을 필요로 하는 보조스트림
// 즉 객체를 생성할때 입출력(직렬화/역직렬화)할 스트림을 지정해 주어야함
ObjectInputStream(InputStream in)
ObjectOutputStream(OutputStream in)
FileOutputStream fos = new FileOutputStream("objectfile.ser");
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(new UserInfo())
위 코드는 objectfile.ser이란 파일에 UserInfo객체를 직렬화 하여 저장
출력할 스트림 생성 -> 이를 기반으로 ObjectOutputStream 생성 -> write를 통해 객체 출력 -> 객체가 파일에 직렬화 되어 저장
FileInputStream fis = new FileInputStream("objectfile.ser");
ObjectInputStream in = new ObjectInputStream(fis);
// 반환 타입이 Object라 형변환을 해야함
UserInfo info = (Userinfo)in.readObject();

이 메소드들은 직렬화와 역직렬화를 직접 구현할 때 주로 사용되며, defaultReadObject()와 defaultWriteObject()는 자동 직렬화를 수행
객체를 직렬화/역직렬화하는 작업은 객체의 모든 인스턴스변수가 참조하고 있는 모든 객체에 대한 것이기 때문에 상당히 복잡하며 시간도 오래 걸린다
readObject()와 writeObject()를 사용한 자동 직렬화가 편리하기는 하지만 직렬화작업시간을 단축시키려면 직렬화하고자 하는 객체의 클래스에 추가적으로 다음과 같은 2개의 메소드를 직접 구현해주어야 한다.
// 마커 인터페이스지만 , 직렬화를 고려하여 작성한 클래스인지 판단하는 기준이 됨
public interface Serializable{}
public class UserInfo implements java.io.Serializable{
String name;
String password;
int age;
}
Serializable을 구현한 클래스를 상속받는다면, Serializable을 구현하지 않아도 직렬화 가능
public class SuperUserInfo implements Serializable{
String name;
String password;
}
public class UserInfo extends SuperUserInfo{
int age;
}
조상 클래스가 Serializable을 구현하지 않았다면 자손 클래스 직렬화 시 조상클래스의 인스턴스 변수는 대상 제외
public class SuperUserInfo{
String name; // 직렬화 대상에서 제외
String passwrod; // 직렬화 대상에서 제외
}
public class UserInfo extends SuperUserInfo implements Serializable{
int age;
}
public class UserInfo implements Serializable {
String name;
String password;
int age;
Object obj = new Object(); // Object 객체는 직렬화할 수 없다.
}
위 코드의 UserInfo는 Serializable을 구현하고 있지만 이 클래스의 객체를 직렬화하면 java.io.NotSerializableException이 발생하면서 직렬화에 실패
-> 직렬화할 수 엇없는 클래스의 객체를 인스턴스변수가 참조
모든 클래스의 조상인 Object는 Serializable을 구현하지 않았기 때문에 직렬화할 수 없다.
//이 경우에, 참조변수는 직렬화가 안되는 object 타입이지만 실제로 저자된 객체는
// 직렬화가 가능한 String 인스턴스이기에 직렬화 가능
Object obj = new String("abc")
transient가 붙은 인스턴스변수의 값은 그 타입의 기본값으로 직렬화된다고 볼 수 있다
-> 직렬화 대상 제외
//obj와 password의 값은 null이 된다
public class UserInfo implements Serializable {
String name;
transient String password; // 직렬화 대상에서 제외된다.
int age;
transient Object obj = new Object(); // 직렬화 대상에서 제외된다.
}
// 다음 예제에 활용될 UserInfo클래스의 소스
public class UserInfo implements java.io.Serializable{
Stirng name;
String password;
int age;
public UserInfo(){
this("Unknown", "1111", 0);
}
public UserInfo(String name, String passwrod, int age){
this.name = name;
this.passwrod = password;
this.age = age;
}
public String toString(){
return "(" + name + "," + password + "," + age + ")";
}
}
import java.io.*;
import java.util.ArrayList;
public class Ex15_20{
public static void main(String[] args){
try{
String fileName = "UserInfo.ser";
FileOutputStream fos = new FileOutputStream(fileName);
BufferedOutputStream bos = new BufferedOutputStream(fos);
ObjectOutputStream out = new ObjectOutputStream(bos);
UserInfo u1 = new UserInfo("JavaMan", "1234", 30);
UserInfo u2 = new UserInfo("JavaWoman", "4321", 26);
ArrayList<UserInfo> list = new ArrayList<>();
list.add(u1);
list.add(u2);
// 객체를 직렬화
out.writeObject(u1);
out.writeObject(u2);
// 배열 직렬화 시 저장된 모든 객체들과 각 객체의 인스턴스 변수가 참조하고 있는 객체까지 직렬화
out.writeObject(list);
out.close();
System.out.println("직렬화가 잘 끝났습니다.");
} catch(IOException e){
e.printStackTrace();
}
}
}
직렬화가 잘 끝났습니다.
역직렬화 예시
import java.io.*;
import java.util.ArrayList;
public class Ex15_21{
public static void main(String[] args){
try{
String fileName = "UserInfo.ser";
FileInputStream fis = new FileInputStream(fileName);
BufferedInputStream bis = new BufferedInputStream(fis);
ObjectInputStream in = new ObjectInputStream(bis);
// 객체를 읽을 때는 출력한 순서와 일치해야 한다
UserInfo u1 = (UserInfo)in.readObject();
UserInfo u2 = (UserInfo)in.readObject();
ArrayList list = (ArrayList)in.readObject();
System.out.println(u1);
System.out.println(u2);
System.out.println(list);
in.close();
} catch(Exception e){
e.printStackTrace();
}
}
}
(JavaMan,1234,30)
(javaWoman,4321,26)
[(JavaMan,1234,30) , (JavaWoman,4321,26)]
객체 u1,u2,list 순으로 직렬화를 했다면 , 역직렬화 할때도 u1, u2, list의 순서대로 처리해야 한다!