news 2026/10/16 11:54:43

MySQL JSON_CONTAINS 详解:告别 LIKE 模糊匹配,高效查询 JSON 数组

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL JSON_CONTAINS 详解:告别 LIKE 模糊匹配,高效查询 JSON 数组

我在做数据清洗的时候遇到过不少类似场景:一张订单表里,某个字段存的是 JSON 数组,里面塞着用户选的 SKU、标签、渠道列表,业务上需要查“包含某个值的所有记录”。很多人的第一反应是 LIKE 一把梭,写个%xxx%去匹配,结果边界条件踩了一堆坑。后来换了 MySQL 原生的 JSON_CONTAINS,才算把这类查询从“能跑”变成了“靠谱”。这篇就专门聊聊这个函数,从语法、底层逻辑到实际场景、性能优化,一次讲透。

1. JSON_CONTAINS 到底帮你解决了什么问题

1.1 为什么 LIKE 和 FIND_IN_SET 都不够用

先说一个非常典型的业务需求:商品表里有个tag_list字段,存储结构是 JSON 数组,比如["爆款", "新品", "限时"],现在要找出所有包含“新品”标签的商品。

用 LIKE 的话是这样写的:

SELECT * FROM product WHERE tag_list LIKE '%新品%';

这写法看着没毛病,但实际隐患很多。假设某个商品有个 tag 叫“新品尝鲜”,那么LIKE '%新品%'也会把它捞出来,这就是典型的模糊匹配误伤。而且如果 JSON 数组里的字符串包含特殊字符,比如引号、反斜杠,LIKE 的转义逻辑又是一堆细节。

还有人会用 FIND_IN_SET,但是它专为逗号分隔的字符串设计,和 JSON 数组的结构并不完全兼容。JSON 数组里元素可以包含逗号,比如["a,b", "c"],FIND_IN_SET 一遇到这种就废了。更重要的是,这两个方案都忽略了一个问题:JSON 是带类型的,数字1和字符串"1"在语义上完全不同,文本匹配根本无法区分这两个。

JSON_CONTAINS 的价值就在于:它按 JSON 文档的结构去判断包含关系,而不是按“字符串子串”去匹配。它知道数组里有哪些元素,知道你给的目标值是字符串还是数字,也就从根上避免了误匹配。

1.2 这个函数的应用边界

JSON_CONTAINS 的适用场景集中在三块:

  • JSON 数组的条件过滤,判断数组是否包含一个或多个元素
  • JSON 对象的键值匹配,判断一个嵌套对象里是否存在指定键值对
  • 和虚拟列、函数索引配合,实现对 JSON 字段的高效查询

它解决的核心痛点是:在 MySQL 5.7 之后引入了 JSON 类型,但很多开发者还是习惯用老办法去处理 JSON 数据,结果既损失了 JSON 语义,又让查询性能变得难以预判。JSON_CONTAINS 补上的正是这个缺口。

适合使用 JSON_CONTAINS 的读者,主要是那些在业务里已经用 JSON 字段存储半结构化数据的人,比如标签系统、配置项、埋点数据、用户画像、商品属性集。如果你还在用 LIKE 搞这类查询,这篇文章值得仔细看。

2. 语法细节与参数解读

2.1 参数结构和返回规则

JSON_CONTAINS 的完整语法如下:

JSON_CONTAINS(target, candidate[, path])

三个参数的含义:

  • target:被搜索的 JSON 文档,可以是 JSON 类型的列,也可以是表达式
  • candidate:要查找的目标 JSON 文档,可以是标量、对象或数组
  • path:可选的 JSON 路径表达式,比如$.tags,用于限定搜索范围

返回值很简单:命中返回 1,未命中返回 0。但如果传入的 target 或 candidate 是非法 JSON,或者 path 写错,会直接报错。

举个例子:

SELECT JSON_CONTAINS('["a", "b", "c"]', '"b"'); -- 返回 1

