11. seminář - Práce s binárními soubory

TEACHINGZPC2

Obsah binárního souboru

V binárních souborech jsou data zapsána ve stejné podobě, jako jsou v paměti. Pokud bychom si vzali hodnotu 123456 typu int a zapíšeme ji do textového souboru, tak se napíše řetězec "123456". Pokud zapíšeme hodnotu v binárním módu,tak se zapíšou 4 bajty 0...11110001001000000. Při otevírání binárních souborů používáme navíc v módu b.

Výhody a nevýhody binárních souborů

Binární soubory typicky zabírají méně místa než nekomprimovaný textový soubor. Práce s binárními soubory je rychlejší: např. nepotřebujeme znaky ”123456” převádět na číslo. Bohužel ne vždy jsou přenositelné na jiný počítač kvůli různě velkým typům.

Zápis binárních dat

K zápisu binárních dat slouží funkce fwrite, která vrací počet úspěsně zapsaných prvků a má hlavičku:

  
    size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
    
    // ptr    - Ukazatel na data která se mají zapsat do souboru.
    // size   - Velikost v bajtech jednotlivých prvků k zápisu.
    // count  - Počet prvků (každý velikosti size) určených k zápisu.
    // stream - Soubor pro zápis.

Příklad zápisu

    
    int numbers[] = {-1, 1, 9, 15, 16, 31};
    
    // otevreme soubor pro zapis binarnich dat
    FILE* file = fopen("numbers.dat", "wb");
    
    // zapiseme 6 polozek typu (velikosti) int,
    // ktera zacinaji na adrese "numbers" (=zacatek pole)
    fwrite(numbers, sizeof(int), 6, file);

    fclose(file);

Obsah souboru

Binární soubor nemá smysl otevírat v běžném textovém editoru. Ale existují editory, které binární soubory zobrazí v hexa zápise. Po zápisu čísel -1, 1, 9, 15, 16, 31 bychom tak mohli dostat:


    ffff ffff 0100 0000 0900 0000 0f00 0000
    1000 0000 1f00 0000

Příklad zápisu jedné proměnné


    double number = 3.14;
    FILE* file = fopen("numbers.dat", "wb");
    
    // Pozor, musime predat adresu. Identifikator pole je adresa
    // ale u bezne promenne musime adresu vynutit operatorem &
    // Pocet nastavime na 1, protoze ukladame 1 polozku.
    fwrite(&number, sizeof(double), 1, file);

    fclose(file);

Čtení binárních dat

Ke čtení binárních dat slouží funkce fread, která vrací počet úspěsně zapsaných prvků a má hlavičku:


    size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
    
    // ptr    - Je adresa paměti, kam se mají data načíst. 
    // size   - Udává velikost jednoho přečteného prvku. 
    // count  - Je počet přečtených prvků.
    // soubor - Je ukazatel na soubor, ze kterého se čte.


Příklad čtení dat


    // Obsah souboru: -1, 1, 9, 15, 16, 31
    
    #define SIZE 6
    
    int loaded_numbers[SIZE];
    //nebo
    int *loaded_numbers = (int*) malloc(SIZE * sizeof(int));
    
    // otevreme pro cteni v binarnim rezimu!
    FILE* file = fopen("numbers.dat", "rb");

    // Precteme 6 polozek typu int do pole nactena_cisla
    // nactena_cislaje ukazatel na zacatek pole
    fread(loaded_numbers, sizeof(int), SIZE, file);

    // Vytiskne se: -1, 31
    printf("%i, %i\n", loaded_numbers[0], loaded_numbers[5]);
    
    fclose(file);

Příklad čtení jedné proměnné


    // V souboru je 3.14
    // Otevreme soubor pro cteni v binarnim rezimu
    FILE* file = fopen("number.dat", "rb");

    // Promenne, do ktere nacteme cislo
    double number;

    // Musime predat adresu, kam to nacteme
    // Nacteme jednu polozku o velikost double
    fread(&number, sizeof(double), 1, file);

    // Vypise se 3.14
    printf("%g\n", number);
    fclose(file);

