Divida un archivo grande en muchos archivos más pequeños con PHP

Tengo un archivo .txt de 209MB con aproximadamente 95,000 líneas que se envían automáticamente a mi servidor una vez a la semana para actualizar algunos contenidos en mi sitio web. El problema es que no puedo asignar suficiente memoria para procesar un archivo tan grande, por lo que quiero dividir el archivo grande en archivos más pequeños con 5.000 líneas cada uno.

No puedo usar file () en absoluto hasta que el archivo se divide en fragmentos más pequeños, por lo que he estado trabajando con SplFileObject. Pero no he llegado a ninguna parte con eso. Aquí hay un pseudocódigo de lo que quiero lograr:

read the file contents while there are still lines left to be read in the file create a new file write the next 5000 lines to this file close this file for each file created run mysql update queries with the new content delete all of the files that were created 

El archivo está en formato csv.

EDITAR: Aquí está la solución para leer el archivo por línea dadas las respuestas a continuación:

 function getLine($number) { global $handle, $index; $offset = $index[$number]; fseek($handle, $offset); return explode("|",fgets($handle)); } $handle = @fopen("content.txt", "r"); while (false !== ($line = fgets($handle))) { $index[] = ftell($handle); } print_r(getLine(18437)); fclose($handle); 

Si su archivo grande está en formato CSV, creo que debe procesarlo línea por línea y no necesita dividirlo en archivos más pequeños. ¡No debería haber ninguna necesidad de contener 5.000 o más líneas en la memoria a la vez! Para hacer eso, simplemente use las funciones de archivo de “bajo nivel” de PHP:

 $fp = fopen("path/to/file", "r"); while (false !== ($line = fgets($fp))) { // Process $line, eg split it into values since it is CSV. $values = explode(",", $line); // Do stuff: Run MySQL updates, ... } fclose($fp); 

Si necesita acceso aleatorio, por ejemplo, lea un número de línea por línea, puede crear un “índice de línea” para su archivo:

 $fp = fopen("path/to/file", "r"); $index = array(0); while (false !== ($line = fgets($fp))) { $index[] = ftell($fp); // get the current byte offset } 

Ahora $index asigna los números de línea a las compensaciones de bytes y puede navegar a una línea usando fseek() :

 function get_line($number) { global $fp, $index; $offset = $index[$number]; fseek($fp, $offset); return fgets($fp); } $line10 = get_line(10); // ... Once you are done: fclose($fp); 

Tenga en cuenta que comencé a contar líneas en 0, a diferencia de los editores de texto.

 //MySQL Connection Stuff goes here $handle = fopen('/path/to/bigfile.txt','r'); //open big file with fopen $f = 1; //new file number while(!feof($handle)) { $newfile = fopen('/path/to/newfile' . $f . '.txt','w'); //create new file to write to with file number for($i = 1; $i <= 5000; $i++) //for 5000 lines { $import = fgets($handle); fwrite($newfile,$import); if(feof($handle)) {break;} //If file ends, break loop } fclose($newfile); //MySQL newfile insertion stuff goes here $f++; //Increment newfile number } fclose($handle); 

Esto debería funcionar, el archivo grande debería ir a través de 5000 líneas por archivo, y los archivos de salida como newfile1.txt, newfile2.txt, etc., se pueden ajustar con el $i <= 5000 bit en el ciclo for.

Oh, ya veo, quieres hacer una inserción en los datos del archivo grande, no almacenar la información sobre los archivos. Luego solo usa fopen / fgets e inserta hasta feof.

Puede usar fgets para leer línea por línea.

Tendrá que crear una función para poner el contenido de lectura en un nuevo archivo. Ejemplo:

 function load(startLine) { read the original file from a point startline puts the content into new file } 

Después de esto, puede llamar a esta función recursivamente para pasar la línea de startline en la función en cada ciclo de lectura.

Esto debería ser el truco para ti, no tengo un archivo de texto muy grande, pero probé con un archivo de 1300 líneas y dividió el archivo en 3 archivos:

  // Store the line no: $i = 0; // Store the output file no: $file_count = 1; // Create a handle for the input file: $input_handle = fopen('test.txt', "r") or die("Can't open output file."); // Create an output file: $output_handle = fopen('test-'.$file_count.'.txt', "w") or die("Can't open output file."); // Loop through the file until you get to the end: while (!feof($input_handle)) { // Read from the file: $buffer = fgets($input_handle); // Write the read data from the input file to the output file: fwrite($output_handle, $buffer); // Increment the line no: $i++; // If on the 5000th line: if ($i==5000) { // Reset the line no: $i=0; // Close the output file: fclose($output_handle); // Increment the output file count: $file_count++; // Create the next output file: $output_handle = fopen('test-'.$file_count.'.txt', "w") or die("Can't open output file."); } } // Close the input file: fclose($input_handle); // Close the output file: fclose($output_handle); 

El problema que ahora puede encontrar es que el tiempo de ejecución es demasiado largo para el script cuando está hablando de un archivo de 200 + mb.

Si esto se está ejecutando en un servidor Linux, simplemente haga que php haga que la línea de comando ejecute lo siguiente:

split -l 5000 -a 4 test.txt out

A continuación, agregue los resultados de los nombres de archivo que puede abrir.


Creo que tu algo es incómodo, parece que estás descomponiendo archivos sin ningún motivo. Si simplemente abre el archivo de datos inicial y lo lee línea por línea, puede preformar la inserción de mysql y luego eliminar el archivo.