Java týden Předvánoční slevová akce
Využij předvánočních slev a získej od nás 20 % bodů zdarma! Více zde
Pouze tento týden sleva až 80 % na Java e-learning!

Lekce 6 - Rozdiely medzi textovými a binárnymi súbormi v C - dodatek

Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem.
Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulej lekcii, Manipulace se soubory a práce s errory, sme sa naučili ďalšie módy funkcie fopen () a naučili sa pracovať s errory. Dnešné, posledná lekcia, zhŕňa rozdiely medzi textovými a binárnymi súbormi.

Skôr ako sa pustíme do samotnej témy, bol by som rád, keby ste si nastavili IDE na normu C99 a aby ste písali čo najlepší kód, nastavili si compilátor tak, že každý warning sa zmení na chybu a compilátor vám nedovolí program scompilovať, kým to nenapravíte.

V Code:Blocks sa to nastavuje takto: Settings -> Compiler and debugger... -> Other options a tam do okna vložíte: -std=gnu99 -Werror -Wall -lm

nastavenie compileru Code Blocks

V dnešnom článku si povieme prečo a v ktorých prípadoch je výhodné používať na ukladanie dát textové súbory a kedy binárne súbory.

Textové a binárne súbory

Textové súbory majú výhodu, že je možné kedykoľvek si prehliadnuť ich obsah, vytvoriť ich alebo upraviť bežným editorom. To je aj dôvod, prečo sú vhodné na ukladanie textu.
Na ukladanie iných dátových typov sa síce môžu použiť, ale nie je to výhodné, pretože napríklad číslo 65535 zaberie v textovom súbore priestor 5 Byte a v binárnom len 2 Byte.
Ďalej nie sú vhodné na ukladanie polí, pretože medzi jednotlivé prvky poľa ešte musíme vložiť nejaký separátor, napriklad medzeru a tým je súbor automaticky väčší.

Binárne súbory majú nevýhodu, že síce si ich obsah môžme prehliadnuť napríklad PSPad editorom, ale niečo v nich meniť je prinajmenšom nevhodné, pretože môžeme viac pokaziť ako napraviť.
Výhodou je, že sú väčšinou menšie, rýchlejšie sa s nimi pracuje a pri ukladaní polí nepotrebujú separátor.

Teraz si dokážeme to, čo som napísal.

Program

Napíšeme si jednoduchý program, ktorý najprv vytvorí pole 50 000 000 integerov. Potom to pole načíta do textového súboru a nakoniec ho načíta do binárneho súboru. Každému úkonu sa bude merať čas a nakoniec sa výsledky vypíšu.

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Založme si nový Project Console application napríklad UlozenieTextBin. Pridajme si do projektu dva súbory, (source) fce.c a (header) fce.h. Súbor main.c sa vytvorí automaticky.

Najprv si v súbore fce.h zadeklarujeme potrebné funkcie a popíšeme ich:

/** fce.h **/
#ifndef FCE_H_INCLUDED // ak nie je súbor načítaný
#define FCE_H_INCLUDED // načítaj súbor

#define POCET 50000000 // počet prvkov poľa

/**
* Funkcia zapíše obsah poľa do textového súboru
* @param pole premenných typu int
* @param reťazec - názov súboru
*/
void vytvor_txt_subor(int pole[], char* subor);

/**
* Funkcia zapíše obsah poľa do binárneho súboru
* @param pole premenných typu int
* @param reťazec - názov súboru
*/
void vytvor_bin_subor(int pole[], char* subor);

#endif // ukončenie podmienky preprocesora

Ďalej si v súbore fce.c deklarované funkcie zadefinujeme:

/** funkcie.c **/
#include <stdio.h>
#include "fce.h"

void vytvor_txt_subor(int pole[], char* subor)
{
    FILE *fw; // deklarácia pointeru na dátový typ FILE

    fw = fopen(subor, "w"); // otvorenie textového súboru na zápis
    for (int i = 0; i < POCET; i++)  // v cykle sa do súboru fw ukladajú
        fprintf(fw, "%d ", pole[i]); // integery s medzerou
    fclose(fw); // uzatvorenie súboru musí byť!!!
}

void vytvor_bin_subor(int pole[], char* subor)
{
    FILE *fw;

    fw = fopen(subor, "wb"); // otvorenie binárneho súboru na zápis
    fwrite(&pole[0], sizeof(int), POCET, fw); // od adresy &pole[0] sa do súboru fw uloží POCET položiek o veľkosti sizeof(int)
    fclose(fw); // uzatvorenie súboru musí byť!!!
}

Nakoniec upravíme súbor main.c:

/** main.c **/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "fce.h"

