前言
某些只能使用ASCII字元的場景,往往需要傳輸非ASCII字元的資料,這時就需要一種編碼可以將資料轉換成ASCII字元,而base64編碼就是其中一種。
編碼原理很簡單,將原始資料以3位元組(24位元)為一組均分成4份,每部分6位元共64種組合,每種組合轉換成對應字元,最後拼接起來即可。若最後一組不夠3位元組則後面用0補齊,轉換後補齊多少位元組就用幾個“=”字元表示。
上面大致描述了base64編碼的場景及原理,具體細節不做探討,本文主要描述用rust實現時涉及的rust知識點。
標準輸出讀取
程式的資料是從標準輸入(stdin)中讀取的,使用std::io::stdin()
返回實現Read特性(trait)的Stdin結構體,呼叫Read特性read函式即可從標準輸出讀取資料,例子如下。
let buf: [u8; 300] = [0; 300];
let size = stdin().read(&buf).unwrap();
read使用一個u8型別陣列用作從標準輸入接收資料的快取,接收到的位元組數以包裹在Result中的usize型別返回,這裡簡單地使用unwrap()
解包獲取位元組數。
快取的大小是固定的但是輸入資料執行時確定的,因此使用迴圈不斷從標準輸入中讀取資料,直到讀取資料位元組數為0。
let mut buf: [u8; 300] = [0; 300];
loop {
let size = stdin().read(&mut buf).unwrap();
if size == 0 {
break;
}
// Output the buffer, and assume that buffer is utf-8 string.
print!("{}", String::from_utf8(buf.to_vec()).unwrap());
}
IO抽象模型
與Java的InputStream和OutputStream一樣,rust也有IO抽象模型,那就是Read和Write特性。
Read和Write特性將輸入輸出抽象為read、write等一系列函式,具體細節尤其實現決定。
使用時無需知道其實現是標準輸入輸出、檔案還是網路,例如可以實現一個輸入源自動匹配函式,當指定路徑的檔案不存在就讀取標準輸入,反之就從檔案中讀取內容。
fn main() {
let mut buf: [u8; 300] = [0; 300];
loop {
let size = input("./input").read(&mut buf).unwrap();
if size == 0 {
break;
}
print!("{}", String::from_utf8(buf.to_vec()).unwrap())
}
}
fn input(path: &'static str) -> Box<dyn Read> {
if !Path::new(path).exists() {
return Box::new(stdin());
}
Box::new(File::open(path).unwrap())
}
陣列
rust陣列是定長的,因此宣告時必須明確長度及型別以便分配記憶體,長度和型別可以自動推斷也可指定。
let arr: [i32; 4]; // 1. Specify type and length, the format is [Type; length].
let arr = [0, 4]; // 2. Infer type automatically.
let arr = [0, 0, 0, 0]; // 3. Infer type and length.
與其他多數語言一樣也是使用下標訪問元素,超出範圍會直接panic。
let mut arr = [0, 0, 0, 0];
print!("{}", arr[0]); // Output is 0.
arr[0] = 1;
print!("{}", arr[0]); // Output is 1.
arr[4] = 4; // Panic here.
字串
rust的字串有str和String兩種:
- str是原始型別,其實現是一種切片(Slice)型別且不可變,由於切片型別沒有所有權,因此只能是以引用方式&str出現;
- String有所有權且可變,其使用的是堆記憶體,因此開銷會比str大。
字串相加是常見場景,一種方式是直接用+運算子,注意其左值必須是String型別,因為String實現了運算子過載的Add
特性且由於其是可變的。
let a = String.from("a");
let b = "b";
let _ = a + b;
// Can not use variable "a" here, its ownership has been moved
注意這裡作為左值的a變數在運算後不能在被使用,因為其所有權已經被移動。
另一種相加方式是String的push_str
方法,其實+實現也是呼叫了此方法。
附錄
base64編碼實現完整程式碼如下:
use std::io::{stdin, Read};
fn main() {
let mut buf: [u8; 300] = [0; 300];
loop {
let size = stdin().read(&mut buf).unwrap();
if size == 0 {
break;
}
print!("{}", String::from_utf8(buf.to_vec()).unwrap());
print!("{}", encode(&buf, size));
}
}
fn encode(bytes: &[u8], size: usize) -> String {
let mut buf = String::new();
let i = 0;
for mut i in 0..(size / 3) {
i = i * 3;
let f = bytes[i];
let s = bytes[i + 1];
let t = bytes[i + 2];
buf.push_str(&cvt((f & 0xfc) >> 2));
buf.push_str(&cvt((f & 0x03) << 4 | ((s & 0xf0) >> 4)));
buf.push_str(&cvt((s & 0x0f) << 2 | ((t & 0xc0) >> 6)));
buf.push_str(&cvt(t & 0x3f));
}
let mut i = (i + 1) * 3;
i = if size < i { 0 } else { i };
let remain = size - i;
if remain == 1 {
let f = bytes[i];
buf.push_str(&cvt((f & 0xfc) >> 2));
buf.push_str(&cvt((f & 0x03) << 4 | 0));
buf.push_str("==");
} else if remain == 2 {
let f = bytes[i];
let s = bytes[i + 1];
buf.push_str(&cvt((f & 0xfc) >> 2));
buf.push_str(&cvt((f & 0x03) << 4 | ((s & 0xf0) >> 4)));
buf.push_str(&cvt((s & 0x0f) << 2 | 0));
buf.push_str("=");
}
buf
}
const BASE64_TABLE: [char; 64] = [
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '+', '/',
];
fn cvt(i: u8) -> String {
BASE64_TABLE.get(i as usize).unwrap().to_string()
}