当前位置:网站首页>【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 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第十八章
原文地址
边栏推荐
- leetcode-1200:最小绝对差
- Time complexity and space complexity
- “磐云杯”中职网络安全技能大赛A模块新题
- leetcode-3:无重复字符的最长子串
- 每日一题-搜索二维矩阵ps二维数组的查找
- LeetCode 1200.最小绝对差
- Typical use cases for knapsacks, queues, and stacks
- Dichotomy, discretization, etc
- Personal developed penetration testing tool Satania v1.2 update
- Sword finger offer 53 - I. find the number I in the sorted array
猜你喜欢
Introduction and experience of wazuh open source host security solution
LeetCode 0108.将有序数组转换为二叉搜索树 - 数组中值为根,中值左右分别为左右子树
Individual game 12
The connection and solution between the shortest Hamilton path and the traveling salesman problem
Using HashMap to realize simple cache
Sword finger offer 53 - I. find the number I in the sorted array
Pointnet++ learning
[cloud native] record of feign custom configuration of microservices
Palindrome (csp-s-2021-palin) solution
[article de jailhouse] jailhouse hypervisor
随机推荐
2022年贵州省职业院校技能大赛中职组网络安全赛项规程
leetcode-1200:最小绝对差
R language [import and export of dataset]
2020ccpc Qinhuangdao J - Kingdom's power
数仓项目的集群脚本
【Jailhouse 文章】Performance measurements for hypervisors on embedded ARM processors
【实战技能】非技术背景经理的技术管理
【Jailhouse 文章】Jailhouse Hypervisor
Brief introduction to tcp/ip protocol stack
Sword finger offer 04 Search in two-dimensional array
A misunderstanding about the console window
【实战技能】如何做好技术培训?
How many checks does kubedm series-01-preflight have
Palindrome (csp-s-2021-palin) solution
Personal developed penetration testing tool Satania v1.2 update
Codeforces Round #716 (Div. 2) D. Cut and Stick
Implement a fixed capacity stack
leetcode-9:回文数
Collection: programming related websites and books
Scope of inline symbol