普通视图

发现新文章,点击刷新页面。
今天 — 2026年4月8日首页

用 3100 个数字造一台计算机

作者 jump_jump
2026年4月8日 01:01

你有没有想过,一台计算机最少需要什么?

不是说你桌上那台——那个有几十亿个晶体管、跑着操作系统和浏览器的庞然大物。我说的是最本质的那个东西:能算数、能画画、能放音乐、能响应你的键盘和鼠标。

答案可能会让你意外:一个数组就够了。

Little Virtual Computer 是一台用 TypeScript 写的虚拟计算机,原作者是 jsdf。我在他的基础上做了不少重构和优化——把代码拆分成了清晰的模块结构,加了音频系统、断点调试、内存追踪、中英文切换等功能。3100 个内存槽位,23 条指令,你可以在上面写汇编程序,画像素,甚至播放一首 Chocolate Rain。打开链接就能玩,不用装任何东西。

接下来聊聊拆解和重构这台计算机的过程中,那些让我觉得"原来如此"的时刻。

"硬件"就是一行代码

这台计算机的内存,就是这行:

static ram: number[] = new Array(3100).fill(0)

3100 个数字,所有东西都住在里面——变量、程序、输入设备、屏幕、声卡:

地址 用途
0 - 999 工作内存(变量)
1000 - 1999 程序代码
2000 - 2051 键盘、鼠标、随机数、时钟
2100 - 2999 屏幕(30x30 像素)
3000 - 3008 声卡(3 个通道)

这就是"内存映射 I/O"。真实计算机里,显卡有自己的显存,声卡有自己的缓冲区,键盘通过中断传递信号。但在这里,一切都是内存地址。想在屏幕左上角画一个红色像素?往地址 2100 写个 2。想让扬声器发出正弦波?往地址 3001 写频率,地址 3000 写 3

第一次把重构后的代码跑起来,盯着屏幕上亮起的那个像素,我突然理解了一件事:CPU 不需要"知道"什么是屏幕。它只是往一个地址写了个数字,恰好有人在监听那个地址。 输入输出不需要特殊的指令,读写内存就是一切。

CPU 其实在做一件很无聊的事

读原作者的 CPU 代码时,我以为会很复杂。结果核心逻辑是这样的:

static step(trace: boolean = true) {
  if (trace) Memory.beginTrace();       // 需要调试时才追踪
  const opcode = this.advanceProgramCounter();  // 从内存读一个数
  const instructionName = this.opcodesToInstructions.get(opcode); // 数字变指令名
  const operands = instruction.operands.map(() => this.advanceProgramCounter()); // 再读几个数当参数
  instruction.execute.apply(null, operands);  // 执行
  if (trace) this.lastStepTrace = Memory.endTrace();
}

程序计数器从地址 1000 开始。读一个数,往前走一步。读到 9010?那是 add,再读三个数当参数,加一下,写回去。然后继续读下一个。没有流水线,没有分支预测,没有乱序执行。一个 while 循环,一直读数字、执行、读数字、执行。

这就是冯·诺依曼架构的全部:程序和数据住在同一片内存里,CPU 按顺序取指令执行。 你桌上那台电脑的 CPU,不管它有多少核、多少级缓存,本质上也在做同样的事——只是快了几十亿倍。

23 条指令够写一个游戏吗

一开始觉得不够。23 条指令,连函数调用都没有,能干什么?

结果发现,不只是够了,还能写出让人意外的东西。这 23 条指令分成五类:

搬运数据(5 条)—— 把值从一个地址复制到另一个,或者写入一个常量。还有两条指针操作,让你可以"地址 A 里存着地址 B,去 B 里取值"——间接寻址,这是实现数组遍历的关键。

算术(10 条)—— 加减乘除取模,每种都有两个版本:两个地址相加,或者一个地址加一个常量。add_constant counter 1 counter 就是 counter++

比较(2 条)—— 比较两个值,结果是 -1、0 或 1。没有布尔值,没有大于小于等于,就一个三态数字。刚开始觉得别扭,后来发现这样反而更灵活。

跳转(5 条)—— jump_to 无条件跳转,branch_if_equal 条件跳转。没有 for 循环?跳回去就是循环。没有 if-else?跳过去就是 else。

系统(3 条)—— data 嵌入原始数据,break 暂停调试,halt 终止。

用这些东西,能写出画板程序、弹球、乒乓球游戏,甚至音乐播放器。

从文本到数字

手动往内存里填操作码太痛苦了,所以需要一个汇编器。你写这样的文本:

define counter 0
define limit 10
copy_to_from_constant counter 0
Loop:
  add_constant counter 1 counter
  branch_if_not_equal_constant counter limit Loop
halt

汇编器把它变成内存里的一串数字:9001 0 0 9011 0 1 0 9104 0 10 1003 9999

过程本身很有启发性。define 给地址起名字,Loop: 标记跳转目标。汇编器用经典的两遍扫描:第一遍收集所有标签的地址(这样你可以先 jump_to SomeLabel,后面再定义 SomeLabel:),第二遍把指令名替换成操作码,把标签和变量名替换成数字,逐个写入程序内存。

所谓"编译",最原始的形态就是这样——把人能读的东西翻译成机器能读的数字。

