views:

430

answers:

1

Hi.

I currently got this script, which compresses byte arrays. But I need it rewritten, so it can compress triple byte arrays [,,]

Thanks!

public static byte[] Compress(byte[] buffer)
{
MemoryStream ms = new MemoryStream();
GZipStream zip = new GZipStream(ms, CompressionMode.Compress, true);
zip.Write(buffer, 0, buffer.Length);
zip.Close();
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];
Buffer.BlockCopy(compressed, 0, gzBuffer, 4, compressed.Length);
Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gzBuffer, 0, 4);
return gzBuffer;
}

public static byte[] Decompress(byte[] gzBuffer)
{
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;
GZipStream zip = new GZipStream(ms, CompressionMode.Decompress);
zip.Read(buffer, 0, buffer.Length);

return buffer;
}
+4  A: 

Update: I rewrote the code, it is running much faster now and the code is cleaner. Just tested it with some random data (see end of this post).

The Compression method:

public static byte[] Compress(byte[, ,] uncompressed)
{
    if (uncompressed == null)
        throw new ArgumentNullException("uncompressed", 
                                        "The given array is null!");
    if (uncompressed.LongLength > (long)int.MaxValue)
        throw new ArgumentException("The given array is to large!");

    using (MemoryStream ms = new MemoryStream())
    using (GZipStream gzs = new GZipStream(ms, CompressionMode.Compress))
    {
        // Save sizes of the dimensions
        for (int dim = 0; dim < 3; dim++)
            gzs.Write(BitConverter.GetBytes(
                      uncompressed.GetLength(dim)), 0, sizeof(int));

        // Convert byte[,,] to byte[] by just blockcopying it
        // I know, some pointer-magic/unmanaged cast wouldnt 
        // have to copy it, but its cleaner this way...
        byte[] data = new byte[uncompressed.Length];
        Buffer.BlockCopy(uncompressed, 0, data, 0, uncompressed.Length);

        // Write the data to the stream to compress it
        gzs.Write(data, 0, data.Length);
        gzs.Close();

        // Get the compressed byte array back
        return ms.ToArray();
    }
}

The Decompression method:

public static byte[, ,] Decompress(byte[] compressed)
{
    if (compressed == null)
        throw new ArgumentNullException("compressed", 
                                        "Data to decompress cant be null!");

    using (MemoryStream ms = new MemoryStream(compressed))
    using (GZipStream gzs = new GZipStream(ms, CompressionMode.Decompress))
    {
        // Read the header and restore sizes of dimensions
        byte[] dimheader = new byte[sizeof(int) * 3];
        gzs.Read(dimheader, 0, dimheader.Length);
        int[] dims = new int[3];
        for (int j = 0; j < 3; j++)
            dims[j] = BitConverter.ToInt32(dimheader, sizeof(int) * j);

        // Read the data into a buffer
        byte[] data = new byte[dims[0] * dims[1] * dims[2]];
        gzs.Read(data, 0, data.Length);

        // Copy the buffer to the three-dimensional array
        byte[, ,] uncompressed = new byte[dims[0], dims[1], dims[2]];
        Buffer.BlockCopy(data, 0, uncompressed, 0, data.Length);

        return uncompressed;
    }
}

The test code:

Random rnd = new Random();

// Create a new randomly big array, fill it with random data
byte[, ,] uncomp = new byte[rnd.Next(70, 100), 
                       rnd.Next(70, 100), rnd.Next(70, 100)];
for (int x = 0; x < uncomp.GetLength(0); x++)
    for (int y = 0; y < uncomp.GetLength(1); y++)
        for (int z = 0; z < uncomp.GetLength(2); z++)
            uncomp[x, y, z] = (byte)rnd.Next(30, 35);

// Compress and Uncompress again
Stopwatch compTime = new Stopwatch(), uncompTime = new Stopwatch();
compTime.Start();
byte[] comp = Compress(uncomp);
compTime.Stop();
uncompTime.Start();
byte[, ,] uncompagain = Decompress(comp);
uncompTime.Stop();

// Assert all dimension lengths and contents are equal
for (int j = 0; j < 3; j++)
    Debug.Assert(uncomp.GetLength(j) == uncompagain.GetLength(j));

for (int x = 0; x < uncomp.GetLength(0); x++)
    for (int y = 0; y < uncomp.GetLength(1); y++)
        for (int z = 0; z < uncomp.GetLength(2); z++)
            Debug.Assert(uncomp[x, y, z] == uncompagain[x, y, z]);

Console.WriteLine(string.Format("Compression: {0}ms, " +
    "Decompression: {1}ms, Ratio: {2}% ({3}/{4} bytes)",
    compTime.ElapsedMilliseconds, uncompTime.ElapsedMilliseconds,
    (int)((double)comp.LongLength / (double)uncomp.LongLength * 100),
    comp.LongLength, uncomp.LongLength));

Output, for example:

Compression: 77ms, Decompression: 23ms, Ratio: 41% (191882/461538 bytes)
Philip Daubmeier
Your creation of `ArgumentNullExceptions` is a little off. If you look at the constructor you are using, the parameter is the name of the parameter that is null. If you want to pass a message, you should pass it as the second parameter in the two parameter overload. Oddly, this is not true for the base `ArgumentException` class which does use `message` as the single or first of two parameters.
Gideon Engelberth
@Gideon: thanks for that, just corrected it. Must have confused it with the overloads of `ArgumentException` where the message is the first and only parameter.
Philip Daubmeier