Zip for WPF and Silverlight
C1Zip Tutorials / Compressing Data in Memory
In This Topic
    Compressing Data in Memory
    In This Topic

    This tutorial shows how you can compress basic data types such as strings and doubles into memory streams, and expand the data back when you read it from the streams. Here is what the final application will look like:

     

    Step 1: Create the main form.

    Create a new WPF or Silverlight project in Visual Studio project. From the Toolbox, add the following controls to the form, by performing a drag-and-drop operation or by double-clicking the component:

    Step 2: Add a reference to the C1.WPF.Zip or C1.Silverlight.Zip assembly.

    Go to the Solution Explorer window and click the Show All Files button. Right-click on References, and select the Add Reference menu option. Select the C1.WPF.Zip or C1.Silverlight.Zip assembly from the list, or browse to find the C1.WPF.Zip.dll or C1.Silverlight.Zip.dll file.

    Select the MainPage.xaml.vb or MainWindow.xaml.cs tab or go to View|Code to open the Code Editor. At the top of the file, add the following statements:

    Visual Basic
    Copy Code
    Imports System.IO
    Imports Microsoft.Win32
    Imports C1.C1Zip
    

     

    C#
    Copy Code
    using System.IO;
    using Microsoft.Win32;
    using C1.C1Zip;
    

    This makes the objects defined in the C1.WPF.Zip or C1.Silverlight.Zip assembly visible to the project and saves a lot of typing.

    Step 3: Add code to compress strings.

    Double-click the Compress String command button, and add the following code to handle the btnCompressString_Click event:

    C#
    Copy Code
    private byte[] _compressedString;
          private void btnCompressString_Click(object sender, RoutedEventArgs e)
            {
                // Compress the string.
                long ticks = DateTime.Now.Ticks;
                richTextBox1.SelectAll();
                string text = richTextBox1.Selection.Text;
                _compressedString = CompressString(text);
                // Tell the user how long it took.
                int ms = (int)((DateTime.Now.Ticks - ticks) / TimeSpan.TicksPerMillisecond);
                int lenBefore = text.Length * 2;
                int lenAfter = _compressedString.Length;
                string msg = string.Format("Compressed from {0} bytes to " + "{1} bytes in {2} milliseconds.", lenBefore, lenAfter, ms);
                MessageBox.Show(msg, "Compressed", MessageBoxButton.OK);
                // We can now expand it.
                btnExpandString.IsEnabled = true;
            }
    

    The first main line declares a member variable called m_CompressedString which will be used to hold the compressed data (encoded as a byte array). The second main line calls a utility function CompressString that compresses a given string into a byte array that can later be expanded to restore the original string. The remainder of the code is used to measure how long the compression process took and to show a dialog box with statistics.

    (Note that the lenBefore variable is calculated as the length of the string times two. This is because .NET strings are Unicode, and each character actually takes up two bytes.)

    Add the following code which implements the CompressString function:

    C#
    Copy Code
    public byte[] CompressString(string str)
    {
        // Open the memory stream.
        MemoryStream ms = new MemoryStream();
        // Attach a compressor stream to the memory stream.
        C1ZStreamWriter sw = new C1ZStreamWriter(ms);
        // Write the data into the compressor stream.
        StreamWriter writer = new StreamWriter(sw);
        writer.Write(str);
        // Flush any pending data.
        writer.Flush();
        // Return the memory buffer.
        return ms.ToArray();
    }
    

    The function starts by creating a new memory stream. This stream will automatically allocate a memory buffer to hold the compressed data.

    Next, the function creates a C1ZStreamWriter object and attaches it to the new memory stream. Any data written to the C1ZStreamWriter object will be compressed and written to the memory stream.

    The C1ZStreamWriter object only supplies the basic Stream methods for writing bytes and byte arrays. To be able to write other basic types such as strings, integers, and so on, we attach a StreamWriter object to the C1ZStreamWriter. Here's a diagram that shows how this works:

    After the StreamWriter is set up, all we need to do is call its Write method to write the string into the compressed memory stream. When done writing, we also call the Flush method to make sure all cached input is written out.

    Finally, the code uses the ToArray method to return the byte array that was created by the memory stream.

    Step 4: Add code to expand strings.

    To expand the string, we need to follow the reverse sequence of steps used to compress. Double-click the Decompress String button, and add the following code to handle the btnExpandString_Click event:

    C#
    Copy Code
    private void btnExpandString_Click(object sender, RoutedEventArgs e)
            {
                // Expand the string.
                long ticks = DateTime.Now.Ticks;
                string text = ExpandString(_compressedString);
                richTextBox1.Selection.Text = text;
                // Tell the user how long it took.
                int ms = (int)((DateTime.Now.Ticks - ticks) / TimeSpan.TicksPerMillisecond);
                int lenBefore = _compressedString.Length;
                int lenAfter = text.Length * 2;
                string msg;
                msg = string.Format("Expanded from {0} bytes to {1} bytes " + "in {2} milliseconds.", lenBefore, lenAfter, ms);
                MessageBox.Show(msg, "Expanded", MessageBoxButton.OK);
            }
    

    The main line calls the utility function ExpandString that takes a byte array and returns the original string. Add the following code for the ExpandString function:

    C#
    Copy Code
    public string ExpandString(byte[] buffer)
    {
        // Turn buffer into a memory stream.
        MemoryStream ms = new MemoryStream(buffer);
        // Attach a decompressor stream to the memory stream.
        C1ZStreamReader sr = new C1ZStreamReader(ms);
        // Read uncompressed data.
        StreamReader reader = new StreamReader(sr);
        return reader.ReadToEnd();
    }
    

    If you run the project now, you can already experiment with string compression and decompression. You can change the text in the text box, or paste new content into it, then compress and expand the string to see how much it compresses.

    Step 5: Add code to compress binary data.

    Compressing binary data is just as easy as compressing strings. The only difference is that instead of attaching a StreamWriter object to the compressor stream, you attach a BinaryWriter object.

    Double-click the Compress Data button and add the following code to handle the btnCompressData_Click event:

    C#
    Copy Code
    private byte[] _compressedData;
      private void btnCompressData_Click(object sender, RoutedEventArgs e)
            {
                // Open the memory stream.
                MemoryStream ms = new MemoryStream();
                // Attach a compressor stream to the memory stream.
                C1ZStreamWriter sw = new C1ZStreamWriter(ms);
                // Attach a BinaryWriter to the compressor stream.
                BinaryWriter bw = new BinaryWriter(sw);
                // Write a bunch of numbers into the stream.
                int i;
                int count = 1000;
                bw.Write(count);
                for (i = 0; i <= count - 1; i++)
                {
                    double a = i * Math.PI / 180.0;
                    bw.Write(i);
                    bw.Write(a);
                    bw.Write(Math.Sin(a));
                    bw.Write(Math.Cos(a));
                }
                // Flush any pending output.
                bw.Flush();
                // Save the compressed data.
                _compressedData = ms.ToArray();
                // Done.
                string msg;
                msg = string.Format("Generated table with {0} points," +
                    " saved into {1} bytes", count, _compressedData.Length);
                label1.Content = msg;
                // We can now expand it.
                btnExpandData.IsEnabled = true;
            }
    

    The code starts by declaring a member variable called m_CompressedData which will be used to hold the compressed data (encoded as a byte array).

    Then it sets up the MemoryStream, C1ZStreamWriter, and BinaryWriter objects as before (the only difference is we're now using a BinaryWriter instead of a StreamWriter).

    Next, the code writes data into the stream using the Write method. The BinaryWriter object overloads this method so you can write all basic object types into streams. Finally, the Flush method is used as before, to make sure any cached data is written out to the compressed stream.

    Step 6: Add code to expand the binary data.

    Expanding the compressed binary data is just a matter of setting up the decompressor stream and reading the data like you would read it from a regular stream.

    Add the following Click event handler code for the Decompress Data command button:

    C#
    Copy Code
    private void btnExpandData_Click(object sender, RoutedEventArgs e)
            {
                // Open the memory stream on saved data.
                MemoryStream ms = new MemoryStream(_compressedData);
                // Attach a decompressor stream to the memory stream.
                C1ZStreamReader sr = new C1ZStreamReader(ms);
                // Read the uncompressed data.
                int i;
                BinaryReader br = new BinaryReader(sr);
                int count = br.ReadInt32();
                for (i = 0; i <= count - 1; i++)
                {
                    int deg = br.ReadInt32();
                    double rad = br.ReadDouble();
                    double sin = br.ReadDouble();
                    double cos = br.ReadDouble();
                }
                // Done, tell the user about it.
                string msg;
                msg = string.Format("Read table with {0} points " +
                    "from stream with {1} bytes.", count, _compressedData.Length);
                label1.Content = msg;
            }
    

    The code reads the data but does not display it. You can step through it in debug mode to make sure the data being read is the same that was written in.

    If you run the project and click the compress/decompress data buttons, you will see that the data is saved in an array with 14,125 bytes. To save this data in a regular stream, it would take [4 + 1000 * (4 + 8 * 3)] = 28,004 bytes. So we compressed it to about half the original size.

    This concludes the Compressing Data in Memory tutorial.