如何使用与 .Net 兼容的 GZIPOutputStream 压缩和解压缩字符串?

发布于 2024-11-23 20:16:02 字数 3832 浏览 2 评论 0原文

我需要一个在 android 中使用 GZip 压缩字符串的示例。我想向该方法发送一个像“hello”这样的字符串并获取以下压缩字符串:

BQAAAB+LCAAAAAAABADtvQdgHEmWJSYvbcp7f0r1StfgdKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXW YWQMztnbz33nvvvffee++997o7nU4n99//P1xmZAFs9s5K2smeIYCqyB8/fnwfPyLmeVlW/w+GphA2BQAAAA==

然后我需要解压它。有人可以给我一个例子并完成以下方法吗?

private String compressString(String input) {
    //...
}

private String decompressString(String input) {
    //...
}

谢谢,


更新

根据后人的回答,现在我有以下4种方法。 Android 和.net 压缩和解压缩方法。除一种情况外,这些方法彼此兼容。我的意思是它们在前 3 个状态下兼容,但在第 4 个状态下不兼容:

  • 状态 1) Android.compress <-> Android.解压缩:(确定
  • 状态 2) Net.compress <-> Net.decompress: (确定)
  • 状态 3) Net.compress -> Android.解压缩:(确定
  • 状态 4) Android.compress -> .Net.解压缩:(不行

有人能解决吗?

Android 方法:

public static String compress(String str) throws IOException {

    byte[] blockcopy = ByteBuffer
            .allocate(4)
            .order(java.nio.ByteOrder.LITTLE_ENDIAN)
            .putInt(str.length())
            .array();
    ByteArrayOutputStream os = new ByteArrayOutputStream(str.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(str.getBytes());
    gos.close();
    os.close();
    byte[] compressed = new byte[4 + os.toByteArray().length];
    System.arraycopy(blockcopy, 0, compressed, 0, 4);
    System.arraycopy(os.toByteArray(), 0, compressed, 4,
            os.toByteArray().length);
    return Base64.encode(compressed);

}

public static String decompress(String zipText) throws IOException {
    byte[] compressed = Base64.decode(zipText);
    if (compressed.length > 4)
    {
        GZIPInputStream gzipInputStream = new GZIPInputStream(
                new ByteArrayInputStream(compressed, 4,
                        compressed.length - 4));

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        for (int value = 0; value != -1;) {
            value = gzipInputStream.read();
            if (value != -1) {
                baos.write(value);
            }
        }
        gzipInputStream.close();
        baos.close();
        String sReturn = new String(baos.toByteArray(), "UTF-8");
        return sReturn;
    }
    else
    {
        return "";
    }
}

.Net 方法:

public static string compress(string text)
{
    byte[] buffer = Encoding.UTF8.GetBytes(text);
    MemoryStream ms = new MemoryStream();
    using (GZipStream zip = new GZipStream(ms, CompressionMode.Compress, true))
    {
        zip.Write(buffer, 0, buffer.Length);
    }

    ms.Position = 0;
    MemoryStream outStream = new MemoryStream();

    byte[] compressed = new byte[ms.Length];
    ms.Read(compressed, 0, compressed.Length);

    byte[] gzBuffer = new byte[compressed.Length + 4];
    System.Buffer.BlockCopy(compressed, 0, gzBuffer, 4, compressed.Length);
    System.Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gzBuffer, 0, 4);
    return Convert.ToBase64String(gzBuffer);
}

public static string decompress(string compressedText)
{
    byte[] gzBuffer = Convert.FromBase64String(compressedText);
    using (MemoryStream ms = new MemoryStream())
    {
        int msgLength = BitConverter.ToInt32(gzBuffer, 0);
        ms.Write(gzBuffer, 4, gzBuffer.Length - 4);

        byte[] buffer = new byte[msgLength];

        ms.Position = 0;
        using (GZipStream zip = new GZipStream(ms, CompressionMode.Decompress))
        {
            zip.Read(buffer, 0, buffer.Length);
        }

        return Encoding.UTF8.GetString(buffer);
    }
}

I need an example for compressing a string using GZip in android. I want to send a string like "hello" to the method and get the following zipped string:

BQAAAB+LCAAAAAAABADtvQdgHEmWJSYvbcp7f0r1StfgdKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee++997o7nU4n99//P1xmZAFs9s5K2smeIYCqyB8/fnwfPyLmeVlW/w+GphA2BQAAAA==

Then I need to decompress it. Can anybody give me an example and complete the following methods?

private String compressString(String input) {
    //...
}

private String decompressString(String input) {
    //...
}

Thanks,


update

According to scessor's answer, Now I have the following 4 methods. Android and .net compress and decompress methods. These methods are compatible with each other except in one case. I mean they are compatible in the first 3 states but incompatible in the 4th state:

  • state 1) Android.compress <-> Android.decompress: (OK)
  • state 2) Net.compress <-> Net.decompress: (OK)
  • state 3) Net.compress -> Android.decompress: (OK)
  • state 4) Android.compress -> .Net.decompress: (NOT OK)

can anybody solve it?

Android methods:

