解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
大石头 authored at 2018-05-15 21:21:05
50.57 KiB
X
using System.Buffers;
using System.Globalization;
using System.IO.Compression;
using System.Text;
using NewLife.Collections;

namespace NewLife;

/// <summary>IO工具类</summary>
/// <remarks>
/// 文档 https://newlifex.com/core/io_helper
/// </remarks>
public static class IOHelper
{
    #region 属性
    /// <summary>最大安全数组大小。超过该大小时,读取数据操作将强制失败,默认1024*1024</summary>
    /// <remarks>
    /// 这是一个保护性设置,避免解码错误数据时读取了超大数组导致应用崩溃。
    /// 需要解码较大二进制数据时,可以适当放宽该阈值。
    /// </remarks>
    public static Int32 MaxSafeArraySize { get; set; } = 1024 * 1024;
    #endregion

    #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;
    }

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

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

        // 避免数据错乱超长
        //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 error, variable length array length is {0}, but the available data for the data stream is only {1}", len, des.Length - des.Position);

        if (len > MaxSafeArraySize) throw new XException("Security required, reading large variable length arrays is not allowed {0:n0}>{1:n0}", len, MaxSafeArraySize);

        return des.ReadExactly(len);
    }

    /// <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)
    {
        _encodes ??= new Byte[16];
        stream.ReadExactly(_encodes, 0, 4);
        var seconds = (Int32)_encodes.ToUInt32();

        return seconds.ToDateTime();
    }

    /// <summary>复制数组</summary>
    /// <param name="src">源数组</param>
    /// <param name="offset">起始位置。一般从0开始</param>
    /// <param name="count">复制字节数。用-1表示截取剩余所有数据</param>
    /// <returns>返回复制的总字节数</returns>
    public static Byte[] ReadBytes(this Byte[] src, Int32 offset, Int32 count)
    {
        if (count == 0) return [];

        // 即使是全部,也要复制一份,而不只是返回原数组,因为可能就是为了复制数组
        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;
    }

    /// <summary>从一个数据流复制特定长度数据到另一个数据流</summary>
    /// <param name="source">源数据流</param>
    /// <param name="destination">目标数据流</param>
    /// <param name="length">要复制的数据长度</param>
    /// <param name="bufferSize">缓冲区大小</param>
    public static void CopyTo(this Stream source, Stream destination, Int64 length, Int32 bufferSize)
    {
        if (length <= 0) return;

        using var buffer = Pool.Rent(bufferSize);

        while (length > 0)
        {
            var bytesRead = source.Read(buffer, 0, (Int32)Math.Min(buffer.Length, length));
            if (bytesRead == 0) break;

            destination.Write(buffer, 0, bytesRead);
            length -= bytesRead;
        }
    }
    #endregion

    #region 数据流转换
    /// <summary>从流中完全读取数据,直到指定大小或者到达流结束</summary>
    /// <remarks>
    /// 主要为了对抗net6开始对Stream.Read的微调。
    /// https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/partial-byte-reads-in-streams
    /// 功能:尝试从 <paramref name="stream"/> 连续读取 <paramref name="count"/> 个字节写入 <paramref name="buffer"/>(起始于 <paramref name="offset"/>)。若底层流在填满前结束,则抛出 <see cref="EndOfStreamException"/>。
    /// 设计背景:.NET 6 起基础库强调 <c>Stream.Read</c> 可能返回“部分数据”(partial read),调用方需自行循环;旧代码里大量“假设一次读满”逻辑会潜在失效。该方法提供一个语义清晰的“精准填充”入口,避免重复样板循环。
    /// 内部实现:调用 <see cref="ReadAtLeast(Stream,Byte[],Int32,Int32,Int32,Boolean)"/>,将 <c>minimumBytes = count</c> 且 <c>throwOnEndOfStream = true</c>,保证要么读取足量要么抛异常。
    /// 性能:仅在调用方提供的缓冲区内循环读取,无额外数组租借 / 复制;适合高频固定长度结构(协议头、长度前缀、魔数校验等)。
    /// 使用建议:
    /// <list type="bullet">
    /// <item><description>需严格保证字节数 → 使用本方法。</description></item>
    /// <item><description>允许不足(探测 EOF / 非阻塞流)→ 使用 <see cref="ReadAtLeast(Stream,Byte[],Int32,Int32,Int32,Boolean)"/> 并设置 <c>throwOnEndOfStream = false</c>。</description></item>
    /// <item><description>需分配新数组 → 使用 <see cref="ReadExactly(Stream, Int64)"/> 重载。</description></item>
    /// </list>
    /// 边界:当 <paramref name="count"/> == 0 时立即返回 0;参数越界/空引用异常行为与 CLR / <c>Stream.Read</c> 保持一致。
    /// 兼容:在所有支持的目标框架上行为一致,避免不同运行时对 <c>Read</c> 语义的潜在差异。
    /// </remarks>
    /// <param name="stream">源数据流,必须可读。</param>
    /// <param name="buffer">目标缓冲区,必须有足够空间容纳 <paramref name="offset"/> + <paramref name="count"/>。</param>
    /// <param name="offset">写入起始偏移。</param>
    /// <param name="count">期望读取且必须精确填充的字节数 (>=0)。</param>
    /// <returns>成功时返回读取的总字节数(恒等于 <paramref name="count"/>;当 <paramref name="count"/> == 0 返回 0)。</returns>
    public static Int32 ReadExactly(this Stream stream, Byte[] buffer, Int32 offset, Int32 count) => ReadAtLeast(stream, buffer, offset, count, count, true);

    /// <summary>读取指定数量字节数据(精确填充)。</summary>
    /// <param name="stream">源数据流,必须可读。</param>
    /// <param name="count">期望读取的精确字节数,需 >= 0。</param>
    /// <returns>长度恰为 <paramref name="count"/> 的新字节数组。</returns>
    public static Byte[] ReadExactly(this Stream stream, Int64 count)
    {
        var buf = new Byte[count];
        ReadAtLeast(stream, buf, 0, buf.Length, (Int32)count, true);
        return buf;
    }

    /// <summary>从流中读取数据到缓冲区,至少读取指定字节数</summary>
    /// <remarks>
    /// 功能:在不超过 <paramref name="count"/> 的前提下,尽量读取不少于 <paramref name="minimumBytes"/> 个字节,并返回本次实际读取量。若底层流过早结束且未满足最小需求,根据 <paramref name="throwOnEndOfStream"/> 决定是否抛出 <see cref="EndOfStreamException"/>。
    /// <para>性能背景:.NET (7/8+) 运行库只提供基于 <c>Span&lt;Byte&gt;</c> 的 <c>Stream.ReadAtLeast(Span&lt;Byte&gt; buffer, int minimumBytes, bool throwOnEndOfStream)</c> 重载。对于仅覆写传统 <c>Read(byte[], int, int)</c> 的自定义流,基类 <c>Read(Span&lt;Byte&gt;)</c> 默认实现流程:</para>
    /// <list type="number">
    /// <item><description>向 <see cref="ArrayPool{T}"/> 租借临时字节数组。</description></item>
    /// <item><description>调用旧版 <c>Read(byte[], int, int)</c> 填充。</description></item>
    /// <item><description>将数据从临时数组拷贝回调用方 <c>Span</c>。</description></item>
    /// <item><description>归还租借数组。</description></item>
    /// </list>
    /// 该过程引入额外租借与复制,在高频/小块读取场景下有额外开销与 GC 压力。本实现直接使用调用方传入的 <see cref="Byte"/> 数组(携带 offset/count),避免:
    /// <list type="bullet">
    /// <item><description>额外数组租借与释放</description></item>
    /// <item><description>二次内存复制</description></item>
    /// <item><description>小块循环读取的池命中成本</description></item>
    /// </list>
    /// 语义:与 .NET ReadAtLeast 语义对齐(满足最小值,否则可选抛异常),但只提供数组版以复用现有缓冲策略(对象池 / 环形缓冲 / 协议解析缓存)。
    /// 参数与返回:
    /// <list type="bullet">
    /// <item><description><paramref name="minimumBytes"/> 必须处于 [0, count]</description></item>
    /// <item><description>返回值范围 [0, count]</description></item>
    /// <item><description><paramref name="minimumBytes"/> == 0 或 <paramref name="count"/> == 0 时快速返回 0</description></item>
    /// <item><description>若 <paramref name="throwOnEndOfStream"/> = true 且未达最小值,抛出 <see cref="EndOfStreamException"/></description></item>
    /// </list>
    /// 使用建议:
    /// <list type="bullet">
    /// <item><description>需要“尽量一次填满协议头 / 固定长度结构”时使用</description></item>
    /// <item><description>必须严格保证长度:设置 <paramref name="throwOnEndOfStream"/> = true</description></item>
    /// <item><description>允许提前结束(如文件截断/半关闭连接):设置为 false 并检查返回值</description></item>
    /// </list>
    /// 边界与校验:显式检查偏移、长度、最小值;无内部线程安全;调用方需保证并发模型正确。
    /// </remarks>
    /// <param name="stream">源数据流</param>
    /// <param name="buffer">目标缓冲区</param>
    /// <param name="offset">写入起始偏移</param>
    /// <param name="count">最多尝试读取的字节数(上限)</param>
    /// <param name="minimumBytes">期望至少读取的字节数(下限)。若超过 <paramref name="count"/> 将被截断</param>
    /// <param name="throwOnEndOfStream">到达流末尾且未满足最小字节数时是否抛异常</param>
    /// <returns>实际读取的字节数,范围 [0, count]</returns>
    /// <exception cref="EndOfStreamException">实际可用数据不足 <paramref name="count"/>。</exception>
    /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> 为负值(由 CLR 在数组分配时触发)。</exception>
    public static Int32 ReadAtLeast(this Stream stream, Byte[] buffer, Int32 offset, Int32 count, Int32 minimumBytes, Boolean throwOnEndOfStream = true)
    {
        if (minimumBytes < 0) throw new ArgumentOutOfRangeException(nameof(minimumBytes));
        if (minimumBytes > count) throw new ArgumentOutOfRangeException(nameof(minimumBytes));

        // 快速返回:无需最小读取或没有读取空间。
        if (minimumBytes == 0 || count == 0) return 0;

        var totalRead = 0;
        while (totalRead < minimumBytes)
        {
            var bytesRead = stream.Read(buffer, offset + totalRead, count - totalRead);
            if (bytesRead == 0)
            {
                // 到达 EOF,若未满足 minimumBytes 并要求抛异常,则抛出(与 .NET ReadAtLeast 一致)。
                if (throwOnEndOfStream) throw new EndOfStreamException($"Unable to read the required minimum number of bytes. Expected {minimumBytes}, actual {totalRead}.");
                break;
            }
            totalRead += bytesRead;
        }

        return totalRead;
    }

    /// <summary>异步从流中完全读取数据,精确填充指定字节数</summary>
    /// <param name="stream">源数据流,必须可读。</param>
    /// <param name="buffer">目标缓冲区,必须有足够空间容纳 <paramref name="offset"/> + <paramref name="count"/>。</param>
    /// <param name="offset">写入起始偏移。</param>
    /// <param name="count">期望读取且必须精确填充的字节数 (>=0)。</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>成功时返回读取的总字节数(恒等于 <paramref name="count"/>;当 <paramref name="count"/> == 0 返回 0)。</returns>
    public static Task<Int32> ReadExactlyAsync(this Stream stream, Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken = default) => ReadAtLeastAsync(stream, buffer, offset, count, count, true, cancellationToken);

    /// <summary>异步读取指定数量字节数据(精确填充)。</summary>
    /// <param name="stream">源数据流,必须可读。</param>
    /// <param name="count">期望读取的精确字节数,需 >= 0。</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>长度恰为 <paramref name="count"/> 的新字节数组。</returns>
    public static async Task<Byte[]> ReadExactlyAsync(this Stream stream, Int64 count, CancellationToken cancellationToken = default)
    {
        var buf = new Byte[count];
        await ReadAtLeastAsync(stream, buf, 0, buf.Length, (Int32)count, true, cancellationToken).ConfigureAwait(false);
        return buf;
    }

    /// <summary>异步从流中读取数据到缓冲区,至少读取指定字节数</summary>
    /// <remarks>
    /// 与同步版 <see cref="ReadAtLeast"/> 语义一致,内部使用 <see cref="Stream.ReadAsync(Byte[], Int32, Int32, CancellationToken)"/> 实现异步读取,适用于网络流等异步场景。
    /// </remarks>
    /// <param name="stream">源数据流</param>
    /// <param name="buffer">目标缓冲区</param>
    /// <param name="offset">写入起始偏移</param>
    /// <param name="count">最多尝试读取的字节数(上限)</param>
    /// <param name="minimumBytes">期望至少读取的字节数(下限)</param>
    /// <param name="throwOnEndOfStream">到达流末尾且未满足最小字节数时是否抛异常</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>实际读取的字节数,范围 [0, count]</returns>
    /// <exception cref="EndOfStreamException">实际可用数据不足 <paramref name="minimumBytes"/>。</exception>
    /// <exception cref="ArgumentOutOfRangeException"><paramref name="minimumBytes"/> 为负值或超过 <paramref name="count"/>。</exception>
    public static async Task<Int32> ReadAtLeastAsync(this Stream stream, Byte[] buffer, Int32 offset, Int32 count, Int32 minimumBytes, Boolean throwOnEndOfStream = true, CancellationToken cancellationToken = default)
    {
        if (minimumBytes < 0) throw new ArgumentOutOfRangeException(nameof(minimumBytes));
        if (minimumBytes > count) throw new ArgumentOutOfRangeException(nameof(minimumBytes));

        // 快速返回:无需最小读取或没有读取空间。
        if (minimumBytes == 0 || count == 0) return 0;

        var totalRead = 0;
        while (totalRead < minimumBytes)
        {
            var bytesRead = await stream.ReadAsync(buffer, offset + totalRead, count - totalRead, cancellationToken).ConfigureAwait(false);
            if (bytesRead == 0)
            {
                // 到达 EOF,若未满足 minimumBytes 并要求抛异常,则抛出(与 .NET ReadAtLeast 一致)。
                if (throwOnEndOfStream) throw new EndOfStreamException($"Unable to read the required minimum number of bytes. Expected {minimumBytes}, actual {totalRead}.");
                break;
            }
            totalRead += bytesRead;
        }

        return totalRead;
    }

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

        if (length > 0 && stream.CanSeek && stream.Length - stream.Position < length)
            throw new XException("Unable to read {1} bytes of data from a data stream with a length of only {0}", 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
            return stream.ReadExactly(length);
        }

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

            return stream.ReadExactly(length);
        }

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

        return ms.Return(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 String.Empty;
        encoding ??= Encoding.UTF8;

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

        // 可能数据流前面有编码字节序列,需要先去掉
        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 String.Empty;
        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>从字节数据指定位置读取一个单精度浮点数</summary>
    /// <param name="data"></param>
    /// <param name="offset">偏移</param>
    /// <param name="isLittleEndian">是否小端字节序</param>
    /// <returns></returns>
    public static Single ToSingle(this Byte[] data, Int32 offset = 0, Boolean isLittleEndian = true)
    {
        // BitConverter得到小端,如果不是小端字节顺序,则倒序
        if (offset > 0) data = data.ReadBytes(offset, 4);
        if (!isLittleEndian)
        {
            (data[3], data[2], data[1], data[0]) = (data[0], data[1], data[2], data[3]);
        }

        // 切片后从 0 开始读取,避免 offset 越界
        return BitConverter.ToSingle(data, 0);
    }

    /// <summary>从字节数据指定位置读取一个双精度浮点数</summary>
    /// <param name="data"></param>
    /// <param name="offset">偏移</param>
    /// <param name="isLittleEndian">是否小端字节序</param>
    /// <returns></returns>
    public static Double ToDouble(this Byte[] data, Int32 offset = 0, Boolean isLittleEndian = true)
    {
        if (offset > 0) data = data.ReadBytes(offset, 8);
        if (!isLittleEndian)
        {
            (data[7], data[6], data[5], data[4], data[3], data[2], data[1], data[0]) = (data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
        }

        // 切片后从 0 开始读取,避免 offset 越界
        return BitConverter.ToDouble(data, 0);
    }

    /// <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="data"></param>
    /// <param name="n">数字</param>
    /// <param name="offset">偏移</param>
    /// <param name="isLittleEndian">是否小端字节序</param>
    /// <returns></returns>
    public static Byte[] Write(this Byte[] data, Single n, Int32 offset = 0, Boolean isLittleEndian = true)
    {
        var buf = BitConverter.GetBytes(n);
        if (isLittleEndian)
        {
            for (var i = 0; i < 4; i++)
            {
                data[offset++] = buf[i];
            }
        }
        else
        {
            for (var i = 4 - 1; i >= 0; i--)
            {
                data[offset + i] = buf[i];
            }
        }

        return data;
    }

    /// <summary>向字节数组的指定位置写入一个双精度浮点数</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, Double n, Int32 offset = 0, Boolean isLittleEndian = true)
    {
        var buf = BitConverter.GetBytes(n);
        if (isLittleEndian)
        {
            for (var i = 0; i < 8; i++)
            {
                data[offset++] = buf[i];
            }
        }
        else
        {
            for (var i = 8 - 1; i >= 0; i--)
            {
                data[offset + i] = buf[i];
            }
        }

        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="value"></param>
    /// <param name="isLittleEndian"></param>
    /// <returns></returns>
    public static Byte[] GetBytes(this Single 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 Double 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="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($"The data stream is out of range! The integer read is {rs: n0}");
            b = (Byte)bt;

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

            n += 7;
            if (n >= 32) throw new FormatException("The number value is too large to read in compressed format!");
        }
        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("The data stream is out of range!");
            b = (Byte)bt;

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

            n += 7;
            if (n >= 64) throw new FormatException("The number value is too large to read in compressed format!");
        }
        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("The number value is too large to read in compressed format!");
        }
        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)
    {
        _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)
    {
        _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 >> 4);
            cs[j + 1] = GetHexValue(b & 0x0F);
        }
        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 (!separate.IsNullOrEmpty()) len += (count - 1) * separate.Length;
        if (groupSize > 0)
        {
            // 计算分组个数
            var g = (count - 1) / groupSize;
            len += g * 2;
            // 扣除间隔
            if (!separate.IsNullOrEmpty()) _ = 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.Append(separate);
                //else
                //    sb.AppendLine();
            }

            var b = data[i];
            sb.Append(GetHexValue(b >> 4));
            sb.Append(GetHexValue(b & 0x0F));
        }

        return sb.Return(true) ?? String.Empty;
    }

    /// <summary>1个字节转为2个16进制字符</summary>
    /// <param name="b"></param>
    /// <returns></returns>
    public static String ToHex(this Byte b)
    {
        //Convert.ToString(b, 16);
        var cs = new Char[2];
        var ch = b >> 4;
        var cl = b & 0x0F;
        cs[0] = (Char)(ch >= 0x0A ? ('A' + ch - 0x0A) : ('0' + ch));
        cs[1] = (Char)(cl >= 0x0A ? ('A' + cl - 0x0A) : ('0' + cl));

        return new String(cs);
    }

    private static Char GetHexValue(Int32 i) => i < 10 ? (Char)(i + '0') : (Char)(i - 10 + 'A');

    /// <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 (data.IsNullOrEmpty()) return [];

        // 过滤特殊字符
        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 String.Empty;

        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.IsNullOrWhiteSpace()) return [];

        data = data.Trim();
        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
}