Java HashMap

HashMap 是最常用的 Map 實作,儲存鍵值對,基於雜湊表,存取效率高。

建立 HashMap

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

// 空的 HashMap
Map<String, Integer> map1 = new HashMap<>();

// 指定初始容量
Map<String, Integer> map2 = new HashMap<>(100);

// Java 9+ 建立不可變 Map
Map<String, Integer> map3 = Map.of("A", 1, "B", 2);

// 從不可變 Map 建立可變 Map
Map<String, Integer> map4 = new HashMap<>(Map.of("A", 1, "B", 2));

常用方法

新增/修改

Map<String, Integer> map = new HashMap<>();

map.put("Apple", 100);     // 新增
map.put("Banana", 50);
map.put("Apple", 120);     // 修改(相同 key)

// 只在 key 不存在時新增
map.putIfAbsent("Cherry", 80);

// 新增多個
map.putAll(Map.of("D", 10, "E", 20));

取得值

Map<String, Integer> map = new HashMap<>();
map.put("Apple", 100);

Integer price = map.get("Apple");        // 100
Integer none = map.get("Unknown");       // null

// 取得值或預設值
Integer value = map.getOrDefault("Unknown", 0);  // 0

刪除

Map<String, Integer> map = new HashMap<>();
map.put("Apple", 100);
map.put("Banana", 50);

map.remove("Apple");             // 刪除 key
map.remove("Banana", 50);        // 只有 value 也符合才刪除
map.clear();                     // 清空

檢查

Map<String, Integer> map = new HashMap<>();
map.put("Apple", 100);

boolean hasKey = map.containsKey("Apple");     // true
boolean hasValue = map.containsValue(100);     // true
boolean empty = map.isEmpty();                 // false
int size = map.size();                         // 1

遍歷

Map<String, Integer> map = new HashMap<>();
map.put("Apple", 100);
map.put("Banana", 50);
map.put("Cherry", 80);

// 遍歷 key
for (String key : map.keySet()) {
    System.out.println(key);
}

// 遍歷 value
for (Integer value : map.values()) {
    System.out.println(value);
}

// 遍歷 entry(推薦)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// forEach + Lambda
map.forEach((key, value) -> {
    System.out.println(key + ": " + value);
});

進階操作

compute

Map<String, Integer> map = new HashMap<>();
map.put("Apple", 100);

// 根據 key 計算新值
map.compute("Apple", (key, value) -> value + 10);  // 110

// 只在 key 存在時計算
map.computeIfPresent("Apple", (k, v) -> v * 2);

// 只在 key 不存在時計算
map.computeIfAbsent("Banana", k -> 50);

merge

Map<String, Integer> map = new HashMap<>();
map.put("Apple", 100);

// 合併值
map.merge("Apple", 50, Integer::sum);  // 150
map.merge("Banana", 50, Integer::sum); // 50(新 key)

replaceAll

Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);

// 所有值乘以 10
map.replaceAll((key, value) -> value * 10);
// {A=10, B=20}

實際應用

計數器

String[] words = {"apple", "banana", "apple", "cherry", "banana", "apple"};
Map<String, Integer> counter = new HashMap<>();

for (String word : words) {
    counter.merge(word, 1, Integer::sum);
}
// {apple=3, banana=2, cherry=1}

分組

List<String> names = Arrays.asList("Alice", "Bob", "Anna", "Charlie", "Amy");
Map<Character, List<String>> groups = new HashMap<>();

for (String name : names) {
    char first = name.charAt(0);
    groups.computeIfAbsent(first, k -> new ArrayList<>()).add(name);
}
// {A=[Alice, Anna, Amy], B=[Bob], C=[Charlie]}

快取

Map<String, String> cache = new HashMap<>();

public String getData(String key) {
    return cache.computeIfAbsent(key, k -> loadFromDatabase(k));
}

HashMap vs TreeMap vs LinkedHashMap

特性HashMapTreeMapLinkedHashMap
順序鍵排序插入順序
存取O(1)O(log n)O(1)
null key允許不允許允許
// 需要排序用 TreeMap
Map<String, Integer> treeMap = new TreeMap<>();

// 需要保持插入順序用 LinkedHashMap
Map<String, Integer> linkedMap = new LinkedHashMap<>();

效能特性

操作平均時間最差時間
getO(1)O(n)
putO(1)O(n)
removeO(1)O(n)
containsKeyO(1)O(n)

注意事項

hashCode 和 equals

作為 key 的物件必須正確實作 hashCode()equals()

public class Person {
    private String name;
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

執行緒安全

HashMap 不是執行緒安全的,多執行緒請使用:

// 方式一
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());

// 方式二(推薦)
ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();