2012-05-10 14 views
6

Tengo un archivo ASCII donde cada línea contiene un registro de longitud variable. Por ejemplolectura y escritura en fragmentos en Linux usando c

Record-1:15 characters 
Record-2:200 characters 
Record-3:500 characters 
... 
... 
Record-n: X characters 

Como el tamaño de los archivos es de aproximadamente 10 GB, me gustaría leer el registro en trozos. Una vez leído, necesito transformarlos, escribirlos en otro archivo en formato binario.

Por lo tanto, para la lectura, mi primera reacción fue la de crear una matriz de caracteres tales como

FILE *stream; 
char buffer[104857600]; //100 MB char array 
fread(buffer, sizeof(buffer), 104857600, stream); 
  1. ¿Es correcto asumir que Linux emitirá una llamada al sistema a buscar todo el 100MB?
  2. Como los registros están separados por una nueva línea, busco carácter por carácter para un nuevo carácter de línea en el búfer y reconstruyo cada registro.

Mi pregunta es, ¿esta es la forma en que debería leer en trozos o hay una mejor alternativa para leer datos en trozos y reconstituir cada registro? ¿Hay una forma alternativa de leer x número de líneas de tamaño variable desde un archivo ASCII en una sola llamada?

Siguiente durante la escritura, hago lo mismo. Tengo un buffer de escritura de char, que paso a fwrite para escribir un conjunto completo de registros en una llamada.

fwrite(buffer, sizeof(buffer), 104857600, stream); 

ACTUALIZACIÓN: Si i setbuf (corriente, tampón), donde búfer es mi char buffer de 100MB, sería FGETS regreso de búfer o hacer que un disco IO?

+0

Buscar en los datos, obtendrá una línea por vez si lo desea. –

+0

Me gustaría evitar leer línea por línea, pero preferiría leer X líneas de tamaño variable en una sola toma. Además, con fgets(), tendría que tener un búfer en el que la línea más larga podría caber. Como mi tamaño de registro puede variar de unos 100 bytes a 16 MB, estaría desperdiciando memoria. – Jimm

Respuesta

6
  1. Sí, fread buscará todo al mismo tiempo. (Suponiendo que es un archivo normal). Pero no leerá 105 MB, a menos que el archivo en sí sea de 105 MB, y si no verifica el valor de retorno no tiene forma de saber cuántos datos se leyeron realmente, o si fue un error

  2. Use fgets (vea man fgets) en lugar de fread. Esto buscará los saltos de línea para usted.

    char linebuf[1000]; 
    FILE *file = ...; 
    while (fgets(linebuf, sizeof(linebuf), file) { 
        // decode one line 
    } 
    
  3. Existe un problema con el código.

    char buffer[104857600]; // too big 
    

    Si intenta asignar un búfer de gran tamaño (105 MB es ciertamente grande) en la pila, a continuación, se producirá un error y el programa se bloqueará. Si necesita un búfer tan grande, deberá asignarlo en el montón con malloc o similar. Ciertamente, mantendría el uso de la pila para una función única en decenas de KB como máximo, aunque probablemente podría salirse con la suya con unos pocos MB en la mayoría de los sistemas Linux.

Como alternativa, puede simplemente mmap guardar todo el archivo en la memoria. Esto no mejorará ni degradará el rendimiento en la mayoría de los casos, pero es más fácil trabajar con él.

int r, fdes; 
struct stat st; 
void *ptr; 
size_t sz; 

fdes = open(filename, O_RDONLY); 
if (fdes < 0) abort(); 
r = fstat(fdes, &st); 
if (r) abort(); 
if (st.st_size > (size_t) -1) abort(); // too big to map 
sz = st.st_size; 
ptr = mmap(NULL, sz, PROT_READ, MAP_SHARED, fdes, 0); 
if (ptr == MAP_FAILED) abort(); 
close(fdes); // file no longer needed 

// now, ptr has the data, sz has the data length 
// you can use ordinary string functions 

La ventaja de usar mmap es que el programa no se ejecutará sin memoria. En un sistema de 64 bits, puede colocar todo el archivo en su espacio de direcciones al mismo tiempo (incluso un archivo de 10 GB), y el sistema leerá automáticamente nuevos fragmentos a medida que su programa acceda a la memoria.Los viejos fragmentos se descartarán automáticamente y volverán a leerse si su programa los necesita de nuevo.

Es una forma muy agradable de abrir archivos grandes.

0

Mi opinión es usar fgets(buff) para detectar automáticamente la nueva línea.

y luego usar strlen(buff) para contar el tamaño del búfer,

if((total+strlen(buff)) > 104857600) 

luego escribir en el nuevo trozo ..

Pero el tamaño de la porción difícilmente será 104857600 bytes.

CMIIW

+0

En mi caso, buff es una matriz de caracteres. Pero la documentación de fgets indica que opera en una secuencia de tipo FILE. – Jimm

+0

[fgets] (http://www.cplusplus.com/reference/clibrary/cstdio/fgets/) hay 3 parámetros 'char *', 'int', y' FILE * '. Entonces, para su caso, coloque su búfer en 'char *' –

2

Si usted puede, usted podría encontrar que mmap ING el archivo será más fácil. mmap mapea un (parte de) un archivo en la memoria para que se pueda acceder al archivo completo esencialmente como una matriz de bytes. En su caso, puede que no sea capaz de asignar todo el archivo a la vez que se vería algo como:

#include <stdio.h> 
#include <sys/stat.h> 
#include <sys/types.h> 
#include <unistd.h> 
#include <sys/mman.h> 


/* ... */ 

struct stat stat_buf; 
long pagesz = sysconf(_SC_PAGESIZE); 
int fd = fileno(stream); 
off_t line_start = 0; 
char *file_chunk = NULL; 
char *input_line; 
off_t cur_off = 0; 
off_t map_offset = 0; 
/* map 16M plus pagesize to ensure any record <= 16M will always fit in the mapped area */ 
size_t map_size = 16*1024*1024+pagesz; 
if (map_offset + map_size > stat_buf.st_size) { 
    map_size = stat_buf.st_size - map_offset; 
} 
fstat(fd, &stat_buf); 
/* map the first chunk of the file */ 
file_chunk = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, map_offset); 
// until we reach the end of the file 
while (cur_off < stat_buf.st_size) { 
    /* check if we're about to read outside the current chunk */ 
    if (!(cur_off-map_offset < map_size)) { 
    // destroy the previous mapping 
    munmap(file_chunk, map_size); 
    // round down to the page before line_start 
    map_offset = (line_start/pagesz)*pagesz; 
    // limit mapped region to size of file 
    if (map_offset + map_size > stat_buf.st_size) { 
     map_size = stat_buf.st_size - map_offset; 
    } 
    // map the next chunk 
    file_chunk = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, map_offset); 
    // adjust the line start for the new mapping 
    input_line = &file_chunk[line_start-map_offset]; 
    } 
    if (file_chunk[cur_off-map_offset] == '\n') { 
    // found a new line, process the current line 
    process_line(input_line, cur_off-line_start); 
    // set up for the next one 
    line_start = cur_off+1; 
    input_line = &file_chunk[line_start-map_offset]; 
    } 
    cur_off++; 
} 

La mayor parte de la complicación es para evitar hacer demasiado grande de un mapeo. Es posible que pueda asignar el archivo completo usando

char *file_data = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_SHARED, fd, 0); 
Cuestiones relacionadas