Java

[이것이 자바다] 자바 NIO 공부 정리

Razelo 2020. 12. 30. 13:21

path

package sec02.exam01_path;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;

public class PathExample {

	public static void main(String[] args) {
		Path path = Paths.get("src/sec02/exam01_path/PathExample.java");
		System.out.println("[파일명]"+ path.getFileName()); //파일명 얻기 
		System.out.println("[부모 디렉토리명]" + path.getParent().getFileName());//getParent하면 부모 디렉토리의 Path객체를 얻는다. 
		System.out.println("[중첩 경로수]"+ path.getNameCount()); // 경로수를 의미한다. 이 경로의 경우 4가 나온다. 직접 세어보면 안다. 
		System.out.println();
		
		for(int i = 0;i<path.getNameCount();i++) { //4번 돈다. 
			System.out.println(path.getName(i)); //각 경로에 있는 Path객체가 출력이 된다. 
		}
		System.out.println();
		
		
		Iterator<Path> iterator = path.iterator(); //각 경로에 있는 이들 Path의 반복자를 리턴한다. 
		while(iterator.hasNext()) {
			Path temp = iterator.next();
			System.out.println(temp.getFileName());
		} //위에 있는 for문과 똑같이 출력된다. 
		
		/*
		 * 	src
			sec02
			exam01_path
			PathExample.java	
		 * 이렇게 출력된다. 
		 * 
		 */
	}

}

 

 

 

filesystem

package sec02.exam02_filesystem;

import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
public class FileSystemExample {

	public static void main(String[] args) throws Exception{
		FileSystem fileSystem = FileSystems.getDefault(); //FileSystem 구현객체를 얻을 수 있다. 
		
		for(FileStore store: fileSystem.getFileStores()) { //FileStore에 대한 객체를 하나씩 가져온다. 
			System.out.println("드라이버명: " +store.name()); //OS 라고 출력됨. 
			System.out.println("파일시스템: "+store.type());
			System.out.println("전체 공간: \t "+store.getTotalSpace() + "바이트");
			System.out.println("사용 중인 공간: \t "+(store.getTotalSpace() - store.getUnallocatedSpace()) + "바이트");
			System.out.println("사용 가능한 공간: \t "+store.getUsableSpace() + "바이트");
			System.out.println();
		}
		/*
		 드라이버명: OS
		 파일시스템: NTFS
		 전체 공간: 	 255193600000바이트
		 사용 중인 공간: 	 201094352896바이트
		 사용 가능한 공간: 	 54099247104바이트 
		 
		 -> 이렇게 출력된다. 
		 */
		
		System.out.println("파일 구분자: "+fileSystem.getSeparator()); //경로 구분자가 뭔지 알아낼 수 있다. 윈도우이면 \가 나온다. 
		System.out.println();
		
		//루트 디렉토리에 대한 정보
		for(Path path: fileSystem.getRootDirectories()) {
			System.out.println(path.toString()); //C:\ 출력된다. 
		}
		
	}

}

 

 

 

 

file_directory

package sec02.exam03_file_directory;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileExample {

	public static void main(String[] args) throws Exception{
		Path path = Paths.get("src/sec02/exam03_file_directory/FileExample.java");
		System.out.println("디렉토리 여부: "+Files.isDirectory(path));
		System.out.println("파일 여부: "+Files.isRegularFile(path));
		System.out.println("마지막 수정 시간: "+Files.getLastModifiedTime(path));
		System.out.println("파일 크기: "+Files.size(path));
		System.out.println("소유자: "+Files.getOwner(path).getName());
		System.out.println("숨김 파일 여부: "+Files.isHidden(path));
		System.out.println("읽기 가능 여부: "+Files.isReadable(path));
		System.out.println("쓰기 가능 여부: "+Files.isWritable(path));

		
		/*
		 
			디렉토리 여부: false
			파일 여부: true
			마지막 수정 시간: 2020-12-28T01:29:37.23361Z
			파일 크기: 830
			소유자: DESKTOP-DRPV8FM\\user
			숨김 파일 여부: false
			읽기 가능 여부: true
			쓰기 가능 여부: true
		 */
		
		
		/*
		  
		  
			★ NTFS는 윈도우NT 운영체계가 하드디스크 상에 파일들을 저장하고 검색하는데 사용하는 시스템이다.  
			
			윈도우NT에서 NTFS의 역할은, 윈도우95의 FAT이나 OS/2의 HPFS에 해당하는 것이다.
			
			그러나, NTFS는 FAT이나 HPFS에 비해 성능이나 확장성 및 보안성 면에 있어, 많은 개선점들을 제공한다.
			
			 
			
			● 특히 주목할만한 NTFS의 기능들은 다음과 같다.
			
			파일 클러스터들을 추적하기 위해 b-tree 디렉토리 개념을 사용.
			
			파일의 클러스터들에 관한 정보와 다른 데이터들이 각 클러스터에 함께 저장된다 (FAT는 관리용 테이블만이 저장된다).
			
			최대 264, 즉 대략 160억 바이트 정도의 매우 큰 파일도 지원.
			
			서버 관리자가 ACL을 이용하여 누가 어떤 파일만을 액세스할 수 있는지 등을 통제 가능. 통합된 파일 압축.
			
			유니코드 기반의 파일이름 들을 지원.
			
			긴 파일이름을 지원.
			
			교체용 디스크와 고정 디스크 모두에 대해 데이터 보안을 지원.
			
			 
			
			● NTFS 동작원리
			
			하드디스크는 초기화(포맷)될 때, 물리적인 전체 하드디스크 공간이 파티션으로 나뉘어진다.
			
			운영체계는, 각 파티션 내에 저장된 모든 파일들에 대한 상황들을 계속 추적한다.
			
			각 파일은 실제로 하드디스크 상의 하나 이상의 클러스터에 저장된다.
			
			NTFS를 사용하면, 클러스터의 크기를 512 바이트에서 64 KB 사이에서 정할 수 있다.
			
			윈도우NT는 드라이브가 지정되면 그 크기에 따라 적당한 기본 클러스터 크기를 추천한다.
			
			예를 들면, 4 GB 짜리 드라이브에 대해서는 기본 클러스터 크기가 4 KB이다.
			
			한번 정해진 클러스터들은 더 이상 나눌 수 없다는데 유의하라.
			
			아무리 크기가 작은 파일이라 하더라도 한 클러스터를 차지하고, 4.1 KB 크기의 파일에 대해 4 KB 클러스터 시스템에서는 두 개의 클러스터(8 KB 필요)가 사용된다.
			
			클러스터 크기는 디스크 공간의 효율적인 사용과, 하나의 파일을 읽어들일 때 몇 번의 디스크 액세스가 필요한지 사이에서 사용자가 선택해야할 문제이다.
			
			NTFS를 사용할 때 일반적으로, 하드디스크의 크기가 클수록 기본 클러스터 크기가 커지는데, 그 이유는 이 시스템의 사용자가 디스크 공간사용은 다소 비효율적이라도, 디스크 액세스 횟수를 줄임으로써 퍼포먼스를 높이기를 원한다고 가정하기 때문이다. 
			
			NTFS를 사용하여 파일이 만들어지면, 그 파일에 관한 레코드가 MFT라고 불리는 특별한 파일 내에 만들어진다.
			
			그 레코드는 하나의 파일이 여기저기 흩어져있는 클러스터들에 나뉘어 저장되어 있을 때, 그 파일을 찾기 위해 사용된다.
			
			NTFS는 하나의 파일 전체를 담을 수 있을 만한 저장공간(여러 개의 클러스터들이 서로 인접해있는)을 찾으려는 시도를 한다.
			
			각 파일은 데이터의 내용과 함께 그것의 속성에 관한 설명, 즉 메타데이터를 포함하고 있다.
		  
		  
		 */
	}

}
package sec02.exam03_file_directory;