900 个像素的屏幕

30x30,900 个像素。听起来少得可怜。

但当你亲手用汇编一个像素一个像素地画出一个弹跳的小球时,你会对"像素"这个词产生全新的理解。每个像素就是一个内存地址,颜色就是 0 到 15 的一个数字。像素地址 = 2100 + y * 30 + x。16 种颜色:黑、白、红、绿、蓝、黄、青、品红、银、灰、栗、橄榄、深绿、紫、蓝绿、海军蓝。

渲染做了分场景优化:慢放模式下追踪"脏像素",只更新被写过的像素,被写入的像素还会短暂闪白,让你看到程序正在画什么——慢放下看着像素一个一个亮起来,有种看延时摄影的感觉。全速模式则跳过逐像素追踪,直接全量重绘,因为每帧都有大量像素变化,追踪反而是浪费。

用内存地址弹钢琴

音频部分是我最喜欢的设计。三个独立的振荡器通道,每个通道就是三个连续的内存地址:波形、频率、音量。

地址 3000: 波形 (0=方波, 1=锯齿波, 2=三角波, 3=正弦波)
地址 3001: 频率 (值 / 1000 = Hz)
地址 3002: 音量 (0-100)

往这几个地址写数字,声音就出来了。改个数字,音调就变了。

内置的 ChocolateRain 程序用两个通道演奏了一首完整的曲子。音乐数据全部用 data 指令嵌入在程序里——本质上就是一个大数组,记录着"第几拍、哪个通道、什么频率、多大音量"。程序读取当前时间,算出现在是第几拍,然后去数组里找对应的音符,写入音频内存。

一首歌,就是一个按时间索引的数组。

调试器:这才是重点

说实话,这台虚拟计算机最有价值的部分不是 CPU,不是显示器,不是音频——是调试器。

点"单步",程序计数器往前走一步。你能看到它读了哪个地址(蓝色高亮),写了哪个地址(橙色高亮)。设个断点,程序跑到那里自动停下来。把速度拉到慢放,看着弹球程序一帧一帧地擦掉旧位置、算出新位置、画上新像素。

我见过很多人学编程时卡在"不知道程序在干什么"。代码写完,跑起来,结果不对,然后就懵了。这台计算机的调试器让一切都暴露在外面:每一步读了什么、写了什么、程序计数器在哪里。没有黑箱,没有抽象层,你看到的就是全部。

六个程序,六种"原来如此"

内置的六个示例程序,每个都在教一件事:

Add —— 4 + 4 = 8。三行代码,结果存在地址 2。这是"指令怎么工作"的最小演示。

RandomPixels —— 用一个指针从地址 2100 扫到 2999,每个位置写一个随机颜色,然后从头再来。满屏闪烁的彩色像素,其实只是一个循环在往内存里写数字。

Paint —— 屏幕顶部一行是 16 色调色板,点击选色,然后在画布上画。鼠标位置就是一个内存地址里的数字,点击就是另一个地址从 0 变成 1。

BouncingBall —— 白色小球弹来弹去。用 Date.now() 控制帧率,每 60ms 更新一次位置,碰到边界就反转方向。这是"游戏循环"的最小实现。

MiniPong —— 乒乓球。两个挡板,一个球,碰到挡板反弹,错过就重置。这是最复杂的示例,用到了几乎所有指令。读完它的代码,你会对"游戏不过是一堆条件判断"有切身体会。

ChocolateRain —— 用汇编写的音乐播放器。理解这个程序怎么工作,就理解了数据驱动编程的本质。

重构与实现细节

原作者 jsdf 的实现是一个完整的单体,功能齐全但耦合度较高。我把它拆成了独立模块——CPU、内存、显示器、音频、输入、汇编器——通过内存这个"总线"连接,加了 TypeScript 类型系统。

拆的过程本身就是一次学习。当你必须决定"这个职责属于 CPU 还是属于 Memory"的时候,你对计算机架构的理解会变得非常具体。

架构

项目分成两个独立的 bundle:

src/index.ts    → dist/computer.module.js   (核心计算机)
src/simulator.ts → dist/simulator.module.js  (模拟器 UI)

index.ts 初始化所有硬件组件,返回一个 Computer 接口对象——这是两层之间唯一的契约。模拟器只通过这个接口操作计算机,不直接碰内部类。换掉整个计算机实现,只要接口不变,模拟器照常工作。

几个有意思的实现决策

内存布局用 const enum——MemoryPosition 定义所有地址常量,编译后直接内联为数字,零运行时开销。改一个数字,整台计算机的内存布局就变了。这就是"硬件规格"。

指令是数据驱动的——每条指令是一个对象,包含名称、操作码、操作数描述和执行函数。operands 数组不只是文档——汇编器用它验证操作数数量,调试器用它显示操作数含义。一份数据,三个用途。

流程控制指令直接改程序计数器——jump_to 的 execute 就是 CPU.programCounter = labelAddress。这形成了循环依赖(CPU → instructions → CPU),更"干净"的做法是把 CPU 状态作为参数传入,但在这个规模的项目里,简单直接比架构纯洁更重要。

性能:在不同场景下做不同的事

性能优化的核心思路不是"让代码更快",而是"在不同场景下做不同的事"——和真实系统的优化思路一样。

全速模式用帧预算策略:用 performance.now() 在每帧 14ms 的预算内尽量多跑 CPU 周期(留 2ms 给浏览器渲染和 GC),用 requestAnimationFrame 和屏幕刷新率同步。同时跳过内存追踪和调试面板更新,显示器切换到全量重绘。

慢放模式每次只执行一条指令,开启内存读写追踪,更新所有调试面板,显示器用脏像素增量重绘。

音频也做了状态缓存——用 state 对象记录上一次的参数值,只在值真正变化时才调用 Web Audio API,避免每帧 9 次无意义的 API 调用。CPU 停止时只需静音所有通道然后立即返回。

其他细节:内存重置用 Array.fill(0) 替代 for 循环;endTrace() 复用同一个对象避免每周期分配新数组;显示器用预计算的 Uint8Array 颜色查找表,位移 << 2 代替乘法索引;程序内存视图用虚拟滚动,只渲染可见区域 ± 10 行。

最后

折腾这台计算机的过程中,我反复体会到一件事:我们日常使用的那些抽象——变量、循环、函数、屏幕、声音——在最底层都是同一个东西:往一个地址读一个数字,或者写一个数字。

3100 个数字,23 条规则。这就是一台计算机的全部。

不信的话,打开试试:wsafight.github.io/little-virt…

点"单步",看看你的程序在做什么。


原项目:github.com/jsdf/little…

重构版源码:github.com/wsafight/li…

每日一题-区间乘法查询后的异或 I🟡

2026年4月8日 00:00

给你一个长度为 n 的整数数组 nums 和一个大小为 q 的二维整数数组 queries,其中 queries[i] = [li, ri, ki, vi]

对于每个查询,按以下步骤执行操作:

  • 设定 idx = li
  • idx <= ri 时:
    • 更新:nums[idx] = (nums[idx] * vi) % (109 + 7)
    • idx += ki

在处理完所有查询后,返回数组 nums 中所有元素的 按位异或 结果。

 

示例 1:

输入: nums = [1,1,1], queries = [[0,2,1,4]]

输出: 4

解释:

  • 唯一的查询 [0, 2, 1, 4] 将下标 0 到下标 2 的每个元素乘以 4。
  • 数组从 [1, 1, 1] 变为 [4, 4, 4]
  • 所有元素的异或为 4 ^ 4 ^ 4 = 4

示例 2:

输入: nums = [2,3,1,5,4], queries = [[1,4,2,3],[0,2,1,2]]

输出: 31

解释:

  • 第一个查询 [1, 4, 2, 3] 将下标 1 和 3 的元素乘以 3,数组变为 [2, 9, 1, 15, 4]
  • 第二个查询 [0, 2, 1, 2] 将下标 0、1 和 2 的元素乘以 2,数组变为 [4, 18, 2, 15, 4]
  • 所有元素的异或为 4 ^ 18 ^ 2 ^ 15 ^ 4 = 31

 

提示:

  • 1 <= n == nums.length <= 103
  • 1 <= nums[i] <= 109
  • 1 <= q == queries.length <= 103
  • queries[i] = [li, ri, ki, vi]
  • 0 <= li <= ri < n
  • 1 <= ki <= n
  • 1 <= vi <= 105

我用 AI 撸了个开源"万能预览器":浏览器直接打开 Office、CAD 和 3D 模型

作者 徐小夕
2026年4月7日 23:07

最近一直在深耕 AI Agent 与大模型应用,比如 JitKnow AI 知识库、JitWord协同AI文档、Pxcharts 超级表格,同时也持续在给大家分享 GitHub 上真正能落地、能解决实际问题的优质AI开源项目。

两周前发布了我们开源的文档预览SDK——jit-viewer。

图片

目前在npm上已有 2.1k 的下载量,我们也在持续更新迭代,满足更多开发者的需求。

github:github.com/jitOffice/j…

国内镜像:gitee.com/lowcode-chi…

在 AI Coding的帮助下,我加速了迭代频率,今天很高兴和大家分享Jit-Viewer最新版本 V1.3.0.

什么是 Jit-Viewer

图片

简单来说,它是一个纯前端的文件预览引擎。不需要后端转换服务,不需要安装任何插件,几行代码就能让浏览器具备"专业软件"的预览能力。

图片

过去我们 preview 文件,要么调用微软/Google 的在线接口(有隐私风险),要么自建转换服务(服务器成本高)。

jit-viewer 的思路很直接:把解析能力搬到浏览器端。下面我就和大家分享一下最新版本的更新内容。

1. 支持CAD文件预览功能

图片

事情的起因很简单:工程团队在处理设计稿交付时,总是要在微信里发"麻烦安装个 CAD 看图软件"或者"这个 3D 模型我截图给你"。

作为一位写过无数款文档编辑器、多维表格的开发者,我突然意识到——为什么我们不能在浏览器里直接预览这些文件?

没有安装包,没有兼容性问题,打开链接就能看。这不应该是 2026 年的标配吗?

于是借助 AI, 我在 Jit-viewer sdk中支持了CAD文件的预览。

目前线上已提供demo测试,大家也可以体验测试一下。

2. 支持3D文件预览功能

图片

