首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

Java 大文件IO操作效率对比【我说说 你瞅瞅】

编程知识
2024年08月14日 17:54

Java 文件IO操作效率对比

注:本文只做时间消耗层面对比,内存占用层面需要特别关注!

参数说明

文件总大小:2,111,993,850 字节(2.11 GB)

static String defaultFilePath = "/tmp/data-24081412.json";

缓冲区大小:8192 字节

static int defaultByteLength = 1024 * 8;

示例介绍

通过以下几种方式读取数据文件,并连续进行 10 次测试:

  1. FileInputStream + byte[] 文件字节输入流 + 字节数组读取方式
  2. FileInputStream + Scanner 文件字节输入流 + Scanner 读取方式
  3. FileReader + char[] 文件字符输入流 + 字符数组方式
  4. BufferedReader 缓冲字符流方式
  5. 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();
        }
    }
}

From:https://www.cnblogs.com/beixuan/p/18359590
本文地址: http://shuzixingkong.net/article/1102
0评论
提交 加载更多评论
其他文章 TCP三次握手和四次挥手
TCP三次握手和四次挥手详解 在网络通信中,TCP(传输控制协议)是一个非常重要的协议,用于确保数据在不可靠的网络环境中能够可靠传输。TCP通过三次握手(Three-way Handshake)建立连接,通过四次挥手(Four-way Termination)终止连接。 一、TCP三次握手 TCP的
JVM 参数配置
JVM 参数设置入门案例 JVM 的内存参数众多,但是在实际应用中主要关注堆内存的大小设置及堆内存中新生代和老年代的大小设置,下面看一个简单的 JVM 启动参数设置案例: java -server -Xms3g -Xmx3g -XX:NewSize=1g -XX:MetaspaceSize=128m
使用Django-Channels实现websocket通信+大模型对话
前言 最近一直在做这个大模型项目,我选了 Django 作为框架(现在很多大模型应用都用的 FastAPI,不过我已经用习惯 Django 了) 之前使用 AspNetCore 作为后端的时候,我先后尝试了 Blazor Server,WebAPI SSE(Server Sent Event)等方案
使用Django-Channels实现websocket通信+大模型对话 使用Django-Channels实现websocket通信+大模型对话
什么是依赖倒置原则
依赖倒置原则(Dependency Inversion Principle, DIP)是面向对象设计原则之一,它是SOLID原则中的&quot;D&quot;。依赖倒置原则的核心思想是高层策略性业务规则不应该依赖于低层的具体实现细节,而两者都应该依赖于抽象。 依赖倒置原则主要包含两个基本点: 抽象不
这次轮到AntV增强Awesome-Graphs
AntV团队迅速将G6图可视化引擎融入Awesome-Graphs项目,发布1.2.0版本,提升交互体验,包括路径高亮、模糊搜索等功能,现邀请体验并征集改进意见。
这次轮到AntV增强Awesome-Graphs 这次轮到AntV增强Awesome-Graphs 这次轮到AntV增强Awesome-Graphs
SenseCraft 部署模型到Grove Vision AI V2图像处理模块
今天教大家快速上手Grove Vision AI V2 图像处理模块,我们将一起探讨如何利用 SenseCraft 部署 AI 模型,和如何通过XIAO ESP32C3调用这些模型,轻松实现智能视觉功能!
SenseCraft 部署模型到Grove Vision AI V2图像处理模块 SenseCraft 部署模型到Grove Vision AI V2图像处理模块 SenseCraft 部署模型到Grove Vision AI V2图像处理模块
PowerShell快速修改多个文件的名称
本文介绍基于PowerShell语言,对文件夹中全部文件的名称加以批量替换、修改的方法。 在之前的文章中,我们介绍了基于Python语言,批量修改大量文件的名称的方法。当时我们修改文件名的需求比较复杂,因此选择了用Python语言来实现;而在我们的需求重命名规则相对比较简单时,则可以基于PowerS
PowerShell快速修改多个文件的名称 PowerShell快速修改多个文件的名称 PowerShell快速修改多个文件的名称
LeetCode39. 组合总和
LeetCode39. 组合总和 题目叙述: 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的数字可以无限制重复被选取。 说明: 所有数字(包括 target)都是正整数
LeetCode39. 组合总和