Uložení/čtení více proměnných


    int number = 213, loaded_number;
    char string[] = "ahoj", loaded_string[5];
    
    // zapis do souboru
    FILE *file = // ... Otevreni souboru pro zapis
    fwrite(&number, sizeof(int), 1, file); // zapiseme int
    fwrite(string, sizeof(char), 5, file); // zapiseme reteze
    fclose(file);
    
    // soubor ma velikost 4 + 5 = 9 bajtu
    // obsah: <int><char><char><char><char><char>
    // zpetne precteme
    file = // ... Otevreni souboru pro cteni
    fread(&loaded_number, sizeof(int), 1, file); // precteme int
    fread(loaded_string, sizeof(char), 5, file); // precteme reteze
    fclose(file);
    printf("%i, %s\n", loaded_number, loaded_string);

Posun pozice v souboru

Pomocí funkce fseek můžeme hýbat s aktuálním pozicí v souboru.


    int fseek(FILE * stream, long int offset, int origin);
    
    // offset - Počet bajtů (může být i záporné číslo) o kolik se má změnit aktuální pozice.
    // origin  - Udává výchozí pozici. Můžeme použít konstanty:
    //           SEEK_SET – od začátku souboru
    //           SEEK_CUR – od aktuální pozice 
    //           SEEK_END – od konce souboru

Příklad


    // Obsah souboru: -1, 1, 9, 15, 16, 31
    FILE* file = fopen("numbers.dat", "rb");
    int number;
    // Posuneme se o dva inty dopredu od zacatku
    fseek(file, 2 * sizeof(int), SEEK_SET);
    fread(&number, sizeof(int), 1, file);
    printf("%i\n", number); // 9
    
    // Posuneme se o jeden int dopredu od aktualni pozice
    fseek(file, sizeof(int), SEEK_CUR);
    fread(&number, sizeof(int), 1, file);
    printf("%i\n", number); // 16

Posun zpět


    // Obsah souboru: -1, 1, 9, 15, 16, 31
    FILE* file = fopen("numbers.dat", "rb");
    int number;
    // Vratime se zpet o jeden int
    fseek(file, -sizeof(int), SEEK_END);
    
    // precteme posledni int v souboru
    fread(&number, sizeof(int), 1, file);
    
    // vytiskne se 31
    printf("%i\n", number);

Zjištění pozice

Funkce ftell vrací aktuální pozici v souboru.


    long ftell(FILE* stream);

Příklad


    FILE* file = fopen("number.dat", "rb");
    int number;
    
    // vytiskne 0, jsme na zacatku
    printf("%lu\n", ftell(file));
    fread(&number, sizeof(int), 1, file);
    
    // Vytiskne 4, posunuli jsme se o 1 int (pokud ma int 4 B)
    printf("%lu\n", ftell(file));
    fseek(file, -sizeof(int), SEEK_END);
    
    // Vytiskne 20
    printf("%lu\n", ftell(file));
    fclose(file);

Standardní proudy

Funkce printf tiskne na tzv. standardní výstup. To není nic jiného, než ukazatel na soubor, tj. FILE*, který je definovaný v stdio.h:


    // Standardní vstup
    FILE* stdin;
    // Standardní výstup
    FILE* stdout;
    // Standardní chyba
    FILE* stderr;
    
    // Tyto zápisy jsou tak totožné:
    printf("%i, %g\n", 10, 5.47);
    fprintf(stdout, "%i, %g\n", 10, 5.47);

Úkoly

Napište program, který́ uloží čísla od 0 do 9 do binárního i textového souboru. Zjistěte velikosti těchto souborů obdobně, jako v předchozím příkladu. Otevře si tyto soubory v textovém editoru.
Napište program, který uloží číslo 1234567 do binárního i textového souboru. Zjistěte velikosti těchto souborů obdobně, jako v předchozím příkladu. Proč je v minulém případě velikost binárního souboru větší a v tomto ne?
Napište program, který do binárního souboru uloží pole desetinných čísel (záleží na vás, jak velké).
Napište program, který z desetinných čísel uložených v binárním souboru (například vytvořeného v předchozí ůloze) spočítá průměr. Měl by být napsán obecně, aby fungoval i když nebudeme vědět, kolik je v souboru čísel.