Esta pregunta surgió el otro día en uno de mis cursos on-line de campusMVP y la verdad es que es bastante interesante, no tanto porque sea especialmente difícil de hacer, si no por las implicaciones que tiene su aplicación en la práctica.

La idea está muy bien: dado que estoy almacenando objetos de mi programa en disco (o enviándolos a través de la red), si los comprimo debería ahorrar espacio en disco y/o ancho de banda. De hecho, la idea es tan buena que en ASP.NET 4.0 una de las novedades es que el almacenamiento de sesión fuera de proceso (es decir, cuando guardamos la sesión en un servidor de estado remoto o en un SQL Server), tiene una nueva opción para permitir la compresión de los datos antes de enviarlos para su almacenamiento o recuperación de sesión. Esto es muy útil para sitios web que quieren acelerar la transferencia de información en una granja de servidores, pero tiene la contrapartida de que los procesadores estarán más cargados debido a las operacioens de compresión y descompresión.

Antes de entrar en detalles sobre si esto es realmente tan bueno como parece, vamos a ver cómo podríamos implementarlo de manera sencilla con nuestro propio código.

El código de seriación (es la palabra adecuada en español, frente al más comñun "serialización" que es una adaptación del término anglosajón) sería este:

public static void SeriaYComprime(object obj, string arch) { 
	using(FileStream fs = new FileStream(arch, FileMode.OpenOrCreate, FileAccess.Write)) {
		using (GZipStream zs = new GZipStream(fs, CompressionMode.Compress, true)) { 
			BinaryFormatter bf = new BinaryFormatter(); 
			bf.Serialize(zs, obj);
		} 
	}
}

A este método se le pasa el objeto que queremos seriar y una ruta en disco para almacenarlo y seria el objeto para almacenarlo en formato binario, usando un BinaryStream y un GZipStream por debajo para conseguir la compresión. Muy sencillo.

Por supuesto, en lugar de almacenar a disco podríamos almacenar a memoria con un MemoryStream o a cualquier otra ubicación, pero el código sería prácticamente igual.

El código para conseguir la deseriación sería como este:

public static object DescomprimeYDeseria(string arch) { 
	using (FileStream fs = new FileStream(arch, FileMode.Open, FileAccess.Read)) {
		using (GZipStream zs = new GZipStream(fs, CompressionMode.Decompress, true)) { 
			BinaryFormatter bf = new BinaryFormatter();
			return bf.Deserialize(zs); 
		}
	} 
} 

Que es el proceso inverso al que hicimos antes, es decir, se indica el archivo, y éste se lee usando un formateador binario que usa a su vez un GZipStream por debajo en modo descompresión.

Si ahora tenemos una clase "serializable" cualquiera, por ejemplo esta:

[Serializable]
public class PruebaClaseSeriable
{
	public string Propiedad1 {get; set;}
	public int Propiedad2 {get; set;}
	public long Propiedad3 {get; set;}
}

y escribimos esto:

PruebaClaseSeriable pcs = new PruebaClaseSeriable();
pcs.Propiedad1 = "Hola";
pcs.Propiedad2 = 2;
pcs.Propiedad3 = 3;

SeriaYComprime(pcs, @"D:\PruebaCompr.bin");

Obtendremos un archivo "pruebaCompr.bin" en la raíz de la unidad D: con el estado completo del objeto. Este archivo lo podemos usar cuando queramos para recrear de nuevo el objeto en memoria simplemente llamando al otro método complementario:

PruebaClaseSeriable pcs2 = (PruebaClaseSeriable) DescomprimeYDeseria(@"D:\PruebaCompr.bin");

Listo. Tendríamos el objeto pcs2 con un estado idéntico al que teníamos anteriormente, aunque se reconstruya días más tarde o en una máquina diferente (esa es la idea de la seriación, o como dicen los modernos que trabajan con SOA, la idea de la deshidratación/hidratación).

En este archivo ZIP (3,56 KB) puedes descargarte el código de ejemplo e incluye también una versión sin compresión de los métodos de seriación/deseriación de objetos.

El efecto conseguido puede ser el contrario

Todo esto está genial y funciona a las mil maravillas. Sin embargo seriemos el mismo objeto anterior con y sin compresión y examinemos más de cerca los dos archivos generados.

El que no lleva compresión ocupa 229 bytes, y el que está comprimido pesa ¡294 bytes!
WTF? ¿Cómo es posible? ¡El comprimido ocupa más!.

En efecto, esto es así ya que la compresión no viene sola sino que trae una serie de elementos previos, en forma de cabecera de archivo, para poder hacer posteriormente la descompresión. Y estos elementos de la cabecera ocupan un cierto espacio (algo más de 100 bytes). Por lo tanto en este caso la compresión obtenida es mínima y encima tenemos la cabecera, por lo que al final acabamos con un archivo mucho mayor :-(

Probemos a sustituir el lacónico "Hola" de la Propiedad1 del objeto por los cuatros primeros párrafos de "El Quijote", que son 5.046 caracteres (está incluido en el ejemplo descargable anterior).
Ahora el seriado sin compresión ocupa 5.400 bytes y el comprimido 2.968 byes, es decir, ahora sí que hemos ganado mucho. En concreto el comprimido ocupa sólo el 55% de lo que ocupaba el que no lleva compresión. No está mal.

Conclusión

La compresión a la hora de seriar objetos para transportarlos o almacenarlos puede estar bien si dichos objetos contienen muchos datos, pero en objetos pequeños con pocos miembros y poca información será contraproducente. Además en ambos casos tendremos una mayor demanda del procesador por lo que si esto es importante para nosotros debemos tenerlo en cuenta. Puede ser muy útil la compresión para seriar objetos grandes y, sobre todo colecciones o matrices de múltiples objetos pequeños, que será más comun.

Espero que el tema te haya resultado interesante y le puedas sacar partido en la práctica.

Escrito por un humano, no por una IA