引入redis服务,支持自动化单元测试
大石头 编写于 2022-03-31 22:56:30
X
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using NewLife.Collections;

namespace NewLife
{
    /// <summary>IO工具类</summary>
    /// <remarks>
    /// 文档 https://www.yuque.com/smartstone/nx/io_helper
    /// </remarks>
    public static class IOHelper
    {
        #region 压缩/解压缩 数据
        /// <summary>压缩数据流</summary>
        /// <param name="inStream">输入流</param>
        /// <param name="outStream">输出流。如果不指定,则内部实例化一个内存流</param>
        /// <remarks>返回输出流,注意此时指针位于末端</remarks>
        public static Stream Compress(this Stream inStream, Stream outStream = null)
        {
            var ms = outStream ?? new MemoryStream();

            // 第三个参数为true,保持数据流打开,内部不应该干涉外部,不要关闭外部的数据流
            using (var stream = new DeflateStream(ms, CompressionLevel.Optimal, true))
            {
                inStream.CopyTo(stream);
                stream.Flush();
            }

            // 内部数据流需要把位置指向开头
            if (outStream == null) ms.Position = 0;

            return ms;
        }

        /// <summary>解压缩数据流</summary>
        /// <returns>Deflate算法,如果是ZLIB格式,则前面多两个字节,解压缩之前去掉,RocketMQ中有用到</returns>
        /// <param name="inStream">输入流</param>
        /// <param name="outStream">输出流。如果不指定,则内部实例化一个内存流</param>
        /// <remarks>返回输出流,注意此时指针位于末端</remarks>
        public static Stream Decompress(this Stream inStream, Stream outStream = null)
        {
            var ms = outStream ?? new MemoryStream();

            // 第三个参数为true,保持数据流打开,内部不应该干涉外部,不要关闭外部的数据流
            using (var stream = new DeflateStream(inStream, CompressionMode.Decompress, true))
            {
                stream.CopyTo(ms);
            }

            // 内部数据流需要把位置指向开头
            if (outStream == null) ms.Position = 0;

            return ms;
        }

        /// <summary>压缩字节数组</summary>
        /// <param name="data">字节数组</param>
        /// <returns></returns>
        public static Byte[] Compress(this Byte[] data)
        {
            var ms = new MemoryStream();
            Compress(new MemoryStream(data), ms);
            return ms.ToArray();
        }

        /// <summary>解压缩字节数组</summary>
        /// <returns>Deflate算法,如果是ZLIB格式,则前面多两个字节,解压缩之前去掉,RocketMQ中有用到</returns>
        /// <param name="data">字节数组</param>
        /// <returns></returns>
        public static Byte[] Decompress(this Byte[] data)
        {
            var ms = new MemoryStream();
            Decompress(new MemoryStream(data), ms);
            return ms.ToArray();
        }

        /// <summary>压缩数据流</summary>
        /// <param name="inStream">输入流</param>
        /// <param name="outStream">输出流。如果不指定,则内部实例化一个内存流</param>
        /// <remarks>返回输出流,注意此时指针位于末端</remarks>
        public static Stream CompressGZip(this Stream inStream, Stream outStream = null)
        {
            var ms = outStream ?? new MemoryStream();

            // 第三个参数为true,保持数据流打开,内部不应该干涉外部,不要关闭外部的数据流
            using (var stream = new GZipStream(ms, CompressionLevel.Optimal, true))
            {
                inStream.CopyTo(stream);
                stream.Flush();
            }

            // 内部数据流需要把位置指向开头
            if (outStream == null) ms.Position = 0;

            return ms;
        }

