. net: эффективный способ чтения двоичного файла в память и последующего доступа - PullRequest
0 голосов
/ 09 мая 2020

Я начинающий программист. Я создаю библиотеку для обработки двоичных файлов определенного типа - например, кода c (хотя без необходимости обрабатывать прогрессивный поток, поступающий по проводам). Я ищу эффективный способ чтения файла в память и последующего анализа частей данных по мере необходимости. В частности, я хотел бы избежать больших копий памяти, надеюсь, без особой дополнительной сложности, чтобы избежать этого.

В некоторых ситуациях я хочу выполнять последовательное чтение значений в данных. Для этого хорошо работает MemoryStream.

    FileStream fs = new FileStream(_fileName, FileMode.Open, FileAccess.Read);
    byte[] bytes = new byte[fs.Length];
    fs.Read(bytes, 0, bytes.Length);
    _ms = new MemoryStream(bytes, 0, bytes.Length, false, true);
    fs.Close();

(Это включало копию из массива байтов в поток памяти; это один раз, и я не знаю способа избежать этого.)

С потоком памяти легко найти произвольные позиции, а затем начать чтение элементов структуры. Например,

    _ms.Seek(_tableRecord.Offset, SeekOrigin.Begin);
    byte[] ab32 = new byte[4];

    _version = ConvertToUint(_ms.Read(ab32));
    _numRecords = ConvertToUint(_ms.Read(ab32));
    // etc.

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

Вместо MemoryStream я мог бы сохранить данные в памяти, используя Memory. Это поддерживает нарезку, но не последовательное чтение.

Если в какой-то ситуации я хочу получить фрагмент (а не передавать поток и смещение / длину), я мог бы построить ArraySegment из MemoryStream.GetBuffer.

    ArraySegment<byte> as = new ArraySegment<byte>(ms.GetBuffer(), offset, length);

Мне, однако, не ясно, приведет ли это к (потенциально большой) копии, или если при этом используется ссылка на ту же самую память, содержащуюся в MemoryStream. Я понимаю, что GetBuffer предоставляет доступ к базовой памяти, а не предоставляет копию; и этот ArraySegment будет указывать на ту же самую память?

Будут моменты, когда мне нужно будет получить фрагмент, который является копией, так как мне нужно будет изменить некоторые элементы, а затем обработать это , но без изменения оригинала. Если ArraySegment получает ссылку, а не копию, я полагаю, что мог бы использовать ArraySegment<byte>.ToArray()?

Итак, мои вопросы: является ли MemoryStream лучшим подходом? Есть ли какой-либо другой тип, который позволяет последовательное чтение, например MemoryStream, но также позволяет нарезать, например Memory?

Если мне нужен фрагмент без копирования памяти, ArraySegment<byte>(ms.GetBuffer(), offset, length) сделает это?

Тогда, если мне нужно копию, которую можно изменить, не затрагивая оригинал, используйте ArraySegment<byte>.ToArray()?

Есть ли способ прочитать данные из файла непосредственно в новый MemoryStream без создания временного массива байтов, который копируется?

Лучше ли я к этому подхожу?

Ответы [ 2 ]

0 голосов
/ 11 мая 2020

Чтобы получить начальный MemoryStream от чтения файла, работает следующее:

    byte[] bytes;
    try
    {
        // File.ReadAllBytes opens a filestream and then ensures it is closed
        bytes = File.ReadAllBytes(_fi.FullName); 
        _ms = new MemoryStream(bytes, 0, bytes.Length, false, true);
    }
    catch (IOException e)
    {
        throw e;
    }

File.ReadAllBytes() копирует содержимое файла в память. Он использует using, что означает, что он обеспечивает закрытие файла. Таким образом, оператор Finally не требуется.

Я могу читать отдельные значения из MemoryStream, используя MemoryStream.Read. Эти вызовы включают копии этих значений, и это нормально.

В одной ситуации мне нужно было прочитать таблицу из файла, изменить значение, а затем вычислить контрольную сумму всего файла с этим изменением в место. Вместо копирования всего файла, чтобы я мог редактировать одну часть, я смог вычислить контрольную сумму поэтапно: сначала на начальном неизмененном сегменте файла, затем продолжить со средним сегментом, который был изменен, затем продолжить с остаток.

Для этого я мог обработать первый и последний сегменты, используя MemoryStream. Это потребовало множества чтений с копированием каждого чтения; но эти копии были временными переменными, поэтому значительного увеличения рабочего набора не было.

Для среднего сегмента это нужно было скопировать, так как его нужно было изменить (но исходную версию нужно было сохранить нетронутой). Сработало следующее:

    // get ref (not copy!) to the byte array underlying the MemoryStream
    byte[] fileData = _ms.GetBuffer();

    // determine the required length
    int length = _tableRecord.Length;

    // create array to hold the copy
    byte[] segmentCopy = new byte[length];

    // get the copy
    Array.ConstrainedCopy(fileData, _tableRecord.Offset, segmentCopy, 0, length);

После изменения значений в segmentCopy мне затем нужно было передать это моему методу stati c для вычисления контрольных сумм, который ожидал MemoryStream (для последовательного чтения). Это сработало:

    // new MemoryStream will hold a ref to the segmentCopy array (no new copy!)
    MemoryStream ms = new MemoryStream(segmentCopy, 0, segmentCopy.Length);

Что мне еще не нужно было делать, но я хочу сделать, так это получить фрагмент MemoryStream, не связанный с копированием. Это работает:

    MemoryStream sliceFromMS = new MemoryStream(fileData, offset, length);

Сверху, fileData было ссылкой на массив, лежащий в основе исходного MemoryStream. Теперь sliceFromMS будет иметь ссылку на сегмент в том же массиве.

0 голосов
/ 10 мая 2020

Можно использовать FileStream.Seek, насколько я понимаю, нет необходимости загружать данные в память, а затем использовать этот метод MemoryStream

В следующем примере str1 и str2 равны:

using (var fs = new FileStream(@"C:\Users\bar_v\OneDrive\Desktop\js_balancer.txt", FileMode.Open))
{
    var buffer = new byte[20];
    fs.Read(buffer, 0, 20);
    var str1= Encoding.ASCII.GetString(buffer);
    fs.Seek(0, SeekOrigin.Begin);
    fs.Read(buffer, 0, 20);
    var str2 = Encoding.ASCII.GetString(buffer);
}

Кстати, когда вы создаете новый объект MemoryStream, вы не копируете массив байтов, вы просто сохраняете ссылку на него:

public MemoryStream(byte[] buffer, bool writable)
{
    if (buffer == null)
        throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);

    _buffer = buffer;
    _length = _capacity = buffer.Length;
    _writable = writable;
    _exposable = false;
    _origin = 0;
    _isOpen = true;
}

Но при чтении как видим, происходит копирование:

public override int Read(byte[] buffer, int offset, int count)
{
    if (buffer == null)
        throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
    if (offset < 0)
        throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
    if (count < 0)
        throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
    if (buffer.Length - offset < count)
        throw new ArgumentException(SR.Argument_InvalidOffLen);

    EnsureNotClosed();

    int n = _length - _position;
    if (n > count)
        n = count;
    if (n <= 0)
        return 0;

    Debug.Assert(_position + n >= 0, "_position + n >= 0");  // len is less than 2^31 -1.

    if (n <= 8)
    {
        int byteCount = n;
        while (--byteCount >= 0)
            buffer[offset + byteCount] = _buffer[_position + byteCount];
    }
    else
        Buffer.BlockCopy(_buffer, _position, buffer, offset, n);
    _position += n;

    return n;
}
...