Innhold:

Denne artikkelen forklarer lesing og skriving av data til fil i Matlab. Alle eksemplene i teksten er laget for å fungere både med Octave og Matlab.

Inn- og utoperasjoner

I programmering brukes begrepet inn/utoperasjoner (engelsk: Input/Output), I/O, eller bare IO når programmet leser eller skriver innholdet av variable, også bare kalt data, til omverdenen. Vanligvis er omverdenen utskrift av data til skjermen eller innlesing av data fra verdier hamret inn på tastaturet.

Innlesing fra tastatur og utskrift til skjerm er greit for å teste enkle ting i programmering, men upraktisk hvis datamengdene blir særlig større enn en skjermfull eller resultatene skal lagres for senere bruk. Mengden av data fra beregninger av været, enkle skipsmodeller eller andre konstruksjoner kan fort komme opp i milliarder av bytes (gigabytes). Data fra seismiske undersøkelser er i størrelsesorden petabytes (en million milliarder bytes). Større datamengder må lagres på fil. I tillegg til lesing fra tastatur og skriving til skjerm må derfor programmeringsspråk som Matlab kunne lesing og skriving til fil, også kalt fil-IO (engelsk: File IO).

 

Lagring av variable til fil

Ved normal avslutning av Matlab eller Octave forsvinner alle data lagret i variable. Det fins to funksjoner for å lagre unna og laste inn variable fra en sesjon til fil, save og load. Variablene lagres i spesielle Matlab-filer, og kan som regel ikke leses av andre programmer enn Matlab. I tillegg kan det være problemer med å lese en fil skrevet i en nyere versjon av Matlab-programmet med en eldre Matlab. Det er heller ikke problemfritt å utveksle filer mellom Matlab og Octave.

Siden save og load ikke er egnet til å lagre data til bruk for andre program enn Matlab brukes de hovedsaklig til å lagre unna variable i arbeidsområdet for bruk på et senere tidspunkt. En tabell Akan lagres på fil med:

A = [1 2 3; 4 5 6];
 save('-ascii'1, 'minfil.txt', 'A');

Navnet på variabelen som skal lagres må skrives inn som en tekststreng'A' i dette tilfellet. Filenminfil.txt er en vanlig tekstfil og kan sjekkes ut med Notepad eller en hvilken som helst annen editor. Tabellen kan leses inn igjen til arbeidsområdet med load,

A = load('-ascii', 'minfil.txt');
De innebygde funksjonene save og load kan brukes på mange forskjellige måter. Her er det bare vist en enkel måte å bruke dem på som bør virke mellom de aller fleste Matlab- og Octave-versjoner. 
I eksempelet på bruk av load kan '-ascii' sløyfes selv om det ikke er anbefalt.

1ASCII er et historisk argument. Det betyr American Standard Code for Information Interchange, og er den første standarden for koding av tegnsett på datamaskin. Første utgave av standarden ble utgitt i 1963. De første 128 tegnene (kode 0-127) av dagens standard, UTF-8, er identisk med ASCII.

Eksempel

Filen 'befolkning.txt' inneholder informasjon om befolkningsutviklingen i Norge på begynnelsen av 2000-tallet, fordelt på antall innbyggere i byen og på landet. For å holde oversikten over hvilke data som er lagret i hver kolonne er det lagt til informasjon om dette i en Matlab kommentarsetning på første linje:

 %     År    Tettbygd  Spredtbygd   Uplassert
     2000     3396382      998922       83193
     2001     3419975     1025055       58406
     2002     3474623     1022609       26834
     2003     3514417     1014854       22981
     2004     3536454     1020840       20163
     2005     3560137     1027690       18536
     2006     3607813     1016736       15670
     2007     3722786     1000900       13485
     2008     3780068     1009400        9766

 

Informasjonen kan leses inn i en Matlab-variabel (tabell) med

 

B = load('-ascii', 'befolkning.txt');

 

Befolkningen i ett bestemt år, f.eks. 2005, kan regnes ut med setningene

aar = 2005;
antall_innbyggere = sum(B(aar-1999,2:4));

Uttrykket aar-1999 gir hvilken rad i tabellen ett bestemt år tilsvarer. Den innebygde funksjonensum brukes til å legge sammen befolkningsgruppene for et bestemt år, lagret i kolonne 2, 3 og 4.

 

