如何在 Java 中处理一个巨大的单行文件
How to deal with a huge, one-line file in Java
我需要读取一个巨大的文件 (15+GB) 并执行一些小的修改(添加一些换行符以便不同的解析器可以实际使用它)。您可能会认为已经有正常执行此操作的答案:
- Reading a very huge file in java
- How to read a large text file line by line using Java?
但我的整个文件都在一行上。
到目前为止,我的一般方法非常基础:
char[] buffer = new char[X];
BufferedReader reader = new BufferedReader(new ReaderUTF8(new FileInputStream(new File("myFileName"))), X);
char[] bufferOut = new char[X+a little];
int bytesRead = -1;
int i = 0;
int offset = 0;
long totalBytesRead = 0;
int countToPrint = 0;
while((bytesRead = reader.read(buffer)) >= 0){
for(i = 0; i < bytesRead; i++){
if(buffer[i] == '}'){
bufferOut[i+offset] = '}';
offset++;
bufferOut[i+offset] = '\n';
}
else{
bufferOut[i+offset] = buffer[i];
}
}
writer.write(bufferOut, 0, bytesRead+offset);
offset = 0;
totalBytesRead += bytesRead;
countToPrint += 1;
if(countToPrint == 10){
countToPrint = 0;
System.out.println("Read "+((double)totalBytesRead / originalFileSize * 100)+" percent.");
}
}
writer.flush();
经过一些实验,我发现 X 的值大于一百万可以提供最佳速度 - 看起来我每 10 分钟获得大约 2%,而大约 60,000 的 X 值只能获得15 小时内 60%。分析显示我 96% 以上的时间都花在了 read() 方法上,所以这绝对是我的瓶颈。写这篇文章的时候,我的800万X版本在2小时40分钟后完成了32%的文件,如果你想知道它的长期表现如何。
有没有更好的方法来处理这么大的单行文件?比如,是否有一种更快的读取此类文件的方法,让我可以相对轻松地插入换行符?
我知道不同的语言或程序可能会很好地处理这个问题,但我将其限制在 Java 的角度。
你让事情变得比它应该的复杂得多。通过使用标准 类 已经提供的缓冲,您应该可以毫不费力地获得每秒至少几 MB 的吞吐量。
这个简单的测试程序在我的 PC 上处理 1GB 不到 2 分钟(包括创建 测试文件):
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Random;
public class TestFileProcessing {
public static void main(String[] argv) {
try {
long time = System.currentTimeMillis();
File from = new File("C:\Test\Input.txt");
createTestFile(from, StandardCharsets.UTF_8, 1_000_000_000);
System.out.println("Created file in: " + (System.currentTimeMillis() - time) + "ms");
time = System.currentTimeMillis();
File to = new File("C:\Test\Output.txt");
doIt(from, to, StandardCharsets.UTF_8);
System.out.println("Converted file in: " + (System.currentTimeMillis() - time) + "ms");
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public static void createTestFile(File file, Charset encoding, long size) throws IOException {
Random r = new Random(12345);
try (OutputStream fout = new FileOutputStream(file);
BufferedOutputStream bout = new BufferedOutputStream(fout);
Writer writer = new OutputStreamWriter(bout, encoding)) {
for (long i=0; i<size; ++i) {
int c = r.nextInt(26);
if (c == 0)
writer.write('}');
else
writer.write('a' + c);
}
}
}
public static void doIt(File from, File to, Charset encoding) throws IOException {
try (InputStream fin = new FileInputStream(from);
BufferedInputStream bin = new BufferedInputStream(fin);
Reader reader = new InputStreamReader(bin, encoding);
OutputStream fout = new FileOutputStream(to);
BufferedOutputStream bout = new BufferedOutputStream(fout);
Writer writer = new OutputStreamWriter(bout, encoding)) {
int c;
while ((c = reader.read()) >= 0) {
if (c == '}')
writer.write('\n');
writer.write(c);
}
}
}
}
如您所见,没有使用复杂的逻辑或过多的缓冲区大小。使用的只是缓冲最接近硬件的流,FileInput/OutputStream.
我需要读取一个巨大的文件 (15+GB) 并执行一些小的修改(添加一些换行符以便不同的解析器可以实际使用它)。您可能会认为已经有正常执行此操作的答案:
- Reading a very huge file in java
- How to read a large text file line by line using Java?
但我的整个文件都在一行上。
到目前为止,我的一般方法非常基础:
char[] buffer = new char[X];
BufferedReader reader = new BufferedReader(new ReaderUTF8(new FileInputStream(new File("myFileName"))), X);
char[] bufferOut = new char[X+a little];
int bytesRead = -1;
int i = 0;
int offset = 0;
long totalBytesRead = 0;
int countToPrint = 0;
while((bytesRead = reader.read(buffer)) >= 0){
for(i = 0; i < bytesRead; i++){
if(buffer[i] == '}'){
bufferOut[i+offset] = '}';
offset++;
bufferOut[i+offset] = '\n';
}
else{
bufferOut[i+offset] = buffer[i];
}
}
writer.write(bufferOut, 0, bytesRead+offset);
offset = 0;
totalBytesRead += bytesRead;
countToPrint += 1;
if(countToPrint == 10){
countToPrint = 0;
System.out.println("Read "+((double)totalBytesRead / originalFileSize * 100)+" percent.");
}
}
writer.flush();
经过一些实验,我发现 X 的值大于一百万可以提供最佳速度 - 看起来我每 10 分钟获得大约 2%,而大约 60,000 的 X 值只能获得15 小时内 60%。分析显示我 96% 以上的时间都花在了 read() 方法上,所以这绝对是我的瓶颈。写这篇文章的时候,我的800万X版本在2小时40分钟后完成了32%的文件,如果你想知道它的长期表现如何。
有没有更好的方法来处理这么大的单行文件?比如,是否有一种更快的读取此类文件的方法,让我可以相对轻松地插入换行符?
我知道不同的语言或程序可能会很好地处理这个问题,但我将其限制在 Java 的角度。
你让事情变得比它应该的复杂得多。通过使用标准 类 已经提供的缓冲,您应该可以毫不费力地获得每秒至少几 MB 的吞吐量。
这个简单的测试程序在我的 PC 上处理 1GB 不到 2 分钟(包括创建 测试文件):
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Random;
public class TestFileProcessing {
public static void main(String[] argv) {
try {
long time = System.currentTimeMillis();
File from = new File("C:\Test\Input.txt");
createTestFile(from, StandardCharsets.UTF_8, 1_000_000_000);
System.out.println("Created file in: " + (System.currentTimeMillis() - time) + "ms");
time = System.currentTimeMillis();
File to = new File("C:\Test\Output.txt");
doIt(from, to, StandardCharsets.UTF_8);
System.out.println("Converted file in: " + (System.currentTimeMillis() - time) + "ms");
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public static void createTestFile(File file, Charset encoding, long size) throws IOException {
Random r = new Random(12345);
try (OutputStream fout = new FileOutputStream(file);
BufferedOutputStream bout = new BufferedOutputStream(fout);
Writer writer = new OutputStreamWriter(bout, encoding)) {
for (long i=0; i<size; ++i) {
int c = r.nextInt(26);
if (c == 0)
writer.write('}');
else
writer.write('a' + c);
}
}
}
public static void doIt(File from, File to, Charset encoding) throws IOException {
try (InputStream fin = new FileInputStream(from);
BufferedInputStream bin = new BufferedInputStream(fin);
Reader reader = new InputStreamReader(bin, encoding);
OutputStream fout = new FileOutputStream(to);
BufferedOutputStream bout = new BufferedOutputStream(fout);
Writer writer = new OutputStreamWriter(bout, encoding)) {
int c;
while ((c = reader.read()) >= 0) {
if (c == '}')
writer.write('\n');
writer.write(c);
}
}
}
}
如您所见,没有使用复杂的逻辑或过多的缓冲区大小。使用的只是缓冲最接近硬件的流,FileInput/OutputStream.