Chuyển tới nội dung chính

🗺️ Bảng Băm - HashMap

🎯 Món Ăn Hôm Nay

Trong quán café, thay vì nhớ "món số 0 là Espresso, món số 1 là Latte...", bạn muốn tra cứu trực tiếp: "Latte giá bao nhiêu?" → 50,000 VND. Đó là HashMap - cấu trúc key-value giúp tìm kiếm siêu nhanh! Hôm nay chúng ta học HashMap - bảng băm thần thánh trong Java!

☕ HashMap Là Gì?

HashMap là cấu trúc dữ liệu lưu trữ cặp key-value (khóa-giá trị), cho phép:

  • Tìm kiếm rất nhanh: O(1)
  • Key là duy nhất (không trùng lặp)
  • Value có thể trùng lặp

So sánh:

Array/ArrayListHashMap
Truy cập bằng index (0, 1, 2...)Truy cập bằng key ("Latte", "Mocha"...)
Tìm kiếm: O(n)Tìm kiếm: O(1)
Thứ tự cố địnhKhông đảm bảo thứ tự

Tưởng tượng:

  • ArrayList: Kệ đánh số - "Lấy ly ở vị trí 3"
  • HashMap: Tủ có nhãn - "Lấy ly Latte" (không cần biết vị trí)

👨‍🍳 Câu Chuyện Trong Quán

Tình huống: Tra cứu giá món

Cách cũ (Array):

String[] tenMon = {"Espresso", "Latte", "Cappuccino"};
int[] gia = {45000, 50000, 55000};

// Tìm giá Latte - phải duyệt qua mảng!
for (int i = 0; i < tenMon.length; i++) {
if (tenMon[i].equals("Latte")) {
System.out.println("Giá: " + gia[i]); // Chậm O(n)
}
}

Cách mới (HashMap):

HashMap<String, Integer> menu = new HashMap<>();
menu.put("Espresso", 45000);
menu.put("Latte", 50000);
menu.put("Cappuccino", 55000);

// Tìm giá Latte - trực tiếp!
int giaLatte = menu.get("Latte"); // Siêu nhanh O(1)
System.out.println("Giá: " + giaLatte);

📝 Công Thức Sử Dụng HashMap

Ví Dụ 1: Tạo và Thêm Phần Tử

Thao tác cơ bản:

import java.util.HashMap;

public class QuanCafe {
public static void main(String[] args) {
// 🗺️ Tạo HashMap<Key, Value>
HashMap<String, Integer> menu = new HashMap<>();

// Thêm cặp key-value
menu.put("Espresso", 45000);
menu.put("Latte", 50000);
menu.put("Cappuccino", 55000);
menu.put("Mocha", 60000);
menu.put("Americano", 48000);

// In toàn bộ menu
System.out.println("📋 MENU QUÁN CAFÉ");
System.out.println("==================");
System.out.println(menu);

// Kích thước
System.out.println("\nTổng số món: " + menu.size());
}
}

Output:

📋 MENU QUÁN CAFÉ
==================
{Cappuccino=55000, Latte=50000, Mocha=60000, Espresso=45000, Americano=48000}

Tổng số món: 5

Ví Dụ 2: Lấy Giá Trị

Truy cập dữ liệu:

import java.util.HashMap;

public class QuanCafe {
public static void main(String[] args) {
HashMap<String, Integer> menu = new HashMap<>();
menu.put("Espresso", 45000);
menu.put("Latte", 50000);
menu.put("Cappuccino", 55000);

// Lấy giá trị theo key
int giaLatte = menu.get("Latte");
System.out.println("☕ Giá Latte: " + giaLatte + " VND");

// Kiểm tra key tồn tại
if (menu.containsKey("Mocha")) {
System.out.println("✅ Có Mocha");
} else {
System.out.println("❌ Không có Mocha");
}

// Lấy với giá trị mặc định (nếu không tồn tại)
int giaMocha = menu.getOrDefault("Mocha", 0);
System.out.println("☕ Giá Mocha: " + giaMocha + " VND");

// Kiểm tra giá trị tồn tại
if (menu.containsValue(50000)) {
System.out.println("✅ Có món giá 50,000 VND");
}
}
}

Output:

☕ Giá Latte: 50000 VND
❌ Không có Mocha
☕ Giá Mocha: 0 VND
✅ Có món giá 50,000 VND

Ví Dụ 3: Cập Nhật và Xóa

Sửa đổi dữ liệu:

import java.util.HashMap;

public class QuanCafe {
public static void main(String[] args) {
HashMap<String, Integer> menu = new HashMap<>();
menu.put("Espresso", 45000);
menu.put("Latte", 50000);

System.out.println("Menu ban đầu: " + menu);

// Cập nhật giá (key đã tồn tại)
menu.put("Latte", 52000); // Thay thế giá cũ
System.out.println("Sau khi tăng giá Latte: " + menu);

// Cập nhật nếu key chưa tồn tại
menu.putIfAbsent("Mocha", 60000); // Thêm mới
menu.putIfAbsent("Latte", 55000); // Không thay đổi (đã có)
System.out.println("Sau putIfAbsent: " + menu);

// Xóa theo key
menu.remove("Espresso");
System.out.println("Sau khi xóa Espresso: " + menu);

// Xóa tất cả
menu.clear();
System.out.println("Sau clear: " + menu);
System.out.println("Rỗng? " + menu.isEmpty());
}
}

Output:

Menu ban đầu: {Latte=50000, Espresso=45000}
Sau khi tăng giá Latte: {Latte=52000, Espresso=45000}
Sau putIfAbsent: {Latte=52000, Mocha=60000, Espresso=45000}
Sau khi xóa Espresso: {Latte=52000, Mocha=60000}
Sau clear: {}
Rỗng? true

Ví Dụ 4: Duyệt HashMap

Các cách lặp qua HashMap:

import java.util.HashMap;
import java.util.Map;

public class QuanCafe {
public static void main(String[] args) {
HashMap<String, Integer> menu = new HashMap<>();
menu.put("Espresso", 45000);
menu.put("Latte", 50000);
menu.put("Cappuccino", 55000);

System.out.println("📋 MENU QUÁN");
System.out.println("============");

// Cách 1: Duyệt key-value (khuyên dùng)
for (Map.Entry<String, Integer> entry : menu.entrySet()) {
System.out.printf("☕ %-12s: %,d VND%n",
entry.getKey(), entry.getValue());
}

System.out.println("\n--- Chỉ tên món ---");

// Cách 2: Chỉ duyệt key
for (String tenMon : menu.keySet()) {
System.out.println("☕ " + tenMon);
}

System.out.println("\n--- Chỉ giá ---");

// Cách 3: Chỉ duyệt value
for (Integer gia : menu.values()) {
System.out.printf("%,d VND%n", gia);
}

System.out.println("\n--- forEach (Java 8+) ---");

// Cách 4: forEach với lambda
menu.forEach((ten, gia) -> {
System.out.printf("☕ %s = %,d VND%n", ten, gia);
});
}
}

Output:

📋 MENU QUÁN
============
☕ Cappuccino : 55,000 VND
☕ Latte : 50,000 VND
☕ Espresso : 45,000 VND

--- Chỉ tên món ---
☕ Cappuccino
☕ Latte
☕ Espresso

--- Chỉ giá ---
55,000 VND
50,000 VND
45,000 VND

--- forEach (Java 8+) ---
☕ Cappuccino = 55,000 VND
☕ Latte = 50,000 VND
☕ Espresso = 45,000 VND

Ví Dụ 5: HashMap Phức Tạp

Value là object:

import java.util.HashMap;

class DoUong {
String ten;
int gia;
String kichCo;

DoUong(String ten, int gia, String kichCo) {
this.ten = ten;
this.gia = gia;
this.kichCo = kichCo;
}

void hienThi() {
System.out.printf("☕ %s (%s): %,d VND%n", ten, kichCo, gia);
}
}

public class QuanCafe {
public static void main(String[] args) {
// HashMap với value là object
HashMap<String, DoUong> menu = new HashMap<>();

menu.put("LAT-S", new DoUong("Latte", 45000, "Small"));
menu.put("LAT-M", new DoUong("Latte", 50000, "Medium"));
menu.put("LAT-L", new DoUong("Latte", 55000, "Large"));
menu.put("CAP-M", new DoUong("Cappuccino", 52000, "Medium"));

// Tra cứu theo mã
DoUong mon = menu.get("LAT-M");
if (mon != null) {
mon.hienThi();
}

System.out.println("\n📋 TOÀN BỘ MENU:");
menu.forEach((ma, doUong) -> {
System.out.print("[" + ma + "] ");
doUong.hienThi();
});
}
}

Output:

☕ Latte (Medium): 50,000 VND

📋 TOÀN BỘ MENU:
[LAT-L] ☕ Latte (Large): 55,000 VND
[CAP-M] ☕ Cappuccino (Medium): 52,000 VND
[LAT-S] ☕ Latte (Small): 45,000 VND
[LAT-M] ☕ Latte (Medium): 50,000 VND

Ví Dụ 6: Đếm Tần Suất

Ứng dụng thực tế - đếm món bán chạy:

import java.util.HashMap;

public class QuanCafe {
public static void main(String[] args) {
// Danh sách đơn hàng hôm nay
String[] donHang = {
"Latte", "Cappuccino", "Latte", "Mocha",
"Latte", "Espresso", "Cappuccino", "Latte"
};

// Đếm số lần bán
HashMap<String, Integer> soLuongBan = new HashMap<>();

for (String mon : donHang) {
// Tăng số lượng lên 1 (hoặc khởi tạo = 1 nếu chưa có)
soLuongBan.put(mon, soLuongBan.getOrDefault(mon, 0) + 1);
}

// Hiển thị thống kê
System.out.println("📊 THỐNG KÊ BÁN HÀNG");
System.out.println("====================");

soLuongBan.forEach((mon, soLuong) -> {
System.out.printf("☕ %-12s: %d ly%n", mon, soLuong);
});

// Tìm món bán chạy nhất
String banChayNhat = "";
int maxSoLuong = 0;

for (Map.Entry<String, Integer> entry : soLuongBan.entrySet()) {
if (entry.getValue() > maxSoLuong) {
maxSoLuong = entry.getValue();
banChayNhat = entry.getKey();
}
}

System.out.println("\n🏆 Món bán chạy nhất: " + banChayNhat +
" (" + maxSoLuong + " ly)");
}
}

Output:

📊 THỐNG KÊ BÁN HÀNG
====================
☕ Cappuccino : 2 ly
☕ Latte : 4 ly
☕ Mocha : 1 ly
☕ Espresso : 1 ly

🏆 Món bán chạy nhất: Latte (4 ly)

🔥 Thực Hành Trong Quán

Bài Tập 1: Quản Lý Tồn Kho

Tạo HashMap lưu số lượng nguyên liệu còn lại.

HashMap<String, Integer> tonKho = new HashMap<>();
tonKho.put("Bột cà phê", 50); // kg
tonKho.put("Sữa", 100); // lít
tonKho.put("Đường", 30); // kg

// Xuất kho
String nguyenLieu = "Sữa";
int xuatKho = 10;

if (tonKho.containsKey(nguyenLieu)) {
int hienCo = tonKho.get(nguyenLieu);
if (hienCo >= xuatKho) {
tonKho.put(nguyenLieu, hienCo - xuatKho);
System.out.println("✅ Xuất " + xuatKho + " lít " + nguyenLieu);
} else {
System.out.println("❌ Không đủ " + nguyenLieu);
}
}

System.out.println("Tồn kho: " + tonKho);

Bài Tập 2: Danh Bạ Khách Hàng

Lưu thông tin khách hàng với số điện thoại làm key.

class KhachHang {
String ten;
String diaChi;
int diemTichLuy;

KhachHang(String ten, String diaChi) {
this.ten = ten;
this.diaChi = diaChi;
this.diemTichLuy = 0;
}
}

HashMap<String, KhachHang> danhBa = new HashMap<>();

// Thêm khách
danhBa.put("0901234567", new KhachHang("Anh Minh", "Hà Nội"));
danhBa.put("0912345678", new KhachHang("Chị Lan", "HCM"));

// Tra cứu
String sdt = "0901234567";
if (danhBa.containsKey(sdt)) {
KhachHang kh = danhBa.get(sdt);
System.out.println("👤 Khách: " + kh.ten);
System.out.println("📍 Địa chỉ: " + kh.diaChi);
}

Bài Tập 3: Menu 2 Ngôn Ngữ

Tạo HashMap dịch tên món sang tiếng Anh.

HashMap<String, String> dich = new HashMap<>();
dich.put("Cà phê đen", "Black Coffee");
dich.put("Cà phê sữa", "Milk Coffee");
dich.put("Bạc xỉu", "Vietnamese Latte");

System.out.println("📋 MENU");
dich.forEach((vi, en) -> {
System.out.printf("☕ %s | %s%n", vi, en);
});

⚠️ Những Lỗi Đầu Bếp Thường Gặp

Lỗi 1: Không Kiểm Tra null

SAI:

HashMap<String, Integer> menu = new HashMap<>();
menu.put("Latte", 50000);

int gia = menu.get("Mocha"); // ❌ Trả về null!
System.out.println(gia + 1000); // NullPointerException!

ĐÚNG:

// Cách 1: Kiểm tra containsKey
if (menu.containsKey("Mocha")) {
int gia = menu.get("Mocha");
System.out.println(gia + 1000);
}

// Cách 2: Dùng getOrDefault
int gia = menu.getOrDefault("Mocha", 0);
System.out.println(gia + 1000);

Lỗi 2: Key Không Immutable

SAI:

// Dùng ArrayList làm key - có thể thay đổi!
HashMap<ArrayList<String>, Integer> map = new HashMap<>();
ArrayList<String> key = new ArrayList<>();
key.add("Latte");
map.put(key, 50000);

key.add("Hot"); // ❌ Thay đổi key - mất dữ liệu!
System.out.println(map.get(key)); // null!

ĐÚNG:

// Dùng String hoặc Integer - immutable
HashMap<String, Integer> map = new HashMap<>();
map.put("Latte", 50000);

Lỗi 3: Dùng == Thay Vì equals()

SAI:

String key1 = new String("Latte");
String key2 = new String("Latte");

map.put(key1, 50000);
System.out.println(map.get(key2)); // ❌ null nếu HashMap tự viết sai

ĐÚNG:

// HashMap tự dùng equals() - không lo!
// Nhưng nếu key là custom class, phải override equals() và hashCode()

Lỗi 4: Thay Đổi Trong Vòng Lặp

SAI:

// ❌ ConcurrentModificationException!
for (String key : map.keySet()) {
if (key.equals("Xóa")) {
map.remove(key); // ❌ Lỗi!
}
}

ĐÚNG:

// ✅ Dùng Iterator
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
if (it.next().equals("Xóa")) {
it.remove();
}
}

// Hoặc dùng removeIf (Java 8+)
map.keySet().removeIf(key -> key.equals("Xóa"));

💡 Bí Quyết Của Barista

1. Methods Quan Trọng

Thêm/Sửa:

put(key, value)              // Thêm hoặc cập nhật
putIfAbsent(key, value) // Chỉ thêm nếu chưa có
putAll(otherMap) // Thêm tất cả từ map khác

Đọc:

get(key)                     // Lấy value (null nếu không có)
getOrDefault(key, default) // Lấy value hoặc giá trị mặc định
containsKey(key) // Kiểm tra key tồn tại
containsValue(value) // Kiểm tra value tồn tại

Xóa:

remove(key)                  // Xóa theo key
remove(key, value) // Xóa nếu key-value khớp
clear() // Xóa tất cả

Khác:

size()                       // Số cặp key-value
isEmpty() // Kiểm tra rỗng
keySet() // Set các key
values() // Collection các value
entrySet() // Set các cặp key-value

2. Performance

get():     O(1)Siêu nhanh
put(): O(1)Siêu nhanh
remove(): O(1)Siêu nhanh
containsKey(): O(1)Siêu nhanh

3. HashMap vs TreeMap vs LinkedHashMap

// HashMap - Không đảm bảo thứ tự, nhanh nhất
HashMap<String, Integer> map1 = new HashMap<>();

// TreeMap - Sắp xếp theo key, chậm hơn
TreeMap<String, Integer> map2 = new TreeMap<>();

// LinkedHashMap - Giữ thứ tự thêm vào
LinkedHashMap<String, Integer> map3 = new LinkedHashMap<>();

🎓 Bạn Đã Học Được

  • HashMap: Lưu cặp key-value
  • Tìm kiếm: Siêu nhanh O(1)
  • Key duy nhất: Không trùng lặp
  • put/get/remove: Methods cơ bản
  • Duyệt: entrySet(), keySet(), values()
  • Ứng dụng: Menu, tồn kho, đếm tần suất
  • Null-safe: Dùng getOrDefault()

☕ Món Tiếp Theo

Bài tiếp theo: 📝 Xử Lý Chuỗi - String

Học cách xử lý tên món, mô tả, ghi chú!


💡 Lời Khuyên Cuối: HashMap là cấu trúc dữ liệu siêu mạnh cho tra cứu! Hầu hết mọi ứng dụng đều cần HashMap. Nhớ luôn kiểm tra null với getOrDefault() và dùng equals() thay vì == khi so sánh key. Happy mapping! 🗺️☕