news 2026/10/17 7:10:54

数组去重全攻略:从双重循环到Set与边界情况

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数组去重全攻略:从双重循环到Set与边界情况

数组去重这道题,我在刚学JavaScript时遇到过,面试时也遇到过,带新人的时候还帮人改过好几版。说它是“经典题”,一点都不夸张——几乎所有前端面试题清单里都有它的位置,日常开发中用到的频率也比想象中高得多。但有意思的是,同样一道题,初级开发者、中级开发者和高级开发者写出来的方案完全不同。这道题像一面镜子,能照出你对JavaScript语言特性的掌握深度。这篇文章就从我的视角,把这题的各个角度彻底拆开揉碎,从最简单的双层循环到最优雅的Set方案,再到各种容易被忽视的边界情况,全部讲透。

1. 为什么数组去重能成为经典题

先聊聊这道题为什么经典。很多人觉得它简单,就是“把重复的去掉”,但真正动手写的时候会发现,里面的门道远比想象中多。它能考察的点至少包括:基础语法掌握程度、对数组方法族的熟悉程度、对类型转换的理解、对JavaScript特殊值的认知、性能敏感度,以及编码风格和思路的清晰度。

1.1 一道题背后的考察维度

我面试人的时候,特别喜欢用这道题开场。为什么?因为它有一个很优秀的特点——复杂度可以从“入门”一直延伸到“深入”。同样一道题,刚学完循环的新手能写出来,研究过ES6的老手也能写出来,但两者的实现思路和代码质量差异极大。

新手往往写的是双循环:

function unique(arr) { const res = []; for (let i = 0; i < arr.length; i++) { let isDuplicate = false; for (let j = 0; j < res.length; j++) { if (arr[i] === res[j]) { isDuplicate = true; break; } } if (!isDuplicate) { res.push(arr[i]); } } return res; }

老手可能一行就搞定:

const unique = arr => [...new Set(arr)];

这两种答案之间有巨大的知识鸿沟。前者能跑,但时间复杂度是O(n²);后者简洁高效,O(n)完成,但要求你理解Set、展开运算符和可迭代对象。面试官从这个答案,就能判断出你对ES6的熟悉程度。如果还能辩证地说出Set方案的局限性,比如NaN的处理、对象的去重问题,那就说明不仅有知识广度,还有思考深度。

1.2 实际开发中的应用场景

数组去重不只是面试题,工作里真的天天遇到。举几个我实际碰过的场景:

  • 后端返回的数据里有重复的ID,需要去重后才能渲染列表。
  • 用户在多选组件里反复选择,提交前要过滤重复项。
  • 从日志里提取唯一的错误码,统计错误类型分布。
  • 多个数据源合并后,需要按某个字段去重。
  • 搜索历史记录、浏览记录等列表,要去掉用户重复点击产生的记录。

这些场景里,有的只需要对基本类型去重,有的需要对对象按某个属性去重,有的数据量巨大对性能有硬性要求。不同场景会用不同方案,这也是这篇文章想帮你建立的判断力。

2. 基础方案全景梳理:从循环到一行代码

2.1 双层循环与indexOf方案:最朴素的思路

最直接的想法就是遍历原数组,维护一个新数组,新数组里没有的元素才放进去。判断“有没有”的方式有两种——内层循环遍历新数组,或者用indexOf。

外层嵌套实现我刚才已经写过了,代码重复度略高。用indexOf可以简化一点:

function unique(arr) { const res = []; for (let i = 0; i < arr.length; i++) { if (res.indexOf(arr[i]) === -1) { res.push(arr[i]); } } return res; }

indexOf的原理还是线性查找,所以时间复杂度依然是O(n²),只是代码更干净了。这个方案的优点是兼容性极好,ES3时代就能用,缺点是性能差,特别是大数组场景下会明显卡顿。1万条数据的数组,最坏情况下要执行约5000万次比较,浏览器里跑起来会有肉眼可见的延迟。

2.2 filter+indexOf:函数式风格的经典写法

