NIO 그리고 NIO.2

gustjtmd·2022년 1월 20일
0

Java

목록 보기
39/40

파일 시스템

기본적인 파일 시스템

윈도우의 파일 경로를 나타내는 방식
C:\javastudy\simple.java

리눅스의 파일 경로를 나타내는 방식
/javastudy/simple.java

절대경로
윈도우 : C:\javastudy\simple.java
리눅스 : /javastudy/simple.java

상대경로는 '현재 디렉토리'를 기준으로 다음과 같이 파일의 위치를 표현한다.
윈도우 : javastudy\simple.java
리눅스 : javastudy/simple.java

프로그램이 실행되면 그 프로그램의 작업 디렉토리가 하나 정해진다. 그리고 그 작업 디렉토리를
가리켜 '현재 디렉토리'라 한다. 예를 들어서 실행 중인 프로그램에서 경로 정보 없이 파일을 
생성하면 파일이 생성되는 디렉토리가 있는데 그곳이 바로 '현재 디렉토리'이다.
그리고 '절대 경로'는 그 이름처럼 파일 또는 디렉토리의 위치를 루트 디렉토리를 기준으로 표현한
경로이다. 따라서 절대 경로로 가리키는 파일은 그 대상이 변하지 않는다.
하지만 '상대 경로'는 현재 디렉토리가 어디냐에 따라서 가리키는 파일이 달라진다.

Paths와 Path 클래스

자바 7에는 다음 인터페이스가 추가되었다. 그리고 이는 결함이 있던 java.io 패키지의 File
클래스를 대체하기 위해 정의된 인터페이스이다.
java.nio.file.Path

이는 경로를 표현하기 위한 인터페이스인데 다음 문장과 같이 Paths.get 메소드가 반환하는
'경로 정보를 담은 인스턴스'를 참조하는 참조변수 선언에 사용된다.

Path path = Paths.get("C:\\Javastudy\\PathDemo.java");

위 문장을 실행하면 path가 참조하는 인스턴스에는 절대 경로 정보 
C:\JavaStudy\PathDemo.java가 담긴다. 물론 이는 해당 파일의 존재 유무와 상관이
없으며 이 문장을 실행한다고 해서 해당 경로에 파일이 생성되는 것은 아니다.
위와 관련된 코드

public class PathDemo {
    public static void main(String[] args) {
        Path pt1 = Paths.get("C:\\JavaStudy\\PathDemo.java");
        Path pt2 = pt1.getRoot();
        Path pt3 = pt1.getParent();
        Path pt4 = pt1.getFileName();

        System.out.println("Absoulute : "+ pt1);
        System.out.println("Rood : "+pt2);
        System.out.println("Parent : " + pt3);
        System.out.println("File : "+pt4);
    }
}



Absoulute : C:/JavaStudy/PathDemo.java
Rood : C:
Parent : C:/JavaStudy
File : PathDemo.java
----------------------------------------------------------------------

위 코드에서 다음 메소드들을 호출하여 먼저 생성한 경로 정보를 기반으로 해당 정보의
루트, 부모, 파일 정보를 담은 인스턴스를 생성하였다.(메소드의 반환형은 Path)
Path getRoot() // 루트 디렉토리 반환
Path getParent() //부모 디렉토리 반환
Path getFileName() //파일 이름 반환
상대 경로를 가지고 메소드 호출 "현재 디렉토리"의 정보를 출력하는 코드

public class CurrentDir {
    public static void main(String[] args) {
        Path cur = Paths.get(""); //현재 디렉토리 정보 담긴 인스턴스 생성
        String cdir;

        if(cur.isAbsolute())
            cdir = cur.toString();
        else
            cdir = cur.toAbsolutePath().toString();

        System.out.println("Current dir: "+ cdir);
    }
}

'현재 디렉토리 출력'

-----------------------------------------------------------------------

Paths.get 메소드에 빈 문자열을 전달하면 현재 디렉토리 정보가 담긴 인스턴스가 생성된다.

Path cur = Paths.get("");

'현재 디렉토리'정보를 상대 경로로 형태로 담은 인스턴스가 반환되나 담겨 있는 정보가
절대 경로 정보인지 묻는 다음 메소드에는 false이다.

boolean isAbsolute();	//정보가 절대 경로인가?

