gpt4 book ai didi

java - 将 Excel 文件流式传输到 S3 内存中?为什么 ZipOutputStream 上的 ByteOutputStream.reset() 会导致写入无法工作?

转载 作者:行者123 更新时间:2023-11-30 06:05:09 25 4
gpt4 key购买 nike

我目前正在使用 Apache POI 创建一个 Excel 文件。我想通过 multipart upload 将此文件发送到 AWS S3 。我正在使用SXSSFWorkbook结合 BigGridDemo 使用的替换技术为了创建文档本身并发送工作表数据。这就是有点棘手的地方。我的一些东西大部分工作正常,但由于 NUL 被写入组成工作表数据的 XML 文件中,因此生成了无效的 excel 文件。

在试图找出发生这种情况的原因时,我偶然发现了这一点:

import java.io._
import java.util.zip._
val bo = new ByteArrayOutputStream()
val zo = new ZipOutputStream(bo)
zo.putNextEntry(new ZipEntry("1"))
zo.write("hello".getBytes())
zo.write("\nhello".getBytes())
val bytes1 = bo.toByteArray()
// bytes1: Array[Byte] = Array(80, 75, 3, 4, 20, 0, 8, 8, 8, 0, 107, -121, -9, 76, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 49)

bo.reset()
zo.write("hello".getBytes())
val bytes2 = bo.toByteArray() // bytes2: Array[Byte] = Array()
zo.flush()
val bytes2 = bo.toByteArray() // bytes2: Array[Byte] = Array()
bo.size //res11: Int = 0
zo.putNextEntry() // If I make a new entry it works but I can't do this in real code...
bo.size // res17: Int = 66

似乎当我重置底层字节输出流时,它会导致 ZipOutputStream 不再记录任何内容。这让我很惊讶,所以我去查看了underlying source code of ZipOutputStream 。我注意到默认方法是 DEFLATED,它只调用 DeflaterOutputStream#write ,然后我研究了压缩器代码本身,认为压缩算法中可能有一些我不明白的更深层次的东西,要求流不被重置或者以某种方式受到它的影响。我找到了对 FULL_FLUSH 的引用并指出

The compression state is reset so that the inflater that works on the compressed output data can restart from this point if previous compressed data has been damaged or if random access is desired.

这对我来说听起来不错,因为我可以想象重置字节流可能会被视为损坏的数据。所以我重复了我的最小实验:

import java.io._
import java.util.zip._
val bo = new ByteArrayOutputStream()
val zo = new ZipOutputStream(bo)
zo.setLevel(Deflater.FULL_FLUSH)
zo.putNextEntry(new ZipEntry("1"))
zo.write("hello".getBytes())

val bytes1 = bo.toByteArray()
// bytes1: Array[Byte] = Array(80, 75, 3, 4, 20, 0, 8, 8, 8, 0, 84, 75, -8, 76, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 49)

zo.flush()
bo.reset()
zo.write("\nhello".getBytes())
zo.flush()
val bytes2 = bo.toByteArray() // bytes2: Array[Byte] = Array()

所以没有骰子。我的目标是将所有内容保留在内存中(因此是字节数组),并通过删除已经写入 UploadPartRequest 的字节来保持较低的内存压力,但这确实给事情带来了麻烦,因为我的印象是XML 文件必须被压缩,因为 Excel 文件格式实际上是一个 zip 文件。我的完整代码显然有点复杂,并且使用 Play framework和 Scala 2.12.6,I've put it on github here and added some additional comments if you'd like to look at it or run it.

我知道我可以通过先将 Excel 文件写入磁盘然后上传它来完成将此文件部分上传到 s3,但出于我的目的,我希望有一个全内存解决方案,所以我没有处理生成大型临时文件时 Web 服务器上的磁盘空间问题。通过保持生成的行在生成时上传,我认为每次上传的内存压力应该保持相当恒定。以下是当前代码在 xml 文件工作表数据中生成的内容:

enter image description here

...

enter image description here

这对我来说意味着,尽管我的实验没有显示任何字节,但在某些时候会发生更多字节,并会在 NUL 最终结束后写入文件。

那么...为什么会发生这种情况?为什么 ByteArrayOutputStream.reset() 会导致在 ZipOutputStream 上写入时出现问题?如果我不调用 .reset() ,ByteArrayOutputStream 似乎会扩展直至变得巨大并导致内存不足错误?或者我不应该担心,因为数据无论如何都会被压缩?

最佳答案

我不认为这是 ByteArrayOutputStream.reset() 的错.

类似于CipherStreams和其他过滤器流,DeflaterOutputStream因此ZipOutputStream 实际上不会写入底层流(您的 ByteArrayOutputStream ),直到它可以/需要(有时甚至在您刷新时)。

我相信在这种情况下 ZipInputStream它可能只在某些 block 大小上或在 ZipEntry 关闭时写入底层流。 ;不太确定,但这是我的猜测。

示例:

val bo = new ByteArrayOutputStream()
val zo = new ZipOutputStream(bo)
zo.putNextEntry(new ZipEntry("example entry"))

// v prints the entry header bytes v
println(bo.toString())

zo.write("hello".getBytes())
zo.flush();

// v still only the entry header bytes v
println(bo.toString())

我在 ExcelStreamingToS3Service - line 155 中注意到一件事您可能想更改为 zos.write(byteBuffer, offset, offset + bytesRead) ,或类似的东西。写入完整缓冲区肯定可能是导致所有这些的原因 NUL字符,因为您的缓冲区在读取过程中可能尚未填充,并且仍然有许多空索引。毕竟,看起来 xml 继续从 NUL 之前中断的地方继续。就像这里:<c r="C1 ... 940" t="inlineStr">所以看起来你确实正在编写所有数据,只是将其与 NUL 散布在一起s。

关于java - 将 Excel 文件流式传输到 S3 内存中?为什么 ZipOutputStream 上的 ByteOutputStream.reset() 会导致写入无法工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51500989/

25 4 0
文章推荐: java - Hibernate 仅在基本类型上指定一次 @Column 注解
文章推荐: javascript - 如何在 Rails 3 中切换