2026/4/18 9:26:38
网站建设
项目流程
新闻静态网站模板下载,手机网站的优缺点,做音乐网站,网站开发体系前言
jdk21 之后#xff0c;随着 FFM 加入并稳定#xff0c;现在 java 中也可以直接使用 mmap 技术将文件直接映射进内存并读取了#xff0c;并且没有 nio 中 21 亿的限制#xff08;Integer.MAX_VALUE#xff09;。
BIO时代
try (FileInputStream fis new FileInputS…前言jdk21 之后随着 FFM 加入并稳定现在 java 中也可以直接使用 mmap 技术将文件直接映射进内存并读取了并且没有 nio 中 21 亿的限制Integer.MAX_VALUE。BIO时代try (FileInputStream fis new FileInputStream(file.txt)) { byte[] buffer new byte[8192]; // 8KB 缓冲区 int bytesRead; while ((bytesRead fis.read(buffer)) ! -1) { // 处理 buffer 中的前 bytesRead 字节 } }最早读取文件就是阻塞读取由于 byte[] 是jdk管理自己传入的所以下标最大就是21亿而且由于正常情况下由代码传入所以不可能 new byte[Integer.MAX_VALUE]而且 byte[] 回收需要看 gc。NIO时代JDK 1.4try (FileChannel channel FileChannel.open(Paths.get(data.txt), StandardOpenOption.READ)) { MappedByteBuffer buffer channel.map(FileChannel.MapMode.READ_ONLY, 0, size); }后来 java 推出了 NIO但是作者本人实际工作接触的需要如此优化的场景太少且 api 并不直观导致完全没有用过几次。返回的 MappedByteBuffer api 中下标参数使用的是 int这直接导致超过 4G 的文件必须分段映射且 MappedByteBuffer 映射的内存也需要 gc 回收。属于初代 mmap 技术虽然原理上是 mmap但是限制颇多。AIO时代JDK 7本质上是 NIO.2或者叫 new NIO原则上属于NIO的一部分AsynchronousFileChannel这个 api 更是查无此人作者并不知道这个应该怎么用/有什么性能。FFMJDK 22try (var arena Arena.ofConfined(); var channel FileChannel.open(path, StandardOpenOption.READ)) { var size channel.size(); var memorySegment channel.map(FileChannel.MapMode.READ_ONLY, 0, size, arena ); } catch (IOException e) { throw new RuntimeException(e); }随着 FFM 稳定原本的 FileChannel api 中也加入了对 FFM 的支持使用 Arena 配置接下来申请的内存的生命周期管理再将 arena 传入 channel返回 MemorySegment使用 MemorySegment 即可对全文件进行随机读写。arena 关闭后申请的所有内存直接回收没有 gc 压力。由于调用的是底层的 mmap 返回的内存段所以直接读写 memorySegment 即可直接反应到文件上也可以调用 MemorySegment#force() 强制写入。更加接近底层的 mmap 技术可选择是否有 gc 压力。读取示例public class MemorySegmentFileTest { private static final Path path Path.of(/home/bin-/Downloads/home/extraData.img); static void main() { try (var arena Arena.ofConfined(); var channel FileChannel.open(path, StandardOpenOption.READ)) { var size channel.size(); var memorySegment channel.map(FileChannel.MapMode.READ_ONLY, 0, size, arena ); System.out.println(成功映射文件大小 printSize(size)); System.out.println(内存段地址 memorySegment.address()); System.out.println(内存段大小 printSize(memorySegment.byteSize())); System.out.println(内存段内容前16字节); for (long i 0; i 16; i) { byte b memorySegment.get(ValueLayout.JAVA_BYTE, i); System.out.printf(0x%02X , b); } System.out.println(); System.out.println(内存段内容后16字节); for (long i size - 16; i size; i) { byte b memorySegment.get(ValueLayout.JAVA_BYTE, i); System.out.printf(0x%02X , b); } System.out.println(); } catch (IOException e) { throw new RuntimeException(e); } } private static String printSize(long size) { String[] units {B, KiB, MiB, GiB, TiB, PiB, EiB}; long s size; var list new ArrayListString(); var builder new StringBuilder(); var formatter new Formatter(builder); for (var unit : units) { int sub (int) (s ((1 10) - 1)); formatter.format(%s %s, sub, unit); list.add(builder.toString()); builder.setLength(0); if (s 1024) { break; } s 10; } return String.join( , list.reversed()); } }原理说明mmap 只是建立了虚拟内存地址到磁盘文件的映射并没有真正加载数据Lazy Loading。只有真正读取数据时OS 才会发生缺页中断Page Fault去加载数据。类似应用数据库系统SQLite、MySQL、RedisKafkaRocketMQ已知缺点本质是在借用 OS 的 Page Cache如果同时运行数据库MySQL/Redis等应用java 使用 mmap 疯狂读取大文件可能会把数据库在 Page Cache 里的热数据挤出去Eviction。结尾本文只是提出一种新版本 jdk 的全新 mmap 使用方式与其他相对较老的方式相比这种方式可以直接操纵堆外内存且性能更高还支持随机读取唯一缺点可能就是 api 太新用起来可能需要重新研究写法。