Cursos
Módulo
Aprenda a crear variables de matriz e iterar los elementos de la matriz.
Este explorador ya no se admite.
Actualice a Microsoft Edge para aprovechar las características y actualizaciones de seguridad más recientes, y disponer de soporte técnico.
En este artículo se proporciona información general sobre los tipos que ayudan a leer datos que se ejecutan en varios búferes. Se usan principalmente para la compatibilidad con objetos PipeReader.
System.Buffers.IBufferWriter<T> es un contrato para la escritura sincrónica en búfer. En el nivel más bajo, la interfaz:
Memory<T>
o Span<T>
y se puede determinar cuántos elementos T
se escribieron.void WriteHello(IBufferWriter<byte> writer)
{
// Request at least 5 bytes.
Span<byte> span = writer.GetSpan(5);
ReadOnlySpan<char> helloSpan = "Hello".AsSpan();
int written = Encoding.ASCII.GetBytes(helloSpan, span);
// Tell the writer how many bytes were written.
writer.Advance(written);
}
El método anterior:
IBufferWriter<byte>
mediante GetSpan(5)
.Span<byte>
devuelto.Este método de escritura usa el búfer Memory<T>
/Span<T>
proporcionado por IBufferWriter<T>
. Como alternativa, se puede usar el método de extensión Write para copiar un búfer existente en IBufferWriter<T>
. Write
realiza el trabajo de llamar a GetSpan
/Advance
según sea necesario, por lo que no hace falta llamar a Advance
después de escribir:
void WriteHello(IBufferWriter<byte> writer)
{
byte[] helloBytes = Encoding.ASCII.GetBytes("Hello");
// Write helloBytes to the writer. There's no need to call Advance here
// since Write calls Advance.
writer.Write(helloBytes);
}
ArrayBufferWriter<T> es una implementación de IBufferWriter<T>
cuya memoria auxiliar es una sola matriz contigua.
GetSpan
y GetMemory
devuelven un búfer con, al menos, la cantidad de memoria solicitada. No asume tamaños de búfer exactos.Advance
para seguir escribiendo más datos. No se puede escribir en un búfer adquirido previamente después de llamar a Advance
.ReadOnlySequence<T> es una estructura que puede representar una secuencia de T
contigua o no contigua. Se puede construir a partir de:
T[]
.ReadOnlyMemory<T>
.La tercera representación es la más interesante, ya que tiene implicaciones para el rendimiento en varias operaciones en ReadOnlySequence<T>
:
Representación | Operación | Complejidad |
---|---|---|
T[] /ReadOnlyMemory<T> |
Length |
O(1) |
T[] /ReadOnlyMemory<T> |
GetPosition(long) |
O(1) |
T[] /ReadOnlyMemory<T> |
Slice(int, int) |
O(1) |
T[] /ReadOnlyMemory<T> |
Slice(SequencePosition, SequencePosition) |
O(1) |
ReadOnlySequenceSegment<T> |
Length |
O(1) |
ReadOnlySequenceSegment<T> |
GetPosition(long) |
O(number of segments) |
ReadOnlySequenceSegment<T> |
Slice(int, int) |
O(number of segments) |
ReadOnlySequenceSegment<T> |
Slice(SequencePosition, SequencePosition) |
O(1) |
Debido a esta representación mixta, ReadOnlySequence<T>
expone los índices como SequencePosition
en lugar de un entero. SequencePosition
:
ReadOnlySequence<T>
donde se originó.ReadOnlySequence<T>
.ReadOnlySequence<T>
expone los datos como un valor enumerable de ReadOnlyMemory<T>
. La enumeración de cada uno de los segmentos puede realizarse mediante una instrucción foreach básica:
long FindIndexOf(in ReadOnlySequence<byte> buffer, byte data)
{
long position = 0;
foreach (ReadOnlyMemory<byte> segment in buffer)
{
ReadOnlySpan<byte> span = segment.Span;
var index = span.IndexOf(data);
if (index != -1)
{
return position + index;
}
position += span.Length;
}
return -1;
}
El método anterior busca un byte específico en cada segmento. Si necesita realizar un seguimiento del valor SequencePosition
de cada segmento, es más adecuado ReadOnlySequence<T>.TryGet. En el ejemplo siguiente se cambia el código anterior para devolver SequencePosition
en lugar de un entero. Devolver SequencePosition
tiene la ventaja de permitir que el autor de la llamada evite un segundo examen para obtener los datos en un índice específico.
SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data)
{
SequencePosition position = buffer.Start;
SequencePosition result = position;
while (buffer.TryGet(ref position, out ReadOnlyMemory<byte> segment))
{
ReadOnlySpan<byte> span = segment.Span;
var index = span.IndexOf(data);
if (index != -1)
{
return buffer.GetPosition(index, result);
}
result = position;
}
return null;
}
La combinación de SequencePosition
y TryGet
actúa como enumerador. El campo de posición se modifica al principio de cada iteración para que sea el inicio de cada segmento dentro de ReadOnlySequence<T>
.
El método anterior existe como método de extensión en ReadOnlySequence<T>
. PositionOf se puede usar para simplificar el código anterior:
SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data) => buffer.PositionOf(data);
El procesamiento de ReadOnlySequence<T>
puede ser difícil, dado que los datos podrían estar divididos en varios segmentos dentro de la secuencia. Para obtener el mejor rendimiento, divida el código en dos rutas de acceso:
Existen varios enfoques que se pueden usar para procesar datos en secuencias de varios segmentos:
SequenceReader<T>
.SequencePosition
y del índice dentro del segmento analizado. De esta forma, se evitan asignaciones innecesarias, aunque puede ser ineficaz, especialmente en el caso de búferes pequeños.ReadOnlySequence<T>
en una matriz contigua y tratarla como un solo búfer: ReadOnlySequence<T>
es pequeño, puede ser razonable copiar los datos en un búfer asignado por la pila mediante el operador stackalloc.ReadOnlySequence<T>
en una matriz agrupada mediante ArrayPool<T>.Shared.ReadOnlySequence<T>.ToArray()
. Este método no se recomienda en las rutas de acceso activas, ya que asigna un nuevo elemento T[]
en el montón.En los siguientes ejemplos se muestran algunos casos comunes de procesamiento de ReadOnlySequence<byte>
:
En el siguiente ejemplo se analiza una longitud de entero bid endian de 4 bytes desde el inicio de ReadOnlySequence<byte>
.
bool TryParseHeaderLength(ref ReadOnlySequence<byte> buffer, out int length)
{
// If there's not enough space, the length can't be obtained.
if (buffer.Length < 4)
{
length = 0;
return false;
}
// Grab the first 4 bytes of the buffer.
var lengthSlice = buffer.Slice(buffer.Start, 4);
if (lengthSlice.IsSingleSegment)
{
// Fast path since it's a single segment.
length = BinaryPrimitives.ReadInt32BigEndian(lengthSlice.First.Span);
}
else
{
// There are 4 bytes split across multiple segments. Since it's so small, it
// can be copied to a stack allocated buffer. This avoids a heap allocation.
Span<byte> stackBuffer = stackalloc byte[4];
lengthSlice.CopyTo(stackBuffer);
length = BinaryPrimitives.ReadInt32BigEndian(stackBuffer);
}
// Move the buffer 4 bytes ahead.
buffer = buffer.Slice(lengthSlice.End);
return true;
}
En el ejemplo siguiente:
\r\n
) en ReadOnlySequence<byte>
y la devuelve mediante el parámetro "line" de salida.\r\n
del búfer de entrada.static bool TryParseLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> line)
{
SequencePosition position = buffer.Start;
SequencePosition previous = position;
var index = -1;
line = default;
while (buffer.TryGet(ref position, out ReadOnlyMemory<byte> segment))
{
ReadOnlySpan<byte> span = segment.Span;
// Look for \r in the current segment.
index = span.IndexOf((byte)'\r');
if (index != -1)
{
// Check next segment for \n.
if (index + 1 >= span.Length)
{
var next = position;
if (!buffer.TryGet(ref next, out ReadOnlyMemory<byte> nextSegment))
{
// You're at the end of the sequence.
return false;
}
else if (nextSegment.Span[0] == (byte)'\n')
{
// A match was found.
break;
}
}
// Check the current segment of \n.
else if (span[index + 1] == (byte)'\n')
{
// It was found.
break;
}
}
previous = position;
}
if (index != -1)
{
// Get the position just before the \r\n.
var delimeter = buffer.GetPosition(index, previous);
// Slice the line (excluding \r\n).
line = buffer.Slice(buffer.Start, delimeter);
// Slice the buffer to get the remaining data after the line.
buffer = buffer.Slice(buffer.GetPosition(2, delimeter));
return true;
}
return false;
}
Es válido almacenar segmentos vacíos dentro de ReadOnlySequence<T>
. Pueden aparecer segmentos vacíos mientras se enumeran los segmentos de manera explícita:
static void EmptySegments()
{
// This logic creates a ReadOnlySequence<byte> with 4 segments,
// two of which are empty.
var first = new BufferSegment(new byte[0]);
var last = first.Append(new byte[] { 97 })
.Append(new byte[0]).Append(new byte[] { 98 });
// Construct the ReadOnlySequence<byte> from the linked list segments.
var data = new ReadOnlySequence<byte>(first, 0, last, 1);
// Slice using numbers.
var sequence1 = data.Slice(0, 2);
// Slice using SequencePosition pointing at the empty segment.
var sequence2 = data.Slice(data.Start, 2);
Console.WriteLine($"sequence1.Length={sequence1.Length}"); // sequence1.Length=2
Console.WriteLine($"sequence2.Length={sequence2.Length}"); // sequence2.Length=2
// sequence1.FirstSpan.Length=1
Console.WriteLine($"sequence1.FirstSpan.Length={sequence1.FirstSpan.Length}");
// Slicing using SequencePosition will Slice the ReadOnlySequence<byte> directly
// on the empty segment!
// sequence2.FirstSpan.Length=0
Console.WriteLine($"sequence2.FirstSpan.Length={sequence2.FirstSpan.Length}");
// The following code prints 0, 1, 0, 1.
SequencePosition position = data.Start;
while (data.TryGet(ref position, out ReadOnlyMemory<byte> memory))
{
Console.WriteLine(memory.Length);
}
}
class BufferSegment : ReadOnlySequenceSegment<byte>
{
public BufferSegment(Memory<byte> memory)
{
Memory = memory;
}
public BufferSegment Append(Memory<byte> memory)
{
var segment = new BufferSegment(memory)
{
RunningIndex = RunningIndex + Memory.Length
};
Next = segment;
return segment;
}
}
El código anterior crea una estructura ReadOnlySequence<byte>
con segmentos vacíos y muestra cómo afectan estos a las distintas API:
ReadOnlySequence<T>.Slice
con un valor SequencePosition
que apunta a un segmento vacío conserva ese segmento.ReadOnlySequence<T>.Slice
con un valor entero omite los segmentos vacíos.ReadOnlySequence<T>
, se enumeran los segmentos vacíos.Hay varios resultados inusuales cuando se trabaja con ReadOnlySequence<T>
/SequencePosition
frente a una representación ReadOnlySpan<T>
normal /ReadOnlyMemory<T>
/T[]
/int
:
SequencePosition
es un marcador de posición para una estructura ReadOnlySequence<T>
específica, no una posición absoluta. Dado que es relativo a una estructura ReadOnlySequence<T>
específica, no tiene ningún significado si se usa fuera de la estructura ReadOnlySequence<T>
donde se origina.SequencePosition
sin ReadOnlySequence<T>
. Eso significa que hacer cosas básicas, como position++
, se escribe position = ReadOnlySequence<T>.GetPosition(1, position)
.GetPosition(long)
no admite índices negativos. Esto significa que es imposible obtener del segundo al último carácter sin recorrer todos los segmentos.SequencePosition
, lo que dificulta: ReadOnlySequence<T>
es mayor que una referencia de objeto y debe pasarse mediante in o ref siempre que sea posible. Pasar ReadOnlySequence<T>
mediante in
o ref
reduce las copias de la estructura.ReadOnlySequence<T>
.ReadOnlySequence<T>.TryGet
.ReadOnlySequence<T>.Slice()
con objetos SequencePosition
.ReadOnlySequence<T>
.ReadOnlySequence<T>
y varios segmentos ReadOnlySequence<T>
.byte
y char
) que podrían estar o no divididos entre segmentos.Existen métodos integrados que se ocupan del procesamiento de datos binarios y delimitados. En la siguiente sección se muestra el aspecto de esos mismos métodos con SequenceReader<T>
:
SequenceReader<T>
tiene métodos para enumerar datos dentro de ReadOnlySequence<T>
directamente. El código siguiente es un ejemplo de procesamiento de ReadOnlySequence<byte>
un byte
cada vez:
while (reader.TryRead(out byte b))
{
Process(b);
}
CurrentSpan
expone el valor Span
del segmento actual, que es similar a lo que se hizo en el método manualmente.
El código siguiente es un ejemplo de implementación de FindIndexOf
mediante SequenceReader<T>
.
SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data)
{
var reader = new SequenceReader<byte>(buffer);
while (!reader.End)
{
// Search for the byte in the current span.
var index = reader.CurrentSpan.IndexOf(data);
if (index != -1)
{
// It was found, so advance to the position.
reader.Advance(index);
return reader.Position;
}
// Skip the current segment since there's nothing in it.
reader.Advance(reader.CurrentSpan.Length);
}
return null;
}
En el siguiente ejemplo se analiza una longitud de entero bid endian de 4 bytes desde el inicio de ReadOnlySequence<byte>
.
bool TryParseHeaderLength(ref ReadOnlySequence<byte> buffer, out int length)
{
var reader = new SequenceReader<byte>(buffer);
return reader.TryReadBigEndian(out length);
}
static ReadOnlySpan<byte> NewLine => new byte[] { (byte)'\r', (byte)'\n' };
static bool TryParseLine(ref ReadOnlySequence<byte> buffer,
out ReadOnlySequence<byte> line)
{
var reader = new SequenceReader<byte>(buffer);
if (reader.TryReadTo(out line, NewLine))
{
buffer = buffer.Slice(reader.Position);
return true;
}
line = default;
return false;
}
SequenceReader<T>
es una estructura mutable, siempre debe pasarse mediante una referencia.SequenceReader<T>
es una estructura de referencia, de modo que solo se puede usar en métodos sincrónicos y no se puede almacenar en campos. Para más información, consulte Cómo evitar asignaciones.SequenceReader<T>
está optimizado para su uso como lector de solo avance. Rewind
está destinado a copias de seguridad pequeñas que no se pueden abordar mediante otras API de Read
, Peek
y IsNext
.Comentarios de .NET
.NET es un proyecto de código abierto. Seleccione un vínculo para proporcionar comentarios:
Cursos
Módulo
Aprenda a crear variables de matriz e iterar los elementos de la matriz.