【D3.js in Action 3 精译_037】4.1 DIY 实战:D3 源码分析之——d3.timeFormat() 函数

当前内容所在位置(可进入专栏查看其他译好的章节内容)

  • 第一部分 D3.js 基础知识
    • 第一章 D3.js 简介(已完结)
      • 1.1 何为 D3.js?
      • 1.2 D3 生态系统——入门须知
      • 1.3 数据可视化最佳实践(上)
      • 1.3 数据可视化最佳实践(下)
      • 1.4 本章小结
    • 第二章 DOM 的操作方法(已完结)
      • 2.1 第一个 D3 可视化图表
      • 2.2 环境准备
      • 2.3 用 D3 选中页面元素
      • 2.4 向选择集添加元素
      • 2.5 用 D3 设置与修改元素属性
      • 2.6 用 D3 设置与修改元素样式
      • 2.7 本章小结
    • 第三章 数据的处理(已完结)
      • 3.1 理解数据
      • 3.2 准备数据
      • 3.3 将数据绑定到 DOM 元素
        • 3.3.1 利用数据给 DOM 属性动态赋值
      • 3.4 让数据适应屏幕
        • 3.4.1 比例尺简介(上篇)
        • 3.4.2 线性比例尺(中篇)
          • 3.4.2.1 基于 Mocha 测试 D3 线性比例尺(DIY 实战)
        • 3.4.3 分段比例尺(下篇)
          • 3.4.3.1 使用 Observable 在线绘制 D3 条形图(DIY 实战)
      • 3.5 加注图表标签(上篇)
        • 3.5.1 人物专访:Krisztina Szűcs(下篇)
      • 3.6 本章小结
    • 第四章 直线、曲线与弧线的绘制 ✔️
      • 4.1 坐标轴的创建(上篇)
        • 4.1.1 D3 中的边距约定(中篇)
        • 4.1.2 坐标轴的生成(中篇)
          • 4.1.2.1 比例尺的声明(中篇)
          • 4.1.2.2 坐标轴的添加(下篇)
          • 4.1.2.3 轴标签的添加(下篇)
          • 4.1.2.4 DIY 实战:在 Observable 平台实现折线图坐标轴的绘制
          • 4.1.2.5 DIY 实战:D3 源码分析之 d3.timeFormat() 函数 ✔️
      • 4.2 D3 折线图的绘制(精译中 ⏳)

文章目录

  • DIY 实战:D3 源码分析之:d3.timeFormat() 函数
    • 1 起因
    • 2 官方文档探秘
    • 3 源码分析
      • 3.1 验证一:local() 函数和 new Date() 是否一样
      • 3.2 验证二:timeFormat() 函数是否为 d3.timeFormat() 函数
      • 3.3 newFormat() 函数详解
    • 4 小结

《D3.js in Action》全新第三版封面

《D3.js in Action》全新第三版封面

DIY 实战:D3 源码分析之:d3.timeFormat() 函数


1 起因

