String compression/decompression routines using Delphi

I wrote the following two functions (in bold) with the purpose of compressing/decompressing string values within a Delphi application:

.........................
implementation

uses
  ZLib;

function ZCompressString(aText: string; aCompressionLevel: TZCompressionLevel): string;
var
  strInput,
  strOutput: TStringStream;
  Zipper: TZCompressionStream;
begin
  Result:= '';
  strInput:= TStringStream.Create(aText);
  strOutput:= TStringStream.Create;
  try
    Zipper:= TZCompressionStream.Create(strOutput, aCompressionLevel);
    try
      Zipper.CopyFrom(strInput, strInput.Size);
    finally
      Zipper.Free;
    end;
    Result:= strOutput.DataString;
  finally
    strInput.Free;
    strOutput.Free;
  end;
end;

function ZDecompressString(aText: string): string;
var
  strInput,
  strOutput: TStringStream;
  Unzipper: TZDecompressionStream;
begin
  Result:= '';
  strInput:= TStringStream.Create(aText);
  strOutput:= TStringStream.Create;
  try
    Unzipper:= TZDecompressionStream.Create(strInput);
    try
      strOutput.CopyFrom(Unzipper, Unzipper.Size);
    finally
      Unzipper.Free;
    end;
    Result:= strOutput.DataString;
  finally
    strInput.Free;
    strOutput.Free;
  end;
end;

.........................

The main advantage of the above functions over the ZCompressStr and ZDecompressStr routines shipped with ZLib.pas, is that you won’t have potential data lost when handling Unicode <->Ansi conversions. In other words, the above functions will work in both Ansi Delphi versions (previous to Delphi 2009) and Unicode Delphi versions (Delphi 2009, 2010, XE and so on).

Note that you need to include ZLib in the uses clause. ZLib.pas is a high level wrapper to the ZLib library created by Jean-Loup Gailly and Mark Adle for data compression.

In addition, using the TZCompressionStream and TZDecompressionStream classes you can also create (compress) and decompress ZIP files.

What did (do) I need this for? Well, the applicability for data compression is wide...

A real life example? I needed to store JSON strings in a MySQL database. As a way to optimize resources I compressed all JSON strings before the insertion into the database. After the retrieval, I was able to decompress each string to its original value. The compression rate was huge: I packed ~9000 chars in ~300 per JSON on average. This is a considerable saving: my table contains more than one million rows. Do the math yourself! :-)

8 comments:

  1. Sorry, it doesn't work with Delphi 7!

    ReplyDelete
  2. Indeed, the ZLib unit shipped with Delphi 7 does not contain the classes TZCompressionStream and TZDecompressionStream. Try a higher version, 2007 for example ;-)

    For Delphi 7, you’ll need to use the ZCompressStr and ZDecompressStr methods.

    ReplyDelete
  3. I tried using this but it doesnt seem to preserve Unicode characters. Try putting some Chinese characters in and when decompressed it looses the encoding.

    ReplyDelete
    Replies
    1. I don't have a Unicode version of Delphi (2009 and above) to try this right now, but I will install it and I'll let you know my findings. Thanks.

      Delete
  4. Thanks for the information!
    But in your real life example you mention MySQL.
    There is no need to compress/decompress anything in Delphi, just use the built-in MySQL functions COMPRESS() and DECOMPRESS().
    They do the same zlib compression.
    for example:

    INSERT INTO dummyTable(id, col1) VALUES (1, COMPRESS('This is a long string'));

    will insert the string zlib compressed, and you can retrieve it with:

    SELECT id, UNCOMPRESS(col1) FROM dummyTable;

    I use this method and it saves me from extra coding.

    ReplyDelete
    Replies
    1. You need to compress in client-side not in server-side, the end-user will thank you.

      Delete
  5. I worked it out and thought it might be worth posting.

    All the TStringStream components need to be created with Unicode encoding enabled (by default its Ansi). As per the following:

    strInput := TStringStream.Create(Text, TEncoding.Unicode);
    strOutput := TStringStream.Create('', TEncoding.Unicode);

    ReplyDelete
  6. Why use TStringStream for output? Use a TMemoryStream and return a byte array instead of string...
    Because compressed data is binary...

    ReplyDelete