【问题标题】:Replacing a string within a stream in C# (without overwriting the original file)在 C# 中替换流中的字符串(不覆盖原始文件)
【发布时间】:2013-09-16 19:33:21
【问题描述】:

我有一个文件要打开到流中并传递给另一个方法。但是,我想在将流传递给其他方法之前替换文件中的字符串。所以:

string path = "C:/...";
Stream s = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
//need to replace all occurrences of "John" in the file to "Jack" here.
CallMethod(s);

不应修改原始文件,只能修改流。最简单的方法是什么?

谢谢...

【问题讨论】:

标签: c# .net string filestream


【解决方案1】:

如果你只是以行的形式读入文件,然后处理这些,而不是强迫自己坚持使用Stream,这会容易得多,因为流同时处理文本和二进制文件,并且需要能够一次读取一个字符(这使得这种替换非常困难)。如果你一次读一整行(只要你没有多行替换),这很容易。

var lines = File.ReadLines(path)
    .Select(line => line.Replace("John", "Jack"));

请注意,ReadLines 仍然会流式传输数据,而Select 不需要具体化整个内容,因此您在执行此操作时仍然不会一次将整个文件读入内存。

如果您实际上不需要流式传输数据,您可以轻松地将其全部加载为一个大字符串,进行替换,然后基于该字符串创建一个流:

string data = File.ReadAllText(path)
    .Replace("John", "Jack");
byte[] bytes = Encoding.ASCII.GetBytes(data);
Stream s = new MemoryStream(bytes);

【讨论】:

  • 如果文件是 10gb 怎么办?
  • @JeroenvanLangen 那么就没有问题了。唯一的问题是单行文本是否为 10GB。希望不是这样。
  • 我比我更喜欢这个答案。
  • @servy,为流媒体 +1,非常好的解决方案。就像你说的,1 行 10GB 不应该是 TextFile :)
  • 当我做 CallMethod(lines) 时它不喜欢它。它期望 System.IO.Stream 作为参数类型。我错过了一步吗?谢谢!
【解决方案2】:

这个问题可能有很多很好的答案。我会尝试一个我用过并且一直为我和我的同龄人工作的。

我建议您创建一个单独的流,例如 MemoryStream。从您的文件流中读取并写入内存。然后,您可以从中提取字符串并替换内容,然后将内存流提前传递。这可以确保您没有弄乱原始流,并且您可以在需要时从其中读取原始值,尽管使用此方法基本上使用了两倍的内存。

【讨论】:

  • 感谢您的建议,最终使用了内存流。
【解决方案3】:

如果文件有非常长的行,被替换的字符串可能包含换行符,或者在需要流式传输时存在其他限制阻止使用File.ReadLines(),则有一个替代解决方案仅使用流式传输,即使它不是微不足道的

实现您自己的流装饰器(包装器)来执行替换。即基于Stream 的类在其构造函数中采用另一个流,在其Read(byte[], int, int) 覆盖中从流中读取数据并在缓冲区中执行替换。如需进一步的要求和建议,请参阅notes to Stream implementers

我们称被替换的字符串为“needle”,源流为“haystack”,替换字符串为“replacement”。

Needle 和 Replacement 需要使用 haystack 内容的编码进行编码(通常是 Encoding.UTF8.GetBytes())。在流内部,数据不会转换为字符串,这与StreamReader.ReadLine() 不同。从而避免了不必要的内存分配。

简单案例:如果 needle 和 replacement 都只是一个字节,则实现只是一个简单的缓冲区循环,替换所有出现的地方。如果 needle 是单个字节并且替换为空(即删除字节,例如删除回车以进行行尾规范化),则这是一个简单的循环,维护缓冲区的 fromto 索引,逐字节重写缓冲区.

在更复杂的情况下,实现 KMP algorithm 来执行替换。

  • 将数据从底层流 (haystack) 读取到至少与 needle 一样长的内部缓冲区,并在将数据重写到输出缓冲区的同时执行替换。需要内部缓冲区,以便在检测到完全匹配之前不会发布来自部分匹配的数据 - 然后,返回并完全删除匹配将为时已晚。

  • 逐字节处理内部缓冲区,将每个字节输入 KMP 自动机。每次自动机更新时,将其释放的字节写入输出缓冲区中的适当位置。

  • 当 KMP 检测到匹配时,替换它:重置自动机保持内部缓冲区中的位置(删除匹配)并将替换写入输出缓冲区。

  • 当到达任一缓冲区的末尾时,保留内部缓冲区的未写入输出和未处理部分(包括当前部分匹配)作为下一次调用该方法的起点并返回当前输出缓冲区。对该方法的下一次调用将写入剩余的输出并开始处理当前停止的 haystack 的其余部分。

  • 当到达 haystack 末尾时,释放当前部分匹配并将其写入输出缓冲区。

请注意不要在处理 haystack 的所有数据之前返回一个空的输出缓冲区——这会向调用者发出流结束的信号并因此截断数据。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-19
    相关资源
    最近更新 更多