따라서 '현재 디렉토리'의 위치를 절대 경로 기준으로 출력하려면 절대 경로로 수정하는 과정을
거쳐야 하는데 사용되는 메소드는 다음과 같다.

Path toAbsolutePath()	//절대 경로 정보로 수정된 인스턴스 생성 및 반환

파일 및 디렉토리의 생성과 소멸

파일 또는 디렉토리 정보를 담은 인스턴스를 생성했따면 다음 메소드 호출을 통해서 해당 파일
또는 디렉토리의 생성을 명령할수 있다.

public static Path createFile(Path path, FileAttribute<?>...attrs)
			throws IOException
-> 지정한 경로에 빈 파일 생성, 경로가 유효하지 않거나 파일이 존재하면 예외 발생

public static Path createDIrectory(Path path, FileAttribute<?>...attrts)
			throws IOException
-> 지정한 경로에 디렉토리 생성, 경로가 유효하지 않으면 예외 발생

public static Path creaDirectories(Path dir, FileAttribute<?>...attrs)
			throws IOException
-> 지정한 경로에 모든 디렉토리 생성

관련된 코드

public class MakeFileAndDir {
    public static void main(String[] args) throws IOException {
        Path fp = Paths.get("C:\\JavaStudy\\empty.txt");
        fp = Files.createFile(fp);  //파일 생성
        
        Path dp1 = Paths.get("C:\\JavaStudy\\Empty");
        dp1 = Files.createDirectory(dp1);   //디렉토리 생성
        
        Path dp2 = Paths.get("C:\\JavaStudy2\\Empty");
        dp2 = Files.createDirectories(dp2); //경로에 모든 디렉토리 생성

        System.out.println("Files : "+fp);
        System.out.println("Dir1 : "+ dp1);
        System.out.println("Dir2 : "+ dp2);
    }
}


File : CJavaStudy₩empty.txt
Dir1 : C:JavaStudyEmpty
DIr2 : C:JavaSyudy2Empty

-------------------------------------------------------------------
위 코드의 실행 결과는 다음 두가지 상황을 만족하여 정상적으로 실행이 된 상황이다

C:\JavaStudy 디렉토리가 존재한다.
C:\JavaStudy\empty.txt 파일이 존재하지 않았다.

만약에 C:\JavaStudy 디렉토리가 존재하지 않았따면 createFIle, createDirectory
메소드 호출은 예외의 발생으로 이어진다. 그리고 해당 경로에 empty.txt 파일이 존재하는
경우에도 createFIle 메소드 호출은 예외로 이어진다.
반면 createDirectories 메소드는 전달된 경로의 디렉토리를 모두 생성하기 때문에
위의 이유로 예외가 발생하지 않는다.

파일을 대상으로 하는 간단한 입력 및 출력

I/O 스트림을 기반으로 입출력을 진행하는 방법도 있다. 그러나 지금 설명하는 방법은
이에 비해 매우 단순하고 간단한 방법이다. 따라서 입출력할 데이터의 양이 적고 성능이 문제되지
않는 경우에 한대 이 방법을 사용해야 한다.

Java.nio.file.Files에 정의된 입출력 메소드들
public static byte[] readAllBytes(Path path) throws IOException
public static Path write(Path path,byte[] bytes, OpenOption...options)
			throws IOException
            
위와 관련된 코드

public class SimpleBinWriteRead {
    public static void main(String[] args) throws IOException {
        Path fp = Paths.get("C:\\JavaStudy\\simple.bin");
        
        //파일 생성 파일이 존재하면 예외 발생
        fp = Files.createFile(fp);
        
        byte buf1[] = {0x13,0x14,0x15};  //파일에 쓸 데이터
        
        for(byte b : buf1)  //저장할 데이터의 출력을 위한 반복문
            System.out.print(b + "\t");
        System.out.println();
        
        //파일에 데이터 쓰기
        Files.write(fp,buf1, StandardOpenOption.APPEND);
        
        //파일로 부터 데이터 읽기
        byte buf2[] = Files.readAllBytes(fp);
        
        for(byte b : buf2)  //읽어 들인 데이터의 출력을 위한 반복문
            System.out.print(b + "\t");
        System.out.println();
    }
}


19	20	21
19	20	21

-----------------------------------------------------------------------
위 코드에서 파일에 데이터를 저장하는 문장은 다음과 같다.
이 문장이 실행되면 fp가 지시하는 파일에 배열 buf1의 데이터가 전부 저장된다(일부X)

Files.write(fp, buf1, StandardOpenOPtion.APPEND);

위 메소드를 호출하면 자동으로 파일이 열리고 닫힌다. 따라서 열고 닫는 과정이 필요가 없다.
문장에서 전달한 옵션과 추가 옵션들(옵션 전달받는 매개변수가 가변인자로 되어있어 여러개 가능)

APPEND			'파일의 끝에 데이터를 추가한다'
CREATE			'파일이 존재하지 않으면 생성한다'
CREATE_NEW		'새 파일을 생성한다 이미 파일이 존재하면 예외 발생'
TURNCATE_EXISTING	'쓰기 위해 파일을 여는데 파일이 존재하면 파일의 내용을 덮어씀'

위의 옵션들은 java.nio.file.StandardOpenOption에 선언되어 있음
그리고 write 메소드를 호출하면서 옵션을 하나도 전달하지 않으면 다음 두 옵션이 기본이 된다.

CREATE, TURNCATE_EXISTING

즉 옵션을 전달하지 않으면 새 파일이 생성된다 따라서 같은 이름의 파일이 있으면
그 파일의 내용을 덮어쓴다.

byte buf2[] = Files.readALLBytes(fp);

데이터를 읽어 들일 배열을 마련할 필요도 없다 배열을 생성해서 파일의 모든 내용을 저장하고
그 배열의 참조 값을 반환하기 떄문.
문자 데이터를 입출력하는 메소드 이들도 java.nio.file.FIles에 정의되어 있다.
그리고 위에서 설명한 옵션의 종류와 내용은 아래의 write 메소드에도 그대로 적용이 된다.

public static List<String> readAllLines(Path path) throws IOException

public static Path write(Path path, Iterable<? extends CharSequence>
		lines, OpenOption...options) throws IOException
        
write 메소드의 경우 두번째 매개변수 선언이 조금 생소하지만 다음 사실을 알면
매개변수의 선언이 의미하는 바를 이해할수있다.

Iterable<E> 인터페이스를 Collection<E> 인터페이스가 상속한다.
CharSequence 인터페이스를 String 클래스가 구현한다.

즉 다음과 같이 '문자열을 저장한(String 인스턴스를 저장한) 컬렉션 인스턴스'가 두번째
매개변수의 인자로 올 수 있다는 의미이다.

String st1 = "One Simple String";
String st2 = "Two Simple String"

List<String> lst = Arrays.asList(st1, st2); //write 메소드의 두번째 인자로 전달가능.

위와 관련된 코드

public class SimpleTxtWriteRead {
    public static void main(String[] args) throws IOException {
        Path fp = Paths.get("C:\\JavaStudy\\simple.txt");
        String st1 = "One Simple String";
        String st2 = "Two Simple String";

        List<String>  lst1 = Arrays.asList(st1,st2);

        Files.write(fp,lst1);   //파일에 문자열 저장하기
        List<String> lst2 = Files.readAllLines(fp); //파일로부터 문자열 읽기
        System.out.println(lst2);
    }
}


[One Simple String, Two SImple String]

-------------------------------------------------------------------------

위의 예제에서는 write 메소드 호출시 옵션을 전달하지 않았기 때문에 다음 두 옵션이 기본으로
설정된다
CREATE, TRUNCATE_EXISTING

파일 및 디렉토리의 복사와 이동

파일 및 디렉토리의 복사와 이동은 자주 사용하는 기능은 아니다 따라서 Files 클래스에서는
다음 두 메소드를 정의하고 있다.(파일 + 디렉토리 대상으로도 전부 가능)

public static Path copy(Path source, Path target, CopyOption...options)
		...throws IOException

public static Path move(Path source, Path target, CopyOption...options)
		...throws IOException
        
copy 메소드에 옵션은

REPLACE_EXISTING	 '이미 파일이 존재한다면 해당 파일을 대체한다.'
COPY_ATTRIBUTES		 '파일의 속성까지 복사를 한다.'

이에 관련된 코드

public class CopyFileFromFiles {
    public static void main(String[] args) throws IOException {
        Path src = Paths.get("C:\\JavaStudy\\CopyFileFromFiles.java");
        Path dst = Paths.get("C:\\JavaStudy\\CopyFileFromFiles2.java");

        //src가 지시하는 파일을 tst가 지시하는 위치와 이름으로 복사
        Files.copy(src,dst, StandardCopyOption.REPLACE_EXISTING);
    }
}

