A. Veneziani - Files di testo in C#
←
→
Trascrizione del contenuto della pagina
Se il tuo browser non visualizza correttamente la pagina, ti preghiamo di leggere il contenuto della pagina quaggiù
A. Veneziani – Files di testo in C# Utilità dei files di testo Come praticamente tutti i linguaggi di programmazione anche C# permette di operare con i file. Come dovrebbe essere noto i files servono alle applicazioni per salvare i dati di interesse oltre il periodo stesso della pura esecuzione. La gestione dei files è quindi determinante per salvare dati che altrimenti verrebbero persi alla chiusura dell’applicazione stessa. Tali dati quindi possono, grazie ai files perdurare per un tempo lungo a piacere o successivamente essere modificati. Esistono tipi diversi di file. Files di testo – contenenti solo testo, avente diversi significati File binari – ad esempio i files eseguibili, che contengono codice macchina e quindi il cui significato è per eccellenza binario Files di record – files il cui contenuto è suddiviso in campi strutturati di lunghezza fissa. Si ricorda che i file di testo (file con codifica in formato testo) sono molto comuni nei sistemi di calcolo. Ad esempio sono file di testo: file sorgenti di programmi scritti in qualunque linguaggio, file .ini (di configurazione) del sistema Windows o di sue applicazioni, file .html, ossia pagine Web, o le stesse quando contengano codice di vari linguaggi per il Web (Javascript, PHP, ASP, ecc…), file XML, e molti altri casi. Noi andremo ad operare solo con i files di testo, i quali risolvono e permettono di affrontare comunque una vasta gamma di problemi. Le fasi fondamentali per svolgere operazioni sui files sono: a) Apertura del file – indicando la sua posizione e nome sul disco e indicando il tipo di operazioni che si intendono effettuare su essi. b) Lettura / scrittura dei dati c) Chiusura del file Durante le operazioni di lettura il punto di lettura è memorizzato da un apposito indice che avanza in automatico via via che i dati vengono letti dal file. Viceversa la scrittura nei file di testo avviene, di norma, alla fine del file. Un file di testo può essere facilmente riconosciuto, al di là dei suoi contenuti specifici o dell’estensione del file, per il fatto che il suo contenuto può essere letto aprendo il file con un programma detto editor di testo. Tali programmi sono del tutto comuni, e molti di essi sono software open, quindi reperibili liberamente, senza pagamento. Il più comune e più noto rappresentate della categoria degli editor di testo è il programma Blocco nome (Notepad), presente di default in tutte le versioni di Windows. Ovviamente esistono editor di testi più evoluti di Blocco note, che hanno funzionalità più complesse e complete, quali: Riconoscimento del linguaggio contenuto nel testo e relativa colorazione del codice Potenti funzioni di undo e redo del testo scritto Multiediting in un unico programma (più editor disponibili in conteporanea) Split della finestra di editor ed editazione di due parti di codice diverse e distanti Gestione di file vari formati ASCII, Unicode, altri Apertura di un file di testo Per operare su un file la prima operazione che deve essere fatta preliminarmente a tutte le altre è l’apertura del file. Prima di operare su di esso il file deve essere aperto. In tal caso esistono due possibilità: Il file è esistente Il file non esiste, ma l’apertura avviene con permessi che consentono la creazione del file Nel secondo caso il file che non esiste ancora viene creato con il nome indicato nelle operazioni di apertura. Esiste un terzo caso, ossia se l’operazione di apertura non è previsto crei files, in tale evenienza un errore verrà ritornato senza creare il file. Pagina 1
Nel caso dell’ utilizzo di StreamWriter questo terzo caso non si verifica in quanto l’utilizzo di questa classe prevede sempre la creazione del file nel caso esso non sia esistente. In C# vi sono diversi modi per effettuare operazioni simili sui files di testo. Indichiamo qui sotto quali noi utilizzeremo: Operazioni di creazione e scrittura di un file tramite la classe StreamWriter Operazioni di lettura di un file tramite la classe StreamReader I file di testo possono essere aperti in scrittura in due modalità: In riscrittura (troncamento1)– il contenuto del file viene azzerato e riscritto ogni volta che il file viene aperto In aggiunta (append) – al contenuto del file vengono aggiunti altri dati in coda. Scrittura di un file di testo In certi casi si desidera aprire il file per scrivere in esso. In questo caso una possibile metodica C#2 è quella di aprire il file utilizzando un oggetto della classe StreamWriter. La classe StreamWriter è utile per effettuare scritture ogniqualvolta si abbia uno “stream” (canale) di comunicazione e si debba scrivere su di esso. In questo caso il “canale di comunicazione” è rappresentato dal flusso di dati che si desiderano scrivere sul file, una volta che sia stato aperto. L’istruzione che permette l’apertura di un file di testo ha la forma: StreamWriter = new StreamWriter(, ); se il file esiste ed il flag append è true l’operazione viene vista come un aggiunta di dati in coda in un file già esistente. Altrimenti l’apertura opera in scrittura da zero (troncamento). Il path deve indicare il percorso completo o relativo del file e deve essere completo ovviamente del nome del file sul quale si desidera operare. Si ricorda che l’estensione3 generica (opportuna) per un file un file di testo è .txt. Nel caso in cui il file non esista, esso viene di default creato tramite tale operazione. Per utilizzare gli oggetti delle classi StreamWriter e StremReader deve essere incluso (using) il package System.IO. Successivamente è possibile utilizzare la variabile oggetto del tipo StreamWriter per effettuare scritture nel file: StreamWriter sw = new StreamWriter(“.\\pippo.txt”); …. sw.WriteLine(….); …. Si noti che il path (percorso) può essere espresso in vari modi: Con il solo nome del file – in questo caso il file deve essere presente o viene creato nella directory ove risiede il file eseguibile (ad. esempio “pippo.txt”) Con un path relativo – il path relativo si riferisce alla directory dove si trova il file .exe della applicazione che stà utilizzando il file. Ammettiamo che il file sia stato sistemato in una sottodirectory files delle directory bin\debug dove è presente l’ .exe, allora il path potrà essere espresso con un percorso relativo da quella directory: (ad es. “.\\files\\pippo.txt”) 1 In inglese truncate 2 Ripetiamo che esistono molteplici tecniche ed istruzioni di diverso tipo per operare con i file di testo in C#; quella presentata è una sola di quelle possibili. 3 Si ricorda anche che l’estensione di un file è rappresentata di norma, nei sistemi Windows/DOS, da tre caratteri in coda al nome vero e proprio preceduti da un punto. Ad esempio pippo.txt (file con estensione .txt) Pagina 2
Infine il terzo ed ultimo caso di percorso, il percorso indicato in modo assoluto. In esso si prende come riferimento la root dell’unità su cui si sta operando, quindi un path assoluto potrebbe essere: C:\\Works\\C#\\console\\files\\leggi_e_stampa\\bin\\Debug\\files\\pippo.txt Si osservi anche una regola importante. Il simbolo di backslash ( \ ), che server per separare il nome delle singole directory e che in DOS/Windows è singolo, qui deve essere sempre raddoppiato. Questo in quanto in numerosi linguaggi (tra cui C#, ma anche C e C++ ed altri), lo stesso simbolo è preposto a caratteri detti speciali quali: Carattere speciale Rappresentazione Ritorno carrello \r Passa a riga successiva \n Tabulazione \t E numerosi altri. In pratica il backslash in tali linguaggi usano il carattere di backslash per indicare una sequenza di due caratteri che indica la presenza di un carattere speciale. Dato tutto ciò per rappresentare lo stesso backslash si deve raddoppiare il backslash stesso \\. Le regole e gli effetti del metodo WriteLine applicate ad un oggetto StreamWriter sono analoghe a quelle dell’analogo metodo applicato alla classe Console (Console.WriteLine(…)). Ovviamente è possibile scrivere qualunque dato numerico, alfanumerico o booleano su un file di testo. La scrittura su un file procede anch’essa in modo analogo a quanto avviene solitamente su console. Di norma non si procede a ritroso, ma la scrittura procede progressivamente in avanti. Via via si possono inserire ritorni a capo opportuni, che producono una nuova linea nel file di testo. In pratica il testo scritto con la stessa sequenza di operazioni e di comandi di a capo su console o su file, produce gli stessi effetti su entrambi gli elementi di gli output. Chiusura di un file di testo in scrittura La classe StreamWriter prevede il comando per effettuare una opportuna operazione di chiusura delle operazioni di scrittura precedentemente attivate (con l’apertura) su un file: …. sw.Close(); … Il metodo non ha parametri in quanto tutte le informazioni sullo stream da chiudere sono già presenti nella variabile oggetto sw. Sebbene non sia tassativa la chiusura di un file (che viene effettuata automaticamente dal programma), è opportuno effettuarla in quanto essa effettua di default lo svuotamento del buffer di scrittura su disco e quindi forza il sistema a riportare tutti i contenuti del buffer nel file. Fino a quando questo non succede, la scrittura nel file potrebbe risultare incompleta, in quanto i contenuti scritti nel buffer potrebbero non essere stati effettivamente scritti nel file di testo. La lettura di un file di testo Le operazioni di lettura su un file di testo possono essere effettuate tramite l’utilizzo della classe StreamReader che ha funzionalità simmetriche a quelle della classe StreamWriter precedentemente considerata. L’apertura in lettura di un file si effettua dunque: StreamReader = new StreamReader(); La composizione del path segue le stesse regole che sono state elencate sopra in dettaglio per l’apertura in scrittura di un file. In questo caso si possono avere due evenienze: Pagina 3
Il file di testo esiste -> esso viene aperto e letto Il file di testo non esiste -> viene prodotto un errore (detta in C# eccezione) del tipo FileNotFoundException Successivamente, esistono appositi metodi anche in questo caso per leggere dal file il suo contenuto. Anche in questo caso i metodi considerati sono analoghi a quelli utilizzati sulla classe Console. In questo caso però esistono ulteriori problematiche. Nel file esiste una quantità di dati non quantificabile a priori, e spesso notevole (un file di testo può essere costituito da migliaia di righe da leggere. Ciò comporta che sia esclusa una lettura comandata da singole istruzioni riga per riga, ma si debba instaurare un cosiddetto ciclo di lettura dei dati. Tale ciclo in questo caso dovrà individuare quando i dati da leggere siano esauriti. La lettura avviene tenendo memoria del punto di lettura precedentemente raggiunto4, per proseguire con il successivo passo di lettura. Quando tale punto di lettura raggiunge la fine del file è evidente che non vi sono più dati da leggere. Un ciclo di lettura avrà quindi la seguente conformazione-tipo: StreamReader sr; string linea; …. sr = new StreamReader("dati.txt"); …. while (! sr.EndOfStream) { linea = sr.ReadLine(); Console.WriteLine(linea); } Questo per un esempio di semplice lettura del testo riga per riga con stampa diretta sulla console dello stesso. In pratica questo ciclo, dato un file di testo riporta direttamente e fedelmente il contenuto del file sulla console, qualunque sia il suo contenuto. Ovviamente le operazioni legate alla lettura del file sono le più svariate e possono essere diversa da questa. Si noti che nella condizione di ciclo c’è un controllo che verifica se il punto di lettura sia o no arrivato alla fine del file, leggendo una proprietà (EndOfStream) che di volta in volta rende il valore (false – il punto di lettura non è alla fine del file o true – il punto di lettura è alla fine del file). Riposizionamento nel file in lettura Come comprensibile, durante la lettura è possibile che il file debba essere letto una seconda volta per ulteriori elaborazioni sul suo contenuto. L’operazione di riposizionare il cursore di lettura è semplice e possibile. Basta utilizzare l’istruzione: … sr.BaseStream.Position = 0; … Nell’esempio indicato il riposizionamento del cursore avviene riportando lo stesso all’inizio del file di testo (scostamento, rispetto all’origine di 0 caratteri). Questo è il caso senz’altro più frequente di utilizzo di questa istruzione. In altri casi può capitare di desiderare spostare il cursore in qualche altra posizione intermedia. Si ricordi a questo proposito che in un file di testo su più righe, ogni fine riga conta quanto 2 caratteri, essendo formato infatti dall’accoppiamento dei codici 10 e 13 ASCII. Quindi anche se i fine linea non appaiono visibili vanno considerati nei calcoli per il riposizionamento opportuno del cursore di lettura. Chiusura del file di testo in lettura Esiste per simmetria, come per lo stream in scrittura (StreamWriter), anche nello stream in lettura, una operazione di chiusura dello stream: 4 Si potrebbe paragonare al dito che le maestre insegnano a tenere, durante la lettura di un testo, sulla parola che leggevamo, alle elementari. Pagina 4
…. sr.Close(); …. Che ovviamente và eseguita al termine delle operazioni. Il comando comunque può essere omesso, in quanto il programma effettua una chiusura automatica dello stream stesso, senza quindi comunque dare errore se lo stream in lettura non viene esplicitamente chiuso. Altri tipi di letture di un file Lettura di un file di testo per singoli caratteri Mostriamo qui una variante della comune lettura per righe, vale a dire una lettura del file di testo che proceda un carattere alla volta. Talora si rivela più pratica e può essere utilizzata in alcuni specifici problemi: StreamReader sr; char ch; … sr.BaseStream.Position = 0; while (! sr.EndOfStream) { ch = (char) sr.Read(); Console.Write(ch); } … Come si nota lo schema di base dell’operazione di lettura è lo stesso, ma invece di andare a leggere un intera linea di testo (precedentemente assegnata ad una stringa), si legge un solo carattere, assegnandolo ad un char. Il metodo applicato ad sr in questo caso è il Read() senza parametri. Esso rende il codice Unicode del carattere letto e lo rende come intero (trattandosi appunto di un codice). Si rende quindi necessaria una operazione di cast fra int e char. Dopodiché volendo è possibile stampare il carattere. Anche in questo esempio l’output su console del frammento di programma coincide perfettamente con il contenuto del file (come si potrebbe vedere in un editor di testo, ad esempio), ma ovviamente la tecnica di lettura dei dati è diversa dalla precedente per righe. Lettura in un'unica operazione di un file di testo In taluni casi piuttosto che effettuare un ciclo che legga linea per linea il file si preferisce elaborarlo in modo posticipato, effettuando la lettura in una sola operazione, ed inserendo tutto quanto letto in una variabile stringa. In questo caso quindi tutto il contenuto del file diviene il contenuto della var. stringa. In C# esiste un apposito metodo per effettuare questo tipo di operazione utilizzando oggetti della classe StreamReader. Ecco un esempio: …. StreamReader sr; string testo; … sr.BaseStream.Position = 0; testo = sr.ReadToEnd(); Console.Write(testo); … Che ha l’effetto delle altre routine di lettura dei dati sul file, ossia stampa tutto il testo, compresi caratteri di a capo in esso contenuti. Lettura di dati “comma delimited” Supponiamo che nel file siano memorizzati dati separati da opportuni segni di separazione (nel nostro caso virgole). Supponiamo poi di voler leggere i dati, ovviamente suddivisi in modo opportuno. Ci troviamo di fronte ad un classico problema di lettura di un file dove i dati sono delimitati da virgole, ossia “comma delimited”. Pagina 5
Spesso questo tipo di file con delimitazioni interne tra i dati è utilizzato per raggruppare gruppi di dati correlati fra loro. Supponiamo di andare a leggere un file in cui su ogni riga il primo dato sia il cognome, il secondo sia il voto di Italiano, il terzo il voto di Matematica, il quarto il voto in Informatica. Questi dati potrebbero essere inseriti in un vettore di variabili strutturate per essere poi elaborati liberamente. Vediamo come si potrebbe effettuare la lettura degli stessi: Consideriamo che il file di testo potrà avere un contenuto del tipo: verdi,4,7,9 rossi,5,8,3 neri,9,5,4 bianchi,3,7,8 dove i dati sono appunto delimitati fra loro da virgole. Il codice C# atto a caricare in un programma tali dati è il seguente: ... struct Alunno { public string cognome; public int voto_ita; public int voto_mate; public int voto_info; } ... static void Main(string[] args) { StreamReader sr; string linea; string[] dati; Alunno[] registro = new Alunno[20]; int i; sr = new StreamReader("dati.txt"); i = 0; while (!sr.EndOfStream) { linea = sr.ReadLine(); dati = linea.Split(','); registro[i].cognome = dati[0]; registro[i].voto_ita = int.Parse(dati[1]); registro[i].voto_mate = int.Parse(dati[2]); registro[i].voto_info = int.Parse(dati[3]); i++; } ... Questo è il frammento di codice che permette di caricare in un vettore di struct di elementi Alunno (chiamato registro); i dati vengono: per prima cosa letti per righe il contenuto delle righe viene suddiviso in più stringhe contenenti i singoli dati grazie alla presenza delle virgole i dati vengono opportunamente convertiti ed assegnati ai campi del vettore strutturato. In questo modo possono essere elaborati liberamente nel programma. Altre operazioni sui file In alcuni casi effettuare operazioni sui file comportano anche operazioni sul file system . Si deve specificare che le operazioni considerate qui di seguito non riguardano solo i file di testo, ma tutti i tipi di files indistintamente. Le più comuni tra esse sono: Controllo dell’esistenza di un certo file in un certo path Pagina 6
Cancellazione di un file Ridenominazione di un file. Spostamento di un file Copia di un file Controllo di esistenza di un file E’ possibile effettuarlo utilizzando la classe File. La classe in questo caso viene usata senza allocare variabili oggetto, ma direttamente. Il metodo (statico) che essa usa per il controllo dell’esistenza del file è Exists(….), applicato al path + nome del file di cui si desidera controllare la presenza: … File.Exists(); … Il metodo rende un booleano che indica se il file è presente (true) o meno (false). Usualmente tale istruzione è utilizzata in istruzioni di selezione. Cancellazione di un file In questo caso ci si pone come scopo quello di rimuovere un file dal file system. In questo caso deve essere utilizzato il metodo (statico) Delete: …. File.Delete(); …. Nulla accade se il file non esiste. Ridenominazione di un file o suo spostamento Il problema di muovere un file viene risolto tramite il metodo Move, sempre appartenente alla classe File. Esso prevede due parametri, che prevedono l’input del path + nome del file originario (primo parametro) e path + nome del path di destinazione (secondo parametro): …. File.Move(, ); …. Ovviamente questa operazione può servire ed è pensata anche nel caso si voglia rinominare il file. Il path di origine in questo caso potrà essere il medesimo. L’operazione produrrà un eccezione FileNotFoundException se il file di origine non esistesse. L’operazione produrrà una eccezione IOException se il file di destinazione dovesse già esistere e dovrebbe quindi essere sovrapposto. Copia di un file Sempre utilizzando la classe File, si può operare tramite il metodo Copy, che aspetta sempre due parametri, uno indicante il path ed il nome del file originario, ed il secondo il path ed il nome del file sorgente. …. File.Copy(, ); … Il comando genera una copia del file di origine, eventualmente con un nome diverso. L’operazione produrrà un eccezione FileNotFoundException se il file di origine non esistesse. L’operazione produrrà una eccezione IOException se il file di destinazione dovesse già esistere e dovrebbe quindi essere sovrapposto. Pagina 7
Puoi anche leggere