【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.mzph.cn/web/56074.shtml

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

相关文章

Axure重要元件三——中继器修改数据

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;中继器修改数据 主要内容&#xff1a;显示编辑内容、表格赋值、修改数据 应用场景&#xff1a;更新行、表单数据行修改 案例展示&#xff1a; 正文…

前端算法合集-2(含面试题-美团一面)

主要考察的就是数组扁平化,由浅入深吧 ①利用tostring()和split() let arr [1, 2, 3, [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]] let newarrarr.toString().split(,) let numarrnewarr.map((item)>{itemNumber(item)return item }) console.log(numarr) ②利用…

AndroidStudio移动开发:使用Service播放音乐【步骤】

目录 一、创建新application 二、准备音乐文件 三、创建项目和布局&#xff08;如果需要交互界面的话&#xff0c;这里简单假设一个基本布局&#xff09; 四、创建MusicService类 五、在MainActivity中启动和控制Service&#xff08;也可以另外创建MusicActivity类&#x…

MoeCTF 2024 ---Misc方向WP

安全杂项 signin 题目描述&#xff1a; xdsec的小伙伴们和参赛者来上课&#xff0c;碰巧这一天签到系统坏了&#xff0c;作为老师的你&#xff0c;要帮他们 教师代签。 特殊提醒&#xff1a;luo同学今天好像在宿舍打游戏&#xff0c;不想来上课&#xff0c;这是严重的缺勤行为…

【数据分享】全国人口-人口年龄结构和抚养比(1990-2021年)

数据介绍 一级标题指标名称单位指标解释人口年末总人口万人年末人口数指每年12月31日24时的人口数。年度统计的全国人口总数内未包括香港、澳门特别行政区和台湾省以及海外华侨人数。1981年及以前人口数据为户籍统计数&#xff1b;1982、1990、2000、2010、2020年数据为当年人口…

实操上手TinyEngine低代码引擎插件化开发

1.背景介绍 1.1 TinyEngine 低代码引擎简介 低代码开发是近些年非常热门的一种开发方式&#xff0c;用户可以通过可视化的方式&#xff0c;简单拖拽&#xff0c;不写代码或者编写少量代码&#xff0c;类似搭积木一样搭建业务应用。 TinyEngine是一个强大的低代码引擎&#x…

Redis 常用指令详解

Redis是一款开源的、高性能的键值对存储数据库&#xff0c;常用于缓存、会话存储以及其他需要快速访问的数据场景。本文将介绍Redis的一些常用指令&#xff0c;并通过代码示例进行说明。 一、连接操作指令 1. 连接 Redis 服务器 ./redis-cli -h 127.0.0.1 -p 63792. 认证&a…

计算广告第三版pdf

需要该书pdf版本的同学点赞&#xff0c;私信我&#xff1a;

Spark_入库时报错ORA-00001 unique constraint violated 解决办法

首先可能是数据入重复了 检查一下看看是否入库前删除了分区的数据&#xff0c;可能是重复数据入库的问题&#xff0c;如果不是这个那么继续排查。 入库的数据有问题&#xff0c;检测方法 如果报主键冲突了&#xff0c;则group by 一下id,date&#xff0c;然后select 的时候加一…

飞睿智能超宽带UWB音频传输模块,超低延迟数据传输,实时音频声音更纯净

在信息爆炸的时代&#xff0c;音频传输技术正以未有的速度发展&#xff0c;创新我们进入一个全新的听觉世界。今天&#xff0c;我们要探讨的&#xff0c;就是这场技术创新中的一颗璀璨明星——飞睿智能超宽带&#xff08;UWB&#xff09;音频传输模块。它以其独特的优势&#x…

RHCSA课后练习1

文件管理命令练习 vi/vim练习 vim newfile 按 a 进入编辑模式 &#xff1a;r 文件 在末尾模式中插入文件 &#xff1a;%s/#/ /g 先将#标记为匹配项&#xff1a;%g#d 再删除 : set nu 开启行号 自行组cp 用grep命令找到6段再用tail命令选中第六段 用find查找 [A-Z]代表所有…

如何写一个视频编码器演示篇

先前写过《视频编码原理简介》&#xff0c;有朋友问光代码和文字不太真切&#xff0c;能否补充几张图片&#xff0c;今天我们演示一下&#xff1a; 这是第一帧画面&#xff1a;P1&#xff08;我们的参考帧&#xff09; 这是第二帧画面&#xff1a;P2&#xff08;需要编码的帧&…

python包以及异常、模块、包的综合案例(较难)

1.自定义包 python中模块是一个文件&#xff0c;而包就是一个文件夹 有这个_init_.py就是python包&#xff0c;没有就是简单的文件夹 包的作用&#xff1a;当我们的模块越来越多时&#xff0c;包可以帮助我们管理这些模块&#xff0c;包的作用就是包含多个模块&#xff0c;但包…

基于JSP的校园宿舍电费缴纳系统【附源码】

基于JSP的校园宿舍电费缴纳系统 效果如下&#xff1a; 系统首页界面 学生登录界面 公告栏页面 在线留言页面 个人中心界面 管理员登录界面 管理员功能界面 宿舍信息管理界面 余额管理界面 使用电量管理界面 余额提醒管理界面 学生功能界面 研究背景 随着网络的高速发展&…

使用休眠的方式来解决电脑合盖后偶尔不能正常睡眠的问题

背景描述 用过Windows笔记本电脑的用户应该都偶尔遇到过这样的一个问题&#xff0c;就是电脑直接合上盖后放在包里&#xff0c;按道理来说应该会自动进入睡眠模式&#xff0c;但是等电脑再从包里拿出来时发现电脑很烫&#xff0c;并且已经没电了&#xff0c;似乎并没有进入到休…

【乐企文件生成工程】关于乐企文件生成工程的详细介绍

发票文件生成方式有两种思路&#xff1a; 1、根据已有的OFD模板&#xff0c;动态替换ofd模板内容&#xff1b;之后将ofd转pdf&#xff08;局限&#xff1a;单行问题不大&#xff09; 可在【乐企】专栏查看详细代码详情可以在此处了解【乐企】有关乐企能力测试接口对接-基础版&a…

Web,RESTful API 在微服务中的作用是什么?

大家好&#xff0c;我是锋哥。今天分享关于【Web&#xff0c;RESTful API 在微服务中的作用是什么&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; Web&#xff0c;RESTful API 在微服务中的作用是什么&#xff1f; 在微服务架构中&#xff0c;Web 和 RESTful …

Python语法结构(三)(Python Syntax Structure III)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

Python编程基础入门:从风格到数据类型再到表达式

前期已经详细介绍了环境搭建&#xff1a;PycharmPython、VsCodePython Python编程基础入门&#xff1a;从风格到数据类型再到表达式 在编写Python程序时&#xff0c;理解其基础结构和语法是每个初学者的必修课。这篇文章将带你深入了解Python的基本编程风格、数据类型、类型转…

【功能安全】相关项定义item definition

目录 01 item definition定义 02 相关项组成 03 相关项最佳实践 📖 推荐阅读 01 item definition定义 概念阶段的开发是以相关项定义(Item Definition)开始的,相关项定义是对系统的描述,此系统也是标准中安全要求应用的对象。 相关项定义目的: a) 在整车层面对相关…