public static String compress(String str) throws IOException {

    byte[] blockcopy = ByteBuffer
            .allocate(4)
            .order(java.nio.ByteOrder.LITTLE_ENDIAN)
            .putInt(str.length())
            .array();
    ByteArrayOutputStream os = new ByteArrayOutputStream(str.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(str.getBytes());
    gos.close();
    os.close();
    byte[] compressed = new byte[4 + os.toByteArray().length];
    System.arraycopy(blockcopy, 0, compressed, 0, 4);
    System.arraycopy(os.toByteArray(), 0, compressed, 4,
            os.toByteArray().length);
    return Base64.encode(compressed);

}

public static String decompress(String zipText) throws IOException {
    byte[] compressed = Base64.decode(zipText);
    if (compressed.length > 4)
    {
        GZIPInputStream gzipInputStream = new GZIPInputStream(
                new ByteArrayInputStream(compressed, 4,
                        compressed.length - 4));

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        for (int value = 0; value != -1;) {
            value = gzipInputStream.read();
            if (value != -1) {
                baos.write(value);
            }
        }
        gzipInputStream.close();
        baos.close();
        String sReturn = new String(baos.toByteArray(), "UTF-8");
        return sReturn;
    }
    else
    {
        return "";
    }
}

.Net methods:

public static string compress(string text)
{
    byte[] buffer = Encoding.UTF8.GetBytes(text);
    MemoryStream ms = new MemoryStream();
    using (GZipStream zip = new GZipStream(ms, CompressionMode.Compress, true))
    {
        zip.Write(buffer, 0, buffer.Length);
    }

    ms.Position = 0;
    MemoryStream outStream = new MemoryStream();

    byte[] compressed = new byte[ms.Length];
    ms.Read(compressed, 0, compressed.Length);

    byte[] gzBuffer = new byte[compressed.Length + 4];
    System.Buffer.BlockCopy(compressed, 0, gzBuffer, 4, compressed.Length);
    System.Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gzBuffer, 0, 4);
    return Convert.ToBase64String(gzBuffer);
}

