diff --git a/404.html b/404.html index 48224a2974..5934515a00 100644 --- a/404.html +++ b/404.html @@ -47,7 +47,7 @@

404

PAGE NOT FOUND

But if you don't change your direction, and if you keep looking, you may end up where you are heading.
- + \ No newline at end of file diff --git a/about/index.html b/about/index.html index 7f3310df0a..806507d004 100644 --- a/about/index.html +++ b/about/index.html @@ -54,7 +54,7 @@
头部图
📝 查尔斯的个人技术知识库,记录 & 分享个人碎片化、结构化、体系化的技术知识内容。

《警惕碎片化陷阱!如何让你的知识体系价值最大化?》

记录,对我们来说意味着什么?

把大脑从那些琐碎的小事中解放出来,去处理更精密复杂的任务?帮助记忆有价值的信息,避免遗忘?又或者是显化内容过滤重组的过程,提升梳理思路的效率......

记录的意义因人而异,但所有的笔记,在记录时都隐藏着一个不被注意到的重要前提,那就是希望知识能被再利用。

潜意识里,我们会觉得记录的创意灵感、知识碎片迟早有一天会派上用场,所以精心地管理个人知识库。但是,你曾记录的笔记被二次查看的频率有多高?你有没有翻阅过去的笔记却惊讶地发现完全不记得曾记录下这件事,收藏了这个知识点?你有没有发现自己难以用上之前记录的知识点?

当一篇笔记完全被遗忘在记忆角落时,它对你的“价值输出”就此停止。

-- 有道云笔记·笔记君

笔者的知识库是关于个人碎片化、结构化、体系化技术知识内容的记录。和大多数人一样,笔者也希望这些内容能被个人再次利用,哪怕没有,也希望能为 “有缘人” 或是后继者增加一些实际靠谱的个人技术经验。

不仅仅是在技术知识层面如此,笔者在各个方面也是一样。尤其是当笔者经历从未有过的程序时,例如:办理个人公积金的程序;为车辆办理上牌的程序。这种经验记录的欲望很强烈,有时候笔者也反复在想,为什么会有这种欲望?后来大概明白,很大程度上是因为吃够了 “吃一堑长一智” 的自我安慰罢了,甚至曾经想过写一个程序,能够记录各种各样的个人经验,让未来的自己或后继者多一些参考,多一些胸有成竹,少走一些弯路,这种意愿强烈到一定程度时又突然醒悟,这不就是个人版知乎吗?

我们继续说回个人知识库的意义,笔者记得有这样一个哲理故事:

《一杯水的重量》

哈佛大学有一位教授在课堂上倒了一杯清水,问学生:“这杯水有多重?”有的同学说8盎司(1盎司=28.35克),有的说12盎司,有的说16盎司。

可这位教授却对学生说:“你们说的重量,都与这杯水的重量无关。”

“这杯水的重量取决于我能坚持多久。我坚持一分钟,什么也没发生。我坚持一小时,我的手臂就会开始疼。我一整天都拿着它,我的手臂就会感到麻木。但实际上这杯水的重量没有任何变化,你长时间抓着它,只会让你感觉到更重。生活中的压力和烦恼就像这杯水,稍微想想没有问题,再多想一会,你就开始疼了。整天想着它们,你会感到瘫痪,什么都做不了。永远记住,要把水杯放下。”

从笔者个人来讲,维护知识库也是一种整理。笔者对杂乱的事物是抱有不喜态度的,无论是个人卫生,还是环境:房间、工位,亦或是任何记录。

杂乱的内容会引起笔者思绪上的混乱,被迫转移注意力,所以笔者尽可能的在各种方面定期进行整理、折腾,将知识整理好,不用再担心知识在记忆中遗忘,不用再担心掌握的知识无迹可寻,这样笔者就可以获得思绪上的梳理、 “放下”,可以更好的 “轻装前行”。😄

尾部图
- + \ No newline at end of file diff --git a/about/me.html b/about/me.html index 39fd22895a..d45a0b95ce 100644 --- a/about/me.html +++ b/about/me.html @@ -54,7 +54,7 @@
头部图

个人主页  GitHub个人主页  码云个人主页  CSDN个人主页  掘金个人主页  语雀个人主页 


  • 👋 Hi, I'm Charles7c
  • 🔭 I'm currently working on backend development
  • 📫 How to reach me: charles7c@126.com
  • 📖 My motto: “东隅已逝,桑榆非晚。”

后端技术栈

Spring  Spring Boot  MySQL  MariaDB  PostgreSQL  Oracle  Microsoft SQL Server  Redis  MongoDB  RabbitMQ  Solr  ElasticSearch  Logstash  Kibana  Kafka  Consul  Tomcat  JUnit5  Liquibase  Maven  Gradle  Spring Security  Hibernate  JSON  JWT  Java  Python  Android  Go  GraphQL 

前端技术栈

Vue3  TypeScript  Ant Design  Node.js  Vite  Webpack  NPM  Axios  ESLint  jQuery  BootStrap  ECharts  JavaScript  HTML5  CSS3  Tailwind CSS  Less 

DevOps

Git  GitHub  Gitee  gitlab  GitHub Actions  Jenkins  SonarQube  Docker  Harbor  Kubernetes  CentOS  Ubuntu 

运维技术栈

阿里云  Nginx  VMware  Prometheus  Grafana  Ansible  Lua 

测试技术栈

Postman  JMeter 

开发工具

Intellij IDEA  Eclipse  WebStorm  PyCharm  Android Studio  VSCode 

其他

Markdown  WordPress  GitHub Pages  Adobe Photoshop 

尾部图
- + \ No newline at end of file diff --git a/archives.html b/archives.html index fe6d4bd69b..68714186c8 100644 --- a/archives.html +++ b/archives.html @@ -55,7 +55,7 @@
- + \ No newline at end of file diff --git "a/assets/categories_fragments_2019_12_28_\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.md.5f862f76.js" "b/assets/categories_fragments_2019_12_28_\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.md.13a840ae.js" similarity index 94% rename from "assets/categories_fragments_2019_12_28_\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.md.5f862f76.js" rename to "assets/categories_fragments_2019_12_28_\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.md.13a840ae.js" index c26607f2db..67df8af8d4 100644 --- "a/assets/categories_fragments_2019_12_28_\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.md.5f862f76.js" +++ "b/assets/categories_fragments_2019_12_28_\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.md.13a840ae.js" @@ -1,10 +1,10 @@ -import{_ as d}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as A,v as c,b as D,F as s,L as a,t as n,O as E,R as p,M as r,C,B as F}from"./chunks/framework.7eed59db.js";import"./chunks/md5.34df8461.js";const Z=JSON.parse('{"title":"个人 SQL 优化技巧","description":"","frontmatter":{"title":"个人 SQL 优化技巧","author":"查尔斯","date":"2019/12/28 10:00","isTop":true,"categories":["杂碎逆袭史"],"tags":["SQL","SQL优化"]},"headers":[],"relativePath":"categories/fragments/2019/12/28/个人SQL优化技巧.md","filePath":"categories/fragments/2019/12/28/个人SQL优化技巧.md","lastUpdated":1671423875000}'),h={name:"categories/fragments/2019/12/28/个人SQL优化技巧.md"},u={id:"个人-sql-优化技巧",tabindex:"-1"},B=s("a",{class:"header-anchor",href:"#个人-sql-优化技巧","aria-label":'Permalink to "个人 SQL 优化技巧 "'},"​",-1),_=s("h2",{id:"查询优化",tabindex:"-1"},[a("查询优化 "),s("a",{class:"header-anchor",href:"#查询优化","aria-label":'Permalink to "查询优化"'},"​")],-1),b={id:"如果确定结果只有一条-使用-limit-1",tabindex:"-1"},g=s("a",{class:"header-anchor",href:"#如果确定结果只有一条-使用-limit-1","aria-label":'Permalink to "如果确定结果只有一条,使用 LIMIT 1 "'},"​",-1),m=p('

我们在根据一个或多个条件查询数据时,如果确定查询结果只有一条,可以在结尾处添加 LIMIT 1 进行限制。

这样既可以让 EXPLAIN 中的 type 达到 const 类型,又可以免去担忧在程序中出现接收是单个对象却返回了一个集合对象的异常问题。

sql
# email 不是主键,也没有设置唯一约束,根据熵增定律,查询结果是有可能会出现多条的\nSELECT * FROM `sys_user` WHERE `email` = 'charles7c@126.com' LIMIT 1;
# email 不是主键,也没有设置唯一约束,根据熵增定律,查询结果是有可能会出现多条的\nSELECT * FROM `sys_user` WHERE `email` = 'charles7c@126.com' LIMIT 1;
sql
# user_id 是主键,主键是非空唯一的,那么不需要添加 LIMIT 进行限制\nSELECT * FROM `sys_user` WHERE `user_id` = 1;
# user_id 是主键,主键是非空唯一的,那么不需要添加 LIMIT 进行限制\nSELECT * FROM `sys_user` WHERE `user_id` = 1;
',3),v={id:"避免隐式类型转换",tabindex:"-1"},N=s("a",{class:"header-anchor",href:"#避免隐式类型转换","aria-label":'Permalink to "避免隐式类型转换 "'},"​",-1),T=p('

我们在使用 MySQL 时,或多或少都感受过 MySQL 的隐式类型转换。例如:user_id 是整数类型,但是依然可以使用字符串类型数据来进行判断。MySQL 帮你做完这种隐式类型转换是有代价的,什么代价呢? 索引不再生效了而已

sql
SELECT * FROM `sys_user` WHERE `user_id` = 10;
SELECT * FROM `sys_user` WHERE `user_id` = 10;
sql
SELECT * FROM `sys_user` WHERE `user_id` = '10';
SELECT * FROM `sys_user` WHERE `user_id` = '10';

数据库表设计

',3),f={id:"列名带上前缀",tabindex:"-1"},L=s("a",{class:"header-anchor",href:"#列名带上前缀","aria-label":'Permalink to "列名带上前缀 "'},"​",-1),M=p('

部分列名带上前缀或缩写,可以有效减少在连接查询、ORM 映射等场景下刻意起别名或思考区分不同的问题。

sql
CREATE TABLE `sys_customer` (\n  `customer_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '客户ID',\n  `customer_name` varchar(255) NOT NULL COMMENT '客户名称',\n  PRIMARY KEY (`customer_id`) USING BTREE,\n) COMMENT = '客户表';\n\nCREATE TABLE `sys_contact_user` (\n  `contact_user_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '联系人ID',\n  `contact_user_name` varchar(255) NOT NULL COMMENT '联系人名称',\n  `customer_id` bigint(20) UNSIGNED NOT NULL COMMENT '客户ID',\n  PRIMARY KEY (`contact_user_id`) USING BTREE,\n) COMMENT = '联系人表';\n\n# 连接查询,你完全不需要用脑去考虑到底是 c.`id` 还是 cu.`customer_id` 的问题,都是 `customer_id`\nSELECT * FROM `sys_customer` c \nLEFT JOIN `sys_contact_user` cu ON c.`customer_id` = cu.`customer_id`
CREATE TABLE `sys_customer` (\n  `customer_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '客户ID',\n  `customer_name` varchar(255) NOT NULL COMMENT '客户名称',\n  PRIMARY KEY (`customer_id`) USING BTREE,\n) COMMENT = '客户表';\n\nCREATE TABLE `sys_contact_user` (\n  `contact_user_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '联系人ID',\n  `contact_user_name` varchar(255) NOT NULL COMMENT '联系人名称',\n  `customer_id` bigint(20) UNSIGNED NOT NULL COMMENT '客户ID',\n  PRIMARY KEY (`contact_user_id`) USING BTREE,\n) COMMENT = '联系人表';\n\n# 连接查询,你完全不需要用脑去考虑到底是 c.`id` 还是 cu.`customer_id` 的问题,都是 `customer_id`\nSELECT * FROM `sys_customer` c \nLEFT JOIN `sys_contact_user` cu ON c.`customer_id` = cu.`customer_id`
',2),I={id:"非负数列添加unsigned无符号约束",tabindex:"-1"},k=s("a",{class:"header-anchor",href:"#非负数列添加unsigned无符号约束","aria-label":'Permalink to "非负数列添加UNSIGNED无符号约束 "'},"​",-1),O=p(`

在大部分的数据存储场景中,我们只会使用正整数,如果能确定该列为非负数,建议添加 UNSIGNED 无符号约束。

sql
# 不添加 UNSIGNED 约束,取值范围
+import{_ as d}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as A,v as c,b as D,F as s,L as a,t as n,O as E,R as p,M as r,C,B as F}from"./chunks/framework.7eed59db.js";import"./chunks/md5.34df8461.js";const z=JSON.parse('{"title":"个人 SQL 优化技巧","description":"","frontmatter":{"title":"个人 SQL 优化技巧","author":"查尔斯","date":"2019/12/28 10:00","isTop":true,"categories":["杂碎逆袭史"],"tags":["SQL","SQL优化"]},"headers":[],"relativePath":"categories/fragments/2019/12/28/个人SQL优化技巧.md","filePath":"categories/fragments/2019/12/28/个人SQL优化技巧.md","lastUpdated":1671423875000}'),h={name:"categories/fragments/2019/12/28/个人SQL优化技巧.md"},B={id:"个人-sql-优化技巧",tabindex:"-1"},u=s("a",{class:"header-anchor",href:"#个人-sql-优化技巧","aria-label":'Permalink to "个人 SQL 优化技巧 "'},"​",-1),_=s("h2",{id:"查询优化",tabindex:"-1"},[a("查询优化 "),s("a",{class:"header-anchor",href:"#查询优化","aria-label":'Permalink to "查询优化"'},"​")],-1),b={id:"如果确定结果只有一条-使用-limit-1",tabindex:"-1"},g=s("a",{class:"header-anchor",href:"#如果确定结果只有一条-使用-limit-1","aria-label":'Permalink to "如果确定结果只有一条,使用 LIMIT 1 "'},"​",-1),m=p('

我们在根据一个或多个条件查询数据时,如果确定查询结果只有一条,可以在结尾处添加 LIMIT 1 进行限制。

这样既可以让 EXPLAIN 中的 type 达到 const 类型,又可以免去担忧在程序中出现接收是单个对象却返回了一个集合对象的异常问题。

sql
# email 不是主键,也没有设置唯一约束,根据熵增定律,查询结果是有可能会出现多条的\nSELECT * FROM `sys_user` WHERE `email` = 'charles7c@126.com' LIMIT 1;
# email 不是主键,也没有设置唯一约束,根据熵增定律,查询结果是有可能会出现多条的\nSELECT * FROM `sys_user` WHERE `email` = 'charles7c@126.com' LIMIT 1;
sql
# user_id 是主键,主键是非空唯一的,那么不需要添加 LIMIT 进行限制\nSELECT * FROM `sys_user` WHERE `user_id` = 1;
# user_id 是主键,主键是非空唯一的,那么不需要添加 LIMIT 进行限制\nSELECT * FROM `sys_user` WHERE `user_id` = 1;
',3),v={id:"避免隐式类型转换",tabindex:"-1"},N=s("a",{class:"header-anchor",href:"#避免隐式类型转换","aria-label":'Permalink to "避免隐式类型转换 "'},"​",-1),T=p('

我们在使用 MySQL 时,或多或少都感受过 MySQL 的隐式类型转换。例如:user_id 是整数类型,但是依然可以使用字符串类型数据来进行判断。MySQL 帮你做完这种隐式类型转换是有代价的,什么代价呢? 索引不再生效了而已

sql
SELECT * FROM `sys_user` WHERE `user_id` = 10;
SELECT * FROM `sys_user` WHERE `user_id` = 10;
sql
SELECT * FROM `sys_user` WHERE `user_id` = '10';
SELECT * FROM `sys_user` WHERE `user_id` = '10';

数据库表设计

',3),f={id:"列名带上前缀",tabindex:"-1"},L=s("a",{class:"header-anchor",href:"#列名带上前缀","aria-label":'Permalink to "列名带上前缀 "'},"​",-1),M=p('

部分列名带上前缀或缩写,可以有效减少在连接查询、ORM 映射等场景下刻意起别名或思考区分不同的问题。

sql
CREATE TABLE `sys_customer` (\n  `customer_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '客户ID',\n  `customer_name` varchar(255) NOT NULL COMMENT '客户名称',\n  PRIMARY KEY (`customer_id`) USING BTREE,\n) COMMENT = '客户表';\n\nCREATE TABLE `sys_contact_user` (\n  `contact_user_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '联系人ID',\n  `contact_user_name` varchar(255) NOT NULL COMMENT '联系人名称',\n  `customer_id` bigint(20) UNSIGNED NOT NULL COMMENT '客户ID',\n  PRIMARY KEY (`contact_user_id`) USING BTREE,\n) COMMENT = '联系人表';\n\n# 连接查询,你完全不需要用脑去考虑到底是 c.`id` 还是 cu.`customer_id` 的问题,都是 `customer_id`\nSELECT * FROM `sys_customer` c \nLEFT JOIN `sys_contact_user` cu ON c.`customer_id` = cu.`customer_id`
CREATE TABLE `sys_customer` (\n  `customer_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '客户ID',\n  `customer_name` varchar(255) NOT NULL COMMENT '客户名称',\n  PRIMARY KEY (`customer_id`) USING BTREE,\n) COMMENT = '客户表';\n\nCREATE TABLE `sys_contact_user` (\n  `contact_user_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '联系人ID',\n  `contact_user_name` varchar(255) NOT NULL COMMENT '联系人名称',\n  `customer_id` bigint(20) UNSIGNED NOT NULL COMMENT '客户ID',\n  PRIMARY KEY (`contact_user_id`) USING BTREE,\n) COMMENT = '联系人表';\n\n# 连接查询,你完全不需要用脑去考虑到底是 c.`id` 还是 cu.`customer_id` 的问题,都是 `customer_id`\nSELECT * FROM `sys_customer` c \nLEFT JOIN `sys_contact_user` cu ON c.`customer_id` = cu.`customer_id`
',2),k={id:"非负数列添加unsigned无符号约束",tabindex:"-1"},I=s("a",{class:"header-anchor",href:"#非负数列添加unsigned无符号约束","aria-label":'Permalink to "非负数列添加UNSIGNED无符号约束 "'},"​",-1),O=p(`

在大部分的数据存储场景中,我们只会使用正整数,如果能确定该列为非负数,建议添加 UNSIGNED 无符号约束。

sql
# 不添加 UNSIGNED 约束,取值范围
 TINYINT:[-128, 127]
 # 添加 UNSIGNED 约束,取值范围
 TINYINT:[0, 255]
# 不添加 UNSIGNED 约束,取值范围
 TINYINT:[-128, 127]
 # 添加 UNSIGNED 约束,取值范围
-TINYINT:[0, 255]
`,2),x={id:"合理采用整数类型",tabindex:"-1"},S=s("a",{class:"header-anchor",href:"#合理采用整数类型","aria-label":'Permalink to "合理采用整数类型 "'},"​",-1),R=p('

例如:状态类的信息,状态再多能有多少个,采用 TINYINT 即可,减少存储空间占用。

下方表数据整理于:MySQL 5.7官方文档/数据类型/数值数据类型/整数类型

类型存储(字节)取值范围取值范围(无符号)
TINYINT1[-128, 127][0, 255]
SMALLINT2[-32768, 32767][0, 65535]
MEDIUMINT3[-8388608, 8388607][0, 16777215]
INT4[-2147483648, 2147483647][0, 4294967295]
BIGINT8[-2^63^, 2^63^-1][0, 2^64^-1]
',3),U={id:"布尔列采用bit类型",tabindex:"-1"},q=s("a",{class:"header-anchor",href:"#布尔列采用bit类型","aria-label":'Permalink to "布尔列采用bit类型 "'},"​",-1),Q=p(`

例如:是否删除这种只有两种状态的信息,在表设计时建议对该列设置 bit 类型(0表示否/假/false,1表示是/真/true),在程序语言中可以采用 boolean 类型对应。

sql
\`is_deleted\` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已删除(0否 1是)'
\`is_deleted\` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已删除(0否 1是)'
java
@Data
+TINYINT:[0, 255]
`,2),x={id:"合理采用整数类型",tabindex:"-1"},S=s("a",{class:"header-anchor",href:"#合理采用整数类型","aria-label":'Permalink to "合理采用整数类型 "'},"​",-1),R=p('

例如:状态类的信息,状态再多能有多少个,采用 TINYINT 即可,减少存储空间占用。

下方表数据整理于:MySQL 5.7官方文档/数据类型/数值数据类型/整数类型

类型存储(字节)取值范围取值范围(无符号)
TINYINT1[-128, 127][0, 255]
SMALLINT2[-32768, 32767][0, 65535]
MEDIUMINT3[-8388608, 8388607][0, 16777215]
INT4[-2147483648, 2147483647][0, 4294967295]
BIGINT8[-2^63^, 2^63^-1][0, 2^64^-1]
',3),U={id:"布尔列采用bit类型",tabindex:"-1"},q=s("a",{class:"header-anchor",href:"#布尔列采用bit类型","aria-label":'Permalink to "布尔列采用bit类型 "'},"​",-1),G=p(`

例如:是否删除这种只有两种状态的信息,在表设计时建议对该列设置 bit 类型(0表示否/假/false,1表示是/真/true),在程序语言中可以采用 boolean 类型对应。

sql
\`is_deleted\` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已删除(0否 1是)'
\`is_deleted\` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已删除(0否 1是)'
java
@Data
 public class User {
     /**
      * 是否已删除(0否 1是)
@@ -16,4 +16,4 @@ import{_ as d}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as A,v as c,b
      * 是否已删除(0否 1是)
      */
     private Boolean isDeleted;
-}

数据库设计

`,3),G={id:"采用utf8mb4编码",tabindex:"-1"},P=s("a",{class:"header-anchor",href:"#采用utf8mb4编码","aria-label":'Permalink to "采用utf8mb4编码 "'},"​",-1),Y=p('

如果要存储特殊字符(例如:emoij表情符),使用 utf8mb4 编码。

MySQL 5.5.3 后增加了一个新的编码: utf8mb4 ,其中 mb4 是 most bytes 4 的意思,用于兼容四字节的 unicode。

utf8mb4 是 utf8 的超集,除了将编码改为 utf8mb4 外不需要做其他转换。

设计数据库时如果想要允许用户使用特殊符号,最好使用 utf8mb4 编码来存储,使得数据库有更好的兼容性,但是这样设计会导致耗费更多的存储空间。

',1);function J(o,H,W,w,$,K){const l=r("Badge"),y=d,i=r("ClientOnly");return c(),D("div",null,[s("h1",u,[a("个人 SQL 优化技巧 "),n(l,{text:"持续更新",type:"warning"}),a(),B]),n(i,null,{default:E(()=>{var e,t;return[(((e=o.$frontmatter)==null?void 0:e.aside)??!0)&&(((t=o.$frontmatter)==null?void 0:t.showArticleMetadata)??!0)?(c(),C(y,{key:0,article:o.$frontmatter},null,8,["article"])):F("",!0)]}),_:1}),_,s("h3",b,[a("如果确定结果只有一条,使用 LIMIT 1 "),n(l,{text:"建议"}),a(),g]),m,s("h3",v,[a("避免隐式类型转换 "),n(l,{text:"强制",type:"danger"}),a(),N]),T,s("h3",f,[a("列名带上前缀 "),n(l,{text:"建议"}),a(),L]),M,s("h3",I,[a("非负数列添加UNSIGNED无符号约束 "),n(l,{text:"建议"}),a(),k]),O,s("h3",x,[a("合理采用整数类型 "),n(l,{text:"建议"}),a(),S]),R,s("h3",U,[a("布尔列采用bit类型 "),n(l,{text:"建议"}),a(),q]),Q,s("h3",G,[a("采用utf8mb4编码 "),n(l,{text:"建议"}),a(),P]),Y])}const X=A(h,[["render",J]]);export{Z as __pageData,X as default}; +}

数据库设计

`,3),P={id:"采用utf8mb4编码",tabindex:"-1"},Y=s("a",{class:"header-anchor",href:"#采用utf8mb4编码","aria-label":'Permalink to "采用utf8mb4编码 "'},"​",-1),Q=p('

如果要存储特殊字符(例如:emoij表情符),使用 utf8mb4 编码。

MySQL 5.5.3 后增加了一个新的编码: utf8mb4 ,其中 mb4 是 most bytes 4 的意思,用于兼容四字节的 unicode。

utf8mb4 是 utf8 的超集,除了将编码改为 utf8mb4 外不需要做其他转换。

设计数据库时如果想要允许用户使用特殊符号,最好使用 utf8mb4 编码来存储,使得数据库有更好的兼容性,但是这样设计会导致耗费更多的存储空间。

',1);function W(o,H,K,w,$,J){const l=r("Badge"),y=d,i=r("ClientOnly");return c(),D("div",null,[s("h1",B,[a("个人 SQL 优化技巧 "),n(l,{text:"持续更新",type:"warning"}),a(),u]),n(i,null,{default:E(()=>{var e,t;return[(((e=o.$frontmatter)==null?void 0:e.aside)??!0)&&(((t=o.$frontmatter)==null?void 0:t.showArticleMetadata)??!0)?(c(),C(y,{key:0,article:o.$frontmatter},null,8,["article"])):F("",!0)]}),_:1}),_,s("h3",b,[a("如果确定结果只有一条,使用 LIMIT 1 "),n(l,{text:"建议"}),a(),g]),m,s("h3",v,[a("避免隐式类型转换 "),n(l,{text:"强制",type:"danger"}),a(),N]),T,s("h3",f,[a("列名带上前缀 "),n(l,{text:"建议"}),a(),L]),M,s("h3",k,[a("非负数列添加UNSIGNED无符号约束 "),n(l,{text:"建议"}),a(),I]),O,s("h3",x,[a("合理采用整数类型 "),n(l,{text:"建议"}),a(),S]),R,s("h3",U,[a("布尔列采用bit类型 "),n(l,{text:"建议"}),a(),q]),G,s("h3",P,[a("采用utf8mb4编码 "),n(l,{text:"建议"}),a(),Y]),Q])}const V=A(h,[["render",W]]);export{z as __pageData,V as default}; diff --git "a/assets/categories_fragments_2019_12_28_\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.md.5f862f76.lean.js" "b/assets/categories_fragments_2019_12_28_\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.md.13a840ae.lean.js" similarity index 94% rename from "assets/categories_fragments_2019_12_28_\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.md.5f862f76.lean.js" rename to "assets/categories_fragments_2019_12_28_\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.md.13a840ae.lean.js" index c26607f2db..67df8af8d4 100644 --- "a/assets/categories_fragments_2019_12_28_\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.md.5f862f76.lean.js" +++ "b/assets/categories_fragments_2019_12_28_\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.md.13a840ae.lean.js" @@ -1,10 +1,10 @@ -import{_ as d}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as A,v as c,b as D,F as s,L as a,t as n,O as E,R as p,M as r,C,B as F}from"./chunks/framework.7eed59db.js";import"./chunks/md5.34df8461.js";const Z=JSON.parse('{"title":"个人 SQL 优化技巧","description":"","frontmatter":{"title":"个人 SQL 优化技巧","author":"查尔斯","date":"2019/12/28 10:00","isTop":true,"categories":["杂碎逆袭史"],"tags":["SQL","SQL优化"]},"headers":[],"relativePath":"categories/fragments/2019/12/28/个人SQL优化技巧.md","filePath":"categories/fragments/2019/12/28/个人SQL优化技巧.md","lastUpdated":1671423875000}'),h={name:"categories/fragments/2019/12/28/个人SQL优化技巧.md"},u={id:"个人-sql-优化技巧",tabindex:"-1"},B=s("a",{class:"header-anchor",href:"#个人-sql-优化技巧","aria-label":'Permalink to "个人 SQL 优化技巧 "'},"​",-1),_=s("h2",{id:"查询优化",tabindex:"-1"},[a("查询优化 "),s("a",{class:"header-anchor",href:"#查询优化","aria-label":'Permalink to "查询优化"'},"​")],-1),b={id:"如果确定结果只有一条-使用-limit-1",tabindex:"-1"},g=s("a",{class:"header-anchor",href:"#如果确定结果只有一条-使用-limit-1","aria-label":'Permalink to "如果确定结果只有一条,使用 LIMIT 1 "'},"​",-1),m=p('

我们在根据一个或多个条件查询数据时,如果确定查询结果只有一条,可以在结尾处添加 LIMIT 1 进行限制。

这样既可以让 EXPLAIN 中的 type 达到 const 类型,又可以免去担忧在程序中出现接收是单个对象却返回了一个集合对象的异常问题。

sql
# email 不是主键,也没有设置唯一约束,根据熵增定律,查询结果是有可能会出现多条的\nSELECT * FROM `sys_user` WHERE `email` = 'charles7c@126.com' LIMIT 1;
# email 不是主键,也没有设置唯一约束,根据熵增定律,查询结果是有可能会出现多条的\nSELECT * FROM `sys_user` WHERE `email` = 'charles7c@126.com' LIMIT 1;
sql
# user_id 是主键,主键是非空唯一的,那么不需要添加 LIMIT 进行限制\nSELECT * FROM `sys_user` WHERE `user_id` = 1;
# user_id 是主键,主键是非空唯一的,那么不需要添加 LIMIT 进行限制\nSELECT * FROM `sys_user` WHERE `user_id` = 1;
',3),v={id:"避免隐式类型转换",tabindex:"-1"},N=s("a",{class:"header-anchor",href:"#避免隐式类型转换","aria-label":'Permalink to "避免隐式类型转换 "'},"​",-1),T=p('

我们在使用 MySQL 时,或多或少都感受过 MySQL 的隐式类型转换。例如:user_id 是整数类型,但是依然可以使用字符串类型数据来进行判断。MySQL 帮你做完这种隐式类型转换是有代价的,什么代价呢? 索引不再生效了而已

sql
SELECT * FROM `sys_user` WHERE `user_id` = 10;
SELECT * FROM `sys_user` WHERE `user_id` = 10;
sql
SELECT * FROM `sys_user` WHERE `user_id` = '10';
SELECT * FROM `sys_user` WHERE `user_id` = '10';

数据库表设计

',3),f={id:"列名带上前缀",tabindex:"-1"},L=s("a",{class:"header-anchor",href:"#列名带上前缀","aria-label":'Permalink to "列名带上前缀 "'},"​",-1),M=p('

部分列名带上前缀或缩写,可以有效减少在连接查询、ORM 映射等场景下刻意起别名或思考区分不同的问题。

sql
CREATE TABLE `sys_customer` (\n  `customer_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '客户ID',\n  `customer_name` varchar(255) NOT NULL COMMENT '客户名称',\n  PRIMARY KEY (`customer_id`) USING BTREE,\n) COMMENT = '客户表';\n\nCREATE TABLE `sys_contact_user` (\n  `contact_user_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '联系人ID',\n  `contact_user_name` varchar(255) NOT NULL COMMENT '联系人名称',\n  `customer_id` bigint(20) UNSIGNED NOT NULL COMMENT '客户ID',\n  PRIMARY KEY (`contact_user_id`) USING BTREE,\n) COMMENT = '联系人表';\n\n# 连接查询,你完全不需要用脑去考虑到底是 c.`id` 还是 cu.`customer_id` 的问题,都是 `customer_id`\nSELECT * FROM `sys_customer` c \nLEFT JOIN `sys_contact_user` cu ON c.`customer_id` = cu.`customer_id`
CREATE TABLE `sys_customer` (\n  `customer_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '客户ID',\n  `customer_name` varchar(255) NOT NULL COMMENT '客户名称',\n  PRIMARY KEY (`customer_id`) USING BTREE,\n) COMMENT = '客户表';\n\nCREATE TABLE `sys_contact_user` (\n  `contact_user_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '联系人ID',\n  `contact_user_name` varchar(255) NOT NULL COMMENT '联系人名称',\n  `customer_id` bigint(20) UNSIGNED NOT NULL COMMENT '客户ID',\n  PRIMARY KEY (`contact_user_id`) USING BTREE,\n) COMMENT = '联系人表';\n\n# 连接查询,你完全不需要用脑去考虑到底是 c.`id` 还是 cu.`customer_id` 的问题,都是 `customer_id`\nSELECT * FROM `sys_customer` c \nLEFT JOIN `sys_contact_user` cu ON c.`customer_id` = cu.`customer_id`
',2),I={id:"非负数列添加unsigned无符号约束",tabindex:"-1"},k=s("a",{class:"header-anchor",href:"#非负数列添加unsigned无符号约束","aria-label":'Permalink to "非负数列添加UNSIGNED无符号约束 "'},"​",-1),O=p(`

在大部分的数据存储场景中,我们只会使用正整数,如果能确定该列为非负数,建议添加 UNSIGNED 无符号约束。

sql
# 不添加 UNSIGNED 约束,取值范围
+import{_ as d}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as A,v as c,b as D,F as s,L as a,t as n,O as E,R as p,M as r,C,B as F}from"./chunks/framework.7eed59db.js";import"./chunks/md5.34df8461.js";const z=JSON.parse('{"title":"个人 SQL 优化技巧","description":"","frontmatter":{"title":"个人 SQL 优化技巧","author":"查尔斯","date":"2019/12/28 10:00","isTop":true,"categories":["杂碎逆袭史"],"tags":["SQL","SQL优化"]},"headers":[],"relativePath":"categories/fragments/2019/12/28/个人SQL优化技巧.md","filePath":"categories/fragments/2019/12/28/个人SQL优化技巧.md","lastUpdated":1671423875000}'),h={name:"categories/fragments/2019/12/28/个人SQL优化技巧.md"},B={id:"个人-sql-优化技巧",tabindex:"-1"},u=s("a",{class:"header-anchor",href:"#个人-sql-优化技巧","aria-label":'Permalink to "个人 SQL 优化技巧 "'},"​",-1),_=s("h2",{id:"查询优化",tabindex:"-1"},[a("查询优化 "),s("a",{class:"header-anchor",href:"#查询优化","aria-label":'Permalink to "查询优化"'},"​")],-1),b={id:"如果确定结果只有一条-使用-limit-1",tabindex:"-1"},g=s("a",{class:"header-anchor",href:"#如果确定结果只有一条-使用-limit-1","aria-label":'Permalink to "如果确定结果只有一条,使用 LIMIT 1 "'},"​",-1),m=p('

我们在根据一个或多个条件查询数据时,如果确定查询结果只有一条,可以在结尾处添加 LIMIT 1 进行限制。

这样既可以让 EXPLAIN 中的 type 达到 const 类型,又可以免去担忧在程序中出现接收是单个对象却返回了一个集合对象的异常问题。

sql
# email 不是主键,也没有设置唯一约束,根据熵增定律,查询结果是有可能会出现多条的\nSELECT * FROM `sys_user` WHERE `email` = 'charles7c@126.com' LIMIT 1;
# email 不是主键,也没有设置唯一约束,根据熵增定律,查询结果是有可能会出现多条的\nSELECT * FROM `sys_user` WHERE `email` = 'charles7c@126.com' LIMIT 1;
sql
# user_id 是主键,主键是非空唯一的,那么不需要添加 LIMIT 进行限制\nSELECT * FROM `sys_user` WHERE `user_id` = 1;
# user_id 是主键,主键是非空唯一的,那么不需要添加 LIMIT 进行限制\nSELECT * FROM `sys_user` WHERE `user_id` = 1;
',3),v={id:"避免隐式类型转换",tabindex:"-1"},N=s("a",{class:"header-anchor",href:"#避免隐式类型转换","aria-label":'Permalink to "避免隐式类型转换 "'},"​",-1),T=p('

我们在使用 MySQL 时,或多或少都感受过 MySQL 的隐式类型转换。例如:user_id 是整数类型,但是依然可以使用字符串类型数据来进行判断。MySQL 帮你做完这种隐式类型转换是有代价的,什么代价呢? 索引不再生效了而已

sql
SELECT * FROM `sys_user` WHERE `user_id` = 10;
SELECT * FROM `sys_user` WHERE `user_id` = 10;
sql
SELECT * FROM `sys_user` WHERE `user_id` = '10';
SELECT * FROM `sys_user` WHERE `user_id` = '10';

数据库表设计

',3),f={id:"列名带上前缀",tabindex:"-1"},L=s("a",{class:"header-anchor",href:"#列名带上前缀","aria-label":'Permalink to "列名带上前缀 "'},"​",-1),M=p('

部分列名带上前缀或缩写,可以有效减少在连接查询、ORM 映射等场景下刻意起别名或思考区分不同的问题。

sql
CREATE TABLE `sys_customer` (\n  `customer_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '客户ID',\n  `customer_name` varchar(255) NOT NULL COMMENT '客户名称',\n  PRIMARY KEY (`customer_id`) USING BTREE,\n) COMMENT = '客户表';\n\nCREATE TABLE `sys_contact_user` (\n  `contact_user_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '联系人ID',\n  `contact_user_name` varchar(255) NOT NULL COMMENT '联系人名称',\n  `customer_id` bigint(20) UNSIGNED NOT NULL COMMENT '客户ID',\n  PRIMARY KEY (`contact_user_id`) USING BTREE,\n) COMMENT = '联系人表';\n\n# 连接查询,你完全不需要用脑去考虑到底是 c.`id` 还是 cu.`customer_id` 的问题,都是 `customer_id`\nSELECT * FROM `sys_customer` c \nLEFT JOIN `sys_contact_user` cu ON c.`customer_id` = cu.`customer_id`
CREATE TABLE `sys_customer` (\n  `customer_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '客户ID',\n  `customer_name` varchar(255) NOT NULL COMMENT '客户名称',\n  PRIMARY KEY (`customer_id`) USING BTREE,\n) COMMENT = '客户表';\n\nCREATE TABLE `sys_contact_user` (\n  `contact_user_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '联系人ID',\n  `contact_user_name` varchar(255) NOT NULL COMMENT '联系人名称',\n  `customer_id` bigint(20) UNSIGNED NOT NULL COMMENT '客户ID',\n  PRIMARY KEY (`contact_user_id`) USING BTREE,\n) COMMENT = '联系人表';\n\n# 连接查询,你完全不需要用脑去考虑到底是 c.`id` 还是 cu.`customer_id` 的问题,都是 `customer_id`\nSELECT * FROM `sys_customer` c \nLEFT JOIN `sys_contact_user` cu ON c.`customer_id` = cu.`customer_id`
',2),k={id:"非负数列添加unsigned无符号约束",tabindex:"-1"},I=s("a",{class:"header-anchor",href:"#非负数列添加unsigned无符号约束","aria-label":'Permalink to "非负数列添加UNSIGNED无符号约束 "'},"​",-1),O=p(`

在大部分的数据存储场景中,我们只会使用正整数,如果能确定该列为非负数,建议添加 UNSIGNED 无符号约束。

sql
# 不添加 UNSIGNED 约束,取值范围
 TINYINT:[-128, 127]
 # 添加 UNSIGNED 约束,取值范围
 TINYINT:[0, 255]
# 不添加 UNSIGNED 约束,取值范围
 TINYINT:[-128, 127]
 # 添加 UNSIGNED 约束,取值范围
-TINYINT:[0, 255]
`,2),x={id:"合理采用整数类型",tabindex:"-1"},S=s("a",{class:"header-anchor",href:"#合理采用整数类型","aria-label":'Permalink to "合理采用整数类型 "'},"​",-1),R=p('

例如:状态类的信息,状态再多能有多少个,采用 TINYINT 即可,减少存储空间占用。

下方表数据整理于:MySQL 5.7官方文档/数据类型/数值数据类型/整数类型

类型存储(字节)取值范围取值范围(无符号)
TINYINT1[-128, 127][0, 255]
SMALLINT2[-32768, 32767][0, 65535]
MEDIUMINT3[-8388608, 8388607][0, 16777215]
INT4[-2147483648, 2147483647][0, 4294967295]
BIGINT8[-2^63^, 2^63^-1][0, 2^64^-1]
',3),U={id:"布尔列采用bit类型",tabindex:"-1"},q=s("a",{class:"header-anchor",href:"#布尔列采用bit类型","aria-label":'Permalink to "布尔列采用bit类型 "'},"​",-1),Q=p(`

例如:是否删除这种只有两种状态的信息,在表设计时建议对该列设置 bit 类型(0表示否/假/false,1表示是/真/true),在程序语言中可以采用 boolean 类型对应。

sql
\`is_deleted\` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已删除(0否 1是)'
\`is_deleted\` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已删除(0否 1是)'
java
@Data
+TINYINT:[0, 255]
`,2),x={id:"合理采用整数类型",tabindex:"-1"},S=s("a",{class:"header-anchor",href:"#合理采用整数类型","aria-label":'Permalink to "合理采用整数类型 "'},"​",-1),R=p('

例如:状态类的信息,状态再多能有多少个,采用 TINYINT 即可,减少存储空间占用。

下方表数据整理于:MySQL 5.7官方文档/数据类型/数值数据类型/整数类型

类型存储(字节)取值范围取值范围(无符号)
TINYINT1[-128, 127][0, 255]
SMALLINT2[-32768, 32767][0, 65535]
MEDIUMINT3[-8388608, 8388607][0, 16777215]
INT4[-2147483648, 2147483647][0, 4294967295]
BIGINT8[-2^63^, 2^63^-1][0, 2^64^-1]
',3),U={id:"布尔列采用bit类型",tabindex:"-1"},q=s("a",{class:"header-anchor",href:"#布尔列采用bit类型","aria-label":'Permalink to "布尔列采用bit类型 "'},"​",-1),G=p(`

例如:是否删除这种只有两种状态的信息,在表设计时建议对该列设置 bit 类型(0表示否/假/false,1表示是/真/true),在程序语言中可以采用 boolean 类型对应。

sql
\`is_deleted\` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已删除(0否 1是)'
\`is_deleted\` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已删除(0否 1是)'
java
@Data
 public class User {
     /**
      * 是否已删除(0否 1是)
@@ -16,4 +16,4 @@ import{_ as d}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as A,v as c,b
      * 是否已删除(0否 1是)
      */
     private Boolean isDeleted;
-}

数据库设计

`,3),G={id:"采用utf8mb4编码",tabindex:"-1"},P=s("a",{class:"header-anchor",href:"#采用utf8mb4编码","aria-label":'Permalink to "采用utf8mb4编码 "'},"​",-1),Y=p('

如果要存储特殊字符(例如:emoij表情符),使用 utf8mb4 编码。

MySQL 5.5.3 后增加了一个新的编码: utf8mb4 ,其中 mb4 是 most bytes 4 的意思,用于兼容四字节的 unicode。

utf8mb4 是 utf8 的超集,除了将编码改为 utf8mb4 外不需要做其他转换。

设计数据库时如果想要允许用户使用特殊符号,最好使用 utf8mb4 编码来存储,使得数据库有更好的兼容性,但是这样设计会导致耗费更多的存储空间。

',1);function J(o,H,W,w,$,K){const l=r("Badge"),y=d,i=r("ClientOnly");return c(),D("div",null,[s("h1",u,[a("个人 SQL 优化技巧 "),n(l,{text:"持续更新",type:"warning"}),a(),B]),n(i,null,{default:E(()=>{var e,t;return[(((e=o.$frontmatter)==null?void 0:e.aside)??!0)&&(((t=o.$frontmatter)==null?void 0:t.showArticleMetadata)??!0)?(c(),C(y,{key:0,article:o.$frontmatter},null,8,["article"])):F("",!0)]}),_:1}),_,s("h3",b,[a("如果确定结果只有一条,使用 LIMIT 1 "),n(l,{text:"建议"}),a(),g]),m,s("h3",v,[a("避免隐式类型转换 "),n(l,{text:"强制",type:"danger"}),a(),N]),T,s("h3",f,[a("列名带上前缀 "),n(l,{text:"建议"}),a(),L]),M,s("h3",I,[a("非负数列添加UNSIGNED无符号约束 "),n(l,{text:"建议"}),a(),k]),O,s("h3",x,[a("合理采用整数类型 "),n(l,{text:"建议"}),a(),S]),R,s("h3",U,[a("布尔列采用bit类型 "),n(l,{text:"建议"}),a(),q]),Q,s("h3",G,[a("采用utf8mb4编码 "),n(l,{text:"建议"}),a(),P]),Y])}const X=A(h,[["render",J]]);export{Z as __pageData,X as default}; +}

数据库设计

`,3),P={id:"采用utf8mb4编码",tabindex:"-1"},Y=s("a",{class:"header-anchor",href:"#采用utf8mb4编码","aria-label":'Permalink to "采用utf8mb4编码 "'},"​",-1),Q=p('

如果要存储特殊字符(例如:emoij表情符),使用 utf8mb4 编码。

MySQL 5.5.3 后增加了一个新的编码: utf8mb4 ,其中 mb4 是 most bytes 4 的意思,用于兼容四字节的 unicode。

utf8mb4 是 utf8 的超集,除了将编码改为 utf8mb4 外不需要做其他转换。

设计数据库时如果想要允许用户使用特殊符号,最好使用 utf8mb4 编码来存储,使得数据库有更好的兼容性,但是这样设计会导致耗费更多的存储空间。

',1);function W(o,H,K,w,$,J){const l=r("Badge"),y=d,i=r("ClientOnly");return c(),D("div",null,[s("h1",B,[a("个人 SQL 优化技巧 "),n(l,{text:"持续更新",type:"warning"}),a(),u]),n(i,null,{default:E(()=>{var e,t;return[(((e=o.$frontmatter)==null?void 0:e.aside)??!0)&&(((t=o.$frontmatter)==null?void 0:t.showArticleMetadata)??!0)?(c(),C(y,{key:0,article:o.$frontmatter},null,8,["article"])):F("",!0)]}),_:1}),_,s("h3",b,[a("如果确定结果只有一条,使用 LIMIT 1 "),n(l,{text:"建议"}),a(),g]),m,s("h3",v,[a("避免隐式类型转换 "),n(l,{text:"强制",type:"danger"}),a(),N]),T,s("h3",f,[a("列名带上前缀 "),n(l,{text:"建议"}),a(),L]),M,s("h3",k,[a("非负数列添加UNSIGNED无符号约束 "),n(l,{text:"建议"}),a(),I]),O,s("h3",x,[a("合理采用整数类型 "),n(l,{text:"建议"}),a(),S]),R,s("h3",U,[a("布尔列采用bit类型 "),n(l,{text:"建议"}),a(),q]),G,s("h3",P,[a("采用utf8mb4编码 "),n(l,{text:"建议"}),a(),Y]),Q])}const V=A(h,[["render",W]]);export{z as __pageData,V as default}; diff --git "a/assets/categories_fragments_2023_01_06_CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.md.41dcdbfd.js" "b/assets/categories_fragments_2023_01_06_CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.md.8bed6c0c.js" similarity index 99% rename from "assets/categories_fragments_2023_01_06_CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.md.41dcdbfd.js" rename to "assets/categories_fragments_2023_01_06_CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.md.8bed6c0c.js" index 6d3bd156b1..ae9eb98c1c 100644 --- "a/assets/categories_fragments_2023_01_06_CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.md.41dcdbfd.js" +++ "b/assets/categories_fragments_2023_01_06_CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.md.8bed6c0c.js" @@ -1,4 +1,4 @@ -import{_ as t}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as c,v as l,b as r,t as y,O as i,F as p,L as A,R as D,M as C,C as B,B as d}from"./chunks/framework.7eed59db.js";import"./chunks/md5.34df8461.js";const E="/assets/202301062024211.391e6e5d.png",F="/assets/202301062027985.485f6ae9.png",L=JSON.parse('{"title":"阿里巴巴的Code Review方法论与实践总结","description":"","frontmatter":{"title":"阿里巴巴的Code Review方法论与实践总结","isOriginal":false,"author":"方基成(润甫)","date":"2023/01/06 20:12","articleTitle":"一文梳理Code Review方法论与实践总结","articleLink":"https://mp.weixin.qq.com/s/_4MFrQSYOIGYRdDGOJPDKQ","categories":["杂碎逆袭史"],"tags":["Code Review","卓越工程文化"]},"headers":[],"relativePath":"categories/fragments/2023/01/06/CodeReview方法论与实践总结.md","filePath":"categories/fragments/2023/01/06/CodeReview方法论与实践总结.md","lastUpdated":1673010072000}'),u={name:"categories/fragments/2023/01/06/CodeReview方法论与实践总结.md"},g=p("h1",{id:"阿里巴巴的code-review方法论与实践总结",tabindex:"-1"},[A("阿里巴巴的Code Review方法论与实践总结 "),p("a",{class:"header-anchor",href:"#阿里巴巴的code-review方法论与实践总结","aria-label":'Permalink to "阿里巴巴的Code Review方法论与实践总结"'},"​")],-1),h=D('

作为卓越工程文化的一部分,Code Review 其实一直在进行中,只是各团队根据自身情况张驰有度,松紧可能也不一,这里简单梳理一下 CR 的方法和团队实践。

为什么要CR

  • 提前发现缺陷 在CodeReview阶段发现的逻辑错误、业务理解偏差、性能隐患等时有发生,CR可以提前发现问题。
  • 提高代码质量 主要体现在代码健壮性、设计合理性、代码优雅性等方面,持续CodeReview可以提升团队整体代码质量。
  • 统一规范和风格 集团编码规范自不必说,对于代码风格要不要统一,可能会有不同的看法,个人观点对于风格也不强求。但代码其实不是写给自己看的,是写给下一任看的,就像经常被调侃的“程序员不喜欢写注释,更不喜欢别人不写注释”,代码风格的统一更有助于代码的可读性及继任者的快速上手。
  • 防止架构腐烂 架构的维护者是谁?仅靠架构师或应用Owner是远远不够的,需要所有成员的努力,所谓人人都是架构师。架构防腐最好前置在设计阶段,但CodeReview作为对最终产出代码的检查,也算是最后一道关键工序。
  • 知识分享 每一次CodeReview,都是一次知识的分享,磨合一定时间后,团队成员间会你中有我、我中有你,集百家之所长,融百家之所思。同时,业务逻辑都在代码中,团队CodeReview也是一种新人业务细节学习的途径。
  • 团队共识 通过多次讨论与交流,逐步达成团队共识,特别是对架构理解和设计原则的认知,在共识的基础上团队也会更有凝聚力,特别是在较多新人加入时尤为重要。

他山之石

某大厂A

非常重视 Code Review,基本上代码需要至少有两位以上 Reviewer 审核通过后,才会让你 Check In。

代码评审规则

  • 如果变更达到可以提升系统整体代码质量的程度,就可以让它们通过,即使它们可能还不完美。这是所有代码评审准则的最高原则。
  • 世界上没有“完美”的代码,只有更好的代码。评审者不应该要求代码提交者在每个细节都写得很完美。评审者应该做好修改时间与修改重要性之间的权衡。

代码评审原则

  • 以客观的技术因素与数据为准,而非个人偏好。
  • 在代码样式上,遵从代码样式指南,所有代码都应与其保持一致,任何与代码样式指南不一致的观点都是个人偏好。但如果某项代码样式在指南中未提及,那就接受作者的样式。
  • 任务涉及软件设计的问题,都应取决于基本设计原则,而不应由个人喜好来决定。当同时有多种可行方案时,如果作者能证明(以数据或公认的软件工程原理为依据)这些方案基本差不多,那就接受作者的选项;否则,应由标准的软件设计原则为准。
  • 如果没有可用的规则,那么审核者应该让作者与当前代码库保持一致,至少不会恶化代码系统的质量。(一旦恶化代码质量,就会带来破窗效应,导致系统的代码质量逐渐下降)

代码审核者应该看什么

  • 设计:代码是否设计良好?这种设计是否适合当前系统?
  • 功能:代码实现的行为与作者的期望是否相符?代码实现的交互界面是否对用户友好?
  • 复杂性:代码可以更简单吗?如果将来有其他开发者使用这段代码,他能很快理解吗?
  • 测试:这段代码是否有正确的、设计良好的自动化测试?
  • 命名:在为变量、类名、方法等命名时,开发者使用的名称是否清晰易懂?
  • 注释:所有的注释是否都一目了然?
  • 代码样式:所有的代码是否都遵循代码样式?
  • 文档:开发者是否同时更新了相关文档?

某大厂B

  • 在开发流程上专门有这个环节,排期会明确排进日程,比如 5 天开发会排 2 天来做代码审核,分为代码自审、交叉审核、集中审核。
  • 有明确的量化指标,如 8 人时审核/每千行代码,8 个以上非提示性有效问题/每千行代码。

某大厂C

  • 推行 Code Owner 机制,每个代码变更必须有 Code Owner 审核通过才可以提交。
  • 所有的一线工程师,无论职级高低,最重要的工程输出原则是 “show me the code”,而 Code Review 是最能够反应这个客观输出的。
  • 尽量让每个人的 Code Review 参与状况都公开透明,每个变更发送给项目合作者,及转发到小组内成员,小组内任何人都可以去 Review 其他人的代码。
  • 明确每个人的考评和 Code Review 表现相关,包括 Code Review 输出状况及提交代码的质量等。

我们怎么做CR

作为代码提交者

  • 发起时机:发起 Code Review 尽量提前,开发过程小步快跑

    202301062024211

  • 代码行数:提交 Code Review 的代码行数最好在 400 行以下。根据数据分析发现,从代码行数来看,超过 400 行的 CR,缺陷发现率会急剧下降;从CR速度来看,超过 500 行/小时后,Review 质量也会大大降低,一个高质量的 CR 最好控制在一个小时以内。

  • 明确意图:编写语义明确的标题(必填)和描述(选填,可以包括背景、思路、改造点和影响面、风险等)

  • 善用工具:IDEA 打开编码规约实时检测,减少代码样式、编码规约等基础性问题 (阿里编码规约插件:https://github.com/alibaba/p3c/tree/master/idea-plugin)

作为代码评审者

评审范围

主要从两方面来评审:

  • 代码逻辑
    • 功能完整:代码实现是否满足功能需求,实现上有没有需求的理解偏差,对用户是否友好;
    • 逻辑设计:是否考虑了全局设计和兼容现有业务细节,是否考虑边界条件和并发控制;
    • 安全隐患:是否存在数据安全隐患及敏感信息泄漏,如越权、SQL 注入、CSRF、敏感信息未脱敏等;
    • 性能隐患:是否存在损害性能的隐患,如死锁、死循环、FullGC、慢 SQL、缓存数据热点等;
    • 测试用例:单元测试用例的验证逻辑是否有效,测试用例的代码行覆盖率和分支覆盖率;
  • 代码质量
    • 编码规范:命名、注释、领域术语、架构分层、日志打印、代码样式等是否符合规范
    • 可读性:是否逻辑清晰、易理解,避免使用奇淫巧技,避免过度拆分
    • 简洁性:是否有重复可简化的复杂逻辑,代码复杂度是否过高,符合 KISS 和 DRY 原则
    • 可维护性:在可读性和简洁性基础上,是否分层清晰、模块化合理、高内聚低耦合、遵从基本设计原则
    • 可扩展性:是否仅仅是满足一次性需求的代码,是否有必要的前瞻性扩展设计
    • 可测试性:代码是否方便写单元测试及分支覆盖,是否便于自动化测试

评审注意事项

  • 尽快完成评审
  • 避免过度追求完美
  • 明确评论是否要解决
  • 避免使用反问句来评价

我们主要是通过交叉 CR、集中 CR 相结合的方式,由应用 Owner + SM + 架构师 + TL 完成。

CR怎么避免流于形式

CR 流于形式的因素很多,大概如下:

  • 不认同 CodeReview

    • 评审者的姿态?有没有带来好处?有没有从中收获?这些都会直观影响团队成员的认可度
    • 每个 Review 建议的提出都是一次思想交流,评论要友好、中肯、具体,避免教条式及负面词汇,在遵守评审原则下,同时尊重个性展现
    • 团队集中 CodeReview 尽量不要太正式和严肃,轻松的气氛下更有助于互相理解,来点水果,聊聊业务聊聊代码
    • 在 Review 过程有时候会陷入谁对谁错的争论,只要是为了寻求真理辩证的去看问题,哪怕是讨论再激烈也是有收获的,注意只对事不对人。
  • CodeReview 后改动太大

    • 发布前发现问题多,改动太大,影响项目计划
    • 大项目要求编码前设计评审,小需求可以事先 Review 设计思路,避免最后的惊喜
    • 每次 Review 的代码行数最好控制在数百行以内
  • 评审者没有足够时间

    • 评审者在任务安排上尽量预留好时间
    • 尽快评审,代码在百行以内及时响应,在千行以内当日完结
  • 评审者不了解业务和代码

    • 代码提交人编写清晰的标题和描述
    • 有必要的情况下评审者需要了解 PRD
    • 评审者需要提前了解系统和代码
  • Review 建议未修改

    • 这一点极为重要,需要对修改后的代码再次 Review,确保理解一致,以及预防带问题上线
    • 应用可以设置 Review 建议需全部解决的卡点,同时对于非必需修改的建议可以进行打标或说明

CR实践中发现的几个常见代码问题

笔者对个人 CR 评论问题做了个大概统计,Bug 发现数占比约 4%(直接或潜在 Bug),重复代码数占比约 5%,其他还有规范、安全、性能、设计等问题。在 CR 代码质量时,可以参考《重构:改善既有代码的设计》,书中所列的 22 种坏味道在 CR 中基本都会遇到。而此处我们主要聚焦以下几个常见问题:

DRY

DRY 是 Don't Repeat Yourself 的缩写,DRY 是 Andy Hunt 和 Dave Thomas's 在《 The Pragmatic Programmer 》一书中提出的核心原则。DRY 原则描述的重复是知识和意图的重复,包含代码重复、文档重复、数据重复、表征重复,我们这里重点讲讲代码重复

代码重复

《重构》中对 “Duplicated Code(重复代码)” 的描述:

坏味道行列中首当其冲的就是 Duplicated Code。如果你在一个以上的地点看到相同的程序结构,那么可以肯定:设法将它们合而为一,程序会变得更好。

最单纯的 Duplicated Code 就是“同一个类的两个函数含有相同的表达式”。这时候你需要做的就是采用 Extract Method (110) 提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。

另一种常见情况就是“两个互为兄弟的子类内含相同表达式”。要避免这种情况,只需对两个类都使用 Extract Method (110),然后再对被提炼出来的代码使用 Pull Up Method (332),将它推入超类内。如果代码之间只是类似,并非完全相同,那么就得运用 Extract Method (110) 将相似部分和差异部分割开,构成单独一个函数。然后你可能发现可以运用 Form Template Method (345) 获得一个Template Method 设计模式。如果有些函数以不同的算法做相同的事,你可以选择其中较清晰的一个,并使用 Substitute Algorithm (139) 将其他函数的算法替换掉。

如果两个毫不相关的类出现 Duplicated Code,你应该考虑对其中一个使用 Extract Class (149),将重复代码提炼到一个独立类中,然后在另一个类内使用这个新类。但是,重复代码所在的函数也可能的确只应该属于某个类,另一个类只能调用它,抑或这个函数可能属于第三个类,而另两个类应该引用这第三个类。你必须决定这个函数放在哪儿最合适,并确保它被安置后就不会再在其他任何地方出现。

代码重复的几种场景:

  • 一个类中重复代码抽象为一个方法
  • 两个子类间重复代码抽象到父类
  • 两个不相关类间重复代码抽象到第三个类
java
private BillVO convertBillDTO2BillVO(BillDTO billDTO) {
+import{_ as t}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as c,v as l,b as r,t as y,O as i,F as p,L as A,R as D,M as C,C as B,B as d}from"./chunks/framework.7eed59db.js";import"./chunks/md5.34df8461.js";const E="/assets/202301062024211.391e6e5d.png",F="/assets/202301062027985.485f6ae9.png",L=JSON.parse('{"title":"阿里巴巴的Code Review方法论与实践总结","description":"","frontmatter":{"title":"阿里巴巴的Code Review方法论与实践总结","isOriginal":false,"author":"方基成(润甫)","date":"2023/01/06 20:12","articleTitle":"一文梳理Code Review方法论与实践总结","articleLink":"https://mp.weixin.qq.com/s/_4MFrQSYOIGYRdDGOJPDKQ","categories":["杂碎逆袭史"],"tags":["Code Review","卓越工程文化"]},"headers":[],"relativePath":"categories/fragments/2023/01/06/CodeReview方法论与实践总结.md","filePath":"categories/fragments/2023/01/06/CodeReview方法论与实践总结.md","lastUpdated":1673010072000}'),u={name:"categories/fragments/2023/01/06/CodeReview方法论与实践总结.md"},g=p("h1",{id:"阿里巴巴的code-review方法论与实践总结",tabindex:"-1"},[A("阿里巴巴的Code Review方法论与实践总结 "),p("a",{class:"header-anchor",href:"#阿里巴巴的code-review方法论与实践总结","aria-label":'Permalink to "阿里巴巴的Code Review方法论与实践总结"'},"​")],-1),h=D('

作为卓越工程文化的一部分,Code Review 其实一直在进行中,只是各团队根据自身情况张驰有度,松紧可能也不一,这里简单梳理一下 CR 的方法和团队实践。

为什么要CR

  • 提前发现缺陷 在CodeReview阶段发现的逻辑错误、业务理解偏差、性能隐患等时有发生,CR可以提前发现问题。
  • 提高代码质量 主要体现在代码健壮性、设计合理性、代码优雅性等方面,持续CodeReview可以提升团队整体代码质量。
  • 统一规范和风格 集团编码规范自不必说,对于代码风格要不要统一,可能会有不同的看法,个人观点对于风格也不强求。但代码其实不是写给自己看的,是写给下一任看的,就像经常被调侃的“程序员不喜欢写注释,更不喜欢别人不写注释”,代码风格的统一更有助于代码的可读性及继任者的快速上手。
  • 防止架构腐烂 架构的维护者是谁?仅靠架构师或应用Owner是远远不够的,需要所有成员的努力,所谓人人都是架构师。架构防腐最好前置在设计阶段,但CodeReview作为对最终产出代码的检查,也算是最后一道关键工序。
  • 知识分享 每一次CodeReview,都是一次知识的分享,磨合一定时间后,团队成员间会你中有我、我中有你,集百家之所长,融百家之所思。同时,业务逻辑都在代码中,团队CodeReview也是一种新人业务细节学习的途径。
  • 团队共识 通过多次讨论与交流,逐步达成团队共识,特别是对架构理解和设计原则的认知,在共识的基础上团队也会更有凝聚力,特别是在较多新人加入时尤为重要。

他山之石

某大厂A

非常重视 Code Review,基本上代码需要至少有两位以上 Reviewer 审核通过后,才会让你 Check In。

代码评审规则

  • 如果变更达到可以提升系统整体代码质量的程度,就可以让它们通过,即使它们可能还不完美。这是所有代码评审准则的最高原则。
  • 世界上没有“完美”的代码,只有更好的代码。评审者不应该要求代码提交者在每个细节都写得很完美。评审者应该做好修改时间与修改重要性之间的权衡。

代码评审原则

  • 以客观的技术因素与数据为准,而非个人偏好。
  • 在代码样式上,遵从代码样式指南,所有代码都应与其保持一致,任何与代码样式指南不一致的观点都是个人偏好。但如果某项代码样式在指南中未提及,那就接受作者的样式。
  • 任务涉及软件设计的问题,都应取决于基本设计原则,而不应由个人喜好来决定。当同时有多种可行方案时,如果作者能证明(以数据或公认的软件工程原理为依据)这些方案基本差不多,那就接受作者的选项;否则,应由标准的软件设计原则为准。
  • 如果没有可用的规则,那么审核者应该让作者与当前代码库保持一致,至少不会恶化代码系统的质量。(一旦恶化代码质量,就会带来破窗效应,导致系统的代码质量逐渐下降)

代码审核者应该看什么

  • 设计:代码是否设计良好?这种设计是否适合当前系统?
  • 功能:代码实现的行为与作者的期望是否相符?代码实现的交互界面是否对用户友好?
  • 复杂性:代码可以更简单吗?如果将来有其他开发者使用这段代码,他能很快理解吗?
  • 测试:这段代码是否有正确的、设计良好的自动化测试?
  • 命名:在为变量、类名、方法等命名时,开发者使用的名称是否清晰易懂?
  • 注释:所有的注释是否都一目了然?
  • 代码样式:所有的代码是否都遵循代码样式?
  • 文档:开发者是否同时更新了相关文档?

某大厂B

  • 在开发流程上专门有这个环节,排期会明确排进日程,比如 5 天开发会排 2 天来做代码审核,分为代码自审、交叉审核、集中审核。
  • 有明确的量化指标,如 8 人时审核/每千行代码,8 个以上非提示性有效问题/每千行代码。

某大厂C

  • 推行 Code Owner 机制,每个代码变更必须有 Code Owner 审核通过才可以提交。
  • 所有的一线工程师,无论职级高低,最重要的工程输出原则是 “show me the code”,而 Code Review 是最能够反应这个客观输出的。
  • 尽量让每个人的 Code Review 参与状况都公开透明,每个变更发送给项目合作者,及转发到小组内成员,小组内任何人都可以去 Review 其他人的代码。
  • 明确每个人的考评和 Code Review 表现相关,包括 Code Review 输出状况及提交代码的质量等。

我们怎么做CR

作为代码提交者

  • 发起时机:发起 Code Review 尽量提前,开发过程小步快跑

    202301062024211

  • 代码行数:提交 Code Review 的代码行数最好在 400 行以下。根据数据分析发现,从代码行数来看,超过 400 行的 CR,缺陷发现率会急剧下降;从CR速度来看,超过 500 行/小时后,Review 质量也会大大降低,一个高质量的 CR 最好控制在一个小时以内。

  • 明确意图:编写语义明确的标题(必填)和描述(选填,可以包括背景、思路、改造点和影响面、风险等)

  • 善用工具:IDEA 打开编码规约实时检测,减少代码样式、编码规约等基础性问题 (阿里编码规约插件:https://github.com/alibaba/p3c/tree/master/idea-plugin)

作为代码评审者

评审范围

主要从两方面来评审:

  • 代码逻辑
    • 功能完整:代码实现是否满足功能需求,实现上有没有需求的理解偏差,对用户是否友好;
    • 逻辑设计:是否考虑了全局设计和兼容现有业务细节,是否考虑边界条件和并发控制;
    • 安全隐患:是否存在数据安全隐患及敏感信息泄漏,如越权、SQL 注入、CSRF、敏感信息未脱敏等;
    • 性能隐患:是否存在损害性能的隐患,如死锁、死循环、FullGC、慢 SQL、缓存数据热点等;
    • 测试用例:单元测试用例的验证逻辑是否有效,测试用例的代码行覆盖率和分支覆盖率;
  • 代码质量
    • 编码规范:命名、注释、领域术语、架构分层、日志打印、代码样式等是否符合规范
    • 可读性:是否逻辑清晰、易理解,避免使用奇淫巧技,避免过度拆分
    • 简洁性:是否有重复可简化的复杂逻辑,代码复杂度是否过高,符合 KISS 和 DRY 原则
    • 可维护性:在可读性和简洁性基础上,是否分层清晰、模块化合理、高内聚低耦合、遵从基本设计原则
    • 可扩展性:是否仅仅是满足一次性需求的代码,是否有必要的前瞻性扩展设计
    • 可测试性:代码是否方便写单元测试及分支覆盖,是否便于自动化测试

评审注意事项

  • 尽快完成评审
  • 避免过度追求完美
  • 明确评论是否要解决
  • 避免使用反问句来评价

我们主要是通过交叉 CR、集中 CR 相结合的方式,由应用 Owner + SM + 架构师 + TL 完成。

CR怎么避免流于形式

CR 流于形式的因素很多,大概如下:

  • 不认同 CodeReview

    • 评审者的姿态?有没有带来好处?有没有从中收获?这些都会直观影响团队成员的认可度
    • 每个 Review 建议的提出都是一次思想交流,评论要友好、中肯、具体,避免教条式及负面词汇,在遵守评审原则下,同时尊重个性展现
    • 团队集中 CodeReview 尽量不要太正式和严肃,轻松的气氛下更有助于互相理解,来点水果,聊聊业务聊聊代码
    • 在 Review 过程有时候会陷入谁对谁错的争论,只要是为了寻求真理辩证的去看问题,哪怕是讨论再激烈也是有收获的,注意只对事不对人。
  • CodeReview 后改动太大

    • 发布前发现问题多,改动太大,影响项目计划
    • 大项目要求编码前设计评审,小需求可以事先 Review 设计思路,避免最后的惊喜
    • 每次 Review 的代码行数最好控制在数百行以内
  • 评审者没有足够时间

    • 评审者在任务安排上尽量预留好时间
    • 尽快评审,代码在百行以内及时响应,在千行以内当日完结
  • 评审者不了解业务和代码

    • 代码提交人编写清晰的标题和描述
    • 有必要的情况下评审者需要了解 PRD
    • 评审者需要提前了解系统和代码
  • Review 建议未修改

    • 这一点极为重要,需要对修改后的代码再次 Review,确保理解一致,以及预防带问题上线
    • 应用可以设置 Review 建议需全部解决的卡点,同时对于非必需修改的建议可以进行打标或说明

CR实践中发现的几个常见代码问题

笔者对个人 CR 评论问题做了个大概统计,Bug 发现数占比约 4%(直接或潜在 Bug),重复代码数占比约 5%,其他还有规范、安全、性能、设计等问题。在 CR 代码质量时,可以参考《重构:改善既有代码的设计》,书中所列的 22 种坏味道在 CR 中基本都会遇到。而此处我们主要聚焦以下几个常见问题:

DRY

DRY 是 Don't Repeat Yourself 的缩写,DRY 是 Andy Hunt 和 Dave Thomas's 在《 The Pragmatic Programmer 》一书中提出的核心原则。DRY 原则描述的重复是知识和意图的重复,包含代码重复、文档重复、数据重复、表征重复,我们这里重点讲讲代码重复

代码重复

《重构》中对 “Duplicated Code(重复代码)” 的描述:

坏味道行列中首当其冲的就是 Duplicated Code。如果你在一个以上的地点看到相同的程序结构,那么可以肯定:设法将它们合而为一,程序会变得更好。

最单纯的 Duplicated Code 就是“同一个类的两个函数含有相同的表达式”。这时候你需要做的就是采用 Extract Method (110) 提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。

另一种常见情况就是“两个互为兄弟的子类内含相同表达式”。要避免这种情况,只需对两个类都使用 Extract Method (110),然后再对被提炼出来的代码使用 Pull Up Method (332),将它推入超类内。如果代码之间只是类似,并非完全相同,那么就得运用 Extract Method (110) 将相似部分和差异部分割开,构成单独一个函数。然后你可能发现可以运用 Form Template Method (345) 获得一个Template Method 设计模式。如果有些函数以不同的算法做相同的事,你可以选择其中较清晰的一个,并使用 Substitute Algorithm (139) 将其他函数的算法替换掉。

如果两个毫不相关的类出现 Duplicated Code,你应该考虑对其中一个使用 Extract Class (149),将重复代码提炼到一个独立类中,然后在另一个类内使用这个新类。但是,重复代码所在的函数也可能的确只应该属于某个类,另一个类只能调用它,抑或这个函数可能属于第三个类,而另两个类应该引用这第三个类。你必须决定这个函数放在哪儿最合适,并确保它被安置后就不会再在其他任何地方出现。

代码重复的几种场景:

  • 一个类中重复代码抽象为一个方法
  • 两个子类间重复代码抽象到父类
  • 两个不相关类间重复代码抽象到第三个类
java
private BillVO convertBillDTO2BillVO(BillDTO billDTO) {
     if (billDTO == null) {
         return null;
     }
@@ -70,7 +70,7 @@ import{_ as t}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as c,v as l,b
         return StringUtils.EMPTY;
     }
     return String.format(MONEY_DISPLAY_TEXT_PATTERN, money.getCurrency(), money.getAmount().toPlainString());
-}

DYR实践忠告

  • 不要借用 DRY 之名,过度提前抽象,请遵循 Rule of three 原则
  • 不要过度追求 DRY,破坏了内聚性,实践中需要平衡复用与内聚

Primitive Obsession

《重构》中对 “Primitive Obsession(基本类型偏执)” 的描述:

大多数编程环境都有两种数据:结构类型允许你将数据组织成有意义的形式;基本类型则是构成结构类型的积木块。结构总是会带来一定的额外开销。它们可能代表着数据库中的表,如果只为做一两件事而创建结构类型也可能显得太麻烦。

对象的一个极大的价值在于:它们模糊(甚至打破)了横亘于基本数据和体积较大的类之间的界限。你可以轻松编写出一些与语言内置(基本)类型无异的小型类。例如,Java 就以基本类型表示数值,而以类表示字符串和日期——这两个类型在其他许多编程环境中都以基本类型表现。

对象技术的新手通常不愿意在小任务上运用小对象——像是结合数值和币种的 money 类、由一个起始值和一个结束值组成的 range 类、电话号码或邮政编码(ZIP)等的特殊字符串。你可以运用 Replace Data Valuewith Object (175) 将原本单独存在的数据值替换为对象,从而走出传统的洞窟,进入炙手可热的对象世界。如果想要替换的数据值是类型码,而它并不影响行为,则可以运用 Replace Type Code with Class (218) 将它换掉。如果你有与类型码相关的条件表达式,可运用 Replace Type Codewith Subclass (213) 或 Replace Type Code with State/Strategy (227) 加以处理。

如果你有一组应该总是被放在一起的字段,可运用 Extract Class(149)。如果你在参数列中看到基本型数据,不妨试试 IntroduceParameter Object (295)。如果你发现自己正从数组中挑选数据,可运用 Replace Array with Object (186)。

给我们的启示主要有两点:

  • 大部分业务场景和语言环境下,结构化类型导致的开销基本可以忽略
  • 结构化类型带来更清晰的语义和复用
java
@Data
+}

DYR实践忠告

  • 不要借用 DRY 之名,过度提前抽象,请遵循 Rule of three 原则
  • 不要过度追求 DRY,破坏了内聚性,实践中需要平衡复用与内聚

Primitive Obsession

《重构》中对 “Primitive Obsession(基本类型偏执)” 的描述:

大多数编程环境都有两种数据:结构类型允许你将数据组织成有意义的形式;基本类型则是构成结构类型的积木块。结构总是会带来一定的额外开销。它们可能代表着数据库中的表,如果只为做一两件事而创建结构类型也可能显得太麻烦。

对象的一个极大的价值在于:它们模糊(甚至打破)了横亘于基本数据和体积较大的类之间的界限。你可以轻松编写出一些与语言内置(基本)类型无异的小型类。例如,Java 就以基本类型表示数值,而以类表示字符串和日期——这两个类型在其他许多编程环境中都以基本类型表现。

对象技术的新手通常不愿意在小任务上运用小对象——像是结合数值和币种的 money 类、由一个起始值和一个结束值组成的 range 类、电话号码或邮政编码(ZIP)等的特殊字符串。你可以运用 Replace Data Valuewith Object (175) 将原本单独存在的数据值替换为对象,从而走出传统的洞窟,进入炙手可热的对象世界。如果想要替换的数据值是类型码,而它并不影响行为,则可以运用 Replace Type Code with Class (218) 将它换掉。如果你有与类型码相关的条件表达式,可运用 Replace Type Codewith Subclass (213) 或 Replace Type Code with State/Strategy (227) 加以处理。

如果你有一组应该总是被放在一起的字段,可运用 Extract Class(149)。如果你在参数列中看到基本型数据,不妨试试 IntroduceParameter Object (295)。如果你发现自己正从数组中挑选数据,可运用 Replace Array with Object (186)。

给我们的启示主要有两点:

  • 大部分业务场景和语言环境下,结构化类型导致的开销基本可以忽略
  • 结构化类型带来更清晰的语义和复用
java
@Data
 public class XxxConfigDTO implements Serializable {
 
     private static final long serialVersionUID = 8018480763009740953L;
@@ -370,7 +370,7 @@ import{_ as t}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as c,v as l,b
     } finally {
         lockService.unlock(LockBizType.PRODUCT, orderId);
     }
-}

注意加锁类型与加锁 KEY 在同一个维度,否则加锁会失效。

分页查询

完全没有分页

java
private List<OrderDTO> queryOrderList(Long customerId) {
+}

注意加锁类型与加锁 KEY 在同一个维度,否则加锁会失效。

分页查询

完全没有分页

java
private List<OrderDTO> queryOrderList(Long customerId) {
     if (customerId == null) {
         return Lists.newArrayList();
     }
@@ -408,7 +408,7 @@ import{_ as t}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as c,v as l,b
     List<OrderDO> orderDOList = orderMapper.list(query);
     List<OrderDTO> orderDTOList = orderConverter.doList2dtoList(orderDOList);
     return PageQueryUtil.buildPageData(query, orderDTOList, cnt);
-}

没有分页的列表查询对DB性能影响非常大,特别是在项目初期,因为数据量非常小问题不明显,而导致没有及时发现,会给未来留坑。

分页size太大

java
private Page<OrderDTO> queryOrderList2(OrderPageQuery query) {
+}

没有分页的列表查询对DB性能影响非常大,特别是在项目初期,因为数据量非常小问题不明显,而导致没有及时发现,会给未来留坑。

分页size太大

java
private Page<OrderDTO> queryOrderList2(OrderPageQuery query) {
     Preconditions.checkNotNull(query, "查询条件不能为空");
     query.setPageSize(10000);
     long cnt = orderMapper.count(query);
@@ -428,7 +428,7 @@ import{_ as t}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as c,v as l,b
     List<OrderDO> orderDOList = orderMapper.list(query);
     List<OrderDTO> orderDTOList = orderConverter.doList2dtoList(orderDOList);
     return PageQueryUtil.buildPageData(query, orderDTOList, cnt);
-}

分页 size 的大小并没有一个固定的标准,取决于业务需求、数据量及数据库等,但动辄几千上万的分页 size,会带来性能瓶颈,而大量的慢 SQL 不但影响客户体验,对系统稳定性也是极大的隐患。

超多分页慢SQL

xml
<!-- 分页查询订单列表 -->
+}

分页 size 的大小并没有一个固定的标准,取决于业务需求、数据量及数据库等,但动辄几千上万的分页 size,会带来性能瓶颈,而大量的慢 SQL 不但影响客户体验,对系统稳定性也是极大的隐患。

超多分页慢SQL

xml
<!-- 分页查询订单列表 -->
 <select id="list" parameterType="com.xxx.OrderPageQuery" resultType="com.xxx.OrderDO">
     SELECT
         <include refid="all_columns"/>
diff --git "a/assets/categories_fragments_2023_01_06_CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.md.41dcdbfd.lean.js" "b/assets/categories_fragments_2023_01_06_CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.md.8bed6c0c.lean.js"
similarity index 99%
rename from "assets/categories_fragments_2023_01_06_CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.md.41dcdbfd.lean.js"
rename to "assets/categories_fragments_2023_01_06_CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.md.8bed6c0c.lean.js"
index 6d3bd156b1..ae9eb98c1c 100644
--- "a/assets/categories_fragments_2023_01_06_CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.md.41dcdbfd.lean.js"
+++ "b/assets/categories_fragments_2023_01_06_CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.md.8bed6c0c.lean.js"
@@ -1,4 +1,4 @@
-import{_ as t}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as c,v as l,b as r,t as y,O as i,F as p,L as A,R as D,M as C,C as B,B as d}from"./chunks/framework.7eed59db.js";import"./chunks/md5.34df8461.js";const E="/assets/202301062024211.391e6e5d.png",F="/assets/202301062027985.485f6ae9.png",L=JSON.parse('{"title":"阿里巴巴的Code Review方法论与实践总结","description":"","frontmatter":{"title":"阿里巴巴的Code Review方法论与实践总结","isOriginal":false,"author":"方基成(润甫)","date":"2023/01/06 20:12","articleTitle":"一文梳理Code Review方法论与实践总结","articleLink":"https://mp.weixin.qq.com/s/_4MFrQSYOIGYRdDGOJPDKQ","categories":["杂碎逆袭史"],"tags":["Code Review","卓越工程文化"]},"headers":[],"relativePath":"categories/fragments/2023/01/06/CodeReview方法论与实践总结.md","filePath":"categories/fragments/2023/01/06/CodeReview方法论与实践总结.md","lastUpdated":1673010072000}'),u={name:"categories/fragments/2023/01/06/CodeReview方法论与实践总结.md"},g=p("h1",{id:"阿里巴巴的code-review方法论与实践总结",tabindex:"-1"},[A("阿里巴巴的Code Review方法论与实践总结 "),p("a",{class:"header-anchor",href:"#阿里巴巴的code-review方法论与实践总结","aria-label":'Permalink to "阿里巴巴的Code Review方法论与实践总结"'},"​")],-1),h=D('

作为卓越工程文化的一部分,Code Review 其实一直在进行中,只是各团队根据自身情况张驰有度,松紧可能也不一,这里简单梳理一下 CR 的方法和团队实践。

为什么要CR

  • 提前发现缺陷 在CodeReview阶段发现的逻辑错误、业务理解偏差、性能隐患等时有发生,CR可以提前发现问题。
  • 提高代码质量 主要体现在代码健壮性、设计合理性、代码优雅性等方面,持续CodeReview可以提升团队整体代码质量。
  • 统一规范和风格 集团编码规范自不必说,对于代码风格要不要统一,可能会有不同的看法,个人观点对于风格也不强求。但代码其实不是写给自己看的,是写给下一任看的,就像经常被调侃的“程序员不喜欢写注释,更不喜欢别人不写注释”,代码风格的统一更有助于代码的可读性及继任者的快速上手。
  • 防止架构腐烂 架构的维护者是谁?仅靠架构师或应用Owner是远远不够的,需要所有成员的努力,所谓人人都是架构师。架构防腐最好前置在设计阶段,但CodeReview作为对最终产出代码的检查,也算是最后一道关键工序。
  • 知识分享 每一次CodeReview,都是一次知识的分享,磨合一定时间后,团队成员间会你中有我、我中有你,集百家之所长,融百家之所思。同时,业务逻辑都在代码中,团队CodeReview也是一种新人业务细节学习的途径。
  • 团队共识 通过多次讨论与交流,逐步达成团队共识,特别是对架构理解和设计原则的认知,在共识的基础上团队也会更有凝聚力,特别是在较多新人加入时尤为重要。

他山之石

某大厂A

非常重视 Code Review,基本上代码需要至少有两位以上 Reviewer 审核通过后,才会让你 Check In。

代码评审规则

  • 如果变更达到可以提升系统整体代码质量的程度,就可以让它们通过,即使它们可能还不完美。这是所有代码评审准则的最高原则。
  • 世界上没有“完美”的代码,只有更好的代码。评审者不应该要求代码提交者在每个细节都写得很完美。评审者应该做好修改时间与修改重要性之间的权衡。

代码评审原则

  • 以客观的技术因素与数据为准,而非个人偏好。
  • 在代码样式上,遵从代码样式指南,所有代码都应与其保持一致,任何与代码样式指南不一致的观点都是个人偏好。但如果某项代码样式在指南中未提及,那就接受作者的样式。
  • 任务涉及软件设计的问题,都应取决于基本设计原则,而不应由个人喜好来决定。当同时有多种可行方案时,如果作者能证明(以数据或公认的软件工程原理为依据)这些方案基本差不多,那就接受作者的选项;否则,应由标准的软件设计原则为准。
  • 如果没有可用的规则,那么审核者应该让作者与当前代码库保持一致,至少不会恶化代码系统的质量。(一旦恶化代码质量,就会带来破窗效应,导致系统的代码质量逐渐下降)

代码审核者应该看什么

  • 设计:代码是否设计良好?这种设计是否适合当前系统?
  • 功能:代码实现的行为与作者的期望是否相符?代码实现的交互界面是否对用户友好?
  • 复杂性:代码可以更简单吗?如果将来有其他开发者使用这段代码,他能很快理解吗?
  • 测试:这段代码是否有正确的、设计良好的自动化测试?
  • 命名:在为变量、类名、方法等命名时,开发者使用的名称是否清晰易懂?
  • 注释:所有的注释是否都一目了然?
  • 代码样式:所有的代码是否都遵循代码样式?
  • 文档:开发者是否同时更新了相关文档?

某大厂B

  • 在开发流程上专门有这个环节,排期会明确排进日程,比如 5 天开发会排 2 天来做代码审核,分为代码自审、交叉审核、集中审核。
  • 有明确的量化指标,如 8 人时审核/每千行代码,8 个以上非提示性有效问题/每千行代码。

某大厂C

  • 推行 Code Owner 机制,每个代码变更必须有 Code Owner 审核通过才可以提交。
  • 所有的一线工程师,无论职级高低,最重要的工程输出原则是 “show me the code”,而 Code Review 是最能够反应这个客观输出的。
  • 尽量让每个人的 Code Review 参与状况都公开透明,每个变更发送给项目合作者,及转发到小组内成员,小组内任何人都可以去 Review 其他人的代码。
  • 明确每个人的考评和 Code Review 表现相关,包括 Code Review 输出状况及提交代码的质量等。

我们怎么做CR

作为代码提交者

  • 发起时机:发起 Code Review 尽量提前,开发过程小步快跑

    202301062024211

  • 代码行数:提交 Code Review 的代码行数最好在 400 行以下。根据数据分析发现,从代码行数来看,超过 400 行的 CR,缺陷发现率会急剧下降;从CR速度来看,超过 500 行/小时后,Review 质量也会大大降低,一个高质量的 CR 最好控制在一个小时以内。

  • 明确意图:编写语义明确的标题(必填)和描述(选填,可以包括背景、思路、改造点和影响面、风险等)

  • 善用工具:IDEA 打开编码规约实时检测,减少代码样式、编码规约等基础性问题 (阿里编码规约插件:https://github.com/alibaba/p3c/tree/master/idea-plugin)

作为代码评审者

评审范围

主要从两方面来评审:

  • 代码逻辑
    • 功能完整:代码实现是否满足功能需求,实现上有没有需求的理解偏差,对用户是否友好;
    • 逻辑设计:是否考虑了全局设计和兼容现有业务细节,是否考虑边界条件和并发控制;
    • 安全隐患:是否存在数据安全隐患及敏感信息泄漏,如越权、SQL 注入、CSRF、敏感信息未脱敏等;
    • 性能隐患:是否存在损害性能的隐患,如死锁、死循环、FullGC、慢 SQL、缓存数据热点等;
    • 测试用例:单元测试用例的验证逻辑是否有效,测试用例的代码行覆盖率和分支覆盖率;
  • 代码质量
    • 编码规范:命名、注释、领域术语、架构分层、日志打印、代码样式等是否符合规范
    • 可读性:是否逻辑清晰、易理解,避免使用奇淫巧技,避免过度拆分
    • 简洁性:是否有重复可简化的复杂逻辑,代码复杂度是否过高,符合 KISS 和 DRY 原则
    • 可维护性:在可读性和简洁性基础上,是否分层清晰、模块化合理、高内聚低耦合、遵从基本设计原则
    • 可扩展性:是否仅仅是满足一次性需求的代码,是否有必要的前瞻性扩展设计
    • 可测试性:代码是否方便写单元测试及分支覆盖,是否便于自动化测试

评审注意事项

  • 尽快完成评审
  • 避免过度追求完美
  • 明确评论是否要解决
  • 避免使用反问句来评价

我们主要是通过交叉 CR、集中 CR 相结合的方式,由应用 Owner + SM + 架构师 + TL 完成。

CR怎么避免流于形式

CR 流于形式的因素很多,大概如下:

  • 不认同 CodeReview

    • 评审者的姿态?有没有带来好处?有没有从中收获?这些都会直观影响团队成员的认可度
    • 每个 Review 建议的提出都是一次思想交流,评论要友好、中肯、具体,避免教条式及负面词汇,在遵守评审原则下,同时尊重个性展现
    • 团队集中 CodeReview 尽量不要太正式和严肃,轻松的气氛下更有助于互相理解,来点水果,聊聊业务聊聊代码
    • 在 Review 过程有时候会陷入谁对谁错的争论,只要是为了寻求真理辩证的去看问题,哪怕是讨论再激烈也是有收获的,注意只对事不对人。
  • CodeReview 后改动太大

    • 发布前发现问题多,改动太大,影响项目计划
    • 大项目要求编码前设计评审,小需求可以事先 Review 设计思路,避免最后的惊喜
    • 每次 Review 的代码行数最好控制在数百行以内
  • 评审者没有足够时间

    • 评审者在任务安排上尽量预留好时间
    • 尽快评审,代码在百行以内及时响应,在千行以内当日完结
  • 评审者不了解业务和代码

    • 代码提交人编写清晰的标题和描述
    • 有必要的情况下评审者需要了解 PRD
    • 评审者需要提前了解系统和代码
  • Review 建议未修改

    • 这一点极为重要,需要对修改后的代码再次 Review,确保理解一致,以及预防带问题上线
    • 应用可以设置 Review 建议需全部解决的卡点,同时对于非必需修改的建议可以进行打标或说明

CR实践中发现的几个常见代码问题

笔者对个人 CR 评论问题做了个大概统计,Bug 发现数占比约 4%(直接或潜在 Bug),重复代码数占比约 5%,其他还有规范、安全、性能、设计等问题。在 CR 代码质量时,可以参考《重构:改善既有代码的设计》,书中所列的 22 种坏味道在 CR 中基本都会遇到。而此处我们主要聚焦以下几个常见问题:

DRY

DRY 是 Don't Repeat Yourself 的缩写,DRY 是 Andy Hunt 和 Dave Thomas's 在《 The Pragmatic Programmer 》一书中提出的核心原则。DRY 原则描述的重复是知识和意图的重复,包含代码重复、文档重复、数据重复、表征重复,我们这里重点讲讲代码重复

代码重复

《重构》中对 “Duplicated Code(重复代码)” 的描述:

坏味道行列中首当其冲的就是 Duplicated Code。如果你在一个以上的地点看到相同的程序结构,那么可以肯定:设法将它们合而为一,程序会变得更好。

最单纯的 Duplicated Code 就是“同一个类的两个函数含有相同的表达式”。这时候你需要做的就是采用 Extract Method (110) 提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。

另一种常见情况就是“两个互为兄弟的子类内含相同表达式”。要避免这种情况,只需对两个类都使用 Extract Method (110),然后再对被提炼出来的代码使用 Pull Up Method (332),将它推入超类内。如果代码之间只是类似,并非完全相同,那么就得运用 Extract Method (110) 将相似部分和差异部分割开,构成单独一个函数。然后你可能发现可以运用 Form Template Method (345) 获得一个Template Method 设计模式。如果有些函数以不同的算法做相同的事,你可以选择其中较清晰的一个,并使用 Substitute Algorithm (139) 将其他函数的算法替换掉。

如果两个毫不相关的类出现 Duplicated Code,你应该考虑对其中一个使用 Extract Class (149),将重复代码提炼到一个独立类中,然后在另一个类内使用这个新类。但是,重复代码所在的函数也可能的确只应该属于某个类,另一个类只能调用它,抑或这个函数可能属于第三个类,而另两个类应该引用这第三个类。你必须决定这个函数放在哪儿最合适,并确保它被安置后就不会再在其他任何地方出现。

代码重复的几种场景:

  • 一个类中重复代码抽象为一个方法
  • 两个子类间重复代码抽象到父类
  • 两个不相关类间重复代码抽象到第三个类
java
private BillVO convertBillDTO2BillVO(BillDTO billDTO) {
+import{_ as t}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as c,v as l,b as r,t as y,O as i,F as p,L as A,R as D,M as C,C as B,B as d}from"./chunks/framework.7eed59db.js";import"./chunks/md5.34df8461.js";const E="/assets/202301062024211.391e6e5d.png",F="/assets/202301062027985.485f6ae9.png",L=JSON.parse('{"title":"阿里巴巴的Code Review方法论与实践总结","description":"","frontmatter":{"title":"阿里巴巴的Code Review方法论与实践总结","isOriginal":false,"author":"方基成(润甫)","date":"2023/01/06 20:12","articleTitle":"一文梳理Code Review方法论与实践总结","articleLink":"https://mp.weixin.qq.com/s/_4MFrQSYOIGYRdDGOJPDKQ","categories":["杂碎逆袭史"],"tags":["Code Review","卓越工程文化"]},"headers":[],"relativePath":"categories/fragments/2023/01/06/CodeReview方法论与实践总结.md","filePath":"categories/fragments/2023/01/06/CodeReview方法论与实践总结.md","lastUpdated":1673010072000}'),u={name:"categories/fragments/2023/01/06/CodeReview方法论与实践总结.md"},g=p("h1",{id:"阿里巴巴的code-review方法论与实践总结",tabindex:"-1"},[A("阿里巴巴的Code Review方法论与实践总结 "),p("a",{class:"header-anchor",href:"#阿里巴巴的code-review方法论与实践总结","aria-label":'Permalink to "阿里巴巴的Code Review方法论与实践总结"'},"​")],-1),h=D('

作为卓越工程文化的一部分,Code Review 其实一直在进行中,只是各团队根据自身情况张驰有度,松紧可能也不一,这里简单梳理一下 CR 的方法和团队实践。

为什么要CR

  • 提前发现缺陷 在CodeReview阶段发现的逻辑错误、业务理解偏差、性能隐患等时有发生,CR可以提前发现问题。
  • 提高代码质量 主要体现在代码健壮性、设计合理性、代码优雅性等方面,持续CodeReview可以提升团队整体代码质量。
  • 统一规范和风格 集团编码规范自不必说,对于代码风格要不要统一,可能会有不同的看法,个人观点对于风格也不强求。但代码其实不是写给自己看的,是写给下一任看的,就像经常被调侃的“程序员不喜欢写注释,更不喜欢别人不写注释”,代码风格的统一更有助于代码的可读性及继任者的快速上手。
  • 防止架构腐烂 架构的维护者是谁?仅靠架构师或应用Owner是远远不够的,需要所有成员的努力,所谓人人都是架构师。架构防腐最好前置在设计阶段,但CodeReview作为对最终产出代码的检查,也算是最后一道关键工序。
  • 知识分享 每一次CodeReview,都是一次知识的分享,磨合一定时间后,团队成员间会你中有我、我中有你,集百家之所长,融百家之所思。同时,业务逻辑都在代码中,团队CodeReview也是一种新人业务细节学习的途径。
  • 团队共识 通过多次讨论与交流,逐步达成团队共识,特别是对架构理解和设计原则的认知,在共识的基础上团队也会更有凝聚力,特别是在较多新人加入时尤为重要。

他山之石

某大厂A

非常重视 Code Review,基本上代码需要至少有两位以上 Reviewer 审核通过后,才会让你 Check In。

代码评审规则

  • 如果变更达到可以提升系统整体代码质量的程度,就可以让它们通过,即使它们可能还不完美。这是所有代码评审准则的最高原则。
  • 世界上没有“完美”的代码,只有更好的代码。评审者不应该要求代码提交者在每个细节都写得很完美。评审者应该做好修改时间与修改重要性之间的权衡。

代码评审原则

  • 以客观的技术因素与数据为准,而非个人偏好。
  • 在代码样式上,遵从代码样式指南,所有代码都应与其保持一致,任何与代码样式指南不一致的观点都是个人偏好。但如果某项代码样式在指南中未提及,那就接受作者的样式。
  • 任务涉及软件设计的问题,都应取决于基本设计原则,而不应由个人喜好来决定。当同时有多种可行方案时,如果作者能证明(以数据或公认的软件工程原理为依据)这些方案基本差不多,那就接受作者的选项;否则,应由标准的软件设计原则为准。
  • 如果没有可用的规则,那么审核者应该让作者与当前代码库保持一致,至少不会恶化代码系统的质量。(一旦恶化代码质量,就会带来破窗效应,导致系统的代码质量逐渐下降)

代码审核者应该看什么

  • 设计:代码是否设计良好?这种设计是否适合当前系统?
  • 功能:代码实现的行为与作者的期望是否相符?代码实现的交互界面是否对用户友好?
  • 复杂性:代码可以更简单吗?如果将来有其他开发者使用这段代码,他能很快理解吗?
  • 测试:这段代码是否有正确的、设计良好的自动化测试?
  • 命名:在为变量、类名、方法等命名时,开发者使用的名称是否清晰易懂?
  • 注释:所有的注释是否都一目了然?
  • 代码样式:所有的代码是否都遵循代码样式?
  • 文档:开发者是否同时更新了相关文档?

某大厂B

  • 在开发流程上专门有这个环节,排期会明确排进日程,比如 5 天开发会排 2 天来做代码审核,分为代码自审、交叉审核、集中审核。
  • 有明确的量化指标,如 8 人时审核/每千行代码,8 个以上非提示性有效问题/每千行代码。

某大厂C

  • 推行 Code Owner 机制,每个代码变更必须有 Code Owner 审核通过才可以提交。
  • 所有的一线工程师,无论职级高低,最重要的工程输出原则是 “show me the code”,而 Code Review 是最能够反应这个客观输出的。
  • 尽量让每个人的 Code Review 参与状况都公开透明,每个变更发送给项目合作者,及转发到小组内成员,小组内任何人都可以去 Review 其他人的代码。
  • 明确每个人的考评和 Code Review 表现相关,包括 Code Review 输出状况及提交代码的质量等。

我们怎么做CR

作为代码提交者

  • 发起时机:发起 Code Review 尽量提前,开发过程小步快跑

    202301062024211

  • 代码行数:提交 Code Review 的代码行数最好在 400 行以下。根据数据分析发现,从代码行数来看,超过 400 行的 CR,缺陷发现率会急剧下降;从CR速度来看,超过 500 行/小时后,Review 质量也会大大降低,一个高质量的 CR 最好控制在一个小时以内。

  • 明确意图:编写语义明确的标题(必填)和描述(选填,可以包括背景、思路、改造点和影响面、风险等)

  • 善用工具:IDEA 打开编码规约实时检测,减少代码样式、编码规约等基础性问题 (阿里编码规约插件:https://github.com/alibaba/p3c/tree/master/idea-plugin)

作为代码评审者

评审范围

主要从两方面来评审:

  • 代码逻辑
    • 功能完整:代码实现是否满足功能需求,实现上有没有需求的理解偏差,对用户是否友好;
    • 逻辑设计:是否考虑了全局设计和兼容现有业务细节,是否考虑边界条件和并发控制;
    • 安全隐患:是否存在数据安全隐患及敏感信息泄漏,如越权、SQL 注入、CSRF、敏感信息未脱敏等;
    • 性能隐患:是否存在损害性能的隐患,如死锁、死循环、FullGC、慢 SQL、缓存数据热点等;
    • 测试用例:单元测试用例的验证逻辑是否有效,测试用例的代码行覆盖率和分支覆盖率;
  • 代码质量
    • 编码规范:命名、注释、领域术语、架构分层、日志打印、代码样式等是否符合规范
    • 可读性:是否逻辑清晰、易理解,避免使用奇淫巧技,避免过度拆分
    • 简洁性:是否有重复可简化的复杂逻辑,代码复杂度是否过高,符合 KISS 和 DRY 原则
    • 可维护性:在可读性和简洁性基础上,是否分层清晰、模块化合理、高内聚低耦合、遵从基本设计原则
    • 可扩展性:是否仅仅是满足一次性需求的代码,是否有必要的前瞻性扩展设计
    • 可测试性:代码是否方便写单元测试及分支覆盖,是否便于自动化测试

评审注意事项

  • 尽快完成评审
  • 避免过度追求完美
  • 明确评论是否要解决
  • 避免使用反问句来评价

我们主要是通过交叉 CR、集中 CR 相结合的方式,由应用 Owner + SM + 架构师 + TL 完成。

CR怎么避免流于形式

CR 流于形式的因素很多,大概如下:

  • 不认同 CodeReview

    • 评审者的姿态?有没有带来好处?有没有从中收获?这些都会直观影响团队成员的认可度
    • 每个 Review 建议的提出都是一次思想交流,评论要友好、中肯、具体,避免教条式及负面词汇,在遵守评审原则下,同时尊重个性展现
    • 团队集中 CodeReview 尽量不要太正式和严肃,轻松的气氛下更有助于互相理解,来点水果,聊聊业务聊聊代码
    • 在 Review 过程有时候会陷入谁对谁错的争论,只要是为了寻求真理辩证的去看问题,哪怕是讨论再激烈也是有收获的,注意只对事不对人。
  • CodeReview 后改动太大

    • 发布前发现问题多,改动太大,影响项目计划
    • 大项目要求编码前设计评审,小需求可以事先 Review 设计思路,避免最后的惊喜
    • 每次 Review 的代码行数最好控制在数百行以内
  • 评审者没有足够时间

    • 评审者在任务安排上尽量预留好时间
    • 尽快评审,代码在百行以内及时响应,在千行以内当日完结
  • 评审者不了解业务和代码

    • 代码提交人编写清晰的标题和描述
    • 有必要的情况下评审者需要了解 PRD
    • 评审者需要提前了解系统和代码
  • Review 建议未修改

    • 这一点极为重要,需要对修改后的代码再次 Review,确保理解一致,以及预防带问题上线
    • 应用可以设置 Review 建议需全部解决的卡点,同时对于非必需修改的建议可以进行打标或说明

CR实践中发现的几个常见代码问题

笔者对个人 CR 评论问题做了个大概统计,Bug 发现数占比约 4%(直接或潜在 Bug),重复代码数占比约 5%,其他还有规范、安全、性能、设计等问题。在 CR 代码质量时,可以参考《重构:改善既有代码的设计》,书中所列的 22 种坏味道在 CR 中基本都会遇到。而此处我们主要聚焦以下几个常见问题:

DRY

DRY 是 Don't Repeat Yourself 的缩写,DRY 是 Andy Hunt 和 Dave Thomas's 在《 The Pragmatic Programmer 》一书中提出的核心原则。DRY 原则描述的重复是知识和意图的重复,包含代码重复、文档重复、数据重复、表征重复,我们这里重点讲讲代码重复

代码重复

《重构》中对 “Duplicated Code(重复代码)” 的描述:

坏味道行列中首当其冲的就是 Duplicated Code。如果你在一个以上的地点看到相同的程序结构,那么可以肯定:设法将它们合而为一,程序会变得更好。

最单纯的 Duplicated Code 就是“同一个类的两个函数含有相同的表达式”。这时候你需要做的就是采用 Extract Method (110) 提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。

另一种常见情况就是“两个互为兄弟的子类内含相同表达式”。要避免这种情况,只需对两个类都使用 Extract Method (110),然后再对被提炼出来的代码使用 Pull Up Method (332),将它推入超类内。如果代码之间只是类似,并非完全相同,那么就得运用 Extract Method (110) 将相似部分和差异部分割开,构成单独一个函数。然后你可能发现可以运用 Form Template Method (345) 获得一个Template Method 设计模式。如果有些函数以不同的算法做相同的事,你可以选择其中较清晰的一个,并使用 Substitute Algorithm (139) 将其他函数的算法替换掉。

如果两个毫不相关的类出现 Duplicated Code,你应该考虑对其中一个使用 Extract Class (149),将重复代码提炼到一个独立类中,然后在另一个类内使用这个新类。但是,重复代码所在的函数也可能的确只应该属于某个类,另一个类只能调用它,抑或这个函数可能属于第三个类,而另两个类应该引用这第三个类。你必须决定这个函数放在哪儿最合适,并确保它被安置后就不会再在其他任何地方出现。

代码重复的几种场景:

  • 一个类中重复代码抽象为一个方法
  • 两个子类间重复代码抽象到父类
  • 两个不相关类间重复代码抽象到第三个类
java
private BillVO convertBillDTO2BillVO(BillDTO billDTO) {
     if (billDTO == null) {
         return null;
     }
@@ -70,7 +70,7 @@ import{_ as t}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as c,v as l,b
         return StringUtils.EMPTY;
     }
     return String.format(MONEY_DISPLAY_TEXT_PATTERN, money.getCurrency(), money.getAmount().toPlainString());
-}

DYR实践忠告

  • 不要借用 DRY 之名,过度提前抽象,请遵循 Rule of three 原则
  • 不要过度追求 DRY,破坏了内聚性,实践中需要平衡复用与内聚

Primitive Obsession

《重构》中对 “Primitive Obsession(基本类型偏执)” 的描述:

大多数编程环境都有两种数据:结构类型允许你将数据组织成有意义的形式;基本类型则是构成结构类型的积木块。结构总是会带来一定的额外开销。它们可能代表着数据库中的表,如果只为做一两件事而创建结构类型也可能显得太麻烦。

对象的一个极大的价值在于:它们模糊(甚至打破)了横亘于基本数据和体积较大的类之间的界限。你可以轻松编写出一些与语言内置(基本)类型无异的小型类。例如,Java 就以基本类型表示数值,而以类表示字符串和日期——这两个类型在其他许多编程环境中都以基本类型表现。

对象技术的新手通常不愿意在小任务上运用小对象——像是结合数值和币种的 money 类、由一个起始值和一个结束值组成的 range 类、电话号码或邮政编码(ZIP)等的特殊字符串。你可以运用 Replace Data Valuewith Object (175) 将原本单独存在的数据值替换为对象,从而走出传统的洞窟,进入炙手可热的对象世界。如果想要替换的数据值是类型码,而它并不影响行为,则可以运用 Replace Type Code with Class (218) 将它换掉。如果你有与类型码相关的条件表达式,可运用 Replace Type Codewith Subclass (213) 或 Replace Type Code with State/Strategy (227) 加以处理。

如果你有一组应该总是被放在一起的字段,可运用 Extract Class(149)。如果你在参数列中看到基本型数据,不妨试试 IntroduceParameter Object (295)。如果你发现自己正从数组中挑选数据,可运用 Replace Array with Object (186)。

给我们的启示主要有两点:

  • 大部分业务场景和语言环境下,结构化类型导致的开销基本可以忽略
  • 结构化类型带来更清晰的语义和复用
java
@Data
+}

DYR实践忠告

  • 不要借用 DRY 之名,过度提前抽象,请遵循 Rule of three 原则
  • 不要过度追求 DRY,破坏了内聚性,实践中需要平衡复用与内聚

Primitive Obsession

《重构》中对 “Primitive Obsession(基本类型偏执)” 的描述:

大多数编程环境都有两种数据:结构类型允许你将数据组织成有意义的形式;基本类型则是构成结构类型的积木块。结构总是会带来一定的额外开销。它们可能代表着数据库中的表,如果只为做一两件事而创建结构类型也可能显得太麻烦。

对象的一个极大的价值在于:它们模糊(甚至打破)了横亘于基本数据和体积较大的类之间的界限。你可以轻松编写出一些与语言内置(基本)类型无异的小型类。例如,Java 就以基本类型表示数值,而以类表示字符串和日期——这两个类型在其他许多编程环境中都以基本类型表现。

对象技术的新手通常不愿意在小任务上运用小对象——像是结合数值和币种的 money 类、由一个起始值和一个结束值组成的 range 类、电话号码或邮政编码(ZIP)等的特殊字符串。你可以运用 Replace Data Valuewith Object (175) 将原本单独存在的数据值替换为对象,从而走出传统的洞窟,进入炙手可热的对象世界。如果想要替换的数据值是类型码,而它并不影响行为,则可以运用 Replace Type Code with Class (218) 将它换掉。如果你有与类型码相关的条件表达式,可运用 Replace Type Codewith Subclass (213) 或 Replace Type Code with State/Strategy (227) 加以处理。

如果你有一组应该总是被放在一起的字段,可运用 Extract Class(149)。如果你在参数列中看到基本型数据,不妨试试 IntroduceParameter Object (295)。如果你发现自己正从数组中挑选数据,可运用 Replace Array with Object (186)。

给我们的启示主要有两点:

  • 大部分业务场景和语言环境下,结构化类型导致的开销基本可以忽略
  • 结构化类型带来更清晰的语义和复用
java
@Data
 public class XxxConfigDTO implements Serializable {
 
     private static final long serialVersionUID = 8018480763009740953L;
@@ -370,7 +370,7 @@ import{_ as t}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as c,v as l,b
     } finally {
         lockService.unlock(LockBizType.PRODUCT, orderId);
     }
-}

注意加锁类型与加锁 KEY 在同一个维度,否则加锁会失效。

分页查询

完全没有分页

java
private List<OrderDTO> queryOrderList(Long customerId) {
+}

注意加锁类型与加锁 KEY 在同一个维度,否则加锁会失效。

分页查询

完全没有分页

java
private List<OrderDTO> queryOrderList(Long customerId) {
     if (customerId == null) {
         return Lists.newArrayList();
     }
@@ -408,7 +408,7 @@ import{_ as t}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as c,v as l,b
     List<OrderDO> orderDOList = orderMapper.list(query);
     List<OrderDTO> orderDTOList = orderConverter.doList2dtoList(orderDOList);
     return PageQueryUtil.buildPageData(query, orderDTOList, cnt);
-}

没有分页的列表查询对DB性能影响非常大,特别是在项目初期,因为数据量非常小问题不明显,而导致没有及时发现,会给未来留坑。

分页size太大

java
private Page<OrderDTO> queryOrderList2(OrderPageQuery query) {
+}

没有分页的列表查询对DB性能影响非常大,特别是在项目初期,因为数据量非常小问题不明显,而导致没有及时发现,会给未来留坑。

分页size太大

java
private Page<OrderDTO> queryOrderList2(OrderPageQuery query) {
     Preconditions.checkNotNull(query, "查询条件不能为空");
     query.setPageSize(10000);
     long cnt = orderMapper.count(query);
@@ -428,7 +428,7 @@ import{_ as t}from"./chunks/ArticleMetadata.bdb63dc3.js";import{_ as c,v as l,b
     List<OrderDO> orderDOList = orderMapper.list(query);
     List<OrderDTO> orderDTOList = orderConverter.doList2dtoList(orderDOList);
     return PageQueryUtil.buildPageData(query, orderDTOList, cnt);
-}

分页 size 的大小并没有一个固定的标准,取决于业务需求、数据量及数据库等,但动辄几千上万的分页 size,会带来性能瓶颈,而大量的慢 SQL 不但影响客户体验,对系统稳定性也是极大的隐患。

超多分页慢SQL

xml
<!-- 分页查询订单列表 -->
+}

分页 size 的大小并没有一个固定的标准,取决于业务需求、数据量及数据库等,但动辄几千上万的分页 size,会带来性能瓶颈,而大量的慢 SQL 不但影响客户体验,对系统稳定性也是极大的隐患。

超多分页慢SQL

xml
<!-- 分页查询订单列表 -->
 <select id="list" parameterType="com.xxx.OrderPageQuery" resultType="com.xxx.OrderDO">
     SELECT
         <include refid="all_columns"/>
diff --git "a/categories/fragments/2019/12/28/\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.html" "b/categories/fragments/2019/12/28/\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.html"
index 3516c797ff..551c3c9a46 100644
--- "a/categories/fragments/2019/12/28/\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.html"
+++ "b/categories/fragments/2019/12/28/\344\270\252\344\272\272SQL\344\274\230\345\214\226\346\212\200\345\267\247.html"
@@ -15,7 +15,7 @@
     
     
     
-    
+    
     
     
     
@@ -58,11 +58,11 @@
             2023年 (3篇)
目录

个人 SQL 优化技巧 持续更新

查询优化

如果确定结果只有一条,使用 LIMIT 1 建议

我们在根据一个或多个条件查询数据时,如果确定查询结果只有一条,可以在结尾处添加 LIMIT 1 进行限制。

这样既可以让 EXPLAIN 中的 type 达到 const 类型,又可以免去担忧在程序中出现接收是单个对象却返回了一个集合对象的异常问题。

sql
# email 不是主键,也没有设置唯一约束,根据熵增定律,查询结果是有可能会出现多条的
+            2019年 (4篇)
目录

个人 SQL 优化技巧 持续更新

查询优化

如果确定结果只有一条,使用 LIMIT 1 建议

我们在根据一个或多个条件查询数据时,如果确定查询结果只有一条,可以在结尾处添加 LIMIT 1 进行限制。

这样既可以让 EXPLAIN 中的 type 达到 const 类型,又可以免去担忧在程序中出现接收是单个对象却返回了一个集合对象的异常问题。

sql
# email 不是主键,也没有设置唯一约束,根据熵增定律,查询结果是有可能会出现多条的
 SELECT * FROM `sys_user` WHERE `email` = 'charles7c@126.com' LIMIT 1;
# email 不是主键,也没有设置唯一约束,根据熵增定律,查询结果是有可能会出现多条的
 SELECT * FROM `sys_user` WHERE `email` = 'charles7c@126.com' LIMIT 1;
sql
# user_id 是主键,主键是非空唯一的,那么不需要添加 LIMIT 进行限制
 SELECT * FROM `sys_user` WHERE `user_id` = 1;
# user_id 是主键,主键是非空唯一的,那么不需要添加 LIMIT 进行限制
-SELECT * FROM `sys_user` WHERE `user_id` = 1;

避免隐式类型转换 强制

我们在使用 MySQL 时,或多或少都感受过 MySQL 的隐式类型转换。例如:user_id 是整数类型,但是依然可以使用字符串类型数据来进行判断。MySQL 帮你做完这种隐式类型转换是有代价的,什么代价呢? 索引不再生效了而已

sql
SELECT * FROM `sys_user` WHERE `user_id` = 10;
SELECT * FROM `sys_user` WHERE `user_id` = 10;
sql
SELECT * FROM `sys_user` WHERE `user_id` = '10';
SELECT * FROM `sys_user` WHERE `user_id` = '10';

数据库表设计

列名带上前缀 建议

部分列名带上前缀或缩写,可以有效减少在连接查询、ORM 映射等场景下刻意起别名或思考区分不同的问题。

sql
CREATE TABLE `sys_customer` (
+SELECT * FROM `sys_user` WHERE `user_id` = 1;

避免隐式类型转换 强制

我们在使用 MySQL 时,或多或少都感受过 MySQL 的隐式类型转换。例如:user_id 是整数类型,但是依然可以使用字符串类型数据来进行判断。MySQL 帮你做完这种隐式类型转换是有代价的,什么代价呢? 索引不再生效了而已

sql
SELECT * FROM `sys_user` WHERE `user_id` = 10;
SELECT * FROM `sys_user` WHERE `user_id` = 10;
sql
SELECT * FROM `sys_user` WHERE `user_id` = '10';
SELECT * FROM `sys_user` WHERE `user_id` = '10';

数据库表设计

列名带上前缀 建议

部分列名带上前缀或缩写,可以有效减少在连接查询、ORM 映射等场景下刻意起别名或思考区分不同的问题。

sql
CREATE TABLE `sys_customer` (
   `customer_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '客户ID',
   `customer_name` varchar(255) NOT NULL COMMENT '客户名称',
   PRIMARY KEY (`customer_id`) USING BTREE,
@@ -92,13 +92,13 @@
 
 # 连接查询,你完全不需要用脑去考虑到底是 c.`id` 还是 cu.`customer_id` 的问题,都是 `customer_id`
 SELECT * FROM `sys_customer` c 
-LEFT JOIN `sys_contact_user` cu ON c.`customer_id` = cu.`customer_id`

非负数列添加UNSIGNED无符号约束 建议

在大部分的数据存储场景中,我们只会使用正整数,如果能确定该列为非负数,建议添加 UNSIGNED 无符号约束。

sql
# 不添加 UNSIGNED 约束,取值范围
+LEFT JOIN `sys_contact_user` cu ON c.`customer_id` = cu.`customer_id`

非负数列添加UNSIGNED无符号约束 建议

在大部分的数据存储场景中,我们只会使用正整数,如果能确定该列为非负数,建议添加 UNSIGNED 无符号约束。

sql
# 不添加 UNSIGNED 约束,取值范围
 TINYINT:[-128, 127]
 # 添加 UNSIGNED 约束,取值范围
 TINYINT:[0, 255]
# 不添加 UNSIGNED 约束,取值范围
 TINYINT:[-128, 127]
 # 添加 UNSIGNED 约束,取值范围
-TINYINT:[0, 255]

合理采用整数类型 建议

例如:状态类的信息,状态再多能有多少个,采用 TINYINT 即可,减少存储空间占用。

下方表数据整理于:MySQL 5.7官方文档/数据类型/数值数据类型/整数类型

类型存储(字节)取值范围取值范围(无符号)
TINYINT1[-128, 127][0, 255]
SMALLINT2[-32768, 32767][0, 65535]
MEDIUMINT3[-8388608, 8388607][0, 16777215]
INT4[-2147483648, 2147483647][0, 4294967295]
BIGINT8[-2^63^, 2^63^-1][0, 2^64^-1]

布尔列采用bit类型 建议

例如:是否删除这种只有两种状态的信息,在表设计时建议对该列设置 bit 类型(0表示否/假/false,1表示是/真/true),在程序语言中可以采用 boolean 类型对应。

sql
`is_deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已删除(0否 1是)'
`is_deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已删除(0否 1是)'
java
@Data
+TINYINT:[0, 255]

合理采用整数类型 建议

例如:状态类的信息,状态再多能有多少个,采用 TINYINT 即可,减少存储空间占用。

下方表数据整理于:MySQL 5.7官方文档/数据类型/数值数据类型/整数类型

类型存储(字节)取值范围取值范围(无符号)
TINYINT1[-128, 127][0, 255]
SMALLINT2[-32768, 32767][0, 65535]
MEDIUMINT3[-8388608, 8388607][0, 16777215]
INT4[-2147483648, 2147483647][0, 4294967295]
BIGINT8[-2^63^, 2^63^-1][0, 2^64^-1]

布尔列采用bit类型 建议

例如:是否删除这种只有两种状态的信息,在表设计时建议对该列设置 bit 类型(0表示否/假/false,1表示是/真/true),在程序语言中可以采用 boolean 类型对应。

sql
`is_deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已删除(0否 1是)'
`is_deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已删除(0否 1是)'
java
@Data
 public class User {
     /**
      * 是否已删除(0否 1是)
@@ -111,7 +111,7 @@
      */
     private Boolean isDeleted;
 }

数据库设计

采用utf8mb4编码 建议

如果要存储特殊字符(例如:emoij表情符),使用 utf8mb4 编码。

MySQL 5.5.3 后增加了一个新的编码: utf8mb4 ,其中 mb4 是 most bytes 4 的意思,用于兼容四字节的 unicode。

utf8mb4 是 utf8 的超集,除了将编码改为 utf8mb4 外不需要做其他转换。

设计数据库时如果想要允许用户使用特殊符号,最好使用 utf8mb4 编码来存储,使得数据库有更好的兼容性,但是这样设计会导致耗费更多的存储空间。

- + \ No newline at end of file diff --git "a/categories/fragments/2019/12/29/\344\270\252\344\272\272\345\270\270\347\224\250Stream\344\275\277\347\224\250\346\212\200\345\267\247.html" "b/categories/fragments/2019/12/29/\344\270\252\344\272\272\345\270\270\347\224\250Stream\344\275\277\347\224\250\346\212\200\345\267\247.html" index cbfa515cfc..966e471b2d 100644 --- "a/categories/fragments/2019/12/29/\344\270\252\344\272\272\345\270\270\347\224\250Stream\344\275\277\347\224\250\346\212\200\345\267\247.html" +++ "b/categories/fragments/2019/12/29/\344\270\252\344\272\272\345\270\270\347\224\250Stream\344\275\277\347\224\250\346\212\200\345\267\247.html" @@ -85,7 +85,7 @@ // 3、输出结果 // 超级管理员,管理员,普通用户 System.out.println(result);
- + \ No newline at end of file diff --git "a/categories/fragments/2019/12/30/\344\270\252\344\272\272\345\270\270\347\224\250Hutool\345\267\245\345\205\267\347\261\273.html" "b/categories/fragments/2019/12/30/\344\270\252\344\272\272\345\270\270\347\224\250Hutool\345\267\245\345\205\267\347\261\273.html" index a80c6c9c6b..d9a2aadb8b 100644 --- "a/categories/fragments/2019/12/30/\344\270\252\344\272\272\345\270\270\347\224\250Hutool\345\267\245\345\205\267\347\261\273.html" +++ "b/categories/fragments/2019/12/30/\344\270\252\344\272\272\345\270\270\347\224\250Hutool\345\267\245\345\205\267\347\261\273.html" @@ -473,7 +473,7 @@ int initialCapacity = (int) (size / DEFAULT_LOAD_FACTOR) + 1; return isOrder ? new LinkedHashMap<>(initialCapacity) : new HashMap<>(initialCapacity); }
- + \ No newline at end of file diff --git "a/categories/fragments/2019/12/31/\344\270\252\344\272\272\345\270\270\347\224\250Linux\345\221\275\344\273\244.html" "b/categories/fragments/2019/12/31/\344\270\252\344\272\272\345\270\270\347\224\250Linux\345\221\275\344\273\244.html" index b403d7e878..cd83c7eae9 100644 --- "a/categories/fragments/2019/12/31/\344\270\252\344\272\272\345\270\270\347\224\250Linux\345\221\275\344\273\244.html" +++ "b/categories/fragments/2019/12/31/\344\270\252\344\272\272\345\270\270\347\224\250Linux\345\221\275\344\273\244.html" @@ -169,7 +169,7 @@ # -S(--show-error):指定只输出错误信息,通常与 -s 一起使用 # -L(--location):让 HTTP 请求跟随服务器的重定向,curl 默认不跟随重定向 curl -fsSL https://get.docker.com
- + \ No newline at end of file diff --git "a/categories/fragments/2021/03/12/\347\262\276\345\257\206\350\256\241\347\256\227\345\267\245\345\205\267\347\261\273-BigDecimal.html" "b/categories/fragments/2021/03/12/\347\262\276\345\257\206\350\256\241\347\256\227\345\267\245\345\205\267\347\261\273-BigDecimal.html" index 671fa68b13..b920ec0255 100644 --- "a/categories/fragments/2021/03/12/\347\262\276\345\257\206\350\256\241\347\256\227\345\267\245\345\205\267\347\261\273-BigDecimal.html" +++ "b/categories/fragments/2021/03/12/\347\262\276\345\257\206\350\256\241\347\256\227\345\267\245\345\205\267\347\261\273-BigDecimal.html" @@ -377,7 +377,7 @@ } }

参考资料

[1]为什么在处理金额时不能用Double和Float,计算机是如何存储浮点数的:https://blog.csdn.net/qq_41872247/article/details/107861608

[2]java.math.BigDecimal类的用法:https://www.iteye.com/blog/jeelee-652003

[3]BigDecimal中的取整模式:https://blog.csdn.net/u010523770/article/details/53068809

后记

C: 好了,BigDecimal 类的介绍到这儿就结束了,认识了 BigDecimal 之后,可千万别再用 double 、float 来进行精确计算了。

- + \ No newline at end of file diff --git "a/categories/fragments/2021/05/29/\350\256\276\350\256\241\346\250\241\345\274\217\344\271\213\345\215\225\344\276\213\346\250\241\345\274\217.html" "b/categories/fragments/2021/05/29/\350\256\276\350\256\241\346\250\241\345\274\217\344\271\213\345\215\225\344\276\213\346\250\241\345\274\217.html" index cbfc042c39..f3c6269c87 100644 --- "a/categories/fragments/2021/05/29/\350\256\276\350\256\241\346\250\241\345\274\217\344\271\213\345\215\225\344\276\213\346\250\241\345\274\217.html" +++ "b/categories/fragments/2021/05/29/\350\256\276\350\256\241\346\250\241\345\274\217\344\271\213\345\215\225\344\276\213\346\250\241\345\274\217.html" @@ -345,7 +345,7 @@ at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178) at com.example.pattern05.SingletonPattern.demo6.Test.main(Test.java:16)

另外,其实枚举式还有一个好处,就是如果涉及到反序列化创建单例对象时,它可以保证反序列化的返回结果是同一对象。对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现 readResolve 方法。

后记

C: 其实,还有一些像 CAS 等方式实现的单例模式,有兴趣的同学们可以自行去了解一下。而上述的这些方式,是我们平时用到最多的单例模式实现方式。

在实际使用的时候,需要根据实际的需求来灵活进行选择,当你确定要在全局持续使用的时候,那么饿汉式、枚举式都比较适合,而如果明确需要懒加载的时候,那么双重校验锁、静态内部类的方式就比较适合。而如果你想防止反射破坏的话,那就可以选择枚举式。如果你需要涉及反序列化创建对象,那也可以选择枚举式。但是,如果你需要对单例类进行继承等操作,那就不能选择它了。

总之,能够解决需求的方式就是好方式。

参考资料

[1]单例模式:https://www.runoob.com/design-pattern/singleton-pattern.html

[2]为什么双重检查锁模式需要 volatile ?https://www.cnblogs.com/goodAndyxublog/p/11356402.html

[3]如何写出更优雅的单例模式?https://mp.weixin.qq.com/s/AdJI5a4w515SPPI_4gVImA

- + \ No newline at end of file diff --git "a/categories/fragments/2022/02/16/\344\270\252\344\272\272\345\270\270\347\224\250SQL\345\207\275\346\225\260.html" "b/categories/fragments/2022/02/16/\344\270\252\344\272\272\345\270\270\347\224\250SQL\345\207\275\346\225\260.html" index 7f8e6fce37..5dd2f31450 100644 --- "a/categories/fragments/2022/02/16/\344\270\252\344\272\272\345\270\270\347\224\250SQL\345\207\275\346\225\260.html" +++ "b/categories/fragments/2022/02/16/\344\270\252\344\272\272\345\270\270\347\224\250SQL\345\207\275\346\225\260.html" @@ -83,7 +83,7 @@ FROM information_schema.TABLES WHERE TABLE_SCHEMA = '数据库名';
  • 将执行结果复制,直接执行即可

  • - + \ No newline at end of file diff --git "a/categories/fragments/2022/03/25/\345\220\210\345\271\266\344\270\244\344\270\252Git\344\273\223\345\272\223\347\232\204\345\216\206\345\217\262\346\217\220\344\272\244\350\256\260\345\275\225.html" "b/categories/fragments/2022/03/25/\345\220\210\345\271\266\344\270\244\344\270\252Git\344\273\223\345\272\223\347\232\204\345\216\206\345\217\262\346\217\220\344\272\244\350\256\260\345\275\225.html" index 7b57b34c2b..987d2e9dee 100644 --- "a/categories/fragments/2022/03/25/\345\220\210\345\271\266\344\270\244\344\270\252Git\344\273\223\345\272\223\347\232\204\345\216\206\345\217\262\346\217\220\344\272\244\350\256\260\345\275\225.html" +++ "b/categories/fragments/2022/03/25/\345\220\210\345\271\266\344\270\244\344\270\252Git\344\273\223\345\272\223\347\232\204\345\216\206\345\217\262\346\217\220\344\272\244\350\256\260\345\275\225.html" @@ -63,7 +63,7 @@ cd eladminx

    202203252252923

  • 将前端仓库作为后端仓库的远程仓库,起别名为 frontend(这个随便起)

    sh
    git remote add -f frontend https://gitee.com/Charles7c/eladminx-web.git
    git remote add -f frontend https://gitee.com/Charles7c/eladminx-web.git

    202203252252926

  • 将前端仓库的 master 分支(自己选择哪个分支)合并到后端仓库

    sh
    git merge --strategy ours --no-commit frontend/master
    git merge --strategy ours --no-commit frontend/master

    202203252252929

    想法很美,但是报错了:

    fatal: refusing to merge unrelated histories
    fatal: refusing to merge unrelated histories

    这是因为后端仓库的本地分支历史记录和前端仓库的历史记录不匹配,人家 Git 怀疑你是不是合并错了,但咱们知道就是要合并,写个声明 “表明出事儿与人家无关”就可以了。

    sh
    git merge --strategy ours --allow-unrelated-histories --no-commit frontend/master
    git merge --strategy ours --allow-unrelated-histories --no-commit frontend/master

    202203252252931

  • 将前端仓库的 master 分支内容放到在后端仓库内刚建好的 eladminx-web 文件夹中

    sh
    mkdir eladminx-web
     git read-tree --prefix=eladminx-web/ -u frontend/master
    mkdir eladminx-web
     git read-tree --prefix=eladminx-web/ -u frontend/master
  • 提交刚才的修改(毕竟你刚才又合并又创建文件夹的,肯定要提交修改啊)

    sh
    git commit -m "迁移前端项目仓库,与后端项目仓库合并"
    git commit -m "迁移前端项目仓库,与后端项目仓库合并"
  • 最后将本地的修改强制推送到远程仓库即可

    sh
    git push --force
    git push --force
  • 到此为止,笔者这两个项目的 master 分支就合并完了,如果你想合并其他分支,例如:dev,那就首先把后端仓库的分支切换到 dev,然后将上述中的 master 这个分支名换为 dev 就可以了。

    后记

    C: 关于这个合并,你以哪个仓库为主都可以。最后合并的提交记录是以历史提交时间进行降序排列的。

    - + \ No newline at end of file diff --git "a/categories/fragments/2022/03/26/\344\277\256\346\224\271Git\346\234\200\345\220\216\344\270\200\346\254\241\346\217\220\344\272\244\350\256\260\345\275\225\347\232\204\344\275\234\350\200\205\345\222\214\351\202\256\347\256\261.html" "b/categories/fragments/2022/03/26/\344\277\256\346\224\271Git\346\234\200\345\220\216\344\270\200\346\254\241\346\217\220\344\272\244\350\256\260\345\275\225\347\232\204\344\275\234\350\200\205\345\222\214\351\202\256\347\256\261.html" index 51bf0d82eb..f2010e4c5c 100644 --- "a/categories/fragments/2022/03/26/\344\277\256\346\224\271Git\346\234\200\345\220\216\344\270\200\346\254\241\346\217\220\344\272\244\350\256\260\345\275\225\347\232\204\344\275\234\350\200\205\345\222\214\351\202\256\347\256\261.html" +++ "b/categories/fragments/2022/03/26/\344\277\256\346\224\271Git\346\234\200\345\220\216\344\270\200\346\254\241\346\217\220\344\272\244\350\256\260\345\275\225\347\232\204\344\275\234\350\200\205\345\222\214\351\202\256\347\256\261.html" @@ -59,7 +59,7 @@ 2022年 (15篇)
    目录

    修改Git最后一次提交记录的作者和邮箱

    前言

    C: 今天周末了,抽出了一点时间继续维护下之前提到过的衍生开源项目。修了一个 bug 后,提交了一下。但是突然想起来,今天开发用的是工作本,工作本中的 Git author、email 是配的真实姓名和公司邮箱,提交前忘了修改,现在已经推送到开源平台了,这肯定不行啊。

    但是现在即使修改了本地的 Git author、email 配置,历史提交记录也改变不了啊。别着急,看看怎么解决的。

    问题解决

    如果你确定是和笔者一样的情况,即确保是要修改最后一次提交记录,无论有没有推送到远程仓库都没问题。解决方法就两步,是不是很简单?

    1. 修改最后一次提交的作者和邮箱信息

      sh
      git commit --amend --author="Charles7c <charles7c@126.com>"
      git commit --amend --author="Charles7c <charles7c@126.com>"
    2. 最后将本地的修改强制推送到远程仓库即可(如果你没推送到远程仓库,这步就不需要执行了)

      sh
      git push --force
      git push --force

    后记

    C: 另外说一下,如果你要修改最后一次提交记录的 commit message,执行下面的命令就可以了。

    sh
    git commit --amend -m "要修改为的提交信息"
    git commit --amend -m "要修改为的提交信息"

    上方修改作者和邮箱信息的命令,还可以继续加参数 --date 来修改提交时间,有需要的话去试试去吧。

    - + \ No newline at end of file diff --git "a/categories/fragments/2022/03/27/\344\277\256\346\224\271Git\346\211\200\346\234\211\346\217\220\344\272\244\350\256\260\345\275\225\344\270\255\347\232\204\344\275\234\350\200\205\345\222\214\351\202\256\347\256\261.html" "b/categories/fragments/2022/03/27/\344\277\256\346\224\271Git\346\211\200\346\234\211\346\217\220\344\272\244\350\256\260\345\275\225\344\270\255\347\232\204\344\275\234\350\200\205\345\222\214\351\202\256\347\256\261.html" index 8648fb9f4f..855222ab3b 100644 --- "a/categories/fragments/2022/03/27/\344\277\256\346\224\271Git\346\211\200\346\234\211\346\217\220\344\272\244\350\256\260\345\275\225\344\270\255\347\232\204\344\275\234\350\200\205\345\222\214\351\202\256\347\256\261.html" +++ "b/categories/fragments/2022/03/27/\344\277\256\346\224\271Git\346\211\200\346\234\211\346\217\220\344\272\244\350\256\260\345\275\225\344\270\255\347\232\204\344\275\234\350\200\205\345\222\214\351\202\256\347\256\261.html" @@ -93,7 +93,7 @@ export GIT_AUTHOR_EMAIL="$NEW_EMAIL" fi ' --tag-name-filter cat -- --branches --tags
  • 保存脚本

  • 将脚本文件放到要批量修改提交记录的 Git 仓库中(根目录就行)

  • 执行脚本

  • 随后你就会看到,先是提示一个 warn 警告,然后它会一条条的修改以往提交记录,如果错误的提交比较多,那就耐心等一会儿吧。

    - + \ No newline at end of file diff --git "a/categories/fragments/2022/03/28/\344\270\272\346\214\207\345\256\232Git\344\273\223\345\272\223\345\215\225\347\213\254\351\205\215\347\275\256\347\224\250\346\210\267\345\220\215\345\222\214\351\202\256\347\256\261.html" "b/categories/fragments/2022/03/28/\344\270\272\346\214\207\345\256\232Git\344\273\223\345\272\223\345\215\225\347\213\254\351\205\215\347\275\256\347\224\250\346\210\267\345\220\215\345\222\214\351\202\256\347\256\261.html" index e1d455058c..68b800fc75 100644 --- "a/categories/fragments/2022/03/28/\344\270\272\346\214\207\345\256\232Git\344\273\223\345\272\223\345\215\225\347\213\254\351\205\215\347\275\256\347\224\250\346\210\267\345\220\215\345\222\214\351\202\256\347\256\261.html" +++ "b/categories/fragments/2022/03/28/\344\270\272\346\214\207\345\256\232Git\344\273\223\345\272\223\345\215\225\347\213\254\351\205\215\347\275\256\347\224\250\346\210\267\345\220\215\345\222\214\351\202\256\347\256\261.html" @@ -81,7 +81,7 @@ git config user.name # 查看所在 Git 仓库配置的邮箱 git config user.email
    - + \ No newline at end of file diff --git "a/categories/fragments/2022/08/29/\345\206\205\347\275\221CentOS\346\234\215\345\212\241\345\231\250\350\256\276\347\275\256\347\275\221\347\273\234\344\273\243\347\220\206.html" "b/categories/fragments/2022/08/29/\345\206\205\347\275\221CentOS\346\234\215\345\212\241\345\231\250\350\256\276\347\275\256\347\275\221\347\273\234\344\273\243\347\220\206.html" index 5e7b5bc67f..a6f7ee0e38 100644 --- "a/categories/fragments/2022/08/29/\345\206\205\347\275\221CentOS\346\234\215\345\212\241\345\231\250\350\256\276\347\275\256\347\275\221\347\273\234\344\273\243\347\220\206.html" +++ "b/categories/fragments/2022/08/29/\345\206\205\347\275\221CentOS\346\234\215\345\212\241\345\231\250\350\256\276\347\275\256\347\275\221\347\273\234\344\273\243\347\220\206.html" @@ -69,7 +69,7 @@ export https_proxy=http://用户名:密码@你的代理服务器地址:你的代理服务器端口号

    设置yum代理

    因为安装一些环境的时候还需要用到 yum,所以给 yum 也配置一下代理。

    1. 修改 yum.conf 文件

      shell
      vi /etc/yum.conf
      vi /etc/yum.conf
    2. 在 yum.conf 文件末尾,追加下方配置内容

      shell
      proxy=http://你的代理服务器地址:你的代理服务器端口号
      proxy=http://你的代理服务器地址:你的代理服务器端口号

      当然了,如果你的代理服务器需要登录,写法也和设置 http/https 代理时一样。

    都设置完之后,执行 reboot 重启服务器,让配置生效即可。

    检测是否可以上网

    重启后,为了确认配置是否成功,执行 curl 来测试一下。

    shell
    curl www.baidu.com
    curl www.baidu.com

    很明显,看到下面的返回就知道配置成功了,如期返回了百度的页面内容。

    html
    <!DOCTYPE html>
     <!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
    <!DOCTYPE html>
     <!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
    - + \ No newline at end of file diff --git "a/categories/fragments/2022/10/01/\344\270\252\344\272\272\345\270\270\347\224\250Docker\345\221\275\344\273\244.html" "b/categories/fragments/2022/10/01/\344\270\252\344\272\272\345\270\270\347\224\250Docker\345\221\275\344\273\244.html" index 04035ffb9a..456d237efe 100644 --- "a/categories/fragments/2022/10/01/\344\270\252\344\272\272\345\270\270\347\224\250Docker\345\221\275\344\273\244.html" +++ "b/categories/fragments/2022/10/01/\344\270\252\344\272\272\345\270\270\347\224\250Docker\345\221\275\344\273\244.html" @@ -157,7 +157,7 @@ docker logs -f [-t] [-n 行数] [--since 开始时间] [--until 结束时间] 容器ID/容器名称

    备份容器为本地镜像

    笔者说

    如果镜像名称后不指定 标签/版本 ,则会默认使用最新版本(latest)。

    shell
    docker commit [-a "作者"] [-m "信息"] 容器ID/容器名称 镜像名称[:标签/版本]
    docker commit [-a "作者"] [-m "信息"] 容器ID/容器名称 镜像名称[:标签/版本]

    将容器导出为 tar.gz 文件

    shell
    docker export 容器ID/容器名称 > 文件路径.tar.gz
    docker export 容器ID/容器名称 > 文件路径.tar.gz

    将 tar.gz 文件导入为镜像

    笔者说

    如果镜像名称后不指定 标签/版本 ,则会默认使用最新版本(latest)。

    shell
    docker import 文件路径.tar.gz 镜像名称[:标签/版本]
    docker import 文件路径.tar.gz 镜像名称[:标签/版本]

    网络相关

    查看网络列表

    shell
    docker network ls
    docker network ls

    创建 bridge 网络

    shell
    docker network create 网络名称
    docker network create 网络名称

    删除网络

    shell
    docker network rm 网络ID/网络名称
    docker network rm 网络ID/网络名称

    其他

    查看 docker 版本

    shell
    docker -v
     docker version
    docker -v
     docker version

    查看 docker 信息

    shell
    docker info
    docker info

    docker-compose命令

    启动并后台运行所有的服务

    shell
    docker-compose up -d
    docker-compose up -d

    停止并删除容器、网络、卷、镜像

    shell
    docker-compose down
    docker-compose down

    列出项目中目前的所有容器

    shell
    docker-compose ps
    docker-compose ps

    停止容器

    shell
    docker-compose stop 容器名
    docker-compose stop 容器名

    启动容器

    shell
    docker-compose start 容器名
    docker-compose start 容器名

    修改 yml 文件后,重新启动并后台运行

    shell
    docker-compose up --force-recreate -d
    docker-compose up --force-recreate -d
    - + \ No newline at end of file diff --git "a/categories/fragments/2022/10/05/\344\270\252\344\272\272\345\270\270\347\224\250Git\345\221\275\344\273\244.html" "b/categories/fragments/2022/10/05/\344\270\252\344\272\272\345\270\270\347\224\250Git\345\221\275\344\273\244.html" index 5be1ce8131..b956270046 100644 --- "a/categories/fragments/2022/10/05/\344\270\252\344\272\272\345\270\270\347\224\250Git\345\221\275\344\273\244.html" +++ "b/categories/fragments/2022/10/05/\344\270\252\344\272\272\345\270\270\347\224\250Git\345\221\275\344\273\244.html" @@ -121,7 +121,7 @@ # git tag -a v1.0.0 -m "version 1.0.0" b43375 # git tag v1.0.0 git tag [-a 标签名] [-m 标签信息] [Commit ID]

    查看指定标签详细信息

    shell
    git show 标签名
    git show 标签名

    删除标签

    shell
    git tag -d 标签名
    git tag -d 标签名

    将本地标签推送到远程仓库

    shell
    git push origin -tags
    git push origin -tags
    - + \ No newline at end of file diff --git "a/categories/fragments/2022/10/06/\344\270\252\344\272\272\345\270\270\347\224\250\345\277\253\346\215\267\351\224\256.html" "b/categories/fragments/2022/10/06/\344\270\252\344\272\272\345\270\270\347\224\250\345\277\253\346\215\267\351\224\256.html" index 09342c9d3d..6372537198 100644 --- "a/categories/fragments/2022/10/06/\344\270\252\344\272\272\345\270\270\347\224\250\345\277\253\346\215\267\351\224\256.html" +++ "b/categories/fragments/2022/10/06/\344\270\252\344\272\272\345\270\270\347\224\250\345\277\253\346\215\267\351\224\256.html" @@ -59,7 +59,7 @@ 2022年 (15篇)
    目录

    个人常用快捷键 持续更新

    通用

    笔者说

    下方快捷键,在大部分软件中通用。

    • Ctrl + C:复制光标所在行 或 复制选中内容
    • Ctrl + V:粘贴复制内容到光标所在行
    • Ctrl + X: 剪切光标所在行 或 剪切选中内容
    • Ctrl + Z:撤销上一步操作(前提是没关闭当前文件)
    • Ctrl + Y:恢复上一步操作(前提是没关闭当前文件)
    • Ctrl + S:保存
      • Ctrl + Shift + S:全部窗口保存
    • Ctrl + A:全选
    • Ctrl + F:在当前文件中进行文本查找
    • Ctrl + H:在当前文件中进行文本替换
    • Tab:向右缩进
    • Shift + Tab:向左缩进
    • Ctrl + W:关闭窗口
      • Ctrl + Shift + W:全部窗口关闭
    • Ctrl + B:粗体
    • Ctrl + I:斜体
    • Ctrl + U:下划线

    Windows

    • Windows:打开开始菜单

    • Windows + D:显示桌面

    • Windows + L:锁屏

    • Windows + E:打开资源管理器

    • Windows + Shift + S:打开系统自带截图

    • Windows + V:打开剪贴板

    • Windows + Tab -> 方向键切换窗口,回车键进入窗口:打开多窗口视图

    • Windows + R:打开“运行”对话框

      • 输入 cmd:打开命令行程序
      • 输入 notepad:打开记事本程序
      • 输入 calc:打开计算器程序
      • 输入 mspaint:打开绘图程序
      • 输入 regedit:打开注册表
      • 输入 services.msc:打开服务列表
      • 输入 mstsc:打开远程桌面连接
      • 输入 subl:打开 Sublime Text 程序(需要安装 Sublime Text并提前设置好环境变量)
      • 输入 typora:打开 Typora 程序(需要安装 Typora)
    • Ctrl + Alt + Delete:打开任务管理器

    • Alt + Tab -> Alt 键不松手,Tab键切换窗口,松手后进入窗口:切换窗口视图

    • [Fn] + Alt + F4:关闭窗口(关闭程序)

      • Alt + 空格键 + C
    • [Fn] + F2:文件重命名

    IntelliJ IDEA

    快捷键

    • Ctrl + Alt + L:格式化代码(代码写不规范的童鞋,起码学会这个快捷键吧)
    • Ctrl + D:复制光标所在行 或 复制选中内容,并把复制内容插入到光标位置下面
    • Ctrl + Y:删除光标所在行 或 删除选中的行(与通用快捷键不同)
    • [Fn] + Alt + Insert:弹出菜单,可以选择进行生成 getter/setter、生成构造方法,重写 toString 等(有了 Lombok 后用的频率低了很多)
    • Ctrl + Alt + 回车:在上方插入一行,光标移动到新行行首
    • Shift + 回车:在下方插入一行,光标移动到新行行首
    • Ctrl + /:给光标所在行 或 选中行代码 添加或取消单行注释(可根据当前的文件类型,使用不同的注释符号)
    • Ctrl + Shift + /:以 多行注释 注释掉选中行
    • Ctrl + Alt + T:给选中代码块添加语句块(try-catch、while等)
    • Alt + 回车:提供快速修复选择
    • Alt + Shift + 上/下键:向上或向下移动当前行/选中行
    • Ctrl + Shift + 上/下键:向上或向下移动当前方法/选中方法
    • Ctrl + Shift + 回车:在当前行任何地方可以快速在末尾生成分号
    • Ctrl + F:在当前文件中进行文本查找
      • Ctrl + Shift + F:全局查找
    • Ctrl + R:在当前文件中进行文本替换(与通用快捷键不同)
      • Ctrl + Shift + R:全局替换
    • Ctrl + H:显示当前类的层次结构
    • [Fn] + Ctrl + F12:显示当前类的结构(方法、属性等)
    • Ctrl + 鼠标左键:在变量或方法上使用此快捷键,可以找到变量或方法的定义位置(如果是已经在变量或方法的定义位置,按下就会进入或弹出它被使用的位置)
      • Ctrl + Alt + 鼠标左键:在某个使用的方法上点击,可直接定位到该方法在对应子类重写的位置(在Controller中想看看调用Service层怎么实现的)
    • [Fn] + Ctrl + End:跳转到文件尾部
    • [Fn] + Ctrl + Home:跳转到文件头部
    • Ctrl + Q:显示光标所在的类名、方法名、变量名的 java doc 注释
    • Ctrl + Alt + O:优化 import 语句,自动导入包或移除无用包
    • [Fn] + Shift + F9:调试按钮
    • [Fn] + Shift + F10:运行按钮
    • Ctrl + T:等效于工具栏 pull 按钮 - VCS(版本控制系统)
    • Ctrl + K:等效于工具栏 commit 按钮 - VCS(版本控制系统)
    • Ctrl + Alt + Z:撤销当前文件的修改(版本控制系统)

    快捷短语

    • psvm + 回车:生成 main 方法
    • sout + 回车:生成输出语句(System.out.println();)
    • 在创建对象时,先写完后面 new Xxx() 部分,然后输入 .var + 回车:补全前面声明部分
    • 数组/Collection系列集合,.for + 回车:生成增强 for 语句
    • 数组/Collection系列集合,.fori + 回车:生成循环下标语句(Set集合不行)
    • 返回值,.return + 回车:生成 return 返回值; 语句

    浏览器

    • Ctrl + 0:恢复页面默认缩放
    - + \ No newline at end of file diff --git "a/categories/fragments/2022/10/26/Docker\345\256\211\350\243\205OpenLDAP.html" "b/categories/fragments/2022/10/26/Docker\345\256\211\350\243\205OpenLDAP.html" index 12f1ab0059..389273e66a 100644 --- "a/categories/fragments/2022/10/26/Docker\345\256\211\350\243\205OpenLDAP.html" +++ "b/categories/fragments/2022/10/26/Docker\345\256\211\350\243\205OpenLDAP.html" @@ -131,7 +131,7 @@ - /opt/disk/docker/volumes/openldap/conf:/etc/ldap/slapd.d - /opt/disk/docker/volumes/openldap/data:/var/lib/ldap privileged: true

    编写好 docker-compose.yml 脚本后,在脚本同级目录执行下方命令即可。

    shell
    docker-compose up -d
    docker-compose up -d
    - + \ No newline at end of file diff --git "a/categories/fragments/2022/10/27/Docker\345\256\211\350\243\205Consul.html" "b/categories/fragments/2022/10/27/Docker\345\256\211\350\243\205Consul.html" index 1cbe9df680..71f5e558b6 100644 --- "a/categories/fragments/2022/10/27/Docker\345\256\211\350\243\205Consul.html" +++ "b/categories/fragments/2022/10/27/Docker\345\256\211\350\243\205Consul.html" @@ -99,7 +99,7 @@ - /opt/disk/docker/volumes/consul/conf:/consul/conf - /opt/disk/docker/volumes/consul/data:/consul/data privileged: true

    编写好 docker-compose.yml 脚本后,在脚本同级目录执行下方命令即可。

    shell
    docker-compose up -d
    docker-compose up -d
    - + \ No newline at end of file diff --git "a/categories/fragments/2022/10/28/Docker\345\256\211\350\243\205MinIO.html" "b/categories/fragments/2022/10/28/Docker\345\256\211\350\243\205MinIO.html" index c764b7ae7b..d22c262091 100644 --- "a/categories/fragments/2022/10/28/Docker\345\256\211\350\243\205MinIO.html" +++ "b/categories/fragments/2022/10/28/Docker\345\256\211\350\243\205MinIO.html" @@ -153,7 +153,7 @@ - /opt/disk/docker/volumes/minio/data:/data command: server --address ':9000' --console-address ':9001' /data privileged: true

    编写好 docker-compose.yml 脚本后,在脚本同级目录执行下方命令即可。

    shell
    docker-compose up -d
    docker-compose up -d
    - + \ No newline at end of file diff --git "a/categories/fragments/2022/10/31/CentOS\345\256\211\350\243\205Docker.html" "b/categories/fragments/2022/10/31/CentOS\345\256\211\350\243\205Docker.html" index 8250dce8fe..36d1ee7b32 100644 --- "a/categories/fragments/2022/10/31/CentOS\345\256\211\350\243\205Docker.html" +++ "b/categories/fragments/2022/10/31/CentOS\345\256\211\350\243\205Docker.html" @@ -85,7 +85,7 @@ systemctl daemon-reload # 重启 docker systemctl restart docker

    Docker Compose 安装

    1. 下载 docker-compose 脚本,并改名为 docker-compose

      shell
      curl -L https://github.com/docker/compose/releases/download/v2.12.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
      curl -L https://github.com/docker/compose/releases/download/v2.12.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
    2. 给脚本授予可执行权限

      shell
      chmod +x /usr/local/bin/docker-compose
      chmod +x /usr/local/bin/docker-compose
    3. 检验是否安装成功

      shell
      docker-compose -v
      docker-compose -v

    参考资料

    1. Custom Docker daemon options#Runtime directory and storage driver:https://docs.docker.com/config/daemon/systemd/#runtime-directory-and-storage-driver
    - + \ No newline at end of file diff --git "a/categories/fragments/2022/11/01/\344\275\277\347\224\250IDEA\350\277\233\350\241\214\350\277\234\347\250\213\347\250\213\345\272\217\350\260\203\350\257\225.html" "b/categories/fragments/2022/11/01/\344\275\277\347\224\250IDEA\350\277\233\350\241\214\350\277\234\347\250\213\347\250\213\345\272\217\350\260\203\350\257\225.html" index aaa3b3fc4f..12a54a3719 100644 --- "a/categories/fragments/2022/11/01/\344\275\277\347\224\250IDEA\350\277\233\350\241\214\350\277\234\347\250\213\347\250\213\345\272\217\350\260\203\350\257\225.html" +++ "b/categories/fragments/2022/11/01/\344\275\277\347\224\250IDEA\350\277\233\350\241\214\350\277\234\347\250\213\347\250\213\345\272\217\350\260\203\350\257\225.html" @@ -63,7 +63,7 @@ java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 你的程序名.jar --spring.profiles.active=test

    好了,还差最后一步,把你配置的远程调试端口放开。

    启动远程调试

    回到 IDE 中,点击 [Debug 运行] 按钮,如果控制台窗口出现下方提示,那么接下来该怎么加断点,该怎么触发断点,该怎么进行调试就不用笔者过多介绍了吧?

    shell
    # Connected to the target VM, address: ''xxx:5005', transport: 'socket''
     已连接到目标 VM,地址:''xxx:5005', 传输: '套接字''
    # Connected to the target VM, address: ''xxx:5005', transport: 'socket''
     已连接到目标 VM,地址:''xxx:5005', 传输: '套接字''

    202211012025211

    远程调试完后别忘了把远程调试端口关闭,该删除安全组规则就删除。

    笔者说

    哦对了,笔者这 “匪夷所思” 的问题到底解决了没有呢?当然解决了,而且这个问题不是代码的问题,而是一位远程办公的同事在他本地连接了测试环境数据库(这在笔者项目开发时强调过不允许连接),他的程序一直运行着把测试环境的一些任务数据处理了。

    具体的不多说,因为当你调试时程序运行正常,断开调试再试就出错,而且在你没调试前添加的部分日志埋点也没输出时,其实就应该摒弃所谓的 “不可能”,即:程序没有在你预想的机器上运行。毕竟福尔摩斯有句话:“排除一切不可能的,剩下的即使再不可能,那也是真相”。

    - + \ No newline at end of file diff --git "a/categories/fragments/2022/12/07/\347\275\221\347\253\231\345\274\200\345\220\257\347\201\260\350\211\262\346\230\276\347\244\272.html" "b/categories/fragments/2022/12/07/\347\275\221\347\253\231\345\274\200\345\220\257\347\201\260\350\211\262\346\230\276\347\244\272.html" index e506722524..de9e435c57 100644 --- "a/categories/fragments/2022/12/07/\347\275\221\347\253\231\345\274\200\345\220\257\347\201\260\350\211\262\346\230\276\347\244\272.html" +++ "b/categories/fragments/2022/12/07/\347\275\221\347\253\231\345\274\200\345\220\257\347\201\260\350\211\262\346\230\276\347\244\272.html" @@ -99,7 +99,7 @@ -o-filter: grayscale(95%); /* opera */ filter:progid:DXImageTransform.Microsoft.BasicImage(grayscale=.95); }

    实际效果如下:

    202212072135211

    参考资料

    1. Web开发技术/CSS/filter:https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter
    - + \ No newline at end of file diff --git "a/categories/fragments/2023/01/06/CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.html" "b/categories/fragments/2023/01/06/CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.html" index 5cf554f085..fd5aa4bc97 100644 --- "a/categories/fragments/2023/01/06/CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.html" +++ "b/categories/fragments/2023/01/06/CodeReview\346\226\271\346\263\225\350\256\272\344\270\216\345\256\236\350\267\265\346\200\273\347\273\223.html" @@ -15,7 +15,7 @@ - + @@ -58,7 +58,7 @@ 2023年 (3篇)
    目录

    阿里巴巴的Code Review方法论与实践总结

    作为卓越工程文化的一部分,Code Review 其实一直在进行中,只是各团队根据自身情况张驰有度,松紧可能也不一,这里简单梳理一下 CR 的方法和团队实践。

    为什么要CR

    • 提前发现缺陷 在CodeReview阶段发现的逻辑错误、业务理解偏差、性能隐患等时有发生,CR可以提前发现问题。
    • 提高代码质量 主要体现在代码健壮性、设计合理性、代码优雅性等方面,持续CodeReview可以提升团队整体代码质量。
    • 统一规范和风格 集团编码规范自不必说,对于代码风格要不要统一,可能会有不同的看法,个人观点对于风格也不强求。但代码其实不是写给自己看的,是写给下一任看的,就像经常被调侃的“程序员不喜欢写注释,更不喜欢别人不写注释”,代码风格的统一更有助于代码的可读性及继任者的快速上手。
    • 防止架构腐烂 架构的维护者是谁?仅靠架构师或应用Owner是远远不够的,需要所有成员的努力,所谓人人都是架构师。架构防腐最好前置在设计阶段,但CodeReview作为对最终产出代码的检查,也算是最后一道关键工序。
    • 知识分享 每一次CodeReview,都是一次知识的分享,磨合一定时间后,团队成员间会你中有我、我中有你,集百家之所长,融百家之所思。同时,业务逻辑都在代码中,团队CodeReview也是一种新人业务细节学习的途径。
    • 团队共识 通过多次讨论与交流,逐步达成团队共识,特别是对架构理解和设计原则的认知,在共识的基础上团队也会更有凝聚力,特别是在较多新人加入时尤为重要。

    他山之石

    某大厂A

    非常重视 Code Review,基本上代码需要至少有两位以上 Reviewer 审核通过后,才会让你 Check In。

    代码评审规则

    • 如果变更达到可以提升系统整体代码质量的程度,就可以让它们通过,即使它们可能还不完美。这是所有代码评审准则的最高原则。
    • 世界上没有“完美”的代码,只有更好的代码。评审者不应该要求代码提交者在每个细节都写得很完美。评审者应该做好修改时间与修改重要性之间的权衡。

    代码评审原则

    • 以客观的技术因素与数据为准,而非个人偏好。
    • 在代码样式上,遵从代码样式指南,所有代码都应与其保持一致,任何与代码样式指南不一致的观点都是个人偏好。但如果某项代码样式在指南中未提及,那就接受作者的样式。
    • 任务涉及软件设计的问题,都应取决于基本设计原则,而不应由个人喜好来决定。当同时有多种可行方案时,如果作者能证明(以数据或公认的软件工程原理为依据)这些方案基本差不多,那就接受作者的选项;否则,应由标准的软件设计原则为准。
    • 如果没有可用的规则,那么审核者应该让作者与当前代码库保持一致,至少不会恶化代码系统的质量。(一旦恶化代码质量,就会带来破窗效应,导致系统的代码质量逐渐下降)

    代码审核者应该看什么

    • 设计:代码是否设计良好?这种设计是否适合当前系统?
    • 功能:代码实现的行为与作者的期望是否相符?代码实现的交互界面是否对用户友好?
    • 复杂性:代码可以更简单吗?如果将来有其他开发者使用这段代码,他能很快理解吗?
    • 测试:这段代码是否有正确的、设计良好的自动化测试?
    • 命名:在为变量、类名、方法等命名时,开发者使用的名称是否清晰易懂?
    • 注释:所有的注释是否都一目了然?
    • 代码样式:所有的代码是否都遵循代码样式?
    • 文档:开发者是否同时更新了相关文档?

    某大厂B

    • 在开发流程上专门有这个环节,排期会明确排进日程,比如 5 天开发会排 2 天来做代码审核,分为代码自审、交叉审核、集中审核。
    • 有明确的量化指标,如 8 人时审核/每千行代码,8 个以上非提示性有效问题/每千行代码。

    某大厂C

    • 推行 Code Owner 机制,每个代码变更必须有 Code Owner 审核通过才可以提交。
    • 所有的一线工程师,无论职级高低,最重要的工程输出原则是 “show me the code”,而 Code Review 是最能够反应这个客观输出的。
    • 尽量让每个人的 Code Review 参与状况都公开透明,每个变更发送给项目合作者,及转发到小组内成员,小组内任何人都可以去 Review 其他人的代码。
    • 明确每个人的考评和 Code Review 表现相关,包括 Code Review 输出状况及提交代码的质量等。

    我们怎么做CR

    作为代码提交者

    • 发起时机:发起 Code Review 尽量提前,开发过程小步快跑

      202301062024211

    • 代码行数:提交 Code Review 的代码行数最好在 400 行以下。根据数据分析发现,从代码行数来看,超过 400 行的 CR,缺陷发现率会急剧下降;从CR速度来看,超过 500 行/小时后,Review 质量也会大大降低,一个高质量的 CR 最好控制在一个小时以内。

    • 明确意图:编写语义明确的标题(必填)和描述(选填,可以包括背景、思路、改造点和影响面、风险等)

    • 善用工具:IDEA 打开编码规约实时检测,减少代码样式、编码规约等基础性问题 (阿里编码规约插件:https://github.com/alibaba/p3c/tree/master/idea-plugin)

    作为代码评审者

    评审范围

    主要从两方面来评审:

    • 代码逻辑
      • 功能完整:代码实现是否满足功能需求,实现上有没有需求的理解偏差,对用户是否友好;
      • 逻辑设计:是否考虑了全局设计和兼容现有业务细节,是否考虑边界条件和并发控制;
      • 安全隐患:是否存在数据安全隐患及敏感信息泄漏,如越权、SQL 注入、CSRF、敏感信息未脱敏等;
      • 性能隐患:是否存在损害性能的隐患,如死锁、死循环、FullGC、慢 SQL、缓存数据热点等;
      • 测试用例:单元测试用例的验证逻辑是否有效,测试用例的代码行覆盖率和分支覆盖率;
    • 代码质量
      • 编码规范:命名、注释、领域术语、架构分层、日志打印、代码样式等是否符合规范
      • 可读性:是否逻辑清晰、易理解,避免使用奇淫巧技,避免过度拆分
      • 简洁性:是否有重复可简化的复杂逻辑,代码复杂度是否过高,符合 KISS 和 DRY 原则
      • 可维护性:在可读性和简洁性基础上,是否分层清晰、模块化合理、高内聚低耦合、遵从基本设计原则
      • 可扩展性:是否仅仅是满足一次性需求的代码,是否有必要的前瞻性扩展设计
      • 可测试性:代码是否方便写单元测试及分支覆盖,是否便于自动化测试

    评审注意事项

    • 尽快完成评审
    • 避免过度追求完美
    • 明确评论是否要解决
    • 避免使用反问句来评价

    我们主要是通过交叉 CR、集中 CR 相结合的方式,由应用 Owner + SM + 架构师 + TL 完成。

    CR怎么避免流于形式

    CR 流于形式的因素很多,大概如下:

    • 不认同 CodeReview

      • 评审者的姿态?有没有带来好处?有没有从中收获?这些都会直观影响团队成员的认可度
      • 每个 Review 建议的提出都是一次思想交流,评论要友好、中肯、具体,避免教条式及负面词汇,在遵守评审原则下,同时尊重个性展现
      • 团队集中 CodeReview 尽量不要太正式和严肃,轻松的气氛下更有助于互相理解,来点水果,聊聊业务聊聊代码
      • 在 Review 过程有时候会陷入谁对谁错的争论,只要是为了寻求真理辩证的去看问题,哪怕是讨论再激烈也是有收获的,注意只对事不对人。
    • CodeReview 后改动太大

      • 发布前发现问题多,改动太大,影响项目计划
      • 大项目要求编码前设计评审,小需求可以事先 Review 设计思路,避免最后的惊喜
      • 每次 Review 的代码行数最好控制在数百行以内
    • 评审者没有足够时间

      • 评审者在任务安排上尽量预留好时间
      • 尽快评审,代码在百行以内及时响应,在千行以内当日完结
    • 评审者不了解业务和代码

      • 代码提交人编写清晰的标题和描述
      • 有必要的情况下评审者需要了解 PRD
      • 评审者需要提前了解系统和代码
    • Review 建议未修改

      • 这一点极为重要,需要对修改后的代码再次 Review,确保理解一致,以及预防带问题上线
      • 应用可以设置 Review 建议需全部解决的卡点,同时对于非必需修改的建议可以进行打标或说明

    CR实践中发现的几个常见代码问题

    笔者对个人 CR 评论问题做了个大概统计,Bug 发现数占比约 4%(直接或潜在 Bug),重复代码数占比约 5%,其他还有规范、安全、性能、设计等问题。在 CR 代码质量时,可以参考《重构:改善既有代码的设计》,书中所列的 22 种坏味道在 CR 中基本都会遇到。而此处我们主要聚焦以下几个常见问题:

    DRY

    DRY 是 Don't Repeat Yourself 的缩写,DRY 是 Andy Hunt 和 Dave Thomas's 在《 The Pragmatic Programmer 》一书中提出的核心原则。DRY 原则描述的重复是知识和意图的重复,包含代码重复、文档重复、数据重复、表征重复,我们这里重点讲讲代码重复

    代码重复

    《重构》中对 “Duplicated Code(重复代码)” 的描述:

    坏味道行列中首当其冲的就是 Duplicated Code。如果你在一个以上的地点看到相同的程序结构,那么可以肯定:设法将它们合而为一,程序会变得更好。

    最单纯的 Duplicated Code 就是“同一个类的两个函数含有相同的表达式”。这时候你需要做的就是采用 Extract Method (110) 提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。

    另一种常见情况就是“两个互为兄弟的子类内含相同表达式”。要避免这种情况,只需对两个类都使用 Extract Method (110),然后再对被提炼出来的代码使用 Pull Up Method (332),将它推入超类内。如果代码之间只是类似,并非完全相同,那么就得运用 Extract Method (110) 将相似部分和差异部分割开,构成单独一个函数。然后你可能发现可以运用 Form Template Method (345) 获得一个Template Method 设计模式。如果有些函数以不同的算法做相同的事,你可以选择其中较清晰的一个,并使用 Substitute Algorithm (139) 将其他函数的算法替换掉。

    如果两个毫不相关的类出现 Duplicated Code,你应该考虑对其中一个使用 Extract Class (149),将重复代码提炼到一个独立类中,然后在另一个类内使用这个新类。但是,重复代码所在的函数也可能的确只应该属于某个类,另一个类只能调用它,抑或这个函数可能属于第三个类,而另两个类应该引用这第三个类。你必须决定这个函数放在哪儿最合适,并确保它被安置后就不会再在其他任何地方出现。

    代码重复的几种场景:

    • 一个类中重复代码抽象为一个方法
    • 两个子类间重复代码抽象到父类
    • 两个不相关类间重复代码抽象到第三个类
    java
    private BillVO convertBillDTO2BillVO(BillDTO billDTO) {
    +            2019年 (4篇)
    目录

    阿里巴巴的Code Review方法论与实践总结

    作为卓越工程文化的一部分,Code Review 其实一直在进行中,只是各团队根据自身情况张驰有度,松紧可能也不一,这里简单梳理一下 CR 的方法和团队实践。

    为什么要CR

    • 提前发现缺陷 在CodeReview阶段发现的逻辑错误、业务理解偏差、性能隐患等时有发生,CR可以提前发现问题。
    • 提高代码质量 主要体现在代码健壮性、设计合理性、代码优雅性等方面,持续CodeReview可以提升团队整体代码质量。
    • 统一规范和风格 集团编码规范自不必说,对于代码风格要不要统一,可能会有不同的看法,个人观点对于风格也不强求。但代码其实不是写给自己看的,是写给下一任看的,就像经常被调侃的“程序员不喜欢写注释,更不喜欢别人不写注释”,代码风格的统一更有助于代码的可读性及继任者的快速上手。
    • 防止架构腐烂 架构的维护者是谁?仅靠架构师或应用Owner是远远不够的,需要所有成员的努力,所谓人人都是架构师。架构防腐最好前置在设计阶段,但CodeReview作为对最终产出代码的检查,也算是最后一道关键工序。
    • 知识分享 每一次CodeReview,都是一次知识的分享,磨合一定时间后,团队成员间会你中有我、我中有你,集百家之所长,融百家之所思。同时,业务逻辑都在代码中,团队CodeReview也是一种新人业务细节学习的途径。
    • 团队共识 通过多次讨论与交流,逐步达成团队共识,特别是对架构理解和设计原则的认知,在共识的基础上团队也会更有凝聚力,特别是在较多新人加入时尤为重要。

    他山之石

    某大厂A

    非常重视 Code Review,基本上代码需要至少有两位以上 Reviewer 审核通过后,才会让你 Check In。

    代码评审规则

    • 如果变更达到可以提升系统整体代码质量的程度,就可以让它们通过,即使它们可能还不完美。这是所有代码评审准则的最高原则。
    • 世界上没有“完美”的代码,只有更好的代码。评审者不应该要求代码提交者在每个细节都写得很完美。评审者应该做好修改时间与修改重要性之间的权衡。

    代码评审原则

    • 以客观的技术因素与数据为准,而非个人偏好。
    • 在代码样式上,遵从代码样式指南,所有代码都应与其保持一致,任何与代码样式指南不一致的观点都是个人偏好。但如果某项代码样式在指南中未提及,那就接受作者的样式。
    • 任务涉及软件设计的问题,都应取决于基本设计原则,而不应由个人喜好来决定。当同时有多种可行方案时,如果作者能证明(以数据或公认的软件工程原理为依据)这些方案基本差不多,那就接受作者的选项;否则,应由标准的软件设计原则为准。
    • 如果没有可用的规则,那么审核者应该让作者与当前代码库保持一致,至少不会恶化代码系统的质量。(一旦恶化代码质量,就会带来破窗效应,导致系统的代码质量逐渐下降)

    代码审核者应该看什么

    • 设计:代码是否设计良好?这种设计是否适合当前系统?
    • 功能:代码实现的行为与作者的期望是否相符?代码实现的交互界面是否对用户友好?
    • 复杂性:代码可以更简单吗?如果将来有其他开发者使用这段代码,他能很快理解吗?
    • 测试:这段代码是否有正确的、设计良好的自动化测试?
    • 命名:在为变量、类名、方法等命名时,开发者使用的名称是否清晰易懂?
    • 注释:所有的注释是否都一目了然?
    • 代码样式:所有的代码是否都遵循代码样式?
    • 文档:开发者是否同时更新了相关文档?

    某大厂B

    • 在开发流程上专门有这个环节,排期会明确排进日程,比如 5 天开发会排 2 天来做代码审核,分为代码自审、交叉审核、集中审核。
    • 有明确的量化指标,如 8 人时审核/每千行代码,8 个以上非提示性有效问题/每千行代码。

    某大厂C

    • 推行 Code Owner 机制,每个代码变更必须有 Code Owner 审核通过才可以提交。
    • 所有的一线工程师,无论职级高低,最重要的工程输出原则是 “show me the code”,而 Code Review 是最能够反应这个客观输出的。
    • 尽量让每个人的 Code Review 参与状况都公开透明,每个变更发送给项目合作者,及转发到小组内成员,小组内任何人都可以去 Review 其他人的代码。
    • 明确每个人的考评和 Code Review 表现相关,包括 Code Review 输出状况及提交代码的质量等。

    我们怎么做CR

    作为代码提交者

    • 发起时机:发起 Code Review 尽量提前,开发过程小步快跑

      202301062024211

    • 代码行数:提交 Code Review 的代码行数最好在 400 行以下。根据数据分析发现,从代码行数来看,超过 400 行的 CR,缺陷发现率会急剧下降;从CR速度来看,超过 500 行/小时后,Review 质量也会大大降低,一个高质量的 CR 最好控制在一个小时以内。

    • 明确意图:编写语义明确的标题(必填)和描述(选填,可以包括背景、思路、改造点和影响面、风险等)

    • 善用工具:IDEA 打开编码规约实时检测,减少代码样式、编码规约等基础性问题 (阿里编码规约插件:https://github.com/alibaba/p3c/tree/master/idea-plugin)

    作为代码评审者

    评审范围

    主要从两方面来评审:

    • 代码逻辑
      • 功能完整:代码实现是否满足功能需求,实现上有没有需求的理解偏差,对用户是否友好;
      • 逻辑设计:是否考虑了全局设计和兼容现有业务细节,是否考虑边界条件和并发控制;
      • 安全隐患:是否存在数据安全隐患及敏感信息泄漏,如越权、SQL 注入、CSRF、敏感信息未脱敏等;
      • 性能隐患:是否存在损害性能的隐患,如死锁、死循环、FullGC、慢 SQL、缓存数据热点等;
      • 测试用例:单元测试用例的验证逻辑是否有效,测试用例的代码行覆盖率和分支覆盖率;
    • 代码质量
      • 编码规范:命名、注释、领域术语、架构分层、日志打印、代码样式等是否符合规范
      • 可读性:是否逻辑清晰、易理解,避免使用奇淫巧技,避免过度拆分
      • 简洁性:是否有重复可简化的复杂逻辑,代码复杂度是否过高,符合 KISS 和 DRY 原则
      • 可维护性:在可读性和简洁性基础上,是否分层清晰、模块化合理、高内聚低耦合、遵从基本设计原则
      • 可扩展性:是否仅仅是满足一次性需求的代码,是否有必要的前瞻性扩展设计
      • 可测试性:代码是否方便写单元测试及分支覆盖,是否便于自动化测试

    评审注意事项

    • 尽快完成评审
    • 避免过度追求完美
    • 明确评论是否要解决
    • 避免使用反问句来评价

    我们主要是通过交叉 CR、集中 CR 相结合的方式,由应用 Owner + SM + 架构师 + TL 完成。

    CR怎么避免流于形式

    CR 流于形式的因素很多,大概如下:

    • 不认同 CodeReview

      • 评审者的姿态?有没有带来好处?有没有从中收获?这些都会直观影响团队成员的认可度
      • 每个 Review 建议的提出都是一次思想交流,评论要友好、中肯、具体,避免教条式及负面词汇,在遵守评审原则下,同时尊重个性展现
      • 团队集中 CodeReview 尽量不要太正式和严肃,轻松的气氛下更有助于互相理解,来点水果,聊聊业务聊聊代码
      • 在 Review 过程有时候会陷入谁对谁错的争论,只要是为了寻求真理辩证的去看问题,哪怕是讨论再激烈也是有收获的,注意只对事不对人。
    • CodeReview 后改动太大

      • 发布前发现问题多,改动太大,影响项目计划
      • 大项目要求编码前设计评审,小需求可以事先 Review 设计思路,避免最后的惊喜
      • 每次 Review 的代码行数最好控制在数百行以内
    • 评审者没有足够时间

      • 评审者在任务安排上尽量预留好时间
      • 尽快评审,代码在百行以内及时响应,在千行以内当日完结
    • 评审者不了解业务和代码

      • 代码提交人编写清晰的标题和描述
      • 有必要的情况下评审者需要了解 PRD
      • 评审者需要提前了解系统和代码
    • Review 建议未修改

      • 这一点极为重要,需要对修改后的代码再次 Review,确保理解一致,以及预防带问题上线
      • 应用可以设置 Review 建议需全部解决的卡点,同时对于非必需修改的建议可以进行打标或说明

    CR实践中发现的几个常见代码问题

    笔者对个人 CR 评论问题做了个大概统计,Bug 发现数占比约 4%(直接或潜在 Bug),重复代码数占比约 5%,其他还有规范、安全、性能、设计等问题。在 CR 代码质量时,可以参考《重构:改善既有代码的设计》,书中所列的 22 种坏味道在 CR 中基本都会遇到。而此处我们主要聚焦以下几个常见问题:

    DRY

    DRY 是 Don't Repeat Yourself 的缩写,DRY 是 Andy Hunt 和 Dave Thomas's 在《 The Pragmatic Programmer 》一书中提出的核心原则。DRY 原则描述的重复是知识和意图的重复,包含代码重复、文档重复、数据重复、表征重复,我们这里重点讲讲代码重复

    代码重复

    《重构》中对 “Duplicated Code(重复代码)” 的描述:

    坏味道行列中首当其冲的就是 Duplicated Code。如果你在一个以上的地点看到相同的程序结构,那么可以肯定:设法将它们合而为一,程序会变得更好。

    最单纯的 Duplicated Code 就是“同一个类的两个函数含有相同的表达式”。这时候你需要做的就是采用 Extract Method (110) 提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。

    另一种常见情况就是“两个互为兄弟的子类内含相同表达式”。要避免这种情况,只需对两个类都使用 Extract Method (110),然后再对被提炼出来的代码使用 Pull Up Method (332),将它推入超类内。如果代码之间只是类似,并非完全相同,那么就得运用 Extract Method (110) 将相似部分和差异部分割开,构成单独一个函数。然后你可能发现可以运用 Form Template Method (345) 获得一个Template Method 设计模式。如果有些函数以不同的算法做相同的事,你可以选择其中较清晰的一个,并使用 Substitute Algorithm (139) 将其他函数的算法替换掉。

    如果两个毫不相关的类出现 Duplicated Code,你应该考虑对其中一个使用 Extract Class (149),将重复代码提炼到一个独立类中,然后在另一个类内使用这个新类。但是,重复代码所在的函数也可能的确只应该属于某个类,另一个类只能调用它,抑或这个函数可能属于第三个类,而另两个类应该引用这第三个类。你必须决定这个函数放在哪儿最合适,并确保它被安置后就不会再在其他任何地方出现。

    代码重复的几种场景:

    • 一个类中重复代码抽象为一个方法
    • 两个子类间重复代码抽象到父类
    • 两个不相关类间重复代码抽象到第三个类
    java
    private BillVO convertBillDTO2BillVO(BillDTO billDTO) {
         if (billDTO == null) {
             return null;
         }
    @@ -130,7 +130,7 @@
             return StringUtils.EMPTY;
         }
         return String.format(MONEY_DISPLAY_TEXT_PATTERN, money.getCurrency(), money.getAmount().toPlainString());
    -}

    DYR实践忠告

    • 不要借用 DRY 之名,过度提前抽象,请遵循 Rule of three 原则
    • 不要过度追求 DRY,破坏了内聚性,实践中需要平衡复用与内聚

    Primitive Obsession

    《重构》中对 “Primitive Obsession(基本类型偏执)” 的描述:

    大多数编程环境都有两种数据:结构类型允许你将数据组织成有意义的形式;基本类型则是构成结构类型的积木块。结构总是会带来一定的额外开销。它们可能代表着数据库中的表,如果只为做一两件事而创建结构类型也可能显得太麻烦。

    对象的一个极大的价值在于:它们模糊(甚至打破)了横亘于基本数据和体积较大的类之间的界限。你可以轻松编写出一些与语言内置(基本)类型无异的小型类。例如,Java 就以基本类型表示数值,而以类表示字符串和日期——这两个类型在其他许多编程环境中都以基本类型表现。

    对象技术的新手通常不愿意在小任务上运用小对象——像是结合数值和币种的 money 类、由一个起始值和一个结束值组成的 range 类、电话号码或邮政编码(ZIP)等的特殊字符串。你可以运用 Replace Data Valuewith Object (175) 将原本单独存在的数据值替换为对象,从而走出传统的洞窟,进入炙手可热的对象世界。如果想要替换的数据值是类型码,而它并不影响行为,则可以运用 Replace Type Code with Class (218) 将它换掉。如果你有与类型码相关的条件表达式,可运用 Replace Type Codewith Subclass (213) 或 Replace Type Code with State/Strategy (227) 加以处理。

    如果你有一组应该总是被放在一起的字段,可运用 Extract Class(149)。如果你在参数列中看到基本型数据,不妨试试 IntroduceParameter Object (295)。如果你发现自己正从数组中挑选数据,可运用 Replace Array with Object (186)。

    给我们的启示主要有两点:

    • 大部分业务场景和语言环境下,结构化类型导致的开销基本可以忽略
    • 结构化类型带来更清晰的语义和复用
    java
    @Data
    +}

    DYR实践忠告

    • 不要借用 DRY 之名,过度提前抽象,请遵循 Rule of three 原则
    • 不要过度追求 DRY,破坏了内聚性,实践中需要平衡复用与内聚

    Primitive Obsession

    《重构》中对 “Primitive Obsession(基本类型偏执)” 的描述:

    大多数编程环境都有两种数据:结构类型允许你将数据组织成有意义的形式;基本类型则是构成结构类型的积木块。结构总是会带来一定的额外开销。它们可能代表着数据库中的表,如果只为做一两件事而创建结构类型也可能显得太麻烦。

    对象的一个极大的价值在于:它们模糊(甚至打破)了横亘于基本数据和体积较大的类之间的界限。你可以轻松编写出一些与语言内置(基本)类型无异的小型类。例如,Java 就以基本类型表示数值,而以类表示字符串和日期——这两个类型在其他许多编程环境中都以基本类型表现。

    对象技术的新手通常不愿意在小任务上运用小对象——像是结合数值和币种的 money 类、由一个起始值和一个结束值组成的 range 类、电话号码或邮政编码(ZIP)等的特殊字符串。你可以运用 Replace Data Valuewith Object (175) 将原本单独存在的数据值替换为对象,从而走出传统的洞窟,进入炙手可热的对象世界。如果想要替换的数据值是类型码,而它并不影响行为,则可以运用 Replace Type Code with Class (218) 将它换掉。如果你有与类型码相关的条件表达式,可运用 Replace Type Codewith Subclass (213) 或 Replace Type Code with State/Strategy (227) 加以处理。

    如果你有一组应该总是被放在一起的字段,可运用 Extract Class(149)。如果你在参数列中看到基本型数据,不妨试试 IntroduceParameter Object (295)。如果你发现自己正从数组中挑选数据,可运用 Replace Array with Object (186)。

    给我们的启示主要有两点:

    • 大部分业务场景和语言环境下,结构化类型导致的开销基本可以忽略
    • 结构化类型带来更清晰的语义和复用
    java
    @Data
     public class XxxConfigDTO implements Serializable {
     
         private static final long serialVersionUID = 8018480763009740953L;
    @@ -430,7 +430,7 @@
         } finally {
             lockService.unlock(LockBizType.PRODUCT, orderId);
         }
    -}

    注意加锁类型与加锁 KEY 在同一个维度,否则加锁会失效。

    分页查询

    完全没有分页

    java
    private List<OrderDTO> queryOrderList(Long customerId) {
    +}

    注意加锁类型与加锁 KEY 在同一个维度,否则加锁会失效。

    分页查询

    完全没有分页

    java
    private List<OrderDTO> queryOrderList(Long customerId) {
         if (customerId == null) {
             return Lists.newArrayList();
         }
    @@ -468,7 +468,7 @@
         List<OrderDO> orderDOList = orderMapper.list(query);
         List<OrderDTO> orderDTOList = orderConverter.doList2dtoList(orderDOList);
         return PageQueryUtil.buildPageData(query, orderDTOList, cnt);
    -}

    没有分页的列表查询对DB性能影响非常大,特别是在项目初期,因为数据量非常小问题不明显,而导致没有及时发现,会给未来留坑。

    分页size太大

    java
    private Page<OrderDTO> queryOrderList2(OrderPageQuery query) {
    +}

    没有分页的列表查询对DB性能影响非常大,特别是在项目初期,因为数据量非常小问题不明显,而导致没有及时发现,会给未来留坑。

    分页size太大

    java
    private Page<OrderDTO> queryOrderList2(OrderPageQuery query) {
         Preconditions.checkNotNull(query, "查询条件不能为空");
         query.setPageSize(10000);
         long cnt = orderMapper.count(query);
    @@ -488,7 +488,7 @@
         List<OrderDO> orderDOList = orderMapper.list(query);
         List<OrderDTO> orderDTOList = orderConverter.doList2dtoList(orderDOList);
         return PageQueryUtil.buildPageData(query, orderDTOList, cnt);
    -}

    分页 size 的大小并没有一个固定的标准,取决于业务需求、数据量及数据库等,但动辄几千上万的分页 size,会带来性能瓶颈,而大量的慢 SQL 不但影响客户体验,对系统稳定性也是极大的隐患。

    超多分页慢SQL

    xml
    <!-- 分页查询订单列表 -->
    +}

    分页 size 的大小并没有一个固定的标准,取决于业务需求、数据量及数据库等,但动辄几千上万的分页 size,会带来性能瓶颈,而大量的慢 SQL 不但影响客户体验,对系统稳定性也是极大的隐患。

    超多分页慢SQL

    xml
    <!-- 分页查询订单列表 -->
     <select id="list" parameterType="com.xxx.OrderPageQuery" resultType="com.xxx.OrderDO">
         SELECT
             <include refid="all_columns"/>
    @@ -529,7 +529,7 @@
             LIMIT #{offset},#{pageSize}
         ) b ON a.id = b.bid
     </select>

    以上 bad case 的 SQL 在超多页分页查询时性能极其低下,存在多次回表甚至 Using Filesort 的问题,在阿里巴巴编码规范中也有明确的规避方案,此处不展开。

    202301062027985

    最后,我们工程师的智慧结晶都尽在代码之中,而 Code Review 可以促进结晶更加清莹通透、纯洁无瑕、精致完美,值得大家一起持续精进!

    - + \ No newline at end of file diff --git "a/categories/fragments/2023/05/06/\350\275\257\344\273\266\350\256\276\350\256\241\345\270\210\347\237\245\350\257\206\347\202\271\351\200\237\350\256\260.html" "b/categories/fragments/2023/05/06/\350\275\257\344\273\266\350\256\276\350\256\241\345\270\210\347\237\245\350\257\206\347\202\271\351\200\237\350\256\260.html" index 98ac447572..c740dfe455 100644 --- "a/categories/fragments/2023/05/06/\350\275\257\344\273\266\350\256\276\350\256\241\345\270\210\347\237\245\350\257\206\347\202\271\351\200\237\350\256\260.html" +++ "b/categories/fragments/2023/05/06/\350\275\257\344\273\266\350\256\276\350\256\241\345\270\210\347\237\245\350\257\206\347\202\271\351\200\237\350\256\260.html" @@ -95,7 +95,7 @@ 11111111 (补码) -------- 100000001 (反码) -> 最高位 1 溢出,则结果为 00000001

    所以,在计算机系统中,数据分为原码反码和补码三种,并且,不论整数或负数,一律用补码来表示和存储。而且,由于反码、补码都是为了解决负数的问题而产生的变化,正数则没有变化,这也是为什么正数的反码、补码和原码一致的原因了。

    数值范围

    码制定点整数定点小数
    原码(2n11)+(2n11)(12(n1))+(12(n1))
    反码(2n11)+(2n11)(12(n1))+(12(n1))
    补码2n1+(2n11)1+(12(n1))
    移码2n1+(2n11)1+(12(n1))

    在一个字长为 n (例如:字长为 8)的计算机中,原码可以表示的整数数值范围为:11111111 ~ 01111111,将除符号位之外的数值位转换为十进制:

    1111111B=126+125+124+123+122+121+120=127D

    即 8 位原码可表示的数值范围为:-127 ~ +127,推导得出 n 位原码可表示的数值范围是:

    (2n11)+(2n11)

    原码和反码之所以负数要 - 1 主要是存在 -0 的表示,而补码、移码则不存在 -0,所以也不需要 - 1了。

    TIP

    其中 -128 的补码为 1000 0000,-1 的补码是 1000 0000 是人为规定的。

    浮点运算

    浮点数表示:

    N=

    运算过程:

    >>

    特点:

    1. 一般尾数用补码,阶码用移码
    2. 阶码的位数决定数的表示范围,位数越多范围越大
    3. 尾数的位数决定数的有效精度,位数越多精度越高
    4. 对阶时,小数向大数看齐
    5. 对接是通过较小数的尾数右移实现的

    计算机结构

    运算器:

    1. 算术逻辑单元 ALU:数据的算术运算和逻辑运算

    2. 累加寄存器 AC:通用寄存器,为 ALU 提供一个工作区,用在暂存数据

    3. 数据缓冲寄存器 DR:写内存时,暂存指令或数据

    4. 状态条件寄存器 PSW:存状态标志与控制标志

      (争议:也有将其归为控制器的)

    控制器:

    1. 程序计数器 PC:存储下一条要执行指令的地址
    2. 指令寄存器 IR:存储即将执行的指令
    3. 指令译码器 ID:对指令中的操作码字段进行分析解释
    4. 时序部件:提供时序控制信号

    计算机体系结构分类-Flynn

    体系结构类型结构关键特性代表
    单指令流单数据流
    SISD
    控制部分:一个
    处理器:一个
    主存模块:一个
    单处理器系统
    单指令流多数据流
    SIMD
    控制部分:一个
    处理器:多个
    主存模块:多个
    各处理器以异步的形式执行同一条指令并行处理机
    阵列处理机
    超级向量处理机
    多指令流单数据流
    MISD
    控制部分:多个
    处理器:一个
    主存模块:多个
    被证明不可能,至少是不实际目前没有,有文献称流水线计算机为此类
    多指令流多数据流
    MIMD
    控制部分:多个
    处理器:多个
    主存模块:多个
    能够实现作业、任务、指令等各级全面并行多处理机系统
    多计算机

    指令的基本概念

    一条指令就是机器语言的一个语句,它是一组有意义的二进制代码,指令的基本格式为:操作码字段|地址码字段。

    操作码部分指出了计算机要执行什么性质的操作,如加法、减法、取数、存数等。地址码字段需要包含各操作数的地址及操作结果的存放地址等,从其地址结构的角度可以分为三地址指令、二地址指令、一地址指令和零地址指令。

    寻址方式

    • 立即寻址方式:操作数直接在指令中,速度快,灵活性差
    • 直接寻址方式:指令中存放的是操作数的地址
    • 间接寻址方式:指令中存放了一个地址,这个地址对应的内容是操作数的地址
    • 寄存器寻址方式:寄存器存放操作数
    • 寄存器间接寻址方式:寄存器内存放的是操作数的地址

    CISC 和 RISC

    指令系统类型指令寻址方式实现方式其他
    CISC(复杂指令集)数量多,使用频率差别大,可变长格式支持多种微程序控制技术(微码)研制周期长,编译子程序库小
    RISC(精简指令集)数量少,使用频率接近,定长格式,大部分为单周期指令,操作寄存器,只有 Load/Store 操作内存。支持方式少增加了通用寄存器,硬布线逻辑控制为主,适合采用流水线优化编译,有效支持高级程序语言,编译子程序库大

    流水线技术

    流水线是指在程序执行时多条指令重叠进行操作的一种准并行处理实现技术。各种部件同时处理时针对不同指令而言的,它们可同时分为多条指令的不同部分进行工作,以提高各部件的利用率和指令的平均速度。

    相关参数计算:

    • 流水线建立时间:第 1 条指令执行时间
    • 流水线周期:指令分段后,最长段时间
    • 流水线执行时间(默认使用理论公式,无答案时考虑实践公式)
      • 理论公式:流水线建立时间+(指令条数-1)x流水线周期
      • 实践公式:指令段数x流水线周期+(指令条数-1)x流水线周期
    • 流水线吞吐率=指令条数/流水线执行时间
    • 流水线最大吞吐率=流水线周期的倒数

    吞吐率和建立时间是使用流水线技术的两个重要指标。吞吐率是指单位时间里流水线处理机流出的结果数。

    对指令而言,就是单位时间里执行的指令数。流水线开始工作时,需经过一定时间才能达到最大吞吐率,这就是建立时间。若 m 个子过程所用时间一样,均为 Δ0 ,则建立时间 T0=mΔ0

    2009上半年:某指令流水线由 5 段组成,第 1、3、5 段所需时间为 Δt,第 2、4 段所需时间分别为 3Δt2Δt,如下图所示,那么连续输入 n 条指令时的吞吐率(单位时间内执行的指令个数)TP 为______。

    答: 连续输入 n 条指令时,第 1 条指令需要的时间为 (1+3+1+2+1)Δt,之后,每隔 3Δt 便完成 1 条指令,即流水线一旦建立好,其吞吐率为最长子过程所需时间的倒数。综合 n 条指令的时间为 (1+3+1+2+1)Δt+(n1)×3Δt,因此吞吐率为:

    n(3+3+2)Δt+3(n1)Δt

    层次化存储结构

    • CPU:寄存器,最快,但容量小,成本高
    • Cache:按内容存取
    • 内存(主存):
      • 随机存储器(RAM)
      • 只读存储器(ROM)
    • 外存(辅存):硬盘、光盘、U盘等

    局部性原理是层次化存储结构的支撑。

    Cache

    概念

    在计算机的存储系统体系中,Cache 是访问速度最快的层次(若有寄存器,则寄存器最快)。

    使用 Cache 改善系统性能的依据是程序的局部性原理。

    如果以 h 代表对 Cache 的访问命中率,t1 表示 Cache 的周期时间,t2 表示主存储器周期时间,以读操作为例,使用 “Cache + 主存储器” 的系统的平均周期为 t3,则:

    t3=ht1+(1h)t2

    其中,(1 - h)又称为失效率(未命中率)。

    映像

    • 直接相联映像:硬件电路较简单,但冲突率很高

    • 全相联映像:电路难于设计和实现,只适用于小容量的 Cache,冲突率较低

    • 组相联映像:直接相联与全相联的折中

    • 地址映像是将主存与 Cache 的存储空间划分为若干大小相同的页(或称为块)。

      例如:某机的主存容量为 1GB,划分为 2048 页,每页 512KB;Cache 容量为 8MB,划分为 16 页,每页 512KB。

    主存-编址与计算

    • 存储单元
    • 按字编址:存储体的存储单元是字存储单元,即最小寻址单位是一个字
    • 按字节编址:存储体的存储单元是字节存储单元;即最小寻址单位是一个字节。

    根据存储器所要求的的容量和选定的存储芯片的容量,就可以计算出所需芯片的总数,即:

    =/

    例如:若内存地址区间为 4000H ~ 43FFH,每个存储单元可存储 16 位二进制数,该内存区域用 4 片存储器芯片构成,则构成该内存所用的存储器芯片的容量是多少?

    解析: 在计算机中,内存通常是按照地址来寻址的,每个地址对应着一个存储单元。在这个问题中,给定了内存地址区间为 4000H ~ 43FFH,其中 H 表示十六进制数。

    在十六进制数中,一个数字可以表示 4 位二进制数。因此,4000H 表示的二进制数为 0100000000000000,43FFH 表示的二进制数为 0100001111111111。这两个二进制数之间的差值为 1111111111,即 2 的 10 次方减 1,也就是 1023。

    因此,该内存地址区间中共有 1024 个存储单元(地址数量),分别对应着 4000H ~ 43FFH 中的每个地址。每个存储单元可以存储一个 16 位二进制数,因此该内存区域的总容量为 1024 x 16 = 16384 位二进制数。

    由于该内存所用的存储器芯片数量为 4 片,因此每片存储器芯片的容量为 16384 / 4 = 4096 位二进制数,即 4KB。

    总线

    一条总线同一时刻仅允许一个设备发送,但允许多个设备接收。

    总线的分类:

    • 数据总线(Data Bus):在 CPU 与 RAM 之间来回传送需要处理或是需要储存的数据。
    • 地址总线(Address Bus):用来指定在 RAM(Random Access Memory)之中储存的数据的地址。
    • 控制总线(Control Bus):将微处理器控制单元(Control Unit)的信号,传送到周边设备,一般常见的为 USB Bus 和 1394 Bus。

    串联系统与并联系统

    串联系统可靠度:

    R=R1R2...Rn

    并联系统可靠度:

    R=1(1R1)(1R2)...(1Rn)

    附录

    考试题型

    软考中级软件设计师考试科目分为上午《基础知识》与下午《应用技术》两门,考试题型也各不相同。

    上午《基础知识》考试题型均为 单选题(客观题) ,一共 75 道题,总分 75 分,考试采取笔试的方式进行作答,考生在答题卡上进行填涂,考试时长共 150 分钟,其中 71~75 题为英语题。

    下午《应用技术》考试题型为 主观题,涵盖题型较广,包括填空题、问答题、算法设计图补充等 多种题型。其中包括四道必做题与二道选答题,选答题一般是在 C++ 和 Java 中二选一,前面四道大题一般是结构化分析设计、数据库分析设计、面向对象分析与设计和常用数据结构和算法的掌握情况。考试时长同样为 150 分钟,总分 75 分[2]

    以下是详细介绍:

    试题1:结构化分析与设计;

    主要考查数据流图 DFD 的绘制,考查形式为顶层数据流图和 0 层数据流图填空(外部实体/数据源、数据存储/文件、加工处理/数据变换填空)、找出遗漏的数据流或者错误的数据流,偶尔考查数据流图的一些绘制要点,如分层数据流绘制需要注意的问题等。

    试题2:数据库分析与设计;

    主要考查 ER 图的绘制以及 ER 图与关系模式的映射,考查形式为补充完成 ER 图(增加实体、联系、属性以及联系类型)、将关系模式补充完整(属性/字段填空)、找出关系模式的主外键,偶尔考查关系模式的规范化,如将某个关系模式转换为第三范式等。

    试题3:面向对象分析与设计;

    主要考查对常用 UML 图形的掌握情况,比较常见的图形包括用例图、类图、顺序图、活动图和状态图,考查形式为图形填空,附带考查 UML 的一些基础知识,例如类图中的几种关系及其区别、用例图中用例之间关系的内涵等;

    试题4:数据结构与算法

    主要考查对常用数据结构和算法的掌握情况,通常考查一些中等难度的算法,例如最短装配时间算法、B树、贪心算法、动态规划、回溯法、背包问题、最短路径、拓扑排序、堆排序等,考查形式为C语言代码填空、复杂度分析(时空复杂度)、算法稳定性分析等。

    试题5、6(二选一):C++ 和 Java

    主要考查对常见设计模式的掌握情况,该试题通常会结合一个设计模式实例,给出实例描述和类图,然后进行程序代码填空,有C++和Java两个语言版本,二选一[3]

    参考资料


    1. 转载的关于 二级制的反码,补码,原码等,筛选过的.:https://blog.csdn.net/tunln5288/article/details/97562905 ↩︎

    2. 信管网-中级软件设计师考试题目类型有哪些?:https://www.cnitpm.com/pm1/108810.html ↩︎

    3. 信管网-软件设计师下午题型有哪些?:https://www.cnitpm.com/pm1/109480.html ↩︎

    - + \ No newline at end of file diff --git "a/categories/fragments/2023/05/08/\350\275\257\344\273\266\350\256\276\350\256\241\345\270\210\347\237\245\350\257\206\347\202\271\351\200\237\350\256\2603.html" "b/categories/fragments/2023/05/08/\350\275\257\344\273\266\350\256\276\350\256\241\345\270\210\347\237\245\350\257\206\347\202\271\351\200\237\350\256\2603.html" index b584260cf1..cd67c8f84c 100644 --- "a/categories/fragments/2023/05/08/\350\275\257\344\273\266\350\256\276\350\256\241\345\270\210\347\237\245\350\257\206\347\202\271\351\200\237\350\256\2603.html" +++ "b/categories/fragments/2023/05/08/\350\275\257\344\273\266\350\256\276\350\256\241\345\270\210\347\237\245\350\257\206\347\202\271\351\200\237\350\256\2603.html" @@ -59,7 +59,7 @@ 2022年 (15篇)
    目录

    软件设计师知识点速记:标准与知识产权

    保护范围与对象

    法律法规名称保护对象及范围注意事项
    著作权法(版权)著作权(文学、绘画、摄影等作品)1、不需要申请 ,作品完成即开始保护;
    2、绘画或摄影作品原件出售(赠予)著作权还归原作者,原件拥有者有:所有权、展览权。
    软件著作权法
    计算机软件保护条例
    软件著作权
    软件作品
    1、不需要申请 ,作品完成即开始保护;
    2、登记制度便于举证。
    专利法专利权需要申请,专利权有效期是从申请日开始计算。
    商标法商标权需要申请,核准之日起商标受保护。
    反不正当竞争法商业秘密权1、商业秘密包括技术与经营两个方面;
    2、必须有保密措施才能认定商业秘密。

    TIP

    • 著作权是提高对作品,包括文学、艺术、自然科学、社会科学和工程技术领域内具有独创性并能以某种有形形式复制的智力成果。
    • 专利权保护的是具有创造性的发明及设计等成果。
    • 商业秘密权用来保护商家的秘密(例如:软件的技术信息及经营信息属于商业秘密范畴)。
    • 计算机软件既是作品,又是一种使用工具,还是一种工业产品(商品),具备作品性、工具性、商业性特征。因此对于计算机软件保护来说,仅依靠某项法律或法规不能解决软件的所有知识产权问题, 需要利用多层次的法律保护体系对计算机软件实施保护。我国已形成了比较完备的计算机软件知识产权保护的法律体系,即已形成以著作权法、计算机软件保护条例、计算机软件著作权登记办法保护为主,以专利法、反不正当竞争法、合同法、商标法、刑法等法律法规为辅的多层次保护体系,可对计算机软件实施交叉和重叠保护。在这样的保护体系下,计算机软件能够得到全面的、适度的保护。例如,计算机软件符合专利法所保 护的法定主题,就可以申请专利,利用专利法来保护其中符合发明创造条件的创造性成 果。对于那些为极少数专门用户开发的专用软件,可以利用反不正当竞争法中的商业秘密权和合同法来保护其中的技术秘密。我国没有专门针对知识产权制定统一的法律(知识产权法),而是在民法通则规定的原则下,根据知识产权的不同类型制定了不同的单项法律及法规,如著作权法、商标法、专利法、计算机软件保护条例等,这些法律、法规共同构成了我国保护知识产权的法律体系。

    保护期限

    客体类型权力类型保护期限
    公民作品署名权、修改权、保护作品完整权没有限制
    发表权、使用权和获得报酬权作者终生及其死亡后的50年(第50年的12月31日)
    单位作品发表权、使用权和获得报酬权50年(首次发表后的第50年的12月31日),若期间未发表,不保护
    公民软件产品署名权、修改权没有限制
    发表权、复制权、发行权、出租权、信息网络传播权、翻译权、使用许可权、获得报酬权、转让权作者终生及其死亡后的50年(第50年的12月31日)。合作开发,以最后死亡作者为准。
    单位软件产品发表权、复制权、发行权、出租权、信息网络传播权、翻译权、使用许可权、获得报酬权、转让权50年(首次发表后的第50年的12月31日),若期间未发表,不保护
    注册商标有效期10年(若注册人死亡或倒闭1年后,未转移则可注销,期满前6个月内必须续住)
    发明专利权保护期为20年(从申请日开始)
    实用新型和外观设计专利权保护期为10年(从申请日开始)
    商业秘密不确定(相当于无期限限制),公开后公众可用

    TIP

    • 保护期限不受限制的有:署名权、修改权、保护作品完整权。保护期限为作者终身及死后50年的,包括:发表权、使用权和获得报酬权。
    • 根据《中华人民共和国商标法》第三十八条:注册商标有效期满,需要继续使用的,应当在期满前六个月内申请续展注册。专利权和著作权到期后都无法延长,商业秘密权无期限限制。
    • 根据我国法律法规的规定,烟草类商品必须使用注册商标。
    • 根据《中华人民共和国商标法》第十条:下列标志不得作为商标使用,县级以上行政区划的地名或者公众知晓的外国地名,不得作为商标。但是,地名具有其他含义或者作为集体商标、证明商标组成部分的除外;已经注册的使用地名的商标继续有权。

    知识产权人确定

    情况说明判断说明归属
    作品(职务作品)利用单位的物质技术条件进行创作,并由单位承担责任的除署名权外其他著作权归单位
    有合同约定,其著作权属于单位除署名权外其他著作权归单位
    其他作者拥有著作权,单位有权在业务范围内优先使用
    软件(职务作品)属于本职工作中明确规定的开发目标单位享有著作权
    属于从事本职工作活动的结果单位享有著作权
    使用了单位资金、专用设备、未公开的信息等物质、技术条件,并由单位或组织承担责任的软件单位享有著作权
    专利权(职务作品)本职工作中作出的发明创造单位享有专利
    履行本单位交付的本职工作之外的任务所作出的的发明创造单位享有专利
    离职、退休或调动工作后1年内,与原单位工作相关单位享有专利
    作品软件(委托创作)有合同约定,著作权归委托方委托方
    合同中未约定著作权归属创作方
    作品软件(合作开发)只进行组织,提供咨询意见、物质条件或者进行其他辅助工作不享有著作权
    共同创作的共同享有,按人头比例。成果可分割的,可分开申请。
    商标在同一种商品或者类似商品上谁先申请谁拥有(除知名商标的非法抢注)
    同时申请(同日申请),则根据谁先使用(需提供证据)
    无法提供证据,协商归属,无效时使用抽签(但不可不确定)
    专利谁先申请谁拥有
    同时申请(同日申请),则协商归属

    TIP

    • 根据“同一的发明创造只能被授予一项专利”的规定,在同一天,两个不同的人就同样的发明创造申请专利的,专利局将分别向各申请人通报有关情况,请他们自己去协商解决这一问题,解决的方法一般有两种,一种是两申请人作为一件申请的共同申请人;另一种是其中一方放弃权利并从另一方得到适当的补偿。
    • 根据专利法实施细则,职务发明创造是指:
      • 在本职工作中做出的发明创造
      • 履行本单位交付的本职工作之外的任务所做出的发明创造
      • 退职、退休或者调动工作后 1年内 做出的,与其在原单位承担的本职工作或者原单位分配的任务有关的发明创造。

    侵权判定

    • 中国公民、法人或者其他组织的作品,不论是否发表,都享有著作权。
    • 开发软件所用的思想、处理过程、操作方法或者数学概念不受保护。
    • 著作权法不适用于下列情形:
      • 法律、法规,国家机关的决议、决定、命令和其他具有立法、行政、司法性质的文件,及其官方正式译文;
      • 时事新闻;
      • 历法、通用数表、通用表格和公式。
    不侵权侵权
    个人学习、研究或者欣赏未经许可,发表他人作品
    适当引用未经合作作者许可,将与他人合作创作的作品当作自己单独创作的作品发表的
    公开演讲内容未参加创作,在他人作品署名
    用于教学或科学研究歪曲、篡改他人作品的
    复制馆藏作品剽窃他人作品的
    免费表演他人作品使用他人作品,未付报酬
    室外公共场所艺术品临摹、绘画、摄影、录像未经出版者许可,使用其出版的图书、期刊的版式设计的
    将汉语作品译成少数民族语言作品或盲文出版

    TIP

    • 如某美国专利在中国没有申请,则在中国不享有专利权,即在中国销售,中国企业不需要向美国公司支付美国专利的许可使用费。

    标准化基础知识

    标准的分类

    • 国际标准:ISO、IEC等国际标准化组织
    • 国家标准:
      • GB:中国
      • ANSI:美国
      • BS:英国
      • JIS:日本
    • 区域标准:又称为地区标准
      • PASC:太平洋地区标准会议
      • CEN:欧洲标准委员会
      • ASAC:亚洲标准咨询委员会
      • ARSO:非洲地区标准化组织
    • 行业标准:
      • GJB:中国军用标准
      • MIT-S:美国军用标准
      • IEEE:美国电气电子工程师协会
    • 地方标准:国家的地方一级行政机构制定的标准
    • 企业标准
    • 项目规范

    标准的编号

    • 国际、国外标准代号:标准代号+专业类号+顺序号+年代号
    • 我国国家标准代号:
      • GB:强制性标准代号
      • GB/T:推荐性标准代号
      • GB/Z:指导性标准代号
      • GSB:实物标准代号
    • 行业标准代号:由汉语拼音大写字母组成(如电子行业为SJ)
    • 地方标准代号:由 DB 加上省级行政区代码的前两位
    • 企业标准代号:由 Q 加上企业代号组成
    - + \ No newline at end of file diff --git a/categories/fragments/index.html b/categories/fragments/index.html index 77e2878c39..21b4368a0e 100644 --- a/categories/fragments/index.html +++ b/categories/fragments/index.html @@ -59,7 +59,7 @@ 2022年 (15篇)
    目录

    "杂碎"逆袭史

    笔者说

    碎片化知识的时代,纵然知晓不成体系的知识仅仅只能积累经验,却无法有效提升,但身在其中还是要如此,记录碎片知识,留作备忘。说不定后面还有机会 “把点连成线”。

    - + \ No newline at end of file diff --git "a/categories/issues/2021/12/01/F\347\233\230\344\270\212\347\232\204\345\233\236\346\224\266\347\253\231\345\267\262\346\215\237\345\235\217\343\200\202\346\230\257\345\220\246\346\270\205\347\251\272\350\257\245\351\251\261\345\212\250\345\231\250\344\270\212\347\232\204\345\233\236\346\224\266\347\253\231.html" "b/categories/issues/2021/12/01/F\347\233\230\344\270\212\347\232\204\345\233\236\346\224\266\347\253\231\345\267\262\346\215\237\345\235\217\343\200\202\346\230\257\345\220\246\346\270\205\347\251\272\350\257\245\351\251\261\345\212\250\345\231\250\344\270\212\347\232\204\345\233\236\346\224\266\347\253\231.html" index fe640eb8c3..b67b8b98b6 100644 --- "a/categories/issues/2021/12/01/F\347\233\230\344\270\212\347\232\204\345\233\236\346\224\266\347\253\231\345\267\262\346\215\237\345\235\217\343\200\202\346\230\257\345\220\246\346\270\205\347\251\272\350\257\245\351\251\261\345\212\250\345\231\250\344\270\212\347\232\204\345\233\236\346\224\266\347\253\231.html" +++ "b/categories/issues/2021/12/01/F\347\233\230\344\270\212\347\232\204\345\233\236\346\224\266\347\253\231\345\267\262\346\215\237\345\235\217\343\200\202\346\230\257\345\220\246\346\270\205\347\251\272\350\257\245\351\251\261\345\212\250\345\231\250\344\270\212\347\232\204\345\233\236\346\224\266\347\253\231.html" @@ -88,7 +88,7 @@ del [-r] 目录路径 erase [-r] 目录路径 remove-item [-Recurse] 目录路径

    202209211550888

    很显然,当你用 CMD 的 rd 命令语法来在 PowerShell 中删除目录时,自然会报错了。

    202209211550999

    以管理员身份运行

    一般来说,操作系统提供商提供多用户功能的目的,一方面是隔离数据,另一方面是为了防止小白用户操作错误还要怪在他们头上。

    可能不需要管理员身份运行也没问题,但是为了防止部分同学机器的操作系统存在一些 “怪癖”,最好是采用管理员身份来运行,避免无权限之类的问题。

    好了,目前就先简单提这两点注意事项,Good luck。

    - + \ No newline at end of file diff --git "a/categories/issues/2021/12/08/for\345\276\252\347\216\257\344\270\255\345\210\240\351\231\244\351\233\206\345\220\210\345\205\203\347\264\240\351\232\220\350\227\217\347\232\204\351\231\267\351\230\261.html" "b/categories/issues/2021/12/08/for\345\276\252\347\216\257\344\270\255\345\210\240\351\231\244\351\233\206\345\220\210\345\205\203\347\264\240\351\232\220\350\227\217\347\232\204\351\231\267\351\230\261.html" index 790de3b492..1b4d98a299 100644 --- "a/categories/issues/2021/12/08/for\345\276\252\347\216\257\344\270\255\345\210\240\351\231\244\351\233\206\345\220\210\345\205\203\347\264\240\351\232\220\350\227\217\347\232\204\351\231\267\351\230\261.html" +++ "b/categories/issues/2021/12/08/for\345\276\252\347\216\257\344\270\255\345\210\240\351\231\244\351\233\206\345\220\210\345\205\203\347\264\240\351\232\220\350\227\217\347\232\204\351\231\267\351\230\261.html" @@ -640,7 +640,7 @@ // 省略其他代码... }

    很显然,官方也是用的迭代器来实现的。

    后记

    C: 虽然是一个小问题,但是见到的犯错者无数,以前并未当回事,这次遇到正好记录一下,给各位同学一个提醒。

    - + \ No newline at end of file diff --git a/categories/issues/2021/12/10/Command line is too long. Shorten command line for XXX or also for Spring Boot default configuration.html b/categories/issues/2021/12/10/Command line is too long. Shorten command line for XXX or also for Spring Boot default configuration.html index 0e1b73e1cf..5c3fd111e3 100644 --- a/categories/issues/2021/12/10/Command line is too long. Shorten command line for XXX or also for Spring Boot default configuration.html +++ b/categories/issues/2021/12/10/Command line is too long. Shorten command line for XXX or also for Spring Boot default configuration.html @@ -56,7 +56,7 @@
    目录

    Command line is too long. Shorten command line for XXX or also for Spring Boot default configuration?

    问题描述

    今天笔者在公司从测试环境拉取了一个 bugfix 分支之后,等待 Maven 依赖也加载完了,点了【Debug】运行按钮,想起身去接杯水,跳过项目启动的这段时间。

    结果,刚握住水杯,就看到 IntelliJ IDEA 在左下角弹出了一个错误提示框,如下:

    202112102211700

    看提示的意思是命令行太长了,让缩短一下命令行。又点了两下【Debug】运行按钮,依然不依不饶的弹出这个提示,那就放下水杯解决吧。

    原因分析

    我想了想原因,就明白什么问题了,给大家贴一下启动的项目程序所在位置。

    • 仓库目录(.git目录)
      • 项目doc目录
      • src
        • 项目源码父级项目目录
          • 启动入口所在项目目录
            • src\main\java(三级目录)
              • com\xx\xxx(N级的包目录)
                • Spring Boot 项目启动类
          • 若干模块项目目录
          • pom.xml

    这个结构,不好多说什么,历史遗留, doc 和源码放在了一个仓库,层级的确挺深,但一般情况下也不会出现此问题,这次算是一个特殊情况。

    解决方案

    其实问题解决起来也不难,这种问题笔者以前也遇到过,不过当时也忘了怎么切到了 IntelliJ IDEA 修复提示内,选了一下就结束了。

    而这一次笔者没找到正确的修复入口,所以只能采用手动修改配置的方法了。

    双击打开项目根目录下的 .idea 目录,这个目录下都是 IntelliJ IDEA 自动保存的项目配置内容,一般情况下我们不需要关注它,但这次我们需要找到其中的 workspace.xml 配置文件,手动修改一下配置。

    202112102211705

    按下 【Ctrl + F】,在弹出的搜索框中,输入【PropertiesComponent】回车,定位到该项配置后,在其所在的 <component> 标签内最后部分添加一条属性配置,如下:

    xml
    <property name="dynamic.classpath" value="true" />
    <property name="dynamic.classpath" value="true" />

    202112102211710

    添加完后,关闭该配置文件即可,再次点击【Debug】运行按钮,项目正常启动了,笔者也该去接水了。

    - + \ No newline at end of file diff --git "a/categories/issues/2021/12/11/SQL \346\263\250\345\205\245\346\224\273\345\207\273\351\243\216\351\231\251.html" "b/categories/issues/2021/12/11/SQL \346\263\250\345\205\245\346\224\273\345\207\273\351\243\216\351\231\251.html" index 2413b45996..8feb693153 100644 --- "a/categories/issues/2021/12/11/SQL \346\263\250\345\205\245\346\224\273\345\207\273\351\243\216\351\231\251.html" +++ "b/categories/issues/2021/12/11/SQL \346\263\250\345\205\245\346\224\273\345\207\273\351\243\216\351\231\251.html" @@ -334,7 +334,7 @@ } } }

    当然了,笔者个人认为基本上能用 ${} 的地方都可以采用 #{} 替代。不过,倒也是见过一些同事在写代码时坚持用 ${} ,代码片段类似如下:

    sql
    SELECT * FROM user WHERE username LIKE '%${username}%'
    SELECT * FROM user WHERE username LIKE '%${username}%'

    的确,LIKE 模糊查询时,后面模糊条件的 % 等符号是不能直接出现在 SQL 语句里的,而是要写在由 ' (单引号)引起的字符串内。但是 MyBatis 的 #{} 又无法写在由 ' (单引号)引起的字符串内,即无法直接写成 '%#{username}%' (如果你不相信,可以自行尝试一下,看看控制台会有什么 “惊喜” 输出),这应该就是这部分同事不得不采用 '%${username}%' 写法的原因。

    好在,笔者这正好也提供一种解决方法,可以解决此问题,那就是使用 SQL 函数 CONCAT(),代码片段类似如下:

    sql
    SELECT * FROM user WHERE username LIKE CONCAT('%', #{username}, '%')
    SELECT * FROM user WHERE username LIKE CONCAT('%', #{username}, '%')

    没错,既然要拼接字符串,那就用 CONCAT() 函数,这个函数就是专门用来拼接字符串的,在拼接时可以使用 #{} ,所以也就不会存在 SQL 注入的问题了。

    - + \ No newline at end of file diff --git "a/categories/issues/2021/12/13/\346\227\240\346\263\225\350\256\277\351\227\256F\347\233\230\343\200\202\346\226\207\344\273\266\346\210\226\347\233\256\345\275\225\346\215\237\345\235\217\344\270\224\346\227\240\346\263\225\350\257\273\345\217\226.html" "b/categories/issues/2021/12/13/\346\227\240\346\263\225\350\256\277\351\227\256F\347\233\230\343\200\202\346\226\207\344\273\266\346\210\226\347\233\256\345\275\225\346\215\237\345\235\217\344\270\224\346\227\240\346\263\225\350\257\273\345\217\226.html" index 7f76ad6cc4..1b7c3af021 100644 --- "a/categories/issues/2021/12/13/\346\227\240\346\263\225\350\256\277\351\227\256F\347\233\230\343\200\202\346\226\207\344\273\266\346\210\226\347\233\256\345\275\225\346\215\237\345\235\217\344\270\224\346\227\240\346\263\225\350\257\273\345\217\226.html" +++ "b/categories/issues/2021/12/13/\346\227\240\346\263\225\350\256\277\351\227\256F\347\233\230\343\200\202\346\226\207\344\273\266\346\210\226\347\233\256\345\275\225\346\215\237\345\235\217\344\270\224\346\227\240\346\263\225\350\257\273\345\217\226.html" @@ -58,7 +58,7 @@ 2021年 (5篇)
    目录

    无法访问 F:\。文件或目录损坏且无法读取。

    问题描述

    笔者这块西数的移动硬盘最近真的是问题频发,前段时间无法删除损坏的回收站,这两天在家里电脑上插上之后,双击 F 盘提示已损坏,较之以前问题更甚。

    这的确给了笔者一个 “惊喜”,最近两周好像没开 Drive 备份到 NAS 。硬盘要是坏了,这两周的东西还能剩下多少就不好说了。

    不过好在最后问题解决了,跟笔者来一起看看解决方法吧。

    202112132257200

    解决方案

    尝试1:尝试检查与修复

    首先,在出现问题的磁盘上【右键】单击,然后选择【属性】。

    202112132257205

    在弹出的【属性】对话框中,选择【工具】选项卡,然后点击【检查】按钮。这个功能是用来检查磁盘文件系统错误的,检查完还会有个错误修复的环节。

    202112132257210

    可惜的是,不知道是笔者这台电脑登录的账号权限问题,还是系统错误,这项修复手段,笔者用不了。

    202112132257215

    尝试2:命令行修复

    还是老规矩,桌面可视化中的功能只是一种手段,每一项功能都有其对应的系统命令。

    按下【Windows】键,弹出【开始】菜单,直接输入【cmd】来在菜单中搜索。搜索出来后,在【cmd.exe/命令行】上【右键】单击,选择【以管理员身份运行】。

    202112132257220

    在弹出的 CMD 命令行窗口中,输入以下命令:

    bash
    # 这条命令是用来检查磁盘并修复的,中间的 f: 换成你出现上方问题的盘符即可。 
     chkdsk f: /f
    # 这条命令是用来检查磁盘并修复的,中间的 f: 换成你出现上方问题的盘符即可。 
     chkdsk f: /f

    202112132257225

    202112132257230

    等待检查修复结束,笔者的 F 盘又回来了。

    202112132257235

    - + \ No newline at end of file diff --git "a/categories/issues/2022/01/26/JavaScript \346\227\240\346\263\225\345\255\230\345\202\250 Java Long \347\261\273\345\236\213\346\225\260\346\215\256\351\227\256\351\242\230.html" "b/categories/issues/2022/01/26/JavaScript \346\227\240\346\263\225\345\255\230\345\202\250 Java Long \347\261\273\345\236\213\346\225\260\346\215\256\351\227\256\351\242\230.html" index 52e8cdbf9d..6646350990 100644 --- "a/categories/issues/2022/01/26/JavaScript \346\227\240\346\263\225\345\255\230\345\202\250 Java Long \347\261\273\345\236\213\346\225\260\346\215\256\351\227\256\351\242\230.html" +++ "b/categories/issues/2022/01/26/JavaScript \346\227\240\346\263\225\345\255\230\345\202\250 Java Long \347\261\273\345\236\213\346\225\260\346\215\256\351\227\256\351\242\230.html" @@ -228,7 +228,7 @@ ], "success": true }
    - + \ No newline at end of file diff --git "a/categories/issues/2022/03/24/\345\210\233\345\273\272\344\270\200\344\270\252\350\207\252\350\272\253\347\261\273\347\232\204\351\235\231\346\200\201\345\257\271\350\261\241\345\217\230\351\207\217\357\274\214\347\251\266\347\253\237\344\274\232\345\246\202\344\275\225\346\211\247\350\241\214\357\274\237.html" "b/categories/issues/2022/03/24/\345\210\233\345\273\272\344\270\200\344\270\252\350\207\252\350\272\253\347\261\273\347\232\204\351\235\231\346\200\201\345\257\271\350\261\241\345\217\230\351\207\217\357\274\214\347\251\266\347\253\237\344\274\232\345\246\202\344\275\225\346\211\247\350\241\214\357\274\237.html" index a01da0ebf9..b2e96ea327 100644 --- "a/categories/issues/2022/03/24/\345\210\233\345\273\272\344\270\200\344\270\252\350\207\252\350\272\253\347\261\273\347\232\204\351\235\231\346\200\201\345\257\271\350\261\241\345\217\230\351\207\217\357\274\214\347\251\266\347\253\237\344\274\232\345\246\202\344\275\225\346\211\247\350\241\214\357\274\237.html" +++ "b/categories/issues/2022/03/24/\345\210\233\345\273\272\344\270\200\344\270\252\350\207\252\350\272\253\347\261\273\347\232\204\351\235\231\346\200\201\345\257\271\350\261\241\345\217\230\351\207\217\357\274\214\347\251\266\347\253\237\344\274\232\345\246\202\344\275\225\346\211\247\350\241\214\357\274\237.html" @@ -278,7 +278,7 @@ // 3、结束方法 14: return }

    很显然了,count2 最后是被自增为 4 了。

    - + \ No newline at end of file diff --git "a/categories/issues/2022/08/11/\346\211\247\350\241\214Shell\350\204\232\346\234\254\357\274\214\346\212\245java command not found.html" "b/categories/issues/2022/08/11/\346\211\247\350\241\214Shell\350\204\232\346\234\254\357\274\214\346\212\245java command not found.html" index d7b0f983aa..2331e1100d 100644 --- "a/categories/issues/2022/08/11/\346\211\247\350\241\214Shell\350\204\232\346\234\254\357\274\214\346\212\245java command not found.html" +++ "b/categories/issues/2022/08/11/\346\211\247\350\241\214Shell\350\204\232\346\234\254\357\274\214\346\212\245java command not found.html" @@ -108,7 +108,7 @@ WantedBy=multi-user.target

    修改完系统服务,别忘了重新加载和重新启动。

    shell
    systemctl daemon-reload
     systemctl restart xxx
    systemctl daemon-reload
     systemctl restart xxx
    - + \ No newline at end of file diff --git "a/categories/issues/2022/08/31/SpringBoot\351\241\271\347\233\256\345\274\225\345\205\245OpenFeign\345\220\216\346\227\240\346\263\225\345\220\257\345\212\250.html" "b/categories/issues/2022/08/31/SpringBoot\351\241\271\347\233\256\345\274\225\345\205\245OpenFeign\345\220\216\346\227\240\346\263\225\345\220\257\345\212\250.html" index ce4105833b..5b42946893 100644 --- "a/categories/issues/2022/08/31/SpringBoot\351\241\271\347\233\256\345\274\225\345\205\245OpenFeign\345\220\216\346\227\240\346\263\225\345\220\257\345\212\250.html" +++ "b/categories/issues/2022/08/31/SpringBoot\351\241\271\347\233\256\345\274\225\345\205\245OpenFeign\345\220\216\346\227\240\346\263\225\345\220\257\345\212\250.html" @@ -512,7 +512,7 @@ <artifactId>spring-cloud-loadbalancer</artifactId> </dependency> </dependencies>

    最后启动成功。

    - + \ No newline at end of file diff --git "a/categories/issues/2022/09/05/Nginx\350\275\254\345\217\221\350\257\267\346\261\202\357\274\214\346\212\24513\357\274\232Permission denied\351\224\231\350\257\257.html" "b/categories/issues/2022/09/05/Nginx\350\275\254\345\217\221\350\257\267\346\261\202\357\274\214\346\212\24513\357\274\232Permission denied\351\224\231\350\257\257.html" index 906ac59c6d..71bbdc7cb5 100644 --- "a/categories/issues/2022/09/05/Nginx\350\275\254\345\217\221\350\257\267\346\261\202\357\274\214\346\212\24513\357\274\232Permission denied\351\224\231\350\257\257.html" +++ "b/categories/issues/2022/09/05/Nginx\350\275\254\345\217\221\350\257\267\346\261\202\357\274\214\346\212\24513\357\274\232Permission denied\351\224\231\350\257\257.html" @@ -96,7 +96,7 @@ pid /run/nginx.pid; ....
    sh
    nginx -s reload
    nginx -s reload

    问题没解决,还是 502,这可就触碰到笔者的知识盲区了,搜索一下吧,实在不行问问网管。

    这一搜啊,有一个搜索结果摘要引起了笔者的注意:

    解决SELinux阻止Nginx访问服务

    “在使用 yum 安装 nginx 后可能会出现配置完成后却无法访问的问题”。[1]

    笔者这一想,我们公司的网管当时让他安装 Nginx 没 5 分钟就告诉 OK 了,那很大可能是用 yum 安装的啊,作者博客让看一下 audit.log 有没有出现错误信息,笔者前去看了一下

    sh
    tail -50 /var/log/audit/audit.log
    tail -50 /var/log/audit/audit.log

    果然和作者贴的图一模一样。

    202209052232777

    根据作者所言,"出现此问题的原因是 SELinux 基于最小权限原则默认拦截了 Nginx 的请求,SELinux 是 Linux 的安全子系统,提供更安全的访问控制。"[1]

    解决方法,要么是直接关掉它,要么执行下方指令开启 HTTP 访问。

    sh
    setsebool -P httpd_can_network_connect 1
    setsebool -P httpd_can_network_connect 1

    执行后,立竿见影。忍不住感叹:Linux 知识学无止境。

    参考资料

    [1]解决SELinux阻止Nginx访问服务:https://blog.csdn.net/liweitao7610/article/details/107073852

    - + \ No newline at end of file diff --git "a/categories/issues/2022/09/23/\350\247\243\345\206\263\346\227\240\346\263\225\351\207\215\345\244\215\350\257\273\345\217\226\350\257\267\346\261\202\344\275\223\345\222\214\345\223\215\345\272\224\344\275\223\347\232\204\351\227\256\351\242\230.html" "b/categories/issues/2022/09/23/\350\247\243\345\206\263\346\227\240\346\263\225\351\207\215\345\244\215\350\257\273\345\217\226\350\257\267\346\261\202\344\275\223\345\222\214\345\223\215\345\272\224\344\275\223\347\232\204\351\227\256\351\242\230.html" index a420211590..fd8dd53fd9 100644 --- "a/categories/issues/2022/09/23/\350\247\243\345\206\263\346\227\240\346\263\225\351\207\215\345\244\215\350\257\273\345\217\226\350\257\267\346\261\202\344\275\223\345\222\214\345\223\215\345\272\224\344\275\223\347\232\204\351\227\256\351\242\230.html" +++ "b/categories/issues/2022/09/23/\350\247\243\345\206\263\346\227\240\346\263\225\351\207\215\345\244\215\350\257\273\345\217\226\350\257\267\346\261\202\344\275\223\345\222\214\345\223\215\345\272\224\344\275\223\347\232\204\351\227\256\351\242\230.html" @@ -290,7 +290,7 @@ } return responseBody; }
    - + \ No newline at end of file diff --git "a/categories/issues/2022/10/15/\350\247\243\345\206\263Windows\346\241\214\351\235\242\351\203\250\345\210\206\345\277\253\346\215\267\346\226\271\345\274\217\345\233\276\346\240\207\345\217\230\344\270\272\347\251\272\347\231\275\347\232\204\351\227\256\351\242\230.html" "b/categories/issues/2022/10/15/\350\247\243\345\206\263Windows\346\241\214\351\235\242\351\203\250\345\210\206\345\277\253\346\215\267\346\226\271\345\274\217\345\233\276\346\240\207\345\217\230\344\270\272\347\251\272\347\231\275\347\232\204\351\227\256\351\242\230.html" index ac11b3261c..cb313c48f2 100644 --- "a/categories/issues/2022/10/15/\350\247\243\345\206\263Windows\346\241\214\351\235\242\351\203\250\345\210\206\345\277\253\346\215\267\346\226\271\345\274\217\345\233\276\346\240\207\345\217\230\344\270\272\347\251\272\347\231\275\347\232\204\351\227\256\351\242\230.html" +++ "b/categories/issues/2022/10/15/\350\247\243\345\206\263Windows\346\241\214\351\235\242\351\203\250\345\210\206\345\277\253\346\215\267\346\226\271\345\274\217\345\233\276\346\240\207\345\217\230\344\270\272\347\251\272\347\231\275\347\232\204\351\227\256\351\242\230.html" @@ -56,7 +56,7 @@
    目录

    解决 Windows 桌面部分快捷方式图标变为空白的问题

    问题描述

    C: 今天把电脑系统从 Windows 11 换回了 Windows 10,使用了半个月的 Windows 11 真是一言难尽。

    换完系统就开始安装一些开发应用,安装了一会儿,突然发现桌面上 draw.io 应用快捷方式的图标变为了空白。

    202210152119199

    解决方案

    1、打开本地应用数据存储位置(C:\Users\用户名\AppData\Local

    按下 Windows + R 键,在弹出的运行对话框中输入 %localappdata%,回车确定。

    202210152120752

    2、在打开的本地应用数据存储窗口中,找到并删除 Iconcache.db 文件

    笔者说

    这是图标缓存文件,实际上我们的操作就是要删除图标缓存,让系统重新生成缓存。

    202210152132275

    3、打开任务管理器

    按下 Windows + X 键,在弹出快捷菜单后,按下 T 键。

    笔者说

    或者按 Ctrl + Alt + Delete 键,这个快捷键大家应该更熟悉。

    4、重新启动 Windows 资源管理器 应用

    右键单击 Windows 资源管理器,在弹出的菜单中选择 重新启动,屏幕会刷新一下。

    202210152132523

    这时候回到桌面,就可以看到 draw.io 快捷方式的图标恢复正常显示了。

    202210152132666

    - + \ No newline at end of file diff --git "a/categories/issues/2022/10/25/\350\247\243\345\206\263CentOS8\346\211\247\350\241\214yum\345\256\211\350\243\205\346\212\245\351\224\231.html" "b/categories/issues/2022/10/25/\350\247\243\345\206\263CentOS8\346\211\247\350\241\214yum\345\256\211\350\243\205\346\212\245\351\224\231.html" index 38e17a23d1..7f5f533d69 100644 --- "a/categories/issues/2022/10/25/\350\247\243\345\206\263CentOS8\346\211\247\350\241\214yum\345\256\211\350\243\205\346\212\245\351\224\231.html" +++ "b/categories/issues/2022/10/25/\350\247\243\345\206\263CentOS8\346\211\247\350\241\214yum\345\256\211\350\243\205\346\212\245\351\224\231.html" @@ -60,7 +60,7 @@ Error: Failed to download metadata for repo 'appstream': Cannot prepare internal mirrorlist: No URLs in mirrorlist

    202210252119166

    原因分析

    首先排除掉是安装 Docker 引起的错误,因为笔者起初还尝试了下把三个依赖分开安装,想看看是不是由于某个依赖安装引起的,最终发现无论执行 yum 安装哪一个包都会报这个错。

    而从报错信息的字面意思来看,应该是和 yum 的镜像源有关。

    错误:从仓库 ‘appstream’ 下载元数据失败:无法准备内部镜像列表:镜像列表中没有 url
    错误:从仓库 ‘appstream’ 下载元数据失败:无法准备内部镜像列表:镜像列表中没有 url

    这可就触碰到笔者的盲区部分了,最不擅长的就是这类 Linux 系统的初始配置,所以笔者搜索了一下,发现了一个较为靠谱的答案。

    2020 年 12 月 8 号,CentOS 官方宣布了停止维护 CentOS Linux 的计划,并推出了 CentOS Stream 项目,CentOS Linux 8 作为 RHEL 8 的复刻版本,生命周期缩短,于 2021 年 12 月 31 日停止更新并停止维护(EOL),更多的信息可以查看 CentOS 官方公告。[1]

    解决方案

    解决起来也较为容易,那就是如果需要更新 CentOS,需要将镜像从 mirror.centos.org 更改为 vault.centos.org。

    1. 进入 yum 的 repos 目录

      shell
      cd /etc/yum.repos.d/
      cd /etc/yum.repos.d/
    2. 替换镜像

      shell
      sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
       sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
      sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
       sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
    3. 生成缓存

      shell
      yum makecache
      yum makecache
    4. 更新

      shell
      yum -y update
      yum -y update

    等待更新完之后,再执行 yum 安装就正常了。

    参考资料

    1. 【已解决】Error: Failed to download metadata for repo ‘appstream‘: Cannot prepare internal mirrorlist:https://blog.csdn.net/weixin_43252521/article/details/124409151
    2. CentOS 8 EOL如何切换源?:https://help.aliyun.com/document_detail/405635.html
    3. CentOS Project shifts focus to CentOS Stream:https://blog.centos.org/2020/12/future-is-centos-stream/
    - + \ No newline at end of file diff --git "a/categories/issues/2022/10/29/Docker\350\256\276\347\275\256\347\275\221\347\273\234\344\273\243\347\220\206.html" "b/categories/issues/2022/10/29/Docker\350\256\276\347\275\256\347\275\221\347\273\234\344\273\243\347\220\206.html" index 27469d193f..e8cc4e5f77 100644 --- "a/categories/issues/2022/10/29/Docker\350\256\276\347\275\256\347\275\221\347\273\234\344\273\243\347\220\206.html" +++ "b/categories/issues/2022/10/29/Docker\350\256\276\347\275\256\347\275\221\347\273\234\344\273\243\347\220\206.html" @@ -66,7 +66,7 @@ Environment="HTTPS_PROXY=http://用户名:密码@你的代理服务器地址:你的代理服务器端口号"

    最后,重新加载服务配置,重启服务。

    shell
    systemctl daemon-reload
     systemctl restart docker
    systemctl daemon-reload
     systemctl restart docker

    OK,再执行 docker 命令就没问题了。

    参考资料

    1. Control Docker with systemd#Custom Docker daemon options 之 HTTP/HTTPS proxy:https://docs.docker.com/config/daemon/systemd/#httphttps-proxy

    笔者说

    这里提一下,官方文档真的很香。

    - + \ No newline at end of file diff --git "a/categories/issues/2022/11/04/\350\247\243\345\206\263Docker\345\256\211\350\243\205Prometheus\345\220\257\345\212\250\346\212\245\351\224\231\347\232\204\351\227\256\351\242\230.html" "b/categories/issues/2022/11/04/\350\247\243\345\206\263Docker\345\256\211\350\243\205Prometheus\345\220\257\345\212\250\346\212\245\351\224\231\347\232\204\351\227\256\351\242\230.html" index 13df5ca4ff..52f74e17b6 100644 --- "a/categories/issues/2022/11/04/\350\247\243\345\206\263Docker\345\256\211\350\243\205Prometheus\345\220\257\345\212\250\346\212\245\351\224\231\347\232\204\351\227\256\351\242\230.html" +++ "b/categories/issues/2022/11/04/\350\247\243\345\206\263Docker\345\256\211\350\243\205Prometheus\345\220\257\345\212\250\346\212\245\351\224\231\347\232\204\351\227\256\351\242\230.html" @@ -94,7 +94,7 @@ --web.enable-lifecycle --storage.tsdb.retention.time=90d privileged: true

    很明显,错误信息中的文件路径 /opt/bitnami/prometheus/data/queries.active,是 Prometheus 容器中的数据存储目录,笔者还将其挂载到了宿主机的 /opt/disk/docker/volumes/prometheus/data 目录。

    关于脚本中的两个挂载目录,笔者仅提前创建了 conf 配置目录,上传了配置文件。至于这个 data 数据目录则是 Docker 在运行容器时自动在宿主机创建的。

    那问题的原因已经呼之欲出了,很大可能是由于 Docker 在宿主机自动创建的 data 数据挂载目录没有写入权限。

    解决方案

    解决起来也较为容易,给 data 目录授予写入权限就好了。

    shell
    chmod 775 /opt/disk/docker/volumes/prometheus/data
    chmod 775 /opt/disk/docker/volumes/prometheus/data

    最后再重启一下 Prometheus 容器。

    shell
    docker restart prometheus
    docker restart prometheus

    此时,再通过 docker ps 命令查看 Prometheus 容器的状态,已经是正常的 Up 状态了。最后,笔者也是建议大家这类挂载目录尽量提前创建和授权。

    - + \ No newline at end of file diff --git "a/categories/issues/2022/11/06/\350\247\243\345\206\263DotNET\345\256\211\350\243\205\345\220\216\346\212\245\351\224\231\347\232\204\351\227\256\351\242\230.html" "b/categories/issues/2022/11/06/\350\247\243\345\206\263DotNET\345\256\211\350\243\205\345\220\216\346\212\245\351\224\231\347\232\204\351\227\256\351\242\230.html" index 7a7be07802..1b898c4246 100644 --- "a/categories/issues/2022/11/06/\350\247\243\345\206\263DotNET\345\256\211\350\243\205\345\220\216\346\212\245\351\224\231\347\232\204\351\227\256\351\242\230.html" +++ "b/categories/issues/2022/11/06/\350\247\243\345\206\263DotNET\345\256\211\350\243\205\345\220\216\346\212\245\351\224\231\347\232\204\351\227\256\351\242\230.html" @@ -80,7 +80,7 @@ at System.DateTime.get_Now() at Microsoft.DotNet.Cli.Program.Main(System.String[]) Aborted

    原因分析

    简单翻译一下关键错误信息。

    进程终止。找不到系统上安装的有效 ICU 包。请使用包管理器安装 libicu,然后重试。或者,如果您想在不支持全球化的情况下运行,可以将配置标志 System.Globalization.Invariant 设置为 true。请访问 https://aka.ms/dotnet-missing-libicu 了解更多信息。

    从提示信息来看,问题的原因是当前系统没有安装 DotNet 需要的 libicu 库。

    解决方案

    实际上这也是因为笔者采用的手动安装方式才导致的问题,如果采用包管理器(在线)安装方式,这个 libicu 库会自动被安装好,也就不会出现这个问题了。

    202211061523521

    知道了问题的原因,那就安装一下这个依赖库。

    shell
    yum -y install libicu
    yum -y install libicu

    安装完后,再执行查看版本命令,版本信息正常输出了。

    参考资料

    1. 在 CentOS 上安装 .NET SDK 或 .NET 运行时:https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-centos
    2. 用于全球化的运行时配置选项:https://learn.microsoft.com/zh-cn/dotnet/core/runtime-config/globalization
    - + \ No newline at end of file diff --git "a/categories/issues/2022/11/23/\350\247\243\345\206\263Maven\344\274\240\351\200\222\344\276\235\350\265\226\346\261\241\346\237\223\347\232\204\351\227\256\351\242\230.html" "b/categories/issues/2022/11/23/\350\247\243\345\206\263Maven\344\274\240\351\200\222\344\276\235\350\265\226\346\261\241\346\237\223\347\232\204\351\227\256\351\242\230.html" index ec3b7db9f0..ee227d8232 100644 --- "a/categories/issues/2022/11/23/\350\247\243\345\206\263Maven\344\274\240\351\200\222\344\276\235\350\265\226\346\261\241\346\237\223\347\232\204\351\227\256\351\242\230.html" +++ "b/categories/issues/2022/11/23/\350\247\243\345\206\263Maven\344\274\240\351\200\222\344\276\235\350\265\226\346\261\241\346\237\223\347\232\204\351\227\256\351\242\230.html" @@ -332,7 +332,7 @@ <artifactId>fastjson</artifactId> </dependency> </dependencies>

    OK,问题解决,写完 JSON 这个单词,IntelliJ IDEA 没有任何犹豫的自动导入了笔者期望的包。

    202211232122166

    - + \ No newline at end of file diff --git a/categories/issues/index.html b/categories/issues/index.html index f073f56dd2..23301cac63 100644 --- a/categories/issues/index.html +++ b/categories/issues/index.html @@ -56,7 +56,7 @@
    目录

    Bug万象集

    笔者说

    你读过的书,遇过的人,扛过的事,写过的 Bug,构成了你作为开发者的人生格局。

    - + \ No newline at end of file diff --git "a/categories/solutions/2021/11/18/\347\224\250Java8\350\216\267\345\217\226\350\277\221N\345\244\251\346\227\245\346\234\237.html" "b/categories/solutions/2021/11/18/\347\224\250Java8\350\216\267\345\217\226\350\277\221N\345\244\251\346\227\245\346\234\237.html" index e52676e911..9b7b9ef62d 100644 --- "a/categories/solutions/2021/11/18/\347\224\250Java8\350\216\267\345\217\226\350\277\221N\345\244\251\346\227\245\346\234\237.html" +++ "b/categories/solutions/2021/11/18/\347\224\250Java8\350\216\267\345\217\226\350\277\221N\345\244\251\346\227\245\346\234\237.html" @@ -190,7 +190,7 @@ return respMap; } }
    - + \ No newline at end of file diff --git "a/categories/solutions/2021/11/22/\344\270\200\346\235\241SQL\346\237\245\350\257\242\344\273\212\345\271\264\346\257\217\346\234\210\347\273\237\350\256\241\344\277\241\346\201\257.html" "b/categories/solutions/2021/11/22/\344\270\200\346\235\241SQL\346\237\245\350\257\242\344\273\212\345\271\264\346\257\217\346\234\210\347\273\237\350\256\241\344\277\241\346\201\257.html" index 2ede62488a..6a1793e7b7 100644 --- "a/categories/solutions/2021/11/22/\344\270\200\346\235\241SQL\346\237\245\350\257\242\344\273\212\345\271\264\346\257\217\346\234\210\347\273\237\350\256\241\344\277\241\346\201\257.html" +++ "b/categories/solutions/2021/11/22/\344\270\200\346\235\241SQL\346\237\245\350\257\242\344\273\212\345\271\264\346\257\217\346\234\210\347\273\237\350\256\241\344\277\241\346\201\257.html" @@ -120,7 +120,7 @@ SUM(CASE MONTH(`create_time`) WHEN '12' THEN 1 ELSE 0 END) AS `12月` FROM `t_user` # 根据自身需要确定实际业务表 WHERE YEAR(`create_time`)= YEAR(NOW());
    - + \ No newline at end of file diff --git "a/categories/solutions/2022/09/07/\351\200\222\345\275\222\346\237\245\350\257\242\346\240\221\345\236\213\347\273\223\346\236\204\346\225\260\346\215\256\347\232\204\346\200\247\350\203\275\344\274\230\345\214\226\346\226\271\346\241\210.html" "b/categories/solutions/2022/09/07/\351\200\222\345\275\222\346\237\245\350\257\242\346\240\221\345\236\213\347\273\223\346\236\204\346\225\260\346\215\256\347\232\204\346\200\247\350\203\275\344\274\230\345\214\226\346\226\271\346\241\210.html" index bdea50dbaf..28b9d634b6 100644 --- "a/categories/solutions/2022/09/07/\351\200\222\345\275\222\346\237\245\350\257\242\346\240\221\345\236\213\347\273\223\346\236\204\346\225\260\346\215\256\347\232\204\346\200\247\350\203\275\344\274\230\345\214\226\346\226\271\346\241\210.html" +++ "b/categories/solutions/2022/09/07/\351\200\222\345\275\222\346\237\245\350\257\242\346\240\221\345\236\213\347\273\223\346\236\204\346\225\260\346\215\256\347\232\204\346\200\247\350\203\275\344\274\230\345\214\226\346\226\271\346\241\210.html" @@ -398,7 +398,7 @@ super(label, value); } }
    - + \ No newline at end of file diff --git a/categories/solutions/index.html b/categories/solutions/index.html index b221fd9ac3..601facf392 100644 --- a/categories/solutions/index.html +++ b/categories/solutions/index.html @@ -56,7 +56,7 @@
    目录

    方案春秋志

    笔者说

    上学的时候除了有错题本之外,一般还会额外准备一个用来记录名言佳剧的本子,按当时的意图是希望日积月累来提升写作能力。
    在写代码的时候,经常会想到或遇到一些小方案,在此记录下来,大抵亦是如此。

    - + \ No newline at end of file diff --git "a/categories/tools/2021/01/14/\345\210\235\350\257\206Lombok.html" "b/categories/tools/2021/01/14/\345\210\235\350\257\206Lombok.html" index eb682b1933..af59fb797d 100644 --- "a/categories/tools/2021/01/14/\345\210\235\350\257\206Lombok.html" +++ "b/categories/tools/2021/01/14/\345\210\235\350\257\206Lombok.html" @@ -313,7 +313,7 @@ public record Pet(String name,int health){ }

    参考资料

    [1]Project Lombok 简介:https://projectlombok.org

    [2]Lombok常用注解:https://www.cnblogs.com/mayhh/p/10113169.html

    [3]Records介绍:https://openjdk.java.net/jeps/359

    后记

    Lombok 入门到这里也就介绍完了,感觉怎么样?这只是个入门示例,如果想再多研究研究其他注解和属性,那需要更多的篇幅和时间。

    实际上,在现在的业内,Lombok 的使用存在着争议,有些人认为它是 开发利器,有些人 避之不及。认为它好的是因为它的确减少了大量的冗余代码,相当于写代码随时带个生成器,阅读代码也变得更加清晰直观;认为它不好的是因为它的使用还需要安装额外的插件,且是侵入性的设计(有些人认为,它这种改变语法的事应该是语言本身自己该做的,它 "越俎代庖" 了),如果开发中 IDEA、JDK、Lombok 不配套,那结果显而易见;还有些人认为它影响了业务控制代码的添加和阅读。

    孰是孰非,仁者见仁智者见智。笔者个人觉得,自己平时练习和测试都可以随便用用,如果上升到公司,还是应该以实际和团队整体出发。

    202101140900595

    202101140900597

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/categories/tools/2021/02/22/RDM\345\277\253\351\200\237\345\205\245\351\227\250.html" "b/categories/tools/2021/02/22/RDM\345\277\253\351\200\237\345\205\245\351\227\250.html" index 0e7e9a55e0..448832c516 100644 --- "a/categories/tools/2021/02/22/RDM\345\277\253\351\200\237\345\205\245\351\227\250.html" +++ "b/categories/tools/2021/02/22/RDM\345\277\253\351\200\237\345\205\245\351\227\250.html" @@ -55,7 +55,7 @@
    目录

    Redis Desktop Manager 快速入门

    前言

    这个时代,Redis 多流行啊,10 个程序员起码有 8 个听过用过。人多了,自然有人不太喜欢使用命令行来直接操作 Redis。所以,在官方没有提供的情况下,大家一直都在寻求一款好用的 Redis 客户端管理工具,而 RDM 这款软件,在咱们国内 IT 圈子里不说人尽皆知吧,也可以说的上是小有名气的。

    简介

    RDM,全称 Redis Desktop Manager。它是一个快速、简单、支持跨平台的 Redis 桌面管理工具,基于 Qt 5 开发,支持通过 SSH Tunnel 连接 [1]。它开源在了 GitHub [2] 上。

    长下面这样。在当前 Redis 客户端工具圈里可以说的上是 “高颜值”,而且也比较实用。

    202102220930199

    但是很可惜,0.9.3.817 是它的最后一个免费版。

    你可能会比较好奇,它不是开源的吗?的确,它是开源的,但也仅仅是开源,即开放源代码。而大多数开源软件都会免费提供安装包,但 RDM 从 0.9.3.817 版本开始就不再免费提供了。

    这意味着什么?如果你懂一定的相关技术,自然可以利用它的源代码自己编译一个。而如果你不懂?不好意思,那就买它吧!看看下方的价格,其实也算良心价了。

    202102220930299

    当然,笔者不是来刺激你的,早就给你准备好了一份 RDM 的第三方编译版 。

    202102220930399

    在 GitHub 上,这类 RDM 第三方编译版还是挺多的,你也可以自己去搜索一下。

    202102220930599

    下载

    笔者这里以 rdm-builder 这个 GitHub 仓库为示例,来介绍下第三方编译版的 Windows 版 RDM 下载和安装。

    打开这个仓库之后,在右侧的 Releases 显示它有15个发行版,最新的是 v2021.2 版,这也是随着官方来更新的。等你看到这篇文章的时候,或许它已经变成了更新的版本。

    点击最新发行版,跳转到版本下载页面。

    202102220930699

    然后再点击 xxx.exe 即可开始下载这个第三方编译的 Windows 版 RDM 了。

    202102220930799

    下载好了,一个普普通通的可执行程序。

    202102220930899

    安装

    接下来,我们 “傻瓜式” 安装即可。

    202102220930999

    202102220931199

    改动一下安装位置,这个目录专门放开发工具,是笔者以前逐渐养成的个人习惯。

    202102220931299

    202102220931399

    202102220931599

    202102220931699

    连接服务器

    安装完成后,直接打开。因为不是最新版,所以每次都会弹出这个更新提示框。别管,直接点 [OK] 就行。

    202102220931799

    RDM 使用起来还是比较容易的,点击左上角的 [连接到 Redis 服务器]。

    202102220931899

    进入到连接设置之后,依次填写 [连接名称,Redis 服务器地址,Redis 密码(可选),用户名(可选)],可以先点击 [测试连接] 查看下是否可以连接成功。

    202102220931999

    常见使用

    虽然,本篇笔者重点是给你安利第三方编译的 RDM,但思来想去还是决定为部分小白们介绍一下 RDM 的简易操作,会用的老白们就不用看了。

    查看所有数据库

    测试连接成功后,双击连接名,就可以看到当前 Redis 服务器的所有数据库。

    202102220932199

    存储键

    202102220932299

    修改值

    202102220932399

    修改过期时间

    202102220932599

    删除键

    刚才我们给 name 这个键设置了5秒过期之后,唯一存储的数据也没了,我们再新建一个,然后来测试一下删除功能。

    202102220932699

    命令行操作

    不仅如此,当你想用命令行操作时,RDM 还可以直接打开控制台连接 Redis 服务器。

    202102220932799

    参考资料

    [1]Redis Desktop Manager 介绍:https://www.oschina.net/p/redisdesktop?hmsr=aladdin1e1

    [2]RDM GitHub 地址:https://github.com/uglide/RedisDesktopManager/

    [3]RDM 的第三方编译版:https://github.com/FuckDoctors/rdm-builder

    - + \ No newline at end of file diff --git "a/categories/tools/2021/03/04/ARDM\345\277\253\351\200\237\345\205\245\351\227\250.html" "b/categories/tools/2021/03/04/ARDM\345\277\253\351\200\237\345\205\245\351\227\250.html" index 2786ec3495..e7d46b314c 100644 --- "a/categories/tools/2021/03/04/ARDM\345\277\253\351\200\237\345\205\245\351\227\250.html" +++ "b/categories/tools/2021/03/04/ARDM\345\277\253\351\200\237\345\205\245\351\227\250.html" @@ -55,7 +55,7 @@
    目录

    Another Redis Desktop Manager 快速入门

    前言

    之前,笔者发布过一篇有关于 Redis 可视化客户端:RDM 的介绍文章,有童鞋看了后留言给笔者说:“RDM 不怎么好用,建议试试 Another Redis Desktop Manager。“

    实际上,虽然笔者发的是 RDM,但近段时间一直在用的就是 ARDM。当然了,这不是笔者藏着掖着,而是因为资源不得一点点发吗?而且,还是有童鞋在用 RDM 的,习惯一个工具之后,随便更换也是需要学习成本不是?

    202103042310166

    OK,本篇笔者就要给童鞋们分享一下这个所谓的 ARDM。

    简介

    简介

    Another Redis Desktop Manager,一个更快,更好,更稳定的 Redis 桌面管理器,兼容 Linux, windows, mac。更重要的是,它不会在加载大量的键时崩溃。[1]

    顾名思义,Another Redis Desktop Manager 就是 另一个 RDM 的意思,它在功能方面和 RDM 大体没什么区别,不过在 UI 和体验上的确更胜一筹。

    这个项目从 2019 年 2 月份就开始了,开源且免费提供打包版本,更新到今天也不短的时间了,所以关于稳定性的问题就暂时不用担心了。[2]

    202103042310266

    下载

    打开 GitHub 直接搜索 AnotherRedisDesktopManager 项目,然后点击项目右下方的最新发行版,就可以跳转到对应的版本下载页面了。

    202103042310366

    当然,也可以直接复制本文最后参考资料 [2] 的下载地址,然后在 PC 浏览器打开,也同样可以跳转到最新版的下载页面。

    然后,根据你的系统情况选择合适的版本下载吧。

    202103042310566

    下载好了,一个平平无奇的 exe 安装包。

    202103042310666

    安装

    接下来,我们 “傻瓜式” 安装即可。

    202103042310766

    改动一下安装位置,这个目录专门放开发工具,是笔者以前逐渐养成的个人习惯。

    202103042310866

    202103042310966

    202103042311166

    连接服务器

    安装完成后,直接打开,界面可真是简洁到家了。

    202103042311266

    连接服务器的步骤也和 RDM 差不多,点击左上角的 [新建连接]。

    进入到新建连接界面之后,依次填写 [Redis 服务器地址,Redis 端口号,Redis 密码,连接名] 后即可点击 [确定] 来新建一个连接。

    笔者说

    如果你要连接的 Redis 就在本机,并且你没改过什么默认设置(端口、密码等),你甚至只需要在这个界面点一下确定就可以新建好一个连接。

    202103042311366

    202103042311566

    常见使用

    虽然,笔者觉得 ARDM 和 RDM 在功能上大体是一样的,但为了照顾小白们的感受,笔者还是按当初介绍 RDM 的步骤再演示一下常见操作。

    查看所有键

    单击连接名,就可以打开单个连接,默认是处于 0 号数据库,可以根据需求进行数据库切换。另外,打开连接时默认还会在右侧打开当前 Redis 的服务监控。

    202103042311666

    存储键

    ARDM 中存储键是先新增一个 key,这个 key 默认什么也没存储,你需要再为这个 key 设置下 value,这一步实际就是下面的修改操作。

    202103042311766

    修改值

    202103042311866

    修改过期时间

    202103042311966

    删除键

    刚才我们给 name 这个键设置了 5 秒过期之后,唯一存储的数据也没了,我们再新建一个,然后来测试一下删除功能。

    202103042312166

    命令行操作

    当你想用命令行操作时,ARDM 同样也可以直接打开控制台连接 Redis 服务器。

    202103042312266

    参考资料

    [1]Another Redis Desktop Manager GitHub 地址:https://github.com/qishibo/AnotherRedisDesktopManager

    [2]Another Redis Desktop Manager 下载地址:https://github.com/qishibo/AnotherRedisDesktopManager/releases

    后记

    C: 好了,ARDM 的介绍就到这儿结束了,至于其他的功能,自行去发现体验吧,那样才更有乐趣,不是吗?

    当然,本篇介绍完,笔者暂时就不会再推荐其他 Redis 可视化客户端了,也许后面有后起之秀,到那时候再说吧,也欢迎童鞋们再留言告诉我。

    - + \ No newline at end of file diff --git "a/categories/tools/2021/03/06/Postman\345\277\253\351\200\237\345\205\245\351\227\250.html" "b/categories/tools/2021/03/06/Postman\345\277\253\351\200\237\345\205\245\351\227\250.html" index bb30d38c9c..41dd44312a 100644 --- "a/categories/tools/2021/03/06/Postman\345\277\253\351\200\237\345\205\245\351\227\250.html" +++ "b/categories/tools/2021/03/06/Postman\345\277\253\351\200\237\345\205\245\351\227\250.html" @@ -55,7 +55,7 @@
    目录

    Postman 快速入门

    前言

    近两年前后端分离开发成为了主流趋势,前端可以专心实现自己的客户端样式和交互,而后端可以更多关注业务逻辑的处理。

    在后端开发了接口之后,需要进行测试工作,而由于前端的拆分,这回想要测试一下,可没有相应的页面能够提供功能入口了。

    莫非,要通过浏览器地址栏来进行测试?但这些接口又不仅仅是 GET 请求方式。而且,有些接口的要求很是复杂,需要传递请求头或更为复杂的参数。再不成,后端自己写个简单页面 demo 来进行测试?那也太 Low 了!

    本篇,笔者就要为后端开发推荐一款强大的测试工具,这个工具可是笔者从测试那儿 GET 到的。

    202103061725199

    简介

    Postman 简介

    Postman 是一款功能强大的,网页调试与发送网页 HTTP 请求的工具,通过 Postman 我们可以发送几乎任何请求方式的请求,也可以附带各种类型的请求头、请求参数。

    202103061725222

    下载

    Postman 最初是谷歌浏览器的一款插件,后来火了之后,人家自己也开发了相应的客户端以及 Web 端。笔者在本篇就以客户端来进行示例使用,你要是不喜欢这两种,也可以去百度找一下它的插件版。

    复制文章最后参考资料 [1] 的地址,然后粘贴到你的 PC 浏览器地址栏,访问之后就可以点击 [Download the App] 按钮,然后根据自己系统情况来进行客户端下载了。

    笔者说

    你点击 [Download the App] 按钮的时候,它会弹出一个下拉框,让你选择 Windows 系统的某个位数版本,然后再开始下载。

    但实际上 Postman 也有 Mac、Linux 系统的版本,就在下载按钮下方 [Not your OS?] 那儿,点击对应系统版本的链接就可以下载了。

    202103061725333

    下载好了,一个平平无奇的 exe 安装包。

    202103061725566

    安装

    双击 exe 安装包,Postman 就会不识抬举的自行完成安装,想必又是装在了 C 盘。

    202103061725666

    安装完成后,双击桌面上出现的 Postman 图标,打开后的 Postman 首屏如下。

    Postman 在首屏极力推荐我们进行注册,注册后可以实现云端同步备份,如果你有这需求可以创建一个。当然,点击 [Skip and go to the app] 跳过这一步,直接进入主界面也行。

    笔者说

    如果一会儿体验过打算长期使用起来,别忘了注册个账号,还可以云端同步。

    202103061725677

    和最初版本比起来,Postman 更新的还是挺快的,功能也更加丰富了,但对于咱们来讲,只需要重点关注好它的核心功能即可。

    202103061725777

    安装好后,笔者将带各位同学,学习 Postman 中的三种基本操作,这也是 Postman 在应用主界面首要推荐你尝试的。

    在开始前,我们先准备一个数据接口,笔者这里注册并申请了天行数据的机器人 API [2],你也可以用自己的项目 API 来进行测试。

    202103061725888

    202103061725999

    测试请求

    找到 [Overview] 窗口右侧的 [Get started],然后点击 [Create a request] 来开始创建一个请求。其实你点击 [Overview] 选项卡右侧的 + 号也可以打开创建请求窗口。

    笔者说

    如果你进入主界面后,不小心把 [Overview] 窗口关闭了,可以点击左侧的 [Scratch Pad] 再次打开它。

    202103061726166

    在弹出的创建请求窗口里,提供了丰富的选项,几乎可以满足我们所有的接口测试需求。

    202103061726266

    接下来我们按照刚才申请的接口要求,来进行请求测试吧。在大多情况下,我们用的最多的就是 GET 请求和 POST 请求,笔者就用 Postman 来分别演示一下。

    GET请求

    按照 API 介绍依次选择并填写好请求方式,请求URL,请求参数,然后点击 [Send] 发送请求即可。

    202103061726366

    接收到的响应内容默认是以 [Pretty] 漂亮的格式化好的 [JSON] 格式来展示的,你也可以调整为其他数据格式和展示方式。

    • Pretty:以漂亮的格式化的形式来展示响应数据,支持 JSON、XML、HTML 等数据内容的格式化;

    • Raw:以普通的文本形式来展示响应数据;

    • Preview:以预览的形式来展示响应数据,适合 HTML 格式的响应数据;(在浏览器控制台的网络选项卡中,查看某个网络请求的响应内容也有此种方式)

      202103061726566

    • Visualize:以可视化的图形来展示响应数据,但这一项需要提前在 Postman 中编写一些测试脚本。

    POST请求

    发送 POST 请求也和 GET 差不多,我们也是依次选择并填写好请求方式,请求URL及请求参数。这里的请求参数,需要在请求体部分设置。

    在请求体 [Body] 中选择 [x-www-form-urlencoded] 然后填写请求参数键值对即可。

    笔者说

    这个过程等价于我们在网页上编写一个 form 表单,设置请求方式为 POST,然后对表单设置好 name 和 value 值一样,以 POST 请求方式来提交 form 表单的时候,默认的 enctype (encodetype,规定了 form 表单在发送到服务器时的编码方式) 就是:application/x-www-form-urlencoded。

    202103061726666

    当然,请求体部分还可以设置为其他的格式:

    • form-data:做文件上传的时候,我们都知道要将请求的 enctype 设置为 multipart/form-data,该选项等价于此;

      202103061726777

    • raw:该选项下,可以发送任意格式的普通文本数据,例如:Text、JSON、XML、HTML等;

    • binary:该选项等价于设置请求头 Content-Type 为 application/octet-stream,只可以发送二进制数据,t通常用于文件的上传,且只能上传一个,没有像 form-data 格式的键值对;

      202103061726888

    • GraphQL:顾名思义,该选项支持 GraphQL 查询。

      GraphQL 简介

      GraphQL 是一种用于 API 的查询语言,GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,通过向你的 API 发出一个 GraphQL 请求就能准确获得你想要的数据,不多不少。[3]
      我们定义的 API 在返回数据的时候,需要定义好相应的 DTO 类,否则直接返回实体类,会包含过多不需要的数据,而 GraphQL 可以有效解决此问题。

      202103061726999

    创建Collection

    我们在测试 API 的时候,有些 API 是属于用户相关的接口,有些是属于用户相关的接口,零零散散的很混乱。Postman 中提供了 collection (集合)的概念,我们通过创建一个个的 collection,就可以在 Postman 中对创建过的请求归类,更方便我们查阅和使用。

    在 [Overview] 窗口,点击右侧 [Get started] 中的 [Create a collection],就可以开始创建一个 collection 了。也可以点击左侧 [Collections] 菜单界面中的 + 号来开始创建。

    2021030617271226

    在创建 collection 界面,先为 collection 起个名,然后就可以点击 [Add a request] 来添加请求了,但是点击这个按钮添加请求,是在 collection 中创建一个新请求,我们之前的那些请求咋办?

    202103061727222

    别着急,打开之前创建过的请求窗口,点击地址栏上的 [Save] 按钮,可以将其保存到指定 collection 中。

    202103061727366

    在弹出的保存请求对话框中,依次填写请求名称,请求描述,选择好要保存到的 collection,最后点击保存即可。

    202103061727521

    笔者只是给你打个样儿。你还可以用项目名作为 collection 的名字,然后在 collection 下可以继续创建一个个的文件夹来细分模块,还是挺方便的。

    202103061727616

    笔者说

    如果你不小心关闭过请求窗口,在 Postman 中还可以点击 [History] 菜单找到之前的历史记录。

    创建环境

    实际项目开发的时候,我们还要准备多套环境:开发环境、测试环境、生产环境...,这些环境的地址等信息是不同的,总不能让我们对相同接口前缀地址换来换去吧。

    当然不能,在 Postman 中可以通过创建 environment 来方便的进行各种环境切换,更加方便了我们对 API 的测试。

    在 [Overview] 窗口,点击右侧 [Get started] 中的 [Create an environment],就可以开始创建一个环境了。也可以点击左侧 [Environments] 菜单界面中的 + 号来开始创建。

    202103061727717

    先给环境起个名,然后添加环境变量,为环境变量设置好初始值和当前值,最后保存。

    笔者说

    关于环境变量的初始值和当前值,它俩的区别在于我们开启云端团队协作后,初始值会同步到 Postman 服务器与团队成员共享,当前值则只会存储在本地。默认情况下,你设置了初始值后,当前值会默认设置相同值。

    202103061727818

    依此类推,你可以再创建一些其他环境。如果用过 Spring Boot 配置文件的 profile 设置,那这应该很好理解的。

    • application.yml
    • application-dev.yml
    • application-prod.yml

    202103061727919

    这环境定义好后,使用起来也很容易,先用双大括号 替换掉原来 URL 的一些固定值。以后,就可以根据当前的环境需求,点击右上角的 [环境切换] 按钮,来自如切换环境了。

    202103061728199

    202103061728299

    参考资料

    [1]Postman 官方下载地址:https://www.postman.com/downloads/

    [2]天行数据 天行机器人 API 介绍:https://www.tianapi.com/apiview/47

    [3]GraphQL 官网介绍:https://graphql.cn/

    后记

    C: 好了,关于 Postman 的介绍就到此结束了,笔者介绍的是基础操作,如果你还想了解更多,可以去看看官方文档,在 Postman 中还可以编写测试脚本,配置认证信息用于 OAuth 等协议请求,有需要的时候搜索一下。

    202103061728399

    其实,除了 Postman 之外,还有一些同类型的工具,例如:ApiPost、Apifox 等,有兴趣和需要的同学也可以去了解一下。

    202103061728599

    202103061728699

    - + \ No newline at end of file diff --git "a/categories/tools/2021/03/10/Quartz\345\277\253\351\200\237\345\205\245\351\227\250.html" "b/categories/tools/2021/03/10/Quartz\345\277\253\351\200\237\345\205\245\351\227\250.html" index ddc91aaea9..501236a96a 100644 --- "a/categories/tools/2021/03/10/Quartz\345\277\253\351\200\237\345\205\245\351\227\250.html" +++ "b/categories/tools/2021/03/10/Quartz\345\277\253\351\200\237\345\205\245\351\227\250.html" @@ -327,7 +327,7 @@ } }

    参考资料

    [1]Quartz 官网:http://www.quartz-scheduler.org/

    [2]Quartz GitHub地址:https://github.com/quartz-scheduler/quartz

    [3]MaTools 在线Cron表达式生成器:https://www.matools.com/cron

    后记

    C: 好了,Quartz 定时器的入门介绍到这儿就结束了。入门介绍中,咱们仅仅是介绍了一下 Quartz 的 RAMJobStore 模式,即 Quartz 将 Trigger 和 Job 存储在内存中,内存中存取自然是很快的,但缺陷就是内存无法持久化存储。

    所以, Quartz 还有一种 JDBC JobStore 模式,即 Quartz 利用 JDBC 将 Trigger 和 Job 存储到数据库中,想要实现 Quartz 的集群也得先完成这一步进阶。

    当然了,Java 中还有很多定时器的实现方案。例如:java.util.Timer、ScheduledThreadPoolExecutor(基于线程池设计的定时任务类)、延时队列等,有兴趣可以先去了解了解。不然就等后面,笔者有时间再介绍吧。

    - + \ No newline at end of file diff --git a/categories/tools/index.html b/categories/tools/index.html index 09b7fc0f6f..34b8496b5f 100644 --- a/categories/tools/index.html +++ b/categories/tools/index.html @@ -55,7 +55,7 @@
    目录

    工具四海谈

    笔者说

    “工欲善其事,必先利其器。” 在应用程序开发的过程中,会使用到大大小小的工具组件,正因为它们的存在,我们的程序开发才会变得高效而便捷。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/01-\345\274\200\345\217\221\347\216\257\345\242\203\346\220\255\345\273\272.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/01-\345\274\200\345\217\221\347\216\257\345\242\203\346\220\255\345\273\272.html" index c50902e922..36d0f74cae 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/01-\345\274\200\345\217\221\347\216\257\345\242\203\346\220\255\345\273\272.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/01-\345\274\200\345\217\221\347\216\257\345\242\203\346\220\255\345\273\272.html" @@ -54,7 +54,7 @@
    目录

    开发环境搭建

    前言

    C: 上篇的介绍是否能让你对 Java 语言有一个初步的认识呢?认识完后,大家可能着急想上手编程了吧?但就像你要去游泳,也得先找到一个泳池?所以还是先耐下性子,听笔者说,在正式开发一个 Java 程序前,我们首先应该在计算机中,准备好对应的开发环境,Java 语言所需要的开发环境是 JDK / JRE。

    这是万里长征的第一步,搭好 Java 基础开发环境是 Java 系开发者必须掌握的技能,所以笔者建议你,收藏好本篇教程,JDK 多安装个几遍,它又不是流氓软件,不影响(卸载不残留,重装如新装)。

    202010011219728

    JDK和JRE的概念

    首先我们介绍一下我们要安装的 JDK / JRE 的概念。

    JDK 的全称是 Java Development Kit,即 Java 开发工具包,是 Sun 公司提供的一套用于开发 Java 应用程序的开发包,它提供了编译、运行 Java 程序所需的各种工具和资源,包括 Java 编译器、Java 运行时环境(JRE),以及常用的 Java类库 等。

    JRE,全称 Java Runtime Environment ,Java 运行时环境。它是运行 Java 程序的必须条件。如果只是运行Java 程序,可以只安装 JRE,无需安装 JDK

    笔者说

    在业内,一般都是直接安装 JDK,因为 JDK 内置了一个 JRE,我们亦是如此。

    JDK的选择

    选择谁家的?

    了解完 JDK 概念之后,我们还要了解下目前 JDK 的现状。Sun 公司当初开发了 Java 语言,作为 Java 语言的开发工具包, JDK 在发展中被 Sun 公司分化为了两大分支。( 可延伸阅读 Java 终于开源了,采用GPLv2授权协议

    • Open JDK ,开源(源代码公开)版本,以 GPL V2(General Public License)协议的形式开源
    • Sun JDK ,使用 JRL(Java Research License,Java 研究授权协议)发布。

    笔者说

    GPL 协议,在开源协议里被称为"病毒"协议,只要是基于 GPL 协议 开源 的代码来开发,那么这项目也必须开源。

    JRL 协议,是 Sun 公司自己搞出来的协议,理解起来就是 Sun 公司公开代码,但是代码的所有权完全归它自己所有,你们能看。

    不过上述协议对我们使用 JDK 没有什么影响,它影响的是那些想改动 JDK 或基于 JDK 代码二次开发的个人或公司群体,我们又不动 JDK 代码。

    其实两个分支版本,在发展中有很大部分的相同代码,不过Open JDK不如 Sun JDK 完整是肯定的(缺少一些特性API),且一部分代码由于产权等原因无法授权给 Open JDK 使用,便在 Open JDK 中替换为没有产权问题的代码。

    很多大公司为了避免版权问题,都在使用基于 Open JDK 开发或自主开发的 JDK 版本,例如亚马逊的 Corretto、阿里巴巴的 Dragonwell、华为的毕昇、腾讯的 Kona等(咱们国内今年井喷式开源 JDK)。

    另外我们都知道,Sun (升阳公司)在2009年被 Oracle(甲骨文公司)收购了,Java 相关业务及版权也就归Oracle 所有。后续的 JDK 更新当然也就由 Oracle 负责了,但是 Oracle 在行业内有一个"不太好"的名声,"什么都要钱,什么都死贵"(实际上,商业公司的本质就是盈利,Sun 公司当初还没做到怎么盈利就没了,Oracle 后面继续做这件事也无可厚非)。

    在2009年到2019年期间,Oracle 没有做什么收费的大动作,但是这种情况在2019年1月1日出现了点变化。Oracle 宣布从2019年1月1日起,Oracle JDK 8 的后续更新将需要收费。Oracle JDK 88u2118u212更新,开始把许可协议从 BCL 换成了 OTN,这就意味着,你不能在生产环境使用这类版本了。

    Oracle 采用的许可协议介绍

    BCL协议,即Oracle Binary Code License Agreement,协议规定你可以使用JDK,但是不能进行修改(和上文的JRL类似),私用和商用都可以,但是JDK中的某些商业特性,是需要付费才可以使用的。

    OTN协议,即Oracle Technology Network License Agreement,目前新发布的JDK用的都是这个协议,可以私用,商用需要付费。[1]

    一石激起千层浪,本来就担心的事终于发生了,很多公司更是开始进行 JDK 版本转移和考虑以后的选择。

    下图是2020年初,Jrebel 在 Java 生态报告中,对 JDK 选择的调查结果(中国内也差不多,仅供参考)。根据结果表示,Oracle JDK(Sun JDK)Oracle Open JDK(Sun Open JDK)还是占据比较大的市场地位,但AdoptOpenJDK的占有率也在迅速提升中。

    我们现在学习选择用 Oracle JDK 就可以了,公司内就看公司的架构师或领导想法了。[可延伸阅读,了解更多的 JDK 发行版]

    202010021221992

    选择哪个版本?

    每个版本的对应 Open JDK 更新也不是无限期,是有支持期限的。Oracle JDK 8 还有个人版、商业版。这些事其实还挺头疼的,不过我们现在学习用 Oracle JDK 没有问题,进公司到时候就"入乡随俗"吧。

    选定好发行版之后,那我们用第几版本呢?下面是 Jrebel 的报告,其中很明显是 JDK 8 应用最广。虽然截止笔者调整教程今天,JDK 已经快要发布到了 JDK 16,但是公司追求的是稳定,所以没有太大更新或修复的情况,一般升级就非常慢,你想想 Windows 7 到 Windows 10 的用户升级之路就理解了。另外 JDK 9、JDK 10 都没人用,是因为它们都是过渡版本,类似于 Windows8 一样,不是长期支持(维护)版本。

    202010021222666

    JDK下载

    既然我们选择了 Oralce JDK 8,那就前往Oracle官网下载吧。

    1.打开下方的链接,或者自行百度搜索 JDK,找到类似下方页面。

    202010021222777

    2.下拉到页面最下方,找到Java Archive点击进入JDK历史版本存档页面。

    202010021222888

    你看 Java SE 8 分为了两个链接,8u211及之后 (收费)和8u202及之前(免费)。

    202010021223666

    3.点击 Java SE 8(8u2020 and earlier) 进入下载页面,然后选择你所需的平台版本。大多数同学应该用的都是 Windows 64 位的系统,选择下方箭头指示的版本即可。

    202010021223777

    记得勾选 卖身协议

    202010021224888

    Oracle 现在要求下载 JDK 必须先登录,没有帐号的同学,自己先注册一个吧。网络是真慢!忍忍!

    202010021224999

    4.终于下载好了,笔者家里开的热点网络,太慢了。

    202010021225560

    JDK安装

    下载好了,开始安装 JDK 吧,和安装 QQ 等软件一样,而且它不是流氓软件,不会静默给你下载一个"全家桶"。

    1.双击程序安装包,开始进行 JDK 安装,点击下一步。

    202010021225676

    2.点击更改,更改 JDK 的安装位置。

    笔者说

    为了防止出现,你自己安装的软件自己都找不到在哪儿这种问题,我们统一安装位置,任选一个磁盘,在其下新建一个 develop 的文件夹,用于以后安装所有开发软件。笔者演示时将 develop 文件夹放在了 d 盘下。

    202010021226600

    在弹出的更改安装目录对话框中,只需要修改前面的盘符 d: 和文件夹 develop,后面的子文件夹 Java\jdk1.8.0_xxx\ 不需要修改,然后点击确定。如果文件夹不存在,安装时会自动创建。

    笔者说

    安装路径不要出现空格,中文,特殊符号等!

    202010021226999

    这个时候程序将要安装的位置已经更改,点击下一步即可开始安装。

    202010021227666

    关于我们安装JDK时到底安装了些什么?

    我们选中第一个 开发工具 时,右侧给出了提示,这是安装的 JDK。它是最主要的,甚至我们可以说只需要有它就可以。

    202010021227777

    当我们选中第二个 源代码 时右侧给出提示,这是 Java 8 的源代码,因为 Java 是公开源代码的。

    202010021227888

    当我选中第三个 公共JRE 时,右边给出提示,这是一个独立的 JRE,我们可以不用安装。不过一般情况我们都选择安装,目的是为了以后如果有一些 Java 程序想单独运行,那么必须配套一个 JRE,到那时候就可以用上了。

    202010021228963

    比如下方是做支付宝第三方支付支持时,支付宝官方给提供的一个做签名校验的 Java 程序。后缀名为 .jar 的是 Java 程序,但是如果想运行此程序就必须依赖上方的一个独立 jre,我们刚才安装选择界面看到的就是它。(支付宝官方下载下来的这工具,就给你带着这个 JRE )

    202010021228999

    4.等待安装,这步只是在安装 JDK

    202010021229777

    安装完 JDK 后,会弹出一个提示框,提示我们安装的 JDK 版本不受到收费影响,点确定即可。

    202010021232169

    5.因为刚才我们没有放弃独立 JRE 的安装,所以现在开始安装它,自己更改好安装路径。最好类似我下方示例,然后点击下一步。

    202010021232222

    等待安装。

    202010021232586

    点击关闭,即完成安装。

    202010021233888

    安装完成后,你的桌面不会出现任何图标,不用大惊小怪,JDK 是开发环境,不是 QQ 这类软件。

    JDK 的安装目录如下。

    笔者说

    刚才之所以说可以不安装那个独立 JRE,因为 JDK 本身自己就自带一个 JRE,为什么JDK 会自带一个?我们就不讨论 JDK 内自带的 Java 程序,就说我们用 JDK 开发 Java 程序,开发好后也需要进行测试运行啊,所以自然需要这 JRE 了。

    202010021234766

    独立 JRE 的安装目录如下。

    202010021234888

    安装完之后,我们想测试一下 JDK 是否安装成功,可以运行 JDK 安装目录下 bin 目录内的 java.exe 程序。如果你看不到 .exe,记得自行开启下计算机的扩展名显示。

    Windows 中我们习惯双击运行程序,但是却发现 java.exe 双击后会弹出一个黑窗口一闪而过。这是因为这种程序,它们需要在特别的系统内运行,比如说我们的 DOS 系统,下面就和笔者去学一下基本的 DOS 使用吧。

    202010021234999

    DOS系统

    什么是DOS?

    DOS 是什么呢?它的全称是 Disk Operating System ,即磁盘操作系统。简单点说,你看过的电影里,黑客们是不是在计算机的一个黑窗口中"运指如飞"?这个所谓的黑窗口不是 DOS 那就是 Linux 系。

    实际上 DOS 它就是早期主流的计算机操作系统,后来 Windows 等主打可视化的系统出现,才让计算机逐渐摆脱专业的概念,变得"平民化",走入千家万家。之前之所以专业化,就是因为这个系统需要通过命令来进行计算机操作,而不能使用鼠标点来点去,所以非专业人士去背命令和习惯这使用方式,简直"太难"了。

    202010021235666

    进入DOS系统

    在Windows 系统任何位置,可以通过按下 Windows 键 + r 键,在左下角弹出的运行窗口输入 cmd 然后回车,就可以弹出 DOS 命令行。

    202010021235777

    另外,还可以通过在 开始菜单 中直接搜索 cmd,然后 右键以管理员身份运行 的方式打开。

    202010021235888

    进入了 DOS 命令行。要求左上方有管理员标识(如果没有此标识,你创建文件等都没有权限,有些命令甚至提示不存在)。当你是 Windows 10 系统,那么很可能没有,因为 Windows 10 对于权限的把控比较严格,所以你可以采取上方的第二种方法进入 DOS 命令行。

    202010021235999

    进入了 DOS 命令行,先认识下组成,前部分是当前你在 DOS 系统所处的路径(当前目录/文件夹,目录就是文件夹的意思,之后不再解释),后部分就是可以输入命令的位置。

    202010021236761

    上方的路径,等价于你在 Windows 系统中进入了如下位置。

    202010021236888

    DOS常用命令

    查看列表

    在上图中,如果我们在 Windows 系统中进入了某个路径,可以很直观的看到当前路径下的所有文件和文件夹。那么在 DOS 中如何实现这一目的呢?

    输入 dir 命令,即可列出当前所处位置的文件和文件夹列表,如下图所示。

    202010021236999

    切换目录

    那如果不想待在默认的路径了,想切换到其它位置。

    • 相同磁盘的目录切换,直接通过 cd 目录路径 来切换。(这个路径必须存在,不然切换不过去)

      例如:我想切换到当前目录下的 Documents 目录。

      202010021237281

    • 不同磁盘的目录切换,先通过 盘符: 来切换磁盘,然后 cd 目录路径 (注意 cd 后有空格)再切换到对应位置。

      例如:我想切换到刚才 JDK 的安装目录。

    笔者说

    如果路径长,在输入的时候,还可以通过 Tab 键来进行内容补全。例如下方的输入,输入完 De 就可以按一下 Tab 键快速补全。因为 DOS 会自动识别所在目录下的内容名字,如果能匹配到就可以快速补全,当然如果有多个 De 打头的内容,那就尽量输入多一些字母后再按 Tab ,这样就更精准。

    202010021237777

    还有一些特别的路径切换,比如返回上一级目录。在 Windows 系统中,鼠标点一下返回键就可以了,在 DOS中,可以通过 cd .. 命令来切换。... 是每个目录下都存在的两个隐藏文件夹,它们一个代表上一级目录,一个代表当前目录。

    202010021237888

    202010021238777

    还有在磁盘比较深的路径时,可以输入 cd / 来快速回到磁盘根目录下。

    202010021238999

    运行程序

    Windows 中如果想运行程序,我们都是双击程序快捷方式或程序启动文件。而在 DOS 中,如果我们想要运行程序,只需要输入程序启动文件路径,然后回车即可。

    例如:我想运行钉钉程序,我知道它的启动程序地址,那么就可以利用 Tab 快速提示着来输入好地址。下图的 "" 是按 Tab 自动生成的,DOS 里为了防止空格产生的影响,可以加 "" 进行包裹,表示一个整体。

    202010021239111

    这个路径实在太长了,如果在 DOS 中,使用过了且没关闭 DOS 窗口的情况下,还需要使用时,建议按 方向键,翻一翻历史命令。

    测试JDK是否安装成功

    OK,掌握了 DOS 基本使用,这时候我们再来通过它运行下 java.exe,输入 java.exe 路径太长了,我们可以偷点懒。

    先通过 Windows 找到 java.exe,然后在地址栏输入 cmd,回车后就可以快速进入程序所在的位置了。

    202010021240572

    202010021241888

    然后就可以运行 java.exe 了,后面追加一个 -version 可以用来查看 JDK 的版本,如果出现下方所示内容,说明 JDK 的安装是完全正常的。

    笔者说

    DOS 中可以省略 exe 之类的后缀

    202010021241999

    环境变量

    概述

    在刚才的内容搞定后,其实我们的 Java 开发环境已经搭建完了,我们之后开发 Java 程序会一直使用刚才的java.exe 程序。不过现在使用还是挺麻烦的,每次都要在 DOS 中先找到程序或输入程序路径才能运行,有没有什么办法可以在 DOS 任意目录使用 java.exe 呢?

    看看百度百科了解一下环境变量吧,其实环境变量就是操作系统里存储的一些参数或关键值,每个在操作系统里运行的程序都可以获取到这些存储的内容。(后面我们学到变量这一程序概念时,就可以更好的理解它的作用了,到时候记得回来再看看)

    202010021242277

    找到环境变量设置

    开始菜单 中搜索 环境变量,点击 编辑系统环境变量,打开 系统属性 对话框。

    202010021243229

    高级 选项卡中,点击 环境变量 就可以进入修改环境变量的对话框。

    202010021243777

    202010021243888

    path环境变量

    其中 path 环境变量就是用来存储路径列表的,里面存储了一个个的路径。当我们在 DOS 命令行中直接输入程序的名字然后回车,这时候 DOS 会先在当前目录下搜索该文件,若找到则运行之,若找不到该文件,则根据 path 环境变量所设置的路径列表,顺序逐条地搜索这些路径下是否有该程序,有的话也能运行。

    这就是我们现在需要的,可以有效解决我们为了运行 java.exe 而很麻烦的输入路径等,一劳永逸。有些同学还把一些游戏启动程序存到了 path 环境变量。

    配置JAVA_HOME

    接下来就将 java.exe 的程序目录存储到 path 环境变量吧。

    1.点击 系统变量 下的 新建,在弹出 新建系统变量 窗口后,将变量值设为 JDK 安装路径(bin 目录上一级),变量名设为 JAVA_HOME (之所以叫这名,是因为Maven、Tomcat等日后所用开发程序大多会使用到此环境变量),所以名称不允许修改。

    202010021245777

    2.上方存储的环境变量还不完整,并且没添加到 path 环境变量。所以我们需要在 path 环境变量中再做些处理。

    点击系统变量中的 path 环境变量,然后点击 编辑,删除其中 Oracle 默认生成的一个目录配置(JDK 1.8之后就开始自动加上了,但是这个地址对我们用处不大,删掉)。

    202010021245888

    点击 新增 ,添加一条 %JAVA_HOME%\bin 变量,%JAVA_HOME% 表示引用 JAVA_HOME 环境变量的值,这一条变量等价于在 path 中添加了 D:\Develop\Java\jdk1.8.0_202\bin

    202010021245999

    笔者说

    Win7系统的 path 环境变量是全部在一起的,而不是像 Win10 这样一条条很清晰。自己去新加入一条:%JAVA_HOME%\bin;(结尾这一定要用英文;来分隔其他的环境变量啊!)。

    还有我们在配置 Java 安装路径的时候,需要格外注意不要将之前的还有一些系统的 path 配置删除,也不要写错。(不要在蓝色选中状态时直接输入,会全部替换的!!!)否则有很多系统命令就没法在 DOS 中便捷愉快的使用了。

    202010021246666

    测试效果

    配置好 path 环境变量之后,关闭所有的 DOS 窗口。再重新打开 DOS 后,输入 java -version ,我们看到和之前一样的效果,而且我们不用在输入 java.exe 冗长的路径了!

    202010021246777

    参考文献

    [1]闷瓜蛋子. Oracle如何对JDK收费[EB/OL]. https://zhuanlan.zhihu.com/p/64731331. 2019-06-18

    后记

    JDK 的安装环节也就介绍到这。补充了一些基础内容,比较杂的感觉,但实际是顺序流程的学习,好好看看本篇文章大纲!每个步骤一定要实践一下!加油!有问题可以邮箱或订阅号联系笔者。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/02-\347\254\254\344\270\200\344\270\252Java\347\250\213\345\272\217.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/02-\347\254\254\344\270\200\344\270\252Java\347\250\213\345\272\217.html" index c28af18179..12ed0d57c0 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/02-\347\254\254\344\270\200\344\270\252Java\347\250\213\345\272\217.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/02-\347\254\254\344\270\200\344\270\252Java\347\250\213\345\272\217.html" @@ -80,7 +80,7 @@ System.out.println("Hello World!!!"); } }

    202010031926318

    跨平台原理

    在《Java概述》中,笔者讲 Java 的能力时就埋了一个伏笔,Java 程序是跨平台的,何谓跨平台?笔者给你举个非跨平台的程序例子,下图是百度网盘客户端程序的下载页面,它为了能够运行在不同的平台(系统)上,开发了6套系统(其实是7套,还有一套网页版)。看到这其实你就应该能明白为什么跨平台是一个非常优秀的能力了。

    202010031926870

    202010031927096

    而经过刚才编写完第一个 Java 程序之后,我们思考一下 Java 为什么能跨平台?其实在开发步骤中就能找到原因。编译!编译会将源代码转变为字节码文件(伪二进制),而这伪二进制内容未来运行时是运行在 JVM(Java Virtual Machine)上的,换而言之,其是因为 JVM 能跨平台安装,所以 Java 才能实现跨平台

    由此,Java 程序员就可以不用考虑所写的程序要在哪里运行了,反正都是在 JVM 虚拟机上运行的,JVM 会负责将其变成相应平台的机器语言,而这个转变并不是程序员应该关心的。后续的很多优秀程序设计都采用了该思想。

    202010031927359

    《Java虚拟机的分析与研究》

    Java虚拟机有自己完善的硬件架构,如处理器、堆栈等,还具有相应的指令系统。

    Java虚拟机本质上就是一个程序,当它在命令行上启动的时候,就开始执行保存在某字节码文件中的指令。Java语言的可移植性正是建立在Java虚拟机的基础上。任何平台只要装有针对于该平台的Java虚拟机,字节码文件(.class)就可以在该平台上运行。这就是“一次编译,多次运行”。

    Java虚拟机不仅是一种跨平台的软件,而且是一种新的网络计算平台。该平台包括许多相关的技术,如符合开放接口标准的各种API、优化技术等。Java技术使同一种应用可以运行在不同的平台上。Java平台可分为两部分,即Java虚拟机(Java virtual machine,JVM)和Java API类库。[1]

    目前先了解到这种程度即可,JVM 深入学习是未来提升 "Java 内功" 的重要一步,但它不太适合刚学习的你。

    编译和反编译

    从刚才的学习中,我们清楚了编译是将源代码文件转换为了字节码文件,这字节码文件也是以后我们运行所需的。如果有一天,你的老板给你了一堆字节码文件,让你去借鉴一下内容(源代码),你当然知道字节码文件里是一堆乱码了,所以随着而来的,我们需要它再转换为源代码文件,这就是反编译,我们可以借助工具来更快,更好的批量处理。

    编译: 将源文件(.java)转换成字节码文件(.class)的过程称为编译。
    反编译: 将字节码文件(.class)转换回源文件(.java)的过程称为反编译。(常用有Jad、FrontEnd、jd-gui)

    此处仅仅演示利用 Jad 来将 HelloWorld.class 进行反编译,类似的工具还有 jd-gui 等。

    202010031927876

    202010031927888

    202010031928091

    答题环节

    输出基本信息

    需求:逐行输出个人基本信息。

    提示:个人基本信息可包括:姓名、年龄、性别、身高、体重、婚否等

    参考文献

    [1]顾玮. Java虚拟机的分析与研究[J]. 办公自动化,2017,22(9):35-36,11

    后记

    今天这篇感觉怎么样?概念是否清楚了?语法是否记住了?万事开头难,加油啊同学!

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/03-\345\210\235\350\257\206Eclipse.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/03-\345\210\235\350\257\206Eclipse.html" index 59ab919d81..8a852cefeb 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/03-\345\210\235\350\257\206Eclipse.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/03-\345\210\235\350\257\206Eclipse.html" @@ -54,7 +54,7 @@
    目录

    初识Eclipse

    前言

    C: 古语有云:"工欲善其事必先利其器"。我们在上一篇开始编写第一个 Java 程序,体验过了一些单词大小写引发的"磕磕绊绊",还体验了来自中文输入法的深深"恶意"。

    那么我们对它们就没治了吗?只能到最后运行或单独调试才能知道错误吗?实际上对于这种语法型错误,我们可以使用专业的工具,在代码编写过程中随时解决掉,这种专业工具被称为:IDE

    今天笔者将带着你领略一款成熟且有魅力的 Java 系 IDE。

    202010042056777

    IDE(集成开发环境)

    IDE概述

    在前言中,笔者已经简单的说了一下 IDE 的能力,它们除了可以有效解决你的上述问题,还能让你编译和运行程序更便捷(提升开发效率),尤其使用上快捷键之后,可能鼠标都将"失业"!但是前期,笔者建议你,不要着急使用 IDE 的快捷键,先练练打字速度和完整语法吧。

    202010042057777

    主流IDE介绍

    我们现在是学习 Java 语言的,所以自然要选择适合 Java 开发的 IDE。目前业内主流的 Java IDE 有这么几个。

    1. Eclipse

      开源免费的 Java IDE,由 Eclipse 基金会负责维护,为各种编程语言都开发了对应的版本或插件。在笔者看来,它的软件体积和使用方式比较适合初期和入门的编程人员。

      202010042058590

    2. IntelliJ IDEA

      收费但很多"白嫖学习党"在用的 Java IDE,它所属的公司 JetBrains 也开发了适配各种编程语言的 IDE 版本。例如:适合前端开发的 WebStorm,适合 PHP 开发的 PhpStorm,适合Python 开发的 PyCharm 等,同样都是"价格不菲"。所以在正版收费和破解学习的斗争上,国内开发者们还需要走很远很远。

    3. MyEclipse

      收费但很多"传统公司"还在用的 Java IDE ,这个软件看名字就知道和 Eclipse 脱不了关系,的确是这样的。它出身于 Eclipse社区,你可以理解为它是 Genuitec 公司为 Java EE 开发者们开发的 VIP 版本,对 Java EE 支持比较友好。其实学会了 Eclipse 之后,MyEclipse 基本就差不多了。

      202010042059387

    4. VSCode(Visual Studio Code)

      免费,微软大厂出品,本质上是一个编辑器,不算是 IDE。但是,国外用的挺多,需要自己安装插件进行配置才能支持更多的功能,对于新手来说还是比较麻烦的。

    笔者说

    关于 IDE 具体哪个好,笔者最后委婉一下:所处学习阶段,"经费",个人习惯、公司团队等决定了使用哪一个,笔者个人只是从市场行情来带大家选择性学习,没有引起"IDE圣战"的意思。

    没落的王族IDE

    在程序开发过程中,得心应手的 IDE 永远令人着迷。在众多 Java IDE 中,如果单纯从初期学习建议和情怀角度评论的话,笔者更喜欢 Eclipse免费 ,扩展性良好,初期使用体验不错,比较简单,这些都是笔者推荐给初期开发者的理由。

    虽然近两年在中高级开发中有所没落,但是不妨碍我们在初期学习一下。

    Eclipse概述

    好的,我们再来回顾一下 Eclipse ,刚才惊鸿一瞥可能没留下太多印象。Eclipse 是一个开源免费的 Java IDE,由 Eclipse 基金会负责维护,为各种编程语言都开发了对应的版本或插件。在笔者看来,它的软件体积和使用方式比较适合初期和入门的编程人员。

    202010042059697

    在 2020 年年初的时候,Jrebel 发布了 《2020年 Java 技术报告》,从来自全球 Java 开发专业人员的近 400 份回复中对 Java 技术生态进行了统计分析。其中在 IDE 的使用分析报告部分,24% 的人使用 Eclipse,仅次于 IntelliJ IDEA

    202010042059777

    PYPLTOP IDE 热度排行榜上,Eclipse 则常年霸占第二。(其实也和 Eclipse 适配了很多编程语言版本,名字都叫 Eclipse 有关)。

    202010042059888

    这些最新的数据看起来也还可以,实际上在更早前,数据还要更加可观。只不过在2017年左右开始,JetBrains 家的产品在国内开始风靡。

    例如:随着 Spring Boot 框架等现代开发技术的兴起,JetBrains 适配 Java 开发的 IntelliJ IDEA 产品以更加方便快捷的优势,快速抢占了 Eclipse 的市场,Eclipse 的使用者们纷纷"投敌"(但是 Eclipse 在现代开发方式中表现不争气,能咋办?很多程序员都是从 Eclipse 跳到IDEA,然后回不去了)。下面是近期 IntelliJ IDEAEclipse 的话题讨论,管中窥豹,可见一斑。

    202010042059936

    好了,简单说这些,就是让你了解一下现状,见见"世面",不至于懵头懵脑的。

    本篇毕竟是 Eclipse 的主场,笔者也不再过多给你介绍其他 IDE 产品。想知道更多的,关于 Eclipse 在不同语言内的生态地位对比,自己度娘即可。

    Eclipse下载

    接下来我们准备下载 EclipseEclipse 官网有两种下载方式,一种是 Installer(exe 安装包,引导型安装),另一种是 Package (zip 包,解压就可以使用,类似于绿色软件)。

    笔者个人建议以 Package 形式下载,点击下载Eclipse Zip版

    下载前一定要牢记好三个注意:

    1. 注意要下载的 Eclipse 与安装的 JDK 版本是否契合

      可查看 Eclipse各版本JDK要求

      Eclipse版本首发时间JDK版本
      Eclipse 4.5 (Mars火星)2015年6月24日1.7
      Eclipse 4.6 (Neon霓虹灯)2016年6月22日1.8
      Eclipse 4.7 (Oxygen氧气)2017年6月28日1.8及以上,1.9建议选择4.7.1a 及以上
      Eclipse 4.8 (Photon光子)2018年6月27日1.8及以上
      Eclipse 4.9 (2018-09)2018年9月19日1.8及以上
      ..............................
      Eclipse 4.15 (2020-03)2020年3月18日1.8及以上,不再支持32位JVM
    2. 注意要下载的 Eclipse 与安装的 JDK 位数是否契合(要么都是32位,要么都是64位)

    3. 注意要下载的 Eclipse 与你的电脑系统和位数是否契合

    我们现在用的 JDK 8,下载当前的最新版 Eclipse(2020-03版本)就可以了。

    笔者说

    时间在更替,笔者指的最新版未来不一定是现在这个,但是笔者仅在变动比较大时,会再进行内容更新,所以选择你打开时的最新版即可。

    202010042103135

    在下载确认页面,我们可以点击 Select Another Mirror 选择其他的镜像源,可以选择国内的镜像源,不然下载太慢了。

    202010042103962

    点击后进入 Eclipse 捐献页面,国外盛行捐赠文化,用来支持这些非盈利组织。不打算捐赠就在页面等一会儿,这个页面按理应该会自动开始下载或弹出下载提示,如果没有开始,你直接点击下图的 click here 也可以开始下载。

    202010042104888

    等待一会儿,一个 Eclipsezip 安装包就下载好了。

    202010042105575

    Eclipse安装

    笔者刚才的下载方式,下载的就是 zip 格式的压缩包。它就像你平时下载的一些破解版绿色软件一样,不需要双击 .exe 安装程序进行引导安装,只需要解压就可以使用。

    右键点击压缩包,寻找个位置解压,笔者还是希望你将其放到你统一的开发软件安装目录(例如:笔者之前建议的 develop 文件夹)。

    202010042105769

    去掉勾选,否则提取的内容额外带一个压缩包同名文件夹。

    202010042106033

    解压后,找到解压的位置。其中 .exe 结尾的就是启动程序。为了方便以后快速打开,可以右键在弹出的菜单中将其发送到桌面快捷方式。

    202010042106083

    然后我们双击这个 .exe 程序,或者双击快捷方式就可以打开 Eclipse 了。

    202010042106188

    只要你上面三条下载注意都核查过了,并且 JDKJAVA_HOME 配置按照笔者要求做了,理论上不会出现别的毛病。没配置好 JAVA_HOME 时,下方就是结果。

    笔者说

    如果你是 win 10 还可能会出现一些兼容性问题:我们环境变量明明配置好了,但是还是提示下图,这时候需要你再次去看一下 JAVA_HOME,甚至不用动,去看环境变量然后确定关闭即可,这个问题曾经在部分同学那儿多次出现。

    202010042106777

    打开之后,第一个弹出的窗口会要求你选择或输入一个文件夹地址作为工作空间,文件夹不存在时会自动创建。Eclipse 会使用此工作空间存储你的代码、项目和一些配置。

    注意:如果切换工作空间,那么你的配置等都需要重新设置,不同的工作空间是相互独立的。

    202010042107605

    进入之后,则来到了欢迎界面,点击关闭 Welcome选项卡 进入程序主界面。

    202010042107888

    Eclipse初始配置

    好工具想使用好,也得调一调。

    视图窗口配置

    进入了主界面,自然意味着我们安装成功了。本身我们下载的就是适配 Java EE 开发的 Eclipse,所以打开之后,Eclipse 默认就处于 Java EE 视图下。但是笔者喜欢在 Java 视图操作,所以我们需要切换一下视图。不同的视图模式,一些窗口和菜单显示也不太一样,建议初期先用Java 视图模式。

    202010042108666

    点击 Open Perspective 按钮,在弹出的对话框中,选中 Java,然后点击 Open,则切换到了Java 视图模式。

    202010042108777

    202010042108888

    可以在以后学习使用中,逐渐关闭一些自己用不上的选项卡窗口,也可以随意调整每个选项卡窗口的位置。如果弄乱了,不用怕,直接在右上角视图名上右击,选择 Reset(重置) 即可回到视图窗口初始的状态。

    202010042108999

    另外还可以在 Window 菜单的 Show View 选项中去添加一些自己需要的选项卡窗口。

    例如:我们之后常用的 Console 控制台。

    202010042109666

    最后初步调整后的窗口效果如下,你们后面使用久了,然后按自己习惯调整就好了。

    202010042109777

    字符编码配置

    Eclipse 中默认使用 GBK 作为字符编码,但是为了解决我们之后开发的编码问题,我们要求统一字符编码的配置,将默认的字符编码更改为 UTF-8。因为这种字符编码具有更好的适用性,对于汉语和外语支持都比较好。

    点击 Window > Preferences(首选项),Eclipse 的所有设置基本都在这里。

    202010042109888

    在搜索框输入 workspace,然后选择图示的选项,在右侧的窗口中设置文本文件编码为 Other > UTF-8,然后点击 Apply and Close(应用并关闭)

    202010042109999

    字体配置

    最后一个配置,写代码没有一个合适的字体及大小怎么行?

    再次打开首选项窗口,依次点击 General > Appearance > Colors and Fonts > Basic,然后点击 Basic 里的最后一项 Text Font(文本字体),双击或者点击 Edit(编辑) 进入修改界面。

    202010042110867

    202010042110888

    一般来讲,字体都是使用默认值 Consolas,只是简单改改大小,方便查看而已,选完之后,一直点确定即可应用成功。

    笔者说

    实际上,Eclipse 现在已经支持在文本编辑时,通过 Ctrl +号-号 来调整编辑区的文字大小了,这一步其实可以不用这么麻烦了。

    202010042111057

    另外 JetBrains 为开发者设计了一套字体,据说可以降低眼疲劳,有需要的从 JetBrains 官网下载JetBrains Mono字体安装即可。

    202010042111555

    用Eclipse开发Java程序

    调也跳完了,接下来我们使用 Eclipse 来开发一个 Hello World 程序,看看它比我们第一章是不是要简单一些?

    Eclipse 开发 Java 程序,可以分为4个步骤:

    1. 创建一个 Java 项目(一个复杂的程序肯定需要很多源代码文件,我们以项目为单位来组织这些源文件)
    2. 手动创建 Java 源程序
    3. 编译 Java 源程序(在 Eclipse 中此步骤是自动的)
    4. 运行 Java 程序

    首先,我们打开配置好的 Eclipse,点击 File 菜单,选择 New 子菜单中的 Java Project 来创建一个新的 Java 项目。

    202010042111666

    输入项目名称,然后确认下是否自动指定好了 JRE运行库(JAVA_HOME配置没有问题的话,应该无异常),此项内容很关键,决定是否能正常编写代码和编译运行。

    202010042111777

    下图就是一个创建好的基本 Java 项目。

    202010042111888

    那我们之前编写的源代码这时候要在哪里写?答案是在 src(source) 源码目录下编写。但是别直接就在 src 根目录下创建源文件,笔者要求你先创建一个包组织 Package(现在知道它是分类存放源文件的文件夹就可以了)。

    笔者说

    Package 的命名是由公司域名的倒序组成的,例如:百度公司写com.baidu.xxx,并且包名的单词全部小写。

    右击 src 目录,然后 New 一个 Package,输入一个自定义包名,然后 Finish

    202010042111999

    202010042112666

    创建好包后,然后右击 包名 进行创建类的操作,这一步才是上一篇我们直接写过的东西。

    202010042112777

    202010042112888

    创建好了,是不是和上篇写的一模一样了,但你发现 pulic class xxx 类声明这部分现在是用Eclipse 快速完成的,你不用再写它了,直接写程序入口 main 方法和内容就可以了。

    编写过程中,如果停下来,这时候没写完呢,Eclipse 会提示报错,这很正常,写完并保存后再看还报不报错。

    202010042112999

    一顿火花带闪电的代码敲写,很舒服的把上篇的内容写完了,而且还有高亮及部分回车自动缩进的功能。

    202010042115555

    输入完代码后,上章节我们需要 javac 先编译然后才能用 java 来运行这代码,现在有了Eclipse,直接在代码空白处右击,在弹出的菜单中选择 Run As > Java Application即可运行了(Eclipse帮你自动编译了)。

    202010042115222

    202010042115666

    笔者说

    其实Eclipse还是在按照我们上篇的形式干活,只不过很多东西帮我们归了归类,做了些自动处理。

    你自己打开设定的工作空间地址,你会发现你刚创建的 Java 项目,实际就是一个特殊的多级文件夹。所以也无需担心玩不转 Eclipse,没你想的那么难,慢慢来,熟练就好了。

    202010042115777

    202010042115888

    后记

    到这里,Eclipse 的初步认识就结束了。Eclipse 还支持更改主题,自己有兴趣可以了解一下。其他的配置,未来我们需要的时候,就会进行对应讲解,目前已经够了。

    另外再提示一点:前期很多同学喜欢使用汉化版的软件,但是殊不知,在众多 IDE 中,中文的并不是很多,养成依赖之后尤为可怕,所以有“汉化一时爽,xxxx”一说。

    对于我们来讲,编程语言大多是英文的,如果我们连使用工具都要用中文,不仅显得不专业,而且也浪费了大好的学习英文的机会!

    所以谨记笔者的劝告,切勿汉化。

    202010042115979

    Eclipse 还有一段传闻:据说是当初的 SUN 公司名称缩写是 太阳 的意思,而且 SUN 公司因为Java 的原因真的是如日中天,在互联网行业首屈一指,于是 IBM 希望能出一款产品来盖过太阳的光芒,所以起名为 eclipse(日蚀)

    孰真孰假,不得而知。只是这个将伴随我们很久的开源 IDE,请记得善待它。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/04-\347\250\213\345\272\217\345\222\214\350\256\241\347\256\227\346\234\272\347\232\204\351\202\243\347\202\271\344\272\213\345\204\277.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/04-\347\250\213\345\272\217\345\222\214\350\256\241\347\256\227\346\234\272\347\232\204\351\202\243\347\202\271\344\272\213\345\204\277.html" index 6afdb18195..8e93988a56 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/04-\347\250\213\345\272\217\345\222\214\350\256\241\347\256\227\346\234\272\347\232\204\351\202\243\347\202\271\344\272\213\345\204\277.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/04-\347\250\213\345\272\217\345\222\214\350\256\241\347\256\227\346\234\272\347\232\204\351\202\243\347\202\271\344\272\213\345\204\277.html" @@ -54,7 +54,7 @@
    目录

    程序和计算机的那点事儿

    前言

    C: 在上一篇,我们已经能够很熟练的使用 Eclipse 开发一个入门的 Java 程序,给计算机下达一些简单的指令。虽然它很简陋,但麻雀虽小,五脏俱全,你平时使用的程序们该有的,它也都有。

    按理说呢,我们接下来就要开始学习更多的 Java 指令(语法)了,但笔者担心你的基础还不够,所以还是想给你再加点“料”。

    在我们讲解下一篇 《变量和常量》前,我们先对计算机中的一些基本概念,以及程序在计算机中安装、执行的原理来分析一下。

    计算机三大件

    我们现在使用的计算机,它是由很多的硬件组成的。但是一个程序要安装及运行,我们主要关注 三个 核心的硬件即可,它们分别是:

    CPURAMROM
    中央处理器,负责 处理数据计算,是一块超大规模的集成电路。内存,临时 存储数据(断电之后,数据会消失),速度快,空间小。 (单位价格高)硬盘,永久 存储数据,速度慢,空间大。(单位价格低)
    202010052011277202010052011666202010052012169

    我们可以拿车道的例子来理解它们三者的作用:

    • CPU核心数:车道数量
    • 内存:车道宽度
    • 磁盘IO:车道限速

    202010052013521

    案例分析:QQ程序

    结合着 QQ 程序,我们来捋一下一个程序从安装到运行的原理。

    程序安装的原理

    像我们以后,在开发好了程序之后,如果需要给客户来使用,就要准备好相应的程序包,否则到了客户电脑上,缺少程序所需的环境就无法运行程序了,例如:Java 程序至少需要配套一份 JRE 。

    我们在平时为了使用 QQ 程序,首先要做的就是下载一个对应的安装程序,然后通过安装程序来引导我们或自动将 QQ 的一系列程序文件解压并存储到 硬盘 的指定位置。

    202010052013602

    202010052013767

    202010052015666

    202010052016288

    笔者说

    安装程序/引导安装程序,它们也是电脑程序,但它们诞生的目的是帮助普通用户快速实现程序的环境初始化、程序文件存储等过程的。

    程序执行的原理

    安装好之后,而当要运行一个程序时,首先操作系统会让 CPU 将存储在 硬盘 中的程序文件们读取到 内存 中来,然后由 CPU 执行 内存 中的程序文件/代码来处理数据。

    202010052017669

    笔者说

    每个程序在运行过程中都会在内存中"占据"一块属于自己的空间,而这块空间的大小及内存的总大小也是决定程序是否可以自如的"施展手脚"。

    所以一般我们想要同时运行更多程序而不卡时,都优先考虑到买大内存的计算机。

    当你在双击桌面的 QQ 快捷方式时,快捷方式会链接到对应位置的 QQ 程序,启动开始了。

    202010052017777

    然后 CPU 就会将存储在 硬盘 上的 QQ 程序文件加载到 内存 中,QQ 程序会在 内存 中占据一块自己的内存区域,然后由 CPU 执行 内存 中的 QQ 程序文件/代码,于是就出现了下方的界面。

    202010052018229

    程序内存中的数据管理

    当我们在 QQ 程序中点击过 “记住密码”,并且登录过一次后,我们再次打开 QQ 程序登录界面,会看到 QQ 号及密码直接回显在了输入框中。

    此时其实是在加载 QQ 程序文件到内存后,QQ 程序代码将保存在硬盘上的帐号数据恢复到了内存中,然后将它们再插入到输入框内。

    202010052019576

    那 QQ 这个程序是怎么保存用户的 QQ 号码QQ 密码 的呢?

    1. 在内存中为 QQ 号码QQ 密码 各自分配一块空间
      • 在 QQ 程序结束之前,这两块空间是由 QQ 程序负责管理的,其他任何程序都不允许使用
      • 在 QQ 自己使用完成之前,这两块空间始终都只负责保存 QQ 号码QQ 密码
    2. 另外为了能够方便找到该内存空间,分别使用一个 别名 标记 QQ 号码QQ 密码 在内存中的位置

    202010052019666

    后记

    实际上,在程序内部,为 QQ 号码QQ 密码 在内存中分配的空间就叫做 变量,这也是我们下一篇要介绍的主要内容!跟上别掉队!

    202010052020276

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/05-\345\217\230\351\207\217\345\222\214\345\270\270\351\207\217.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/05-\345\217\230\351\207\217\345\222\214\345\270\270\351\207\217.html" index af6ab1e48e..37600e8e8d 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/05-\345\217\230\351\207\217\345\222\214\345\270\270\351\207\217.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/05-\345\217\230\351\207\217\345\222\214\345\270\270\351\207\217.html" @@ -232,7 +232,7 @@ int mi = 40; mi = (int)mian;

    答题环节

    变量练习

    需求:使用变量分别存储个人基本信息,然后逐行输出个人基本信息。

    提示:个人基本信息可包括:姓名、年龄、性别、身高、体重、婚否等

    需求:使用 Scanner 分别录入个人基本信息、并逐行输出。

    提示:个人基本信息可包括:姓名、年龄、性别、身高、体重、婚否等

    参考文献

    [1]谭浩强. C程序设计(第4版):清华大学出版社,2010.6

    [2]明日科技. Visual Basic从入门到精通:清华大学出版社 ,2017.6

    后记

    无论是学习任何编程语言,变量都是必不可少的一个基础内容,所以,结合上一篇《Java语法 | 程序的那点事儿》好好在脑海中构想一下变量在内存中的使用吧。

    202010061320866

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/06-\345\270\270\347\224\250\347\232\204\350\277\220\347\256\227\347\254\246.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/06-\345\270\270\347\224\250\347\232\204\350\277\220\347\256\227\347\254\246.html" index ab5afad435..f856e2d66a 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/06-\345\270\270\347\224\250\347\232\204\350\277\220\347\256\227\347\254\246.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/06-\345\270\270\347\224\250\347\232\204\350\277\220\347\256\227\347\254\246.html" @@ -236,7 +236,7 @@ System.out.println(min); // 5 int max = (10 < 7) ? 7 : 10; System.out.println(max); // 10

    笔者说

    后期用的还挺多的,用好了它可以有效优化一些代码结构,简化代码。

    运算符优先级

    至于运算符号的优先级,笔者则认为无需记忆,只要记住:想让谁先运行,就给其加小括号即可。而且其实从我们多年的数学习惯来看,加小括号也有利于阅读。

    • 单目运算符包括 !++--,优先级别高
    • 优先级别最低的事赋值运算符
    • 可以通过 () 控制表达式的运算顺序,() 优先级最高
    • 从右向左结合性的只有赋值运算符、三目运算符和单目运算符
    • 算术运算符 > 关系运算符 > 逻辑运算符

    答题环节

    计算BMI

    需求:使用 Scanner 收集身高体重,并计算出相应BMI值是多少?

    提示:BMI的计算公式是:体重(kg) / (身高 * 身高)

    例如:小明的体重是 72 kg, 身高是1.69,那么小明的 BMI 就是:72 / (1.69 * 1.69)

    BMI 判定

    需求:亚洲人的肥胖标准应该是 BMI 在18.5至24.9时为正常水平,根据计算的 BMI,告知使用者身体是否标准。

    标准:您当前的 BMI 符合正常水平!

    不标准:您当前的 BMI 偏离正常水平!

    参考文献

    [1]莫绍强、陈善国. 计算机应用基础教程:中国铁道出版社,2012年:12-13

    [2]钟小莉, 谢旻旻, 李永宁. 文字编码与Unicode编码研究[J]. 经营管理者, 2010(20):364-364.

    后记

    这些符号我们也算初步认识过了,你还 以吗?你的数学基础是否给予你了力量呢?计算机学习不仅要和英语打交道,数学更加重要,不过还好目前笔者更新的都是小白文,对数学基础要求不是太多。

    202010061326166

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/07-\346\216\247\345\210\266\350\257\255\345\217\245\345\222\214\346\265\201\347\250\213\345\233\276.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/07-\346\216\247\345\210\266\350\257\255\345\217\245\345\222\214\346\265\201\347\250\213\345\233\276.html" index 9406494201..6c987e4ccf 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/07-\346\216\247\345\210\266\350\257\255\345\217\245\345\222\214\346\265\201\347\250\213\345\233\276.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/07-\346\216\247\345\210\266\350\257\255\345\217\245\345\222\214\346\265\201\347\250\213\345\233\276.html" @@ -54,7 +54,7 @@
    目录

    控制语句和流程图

    前言

    C: 现实生活中,我们每天都要面对各种选择,有句俗话叫" 选择比努力更重要 ",不同选择所对应的结果千差万别。在程序中编写的代码也是如此,我们上篇学到的关系运算符、逻辑运算符就可以让程序执行不同的选择。

    而且对于初级的应用开发者(码农),未来每天的工作就是用本篇的选择结构写业务逻辑,所以它们学起来也不难,因为经常要使用啊!

    在介绍 Java 中的选择结构语法前,笔者先带你在本篇介绍一下程序中常见的流程控制语句。

    概述

    在前几篇的学习中,我们也写了几行代码,而且也明白编写的代码是自上而下依次执行下来的,编写几行,就会自上而下执行几行。这种"一根筋"的代码被称为 顺序控制语句

    202010071016008

    顺序控制语句

    顺序控制语句 是程序中最简单的流程控制,按照代码执行的先后顺序,依次执行,程序中的大多数代码都是这样执行的。[1]

    null

    选择控制语句

    本篇我们要学习的选择结构就属于 选择控制语句选择控制语句 也被称为分支结构语句,选择结构有特定的语法规则,代码要执行具体的逻辑运算进行判断,逻辑运算的结果有两个或多个(真或假),所以产生了选择,根据不同的选择就会执行不同的代码。[1]

    null

    循环控制语句

    循环控制语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码被称为循环体语句(循环操作)。当反复执行这个循环体时,需要在合适的时候把循环判断条件修改为 false ,从而结束循环,否则循环将一直执行下去,形成死循环。[1]

    null

    流程图

    概述

    你看上方的控制语句,笔者每个都配有一个简单图示,这种图示叫流程图。以后的程序逻辑越来越复杂,我们就可以通过流程图,用图示的方式,来反映出特定主体为了满足特定需求而进行的,有特定逻辑关系的一系列操作过程(程序步骤)。

    什么是流程图?

    流程图是对过程、算法、流程的一种图像表示,在技术设计、交流及商业简报等领域有广泛的应用。通常用一些图框来表示各种类型的操作,在框内写出各个步骤,然后用带箭头的线把它们连接起来,以表示执行的先后顺序。用图形表示算法,直观形象,易于理解。有时候也被称之为输入-输出图。顾名思义,就是用来直观地描述一个工作过程的具体步骤。这种过程既可以是生产线上的工艺流程,也可以是完成一项任务所必需的管理过程。

    一张简明的流程图,不仅能促进产品经理与设计师、开发者的交流,还能帮助我们查漏补缺,避免功能流程、逻辑上出现遗漏,确保流程的完整性。流程图能让思路更清晰、逻辑更清楚,有助于程序的逻辑实现和有效解决实际问题。

    通常,对于任何希望创建流程的人来说,无论创建的是什么用的流程,流程图都是很有用的。它可以帮你:

    1. 设计你产品的交互流程
    2. 确保的你的产品在任何时候都是友好的(甚至包括你原来根本未曾考虑过的故障发生时)
    3. 帮助你整合零散的线框图
    4. 帮助你与不同背景的同事进行沟通:比如引导工程师开发[2]

    图示

    为便于识别,绘制流程图的习惯做法是:

    • 圆角矩形:表示开始与结束
    • 矩形:表示操作步骤、用于普通工作环节
    • 菱形:表示问题判断(审核/审批/评审)环节
    • 平行四边形:表示输入和输出
    • 箭头:代表工作流方向

    202010071027166

    202010071032777

    流程图中有这么多符号,想要更好更快的绘制,我们可以使用一些流程图绘制工具!例如微软的 visio 或是直接使用在线流程图绘制网站:Process On(使用挺顺手,就是容量小,免费用户只能创建9张图,想新建更多,要么充钱要么导出图后删除一些无用的,记得删除后再进入回收站内删除,否则也会占用容量)、还有 亿图 也可以使用。

    202010071032888

    示例

    例如:公司报销的流程,特定主体是员工,特定需求是报销,特定逻辑关系是员工报销过程中的一系列操作。下方是某办公自动化系统的报销业务流程设计图。

    202010061032999

    注意事项

    1. 绘制流程图时,为了提高流程图的逻辑性,应遵循从左到右、从上到下的顺序排列,而且可以在每个元素上用阿拉伯数字进行标注。
    2. 从开始符开始,以结束符结束。开始符号只能出现一次,而结束符号可出现多次。若流程足够清晰,可省略开始、结束符号。
    3. 当各项步骤有选择或决策结果时,需要认真检查,避免出现漏洞,导致流程无法形成闭环。
    4. 处理符号应为单一入口、单一出口。
    5. 连接线不要交叉。
    6. 如果两个同一路径的下的指示箭头应只有一个。
    7. 相同流程图符号大小需要保持一致。
    8. 处理为并行关系,可以放在同一高度。
    9. 必要时应采用标注,以此来清晰地说明流程。
    10. 流程图中,如果有参考其他已经定义的流程,不需重复绘制,直接用已定义流程符号即可。

    参考文献

    [1]文泷Vincent. 流程控制语句—顺序、选择、循环[EB/OL]. https://blog.csdn.net/qq_34236718/article/details/80596376. 2018-06-06

    [2]edraw 亿图. 什么是流程图?看完你就明白了![EB/OL]. https://www.edrawsoft.cn/what-is-flowchart/. 2021-01-08

    后记

    我们即将进入选择结构的学习,意味着我们要开始编写稍微灵活的程序,这时候大家要注意自己的逻辑思维训练。如果无法在脑海中构建一个比较好的空间思维图,那就老实的画画流程图,然后再写程序,绝对是有用的!!!

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/08-if\351\200\211\346\213\251\347\273\223\346\236\204.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/08-if\351\200\211\346\213\251\347\273\223\346\236\204.html" index 2f990c4218..d500a89738 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/08-if\351\200\211\346\213\251\347\273\223\346\236\204.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/08-if\351\200\211\346\213\251\347\273\223\346\236\204.html" @@ -404,7 +404,7 @@ // 个位 int ge = num % 10 % 10;

    后记

    选择结构的使用,一般不会困扰到大家。因为它与我们日常生活是非常贴近的,可读性非常强。当然如果还是不能理解,别忘了流程图哈!

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/09-switch\351\200\211\346\213\251\347\273\223\346\236\204.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/09-switch\351\200\211\346\213\251\347\273\223\346\236\204.html" index 9ff2652635..63ebaa1687 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/09-switch\351\200\211\346\213\251\347\273\223\346\236\204.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/09-switch\351\200\211\346\213\251\347\273\223\346\236\204.html" @@ -150,7 +150,7 @@ }

    上方的代码,如果 case 1 后缺少一个 break,那么输出结果是将会是。

    小杨将会担任《诛仙》女主角!
     小杨将会担任《斗罗大陆》女主角!
    小杨将会担任《诛仙》女主角!
     小杨将会担任《斗罗大陆》女主角!
  • 建议加上一个 default 来进行默认处理。

  • switch 的表达式支持的类型有:int、( shortbytechar 可以自动类型转换为 int),枚举类型(Enum)String(自JDK1.7开始,switch 支持了字符串的等值判断,参考Oracle Java7 RELEASE介绍)。

    202010071312989

    202010071315171

  • switch和if的对比

    到此为止,Java 中的选择结构我们就学习完了,别看语法挺多,论派系的话只有两个,一个是 if,一个是switch,而且 switch多重if 也很相像,理解起来也比较容易了。

    相同点: 都是用来处理多分支条件的结构。

    不同点: switch 只能处理等值条件判断的情况,多重if 选择结构没有 switch 选择结构的限制,特别适合某个变量处于某个连续区间时的情况(范围型判断)。

    switch 从效率方面考虑,是要比 if 选择结构执行快(有兴趣自己百度下执行原理),但是随着硬件的发展,这两者之间的效率差距几乎可以忽略不计。

    笔者说

    其实 Java 近几版本一直在对 switch 进行优化,switch 的使用也更加现代,后面有机会使用其他版本,再给大家开开眼。

    后记

    选择结构出现后,我们就可以把现实生活的业务逻辑,在程序中模拟实现了。这些流程控制语句就像汉语拼音和基本汉字一样基础,好好记忆下语法。

    把文章案例实现一下,千万要动手实现!因为理解和熟练掌握是两回事!就好像你在抖音上看了那么多生活上的教程:叠衣服、弹吉他.....,但你从没练过,那永远是学不会的,千万不要眼高手低。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/10-\345\276\252\347\216\257\347\273\223\346\236\204.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/10-\345\276\252\347\216\257\347\273\223\346\236\204.html" index ff477f3e44..c8302247cf 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/10-\345\276\252\347\216\257\347\273\223\346\236\204.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/10-\345\276\252\347\216\257\347\273\223\346\236\204.html" @@ -446,7 +446,7 @@ System.out.println("登录失败!请重新输入!"); } } while (flag);

    答题环节

    计算和

    需求:使用 while、do-while 以及 for 循环三种编程方式分别实现:计算100以内(包括100)的偶数之和。

    提示:偶数,能够被 2 整除的数字被称为偶数。例如:4 % 2 == 0,4为偶数。

    打印数列

    需求: 使用循环输出 100、95、90、85、.......5。

    输出所有的水仙花数

    需求:使用单层循环实现所有水仙花数的输出。

    提示:什么是水仙花数?

    1. 水仙花数都是三位数

    2. 水仙花数的个位的立方 + 十位立方 + 百位立方 = 水仙花数字本身

    输出星期数

    需求: 从键盘输入一位整数,当输入1 ~ 7 时,输出"星期一" ~ "星期日"。

    • 输入其他数字时,提示用户重新输入。
    • 输入数字0,程序结束

    202010080741825

    参考文献

    [1]张桂珠、张平、陈爱国.Java面向对象程序设计(jdk1.6)第三版:北京邮电大学出版社,2005

    后记

    我想,学完这一篇的同学们一定会开始有些许头疼。因为循环它开始变得有所抽象,简单的几行代码却要表述出成百上千次操作,循环操作越是复杂,那需要的空间思维越是庞大,不过别担心,下一篇,笔者解救你。

    另外,笔者在本篇给相同的需求,反复给出不同的解决方法,这对于初次学习,以及"刻板化"学习的同学会有些难受,因为不知道选择哪个了。

    笔者说

    实际上,生活从来不是一个"刻板化"的步骤,生活中遇到的问题也不总是“刻板化”的来解决,程序亦是如此,千人千面,千题千解。

    笔者给你一个个人建议,根据自身情况,先选择一种最喜欢的解决方法,然后当这种解决方法怎么都无法解决问题的时候,考虑下另一种或更多种。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/11-\345\244\232\351\207\215\345\276\252\347\216\257.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/11-\345\244\232\351\207\215\345\276\252\347\216\257.html" index 1c0773953d..46dec2357b 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/11-\345\244\232\351\207\215\345\276\252\347\216\257.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/11-\345\244\232\351\207\215\345\276\252\347\216\257.html" @@ -232,7 +232,7 @@ // 每行结尾换行 System.out.println(); }

    答题环节

    打印数字等腰三角形

    需求: 使用多重循环根据用户输入的数字,输出如下图形。

    202010081216912

    倒序打印九九乘法表

    需求: 使用多重循环倒序打印九九乘法表。

    202010081217251

    打印菱形

    需求: 使用多重循环输出如下图形。

    202010081217621

    后记

    你就套着思路分析的步骤,塌下心来实现一下,相似的问题一定要回忆起相似的解决方法。别学一个忘一个,下一篇笔者带你学习一种专业工具,这样就可以逐行的看程序执行了。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/12-\347\250\213\345\272\217\350\260\203\350\257\225\345\205\245\351\227\250.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/12-\347\250\213\345\272\217\350\260\203\350\257\225\345\205\245\351\227\250.html" index 499a43fac9..ea01434669 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/12-\347\250\213\345\272\217\350\260\203\350\257\225\345\205\245\351\227\250.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/12-\347\250\213\345\272\217\350\260\203\350\257\225\345\205\245\351\227\250.html" @@ -64,7 +64,7 @@ System.out.println(i); i++; }

    存在问题: 只能输出到4,无法输出5这个数

    202010081755202

    使用步骤

    程序运行嗖嗖的,我们根本无法逐行的观察具体真实的执行过程,Eclipse 的程序调试(debug)又被称为断点调试。所谓断点,就是你希望运行中的程序在哪一行停下来,让你可以逐行进行分析。

    第一步:看故障现象,分析错误,设置断点。

    在你想监测程序运行的开始代码行左侧栏,双击鼠标左键将出现一个断点标志,再双击可以取消断点。

    202010081755352

    第二步:启动调试。

    这时候,我们不再以 run as 运行了,而是右键以 debug as 运行。

    启动时,Eclipse 会弹出一个对话框提示你是否要切换到 debug 模式视图,我们点击 switch 切换过去,debug as 运行后,它会按照正常的执行顺序进行代码执行,直到遇到断点行才停下来。此时这一行代码处于 等待执行 ,还未执行的状态。

    202010081755573

    debug 视图的界面布局如下:

    202010081755606

    第三步:单步运行。

    连续点击 F6 键可以单步运行程序,即逐行执行程序,这时候我们就可以来观察程序运行过程了。

    202010081755712

    第四步:观察变量变化。

    在逐行运行过程中,可以观察右侧变量表来查看变量的变化情况,鼠标直接放在变量名上,也可以直接查看它的当前值。

    选中表达式还可以查看表达式的计算结果。

    202010081755798

    其他调试按钮的使用:

    我们可以按下 F8 ,按下它,会向下快速执行代码行到下个断点才会停住。这样我们就可以只观察想要看到的代码行效果了。

    笔者说

    在运行过程中,随时可以添加断点或取消断点,非常灵活。

    我们也可以按下 Ctrl + F2 随时结束当前的调试。

    202010081756376

    好了,我们最后完整调试一下吧,调试到最后环节时,很容易就发现 i 的值到了5的时候,就无法进入循环了,问题就出在这,改动条件表达式为 i <= 5 就没事了。

    调试完之后,你可以在右侧 断点表 快速清除所有的断点,然后再点击右上角的 Java 视图标志切换回之前的开发模式。

    202010081756666

    参考文献

    [1]百度百科. bug (计算机领域漏洞)[EB/OL]. https://baike.baidu.com/item/bug/3353935. 2020-1-13

    [2]百度学术. The Pragmatic programmer:From journeyman to master[EB/OL]. https://xueshu.baidu.com/usercenter/paper/show?paperid=1971af4403d863660114ff571f6757a5&site=xueshu_se. 2021-1-13

    [3]百度百科. 小黄鸭调试法[EB/OL]. https://baike.baidu.com/item/小黄鸭调试法/16569594. 2021-1-13

    后记

    在本篇中,笔者试着添加了一些动态 gif 图来更好的展现一些操作步骤,希望它们对你有帮助,也希望你能看懂这无声的“对白”。

    本篇也是笔者语言入门系列的第一个程序调试使用,只是入门操作而已,后续随着系列的延续,再补充更多的使用技巧以及其他调试按钮的功能。

    希望你从现在开始好好用用每个 IDE 的程序调试工具,绝对比你遇到问题或思路不畅时,只靠眼睛瞪代码有效。

    202010081756767

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/13-\344\270\200\347\273\264\346\225\260\347\273\204.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/13-\344\270\200\347\273\264\346\225\260\347\273\204.html" index 4a0fee9b53..9b4dee3a22 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/13-\344\270\200\347\273\264\346\225\260\347\273\204.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/13-\344\270\200\347\273\264\346\225\260\347\273\204.html" @@ -386,7 +386,7 @@ } } System.out.println("第" + minStore + "家价格最低,价格为:" + min);

    答题环节

    下方这些题大多来自于互联网,笔者收集汇总在一起,针对性的练习一下。

    求最小值

    需求:获取指定数组中偶数元素值的最小值

    1. 定义一个 int 数组 arr
    2. 键盘录入5个整数,存入数组 arr 中,并且录入之前提示输入的是第几个数字
    3. 获取指定数组 arr 中偶数元素值的最小值,并在控制台打印

    求差值

    需求1:获取到数组中最大值和最小值的差值

    1. 获取键盘录入的5个整数,并存放在 int 数组 arr 中,输入前提示输入的是第几个值。
    2. 分别获取数组中最大值和最小值,并计算差值。
    3. 输出差值。

    需求2:获取数组元素的偶数和与奇数和之差

    1. 获取键盘录入的5个整数,并存放在 int 数组 arr 中,输入前提示输入的是第几个值;

    2. 分别获取数组中元素的偶数和与奇数和;

    3. 输出偶数和与奇数和的差值

    个数统计

    需求1:获取指定数组中元素值为偶数的元素个数

    1. 定义一个整数数组 arr
    2. 获取5个0~50之间(包含0和50)的随机数,并存入 arr
    3. 获取指定数组 arr 中元素值为偶数的元素个数,并打印

    需求2:获取指定数组中大于指定整数的元素个数

    1. 获取键盘录入的5个整数,并存放在 int 数组 arr 中,输入前提示输入的是第几个值

    2. 键盘录入一个需要进行比较的整数 num

    3. 计算数组 arr 中比整数 num 大的元素个数

    查询下标

    需求:查询指定元素值在指定数组中的下标值

    1. 创建 int 数组 arr,arr 包含11, 32,55, 47,55, 79,23
    2. 输入任意一个整数 num
    3. 遍历数组,如果指定数组 arr 中不存在指定整数 num,那么输出 -1,指定数组 arr 中存在多个相同的指定整数 num,那么输出 num 值对应的最大角标值(最后的num对应的角标)

    替换值

    需求:用指定整数替换指定数组中的元素值

    1. 创建 int 数组 arr,数组内包含0-9之间的10个整数

    2. 获取键盘录入的一个整数 num,如果 num 为偶数,则用 num 替换指定数组 arr 中的所有偶数下标的元素值

      如果 num 为奇数,则用 num 替换指定数组 arr 中所有的奇数下标的元素值

    3. 输出替换值之后的数组 arr

    去除值

    需求:去除数组中所有的数值0

    1. 定义一个数组 oldArr,元素为1,3,4,5,0,0,6,6,0,5,4,7,6,7,0,5

    2. 去除所有为 0 的元素值,并存入到一个新的数组 newArr,效果为1,3,4,5,6,6,5,4,7,6,7,5

    3. 分别遍历两个数组

    互换值

    需求:将指定数组元素值的位置前后互换,例如:[11, 32,55, 47,79,23] 置换后的数组元素为:[23, 79, 47, 55, 32, 11]

    1. 定义一个整数数组 arr
    2. 键盘录入5个整数,并存入数组 arr
    3. 定义一个新数组 newArr
    4. 将指定数组 arr 的元素值的位置前后互换,并存储到新数组 newArr 中
    5. 在控制台分别横向打印 arr 和 newArr 的内容

    获取随机值

    需求1:获取指定数组中随机的2个元素值

    1. 定义一个 int 数组 arr
    2. 键盘录入5个整数,存入数组 arr 中,并且录入之前提示输入的是第几个数字
    3. 随机获取数组中的两个元素值并在控制台打印

    需求2:随机获取4个A-Z之间(包含A和Z)的大写字母

    1. 定义一个 char 数组 arr

    2. 生成A-Z之间的26个大写字母,并存入数组 arr 中

    3. 从 arr 数组中获取4个随机大写字母。

    字符串的拼接

    需求:获取指定字符数组中下标为奇数的所有字符,并连接成一个字符串

    1. 创建字符数组,数组包含 26 个大写字符及 0-9 字符
    2. 获取指定字符数组中,下标为奇数的所有字符,并连接成一个字符串
    3. 将字符串打印在控制台上

    参考文献

    [1]彭军、向毅主编.数据结构与算法:人民邮电出版社,2013年

    [2]刘亚东;曲心慧编.C/C++常用算法手册:中国铁道出版社,2017.09:第21页

    [3]冯小圆. 数据结构之数组[EB/OL]. https://www.cnblogs.com/fengxiaoyuan/p/10934399.html. 2019-05-27

    后记

    有了数组这一数据结构之后,多个数据存储及使用变得更加方便,随之而来的就是一些算法题型的解锁,笔者在本篇的作业中汇总了10来道练习题,希望你能通过解决这些问题,来锻炼下自己的逻辑思维。

    做不出来也别着急,你还可以评论求助笔者来给你参考答案。

    202010091355395

    最后别忘了,牢记学习数组的原因:因为前面的数据存储方式,已经不能满足我们日益复杂的需求。而且,数组作为一种典型的,基础的线性表数据结构,有其存储上的优势。

    它在查找方法因为有下标的存在,效率较为不错,所以 适合增删情况较少,查取数据较多的场景 。但它也有不足之处:它只能存储一组定长的具有相同数据类型的数据。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/14-\345\244\232\347\273\264\346\225\260\347\273\204.html" "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/14-\345\244\232\347\273\264\346\225\260\347\273\204.html" index bdf31b0ea0..a53d69ecc8 100644 --- "a/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/14-\345\244\232\347\273\264\346\225\260\347\273\204.html" +++ "b/courses/java/01-Java\350\257\255\346\263\225\345\205\245\351\227\250/14-\345\244\232\347\273\264\346\225\260\347\273\204.html" @@ -372,7 +372,7 @@ int[] arr2 = {10, 9, 11, 7, 8}; int[] newArr = Arrays.copyOf(arr2, arr2.length + 1); System.out.println(Arrays.toString(newArr)); // [10, 9, 11, 7, 8, 0]

    笔者说

    Arrays 工具类还有更多的数组操作方法,等以后需要使用时,笔者再告诉你。或者在未来你有需求时,也可以去查阅 Java 官方的 API 文档,来挑选适合你的数组操作方法。

    答题环节

    统计销售额

    需求:统计一个公司三个销售小组中每个小组的 总销售额 以及 整个公司的销售额 。

    1. 定义一个二维数组,存储三个销售小组的销售额。

    2. 第一小组销售额为{5, 10}万元,第二小组销售额为{10, 20, 30}万元,第三小组销售额为{10, 10, 20, 20}万元。

    3. 计算每个小组的总销售额和整个公司的销售额并输出。

    参考文献

    [1]百度百科. 二维数组[EB/OL]. https://baike.baidu.com/item/二维数组/8168543. 2021.1.17

    后记

    这一篇,咱们还学到了一个 Arrays 工具类,是不是感觉一些数组操作也没那么难了?的确,有了这类工具类的加入,将会更方便我们日常的需求实现。

    但也别忘了自身内功的提升,如果以后没有合适的工具类,难道我们就不能自己写出来了吗?

    下一篇,我们将围绕数组进行一些排序算法学习,毕竟数据结构和算法不分家,算法中又以排序算法最为经典,期待一下吧。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/java/02-Java\351\235\242\345\220\221\345\257\271\350\261\241/01-\347\261\273\345\222\214\345\257\271\350\261\241.html" "b/courses/java/02-Java\351\235\242\345\220\221\345\257\271\350\261\241/01-\347\261\273\345\222\214\345\257\271\350\261\241.html" index 73f2bb7d79..0492d8273b 100644 --- "a/courses/java/02-Java\351\235\242\345\220\221\345\257\271\350\261\241/01-\347\261\273\345\222\214\345\257\271\350\261\241.html" +++ "b/courses/java/02-Java\351\235\242\345\220\221\345\257\271\350\261\241/01-\347\261\273\345\222\214\345\257\271\350\261\241.html" @@ -54,7 +54,7 @@
    目录
    - + \ No newline at end of file diff --git "a/courses/java/03-Java\351\253\230\347\272\247\347\211\271\346\200\247/01-\351\233\206\345\220\210\344\270\216\346\263\233\345\236\213-1.html" "b/courses/java/03-Java\351\253\230\347\272\247\347\211\271\346\200\247/01-\351\233\206\345\220\210\344\270\216\346\263\233\345\236\213-1.html" index 941ae15ccc..57756394f9 100644 --- "a/courses/java/03-Java\351\253\230\347\272\247\347\211\271\346\200\247/01-\351\233\206\345\220\210\344\270\216\346\263\233\345\236\213-1.html" +++ "b/courses/java/03-Java\351\253\230\347\272\247\347\211\271\346\200\247/01-\351\233\206\345\220\210\344\270\216\346\263\233\345\236\213-1.html" @@ -54,7 +54,7 @@
    目录
    - + \ No newline at end of file diff --git "a/courses/java/04-\351\231\204\345\275\225/01-CentOS\345\256\211\350\243\205JDK.html" "b/courses/java/04-\351\231\204\345\275\225/01-CentOS\345\256\211\350\243\205JDK.html" index 02cd05c7bf..69051fc2d4 100644 --- "a/courses/java/04-\351\231\204\345\275\225/01-CentOS\345\256\211\350\243\205JDK.html" +++ "b/courses/java/04-\351\231\204\345\275\225/01-CentOS\345\256\211\350\243\205JDK.html" @@ -114,7 +114,7 @@ Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)
    java version "1.8.0_202"
     Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
     Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)
    - + \ No newline at end of file diff --git a/courses/java/index.html b/courses/java/index.html index 08da07cbc1..08b279253b 100644 --- a/courses/java/index.html +++ b/courses/java/index.html @@ -54,7 +54,7 @@
    目录

    Java基础快速入门

    C: Hi,大家好!从本篇起,笔者将开辟一个新的专栏《Java基础快速入门》,这个专栏初期将涵盖 Java 语法、Java OOP、Java 高级特性三大部分。

    当然了,此类教程在网络上不胜枚举,笔者在以前也是比较推荐大家去看谁谁谁的什么什么文章,但后来发现,这些知识是散布在网络间的,所以分散是第一个大问题;而且由于时间和作者等因素,有些内容至今还没有得到更新,这就导致一个知识的滞后性。

    所以,笔者再开辟此专栏的目的,就是为了继续做好知识更新延续和传播,未来对这些内容还会继续编辑更新,仅仅希望能让后辈在学习 Java 语言的路上可以走的顺畅一些,也可以在前期少走一些弯路。

    202010011205888

    程序概述

    好了,言归正传。从看到本篇的那一刻起,你就已经推开了 IT 编程的大门。自此,程序一词或将改变你未来的人生轨迹。未来的你,按键行云流水,脑内 CPU 高速运转,屏幕切换间,高可用、高扩展、高安全性的” 完美”程序,将由你所著。[未来的你]

    202010011205999

    首先我们来看看程序的概念,程序在现世纪存在两种含义,即生活中的程序和计算机中的程序。

    202010011206888

    生活中程序

    生活中的程序: 我们去银行办理业务或者在学校时因事请假再或者在工作中去办理报销,这些场景我们都不陌生。我们一遍一遍,一步一步的执行着所谓的流程和手续(套路),这就是生活中的程序。

    计算机中的程序

    在本世纪的今天,现实生活中的程序你可能已经把它换了个称呼,比如:流程或者手续。而程序这个概念,更多的时候已经被你安放到了手机上的APP,或者计算机上的QQ等方面。它们的确是程序,是由我们未来的同行,前辈所创造编写,编写这些程序的过程,就是所谓的编程

    笔者说

    软件不是程序,软件是比应用程序更大的概念,软件是程序、数据及相关文档的完整集合 。可延伸阅读张海藩和吕云翔所著的《软件工程 第4版》

    但是如果单单这么理解程序,可就有点小儿科了。下面的[ 百度百科 ],解释的还挺不错!程序其实是一个指令 的集合。

    202010011206999

    何为指令呢? 比方说:我是一位老板(目前不是),我有个女秘书(目前没有),每天我的行程安排或者一些繁琐的事项都交给了我秘书去做,我告诉秘书去帮我邀约一位客人,她就如是去做了。我告诉秘书去帮我通知一下部门要开会了,她也会如是去通知。这个比方里老板所告诉秘书的一件件事就是一个个指令 ,而秘书就是接收指令然后去执行的人。

    202010011209888

    换到计算机世界的概念,我们被称为程序员/码农 ,如果想让计算机这些秘书去执行一些操作,例如在屏幕上输出一些内容或者自动绘制一个小猪佩奇图像等,就需要去给计算机下达指令。学习编程就是在学习给计算机秘书下达指令的过程而已。

    202010011209999

    程序和程序的关系

    从上面我们理解了程序的两种含义,它们之间是否存在什么关系呢?其实计算机中的程序绝大多数都是因为现实中的某些流程/程序,很是麻烦,耗时或者产生一些资源的浪费(纸张),所以在有了计算机帮助之后,我们人类就将现实生活中的这些程序,以计算机中的程序表现出来了。例如: 在线挂号,在线购物,在线选课、OA系统等。

    一句话可以阐述这个关系: 我们编程就是将现实生活中的业务程序移植到计算机中,以计算机指令的形式表现出来。

    编程语言概述

    了解完程序的概念,那这些程序指令我们是怎么告诉计算机的?口头告诉?脑电波交流?显然不是,那我们写自然语言来告诉计算机要做什么?不好意思的是,计算机并不能看懂,它只能看懂二进制(0 1这种数制)。早期的计算机从业者就是在敲打0 1来告诉计算机需要做什么,但是对于我们人类来讲,这一堆0110阅读起来太难了,所以后期的发展中,我们用自然语言定义了一些特殊的语法,再通过一个”翻译官”(编译器)帮我们翻译给计算机看,这样就能实现计算机来执行我们的指令,而且我们自己也能看懂自己写的是什么。 上述提到的特殊语法就是编程语言,也被称为计算机语言。

    202010011210888

    主流的编程语言

    编程语言太多了,怎么定义语法的都有。这也很正常,就好像当今世界,自然语言都千奇百怪呢。所以我们不需要去过多的关注偏门的”小语种”,只需要去关注一下当今IT编程领域的主角们即可。[ 参见TIOBE ]

    下图是知名排行榜的统计数据,类似的网站有PYPL等。TIOBE排行榜是根据互联网上有经验的程序员、课程和第三方厂商的数量,并使用搜索引擎(如Google、Bing、Yahoo!)以及Wikipedia、Amazon、YouTube统计出排名数据,只是反映某个编程语言的热门程度,并不能说明一门编程语言好不好,或者一门语言所编写的代码数量多少。

    202010011210999

    笔者说

    在笔者看来,数据还是能说明一些语言的好坏或流行度的。截止目前,即使谦虚一些,我们要学习的 Java(爪哇/国内音译加哇,扎哇),它仍然是最热门的编程语言之一。即便在网络上有一些人每天都在喊着不要学 Java,要学 Python,学 C,学 PHP,甚至还听过 PHP 是最好的语言,这种引战言论。

    事实上呢?哪有什么完美的编程语言?只不过是不同的场景,谁更适合而已。而且处于当今时代的我们,只会一种编程语言已经无法适应快速迭代的互联网应用和企业需要了(全栈)。所以不学哪种编程语言本身就是个”伪命题”

    尤其对于刚踏入编程领域的小伙伴们来讲,笔者个人认为,Java 是一门极其合适的入门和谋生语言!它在理解难度,语法规范,性能,流传广度、社区支持,企业需要和热度等方面都属于领先地位。稍后笔者就给你详细讲讲它的来历和能力。

    Java的前世今生

    Java的诞生

    Java是SUN Microsystems(国内译为升阳公司)于1995年推出的高级编程语言。下图是Java的共同创始人之一:詹姆斯·高斯林James Gosling(被誉为Java之父)。2009年,SUN公司被Oracle并购,高斯林离职。截止目前,高斯林加入了亚马逊AWS工作。

    202010011211888

    下图是 Java 的 Logo(一杯热气腾腾的咖啡,有传言Java也是由于大佬们爱喝印尼爪哇岛的咖啡而得名)。不过这两缕蒸汽是多么像大佬们头上稀疏的秀发。

    202010011212999

    《深入理解Java虚拟机》

    1991年4月,由James Gosling博士领导的绿色计划(Green Project)开始启动,此计划的目的是开发一种能够在各种消费性电子产品(如机顶盒、冰箱、收音机等)上运行的程序架构。这个计划的产品就是Java语言的前身:Oak(橡树)。Oak当时在消费品市场上并不算成功,但随着1995年互联网潮流的兴起,Oak迅速找到了最适合自己发展的市场定位并蜕变成为Java语言。[1]

    Java的发展

    下图是Java的发展过程,重点关注一下1995年,1998年,2009年和2013年中期即可,了解下它的历史,才能让我们更好的与它"对话和结伴"。

    202010011215888

    《深入理解Java虚拟机》

    1995年5月23日,Oak语言改名为Java ,并且在SunWorld大会上正式发布Java 1.0版本。Java语言第一次提出了“Write Once,Run Anywhere ”的口号。

    1996年1月23日,JDK 1.0发布,Java语言有了第一个正式版本的运行环境。JDK 1.0提供了一个纯解释执行的Java虚拟机实现(Sun Classic VM)。JDK 1.0版本的代表技术包括:Java虚拟机、Applet、AWT等。

    1998年12月4日,JDK迎来了一个里程碑式的版本JDK 1.2,工程代号为Playground(竞技场),Sun在这个版本中把Java技术体系拆分为3个方向,分别是面向桌面应用开发的J2SE (Java 2 Platform,Standard Edition)、面向企业级开发的J2EE (Java 2 Platform,Enterprise Edition)和面向手机等移动终端开发的J2ME (Java 2 Platform,Micro Edition)。在这个版本中出现的代表性技术非常多,如EJB、Java Plug-in、Java IDL、Swing等,并且这个版本中Java虚拟机第一次内置了JIT(Just In Time)编译器(JDK 1.2中曾并存过3个虚拟机,Classic VM、HotSpot VM和Exact VM,其中Exact VM只在Solaris平台出现过;后面两个虚拟机都是内置JIT编译器的,而之前版本所带的Classic VM只能以外挂的形式使用JIT编译器)。

    1999年4月27日,HotSpot 虚拟机发布,HotSpot最初由一家名为“Longview Technologies”的小公司开发,因为HotSpot的优异表现,这家公司在1997年被Sun公司收购了。HotSpot虚拟机发布时是作为JDK 1.2的附加程序提供的,后来它成为了JDK 1.3及之后所有版本的Sun JDK的默认虚拟机。

    2009年2月19日,工程代号为Dolphin(海豚)的JDK 1.7完成了其第一个里程碑版本。根据JDK 1.7的功能规划,一共设置了10个里程碑。最后一个里程碑版本原计划于2010年9月9日结束,但由于各种原因,JDK 1.7最终无法按计划完成。在JDK 1.7开发期间,Sun公司由于相继在技术竞争和商业竞争中都陷入泥潭,公司的股票市值跌至仅有高峰时期的3%,已无力推动JDK 1.7的研发工作按正常计划进行。为了尽快结束JDK 1.7长期“跳票”的问题,Oracle公司收购Sun公司 后不久便宣布将实行“B计划”,大幅裁剪了JDK 1.7预定目标,以便保证JDK 1.7的正式版能够于2011年7月28日准时发布。[1]

    笔者说

    2017年11月,Oracle(甲骨文)将Java EE(Java Enterprise Edition)移交给 Eclipse基金会,2018年3月份Eclipse将其更名为Jakarta EE。

    Java的能力

    从1998年的1.2版本开始,Java出现了3个方向版本,上面笔者也给你摘了一部分周老师的书籍内容。这三个版本分别是:面向桌面应用开发的J2SE(Java 2 Platform,Standard Edition)、面向企业级开发的J2EE(Java 2 Platform,Enterprise Edition)和面向手机等移动终端开发的J2ME(Java 2 Platform,Micro Edition)

    Java SE是基础核心,Java ME和Java EE是核心外的两个分支。无论是走Java ME还是Java EE都需要学习Java SE,我们系列课程的前三小阶段就是在学习Java SE基础。另外以后我们主要从事的分支就是Java EE,典型的产品就是天猫、京东等大型分布式应用。再进阶之后我们还可以进入热门的大数据领域,国内现在比较知名的大数据框架Hadoop就是Java语言编写的,而且现在国内做大数据的人才很多都是由原Java工程师进阶过去的。

    还有非常重要的一点,Java程序是跨平台的 ,即Java程序可以运行在任何平台上,不需要做不同系统平台的兼容适配。 “write once , run anywhere.” ,稍后我们编写完第一个Java程序后会介绍一下这句口号及原理。

    笔者说

    1. 因为windows的普及性,在桌面程序开发领域,微软的C#语言更具有优势,开发游戏方面C和C++性能和渲染上也更有优势,Java几乎没有市场。
    2. Java ME已经凉凉,被Android等抢占了市场,不知道你有没有用过诺基亚等老式手机?还记得Java 2D游戏吗?
    3. Android(Andorid不是编程语言)虽然在2017年Google将Kotlin正式列为官方支持开发语言,但Java目前仍是其主要开发语言,原因是Android的底层有大量的Java API(可延伸阅读Oracle与Google的官司大战)。

    202010011216999

    《深入理解Java虚拟机》

    Java不仅仅是一门编程语言,还是一个由一系列计算机软件和规范形成的技术体系,这个技术体系提供了完整的用于软件开发和跨平台部署的支持环境,并广泛应用于嵌入式系统、移动终端、企业服务器、大型机等各种场合 。时至今日,Java技术体系已经吸引了900多万软件开发者,这是全球最大的软件开发团队。使用Java的设备多达几十亿台,其中包括11亿多台个人计算机、30亿部移动电话及其他手持设备、数量众多的智能卡,以及大量机顶盒、导航系统和其他设备。[1]

    参考文献

    [1]周志明. 深入理解Java虚拟机[M]. 第3版. 北京:机械工业出版社,2019

    - + \ No newline at end of file diff --git "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/01-\345\277\253\351\200\237\345\205\245\351\227\250.html" "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/01-\345\277\253\351\200\237\345\205\245\351\227\250.html" index 12e851e0c5..cfa0880d26 100644 --- "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/01-\345\277\253\351\200\237\345\205\245\351\227\250.html" +++ "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/01-\345\277\253\351\200\237\345\205\245\351\227\250.html" @@ -278,7 +278,7 @@ User [id=3, name=Tom, age=28, email=Tom@126.com] User [id=4, name=Sandy, age=21, email=Sandy@126.com] User [id=5, name=Billie, age=24, email=Billie@126.com]

    后记

    C: 好了,与 MyBatis 的第一次约会结束了。怎么样?约会体验如何?使用步骤是不是还挺简单的?

    虽然是在学习一个新技术,但是一定要时刻想想当初 DAO 模式你是怎么一个开发步骤,这样对比着会发现 MyBatis 就是在简化、优化原来的每个环节而已。根本上还是那么回事,多想想,脑子里就能留下使用思路。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/02-\346\240\270\345\277\203\345\257\271\350\261\241.html" "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/02-\346\240\270\345\277\203\345\257\271\350\261\241.html" index cf27447613..a363425eb4 100644 --- "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/02-\346\240\270\345\277\203\345\257\271\350\261\241.html" +++ "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/02-\346\240\270\345\277\203\345\257\271\350\261\241.html" @@ -192,7 +192,7 @@ e.printStackTrace(); } }

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。 所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/03-\346\240\270\345\277\203\351\205\215\347\275\256\346\226\207\344\273\266.html" "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/03-\346\240\270\345\277\203\351\205\215\347\275\256\346\226\207\344\273\266.html" index abd15d1bbd..fc5e275e5a 100644 --- "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/03-\346\240\270\345\277\203\351\205\215\347\275\256\346\226\207\344\273\266.html" +++ "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/03-\346\240\270\345\277\203\351\205\215\347\275\256\346\226\207\344\273\266.html" @@ -568,7 +568,7 @@ <mappers> <package name="com.example.mapper"/> </mappers>

    笔者说

    Mapper 接口开发是我们以后主要使用的方式,必须掌握!

    参考文献

    [1]MyBatis 官网. MyBatis 配置[EB/OL]. https://mybatis.org/mybatis-3/zh/configuration.html. 2020-12-26

    后记

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/04-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\346\237\245\350\257\242\345\205\203\347\264\240.html" "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/04-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\346\237\245\350\257\242\345\205\203\347\264\240.html" index 0644302598..16ba594f4e 100644 --- "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/04-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\346\237\245\350\257\242\345\205\203\347\264\240.html" +++ "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/04-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\346\237\245\350\257\242\345\205\203\347\264\240.html" @@ -494,7 +494,7 @@ <include refid="selectUser"/> where id = #{id} </select>

    参考文献

    [1]MyBatis 官网. XML 映射文件[EB/OL]. https://mybatis.org/mybatis-3/zh/sqlmap-xml.html. 2020-12-26

    后记

    本篇中,select 元素是重点,笔者列了好多个示例,你一定要将示例代码完整"临摹" + "思考"一遍,这样才能达到笔者所说的技术学习的第二步、第三步。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/05-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\345\242\236\345\210\240\346\224\271\345\205\203\347\264\240.html" "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/05-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\345\242\236\345\210\240\346\224\271\345\205\203\347\264\240.html" index bf6fcb101e..33187ed586 100644 --- "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/05-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\345\242\236\345\210\240\346\224\271\345\205\203\347\264\240.html" +++ "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/05-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\345\242\236\345\210\240\346\224\271\345\205\203\347\264\240.html" @@ -388,7 +388,7 @@ }

    控制台输出:

    sql
    -- 输出的 SQL 语句
     delete from user where id = ?
    -- 输出的 SQL 语句
     delete from user where id = ?
    影响行数为:1
    影响行数为:1

    参考文献

    [1]MyBatis 官网. XML 映射文件[EB/OL]. https://mybatis.org/mybatis-3/zh/sqlmap-xml.html. 2020-12-26

    后记

    你就看看,它们使用起来简单不?这还能有理由说学不动、学不会吗?

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/06-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\350\207\252\345\256\232\344\271\211\346\230\240\345\260\204\345\205\203\347\264\240.html" "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/06-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\350\207\252\345\256\232\344\271\211\346\230\240\345\260\204\345\205\203\347\264\240.html" index b9de0571b7..8b47bcc428 100644 --- "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/06-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\350\207\252\345\256\232\344\271\211\346\230\240\345\260\204\345\205\203\347\264\240.html" +++ "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/06-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\350\207\252\345\256\232\344\271\211\346\230\240\345\260\204\345\205\203\347\264\240.html" @@ -462,7 +462,7 @@ LinkUser [id=2, name=李四, phone=18822233322, address=北京东城区, userId=null]
    User [id=2, name=Jack, age=20, email=Jack@126.com]
     LinkUser [id=1, name=张三, phone=18822233311, address=北京西城区, userId=null]
     LinkUser [id=2, name=李四, phone=18822233322, address=北京东城区, userId=null]

    参考文献

    [1]MyBatis 官网. XML 映射文件[EB/OL]. https://mybatis.org/mybatis-3/zh/sqlmap-xml.html. 2020-12-26

    后记

    大多数人在学到复杂自定义映射时都容易犯迷糊,所以笔者说过如果要学习 Hibernate 框架,开局容易深入难,因为 Hibernate 这框架中经常要处理类似的映射,年轻人慢慢来,加油!

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/07-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\347\274\223\345\255\230\345\205\203\347\264\240.html" "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/07-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\347\274\223\345\255\230\345\205\203\347\264\240.html" index 0fc6c755be..5a7e34616b 100644 --- "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/07-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\347\274\223\345\255\230\345\205\203\347\264\240.html" +++ "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/07-SQL\346\230\240\345\260\204\346\226\207\344\273\266\344\271\213\347\274\223\345\255\230\345\205\203\347\264\240.html" @@ -194,7 +194,7 @@ }

    再测试之后,控制台输出如下,Cache Hit Ratio 表示缓存命中率,开启二级缓存后,每执行一次查询,系统都会计算一次二级缓存的命中率。第一次查询也是先从缓存中查询,只不过缓存中一定是没有的,所以命中率为0,然后再从DB中查询后缓存到二级缓存中。第二次查询的时候是从二级缓存中读取的,这一次的命中率为1/2=0.5。 当然,若有第三次查询,则命中率为1/3=0.66 ,依此类推。[3]

    202012281139540

    cache-ref元素

    当我们想要在多个命名空间中共享相同的缓存配置和实例时,cache-ref 元素就可以派上用场了,当同时使用了 cache 元素和 cache-ref 元素时,cache 元素的优先级更高。

    xml
    <!-- namespace:要共享二级缓存的某 SQL 映射文件的 namespace 的值 -->
     <cache-ref namespace="com.example.mapper.UserMapper"/>
    <!-- namespace:要共享二级缓存的某 SQL 映射文件的 namespace 的值 -->
     <cache-ref namespace="com.example.mapper.UserMapper"/>

    笔者说

    二级缓存也不是万能的,需要根据实际情况来,当查询操作远远多于增删改操作的情况下,并且业务对数据的实时性要求不高的时候可以采用二级缓存,否则增删改频繁刷新二级缓存将会降低系统性能,而缓存又会导致实时效果差。 而且 MyBatis 的二级缓存也存在着一些缺陷,使用 MyBatis 二级缓存必须有一个前提:保证所有的增删改查都在同一个 namespace 下才行,不然容易出现数据不一致问题,例如:当两个 SQL 映射文件中均存在对同一个表的操作,那么其中一方修改了表,只会引发该 SQL 映射文件的二级缓存清空,而不会清空另一个的。

    参考文献

    [1]MyBatis 官网. XML 映射文件[EB/OL]. https://mybatis.org/mybatis-3/zh/sqlmap-xml.html. 2020-12-26

    [2]花好夜猿. Mybatis【面试题】讲讲Mybatis的缓存-简答[EB/OL]. https://blog.csdn.net/qq_23202687/article/details/103708458. 2019-12-26

    [3]陈浩翔. 你真的懂Mybatis缓存机制吗[EB/OL]. https://mp.weixin.qq.com/s/h2x15k71rClaHjcz7u2dlQ. 2018-07-10

    后记

    SQL 映射文件的初步学习终于结束了,幸好有之前的文章雏形,但就这样还花费了半天时间整理和完善,但愿它能给小白用户带来一份系统的学习方案。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/08-\345\212\250\346\200\201SQL.html" "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/08-\345\212\250\346\200\201SQL.html" index f7c6828c2c..5bc828435d 100644 --- "a/courses/mybatis/01-MyBatis\345\237\272\347\241\200/08-\345\212\250\346\200\201SQL.html" +++ "b/courses/mybatis/01-MyBatis\345\237\272\347\241\200/08-\345\212\250\346\200\201SQL.html" @@ -854,7 +854,7 @@ }

    控制台输出:

    sql
    -- 输出的 SQL
     select * from user WHERE roleId = 1
    -- 输出的 SQL
     select * from user WHERE roleId = 1

    笔者说

    效果很明显了,自上而下进行条件判断,只要其中一个满足,后面的条件就不再执行。这就是 switch 的机制。

    参考文献

    [1]MyBatis 官网. 动态 SQL[EB/OL]. https://mybatis.org/mybatis-3/zh/dynamic-sql.html. 2020-12-28

    后记

    《初识MyBatis》 系列到本篇就结束了,笔者的更文速度,你还能追的上吗?😄

    其实这不过是因为笔者有存稿而已,否则哪那么快,每篇文章在最初时都花费了大量的时间,在存稿基础上再来发表又花费了不短时间,所以真没什么产出速度,大概比一些"临近太监的网文"的作者快点?

    这个系列结束,不代表 MyBatis 系列就完全结束了,后续笔者会再次抽时间开放 MyBatis 新的系列 《MyBatis原理》,当然这可能需要一段时间,因为笔者最近在忙另一个系列。但愿那时候还在关注的你,依然保持有对 MyBatis 热爱、研究的心。💝

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/01-\345\277\253\351\200\237\345\205\245\351\227\250.html" "b/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/01-\345\277\253\351\200\237\345\205\245\351\227\250.html" index 6fe913846e..f0c927d1c9 100644 --- "a/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/01-\345\277\253\351\200\237\345\205\245\351\227\250.html" +++ "b/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/01-\345\277\253\351\200\237\345\205\245\351\227\250.html" @@ -332,7 +332,7 @@ User(id=3, name=Tom, age=28, email=Tom@126.com) User(id=4, name=Sandy, age=21, email=Sandy@126.com) User(id=5, name=Billie, age=24, email=Billie@126.com)

    笔者说

    你在编写单元测试时,可能会看到如下报错。别担心这不是什么要紧的事儿,不影响你运行,也不影响你开法拉利。 如果你看不惯它,那就去 UserMapper 接口上加一个 @Repository@Component 注解,这问题就迎刃而解了。

    202101161200243

    参考文献

    [1]MyBatis Plus 官网. 指南[EB/OL]. https://baomidou.com/guide/. 2021-01-16

    后记

    C: 好了,MP 的快速入门就到这儿结束了,是不是快的都让你觉得在做梦?通过以上几个简单的步骤,我们就拥有了一个具有通用 CRUD 操作功能的 Mapper 接口,甚至连 XML 文件都不用编写!

    当然,若是它们还不够你用,或是你需要写一些复杂的数据操作,继续按照 MyBatis 原来的玩法去写就可以了,一点不影响。

    MyBatis-Plus 的强大远不止如此,想要详细了解 MyBatis-Plus 的强大功能,那就继续查看笔者该系列的下一篇吧!

    另外,提醒一下,本系列的编写思路,就是基于官网而来,甚至有同学看完官网会觉得笔者多余了。但笔者一贯以小白文著称,学起来不吃力,学重点,比官网更细致,是笔者本系列的目的。所以等等再 "恶评" 吧。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/02-\345\242\236\345\210\240\346\224\271\346\223\215\344\275\234.html" "b/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/02-\345\242\236\345\210\240\346\224\271\346\223\215\344\275\234.html" index 6fb66ae144..aaee11b288 100644 --- "a/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/02-\345\242\236\345\210\240\346\224\271\346\223\215\344\275\234.html" +++ "b/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/02-\345\242\236\345\210\240\346\224\271\346\223\215\344\275\234.html" @@ -768,7 +768,7 @@ <== Updates: 1
    ==>  Preparing: UPDATE user SET is_delete=1 WHERE id=? AND is_delete=0
     ==> Parameters: 5(Long)
     <==    Updates: 1

    这回就不再报错了,而且显而易见,执行的 SQL 由没有做逻辑删除配置前的 DELETE 操作现在变为了 UPDATE 操作。

    202101190000700

    笔者说

    在逻辑删除配置好之后,原来的部分操作,像查询操作,在执行 SQL 时将自动带上 where is_delete = 0 这个条件。

    参考文献

    [1]MyBatis Plus 官网. 指南[EB/OL]. https://baomidou.com/guide/. 2021-01-18

    后记

    C: 学习 MyBatis 的时候,我们就没担心过 CUD,现在 MP 中自然更不存在这事儿了。而且 MP 还给我们提供了这么多实用功能。

    下一篇我们将会学到较为复杂的查询操作,但 MP 还是相对简单的,敬请期待吧。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/03-\347\256\200\345\215\225\346\237\245\350\257\242\346\223\215\344\275\234.html" "b/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/03-\347\256\200\345\215\225\346\237\245\350\257\242\346\223\215\344\275\234.html" index d3246a1a01..20f88efa00 100644 --- "a/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/03-\347\256\200\345\215\225\346\237\245\350\257\242\346\223\215\344\275\234.html" +++ "b/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/03-\347\256\200\345\215\225\346\237\245\350\257\242\346\223\215\344\275\234.html" @@ -408,7 +408,7 @@ 页大小:2 User(id=1, name=Jone, age=18, email=Jone@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0) User(id=2, name=Jack, age=20, email=Jack@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)

    笔者说

    测试完后,笔者再告诉你一件事儿,MP 的这款分页插件,支持的数据库也很多: MySQL 、MariaDB 、Oracle 、DB2 、H2 、HSQL 、SQL Lite 、PostgreSQL 、SQL Server 、Presto 、Gauss 、Firebird、Phoenix 、ClickHouse 、Sybase ASE 、 OceanBase 、达梦数据库 、虚谷数据库 、人大金仓数据库 、南大通用数据库。

    这么多种类的数据库支持,从我们日常使用上来讲绝对够用了!

    参考文献

    [1]MyBatis Plus 官网. 指南[EB/OL]. https://baomidou.com/guide/. 2021-01-18

    后记

    C: 好了,简单的查询操作就介绍到这儿了,这些基础查询是不是还挺简单的?下一篇,我们将介绍预热了两篇已久的条件构造器,肯定有很多同学等急了。别着急,好饭不怕晚,下一篇笔者就带你好好认识认识它。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/04-\346\235\241\344\273\266\346\236\204\351\200\240\345\231\250.html" "b/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/04-\346\235\241\344\273\266\346\236\204\351\200\240\345\231\250.html" index 2eac0c5b4a..b5a6cae825 100644 --- "a/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/04-\346\235\241\344\273\266\346\236\204\351\200\240\345\231\250.html" +++ "b/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/04-\346\235\241\344\273\266\346\236\204\351\200\240\345\231\250.html" @@ -622,7 +622,7 @@ <== Columns: id, name, age, email, create_time, update_time, is_delete <== Row: 3, Tom, 28, Tom@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0 <== Total: 1
    User(id=3, name=Tom, age=28, email=Tom@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
    User(id=3, name=Tom, age=28, email=Tom@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)

    笔者说

    需要注意的是 allEq() 方法 只能用于具有 and 关系且都是等值判断的情况

    参考文献

    [1]MyBatis Plus 官网. 指南[EB/OL]. https://baomidou.com/guide/. 2021-01-18

    后记

    C: 好了,MP 的条件构造器就介绍完了。笔者有很多 API 没有带大家测试,但我想你也能明白,即便我全部测试了,它也不一定能被你用上,灵活使用它们是需要时间积累的。所以说,讲些典型的,让你 "悟了" MP 的大致规律和节奏,这才是最重要的。

    除了 API 之外,实际上,MP 还有一些功能,因为时间原因,笔者在本次系列并没有提及,你可以前往官网查阅一下,或者等后面笔者时间允许的时候,会再去介绍下它的其他功能。

    最后的最后,笔者个人建议,在日常开发中,如果遇到的需求需要写一个比较复杂的 SQL,那还是直接使用 MyBatis 原生写法去 XML 中写吧,毕竟 MyBatis 最强大的优势还是体现在 XML 的 SQL 中。

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git "a/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/05-\344\273\243\347\240\201\347\224\237\346\210\220\345\231\250.html" "b/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/05-\344\273\243\347\240\201\347\224\237\346\210\220\345\231\250.html" index 6527a01d07..fc57146130 100644 --- "a/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/05-\344\273\243\347\240\201\347\224\237\346\210\220\345\231\250.html" +++ "b/courses/mybatis/02-MyBatis-Plus\345\237\272\347\241\200/05-\344\273\243\347\240\201\347\224\237\346\210\220\345\231\250.html" @@ -474,7 +474,7 @@ } }

    笔者说

    对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

    所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。

    - + \ No newline at end of file diff --git a/courses/mybatis/index.html b/courses/mybatis/index.html index d0300235e7..b1719ef93a 100644 --- a/courses/mybatis/index.html +++ b/courses/mybatis/index.html @@ -148,7 +148,7 @@ return this.insert(sqlBuffer.toString(), params); } }

    有问题存在,就不缺解决问题的人。在行业内随之诞生了大量的持久层解决方案,除了各自公司自研的方案之外,比较有名的通用开源方案有:Hibernate、MyBatis等,各有各的优劣势,在此我先不谈论它们的区别,只说一个现象:在国内来讲MyBatis应用相对广泛。

    202012252219533

    所以那没什么好说的了,直接学吧?

    开玩笑的,其实MyBatis在国内应用广泛的原因是因为相对于Hibernate等,它可以更加灵活的编写SQL语句,对于需求变动比较频繁,业务比较复杂,高并发要求较高的应用,优势显而易见。国内这两年互联网发展的挺快,互联网用户群体基数大,而国外人群规模相对较少,相比于国内开发者,他们更关注实现效率而非极致的性能。

    简介

    笔者说

    MyBatis读音是:[mai'bətɪs](买杯涕死),原是 Apache 软件基金会的一个开源项目 iBatis , 2010年这个项目由 Apache 软件基金会迁移到了 Google Code 平台,并且改名为 MyBatis 。2013年11月再迁移到 GitHub

    MyBatis 是一款优秀的 半自动的持久层ORM框架 ,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

    当前,最新版本是MyBatis 3.5.6 ,其发布时间是2020年10月6日。[1] 202012252221059

    1.什么是 ORM ? [2]

    ORM,对象关系映射(Object Relation Mapping,简称ORM,或O/RM,或O/R Mapping),它是随着面向对象的软件开发方法发展而产生的。

    面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射( ORM )系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。 202012252221734

    面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为了解决这个不匹配的现象,对象关系映射技术应运而生。

    它可以有效解决数据库与程序间的异构性,实现面向对象编程语言里不同类型系统的数据之间的转换。

    大白话: 一个完整的ORM框架,可以使得我们在具体的操作业务对象的时候,不需要再去和复杂的SQL语句之流打交道,只需简单的操作对象的属性即可。例如:在指定好对象和数据库映射之后,要保存一个用户数据,只需要创建好用户对象,然后调用ORM解决方案,就会自动将对象数据持久化到数据库中。(无需关心SQL,ORM自动生成SQL) ;要更新一个用户信息,只需要在程序中对该用户对象的属性进行更新,ORM解决方案就会自动将更改后的结果持久化到数据库中。

    2.为什么说 MyBatis 是半自动 ORM ? [2]

    Mybatis 在查询关联对象或关联集合对象时,需要手动编写 SQL 来完成,所以称之为半自动 ORM 映射工具。也可以说 MyBatis 是一个业务实体对象和 SQL 之间的映射工具。

    特点

    C: 笔者其实不愿意先给你介绍一个技术的特点,因为没有用过,光靠说是不行的。只有在我们实际体验之后,它的优缺点才可以感受到了,不过提前先了解一下,也更有学习动力,千万记得学习的过程中及时思考和理解这些特点。

    优势:

    • 简单易学: 框架规模小,学习门槛低(官方文档简单详细),与 JDBC 相比,减少了50%以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接。
    • 灵活度高: 半自动化 ORM,程序员直接编写原生态 SQL ,可严格控制 SQL 执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如:互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。
    • 解除 SQL 与程序代码的耦合: SQL 代码从程序代码中彻底分离,写在XML里,可重用。
    • 提供映射标签,支持对象与数据库的 ORM 字段映射
    • 提供 XML 标签,支持编写动态 SQL : 在使用 JDBC 的过程中, 根据条件进行 SQL 的拼接是很麻烦且很容易出错的。 MyBatis 动态 SQL 的出现, 解决了这个麻烦。

    劣势:

    • SQL语句的编写工作量较大:尤其当字段多,关联表多时,对开发人员编写SQL语句的功底有一定要求。
    • 数据库无关性、移植性差: SQL语句依赖数据库,导致数据库移植性差,不能随意更换数据库。[3]

    参考文献

    [1]MyBatis 官网. MyBatis 简介[EB/OL]. https://mybatis.org/mybatis-3/zh/index.html. 2020-12-25

    [2]laouei. 理解 ORM 和数据持久化[EB/OL]. https://blog.csdn.net/u012585964/article/details/52412520. 2016-09-02

    [3]W3CSchool. MyBatis 教程[EB/OL]. https://www.w3cschool.cn/mybatis/. 2020-12-25

    - + \ No newline at end of file diff --git "a/courses/mysql/01-MySQL\345\237\272\347\241\200/01-MySQL\345\256\211\350\243\205\344\270\216\351\205\215\347\275\256.html" "b/courses/mysql/01-MySQL\345\237\272\347\241\200/01-MySQL\345\256\211\350\243\205\344\270\216\351\205\215\347\275\256.html" index d84d453913..bf43754959 100644 --- "a/courses/mysql/01-MySQL\345\237\272\347\241\200/01-MySQL\345\256\211\350\243\205\344\270\216\351\205\215\347\275\256.html" +++ "b/courses/mysql/01-MySQL\345\237\272\347\241\200/01-MySQL\345\256\211\350\243\205\344\270\216\351\205\215\347\275\256.html" @@ -54,7 +54,7 @@
    目录

    MySQL安装与配置

    未完待续......

    - + \ No newline at end of file diff --git "a/courses/mysql/03-\351\231\204\345\275\225/01-CentOS\345\256\211\350\243\205MySQL.html" "b/courses/mysql/03-\351\231\204\345\275\225/01-CentOS\345\256\211\350\243\205MySQL.html" index 10e0d4d2a1..61da1043b9 100644 --- "a/courses/mysql/03-\351\231\204\345\275\225/01-CentOS\345\256\211\350\243\205MySQL.html" +++ "b/courses/mysql/03-\351\231\204\345\275\225/01-CentOS\345\256\211\350\243\205MySQL.html" @@ -136,7 +136,7 @@ # 3、重新加载 profile 文件,使最新配置生效 source /etc/profile

    登录并修改密码

    第一次登录时,首先从日志文件中找到随机生成的密码。

    shell
    cat /opt/disk/mysql/data/mysqld.log
    cat /opt/disk/mysql/data/mysqld.log

    在日志文件中找到类似于下方输出的位置,其中 8QE2NEqhB:ks 就是密码。

    [Note] A temporary password is generated for root@localhost: 8QE2NEqhB:ks
    [Note] A temporary password is generated for root@localhost: 8QE2NEqhB:ks

    登录到 MySQL 服务端。

    笔者说

    有些时候随机生成的密码包含特殊符号,例如:&/.(你没看错,有时候最后有个 . 可千万别当作是句子结尾。

    这种密码你在登录的时候,记得用 ' 单引号给它引起来。

    例如:mysql -uroot -p'7AB5CDadE&'

    shell
    mysql -uroot -p刚才从日志中找到的随机密码
    mysql -uroot -p刚才从日志中找到的随机密码

    修改密码。

    shell
    set password = password('新密码');
    set password = password('新密码');

    创建用户并授权

    这一步就要根据你的实际个人需要来操作了。

    shell
    grant all on *.* to root@'%' identified by '你的密码';
    grant all on *.* to root@'%' identified by '你的密码';
    - + \ No newline at end of file diff --git "a/courses/mysql/03-\351\231\204\345\275\225/02-Docker\345\256\211\350\243\205MySQL.html" "b/courses/mysql/03-\351\231\204\345\275\225/02-Docker\345\256\211\350\243\205MySQL.html" index dd394b0133..5a0d211853 100644 --- "a/courses/mysql/03-\351\231\204\345\275\225/02-Docker\345\256\211\350\243\205MySQL.html" +++ "b/courses/mysql/03-\351\231\204\345\275\225/02-Docker\345\256\211\350\243\205MySQL.html" @@ -178,7 +178,7 @@ - /opt/disk/docker/volumes/mysql/conf:/etc/mysql/conf.d - /opt/disk/docker/volumes/mysql/data:/var/lib/mysql privileged: true - + \ No newline at end of file diff --git a/courses/mysql/index.html b/courses/mysql/index.html index dd593a38d6..3649a75311 100644 --- a/courses/mysql/index.html +++ b/courses/mysql/index.html @@ -54,7 +54,7 @@
    目录

    MySQL快速入门

    未完待续......

    - + \ No newline at end of file diff --git a/hashmap.json b/hashmap.json index 3d2477bcc9..c1a8f99925 100644 --- a/hashmap.json +++ b/hashmap.json @@ -1 +1 @@ -{"about_index.md":"04e699aa","categories_fragments_2019_12_29_个人常用stream使用技巧.md":"775cabe3","about_me.md":"e1348efe","archives.md":"c1ab078e","categories_fragments_2019_12_28_个人sql优化技巧.md":"5f862f76","categories_fragments_2022_02_16_个人常用sql函数.md":"73a8fddc","categories_fragments_2022_03_25_合并两个git仓库的历史提交记录.md":"d3e287f6","categories_fragments_2022_03_26_修改git最后一次提交记录的作者和邮箱.md":"5fd2eddf","categories_fragments_2022_03_28_为指定git仓库单独配置用户名和邮箱.md":"8a467aff","categories_fragments_2019_12_30_个人常用hutool工具类.md":"53750d79","categories_fragments_2022_03_27_修改git所有提交记录中的作者和邮箱.md":"9b1a96cb","categories_fragments_2022_08_29_内网centos服务器设置网络代理.md":"c826dbee","categories_fragments_2021_05_29_设计模式之单例模式.md":"bbdf8f83","categories_fragments_2021_03_12_精密计算工具类-bigdecimal.md":"8aeb37ee","categories_fragments_2022_10_06_个人常用快捷键.md":"aa47c38e","categories_fragments_2022_10_27_docker安装consul.md":"ec0e19e3","categories_fragments_2022_12_07_网站开启灰色显示.md":"bc7872ec","categories_fragments_2022_10_31_centos安装docker.md":"4e4c5c68","categories_fragments_2022_11_01_使用idea进行远程程序调试.md":"3afb80e0","categories_fragments_2022_10_28_docker安装minio.md":"6fffe81e","categories_issues_2021_12_10_command line is too long. shorten command line for xxx or also for spring boot default configuration.md":"0b83f1b1","categories_issues_2021_12_11_sql 注入攻击风险.md":"c473a0c9","categories_issues_2021_12_13_无法访问f盘。文件或目录损坏且无法读取.md":"1b8e86c6","categories_issues_2022_03_24_创建一个自身类的静态对象变量,究竟会如何执行?.md":"502b6346","categories_issues_2022_08_11_执行shell脚本,报java command not found.md":"6979fdd2","categories_fragments_2023_05_08_软件设计师知识点速记3.md":"0ada043b","courses_java_01-java语法入门_07-控制语句和流程图.md":"24a94233","categories_fragments_2019_12_31_个人常用linux命令.md":"581875c3","categories_issues_2021_12_08_for循环中删除集合元素隐藏的陷阱.md":"bb65e710","categories_fragments_2023_01_06_codereview方法论与实践总结.md":"41dcdbfd","categories_fragments_2022_10_05_个人常用git命令.md":"c35e3625","categories_issues_2022_08_31_springboot项目引入openfeign后无法启动.md":"b577266e","categories_fragments_index.md":"350bc739","categories_issues_2022_09_05_nginx转发请求,报13:permission denied错误.md":"8b2c5049","categories_issues_2021_12_01_f盘上的回收站已损坏。是否清空该驱动器上的回收站.md":"64873e6f","categories_issues_2022_10_15_解决windows桌面部分快捷方式图标变为空白的问题.md":"2418ddcc","categories_issues_2022_10_29_docker设置网络代理.md":"6324982a","categories_issues_2022_10_25_解决centos8执行yum安装报错.md":"a1e754ab","categories_issues_2022_09_23_解决无法重复读取请求体和响应体的问题.md":"dbc21540","categories_issues_2022_11_06_解决dotnet安装后报错的问题.md":"613bc89f","categories_issues_2022_11_23_解决maven传递依赖污染的问题.md":"591f5798","categories_issues_index.md":"1eb3a7cb","categories_solutions_2021_11_18_用java8获取近n天日期.md":"f4986ca4","categories_solutions_2022_09_07_递归查询树型结构数据的性能优化方案.md":"36230b97","categories_solutions_2021_11_22_一条sql查询今年每月统计信息.md":"5a68ce61","categories_solutions_index.md":"1803a116","courses_java_01-java语法入门_04-程序和计算机的那点事儿.md":"da1fe7cc","categories_tools_2021_02_22_rdm快速入门.md":"2cd6a3e5","categories_tools_2021_03_06_postman快速入门.md":"07c66d5d","courses_java_01-java语法入门_01-开发环境搭建.md":"5242da01","courses_mybatis_01-mybatis基础_07-sql映射文件之缓存元素.md":"32d61ab0","categories_tools_index.md":"3cd8d85f","categories_tools_2021_03_04_ardm快速入门.md":"5ae64a4b","categories_tools_2021_01_14_初识lombok.md":"273cac64","courses_java_01-java语法入门_02-第一个java程序.md":"8c629176","courses_java_01-java语法入门_03-初识eclipse.md":"14f2248d","courses_java_01-java语法入门_13-一维数组.md":"86be3bff","categories_issues_2022_01_26_javascript 无法存储 java long 类型数据问题.md":"cf221101","courses_java_01-java语法入门_06-常用的运算符.md":"4e3324a2","categories_fragments_2022_10_26_docker安装openldap.md":"de4e9e36","categories_issues_2022_11_04_解决docker安装prometheus启动报错的问题.md":"72277ead","courses_mysql_03-附录_01-centos安装mysql.md":"ca46bfe7","courses_mybatis_index.md":"64e1b52d","courses_mysql_01-mysql基础_01-mysql安装与配置.md":"847deec9","courses_mysql_index.md":"e2fc437e","index.md":"955216b8","courses_java_01-java语法入门_10-循环结构.md":"810215e4","courses_mysql_03-附录_02-docker安装mysql.md":"70c4372d","courses_java_03-java高级特性_01-集合与泛型-1.md":"7e39640b","courses_java_04-附录_01-centos安装jdk.md":"5ab0a87c","courses_java_01-java语法入门_05-变量和常量.md":"0a2a6d95","courses_mybatis_01-mybatis基础_01-快速入门.md":"1143d030","courses_mybatis_01-mybatis基础_02-核心对象.md":"266a880a","courses_mybatis_02-mybatis-plus基础_05-代码生成器.md":"43a9df43","courses_java_01-java语法入门_11-多重循环.md":"180a1fa2","courses_java_01-java语法入门_12-程序调试入门.md":"5ed66a93","courses_java_01-java语法入门_09-switch选择结构.md":"2227a31c","courses_java_02-java面向对象_01-类和对象.md":"fa35096d","courses_mybatis_01-mybatis基础_03-核心配置文件.md":"1e81345f","courses_java_01-java语法入门_08-if选择结构.md":"dcbe04bc","categories_tools_2021_03_10_quartz快速入门.md":"af578edc","courses_java_index.md":"455eab30","courses_mybatis_02-mybatis-plus基础_02-增删改操作.md":"768601e2","courses_mybatis_01-mybatis基础_05-sql映射文件之增删改元素.md":"a24e9141","categories_fragments_2022_10_01_个人常用docker命令.md":"a6aa3723","courses_mybatis_01-mybatis基础_06-sql映射文件之自定义映射元素.md":"6989b375","courses_mybatis_02-mybatis-plus基础_03-简单查询操作.md":"0e3455b9","courses_mybatis_01-mybatis基础_04-sql映射文件之查询元素.md":"ec65ab45","courses_mybatis_01-mybatis基础_08-动态sql.md":"0967ca38","courses_java_01-java语法入门_14-多维数组.md":"a63ae80d","courses_mybatis_02-mybatis-plus基础_01-快速入门.md":"9cf4d646","courses_mybatis_02-mybatis-plus基础_04-条件构造器.md":"25322850","categories_fragments_2023_05_06_软件设计师知识点速记.md":"5a2c92c6","tags.md":"c3008682"} +{"about_index.md":"04e699aa","about_me.md":"e1348efe","archives.md":"c1ab078e","categories_fragments_2022_10_27_docker安装consul.md":"ec0e19e3","categories_fragments_2022_10_28_docker安装minio.md":"6fffe81e","categories_fragments_2022_10_26_docker安装openldap.md":"de4e9e36","categories_fragments_2022_10_06_个人常用快捷键.md":"aa47c38e","categories_fragments_2021_05_29_设计模式之单例模式.md":"bbdf8f83","categories_fragments_2022_03_26_修改git最后一次提交记录的作者和邮箱.md":"5fd2eddf","categories_fragments_2021_03_12_精密计算工具类-bigdecimal.md":"8aeb37ee","courses_mybatis_02-mybatis-plus基础_02-增删改操作.md":"768601e2","courses_mybatis_02-mybatis-plus基础_05-代码生成器.md":"43a9df43","courses_mysql_03-附录_01-centos安装mysql.md":"ca46bfe7","courses_mysql_03-附录_02-docker安装mysql.md":"70c4372d","courses_mysql_index.md":"e2fc437e","index.md":"955216b8","categories_fragments_2022_11_01_使用idea进行远程程序调试.md":"3afb80e0","categories_fragments_2019_12_30_个人常用hutool工具类.md":"53750d79","categories_fragments_2022_10_05_个人常用git命令.md":"c35e3625","categories_fragments_2022_10_01_个人常用docker命令.md":"a6aa3723","categories_fragments_2022_10_31_centos安装docker.md":"4e4c5c68","categories_fragments_2022_02_16_个人常用sql函数.md":"73a8fddc","courses_mybatis_index.md":"64e1b52d","categories_issues_2021_12_10_command line is too long. shorten command line for xxx or also for spring boot default configuration.md":"0b83f1b1","categories_issues_2021_12_11_sql 注入攻击风险.md":"c473a0c9","categories_fragments_2022_03_27_修改git所有提交记录中的作者和邮箱.md":"9b1a96cb","courses_mybatis_02-mybatis-plus基础_03-简单查询操作.md":"0e3455b9","categories_fragments_2019_12_28_个人sql优化技巧.md":"13a840ae","categories_fragments_2022_03_28_为指定git仓库单独配置用户名和邮箱.md":"8a467aff","courses_mybatis_02-mybatis-plus基础_04-条件构造器.md":"25322850","categories_fragments_2022_03_25_合并两个git仓库的历史提交记录.md":"d3e287f6","categories_fragments_2019_12_29_个人常用stream使用技巧.md":"775cabe3","courses_java_01-java语法入门_08-if选择结构.md":"dcbe04bc","categories_fragments_2019_12_31_个人常用linux命令.md":"581875c3","categories_fragments_2023_01_06_codereview方法论与实践总结.md":"8bed6c0c","categories_fragments_2022_12_07_网站开启灰色显示.md":"bc7872ec","categories_fragments_2022_08_29_内网centos服务器设置网络代理.md":"c826dbee","categories_fragments_2023_05_08_软件设计师知识点速记3.md":"0ada043b","categories_fragments_index.md":"350bc739","categories_issues_2021_12_01_f盘上的回收站已损坏。是否清空该驱动器上的回收站.md":"64873e6f","categories_issues_2021_12_13_无法访问f盘。文件或目录损坏且无法读取.md":"1b8e86c6","categories_issues_2022_01_26_javascript 无法存储 java long 类型数据问题.md":"cf221101","categories_issues_2022_03_24_创建一个自身类的静态对象变量,究竟会如何执行?.md":"502b6346","categories_issues_2022_08_11_执行shell脚本,报java command not found.md":"6979fdd2","categories_issues_2022_08_31_springboot项目引入openfeign后无法启动.md":"b577266e","categories_issues_2022_09_05_nginx转发请求,报13:permission denied错误.md":"8b2c5049","categories_issues_2022_09_23_解决无法重复读取请求体和响应体的问题.md":"dbc21540","categories_issues_2022_10_15_解决windows桌面部分快捷方式图标变为空白的问题.md":"2418ddcc","categories_issues_2022_10_25_解决centos8执行yum安装报错.md":"a1e754ab","categories_issues_2022_10_29_docker设置网络代理.md":"6324982a","categories_issues_2022_11_04_解决docker安装prometheus启动报错的问题.md":"72277ead","categories_issues_2022_11_06_解决dotnet安装后报错的问题.md":"613bc89f","categories_issues_2022_11_23_解决maven传递依赖污染的问题.md":"591f5798","categories_issues_index.md":"1eb3a7cb","categories_solutions_2021_11_18_用java8获取近n天日期.md":"f4986ca4","categories_solutions_2021_11_22_一条sql查询今年每月统计信息.md":"5a68ce61","categories_solutions_2022_09_07_递归查询树型结构数据的性能优化方案.md":"36230b97","categories_solutions_index.md":"1803a116","categories_tools_2021_02_22_rdm快速入门.md":"2cd6a3e5","categories_tools_2021_03_04_ardm快速入门.md":"5ae64a4b","categories_tools_2021_03_06_postman快速入门.md":"07c66d5d","categories_tools_2021_03_10_quartz快速入门.md":"af578edc","categories_tools_index.md":"3cd8d85f","courses_java_01-java语法入门_01-开发环境搭建.md":"5242da01","courses_java_01-java语法入门_02-第一个java程序.md":"8c629176","courses_java_01-java语法入门_03-初识eclipse.md":"14f2248d","courses_java_01-java语法入门_04-程序和计算机的那点事儿.md":"da1fe7cc","courses_java_01-java语法入门_05-变量和常量.md":"0a2a6d95","courses_java_01-java语法入门_06-常用的运算符.md":"4e3324a2","courses_mybatis_02-mybatis-plus基础_01-快速入门.md":"9cf4d646","categories_tools_2021_01_14_初识lombok.md":"273cac64","courses_java_01-java语法入门_07-控制语句和流程图.md":"24a94233","courses_java_01-java语法入门_09-switch选择结构.md":"2227a31c","courses_java_01-java语法入门_10-循环结构.md":"810215e4","courses_java_01-java语法入门_11-多重循环.md":"180a1fa2","courses_java_01-java语法入门_12-程序调试入门.md":"5ed66a93","courses_java_01-java语法入门_13-一维数组.md":"86be3bff","courses_java_01-java语法入门_14-多维数组.md":"a63ae80d","courses_java_02-java面向对象_01-类和对象.md":"fa35096d","courses_java_03-java高级特性_01-集合与泛型-1.md":"7e39640b","courses_java_04-附录_01-centos安装jdk.md":"5ab0a87c","courses_java_index.md":"455eab30","courses_mybatis_01-mybatis基础_01-快速入门.md":"1143d030","courses_mybatis_01-mybatis基础_03-核心配置文件.md":"1e81345f","courses_mybatis_01-mybatis基础_04-sql映射文件之查询元素.md":"ec65ab45","courses_mybatis_01-mybatis基础_05-sql映射文件之增删改元素.md":"a24e9141","courses_mybatis_01-mybatis基础_02-核心对象.md":"266a880a","courses_mybatis_01-mybatis基础_06-sql映射文件之自定义映射元素.md":"6989b375","courses_mybatis_01-mybatis基础_07-sql映射文件之缓存元素.md":"32d61ab0","courses_mybatis_01-mybatis基础_08-动态sql.md":"0967ca38","courses_mysql_01-mysql基础_01-mysql安装与配置.md":"847deec9","categories_issues_2021_12_08_for循环中删除集合元素隐藏的陷阱.md":"bb65e710","categories_fragments_2023_05_06_软件设计师知识点速记.md":"5a2c92c6","tags.md":"c3008682"} diff --git a/index.html b/index.html index cc0d77e3ba..7eecd69c7c 100644 --- a/index.html +++ b/index.html @@ -53,7 +53,7 @@

    查尔斯的知识库

    专注 & 洞察 & 分享

    个人技术知识库,记录 & 分享个人碎片化、结构化、体系化的技术知识内容。

    Logo
    - + \ No newline at end of file diff --git a/tags.html b/tags.html index 4d5a2d472b..270675e984 100644 --- a/tags.html +++ b/tags.html @@ -55,7 +55,7 @@
    - + \ No newline at end of file