HashMap的api使用详解
情况一:直接使用视图(会影响原 Map)
keySet()、values()、entrySet() 返回的是 原 Map 的“视图”,不是副本。它们的操作会直接反映到原 Map 上。
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);Set<String> keySet = map.keySet(); // 这是视图,不是副本
Collection<Integer> values = map.values();
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();// 删除操作会影响原 map
keySet.remove("a"); // ✅ 原 map 中的 "a" 被删除
System.out.println(map); // 输出: {b=2}// entrySet 修改 value 也会影响原 map
entrySet.iterator().next().setValue(99); // ✅ 修改原 map 的 value
⚠️ 这些视图集合不允许添加元素(会抛 UnsupportedOperationException),但删除和通过 entry 修改 value 是允许的,并且会同步到原 map。
情况二:创建新集合(不会影响原 Map)
如果你用这些视图创建了一个新的集合(例如 new ArrayList<>()),那么这个新集合就是一个独立的副本,对它的修改不会影响原 Map。
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);// 创建副本
List<String> newKeyList = new ArrayList<>(map.keySet());
List<Integer> newValueList = new ArrayList<>(map.values());
List<Map.Entry<String, Integer>> newEntryList = new ArrayList<>(map.entrySet());// 修改新集合
newKeyList.add("c");
newValueList.set(0, 999);
newEntryList.get(0).setValue(888);// 查看原 map
System.out.println(map); // 输出: {a=1, b=2} —— 完全没变!
✅ 结论:因为 newKeyList、newValueList、newEntryList 是新的独立对象,所以修改它们不会影响原 Map。
特别注意:对象引用问题
即使你创建了新集合,如果 key 或 value 是可变对象,并且你修改的是对象的内容(而不是集合本身),那仍然可能“间接”影响原 map 中的 value。
class Person {String name;Person(String name) { this.name = name; }@Override public String toString() { return "Person{" + name + "}"; }
}Map<String, Person> map = new HashMap<>();
map.put("p1", new Person("Alice"));// 创建副本
List<Person> copyValues = new ArrayList<>(map.values());// 修改副本中对象的内容
copyValues.get(0).name = "Bob";System.out.println(map); // 输出: {p1=Person{Bob}} —— 原 map 的 value 也被改了!
📌 原因:ArrayList 存储的是对象的引用,不是对象的深拷贝。所以 copyValues 和 map 中的 value 指向同一个 Person 对象。修改对象内容,两边都能看到。
情况四:使用stream流(一般不会影响原 Map)
示例 1:过滤 + 排序(不影响原 map)
Map<String, Integer> map = new HashMap<>();
map.put("apple", 10);
map.put("banana", 5);
map.put("orange", 15);// 转为 Stream 并进行中间操作
List<String> filteredKeys = map.keySet().stream().filter(k -> k.length() > 5).sorted().collect(Collectors.toList());// 原 map 未受影响
System.out.println("原 map: " + map);
// 输出: 原 map: {apple=10, banana=5, orange=15}
✅ 这里的 filter 和 sorted 是中间操作,它们只是在流中处理数据,不会修改原 Map。
示例 2:通过 entrySet 修改 value?会!
map.entrySet().stream().filter(e -> e.getValue() > 8).forEach(e -> e.setValue(99)); // ❌ 危险!但这不是流的问题
⚠️ 注意:上面代码中 .forEach(e -> e.setValue(99)) 确实会修改原 map!
但这不是因为 Stream 的中间操作,而是因为你在 forEach(终端操作)中显式调用了 Map.Entry.setValue(),这是 entrySet 视图允许的操作。
✅ 正确理解:
filter、sorted、map 等中间操作:不会修改原 map
forEach 中调用 entry.setValue():会修改原 map(因为你在操作视图)
示例 3:安全的流处理(完全不影响原 map)
Map<String, Integer> map = new HashMap<>();
map.put("a", 3);
map.put("b", 7);
map.put("c", 1);List<Integer> result = map.values().stream().filter(v -> v > 2).map(v -> v * 2).sorted().collect(Collectors.toList());System.out.println("原 map: " + map); // {a=3, b=7, c=1}
System.out.println("结果: " + result); // [6, 14]
✅ 完全安全,原 Map 未被修改。
HashMap排序
方法一:使用 Stream API(推荐,Java 8+)
import java.util.*;
import java.util.stream.Collectors;public class SortHashMapByValue {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();map.put("apple", 10);map.put("banana", 5);map.put("orange", 15);map.put("grape", 8);// 根据 value 升序排序Map<String, Integer> sortedAsc = map.entrySet().stream().sorted(Map.Entry.comparingByValue()).collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue,(e1, e2) -> e1,LinkedHashMap::new // 保持排序顺序));System.out.println("升序: " + sortedAsc);// 根据 value 降序排序Map<String, Integer> sortedDesc = map.entrySet().stream().sorted(Map.Entry.<String, Integer>comparingByValue().reversed()).collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue,(e1, e2) -> e1,LinkedHashMap::new));System.out.println("降序: " + sortedDesc);}
}
输出:
升序: {banana=5, grape=8, apple=10, orange=15}
降序: {orange=15, apple=10, grape=8, banana=5}
方法二:使用 List 和 Collections.sort()
import java.util.*;public class SortHashMapByValue {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();map.put("apple", 10);map.put("banana", 5);map.put("orange", 15);map.put("grape", 8);// 将 entry 转为 List 并排序List<Map.Entry<String, Integer>> list = new ArrayList<>(map.entrySet());list.sort(Map.Entry.comparingByValue()); // 升序// 输出排序结果System.out.println("升序:");for (Map.Entry<String, Integer> entry : list) {System.out.println(entry.getKey() + " = " + entry.getValue());}// 或者构建新的有序 MapMap<String, Integer> sortedMap = new LinkedHashMap<>();for (Map.Entry<String, Integer> entry : list) {sortedMap.put(entry.getKey(), entry.getValue());}}
}
方法三:自定义比较器(适用于复杂 value 类型)
如果 value 是自定义对象,可以使用自定义 Comparator或使用lambda表达式
Map<String, Person> map = new HashMap<>();
// 假设 Person 有 age 属性
map.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.comparing(Person::getAge))).collect(...);
注意事项
排序后通常使用 LinkedHashMap 来保持插入顺序。
HashMap 本身不保证顺序,所以排序后不能直接存回 HashMap。
如果 value 为 null,需处理 NullPointerException,可使用 Comparator.nullsLast()。
TreeMap排序
package org.example;import java.util.*;public class Test {public static class Student {private int age;private int seqno;public Student(int age, int seqno) {this.age = age;this.seqno = seqno;}// Getter 方法public int getAge() {return age;}public int getSeqno() {return seqno;}// 便于打印调试@Overridepublic String toString() {return "Student{age=" + age + ", seqno=" + seqno + '}';}}public static void main(String[] args) {// 自定义比较器:先按 age 升序,再按 seqno 升序Comparator<Student> studentComparator = (s1, s2) -> {if (s1.getAge() != s2.getAge()) {return Integer.compare(s1.getAge(), s2.getAge());}return Integer.compare(s1.getSeqno(), s2.getSeqno());};// 创建 TreeMap,使用自定义比较器Map<Student, Integer> sortedMap = new TreeMap<>(studentComparator);// 添加数据sortedMap.put(new Student(20, 2), 100);sortedMap.put(new Student(18, 5), 200);sortedMap.put(new Student(20, 1), 300);sortedMap.put(new Student(18, 3), 400);sortedMap.put(new Student(22, 1), 500);sortedMap.put(new Student(22, 1), 600);// 重复的 key 会被覆盖,覆盖原来的值,treemap原理compare方法返回0,会覆盖原来的值// 遍历输出(会按排序顺序输出)for (Map.Entry<Student, Integer> entry : sortedMap.entrySet()) {System.out.println(entry.getKey() + " -> " + entry.getValue());}}
}