如何使用与.Net兼容的GZIPOutputStream来压缩和解压缩string?
我需要一个在Android中使用GZip压缩string的例子。 我想发送一个string像“你好”的方法,并获得下面的压缩string:
BQAAAB + LCAAAAAAABADtvQdgHEmWJSYvbcp7f0r1StfgdKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee ++ 997o7nU4n99 // P1xmZAFs9s5K2smeIYCqyB8 / fnwfPyLmeVlW / W + GphA2BQAAAA ==
然后我需要解压缩它。 有谁能给我一个例子,并完成以下方法?
private String compressString(String input) { //... } private String decompressString(String input) { //... }
谢谢,
更新
根据主席的回答 ,现在我有以下四种方法。 Android和.net压缩和解压缩方法。 除了一种情况,这些方法是相互兼容的。 我的意思是他们在前三个州是兼容的,但在第四个州是不兼容的:
- 状态1)Android.compress < – > Android.decompress:( OK )
- 状态2)Net.compress < – > Net.decompress:( OK )
- 状态3)Net.compress – > Android.decompress:( 确定 )
- 状态4)Android.compress – > .Net.decompress:( 不OK )
任何人都可以解决它?
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); } }
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(); }
还有一个testing:
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(); }
您可以使用相同的testing脚本。
无论是压缩“Hello”给BQAAAB + LC …是gzipper的一个特别糟糕的实现。 它扩展了“你好”远远超过必要的,使用dynamic块,而不是放气格式的静态块。 删除gzipstream(总是以hex 1f 8b开始)的四字节前缀后,“Hello”扩展为123字节。 在压缩的世界里,这被认为是犯罪。
您所抱怨的压缩方法正常且正确地工作。 它正在生成一个静态块和25个字节的总输出。 gzip格式有一个10字节的头部和8字节的尾部开销,剩下的5字节input已经被编码为7个字节。 这还差不多。
不可压缩的stream将被扩展,但不应该太多。 gzip使用的deflate格式将不可压缩数据的每16K到64K增加5个字节。
为了得到实际的压缩效果,一般来说,需要给压缩器多五个字节的工作量,以便在可压缩数据中find重复的string和有偏差的统计量。 我知道你只是用一个简短的string进行testing。 但是在实际的应用中,你永远不会使用这种短串的通用压缩器,因为只要发送串就总是更好。
在Decompress()
方法中,Base64解码input的前4个字节在传递给GZipInputStream
之前被跳过。 在这种情况下,发现这些字节是05 00 00 00
。 所以在Compress()
方法中,这些字节必须放在Base64编码之前。
如果我这样做,Compress()返回以下内容:
BQAAAB+LCAAAAAAAAADLSM3JyQcAhqYQNgUAAAA=
我知道这与你的期望并不完全一样,即:
BQAAAB+LCAAAAAAABADtvQdgHEmWJSYvbcp7f0r1StfgdKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee++997o7nU4n99//P1xmZAFs9s5K2smeIYCqyB8/fnwfPyLmeVlW/w+GphA2BQAAAA==
但是,如果我的结果被插回到Decompress()
,我想你仍然会得到"Hello"
。 尝试一下。 这种差异可能是由于您获得原始string的压缩级别不同所致。
那么什么是神秘的前缀字节05 00 00 00
? 根据这个答案,它可能是压缩的string的长度,以便程序知道解压缩的字节缓冲区应该是多长时间。 这仍然不符合这种情况。
这是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代码中的输出string不匹配的原因是.NET GZip实现的压缩速度更快(因此输出更大)。 这可以通过查看原始Base64解码的字节值来validation:
。净:
1F8B 0800 0000 0000 04 00 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 00 00 CB48 CDC9 C907 0086 A610 3605 0000 00
现在,如果我们检查GZip文件格式 ,我们可以看到.NET和Android版本在初始标题和尾随CRC32和大小字段中大部分是相同的。 唯一的区别是在下面的领域:
- 在.NET中,XFL = 04(压缩器使用最快的algorithm),而在Android中则为00
- 实际的压缩块
所以从XFL领域很清楚,.NET压缩algorithm产生更长的输出。
事实上,当我用这些原始数据值创build一个二进制文件,然后使用gunzip解压缩它们时,.NET和Android版本都给出了与 “hello” 完全相同的输出 。
所以你不必担心不同的结果。
我在我的项目中尝试了你的代码,并发现在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());
在上面的代码中,你应该使用更正后的编码,并填充字节长度,而不是string长度:
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 );
这个问题我疯了。 最后,在我的情况下(.Net 4),没有必要在.Net兼容性开始时添加这个额外的4个字节。
它的工作原理是这样的:
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(); } } }
好吧,我讨厌在有很多现有的答案的时候,但不幸的是他们中的大多数只是错误的各种原因:
- .NET Framework中的GZIPalgorithm存在差异。 如果您使用的是.NET 4.5,大部分的投诉都会以不同的答案显示,但不适用于您(而不适用于使用2.0或3.5的用户)。 如果你使用“固定”版本的代码,你实际上会搞乱压缩/解压。
- Java使用无符号的byte [],.NET使用带符号的byte []。 这可能会导致在运输过程中出现问题,具体取决于您输送该字节[]的方式。
- 我用Base64来传输byte [],这可能会带来更多的问题。 还有其他一些原因,但是我们不要再抱怨了
如果您使用的是.NET Framework 4.5,那么您需要(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); 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(); } 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 == decompressed; } }
所以,你去 – 依赖免费,100%工作压缩Android / Java / C#/ .NET类。 如果你发现string不工作与.NET 4.5 (我试过从“你好世界”到1000字短故事) – 让我知道。
Android方法解压不好
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(); } } }
.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); return Convert.ToBase64String(compressed); }
Android Decompress – >不好:
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"); }
下面是一个简单的例子,让你开始。
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); }
我在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