3D模型预览我们开放了很多能力,比如自动旋转3D模型,对3D模型进行旋转,截图,环境渲染器配置等,基本上开箱即用,开发者不需要关注复杂的3D空间知识,只需要按照我们api文档提供的信息配置,即可实现专业的3D模型预览功能。比如你想在web系统中预览3D商品图,手动调整模型渲染方式,都是用轻松用Jit-Viewer 来实现。大家另一个比较关注的问题可能是性能问题,这里我也做了性能优化的方案:

  1. WebAssembly 承担重计算:CAD 的几何解析、3D 模型的三角化都在 WASM 中完成,避免阻塞主线程
  2. 流式加载:大模型支持 LOD(细节层次)加载,先展示低精度轮廓,再逐步细化
  3. Worker 多线程:解析和渲染分离,UI 永不卡顿

3. 视频预览支持完全可控的视频播放控件

图片

我基本上重写了视频播放器,隐藏了video原生的视频播放控件,利用js api,重写了一个完全可控的视频播放 API 接口。

大家可以通过编程式来控制视频的播放,同时还能配置式控制播放控件的显示逻辑:

图片

那么最近的迭代,有哪些应用场景呢?

jit-viewer 不只是用来"打开文件",在我们实际业务中,它解决了几个实际的痛点:

场景 1:设计评审系统

  • 设计师上传 CAD 图纸,产品经理和开发直接在浏览器标注尺寸,无需安装 AutoCAD
  • 支持测量工具(距离、角度、面积),数据实时同步到多维表格

场景 2:3D 电商展示

  • 用户上传 3D 模型,自动生成 360° 预览,替代传统的图片轮播
  • 支持爆炸图动画,展示产品内部结构

场景 3:BIM 轻量化查看

  • 建筑信息模型在浏览器端轻量化展示,现场工程师用手机就能查看管线碰撞

场景 4:制造业协同

  • 供应商和客户之间传递 3D 模型,不再担心"你用的 SolidWorks 版本和我不兼容"

优缺点分析(客观总结,方便大家参考评估)

✅ 优势:

  • 零服务端成本:纯前端方案,不需要维护昂贵的文件转换服务器
  • 隐私安全:文件不上云,本地解析,适合涉密图纸
  • 极致体验:打开即看,无需等待"转换中"的 loading
  • 插件化架构:按需加载,不用 CAD 功能就不加载 2MB 的 WASM 文件

❌ 局限:

  • 超大文件限制:超过 500MB 的 CAD 文件还是建议用桌面软件,浏览器内存有限
  • 复杂特性缺失:CAD 的图层编辑、3D 的复杂材质节点暂时不支持(仅预览)
  • 移动端性能:3D 模型在低端手机上帧率可能下降,建议开启简化模式

写在最后:独立开发者的 vibe coding 感悟

作为一个连续创业者,我越来越确信:AI 不是替代开发者,而是让独立开发者有了对抗大厂的武器。

jit-viewer 的 CAD 解析模块,如果让我手写 C++ 几何算法,可能需要半年。但在 AI 辅助下,我花了两周就把 OpenCascade 移植到了 WebAssembly。剩下的时间,我可以专注在产品设计和开发者体验上。

这也是我开源这个项目的初衷——降低技术门槛,让更多人能做出专业的工具

如果你在做 PLG(产品驱动增长)的 SaaS 工具,或者有文件预览的需求,欢迎试试 jit-viewer。

遇到问题直接提 Issue,我会亲自回复(没错,目前 issue 响应速度还在 2 小时内 ~)。

github:github.com/jitOffice/j…

国内镜像:gitee.com/lowcode-chi…

联手华为乾崑!五菱推出新品牌华境,首发大六座车长超 5 米 2

作者 李华
2026年4月7日 22:59

提到上汽通用五菱,大多数人的认知还停留在几万块的微型小车。在这个价位段他们确实少有对手,但在眼下的新能源车市,十万以内的基本盘已经撑不起一家老牌车企的利润增长。

为了留在主牌桌上,五菱必须往上走。

今天正式亮相的新品牌华境,就是他们酝酿已久的动作。

配合着首款中大型六座 SUV 华境 S 的首发,官方亮出了一份相当扎实的供应商名单。

这里面有宁德时代的增混电芯,有宝钢的一体化热成型钢,还有福耀专门定制的光学玻璃,再加上华为乾崑的智能底座,这台车基本把目前行业里能拿到的尖端硬件都用上了。

这种满手名牌的做法,很容易让人产生一种错觉。过去几年大家见惯了拿着大厂方案堆料要高价的套路,难免会对华境打上一个问号。

这个挂着宝骏徽标的华境,还是那个「为人民造车」的五菱吗?

先把硬件拉满,再谈家用

华境 S 是一台标准的中大型 SUV。5235 毫米的车长,加上 3105 毫米的轴距,即便放在中大型 SUV 阵营里也绝对算得上是大块头。

外观上没有很夸张的线条,车头一条贯穿式流光大灯,车尾配上极光之翼造型,整体气场走的是那种克制且沉稳的家用路线。

首发给出的四款车漆,墨玉黑、羊脂白、玛瑙灰和琥珀蓝,也都很符合中国家庭的审美习惯。