注意 candidate 的写法,字符串b在 JSON 里是以"b"形式存储的,所以传参的时候必须写成带双引号的字符串字面量。很多初学者在这一步翻车,写的是JSON_CONTAINS('["a", "b"]', 'b'),结果返回 0,还以为是函数的问题。

candidate 是数组也可以:

SELECT JSON_CONTAINS('["a", "b", "c"]', '["a", "b"]'); -- 返回 1

这个很好理解,语义是“target 里是否存在 candidate 数组的全部元素”,相当于数组子集的判断。但是如果 candidate 里有一个元素不存在,整体就是 0:

SELECT JSON_CONTAINS('["a", "b", "c"]', '["a", "d"]'); -- 返回 0

2.2 path 参数的用法和坑

实际开发中,JSON 不是总是顶层数组,更多时候是嵌套对象。比如下面这个存储用户信息的 JSON:

{ "name": "张三", "tags": ["老客户", "高净值"], "address": { "city": "北京", "district": "朝阳区" } }

如果想判断 tags 里是否包含“高净值”,就要用到 path:

SELECT JSON_CONTAINS( '{"name": "张三", "tags": ["老客户", "高净值"]}', '"高净值"', '$.tags' );

path 的写法是标准的 JSON 路径,$表示文档根,$.tags表示取根对象里的 tags 字段。如果路径指向一个对象里的某个键:

SELECT JSON_CONTAINS( '{"address": {"city": "北京"}}', '"北京"', '$.address.city' );

需要注意两点。第一,path 指到的是一个对象时,candidate 也要传对象;path 指到数组时,candidate 传数组里的元素。第二,如果 path 写成了不存在的位置,返回结果不是 0,而是直接报错:

SELECT JSON_CONTAINS( '{"name": "张三"}', '"李四"', '$.address.city' ); -- ERROR 3143: Invalid JSON path expression

实际开发中,我最常用的方式是先用 JSON_EXTRACT 或 JSON_KEYS 看看目标路径的结构,确认无误后再写 JSON_CONTAINS 的 path。

2.3 对象作为 candidate 时的行为

JSON_CONTAINS 对对象的匹配规则,是要求 target 中对应位置包含 candidate 对象里的所有键值对,但允许 target 有额外的键。

SELECT JSON_CONTAINS('{"name": "张三", "age": 30}', '{"name": "张三"}'); -- 返回 1 SELECT JSON_CONTAINS('{"name": "张三", "age": 30}', '{"name": "李四"}'); -- 返回 0 SELECT JSON_CONTAINS('{"name": "张三", "age": 30}', '{"name": "张三", "age": 30}'); -- 返回 1

也就是说,candidate 对象是 target 对象的“子集”时,结果为 1。这个规则和数组的逻辑是呼应的:candidate 必须被完全包含。

这里容易忽略的是嵌套场景。比如 target 是{"info": {"name": "张三"}},如果用不带 path 的方式去查:

SELECT JSON_CONTAINS( '{"info": {"name": "张三"}}', '{"name": "张三"}' ); -- 返回 0

原因是 target 的根节点是info对象本身,candidate 的{"name": "张三"}和根节点对不上。这种场景必须用 path 指到$.info,或者直接把 candidate 写成完整结构:

SELECT JSON_CONTAINS( '{"info": {"name": "张三"}}', '{"info": {"name": "张三"}}' );

这两个写法结果都是 1,但语义完全不同。第一个是判断 info 内部结构,第二个是判断整个文档完全包含。写代码前先想清楚你要查的是哪个层级。

3. 实战场景:标签过滤、动态属性和多条件匹配

3.1 数组标签的精确匹配

回到开头说的商品标签场景。建一张简化表:

CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100), tag_list JSON ); INSERT INTO product (name, tag_list) VALUES ('商品A', '["爆款", "新品"]'), ('商品B', '["新品尝鲜", "限时"]'), ('商品C', '["限时", "清仓"]');

