Akashic Records

자바 nio - 11 본문

오래된글/Java

자바 nio - 11

Andrew's Akashic Records 2018. 4. 9. 12:45
728x90

11. FileChannels 클래스

FileChannels는 파일입출력을 위한 채널로 AbstractInterruptibleChannel 클래스를 상속해서 비동기적으로 중단될 수 있게 되어있다. 그리고 ByteChannel 인터페이스를 구현해서 읽기와 쓰기를 동시에 할 수 있다.


1. FileChannels 클래스 객체 생성

FileChannels 클래스는 스스로 인스턴스를 만들 수 없다. 왜냐하면 FileChannels 클래스는 abstract class이고 어떠한 클래스 메서드도 가지지 않는다. 그럼 어떻게 FileChannels 클래스를 사용할 수 있을까? 바로 java.io패키지의 FileInputStream, FileOutputStream, RandomAccessFile 클래스의 getChannel()메서드를 호출함으로서 FileChannels 클래스의 인스턴스를 얻을 수 있다. FileInputStream의 getChannel()메서드를 사용한다면 읽기만 할 수 있는 FileChannel을 얻게 되고, FileOutputStream의 getChannel()메서드를 사용한다면 쓰기만 할 수 있는 FileChannel을 얻게 되고, RandomAccessFile의 getChannel()메서드를 사용한다면 읽기와 쓰기를 모두 할 수 있는 FileChannel을 얻게 된다. 예를 들면 다음과 같다.

FileInputStream fis=new FileInputStream("파일명");

FileChannels fc=fis.getChannels();


2. 주요메서드

- read()류 메서드와 write()류 메서드

: 읽기와 쓰기를 위한 메서드로 전부 ByteBuffer를 인자로 받고 있어서 버퍼기반의 파일입출력을 할 수 있다. 또한 특정 포인터 위치를 지정해서 입출력을 할 수 있는데 이는 인자로 position값을 주면된다. 리턴값은 long형으로 해당 채널의 스트림의 끝에 이르고 있는 경우는 -1 를 리턴한다.

- public abstract void force (boolean metaData) throws IOException

: 버퍼에 쓰기를 다한 다음 그 내용을 컴퓨터 하드디스크에 강제적으로 반영하도록하는 메서드이다. FileChannels에서 쓰기는 버퍼의 내용을 바꾸는 것이므로 실제 파일에 변경내용을 반영하려면 force()를 호출해야 한다. 파라미터값으로 true가 오면 파일의 내용뿐만 아니라 파일의 최종 변경시간 등에 대한 내용도 반영이 되고 false이면 파일의 내용만 반영이 된다. 단 로컬에 위치한 파일에만 적용된다.

- public abstract long size () throws IOException

: 파일의 크기를 리턴. 단 파일이 close된 경우(close()메서드 호출) 예외가 발생한다.

- public abstract FileChannel truncate (long size) throws IOException

: 인자로 들어온 수로 크기를 설정한다. 현재 크기보다 인자로 들어온 수가 작다면 크기는 줄어드나 인자가 크다면 아무런 변화가 없다. 즉 크기는 줄일 순 있지만 늘리려면 새로운 데이터를 쓰는 방법을 써야 한다.

- transferFrom (ReadableByteChannel src, long position, long count): FileChannels에 대해 ReadableByteChannel 인스턴스인 채널로부터 읽어들인다.

- transferTo (long position, long count, WritableByteChannel target) : FileChannels에 대해 WritableByteChannel 인스턴스로 데이터를 출력한다.

--> 이들 메서드는 인자로 읽거나 쓰기를 할 위치를 position으로 지정하고 얼마나 읽거나 쓸지를 conut로 지정한다. 그리고 성공적으로 작업을 마친 경우에는 읽거나 쓴 값을 long형으로 리턴해 준다.

--> 이들 메서드 인자로 FileChannel을 주게 되면 파일간의 복사도 할 수 있다. 이렇게 하면 read(),write()메서드를 사용하는 것보다 빠르게 처리할 수 있다.

- public final FileLock lock ()

: 파일에 Lock을 걸어서 다른 프로세스나 스레드의 접근을 막은 메서드이다. 이 메서드를 호출하면 FileLock 클래스의 인스턴스를 리턴한다.

- public abstract FileLock lock (long position, long size, boolean shared)

: 인자인 position에서 size까지의 일부 영역을 lock을 걸수 있고 shared가 true이면 공유 FileLock을 얻을 수 있는데 이는 FileLock이 공유가 될 수 있다는 뜻이다. 이는 하나의 스레드나 프로세스가 독점을 방지하기 위해서이다. 공유 FileLock이라면 그 파일에 대해 두 개의 혹은 프로세스가 접근할 수 있다. 이 메서드 외에도 tryLock()에서도 설정할 수 있다.

- public final FileLock tryLock ()

: 다른 스드가 lock을 걸고 있는 경우 null를 리턴해서 lock()메서드가 블로킹되는 것을 방지해 준다.

※ 파일 Lock 풀기

  1. release() 사용.-> 이 FileLock이 release()된것인지 아닌지는 isValid()로 알 수 있다.

  2. FileLock에 연결된 하부 채널이 close()된 경우.

  3. 자바가상머신이 종료된경우

- public abstract MappedByteBuffer map (FileChannel.MapMode mode, long position, long size)

: 파일의 영역을 position의 위치에 size만큼의 크기로 직접 메모리에 매핑한다. 이 메서드는 파일을 처리하는데 있어 그 내용을 자주 읽거나 쓰는 경우에 주로 사용한다. 이 메서드의 리턴형은 MappedByteBuffer이다. MappedByteBuffer 클래스는 Heap영역이 아닌 자바가 관리하는 일반 메모리 영역을 따로 잡는다. 이 영역은 순수하게 버퍼의 목적으로 쓰이기 때문에 객체를 보관하는 목적의 Heap영역보다 속도가 빠르다. 대신 일반 메모리를 잡아서 처리하는 것이 아니므로 준비시간이 많이 걸린다. 따라서 작은 파일보다는 큰 파일을 처리하고자 할 때 이용하는 것이 좋다.

인자로 들어온 mode는 MappedByteBuffer를 파일에 대한 버퍼로 사용할 때 어떤 방식으로 사용할 지를 설정해주는 것으로 FileChannel.MapMode 클래스에 상수로 선언이 되어 있다. 다음과 같은 값들이 있다.

  • public static final FileChannel.MapMode READ_ONLY : 읽기전용

  • public static final FileChannel.MapMode READ_WRITE : 읽기/쓰기 가능

  • public static final FileChannel.MapMode PRIVATE : copy-on-write로 읽기와 쓰기는 가능하지만 쓰기를 하는 경우 복사본을 만들어서 변화내용을 따로 보관한다. 원래 파일에는 적용되지 않는다.

MappedByteBuffer에서 파일의 내용을 실제 메모리로 읽어들이는 메서드는 load(). 로드되었는지 여부는 isLoaded()로 알 수 있다.


이로서 FileChannel클래스가 가진 메서드를 살펴보았다. 보면 알수 있겠지만 FileChannel는 파일과 관련된 작업들을 할 수 있어 io패키지의 FileInputStream이나 FileOutputStream과 같다. 하지만 다른점은 Non-Blocking를 지원한다는 점이다. 이는 네트워크하고 관련이 깊다. 따라서 네트워크를 모른다면 이해할 수 없을 것이다. 그리고 파일에 Lock을 걸수 가 있다는 것이다. 이는 아무나 그 파일에 접근할 수 없다는 것이다. 어쨌든 전에 비해서 많은 것들이 추가 되었음을 볼수 있다.

3. FileChannel클래스 예제


1. 파일에 쓰기와 읽기

mefile.java 소스는 문자열을 파일에 저장해서 이를 다시 읽어서 화면에 출력하는 소스이다. FileChannel클래스를 사용하고 있으며 버퍼로는 BytrBuffer 클래스를 사용하고 있다.

package nio;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.nio.ByteBuffer;

import java.nio.channels.FileChannel;

public class fileChannelTest {

public static void main(String[] args)

throws Exception {

if( args.length > 1 ) {

new fileChannelTest().exec(args[0], args[1]);

} else {

System.err.println("사용법) java nio.fileChannelTest [fileName] [I/O]");

System.exit(0);

}

}


public void exec(String fileName, String handleMode)

throws FileNotFoundException, IOException {

if(handleMode.equals("O"))

fileOutPut(fileName);

else

fileInPut(fileName);

}

protected void fileInPut(String fileName)

throws IOException, FileNotFoundException {

FileInputStream fileIn = new FileInputStream("fileName");

FileChannel in = fileIn.getChannel();


ByteBuffer buf = ByteBuffer.allocate((int)in.size());

in.read(buf);

System.out.println(new String(buf.array()));

fileIn.close();

}

protected void fileOutPut(String fileName)

throws IOException, FileNotFoundException {

FileOutputStream f_out = new FileOutputStream(fileName);

FileChannel out = f_out.getChannel();


String s = "java.nio.FileChannels 클래스";

byte[] by = s.getBytes();

ByteBuffer buf = ByteBuffer.allocate(by.length);

buf.put(by);

buf.clear();

out.write(buf);

out.close();

}

}


결과를 보면 a2.txt파일이 생성되고 파일의 내용이 저장되어 있는 것을 확인할 수 있다. 그리고 화면을 통해 한글도 깨지지 않고 출력됨을 알 수 있다. 한글이 깨지지 않게 출력이 되도록 하기위해 바이트 배열을 스트링 객체로 변환 시켰지만 한글이 없는 경우에는 ByteBuffer클래스를 CharBuffer클래스로 바꾸고 출력해 주면 된다.

소스를 보면 FileChannel 객체를 쓰기와 읽기용 두개를 생성하고 있는데 RandomAccessFile의 getChannel()메서드를 사용한다면 읽기와 쓰기를 모두 할 수 있는 FileChannel을 얻을 수 있게되어 소스가 좀더 간단하게 된다


2. 파일 복사.

FileTest.java는 파일간 복사를 하는 소스로 4가지 방법으로 복사를 한다. 결과는 같지만 하는 방법이 틀리다.

1. io패키지 사용

2. 매핑을 이용.

3. read()와 write()사용

4. transferTo() 사용

이 기능은 번호로 구분하며 명령행 인자로 실행시 해당 번호와 파일명, 복사파일명을 주면 그 기능이 실행되도록 하였다.

package nio;

import java.io.BufferedInputStream;

import java.io.BufferedOutputStream;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.nio.ByteBuffer;

import java.nio.MappedByteBuffer;

import java.nio.channels.FileChannel;

public class fileCopy {

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

if(args.length<3){

System.out.println("사용법 : java FileTest 메서드번호 파일명 복사파일명");

System.out.println("1. io패키지 사용");

System.out.println("2. 매핑을 이용.");

System.out.println("3. read()와 write()사용");

System.out.println("4. transferTo() 사용");

System.exit(1);

}

switch(Integer.parseInt(args[0])){

case 1:

copyIO(args[1],args[2]);

break;

case 2:

copyMap(args[1],args[2]);

break;

case 3:

copyNIO(args[1],args[2]);

break;

case 4:

copyTransfer(args[1],args[2]);

break;

}

}

public static void copyIO(String inFileName, String outFileName)

throws FileNotFoundException, IOException {

BufferedInputStream bufferInStream =

new BufferedInputStream(new FileInputStream(inFileName));


BufferedOutputStream bufferOutStream =

new BufferedOutputStream(new FileOutputStream(outFileName));


byte[] buffer = new byte[1024];

int read = 0;

while ((read = bufferInStream.read(buffer, 0, 1024)) != -1) {

bufferOutStream.write(buffer, 0, read);

}


bufferInStream.close();

bufferOutStream.close();

}

public static void copyMap(String inFileName, String outFileName)

throws FileNotFoundException, IOException {

FileChannel in = new FileInputStream(inFileName).getChannel();

FileChannel out = new FileOutputStream(outFileName).getChannel();

MappedByteBuffer mapBuffer =

in.map(FileChannel.MapMode.READ_ONLY,0,in.size());

out.write(mapBuffer);

in.close();

out.close();

}

public static void copyNIO(String inFileName, String outFileName)

throws FileNotFoundException, IOException {

FileChannel in = new FileInputStream(inFileName).getChannel();

FileChannel out = new FileOutputStream(outFileName).getChannel();


ByteBuffer buf =

ByteBuffer.allocate((int)in.size());

in.read(buf);

buf.flip();

out.write(buf);

in.close();

out.close();

}

public static void copyTransfer(String inFileName, String outFileName)

throws FileNotFoundException, IOException {

FileChannel in = new FileInputStream(inFileName).getChannel();

FileChannel out = new FileOutputStream(outFileName).getChannel();


in.transferTo(0,in.size(),out);

in.close();

out.close();

}

}


위 소스는 4가지 방법으로 파일간의 복사를 하고있다. 어느 것이 빠르겠는가는 테스트를 해 보면 간단하다. 파일 복사간의 시간차를 평균을 내보면된다. 첫번째 방법은 우리가 잘 알고 있는 스트림을 이용한 복사이다. 두번째 방법은 MappedByteBuffer를 버퍼로 사용해서 복사를 한다. 읽기용 파일채널이 우선 버퍼에 매핑이 되고 그 버퍼는 다시 출력 파일 채널에 사용된다. 이는 이 버퍼에 통째로 전체 파일이 저장되어 있으므로 이 버퍼를 그대로 출력을 하면 파일을 복사하는 것과 동일한 기능을 나타낸다. 세번째 방법은 파일채널이 가진 일반적인 복사 방법이다. 미리 준비된 버퍼에 파일의 내용을 읽여들인다. 그런후 버퍼의filp() 메서드를 이용해서 현재 위치의 한도를 설정하고 현재 위치를 0으로 만들어 쓰기 준비를 한다. 그리고 쓰기. 네번째 방법은 transferTo() 를 사용하는 것이다. 이들 방법중 가장 빠른 것은 transferTo() 를 사용하는 것이다. 이는 기존의 반복문을 사용한 복사보다 더 효율적이다. 몇몇의 운영체계들은 실제 복사하지 않고 파일시스템 캐시에서 타켓 채널로 바이트를 직접 전송하는 방법을 사용한다. 즉 transferTo()는 이런 운영체제의 특성에 의존해서 매우 빠른 파일 전송을 제공해준다.



728x90

'오래된글 > Java' 카테고리의 다른 글

자바 nio - 13  (0) 2018.04.09
자바 nio - 12  (0) 2018.04.09
자바 nio - 10  (0) 2018.04.09
자바 nio - 9  (0) 2018.04.09
자바 nio - 8  (0) 2018.04.09
Comments