Порядок байтов при чтении файла
Почему при чтении бинарного файла нет опции для указания порядка байтов? Есть только binary режим, но нет LE или BE . Как >> узнаёт порядок байтов файла? Как узнать его программно?
Отслеживать
задан 19 ноя 2020 в 14:34
56 3 3 бронзовых знака
Вообще-то файл — это последовательность байтов, и не более того. А не int ов, скажем, для которых это имело бы значение.
19 ноя 2020 в 14:39
@Harry так как узнать, как инт прочитать?
19 ноя 2020 в 15:32
Использовать текстовый файл 🙂 Или оговаривать формат бинарного файла (по сути, сериализацию) заранее.
19 ноя 2020 в 15:36
@Harry дан бинарный файл с 4байтными числами (вся информация). Как прочитать эти числа, не зная его порядок байтов? Понятно, что можно посмотреть его содержимое и угадать. Но почему из кода нельзя это сделать? Если можно, то как? То есть, я читаю in >> x; , как он определяет порядок?
19 ноя 2020 в 15:48
in >> x — это текстовое чтение, а не бинарное! Ответ: в общем случае — а никак. Этот порядок определяется тем, как создавался файл.
Побайтовая работа с файлами

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

- Ввести с консоли имя файла
- Считать все байты из файла.
- Не учитывая повторений — отсортировать их по байт-коду в убывающем порядке.
- Вывести на экран
- Закрыть поток ввода-вывода
Решаем в лоб:
// Вариант 1. Загоняем в коллекцию и сортируем используя ее метод Collections.sort public class Solution < public static void main(String[] args) throws Exception < FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine()); long startTime = System.currentTimeMillis(); ArrayListlistData = new ArrayList(); while (inputStream.available() > 0) listData.add(inputStream.read()); inputStream.close(); ArrayList result = new ArrayList(new HashSet(listData)); Collections.sort(result); while (!result.isEmpty()) < System.out.print(result.get(result.size()-1) + " "); result.remove(result.get(result.size()-1)); >long finishTime = System.currentTimeMillis(); System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms."); > >
Решает все замечательно! Тест (если бы был — прошелся бы на ура). Но в жизни мало файлов содержащих только строчку «Мама мыла раму». Давайте скормим нашей программе файл в 46Мб (по нынешним меркам вроде и не особо много). Что такое, программа выполняется 220 секунд. Попытка скормить с вечера 1Gb файл (размер MPEG4 фильма не в самом лучшем качестве) не увенчалась успехом. Программа утром все еще читала — а мне идти на работу уже. В чем проблема? Наверное в использовании ArrayList
Встречаем TreeSet
- не допускает хранение двух одинаковых элементов (а значит мы будем хранить в памяти все 255 элементов, вместо миллиарда!)
- при манипуляциях со своими элементами автоматом упорядочивает (само сортирует — вот он, верх совершенства!)
// Вариант 2. Загоняем в ТreeSet который сам сортирует (лютый win!) public class Solution < public static void main(String[] args) throws Exception < FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine()); byte[] arrBytes = new byte[256]; long startTime = System.currentTimeMillis(); SortedSetlist = new TreeSet(); while(inputStream.available()>0) list.add(inputStream.read()); inputStream.close(); while (!list.isEmpty()) < System.out.print(list.last() + " "); list.remove(list.last()); >long finishTime = System.currentTimeMillis(); System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms."); > >
Имеем на выходе: 46Мб файл 176 секунд. 1Gb файл — 3 часа 5 минут. Прогресс на лицо. Мы смогли «дождаться» результатов, да и 46Мб файл заметно быстрее обрабатывается. Идем дальше. Давайте попытаемся отказаться от коллекций (это будет для некоторых мучительно больно). Будем использовать простые массивы (это так примитивно). Заметим одну важную вещь. Кол-во встречающихся байт можно загнать в массив длиной 256. Так просто будем увеличивать на единицу соответствующий считанному байту элемент массива.
Массив — побайтно
// Вариант 3. Считываем массив побайтно. public class Solution < public static void main(String[] args) throws Exception < FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine()); long[] arrBytes = new long[256]; long startTime = System.currentTimeMillis(); while (inputStream.available() >0) arrBytes[inputStream.read()]++; inputStream.close(); // Выводим отсортированный по байт-коду в обратном порядке for (long i = 255; i >= 0 ; i--) if (arrBytes[(int) i] > 0) System.out.print(i + " "); long finishTime = System.currentTimeMillis(); System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms."); > >
Имеем на выходе: 46Мб файл 158 секунд. 1Gb файл — 2 часа 55 минут. Опять улучшение, но небольшое. И мы сделали все простыми инструментами. Не использовали микроскоп для забивания гвоздей. Теперь лирическое отступление. Вспомним устройство компьютера. Память ОЗУ (DRAM) где обычно выполняется программа и хранятся переменные имеет высокую скорость доступа, но небольшой размер. Память на жестком/flash диске (HDD или Flash-накопители) где обычно хранятся файлы, наоборот имеет низкую скорость доступа, но большой размер. Так что когда мы побайтно читаем 1Gb файл (то есть миллиард раз обращаемся к HDD) — мы тратим много времени на работу с низкоскоростным устройством (по песчинке перекладываем песок с кузова КамАЗа в песочницу). Попробуем еще улучшить.
Вывалим сразу ВЕСЬ КамАЗ с песком за один раз!
// Вариант 4. Считываем массив сразу целиком за раз в память. public class Solution < public static void main(String[] args) throws Exception < FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine()); long[] arrBytes = new long[256]; long startTime = System.currentTimeMillis(); byte fileImage[]=new byte[inputStream.available()]; long fileSize=fileImage.length; inputStream.read(fileImage); for (int i = 0; i = 0 ; i--) if (arrBytes[(int) i] > 0) System.out.print(i + " "); long finishTime = System.currentTimeMillis(); System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms."); > >
- индекс у arrBytes определен в пределах 0..255,
- fileImage — массив байт, элементы которого имеют значение -128..127
Используем буфер
// Вариант 5. Считываем массив кусками. public class Solution < public static void main(String[] args) throws Exception < FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine()); long[] arrBytes = new long[256]; long startTime = System.currentTimeMillis(); int bufferSize = 64000; byte buffer[] = new byte[64000]; while (inputStream.available() >0) < if (inputStream.available() < 64000) bufferSize = inputStream.available(); inputStream.read(buffer, 0, bufferSize ); for (int i = 0; i inputStream.close(); // Выводим отсортированный по байт-коду в обратном порядке for (long i = 255; i >= 0 ; i--) if (arrBytes[(int) i] > 0) System.out.print(i + " "); long finishTime = System.currentTimeMillis(); System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms."); > >
В итоге получили: 46Мб файл 0.08 секунд (меньше секунды). 1Gb файл — 0.9 секунд(меньше секунды). 32Gb файл — 31 секунда. Заметим для 1 Gb файла мы улучшили производительность с нескольких часов до долей секунд. На этом скромном факте закончим эксперимент и улучшение начального кода. Мы достигли прогресса во многом — нас радуют новые показатели расхода памяти и времени работы. Также мы не подтягиваем в данном случае бесполезные коллекции из стандартной библиотеки. P.S. Кто-то скажет пример надуманный и т.п. Но полно похожих задач — проанализировать огромный объем элементов, имеющих конечное число состояний. Например изображения (RGB — обычно хранятся в 24 байтах, в нашем случае long[] arrRGB = new long[256*256*256] занял бы в памяти всего 64Мб), музыка (амплитуда обычно оцифровывается в 16 или 24 бита) или дискретные показатели датчиков и т.п.
Проверить первые 2 байта?
Здравствуйте. Например, есть файл, я его открываю. Но как мне представить его в бинарном виде, чтобы считать первые 2 байта из него? Если быть конкретно, то мне нужно сравнивать первые 2 байта с 0x4D 0x5A. Гуглил, но так и не понял, каким образом преобразовать в массив байтов файл, который я получаю через CreateFile()
- Вопрос задан более года назад
- 145 просмотров
5 комментариев
Простой 5 комментариев
но так и не понял, каким образом преобразовать в массив байтов файл, который я получаю через CreateFile()
Это шедевр я считаю! Никогда не встречал настолько завуалированное название операции «чтения из файла» 🙂
Когда вы открываете файл, то в системе просто создается некая ссылка на этот файл, никаких данных из файла в памяти еще нет. Чтоб данные появились в памяти их надо из файла прочитать.
Аналогично, чтоб данные появились в файле их надо записать.
Это отдельные операции и вызовы.
Зачем вы сразу залезли в WinAPI? Это сложно.
Купите учебник по плюсам. В любом учебнике есть описание работы с файлами.
В стандартной библиотеки С++ есть объекты для работы с файлами. Это на много проще, чем WinAPI.
Haaaaz @Haaaaz Автор вопроса
res2001, Вы не поняли, что мне нужно. Во всех учебниках описываются работа с fstream. Но мне нужно прочитать файл побайтово, чтобы сравнить сигнатуру (первые 2 байта должны быть 0x4D 0x5A), такого функционала там нет
функционала там нет
Есть. Надо открыть файл в двоичном режиме и прочитать 2 байта в переменную unsigned short или двухбайтовый массив.
https://en.cppreference.com/w/cpp/io/basic_istream/read
По ссылке выше смотрите пример.
Haaaaz @Haaaaz Автор вопроса
res2001, А вот как открыть файл в двоичном режиме? Это именно то, с чем у меня проблема
Haaaaz, Ссылку открой, там пример как раз для тебя:
std::ifstream is
В примере в конструктор передается еще std::ios::ate — это заставляет сделать переход в конец файла сразу после открытия. В примере это нужно для того, что бы узнать длину файла. Тебе это не нужно, так что ate не указывай.
Решения вопроса 1
Wataru @wataru Куратор тега C++
Разработчик на С++, экс-олимпиадник.
Окройте файл в ifstream в бинарном режиме и читайте 2 байта через read.
Или используйте fread. Читайте 2 байта в буфер длинной 2.
Как посмотреть байты в файле
Класс FileOutputStream предназначен для записи байтов в файл. Он является производным от класса OutputStream, поэтому наследует всю его функциональность.
Через конструктор класса FileOutputStream задается файл, в который производится запись. Класс поддерживает несколько конструкторов:
FileOutputStream(String filePath) FileOutputStream(File fileObj) FileOutputStream(String filePath, boolean append) FileOutputStream(File fileObj, boolean append)
Файл задается либо через строковый путь, либо через объект File. Второй параметр — append задает способ записи: eсли он равен true, то данные дозаписываются в конец файла, а при false — файл полностью перезаписывается
Например, запишем в файл строку:
import java.io.*; public class Program < public static void main(String[] args) < String text = "Hello world!"; // строка для записи try(FileOutputStream fos=new FileOutputStream("notes.txt")) < // перевод строки в байты byte[] buffer = text.getBytes(); fos.write(buffer, 0, buffer.length); System.out.println("The file has been written"); >catch(IOException ex) < System.out.println(ex.getMessage()); >> >
Для создания объекта FileOutputStream используется конструктор, принимающий в качестве параметра путь к файлу для записи. Если такого файла нет, то он автоматически создается при записи. Так как здесь записываем строку, то ее надо сначала перевести в массив байтов. И с помощью метода write строка записывается в файл.
Для автоматического закрытия файла и освобождения ресурса объект FileOutputStream создается с помощью конструктции try. catch.
При этом необязательно записывать весь массив байтов. Используя перегрузку метода write() , можно записать и одиночный байт:
fos.write(buffer[0]); // запись первого байта
Чтение файлов и класс FileInputStream
Для считывания данных из файла предназначен класс FileInputStream , который является наследником класса InputStream и поэтому реализует все его методы.
Для создания объекта FileInputStream мы можем использовать ряд конструкторов. Наиболее используемая версия конструктора в качестве параметра принимает путь к считываемому файлу:
FileInputStream(String fileName) throws FileNotFoundException
Если файл не может быть открыт, например, по указанному пути такого файла не существует, то генерируется исключение FileNotFoundException .
Считаем данные из ранее записанного файла и выведем на консоль:
import java.io.*; public class Program < public static void main(String[] args) < try(FileInputStream fin=new FileInputStream("notes.txt")) < int i; while((i=fin.read())!=-1)< System.out.print((char)i); >> catch(IOException ex) < System.out.println(ex.getMessage()); >> >
В данном случае мы считываем каждый отдельный байт в переменную i:
while((i=fin.read())!=-1)Когда в потоке больше нет данных для чтения, метод возвращает число -1.
Затем каждый считанный байт конвертируется в объект типа char и выводится на консоль.
Подобным образом можно считать данные в массив байтов и затем производить с ним манипуляции:
import java.io.*; public class Program < public static void main(String[] args) < try(FileInputStream fin=new FileInputStream("notes.txt")) < byte[] buffer = new byte[256]; System.out.println("File data:"); int count; while((count=fin.read(buffer))!=-1)< for(int i=0; i> > catch(IOException ex) < System.out.println(ex.getMessage()); >> > В данном случае с помощью метода read() считываем данные в массив buffer длиной 256 байтов. Метод возвращает количество считанных байтов.
Поскольк файл может быть больше 256 байтов, то считываем в цикле while до конца файла. Когда больше не останется файлов для считывания, то метод возвратит -1.
while((count=fin.read(buffer))!=-1)Поскольку количество считанных байтов/размер файла могут быть меньше 256 байт, то реальное количество считанных байт сохраняем в переменную count. Затем выводим считанное количество данных на консоль в цикле for.
for(int i=0; iСовместим оба класса и выполним чтение из одного и запись в другой файл:
import java.io.*; public class Program < public static void main(String[] args) < try(FileInputStream fin=new FileInputStream("notes.txt"); FileOutputStream fos=new FileOutputStream("notes_new.txt")) < byte[] buffer = new byte[256]; int count; // считываем буфер while((count=fin.read(buffer))!=-1)< // записываем из буфера в файл fos.write(buffer, 0, count); >System.out.println("File has been written"); > catch(IOException ex) < System.out.println(ex.getMessage()); >> >Классы FileInputStream и FileOutputStream предназначены прежде всего для записи двоичных файлов, то есть для записи и чтения байтов. И хотя они также могут использоваться для работы с текстовыми файлами, но все же для этой задачи больше подходят другие классы.