查询包含“新品”这个独立标签的商品:

SELECT id, name FROM product WHERE JSON_CONTAINS(tag_list, '"新品"');

这条 SQL 的结果只会返回商品A,不会误伤商品B,因为 JSON_CONTAINS 对比的是数组里的完整元素,而不是做子串匹配。这就是它和 LIKE 的本质差异。

再复杂一点,筛选同时包含“爆款”和“新品”两个标签的商品:

SELECT id, name FROM product WHERE JSON_CONTAINS(tag_list, '["爆款", "新品"]');

这里 candidate 写成数组,语义是“必须同时包含这两个元素”。如果业务需要“包含其中一个就行”,那就要用 OR:

SELECT id, name FROM product WHERE JSON_CONTAINS(tag_list, '"爆款"') OR JSON_CONTAINS(tag_list, '"新品"');

实测小数据量时这两条 SQL 性能差距不大,但到几十万行的表,OR 写法会让优化器更难走索引。后面性能部分再展开聊。

3.2 嵌套对象的关键值查询

再看一个用户画像场景,每个用户有一条 profile 的 JSON,包含多个维度的标签和属性:

{ "user_id": 1001, "level": "VIP", "preferences": { "category": ["数码", "家电"], "brands": ["某品牌A", "某品牌B"] } }

查询偏好品牌里包含“某品牌A”的 VIP 用户:

SELECT user_id FROM user_profile WHERE JSON_CONTAINS(profile, '{"level": "VIP"}') AND JSON_CONTAINS(profile, '"某品牌A"', '$.preferences.brands');

注意这里我同时用了两个条件,第一个验证 level,第二个深入嵌套路径。这个写法很直观,但要注意 JSON_CONTAINS 对同一 JSON 文档做两次扫描,如果 profile 特别大、数据量特别多,性能会成为瓶颈。

另一种做法是直接把整个 profile 都匹配一次:

SELECT user_id FROM user_profile WHERE JSON_CONTAINS(profile, '{ "level": "VIP", "preferences": { "brands": ["某品牌A"] } }');

这个写法看起来一步到位,实际上有一个大坑:JSON 对象的包含判断要求 candidate 里的数组必须完整匹配 target 数组内容。也就是说,如果某用户 brands 字段是["某品牌A", "某品牌B"],candidate 写成["某品牌A"]是不行的,因为数组不是对象,不存在“子集”的宽松匹配规则。除非业务能保证 brands 只有一个元素,否则这个写法会漏数据。

所以实际线上我推荐用多条件 AND 方案,虽然多扫一次,但语义清晰,也不会因为数组长度变化而漏查。

3.3 和 JSON_EXTRACT、JSON_TYPE 的联合作战

JSON_CONTAINS 有时候不是单独用的,而是和其他 JSON 函数配合,先做结构判断,再做值提取。

比如,判断一个 JSON 字段里某个数组非空,且包含特定值时:

SELECT user_id FROM user_profile WHERE JSON_CONTAINS(profile, '"某品牌A"', '$.preferences.brands') AND JSON_EXTRACT(profile, '$.preferences.brands') IS NOT NULL;

第二个条件其实是防御性的,防止preferences或brands键不存在导致 JSON_CONTAINS 报错。一般来说,如果 JSON 结构是程序写入且固定的,可以不写这个防御条件,但如果是历史数据或者第三方导入的数据,我建议加上,线上排查问题会省很多事。

还可以用 JSON_TYPE 判断字段类型,因为一个字段虽然名为 brands,但历史数据里可能存的是字符串而不是数组:

SELECT user_id FROM user_profile WHERE JSON_TYPE(JSON_EXTRACT(profile, '$.preferences.brands')) = 'ARRAY' AND JSON_CONTAINS(profile, '"某品牌A"', '$.preferences.brands');

这类写法适合做数据质量检查时用,不太适合直接放到高频查询路径里,原因还是性能。JSON_TYPE 是全文档解析,开销不小。

4. 性能分析:从全表扫描到函数索引

4.1 为什么 JSON_CONTAINS 默认不走索引

MySQL 5.7 引入 JSON 类型后,一个常见误解是:JSON 字段也可以建索引。其实 MySQL 的普通索引无法直接建立在 JSON 列上,原因是 JSON 是文档型结构,内部数据经过了二进制格式化和压缩,不像 B+ 树的 key 那样可以按规则排序和比较。

所以一条带有 JSON_CONTAINS 的 WHERE 条件,默认情况下的执行计划就是全表扫描,对每一行做 JSON 解析和匹配。数据量超过十万行时,这个开销非常可观。

我之前在一个模拟项目X里面做过压测,一张 20 万行的表,JSON 字段平均大小约 1KB,一条 JSON_CONTAINS 查询的响应时间在 200ms 到 500ms 之间浮动,而相同条件的普通 B+ 树索引查询只需要几毫秒。差距就是这么明显。

4.2 虚拟列 + 函数索引的正确打法

既然 JSON 列本身不能直接建索引,业界通用做法是使用生成列(Generated Column),配合函数索引来加速。

思路是这样的:从 JSON 字段里把你要查的值提取成一个虚拟列,然后在虚拟列上建普通索引。MySQL 对表达式的值是有缓存逻辑的,插入和更新时自动计算,之后查询就能走索引。

建表语句如下:

CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100), tag_list JSON, first_tag VARCHAR(50) AS (JSON_UNQUOTE(JSON_EXTRACT(tag_list, '$[0]'))) STORED, INDEX idx_first_tag (first_tag) );

这里把tag_list数组的第一个元素提取到first_tag列,并加上 STORED 关键字,让虚拟列持久化存储。然后查询:

SELECT id, name FROM product WHERE first_tag = '新品';

这是对“提取固定位置元素”场景的优化。但更常见的 JSON_CONTAINS 判断是“数组里是否存在某个元素”,这个用虚拟列建索引的思路要变一下。

比如,你要经常查 tag_list 是否包含“爆款”,可以单独建一个生成列:

ALTER TABLE product ADD COLUMN has_burst VARCHAR(20) AS (CASE WHEN JSON_CONTAINS(tag_list, '"爆款"') THEN 'yes' ELSE 'no' END) STORED, ADD INDEX idx_has_burst (has_burst);

然后查询就退化成等值比较:

SELECT id, name FROM product WHERE has_burst = 'yes';

这个方法非常有效,但代价是业务需求一旦变化,比如要查“新品”,就得再加一个生成列。所以它适合标签集合相对固定、查询模式比较明确的场景,不适合那种随意筛选标签的 BI 报表环境。

4.3 大数据量下的替代方案

当数据量上升到百万级甚至更高,JSON_CONTAINS 搭配虚拟列仍然有限制,因为每个标签都要建一列根本不可维护。这时候我会考虑更务实的替代方案。

一是把高频查询的标签拆出来,单独建关联表。比如 product_tag 表,每行是 product_id 和 tag 两个字段,tag 列建普通索引,查询变成 JOIN 或 IN 子查询,性能完全是常规索引能力范围。

二是使用全文索引。如果 JSON 里的标签是短文本,而且允许一定的模糊匹配损耗,可以建全文索引,用 MATCH AGAINST 查询,性能比 JSON_CONTAINS 强很多。

三是在应用层做缓存。对于标签筛选这类查询条件相对固定、结果集更新不频繁的场景,可以用缓存维护筛选结果集合,业务侧直接读缓存,数据库只承担写入和兜底。

这三个方案的取舍没有标准答案,核心看你的查询频率、数据一致性要求、以及团队维护成本。在我的实践里,查询频率高的核心链路建议走关联表;分析类的临时场景保留 JSON_CONTAINS 完全没问题。

5. 常见问题与排查技巧

5.1 数字类型匹配总是返回 0

