如果文件有非常长的行,被替换的字符串可能包含换行符,或者File.ReadLines()
在需要流式传输时有其他限制阻止使用,则有一个仅使用流的替代解决方案,即使它不是微不足道的。
实现您自己的执行替换的流装饰器(包装器)。即基于它的类Stream
在其构造函数中采用另一个流,在其Read(byte[], int, int)
覆盖中从流中读取数据并在缓冲区中执行替换。有关进一步的要求和建议,请参阅Stream 实施者的说明。
我们称被替换的字符串为“needle”,源流为“haystack”,替换字符串为“replacement”。
针和替换需要使用 haystack 内容的编码进行编码(通常是Encoding.UTF8.GetBytes()
)。在流内部,数据不会转换为字符串,这与StreamReader.ReadLine()
. 因此避免了不必要的内存分配。
简单案例:如果 needle 和 replacement 都只是一个字节,则实现只是一个简单的缓冲区循环,替换所有出现的地方。如果 needle 是单个字节并且替换为空(即删除字节,例如删除回车以进行行尾规范化),则它是一个简单的循环维护from
和to
对缓冲区的索引,逐字节重写缓冲区。
在更复杂的情况下,实现KMP 算法来执行替换。
将数据从底层流(haystack)读取到至少与 needle 一样长的内部缓冲区,并在将数据重写到输出缓冲区的同时执行替换。需要内部缓冲区,以便在检测到完全匹配之前不发布来自部分匹配的数据——然后,返回并完全删除匹配就为时已晚。
逐字节处理内部缓冲区,将每个字节输入 KMP 自动机。每次自动机更新时,将它释放的字节写入输出缓冲区中的适当位置。
当 KMP 检测到匹配时,替换它:重置自动机,保持内部缓冲区中的位置(删除匹配)并将替换写入输出缓冲区。
当到达任一缓冲区的末尾时,保留内部缓冲区的未写入输出和未处理部分(包括当前部分匹配)作为下一次调用该方法的起点并返回当前输出缓冲区。对该方法的下一次调用写入剩余的输出并开始处理当前停止的干草堆的其余部分。
当 haystack 结束时,释放当前部分匹配并将其写入输出缓冲区。
请注意在处理 haystack 的所有数据之前不要返回一个空的输出缓冲区——这会向调用者发出流结束的信号,从而截断数据。