【问题标题】:Read large amount of data from file in Java用Java从文件中读取大量数据
【发布时间】:2011-02-11 04:50:45
【问题描述】:

我有一个包含1 000 002 数字的文本文件,格式如下:

123 456
1 2 3 4 5 6 .... 999999 100000

现在我需要读取该数据并将其分配给 int 变量(前两个数字)和所有其余的(1 000 000 个数字)到数组 int[]

这不是一项艰巨的任务,但是 - 速度太慢了。

我的第一次尝试是java.util.Scanner:

 Scanner stdin = new Scanner(new File("./path"));
 int n = stdin.nextInt();
 int t = stdin.nextInt();
 int array[] = new array[n];

 for (int i = 0; i < n; i++) {
     array[i] = stdin.nextInt();
 }

它在例外情况下工作,但执行大约需要 7500 毫秒。我需要在数百毫秒内获取该数据。

然后我试了java.io.BufferedReader:

使用 BufferedReader.readLine()String.split() 我在大约 1700 毫秒 内得到了相同的结果,但仍然太多了。

如何在不到 1 秒的时间内读取这么多数据?最终结果应该等于:

int n = 123;
int t = 456;
int array[] = { 1, 2, 3, 4, ..., 999999, 100000 };

根据trashgod的回答:

StreamTokenizer 解决方案很快(大约需要 1400 毫秒)但仍然太慢:

StreamTokenizer st = new StreamTokenizer(new FileReader("./test_grz"));
st.nextToken();
int n = (int) st.nval;

st.nextToken();
int t = (int) st.nval;

int array[] = new int[n];

for (int i = 0; st.nextToken() != StreamTokenizer.TT_EOF; i++) {
    array[i] = (int) st.nval;
}

PS。无需验证。我 100% 确定 ./test_grz 文件中的数据是正确的。

【问题讨论】:

  • 为什么不将它们存储到 LinkedList {如果你要在列表中移动它们或对它们进行排序} 或 ArrayList(如果你想要随机访问}(取决于你如何'会使用它们)?这是大量数据,我假设您稍后会使用它们。
  • 我已经改变了我的问题 - 它只是关于从文件中读取。 ;) 我不需要任何集合 - 简单数组是我真正需要的,但问题是如何以最快的方式用文件中的数据填充这个数组。
  • 大数组(近 4MB)的分配与解析花费了多少时间?您可以在该调用之外分配数组吗?假设您使用的是 Integer.parseInt,您是否寻找过任何其他可能优化了以 10 为底的整数解析的库?这声称:cs.ou.edu/~weaver/improvise/downloads/javadoc/oblivion/oblivion/…
  • 只是抛出一些想法-您可以创建一个单独的程序将文件拆分为多个文件,然后使用单独的线程读取数据,然后组合结果吗?虽然会使整个设计更加复杂。也不确定它会更快。
  • @JRL:这是不可能的。一切都必须在这个程序中以最简单、最原始但快速的方式完成。

标签: java performance optimization input


【解决方案1】:

你的电脑有多少内存?您可能会遇到 GC 问题。

如果可能,最好一次处理一行数据。不要将其加载到数组中。加载您需要的内容、处理、写出并继续。

这将减少您的内存占用并仍然使用相同数量的文件 IO

【讨论】:

  • 看起来他的第二行是一个包含一百万个数字的 looong 行..
  • 如果我的计算是正确的,100 万个int 只花费了我 7 MB 的内存——这还不算多。我只需要将数据从文件加载到内存 - 对于一些需要加载整个数据的计算,我需要它。
【解决方案2】:

StreamTokenizer 可能会更快,正如 here 建议的那样。

【讨论】:

  • 事实上 StreamTokenizer 似乎是迄今为止最快的解决方案(请查看我的问题更新)。但它仍然需要大约 1400 毫秒才能读取必要的数据。
  • 优秀。另请参阅@Kevin Brock 的内容丰富的答案:stackoverflow.com/questions/2693223/…
【解决方案3】:

可以重新格式化输入,以便每个整数在单独的行上(而不是一百万个整数的长行),您应该会看到使用Integer.parseInt(BufferedReader.readLine()) 的性能大大提高,因为更智能的逐行缓冲而不是必须将长字符串拆分为单独的字符串数组。

编辑:我对此进行了测试,并设法在不到半秒的时间内将seq 1 1000000 产生的输出读入int 的数组中,但这当然取决于机器。

【讨论】:

  • 很遗憾我无法更改文件格式。它必须是第一行中由单个空格分隔的两个整数,第二行中必须是 100 万个整数(也由单个空格分隔)。
【解决方案4】:

我会扩展 FilterReader 并解析在 read() 方法中读取的字符串。让 getNextNumber 方法返回数字。代码留给读者作为练习。

