美文网首页
javaer学rust(四)

javaer学rust(四)

作者: yiang飞扬 | 来源:发表于2022-10-11 15:22 被阅读0次

    之前实现了文件上传下载的功能,这次我们利用go-fastdfs实现一个分片上传的功能,按照惯例,先贴代码

    use std::fs::File;
    use std::io::Read;
    use std::thread;
    use std::thread::spawn;
    use reqwest::blocking::Response;
    
    fn main() {
        let file_path="D:/uploadBigTest.txt";
        let mut file=std::fs::File::open(file_path).unwrap();
        let file_size=file.metadata().unwrap().len();
    
        let mut buf=Vec::new();
        file.read_to_end(&mut buf);
    
        let part_size=10*1024*1024;
        let part_num=if file_size%part_size==0{
            file_size/part_size
        }else{
            file_size/part_size+1
        };
        let client=reqwest::blocking::Client::new();
        let response=client.post("http://localhost:8234/group2/big/upload/")
            .header("Upload-Length",file_size)
            .header("Tus-Resumable","1.0.0")
            .send()
            .unwrap();
        let file_location=response.headers().get("Location").unwrap().to_str().unwrap().to_string();
        println!("upload url:{}",file_location);
    
        for i in 0..part_num {
            let start:usize= (i * part_size) as usize;
            let mut end=((i+1)*part_size) as usize;
            if end>(file_size as usize){
                end=file_size as usize;
            }
            let upload_url=file_location.clone();
            let body=(&buf[start..end]).to_vec();
            thread::spawn(move ||{
                let client=reqwest::blocking::Client::new();
                let response=client.patch(upload_url.as_str())
                    .header("Content-Type","application/offset+octet-stream")
                    .header("Tus-Resumable","1.0.0")
                    .header("Upload-Offset",start)
                    .body(body).send().unwrap();
                println!("part {} upload success",i);
            }).join();
        }
    }
    

    这段代码的大体流程如下:
    1、先读取文件数据到数组,rust里面数组类型是Vec
    2、根据文件大小和分片大小计算分片数量
    3、按照go-fastdfs的接口,先调用big/upload接口获取分片上传的地址
    4、开启多个线程进行分片上传

    虽然代码能够运行,但还是有要继续优化的地方,我们先看一下用到的一些知识点和遇到问题的地方,后面有针对性的进行优化

    1、为了使用多线程,这里我们使用reqwest的同步模式(使用异步模式在线程中有问题,而且异步模式其实也没必要在使用多线程了,异步和多线程就是解决阻塞,提高并发的的两种方式),可以看到main方法上已经没有async修饰符了,另外因为用了同步模式,别忘了把blocking特性加到Cargo.toml的依赖特性里面,创建client的时候需要使用blocking模块

    let client=reqwest::blocking::Client::new();
    

    2、mut修饰符 ,rust使用let定义的变量默认是不能修改的,类似java的final,如果定义的变量需要修改,则需要通过let mut进行定义,如果某个方法内部要对变量进行修改,则传参的时候也要带着mut修改符,如:

    let mut buf=Vec::new();
    file.read_to_end(&mut buf);
    

    3、在java中,除了基本类型,对象都传递的是引用,但在rust中,传递引用需要用&符号明确指明,否则都是传递的指针。

    4、三目运算符,rust中是没有java中的 <expression> ? <result1> : <result2>这种三目运算符的,但我们可以使用if{}else{}来替代,见代码:

    let part_num=if file_size%part_size==0{
            file_size/part_size
        }else{
            file_size/part_size+1
        };
    

    这里需要注意的是,代码不要写成下面这样

    let part_num=if file_size%part_size==0{
            file_size/part_size;
    }else{
            file_size/part_size+1;
    };
    

    两者的区别主要是在分号上,不加分号在rust中表示表达式,加上分号就变成了语句,表达式是可以作为值返回的,而语句返回的是()

    5、rust开启线程的方式会比java方便一些, 通过thread::spawn()既可以开启一个线程,spawn方法里面是rust闭包,这里我们可以简单理解成java的匿名内部类类(其实还是有比较大的差异的),写法如下:

    |param:type|->type{} 
    

    代码中是不需要传参和返回值简化了的写法,相当于 ||{},即下面代码:

    ||{
         let client=reqwest::blocking::Client::new();
         let response=client.patch(upload_url.as_str())
                .header("Content-Type","application/offset+octet-stream")
               .header("Tus-Resumable","1.0.0")
               .header("Upload-Offset",start)
               .body(body).send().unwrap();
                println!("part {} upload success",i);
     }       
    

    当然也可以把{}部分抽成具体的方法
    另外我们在这块代码部分还看到一个move关键字,其实是将线程中用到的变量的所有权转移到闭包中来的意思,具体看下面内容

    6、生命周期和所有权,生命周期我们都理解,任何变量都有一个从创建到销毁的过程。所有权是rust特有的概念,规定了变量使用的范围,用来进行内存管理,是rust不需要进行GC的基础。由所有权会引申出借用和引用的概念,当我们定义一个变量符赋值后,变量值就已经属于了这个变量,别的变量要用的话则需要借用,一旦别的变量借用了,原有的变量就不能再使用了,因为值的所有权已经变了,这其实和物理世界很像,就像你有一个东西借给了你的好朋友,那在朋友换回来之前,你就不能再使用这个物品了。举个例子:

      let s1=String::from("test ownership");
      let s2=s1;
      println!("{}",s1);
    

    如果执行上面的代码是编译不过去的,编译器会提示下面信息

    error[E0382]: borrow of moved value: `s1`
      --> src\main.rs:17:19
       |
    15 |     let s1=String::from("test ownership");
       |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
    16 |     let s2=s1;
       |            -- value moved here
    17 |     println!("{}",s1);
       |                   ^^ value borrowed here after move
    

    错误信息提示的很明白,就是s1的所有权已经发生了转移,再去使用s1的值是不能用的。那如果想继续使用s1的值怎么办呢,这就需要在把值付给s1,可以理解成你朋友把东西还给你了(不过很可能你的朋友借了就不换了:) )。

    let mut s1=String::from("test ownership");
    let s2 = s1;
    s1=s2;
    println!("{}",s1);
    

    另外就是一个变量的值同时只能被借用一次,这和我们的现实世界也很像,你一个东西是不能同时借给两个朋友使用的,如下代码是有问题的:

    let mut s1=String::from("test ownership");
    let s2 = s1;
    let s3=s1;
    println!("{}",s3);
    

    但是在处理业务过程中,一个变量被同时处理的场景是不可避免的,在现实世界其实也一样,假如你有一本绝版的书,你两个朋友都想在同段时间内借阅,那怎么办呢?这就要用到引用的概念了,还是拿借书比喻,如果两个朋友都想同时借阅,我们是不是可以把他们邀请到家里一起阅读,这样是不是就解决问题了。我们把上面的错误代码改下,在变量上加个&符号就没问题了

        let s1=String::from("test ownership");
        let s2 = &s1;
        let s3=&s1;
        println!("{},{}",s1,s3);
    

    继续用来对照现实,如果两个朋友在读书的过程中,你允许某个人可以翻到自己喜欢的章节,那是不是就容易造成冲突。在rust中,如果存在可变引用,即引用方可以修改变量值,那么和借用一样,只能同时存在一个引用。例如,下面的代码是不行的

     let mut s1=String::from("test ownership");
     let s2 = &mut s1;
     s2.push_str("12345");
     let s3=&s1;
     println!("{},{},{}",s1,s2,s3);
    

    说了这么多,其实可以用一句话概括,即‘共享不可变,可变不共享‘

    除了借用和引用,其实还有一种情况就是转让,也就是上面提到的move关键字,就是我把东西送给你了,以后它的生死存活就和我没关系了。我们上面讲所有权其实都是默认在变量的生命周期内,那如果借用或引用的范围已经超出了变量本身的生命周期了呢,就像你有一个宠物交给朋友养,但你移民火星再也不回来了,最好的选择就是把抚养权交给朋友。

    再次回到代码中来,为什么线程部分需要加上move关键字,因为在闭包中我们用到了两个变量upload_url和body,因为这两个变量的生命周期是在for循环体内,但线程里面需要的变量生命周期比for循环体时间长,所以需要通过move关键字把所有权转移到线程的闭包里面去。另外我们可发现upload_url其实是对file_location的值的拷贝,为什么在闭包中不直接用file_location呢,我们不是可以通过move把file_location的所有权移到闭包中吗?因为我们线程在for循环里面,会存在多个线程,而move只能把所有权交给一个线程,因此每个线程我们都需要做个拷贝。虽然解决了问题,但这肯定不是好的办法,后面继续优化下,看看有没有好的办法。

    相关文章

      网友评论

          本文标题:javaer学rust(四)

          本文链接:https://www.haomeiwen.com/subject/ftwgartx.html