要读取 PNG 文件的字节,我想创建一个名为 的函数read_8_bytes,每次调用该函数时都会读取文件中接下来的 8 个字节。
fn main(){
let png = File::open("test.png").expect("1");
let mut png_reader = BufReader::new(png);
let mut byteBuffer: Vec<u8> = vec![0;8];
png_reader.read_exact(&mut byteBuffer).expect("2");
}
Run Code Online (Sandbox Code Playgroud)
这工作正常,如果我继续read_exact从 main 调用,我可以读取接下来的 8 个字节。我尝试创建一个函数来执行此操作,但解决方案似乎不必要地复杂。我想知道是否有更好的方法。
我以为我必须将 BufReader 传递给该函数,但由于 Rust 的工作原理,这使事情变得复杂,我最终发现我需要做类似的事情:
fn read_eight_bytes<R: BufRead>(fd: &mut R)
Run Code Online (Sandbox Code Playgroud)
这可以编译,但我不高兴,因为我不明白为什么需要这样做并且看起来很复杂。有没有一种简单的方法可以让我可以将文件描述符类型的东西传递给函数并让它像 C 中那样存储位置而不必这样做?
看看你的问题,我认为你想说你很困惑为什么这<R: BufRead>是必要的,或者为什么这甚至有效。
在您的示例中,这个泛型并不是绝对必要的。人们可以像这样实现您描述的功能:
use std::{fs, io};
fn main() -> io::Result<()> {
let mut file = fs::File::open("./path/to/file")?;
let bytes = read_eight_bytes(&mut file)?;
println!("{:?}", bytes);
Ok(())
}
fn read_eight_bytes(file: &mut fs::File) -> io::Result<[u8; 8]> {
use io::Read;
let mut bytes = [0; 8];
file.read_exact(&mut bytes)?;
Ok(bytes)
}
Run Code Online (Sandbox Code Playgroud)
这是完全有效的,希望应该有意义。
但是,为什么fn read_eight_bytes<R: BufRead>(file: &mut R) -> [u8; 8]有效呢?首先,我假设您了解以下概念:
了解了上述概念后,您应该知道这种语法意味着该函数read_eight_bytes是一个泛型函数,其泛型类型名为R。然后您还应该了解泛型具有特征绑定,需要类型R来实现BufRead。该函数采用一个参数,该参数是对变量的可变引用file,该变量的类型为R.
现在看一下 的定义BufRead:我们看到它包含几个函数。但奇怪的是没有任何read_exact功能!为什么这样的函数可以编译?
use std::{fs, io};
use io::BufRead;
fn main() -> io::Result<()> {
let file = fs::File::open("./path/to/file")?;
let mut reader = io::BufReader::new(file);
let bytes = read_eight_bytes(&mut reader)?;
println!("{:?}", bytes);
Ok(())
}
fn read_eight_bytes<R: BufRead>(reader: &mut R) -> io::Result<[u8; 8]> {
let mut bytes = [0; 8];
reader.read_exact(&mut bytes)?;
Ok(bytes)
}
Run Code Online (Sandbox Code Playgroud)
注意:我已将返回类型更改为
io::Result<...>.unwrap与ing every相比,这被认为是更好的做法Result。我还更改了函数调用以使用 a,BufReader因为BufReader实现了BufRead,而File没有实现。我将在下面进一步介绍其中的差异。
之所以有效,是因为它BufRead是一个超级特质。这意味着任何实现的类型BufRead也必须实现Read。那么它一定有这个read_exact功能!
鉴于我们的函数从不需要函数,BufRead我们可以将特征更改为仅需要Read:
use std::{fs, io};
use io::Read;
fn main() -> io::Result<()> {
let file = fs::File::open("./path/to/file")?;
let mut reader = io::BufReader::new(file);
let bytes = read_eight_bytes(&mut reader)?;
println!("{:?}", bytes);
Ok(())
}
fn read_eight_bytes<R: Read>(reader: &mut R) -> io::Result<[u8; 8]> {
let mut bytes = [0; 8];
reader.read_exact(&mut bytes)?;
Ok(bytes)
}
Run Code Online (Sandbox Code Playgroud)
现在,这个变化有一些有趣的地方。read_eight_bytes现在可以(至少)两种不同的方式调用该函数:
use std::{fs, io};
use io::Read;
fn main() -> io::Result<()> {
let mut file = fs::File::open("./path/to/file")?;
let bytes = read_eight_bytes(&mut file)?;
println!("{:?}", bytes);
let file = fs::File::open("./path/to/file")?;
let mut reader = io::BufReader::new(file);
let bytes = read_eight_bytes(&mut reader)?;
println!("{:?}", bytes);
Ok(())
}
fn read_eight_bytes<R: Read>(reader: &mut R) -> io::Result<[u8; 8]> {
let mut bytes = [0; 8];
reader.read_exact(&mut bytes)?;
Ok(bytes)
}
Run Code Online (Sandbox Code Playgroud)
为什么是这样?这是因为 和File都BufReader实现了该Read特征。因此两者都可以与该read_eight_bytes函数一起使用!
那么为什么有人想要使用其中一个或File而BufReader不是另一个呢?
文档BufReader解释了这一点:
BufReader 结构为任何读取器添加缓冲。
直接使用 Read 实例可能效率非常低。例如,每次对 TcpStream 的 read 调用都会导致系统调用。BufReader 对底层 Read 执行大量、不频繁的读取,并维护结果的内存缓冲区。
BufReader 可以提高对同一文件或网络套接字进行小规模重复读取调用的程序的速度。当一次阅读大量内容或只阅读一次或几次时,它没有帮助。当从内存中已有的源(如 Vec)读取数据时,它也没有任何优势。
现在,还记得我们之前是如何为File类型编写这个函数的吗?人们想要用泛型编写它的主要原因是调用者可以做出上面提出的选择。这是图书馆的常见做法,这种选择确实很重要。然而,泛型的代价是增加编译时间(过度使用时)和增加代码复杂性。