Как сэкономить память при чтении файла в Php?

У меня есть файл размером 200 КБ, который я использую на нескольких страницах, но на каждой странице мне нужно только 1-2 строки этого файла, так как я могу прочитать только эти строки, что мне нужно, если я знаю номер строки?

Например, если мне нужна только 10-я строка, я не хочу загружать в память все строки, только 10-ю.

Извините за мой плохой английский!


person coolboycsaba    schedule 08.04.2010    source источник


Ответы (6)


Если вы не знаете смещение строки, вам нужно будет прочитать каждую строку до этой точки. Вы можете просто выбросить старые строки (которые вам не нужны), перебирая файл с чем-то вроде fgets(). (EDIT: вместо fgets() я бы предложил @Gordon решение)

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

person Yacoby    schedule 08.04.2010
comment
То, что база данных будет быстрее, субъективно. Если информация, к которой он пытается получить доступ, находится в начале файла, это будет намного быстрее. Чтение из базы данных по-прежнему является чтением из файла. Он получит улучшение от индекса базы данных, только если будет искать что-то далеко от начала своего файла. Это также зависит от того, чего именно он пытается достичь. - person Ivo Sabev; 09.04.2010
comment
Он никогда не говорил, что база данных будет быстрее. Лишь бы было лучше. Обеспокоенность ОП можно рассматривать как проблему памяти, а не скорости. - person webbiedave; 09.04.2010
comment
@Ivo Как сказал @webbiedave, я никогда не упоминал быстрее. Я пытался добавить, что есть альтернативы, которые могут быть лучшим решением проблемы, чем первое решение, которое я предложил. - person Yacoby; 09.04.2010

Попробуйте SplFileObject

echo memory_get_usage(), PHP_EOL;        // 333200

$file = new SplFileObject('bible.txt');  // 996kb
$file->seek(5000);                       // jump to line 5000 (zero-based)
echo $file->current(), PHP_EOL;          // output current line 

echo memory_get_usage(), PHP_EOL;        // 342984 vs 3319864 when using file()

Для вывода текущей строки вы можете использовать либо current(), либо просто echo $file. Я считаю более ясным использовать этот метод. Вы также можете использовать fgets(), но это приведет к переходу на следующую строку.

Конечно, вам нужны только три средние строки. Я добавил вызовы memory_get_usage только для того, чтобы доказать, что этот подход почти не потребляет памяти.

person Gordon    schedule 08.04.2010
comment
Хороший. Я не заметил, что seek был строковым, а не байтовым. - person Yacoby; 09.04.2010
comment
+1 Я предпочитаю этот код, потому что он просто требует меньше работы для программиста и понятнее, что происходит (ищет определенную строку), чем fgets. - person davidtbernal; 09.04.2010
comment
@Yacoby есть SplFileInfo::fseek() и SplFileInfo::seek(). Последний основан на строках, другой - на основе байтов. seek() — это метод интерфейса SeekableIterator. - person Gordon; 09.04.2010
comment
Обратите внимание, что номер строки, к которой seek приводится, не является строкой 5000. Параметр $line_pos отсчитывается от нуля, поэтому пример ищет строку с номером 5001, как это будет видно в текстовом редакторе и т. д. - person salathe; 09.04.2010
comment
Спасибо, это действительно полезно! - person coolboycsaba; 09.04.2010

Меняется ли содержимое файла? Если он статичен или относительно статичен, вы можете создать список смещений, где вы хотите прочитать свои данные. Например, если файл меняется раз в год, но вы читаете его сотни раз в день, то вы можете предварительно вычислить смещения нужных вам строк и сразу перейти к ним следующим образом:

 $offsets = array();
 while ($line = fread($filehandle)) { .... find line 10 .... }
 $offsets[10] = ftell($filehandle); // store line 10's location
 .... find next line
 $offsets[20] = ftell($filehandle);

и так далее. После этого вы можете тривиально перейти к расположению этой строки следующим образом:

 $fh = fopen('file.txt', 'rb');
 fseek($fh, $offsets[20]); // jump to line 20

Но это может быть совершенно излишним. Попробуйте провести бенчмаркинг операций — сравните, сколько времени занимает старомодное «прочитать 20 строк» ​​по сравнению с предварительным вычислением/переходом.

person Marc B    schedule 09.04.2010

Просто прокручивайте их без сохранения, например.

$i = 1;
$file = fopen('file.txt', 'r');
while (!feof($file)) {
   $line = fgets($file); // this gets whole line from the file;
   if ($i == 10) {
       break; // break on tenth line
   } 
   $i ++;
}

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

person bisko    schedule 08.04.2010
comment
1. вы забыли $i++, 2. почему бы просто не проверить, $i == 10? - person zerkms; 09.04.2010
comment
Блех, всегда забываю ставить приращения. Что касается == 10 ... опять же, дурная привычка разбирать слишком много материала с повторениями .. очень жаль, исправлено :) - person bisko; 09.04.2010
comment
stream_get_line() быстрее, чем fgets() - person Ivo Sabev; 09.04.2010
comment
@Ivo: ты можешь измерить эту разницу? Кстати, код на С++ будет быстрее, чем на php - так что нам нужно переписать это на С++? - person zerkms; 09.04.2010
comment
Файл из 10 000 строк fgets() — 27 секунд, stream_get_line() — 0,5 секунды. Вы можете использовать ассемблер, если хотите. - person Ivo Sabev; 09.04.2010
comment
@Ivo, проверь свой жесткий диск, пожалуйста. 10 000 строк с fgets = ~0,000327, а stream_get_line — ~0,0000532. Так что это подтверждает, что это быстрее. Хотя не знаю почему. - person bisko; 09.04.2010
comment
@bisko Это может быть из-за версии, поскольку stream_get_line работает быстрее в новых версиях PHP 5+. - person Ivo Sabev; 09.04.2010
comment
@brisko Я проверил исходный код PHP. fgets() определен в file.c, а stream_get_line определен в streamsfuncs.c. Вы можете прочитать их исходный код и увидеть, что fgets() на самом деле вызывает stream_get_line с несколькими проверками аргументов перед этим и некоторыми улучшениями результатов сзади, которые делают fgets () немного медленнее. Это версия 5.3.2 - person Ivo Sabev; 09.04.2010
comment
@Иво, я так и сказал. Что меня озадачивает, так это то, что на fgets ушло 27 секунд. - person bisko; 09.04.2010

используйте fgets(). 10 раз :-) в этом случае вы не сохраните в памяти все 10 строк

person zerkms    schedule 08.04.2010

Почему вы пытаетесь загрузить только первые десять строк? Знаете ли вы, что загрузка всех этих строк на самом деле является проблемой?

Если вы не измеряли, то вы не знаете, что это проблема. Не тратьте свое время на оптимизацию без проблем. Скорее всего, любое изменение производительности, связанное с загрузкой всего 200-килобайтного файла, будет незаметным, если только вы точно не знаете, что загрузка этого файла действительно является узким местом.

person Andy Lester    schedule 08.04.2010