我正在写很多像贝娄这样的文件。
public void call(Iterator<Tuple2<Text, BytesWritable>> arg0)
throws Exception {
// TODO Auto-generated method stub
while (arg0.hasNext()) {
Tuple2<Text, BytesWritable> tuple2 = arg0.next();
System.out.println(tuple2._1().toString());
PrintWriter writer = new PrintWriter("/home/suv/junk/sparkOutPut/"+tuple2._1().toString(), "UTF-8");
writer.println(new String(tuple2._2().getBytes()));
writer.close();
}
}
有没有更好的方法来编写文件..无需每次关闭或创建printwriter。
答案 0 :(得分:2)
没有明显更好的方法来编写大量文件。你正在做的事情本身就是I / O密集型。
更新 - @Michael Anderson是对的,我想。使用多个线程来编写文件(可能)会大大加快速度。但是,I / O仍然是几个方面的最终瓶颈:
创建,打开和关闭文件涉及文件&amp;目录元数据访问和更新。这需要非常重要的CPU。
需要将文件数据和元数据更改写入光盘。这可能是多次光盘写入。
每个文件至少有3个系统调用。
然后是线程拼接开销。
除非写入每个文件的数据量很大(每个文件多个千字节),否则我怀疑使用NIO,直接缓冲区,JNI等技术是值得的。真正的瓶颈将出现在内核中:文件系统操作和低级磁盘I / O.
...每次都不关闭或创建版画。
没有。您需要为每个文件创建一个新的PrintWriter
(或Writer
或OutputStream
)。
然而,这......
writer.println(new String(tuple2._2().getBytes()));
......看起来很奇怪。你似乎是:
getBytes()
(?),String
String
println()
上的String
方法,并在最终输出之前将其转换回字节。是什么给出的?字符串有什么意义 - &gt; bytes - &gt;字符串转换?
我只是这样做:
writer.println(tuple2._2());
这应该更快,但我不希望百分比加速到那么大。
答案 1 :(得分:1)
我假设你是追求最快的方式。因为每个人都知道最快最好;)
一种简单的方法是使用一堆线程为您写作。 但是,除非你的文件系统能够很好地扩展,否则你不会从中获得太多好处。 (我在基于Lustre的集群系统上使用这种技术,并且在“大量文件”可能意味着10k的情况下 - 在这种情况下,许多写入将转到不同的服务器/磁盘)
代码看起来像这样:(注意我认为这个版本不适合填充工作队列的少量文件 - 但无论如何都要查看下一个版本的版本......)
public void call(Iterator<Tuple2<Text, BytesWritable>> arg0) throws Exception {
int nThreads=5;
ExecutorService threadPool = Executors.newFixedThreadPool(nThreads);
ExecutorCompletionService<Void> ecs = new ExecutorCompletionService<>(threadPool);
int nJobs = 0;
while (arg0.hasNext()) {
++nJobs;
final Tuple2<Text, BytesWritable> tuple2 = arg0.next();
ecs.submit(new Callable<Void>() {
@Override Void call() {
System.out.println(tuple2._1().toString());
String path = "/home/suv/junk/sparkOutPut/"+tuple2._1().toString();
try(PrintWriter writer = new PrintWriter(path, "UTF-8") ) {
writer.println(new String(tuple2._2().getBytes()))
}
return null;
}
});
}
for(int i=0; i<nJobs; ++i) {
ecs.take().get();
}
}
更好的是,只要有第一个数据就开始编写文件,而不是在获得所有文件的数据时 - 并且写入时不会阻止计算线程。
要执行此操作,请将应用程序拆分为多个通过(线程安全)队列进行通信的部分。
然后代码看起来更像是这样:
public void main() {
SomeMultithreadedQueue<Data> queue = ...;
int nGeneratorThreads=1;
int nWriterThreads=5;
int nThreads = nGeneratorThreads + nWriterThreads;
ExecutorService threadPool = Executors.newFixedThreadPool(nThreads);
ExecutorCompletionService<Void> ecs = new ExecutorCompletionService<>(threadPool);
AtomicInteger completedGenerators = new AtomicInteger(0);
// Start some generator threads.
for(int i=0; ++i; i<nGeneratorThreads) {
ecs.submit( () -> {
while(...) {
Data d = ... ;
queue.push(d);
}
if(completedGenerators.incrementAndGet()==nGeneratorThreads) {
queue.push(null);
}
return null;
});
}
// Start some writer threads
for(int i=0; i<nWriterThreads; ++i) {
ecs.submit( () -> {
Data d
while((d = queue.take())!=null) {
String path = data.path();
try(PrintWriter writer = new PrintWriter(path, "UTF-8") ) {
writer.println(new String(data.getBytes()));
}
return null;
}
});
}
for(int i=0; i<nThreads; ++i) {
ecs.take().get();
}
}
注意我没有提供队列类的实现,你可以轻松地包装标准的java线程类,以获得你需要的东西。
还有很多可以做的事情来减少延迟等等 - 这是我用来减少时间的一些其他事情......
甚至不等待为给定文件生成所有数据。传递包含要写入的字节数据包的另一个队列。
注意分配 - 您可以重复使用某些缓冲区。
nio中存在一些延迟 - 使用C写入和JNI以及直接缓冲区可以提高性能。
线程切换可能会受到影响,并且队列中的延迟可能会受到影响,因此您可能需要稍微批量处理数据。将此与1平衡可能会非常棘手。