5

rust实战系列-base64编码 - ZachLim

 1 year ago
source link: https://www.cnblogs.com/linzhehuang/p/16472298.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

某些只能使用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两种:

  1. str是原始类型,其实现是一种切片(Slice)类型且不可变,由于切片类型没有所有权,因此只能是以引用方式&str出现;
  2. 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()
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK