iOS 和 Android 上 zlib 通缩的不同结果。如何获得相同的结果?

Different result from zlib deflation on iOS and Android. How to obtain same result?

目前在我的 iOS 应用程序中,我正在使用 zlib 压缩数据,我想在 Android 中实现相同的逻辑,以便在这两个应用程序中处理压缩数据平台兼容,可以转移。

在下面的代码中,inputString 都是任意随机字符串,例如:

Developers trust Stack Overflow to help solve coding problems and use Stack Overflow Careers to find job opportunities. We’re committed to making the internet a better place, and our products aim to enrich the lives of developers as they grow and mature in their careers.

在iOS中使用了如下代码段:

NSData *rawData = [inputString dataUsingEncoding:NSUTF8StringEncoding];
NSInputStream * src = [NSInputStream inputStreamWithData:rawData];
[src open];
NSOutputStream * dest = [NSOutputStream outputStreamToMemory];
[dest open];
int res = [self deflateDataStream:src toOutputStream:dest level:Z_DEFAULT_COMPRESSION];
[dest close];
[src close];

if (res != Z_OK) return nil;

NSData *ret = [dest propertyForKey:NSStreamDataWrittenToMemoryStreamKey];

+ (int) deflateDataStream:(NSInputStream *)source toOutputStream:(NSOutputStream *)dest level:(int)level {
    int ret, flush;
    unsigned have;
    z_stream strm;
    unsigned char inBuf[CHUNK];
    unsigned char outBuf[CHUNK];

    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    ret = deflateInit2(&strm, level, Z_DEFLATED, (16+MAX_WBITS), MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
    if (ret != Z_OK) return ret;

    do {
        NSInteger res = [source read:inBuf maxLength:CHUNK];
        if (res < 0)  {
            NSLog(@"!!! Error reading stream: %ld %@", (long)source.streamStatus, source.streamError);
            (void)deflateEnd(&strm);
            return Z_ERRNO;
        }

        flush = [source hasBytesAvailable] ? Z_NO_FLUSH : Z_FINISH;
        strm.avail_in = (uInt)res;
        strm.next_in = inBuf;

        do {
            strm.avail_out = CHUNK;
            strm.next_out = outBuf;
            ret = deflate(&strm, flush);
            assert(ret != Z_STREAM_ERROR);
            have = CHUNK - strm.avail_out;

            res = [dest write:outBuf maxLength:have];
            if (res != have || res < 0) {
                (void)deflateEnd(&strm);
                return Z_ERRNO;
            }
        } while (strm.avail_out == 0);
        assert(strm.avail_in == 0);
    } while (flush != Z_FINISH);
    assert(ret == Z_STREAM_END);

    (void)deflateEnd(&strm);
    return Z_OK;
}

之后压缩数据将被进一步处理(进行加密等)并保存。

然后对于我目前正在处理的 Android 版本,从文档页面 here Deflater class 使用 zlib 逻辑执行紧缩,所以我尝试使用以下代码段:

byte[] dataToBeDeflated = inputString.getBytes(Charset.forName("UTF-8"));

Deflater deflater = null;
ByteArrayOutputStream outputStream = null;
byte[] deflatedData = null;

try {
    deflater = new Deflater();
    deflater.setStrategy(Deflater.DEFAULT_STRATEGY);
    deflater.setLevel(Deflater.DEFAULT_COMPRESSION);
    deflater.setInput(dataToBeDeflated);
    outputStream = new ByteArrayOutputStream(dataToBeDeflated.length);
    deflater.finish();
    byte[] buffer = new byte[1024];
    while (!deflater.finished()) {
        int count = deflater.deflate(buffer);
        outputStream.write(buffer, 0, count);
    }

    deflatedData = outputStream.toByteArray();
} catch (Exception e) {
    Log.e(TAG, "Deflate exception", e);
} finally {
    if (outputStream != null) {
        try {
            outputStream.close();
        } catch (IOException e) {
            Log.e(TAG, "Failed to close the output stream", e);
        }
    }
}

但是,上述实现在 Android 上返回的结果与 iOS 中的结果不同,因此我现有的 iOS 应用程序无法使用它。

使用我引用的测试字符串,iOS 产生大小为 197 字节的 NSData,其中原始字符串数据为 273 字节。虽然 Android 上的原始输入大小也是 273 字节,但上面的实现给出了大小为 185 的结果。

更改 iOS 方面的逻辑目前不可行,因为这将涉及许多额外的流程,例如提交审核等。

我假设两个平台的底层算法应该相同?如果是这样,为什么结果不同?我是不是做错了什么,如何更正它并在 Android 上获得相同的结果?

谢谢!

好吧,您使用的是不同级别,在 iOS 上您使用的是 MAX_MEM_LEVEL (9),而在 Android 上您使用的是 DEFAULT_COMPRESSION(-1)。尝试在 Android 上使用 BEST_COMPRESSION(9)。

deflateInit2() 中的 16+MAX_WBITS 请求 gzip 格式,而 Deflater class 请求 zlib 格式。可以去掉iOS代码中的16+请求zlib格式

请注意,输出可能仍然不同,因为不要求来自不同压缩器的压缩数据对于相同的输入是相同的。重要的是你从解压器得到的东西和你给任何压缩器的东西是一样的。

iOS中有两种deflate方法:

1. deflateInit(strm, level)
2. deflateInit2(strm, level, method, windowBits, memLevel, strategy)

第一个兼容 java 的放气器。还要确保您在 iOS 和 Java(Android) 中使用相同的压缩级别。

压缩级别:

   -1: default
    0: NO_COMPRESSION
    1: BEST_SPEED //generally used 
       ......
    9: BEST_COMPRESSION