如何动态生成大数据流

时间:2014-05-19 16:40:12

标签: scala playframework-2.1

我必须动态生成一个大文件。读取数据库并将其发送给客户端。 我读了一些文档,我做了这个

val streamContent: Enumerator[Array[Byte]] = Enumerator.outputStream {
        os => 
              // new PrintWriter() read from database and for each record 
              // do some logic and write
              // to outputstream
      }
      Ok.stream(streamContent.andThen(Enumerator.eof)).withHeaders(
              CONTENT_DISPOSITION -> s"attachment; filename=someName.csv"
        )

我对scala一般都是新手一般只用了一个星期,因此不能指导我的声誉。

我的问题是:

1)这是最好的方法吗?我发现这个如果我有一个大文件,这将加载到内存中,并且也不知道在这种情况下什么是块大小,如果它将为每个write()发送不方便。

2)我发现这个方法Enumerator.fromStream(data : InputStream, chunkedSize : int)更好一点,因为它有一个块大小,但我没有一个inputStream导致即时创建文件。

1 个答案:

答案 0 :(得分:4)

docs for Enumerator.outputStream中有一条注释:

  

不是 [sic!] 对write的调用不会阻塞,所以如果被输入的iteratee使用输入的速度很慢,则OutputStream不会反推。这意味着它不应该与大流一起使用,因为存在内存不足的风险。

如果发生这种情况取决于您的情况。如果你可以并且将在几秒钟内产生千兆字节,你应该尝试不同的东西。我不确定是什么,但我从Enumerator.generateM()开始。但是对于很多情况来说,你的方法非常好。看看at this example by Gaëtan Renaudeau for serving a Zip file that's generated on the fly in the same way you're using it

val enumerator = Enumerator.outputStream { os =>
  val zip = new ZipOutputStream(os);
  Range(0, 100).map { i =>
    zip.putNextEntry(new ZipEntry("test-zip/README-"+i+".txt"))
    zip.write("Here are 100000 random numbers:\n".map(_.toByte).toArray)
    // Let's do 100 writes of 1'000 numbers
    Range(0, 100).map { j =>
      zip.write((Range(0, 1000).map(_=>r.nextLong).map(_.toString).mkString("\n")).map(_.toByte).toArray);
    }
    zip.closeEntry()
  }
  zip.close()
}
Ok.stream(enumerator >>> Enumerator.eof).withHeaders(
  "Content-Type"->"application/zip", 
  "Content-Disposition"->"attachment; filename=test.zip"
)

请注意,在较新版本的Play中Ok.stream已被Ok.chunked取代,以防您想要升级。

对于块大小,您始终可以使用Enumeratee.grouped来收集一堆值并将它们作为一个块发送。

val grouper = Enumeratee.grouped(  
  Traversable.take[Array[Double]](100) &>> Iteratee.consume()  
)

然后你会做类似

的事情
Ok.stream(enumerator &> grouper >>> Enumerator.eof)