首页 > 编程语言 >Java 大文件IO操作效率对比【我说说 你瞅瞅】

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

时间:2024-08-14 18:54:56浏览次数:6  
标签:文件 Java 读取 方式 毫秒 IO new FileInputStream 对比

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();
        }
    }
}

标签:文件,Java,读取,方式,毫秒,IO,new,FileInputStream,对比
From: https://www.cnblogs.com/beixuan/p/18359590

相关文章

  • [pnpm] pnpm 与 npm/yarn 的对比
    JavaScript应用程序通常依赖于许多外部库,这些依赖项通常通过包管理器来管理。默认情况下,Node.js使用NPM作为包管理器。由于早期的NPM存在各种不足,社区后来开发了Yarn和pnpm作为替代品。如果要使用Yarn和pnpm,则需要先通过NPM进行安装。早期NPM的不足依赖树......
  • Golang文件操作秘籍:ioutil包的终极指南
    Golang文件操作秘籍:ioutil包的终极指南在Go语言的世界中,文件操作是一项基本而常见的任务。Go标准库中的ioutil包提供了一些简便的方法来执行常见的I/O操作,包括文件的读写。本文将深入探讨如何使用ioutil包进行文件读写操作,并提供详细的代码示例,帮助你快速掌握这一技能。io......
  • 使用 Axios 下载文件并更新进度条
    使用Axios下载文件并更新进度条使用axios的onDownloadProgress回调函数实现下载文件,并更新下载进度条。示例代码import{ElMessage}from"element-plus";importaxiosfrom"axios";importtype{AxiosResponse,AxiosProgressEvent}from"axios";import{baseUrl......
  • io流
    io流一个程序总是离不开数据的输入(Input)输出(loput),而JavaIO流就是用于程序从外部读取数据,以及向外部写出数据的java处理包,一、什么是IO流I: Input 输入O: Output 输出 通过IO可以完成硬盘文件的读和写。二、IO流的分类按照 流的方向 进行分类:以内存作为参照......
  • CF830E Perpetual Motion Machine
    一堆CornnerCase的大分讨,我们全队一边写一边补情况,WA了五发终于干过去了首先当图中存在某个环时,我们只要给环上的所有点赋值为\(1\)即可;又因为图连通所以只要考虑树的情况即可考虑如果存在一个度数\(\ge4\)的点,将其赋值为\(2\)并将其周围的四个点赋值为\(1\)即可......
  • JAVA字段审计功能-对比修改前后变化并使用枚举Enums进行翻译
    最近接到了一个业务是,审计客户和合同的字段变化,要明细到使用系统的人员能看懂(大概就是我们存入数据库是12什么的进行翻译)返回的信息大概就是:客户A的客户状态从客户状态A 修改成了 客户状态B,客户性别从客户性别A变成了客户性别B。我实现的思路大概就是:1、获取到......
  • java反射简介
    1.反射定义 反射是一种可以间接操作目标对象的机制。当使用反射时,JVM 在运行的时候才动态加载类,对于任意类,知道其属性和方法,并不需要提前在编译期知道运行的对象是谁,允许运行时的Java程序获取类的信息并对其进行操作。2.获取类的四个方式 3.class从类中获取构造器......
  • Cross-Image Attention for zero-shot appearance transfer
     1.Introduction探讨appearancetransfer的背景下进行图像操作,目标是将一个图像中概念的视觉外观转移到另一个图像中存在的概念上,例如,将斑马的外观转移到长颈鹿上,完成这一任务需要首先将长颈鹿和斑马之间在语义上相似的区域进行关联,然后通过这些关联转移,而不改变长颈鹿的结......
  • Cookie和Sessio
    Cookie是什么?    Cookie是保存在客户端的,是浏览器向服务端发送请求时,会携带Cookie信息,将Cookie发送至服务端。    一个浏览器访问多个不同的服务端,而请求每一个服务端时,可以携带多个Cookie        Cookie组成是由key和value组成的,key存放的是该Co......
  • 计算机毕业设计推荐-基于JAVA的航空机票预定管理系统
    ......