위의 코드를 컴파일 및 실행하면 C:\JavaStudy\CopyFileFromFiles.java를 같은 경로에
CopyFileFromFiles2.java라는 이름으로 복사한다.
만약 CopyFileFromFiles2.java가 존재하는 상황에서 실행하면
이 파일을 대체하면서 복사가 진행된다 옵션으로 REPLACE_EXISTING를 전달했기 때문.

----------------------------------------------------------------------
move 메소드의 호출 코드

public class MoveFileFromFiles {
    public static void main(String[] args) throws IOException {
        Path src = Paths.get("C:\\JavaStudy\\Dir1");
        Path dst = Paths.get("C:\\JavaStudy\\Dir2");
        
        // src가 지시하는 디렉토리를 dst가 지시하는 디렉토리로 이동
        Files.move(src,dst, StandardCopyOption.REPLACE_EXISTING);
    }
}

위 코드의 move 메소드 호출 결과로
C:\JavaStudy\Dir1 -> C:\JavaStudy\Dir2 로 이동한다.
이동의 의미를 보충하면 Dir1의 모든 파일과 디렉토리를 통째로 옮긴 다음 이름을 Dir2로 
수정하라는 뜻. 따라서 Dir1은 사라진다.
그리고 Dir1의 모든 내용은 Dir2에 존재하게된다.

NIO.2 기반의 I/O 스트림 생성

I/O 스트림 생성 방법 당시에는 new를 통한 인스턴스의 생성으로 스트림을 생성하였다.
그런데 NIO.2 Files 클래스에서는 스트림의 생성을 위한 메소드를 별도로
제공하고 있어 보다 간단하게 스트림을 생성할수 있다.

바이트 스트림의 생성

I/O 바이트 입력 스트림의 생성 방법은 다음과 같았다.
InputStream in = new FileInputStream("data.dat");

NIO.2에서는 다음과 같은 방법으로 스트림 생성을 제안한다.
Path fp = Paths.get("data.dat");
InputStream in Files.newInputStream(fp);

이렇듯 스트림의 생성 방법에는 차이가 있지만 생성된 스트림에는 차이가 없다.

NIO.2 방법으로 데이터 내보내기 코드

public class DataFilterOutputStreamNewVer {
    public static void main(String[] args) {
        Path fp = Paths.get("data.dat");
        
        try(DataOutputStream out = 
                new DataOutputStream(Files.newOutputStream(fp))){
            out.writeInt(370);
            out.writeDouble(3.14);
        }catch (IOException e){
            e.printStackTrace();           
        }
    }
}

---------------------------------------------------------------------------
NIO.2 방법으로 데이터 읽어 들이는 코드

