我有一个文件,我打开流并传递给另一个方法。但是,我想在将流传递给另一个方法之前替换文件中的字符串。所以:
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);
不应修改原始文件,只修改流。最简单的方法是什么?
...谢谢
答案 0 :(得分:11)
如果您只是将文件作为行读入,然后处理这些内容,而不是强迫自己坚持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);
答案 1 :(得分:2)
这个问题可能有很多好的答案。我会尝试一下我用过的,一直为我和我的同龄人工作。
我建议你创建一个单独的流,比如MemoryStream
。从文件流中读取并写入内存。然后,您可以从中提取字符串并替换内容,然后您将提前传递内存流。这样可以确保您不会弄乱原始流,并且您可以随时从中读取原始值,但使用此方法时使用的内存基本上是原来的两倍。
答案 2 :(得分:1)
如果文件中的行非常长,则替换的字符串可能包含换行符,或者存在其他限制,从而在需要流传输时阻止使用File.ReadLines()
,那么有一种替代解决方案仅,即使它不是很简单。
实施自己的执行替换的流装饰器(包装器)。即一个基于Stream
的类,该类在其构造函数中使用另一个流,以其Read(byte[], int, int)
重写从流中读取数据,并在缓冲区中执行替换。有关更多要求和建议,请参见notes to Stream implementers。
我们将要替换的字符串称为“ needle”,将源流称为“ haystack”,并将替换字符串“ replacement”。
需要使用干草堆内容的编码(通常为Encoding.UTF8.GetBytes()
)对针和替换进行编码。在流内部,与StreamReader.ReadLine()
不同,数据不会转换为字符串。这样可以防止不必要的内存分配。
简单情况:如果needle和replacement都只是一个字节,则实现只是缓冲区上的简单循环,可以替换所有出现的情况。如果needle是单个字节,而replacement为空(即删除该字节,例如删除行末尾归一化的回车),则这是一个简单的循环,它将from
和to
的索引保留到缓冲区,然后重写逐字节缓冲。
在更复杂的情况下,请实施 KMP algorithm 进行替换。
将数据从基础流(干草堆)读取到至少与针一样长的内部缓冲区,并在将数据重写到输出缓冲区的同时执行替换。需要内部缓冲区,这样才能在检测到完全匹配之前不发布部分匹配的数据-那么,现在返回并完全删除该匹配为时已晚。
逐字节处理内部缓冲区,将每个字节送入KMP自动机。在每次自动机更新时,将释放的字节写入输出缓冲区中的适当位置。
当KMP检测到匹配项时,将其替换:重置自动机,将其位置保留在内部缓冲区中(这会删除匹配项),并将替换项写入输出缓冲区中。
到达任意一个缓冲区的末尾时,请将内部缓冲区的未写输出和未处理部分(包括当前部分匹配)作为下一次调用该方法的起点,并返回当前输出缓冲区。对该方法的下一次调用将写入剩余的输出,并开始处理当前停止的干草堆的其余部分。
到达干草堆的末端时,释放当前的部分匹配并将其写入输出缓冲区。
请注意在处理所有干草堆数据之前,不要返回空的输出缓冲区,否则会向调用者发出流结束信号,从而截断数据。