1 #LyX 1.3 created this file. For more info see http://www.lyx.org/
11 \paperpackage widemarginsa4
15 \use_numerical_citations 0
16 \paperorientation portrait
19 \paragraph_separation indent
21 \quotes_language english
25 \paperpagestyle default
29 Organización de Datos (75.06)
34 \begin_inset Formula $\mu$
50 Leandro Lucarella (77891)
52 Ricardo Markiewicz (78226)
55 Segunda Entrega, 31 de Mayo de 2004
59 \begin_inset LatexCommand \tableofcontents{}
69 En esta entrega el trabajo estuvo concentrado en el manejo de índices para
70 los tipos de archivos implementados en la primer entrega.
71 Los índices se implementaron con:
83 Además de esto, se pide 3 funciones distintas para estos índices:
95 Con la autorización de los ayudantes de la cátedra decidimos que el árbol
96 B+ sólo pueda ser utilizado para índices principal ya que de otra manera
97 no tiene sentido el set secuencial.
100 Finalmente, para obtener listados basados en campos de los cuales no se
101 tiene un índice, se implementó un ordenamiento externo.
104 A continuación se presenta una descripción un poco más detallada sobre todas
105 herramientas utilizadas para resolver el trabajo práctico.
108 Documentación de la API
111 Para obtener una documentación de la API más completa, se incluye en formato
112 HTML en el CD-ROM la documentación generado con Doxygen.
113 Esta documentación se encuentra en el directorio
115 doc/api/html/index.html
126 Se detallan a continuación los tipos de datos definidos y utilizados en
127 las distintas implementaciones que conforman nuestro sistema, siendo el
128 más importante de ellos en esta entrega, la estructura
132 que actúa como interfaz común para el manejo de cualquier tipo de índice
133 (no importa que tipo de organización física ni de que forma esté implementado,
134 esta estructura proveerá una interfaz abstracta para su manejo).
140 Se agregaron varios tipos comunes nuevos en esta entrega, en su mayoría
141 relacionados a los índices.
142 Estos tipos son brevemente descriptos a continuación y pueden ser hallados
154 : usado para representar el conjunto de un ID más su dato.
161 : indica el tipo de índice (B, B* o B+).
168 : indica la función que cumple el índice (principal, selectivo o exhaustivo).
175 : indica el tipo de dato que se usa como clave.
182 : representa una clave de un índice.
195 es la estructura principal que encapsula todas las funciones para el manejo
197 Posee punteros a funciones que permite utilizar la misma interfaz para
198 distintas implementaciones de árboles.
202 Su declaración puede ser observada en el archivo
209 y cuenta con la siguiente información:
215 Tipo de dato que maneja.
221 Información sobre el desplazamiento para ubicar el dato dentro de la estructura
222 a indexar (para poder tener una implementación genérica que sirva para
223 cualquier estructura).
226 Información sobre archivos auxiliares para almacenar cadenas de texto y
227 otras estructuras que eventualmente requiera un índice.
230 Punteros a funciones para:
240 Verificar la existencia de una entrada.
246 Obtener clave menor o mayor del índice.
249 Obtener siguiente clave (para recorrido secuencial).
253 Esta estructura define los valores de sus punteros según el tipo de implementaci
254 ón que se desee manejar y esto se realiza a través de la API
263 Esta API posee funciones para crear y destruir índices, agregarlos y quitarlos
268 , comparar claves y otras, necesarias para la correcta y completa utilización
269 de los índices a través de la interfaz de
273 descripta en la entrega anterior.
274 \layout Subsubsection
283 Para integrar la utilización de índices a
287 fueron necesarios los siguientes cambios:
290 Nuevos tipos de archivo.
293 Se incluyen dos tipos de archivo nuevos T4 y T5, que representan, respectivament
294 e, un archivo T1 (registros variables, bloques fijos) y un archivo T3 (registros
295 y bloques fijos), ambos organizados como un set secuencial indexado.
296 De esta manera se conserva la interfaz de
300 (los punteros a funciones) incluso cuando se debe insertar de forma ordenada,
301 ya que al saber que es T4 o T5 siempre se inserta de forma ordenada.
304 Puntero a un arreglo de índices.
307 Se agrega a la estructura
311 un puntero a un arreglo de
315 , donde el primero es siempre el índice principal.
318 Especificaciones de índices
330 Una de las pocas decisiones que tuvimos que tomar fue la forma de manejar
332 Hay dos formas comunes de hacerlo:
335 Permitir que el nodo raíz pueda almacenar 2N+1 claves (siendo N el número
336 máximo de claves permitido por nodo).
339 Hacer que se comporte como un árbol B.
342 La primera forma garantiza un mejor aprovechamiento del espacio, ya que
343 se sigue haciendo una partición en 3 nodos hijo con 2/3 de los espacios
345 El problema que encontramos para hacerlo de esa forma fue que usamos un
346 tamaño de nodo fijo de 512 para poder leer un sector completo del disco
347 y ganar algo de velocidad, por lo que para poder mantener este esquema
348 hubiéramos necesitado de 3 bloques de 512 para poder guardar los 2N+1 claves,
349 desperdiciando 512-tamaño_de_clave espacio en el bloque final y haciendo
350 que cualquier ahorro de espacio en los hijos del nodo raíz difícilmente
354 Además de esto, el utilizar la segunda forma trae como ventaja la reutilización
355 de código del árbol B, lo que facilita la implementación y el mantenimiento
359 Estas son las dos razones principales por las cuales elegimos tratar el
360 nodo raíz como lo hace el árbol B.
363 Indice B+ Organizacion Secuencial Indexada
369 Para la implementación de la organización secuencial indexada de archivos,
370 se ha utilizado un árbol B+, conocido por ser utilizado en términos generales
371 para implementaciones de esta índole.
374 Como particularidad el arbol B+, poseerá en sus hojas todas las claves que
375 se hayan insertado en el árbol.
376 No obstante, las mismas no serán todas las claves que se encuentren en
377 el archivo de datos, sino la primer clave de cada bloque de datos, también
378 denominadas 'anclas de bloque'.
381 En torno a esta distinción respecto de los demás arboles, el árbol B+ nos
382 indicará a la hora de grabar registros en nuestro archivo de datos con
383 bloques (Organización del TP1, Tipo1 o 3), en que bloque de datos debemos
384 realizar la mencionada inserción.
385 La operativa se detalla mas adelante, pero basicamente realizaremos una
386 búsqueda del ancla menor inmediata a la clave del registro que se desea
387 insertar, y esto nos indicara el bloque apropiado.
388 (el bloque donde esta el ancla).
391 Como resultado concreto de este comportamiento (teniendo en cuenta también
392 el borrado y partición de bloques del .dat), obtendremos un archivo secuencial
393 indexado, en donde los registros se encuentran ordenados a nivel de bloques,
394 esto es, dentro de un bloque dado del archivo de datos, los registros estan
395 ordenados por clave primaria.
396 No obstante, los bloques no estarán necesariamente ordenados, pero igualmente
397 la cantidad de accesos para recorrer el archivo en forma secuencial, se
398 ve minimizada respecto de otras organizaciones, gracias al encadenamiento
399 de las hojas y la posesión de las anclas de bloque, cuya lista resultante
400 del encadenamiento es denominada
408 Para comprender mejor la implementación particular que hemos dado al árbol
409 B+, damos una breve reseña de la estructura de un nodo del arbol, la cual
425 Esta estructura se encuentra en el archivo
430 Esta organización permite, con la ayuda del árbol, mantener el archivo de
431 datos ordenado por la clave principal.
434 Para lograr esto, como fue expuesto anteriormente, el árbol nos indicará
435 donde (en qué bloque) debe insertarse un registro.
436 (ver 3.3.1 Inserción)
439 En el caso de una hoja, dado que cada nodo posee un hijo mas que la cantidad
440 de claves, el hijo que sobra será utilizado como referencia al nodo
441 \begin_inset Quotes eld
445 \begin_inset Quotes erd
448 , lo cual constituye el
449 \begin_inset Quotes eld
453 \begin_inset Quotes erd
457 Para un nodo que no sea hoja el hijo será el número de nodo correspondiente
458 según la clave, es decir, para la clave
470 contiene claves menores y el hijo
476 contiene las claves mayores.
477 En el caso particular del nivel 1 (index set) el hijo
483 (sequence set) contiene las claves mayores o iguales ya que el
484 \begin_inset Quotes eld
488 \begin_inset Quotes erd
491 debe contener todas las claves insertadas, esto produce que exista una
492 repetición de las claves entre el nivel 1 y el 0.
495 En nuestro caso hemos implementado un Secuencial Indexado tipo ISAM (Indexed
496 Sequential Access Method) el cual posee en sus hojas las anclas de cada
497 bloque en el archivo de datos, es decir, solo se guardan en los nodos del
498 árbol la menor de las claves de un bloque del archivo de datos, acompañada
499 cada clave por el numero de bloque al cual pertenece.
502 Hemos adoptado que la cantidad de claves máxima en un nodo del árbol sea
503 una cantidad impar, ya que esto facilita la elección de la clave que será
504 promovida hacia su nodo padre en caso de que se produzca un overflow en
511 Para realizar una inserción en el archivo de datos se debe realizar una
512 consulta en el árbol, la cual nos indicará el número de bloque donde debemos
513 insertar el nuevo registro.
516 Las consultas se realizan a través de una estructura INDEX_DAT que posee:
525 Esta estructura se encuentra en el archivo
530 El modo de uso es el siguiente:
533 En primer lugar se carga la clave a insertar en el campo Clave, y en el
534 campo Número de Bloque se almacena un número de bloque válido, mas adelante
535 se explica el por qué.
538 Luego se invoca a la función
540 int emufs_b_plus_get_bloque(INDICE, INDEX_DAT)
542 la cual recibe como parámetro una estructura de índice y un INDEX_DAT para
543 realizar la consulta.
546 Esta función recorre recursivamente el árbol y busca una clave mayor inmediata
547 a la enviada, siempre culminando la búsqueda en una hoja.
548 Al encontrar la clave mayor inmediata, el resultado de la búsqueda será
549 la clave anterior en el nodo, pues cada clave en el nodo es un ancla de
550 bloque de datos, de esta manera la clave anterior será menor a la clave
551 enviada, pues las claves en las hojas están ordenadas.
555 En este paso pueden suceder dos cosas:
558 Que exista una clave menor a la enviada.
561 Que la clave enviada sea menor a todas las claves del árbol.
564 En el primer caso, se ha encontrado la clave y se carga la estructura con
565 el hijo de esa clave, que será el número de bloque donde debe insertarse
566 el nuevo registro (por el cual se realizó la consulta), sobreescribiendo
567 el valor que almacenaba al ingresar, y la función retornará código 0 que
568 indica que se ha encontrado un bloque donde insertar.
571 En el segundo caso, puede darse que la clave enviada sea menor a todas las
572 claves del árbol, por lo cual no es posible encontrar un ancla de bloque
574 Aquí la función retornará código -1 lo cual indica que no se ha encontrado
575 un bloque donde insertar el registro nuevo, y es por esto que la estructura
576 debe inicializarse con un número de bloque válido antes de realizarse la
577 consulta.De esta manera el árbol indica donde debe insertarse un nuevo registro
578 en el archivo de datos.
581 Otro detalle de la inserción es que cuando el árbol indica donde debe insertarse
582 un registro pueden pasar dos cosas nuevamente:
585 Que el registro quepa en el bloque.
588 Que el registro no quepa en el bloque.
591 El primer caso es trivial y el registro se insertará sin problemas en el
595 En el caso que el registro no quepa en el bloque, se deberán separar los
596 registros del bloque en 2 bloques, en original y uno nuevo, cada uno con
597 la mitad (aproximadamente) de los registros.
601 Al partir el bloque el ancla del bloque original no se modificará, pero
602 en el bloque nuevo se crea una nueva anlca de bloque, pues una de las claves
603 pertenecientes a los registros que contiene, será la menor.
606 Antes de actualizar el árbol con el ancla nueva, habrá que discriminar en
607 qué bloque se debe insertar el registro nuevo.
608 Para ello se compara la menor de las claves del nuevo bloque con la clave
609 del registro, si la clave del registro es menor que el ancla del nuevo
610 bloque, este debe ir en el bloque original, y se inserta ordenado en él
611 y se le informa al árbol que actualice (inserte) una nueva clave correspondient
612 e al bloque nuevo, sino se inserta en el bloque nuevo de forma ordenada
613 y en este caso cabe la posibilidad de que el nuevo registro posea la clave
614 mas pequeña de todas en el bloque, por ello se lo inserta ordenadamente
615 con ayuda de la función
617 CLAVE grabar_ordenado_en_bloque(EMUFS *emu, void *ptr, EMUFS_REG_SIZE size,
618 void *bloque, int num_bloque, EMUFS_FREE fs, int *err)
620 la cual inserta el registro ordenado por CLAVE y devuelve la menor de las
621 claves del bloque, que se usará para informarle al árbol que inserte una
622 clave nueva junto con el número de bloque, para indexar este bloque.
628 El proceso de eliminación es bastante similar al de inserción en el sentido
629 que también hay que realizar una consulta en el árbol para obtener el número
630 de bloque al que pertenece una clave.
631 Una vez conocido este número se levanta el bloque correspondiente y se
632 busca secuencialmente el registro que se debe eliminar.
635 Si el registro a eliminar fuera el primero del bloque, habrá que modificar
636 el ancla de bloque en el árbol con el ancla que corresponda a la clave
637 del nuevo menor registro, y si el que se elimina fuera el único registro
638 en el bloque habrá que eliminar la clave del árbol.
641 En cualquier otro caso, solo se eliminará el registro correspondiente y
642 se justificarán los registros a izquierda.
648 El proceso de búsqueda de un registro por su clave de identificación primaria
649 en la Organización Secuencial Indexada, es bastante directa en su entendimiento.
650 Para buscar un registro, acudiremos al árbol B+ con la clave anteriormente
651 mencionada, y obtendremos del mismo, el número de bloque donde se debe
652 encontrar el registro.
655 Para obtener dicho número de bloque, el árbol internamente busca el ancla
656 menor inmediata a la clave buscada y luego devuelve el número de bloque
657 de datos donde está dicha ancla (el nro bloque será el dato asociado a
658 la clave ancla, en el árbol), el cual será el bloque potencial donde se
659 encuentre el registro buscado.
662 Una desventaja de esta implementación con indexación parcial, es que no
663 sabremos si el registro se encuentra efectivamente en el bloque indicado,
664 hasta no buscarlo dentro del mismo en formal secuencial.
665 Si lo hallamos, daremos por finalizada la búsqueda del registro.
668 Recorrida secuencial de registros
671 Una consecuencia importante de la organización secuencial indexada, en este
672 caso implementada a través de un árbol B+ con indexación parcial, es que
673 como mencionamos anteriormente, los registros dentro de un bloque se encuetran
674 ordenados, y si bien los bloques en si pueden no estar ordenados en el
675 archivo de datos, algunos lo estarán, y minimizarán en base a estas característ
676 icas, los tiempos de acceso para una recorrida secuencial de registros.
679 Suponiendo que nos encontramos con varios registros cargados en esta organizació
680 n de archivo, y con el correspondiente árbol de indexación primaria B+ en
681 el disco, si se nos pidiera por ejemplo, recorrer los registros de articulos
682 desde el ID_Articulo 40, hasta el ID_Articulo 1406, la operativa será la
686 Acudimos al árbol y obtenemos el numero de bloque para el ID_Articulo 40,
687 buscando el ancla menor inmediata y obteniendo el nro de bloque de datos
688 donde se encuentra, siendo este el nro de bloque donde debería estar el
689 artículo de clave ID_Articulo 40.
692 Levantamos el bloque de datos y lo recorremos secuencialmente, pues como
693 dijimos anteriormente, sus registros se encuentran ordenados por la clave
694 de identificación primaria, en este caso ID_Articulo.
697 Cuando ya hayamos procesado todo el bloque, debemos obtener la siguiente
698 ancla a través del árbol y repetir el proceso.
701 NOTA: Cabe desatacar, que todas las anclas estan en las hojas del B+, y
702 por ello las recorremos a nivel hojas a traves del Sequence Set, que gracias
703 a su encadenamiento, nos permite obtener en forma muy directa y efectiva,
704 las anclas de bloque ordenadas en secuencia, y por consiguiente, recorrer
705 el archivo en forma secuencial minimizando accesos.
711 Descripción del algoritmo
714 Luego de buscar varias alternativas sobre algoritmos de ordenamiento externo,
715 se optó por el siguiente (que resultó una mezcla de las alternativas analizadas
719 Tomar uno a uno los registros del archivo a ordenar e
723 en un buffer ordenado hasta llenar el buffer.
726 Quitar el menor de los valores (
730 uno nuevo desde el archivo a ordenar) e insertarlo en un archivo temporal.
733 Quitar del buffer el mínimo valor mayor al último insertado en el archivo
738 nuevamente un registro obtenido del archivo a ordenar) y se lo inserta
739 en el archivo temporal.
740 De esta forma quedan ordenados los registros en el archivo temporal.
743 Repetir el paso 3 hasta que se vacíe el buffer o hasta que no haya ningún
744 valor mayor al último insertado en el archivo temporal.
745 Cuando esto suceda se crea un nuevo archivo temporal volviendo al paso
749 En este punto ya tenemos el buffer vacío y todos los valores del archivo
750 a ordenar repartidos en 1 o más archivos temporales ordenados, sólo queda
751 unir los archivos para volver a un sólo archivo completo y ordenado.
752 El procedimiento es simple:
755 Obtener el mínimo valor de los archivos temporales e insertarlo en el archivo
759 Repetir 1 hasta agotar los registros de todos los archivos temporales.
762 Debe quedar claro que los archivos temporales se comportan como una cola.
763 Es decir que al obtener un registro de un archivo temporal se obtiene el
764 primer registro que se haya insertado (el mínimo por la forma en la que
771 A continuación se presenta un ejemplo para una más fácil comprensión del
775 Supongamos que queremos ordenar un archivo con registros de números enteros
776 (el archivo se lee de izquierda a derecha): 9 6 34 2 8 3 12 43 23 4 19
780 Supongamos que disponemos de un buffer capaz de almacenar 3 registros.
783 Se llena el buffer ordenado
786 Se lee 9 del archivo original y se lo inserta en el buffer ordenado.
790 Se lee 6 del archivo original y se lo inserta en el buffer ordenado.
794 Se lee 34 del archivo original y se lo inserta en el buffer ordenado.
798 Se crea el archivo temporal ordenado 1
801 Se lee el mínimo valor del buffer (6), se lo inserta en el archivo temporal
802 y se carga un nuevo valor del archivo original al buffer (2).
807 Se lee el mínimo valor del buffer mayor al insertado anteriormente (9),
808 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
809 original al buffer (8).
814 Se lee el mínimo valor del buffer mayor al insertado anteriormente (34),
815 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
816 original al buffer (3).
821 No hay más valores en el buffer mayores al último insertado (34), fin del
825 Se crea el archivo temporal ordenado 2
828 Se lee el mínimo valor del buffer (2), se lo inserta en el archivo temporal
829 y se carga un nuevo valor del archivo original al buffer (12).
834 Se lee el mínimo valor del buffer mayor al insertado anteriormente (3),
835 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
836 original al buffer (43).
841 Se lee el mínimo valor del buffer mayor al insertado anteriormente (8),
842 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
843 original al buffer (23).
848 Se lee el mínimo valor del buffer mayor al insertado anteriormente (12),
849 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
850 original al buffer (4).
855 Se lee el mínimo valor del buffer mayor al insertado anteriormente (23),
856 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
857 original al buffer (19).
859 Archivo2: 2 3 8 12 23
862 Se lee el mínimo valor del buffer mayor al insertado anteriormente (43),
863 se lo inserta en el archivo temporal y se carga un nuevo valor del archivo
864 original al buffer (21).
866 Archivo2: 2 3 8 12 23 43
869 No hay más valores en el buffer mayores al último insertado (43), fin del
873 Se crea el archivo temporal ordenado 3
876 Se repite el proceso anterior.
881 Se crea el archivo temporal ordenado 4
884 Se repite el proceso anterior.
886 Archivo4: 1 16 36 42 65
889 Se mezclan los archivos temporales ordenados obteniendo un archivo completo
893 Se obtiene el menor valor de los archivos temporales ordenados (sólo tenemos
894 que elegir entre el primer valor de cada uno).
898 Archivo2: 2 3 8 12 23 43.
899 Archivo3: 4 19 21 87.
900 Archivo4: 1 16 36 42 65
903 Sólo debo comparar y obtener el menor entre 6, 2, 4, y 1.
904 Obtengo el 1, lo saco del archivo temporal y lo agrego al de salida:
908 Archivo2: 2 3 8 12 23 43.
909 Archivo3: 4 19 21 87.
910 Archivo4: 16 36 42 65 Salida: 1
913 Repito hasta que no hayan más valores en los archivos temporales:
917 Archivo2: 3 8 12 23 43.
918 Archivo3: 4 19 21 87.
919 Archivo4: 16 36 42 65.
924 Archivo2: 8 12 23 43.
925 Archivo3: 4 19 21 87.
926 Archivo4: 16 36 42 65.
931 Archivo2: 8 12 23 43.
933 Archivo4: 16 36 42 65.
938 Archivo2: 8 12 23 43.
940 Archivo4: 16 36 42 65.
945 Archivo2: 8 12 23 43.
947 Archivo4: 16 36 42 65.
954 Archivo4: 16 36 42 65.
961 Archivo4: 16 36 42 65.
962 Salida: 1 2 3 4 6 8 9
968 Archivo4: 16 36 42 65.
969 Salida: 1 2 3 4 6 8 9 12
976 Salida: 1 2 3 4 6 8 9 12 16
983 Salida: 1 2 3 4 6 8 9 12 16 19
990 Salida: 1 2 3 4 6 8 9 12 16 19 21
997 Salida: 1 2 3 4 6 8 9 12 16 19 21 23
1004 Salida: 1 2 3 4 6 8 9 12 16 19 21 23 34
1011 Salida: 1 2 3 4 6 8 9 12 16 19 21 23 34 36
1018 Salida: 1 2 3 4 6 8 9 12 16 19 21 23 34 36 42
1025 Salida: 1 2 3 4 6 8 9 12 16 19 21 23 34 36 42 43
1032 Salida: 1 2 3 4 6 8 9 12 16 19 21 23 34 36 42 43 65
1039 Salida: 1 2 3 4 6 8 9 12 16 19 21 23 34 36 42 43 65 87
1045 Finalmente, tengo en el archivo de salida el archivo original ordenado.
1051 El algoritmo de ordenamiento es completamente genérico, ya que recibe un
1052 puntero void como registro, su tamaño (para poder manipularlo sin conocer
1053 su tipo) y una función de comparación, para poder comparar dos registros
1054 (sin saber su tipo) a través de una relación de orden (descripta por dicha
1058 Decisiones de diseño.
1061 El algoritmo se eligió en base a una serie de razones y cuenta con una serie
1062 de ventajas y desventajas.
1065 El algoritmo es simple, tanto teóricamente como para implementar.
1068 Tiene la desventaja de que puede llegar a usar muchos archivos temporales
1069 y todos abiertos al mismo tiempo, pero considerando que el sistema operativo
1070 en el que se utiliza suele manejar bien grandes cantidades de archivos
1071 no es una desventaja importante.
1074 Al usar un buffer intermedio, se puede controlar muy bien la cantidad de
1075 memoria que utiliza y experimentar con distintos valores para analizar
1079 El buffer ordenado se implementó con un árbol binario debido a que tiene
1080 una buena relación entre velocidad de búsqueda y facilidad de implementación.
1081 Al ser el principal determinante de la velocidad los accesos a disco no
1082 se creyó necesario buscar una alternativa más rápida para mantener el buffer
1083 ordenado en memoria, ya que no cambiaría de forma notable el tiempo total
1085 Otras posibilidades hubieran sido cargar todo el buffer en memoria y ordenarlo
1086 posteriormente (dependiendo del algoritmo de ordenamiento a utilizar puede
1087 ser más o menos rápido que el árbol y más o menos complicado de implementar)
1088 o hacer una búsqueda secuencial sobre un buffer desordenado (es más fácil
1089 de implementar pero claramente más lento).
1090 Una posible ventaja notable de leer el buffer primero y luego ordenarlo
1091 en memoria es que se necesita un sólo acceso al disco para llenar el buffer,
1092 mientras que al obtener uno a uno los valores puede generar muchos accesos
1094 Esto no debería ser muy notable ya que las funciones de acceso a archivos
1095 de la biblioteca estándar de C poseen un buffer interno, por lo que los
1096 accesos a disco probablemente sea muy poco aún cuando se obtienen uno a