        /// <summary>解压缩数据流</summary>
        /// <param name="inStream">输入流</param>
        /// <param name="outStream">输出流。如果不指定,则内部实例化一个内存流</param>
        /// <remarks>返回输出流,注意此时指针位于末端</remarks>
        public static Stream DecompressGZip(this Stream inStream, Stream outStream = null)
        {
            var ms = outStream ?? new MemoryStream();

            // 第三个参数为true,保持数据流打开,内部不应该干涉外部,不要关闭外部的数据流
            using (var stream = new GZipStream(inStream, CompressionMode.Decompress, true))
            {
                stream.CopyTo(ms);
            }

            // 内部数据流需要把位置指向开头
            if (outStream == null) ms.Position = 0;

            return ms;
        }
        #endregion

        #region 复制数据流
        /// <summary>把一个字节数组写入到一个数据流</summary>
        /// <param name="des">目的数据流</param>
        /// <param name="src">源数据流</param>
        /// <returns></returns>
        public static Stream Write(this Stream des, params Byte[] src)
        {
            if (src != null && src.Length > 0) des.Write(src, 0, src.Length);
            return des;
        }

        /// <summary>写入字节数组,先写入压缩整数表示的长度</summary>
        /// <param name="des"></param>
        /// <param name="src"></param>
        /// <returns></returns>
        public static Stream WriteArray(this Stream des, params Byte[] src)
        {
            if (src == null || src.Length == 0)
            {
                des.WriteByte(0);
                return des;
            }

            des.WriteEncodedInt(src.Length);
            des.Write(src);

            return des;
        }

        /// <summary>读取字节数组,先读取压缩整数表示的长度</summary>
        /// <param name="des"></param>
        /// <returns></returns>
        public static Byte[] ReadArray(this Stream des)
        {
            var len = des.ReadEncodedInt();
            if (len <= 0) return Array.Empty<Byte>();

            // 避免数据错乱超长
            //if (des.CanSeek && len > des.Length - des.Position) len = (Int32)(des.Length - des.Position);
            if (des.CanSeek && len > des.Length - des.Position) throw new XException("ReadArray错误,变长数组长度为{0},但数据流可用数据只有{1}", len, des.Length - des.Position);

            if (len > 1024 * 2) throw new XException("安全需要,不允许读取超大变长数组 {0:n0}>{1:n0}", len, 1024 * 2);

            var buf = new Byte[len];
            des.Read(buf, 0, buf.Length);
            return buf;
        }

        /// <summary>写入Unix格式时间,1970年以来秒数,绝对时间,非UTC</summary>
        /// <param name="stream"></param>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static Stream WriteDateTime(this Stream stream, DateTime dt)
        {
            var seconds = dt.ToInt();
            stream.Write(seconds.GetBytes());

            return stream;
        }

        /// <summary>读取Unix格式时间,1970年以来秒数,绝对时间,非UTC</summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static DateTime ReadDateTime(this Stream stream)
        {
            var buf = new Byte[4];
            stream.Read(buf, 0, 4);
            var seconds = (Int32)buf.ToUInt32();

            return seconds.ToDateTime();
        }

        /// <summary>复制数组</summary>
        /// <param name="src">源数组</param>
        /// <param name="offset">起始位置</param>
        /// <param name="count">复制字节数</param>
        /// <returns>返回复制的总字节数</returns>
        public static Byte[] ReadBytes(this Byte[] src, Int32 offset = 0, Int32 count = -1)
        {
            if (count == 0) return Array.Empty<Byte>();

            // 即使是全部,也要复制一份,而不只是返回原数组,因为可能就是为了复制数组
            if (count < 0) count = src.Length - offset;

            var bts = new Byte[count];
            Buffer.BlockCopy(src, offset, bts, 0, bts.Length);
            return bts;
        }

        /// <summary>向字节数组写入一片数据</summary>
        /// <param name="dst">目标数组</param>
        /// <param name="dstOffset">目标偏移</param>
        /// <param name="src">源数组</param>
        /// <param name="srcOffset">源数组偏移</param>
        /// <param name="count">数量</param>
        /// <returns>返回实际写入的字节个数</returns>
        public static Int32 Write(this Byte[] dst, Int32 dstOffset, Byte[] src, Int32 srcOffset = 0, Int32 count = -1)
        {
            if (count <= 0) count = src.Length - srcOffset;
            if (dstOffset + count > dst.Length) count = dst.Length - dstOffset;

            Buffer.BlockCopy(src, srcOffset, dst, dstOffset, count);
            return count;
        }
        #endregion

        #region 数据流转换
        /// <summary>数据流转为字节数组</summary>
        /// <remarks>
        /// 针对MemoryStream进行优化。内存流的Read实现是一个个字节复制,而ToArray是调用内部内存复制方法
        /// 如果要读完数据,又不支持定位,则采用内存流搬运
        /// 如果指定长度超过数据流长度,就让其报错,因为那是调用者所期望的值
        /// </remarks>
        /// <param name="stream">数据流</param>
        /// <param name="length">长度,0表示读到结束</param>
        /// <returns></returns>
        public static Byte[] ReadBytes(this Stream stream, Int64 length = -1)
        {
            if (stream == null) return null;
            if (length == 0) return Array.Empty<Byte>();

            if (length > 0 && stream.CanSeek && stream.Length - stream.Position < length)
                throw new XException("无法从长度只有{0}的数据流里面读取{1}字节的数据", stream.Length - stream.Position, length);

            // 如果指定长度超过数据流长度,就让其报错,因为那是调用者所期望的值
            if (length > 0)
            {
                //!!! Stream.Read 的官方设计从未承诺填满缓冲区,需要用户自己多次读取
                // https://docs.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/partial-byte-reads-in-streams
                var p = 0;
                var buf = new Byte[length];
                while (true)
                {
                    var n = stream.Read(buf, p, buf.Length - p);
                    if (n == 0 || p + n == buf.Length) break;

                    p += n;
                }
                return buf;
            }

            // 支持搜索
            if (stream.CanSeek)
            {
                // 如果指定长度超过数据流长度,就让其报错,因为那是调用者所期望的值
                length = (Int32)(stream.Length - stream.Position);

                var buf = new Byte[length];
                stream.Read(buf, 0, buf.Length);
                return buf;
            }

            // 如果要读完数据,又不支持定位,则采用内存流搬运
            var ms = Pool.MemoryStream.Get();
            stream.CopyTo(ms);

            return ms.Put(true);
        }

        /// <summary>流转换为字符串</summary>
        /// <param name="stream">目标流</param>
        /// <param name="encoding">编码格式</param>
        /// <returns></returns>
        public static String ToStr(this Stream stream, Encoding encoding = null)
        {
            if (stream == null) return null;
            if (encoding == null) encoding = Encoding.UTF8;

            var buf = stream.ReadBytes();
            if (buf == null || buf.Length <= 0) return null;

            // 可能数据流前面有编码字节序列,需要先去掉
            var idx = 0;
            var preamble = encoding.GetPreamble();
            if (preamble != null && preamble.Length > 0)
            {
                if (buf.Take(preamble.Length).SequenceEqual(preamble)) idx = preamble.Length;
            }

            return encoding.GetString(buf, idx, buf.Length - idx);
        }

        /// <summary>字节数组转换为字符串</summary>
        /// <param name="buf">字节数组</param>
        /// <param name="encoding">编码格式</param>
        /// <param name="offset">字节数组中的偏移</param>
        /// <param name="count">字节数组中的查找长度</param>
        /// <returns></returns>
        public static String ToStr(this Byte[] buf, Encoding encoding = null, Int32 offset = 0, Int32 count = -1)
        {
            if (buf == null || buf.Length <= 0 || offset >= buf.Length) return null;
            if (encoding == null) encoding = Encoding.UTF8;

            var size = buf.Length - offset;
            if (count < 0 || count > size) count = size;

            // 可能数据流前面有编码字节序列,需要先去掉
            var idx = 0;
            var preamble = encoding?.GetPreamble();
            if (preamble != null && preamble.Length > 0 && buf.Length >= offset + preamble.Length)
            {
                if (buf.Skip(offset).Take(preamble.Length).SequenceEqual(preamble)) idx = preamble.Length;
            }

            return encoding.GetString(buf, offset + idx, count - idx);
        }
        #endregion

        #region 数据转整数
        /// <summary>从字节数据指定位置读取一个无符号16位整数</summary>
        /// <param name="data"></param>
        /// <param name="offset">偏移</param>
        /// <param name="isLittleEndian">是否小端字节序</param>
        /// <returns></returns>
        public static UInt16 ToUInt16(this Byte[] data, Int32 offset = 0, Boolean isLittleEndian = true)
        {
            if (isLittleEndian)
                return (UInt16)((data[offset + 1] << 8) | data[offset]);
            else
                return (UInt16)((data[offset] << 8) | data[offset + 1]);
        }

        /// <summary>从字节数据指定位置读取一个无符号32位整数</summary>
        /// <param name="data"></param>
        /// <param name="offset">偏移</param>
        /// <param name="isLittleEndian">是否小端字节序</param>
        /// <returns></returns>
        public static UInt32 ToUInt32(this Byte[] data, Int32 offset = 0, Boolean isLittleEndian = true)
        {
            if (isLittleEndian) return BitConverter.ToUInt32(data, offset);

            // BitConverter得到小端,如果不是小端字节顺序,则倒序
            if (offset > 0) data = data.ReadBytes(offset, 4);
            if (isLittleEndian)
                return (UInt32)(data[0] | data[1] << 8 | data[2] << 0x10 | data[3] << 0x18);
            else
                return (UInt32)(data[0] << 0x18 | data[1] << 0x10 | data[2] << 8 | data[3]);
        }

        /// <summary>从字节数据指定位置读取一个无符号64位整数</summary>
        /// <param name="data"></param>
        /// <param name="offset">偏移</param>
        /// <param name="isLittleEndian">是否小端字节序</param>
        /// <returns></returns>
        public static UInt64 ToUInt64(this Byte[] data, Int32 offset = 0, Boolean isLittleEndian = true)
        {
            if (isLittleEndian) return BitConverter.ToUInt64(data, offset);

            if (offset > 0) data = data.ReadBytes(offset, 8);
            if (isLittleEndian)
            {
                var num1 = data[0] | data[1] << 8 | data[2] << 0x10 | data[3] << 0x18;
                var num2 = data[4] | data[5] << 8 | data[6] << 0x10 | data[7] << 0x18;
                return (UInt32)num1 | (UInt64)num2 << 0x20;
            }
            else
            {
                var num3 = data[0] << 0x18 | data[1] << 0x10 | data[2] << 8 | data[3];
                var num4 = data[4] << 0x18 | data[5] << 0x10 | data[6] << 8 | data[7];
                return (UInt32)num4 | (UInt64)num3 << 0x20;
            }
        }

        /// <summary>向字节数组的指定位置写入一个无符号16位整数</summary>
        /// <param name="data"></param>
        /// <param name="n">数字</param>
        /// <param name="offset">偏移</param>
        /// <param name="isLittleEndian">是否小端字节序</param>
        /// <returns></returns>
        public static Byte[] Write(this Byte[] data, UInt16 n, Int32 offset = 0, Boolean isLittleEndian = true)
        {
            // STM32单片机是小端
            // Modbus协议规定大端

            if (isLittleEndian)
            {
                data[offset] = (Byte)(n & 0xFF);
                data[offset + 1] = (Byte)(n >> 8);
            }
            else
            {
                data[offset] = (Byte)(n >> 8);
                data[offset + 1] = (Byte)(n & 0xFF);
            }

            return data;
        }