这是一个我见过的最高频问题。JSON 里存的数字是1,查询时写:

SELECT JSON_CONTAINS('[1, 2, 3]', '1'); -- 返回 0

看上去没毛病,实际返回 0。原因是 JSON_CONTAINS 的 candidate 参数需要符合 JSON 文档规范,裸数字1不是合法的 JSON 文档,它实际上被当作字符串处理了。

正确的写法是:

SELECT JSON_CONTAINS('[1, 2, 3]', '1'); -- 错误 SELECT JSON_CONTAINS('[1, 2, 3]', '1'); -- 正确

第二行的1用双引号包起来,是一个 JSON 字符串字面量。如果 target 中的元素是 JSON 数字,那么 candidate 要写成:

SELECT JSON_CONTAINS('[1, 2, 3]', '1');

JSON 数字可以用1或者1.0表示。这里有个细节,JSON_CONTAINS 在比较时会区分整数和浮点数吗?实测下来,MySQL 对1和1.0的判断是相等的。不过保险起见,建议在代码层统一数字格式,不要让它依赖 MySQL 的隐式处理。

5.2 NULL 和非法 JSON 引发的连锁问题

如果 target 列本身是 NULL,JSON_CONTAINS 的返回值不是 0,而是 NULL。这在 WHERE 条件里不会报错,但配合 NOT 使用时会出问题。

比如你要查“不包含某标签”的商品:

SELECT id, name FROM product WHERE NOT JSON_CONTAINS(tag_list, '"新品"');

如果有一行 tag_list 是 NULL,那么 JSON_CONTAINS 返回 NULL,NOT NULL 仍是 NULL,WHERE 条件不成立,这行数据就被过滤掉了。从业务语义上看,“没有标签的商品的应该算不包含新品”,但现在它没有被返回。

解决办法是显式处理 NULL:

SELECT id, name FROM product WHERE tag_list IS NULL OR NOT JSON_CONTAINS(tag_list, '"新品"');

另一个问题是非法 JSON。JSON 类型的列本身有合法性校验,理论上不会存非法 JSON,但如果是从 TEXT 或 VARCHAR 字段强转过来的历史数据,就可能存在非 JSON 内容。JSON_CONTAINS 遇到这种数据会直接报错,整条 SQL 失败。

排查方法很简单,先把疑似有问题的行筛出来:

SELECT id, tag_list FROM product WHERE JSON_VALID(tag_list) = 0;

5.3 关于路径和转义的几个高频翻车点

路径中的键如果含特殊字符,比如空格、汉字、引号,一般建议用双引号包裹键名:

SELECT JSON_CONTAINS( '{"用户 标签": ["A", "B"]}', '"A"', '$."用户 标签"' );

注意$."用户 标签"这个写法,键名用双引号包裹。不加引号在某些版本下会报路径错误。

还有一种情况是 JSON 字符串里本身包含双引号,比如标签值是"a\"b"。在 SQL 里写 candidate 时,需要用反斜杠转义,还要处理 SQL 本身的引号转义规则,多层嵌套下来非常容易写错。

我的建议是:遇到这类场景,先单独跑一条 SELECT 把 JSON 内容打印出来,确认目标值的原始结构,再用 JSON_QUOTE 函数辅助生成合法 JSON 字符串字面量:

SELECT JSON_CONTAINS( '["a\"b"]', JSON_QUOTE('a"b') );

JSON_QUOTE 能帮你在运行时生成正确的 JSON 字符串格式,避免手写转义。

6. 其他 JSON 条件函数的替代和对比

JSON_CONTAINS 不是唯一的 JSON 条件查询工具,实际开发中还要根据场景区分 JSON_CONTAINS、JSON_OVERLAPS 和 JSON_SEARCH 的取舍。

