IO: Java 1.0
NIO: Java 1.4
NIO.2: Java 1.7
IO -> NIO
NIO -> NIO.2
- JSR 51 adopted in JDK 1.4
- 일반적인 IO API
- java.nio
- java.nio.channels
- java.nio.charset
- as an OpenJDK project
- 3 primary elements
- extensive File I/O API
- socket channel API -> multicasting, socket binding with channel
- asynchronous I/O API -> scalability
- java.nio.file 추가
- java.nio.file.attribute
- java.nio.file.spi
- 기존 File API의 문제점
- IO 예외처리가 부족하여 종종 crash하기도 했음
- Linux와 Windows가 다르게 동작하기도 함
- 동시에 메타데이터를 얻는 메소드 부족
- 풍부한 메타데이터를 활용 희망
- 자체적인 파일시스템 구현체 사용 희망
- 메모리 기반 유사 파일시스템
- Zip 파일 포맷 파일시스템
- java.nio.file의 Path class
- java.io의 File class와 유사함
BufferReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8);
- Path란 file을 가리키는 (시스템 의존적인) 경로
- 새로운 delete() method는 예외를 발생시킴
- 두 가지 유형의 operation
- path 조작, path 요소 추출, path에 대한 iteration
- create/open/delete a file, create a directory, ...
// 파일 검사
Path p = Paths.get(HOME);
Files.exists(p);
Files.isRegularFile(p);
Files.isReadable(p);
// 파일, 디렉토리 생성
Path p = Paths.get(HOME + "/" + fileName);
Files.createFile(p);
Files.delete(p);
Path p = Paths.get(HOME + "/" + dirName);
Files.createDirectory(p);
Files.delete(dir);
// 파일 복사
Files.createDirectory(dir1);
Files.createDirectory(dir2);
Path file1 = dir1.resolve("filetocopy.txt");
Path file2 = dir2.resolve("filetocopy.txt");
Files.createFile(file1);
Files.copy(file1, file2);
// 경로 분해하기
Path p = Paths.get("/articles/baeldung/logs");
p.toString();
Path fileName = p.getFileName();
Path name0 = p.getName(0);
Path name1 = p.getName(1);
Path name2 = p.getName(2);
// parent와 root 구하기
Path p1 = Paths.get("/articles/baeldung/logs");
Path p2 = Paths.get("/articles/baeldung");
Path parent1 = p1.getParent();
Path parent2 = p2.getParent();
Path root1 = p1.getRoot();
Path root2 = p2.getRoot();
// 표준화하기
Path p = Paths.get("/home/./baeldung/articles");
Path cleanPath = p.normalize(); // /home1/baeldung/articles
// 경로 이어붙이기
Path p = Paths.get("/baeldung/articles");
Path p2 = p.resolve("java"); // /baeldung/articles/java
- IO
- java.io.File.list(), java.io.File.listFiles()의 문제점
- 대규모 디렉토리에서 list를 수행하면 hang 발생함
- 동시성도 제공되지 않아서 OOME 발생함
- NIO.2
- 디렉토리는 iterator 형태로 사용하는 stream 객체
- 디렉토리 내부의 파일 이름은 byte sequence 형태로 보관됨
- 이름으로 필터링 가능(regex, glob pattern)
// Files.newDirectoryStream()을 이용한 방법
Set<String> fileList = new HashSet<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(dir))) {
for (Path path : stream) {
if (!Files.isDirectory(path)) {
fileList.add(path.getFileName().toString());
}
}
}
- DirectoryStream은 Java 8의 Stream과는 관련없음
- Java 8의 Stream 활용
// Files.list()를 이용한 방법
try (Stream stream = Files.list(Paths.get(dir))) {
return stream
.filter(file -> !Files.isDirectory(file))
.map(Path::getFileName)
.map(Path::toString)
.collect(Collectors.toSet());
}
- walkFileTree()
- 파일시스템을 tree 형태로 간주하고 traverse하는 수단
- recursive copy/move/delete/...
- 에러 케이스 대응
- FileVisitResult visitFileFailed()
- preVisitDirectory()
- postVisitDirectory()
public static class PrintFiles extends SimpleFileVisitor<Path> {
// 방문한 파일 정보
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
if (attr.isSymbolicLink()) {
System.out.format("Symbolic link: %s ", file);
} else if (attr.isRegularFile()) {
System.out.format("Regular file: %s ", file);
} else {
System.out.format("Other: %s ", file);
}
System.out.println("(" + attr.size() + "bytes)");
return CONTINUE;
}
// 방문한 디렉토리 정보
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
System.out.format("Directory: %s%n", dir);
return CONTINUE;
}
// 파일을 접근하는 중에 에러가 존재하면, 사용자에게 에러와 예외로 알려줌
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
System.err.println(exc);
return CONTINUE;
}
}
- 일부 파일시스템은 symbolic link(soft link) 기능을 제공함
- link는 대상 파일시스템 객체를 투명하게 가리키는 역할
- link에 접근하면 대상 객체인 것처럼 보임
- 그러나 삭제나 이름변경은 대상 객체가 아니라 link에 영향을 끼침
- NIO.2 java.nio.file에서야 드디어 POSIX symbolic link를 완벽 지원
- Linux, Unix, Windows Vista, ...
- circular references에 의한 loop를 보고함
- file change notification
- 사용방법
- WatchService를 생성
- event를 등록
- event를 받아내서 정보를 활용함
- event 초기화
public static void main(String[] args) {
WatchService watcher = FileSystems.getDefault().newWatchService();
Path dir = FileSystems.getDefault().getPath("/usr/karianna");
WatchKey key = dir.register(watcher, ENTRY_MODIFY, ENTRY_CREATE);
while (true) {
key = watcher.take();
for (WatchEvent<?> event : key.pollEvents()) {
// 실습 1. event의 kind()에 따라 각기 다른 메시지 출력하기
// 실습 2. 다른 event(delete 등) 추가해보기
}
key.reset();
}
}
- POSIX file permission
- Access Control List
- ACL component: 권한 허용 여부
- principal component: group or user
- permissions component: POSIX permission
- flags component: 상속이나 전파 방법
// Files.getFileAttributeView().readAttributes()
Path home = Paths.get("/Users/myid");
BasicFileAttributeView basicView = Files.getFileAttributeView(home, BasicFileAttributeView.class);
BasicFileAttributes basicAttribs = basicView.readAttributes();
long size = basicAttribs.size();
boolean isDir = basicAttribs.isDirectory();
boolean isFile = basicAttribs.isRegularFile();
boolean isSymLink = basicAttribs.isSymbolicLink();
boolean isOther = basicAttribs.isOther();
FileTime created = basicAttribs.creationTime();
FileTime modified = basicAttribs.lastModifiedTime();
FileTime accessed = basicAttribs.lastAccessTime();
// Files.getFileStore().getTotalSpace()
Path file = Paths.get("file");
FileStore store = Files.getFileStore(file);
long total = store.getTotalSpace();
long used = store.getTotalSpace() - store.getUnallocatedSpace();
// FileSystems.getDefault().getFileStores().getTotalSpace()
Iterable<FileStore> fileStores = FileSystems.getDefault().getFileStores();
for (FileStore fileStore : fileStores) {
long totalSpace = fileStore.getTotalSpace();
long unAllocated = fileStore.getUnallocatedSpace();
long usable = fileStore.getUsableSpace();
}
// Files.getFileAttributeView().getOwner()
Path path = Paths.get(HOME);
FileOwnerAttributeView ownerView = Files.getFileAttributeView(attribPath, FileOwnerAttributeView.class);
UserPrincipal owner = ownerView.getOwner();
String ownerName = owner.toString();
// Files.getFileAttributeView().list()
Path path = Paths.get("somefile");
UserDefinedFileAttributeView userDefView = Files.getFileAttributeView(attribPath, UserDefinedFileAttributeView.class);
List<String> attribList = userDefView.list();
// 속성 쓰기
String name = "attrName";
String value = "attrValue";
userDefView.write(name, Charset.defaultCharset().encode(value));
// 속성 읽기
ByteBuffer attrValue = ByteBuffer.allocate(userView.size(attrName));
userDefView.read(attribName, attribValue);
attrValue.flip();
String attrValue = Charset.defaultCharset().decode(attrValue).toString();
// 속성 삭제하기
userDefView.delete(attrName);
- 새로운 파일시스템을 제공하려는 경우에 사용하는 service provider interface
- blocking vs nonblocking
- IO operation이 return할 때까지 기다릴지의 여부
- blocking은 무조건 대기
- synchronous vs asynchronous
- main thread가 처리하는지, callback이 처리할지
- synchronous blocking IO
- synchronous nonblocking IO
- asynchronous blocking IO
- with callback, with blocking
- asynchronous nonblocking IO
- with callback, without blocking
- NIO에 있던 SocketChannel, ServerSocketChannel, FileChannel class에 Asynchronous prefix 붙은 class 제공
- Future나 CompletionHandler를 이용하여 return값 처리
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open().bind(null);
Future<AsynchronousSocketChannel> future = server.accept();
future.isDone();
future.isCancelled();
future.cancel(true);
AsynchronousSocketChannel client= future.get();
¶ CompletionHandler 방식의 예제
AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind(null);
listener.accept(attachment, new CompletionHandler<AsynchronousSocketChannel, Object>() {
public void completed(AsynchronousSocketChannel client, Object attachment) {
// do whatever with client
}
public void failed(Throwable exc, Object attachment) {
// handle failure
}
});