rust学习十二、一个I/O程序练习
本文内容,基本同原书本,只是稍作了一些改动:
- 可以输入 --help展示用法
- 如果文件超过1MB,则会采用逐行查找的方法,避免内存不足
整个程序包含个文件:main.rs,lib.rs
一、代码
闲话少叙,直接上代码。
main.rs
/** * 完全模仿书本的例子 * * 运行示例 * cargo run 宋 E:\learning\gitee\rust-org\test-1\minigrep\src\古诗.txt -- ok * cargo run 宋 古诗.txt -- ok * cargo run 宋 -- 错误,因为参数太少了 */ use std::env; use minigrep::{config,search}; fn main() { //1.0 args() 返回的字符是unicode编码的 ,args()本身返回迭代器,collect()方法将迭代器中的元素收集到Vec<String>中 let args: Vec<String> = env::args().collect(); //2.0 解析参数,并构建配置 // unwrap_or_else()方法,如果Config::build(&args)返回Ok(config),则执行闭包中的内容,否则执行|err|{...}中的内容 let con=config::build(&args).unwrap_or_else(|err|{ eprintln!("程序异常:{}",err); std::process::exit(1); }); if con.show_help { config::show_help(); std::process::exit(0); } println!("配置信息:{:?}",&con); //3.0 根据配置查找文件,并打印查找到的结果 //如果文件比较小,那么就直接在内存处理;否则直接在search中逐行处理 if config::is_too_big(&con.file_name){
print!("正在查找大文件...");
let result= search::searh_big_file(&con.file_name,&con.target); match result{ Ok(_)=>println!("查找完成!"), Err(e)=>println!("读取文件失败: {:?}", e) } } else{
print!("正在查找小文件...");
let result=search::search_small_file(&con); for line in result.iter() { println!("{}",line); } } }
lib.rs
/** * 创建两个子模块 */ #[derive(Debug)] pub struct Config { pub file_name: String, pub target: String, pub show_help: bool, } /** * 模块执行配置 */ pub mod config { use super::Config; use std::env; use std::fs; use std::path::Path; /** * 第二个参数: 被查找的字符串 * 第三个参数: 文件路径,可以是全路径或者不包含路径的文件名 */ pub fn build(args: &[String]) -> Result<Config, &str> { if args.len() < 3 { if args.len() == 2 { //如果target_name=="--help"则打印帮助信息,否则执行文件 let target_name = args[1].clone(); if target_name == "--help" { return Ok(Config { target: target_name, file_name: "".to_string(), show_help: true, }); } } return Err("必须有两个参数"); } println!("rust返回的程序路径:{}", args[0]); let target_name = args[1].clone(); let file_path = args[2].clone(); if !file_exists(&file_path) { //用户输入的是文件名 let exec_path = get_current_exec_path(); println!("当前执行路径:{}", exec_path); let mut tmp_path = exec_path.clone() + &file_path; if !file_exists(&tmp_path) { tmp_path = exec_path.clone().replace("\\target\\debug\\", "\\src\\") + &file_path; if !file_exists(&tmp_path) { tmp_path = exec_path.clone().replace("\\target\\release\\", "\\src\\") + &file_path; if !file_exists(&tmp_path) { return Err("文件不存在"); } } //在src下 Ok(Config { target: target_name, file_name: tmp_path, show_help: false, }) } else { //在当前目录下=执行路径+文件名 Ok(Config { target: target_name, file_name: tmp_path, show_help: false, }) } } else { //在当前目录下 -- 即用户输入的是全路径 println!("{}", file_path); Ok(Config { target: target_name, file_name: file_path, show_help: false, }) } } /** * 判断文件是否存在,如果存在则返回true,否则返回false */ fn file_exists(file_name: &String) -> bool { Path::new(&file_name).exists() } /** * 获取当前执行路径,最后会包含分隔符号\ * 例如 d:\soft\rust-test\ * 注意:仅限用于windows平台 */ fn get_current_exec_path() -> String { let cur_dir = match env::current_exe() { Ok(exe_path) => exe_path.display().to_string(), Err(e) => panic!("获取当前路径失败:{}", e), }; // 获取cur_dir的目录,不需要包含文件名称 let path = Path::new(&cur_dir); let result = path.parent().unwrap().to_str().unwrap().to_string() + "\\"; result } pub fn is_too_big(file_name: &String) -> bool { //判断file_name是否超过1Mb,如果是,则返回true,否则返回false let file_size = fs::metadata(file_name).unwrap().len(); if file_size > 1 * 1024 * 1024 { return true; } else { false } } pub fn show_help() { println!("用法:minigrep target path"); println!(" minigrep --help"); println!("参数:"); println!(" target 需要查找的目标字符串"); println!(" path 包含target的文件,可以是全路径,也可以是文件名"); println!(" 如果是文件名,则会尝试在当前目录下,或者是在相对路径..\\..\\src"); println!(" --help 查看帮助"); println!("示例:"); println!(" minigrep 宋 E:\\learning\\gitee\\rust-org\\test-1\\minigrep\\src\\古诗.txt "); println!(" minigrep 宋 古诗.txt"); println!("版本 1.0 for windows, 作者->lzfto"); } } /** * 模块执行搜索 */ pub mod search { use super::Config; use std::fs; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; /** * 搜索小于1M的文件,并返回查找的结果 * @return 返回查找的结果 Vec<String> */ pub fn search_small_file(con: &Config) -> Vec<String> { let contents = fs::read_to_string(con.file_name.clone()).expect("文件不存在"); println!("文章内容:\n{}", contents); //把contents根据换行符切割成向量 let mut result: Vec<String> = vec![]; let lines = contents.lines(); for line in lines { if line.find(&con.target).is_some() { result.push(line.to_string()); } } return result; } /** * 搜索大于1M的文件 * @return 表示成功与否的信息 Result<()> */ pub fn searh_big_file(file_name: &String, target: &String) -> std::io::Result<()> { //打开文件file_name,逐行查找,找到则直接打印 let path = Path::new(file_name); let file = File::open(&path)?; let reader = BufReader::new(file); for line in reader.lines() { match line { Ok(line) => { if line.find(target).is_some() { println!("{}", line); } } Err(e) => println!("Error reading line: {}", e), } } Ok(()) } }
二、测试
为了便于测试,在src目录下放了两个文件:
古诗.txt 1kb
武林外史_utf8.txt 2120kb
显示帮助
查找小文件
查找大文件
三、小结
本例用到了截止12章节的大部分知识:
- 类型-主要是Vec,String,枚举,struct
- 结果处理Result,错误处理
- 程序组织,包括模块,use等等
- i/o-读取程序参数、读取文件,分行读取
必须得承认,rust的代码不好写,也不好看,幸好有优异的编译器和优秀的ide帮了大忙!
这些语法对于初学者而言,还是比较别扭,充斥着怪异的符号和奇特的语法。也许rust的发明者需要花费一些心思再优化下编译器,让工程师能够
以更自然优雅的方式写出不违背初心的代码。工程效率也是很重要的!
在编写的过程中,对引用借用有了更具体的体会!