본문 바로가기

JAVA

15장- 입출력 I/O 끝 21. 02. 16.

package chap15;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

public class FileEx3 {	   // 필드 2개, 메서드 2개
	static int totalFiles; // 클래스변수 (클래스를 호출하는 시점에 초기화됨 생성. / 프로그램 종료될 때 죽음.)
	static int totalDirs;
	public static void printFileList(File dir) {
		System.out.println(dir.getAbsolutePath() + " 디렉토리");
		File[] files = dir.listFiles();
		
		ArrayList<String> subDir = new ArrayList<>();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd kk:mm");
		for(int i = 0 ; i < files.length ; i++) {
			String fileName = files[i].getName();
			String attr="";
			String size = "";
			if(files[i].isDirectory()) {
				subDir.add(i+"");
				attr = "DIR";
			}
			else {
				size = files[i].length() + ""; 
				attr += files[i].canRead() ? "R" : " ";
				attr += files[i].canWrite() ? "W" : " ";
				attr += files[i].isHidden() ? "H" : " ";
			}
			System.out.println(
					String.format("%s %3s %6s %s", sdf.format(new Date(files[i].lastModified())), attr, size, fileName)
					);
		}
		int dirNum = subDir.size();
		int fileNum = files.length - dirNum;
		
		totalDirs += dirNum; // 값을 계속해서 유지함.(불변이 아니라)
		totalFiles += fileNum;
		
		System.out.println(fileNum + "개의 파일, " + dirNum + "개의 디렉토리");
		System.out.println();
		// 내가 나를 호출 rescursive call
		// call stack
		for(int i = 0 ; i < subDir.size() ; i++) {
			int idx = Integer.parseInt(subDir.get(i));
			printFileList(files[idx]);
		}
	}
	public static void main(String[] args) {
		String dirName = "C:\\Program Files\\Java\\jdk1.8.0_271";
		File dir = new File(dirName);
		
		if(!dir.exists() || !dir.isDirectory()) {
			System.out.println("유효하지 않은 디렉토리");
			System.exit(0);
		}
		printFileList(dir);
		
		System.out.println("총 " + totalFiles + "개의 파일");
		System.out.println("총 " + totalDirs + "개의 디렉토리");
	}
}

FileEx3.java

- 파일 목록을 출력하고, 디렉토리인 경우 ArrayList에 담았다가 각 디렉토리에 대해 pringFileList(File dir)를 재귀호출함.

 

 

 

package chap15;

import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

public class TmpFileCreator {
	public static void main(String[] args) throws Exception {
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("tmp.dat"));
		for(int i = 0 ; i < 4_881_080 ; i++) { // 횟수 = 바이트숫자
			bos.write(0);
		}
		bos.close();
	}
}

TpmFileCreator.java

- 크기가 4,881,080 바이트인 .dat 파일 생성.

 

package chap15;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileSplit {
	public static void main(String[] args) throws IOException {

		int unit = 1000;
		final int VOLUMN = unit * 1000; // 백만바이트
		String fileName = "tmp.dat";

		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName));
		BufferedOutputStream bos = null;

		int data = 0;
		int i = 0;
		int number = 0;

		while ((data = bis.read()) != -1) { // 1바이트씩 읽은 걸 data에 담음. -1이 아닐때까지
			if (i % VOLUMN == 0) {
				if (i != 0) {
					bos.close();
				}
				bos = new BufferedOutputStream(new FileOutputStream(fileName + "_." + ++number));
			}
			bos.write(data);
			i++;
		}
		bis.close();
		bos.close();
	}
}

FileSplit

- 위에서 만든 tmp.dat 파일을 unit * 1000 바이트 크기인 파일로 쪼갬. tmp.dat_.5는 881,080 바이트. tmp.dat는 0 바이트.

 

 

package chap15;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;


public class FileMerge {
	public static void main(String[] args) throws Exception {
		String mergeFilename = "tmp.dat";
		File tmpFile = File.createTempFile("~mergetmp", ".tmp");
		tmpFile.deleteOnExit();

		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tmpFile));
		BufferedInputStream bis = null;

		int number = 1;

		File f = new File(mergeFilename + "_." + number);

		while (f.exists()) {
			f.setReadOnly();
			bis = new BufferedInputStream(new FileInputStream(f));

			int data = 0;
			while ((data = bis.read()) != -1) {
				bos.write(data);
			}
			bis.close();
			f = new File(mergeFilename + "_." + ++number);
		}
		bos.close();
		File oldFile = new File(mergeFilename);
		if (oldFile.exists()) {
			oldFile.delete();
		}
		tmpFile.renameTo(oldFile);
	}
}

