Форум: Форум PHPФорум ApacheФорум Регулярные ВыраженияФорум MySQLHTML+CSS+JavaScriptФорум FlashРазное
Новые темы: 0000000
Программирование. Ступени успешной карьеры. Авторы: Кузнецов М.В., Симдянов И.В. MySQL 5. В подлиннике. Авторы: Кузнецов М.В., Симдянов И.В. PHP Puzzles. Авторы: Кузнецов М.В., Симдянов И.В. PHP 5. На примерах. Авторы: Кузнецов М.В., Симдянов И.В., Голышев С.В. MySQL на примерах. Авторы: Кузнецов М.В., Симдянов И.В.
ВСЕ НАШИ КНИГИ
Консультационный центр SoftTime

Форум PHP

Выбрать другой форум

 

Здравствуйте, Посетитель!

вид форума:
Линейный форум Структурный форум

тема: Разбивка файла на части: как избавиться от файлов с нулевым размером?
 
 автор: baston   (02.03.2010 в 15:08)   письмо автору
 
 

В книге "PHP 5 на примерах" в листинге 6.15 приведен код разбивки файла на части (код ниже). В следующем листинге 6.16 приведен код для склейки этих частей в новый файл.
При исполнении кода из второго листинга проявляется ошибка, грешашая на присутствие нулевых файлов.
Как оказалось, эти нулевые файлы и создаются кодом из первого листинга (что логично, ибо вытекает из математики).
У меня возникли два вопроса:
1. Почему все же возникают файлы с нулевым размером при разрезке файла и как от них избавится?
2. Для чего нужна строка из кода:
if((float)(filesize($fn)/$piece) - $count != 0) $count++;

Сам код из листинга:
//Имя файла
$fn = "clip2net082b.zip";  // размер этого файла: 1.511.371
//Задаем размер части для разбивки файла - 100.000 байт
$piece = 100000;
//Открываем файл в режиме чтения
$fp = fopen($fn, "r");
//Читаем содержимое файла в буфер
$bufer = fread($fp, filesize($fn));
//Закрываем файл
fclose($fp);
//Подсчитываем число частей, на которые должен быть разбит файл
$count = (int)filesize($fn)/$piece;
if((float)(filesize($fn)/$piece) - $count != 0) $count++;
//В цикле разбиваем содержимое файла в буфере на части
for($i=0; $i<$count; ++$i)
{
  $part = substr($bufer, $i*$piece, $piece);
  //Сохраняем текущую часть в отдельном файле
  $fp = fopen("site.tm".$i, "w");
  fwrite($fp, $part);
  fclose($fp);
}

  Ответить  
 
 автор: baston   (02.03.2010 в 15:46)   письмо автору
 
   для: baston   (02.03.2010 в 15:08)
 

Собственно, можно избавится от ошибки, если в коде второго листинга одновременно с проверкой на существование файла (части) проверять и на его размер:
if((file_exists($filename)) && (filesize($filename) != 0))

Но меня по-прежнему интересуют вопросы, изложенные в первом сообщении.
Да и еще смутило, что листинги 6.15 и 6.16 в результате приводят к ошибке... Вроде учебник, должен быть выверен.
Спасибо.

  Ответить  
 
 автор: baston   (03.03.2010 в 11:17)   письмо автору
 
   для: baston   (02.03.2010 в 15:08)
 

Очень странное поведение: перестал работать скрипт загрузки (код в первом сообшении). Пишет, что нет такого файла:
>Цитата
>Warning: fopen(clip2net082b.zip): failed to open stream: No such file or directory in Z:\home\test.ru
>\www\zagruzka_file.php on line 8 Warning: filesize(): stat failed for clip2net082b.zip in Z:\home\test.ru
>\www\zagruzka_file.php on line 10

При том, что вчера (впрочем, как и сегодня) этот файл лежал в той же папке, что и выполняемый скрипт...

Кто-то может пояснить, в чем может быть причина такого поведения?
P.S. Сервер (то есть Денвер) перезагружал.

  Ответить  
 
 автор: ДобрыйУхх   (03.03.2010 в 19:48)   письмо автору
 
   для: baston   (02.03.2010 в 15:08)
 

2. Для чего нужна строка из кода:
if((float)(filesize($fn)/$piece) - $count != 0) $count++;

- что бы не потерялся остаток файла.

Получиться, допустим, файл разбит на 10 частей по 100 килобайт и плюс еще одна часть - 20 кб в остатке. В итоге файл разбит на 11 частей

  Ответить  
 
 автор: baston   (03.03.2010 в 22:29)   письмо автору
 
   для: ДобрыйУхх   (03.03.2010 в 19:48)
 

Да, спасибо. Интуитивно я это сообразил, но хотелось уточнения.

Меня еще смущает, что создаются файлы с нулевым размером. Теоретически все верно, если файл "весит" полтора мегабайта, то деление на 10.000 приведет к 15 частям. Однако при реальном разделении 10 частей имеют размер, а 5 - имеют 0-й размер. И второй код из-за этого выдает ошибку (как исправить, я показал выше).
Впрочем, как учебный пример, код сойдет. Но не как реальный. Жаль...

  Ответить  
 
 автор: Trianon   (03.03.2010 в 22:37)   письмо автору
 
   для: baston   (02.03.2010 в 15:08)
 

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

Такое вытекает лишь из незнания даже не математики, а арифметики.
Учитывая арифметику, можно добиться не только отсутствия томов с нулевым размером, но и сделать так, чтоб размер любого тома отличался от размера любого другого тома не более чем на 1 байт.

  Ответить  
 
 автор: baston   (04.03.2010 в 07:50)   письмо автору
 
   для: Trianon   (03.03.2010 в 22:37)
 

Это, наверное, камень в огород авторов данного листинга.
Если нет, то можно конкретно подсказать направление в арифметике для исправления кода?
Спасибо.

  Ответить  
 
 автор: Trianon   (04.03.2010 в 08:40)   письмо автору
 
   для: baston   (04.03.2010 в 07:50)
 

там нечего править.
Код нужно переписывать.

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

Книга "php5 на примерах", насколько я помню, авторами лишь организована.
Сами примеры взяты из ответов этого форума.
Так что камень здесь в огороде чисто номинальный.

  Ответить  
 
 автор: baston   (04.03.2010 в 10:49)   письмо автору
 
   для: Trianon   (04.03.2010 в 08:40)
 

Понял, спасибо.

  Ответить  
 
 автор: baston   (04.03.2010 в 16:57)   письмо автору
 
   для: Trianon   (04.03.2010 в 08:40)
 

У меня получилось, как корректно переписать этот код.
Я заношу в память небольшие куски файла (а не весь файл).
Посмотрите пожалуйста и, если есть что поправить, сообщите. Спасибо.
//Сохраняем в переменную название файла
$fn = "clip2net082b.zip";
//Проверяем существование файла в текущем каталоге (местонахождение скрипта)
if (!is_file($fn)) { die("Файл \"$fn\" не существует"); }
//Определяем размер частей: не больше 102.400 байт (100 кб)
$piece = 102400;
//Получаем размер файла в байтах
$size = filesize($fn);
//Расчитываем количество частей, на которые необходимо разбить файл
$count = (int)$size/$piece;
//Открываем файл в режиме чтения (с проверкой на его существование)
if($fd = fopen($fn, "rb"))
{
  //Пока не достигнем конца файла
  while(!feof($fd))
  {
    for($i=0; $i<$count; $i++)
    {
      //Считываем в память определенное количество байт из основного файла
      $buffer = fread($fd, $piece);
      //Создаем файл-часть
      $part = fopen("part.tm".$i, "w");
      //Записываем в файл-часть данные из буфера
      fwrite($part, $buffer);
      //Закрываем файл-часть
      fclose($part);
    }
  }
  //Закрываем основной файл
  fclose($fd);
}


P.S. Возможно проверок существования файла много...

  Ответить  
 
 автор: Trianon   (04.03.2010 в 22:06)   письмо автору
 
   для: baston   (04.03.2010 в 16:57)
 

