HashMap 設計為 Key → Value 單向映射,原生不支持反向查找。業務場景常需「根據值找鍵」:如權限碼反查角色、配置值反查鍵名、數據去重反查原始鍵等。
| 方案 | 時間複雜度 | 空間複雜度 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|---|---|
| 遍歷 entrySet | O(n) | O(1) | 臨時查詢、數據量小 | 無額外內存、代碼簡單 | 頻繁查詢性能差 |
| 維護雙向 Map | O(1) | O(n) | 高頻反向查詢、數據穩定 | 查詢極快 | 插入/刪除需同步維護、雙倍內存 |
| Guava BiMap | O(1) | O(n) | 值唯一、需強一致性 | 框架級實現、自動同步 | 值必須唯一、不支持一值多鍵 |
| 值→鍵集合 Map | O(1) | O(n) | 一值多鍵、高頻查詢 | 支持重複值、查詢快 | 維護成本高 |
import java.util.*;
public class ReverseLookup {
/**
* 獲取指定值對應的所有鍵
* @return 空列表表示無匹配,不返回 null
*/
public static List getKeysByValue(Map map, V targetValue) {
if (map == null || map.isEmpty()) return Collections.emptyList();
List keys = new ArrayList<>();
for (Map.Entry entry : map.entrySet()) {
// null 安全比較
if (Objects.equals(entry.getValue(), targetValue)) {
keys.add(entry.getKey());
}
}
return keys;
}
/**
* 獲取第一個匹配的鍵 (效能略優)
*/
public static K getFirstKeyByValue(Map map, V targetValue) {
for (Map.Entry entry : map.entrySet()) {
if (Objects.equals(entry.getValue(), targetValue)) {
return entry.getKey();
}
}
return null;
}
// 測試
public static void main(String[] args) {
Map map = new HashMap<>();
map.put(1, "A");
map.put(2, "A");
map.put(3, "B");
map.put(4, "A");
map.put(5, "C");
System.out.println("值 'A' 對應的鍵: " + getKeysByValue(map, "A")); // [1, 2, 4]
System.out.println("值 'B' 對應的鍵: " + getKeysByValue(map, "B")); // [3]
System.out.println("值 'D' 對應的鍵: " + getKeysByValue(map, "D")); // []
System.out.println("首個 'A' 的鍵: " + getFirstKeyByValue(map, "A")); // 1
}
}
public static List getKeysByValueStream(Map map, V value) {
return map.entrySet().stream()
.filter(entry -> Objects.equals(entry.getValue(), value))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
// 並行流 (大數據量)
public static List getKeysByValueParallel(Map map, V value) {
return map.entrySet().parallelStream()
.filter(entry -> Objects.equals(entry.getValue(), value))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
import java.util.*;
public class BidirectionalMap {
private final Map forward = new HashMap<>();
private final Map> reverse = new HashMap<>();
// 線程安全版本可用 ConcurrentHashMap + ConcurrentSkipListSet
public V put(K key, V value) {
// 1. 移除舊映射的反向索引
V oldValue = forward.get(key);
if (oldValue != null) {
Set oldKeys = reverse.get(oldValue);
if (oldKeys != null) {
oldKeys.remove(key);
if (oldKeys.isEmpty()) reverse.remove(oldValue);
}
}
// 2. 建立新映射
forward.put(key, value);
reverse.computeIfAbsent(value, k -> new HashSet<>()).add(key);
return oldValue;
}
public V get(K key) {
return forward.get(key);
}
public Set getKeys(V value) {
return reverse.getOrDefault(value, Collections.emptySet());
}
public V remove(K key) {
V value = forward.remove(key);
if (value != null) {
Set keys = reverse.get(value);
if (keys != null) {
keys.remove(key);
if (keys.isEmpty()) reverse.remove(value);
}
}
return value;
}
public boolean containsKey(K key) { return forward.containsKey(key); }
public boolean containsValue(V value) { return reverse.containsKey(value); }
public int size() { return forward.size(); }
public void clear() { forward.clear(); reverse.clear(); }
// 測試
public static void main(String[] args) {
BidirectionalMap bimap = new BidirectionalMap<>();
bimap.put(1, "A");
bimap.put(2, "A");
bimap.put(3, "B");
System.out.println("反向查找 'A': " + bimap.getKeys("A")); // [1, 2]
bimap.put(2, "C"); // 更新 key=2 的值
System.out.println("更新後 'A': " + bimap.getKeys("A")); // [1]
System.out.println("新值 'C': " + bimap.getKeys("C")); // [2]
}
}
// 依賴: com.google.guava:guava:32.1.3-jre
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
BiMap biMap = HashBiMap.create();
biMap.put(1, "A");
biMap.put(2, "B");
// biMap.put(3, "A"); // 拋出 IllegalArgumentException: value already present
// 反向視圖 (O(1) 查詢)
BiMap inverse = biMap.inverse();
System.out.println(inverse.get("A")); // 1
// 強制覆蓋值
biMap.forcePut(3, "A"); // 會移除 key=1 的映射
// 依賴: org.apache.commons:commons-collections4:4.4
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
BidiMap bidiMap = new DualHashBidiMap<>();
bidiMap.put(1, "A");
bidiMap.put(2, "B");
System.out.println(bidiMap.getKey("A")); // 1 (單值唯一)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class ReverseLookupBenchmark {
private Map map;
private BidirectionalMap biMap;
@Setup
public void setup() {
map = new HashMap<>();
biMap = new BidirectionalMap<>();
for (int i = 0; i < 10000; i++) {
String val = "VAL_" + (i % 100); // 100 個不同值,每值 100 個鍵
map.put(i, val);
biMap.put(i, val);
}
}
@Benchmark
public List traversalLookup() {
return ReverseLookup.getKeysByValue(map, "VAL_50");
}
@Benchmark
public Set indexLookup() {
return biMap.getKeys("VAL_50");
}
}
/*
典型結果 (10000 條,查找 100 個鍵):
traversalLookup ~1,200,000 ns/op (1.2 ms)
indexLookup ~ 50,000 ns/op (0.05 ms) → 快 24 倍
*/
// 低效:values() 返回 Collection,contains 也是 O(n) 遍歷
if (map.values().contains("A")) { ... }
// 正確:entrySet 遍歷或預建索引
// 危險:ConcurrentModificationException 或讀到中間狀態
// 正確:ConcurrentHashMap + 段鎖,或複製快照遍歷
Map snapshot = new HashMap<>(concurrentMap);
List keys = getKeysByValue(snapshot, value);
// 危險:List 作為 Value,修改內容後 hashCode/equals 變化
Map> map = new HashMap<>();
List list = new ArrayList<>(Arrays.asList("a"));
map.put("key1", list);
list.add("b"); // 值對象內容變了,但 Map 結構沒變
// 查找時需注意:Objects.equals 比較的是當前內容
// 若需按引用查找,用 == 或 IdentityHashMap
// 長期運行服務中,定期清理空集合
if (keys.isEmpty()) reverse.remove(value);
// 或使用 WeakHashMap (僅 Key 弱引用,Value 強引用不適用)
// 自定義 ReferenceQueue 清理機制
entrySet(),配合 Objects.equals 空安全BidirectionalMap 維護反向索引 Map> ConcurrentHashMap + ConcurrentSkipListSet 或 CopyOnWriteArrayList
// Java 8+ 單行獲取所有鍵
List keys = map.entrySet().stream()
.filter(e -> Objects.equals(e.getValue(), target))
.map(Map.Entry::getKey)
.toList(); // Java 16+ / Collectors.toList()
// 僅判斷是否存在
boolean exists = map.containsValue(target); // O(n) 遍歷
// 獲取任意一個鍵
K anyKey = map.entrySet().stream()
.filter(e -> Objects.equals(e.getValue(), target))
.map(Map.Entry::getKey)
.findAny().orElse(null);