(分享)Rust 数据读取器
分享一下我没花多少时间写的一个小工具。
以下分享的代码遵循 MIT License(MIT 许可协议)。您被授予任何处理该代码所提供的工具的权利,包括复制、修改、再发布、商业使用甚至用于闭源软件中。您只需要备注原作者署名(中文:凌雪蓝冰。英文:CleanIce 或 Glacyend)即可。
背景
我有的时候会使用 Rust 来写几道题(原因在下面补充)。随后我发现,对于常见的数据输入格式来说,Rust 标准库提供的输入方法太难用了——按行读取、不过滤换行符、手动切割、手动转换等。所以,我就花了一点点小时间来写了一个 DataReader。在这里分享一下,用不用的上就不关我的事了。
:::info[我为什么有时候用 Rust 写题] 当满足下面任意一个条件时,我可能就会用 Rust 写题:
- C++ 用烦了,用腻了,用不惯了。用 Rust 来调整一下口味。
- 有出现 128 位整数但是不想给
__int128手写输入输出的时候。Rust 内置i128。 - 在 Codeforces 上提交 C++ 正解却发现最后一个点超时的时候。用 Rust 重写一遍就过了。
- 题目与最近正在开发或学习的项目有关。拿 Rust 写熟悉一下该算法可能在此项目中的类似用法。
- 有身边的 Rust 学习者请求演示一段任意程序代码的时候。正好拿个题目写一下我就懒得去思考演示什么程序了。 :::
该数据读取器的代码在文章末尾。想要使用的或对实现感兴趣的可以去看看。当然,如果发现了什么 bug 或有更好的实现方式,欢迎指出!
使用文档
基本结构
结构体定义
数据读取器使用 DataReader 结构体封装。由于是适配题目中常见的数据输入格式的读取器,所以没有实现标准库中 std::io::Reader 等特征。
#[must_use]
pub(crate) struct DataReader {
/* 非公开字段 */
}
模块
该结构体在 data_reader 模块下。加模块是为了更好的封装,隐藏不该暴露的字段以免用户意外使用或修改。
模块内容大致如下:
mod data_reader {
use std::str::FromStr;
#[must_use]
pub(crate) struct DataReader {
/* 非公开字段 */
}
impl DataReader {
/* 详细见下 */
}
}
创建数据读取器
函数原型
pub(crate) fn new() -> Self
参数
无。
返回值
一个新的数据读取器,使用标准输入流。
警告与提示
不建议创建多个数据读取器。
示例
let mut reader = data_reader::DataReader::new();
// 使用 mut 保证可变。DataReader 的某些方法需要 &mut self。
检查是否读取到 EOF
函数原型
#[allow(dead_code)]
#[must_use]
pub(crate) fn eof(&mut self) -> bool
参数
无。
返回值
一个 bool 类型的值。若为 true,则代表读取到了 EOF。
警告与提示
eof() 方法在返回过一次 true(即遇到 EOF)之后,不可能再返回 false。
此方法需要 &mut self,因为其内部实现可能会刷新读入缓冲区。
示例
let mut reader = data_reader::DataReader::new();
if reader.eof() {
println!("Nothing to read.");
}
读取行
函数原型
#[allow(dead_code)]
pub(crate) fn read_line(&mut self) -> Option<String>
参数
无。
返回值
Option<String> 类型。
当读取到 EOF 时,返回 None。
否则,读取当前行并返回 Some(line)。
警告与提示
该方法读取的是当前行的剩余部分。也就是说,如果当前行的前面已经被其他方法读取过了,则此方法将从当前位置继续向后读取直到把当前行读完。
过滤换行符。
示例
let mut reader = data_reader::DataReader::new();
let line = reader.read_line().unwrap();
读取单个 Token
函数原型
#[allow(dead_code)]
pub(crate) fn read<T>(&mut self) -> Option<T>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Debug,
参数
无。
返回值
一个 Option<T> 类型的值。
与 C++ 的 std::cin 使用 << 运算符的行为相似,读取一个按空白符分割的 Token 并转换为 T 类型。
若已读取到 EOF,则返回 None。
否则,返回 Some(value)。
警告与提示
忽略换行。上一行 Token 读完之后继续读取下一行的 Token。
泛型 T 必须满足 where 子句中的约束,必须能够由字符串转换而来。例如,T 可以是基本类型、字符串或自定义的实现了对应特征的类型。
示例
let mut reader = data_reader::DataReader::new();
// 方法 1:明确指定泛型 T
let length /* : usize */ = reader.read::<usize>().unwrap();
// 方法 2:明确标注接收者类型
let number: i32 = reader.read().unwrap();
// 方法 3:编译器自动推断类型
let factor /* : f64 */ = reader.read().unwrap();
let prod = factor * 3.14_f64;
从行读取数组
函数原型
#[allow(dead_code)]
#[must_use]
pub(crate) fn read_array<T>(&mut self) -> Option<Vec<T>>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Debug,
参数
无。
返回值
Option<Vec<T>> 类型的值。
从一行中读取用空格分隔的 Token,将每个 Token 转换为 T 类型,直到读取到行尾。
如果已经遇到 EOF,则返回 None。
否则,返回 Some(arr)。
警告与提示
与 read_line() 方法类似,从当前行已经被读取完的最后一个位置开始,读取到行尾。
返回的数组是 0-based 的数组,即第一个有意义数据的下标是 0。
类型 T 必须满足 where 子句的约束,要能够从字符串转换而来。
示例
let mut reader = data_reader::DataReader::new();
let numbers: Vec<i32> = reader.read_array().unwrap();
从行读取数组(1-Based)
函数原型
#[allow(dead_code)]
#[must_use]
pub(crate) fn read_array_one_based<T>(&mut self) -> Option<Vec<T>>
where
T: FromStr + Default,
<T as FromStr>::Err: std::fmt::Debug,
参数
无。
返回值
Option<Vec<T>> 类型的值。
从一行中读取用空格分隔的 Token,将每个 Token 转换为 T 类型,直到读取到行尾。
如果已经遇到 EOF,则返回 None。
否则,返回 Some(arr)。
警告与提示
与 read_array() 方法类似,从当前行已经被读取完的最后一个位置开始,读取到行尾。
返回的数组是 1-based 的数组,即第一个有意义数据的下标是 1。
类型 T 必须满足 where 子句的约束,要能够从字符串转换而来。T 必须实现 Default 特征,即有默认值(因为数组的下标 0 需要用默认值填充)。对于整数类型,默认值是 0;对于浮点类型,默认值是 0.0;对于布尔类型,默认值是 false;对于字符串类型,默认值是空串。
示例
let mut reader = data_reader::DataReader::new();
let numbers: Vec<i32> = reader.read_array_one_based().unwrap();
代码实现
想要使用的或对实现感兴趣的可以看看。当然,如果发现了什么 bug 或有更好的实现方式,欢迎指出!
代码实现如下。将该代码粘贴到你的源程序末尾,或者放在单独的模块文件中,即可使用。
:::success[源代码]
// Copyright (C) 2026 凌雪蓝冰 / CleanIce / Glacyend
// Licensed under the MIT License.
// SPDX-License-Identifier: MIT
mod data_reader {
use std::str::FromStr;
#[must_use]
pub(crate) struct DataReader {
stdin: std::io::Stdin,
line: String,
line_over: bool,
read_pos: usize,
eof: bool,
}
impl DataReader {
pub(crate) fn new() -> Self {
Self {
stdin: std::io::stdin(),
line: String::new(),
line_over: true,
read_pos: 0,
eof: false,
}
}
fn auto_flush(&mut self) -> bool {
if self.eof {
return true;
}
if self.line_over || self.read_pos >= self.line.len() {
self.line.clear();
let cnt = self.stdin.read_line(&mut self.line).unwrap();
if cnt == 0 {
self.eof = true;
return true;
}
if self.line.ends_with('\n') {
self.line.pop();
if self.line.ends_with('\r') {
self.line.pop();
}
}
self.line_over = false;
self.read_pos = 0;
}
false
}
#[allow(dead_code)]
#[must_use]
pub(crate) fn eof(&mut self) -> bool {
self.auto_flush()
}
#[allow(dead_code)]
pub(crate) fn read_line(&mut self) -> Option<String> {
if self.auto_flush() {
return None;
}
let mut result = std::mem::take(&mut self.line);
self.line_over = true;
Some(result.split_off(self.read_pos))
}
#[allow(dead_code)]
pub(crate) fn read<T>(&mut self) -> Option<T>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Debug,
{
if self.auto_flush() {
return None;
}
let bytes = self.line.as_bytes();
let bytes_len = bytes.len();
while self.read_pos < bytes_len && bytes[self.read_pos].is_ascii_whitespace() {
self.read_pos += 1;
}
let start = self.read_pos;
while self.read_pos < bytes_len && !bytes[self.read_pos].is_ascii_whitespace() {
self.read_pos += 1;
}
let token = &self.line[start..self.read_pos];
Some(token.parse().unwrap())
}
#[allow(dead_code)]
#[must_use]
pub(crate) fn read_array<T>(&mut self) -> Option<Vec<T>>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Debug,
{
if self.auto_flush() {
return None;
}
let remaining = self.line.split_off(self.read_pos);
let iter = remaining.split_ascii_whitespace();
self.line_over = true;
Some(iter.map(|x| x.parse::<T>().unwrap()).collect())
}
#[allow(dead_code)]
#[must_use]
pub(crate) fn read_array_one_based<T>(&mut self) -> Option<Vec<T>>
where
T: FromStr + Default,
<T as FromStr>::Err: std::fmt::Debug,
{
if self.auto_flush() {
return None;
}
let remaining = self.line.split_off(self.read_pos);
let iter = remaining.split_ascii_whitespace();
let mut result = vec![T::default()];
result.extend(iter.map(|x| x.parse::<T>().unwrap()));
self.line_over = true;
Some(result)
}
}
}
:::