前几天完成了 4.1 节剩余内容的翻译,主要介绍了 D3 折线图坐标轴的绘制方法(详见本专栏 第 035 篇译文)。讲解过程中,作者通过 d3.timeFormat('%b') 函数拿到了月份的英文简写字符串(即 "Jan""Feb" 等),但对于该函数的用法及参数的含义却一笔带过,让大家感兴趣的话自行参考 D3 官方文档(更奇怪的是,当时也没有提供具体的文档链接)。这一做法似乎和本书一贯的“手把手”教学风格相悖。怀着这份好奇,我自行补上了这个链接(https://d3js.org/d3-time-format),想看看作者不展开讲解的原因;结果在 D3 官网越看越上头,就有了分享出来的冲动。

2 官方文档探秘

原来,这个 d3-time-format 模块是仿照 C 语言的标准库函数 strptime 和 strftime 实现的。要在 D3 语境下格式化某个日期,需要用指定的标识符(specifier,格式为 %格式指令,如 %b%d 等等)声明一个格式化工具函数 formatter,然后再将日期传入,就能得到最终的结果。换句话说,d3.timeFormat() 其实是一个高阶函数,示例代码中传入 tickFormat() 的其实就是一个 formatter 函数:

const bottomAxis = d3.axisBottom(xScale).tickFormat(d3.timeFormat("%b"));

我就纳闷了:实现这么简单的一个格式化逻辑,竟然也需要用高阶函数这把牛刀?不就是两行代码的事么:

const months = 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(',');
const formatter = date => months[date.getMonth()];

原谅我的强迫症——

图 1 根据需求自行实现的月份格式化逻辑

【图 1 根据需求自行实现的月份格式化逻辑】

难道说 D3 另有深意?带着这个疑问,我又一次愉快地打开了潘多拉女神的魔盒:

图 2 将 d3.timeFormat("%b") 打印到控制台得到的结果(貌似玩笑开大了点)

【图 2 将 d3.timeFormat(“%b”) 打印到控制台得到的结果(貌似玩笑开大了点)】

点进去一看,发现还不如不点:

图 3 点开 d3.timeFormat("%b") 看到的格式化处理后的函数源码

【图 3 点开 d3.timeFormat(“%b”) 看到的格式化处理后的函数源码】

这是要逼我看源码的节奏啊……别慌,先把那页官方文档看完。所谓的标识符 specifier,可用的格式指令(directives)如下:

  • %a:缩写的星期名称。*
  • %A:完整的工作日名称。*
  • %b:缩写的月份名称。*
  • %B:完整月份名称。*
  • %c:本地的日期和时间,例如 %x, %X .*
  • %d:用十进制数字表示的零填充的月份中的天数 [01,31]。
  • %e:用空格填充的月份日期,作为十进制数字 [1,31];等同于 %_d
  • %f:微秒作为十进制数字 [000000, 999999]。
  • %g:ISO 8601 基于周的年份(不含世纪),以十进制数字表示 [00,99]。
  • %G:ISO 8601 基于周的年份,世纪作为十进制数字。
  • %H:小时(24 小时制)作为十进制数字 [00,23]。
  • %I:小时(12 小时制)作为十进制数字 [01,12]。
  • %j:一年中的天数,作为十进制数字 [001,366]。
  • %m:作为十进制数字的月份 [01,12]。
  • %M:以十进制数字表示的分钟 [00,59]。
  • %L:毫秒,作为一个十进制数字 [000, 999]。
  • %p:早上或下午。*
  • %q:年的四分之一,作为小数表示 [1,4].
  • %Q:自 UNIX 纪元以来的毫秒数。
  • %s:自 UNIX 纪元以来的秒数。
  • %S:作为小数的秒数 [00,61].
  • %u:以星期一为基础的(ISO 8601)工作日,作为十进制数字 [1,7]。
  • %U:以星期日为基础的年份周数,作为十进制数字 [00,53]。
  • %V:ISO 8601 年中的周数,作为十进制数字 [01, 53]。
  • %w:以星期日为基础的工作日,作为十进制数字 [0,6]。
  • %W:以星期一为基础的年份周数,作为十进制数字 [00,53]。
  • %x:本地的日期,例如 %-m/%-d/%Y .*
  • %X:本地时间,例如 %-I:%M:%S %p .*
  • %y:不带世纪的年份,作为十进制数字 [00,99]。
  • %Y:以十进制数字表示的世纪年份,例如 1999
  • %Z:时区偏移,例如 -0700-07:00-07Z
  • %%:一个字面上的百分号 ( % )。

其中,末尾带星号标记(*)的指令可能会受到当地区域设置的影响。

另外,% 符号用来标识一个指令,后面还可以紧跟一个填充修饰符:

  • 0:用 0 来填充;
  • _:用空格来填充;
  • -:禁用填充。

介绍完 specifier,文档还提到了 D3 的默认区域设置(美国-英文):

const enUs = d3.timeFormatDefaultLocale({dateTime: "%x, %X",date: "%-m/%-d/%Y",time: "%-I:%M:%S %p",periods: ["AM", "PM"],days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
});

言下之意……D3 还支持其他地区和语言的设置吗?于是果断进入 d3-time-format 模块的 GitHub 仓库。果然,在 d3-time-format/locale/ 文件夹看到了 8 年前最后提交的中文配置(zh-CN.json):

{"dateTime": "%x %A %X","date": "%Y年%-m月%-d日","time": "%H:%M:%S","periods": ["上午", "下午"],"days": ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],"shortDays": ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],"months": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],"shortMonths": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]
}