public static string decompress(string compressedText)
{
    byte[] gzBuffer = Convert.FromBase64String(compressedText);
    using (MemoryStream ms = new MemoryStream())
    {
        int msgLength = BitConverter.ToInt32(gzBuffer, 0);
        ms.Write(gzBuffer, 4, gzBuffer.Length - 4);

        byte[] buffer = new byte[msgLength];

        ms.Position = 0;
        using (GZipStream zip = new GZipStream(ms, CompressionMode.Decompress))
        {
            zip.Read(buffer, 0, buffer.Length);
        }

        return Encoding.UTF8.GetString(buffer);
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

蝶…霜飞 2024-11-30 20:16:02

GZIP 方法:

public static byte[] compress(String string) throws IOException {
    ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(string.getBytes());
    gos.close();
    byte[] compressed = os.toByteArray();
    os.close();
    return compressed;
}

public static String decompress(byte[] compressed) throws IOException {
    final int BUFFER_SIZE = 32;
    ByteArrayInputStream is = new ByteArrayInputStream(compressed);
    GZIPInputStream gis = new GZIPInputStream(is, BUFFER_SIZE);
    StringBuilder string = new StringBuilder();
    byte[] data = new byte[BUFFER_SIZE];
    int bytesRead;
    while ((bytesRead = gis.read(data)) != -1) {
        string.append(new String(data, 0, bytesRead));
    }
    gis.close();
    is.close();
    return string.toString();
}

和一个测试:

final String text = "hello";
try {
    byte[] compressed = compress(text);
    for (byte character : compressed) {
        Log.d("test", String.valueOf(character));
    }
    String decompressed = decompress(compressed);
    Log.d("test", decompressed);
} catch (IOException e) {
    e.printStackTrace();
}

=== 更新 ===

如果您需要 .Net 兼容性,我的代码必须稍作更改:

public static byte[] compress(String string) throws IOException {
    byte[] blockcopy = ByteBuffer
        .allocate(4)
        .order(java.nio.ByteOrder.LITTLE_ENDIAN)
        .putInt(string.length())
        .array();
    ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(string.getBytes());
    gos.close();
    os.close();
    byte[] compressed = new byte[4 + os.toByteArray().length];
    System.arraycopy(blockcopy, 0, compressed, 0, 4);
    System.arraycopy(os.toByteArray(), 0, compressed, 4, os.toByteArray().length);
    return compressed;
}

public static String decompress(byte[] compressed) throws IOException {
    final int BUFFER_SIZE = 32;
    ByteArrayInputStream is = new ByteArrayInputStream(compressed, 4, compressed.length - 4);
    GZIPInputStream gis = new GZIPInputStream(is, BUFFER_SIZE);
    StringBuilder string = new StringBuilder();
    byte[] data = new byte[BUFFER_SIZE];
    int bytesRead;
    while ((bytesRead = gis.read(data)) != -1) {
        string.append(new String(data, 0, bytesRead));
    }
    gis.close();
    is.close();
    return string.toString();
}

您可以使用相同的测试脚本。

The GZIP methods:

public static byte[] compress(String string) throws IOException {
    ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(string.getBytes());
    gos.close();
    byte[] compressed = os.toByteArray();
    os.close();
    return compressed;
}

public static String decompress(byte[] compressed) throws IOException {
    final int BUFFER_SIZE = 32;
    ByteArrayInputStream is = new ByteArrayInputStream(compressed);
    GZIPInputStream gis = new GZIPInputStream(is, BUFFER_SIZE);
    StringBuilder string = new StringBuilder();
    byte[] data = new byte[BUFFER_SIZE];
    int bytesRead;
    while ((bytesRead = gis.read(data)) != -1) {
        string.append(new String(data, 0, bytesRead));
    }
    gis.close();
    is.close();
    return string.toString();
}

And a test:

final String text = "hello";
try {
    byte[] compressed = compress(text);
    for (byte character : compressed) {
        Log.d("test", String.valueOf(character));
    }
    String decompressed = decompress(compressed);
    Log.d("test", decompressed);
} catch (IOException e) {
    e.printStackTrace();
}

=== Update ===

If you need .Net compability my code has to be changed a little:

public static byte[] compress(String string) throws IOException {
    byte[] blockcopy = ByteBuffer
        .allocate(4)
        .order(java.nio.ByteOrder.LITTLE_ENDIAN)
        .putInt(string.length())
        .array();
    ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(string.getBytes());
    gos.close();
    os.close();
    byte[] compressed = new byte[4 + os.toByteArray().length];
    System.arraycopy(blockcopy, 0, compressed, 0, 4);
    System.arraycopy(os.toByteArray(), 0, compressed, 4, os.toByteArray().length);
    return compressed;
}

public static String decompress(byte[] compressed) throws IOException {
    final int BUFFER_SIZE = 32;
    ByteArrayInputStream is = new ByteArrayInputStream(compressed, 4, compressed.length - 4);
    GZIPInputStream gis = new GZIPInputStream(is, BUFFER_SIZE);
    StringBuilder string = new StringBuilder();
    byte[] data = new byte[BUFFER_SIZE];
    int bytesRead;
    while ((bytesRead = gis.read(data)) != -1) {
        string.append(new String(data, 0, bytesRead));
    }
    gis.close();
    is.close();
    return string.toString();
}

You can use the same test script.

忘你却要生生世世 2024-11-30 20:16:02

不管是什么,将“Hello”压缩为 BQAAAB+LC... 都是 gzipper 的一个特别糟糕的实现。它使用动态块而不是 deflate 格式的静态块,将“Hello”扩展得远远超出了必要的范围。删除 gzip 流的四字节前缀(始终以十六进制 1f 8b 开头)后,“Hello”扩展到 123 字节。在压缩领域,这被认为是犯罪。

您所抱怨的压缩方法工作正常。它生成一个静态块,总共输出 25 个字节。 gzip 格式具有 10 字节标头和 8 字节尾部开销,使 5 字节输入被编码为 7 字节。这更像是这样。

不可压缩的流将被扩展,但扩展幅度不应太大。 gzip 使用的 deflate 格式将为不可压缩数据每 16K 到 64K 添加 5 个字节。

为了获得实际的压缩,通常您需要给压缩器更多的时间来处理这五个字节,以便它可以在可压缩数据中找到重复的字符串和有偏差的统计信息。我知道您只是用短字符串进行测试。但在实际应用中,您永远不会使用具有如此短字符串的通用压缩器,因为只发送字符串总是更好。

Whatever it was that compressed "Hello" to BQAAAB+LC... is a particularly poor implementation of a gzipper. It expanded "Hello" far, far more than necessary, using a dynamic block instead of a static block in the deflate format. After removing the four-byte prefix to the gzip stream (which always starts with hex 1f 8b), "Hello" was expanded to 123 bytes. In the world of compression, that is considered a crime.

The Compress method that you are complaining about is working correctly and properly. It is generating a static block and a total output of 25 bytes. The gzip format has a ten-byte header and eight-byte trailer overhead, leaving the five-byte input having been coded in seven bytes. That's more like it.

Streams that are not compressible will be expanded, but it shouldn't be by much. The deflate format used by gzip will add five bytes to every 16K to 64K for incompressible data.

To get actual compression, in general you need to give the compressor much more to work with that five bytes, so that it can find repeated strings and biased statistics in compressible data. I understand that you were just doing tests with a short string. But in an actual application, you would never use a general-purpose compressor with such short strings, since it would always be better to just send the string.

等风来 2024-11-30 20:16:02

Decompress() 方法中,Base64 解码输入的前 4 个字节在传递给 GZipInputStream 之前会被跳过。在本例中,这些字节为 05 00 00 00。因此,在 Compress() 方法中,必须将这些字节放回到 Base64 编码之前。

如果我这样做,压缩()返回以下内容:

BQAAAB+LCAAAAAAAAADLSM3JyQcAhqYQNgUAAAA=

我知道这与您的期望不完全相同,即:

BQAAAB+LCAAAAAAABADtvQdgHEmWJSYvbcp7f0r1StfgdKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee++997o7nU4n99//P1xmZAFs9s5K2smeIYCqyB8/fnwfPyLmeVlW/w+GphA2BQAAAA==

但是,如果我的结果插回Decompress(),我想你仍然会得到“Hello”。尝试一下。差异可能是由于获得原始字符串的压缩级别不同所致。

那么神秘的前缀字节 05 00 00 00 是什么?根据这个答案,它可能是压缩字符串的长度,以便程序知道解压缩的字节缓冲区应该有多长。但在本例中这仍然不符合。

这是 compress() 的修改代码:

public static String Compress(String text) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    // TODO: Should be computed instead of being hard-coded
    baos.write(new byte[]{0x05, 0, 0, 0}, 0, 4);

    GZIPOutputStream gzos = new GZIPOutputStream(baos);
    gzos.write(text.getBytes());
    gzos.close();

    return Base64.encode(baos.toByteArray());
}

更新:

Android 中的输出字符串与 .NET 代码不匹配的原因是 .NET GZip 实现执行更快的压缩(因此输出更大)。这可以通过查看原始 Base64 解码字节值来确定:

.NET:

1F8B 0800 0000 0000 0400 EDBD 0760 1C49
9625 262F 6DCA 7B7F 4AF5 4AD7 E074 A108
8060 1324 D890 4010 ECC1 88CD E692 EC1D
6947 2329 AB2A 81CA 6556 655D 6616 40CC
ED9D BCF7 DE7B EFBD F7DE 7BEF BDF7 BA3B
9D4E 27F7 DFFF 3F5C 6664 016C F6CE 4ADA
C99E 2180 AAC8 1F3F 7E7C 1F3F 22E6 7959
56FF 0F86 A610 3605 0000 00

我的 Android 版本:

1F8B 0800 0000 0000 0000 CB48 CDC9 C907
0086 A610 3605 0000 00

现在,如果我们检查 GZip 文件格式,我们看到 .NET 和 Android 版本在初始阶段基本相同标头和尾部 CRC32 &大小字段。唯一的区别在于以下字段:

  • 在 .NET 中,XFL = 04(压缩器使用最快的算法),而在 Android 中为 00
  • 实际压缩块

因此,从 XFL 字段可以清楚地看出 .NET 压缩算法产生更长的输出。

事实上,当我使用这些原始数据值创建一个二进制文件,然后使用gunzip解压缩它们时,.NET 和 Android 版本都给出完全相同的输出 “你好”。

因此,您不必担心不同的结果。

In your Decompress() method, the first 4 bytes of the Base64 decoded input are skipped before passing to GZipInputStream. These bytes are found to be 05 00 00 00 in this particular case. So in the Compress() method, these bytes have to be put back in just before the Base64 encode.

If I do this, Compress() returns the following:

BQAAAB+LCAAAAAAAAADLSM3JyQcAhqYQNgUAAAA=

I know that this is not exactly the same as your expectation, which is:

BQAAAB+LCAAAAAAABADtvQdgHEmWJSYvbcp7f0r1StfgdKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee++997o7nU4n99//P1xmZAFs9s5K2smeIYCqyB8/fnwfPyLmeVlW/w+GphA2BQAAAA==

But, if my result is plugged back into Decompress(), I think you'll still get "Hello". Try it. The difference may be due to the different compression level with which you got the original string.

So what are the mysterious prefixed bytes 05 00 00 00? According to this answer it may be the length of the compressed string so that the program knows how long the decompressed byte buffer should be. Still that does not tally in this case.

This is the modified code for compress():

public static String Compress(String text) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    // TODO: Should be computed instead of being hard-coded
    baos.write(new byte[]{0x05, 0, 0, 0}, 0, 4);

    GZIPOutputStream gzos = new GZIPOutputStream(baos);
    gzos.write(text.getBytes());
    gzos.close();

    return Base64.encode(baos.toByteArray());
}