拉开车门,你能直观感受到大尺寸带来的空间红利。

这台车的「得房率」达到了 87.4%,横向空间利用率也有 77%。车内是标准的大六座布局,第二排做了独立座椅和纯平地板。为了解决一家人满载出行时的行李堆放问题,五菱在尾部挖出了一个 423 升的下沉式双层后备箱。

▲ 华境 S 的两种内饰颜色

用料也基本拉满了,座舱内部能摸到的地方大面积使用了软包材质,细节处还加了一些竹韵风格的饰件,主打一个温馨的居家氛围。

对于一台全家出行的主力车,乘坐质感直接决定了这台车的及格线。

为了把这台大车调好,华境自称找来了「大师级」的工程团队,经过多轮调校后,过弯精度提升了 25%,综合舒适性提升了 15%。

更实用的是防晕车系统。通过底盘和动力的协同控制,这台车在过弯时的侧倾晃动,以及加减速时的纵向俯仰,分别降低了 15% 和 30%。

发布会还强调了华境 S 的静谧性,哪怕跑到 120km/h 的高速路况,车内噪音依然能压在 60 分贝以内。

动力和能耗,是这台车的一道硬菜。

华境 S 整车基于 EEA 4.0 中央集成式电子电气架构打造,驱动部分用的是上汽通用五菱新一代高效集成电驱,单电机最大功率 200kW,极速可以跑上 190km/h。

作为一台插电混动车型,它的增程器热效率达到了 43.2%。得益于这套动力系统,这台五米多长、两吨多重的大六座 SUV,在 WLTC 工况下的亏电油耗只有百公里 5.98 升。

藏在底盘下面的,是宁德时代专门定制的磷酸铁锂超级增程电池。

这块神炼 3.0 电池最大的特点就是放电功率极大,达到了 280kW。即便在电量只剩 20% 的亏电状态下,它依然能输出超过 280kW 的大功率,相当于一台 3.0T V6 发动机,跑高速超车不会有那种明显的乏力感。

充电速度也跟上了现在的行业节奏,充 12 分钟就能增加一半的续航。

除了日常通勤的两驱版本,华境 S 还有一套智能适时四驱系统。

根据官方的测试数据,华境 S 在雪地这种低附着力路面上,满载 6 人的国标麋鹿测试能跑到 48km/h,还能完成 30% 坡度的蠕动爬坡。

车身骨架采用的是五纵十一横的底部结构和六环笼式框架,全车高强钢和铝合金的占比高达 85%。

车内标配了 9 气囊和 14 个独立腔体。其中侧气帘长度达到了 2.6 米,具备 6 秒的超长保压功能;前排中间也补上了一个双腔体远端气囊,用来防止主副驾在侧碰时发生二次伤害。

剩下的就是大家非常熟悉的华为全家桶了。

华境 S 搭载了华为乾崑 ADS Pro V4.1 版本的辅助驾驶系统,支持城区和高速领航辅助,配合 CAS 4.0 系统,全车具备 23 项主动安全能力。至于泊车功能,支持离车泊入和手机遥控,成功率做到了 99%。

座舱里面运行的自然是 HarmonySpace 鸿蒙系统。多音区语音识别、手机导航无缝流转、超级桌面这些华为的拿手好戏全都在。

为了保证网联体验,华为还掏出了通信技术的家底。官方表示,在隧道或者地库这种弱网环境,华境 S 的信号强度比行业平均水平高出 60%。车主手里那把带星闪技术的数字钥匙,定位精度也比传统的蓝牙钥匙提升了五倍。

硬件堆料很猛,参数也很漂亮,但这台车真正有意思的地方并不在这些表面数据上。

华境和它的「朋友圈」

下午和一位汽车供应链的朋友闲聊。

她刚好刷到了华境预热的海报,上面密密麻麻排满了宁德时代、博世、宝钢这些大厂的 logo。看到这套阵容,她吐槽道:

要是我们的客户能把我们的名字写上去就好了,他们只会在发布会上说「全栈自研」。

的确,现在许多车企开发布会的时候特别喜欢把全栈自研挂在嘴边。一台车里大量的底层硬件明明都是供应商出的力,但厂商为了立自己「技术狂人」的人设,往往会把真正的合作伙伴藏在幕后。

看着华境大大方方把供应商名字全亮出来,她觉得挺难得。

当然,华境这么干,也有着非常现实的考量。作为一个中大型新能源 SUV 市场的新玩家,他们现在最不需要的就是去硬拗自研的人设。

在这片早就卷成红海的市场里,把业内公认的大厂拉出来站台,是快速建立信任的最快路径。这也是他们在硬件参数上逼平、甚至超越头部竞品最有效的解法。

依靠五菱对供应链的议价权,用成熟的成本管控体系去消化高阶硬件的溢价,是华境敢于把价格打下来的逻辑支撑。

我知道,把一堆名牌零部件买回来拼在一起,很容易让人觉得华境只是个组装厂,所谓「都是供应商供的,你装什么孙子」对吧。

但事实是,车企间的供应链整合能力亦有差距,有些工程能力,是花钱买不来的。比如那颗激光雷达。

华境 S 搭载的华为 Limera 舱内激光视觉系统没有像其他车型那样,把激光雷达放在车顶,而是把它挪到座舱内部,避免雨雪遮挡的同时,还能降低整车风阻。

