Java和C#的deflate函数有什么区别?

7

我有两个用C#和Scala编写的deflate函数,当使用相同的输入运行时,返回的字节数组在前导字节和尾随字节上有所不同(中间字节的差异由C#和Scala之间的无符号/有符号字节机制引起,这是预期的)。

Scala中的deflate函数:

import java.io.ByteArrayOutputStream
import java.util.zip.{Deflater, DeflaterOutputStream}

import zio._


object ZDeflater {
  val deflater = ZManaged.makeEffectTotal(new Deflater(Deflater.DEFLATED, true))(_.end)

  val buffer = ZManaged.fromAutoCloseable(ZIO.succeed(new ByteArrayOutputStream()))

  val stream = for {
    d <- deflater
    b <- buffer
    s <- ZManaged.fromAutoCloseable(ZIO.succeed(new DeflaterOutputStream(b, d, true)))
  } yield (b, s)

  def deflate(input: Array[Byte]): RIO[blocking.Blocking, Array[Byte]] = stream.use { case (buffer, stream) =>
    for {
      ()    <- blocking.effectBlocking(stream.write(input))
      ()    <- blocking.effectBlocking(stream.flush())
      result = buffer.toByteArray
    } yield result
  }
}

C#中的压缩函数Deflate:

private static byte[] Deflate(byte[] uncompressedBytes)
{
    using (var output = new MemoryStream())
    {
        using (var zip = new DeflateStream(output, CompressionMode.Compress, true))
        {
            zip.Write(uncompressedBytes, 0, uncompressedBytes.Length);
        }

        return output.ToArray();
    }
}

解压缩后的输出: Scala:

ZDeflater.deflate(data.getBytes(StandardCharsets.UTF_8))

124, -111, …, 126, 1, 0, 0, -1, -1

C#:

Deflate(Encoding.UTF8.GetBytes(data))
125, 145, …, 126, 1

有人知道是什么原因导致第一个字节和最后一个字节之间的差异吗?任何假设对我都非常有帮助。感谢!

附:我们遇到了一个问题,即C#的Deflate输出适用于特定的第三方,而Scala的输出不适用。因此,我正在尝试弄清楚如何使Scala的输出与C#的输出相同。


2
快速反应而没有研究 - 请查看字节顺序标记。 - Adam Rabung
1个回答

7
此处所述,Java的Deflater类将字节序列压缩成ZLIB压缩数据格式。 ZLIB数据格式将压缩数据用DEFLATE数据格式进行封装,并在压缩数据后添加标题和ADLER-32校验和。
Microsoft的DeflateStream文档关于数据格式的准确性存在问题。实际上,它生成的数据采用原始的DEFLATE数据格式而不是ZLIB格式(dotnet-2236)。因此,其输出也与HTTP的“deflate”传输编码不兼容,后者实际上引用的是ZLIB数据格式而不是DEFLATE数据格式(RFC-2616)。
但是,如何使用Scala和C#实现相同的输出呢?
A)在Scala中也使用原始的DEFLATE格式来写入数据。 Deflater类有一个重载构造函数,带有一个nowrap参数,允许省略头和校验和。将此参数设置为true将导致以原始DEFLATE数据格式压缩的数据。如果您还计划在Java中反序列化数据,请仔细阅读Inflater构造函数的Javadoc。

B) 使用C#也以ZLIB格式写入数据(推荐)

使用.NET的ZLibStream类或任何第三方库,而不是Deflater类,将数据序列化为ZLIB格式。

C) 改用GZIP格式

GZIP格式与ZLIB相当,但使用不同的头部和不同的校验和。 .NET和Java都提供了专门的流类来处理它。尽管ZLIB的校验和计算性能更好,并且产生的头部比GZIP还要小,但后者更常见(特别是在Web中)。 GZIP流行的主要原因是Microsoft一直难以区分原始DEFLATE和ZLIB或HTTP的deflate传输编码(请参见ZLIB的FAQ-39;-)。


正如您在Scala片段中看到的那样,我已将nowrap参数设置为true,因此选项1似乎不正确。由于我们遇到了一种情况,即C#的Deflate输出适用于特定的第三方,而Scala的输出则不适用,我将首先尝试第3种方法。非常感谢您提供的详细解释。 - Cypherius
压缩是必要的步骤,因为我在 SAML 请求的编码过程中使用它,所以我认为使用 gzip 没有帮助(实际上我尝试过,它破坏了 SAML SSO 流程)。 - Cypherius
@Cypherius,我已经注意到你已经将nowrap设置为true。同时,我也检查了Java的本地代码和Microsoft对zlib的使用,似乎DeflateStream实现使用了不同的默认压缩级别。请尝试将Deflater的压缩级别设置为6而不是8,然后再次比较Scala和C#的输出。 - rmunge
没有可用的压缩级别(0-9)能够返回预期的结果 @rmunge - Cypherius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接