public class DataFilterInputStreamNewVer {
    public static void main(String[] args) {
        Path fp = Paths.get("data.dat");
        
        try(DataInputStream in = 
                new DataInputStream(Files.newInputStream(fp))){
            int num1 = in.readInt();
            double num2 = in.readDouble();

            System.out.println(num1);
            System.out.println(num2);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

370
3.14

문자 스트림의 생성

I/O 버퍼 스트림이 연결된 문자 스트림의 생성 방법은 다음과 같았다.

BufferedWriter bw = new BufferedWriter(new FileWriter("String.txt"));

NIO.2에서는 위 문장을 통째로 대신하는 newBufferedWriter 메소드를 제공하기 때문에
다음과 같이 버퍼링 기능이 있는 문자 스트림을 바로 생성할수 있다.

Path fp = Paths.get("String.txt");
BufferedWriter bw = Files.newBufferedWriter(fp); //버퍼링 하는 문자 출력 스트림 생성

NIO.2 방법으로 데이터 내보내기 코드

public class StringWriterNewVer {
    public static void main(String[] args) {
        String ks = "공부에 있어서 돈이 꼭 필요한 것은 아니다.";
        String es = "Life is long if you know hot to use it";
        Path fp = Paths.get("String.txt");
        
        try(BufferedWriter bw = Files.newBufferedWriter(fp)){
            bw.write(ks,0,ks.length());
            bw.newLine();
            bw.write(es,0,es.length());
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

-----------------------------------------------------------------------------
NIO.2 방법으로 생성되는 파일로부터 데이터를 읽어서 출력하는 코드

public class StringReaderNewVer {
    public static void main(String[] args) {
        Path fp = Paths.get("String.txt");
        
        try(BufferedReader br = Files.newBufferedReader(fp)){
            String str;
            while(true){
                str = br.readLine();
                if(str == null)
                    break;
                System.out.println(str);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}


공부에 있어서 돈이 꼭 필요한 것은 아니다.
Life is long if you know hot to use it

NIO 기반의 입출력

NIO의 채널과 버퍼

I/O 모델에서는 스트림을 생성하여 입력 및 출력을 진행하였다.
NIO에서는 스트림을 대신해서 '채널'이라는 것을 생성한다. 그리고 다음 관점에서 스트림과 

채널은 유사하다
"스트림도 채널도 데이터의 입출력을 위한 통로가 된다"

그러나 채널에는 다음 특징이 있다.
"스트림은 한 방향으로만 데이터가 이동하지만 채널은 양방향으로 데이터 이동이 가능하다."

스트림은 입력 스트림과 출력 스트림이 구분된다. 따라서 쓰면서 동시에 읽는 것도 가능한
스트림을 생성할수 없다. 

그러나 채널은 하나의 채널을 대상으로 읽고 쓰는 것이 가능하다. 반면 채널은 다음과 같은
제약 사항이 존재한다

"채널은 반드시 버퍼에 연결해서 사용해야 한다."

우리는 스트림 생성후 성능 향상을 위해 필터 스트림인 버퍼 스트림을 연결한적이 있다.
그리고 버퍼 스트림의 연결은 선택이였지만

NIO에서는 채널에 직접 데이터를 쓰고 읽는 것을 허용하지 않는다. 
반드시 채널에 버퍼를 연결해서 버퍼를 대상으로 쓰고 읽는것을 요구한다.
파일을 대상으로 하는 채널의 데이터 출력 경로
데이터 -> 버퍼 -> 채널 -> 파일

파일을 대상으로 하는 채널의 데이터 입력 경로

파일 -> 채널 -> 버퍼 -> 데이터

위와 관련된 코드

public class FileCopierVerNIO {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("대상 파일 : ");
        Path src = Paths.get(sc.nextLine());

        System.out.print("사본 이름 : ");
        Path dst = Paths.get(sc.nextLine());
        
        //하나의 버퍼 생성
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        //try에서 두 개의 채널 생성
        try(FileChannel ifc = FileChannel.open(src, StandardOpenOption.READ);
        FileChannel ofc = FileChannel.open(dst,StandardOpenOption.WRITE,
                                            StandardOpenOption.CREATE)){
            int num;
            while(true){
                num = ifc.read(buf);    //채널 ifc에서 버퍼로 읽어 들임
                if(num == -1)   //읽어 들일 데이터가 없다면
                    break;
                buf.flip(); //모드 변환
                ofc.write(buf); //버퍼에서 채널 ofc로 데이터 전송
                buf.clear();    //버퍼 비우기
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

실행 결과
대상 파일 : FileCopierVerNIO.java
사본 이름 : copy.java
-------------------------------------------------------------------------

위 코드에서 다음 문장을 통해 버퍼를 생성하였다. 그리고 이것이 NIO의 버퍼를 생성하는 방법.
(인자로 전달된 크기의 버퍼가 생성됨)

ByteBuffer buf = ByteBuffer.allocate(1024);

이어서 두개의 채널을 생성하였다.(옵션 CREATE이 전달되면 해당 파일이 존재하지 않을경우 생성)

// src가 지시하는 파일의 내용을 읽기 위한 입력용 채널 생성
FileChannel ifc = FileChannel.open(src, StandardOpenOption.READ);

// dst가 지시하는 파일에 내용을 쓰기 위한 출력용 채널 생성
FileChannel ofc = FileChannel.open(dst,StandardOpenOption.WRITE,
                                            StandardOpenOption.CREATE))
                                            
이렇게 해서 버퍼와 채널을 생성하였다. 그리고 데이터를 입출력할 준비가 모두 되었다.
I/O스트림과 달리 NIO에서는 버퍼와 채널의 연결 과정을 거치지 않으며 이 둘은 각각 독립적으로
존재한다. 그리고 위코드에서는 채널 둘, 버퍼 하나를 생성했는데 이는 파일 복사 프로그램이기
때문이다.

코드에서는 파일 복사를 위해 다음 메소드 호출을 통해서 복사할 내용을 버퍼로 읽어들인다.

num = ifc.read(buf);	//채널 ifc로부터 데이터 버퍼 buf로 읽어들임

buf.flip(); //버퍼에 저장된 데이터를 읽을수 있는 상태로 변경

ofc.write(buf); //버퍼에 저장된 데이터를 채널 ofc로 전송

모든 데이터를 복사할 떄까지 이 과정을 반복하게 된다.
그런데 버퍼에 데이터를 읽어 들였다고 해서 버퍼가 비워지는 것은 아니다.
때문에 한 차례의 복사 과정을 거치고 나면 다음 문장중 하나를 실행해서 버퍼를 비워줘야한다.

buf.clear();	//버퍼를 완전히 비운다
buf.compact();	//버퍼에 저장된 내용 중에서 읽은 데이터만 지운다.

버퍼를 비우는 과정을 생략해도 컴파일과 실행은 되지만 버퍼에 데이터가 누적이 되어 정상적인
복사의 결과를 확인할수 없게된다.

성능 향상 포인트는 어디에?

성능 향상의 포인트는 일단 효율적인 버퍼링에서 찾을 수 있다. 기존 IO 모델을 기반으로
파일 복사 프로그램을 작성하려면 입력 스트림, 출력 스트림 각각에 버퍼 스트림을 연결해야 했다.
그리고 버퍼에 저장된 데이터를 출력 버퍼로 이동하는 '버퍼 사이의 데이터 이동 과정'을
반드시 거쳐야 했다. 그러나 이러한 작업을 NIO는 생략할수 있다.

ByteBuffer buf = ByteBuffer.allocate(1024) // Non-direct 버퍼

다음과 같이 Direct버퍼를 생성하여 성능의 향상을 기대할수 있다.
(호출하는 메소드만 바꾸면 Direct 버퍼 생성 가능)

ByteBuffer buf = ByteBuffer.allocateDirect(1024) //Direct 버퍼 생성

Non-direct버퍼
파일 -> 운영체제 버퍼 -> 가상머신 버퍼 -> 실행중인 자바 프로그램

Direct 버퍼를 활용하면 과정이 다음과 같이 줄어든다.

파일 -> 운영체제 버퍼 -> 실행중인 자바프로그램

이렇듯 중간 과정하나가 생략되므로 성능 향상을 기대할수 있다.
그러나 Direct 버퍼의 할당과 해제에 드는 시간적 비용이 Non-direct버퍼에 다소 높기 때문에
입출력할 파일의 크기가 크지 않거나 버퍼를 빈번히 할당하고 해제해야 하는 상황이라면
Non-direct버퍼를 이용해서 입출력을 하는것이 빠를수 있다.

그리고 다양한 데이터의 입출력을 위해 ByteBuffer 이외에도 기본 자료형 버퍼 클래스가
정의되어 있다. 그러나 Direct 버퍼는 ByteBuffer 또는 그 하위 클래스를 대상으로만 생성됨

charBuffer, IntBuffer, DoubleBuffer (버퍼 클래스 중 일부)

파일 랜덤 접근

120 240 0.94 0.75 기본형 데이터가 저장되어있다
이 상황에서 NIO 파일 랜덤 접근을 통해(양방향 데이터 이동)
120 0.94 0.75 240의 순으로 읽어서 출력이 가능하다 

이와 관련된 코드

public class FileRandomAccess {
    public static void main(String[] args) {
        Path fp = Paths.get("data.dat");

        //버퍼 생성
        ByteBuffer wb = ByteBuffer.allocate(1024);

        //버퍼에 데이터 저장
        wb.putInt(120); //int형 데이터 저장
        wb.putInt(240);
        wb.putDouble(0.94); //double형 데이터 저장
        wb.putDouble(0.75);

        //하나의 채널 생성
        try(FileChannel fc = FileChannel.open(fp,
                StandardOpenOption.CREATE,
                StandardOpenOption.READ,
                StandardOpenOption.WRITE)){
            //파일에 쓰기
            wb.flip();
            fc.write(wb);

            //파일로부터 읽기
            ByteBuffer rb = ByteBuffer.allocate(1024);  //버퍼 생성
            fc.position(0); //채널의 포지션을 맨 앞으로 이동
            fc.read(rb);

            //이하 버퍼로부터 데이터 읽기
            rb.flip();
            System.out.println(rb.getInt());
            rb.position(Integer.BYTES * 2); //버퍼의 포지션 이동
            System.out.println(rb.getDouble());
            System.out.println(rb.getDouble());

            rb.position(Integer.BYTES); //버퍼의 포지션 이동
            System.out.println(rb.getInt());
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}


120
0.94
0.75
240

---------------------------------------------------------------------------

위 코드에서 두개의 버퍼와 나의 채널을 생성하였다. 두개의 버퍼중 하나는 다음의 목적을 갖는다
데이터 -> 버퍼 -> 채널 -> 파일

즉 버퍼에 데이터를 담은 후 그 내용을 파일에 전달하기 위한 것이다. 반면 다른 하나는 다음의 
목적을 갖는다.
파일 -> 채널 -> 버퍼2 -> 출력

즉 파일에 저장된 데이터를 출력하는 목적을 갖는다.
이렇듯 목적은 다르지만 둘 다 데이터를 담았다가 내보내는 과정을 거치기 떄문에 중간에 각
버퍼를 대상으로 flip 메소드를 호출해야 한다. 
그리고 다음 메소드 호출을 통해서 기본 자료형 데이터를 입출력 하고있다.

wb.putInt(120); //int형 데이터 저장 
wb.putDouble(0.94); //double형 데이터 저장

rb.getInt();	//int형 데이터 꺼내기
rb.getDouble();	//double형 데이터 꺼내기

(FileChannel fc = FileChannel.open(fp, StandardOpenOption.CREATE,
                StandardOpenOption.READ, StandardOpenOption.WRITE))
                
위 코드는 하나의 채널을 생성하는 문장인데 READ,WRITE옵션이 모두 전달되었다.
따라서 이 채널을 대상으로는 데이터의 입출력이 모두 가능하다.

파일 랜덤 접근의 핵심인 '포지션'에 대해 알아보자
그 대상이 채널이건 버퍼건 어느 위치까지 데이터를 썼는지 어느 위치까지 데이터를 읽었는지
표시하기 위해서'포지션'이라는 위치 정보자 유지된다.
먼저 버퍼를 대상으로 포지션에 대한 설명으로 버퍼를 생성하는 순간 포지션은 0이다

ByteBuffer wb = ByteBuffer.allocate(1024);	//포지션 0

그리고 다음과 같이 4바이트 데이터를 저장할 때마다 포지션은 4씩 증가한다

wb.putInt(120);	//메소드 호출후 포지션 4
wb.putInt(240);	//메소드 호출후 포지션 9

그럼 이어 버퍼에서 데이터를 읽기 위해 flip 메소드를 호출하면 포지션은 0이 된다
(그래야 먼저 저장된 데이터부터 읽기 떄문)

wb.flip();	//메소드 호출후 포지션 0

그리고 버퍼에서 데이터를 읽기 시작하면 읽은 바이트 수만큼 포지션은 증가한다.

rb.position(Integer.BYTES * 2);
위 문장을 실행하면 포지션 0에서 부터 두 개의 정수를 건너뛴 위치로 포지션이 이동한다.

버퍼의 포지션과 마찬가지로 채널도 포지션 정보를 유지한다. 따라서 채널을 통해서
파일에 데이터를 전송하면 포지션 값이 전송한 바이트 수만큼 증가한다.
그래서 랜덤 파일 접근 코드에서 채널을 통해 파일에 데이터를 전송한 이후에
파일에 저장된 데이터를 앞에서부터 읽기 위해 채널의 포지션을 0으로 초기화 하였다.

fc.position(0);


-----------------------------------------------------------------------------

포지션의 대한 코드

public class BufferPositionCheck {
    public static void main(String[] args) {
        ByteBuffer bb = ByteBuffer.allocate(1024);
        System.out.println("Position : "+bb.position());
        bb.putInt(120);
        System.out.println("Position : "+bb.position());
        bb.putDouble(0.94);
        System.out.println("Position : "+bb.position());

        bb.flip();
        
        System.out.println("Position : "+bb.position());
        int n = bb.getInt();
        System.out.println("Position : "+bb.position());
        double d = bb.getDouble();
        System.out.println("Position : "+bb.position());
    }
}

Position : 0
Position : 4
Position : 12
Position : 0
Position : 4
Position : 12

profile
반갑습니다

0개의 댓글