当前位置:网站首页>【Rust 笔记】16-输入与输出(上)
【Rust 笔记】16-输入与输出(上)
2022-07-05 05:50:00 【phial03】
16 - 输入与输出
16.1 - 读取器与写入器
Rust 标准库针对输入与输出的特性,是通过
Read、BufRead和Write特型,以及实现它们的各种类型创建的。- 实现
Read的值是读取器(reader),有读取字节输入的方法。 - 实现
BufRead的值是缓冲读取器,支持Read的所有方法,且额外又支持读取文本行等的方法。 - 实现
Write的值是写入器(writer),既支持字节输出,也支持 UTF-8 文本输出。
- 实现
常用的读取器:读取字节
std::fs::File::open(filename):用于打开文件。std::net::TcpStream:用于从网络接收数据。std::io::stdin():用于从进程的标准输入流读取数据。std::io::Cursor<&[u8]>值:从内存的字节数组中 “读取” 数据。
常用的写入器:写入字节
std::fs::File::create(filename):用于打开文件。std::net::TcpStream:用于通过网络发送数据。std::io::stdout()和std::io::stderr():用于将数据写入终端。std::io::Cursor<&mut [u8]>:允许将任何可修改字节切片作为文件写入。Vec<u8>:也是一个写入器,它的write方法可以为向量追加元素。
基于
std::io::Read和std::io::Write特型实现的泛型代码,可以涵盖各种输入和输出渠道。// 从任何读取器,将全部字节复制到任何写入器 use std::io::{ self, Read, Write, ErrorKind}; const DEFAULT_BUF_SIZE: usize = 8 * 1024; pub fn copy<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W) -> io::Result<u64> where R: Read, W: Write { let mut buf = [0; DEFAULT_BUF_SIZE]; let mut written = 0' loop { let len = match reader.ead(&mut buf) { Ok(0) => return Ok(written), Ok(len) => len, Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, Err(e) => return Err(e), }; writer.write_all(&buf[..len])?; written += len as u64; } }std::io::copy()是泛型的,可以将数据从File复制到TcpStream,从Stdin复制到内存中的Vec<u8>。
四个常用的
std::io的特型Read、BufRead、Write和Seek的导入方法:导入一个专用的前置模块,可以直接包含它们:
use std::io::prelude::*;导入
std::io模块自身use std::io::{ self, Read, Write, ErrorKind}; // self可以将io生命为`std::io`模块的别名,这样std::io::Result和std::io::Error,就可以简单地写成io::Result和io::Error。
16.1.1 - 读取器
std::io::Read的常用读取器方法,用于读取数据,它们都以读取器本身的mut引用作为参数:reader.read(&mut buffer):从数据源读取某些字节,然后存储到给定的buffer中。buffer参数的类型是&mut [u8]。这个方法会读取
buffer.len()个字节。返回值的类型是
io::Result<u64>,这个是Result<y64, io::Error>类型的别名。读取成功,会返回
u64值的读取字节数,数值<= buffer.len()。读取错误,
.read()返回Err(err),其中err是io::Error值。.kind()方法可以返回io::ErrorKind类型的错误码。
reader.read_to_end(&mut byte_vec):从读取器中读取出所有剩余输入,并追加到byte_vec中。byte_vec是一个Vec<u8>类型的值。- 此方法返回
io::Result<(usize)>,表示读取到的字节数。 - 此方法对添加到向量中的数据量没有限制,不要对不可信的数据源使用它。可以使用
.take()方法来提高安全限制。
reader.read_to_string(&mut string):从读取器中读取出所有剩余输入,并追加到string中。- 如果输入流不是有效的 UTF-8,那么此方法会返回
ErrorKind::InvaliData错误。 - 除 UTF-8 外的其他字符集,可以通过开源的
encoding包支持。
- 如果输入流不是有效的 UTF-8,那么此方法会返回
read.read_exact(&mut buf):从读取器中读取恰好足够的数据,填充到给定的buffer中。- 参数类型是
&[u8]。 - 如果读取器在读到
buf.len()字节前就已经把数据读完了,那么此方法会返回ErrorKind::UnexpectedEof错误。
- 参数类型是
std::io::Read的常用适配器方法,以读取器(reader)的值作为参数,将其转换为一个迭代器或一个不同的读取器:reader.bytes():返回输入流字节的迭代器。
- 迭代器项的类型是
io::Result<u8>,每个字节都需要错误检查。 - 此方法对每个字节都会调用一次
reader.read(),对没有缓冲的读取器来说效率很低。
- 迭代器项的类型是
reader.chars():读取器为 UTF-8,并返回迭代器项是字符的迭代器。无效的 UTF-8 会导致InvalidData错误。reader1.chain(reader2):返回一个新的读取器,包含reader1和reader2的所有输入。reader.take(n):从与reader相同的数据源读取输入,但只读取n字节,再返回一个新的读取器。
读取器和写入去都会实现
Drop特型,在操作完成后会自动关闭。
16.1.2 - 缓冲读取器
缓冲:给读取器和写入器分配一块内存作为缓冲区,暂时保存输入和输出的数据。缓冲可以减少系统调用。
缓冲读取器实现了
Read和BufRead两个特型。BufRead特型的常用读取器方法:
reader.read_line(&mut line):读取一行文本并追加到
line。
line是一个String类型的值。- 行尾的换行符
'\n'或"\r\n"也会包含在line中。 - 返回值是
io::Result<usize>,表示读取的字节数,包括行终结符。 - 如果读取处于输入末尾,则
line不变,且返回Ok(0)。
reader.lines():返回输入行的迭代器。
- 迭代项类型是
io::Result<String>。 - 换行符不会包含在字符串中。
- 迭代项类型是
reader.read_until(stop_byte, &mut byte_vec)和reader.split(stop_byte):与.read_line()和.lines()类似。但以字节为单位,产生Vec<u8>值。stop_byte表示界定符。.fill_buf()和.consume(n):可以用于直接访问读取器内部的缓冲区。
16.1.3 - 读取文本行
Unix 的
grep命令分析:- 搜索多行文本,并与管道组合使用,以查找指定写入器。
use std::io; use std::io::prelude:: *; fn grep(target: &str) -> io::Result<()> { let stdin = io::stdin(); for line_result in stdin.lock().lines() { let line = line_result?; if line.contains(target) { println!("{}", line); } } Ok(()) }进一步扩展,增加搜索磁盘上文件的功能,改进为泛型函数:
fn grep<R>(target: &str, reader: R) -> io::Result<()> where R: BufRead { for line_result in reader.lines() { let ine = line_result?; if line.contains(target) { println!("{}", line); } } Ok(()) }通过
StdinLock或缓冲File调用。let stdin = io::stdin() grep(&target, stdin.lock())?; let f = File::open(file)?; grep(&target, BufReader::new(f))
File和BufReader是两个不同的库特性,因为有时候需要不带缓冲的文件,也有时候需要非文件的缓冲。File不会自动缓冲,而是要通过BufReader::new(reader)创建。- 如果要设置缓冲区的到校,则可以使用
BufReader::with_capacity(size, reader)。
Unix 的
grep命令完整程序:// grep: 搜索stdin或某些文件中匹配指定字符串的行 use std::error::Error; use std::io::{ self, BufReader}; use std::io::prelude:: *; use std::fs::File; use std::path::PathBuf; fn grep<R>(target: &str, reader: R) -> io::Result<()> where R: BufRead { for line_result in reader.lines() { let line = line_result?; if line.contains(target) { println!("{}", line); } } Ok(()) } fn grep_main() -> Result<(), Box<Error>> { // 取得命令行参数。第一个参数是要搜索的字符串,其余参数是文件名 let mut args = std::env::args().skip(1); let target = match args.next() { Some(s) => s, None = Err("usage: grep PATTERN FILE...")? }; let files: Vec<PathBuf> = args.map(PathBuf::from).collect(); if files.is_empty() { let stdin = io::stdin(); grep(&target, stdin.local())?; } else { for file in files { let f = File::open(file)?; grep(&target, BufReader::new(f))?; } } Ok(()) } fn main() { let result = grep_main(); if let Err(err) = result { let _ = writelen!(io::stderr(), "{}, err"); } }
16.1.4 - 收集行
- 读取器方法会返回
Result值的迭代器。 .collect()可以实现收集行的操作。
let lines = reader.lines().collect::<io::Result<Vec<String>>>()?;
// io::Result<Vec<String>>是一个集合类型,因此.collect()方法可以创建并填充该类型的值。
- 标准库为
Result实现了FromIterator特型:
impl<T, E, C> FromIterator<Result<T, E>> for Result<C, E> where C: FromIterator<T> {
...
}
- 如果可以将类型
T的项,收集到类型C(where C: FromIterator<T>)的集合中,那么就可以将类型Result<T, E>的项收集为类型Result<C, E>(FromIterator<Result<T, E>> for Result<C, E>)。
16.1.5 - 写入器
向标准输出流输出,可以使用
println!()和print!()宏。它们写入失败时只会诧异。向写入器输出,则可以使用
writeln!()和write!()宏。- 它们包含两个参数,第一个参数是写入器。
- 它们的返回值是
Result。在使用时,建议以?操作符结尾,用于处理错误。
Write特型的方法:writer.write(&buf):将切片buf中的某些字节写入底层流。返回io::Result<usize>,成功时包含写入的字节数,可能小于buf.len()。该方法安全限制较低,尽量不要直接使用。writer.write_all(&buf):将切片buf中的所有字节都写入,返回Result<()>。writer.flush():将所有缓冲数据都写到底层流,返回Result<()>。
与读取器类似,写入器也会在被清除时自动关闭。所有剩余缓冲数据会被写入底层写入器,在写入期间发生错误,错误会被忽略。为确保应用可以发现所有输出错误,应该在清除之前,手工使用
.flush()方法清理缓冲写入器。BufWriter::new(writer)可以给任何写入器添加缓冲。BufReader::new(reader)可一个任何读取器添加缓冲。let file = File::create("tmp.txt")?; let writer = BufWriter::new(file);要设置读取器的缓冲区大小,可以使用
BufWriter::with_capacity(size, writer)。
16.1.6 - 文件
打开文件的方式:
File::open(filename):打开已有文件供读取。返回一个io::Result<File>,如果文件不存在会返回错误。File::create(finename):创建新文件供写入。如果指定名字的文件已存在,则该文件会删节。使用
OpenOptions指定打开文件的行为use std::fs::OpenOptions; let log = OpenOptions::new() .append(true) // 如果文件存在,则在末尾追加内容 .open("server.lgo")?; let file = OpenOptions::new() .write(true) .create_new(true) // 如果文件存在则失败 .open("new_file.txt")?;.append()、.write()、.create_new()等都可以连缀调用,因为它们都返回self。这种方法连缀调用的模式,在 Rust 种被称为构建器(builder)。
File类型在文件系统模块std::fs中。File打开后,可以与其他读取器或写入器一样使用。可以根据需要添加缓冲。File也会在被清除时自动关闭。
16.1.7 - 搜寻
File 实现了 Seek 特型:支持在文件里跳转读取,而不是只能从头到尾一次性读取或写入。
pub trait Seek {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64>;
}
pub enum SeekFrom {
Start(u64),
End(i64),
Current(i64)
}
file.seek(SeekFrom::Start(0))表示跳到开始位置。file.seek(SeekFrom::Current(-8))表示后退 8 字节。- 无论是机械硬盘还是 SSD 固态硬盘,一次搜寻只能读取几兆数据。
16.1.8 - 其他读取器和写入器类型
io::stdin():返回标准输入流的读取器,类型为io::Stdin。它由所有线程共享,每次读取都设计获得和释放互斥量。
Stdin的.lock()方法,用于获得互斥量,并返回一个io::StdinLock缓冲读取器,在被清除前会持有互斥量,避免互斥量开销。而
io::stdin().lock()不能应用与互斥量,因为它会保存对Stdin值的引用,要求Stdin值必须保存在某个生命期较长的地方。但它可以用在收集行中。let stdin = io::stdin(); let lines = stdin.lock().lines();
io::stdout():返回标准输出流的写入器。拥有互斥量和.lock()方法。io::stderr():返回标准错误流的写入器。拥有互斥量和.lock()方法。Vec<u8>实现了Write。- 可以写入
Vec<u8>,用新数据扩展向量。 String没有实现Write。要使用Write构建字符串,首先需要写到一个Vec<u8>中,然后再使用String::from_utf8(vec)把限量转换为字符串。
- 可以写入
Cursor::new(buf):创建一个新Cursor,它是一个从buf中读取数据的缓冲读取器。- 用于创建读取
String的读取器。 - 参数
buf可以是实现AsRef<[u8]>的任何类型,因此也可以传入&[u8]、&str或Vec<u8>。 Cursor内部只有buf本身和一个整数。该整数用来表示在buf中的偏移量,初始值为 0。Cursor实现了Read、BufRead和Seek特型。- 如果
buf的类型是&mut [u8]或Vec<u8>,那么也支持Write特型。Cursor<&mut [u8]>和Cursor<Vec<u8>>也实现了std::io::prelude的所有 4 个特型。
- 用于创建读取
std::net::TcpStream:表示 TCP 网络连接。- 既是读取器,也是写入器,以支持 TCP 双向通信。
TcpStream::connect(("hostname", PORT))静态方法:尝试连接服务器,返回io::Result<TcpStream>。
std::process::Command:支持创建一个子进程,将数据导入其标准输入。use std::process::{ Command, Stdio}; let mut child = Command::new("grep") .arg("-e") .arg("a.*e.*i.*o.*u") .stdin(Stdio::piped()) .spawn()?; let mut to_child = child.stdin.take().unwrap(); for word in my_words { writelen!(to_child, "{}", word)?; } drop(to_child); // 关闭grep的stdin child.wait()?;child.stdin的类型是Option<std::process::ChildStdin>。Command也有.stdout()和.stderr()方法。
std::io模块:提供了一些函数,以返回简单的读取器和写入器。io::sink():无操作写入器。所有写入方法都返回 Ok,但数据会被丢弃。io::empty():无操作读取器。读取始终成功,但返回输入终止。io::repeat(byte):返回的读取器会反复给出指定字节。
16.1.9 - 二进制数据、压缩和序列化 —— 开源包的 std::io 扩展
byteorder包:提供了ReadBytesExt和WriteBytesExt特型,为所有二进制输入和输出的读取器和写入器提供方法。flate2包:为读、写gzip压缩的数据提供了额外的适配器方法。serde包:面向序列化和反序列化,可以实现 Rust 数据结构与字节之间的转换。serde::Serialize特型的serialize方法:为所有支持序列化的类型服务,如字符串、字符、元组、向量和HashMap。serde也支持派生特型,以服务于自定义类型:#[derive(Serialize, Deserialize)] struct Player { location: String, items: Vec<String>, health: u32 }
详见《Rust 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第十八章
原文地址
边栏推荐
- Collection: programming related websites and books
- 【Jailhouse 文章】Performance measurements for hypervisors on embedded ARM processors
- Reader writer model
- 卷积神经网络简介
- Daily question 1342 Number of operations to change the number to 0
- Implement an iterative stack
- Daily question 2013 Detect square
- leetcode-6110:网格图中递增路径的数目
- Codeforces round 712 (Div. 2) d. 3-coloring (construction)
- 每日一题-搜索二维矩阵ps二维数组的查找
猜你喜欢

