2010-12-10 16 views
6

¿Hay un getline función que usa fread (bloque E/S) en lugar de fgetc (E/S de caracteres)?C: Lectura de un archivo de texto (con líneas de longitud variable) línea por línea usando fread()/fgets() en lugar de fgetc() (E/S de bloque frente a E/S de caracteres)

Hay una penalización de rendimiento al leer un archivo carácter por carácter a través de fgetc. Creemos que para mejorar el rendimiento, podemos usar lecturas de bloque a través de fread en el bucle interno de getline. Sin embargo, esto introduce el efecto potencialmente indeseable de leer más allá del final de una línea. Al menos, esto requeriría la implementación de getline para realizar un seguimiento de la parte "no leída" del archivo, que requiere una abstracción más allá de la semántica ANSI C FILE. ¡Esto no es algo que queremos implementar nosotros mismos!

Hemos perfilado nuestra aplicación, y el rendimiento lento se aísla al hecho de que estamos consumiendo archivos grandes carácter por carácter a través de fgetc. El resto de los gastos generales en realidad tiene un costo trivial en comparación. Siempre estamos leyendo secuencialmente cada línea del archivo, de principio a fin, y podemos bloquear todo el archivo durante la lectura. Esto probablemente hace que sea más fácil implementar un fread-based getline.

Entonces, ¿existe una función getline que utiliza fread (E/S de bloque) en lugar de fgetc (E/S de caracteres)? Estamos bastante seguros de que sí, pero si no, ¿cómo deberíamos implementarlo?

actualización Encontrado un artículo útil, Handling User Input in C, por Paul Hsieh. Es un enfoque basado en fgetc, pero tiene una interesante discusión de las alternativas (a partir de lo mal gets es decir, luego discutir fgets):

Por otro lado, la réplica común de los programadores de C (incluso los que se consideran con experiencia) es decir que fgets() se debe utilizar como una alternativa. Por supuesto, por sí mismo, fgets() realmente no maneja la entrada del usuario per se. Además de tener una condición de terminación de cadena extraña (al encontrar \ n o EOF, pero no \ 0) el mecanismo elegido para la terminación cuando el búfer ha alcanzado su capacidad es detener bruscamente la operación fgets() y \ 0 terminarlo. Por lo tanto, si la entrada del usuario excede la longitud del búfer preasignado, fgets() devuelve un resultado parcial. Para lidiar con este programador tenemos un par de opciones; 1) simplemente trate con la entrada de usuario truncada (no hay manera de retroalimentar al usuario que la entrada ha sido truncada, mientras están proporcionando entrada) 2) Simular una matriz de caracteres cultivables y completarla con llamadas sucesivas a fgets (). La primera solución es casi siempre una solución muy pobre para la entrada de usuarios de longitud variable porque el buffer será inevitablemente demasiado grande la mayor parte del tiempo porque intenta capturar demasiados casos comunes y demasiado pequeño para casos inusuales. La segunda solución está bien, excepto que puede ser complicado implementarla correctamente. Tampoco se ocupa de fgets ' comportamiento impar con respecto a' \ 0 '.

Ejercicio deja al lector: Con el fin de determinar la cantidad de bytes que realmente leído por una llamada a fgets(), se podría tratar en los escaneos, tal como lo hace, por un '\ n' y saltar sobre cualquier '\ 0' mientras que no excede el tamaño pasado a fgets(). Explica por qué esto es insuficiente para la última línea de una transmisión.¿Qué debilidad de ftell() impide que solucione este problema por completo?

Ejercicio deja al lector: resolver el problema de determinar la longitud de los datos consumidos por fgets() sobrescribiendo todo el tampón con un valor distinto de cero entre cada llamada a fgets().

Así que con fgets() Nos quedamos con la opción de escribir un montón de código y vivir con una condición de terminación de línea que es incompatible con el resto de la biblioteca C, o que tenga un límite arbitrario. Si esto no es lo suficientemente bueno, entonces, ¿qué nos queda? scanf() mezcla el análisis con lectura de una manera que no se puede separar, y fread() leerá más allá del final de la cadena. En resumen, la biblioteca C nos deja sin nada. Estamos obligados a rodar el nuestro basado en la parte superior de fgetc() directamente. Así que vamos a darle una oportunidad.

Por lo tanto, funciona un getline que se basa en fgets (y no trunca la entrada) existen?

+0

