Vor etwas längerem, brauchte ich auch mal eine Komprimierungsfunktion für ein Mobil-Game. Speicher und CPU durften nicht stark belastet werden. Daher hatte ich letztlich dann auch eine simple Lauflängenkodierung programmiert. Dabei zeigte sich jedoch mit der Zeit, das es doch sehr viele Situationen gibt, in denen die Daten zweideutig sind.... hier im Thema wurden das ja auch schon angesprochen ;-)
Nach sehr vielen Tests mit Realdaten, entwickelte sich der Algorithmus deswegen letztlich hin zu einer Lauflängenkodierung mit Flag als Signal für komprimierte Bytes/Zeichen.
Dabei habe ich dann in einem einzigen Byte, sowohl dieses Flag, als auch die Anzahl der Wiederholungen gespeichert.
Flag ist: Binär 1110 0000
Damit stehen also die rechten 5 Bits noch zur Verfügung für die 'Anzahl Wiederholungen'.
Mit 5 Bits lassen sich nur die Dezimalzahlen bis 31 speichern. Entsprechend werden also bei mehr als 31 Wiederholungen, für je 31 Zeichen dann 'Zwischenschritte' sozusagen komprimiert.
Ich hatte auch mit Flag '1100 0000' und '0100 0000' experimentiert. Was 63 Wiederholungen erlaubt hätte ohne 'Zwischen-Komprimierung'. Jedoch zeigte sich, dass es mit Realdaten viel seltener zu mehr als 31 Wiederholungen kommt, als das in den unkomprimierten Originaldaten ein Byte '1100 0000' enthalten ist was dann extra Maskiert werden müsste.
Auch mit einem '2-Byte Flag / Wiederholungsanzahl' habe ich experimentiert. Doch auch hier zeigte sich, dass bei Realdaten viel zu selten so viele Wiederholungen vorkommen, um den Mehrbedarf des 2. Bytes auszugleichen.
Wenn also eine Wiederholung entdeckt wird, dann wird dies mit 2 Bytes gespeichert. Zuerst das original Zeichen/Byte, dann das Flag inkl. Anzahl. Komprimierungsmäßig wäre es kein Unterschied welches der beiden Bytes zuerst kommt, aber in dieser Reihenfolge lies es sich besser in Quellcode programmieren.
Ein komprimiertes Zeichen wird also gespeichert mit diesen 2 Bytes:
<Zeichen/Byte> + <111<Wiederholungen>>
Eine Komprimierung erfolgt erst bei 3 Wiederholungen. 2 Wiederholungen würden sich zwar auch kodieren lassen, aber ohne Speicherersparnis und da dies keine Vorteile bringt sondern nur mehr Aufwand für die CPU wäre, wird erst bei 3 Wiederholung komprimiert.
Was ich damit sagen will: Selbst so etwas eigentlich simples wie die Lauflängenkodierung, hat schon einige Fallstricke und Sondersituationen die man beachten muss, damit sie absolut Fehlerfrei arbeitet. Und dies wiederum führt dazu, dass erst bei mehr als 2x gleiches Zeichen/Byte eine Komprimierung erfolgen kann. Zumindest gilt dies, wenn nicht nur ausschließlich ASCII Zeichen bis 127 komprimiert werden sollen. Also bereits bei UTF8 / Unicode Texte, braucht es das … netter Nebeneffekt ist dann jedoch, dass Binärdaten dann auch genauso problemlos funktionieren :-D
Falls jemand Interesse hat... im Anhang ist der Quellcode dazu. Der Code ist allerdings wegen der Performanceoptimierungen nicht sonderlich schön und die Variablennamen würde ich Heute auch anders benennen.
XCompress.cs
Die Benutzung ist ganz einfach. Es sind 2 static Funktionen in einer static Class:
byte[] Compress(byte[] data, ref ushort dataSize)
byte[] DeCompress(byte[] data, ref ushort dataSize)
Übergeben wird jeweils ein byte Array und die Größe als ushort.
Das Array darf also größer sein als die darin enthaltenen Nutzdaten. Und, wegen ushort, können nur maximal 65535 Bytes auf einmal komprimiert/dekomprimiert werden.
Zurück gegeben wird jeweils auch wieder ein byte Array. Entweder sind dies dann komprimierte Daten, oder die unveränderten original Daten.
Und zurück kommt bei BEIDEN Funktionen, auch wieder die Größe der Nutzdaten des zurückgegebenen byte Arrays (deswegen
ref ushort).
Compress() gibt Original zurück wenn sich Komprimierung nicht gelohnt hat. Und DeCompress() gibt Original zurück wenn die Input-Daten nicht komprimiert waren.