Modificare una riga di un file
DOMANDA:
Come si modifica una riga di un file senza creare un file di appoggio duplicato?
RISPOSTA:
La soluzione più semplice, ma anche più dispendiosa, è quella di leggere ciclicamente dal file di origine una riga, controllare se è quella da modificare e scriverla in un nuovo file continuando sino all'EOF (end-of-file); al termine si cancella il file di origine e si lascia il nuovo.
Ovviamente tutta questa procedura è molto laboriosa, in particolare di fronte a file di dimensioni notevoli.
L'esempio che vi proponiamo oggi fa in modo di modificare al volo la riga e di scriverle direttamente sullo stesso file di origine:
package file; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; public class ModificaFile { public static void main(String[] args) { FileInputStream fstream = null; DataInputStream in = null; BufferedWriter out = null; try { // apro il file fstream = new FileInputStream("c:/prova.txt"); // prendo l'inputStream in = new DataInputStream(fstream); BufferedReader br = new BufferedReader(new InputStreamReader(in)); String strLine; StringBuilder fileContent = new StringBuilder(); // Leggo il file riga per riga while ((strLine = br.readLine()) != null) { System.out.println(strLine); // stampo sulla console la riga corrispondente if(strLine.equals("pluto")){ // se la riga è uguale a quella ricercata fileContent.append("AGGIORNATO"+System.getProperty("line.separator")); } else { // ... altrimenti la trascrivo così com'è fileContent.append(strLine); fileContent.append(System.getProperty("line.separator")); } } // Sovrascrivo il file con il nuovo contenuto (aggiornato) FileWriter fstreamWrite = new FileWriter("c:/prova.txt"); out = new BufferedWriter(fstreamWrite); out.write(fileContent.toString()); } catch (Exception e) { e.printStackTrace(); } finally { // chiusura dell'output e dell'input try { fstream.close(); out.flush(); out.close(); in.close(); } catch (IOException e) { e.printStackTrace(); } } } }
Ricordatevi ovviamente di avere un file di nome prova.txt in C:\ con un contenuto simile a questo:
pippo
pluto
paperino
pippo
pluto
paperino
pluto
paperino
pippo
pluto
paperino
Se tutto è andato a buon fine, dopo l'esecuzione della classe ModificaFile, troverete il file C:\prova.txt con il seguente contenuto:
pippo
AGGIORNATO
paperino
pippo
AGGIORNATO
paperino
AGGIORNATO
paperino
pippo
AGGIORNATO
paperino
Articolo ben fatto.
RispondiEliminaSolo alcune considerazioni.
1) Non usare \r\n: usa piuttosto System.getProperty("line.separator"); è cross-platform.
2) Prima di effettuare la close() sull'oggetto out assicurati di effettuare un flush() (i sistemi operativi sono subdoli)
3) Questa metodologia ha il vantaggio di non creare un file di appoggio, ma non risolve il problema dei file grandi. Al contrario: soffre di più della soluzione con file di appoggio perchè caricando tutto il file in memoria nello StringBuilder rischi di esaurirla, sollevando una OutOfMemoryException che farà crashare il programma.
4) La gestione delle eccezioni va un po' rivista: gli oggetti di gestione dei file andrebbero dichiarati fuori dal blocco try/catch poi inizializzati all'interno di tale blocco; il blocco andrebbe inoltre integrato con la clausola finally in cui si effettua la chiusura dei file.
Ciao, grazie mille per i suggerimenti!
EliminaPermettimi di rispondere:
1) Provvedo a mettere una pezza :o)
2) Se non erro la close() dovrebbe chiamare flush() implicitamente! Ti risulta questa info?
3) Pienamente d'accordo, per file di grosse dimensioni va trovata una soluzione differente, ma cercavo di rispondere alla domanda "...senza usare un file di appoggio".
4) Anche qui hai ragione. L'esempio non si prefiggeva lo scopo di essere uno stato dell'arte di gestione delle eccezioni, ma sicuramente è giusto essere precisi :o)... metterò una pezza anche lì!
Grazie per la lettura e di nuovo per i suggerimenti!
Sì, la close() richiama un flush() su tutti gli oggetti Java sottostanti... ma senza propagarlo anche al sistema operativo (cosa che la flush() fa). Ho avuto, purtroppo, in passato diversi casi "strani" dovuti proprio a questo comportamento: ora come ora non effettuo mai più una close() senza prima essermi assicurato un flush().
EliminaDocumentandomi un po' in giro sembrerebbe che il problema che sollevi sia dovuto al close() in alcune implementazioni del JDK che non richiama esplicitamente il flush(). Quindi, non sapendo che versione di JDK si sta utilizzando, è buona norma "flushare" sempre il contenuto. Non si finisce mai di imparare :o)
Elimina