        /// <summary>向字节数组的指定位置写入一个无符号32位整数</summary>
        /// <param name="data"></param>
        /// <param name="n">数字</param>
        /// <param name="offset">偏移</param>
        /// <param name="isLittleEndian">是否小端字节序</param>
        /// <returns></returns>
        public static Byte[] Write(this Byte[] data, UInt32 n, Int32 offset = 0, Boolean isLittleEndian = true)
        {
            if (isLittleEndian)
            {
                for (var i = 0; i < 4; i++)
                {
                    data[offset++] = (Byte)n;
                    n >>= 8;
                }
            }
            else
            {
                for (var i = 4 - 1; i >= 0; i--)
                {
                    data[offset + i] = (Byte)n;
                    n >>= 8;
                }
            }

            return data;
        }

        /// <summary>向字节数组的指定位置写入一个无符号64位整数</summary>
        /// <param name="data"></param>
        /// <param name="n">数字</param>
        /// <param name="offset">偏移</param>
        /// <param name="isLittleEndian">是否小端字节序</param>
        /// <returns></returns>
        public static Byte[] Write(this Byte[] data, UInt64 n, Int32 offset = 0, Boolean isLittleEndian = true)
        {
            if (isLittleEndian)
            {
                for (var i = 0; i < 8; i++)
                {
                    data[offset++] = (Byte)n;
                    n >>= 8;
                }
            }
            else
            {
                for (var i = 8 - 1; i >= 0; i--)
                {
                    data[offset + i] = (Byte)n;
                    n >>= 8;
                }
            }

            return data;
        }

        /// <summary>整数转为字节数组,注意大小端字节序</summary>
        /// <param name="value"></param>
        /// <param name="isLittleEndian"></param>
        /// <returns></returns>
        public static Byte[] GetBytes(this UInt16 value, Boolean isLittleEndian = true)
        {
            if (isLittleEndian) return BitConverter.GetBytes(value);

            var buf = new Byte[2];
            return buf.Write(value, 0, isLittleEndian);
        }

        /// <summary>整数转为字节数组,注意大小端字节序</summary>
        /// <param name="value"></param>
        /// <param name="isLittleEndian"></param>
        /// <returns></returns>
        public static Byte[] GetBytes(this Int16 value, Boolean isLittleEndian = true)
        {
            if (isLittleEndian) return BitConverter.GetBytes(value);

            var buf = new Byte[2];
            return buf.Write((UInt16)value, 0, isLittleEndian);
        }

        /// <summary>整数转为字节数组,注意大小端字节序</summary>
        /// <param name="value"></param>
        /// <param name="isLittleEndian"></param>
        /// <returns></returns>
        public static Byte[] GetBytes(this UInt32 value, Boolean isLittleEndian = true)
        {
            if (isLittleEndian) return BitConverter.GetBytes(value);

            var buf = new Byte[4];
            return buf.Write(value, 0, isLittleEndian);
        }

        /// <summary>整数转为字节数组,注意大小端字节序</summary>
        /// <param name="value"></param>
        /// <param name="isLittleEndian"></param>
        /// <returns></returns>
        public static Byte[] GetBytes(this Int32 value, Boolean isLittleEndian = true)
        {
            if (isLittleEndian) return BitConverter.GetBytes(value);

            var buf = new Byte[4];
            return buf.Write((UInt32)value, 0, isLittleEndian);
        }

        /// <summary>整数转为字节数组,注意大小端字节序</summary>
        /// <param name="value"></param>
        /// <param name="isLittleEndian"></param>
        /// <returns></returns>
        public static Byte[] GetBytes(this UInt64 value, Boolean isLittleEndian = true)
        {
            if (isLittleEndian) return BitConverter.GetBytes(value);

            var buf = new Byte[8];
            return buf.Write(value, 0, isLittleEndian);
        }

        /// <summary>整数转为字节数组,注意大小端字节序</summary>
        /// <param name="value"></param>
        /// <param name="isLittleEndian"></param>
        /// <returns></returns>
        public static Byte[] GetBytes(this Int64 value, Boolean isLittleEndian = true)
        {
            if (isLittleEndian) return BitConverter.GetBytes(value);

            var buf = new Byte[8];
            return buf.Write((UInt64)value, 0, isLittleEndian);
        }