int main()
{
    int *pole1; // deklarácia pointera na pole integerov

    if ((pole1 = (int*)malloc(POCET * sizeof(int))) == NULL) { // dynamická alokácia pamäti s kontrolou
        printf("Malo pamati!");
        return 1;
    }

    srand(time(0)); // inicializácia generátora náhodných čísiel

    printf("Vytvorim pole celych cisiel.\n");
    clock_t start1 = clock(); // začiatok merania
    for (int i = 0; i < POCET; i++) // v cykle vložím do poľa
        pole1[i] = rand()%200000 + 100000; // náhodné čísla v rozsahu 10 000 - 200 000
    clock_t finish1 = clock(); // koniec merania
    printf("Potrebny cas %.3f secund.\n", (float)((int)finish1 - (int)start1) / CLOCKS_PER_SEC);

    printf("\nZapisem pole do textoveho suboru.\n");
    clock_t start2 = clock(); // začiatok merania
    vytvor_txt_subor(pole1, "test.txt"); // volanie funkcie
    clock_t finish2 = clock(); // koniec merania
    printf("Potrebny cas %.3f secund.\n", (float)((int)finish2 - (int)start2) / CLOCKS_PER_SEC);

    printf("\nZapisem pole do binarneho suboru.\n");
    clock_t start3 = clock(); // začiatok merania
    vytvor_bin_subor(pole1, "test.dat"); // volanie funkcie
    clock_t finish3 = clock(); // koniec merania
    printf("Potrebny cas %.3f secund.\n", (float)((int)finish3 - (int)start3) / CLOCKS_PER_SEC);
    free(pole1); // uvolnenie pamäti vyčlenenej pre pole

    return 0;
}

Keď project skompilujeme a spustíme dostaneme výstup:

UlozenieTextBin
Vytvorim pole celych cisiel.
Potrebny cas 2.470 secund.

Zapisem pole do textoveho suboru.
Potrebny cas 18.140 secund.

Zapisem pole do binarneho suboru.
Potrebny cas 0.490 secund.

Ako vidíte, rozdiel v čase zápisu do súborov je obrovský.

Veľkosť súborov:
test.txt

velkosť txt súboru

test.dat

velkosť binárného súboru

Aj veľkosť súborov je veľmi rozdielna.

Doplníme náš program o načítanie textového a binárneho súboru do polí, samozrejme s meraním času.

Do súboru fce.h doplníme deklarácie nových funkcií:

/**
* Funkcia prečíta obsah textového súboru a vloží ho do poľa
* @param pole premenných typu int
* @param reťazec - názov súboru
*/
void precitaj_txt_subor(int pole[], char* subor);

/**
* Funkcia prečíta obsah binárneho súboru a vloží ho do poľa
* @param pole premenných typu int
* @param reťazec - názov súboru
*/
void precitaj_bin_subor(int pole[], char* subor);

V súbore fce.c. in zadefinujeme:

void precitaj_txt_subor(int pole[], char* subor)
{
    FILE *fr;
    int i = 0, j;

    fr = fopen(subor, "r");
    while ((fscanf(fr, "%d", &j)) != EOF) { // v cykle číta integery zo súboru
        pole[i] = j; // a vkladá ich do poľa
        i++;
    }
    fclose(fr);
}

void precitaj_bin_subor(int pole[], char* subor)
{
    FILE *fr;

    fr = fopen(subor, "rb");
    fread(&pole[0], sizeof(int), POCET, fr); // od adresy &pole[0] vloží zo súboru POCET integerov do poľa
    fclose(fr);
}

A nakoniec doplníme do súboru main.c nasledujúci kód:

int *pole2;
    if ((pole2 = (int*)malloc(POCET * sizeof(int))) == NULL) {
        printf("Malo pamati!");
        return 1;
    }
    printf("\nPrecitam textovy subor do pola.\n");
    clock_t start4 = clock(); // začiatok merania
    precitaj_txt_subor(pole2, "test.txt");
    clock_t finish4 = clock(); // koniec merania
    printf("Potrebny cas %.3f secund.\n", (float)((int)finish4 - (int)start4) / CLOCKS_PER_SEC);

    int *pole3;
    if ((pole3 = (int*)malloc(POCET * sizeof(int))) == NULL) {
        printf("Malo pamati!");
        return 1;
    }
    printf("\nPrecitam binarny subor do pola.\n");
    clock_t start5 = clock(); // začiatok merania
    precitaj_bin_subor(pole3, "test.dat");
    clock_t finish5 = clock(); // koniec merania
    printf("Potrebny cas %.3f secund.\n", (float)((int)finish5 - (int)start5) / CLOCKS_PER_SEC);

