Возможно ли считать из файла информации больше чем там сохранено

Обновлено: 06.07.2024

БлогNot. Лекции по C/C++: работа с файлами (fstream)

Лекции по C/C++: работа с файлами (fstream)

Механизм ввода-вывода, разработанный для обычного языка С, не соответствует общепринятому сегодня стилю объектно-ориентированного программирования, кроме того, он активно использует операции с указателями, считающиеся потенциально небезопасными в современных защищённых средах выполнения кода. Альтернативой при разработке прикладных приложений является механизм стандартных классов ввода-вывода, предоставляемый стандартом языка C++.

Открытие файлов

Наиболее часто применяются классы ifstream для чтения, ofstream для записи и fstream для модификации файлов.

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

Ниже приведены возможные значения флагов и их назначение.

Режим Назначение
in Открыть для ввода (выбирается по умолчанию для ifstream)
out Открыть для вывода (выбирается по умолчанию для ofstream)
binary Открыть файл в бинарном виде
aрр Присоединять данные; запись в конец файла
ate Установить файловый указатель на конец файла
trunc Уничтожить содержимое, если файл существует (выбирается по умолчанию, если флаг out указан, а флаги ate и арр — нет)

Например, чтобы открыть файл с именем test.txt для чтения данных в бинарном виде, следует написать:

Оператор логического ИЛИ ( | ) позволяет составить режим с любым сочетанием флагов. Так, чтобы, открывая файл по записи, случайно не затереть существующий файл с тем же именем, надо использовать следующую форму:

Предполагается, что к проекту подключён соответствующий заголовочный файл:

Для проверки того удалось ли открыть файл, можно применять конструкцию

Операторы включения и извлечения

Переопределённый в классах работы с файлами оператор включения ( << ) записывает данные в файловый поток. Как только вы открыли файл для записи, можно записывать в него текстовую строку целиком:

Можно также записывать текстовую строку по частям:

Оператор endl завершает ввод строки символом "возврат каретки":

С помощью оператора включения несложно записывать в файл значения переменных или элементов массива:

В результате выполнения кода образуется три строки текстового файла Temp.txt :

Обратите внимание, что числовые значения записываются в файл в виде текстовых строк, а не двоичных значений.

Оператор извлечения ( >> )производит обратные действия. Казалось бы, чтобы извлечь символы из файла Temp.txt , записанного ранее, нужно написать код наподобие следующего:

Однако оператор извлечения остановится на первом попавшемся разделителе (символе пробела, табуляции или новой строки). Таким образом, при разборе предложения "Текстовый массив содержит переменные" только слово "Текстовый" запишется в массив buff , пробел игнорируется, а слово "массив" станет значением целой переменной vx и исполнение кода "пойдет вразнос" с неминуемым нарушением структуры данных. Далее, при обсуждении класса ifstream , будет показано, как правильно организовать чтение файла из предыдущего примера.

Класс ifstream: чтение файлов

Как следует из расшифровки названия, класс ifstream предназначен для ввода файлового потока. Далее перечислены основные методы класса. Большая часть из них унаследована от класса istream и перегружена с расширением родительской функциональности. К примеру, функция get , в зависимости от параметра вызова, способна считывать не только одиночный символ, но и символьный блок.

Метод Описание
open Открывает файл для чтения
get Читает один или более символов из файла
getline Читает символьную строку из текстового файла или данные из бинарного файла до определенного ограничителя
read Считывает заданное число байт из файла в память
eof Возвращает ненулевое значение (true), когда указатель потока достигает конца файла
peek Выдает очередной символ потока, но не выбирает его
seekg Перемещает указатель позиционирования файла в заданное положение
tellg Возвращает текущее значение указателя позиционирования файла
close Закрывает файл

Теперь понятно, как нужно модифицировать предыдущий пример, чтобы использование оператора извлечения данных давало ожидаемый результат:

Метод getline прочитает первую строку файла до конца, а оператор >> присвоит значения переменным.

Следующий пример показывает добавление данных в текстовый файл с последующим чтением всего файла. Цикл while (1) используется вместо while(!file2.eof()) по причинам, которые обсуждались в предыдущей лекции.

Этот код под ОС Windows также зависит от наличия в последней строке файла символа перевода строки, надежнее было бы сделать так:

Явные вызовы методов open и close не обязательны. Действительно, вызов конструктора с аргументом позволяет сразу же, в момент создания поточного объекта file , открыть файл:

Вместо метода close можно использовать оператор delete , который автоматически вызовет деструктор объекта file и закроет файл. Код цикла while обеспечивает надлежащую проверку признака конца файла.

