Как считать файл в массив байт
Перед изучением данной темы рекомендуется ознакомиться со следующими темами:
Содержание
- 4.1. Методы типа Read…() для чтения данных примитивных типов ( int , bool , double , …)
- 4.1.1. Метод ReadBoolean()
- 4.1.2. Методы ReadByte() и ReadBytes()
- 4.1.3. Методы ReadChar() и ReadChars(int)
- 4.1.4. Методы чтения данных с плавающей запятой ReadDecimal() , ReadDouble() , ReadSingle()
- 4.1.5. Методы ReadInt16() , ReadInt32() , ReadInt64() чтения данных типа int
- 4.1.6. Метод ReadSByte() . Чтение данных типа sbyte
- 4.1.7. Метод ReadString() . Чтение строки типа string
- 4.1.8. Методы считывания данных беззнакового целого типа
Поиск на других ресурсах:
1. Классы BinaryReader и BinaryWriter . Назначение. Общая информация
Классы BinaryReader и BinaryWriter предназначены соответственно для чтения и записи данных в двоичном формате.
Для чтения данных примитивных типов и строк из потока используется класс BinaryReader . При чтении можно указать необходимую кодировку. По умолчанию установлена кодировку UTF-8.
Класс BinaryWriter используется для записи в поток стандартных типов данных и строк в двоичном формате. Существует возможность указания кодировки (по умолчанию UTF-8).
Классы реализованы в пространстве имен System.IO . Для того, чтобы использовать возможности классов BinaryReader , BinaryWriter нужно подключить System.IO
2. Взаимодействие класса BinaryReader с потоками опорных хранилищ. Использование класса FileStream
Как известно, классы BinaryReader и BinaryWriter относятся к адаптерам потоков. Непосредственно взаимодействовать с различными видами хранилищ классы не могут. Для того, чтобы подойти к файловому, сетевому или иному виду хранилища нужно:
- создать экземпляр соответствующего класса как потока. Это может быть экземпляр одного из четырех классов FileStream , IsolatedStorageStream , MemoryStream или NetworkStream ;
- связать созданный экземпляр с экземпляром класса BinaryReader или BinaryWriter .
Например. Для того, чтобы прочитать информацию из файла или записать ее в файл, нужно создать экземпляр потока FileStream (рисунок 1).
Рисунок 1. Взаимодействие класса BinaryReader с файлом с помощью класса FileStream
3. Конструкторы класса BinaryReader . Создание экземпляра
Класс BinaryReader имеет несколько конструкторов, наиболее используемые из которых следующие:
- input – ссылка на абстрактный базовый класс Stream ;
- encoding – система кодировки (Unicode, UTF32, UTF8 и другая). По умолчанию устанавливается система кодировки UTF8.
Пример. В примере продемонстрировано создание экземпляра класса BinaryReader с кодировкой по умолчанию.
4. Класс BinaryReader . Обзор основных методов
Для считывания данных примитивных (стандартных) типов ( int , double , char и других) в двоичном формате используется ряд методов. Предварительно эти данные должны быть записаны методом Write() .
Ниже дан перечень этих методов.
4.1.1. Метод ReadBoolean()
Общая форма метода
Метод считывает значение типа bool из текущего потока и сдвигает текущую позицию на один байт.
Пример. Считывание из файла значения логического типа bool . Фрагмент кода предполагает, что это значение предварительно было записано методом Write() .
4.1.2. Методы ReadByte() и ReadBytes()
Метод ReadByte() считывает из потока один байт. Метод ReadBytes() считывает из потока несколько байт. Общая форма методов
здесь count – количество считываемых байт.
Пример. Приводится пример считывания данных методами ReadByte() и ReadBytes() .
4.1.3. Методы ReadChar() и ReadChars(int)
Методы используются для считывания одиночного символа типа char или нескольких символов типа char . Общая форма методов следующая:
здесь count – количество символов, которые нужно прочитать из потока.
Пример.
4.1.4. Методы чтения данных с плавающей запятой ReadDecimal() , ReadDouble() , ReadSingle()
Для чтения данных с алавающей запятой используются методы ReadDecimal() , ReadDouble() , ReadSingle() , которые считывают данные типов decimal , double и float соответственно. Общая форма методов следующая:
Пример. В примере считывается массив вещественных чисел из файла.
4.1.5. Методы ReadInt16() , ReadInt32() , ReadInt64() чтения данных типа int
Для чтения данных типа int используются следующие методы:
Общая форма методов следующая
Пример.
4.1.6. Метод ReadSByte() . Чтение данных типа sbyte
Метод ReadSByte() считывает данные типа sbyte . Общая форма метода:
Пример.
4.1.7. Метод ReadString() . Чтение строки типа string
Для чтения одной строки из текущего потока используется метод ReadString() , который имеет следующую общую форму:
Пример.
4.1.8. Методы считывания данных беззнакового целого типа
Для считывания беззнаковых целых типов используются методы ReadUint16() , ReadUint32() , ReadUint64() . Общая форма методов
Пример.
4.2. Метод Read()
Метод Read() имеет несколько перегруженных реализаций. Ниже приведена общая форма наиболее употребляемых реализаций метода. Первая реализация метода не содержит параметров
При такой реализации считываются символы из базового потока и перемещается текущая позиция потока вперед в соответствии с используемым кодированием. Метод возвращает следующий символ из потока ввода.
считывают соответственно определенное число байт или символов из потока, начиная с определенного потока в массиве байт.
Пример.
В примере демонстрируется использование метода Read() , который считывает массив байт типа byte[] . Сначала в файл записывается массив типа double[] . Затем этот массив считывается методом Read(byte[], int, int) .
Пример также демонстрирует возможности класса BitConverter , который предназначен для преобразования различных типов в тип byte[] и, наоборот. Демонстрируется конвертирование чисел массива double[] в массив byte[] .
Текст программы следующий.
Результат работы программы
4.3. Метод PeekChar()
Метод PeekChar() получает следующий имеющийся символ без прироста позиции байта или символа
Если невозможно прочитать символ (например, конец файла), то метод возвращает -1.
Пример. В примере в файл записывается строка, затем эта строка считывается посимвольно. Для определения того, можно ли прочитать символ, используется метод PeekChar() .
Файл, содержащий бинарные данные, называется двоичным (бинарным) файлом. Любые форматированные и неформатированные бинарные данные хранятся в бинарных файлах, нечитабельных для человека и использующихся компьютером напрямую.
Когда бинарный файл требуется просмотреть или переместить, содержимое файла переводится в формат, понятный человеку. Бинарный файл имеет расширение .bin. Прочитать его можно с помощью встроенной функции или модуля. В этом уроке мы разберём различные способы чтения бинарных файлов с помощью Python.
Подготовка
Перед тем, как начать урок, желательно создать один или несколько бинарных файлов, чтобы воспользоваться скриптом из примера. Ниже представлены два скрипта на Python, которые создадут два бинарника. Файл binary1.py создаёт string.bin, содержащий строковые данные, а binary2.py – number_list.bin со списком из числовых данных.
Binary1.py
Binary2.py
Считываем бинарный файл со строковыми данными в массив байтов
В Python существует множество способов прочитать бинарный файл. Можно прочитать определённое количество байтов или весь файл сразу.
Результат
После выполнения скрипта мы получим следующий результат.
Считываем бинарный файл со строковыми данными в массив
Следующий скрипт поможет нам прочитать бинарник number_list.bin, созданный нами ранее.
Бинарный файл содержит список с числовыми данными. Как и в предыдущем примере, функция open() открывает файл и читает из него данные. Затем из бинарника читаются первые 5 чисел и перед выводом объединяются в список.
Результат
После выполнения скрипта мы получим следующий результат. Бинарный файл содержит 7 чисел, первые 5 вывелись на консоль.
Читаем бинарный файл с помощью NumPy
В этой части мы поговорим о том, как создать бинарный файл и прочитать его с помощью массивов NumPy. Перед началом работы необходимо установить модуль NumPy командой в терминале или через ваш редактор Python, в котором вы будете писать программу.
Функция tofile() создаёт текстовый или бинарный файл, а fromfile() считывает данные из файла и создаёт массив.
Синтаксис tofile()
Первый аргумент обязательный – он принимает имя файла, путь или строку. Файл создастся, только если будет указан первый аргумент. Второй аргумент – необязательный, он используется для разделения элементов массива. Третий аргумент также необязателен, он отвечает за форматированный вывод содержимого файла.
Синтаксис fromfile()
Первый аргумент обязательный – он принимает имя файла, путь или строку. Содержимое файла будет прочитано, только если вы укажете имя файла. dtype определяет тип данных в возвращаемом массиве. Count задаёт число элементов массива. Sep – для разделения элементов текста или массива. Offset определяет позицию в файле, с которой начинается считывание. Последний аргумент нужен, чтобы создать массив, не являющийся массивом NumPy.
Напишем следующий код, чтобы создать бинарный файл с помощью массива NumPy, прочитать его и вывести содержимое.
Результат
После выполнения скрипта мы увидим следующий результат.
Заключение
Мы рассмотрели 3 разных способа чтения бинарных файлов. В первом примере мы получили содержимое файла в виде массива байтов, во втором и третьем – в виде списка.
В 18 уровне начались первые задачи побайтного чтения файлов: прочитать файл, далее найти минимальные/максимальные байты или вывести в упорядоченном виде и т.п.
- Ввести с консоли имя файла
- Считать все байты из файла.
- Не учитывая повторений - отсортировать их по байт-коду в убывающем порядке.
- Вывести на экран
- Закрыть поток ввода-вывода
Решаем в лоб:
Решает все замечательно! Тест (если бы был — прошелся бы на ура). Но в жизни мало файлов содержащих только строчку "Мама мыла раму". Давайте скормим нашей программе файл в 46Мб (по нынешним меркам вроде и не особо много). Что такое, программа выполняется 220 секунд. Попытка скормить с вечера 1Gb файл (размер MPEG4 фильма не в самом лучшем качестве) не увенчалась успехом. Программа утром все еще читала - а мне идти на работу уже. В чем проблема? Наверное в использовании ArrayList<Integer> у которого внутри 1 миллиард элементов. Каждый элемент его занимает 16 байт минимум (Заголовок: 8 байт + Поле int: 4 байта + Выравнивание для кратности 8: 4 байта). Итого мы добровольно загоняем в память 16 Gb данных при размере оперативы в 8. Будем делать лучше. Нырнем в коллекции глубже. И ура, нашлось то, что нам нужно.
Встречаем TreeSet
- не допускает хранение двух одинаковых элементов (а значит мы будем хранить в памяти все 255 элементов, вместо миллиарда!)
- при манипуляциях со своими элементами автоматом упорядочивает (само сортирует - вот он, верх совершенства!)
Массив — побайтно
Имеем на выходе: 46Мб файл 158 секунд. 1Gb файл - 2 часа 55 минут. Опять улучшение, но небольшое. И мы сделали все простыми инструментами. Не использовали микроскоп для забивания гвоздей. Теперь лирическое отступление. Вспомним устройство компьютера. Память ОЗУ (DRAM) где обычно выполняется программа и хранятся переменные имеет высокую скорость доступа, но небольшой размер. Память на жестком/flash диске (HDD или Flash-накопители) где обычно хранятся файлы, наоборот имеет низкую скорость доступа, но большой размер. Так что когда мы побайтно читаем 1Gb файл (то есть миллиард раз обращаемся к HDD) - мы тратим много времени на работу с низкоскоростным устройством (по песчинке перекладываем песок с кузова КамАЗа в песочницу). Попробуем еще улучшить.
Т екстовые файлы хранят данные в виде текста (sic!). Это значит, что если, например, мы записываем целое число 12345678 в файл, то записывается 8 символов, а это 8 байт данных, несмотря на то, что число помещается в целый тип. Кроме того, вывод и ввод данных является форматированным, то есть каждый раз, когда мы считываем число из файла или записываем в файл происходит трансформация числа в строку или обратно. Это затратные операции, которых можно избежать.
Текстовые файлы позволяют хранить информацию в виде, понятном для человека. Можно, однако, хранить данные непосредственно в бинарном виде. Для этих целей используются бинарные файлы.
Выполните программу и посмотрите содержимое файла output.bin. Число, которое ввёл пользователь записывается в файл непосредственно в бинарном виде. Можете открыть файл в любом редакторе, поддерживающем представление в шестнадцатеричном виде (Total Commander, Far) и убедиться в этом.
Запись в файл осуществляется с помощью функции
Функция возвращает число удачно записанных элементов. В качестве аргументов принимает указатель на массив, размер одного элемента, число элементов и указатель на файловый поток. Вместо массив, конечно, может быть передан любой объект.
Запись в бинарный файл объекта похожа на его отображение: берутся данные из оперативной памяти и пишутся как есть. Для считывания используется функция fread
Функция возвращает число удачно прочитанных элементов, которые помещаются по адресу ptr. Всего считывается count элементов по size байт. Давайте теперь считаем наше число обратно в переменную.
fseek
Одной из важных функций для работы с бинарными файлами является функция fseek
Эта функция устанавливает указатель позиции, ассоциированный с потоком, на новое положение. Индикатор позиции указывает, на каком месте в файле мы остановились. Когда мы открываем файл, позиция равна 0. Каждый раз, записывая байт данных, указатель позиции сдвигается на единицу вперёд.
fseek принимает в качестве аргументов указатель на поток и сдвиг в offset байт относительно origin. origin может принимать три значения- SEEK_SET - начало файла
- SEEK_CUR - текущее положение файла
- SEEK_END - конец файла. К сожалению, стандартом не определено, что такое конец файла, поэтому полагаться на эту функцию нельзя.
В случае удачной работы функция возвращает 0.
Дополним наш старый пример: запишем число, затем сдвинемся указатель на начало файла и прочитаем его.
Вместо этого можно также использовать функцию rewind, которая перемещает индикатор позиции в начало.
В си определён специальный тип fpos_t, который используется для хранения позиции индикатора позиции в файле.
Функцияиспользуется для того, чтобы назначить переменной pos текущее положение. Функция
используется для перевода указателя в позицию, которая хранится в переменной pos. Обе функции в случае удачного завершения возвращают ноль.
возвращает текущее положение индикатора относительно начала файла. Для бинарных файлов - это число байт, для текстовых не определено (если текстовый файл состоит из однобайтовых символов, то также число байт).
Рассмотрим пример: пользователь вводит числа. Первые 4 байта файла: целое, которое обозначает, сколько чисел было введено. После того, как пользователь прекращает вводить числа, мы перемещаемся в начало файла и записываем туда число введённых элементов.
Вторая программа сначала считывает количество записанных чисел, а потом считывает и выводит числа по порядку.
Примеры
1. Имеется бинарный файл размером 10*sizeof(int) байт. Пользователь вводит номер ячейки, после чего в неё записывает число. После каждой операции выводятся все числа. Сначала пытаемся открыть файл в режиме чтения и записи. Если это не удаётся, то пробуем создать файл, если удаётся создать файл, то повторяем попытку открыть файл для чтения и записи.
2. Пишем слова в бинарный файл. Формат такой - сначало число букв, потом само слово без нулевого символа. Ели длина слова равна нулю, то больше слов нет. Сначала запрашиваем слова у пользователя, потом считываем обратно.
3. Задача - считать данные из текстового файла и записать их в бинарный. Для решения зачи создадим функцию обёртку. Она будет принимать имя файла, режим доступа, функцию, которую необходимо выполнить, если файл был удачно открыт и аргументы этой функции. Так как аргументов может быть много и они могут быть разного типа, то их можно передавать в качестве указателя на структуру. После выполнения функции файл закрывается. Таким образом, нет необходимости думать об освобождении ресурсов.
4. Функция saveInt32Array позволяет сохранить массив типа int32_t в файл. Обратная ей loadInt32Array считывает массив обратно. Функция loadInt32Array сначала инициализирует переданный ей массив, поэтому мы должны передавать указатель на указатель; кроме того, она записывает считанный размер массива в переданный параметр size, из-за чего он передаётся как указатель.
5. Создание таблицы поиска. Для ускорения работы программы вместо вычисления функции можно произвести сначала вычисление значений функции на интервале с определённой точностью, после чего брать значения уже из таблицы. Программа сначала производит табулирование функции с заданными параметрами и сохраняет его в файл, затем подгружает предвычисленный массив, который уже используется для определения значений. В этой программе все функции возвращают переменную типа Result, которая хранит номер ошибки. Если функция отработала без проблем, то она возвращает Ok (0).
6. У нас имеются две структуры. Первая PersonKey хранит логин, пароль, id пользователя и поле offset. Вторая структура PersonInfo хранит имя и фамилию пользователя и его возраст. Первые структуры записываются в бинарный файл keys.bin, вторые структуры в бинарный файл values.bin. Поле offset определяет положение соответствующей информации о пользователе во втором файле. Таким образом, получив PersonKey из первого файла, по полю offset можно извлечь из второго файла связанную с данным ключом информацию.
Зачем так делать? Это выгодно в том случае, если структура PersonInfo имеет большой размер. Извлекать массив маленьких структур из файла не накладно, а когда нам понадобится большая структура, её можно извлечь по уже известному адресу в файле.
Всё ещё не понятно? – пиши вопросы на ящикУ меня есть веб-сервер, который будет читать бинарные файлы большого размера (несколько мегабайт) в байтовые массивы. Сервер может одновременно читать несколько файлов (разные запросы страниц), поэтому я ищу наиболее оптимизированный способ сделать это, не слишком обременяя процессор. Является ли приведенный ниже код достаточно хорошим?
просто замените все это на:
однако, если вы обеспокоены потреблением памяти, вы должны не читать весь файл в память сразу все на всех. Вы должны делать это кусками.
Я могу утверждать, что ответ тут вообще "не". Если только ты совершенно все данные сразу, рассмотрите возможность использования Stream -основанный API (или некоторый вариант читателя / итератора). То есть особенно важно, когда у вас есть несколько параллельных операций (как предложено в вопросе), чтобы минимизировать нагрузку на систему и максимизировать пропускную способность.
например, если вы передаете данные вызывающему абоненту:
ваш код может быть отнесен к этому (вместо файла.ReadAllBytes):
обратите внимание на целое число.MaxValue-ограничение размера файла, устанавливаемое методом Read. Другими словами, вы можете прочитать только кусок 2GB сразу.
также обратите внимание, что последним аргументом для FileStream является размер буфера.
Я бы также предложил прочитать о FileStream и BufferedStream.
Как всегда простой пример программы для профилирования что быстрее всего будет наиболее выгодно.
также ваше базовое оборудование будет иметь большое влияние на производительность. Вы используете серверные жесткие диски с большими кэшами и RAID-карту с встроенным кэшем памяти? Или вы используете стандартный диск, подключенный к порту IDE?
в зависимости от частоты операций, размера файлов и количества файлов, которые вы просматриваете, есть и другие проблемы производительности, которые следует учитывать. Одна вещь, которую нужно помнить, заключается в том, что каждый из ваших байтовых массивов будет выпущен на милость сборщика мусора. Если вы не кэшируете какие-либо из этих данных, вы можете в конечном итоге создать много мусора и потерять большую часть своей производительности в % времени в GC. Если куски больше, чем 85K, вы будете выделение кучи больших объектов (LOH), для освобождения которой потребуется коллекция всех поколений (это очень дорого, и на сервере будет останавливаться все выполнение во время его выполнения). Кроме того, если у вас есть тонна объектов на LOH, вы можете получить фрагментацию LOH (LOH никогда не уплотняется), что приводит к низкой производительности и исключениям из памяти. Вы можете переработать процесс, как только вы достигнете определенной точки, но я не знаю, является ли это лучшей практикой.
в дело в том, что вы должны рассмотреть полный жизненный цикл вашего приложения, прежде чем обязательно просто считывать все байты в память самым быстрым способом, или вы можете торговать краткосрочной производительностью для общей производительности.
Я бы сказал BinaryReader это нормально, но может быть рефакторинг на это, вместо всех этих строк кода для получения длины буфера:
должно быть лучше, чем при использовании .ReadAllBytes() , так как я видел в комментариях на первом месте, что включает в себя .ReadAllBytes() что у одного из комментаторов были проблемы с файлами > 600 MB, так как A BinaryReader предназначен для такого рода вещи. Кроме того, положить его в using заявление гарантирует FileStream и BinaryReader закрываются и утилизируются.
Я бы рекомендовал пробовать Response.TransferFile() метод тогда a Response.Flush() и Response.End() для обслуживания больших файлов.
Если вы имеете дело с файлами выше 2 ГБ, вы обнаружите, что вышеуказанные методы не работают.
гораздо проще просто передать поток в MD5 и позвольте этому фрагментировать ваш файл для вас:
Читайте также: