NIO2란?
· New Input Output2의 약자로 자바 1.4에 등장한 NIO를 개선
· java.io 패키지의 File 클래스에 미흡한 부분을 보완하는 내용이 다수 포함, 네트워킹 I/O에 마이너한 변화 포함
- 이전의 자바에서 다루지 않은 파일 속성들을 다루는 기능 추가
- 심볼릭 링크 처리 기능 추가 (https://ko.wikipedia.org/wiki/심볼릭_링크)
- WatchService 인터페이스 추가 (어떤 파일이 변경되었는지 쉽게 확인 가능)
- 여러 채널 추가
· NIO2 이전(Java 6)까지 사용된 File 클래스의 단점
- 심볼릭 링크, 속성, 파일의 권한 등에 대한 기능이 없음
- 파일을 삭제하는 delete() 메소드는 실패시 아무런 예외 발생 x, boolean 타입의 결과만 제공
- 클래스 이름만 File이고, 실제로는 경로에 대한 정보를 담을 수 있어 사용할 때 혼란 유발
- 파일 변경을 확인하는 방법이 부족함.
lastModified 메소드를 제공하여 long 타입의 결과로 이전 시간과 비교하는 제한적인 방법만 제공,
해당 메소드 호출시 연계되어 호출되는 클래스가 다수 존재하며 성능상 문제가 많음
NIO2에서 File 클래스를 대체하는 클래스
클래스 | 설명 |
Paths | · 클래스에서 제공하는 static한 get() 메서드를 사용하면 Path 인터페이스 객체를 얻을 수 있음 · Path 인터페이스: 파일과 경로에 대한 정보를 가짐 |
Files | · 기존 File 클래스에서 제공되던 클래스의 단점을 보완한 클래스 · 매우 많은 메소드를 제공 · Path 객체를 사용하여 파일을 통제하는데 사용 |
FileSystems | · 현재 사용중인 파일 시스템에 대한 정보를 처리하는 데 필요한 메서드를 제공 · 클래스에서 제공되는 static한 getDefault() 메서드를 사용하여 현재 사용중인 기본 파일 시스템에 대한 정보를 갖고 있는 FileSystem 인터페이스 객체를 얻을 수 있음 |
FileStore | · 파일을 저장하는 디바이스, 파티션, 볼륨 등에 대한 정보를 확인하는 데 필요한 메서드 제공 |
· 위 클래스는 새로 추가된 java.nio.file 패키지에 위치
· Paths 클래스의 get() 메서드를 통해 Path 객체를 얻음
리턴 타입 | 메서드 |
static Path | get(String first, String... more) |
static Path | get(URI uri) |
· 디렉터리의 경로를 문자열로 지정 또는 URI 정보를 통해 Path 객체를 얻음
· java.io 패키지 File 클래스의 toPath() 메소드로 Path 객체를 얻을 수 있음 (Java 7에 추가)
Path 인터페이스에 정의된 메서드
리턴 타입 | 메서드(매개변수) | 설명 |
int | compareTo(Path other) | 파일 경로가 동일하면 0을 리턴, 상위 경로면 음수, 하위 경로면 양수 리턴, 음스와 양수 값의 차이나는 문자열 수 |
Path | getFileName() | 부모 경로를 제외한 파일 또는 디렉토리 이름만 가진 Path 리턴 |
FileSystem | getFileSystem() | FileSystem 객체 리턴 |
Path | getName(int index) | C:\Temp\dir\file.txt 일 경우 index가 0이면 "Temp"의 Path 객체 리턴 index가 1이면 "dir"의 Path 객체 리턴 index가 2이면 "file.txt"의 Path 객체 리턴 |
int | getNameCount() | 중첩 경로 수, C:\Temp\dir\file.txt일 경우 3을 리턴 |
Path | getParent() | 바로 위 부모 폴더의 Path 리턴 |
Path | getRoot() | 루트 디렉토리의 Path 리턴 |
Path | relativeze() | 매개 변수로 넘긴 Path와 현재 Path의 상대 경로 리턴 |
Path | toAbsolutePath() | 상대 경로를 절대 경로로 견경 |
Path | resolve() | 매개 변수로 넘어온 문자열을 하나의 경로로 생각하고, 현재 Path의 가장 마지막 path로 추가 |
Iterator<Path> | iterator() | 경로에 있는 모든 디렉토리와 파일을 Path 객체로 생성 후 반복자 리턴 |
Path | normalize() | 상대 경로로 표기할 때 불필요한 요소 제거 |
WatchKey | register(...) | WatchService를 등록 |
File | toFile() | java.io.file 객체로 리턴 |
String | toString() | 파일 경로를 문자열로 리턴 |
URI | tiUri() | 파일 경로를 URI 객체로 리턴 |
▶ 예시 - Path 객체 메서드 사용
import java.nio.file.Path;
import java.nio.file.Paths;
import static java.io.File.separator;
public class PathsAndFiles {
public static void main(String args[]){
String dir = separator + "Users" + separator + "ted.sc" + separator + "Desktop" + separator + "git" +
separator + "javaStudy" + separator + "src" +separator + "nio2";
String dir2 = separator + "Users" + separator + "ted.sc";
checkPath(dir,dir2);
}
public static void checkPath(String dir1, String dir2){
Path path = Paths.get(dir1);
Path path2 = Paths.get(dir2);
Path relatived = path.relativize(path2);
System.out.println("relatived path="+relatived);
Path absolute = relatived.toAbsolutePath();
System.out.println("toAbsolutePath path="+absolute);
Path nomalized = absolute.normalize();
System.out.println("nopmalized path="+nomalized);
Path resolved = path.resolve("Users");
System.out.println("resolved path="+resolved);
}
}
Files 클래스
기능 | 관련 메서드 |
복사 및 이동 | copy(), move() |
파일, 디렉터리 등 생성 | createDirectories(), createDirectory(), createFile(), createLink(), createSymbolicLink(), createTempDirectory(), createTempFile() |
삭제 | delete(), deleteIfExists() |
읽기와 쓰기 | readAllBytes(), readAllLines(), readAttributes(), readSymbolicLink(), write() |
Stream 및 객체 생성 | newBufferedReader(), newBufferedWriter(),newByteChannel(),new(),new(),new() |
특정 경로의 하위 파일 및 폴더를 탐색 | walk() |
각종 확인 | get으로 시작하는 메서드와 is로 시작하는 메서드들로 파일의 상태 확인 (매우 많음) |
▶ 예시 - 파일 쓰기
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.ArrayList;
import java.util.Date;
import java.util.List;
import static java.io.File.separator;
public class FileManager {
public static void main(String[] args) throws Exception {
String file = separator + "Users" + separator + "ted.sc" + separator + "Desktop" + separator + "git" +
separator + "javaStudy" + separator + "src" +separator + "nio2" + separator + "file.txt";
writeFile(Paths.get(file));
}
public static List<String> getContents(){
List<String> contents = new ArrayList<>();
contents.add("첫째 줄");
contents.add("둘째 줄");
contents.add("셋째 줄");
contents.add("Current Date="+new Date());
return contents;
}
public static Path writeFile(Path path) throws Exception{
Charset charset = Charset.forName("EUC_KR");
List<String> contents = getContents();
StandardOpenOption openOption = StandardOpenOption.CREATE;
return Files.write(path, contents, charset, openOption);
}
}
· Charset 객체: 저장되는 파일의 문자열 캐릭터 셋 지정
· StandardOpenOption: 파일을 열 때의 조건, Enum 클래스
· write() 메소드 시그니처는 두 가지
public static Path write(Path path, byte[] bytes, OpenOption ... options)
public static Path write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption ... options))
· walk() 메서드를 통해 특정 경로의 하위 파일 및 폴더를 탐색 https://codechacha.com/ko/java-traverse-directory/
StandardOpenOption에 선언된 항목
항목 | 내용 |
APPEND | 쓰기 권한으로 파일을 열고, 기존에 존재하는 데이터가 있으면 가장 끝부붙부터 데이터를 저장 |
CREATE | 파일이 존재하지 않으면 새로 생성할 때 사용 |
CREATE_NEW | 파일을 생성하며, 기존 파일이 있으면 실패로 간주 |
DELETE_ON_CLOSE | 파일을 닫을 떄 삭제 |
DSYNC | 파일을 수정하는 모든 작업이 동기적(순차적)으로 파일 저장소에서 처리되도록 함 |
READ | 읽기 권한으로 파일을 열 때 사용 |
SPARSE | Sparse file. 파일을 Sparse 할 때 사용 (https://meetup.toast.com/posts/37) |
SYNC | 파일의 내용 및 메타 데이터에 대한 모든 업데이트는 순차적으로 파일 저장소에서 처리되도록 할 때 사용 |
TRUNCATE_EXISTING | 이미 존재하는 파일이 있을 때 쓰기 권한으로 파일을 열고 해당 파일에 있는 모든 내용을 지울 때 사용 |
WRITE | 쓰기 권한으로 파일을 열 때 사용 |
▶ 예시 - 파일 읽기
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.ArrayList;
import java.util.Date;
import java.util.List;
import static java.io.File.separator;
public class FileManager {
public static void main(String[] args) throws Exception {
String file = separator + "Users" + separator + "ted.sc" + separator + "Desktop" + separator + "git" +
separator + "javaStudy" + separator + "src" +separator + "nio2" + separator + "file.txt";
readFile(Paths.get(file));
}
public static void readFile(Path path) throws Exception{
Charset charset=Charset.forName("EUC_KR");
System.out.println("Path="+path);
List<String> fileContents = Files.readAllLines(path, charset);
for(String tempContents:fileContents)
System.out.println(tempContents);
System.out.println();
}
}
· Files 클래스에 선언된 메소드 중 파일을 읽는 데 사용하는 메서드는 두 가지 (readAllBytes, readAllLines)
public static byte[] readAllBytes(Path path)
public static List<String> readAllLines(Path path, Charset cs)
· 위 메서드로 몇백 Mega Bytes 이상의 파일을 읽으면 OutOfMemoryError 예외가 발생하므로,
파일의 용량이 작은 경우에만 사용할 것
▶ 예시 - 파일 복사, 이동, 삭제
public class FileManager {
public static void main(String[] args) throws Exception {
String file = separator + "Users" + separator + "ted.sc" + separator + "Desktop" + separator + "git" +
separator + "javaStudy" + separator + "src" +separator + "nio2" + separator + "file.txt";
Path fromPath = writeFile(Paths.get(file));
readFile(fromPath);
copyMoveDelete(fromPath, file);
}
public static void copyMoveDelete(Path fromPath, String fileName){
try{
Path toPath = fromPath.toAbsolutePath().getParent();
//디렉토리가 없다면 생성
Path copyPath = toPath.resolve("copied");
if(!Files.exists(copyPath)) Files.createDirectories(copyPath);
//파일 복사
Path copiedFilePath = copyPath.resolve(fileName);
StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
//복사된 파일 읽기
System.out.println("***** Copied file contents *****");
readFile(copiedFilePath);
//파일 이동
Path movedFilePath = Files.move(copiedFilePath, copyPath.resolve("moved.txt"),copyOption);
//파일 삭제
Files.delete(movedFilePath);
Files.delete(copyPath);
}catch (Exception e){
e.printStackTrace();
}
}
}
· 기존 자바 API를 사용하면 파일 복사와 이동이 어려웠고,
삭제가 되지 않아도 예외 발생이 아닌 boolean 타입 값만 리턴하는 문제가 있었음
· NIO2에서는 Files 클래스에서 제공되는 다양한 기능으로 이러한 문제를 해결함
· StandardCopyOption: 복사할 떄의 옵션 지정, Enum 클래스
StandardCopyOption에 선언된 항목
항목 | 내용 |
ATOMIC_MOVE | · 시스템 처리를 통하여 단일 파일을 이동 · 복사할 때에는 사용 불가 |
COPY_ATTRIBUTES | · 새로운 파일에 속성 정보 복사 |
REPLACE_EXISTING | · 기존 파일이 있으면 새 파일로 변경 |
WatchService 인터페이스
· 어떤 파일이 변경되었는지 확인하는 기능 제공
▶ 예시 - 자바7 이전 파일 변경을 확인하는 방법
long lastModified = -1;
public boolean fileChangeCheck(String fileName){
boolean result = false;
File file = new File(fileName);
long modifiedTime = file.lastModified();
if(lastModified==-1){
lastModified=modifiedTime;
}else{
if(modifiedTime!=lastModified)
result=true;
}
return result;
}
· File 클래스에서 제공하는 lastModified() 메서드를 사용하여 최근에 변경된 파일의 시간을 가져와서 기존에 저장된 시간과 비교하는 방법
· 단점
1. lastModified() 메소드를 주기적으로 호출해야함
2. 메소드 호출시 내부적으로 호출되는 연계된 메서드가 많아 성능에 영향을 줌
▶ 예시 - WatchService로 파일 변경을 확인하는 방법
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.util.List;
import static java.io.File.separator;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
public class WatcherSample extends Thread{
String dirName;
public static void main(String args[]) throws InterruptedException {
String dirName = separator + "Users" + separator + "ted.sc" + separator + "Desktop" + separator + "git" +
separator + "javaStudy" + separator + "src" +separator + "nio2";
String fileName = "file.txt";
WatcherSample sample = new WatcherSample(dirName);
sample.setDaemon(true);
sample.start();
Thread.sleep(1000);
for(int loop=0; loop<10; loop++){
sample.fileWriteDelete(dirName, fileName+loop);
}
}
public WatcherSample(String dirName) {
this.dirName = dirName;
}
public void run(){
System.out.println("### Watcher thread is started ### ");
System.out.format("Dir=%s\n", dirName);
addWatcher();
}
public void addWatcher(){
try {
Path dir = Paths.get(dirName);
WatchService watcher = FileSystems.getDefault().newWatchService();
WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
while (true){
key = watcher.take();
List<WatchEvent<?>> eventList = key.pollEvents();
for(WatchEvent<?> event: eventList){
Path name = (Path) event.context();
if(event.kind()==ENTRY_CREATE) System.out.format("%s created%n",name);
else if(event.kind()==ENTRY_DELETE) System.out.format("%s deleted%n",name);
else if(event.kind()==ENTRY_MODIFY) System.out.format("%s modified%n",name);
}
key.reset();
}
}catch (IOException|InterruptedException e){
e.printStackTrace();
}
}
private void fileWriteDelete(String dirName, String fileName){
Path path = Paths.get(dirName, fileName);
String contents = "Watcher sample";
StandardOpenOption openOption = StandardOpenOption.CREATE;
try{
System.out.println("Write "+fileName);
Files.write(path, contents.getBytes(), openOption);
Files.delete(path);
Thread.sleep(100);
}catch (IOException|InterruptedException e){
e.printStackTrace();
}
}
· FileSystems.getDefault()를 호출하여 기본 파일 시스템 객체를 얻고,
해당 객체의 newWatchService() 메서드를 호출하여 WatchService 객체 얻기
· Path 클래스의 register() 메서드로 어떤 감시를 할지 지정
리턴값 WatchKey는 WatchServic와 관련된 작업을 수행했을 때 받는 타입
· register() 메서드는 두 가지 메서드 시그니처가 존재
public WatchKey register(WatchService watcher, WatchEvent.kind<?>... events)
public WatchKey register(WatchService watcher, WatchEvent.kind<?>[] events, WatchEvent.Modifier ... modifiers)
· register() 메서드에 WatchService 이후 매개 변수들은 java.nio.file 패키지의 StandardWatchEventKinds 클래스에 선언된 상수로, 매개변수로 포함된 상수에 해당하는 이벤트만 처리함 ex) ENTRY_CREATE 등
· 선언된 세 개의 이벤트(create, delete, modify) 중 한가지가 발생하면 watcher에 해당 이벤트가 등록,
take() 메서드로 전달, take() 메서드 호출 부분에는 이벤트가 발생할 떄까지 기다리고, 이벤트를 받으면 WatchKey 객체가 리턴
· WatchKey 객체는 여러 이벤트를 포함할 수 있고 WatchKey 인터페이스에 선언된 pollEvents() 메서드를 호출하면,
WatchEvent 객체가 들어 있는 List 형태로 리턴
· WatchEvent 인터페이스의 context() 메서드를 실행하면,
객체 선언시 선언했던 제네릭 타입을 리턴하며, 일반적인 경우 Path 객체 리턴
· 모든 처리가 끝나고 key 객체에 reset() 메서드를 호출하면 여러 가지 조건에 따라서 처리됨.
일반적으로 이벤트가 다시 발생할 때까지 대기 상태로 넘어감.
· WatchService는 디렉터리를 지키고 있다가 해당 디렉터리에 파일을 생성, 수정, 삭제하면 감지된 결과를 알려주는 방식으로 동작
· 즉, lastModified() 메서드 처럼 주기적인 확인이 필요 없음
NIO2에 추가된 기타 API
seekableByteChannel
· 바이트 기반 채널 처리에 사용 되며, 채널을 보다 유연하게 처리하기 위해 추가된 인터페이스
· 현재위치를 관리하고, 해당 위치가 변경되는 것을 허용
· java.nio.channels 패키지에 선언
· 채널: 파일을 읽거나 네이트워크에서 데이터를 받는 작업을 처리하기 위한 통로
networkchannel
· 네트워크 소켓을 처리하기 위한 채널
· 네트워크 연결에 대한 바인딩, 소켓 옵션을 세팅하고, 로컬 주소를 알려주는 인터페이스
Multicastchannel
· IP 멀티캐스트를 지원하는 네트워크 채널
· 멀티캐스트: 네트워크 주소인 IP를 그룹으로 묶어, 그 그룹에 데이터를 전송하는 방식
AsynchronousFileChannel
· 비동기 방식의 File I/O를 지원하기 위한 추상 클래스
· AsynchronousFileChannel을 처리한 결과는 java.util.cocurrent의 Future 객체 또는
CompletionHandler 인터페이스를 구현한 객체로 받을 수 있음
· CompletionHandler는 모든 데이터가 성공적으로 처리되었을 때 수행되는 completed() 메소드와,
실패했을 때 처리되는 failed() 메서드를 제공하므로 필요에 따라 적절한 구현체를 사용할 것
출처
자바의신
'자바' 카테고리의 다른 글
[Java] 자바 8에 추가/변경된 사항 (0) | 2021.09.29 |
---|---|
[Java] 자바 String을 효율적으로 사용하는 방법 (StringBuilder, StringBuffer, StringJoiner, Collectors.joining) (0) | 2021.09.29 |
[Java] 자바의 Serializable (0) | 2021.09.14 |
[Java] 자바의 Input과 Output (입출력, i/o) (0) | 2021.09.10 |
[Java] 자바의 Scanner 클래스 사용하기 (0) | 2021.09.10 |
댓글