(分享)Rust 数据读取器

· · 科技·工程

分享一下我没花多少时间写的一个小工具。

以下分享的代码遵循 MIT License(MIT 许可协议)。您被授予任何处理该代码所提供的工具的权利,包括复制、修改、再发布、商业使用甚至用于闭源软件中。您只需要备注原作者署名(中文:凌雪蓝冰。英文:CleanIce 或 Glacyend)即可。

背景

我有的时候会使用 Rust 来写几道题(原因在下面补充)。随后我发现,对于常见的数据输入格式来说,Rust 标准库提供的输入方法太难用了——按行读取、不过滤换行符、手动切割、手动转换等。所以,我就花了一点点小时间来写了一个 DataReader。在这里分享一下,用不用的上就不关我的事了。

:::info[我为什么有时候用 Rust 写题] 当满足下面任意一个条件时,我可能就会用 Rust 写题:

  1. C++ 用烦了,用腻了,用不惯了。用 Rust 来调整一下口味。
  2. 有出现 128 位整数但是不想给 __int128 手写输入输出的时候。Rust 内置 i128
  3. 在 Codeforces 上提交 C++ 正解却发现最后一个点超时的时候。用 Rust 重写一遍就过了。
  4. 题目与最近正在开发或学习的项目有关。拿 Rust 写熟悉一下该算法可能在此项目中的类似用法。
  5. 有身边的 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)
        }
    }
}

:::