读者写者模型

Some common problems in the assessment of network engineers: WLAN, BGP, switch

Sword finger offer 53 - I. find the number I in the sorted array

Sword finger offer 35 Replication of complex linked list

Solution to game 10 of the personal field

【云原生】微服务之Feign自定义配置的记录

On the characteristics of technology entrepreneurs from Dijkstra's Turing Award speech

Sword finger offer 06 Print linked list from beginning to end

Full Permutation Code (recursive writing)

【Jailhouse 文章】Jailhouse Hypervisor
随机推荐
Daily question 1688 Number of matches in the competition
One question per day 1765 The highest point in the map
CF1637E Best Pair
kubeadm系列-01-preflight究竟有多少check
Talking about JVM (frequent interview)
剑指 Offer 09. 用两个栈实现队列
Daily question 1342 Number of operations to change the number to 0
卷积神经网络——卷积层
剑指 Offer 05. 替换空格
leetcode-6109:知道秘密的人数
二十六、文件系统API(设备在应用间的共享;目录和文件API)
2020ccpc Qinhuangdao J - Kingdom's power
One question per day 1447 Simplest fraction
2022年贵州省职业院校技能大赛中职组网络安全赛项规程
中职网络安全技能竞赛——广西区赛中间件渗透测试教程文章
每日一题-搜索二维矩阵ps二维数组的查找
Bit mask of bit operation
2022 极术通讯-Arm 虚拟硬件加速物联网软件开发
In this indifferent world, light crying
CF1634E Fair Share