Práce s vyrovnávacími paměťmi v .NET
Tento článek obsahuje přehled typů, které pomáhají číst data, která běží ve více vyrovnávacích pamětích. Primárně se používají k podpoře PipeReader objektů.
System.Buffers.IBufferWriter<T> je kontrakt pro synchronní zápis do vyrovnávací paměti. Na nejnižší úrovni rozhraní:
- Je základní a není obtížné ji používat.
- Umožňuje přístup k nebo Memory<T>Span<T>. Můžete
Memory<T>
je napsat neboSpan<T>
můžete určit,T
kolik položek bylo zapsáno.
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);
}
Předchozí metoda:
- Požaduje vyrovnávací paměť nejméně 5 bajtů z
IBufferWriter<byte>
použitíGetSpan(5)
. - Zapíše bajty pro řetězec ASCII "Hello" na vrácený
Span<byte>
. - Volání IBufferWriter<T> označující, kolik bajtů bylo zapsáno do vyrovnávací paměti.
Tato metoda zápisu Memory<T>
/Span<T>
používá vyrovnávací paměť poskytovanou .IBufferWriter<T>
Alternativně lze metodu Write rozšíření použít ke zkopírování existující vyrovnávací paměti do IBufferWriter<T>
. Write
funguje volání GetSpan
/Advance
podle potřeby, takže po napsání není nutné volat:Advance
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> je implementace, jejíž IBufferWriter<T>
záložní úložiště je jedno souvislé pole.
GetSpan
aGetMemory
vrátit vyrovnávací paměť s alespoň požadovanou velikostí paměti. Nepředpokládáme přesné velikosti vyrovnávací paměti.- Není zaručeno, že následná volání vrátí stejnou vyrovnávací paměť nebo vyrovnávací paměť stejné velikosti.
- Po volání
Advance
musí být požadována nová vyrovnávací paměť, aby bylo možné pokračovat v zápisu dalších dat. Dříve získanou vyrovnávací paměť nelze zapsat do poAdvance
zavolání.
ReadOnlySequence<T> je struktura, která může představovat souvislou nebo nesouvisenou sekvenci T
. Dá se vytvořit z:
- Provede
T[]
. - Provede
ReadOnlyMemory<T>
. - Dvojice propojených uzlů ReadOnlySequenceSegment<T> seznamu a indexu, která představuje počáteční a koncovou pozici sekvence.
Třetí reprezentace je nejzajímavější, protože má vliv na výkon na různé operace na ReadOnlySequence<T>
:
Reprezentace | Operace | Složitost |
---|---|---|
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) |
Z důvodu této smíšené reprezentace ReadOnlySequence<T>
zveřejňuje indexy místo SequencePosition
celého čísla. A SequencePosition
:
- Je neprůhledná hodnota, která představuje index do
ReadOnlySequence<T>
místa, kde pochází. - Skládá se ze dvou částí, celého čísla a objektu. Jaké tyto dvě hodnoty představují, jsou svázané s implementací
ReadOnlySequence<T>
.
Zpřístupňuje ReadOnlySequence<T>
data jako výčet .ReadOnlyMemory<T>
Výčet jednotlivých segmentů lze provést pomocí základního foreachu:
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;
}
Předchozí metoda hledá v každém segmentu konkrétní bajt. Pokud potřebujete sledovat jednotlivé segmenty SequencePosition
, ReadOnlySequence<T>.TryGet je vhodnější. Další ukázka změní předchozí kód tak, aby vrátil SequencePosition
místo celého čísla. Vrácení SequencePosition
má výhodu, že volajícímu umožníte vyhnout se druhému prohledávání, abyste získali data v určitém indexu.
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;
}
SequencePosition
Kombinace enumerátoru a TryGet
funguje jako enumerátor. Pole pozice je změněno na začátku každé iterace tak, aby začínalo u každého segmentu v rámci ReadOnlySequence<T>
.
Předchozí metoda existuje jako rozšiřující metoda pro ReadOnlySequence<T>
. PositionOf lze použít ke zjednodušení předchozího kódu:
SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data) => buffer.PositionOf(data);
ReadOnlySequence<T>
Zpracování může být náročné, protože data mohou být rozdělena mezi více segmentů v rámci sekvence. Nejlepšího výkonu dosáhnete rozdělením kódu do dvou cest:
- Rychlá cesta, která se zabývá jedním segmentovým případem.
- Pomalá cesta, která se zabývá rozdělením dat mezi segmenty.
Existuje několik přístupů, které je možné použít ke zpracování dat ve více segmentovaných sekvencích:
- Použijte .
SequenceReader<T>
- Parsujte datový segment podle segmentů a sledujte index
SequencePosition
v rámci analyzovaného segmentu. Tím se vyhnete zbytečným přidělením, ale může to být neefektivní, zejména u malých vyrovnávacích pamětí. - Zkopírujte ho
ReadOnlySequence<T>
do souvislého pole a zacházejte s ním jako s jednou vyrovnávací pamětí:- Pokud je velikost
ReadOnlySequence<T>
malé, může být vhodné data zkopírovat do vyrovnávací paměti přidělené zásobníku pomocí operátoru stackalloc . - Zkopírujte do
ReadOnlySequence<T>
pole ve fondu pomocí .ArrayPool<T>.Shared - Použijte
ReadOnlySequence<T>.ToArray()
. Nedoporučuje se to v horkých cestách, protože přiděluje novouT[]
haldu.
- Pokud je velikost
Následující příklady ukazují některé běžné případy zpracování ReadOnlySequence<byte>
:
Následující příklad analyzuje celočíselnou délku 4bajtů big-endian od začátku 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;
}
Následující příklad:
- Vyhledá první nový řádek (
\r\n
) v saděReadOnlySequence<byte>
a vrátí ho prostřednictvím parametru out 'line'. - Oříznou tento řádek s výjimkou
\r\n
vstupní vyrovnávací paměti.
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;
}
Je platné ukládat prázdné segmenty uvnitř objektu ReadOnlySequence<T>
. Při explicitní výčtu segmentů může dojít k prázdným segmentům:
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;
}
}
Předchozí kód vytvoří ReadOnlySequence<byte>
s prázdnými segmenty a ukáže, jak tyto prázdné segmenty ovlivňují různá rozhraní API:
ReadOnlySequence<T>.Slice
SequencePosition
odkazující na prázdný segment zachová tento segment.ReadOnlySequence<T>.Slice
funkce int přeskočí prázdné segmenty.- Výčet výčtů
ReadOnlySequence<T>
vytvoří výčty prázdných segmentů.
Při práci s vsReadOnlySequence<T>
SequencePosition
/. normálnímReadOnlyMemory<T>
/int
ReadOnlySpan<T>
/T[]
/ výsledkem je několik neobvyklých výsledků:
SequencePosition
je značka pozice pro konkrétníReadOnlySequence<T>
, nikoli absolutní pozici. Vzhledem k tomu, že je relativní ke konkrétnímuReadOnlySequence<T>
, nemá význam, pokud se používá mimoReadOnlySequence<T>
místo, kde pochází.- Aritmetika nemůže být provedena
SequencePosition
bezReadOnlySequence<T>
. To znamená, že dělat základní věci, jakoposition++
je napsánposition = ReadOnlySequence<T>.GetPosition(1, position)
. GetPosition(long)
nepodporuje záporné indexy. To znamená, že není možné získat druhý až poslední znak bez chůze všech segmentů.- Dva
SequencePosition
se nedají porovnat, což ztěžuje:- Zjistěte, jestli je jedna pozice větší nebo menší než jiná pozice.
- Napište několik algoritmů analýzy.
ReadOnlySequence<T>
je větší než odkaz na objekt a měl by být předán v nebo ref, pokud je to možné. PředánímReadOnlySequence<T>
neboref
zmenšenímin
kopií struktury.- Prázdné segmenty:
- Jsou platné v rámci .
ReadOnlySequence<T>
- Může se zobrazit při iteraci pomocí
ReadOnlySequence<T>.TryGet
metody. - Může se zobrazit řez sekvence pomocí
ReadOnlySequence<T>.Slice()
metody sSequencePosition
objekty.
- Jsou platné v rámci .
- Je nový typ, který byl zaveden v .NET Core 3.0 pro zjednodušení zpracování
ReadOnlySequence<T>
. - Sjednocuje rozdíly mezi jedním segmentem
ReadOnlySequence<T>
a více segmentyReadOnlySequence<T>
. - Poskytuje pomocné rutiny pro čtení binárních a textových dat (
byte
achar
) , které mohou nebo nemusí být rozděleny mezi segmenty.
Existují integrované metody pro zpracování binárních i oddělených dat. Následující část ukazuje, jak tyto stejné metody vypadají pomocí SequenceReader<T>
:
SequenceReader<T>
obsahuje metody pro vytvoření výčtu dat uvnitř ReadOnlySequence<T>
přímo. Následující kód je příkladem zpracování ReadOnlySequence<byte>
byte
najednou:
while (reader.TryRead(out byte b))
{
Process(b);
}
Zveřejňuje CurrentSpan
aktuální segment Span
, který je podobný tomu, co bylo provedeno v metodě ručně.
Následující kód je příkladem implementace FindIndexOf
použití 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;
}
Následující příklad analyzuje celočíselnou délku 4bajtů big-endian od začátku 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;
}
- Protože
SequenceReader<T>
je proměnlivá struktura, měla by být vždy předána odkazem. SequenceReader<T>
je ref struktura, takže ji lze použít pouze v synchronních metodách a nelze ji uložit do polí. Další informace najdete v tématu Vyhněte se přidělování.SequenceReader<T>
je optimalizovaná pro použití jako čtečka určená jen pro předávání.Rewind
je určený pro malé zálohy, které nelze řešit pomocí jinýchRead
Peek
rozhraní API aIsNext
rozhraní API.
Zpětná vazba k produktu .NET
.NET je open source projekt. Vyberte odkaz pro poskytnutí zpětné vazby: