Lz77.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using System.Text;
  4. namespace System.Net.ApiService.Compression.Lz77 {
  5. public class LZ77 {
  6. #region Fields
  7. private int _defaultWindowLength;
  8. private int _maxStringDistance;
  9. private int _maxStringLength;
  10. private int _maxWindowLength;
  11. private int _minStringLength;
  12. private int _referenceIntBase;
  13. private int _referenceIntCeilCode;
  14. private int _referenceIntFloorCode;
  15. private byte _referencePrefix;
  16. #endregion Fields
  17. #region Constructors
  18. public LZ77() {
  19. _referencePrefix = (byte)'`';
  20. _referenceIntBase = 96;
  21. _referenceIntFloorCode = 32;
  22. _referenceIntCeilCode = 128;
  23. _maxStringDistance = (int)Math.Pow(_referenceIntBase, 2) - 1;
  24. _minStringLength = 5;
  25. _maxStringLength = (int)Math.Pow(_referenceIntBase, 1) - 1 + _minStringLength;
  26. _defaultWindowLength = 144;
  27. _maxWindowLength = _maxStringDistance + _minStringLength;
  28. }
  29. #endregion Constructors
  30. #region Methods
  31. public static byte[] CompressStr(string data) {
  32. LZ77 lz = new LZ77();
  33. return lz.Compress(Encoding.Default.GetBytes(data), 50);
  34. }
  35. public static string DecompressStr(byte[] data) {
  36. LZ77 lz = new LZ77();
  37. return lz.Decompress(data);
  38. }
  39. public byte[] Compress(string data) {
  40. return Compress(Encoding.Default.GetBytes(data), -1);
  41. }
  42. public byte[] Compress(byte[] data, int windowLength) {
  43. if (windowLength == -1) {
  44. windowLength = _defaultWindowLength;
  45. }
  46. if (windowLength > _maxWindowLength) {
  47. throw new ArgumentException("Window length is too large.");
  48. }
  49. List<byte> compressed = new List<byte>();
  50. int pos = 0;
  51. int lastPos = data.Length - _minStringLength;
  52. while (pos < lastPos) {
  53. int searchStart = Math.Max(pos - windowLength, 0);
  54. int matchLength = _minStringLength;
  55. bool foundMatch = false;
  56. int bestMatchDistance = _maxStringDistance;
  57. int bestMatchLength = 0;
  58. List<byte> newCompressed = new List<byte>();
  59. while ((searchStart + matchLength) < pos) {
  60. int sourceWindowEnd = Math.Min(searchStart + matchLength, data.Length);
  61. int targetWindowEnd = Math.Min(pos + matchLength, data.Length);
  62. IList<byte> m1 = data.Skip(searchStart).Take(sourceWindowEnd - searchStart).ToList();
  63. IList<byte> m2 = data.Skip(pos).Take(targetWindowEnd - pos).ToList();
  64. bool isValidMatch = m1.SequenceEqual(m2) && matchLength < _maxStringLength;
  65. if (isValidMatch) {
  66. matchLength++;
  67. foundMatch = true;
  68. } else {
  69. int realMatchLength = matchLength - 1;
  70. if (foundMatch && (realMatchLength > bestMatchLength)) {
  71. bestMatchDistance = pos - searchStart - realMatchLength;
  72. bestMatchLength = realMatchLength;
  73. }
  74. matchLength = _minStringLength;
  75. searchStart++;
  76. foundMatch = false;
  77. }
  78. }
  79. if (bestMatchLength != 0) {
  80. newCompressed.Add(_referencePrefix);
  81. newCompressed.AddRange(EncodeReferenceInt(bestMatchDistance, 2));
  82. newCompressed.AddRange(EncodeReferenceLength(bestMatchLength));
  83. pos += bestMatchLength;
  84. } else {
  85. if (data[pos] != _referencePrefix) {
  86. newCompressed = new List<byte>(new byte[] { data[pos] });
  87. } else {
  88. newCompressed = new List<byte>(new byte[] { _referencePrefix, _referencePrefix });
  89. }
  90. pos++;
  91. }
  92. compressed.AddRange(newCompressed);
  93. }
  94. var lasts = data.Skip(pos).Take(data.Length - pos);
  95. lasts
  96. .Where(x => x == _referencePrefix)
  97. .ToList()
  98. .ForEach
  99. (
  100. x =>
  101. compressed
  102. .AddRange
  103. (
  104. new byte[]
  105. {
  106. _referencePrefix,
  107. _referencePrefix
  108. }
  109. )
  110. );
  111. return compressed.ToArray();
  112. }
  113. public string Decompress(byte[] data) {
  114. List<byte> decompressed = new List<byte>();
  115. int pos = 0;
  116. while (pos < data.Length) {
  117. byte currentByte = data[pos];
  118. if (currentByte != _referencePrefix) {
  119. decompressed.Add(currentByte);
  120. pos++;
  121. } else {
  122. byte nextChar = data[pos + 1];
  123. if (nextChar != _referencePrefix) {
  124. int distance = DecodeReferenceInt(data.Skip(pos + 1).Take(2).ToList(), 2);
  125. int length = DecodeReferenceLength(data.Skip(pos + 3).Take(1).ToList());
  126. int start = decompressed.Count - distance - length;
  127. int end = start + length;
  128. var slice = decompressed.Skip(start).Take(end - start).ToList();
  129. decompressed.AddRange(slice);
  130. pos += _minStringLength - 1;
  131. } else {
  132. decompressed.Add((byte)_referencePrefix);
  133. pos += 2;
  134. }
  135. }
  136. }
  137. return Encoding.Default.GetString(decompressed.ToArray());
  138. }
  139. private int DecodeReferenceInt(IList<byte> data, int width) {
  140. int value = 0;
  141. for (int i = 0; i < width; i++) {
  142. value *= _referenceIntBase;
  143. int charCode = (int)data[i];
  144. if ((charCode >= _referenceIntFloorCode) && (charCode <= _referenceIntCeilCode)) {
  145. value += charCode - _referenceIntFloorCode;
  146. } else {
  147. throw new ArgumentException("Invalid char code in reference int: " + charCode);
  148. }
  149. }
  150. return value;
  151. }
  152. private int DecodeReferenceLength(IList<byte> data) {
  153. return DecodeReferenceInt(data, 1) + _minStringLength;
  154. }
  155. private IList<byte> EncodeReferenceInt(int value, int width) {
  156. if ((value >= 0) && (value < (Math.Pow(_referenceIntBase, width) - 1))) {
  157. IList<byte> encoded = new List<byte>();
  158. while (value > 0) {
  159. byte b = (byte)((value % _referenceIntBase) + _referenceIntFloorCode);
  160. encoded.Insert(0, b);
  161. value = (int)Math.Floor((double)value / _referenceIntBase);
  162. }
  163. int missingLength = width - encoded.Count;
  164. for (int i = 0; i < missingLength; i++) {
  165. byte b = (byte)_referenceIntFloorCode;
  166. encoded.Insert(0, b);
  167. }
  168. return encoded;
  169. } else {
  170. throw new ArgumentException(string.Format("Reference int out of range: {0} (width = {1})", value, width));
  171. }
  172. }
  173. private IList<byte> EncodeReferenceLength(int length) {
  174. return EncodeReferenceInt(length - _minStringLength, 1);
  175. }
  176. #endregion Methods
  177. }
  178. }