Files
ThingsGateway/src/Admin/ThingsGateway.NewLife.X/Buffers/SpanHelper.cs
2025-05-23 12:55:21 +08:00

339 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Runtime.InteropServices;
using System.Text;
using ThingsGateway.NewLife.Collections;
namespace ThingsGateway.NewLife.Extension;
/// <summary>Span帮助类</summary>
public static class SpanHelper
{
#region
/// <summary>转字符串</summary>
/// <param name="span"></param>
/// <param name="encoding"></param>
/// <returns></returns>
public static String ToStr(this ReadOnlySpan<Byte> span, Encoding? encoding = null) => (encoding ?? Encoding.UTF8).GetString(span);
/// <summary>转字符串</summary>
/// <param name="span"></param>
/// <param name="encoding"></param>
/// <returns></returns>
public static String ToStr(this Span<Byte> span, Encoding? encoding = null) => (encoding ?? Encoding.UTF8).GetString(span);
/// <summary>获取字符串的字节数组</summary>
public static unsafe Int32 GetBytes(this Encoding encoding, ReadOnlySpan<Char> chars, Span<Byte> bytes)
{
fixed (Char* chars2 = &MemoryMarshal.GetReference(chars))
{
fixed (Byte* bytes2 = &MemoryMarshal.GetReference(bytes))
{
return encoding.GetBytes(chars2, chars.Length, bytes2, bytes.Length);
}
}
}
/// <summary>获取字节数组的字符串</summary>
public static unsafe String GetString(this Encoding encoding, ReadOnlySpan<Byte> bytes)
{
if (bytes.IsEmpty) return String.Empty;
#if NET45
return encoding.GetString(bytes.ToArray());
#else
fixed (Byte* bytes2 = &MemoryMarshal.GetReference(bytes))
{
return encoding.GetString(bytes2, bytes.Length);
}
#endif
}
/// <summary>把字节数组编码为十六进制字符串</summary>
/// <param name="data">字节数组</param>
/// <returns></returns>
public static String ToHex(this ReadOnlySpan<Byte> data)
{
if (data.Length == 0) return String.Empty;
Span<Char> cs = stackalloc Char[data.Length * 2];
for (Int32 i = 0, j = 0; i < data.Length; i++, j += 2)
{
var b = data[i];
cs[j] = GetHexValue(b >> 4);
cs[j + 1] = GetHexValue(b & 0x0F);
}
return cs.ToString();
}
/// <summary>把字节数组编码为十六进制字符串</summary>
/// <param name="data">字节数组</param>
/// <param name="maxLength">最大长度</param>
/// <returns></returns>
public static String ToHex(this ReadOnlySpan<Byte> data, Int32 maxLength)
{
if (data.Length == 0) return String.Empty;
if (maxLength > 0 && data.Length > maxLength) data = data[..maxLength];
return data.ToHex();
}
/// <summary>把字节数组编码为十六进制字符串</summary>
/// <param name="data">字节数组</param>
/// <returns></returns>
public static String ToHex(this Span<Byte> data) => ToHex((ReadOnlySpan<Byte>)data);
private static Char GetHexValue(Int32 i) => i < 10 ? (Char)(i + '0') : (Char)(i - 10 + 'A');
/// <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 ReadOnlySpan<Byte> data, String? separate, Int32 groupSize = 0, Int32 maxLength = -1)
{
if (data.Length == 0 || maxLength == 0) return String.Empty;
if (maxLength > 0 && data.Length > maxLength) data = data[..maxLength];
//return data.ToArray().ToHex(separate, groupSize);
if (groupSize < 0) groupSize = 0;
var count = data.Length;
if (groupSize == 0)
{
// 没有分隔符
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);
}
var b = data[i];
sb.Append(GetHexValue(b >> 4));
sb.Append(GetHexValue(b & 0x0F));
}
return sb.Return(true) ?? String.Empty;
}
/// <summary>以十六进制编码表示</summary>
/// <param name="span"></param>
/// <param name="separate">分隔符</param>
/// <param name="groupSize">分组大小为0时对每个字节应用分隔符否则对每个分组使用</param>
/// <param name="maxLength">最大显示多少个字节。默认-1显示全部</param>
/// <returns></returns>
public static String ToHex(this Span<Byte> span, String? separate, Int32 groupSize = 0, Int32 maxLength = -1)
{
if (span.Length == 0 || maxLength == 0) return String.Empty;
return ToHex((ReadOnlySpan<Byte>)span, separate, groupSize, maxLength);
}
/// <summary>通过指定开始与结束边界来截取数据源</summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="start"></param>
/// <param name="end"></param>
/// <returns></returns>
public static ReadOnlySpan<T> Substring<T>(this ReadOnlySpan<T> source, ReadOnlySpan<T> start, ReadOnlySpan<T> end) where T : IEquatable<T>
{
var startIndex = source.IndexOf(start);
if (startIndex == -1) return [];
startIndex += start.Length;
var endIndex = source[startIndex..].IndexOf(end);
if (endIndex == -1) return [];
return source.Slice(startIndex, endIndex);
}
/// <summary>通过指定开始与结束边界来截取数据源</summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="start"></param>
/// <param name="end"></param>
/// <returns></returns>
public static Span<T> Substring<T>(this Span<T> source, ReadOnlySpan<T> start, ReadOnlySpan<T> end) where T : IEquatable<T>
{
var startIndex = source.IndexOf(start);
if (startIndex == -1) return [];
startIndex += start.Length;
var endIndex = source[startIndex..].IndexOf(end);
if (endIndex == -1) return [];
return source.Slice(startIndex, endIndex);
}
/// <summary>在数据源中查找开始与结束边界</summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="start"></param>
/// <param name="end"></param>
/// <returns></returns>
public static (Int32 offset, Int32 count) IndexOf<T>(this ReadOnlySpan<T> source, ReadOnlySpan<T> start, ReadOnlySpan<T> end) where T : IEquatable<T>
{
var startIndex = source.IndexOf(start);
if (startIndex == -1) return (-1, -1);
startIndex += start.Length;
var endIndex = source[startIndex..].IndexOf(end);
if (endIndex == -1) return (startIndex, -1);
return (startIndex, endIndex);
}
/// <summary>在数据源中查找开始与结束边界</summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="start"></param>
/// <param name="end"></param>
/// <returns></returns>
public static (Int32 offset, Int32 count) IndexOf<T>(this Span<T> source, ReadOnlySpan<T> start, ReadOnlySpan<T> end) where T : IEquatable<T>
{
var startIndex = source.IndexOf(start);
if (startIndex == -1) return (-1, -1);
startIndex += start.Length;
var endIndex = source[startIndex..].IndexOf(end);
if (endIndex == -1) return (startIndex, -1);
return (startIndex, endIndex);
}
#endregion
/// <summary>写入Memory到数据流。从内存池借出缓冲区拷贝仅作为兜底使用</summary>
/// <param name="stream"></param>
/// <param name="buffer"></param>
/// <returns></returns>
public static void Write(this Stream stream, ReadOnlyMemory<Byte> buffer)
{
if (MemoryMarshal.TryGetArray(buffer, out var segment))
{
stream.Write(segment.Array!, segment.Offset, segment.Count);
return;
}
var array = ArrayPool<Byte>.Shared.Rent(buffer.Length);
try
{
buffer.Span.CopyTo(array);
stream.Write(array, 0, buffer.Length);
}
finally
{
ArrayPool<Byte>.Shared.Return(array);
}
}
/// <summary>写入Memory到数据流。从内存池借出缓冲区拷贝仅作为兜底使用</summary>
/// <param name="stream"></param>
/// <param name="buffer"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static Task WriteAsync(this Stream stream, ReadOnlyMemory<Byte> buffer, CancellationToken cancellationToken = default)
{
if (MemoryMarshal.TryGetArray(buffer, out var segment))
return stream.WriteAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken);
var array = ArrayPool<Byte>.Shared.Rent(buffer.Length);
buffer.Span.CopyTo(array);
var writeTask = stream.WriteAsync(array, 0, buffer.Length, cancellationToken);
return Task.Run(async () =>
{
try
{
await writeTask.ConfigureAwait(false);
}
finally
{
ArrayPool<Byte>.Shared.Return(array);
}
}, cancellationToken);
}
#if NETFRAMEWORK || NETSTANDARD
/// <summary>去掉前后字符</summary>
/// <typeparam name="T"></typeparam>
/// <param name="span"></param>
/// <param name="trimElement"></param>
/// <returns></returns>
public static ReadOnlySpan<T> Trim<T>(this ReadOnlySpan<T> span, T trimElement) where T : IEquatable<T>
{
var start = ClampStart(span, trimElement);
var length = ClampEnd(span, start, trimElement);
return span.Slice(start, length);
}
/// <summary>去掉前后字符</summary>
/// <typeparam name="T"></typeparam>
/// <param name="span"></param>
/// <param name="trimElement"></param>
/// <returns></returns>
public static Span<T> Trim<T>(this Span<T> span, T trimElement) where T : IEquatable<T>
{
var start = ClampStart(span, trimElement);
var length = ClampEnd(span, start, trimElement);
return span.Slice(start, length);
}
private static Int32 ClampStart<T>(ReadOnlySpan<T> span, T trimElement) where T : IEquatable<T>
{
var i = 0;
for (; i < span.Length; i++)
{
ref var reference = ref trimElement;
if (!reference.Equals(span[i]))
{
break;
}
}
return i;
}
private static Int32 ClampEnd<T>(ReadOnlySpan<T> span, Int32 start, T trimElement) where T : IEquatable<T>
{
var num = span.Length - 1;
while (num >= start)
{
ref var reference = ref trimElement;
if (!reference.Equals(span[num]))
{
break;
}
num--;
}
return num - start + 1;
}
#endif
}