반응형
RandomAccessFile 을 통한 일정 사이즈만큼의 바이트 배열 처리
최근 회사에서 MS OneDrive & Sharepoint에서 제공하는 REST API를 이용하여 서비스를 만드는 프로젝트를 진행했다. 해당 프로젝트에서 개발한 기능들중, 일정 크기 이상의 파일을 OneDrive 에 업로드 하는 기능을 개발하였는데, 이 때 RandomAccessFile 을 이용하여 파일을 chunk 단위로 byte array 채로 전송하는 기능을 개발하였다. 파일을 읽을 때 RandomAccessFile 은 처음 써봤기 때문에 정리를 할겸, 오랜만에 포스팅을 해본다.
BufferedReader 와의 간단 비교
RandomAccessFile 사용 예시들을 웹 상에서 찾다보니, BufferedReader와의 비교 글들도 볼 수 있게 되었다. 이 두 가지 클래스를 비교하는 주요 이유로는 아래와 같았다.
- BufferedReader
- 장점: 사용하기 쉬움
- InputStreamReader 를 이용하여 Charset 처리가 용이함 => String 으로 변환시에 용이
- 단점: BufferedReader 에서 제공하는 skip() 의 처리 방식으로 인해 메모리가 낭비될 수 있음
- skip() 은 실제로 BufferedReader 로 읽은 파일에서 어디를 읽고 있는지에 대한 위치 정보(pointer)를 바탕으로 건너뛰게 해주는 목적으로 존재하는 method다.
- 하지만 실제로 BufferedReader 의 skip()이 어떻게 구현이 되어있는지 실제 내용을 보면 건너 뛸 때에 대한 데이터를 버퍼에 넣어두고(메모리에 올려두고) 건너뛰고 있다. => BufferedReader.skip() 안에서 fill() 이라는 메소드를 호출하고, System.arraycopy 를 사용하여 데이터를 버퍼에 넣어주고 있음
- 장점: 사용하기 쉬움
- RandomAccessFile
- 장점
- BufferedReader 의 skip() 과 동일한 기능을 제공하는 RandomAccessFile의 seek()은 pointer 만을 이동시킴으로써 건너 뛰는 데이터들을 메모리에 올리지 않는다.
- getChannel() 함수를 통해서 FileChannel을 얻을 수 있어 NIO 방식의 File IO 지원이 가능하다.
- 단점
- 성능이 떨어지고 한글 처리가 어렵다. byte array를 그대로 전달하는 측면에서는 해당 단점이 구현하는 입장에서는 상관없지만, readLine() 을 통해서 데이터를 읽을 경우에는 인코딩 타입이 ISO-8859-1 이 되기 때문에 추가적인 인코딩 처리가 필요하다. 예를 들어 UTF-8 방식의 인코딩으로 처리를 하기 위해서는 아래와 같이 처리를 해줘야 하기 때문에 성능이 다소 떨어진다.
public class Test { public void doTest() { File targetFile = new File("/dev/test/test.txt"); try(RandomAccessFile reader = new RandomAccessFile(targetFile, "r")) { String line = new String(reader.readLine().getBytes("ISO-8859-1"), "UTF-8"); ... } catch(FileNotFoundException e) { ... } catch(IOException e) { ... } } }
- 성능이 떨어지고 한글 처리가 어렵다. byte array를 그대로 전달하는 측면에서는 해당 단점이 구현하는 입장에서는 상관없지만, readLine() 을 통해서 데이터를 읽을 경우에는 인코딩 타입이 ISO-8859-1 이 되기 때문에 추가적인 인코딩 처리가 필요하다. 예를 들어 UTF-8 방식의 인코딩으로 처리를 하기 위해서는 아래와 같이 처리를 해줘야 하기 때문에 성능이 다소 떨어진다.
- 장점
위와 같이 BufferedReader 와 RandomAccessFile 의 장단점을 파악하고, 어플리케이션의 혹시 모를 OOM 방지를 위해 아래와 비슷한 느낌으로 구현하였다. (HTTP Header 에 들어가는 Content-Range는 생략하였다)
byte[] data = null;
File fileToUpload = new File(".../test.txt");
long fileByteSize = fileToUpload.length();
try(RandomAccessFile randomAccessFile = new RandomAccessFile(fileToUpload, "r"))
{
int chunkSize = 320 * 1024; //320KB
// reference: https://beautifulkim.tistory.com/302
long loopTotalCount = fileByteSize / chunkSize + (fileByteSize % chunkSize == 0 ? 0 : 1);
int loopCount = 1;
long startPoint = 0;
while(loopCount < loopTotalCount) {
startPoint = i * chunkSize;
data = new byte[chunkSize];
randomAccessFile.seek(i * chunkSize);
randomAccessFile.read(data);
api.put(data); // PUT(HTTP METHOD) 방식으로 data 실어서 전송
loopCount++;
}
// 마지막 데이터 처리
int lastSize = fileByteSize - startPoint;
data = new byte[lastSize];
randomAccessFile.seek(loopCount * chunkSize);
randomAccessFile.read(data);
api.put(data)/ // PUT(HTTP METHOD) 방식으로 data 실어서 전송
}
catch (FileNotFoundException e)
{
...
}
catch (IOException e)
{
...
}
Reference
- RandomAccessFile과 BufferedReader의 한계점과 해결 방안 : https://blog.daum.net/oiztlomo/4609507
반응형
'개발 지식 > Java' 카테고리의 다른 글
[Java] 일자 계산 LocalDateTime 사용법 (0) | 2020.09.21 |
---|---|
[Java] Lombok 정리 (0) | 2020.07.02 |
[JAVA] JAR WAR 차이 (0) | 2020.06.27 |
[Java] ParameterizedPreparedStatementSetter setValues해야 하는 컬럼이 많다면? (0) | 2020.06.16 |
[JAVA] Disruptor, Multiple Worker Threads 비교 (0) | 2020.05.09 |