- 위에 나눈 파일을 다시 합침.

- 작업할 임시파일을 새로 만들고 프로그램 종료시 자동 삭제 되도록 함.

- 합치는 작업이 온전히 끝나면, 기존 파일을 학제하고 임시파일의 이름을 기존 파일의 이름으로 변경.

- 0 바이트였던 tmp.dat가 다시 4,881,080 바이트가 됨.

 

 

7. 직렬화(Serialization)

 

7.1 직렬화란?

- 객체를 컴퓨터에 저장했다가 다음에 다시 꺼내 쓰거나, 네드웤을 통해 컴퓨터 간에 서로 객체를 주고받을 수 있게 해 줌.

- 객체를 데이터 스트림으로 만드는 것. 즉, 객체에 저장된 데이터를 스트림에 쓰기(write) 위해 연속적인(serial) 데이터로 변환하는 것.

- 반대로 스트림으로부터 데이터를 읽어서 객체를 만드는 것은 역직렬화.

- 객체는 클래스에 정의된 인스턴스변수의 집합. (클래스변수나 메서드가 객체에 포함되지 않음. > 직렬화 제외)

 

7.2 ObjectInputStream, ObjectOutputStream

 

7.3 직렬화가 가능한 클래스 만들기 - Serializable, transient

- 직렬화하고자 하는 클래스가 java.io.Serializable인터페이스를 구현하도록 하면 됨.

- 직렬화하고자 하는 객체의 클래스에 직렬화가 안 되는 객체에 대한 참조를 포함하고 있다면, 제어자 transient를 붙여서 직렬화 대상에서 제외되도록 할 수 있음.

 

package chap15.serial;

import java.io.Serializable;

public class UserInfo implements Serializable {
	/**
	 * 직렬화시 포함된 필드 (문서주석화)
	 * name, password, age 
	 */
	private static final long serialVersionUID = 1348362370342145473L;
	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;
	}

	@Override
	public String toString() {
		return "UserInfo [name=" + name + ", password=" + password + ", age=" + age + "]";
	}
}

- 아래 예시들을 하기 위한 UserInfo클래스의 소스.

 

 

package chap15.serial;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

public class SerialEx1 {
	public static void main(String[] args) throws Exception {
		String fileName = "UserInfo.ser";
		ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(fileName)));
		UserInfo u1 = new UserInfo("javaman", "1234", 30);
		UserInfo u2 = new UserInfo("javawoman", "4321", 20);
		
		ArrayList<UserInfo> list = new ArrayList<>();
		list.add(u1);
		list.add(u2);
		
        // 객체를 직렬화 함.
		oos.writeObject(u1);
		oos.writeObject(u2);
		oos.writeObject(list);
		oos.close();
		System.out.println("직렬화가 잘 끝났습니다.");
	}
}

- 생성한 객체를 직렬화하여 UserInfo.ser에 저장.

- ArrayList와 같은 객체를 직렬화하면 ArrayList에 저장된 모든 객체들과 각 객체의 인스턴스변수가 참조하고 있는 객체들까지 모두 직렬화 됨.

 

 

package chap15.serial;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.ArrayList;

public class SerialEx2 {
	public static void main(String[] args) throws Exception {
		String fileName = "UserInfo.ser";
		ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(fileName)));
		
		UserInfo u1 = (UserInfo)ois.readObject();
		UserInfo u2 = (UserInfo)ois.readObject();
		ArrayList list = (ArrayList)ois.readObject();
		
		System.out.println(u1);
		System.out.println(u2);
		System.out.println(list);
		
		ois.close();
		
	}
}

SerialEx2.java

- 직렬화한 객체를 역직렬화함. (OujectInputStream의 readObject() 사용)

- readObject()의 리턴타입이 Object이므로 원래의 타입으로 형변환을 해줘야 함.

- 객체를 역직렬화할 때는 직렬화할 때의 순서와 일치해야 함. (u1, u2, list의 순서로 직렬화 했기 때문에 u1, u2, list의 순서로 역직렬화!)

-> 직렬화할 객체가 많을 때는 ArrayList와 같은 컬렉션에 저장해서 ArrayList 하나만 역직렬화함. (객체의 순서를 고려하지 않아도 되기 때문)

 

7.4 직렬화가능한 클래스의 버전관리

- 직렬화된 객체를 역직렬화할 때는 직렬화 했을 때와 같은 클래스를 사용해야 함.

- serialVersionUID