Redis 数据结构 ZSet, BIT, HyperLogLog,Geo 空间数据

一,ZSet

Redis 的 Set 数据类型是 无序,不重复,而 ZSet 是 有序,不重复的数据类型。 它在 Set 的基础上,给每个元素加了一个 score(分数) ,Redis 就根据这个分数自动排序。 分数越小 → 排名越靠前(索引越小)

ZSet 的 3 大特点

  1. 有序性 :按 score 分数自动排序(默认从小到大)
  2. 唯一性 :元素(member)不重复,分数可以重复
  3. 查询快:插入、删除、范围查询都是 O (logN) 效率

核心结构:一个 ZSet 里存的是:member(元素) + score(分数)

css 复制代码
zkey: {
  "apple": 10,
  "banana": 5,
  "orange": 15
}

Redis 会按分数排序:banana(5) → apple(10) → orange(15)

1.1 ZADD 简介

| 命令 | ZADD key NX \| XX GT \| LT CH INCR score member score member ... **** | 时间复杂度 | 每添加一个元素,所需的时间复杂度为 O(log(N)),其中 N 为该有序集合中元素的数量。 | | -- | --------------------------------------------------------------------------------------- | --------- | ---------------------------------------------- | | 描述 | Sorted Set Add → 往有序集合(ZSet)里添加元素****给 ZSet 添加一个 / 多个【带分数的元素】,如果元素已存在,就更新它的分数 | | |

【参数讲解】

NX:仅添加新元素,不要更新已有的元素。

XX:仅更新已存在的元素,不要添加新元素。

LT:只有当新得分低于当前得分时,才更新现有元素。此选项并不会阻止新元素的添加。

GT:只有当新得分高于当前得分时,才更新现有元素。此选项并不会阻止新元素的添加。

CH:将返回值从"新增元素的数量"改为"所有发生变动的元素的总数"。其中,"Changed"即表示"发生变动的元素"。所谓"发生变动的元素",既包括新添加的元素,也包括那些分数被更新的现有元素。需要注意的是,命令行中那些分数与之前相同的元素不会被计入统计范围。注意:通常情况下, ZADD 的返回值仅表示新增元素的数量。

INCR:当选择此选项时, ZADD 的行为与 ZINCRBY 相同。在这种模式下,只能指定一对得分元素。

1.1.1 ZADD NX/XX 判断key是否更新写入数据

含义: NX 仅新增,不更新

  • 元素不存在 → 新增
  • 元素已存在不做任何操作(不更新分数)
ruby 复制代码
127.0.0.1:6379> ZADD rank NX 200 tom  # tom 已存在 → 不更新,返回 0
(integer) 0
127.0.0.1:6379> ZADD rank NX 99999 God # God 不存在 → 新增,返回 1
(integer) 1

含义: XX 仅更新,不新增

  • 元素已存在 → 更新分数
  • 元素不存在不创建
ruby 复制代码
127.0.0.1:6379> ZADD rank NX 200 tom  # tom 已存在 → 不更新,返回 0
(integer) 0
127.0.0.1:6379> ZADD rank NX 99999 God # God 不存在 → 新增,返回 1
(integer) 1

XX 只负责 "只更新不新增",不负责改变返回值。 更新永远返回 0

ruby 复制代码
127.0.0.1:6379> ZRANGE rank 0 -1 
1) "jack"
2) "luxi"
3) "tom"
4) "God"
127.0.0.1:6379> ZADD rank XX 600 tom
(integer) 0
127.0.0.1:6379> ZADD rank XX 700 one
(integer) 0

1.1.2 ZADD NX 实现 "类锁" 的两大典型场景

场景 1:防重复提交 / 防重复入队(最常用)

需求:同一个用户只能进入一次榜单 / 任务队列,不能重复添加。

arduino 复制代码
# 用户 tom 尝试加入榜单,已存在则直接失败(禁止重复)
ZADD task:queue 0 tom NX

这里 ZADD NX 就起到了 "准入锁" 效果:

成功 = 准入通过

失败 = 已准入,拒绝重复操作

场景 2:分布式排队、抢名额(限流 / 名额锁)

需求:活动仅限前 100 人参与,每人只能报名一次。

bash 复制代码
# 报名:用户id作为member,时间戳作为分数
ZADD activity:sign ${timestamp} ${userId} NX
# 再限制总人数
ZCARD activity:sign

NX 保证一人只能报一次名(报名锁)

ZSet 有序 + 可计数,天然适合带顺序的名额抢占