        /// <summary>字节翻转。支持双字节和四字节多批次翻转,主要用于大小端转换</summary>
        /// <param name="data"></param>
        /// <param name="swap16"></param>
        /// <param name="swap32"></param>
        /// <returns></returns>
        public static Byte[] Swap(this Byte[] data, Boolean swap16, Boolean swap32)
        {
            var buf = new Byte[data.Length];
            Buffer.BlockCopy(data, 0, buf, 0, data.Length);
            if (swap16)
            {
                for (var i = 0; i < buf.Length - 1; i += 2)
                {
                    (buf[i + 1], buf[i]) = (buf[i], buf[i + 1]);
                }
            }

            if (swap32)
            {
                for (var i = 0; i < buf.Length - 3; i += 4)
                {
                    (buf[i + 2], buf[i + 3], buf[i], buf[i + 1]) = (buf[i], buf[i + 1], buf[i + 2], buf[i + 3]);
                }
            }

            return buf;
        }
        #endregion

        #region 7位压缩编码整数
        /// <summary>以压缩格式读取32位整数</summary>
        /// <param name="stream">数据流</param>
        /// <returns></returns>
        public static Int32 ReadEncodedInt(this Stream stream)
        {
            Byte b;
            UInt32 rs = 0;
            Byte n = 0;
            while (true)
            {
                var bt = stream.ReadByte();
                if (bt < 0) throw new Exception($"数据流超出范围!已读取整数{rs:n0}");
                b = (Byte)bt;

                // 必须转为Int32,否则可能溢出
                rs |= (UInt32)((b & 0x7f) << n);
                if ((b & 0x80) == 0) break;

                n += 7;
                if (n >= 32) throw new FormatException("数字值过大,无法使用压缩格式读取!");
            }
            return (Int32)rs;
        }

        /// <summary>以压缩格式读取32位整数</summary>
        /// <param name="stream">数据流</param>
        /// <returns></returns>
        public static UInt64 ReadEncodedInt64(this Stream stream)
        {
            Byte b;
            UInt64 rs = 0;
            Byte n = 0;
            while (true)
            {
                var bt = stream.ReadByte();
                if (bt < 0) throw new Exception("数据流超出范围!");
                b = (Byte)bt;

                // 必须转为Int32,否则可能溢出
                rs |= (UInt64)(b & 0x7f) << n;
                if ((b & 0x80) == 0) break;

                n += 7;
                if (n >= 64) throw new FormatException("数字值过大,无法使用压缩格式读取!");
            }
            return rs;
        }

        /// <summary>尝试读取压缩编码整数</summary>
        /// <param name="stream"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        internal static Boolean TryReadEncodedInt(this Stream stream, out UInt32 value)
        {
            Byte b;
            value = 0;
            Byte n = 0;
            while (true)
            {
                var bt = stream.ReadByte();
                if (bt < 0) return false;
                b = (Byte)bt;

                // 必须转为Int32,否则可能溢出
                value += (UInt32)((b & 0x7f) << n);
                if ((b & 0x80) == 0) break;

                n += 7;
                if (n >= 32) throw new FormatException("数字值过大,无法使用压缩格式读取!");
            }
            return true;
        }

        [ThreadStatic]
        private static Byte[] _encodes;
        /// <summary>
        /// 以7位压缩格式写入32位整数,小于7位用1个字节,小于14位用2个字节。
        /// 由每次写入的一个字节的第一位标记后面的字节是否还是当前数据,所以每个字节实际可利用存储空间只有后7位。
        /// </summary>
        /// <param name="stream">数据流</param>
        /// <param name="value">数值</param>
        /// <returns>实际写入字节数</returns>
        public static Stream WriteEncodedInt(this Stream stream, Int64 value)
        {
            if (_encodes == null) _encodes = new Byte[16];

            var count = 0;
            var num = (UInt64)value;
            while (num >= 0x80)
            {
                _encodes[count++] = (Byte)(num | 0x80);
                num >>= 7;
            }
            _encodes[count++] = (Byte)num;

            stream.Write(_encodes, 0, count);

            return stream;
        }

        /// <summary>获取压缩编码整数</summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static Byte[] GetEncodedInt(Int64 value)
        {
            if (_encodes == null) _encodes = new Byte[16];

            var count = 0;
            var num = (UInt64)value;
            while (num >= 0x80)
            {
                _encodes[count++] = (Byte)(num | 0x80);
                num >>= 7;
            }
            _encodes[count++] = (Byte)num;

            return _encodes.ReadBytes(0, count);
        }
        #endregion

        #region 十六进制编码
        /// <summary>把字节数组编码为十六进制字符串</summary>
        /// <param name="data">字节数组</param>
        /// <param name="offset">偏移</param>
        /// <param name="count">数量。超过实际数量时,使用实际数量</param>
        /// <returns></returns>
        public static String ToHex(this Byte[] data, Int32 offset = 0, Int32 count = -1)
        {
            if (data == null || data.Length <= 0) return "";

            if (count < 0)
                count = data.Length - offset;
            else if (offset + count > data.Length)
                count = data.Length - offset;
            if (count == 0) return "";

            //return BitConverter.ToString(data).Replace("-", null);
            // 上面的方法要替换-,效率太低
            var cs = new Char[count * 2];
            // 两个索引一起用,避免乘除带来的性能损耗
            for (Int32 i = 0, j = 0; i < count; i++, j += 2)
            {
                var b = data[offset + i];
                cs[j] = GetHexValue(b / 0x10);
                cs[j + 1] = GetHexValue(b % 0x10);
            }
            return new String(cs);
        }

        /// <summary>把字节数组编码为十六进制字符串,带有分隔符和分组功能</summary>
        /// <param name="data">字节数组</param>
        /// <param name="separate">分隔符</param>
        /// <param name="groupSize">分组大小,为0时对每个字节应用分隔符,否则对每个分组使用</param>
        /// <param name="maxLength">最大显示多少个字节。默认-1显示全部</param>
        /// <returns></returns>
        public static String ToHex(this Byte[] data, String separate, Int32 groupSize = 0, Int32 maxLength = -1)
        {
            if (data == null || data.Length <= 0) return "";

            if (groupSize < 0) groupSize = 0;

            var count = data.Length;
            if (maxLength > 0 && maxLength < count) count = maxLength;

            if (groupSize == 0 && count == data.Length)
            {
                // 没有分隔符
                if (String.IsNullOrEmpty(separate)) return data.ToHex();

                // 特殊处理
                if (separate == "-") return BitConverter.ToString(data, 0, count);
            }

            var len = count * 2;
            if (!String.IsNullOrEmpty(separate)) len += (count - 1) * separate.Length;
            if (groupSize > 0)
            {
                // 计算分组个数
                var g = (count - 1) / groupSize;
                len += g * 2;
                // 扣除间隔
                if (!String.IsNullOrEmpty(separate)) _ = g * separate.Length;
            }
            var sb = Pool.StringBuilder.Get();
            for (var i = 0; i < count; i++)
            {
                if (sb.Length > 0)
                {
                    if (groupSize > 0 && i % groupSize == 0)
                        sb.AppendLine();
                    else
                        sb.Append(separate);
                }

                var b = data[i];
                sb.Append(GetHexValue(b / 0x10));
                sb.Append(GetHexValue(b % 0x10));
            }

            return sb.Put(true);
        }

        private static Char GetHexValue(Int32 i)
        {
            if (i < 10)
                return (Char)(i + 0x30);
            else
                return (Char)(i - 10 + 0x41);
        }

        /// <summary>解密</summary>
        /// <param name="data">Hex编码的字符串</param>
        /// <param name="startIndex">起始位置</param>
        /// <param name="length">长度</param>
        /// <returns></returns>
        public static Byte[] ToHex(this String data, Int32 startIndex = 0, Int32 length = -1)
        {
            if (String.IsNullOrEmpty(data)) return Array.Empty<Byte>();

            // 过滤特殊字符
            data = data.Trim()
                .Replace("-", null)
                .Replace("0x", null)
                .Replace("0X", null)
                .Replace(" ", null)
                .Replace("\r", null)
                .Replace("\n", null)
                .Replace(",", null);

            if (length <= 0) length = data.Length - startIndex;

            var bts = new Byte[length / 2];
            for (var i = 0; i < bts.Length; i++)
            {
                bts[i] = Byte.Parse(data.Substring(startIndex + 2 * i, 2), NumberStyles.HexNumber);
            }
            return bts;
        }
        #endregion

        #region BASE64编码
        /// <summary>字节数组转为Base64编码</summary>
        /// <param name="data"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <param name="lineBreak">是否换行显示</param>
        /// <returns></returns>
        public static String ToBase64(this Byte[] data, Int32 offset = 0, Int32 count = -1, Boolean lineBreak = false)
        {
            if (data == null || data.Length <= 0) return "";

            if (count <= 0)
                count = data.Length - offset;
            else if (offset + count > data.Length)
                count = data.Length - offset;

            return Convert.ToBase64String(data, offset, count, lineBreak ? Base64FormattingOptions.InsertLineBreaks : Base64FormattingOptions.None);
        }

        /// <summary>字节数组转为Url改进型Base64编码</summary>
        /// <param name="data"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public static String ToUrlBase64(this Byte[] data, Int32 offset = 0, Int32 count = -1)
        {
            var str = ToBase64(data, offset, count, false);
            str = str.TrimEnd('=');
            str = str.Replace('+', '-').Replace('/', '_');
            return str;
        }

        /// <summary>Base64字符串转为字节数组</summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static Byte[] ToBase64(this String data)
        {
            if (data.IsNullOrEmpty()) return Array.Empty<Byte>();

            if (data[^1] != '=')
            {
                // 如果不是4的整数倍,后面补上等号
                var n = data.Length % 4;
                if (n > 0) data += new String('=', 4 - n);
            }

            // 针对Url特殊处理
            data = data.Replace('-', '+').Replace('_', '/');

            return Convert.FromBase64String(data);
        }
        #endregion

        #region 搜索
        /// <summary>Boyer Moore 字符串搜索算法,比KMP更快,常用于IDE工具的查找</summary>
        /// <param name="source"></param>
        /// <param name="pattern"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public static Int32 IndexOf(this Byte[] source, Byte[] pattern, Int32 offset = 0, Int32 count = -1)
        {
            if (source == null) throw new ArgumentNullException(nameof(source));
            if (pattern == null) throw new ArgumentNullException(nameof(pattern));

            var total = source.Length;
            var length = pattern.Length;

            if (count > 0 && total > offset + count) total = offset + count;
            if (total == 0 || length == 0 || length > total) return -1;

            // 初始化坏字符,即不匹配字符
            var bads = new Int32[256];
            for (var i = 0; i < 256; i++)
            {
                bads[i] = length;
            }

            // 搜索词每个字母在坏字符中的最小位置
            var last = length - 1;
            for (var i = 0; i < last; i++)
            {
                bads[pattern[i]] = last - i;
            }

            var index = offset;
            while (index <= total - length)
            {
                // 尾部开始比较
                for (var i = last; source[index + i] == pattern[i]; i--)
                {
                    if (i == 0) return index;
                }

                // 坏字符规则:后移位数 = 坏字符的位置 - 搜索词中的上一次出现位置
                index += bads[source[index + last]];
            }

            return -1;
        }
        #endregion
    }
}