C#和JavaScript 使用 Lz4 文本压缩算法 相互压缩/解压

C# & JavaScript 使用LZ4 相互压缩&解压

  • 前言
    • 正文
      • util.js -> Util.cs
      • xxh32.js -> Xxh32.cs
      • lz4.js -> Lz4.cs
  • 测试
    • C#端
    • JavaScript端
  • 不足之处
  • 看完了? 点赞、收藏、关注!
  • 转载只能放本文标题、链接

前言

      工作需要,由于复杂度提升,公司算法端生成的结果会有几十甚至上百M,直接将结果发送到网页端,太占用网络资源。因此考虑将C#端的结果文本进行压缩,发送给前端。通过比较压缩/解压速度,最后选取了Lz4压缩算法。
      网上找了一些资料,C#端用lz4net这个库,js端用这个库lz4js。
      近一步测试,发现这两个库只能压缩/解压自己的内容,并不能相互压缩/解压。通过对C#端lz4net库的dll文件的反编译,发现其压缩算法和js端稍有差异,导致无法相互解析。
      综合以上,只要保证C#和JS两边算法一致即可相互解析。由于C#端算法阅读性差,最终决定将js的lz4压缩算法移植到C#端。

正文

util.js -> Util.cs

using System;

namespace Lz4CSharp
{
    class Util
    {
        //相当于js中运算符‘>>>‘
        public static UInt32 UInt32MoveRight(UInt32 x, int y)
        {
            UInt32 mask = 0x7fffffff; //Integer.MAX_VALUE
            for (int i = 0; i < y; i++)
            {
                x >>= 1;
                x &= mask;
            }
            return x;
        }
        // Simple hash function, from: http://burtleburtle.net/bob/hash/integer.html.
        // Chosen because it doesn't use multiply and achieves full avalanche.
        public static UInt32 hashU32(UInt32 a)
        {
            a |= 0;
            a = a + 2127912214 + (a << 12) | 0;
            a = (UInt32)(a ^ -949894596 ^ UInt32MoveRight(a, 19));
            a = a + 374761393 + (a << 5) | 0;
            a = (UInt32)(a + -744332180 ^ a << 9);
            a = (UInt32)(a + -42973499 + (a << 3) | 0);
            return (UInt32)(a ^ -1252372727 ^ UInt32MoveRight(a, 16) | 0);
        }

        // Reads a 64-bit little-endian integer from an array.
        public static UInt64 readU64(UInt32[] b, int n)
        {
            UInt64 x = 0;
            x |= b[n++] << 0;
            x |= b[n++] << 8;
            x |= b[n++] << 16;
            x |= b[n++] << 24;
            x |= b[n++] << 32;
            x |= b[n++] << 40;
            x |= b[n++] << 48;
            x |= b[n++] << 56;
            return x;
        }

        // Reads a 32-bit little-endian integer from an array.
        public static UInt32 readU32(UInt32[] b, int n)
        {
            UInt32 x = 0;
            x |= b[n++] << 0;
            x |= b[n++] << 8;
            x |= b[n++] << 16;
            x |= b[n++] << 24;
            return x;
        }

        // Writes a 32-bit little-endian integer from an array.
        public static void writeU32(UInt32[] b, int n, UInt32 x)
        {
            b[n++] = (x >> 0) & 0xff;
            b[n++] = (x >> 8) & 0xff;
            b[n++] = (x >> 16) & 0xff;
            b[n++] = (x >> 24) & 0xff;
        }

        // Multiplies two numbers using 32-bit integer multiplication.
        // Algorithm from Emscripten.
        public static UInt32 imul(UInt32 a, UInt32 b)
        {
            var ah = UInt32MoveRight(a, 16);
            var al = a & 65535;
            var bh = UInt32MoveRight(b, 16);
            var bl = b & 65535;

            return al * bl + (ah * bl + al * bh << 16) | 0;
        }
    }
}

xxh32.js -> Xxh32.cs

using System;

namespace Lz4CSharp
{
    class Xxh32
    {
        // xxhash32 primes
        static UInt32 prime1 = 0x9e3779b1;
        static UInt32 prime2 = 0x85ebca77;
        static UInt32 prime3 = 0xc2b2ae3d;
        static UInt32 prime4 = 0x27d4eb2f;
        static UInt32 prime5 = 0x165667b1;

