Entrainement
Module
Apprenez à créer des variables de tableau et à effectuer une itération à travers des éléments du tableau.
Ce navigateur n’est plus pris en charge.
Effectuez une mise à niveau vers Microsoft Edge pour tirer parti des dernières fonctionnalités, des mises à jour de sécurité et du support technique.
Cet article fournit une vue d’ensemble des types qui facilitent la lecture des données qui se trouvent dans plusieurs mémoires tampons. Ils sont principalement utilisés pour prendre en charge des objets PipeReader.
System.Buffers.IBufferWriter<T> est un contrat pour l’écriture en mémoire tampon synchrone. Au niveau le plus bas, l’interface :
Memory<T>
ou le Span<T>
est accessible en écriture et vous pouvez déterminer le nombre d’éléments T
qui y ont été écrits.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);
}
La méthode précédente :
IBufferWriter<byte>
en utilisant GetSpan(5)
.Span<byte>
retourné.Cette méthode d’écriture utilise la mémoire tampon Memory<T>
/Span<T>
fournie par le IBufferWriter<T>
. Vous pouvez aussi utiliser la méthode d’extension Write pour copier une mémoire tampon existante dans le IBufferWriter<T>
. Write
effectue le travail consistant à appeler GetSpan
/Advance
de façon appropriée : il n’est donc pas nécessaire d’appeler Advance
après l’écriture :
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> est une implémentation de IBufferWriter<T>
qui utilise comme magasin un seul tableau contigu.
GetSpan
et GetMemory
retournent une mémoire tampon avec au moins la quantité de mémoire demandée. Les tailles des mémoires tampons ne seront pas nécessairement exactes.Advance
pour continuer à écrire d’autres données. Vous ne pouvez pas écrire dans une mémoire tampon précédemment acquise après avoir appelé Advance
.ReadOnlySequence<T> est un struct qui peut représenter une séquence contiguë ou non contiguë de T
. Il peut être construit à partir de :
T[]
ReadOnlyMemory<T>
La troisième représentation est la plus intéressante, car elle a des implications en termes de performances sur les différentes opérations effectuées sur la ReadOnlySequence<T>
:
Représentation | Opération | Complexité |
---|---|---|
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) |
En raison de cette représentation mixte, la ReadOnlySequence<T>
expose les index sous la forme d’une SequencePosition
au lieu d’un entier. Une SequencePosition
:
ReadOnlySequence<T>
dont elle provient.ReadOnlySequence<T>
.La ReadOnlySequence<T>
expose les données sous la forme d’un énumérable de ReadOnlyMemory<T>
. L’énumération de chacun des segments peut être effectuée en utilisant une instruction foreach de base :
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;
}
La méthode précédente recherche un octet spécifique dans chaque segment. Si vous devez effectuer le suivi de la SequencePosition
de chaque segment, ReadOnlySequence<T>.TryGet est plus approprié. L’exemple suivant est le code précédent modifié pour retourner une SequencePosition
au lieu d’un entier. Retourner une SequencePosition
a l’avantage de permettre à l’appelant d’éviter de devoir parcourir une deuxième fois la séquence pour obtenir les données à un index spécifique.
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 combinaison de SequencePosition
et de TryGet
agit comme un énumérateur. Le champ de position est modifié au début de chaque itération de façon à refléter le début de chaque segment dans la ReadOnlySequence<T>
.
La méthode précédente existe en tant que méthode d’extension sur ReadOnlySequence<T>
. PositionOf peut être utilisé pour simplifier le code précédent :
SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data) => buffer.PositionOf(data);
Le traitement d’une ReadOnlySequence<T>
peut être problématique, car les données peuvent être fractionnées sur plusieurs segments au sein de la séquence. Pour de meilleures performances, divisez le code en deux chemins :
Quelques approches différentes peuvent être utilisées pour traiter des données dans des séquences sur plusieurs segments :
SequenceReader<T>
.SequencePosition
et de l’index dans le segment analysé. Ceci évite des allocations non nécessaires, mais peut être inefficace, en particulier pour les mémoires tampons de petite taille.ReadOnlySequence<T>
dans un tableau contigu et le traiter comme une même mémoire tampon : ReadOnlySequence<T>
est petite, il peut être raisonnable de copier les données dans une mémoire tampon allouée par la pile en utilisant l’opérateur stackalloc.ReadOnlySequence<T>
dans un tableau mis en pool en utilisant ArrayPool<T>.Shared.ReadOnlySequence<T>.ToArray()
. Ceci n’est pas recommandé dans les chemins très sollicités, car cela alloue un nouveau T[]
sur le tas.Les exemples suivants illustrent quelques cas courants de traitement de ReadOnlySequence<byte>
:
L’exemple suivant analyse la longueur d’un entier Big Endian sur 4 octets à partir du début de la 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;
}
L’exemple suivant :
\r\n
) dans la ReadOnlySequence<byte>
et le retourne via le paramètre de sortie « line ».\r\n
de la mémoire tampon d’entrée.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;
}
Stocker des segments vides à l’intérieur d’une ReadOnlySequence<T>
est une action valide. Des segments vides peuvent se produire lors de l’énumération explicite de segments :
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;
}
}
Le code précédent crée une ReadOnlySequence<byte>
avec des segments vides et montre comment ils affectent les différentes API :
ReadOnlySequence<T>.Slice
avec une SequencePosition
pointant vers un segment vide conserve ce segment.ReadOnlySequence<T>.Slice
avec un entier (int) passe les segments vides.ReadOnlySequence<T>
énumère les segments vides.Plusieurs résultats inhabituels peuvent se produire lors de l’utilisation d’une ReadOnlySequence<T>
/SequencePosition
par rapport à un ReadOnlySpan<T>
/ReadOnlyMemory<T>
/T[]
/int
normal :
SequencePosition
est un marqueur de position pour une ReadOnlySequence<T>
spécifique, et non pas une position absolue. Étant donné qu’il est relatif à une ReadOnlySequence<T>
spécifique, il n’a pas de signification s’il est utilisé en dehors de la ReadOnlySequence<T>
dont il provient.SequencePosition
sans la ReadOnlySequence<T>
. Cela signifie que faire des opérations de base comme position++
doit être écrit comme ceci : position = ReadOnlySequence<T>.GetPosition(1, position)
.GetPosition(long)
ne prend pas en charge les index négatifs. Cela signifie qu’il est impossible d’obtenir l’avant-dernier caractère sans parcourir tous les segments.SequencePosition
, ce qui rend difficile : ReadOnlySequence<T>
est plus grande qu’une référence d’objet, et doit être passée via in ou ref là où c’est possible. Passer ReadOnlySequence<T>
via in
ou ref
réduit les copies du struct.ReadOnlySequence<T>
.ReadOnlySequence<T>.TryGet
.ReadOnlySequence<T>.Slice()
avec des objets SequencePosition
.ReadOnlySequence<T>
.ReadOnlySequence<T>
avec un seul segment et une ReadOnlySequence<T>
avec plusieurs segments.byte
et char
) qui peuvent ou non être fractionnées sur plusieurs segments.Il existe des méthodes intégrées pour traiter à la fois les données binaires et les données délimitées. La section suivante montre à quoi ressemblent ces mêmes méthodes avec le SequenceReader<T>
:
SequenceReader<T>
a des méthodes pour énumérer des données directement à l’intérieur de la ReadOnlySequence<T>
. Le code suivant est un exemple de traitement d’une ReadOnlySequence<byte>
un byte
à la fois :
while (reader.TryRead(out byte b))
{
Process(b);
}
CurrentSpan
expose le Span
du segment actif, ce qui est similaire à ce qui était effectué manuellement dans la méthode.
Le code suivant est un exemple d’implémentation de FindIndexOf
en utilisant le 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;
}
L’exemple suivant analyse la longueur d’un entier Big Endian sur 4 octets à partir du début de la 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>
est un struct mutable, il doit toujours être passé par référence.SequenceReader<T>
est un ref struct : il peut être donc être utilisé seulement dans des méthodes synchrones et ne peut pas être stocké dans des champs. Pour plus d’informations, consultez Éviter les allocations.SequenceReader<T>
est optimisé pour une utilisation en tant que lecteur vers l’avant uniquement. Rewind
est destiné aux petites sauvegardes qui ne peuvent pas être traitées en utilisant d’autres API Read
, Peek
et IsNext
.Commentaires sur .NET
.NET est un projet open source. Sélectionnez un lien pour fournir des commentaires :
Entrainement
Module
Apprenez à créer des variables de tableau et à effectuer une itération à travers des éléments du tableau.