使用C# / .NET替换文件中的文本的最佳方法是什么?

7
我有一个文本文件,作为大型数据提取的一部分而被写入。文本文件的第一行是提取的“账户”数量。
由于提取的特性,该数字直到过程结束才能确定,但文件可能很大(几百兆)。
在C# / .NET中打开文件(在这种情况下是简单的文本文件),并替换第一行文本中的数据,哪种方式最好?
重要提示:我不需要替换“固定字节数”-那很容易。问题在于需要插入到文件顶部的数据是可变的。
重要提示2:有些人问及/提到仅将数据保存在内存中然后替换它……但那完全不可能。此过程之所以更新是因为有时会在将几GB加载到内存时崩溃。

"#####\r\n(意思是没有填充)" 你确定不能有前导零吗? - Binary Worrier
6个回答

4
如果可以的话,您应该插入一个占位符,然后在最后用实际数字和空格来覆盖它。
如果不行,请先将数据写入缓存文件。当您知道实际数字时,创建输出文件并从缓存中追加数据。

这是我想要做的(保留一些空白)-唯一的问题是我要写入的文件格式需要精确的#####\r\n(意思是没有填充)。-不过回答很好。 - Timothy Khouri
@Henk - 不要在前导零上浪费时间 - 如果那个方案可行的话,“二进制焦虑者”提出了一个好的解决方案。 - Timothy Khouri

3
“最佳”是非常主观的。对于任何小型文件,您可以轻松地在内存中打开整个文件,并使用字符串替换将其更改,然后重新写入文件。即使对于较大的文件,将其加载到内存中也并不难。在多个G的内存时代,我认为数百兆仍然可以轻松地在内存中完成。
您是否已经测试过这种朴素的方法?您是否有遇到过实际问题?
如果这是一个非常大的文件(大小为几GB),我建议首先将所有数据写入临时文件,然后将正确的文件与标题行一起写入,然后将其余数据附加到文件中。由于它只是文本,我可能会直接使用DOS外壳程序:
 TYPE temp.txt >> outfile.txt

2

我不需要替换“固定字节数”

你确定吗? 如果你将一个大数写入文件的第一行(UInt32.MaxValue或UInt64.MaxValue),然后当你找到正确的实际数字时,你可以用正确的数字替换那些字节,但是左侧添加零,这样它仍然是一个有效的整数。 例如:

Replace  999999 - your "large number placeholder"
With     000100 - the actual number of accounts

聪明的解决方法!- 但是我正在处理的文件规范不接受那个... 不过想法很好 :) - Timothy Khouri
这只是一个文件规范,它没有回答我的问题 :P - Timothy Khouri
1
“这只是一个文件规范”,这并没有告诉我任何信息。你能否包含一小段定义计数应该是什么的规范片段?很抱歉,我很难想象有什么东西不能使用前导零。不过没关系,这只是为了我的自我提高。谢谢伙计。 - Binary Worrier

2

如果我正确理解了问题,最好的方法是用C# / .NET打开文件(在这种情况下是一个简单的文本文件),并替换第一行文本中的数据。

创建文件时,在文件顶部放置一个标记{UserCount}。

然后使用TextReader逐行读取文件。如果是第一行,则查找{UserCount}并替换为您的值。使用TextWriter写出每一行您读取到的内容。

例如:

    int lineNumber = 1;
    int userCount = 1234;
    string line = null;

    using(TextReader tr = File.OpenText("OriginalFile"))
    using(TextWriter tw = File.CreateText("ResultFile"))
    {

        while((line = tr.ReadLine()) != null)
        {
            if(lineNumber == 1)
            {
                line = line.Replace("{UserCount}", userCount.ToString());
            }

            tw.WriteLine(line);
            lineNumber++;
        }

    }

这基本上就是我所要做的,但我的目标是必须创建2个文件。 - Timothy Khouri
我还有一个解决方案,但我还没有验证或尝试过。基本上你需要使用像StreamWriter流这样的东西来写入你的第一个文件并保持它打开。同时按照我的建议编写占位符并保留令牌的起始和结束点。所以现在你已经到了文件的末尾,你已经有了UserCount,只需要回去用你的值替换令牌即可。为此,你可以使用BitStream,我相信你可以通过访问StreamWriter.BaseStream来获取它,并且可以将字节写入流中的特定位置。我会尝试测试一下并发布结果。 - Jim Scott

2

好的,之前我建议了一种处理现有文件更好的方法。

但是在您的情况下,您想要创建文件并在创建过程中返回顶部并写出用户计数。这将正好做到这一点。

以下是一种无需编写临时文件即可完成的方法。

    private void WriteUsers()
    {   
        string userCountString = null;
        ASCIIEncoding enc = new ASCIIEncoding();
        byte[] userCountBytes = null;
        int userCounter = 0;

        using(StreamWriter sw = File.CreateText("myfile.txt"))
        {
            // Write a blank line and return
            // Note this line will later contain our user count.
            sw.WriteLine();

            // Write out the records and keep track of the count 
            for(int i = 1; i < 100; i++)
            {
                sw.WriteLine("User" + i);
                userCounter++;
            }

            // Get the base stream and set the position to 0
            sw.BaseStream.Position = 0;

            userCountString = "User Count: " + userCounter;

            userCountBytes = enc.GetBytes(userCountString);

            sw.BaseStream.Write(userCountBytes, 0, userCountBytes.Length);
        }

    }

1
实际上,这是唯一回答编辑现有文件请求的答案...谢谢! - ephraim

1
如果提取的文件只有几百兆字节,那么您可以轻松地将所有文本保存在内存中,直到提取完成。然后,您可以将输出文件作为最后一个操作进行编写,从记录计数开始。

1
我机器上只有2G内存,我们办公室的其他人大多数都有4到8G。 200MB算什么?可能只占总内存的10%… - Jack Bolding
一年后,当文件“只有几个千兆字节”时,你也会将它们全部保存在内存中吗? - Binary Worrier
1
我应该现在就开始担心两年后会发生什么吗?两年后,我预计将运行一个具有至少8GB RAM的四核处理器x64机器。为什么我不能将其保留在内存中? - Jack Bolding
2
将时间浪费在不必要的优化上是浪费时间。现在做简单的事情,如果情况在“两年内”发生变化,则升级计算机的内存。你看到最近内存的成本了吗?它们几乎是免费赠送的。 - Tim Long
物理内存数量并不重要。对于非常大的分配,重要的是进程地址空间的大小。在32位进程中,默认情况下为2 GB。因此,200 MB的文件占用整个地址空间的10%。这是一个非常大的分配,在认真考虑之前需要三思而后行。在CLR中,它将来自大对象堆,该堆未压缩,这意味着会出现碎片。如果编写64位程序,则另当别论,但由于指针大小加倍,可能会发现性能有所下降。 - Daniel Earwicker

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