        // Utility functions/primitives
        // --

        public static UInt32 rotl32(UInt32 x, int r)
        {
            x |= 0;
            r |= 0;

            return Util.UInt32MoveRight(x, 32 - r | 0) | x << r | 0;
        }

        public static UInt32 rotmul32(UInt32 h, int r, UInt32 m)
        {
            h |= 0;
            r |= 0;
            m |= 0;

            return Util.imul(Util.UInt32MoveRight(h, 32 - r | 0) | h << r, m) | 0;
        }

        public static UInt32 shiftxor32(UInt32 h, int s)
        {
            h |= 0;
            s |= 0;

            return Util.UInt32MoveRight(h, s) ^ h | 0;
        }

        // Implementation
        // --

        public static UInt32 xxhapply(UInt32 h, UInt32 src, UInt32 m0, int s, UInt32 m1)
        {
            return rotmul32(Util.imul(src, m0) + h, s, m1);
        }

        public static UInt32 xxh1(UInt32 h, UInt32[] src, int index)
        {
            return rotmul32((h + Util.imul(src[index], prime5)), 11, prime1);
        }

        public static UInt32 xxh4(UInt32 h, UInt32[] src, int index)
        {
            return xxhapply(h, Util.readU32(src, index), prime3, 17, prime4);
        }

        public static UInt32[] xxh16(UInt32[] h, UInt32[] src, int index)
        {
            return new UInt32[] {
              xxhapply(h[0], Util.readU32(src, index + 0), prime2, 13, prime1),
              xxhapply(h[1], Util.readU32(src, index + 4), prime2, 13, prime1),
              xxhapply(h[2], Util.readU32(src, index + 8), prime2, 13, prime1),
              xxhapply(h[3], Util.readU32(src, index + 12), prime2, 13, prime1)
            };
        }

        public static UInt32 xxh32(UInt32 seed, UInt32[] src, int index, int len)
        {
            UInt32 h;
            UInt32 l = (UInt32)len;
            if (len >= 16)
            {
                UInt32[] tmpH = new UInt32[] {
                  seed + prime1 + prime2,
                  seed + prime2,
                  seed,
                  seed - prime1
                };

                while (len >= 16)
                {
                    tmpH = xxh16(tmpH, src, index);

                    index += 16;
                    len -= 16;
                }

                h = rotl32(tmpH[0], 1) + rotl32(tmpH[1], 7) + rotl32(tmpH[2], 12) + rotl32(tmpH[3], 18) + l;
            }
            else
            {
                h = Util.UInt32MoveRight(seed + prime5 + (UInt32)len, 0);
            }

            while (len >= 4)
            {
                h = xxh4(h, src, index);

                index += 4;
                len -= 4;
            }

            while (len > 0)
            {
                h = xxh1(h, src, index);

                index++;
                len--;
            }

            h = shiftxor32(Util.imul(shiftxor32(Util.imul(shiftxor32(h, 15), prime2), 13), prime3), 16);

            return Util.UInt32MoveRight(h , 0);
        }
    }
}

lz4.js -> Lz4.cs

using System;
using System.Collections.Generic;

namespace Lz4CSharp
{
    class Lz4
    {
        // lz4.js - An implementation of Lz4 in plain JavaScript.
        //
        // TODO:
        // - Unify header parsing/writing.
        // - Support options (block size, checksums)
        // - Support streams
        // - Better error handling (handle bad offset, etc.)
        // - HC support (better search algorithm)
        // - Tests/benchmarking

        // Constants
        // --

        // Compression format parameters/constants.
        static UInt32 minMatch = 4;
        static UInt32 minLength = 13;
        static UInt32 searchLimit = 5;
        static int skipTrigger = 6;
        static UInt32 hashSize = 1 << 16;

        // Token constants.
        static int mlBits = 4;
        static int mlMask = (1 << mlBits) - 1;
        static int runBits = 4;
        static int runMask = (1 << runBits) - 1;

        // Shared buffers
        static UInt32[] blockBuf = new UInt32[5 << 20];
        static UInt32[] hashTable = new UInt32[hashSize];

        // Frame constants.
        static UInt32 magicNum = 0x184D2204;

