当前位置: 首页 > article >正文

【1.8w字深入解析】从依赖地狱到依赖天堂:pnpm 如何革新前端包管理?

目录

  • 前言
  • npm 的诞生与发展
  • 嵌套依赖模型存在的问题
  • npm3架构与yarn
  • Yarn 的诞生与局限
    • Yarn 的诞生背景
    • Yarn 仍然存在的问题
  • 何为幽灵依赖
  • 依赖结构的不确定性
  • pnpm王牌登场 -- 网状+平铺结构
    • 安装包速度快
    • 依赖管理
    • 软链接 和 硬链接 机制
  • 幽灵依赖产生的根本原因
    • 包管理工具的依赖解析机制
    • 第三方库历史问题
    • JavaScript 模块解析策略
  • pnpm 项目的依赖治理方案
    • 冗余依赖治理
    • 重叠依赖治理
  • 最后

前言

在现代前端开发中,高效的包管理和依赖治理对于项目的健康发展至关重要。随着项目规模的不断扩大,传统的 npmyarn 在处理依赖关系时可能会遇到诸如依赖重复安装磁盘空间浪费依赖版本冲突等问题。而 pnpm(performant npm)作为新一代包管理工具,不仅显著提升了安装效率,还为项目依赖治理带来了全新的解决方案。

本文将深入探讨 pnpm 的核心特性及其在实际项目中的应用。我们将从以下几个方面展开:

  • 包管理工具的发展历程以及pnpm的独特优势
  • 产生幽灵依赖的根本原因探究
  • 什么是依赖结构的不确定性
  • pnpm 的工作原理及其相比 npm / yarn 的优势
  • 基于 pnpm 的依赖治理最佳实践

通过本文的介绍,希望能够帮助你更好地理解和使用 pnpm,建立起完善的依赖管理体系。(看完还不懂任你说!😘)

在这里插入图片描述


npm 的诞生与发展

在 Web 开发的早期阶段,JavaScript 代码主要以简单的脚本形式存在,开发者通常通过手动下载和管理代码库。随着 2009 年 Node.js 的诞生,JavaScript 开始在服务器端大放异彩,开发者对模块化开发和依赖管理的需求也随之增长。

在这样的背景下,Isaac Z. Schlueter 于 2010 年创建了 npm(Node Package Manager)。作为 Node.js 的标准包管理工具,npm 优雅地解决了模块安装版本管理依赖管理等问题。它引入了革命性的 node_modules 目录结构,允许每个项目维护自己的依赖,实现了依赖的局部安装,使得不同项目能够使用不同版本的包而不会产生冲突。

npm 的出现极大地促进了 JavaScript 生态系统的发展。开发者可以轻松地发布、共享和复用代码,这导致了开源社区的蓬勃发展。截至目前,npm 注册表已经成为世界上最大的软件注册库,拥有超过 200 万个包,每周下载量超过 350 亿次。

在这里插入图片描述

然而,随着项目规模的扩大和依赖数量的增加,npm 的一些固有问题开始显现:

  1. node_modules 体积膨胀:由于依赖嵌套和重复安装,一个简单的项目可能产生数百兆的 node_modules 目录
  2. 安装效率低下:重复的依赖下载和磁盘写入操作导致安装速度慢
  3. 依赖结构复杂:扁平化算法可能导致依赖关系难以预测
  4. 磁盘空间浪费:相同的依赖包在不同项目中重复存储

这些问题推动了包管理工具的进一步发展,催生了 Yarn(2016)和 pnpm(2017)等新一代包管理工具的诞生。


嵌套依赖模型存在的问题

在 npm2 及以前,每个包会将其依赖安装在自己的 node_modules 目录下,这意味着每个依赖也会带上自己的依赖,形成一个嵌套的结构,结构如下::
在这里插入图片描述

假如嵌套的层数很深呢?

node_modules 
└─ 依赖A ├─ index.js ├─ package.json └─ node_modules └─ 依赖B ├─ index.js ├─ package.json└─ node_modules └─ 依赖C ├─ index.js ├─ package.json └─ node_modules └─ 依赖D ├─ index.js └─ package.json

可以发现,
这样的结构虽然解决了版本冲突、依赖隔离等问题,但却有几个致命的缺点:

  • 磁盘空间占用:每个依赖都会安装自己的依赖,导致了大量的重复,特别是在多个包共享同一依赖的场景下。
  • 深层嵌套问题:这种嵌套结构在文件系统中造成了非常长的路径,然而大多数 Windows 工具、实用程序和 shell 最多只能处理长达 260 个字符的文件和文件夹路径。一旦超过,安装脚本就会开始出错,而且无法再使用常规方法删除 node_modules 文件夹。相关 issue:github.com/nodejs/node…
  • 安装和更新缓慢:每次安装或更新依赖时,npm 需要处理和解析整个依赖树,过程非常缓慢。

