본문 바로가기

개발 지식/Java

RandomAccessFile 을 통한 일정 사이즈만큼의 바이트 배열 처리

반응형

RandomAccessFile 을 통한 일정 사이즈만큼의 바이트 배열 처리

최근 회사에서 MS OneDrive & Sharepoint에서 제공하는 REST API를 이용하여 서비스를 만드는 프로젝트를 진행했다. 해당 프로젝트에서 개발한 기능들중, 일정 크기 이상의 파일을 OneDrive 에 업로드 하는 기능을 개발하였는데, 이 때 RandomAccessFile 을 이용하여 파일을 chunk 단위로 byte array 채로 전송하는 기능을 개발하였다. 파일을 읽을 때 RandomAccessFile 은 처음 써봤기 때문에 정리를 할겸, 오랜만에 포스팅을 해본다.

BufferedReader 와의 간단 비교

RandomAccessFile 사용 예시들을 웹 상에서 찾다보니, BufferedReader와의 비교 글들도 볼 수 있게 되었다. 이 두 가지 클래스를 비교하는 주요 이유로는 아래와 같았다.

  1. BufferedReader
    • 장점: 사용하기 쉬움
      • InputStreamReader 를 이용하여 Charset 처리가 용이함 => String 으로 변환시에 용이
    • 단점: BufferedReader 에서 제공하는 skip() 의 처리 방식으로 인해 메모리가 낭비될 수 있음
      • skip() 은 실제로 BufferedReader 로 읽은 파일에서 어디를 읽고 있는지에 대한 위치 정보(pointer)를 바탕으로 건너뛰게 해주는 목적으로 존재하는 method다.
      • 하지만 실제로 BufferedReader 의 skip()이 어떻게 구현이 되어있는지 실제 내용을 보면 건너 뛸 때에 대한 데이터를 버퍼에 넣어두고(메모리에 올려두고) 건너뛰고 있다. => BufferedReader.skip() 안에서 fill() 이라는 메소드를 호출하고, System.arraycopy 를 사용하여 데이터를 버퍼에 넣어주고 있음
  1. 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) { 
              ... 
            } 
          } 
        }

위와 같이 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

반응형