Tekst- og binærfiler

Desimaltall kan lagres til fil på to måter, som tekst eller binært. Ved lagring av tall som tekst oversettes tallet til en tekststreng og lagres i en vanlig tekstfil, for eksempel med num2str:

>> format long
>> x = 1 + 1/1024
x =  1.00097656250000
>> string = num2str(x,10)
string = 1.000976562

I eksempelet gjøres 1+1/1024 om til en 10-siffers tekststreng, '1.000976562'. Det er lett å sjekke innholdet av tekstfiler, men det er ikke bra å lagre store datamengder som tekst, fordi

  • å lagre tall som tekst tar mer plass
  • omgjøring fra tall til tekst og tilbake krever ekstra beregninger


Å lagre tegn som 'a''b''c''1''2' osv. krever minst 1 byte lagerplass pr. tegn (1 i Octave, 2 i Matlab). Lagring av ett tall binært ved å overføre innholdet av dataminnet (RAM) direkte til fil krever 8 tegn lagerplass:

>> whos
 
 *** local user variables:
 
   Prot Name        Size                     Bytes  Class
   ==== ====        ====                     =====  =====
    rwd string      1x11                        11  char
    rwd x           1x1                          8  double
 
 Total is 12 elements using 19 bytes


En studie av hva som lagres i minnet viser at oversetting av tallverdien 1+1/1024 til tekst med 10 sifre, lagring til fil og tilbakelesing til en variabel tar mer plass enn de opprinnelige dataene, og klarer heller ikke å gjenskape den opprinnelige verdien.

Overføring av et datum til og fra tekstfil

DatumEksakt innholdHeksadesimalt innhold i minnetPlass på fil i bytes
Opprinnelig verdi1 + 1/10243ff004000000000028
Verdi som streng'1.000976562'312e30303039373635363211
Tilbakelest verdi1 + 1/1024 - 5×10-103ff003ffffdda3e88

2 Et desimaltall lagres som (-1)s × mantisse × 2eksponent-1023. Den første biten i minnet er fortegnet s, deretter 11 biter for eksponent og 53 biter for mantisse. Første bit i mantissen er alltid 1 for tall forskjellig fra 0, og lagres ikke.

 

Åpning og lukking av filer

For å overføre innholdet av en variabel direkte fra minnet i Matlab til fil må filen åpnes for binær overføring. Det er nødvendig å lage en kobling mellom Matlab-programmet og filen som skal leses eller skrives. Den innebygde funksjonen fopen gjør det:

[filnummer beskjed] = fopen('filnavn', 'tilgangstype');

Variabelen filnummer blir satt til et tall > 0 hvis filåpningen gikk bra, ellers får den verdien -1. Ved feil lagres det en tekststreng i beskjed som forklarer hvorfor det gikk galt. Tekststrengen'filnavn' angir et stinavn til filen som skal åpnes. Den siste parameteren, strengen'tilgangstype' er en kode for hva slags filoverføring som skal finne sted:

StrengFiloperasjon
'r'Åpne en fil for lesing
'w'Åpne en fil for skriving og fjern eventuelt gammelt innhold. Lag en ny fil hvis den ikke fins.
'a'Åpne en fil for å legge til nye data på slutten (logging). Lag en ny fil hvis den ikke fins.
'r+'Åpne en fil som fins fra før for lesing og skriving.
'w+'Åpne en fil for lesing og skriving, og fjern eventuelt gammelt innhold. Lag en ny fil hvis den ikke fins.
'a+'Åpne en fil for å lese og legge til nye data på slutten (logging). Lag en ny fil hvis den ikke fins.


Ved oppstart av Matlab eller Octave åpnes 3 filer automatisk, med filnumre 0,1 og 2:

Når det ikke er mer som skal gjøres bør forbindelsen til filen lukkes med fclose:

 status = fclose(filnummer);

Hvis filen kan lukkes uten problemer får status verdien 0, ellers -1. Filnumrene 0, 1 og 2 kan også stenges av med fclose. Ved lukking av en fil vil operativsystemet sende eventuelle fildata som er mellomlagret i minnet ut til disken.