要配置成中文对应的地区,D3 只提供了一个 d3.timeFormatDefaultLocale(definition) 接口,参数 definition 就是上面的 JSON 配置。只可惜,D3 没能提供查询地区配置文件的接口,如果要让 d3.timeFormat('%b') 显示 十月,只能像这样手动操作:

图 4 手动切换 D3 默认地区的相关接口测试情况(切换为中文)

【图 4 手动切换 D3 默认地区的相关接口测试情况(切换为中文)】

有了上述的准备工作,就可以正式开始 d3.timeFormat() 的源码解读了。

3 源码分析

可能很多朋友看源码都是直接从 src 目录开始的,但我更习惯从项目的测试用例入手。找到 test 文件夹下的 format-test.js,很快就定位到了 %b 标识符对应的单元测试模块:

it("timeFormat(\"%b\")(date) formats abbreviated months", () => {const f = timeFormat("%b");assert.strictEqual(f(local(1990,  0, 1)), "Jan");assert.strictEqual(f(local(1990,  1, 1)), "Feb");assert.strictEqual(f(local(1990,  2, 1)), "Mar");assert.strictEqual(f(local(1990,  3, 1)), "Apr");assert.strictEqual(f(local(1990,  4, 1)), "May");assert.strictEqual(f(local(1990,  5, 1)), "Jun");assert.strictEqual(f(local(1990,  6, 1)), "Jul");assert.strictEqual(f(local(1990,  7, 1)), "Aug");assert.strictEqual(f(local(1990,  8, 1)), "Sep");assert.strictEqual(f(local(1990,  9, 1)), "Oct");assert.strictEqual(f(local(1990, 10, 1)), "Nov");assert.strictEqual(f(local(1990, 11, 1)), "Dec");
});

可能为了大幅降低单元测试的编写难度,这里只用了 Mocha.jsBDD 风格,断言方法也是直接来自 node 的内置断言模块。这里有两点需要明确:

  1. 第 3 ~ 14 行中的 local(...) 函数为什么不使用 new Date(...)
  2. 第 2 行的 timeFormat 是否是我要考察的目标函数?

由于网页不支持方法的快速定位,只能转到本地操作了:

git clone https://github.com/d3/d3-time-format.git d3-time-format
cd d3-time-format
yarn
yarn test

不出意外的话,马上就出意外了:

图 5 本地运行单元测试报错(不支持 Windows 环境)

【图 5 本地运行单元测试报错(不支持 Windows 环境)】

好在这个坑已经踩过了,加个 cross-env 依赖就行了:

# 修复 Windows 不兼容 TZ 设置问题
$ yarn add -D cross-env
# 修改 test 命令脚本
$ (gc package.json) -replace '"test": "(.*?)"', '"test": "cross-env $1"' | Set-Content package.json
# 验证 test 命令脚本是否修改成功
$ cat package.json | sls TZ"test": "cross-env TZ=America/Los_Angeles mocha 'test/**/*-test.js' && eslint src test",
# 再次运行测试
$ yarn test

运行结果:

图 6 修复单元测试不兼容 Windows 系统的问题后,重新运行测试,全部通过。

【图 6 修复单元测试不兼容 Windows 系统的问题后,重新运行测试,全部通过。】

然后就可以用 VSCode 打开该模块了:

$ code .

3.1 验证一:local() 函数和 new Date() 是否一样

先从简单的问题入手:单元测试为什么要用自定义的 local() 函数,而不是使用原生的 new Date()?直接跳转到 local() 的定义:

export function local(year, month, day, hours, minutes, seconds, milliseconds) {if (year == null) year = 0;if (month == null) month = 0;if (day == null) day = 1;if (hours == null) hours = 0;if (minutes == null) minutes = 0;if (seconds == null) seconds = 0;if (milliseconds == null) milliseconds = 0;if (0 <= year && year < 100) {const date = new Date(-1, month, day, hours, minutes, seconds, milliseconds);date.setFullYear(year);return date;}return new Date(year, month, day, hours, minutes, seconds, milliseconds);
}