Update:

The reason why the output strings in Android and your .NET code don't match is that the .NET GZip implementation does a faster compression (and thus larger output). This can be verified for sure by looking at the raw Base64 decoded byte values:

.NET:

1F8B 0800 0000 0000 0400 EDBD 0760 1C49
9625 262F 6DCA 7B7F 4AF5 4AD7 E074 A108
8060 1324 D890 4010 ECC1 88CD E692 EC1D
6947 2329 AB2A 81CA 6556 655D 6616 40CC
ED9D BCF7 DE7B EFBD F7DE 7BEF BDF7 BA3B
9D4E 27F7 DFFF 3F5C 6664 016C F6CE 4ADA
C99E 2180 AAC8 1F3F 7E7C 1F3F 22E6 7959
56FF 0F86 A610 3605 0000 00

My Android version:

1F8B 0800 0000 0000 0000 CB48 CDC9 C907
0086 A610 3605 0000 00

Now if we check the GZip File Format, we see that both the .NET and Android versions are mostly identical in the initial header and trailing CRC32 & Size fields. The only differences are in the below fields:

  • XFL = 04 (compressor used fastest algorithm) in the case of .NET, whereas it's 00 in Android
  • The actual compressed blocks

So it's clear from the XFL field that the .NET compression algorithm produces longer output.

Infact, when I creates a binary file with these raw data values and then uncompressed them using gunzip, both the .NET and Android versions gave exactly the same output as "hello".

So you don't have to bother about the differing results.

差↓一点笑了 2024-11-30 20:16:02

我在我的项目中尝试了你的代码,发现Android上的压缩方法存在编码错误:

byte[] blockcopy = ByteBuffer
        .allocate(4)
        .order(java.nio.ByteOrder.LITTLE_ENDIAN)
        .putInt(str.length())
        .array();
ByteArrayOutputStream os = new ByteArrayOutputStream(str.length());
GZIPOutputStream gos = new GZIPOutputStream(os);
gos.write(str.getBytes());

在上面的代码中,你应该使用正确的编码,并填充字节长度,而不是字符串长度:

byte[] data = str.getBytes("UTF-8");

byte[] blockcopy = ByteBuffer
        .allocate(4)
        .order(java.nio.ByteOrder.LITTLE_ENDIAN)
        .putInt(data.length)
            .array();

ByteArrayOutputStream os = new ByteArrayOutputStream( data.length );    
GZIPOutputStream gos = new GZIPOutputStream(os);
gos.write( data );

I tried your code in my project, and found a encoding bug in compress method on Android:

byte[] blockcopy = ByteBuffer
        .allocate(4)
        .order(java.nio.ByteOrder.LITTLE_ENDIAN)
        .putInt(str.length())
        .array();
ByteArrayOutputStream os = new ByteArrayOutputStream(str.length());
GZIPOutputStream gos = new GZIPOutputStream(os);
gos.write(str.getBytes());

on above code, u should use the corrected encoding, and fill the bytes length, not the string length:

byte[] data = str.getBytes("UTF-8");

byte[] blockcopy = ByteBuffer
        .allocate(4)
        .order(java.nio.ByteOrder.LITTLE_ENDIAN)
        .putInt(data.length)
            .array();

ByteArrayOutputStream os = new ByteArrayOutputStream( data.length );    
GZIPOutputStream gos = new GZIPOutputStream(os);
gos.write( data );
相守太难 2024-11-30 20:16:02

好吧,我讨厌在有大量现有答案时插话,但不幸的是,由于各种原因,大多数答案都是错误的:

  • .NET Framework 中的 GZIP 算法之间存在差异。如果您使用 .NET 4.5,您在不同答案中看到的大多数抱怨根本不适用于您(而不是那些使用 2.0 或 3.5 的人)。如果您使用“固定”版本的代码,您实际上会弄乱压缩/解压缩。
  • Java 使用无符号字节[],.NET 使用有符号字节[]。这可能会在传输过程中导致问题,具体取决于您传输该 byte[] 的方式。
  • 我使用 Base64 来传输 byte[],这可能会带来更多问题。还有各种其他原因,但让我们跳过进一步的抱怨并开始讨论代码...

如果您使用 .NET Framework 4.5,这里是您需要的 C# 类(Base64 作为奖励):

public class CompressString
{
    private static void CopyTo(Stream src, Stream dest)
    {
        byte[] bytes = new byte[4096];
        int cnt;

        while ((cnt = src.Read(bytes, 0, bytes.Length)) != 0)
        {
            dest.Write(bytes, 0, cnt);
        }
    }

    public static byte[] Zip(string str)
    {
        var bytes = Encoding.UTF8.GetBytes(str);

        using (var msi = new MemoryStream(bytes))
        using (var mso = new MemoryStream())
        {
            using (var gs = new GZipStream(mso, CompressionMode.Compress))
            {
                //msi.CopyTo(gs);
                CopyTo(msi, gs);
            }

            return mso.ToArray();
        }
    }

    public static string Unzip(byte[] bytes)
    {
        using (var msi = new MemoryStream(bytes))
        using (var mso = new MemoryStream())
        {
            using (var gs = new GZipStream(msi, CompressionMode.Decompress))
            {
                //gs.CopyTo(mso);
                CopyTo(gs, mso);
            }

            return Encoding.UTF8.GetString(mso.ToArray());
        }
    }

    // Base64
    public static string ZipBase64(string compress)
    {
        var bytes = Zip(compress);
        var encoded = Convert.ToBase64String(bytes, Base64FormattingOptions.None);
        return encoded;
    }

    public static string UnzipBase64(string compressRequest)
    {
        var bytes = Convert.FromBase64String(compressRequest);
        var unziped = Unzip(bytes);
        return unziped;
    }

    // Testing
    public static bool TestZip(String stringToTest)
    {
        byte[] compressed = Zip(stringToTest);
        Debug.WriteLine("Compressed to " + compressed.Length + " bytes");
        String decompressed = Unzip(compressed);
        Debug.WriteLine("Decompressed to: " + decompressed);

        return stringToTest == decompressed;
    }
}

这是您需要的 Android/Java 类:

public class CompressString {
    public static byte[] compress(String string) {
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
            GZIPOutputStream gos = new GZIPOutputStream(os);
            gos.write(string.getBytes());
            gos.close();
            byte[] compressed = os.toByteArray();
            os.close();
            return compressed;
        } catch (IOException ex) {
            return null;
        }
    }

    public static String decompress(byte[] compressed) {
        try {
            final int BUFFER_SIZE = 32;
            ByteArrayInputStream is = new ByteArrayInputStream(compressed);
            GZIPInputStream gis = new GZIPInputStream(is, BUFFER_SIZE);
            byte[] data = new byte[BUFFER_SIZE];
            int bytesRead;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while ((bytesRead = gis.read(data)) != -1) {
                baos.write(data, 0, bytesRead);
            }
            gis.close();
            is.close();
            return baos.toString("UTF-8");
        } catch (IOException ex) {
            return null;
        }
    }    

    // Base64
    public static String compressBase64(String strToCompress) {
        byte[] compressed = compress(strToCompress);
        String encoded = android.util.Base64.encodeToString(compressed, android.util.Base64.NO_WRAP);
        return encoded;
    }

    public static String decompressBase64(String strEncoded) {
        byte[] decoded = android.util.Base64.decode(strEncoded, android.util.Base64.NO_WRAP);
        String decompressed = decompress(decoded);
        return decompressed;
    }


    // test
    public static boolean testCompression(String stringToTest) {
        byte[] compressed = compress(stringToTest);
        Log.d("compress-test", "Compressed to " + compressed.length + " bytes");
        String decompressed = decompress(compressed);
        Log.d("compress-test", "Decompressed to " + decompressed);

        return stringToTest.equals(decompressed);
    }
}

所以,你就可以了 - 无依赖、100% 工作压缩的 Android/Java/C#/.NET 类。如果您发现不适用于 .NET 4.5 的字符串(我已经尝试过从“Hello world”到 1000 字短篇小说的所有内容),请告诉我。

OK, I hate chiming in when there are tons of existing answers, but unfortunately most of them are simply wrong for variety of reasons:

  • There are differences between GZIP algorithms within .NET Framework. If you are using .NET 4.5 most of the complaints you see in different answers simply don't apply to you (rather to those who use 2.0 or 3.5). If you go with "fixed" versions of code you'll actually mess-up compression/decompression.
  • Java uses unsigned byte[], .NET uses signed byte[]. This may cause problems during transport depending on how exactly you are transporting that byte[].
  • I've used Base64 to transport byte[] which can introduce even more problems. There are variety of other reasons, but let's skip further whining and get to the code...

If you are using .NET Framework 4.5 here is C# class you need (Base64 as a bonus):

public class CompressString
{
    private static void CopyTo(Stream src, Stream dest)
    {
        byte[] bytes = new byte[4096];
        int cnt;

        while ((cnt = src.Read(bytes, 0, bytes.Length)) != 0)
        {
            dest.Write(bytes, 0, cnt);
        }
    }

    public static byte[] Zip(string str)
    {
        var bytes = Encoding.UTF8.GetBytes(str);

        using (var msi = new MemoryStream(bytes))
        using (var mso = new MemoryStream())
        {
            using (var gs = new GZipStream(mso, CompressionMode.Compress))
            {
                //msi.CopyTo(gs);
                CopyTo(msi, gs);
            }

            return mso.ToArray();
        }
    }

    public static string Unzip(byte[] bytes)
    {
        using (var msi = new MemoryStream(bytes))
        using (var mso = new MemoryStream())
        {
            using (var gs = new GZipStream(msi, CompressionMode.Decompress))
            {
                //gs.CopyTo(mso);
                CopyTo(gs, mso);
            }

            return Encoding.UTF8.GetString(mso.ToArray());
        }
    }

    // Base64
    public static string ZipBase64(string compress)
    {
        var bytes = Zip(compress);
        var encoded = Convert.ToBase64String(bytes, Base64FormattingOptions.None);
        return encoded;
    }

    public static string UnzipBase64(string compressRequest)
    {
        var bytes = Convert.FromBase64String(compressRequest);
        var unziped = Unzip(bytes);
        return unziped;
    }

