6. díl - Rozdiely medzi textovými a binárnymi súbormi v C - dodatek

C++ Práce se soubory Rozdiely medzi textovými a binárnymi súbormi v C - dodatek

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.

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:

zápis do súborov v c

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:

čítanie súborov do poľa

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 105x (12.09 kB)
Aplikace je včetně zdrojových kódů v jazyce C

 

  Aktivity (2)

Článek pro vás napsal Libor Šimo (libcosenior)
Avatar
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.

Jak se ti líbí článek?
Celkem (6 hlasů) :
4.833334.833334.833334.833334.83333


 


Miniatura
Všechny články v sekci
Práce se soubory v jazyce C

 

 

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

Avatar
Libor Šimo (libcosenior):

Pekné.
Prekonvertoval si čísla na reťazce a potom si ich uložil, ale keď ich bude treba načítať, zase to bude treba prekonvertovať na čísla a to zaberie čas.
Ani si nechcem predstaviť načítanie poľa štruktúr z textového súboru.
Navyše na mojom pc v práci (winxp) výstup (release) vyzerá tak, že načítanie textového súboru trvá v priemere skoro 2 krát viac ako binárneho. (spustil som to zhruba 10x)

Editováno 9.6.2014 12:40
Odpovědět 9.6.2014 12:38
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
coells
Redaktor
Avatar
Odpovídá na Libor Šimo (libcosenior)
coells:

Časy se budou lišit podle překladače, hardwaru a OS. Na OS X mám také úplně jiné výsledky než na Win7. To je faktor, který se špatně ovlivňuje, pokud jsou data takhle velká. Stejně tak clang a MS Visual Studio jsou mnohem lepší než GCC, takže se také projeví rozdíl v rychlosti. V mém případě už byla práce s textem zanedbatelná, protože většinu času zabral zápis na disk.

Smysl je ale v tom, že se nedá říct, že práce s textovými soubory je pomalejší než práce s binárními soubory. Pomalejší je pouze práce s jinou reprezentací dat než je ta nativní. A i tam lze napsat velice efektivní kód, pokud je to potřeba.

 
Odpovědět 9.6.2014 13:00
Avatar
Odpovídá na coells
Libor Šimo (libcosenior):

Schválne to doma vyskúšam na ubuntu. Tam som mal v pôvodnom znení najväčšie rozdiely, tak som zvedavý.
Ty vlastne používaš na ukladanie funkcie fwrite(), ktorá sa nativne používa pre binárne súbory.

Odpovědět 9.6.2014 13:12
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
coells
Redaktor
Avatar
Odpovídá na Libor Šimo (libcosenior)
coells:

fwrite() se nativně používá pro binární i textové soubory. Textový mód pouze značí, že se \n má chovat podle toho, jak to vyžaduje OS. Na windows se bude přepisovat na \r\n, na unixu nemá žádný efekt.

viz http://msdn.microsoft.com/…S.71%29.aspx

V příkladu, který máš v článku, je naprosto jedno, jaký mód použiješ.

Editováno 9.6.2014 14:03
 
Odpovědět 9.6.2014 14:02
Avatar
Odpovídá na coells
Libor Šimo (libcosenior):

Ta som doma a testol som to tvoje na ubuntu 12.04 a gcc.
Výsledok:

Odpovědět 9.6.2014 17:11
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
coells
Redaktor
Avatar
Odpovídá na Libor Šimo (libcosenior)
coells:

V tom případě dokážu kód upravit tak, aby s kvalitním překladačem, na dobrém CPU a Win7 běhal stejně rychle :-)

 
Odpovědět 9.6.2014 17:34
Avatar
Odpovídá na coells
Libor Šimo (libcosenior):

Nemyslíš, že je to zbytočné? Pri spôsobe odporúčanom Heroutom to funguje tak ako som písal. Kto sa bude chcieť baviť a hľadať iné spôsoby, nech sa páči.
Navyše, nie každý má dobré CPU a WIN7. ;)

Odpovědět  +2 9.6.2014 18:15
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
coells
Redaktor
Avatar
Odpovídá na Libor Šimo (libcosenior)
coells:

To spíš prozraď Ty mně.

Předvádíš práci s "rozdílnými" soubory, když vlastně rozdílné nejsou. Textový mód je zastaralý princip a navíc funguje trochu jinak, než sis zjevně myslel, což jsme probrali výše. Ke všemu měříš vždy něco jiného, když v jednom případě nepoužíváš buffer, zatímco ve druhém ano.

Článek je tedy vlastně o rozdílném formátu dat, což předvádíš na souborech o velikosti stovek MB. Prosím Tě, kolik 0.5GB textových souborů jsi už viděl? Jestliže pracuji s obvyklými velikostmi do řádů stovek kB, jak moc bude záležet na měřených časech?

Pokud bych už opravdu potřeboval zpracovat textový soubor, který bude hodně velký, ano, vyplatí se si s tím pohrát a zrychlit práci, ale to už jsou trochu jiné techniky.

 
Odpovědět  +1 9.6.2014 19:37
Avatar
Jaroslav Polívka:

Ahoj, zkoušel jsem níže uvedeným kódem kopírovat soubor, jednou se fopen dával w+b a jednou na w+t a časy mi vycházeli velmi podobně.

//---------------------------------------------------------------------------
#include <stdio.h>
#include <iostream.h>
#include <conio.h>
#include <time.h>
#pragma hdrstop

//---------------------------------------------------------------------------
#pragma argsused

int main(int argc, char* argv[])
{

FILE *in, *out;
char text = 0;

if (argc > 3){
cout << "Neplatne zadani souboru!!!" << endl;
getch();
return 0;
}

if ((in = fopen(argv[1], "r+t"))== NULL)
{
cout << "Nemohu otevrit zdrojovy soubor!";
getch();
return 0;
}

if ((out = fopen(argv[2], "w+t")) == NULL)
{
cout << "Nemohu vytvorit cilovy soubor!";
getch();
return 0;
}

clock_t start1 = clock();

while (!feof(in))
{
text = fgetc(in);
if ( text == ' ' ) continue;
fputc(text, out);
}

clock_t finish1 = clock();

fclose(in);
fclose(out);

printf("Potrebny cas %.3f secund.\n", (float)((int)fi­nish1 - (int)start1) / CLOCKS_PER_SEC);

getchar();

return 0;

}
//---------------------------------------------------------------------------

Odpovědět  +1 10.6.2014 2:11
Velice často si věci žijí svým životem
Avatar
Jaroslav Polívka:

Ale článek mě velmi chytil.

Odpovědět 10.6.2014 2:12
Velice často si věci žijí svým životem
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 14. Zobrazit vše