자바 4부터 새로운 입출력이라는 뜻으로 NIO(new Input/Output)인 java.nio 패키지가 포함 되었고 자바 7로 버전업하면서 비동기 채널 등의 네트워크 지원을 대폭 강화한 NIO.2 API가 추가되었습니다.
NIO.2는 java.nio2 패키지로 제공되지 않고 기존 jav.nio의 하위 패키지로 통합되어 제공하고 있습니다.
| NIO 패키지 | 포함되어 있는 내용 |
| 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 |
| 입출력 방식 | 스트림 방식 | 채널 방식 |
| 버퍼 방식 | 넌버퍼 | 버퍼 |
| 비동기 방식 | 지원 안함 | 지원 |
| 블로킹 / 넌블로킹 방식 | 블로킹 방식만 지원 | 블로킹 / 넌블로킹 방식 모두 지원 |
스트림 vs 채널
- IO는 스트림 기반으로 입력과 출력이 구분되어 있어 따로 생성해야 합니다.
- NIO는 채널 기반으로 양방향으로 입출력이 가능합니다.
넌버퍼 vs 버퍼
- IO는 출력 스트림이 1바이트를 쓰면 입력 스트림이 1바이트를 읽는 식으로 하나씩 순차적으로 읽는 방식으로 이런 시스템은 대체로 느립니다.
- NIO는 버퍼를 제공하여 복수 개의 바이트를 한꺼번에 입력받고 출력 하는 것이 가능하여 빠른 성능을 냅니다.
블로킹 vs 넌블로킹
- IO는 블로킹되어 데이터가 입력되기 전까지 스레드는 블로킹되어 다른일을 할 수 없고 기다립니다.
- NIO는 블로킹과 넌블로킹의 특성을 가지며 IO와 차이점은 스레드를 인터럽트로 빠져나올수 있습니다.
IO와 NIO의 선택
- IO는 대용량 처리할 경우에 유리하며 버퍼가 없이 데이터를 흘리면 되고 연결 클라이언트 수가 적고, 전송되는 데이터가 대용량이면 순차적으로 처리될 필요성이 있을 경우에 사용하면 좋습니다.
- NIO는 불특정 다수의 클라이언트 연결 또는 멀티 파일들을 넌블로킹이나 비동기로 처리하거나 스레드를 효과적으로 재사용할 수 있는 장점으로 하나의 입출력 처리 작업이 오래 걸리지 않는 경우에 사용하면 좋습니다.
NIO의 파일과 디렉토리
경로 정의(Path)
- Path 구현체를 얻기 위해서는 java.nio.file.Paths 클래스의 정적 메서드인 get()메서드를 호출하면 됩니다.
| 리턴 타입 | 메서드(매개 변수) | 설명 |
| 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 리턴 |
| Interator<Path> | iterator() | 경로에 있는 모든 디렉토리와 파일을 Path 객체로 생성하고 반복자를 리턴 |
| Path | normalize() | 상대 경로로 표기할 때 불필요한 요소를 제거 C:/Temp/dir1/.../dir2/file.txt -> C:/Temp/dir2/file.txt |
| WatchKey | register(..) | WatchService를 등록(와치 서비스에서 설명함) |
| File | toFile() | java.io.File 객체로 리턴 |
| String | toString() | 파일 경로를 문자열로 리턴 |
| URI | toUri() | 파일 경로를 URI 객체로 리턴 |

package networks2;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
public class Nio1 {
public static void main(String[] args) {
Path path = Paths.get("src/networks2/Nio1.java");
System.out.println("파일명 : " + path.getFileName());
System.out.println("부모 디렉토리 : " + path.getParent().getFileName());
System.out.println("중첩 경로 수 : " + path.getNameCount());
System.out.println();
for (int i = 0; i < path.getNameCount(); i++ ) {
System.out.println(path.getName(i));
}
System.out.println();
Iterator<Path> iterator = path.iterator();
while (iterator.hasNext()) {
Path temp = iterator.next();
System.out.println(temp.getFileName());
}
}
}
파일 시스템 정보(FileSystem)
| 리턴 타입 | 메서드(매개 변수) | 설명 |
| Iterable<FileStore> | getFileStores() | 드라이버 정보를 가진 FileStore 객체들을 리턴 |
| Iteralbe<Path> | getRootDirectories() | 루트 디렉토리 정보를 가진 Path 객체들을 리턴 |
| String | getSeparator() | 디렉토리 구분자 리턴 |
| long | getTotalSpace() | 드라이버 전체 공간 크기 리턴 |
| long | getUnallocatedSpace() | 할당되지 않은 공간 크기 리턴 |
| long | getUsableSpace() | 사용 가능한 공간 크기, getUnallocatedSpace()와 동일한 값 |
| boolean | isReadOnly() | 읽기 전용 여부 |
| String | name() | 드라이버명 리턴 |
| String | type() | 파일 시스템 종류 |
파일 속성 읽기 및 파일, 디렉토리 생성/삭제
| 리턴 타입 | 메서드(매개 변수) | 설명 |
| long 또는 Path | copy(...) | 복사 |
| Path | createDirectories(...) | 모든 부모 디렉토리 생성 |
| Path | createDirectory(...) | 경로의 마지막 디렉토리만 생성 |
| Path | createFile(...) | 파일 생성 |
| void | delete(...) | 삭제 |
| boolean | deleteIfExists(...) | 존재하면 삭제 |
| boolean | exists(...) | 존재 여부 |
| FileStore | getFIleStore(...) | 파일이 위치한 FileStore(드라이브) 리턴 |
| FileTime | getLastModifiedTime(...) | 마지막 수정 시간을 리턴 |
| UserPrincipal | getOwner(...) | 소유자 정보를 리턴 |
| boolean | isDirectory(...) | 디렉토리인지 여부 |
| boolean | isExecutable(...) | 실행 가능 여부 |
| boolean | isHidden(...) | 숨김 여부 |
| boolean | isReadable(...) | 읽기 가능 여부 |
| boolean | isRegularFile(...) | 일반 파일인지 여부 |
| boolean | isSameFile(...) | 같은 파일인지 여부 |
| boolean | isWritable(...) | 쓰기 가능 여부 |
| Path | move(...) | 파일 이동 |
| BufferedReader | newBufferedReader(...) | 텍스트 파일을 읽는 BufferedReader 리턴 |
| BufferedWriter | newBufferedWriter(...) | 텍스트 파일에 쓰는 BufferedWriter 리턴 |
| SeekableByteChannel | newByteChannel(...) | 파일에 읽고 쓴느 바이트 채널을 리턴 |
| DirectoryStream<Path> | newDirectoryStream(...) | 디렉토리의 모든 내용을 스트림으로 리턴 |
| InputStream | newInputStream(...) | 파일의 InputStream 리턴 |
| OutputStream | newOutputStream(...) | 파일의 OutputStream 리턴 |
| boolean | notExists(...) | 존재하지 않는지 여부 |
| String | probeContentType(...) | 파일의 MIME 타입을 리턴 |
| byte[] | readAllBytes(...) | 파일의 모든 바이트를 읽고 배열로 리턴 |
| List<String> | readAllLines(...) | 텍스트 파일의 모든 라인을 읽고 리턴 |
| long | size(...) | 파일의 크기 리턴 |
| Path | write(...) | 파일에 바이트나 문자열을 저장 |
와치 서비스(WatchService)
자바 7에서 처음 소개되었고 디렉토리 내부에서 파일 생성, 삭제, 수정 등의 내용의 변화를 감시하는데 사용합니다.
package networks2;
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;
import java.io.IOException;
import java.nio.file.*;
import java.util.List;
public class Nio2 extends Application {
class WatchServiceThread extends Thread {
@Override
public void run() {
try {
WatchService watchService = FileSystems.getDefault().newWatchService();
Path directory = Paths.get("C:/_temp");
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) {
WatchEvent.Kind kind = watchEvent.kind();
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();
if(!valid) { break;}
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
TextArea textArea;
@Override
public void start(Stage stage) throws Exception {
BorderPane root = new BorderPane();
root.setPrefSize(500, 300);
textArea = new TextArea();
textArea.setEditable(false);
root.setCenter(textArea);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("WatchServiceExample");
stage.show();
WatchServiceThread watchServiceThread = new WatchServiceThread();
watchServiceThread.start();
}
public static void main(String[] args) {
launch(args);
}
}
- javaFx 버전 문제로 실행은 못시켜봤습니다.

버퍼
넌다이렉트와 다이렉트 버퍼
넌다이렉트 버퍼는 JVM이 관리하는 힙 메모리 공간을 이용하는 버퍼이고, 다이렉트 버퍼는 운영체제가 관리하는 메모리 공간을 이용하는 버퍼입니다.
| 구분 | 넌다이렉트 버퍼 | 다이렉트 버퍼 |
| 사용하는 메모리 공간 | JVM의 힙 메모리 | 운영체제의 메모리 |
| 버퍼 생성 시간 | 버퍼 생성이 빠르다. | 버퍼 생성이 느리다. |
| 버퍼의 크기 | 작다. | 크다.(큰 데이터를 처리할 때 유리) |
| 입출력 성능 | 낮다. | 높다.(입출력이 빈번할 때 유리) |
package networks2;
import java.io.IOException;
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 Nio3 {
public static void main(String[] args) {
Path from = Paths.get("C:/_temp/test.txt");
Path to1 = Paths.get("C:/_temp/test1.txt");
Path to2 = Paths.get("C:/_temp/test2.txt");
try {
long size = Files.size(from);
FileChannel fileChannelFrom = FileChannel.open(from);
FileChannel fileChannelTo1 = FileChannel.open(to1, EnumSet.of(StandardOpenOption.CREATE,
StandardOpenOption.WRITE));
FileChannel fileChannelTo2 = FileChannel.open(to2, EnumSet.of(StandardOpenOption.WRITE));
ByteBuffer noneDirectBuffer = ByteBuffer.allocate((int) size); // allocate의 인자 수만큼 버퍼 생성
ByteBuffer directBuffer = ByteBuffer.allocateDirect((int) size); // allocate의 인자 수만큼 버퍼 생성
long start, end;
start = System.nanoTime();
for (int i =0; i < 100; i++) {
fileChannelFrom.read(noneDirectBuffer);
noneDirectBuffer.flip();
fileChannelTo1.write(noneDirectBuffer);
noneDirectBuffer.clear();
}
end = System.nanoTime();
System.out.println("넌다이렉트 \t " + (end - start) + " ns");
fileChannelFrom.position(0);
start = System.nanoTime();
for (int i =0; i < 100; i++) {
fileChannelFrom.read(directBuffer);
directBuffer.flip();
fileChannelTo2.write(directBuffer);
directBuffer.clear();
}
end = System.nanoTime();
System.out.println("다이렉트 \t " + (end - start) + " ns");
fileChannelFrom.close();
fileChannelTo1.close();
fileChannelTo2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Buffer 생성
넌다이렉트 버퍼를 생성하기 위해서는 Buffer 클래스의 allocate()와 wrap() 메서드를 호출하면 되고,
다이렉트 버퍼를 생성하기 위해서는 ByteBuffer의 allocateDirect() 메서드를 호출하면 됩니다.
- allocate() 메서드 : 인자만큼의 버퍼를 생성합니다.
- wrap() 메서드 : 이미 생성되어 있는 자바 배열을 래핑해서 Buffer 객체를 생성합니다.
- allocateDirect() 메서드 : 운영체제가 관리하는 메모리에 다이렉트 버퍼를 생성합니다. 단, ByteBuffer에서만 제공
byte 해석 순서
데이터를 처리할 때 바이트 처리 순서는 운영체제마다 차이가 있는데, 앞쪽 바이트부터 먼저 처리하는 것을 Big endian이라고 하고, 뒤쪽 바이트부터 먼저 처리하는 것을 Little endian이라고 합니다.
Buffer의 위치 속성
| 속성 | 설명 |
| position | 현재 읽거나 쓰는 위치값입니다. 인덱스 값이기 때문에 0부터 시작하며, limit보다 큰 값을 가질수 없고 만약 position과 limit의 값이 같아진다면 더 이상 데이터를 쓰거나 읽을 수 없다는 의미입니다. |
| limit | 버퍼에서 읽거나 쓸 수 있는 위치의 한계를 나타내며, 이 값은 capacity보다 작거나 같은 값을 가집니다. 최초에 버퍼를 만들때는 capacity와 같은 값을 가집니다. |
| capacity | 버퍼의 최대 데이터 개수(메모리 크기)를 나타내며 인덱스 값이 아니라 수량임을 주의합니다. |
| mark | reset() 메서드를 실행했을 때에 돌아오는 위치를 지정하는 인덱스로서 mark() 메서드로 지정할 수 있습니다. 주의할 점은 반드시 position이하의 값으로 지정해주어야 하고 position이나 limit의 값이 mark값보다 작은 경우, mark는 자동 제거 됩니다. mark가 없는 상태에서 reset()메서드를 호출하면 InvalidMarkException이 발생합니다. |
Buffer 공통 메서드
| 리턴 타입 | 메서드(매개변수) | 설명 |
| Object | array() | 버퍼가 래핑한 배열을 리턴 |
| int | arrayOffset() | 버퍼의 첫 번째 요소가 있는 내부 배열의 인덱스를 리턴 |
| int | capacity() | 버퍼의 전체 크기를 리턴 |
| Buffer | clear() | 버퍼의 위치 속성을 초기화(position=0, limit=capacity) |
| Buffer | flip() | limit를 position으로, position을 0인덱스로 인동 |
| boolean | hasArray() | 버퍼가 래핑한 배열을 가지고 있는지 여부 |
| boolean | hasRemaining() | position과 limit사이에 요소가 있는지 여부(position < limit) |
| boolean | hasDirect() | 운영체제의 버퍼를 사용하는지 여부 |
| boolean | isReadOnly() | 버퍼가 읽기 전용인지 여부 |
| int | limit() | limit 위치를 리턴 |
| Buffer | limit(int newLimit) | newLimit으로 limit위치를 설정 |
| Buffer | mark() | 현재 위치를 mark로 표시 |
| int | position() | position 위치를 리턴 |
| Buffer | position(int newPosition) | newPosition으로 position 위치를 설정 |
| int | remaining() | position과 limit사이의 요소의 개수 |
| Buffer | reset() | position을 mark위치로 이동 |
| Buffer | rewind() | position을 0인덱스로 이동 |
파일 채널
fileChannel은 정적 메서드은 open()을 호출해서 사용할 수 있고 IO의 FileInputStream, FileOutputStream의 getChannel()메서드를 호출해서 얻을 수도 있습니다.
| 열거 상수 | 설명 |
| READ | 읽기용으로 파일을 연다 |
| WRITE | 쓰기용으로 파일을 연다 |
| CREATE | 파일이 없다면 새 파일을 생성한다 |
| CREATE_NEW | 새 파일을 만든다. 이미 파일이 있으면 예외와 함께 실패한다 |
| APPEND | 파일 끝에 데이터를 추가한다(WRITE나 CREATE와 함께 사용) |
| DELETE_ON_CLOSE | 채널을 닫을 때 파일을 삭제한다(임시 파일을 삭제할 때 사용) |
| TRUNCATE_EXISTING | 파일을 0바이트로 잘라낸다(WRITE 옵션과 함께 사용) |
기본 사용법
package networks2;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Nio4 {
public static void main(String[] args) {
try {
FileChannel fileChannel = FileChannel.open(Paths.get("C:/_temp/test.txt"), StandardOpenOption.CREATE_NEW,
StandardOpenOption.WRITE);
} catch (IOException e) {
e.printStackTrace();
}
}
}
파일 생성 및 쓰기
public class Nio5 {
public static void main(String[] args) throws IOException {
Path path = Paths.get("C:/_temp/fileChannel.txt");
Files.createDirectories(path.getParent());
FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
String data = "안녕하세요";
Charset charset = Charset.defaultCharset();
ByteBuffer byteBuffer = charset.encode(data);
int byteCount = fileChannel.write(byteBuffer);
System.out.println("file.txt : " + byteCount + " bytes written");
fileChannel.close();
}
}
파일 읽기
package networks2;
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 Nio6 {
public static void main(String[] args) throws IOException {
Path path = Paths.get("C:/_temp/fileChannel.txt");
FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.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();
data += charset.decode(byteBuffer).toString();
byteBuffer.clear();
}
fileChannel.close();
System.out.println("file.txt : " + data);
}
}
파일 복사
package networks2;
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 Nio7 {
public static void main(String[] args) throws IOException {
Path from = Paths.get("C:/_temp/test.txt");
Path to = Paths.get("C:/_temp/fileChannelCopy.txt");
FileChannel fileChannelFrom = FileChannel.open(from, StandardOpenOption.READ);
FileChannel fileChannelTo = FileChannel.open(to, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
ByteBuffer buffer = ByteBuffer.allocateDirect(100);
int byteCount;
while(true) {
buffer.clear();
byteCount = fileChannelFrom.read(buffer);
if (byteCount == -1) break;
buffer.flip();
fileChannelTo.write(buffer);
}
fileChannelFrom.close();
fileChannelTo.close();
System.out.println("파일 복사 성공");
}
}
파일 비동기 채널
비동기 처리는 AsynchronousFileChannel로 처리할 수 있습니다.
'개발(합니다) > Java&Spring' 카테고리의 다른 글
| [spring boot 설정하기-1] spring boot intellij에서 구성하기 (2) | 2021.04.02 |
|---|---|
| [java]java로 마우스 움직이기 (0) | 2021.03.30 |
| [java-기초-18] 네트워크 기초 (2) | 2021.03.11 |
| [java-기초-17] IO 기반 입출력 (0) | 2021.03.10 |
| [java-기초-16] 스트림과 병렬 처리 (0) | 2021.02.24 |