Åpning og lukking av filer regnes som kostbare operasjoner i programmering. Selv om moderne operativsystemer takler et stort antall samtidig åpne filer lønner det seg å holde antallet så lavt som mulig av hensyn til ressursbruken, spesielt bruk av minne for mellomlagring av filinnhold. Tilsvarende lønner det seg å redusere antall åpninger og lukkinger av filer, f.eks. ved å unngå slike operasjoner inne i for-løkker.

Eksempler

 

>> [fid,msg] = fopen('varmeledning.dat','w');
>> fid
fid =  3
>> fclose(3)
ans = 0
>> [fid,msg] = fopen('tulll','r')
fid = -1
msg = No such file or directory

Det første kallet til fopen lykkes med å åpne 'varmeledning.dat' for skriving, mens det siste kallet mislykkes i å åpne 'tulll' for lesing. Tilbakemeldingen er at det fins ingen fil med dette navnet.
 

Lesing og skriving av binærfiler

Innholdet i en variabel overføres direkte til fil med fwrite. Dataene lagres på binær form. Det er normalt ikke mulig å se på innholdet i binære filer med en editor. I de tilfellene editoren klarer å åpne en binærfil vil det bare komme opp en haug med rare tegn, lik ufint språk i et Donald-hefte. Selv om binærfiler ikke er så nyttige for mennesker er de nyttige for maskiner, fordi det er den mest effektive måten å overføre data fra minne til disk.

Hvis filen 'test.dat' er åpnet for skriving med fopen og filnummeret ble lagret i filnr kan tabellen A overføres direkte til disk med

antall = fwrite(filnr, A, 'double');

 

Returvariabelen antall inneholder antall elementer i A som ble overført til disk. Hvis alt går bra skal den inneholde antall rader × antall kolonner. Strengen 'double' må være med for å angi atA skal skrives ut som et desimaltall (et tall med desimalpunkt), og ikke gjøres om til noe annet ved lagring. Det er mulig, men er ikke tema her.

En vanlig 2-dimensjonal tabell ordner elementene i rader og kolonner. Begrepene rader og kolonner har ingen mening i en fil. Der lagres tallene bare i en lang rekke eller liste etter hverandre, i enstrøm av bytes. Når Matlab lagrer en tabell på binær form til fil, skrives elementene ut kolonne for kolonne. Først alle elementene i første kolonne, så alle elementene i andre kolonne, osv. Som i minnet (RAM), nummereres data i filer i bytes. Første element lagres derfor i byte nummer 0-7, andre element i byte 8-15 og tilsvarende. En tabell A med 2 rader og 3 kolonner vil derfor lagres som i en kommode med 48 nummererte skuffer, fra skuff 0 til og med skuff 47:

A(1,1)A(2,1)A(1,2)A(2,2)A(1,3)A(2,3)
byte 0-7byte 8-15byte 16-23byte 24-31byte 32-39byte 40-47

 

Eksempel på skriving til fil

 

>> A = [1 2 3; 4 5 6];
>> fid = fopen('test.dat', 'w')
fid =  3
>> fwrite(fid, A, 'double')
ans =  6
>> fclose(fid)
ans = 0

>> whos A
 Name      Size            Bytes  Class     Attributes
 A         2x3                48  double

>> ls -l test.dat
 -rw-rw-r--. 1 ola ola 48 2009-09-01 14:32 test.dat

Kommandoen whos viser at A er en tabell med 2 rader og 3 kolonner, og at den bruker 48 bytes lagerplass i minnet. Etter skriving til fil og lukking av filen viser ls på Linux eller Mac at dataene bruker 48 bytes lagerplass på disk. I Windows viser ikke ls filstørrelsen, men det er mulig å få ut denne ved å lagre resultatet av dir i en variabel, f.eks. r = dir('test.dat').
 


Siden data i binærfiler er lagret i en lang rekke, må programmereren bestemme seg for hvordan dataene skal tolkes ved innlesing fra disk. Det enkleste er å lese inn alt i en lang liste med

L = fread(fid, inf, 'double');
 

Parameteren inf står for infinity, eller at så mye som mulig skal leses, mens strengen 'double ' er med som i fwrite for å angi at det er desimaltall som leses. Skal de 6 første desimaltallene inn i en 2×3-tabell A, blir kallet