SET NX 锁 vs ZADD NX "锁" 核心对比

特性 SET key NX(String 锁) ZSet 成员锁
锁定粒度 整个 Key(独立互斥锁) ZSet 内单个 member(集合内元素互斥)
并发模型 一对一互斥(抢同一把锁) 多元素共存,单个元素防重复
典型用途 全局分布式锁、接口防重、任务互斥 榜单去重、报名防重、队列去重、有序抢占
过期 支持 EX/EXAT 自动过期 ZSet 不支持单 member 过期,需额外清理

XX 参数简单补充(更新专用)

XX = 仅键 / 成员存在时才更新 ,一般不做锁,只做安全更新:

SET key newVal XX 只更新已存在的 key,不新建,防止误创建脏数据。

ZADD key newScore member XX 只修改已有 member 的分数,不新增成员,常用于动态更新排名分数。

1.4 ZADD LT / GT:判断分数范围是否更新写入数据

含义: LT 只在【新分数 < 旧分数】时更新

bash 复制代码
ZADD rank 100 tom

ZADD rank LT 80 tom   # 80 < 100 → 更新成功
ZADD rank LT 120 tom  # 120 > 100 → 不更新

含义: GT 只在【新分数 > 旧分数】时更新

bash 复制代码
ZADD rank 100 tom

ZADD rank GT 120 tom  # 120 > 100 → 更新成功
ZADD rank GT 80 tom   # 80 < 100 → 不更新

1.5 ZADD CH 返回【变化总数】(新增 + 更新)

CH = Changed ,直译:发生变更的成员计数

ZADD 加 CH 后返回:新增的数量 + 更新分数的数量 = 发生变化的总数

sql 复制代码
ZADD key score member [score member ...]

返回值本次命令新成功添加的成员数量

成员原本不存在:新增 → 计数 +1

成员已存在:仅更新分数、成员不变 → 不计入返回值

ruby 复制代码
127.0.0.1:6379> ZADD test:zset 10 a 20 b
2
127.0.0.1:6379> ZADD test:zset CH 15 a 20 b 30 c
2
# a:分数变更 → 计入
# b:分数不变 → 不计入
# c:新增 → 计入
# 返回结果:2(a + c 两个发生变更)

CH 核心价值:精准判断本次操作到底修改了多少数据 ,用于状态监控、业务统计、回调触发、数据同步

1.2 ZRANGE 查看命令

ZRANGEZSet 有序集合 核心查询命令,作用:按索引 / 分数 / 字典序 取出集合成员,搭配多参数实现分页、倒序、按分数筛选、带分数返回等能力。

sql 复制代码
ZRANGE key start stop 
  [BYSCORE | BYLEX]  				-- 按分数查 / 按字典序查(二选一)
  [REV]              				-- 倒序
  [LIMIT offset count]			-- 分页
  [WITHSCORES]       				-- 同时返回分数

ZSet 内部默认按 score 升序排列:

分数越小 → 排名越靠前(索引越小)

索引 start/stop从 0 开始

负数索引:-1 最后一个元素,-2 倒数第二个

示例 1:查询全部成员(索引 0 ~ -1)

less 复制代码
127.0.0.1:6379> ZADD ztest 10 a 20 b 30 c 40 d 50 e
5
127.0.0.1:6379> ZRANGE ztest 0 -1
a
b
c
d
e

示例 2:带分数返回 WITHSCORES(最常用)

less 复制代码
127.0.0.1:6379> ZRANGE ztest 0 -1 WITHSCORES
a
10
b
20
c
30
d
40
e
50

示例 3:索引区间截取

less 复制代码
127.0.0.1:6379> ZRANGE ztest 0 2 WITHSCORES
a
10
b
20
c
30

示例 4:倒序查全部 + 带分数

不加 REV:默认分数升序, 加 REV :分数降序(高分在前,排行榜常用)

注意: REV 只反转排序,不改变 start/stop 索引逻辑。

less 复制代码
127.0.0.1:6379> ZRANGE ztest 0 -1 REV WITHSCORES
e
50
d
40
c
30
b
20
a
10

示例 5: 分页查询(第 1 页,每页 2 条)

less 复制代码
127.0.0.1:6379> ZRANGE ztest -inf +inf BYSCORE LIMIT 0 2 WITHSCORES
a
10
b
20

ZRANGE, ZRANGEBYSCORE, ZRANGEBYLEX, ZRANGESTORE 之间的区别?

