首页 > 解决方案 > 如何在 Java 中读取大文件(单个连续字符串)?

问题描述

我正在尝试读取一个非常大的文件(~2GB)。内容是带有句子的连续字符串(我想根据“。”来拆分它们)。无论我如何尝试,最终都会出现内存不足错误。

    BufferedReader in = new BufferedReader(new FileReader("a.txt"));
    String read = null;
    int i = 0;
    while((read = in.readLine())!=null) {
        String[] splitted = read.split("\\.");
        for (String part: splitted) {
            i+=1;
            users.add(new User(i,part));
            repository.saveAll(users);
        }
    }

还,

inputStream = new FileInputStream(path);
    sc = new Scanner(inputStream, "UTF-8");
    while (sc.hasNextLine()) {
        String line = sc.nextLine();
        // System.out.println(line);
    }
    // note that Scanner suppresses exceptions
    if (sc.ioException() != null) {
        throw sc.ioException();
    }

文件内容(由随机词组成,10 个词后有句号):

fmfbqi .xcdqnjqln kvjhw pexrbunnr cgvrqlr fpaczdegnb puqzjdbp gcfxne jawml aaiwwmo ugzoxn .opjc fmfbqi .xcdqnjqln kvjhw pexrbunnr cgvrqlr fpaczdegnb puqzjdbp gcfxne jawml aaiwwmo ugzoxn .opjc  (so on)

请帮忙!

标签: javafilestreambufferedinputstream

解决方案


因此,首先,根据对您问题的评论,正如 Joachim Sauer 所说:

如果没有换行符,则只有一行,因此只有一个行号。

所以你的用例充其量是有问题的。

让我们超越这一点,假设可能有换行符 - 或者更好的是,假设.您要拆分的字符旨在成为换行符伪替换。

Scanner尽管还有其他方法,但这并不是一个坏方法。由于您提供了 a Scanner,让我们继续,但您要确保将它包裹在 a 周围BufferedReader。您显然没有很多内存,并且 aBufferedReader允许您读取由 缓冲的文件的“块” BufferedReader,同时利用对Scanner您来说完全模糊的功能,作为正在发生缓冲的调用者:

Scanner sc = new Scanner(new BufferedReader(new FileReader(new File("a.txt")), 10*1024));

这基本上是在做Scanner你所期望的功能,但允许你一次缓冲 10MB,最大限度地减少你的内存占用。现在,你只要继续打电话

sc.useDelimiter("\\.");
for(int i = 0; sc.hasNext(); i++) {
    String psudeoLine = sc.next();
    //store line 'i' in your database for this psudeo-line
    //DO NOT store psudeoLine anywhere else - you don't have memory for it
}

由于您没有足够的内存,因此迭代(和重新迭代)的明确事项是在读取文件后不要将文件的任何部分存储在 JVM 的堆空间中。阅读它,根据需要使用它,并允许将其标记为 JVM 垃圾回收。在您的情况下,您提到要将伪行存储在数据库中,因此您想读取伪行,将其存储在数据库中,然后丢弃它。

这里还有其他需要指出的事情,例如配置您的 JVM 参数,但我什至不愿提及它,因为仅将您的 JVM 内存设置为高也是一个坏主意——另一种蛮力方法。将 JVM 内存最大堆大小设置得更高没有任何问题,但是如果您仍在学习如何编写软件,那么学习内存管理会更好。当您进入专业发展阶段时,您以后遇到的麻烦就会减少。

另外,我提到Scanner并且BufferedReader因为您在问题中提到了这一点,但我认为检查deHaar 指出的java.nio.file.Path.lines()也是一个好主意。这基本上与我明确列出的代码做同样的事情,但需要注意的是,它仍然一次只能执行 1 行,而无法更改您“拆分”的内容。因此,如果您的文本文件中有 1 行,这仍然会给您带来问题,您仍然需要像扫描仪这样的东西来分割行。


推荐阅读