RandomeAccessFile
보통의 자바에서는 입력과 출력이 별도로 분리되어있습니다. 그러니까 InputStream이 있고, OutputStream으로 입력받는 스트림, 그리고 출력하는 스트림이 따로 존재하잖아요? 하지만 RandomAccessFile은 예외적으로 RandomAccessFile이라는 객체 하나로 쓰기와 읽기 둘 다 할 수 있습니다. 사실 RandomAccessFile 클래스는 DataInput, 그리고 DataOutput을 둘다 implements 했기 때문에 읽기, 쓰기가 가능합니다. DataStream 중에 DataInputStream클래스가 DataInput, DataOutputStream클래스가 DataOutput을 구현했습니다.
RandomAccess 파일은 다음 데이터를 읽을지 쓸지 가리키는 Pointer가 존재합니다. 맨 처음 파일이 생성되면 가장 처음 위치인 0의 위치를 가리키고 있다가 쓰거나 읽으면 이 포인터가 다음의 위치로 옮겨가게 됩니다.
1. 데이터 쓰기 - write ~()
아래의 코드는 RandomAccessFile에 파일을 기록하는 프로그램을 구현한 건데요.
public static void main(String[] ar) throws IOException{
RandomAccessFile raf=new RandomAccessFile("raf_file.dat","rw");
System.out.println("position:" + raf.getFilePointer());
raf.writeInt(10);
System.out.println("position:" + raf.getFilePointer()); //4bytes
raf.writeChar('C');
System.out.println("position:" + raf.getFilePointer()); //2bytes
raf.writeLong(1010L);
System.out.println("position:" + raf.getFilePointer()); //8bytes
raf.writeByte(8);
System.out.println("position:" + raf.getFilePointer()); //1byte
raf.close();
}
출력을 보면 각각 기록하는 자료형에 따라서 그 포인터의 값이 증가함을 알 수 있습니다.
position:0
position:4
position:6
position:14
position:15
아래의 그림처럼 데이터가 자료형에 따라서 포인터의 다음 자료를 저장할 위치를 가리킵니다. 처음은 아무것도 기록되어 있지 않으니 0의 위치에 있고, 다음 정수형 자료를 기록했으니 다음 입력받을 위치는 4의 위치에 기록하면 됩니다.
문자열을 쓸때는 writeUTF() 메소드를 사용합니다. 여기서는 간단하게 세글자를 써보도록 하겠습니다.
//write
raf.writeUTF("RAF");
System.out.println(raf.getFilePointer());
그렇다면 결과는 3이 되어야하지 않을까요? 하지만 결과는 5가 나오게 됩니다. 그 이유는 파일에 문자열을 쓰는 것은 문제가 없는데 읽을때가 문제가 됩니다. 어디까지 읽어야하는지 알수 없게 되거든요. 그래서 앞 2바이트는 문자열의 길이를 기록합니다. 그래서 총 5바이트가 되는 것이죠.
2. Data 읽기 - read~ ()
기록을 해봤으니 이제 데이터를 읽어와볼까요? 읽어오는 메소드도 write와 같은 형태의 메소드명으로 만들어져있습니다. 만약 Int형의 자료를 읽으려면 readInt() 메소드를 사용하면 되겠죠? 아래의 코드가 읽는 코드를 구현한 것입니다.
public static void main(String[] ar) throws IOException{
RandomAccessFile raf=new RandomAccessFile("raf_file.dat","rw");
//write
raf.writeInt(10);
raf.writeChar('C');
raf.writeLong(1010L);
raf.writeByte(8);
//read
System.out.println(raf.readInt());
System.out.println(raf.readChar());
System.out.println(raf.readLong());
System.out.println(raf.readByte());
raf.close();
}
결과는 EOFException이 발생하여 프로그램이 종료됩니다. EOF(End Of File)는 파일의 마지막이라는 뜻으로 이미 끝인 파일에 read메소드를 수행해서 발생합니다.
Exception in thread "main" java.io.EOFException
at java.base/java.io.RandomAccessFile.readInt(RandomAccessFile.java:841)
at reakwon.Main.main(Main.java:17)
3. 파일 포인터(위치) 변경 - seek()
이쯤에서 위의 포인터를 그린 그림을 살짝 보면 우리는 이미 포인터가 15의 위치로 이 위치가 EOF였습니다. 그러다가 read로 그 지점부터 읽다가 보니 바로 EOF 예외가 발생하게 되었죠. 따라서 파일 포인터 위치를 맨 처음으로 되돌려야합니다. 그럴때 사용할 수 있는 메소드가 seek 메소드입니다. 여기서 0이 파일의 맨 처음을 의미합니다. 읽기 전에 seek() 메소드로 되돌려보면 정상적으로 읽히고 있음을 확인할 수 있습니다.
//... 중략 ...
raf.writeByte(8);
raf.seek(0);
//read
System.out.println(raf.readInt());
//... 중략 ...
}
10
C
1010
8
4. 파일의 크기 - length()
파일의 크기를 구하고 싶다면 length() 의 함수를 쓰면 파일의 크기를 구할 수 있습니다. 사실 파일의 크기이기도 하지만 다음 파일이 기록될 위치이기도 하며 EOF입니다. 이때 byte단위로 파일의 크기가 return 됩니다.
이상으로 RandomAccessFile에 대해서 알아보았습니다. 파일을 읽기, 쓰기 스트림 따로 두지 않고 하나의 객체를 통해서 읽기, 쓰기를 한다는 개념입니다. 이때 읽기, 쓰기에 따라 파일의 pointer가 달라진다는 점을 기억하시면 무엇인가 에러가 발생했을때 가장 처음 의심해볼 수 있습니다.
'언어 > JAVA' 카테고리의 다른 글
[자바/GUI] 테이블 Swing Component JTable 조지기 - 동적으로 추가, 삭제, 읽기 구현 (0) | 2021.04.13 |
---|---|
[자바/GUI] Swing JList(다중 선택, 단일 선택) - 동적으로 아이템 추가, 삭제 (0) | 2021.04.12 |
[자바/GUI]Event와 Event Handling의 기초 - WindowListener 다뤄보기 (0) | 2021.04.09 |
[자바] Scanner를 이용한 입력 쉽게 받는 방법 - System, 문자열, 파일에서 입력 받기 (3) | 2021.04.07 |
[자바] 직렬화(Serialization)의 개념과 객체 파일로 저장하기 예제 - ObjectOutputStream, ObjectInputStream (4) | 2021.04.07 |