【讨论】:

    【解决方案5】:

    您可以使用BufferedReader 来减少StreamTokenizer 结果的时间:

    Reader r = null;
    try {
        r = new BufferedReader(new FileReader(file));
        final StreamTokenizer st = new StreamTokenizer(r);
        ...
    } finally {
        if (r != null)
            r.close();
    }
    

    另外,不要忘记关闭您的文件,就像我在这里展示的那样。

    您还可以通过使用自定义标记器来节省更多时间:

    public class CustomTokenizer {
    
        private final Reader r;
    
        public CustomTokenizer(final Reader r) {
            this.r = r;
        }
    
        public int nextInt() throws IOException {
            int i = r.read();
            if (i == -1)
                throw new EOFException();
    
            char c = (char) i;
    
            // Skip any whitespace
            while (c == ' ' || c == '\n' || c == '\r') {
                i = r.read();
                if (i == -1)
                    throw new EOFException();
                c = (char) i;
            }
    
            int result = (c - '0');
            while ((i = r.read()) >= 0) {
                c = (char) i;
                if (c == ' ' || c == '\n' || c == '\r')
                    break;
                result = result * 10 + (c - '0');
            }
    
            return result;
        }
    
    }
    

    请记住为此使用BufferedReader。此自定义分词器假定输入数据始终完全有效,并且仅包含空格、换行符和数字。

    如果您经常阅读这些结果并且这些结果没有太大变化,您可能应该保存数组并跟踪上次文件修改时间。然后,如果文件没有更改,只需使用数组的缓存副本,这将显着加快结果。例如:

    public class ArrayRetriever {
    
        private File inputFile;
        private long lastModified;
        private int[] lastResult;
    
        public ArrayRetriever(File file) {
            this.inputFile = file;
        }
    
        public int[] getResult() {
            if (lastResult != null && inputFile.lastModified() == lastModified)
                return lastResult;
    
            lastModified = inputFile.lastModified();
    
            // do logic to actually read the file here
    
            lastResult = array; // the array variable from your examples
            return lastResult;
        }
    
    }
    

    【讨论】:

    • 感谢您的回答 - 我明天会检查 - 我希望这就是我要找的。​​span>
    • +1 在构造BufferedReader 时也可能值得指定缓冲区大小。
    【解决方案6】:

    感谢您的每一个回答,但我已经找到了符合我标准的方法:

    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("./path"));
    int n = readInt(bis);
    int t = readInt(bis);
    int array[] = new int[n];
    for (int i = 0; i < n; i++) {
        array[i] = readInt(bis);
    }
    
    private static int readInt(InputStream in) throws IOException {
        int ret = 0;
        boolean dig = false;
    
        for (int c = 0; (c = in.read()) != -1; ) {
            if (c >= '0' && c <= '9') {
                dig = true;
                ret = ret * 10 + c - '0';
            } else if (dig) break;
        }
    
        return ret;
    }
    

    读取 100 万个整数只需要大约 300 毫秒

    【讨论】:

    • 你的 int t 变量有什么作用?
    • @AdamJohns 绝对没有,它只是文件中的第二个数字(请参阅问题中的文件格式)。 array 变量也不做任何事情。 ;)
    • 优秀。这大约是。比在我的问题中使用 StringTokenizer 快​​ 2 倍(读取 100 万个整数,每个整数最多 100 万个)。
    【解决方案7】:

    在 BufferedReader 上使用 StreamTokenizer 已经可以为您提供相当不错的性能。您不需要编写自己的 readInt() 函数。

    这是我用来做一些本地性能测试的代码:

    /**
     * Created by zhenhua.xu on 11/27/16.
     */
    public class MyReader {
    
    private static final String FILE_NAME = "./1m_numbers.txt";
    private static final int n = 1000000;
    
    public static void main(String[] args) {
        try {
            readByScanner();
            readByStreamTokenizer();
            readByStreamTokenizerOnBufferedReader();
            readByBufferedInputStream();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void readByScanner() throws Exception {
        long startTime = System.currentTimeMillis();
    
        Scanner stdin = new Scanner(new File(FILE_NAME));
        int array[] = new int[n];
        for (int i = 0; i < n; i++) {
            array[i] = stdin.nextInt();
        }
    
        long endTime = System.currentTimeMillis();
        System.out.println(String.format("Total time by Scanner: %d ms", endTime - startTime));
    }
    
    public static void readByStreamTokenizer() throws Exception {
        long startTime = System.currentTimeMillis();
    
        StreamTokenizer st = new StreamTokenizer(new FileReader(FILE_NAME));
        int array[] = new int[n];
    
        for (int i = 0; st.nextToken() != StreamTokenizer.TT_EOF; i++) {
            array[i] = (int) st.nval;
        }
    
        long endTime = System.currentTimeMillis();
        System.out.println(String.format("Total time by StreamTokenizer: %d ms", endTime - startTime));
    }
    
    public static void readByStreamTokenizerOnBufferedReader() throws Exception {
        long startTime = System.currentTimeMillis();
    
        StreamTokenizer st = new StreamTokenizer(new BufferedReader(new FileReader(FILE_NAME)));
        int array[] = new int[n];
    
        for (int i = 0; st.nextToken() != StreamTokenizer.TT_EOF; i++) {
            array[i] = (int) st.nval;
        }
    
        long endTime = System.currentTimeMillis();
        System.out.println(String.format("Total time by StreamTokenizer with BufferedReader: %d ms", endTime - startTime));
    }
    
    public static void readByBufferedInputStream() throws Exception {
        long startTime = System.currentTimeMillis();
    
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(FILE_NAME));
        int array[] = new int[n];
        for (int i = 0; i < n; i++) {
            array[i] = readInt(bis);
        }
    
        long endTime = System.currentTimeMillis();
        System.out.println(String.format("Total time with BufferedInputStream: %d ms", endTime - startTime));
    }
    
    private static int readInt(InputStream in) throws IOException {
        int ret = 0;
        boolean dig = false;
    
        for (int c = 0; (c = in.read()) != -1; ) {
            if (c >= '0' && c <= '9') {
                dig = true;
                ret = ret * 10 + c - '0';
            } else if (dig) break;
        }
    
        return ret;
    }
    

    我得到的结果:

    • 扫描仪的总时间:789 毫秒
    • StreamTokenizer 的总时间:226 毫秒
    • StreamTokenizer 与 BufferedReader 的总时间:80 毫秒
    • BufferedInputStream 的总时间:95 毫秒

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-06
      • 2012-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-03
      • 2015-07-07
      相关资源
      最近更新 更多