Rust Read、BufRead、BufReader.. 您所在的位置:网站首页 什么叫reader Rust Read、BufRead、BufReader..

Rust Read、BufRead、BufReader..

2023-06-12 01:37| 来源: 网络整理| 查看: 265

今天写代码的时候有一个需求,我希望在某个代表路径的字符串不为空时,以这个路径来读取文件,得到一个File对象:

if xxx is not empty str { let file = File::open(Path::new(fpath)) .expect(format!("cannot open {}", fpath).as_str()); }

而当它为空时,我希望从Stdin来读取:

else { let stdin = std::io::stdin(); }

我现在需要在if-else块下面进行读取,我希望使用一个变量来接收file或者stdin,也就是像如下这样:

// 这段代码还不能编译,需要补全xxx部分的内容 let mut read_from: ??? = if xxx is not empty str { let file = File::open(Path::new(fpath)) .expect(format!("cannot open {}", fpath).as_str()); ??? } else { let stdin = std::io::stdin(); ??? };

后来,我摸索了几下,最后发现可以写成这样:

let mut read_from: Box = if xxx is not empty str { let file = File::open(Path::new(fpath)) .expect(format!("cannot open {}", fpath).as_str()); Box::new(file) } else { let stdin = std::io::stdin(); Box::new(stdin) };

Stdin类型以及File类型都实现了Read trait,所以,这样写是没有问题的。下面先来看一下Read trait。

Read Trait

Read Trait允许从一个给定字节来源进行读取。

Read Trait的实现者称为readers

Read被定义成只有一个必要方法——read()。每一个read()调用都将尝试从源中拉取数据到一个指定buffer。还有一系列方法都是根据read()实现的,所以实现者只需要实现一个read()方法,就获得了一系列其它的方法。

Reader(Read的实现者)之间应该是可组合的,std::io中的很多实现者都需要或者提供一个实现了Readtrait的类型。

上一句有点难以理解,如果你学过Java,你应该知道IO流的装饰器模式,这里就有点像装饰器模式,即使用一个Read实现包装另一个Read实现,稍后介绍BufReader时会看到。

请注意,每一个read()调用都有可能卷入一个系统调用,因此,使用一些BufRead的实现会更高效,例如:BufReader

让我们看看实际的代码:

use std::io::{Read, stdin}; use std::env; use std::fs::File; use std::path::Path; use std::io; fn main() { let args: Vec = env::args().collect(); let mut read_from: Box = if args.len() > 1 { let fpath = args.get(1).expect("cannot get argument!"); let file = File::open(Path::new(fpath)) .expect(format!("cannot open {}", fpath).as_str()); Box::new(file) } else { Box::new(stdin()) }; // add some code here }

在add some code here处,可以添加对read_from的调用,它是一个Read trait的实现者,现在,我们已经向下面的代码隐藏了它具体是一个文件还是来自于stdin了。

Read.read

下面会简单的介绍一下Read trait的API,但并不会像官方文档一样面面俱到。如果想了解更多,请移步:Rust官方文档#Read

从这个来源中拉取一些字节到指定的buffer上,返回读取了多少字节。

// 你可以把下面的代码追加到上面的 add some code here处 // 它会从file或者stdin中读取,这取决于是否有一个参数指示了文件路径 let mut buf: [u8; 1024] = [0; 1024]; while let Ok(nbytes_read) = read_from.read(&mut buf) { // 如果没有字节可读,跳出循环 if nbytes_read == 0 { break } // 从buffer构造字符串 let content = String::from_utf8_lossy(&buf[0..nbytes_read]); print!("{}", content); }

当read()返回的n为0时,可能意味着reader已经到达了“end of file(EOF)”,并且有可能不会再产生新字节了。但是请注意,这并不意味着reader将永远不会产生新的的字节。举个例子,在linux上,这个方法将为一个TcpStream调用recv系统调用,此时返回0代表着连接已经被正确关闭。而当在处理文件时,有可能你到达了文件尾,并且得到了0这个结果,但是,如果更多数据被追加到文件中,未来的调用可能会返回更多数据。

Read.read_to_end

很容易理解,读取到末尾。

可以使用以下代码简化之前的代码,但想想这会带来什么问题。

let mut vec: Vec = Vec::new(); read_from.read_to_end(&mut vec); println!("{}", String::from_utf8(vec).unwrap())

当你并没有使用管道重定向stdin,并且也没通过参数指定一个文件,此时,你需要手动的在stdin中输入。直到你退出程序,read_to_end都不会认为已经读取到了末尾,所以,你输入的内容并不会被重新打印在终端上。

这个函数将持续调用read()来向buf中添加数据,直到read()返回Ok(0)或者返回非ErrorKind::Interrupted类型的错误。

Read.read_to_string

直接读取到字符串中。

let mut string = String::new(); read_from.read_to_string(&mut string); println!("{}", string); Read.read_exact

read_exact(&mut self, buf: &mut [u8])

读取指定字节以填满buf,当尚未填满时遇到EOF,返回ErrorKind::UnexpectedEof

BufRead Trait

Read就介绍到这里,我们知道了,Read的实现者叫Reader,其中一个必须的方法是read(),其它的都具有基于read()的默认实现,并且,我们通过它的官方文档也了解到了,官方文档其实并不推荐直接使用Read,因为每一次调用read,都有可能卷入一次系统调用。官方比较推荐的是BufRead——一个带缓存的Read Trait。

pub trait BufRead: Read { // ... }

Rust是支持Trait之间的继承的,BufRead继承自Read。好家伙,又学到了,我发现看Rust的源码也能学到不少啊。

BufRead是一种具有内部buffer的Reader,允许执行几种额外的读取。比如,在不使用buffer时,按行读取是低效的,所以如果你想要按行读取,你需要BufRead,它包含read_line方法,可以作为一个lines的迭代器使用。

有点像Java里面普通Reader和BufferedReader的区别。

通过Idea中的实现者查看功能,我们可以看到,这个trait有如下实现者:

img

BufReader是BufRead的一个实现,允许通过组合方式对Reader进行包装扩展,这很类似Java的装饰器模式。

#[stable(feature = "rust1", since = "1.0.0")] pub struct BufReader { inner: R, buf: Buffer, } impl BufReader { // ... }

inner是一个Read的实现者,也就是一个Reader,所以我们可以通过BufReader,让任意一个Reader具有BufRead的功能,即具有内部缓冲区以及支持按行读取。

// 使用BufReader包装原始Reader let mut buf_reader = BufReader::new(reader); // 现在,它具有了BufRead Trait中的所有功能 for line in buf_reader.lines() { // xxx }

如果你理清了Read、BufRead、BufReader之间的关系,我们将上面的代码改成BufRead版本。如果你的脑袋很乱,那么提示一下,Read和BufRead是特质,是trait,BufReader是实现,请把特质和实现分开。

// 使用BufRead Trait替代Read Trait let mut read_from: Box = if args.len() > 1 { let fpath = args.get(1).expect("cannot get argument!"); let file = File::open(Path::new(fpath)) .expect(format!("cannot open {}", fpath).as_str()); // File是一个Read,使用BufReader包装它 Box::new(BufReader::new(file)) } else { Box::new(BufReader::new(stdin())) }; // 按行读取 for line in read_from.lines() { println!("{}", line.unwrap()); }

如果再进一步,我们发现,Stdin类型中的Read Trait的实现,其实完全是对其内部StdinLock的委托:

img

而StdinLock本身就实现了BufRead

img

所以,上面的代码可以改成:

let mut read_from: Box = if args.len() > 1 { // ... Box::new(BufReader::new(file)) } else { // 直接使用`stdin().lock()` Box::new(stdin().lock()) };

关于BufReadTrait其它方法的使用,已经超出本篇博客的内容了,关键的关键,不是我们学会了Read、BufRead、BufReader的区别以及用法,而是我们看到了Rust标准库中的一个设计思路,我们学会了使用Trait继承、默认Trait实现、组合来进行装饰器模式设计(管它在Rust中叫什么呢),当以后我们遇到类似的设计需求时,我们也可以使用这一模式进行设计。我们还学会了使用Box来对一个Trait的不同实现进行整合,以向后面隐藏具体的实现,这也是多态性在Rust中的体现。

总结 Read Trait可以从一个字节来源中进行读取 Read Trait的实现者称为Reader,他们必须实现read()方法,而其它方法都有默认实现 BufRead Trait继承自Read,提供了额外的一些方法,提供内置缓冲区以及按行读取,我们无需再手动处理buffer了 BufReader是BufRead的一个实现,使用装饰器模式对底层Reader进行功能扩展 File和Stdin实现了Read,Stdin中的Read实现就是对其内部StdinLock的委托 StdinLock实现了BufRead


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有