сразу бросается в глаза ошибка в логике
Цикл у Вас один. А операторов цикла - два
  while(!feof($fd))

    for($i=0; $i<$count; $i++)

Один лишний. Результаты могут быть печальные.
Навскидку - затирание первого тома данными из хвостика последнего.

Кроме того Ваш код все равно читает в оперативную память объем целого тома.
Так что внутренний цикл таки нужен. Но с другой логикой и другим условием повтора.

Но вот за то, что Вы стали работать над алгоритмом - респект.

  Ответить  
 
 автор: baston   (05.03.2010 в 07:58)   письмо автору
 
   для: Trianon   (04.03.2010 в 22:06)
 

Я думал над тем, что по окончании работы скрипта в памяти остается все, что мы занесли туда.
Может быть, стоит очищать память после каждой итерации?
Жаль, мало знаний для соображения о том, какую иную логику здесь можно применить....
За критику отдельное большое спасибо.

  Ответить  
 
 автор: Trianon   (05.03.2010 в 10:42)   письмо автору
 
   для: baston   (05.03.2010 в 07:58)
 

просто Вы кладете данные в том за один раз.

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

У автора скрипта из книги экскаватор имел ковш размером с весь карьер.
Один раз зачерпнув всё, он отсыпал каждому самосвалу в кузов.
У Вас экскаватор имеет ковш размером с кузов самосвала.
Зачерпывает из карьера, насыпает в самосвал, зачерпывает - насыпает в следующий, и т.д.
Обычно же экскаватор насыпает самосвалы в несколько шагов.
Это позволяет ему иметь относительнокомпактный (по сравнению с размерами самосвала) ковш.

Размер ковша в этой аналогии - в чистом виде объем оперативной памяти, потребляемой скриптом.

  Ответить  
 
 автор: baston   (07.03.2010 в 15:35)   письмо автору
 
   для: Trianon   (05.03.2010 в 10:42)
 

Честно говоря, бьюсь уже второй день над этой задачей и не продвинулся ни на йоту.
Если я правильно понял вашу мысль, вы предлагаете читать из большого файла по маленькому кусочку (скажем, 10 кб) и записывать этот кусочек в том, пока размер тома не станет равным, например, 100 кб.
Я перепробовал разные варианты цикла, но все стопорится зацикливанием и созданием одного большого файла.
Уже отчаяние берет и мозги кипят, как говорится...
Может быть, еще какие наводки дадите? Например, какими функциями ограничиться, какими циклами...
Мне сложно выйти за рамки, так как практики-то маловато.
Можно было бы воспользоваться функцией file_get_contents(), но она сродни функции file и в память получается тоже заносится много данных... Или я не прав?

  Ответить  
 
 автор: baston   (07.03.2010 в 19:33)   письмо автору
 
   для: baston   (07.03.2010 в 15:35)
 

Вот так сейчас неожиданно получилось (добавил функцию). Поправьте, если опять есть неоптимальности или ошибки. Спасибо.
//Сохраняем в переменную название файла
$fn = "clip2net082b.zip";
//Проверяем существование файла в текущем каталоге (местонахождение скрипта)
if (!is_file($fn)) { die("Файл \"$fn\" не существует"); }
//Получаем размер файла в байтах
$size = filesize($fn);
//Задаем размер считываемых данных за раз (не более 10 кб)
$read = 10240;
//Задаем максимальный размер тома (100 кб)
$tom = 102400;
//Определяем количество томов
$count = (int)$size/$tom;    //~14 томов с хвостиком
//Увеличиваем количество томов, если есть хвостик
if((float)($size/$tom) - $count != 0) $count++;
//Определяем размер каждого тома
$sizetom = (int)$size/$count;
if((float)($size/$count) - $sizetom != 0) $sizetom++;
//Открываем файл в режиме чтения
$fd = fopen($fn, "rb");
//Функция чтения данных по 10 кб и записи их в конец файла
function readwrite($bigfile, $tomus, $maxsize)
{
  for($i=0; $i<102400; $i+=10240)
    {
        $buf = fread($bigfile, 10240);
        fwrite($tomus, $buf);
        ob_clean();
    }
}
for($i=0; $i<$count; $i++)
{
    $part = fopen("part.tm".$i, "ab");
  readwrite($fd, $part, $sizetom);
    fclose($part);
}
//Закрываем основной файл
fclose($fd);

  Ответить  
 
 автор: Trianon   (08.03.2010 в 17:53)   письмо автору
 
   для: baston   (07.03.2010 в 19:33)
 

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

