#c# #.net-core
#c# #.net-ядро
Вопрос:
У меня есть буфер типа ReadOnlySequence<byte>
. Я хочу извлечь из него подпоследовательность (которая будет содержать 0 — n сообщений), зная, что каждое сообщение заканчивается 0x1c, 0x0d
(как описано здесь ).
Я знаю, что у буфера есть метод расширения PositionOf, но он
Возвращает позицию первого вхождения
item
вReadOnlySequence<T>
.
и я ищу метод, который возвращает мне позицию последнего вхождения. Я попытался реализовать это самостоятельно, это то, что у меня есть до сих пор
private SequencePosition? GetLastPosition(ReadOnlySequence<byte> buffer)
{
// Do not modify the real buffer
ReadOnlySequence<byte> temporaryBuffer = buffer;
SequencePosition? lastPosition = null;
do
{
/*
Find the first occurence of the delimiters in the buffer
This only takes a byte, what to do with the delimiters? { 0x1c, 0x0d }
*/
SequencePosition? foundPosition = temporaryBuffer.PositionOf(???);
// Is there still an occurence?
if (foundPosition != null)
{
lastPosition = foundPosition;
// cut off the sequence for the next run
temporaryBuffer = temporaryBuffer.Slice(0, lastPosition.Value);
}
else
{
// this is required because otherwise this loop is infinite if lastPosition was set once
break;
}
} while (lastPosition != null);
return lastPosition;
}
Я борюсь с этим. Прежде всего PositionOf
, метод принимает только a byte
, но есть два разделителя, поэтому я должен передать a byte[]
. Затем я думаю, что смогу оптимизировать цикл «как-нибудь».
Есть ли у вас какие-либо идеи, как найти последнее вхождение этих разделителей?
Комментарии:
1. Если вы хотите извлечь до n сообщений, каждое из которых заканчивается на этом разделителе, не означает ли это, что вы могли бы искать первое вхождение этого разделителя, извлекать сообщение до этой точки и повторять, а не искать последнее вхождение?
2. @JohnH Я думаю, что OP уже знает, как это сделать, но, как упоминалось в вопросе, но я думаю, что он ищет метод, который делает это под капотом.
3. И я думаю, что он не знает, как справиться,
{ 0x1c, 0x0d }
когдаPositionOf
требуется толькоbyte
4. Я немного обновил свой ответ, Олаф. Это может иметь или не иметь отношения к вам.
5. спасибо, я попробую 🙂
Ответ №1:
Я спустился в гигантскую кроличью нору, копаясь в этом, но мне удалось придумать метод расширения, который, я думаю, отвечает на ваш вопрос:
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
public static class ReadOnlySequenceExtensions
{
public static SequencePosition? LastPositionOf(
this ReadOnlySequence<byte> source,
byte[] delimiter)
{
if (delimiter == null)
{
throw new ArgumentNullException(nameof(delimiter));
}
if (!delimiter.Any())
{
throw new ArgumentException($"{nameof(delimiter)} is empty", nameof(delimiter));
}
var reader = new SequenceReader<byte>(source);
var delimiterToFind = new ReadOnlySpan<byte>(delimiter);
var delimiterFound = false;
// Keep reading until we've consumed all delimiters
while (reader.TryReadTo(out _, delimiterToFind, true))
{
delimiterFound = true;
}
if (!delimiterFound)
{
return null;
}
// If we got this far, we've consumed bytes up to,
// and including, the last byte of the delimiter,
// so we can use that to get the position of
// the starting byte of the delimiter
return reader.Sequence.GetPosition(reader.Consumed - delimiter.Length);
}
}
Вот также несколько тестовых примеров:
var cases = new List<byte[]>
{
// Case 1: Check an empty array
new byte[0],
// Case 2: Check an array with no delimiter
new byte[] { 0xf },
// Case 3: Check an array with part of the delimiter
new byte[] { 0x1c },
// Case 4: Check an array with the other part of the delimiter
new byte[] { 0x0d },
// Case 5: Check an array with the delimiter in the wrong order
new byte[] { 0x0d, 0x1c },
// Case 6: Check an array with a correct delimiter
new byte[] { 0x1c, 0x0d },
// Case 7: Check an array with a byte followed by a correct delimiter
new byte[] { 0x1, 0x1c, 0x0d },
// Case 8: Check an array with multiple correct delimiters
new byte[] { 0x1, 0x1c, 0x0d, 0x2, 0x1c, 0x0d },
// Case 9: Check an array with multiple correct delimiters
// where the delimiter isn't the last byte
new byte[] { 0x1, 0x1c, 0x0d, 0x2, 0x1c, 0x0d, 0x3 },
// Case 10: Check an array with multiple sequential bytes of a delimiter
new byte[] { 0x1, 0x1c, 0x0d, 0x2, 0x1c, 0x1c, 0x0d, 0x3 },
};
var delimiter = new byte[] { 0x1c, 0x0d };
foreach (var item in cases)
{
var source = new ReadOnlySequence<byte>(item);
var result = source.LastPositionOf(delimiter);
} // Put a breakpoint here and examine result
1
5
Все случаи корректно возвращаются null
. 6
10
Все случаи корректно возвращают SequencePosition
значение первому байту в разделителе (т. Е. В данном случае 0x1c
).
Я также попытался создать итеративную версию, которая выдавала бы позицию после нахождения разделителя, например:
while (reader.TryReadTo(out _, delimiterToFind, true))
{
yield return reader.Sequence.GetPosition(reader.Consumed - delimiter.Length);
}
Но SequenceReader<T>
и ReadOnlySpan<T>
не может использоваться в блоках итератора, поэтому я придумал AllPositionsOf
вместо:
public static IEnumerable<SequencePosition> AllPositionsOf(
this ReadOnlySequence<byte> source,
byte[] delimiter)
{
if (delimiter == null)
{
throw new ArgumentNullException(nameof(delimiter));
}
if (!delimiter.Any())
{
throw new ArgumentException($"{nameof(delimiter)} is empty", nameof(delimiter));
}
var reader = new SequenceReader<byte>(source);
var delimiterToFind = new ReadOnlySpan<byte>(delimiter);
var results = new List<SequencePosition>();
while (reader.TryReadTo(out _, delimiterToFind, true))
{
results.Add(reader.Sequence.GetPosition(reader.Consumed - delimiter.Length));
}
return results;
}
Тестовые примеры для этого тоже работают правильно.
Обновить
Теперь, когда я немного поспал и получил возможность подумать о вещах, я думаю, что вышесказанное можно улучшить по нескольким причинам:
SequenceReader<T>
имеетRewind()
метод, который заставляет меня думатьSequenceReader<T>
, предназначен для повторного использованияSequenceReader<T>
похоже, это сделано для упрощения работы сReadOnlySequence<T>
s в целом- Создание метода расширения
ReadOnlySequence<T>
для использования aSequenceReader<T>
для чтения из aReadOnlySequence<T>
кажется обратным
Учитывая вышесказанное, я думаю, что, вероятно, имеет смысл стараться избегать прямой работы с ReadOnlySequence<T>
s, где это возможно, предпочитая и повторно SequenceReader<T>
используя вместо этого. Итак, имея это в виду, вот другая версия LastPositionOf
, которая теперь является методом расширения на SequenceReader<T>
:
public static class SequenceReaderExtensions
{
/// <summary>
/// Finds the last occurrence of a delimiter in a given sequence.
/// </summary>
/// <param name="reader">The reader to read from.</param>
/// <param name="delimiter">The delimeter to look for.</param>
/// <param name="rewind">If true, rewinds the reader to its position prior to this method being called.</param>
/// <returns>A SequencePosition if a delimiter is found, otherwise null.</returns>
public static SequencePosition? LastPositionOf(
this ref SequenceReader<byte> reader,
byte[] delimiter,
bool rewind)
{
if (delimiter == null)
{
throw new ArgumentNullException(nameof(delimiter));
}
if (!delimiter.Any())
{
throw new ArgumentException($"{nameof(delimiter)} is empty", nameof(delimiter));
}
var delimiterToFind = new ReadOnlySpan<byte>(delimiter);
var consumed = reader.Consumed;
var delimiterFound = false;
// Keep reading until we've consumed all delimiters
while (reader.TryReadTo(out _, delimiterToFind, true))
{
delimiterFound = true;
}
if (!delimiterFound)
{
if (rewind)
{
reader.Rewind(reader.Consumed - consumed);
}
return null;
}
// If we got this far, we've consumed bytes up to,
// and including, the last byte of the delimiter,
// so we can use that to get the starting byte
// of the delimiter
var result = reader.Sequence.GetPosition(reader.Consumed - delimiter.Length);
if (rewind)
{
reader.Rewind(reader.Consumed - consumed);
}
return resu<
}
}
Приведенные выше тестовые примеры продолжают использоваться для этого, но теперь мы можем повторно использовать то же reader
самое. Кроме того, это позволяет вам указать, хотите ли вы вернуться к исходной позиции reader
перед вызовом.
Комментарии:
1. итак, вместо того, чтобы использовать ваш первый подход для последовательности, я мог бы теперь сделать что-то вроде
SequenceReader<byte> sequenceReader = new SequenceReader<byte>(buffer); SequencePosition? lastPosition = sequenceReader.LastPositionOf(delimiters, false);
права? По крайней мере, мой тест пройден 🙂