如何使用 Java 高效读取大文件

1.介绍

如何以高效的方式从 Java 大文件中读取所有行。

2.读在内存中

准备

准备1G作用的数据

    @Test
    public void test0() throws IOException {
        final String json = "[{\"id\":\"380001671739949056\",\"type\":\"3\",\"work_no\":\"chn001\",\"display_name\":\"测试chn\",\"display_py\":\"ceschn\",\"country\":\"1001\",\"id_card_no\":null,\"birthday\":null,\"sex\":null,\"secret_level\":\"1\",\"nationality\":\"1\",\"mobile\":null,\"sw_email\":null,\"ip_phone\":null,\"leave_date\":null,\"marital_status\":null,\"certificate_cn\":null,\"politics_status\":null,\"native_place\":null,\"work_time\":null,\"image\":null,\"status\":1,\"update_time\":1631601059000,\"create_time\":1631601076000,\"userJobInfo\":[{\"company_id\":\"129343111846100992\",\"company_job_type\":\"0\",\"jh_email\":null,\"email\":null,\"office_address\":null,\"job_level\":null,\"depJobInfo\":[{\"org_id\":\"129343111846100992\",\"dept_job_type\":\"1\",\"job\":null,\"job_status\":null,\"position\":null,\"show_order\":null,\"valid_time\":null,\"job_attribute\":null}]}],\"syncStatus\":2000,\"syncSequence\":380001673615736833},{\"id\":\"380388589673725952\",\"type\":\"1\",\"work_no\":\"727411\",\"display_name\":\"冬测试导入用户-11\",\"display_py\":\"dongcsdryh-11\",\"country\":null,\"id_card_no\":null,\"birthday\":null,\"sex\":null,\"secret_level\":\"1\",\"nationality\":null,\"mobile\":null,\"sw_email\":null,\"ip_phone\":null,\"leave_date\":null,\"marital_status\":null,\"certificate_cn\":null,\"politics_status\":null,\"native_place\":null,\"work_time\":null,\"image\":null,\"status\":1,\"update_time\":1631693308000,\"create_time\":1631693324000,\"userJobInfo\":[{\"company_id\":\"114836879844122624\",\"company_job_type\":\"0\",\"jh_email\":null,\"email\":null,\"office_address\":null,\"job_level\":null,\"depJobInfo\":[{\"org_id\":\"124944489058598912\",\"dept_job_type\":\"1\",\"job\":null,\"job_status\":null,\"position\":null,\"show_order\":null,\"valid_time\":null,\"job_attribute\":null}]}],\"syncStatus\":2000,\"syncSequence\":380388591876669441}]";
        final BufferedWriter writer = new BufferedWriter(new FileWriter("D:\\test.txt"));
        long start = System.currentTimeMillis();
        IntStream.range(0, 800000).forEach(value -> {
            try {
                //新起一行 写数据
                writer.newLine();
                writer.write(json);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        long end = System.currentTimeMillis();
        writer.close();
        System.out.println((end - start) / 1000 + "ms");
        //1.23g 写完 需要7s
    }

读取文件行的标准方法是在内存中——Guava 和 Apache Commons IO 都提供了一种快速的方法来做到这一点:

Guava

@Test
public void test1() throws IOException {
    Files.readLines(new File(path), Charsets.UTF_8);
}

Apache

@Test
public void test2() throws IOException {
    FileUtils.readLines(new File(path));
}

这种方法的问题是所有文件行都保存在内存中——如果文件足够大,这将很快导致 OutOfMemoryError。

例如——读取一个 ~1Gb 的文件:

    private void logMemory() {
        System.out.println("最大内存: " + Runtime.getRuntime()
                .maxMemory() / 1048576 + "Mb");
        System.out.println("总内存:" + Runtime.getRuntime()
                .totalMemory() / 1048576+"Mb");
        System.out.println("空闲内存:"+ + Runtime.getRuntime()
                .freeMemory() / 1048576+ "Mb");
    }

    @Test
    public void test1() throws IOException {
        final String path = "D:\\test.txt";
        logMemory();
        Files.readLines(new File(path), Charsets.UTF_8);
        logMemory();
    }

这从消耗少量内存开始:(消耗约 0 Mb)

[main] INFO com.demo.javabase.ReadFileTest - 总内存: 128 Mb

[main] INFO com.demo.javabase.ReadFileTest - 空闲内存: 116 Mb

但是,在处理完完整文件后,最终得到了:(消耗了约 2 Gb)

[main] INFO com.demo.javabase.ReadFileTest - 总内存: 2666 Mb

[main] INFO com.demo.javabase.ReadFileTest - 空闲内存: 490 Mb

这意味着进程消耗了大约 2.1 Gb 的内存——原因很简单——文件的行现在都存储在内存中。

很明显,将文件内容保存在内存中会很快耗尽可用内存——不管实际有多少。

更重要的是,通常不需要一次性将文件中的所有行都保存在内存中——相反,只需要能够遍历每一行,进行一些处理并将其丢弃。 所以,要做的就是——遍历行而不将所有行都保存在内存中

3.通过文件流读取

一个解决方案——使用 java.util.Scanner 来遍历文件的内容并逐行检索行:

    @Test
    public void test3() throws Exception {
        logMemory();
        final String path = "D:\\test.txt";
        FileInputStream inputStream = null;
        Scanner sc = null;
        try {
            inputStream = new FileInputStream(path);
            sc = new Scanner(inputStream, "UTF-8");
            //一行一行读取数据
            while (sc.hasNextLine()) {
                String line = sc.nextLine();
                 System.out.println(line);
            }
            if (sc.ioException() != null) {
                throw sc.ioException();
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (sc != null) {
                sc.close();
            }
        }
        logMemory();
    }

该解决方案将遍历文件中的所有行 - 允许处理每一行 - 不保留对它们的引用 - 总之,不将它们保留在内存中:(消耗约 150 Mb)

[main] INFO com.demo.javabase.ReadFileTest - 总内存: 763 Mb

[main] INFO com.demo.javabase.ReadFileTest - 空闲内存: 605 Mb

4.使用 Apache Commons IO 进行流传输

使用 Commons IO 库也可以通过使用库提供的自定义 LineIterator 来实现相同的目的:

由于整个文件并未完全在内存中 - 这也会导致相当保守的内存消耗数字:(消耗约 150 Mb)

    @Test
    public final void test6() throws IOException {
        final String path = "D:\\test.txt";

        logMemory();

        final LineIterator it = FileUtils.lineIterator(new File(path), "UTF-8");
        try {
            while (it.hasNext()) {
                final String line = it.nextLine();
                System.out.println(line);
            }
        } finally {
            LineIterator.closeQuietly(it);
        }

        logMemory();
    }

由于整个文件并未完全在内存中 - 这也会导致相当保守的内存消耗数字:(消耗约 150 Mb)

[main] INFO com.demo.javabase.ReadFileTest - 总内存: 752 Mb

[main] INFO com.demo.javabase.ReadFileTest - 空闲内存: 564 Mb

在这里插入图片描述

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值