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

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

Commenti

  1. Articolo ben fatto.
    Solo 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.

    RispondiElimina
    Risposte
    1. Ciao, grazie mille per i suggerimenti!
      Permettimi 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!

      Elimina
    2. 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().

      Elimina
    3. Documentandomi 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

Posta un commento

Post popolari in questo blog

Arrotondamento e troncamento in Java

Eclipse: Shortcuts (scorciatoie) da tastiera

Strutture dati: List, Set, Map

Creare un eseguibile Java