问题在于:前风挡的夹胶层存在一定曲率,激光穿透时会发生折射和信号散射。

为了解决这个问题,华境和福耀玻璃的研发团队联合开发了 300 多个日夜,专门定制了前挡光学玻璃,让激光信号在穿透玻璃后依然保持极高的准直性。

这种物理材料级别的联合研发,是一家简单的组装厂做不到的。

再比如生产制造环节。

高规格的零部件往往需要极高的装配精度去承接。根据公开资料,上汽通用五菱在「十四五」期间投入超百亿元人民币,打造了首创的 I²MS 智能岛制造体系。

这套系统把传统的长流水线解耦,拆解成了 16 个独立的智能岛。工厂里的 AGV 小车会根据具体订单,自主规划路径去不同的岛位进行定制化装配。

目前这个智能工厂的人工智能应用场景占比超过 75%,单台车的下线时间被压缩到了 24 秒;机械臂的装配精度控制在了 0.1 毫米的丝米级。

「万车如一,万里如一。」这是华境在发布会上喊出来的口号。

只有产品力足够优秀,华境才有底气去面对残酷的外部环境。

和鸿蒙智行一样,华为乾崑正在构建一个庞大的车企合作矩阵。广汽的启境已经亮相,东风的奕境很快就会进场。大家都在用同一套华为智能化底座,华境必须在朋友圈里找到自己的差异化优势。

广汽等传统大厂有着经营中高端市场的品牌积淀,他们大概率会去主攻那些更看重品牌质感的传统中产份额。华境则忠于一个口号:

人民需要什么,五菱就造什么。

五菱过去服务了中国最庞大的基层消费者,他们非常清楚这部分人群在消费升级时真正需要什么。

比起花哨的舒适性配置,这些家庭用户更看重第三排乘客的真实乘坐空间和安全防护水平。他们需要增混动力去彻底解决长途出行的里程焦虑,也需要一套靠谱的底盘去应对各种路况。

在整个华为乾崑生态里,华境承担的任务就是将高阶辅助驾驶和豪华安全配置普及到最广泛的家用市场。

只要后续给出的价格足够有诚意,这台车在目前的主流大空间 SUV 市场里会非常有杀伤力。

带轮子的都关注,欢迎交流。 邮箱:tanjiewen@ifanr.com

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

git commit

作者 nanfeiyan
2026年4月7日 22:16
# Git Commit Message 规范

## 1. 基本格式

```aiignore
<type>(<scope>): <subject>

<body>

<footer>
```

## 2. Type 类型

| 类型 | 描述 |
| --- | --- |
| feat | 新功能 (feature) |
| fix | 修复 bug |
| docs | 文档变更 |
| style | 代码格式变更(不影响代码运行) |
| refactor | 重构(既不是新增功能,也不是修复 bug 的代码变动) |
| test | 增加测试 |
| chore | 构建过程或辅助工具的变动 |
| perf | 性能优化 |
| ci | CI/CD 相关变更 |
| build | 构建系统或外部依赖变更 |
| revert | 撤销之前的 commit |

## 3. Scope 范围(可选)

表示 commit 影响的范围,如:

- `api`
- `ui`
- `auth`
- `database`
- `config`

## 4. Subject 主题

- 使用祈使句、现在时态
- 首字母小写
- 结尾不加句号
- 不超过 50 个字符

## 5. Body 正文(可选)

- 解释“是什么”和“为什么”,而不是“怎么做”
- 每行不超过 72 个字符
-`subject` 用空行分隔

## 6. Footer 页脚(可选)

- 记录 breaking changes
- 关闭 issues

## 7. 语言

- 尽量使用中文

## 8. 示例

### 简单示例

```text
feat: 添加用户登录功能
fix(auth): 修复密码验证逻辑错误
docs: 更新 API 文档
style: 格式化代码缩进
refactor(api): 重构用户服务接口
```

### 完整示例

```text
feat(shopping cart): 添加商品到购物车功能

用户现在可以通过点击“添加到购物车”按钮将商品添加到购物车。

这个功能包括:
- 商品数量选择
- 库存验证
- 价格计算

Closes #123
```

env Command in Linux: Show and Set Environment Variables

When you need to run a program with a different set of variables, test a script in a clean environment, or write a shebang line that works across systems, the env command is the right tool. It prints the current environment, sets or removes variables for a single command, and can even start a process with no inherited variables at all.

This guide covers how to use the env command with practical examples for everyday tasks.

env Syntax

txt
env [OPTIONS] [NAME=VALUE]... [COMMAND [ARGS]]

When called without arguments, env prints every environment variable in the current session, one per line. When followed by NAME=VALUE pairs and a command, it runs that command with the specified variables added or changed without affecting the current shell.

Print All Environment Variables

The simplest use of env is printing the full environment:

Terminal
env
output
SHELL=/bin/bash
USER=john
HOME=/home/john
LANG=en_US.UTF-8
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
TERM=xterm-256color
XDG_SESSION_TYPE=tty
EDITOR=vim

Each line is a KEY=value pair. This is identical to what printenv shows with no arguments. The difference between the two commands becomes clear when you start passing options: printenv is designed for inspecting variables, while env is designed for modifying the environment before launching a program.