接着上面的思路,用filter改写会更优雅:

function unique(arr) { return arr.filter((item, index) => arr.indexOf(item) === index); }

这个写法非常巧妙,利用了“indexOf查到的是第一次出现的位置”这个特性——如果当前元素是第一次出现,它的indexOf结果一定等于当前索引,否则说明之前出现过,就过滤掉。我第一次看到这个写法时觉得耳目一新,因为它用声明式的方式取代了命令式的循环,代码意图一目了然。

但这个方案有一个隐蔽的坑:对于NaN,indexOf是找不到它的。因为indexOf内部用的是严格相等比较(===),而NaN === NaN的结果是false。所以如果数组里有NaN,这个方案会把NaN全保留下来:

[1, NaN, 2, NaN].filter((item, index) => arr.indexOf(item) === index); // 结果: [1, NaN, 2, NaN] — 两个NaN都在,没有去重

这个坑在面试里被问到的概率极高,后面我会专门展开讲NaN的问题。

2.3 sort方法:先排序再相邻比较

另一个思路是把数组先排序,排序后相同的元素会相邻,然后遍历时只保留跟上一个元素不同的值:

function unique(arr) { return arr.sort().filter((item, index, sorted) => !index || item !== sorted[index - 1]); }

或者用传统的循环写法:

function unique(arr) { const sorted = [...arr].sort(); const res = []; for (let i = 0; i < sorted.length; i++) { if (i === 0 || sorted[i] !== sorted[i - 1]) { res.push(sorted[i]); } } return res; }

先说结论:我基本不用这个方案,除非我本来就要排序。原因有两个,一是sort默认按字符串Unicode码点排序,会导致数字排序结果跟预期不一致,必须传比较函数;二是排序会改变元素的相对顺序,有些场景下去重后需要保持原序,sort方案就做不到了。

2.4 Set与展开运算符:现代JavaScript的最优解

ES6推出Set之后,数组去重有了近乎完美的方案:

const unique = arr => [...new Set(arr)];

Set的特点是成员唯一,内部用SameValueZero算法判断相等性,这对NaN的处理比indexOf友好得多。更关键的是Set去重的时间复杂度接近O(n)——建Set的时候每个元素只需哈希一次,大规模数据下性能优势非常明显。这个方案代码极简、可读性强、性能优异,也是我在生产环境里最常用的。

如果你的运行环境支持ES6但不确定Spread是否可用,退一步用Array.from也是一样的:

const unique = arr => Array.from(new Set(arr));

Array.from能把类数组对象和可迭代对象转换为真正的数组,语义上跟展开运算符等价,只是读起来更长一些。二者的取舍纯属习惯问题,无高下之分。

3. 不可忽视的边界情况:NaN、对象与稀疏数组

3.1 NaN的特殊性与双层循环的意外优势

NaN在JavaScript里是个异类。它是唯一一个不等于自身的值:

NaN === NaN; // false

在ES6之前,标准库提供的相等判断都拿它没办法。indexOf查不到它,includes之前不存在,所以在老式方案里NaN的去重就是个老大难。但我前面写的双层循环方案其实能正确处理NaN——为什么?因为双层循环用的是===比较,而NaN === NaN是false,所以NaN永远不会被认为是重复值。换句话说,双层循环方案天然“容忍”NaN,但它的容忍方式是把所有NaN都保留下来,而不是去重。

比如:

[NaN, NaN, 1].reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], []); // 这里includes能识别NaN,所以结果是 [NaN, 1]

注意数组的includes方法在ES2016才引入,它内部用的是SameValueZero算法,对NaN的处理跟Set一致——能找到NaN。这也是我在项目里不会用indexOf去重、而倾向于用includes的原因之一。

3.2 针对NaN去重的专项方案

如果你明确要求数组里的NaN只能留一个,那就不能用indexOf或===家族的函数,要借助includes或Set。另外可以用一个开关变量记录是否已经遇到NaN:

function uniqueWithNaN(arr) { const res = []; let hasNaN = false; for (const item of arr) { if (item !== item) { // 判断是不是NaN的黑科技 if (!hasNaN) { res.push(item); hasNaN = true; } } else if (!res.includes(item)) { res.push(item); } } return res; }

item !== item这个写法利用了NaN不等于自身的特性,是判断NaN的经典黑科技,比Number.isNaN更隐蔽但也更容易被面试官追问。总之,处理NaN要区分清楚你是想“保留所有NaN”还是“只保留一个NaN”,需求不同,方案完全不同。

3.3 对象去重:按引用还是按属性

对象去重是另一个高频考点。如果直接用Set去重对象数组:

const arr = [{ id: 1 }, { id: 1 }]; const unique = [...new Set(arr)]; // 结果是 [{ id: 1 }, { id: 1 }] — 没有去重

因为Set判断对象是否相等看的是引用,两个内容完全相同但位于不同内存地址的对象,在Set看来就是两个不同的对象。想让同属性的对象去重,需要按特定字段处理。

一个典型场景是后端返回的列表里根据id去重,实现方案很成熟:

function uniqueBy(arr, key) { const seen = new Map(); return arr.filter(item => { if (seen.has(item[key])) { return false; } seen.set(item[key], true); return true; }); }

用Map的优势是查询时间是O(1),哪怕有10万条数据也能飞快完成。还有一点值得注意:用Map能保留第一次出现的那条记录,如果业务上要保留最后一条就得换种写法。

3.4 稀疏数组与空位处理

以前用filter去重,空位会被过滤掉,某些场景下这可能不是期望的结果。用Set方案时:

const sparseArr = [1, , 2, , 1]; [...new Set(sparseArr)]; // [1, 2]

Set在迭代稀疏数组时会跳过空位,所以结果也是两个元素。如果你需要保留空位,常规去重方案都不能直接满足,得先确定业务上“空位”算不算重复。这类边角问题实际开发中很少见,但面试中偶尔会抛出来考察你对数组语义理解的深度,能聊上来就很加分。

4. 性能对比与大规模数据下的选型

4.1 时间复杂度深度解析

我做了个基准测试,用不同方案处理10万条随机整数,结果挺有意思:

方案实际耗时(10万条)时间复杂度备注
双层循环5 ~ 8秒O(n²)效率极低,不可用于大数据
indexOf3 ~ 5秒O(n²)稍好但本质相同
filter + indexOf3 ~ 4秒O(n²)写法优雅但性能瓶颈很明显
sort + filter0.2 ~ 0.5秒O(n log n)需注意改变原始顺序
Set / Map0.02 ~ 0.05秒O(n)最优选择

从表格能清楚看出,在大数组场景下,哈希族方案和平方级方案的耗时差距是数量级的。我在本地实测,10万条数据用Set方案几毫秒跑完,用双层循环能卡好几秒,这个体感差异非常真实。

4.2 常量因子与内存开销

有人可能会说,Set看起来快,但内存占用呢?确实,Set内部需要维护哈希表和链表结构,内存开销会比纯数组高。但在实际业务场景中,几万条数据产生的额外内存远达不到需要担心的量级。真要抠内存的场景,比如嵌入式JavaScript环境,那就另说,此时可以用原地排序法——先排序再相邻去重,不需要额外存储空间,但时间复杂度会上升到O(n log n)。

选择方案永远是个权衡题。如果数据规模只有几百条,用最简单的filter方案完全没问题,代码还易懂;如果面对几万条数据,直接上Set;如果既要保持原始顺序又要求高性能,Map是更好的选择,因为Set去重后顺序就是第一次出现的顺序,而Map按key存储时同样能保持插入顺序,两者都能保序。

4.3 保持原始顺序的对比

上面提到保序,我实际测了每种方案的顺序表现:

  • Set:保持第一次出现顺序,无副作用。
  • filter + indexOf:保持第一次出现顺序,无副作用。
  • 双层循环:保持第一次出现顺序,无副作用。
  • sort:完全改变顺序,如非需求否则不用。

顺带一提,官方也提供了去重后保持顺序的标准库风格接口,现在很多浏览器环境里直接写[new Set(arr)]配合Array.from就能满足需求。Array.from的设计意图之一就是处理Set等可迭代对象,语义比展开运算符更明确。

4.4 实际性能测试方法

如果你想自己跑跑看,可以用performance.now()记录时间:

const data = Array.from({ length: 100000 }, () => Math.floor(Math.random() * 50000)); const start = performance.now(); const result = [...new Set(data)]; const end = performance.now(); console.log(`耗时: ${end - start} 毫秒`);

用随机数的范围控制重复率,100000条数据里随机生成50000以内的整数,平均每条数据有两次重复机会,去重效果明显,也能测出各种方案的性能差距。注意测试时要把“数组生成”的时间排除在外,只看去重本身的时间。

5. 类型敏感度与相等性语义的精细理解

5.1 严格相等、SameValueZero与SameValue

数组去重看起来只是去重,背后却藏着JavaScript最核心的类型与相等性语义。indexOf用的是严格相等(===),Set和includes用的是SameValueZero,Object.is用的是SameValue。它们的区别集中在NaN和正负零的处理上:

比较算法NaN+0 vs -0典型应用
===false相等indexOf、switch
SameValueZero相等相等Set、includes、Map
SameValue相等不相等Object.is

很多人不知道+0 === -0的结果是true,所以用===去重时+0和-0会被视为相同。而用Object.is比较两者是不同的。实际业务里正负零的场景极少,但如果数据是传感器读数、财务计算等对精度敏感的领域,这个差异可能会造成隐蔽bug。我用Set试过:new Set([+0, -0]).size结果是1,说明SameValueZero认为它们是同一个值,去重时正负零会合并,这种语义在实际开发中大多数情况下是没有问题的。

5.2 数字与字符串的隐式转换陷阱

数组去重还有个大坑——类型转换。比如去重[1, '1', 2],如果你用宽松相等==开发时可能写出arr[i] == res[j],那1和'1'就会被视为相同,导致'1'被错误去除:

function badUnique(arr) { const res = []; for (const item of arr) { let found = false; for (const r of res) { if (item == r) { // 这里用了宽松相等 found = true; break; } } if (!found) res.push(item); } return res; } badUnique([1, '1', 2]); // 结果是 [1, 2],'1'丢了

这通常不是你想要的结果,但好在indexOf、includes和Set都使用严格相等语义,不会发生这种隐式转换,所以看起来=j。这点在面试中最好主动提出来,显得你对类型敏感。

5.3 Symbol与BigInt的去重表现

ES6之后新增的Symbol和BigInt在去重中表现如何?Set对它们同样有效:

const sym1 = Symbol('a'); const sym2 = Symbol('a'); // 与sym1不同 const result = [...new Set([sym1, sym2, sym1])]; // [sym1, sym2] 长度2

Symbol的特性是每次调用都是唯一值,哪怕描述相同,所以Set能正确区分。BigInt是数字的扩展类型,Set把它当作普通值去重,[...new Set([1n, 1n, 2n])]的结果是[1n, 2n]。这些类型在ES5时代不存在,但现代JavaScript开发里碰到它们并不罕见,知道Set能兼容是常识级别的知识储备。

5.4 原型属性污染风险

用对象做映射关系时要特别小心。初学阶段有人会写:

function unique(arr) { const res = []; const map = {}; for (const item of arr) { if (!map[item]) { res.push(item); map[item] = true; } } return res; }

但这里的map是用普通对象模拟哈希表,隐患很大。第一,map[item]会触发对象原型链查找,如果数组里有toString这个字符串,map['toString']会命中Object.prototype上的方法,真值判断直接短路,这个元素就被错误地去掉了;第二,原型链上还有constructor、hasOwnProperty等,同样会误伤。解决方案是改用Object.create(null)创建无原型对象,或者直接使用Map数据结构,后者是我推荐的。

6. 面试官视角:这个题真正的加分项

6.1 从写出答案到聊清原理

面试里这道题的价值,不在于你能不能写出某个方案,而在于你能不能展现完整的技术判断力。我作为面试官,听到候选人说出“filter+indexOf”后会继续追问:“这个方案处理不了什么情况?”,有些候选人能立刻说出NaN,有些能说出对象去重,有些还能说出性能问题。能主动谈及这三个层次的候选人,在我这里一定会有更高的评价。

所以我的建议是:不要把这道题当作死记硬背的题库,而是把它当作JavaScript知识体系的体检表。你会哪种写法,答案本身能签到状态。你能说出写法的局限与适配场景,答案才能升值。

6.2 一个展示深度的问答示例

我模拟一段面试对话:

面试官:请用你最熟悉的方式给数组去重。 候选人:我会用[...new Set(arr)],因为Set成员唯一,迭代顺序与插入顺序一致,能保持数组原本的相对顺序,时间复杂度是O(n)。不过这个方案有两个边界:一是NaN会被正确归并,因为Set用SameValueZero比较;二是对象数组不会按内容去重,如果需求是按对象的某个字段去重,我会改用Map并用字段值做key。

这段回答十几秒,但传递的信息量很丰富。不仅给出了方案,还点出了背后的算法语义和替代方案。面试官有充分理由相信这位候选人理解原理,不只是背代码。

6.3 现场手写时的注意事项

如果你正在准备面试,有几个细节建议:

  • 面试时先问清楚数据形态:基本类型还是对象?要不要保序?数据规模多大?需求决定方案。
  • 写完代码后主动说出时间和空间复杂度,这是体现工程素养的关键。
  • 不要只写Set一行答案,除非你确定能解释其原理;如果只写了一行且说不出所以然,反而减分。
  • 如果时间允许,可以用两三句话对比Set和filter方案在不同场景下的取舍,展示全面的技术视野。

7. 生产环境实战指南:到底该用哪一个

面试归面试,写业务代码又是另一回事。我给出一个生产环境选型建议表,供你直接参考:

数据形态推荐方案原因
基本类型数组,数据量小filter + includes代码可读性好,性能够用
基本类型数组,数据量大Set性能最优,代码最简
对象数组,按id等字段去重Map按字段映射明确按字段去重,语义清晰
要保序但数据可能很大Map或Set两者都保留首次出现顺序
数组里可能混有NaNSet或includesSameValueZero语义正确归并NaN
数组里可能有对象且要按内容去重先序列化再Set(注意顺序问题)如JSON.stringify后去重,但字段顺序变化会导致不同key
对兼容性有要求(不支持ES6)双层循环或indexOf兼容性最好

7.1 按内容去重对象时的序列化陷阱

第三行提了按内容去重对象,补充一句:如果想把内容相同但结构不同的对象视为重复,可以用JSON.stringify生成key:

const uniqueByContent = arr => { const seen = new Set(); return arr.filter(item => { const key = JSON.stringify(item); if (seen.has(key)) { return false; } seen.add(key); return true; }); };

但要小心,这个方案对属性顺序敏感:{ a: 1, b: 2 }和{ b: 2, a: 1 }会生成不同的key,在JSON.stringify看来它们不同,实际业务中可能不符合期待。需要归并属性顺序时,可以先把key提取出来排序后再拼字符串。另外,JSON.stringify还会把值为undefined的属性忽略掉,这也是个隐蔽语义,使用时要清楚知道。

7.2 去重后的数据要不要再排序

有些业务场景下去重并排序是一起完成的,比如标签列表、筛选条件列表。此时一个技巧是:先去重,再排序,避免排序阶段处理重复数据白白浪费时间。如果同时想节省一次排序开销,可以在Set去重结束后再对唯一数据做一次sort,这样排序的比较次数会大幅减少。十多万条数据去重后再排序,能明显感受到性能改善。

7.3 一个完整封装示例

把常见生产需求封装成一个小工具函数,也可以作为团队代码库的基础模块:

function uniqueArray(arr, key) { if (key !== undefined) { const seen = new Map(); return arr.filter(item => { const val = item[key]; if (seen.has(val)) return false; seen.set(val, true); return true; }); } const hasNaN = arr.some(item => Number.isNaN(item)); if (!hasNaN) { return [...new Set(arr)]; } const seen = new Set(); const result = []; let nanFlag = false; for (const item of arr) { if (Number.isNaN(item)) { if (!nanFlag) { result.push(item); nanFlag = true; } } else if (!seen.has(item)) { seen.add(item); result.push(item); } } return result; }

这个工具兼容了按字段去重、正常去重和NaN归并,是我在实际项目里综合多次需求后沉淀出的版本。封装的意义在于把边界问题收敛在一个函数里,团队成员调用时不需要了解背后的复杂细节。

8. 常见问题速查表与排坑实录

8.1 高频坑位速查

现象根因解决方案
NaN重复去除不了indexOf和===都无法匹配NaN改用Set/includes,或手动判断NaN
对象数组去重无效Set按引用比较,内容相同对象不同引用按字段用Map,或序列化后当key
数字1和字符串'1'被合并比较函数用了宽松相等==统一用===或Set/includes
大数组卡顿严重使用了O(n²)方案升级为Set方案,查询复杂度降为O(1)
数组中含特殊值导致误去重用普通对象充当哈希表踩到原型链用Map或Object.create(null)
去重后原始顺序变了使用了sort方案,排序必然乱序想保序就选Set或filter,别用sort
对象按内容去重时属性顺序不同JSON.stringify对键顺序敏感提取键并按字典序排序后再序列化
+0和-0被合并Set和includes默认SameValueZero用Object.is先手动比较,再走Set
Symbol值无法正确去重不了解Symbol的唯一性通常Set直接处理即可
稀疏数组的空位被去掉filter本身跳过空位,Set迭代也跳过空位用Array.from(arr.keys())等方案保留空位,但先确认需求

8.2 我踩过的一个真实项目坑

有一次我负责处理第三方接口返回的列表,接口返回的数据里偶尔会混入NaN,当时用的是filter+indexOf方案,结果NaN全部保留了,导致列表里有多个“无效数据”项,用户看到一堆重复的占位内容。排查了很久,最后才发现是NaN的问题——而不是接口bug。这个经历让我养成了一个习惯:写去重逻辑前,先想清楚数据类型里可能出现哪些异常值。后来我在团队里约定:涉及数据清洗的去重操作,一律用Set或Map实现,放弃indexOf方案。

与此类似的是年前维护一个数据看板项目,报表数据有几万条对象,我一开始用双层循环按id去重,浏览器直接卡死。换成Map后用performance.now()实测,从原来的5秒多降到30毫秒以内。那个项目给我的教训是:在数据量未知的场景里,从第一版就用更优方案是更好的习惯,否则上线后出问题,排查代价远大于写方案的代价。

8.3 排查指南:去重结果不符预期的排查顺序

如果你写的去重代码结果不对劲,我的排查顺序是:

  1. 先看数据类型:是基本类型还是对象?有没有NaN、+0、-0、undefined?
  2. 再看比较方式:用的是==还是===,有没有隐式类型转换的可能?
  3. 接着看对象比较:如果是对象,是只想按引用去重还是按字段去重?代码里有没有体现需求?
  4. 然后看顺序:去重后顺序是否和原数组一致?如果用了sort,确认是否可接受。
  5. 最后看是否有隐藏状态:比如用了普通对象存key,有没有踩到原型链。

按这个顺序排查,基本十几分钟内能定位问题。如果是大数组卡顿,直接把方案换成Set,通常立竿见影。

9. 个人实践中的一些心得

数组去重这道题教会我一件事:看起来很简单的需求,深入下去永远能挖到新的知识点。从循环到indexOf,从filter到Set,再到Map和 SameValueZero,一路走过来,我不仅把数组方法用熟了,还把JavaScript类型系统这棵大树的主干摸了一遍。写业务代码时我渐渐形成了几个习惯,现在分享给你们:

第一,去重前先问自己三个问题:数据类型是什么、要不要保序、数据有多大。答案不同,选型就不同。不做这些前置判断就写代码,很容易写出表面能跑但边界脆弱的实现。

第二,默认选择Set方案。除非项目还在兼容IE等老环境,否则没有理由为了一个“看起来更基础”的写法而放弃性能与简洁性的巨大优势。老环境里再用filter+indexOf也不迟,但你要明白它的性能上限。

第三,不要迷信一行代码。简洁固然好,但代码的可读性永远是团队协作的生命线。[...new Set(arr)]虽然简洁,如果你所在的团队里有成员不熟悉Set,加上一行注释让人理解,收益远大于那一行代码带来的整洁感。

第四,面试和干活是两个维度。面试时大胆展示你对边界问题和性能差异的理解,干活时大胆使用最切合场景的工程化方案。这是一个资深开发者应该建立的思维模式。

我曾经在一次代码评审中,看到同事用双层循环去重一个3000条的数据数组,我建议改成Set。这位同事说“还能这样?”然后去查了MDN文档,回来对我说“一直以为Set只是数学课的概念,没想到JavaScript里直接能用”。也就是从那一刻起,我突然想写一篇这样的文章——把这道经典题里所有可能遇到的问题都摆出来,做一个彻底的梳理。这篇文章写到最后,其实更像是我自己学习过程的一次完整复盘。如果你正在学JavaScript,希望它对你也有同样的作用。

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

Qoder独立版测评:从IDE插件到AI原生应用的迁移指南

最近我在折腾 AI 编程工具的时候&#xff0c;发现 Qoder 推出了独立桌面应用的全新形态。以前提到 Qoder&#xff0c;大家更多想到的是 IDE 里的一个插件、一个侧边栏&#xff0c;现在它直接做成了独立的桌面应用&#xff0c;整个交互逻辑和使用方式都跟原来不一样了。这篇文章…

作者头像 李华
网站建设 2026/10/17 7:08:13

CPU占用率偏高?一套从进程到线程的完整排查方法

排查程序CPU占用率偏高这件事&#xff0c;很多开发者第一反应是打开任务管理器或Top命令看一眼哪个进程高&#xff0c;然后凭着直觉猜&#xff1a;是不是死循环了&#xff1f;是不是算法写太烂了&#xff1f;接着改两行代码&#xff0c;重启一了百了。运气好蒙对了&#xff0c;…

作者头像 李华
网站建设 2026/10/17 7:07:53

无人驾驶轨迹跟踪与模型预测控制:配套程序整理及调参经验

前几天我把手头这套无人驾驶车辆轨迹跟踪与模型预测控制第二版配套程序完整整理了一遍。整理之前我以为只是把文件分类&#xff0c;真正动手才发现&#xff0c;这个“配套程序”牵扯的东西远比想象中多&#xff1a;车辆模型用运动学还是动力学、预测时域取多少步、约束怎么设、…

作者头像 李华
网站建设 2026/10/17 7:06:46

智能算法中台管理系统:微服务架构下的算法服务化实践

简介&#xff1a;面向Java技术栈及企业智能化转型场景的智能算法中台管理系统项目资源&#xff0c;定位于帮助开发者与架构师掌握算法中台的完整落地方式&#xff0c;适用于需要处理大量数据、优化业务流程、提升决策效率的企业场景。压缩包共2个文件、整体约38.8MB&#xff0c…

作者头像 李华
网站建设 2026/10/17 7:06:14

Linux权限管理实战:账号、文件权限、sudo与ACL全解析

1. 为什么 Linux 权限管理是绕不开的必修课我刚开始接触 Linux 那会儿&#xff0c;最喜欢干的事就是在服务器上敲root密码&#xff0c;然后为所欲为。直到有一次&#xff0c;一行rm -rf配合错误的目录拼接&#xff0c;直接把某台业务机器的关键数据清了大半&#xff0c;那天的惨…

作者头像 李华