如何在docker中产生core文件

原文链接:如何在docker中产生core文件
发布时间:2021-11-18 23:16:50

文章目录

前言

对于C/C++程序运行在docker容器中,希望它崩溃的时候能产生core文件,便于排查问题,这是十分必要的。

由于docker容器的权限问题,默认无法产生core文件,需要做一些配置修改。

1 修改docker运行参数

在 docker-compose 配置中添加以下参数:

1
2
3
4
5
6
7
8
security_opt:
- seccomp:unconfined
privileged: true ### 主要是这一项
cap_add:
- SYS_PTRACE
- SYS_ADMIN
ulimits:
core: -1

2 修改docker启动脚本

在容器启动脚本中,设置 core 文件大小限制:

1
ulimit -c unlimited

同时在系统层面设置 core 文件的输出路径和命名格式:

1
echo "/data/coredump/core.%e.%p" > /proc/sys/kernel/core_pattern

Golang易错知识点

原文链接:Golang易错知识点
发布时间:2021-11-17 23:34:25

文章目录

string和’\0’

Golang的string内部就是字节数组,末尾没有’\0’,对于Golang来说,’\0’也是一个普通的字节。其他语言在传输字符串的时候有可能在末尾加’\0’,那么Golang解析的时候会在末尾多了’\0’字符。

就会出现很尴尬的事情。比如C语言传输了’CHINA\0’,Golang解析后,再用’CHINA’比较,就不会相等了,但是打印出来确是一样。用C语言接收处理就不会出现问题。

slice

slice本质是一个结构体,不是指针,尽管是调用make创建。而同样调用make创建的map、channel,却是指针。

参见我另一篇博客《slice在Golang中的实现和应用》

WaitGroup使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 错误的写法
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
go func(i int) {
wg.Add(1)
fmt.Println(i)
wg.Done()
}(i)
}
wg.Wait()
}

// 正确的写法
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func(i int) {
fmt.Println(i)
wg.Done()
}(i)
}
wg.Wait()
}

第一种写法是错误的,达不到预期的效果。在当前的循环中,会快速地创建100个协程,无任何阻塞,然后立即执行到wg.Wait(),然而可能 wg.Add(1)一次都没有执行到,所以程序结束了。

切记,wg.Add(1)一定要在协程之外执行。

range产生的临时变量

1
2
3
4
arr := []int{1, 2, 3, 4, 5}
for k,v := range arr {
fmt.Printf("[%d] %p [%d] %p -->%p\n", k, &k, v, &v, &(arr[k]))
}
1
2
3
4
5
[0] 0xc00000a0b8 [1] 0xc00000a0d0 -->0xc00000c330
[1] 0xc00000a0b8 [2] 0xc00000a0d0 -->0xc00000c338
[2] 0xc00000a0b8 [3] 0xc00000a0d0 -->0xc00000c340
[3] 0xc00000a0b8 [4] 0xc00000a0d0 -->0xc00000c348
[4] 0xc00000a0b8 [5] 0xc00000a0d0 -->0xc00000c350

k和v并不是每次分配新的变量,一直是一个变量。使用的时候要小心,要使用值传递

goroutine泄露

进程内存增加,到最后崩溃,按理说Golang自己管理内存,不存在内存泄露。但是大量使用goroutine,有的地方反复调用开启goroutine,之前的goroutine没有回收,就会造成goroutine泄露。

解决方法,在日志中定期打印goroutine数量,可以调用runtime.NumGoroutine() ,观察是否一直增加。

闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
n := 0
f := func() int {
n += 1
fmt.Println(n)
return n
}

f()
time.Sleep(time.Second)
go f()
time.Sleep(time.Second)

//输出
1
2

闭包,就是指匿名函数以及匿名函数引用的环境组合的实体。可以看到f函数对n做了修改,对函数来说,n就是本包的全局变量。

闭包与range带来的问题

1
2
3
4
5
6
7
8
9
10
11
12
arr := []int{1, 2, 3, 4, 5}
for k,v := range arr {
go func(){
fmt.Println(k, v)
}()
}
// 输出结果
2 3
4 5
4 5
4 5
2 3
1
2
3
4
5
6
7
8
9
10
11
12
arr := []int{1, 2, 3, 4, 5}
for k,v := range arr {
go func(key, value int){
fmt.Println(key, value)
}(k,v)
}
// 输出结果
1 2
0 1
3 4
2 3
4 5

第一个程序可以看到,结果没有达到预期,k和v是引用传递。

第二个程序使用函数参数显示的传入函数中,k和v是值传递。

对象滥用的后果

由于Golang会自己回收内存,因此不担心内存泄漏,可以用大胆地创建对象。但是会带来其他问题,比如网络句柄泄漏、文件句柄泄漏。

Golang会帮你释放对象,但是Golang不会帮你释放对象拥有的资源。

Mysql索引设计和使用

原文链接:Mysql索引设计和使用
发布时间:2021-11-13 11:03:05

文章目录

1 概述

索引就是对某些字段提前排序,生成特定的数据结构(例如B+树或者哈希),然后加快查询。

2 设计原则

  • 要在过滤字段上创建索引。即where后出现的字段上创建索引。
  • 要在取值范围广的字段创建索引。比如用性别字段创建索引,那意义就不大。
  • 尽量使用短字段做索引。首先是节省空间,其次是IO较少,比较更快。
  • 主键尽量使用自增、整型字段。因为普通索引会保存主键,太长的主键会占用空间。
  • 创建联合索引时,利用最左字段查询。创建联合索引后,where后一定要使用上最左边的一个字段,否则根本使用不上索引。

3 设计误区

  • 每个表都要有索引。有的公司DBA,不管三七二十一,凡是创建的表都要有索引,否则不给审核,这明显谨慎过度,也能理解。对于数据量不大的表,也没有必须创建索引,反而会占用一定的空间。但是DBA会反驳说:既然数据量不大,创建索引也没有坏的影响。无可反驳。
  • 不要过度索引。索引当然要有,但是不可太多。因为数据更新的时候,同样会更新索引。太多的索引会影响数据更新的效率。另外,也会占用空间。

4 索引方法

包括2种: btree索引和hash索引

btree索引很常见,优势在于区间查询,适合的查询方式 >,>=,<,=<。

hash索引,顾名思义,底层是hash结构,查询单个值,常数级时间复杂度,不善于区间查询,适合的查询方式 = ,in。

实际中,似乎大部分情况用btree,如果某些字段不使用区间查询,可以用hash,比如某个字段是url。

5 索引类型

  • FULLTEXT 全文索引,用于模糊查询,对长字段做匹配。
  • NORMAL 普通索引,任意普通字段都可以创建。
  • SPATIAL 空间索引,这个暂时没有用过,不知道是什么东西。
  • UNIQUE 唯一索引,比普通索引多了一个唯一性约束,若是联合索引,字段组合起来必须唯一。

6 参考资料

  • 《深入浅出MYSQL:数据库开发、优化与管理维护》(第三版)

Mysql四种隔离级别

原文链接:Mysql四种隔离级别
发布时间:2021-11-11 21:37:22

文章目录

一、 查看隔离级别

首先隔离级别是Mysql的一个参数配置,是可以修改的。

SQL为:

SHOW VARIABLES LIKE ‘transaction_isolation’;

+———————–+—————-+
| Variable_name | Value |
+———————–+—————-+
| transaction_isolation | READ-COMMITTED |
+———————–+—————-+
1 row in set (0.03 sec)

Mysql的默认隔离级别为 READ-COMMITTED (读已经提交)。

二、 四种隔离级别

有四种隔离级别,隔离程度依次加强。

所谓隔离级别,就是衡量事务之间的隔离程度,衡量事务之间的相互影响程度。最理想的转态,是事务之间绝对独立,互不影响,当然这样就会牺牲Mysql的性能,无法实现高并发。

隔离程度太弱或者太强,未必能满足需求,需要根据需要来配置隔离级别。因此,就定义四种隔离级别。

2.1 read uncommitted ( RU,读未提交 )

这种隔离程度最弱,其实就是没有任何隔离,会读到脏数据,即可以读到别的事务还未提交的数据。

2.2 read committed (RC,读已提交 )

在自己的事务中,可以读到别的事务已经提交的数据,即在自己的事务期间,如果别的事务提交了数据,则可以读到。

2.3 repeatable read (RR,可重复读)

即自己的事务期间,多次执行同一个查询,可以得到相同的结果。这个比 read committed 更加隔离了,别人在此期间的更新也感知不到了。

2.4 serializable 可串行化

可以理解是串行化操作。并发读没有问题,若是有个事务是写操作,就会加锁,其他的事务无论读写都要被锁住,直到那个获得锁的事务commit,然后才释放锁。可以看到,这里无法实现高并发了。

三、 隔离解决的问题

为什么需要事务隔离,是需要解决以下问题。

3.1 dirty read 脏读

一个事务读到了另一个事务未被提交的数据。

non-repeatable read 不可重复读

同一个事务中,多次读取某一条记录,获得的结果不同。即感知到了别的事务对某条记录的修改。例如修改了或者删除了某一行数据。

phantom 幻读

同一个事务中,多次查询,查询的记录数量不同。与不可重复读很像,幻读是指多次读取,但是读了新增的数据,不可重读是指多次读取,但是某些行的数据被修改或者删除了。

所以,insert会导致幻读,update和delete会导致不可重复读。

3.4 对应的解决方法

隔离级别脏读不可重复读幻读未提交读(Read uncommitted)可能可能可能已提交读(Read committed)不可能可能可能可重复读(Repeatable read)不可能不可能可能可串行化(Serializable)不可能不可能不可能

可以看到:

  • 可重复读能解决脏读和不可重复读的问题,但是不能解决幻读的问题。
  • 可串行化可以脏读、不可重复读和幻读的问题。

为什么可重复读不能解决幻读的问题呢?
我个人的理解是:可重复读使用了行级锁,可能保证当前读到的行可重复读,但是会读到新增的数据,那么可串行化应该是使用表级锁解决了这个问题。

C语言实现选择排序

原文链接:C语言实现选择排序
发布时间:2021-10-13 23:11:52

文章目录

算法描述

  • 第一步,找到数组中最小的数据,把它和第一个数据交换位置
  • 第二步,在剩余数据中,找到最小的数据,把它和第二个数据交换位置
  • 如此不断地重复,直到所有的数据排好顺序

因为这种算法,总是在剩余数据中选择最小的数据,所以叫做选择排序。

C语言代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>

void SelectSort(int data[], int len) {
for (int i = 0; i < len; i++) {
int min = i;
for (int j = i + 1; j < len; j++) {
if (data[j] < data[min]) {
min = j;
}
}
int temp = data[i];
data[i] = data[min];
data[min] = temp;
}
return;
}

int main()
{
int arr[5] = { 4,5,10,3,20 };
SelectSort(arr, 5);
for (int i = 0; i < sizeof(arr) / sizeof(int); i++) {
printf("%d,", arr[i]);
}
printf("\n");
return 0;
}

算法特点

  • 不稳定的排序算法 因为交换位置,打乱了相对顺序。
  • 运行时间和输入无关 没有利用数据的输入状态,就算输入是已经有序的数组,排序时间仍然不会缩短,却和无序数组所需要的时间一样长。
  • 数据移动最少 即总共使用N次交换。

时间复杂度

对于长度为N的数组,需要

  • 交换次数:N
  • 比较次数:(N-1) + (N-2) + … + 1 = N(N-1)/2

所以,时间复杂度是:O(N²)

写博客的原则和方法

原文链接:写博客的原则和方法
发布时间:2021-09-05 19:47:22