原来如此!第 9 行对年份介于 0 ~ 99 的日期做了单独处理,不让原生 JavaScriptDate 构造函数中的默认转换生效(new Date(99, 0, 1) 的结果为 1999 年 1 月 1 日)。第 10 行的 -1 也很巧妙,刚好绕开了 Date 的默认转换,写起来也方便。

结论:local() 函数得到的就是一个 Date 实例,只不过考虑得更全面。

3.2 验证二:timeFormat() 函数是否为 d3.timeFormat() 函数

再来看此次源码解读的核心 —— timeFormat() 函数。虽然种种迹象表明,答案必定是肯定的,但还是有必要跟着源码过一遍。这样就跟踪到了 src/index.js,进而定位到 defaultLocale.js 模块:

// d3-time-format/test/format-test.js
import {timeFormat} from "../src/index.js";
// index.js
export {default as timeFormatDefaultLocale, timeFormat, timeParse, utcFormat, utcParse} from "./defaultLocale.js";
// defaultLocale.js
export var timeFormat;
// ...
export default function defaultLocale(definition) {locale = formatLocale(definition);timeFormat = locale.format;// ...return locale;
}

从第 2 行可以断定,单元测试中的 timeFormat() 函数就是 d3.timeFormat() 函数。继续追踪可以看到,它的赋值是在 defaultLocale.js 中完成的(第 10 行)。那么赋给它的值 locale.format 究竟是什么呢?这得看上一行中的 formatLocale(definition) 究竟在干什么。还是分两步走:

  1. 搞懂 definition 是什么;
  2. 搞懂 formatLocale 函数的定义。

第一个问题很简单,definition 就是前面提过的 D3 默认地区设置,来看 defaultLocale.js 的完整截图就明白了:

图 7 搞懂 definition 是什么:D3 默认的地区语言设置

【图 7 搞懂 definition 是什么:D3 默认的地区语言设置】

接着跳转到 formatLocale() 函数的定义,就来到了 src/locale.js 模块:

图 8 找到 src/locale.js 模块下的 formatLocale() 函数定义

【图 8 找到 src/locale.js 模块下的 formatLocale() 函数定义】

这里我们只关心函数返回值中的 format 属性,因此直接定位到该函数的 return 语句:

图 9 定位到 formatLocale 函数的 return 语句,并锁定返回值中的 format 属性

【图 9 定位到 formatLocale 函数的 return 语句,并锁定返回值中的 format 属性】

从图 9 不难看出,最终赋值给 d3.timeFormat 函数的,正是第 366 行中的 newFormat(specifier += "", formats),也就是文章最开始的图 2 所看到的那一堆压缩版的函数定义。注意第 366 行还传入了第二个参数 formats,这是一个典型的闭包结构,formats 是一个内置的 JS 对象。对于我们要考察的 %b 而言,只需要用到其中的两个键值对,可简化为:

var specifier = "%b";
var formats = {"b": formatShortMonth,"%": formatLiteralPercent
};
var f = newFormat("%b", {"b": formatShortMonth,"%": formatLiteralPercent
})

这样一来,问题的关键就变为对函数 newFormat() 的解读了。

3.3 newFormat() 函数详解

定位到 newFormat 函数,将看到这一段终极源码:

function newFormat(specifier, formats) {return function(date) {var string = [],i = -1,j = 0,n = specifier.length,c,pad,format;if (!(date instanceof Date)) date = new Date(+date);while (++i < n) {if (specifier.charCodeAt(i) === 37) {string.push(specifier.slice(j, i));if ((pad = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i);else pad = c === "e" ? " " : "0";if (format = formats[c]) c = format(date, pad);string.push(c);j = i + 1;}}string.push(specifier.slice(j, i));return string.join("");};
}

虽然也比较复杂,但对比图 3 那样的简化版已经很不错了。注意第 16 行新引入的闭包结构 pads,这是格式化结果中负责拼接填充符号的键值对,比较简单:

var pads = {"-": "", "_": " ", "0": "0"
};

再次明确我们的分析目标:考察以下代码的底层逻辑:

const formatter = d3.timeFormat('%b'); 
console.log(formatter(new Date())); // 'Oct'

因此,将 '%b'new Date() 即刚才分析的简化 formats 代入,就可以得到简化版的 formatter 定义:

var formats = {"b": formatShortMonth,"%": () => '%'
};
var pads = {"-": "", "_": " ", "0": "0"};
const formatter = function(date) {var string = [],i = -1,j = 0,n = 2,  // '%b'.length => 2c,pad,format;while (++i < 2) {if ('%b'.charCodeAt(i) === 37) {string.push('%b'.slice(j, i));if ((pad = pads[c = '%b'.charAt(++i)]) != null) c = '%b'.charAt(++i);else pad = c === "e" ? " " : "0";if (format = formats[c]) c = format(date, pad);string.push(c);j = i + 1;}}string.push('%b'.slice(j, i));return string.join("");
}

注意,第 11 行就是判定字符串的首字符是否为 %,这显然是满足的,因此重点关注第 16 ~ 22 行。

第一轮:i = 0, j = 0——

  • 执行第 17 行,结果为 string = ['']
  • 执行第 18 行,c = '%b'.charAt(1) = 'b'pad = pads[c] = undefined,显然 if 条件 undefined != null 为假,pad 转到第 19 行被重新赋值:pad = c === "e" ? " " : "0",因此 pad = " "
  • 执行第 20 行,此时 c = 'b',故 format = formats['b'] = formatShortMonth,满足 if 条件,c 被重新赋值为 formatShortMonth(date, " ")
  • 执行第 21 行,得到新的 string 数组:['', c]
  • 执行第 22 行,此时 i = 1, j = 1

第二轮:i = 2, j = 1——

  • 由于 i 值已不满足 while 循环条件,因此跳出循环,直接前往第 26 行;此时 i = 2, j = 1

  • 执行第 26 行,string 数组更新为 ['', c, '']

  • 执行第 27 行,可得到 formatter 的进一步简化版定义:

    const formatter = date => "" + formatShortMonth(date, " ") + "";
    

这里的 formatShortMonth() 又是什么呢,跳转过去看到的源码是这样的:

function formatShortMonth(d) {return locale_shortMonths[d.getMonth()];
}

可见,formatShortMonth(date, " ") 的第二个参数根本没用到!因此 formatter 还可以精简为:

const formatter = date => "" + locale_shortMonths[date.getMonth()] + "";

这样,就和我自定义的逻辑很像了,我之前是这样写的:

const months = 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(',');
const formatter = date => months[date.getMonth()];

现在问题就变成了:locale_shortMonthsmonths 是不是同一个数组?别急,来看 locale_shortMonths 的定义:

图 10 变量 locale_shortMonths 的声明情况

【图 10 变量 locale_shortMonths 的声明情况】

显然,locale_shortMonths 是从参数中直接赋的值。那这个参数 locale 是什么值呢?这就得再回到此前调用 formatLocale() 函数的地方了,也就是前面提过的图 7:

图 7 搞懂 definition 是什么:D3 默认的地区语言设置

注意第 17 行,shortMonths 就是我要找的那个数组。终于衔接上了!!!formatter 的终极定义如下:

var locale_shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const formatter = date => "" + locale_shortMonths[date.getMonth()] + "";

大功告成。

4 小结

通过对 d3.timeFormat() 源码的全面解读,可以归纳出以下几点:

  • 从单元测试用例入手,既可以快速锁定目标函数,又可以了解目标函数的具体用法,一举多得;
  • 遇到需要分步走的情况时,先做好记录,从简单的分支入手,再逐步逼近复杂分支;
  • 作为工具库函数,需要考虑各种格式化指令的解析和其他辅助配置,因此不得不经过一系列筛选、赋值、高阶函数处理,以满足工具函数的一致性;对于一些简单的格式化逻辑,手写应该比调用库函数更方便。
  • 源码最复杂的部分,其实就是那个 while 循环,用于解析不同的 specifier 标识符,并在内置的 formats 对象里找到对应的格式化方法,然后返回最终结果。
  • 遇到复杂的问题,要时刻明确自己的目标,并围绕目标将问题一步步简化,做到心中有数,稳扎稳打。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/456453.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

yarn的安装与使用以及与npm的区别(安装过程中可能会遇到的问题)

一、yarn的安装 使用npm就可以进行安装 但是需要注意的一点是yarn的使用和node版本是有关系的必须是16.0以上的版本。 输入以下代码就可以实现yarn的安装 npm install -g yarn 再通过版本号的检查来确定&#xff0c;yarn是否安装成功 yarn -v二、遇到的问题 1、问题描述…

【Qt】控件——Qt控件的介绍、QWidget的介绍、QWidget的属性、QWidget的函数

文章目录 Qt1. 控件的概念2. QWidgetenabledgeometrywindowTitlewindowIconwindowOpacitycursorfonttoolTiptoolTipDuringstyleSheet Qt 1. 控件的概念 Widget 是 Qt 中的核心概念。英文原义是 “小部件”&#xff0c;我们此处也把它翻译为 “控件”。控件是构成一个图形化界面…

算法剖析:二分查找

文章目录 前言二分查找模板朴素模板左右查找模板 一、二分查找二、 在排序数组中查找元素的第一个和最后一个位置三、搜索插入位置四、x 的平方根五、山脉数组的峰顶索引六、寻找峰值七、寻找旋转排序数组中的最小值八、 点名总结 前言 二分查找是一种高效的查找算法&#xff…

基于SpringBoot的“高校校园点餐系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“高校校园点餐系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 前台首页功能界面图 用户注册、登录界面图 我…

时间序列预测(十)——长短期记忆网络(LSTM)

目录 一、LSTM结构 二、LSTM 核心思想 三、LSTM分步演练 &#xff08;一&#xff09;初始化 1、权重和偏置初始化 2、初始细胞状态和隐藏状态初始化 &#xff08;二&#xff09;前向传播 1、遗忘门计算&#xff08;决定从上一时刻隐状态中丢弃多少信息&#xff09; 2、…

基于.NET 8.0,C#中Microsoft.Office.Interop.Excel来操作office365的excel

开发环境&#xff1a; Visual Studio 2022 office365 项目模板&#xff1a;WPF应用程序 框架&#xff1a;.NET 8.0 依赖&#xff1a;Microsoft.Office.Interop.Excel 注意&#xff1a; 1.使用Microsoft.Office.Interop.Excel库时&#xff0c;服务器或电脑里面必须安装得…

qt QLineEdit详解

一、概述 QLineEdit 是 Qt 框架中用于创建单行文本输入框的类。它非常适合用于接收用户输入&#xff0c;例如用户名、密码或其他简单的文本信息。它提供了许多有用的编辑功能&#xff0c;支持多种输入模式和文本限制&#xff0c;并支持撤销、重做、剪切、粘贴以及拖放等功能。…

【AI服务器】全国产PCIe 5.0 Switch SerDes 测试和分析,以11槽PCIe GPU底板(PCIe 4.0/5.0)为例(二)

3 PCIe 4.0 SerDes 和 5.0 SerDes 要求比较 表 2 总结 PCIe 4.0 和 5.0 SerDes 要求之间的差 异。PCIe 标准包含三个相互依赖的规范&#xff0c;这些规范 旨在确保不同供应商的 SerDes 和通道的互操作性&#xff1a; ● PCIe BASE 规范定义了整个协议栈的芯片 级性能,是一…

使用QT绘图控件QCustomPlot绘制波形图

使用QT绘图控件QCustomPlot绘制波形图 下载QCustomPlot 下载QCustomPlot,链接路径 解压之后就能看到源代码了 在Qt中添加QCustomPlot的帮助文档 在Qt Creator的菜单:工具–>选项–>帮助–>文档–>添加qcustomplot\documentation\qcustomplot.qch文件。

Elasticsearch基本使用及介绍

Elasticsearch 1. 关于各种数据库的使用 关于MySQL&#xff1a;是关系型数据库&#xff0c;能清楚的表示数据之间的关系&#xff0c;并且&#xff0c;是基于磁盘存储的&#xff0c;可以使用相对较低的成本存储大量的数据 关于Redis&#xff1a;是基于K-V结构的在内存中读写数…

同世界,共北斗|遨游通讯亮相第三届北斗规模应用国际峰会!

10月24日&#xff0c;第三届北斗规模应用国际峰会在湖南省株洲市隆重开幕&#xff0c;此次峰会以“同世界&#xff0c;共北斗”为主题&#xff0c;旨在加速北斗系统的市场化进程、促进其产业化布局及国际化拓展。全国政协副主席、农工党中央常务副主席杨震讲话并宣布开幕&#…

【赵渝强老师】Oracle的联机重做日志文件与数据写入过程

在Oracle数据库中&#xff0c;一个数据库可以有多个联机重做日志文件&#xff0c;它记录了数据库的变化。例如&#xff0c;当Oracle数据库产生异常时&#xff0c;导致对数据的改变没有及时写入到数据文件中。这时Oracle数据库就会根据联机重做日志文件中的信息来获得数据库的变…

上传Gitee仓库流程图

推荐一个流程图工具 登录 | ProcessOnProcessOn是一个在线协作绘图平台&#xff0c;为用户提供强大、易用的作图工具&#xff01;支持在线创作流程图、思维导图、组织结构图、网络拓扑图、BPMN、UML图、UI界面原型设计、iOS界面原型设计等。同时依托于互联网实现了人与人之间的…

立志最细,FreeRtos中 中断、 调度器、的屏蔽/恢复,详解!!!

#1024程序员节征文|征文# 前言&#xff1a;本文参考&#xff0c;韦东山开发文档&#xff0c;连接最后 任务调度器 任务调度器(scheduler)&#xff0c;在FreeRtos操作系统中&#xff0c;主要负责多任务之间的切换&#xff0c;确保系统按照优先级和多任务的并发的方式去运行&…

为Windows Terminal 配置zsh + Oh-My-Zsh!

参考&#xff1a; 为Windows Terminal 配置zsh Oh-My-Zsh! [非WSL] https://zhuanlan.zhihu.com/p/625583037 Package: zsh - MSYS2 Packages 安装配置 1、安装 Windows Terminal(必须) Method 1: 打开 Microsoft Store&#xff0c;搜索 “Windows Terminal”。点击 “…

K最近邻算法

一、近朱者赤&#xff0c;近墨者黑 通常称对门、楼上、楼下和隔壁均是我们的邻居。为什么呢&#xff1f;离得近呗。 “近朱者赤近墨者黑”“昔孟母&#xff0c;择邻处”等充分说明了邻居对我们的重要性。基本上你的邻居是什么人&#xff0c;你也是什么人。假如你楼上是马云&am…

操作系统期末|考研复习知识点汇总 - 持续更新

本文将根据个人学习进度对b站王道408课程以及题目考察的知识点进行整合&#xff0c;视频中详细的导图将会直接复用&#xff0c;并且将会对一些重点知识进行扩展以及一些思维导图的补充&#xff0c;目前第三章内容正在整理中…… 一&#xff1a;计算机系统概述 1.1操作系统概念…

DockerCompose快速部署Java项目、nginx前端和mysql数据库到centos虚拟机

简介&#xff1a;整理自&#xff1a;SpringCloud微服务开发与实战&#xff0c;java黑马商城项目微服务实战开发&#xff08;涵盖MybatisPlus、Docker、MQ、ES、Redis高级等&#xff09;课程的飞书文档。 DockerCompose介绍 大家可以看到&#xff0c;我们部署一个简单的java项…

实现可扩展人工智能的便捷之路:英特尔 Tiber 开发者云 + MinIO 对象存储

当今组织在 AI 和数据管理方面面临的最大挑战之一是获得可靠的基础设施和计算资源。英特尔 Tiber 开发人员云专为需要概念验证、实验、模型训练和服务部署环境的工程师而构建。与其他难以接近且复杂的云不同&#xff0c;英特尔 Tiber 开发人员云简单易用。该平台对于开发各种类…

信息安全工程师(67)网络流量清洗技术与应用

前言 网络流量清洗技术是现代网络安全领域中的一项关键技术&#xff0c;它主要用于过滤和清理网络流量中的恶意部分&#xff0c;确保正常的网络通信。 一、网络流量清洗技术的定义与原理 网络流量清洗技术&#xff0c;也称为流量清理&#xff08;Traffic Scrubbing&#xff09;…