import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class DirectoryExample {

	public static void main(String[] args) throws Exception{
		Path path1 = Paths.get("C:/Temp/dir/subdir");
		Path path2 = Paths.get("C:/Temp/file.txt");
		
		if(Files.notExists(path1)) {
			Files.createDirectories(path1);
		}
		
		if(Files.notExists(path2)) {
			Files.createFile(path2);
		}
		Path path3 = Paths.get("C:/Temp");
		DirectoryStream<Path> directorySream = Files.newDirectoryStream(path3); //DirectoryStream 은 반복자 역할도 한다. 
		for(Path path: directorySream) {
			if(Files.isDirectory(path)) {
				System.out.println("[디렉토리]"+path.getFileName());
			}else {
				System.out.println("[파일]"+path.getFileName() + "(크기:"+Files.size(path) + ")");
			}
		}
		
		/*
		[디렉토리]AUtempR
		[디렉토리]dir
		[파일]file.txt(크기:0)
		[디렉토리]HncDownload 
		  라고 출력된다. 
		 */ 
	}

}

 

 

 

watchservice

package application;

import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.List;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class WatchServiceExample extends Application {
	class WatchServiceThread extends Thread { //스레드를 생성했다. fx어플리케이션 스레드가 ui를 담당해야 하기 때문에 와치서비스를 실행할 수가 없다. 그래서 별도의 스레드로 와치 서비스를 구동했다. 
		@Override
		public void run() {
			try {
				WatchService watchService = FileSystems.getDefault().newWatchService(); // 와치 서비스 생성 
				Path directory = Paths.get("C:/Temp");															//감시할 디렉토리를 Path 객체로 얻음. 
				directory.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,    //와치 서비스 등록하기. 생성,삭제,수정 감시하기. 
						                         StandardWatchEventKinds.ENTRY_DELETE, 
						                         StandardWatchEventKinds.ENTRY_MODIFY);
				while(true) {				//무한루프 
					WatchKey watchKey = watchService.take(); //큐에서 와치키가 들어왔는지 감시, 안들어왔으면 기다리고, 들어오면 와치키를 반환 
					List<WatchEvent<?>> list = watchKey.pollEvents(); //와치키 안에 있는 와치 이벤트를 컬렉션으로 얻어낸다. 
					for(WatchEvent watchEvent : list) {      //하나씩 얻어낸다. 
						//이벤트 종류 얻기
						Kind kind = watchEvent.kind();
						//감지된 Path 얻기
						Path path = (Path)watchEvent.context();  
						if(kind == StandardWatchEventKinds.ENTRY_CREATE) {
							//생성되었을 경우, 실행할 코드
							Platform.runLater(()->textArea.appendText("파일 생성됨 -> " + path.getFileName() + "\n")); //파일이름도 같이 출력ㅎㅁ. 
						} else if(kind == StandardWatchEventKinds.ENTRY_DELETE) {
							//삭제되었을 경우, 실행할 코드
							Platform.runLater(()->textArea.appendText("파일 삭제됨 -> " + path.getFileName() + "\n"));
						} else if(kind == StandardWatchEventKinds.ENTRY_MODIFY) {
							//변경되었을 경우, 실행할 코드
							Platform.runLater(()->textArea.appendText("파일 변경됨 -> " + path.getFileName() + "\n"));
						} else if(kind == StandardWatchEventKinds.OVERFLOW) {
						}
					}
					boolean valid = watchKey.reset(); //와치키를 초기화한다. 초기화가 성공했다면, true이므로 계속해서 루프를 실행 
					if(!valid) { break; }  //만약 false이면 while문을 빠져나간다. 
				}
			} catch (Exception e) {}
		}
	}	
	//여기는 fxui이다. 
	TextArea textArea;
	
	@Override
	public void start(Stage primaryStage) throws Exception {
		BorderPane root = new BorderPane();
		root.setPrefSize(500, 300);
		
		textArea = new TextArea();
		textArea.setEditable(false);
		root.setCenter(textArea);
		
		Scene scene = new Scene(root);
		primaryStage.setScene(scene);
		primaryStage.setTitle("WatchServiceExample");
		primaryStage.show();
		
		WatchServiceThread wst = new WatchServiceThread();
		wst.start();
	}
	
	public static void main(String[] args) {
		launch(args);
	}
}


















 

 

 

 

direct_buffer

package sec03.exam01_direct_buffer;

import java.nio.ByteBuffer;

public class BufferSizeExample {

	public static void main(String[] args) {
		//다이렉트 버퍼는 바이트 버퍼만 생성이 가능
		ByteBuffer directBuffer = ByteBuffer.allocateDirect(200*1024*1024); //200MB //매개값은 몇 바이트까지 저장할 것인지 임.
		System.out.println("다이렉트 버퍼가 생성되었습니다.");
		
		ByteBuffer nonDirectBuffer = ByteBuffer.allocate(200*1024*1024); //JVM 힙 메모리 영역에 매개변수값만큼 바이트가 저장될 넌다이렉트 버퍼를 생성한다. 
		System.out.println("넌다이렉트 버퍼가 생성되었습니다.");
		
		
	}

}
package sec03.exam01_direct_buffer;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.IntBuffer;

public class DirectBufferCapacityExample {

	public static void main(String[] args) {
		//필요에 따라서 타입별 버퍼로 변환해서 사용할 수 있다. 
		
		ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100);
		System.out.println("저장용량: "+byteBuffer.capacity() + "바이트");
		
		CharBuffer charBuffer = ByteBuffer.allocateDirect(100).asCharBuffer();
		System.out.println("저장용량: "+charBuffer.capacity() + " 문자");
		
		IntBuffer intBuffer = ByteBuffer.allocateDirect(100).asIntBuffer();
		System.out.println("저장용량: "+intBuffer.capacity() + " 정수");
	/*
	 * 
	 * 저장용량: 100바이트
		저장용량: 50 문자
		저장용량: 25 정수
	 * 
	 */
	
	
	}

}
package sec03.exam01_direct_buffer;

import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;

public class PerformanceExample {
	public static void main(String[] args) throws Exception {
		
		//테스트만 하는 클래스이다. <- ( 아직 배우지 않은 채널이 나오므로,) 다이렉트가 훨씬 빠른것을 확인할 수 있다. 
		Path from = Paths.get("src/sec03/exam01_direct_buffer/yein.jpg"); //원본 파일 
		Path to1 = Paths.get("src/sec03/exam01_direct_buffer/yein2.jpg"); //타겟 파일 
		Path to2 = Paths.get("src/sec03/exam01_direct_buffer/yein3.jpg"); //타겟 파일 
		  
		long size = Files.size(from);  //파일의 사이즈 
		
	    FileChannel fileChannel_from = FileChannel.open(from); //파일 채널 생성. 
	    FileChannel fileChannel_to1 = FileChannel.open(to1, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));
	    FileChannel fileChannel_to2 = FileChannel.open(to2, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));
	    
	    //버퍼 생성 
	    ByteBuffer nonDirectBuffer = ByteBuffer.allocate((int) size);   //넌 다이렉트 
	    ByteBuffer directBuffer = ByteBuffer.allocateDirect((int)size); //다이렉트 
	    
	    long start, end;
    	//넌 다이렉트버퍼 이용해서 100번 복사한다. 
	    start = System.nanoTime();
	    for(int i=0; i<100; i++) {
		    fileChannel_from.read(nonDirectBuffer);
	    	nonDirectBuffer.flip();
	    	fileChannel_to1.write(nonDirectBuffer);
	    	nonDirectBuffer.clear();
	    }
    	end = System.nanoTime();
    	System.out.println("넌다이렉트:\t" + (end-start) + " ns");
    	
    	fileChannel_from.position(0);
    	//다이렉트버퍼 이용해서 100번 복사한다. 
	    start = System.nanoTime();	    
    	for(int i=0; i<100; i++) {
		    fileChannel_from.read(directBuffer);
	    	directBuffer.flip();
	    	fileChannel_to2.write(directBuffer);
	    	directBuffer.clear();
    	}
    	end = System.nanoTime();
    	System.out.println("다이렉트:\t" + (end-start) + " ns");
    	
    	
    	//파일 채널 닫기 
    	fileChannel_from.close();
    	fileChannel_to1.close();
    	fileChannel_to2.close();
	}
}

 

 

 

 