        // Frame descriptor flags.
        static UInt32 fdContentChksum = 0x4;
        static UInt32 fdContentSize = 0x8;
        static UInt32 fdBlockChksum = 0x10;
        // var fdBlockIndep = 0x20;
        static UInt32 fdVersion = 0x40;
        static UInt32 fdVersionMask = 0xC0;

        // Block sizes.
        static UInt32 bsUncompressed = 0x80000000;
        static UInt32 bsDefault = 7;
        static int bsShift = 4;
        static UInt32 bsMask = 7;
        static Dictionary<UInt32, UInt32> bsMap = new Dictionary<UInt32, UInt32>();
        public static void Init()
        {
            bsMap.Add(4, 0x10000);
            bsMap.Add(5, 0x40000);
            bsMap.Add(6, 0x100000);
            bsMap.Add(7, 0x400000);
        }

        // Utility functions/primitives
        // --



        // Clear hashtable.
        static void clearHashTable(UInt32[] table)
        {
            for (var i = 0; i < hashSize; i++)
            {
                hashTable[i] = 0;
            }
        }



        static UInt32[] sliceArray(UInt32[] array, int start, int end)
        {
            // Uint8Array#slice polyfill.
            var len = array.Length;

            // Calculate start.
            start = start | 0;
            start = (start < 0) ? Math.Max(len + start, 0) : Math.Min(start, len);

            // Calculate end.
            end = end == null ? len : end | 0;
            end = (end < 0) ? Math.Max(len + end, 0) : Math.Min(end, len);

            // Copy into new array.
            var arraySlice = new UInt32[end - start];
            for (int i = start, n = 0; i < end;)
            {
                arraySlice[n++] = array[i++];
            }

            return arraySlice;
        }


        // Implementation
        // --

        // Calculates an upper bound for lz4 compression.
        static UInt32 compressBound(UInt32 n)
        {
            return (n + (n / 255) + 16) | 0;
        }

        // Calculates an upper bound for lz4 decompression, by reading the data.
        static UInt64 decompressBound(UInt32[] src)
        {
            int sIndex = 0;

            // Read magic number
            if (Util.readU32(src, sIndex) != magicNum)
            {
                throw new Exception("invalid magic number");
            }

            sIndex += 4;

            // Read descriptor
            var descriptor = src[sIndex++];

            // Check version
            if ((descriptor & fdVersionMask) != fdVersion)
            {
                throw new Exception($"incompatible descriptor version {descriptor & fdVersionMask}");
            }

            // Read flags
            var useBlockSum = (descriptor & fdBlockChksum) != 0;
            var useContentSize = (descriptor & fdContentSize) != 0;

            // Read block size
            UInt32 bsIdx = (src[sIndex++] >> bsShift) & bsMask;

            if (!bsMap.ContainsKey(bsIdx))
            {
                throw new Exception($"invalid block size {bsIdx}");
            }

            var maxBlockSize = bsMap[bsIdx];

            // Get content size
            if (useContentSize)
            {
                return Util.readU64(src, sIndex);
            }

            // Checksum
            sIndex++;

            // Read blocks.
            UInt32 maxSize = 0;
            while (true)
            {
                var blockSize = Util.readU32(src, sIndex);
                sIndex += 4;

                if ((blockSize & bsUncompressed) != 0)
                {
                    blockSize &= ~bsUncompressed;
                    maxSize += blockSize;
                }
                else
                {
                    maxSize += maxBlockSize;
                }

                if (blockSize == 0)
                {
                    return maxSize;
                }

                if (useBlockSum)
                {
                    sIndex += 4;
                }

                sIndex += (int)blockSize;
            }
        }

        // Decompresses a block of Lz4.
        static UInt32 decompressBlock(UInt32[] src, UInt32[] dst, UInt32 sIndex, UInt32 sLength, UInt32 dIndex)
        {
            UInt32 mLength, mOffset, sEnd, n, i;

            // Setup initial state.
            sEnd = sIndex + sLength;

            // Consume entire input block.
            while (sIndex < sEnd)
            {
                var token = src[sIndex++];

                // Copy literals.
                var literalCount = (token >> 4);
                if (literalCount > 0)
                {
                    // Parse length.
                    if (literalCount == 0xf)
                    {
                        while (true)
                        {
                            literalCount += src[sIndex];
                            if (src[sIndex++] != 0xff)
                            {
                                break;
                            }
                        }
                    }

                    // Copy literals
                    for (n = sIndex + literalCount; sIndex < n;)
                    {
                        dst[dIndex++] = src[sIndex++];
                    }
                }

                if (sIndex >= sEnd)
                {
                    break;
                }

                // Copy match.
                mLength = (token & 0xf);

                // Parse offset.
                mOffset = src[sIndex++] | (src[sIndex++] << 8);

                // Parse length.
                if (mLength == 0xf)
                {
                    while (true)
                    {
                        mLength += src[sIndex];
                        if (src[sIndex++] != 0xff)
                        {
                            break;
                        }
                    }
                }

                mLength += minMatch;

                // Copy match.
                for (i = dIndex - mOffset, n = i + mLength; i < n;)
                {
                    dst[dIndex++] = dst[i++] | 0;
                }
            }

            return dIndex;
        }

        // Compresses a block with Lz4.
        static UInt32 compressBlock(UInt32[] src, UInt32[] dst, int sIndex, int sLength, UInt32[] hashTable)
        {
            int mIndex, mAnchor, mLength, mOffset, mStep;
            int literalCount, dIndex, sEnd, n;

            // Setup initial state.
            dIndex = 0;
            sEnd = sLength + sIndex;
            mAnchor = sIndex;

            // Process only if block is large enough.
            if (sLength >= minLength)
            {
                var searchMatchCount = (1 << skipTrigger) + 3;

                // Consume until last n literals (Lz4 spec limitation.)
                while (sIndex + minMatch < sEnd - searchLimit)
                {
                    var seq = Util.readU32(src, (int)sIndex);
                    var hash = Util.UInt32MoveRight(Util.hashU32(seq), 0);

                    // Crush hash to 16 bits.
                    hash = Util.UInt32MoveRight((hash >> 16) ^ hash , 0) & 0xffff;

                    // Look for a match in the hashtable. NOTE: remove one; see below.
                    mIndex = (int)hashTable[hash] - 1;

                    // Put pos in hash table. NOTE: add one so that zero = invalid.
                    hashTable[hash] = (UInt32)sIndex + 1;

                    // Determine if there is a match (within range.)
                    if (mIndex < 0 || Util.UInt32MoveRight((UInt32)(sIndex - mIndex) , 16) > 0 || Util.readU32(src, (int)mIndex) != seq)
                    {
                        mStep = searchMatchCount++ >> skipTrigger;
                        sIndex += mStep;
                        continue;
                    }

                    searchMatchCount = (1 << skipTrigger) + 3;

                    // Calculate literal count and offset.
                    literalCount = sIndex - mAnchor;
                    mOffset = sIndex - mIndex;

                    // We've already matched one word, so get that out of the way.
                    sIndex += (int)minMatch;
                    mIndex += (int)minMatch;

                    // Determine match length.
                    // N.B.: mLength does not include minMatch, Lz4 adds it back
                    // in decoding.
                    mLength = sIndex;
                    while (sIndex < sEnd - searchLimit && src[sIndex] == src[mIndex])
                    {
                        sIndex++;
                        mIndex++;
                    }
                    mLength = sIndex - mLength;

                    // Write token + literal count.
                    int token = mLength < mlMask ? mLength : mlMask;
                    if (literalCount >= runMask)
                    {
                        dst[dIndex++] = (UInt32)((runMask << mlBits) + token);
                        for (n = literalCount - runMask; n >= 0xff; n -= 0xff)
                        {
                            dst[dIndex++] = 0xff;
                        }
                        dst[dIndex++] = (UInt32)n;
                    }
                    else
                    {
                        dst[dIndex++] = (UInt32)((literalCount << mlBits) + token);
                    }

                    // Write literals.
                    for (var i = 0; i < literalCount; i++)
                    {
                        dst[dIndex++] = src[mAnchor + i];
                    }

                    // Write offset.
                    dst[dIndex++] = (UInt32)mOffset;
                    dst[dIndex++] = (UInt32)mOffset >> 8;

                    // Write match length.
                    if (mLength >= mlMask)
                    {
                        for (n = mLength - mlMask; n >= 0xff; n -= 0xff)
                        {
                            dst[dIndex++] = 0xff;
                        }
                        dst[dIndex++] = (UInt32)n;
                    }

                    // Move the anchor.
                    mAnchor = sIndex;
                }
            }

            // Nothing was encoded.
            if (mAnchor == 0)
            {
                return 0;
            }

            // Write remaining literals.
            // Write literal token+count.
            literalCount = sEnd - mAnchor;
            if (literalCount >= runMask)
            {
                dst[dIndex++] = (UInt32)(runMask << mlBits);
                for (n = literalCount - runMask; n >= 0xff; n -= 0xff)
                {
                    dst[dIndex++] = 0xff;
                }
                dst[dIndex++] = (UInt32)n;
            }
            else
            {
                dst[dIndex++] = (UInt32)(literalCount << mlBits);
            }

            // Write literals.
            sIndex = mAnchor;
            while (sIndex < sEnd)
            {
                dst[dIndex++] = src[sIndex++];
            }

            return (UInt32)dIndex;
        }

