我一直在研究一些代码,这些代码从块中的Read类型(input)读取数据并对每个块进行一些处理。问题是最终的块需要使用不同的函数进行处理。据我所知,有几种方法可以从 a 中检测 EOF Read,但对于这种情况,没有一种方法特别符合人体工程学。我正在寻找更惯用的解决方案。
我当前的方法是维护两个缓冲区,这样如果下一次读取读取零字节(在这种情况下表示 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(())
}
Run Code Online (Sandbox Code Playgroud)
我觉得这是一种非常丑陋的方法,我不需要在这里使用两个缓冲区。
我能想到的另一种选择是强加Seek并input使用input.read_exact(). 然后,我可以检查UnexpectedEof错误类型以确定我们已经到达输入末尾,并向后查找以再次读取最后的块(此处再次查找和读取是必要的,因为在以下情况下缓冲区的内容未定义)UnexpectedEof错误)。但这似乎根本不符合习惯:遇到错误、向后查找并再次读取只是为了检测我们是否位于文件末尾,这是非常奇怪的。
我理想的解决方案是这样的,使用一个虚input.feof()函数,如果最后一次input.read()调用到达 EOF,则返回 true,就像feofC 中的系统调用:
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(())
}
Run Code Online (Sandbox Code Playgroud)
任何人都可以建议一种更惯用的实现方法吗?谢谢!
当readofstd::io::Read返回时Ok(n),这不仅意味着缓冲区已被来自该源的数据字节buf填充。n,但它也表明索引之后的字节n(包括索引)保持不变。考虑到这一点,实际上根本不需要 a prev_buf,因为当n为 0 时,缓冲区的所有字节都将保持不变(将它们保留为先前读取的那些字节)。
prog-fh 的解决方案是您想要进行的处理类型的解决方案,因为它只会将完整的块移交给process_chunk. 可能会返回和read之间的值0BUF_SIZE,因此这是必需的。有关更多信息,请参阅上述链接的这一部分:
\n\n如果返回值 n 小于缓冲区大小,即使读取器尚未到达流的末尾,也不是错误。例如,这可能会发生,因为现在实际可用的字节较少(例如接近文件结尾)或因为 read() 被信号中断。
\n
但是,我建议您考虑一下当您Ok(0)从read。看这部分:
\n\n如果 n 为 0,则它可以指示以下两种情况之一:
\n\n
\n- 该读取器已到达文件\xe2\x80\x9d 的\xe2\x80\x9cend,并且可能不再能够生成字节。请注意,这并不意味着读取器将始终无法再生成字节。
\n
因此,如果您要获得返回的读取序列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它本身。