Domanda

Ho un file enorme, in cui devo inserire determinati caratteri in una posizione specifica.Qual è il modo più semplice per farlo in C# senza riscrivere nuovamente l'intero file.

È stato utile?

Soluzione

I file system non supportano l'"inserimento" di dati nel mezzo di un file.Se hai davvero bisogno di un file su cui sia possibile scrivere in modo ordinato, ti suggerisco di considerare l'utilizzo di un database incorporato.

Potresti voler dare un'occhiata SQLite O Berkeley DB.

Inoltre, potresti lavorare con un file di testo o un file binario legacy.In tal caso l'unica opzione è riscrivere il file, almeno dal punto di inserimento fino alla fine.

Vorrei guardare il FileStream per eseguire I/O casuali in C#.

Altri suggerimenti

Probabilmente dovrai riscrivere il file dal punto in cui inserisci le modifiche fino alla fine.Potrebbe essere meglio scrivere sempre fino alla fine del file e utilizzare strumenti come sort e grep per ottenere i dati nell'ordine desiderato.Presumo che tu stia parlando di un file di testo qui, non di un file binario.

Non è possibile inserire caratteri in un file senza riscriverli.Con C# è possibile farlo con qualsiasi classe Stream.Se i file sono enormi, ti consiglierei di utilizzare GNU Core Utils all'interno del codice C#.Sono i più veloci.Gestivo file di testo molto grandi con le utilità principali (di dimensioni 4 GB, 8 GB o più ecc.).Comandi come head, tail, split, csplit, cat, shuf, shred, uniq aiutano davvero molto nella manipolazione del testo.

Ad esempio, se devi inserire alcuni caratteri in un file da 2 GB, puoi utilizzare split -b BYTECOUNT, inserire l'output in un file, aggiungervi il nuovo testo, ottenere il resto del contenuto e aggiungerlo.Questo dovrebbe presumibilmente essere più veloce di qualsiasi altro modo.

Spero funzioni.Provaci.

Puoi utilizzare l'accesso casuale per scrivere in posizioni specifiche di un file, ma non sarai in grado di farlo in formato testo, dovrai lavorare direttamente con i byte.

Se conosci la posizione specifica in cui vuoi scrivere i nuovi dati, utilizza la classe BinaryWriter:

using (BinaryWriter bw = new BinaryWriter (File.Open (strFile, FileMode.Open)))
{
    string strNewData = "this is some new data";
    byte[] byteNewData = new byte[strNewData.Length];

    // copy contents of string to byte array
    for (var i = 0; i < strNewData.Length; i++)
    {
        byteNewData[i] = Convert.ToByte (strNewData[i]);
    }

    // write new data to file
    bw.Seek (15, SeekOrigin.Begin);  // seek to position 15
    bw.Write (byteNewData, 0, byteNewData.Length);
}

Potresti dare un'occhiata a questo progetto:Vinci Ispettore dati

Fondamentalmente il codice è il seguente:

// this.Stream is the stream in which you insert data

{

long position = this.Stream.Position;

long length = this.Stream.Length;

MemoryStream ms = new MemoryStream();

this.Stream.Position = 0;

DIUtils.CopyStream(this.Stream, ms, position, progressCallback);

ms.Write(data, 0, data.Length);

this.Stream.Position = position;

DIUtils.CopyStream(this.Stream, ms, this.Stream.Length - position, progressCallback);

this.Stream = ms;

}

#region Delegates

public delegate void ProgressCallback(long position, long total);

#endregion

DIUtils.cs

public static void CopyStream(Stream input, Stream output, long length, DataInspector.ProgressCallback callback)
{
    long totalsize = input.Length;
    long byteswritten = 0;
    const int size = 32768;
    byte[] buffer = new byte[size];
    int read;
    int readlen = length < size ? (int)length : size;
    while (length > 0 && (read = input.Read(buffer, 0, readlen)) > 0)
    {
        output.Write(buffer, 0, read);
        byteswritten += read;
        length -= read;
        readlen = length < size ? (int)length : size;
        if (callback != null)
            callback(byteswritten, totalsize);
    }
}

