0Pricing
Java Academy · 课时

子映射与范围视图

提取 subMap、headMap 和 tailMap 视图,以便在有序映射中进行基于范围的查找

子映射与范围视图 是 CoddyKit 上的免费 Java Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。

TreeMap 中的范围视图

TreeMap 的 subMap、headMap 和 tailMap 返回由原映射支持的视图——它们会反映底层映射中的更改,反之亦然。通过视图进行的更改也会反映到原始映射中。

TreeMap<Integer, String> map = new TreeMap<>();
for (int i = 1; i <= 10; i++) map.put(i * 10, "item" + i);

var view = map.subMap(30, 70); // [30, 70)
System.out.println(view); // {30=item3, 40=item4, 50=item5, 60=item6}

map.put(45, "new"); // also visible through view!
System.out.println(view.containsKey(45)); // true

headMap:边界以下的键

headMap(toKey) 返回所有键严格小于 toKey 的 Entry。使用包含边界的版本 headMap(toKey, true) 可以包含该边界。

TreeMap<String, Integer> words = new TreeMap<>();
"banana cherry apple date elderberry".chars()
    .mapToObj(c -> String.valueOf((char)c)).distinct()
    .forEach(w -> words.put(w, w.length()));
// Actually let's use real words:
TreeMap<String, Integer> wc = new TreeMap<>();
wc.put("apple",5); wc.put("banana",6); wc.put("cherry",6); wc.put("date",4);

System.out.println(wc.headMap("cherry")); // {apple=5, banana=6}

tailMap:从边界开始的键

tailMap(fromKey) 返回所有键大于或等于 fromKey 的 Entry。

TreeMap<Integer, String> grades = new TreeMap<>();
grades.put(50,"F"); grades.put(60,"D"); grades.put(70,"C"); grades.put(80,"B"); grades.put(90,"A");

// All passing grades (>= 60)
var passing = grades.tailMap(60);
System.out.println(passing); // {60=D, 70=C, 80=B, 90=A}

使用包含边界的 subMap

四参数版本 subMap(from, fromInclusive, to, toInclusive) 可以完全控制边界是否包含:

TreeMap<Integer, String> map = new TreeMap<>();
for (int i = 10; i <= 100; i += 10) map.put(i, "v"+i);

// [30, 60] — both inclusive
System.out.println(map.subMap(30, true, 60, true));
// {30=v30, 40=v40, 50=v50, 60=v60}

// (30, 60) — both exclusive
System.out.println(map.subMap(30, false, 60, false));
// {40=v40, 50=v50}

通过视图修改映射

对 subMap 视图执行 put/remove 操作会反映到原始映射中(反之亦然)。尝试插入视图范围之外的键会抛出异常。

TreeMap<Integer, String> map = new TreeMap<>();
for (int i = 1; i <= 5; i++) map.put(i * 10, "v" + i);

var view = map.subMap(20, 40); // [20, 40)
view.remove(20); // removes from both view and original map
System.out.println(map.containsKey(20)); // false

// This would throw IllegalArgumentException:
// view.put(50, "out of range");

使用场景:日志范围查询

使用 TreeMap 的范围视图,检索两个时间戳之间的所有日志条目:

import java.time.*;
TreeMap<LocalDateTime, String> logs = new TreeMap<>();
logs.put(LocalDateTime.of(2024,1,1,8,0), "Server start");
logs.put(LocalDateTime.of(2024,1,1,10,0), "Request spike");
logs.put(LocalDateTime.of(2024,1,1,14,0), "Maintenance");
logs.put(LocalDateTime.of(2024,1,1,18,0), "Server stop");

var morning = logs.subMap(
    LocalDateTime.of(2024,1,1,8,0), true,
    LocalDateTime.of(2024,1,1,12,0), false
);
morning.forEach((t,m) -> System.out.println(t+" : "+m));

使用场景:价格范围查找

将 TreeMap 的键作为价格,查找价格范围内的所有商品:

TreeMap<Double, String> products = new TreeMap<>();
products.put(9.99, "Pen");
products.put(24.99, "Book");
products.put(49.99, "Headphones");
products.put(299.99, "Tablet");

double min = 10.0, max = 100.0;
var affordable = products.subMap(min, true, max, true);
affordable.forEach((p,n) -> System.out.println(n+" $"+p));
// Book $24.99, Headphones $49.99

降序 subMap

在视图上链式调用 descendingMap(),以进行逆序导航:

TreeMap<Integer, String> map = new TreeMap<>();
for (int i = 10; i <= 100; i += 10) map.put(i, "v"+i);

// Get [40, 80] in descending order
map.subMap(40, true, 80, true)
   .descendingMap()
   .forEach((k,v) -> System.out.println(k + "=" + v));
// 80=v80, 70=v70, 60=v60, 50=v50, 40=v40

NavigableMap 接口

NavigableMap 扩展了 SortedMap,增加了 ceiling/floor/higher/lower 键导航以及降序视图。TreeMap 是最常见的实现;ConcurrentSkipListMap 是线程安全的替代方案。

视图的性能

子映射视图的操作(get、put、containsKey)与底层 TreeMap 相同,均为 O(log n)。创建视图本身的复杂度为 O(1),因为不会发生复制。在视图中扫描 n 个键的范围,复杂度为 O(log N + n),其中 N 是完整映射的大小。

陷阱:过时的视图

由于视图由原始映射支持,原始映射被清空后,视图可能变为空或抛出异常。请始终说明视图是实时的,不要让它们超出预定的生命周期。

TreeMap<Integer, String> map = new TreeMap<>();
map.put(10, "a"); map.put(20, "b"); map.put(30, "c");

var view = map.subMap(10, 30);
map.clear(); // view becomes empty
System.out.println(view.size()); // 0 — but no exception

快速检查

您在键为 {10,20,30,40,50,60,70,80} 的 TreeMap 上调用 map.subMap(30, false, 70, true)。结果中包含哪些键?

回顾:子映射与范围视图

要点:

  • subMap、headMap、tailMap 返回实时的支持视图——不会复制
  • 视图中的更改会反映到原始映射中,反之亦然
  • 使用四参数 subMap(from, fromInclusive, to, toInclusive) 完全控制边界
  • 通过视图执行超出范围的 put 会抛出 IllegalArgumentException
  • 范围扫描的复杂度为 O(log N + n)

常见问题解答

「子映射与范围视图」课时是免费的吗?

是的 — 「子映射与范围视图」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。

「子映射与范围视图」这节课中我会学到什么?

提取 subMap、headMap 和 tailMap 视图,以便在有序映射中进行基于范围的查找 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Java Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「子映射与范围视图」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Java Academy 课中编写并运行代码吗?

能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. TreeMap:有序键值对
  2. 子映射与范围视图
  3. TreeSet 与 NavigableSet
  4. Tree 集合中的自定义排序
← 返回 Java Academy