android - como - ¿Cómo puedo Zip y descomprimir una cadena usando GZIPOutputStream que es compatible con.Net?
winrar android (9)
Necesito un ejemplo para comprimir una cadena usando GZip en Android. Quiero enviar una cadena como "hola" al método y obtener la siguiente cadena comprimida:
BQAAAB + LCAAAAAAABADtvQdgHEmWJSYvbcp7f0r1StfgdKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee ++ 997o7nU4n99 // P1xmZAFs9s5K2smeIYCqyB8 / fnwfPyLmeVlW / w + GphA2BQAAAA ==
Entonces necesito descomprimirlo. ¿Alguien puede darme un ejemplo y completar los siguientes métodos?
private String compressString(String input) {
//...
}
private String decompressString(String input) {
//...
}
Gracias,
actualizar
De acuerdo con la respuesta del scessor , ahora tengo los siguientes 4 métodos. Android y .net comprimen y descomprimen métodos. Estos métodos son compatibles entre sí excepto en un caso. Quiero decir que son compatibles en los primeros 3 estados pero incompatibles en el 4to estado:
- estado 1) Android.compress <-> Android.decompress: ( OK )
- estado 2) Net.compress <-> Net.decompress: ( OK )
- estado 3) Net.compress -> Android.decompress: ( OK )
- estado 4) Android.compress -> .Net.decompress: ( NO OK )
¿Alguien puede resolverlo?
Métodos de 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 "";
}
}
Métodos de .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);
}
}
Aquí hay un ejemplo simple para comenzar.
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);
}
De acuerdo, odio llamar la atención cuando hay toneladas de respuestas existentes, pero desafortunadamente la mayoría de ellas simplemente están equivocadas por varias razones:
- Existen diferencias entre los algoritmos GZIP dentro de .NET Framework. Si usa .NET 4.5, la mayoría de las quejas que ve en diferentes respuestas simplemente no se aplican a usted (más bien a quienes usan 2.0 o 3.5). Si opta por versiones "fijas" de código, en realidad causará problemas de compresión / descompresión.
- Java usa byte sin signo [], .NET usa byte firmado []. Esto puede causar problemas durante el transporte dependiendo de cómo exactamente transporta ese byte [].
- Utilicé Base64 para transportar byte [], lo que puede ocasionar aún más problemas. Hay una variedad de otras razones, pero salteemos más lloriqueos y lleguemos al código ...
Si está utilizando .NET Framework 4.5, aquí está la clase C # que necesita (Base64 como bonificación):
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;
}
}
Y aquí está la clase de Android / Java que necesita:
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;
}
}
Entonces, ahí lo tienes: sin dependencia, con compresión 100% de las clases de Android / Java / C # / .NET. Si encuentras una cadena que no funciona con .NET 4.5 (he intentado de todo, desde "Hola mundo" a una historia corta de 1000 palabras), házmelo saber.
En su método Decompress()
, los primeros 4 bytes de la entrada decodificada Base64 se omiten antes de pasar a GZipInputStream
. Estos bytes se encuentran 05 00 00 00
en este caso particular. Por lo tanto, en el método Compress()
, estos bytes deben volver a colocarse justo antes de la codificación Base64.
Si hago esto, Compress () devuelve lo siguiente:
BQAAAB+LCAAAAAAAAADLSM3JyQcAhqYQNgUAAAA=
Sé que esto no es exactamente lo mismo que tu expectativa, que es:
BQAAAB+LCAAAAAAABADtvQdgHEmWJSYvbcp7f0r1StfgdKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee++997o7nU4n99//P1xmZAFs9s5K2smeIYCqyB8/fnwfPyLmeVlW/w+GphA2BQAAAA==
Pero, si mi resultado se vuelve a conectar a Decompress()
, creo que seguirá recibiendo "Hello"
. Intentalo. La diferencia puede deberse al diferente nivel de compresión con el que obtuvo la cadena original.
Entonces, ¿cuáles son los misteriosos prefijos de los bytes 05 00 00 00
? De acuerdo con esta respuesta , puede ser la longitud de la cadena comprimida para que el programa sepa cuánto tiempo debe estar el búfer de byte descomprimido. Aún así eso no cuenta en este caso.
Este es el código modificado para 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());
}
Actualizar:
La razón por la cual las cadenas de salida en Android y su código .NET no coinciden es que la implementación de .NET GZip realiza una compresión más rápida (y por lo tanto un mayor rendimiento). Esto se puede verificar con seguridad mirando los valores de bytes decodificados Base64 sin procesar:
.RED:
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
Mi versión de Android:
1F8B 0800 0000 0000 0000 CB48 CDC9 C907 0086 A610 3605 0000 00
Ahora, si comprobamos el formato de archivo de GZip , vemos que las versiones de .NET y Android son en su mayoría idénticas en el encabezado inicial y en los campos de CRC32 y tamaño. Las únicas diferencias se encuentran en los siguientes campos:
- XFL = 04 (el compresor usó el algoritmo más rápido) en el caso de .NET, mientras que es 00 en Android
- Los bloques comprimidos reales
Por lo tanto, es claro desde el campo XFL que el algoritmo de compresión .NET produce una salida más larga.
De hecho, cuando creo un archivo binario con estos valores de datos brutos y luego los descomprimio usando gunzip, las versiones .NET y Android dieron exactamente el mismo resultado que "hello".
Para que no tenga que preocuparse por los resultados diferentes.
Lo hago así en 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
Lo que sea que comprimió "Hola" a BQAAAB + LC ... es una implementación particularmente pobre de un gzipper. Amplió "Hola" mucho, mucho más de lo necesario, utilizando un bloque dinámico en lugar de un bloque estático en el formato de desinflado. Después de eliminar el prefijo de cuatro bytes de la secuencia gzip (que siempre comienza con hex 1f 8b), "Hello" se expandió a 123 bytes. En el mundo de la compresión, eso se considera un crimen.
El método Compress del que se queja está funcionando correctamente y correctamente. Está generando un bloque estático y una salida total de 25 bytes. El formato gzip tiene un encabezado de diez bytes y una sobrecarga de remolque de ocho bytes, dejando la entrada de cinco bytes codificada en siete bytes. Eso es más como eso.
Las secuencias que no son comprimibles se expandirán, pero no debería ser demasiado. El formato de desinflado utilizado por gzip agregará cinco bytes a cada 16K a 64K para datos incompresibles.
Para obtener la compresión real, en general necesita darle al compresor mucho más para trabajar con esos cinco bytes, de modo que pueda encontrar cadenas repetidas y estadísticas sesgadas en datos comprimibles. Entiendo que solo estabas haciendo pruebas con una cadena corta. Pero en una aplicación real, nunca usarías un compresor de propósito general con tan pocas cadenas, ya que siempre sería mejor enviar la cadena.
Los métodos 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();
}
Y una prueba:
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();
}
=== Actualización ===
Si necesita compatibilidad .Net, mi código debe cambiarse un poco:
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();
}
Puede usar el mismo script de prueba.
Me volví loco con este problema. Al final, en mi caso (.Net 4) no fue necesario agregar estos 4 bytes adicionales al inicio para la compatibilidad con .Net.
Funciona simplemente así:
Comprimir 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;
}
Descompresión de .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();
}
}
}
Probé tu código en mi proyecto y encontré un error de codificación en el método de compresión en 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());
en el código anterior, debe usar la codificación corregida y completar la longitud de los bytes, no la longitud de la cadena:
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 );
Método de Android descomprimir no está bien
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;
}
Descompresión .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 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);
}
Descompresión de Android -> No está bien:
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");
}