我有一个巨大的文件,拥有数百万列,由空格分隔,但只有有限的行数:
examples.txt:
1 2 3 4 5 ........
3 1 2 3 5 .........
l 6 3 2 2 ........
现在,我只想读取第二列:
2
1
6
我该如何在Java中实现高性能?
谢谢。
更新:文件通常包含数百行,大小为1.4G。
我有一个巨大的文件,拥有数百万列,由空格分隔,但只有有限的行数:
examples.txt:
1 2 3 4 5 ........
3 1 2 3 5 .........
l 6 3 2 2 ........
2
1
6
我该如何在Java中实现高性能?
谢谢。
更新:文件通常包含数百行,大小为1.4G。
FileReader
。seek()
到该位置。这里有一个小状态机,它使用FileInputStream
作为输入,处理自己的缓冲。没有区域转换。
在我的7年前的1.4 GHz笔记本电脑上,内存为1/2 Gb,在浏览了12.8亿字节的数据后需要48秒。缓冲区大于4KB似乎运行较慢。
在一台新的1年前的4Gb 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);
}
}
我必须同意 @gene 的观点,首先尝试使用 BufferedReader 和 getLine,这很简单且易于编码。只需小心不要在使用任何子字符串操作时将支持数组别名与 getLine 的结果重叠。String.substring() 是一个特别常见的罪魁祸首,我曾经因为一个 3 字符子字符串引用了它而锁定了多 MB 的字节数组。
假设是 ASCII 编码,我在执行此操作时更喜欢降到字节级别。使用 mmap 将文件视为 ByteBuffer,然后进行线性扫描以查找 0x20 和 0x0A(假设是 Unix 风格的行分隔符)。然后将相关字节转换为 String。如果您使用的是 8 位字符集,则极难比这更快。
如果您使用 Unicode,则问题会更加复杂,我强烈建议您除非性能真的无法接受,否则请使用 BufferedReader。如果 getLine() 不起作用,则考虑只循环调用 read()。
无论如何,初始化来自外部字节流的 String 时,您应始终指定 Charset。这明确记录了您的字符集假设。因此,我建议对 gene 的建议进行轻微修改,其中之一:
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"));
根据情况而定。