//Задаем размер считываемых данных за раз (не более 10 кб)
$read = 10240;
//Задаем максимальный размер тома (100 кб)
$tom = 102400;

а стоит сделать, чтобы применялись в конечном итоге именно значения этих переменных.
Еще полезно знать, что округление от деления a на b вверх быстро считается так:
$ n = (int)(($a+$b-1)/$b);

  Ответить  
 
 автор: baston   (16.03.2010 в 12:46)   письмо автору
 
   для: Trianon   (08.03.2010 в 17:53)
 

Спасибо за подсказки.
Я, наконец, собрался с духом и вернулся к этому скрипту. Решил сделать всё простой функцией + использование вложенной функции. Не уверен, что это оптимально...
Однако столкнулся с тем, что у меня повторяются данные из первой функции во второй. Как это обойти - не соображу. Если можете, подскажите пожалуйста. Спасибо. Код ниже:
//Функция разбивки файла на части
function splitfile($fn)
{
  //Проверяем существование файла
  if(!is_file($fn)) {die("Файл не найден. Проверьте его существование.");}
  //Определяем размер файла
  $fs = filesize($fn);
  //Задаем размер считываемых данных за раз (не более 10 кб)
  $read = 10240;
  //Задаем максимальный размер тома (100 кб)
  $tom = 102400;
  //Определяем количество томов и увеличиваем, если есть хвостик
  $count = (int)(($fs+$tom-1)/$tom);
  //Определяем размер каждого тома и увеличиваем, если есть хвостик
  $sizetom = (int)(($fs+$count-1)/$count);
  //Открываем файл для чтения
  $fd = @fopen($fn, "rb");

  //Вложенная функция чтения данных по 10 кб и записи их в конец файла
  function readwrite($bigfile, $tomus, $maxsize, $tom = 102400, $read = 10240)
  {
    for($i=0; $i<$tom; $i+=$read)
    {
        $buf = fread($bigfile, $read);
        fwrite($tomus, $buf);
        ob_clean();
    }
  }
  //Проходим циклом и создаем части
  for($i=0; $i<$count; $i++)
  {
    $part = fopen("part.tm".$i, "ab");
    readwrite($fd, $part, $sizetom);
    fclose($part);
  }
  //Закрываем основной файл
  fclose($fd);
}
splitfile("clip2net082b.zip");

  Ответить  
 
 автор: Trianon   (16.03.2010 в 13:04)   письмо автору
 
   для: baston   (16.03.2010 в 12:46)
 

надо просто вызов записать полной формой
readwrite($fd, $part, $sizetomб $tom, $read);

  Ответить  
 
 автор: baston   (16.03.2010 в 13:23)   письмо автору
 
   для: Trianon   (16.03.2010 в 13:04)
 

Благодарю!
Таким образом, можно ли считать, что решение данной задачи с помощью этой функции является оптимальным решением и может использоваться в реальной ситуации?

  Ответить  
 
 автор: Trianon   (16.03.2010 в 14:06)   письмо автору
 
   для: baston   (16.03.2010 в 13:23)
 

Сравните.
Сделайте выводы.

  Ответить  
 
 автор: baston   (08.03.2010 в 17:30)   письмо автору
 
   для: Trianon   (05.03.2010 в 10:42)
 

Простите, что опять напоминаю о себе :)

Можете вы оценить финальное решение данной задачи (код выше в предпоследнем сообщении)?
А то варюсь в собственном соку...
Буду очень благодарен за критику и указание на ошибки.
С уважением.

  Ответить  
Rambler's Top100
вверх

Rambler's Top100 Яндекс.Метрика Яндекс цитирования