byteorder

package sec03.exam02_byteorder;

import java.nio.ByteOrder;

public class ComputerByteOrderExample {

	public static void main(String[] args) {
		System.out.println("운영체제 종류: "+System.getProperty("os.name"));
		System.out.println("네이티브의 바이트 해석 순서: "+ByteOrder.nativeOrder());
		
		/*
		 * 
		 * 운영체제 종류: Windows 10
			네이티브의 바이트 해석 순서: LITTLE_ENDIAN
		 */
	}
}

 

 

 

buffer

package sec03.exam03_buffer;

import java.nio.Buffer;
import java.nio.ByteBuffer;

public class BufferExample {

	public static void main(String[] args) {
		
		//버퍼의 위치속성에 대해 한눈에 알 수 있는 아주 좋은 예제입니다. 
		
		System.out.println("[7바이트 크기로 버퍼 생성]");
		ByteBuffer buffer = ByteBuffer.allocateDirect(7);
		
		printState(buffer); 
		//position: 0,	limit: 7,	capacity: 7
		
		//두개의 바이트 저장
		buffer.put((byte)10); //바이트로 형변환을 해야함  
		buffer.put((byte)11); 
		System.out.println("[2바이트 저장후]");
		printState(buffer);
		//position: 2,	limit: 7,	capacity: 7
		
		buffer.put((byte)12);
		buffer.put((byte)13); 
		buffer.put((byte)14); 
		System.out.println("[3바이트 저장후]");
		printState(buffer);
		//position: 5,	limit: 7,	capacity: 7
		
		//이제 읽어볼게요 
		buffer.flip(); //저장에서 읽기모드로 넘어올려면 이 메소드를 호출한다. 
		System.out.println("[flip() 실행후]");
		printState(buffer);
		//position: 0,	limit: 5,	capacity: 7
		
		buffer.get(new byte[3]); //바이트 배열을 줬으니 이 바이트 배열의 길이만큼 읽는다. 
		System.out.println("[3바이트 읽은후]");
		printState(buffer);
		//position: 3,	limit: 5,	capacity: 7 
		
		buffer.mark(); //현재 position에 mark를 설정한다. 
		System.out.println("----------[현재 위치를 마크 해놓음]");
		
		//2바이트 더 읽을게요. 
		buffer.get(new byte[2]); 
		System.out.println("[2바이트 읽은후]");
		printState(buffer);
		//position: 5,	limit: 5,	capacity: 7
		//limit도 5이다. 즉 더 이상 읽을 게 없다는 뜻이다. 
		
		
		buffer.reset(); //mark가 있는 위치로 돌아간다. 
		System.out.println("----------[position을 마크 위치로]");
		printState(buffer);
		//position: 3,	limit: 5,	capacity: 7
		
		buffer.rewind();//되감는다. 즉 position을 맨 앞으로 다시 보낸다. 
		System.out.println("[rewind() 실행후]");
		printState(buffer);
		//position: 0,	limit: 5,	capacity: 7
		
		buffer.clear();//position은 0으로 limit은 capacity로.. 즉 초기화한다. 하지만 위치속성값만 처음 값으로 돌아갈뿐, 버퍼의 값(내용)이 지워지지는 않는다. 
		System.out.println("[clear() 실행후]");
		printState(buffer);
		//position: 0,	limit: 7,	capacity: 7
	}
	
	public static void printState(Buffer buffer) { //추상 클래스로 매개변수값을 받기에, 모든 종류의 버퍼를 다 받을 수 있다. 
		System.out.print("\tposition: "+buffer.position()+",");
		System.out.print("\tlimit: "+buffer.limit()+",");
		System.out.println("\tcapacity: "+buffer.capacity());
	}
}
package sec03.exam03_buffer;

import java.nio.Buffer;
import java.nio.ByteBuffer;

public class CompactExample {

	public static void main(String[] args) {
		System.out.println("[7바이트 크기로 버퍼생성]");
		ByteBuffer buffer = ByteBuffer.allocateDirect(7);
		buffer.put((byte)10);
		buffer.put((byte)11);
		buffer.put((byte)12);
		buffer.put((byte)13);
		buffer.put((byte)14);
		buffer.flip();//읽기로 바꾼다. 
		printState(buffer);
		//10,11,12,13,14
		//position: 0,limit: 5,capacity: 7
		
		//세바이트를 읽을게요
		buffer.get(new byte[3]);
		System.out.println("[3바이트 읽음]"); //position은 아마 13위치에 있을 것이다. 
		
		buffer.compact(); //
		System.out.println("[compact() 실행후]");
		printState(buffer);
		//[compact() 실행후]
		//13,14,12,13,14
		//position: 2,limit: 7,capacity: 7
		//position이 13에 있는 상태에서 compact를 하면 아직 읽지 않은 13, 14를 맨 앞에 복사시키고, 그 바로 뒤에 position을 위치시킨다. 그래서 position은 2이다. 
		//limit은 capacity와 같은 값을 갖게 된다. 
		
	}
	public static void printState(ByteBuffer buffer) { 
		//실제로 어떤 값이 들어있는지도 확인해보자. 
		System.out.print(buffer.get(0)+","); //get메소드 같은 경우는 이전예제와는 다르게 Buffer클래스에는 없어서 ByteBuffer로 만들어줘야 get메소드를 쓸 수 있다. 그래서 매개변수가 ByteBuffer인거 
		System.out.print(buffer.get(1)+",");
		System.out.print(buffer.get(2)+",");
		System.out.print(buffer.get(3)+",");
		System.out.println(buffer.get(4));
		System.out.print("position: "+buffer.position()+",");
		System.out.print("limit: "+buffer.limit()+",");
		System.out.println("capacity: "+buffer.capacity());
	}
}

 

 

 

 

 

convertbuffer

package sec03.exam04_convert_buffer;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;

public class ByteBufferToStringExample {

	public static void main(String[] args) {
		//ByteBuffer <--> String
		
		Charset charset = Charset.forName("UTF-8"); //UTF-8문자 셋에 대한 Charset 객체를 얻어냈다. 
		
		//문자열 -> 인코딩 -> ByteBuffer 
		String data = "안녕하세요";
		ByteBuffer byteBuffer = charset.encode(data);
		
		//ByteBuffer -> 디코딩 -> CharBuffer -> 문자열 
		data = charset.decode(byteBuffer).toString();//charset.decode(byteBuffer) 여기까지가 CharBuffer이다. 
		System.out.println("문자열 복원: "+data);
	}

}
package sec03.exam04_convert_buffer;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;

public class ByteBufferToIntBufferExample {

	public static void main(String[] args) {
		//int[] -> IntBuffer -> ByteBuffer
		int[] writeData = {10,20};
		IntBuffer writeIntBuffer = IntBuffer.wrap(writeData);
		ByteBuffer writeByteBuffer = ByteBuffer.allocate(writeIntBuffer.capacity()*4);
		for(int i = 0;i<writeIntBuffer.capacity();i++) {
			writeByteBuffer.putInt(writeIntBuffer.get(i)); //하나씩 저장하기. 
		}
		writeByteBuffer.flip();//읽기 모드로 바꾸기 .
		
		//ByteBuffer -> IntBuffer -> int[] 
		ByteBuffer readByteBuffer = writeByteBuffer;
		IntBuffer readIntBuffer = readByteBuffer.asIntBuffer();
		int[] readData = new int[readIntBuffer.capacity()];
		readIntBuffer.get(readData); //readData에 저장
		System.out.println("배열 복원: "+Arrays.toString(readData)); //배열 복원: [10, 20] 잘 출력됨. 
				
	}

}

 

 

 

 

 

