如何从ReadOnlySequence解析UTF8字符串
ReadOnlySequence由多个部分组成,并且由于UTF8字符的长度可变,因此这些部分的中断可能在一个字符的中间。 因此,仅在部件上使用Encoding.UTF8.GetString()并将其组合到StringBuilder中是行不通的。
是否可以从ReadOnlySequence中解析UTF8字符串,而无需先将它们组合成一个数组。我宁愿避免在这里分配内存。
答案 0 :(得分:2)
我们在这里要做的第一件事是测试序列是否实际上是单个跨度;如果是这样,我们可以极大地简化和优化。
一旦我们知道我们有一个多段(不连续)的缓冲区,我们可以采用两种方法:
GetDecoder()
API,并使用它填充新的字符串,这在较旧的框架上意味着覆盖新分配的字符串,或者在较新的框架中意味着使用string.Create
API 第一个选项简单得多,但是涉及一些内存复制操作(除了字符串之外,没有其他分配):
public static string GetString(in this ReadOnlySequence<byte> payload,
Encoding encoding = null)
{
encoding ??= Encoding.UTF8;
return payload.IsSingleSegment ? encoding.GetString(payload.FirstSpan)
: GetStringSlow(payload, encoding);
static string GetStringSlow(in ReadOnlySequence<byte> payload, Encoding encoding)
{
// linearize
int length = checked((int)payload.Length);
var oversized = ArrayPool<byte>.Shared.Rent(length);
try
{
payload.CopyTo(oversized);
return encoding.GetString(oversized, 0, length);
}
finally
{
ArrayPool<byte>.Shared.Return(oversized);
}
}
}
答案 1 :(得分:1)
您可以使用Decoder
。可能是这样的:
var decoder = Encoding.UTF8.GetDecoder();
var sb = new StringBuilder();
var processed = 0L;
var total = bytes.Length;
foreach (var i in bytes)
{
processed += i.Length;
var isLast = processed == total;
var span = i.Span;
var charCount = decoder.GetCharCount(span, isLast);
Span<char> buffer = stackalloc char[charCount];
decoder.GetChars(span, buffer, isLast);
sb.Append(buffer);
}
Decoder.GetChars方法将字节的顺序块转换为字符的顺序块,其方式类似于此类的GetChars方法。但是,解码器维护调用之间的状态信息,因此它可以正确解码跨越块的字节序列。解码器还在数据块的末尾保留尾随字节,并在下一个解码操作中使用尾随字节。因此,GetDecoder和GetEncoder对于网络传输和文件操作很有用,因为这些操作通常处理数据块而不是完整的数据流。
当然StringBuilder
会引入一个新的分配源,但是如果有问题,您可以用其他类型的缓冲区替换它。
答案 2 :(得分:1)
.NET 5.0 似乎引入了 EncodingExtensions.GetString
来解决这个问题。
使用指定的编码将指定的 ReadOnlySequence 解码为字符串。
using System.Text;
string message = EncodingExtensions.GetString(Encoding.UTF8, buffer);
答案 3 :(得分:0)
警告:未经测试
我对官方回答做了改进:
StringBuilder
的需求GetCharCount
找到的总字符相加,并移动目标跨度切片,消除了额外的GetChars
步骤的需要preProcessedBytes
对我来说尤其重要,IMO直到导出字符后才对它们进行处理。这是源代码:
/// <summary>
/// Parses UTF8 characters in the ReadOnlySequence
/// </summary>
/// <param name="slice">Aligned slice of ReadOnlySequence that contains the UTF8 string bytes. Use slice before calling this function to ensure you have an aligned slice.</param>
/// <param name="stringLengthEstimate">The amount of characters in the final string. You should use a header before the string bytes for the best accuracy. If you are not sure -1 means that the most pessimistic estimate will be used: slice.Length</param>
/// <returns>a string parsed from the bytes in the ReadOnlySequence</returns>
public static string ParseAsUTF8String(this ReadOnlySequence<byte> slice, int stringLengthEstimate = -1)
{
if (stringLengthEstimate == -1)
stringLengthEstimate = (int)slice.Length; //overestimate
var decoder = Encoding.UTF8.GetDecoder();
var preProcessedBytes = 0;
var processedCharacters = 0;
Span<char> characterSpan = stackalloc char[stringLengthEstimate];
foreach (var memory in slice)
{
preProcessedBytes += memory.Length;
var isLast = (preProcessedBytes == slice.Length);
var emptyCharSlice = characterSpan.Slice(processedCharacters, characterSpan.Length - processedCharacters);
var charCount = decoder.GetChars(memory.Span, emptyCharSlice, isLast);
processedCharacters += charCount;
}
var finalCharacters = characterSpan.Slice(0, processedCharacters);
return new string(finalCharacters);
}