我想将包含字符串的大文件合并到一个文件中,并尝试使用 nio2。我不想将整个文件加载到内存中,所以我用 BufferedReader 尝试了:
public void mergeFiles(filesToBeMerged) throws IOException{
Path mergedFile = Paths.get("mergedFile");
Files.createFile(mergedFile);
List<Path> _filesToBeMerged = filesToBeMerged;
try (BufferedWriter writer = Files.newBufferedWriter(mergedFile,StandardOpenOption.APPEND)) {
for (Path file : _filesToBeMerged) {
// this does not work as write()-method does not accept a BufferedReader
writer.append(Files.newBufferedReader(file));
}
} catch (IOException e) {
System.err.println(e);
}
}
我用这个尝试过,这有效,但是,字符串的格式(例如新行等不会复制到合并文件中):
...
try (BufferedWriter writer = Files.newBufferedWriter(mergedFile,StandardOpenOption.APPEND)) {
for (Path file : _filesToBeMerged) {
// writer.write(Files.newBufferedReader(file));
String line = null;
BufferedReader reader = Files.newBufferedReader(file);
while ((line = reader.readLine()) != null) {
writer.append(line);
writer.append(System.lineSeparator());
}
reader.close();
}
} catch (IOException e) {
System.err.println(e);
}
...
如何使用 NIO2 合并大文件而不将整个文件加载到内存中?
如果您想有效地合并两个或更多文件,您应该问自己,为什么要使用基于
char
的 Reader
和 Writer
来执行该任务。
通过使用这些类,您可以将文件的字节转换为字符,从系统默认编码转换为 unicode,然后再从 unicode 转换回系统默认编码。这意味着程序必须对整个文件执行两次数据转换。
顺便说一句,
BufferedReader
和BufferedWriter
绝不是NIO2
人工制品。这些类自 Java 的第一个版本以来就存在。
当您通过真正的 NIO 函数使用字节复制时,文件可以在不被 Java 应用程序触及的情况下进行传输,在最好的情况下,传输将直接在文件系统的缓冲区中执行:
import static java.nio.file.StandardOpenOption.*;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
public class MergeFiles
{
public static void main(String[] arg) throws IOException {
if(arg.length<2) {
System.err.println("Syntax: infiles... outfile");
System.exit(1);
}
Path outFile=Paths.get(arg[arg.length-1]);
System.out.println("TO "+outFile);
try(FileChannel out=FileChannel.open(outFile, CREATE, WRITE)) {
for(int ix=0, n=arg.length-1; ix<n; ix++) {
Path inFile=Paths.get(arg[ix]);
System.out.println(inFile+"...");
try(FileChannel in=FileChannel.open(inFile, READ)) {
for(long p=0, l=in.size(); p<l; )
p+=in.transferTo(p, l-p, out);
}
}
}
System.out.println("DONE.");
}
}
与
Files.newBufferedReader(file).readLine()
你每次创建一个新的缓冲区,它总是在第一行重置。
替换为
BufferedReader reader = Files.newBufferedReader(file);
while ((line = reader.readLine()) != null) {
writer.write(line);
}
完成后和
.close()
读者。
readLine()
不会产生行结尾 ("
“ 或者 ”
“)。这就是错误。
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.write("\r\n"); // Windows
}
您也可以忽略对(可能不同的)行结尾的过滤,并使用
try (OutputStream out = new FileOutputStream(file);
for (Path source : filesToBeMerged) {
Files.copy(path, out);
out.write("\r\n".getBytes(StandardCharsets.US_ASCII));
}
}
在最后一行不以换行符结尾的情况下,这会显式写入换行符。
在文件开头将文本标记为 UTF-8/UTF-16LE/UTF-16BE 的可选丑陋 Unicode BOM 字符可能仍然存在问题。
我尝试用 3 种方式将文件合并为一个文件。 我测试了这些方法,但我仍然不知道哪一种最好。 我认为 FileChannel 比其他的更快,但它不适合我。 如果你们有任何疑问,请告诉我。
private static void mergeFiles(List<Path> sources, Path destination) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(destination.toFile(), true))) {
for (Path path : sources) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path.toFile())))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void mergeFiles2(List<Path> sources, Path destination) {
try {
BinaryOperator<InputStream> sequenceInputStream = SequenceInputStream::new;
List<InputStream> inputStreams = new ArrayList<>();
for (Path path : sources) {
InputStream is = Files.newInputStream(path, StandardOpenOption.READ);
inputStreams.add(is);
}
InputStream streams = inputStreams.parallelStream().reduce(sequenceInputStream).orElseThrow(() -> new IllegalStateException("inputStreams reduce exception"));
Files.copy(streams, destination, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void mergeFiles3(List<Path> sources, Path destination) {
try (FileChannel desChannel = FileChannel.open(destination, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
for (Path path : sources) {
try (FileChannel srcChannel = FileChannel.open(path, StandardOpenOption.READ)) {
for (long position = 0, size = srcChannel.size(); position < size; ) {
position += srcChannel.transferTo(position, size - position, desChannel);
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}