file_read_write

package sec04.exam01_file_read_write;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class FileChannelWriteExample { 

	public static void main(String[] args) throws IOException{
		
		Path path = Paths.get("C:/Temp/file.txt");
		Files.createDirectories(path.getParent()); //file.txt의 부모 Temp가 없다면 Temp 디렉토리를 만든다. 예외처리도 한다. 
	
		FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE,StandardOpenOption.WRITE);
		
		//데이터를 파일 채널을 통해 쓰기를 한다. 
		String data = "안녕하세요"; // -> 2바이트씩 5글자이므로 10바이트이다. 그래서 결과도 10바이트가 나온다. 
		Charset charset = Charset.defaultCharset();//운영체제의 기본 Charset을 이용한다. 
		ByteBuffer byteBuffer = charset.encode(data); //문자열로부터 바이트 버퍼를 얻었으니 이 바이트 버퍼를 가지고 파일 채널은 데이터를 파일에 저장할 수 있다. 
		
		int byteCount = fileChannel.write(byteBuffer); //write()는 이 바이트 버퍼에서 데이터를 파일에 저장하고 나서 실제로 쓴 바이트 수를 리턴한다. 
		System.out.println("file.txt: "+byteCount+"bytes written");
		fileChannel.close();
		
		//file.txt: 10bytes written 가 출력된다. 실제로 Temp에도 file.txt에 안녕하세요가 저장되어 있다. 
	
	}

}
package sec04.exam01_file_read_write;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class FileChannelReadExample {

	public static void main(String[] args) throws IOException{
		//FileChannelWriteExample.java를 먼저 보고 오면 좋습니다. 
		
		Path path = Paths.get("C:/Temp/file.txt");
		
		//파일 읽기용 채널 생성하기. 
		FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ); //읽을 꺼니까 READ 만 준다. 
		
		//읽은 데이터를 저장할 바이트 버퍼
		ByteBuffer byteBuffer = ByteBuffer.allocate(100);
		
		Charset charset = Charset.defaultCharset();
		String data = ""; //실제로 읽은 문자열을 여기에 저장한다. 
 		int byteCount;    //실제 읽은 바이트 수를 저장한다. 
 		
 		while(true) {
 			byteCount = fileChannel.read(byteBuffer);
 			if(byteCount == -1)break;  //파일의 끝을 읽게 된다면 무한루프 빠져나간다. 
 			byteBuffer.flip(); // -1 리턴이 아니므로 계속 진행 가능하다. 그러므로 읽기 모드로 전환해야 한다. byteBuffer에 저장된걸 읽어야 되니까 flip()한다. 
 			data += charset.decode(byteBuffer).toString(); // position부터 limit까지 ChatBuffer가 만들어지고, 여기에 toString을 호출하게 되서 문자열을 얻는다. 
 			byteBuffer.clear(); //(byteBuffer를 재사용하기 위해 작성함.)다시 while문으로 처음으로 돌아갔을 떄 read()가 0인덱스부터 바이트버퍼에 데이터를 저장하게 하기 위함이다. 
 		}
		
 		fileChannel.close();
 		
 		System.out.println("file.txt: "+data);
	}

}

 

 

 

file_copy

package sec04.exam02_file_copy;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;


public class FileCopyExample {

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

		//파일 복사 
		Path from = Paths.get("src/sec04/exam02_file_copy/yein.jpg"); //복사할 원본 파일 
		Path to = Paths.get("src/sec04/exam02_file_copy/yein2.jpg");  // 타겟 파일 

		Files.copy(from,to,StandardCopyOption.REPLACE_EXISTING); //존재한다면 대체해라 //단순히 파일만 복사한다면 이걸 사용해라. 
		System.out.println("[파일 복사 성공]");
		
	}

}
package sec04.exam02_file_copy;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class FilesCopyMethodExample {

	public static void main(String[] args) throws IOException {
		//파일 복사 
		Path from = Paths.get("src/sec04/exam02_file_copy/yein.jpg"); //복사할 원본 파일 
		Path to = Paths.get("src/sec04/exam02_file_copy/yein2.jpg");  // 타겟 파일 
		
		FileChannel fileChannel_from = FileChannel.open(from,StandardOpenOption.READ); //원본 소스로부터 데이터를 읽을 파일 채널 
		FileChannel fileChannel_to = FileChannel.open(to, StandardOpenOption.CREATE,StandardOpenOption.WRITE);  // 타겟 파일에 데이터를 출력할 파일 채널 
	
		//공통적으로 사용할 바이트 버퍼 / 채널로부터 읽고 채널에 데이터를 출력할 것 같으면 다이렉트 버퍼를 사용한다. 
		ByteBuffer buffer = ByteBuffer.allocateDirect(100);
		int byteCount;
		while(true) {
			buffer.clear();//버퍼 재사용해야 하기 때문에 클리어 
			byteCount = fileChannel_from.read(buffer);
			if(byteCount == -1)break;
			buffer.flip();// 데이터를 읽었다면 버퍼를 읽기 모드로 바꾸기 위해 플립한다. limit을 position 으로 position을 0인덱스로 옭기는 작업을 함. 
			fileChannel_to.write(buffer); //position부터 limit까지 데이터를 파일에 출력을 한다. 
		} //계속 반복해서 실행하다가 -1 되면 빠져나온다. 
		
		fileChannel_to.close();
		fileChannel_from.close();
		System.out.println("[파일 복사 성공]");
		
	}

}

 

 

 

asynchronous_filechannel

