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<Byte></c> 的 <c>Stream.ReadAtLeast(Span<Byte> buffer, int minimumBytes, bool throwOnEndOfStream)</c> 重载。对于仅覆写传统 <c>Read(byte[], int, int)</c> 的自定义流,基类 <c>Read(Span<Byte>)</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
}
|