        // Decompresses a frame of Lz4 data.
        static UInt32 decompressFrame(UInt32[] src, UInt32[] dst)
        {
            bool useBlockSum, useContentSum, useContentSize;
            UInt32 descriptor;
            UInt32 sIndex = 0;
            UInt32 dIndex = 0;

            // Read magic number
            if (Util.readU32(src, (int)sIndex) != magicNum)
            {
                throw new Exception("invalid magic number");
            }

            sIndex += 4;

            // Read descriptor
            descriptor = src[sIndex++];

            // Check version
            if ((descriptor & fdVersionMask) != fdVersion)
            {
                throw new Exception("incompatible descriptor version");
            }

            // Read flags
            useBlockSum = (descriptor & fdBlockChksum) != 0;
            useContentSum = (descriptor & fdContentChksum) != 0;
            useContentSize = (descriptor & fdContentSize) != 0;

            // Read block size
            var bsIdx = (src[sIndex++] >> bsShift) & bsMask;
            
            if (!bsMap.ContainsKey(bsIdx))
            {
                throw new Exception("invalid block size");
            }

            if (useContentSize)
            {
                // TODO: read content size
                sIndex += 8;
            }

            sIndex++;

            // Read blocks.
            while (true)
            {
                UInt32 compSize;

                compSize = Util.readU32(src, (int)sIndex);
                sIndex += 4;

                if (compSize == 0)
                {
                    break;
                }

                if (useBlockSum)
                {
                    // TODO: read block checksum
                    sIndex += 4;
                }

                // Check if block is compressed
                if ((compSize & bsUncompressed) != 0)
                {
                    // Mask off the 'uncompressed' bit
                    compSize &= ~bsUncompressed;

                    // Copy uncompressed data into destination buffer.
                    for (var j = 0; j < compSize; j++)
                    {
                        dst[dIndex++] = src[sIndex++];
                    }
                }
                else
                {
                    // Decompress into blockBuf
                    dIndex = decompressBlock(src, dst, sIndex, compSize, dIndex);
                    sIndex += compSize;
                }
            }

            if (useContentSum)
            {
                // TODO: read content checksum
                sIndex += 4;
            }

            return dIndex;
        }

        // Compresses data to an Lz4 frame.
        static UInt32 compressFrame(UInt32[] src, UInt32[] dst)
        {
            var dIndex = 0;

            // Write magic number.
            Util.writeU32(dst, dIndex, magicNum);
            dIndex += 4;

            // Descriptor flags.
            dst[dIndex++] = fdVersion;
            dst[dIndex++] = bsDefault << bsShift;

            UInt32 x32 = Xxh32.xxh32(0, dst, 4, dIndex - 4);
            // Descriptor checksum.
            dst[dIndex] = (UInt32)((UInt16)x32 >> 8);
            dIndex++;

            // Write blocks.
            var maxBlockSize = bsMap[bsDefault];
            var remaining = src.Length;
            var sIndex = 0;

            // Clear the hashtable.
            clearHashTable(hashTable);

            // Split input into blocks and write.
            while (remaining > 0)
            {
                UInt32 compSize = 0;
                int blockSize = remaining > maxBlockSize ? (int)maxBlockSize : remaining;

                compSize = compressBlock(src, blockBuf, sIndex, blockSize, hashTable);

                if (compSize > blockSize || compSize == 0)
                {
                    // Output uncompressed.
                    Util.writeU32(dst, dIndex, (UInt32)(0x80000000 | blockSize));
                    dIndex += 4;

                    for (var z = sIndex + blockSize; sIndex < z;)
                    {
                        dst[dIndex++] = src[sIndex++];
                    }

                    remaining -= blockSize;
                }
                else
                {
                    // Output compressed.
                    Util.writeU32(dst, dIndex, compSize);
                    dIndex += 4;

                    for (var j = 0; j < compSize;)
                    {
                        dst[dIndex++] = blockBuf[j++];
                    }

                    sIndex += blockSize;
                    remaining -= blockSize;
                }
            }

            // Write blank end block.
            Util.writeU32(dst, dIndex, 0);
            dIndex += 4;

            return (UInt32)dIndex;
        }

