• rust实战系列-base64编码


    前言

    某些只能使用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()
    }
    
    折叠
  • 相关阅读:
    外星人Alienware m16R1 原厂Windows11系统 oem系统
    Scrapy+Selenium自动化获取个人CSDN文章质量分
    数学重点复习
    计算机毕业设计(附源码)python校园疫情防控管理软件
    JVM 一些常见问题Q&A
    【牛客刷题】——Python入门 05 运算符(上)
    【UE5】创建蓝图
    leetcode竞赛:20220821周赛
    Golang之封装Mysql Slave小例子
    面试官:你是怎样进行react组件代码复用的
  • 原文地址:https://www.cnblogs.com/linzhehuang/p/16472298.html