使用这些配置来规范和格式化您的代码
在日常工作中,我们会接触形形色色的工程。如果工程使用的技术架构不同,可能会有对应不同的代码规范。而每个人的编码习惯是不一样的,也是难以短时间内改变的,这也是我们常常在开发一个新工程的时候,会遇到各种规范报错的原因。
此时,如果能有一套配置,能够让我们在写代码时不用考虑该工程的规则,只要在保存时就能够自动按照当前工程配置好的规则修复所有错误,这无疑会大大增加我们的开发体验和效率。
下面我将详细讲解为了实现这一目标,我们需要做什么,以及各种规范的基本配置。
EditorConfig
首先,我们需要一个基本的规范,例如缩进,如何换行等等。它要适用于所有的团队,适用于所有的语言,适用于所有的编辑器。
EditorConfig
能帮助我们实现这一点。它让所有的开发者在基本编码规范上保持一致。
我们需要做的是:
- 安装
EditorConfig
插件(有些编辑器默认支持EditorConfig
,具体请看 这些编辑器不需要安装插件 (https://editorconfig.org/#pre-installed))。 - 配置
.editorconfig
文件。
以下是 .editorconfig
的用法和例子:
## 打开文件时,EditorConfig 插件会在打开的文件的目录和每个父目录中查找名为 .editorconfig 的文件。
## 如果到达根文件路径或找到具有 root=true 的 EditorConfig 文件,将停止对 .editorconfig 文件的搜索。
## 如果 root=true 没有配置, EditorConfig 插件将会在工程之外寻找 .editorconfig 文件
root = true
## 使用规则匹配文件
## * 匹配任何字符串,路径分隔符 (/) 除外
## ** 匹配任意字符串
## ? 匹配任何单个字符
## [name] 匹配给定的字符串中的任何单个字符
## [!name] 匹配不在给定字符串中的任何单个字符
## {s1,s2,s3} 匹配任意给定的字符串
## {num1..num2} 匹配num1和num2之间的任何整数,其中num1和num2可以是正数或负数
## 如规则[*.{js}]只对 .js 文件生效。一般来说,我们配置 [*] 对所有文件生效。
[*]
## 缩进方式。 值可以是 tab 或者 space
indent_style = space
## 缩进大小。当设置为 tab 时,会取 tab_width 的值。
indent_size = 2
## 通常不需要设置。当 indent_size = tab 时,才会生效。
tab_width = 2;
## 设置为 lf、cr 或 crlf 以控制如何表示换行符。
end_of_line = lf
## 设置为 latin1、utf-8、utf-8-bom、utf-16be 或 utf-16le 来控制字符集。
charset = utf-8
## 设置为 true 以删除换行符之前的任何空格字符,设置为 false 以确保不会。
trim_trailing_whitespace = true
## 设置为 true 以确保文件在保存时以换行符结束,设置为 false 以确保不以换行符结束。
inset_final_newline = true
Eslint
对于前端开发工程师来说,JavaScript 无疑是我们最好的伙伴了。而 ESLint,它是一款插件化的 JavaScript 代码静态检查工具,其核心是通过对代码解析得到的 AST(Abstract Syntax Tree,抽象语法树)进行模式匹配,定位不符合约定规范的代码。
社区里有很多不同版本的规范,每个团队也可能会制定自己的规范。编码风格千千万,而工程的配置就一套,在多人协作时就必然会出现规范报错的情况。我们需要配置一套规则,让我们不需要 Care 规则到底是什么,在保存文件的时候,自动按照工程规范格式化代码。
怎么办呢?
Eslint 提供了风格指南规则,并明确表示了哪些是可修复的:Stylistic Issues (https://cn.eslint.org/docs/rules/#stylistic-issues)
我们需要做的是:
- 本地安装 Eslint 和社区推荐的规范 eslint-config-airbnb (https://github.com/airbnb/javascript) (也可以是别的规范)。插件会使用安装的 Eslint 库(如果你还未安装:
npm i eslint eslint-config-airbnb
)。 - VSCode 安装
Eslint插件
。 - 添加
.eslintrc.js
配置文件。 - 更改 VSCode 的
setting.json
文件的配置。
其中,想要实现自动按照工程的规则格式化,第四步必不可少。
setting.json
如果你已经安装好了 Eslint插件
,按 cmd + shif + p
,打开 defaultSettings.json
文件,按 cmd + f
搜索 eslint
可以看到所有 ESlint 在 VSCode 内的默认配置。我们需要对它做一些修改。
还是按 cmd + shift + p
打开 settings.json
文件。这个文件是用户自定义配置,里面的配置会覆盖 defaultSettings.json
里的同名配置。我们在这个文件里对 ESLint 插件
的配置做一些修改,让它达到我们想要的效果。
首先,我们想要 保存时自动格式化
,实现这个效果的配置有三种:
-
editor.formatOnSave
+eslint.format.enable
。前者配置:保存时格式化
,后者配置:将 ESlint 规则作为格式化标准
。 eslint.autoFixOnSave
editor.codeActionsOnSave
其中,第二种 eslint.autoFixOnSave
已经被废弃。使用它会提示更改为 editor.codeActionsOnSave
。
而第一种和第三种都可以实现,但是更推荐使用第三种 editor.codeActionsOnSave
,它支持更高的可配置性。
使用 editor.codeActionsOnSave
的时候,我们需要禁用其它格式化程序,最好的做法是将 ESlint 设置为格式化程序默认值。并且当我们这么做的时候,我们可以关闭 editor.formatOnSave
,否则我们的文件将被修复两次,这是没有必要的。
以下便是我们需要在 setting.json
里新增的配置。(注释的地方是默认配置,无需新增)
// 编辑的时候检测还是保存的时候检测,默认在编辑的时候就检测。default: onType
// "eslint.run": "onType",
// default: false
// "eslint.format.enable": false,
// default: false
// "editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[vue]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
// 始终在VSCode的右下角状态栏显示 ESLint 字样,查看 ESLint 运行状态,确保 ESLint 在正常运行
"eslint.alwaysShowStatus": true,
.eslintrc.js
接下来,我们聊聊 .eslintrc.js
文件。这个文件将会规定我们的 ESLint 具体该使用什么规则去规范我们的代码。
我们自己往往不需要去配置这个文件,因为工程一般都会配置好了一套规则。我们只需要使用这套规则去格式化代码就好了。
但是看懂每条规则的意义,对于我们也是很重要的,例如你想自己新建工程。
接下来,我将从 普遍用法
、Vue项目特殊配置
、React项目特殊配置
来看下如何配置 .eslintrc.js
文件。
普遍用法
- 默认情况下,ESLint 支持 ES5 的语法。我们可以覆盖这个配置,启用对 ES6、 ES7 ... 的支持 (https://cn.eslint.org/docs/user-guide/configuring#specifying-parser-options)。
// 启用对 es6 的语法和全局变量的支持
{
env: {
es6: true,
},
}
- 如果我们想让 ESLint 不仅能识别浏览器环境中的语法,其它环境(https://cn.eslint.org/docs/user-guide/configuring#specifying-environments)(如
Node
)我们也希望它能识别,这时候我们可以这样配置:
{
env: {
browser: true,
node: true,
},
}
- 在一些项目中,我们需要特殊的解析器去解析我们的代码,是否是符合规范的。这时候我们可以使用 Parser (https://cn.eslint.org/docs/user-guide/configuring#specifying-parser)
{
parser: 'babel-eslint',
}
- 当访问当前源文件内未定义的变量时,no-undef (https://cn.eslint.org/docs/rules/no-undef) 规则将发出警告。如果你想在一个源文件里使用全局变量,推荐你 在 ESLint 中定义这些全局变量 (https://cn.eslint.org/docs/user-guide/configuring#specifying-globals),这样 ESLint 就不会发出警告了。
当访问当前源文件内未定义的变量时,no-undef 规则将发出警告。如果你想在一个源文件里使用全局变量,推荐你在 ESLint 中定义这些全局变量,这样 ESLint 就不会发出警告了。
{
globals: {
"__DEV__": true,
"If": true,
"For": true,
"POBrowser": true
},
}
- ESLint 支持使用第三方插件 (https://cn.eslint.org/docs/user-guide/configuring#configuring-plugins)。在使用插件之前,你必须使用 npm 安装它。在配置文件里配置插件时,可以使用
plugins
关键字来存放插件名字的列表。插件名称可以省略eslint-plugin-
前缀。
{
plugins: ['react-hooks', 'jsx-control-statements'],
}
- ESLint 附带有大量的规则。你可以使用注释或配置文件修改你项目中要使用的规则 (https://cn.eslint.org/docs/user-guide/configuring#configuring-rules)。要改变一个规则设置,你必须将规则 ID 设置为下列值之一:
-
"off"
或0
- 关闭规则 -
"warn"
或1
- 开启规则,使用警告级别的错误:warn
(不会导致程序退出) -
"error"
或2
- 开启规则,使用错误级别的错误:error
(当被触发的时候,程序会退出)
-
{
rules: {
eqeqeq: 'off',
curly: 'error',
quotes: ['error', 'double']
}
}
- 配置定义在插件中的一个规则的时候,你必须使用
插件名/规则ID
的形式。比如:
{
plugins: ['react-hooks', 'jsx-control-statements'],
rules: {
'arrow-parens': 0,
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'jsx-control-statements/jsx-use-if-tag': 0,
'react/jsx-no-undef': ['error', { 'allowGlobals': true }],
'no-prototype-builtins': 'off',
}
}
- ESLint 的配置规则实在太多,如果我们自己一条条规则去配置,这个工作了将会非常大。我们可以直接拿现有的规范来使用 (https://cn.eslint.org/docs/user-guide/configuring#extending-configuration-files)。
{ extends: 'zoo/react',}
Vue
特殊配置
由于 Vue
单文件组件的特殊写法,针对 Vue
项目,需要做一些特殊的 ESLint 配置,以达到自动化的效果。
高亮语法支持
安装 Vetur插件
。
使用 ESLint 而不是 Vetur 做代码检测
Vetur 为 Vue
项目带来了语法高亮和便捷的操作。但是它本身也会自动开启对 Vue
文件的代码检测。这往往会和我们配置的 ESLint 有冲突。为了避免这一点,需要在 VSCode 的 settings.json
中做一些配置:
// 不允许它格式化代码
"vetur.format.enable": false,
// 不允许它做代码检测
"vetur.validation.template": false,
"vetur.validation.script": false,
"vetur.validation.style": false,
无需将 vue
添加进 eslint.validate
,因为 eslint.probe
默认会检测 vue
类型文件。
然后,我们需要配置 .eslintrc.js
文件,里面用到的插件都需要本地安装。
module.exports = {
root: true,
// 如果是SSR项目,则需要配置node:true
env: {
browser: true,
node: true,
},
// 为什么是这样的parser配置?https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
parser: 'vue-eslint-parser',
parserOptions: {
parser: 'babel-eslint',
},
extends: [
// 如果是nuxt.js的脚手架项目,则需要安装对应的插件并做以下配置
'@nuxtjs',
'plugin:nuxt/recommended',
// 让eslint可以规范vue文件
'plugin:vue/base',
// vue3的项目需要使用,如果是vue2项目,使用 plugin:vue/recommended
'plugin:vue/vue3-recommended',
],
plugins: [
// 注意这里不能配置 html 选项,为什么?https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
'vue',
],
// 配置自己的规则,覆盖上面继承的规则
rules: {
// 配置js的缩进为 2,switch case 语句的 case 也使用2个空格缩进
indent: ['error', 2, { SwitchCase: 1 }],
// 使用 eslint 检测 template里的代码,这里我配置 2 个空格缩进
'vue/html-indent': ['error', 2],
},
};
以上配置,大家根据自己的项目特点,自行删减即可。比如,如果你的项目不是 nuxt.js
的,可以去掉 extends
里的 '@nuxtjs
和 plugin:nuxt/recommended
。
如果是 Vue cli
创建的项目,并且没有使用 ts
,需要在项目根目录添加 jsconfig.json
文件。有关 jsconfig
的配置在这里:jsconfig (https://code.visualstudio.com/docs/languages/jsconfig)
React
特殊配置
React
项目中,因为是 .js
文件,一般不需要特殊的配置。但即使如此,针对 JSX 和 Hooks 的使用规则,我们仍然需要做一些事情
针对 React Hooks
lint 规则具体强制了哪些内容?(https://zh-hans.reactjs.org/docs/hooks-faq.html#what-exactly-do-the-lint-rules-enforce)
eslint-plugin-hooks
是 React
源码目录 packages
里提供的一个包。它会强制执行 Hooks 规则,它也是 Hooks API 的一部分。
npm i eslint-plugin-reack-hooks
在 .eslintrc.js
中
module.exports = {
// eslint-plugin 可以简写
plugins: ['react-hooks'],
}
针对 JSX
JSX 不过只是 React
的一个语法糖,其最终都会被 React 调用 React.createElement 编译成 React Element 形式。所以在 17 版本之前,如果我们使用到了 JSX 但是没有引入 React
,会提示 'React' must be in scope when using JSX
。而在 17 版本之后, React 与 Babel 和 TypeScript 编译器合作,将转化任务交给了编译器自动转化。
如果我们是之前的转化版本,我们要获得对 JSX 的语法支持,我们需要安装 eslint-plugin-react
,它内置了对 JSX 的代码规范检测。
{
extends: ['plugin:react/recommended'],
}
如果不想使用内置的规则,我们也可以自定义规则
{
plugins: ['react'],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
rules: {
'react/jsx-no-undef': ['error', { "allowGlobals": true }],
},
}
如果是新的转化版本,则需要做一点小小的更改,以便在使用 JSX 的时候,不会要求我们引入 React
。
{
extends: ['plugin:react/recommended', 'plugin:react/jsx-runtime'],
}
StyleLint
在完成了以上的配置之后,我们已经可以对 .js
文件、.vue
文件的 template
和 script
模块实现代码规范和保存时自动格式化了。但是对于 .css、.less、.scss
文件和 .vue
文件的 style
模块,我们还需要做额外的配置,否则样式部分不规范,我们也是没法检测并自动修复的。
我们需要做的是:
-
npm i stylelint stylelint-config-standard stylelint-scss
。 - 安装
Stylelint插件
。 - 配置
.stylelintrc
文件。 - 配置 VSCode 的
setting.json
文件。
其中,第四步也是必须的,我们需要做如下配置:
// 防止编辑器内置的 [css] [less] [scss] 校验和此扩展 [stylelint] 报告相同的错误
"css.validate": false,
"less.validate": false,
"scss.validate": false,
// 保存时使用 eslint 和 stylelint 进行修复
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
// 默认会对很多文件进行检测,这是不必要的,我们只让他检测样式
"stylelint.validate": [
"css",
"html",
"less",
"postcss",
"sass",
"scss",
"source.css.styled",
"styled-css",
],
以上,我们的目标已经达成啦!
Prettier
代码格式化工具。很多同学都接触过这个工具,我个人深入了解了一下这个工具,以下是我的个人见解。先看下 Prettier 官方的一段话吧。
So why choose the “Prettier style guide” over any other random style guide? Because Prettier is the only “style guide” that is fully automatic. Even if Prettier does not format all code 100% the way you’d like, it’s worth the “sacrifice” given the unique benefits of Prettier, don’t you think?
可以看到,这个工具旨在让不同公司不同团队不需要考虑代码规范,实现自动化保存格式化。牺牲掉个性化内容。
但是往往不同的团队对规则的使用是不一致的,如果强制所有文件都使用 prettier
自动格式化,会出现与公司配置的代码规范检查工具(例如 ESLint) 冲突的情况。实际表现为自动保存之后,依然出现 ESLint 格式报错。
想让 prettier
生效,需要我们在 VSCode 里配置:
// 所有文件都使用 prettier 格式化
"editor.defaultFormatter": "esbenp.prettier-vscode",
// 只对 js 文件使用 prettier
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
// 所有文件都不指定自动格式化方式
"editor.defaultFormatter": null,
// js文件不指定自动格式化方式
"[javascript]": {
"editor.defaultFormatter": null
}
可以使用 .prettierrc
文件、VSCode 的 setting.json
、.editorConfig
来配置 prettier
。
推荐不常使用的文件类型,使用 prettier
去格式化。js,json,jsx,html,css,less,vue
等这些文件,使用工程统一的规范去格式化。
所以,我觉得完全可以卸载它。不知道你怎么看呢?
以上就是全部内容了,希望对你有所帮助~
看完两件事
如果你觉得这篇内容对你挺有启发,我想邀请你帮我两件小事
1.点个「在看」,让更多人也能看到这篇内容(点了「在看」,bug -1 ????)
2.关注公众号「政采云前端团队」,持续为你推送精选好文
招贤纳士
政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 50 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。
如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com
上一篇: Mysql 故障与优化
下一篇: 编制材料编码(信息收集)
推荐阅读
-
[姿势估计] 实践记录:使用 Dlib 和 mediapipe 进行人脸姿势估计 - 本文重点介绍方法 2):方法 1:基于深度学习的方法:。 基于深度学习的方法:基于深度学习的方法利用深度学习模型,如卷积神经网络(CNN)或递归神经网络(RNN),直接从人脸图像中学习姿势估计。这些方法能够学习更复杂的特征表征,并在大规模数据集上取得优异的性能。方法二:基于二维校准信息估计三维姿态信息(计算机视觉 PnP 问题)。 特征点定位:人脸姿态估计的第一步是通过特征点定位来检测和定位人脸的关键点,如眼睛、鼻子和嘴巴。这些关键点提供了人脸的局部结构信息,可用于后续的姿势估计。 旋转表示:常见的旋转表示方法包括欧拉角和旋转矩阵。欧拉角通过三个旋转角度(通常是俯仰、偏航和滚动)描述头部的旋转姿态。旋转矩阵是一个 3x3 矩阵,表示头部从一个坐标系到另一个坐标系的变换。 三维模型重建:根据特征点的定位结果,三维人脸模型可用于姿势估计。通过将人脸的二维图像映射到三维模型上,可以估算出人脸的旋转和平移信息。这就需要建立人脸的三维模型,然后通过优化方法将模型与特征点对齐,从而获得姿势估计结果。 特征点定位 特征点定位是用于检测人脸关键部位的五官基础部分,还有其他更多的特征点表示方法,大家可以参考我上一篇文章中介绍的特征点检测方案实践:人脸校正二次定位操作来解决人脸校正的问题,客户在检测关键点的代码上略有修改,坐标转换部分客户见上图 def get_face_info(image). img_copy = image.copy image.flags.writeable = False image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) results = face_detection.process(image) # 在图像上绘制人脸检测注释。 image.flags.writeable = True image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) box_info, facial = None, None if results.detections: for detection in results. for detection in results.detections: mp_drawing.Drawing.detection = 无 mp_drawing.draw_detection(image, detection) 面部 = detection.location_data.relative_keypoints 返回面部 在上述代码中,返回的数据是五官(6 个关键点的坐标),这是用 mediapipe 库实现的,下面我们可以尝试用另一个库:dlib 来实现。 使用 dlib 使用 Dlib 库在 Python 中实现人脸关键点检测的步骤如下: 确保已安装 Dlib 库,可使用以下命令: pip install dlib 导入必要的库: 加载 Dlib 的人脸检测器和关键点检测器模型: 读取图像并将其灰度化: 使用人脸检测器检测图像中的人脸: 对检测到的人脸进行遍历,并使用关键点检测器检测人脸关键点: 显示绘制了关键点的图像: 以下代码将参数 landmarks_part 添加到要返回的关键点坐标中。
-
使用这些配置来规范和格式化您的代码
-
使用这些配置来规范和格式化您的代码
-
趣谈留言队列,搞清楚留言队列到底是什么!-说到消息队列,洪觉大概能猜到人们听到消息队列的反应,大致可以分为以下几类人。 第一类人,懵懵懂懂,刚上大学接触编程,还没用过消息队列,甚至还以为消息队列就是代码里面要新建一个List之类的;第二类人,听过消息队列,了解消息队列,但具体是什么还不是太明白,只知道一说到消息队列,脑海里马上出现了三组词,削峰、异步、解耦;第三类人,用过消息队列,对它有一定了解,但不知道为什么要这样设计,消息队列有什么样的前世今生,是如何演化到现在的模式的?**第四类人,已经对消息队列有了足够的了解,可以阅读本帖作为复习和温习。**你属于哪一类?无论你对消息队列了解多少,读完这篇文章后,我相信你都会有所收获。 什么是消息队列?我们为什么要使用消息队列?真的只是因为它看起来很勉强、很常用吗?当然不是,一项技术的出现往往是为了解决某种痛点,我们就从这个痛点出发,看看消息队列到底是为了解决什么问题而诞生的。 相信大家在工作之前,或者工作中接触单片机的次数会多一点,不管什么业务都一股脑塞进一个系统里,这种情况下接触消息队列的场景会比较少。但随着业务的增长,量上去了,单机系统就很难维护了,也扛不住并发量的增长,就需要把原来的单体应用拆分成多个服务。例如,牛奇网采用分布式架构,将原来的单体系统拆分成用户服务、题库服务、求职服务、论坛服务等,每个分布式节点都有一个集群,保证高可用性。 那虽然在这样的微服务架构下,如果某个核心业务并发量过大,系统就扛不住了。比如淘宝、淘票票、拼多多、京东等电商场景中的支付场景,你在某宝下单并支付后,调用支付服务,完成支付后,还需要更新订单的状态,这个时候就需要调用订单服务,那我们平时也下单,除了简单完成这些操作外,还会给你相应的积分;商家也会收到订单消息,并给您发送旺旺消息,确认订单无误;同时,也会给您发送消息,确认订单无误。确认订单无误;同时您还可以查看您的物流状态;还有系统为了给您推荐更适合您的商品,会根据您的订单做类似的推荐等等,我说的这些都是当我们下单后,肉眼可以感知到系统所做的动作。 **一个支付动作如果还需要调用那么多服务,等他们响应成功,最后再告诉用户你支付成功了,用户在系统中的整个体验会非常糟糕。**设想一下,假设请求服务+处理请求+响应总共需要 50ms,我们上面列出的场景:支付服务、订单服务、积分服务、商家服务、物流服务、推荐服务,总共需要 300ms。
-
.NET高级面试指南 Topic XVIII [ 介绍外观模式(Appearance Pattern),该模式提供了一个隐藏系统复杂性的简化界面 ]。- 简化复杂系统:当系统具有复杂的子系统结构时,可以使用外观模式来简化界面。提供统一界面:当客户端需要访问多个子系统时,可以使用外观模式提供统一界面。 外观模式在现代软件开发中得到广泛应用,尤其是在复杂系统中。例如 图形用户界面库:许多图形用户界面库(如 Qt、GTK+ 等)都使用外观模式来隐藏底层的复杂性,并为开发人员提供简单的界面来创建用户界面。 操作系统接口:操作系统中的系统调用和应用程序接口通常也使用外观模式来隐藏底层硬件和系统的复杂性,为应用程序提供访问系统资源的简单接口。企业应用程序:在可能涉及多个子系统的大型企业应用程序中,外观模式可用于封装这些子系统,并为客户端提供统一的使用界面。 网络框架:许多网络框架(如 ASP.NET MVC、Spring MVC 等)也使用外观模式来隐藏底层的复杂性,并为开发人员提供简单的接口来处理 HTTP 请求和响应。 集成开发环境(IDE):集成开发环境通常包含代码编辑器、编译器、调试器等多种功能。外观模式可用于封装这些功能,并为开发人员提供开发软件的简单界面。 代码示例:
-
Android 开发中 nodpi、xhdpi、hdpi、mdpi、ldpi 的概念 - 术语和概念 屏幕尺寸 屏幕的物理尺寸,基于屏幕的对角线长度(如 2.8 英寸、3.5 英寸)。 简而言之,安卓系统将所有屏幕尺寸简化为三大类:大、普通和小。 程序可以为这三种屏幕尺寸提供三种不同的布局选项,然后系统会以合适的方式将布局选项呈现到相应的屏幕上,这个过程不需要程序员用代码进行干预。 屏幕纵横比 屏幕的物理长度与物理宽度之比。程序只需使用系统提供的资源分类器 long(长)和 notlong(不长),就能为具有特定长宽比的屏幕提供配制材料。 分辨率 屏幕的像素总数。请注意,分辨率并不意味着长宽比,尽管在大多数情况下,分辨率表示为 "宽度 x 长度"。在安卓系统中,程序一般不直接处理分辨率。 密度 根据屏幕分辨率,沿屏幕宽度和长度排列的像素数量。 密度较低的屏幕在长度和宽度方向上的像素都相对较少,而密度较高的屏幕通常会在同一区域内排列很多甚至非常非常多的像素。屏幕的密度非常重要;例如,一个界面元素(如按钮)的长度和宽度以像素为单位,在低密度屏幕上会显得很大,但在高密度屏幕上就会显得很小。 独立于密度的像素(DIP)是指程序用来定义界面元素的抽象意义上的像素。它作为一个与实际密度无关的单位,帮助程序员构建布局方案(界面元素的宽度、高度和位置)。 与密度无关的像素在逻辑上与像素密度为 160 DPI 的屏幕上的像素大小相同,而 160 DPI 是安卓平台默认的显示设备。在运行时,平台会以目标屏幕的密度为基准,"透明 "地处理所有所需的 DIP 缩放操作。要将与密度无关的像素转换为屏幕像素,可以使用一个简单的公式:像素 = DIP * (密度 / 160)。例如,在 240 DPI 的屏幕上,1 个 DIP 等于 1.5 个物理像素。强烈建议使用 DIP 来定义程序界面的布局,因为这样可以确保用户界面在所有分辨率的屏幕上都能正常显示。 为了简化程序员在面对各种分辨率时的麻烦,也为了让各种分辨率的平台都能直接运行这些程序,Android 平台将所有屏幕以密度和分辨率作为分类方式,分别分为三类:- 三大尺寸:大、普通、小;- 三种不同密度:高(hdpi)、中(mdpi)和低(ldpi)。DPI 表示 "每英寸点数",即每英寸的像素数。如果需要,程序可以为不同的屏幕尺寸提供不同的资源(主要是布局),为不同的屏幕密度提供不同的资源(主要是位图)。除此之外,程序无需对屏幕尺寸或密度进行任何额外处理。执行时,平台会根据屏幕本身的尺寸和密度特性自动加载相应的资源,并将其从逻辑像素(DIP,用于定义界面布局)转换为屏幕上的物理像素。
-
Java 类加载器的作用 - 简介:类加载器是 Java™ 中一个非常重要的概念。类加载器负责将 Java 类的字节码加载到 Java 虚拟机中。本文首先详细介绍了 Java 类加载器的基本概念,包括代理模型、加载类的具体过程和线程上下文类加载器等。然后介绍了如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™ 中的应用。 类加载器是 Java 语言的一项创新,也是 Java 语言广受欢迎的重要原因之一。它允许将 Java 类动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 开始出现,最初是为了满足 Java Applets 的需求而开发的,Java Applets 需要从远程位置下载 Java 类文件并在浏览器中执行。现在,类加载器已广泛应用于网络容器和 OSGi。一般来说,Java 应用程序的开发人员不需要直接与类加载器交互;Java 虚拟机的默认行为足以应对大多数情况。但是,如果遇到需要与类加载器交互的情况,而您又不太了解类加载器的机制,就很容易花费大量时间调试异常,如 ClassNotFoundException 和 NoClassDefFoundError。本文将详细介绍 Java 的类加载器,帮助读者深入理解 Java 语言中的这一重要概念。下面先介绍一些基本概念。 类加载器的基本概念 顾名思义,类加载器用于将 Java 类加载到 Java 虚拟机中。一般来说,Java 虚拟机以如下方式使用 Java 类:Java 源程序(.java 文件)经 Java 编译器编译后转换为 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码并将其转换为 java.lang 实例。每个实例都用来表示一个 Java 类。通过该实例的 newInstance 方法创建该类的对象。实际情况可能更加复杂,例如,Java 字节代码可能是由工具动态生成或通过网络下载的。 基本上,所有类加载器都是 java.lang.ClassLoader 类的实例。下面将详细介绍这个 Java 类。 java.lang.ClassLoader 类简介 java.lang.ClassLoader 类的基本职责是根据给定类的名称为其查找或生成相应的字节码,然后根据这些字节码定义一个 Java 类,即 java.lang.Class 类的实例。除此之外,ClassLoader 还负责加载 Java 应用程序所需的资源,如图像文件和配置文件。不过,本文只讨论它加载类的功能。为了履行加载类的职责,ClassLoader 提供了许多方法,其中比较重要的方法如表 1 所示。下文将详细介绍这些方法。 表 1.与加载类相关的 ClassLoader 方法
-
腾讯视频直播 02-推流-美颜滤镜 同样,腾讯云提供了 setBeautyFilter 方法来设置美颜风格、磨皮程度、美白程度和泛红程度 //style 磨皮风格:0:平滑 1:自然 2:朦胧 //美容级别:0-9。值为 0 时关闭美颜效果。默认值:0,关闭美颜效果。 //美白级别:取值 0-9。值为 0 时,将关闭美白效果。默认值:0,关闭美白效果。 //ruddyLevel:取值范围为 0-9。值为 0 时关闭美白效果。默认值:0,关闭美白效果。 public boolean setBeautyFilter(int style, int beautyLevel, int whiteningLevel, int ruddyLevel);; public boolean setBeautyFilter(int style, int beautyLevel, int whiteningLevel, int ruddyLevel) 滤镜 setFilter 方法可以设置滤镜效果,滤镜本身是一个直方图文件。setSpecialRatio 方法可以设置滤镜的程度,从 0 到 1,越大滤镜效果越明显,默认值为 0.5。 Bitmap bitmap = BitmapUtils.decodeResource(getResources, R.drawable.langman); if (mLivePusher) if (mLivePusher ! = null) { mLivePusher.setFilter(bmp); } 控制摄像头 腾讯云 sdk 默认为前置摄像头(可以通过修改 TXLivePushConfig 的配置函数 setFrontCamera 来修改默认值),调用一次 switchCamera 就切换一次,注意切换摄像头前要确保 TXLivePushConfig 和 TXLivePusher 对象已经初始化。 mLivePushConfig.setFrontCamera(true); // 默认前置摄像头。 mLivePusher.switchCamera; //切换摄像头。 ⑦ 设置徽标水印 腾讯视频云目前支持两种设置水印的方式:一种是在流媒体 SDK 中设置水印,原理是在 SDK 中对视频进行编码前在画面中设置水印。另一种方式是在云端设置水印,即由云端解析视频并添加水印标识。 建议使用 SDK 添加水印,因为在云端添加水印会有问题。下面是添加水印的 SDK 介绍: //设置视频水印 mLivePushConfig.setWatermark(BitmapFactory.decodeResource(getResources,R.drawable.watermark), 10, 10); // 最后两个参数是视频的水印。 //最后两个参数是水印位置的 X 轴和 Y 轴坐标。 mLivePusher.setConfig(mLivePushConfig); 如果需要对水印图像的位置进行模型适配,则需要调用水印规范化接口。 /设置视频水印 mLivePushConfig.setWatermark(mBitmap, 0.02f, 0.05f, 0.2f); //参数为水印图像。 //参数包括水印图像的位图、水印位置的 X 轴坐标、水印位置的 Y 轴坐标和水印宽度。后三个参数的范围是 [0,1]。 // 最后两个参数是水印位置的 X 轴坐标和 Y 轴坐标。 mLivePusher.setConfig(mLivePushConfig); TXLivePushConfig 中的 setHardwareAcceleration 方法可以启用或禁用硬件编码。 if (mHWVideoEncode){ if (mLivePushConfig ! = null) { if (Build.VERSION.SDK_INT < 18){ Toast.makeText(getApplicationContext, "Hardware acceleration failed, current phone API level is too low (min 18)"、 Toast.LENGTH_SHORT).show; mHWVideoEncode = false; } } } } mLivePushConfig.setHardwareAcceleration(mHWVideoEncode ? TXLiveConstants.ENCODE_VIDEO_HARDWARE : TXLiveConstants.ENCODE_VIDEO_SOFTWARE); mLivePusher.setConfig(mLivePushConfig); // 如果您不确定何时启用硬件加速,建议将其设置为 ENCODE_VIDEO_AUTO。 // 默认情况下启用软件编码,但如果手机的 CPU 使用率超过 80% 或帧速率为 10,SDK 将自动切换到硬件编码。 ⑨ 后台推流 在常规模式下,一旦应用程序进入后台,摄像头捕捉数据的能力就会被 Android 禁用,这意味着 SDK 无法继续捕捉和编码音频和视频数据。如果我们什么都不做,故事就会按照下面的脚本发展: 阶段 1(背景剪切后 10 秒 ->)- CDN 无法将视频流传输给观众,因为没有数据,观众看到的是主帧。 阶段 2(10 秒-> 70 秒)--观众一方的播放器因无法接收到直播流而退出,房间里空无一人。 第 3 阶段(70 秒后)--服务器直接断开了推送流媒体的 RTMP 链接,主播需要重新打开直播才能继续。 主播可能只是短暂地接了一个紧急电话,但各云提供商的安全措施会迫使主播的直播提前结束。 1) 设置 setPauseFlag 在开始推流之前,使用 TXLivePushConfig 的 setPauseImg 接口设置一个等待图像,其含义建议为 "主播将暂时离开,稍后再回来"。
-
@Validated和@Valid区别-1.分组 @Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制。没有添加分组属性时,默认验证没有分组的验证属性。 伪代码如下: public interface First{ } public interface Second{ } public class UserModel { @NotNull(message = "{id.empty}", groups = { First.class }) private int id; @NotNull(message = "{username.empty}", groups = { First.class, Second.class }) private String username; @NotNull(message = "{content.empty}", groups = { First.class, Second.class }) private String content; } public String save(@Validated( { Second.class }) UserModel userModel, BindingResult result) { if (result.hasErrors) { return "validate/error"; } return "redirect:/success"; } 对一个参数需要多种验证方式时,也可通过分配不同的组达到目的。例: @NotEmpty(groups = { First.class }) @Size(min = 3, max = 8, groups = { Second.class }) private String name; 分组还支持组序列 默认情况下,不同组别的约束验证是无序的,然而在某些情况下,约束验证的顺序却很重要,如下面两个例子:(1)第二个组中的约束验证依赖于一个稳定状态来运行,而这个稳定状态是由第一个组来进行验证的。(2)某个组的验证比较耗时,CPU 和内存的使用率相对比较大,最优的选择是将其放在最后进行验证。因此,在进行组验证的时候尚需提供一种有序的验证方式,这就提出了组序列的概念。 一个组可以定义为其他组的序列,使用它进行验证的时候必须符合该序列规定的顺序。在使用组序列验证的时候,如果序列前边的组验证失败,则后面的组将不再给予验证。 public interface GroupA { } public interface GroupB { } @GroupSequence( { GroupA.class, GroupB.class }) public interface Group { } public @ResponseBody String addPeople(@Validated({Group.class}) People p,BindingResult result) { if(result.hasErrors) { return "0"; } return "1"; } @Valid:作为标准JSR-303规范,还没有吸收分组的功能。 2.注解地方 @Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上 @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上 两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。 3.嵌套验证 在比较两者嵌套验证时,先说明下什么叫做嵌套验证。 比如我们现在有个实体叫做Item: public class Item { @NotNull(message = "id不能为空") @Min(value = 1, message = "id必须为正整数") private Long id; @NotNull(message = "props不能为空") @Size(min = 1, message = "至少要有一个属性") private List<Prop> props; } Item带有很多属性,属性里面有:pid、vid、pidName和vidName,如下所示: public class Prop { @NotNull(message = "pid不能为空") @Min(value = 1, message = "pid必须为正整数") private Long pid; @NotNull(message = "vid不能为空") @Min(value = 1, message = "vid必须为正整数") private Long vid; @NotBlank(message = "pidName不能为空") private String pidName; @NotBlank(message = "vidName不能为空") private String vidName; } 属性这个实体也有自己的验证机制,比如pid和vid不能为空,pidName和vidName不能为空等。 现在我们有个ItemController接受一个Item的入参,想要对Item进行验证,如下所示: @RestController public class ItemController { @RequestMapping("/item/add") public void addItem(@Validated Item item, BindingResult bindingResult) { doSomething; } } 在上图中,如果Item实体的props属性不额外加注释,只有@NotNull和@Size,无论入参采用@Validated还是@Valid验证,Spring Validation框架只会对Item的id和props做非空和数量验证,不会对props字段里的Prop实体进行字段验证,也就是@Validated和@Valid加在方法参数前,都不会自动对参数进行嵌套验证。也就是说如果传的List中有Prop的pid为空或者是负数,入参验证不会检测出来。 为了能够进行嵌套验证,必须手动在Item实体的props字段上明确指出这个字段里面的实体也要进行验证。由于@Validated不能用在成员属性(字段)上,但是@Valid能加在成员属性(字段)上,而且@Valid类注解上也说明了它支持嵌套验证功能,那么我们能够推断出:@Valid加在方法参数时并不能够自动进行嵌套验证,而是用在需要嵌套验证类的相应字段上,来配合方法参数上@Validated或@Valid来进行嵌套验证。 我们修改Item类如下所示: public class Item { @NotNull(message = "id不能为空") @Min(value = 1, message = "id必须为正整数") private Long id; @Valid // 嵌套验证必须用@Valid @NotNull(message = "props不能为空") @Size(min = 1, message = "props至少要有一个自定义属性") private List<Prop> props; } 然后我们在ItemController的addItem函数上再使用@Validated或者@Valid,就能对Item的入参进行嵌套验证。此时Item里面的props如果含有Prop的相应字段为空的情况,Spring Validation框架就会检测出来,bindingResult就会记录相应的错误。 总结一下@Validated和@Valid在嵌套验证功能上的区别:
-
【Netty】「萌新入门」(七)ByteBuf 的性能优化-堆内存的分配和释放都是由 Java 虚拟机自动管理的,这意味着它们可以快速地被分配和释放,但是也会产生一些开销。 直接内存需要手动分配和释放,因为它由操作系统管理,这使得分配和释放的速度更快,但是也需要更多的系统资源。 另外,直接内存可以映射到本地文件中,这对于需要频繁读写文件的应用程序非常有用。 此外,直接内存还可以避免在使用 NIO 进行网络传输时发生数据拷贝的情况。在使用传统的 I/O 时,数据必须先从文件或网络中读取到堆内存中,然后再从堆内存中复制到直接缓冲区中,最后再通过 SocketChannel 发送到网络中。而使用直接缓冲区时,数据可以直接从文件或网络中读取到直接缓冲区中,并且可以直接从直接缓冲区中发送到网络中,避免了不必要的数据拷贝和内存分配。 通过 ByteBufAllocator.DEFAULT.directBuffer 方法来创建基于直接内存的 ByteBuf: ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); 通过 ByteBufAllocator.DEFAULT.heapBuffer 方法来创建基于堆内存的 ByteBuf: ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); 注意: 直接内存是一种特殊的内存分配方式,可以通过在堆外申请内存来避免 JVM 堆内存的限制,从而提高读写性能和降低 GC 压力。但是,直接内存的创建和销毁代价昂贵,因此需要慎重使用。 此外,由于直接内存不受 JVM 垃圾回收的管理,我们需要主动释放这部分内存,否则会造成内存泄漏。通常情况下,可以使用 ByteBuffer.clear 方法来释放直接内存中的数据,或者使用 ByteBuffer.cleaner 方法来手动释放直接内存空间。 测试代码: public static void testCreateByteBuf { ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(16); System.out.println(buf.getClass); ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); System.out.println(heapBuf.getClass); ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); System.out.println(directBuf.getClass); } 运行结果: class io.netty.buffer.PooledUnsafeDirectByteBuf class io.netty.buffer.PooledUnsafeHeapByteBuf class io.netty.buffer.PooledUnsafeDirectByteBuf 池化技术 在 Netty 中,池化技术指的是通过对象池来重用已经创建的对象,从而避免了频繁地创建和销毁对象,这种技术可以提高系统的性能和可伸缩性。 通过设置 VM options,来决定池化功能是否开启: -Dio.netty.allocator.type={unpooled|pooled} 在 Netty 4.1 版本以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现; 这里我们使用非池化功能进行测试,依旧使用的是上面的测试代码 testCreateByteBuf,运行结果如下所示: class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf 可以看到,ByteBuf 类由 PooledUnsafeDirectByteBuf 变成了 UnpooledUnsafeDirectByteBuf; 在没有池化的情况下,每次使用都需要创建新的 ByteBuf 实例,这个操作会涉及到内存的分配和初始化,如果是直接内存则代价更为昂贵,而且频繁的内存分配也可能导致内存碎片问题,增加 GC 压力。 使用池化技术可以避免频繁内存分配带来的开销,并且重用池中的 ByteBuf 实例,减少了内存占用和内存碎片问题。另外,池化技术还可以采用类似 jemalloc 的内存分配算法,进一步提升分配效率。 在高并发环境下,池化技术的优点更加明显,因为内存的分配和释放都是比较耗时的操作,频繁的内存分配和释放会导致系统性能下降,甚至可能出现内存溢出的风险。使用池化技术可以将内存分配和释放的操作集中到预先分配的池中,从而有效地降低系统的内存开销和风险。 内存释放 当在 Netty 中使用 ByteBuf 来处理数据时,需要特别注意内存回收问题。 Netty 提供了不同类型的 ByteBuf 实现,包括堆内存(JVM 内存)实现 UnpooledHeapByteBuf 和堆外内存(直接内存)实现 UnpooledDirectByteBuf,以及池化技术实现的 PooledByteBuf 及其子类。 UnpooledHeapByteBuf:通过 Java 的垃圾回收机制来自动回收内存; UnpooledDirectByteBuf:由于 JVM 的垃圾回收机制无法管理这些内存,因此需要手动调用 release 方法来释放内存; PooledByteBuf:使用了池化机制,需要更复杂的规则来回收内存; 由于池化技术的特殊性质,释放 PooledByteBuf 对象所使用的内存并不是立即被回收的,而是被放入一个内存池中,待下次分配内存时再次使用。因此,释放 PooledByteBuf 对象的内存可能会延迟到后续的某个时间点。为了避免内存泄漏和占用过多内存,我们需要根据实际情况来设置池化技术的相关参数,以便及时回收内存; Netty 采用了引用计数法来控制 ByteBuf 对象的内存回收,在博文 「源码解析」ByteBuf 的引用计数机制 中将会通过解读源码的形式对 ByteBuf 的引用计数法进行深入理解; 每个 ByteBuf 对象被创建时,都会初始化为1,表示该对象的初始计数为1。 在使用 ByteBuf 对象过程中,如果当前 handler 已经使用完该对象,需要通过调用 release 方法将计数减1,当计数为0时,底层内存会被回收,该对象也就被销毁了。此时即使 ByteBuf 对象还在,其各个方法均无法正常使用。 但是,如果当前 handler 还需要继续使用该对象,可以通过调用 retain 方法将计数加1,这样即使其他 handler 已经调用了 release 方法,该对象的内存仍然不会被回收。这种机制可以有效地避免了内存泄漏和意外访问已经释放的内存的情况。 一般来说,应该尽可能地保证 retain 和 release 方法成对出现,以确保计数正确。