ZRANGE统一入口 (Redis 6.2+ 整合),通过后缀参数切换能力,但不是能完全替代所有旧命令,语法、适用场景仍有区分;

ZRANGEBYSCORE / ZRANGEBYLEX传统独立命令 ,功能被 ZRANGE + BYSCORE/BYLEX 兼容

ZRANGESTORE结果写入新集合,纯存储用途,查询命令无法替代。

命令 核心作用 检索维度 能否用 LIMIT 主要用途
ZRANGE 通用查询 索引(默认)、分数、字典序 仅搭配 BYSCORE/BYLEX 基础查索引、排行榜 TopN、简单分页
ZRANGEBYSCORE 分数区间查询 分数 原生支持 LIMIT 延时队列、分数筛选、分数分页(旧版标准写法)
ZRANGEBYLEX 成员字典序查询 成员字符串 原生支持 LIMIT 同分数下按字符串过滤、分组筛选
ZRANGESTORE 查询结果存入新 ZSet 同 ZRANGE(索引 / 分数 / 字典序) 支持 数据拷贝、榜单快照、临时集合生成
lua 复制代码
1. 按索引查询
ZRANGE key s e  <-- 独立功能,无等价旧命令

2. 按分数查询
ZRANGE key min max BYSCORE  ↔  ZRANGEBYSCORE key min max

3. 按字典序查询
ZRANGE key min max BYLEX    ↔  ZRANGEBYLEX key min max

4. 结果存入新集合
ZRANGESTORE  <-- 独有功能,任何查询命令都替代不了

1.3 ZREM 删除命令

命令 ZREM key member member ...**** 时间复杂度 O(M*log(N))其中 N 是排序后集合中的元素数量,M 是需要被移除的元素数量。
描述 从存储在 key 处的有序集合中移除指定的成员。不存在的成员将被忽略。****
ruby 复制代码
127.0.0.1:6379> ZADD myzset 1 "one"
1
127.0.0.1:6379> ZADD myzset 2 "two"
1
127.0.0.1:6379> ZADD myzset 3 "three"
1
# 删除 two 成员
127.0.0.1:6379> ZREM myzset "two"
1
# 查看
127.0.0.1:6379> ZRANGE myzset 0 -1 WITHSCORES
one
1
three
3

1.4 ZCARD

命令 ZCARD key**** 时间复杂度 O(1);集合里面存储着总数的变量
描述 返回存储在 key 处的排序集的元素数量
ruby 复制代码
127.0.0.1:6379> ZADD myzset:test 1 one 2 two 3 three
3
127.0.0.1:6379> ZCARD myzset:test
3

1.5 ZCOUNT

命令 ZCOUNT key min max 时间复杂度 O(log(N)),其中 N 为排序集合中的元素数量
描述 统计有序集合(ZSet)中分数在 [min, max] 闭区间内的元素数量。

闭区间案例

ruby 复制代码
127.0.0.1:6379> ZADD score 10 a 20 b 30 c 40 d
4

# 闭区间-统计分数 [20,30] 区间数量
127.0.0.1:6379> ZCOUNT score 20 30
2 # 返回:2 (b、c)