Para su nueva pregunta al final, sí, existe. Lo describí en mi respuesta. El artículo que ha mencionado menciona un problema con una línea final no terminada en una nueva línea; He hecho que esto no sea un problema al rellenar previamente el buffer con ''\ n'' y proporcionar una forma de detectar la condición. –

+1

También tenga en cuenta que la solución de Paul Hsieh para usar 'fgetc' es muy mala. En las implementaciones modernas, debido al requisito de admitir el bloqueo en caso de que varios hilos accedan al mismo objeto 'FILE', usar' fgetc' será muy lento. Puede usar 'getc_unlocked' (pero esta es una función POSIX, no una función C estándar), pero incluso con una macroexpansión óptima de' getc_unlocked', la forma 'fgets' busca en el buffer' '\ n'' (es decir, usar 'memchr') será mucho más rápido que cualquier cosa que pueda hacer sin acceder al búfer interno. También tenga en cuenta que si tiene POSIX (2008), ya tiene 'getline'. –

Respuesta

5

No utilice fread. Use fgets. Entiendo que este es un problema de tarea/proyecto de clase, así que no estoy dando una respuesta completa, pero si dices que no es así, te daré más consejos. Definitivamente es posible proporcionar el 100% de la semántica del estilo GNU getline, incluidos los bytes nulos incorporados, usando puramente fgets, pero requiere un pensamiento inteligente.

bien, la actualización ya que esto no es tarea:

  • memset su buffer para '\n'.
  • Use fgets.
  • Usa memchr para encontrar la primera '\n'.
  • Si no se encuentra '\n', la línea es más larga que su búfer. Amplíe el buffer, llene la nueva porción con '\n' y fgets en la nueva porción, repitiendo según sea necesario.
  • Si el carácter que sigue a '\n' es '\0', entonces fgets finalizó debido al alcance de una línea.
  • De lo contrario, fgets terminado debido a alcanzar EOF, la '\n' es un remanente de su memset, el carácter anterior es la terminación nula de que fgets escribió, y el carácter anterior que es el último carácter de los datos reales leídos.

Puede eliminar la memset y utilizar en su lugar de strlenmemchr si no se preocupan por el apoyo a líneas con nulos incrustados (en cualquier caso, la hipótesis nula no se terminará, la lectura, sino que sólo va a ser parte de su lectura en línea).

También hay una manera de hacer lo mismo con fscanf y la "%123[^\n]" especificador (donde 123 es su límite de búfer), que le da la flexibilidad necesaria para parar en caracteres que no sean de nueva línea (ALA) getdelim GNU.Sin embargo, es probable que sea lento a menos que su sistema tenga una implementación muy elegante de scanf.

+0

Esto no es tarea ... :) ¿Cómo sugerirías usar 'fgets'? Usar una matriz de caracteres que pueda crecer y llenarla con llamadas sucesivas a 'fgets' parece complicado de implementar correctamente. Además, entiendo que 'fgets' termina al encontrar '\ n' o EOF, pero no '\ 0'. Sin embargo, esto no es un problema para nuestros archivos. –

+1

@R .. Un agujero menor: después de usar 'char s [5]; memset (s, '\ n', sizeof s); fgets (s, sizeof s, ...); 'en un archivo con 3 bytes" xyz "lleva a" xyz \ 0 \ n "en' s'. Encontrar el primer ''\ n'' está bien, pero verificar el siguiente carácter es UB. Sugerir agregar "Si '\ n' en el último lugar, entonces' fgets' terminado debido a llegar a la última línea del archivo. " luego vaya a "Si el personaje siguiente ..." – chux

+0

Me pregunto por qué tantas funciones relacionadas con cadenas tienen valores de retorno comparativamente inútiles? El código que llama a 'strcat' y' fgets' a menudo necesitará encontrar el último carácter escrito, algo que el código de esas funciones ya habrá conocido. No puedo pensar en ninguna utilidad para el valor de retorno de esas funciones implementadas. – supercat

1

No hay una gran diferencia de rendimiento entre fgets y fgetc/setvbuf. Probar:

int c; 
FILE *f = fopen("blah.txt","r"); 
setvbuf(f,NULL,_IOLBF,4096); /* !!! check other values for last parameter in your OS */ 
while((c=fgetc(f))!=EOF) 
{ 
    if(c=='\n') 
    ... 
    else 
    ... 
} 
Cuestiones relacionadas