package sec05.exam01_asynchronous_filechannel;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AsynchronousFileChannelWriteExample {

	public static void main(String[] args) throws IOException {
		//스레드풀 생성
		ExecutorService executorService = Executors.newFixedThreadPool(
				Runtime.getRuntime().availableProcessors()
		);
		
		for(int i = 0;i<10;i++) { //10개의 파일을 생성하고 안에 안녕하세요라는 문구를 넣어준다. 
			Path path = Paths.get("C:/Temp/file" + i +".txt");
			Files.createDirectories(path.getParent()); //Temp가 없다면 만들어라 
			
			//비동기 파일 채널 생성 
			AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
					path,
					EnumSet.of(StandardOpenOption.CREATE,StandardOpenOption.WRITE),
					executorService
			);
			
			Charset charset = Charset.defaultCharset();
			ByteBuffer byteBuffer = charset.encode("안녕하세요");
			
			//콜백 메소드에 제공할 첨부 객체 생성 
			class Attachment{
				Path path;
				AsynchronousFileChannel fileChannel;
			}
			Attachment attachment = new Attachment();
			attachment.path = path;
			attachment.fileChannel = fileChannel;
			
			//CompletionHandler 객체 생성
			CompletionHandler<Integer,Attachment> completionHandler = 
					new CompletionHandler<Integer,Attachment>(){
				public void completed(Integer result, Attachment attachment) {
					System.out.println(attachment.path.getFileName() + ":" + result + "bytes written : " + Thread.currentThread().getName()); //어떤 파일에 몇바이트를 출력을 했냐 
					try {
						attachment.fileChannel.close();
					} catch (IOException e) {} //첨부객체로부터 파일 채널을 얻어서 닫아준다. 
				}; //스레드가 작업을 성공적으로 완료하면 콜백이 된다. 
				
				public void failed(Throwable exc, Attachment attachment) {
					exc.printStackTrace();
					try {
						attachment.fileChannel.close(); //어찌됬든 닫아야 하니까 닫아준다. 
					} catch (IOException e) {}
				};     //스레드가 작업 도중 예외가 발생했을 경우 콜백이 된다. 
			};
			
			fileChannel.write(byteBuffer,0,attachment,completionHandler); //0은 파일의 첫번째 위치에서부터 저장이라는 뜻 
		}
		//스레드풀 종료
		executorService.shutdown();
		/*
		 	file1.txt:10bytes written : pool-1-thread-2
			file4.txt:10bytes written : pool-1-thread-1
			file2.txt:10bytes written : pool-1-thread-3
			file3.txt:10bytes written : pool-1-thread-4
			file5.txt:10bytes written : pool-1-thread-2
			file6.txt:10bytes written : pool-1-thread-1
			file7.txt:10bytes written : pool-1-thread-3
			file8.txt:10bytes written : pool-1-thread-2
			file9.txt:10bytes written : pool-1-thread-4 
		 
		 */
	}

}
package sec05.exam01_asynchronous_filechannel;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AsynchronousFileChannelReadExample {

	public static void main(String[] args) throws IOException {
		//스레드풀 생성
		ExecutorService executorService = Executors.newFixedThreadPool(
				Runtime.getRuntime().availableProcessors()
		);
		
		for(int i = 0;i<10;i++) {
			Path path = Paths.get("C:/Temp/file" + i + ".txt");
			
			//비동기 파일 채널 생성 
			AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
					path,
					EnumSet.of(StandardOpenOption.READ),
					executorService
			);
			
			ByteBuffer byteBuffer = ByteBuffer.allocate((int)fileChannel.size()); //path에 지정된 파일의 사이즈를 얻어낸다. 사이즈는 long타입을 리턴하므로 int롤 타입변환 해줌. 
			
			//첨부 객체 생성
			class Attachment{ //첨부 타입 선언 
				Path path;
				AsynchronousFileChannel fileChannel;
				ByteBuffer byteBuffer;
			}
			Attachment attachment = new Attachment();
			attachment.path = path;
			attachment.fileChannel = fileChannel;
			attachment.byteBuffer = byteBuffer;
			
			//CompletionHandler 객체 생성하기 
			CompletionHandler<Integer,Attachment> completionHandler = 
					new CompletionHandler<Integer,Attachment>(){
				public void completed(Integer result, Attachment attachment) {
					attachment.byteBuffer.flip(); //읽기 모드로 바꾼다. 
					
					Charset charset = Charset.defaultCharset();
					String data = charset.decode(attachment.byteBuffer).toString(); // charset.decode(attachment.byteBuffer)의 CharBuffer를 toString()으로 문자열로 만든다. 
					
					System.out.println(attachment.path.getFileName() + ":" + data + ":" + Thread.currentThread().getName());
					try {
						fileChannel.close();
					} catch (IOException e) {}
				};
				
				public void failed(Throwable exc, Attachment attachment) {
					exc.printStackTrace();
					try {
						fileChannel.close(); //예외가 발생했더라도 채널은 닫혀야 함. 
					} catch (IOException e) {}
				};
			};
			
			
			//파일 읽기
			fileChannel.read(byteBuffer, 0,attachment,completionHandler); //read()메소드는 호출 즉시 리턴이 된다. 실제 파일에서 읽는 작업은 스레드풀의 작업 스레드가 담당한다. 
																		//작업 스레드가 작업을 성공적으로 완료하면 completionHandler의 completed 콜백 메소드르 실행한다.  
		}
		
		//스레드풀 종료 
		executorService.shutdown();
	}

}

 

 

 

 

 

필기

package nio;

import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
 Tip:
스트림 방식 -> 데이터가 단방향이다. 
채널 방식 -> 하나의 채널로 데이터를 읽고 쓰기가 가능하다. 즉 양방향

nio는 버퍼가 기본이다. 무조건 버퍼에서 읽고 버퍼에서 쓴다. 
동기 방식-> 작업을 다 마쳐야 실행
비동기 -> 즉시 다른 작업을 할 수 있음. 
 
 
 
<NIO(New Input/Output)>
기존 java.io API와 다른 새로운 입출력 API를 말한다. 
자바4부터 처음 추가 -> 자바 7부터 네트워크 지원 강화된 NIO.2 API 추가 

<관련 패키지>									포함되어 있는 내용
java.nio									다양한 버퍼 클래스 
java.nio.channels							파일 채널, TCP채널, UDP채널 등의 클래스
java.nio.channels.spi						java.nio.channels 패키지를 위한 서비스 제공자 클래스 
java.nio.charset							문자셋,인코더, 디코어 API
java.nio.charset.spi						java.nio.charset 패키지를 위한 서비스 제공자 클래스 
java.nio.file								파일 및 파일 시스템에 접근하기 위한 클래스
java.nio.file.attribute						파일 및 파일 시스템의 속성에 접근하기 위한 클래스
java.nio.file.spi							java.nio.file 패키지를 위한 서비스 제공자 클래스 

<IO와 NIO의 차이점>

구분							IO					NIO
입출력 방식				스트림 방식				채널 방식
버퍼 방식					넌 버퍼(non-buffer)		버퍼(buffer)
동기/ 비동기 방식			동기 방식					동기 / 비동기 방식 모두 지원
블로킹/ 넌블로킹 방식		블로킹 방식				블로킹/ 넌블로킹 방식 모두 지원 


<스트림 vs 채널> 
IO스트림: 입력 스트림과 출력 스트림으로 구분되어 있기 때문에 별도로 생성
NIO 채널: 양방향으로 입력과 출력이 가능하므로 하나만 생성 

<넌버퍼 VS 버퍼>
IO스트림 - 넌버퍼(non-buffer)
-> IO에서는 1바이트씩 읽고 출력하기 때문에 느리다. 
보조 스트림인 BufferedInputStream, BufferedOutputStream을 사용해서 버퍼를 제공할 수 있다.\
-> 스트림으로부터 전체 데이터를 별도로 저장하지 않는다면, 
입력 데이터의 위치를 이동해가면서 자유롭게 이용할 수 없다. 

<NIO채널 - 버퍼(buffer)>
기본적으로 버퍼를 사용해서 입출력하기 때문에 입출력 성능이 좋다. 
읽은 데이터를 무조건 버퍼(buffer: 메모리 저장소)에 저장하기 때문에 
버퍼 내에서 데이터 위치를 이동해 가면서 필요한 부분만 읽고 쓸 수 있다. 


<블로킹 vs 넌블로킹>
IO 스트림 - 블로킹 
-> 입력 스트림의 read() 메소드를 호출하면 데이터가 입력되기 전까지 스레드가 블로킹(대기상태)가 된다.
출력 스트림의 write() 메소드를 호출하면 데이터가 출력되기 전까지 스레드는 블로킹이 된다.
**스레드가 블로킹되면 다른 일을 할 수가 없고, interrupt해서 블로킹을 빠져나올 수도 없다. 
블로킹을 빠져나오는 유일한 방법은 스트림을 닫는 것이다. 

NIO 채널 - 블로킹, 넌블로킹 
-> IO 블로킹과 차이점은 NIO 블로킹은 스레드를 interrupt 함으로써 빠져나올 수 있다. 
NIO는 넌블로킹을 지원하는데, 입출력 작업시 스레드가 블로킹되지 않는다. 


<IO와 NIO의 선택 >
IO선택
연결 클라이언트의 수가 적고,
 전송되는 데이터가 대용량이면서
 순차적으로 처리될 필요성이 있을 경우

NIO선택 
연결 클라이언트의 수가 많고, 
전송되는 데이터 용량이 적으면서, 
입출력 작업 처리가 빨리 끝나는 경우 
---------------------------------------------------------------------


<파일 관련 패키지>
IO는 파일의 속성 정보를 읽기 위해 File 클래스만 제공
NIO는 좀 더 다양한 파일의 속성 정보를 제공해주는 클래스와 인터페이스를 java.nio.file, java.nio.file.attribute 패키지에서 제공

<경로 정의(Path)>
java.nio.file.Path 인터페이스 
IO의 java.io.File 클래스에 대응
NIO의 여러 곳에서 파일의 경로를 지정하기 위해 Path 를 사용한다. 
-> Path 구현 객체는 java.nio.file.Paths 클래스의 정적 메소드인 get()메소드로 얻는다. 
Path path = Paths.get("../dir2/file.txt"); <--  ../  <- 상위 디렉토리로 가서 밑으로 내려가라라는 뜻.


<파일 시스템 정보 (FileSystem)>
운영체제의 파일 시스템은 FileSystem 인터페이스를 통해서 접근한다. 
FileSystem 구현 객체는 FileSystems의 정적 메소드인 getDefault()로 얻을 수 있음.
FileSystem fileSystem = FileSystems.getDefault();

<FileSystem에서 제공하는 메소드 >
리턴타입						메소드					설명
Iterable<FileStore>			getFileStores()			드라이버 정보를 가진 FileStore객체들을 리턴
Iterable<Path>				getRootDirectories()	루트 디렉토리 정보를 가진 Path 객체들을 리턴 
String						getSeparator()			디렉토리 구분자 리턴 



<파일 및 디렉토리 생성/ 삭제 및 속성 읽기(Files)>
java.nio.file.Files 클래스
파일과 디렉토리의 생성 및 삭제, 그리고 이들의 속성을 읽는 메소드를 제공 
-> 속성: 숨김, 디렉토리 여부, 크기, 소유자... 

<제공 메소드 >
copy(...) -> Path or long 리턴/ 복사
createDirectories(...) -> Path / 모든 부모 디렉토리도 생성 
createDirectory(...) -> Path / 경로의 마지막 디렉토리만 생성
createFile(...) -> Path / 파일 생성
delete(...) -> void / 삭제    -> 존재하지 않는데 삭제하면 예외 발생. 
deleteIfExists(...) -> boolean / 존재한다면 삭제
exists(...) -> boolean / 존재 여부
getFileStore(...) -> FileStore / 속한 FileStore(드라이브) 리턴 
getLastModifiedTime(...) -> FileTime / 마지막 수정 시간을 리턴
getOwner(...) -> UserPrincipal / 소유자 정보를 리턴한다. 
isDirectory(...) -> boolean / 디렉토리인지 여부 
isExecutable(...) -> boolean / 실행 가능 여부
isHidden(...) -> boolean / 숨김 여부 
isReadable(...) -> boolean / 읽기 가능 여부
isRegularFile(...) -> boolean / 일반 파일인지 여부 
isSameFile(...) -> boolean / 같은 파일인지 여부
isWritable(...) -> boolean / 쓰기 가능 여부
move(...) -> Path / 파일 이동
newBufferedReader(...) -> BufferedReader / 텍스트 파일을 읽는 BufferedReader 리턴
newBufferedWriter(...) -> BufferedWriter/ 텍스트 파일에 쓰는 BufferedWriter 리턴
newByteChannel(...) -> SeekableByteChannel // 파일에 읽고 쓰는 바이트 채널을 리턴
newDirectoryStream(...) -> DirectoryStream<Path> / 디렉토리의 모든 내용을 리턴
newInputStream(...) -> InputStream / 파일의 InputStream 리턴
newOutputStream(...) -> OutputStream / 파일의 OutputStream 리턴
notExists(...) -> boolean / 존재하지 않는지 여부 
probeContentType(...) -> String / 파일의 MIME 타입을 리턴
readAllBytes(...) -> byte[] / 파일의 모든 바이트를 읽고 리턴 
readAllLines(...) -> List<String> / 텍스트 파일의 모든 라인을 읽고 리턴
size(...) -> long / 파일의 크기 
write(...) -> Path / 파일에 바이트나 문자열을 저장한다.  

----------------------------------------------------------------------------


<와치 서비스 (WatchService)>
디렉토리의 내용 변화를 감시
자바 7에서 처음 소개된 것으로 디렉토리 내의 파일의 생성, 삭제 수정을 감시한다. 
적용사례: 에디터 바깥에서 파일 내용을 수정하게 되면 
파일 내용이 변경됐으니 파일을 다시 불러올 것인지 묻는 대화상자를 띄운다. 

<WatchService 생성>
WatchService watchService = FileSystems.getDefault().newWatchService();

감시가 필요한 디렉토리에서 WatchService 등록
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
							StandardWatchEventKinds.ENTRY_MODIFY,
							StandardWatchEventKinds.ENTRY_DELETE);

어떤 내용(생성,삭제,수정) 을 감시할지를 StandardWatchEventKinds 상수로 지정 


디렉토리 내부에서 변경 발생할 경우
WatchEvent 발생
WatchService 는 해당 이벤트 정보를 가진 와치키 (WatchKey) 를 생성
WatchService 는 와치키를 큐에 저장한다. 

 이벤트 처리 코드 작성 방법
 WatchService의 take()메소드를 호출
 WatchKey가 큐에 들어올 때까지 대기, WatchKey가 큐에 들어오면 리턴한다. 
 
 WatchKey로부터 List<WatchEvent>획득
 
 여러 개의 WatchEvent가 발생하는 이유
 -> 여러 개의 파일이 동시에 삭제, 수정, 생성이 될 수 있기 때문
 WatchEvent는 파일당 하나씩 발생 
 
 
 <OVERFLOW 이벤트>
 운영체제에서 이벤트가 소실됐거나 버려진 경우에 발생하므로 별도의 처리코드가 필요없다. 
 
 한번 사용된 WatchKey는 reset()메소드로 초기화해야 한다. 
 -> 새로운 WatchEvent가 발생하면 큐에 다시 들어가기 때문
 초기화에 성공되면 true 리턴
 감시하는 디렉토리가 삭제되었거나 키가 더이상 유효하지 않을 경우 false 리턴한다. 
 
 WatchKey가 더이상 유효하지 않게 되면 reset()이 false를 리턴할 경우
-> 무한 루프를 빠져나와 WatchService의 close()메소드를 호출하고 종료한다. 



--------------------------------------------------------------

<버퍼 >
버퍼는 읽고 쓰기 가능한 메모리 배열
NIO에서는 데이터를 입출력을 하기 위해서는 항상 버퍼를 사용한다. 

버퍼 종류
분류 기준 
저장되는 데이터 타입에 따라 분류 
ByteBuffer, CharBuffer, IntBuffer, DoubleBuffer 

어떤 메모리를 사용하느냐에 따른 분류
다이렉트 버퍼 -> 운영체제가 관리하는 메모리를 이용 
넌 다이렉트 버퍼 -> JVM이 관리하는 메모리를 이용 

데이터 타입에 따른 버퍼
NIO 버퍼는 저장되는 데이터 타입에 따라서 별도의 클래스로 제공
이들 버퍼 클래스들은 Buffer 추상 클래스를 모두 상속


MappedByteBuffer는 파일의 내용에 램덤하게 접근하기 위해서 파일의 내용을
메모리와 맵핑시킨 버퍼이다. 

넌다이렉트와 다이렉트 버퍼

<넌다이렉트 버퍼 >
-> JVM이 관리하는 힙 메모리 공간을 이용하는 버퍼
버퍼 생성 시간이 빠름.
버퍼의 크기를 크게 잡을 수가 없음.
입출력을 하기 위해 임시 다이렉트 버퍼를 생성하고, 넌 다이렉트 버퍼에 있는 내용을 임시 다이렉트 버퍼에 복사한다. 
그리고 나서 임시 다이렉트 버퍼를 사용해서 운영체제의 native I/O 기능을 수행한다. 그렇기 때문에 직접
다이렉트 버퍼를 사용하는 것보다는 입출력 성능이 낮다. 

<다이렉트 버퍼>
JVM이 관리하지 않고, 운영체제가 관리하는 메모리 공간을 이용하는 버퍼
운영체제의 native C 함수를 호출해야 하고, 잡다한 처리를 해야하므로 버퍼 생성이 상대적으로 느림.
자주 생성하기 보다는 한 번 생성해 놓고 재사용하는 곳에서 적합하다. 
운영체제가 허용하는 범위내에서 대용량 버퍼를 생성시킬 수 있다. 

구분							넌다이렉트 버퍼 						다이렉트 버퍼
사용하는 메모리 공간				JVM의 힙 메모리						운영체제의 메모리
버퍼 생성 시간					버퍼 생성이 빠르다. 					버퍼 생성이 느리다. 
버퍼의 크기					작다. 								크다. (큰 데이터를 처리할 때 유리)
입출력 성능					낮다.								높다. (입출력이 빈번할 경우 유리)


<Buffer 생성>
allocate() 메소드 (넌 다이렉트 버퍼 생성)
각 데이터 타입별 넌다이렉트 버퍼를 생성한다.
매개값은 해당 데이터 타입의 저장 개수를 말한다. 

wrap()메소드 (넌 다이렉트 버퍼 생성)
이미 생성되어 있는 타입별 배열을 래핑해서 버퍼를 생성한다. 

allocateDirect()메소드 (다이렉트 버퍼 생성)
JVM 힙 메모리 바깥쪽 즉 운영체제가 관리하는 메모리에 다이렉트 버퍼를 생성한다. 
각 타입별 Buffer 클래스에는 없고, ByteBuffer 에서만 제공된다. 

asXXXBuffer() 메소드 (다이렉트 버퍼 생성)
각 타입별 다이렉트 버퍼를 생성(asCharBuffer(), asIntBuffer().....~~)
우선 다이렉트 ByteBuffer를 생성하고 호출 
예시:   
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100); //100개의 byte값 저장 
CharBuffer charBuffer = ByteBuffer.allocateDirect(100).asCharBuffer();  // 50개의 char 값 저장 
int 의 경우에는 25개의 int 값 저장
char는 2바이트 크기를 가지고 , int는 4바이트 크기를 가지기 때문에 초기 다이렉트 ByteBuffer 생성 크기에 따라 저장 용량이 결정된다. 

새로운 내용 ! 
성능끌어올리기!! 

byte 해석 순서(ByteOrder)
운영체제는 두 바이트 이상을 처리할 때 처리 효율이나 CPU 디자인 상의 문제로
바이트 해석 순서가 정해져 있다. 

바이트 해석 순서는 데이터를 외부로 보내거나 외부에서 받을 때 영향을 미치기 때문에 
바이트 데이터를 다루는 버퍼도 이를 고려해야 한다. 

Big endian: 앞 바이트부터 먼저 처리
Little endian: 뒤 바이트부터 먼저 처리 

운영체제가 사용하는 바이트 해석 순서 확인 방법
System.out.println("네이티브의 바이트 해석 순서: "+ByteOrder.nativeOrder());

JVM은 동일한 조건으로 클래스를 실행해야 하므로 무조건 Big endian 으로 동작하게끔 되어 있다. 
Little endian 으로 동작하는 운영체제에서 만든 데이터 파일을 
Big endian로 동작하는 운영체제에서 읽어 들여야 한다면 ByteOrder 클래스로 데이터 순서를 맞춰야 한다. 

 운영체제와 JVM의 바이트 해석 순서가 다를 경우 JVM이 운영체제와 데이터를 교환을 할 때 자동적으로 처리한다. 
 
 다만!! 다이렉트 버퍼를 이용할 경우 운영체제의 native I/O를 사용하므로 운영체제의 기본 해석 순서로 
 JVM의 해석 순서를 맞추는 것이 성능에 도움이 된다. 
 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100).order(ByteOrder.nativeOrder());
 // order메소드를 통해 JVM과 운영체제의 해석 순서를 맞추게 된다. 이걸 가지고 입출력에 사용하면 좀더 성능에 도움이 된다. 
-------------------------------------------------------------------------------------------------
Buffer의 위치 속성(position, limit, capacity, mark)
Buffer를 사용전에 위치 속성의 개념과 속성이 언제 변경되는지 알고 있어야 한다. 

position:
현재 읽거나 쓰는 위치값이다. 인덱스 값이기 때문에 0부터 시작하며, 음수를 가지거나
limit 보다 큰값을 가질 수 없다. 만약 position과 limit 의 값이 같아진다면
더이상 데이터를 쓰거나 읽을 수 없다는 뜻이 된다. 

limit:
버퍼에서 읽거나 쓸 수 있는 위치의 한계를 나타낸다. 이 값은 capacity 보다 작거나
같은 값을 가지며 음수 값은 가지지 않는다. 인덱스 값이기 때문에 0부터 시작하며
최초 버퍼를 만들었을때는 capacity 와 같은 값을 가진다. 

capacity: 
버퍼의 최대 데이터 개수(메모리 크기)를 나타낸다. 크기는 음수가 되지 않으며
한번 만들어지면 절대 변하지 않는다. 인덱스 값이 아니라 수량임을 주의하자. 

mark: 
reset() 메소드를 실행했을 때에 돌아오는 위치를 지정하는 인덱스로써 mark() 메소드로 지정할 수 있다.
주의할 점은 반드시 position 이하의 값으로 지정해주어야 한다. position 나 limit 의 값이 mark 값보다 작은 경우
mark 는 자동 제거된다. mark 없는 상태에서 reset() 메소드르 호출하면 InvalidMarkException 이 발생한다. 


position,limit,capacity,mark 속성의 크기 관계 
0 <= mark <= position <= limit <= capacity
--------------------------------------------------------------------- 

공통 메소드: Buffer 추상 클래스에 정의된 메소드 -> 사진으로 제공함. 
flip() <- 이건 아주 자주 사용함. 

<데이터를 읽고 저장하는 메소드>
데이터 읽기: get(...)
데이터 저장: put(...)
Buffer 추상클래스에는 없고, 각 타입별 하위 Buffer 클래스가 가지고 있다. 

상대적 메소드 : 현재 위치 속성인 position 에서 데이터를 읽고 저장한다. 
상대적 get() 과 put() 메소드를 호출하면 position의 값은 증가된다. 
position 값이 limit 값 까지 증가했을 경우에 -->
상대적 get()을 사용하면 BufferUnderflowException 예외가 발생
상대적 put() 메소드를 사용하면, BufferOverflowException 예외가 발생한다. 

절대적 메소드 : position과 상관없이 주어진 인덱스에서 데이터를 읽고 저장한다. 
절대적 get()과 put()메소드를 호출하면 position의 값은 증가되지 않는다. 

상대적과 절대적 메소드 구분 방법
상대적 메소드: index 매개값이 없는 메소드 
절대적 메소드: index 매개값이 있는 메소드 


버퍼 예외의 종류
버퍼 예외가 발생하는 주요 원인 
버퍼가 다 찼을 때, 데이터를 저장하려는 경우
버퍼에서 더 이상 읽어올 데이터가 없을때 데이터를 읽을려는 경우 

예외							설명 
BufferOverflowException:    position이 limit에 도달했을 때 put() 호출하면 발생
BufferUnderflowException:	position이 limit에 도달했을 때 get() 호출하면 발생
InvalidMarkException:		mark가 없는 상태에서 reset()메소드를 호출하면 발생 
ReadOnlyBufferException: 	읽기 전용 버퍼에서 put() 또는 compact() 메소드를 호출하면 발생 


<Buffer 변환>
채널이 데이터를 저장하고 읽는 버퍼는 모두 ByteBuffer 이다. 
프로그램 목적에 맞게 ByteBuffer를 다른 기본 타입 버퍼로 변환 필요

경우1: ByteBuffer에 특정 문자셋으로 인코딩된 바이트들이 저장되어 있을 경우
-> 디코딩된 CharBuffer 롤 변환후 문자열을 얻어야 한다. 

경우2: ByteBuffer 에 정수 바이트들이 저장되어 있을 경우
-> IntBuffer로 변환해서 읽어야 한다. 

경우3: CharBuffer와 IntBuffer 의 내용을 채널로 출력할 경우
-> ByteBuffer 로 변환해야 한다. 


-------------------------------------------------------------------
채널은 버퍼를 이용해서 데이터를 읽고 쓰기를 한다. 
<파일 채널>FileChannel
FileChannel 을 이용하면 파일 읽기와 쓰기를 할 수 있다. 
동기화 처리가 되어 있기 때문에 멀티 쓰레드 환경에서 사용해도 안전하다. 


<FileChannel 생성과 닫기 >
FileChannel 의 정적 메소드인 open()을 호출  -> 이 방법이 조금 더 일반적이다. 
FileInputStream, FileOutputStream의 getChannel()메소드를 호출 -> 이경우는 이미 만들어놓은 fileinputouput이 있을 경우에 사용하는건데
사실 일반적인 방법은 아니다. 


FileChannel.open()
FileChannel fileChannel = FileChannel.open(Path path, OpenOption ... options); <- 여기에 options는 계속 추가할 수 있다. 

첫번째 path 매개값은 열거나, 생성하고자 하는 파일의 경로
두번째 options 매개값은 열기 옵션 값인데, StandardOpenOption 의 열거 상수 

열거 상수						설명
READ						읽기용으로 파일을 연다. 
WRITE						쓰기용으로 파일을 연다. 
CREATE						파일이 없다면 새 파일을 생성한다. 
CREATE_NEW					새 파일을 만든다. 파일이 이미 있으면 예외와 함께 실패한다. 
APPEND						파일 끝에 데이터를 추가한다. (WRITE나 CREATE와 함께 사용됨)
DELETE_ON_CLOSE				채널을 닫을 때 파일을 삭제한다. (임시파일을 삭제할 때 사용)
TRUNCATE_EXISTING			파일을 0바이트로 잘라낸다. (WRITE 옵션과 함께 사용됨.)

채널 닫기: FileChannel 을 더 이상 사용하지 않을 경우
fileChannel.close();

 
파일 쓰기와 읽기
파일 쓰기:
int bytesCount = fileChannel.write(ByteBuffer src);
파일에 쓰여지는 바이트는 ByteBuffer의 position부터 limit 까지이다. 

파일 읽기:
int bytesCount = fileChannel.read(ByteBuffer dst);
파일에서 읽혀지는 바이트는 ByteBuffer의 position부터 저장된다.
-> 버퍼에 한 바이트를 저장할 때마다 position이 1씩 증가
-> 버퍼에 저장한 마지막 바이트의 위치는 position-1 인덱스까지 이다. 
-> 한번 읽을 수 있는 최대 바이트수는 position부터 ByteBuffer의 capacity 까지이다. 

리턴값은 파일에서 ByteBuffer로 읽혀진 바이트 수이다. 
0~ (position-1) 까지의 바이트 수 
더이상 읽을 바이트가 없다면 read() 메소드는 -1을 리턴 
  

<파일 복사>

두개의 FileChannel을 이용 하는 방법이 있고,  방법1: 

<Files.copy() 메소드 이용> 를 이용하는 방법이 있다  (단순히 파일만 복사할 경우 이걸 사용한다. )방법 2: 
Path targetPath = Files.copy(Path source, Path targer, CopyOption.... options);
젓번째 source 매개값에는 원본 파일의 Path 객체를 지정
두 번째 target 매개값에는 타겟 파일의 Path 객체를 지정 
세번째 매개값은 StandardCopyOption 열거 상수를 목적에 맞게 나열 

열거 상수 				설명 
REPLACE_EXISTING		타겟 파일이 존재하면 대체한다. 
COPY_ATTRIBUTES			파일의 속성까지도 복사한다. 
NOFOLLOW_LINKS 			링크 파일일 경우 링크 파일만 복사하고 링크된 파일은 복사안한다. 

-------------------------------------------------------------------------------------------
<파일 비동기 채널>
FileChannel의 단점
read()와 write()메소드는 블로킹이 된다.
-> 블로킹 동안에 UI갱신이나 이벤트 처리를 할 수 없다. 
따라서 별도의 작업 스레드를 생성해서 이들 메소드를 호출해야 한다.
동시에 처리해야할 파일 수가 많다면 스레드 수 증가로 문제가 될 수 있다. 

 AsynchronousFileChannel -> 비동기 파일 채널 
 read()와 write()메소드는 즉시 리턴된다. 
 -> 이들 메소드는 스레드풀에게 작업 처리를 요청하고 즉시 리턴된다. 
 작업 스레드가 파일 입출력을 완료하게 되면 콜백(callback) 메소드가 자동 호출된다. -> 그래서 작업 완료후에 다른 작업을 연이어서 하고 싶다면 콜백 메소드에 그 내용을 작성하면 된다. 
 불특정 다수의 파일 및 대용량 파일의 입출력 작업시 유리하다. 
-->read()와 write()가 그 즉시 리턴된다... 라고 받아들이면 편함. 


AsynchronousFileChannel 생성과 닫기
ExecutorService executorService = Executors.newFixedThreadPool(
				Runtime.getRuntime().availableProcessors()
		);
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
				Paths.get("C:/Temp/file.txt"),                        
				EnumSet.of(StandardOpenOption.CREATE,StandardOpenOption.WRITE), //열기 옵션들을 셋컬렉션에 담아서 전달해준다.EnumSet에 대해서는 좀더 알아보기  
				executorService                                                 //스레드풀 제공 
		);

닫기
fileChannel.close(); 


파일 읽기와 쓰기 
void read(ByteBuffer dst, long position,
	A attachment, CompletionHandler<Integer,A> handler) // A는 제너릭 타입이다. CompletionHandler의 두번째 타입 파라미터에 의해서 결정된다. 4번째 배개변수 안에는 콜벡메소드가 작성된다. 
														
void write(ByteBuffer src, long position,
	A attachment, CompletionHandler<Integer,A> handler)


매개변수
dst,src: 읽거나 쓰기 위한 ByteBuffer  
position: 파일에서 읽을 위치이거나 쓸 위치 
attachment: 콜백 메소드로 전달할 첨부 객체 
handler:	CompletionHandler<Integer,A> 구현 객체 

CompletionHandler<Integer,A>
Integer: 입출력 작업 처리후의 결과 타입(결과값은 읽거나 쓴 바이트 수 )(고정) 
A: 첨부 객체 타입으로 첨부 객체가 필요없다면 Void를 지정(개발자 지정) <- Void는 V로 대문자인것을 명심하기 


<CompletionHandler<Integer,A> 구현 객체 생성>
리턴타입 			메소드명(매개변수)						설명
void completed(Integer result, A attachment) 		작업이 정상적으로 완료된 경우 콜백 
void failed(Throwable exc, A attachment) 			예외 때문에 작업이 실패된 경우 콜백 

<매개변수>
result: 입출력 작업 처리후 읽거난 쓴 바이트 수 
A: read(), write() 호출 시 두번째 매개값으로 대입한 첨부 객체 타입 
exc: 입출력 작업 도중 발생한 예외 객체 

new CompletionHandler<Integer,A>(){
	@Override
	public void completed(Integer result, A attachment){...}
	@Override
	public void failed(Throwable exc, A attachment) {...}
}

와 같이 작성한다. 









*/
반응형