为什么调用后调用BufReader :: fill_buf消耗的返回字节少于我的预期?

时间:2018-10-04 15:10:19

标签: rust

我正在尝试从文件中实现UTF-8字符流。这是到目前为止,请原谅丑陋的代码。

use std::fs::File;
use std::io;
use std::io::BufRead;
use std::str;

fn main() -> io::Result<()> {
    let mut reader = io::BufReader::with_capacity(100, File::open("utf8test.txt")?);
    loop {
        let mut consumed = 0;
        {
            let buf = reader.fill_buf()?;
            println!("buf len: {}", buf.len());
            match str::from_utf8(&buf) {
                Ok(s) => {
                    println!("====\n{}", s);
                    consumed = s.len();
                }
                Err(err) => {
                    if err.valid_up_to() == 0 {
                        println!("1. utf8 decoding failed!");
                    } else {
                        match str::from_utf8(&buf[..err.valid_up_to()]) {
                            Ok(s) => {
                                println!("====\n{}", s);
                                consumed = s.len();
                            }
                            _ => println!("2. utf8 decoding failed!"),
                        }
                    }
                }
            }
        }
        if consumed == 0 {
            break;
        }
        reader.consume(consumed);
        println!("consumed {} bytes", consumed);
    }
    Ok(())
}

我有一个偏移量为98的多字节字符的测试文件,由于无法完全装入我(任意大小)的100字节缓冲区,因此无法解码。很好,我只是忽略它,并解码直到该字符开头的有效内容。

问题在于,在consume(98)上调用BufReader之后,对fill_buf()的下一次调用仅返回2个字节...似乎不必费心将更多的字节读入缓冲区。我不明白为什么。也许我误解了文档。

以下是示例输出:

buf len: 100
====
UTF-8 encoded sample plain-text file
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
consumed 98 bytes
buf len: 2
1. utf8 decoding failed!

如果from_utf8()返回部分已解码的字符串和解码错误的位置,那将是很好的,因此,每次发生这种情况我都不必两次调用它,但是似乎不会出现这样的情况。函数(我知道)在标准库中。

1 个答案:

答案 0 :(得分:0)

我鼓励您学习如何产生Minimal, Complete, and Verifiable example。这是专业程序员用来更好地理解问题并将注意力集中在问题的重要方面的一项宝贵技能。例如,您没有提供实际的输入文件,因此任何人都很难使用您提供的代码来重现您的行为。

经过反复试验,我能够将您的问题简化为以下代码:

use std::io::{self, BufRead};

fn main() -> io::Result<()> {
    let mut reader = io::BufReader::with_capacity(100, io::repeat(b'a'));

    let a = reader.fill_buf()?.len();
    reader.consume(98);
    let b = reader.fill_buf()?.len();

    println!("{}, {}", a, b); // 100, 2

    Ok(())
}

不幸的是,您的情况是BufRead的合同允许这种行为,实际上几乎是必需的。缓冲读取器的目的是避免尽可能多地调用底层读取器。该特征不知道您需要读取多少个字节,也不知道2个字节不够,它应该执行另一个调用。用另一种方式翻转它,假设您只消耗了100个字节中的1个字节-您是否希望将所有其余99个字节都复制到内存中,然后执行另一个基础读取?这比不首先使用BufRead还要慢!

该特征也没有任何规定将缓冲区中的剩余字节移到开头,然后再次填充缓冲区。似乎可以将其添加到具体的BufReader中,因此您可能希望提供拉取请求以将其添加。

目前,我建议在缓冲区末尾使用Read::read_exact

use std::io::{self, BufRead, Read};

fn main() -> io::Result<()> {
    let mut reader = io::BufReader::with_capacity(100, io::repeat(b'a'));

    let a = reader.fill_buf()?.len();
    reader.consume(98);

    let mut leftover = [0u8; 4]; // a single UTF-8 character is at most 4 bytes
    // Assume we know we need 3 bytes based on domain knowledge
    reader.read_exact(&mut leftover[..3])?;

    let b = reader.fill_buf()?.len();

    println!("{}, {}", a, b); // 100, 99

    Ok(())
}

另请参阅: