跳到主要内容

哈希映射(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);
}
}

最佳实践

  1. 使用 entry API 进行复杂的插入逻辑
  2. 考虑键的类型 - 使用引用可以避免不必要的克隆
  3. 预分配容量 当你知道大概的元素数量时
  4. 定期清理 长期运行的缓存或映射
  5. 实现合适的 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);
}
}