A = fread(fid, [2 3], 'double');

Skal dataene lagres i en tabell med 2 rader, mens det er ukjent eller uinteressant hvor mange kolonner binærfilen inneholder, er det mulig å si at alle kolonnene skal leses inn med inf:

A = fread(fid, [2 inf], 'double');

Det er ikke mulig å angi uendelig rad-dimensjon når data lagres kolonnevis. Skal data leses inn til en tabell med mer enn 2 dimensjoner er det tilsvarende bare den siste dimensjonen som kan settes likinf. Inneholder filen for få elementer til å fylle ut hele tabellen slenger Matlab på ekstra 0-elementer på slutten som vanlig.

 

Filposisjonering

Når en fil åpnes for lesing eller skriving holder operativsystemet orden på hvor i filen programmet leser eller skriver til enhver tid. Åpnes en ny fil for skriving settes filposisjonen til 0, og åpnes en fil for å legge til på slutten (med tilgangstype 'a' eller 'a+') settes posisjonen lik størrelsen på filen. Posisjon eller foranding av posisjon i en fil angis i antall bytes, nummerert fra byte 0 og utover.

Et Matlab-program kan lese av filposisjonen med ftell, og i tillegg endre den med fseek. Etter at en fil er åpnet og tildelt et filnummer med fopen vil kallet

posisjon = ftell(filnummer);

returnere posisjonen i filen hvor neste lesing eller skriving skal finne sted. Klarer ftell å finne filposisjonen returneres en verdi i området 0 til filens lengde, ved feil returneres -1.


Kalles fread eller fwrite flere ganger etter hverandre leses eller skrives filen i rekkefølge, ellersekvensielt, fra begynnelsen til slutten. Det er den vanligste måten å gjøre fil-IO på, men det er mulig å skrive eller lese til en fil i et tilfeldig mønster ved å endre filposisjonen før lesing eller skriving med fseek:

status = fseek(filnummer, posisjon, utgangspunkt);

Parameteren utgangspunkt er en streng med 3 lovlige verdier,

Ved å kalle fseek settes en ny posisjon for neste lesing eller skriving. Går det bra å sette ny posisjon returneres status 0, ellers returneres -1. Kallet

status = fseek(filnummer, 0, 'bof');

flytter neste innlesing eller utskrift tilbake til begynnelsen av filen, mens

status = fseek(filnummer, 0, 'eof');

flytter til slutten av filen. Utgangspunktet 'cof' brukes til å endre filposisjonen i forhold til siste lesing eller skriving. Som eksempel vil

status = fseek(filnummer, -8, 'cof');

flytte seg 8 bytes tilbake i forhold til nåværende posisjon, eller med andre ord flytte seg tilbake til siste element hvis utgangspunktet var rett etter en nylig innlest eller utskrevet tabell.

I motsetning til andre programmeringsspråk er det ikke mulig å flytte filposisjonen til en plass utenfor filen for å lage filer med hull i Matlab.

 

Et fullstendig eksempel

En tabell A med 3 rader og 3 kolonner lagres binært til 'tab33.dat'. Etterpå leses to lister B ogC inn fra filen, med verdier fra A som angitt i figuren:

figuren viser en 3x3 tabell A med to 2x1 undertabeller B og C


Å opprette A og skrive den ut til 'tab33.dat' gjøres med setningene:

A = [1:3; 4:6; 7:9];
[fid mld] = fopen('tab33.dat', 'w');
antall_elementer = fwrite(fid, A, 'double');
status = fclose(fid);
 

Setningene lager en tabell, åpner filen 'tab33.dat' for skriving, skriver ut A på binær form og lukker filforbindelsen etter lagring. Sjekk av returverdier er utelatt for å få koden så kort som mulig, men det bør være med i et ferdig program. Hvis setningene kjøres uten feil blir det opprettet en fil'tab33.dat' på 9*8 = 72 bytes. Tabellene (listene) B og C kan nå leses inn ved å åpne'tab33.dat' for lesing og så posisjonere seg riktig i filen med fseek før innlesing:

[fid mld] = fopen('tab33.dat', 'r');
status = fseek(fid, 8*1, 'bof');
B = fread(fid, 2, 'double');
status = fseek(fid, 8*4, 'cof');
C = fread(fid, [2 1], 'double');
 

