JAVA/지식

[외부 데이터 입출력] java.nio (3/3)

우엥우아앙 2021. 1. 11. 17:08

[java.nio 패키지]

  • New IO 라는 의미로 java.io 의 업그레이드 버전
  • NIO2는 Java Se-7버전에서 java.nio가 한 번 더 업그레이드 되었다는 의미

[기존 java.io와 차이점]

  • 양방향 Channel 방식을 사용해 외부 데이터와 입출력 연동(통로가 하나만 있으면 됨)
  • 기본적으로 버퍼(buffer)를 사용해 속도를 높임(커널 버퍼를 직접 사용해서 입출력 속도 향상도 가능)
  • 비동기 지원(메소드 호출 시점과 출력 시점이 다름)
  • Non-Blocking 지원(I/O를 수행하는 동안 스레드가 놀지 않도록 함)

 

 


[Java.nio.Path / Java.nio.Files 클래스]

  • java.io.FIle 클래스를 대체할 수 있는 파일 시스템 작업 기능
  • 메소드가 워낙 많고 사용법도 복잡해서 간단한 작업은 java.io.File 클래스 사용이 편함
  • java.io.File 인스턴스와 java.nio.Path 인스턴스는 서로 타입 변환 메소드를 제공

java.io에서는 File 클래스에서 경로와 파일을 다루는 기능이 모두 포함돼 있습니다. 그러나 NIO를 업그레이드 하면서 java.nio.file 패키지에 경로를 다루는 "Path" 클래스와 파일과 디렉토리를 다루는 "Files" 클래스로 나눠서 다시 만들었습니다. 물론 파일 시스템에 대한 안정성과 기능도 확장되었습니다. 그리고 기존 java.io.File 클래스와도 연동해서 사용할 수 있습니다.

 


[채널 생성(Channel)]

  • java.io의 단방향 스트림 개념과 달리, 양방향 통로를 채널이라고 함
  • 하드웨어, 장비, 파일, 네트워크 소켓 등과 입출력 작업을 수행할 수 있는 통로
  • 하나의 채널만 열어두면 입출력을 동시에 진행할 수 있음
  • 기존 스트림 방식 대비 속도가 빠름(통로 자체가 빠른 속도로 동작함)
  • non-blocking 방식을 지원하여 자원 사용의 효율성을 높임

외부 데이터 입출력을 위해서는 통로를 생성해야 합니다. 기존 java.io가 input, output이라는 단방향 스트림 방식으로 통롤르 생성했다면, java.nio에서는 채널이라는 양방향 통로를 생성해서 사용합니다. 일단 하나만 생성해도 돼서 편리한 점도 있고, 저레벨 언어가 가진 기술을 적용해서 속도도 기존 대비 빨라졌다고 합니다. 

 

또한 blocking 방식으로 작동하던 스트림과는 달리, non-blocking 방식이 지원되어 보다 효율적으로 리소스를 사용할 수 있도록 했습니다. non-blocking이란, 스레드가 입출력 작업을 할 때 스레드를 멈추지 않고 여러 입출력 작업을 동시에 할 수 있도록 하는 것을 말합니다. 참고로 FileChannel 클래스는 non-blocking이 지원되지 않아 대용량 입출력을 위해서는 다중 스레드를 이용해야 합니다.

 

// 채널 생성 (읽기, 쓰기모드)
Path input = Paths.get("c:\\input.txt");

try (FileChannel in = FileChannel.open(input, 
	StandardOpenOption.READ, StandardOpenOption.WRITE)) {			
} catch (Exception e) {
	System.out.println("예외가...!");
}

[java.nio.Buffer 인터페이스를 상속받는 버퍼 클래스들]

  • java.io 필터 스트림의 버퍼 클래스들과 같이 버퍼를 사용해서 파일 입출력을 사용한다고 볼 수 있음
  • allocateDirect() 매소드를 사용하면 시스템의 커널 버퍼를 사용할 수 있어 입출력 속도 향상
  • boolean 타입을 제외한 원시타입의 버퍼 클래스들이 존재

ByteBuffer 클래스의 allocateDirect() 매소드로 생성하는 버퍼 외에는 모두 커널 버퍼가 아닌 JVM 내에 생성되는 버퍼입니다. 따라서 버퍼 사용 방식은 java.io 필터 스트림의 버퍼 클래스를 사용하는 것과 비슷합니다. 대신 위에서 언급한 차이점은 여전히 존재합니다.

 

커널 버퍼라너 운영체제가 관리하는 메모리 영역에 생성되는 버퍼 공간입니다. 자바의 특성 상 외부 데이터를 가져올 때는 OS의 메모리 버퍼에 먼저 담았다가 JVM 내의 버퍼로 한번 더 데이터를 옮겨줘야 하기 때문에 메모리를 직접 다루는 C언어에 비해 입출력이 느릴 수 밖에 없습니다. 이러한 단점을 개선하기 위해 나온 기능이 ByteBuffer 클래스에서 제공하는 allocateDirect() 메소드입니다. 내부적으로는 C언어를 호출해 시스템 메모리 영역을 사용한다고 합니다. 이 때문에 입출력 속도 자체는 빠르지만 내부적인 과정이 복잡하기 때문에 버퍼공간을 생성하고 해제하는 속도가 느려집니다. 따라서 커널 버퍼 사용은 한 번 만들어서 오래 사용해야할 때 활용하는 것이 좋습니다.

 

 

 

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 javaNio{
	public static void main(String[] args) {
		// 커널 버퍼 사용
		useKernelBuffer();
		
	}
	
	public static void useKernelBuffer() {
		String str = "";

		// 채널 생성 (읽기, 쓰기모드)
		Path input = Paths.get("c:\\tmp\\test1.txt");
		try (FileChannel in = FileChannel.open(input, 
			StandardOpenOption.READ, StandardOpenOption.WRITE)) {

		    // ByteBuffer 생성 (Direct)
		    ByteBuffer buf = ByteBuffer.allocateDirect(100);
		    Charset charset = Charset.defaultCharset();

		    int count = 0;

		    while(count >= 0) {
		        count = in.read(buf);
		        buf.flip();
		        // flip()
		        // 이 버퍼의 위치(position)는 0으로 limit와 position값과 같게 설정한다. 마크값은 파기된다. 
		        // 이 메서드는 read 조작뒤 write()나 get()하기전에 이 메소드를 호출한다.

		        str += charset.decode(buf).toString();
		        System.out.println(str);
		        System.out.println("--------------");
		        buf.clear();
		    }

		    // 파일 쓰기
		    String str2 = "\nnew Hello world..!!";
		    buf = charset.encode(str2);
		    count = in.write(buf);

		} catch (Exception e) {
		    System.out.println("예외 발생");
		}

		System.out.println(str);
	}
}

 

참고 : https://codevang.tistory.com/157?category=827591