Aby sme znovu nespustili vytvorenie poľa a jeho zápis do súborov (súbory by sa prepísali), zakomentujeme prvú časť v súbore main.c. Teda všetko čo je nad riadkom
int *pole2;
Scompilujeme a môžme spustiť. Výstup je:

UlozenieTextBin
Precitam textovy subor do pola.
Potrebny cas 18.570 secund.

Precitam binarny subor do pola.
Potrebny cas 0.390 secund.

A zase vidíte obrovský rozdiel v časoch čítania jednotlivých súborov.
To je pre dnešok všetko.

Projekt je napísaný v Code:Blocks na Ubuntu 10.04. Nedá sa otvoriť v Code:Blocks vo Windowse, ale jednotlivé súbory sa môžu skopírovať a bude to pracovať aj tam. Projekt je priložený.


 

Stáhnout

Staženo 112x (12.09 kB)
Aplikace je včetně zdrojových kódů v jazyce C

 

 

Článek pro vás napsal Libor Šimo (libcosenior)
Avatar
Jak se ti líbí článek?
6 hlasů
Obľúbil som si jazyk C a snažím sa ho spoznať čo najhlbšie. Začal som používať c# WPF, je to krása.
Předchozí článek
Manipulace se soubory a práce s errory
Všechny články v sekci
Práce se soubory v jazyce C
Miniatura
Následující článek
Zdrojákoviště jazyka C - Práce se soubory
Aktivity (4)

 

 

Komentáře
Zobrazit starší komentáře (22)

Avatar
DarkCoder
Člen
Avatar
DarkCoder:2. září 14:53

Jedná se o úsek programu, ve kterém se převádí číslice čísla value do znakové podoby odzadu. Výraz value % 10 vrací poslední číslici čísla, přičtením k hodnotě '0' se získá její znakový ekvivalent. Výraz value /= 10 provádí "mazání" poslední číslice čísla, čímž se pro další iterakci cyklu vytváří prostor pro převod další číslice do znakové podoby.

Pro představu se podívej na následující úsek programu:

int value = 12345;

while (value > 0) {
        putchar(value % 10 + '0');
        value /= 10;
}

Na obrazovku se postupně budou vypisovat znaky do podoby: 54321.

Odpovědět
2. září 14:53
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:2. září 17:24

dik uz chapu. Ale normalne by me to nenapadlo. Co mam precist/nastudovat abych takhle uvazoval?

 
Odpovědět
2. září 17:24
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:2. září 17:34

Programovat, studovat cizí programy, naučit se cokoli analyzovat, optimalizovat a pochopit, umět ladit, přemýšlet na problémem a řešením, umět využít všechny možnosti programovacího jazyka, koncentrovat se...

Odpovědět
2. září 17:34
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:2. září 18:39

jeste jedna vec t = *tmp, *tmp-- = *dst, *dst++ = t; - jak to, ze muze napsat vse na jeden radek (tj bez semicolon) ale pouziva ciste carky mezi prikazy?

 
Odpovědět
2. září 18:39
Avatar
DarkCoder
Člen
Avatar
Odpovídá na DarkCoder
DarkCoder:2. září 19:58

Protože využívá operátoru čárka, který slouží ke zřetězení několika operací. Vysloveně říká překladači: "udělej tohle a tohle a tohle."

Nejčastěji to je vidět ve for cyklech v inicializační a inkrementační části při práci s vícero proměnnými.

for(i=0, j=0; i+j < val; i++, j++) …

V seznamu výrazů je jeho hodnota dána výrazem, který stojí nejvíce vpravo:

val = (i, 10, 100, 1000);

V předchozím příkladu bude mít proměnná val hodnotu 1000. Závorky jsou nutné z důvodu priorit operátorů (operátor čárka má nižší prioritu nežli přiřazovací operátor).

Editováno 2. září 19:58
Odpovědět
2. září 19:58
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Odpovídá na DarkCoder
Patrik Pastor:2. září 20:44

Dobre ale pokud je dano vice promennych oddelenych carkou, musi byt prece nejak logicky svazany, napriklad

int x,y,z;

coz chapu - protoze vsechny promenne xyz maji stejny datovy typ int. Ale priklad
*tmp++ = value % 10 + '0', value /= 10; moc dobre nechapu, kdyz tady zadna logika mezi prikaze neni (nebo ji aspon nevidim - value je neco jineho nez *tmp). Pokud bych "retezil prikazy" potom bych prece nepouzival ';' stredniky vubec protoze bych vsechno nahazel na jeden radek a argumenty oddelil carkami (promenne bych treba deklaroval nahore v souboru, ale prikazy bych nabombil oddelneymi carkami). Jak to tedy ve skutecnosti je?

 
Odpovědět
2. září 20:44
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:2. září 21:01

