鍍金池/ 教程/ Java/ 動(dòng)態(tài)數(shù)組Vec
標(biāo)準(zhǔn)輸入與輸出
消息傳遞
循環(huán)
注釋
Rust for Mac OS
幾種智能指針
Cell, RefCell
trait對(duì)象 (trait object)
rust web 開發(fā)
Unsafe、原始指針
Macro
迭代器
函數(shù)
Borrow, BorrowMut, ToOwned
快速上手
二叉樹
編輯器
測(cè)試與評(píng)測(cè)
Deref
安裝Rust
哈希表 HashMap
原生類型
17.錯(cuò)誤處理
VS Code 安裝配置
動(dòng)態(tài)數(shù)組Vec
模式匹配
操作符和格式化字符串
Rust for Linux
函數(shù)參數(shù)
Visual Studio
vim/GVim安裝配置
閉包作為參數(shù)和返回值
安全(Safety)
Cow
生命周期( Lifetime )
閉包的實(shí)現(xiàn)
所有權(quán)(Ownership)
Atom
將Rust編譯成庫
類型、運(yùn)算符和字符串
類型系統(tǒng)中的幾個(gè)常見 trait
特性
屬性和編譯器參數(shù)
Spacemacs
集合類型
Rust json處理
Heap & Stack
并行
標(biāo)準(zhǔn)庫示例
基本程序結(jié)構(gòu)
鏈表
trait 和 trait對(duì)象
前期準(zhǔn)備
代碼風(fēng)格
編譯器參數(shù)
基于語義化版本的項(xiàng)目版本聲明與管理
Rust 版本管理工具: rustup
引用&借用(References&Borrowing)
注釋與文檔
10.1 trait關(guān)鍵字
模式
調(diào)用ffi函數(shù)
unsafe
并發(fā),并行,多線程編程
AsRef 和 AsMut
Rust旅程
Rust for Windows
結(jié)構(gòu)體與枚舉
條件分支
附錄I-術(shù)語表
變量綁定與原生類型
Mutex 與 RwLock
泛型
裸指針
常用數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)
系統(tǒng)命令:調(diào)用grep
Into/From 及其在 String 和 &str 互轉(zhuǎn)上的應(yīng)用
共享內(nèi)存
Sublime
網(wǎng)絡(luò)模塊:W貓的回音
函數(shù)返回值
包和模塊
高階函數(shù)
函數(shù)與方法
match關(guān)鍵字
隊(duì)列
目錄操作:簡(jiǎn)單grep
語句和表達(dá)式
并發(fā)編程
閉包
測(cè)試
閉包的語法
同步
迭代器
String
Send 和 Sync
Rc 和 Arc
屬性
Emacs
優(yōu)先隊(duì)列
Prelude
cargo簡(jiǎn)介
控制流(control flow)
數(shù)組、動(dòng)態(tài)數(shù)組和字符串
FFI
模塊和包系統(tǒng)、Prelude
實(shí)戰(zhàn)篇
Rust 是一門系統(tǒng)級(jí)編程語言,被設(shè)計(jì)為保證內(nèi)存和線程安全,并防止段錯(cuò)誤。作為系統(tǒng)級(jí)編程語言,它的基本理念是 “零開銷抽象”。理
運(yùn)算符重載
Any和反射
rust數(shù)據(jù)庫操作
輸入輸出流
復(fù)合類型
性能測(cè)試

動(dòng)態(tài)數(shù)組Vec

在第七章我們粗略介紹了一下Vec的用法。實(shí)際上,作為Rust中一個(gè)非常重要的數(shù)據(jù)類型,熟練掌握Vec的用法能大大提升我們?cè)赗ust世界中的編碼能力。

特性及聲明方式

