哈希映射(HashMap)
哈希映射(HashMap)存储了一个键类型 K
对应一个值类型 V
的映射。它通过一个哈希函数(hashing function)来实现映射,决定如何将键和值放入内存中。
创建 HashMap
基本创建
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
println!("{:?}", scores);
}
使用 collect 创建
use std::collections::HashMap;
fn main() {
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let mut scores: HashMap<_, _> =
teams.into_iter().zip(initial_scores.into_iter()).collect();
println!("{:?}", scores);
}
访问 HashMap 中的值
使用 get 方法
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name);
match score {
Some(s) => println!("Blue 队的分数: {}", s),
None => println!("Blue 队不存在"),
}
}
使用 get 和 unwrap_or
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
// 获取值,如果不存在则使用默认值
let blue_score = scores.get("Blue").unwrap_or(&0);
let red_score = scores.get("Red").unwrap_or(&0);
println!("Blue: {}, Red: {}", blue_score, red_score);
}
遍历 HashMap
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
for (key, value) in &scores {
println!("{}: {}", key, value);
}
}
更新 HashMap
覆盖一个值
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores); // {"Blue": 25}
}
只在键没有对应值时插入
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores); // {"Blue": 10, "Yellow": 50}
}
根据旧值更新一个值
use std::collections::HashMap;
fn main() {
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map); // {"hello": 1, "world": 2, "wonderful": 1}
}
HashMap 的常用方法
检查键是否存在
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert("Blue", 10);
scores.insert("Yellow", 50);
if scores.contains_key("Blue") {
println!("Blue 队存在");
}
if !scores.contains_key("Red") {
println!("Red 队不存在");
}
}
删除键值对
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert("Blue", 10);
scores.insert("Yellow", 50);
// 删除并返回值
if let Some(score) = scores.remove("Blue") {
println!("删除了 Blue 队,分数为: {}", score);
}
println!("剩余队伍: {:?}", scores);
}
获取长度和清空
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert("Blue", 10);
scores.insert("Yellow", 50);
println!("队伍数量: {}", scores.len());
println!("是否为空: {}", scores.is_empty());
scores.clear();
println!("清空后是否为空: {}", scores.is_empty());
}
实际应用示例
单词计数器
use std::collections::HashMap;
fn count_words(text: &str) -> HashMap<String, usize> {
let mut word_count = HashMap::new();
for word in text.split_whitespace() {
let word = word.to_lowercase().trim_matches(|c: char| !c.is_alphabetic()).to_string();
if !word.is_empty() {
*word_count.entry(word).or_insert(0) += 1;
}
}
word_count
}
fn main() {
let text = "Hello world! This is a test. Hello again, world.";
let word_count = count_words(text);
println!("单词统计:");
for (word, count) in &word_count {
println!("{}: {}", word, count);
}
// 找出最常见的单词
if let Some((most_common_word, count)) = word_count.iter().max_by_key(|(_, &count)| count) {
println!("最常见的单词: '{}' 出现了 {} 次", most_common_word, count);
}
}
学生成绩管理系统
use std::collections::HashMap;
#[derive(Debug)]
struct Student {
name: String,
age: u32,
grades: Vec<f64>,
}
impl Student {
fn new(name: String, age: u32) -> Student {
Student {
name,
age,
grades: Vec::new(),
}
}
fn add_grade(&mut self, grade: f64) {
self.grades.push(grade);
}
fn average_grade(&self) -> Option<f64> {
if self.grades.is_empty() {
None
} else {
let sum: f64 = self.grades.iter().sum();
Some(sum / self.grades.len() as f64)
}
}
}
struct GradeBook {
students: HashMap<String, Student>,
}
impl GradeBook {
fn new() -> GradeBook {
GradeBook {
students: HashMap::new(),
}
}
fn add_student(&mut self, student: Student) {
self.students.insert(student.name.clone(), student);
}
fn add_grade(&mut self, student_name: &str, grade: f64) -> Result<(), String> {
match self.students.get_mut(student_name) {
Some(student) => {
student.add_grade(grade);
Ok(())
}
None => Err(format!("学生 '{}' 不存在", student_name)),
}
}
fn get_student_average(&self, student_name: &str) -> Option<f64> {
self.students.get(student_name)?.average_grade()
}
fn class_average(&self) -> Option<f64> {
let averages: Vec<f64> = self.students
.values()
.filter_map(|student| student.average_grade())
.collect();
if averages.is_empty() {
None
} else {
let sum: f64 = averages.iter().sum();
Some(sum / averages.len() as f64)
}
}
fn list_students(&self) {
println!("学生列表:");
for (name, student) in &self.students {
let avg = student.average_grade()
.map(|a| format!("{:.2}", a))
.unwrap_or_else(|| "无成绩".to_string());
println!(" {}: 年龄 {}, 平均分 {}", name, student.age, avg);
}
}
}
fn main() {
let mut grade_book = GradeBook::new();
// 添加学生
grade_book.add_student(Student::new("张三".to_string(), 20));
grade_book.add_student(Student::new("李四".to_string(), 19));
grade_book.add_student(Student::new("王五".to_string(), 21));
// 添加成绩
grade_book.add_grade("张三", 85.5).unwrap();
grade_book.add_grade("张三", 92.0).unwrap();
grade_book.add_grade("李四", 78.5).unwrap();
grade_book.add_grade("李四", 88.0).unwrap();
grade_book.add_grade("王五", 95.0).unwrap();
// 显示信息
grade_book.list_students();
if let Some(avg) = grade_book.get_student_average("张三") {
println!("张三的平均分: {:.2}", avg);
}
if let Some(class_avg) = grade_book.class_average() {
println!("班级平均分: {:.2}", class_avg);
}
}
缓存系统
use std::collections::HashMap;
use std::time::{Duration, Instant};
struct CacheEntry<T> {
value: T,
created_at: Instant,
ttl: Duration,
}
impl<T> CacheEntry<T> {
fn new(value: T, ttl: Duration) -> Self {
CacheEntry {
value,
created_at: Instant::now(),
ttl,
}
}
fn is_expired(&self) -> bool {
self.created_at.elapsed() > self.ttl
}
}
struct Cache<K, V> {
data: HashMap<K, CacheEntry<V>>,
}
impl<K, V> Cache<K, V>
where
K: std::hash::Hash + Eq + Clone,
{
fn new() -> Self {
Cache {
data: HashMap::new(),
}
}
fn insert(&mut self, key: K, value: V, ttl: Duration) {
self.data.insert(key, CacheEntry::new(value, ttl));
}
fn get(&mut self, key: &K) -> Option<&V> {
// 清理过期条目
if let Some(entry) = self.data.get(key) {
if entry.is_expired() {
self.data.remove(key);
return None;
}
}
self.data.get(key).map(|entry| &entry.value)
}
fn cleanup_expired(&mut self) {
let expired_keys: Vec<K> = self.data
.iter()
.filter(|(_, entry)| entry.is_expired())
.map(|(key, _)| key.clone())
.collect();
for key in expired_keys {
self.data.remove(&key);
}
}
fn len(&self) -> usize {
self.data.len()
}
}
fn main() {
let mut cache = Cache::new();
// 插入一些数据
cache.insert("user:1", "张三", Duration::from_secs(5));
cache.insert("user:2", "李四", Duration::from_secs(10));
println!("缓存大小: {}", cache.len());
// 获取数据
if let Some(user) = cache.get(&"user:1") {
println!("找到用户: {}", user);
}
// 模拟时间流逝(在实际应用中,这会是真实的时间)
std::thread::sleep(Duration::from_secs(1));
// 清理过期条目
cache.cleanup_expired();
println!("清理后缓存大小: {}", cache.len());
}
自定义哈希函数
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
impl PartialEq for Person {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for Person {}
impl Hash for Person {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
// 注意:我们只根据名字进行哈希,忽略年龄
}
}
fn main() {
let mut people = HashMap::new();
let person1 = Person {
name: "张三".to_string(),
age: 25,
};
let person2 = Person {
name: "张三".to_string(),
age: 30, // 年龄不同,但名字相同
};
people.insert(person1, "第一个张三");
people.insert(person2, "第二个张三"); // 会覆盖第一个
println!("人员数量: {}", people.len()); // 输出: 1
for (person, description) in &people {
println!("{:?}: {}", person, description);
}
}
性能考虑
预分配容量
use std::collections::HashMap;
fn main() {
// 如果知道大概的元素数量,预分配容量
let mut map = HashMap::with_capacity(100);
for i in 0..50 {
map.insert(i, i * 2);
}
println!("容量: {}, 长度: {}", map.capacity(), map.len());
}
使用引用作为键
use std::collections::HashMap;
fn count_references(words: &[&str]) -> HashMap<&str, usize> {
let mut counts = HashMap::new();
for &word in words {
*counts.entry(word).or_insert(0) += 1;
}
counts
}
fn main() {
let words = vec!["hello", "world", "hello", "rust"];
let counts = count_references(&words);
for (word, count) in counts {
println!("{}: {}", word, count);
}
}
最佳实践
- 使用
entry
API 进行复杂的插入逻辑 - 考虑键的类型 - 使用引用可以避免不必要的克隆
- 预分配容量 当你知道大概的元素数量时
- 定期清理 长期运行的缓存或映射
- 实现合适的 Hash 和 Eq 对于自定义类型
use std::collections::HashMap;
// 好的实践示例
fn build_index(items: &[String]) -> HashMap<&str, Vec<usize>> {
let mut index = HashMap::with_capacity(items.len());
for (pos, item) in items.iter().enumerate() {
index.entry(item.as_str()).or_insert_with(Vec::new).push(pos);
}
index
}
fn main() {
let items = vec![
"apple".to_string(),
"banana".to_string(),
"apple".to_string(),
"cherry".to_string(),
"banana".to_string(),
];
let index = build_index(&items);
for (item, positions) in index {
println!("{}: {:?}", item, positions);
}
}