ㆍ 문자기반 스트림
▶ Reader와 Writer
- 각각 문자기반 스트림(input / output)의 최고 조상이다.
- byte배열 대신 char배열을 사용한다는 것 외에는 InputStream / OutputStream의 메소드와 다르지 않다.
▶ FileReader와 FileWriter
- 파일로부터 텍스트데이터를 읽고, 파일에 쓰는데 사용된다. 사용법은 FileInputStream / FileOutputStream과 다르지 않음
ex) FileInputStream과 FileReader
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
public class Ex15_8 {
public static void main(String[] args) {
String fileName = "data.txt";
try {
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) {
}
}
}
(FileInputStream을 사용하면 한글이 깨져서 출력이 된다)
ex) 파일 내용의 공백을 모두 없애서 복사하기
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Ex15_9 {
public static void main(String[] args) {
try {
FileReader fr = new FileReader("Ex15_9.java");
FileWriter fw = new FileWriter("convert.txt");
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.printStackTrace();
}
}
}
(이 자바 파일에 있는 모든 공백을 없애서 convert.txt에 저장했다)
▶ PipedReader와 PipedWriter
- 쓰레드간에 데이터를 주고받을 때 사용된다.
- 다른 스트림과는 달리 입력과 출력스트림을 하나의 스트림으로 연결(connect)해서 데이터를 주고받는다.
- 입출력을 마친 후에는 어느 한쪽 스트림만 닫아도 나머지 스트림은 자동으로 닫힌다.
(심화과정인것 같음. 스레드와 관련되어 있으므로 나중에 다시 보려고 함)
▶ StringReader와 StringWriter
- CharArrayReader/CharArrayWriter와 같이 입출력 대상이 메모리인 스트림이다.
- StringWriter에 출력되는 데이터는 내부의 StringBuffer에 저장된다.
StringBuffer getBuffer() // StringWriter에 출력된 데이터가 저장된 StringBuffer를 반환한다.
String toString() // StringWriter에 출력된 (StringBuffer에 저장된) 문자열을 반환한다.
ex)
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
public 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());
}
}
ㆍ 문자기반 보조스트림
▶ BufferedReader와 BufferedWriter
- 입출력 효율을 높이기 위해 버퍼(char[])를 사용하는 보조스트림
- 데이터를 라인(line) 단위로 읽을 수 있어 입출력이 편리하다.
ex)
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Ex15_11 {
public static void main(String[] args) {
FileReader fr;
try {
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) {
}
}
}
(더 이상 읽어올 라인이 없으면 readLine()이 null을 반환한다)
ㆍ InputStreamReader와 OutputStreamWriter
- 바이트기반 스트림을 문자기반 스트림처럼 쓸 수 있게 해준다.
- 인코딩(encoding)을 변환하여 입출력할 수 있게 해준다.
ex)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public 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) {}
}
}
ㆍ표준입출력과 File
▶ 표준입출력 - System.in, System.out, System.err
- 콘솔(console, 화면)을 통한 데이터의 입출력을 '표준 입출력'이라 한다.
- JVM이 시작되면서 자동적으로 생성되는 스트림이다.
ex)
import java.io.IOException;
public class StandardIOEx1 {
public static void main(String[] args) {
int input = 0;
try {
while((input=System.in.read())!=-1) {
System.out.println("input :" + input + ", (char)input :"+(char)input);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
(Ctrl 키와 z키를 동시에 누르면 이 프로그램이 종료된다. Ctrl+z를 누르는 것은 스트림의 끝을 의미함)
▶ 표준입출력의 대상변경 - setOut(), setErr(), setIn()
setOut(), setErr(), setIn()를 사용하면 입출력을 콘솔 이외에 다른 입출력 대상으로 변경하는 것이 가능하다.
ex)
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
public class Ex15_14 {
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream("test.txt");
PrintStream ps = new PrintStream(fos);
System.setOut(ps); // System.out의 출력대상을 test.txt파일로 변경
} catch (FileNotFoundException e) {
System.err.println("File not found.");
}
System.out.println("Hello by System.out");
System.err.println("Hello by System.err");
}
}
실행하면 Hello by System.err만 콘솔에 출력된다.
System.out의 출력소스를 test.txt 파일로 변경하였기 때문에 System.out을 이용한 출력은 모두 test.txt파일에 저장된다.
▶ RandomAccessFile
- 하나의 스트림으로 파일에 입력과 출력을 모두 수행할 수 있는 스트림
- 다른 스트림들과 달리 Object의 자손이다.
- DataInput과 DataOutput 인터페이스를 구현했기 때문에 읽기와 쓰기가 모두 가능하다.
그러므로 RandomAccessFile 클래스도 DataInputStream과 DataOutputStream 처럼 기본자료형 단위로 데이터를 읽고 쓸 수 있다.
ex)
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileEx1 {
public static void main(String[] args) {
try {
RandomAccessFile raf = new RandomAccessFile("test.dat", "rw");
System.out.println("파일 포인터의 위치: "+raf.getFilePointer());
raf.writeInt(100);
System.out.println("파일 포인터의 위치: "+raf.getFilePointer());
raf.writeLong(100L);
System.out.println("파일 포인터의 위치 "+raf.getFilePointer());
} catch (IOException e) {
e.printStackTrace();
}
}
}
▶ File
- 파일과 디렉토리를 다루는데 사용되는 클래스
ex)
import java.io.File;
import java.io.IOException;
public class Ex15_15 {
public static void main(String[] args) throws IOException {
File f = new File("C:\\Users\\K\\Desktop\\Java1\\자바의 정석\\ch15\\Ex15_15.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"));
}
}
ex)
import java.io.File;
public 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.exists() || !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);
}
}
}
ex) 지정한 확장자를 가진 파일 삭제하기
import java.io.*;
public class Ex15_17 {
static int deletedFiles = 0; // 삭제파일 카운팅하기 위한 변수
public static void delete(File dir, String ext) {
File[] files = dir.listFiles(); // 해당 디렉토리에 있는 파일목록을 File배열로 반환
for(int i=0; i<files.length; i++) {
if(files[i].isDirectory()) { // files[i]가 디렉토리인지 확인
delete(files[i], ext); // 디렉토리이면 재귀호출
} else {
String filename = files[i].getAbsolutePath(); // files[i]가 파일이라면 해당 파일경로를 저장
if(filename.endsWith(ext)) { // 확장자가 ext 인지 확인
System.out.print(filename); // 경로 출력
if(files[i].delete()) {
System.out.println(" - 삭제 성공"); // 삭제시 붙을 문장
deletedFiles++; // 삭제파일 카운트 증가
} else {
System.out.println(" - 삭제 실패");
}
}
}
}
}
public static void main(String[] args) {
if(args.length != 1) {
System.out.println("USAGE : java Ex15_17 Extension");
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 + "개의 파일이 삭제되었습니다.");
}
}
ex) 특정 디렉토리에 있는 파일들의 이름 바꾸기
import java.io.File;
public class Ex15_18 {
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Usage: java Ex15_18 DIRECTORY");
System.exit(0);
}
File dir = new File(args[0]);
if(!dir.exists() || !dir.isDirectory()) {
System.out.println("유효하지 않은 디렉토리 입니다.");
System.exit(0);
}
File[] list = dir.listFiles();
for(int i = 0; i< list.length; i++) {
String fileName = list[i].getName();
// 파일명
String newFileName = "0000" + fileName;
newFileName = newFileName.substring(newFileName.length() - 7);
list[i].renameTo(new File(dir,newFileName));
}
}
}
ㆍ 직렬화
▶ 직렬화란?
- 객체를 연속적인 데이터로 변환하는 것을 말한다. 반대과정은 '역직렬화' 라고 한다.
- 객체의 인스턴스변수들의 값을 일렬로 나열하는 것
- 객체를 저장하기 위해서는 객체를 직렬화해야 한다.
- 객체를 저장한다는 것은 객체의 모든 인스턴스변수의 값을 저장하는 것(메소드는 포함X)
▶ ObjectInputStream, ObjectOutputStream
- 객체를 직렬화하여 입출력할 수 있게 해주는 보조스트림
ObjectInputStream(InputStream in)
ObjectOutputStream(OutputStream out)
- 객체를 파일에 저장하는 방법
FileOutputStream fos = new FileOutputStream("objectfile.ser");
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(new UserInfo());
- 파일에 저장된 객체를 다시 읽어오는 방법
FileInputStream fis = new FileInputStream("objectfile.ser");
ObjectInputStream in = new ObjectInputStream(fis);
UserInfo info = (UserInfo)in.readObject();
▶ 직렬화 가능한 클래스 만들기
- java.io.Serializable 을 구현해야만 직렬화가 가능하다. Serializable 인터페이스는 아무런 내용도 없는 빈 인터페이스지만, 직렬화를 고려하여 작성한 클래스인지를 판단하는 기준이 된다.
public class UserInfo implements java.io.Serializable {
String name;
String password;
int age;
}
- 제어자 transient가 붙은 인스턴스변수는 직렬화 대상에서 제외된다.
public class UserInfo implements Serializable {
String name;
transient String password; // 직렬화 대상에서 제외된다.
int age;
}
- Serializable을 구현하지 않은 클래스의 인스턴스도 직렬화 대상에서 제외된다.
public class UserInfo implements Serializable {
String name;
transient String password;
int age;
Object obj = new Object(); // Object 객체는 직렬화할 수 없다.
Object obj = new String("abc");
// 인스턴스 변수 타입이 Object 이지만 String은 직렬화 될 수 있다.
// 즉, 인스턴스변수의 타입이 아닌 실제로 연결된 객체의 종류에 의해서 직렬화 여부가 결정된다.
}
ex) Serializable 인터페이스를 구현한 클래스 생성
import java.io.Serializable;
public class UserInfo implements Serializable {
String name;
String password;
int age;
public UserInfo() {
this("Unknown", "1111", 0);
}
public UserInfo(String name, String password, int age) {
this.name = name;
this.password = password;
this.age = age;
}
public String toString() {
return "(" + name + "," + password + "," + age + ")";
}
}
ex) UserInfo 객체를 직렬화 하여 UserInfo.ser에 저장하기
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
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();
}
}
}
ex) UserInfo.ser에 저장된 객체를 역직렬화하기
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
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();
}
}
}
▶ 직렬화 가능한 클래스의 버전관리
- 직렬화 했을 때와 역직렬화 했을 때의 클래스가 같은지 확인해야 한다. 확인하기 위해 클래스의 버전을 비교하게 된다.
- 직렬화 할 때, 클래스의 버전(serialVersionUID)을 자동계산해서 저장한다.
- 클래스의 버전을 수동으로 관리하려면, 클래스 내에 serialVersionUID를 추가로 정의해야 한다.
class MyData implements java.io.Serializable {
static final long serialVersionUID = 3518731767529258119L;
int value1;
}
이렇게 클래스 내에 serialVersionUID를 정의해주면, 클래스의 내용이 바뀌어도 클래스의 버전이 자동생성된 값으로 변경되지 않는다.
- serialver.exe는 클래스의 serialVersionUID를 자동생성해준다.
해당 클래스가 있는 디렉토리에서 'serialver 클래스이름' 을 입력하면 클래스의 serialVersionUID를 알아낼 수 있다.
serialver.exe는 클래스에 serialVersionUID가 정의되어 있으면 그 값을 출력하고 정의되어 있지 않으면 자동 생성한 값을 출력한다.
serialver.exe에 의해서 생성되는 serialVersionUID값은 클래스의 멤버들에 대한 정보를 바탕으로 하기 때문에 이 정보가 변경되지 않는 한 항상 같은 값을 생성한다.
출처 : 자바의정석 3rd Edtion [저자 남궁 성]
'Java > Java의 정석' 카테고리의 다른 글
220409 Java - Chapter 16. 네트워킹(Networking) (0) | 2022.04.09 |
---|---|
220406 Java - Chapter 15. 입출력 I/O (0) | 2022.04.06 |
220402 Java - Chapter 14. 람다와 스트림(Lambda & Stream) Part 3 (0) | 2022.04.02 |
220331 Java - Chapter 14. 람다와 스트림(Lambda & Stream) Part 2 (0) | 2022.03.31 |
220330 Java - Chapter 14. 람다와 스트림(Lambda & Stream) Part 1 (0) | 2022.03.30 |