和我們之前接觸到的Array不同,Vec具有動(dòng)態(tài)的添加和刪除元素的能力,并且能夠以O(1)的效率進(jìn)行隨機(jī)訪問。同時(shí),對(duì)其尾部進(jìn)行push或者pop操作的效率也是平攤O(1)的。 同時(shí),有一個(gè)非常重要的特性(雖然我們編程的時(shí)候大部分都不會(huì)考量它)就是,Vec的所有內(nèi)容項(xiàng)都是生成在堆空間上的,也就是說,你可以輕易的將Vec move出一個(gè)棧而不用擔(dān)心內(nèi)存拷貝影響執(zhí)行效率——畢竟只是拷貝的棧上的指針。

另外的就是,Vec<T>中的泛型T必須是Sized的,也就是說必須在編譯的時(shí)候就知道存一個(gè)內(nèi)容項(xiàng)需要多少內(nèi)存。對(duì)于那些在編譯時(shí)候未知大小的項(xiàng)(函數(shù)類型等),我們可以用Box將其包裹,當(dāng)成一個(gè)指針。

new

我們可以用std::vec::Vec::new()的方式來聲明一個(gè)Vec。

let mut v1: Vec<i32> = Vec::new();

這里需要注意的是,new函數(shù)并沒有提供一個(gè)能顯式規(guī)定其泛型類型的參數(shù),也就是說,上面的代碼能根據(jù)v1的類型自動(dòng)推導(dǎo)出Vec的泛型;但是,你不能寫成如下的形式:

let mut v1 = Vec::new::<i32>();
// 與之對(duì)比的,collect函數(shù)就能指定:
// let mut v2 = (0i32..5).collect::<Vec<i32>>();

這是因?yàn)檫@兩個(gè)函數(shù)的聲明形式以及實(shí)現(xiàn)形式,在此,我們不做深究。

宏聲明

相比調(diào)用new函數(shù),Rust提供了一種更加直觀便捷的方式聲明一個(gè)動(dòng)態(tài)數(shù)組: vec! 宏。

let v: Vec<i32> = vec![];

// 以下語句相當(dāng)于:
// let mut temp = Vec::new();
// temp.push(1);
// temp.push(2);
// temp.push(3);
// let v = temp;
let v = vec![1, 2, 3];

let v = vec![0; 10]; //注意分號(hào),這句話聲明了一個(gè) 內(nèi)容為10個(gè)0的動(dòng)態(tài)數(shù)組

從迭代器生成

因?yàn)閂ec實(shí)現(xiàn)了FromIterator這個(gè)trait,因此,借助collect,我們能將任意一個(gè)迭代器轉(zhuǎn)換為Vec。

let v: Vec<_> = (1..5).collect();

訪問及修改

隨機(jī)訪問

就像數(shù)組一樣,因?yàn)閂ec借助IndexIndexMut提供了隨機(jī)訪問的能力,我們通過[index]來對(duì)其進(jìn)行訪問,當(dāng)然,既然存在隨機(jī)訪問就會(huì)出現(xiàn)越界的問題。而在Rust中,一旦越界的后果是極其嚴(yán)重的,可以導(dǎo)致Rust當(dāng)前線程panic。因此,除非你確定自己在干什么或者在for循環(huán)中,不然我們不推薦通過下標(biāo)訪問。

以下是例子:

let a = vec![1, 2, 3];
assert_eq!(a[1usize], 2);

那么,Rust中有沒有安全的下標(biāo)訪問機(jī)制呢?答案是當(dāng)然有:—— .get(n: usize).get_mut(n: usize)) 函數(shù)。 對(duì)于一個(gè)數(shù)組,這個(gè)函數(shù)返回一個(gè)Option<&T> (Option<&mut T>),當(dāng)Option==None的時(shí)候,即下標(biāo)越界,其他情況下,我們能安全的獲得一個(gè)Vec里面元素的引用。

let v =vec![1, 2, 3];
assert_eq!(v.get(1), Some(&2));
assert_eq!(v.get(3), None);

迭代器

對(duì)于一個(gè)可變數(shù)組,Rust提供了一種簡(jiǎn)單的遍歷形式—— for 循環(huán)。 我們可以獲得一個(gè)數(shù)組的引用、可變引用、所有權(quán)。