    // Testing
    public static bool TestZip(String stringToTest)
    {
        byte[] compressed = Zip(stringToTest);
        Debug.WriteLine("Compressed to " + compressed.Length + " bytes");
        String decompressed = Unzip(compressed);
        Debug.WriteLine("Decompressed to: " + decompressed);

        return stringToTest == decompressed;
    }
}

And here is Android/Java class you need:

public class CompressString {
    public static byte[] compress(String string) {
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
            GZIPOutputStream gos = new GZIPOutputStream(os);
            gos.write(string.getBytes());
            gos.close();
            byte[] compressed = os.toByteArray();
            os.close();
            return compressed;
        } catch (IOException ex) {
            return null;
        }
    }

    public static String decompress(byte[] compressed) {
        try {
            final int BUFFER_SIZE = 32;
            ByteArrayInputStream is = new ByteArrayInputStream(compressed);
            GZIPInputStream gis = new GZIPInputStream(is, BUFFER_SIZE);
            byte[] data = new byte[BUFFER_SIZE];
            int bytesRead;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while ((bytesRead = gis.read(data)) != -1) {
                baos.write(data, 0, bytesRead);
            }
            gis.close();
            is.close();
            return baos.toString("UTF-8");
        } catch (IOException ex) {
            return null;
        }
    }    

    // Base64
    public static String compressBase64(String strToCompress) {
        byte[] compressed = compress(strToCompress);
        String encoded = android.util.Base64.encodeToString(compressed, android.util.Base64.NO_WRAP);
        return encoded;
    }

    public static String decompressBase64(String strEncoded) {
        byte[] decoded = android.util.Base64.decode(strEncoded, android.util.Base64.NO_WRAP);
        String decompressed = decompress(decoded);
        return decompressed;
    }


    // test
    public static boolean testCompression(String stringToTest) {
        byte[] compressed = compress(stringToTest);
        Log.d("compress-test", "Compressed to " + compressed.length + " bytes");
        String decompressed = decompress(compressed);
        Log.d("compress-test", "Decompressed to " + decompressed);

        return stringToTest.equals(decompressed);
    }
}

So, there you go - dependency free, 100% working compression Android/Java/C#/.NET classes. If you find string that's not working with .NET 4.5 (I've tried everything from "Hello world" to 1000 word short story) - let me know.

你又不是我 2024-11-30 20:16:02

我对这个问题感到抓狂。最后,在我的例子(.Net 4)中,没有必要在开头添加这个额外的 4 个字节来实现 .Net 兼容性。

它的工作原理如下:

Android 压缩:

public static byte[] compress(String string) throws IOException {
    ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(string.getBytes());
    gos.close();
    byte[] compressed = os.toByteArray();
    os.close();
    return compressed;
}

.Net 解压缩

public static byte[] DecompressViD(byte[] gzip)
    {
        // Create a GZIP stream with decompression mode.
        // ... Then create a buffer and write into while reading from the GZIP stream.
        using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
        {
            const int size = 4096;
            byte[] buffer = new byte[size];
            using (MemoryStream memory = new MemoryStream())
            {
                int count = 0;
                do
                {
                    count = stream.Read(buffer, 0, size);
                    if (count > 0)
                    {
                        memory.Write(buffer, 0, count);
                    }
                }
                while (count > 0);
                return memory.ToArray();
            }
        }
    }

I got crazy with this issue. At the end, in my case (.Net 4) it was not necessary to add this extra 4 bytes at the begining for the .Net compatibility.

It works simply like this:

Android Compress:

public static byte[] compress(String string) throws IOException {
    ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(string.getBytes());
    gos.close();
    byte[] compressed = os.toByteArray();
    os.close();
    return compressed;
}

.Net Decompress

public static byte[] DecompressViD(byte[] gzip)
    {
        // Create a GZIP stream with decompression mode.
        // ... Then create a buffer and write into while reading from the GZIP stream.
        using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
        {
            const int size = 4096;
            byte[] buffer = new byte[size];
            using (MemoryStream memory = new MemoryStream())
            {
                int count = 0;
                do
                {
                    count = stream.Read(buffer, 0, size);
                    if (count > 0)
                    {
                        memory.Write(buffer, 0, count);
                    }
                }
                while (count > 0);
                return memory.ToArray();
            }
        }
    }
岁月流歌 2024-11-30 20:16:02

Android方法解压不行

Android 压缩 -> OK:

public static byte[] compress(String string) throws IOException {
    ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(string.getBytes());
    gos.close();
    byte[] compressed = os.toByteArray();
    os.close();
    return compressed;
}

.Net解压-> OK:

public static byte[] DecompressViD(byte[] gzip)
{
    // Create a GZIP stream with decompression mode.
    // ... Then create a buffer and write into while reading from the GZIP stream.
    using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
    {
        const int size = 4096;
        byte[] buffer = new byte[size];
        using (MemoryStream memory = new MemoryStream())
        {
            int count = 0;
            do
            {
                count = stream.Read(buffer, 0, size);
                if (count > 0)
                {
                    memory.Write(buffer, 0, count);
                }
            }
            while (count > 0);
            return memory.ToArray();
        }
    }
}

.Net压缩-> OK:

    public static string compress(string text)
    {
        byte[] buffer = Encoding.UTF8.GetBytes(text);
        MemoryStream ms = new MemoryStream();
        using (GZipStream zip = new GZipStream(ms, CompressionMode.Compress, true))
        {
            zip.Write(buffer, 0, buffer.Length);
        }

        ms.Position = 0;
        MemoryStream outStream = new MemoryStream();

        byte[] compressed = new byte[ms.Length];
        ms.Read(compressed, 0, compressed.Length);

        return Convert.ToBase64String(compressed);
    }

Android解压->不好:

