Java程式語言的java.util.Scanner是一個非常方便的類別,可以用來讀取任意的字串或是串流中的文字資料,它也是剛開始學習Java的人通常會第一個使用到的java.util套件中的類別。而Rust的標準函式庫中並沒有提供像是Java的Scanner這樣方便好用的模組,大多就是用已經有實作好Read特性的結構實體所提供的readread_to_endread_to_string等低階方法,而要稍微高階一點的話就是用BufRead特性所提供的read_line方法。Java的Scanner之所以如此方便的原因主要就是它提供了略過來源資料中空白字元的功能,並且可以直接以呼叫next或是nextXXX方法的方式來得到我們預期會取得的資料,我們也不必自行再另外去做字串資料的解析。



為了在Rust程式語言中也能享有Java程式語言的Scanner的益處,筆者決定用Rust程式語言實作出Scanner的主要功能。不考慮把Scanner大部份功能實作出來的原因是,Rust程式語言是比較注重效能和記憶體使用量的相對低階的語言,而Java程式語言的Scanner功能實在太多了,有一大半筆者從來沒用到過,像是不同進制的數值轉換、或是讀取自訂的正規表示式樣本的資料等,就連在數值轉換上,Java的Scanner也還支援「123,456」這種格式(有用逗號分隔位數)的字串,這類特殊格式筆者覺得還是要由程式設計師自行在使用Scanner讀取字串後再另外處理會比較好一點,不然不需使用到的話會消耗額外的運算資源。

Scanner

「Scanner」是筆者用Rust程式語言撰寫的類似Java的Scanner的同名套件(不過因為scanner這個名稱已經被功能完全不同的crate佔用,只好將crate命名為scanner-rust),可以用來讀取字串或是串流中的ASCII或是UTF-8資料。實作了nextnext_linenext_u8next_u16next_u32next_u64next_u128next_usizenext_i8next_i16next_i32next_i64next_i128next_isizenext_f32next_f64,遵守Java的Scanner略過空白字元的策略,且另外還提供Java的Scanner所沒有的next_charskip_whitespacesdrop_nextdrop_next_line方法。

Crates.io

Cargo.toml

scanner-rust = "*"

使用方法

使用方法很簡單,參考以下範例即可:

use std::io::{self, Write};

use scanner_rust::ScannerAscii;

print!("Please input two integers, a and b: ");
io::stdout().flush().unwrap();

let mut sc = ScannerAscii::new(io::stdin());

let a = {
    loop {
        match sc.next_isize() {
            Ok(i) => break i.unwrap_or(0),
            Err(_) => {
                print!("Re-input a and b: ");
                io::stdout().flush().unwrap();
            }
        }
    }
};

let b = {
    loop {
        match sc.next_isize() {
            Ok(i) => break i.unwrap_or(0),
            Err(_) => {
                print!("Re-input b: ");
                io::stdout().flush().unwrap();
            }
        }
    }
};

println!("{} + {} = {}", a, b, a + b);

如果確定輸入的串流資料是ASCII編碼的話,可以換成ScannerAscii結構體,效能會更好。

預設的緩衝空間大小為256個位元組。若您想要改變緩衝空間大小,可以使用new2或是scan_path2關聯函數並且明確地定義要使用的長度(透過泛型),來產生出上面介紹的結構體的實體。

例如,要將緩衝空間大小改為64個位元組的話,程式可以這樣寫:

use scanner_rust::generic_array::typenum::U64;
use scanner_rust::Scanner;

let mut sc: Scanner<_, U64> = Scanner::scan_path2("Cargo.toml").unwrap();

此外,如果要解析的資料已存在於記憶體中,可以使用ScannerStrScannerU8Slice或是ScannerU8SliceAscii結構體,避免在記憶體中複製資料。

use std::io::{self, Write};

use scanner_rust::ScannerU8Slice;

let mut sc = ScannerU8Slice::new(" 123   456.7    \t\r\n\n c中文字\n\tHello world!".as_bytes());

assert_eq!(Some(123), sc.next_u8().unwrap());
assert_eq!(Some(456.7), sc.next_f64().unwrap());
assert_eq!(Some(' '), sc.next_char().unwrap());
assert_eq!(Some(' '), sc.next_char().unwrap());
assert_eq!(true, sc.skip_whitespaces().unwrap());
assert_eq!(Some('c'), sc.next_char().unwrap());
assert_eq!(Some("中文字".as_bytes()), sc.next_line().unwrap());
assert_eq!(Some("\tHello world!".as_bytes()), sc.next_line().unwrap());
assert_eq!(None, sc.next_line().unwrap());