如何读取大文件中的第二列

时间:2012-06-19 00:04:38

标签: java file

我有一个包含数百万列的巨大文件,按空格分割,但它只有有限的行数:

examples.txt:

1 2 3 4 5 ........
3 1 2 3 5 .........
l 6 3 2 2 ........

现在,我只想阅读第二栏:

2
1
6

如何在具有高性能的java中实现这一点。

由于

更新:文件通常是1.4G,包含数百行。

3 个答案:

答案 0 :(得分:2)

如果您的文件不是静态结构,那么您唯一的选择是天真的:通过字节序列读取文件字节序列以查找换行符并在每个列之后获取第二列。使用FileReader

如果您的文件是静态结构化的,您可以计算文件中给定行的第二列的位置,并直接计算seek()

答案 1 :(得分:0)

这是一个小型状态机,它使用FileInputStream作为输入并处理自己的缓冲。没有区域设置转换。

在具有1/2 Gb内存的7岁1.4 GHz笔记本电脑上,需要48秒才能完成12.8亿字节的数据。大于4Kb的缓冲区似乎运行得更慢。

在一台带有4Gb的新款1年MacBook上,它可以在14秒内运行。文件在缓存后,它运行2.7秒。同样,大于4Kb的缓冲区没有区别。这是相同的12亿字节数据文件。

我希望内存映射IO会做得更好,但这可能更便携。

它将获取您告诉它的任何列。

import java.io.*;
import java.util.Random;

public class Test {

public static class ColumnReader {

    private final InputStream is;
    private final int colIndex;
    private final byte [] buf;
    private int nBytes = 0;
    private int colVal = -1;
    private int bufPos = 0;

    public ColumnReader(InputStream is, int colIndex, int bufSize) {
        this.is = is;
        this.colIndex = colIndex;
        this.buf = new byte [bufSize];
    }

    /**
     * States for a tiny DFA to recognize columns.
     */
    private static final int START = 0;
    private static final int IN_ANY_COL = 1;
    private static final int IN_THE_COL = 2;
    private static final int WASTE_REST = 3;

    /**
     * Return value of colIndex'th column or -1 if none is found. 
     * 
     * @return value of column or -1 if none found.
     */
    public int getNext() {
        colVal = -1;
        bufPos = parseLine(bufPos);
        return colVal;
    }

    /**
     * If getNext() returns -1, this can be used to check if
     * we're at the end of file.
     * 
     * Otherwise the column did not exist.
     * 
     * @return end of file indication 
     */
    public boolean atEoF() {
        return nBytes == -1;
    }

    /**
     * Parse a line.  
     * The buffer is automatically refilled if p reaches the end.
     * This uses a standard DFA pattern.
     *
     * @param p position of line start in buffer
     * @return position of next unread character in buffer
     */
    private int parseLine(int p) {
        colVal = -1;
        int iCol = -1;
        int state = START;
        for (;;) {
            if (p == nBytes) {
                try {
                    nBytes = is.read(buf);
                } catch (IOException ex) {
                    nBytes = -1;
                }
                if (nBytes == -1) {
                    return -1;
                } 
                p = 0;
            }
            byte ch = buf[p++];
            if (ch == '\n') {
                return p;
            }
            switch (state) {
                case START:
                    if ('0' <= ch && ch <= '9') {
                        if (++iCol == colIndex) {
                            state = IN_THE_COL;
                            colVal = ch - '0';
                        } 
                        else { 
                            state = IN_ANY_COL;
                        }
                    }
                    break;

                case IN_THE_COL:
                    if ('0' <= ch && ch <= '9') {
                        colVal = 10 * colVal + (ch - '0');
                    }
                    else {
                        state = WASTE_REST;
                    }
                    break;

                case IN_ANY_COL:
                    if (ch < '0' || ch > '9') {
                        state = START;
                    }
                    break;

                case WASTE_REST:
                    break;
            }
        }
    }
}

public static void main(String[] args) {
    final String fn = "data.txt";
    if (args.length > 0 && args[0].equals("--create-data")) {
        PrintWriter pw;
        try {
            pw = new PrintWriter(fn);
        } catch (FileNotFoundException ex) {
            System.err.println(ex.getMessage());
            return;
        }
        Random gen = new Random();
        for (int row = 0; row < 100; row++) {
            int rowLen = 4 * 1024 * 1024 + gen.nextInt(10000);
            for (int col = 0; col < rowLen; col++) {
                pw.print(gen.nextInt(32));
                pw.print((col < rowLen - 1) ? ' ' : '\n');
            }
        }
        pw.close();
    }

    FileInputStream fis;
    try {
        fis = new FileInputStream(fn);
    } catch (FileNotFoundException ex) {
        System.err.println(ex.getMessage());
        return;
    }
    ColumnReader cr = new ColumnReader(fis, 1, 4 * 1024);
    int val;
    long start = System.currentTimeMillis();
    while ((val = cr.getNext()) != -1) {
        System.out.print('.');
    }
    long stop = System.currentTimeMillis();
    System.out.println("\nelapsed = " + (stop - start) / 1000.0);
}
}

答案 2 :(得分:0)

我必须同意@gene,首先尝试使用BufferedReader和getLine,它的编码简单易行。注意不要在getLine的结果和您使用的任何子字符串操作之间为支持数组添加别名。 String.substring()是一个特别常见的罪魁祸首,我在内存中锁定了多MB字节数组,因为3-char子字符串正在引用它。

假设使用ASCII,我这样做的首选是下拉到字节级别。使用mmap将文件视为ByteBuffer,然后对0x20和0x0A执行线性扫描(假设使用unix样式的行分隔符)。然后将相关字节转换为String。如果你使用的是8位字符集,那么速度比这要快得多。

如果您使用的是Unicode,那么问题就会变得非常复杂,我强烈建议您使用BufferedReader,除非该性能确实是不可接受的。如果getLine()不起作用,请考虑循环调用read()

无论在从外部字节流初始化String时,都应始终指定Charset。这会明确记录您的charset假设。因此,我建议对基因的建议进行微小的修改,以便其中一个:

int i = Integer.parseInt(new String(buffer, start, length, "US-ASCII"));

int i = Integer.parseInt(new String(buffer, start, length, "ISO-8859-1"));

int i = Integer.parseInt(new String(buffer, start, length, "UTF-8"));

酌情。

相关问题