        // Decompresses a buffer containing an Lz4 frame. maxSize is optional; if not
        // provided, a maximum size will be determined by examining the data. The
        // buffer returned will always be perfectly-sized.
        public static UInt32[] decompress(UInt32[] src, UInt64 maxSize = 0)
        {
            UInt32[] dst;
            UInt32 size;

            if (maxSize == 0)
            {
                maxSize = decompressBound(src);
            }

            dst = new UInt32[maxSize];
            size = decompressFrame(src, dst);

            if (size != maxSize)
            {
                dst = sliceArray(dst, 0, (int)size);
            }

            return dst;
        }

        // Compresses a buffer to an Lz4 frame. maxSize is optional; if not provided,
        // a buffer will be created based on the theoretical worst output size for a
        // given input size. The buffer returned will always be perfectly-sized.
        public static UInt32[] compress(UInt32[] src, UInt32 maxSize = 0)
        {
            UInt32[] dst;
            UInt32 size;

            if (maxSize == 0)
            {
                maxSize = compressBound((UInt32)src.Length);
            }

            dst = new UInt32[maxSize];
            size = compressFrame(src, dst);

            if (size != maxSize)
            {
                dst = sliceArray(dst, 0, (int)size);
            }

            return dst;
        }
    }
}

测试

C#端

static void Main(string[] args)
{
    Lz4.Init();

    string str1 = "Hello World! 你好 世界!";
    string str2 = "A painter hangs his or her finished picture on a wall, and everyone can see it. A composer writes a work, but no one can hear it until it is performed. Professional singers and players have great responsibilities, for the composer is utterly dependent on them. A student of music needs as long and as arduous a training to become a performer as a medical student needs to become a doctor. Most training is concerned with technique, for musicians have to have the muscular proficiency of an athlete or a ballet dancer. Singers practice breathing every day, as their vocal chords would be inadequate without controlled muscular support. String players practice moving the fingers of the left hand up and down, while drawing the bow to and fro with the right arm -- two entirely different movements.画家将已完成的作品挂在墙上,每个人都可以观赏到。 作曲家写完了一部作品,得由 演奏者将其演奏出来,其他人才能得以欣赏。因为作曲家是如此完全地依赖于职业歌手和职 业演奏者,所以职业歌手和职业演奏者肩上的担子可谓不轻。 一名学音乐的学生要想成为 一名演奏者,需要经受长期的、严格的训练,就象一名医科的学生要成为一名医生一样。 绝 大多数的训练是技巧性的。 音乐家们控制肌肉的熟练程度,必须达到与运动员或巴蕾舞演 员相当的水平。 歌手们每天都练习吊嗓子,因为如果不能有效地控制肌肉的话,他们的声 带将不能满足演唱的要求。 弦乐器的演奏者练习的则是在左手的手指上下滑动的同时,用 右手前后拉动琴弓--两个截然不同的动作。";

    byte[] bytes = Encoding.UTF8.GetBytes(str2);

    var compress = Lz4.compress(Array.ConvertAll<byte, UInt32>(bytes, input => { return (UInt32)input; }));
    byte[] compressBytes = Array.ConvertAll<UInt32, byte>(compress, input => { return (byte)input; });
    var base64Str = Convert.ToBase64String(compressBytes);
    Console.WriteLine(base64Str);


    var decompress = Lz4.decompress(Array.ConvertAll<byte, UInt32>(Convert.FromBase64String(base64Str), input => { return (UInt32)input; }));
    byte[] decompressBytes = Array.ConvertAll<UInt32, byte>(decompress, input => { return (byte)input; });
    string decompressString = Encoding.UTF8.GetString(decompressBytes);
    Console.WriteLine(decompressString);

    Console.ReadKey();
}