Řetězí se výrazy nikoli příkazy. Příkaz je výraz zakončený středníkem a tam už není co řetězit. Uskupení několika příkazů do jedné jednotky je blok příkazů, ale to už je mimo toto téma.

Ano, při deklaraci proměnných oddělených čárkou bývá obvykle vazba. Obvykle se takto vazbí proměnná a ukazatel na ni. Je ale úplně něco jiného deklarovat více proměnných stejného typu a řetězení výrazů.

Samozřejmě že tam je logika. V prvním výrazu získáváš znakovou podobu poslední číslice, v druhém příkazu tedy po provedení prvního příkazu se zbavuješ poslední číslice, čímž získáváš novou poslední číslici. Obě tyto operace jsou součástí jedna druhé, proto zde může být použit operátor čárka. Nic nebrání napsat obě operace samostatně, každou na samostatném řádku a oddělit středníkem. Že jsou ve výrazech odlišné typy, to je úplně jedno. Hlavní je smysl a vazba obou operací. Na mnoha místech "lze" místo středníku použít operátor čárku, ale takovéto používání operátoru čárka je považováno za velmi špatný formát zápisu.

Odpovědět
2. září 21:01
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:3. září 15:17

jak presne funguje pointerova aritmetika zde:

int pozice = strstr(p_aktualni->jmeno, jmeno) - p_aktualni->jmeno;

rekneme kuprikladu:
aktualni.jmeno = Alena (5 bytu)
hledam:
jmeno = lena (4 byty)

potom podle rovnice:
jmeno -aktaulni.jmeno = 4-5

to je zaporne cislo (resp. adresa) - to by ale neproslo podminkou kde musi byt
pozice vetsi nez 0.

Pointerovou aritmetiku jsem si cetl, tam je ale posunovani o jednotlivych bajtech v retezcich (adresach bajtech). Jak je to ale mysleno tady?

 
Odpovědět
3. září 15:17
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:3. září 19:37

Lze odečítat jeden ukazatel od druhého. Tím se zjistí počet prvků které mezi nimi leží. To má samozřejmě smysl pouze u polí, kde oba tyto ukazatele ukazují na nějaký prvek tohoto pole.

Odpovědět
3. září 19:37
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:3. září 21:17

Ukazatelová aritmetika se v řetězcích neprojeví, respektive se chová jako ta klasická. To proto, že posun o 1 nebo o N u klasické aritmetiky odpovídá posunu u ukazatelové aritmetiky s datovým typem char. Na tom není vidět ten princip co vlastně ukazatelová aritmetika přináší.

Kromě operátorů & a *, lze na ukazatelové proměnné používat aritmetické operátory: +, ++, - a --. Navíc lze přičítat a odečítat pouze celočíselnou hodnotu.

Ukazatelová aritmetika se od té "normální" liší v tom, že se provádí s ohledem na základní typ ukazatele. Když je ukazatel inkrementován, resp. dekrementován, bude ukazaovat na následující reps. předcházející položku podle základního typu ukazatele.

pi++;

Pokud pi je ukazatel na int a nachází-li se na adrese A, pak po provedení předchozího příkazu bude obsahovat A + sizeof(int). Nikoli A + 1.

pi = pi + N;

Pokud pi je ukazatel na int, pak po provedení příkazu bude nyní pi ukazovat na Nté celé číslo za číslem, na které ukazoval předtím. pi = pi + (N * sizeof(int)). Nikoli pi + N.

Chování s operátory - a -- je obdobné pouze s opačným směrem.

Lze odečítat jeden ukazatel od druhého, tím se zjistí kolik prvků mezi nimi leží.

*pi++ - nejprve inkrementuje ukazatel a poté vezme hodnotu na nové adrese.
(*pi)++ - inkrementace prvku na který ukazatel ukazuje.

Spolu související ukazatele lze porovnávat nebo lze porovnávat ukazatel s nulou, aby se zjistilo, zda jde o nulový ukazatel.

Nejvyšší využití ukazatelové aritmetiky je při práci ukazatelů s poli.

Máme-li pole p pak platí:

p[N] ~ *(p + N)

Ukazatelová aritmetika se provádí s ohledem na základní typ ukazatele proto, aby umožňovala vzájemný vztah mezi a ukazateli. To je to, proč právě vztah mezi ukazateli a poli činí jejích implementaci tak jedinečnou a výkonnou.

Odpovědět
3. září 21:17
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 32. Zobrazit vše