Java 文件IO操作效率对比
注:本文只做时间消耗层面对比,内存占用层面需要特别关注!
参数说明
文件总大小:2,111,993,850 字节(2.11 GB)- static String defaultFilePath = "/tmp/data-24081412.json";
复制代码 缓冲区大小:8192 字节- static int defaultByteLength = 1024 * 8;
复制代码 示例介绍
通过以下几种方式读取数据文件,并一连进行 10 次测试:
- FileInputStream + byte[] 文件字节输入流 + 字节数组读取方式
- FileInputStream + Scanner 文件字节输入流 + Scanner 读取方式
- FileReader + char[] 文件字符输入流 + 字符数组方式
- BufferedReader 缓冲字符流方式
- FileChannel 文件输入输出管道流「NIO」
比对结果
「5. FileChannel」 > 「1. FileInputStream + byte[]」> 「3. FileReader + char[]」>= 「4. BufferedReader」 > 「2. FileInputStream + Scanner」
注: 在操作文件时,会将文件区分为大文件、小文件、文本文件、二进制文件等,根据不同的文件需要选择符合的读取方式。通常大文件推荐使用 「FileChannel」效率会更高,小文件采用 IO 或 NIO 都可以,文本文件采用「BufferedReader」或者「FileChannel」判断换行符。
示例代码
4.1. FileInputStream + byte[] 文件字节输入流 + 字节数组读取方式
- /**
- * FileInputStream + byte[] 方式
- * 等同于 BufferedInputStream 字节输入缓冲流
- * 文件字节输入流 + 字节数组读取方式
- * 适用于:二进制文件或非文本文件
- */
- public void testFileInputStreamWithBytes() {
- long startTime = new Date().getTime();
- // 使用 try 包装
- try (FileInputStream fileInputStream = new FileInputStream(defaultFilePath)){
- byte[] reads = new byte[defaultByteLength];
- int readCount;
- while ((readCount = fileInputStream.read(reads)) != -1) {
- // TODO 处理数据
- }
- } catch (IOException e) {
- System.out.printf("读取文件异常[ %s ]%n", e.getMessage());
- }
- System.out.printf("FileInputStream + byte[] 方式 读取文件共使用 %d 毫秒%n", new Date().getTime() - startTime);
- }
复制代码 10 次测试结果如下- FileInputStream + byte[] 方式 读取文件共使用 884 毫秒
- FileInputStream + byte[] 方式 读取文件共使用 331 毫秒
- FileInputStream + byte[] 方式 读取文件共使用 319 毫秒
- FileInputStream + byte[] 方式 读取文件共使用 420 毫秒
- FileInputStream + byte[] 方式 读取文件共使用 333 毫秒
- FileInputStream + byte[] 方式 读取文件共使用 321 毫秒
- FileInputStream + byte[] 方式 读取文件共使用 327 毫秒
- FileInputStream + byte[] 方式 读取文件共使用 339 毫秒
- FileInputStream + byte[] 方式 读取文件共使用 328 毫秒
- FileInputStream + byte[] 方式 读取文件共使用 398 毫秒
复制代码 4.2. FileInputStream + Scanner 文件字节输入流 + Scanner 读取方式
- /**
- * FileInputStream + Scanner 方式
- * 文件字节输入流 + Scanner 读取文本方式
- * 适用于:文本文件
- */
- public void testFileInputStreamWithScanner() {
- long startTime = new Date().getTime();
- // 使用 try 包装
- try (FileInputStream fileInputStream = new FileInputStream(defaultFilePath)){
- Scanner scanner = new Scanner(fileInputStream);
- while (scanner.hasNext()) {
- scanner.nextLine();
- // TODO 处理数据
- }
- } catch (IOException e) {
- System.out.printf("读取文件异常[ %s ]%n", e.getMessage());
- }
- System.out.printf("FileInputStream + Scanner 方式 读取文件共使用 %d 毫秒%n", new Date().getTime() - startTime);
- }
复制代码 10 次测试结果如下- 没有缓冲区,性能急剧下降!!
- FileInputStream + Scanner 方式 读取文件共使用 16755 毫秒
- FileInputStream + Scanner 方式 读取文件共使用 18744 毫秒
- FileInputStream + Scanner 方式 读取文件共使用 17929 毫秒
- FileInputStream + Scanner 方式 读取文件共使用 18640 毫秒
- FileInputStream + Scanner 方式 读取文件共使用 18316 毫秒
- FileInputStream + Scanner 方式 读取文件共使用 18015 毫秒
- FileInputStream + Scanner 方式 读取文件共使用 18479 毫秒
- FileInputStream + Scanner 方式 读取文件共使用 18755 毫秒
- FileInputStream + Scanner 方式 读取文件共使用 18907 毫秒
- FileInputStream + Scanner 方式 读取文件共使用 18783 毫秒
复制代码 4.3. FileReader + char[] 文件字符输入流 + 字符数组方式
- /**
- * FileReader + char[] 方式
- * 等同于 BufferedReader 字符输入缓冲流
- * 文件字符输入流 + 字符数组方式
- * 适用于:字符文件
- */
- public void testFileReaderWithChars() {
- long startTime = new Date().getTime();
- // 使用 try 包装
- try (FileReader fileReader = new FileReader(defaultFilePath)){
- char[] reads = new char[defaultByteLength];
- int readCount;
- while ((readCount = fileReader.read(reads)) != -1) {
- // TODO 处理数据
- }
- } catch (IOException e) {
- System.out.printf("读取文件异常[ %s ]%n", e.getMessage());
- }
- System.out.printf("FileReader + char[] 方式 读取文件共使用 %d 毫秒%n", new Date().getTime() - startTime);
- }
复制代码 10 次测试结果如下- FileReader + char[] 方式 读取文件共使用 922 毫秒
- FileReader + char[] 方式 读取文件共使用 971 毫秒
- FileReader + char[] 方式 读取文件共使用 842 毫秒
- FileReader + char[] 方式 读取文件共使用 985 毫秒
- FileReader + char[] 方式 读取文件共使用 868 毫秒
- FileReader + char[] 方式 读取文件共使用 1207 毫秒
- FileReader + char[] 方式 读取文件共使用 1031 毫秒
- FileReader + char[] 方式 读取文件共使用 981 毫秒
- FileReader + char[] 方式 读取文件共使用 1259 毫秒
- FileReader + char[] 方式 读取文件共使用 1034 毫秒
复制代码 4.4. BufferedReader 缓冲字符流方式
- /**
- * BufferedReader 方式
- * 缓冲字符流方式
- * 适用于:字符文件
- */
- public void testBufferedReader() {
- long startTime = new Date().getTime();
- // 使用 try 包装
- try (BufferedReader fileReader = new BufferedReader(new FileReader(defaultFilePath))){
- String line;
- while ((line = fileReader.readLine()) != null) {
- // TODO 处理数据
- }
- } catch (IOException e) {
- System.out.printf("读取文件异常[ %s ]%n", e.getMessage());
- }
- System.out.printf("BufferedReader 方式 读取文件共使用 %d 毫秒%n", new Date().getTime() - startTime);
- }
复制代码 10 次测试结果如下- BufferedReader 方式 读取文件共使用 1870 毫秒
- BufferedReader 方式 读取文件共使用 1895 毫秒
- BufferedReader 方式 读取文件共使用 1890 毫秒
- BufferedReader 方式 读取文件共使用 1875 毫秒
- BufferedReader 方式 读取文件共使用 1829 毫秒
- BufferedReader 方式 读取文件共使用 2060 毫秒
- BufferedReader 方式 读取文件共使用 1821 毫秒
- BufferedReader 方式 读取文件共使用 1944 毫秒
- BufferedReader 方式 读取文件共使用 1902 毫秒
- BufferedReader 方式 读取文件共使用 1860 毫秒
复制代码 4.5. FileChannel 文件输入输出管道流「NIO」
- /**
- * FileChannel 方式
- * 文件输入输出管道流
- */
- public void testFileChannel() {
- long startTime = new Date().getTime();
- // 使用 try 包装
- try (FileChannel channel = FileChannel.open(Paths.get(defaultFilePath), StandardOpenOption.READ)){
- // 构造B yteBuffer 有两个方法,ByteBuffer.allocate和 ByteBuffer.allocateDirect,两个方法都是相同入参,含义却不同。
- // ByteBuffer.allocate(capacity) 分配的是非直接缓冲区,非直接缓冲区的操作会在Java堆内存中进行,数据的读写会通过Java堆内存来传递。
- // ByteBuffer.allocateDirect(capacity) 分配的是直接缓冲区, 直接缓冲区的操作可以通过本地I/O传递,避免了在Java堆和本地堆之间的数据传输。
- ByteBuffer buf = ByteBuffer.allocate(defaultByteLength);
- while (channel.read(buf) != -1) {
- buf.flip();
- // TODO 处理数据
- // System.out.println(new String(buf.array()));
- buf.clear();
- }
- } catch (IOException e) {
- System.out.printf("读取文件异常[ %s ]%n", e.getMessage());
- }
- System.out.printf("FileChannel 方式 读取文件共使用 %d 毫秒%n", new Date().getTime() - startTime);
- }
复制代码 10 次测试结果如下- FileChannel 方式 读取文件共使用 314 毫秒
- FileChannel 方式 读取文件共使用 293 毫秒
- FileChannel 方式 读取文件共使用 332 毫秒
- FileChannel 方式 读取文件共使用 296 毫秒
- FileChannel 方式 读取文件共使用 285 毫秒
- FileChannel 方式 读取文件共使用 290 毫秒
- FileChannel 方式 读取文件共使用 283 毫秒
- FileChannel 方式 读取文件共使用 282 毫秒
- FileChannel 方式 读取文件共使用 298 毫秒
- FileChannel 方式 读取文件共使用 280 毫秒
复制代码 结语
在 Java 8 中,「FileChannel」是处理文件 I/O 的高效方式,相比于传统的 I/O流「FileInputStream」、「FileOutputStream」等更加灵活方便且效率更高。
代码附录
- package com.demo.io;
- import org.junit.Test;
- import java.io.BufferedReader;
- import java.io.FileInputStream;
- import java.io.FileReader;
- import java.io.IOException;
- import java.nio.ByteBuffer;
- import java.nio.channels.FileChannel;
- import java.nio.file.Paths;
- import java.nio.file.StandardOpenOption;
- import java.util.Date;
- import java.util.Scanner;
- /**
- * 文件读取
- * @date 2024-08-14 16:42:21
- */
- public class FileReadTest {
- // 文件路径
- static String defaultFilePath = "/Users/changbeibei/Desktop/work/wfilep.log-24081412";
- // 8k
- static int defaultByteLength = 1024 * 8;
- /**
- * FileInputStream + byte[] 方式
- * 等同于 BufferedInputStream 字节输入缓冲流
- * 文件字节输入流 + 字节数组读取方式
- * 适用于:二进制文件或非文本文件
- */
- @Test
- public void testFileInputStreamWithBytes() {
- long startTime = new Date().getTime();
- // 使用 try 包装
- try (FileInputStream fileInputStream = new FileInputStream(defaultFilePath)){
- byte[] reads = new byte[defaultByteLength];
- int readCount;
- while ((readCount = fileInputStream.read(reads)) != -1) {
- // TODO 处理数据
- }
- } catch (IOException e) {
- System.out.printf("读取文件异常[ %s ]%n", e.getMessage());
- }
- System.out.printf("FileInputStream + byte[] 方式 读取文件共使用 %d 毫秒%n", new Date().getTime() - startTime);
- }
- /**
- * FileInputStream + Scanner 方式
- * 文件字节输入流 + Scanner 读取文本方式
- * 适用于:文本文件
- */
- @Test
- public void testFileInputStreamWithScanner() {
- long startTime = new Date().getTime();
- // 使用 try 包装
- try (FileInputStream fileInputStream = new FileInputStream(defaultFilePath)){
- Scanner scanner = new Scanner(fileInputStream);
- while (scanner.hasNext()) {
- scanner.nextLine();
- // TODO 处理数据
- }
- } catch (IOException e) {
- System.out.printf("读取文件异常[ %s ]%n", e.getMessage());
- }
- System.out.printf("FileInputStream + Scanner 方式 读取文件共使用 %d 毫秒%n", new Date().getTime() - startTime);
- }
- /**
- * FileReader + char[] 方式
- * 等同于 BufferedReader 字符输入缓冲流
- * 文件字符输入流 + 字符数组方式
- * 适用于:字符文件
- */
- @Test
- public void testFileReaderWithChars() {
- long startTime = new Date().getTime();
- // 使用 try 包装
- try (FileReader fileReader = new FileReader(defaultFilePath)){
- char[] reads = new char[defaultByteLength];
- int readCount;
- while ((readCount = fileReader.read(reads)) != -1) {
- // TODO 处理数据
- }
- } catch (IOException e) {
- System.out.printf("读取文件异常[ %s ]%n", e.getMessage());
- }
- System.out.printf("FileReader + char[] 方式 读取文件共使用 %d 毫秒%n", new Date().getTime() - startTime);
- }
- /**
- * BufferedReader 方式
- * 缓冲字符流方式
- * 适用于:字符文件
- */
- @Test
- public void testBufferedReader() {
- long startTime = new Date().getTime();
- // 使用 try 包装
- try (BufferedReader fileReader = new BufferedReader(new FileReader(defaultFilePath))){
- String line;
- while ((line = fileReader.readLine()) != null) {
- // TODO 处理数据
- }
- } catch (IOException e) {
- System.out.printf("读取文件异常[ %s ]%n", e.getMessage());
- }
- System.out.printf("BufferedReader 方式 读取文件共使用 %d 毫秒%n", new Date().getTime() - startTime);
- }
- /**
- * FileChannel 方式
- * 文件输入输出管道流
- */
- @Test
- public void testFileChannel() {
- long startTime = new Date().getTime();
- // 使用 try 包装
- try (FileChannel channel = FileChannel.open(Paths.get(defaultFilePath), StandardOpenOption.READ)){
- // 构造B yteBuffer 有两个方法,ByteBuffer.allocate和 ByteBuffer.allocateDirect,两个方法都是相同入参,含义却不同。
- // ByteBuffer.allocate(capacity) 分配的是非直接缓冲区,非直接缓冲区的操作会在Java堆内存中进行,数据的读写会通过Java堆内存来传递。
- // ByteBuffer.allocateDirect(capacity) 分配的是直接缓冲区, 直接缓冲区的操作可以通过本地I/O传递,避免了在Java堆和本地堆之间的数据传输。
- ByteBuffer buf = ByteBuffer.allocate(defaultByteLength);
- while (channel.read(buf) != -1) {
- buf.flip();
- // TODO 处理数据
- // System.out.println(new String(buf.array()));
- buf.clear();
- }
- } catch (IOException e) {
- System.out.printf("读取文件异常[ %s ]%n", e.getMessage());
- }
- System.out.printf("FileChannel 方式 读取文件共使用 %d 毫秒%n", new Date().getTime() - startTime);
- }
- @Test
- public void testMain() {
- for (int i = 0; i < 10; i++) {
- System.out.printf("第 %d 次测试%n", i + 1);
- testFileInputStreamWithBytes();
- testFileInputStreamWithScanner();
- testFileReaderWithChars();
- testBufferedReader();
- testFileChannel();
- }
- }
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |