支持.net core 3.0
大石头 authored at 2019-09-24 14:01:15
51.21 KiB
X
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Text;
using NewLife;
using NewLife.Collections;
using NewLife.Reflection;

namespace System
{
    /// <summary>IO工具类</summary>
    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,保持数据流打开,内部不应该干涉外部,不要关闭外部的数据流
#if NET4
            using (var stream = new DeflateStream(ms, CompressionMode.Compress, true))
            {
                inStream.CopyTo(stream);
                stream.Flush();
            }
#else
            using (var stream = new DeflateStream(ms, CompressionLevel.Optimal, true))
            {
                inStream.CopyTo(stream);
                stream.Flush();
            }
#endif

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

            return ms;
        }

        /// <summary>解压缩数据流</summary>
        /// <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>
        /// <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,保持数据流打开,内部不应该干涉外部,不要关闭外部的数据流
#if NET4
            using (var stream = new GZipStream(ms, CompressionMode.Compress, true))
            {
                inStream.CopyTo(stream);
                stream.Flush();
            }
#else
            using (var stream = new GZipStream(ms, CompressionLevel.Optimal, true))
            {
                inStream.CopyTo(stream);
                stream.Flush();
            }
#endif

            // 内部数据流需要把位置指向开头
            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="src">源数据流</param>
        /// <param name="des">目的数据流</param>
        /// <param name="bufferSize">缓冲区大小,也就是每次复制的大小</param>
        /// <param name="max">最大复制字节数</param>
        /// <returns>返回复制的总字节数</returns>
        public static Int32 CopyTo(this Stream src, Stream des, Int32 bufferSize = 0, Int32 max = 0)
        {
            // 优化处理内存流,直接拿源内存流缓冲区往目标数据流里写
            if (src is MemoryStream)
            {
                var ms = src as MemoryStream;
                // 如果指针位于开头,并且要读完整个缓冲区,则直接使用WriteTo
                var count = (Int32)(ms.Length - ms.Position);
                if (ms.Position == 0 && (max <= 0 || count <= max))
                {
                    ms.WriteTo(des);
                    ms.Position = ms.Length;
                    return count;
                }

                // 反射读取内存流中数据的原始位置,然后直接把数据拿出来用
                if (ms.TryGetValue("_origin", out var obj))
                {
                    var _origin = (Int32)obj;
                    // 其实地址不为0时,一般不能直接访问缓冲区,因为可能被限制访问
                    var buf = ms.GetValue("_buffer") as Byte[];

                    if (max > 0 && count > max) count = max;
                    des.Write(buf, _origin, count);
                    ms.Position += count;
                    return count;
                }

                // 一次读完
                bufferSize = count;
            }
            // 优化处理目标内存流,直接拿目标内存流缓冲区去源数据流里面读取数据
            if (des is MemoryStream)
            {
                var ms = des as MemoryStream;
                // 
                if (ms.TryGetValue("_origin", out var obj))
                {
                    var _origin = (Int32)obj;
                    // 缓冲区还剩下多少空间
                    var count = (Int32)(ms.Length - ms.Position);
                    // 有可能是全新的内存流
                    if (count == 0)
                    {
                        if (max > 0)
                            count = max;
                        else if (src.CanSeek)
                        {
                            try { count = (Int32)(src.Length - src.Position); }
                            catch { count = 256; }
                        }
                        else
                            count = 256;
                        ms.Capacity += count;
                    }
                    else if (max > 0 && count > max)
                        count = max;

                    // 其实地址不为0时,一般不能直接访问缓冲区,因为可能被限制访问
                    var buf = ms.GetValue("_buffer") as Byte[];

                    // 先把长度设为较大值,为后面设定长度做准备,因为直接使用SetLength会清空缓冲区
                    var len = ms.Length;
                    ms.SetLength(ms.Position + count);
                    // 直接从源数据流往这个缓冲区填充数据
                    var rs = src.Read(buf, _origin, count);
                    if (rs > 0)
                    {
                        // 直接使用SetLength会清空缓冲区
                        ms.SetLength(ms.Position + rs);
                        ms.Position += rs;
                    }
                    else
                        ms.SetLength(len);
                    // 如果得到的数据没有达到预期,说明读完了
                    if (rs < count) return rs;
                    // 如果相等,则只有特殊情况才是达到预期
                    if (rs == count)
                    {
                        if (count != max && count != 256) return rs;
                    }

                    // 如果还有数据,说明是目标数据流缓冲区不够大
#if DEBUG
                    NewLife.Log.XTrace.WriteLine("目标数据流缓冲区不够大,设计上建议加大(>{0})以提升性能!", count);
#endif
                }
            }

            if (bufferSize <= 0) bufferSize = 1024;
            var buffer = new Byte[bufferSize];

            var total = 0;
            while (true)
            {
                var count = bufferSize;
                if (max > 0)
                {
                    if (total >= max) break;

                    // 最后一次读取大小不同
                    if (count > max - total) count = max - total;
                }

                count = src.Read(buffer, 0, count);
                if (count <= 0) break;
                total += count;

                des.Write(buffer, 0, count);
            }

            return total;
        }

        /// <summary>把一个数据流写入到另一个数据流</summary>
        /// <param name="des">目的数据流</param>
        /// <param name="src">源数据流</param>
        /// <param name="bufferSize">缓冲区大小,也就是每次复制的大小</param>
        /// <param name="max">最大复制字节数</param>
        /// <returns></returns>
        public static Stream Write(this Stream des, Stream src, Int32 bufferSize = 0, Int32 max = 0)
        {
            src.CopyTo(des, bufferSize, max);
            return des;
        }

        /// <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 new Byte[0];

            // 避免数据错乱超长
            //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);

            return des.ReadBytes(len);
        }

        ///// <summary>读取数据包,先读取压缩整数表示的长度</summary>
        ///// <param name="ms"></param>
        ///// <returns></returns>
        //public static Packet ReadPacket(this MemoryStream ms)
        //{
        //    var len = ms.ReadEncodedInt();

        //    return new Packet(ms.GetBuffer(), (Int32)ms.Position, 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)
        {
            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 new Byte[0];

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

#if MF
            Array.Copy(src, srcOffset, dst, dstOffset, count);
#else
            Buffer.BlockCopy(src, srcOffset, dst, dstOffset, count);
#endif
            return count;
        }

        /// <summary>合并两个数组</summary>
        /// <param name="src">源数组</param>
        /// <param name="des">目标数组</param>
        /// <param name="offset">起始位置</param>
        /// <param name="count">字节数</param>
        /// <returns></returns>
        public static Byte[] Combine(this Byte[] src, Byte[] des, Int32 offset = 0, Int32 count = -1)
        {
            if (count < 0) count = src.Length - offset;

            var buf = new Byte[src.Length + count];
            Buffer.BlockCopy(src, 0, buf, 0, src.Length);
            Buffer.BlockCopy(des, offset, buf, src.Length, count);
            return buf;
        }
        #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 new Byte[0];

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

            if (length > 0)
            {
                var buf = new Byte[length];
                var n = stream.Read(buf, 0, buf.Length);
                //if (n != buf.Length) buf = buf.ReadBytes(0, n);
                return buf;
            }

            // 如果要读完数据,又不支持定位,则采用内存流搬运
            if (!stream.CanSeek)
            {
                var ms = Pool.MemoryStream.Get();
                while (true)
                {
                    var buf = new Byte[1024];
                    var count = stream.Read(buf, 0, buf.Length);
                    if (count <= 0) break;

                    ms.Write(buf, 0, count);
                    if (count < buf.Length) break;
                }

                return ms.Put(true);
            }
            else
            {
                // 如果指定长度超过数据流长度,就让其报错,因为那是调用者所期望的值
                length = (Int32)(stream.Length - stream.Position);

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

        /// <summary>数据流转为字节数组,从0开始,无视数据流的当前位置</summary>
        /// <param name="stream">数据流</param>
        /// <returns></returns>
        public static Byte[] ToArray(this Stream stream)
        {
            if (stream is MemoryStream) return (stream as MemoryStream).ToArray();

            stream.Position = 0;
            return stream.ReadBytes();
        }

        /// <summary>从数据流中读取字节数组,直到遇到指定字节数组</summary>
        /// <param name="stream">数据流</param>
        /// <param name="buffer">字节数组</param>
        /// <param name="offset">字节数组中的偏移</param>
        /// <param name="length">字节数组中的查找长度</param>
        /// <returns>未找到时返回空,0位置范围大小为0的字节数组</returns>
        public static Byte[] ReadTo(this Stream stream, Byte[] buffer, Int64 offset = 0, Int64 length = -1)
        {
            //if (!stream.CanSeek) throw new XException("流不支持查找!");

            if (length == 0) return new Byte[0];
            if (length < 0) length = buffer.Length - offset;

            var ori = stream.Position;
            var p = stream.IndexOf(buffer, offset, length);
            stream.Position = ori;
            if (p < 0) return null;
            if (p == 0) return new Byte[0];

            return stream.ReadBytes(p);
        }

        /// <summary>从数据流中读取字节数组,直到遇到指定字节数组</summary>
        /// <param name="stream">数据流</param>
        /// <param name="str"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static Byte[] ReadTo(this Stream stream, String str, Encoding encoding = null)
        {
            if (encoding == null) encoding = Encoding.UTF8;
            return stream.ReadTo(encoding.GetBytes(str));
        }

        /// <summary>从数据流中读取一行,直到遇到换行</summary>
        /// <param name="stream">数据流</param>
        /// <param name="encoding"></param>
        /// <returns>未找到返回null,0位置返回String.Empty</returns>
        public static String ReadLine(this Stream stream, Encoding encoding = null)
        {
            var bts = stream.ReadTo(Environment.NewLine, encoding);
            //if (bts == null || bts.Length < 1) return null;
            if (bts == null) return null;

            stream.Seek(encoding.GetByteCount(Environment.NewLine), SeekOrigin.Current);
            if (bts.Length == 0) return String.Empty;

            return encoding.GetString(bts);
        }

        /// <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 < 1) return null;

            // 可能数据流前面有编码字节序列,需要先去掉
            var idx = 0;
            var preamble = encoding.GetPreamble();
            if (preamble != null && preamble.Length > 0)
            {
                if (buf.StartsWith(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 < 1 || 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.ReadBytes(offset, preamble.Length).StartsWith(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)
        {
            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)
        {
            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)
        {
            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)
        {
            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)
        {
            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)
        {
            var buf = new Byte[8];
            return buf.Write((UInt64)value, 0, isLittleEndian);
        }
        #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("数据流超出范围!已读取整数{0:n0}".F(rs));
                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 = 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 = num >> 7;
            }
            _encodes[count++] = (Byte)num;

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

        #region 数据流查找
        /// <summary>在数据流中查找字节数组的位置,流指针会移动到结尾</summary>
        /// <param name="stream">数据流</param>
        /// <param name="buffer">字节数组</param>
        /// <param name="offset">字节数组中的偏移</param>
        /// <param name="length">字节数组中的查找长度</param>
        /// <returns></returns>
        public static Int64 IndexOf(this Stream stream, Byte[] buffer, Int64 offset = 0, Int64 length = -1)
        {
            if (length <= 0) length = buffer.Length - offset;

            // 位置
            Int64 p = -1;

            for (Int64 i = 0; i < length;)
            {
                var c = stream.ReadByte();
                if (c == -1) return -1;

                p++;
                if (c == buffer[offset + i])
                {
                    i++;

                    // 全部匹配,退出
                    if (i >= length) return p - length + 1;
                }
                else
                {
                    //i = 0; // 只要有一个不匹配,马上清零
                    // 不能直接清零,那样会导致数据丢失,需要逐位探测,窗口一个个字节滑动
                    // 上一次匹配的其实就是j=0那个,所以这里从j=1开始
                    var n = i;
                    i = 0;
                    for (var j = 1; j < n; j++)
                    {
                        // 在字节数组前(j,n)里面找自己(0,n-j)
                        if (CompareTo(buffer, j, n, buffer, 0, n - j) == 0)
                        {
                            // 前面(0,n-j)相等,窗口退回到这里
                            i = n - j;
                            break;
                        }
                    }
                }
            }

            return -1;
        }

        /// <summary>在字节数组中查找另一个字节数组的位置,不存在则返回-1</summary>
        /// <param name="source">字节数组</param>
        /// <param name="buffer">另一个字节数组</param>
        /// <param name="offset">偏移</param>
        /// <param name="length">查找长度</param>
        /// <returns></returns>
        public static Int64 IndexOf(this Byte[] source, Byte[] buffer, Int64 offset = 0, Int64 length = -1) => IndexOf(source, 0, 0, buffer, offset, length);

        /// <summary>在字节数组中查找另一个字节数组的位置,不存在则返回-1</summary>
        /// <param name="source">字节数组</param>
        /// <param name="start">源数组起始位置</param>
        /// <param name="count">查找长度</param>
        /// <param name="buffer">另一个字节数组</param>
        /// <param name="offset">偏移</param>
        /// <param name="length">查找长度</param>
        /// <returns></returns>
        public static Int64 IndexOf(this Byte[] source, Int64 start, Int64 count, Byte[] buffer, Int64 offset = 0, Int64 length = -1)
        {
            if (start < 0) start = 0;
            if (count <= 0 || count > source.Length - start) count = source.Length;
            if (length <= 0 || length > buffer.Length - offset) length = buffer.Length - offset;

            // 已匹配字节数
            Int64 win = 0;
            for (var i = start; i + length - win <= count; i++)
            {
                if (source[i] == buffer[offset + win])
                {
                    win++;

                    // 全部匹配,退出
                    if (win >= length) return i - length + 1 - start;
                }
                else
                {
                    //win = 0; // 只要有一个不匹配,马上清零
                    // 不能直接清零,那样会导致数据丢失,需要逐位探测,窗口一个个字节滑动
                    i = i - win;
                    win = 0;
                }
            }

            return -1;
        }

        /// <summary>比较两个字节数组大小。相等返回0,不等则返回不等的位置,如果位置为0,则返回1。</summary>
        /// <param name="source"></param>
        /// <param name="buffer">缓冲区</param>
        /// <returns></returns>
        public static Int32 CompareTo(this Byte[] source, Byte[] buffer) => CompareTo(source, 0, 0, buffer, 0, 0);

        /// <summary>比较两个字节数组大小。相等返回0,不等则返回不等的位置,如果位置为0,则返回1。</summary>
        /// <param name="source"></param>
        /// <param name="start"></param>
        /// <param name="count">数量</param>
        /// <param name="buffer">缓冲区</param>
        /// <param name="offset">偏移</param>
        /// <param name="length"></param>
        /// <returns></returns>
        public static Int32 CompareTo(this Byte[] source, Int64 start, Int64 count, Byte[] buffer, Int64 offset = 0, Int64 length = -1)
        {
            if (source == buffer) return 0;

            if (start < 0) start = 0;
            if (count <= 0 || count > source.Length - start) count = source.Length - start;
            if (length <= 0 || length > buffer.Length - offset) length = buffer.Length - offset;

            // 逐字节比较
            for (var i = 0; i < count && i < length; i++)
            {
                var rs = source[start + i].CompareTo(buffer[offset + i]);
                if (rs != 0) return i > 0 ? i : 1;
            }

            // 比较完成。如果长度不相等,则较长者较大
            if (count != length) return count > length ? 1 : -1;

            return 0;
        }

        /// <summary>字节数组分割</summary>
        /// <param name="buf"></param>
        /// <param name="sps"></param>
        /// <returns></returns>
        public static IEnumerable<Byte[]> Split(this Byte[] buf, Byte[] sps)
        {
            var p = 0;
            var idx = 0;
            while (true)
            {
                p = (Int32)buf.IndexOf(idx, 0, sps);
                if (p < 0) break;

                yield return buf.ReadBytes(idx, p);

                idx += p + sps.Length;

            }
            if (idx < buf.Length)
            {
                p = buf.Length - idx;
                yield return buf.ReadBytes(idx, p);
            }
        }

        /// <summary>一个数据流是否以另一个数组开头。如果成功,指针移到目标之后,否则保持指针位置不变。</summary>
        /// <param name="source"></param>
        /// <param name="buffer">缓冲区</param>
        /// <returns></returns>
        public static Boolean StartsWith(this Stream source, Byte[] buffer)
        {
            var p = 0;
            for (var i = 0; i < buffer.Length; i++)
            {
                var b = source.ReadByte();
                if (b == -1) { source.Seek(-p, SeekOrigin.Current); return false; }
                p++;

                if (b != buffer[i]) { source.Seek(-p, SeekOrigin.Current); return false; }
            }
            return true;
        }

        /// <summary>一个数据流是否以另一个数组结尾。如果成功,指针移到目标之后,否则保持指针位置不变。</summary>
        /// <param name="source"></param>
        /// <param name="buffer">缓冲区</param>
        /// <returns></returns>
        public static Boolean EndsWith(this Stream source, Byte[] buffer)
        {
            if (source.Length < buffer.Length) return false;

            var p = source.Length - buffer.Length;
            source.Seek(p, SeekOrigin.Current);
            if (source.StartsWith(buffer)) return true;

            source.Seek(-p, SeekOrigin.Current);
            return false;
        }

        /// <summary>一个数组是否以另一个数组开头</summary>
        /// <param name="source"></param>
        /// <param name="buffer">缓冲区</param>
        /// <returns></returns>
        public static Boolean StartsWith(this Byte[] source, Byte[] buffer)
        {
            if (source.Length < buffer.Length) return false;

            for (var i = 0; i < buffer.Length; i++)
            {
                if (source[i] != buffer[i]) return false;
            }
            return true;
        }

        /// <summary>一个数组是否以另一个数组结尾</summary>
        /// <param name="source"></param>
        /// <param name="buffer">缓冲区</param>
        /// <returns></returns>
        public static Boolean EndsWith(this Byte[] source, Byte[] buffer)
        {
            if (source.Length < buffer.Length) return false;

            var p = source.Length - buffer.Length;
            for (var i = 0; i < buffer.Length; i++)
            {
                if (source[p + i] != buffer[i]) return false;
            }
            return true;
        }
        #endregion

        #region 倒序、更换字节序
        /// <summary>倒序、更换字节序</summary>
        /// <param name="buf">字节数组</param>
        /// <returns></returns>
        public static Byte[] Reverse(this Byte[] buf)
        {
            if (buf == null || buf.Length < 2) return buf;

            //if (buf.Length > 100)
            //{
            Array.Reverse(buf);
            return buf;
            //}

            //// 小数组使用指针更快
            //fixed (Byte* p = buf)
            //{
            //    Byte* pStart = p;
            //    Byte* pEnd = p + buf.Length - 1;
            //    for (var i = buf.Length / 2; i > 0; i--)
            //    {
            //        var temp = *pStart;
            //        *pStart++ = *pEnd;
            //        *pEnd-- = temp;
            //    }
            //}
            //return buf;
        }
        #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 < 1) 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 < 1) 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)) len -= 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 new Byte[0];

            // 过滤特殊字符
            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 < 1) 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 new Byte[0];

            if (data[data.Length - 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
    }
}