Pergunta

Eu tenho um arquivo enorme, onde eu tenho que inserir determinados caracteres em um local específico. Qual é a maneira mais fácil de fazer isso em C # sem reescrever o arquivo inteiro novamente.

Foi útil?

Solução

Sistemas de arquivos não suportam "inserção" de dados no meio de um arquivo. Se você realmente tem uma necessidade para um arquivo que pode ser escrito em uma espécie classificada de forma, eu sugiro que você olhar para usar um banco de dados incorporado.

Você pode querer dar uma olhada SQLite ou BerkeleyDB .

Então, novamente, você pode estar trabalhando com um arquivo de texto ou um arquivo legado binário. Nesse caso, sua única opção é reescrever o arquivo, pelo menos do ponto de inserção até o final.

Eu olhava para o FileStream classe para fazer aleatória I / O em C #.

Outras dicas

Você provavelmente terá que reescrever o arquivo a partir do ponto de inserir as alterações ao fim. Você pode ser melhor sempre escrevendo para o final do arquivo e usar ferramentas como o tipo e grep para obter os dados na ordem desejada. Eu estou supondo que você está falando de um arquivo de texto aqui, não um arquivo binário.

Não há nenhuma maneira para inserir caracteres em um arquivo sem reescrevê-los. Com C # pode ser feito com qualquer classe Stream. Se os arquivos são enormes, eu recomendo que você use Básicos da GNU Utils dentro C # código. Eles são os mais rápidos. Eu costumava lidar com grandes arquivos de texto com os utils núcleo (de tamanhos 4GB, 8GB ou mais etc). Comandos como cabeça, cauda, ??dividir, csplit, gato, shuf, triturar, uniq realmente ajudar muito na manipulação de texto.

Por exemplo, se você precisa colocar alguns caracteres em um arquivo de 2 GB, você pode usar bytecount -b divisão, colocar o ouptut para um arquivo, anexe o novo texto a ele, e tirar o resto do conteúdo e adicionar isto. Isso deve supostamente ser mais rápido do que qualquer outra forma.

espero que funcione. Experimentá-lo.

Você pode usar o acesso aleatório a gravação para locais específicos de um arquivo, mas você não será capaz de fazê-lo no formato de texto, você tem que trabalhar com bytes diretamente.

Se você souber o local específico para o qual você quer escrever os novos dados, use a 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);
}

Você pode dar uma olhada neste projeto: Inspector de dados Win

Basicamente, o código é a seguinte:

// 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);
    }
}

Dependendo do escopo do seu projeto, você pode querer decidir para inserir cada linha de texto com o seu arquivo em um Tabela de estrutura de dados. Como uma espécie de tabela de banco de dados , de que maneira você pode inserir para um local específico em um dado momento, e não tem que ler-in, modificar e saída de todo o arquivo de texto de cada vez. Isto é devido ao fato de que seus dados é "enorme", como você colocou. Você ainda iria recriar o arquivo, mas pelo menos você criar uma solução escalável desta forma.

Pode ser "possível", dependendo de como as armazena arquivos de sistema de arquivos de forma rápida inserção (ou seja, adicionar mais) bytes no meio. Se é remotamente possível que só pode ser viável para fazê-lo um bloco completo de cada vez, e só quer fazer baixo nível de modificação do próprio sistema de arquivos ou usando uma interface de sistema de arquivos específico.

Sistemas de arquivos não são geralmente projetados para esta operação. Se você precisa fazer rapidamente inserções você realmente precisa de um banco de dados mais geral.

Dependendo da sua aplicação um meio termo seria bando suas inserções juntos, então você faz uma reescrita do arquivo ao invés de vinte anos.

Você sempre vai ter que reescrever os bytes restantes do ponto de inserção. Se neste momento é em 0, então você vai reescrever o arquivo inteiro. Se ele é de 10 bytes antes do último byte, então você vai reescrever os últimos 10 bytes.

Em qualquer caso não há nenhuma função para apoiar diretamente a "inserção de arquivo". Mas o seguinte código pode fazê-lo com precisão.

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);

Para obter um melhor desempenho para o arquivo IO, jogo com "mágicas dois números amplificados", como no código acima. A criação do arquivo usa um buffer de 262144 bytes (256 KB) que não ajuda em tudo. O mesmo tampão para a inserção faz o "trabalho performance" como você pode ver pelos resultados StopWatch se você executar o código. Um projecto de teste no meu PC deu os seguintes resultados:

13628.8 ms para a criação e 3597.0971 ms para inserção.

Note que o byte-alvo para a inserção é 10, o que significa que quase todo o arquivo foi reescrito.

Por que você não colocar um ponteiro para o final do arquivo (literalmente, quatro bytes acima do tamanho atual do arquivo) e, em seguida, no final de gravação de arquivo o comprimento dos dados inseridos e, finalmente, os dados que deseja inserir-se. Por exemplo, se você tem uma corda no meio do arquivo, e você deseja inserir alguns caracteres no meio da cadeia, você pode escrever um ponteiro para o final do arquivo mais de cerca de quatro caracteres na seqüência, e escrever, em seguida, que quatro caracteres para o conjunto final com os caracteres em primeiro lugar queria inserir. É tudo sobre dados de encomenda. Claro, você só pode fazer isso se você estiver escrevendo o arquivo inteiro por si mesmo, quero dizer que você não está usando outros codecs.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top