MySQL 8.0.17 之后的 JSON_OVERLAPS 是“两个 JSON 文档有交集”就返回 1,它和 JSON_CONTAINS 的区别很简单:JSON_CONTAINS 要求 candidate 完全被包含,JSON_OVERLAPS 只要有一个公共元素就算命中。

SELECT JSON_CONTAINS('["a", "b"]', '["b", "c"]'); -- 返回 0,因为 c 不在 target 中 SELECT JSON_OVERLAPS('["a", "b"]', '["b", "c"]'); -- 返回 1,因为 b 是公共元素

业务含义就是“同时包含”和“包含任一”的区别。

JSON_SEARCH 则是返回匹配字符串在 JSON 文档中的路径,它的重点是“找位置”,不是“判断是否包含”。如果只是条件过滤,JSON_SEARCH 不是最佳选择,因为它的语义更接近模糊搜索,性能也不占优势。

按场景给一个直接的选型建议:

场景推荐函数
数组是否包含某个元素JSON_CONTAINS
数组是否包含多个元素JSON_CONTAINS
数组是否和目标有交集JSON_OVERLAPS
查找包含某值的路径JSON_SEARCH
对象中是否存在键值对JSON_CONTAINS
提取值后再做数值比较JSON_EXTRACT

选函数的核心逻辑是:先确认查询的语义到底要“完全包含”还是“存在交集”,选错函数会导致数据漏选或多选,比性能问题更棘手。

7. 除了查询,JSON_CONTAINS 还能用在哪些地方

JSON_CONTAINS 不只是 WHERE 条件里的工具,它还能放在 SELECT 列表、JOIN 条件、CASE WHEN 表达式里,用起来比想象中灵活。

7.1 用布尔标记实现复杂筛选结果

有的业务需要把满足条件的记录打标,但不想真正过滤掉。比如统计每个商品是否属于“爆款标签分组”:

SELECT id, name, CASE WHEN JSON_CONTAINS(tag_list, '"爆款"') THEN '爆款' WHEN JSON_CONTAINS(tag_list, '"新品"') THEN '新品' ELSE '普通' END AS tag_group FROM product;

这种情况下 JSON_CONTAINS 作为表达式参与计算,和普通 WHERE 条件用法不同。如果你要把多个标签匹配结果拼成一组,也能在 SELECT 里做布尔运算,只是注意 JSON_CONTAINS 返回值不是严格的 0/1,而是可能为 NULL,遇到 NULL 时 CASE 分支不会命中。

7.2 JOIN 条件里的点匹配

表 A 的某个字段存的是 JSON 数组,表 B 存的是具体值,要找出所有关联关系:

SELECT A.id, B.name FROM table_a A JOIN table_b B ON JSON_CONTAINS(A.tags, JSON_QUOTE(B.tag_name));

这个写法把 B 的字段值先转成合法的 JSON 字符串,再和 A 的 JSON 数组做匹配。从可用性角度讲,它避免了动态拼接 SQL 的麻烦,但在大数据量表 JOIN 时性能会比较差,因为无法走索引。一般用在关联表数据量小、或一次性数据处理的场景。

7.3 需要注意的是,它不是判断“键是否存在”的工具

如果你想判断 JSON 对象里是否包含某个键,JSON_CONTAINS 并不是一个好选择,应该用 JSON_KEYS 或 JSON_EXTRACT 结合 IS NOT NULL 判断。

比如判断 profile 里是否有 age 字段:

SELECT JSON_EXTRACT(profile, '$.age') IS NOT NULL FROM user_profile;

但这里也有坑:如果 age 的值本身就是 null,那么 JSON_EXTRACT 返回 JSON null,IS NOT NULL 结果会是 false,这就有歧义。更准确的写法是配合 JSON_CONTAINS_PATH:

SELECT JSON_CONTAINS_PATH(profile, 'one', '$.age');

JSON_CONTAINS_PATH 专门用来判断路径是否存在,语义精确。JSON_CONTAINS 解决的是“值到底有没有”的问题,JSON_CONTAINS_PATH 解决的是“键到底有没有”的问题,两者分工不同。