let v = vec![1, 2, 3];
for i in &v { .. } // 獲得引用
for i in &mut v { .. } // 獲得可變引用
for i in v { .. } // 獲得所有權(quán),注意此時(shí)Vec的屬主將會(huì)被轉(zhuǎn)移??!

但是,這么寫很容易出現(xiàn)多層for循環(huán)嵌套,因此,Vec提供了一個(gè)into_iter()方法,能顯式地將自己轉(zhuǎn)換成一個(gè)迭代器。然而迭代器怎么用呢?我們下一章將會(huì)詳細(xì)說明。

push的效率研究

前面說到,Vec有兩個(gè)O(1)的方法,分別是poppush,它們分別代表著將數(shù)據(jù)從尾部彈出或者裝入。理論上來說,因?yàn)?code>Vec是支持隨機(jī)訪問的,因此push效率應(yīng)該是一致的。但是實(shí)際上,因?yàn)閂ec的內(nèi)部存在著內(nèi)存拷貝和銷毀,因此,如果你想要將一個(gè)數(shù)組,從零個(gè)元素開始,一個(gè)一個(gè)的填充直到最后生成一個(gè)非常巨大的數(shù)組的話,預(yù)先為其分配內(nèi)存是一個(gè)非常好的辦法。

這其中,有個(gè)關(guān)鍵的方法是reserve。

如下代碼(注意:由于SystemTime API在1.8以后才穩(wěn)定, 請(qǐng)使用1.8.0 stable 以及以上版本的rustc編譯):

use std::time;

fn push_1m(v: &mut Vec<usize>, total: usize) {
    let e = time::SystemTime::now();
    for i in 1..total {
        v.push(i);
    }
    let ed = time::SystemTime::now();
    println!("time spend: {:?}", ed.duration_since(e).unwrap());
}

fn main() {
    let mut v: Vec<usize> = vec![];
    push_1m(&mut v, 5_000_000);
    let mut v: Vec<usize> = vec![];
    v.reserve(5_000_000);
    push_1m(&mut v, 5_000_000);
}

在筆者自己的筆記本上,編譯好了debug的版本,上面的代碼跑出了:

?  debug git:(master) ? time ./demo
time spend: Duration { secs: 0, nanos: 368875346 }
time spend: Duration { secs: 0, nanos: 259878787 }
./demo  0.62s user 0.01s system 99% cpu 0.632 total

好像并沒有太大差異?然而切換到release版本的時(shí)候:

?  release git:(master) ? time ./demo
time spend: Duration { secs: 0, nanos: 53389934 }
time spend: Duration { secs: 0, nanos: 24979520 }
./demo  0.06s user 0.02s system 97% cpu 0.082 total

注意消耗的時(shí)間的位數(shù)。可見,在去除掉debug版本的調(diào)試信息之后,是否預(yù)分配內(nèi)存消耗時(shí)間降低了一倍!

這樣的成績(jī),可見,預(yù)先分配內(nèi)存確實(shí)有助于提升效率。

有人可能會(huì)問了,你這樣糾結(jié)這點(diǎn)時(shí)間,最后不也是節(jié)省在納秒級(jí)別的么,有意義么?當(dāng)然有意義。

第一,納秒也是時(shí)間,這還是因?yàn)檫@個(gè)測(cè)試的Vec只是最簡(jiǎn)單的內(nèi)存結(jié)構(gòu)。一旦涉及到大對(duì)象的拷貝,所花費(fèi)的時(shí)間可就不一定這么少了。 第二,頻繁的申請(qǐng)和刪除堆空間,其內(nèi)存一旦達(dá)到瓶頸的時(shí)候你的程序?qū)?huì)異常危險(xiǎn)。

更多Vec的操作,請(qǐng)參照標(biāo)準(zhǔn)庫的api。

上一篇:Deref下一篇:快速上手