Класс ofstream: запись файлов

Класс ofstream предназначен для вывода данных из файлового потока. Далее перечислены основные методы данного класса.

Метод Описание
open Открывает файл для записи
put Записывает одиночный символ в файл
write Записывает заданное число байт из памяти в файл
seekp Перемещает указатель позиционирования в указанное положение
tellp Возвращает текущее значение указателя позиционирования файла
close Закрывает файл

Описанный ранее оператор включения удобен для организации записи в текстовый файл:

Бинарные файлы

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

Первый параметр методов write и read (адрес блока записи/чтения) должен иметь тип символьного указателя char * , поэтому необходимо произвести явное преобразование типа адреса структуры void * . Второй параметр указывает, что бинарные блоки файла имеют постоянный размер байтов независимо от фактической длины записи. Следующее приложение дает пример создания и отображения данных простейшей записной книжки. Затем записи файла последовательно считываются и отображаются на консоли.

P.S. При выполнении этого и других листингов в Visual Studio последних версий может дополнительно понадобиться подключение директивы _CRT_SECURE_NO_WARNINGS.

В результате выполнения этого кода образуется бинарный файл Notebook.dat из трех блоков размером по 80 байт каждый (при условии, что символы - однобайтовые). Естественно, вы можете использовать другие поточные методы и проделывать любые операции над полями определенной структуры данных.

Класс fstream: произвольный доступ к файлу

Предположим что в нашей записной книжке накопилось 100 записей, а мы хотим считать 50-ю. Конечно, можно организовать цикл и прочитать все записи с первой по заданную. Очевидно, что более целенаправленное решение - установить указатель позиционирования файла pos прямо на запись 50 и считать ее:

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

Если не указать флаг ios::ate (или ios::app ), то при открытии бинарного файла Notebook.dat его предыдущее содержимое будет стерто!

Дополнительно может понадобиться указать, откуда отсчитывается смещение.

Наконец, можно открыть файл одновременно для чтения/записи, используя методы, унаследованные поточным классом fstream от своих предшественников. Поскольку класс fstream произведен от istream и ostream (родителей ifstream и ofstream соответственно), все упомянутые ранее методы становятся доступными в приложении.

Считывание больших файлов и их обработка
Помогите с задачей 1) Имеются несколько txt файлов (больших размеров), в которых содержатся.


Считывание больших wav файлов частями
Привет ребята, столкнулся со следующей проблемой, у меня есть wav файл размером 2Gb, мне его нужно.

Обработка очень больших файлов
У меня есть большие текстовые документы, состоящие из 10 и 100 тыс символов каждый. Эти документы.

Открытие очень больших AVI файлов
Суть в том, что имеется .avi файл размером больше 1GB(открытие .avi делаю библиотекой AForge).В.

Если файлы действительно по 9 гигабайт стает вопрос о месте куда вы их записывать будете) в оперативной памяти) При том что у меня был файл 500кб на 120 страниц вордовских с ними были довольно напряжные операции на вин формах и обрабатывался 1 такой файл моим "гавнокодом" до 10 минут Да, теперь возник вопрос: как хранить 9.5 млн строк и 17.5 млн строк?
Если что, то это строки csv файла.
В List<string> не удалось сохранить.

Ну csv файл это насколько я понимаю табличное чтоот на подобии экселя. Можно создать класс с полями. И в будушем просто создать массив этого класса на подобии

А можно подумать, стоит ли хранить все в памяти или обрабатывать постепенно. Собственно вопрос: чем(как?) построчно считать текстовый файл(ну точнее csv файл) который весит почти 9 гб?
Второй вопрос: можно ли как-нибудь в цикле динамически менять путь к считываемому файлу? Т.е. у меня есть 25 файлов и из них хочу построчно считать данные. Хотелось бы не писать 25 разных цилов/ не запускать 25 раз программу, каждый раз вручную меняя путь. Вы что правда собираетесь работать с таким объемом данных без SQL-серверов?! Если данные нужно обрабатывать то лучше считал строку - сделал с ней действие сделал результирующеее деййствие очистил оперативку поехал дальше

csv файл - это обычный текстовый файл с разделителями, обычно в качестве разделителя ";" (зависит от региональных настроек)

Файл размером 9Гб читать надо по кускам в зависимости от того сколько свободной оперативки

Лучше эти файлы преобразовать в бинарники, размер значительно уменьшится