8. 踩过几次坑之后的操作习惯

我用了几年 JSON_CONTAINS,踩过的坑不少,总结几个已经固化的操作习惯。

第一个习惯:写任何 JSON 查询前,先单独验证 JSON 文档本身的结构和值类型。用一个最小化的 SELECT 确认无误后再放到业务查询里。比如先在控制台跑:

SELECT JSON_EXTRACT(tag_list, '$') FROM product LIMIT 5; SELECT JSON_TYPE(JSON_EXTRACT(tag_list, '$[0]')) FROM product LIMIT 5;

确认数组元素类型是字符串还是数字,再写 candidate。这一步能筛掉大多数“明明有值却查不出来”的问题。

第二个习惯:对历史数据或者外部导入数据,永远先做一次 JSON_VALID 检测。老表从 VARCHAR 迁移到 JSON 类型时,数据质量往往参差不齐,直接上线 JSON_CONTAINS 查询很可能在运行时炸出无效 JSON 的报错。

第三个习惯:构造复杂 JSON 条件时,尽量把 JSON 的字符串字面量交给函数生成,比如 JSON_QUOTE 和 JSON_ARRAY,而不是手写。手写多层引号转义的 SQL 极易出错,尤其在代码里嵌又一层语言变量时,双重转义让人崩溃。

我的最后一个经验是:JSON_CONTAINS 适合灵活性高、数据量中等的场景。如果你的数据量已经大到需要给它做各种索引优化和拆表,说明 JSON 不是最适合这种查询的结构,早一步拆出来建关系表,反而省事。做数据模型选型时不要把 JSON 列当成万能选项,它解决的是“结构不固定”的问题,代价就是查询和关联能力的削弱。

如果你正被 JSON 查询折腾得头疼,不妨想一下你的业务到底是查询固定字段多,还是结构变化多。前者用关系表更清爽,后者用 JSON_CONTAINS 这类函数恰到好处。做对了取舍,后面能省下不少维护成本。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 11:52:18

视觉里程计三十年发展史:从VO原理到SLAM工程实践详解

做视觉定位这些年,我发现自己被问得最多的一个问题不是“怎么标定相机”,也不是“换哪种特征点”,而是“VO到底是什么,它和SLAM到底哪里不一样”。很多刚入门的同学抱着“我要做一个SLAM”的心态来找我,结果折腾了半天…

作者头像 李华
网站建设 2026/10/16 11:52:03

Unity5实战源码拆解:C#多平台游戏开发从跑通到避坑

简介:这份源码资源对应《Unity5实战:使用C#和Unity开发多平台游戏》一书,面向具备一定C#基础、希望系统掌握Unity5跨平台游戏开发的初学者与进阶开发者。内容围绕Unity的组件系统展开,涵盖GameObject与Transform、Mesh Renderer、…

作者头像 李华
网站建设 2026/10/16 11:38:50

东南亚Shopee/TikTok/Lazada多店铺ERP推荐,怎么选?

很多卖家在ERP选型时会陷入 “功能越多越好” 的误区,但东南亚本土店业务复杂,不同平台规则、不同站点政策、不同订单类型差异很大。真正适合本土卖家的ERP,不只是能同步订单,还要能处理多平台商品统一管理、跨店库存防超卖、COD订…

作者头像 李华
网站建设 2026/10/16 11:35:09

C#工控仪表控件库:WinForms实时数据绑定与DPI自适应渲染

简介:本资源是一套面向工控、组态软件开发及.NET桌面应用初学者的C#仪表控件实战示例,聚焦圆表、直表(测量标尺)、旋钮、开关与指示灯等高复用性UI组件的集成与定制。资源完整包含可直接运行的演示程序(CShapeExample.…

作者头像 李华
网站建设 2026/10/16 11:32:09

Android UI四大组件实战:Toast/Dialog/Menu/Spinner协同开发

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华