如何使用C#向文件中插入字符

13

我有一个很大的文件,需要在特定位置插入某些字符。在不重新编写整个文件的情况下,在C#中最简单的方法是什么。


好问题。我也需要弄清楚你所问的内容,而且你的问题是谷歌搜索的第一个结果。 - Scott Marlowe
10个回答

10

文件系统不支持在文件中间“插入”数据。如果你确实需要一个可以按排序方式写入的文件,建议考虑使用嵌入式数据库。

你可能需要查看SQLiteBerkeleyDB

然而,如果你正在处理文本文件或旧的二进制文件,则唯一的选择是从插入点到文件结尾重写文件。

我建议你查看FileStream类来进行C#中的随机I/O操作。


那并不完全正确。您可以使用随机访问从任何点(以字节为单位)读取和写入文件。但是,在插入内容时,您需要自行移动文件偏移量。换句话说,重新生成文件更简单。 - FlySwat
2
我不同意。当然,你可以使用随机访问来寻找文件中的任何位置。但是如果你在那个位置写入,你将覆盖之前在那个位置的内容。因此,如果你的文件中有"abced",你寻找到'c'并写入"123",你最终得到的是"ab123",而不是"ab123cde"。 - bineteri

3

您可能需要从插入更改的位置重写文件到结尾。最好总是将信息写入文件末尾,并使用诸如sort和grep等工具按所需顺序获取数据。我假设您在这里谈论的是文本文件,而不是二进制文件。


我实际上正在寻找一些使用C#的随机访问技术,即使我必须使用不安全的代码。无论如何,感谢您的建议。 - Gulzar Nazim

2
没有办法在不重写文件的情况下插入字符。使用C#可以使用任何流类完成此操作。如果文件很大,建议您在C#代码中使用GNU Core Utils。它们是最快的。我曾经使用核心工具处理非常大的文本文件(大小为4GB、8GB或更大等)。head、tail、split、csplit、cat、shuf、shred、uniq等命令在文本操作方面确实有很大帮助。
例如,如果您需要将一些字符放入2GB文件中,可以使用split -b BYTECOUNT,将输出放入文件中,将新文本附加到其中,并获取其余内容并添加到其中。这应该比其他任何方法都要快。
希望它能起作用。试一试吧。

1
你可以查看这个项目:Win Data Inspector
基本上,代码如下:
// 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);
    }
}

请不要将相同的答案复制/粘贴到多个问题中。此外,请注意在此处宣传自己的工作 - 我们有反对公开自我推销的规定。 - S.L. Barth
在这种情况下,也许你想阅读《如何提供个人开源库?》(https://meta.stackexchange.com/q/229085)。 - Martijn Pieters

1
如果您知道要写入新数据的特定位置,请使用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);
}

1
请注意,此代码将覆盖位置15处的数据。 - data

1

你可以使用随机访问来写入文件的特定位置,但你无法以文本格式进行操作,必须直接使用字节。


请问您能否指出一些网络资源?我认为在C#中进行随机访问文件处理是不可能的。 - Gulzar Nazim
我认为他不想覆盖旧字节。 - Cristian Ciupitu

0

您总是需要从插入点开始重写剩余的字节。如果该点位于0,则需要重写整个文件。如果它在最后一个字节之前的10个字节处,则需要重写最后的10个字节。

无论如何,没有直接支持“插入到文件”的功能。但是以下代码可以准确地完成此操作。

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

为了获得更好的文件IO性能,可以像上面的代码一样使用“魔法二次幂”进行优化。创建文件时使用的缓冲区大小为262144字节(256KB),但并没有起到任何帮助作用。相同大小的缓冲区用于插入操作时,可以看到StopWatch结果表现出了良好的性能。在我的电脑上进行的初步测试结果如下:
创建:13628.8毫秒,插入:3597.0971毫秒。
请注意,插入的目标字节数为10,这意味着几乎整个文件都被重写了。

0
根据您的项目范围,您可能希望决定将文本的每一行插入到一个表数据结构中。就像数据库表一样,这样您可以在任何给定时刻插入到特定位置,而不必每次读入、修改和输出整个文本文件。鉴于您所说的数据“巨大”,这是非常必要的。您仍然需要重新创建文件,但至少以这种方式创建可扩展的解决方案。

0

根据文件系统如何存储文件,可能可以“快速”插入(即添加额外的)字节。如果远程可能,每次仅通过对文件系统进行低级修改或使用特定于文件系统的接口以一个完整块为单位执行此操作可能是可行的。

文件系统通常不设计此操作。如果您需要快速进行插入,则确实需要更通用的数据库。

根据您的应用程序,中间地带将是将插入分组在一起,因此您只需重写文件而不是二十个。


0
为什么不将指针放在文件末尾(确切地说,是在当前文件大小的四个字节之上),然后在文件末尾写入插入数据的长度,最后再写入要插入的数据本身呢?例如,如果您在文件的中间有一个字符串,并且您想要在字符串的中间插入几个字符,您可以在字符串的某四个字符上写入指针到文件末尾,然后将这四个字符与您首先想要插入的字符一起写入到文件末尾。这就是关于数据排序的问题。当然,只有在您自己编写整个文件时,才能够这样做,我的意思是您不使用其他编解码器。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接