Funksjonen fseek skal ha inn avstand i antall bytes, så derfor må avstand i antall elementer skrives som 8*n for n elementer. Etter første innlesing er filposisjonen 24, så den må flyttes 4 elementer til begynnelsen av C. Både B og C er nøyaktig like store, men for å få riktig antall rader og kolonner på tabellen er det bedre å bruke [2 1], enn bare å angi antall elementer som ved lesing av B. Sjekk av returverdier er ikke vist.

 

Formatert utskrift

Funksjonen disp brukes ofte til utskrift fra skriptfiler eller m-filer i Matlab. Blandet utskrift av tekst og tall er imidlertid tungvint fordi tallene må gjøres om til tekst med num2str. Funksjonenfprintf3 kan brukes til mer presis kontroll av utskrift. Den er beregnet på utskrift til tekstfiler åpnet med fopen, men kan også brukes for utskrift til skjerm fordi skjermen alltid har filnummer 1. I stedet for å gjøre om alt til tekst bruker fprintf et ekstra argument, en format-streng, for å beskrive hvordan ting skal se ut. Format-strengen kan inneholde en blanding av forklarende tekst og utskriftskoder som forteller hvordan variablene som følger skal skrives ut, for eksempel

>> fprintf(1,'Pi med 10 desimaler: %.10f\n', pi);
Pi med 10 desimaler: 3.1415926536
>> fprintf(1,'Pi med 2 desimaler over 5 tegns bredde >%5.2f<\n', pi);
Pi med 2 desimaler over 5 tegns bredde > 3.14<
 

Et utskriftsformat er på formen

 %<bredde><.antall desimaler><typebokstav>

Det begynner alltid med % (prosent), har en valgfri total bredde, et valgfritt antall desimaler og avsluttes med en typebokstav som sier hva slags data som skal skrives ut. Typebokstavene angir at argumentet skal skrives ut som

Avslutningen med '\n' betyr at det skal skrives et linjeskift. Ved utskrift av en tabell gjentas format-strengen for alle elementene:

>> A = [25 77 128 255];
>> fprintf(1, 'Heksadesimale verdier:'); fprintf(1,' 0x%x', A); fprintf(1,'\n');
Heksadesimale verdier: 0x19 0x4d 0x80 0xff
 

Linjeskift legges bare inn i den siste fprintf-setningen. I et skript er det naturlig å skrive enfprintf på hver linje. Det er mulig å blande disp og fprintf ved utskrift.

3 Octave har en ekstra utskriftsfunksjon printf. Den fungerer som fprintf, bortsett fra at den mangler argumentet for filnummer — den skriver alltid ut til skjermen.

 

Formatert innlesing

Analogt til fread for binær IO, fins det en funksjon fscanf for å lese inn data lagret i tekstfiler. Den har et format-argument som for fprintf og et argument for antall elementer som skal leses likfread. Funksjonen kan kalles på to forskjellige måter:

A = fscanf(filnummer, 'format', antall);
[A antall_innlest] = fscanf(filnummer, 'format', antall);
 

I tillegg til variabelen som skal motta dataene er det mulig å angi en variabel antall_innlest som fylles inn med antall elementer som ble lest inn i A. Antall elementer som skal leses inn kan angis med inf (så mange som mulig), totalt antall elementer, eller antall rader og kolonner [M N]. Dette fungerer nøyaktig som i fread.

Eksempel på formatert lesing

Anta filen test.dat inneholder en linje med ett tall i hver 5. kolonne, inkludert blanke tegn:

1    5    3    4

Filen åpnes for lesing, leses med fscanf og lukkes etterpå:

>> [fid msg] = fopen('test.dat', 'r');
>> A = fscanf(fid,'%5d',[1 inf])
 
A =     1     5     3     4
 
>>  status = fclose(fid);
 

Her brukes [1 inf] for å angi at dataene skal leses inn som én rad. Matlab gjenbruker formatet'%5d' for hvert element i A.

Funksjonen fscanf kan ikke brukes til innlesing fra tastatur. I motsetning til fprintf(1,...for å skrive ut på skjermen har ikke fscanf(0,... noen mening. For innlesing fra tastaturet i måinput benyttes.