Memory-Mapped File은 파일의 내용을 프로세스의 가상 메모리 주소 공간에 매핑하는 기술입니다. 운영체제가 파일을 메모리처럼 다룰 수 있게 해주어, 디스크 I/O 작업을 메모리 접근처럼 처리할 수 있습니다.
Java는 java.nio 패키지를 통해 기본적으로 MMF를 지원합니다. 별도의 라이브러리 설치가 필요 없습니다.
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
public class MMFReadExample {
public static void main(String[] args) {
String filePath = "example.txt";
try (RandomAccessFile file = new RandomAccessFile(filePath, "r");
FileChannel channel = file.getChannel()) {
// 파일 전체를 메모리에 매핑 (읽기 전용)
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY,
0,
channel.size()
);
// 데이터 읽기
byte[] data = new byte[(int) channel.size()];
buffer.get(data);
String content = new String(data, StandardCharsets.UTF_8);
System.out.println("파일 내용: " + content);
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
public class MMFWriteExample {
public static void main(String[] args) {
String filePath = "output.txt";
String content = "Memory-Mapped File로 쓰는 내용입니다!";
try (RandomAccessFile file = new RandomAccessFile(filePath, "rw");
FileChannel channel = file.getChannel()) {
byte[] data = content.getBytes(StandardCharsets.UTF_8);
// 파일을 데이터 크기만큼 메모리에 매핑 (읽기/쓰기)
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_WRITE,
0,
data.length
);
// 데이터 쓰기
buffer.put(data);
buffer.force(); // 강제로 디스크에 flush
System.out.println("파일 쓰기 완료!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class LargeFileProcessing {
private static final int CHUNK_SIZE = 100 * 1024 * 1024; // 100MB 청크
public static void processLargeFile(String filePath) {
try (RandomAccessFile file = new RandomAccessFile(filePath, "r");
FileChannel channel = file.getChannel()) {
long fileSize = channel.size();
long position = 0;
while (position < fileSize) {
long remaining = fileSize - position;
long size = Math.min(CHUNK_SIZE, remaining);
// 청크 단위로 메모리 매핑
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY,
position,
size
);
// 데이터 처리
processChunk(buffer);
position += size;
}
System.out.println("대용량 파일 처리 완료!");
} catch (Exception e) {
e.printStackTrace();
}
}
private static void processChunk(MappedByteBuffer buffer) {
// 여기서 실제 데이터 처리 로직 구현
// 예: 바이트 단위로 읽기
while (buffer.hasRemaining()) {
byte b = buffer.get();
// 처리 로직...
}
}
}
고성능 Key-Value 저장이 필요할 때 사용하는 오픈소스 라이브러리입니다.
<dependency>
<groupId>net.openhft</groupId>
<artifactId>chronicle-map</artifactId>
<version>3.24ea3</version>
</dependency>
implementation 'net.openhft:chronicle-map:3.24ea3'
import net.openhft.chronicle.map.ChronicleMap;
import java.io.File;
public class ChronicleMapExample {
public static void main(String[] args) {
File file = new File("chronicle-map.dat");
try (ChronicleMap<String, String> map = ChronicleMap
.of(String.class, String.class)
.name("user-map")
.entries(1_000_000)
.averageKeySize(10)
.averageValueSize(100)
.createPersistedTo(file)) {
// 데이터 쓰기
map.put("user1", "홍길동");
map.put("user2", "김철수");
// 데이터 읽기
String value = map.get("user1");
System.out.println("Value: " + value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
컬럼 기반 인메모리 데이터 포맷으로, 대용량 데이터 분석에 최적화되어 있습니다.
<dependency>
<groupId>org.apache.arrow</groupId>
<artifactId>arrow-memory-netty</artifactId>
<version>14.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.arrow</groupId>
<artifactId>arrow-vector</artifactId>
<version>14.0.1</version>
</dependency>
| 방식 | 읽기 속도 | 쓰기 속도 | 사용 사례 |
|---|---|---|---|
| 일반 FileInputStream | 보통 | 보통 | 작은 파일, 순차 읽기 |
| BufferedInputStream | 빠름 | 빠름 | 중간 크기 파일 |
| Memory-Mapped File | 매우 빠름 | 매우 빠름 | 대용량 파일, 랜덤 액세스 |
| Chronicle Map | 매우 빠름 | 매우 빠름 | Key-Value 저장소 |
// 읽기 전용: FileChannel.MapMode.READ_ONLY
// 읽기/쓰기: FileChannel.MapMode.READ_WRITE
// 쓰기 전용 (copy-on-write): FileChannel.MapMode.PRIVATE
// MappedByteBuffer 명시적 정리 (Java 9+)
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public static void unmap(MappedByteBuffer buffer) {
try {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
unsafe.invokeCleaner(buffer);
} catch (Exception e) {
e.printStackTrace();
}
}
대용량 파일은 한 번에 매핑하지 말고 청크 단위로 나눠서 처리하세요.
private static final int CHUNK_SIZE = 128 * 1024 * 1024; // 128MB
데이터 무결성이 중요한 경우 force() 메서드로 즉시 디스크에 쓰기를 보장하세요.
buffer.force(); // 버퍼의 변경사항을 즉시 디스크에 반영
FileChannel과 RandomAccessFile을 반드시 닫아야 합니다 (try-with-resources 권장).MappedByteBuffer에 접근할 때는 동기화가 필요합니다.