EVG-1980, а можно поподробнее на счёт бинарников? Как это сделать? EVG-1980, эту штуку я видел. Вы предлагаете перезаписать мой файл в бинарный вид?
А как потом в бинарнике ориентироваться где разделитель, где конец строки и т.д.? Sean_mephi, первый вопрос, который следовало задать: "А почему файл такой огромный?".
Может следует продумать какую-то дифференцированную систему хранения? А то пляски с бубном по полю усеянному граблями могут плохо закончиться.

Sean_mephi, расскажи что у тебя охранница в этом файле?

Добавлено через 1 минуту

А как потом в бинарнике ориентироваться где разделитель, где конец строки и т.д.?

nio, изначально было много маленьких файлов. Просто из каждого надо было отобрать нужную мне информацию. Думаю вернуться с изначальной задумке и обработать каждый файл отдельно.

Добавлено через 1 час 11 минут
Так всё таки как перевести файл в бинарный вид?
Считать и сразу записать через Binary reader/writer?
А как потом работать с этими данными?

Sean_mephi, Расскажи что храниться , в твоем супер секретном файле и что надо с этими данными делать, не бойся мы тебя не сдадим , поверь ты так быстрее получишь самое оптимальное решение твоей задачи EVG-1980, логи. Есть несколько полей, меня интересует поле дата и количество. Поле дата мне надо просто отсортировать в двух файлах(независимо), а потом вычитать количество первого файла, из количества второго файла(с одинаковыми датами). Суть в том, что после фильтрации получилось 2 файла: 600мб и 1.1 гб

Понятно что ни хрена не понятно , сам прочитай что написал потом подумай как с другой стороны это понимается.

1. Что создает эти логи ?
2. Сколько дней в твоем файле.
3. Пару строк из исходного файла и что надо сделать с этим

EVG-1980, я не знаю кто создаёт эти логи.
Период: весь 2013 год.
Вот пример строки файла:
GZH3;GAZR-3.13;14800.00000;25;2013-01-08 10:00:00:00/067;679857248;0

Для начала нужно отсортировать по времени и откинуть строчки, у которых последнее поле не ноль.
Затем:
Есть такой же только с другими строчками(первые два поля немного другие). Нужно взять одинаковое время в двух файлах, и найти разницу между значениями третьих полей. После сортировки по времени можно откинуть 2 последних поля, т.к. они больше не понадобятся.

Добавлено через 30 минут
Ох ошибся, там перед милисекундами точка стоит, а не слэш.

Заливка данных больших файлов
Всем привет. Есть файл формата *.txt, разделитель столбцов /tab, значение в &quot;&quot;. Заливаю данные в.


Считывание данных из файлов
Помогите считать каждое слово в текстовом файле и закинуть их в массив. Приблизительно понимаю что.

Сохранение больших файлов в базе данных
Как сохранять большие файлы в базе данных? У меня есть программа, которая предоставляет.

Автоматизированный cбор данных из больших (до 100мБ) текстовых файлов
Помогите решить проблему( на VB встроенном в эксель,наверно): написать макрос для .

В зависимости от того, какие действия ваша программа собирается производить с открываемым файлом, возможно троякое его открытие:

Закрытие файла

После того как ваша программа закончит работу с файлом, очень желательно закрыть его:

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

Считывание из файла

Чтение данных из файла, открытого для считывания, производится с помощью команд read() и readln() . В скобках сначала указывается имя файловой переменной , а затем - список ввода 2 См. лекцию 1. . Например:

Если вспомнить, что в памяти компьютера любой файл записывается линейной последовательностью символов и никакой разбивки на строки там реально нет, то действия процедуры readln() можно пояснить так: читать все указанные переменные, а затем игнорировать все символы вплоть до ближайшего символа "конец строки" или "конец файла". Указатель при этом перемещается на позицию непосредственно за первым найденным символом "конец строки".

Если же символ конца строки встретится где-нибудь между переменными, указанными в списке ввода, то обе процедуры его просто проигнорируют.

Считывать из текстового файла можно только переменные простых типов: целых, вещественных, символьных, - а также строковых. Численные переменные, считываемые из файла, должны разделяться хотя бы одним пробельным символом . Типы вводимых данных и типы тех переменных, куда эти данные считываются, обязаны быть совместимыми 3 См. лекцию 2. . Здесь действуют все те же правила, что и при считывании с клавиатуры.

Считываемые переменные могут иметь различные типы. Например, если в файле 4 Более правильно было бы сказать: "в файле, подключенном к файловой переменной f ", однако для краткости здесь и далее мы будем использовать упрощенный вариант, полагая, что это не вызовет никаких недоразумений. f записана строка

то командой read(f,a,b,c); можно прочитать одновременно значения для трех переменных, причем все - разных типов:

Замечание: Обратите внимание, что символьную переменную c пришлось считывать дважды, так как после числа " 2.5 " сначала идет символ пробела и только затем буква " c ".

Из файла невозможно считать переменную составного типа (например, если а - массив, то нельзя написать read(f,a) , можно ввести его только поэлементно, в цикле), файлового, а также логического.

Особенно внимательно нужно считывать строки ( string[length] и string ): эти переменные "забирают" из файла столько символов, сколько позволяет им длина (либо вплоть до ближайшего конца строки). Если строковая переменная неопределенной длины (тип данных string ) является последней в текущей строке файла, то никаких проблем с ее считыванием не возникнет. Но в случае, когда необходимо считывать переменную типа string из начала или из середины строки файла, это придется делать с помощью цикла - посимвольно. Аналогичным образом - посимвольно, от пробела до пробела - считываются из текстового файла слова.

Есть еще один, гораздо более трудоемкий способ: считать из файла всю строку целиком, а затем "распотрошить" ее - разобрать на части специальной процедурой выделения подстрок copy() . После чего (где необходимо) применить процедуру превращения символьной записи числа в само число, применяя стандартную процедуру val () . Кроме того, совсем не очевидно, что длина вводимой строки не будет превышать 256 символов, поэтому такой способ приходится признать неудовлетворительным.

Запись в файл

Сохранять переменные в файл, открытый для записи командами rewrite (f) или append (f) , можно при помощи команд write() и writeln() . Так же как в случае считывания, первой указывается файловая переменная , а за ней - список вывода :

Выводить в текстовый файл можно переменные любых базовых типов (вместо значений логического типа выведется их строковый аналог TRUE или FALSE ) или строки.

PHP разработчикам не так уж часто приходится следить за расходом памяти в своих приложениях. Сам движок PHP неплохо подчищает мусор за нами, да и модель веб-сервера с контекстом исполнения, "умирающим" после выполнения каждого запроса, позволяет даже самому плохому коду не создавать больших долгих проблем.

Однако, в некоторых ситуациях, мы можем столкнуться с проблемами нехватки оперативной памяти — например, пытаясь запустить композер на маленьком VPS, или при открытии большого файла на сервере не богатом ресурсами.

Fragmented terrain

Последняя проблема и будет рассмотрена в этом уроке.

Мерила Успеха

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

Обычно измеряют загрузку CPU и использование оперативной памяти. Часто бывает, что экономия одного, ведёт к увеличенным затратам другого и наоборот.

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

Измерять использование CPU внутри PHP плохая идея. Лучше использовать какую-либо утилиту, как top из Ubuntu или macOS. Если вы у вас Windows, то можно использовать Linux Subsystem, чтобы иметь доступ к top.

В этом уроке мы будем измерять использование памяти. Мы посмотрим, как память расходуется в традиционных скриптах, а затем применим парочку фишек для оптимизации и сравним результаты. Надеюсь, к концу статьи, читатель получит базовое понимание основных принципов оптимизации расхода памяти при чтении больших объемов данных.

Будем замерять память так:

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

Какие есть варианты?

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

Давайте представим, что для первого варианта мы хотим читать файл и отдельно обрабатывать каждые 10000 строк. Нужно будет держать по крайней мере 10000 строк в памяти и передавать их в очередь(в какой бы форме она не была реализована).

Для второго сценария, предположим, мы хотим сжать содержимое очень большого ответа API. Нам не важно, что за данные там содержатся, важно вернуть их в сжатой форме.

В обоих случаях нужно считать большие объемы информации. В первом, нам известен формат данных, во втором, формат значения не имеет. Рассмотрим оба варианта.

Чтение Файла Строка За Строкой

Есть много функций для работы с файлами. Давайте напишем с их помощью свой ридер:

Тут мы считываем файл с работами Шекспира. Размер файла около 5.5MB и пиковое использование памяти 12.8MB.

А теперь, давайте воспользуемся генератором:

Файл тот же, а пиковое использование памяти упало до 393KB! Но пока мы не выполняем со считываемыми данными никаких операций, это не имеет практической пользы. Для примера, мы можем разбивать документ на части, если встретим две пустые строки:

Хотя мы разбили документ на 1,216 кусков, мы использовали лишь 459KB памяти. Всё это, благодаря особенности генераторов — объем памяти для их работы равен размеру самой большой итерируемой части. В данном случае, самая большая часть состоит из 101,985 символов.

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