# 开区间-统计分数(20, 40) 
127.0.0.1:6379> ZCOUNT score (20 (40
1 # 返回:1 (c)

# 无穷范围
127.0.0.1:6379> ZCOUNT score -inf +inf
4

# 分数 <30
127.0.0.1:6379> ZCOUNT score -inf (30
2 # 返回:2

1.6 ZINTER & ZINTERSTORE 交集

Redis 中有序集合交集主要分两个命令:

ini 复制代码
# 直接返回交集结果(Redis 6.2.0+ 新增)
ZINTER numkeys key [key ...] [WEIGHTS weight [weight ...]]
  [AGGREGATE <SUM | MIN | MAX | COUNT>] [WITHSCORES]

# 把交集结果存入新 key(经典常用)
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight
  [weight ...]] [AGGREGATE <SUM | MIN | MAX | COUNT>]
ruby 复制代码
127.0.0.1:6379> ZADD s1 10 a 20 b 30 c
3
127.0.0.1:6379> ZADD s2 20 b 30 c 40 d
3
# 2个集合求交集,结果存入 inter_res
127.0.0.1:6379> ZINTERSTORE inter_res 2 s1 s2
2

1.7 ZDIFF & ZDIFFSTORE 差集

二者用于计算有序集合差集 ,规则:保留在第一个集合存在、但在后续集合中不存在的元素

css 复制代码
# 初始化数据
ZADD A 10 a 20 b 30 c 40 d
ZADD B 20 b 50 e
ZADD C 30 c 60 f

# 求 A - B,结果存入 diff1
ZDIFFSTORE diff1 2 A B
ZRANGE diff1 -1 -1 WITHSCORES
# 结果:a(10)、c(30)、d(40)

# 求 A - B - C,结果存入 diff2
ZDIFFSTORE diff2 3 A B C
ZRANGE diff2 -1 -1 WITHSCORES
# 结果:a(10)、d(40)
命令 版本 特点 适用场景
ZDIFFSTORE 全版本 结果持久化到新 key 数据复用、长期存储差集结果
ZDIFF 6.2.0+ 临时查询,不存数据 一次性统计、临时筛选

1.8 ZUNION & ZUNIONSTORE 并集

vbnet 复制代码
ZUNIONSTORE dest-key numkeys key [key ...] [WEIGHTS weight...] [AGGREGATE SUM|MIN|MAX]
css 复制代码
# 初始化数据
ZADD s1 10 a 20 b
ZADD s2 20 b 30 c

# 1. 基础并集(默认 SUM)
ZUNIONSTORE union1 2 s1 s2
ZRANGE union1 -1 -1 WITHSCORES
# 结果:a 10,b 40,c 30

# 2. 聚合规则 MIN / MAX
ZUNIONSTORE union_min 2 s1 s2 AGGREGATE MIN
ZRANGE union_min -1 -1 WITHSCORES
# a 10,b 20,c 30

ZUNIONSTORE union_max 2 s1 s2 AGGREGATE MAX
ZRANGE union_max -1 -1 WITHSCORES
# a 10,b 20,c 30

# 3. 使用权重
ZUNIONSTORE union_weight 2 s1 s2 WEIGHTS 2 1
ZRANGE union_weight -1 -1 WITHSCORES
# a 20(10*2),b 60(20*2+20),c 30

二,位操作

Redis 的位图(Bitmap) 并不是一种独立的数据结构,而是基于 String(字符串) 类型提供的一组按位(bit)操作的命令。它将字符串视为一个二进制位数组,允许你对字符串中的特定位进行设置、获取和统计。

命令 SETBIT key offset value 时间复杂度 O(1)
描述 设置指定 key 的字符串在 offset(偏移量)处的 bit 值。

由于Redis 位图本质是 String 类型 ,底层用二进制位 (bit) 存储数据,最小操作单位是 1 个 bit 。 单个字符串最大 512MB,对应 4294967296 个 bit ,可实现海量二值状态存储,极度省内存

适用场景:只有 0/1 两种状态的数据。 1 个字节(byte)等于 8 个位(bit),位图在处理大量布尔值(0 或 1,如"是/否"、"在线/离线"、"签到/未签到")时,能极大地节省内存空间,并提供极高效的聚合计算能力。

场景:用户签到系统

设计 :Key 为 sign:{year}{month}{day},Offset 为用户ID,Value 为 1(签到)或 0(未签到)

优势:一个用户一年的签到记录只需要 365 bits ≈ 46 字节,极其节省空间。

ruby 复制代码
127.0.0.1:6379> SETBIT sign:20260612 0 1  # 6月12号,0号用户 已签到
0
127.0.0.1:6379> SETBIT sign:20260612 1 1  # 6月12号,1号用户 已签到
0
127.0.0.1:6379> SETBIT sign:20260612 2 1  # 6月12号,2号用户 已签到
0
127.0.0.1:6379> SETBIT sign:20260612 3 1  # 6月12号,3号用户 已签到
0
127.0.0.1:6379> SETBIT sign:20260612 4 1  # 6月12号,4号用户 已签到
......
127.0.0.1:6379> SETBIT sign:20260612 41 0
0
127.0.0.1:6379> SETBIT sign:20260612 42 0
0
127.0.0.1:6379> SETBIT sign:20260612 43 0
0
127.0.0.1:6379> SETBIT sign:20260612 44 0
0
127.0.0.1:6379> SETBIT sign:20260612 45 0 # 6月12号,45号用户 未签到
0

这边小编 从0到45号用户,其中只有41到45未签到,其余的都已签到

需求一:查询指定用户是否已签到

ruby 复制代码
127.0.0.1:6379> GETBIT sign:20260612 45
0  # 查询 45号用户,返回 0,表示未签到
127.0.0.1:6379> GETBIT sign:20260612 10
1  # 查询 10号用户,返回 1,表示已签到

需求二:查询当天所有的签到情况

ruby 复制代码
127.0.0.1:6379> BITCOUNT sign:20260612
41  # 一共有42 位用户已签到

但是位图有一个短板,就是查出用户的签到明细列表,比如导出今天签到的用户 ID 列表:0, 1, 2, 3...40"。

因为位图底层是一长串二进制(比如 1111111111...00000),Redis 没有 提供类似 SMEMBERS(获取集合所有元素)的命令来直接列出所有为 1 的偏移量。因为如果数据量是千万级,要把所有为 1 的位遍历一遍找出来,会极其消耗 CPU,甚至卡死 Redis。

首先我们要达成一个共识:位图(Bitmap)从娘胎里出来,就不是为了"查明细"设计的。 它的基因里只有两个技能:

极速的宏观统计 (今天多少人签到?用 BITCOUNT

极速的集合运算 (连续三天签到的人有哪些?用BITOP

如果你非要问"具体是哪几个人签到了?",这就好比你问一个秤:"请告诉我这根头发有多长"。秤只能告诉你重量(统计),不能告诉你长度(明细)。

三,HyperLogLog

HyperLogLog 是 Redis 专门用来做基数统计 的数据结构,核心作用:估算集合中不重复元素的数量(去重总数)

底层依旧基于 String 类型 ,单个 HLL 对象固定占用 12KB 内存。 理论可统计 上亿级唯一元素 ,内存几乎不增长。 存在标准误差 :默认误差率约 0.81% ,业务可接受 。 只存「基数估算值」,不存储原始数据,无法取出元素

适用场景: 只关心去重数量、不关心具体明细的场景:

  • 页面每日独立访客 UV
  • 接口独立调用用户数
  • 直播间独立观看人数 (比如6月13号开播的《凡人修仙传》的在线人数的大概统计。数据成千上万之后不需要精确到个位数的计算。
  • 统计各类唯一访问 / 参与人数

对比:Set 也能去重计数,但元素越多内存占用越大;HLL 内存恒定,海量数据首选。

3.1 PFADD:添加元素到 HLL

HyperLogLog 的命令都是以 PF 开头的(这是为了纪念它的发明者 Philippe Flajolet)。在 Redis 中,它底层依然是复用 String 类型的 12KB 空间。

ruby 复制代码
# 用户 1001 打开了 App
127.0.0.1:6379> PFADD dau:20260612 "user_1001"
(integer) 1  # 返回 1 表示内部估计的基数可能发生了变化

# 用户 1002 打开了 App
127.0.0.1:6379> PFADD dau:20260612 "user_1002"
(integer) 1

# 用户 1001 又打开了 App(重复添加)
127.0.0.1:6379> PFADD dau:20260612 "user_1001"
(integer) 0  # 返回 0 表示内部估计的基数没变(自动去重了!)

注:HLL 接受字符串作为元素,所以不管你的用户 ID 是雪花算法的长数字,还是 UUID,直接往里塞就行,不需要像 Bitmap 那样非得转成连续自增数字!

3.2 PFCOUNT:获取估算的基数

ruby 复制代码
127.0.0.1:6379> PFCOUNT dau:20260612
(integer) 2  # 返回估算的独立用户数

3.3 PFMERGE:合并多个 HLL(计算周活/月活)

假设你想统计这周的周活(WAU),只需要把每天的 HLL 合并起来。

ruby 复制代码
# 把周一到周日的 HLL 合并到 wau:20260612 这个 key 中
127.0.0.1:6379> PFMERGE wau:20260612 dau:20260606 dau:20260607 ... dau:20260612
"OK"

# 查看本周的总独立访客数
127.0.0.1:6379> PFCOUNT wau:20260612
(integer) 154000 # 自动去重后的周活估算值

四,Geo 空间数据

Redis 的 Geo(Geographic) 数据结构是 Redis 3.2 版本引入的,专门用于存储地理位置信息 以及基于地理位置进行计算和查询(如:附近的人、共享单车查找、外卖骑手距离计算等)

Redis 并没有为 Geo 发明一种全新的底层数据结构,而是巧妙地复用了 ZSet(有序集合) ,并结合了 GeoHash 算法

  1. 为什么用 ZSet? 在 ZSet 中,member 存储位置名称(如"张三"、"单车A"),score 存储该位置的 GeoHash 值。因为 ZSet 本身是有序的,所以可以非常方便地按距离进行范围查询和排序。
  2. 什么是 GeoHash 算法?
    • 核心思想 :将二维的经纬度 转换为一维的整数(或字符串)
    • 实现方式:把地球的经纬度范围进行二分法切割,形成一个个网格。经度和纬度分别编码后,再按位交叉合并。
    • 神奇特性前缀越相似的 GeoHash 值,代表两个位置在物理上越接近 。Redis 底层使用 52 位的整数来存储 GeoHash,精度可以达到 0.6米 左右。

Redis 的 Geo 数据结构仅仅只支持二维平面上的"点(Point)"数据 ,它不支持"面(Polygon/Area)" ,也不支持"三维空间(3D/包含海拔高度)"

⚠️ 注意 :在 Redis 6.2 之前,常用的是 GEORADIUSGEORADIUSBYMEMBER。但由于这两个命令在执行时会阻塞主线程,存在性能和安全问题,Redis 6.2 引入了全新的 GEOSEARCH GEOSEARCHSTORE 命令来替代它们。以下以新版命令为主。

1. 增删改查基础命令

bash 复制代码
# 1. 添加地理位置 (经度在前,纬度在后)
# 格式:GEOADD key 经度 纬度 成员名
GEOADD bikes:parking 116.404 39.915 "bike1" 116.405 39.916 "bike2"

# 2. 获取位置的经纬度
GEOPOS bikes:parking bike1

# 3. 计算两点之间的距离 (支持 m, km, mi, ft)
GEODIST bikes:parking bike1 bike2 km

# 4. 获取位置的 GeoHash 字符串 (常用于前端展示或缓存)
GEOHASH bikes:parking bike1

# 5. 删除位置 (其实就是 ZSet 的 ZREM)
ZREM bikes:parking bike1

2. 范围查询(核心)

css 复制代码
# 格式:GEOSEARCH key [FROMMEMBER 成员名 | FROMLONLAT 经度 纬度] 
#       [BYRADIUS 半径 m|km | BYBOX 宽 高 m|km] 
#       [ASC|DESC] [COUNT 数量] [WITHCOORD] [WITHDIST] [WITHHASH]

# 案例:查找经度 116.403,纬度 39.914 附近 3公里 内的单车,按距离升序,最多返回5个,并返回距离和坐标
GEOSEARCH bikes:parking FROMLONLAT 116.403 39.914 BYRADIUS 3 km ASC COUNT 5 WITHCOORD WITHDIST

3. 实战案例:查找附近的共享单车

业务场景:用户打开 App,系统需要获取用户当前位置,并找出附近 1 公里内的 3 辆共享单车,按距离从近到远排序。

Redis CLI 模拟
bash 复制代码
# 1. 运营人员录入几辆单车的位置 (北京国贸附近)
GEOADD shared_bikes 116.4610 39.9075 "bike_001"
GEOADD shared_bikes 116.4625 39.9080 "bike_002"
GEOADD shared_bikes 116.4650 39.9100 "bike_003"
GEOADD shared_bikes 116.4800 39.9200 "bike_004" # 这辆比较远

# 2. 用户当前定位在 (116.4615, 39.9078),查找 1km 内的单车
GEOSEARCH shared_bikes FROMLONLAT 116.4615 39.9078 BYRADIUS 1 km ASC COUNT 3 WITHDIST

返回结果

lua 复制代码
1) 1) "bike_002"
   2) "0.0432"  <-- 距离 0.0432 km
2) 1) "bike_001"
   2) "0.0556"  <-- 距离 0.0556 km
# bike_003 距离超过 1km 被过滤,bike_004 更远被过滤
相关推荐
苏三说技术1 小时前
为什么越来越多的人使用FastAPI?
后端
JavaGuide1 小时前
比 iTerm2 更适合 Claude Code/Codex 的终端,我换成 Ghostty 了
人工智能·后端
DyLatte2 小时前
AI 时代,最危险的不是被替代,而是努力不沉淀
前端·后端·程序员
神奇小汤圆2 小时前
架构师必备:CPU使用率不均匀排查
后端
神奇小汤圆2 小时前
Multi-Agent 执行闭环:AI Coding 真正进生产,要靠模型分工和工程护栏
后端
柒和远方3 小时前
从一次工程审查看 AI 学习产品的边界兜底:RAG 资料链路一致性实战
前端·后端·架构
亦暖筑序3 小时前
Java 8老系统Browser Agent实战:三层拦截把AI操作后台变成可审计流程
java·后端·设计模式
用户34232323763173 小时前
GPIO控制与按键中断入门
后端
Gopher_HBo3 小时前
Go语言学习笔记(十五)Http响应
后端