npm3架构与yarn

为解决这些问题,npm 在第三个版本进行了重构:github.com/npm…

通过将依赖扁平化,尽可能地减少了重复的包版本,有效减少了项目的总体积,同时也避免了 npm 早期的深层嵌套问题。

在这里插入图片描述

扁平化结构如下:

在这里插入图片描述

代码结构:

node_modules 
└─ 依赖A  ├─ index.js ├─ package.json └─ node_modules 
└─ 依赖C   ├─ index.js ├─ package.json └─ node_modules 
└─ 依赖B ├─ index.js ├─ package.json └─ node_modules 

node_modules下所有的依赖都会平铺到同一层级。由于require寻找包的机制,如果A和C都依赖了B,那么A和C在自己的node_modules中未找到依赖C的时候会向上寻找,并最终在与他们同级的node_modules中找到依赖包C。 这样就不会出现重复下载的情况。而且依赖层级嵌套也不会太深。因为没有重复的下载,所有的A和C都会寻找并依赖于同一个B包。自然也就解决了实例无法共享数据的问题


Yarn 的诞生与局限

Yarn 的诞生背景

2016 年,Facebook 团队面对 npm 在大型项目中的种种问题,如安装不确定性、性能低下等,推出了新的包管理工具 Yarn(Yet Another Resource Negotiator)。Yarn 在发布之初就展现出了显著的优势:

  1. 确定性安装:通过 yarn.lock 文件确保了在不同环境下安装的依赖版本完全一致
  2. 并行下载:利用并行下载提升了安装速度
  3. 离线模式:引入缓存机制,支持离线安装
  4. 更好的命令行界面:提供了更友好的命令行交互体验

这些改进使得 Yarn 迅速获得了开发者的青睐,成为了 npm 的有力竞争者。


Yarn 仍然存在的问题

然而,Yarn 虽然解决了 npm 的一些问题,但在核心设计上仍然沿用了与 npm 相似的依赖管理模式,因此存在一些根本性问题:

  1. 依赖存储效率问题
  • 仍然采用扁平化的 node_modules 结构
  • 不同项目的相同依赖包会被重复存储,造成磁盘空间浪费
  • monorepo 项目中,即使使用 workspace 功能,依赖重复问题依然存在
  1. 幽灵依赖(Phantom Dependencies)
    {"dependencies": {"express": "4.17.1"  // express 依赖了 body-parser}
    }
    
    • 由于扁平化处理,项目可以直接使用未声明在 package.json 中的依赖
    • 这种隐式依赖可能导致潜在的问题和不可预测的行为

文章后面会详细补充什么是幽灵依赖以及如何解决~

  1. 依赖管理的不确定性
    • 扁平化算法的复杂性可能导致依赖树的结构难以预测
    • 不同的安装顺序可能产生不同的 node_modules 结构

文章后面会举case详细补充什么是依赖管理的不确定性

  1. 安装性能

    # 在大型项目中,即使使用缓存
    yarn install  # 仍然需要大量的文件复制操作
    
    • 虽然有并行下载,但文件复制和链接操作仍然耗时
    • 大型项目的首次安装和清理重装仍然较慢
  2. 磁盘空间占用

    • 即使是小型项目,node_modules 目录也可能占用数百 MB 空间
    • 对于维护多个项目的开发者来说,磁盘空间消耗巨大

这些问题的存在,促使开发社区继续探索更好的解决方案。pnpm 的出现,通过创新的依赖管理方式,为这些问题提供了更优的解决方案:

  • 采用内容寻址存储,通过硬链接共享依赖
  • 使用符号链接创建严格的依赖结构
  • 避免依赖重复安装和幽灵依赖
  • 显著减少磁盘空间占用

这使得 pnpm 在包管理工具的演进中代表了一个重要的技术突破,为前端工程化带来了新的可能。

最后在详细介绍pnpm之前,我来给大家演示一下幽灵依赖👻和依赖结构的不确定性(lock文件产生的原因)

何为幽灵依赖

由于这个扁平化结构的特点,想必大家都遇到了这样的体验,自己明明就只安装了一个依赖包,打开node_modules文件夹一看,里面却有一大堆。
例如我们在终端执行:

npm init -ynpm i express -S

这时候我们打开 node_modules 文件夹,你会惊奇的发现:
在这里插入图片描述

在这里插入图片描述

我明明只安装了 express,怎么 node_modules 下会出现那么多包?其实很简单,那是因为 express 依赖了一些包,而依赖的这些包又会依赖其它包…npm 则是把这些包拍平了放到了 node_modules 下,这也就导致 node_modules 里出现了这么多包

这就衍生了一个问题:

假设: 引入依赖a,a依赖又依赖于b,逻辑上则结构就应该是:

> -node_module/a 
> -node_module/a/node_module/b

但是在扁平化展开后则变成了:

> -node_module/a > -node_module/b

这样说那岂不是…😈

在这里插入图片描述

把安装express的时候自动下载的body-parser拿出来测试一下嘿嘿😈😈

在这里插入图片描述

import bd from "body-parser"; 
console.log(bd); //成功输出了

在这里插入图片描述

这会带来什么后果和隐患呢?

  • express 在未来版本中移除或更换 body-parser 依赖时,你的项目将意外破损(直接崩了)
  • 你无法控制 body-parser 的具体版本,完全依赖于 express 的依赖声明

依赖结构的不确定性

这个怎么理解,为什么会产生这种问题呢?我们来仔细想想,加入有如下一种依赖结构:

在这里插入图片描述

foo包与bar包同时依赖了base64-js包的不同版本,由于同一目录下不能出现两个同名文件,所以这种情况下同一层级只能存在一个版本的包,另外一个版本还是要被嵌套依赖。

那么问题又来了,既然是要一个扁平化一个嵌套,那么执行npm/yarn install 的时候,通过扁平化处理之后:

究竟是这样呢?

在这里插入图片描述

还是这样:

在这里插入图片描述

答案:这两种结构都有可能

在这里插入图片描述

准确点说哪个版本的包被提升,取决于包的安装顺序! 取决于 foo 和 bar 在 package.json中的位置,如果 foo 声明在前面,那么就是前面的结构,否则是后面的结构

这就是为什么会产生依赖结构的不确定问题,也是 lock 文件诞生的原因,无论是package-lock.json(npm 5.x 才出现)还是yarn.lock,都是为了保证 install 之后都产生确定的node_modules结构。

因此,npm/yarn 本身还是存在扁平化算法复杂package非法访问的问题,影响性能和安全。

pnpm王牌登场 – 网状+平铺结构

pnpm (performant npm) 是一个快速、节省磁盘空间的包管理工具。它于 2017 年发布,是 npm 的替代品,专注于解决传统包管理工具存在的问题。

就这么简单,说白了它跟npmyarn没有区别,都是包管理工具。但它的独特之处在于:

  • 包安装速度极快
  • 磁盘空间利用非常高效

安装包速度快

在这里插入图片描述

从上图可以看出,pnpm的包安装速度明显快于其它包管理工具。那么它为什么会比其它包管理工具快呢?

在这里插入图片描述

我们来可以来看一下各自的安装流程:

npm / yarn :

在这里插入图片描述

  1. resolving:首先他们会解析依赖树,决定要fetch哪些安装包。
  2. fetching:安装去fetch依赖的tar包。这个阶段可以同时下载多个,来增加速度。
  3. wrting:然后解压包,根据文件构建出真正的依赖树,这个阶段需要大量文件IO操作。

pnpm :

在这里插入图片描述

上图是pnpm的安装流程,可以看到针对每个包的三个流程都是平行的,并行处理所以速度当然会快很多。不过pnpm会多一个阶段,就是通过链接组织起真正的依赖树目录结构。

依赖管理

pnpm使用的是npm 2.x类似的嵌套结构,同时使用.pnpm 以平铺的形式储存着所有的包。然后使用Store + Links和文件资源进行关联。

简单说pnpm把会包下载到一个公共目录,如果某个依赖在 sotre 目录中存在了话,那么就会直接从 store 目录里面去 hard-link,避免了二次安装带来的时间消耗,如果依赖在 store 目录里面不存在的话,就会去下载一次。通过Store + hard link的方式,使得项目中不存在NPM依赖地狱问题,从而完美解决了npm3+yarn中的包重复问题。

在这里插入图片描述

我们分别用npmpnpm来安装vite对比看一下:

npmpnpm
在这里插入图片描述
在这里插入图片描述
所有依赖包平铺在node_modules目录,包括直接依赖包以及其他次级依赖包node_modules目录下只有.pnpm和直接依赖包,没有其他次级依赖包
没有符号链接(软链接)直接依赖包的后面有符号链接(软链接)的标识

软链接 和 硬链接 机制

硬链接:pnpm 通过使用全局的 .pnpm-store 来存储下载的包,使用硬链接来重用存储在全局存储中的包文件,这样不同项目中相同的包无需重复下载,节约磁盘空间。
在这里插入图片描述


软链接:pnpm 将各类包的不同版本平铺在 node_modules/.pnpm 下,对于那些需要构建的包,它使用符号链接连接到存储在项目中的实际位置。这种方式使得包的安装非常快速,并且节约磁盘空间。

在这里插入图片描述

举个例子,项目中依赖了 A,这时候可以通过创建软链接,在 node_modules 根目录下创建 A 软链指向了 node_modules/.pnpm/A/node_modules/A。此时如果 A 依赖 B,pnpm 同样会把 B 放置在 .pnpm 中,A 同样可以通过 软链接依赖到 B,避免了嵌套过深的情况。


依赖处理方式:依赖包 —(软链接)— > .pnpm ----(硬链接) —> 全局的 Store

我们使用刚刚的express来举个🌰:

执行pnpm install :

在这里插入图片描述

  1. 打开 node_modules 可以看到,确实不是扁平化的了,依赖了 express,那 node_modules 下就只有 express,没有幽灵依赖
  2. 同时下面还有个 .pnpm 文件夹,展开 .pnpm 后可以看到,所有的依赖都在这里铺平了

在这里插入图片描述

  1. 所有的依赖都是从全局 store 硬连接到了 node_modules/.pnpm 下,然后包和包之间的依赖关系是通过软链接组织的
  2. .pnpm是个一个虚拟store(Virtual store),里面的依赖包硬链接到真实Store(Content-addressable store)中,真实Store才是依赖包文件真正的存储位置
  3. package.json中的依赖(比如express)通过软链接,指向.pnpm下对应的依赖包
  4. 每次pnpm安装先检查Store,如果已经存在,直接通过硬链接的形式连接到.pnpm;如果不存在,则先下载,然后再硬链接

类似如下的🌰:

node_modules
└── A // symlink to .pnpm/A@1.0.0/node_modules/A
└── B // symlink to .pnpm/B@1.0.0/node_modules/B
└── .pnpm├── A@1.0.0│   └── node_modules│       └── A -> <store>/A│           ├── index.js│           └── package.json└── B@1.0.0└── node_modules└── B -> <store>/B├── index.js└── package.json

node_modules 中的 A 和 B 两个目录会软连接到 .pnpm 这个目录下的真实依赖中,而这些真实依赖则是通过 hard link 存储到全局的 store 目录中。

在这里插入图片描述


对于store,你应该记住:

pnpm下载的依赖全部都存储到store中去了,store是pnpm在硬盘上的公共存储空间。

pnpm的store在Mac/linux中默认会设置到{home dir}>/.pnpm-store/v3;windows下会设置到当前盘符的根目录下。使用名为 .pnpm-store的文件夹名称。

项目中所有.pnpm/依赖名@版本号/node_modules/下的软连接都会连接到pnpm的store中去。

幽灵依赖产生的根本原因

然而就算使用 pnpm,幽灵依赖还是难以根除,我们不妨分析一下幽灵依赖产生的根本原因。

包管理工具的依赖解析机制

这就是前面介绍的平铺式带来的问题,这边就不重复讲述了。

第三方库历史问题

由于历史原因或开发者的疏忽,有些项目可能没有正确地声明所有直接使用的依赖。对于三方依赖,幽灵依赖已经被当做了默认的一种功能来使用,提 issue 修复的话,周期很长,对此 pnpm 也没有任何办法,只能做出妥协。

下面是 pnpm 的处理方式:

  • 对直接依赖严格管理:对于项目的直接依赖,pnpm 保持严格的依赖隔离,确保项目只能访问到它在package.json 中声明的依赖。

  • 对间接依赖妥协处理:考虑到一些第三方库可能依赖于未直接声明的包(幽灵依赖),pnpm 默认启用了 hoist 配置。这个配置会将一些间接依赖提升(hoist)到一个特殊的目录 node_modules/.pnpm/node_modules中。这样做的目的是在保持依赖隔离的同时,允许某些特殊情况下的间接依赖被访问。

在这里插入图片描述

JavaScript 模块解析策略

Node.js 的模块解析策略允许从当前文件夹的 node_modules 开始,向上遍历文件系统,直到找到所需模块。

这种解析策略,虽然提供了灵活性,也使得幽灵依赖更容易产生,因为它允许模块加载那些未直接声明在项目package.json 中的依赖。

综合来看,幽灵依赖在目前是无法根除的,只能通过一些额外的处理进行管控,比如 eslint 对幽灵依赖的检查规则、pnpm 的 hoist 配置等。

pnpm 项目的依赖治理方案

对于依赖治理,大概涉及到以下几个部分:

  • 冗余依赖治理:例如遗留的未使用依赖重复声明的依赖过时的依赖版本,导致 package.json 愈发混乱。
  • 重叠依赖治理:例如 monorepo 项目中根目录和子项目的重复依赖,加大了 package.json 的管理成本,同一依赖的多个版本并存,依赖版本冲突。

冗余依赖治理

例如遗留的未使用依赖重复声明的依赖过时的依赖版本

对于冗余的情况,可以按照如下顺序检查:

  1. 执行 pnpm why <package-name>,用来找出项目中一个特定的包被谁所依赖,给出包的依赖来源。
  2. 全局搜索包名,检查是否有被引入。
  3. 了解包的作用,判断项目中是否存在包的引用。
  4. 删除包,执行 pnpm i 后,分别运行、打包项目,查看是否有明显问题。

按照顺序执行完毕后,仍然可能存在问题,这是没法完全避免的,可以进一步通过测试进行排查。

在这里插入图片描述

重叠依赖治理

对于 monorepo 而言,依赖的管理就比较复杂了,这边可以通过人肉+脚本的方式进行治理。

为方便识别重叠依赖,可以编写一个脚本,遍历子项目中的 package.json 将与根目录重叠的依赖进行输出:

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import chalk from 'chalk'; // 引入 chalk// 获取当前文件的目录路径,确保脚本可以在不同环境下正确执行
const __dirname = path.dirname(fileURLToPath(import.meta.url));// 修改后的读取 package.json 文件函数保持不变
function readPackageJson(filePath) {try {const jsonData = fs.readFileSync(filePath, 'utf8');return JSON.parse(jsonData);} catch (error) {console.error(`读取文件失败: ${filePath}`, error);return null;}
}// 修改后的比较依赖函数保持不变
function compareDependencies(rootDeps, childDeps, depType, childName) {const overlaps = [];for (const [dep, version] of Object.entries(childDeps)) {if (rootDeps[dep]) {const versionCompare = (rootDeps[dep] === version)// 如果子项目中的依赖在根目录中也存在,则记录下来overlaps.push(`${dep}: ${chalk.blueBright(version)} (在根目录中为: ${chalk.blueBright(rootDeps[dep])}) ${versionCompare ? chalk.green('✔') : chalk.red('✘')}`);}}return {overlaps: overlaps.length > 0 ? `${chalk.greenBright('- 重叠的',depType)}\n` + overlaps.join('\n') + '\n\n' : '',};
}function main() {const rootPackageJsonPath = path.join(__dirname, 'package.json');const rootPackageJson = readPackageJson(rootPackageJsonPath);if (!rootPackageJson) {console.error('无法读取根目录的 package.json 文件');return;}// 修改输出为终端输出,使用 chalk 增加颜色console.log(chalk.bold('📖 依赖分析报告\n'));const packagesDir = path.join(__dirname, 'packages');const childDirs = fs.readdirSync(packagesDir).filter(child => fs.statSync(path.join(packagesDir, child)).isDirectory());for (const child of childDirs) {const childPackageJsonPath = path.join(packagesDir, child, 'package.json');const childPackageJson = readPackageJson(childPackageJsonPath);if (childPackageJson) {console.log(chalk.bold(`🟢 子项目 ${child}`));['dependencies', 'devDependencies', 'peerDependencies'].forEach(depType => {const { overlaps } = compareDependencies(rootPackageJson[depType] || {},childPackageJson[depType] || {},depType,child);console.log(overlaps);});}}
}main();

核心流程就是先遍历所有子项目的 package.json ,然后通过compareDependencies方法检查子项目的依赖是否在根目录存在,然后对重叠的依赖进行版本一致性检查,最后对 分别处理不同类型的依赖(dependencies / devDependencies / peerDependencies

执行效果如下:

📖 依赖分析报告🟢 子项目 A
- 重叠的 dependencies
@babel/runtime-corejs3: ^7.14.0 (在根目录中为: ^7.14.0) ✔
……- 重叠的 devDependencies
@commitlint/cli: ^13.1.0 (在根目录中为: ^13.1.0) ✔
@commitlint/config-conventional: ^13.1.0 (在根目录中为: ^13.1.0) ✔
……🟢 子项目 B- 重叠的 devDependencies
typescript: ^4.4.0 (在根目录中为: ^4.3.5)zx: ^4.2.0 (在根目录中为: ^4.2.0)chalk: ^4.1.0 (在根目录中为: ^4.1.0)

通过这种方式我们就可以有目的性的去逐个检查依赖,依据一种合理的 monorepo 依赖管理模式进行处理,下面是一种合适的处理规则:

  • 将共享的开发时依赖移至根目录的 package.json,如 jest、eslint、lint-stage。
  • 对于需要特定版本以保证兼容性的依赖,考虑使用 resolutions 字段强制解析为特定版本。
  • 为需要发包的工具、类库提供 peerDependencies 字段。
  • 对于运行时依赖,如果所有子项目都有依赖,将删除子项目中的声明,提升至根目录,同时在需要发包的工具、类库的 peerDependencies 中声明相关的依赖。
  • 发包时,通过调用脚本将目标子项目中的 peerDependencies 内容转移至 dependicies

最后

虽然 pnpm 的优势非常明显,但目前 pnpm 的生态还在成长阶段,一些功能还没法在网络上找到最佳实践,这需要一定的时间去沉淀,但经过权衡,拥抱 pnpm 无疑是一个非常好的选择!

最后,如果这篇文章对你有帮助,可以给作者点赞关注支持一波~

在这里插入图片描述

参考资料:

https://pnpm.io/zh/blog/2020/05/27/flat-node-modules-is-not-the-only-way

https://juejin.cn/post/7358267939441950720#heading-25

https://juejin.cn/post/7053340250210795557#heading-4

相关文章:

【1.8w字深入解析】从依赖地狱到依赖天堂:pnpm 如何革新前端包管理?

目录 前言npm 的诞生与发展嵌套依赖模型存在的问题npm3架构与yarnYarn 的诞生与局限Yarn 的诞生背景Yarn 仍然存在的问题 何为幽灵依赖依赖结构的不确定性pnpm王牌登场 -- 网状平铺结构安装包速度快依赖管理软链接 和 硬链接 机制 幽灵依赖产生的根本原因包管理工具的依赖解析机…...

【ISO 14229-1:2023 UDS诊断(ECU复位0x11服务)测试用例CAPL代码全解析⑤】

ISO 14229-1:2023 UDS诊断【ECU复位0x11服务】_TestCase05 作者&#xff1a;车端域控测试工程师 更新日期&#xff1a;2025年02月17日 关键词&#xff1a;UDS诊断协议、ECU复位服务、0x11服务、ISO 14229-1:2023 TC11-005测试用例 用例ID测试场景验证要点参考条款预期结果TC…...

137,【4】 buuctf web [SCTF2019]Flag Shop

进入靶场 都点击看看 发现点击work会增加&#xffe5; 但肯定不能一直点下去 抓包看看 这看起来是一个 JWT&#xff08;JSON Web Token&#xff09;字符串。JWT 通常由三部分组成&#xff0c;通过点&#xff08;.&#xff09;分隔&#xff0c;分别是头部&#xff08;Header&…...

Node.js 异步并发控制:`p-map` 和 `p-limit` 的使用与对比

在 Node.js 中&#xff0c;处理异步任务是开发中非常常见的需求。无论是批量处理数据、调用外部 API&#xff0c;还是操作文件系统&#xff0c;我们经常需要对多个异步任务进行管理。然而&#xff0c;当任务数量较多时&#xff0c;如果不加以控制&#xff0c;并发可能会导致性能…...

【c++】c++内存管理

目录 c和c的内存分布回顾C语言动态管理内存的方式malloccallocreallocfree C动态管理内存的方式new和deleteoperator new和operator delete定位new c和c的内存分布 回顾C语言动态管理内存的方式 malloc void* malloc (size_t size);malloc可以在堆上开辟指定内存的空间&#…...

EtherNet/IP转Modbus TCP:新能源风电监控与分析实用案例

EtherNet/IP转Modbus TCP&#xff1a;新能源风电监控与分析实用案例 一、案例背景 在某新能源汽车电池生产线上&#xff0c;需要将采用EtherNet/IP协议的电池检测设备与采用ProfiNet协议的生产线控制系统进行集成&#xff0c;以实现对电池生产过程的全面监控和数据采集。 二、…...

伪装目标检测(Camouflaged Object Detection, COD)教程

1. 引言 伪装目标检测&#xff08;Camouflaged Object Detection, COD&#xff09;是一项计算机视觉任务&#xff0c;旨在识别和分割背景中难以察觉的目标&#xff0c;如动物伪装、隐形物体检测等。由于伪装目标通常与背景高度相似&#xff0c;这项任务比传统的目标检测更具挑…...

烧烤炉出口亚马逊欧盟站CE认证EN1860安全标准

什么是欧盟CE认证&#xff1a; 在欧盟市场“CE”标志属强制性认证标志&#xff0c;不论是欧盟内部企业生产的产品&#xff0c;还是其他国家生产的产品&#xff0c;要想在欧盟市场上自由流通&#xff0c;就必须加贴“CE”标志&#xff0c;以表明产品符合欧盟《技术协调与标准化新…...

动态DNS神器nip.io使用指南:快速实现域名与IP的动态映射--告别配置本地hosts

动态DNS神器nip.io使用指南&#xff1a;快速实现域名与IP的动态映射--告别配置本地hosts 一、项目简介二、快速入门三、进阶配置四、典型应用场景 本文基于开源项目 v1.2.1版本撰写&#xff0c;适用于开发测试、CI/CD等场景 一、项目简介 nip.io 是由Exentrique Solutions开发…...

人工智能 - 机器学习、深度学习、强化学习是人工智能领域的理论基础和方法论

机器学习、深度学习、强化学习是人工智能领域的三大核心方向,各自具有独特的理论基础和方法论。以下是它们的核心理论知识总结: 一、机器学习(Machine Learning, ML) 1. 基础概念 目标:通过数据驱动的方式,让机器从经验中学习规律,完成预测、分类或决策任务。 核心范式…...

数字电路-基础逻辑门实验

基础逻辑门是数字电路设计的核心元件&#xff0c;它们执行的是基本的逻辑运算。通过这些基本运算&#xff0c;可以构建出更为复杂的逻辑功能。常见的基础逻辑门包括与门&#xff08;AND&#xff09;、或门&#xff08;OR&#xff09;、非门&#xff08;NOT&#xff09;、异或门…...

国产编辑器EverEdit - 如虎添翼的功能:快速选择

1 快速选择 1.1 应用场景 快速选择适用于批量选择和修改的场景&#xff0c;比如&#xff1a;变量改名。 1.2 使用方法 1.2.1 逐项快速选择 将光标放置在单词前或单词中&#xff0c;选择主菜单查找 -> 快速选择 -> 快速选择或使用快捷键Ctrl D 注&#xff1a;光标放…...

国内外网络安全政策动态(2025年1月)

▶︎ 1.国家互联网信息办公室发布《个人信息出境个人信息保护认证办法&#xff08;征求意见稿&#xff09;》 1月3日&#xff0c;国家互联网信息办公室发布《个人信息出境个人信息保护认证办法&#xff08;征求意见稿&#xff09;》。根据《意见稿》&#xff0c;个人信息出境个…...

68页PDF | 数据安全总体解决方案:从数据管理方法论到落地实践的全方位指南(附下载)

一、前言 这份报告旨在应对数字化转型过程中数据安全面临的挑战&#xff0c;并提供全面的管理与技术体系建设框架。报告首先分析了数字化社会的发展背景&#xff0c;强调了数据安全在国家安全层面的重要性&#xff0c;并指出数据安全风险的来源和防护措施。接着&#xff0c;报…...

AI大模型的文本流如何持续吐到前端,实时通信的技术 SSE(Server-Sent Events) 认知

写在前面 没接触过 SSE&#xff08;Server-Sent Events&#xff09;&#xff0c;AI大模型出来之后&#xff0c;一直以为文本流是用 WebSocket 做的偶然看到返回到报文格式是 text/event-stream,所以简单认知&#xff0c;整理笔记博文内容涉及 SSE 认知&#xff0c;以及对应的 D…...

Electron:使用electron-react-boilerplate创建一个react + electron的项目

使用 electron-react-boilerplate git clone --depth 1 --branch main https://github.com/electron-react-boilerplate/electron-react-boilerplate.git your-project-name cd your-project-name npm install npm start 安装不成功 在根目录加上 .npmrc文件 内容为 electron_…...

Spring Boot三:Springboot自动装配原理

精心整理了最新的面试资料&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 原理初探 pom.xml 核心依赖在父工程中 spring-boot-dependencies所有的jar包都在这里管理 我们在写或者引入一些依赖的时候&#xff0c;不需要指定版本 启动器 <…...

【ISO 14229-1:2023 UDS诊断全量测试用例清单系列:第十八节】

ISO 14229-1:2023 UDS诊断服务测试用例全解析&#xff08;ResponseOnEvent_0x86服务&#xff09; 作者&#xff1a;车端域控测试工程师 更新日期&#xff1a;2025年02月14日 关键词&#xff1a;UDS协议、0x86服务、事件响应、ISO 14229-1:2023、ECU测试 一、服务功能概述 0x86…...

Qt 中使用 SQLite 数据库的完整指南

SQLite 是一款轻量级、嵌入式的关系型数据库&#xff0c;无需独立的服务器进程&#xff0c;数据以文件形式存储&#xff0c;非常适合桌面和移动端应用的本地数据管理。Qt 通过 Qt SQL 模块提供了对 SQLite 的原生支持&#xff0c;开发者可以轻松实现数据库的增删改查、事务处理…...

2024 年 CSDN 博客之星年度评选:技术创作与影响力的碰撞(统计时间2025-02-17 11:06:06)

摘要&#xff1a;在技术的海洋里&#xff0c;每一位博主都像是一座独特的灯塔&#xff0c;用自己创作的光芒照亮他人前行的道路。2024 年 CSDN 博客之星年度评选活动&#xff0c;正是对这些灯塔的一次盛大检阅&#xff0c;让我们看到了众多优秀博主在技术创作领域的卓越表现以及…...

Java零基础入门笔记:(3)程序控制

前言 本笔记是学习狂神的java教程&#xff0c;建议配合视频&#xff0c;学习体验更佳。 【狂神说Java】Java零基础学习视频通俗易懂_哔哩哔哩_bilibili Scanner对象 之前我们学的基本语法中我们并没有实现程序和人的交互&#xff0c;但是Java给我们提供了这样一个工具类&…...

后端生成二维码,前端请求接口生成二维码并展示,且多个参数后边的参数没有正常传输问题处理

一、后端代码 1、controller GetMapping("/generateQRCode/{url}")ApiOperation(value "生成url链接二维码",notes "生成url链接二维码")public JsonResult<NewsQRCodeVo> generateQRCode(PathVariable String url,HttpServletRespons…...

(8/100)每日小游戏平台系列

项目地址位于&#xff1a;小游戏导航 新增一个打地鼠游戏&#xff01; 打地鼠&#xff08;Whack-a-Mole&#xff09;是一款经典的休闲游戏&#xff0c;玩家需要点击随机出现的地鼠&#xff0c;以获取分数。游戏时间有限&#xff0c;玩家需要在规定时间内尽可能多地击中地鼠&am…...

【jar包启动命令简单分享】

最近在做springcloud项目&#xff0c;整理了下启停脚本 批量启动脚本 #!/bin/bashAPP_HOME/data/java/ APP_NAMES("ruoyi-auth.jar""ruoyi-gateway.jar""ruoyi-modules-file.jar""ruoyi-modules-gen.jar""ruoyi-modules-job.jar…...

[Python人工智能] 五十.PyTorch入门 (5)快速搭建神经网络及模型保存

从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。前文讲解PyTorch构建分类神经网络。这篇文章将介绍如何利用PyTorch快速构建神经网络,之前的代码比较复杂,通过自定义Net类实现,本文通过Torch函数定义神经网络。前面我们的Python人工智能主要以Tens…...

SpringBoot+Vue+数据可视化的动漫妆造服务平台(程序+论文+讲解+安装+调试+售后等)

感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;我会一一回复&#xff0c;希望帮助更多的人。 系统介绍 在当今数字化高速发展的时代&#xff0c;动漫产业迎来了前所未有的繁荣&#xff0c;动漫…...

Go入门之语言变量 常量介绍

func main(){var a int8 10var b int 5var c int 6fmt.Println("a", a, "b", b, "c", c)d : 10fmt.Printf("a%v leixing%T\n", d, d) } main函数是入口函数,fmt包有三个打印的函数Println&#xff0c;Print&#xff0c;Printf。第…...

基于web的留守儿童网站的设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…...

鸡兔同笼问题

鸡兔同笼问题是这样一个问题&#xff1a; 现有鸡、兔合装在一个笼子里。数头一共100个头&#xff0c;数脚一共300只脚。问有多少只鸡多少只兔&#xff1f; 在这里讨论这个问题的解法当然太小儿科了。但是y_tab这个C语言解释器只提供了1维数组。如果需要用到2维数组时&#xff…...

使用 Spring Boot 和 Canal 实现 MySQL 数据库同步

文章目录 前言一、背景二、Canal 简介三、主库数据库配置1.主库配置2.创建 Canal 用户并授予权限 四.配置 Canal Server1.Canal Server 配置文件2.启动 Canal Server 五.开发 Spring Boot 客户端1. 引入依赖2. 配置 Canal 客户端3. 实现数据同步逻辑 六.启动并测试七.注意事项八…...