代码之家  ›  专栏  ›  技术社区  ›  EdgeCaseBerg

将Excel文件流式传输到内存中的S3?为什么zipOutputstream上的byteOutputstream.reset()会导致写入失败?

  •  1
  • EdgeCaseBerg  · 技术社区  · 8 年前

    我目前正在与ApachePoi合作创建一个Excel文件。我想通过 multipart upload 将此文件发送到AWS S3。 我使用的是 sxsfworkbook =“nofollow noreferrer”>biggriddemo 以便创建文档本身并发送工作表数据。这是有点棘手的地方。我有一些东西大部分都在工作,但是生成的Excel文件无效,因为组成工作表数据的XML文件中写入了 nul s。

    在试图追查 为什么 发生这种情况时,我偶然发现了:

    < Prime>代码>导入java. 导入java. val bo=新的bytearrayOutputstream()。 val zo=新的zipOutputstream(bo) ZO.Putnextery(新ZipEntry(“1”)) zo.write(“hello”.getbytes()) zo.write(“\nhello”.getbytes()) val字节1=bo.tobytearray() //字节1:数组[字节]=数组(80、75、3、4、20、0、8、8、0、107、-121、-9、76、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、1、0、0、0、0、49) Bo.ReSET() zo.write(“hello”.getbytes()) val bytes2=bo.tobytearray()//字节2:array[byte]=array()) 浮点() val bytes2=bo.tobytearray()//字节2:array[byte]=array()) bo.size//res11:int=0 zo.putnextentry()//如果我创建一个新条目,它会起作用,但我不能在真正的代码中这样做… bo.size//res17:int=66 < /代码>

    当我重置底层的字节输出流时,它似乎会导致zipOutputstream记录下任何内容。这让我很惊讶,所以我开始研究zippoutputstream的基础源代码 我注意到默认的方法是收缩的,它只是调用了一个HRIF:=“http://Github.com /OpenDJDK镜像/JDK7U/JDK/BLUB/MST/SRC/Stuty/Stask/Java/UTL/ZIP/DopaToRutoStudio.java=L199”Re= =“NoFoLoLoNeFror”> DopaToRoPuthStudioScript编写< < /A>,然后我查看了Butter代码本身,认为可能在压缩中有更深层的东西。我不明白的sion算法要求流不被重置,或者受到它的某种影响。我找到了一个引用:“HRIF:=”http://gthub.com /Openjdk镜像/jdk7u jdk/Bulb/Mrd/Src/Stuty/Stase/Java/UTL/ZIP/Dopter。 < Buff行情>

    压缩状态被重置,这样,如果以前的压缩数据已损坏或需要随机访问,对压缩输出数据工作的充气器可以从此点重新启动。

    < /块引用>

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

    < Prime>代码>导入java. 导入java. val bo=新的bytearrayOutputstream()。 val zo=新的zipOutputstream(bo) ZO.设定液位(放气阀,完全冲洗) ZO.Putnextery(新ZipEntry(“1”)) zo.write(“hello”.getbytes()) val字节1=bo.tobytearray() //字节1:数组[字节]=数组(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、0、0、0、1、0、0、0、0、49) 浮点() Bo.ReSET() zo.write(“\nhello”.getbytes()) 浮点() val bytes2=bo.tobytearray()//字节2:array[byte]=array()) < /代码> 所以没有骰子。我在这里的目标是将所有内容都保存在内存中(因此是字节数组),并通过删除我已经写入uploadpartrequest的字节来降低内存压力,但这确实给事情带来了麻烦,因为我觉得XML文件 必须进行压缩,因为Excel文件格式实际上是一个zip文件。我的完整代码显然有点复杂,并且使用了 play framework和scala 2.12.6, 好的,或者运行它。

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

    这就意味着,尽管我的实验没有显示字节,但在某个时刻,会发生更多的字节,并且会在nuls最终结束后写入文件。

    所以…为什么会这样?为什么 BytearrayOutputstream.reset()会导致写入 ZipOutputstream的问题?如果我不调用.reset(),则似乎 BytearrayOutputstream 将展开,直到它变大并导致内存不足错误?还是我不必担心,因为数据无论如何都会被压缩?. 我正在使用 SXSSFWorkbook 结合了 BigGridDemo 以便创建文档本身并发送工作表数据。这是有点棘手的地方。我有一些大部分工作,但正在生成一个无效的Excel文件,因为 NUL 正在写入组成工作表数据的XML文件中。

    在试图追查 为什么? 我偶然发现:

    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 然后,我研究了deflater代码本身,认为在压缩算法中可能有更深层的东西,我不理解它要求流不被重置,或者受到它的某种影响。我找到一个参考 FULL_FLUSH 并注意到

    压缩状态被重置,这样,如果以前的压缩数据已损坏或需要随机访问,可以从此点重新启动处理压缩输出数据的充气器。

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

    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

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

    所以…为什么会这样?为什么 ByteArrayOutputStream.reset() 导致在 ZipOutputStream ?如果我不调用.reset(),则 ByteArrayOutputStream 会一直扩展到很大,导致内存不足错误吗?还是我不必担心,因为数据无论如何都会被压缩?

    2 回复  |  直到 8 年前
        1
  •  1
  •   xtratic Rob    8 年前

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

    类似 CipherStreams 以及其他过滤流, DeflaterOutputStream 因此 ZipOutputStream 事实上 写入底层流(您的 ByteArrayOutputStream )直到它可以/需要(有时甚至当你冲洗时)。

    我相信这是一个 ZipInputStream 它可能只在某些块大小或关闭 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看起来像是从它在 努尔 就像这里: <c r="C1 ... 940" t="inlineStr"> 所以看起来你好像在写所有的数据,只是在散布 努尔 S.

        2
  •  0
  •   acarlstein ironhide96    8 年前

    从Bell实验室检查这个PDF,提高Java服务器性能的实用指南: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.99.3674&rep=rep1&type=pdf

    它讨论了所有的事情,包括重置方法的使用。

    另外,看看这篇文章: http://java-performance.info/java-io-bytearrayoutputstream/

    最后,对于内存不足等问题,您应该总是有一个尝试/捕获。

    如果我不调用.reset(),那么bytearrayOutputstream将 扩展到很大并导致内存不足错误?

    让我知道这是否有帮助。

    推荐文章