ㆍ 문자기반 스트림

 

▶ Reader와 Writer

- 각각 문자기반 스트림(input / output)의 최고 조상이다.

- byte배열 대신 char배열을 사용한다는 것 외에는 InputStream / OutputStream의 메소드와 다르지 않다.

 

Reader의 메소드
Writer의 메소드

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)을 변환하여 입출력할 수 있게 해준다.

InputStreamReader의 생성자와 메소드
OutputStreamWriter의 생성자와 메소드

 

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 처럼 기본자료형 단위로 데이터를 읽고 쓸 수 있다.

RandomAccessFile의 상속계층도.

 

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"));

	}

}

 

File의 메소드

 

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();

ObjectInputStream의 메소드
ObjectOutputStream의 메소드

▶ 직렬화 가능한 클래스 만들기

- 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 [저자 남궁 성]

 

+ Recent posts