💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# Java中几种文件读写的性能对比 准备: ``` static byte[] data = "123456789\n".getBytes(); static String path = "/root/testfileio/out.txt"; ``` 查看系统脏页及文件被缓存大小: ``` ll -h out.txt && /home/golang/go/bin/pcstat out.txt && cat /proc/vmstat | egrep "dirty|writeback" ```   ## 一、使用最传统的stream ~~~ public static void testBasicFileIO() throws Exception { File file = new File(path); FileOutputStream out = new FileOutputStream(file); while (true) { out.write(data); } } ~~~ 其中data为10个字节大小的字节数组,在while循环中不断的将其写入文件中。查看输出文件的增长大小,会发现增长的还是比较缓慢的。同时所有的内容也会被pageCache所缓存(缓冲的限制可以查看dirty_ratio参数。) :-: ![](https://img.kancloud.cn/48/83/4883d40dad7c1c96096f41e50636a300_834x125.png) 即是暂停了程序,会发现该文件仍然有缓存在pageCache中,如果这个时候突然断电,可能就会导致数据丢失,因为有可能没有被持久化到磁盘中。   ## 二、使用buffer + stream ~~~ public static void testBufferedFileIO() throws Exception { File file = new File(path); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file)); while (true) { out.write(data); } } ~~~ 源码: ~~~ public synchronized void write(int b) throws IOException { if (count >= buf.length) { flushBuffer(); } // 先放到本地的buffer中 buf[count++] = (byte)b; } ~~~ 可以发现使用buffer之后其写入的速度比没有使用buffer就会快很多。其原因是Buffer流会在用户空间中准备一个8KB大小的字节数组作为缓冲区,该数组满了之后才会发生一次调用系统,一次性的写入8KB的内容。这样就大大的减少了系统调用的次数,因此要比没有使用Buffer的快很多。 同时在查看文件的缓存大小的时候会发现系统有明显的卡顿现象,这是因为当在脏页的数据超过一定比例之后需要暂停进行脏页的落盘。 :-: ![](https://img.kancloud.cn/1a/e0/1ae05c8d5fedd74f5e76a1dbebbdf208_1341x507.png)   ## 三、使用mmap ~~~ public static void testRandomAccessFileWrite() throws Exception { RandomAccessFile raf = new RandomAccessFile(path, "rw"); FileChannel rafchannel = raf.getChannel(); //mmap 使得jvm堆和pageCache有一块映射空间 MappedByteBuffer map = rafchannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096 * 1024 * 250); // 1000M的pageCache大小 while (true) { map.put(data); //不是系统调用 但是数据会到达 内核的pagecache } } ~~~ 可以看到快1G的pageCache写入几乎是一瞬间的事情。使用mmap会直接在pageCache中开辟一块空间,作为映射(思考一些虚拟内存的地址转化),不会发生系统调用。   使用拓扑图总结: :-: ![](https://img.kancloud.cn/7d/73/7d73553a005a9bf38ef6687f5d81d611_1240x714.png)