A seconda dell'ambito del tuo progetto, potresti decidere di inserire ogni riga di testo con il tuo file in un file struttura dati della tabella.Una specie di tabella di database, in questo modo puoi inserirlo in una posizione specifica in qualsiasi momento e non dover leggere, modificare e generare ogni volta l'intero file di testo.Ciò è dovuto al fatto che i tuoi dati sono "enormi" come dici tu.Ricreerai comunque il file, ma almeno creerai una soluzione scalabile in questo modo.

Potrebbe essere "possibile" a seconda di come il filesystem memorizza i file per inserire rapidamente (cioè aggiungere ulteriori) byte nel mezzo.Se è remotamente possibile, potrebbe essere fattibile farlo solo un blocco completo alla volta e solo eseguendo modifiche di basso livello al filesystem stesso o utilizzando un'interfaccia specifica del filesystem.

I filesystem generalmente non sono progettati per questa operazione.Se hai bisogno di fare rapidamente degli inserimenti hai davvero bisogno di un database più generale.

A seconda dell'applicazione, una via di mezzo sarebbe quella di raggruppare insieme gli inserti, in modo da eseguire solo una riscrittura del file anziché venti.

Dovrai sempre riscrivere i byte rimanenti dal punto di inserimento.Se questo punto è a 0, riscriverai l'intero file.Se sono 10 byte prima dell'ultimo byte, riscriverai gli ultimi 10 byte.

In ogni caso non esiste alcuna funzione che supporti direttamente "inserisci nel file".Ma il seguente codice può farlo in modo accurato.

var sw = new Stopwatch();
var ab = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ";

// create
var fs = new FileStream(@"d:\test.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 262144, FileOptions.None);
sw.Restart();
fs.Seek(0, SeekOrigin.Begin);
for (var i = 0; i < 40000000; i++) fs.Write(ASCIIEncoding.ASCII.GetBytes(ab), 0, ab.Length);
sw.Stop();
Console.WriteLine("{0} ms", sw.Elapsed.TotalMilliseconds);
fs.Dispose();

// insert
fs = new FileStream(@"d:\test.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 262144, FileOptions.None);
sw.Restart();
byte[] b = new byte[262144];
long target = 10, offset = fs.Length - b.Length;
while (offset != 0)
{
    if (offset < 0)
    {
        offset = b.Length - target;
        b = new byte[offset];
    }
    fs.Position = offset; fs.Read(b, 0, b.Length);
    fs.Position = offset + target; fs.Write(b, 0, b.Length);
    offset -= b.Length;
}
fs.Position = target; fs.Write(ASCIIEncoding.ASCII.GetBytes(ab), 0, ab.Length);
sw.Stop();
Console.WriteLine("{0} ms", sw.Elapsed.TotalMilliseconds);

Per ottenere prestazioni migliori per l'IO dei file, gioca con "numeri magici a due motori" come nel codice sopra.La creazione del file utilizza un buffer di 262144 byte (256KB) che non aiuta affatto.Lo stesso buffer per l'inserimento esegue il "lavoro prestazionale", come puoi vedere dai risultati del cronometro se esegui il codice.Una bozza di test sul mio PC ha dato i seguenti risultati:

13628,8 ms per la creazione e 3597,0971 ms per l'inserimento.

Si noti che il byte di destinazione per l'inserimento è 10, il che significa che è stato riscritto quasi l'intero file.

Perché non metti un puntatore alla fine del file (letteralmente, quattro byte sopra la dimensione corrente del file) e poi, alla fine del file scrivi la lunghezza dei dati inseriti e infine i dati che vuoi inserire si.Ad esempio, se hai una stringa al centro del file e desideri inserire alcuni caratteri al centro della stringa, puoi scrivere un puntatore alla fine del file su circa quattro caratteri nella stringa, quindi scrivere quei quattro caratteri fino alla fine insieme ai caratteri che volevi inserire per primi.Si tratta di ordinare i dati.Naturalmente, puoi farlo solo se stai scrivendo l'intero file da solo, cioè se non stai usando altri codec.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top