public static String decompress(String zipText) throws IOException {
    byte[] compressed = Base64.decode(zipText);

    GZIPInputStream os = new GZIPInputStream(new ByteArrayInputStream(compressed));

    GZIPInputStream gzipInputStream = new GZIPInputStream(os);

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    for (int value = 0; value != -1;) {
        value = gzipInputStream.read();
        if (value != -1) {
            baos.write(value);
        }
    }
    gzipInputStream.close();
    baos.close();

    return new String(baos.toByteArray(), "UTF-8");
}

Android method decompress not ok

Android Compress -> OK:

public static byte[] compress(String string) throws IOException {
    ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(string.getBytes());
    gos.close();
    byte[] compressed = os.toByteArray();
    os.close();
    return compressed;
}

.Net Decompress -> OK:

public static byte[] DecompressViD(byte[] gzip)
{
    // Create a GZIP stream with decompression mode.
    // ... Then create a buffer and write into while reading from the GZIP stream.
    using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
    {
        const int size = 4096;
        byte[] buffer = new byte[size];
        using (MemoryStream memory = new MemoryStream())
        {
            int count = 0;
            do
            {
                count = stream.Read(buffer, 0, size);
                if (count > 0)
                {
                    memory.Write(buffer, 0, count);
                }
            }
            while (count > 0);
            return memory.ToArray();
        }
    }
}

.Net Compress -> OK:

    public static string compress(string text)
    {
        byte[] buffer = Encoding.UTF8.GetBytes(text);
        MemoryStream ms = new MemoryStream();
        using (GZipStream zip = new GZipStream(ms, CompressionMode.Compress, true))
        {
            zip.Write(buffer, 0, buffer.Length);
        }

        ms.Position = 0;
        MemoryStream outStream = new MemoryStream();

        byte[] compressed = new byte[ms.Length];
        ms.Read(compressed, 0, compressed.Length);

        return Convert.ToBase64String(compressed);
    }

Android Decompress -> Not OK:

public static String decompress(String zipText) throws IOException {
    byte[] compressed = Base64.decode(zipText);

    GZIPInputStream os = new GZIPInputStream(new ByteArrayInputStream(compressed));

    GZIPInputStream gzipInputStream = new GZIPInputStream(os);

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    for (int value = 0; value != -1;) {
        value = gzipInputStream.read();
        if (value != -1) {
            baos.write(value);
        }
    }
    gzipInputStream.close();
    baos.close();

    return new String(baos.toByteArray(), "UTF-8");
}
初心未许 2024-11-30 20:16:02

这是一个帮助您入门的简单示例。

public static void main(String[] args) throws IOException 
{
    byte[] buffer = new byte[4096];
    StringBuilder sb = new StringBuilder();

    //read file to compress

    String read = readFile( "spanish.xml", Charset.defaultCharset());

    if( read != null )
    {
        //compress file to output

        FileOutputStream fos = new FileOutputStream("spanish-new.xml");
        GZIPOutputStream gzos = new GZIPOutputStream(fos);
        gzos.write( read.getBytes());
        gzos.close();

        //uncompress and read back

        FileInputStream fis = new FileInputStream("spanish-new.xml");
        GZIPInputStream gzis = new GZIPInputStream(fis);

        int bytes = 0;

        while ((bytes = gzis.read(buffer)) != -1) {
            sb.append( new String( buffer ) );
        }
    }
}

static String readFile(String path, Charset encoding) throws IOException {
    byte[] encoded = Files.readAllBytes(Paths.get(path));
    return new String(encoded, encoding);
}

Here's a simple example to get you started.

public static void main(String[] args) throws IOException 
{
    byte[] buffer = new byte[4096];
    StringBuilder sb = new StringBuilder();

    //read file to compress

    String read = readFile( "spanish.xml", Charset.defaultCharset());

    if( read != null )
    {
        //compress file to output

        FileOutputStream fos = new FileOutputStream("spanish-new.xml");
        GZIPOutputStream gzos = new GZIPOutputStream(fos);
        gzos.write( read.getBytes());
        gzos.close();

        //uncompress and read back

        FileInputStream fis = new FileInputStream("spanish-new.xml");
        GZIPInputStream gzis = new GZIPInputStream(fis);

        int bytes = 0;

        while ((bytes = gzis.read(buffer)) != -1) {
            sb.append( new String( buffer ) );
        }
    }
}

static String readFile(String path, Charset encoding) throws IOException {
    byte[] encoded = Files.readAllBytes(Paths.get(path));
    return new String(encoded, encoding);
}
等待圉鍢 2024-11-30 20:16:02

这可能会迟到,但可能对某人有用,
我最近需要在 C# Xamarin 中压缩字符串并在 Android 中解压缩。基本上,Xamarin Android 应用程序会向另一个本机 Android 应用程序发送带有压缩的额外字符串的意图。 Android应用程序在使用它之前必须解压它。

这些是对我有用的方法。

ANDROID 解压

 public static String decompress(String zipText) throws IOException {
    int size = 0;
    byte[] gzipBuff = Base64.decode(zipText,Base64.DEFAULT);
    ByteArrayInputStream memstream = new ByteArrayInputStream(gzipBuff, 4,gzipBuff.length - 4);
    GZIPInputStream gzin = new GZIPInputStream(memstream);
    final int buffSize = 8192; 
   byte[] tempBuffer = new byte[buffSize];
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
  while ((size = gzin.read(tempBuffer, 0, buffSize)) != -1) { 
   baos.write(tempBuffer, 0, size);
  } byte[] buffer = baos.toByteArray();
  baos.close(); return new String(buffer, StandardCharsets.UTF_8);
  }

Xamarin 压缩

public static string CompressString(string text)
    {
        byte[] buffer = Encoding.UTF8.GetBytes(text);
        var memoryStream = new MemoryStream();
        using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
        {
            gZipStream.Write(buffer, 0, buffer.Length);
        }
        memoryStream.Position = 0;
        var compressedData = new byte[memoryStream.Length];
        memoryStream.Read(compressedData, 0, compressedData.Length);
        var gZipBuffer = new byte[compressedData.Length + 4];
        Buffer.BlockCopy(compressedData, 0, gZipBuffer, 4, compressedData.Length);
        Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gZipBuffer, 0, 4);
        return Convert.ToBase64String(gZipBuffer);
    }

