Память C # в TextReader

Каков оптимальный способ получить TextReader экземпляр из Memory<byte> объекта?

Я мог бы написать что-то вроде:

using (var stream = new MemoryStream(body.ToArray()))
using (var reader = new StreamReader(stream))
{
}

а может есть способ получше?


person obratim    schedule 12.08.2020    source источник
comment
Интересно; не то, чтобы я знал, но такой Stream должен быть простым в реализации.   -  person Klaus Gütter    schedule 12.08.2020


Ответы (1)


StreamReader автоматически удалит базовый Stream.

# 1 Самый простой способ

Memory<byte> memory = GetSomeData();
using TextReader reader = new StreamReader(new MemoryStream(memory.ToArray()));
// some code

Но здесь вы копируете все содержимое памяти в другой массив, это потребляет много памяти и дает сборщику мусора больше работы. Не рекомендуется, особенно если массив содержит большой объем данных.

Есть другой способ сделать это без выделения нового массива.

# 2 Оптимальный способ (рекомендуется для экономии памяти)

Memory<byte> memory = GetSomeData();
if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
{
    using TextReader reader = new StreamReader(new MemoryStream(segment.Array));
    // some code
}

Другими словами, ArraySegment возвращает исходную область памяти в виде массива.

Тесты

Вот пример для игры (на основе консольного приложения .NET Core 3.1).

class Program
{
    static void Main(string[] args)
    {
        string text = "Hello World!";
        byte[] data = Encoding.UTF8.GetBytes(text);
        Memory<byte> memory = data;

        byte[] data1 = memory.ToArray();
        Console.WriteLine("data == data1: {0}", data == data1);
            
        if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
        {
            byte[] data2 = segment.Array;
            Console.WriteLine("data == data2: {0}", data == data2);
        }

        Console.WriteLine();

        Console.WriteLine("Test 1");
        Test1(text);

        Console.WriteLine();

        Console.WriteLine("Test 2");
        Test2(text);

        Console.ReadKey();
    }

    private static void Test1(string text)
    {
        Memory<byte> memory = Encoding.UTF8.GetBytes(text);
        byte[] data = memory.ToArray();
        ReadItTwice(memory, data);
    }

    private static void Test2(string text)
    {
        Memory<byte> memory = Encoding.UTF8.GetBytes(text);
        if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
        {
            byte[] data = segment.Array;
            ReadItTwice(memory, data);
        }
    }

    private static void ReadItTwice(Memory<byte> memory, byte[] data)
    {
        using MemoryStream ms = new MemoryStream(data);
        using TextReader sr = new StreamReader(ms);
        Console.WriteLine("Before change: {0}", sr.ReadToEnd());
        if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
            segment.Array[0] = (byte)'_'; // change first symbol
        ms.Position = 0;
        Console.WriteLine("After change: {0}", sr.ReadToEnd());
    }
}

Выход

data == data1: False
data == data2: True

Test 1
Before change: Hello World!
After change: Hello World!

Test 2
Before change: Hello World!
After change: _ello World!
person aepot    schedule 12.08.2020
comment
@obratim клон массива, поскольку диапазон памяти выполняется DMA, и это очень быстро и не задействует CPU в этой операции (сюрприз!). Но клонирование массива - это выделение дополнительной памяти, которая позже должна быть собрана сборщиком мусора. Операции сборки мусора обходятся дорого, особенно если длина каждого массива ›85кб. Вы сравниваете скорость между методом копирования данных и без копирования. Это проблема с памятью, а не с процессором. Например, если вам нужно выполнить эту операцию 100 тыс. Раз с массивом 100 тыс. В секунду, что вы предпочтете: 100k x 100k = 10MB дополнительное выделение памяти в секунду или ноль? - person aepot; 13.08.2020
comment
Я провел правильный бенчмаркинг: похоже, что метод MemoryMarshal медленнее, но выделяет меньше памяти. эталонный тест: a hrefilez/file/ results: : //mega.nz/file/04hTVA4K#R3ahehgUoXp3IOAHNdBi-T5tfNDUc0Nb6LvoSMFc7MY "rel =" nofollow noreferrer "> mega.nz/file/ - person obratim; 13.08.2020
comment
как вы объяснили, тест не измеряет время для сборки мусора, поэтому способ MemoryMarshal кажется медленнее - person obratim; 13.08.2020
comment
@obratim Одно замечание: обратите внимание на segment.Offset и segment.Length из ArraySerment. MemoryMarshal возвращает весь массив как segment.Array, использованный для создания Memory<T>. Таким образом, segment.Array.Length и segment.Length могут быть разными. Просто совет. - person aepot; 13.08.2020