Пайпинг между файлами

В ситуациях, когда обработка данных не требуется, мы можем пробрасывать данные из одного файла в другой. Это называется пайпингом( pipe — труба, возможно потому что мы не видим что происходит внутри трубы, но видим что входит и выходит и неё). Это можно сделать с помощью потоковых методов. Но сперва, давайте напишем классический скрипт, который тупо передает данные из одного файла в другой:

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

Давайте попробуем стримить(или пайпить) файлы, один в другой:

Код довольно странный. Мы открываем оба файла, первый на чтение, второй на запись. Затем мы копируем первый во второй, после чего закрываем оба файла. Возможно будет сюрпризом, но мы потратили всего 393KB.

Что-то знакомое. Не похоже ли это на генератор, читающий каждую строчку? Это так, потому что второй аргумент fgets определяет как много байт каждой строки нужно считывать(по умолчанию -1, т.е до конца строки). Необязательный, третий аругмент stream_copy_to_stream делает то же самое. stream_copy_to_stream читает первый поток по одной строке и пишет во второй.

Пайпинг этого текста не особо полезен для нас. Давайте придумаем реальный пример. Предположим, что мы хотим получить картинку из нашего CDN и передать её в файл или в stdout . Мы могли бы сделать это так:

Для того чтобы осуществить задуманное этим способом потребовалось 581KB. Теперь попробуем сделать то же самое с помощью потоков.

Потратили немного меньше памяти(400KB) при одинаковом результате. А если б нам не нужно было сохранять картинку в памяти, мы могли бы сразу застримить её в stdout :

Другие потоки

Существуют и другие потоки, в/из которых можно стримить:

  • php://stdin — только чтение
  • php://stderr — только запись
  • php://input — только чтение(дает доступ к голому телу запроса)
  • php://output — только запись(позволяет писать в буфер вывода)
  • php://memory and php://temp — чтение и запись. Тут можно хранить временные данные, отличие в том что php://temp будет хранить данные в файловой системе при их разрастании, а php://memory будет писать всё в оперативную память до последнего.

Фильтры

Есть еще одна фишка, которую мы можем использовать — это фильтры. Промежуточный вариант, который дает нам немного контроля над потоком, без необходимости детально погружаться в его содержимое. Допустим, мы хотим сжать файл. Можно применить zip extension:

Хороший код, но он потребляет почти 11MB. С фильтрами, получится лучше:

Здесь мы используем php://filter/zlib.deflate который считывает и сжимает входящие данные. Мы можем пайпить сжатые данные в файл, или куда-нибудь еще. Этот код использовал лишь 896KB.

Я знаю что это не совсем тот же формат, что и zip архив. Но задумайтесь, если у нас есть возможность выбрать иной формат сжатия, затратив в 12 раз меньше памяти, стоит ли это делать?

Чтобы распаковать данные, применим другой zip фильтр.

Вот парочка статей, для тех кому хотелось бы поглубже погрузиться в тему потоков: “Understanding Streams in PHP” и“Using PHP Streams Effectively”.

Кастомизация потоков

fopen и file_get_contents имеют ряд предустановленных опций, но мы можем менять их как душе угодно. Чтобы сделать это, нужно создать новый контекст потока:

В этом примере мы пытаемся сделать POST запрос к API. Прописываем несколько заголовков, и обращаемся к API по файловому дескриптору. Существует много других опций для кастомизации, так что не будет лишним ознакомиться с документацией по этому вопросу.

Создание своих протоколов и фильтров

Перед тем как закончить, давайте поговорим о создании кастомных протоколов. Если посмотреть в документацию, то можно увидеть пример:

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

Аналогичным образом, можно создать и кастомные фильтры потока. Пример класса фильтра из доков:

И его также легко зарегистрировать:

Свойство filtername в новом классе фильтра должно быть равно highlight-names . Также можно использовать инлайновый фильтр php://filter/highligh-names/resource=story.txt . Создавать фильтры гораздо легче чем протоколы. Но протоколы, имеют более гибконастраеваемые возможности и функциональность. К примеру, дной из причин для которой фильтры не годятся, а требуются протоколы — это операции с директориями, где фильтр будет нужен для обработки каждой порции данных.

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

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

Надеюсь, что этот урок подарил вам несколько новых идей(или освежил их в памяти) и теперь вы сможете работать с большими файлами гораздо эффективнее. Познакомившись с генераторами и потоками( и перестав использовать функции по типу file_get_contents ) можно избавить наши приложения от целого класса ошибок. That seems like a good thing to aim for!

Читайте также: