题
我有一个巨大的文件,我必须在其中的特定位置插入某些字符。在 C# 中执行此操作而无需再次重写整个文件的最简单方法是什么?
其他提示
您可能需要从插入更改到末尾重写文件。您最好始终写入文件末尾,并使用 sort 和 grep 等工具以所需的顺序获取数据。我假设您在这里谈论的是文本文件,而不是二进制文件。
无法在不重写字符的情况下将字符插入到文件中。使用 C# 可以使用任何 Stream 类来完成。如果文件很大,我建议您在 C# 代码中使用 GNU Core Utils。他们是最快的。我曾经使用核心实用程序处理非常大的文本文件(大小为 4GB、8GB 或更大等)。head、tail、split、csplit、cat、shuf、shred、uniq 等命令在文本操作方面确实有很大帮助。
例如,如果您需要将一些字符放入 2GB 文件中,则可以使用 split -b BYTECOUNT,将输出放入文件中,将新文本附加到其中,然后获取其余内容并添加到其中。据说这应该比任何其他方式都要快。
希望它有效。试一试。
您可以使用随机访问写入文件的特定位置,但无法以文本格式执行此操作,您必须直接使用字节。
如果您知道要将新数据写入的具体位置,请使用 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);
}
你可以看一下这个项目:赢得数据检查器
基本上,代码如下:
// 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);
}
}
根据项目的范围,您可能需要决定将文件中的每一行文本插入到 表数据结构。有点像数据库表, ,这样您就可以在任何给定时刻插入到特定位置,而不必每次都读入、修改和输出整个文本文件。这是因为您的数据正如您所说的那样“巨大”。您仍然会重新创建该文件,但至少您以这种方式创建了一个可扩展的解决方案。
根据文件系统存储文件的方式,在中间快速插入(即添加额外的)字节可能是“可能的”。如果远程可能,则可能只能一次完成整个块,并且只能通过对文件系统本身进行低级修改或使用文件系统特定接口来实现。
文件系统通常不是为这种操作而设计的。如果您需要快速执行插入,您确实需要一个更通用的数据库。
根据您的应用程序,中间立场是将插入内容捆绑在一起,因此您只需对文件进行一次重写,而不是二十次。
您始终必须重写插入点的剩余字节。如果该点为 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) 的缓冲区,这根本没有帮助。如果您运行代码,则用于插入的相同缓冲区会执行“性能作业”,正如您可以通过秒表结果看到的那样。在我的电脑上进行的草稿测试给出了以下结果:
创建时间为 13628.8 毫秒,插入时间为 3597.0971 毫秒。
请注意,插入的目标字节是 10,这意味着几乎整个文件都被重写。
为什么不放置一个指向文件末尾的指针(字面意思是文件当前大小之上的四个字节),然后在文件末尾写入插入数据的长度,最后写入要插入的数据本身。例如,如果文件中间有一个字符串,并且想要在字符串中间插入几个字符,则可以在字符串中的四个字符上写入一个指向文件末尾的指针,然后写入到最后的四个字符以及您首先要插入的字符。这都是关于订购数据的。当然,只有当您自己编写整个文件时才可以执行此操作,我的意思是您没有使用其他编解码器。