文章目录

原则篇

1、不写安装教程类博客

比如Mysql安装教程,Redis集群搭建,Golang在Linux上安装配置教程等等,这类教程网上非常多,也有一定的作用。但是,却不是我写博客的范围。

因为,我觉得这种工具类的软件,其官方会有对应的教程,我不用去写这类教程。另外,纯操作类型的教程仅仅指明如何操作而已,既没有说明底层原理,也没有说明这样操作的目的,反正就是跟着操作就对了。参与者没有多少思考和学习的空间。

但是,可以写某个工具的使用总结,比如它有哪些坑,有哪些优势和缺点,它的常用操作方法,比如我的博客《使用dlv调试golang程序》《git使用》《tcpdump简明实用教程》

2、用自己的语言来表达

一些核心概念、设计原理等等,一定要用自己的语言表达出来,不要复制别人的,也不用复制官方的原话。因为只有自己的语言表达出来,才算真正理解某个知识和技术。另外,对自己的语言重述知识的过程会加深你对这个知识的思考,这是一个强制再次理解的过程。

只有用自己的语言表达出来,才表示你真正拥有了这个知识。

3、站在他人的肩膀上

我们都没有发明知识,都是知识搬运工而已,要学会站在别人的肩膀上。在写博客的时候,我们可以引用别人博客的研究成果,然后用自己的语言重述这个知识,当然要加上引用链接。我们没有必要对某个知识从头开始研究,除非某种情况特殊需要。比如我们使用牛顿定律的时候,就是直接使用,没必要自己重新发明和证明一遍再使用。记住那句话,绝大多情况下,我们都是知识的搬运工而已。

方法篇

1、平时注重记录

写出一篇博客,并非一蹴而就,而是要有大量的素材。这个素材哪里来呢?平时要多记录。

记录网上搜索的答案。对于某个不懂的问题,然后去网上搜索,当搜索到答案后,就应该记录下来。因为这个问题,你之前不懂,你现在明白了,如果不记录下来,可能很快就忘了,下次还需要搜索。举例:git如何回滚未push的commit、什么是一致性hash算法,这样问题的答案应该记录下来,包括原文章的链接。

记录开发过程中犯的错误。你犯下的错误,之所以会犯下,肯定是某种思维定势,下次很有可能再犯。把它记录下来,并做说明分析,例如为什么犯,哪里理解错了。举例:Golang中map的遍历不是有序的、slice是值传递,初学者很容易犯这样问题的错误,应该记录下来并分析背后的真实原理。

记录新的理解和发现。以前不理解的东西,现在突然理解,或者有了更新的理解,要记录下来,因为灵感来之不易。举例:突然理解了top命令中的 load average 的含义,突然理解了mysql中索引的含义,突然理解了docker是什么东西,要记录这种新的理解,以备后续翻阅。

这些记录就是很好的博客素材。

2、平时注重总结

对于某个使用一段时间的技术,可以总结下使用心得,包括基本原理、常用操作、要避免的错误、它的优势和缺点、它的使用场景等等。那么,这样的总结写出来就是一篇博客。举例: git常用操作总结、docker常用操作总结,这种技术使用的总结非常有用,为下次再次使用提供宝贵的经验。

3、平时注重思考

对于多思考,就是想知道为什么这样做,为什么这样设计,它到底是什么?背后需要好奇心驱动。只有多思考,才会有更多的发现,才会理解的更深。举例:我们都会使用数据库索引,此时还要思考索引到底是什么东西?是什么数据结构?为什么这样设计?Redis也是很多人使用,那么Redis内部是如何组织数据的呢?Redis内部如何处理网络的?高可用方式有哪些?等等许多疑问。只有在多思考的基础上,才会去探究更多的问题,收获更多。那么,这些问题搞明白了,就可以写一篇博客了。

Golang中如何处理tcp粘包

原文链接:Golang中如何处理tcp粘包
发布时间:2021-09-05 18:05:37

文章目录

