TreeMap 的使用
TreeMap 讲解
遍历TreeMap
"1、增强 for 循环,(兼容全版本,通用性最强)"// 遍历结果按 key 升序:1=葡萄、2=香蕉、5=苹果、9=橘子for(Map.Entry<Integer,String>entry:map.entrySet()){Integerkey=entry.getKey();Stringvalue=entry.getValue();// 业务逻辑System.out.println("序号:"+key+",水果:"+value);}"2、遍历中需删除元素:Iterator 迭代器"开发注意点: 如果需要在遍历过程中删除元素,绝对不能用增强for/forEach, 否则会抛出ConcurrentModificationException(快速失败机制)。 必须使用Iterator的remove()方法安全删除Iterator<Map.Entry<Integer,String>>iterator=map.entrySet().iterator();while(iterator.hasNext()){Map.Entry<Integer,String>entry=iterator.next();Integerkey=entry.getKey();// 示例:删除序号为2的条目if(key==2){iterator.remove();// 安全删除,不会触发并发修改异常continue;}System.out.println("序号:"+key+",水果:"+entry.getValue());}"3、仅需Key,使用 keySet 遍历"如果业务只需要处理所有Key、不需要Value,使用keySet()。 ❌ 注意:不要用 keySet 遍历+反复 map.get(key)取值, 每次 get 都会触发一次红黑树查找,性能远低于 entrySet。for(Integerkey:map.keySet()){System.out.println("序号:"+key);// 非必要不写:String value = map.get(key);}"4、仅需Value,使用 values () 遍历"只需要获取所有Value、不关心对应Key时使用。for(Stringfruit:map.values()){System.out.println("水果:"+fruit);}八、一句话总结
需要自动按键排序、截取区间、获取前后key→ TreeMap
只想要存入顺序不乱 → LinkedHashMap
普通存取啥都不需要 → HashMap
一、核心本质
- 继承关系:
TreeMap implements NavigableMap,和HashMap无继承关系 - 底层数据结构:红黑树(自平衡二叉查找树)
- 核心特点:key 会自动按照规则全局排序,不是存入顺序
- 线程不安全;key 不允许为 null;value 可以null
二、排序两种方式(开发必用)
TreeMap 必须规定key怎么排,二选一:
方式1:自然排序(无参构造)new TreeMap<>()
要求:存入的key类型 必须实现 Comparable 接口
常用自带类型全都实现了:Integer、Long、String、Date
规则:从小到大升序排列
示例代码:
// 无参构造:自然排序TreeMap<Integer,String>map=newTreeMap<>();map.put(5,"苹果");map.put(2,"香蕉");map.put(9,"橘子");map.put(1,"葡萄");// 遍历顺序:1 → 2 → 5 → 9 按键数字升序for(Map.Entry<Integer,String>entry:map.entrySet()){System.out.println(entry.getKey()+":"+entry.getValue());}String排序规则:按照ASCII码字典序
TreeMap<String,Integer>map=newTreeMap<>();map.put("b",1);map.put("a",2);map.put("c",3);// 输出 a b c⚠️ 坑:
如果你自定义对象当做key,没实现Comparable,直接put会直接抛异常:ClassCastException
方式2:定制排序(构造传入 Comparator)【开发更推荐】
不用修改实体类代码,排序规则灵活切换,优先级高于Comparable
语法:
"语法:new TreeMap(比较器对象)"TreeMap<实体类,Object>map=newTreeMap<>(Comparator);案例:自定义User对象,按年龄降序排列
classUser{privateStringname;privateIntegerage;// 构造、get/set省略publicUser(Stringname,Integerage){this.name=name;this.age=age;}publicIntegergetAge(){returnage;}@OverridepublicStringtoString(){returnname+"-"+age;}}publicstaticvoidmain(String[]args){// 比较器:年龄 降序Comparator<User>comparator=(u1,u2)->Integer.compare(u2.getAge(),u1.getAge());TreeMap<User,String>treeMap=newTreeMap<>(comparator);treeMap.put(newUser("张三",20),"员工1");treeMap.put(newUser("李四",35),"员工2");treeMap.put(newUser("王五",28),"员工3");// 遍历:35岁 →28岁 →20岁for(Useruser:treeMap.keySet()){System.out.println(user);}}优势:同一个实体类,想要升序、降序随时新建比较器就行,不用改动实体源码。
三、TreeMap独有的强大API(这是它不可替代的核心原因)
因为底层红黑树支持区间查找,拥有HashMap、LinkedHashMap没有的方法:
TreeMap<Long,String>map=newTreeMap<>();map.put(100L,"A");map.put(200L,"B");map.put(300L,"C");map.put(400L,"D");// 1. 获取最小keymap.firstKey();// 100// 2. 获取最大keymap.lastKey();// 400// 3. 小于250的最大key:200map.floorKey(250L);// 4. 大于250的最小key:300map.ceilingKey(250L);// 5. 截取区间:key >=200 且 <=400 的子MapSortedMap<Long,String>sub=map.subMap(200L,401L);// 6. 小于300所有数据map.headMap(300L);// 7. 大于等于300所有数据map.tailMap(300L);只要需要区间截取、上下限查找,必须TreeMap
四、真实业务使用场景(什么时候该用TreeMap)
场景1:按键持续排序 + 区间数据查询(最典型)
- 以时间戳Long作为key存储定时任务、排班数据
需要查询:某段时间范围内所有任务、查询当前时间上一个/下一个排班
SQL写起来繁琐,内存TreeMap一键调用方法即可。
场景2:商品价格区间筛选
key存商品售价,需要快速拿出 100~500元之间所有商品列表。
场景3:排行榜、计分排序
比如积分排行榜,key=用户积分,自动由高到低排序,随时拿第一名、前10名。
场景4:系统固定配置项按编码排序
系统字典编码:1001、1002、1003…启动加载到内存,永久保持升序,频繁首尾取值。
五、绝对不要用TreeMap的场景
- 单纯存取键值对,不需要排序:HashMap速度快太多
- 只需要保留插入顺序:LinkedHashMap
- 大数据量(上万+)全部载入内存:极易OOM,排序交给数据库order by
- 多线程并发读写:线程不安全
六、三大Map核心对比表
| 集合 | 有序类型 | 底层结构 | 查询速度 | 适用场景 |
|---|---|---|---|---|
| HashMap | 无序 | 数组+链表+红黑树 | 最快 | 日常90%业务存取 |
| LinkedHashMap | 插入/访问有序 | HashMap+双向链表 | 中等 | 固定存入顺序、LRU缓存 |
| TreeMap | key自动排序 | 红黑树 | 最慢 | 按键排序、区间查找、首尾取值 |
七、关键注意事项
- TreeMap判断key相等:依靠比较器的返回值,不是equals/hashCode;
- key不能为null,否则空指针报错;
- 排序规则一旦确定,整个容器所有key必须统一类型;
- 日常CRUD开发TreeMap使用频率很低,大多面试考察红黑树、排序原理。
