我一直在编写一些代码,从Read
类型(input
)中读取数据,并对每个块进行一些处理。问题是最后一个块需要用不同的函数来处理。据我所知,有几种方法可以从Read
中检测EOF,但在这种情况下没有一种感觉特别符合人体工程学。我在找一个更地道的解决方案。
我目前的方法是维护两个缓冲区,这样如果下一个读取读取零字节,则可以维护上一个读取结果,在这种情况下表示EOF,因为缓冲区的长度为非零:
use std::io::{Read, Result};
const BUF_SIZE: usize = 0x1000;
fn process_stream<I: Read>(mut input: I) -> Result<()> {
// Stores a chunk of input to be processed
let mut buf = [0; BUF_SIZE];
let mut prev_buf = [0; BUF_SIZE];
let mut prev_read = input.read(&mut prev_buf)?;
loop {
let bytes_read = input.read(&mut buf)?;
if bytes_read == 0 {
break;
}
// Some function which processes the contents of a chunk
process_chunk(&prev_buf[..prev_read]);
prev_read = bytes_read;
prev_buf.copy_from_slice(&buf[..]);
}
// Some function used to process the final chunk differently from all other messages
process_final_chunk(&prev_buf[..prev_read]);
Ok(())
}
我觉得这是一种非常丑陋的方式,我不应该在这里使用两个缓冲区。
我能想到的一个替代方案是在input
上强加Seek
,并使用input.read_exact()
。然后,我可以检查UnexpectedEof
错误类型,以确定我们已经到达了输入的末尾,并向后查找以再次读取最后一个块(这里需要再次查找和读取,因为在UnexpectedEof
错误的情况下,缓冲区的内容是未定义的)。但这似乎一点也不符合习惯用法:遇到一个错误,寻找回来,并再次阅读只是为了检测我们在一个文件的末尾是非常奇怪的。
我理想的解决方案是这样的,使用一个假想的input.feof()
函数,如果最后一个input.read()
调用到达EOF,则返回true,如feof
syscall in C:
fn process_stream<I: Read>(mut input: I) -> Result<()> {
// Stores a chunk of input to be processed
let mut buf = [0; BUF_SIZE];
let mut bytes_read = 0;
loop {
bytes_read = input.read(&mut buf)?;
if input.feof() {
break;
}
process_chunk(&buf[..bytes_read]);
}
process_final_chunk(&buf[..bytes_read]);
Ok(())
}
谁能提出一种更习惯的实现方法?谢谢!
2条答案
按热度按时间btqmn9zl1#
当
std::io::Read
的read
返回Ok(n)
时,这不仅意味着缓冲区buf
已经填充了来自此源的n
字节的数据。,但它也指示索引n
(包括n
)之后的字节保持不变。考虑到这一点,实际上根本不需要prev_buf
,因为当n
为0时,缓冲区的所有字节都将保持不动(使它们成为前一次读取的字节)。prog-fh的解决方案是您想要的处理类型,因为它只会将完整的块传递给
process_chunk
。由于read
可能返回一个介于0
和BUF_SIZE
之间的值,因此需要这样做。有关更多信息,请参阅上述链接的这一部分:如果返回的值n小于缓冲区大小,这不是错误,即使读取器还没有到达流的末尾。这可能发生,例如,因为现在实际可用的字节较少(例如:例如,接近文件结束)或因为read()被信号中断。
但是,我建议您考虑一下,当您从
read
中获得一个不代表永远结束文件的Ok(0)
时,应该发生什么。请参阅本部分:如果n为0,则它可以指示两种情况之一:
1.此读取器已到达其“文件结尾”,可能无法再生成字节。请注意,这并不意味着读取器将永远无法再产生字节。
因此,如果您要获得一个返回
Ok(BUF_SIZE), Ok(BUF_SIZE), 0, Ok(BUF_SIZE)
的读取序列(这是完全可能的,它只是表示IO中的一个故障),您是否不希望将最后一个Ok(BUF_SIZE)
视为读取块?如果你把Ok(0)
永远当作EOF,那可能是一个错误。可靠地确定什么应该被认为是最后一个块的唯一方法是将预期的长度(以字节为单位,而不是块的数量)作为协议的一部分预先发送。给定一个变量
expected_len
,然后可以通过expected_len - expected_len % BUF_SIZE
确定最后一个块的开始索引,而expected_len
本身的结束索引。8ehkhllq2#
既然你认为
read_exact()
是一个可能的解决方案,那么我们可以认为一个非final块正好包含BUF_SIZE
字节。那么,为什么不尽可能多地读取数据来填充这样一个缓冲区,然后用一个函数处理它,然后,当绝对不可能时(因为达到EOF),用另一个函数处理不完整的最后一个块呢?注意,C中的
feof()
不会 * 猜测 * 在下次读取尝试时会达到EOF;它仅报告在先前读取尝试期间可能已经设置的EOF标志。因此,要设置EOF并报告它,必须首先遇到返回0的读取尝试(如下面的示例所示)。编辑
感谢@AndreKR的评论,算法不考虑大小为零的最终块似乎很重要。即当文件中的字节总量是预期块大小的精确倍数时)。因此,我调整了算法(最近,对不起。..),结果是额外的复杂性:我们必须在处理每个完整的块之前保存它,只是为了检查下一个是否为空,然后决定哪个块可以被认为是最终的。
请注意,我选择在两个不同的缓冲区之间交替,并让字节就位,而不是将字节从一个容器复制到另一个容器。