测试结果
C#和JavaScript 使用 Lz4 文本压缩算法 相互压缩/解压_第1张图片

JavaScript端

<script>
export default {
  mounted() {
    var lz4 = require("lz4js");

    var str1 = "Hello World! 你好 世界!"
    var str2 = 'A painter hangs his or her finished picture on a wall, and everyone can see it. A composer writes a work, but no one can hear it until it is performed. Professional singers and players have great responsibilities, for the composer is utterly dependent on them. A student of music needs as long and as arduous a training to become a performer as a medical student needs to become a doctor. Most training is concerned with technique, for musicians have to have the muscular proficiency of an athlete or a ballet dancer. Singers practice breathing every day, as their vocal chords would be inadequate without controlled muscular support. String players practice moving the fingers of the left hand up and down, while drawing the bow to and fro with the right arm -- two entirely different movements.画家将已完成的作品挂在墙上,每个人都可以观赏到。 作曲家写完了一部作品,得由 演奏者将其演奏出来,其他人才能得以欣赏。因为作曲家是如此完全地依赖于职业歌手和职 业演奏者,所以职业歌手和职业演奏者肩上的担子可谓不轻。 一名学音乐的学生要想成为 一名演奏者,需要经受长期的、严格的训练,就象一名医科的学生要成为一名医生一样。 绝 大多数的训练是技巧性的。 音乐家们控制肌肉的熟练程度,必须达到与运动员或巴蕾舞演 员相当的水平。 歌手们每天都练习吊嗓子,因为如果不能有效地控制肌肉的话,他们的声 带将不能满足演唱的要求。 弦乐器的演奏者练习的则是在左手的手指上下滑动的同时,用 右手前后拉动琴弓--两个截然不同的动作。'

    var u8a = Buffer.from(str2);
    var compressed = lz4.compress(u8a);
    var base64Str = this.uint8arrayToBase64(compressed);
    console.log(base64Str);

    var decompressU8A = Uint8Array.from(atob(base64Str), c => c.charCodeAt(0));
    var decompressed = lz4.decompress(decompressU8A);
    
    // 由于部分浏览器对String栈有大小限制,下面这行解析字符串,对于本文的短字符串没有问题,但是对于长字符串就会导致栈溢出(可自行测试几兆的字符串,及不同的浏览器)
    // var encodedString = String.fromCharCode.apply(null, decompressed);

    // 使用分段解析可以避免出现栈溢出的问题
    var encodedString = this.sliceReadArray(decompressed);
    var decodedString = decodeURIComponent(escape(encodedString));
    console.log(decodedString);
  },
  methods:{
    uintToString(uintArray) {
        var encodedString = String.fromCharCode.apply(null, uintArray),
            decodedString = decodeURIComponent(escape(encodedString));
        return decodedString;
    },
    uint8arrayToBase64(u8Arr) {
        let CHUNK_SIZE = 0x8000; //arbitrary number
        let index = 0;
        let length = u8Arr.length;
        let result = '';
        let slice;
        while (index < length) {
            slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length));
            result += String.fromCharCode.apply(null, slice);
            index += CHUNK_SIZE;
        }
        return btoa(result);
    },
    sliceReadArray(array){
      var res = '';
      var chunk = 8 * 1024;
      var i;
      for (i = 0; i < array.length / chunk; i++) {
        res += String.fromCharCode.apply(null, array.slice(i * chunk, (i + 1) * chunk));
      }
      res += String.fromCharCode.apply(null, array.slice(i * chunk));
      return res;
    }
  }
};
</script>

测试结果
C#和JavaScript 使用 Lz4 文本压缩算法 相互压缩/解压_第2张图片

不足之处

      由于一开始的误判,将byte类型写为UInt32,导致C#在压缩及解压时,会多占用4倍的空间。但不影响压缩、解压。
      有时间再优化吧~~

看完了? 点赞、收藏、关注!

转载只能放本文标题、链接

你可能感兴趣的:(笔记,c#,javascript,算法)