为什么要处理粘包

由于tcp协议是数据流传输,一次读数据不一定能得到一个完整的业务数据包,所以需要进行粘包处理,保证要处理的数据是一个或者多个完成的业务数据包。

处理粘包的方法

假设:

  • 包的格式为 四字节包头 + 包体, 包头指明包体的长度
  • 大端传输数据

处理过程:

  • 先读取四字节包头,解析出包头的长度bodyLen
  • 再读取bodyLen长度的包体,这次读取的数据就是一个完整的业务包bodyData
  • 把bodyData返回到业务层处理

处理代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
```go
const (
MAX_CONN_TIMEOUT = 30
PACKET_HEAD_LEN = 4
)

func Recv(conn net.Conn) ([]byte, error) {
//读取包头
head := make([]byte, PACKET_HEAD_LEN)
_ = c.Conn.SetReadDeadline(time.Now().Add(time.Second * MAX_CONN_TIMEOUT))
_, err := io.ReadFull(conn, head)
if nil != err {
log.Error("io.ReadFull() failed, error[%s]", err.Error())
return nil, err
}
bodyLen := binary.BigEndian.Uint32(head)

//读取包体
bodyData := make([]byte, bodyLen)
_ = c.Conn.SetReadDeadline(time.Now().Add(time.Second * MAX_CONN_TIMEOUT))
_, err = io.ReadFull(conn, bodyData)
if nil != err {
log.Error("io.ReadFull() failed, error[%s]", err.Error())
return nil, err
}

return bodyData, nil
}

总结

  • 每调用一次Recv函数就可以得到一个完整的业务包,返回到业务层进行处理
  • 代码中设置了超时时间,这个根据实际业务需要来设置,有些业务场景不设置也可以。如果不设置,那么读不够数据就会一直阻塞在那里。关于设置的超时时间的大小,也是根据实际业务场景,比如有的客户端与服务端之间是10秒一次心跳,那么超时时间设置就要大于10秒,比如20秒或者30秒等等。
  • Recv返回后,应当判断error是否为nil,进行错误处理,比如可能网络出错需要重新连接。

slice在Golang中的实现和应用

原文链接:slice在Golang中的实现和应用
发布时间:2021-09-05 12:36:27

文章目录

底层实现

1
2
3
4
5
6
struct Slice
{
byte* array; // actual data
uintgo len; // number of elements
uintgo cap; // allocated number of elements
};

我们知道slice是用来管理一个内存地址连续的内存空间,类似C++中的vector。由上面的定义可以看到,Slice在底层实现是一个结构体。

  • array 是内存空间的首地址
  • len 是内存中元素的个数,这个标记很重要,由它可以知道下一次插入数据时该插入到哪个位置
  • cap 内存空间的长度,这个标记同样很重要,由它可以知道什么时候空间被使用完,然后申请更大的空间。

基本过程:

不断地向slice插入新的数据,len会不断增大,当len 大于 cap时,会申请更大的空间(据我观察,申请的空间是原来的2倍),然后把原来的数据复制到新的空间中。

创建slice的三种方法

1、使用make创建slice

由上面定义可知,slice底层有三个参数,所以使用make创建slice也需要指定3个参数。

1
2
3
s1 := make([]int, 3)    // 有3个元素的切片, len为3, cap缺省此时等于len
s2 := make([]int, 3, 3) // 有3个元素的切片, len为3, cap为3
s3 := make([]int, 0, 3) // 有0个元素的切片, len为0, cap为3

其中s1和s2的定义方式,本质上是一致的。但是他们和s3不同,因为s1和s2已经有了3个元素,再插入一个新元素就会触发内存扩容,新的数据被插入第四个位置上。而s3内没有元素,下一个新元素被插入到第一个位置上。

2、使用数组创建slice

语法格式:

1
s := data[low : high : max]

同样是三个参数,但是这三个参数都是data数组上的索引位置,言外之意就是:假设data数组容量为10,那么这三个参数最大为10,超过10就是不合法,就会编译报错(out of bounds for 10-element array)。

所以:

  • low 确定了slice中内存的起始位置,即确定了array的值
  • high 确定了slice中len的值,len等于 high - low
  • max 确定了slice中cap的值,cap等于 max - low

另外有一点需要说明,这样产生的切片底层指向data数组,修改s中数据,就会修改到data中的数据。

举例:

1
2
3
4
5
6
data := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // 创建数组,容量为10,同时包含10个元素
s1 := data[1:3:8] // 包含2个元素为1,2, cap为7
s2 := data[0:10:10] // 包含10个元素,cap为10
s3 := data[10:10:10] // 不包含任何元素,cap为0
s4 := data[0:] // 包含10个元素,cap为10
s5 := data[:10] // 包含10个元素,cap为10

刚才说了,指定的三个参数,都是原数组的索引位置,但是原数组10个元素,其索引范围为0-9,怎么能出现10呢?这个需要从指针的角度去理解,索引为10,其实就是第11个元素内存的起始指针,同时也是第10个元素内存的终止位置,这个位置是确实存在,没有越界。比如s3的定义方式,就很特殊,它的容量为0。

3、直接定义

1
var s []int  // 表示一个容量为0的切片

向slice插入数据

直接使用在索引位置插入

1
2
3
4
5
6
data := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // 创建数组,容量为10,同时包含10个元素
s1 := data[1:3:8] // 包含2个元素为1,2, cap为7

// 此时 s1包含2个元素,即s1[0]为1,s1[1]为2
// 往s1[2]写数据,注意s1的cap为7,即索引位置最大为6
s1[2] = 100

使用append插入数据

1
2
3
4
5
6
// 等价于s1[2]=100
// append表示在切片尾部插入新的数据,相当于C++中的push_back操作
s1 = append(s1, 100)

s2 := data[5:7]
s1 = append(s1, s2...) // 表示插入多个元素,因为s2有2个元素,后面要加三个点,注意语法格式

append的语法格式:

1
2
s1 = append(s1, 100)
s2 := append(s1, 100)

必须要接收append的返回值,否则编译报错。

为甚么一定需要一个值接收呢?
因为slice本质是一个结构体,不是指针,是值传递。当append对一个slice操作后,增加新的元素,它的len值就变化了,需要返回新的slice结构体,必须要接收。

总结

  • slice是一个结构体,不是指针,map和channel是指针
  • slice使用append插入数据,就是一个动态数组,会自动扩容
  • 利用数组创建切片时,修改切片会修改原数组的数组(在扩容之前)

Golang和C++简单对比

原文链接:Golang和C++简单对比
发布时间:2021-08-29 14:08:11

文章目录

编译

都可以静态编译,直接编译成二进制文件。目前,许多语言(比如Java,C#)都是基于运行时,能静态编译语言的不多,Go算一个。同时,都可以跨平台。

内存管理

在C++中,需要自己使用new和delete管理内存,尽管C++已经有了智能指针,但是有一些坑,不是那么好用。

Go虽是静态语言,但是自己管理内存,减轻了程序员的心智负担。这一点,非常重要。因为C++程序的崩溃,大多数时候都是内存问题,比如内存泄漏,非常难以解决。

标准库

一门语言开发效率高不高,它的标准库起着关键的作用。Go的标准库十分强大,常用的库几乎都有,开箱即用,十分顺手。C++的标准库里面的工具并不多,很多时候只能下载第三方库使用。不过,boost是一个著名的C++库,包含了大量的常用库。

性能

关于性能,看过几家测试的文章,得出结论的是Go性能非常强。绝大部分场景,可能不需要拼语言本身的性能,代码如何写才是关键。这个问题,我不太感兴趣。

并发编程

并发编程是Go语言的一大特色,可以轻松实现高并发,在语言层面就支持。C++只能调动系统API开启线程实现并发,语言层面并不支持。

语法复杂度

Go语言的语法简单,本身的语言特性不多,只有很少的关键字。对比C++,其语法越来越复杂,C++创始人都不敢说自己掌握C++。

开发和调试难度

C++的内存问题,很难排查和调试,比如内存越界,从程序崩溃的栈信息上可能就看不出来是什么问题,定位问题很难。Go进程中会启动Go自己的运行时,抛出的异常会指明错误信息,很容易能排查出问题。

总结

Go和C++都是偏后台的语言,都可以用来开发高性能的后台服务,但是由于C++历史辉煌,C++还可以做前端开发,比如QT开发

写博客的五个好处

原文链接:写博客的五个好处
发布时间:2021-08-29 13:02:56

文章目录

绪论

写博客,其实是一件费力费时的事情,现实中没有多少人能长期坚持下来。我自己也是断断续续地写,也没有写坚持下来。在实践的过程中,我本身却是收益良多。我养成了记录的习惯,记录确实提高我的工作效率。

其根本原因:程序员本身就是一个知识工作者,每天的工作就是使用知识、创造知识。人的大脑,处理能力是有限的,记忆力更是不靠谱,因此做好知识管理可以分担大脑的负担。为什么有的人总是遗忘,总是粗心?光凭脑子,是记不住的,必然容易犯错。

我认为,写博客至少有几个方面的好处?

减轻记忆负担

我们的学的东西越来越多,接触到信息也越来越多,但是大脑的记忆力却在下降。因为随着年龄的增大,人的记忆力下降,这是必然的。

对于IT工作者而言,知识更新更快,新技术层出不穷。今天学的东西,可能过几天就忘。已经使用几年的编程语言,可能几个月不用就生疏了。当时理解得很透彻的技术原理,过不多久就不明白了,反而还需要重头理解一遍,浪费了不少时间和精力。

如果能把当时的所知道、所理解的东西记录下来,之后需要用到时候,再翻阅一遍自己的记录,就可以很快理解了。

减少检索时间

有的人说,你写的博客有什么用呢?你写的东西,网上都有,我搜索一下都能找到。这句话,对了一半,错了一半。对于,一些常识问题,确实不需要记录下来,网上都可以找到。但是,对于稍微专业和深入的问题,网上就不那么容易找到,你需要多检索几篇文章才能找到你想要的答案。

然而,多检索几篇文章,就会浪费了你不少时间,因为你需要通读它们,最关键的问题是你还得验证一下,这又浪费了不少时间。

如果,能在第一次网上检索的时候记录下来,下次再遇到,翻看自己的记录,直接就找到答案,而且是自己上次验证和使用过的,可以放心使用,这提高了效率。

知识积累显性化

你知道的知识仅仅存在你的脑子里,除了你知道,别人不知道,而且别人也不知道你是否知道。如果能把你知道的东西写出来,就是显性化的过程。

因为写出来后,你可以保存、分享和传播,变成了看得见的东西。 另一方面,别人从你的博客中,知道了你拥有什么的知识和技能,因此你的博客就是你的知识和经验的显性化,它在一定程度上体现了你个人的知识水平。

另外,一篇篇博客的真实存在,让你获得更加真实和持久的成就感。

有利于传播和复用

就算你过目不忘(不可能),永远记住你所学的东西,但是你的知识和经验,仅仅只能给你自己使用。但是,写出来的技术文章,可以供团队其他成员学习和使用,也可以发到网上供其他无数人的检索和使用。这样,知识和经验的传播,给别人复用,它的价值就放大了。

总结经验

最后一个好处,我觉得是总结经验,这个可能比较宽泛,对于工作经验、技术经验、学习经验等等,都可以进行总结。用一个比较时髦的词就是复盘,总结出好的东西,以便后续保持和发扬,总结出不好的东西,以便后续改进和避免

人类的进步,也都是在错误和失败中总结经验,然后不断前进。客观规律,也是从一个一个具体事情中总结出来的。因此,对个人而言,总结经验是非常重要的。