Print Variables with NUL Separator

By default, env separates each variable with a newline character. If variable values contain newlines themselves, the output becomes ambiguous. The -0 (or --null) option ends each entry with a NUL byte instead:

Terminal
env -0 | tr '\0' '\n' | head -5

This is useful when piping environment data into tools that expect NUL-delimited input, such as xargs with its -0 flag.

Run a Command with Modified Variables

The most practical feature of env is launching a command with variables changed for that command only. Place NAME=VALUE pairs before the command:

Terminal
env LANG=C sort unsorted.txt

This runs sort with LANG set to C, which forces byte-order sorting regardless of the system locale. The current shell’s LANG value stays unchanged after the command finishes.

You can set multiple variables at once:

Terminal
env DB_HOST=localhost DB_PORT=5432 python3 app.py

The application sees DB_HOST and DB_PORT in its environment, but those variables do not persist in your shell session after the process exits.

Tip
You can achieve the same result with the shell’s built-in VAR=value command syntax (for example, LANG=C sort unsorted.txt). The env command is useful when you need additional options like -i or -u, or when you want to be explicit about environment manipulation in scripts.

Run a Command in a Clean Environment

The -i (or --ignore-environment) option clears the entire inherited environment before running the command. Only the variables you explicitly set on the command line will be present:

Terminal
env -i bash -c 'env'
output
PWD=/home/john
SHLVL=1
_=/usr/bin/env

The output shows almost nothing. The shell itself sets a few internal variables (PWD, SHLVL, _), but everything the parent shell normally passes along (PATH, HOME, LANG, USER) is gone.

This is useful for testing whether a script depends on variables it does not set itself. You can combine -i with explicit variables to create a minimal, controlled environment:

Terminal
env -i HOME=/home/john PATH=/usr/bin:/bin bash -c 'echo $HOME; echo $PATH'
output
/home/john
/usr/bin:/bin

A bare - (hyphen) works as a shorthand for -i:

Terminal
env - PATH=/usr/bin bash -c 'echo $PATH'

Unset a Variable for a Command

The -u (or --unset) option removes a specific variable from the environment before running the command:

Terminal
env -u EDITOR vim

This launches vim without the EDITOR variable in its environment. Other variables remain untouched. You can unset multiple variables by repeating -u:

Terminal
env -u LANG -u LC_ALL python3 script.py

The difference from -i is that -u is surgical: it removes only the named variables and leaves everything else in place.

Change Directory Before Running a Command

The -C (or --chdir) option changes the working directory before executing the command:

Terminal
env -C /var/log cat syslog | head -3

This is equivalent to running cd /var/log && cat syslog, but without affecting the current shell’s working directory. It is a convenient way to run a command in another directory from within a script or one-liner.

Info
The -C option requires GNU coreutils 8.28 or later. Check your version with env --version.

Using env in Shebangs

One of the most common uses of env is in shebang lines at the top of scripts:

sh
#!/usr/bin/env bash
echo "Hello from Bash"

When the kernel encounters #!/usr/bin/env bash, it runs /usr/bin/env with bash as its argument. env then searches the PATH for the bash executable and runs it. This is more portable than hardcoding #!/bin/bash, because bash is not always located in /bin on every system (for example, on FreeBSD it is typically at /usr/local/bin/bash).

The same pattern works for other interpreters:

sh
#!/usr/bin/env python3
sh
#!/usr/bin/env node

On systems with GNU coreutils 8.30 or later, you can pass arguments to the interpreter using the -S (split string) option:

sh
#!/usr/bin/env -S python3 -u

Without -S, the kernel treats python3 -u as a single argument. The -S option tells env to split the string into separate arguments before executing.

env Options

  • -i, --ignore-environment - Start with an empty environment
  • -u NAME, --unset=NAME - Remove NAME from the environment
  • -C DIR, --chdir=DIR - Change working directory to DIR before running the command
  • -0, --null - End each output line with a NUL byte instead of a newline
  • -S STRING, --split-string=STRING - Split STRING into separate arguments (useful in shebangs)
  • -v, --debug - Print verbose information for each processing step
  • --block-signal=SIG - Block delivery of the specified signal to the command
  • --default-signal=SIG - Reset signal handling to the default for the command
  • --ignore-signal=SIG - Set signal handling to ignore for the command

Quick Reference

For a printable quick reference, see the env cheatsheet .

Command Description
env Print all environment variables
env -0 Print variables with NUL separator
env VAR=value command Run a command with a modified variable
env -i command Run a command in a clean environment
env -i VAR=value command Run a command with only the specified variables
env -u VAR command Run a command with a variable removed
env -C /path command Run a command in a different directory
#!/usr/bin/env bash Portable shebang line
#!/usr/bin/env -S python3 -u Shebang with interpreter arguments

FAQ

What is the difference between env and printenv?
Both commands print environment variables when called without arguments. The difference is in their purpose: printenv is a read-only inspection tool that can print individual variables by name (printenv HOME). env is designed to modify the environment and run commands. Use printenv when you need to check a value, and env when you need to change the environment for a process.

What is the difference between env and export?
export is a shell built-in that adds a variable to the current shell’s environment permanently (until the session ends or you unset it). env sets variables only for the duration of a single command and does not affect the current shell. If you need a variable to persist for all subsequent commands in your session, use export. If you need a variable set for one command only, use env or the shell’s VAR=value command syntax.

When should I use env -i?
Use env -i when you want to verify that a script or program works without relying on inherited environment variables. It is also useful in security-sensitive contexts where you want to prevent a child process from seeing variables like AWS_SECRET_ACCESS_KEY or database credentials that exist in the parent shell.

Why use #!/usr/bin/env bash instead of #!/bin/bash?
The env-based shebang is more portable. On most Linux distributions, bash lives at /bin/bash, but on other Unix systems (FreeBSD, macOS with Homebrew, NixOS) it may be installed elsewhere. Using #!/usr/bin/env bash searches the PATH for the interpreter, so the script works regardless of where bash is installed.

Conclusion

The env command gives you fine-grained control over the environment a process sees without touching your current shell session. For a broader look at how environment and shell variables work in Linux, including persistent configuration and the PATH variable, see the guide on how to set and list environment variables .

模拟

作者 tsreaper
2025年8月18日 12:43

解法:模拟

数据范围较小,模拟即可。复杂度 $\mathcal{O}(nq)$。

参考代码(c++)

class Solution {
public:
    int xorAfterQueries(vector<int>& nums, vector<vector<int>>& queries) {
        int n = nums.size();
        long long A[n];
        for (int i = 0; i < n; i++) A[i] = nums[i];

        const int MOD = 1e9 + 7;
        for (auto &qry : queries) for (int i = qry[0]; i <= qry[1]; i += qry[2]) A[i] = A[i] * qry[3] % MOD;
        
        long long ans = 0;
        for (int i = 0; i < n; i++) ans ^= A[i];
        return ans;
    }
};
昨天 — 2026年4月7日首页

赛微电子:参股子公司瑞典Silex拟在瑞典纳斯达克IPO

2026年4月7日 20:46
36氪获悉,赛微电子公告,公司召开董事会审议通过《关于参股子公司瑞典Silex拟境外上市的议案》。公司及全资子公司合计持有瑞典Silex45.24%股份。为促进其业务开拓及长远发展,拟推动瑞典Silex在瑞典NasdaqStockholmIPO上市。公司授权董事长或总经理办理开设账户、签署协议等事宜,授权期限12个月。该事项不构成关联交易及重大资产重组,无需提交股东会审议。

伯恩斯坦:iPhone 17系列需求强劲,2月全球销量大涨26%

2026年4月7日 20:43
伯恩斯坦分析师周一在报告中���示,得益于iPhone 17系列的热销,苹果公司的智能手机产品iPhone上月继续保持强劲的销售势头。从去年9月至今年1月这一时间段来看,iPhone 17的整体销量较上一财年同期的iPhone 16增长了20%。全球iPhone的“出货销售”(即从零售商卖给终端消费者的销量)在2月份同比增长了26%。(财联社)

亿纬锂能:拟合计投资110亿元建两家储能(动力)电池基地

2026年4月7日 20:40
36氪获悉,亿纬锂能公告,公司第七届董事会第九次会议审议通过多项议案。一是拟对子公司亿纬匈牙利提供不超过等值欧元4,200万元的担保,该事项尚需提交股东会审议。二是拟与启东市政府签订协议,投资建设50GWh储能(动力)电池生产基地,项目总投资约50亿元。三是拟与上杭县政府签订协议,投资建设60GWh储能电池生产基地,项目计划总投资约60亿元。四是拟与龙净环保签订合资协议,共同设立合资公司开展相关业务。上述投资及合资事项均尚需提交公司股东会审议。

南都电源:股东朱保义拟合计减持不超1.58%股份

2026年4月7日 20:15
36氪获悉,南都电源公告,董事长兼总经理朱保义因个人资金需求,计划于2026年4月29日至2026年7月28日期间,通过集中竞价及大宗交易方式减持公司股份不超过1405万股,占公司总股本的1.5808%。股份来源为非公开发行及董高增持计划。

亿纬锂能:2026年一季度净利同比预增25%-35%

2026年4月7日 20:08
36氪获悉,亿纬锂能公告,预计2026年第一季度归属于上市公司股东的净利润为13.76亿元-14.87亿元,比上年同期增长25.00%-35.00%。业绩变动主要原因是:公司坚持产品迭代与服务升级,把握市场机遇驱动业务增长;同时通过供应链多元化及战略性采购等前置管理措施,有效缓冲材料成本上涨波动,确保主营业务盈利能力稳定。

煌上煌:股东新余煌上煌拟减持不超3%公司股份

2026年4月7日 20:03
36氪获悉,煌上煌公告,控股股东的一致行动人新余煌上煌因自身资金需求,计划自公告披露之日起15个交易日后的3个月内,通过集中竞价交易及大宗交易方式减持公司股份,合计减持数量不超过16,786,000股,占公司总股本的比例不超过3%,其中,通过大宗交易的方式减持股份数量不超过11,191,000股,占公司总股本的比例不超过2%;通过集中竞价交易方式减持股份数量不超过5,595,000股,占公司总股本的比例不超过1%。
❌
❌