This might be late but could be useful for someone,
I recently had a requirement to compress a string in C# Xamarin and decompress it in Android. Basically a Xamarin android app sends another Native Android app an intent with a compressed extra string. And the Android app has to decompress it before using it.

These are the methods that worked for me.

ANDROID DECOMPRESS

 public static String decompress(String zipText) throws IOException {
    int size = 0;
    byte[] gzipBuff = Base64.decode(zipText,Base64.DEFAULT);
    ByteArrayInputStream memstream = new ByteArrayInputStream(gzipBuff, 4,gzipBuff.length - 4);
    GZIPInputStream gzin = new GZIPInputStream(memstream);
    final int buffSize = 8192; 
   byte[] tempBuffer = new byte[buffSize];
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
  while ((size = gzin.read(tempBuffer, 0, buffSize)) != -1) { 
   baos.write(tempBuffer, 0, size);
  } byte[] buffer = baos.toByteArray();
  baos.close(); return new String(buffer, StandardCharsets.UTF_8);
  }

XAMARIN COMPRESS

public static string CompressString(string text)
    {
        byte[] buffer = Encoding.UTF8.GetBytes(text);
        var memoryStream = new MemoryStream();
        using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
        {
            gZipStream.Write(buffer, 0, buffer.Length);
        }
        memoryStream.Position = 0;
        var compressedData = new byte[memoryStream.Length];
        memoryStream.Read(compressedData, 0, compressedData.Length);
        var gZipBuffer = new byte[compressedData.Length + 4];
        Buffer.BlockCopy(compressedData, 0, gZipBuffer, 4, compressedData.Length);
        Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gZipBuffer, 0, 4);
        return Convert.ToBase64String(gZipBuffer);
    }
攒眉千度 2024-11-30 20:16:02

我在 Vb.net 中这样做:

  Public Function zipString(ByVal Text As String) As String
    Dim res As String = ""
    Try

        Dim buffer As Byte() = System.Text.Encoding.UTF8.GetBytes(Text)
        Dim ms As New MemoryStream()
        Using zipStream As New System.IO.Compression.GZipStream(ms, System.IO.Compression.CompressionMode.Compress, True)
            zipStream.Write(buffer, 0, buffer.Length)
        End Using
        ms.Position = 0
        Dim outStream As New MemoryStream()
        Dim compressed As Byte() = New Byte(ms.Length - 1) {}
        ms.Read(compressed, 0, compressed.Length)
        Dim gzBuffer As Byte() = New Byte(compressed.Length + 3) {}
        System.Buffer.BlockCopy(compressed, 0, gzBuffer, 4, compressed.Length)
        System.Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gzBuffer, 0, 4)
        res = Convert.ToBase64String(gzBuffer)
    Catch ex As Exception
        Log("mdl.zipString: " & ex.Message)
    End Try
    Return res
End Function

Public Function unzipString(ByVal compressedText As String) As String
    Dim res As String = ""
    Try
        Dim gzBuffer As Byte() = Convert.FromBase64String(compressedText)
        Using ms As New MemoryStream()
            Dim msgLength As Integer = BitConverter.ToInt32(gzBuffer, 0)
            ms.Write(gzBuffer, 4, gzBuffer.Length - 4)
            Dim buffer As Byte() = New Byte(msgLength - 1) {}
            ms.Position = 0
            Using zipStream As New System.IO.Compression.GZipStream(ms, System.IO.Compression.CompressionMode.Decompress)
                zipStream.Read(buffer, 0, buffer.Length)
            End Using
            res = System.Text.Encoding.UTF8.GetString(buffer, 0, buffer.Length)
        End Using
    Catch ex As Exception
        Log("mdl.unzipString: " & ex.Message)
    End Try
    Return res
End Function

I do it so in Vb.net:

  Public Function zipString(ByVal Text As String) As String
    Dim res As String = ""
    Try

        Dim buffer As Byte() = System.Text.Encoding.UTF8.GetBytes(Text)
        Dim ms As New MemoryStream()
        Using zipStream As New System.IO.Compression.GZipStream(ms, System.IO.Compression.CompressionMode.Compress, True)
            zipStream.Write(buffer, 0, buffer.Length)
        End Using
        ms.Position = 0
        Dim outStream As New MemoryStream()
        Dim compressed As Byte() = New Byte(ms.Length - 1) {}
        ms.Read(compressed, 0, compressed.Length)
        Dim gzBuffer As Byte() = New Byte(compressed.Length + 3) {}
        System.Buffer.BlockCopy(compressed, 0, gzBuffer, 4, compressed.Length)
        System.Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gzBuffer, 0, 4)
        res = Convert.ToBase64String(gzBuffer)
    Catch ex As Exception
        Log("mdl.zipString: " & ex.Message)
    End Try
    Return res
End Function

Public Function unzipString(ByVal compressedText As String) As String
    Dim res As String = ""
    Try
        Dim gzBuffer As Byte() = Convert.FromBase64String(compressedText)
        Using ms As New MemoryStream()
            Dim msgLength As Integer = BitConverter.ToInt32(gzBuffer, 0)
            ms.Write(gzBuffer, 4, gzBuffer.Length - 4)
            Dim buffer As Byte() = New Byte(msgLength - 1) {}
            ms.Position = 0
            Using zipStream As New System.IO.Compression.GZipStream(ms, System.IO.Compression.CompressionMode.Decompress)
                zipStream.Read(buffer, 0, buffer.Length)
            End Using
            res = System.Text.Encoding.UTF8.GetString(buffer, 0, buffer.Length)
        End Using
    Catch ex As Exception
        Log("mdl.unzipString: " & ex.Message)
    End Try
    Return res
End Function
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文