【问题标题】:Java vs C# GZip CompressionJava 与 C# GZip 压缩
【发布时间】:2021-05-06 15:45:53
【问题描述】:

知道为什么 Java 的 GZIPOutputStream 压缩字符串与我的 .NET 的 GZIP 压缩字符串不同吗?

Java 代码:

package com.company;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Base64;

public class Main {

    public static void main(String[] args) {
        String myValue = "<Grid type=\"mailing_activity_demo\"><ReturnFields><DataElement>mailing_id</DataElement></ReturnFields></Grid>";

        int length = myValue.length();

        byte[] compressionResult = null;

        try {
            compressionResult = MyUtils.compress(myValue);
        } catch (IOException e) {
            e.printStackTrace();
        }

        byte[] headerBytes = ByteBuffer.allocate(4).putInt(length).array();

        byte[] fullBytes = new byte[headerBytes.length + compressionResult.length];

        System.arraycopy(headerBytes, 0, fullBytes, 0, headerBytes.length);

        System.arraycopy(compressionResult, 0, fullBytes, headerBytes.length, compressionResult.length);

        String result = Base64.getEncoder().encodeToString(fullBytes);
        System.out.println((result));
    }
}




package com.company;

import javax.sound.sampled.AudioFormat;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPOutputStream;

public class MyUtils
{

    private static Object BitConverter;

    public static byte[] compress(String data) throws IOException
    {
        ByteBuffer buffer = StandardCharsets.UTF_8.encode(data);
        System.out.println(buffer.array().length);
        System.out.println(data.length());
        ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length());

        GZIPOutputStream gzip = new GZIPOutputStream(bos);

        gzip.write(data.getBytes());

        gzip.close();

        byte[] compressed = bos.toByteArray();

        bos.close();

        return compressed;

    }

}

我从上面得到的字符串是:

AAAAbB+LCAAAAAAAAP+zcS/KTFEoqSxItVXKTczMycxLj09MLsksyyypjE9Jzc1XsrMJSi0pLcpzy0zNSSm2s3FJLEl0zUnNTc0rsYPpyEyx0UcWt9FH1aMPssUOAKHavIJsAAAA

来自 .NET c# 代码:

    public static string CompressData(string data)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            byte[] plainBytes = Encoding.UTF8.GetBytes(data);

            using (GZipStream zipStream = new GZipStream(memoryStream, CompressionMode.Compress, leaveOpen: true))
            {
                zipStream.Write(plainBytes, 0, plainBytes.Length);
            }

            memoryStream.Position = 0;

            byte[] compressedBytes = new byte[memoryStream.Length + CompressedMessageHeaderLength];

            Buffer.BlockCopy(
                BitConverter.GetBytes(plainBytes.Length),
                0,
                compressedBytes,
                0,
                CompressedMessageHeaderLength
            );

            // Add the header, which is the length of the compressed message.
            memoryStream.Read(compressedBytes, CompressedMessageHeaderLength, (int)memoryStream.Length);

            string compressedXml = Convert.ToBase64String(compressedBytes);

            return compressedXml;
        }
    }

压缩字符串:

bAAAAB+LCAAAAAAABACzcS/KTFEoqSxItVXKTczMycxLj09MLsksyyypjE9Jzc1XsrMJSi0pLcpzy0zNSSm2s3FJLEl0zUnNTc0rsYPpyEyx0UcWt9FH1aMPssUOAKHavIJsAAAA

知道我在 Java 代码中做错了什么吗?

【问题讨论】:

  • 为什么认为你在做任何“错误”的事情。它是具有大致相同压缩率和大小的有效 zip 吗?这很可能是某些参数的微小差异。我不会仅仅因为两个结果不匹配 100% 就断定你在做“错误”的事情。事实上,我早就料到了。
  • 第一个不同的 base64 字符是您写入流的标头的一部分——与 gzip 无关。您是唯一可以确定为什么 plainBytes.Length 在这两种情况下不同的人。
  • 实际上,这看起来像是字节顺序问题?长度被写入具有不同字节序的标题中。 .NET 的 BitConverter.GetBytes 将使用您机器的字节序,对于 x86 是 little-endian,但 Java 的 ByteBuffer 默认为 big-endian。要么将 ByteBuffer 配置为使用小端,要么告诉 .NET 使用大端
  • 我将您的问题退回到包含压缩字符串以及 .NET 和 Java 用于编写标头的不同代码的版本。这些都是解决这个问题所必需的重要信息。编辑问题,使其不再可解决,并且接受的答案没有意义,对任何人都没有帮助。

标签: java c# gzip gzipstream


【解决方案1】:

要添加到@MarcGravell 关于 GZip 编码差异的答案,值得注意的是,您的标头字节似乎存在字节顺序问题,这会弄乱解码器。

您的标头是 4 个字节,编码为 5 1/3 base64 字符。 .NET 版本输出 bAAAAB(前 4 个字节为 6c 00 00 00),而 Java 版本输出 AAAAbB(前 4 个字节为 00 00 00 6c)。 b 在 A 的海洋中移动了大约 5 个字符这一事实是您的第一条线索(A 代表 base64 中的 000000),但解码它会使问题变得明显。

.NET 的 BitConverter 使用您机器架构的字节序,在 x86 上是小字节序(检查 BitConverter.IsLittleEndian)。 Java 的 ByteBuffer defaults to big-endian,但是是可配置的。这就解释了为什么一个写小端,另一个写大端。

您需要确定字节顺序,然后对齐两边。您可以通过调用 .order(ByteBuffer.LITTLE_ENDIAN) 将 ByteBuffer 更改为使用 little-endian。在 .NET 中,如果您使用的是 .NET Core 2.1+,则可以使用 BinaryPrimitives.WriteInt32BigEndian / BinaryPrimitives.WriteInt32LittleEndian 以显式字节顺序编写,或者在必要时使用 IPAddress.HostToNetworkOrder 切换字节顺序(取决于 BitConverter.IsLittleEndian)如果您'之前被困在某事上。

【讨论】:

  • @MarcGravell 你已经删除了你的答案!我会考虑取消删除它——这是对其余差异的一个很好的解释
  • 将其更改为以下内容,但仍然得到相同的字符串。那是我应该做的吗? ByteBuffer 缓冲区 = StandardCharsets.UTF_8.encode(data); buffer.order(ByteOrder.LITTLE_ENDIAN);
  • @John 你关心的是byte[] headerBytes = ByteBuffer.allocate(4).putInt(length).array();——这需要是小端的。那是写入标头字节的位,并且使用了错误的字节序。例如。 byte[] headerBytes = ByteBuffer.allocate(4).order(ByteBuffer.LITTLE_ENDIAN).putInt(length).array();。 gzip 的字节似乎没有任何问题(我不希望有,因为GZIPOutputStream 将处理单个字节,其中字节顺序无关紧要)
  • @John 很高兴听到这个消息!如果它解决了您的问题,请考虑支持/接受此答案
猜你喜欢
  • 2015-07-14
  • 1970-01-01
  • 2015-09-19
  • 1970-01-01
  • 2013-10-01
  • 2020-05-28
  • 2012-01-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多