2016-03-01 16 views
6

Voglio leggere e rimuovere la prima riga da un file txt (senza copiare, è un file enorme).
Ho letto la rete ma chiunque copia semplicemente il contenuto desiderato in un nuovo file. Non posso farlo.Leggere e rimuovere la prima riga (o l'ultima) dal file txt senza copiare

Sotto un primo tentativo. Questo codice verrà bloccato in un loop in quanto non vengono rimosse righe. Se il codice rimuovesse la prima riga di file ad ogni apertura, il codice raggiungerà la fine.

#include <iostream> 
#include <string> 
#include <fstream> 
#include <boost/interprocess/sync/file_lock.hpp> 

int main() { 
    std::string line; 
    std::fstream file; 
    boost::interprocess::file_lock lock("test.lock"); 
    while (true) { 
     std::cout << "locking\n"; 
     lock.lock(); 
     file.open("test.txt", std::fstream::in|std::fstream::out); 
     if (!file.is_open()) { 
      std::cout << "can't open file\n"; 
      file.close(); 
      lock.unlock(); 
      break; 
     } 
     else if (!std::getline(file,line)) { 
      std::cout << "empty file\n"; // 
      file.close();    // never 
      lock.unlock();    // reached 
      break;      // 
     } 
     else { 
      // remove first line 
      file.close(); 
      lock.unlock(); 
      // do something with line 
     } 
    } 
} 
+1

file semplicemente non funziona in questo modo (simile a un array cruda, non è possibile rimuovere il primo elemento senza spostare tutti gli elementi rimanenti in uno slot). – crashmstr

+0

Ogni ramo di quell'istruzione 'if' ha' file.close(); lock.unlock(); '. Il distruttore per l'oggetto 'std :: file' chiuderà il file, quindi non è necessario chiuderlo esplicitamente (e quando' file.is_open() 'restituisce false, non è necessario chiuderlo). E c'è senza dubbio un tipo RAII in Boost per gestire quel blocco, con un distruttore per sbloccarlo. –

+0

Sì certo, "tutti" ... https://www.google.com/search?q=c%2B%2B+modify+file+in+place –

risposta

3

Che cosa si vuole fare, anzi, non è facile.

Se apri lo stesso file per leggerlo e scriverlo senza fare attenzione, finirai per leggere ciò che hai appena scritto e il risultato non sarà quello che desideri.

Modificare il file è possibile: basta aprirlo, cercarlo, modificarlo e chiuderlo. Tuttavia, si desidera copiare tutto il contenuto del file tranne K byte all'inizio del file. Significa che dovrai leggere e scrivere iterativamente l'intero file per blocchi di N byte.

Una volta terminato, i byte K rimarranno alla fine che è necessario rimuovere. Non penso che ci sia un modo per farlo con i flussi. È possibile utilizzare le funzioni ftruncate o truncate da unistd.h o utilizzare Boost.Interprocesstruncate per questo.

Ecco un esempio (senza alcun controllo degli errori, ho lasciato lo si aggiunge):

#include <iostream> 
#include <fstream> 
#include <unistd.h> 

int main() 
{ 
    std::fstream file; 
    file.open("test.txt", std::fstream::in | std::fstream::out); 

    // First retrieve size of the file 
    file.seekg(0, file.end); 
    std::streampos endPos = file.tellg(); 
    file.seekg(0, file.beg); 

    // Then retrieve size of the first line (a.k.a bufferSize) 
    std::string firstLine; 
    std::getline(file, firstLine); 

    // We need two streampos: the read one and the write one 
    std::streampos readPos = firstLine.size() + 1; 
    std::streampos writePos = 0; 

    // Read the whole file starting at readPos by chunks of size bufferSize 
    std::size_t bufferSize = 256; 
    char buffer[bufferSize]; 
    bool finished = false; 
    while(!finished) 
    { 
    file.seekg(readPos); 
    if(readPos + static_cast<std::streampos>(bufferSize) >= endPos) 
    { 
     bufferSize = endPos - readPos; 
     finished = true; 
    } 
    file.read(buffer, bufferSize); 
    file.seekg(writePos); 
    file.write(buffer, bufferSize); 
    readPos += bufferSize; 
    writePos += bufferSize; 
    } 
    file.close(); 

    // No clean way to truncate streams, use function from unistd.h 
    truncate("test.txt", writePos); 
    return 0; 
} 

mi piacerebbe davvero essere in grado di fornire una soluzione più pulita per la modifica sul posto del file, ma non sono sicuro che ce ne sia uno.

+0

Sarebbe più semplice leggere e rimuovere l'ultima riga? Tale soluzione sarebbe anche sufficiente per me. – user1587451

+0

Con gli stream, AFAIK, a meno che non si apra con il flag truncate, non è possibile ridurre la dimensione del file, è possibile solo aumentarlo (ma potrei sbagliarmi). –

2

Ecco una soluzione scritta in C per Windows. Verrà eseguito e terminato su un file di 700.000 linee, 245 MB in pochissimo tempo. (0,14 secondi)

Fondamentalmente, la memoria mappa il file, in modo che sia possibile accedere ai contenuti utilizzando le funzioni utilizzate per l'accesso alla memoria non elaborata. Una volta che il file è stato mappato, uso semplicemente la funzione strchr per trovare la posizione di una delle coppie di simboli usati per denotare un EOL in windows (\ n e \ r) - questo ci dice per quanto tempo in byte la prima riga è .

Da qui, ho appena memcpy dal primo byte della seconda riga indietro all'inizio dell'area di memoria mappata (in pratica, il primo byte nel file).

Una volta eseguita questa operazione, il file non viene mappato, l'handle per il file mem-mapped viene chiuso e viene quindi utilizzata la funzione SetEndOfFile per ridurre la lunghezza del file in base alla lunghezza della prima riga. Quando chiudiamo il file, si è ridotto di questa lunghezza e la prima riga è sparita.

Avere il file già in memoria da quando l'ho appena creato e scritto, ovviamente modifica un po 'il tempo di esecuzione, ma il meccanismo di caching di Windows è il "colpevole" qui - lo stesso meccanismo che stiamo sfruttando per rendere operazione completa molto rapidamente.

I dati di test sono la fonte del programma duplicati 100.000 volte e salvati come testInput2.txt (incollarlo 10 volte, selezionare tutto, copiare, incollare 10 volte - sostituendo l'originale 10, per un totale di 100 volte - ripetere fino all'output abbastanza grande.Mi sono fermato qui perché più sembrava rendere Notepad ++ un po 'infelice)

Il controllo degli errori in questo programma è praticamente inesistente e l'ingresso non dovrebbe essere UNICODE, cioè l'input è di 1 byte per carattere. La sequenza EOL è 0x0D, 0x0A (\ r \ n)

Codice:

#include <stdio.h> 
#include <windows.h> 

void testFunc(const char inputFilename[]) 
{ 
    int lineLength; 

    HANDLE fileHandle = CreateFile(
            inputFilename, 
            GENERIC_READ | GENERIC_WRITE, 
            0, 
            NULL, 
            OPEN_EXISTING, 
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, 
            NULL 
            ); 

    if (fileHandle != INVALID_HANDLE_VALUE) 
    { 
     printf("File opened okay\n"); 

     DWORD fileSizeHi, fileSizeLo = GetFileSize(fileHandle, &fileSizeHi); 

     HANDLE memMappedHandle = CreateFileMapping(
                fileHandle, 
                NULL, 
                PAGE_READWRITE | SEC_COMMIT, 
                0, 
                0, 
                NULL 
               ); 
     if (memMappedHandle) 
     { 
      printf("File mapping success\n"); 
      LPVOID memPtr = MapViewOfFile(
              memMappedHandle, 
              FILE_MAP_ALL_ACCESS, 
              0, 
              0, 
              0 
             ); 
      if (memPtr != NULL) 
      { 
       printf("view of file successfully created"); 
       printf("File size is: 0x%04X%04X\n", fileSizeHi, fileSizeLo); 

       LPVOID eolPos = strchr((char*)memPtr, '\r'); // windows EOL sequence is \r\n 
       lineLength = (char*)eolPos-(char*)memPtr; 
       printf("Length of first line is: %ld\n", lineLength); 

       memcpy(memPtr, eolPos+2, fileSizeLo-lineLength); 
       UnmapViewOfFile(memPtr); 
      } 

      CloseHandle(memMappedHandle); 
     } 
     SetFilePointer(fileHandle, -(lineLength+2), 0, FILE_END); 
     SetEndOfFile(fileHandle); 
     CloseHandle(fileHandle); 
    } 
} 

int main() 
{ 
    const char inputFilename[] = "testInput2.txt"; 
    testFunc(inputFilename); 
    return 0; 
} 
Problemi correlati