diff --git a/0-yES6ENP/index.html b/0-yES6ENP/index.html new file mode 100644 index 00000000..edf6b0a0 --- /dev/null +++ b/0-yES6ENP/index.html @@ -0,0 +1,488 @@ + + + + + + + + 深入理解计算机系统——函数调用与空间分配 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 深入理解计算机系统——函数调用与空间分配 +

+ + +
+ +
+

我们在编程序的时候,都会把某一个特定功能封装在一个函数里面,对外暴露一个接口,而隐藏了函数行为的具体实现,一个大型的复杂系统里面包含了很多这样的小函数,我们称之为过程

+

过程是相对独立的小模块,系统的运行需要这些过程的紧密合作,这种合作就是函数调用。

+

在一个函数执行时调用别的函数,比如 P 调用 Q,需要执行一些特定的动作。传递控制,在调用 Q 之前,控制权在 P 的手里,既然要调用 Q,那么就需要把控制权交给 Q;传递数据,就是函数传参;分配与释放内存,在开始时,Q 可能需要位局部变量分配空间,结束时又必须释放这些存储空间。

+

大多数语言都使用栈提供的先进后出机制来管理内存,x86-64 可以通过通用寄存器传递最多 6 个整数值(整数或地址),如果超过 6 个,那就需要在栈中分配内存,并且通过栈传递参数时,所有数据的大小都要向 8 的倍数对齐。将控制权从 P 转交给 Q,只需要将 PC(程序计数器)的值置为 Q 代码的起始位置,并记录好 P 执行的位置,方便 Q 执行完了,继续执行 P 剩余的代码。

+

在函数的传参、执行中,多多少少都需要空间来保存变量,局部数据能保存在寄存器中就会保存在寄存器中,如果寄存器不够,将会保存在内存中。除了寄存器不够用的情况,还有数组、结构体和地址等局部变量都必须保存在内存中。分配内存很简单,只需要减小栈指针的值就行了,同样释放也只需要增加栈指针。

+

在函数执行过程中,处理栈指针%rsp,其它寄存器都被分类为被调用者保存寄存器,即当过程 P 调用过程 Q 时,Q 必须保存这些寄存器的值,保证它们的值在 Q 返回到 P 时与 Q 被调用时是一样的。

+

所以递归也就不难理解了,初学算法总觉得递归有点奇妙,怎么自己调用自己,而实际上对于计算机来说,它和调用其它函数没什么区别,在计算机眼里,没有自身与其它函数的区别,所有被调用者都是其它人。

+

数组是编程中不可或缺的一种结构,“数组是分配在连续的内存中”这句话已经烂熟于心了,历史上,C 语言只支持大小在编译时就能确定的多维数组,这个多多少少有一些不便利,所以在ISO C99标准中就引入了新的功能,允许数组的维度是表达式。

+
int A[expr1][expr2]
+
+

因为数组是连续的内存,所以很容易就能访问到指定位置的元素,它通过首地址加上偏移量即可计算出对应元素的地址,这个偏移量一定意义上就是由索引给出。

+

比如现在有一个数组A,那么A[i]就等同于表达式* (A + i),这是一个指针运算。C 语言的一大特性就是指针,既是优点也是难点,单操作符&*可以产生指针和简介引用指针,也就是,对于一个表示某个对象的表达式expr&expr给出该对象地址的一个指针,而对于一个表示地址的表达式Aexpr*Aexpr给出该地址的值。

+

即使我们创建嵌套(多维)数组,上面的一般原则也是成立的,比如下面的例子。

+
int A[5][3];
+
+// 上面声明等价于下面
+typedef int row3_t[3];
+row3_t A[5];
+
+

这个数组在内存的中就是下面那个样子的。

+
+

还有一个重要的概念叫做数据对齐,即很多计算机系统要求某种类型的对象的地址必须是某个值 K(一般是2、4 或 8)的倍数,这种限制简化了处理器和内存接口之间的设计,甚至有的系统没有进行数据对齐,程序就无法正常运行。

+

比如现在有一个如下的结构体。

+
struct S1 {
+    int i;
+    char c;
+    int j;
+}
+
+

如果编译器用最小的 9 字节分配,那么将是下面的这个样子。

+
+

但是上面这种结构无法满足 i 和 j 的 4 字节对齐要求,所以编译器会在 c 和 j 之间插入 3 个字节的间隙。

+
+

在极客时间专栏中有这样一段代码。

+
int main(int argc, char *argv[]){
+    int i = 0;
+    int arr[3] = {0};
+    for(; i <= 3; i++){
+        arr[i] = 0;
+        printf("Hello world!\n");
+    }
+    return 0;
+}
+
+

这段代码神奇的是在某种情况下会一直循环的输出Hello world,并不会结束,在计算机系统漫游(补充)中也提到过。

+

造成上面这种结果是因为函数体内的局部变量存在栈中,并且是连续压栈,而 Linux 中栈又是从高向低增长。数组arr中是 3 个元素,加上 i 是 4 个元素,刚好满足 8 字节对齐(编译器 64 位系统下默认会 8 字节对齐),变量i在数组arr之前,即i的地址与arr相邻且比它大。

+

代码中很明显访问数组时越界了,当i为 3 时,实际上正好访问到变量i的地址,而循环体中又有一句arr[i] = 0;,即又把i的值设置为了 0,由此就导致了死循环。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/05lwXg0GQ/index.html b/05lwXg0GQ/index.html new file mode 100644 index 00000000..3e3d9aef --- /dev/null +++ b/05lwXg0GQ/index.html @@ -0,0 +1,521 @@ + + + + + + + + SEO 入门——你必须了解站长平台知识 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ SEO 入门——你必须了解站长平台知识 +

+ + +
+ +
+

我自己也刚接触建站不久,10 月 1 日开始购买服务器和域名学习建站,10 月 11 日一查已经被 Google 收录了 20 多个链接了,下面是我个人就我自己的实践总结的一点关于站长平台的知识。

+

基本上只要做的大的搜索引擎都会有站长平台的,站长平台是搜索引擎官方提供的辅助网站优化管理的工具,它可以针对我们的网站提供一定的优化方向,而且新站一开始搜索引擎的爬虫可能来都不会来,我们一开始也需要通过站长平台主动给搜索引擎提交我们的站点地图。

+

盘点业界主流站长平台

+

既然我们讲的是站长平台知识,肯定是得先要去了解一些主流的站长平台,那业内知名的站长平台有哪些呢?下面是 Guanngxu 为大家盘点的主流站长平台。

+

谷歌站长平台

+

入口:https://search.google.com/search-console

+

谷歌在搜索引擎领域的地位不言而喻,稍微遗憾的是它已经退出中国市场了,它的技术、算法等在业界都是一流,即使它已经退出中国市场了,但也绝对是一个不可忽略的存在

+

百度站长平台

+

入口:https://ziyuan.baidu.com/

+

目前国内使用最多的站长平台就是百度了,谷歌退出中国之后就百度一家独大,也是目前国内功能最完善的站长平台,对于网站管理、数据检测等都有一定的参考意义。

+

搜狗站长平台

+

入口:http://zhanzhang.sogou.com/index.php/site/index

+

搜狗的功能相对来说简单一些,只提供了一些基础的网站优化功能,虽然目前看搜狗搜索的流量也跟不上,搜狗现在已经被腾讯收购,不知道后续是否会有更大的发展,在国内搜狗的流量也还是不小的。

+

必应站长平台

+

入口:https://www.bing.com/toolbox/webmaster/

+

必应是微软旗下的搜索引擎,必应在国内的市场占有率不太高,使用必应的大部分是讨厌百度的广告,又暂时不知道如何科学上网的人群,不过它的站长平台做的相对来说还是可以。

+

头条站长平台

+

入口:https://zhanzhang.toutiao.com/

+

大家都知道字节跳动是互联网界的一匹黑马,字节跳动最近正在抖音等旗下的应用开始测试搜索广告业务,虽然头条站长平台很多功能处于不完善的状态,但是它之前的成绩也是大家有目共睹的,值得期待。

+

神马站长平台

+

入口:https://zhanzhang.sm.cn/

+

神马站长平台是依托于 UC 浏览器和神马搜索衍生出来的一个平台,它大部分的流量都来自于移动端,所以它平台的功能比较偏向于移动端,功能比较基础,几乎没有什么算法和优化的通知。

+

利用站长平台我们可以做些什么?

+

新站一般都不会有搜索引擎的爬虫程序主动来爬取的,人家甚至都不知道你这个小网站的存在,那么我们就需要主动的去告诉各个搜索引擎,让他们知道我们这个小网站的存在。比如百度站长平台,我们可以先到「站点管理」添加一个自己的站点。

+
+

说一下每个站长平台都具备的一个功能,那就是提交自己网站地图的接口。新站一开始都可以通过这些接口去提交我们网站的链接,将站点提交到各个搜索引擎以后,可以加快蜘蛛抓取我们网站的速度,增加网站被收录的速度。当然也有人说用老的域名比较好,这一点我自己并没有亲身尝试过,不敢妄下断论。

+
+

像必应站长后台还有个网站扫描功能,谷歌也有类似的网址检查功能,我们可以借用这些工具看看如何优化自己的网站,让自己网站的内容对搜索引擎爬虫程序更加友好。

+
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/0WoAZKl_S/index.html b/0WoAZKl_S/index.html new file mode 100644 index 00000000..4a1ec240 --- /dev/null +++ b/0WoAZKl_S/index.html @@ -0,0 +1,471 @@ + + + + + + + + 记录在南京大学半天 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 记录在南京大学半天 +

+ + +
+ +
+

因为工作需要,到南京出差了半个月,中间利用周末和最好的朋友疯了一天,之后自己又一个人到南京大学鼓楼校区逛了逛。

+
南京大学钟楼
+

不会勾搭妹子的我总是能勾搭到老爷爷,到南大就勾搭了一个 86 岁高龄的老教授,他毕业于中山大学,年轻时候是做地质工作的。

+

我就像个熊孩子一样要爷爷给我讲有趣的故事,要听他讲我们这一代人或者是大部分人都不知道的历史。

+

爷爷虽然已经是快到耄耋之年的人了,但是对年轻时候的事记得很清楚,只是对最近的事记不起来。这篇文章仅仅是记录一下爷爷所讲的趣事。

+

爷爷年轻时候接到中科院的任务,前往内蒙古考察。在考察期间他们用汽车压死过一只狼,而且当时吃了狼肉,一行 30 多个人都吃过那匹狼的心,但是没有吃过狗肺。

+

据爷爷说,狼是很狡猾的动物,他们用汽车去追狼,狼就在原地不跑,等到你离它只有 10 来米的时候,突然拐弯跑了,这样的情况他们一共遇到了 6 次。这和《狼图腾》一书中的描写基本一致,狼有先进的军事文化。

+
南京大学教授
+

爷爷告诉我,南大起源于金陵大学,南京大学的标志性建筑「北大楼」是个教堂的样子,金陵大学本来是个教会大学,现在的「北大楼」就是原来的「钟楼」。

+

南大的地下有隧道,是当年毛主席提倡「深挖洞、广积粮、不称霸」时挖的,目的是为了防空。后来被南京食品公司用来存放香蕉,就是那种没有熟的香蕉,在隧道里面放熟了,再拿出来卖。不过现在隧道所有的口都没堵上了,完全废弃了。

+

在南大,有一些楼中间有 5 层,然后到两遍就只有 3 层了,整体看来像是个三角形。实际上这些楼当年都是要修 8 层的,因为那时候没钱,建着建着发现没资金了,所以就封顶了。

+

但是南大计算中心那栋楼只有 3 层却不是因为没钱,而是因为它旁边是消防大队,本来也是要建 8 层的,消防队说建高了挡住了他们视线,不能及时发现火情。爷爷笑着对我说:“但是也没见他们在上面拿个望远镜望啊!”。

+

我们都知道「五四运动」,但是却很少有人知道「四五运动」,这个运动的起源就在南大,当时 300 多学生(我回来查资料说是 400)发起了这个运动,后来演变为全国性的运动,直接带动了半年后四人帮被粉碎。

+

那是爷爷是个老师,他说他们教职工是很支持这些学生的,但是不敢公开性的支持。学生们很聪明,把标语刷到火车上,但是所有出南京的火车都被四人帮用水把标语给冲刷掉了,学生们就用沥青往火车上面写,才通过火车把这项运动的信息带到了全国各地。

+
+

我回来后查了一点资料,「四五运动」的起源是因为周恩来总理的去世,四人帮居然压制人民群众悼念周恩来,诬陷邓小平,而那时的毛主席也已经病到无法行动。

+
+
+

人们把花圈都放到人民英雄纪念碑前悼念周总理,却被四人帮给清理了,北京广大人民群众在“还我花圈,还我战友”的口号下行成了天安门广场大规模的群众抗议运动。

+
+
+

那也是一个诗意的年代,人们通过写诗来表达自己心中的愤怒,把小瓶子挂在树上,蕴意着期待邓小平的归来。那段时间四人帮应该是很难过的,从姚文元的日记就可以看出来。

+
+

爷爷还给我讲了一点他们的研究,他们研究行政规划的很多人认为,中国现在的行政划分有很多缺点的,中国应该划分 50~80 个省级单位。现在中国的行政级别也有问题,宪法规定行政层级只有三级(这一点我没查),而现在很多地方县下面是镇,镇下面还有乡,严格讲这是违宪的。

+

快到午饭时间时,爷爷还教我写了一会儿字,有的简体字很难看,比如「龍飛鳳舞」用繁体字写出来很好看,但是用简体字写出来就特难看。要想练好毛笔字,把三个字写好了就行了,然而我现在只记得一个“飛”字了,这可能就是老师们常说的「你又还给我了」。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/0i4w5ceIc/index.html b/0i4w5ceIc/index.html new file mode 100644 index 00000000..5167aceb --- /dev/null +++ b/0i4w5ceIc/index.html @@ -0,0 +1,557 @@ + + + + + + + + 如何搭建一个属于自己的博客/企业网站 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 如何搭建一个属于自己的博客/企业网站 +

+ + +
+ +
+
+

参考内容:
+如何做博客/企业站以及注意事项
+Typecho支持Emoji表情方法

+
+

说明:此篇文章得益于王红星的指导,喜欢直接粗暴一点的朋友可以跳过前面,直接从程序的选择开始阅读。

+

我的博客搭建之路

+

说起来有点惭愧,我自己是计算机科学与技术专业出身,虽然博客系统在我眼里是很简单的系统,但是我却一直畏惧从零开始搭建一个系统的麻烦性,因为但是安装程序的运行环境就会耗掉我大量的时间,再加上我写代码、测试、上线等工作少说也得要四五天!所以我一直都在 CSDN 一类的平台写作,然而这些平台为了利益把体验做的越来越差!

+

还在大学时听说过 WordPress 可以搭建博客,不过总是迈不出行动的步伐,认为一旦涉及到服务器的工作就不会简单。直到我在 Twitter 发现有人推荐 Gridea,才发现搭建博客系统原来可以这么简单,主要还是免费的。

+

于是我借用 Gridea 和 Github Pages 搭建了一个博客系统,EryouHao 开发的这个写作软件用起来相当便捷。但是 Github Pages 在国内总是速度很慢,而且图片资源经常加载不出来,而且 Gridea 是用 Vue 技术开发的,打包成桌面软件后总会出现卡顿情况,自己写作的那个劲又逐渐褪去了。在2020 年国庆观《我和我的家乡》有感所写中也提到在知乎一不小心就违反了社区规范,索性花功夫研究如何搭建自己的博客系统,也才有了这篇简单的教程。

+

一个博客/企业站需要什么

+

可以在网络上访问的东西背后都有一套程序支撑,博客系统(企业站点)就是运行在某处的一套程序。要把一套程序运行起来,肯定需要运行程序的空间和驱动程序运行的系统,比如我们的手里的电脑就是一种运行程序的空间,你使用的系统(Windows、macOS、Linux)就是驱动程序运行的系统,下面推荐的服务器都已经把把空间和驱动给你安装好了,所以完全不用担心自己不懂如何安装。

+

程序运行起来了还得让外面的人看到才行,所以我们还需要一个域名(类似于www.baidu.com一样的东西),我们把这个域名绑定到服务器上,别人在浏览器输入这个域名就能看到我们的博客(程序)了。

+

如果希望自己的博客打开速度很快,除了选择比较好的空间外,还可以购买 CDN 服务;希望自己的文章能更好的被百度之类搜索引擎收录的话,可以购买独立 IP 主机。

+

程序的选择

+

WordPress 应该是目前全球使用的最广泛的开源程序,结构良好,功能强大,社区内容丰富。但是对于博客(企业站点)来说,WordPress 就显得比较臃肿。Typecho 是一个轻量、简洁、快速的程序,除了官方论坛,在https://typecho.me/也有很多主题和插件。虽然 Typecho 各种主题、插件没有 WordPress 丰富,但是对于搭建博客系统已经足够了。

+

网上也有文章对两个系统做了对比,比如这篇:个人博客平台选择 Typecho 还是 WordPress ?Typecho 是原生支持 Markdown 语法的,可能是喜欢 Markdown 写作同学的福音,具体选 Typecho 还是 WordPress 可以凭自己的感觉,两个程序之间也是可以互相迁移的。

+

WordPress 安装包下载:https://wordpress.org/download/(12.4M)
+Typecho 安装包下载:http://typecho.org/(400K)

+

空间的选择

+

空间类型包括服务器、VPS和虚拟主机,它们的价格是逐渐减少的。对大部分人来说并不是越贵越好,比如服务器和 VPS 还需要自己懂一些运维知识才行。仅仅只是搭建博客(企业站点)的话,一个虚拟机就足够使用了,又便宜又不费心。

+

空间地理位置是需要重点考虑的一个因素,如果你做的是英文站点、都是国外用户,那可能一个美国主机更适合你;如果你的用户是国内的话,那大陆主机和香港主机可能更适合你。

+

王红星在他的文章中推荐了两个主机商,分别是衡天主机戈戈主机。我自己选的是衡天主机,有问题可以随时通过他们的客服询问或是提工单,体验很不错!偷偷告诉你,在购买前可以搜搜优惠码!阿里云也很不错,只是稍微有点贵!

+

域名注册

+

国内有腾讯云、阿里云、爱名网等域名注册商,国外有 Name、Namecheap、Godaddy、Dynadot 等域名注册商。有些域名商默认会提供域名保护功能,有的则需要购买该功能。各个域名注册商的价格也都差不多,根据自己的实际需求选一个就可以了。

+

我自己选的是腾讯云,它第一年域名的优惠力度比较大,比如我要注册guanngxu.com这个域名,可以看到都是有优惠的。

+

腾讯云:https://dnspod.cloud.tencent.com/
+阿里云:https://wanwang.aliyun.com/
+爱名网:https://www.22.cn/

+
+

域名解析

+

我们所有的准备工作都做好了,下一步就是安装程序搭建博客系统了,下面我以衡天主机安装 Typecho 为例进行一个简略的讲解,结合我下面的内容和网上的资料,应该很快就能搭建自己的博客系统了。

+

主机购买后就知道它的 IP 地址了,首先我们去域名注册商处选择把域名解析到我们买的服务器上面。以腾讯云为例,在「我的域名」页面点击「解析」-->「快速添加网站/邮件解析」后会弹出如下页面。

+
+

选择「网站解析」的「立即设置」后会弹出另一个页面,你只需要在这个页面把你购买的主机 IP 地址填进去就可以了。稍等几分钟直接在浏览器输入域名访问,如果浏览器出现了页面而不是报找不到服务器 IP 地址,那么域名解析就完成了。

+
+

安装 Typecho

+

进入到云主机管理面板后,点击「文件管理器」可以看到如下图的目录,其中public_html目录就是咱们博客程序的目录,我们把下载好的 Typecho 包上传到这个目录并解压。

+
+

需要注意的是解压后的目录是build目录,我们需要将解压后的目录移动到上一级,,保证public_html目录看到的是下图这个样子的,然后再输入域名去访问自己的博客。

+
+

如果不出意外,输入域名后出现的应该是下面的页面,点击「我准备好了,开始下一步」发现了什么?需要填写数据库信息。我们再回到衡天云主机管理面板,滑到「数据库管理」选择「新建数据库」,设置数据库名、数据库用户名、密码等,点击「创建」即可,这些信息就是安装 Typecho 需要的信息,再回到之前的页面把这些信息填进去,自己的博客系统就搭建完成了。

+
+

那么现在你就拥有了一个自己的博客系统,如果样子不太好看你可以去官方论坛或https://typecho.me/里面找自己喜欢的主题,如果有能力甚至可以自己修改或原创主题,一些插件在上面也都是可以找到的。

+

Typecho 官方也提供了相应的安装文档,如何使用插件、如何调整网站外观等常见问题在官方链接http://docs.typecho.org/doku.php也都已经有说明了,此处便不再作赘述!

+

让 Typecho 支持 Emoji 表情

+

21 世纪的互联网时代怎么能少了 Emoji 表情呢?由于编码问题,Typecho 默认是不支持 Emoji 表情的,所以我们只需要将编码改成支持 Emoji 表情的编码就可以了,具体一点就是把原来的utf8编码修改为utf8mb4,修改编码的方式如下:

+

修改数据库编码

+

在衡天云主机管理面板选择「phpMyAdmin」,然后选择你刚才建立的数据库,选择「操作」-->「排序规则」-->「utf8mb4_unicode_ci」-->「执行」

+

修改表编码

+

在 phpMyAdmin 面板点击「SQL」,直接运行下面的语句就可以了。要注意你所建表的前缀,我建表所填写的前缀是gx,所以我都是gx_xxxxxxxxx,默认前缀是typecho

+
alter table gx_comments convert to character set utf8mb4 collate utf8mb4_unicode_ci;
+alter table gx_contents convert to character set utf8mb4 collate utf8mb4_unicode_ci;
+alter table gx_fields convert to character set utf8mb4 collate utf8mb4_unicode_ci;
+alter table gx_metas convert to character set utf8mb4 collate utf8mb4_unicode_ci;
+alter table gx_options convert to character set utf8mb4 collate utf8mb4_unicode_ci;
+alter table gx_relationships convert to character set utf8mb4 collate utf8mb4_unicode_ci;
+alter table gx_users convert to character set utf8mb4 collate utf8mb4_unicode_ci;
+
+

修改数据库配置文件

+

数据库配置文件在public_html文件夹下,文件名为config.inc.php,其中有一行的内容是charset => utf8,将它修改为charset => utf8mb4就可以了。

+

看看有了小表情是不是很可爱?

+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/1M5AYjF7f/index.html b/1M5AYjF7f/index.html new file mode 100644 index 00000000..c2196f6e --- /dev/null +++ b/1M5AYjF7f/index.html @@ -0,0 +1,640 @@ + + + + + + + + JavaScript 性能优化——惰性载入函数 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ JavaScript 性能优化——惰性载入函数 +

+ + +
+ +
+
+

参考资料:
+《JavaScript 高级程序设计(第三版)》
+JavaScript专题之惰性函数
+深入理解javascript函数进阶之惰性函数

+
+

因为不同厂商的浏览器相互之间存在一些行为上的差异,很多 js 代码包含了大量的if语句,将执行引导到正确的分支代码中去,比如下面的例子。

+
function createXHR() {
+    if (typeof XMLHttpRequest != 'undefined') {
+        return new XMLHttpRequest();
+    } else if (typeof ActiveXObject != 'undefined') {
+        if (typeof arguments.callee.activeXString != 'string') {
+            var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'];
+            var i, len;
+            for (i = 0, len = versions.length; i < len; i++) {
+                try {
+                    new ActiveXObject(versions[i]);
+                    arguments.callee.activeXString = versions[i];
+                } catch (e) {
+                    // skip
+                }
+            }
+        }
+        return new ActiveXObject(arguments.callee.activeXString);
+    } else {
+        throw new Error('No XHR object available.');
+    }
+}
+
+

我们可以发现,在浏览器每次调用createXHR()的时候,它都要对浏览器所支持的能力仔细检查,但是很明显当第一次检查之后,我们就应该知道浏览器是否支持我们所需要的能力,因此除第一次之外的检查都是多余的。即使只有一个if语句也肯定要比没有if语句慢,所以if语句不必每次都执行,那么代码可以运行的更快一些,惰性载入就是用来解决这种问题的技巧。

+

函数重写

+

要理解惰性载入函数的原理,我们有必要先理解一下函数重写技术,由于一个函数可以返回另一个函数,因此可以在函数内部用新的函数来覆盖旧的函数。

+
function sayHi() {
+    console.info('Hi');
+    sayHi = function() {
+        console.info('Hello');
+    }
+}
+
+

我们第一次调用sayHi()函数时,控制台会打印出Hi,全局变量sayHi被重新定义,被赋予了新的函数,从第二次开始之后的调用都会打印出Hello。惰性载入函数的本质就是函数重写,惰性载入的意思就是函数执行的分支只会发生一次。

+

惰性载入

+

我们来看一个例子(例子来源于冴羽所写的JavaScript专题之惰性函数)。现在需要写一个foo函数,这个函数返回首次调用时的Date对象,注意是首次。

+

方案一

+
var t;
+function foo() {
+    if (t) return t;
+    t = new Date()
+    return t;
+}
+// 此方案存在两个问题,一是污染了全局变量
+// 二是每次调用都需要进行一次判断
+
+

方案二

+
var foo = (function() {
+    var t;
+    return function() {
+        if (t) return t;
+        t = new Date();
+        return t;
+    }
+})();
+// 使用闭包来避免污染全局变量,
+// 但是还是没有解决每次调用都需要进行一次判断的问题
+
+

方案三

+
function foo() {
+    if (foo.t) return foo.t;
+    foo.t = new Date();
+    return foo.t;
+}
+// 函数也是一种对象,利用这个特性也可以解决
+// 和方案二一样,还差一个问题没有解决
+
+

方案四

+
var foo = function() {
+    var t = new Date();
+    foo = function() {
+        return t;
+    };
+    return foo();
+};
+// 利用惰性载入技巧,即重写函数
+
+

惰性载入函数有两种实现方式,第一种是在函数被调用时再处理函数。在第一次调用的过程中,该函数会被覆盖为另外一种按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行分支了。

+

第二种实现方式是在声明函数时就指定适当的函数。这样第一次调用时就不会损失性能了,而是在代码首次加载时会损失一点性能,即是利用闭包写一个自执行的函数。

+

改进 createXHR

+

有了上面的基础,我们就可以将createXHR()改进为下列形式,这样就不用每次调用都进行判断了。

+
// 第一种实现方式
+function createXHR() {
+    if (typeof XMLHttpRequest != 'undefined') {
+        createXHR = function() {
+            return new XMLHttpRequest();
+        }
+    } else if (typeof ActiveXObject != 'undefined') {
+        createXHR = function() {
+            if (typeof arguments.callee.activeXString != 'string') {
+                var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'];
+                var i, len;
+                for (i = 0, len = versions.length; i < len; i++) {
+                    try {
+                        new ActiveXObject(versions[i]);
+                        arguments.callee.activeXString = versions[i];
+                    } catch (e) {
+                        // skip
+                    }
+                }
+            }
+            return new ActiveXObject(arguments.callee.activeXString);
+        };
+    } else {
+        createXHR = function() {
+            throw new Error('No XHR object available.');
+        }
+    }
+}
+
+// 第二种实现方式
+function createXHR() {
+    if (typeof XMLHttpRequest != 'undefined') {
+        return function() {
+            return new XMLHttpRequest();
+        }
+    } else if (typeof ActiveXObject != 'undefined') {
+        return function() {
+            if (typeof arguments.callee.activeXString != 'string') {
+                var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'];
+                var i, len;
+                for (i = 0, len = versions.length; i < len; i++) {
+                    try {
+                        new ActiveXObject(versions[i]);
+                        arguments.callee.activeXString = versions[i];
+                    } catch (e) {
+                        // skip
+                    }
+                }
+            }
+            return new ActiveXObject(arguments.callee.activeXString);
+        };
+    } else {
+        return function() {
+            throw new Error('No XHR object available.');
+        }
+    }
+}
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/2-3keFH0c/index.html b/2-3keFH0c/index.html new file mode 100644 index 00000000..2a44f5dc --- /dev/null +++ b/2-3keFH0c/index.html @@ -0,0 +1,650 @@ + + + + + + + + 电路 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 电路 +
+ + +
+

+ + LDO 基础知识 + +

+ +
+ + + + +
+ +
+ +参考内容: +ADI 公司 LDO 电容选型指南 +线性和低压降 (LDO) 稳压器 + +BUCK 电路通过控制占空比来达到降压的目的,添加 LC 二阶低通滤波器将高频部分滤除,即可达到稳定输出直流的目的。但是滤波不能完全滤除高频分量,BUCK 从原理上就决定了其纹波不容易做到很小,其固有的开关频率会导致电源噪声很大,用来给噪声敏感的元器件供电就不合适。 +相比 BUCK 来说,LDO(Low Dropout Regulaor:低压差线性稳压器)输出的电压会更加平稳,可以弥补 BUCK 输出纹波大的缺点。 +总体框图 +线性稳压器主要由四部分组成,基准源用于提供精准的电压基准、导通器件用于控制从 VIN 到 VOUT 的电流大小、误差放大器将强制反馈节点与基准电压匹配、反馈电阻用于调整以改变输出电压。 + +从框图中也可以看到线性稳压器只能用于降压,因此输入电压必须高于输出电压。当然其名字中本身带了低压差的,低压差就意味着少的发热,意味着电源转化效率的提升。线性则是指器件的工作状态,器件的内部模块工作在放大区,放大状态呈线性关系。 +工作原理 +线性稳压器的工作可以模拟为两个电阻器和一个用于 VIN 的电源,其中电源用于给负载供电,通过调整可变电阻(导通器件)的阻值来控制负载电阻所获得的电压,整个系统中唯一不变的恒定的参数就是输出电压 VOUT。 + +其稳压过程如下图所示,当负载电压升高/降低时,采样电路所采到的电压就跟着升高/降低,传递给误差放大器后通过调节导通器件的导通程度来调节输出电压。 + +导通器件 +导通器件常见的有 PMOS、NMOS、BJT 等。BJT 应用于大电流的场景。PMOS 不需要额外的电源轨即可控制其导通程度,但是相比 NMOS 其 RDSon 更大,即 PMOS 架构的 LDO 在芯片本身所消耗的能量会更大。 + +使用 NMOS 作为导通器件时,需要添加辅助电源轨或者使用电荷泵才能将 NMOS 打开。当然电荷泵也有其缺点,虽然电荷泵可以提升 VIN,但是也带来了额外的噪声影响。若采用辅助电源轨时则需要注意,VBIAS 会影响 NMOS 的导通程度,进而影响输出电压的大小。 + +PSRR +PSRR(Power Supply Rejection Ratio)量化了 LDO 抑制任何电源变化传递到其输出信号的能力,也就是 PSRR 决定了输入耦合到输出的噪声有多少。除了 LDO 本身的设计影响 PSRR 外,也可以通过调整 VIN 与 VOUT 之间的差值、输出电容来提高在特定应用(频率)下的 PSRR。 + + +输入输出电容 +为了确保 LDO 稳定工作,会在 LDO 输入输出端增加旁路电容,并且旁路电容的 ESR 需要很小,即在符合最小电容和最大 ESR 的要求下,使用任何质量良好的电容都可采用。在选择电容时还需要注意由于直流电压偏置、温度变化、制造商容差等需要对电容进行一定的降额。 +输出电容除了可以进行滤波外,还会影响负载电流的变化的瞬态响应,采用较大的输出电容可以改善 LDO 对大负载电流变化的瞬态响应。输入电容则可以降低电路对 PCB 布局的敏感性,尤其是在长输入走线或者高源阻抗的情况下。 +多层陶瓷电容、固态钽点解电容、铝电解电容通常用作输入和输出旁路电容。多层陶瓷电容具备 ESR 和 ESL 低、工作温度范围宽的优点,但是陶瓷电容中的介质材料具备压电性,振动或机械冲击可能会转化为电容上的交流噪声电压,在极端情况下可能会产生 mV 级的噪声。 + +压电性是在某些固体材料(晶体、陶瓷、骨头、DNA、蛋白质等)受到机械应力作用后,在材料中聚集电荷的现象。「压电」即由压力产生的电。 + +钽电容的优点是单位体积电容最高(CV 乘积),并且不太容易受到温度、偏执电压、震动效应的影响,在无法容忍压电效应的低噪声应用中,钽电容基本是唯一可行的选择。与陶瓷电容相比,钽电容的泄漏电流要比等值的陶瓷电容大很多倍,不适合超低电流应用。 +铝电解电容往往体积较大、ESR 和 ESL 较高,漏电流相对较高,与钽电容一样不受压电效应影响,适合要求低噪声的应用场合,但是铝电解电容在航天应用中禁止使用。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 半导体功率器件 + +

+ +
+ + + + +
+ +
+ 高中时候我们在化学课程中学过元素周期表,「氢氦锂铍硼、碳氮氧氟氖......」倒背如流,在元素周期表的中间三、四、五族元素定义为半导体元素,所谓半导体是根据其导电能力来定义的,我们可以通过一定的半导体工艺来改变其导电能力。 +以硅(Si)为例,硅是处在第四族的元素,它的外部有 4 个电子,所以硅的稳定结构是形成下图所示的及其稳定的共价键结构。 + +硅的两侧对应的是三族和五族的元素,三族的元素意味着外层有 3 个电子,五族的元素意味着外层有 5 个电子。如果把三族元素插入到四族的硅当中,由于硅想形成稳定的四个共价键结构,所以会存在一个空置的位置,我们称之为空穴,这个空穴是具备一定的正电荷的能力的,如此就形成了 P 型半导体。 + +同理,若使用五族元素与硅进行掺杂,就会多出一个可移动的电子,即存在自由电荷,形成了 N 型半导体。 + +PN 结(普通二极管) +当我们把 P 型半导体和 N 型半导体进行组合后,即可得到最基本的二极管(PN 结)。在 P 型半导体中存在高浓度的空穴(正电荷),在 N 型半导体中存在高浓度的电子,浓度高的载流子会自然而然向浓度低的区域进行扩散。 +由于载流子扩散,最终会形成一个势垒,这是一个空间电荷区,也称之为耗尽层。可以发现 PN 结存在 P 区、耗尽层、N 区三个区域,这几个区域都是呈现电中性的,不管是空穴还是电子想要到达另一个区域,都必须要穿过耗尽层,即耗尽层会阻碍空穴和电子的运动,因此整个 PN 结在没有外界干扰的情况下,是不具备导电能力的。 + +当我们从外界施加 N 到 P 的电场时,即 PN 结反偏。此时外界电场与耗尽层电场是同向的,所以在外部电场的作用下,耗尽层的宽度会被加强,于是 PN 结的导电能力就变得更弱,因此就呈现了一个无导电能力的特性。 +当然导电只是一种相对情况,即便空间电荷区变宽了,也不能百分百保证说就完全没有导电能力,因为还是有一定的空间电荷浓度,在这样的情况下会有微弱的电流流经 PN 结,意味着系统存在一个反向电流,这就是二极管一个比较重要的漏电流参数。 + +当外部施加的电场是从 P 到 N 时,即 PN 结正偏。外界电场的效果是使耗尽层变窄,加强了 P 区内空穴往 N 区内移动的能力,扩散电流远大于漂移电流,形成了一个正向导通电流。 + +最终二极管将呈现如下的导通特性,当正向电压大于势垒电压时,二极管开始导通。当施加反向电压时,二极管将截止,当反向电压大到一定程度后,二极管就会被反向击穿,即二极管损坏的过程。 + +功率二极管 +既然谈到了「功率」二字,那么更加关注的就是二极管承载电流、电压的能力了。如何把二极管承载电流、电压的能力加强呢?根据上文关于二极管的介绍可以知道,将耗尽层加宽可以承载更大的电压。 +图中中间 n- 为轻度参杂区域,下面 n+ 为重度参杂区域,这个参杂就导致了耗尽层的加宽,当然也导致导通损耗更大,不过也正因为如此,功率二极管才更加能耐压。 + +我们以非同步 BUCK 电路为载体,来说明一下功率二极管的变化过程。 + + +图中(1)部分指二极管导通,有一个小小的二极管导通压降,因此曲线没有贴着 x 轴; +图中(2)的位置由于二极管承受的是反向电压,此时它关断了,所以电压为负; +图中(3)二极管需要经历一个从没有电压到有外加电压的变化,当电压加到二极管上时,二极管中的载流子流动的趋势逐渐增大,宏观表现出来是电阻慢慢变小的过程,但是电流保持不变,所有会有一个小尖峰,这一小段时间也会导致整体功率的损耗,开关频率越高,这个导通过程导致的损耗越多; +图中(4)处伴随系统从通到断的状态变化,大规模载流子需要进行重新分配,这个重新分配表现出来就是电流,而且这个电流与主电流相反,所以会看到一个反向的电流,而且这个反向电流会施加在主电路里面。这一段反向电流又分为两部分,下降阶段是之前外加电压时,PN 结中从 P 区域移动到 N 区域的载流子移除(恢复)过程,即从正偏到反偏的过程,正偏时空间电荷区非常非常窄,此时要进入反偏状态,空间电荷区需要加强,载流子需要重新分配,外部激励会移除不必要的空间电荷。电流上升的过程,即二极管又变成一个耐压器件了,也就是空间电荷区加宽,更多的载流子会不均匀的分布在两端。整个过程不可避免的需要移动电荷,而电荷的聚集效应可以认为就是一个电容的效应,当我们需要施加电压时,电压的增加就会需要额外的电荷,电荷不断聚集提供相反电荷,使其电压不断增加,以致增加到刚好截止输出电压为止。 + +MOS 管 +以 NMOS 为例,它以 P 型半导体衬底,以 N 型半导体作为导电沟道,金属部分作为栅极(Gate),氧化部分(SiO2)作为绝缘层,两端分别为源极(Source)和漏极(Drain),从物理结构可以看出 MOS 管的源极和漏极是可以互换的,不像三极管有严格的顺序。 +在栅极和源极施加电压,随着电压的不断增大,导电沟道将逐渐形成,当导电沟道刚好形成时的电压,称之为开启电压。外加电压继续增大,导电沟道将变得越来越宽,即导电能力越来越强。 + +PMOS 相比 NMOS 更加容易驱动,只需要 VGS 小于一定值即可导通。但是 PMOS 的导通电阻比 NMOS 要大,并且成本也比 NMOS 要高,所以比 NMOS 的实际应用场景要少许多。 + +功率 MOS 管 + +对比前文普通 MOS 管,可以看到源极、栅极、漏极是分开的,顶上那个灰色的板子是金属板。而功率 MOS 管在这个基础上做了一点创新,下图中的阴影部分就是金属板,可以发现总共只有两个金属板,上面的金属板把 N 区和 P 区都给连起来了,所以即使在栅极没有加电压的时候,也会存在一个天然的二极管通道,但是普通 MOS 管是没有体二极管通道存在的。同时由于是功率 MOS 管,所以也会想办法将耗尽层加宽,以增加其耐压能力。 + +体二极管和耐压能力的加强是功率 MOS 和普通 MOS 的区别。 + +功率 MOS 管的正向导通能力就是涉及「场效应」了,所谓的场效应即意味着外部可以通过电场来控制其内部载流子的浓度,在栅极施加正电压时就会产生一个电子的导电沟道,由于整体是 N 型半导体衬底,所以整体也就形成了一个电子的导电沟道,并且该沟道支持电子的双向移动。 + +如下图所示是功率 MOS 管的等效电路模型。其主要损耗由三部分组成,分别为导通损耗、开关损耗(开通损耗和关断损耗)、驱动损耗。其中导通损耗与开关损耗容易理解,驱动损耗作何理解呢?MOS 并不像二极管是一个被动型器件,MOS 管开或关的行为都需要能量作为代价,就好比要打开机械开关需要用手去按压,这个过程所消耗的能量就是驱动损耗。 + +晶体管 +二极管只有一个 P 型半导体和一个 N 型半导体结合,如果再加一个 N 型半导体(或 P 型半导体)即构成了晶体管(三极管),晶体管有集电极、发射极、基极三个极。 + +需要注意的是三极管的集电区和发射区掺杂浓度是不一样的,其中基区多子少且做的很薄,而发射区的多子浓度很高,集电区多子浓度相对较低但面积大。不管三极管是正接还是反接,三极管都处于截止状态,这是因为三极管可以看作两个二极管反向相连,不论如何接都会有一个二极管处于截止状态。 + +为了能让三极管导通,我们在基极和发射极再施加一个电压,此时二极管开始导通,发射区的自由电子就可以源源不断的流向基区,但是基区的掺杂浓度很低且很薄,基区短时间内吸收不了太多的电子,只有一少部分电子能与空穴复合形成基极电流,而大部分被吸引到了集电区,形成集电极电流,也就是三极管的输出电流。 + +流过基极的电流越大,流到基区的自由电子也就越多,相应的被吸引到集电区的电子也就更多,这就是三极管小电流控制大电流的原理。基区做的很薄是为了让发射区的电子更容易进入集电区,浓度很低视为了形成更小的基极电流,这样才会有更多的自由电子流向集电区。 +IGBT +三极管工作时涉及载流子的注入和抽离所以会很慢,由于其性能的关系正在逐步退出历史舞台,因此需要对其进行改进,改进后的器件就是 IGBT,如下图所示。 + +可以发现 IGBT 是一个受 MOS 管控制的 BJT,即同时继承了 MOS 管快速和 BJT 大电流的优点。当然,它也有缺点,并且缺点主要来自于 BJT 关断较慢的问题,因为当 MOS 管门级信号撤出时,并不能立马把电流都抽走,所以电流会经历一段下降时间。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + BUCK 电路基础知识 + +

+ +
+ + + + +
+ +
+ +参考内容: +手撕Buck!Buck公式推导过程 +电力电子的基础应用 +《精通开关电源设计(第二版)》 +Buck电源芯片输出有问题?检查这几样 +原来PWM这么简单! +为什么 DC-DC 芯片设计中都有一个自举电容? +如何克服开关电源中的最小导通时间挑战 + +BUCK 电路构建 +根据高中所学习的物理知识可以很容易的想到,使用一个滑动变阻器即可实现降压和稳压的效果。当负载波动时,通过改变滑动变阻器的阻值,可以调节负载所获得的电压。但是使用滑动变阻器的劣势也很明显,大量的耗能会导致器件温度快速升高。 + + +上面所提到的电路主要缺点在于导通器件(变阻器或三极管)本身存在耗能,那么有没有不会耗能的导通器件呢?首先肯定不能选导线,不然又回到最原始的问题,所有电压都被加到负载上了。有没有能不耗能且能控制加在负载电压的导通器件呢?最常见的机械开关就能做到这个效果。 + +当开关闭合时,负载即获得电压源输出的电压;当开关打开时,负载所获得的电压为 0V。计算平均值可以确定达到了降压的目的,通过控制开关闭合的时间长短,就可以达到调节电压的效果。但仔细想想就会发现不对劲,电路并不会帮助我们计算平均值,负载所获得的电压波形如下图所示,是完美的方波,并不是一条直线。 + +控制开关闭合的时间,即后文要讲的控制占空比 + + +此时很容易就能想到利用电容两端电压不能突变的特点,给负载并联一个电容即可,电容即保证负载可以获得连续的能量流。 + +一旦引入了电容,就需要考虑浪涌电流的问题。根据公式 Q=CV=ItQ=CV=ItQ=CV=It 可得 I=CVtI=\frac{CV}{t}I=tCV​,开关闭合时电压在非常短的时间内升高,所以电流会突然变得很大。 +我们当然可以简单的利用电阻来抑制浪涌电流,但不幸的是电阻总要消耗功率。为了最大限度的提高效率,可以考虑使用电感,电感本身不消耗任何能量,只会进行储能,且其无损限流的能力正好可以用来抑制电容的浪涌电流。 + +引入电感后可以发现当开关打开时,电感没有续流回路,因此需要想办法构造电感的续流回路。续流回路需要保证不论开关打开还是闭合,电流都流向负载,且开关闭合时电源正极与负极回路必须经过电感与负载。这个需求很符合二极管的特点,即只允许单向导通。 + +到目前为止我们构建了非同步 BUCK 电路,考虑到机械开关容易磨损、使用寿命短、有机械惯性(转换频率低)的问题,我们需要将机械开关换成转换频率高的半导体器件,此处我们选择 NMOS 管来替代开关。 + +选择 NMOS 和 PMOS 的主要区别在于驱动电路的设计 + + +可以发现当 NMOS 开关管导通时,续流二极管处于截止状态;当开关管关断时,续流二极管处于导通状态。即二极管的导通和截止和开关管的截止导通是同步的,也就是说二极管起到的是一个开关的作用。而且考虑到电流从二极管流过期间,二极管两端的压降恒定为导通电压 0.7V,二极管所消耗的能量较大。因此我们也可以把二极管换为导通电阻更小的 NMOS 管。 + +为了提高 BUCK 电路的稳定性,防止由于输入纹波带来异常,我们在 BUCK 电路的输入端并联一个电容,用于滤除输入电压的纹波。 + +至此我们就搭建了一个标准的同步 BUCK 电路,我们将其简单变个样子,再加几个标签即可得到下图。在同步 BUCK 电路中需要两个开关管密切配合,以防止整个线路导通,所以它们之间需要保持一定的相位关系,即上管导通下管截止;上管截止下管导通,我们把这种关系称之为同步。 + +信号角度理解 LC +我们以占空比为 0.5 来进行说明,将时域下的方波转换到频域,通过傅立叶变换可以分解出一系列的频率分量。其中包含频率为 0 的分量,即直流分量,也就是我们想要保留的部分,还有频率为 n 倍 fsf_{s}fs​ 的分量。那么如何把我们不想要的部分去掉呢?从滤波角度考虑就需要加入一个低通滤波器。 + +通过加入低通滤波器可以把高频分量滤除,把二阶低通滤波器的截止频率设置在 0 到 fsf_{s}fs​ 之间,即可把 fsf_{s}fs​ 所有以上的部分给滤除。整体达到的效果即通过一个 LC 低通滤波器,配合一个开关网络,将一个数字化的电平重新滤出,得到一个比较平缓的电压输出,这个过程即完成了电压从高到低的转换。其中直流分量的大小受占空比 D 控制,所以通过改变占空比 D 即可改变输出电压大小。 + +稳态分析 +我们需要先强调一下前提,此处我们说的稳态分析,即输入电压输出电压都是稳定,且纹波足够小的状态。下文的所有计算都将基于稳态进行分析,并且是在 (F)CCM(连续导通模式)下计算的。 +我们将一些已知条件列出来: + +输入电压:ViV_{i}Vi​ +输出电压:VoV_{o}Vo​ +负载电阻:RRR +输出电感:LLL +开关频率:fff + +伏秒平衡 +当上管导通下管截止时,电感右边的电压为 ViV_{i}Vi​,左边的电压为 VoV_{o}Vo​,因为同步 BUCK 电路是降压电路,所以 Vi&gt;VoV_{i}&gt;V_{o}Vi​&gt;Vo​,所以电感两端电压即为 Vi−VoV_{i}-V_{o}Vi​−Vo​,也就是说是一个恒定值。由于有 Ldidt=Vi−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =V_{i}-V_{o}Ldtdi​=Vi​−Vo​,所以 didt=Vi−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{V_{i}-V_{o}}{L}dtdi​=LVi​−Vo​​,即电感电流的上升斜率,由于是稳态前提,所以可以确定该值是一个常数。 +当上管截止下管导通时,电感右边电压为 VoV_{o}Vo​,左边电压为 000,所以电感两端电压为 0−Vo0-V_{o}0−Vo​,即 −Vo-V_{o}−Vo​。由于 Ldidt=−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-V_{o}Ldtdi​=−Vo​,所以 didt=−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-\frac{V_{o}}{L}dtdi​=−LVo​​,即电感电流的下降斜率,也是一个常数。 +整个电路处于稳定状态,负载电路恒定,那么在一个周期内,电感电流增加的量肯定等于电感电流减小的量,即充了多少电就要放多少电,不然负载的电流或电压将会发生变化。 +前文已有didt=UL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{{U}}{L}dtdi​=LU​,而 LLL 恒定,那么电感电流的变化速度即与电压成正比关系,即电感电流上升(下降)的斜率与电压成正比关系。而电感电流上升和下降的高度相同,那么上升时间和下降时间就自然构成反比关系。 +TonToff=VoVi−Vo\frac{T_{on} }{T_{off} } = \frac{V_{o}}{V_{i}-V_{o}}Toff​Ton​​=Vi​−Vo​Vo​​,将其进行简单变换即可得到闻名江湖的伏秒平衡法则。 +Ton(Vi−Vo)=ToffVoT_{on}(V_{i}-V_{o}) = T_{off}V_{o}Ton​(Vi​−Vo​)=Toff​Vo​ +占空比 +已知 T=Ton+Toff=1fT=T_{on}+T_{off}=\frac{1}{f}T=Ton​+Toff​=f1​,结合伏秒平衡法则可以计算出: +开通时间:Ton=VoVi∙1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f}Ton​=Vi​Vo​​∙f1​ +关断时间:Toff=Vi−VoVi∙1fT_{off} = \frac{V_{i}-V_{o} }{V_{i} } \bullet \frac{1}{f}Toff​=Vi​Vi​−Vo​​∙f1​ +占空比:D=TonT=VoViD = \frac{T_{on} }{T}=\frac{V_{o} }{V_{i}}D=TTon​​=Vi​Vo​​ +纹波电流 +由于输出电压不变,也就是说输出电容两端的电压没有变化,即输出电容的平均电流为 0。根据输出节点的基尔霍夫电流定律可知,输出节点电流和为 0,那么功率电感的平均电流就等于负载的平均电流,即IL=Io=VoRI_{L} = I_{o} = \frac{V_{o} }{R}IL​=Io​=RVo​​。 + +上文计算电感电流斜率时已经能确定电流波形是个三角波,纹波电流等于在开关导通时电感电流的增大值,也等于在关断时电感电流减小的值,计算任意一个即可得到纹波电流。我们以上管导通时增大的电感电流计算。 + +上管导通时电感两端电压为 Vi−VoV_{i}-V_{o}Vi​−Vo​,导通时间为 Ton=VoVi∙1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f}Ton​=Vi​Vo​​∙f1​,根据 U=LdidtU=L\frac{\mathrm{d} i}{\mathrm{d} t}U=Ldtdi​ 可知: +△IL=di\triangle I_{L} =di△IL​=di +=Ton∙UL=T_{on}\bullet \frac{U}{L}=Ton​∙LU​ +=Vi−VoL∙VoVi∙1f=\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}=LVi​−Vo​​∙Vi​Vo​​∙f1​ +根据理论计算可以发现,电感电流的纹波和负载电流的大小没有关系,但是负载电流与平均电感电流是相等关系。 +功率电感选择 +根据上文的信息进一步可以计算出电感的峰值电流: +ILP=Io+△IL2I_{LP} =I_{o}+\frac{\triangle I_{L}}{2}ILP​=Io​+2△IL​​ +=Io+Vi−Vo2L∙VoVi∙1f=I_{o}+\frac{V_{i}-V_{o}}{2L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}=Io​+2LVi​−Vo​​∙Vi​Vo​​∙f1​ +那么在选择功率电感时,电感的饱和电流就必须要大于这个ILPI_{LP}ILP​,并且需要留有一定的裕量。实际应用时电感的纹波电流应是平均电流的 30%30\%30% 至 50%50\%50% 为宜,我们将这个参数称之为电流纹波率 r。根据电流纹波率范围就可以计算出电感值的范围: +△IL=Vi−VoL∙VoVi∙1f\triangle I_{L} =\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}△IL​=LVi​−Vo​​∙Vi​Vo​​∙f1​ +L=Vi−Vo△IL∙VoVi∙1fL =\frac{V_{i}-V_{o}}{\triangle I_{L}}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}L=△IL​Vi​−Vo​​∙Vi​Vo​​∙f1​ +=Vi−Vo(0.3至0.5)Io∙VoVi∙1f=\frac{V_{i}-V_{o}}{(0.3至0.5) I_{o}}\bullet \frac{V_{o}} {V_{i}}\bullet \frac{1}{f}=(0.3至0.5)Io​Vi​−Vo​​∙Vi​Vo​​∙f1​ +=(Vi−Vo)Vo(0.3至0.5)IoVif=\frac{(V_{i}-V_{o})V_{o}}{(0.3至0.5) I_{o} V_{i} f}=(0.3至0.5)Io​Vi​f(Vi​−Vo​)Vo​​ +为何 r 为 0.3~0.5 +电流纹波率即是电感电流的交流分量与其相对应的直流分量的比值,一旦 r 确定,那么输入输出滤波电容的电流、开关管的有效电流等都确定了,因此 r 的选择会影响器件选择和芯片的成本。使用公式可以表述为: +r=△IIL=2×IACIDCr=\frac{\triangle I}{I_{L}}=2\times \frac{I_{AC}}{I_{DC}}r=IL​△I​=2×IDC​IAC​​ +一般认为,电感体积与其能量处理能量成正比,因为要处理更高的能量就需要更大的磁芯。选择电感磁芯的能量处理能力至少要等于其需存储量,即 E=12×L×Ipk2E=\frac{1}{2} \times L \times I_{pk}^{2}E=21​×L×Ipk2​,下图是 E 与 r 的的函数曲线,可以发现在 r=0.4r=0.4r=0.4 附近有一个拐点。 + +选择的 r 如果较 0.4 低很多,则所需要的电感体积越大;而若继续增大 r,则电感的体积并不会减少多少,即当 r 超过 0.4 后,通过增加 r 来减少电感体积的效果已经不明显了。 +输入纹波 +电源输入功率为 Pi=ViIiP_{i}=V_{i}I_{i}Pi​=Vi​Ii​,负载功率为 Pr=VoIoP_{r}=V_{o}I_{o}Pr​=Vo​Io​,不考虑开关损耗、导通损耗等等因素,那么输入功率和输出功率相等,可得输入平均电流为 Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}Ii​=Vi​Vo​Io​​。 +输入电压纹波就是输入电容上面电压的变化,这个变化可以分为两部分。一部分为电容充放电所导致的电压变化 UqU_{q}Uq​,另一部分为电流流过电容 ESRESRESR 导致的压降 UesrU_{esr}Uesr​。即 △Vi=Uq+User\triangle V_{i} = U_{q} + U_{ser}△Vi​=Uq​+User​。 +∵Q=CiUq=Iit=IiToff\because Q = C_{i}U_{q} = I_{i}t = I_{i}T_{off}∵Q=Ci​Uq​=Ii​t=Ii​Toff​ +∴Uq=IiToffCi\therefore U_{q} = \frac{I_{i}T_{off}}{C_{i}}∴Uq​=Ci​Ii​Toff​​ +∵Toff=Vi−VoVif\because T_{off} = \frac{V_{i}-V_{o} }{V_{i}f }∵Toff​=Vi​fVi​−Vo​​ +且 Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}Ii​=Vi​Vo​Io​​ +∴Uq=VoIoCiVif∙Vi−VoVi\therefore U_{q} = \frac{V_{o}I_{o}}{C_{i}V_{i}f}\bullet \frac{V_{i}-V_{o}}{V_{i}}∴Uq​=Ci​Vi​fVo​Io​​∙Vi​Vi​−Vo​​ +要想知道 ESRESRESR 所造成的纹波,只需要知道流过输入电容的电流即可。当上管断开时,电源输入电流 IiI_{i}Ii​ 全部流入电容 CiC_{i}Ci​。电感电流原本从下管的体二极管续流,当上管导通后,变为了从上管续流。因为此前电感一直处于放电状态,所以切换的那一刻电感电流是最小的,为 IL−△IL2I_{L}-\frac{\triangle I_{L}}{2}IL​−2△IL​​。 +在整个 TonT_{on}Ton​ 时间内,电感都被充电,电感电流一直都在增大,直到 IL+△IL2I_{L}+\frac{\triangle I_{L}}{2}IL​+2△IL​​,并且在 TonT_{on}Ton​ 时间内,电感电流都是走的上 MOS 管通路,所以上 MOS 管最大电流也是 IL+△IL2I_{L}+\frac{\triangle I_{L}}{2}IL​+2△IL​​。 +根据基尔霍夫电流定律可知,输入节点的电流和为 0,那么输入电源电流 IiI_{i}Ii​ 和电容 CiC_{i}Ci​ 的放电电流就等于通过上 MOS 管的电流。所以 CiC_{i}Ci​ 的最大放电电流即为 IL+△IL2−IiI_{L}+\frac{\triangle I_{L}}{2} - I_{i}IL​+2△IL​​−Ii​。我们约定充电为正,放电为负,则放电电流为 Ii−△IL2−ILI_{i} - \frac{\triangle I_{L}}{2} - I_{L}Ii​−2△IL​​−IL​。 + + +上管截止时 ESRESRESR 的压降为 Ii∙ESRI_{i} \bullet ESRIi​∙ESR,上管导通时压降为 (Ii−△IL2−IL)∙ESR(I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESR(Ii​−2△IL​​−IL​)∙ESR,则可得: +User=Ii∙ESR+(Ii−△IL2−IL)∙ESRU_{ser} = I_{i} \bullet ESR + (I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESRUser​=Ii​∙ESR+(Ii​−2△IL​​−IL​)∙ESR +=(IL+△IL2)∙ESR=(I_{L} + \frac{\triangle I_{L}}{2}) \bullet ESR=(IL​+2△IL​​)∙ESR +∵△IL==Vi−VoL∙VoVi∙1f\because \triangle I_{L} ==\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}∵△IL​==LVi​−Vo​​∙Vi​Vo​​∙f1​ +且 IL=IoI_{L} = I_{o}IL​=Io​ +∴User=(Io+(Vi−Vo)Vo2ViLf)∙ESR\therefore U_{ser} = \left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR∴User​=(Io​+2Vi​Lf(Vi​−Vo​)Vo​​)∙ESR +综上所述可得: +△Vi=Uq+Uesr\triangle V_{i} = U_{q} + U_{esr}△Vi​=Uq​+Uesr​ +=VoIoCiVif∙Vi−VoVi+(Io+(Vi−Vo)Vo2ViLf)∙ESR=\frac{V_{o}I_{o}}{C_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}} +\left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR=Ci​Vi​fVo​Io​​∙Vi​Vi​−Vo​​+(Io​+2Vi​Lf(Vi​−Vo​)Vo​​)∙ESR +输入电容选择 +考虑到电容的实际使用情况,陶瓷电容的 ESRESRESR 小,容量小,所以 UqU_{q}Uq​ 对纹波起决定性作用,输入纹波可近似为 UqU_{q}Uq​。若选择铝电解电容,则 ESRESRESR 大,容量大,UesrU_{esr}Uesr​ 对纹波起到决定性作用,输入纹波可以近似为 UesrU_{esr}Uesr​,假设电路设计要求输入纹波不能大于 △Vi\triangle V_{i}△Vi​,则有: +陶瓷电容:Ci≥VoIo△ViVif∙Vi−VoViC_{i} \ge \frac{V_{o}I_{o}}{\triangle V_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}}Ci​≥△Vi​Vi​fVo​Io​​∙Vi​Vi​−Vo​​ +铝电解电容:ESR≤△ViIo+(Vi−Vo)Vo2fLViESR \le \frac{\triangle V_{i}}{I_{o} + \frac{(V_{i}-V_{o})V_{o}}{2fLV_{i}} }ESR≤Io​+2fLVi​(Vi​−Vo​)Vo​​△Vi​​ +输出纹波 +输出纹波与输入纹波同理,亦是 △Vo=Uq+Uesr\triangle V_{o} = U_{q} + U_{esr}△Vo​=Uq​+Uesr​,我们画出负载、功率电感、输出电容三者的电流波形。其中电感的纹波电流是 △IL\triangle I_{L}△IL​,则电容的纹波电流也是 △IL\triangle I_{L}△IL​,又因为电容的平均电流为 0,所以充电电流和放电电流都是 △IL2\frac{\triangle I_{L}}{2}2△IL​​。 +电容充放电的总电荷量 Q 等于电流乘以时间,即图中阴影三角形的面积,三角形底部时间为 T2\frac{T}{2}2T​,高为 △IL2\frac{\triangle I_{L}}{2}2△IL​​,所以总的放电量可以计算出来为 Q=12∙T2∙△IL2Q=\frac{1}{2} \bullet \frac{T}{2} \bullet \frac{\triangle I_{L}}{2}Q=21​∙2T​∙2△IL​​ + +结合 Q=CoUqQ=C_{o}U_{q}Q=Co​Uq​ 可得: +Uq=(Vi−Vo)Vo8ViCoLf2U_{q} = \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} }Uq​=8Vi​Co​Lf2(Vi​−Vo​)Vo​​ +由前面电流波形可知,电容的充电电流最大是 △IL2\frac{\triangle I_{L}}{2}2△IL​​,放电电流最大是 −△IL2-\frac{\triangle I_{L}}{2}−2△IL​​,则可以得到 ESRESRESR 引起的总压降为: +User=△IL2∙ESR−(−△IL2∙ESR)U_{ser} = \frac{\triangle I_{L}}{2} \bullet ESR - (-\frac{\triangle I_{L}}{2} \bullet ESR)User​=2△IL​​∙ESR−(−2△IL​​∙ESR) +∵△IL=Vi−VoL∙VoVi∙1f\because \triangle I_{L} = \frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}∵△IL​=LVi​−Vo​​∙Vi​Vo​​∙f1​ +∴(Vi−Vo)VoViLf∙ESR\therefore \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR∴Vi​Lf(Vi​−Vo​)Vo​​∙ESR +最终可得: +△Uo=Uq+Uesr\bigtriangleup U_{o} = U_{q} + U_{esr}△Uo​=Uq​+Uesr​ +=(Vi−Vo)Vo8ViCoLf2+(Vi−Vo)VoViLf∙ESR=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} } + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR=8Vi​Co​Lf2(Vi​−Vo​)Vo​​+Vi​Lf(Vi​−Vo​)Vo​​∙ESR +=(Vi−Vo)VoViLf∙(ESR+18fCo)=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf}\bullet \left ( ESR + \frac{1}{8fC_{o}} \right )=Vi​Lf(Vi​−Vo​)Vo​​∙(ESR+8fCo​1​) +输出电容选择 +与输入电容选择的方式一致,考虑是容值还是 ESRESRESR 占主导地位,假设要求输出纹波要小于 △Vo\triangle V_{o}△Vo​,则有: +陶瓷电容:Co≥(Vi−Vo)Vo8Vi△VoLf2C_{o} \ge \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}\triangle V_{o}Lf^{2} }Co​≥8Vi​△Vo​Lf2(Vi​−Vo​)Vo​​ +铝电解电容:ESR≤△VoViLf(Vi−Vo)VoESR \le \frac{\triangle V_{o}V_{i}Lf}{\left ( V_{i}-V_{o} \right ) V_{o}}ESR≤(Vi​−Vo​)Vo​△Vo​Vi​Lf​ +电感续流模式 +电感电流曲线不能断续(无突降),因为电流断续会引起实际不可能发生的能量断续现象。但是电流的变化率可以突变,比如从上升斜率(电感储能增加)变为下降斜率(电感储能释放),尽管这样电感电流也必须连续。根据稳定状态下每个周期电流是否回到零,划分为不同的导通模式,并且通过减小负载电流,可以使电路从 CCM 经过 BCM 最终转变为 DCM。 +CCM(连续导通模式) +稳定状态下每个周期,电流都回到某一非零值,称之为连续导通模式(CCM:Continuous Conduction Mode)。CCM 是功率变换中最常见的工作模式,有输出纹波小但功耗高的特点。 +FCCM(强制连续导通模式)只存在于同步 BUCK 中,由于使用 MOS 管将非同步拓扑的二极管取代,MOS 管的导通压降远低于二极管压降,除了显著减小了续流通路的导通损耗外,也允许电感电流反向,即从负载瞬时流出电流。 + +DCM(断续导通模式) +若稳定状态下每个周期中电流都会回到零,那么就称之为断续导通模式(DCM:Discontinuous Conduction Mode),DCM 由于其电感电流的不连续,计算平均电感电流就需要更加详细复杂的公式,这也是 DCM 方程看上去复杂的根本原因。 + +BCM(临界导通模式) +BCM 是临界导通模式(Boundary Conduction Mode),由控制器监控电感电流,一旦检测到电流等于 0,功率开关立即闭合,控制器总是等电感电流「复位」来激活开关。即 BCM 处于 CCM 和 DCM 之间,可以将其视为 CCM 和 DCM 的极端情况,所以 BCM 模式下可以自由的选择 CCM 或 BCM 方程。 + +DC-DC 功能框图 +前文所构建的 BUCK 电路只能是纸上谈兵,还需要解决诸多问题才能应用于实际电路。 +基础驱动与控制 +首先需要解决的问题就是 MOS 管不可能平白无故就打开,所以我们需要添加 MOS 管驱动器。 + +理想情况是上管关闭,下管立刻打开,中间没有任何时间差,但是 MOS 管并非理想开关,从关断到导通存在一个过渡的过程,若同时导通则电源通过上下 MOS 管直接对地短路,很容易就会导致 MOS 损坏,甚至可能会把前一级电源也损坏,所以上下管同时导通的状态必须得避免。 +为了避免上下管直通的情况,实际应用会故意让上管和下管切换时多等一会儿,宁愿出现同时关断的情况,也不能出现同时导通的状态,这个等待的过程就叫做死区时间。 + +需要注意的是,在死区时间内虽然下管没有被导通,但是功率 MOS 管本身存在一个寄生二极管,这个寄生二极管可以像非同步 BUCK 那样帮助电感续流,而且这个时间非常的短暂,所以产生的功耗没有那么大,因此不必担心系统会出问题。 +到目前为止,不知道您有没有发现我们都在自嗨,系统中并没有用来控制上下 MOS 导通和关断的信号。因此需要增加一个振荡器用来产生控制信号,注意我们在前文中使用的是占空比一词,也就是说我们要使用的是 PWM(脉冲宽度调制)。当然你也可以使用 PFM(脉冲频率调制),本文只介绍 PWM 方式。 + +PWM(脉冲宽度调制) +PWM 的全称是脉冲宽度调制(Pulse-width modulation),是通过将有效的电信号分散成离散形式,从而来降低电信号所传递的平均功率的一种方式。其基本实现原理是通过锯齿波/三角波(载波)与所需要合成的波形(调制波)进行比较,然后确定 PWM 所需要输出的极性。因为一般都是用到开关器件上,通常是 ON 或者 OFF,具体如下图所示。 + +将振荡器输出的锯齿波和参考值 VthV_{th}Vth​ 进行比较,就可以输出 PWM 波形了。话不多说,上图就明白了。 + +上图中的锯齿波(橙色)最大为 10,但是我们希望输出平均为 5 的波形(图中紫色的水平线),那么通过比较器进行比较,当锯齿波小于 5 时,PWM 即输出低电平 OFF,当锯齿波大于 5 时,PWM 即输出高电平 ON,此时的占空比即为 50%。 +若是想输出一个电压逐渐抬高的波形,即占空比逐渐增大,那只需要将调制的波形设置为斜坡输出即可达到效果。比如下图中可以看到,占空比从 0% 逐渐增大到 100%。 + +同样的道理,我们可以通过改变调制波形,进一步调制出来其它的波形,比如要调制一个正弦波(sin wave),也就是我们常说的 SPWM,那么就是下面的样子。 + +负反馈环路 +有了调制信号,开关管也可以正常打开与关闭,看起来可以应用到实际电路中了,但是别忘了负载的电阻并不是恒定的,负载的变化必然会引起输出电压的波动。为了减小输出电压的波动,我们可以在输出端添加分压电阻,与误差放大器和基准电压一起构成负反馈回路,这种通过取样输出电压进行闭环反馈的方式称之为电压模式控制。 + +误差放大器的输入端分别为带隙基准源输出电压采样,当输出电压减小/增大时,与基准电压的细微差异都会被误差放大器放大,今儿调节脉冲宽度来达到调节调整输出电压的目的。图中 R2 接地,所以可以很容易计算出输出电压与分压电阻的关系:Vout=Vref(R1+R2)R2V_{out} = \frac{V_{ref}(R_{1}+R_{2})}{R_{2}}Vout​=R2​Vref​(R1​+R2​)​。 +除了输出电压可以用作控制取样信号,还有输入电压、输出电流、输出电感电压、开关器件峰值电流可以作为控制取样信号。使用这些信号可以构成单环、双环或多环反馈系统,进而实现稳压、稳流以及恒定功率的目的,也可以实现过流、过压、均流等功能。 +现在回过头来评判一下电压模式控制的优缺点。单一的反馈电压闭环设计使得调试更加容易、对输出负载的变化有比较好的响应调节、占空比的调节也不会受到什么限制等等都是它的优点,但是其缺点也很明显。由于主电路有较大的输出电容和电感的相移延时作用,输出电压的变小/变大也延时滞后,再经过误差放大器的延时,使得瞬态响应变得更慢。由于电压控制模式不采样电流,逐周期限流保护功能必须另外增加电路来实现。 +峰值电流模式控制在电压模式控制的基础上又增加了电流环,所以峰值电流模式控制是一个双环反馈系统。误差电压信号与一个变化的,其峰值代表输出电感电流峰值的三角波形进行比较,然后得到 PWM 脉冲的关断时刻。所以峰值电流模式控制不是使用电压误差信息直接控制 PWM 脉冲宽度,而是直接控制峰值输出侧的电感电流大小,进而间接的控制 PWM 脉冲宽度。 + +峰值电流在逻辑上与平均电感电流大小变化一致,但是峰值电感电流的大小并不能与平均电感电流的大小一一对应。在占空比不同的情况下,相同的峰值电感电流大小可以对应不同的平均电感电流大小,但平均电感电流大小才是唯一决定输出电压大小的因素。 +为了解决不同占空比对平均电感电流大小的扰动作用,使得所控制的峰值电感电流最后收敛于平均电感电流,需要将电感电流下斜坡斜率的至少一半以上斜率加在实际检测电流的上斜坡上,这一点可以从数学上进行证明(具体咋证明暂不讨论)。 +总结一下峰值电流模式控制 PWM 是双闭环控制系统,电压外环控制电流内环。电流内环是瞬时快速按照逐个脉冲工作的。功率级石油电流内环控制的电流源,而电压外环再控制次功率级电流源。电流内环只负责输出电感的动态变化,电压外环仅需控制输出电容,所以峰值电流模式控制 PWM 具有比电压模式控制大得多的带宽。 +为了防止在应用过程中可能出现的短路等异常场景,DC-DC 少不了过温保护、过流保护、过压保护等保护手段。再设定一定的辅助功能,比如 PG 状态显示、缓启动、欠压保护等即可搭建完整的 DC-DC 电路。 + +异常模式 +参考上文中的电路图,我们把绿色部分称之为控制电路,灰色部分是功率电路,功率电路中最核心的就是上下两个 MOS 管,下文我们讨论不同的异常场景中,控制电路、上管、下管三部分应该处于什么状态,其中控制电路关闭相当于整个芯片重启。 +过压保护 +当输出电压偏高并且达到了过压保护的阈值。过压状态需要控制电路去调整把输出电压降下来,所以不需要重启整个芯片。可以想到输出端已经处于过压状态了,上管如果打开那会加重过压的程度,因此上管需要关闭。若下管打开,则电感、负载、下管形成回路,即电感有续流回路,会把过压状态维持的时间更长,因此下管也需要关闭。综上有:过压保护:关上管、关下管。 +过温保护 +温度过高的情况无非两种,一种是流过芯片的电流太大,即功率太大导致芯片自身发热达到了过温保护的阈值,此时关闭芯片肯定可以解决,另外切断电流回路也是可以解决的,即关闭上管。过温的第二种情况是由于环境温度过高而导致芯片温度过高,此时最好还是关闭芯片吧。综上有:过温保护:关闭芯片。 + +关闭芯片指关闭芯片中的 BUCK 部分,但是基准源部分仍然保持工作 + +过流保护 +过流保护还需要区分是正向过流还是负向过流,因为工作在 FCCM 模式的 DC-DC 在轻载或空载时,可能会有负向过流的情况。存在负向过流的另一原因也是因为同步 BUCK 没有像非同步 BUCK 那样的整流二极管,所以当存在负向过流情况时,直接模拟非同步 BUCK 中的二极管即可。综上有:负向过流保护:关下管。 +若发生正向过流时如何进行保护呢?首先考虑到电流经上管到负载,既然已经过流了那么肯定需要关上管。为了使电流减小的更快,那么就需要将电流流向地,所以需要将下管打开以构成回路。综上有:正向过流保护:关上管、开下管。 +异常排查 +不管系统设计的多好,在实际应用中都可能会或多或少出现问题,比如电感选用不合适、触发 min-on time、触发 min-off time、输出电容 ESR 过大等,下面我们逐一进行讨论。 +min-on time +虽然 MOS 管打开速度很快,但是打开始终是一个过程,要完成一个过程就必须需要一定的时间,当高频且压差大的情况下很容易触发完成「打开」这个过程的最小时间。也就是说占空比已经是实际最小了,占空比无法再降低了,所以查看输出电压纹波可能会出现下面的波形。 + +出现该波形的原因在于,占空比已经无法继续降低,所以电压整体处于逐渐抬高的趋势,当抬高到一定程度时即触发过压保护,上下管都关断,所以电压快速下降。 +min-off time +与 min-on time 相对应的是 min-off time,当开关频率足够高且输入和输出电压接近时即容易出现此问题,此时即达到系统所能达到的最大占空比也无法满足负载所需要的电压,表现为输出电压无法达到设定值,负反馈分压电阻电压也低于电压基准值。 +电感饱和电流过小 +电感电流正常是一个三角波,但是如果电感饱和电流过小,则会电感电流将会变成下图很苗条的样子。因为电感电流饱和所以电流不再线性增加,电流快速增大导致磁通率减小,会导致磁性损耗增大、芯片热耗增大,而且这是一个正反馈过程,整个系统的可靠性会大大降低。 + +输出电容选用不合适 +当输出电容选用过小时,会导致动态响应输出出现抖动。若输出电容的等效串联电阻(ESR)过大,也会导致输出纹波异常增大,这一点从前文的理论计算即可验证。因此在实际使用过程中需要同时考虑电容容值和所选电容的 ESR。 +为什么需要 min-on time +占空比 D 控制相对于输入电压的输出电压,虽然通过提高开关频率有助于减小电感尺寸,但是也必须满足最小导通时间(min-on time)才能使芯片正常工作。那么这个 min-on time 是由哪些因素引起的呢? +因为上管中电流波形前沿的电流尖峰。由于 MOS 管也是由 PN 结组成,存在 PN 结就肯定存在结电容,MOS 管的寄生电容 CgsC_{gs}Cgs​ 和 CgdC_{gd}Cgd​ 会导致上管在导通时电流突然变化,也就是说会出现电流尖峰。如果在这个电流尖峰的时间段内去检测电流的话,很可能就会触发过流保护,因此开关电路的最小导通时间必须大于电流尖峰出现的时间,这个时间我们称之为消隐时间。 + +另一个原因是因为上下管开关完成后,由于键合线存在寄生电感的原因会产生很大的振铃,这个振铃同样可能会导致峰值电流检测出错,需要一个 min-on time 将这个振铃隔离过去。 +为什么需要 min-off time +如下图所示,最简单的需要最小关断时间(min-off time)的原因是,若下管不打开则没有办法给自举电容充电,所以需要在该时间内给自举电容充电,为下一个开关周期做准备。 + +另一个原因是因为没有最小关断时间,即占空比 D 增大到 100%,那么就无法对负向电流、谷值电流进行采样,也就无法实现实现相应的异常保护功能。与 min-on time 一致,电流检测也需要一个 min-off time 隔离振铃。 +为什么需要自举电容 +DC-DC 的上 MOS 管可以是 PMOS,也可以是 NMOS。但是一般因为生产工艺问题,PMOS 导通电流往往做不不到很大,而在相同成本下 NMOS 的导通电流可以做到更大,也就是 RdsonR_{dson}Rdson​ 可以做到相对较低,所以往往更倾向于 NMOS。 +将上管换为 NMOS 后也带来了新的问题,如何打开 NMOS ?如图所示,上管的 S 极连接 PH 点,该点的电压为 +5V,要打开 NMOS 需要 VGS&gt;0V_{GS} &gt; 0VGS​&gt;0,驱动 MOS 管打开的压降需要 5V,那么驱动电压就需要 +10V 才可以打开上管,但是纵观整个电路并没有能达到 +10V 级别的电压,所以需要自举电容来进行升压才能打开上 MOS 管。 + +所以 DC-DC 芯片是否需要自举电容是由芯片所选用的 MOS 管类型决定的,若是 PMOS 则无需自举电容。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/2-Lad22RM/index.html b/2-Lad22RM/index.html new file mode 100644 index 00000000..08e11e39 --- /dev/null +++ b/2-Lad22RM/index.html @@ -0,0 +1,463 @@ + + + + + + + + 用 flomo 管理自己的奇思妙想瀑布流 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 用 flomo 管理自己的奇思妙想瀑布流 +

+ + +
+ +
+

使用 flomo 已经有一段时间了,太喜欢它的简洁与便捷了。它的微信输入方式可以随时随地记录突然冒出来的灵感;使用微信读书的时候看到一段写的很漂亮的文字,顺手贴到 flomo 的小卡片中便于下一次再回顾;使用 #todo 标签记录一些重要的待办事项......

+

基本上的脑力劳动者都会有记笔记的习惯,我也习惯去捡日常零零星星掉下的小拼图块,找到一个适当的时机再用这些小拼图块进行排列组合完成一个小作品,作品可以是一篇文章、可以是某个问题的解决方案,亦或是简单的工具、词句集合。

+

我用了「拼图块」这个词是因为我觉得人接收的信息就是碎片化的,尤其在充斥着各种奶头乐 APP 的时代,信息被磨揉的更加细碎无营养。不管是生活中还是工作上遇到的问题,很多都不是简简单单的接收一点碎片化知识就能找到解决方案的。学习新知识也是逐个去吸收小的知识点,再用这些小的知识点构建自己的知识体系,一些让主干更加粗壮,另一些让枝桠更加繁茂。

+

flomo 背后的笔记理念就是去捡那些小小的拼图块,这和我现在的理念是保持一致的。借用 flomo 网站上的话说就是我们不可能都成为作家或者发表论文,但是我们都需要记录和思考。写 MEMO(卡片)而不是写文章的价值在于,能让我们更好的思考

+

大部朋友记笔记都仅仅是在辛勤的记录,最重要的思考环节却被忽略了,在之前写的你如果只是一直囤干货,那永远不可能进步中也提过没必要去假装学习。这里没有倡导不去记笔记的意思,而是找到一个适合自己的记录方式,笔记究竟要记什么中有一部分答案。

+

我使用的第一个笔记软件是有道云笔记,用了有将近两年的时间,让我放弃它的原因是文件同步老出问题,另一个原因就是速度太慢了。印象笔记更是使用时间三天都没有超过,它那些看似强大实际却毫无用处的功能严重分散了我的注意力,这违背了我记笔记的初衷。

+

还有像为知笔记、石墨文档一类的软件其实也还不错,但是和印象笔记、有道云笔记类似,它们的共同问题都是以文章的形式在组织笔记。这让记笔记变成了一件极为费时的事情,我看到一篇文章的字数少于 600 就难受,强迫症患者。

+

所以有一段时间我选择了使用本地 VsCode + Markdown 插件方式记笔记,Markdown 的标题语法可以很轻松的将每个小片段分开,不同的文件(名)自然而然就变成标签了。一个新的技术点记到「技术.md」中,一段摘录记到「摘录.md」中。不过因为 VsCode 本身是一个方便程序员使用的文本编辑器,所以我这种方式记笔记总是有一点别扭,具体哪里别扭我自己也说不出来,总之就是用起来差那么点感觉。

+

这里不得不提一下现在比较流行的数据库类型软件 Notion,这个工具做的让我有一种只有我想不到没有它做不到的错觉。团队在设计一个数据度量系统的时候,我还多次提出过借鉴 Notion 中 Block 的思想。Notion 是一款很优秀的软件,但仅对记笔记这件事来说它显得大材小用了,功能过于强大、使用过于灵活到变成了我不选择它作为笔记软件的原因。

+

一小段时间使用知识星球做为笔记软件,但是它的搜索功能做的太弱了。一直使用到现在的笔记软件是微信,我建了只有我一个人的群,一些突然冒出来的想法、读书时的思考与摘录、todo things 都直接通过对话框发到群里。我看到有很多朋友也用了我类似的方法,选择发送到文件助手,这种方式的好处是可以借用微信强大的「查找聊天内容」功能,虽然有些鸡肋但用起来也还凑合。

+
+

其实我的记笔记方式是逐渐在向 flomo 靠拢的,虽然它出现的比较晚。flomo(浮墨笔记)看起来像是一个个人版的 twitter,或者就像少楠自己说的是一个加强版的文件传输助手,没有多余的功能去扰乱我的视线,就是一个简简单单的流式布局一元笔记软件。

+

大多数习惯于像装抽屉一样去组织文件,windows 的文件系统也是这样设计的,不同的文件夹放不同的类别的文件,看起来好像很符合我们现实生活的打扫房间的场景。但不知道你有没有意识到每次去找一个具体的文件夹都要耗费大把的时间,这种初衷极好的分类整理方式竟然渐渐变成阻止我们去记录绊脚石。

+

一款好用的文件系统更多的应该聚焦于搜索上,当用户搜索时能够快速的返回与之相关的文件才是关键,而不是把目光放在文件的分类上面。同样,一款好的笔记软件也应该是这样的,今天我看到一朵花,恍惚记得之前有记过与花相关的笔记,当我去搜索时它能够快速返回我想要的记录就 OK,flomo 在这两点上做的刚好甚得我心。

+

当然 flomo 还开放了 API 功能,这让记笔记这件事变得方便且有趣,比如在 iOS 上选中文字发送到 flomo 就变成了一个 MEMO,同样也可以实现在 Mac 上选中文字发送到 flomo,不过我使用最多的还是它的微信输入和随机漫步功能。

+
+

记笔记是为了更好的让自己思考,不要像微信和 QQ 收藏那样,让 read it later 变成了 read it never。最后说一下可以通过邀请链接注册 flomo 获得 28 天 PRO 会员。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/25MtObxIW/index.html b/25MtObxIW/index.html new file mode 100644 index 00000000..f1cd3acf --- /dev/null +++ b/25MtObxIW/index.html @@ -0,0 +1,573 @@ + + + + + + + + 如何加快 Nginx 的文件传输?——Linux 中的零拷贝技术 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 如何加快 Nginx 的文件传输?——Linux 中的零拷贝技术 +

+ + +
+ +
+
+

参考内容:
+Two new system calls: splice() and sync_file_range()
+Linux 中的零拷贝技术1
+Linux 中的零拷贝技术2
+Zero Copy I: User-Mode Perspective
+Linux man-pages splice()
+Nginx AIO 机制与 sendfile 机制
+sendfile 适用场景
+扯淡 Nginx 的 sendfile 零拷贝的概念
+浅析 Linux 中的零拷贝技术
+Linux man-pages sendfile

+
+

今天在看 Nginx 配置的时候,看到了一个sendfile配置项,它可以配置在http、server、location三个块中,出于好奇就去查了一下sendfile的作用。

+

文件下载是服务器的基本功能,其基本流程就是循环的从磁盘读取文件内容到缓冲区,再将缓冲区内容发送到socket文件,程序员基本都会写出类似下面看起来比较高效的程序。

+
while((n = read(diskfd, buf, BUF_SIZE)) > 0)
+    write(sockfd, buf , n);
+
+

上面程序中我们使用了readwrite两个系统调用,看起来也已经没有什么优化空间了。这里的readwrite屏蔽了系统内部的操作,我们并不知道操作系统做了什么,现实情况却是由于 Linux 的 I/O 操作默认是缓冲 I/O,上面的程序发生了多次不必要的数据拷贝与上下文切换。

+

上述两行代码执行流程大致可以描述如下:

+
    +
  1. 系统调用read产生一个上下文切换,从用户态切换到内核态;
  2. +
  3. DMA 执行拷贝(现在都是 DMA 了吧!),把文件数据拷贝到内核缓冲区;
  4. +
  5. 文件数据从内核缓冲区拷贝到用户缓冲区;
  6. +
  7. read调用返回,从内核态切换为用户态;
  8. +
  9. 系统调用write产生一个上下文切换,从用户态切换到内核态;
  10. +
  11. 把步骤 3 读到的数据从用户缓冲区拷贝到 Socket 缓冲区;
  12. +
  13. 系统调用write返回,从内核态切换到用户态;
  14. +
  15. DMA 从 Socket 缓冲区把数据拷贝到协议栈。
  16. +
+
+

可以看到两行程序共发生了 4 次拷贝和 4 次上下文切换,其中 DMA 进行的数据拷贝不需要 CPU 访问数据,所以整个过程需要 CPU 访问两次数据。很明显中间有些拷贝和上下文切换是不需要的,sendfile就是来解决这个问题的,它是从 2.1 版本内核开始引入的,这里放个 2.6 版本的源码

+

系统调用sendfile是将in_fd的内容发送到out_fd,描述符out_fd在 Linux 2.6.33 之前,必须指向套接字文件,自 2.6.33 开始,out_fd可以是任何文件;in_fd只能是支持mmap的文件(mmap是一种内存映射方法,在被调用进程的虚拟地址空间中创建一个新的指定文件的映射)。

+

所以当 Nginx 是一个静态服务器时,开启sendfile配置项是可以大大提高 Nginx 性能的,但是当把 Nginx 作为一个反向代理服务器时,sendfile则没有什么用,因为当 Nginx 时反向代理服务器时,in_fd就是一个套接字,这不符合sendfile的参数要求。

+
+

可以看到现在我们只需要一次拷贝就可以完成功能了,但是能否把这一次拷贝也省略掉呢?我们可以借助硬件来实现,仅仅需要把缓冲区描述符和文件长度传过去,这样 DMA 直接将缓冲区的数据打包发送到网络中就可以了。

+

这样就实现了零拷贝技术,需要注意的是这里所说的零拷贝是相对操作系统而言的,即在内核空间不存在冗余数据。数据的实际走向是从硬盘到内存,再从内存到设备。

+

Nginx 中还有一个aio配置,它的作用是启用内核级别的异步 I/O 功能,要使aio生效需要将directio开启(directio对大文件的读取速度有优化作用),aio很适合大文件的传送。需要注意的是sendfileaio是互斥的,不可同时兼得二者,因此我们可以设置一个文件大小限制,超过该阀值使用aio,低于该阀值使用sendfile

+
location /video/ {
+    sendfile on;
+    sendfile_max_chunk 256k; 
+    aio threads;
+    directio 512k;
+    output_buffers 1 128k;
+}
+
+

上面已经提到了零拷贝技术,它可以有效的改善数据传输的性能,但是由于存储体系结构非常复杂,而且网络协议栈有时需要对数据进行必要的处理,所以零拷贝技术有可能会产生很多负面影响,甚至会导致零拷贝技术自身的优点完全丧失。

+

零拷贝就是一种避免 CPU 将一块存储拷贝到另一块存储的技术。它可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效的提高数据传输效率,而且零拷贝技术也减少了内核态与用户态之间切换所带来的开销。进行大量的数据拷贝操作是一件简单的任务,从操作系统的角度来看,如果 CPU 一直被占用着去执行这项简单的任务,是极其浪费资源的。如果是高速网络环境下,很可能就出现这样的场景。

+

零拷贝技术分类

+

现在的零拷贝技术种类很多,也并没有一个适合于所有场景的零拷贝零拷贝技术,概括起来总共有下面几种:

+
    +
  • +

    直接 I/O:对于这种数据传输方式来说,应用程序可以直接访问硬件存储,操作系统只是辅助数据传输,这类零拷贝技术可以让数据在应用程序空间和磁盘之间直接传输,不需要操作系统提供的页缓存支持。关于直接 I/O 可以参看Linux 中直接 I/O 机制的介绍

    +
  • +
  • +

    避免数据在内核态与用户态之间传输:在一些场景中,应用程序在数据进行传输的过程中不需要对数据进行访问,那么将数据从页缓存拷贝到用户进程的缓冲区是完全没有必要的,Linux 中提供的类似系统调用主要有mmap()sendfile()splice()

    +
  • +
  • +

    对数据在页缓存和用户进程之间的传输进行优化:这类零拷贝技术侧重于灵活地处理数据在用户进程的缓冲区和操作系统页缓存之间的拷贝操作,此类方法延续了传统的通信方式,但是更加灵活。在 Linux 中主要利用了「写时复制」技术。

    +
  • +
+

前两类方法的目的主要是为了避免在用户态和内核态的缓冲区间拷贝数据,第三类方法则是对数据传输本身进行优化。我们知道硬件和软件之间可以通过 DMA 来解放 CPU,但是在用户空间和内核空间并没有这种工具,所以此类方法主要是改善数据在用户地址空间和操作系统内核地址空间之间传递的效率。

+

避免在内核与用户空间拷贝

+

Linux 主要提供了mmap()sendfile()splice()三个系统调用来避免数据在内核空间与用户空间进行不必要的拷贝,在Nginx 文件操作优化sendfile()已经做了比较详细的介绍了,这里就不再赘述了,下面主要介绍mmap()splice()

+

mmap()

+

当调用mmap()之后,数据会先通过 DMA 拷贝到操作系统的缓冲区,然后应用程序和操作系统共享这个缓冲区,这样用户空间与内核空间就不需要任何数据拷贝了,当大量数据需要传输的时候,这样做就会有一个比较好的效率。

+

但是这种改进是需要代价的,当对文件进行了内存映射,然后调用write()系统调用,如果此时其它进程截断了这个文件,那么write()系统调用将会被总线错误信号SIGBUG中断,因为此时正在存储的是一个错误的存储访问,这个信号将会导致进程被杀死。

+

一般可以通过文件租借锁来解决这个问题,我们可以通过内核给文件加读或者写的租借锁,当另外一个进程尝试对用户正在进行传输的文件进行截断时,内核会给用户发一个实时RT_SIGNAL_LEASE信号,这个信号会告诉用户内核破坏了用户加在那个文件上的写或者读租借锁,write()系统调用就会被中断,并且进程会被SIGBUS信号杀死。需要注意的是文件租借锁需要在对文件进行内存映射之前设置。

+

splice()

+

sendfile()类似,splice()也需要两个已经打开的文件描述符,并且其中的一个描述符必须是表示管道设备的描述符,它可以在操作系统地址空间中整块地移动数据,从而减少大多数数据拷贝操作。适用于可以确定数据传输路径的用户应用程序,不需要利用用户地址空间的缓冲区进行显示的数据传输操作。

+

splice()不局限于sendfile()的功能,也就是说sendfile()splice()的一个子集,在 Linux 2.6.23 中,sendfile()这种机制的实现已经没有了,但是这个 API 以及相应的功能还存在,只不过内部已经使用了splice()这种机制来实现了。

+

写时复制

+

在某些情况下,Linux 操作系统内核中的页缓存可能会被多个应用程序所共享,操作系统有可能会将用户应用程序地址空间缓冲区中的页面映射到操作系统内核地址空间中去。如果某个应用程序想要对这共享的数据调用write()系统调用,那么它就可能破坏内核缓冲区中的共享数据,传统的write()系统调用并没有提供任何显示的加锁操作,Linux 中引入了写时复制这样一种技术用来保护数据。

+

写时复制的基本思想是如果有多个应用程序需要同时访问同一块数据,那么可以为这些应用程序分配指向这块数据的指针,在每一个应用程序看来,它们都拥有这块数据的一份数据拷贝,当其中一个应用程序需要对自己的这份数据拷贝进行修改的时候,就需要将数据真正地拷贝到该应用程序的地址空间中去,也就是说,该应用程序拥有了一份真正的私有数据拷贝,这样做是为了避免该应用程序对这块数据做的更改被其他应用程序看到。这个过程对于应用程序来说是透明的,如果应用程序永远不会对所访问的这块数据进行任何更改,那么就永远不需要将数据拷贝到应用程序自己的地址空间中去。这也是写时复制的最主要的优点。

+

写时复制的实现需要 MMU 的支持,MMU 需要知晓进程地址空间中哪些特殊的页面是只读的,当需要往这些页面中写数据的时候,MMU 就会发出一个异常给操作系统内核,操作系统内核就会分配新的物理存储空间,即将被写入数据的页面需要与新的物理存储位置相对应。它最大好处就是可以节约内存,不过对于操作系统内核来说,写时复制增加了其处理过程的复杂性。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/3pFNTYNB8/index.html b/3pFNTYNB8/index.html new file mode 100644 index 00000000..7a5e10b6 --- /dev/null +++ b/3pFNTYNB8/index.html @@ -0,0 +1,657 @@ + + + + + + + + 翻译摘录 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 翻译摘录 +
+ + +
+

+ + KK 给年轻人的 68 条建议 + +

+ +
+ + + + +
+ +
+ KK 是凯文·凯利的网名,他曾经担任《连线》杂志的第一任主编,是著名的科技评论家,也是畅销书《失控》的作者。去年的 4 月 28 日是他 68 岁生日,他在个人网站上发表了一篇给年轻人的 68 条建议,文章被翻译成了十几种其它语言,今年 4 月 28 日老爷子又续写了一篇给年轻人的 99 条建议,本文是给年轻人的 68 条建议中文翻译版,翻译除了借助 DeepL 机器翻译工具外,更多参考自KK 在 68 岁生日时给出的 68 条建议。 +Learn how to learn from those you disagree with, or even offend you. See if you can find the truth in what they believe. +学着从那些你不认可甚至冒犯你的人身上学习,看看能否从他们的信仰中找到真理 +Being enthusiastic is worth 25 IQ points. +充满热情可以抵得上 25 点智商 +Always demand a deadline. A deadline weeds out the extraneous and the ordinary. It prevents you from trying to make it perfect, so you have to make it different. Different is better. +做任何事都应该设一个 deadline,它可以帮你排除那些无关紧要的事情,也能避免过分要求自己尽善尽美。努力去做到与众不同,差异比完美更好 +Don’t be afraid to ask a question that may sound stupid because 99% of the time everyone else is thinking of the same question and is too embarrassed to ask it. +不要害怕自己问的问题看起来很愚蠢,99% 的情况下,其他人和你有一样的问题,只不过他们羞于问而已 +Being able to listen well is a superpower. While listening to someone you love keep asking them “Is there more?”, until there is no more. +倾听是一种超能力,当听到你喜欢的人说话时,要不时的追问「还有吗」,直到他们没有更多的东西可讲 +A worthy goal for a year is to learn enough about a subject so that you can’t believe how ignorant you were a year earlier. +一个有意义的年度目标是去充分了解一个学科,这样你就会对一年前的无知感到难以置信 +Gratitude will unlock all other virtues and is something you can get better at. +感恩可以解锁其它所有的美德,也是你可以继续做的更好的一件事情 +Treating a person to a meal never fails, and is so easy to do. It’s powerful with old friends and a great way to make new friends. +请一个人吃饭是非常简单的一件事情,不仅仅是老朋友,这也是结交新朋友的有效方式 +Don’t trust all-purpose glue. +不要相信万能药 +Reading to your children regularly will bond you together and kickstart their imaginations. +经常给的孩子读书不仅能巩固你们之间的感情,也能帮助孩子开启想象力 +Never use a credit card for credit. The only kind of credit, or debt, that is acceptable is debt to acquire something whose exchange value is extremely likely to increase, like in a home. The exchange value of most things diminishes or vanishes the moment you purchase them. Don’t be in debt to losers. +永远不要用信用卡去透支。唯一可以接受的透支或负债,应该是那些通过负债有极大可能获得增值的事物,比如房屋。绝大多数事物在你买下它的那一刻就开始贬值了,别为那些没有未来的事物透支 +Pros are just amateurs who know how to gracefully recover from their mistakes. +专业人士不过是善于从挫折中优雅爬起的菜鸟 +Extraordinary claims should require extraordinary evidence to be believed. +要想让人相信非同寻常的观点,就需要非同寻常的证据 +Don’t be the smartest person in the room. Hangout with, and learn from, people smarter than yourself. Even better, find smart people who will disagree with you. +别成为一群人中最聪明的那一个,和那些比你聪明的人待在一起,向他们学习。如果能找到和你观点相反的聪明人,那就更好了 +Rule of 3 in conversation. To get to the real reason, ask a person to go deeper than what they just said. Then again, and once more. The third time’s answer is close to the truth. +对话中的「数字 3 原则」。想要找到一个人真正的意图,那就请他把刚才说的话再深入一些,如此反复直到第三遍,你就能比较接近真相了 +Don’t be the best. Be the only. +不做最好的,去做唯一的 +Everyone is shy. Other people are waiting for you to introduce yourself to them, they are waiting for you to send them an email, they are waiting for you to ask them on a date. Go ahead. +每个人都很害羞,其他人正等着你向他们介绍你自己,等着你给他们发送邮件,等着你约他们见面。大胆的向前走 +Don’t take it personally when someone turns you down. Assume they are like you: busy, occupied, distracted. Try again later. It’s amazing how often a second try works. +别人拒绝你的时候不要往心里去。假设他们和你一样忙碌、腾不出手、心烦意乱,再试一次,第二次成功的几率超乎你的想象 +The purpose of a habit is to remove that action from self-negotiation. You no longer expend energy deciding whether to do it. You just do it. Good habits can range from telling the truth, to flossing. +习惯的意义在于无需再为某类行为纠结,不用再消耗精力去觉得是否做这件事。干就完了,讲真话和使用牙线都是很好的习惯 +Promptness is a sign of respect. +及时回应是表示尊重的一种方式 +When you are young spend at least 6 months to one year living as poor as you can, owning as little as you possibly can, eating beans and rice in a tiny room or tent, to experience what your “worst” lifestyle might be. That way any time you have to risk something in the future you won’t be afraid of the worst case scenario. +当你年轻的时候,应该至少花半年到一年的时间,过尽可能穷的日子,拥有尽可能少的身外之物,居陋室而箪食瓢饮,体验你可能会经历的最穷困潦倒的生活。这样,在未来任何时候,你都不用担心最坏的情况 +Trust me: There is no “them”. +相信我,没有「他们」 + +个人理解,KK 大叔想表达的意思应该是:太阳底下无新事,每个人都是历史的参与者 + +The more you are interested in others, the more interesting they find you. To be interesting, be interested. +你越有兴趣了解别人,别人就会发现你越有趣,要成为有趣的人,先要对别人感兴趣 +Optimize your generosity. No one on their deathbed has ever regretted giving too much away. +常行慷慨之事,没有人会在死的时候后悔给予的太多 +To make something good, just do it. To make something great, just re-do it, re-do it, re-do it. The secret to making fine things is in remaking them. +想要做好一件事,干就完了。想要做一件值得称赞的事情,那就重做一遍,重做一遍,再重做一遍。制造好东西的秘诀在于不断的重做 +The Golden Rule will never fail you. It is the foundation of all other virtues. +金科玉律永远不会让你失望,它是所有其他美德的基础 +If you are looking for something in your house, and you finally find it, when you’re done with it, don’t put it back where you found it. Put it back where you first looked for it. +如果你正在你的房子里寻找什么东西,那么用完后不要放回你找到它的地方,而是放到你最初找它的地方 +Saving money and investing money are both good habits. Small amounts of money invested regularly for many decades without deliberation is one path to wealth. +存钱和投资是好习惯。几十年如一日的定期进行小额投资(定投),是一条致富之路 +To make mistakes is human. To own your mistakes is divine. Nothing elevates a person higher than quickly admitting and taking personal responsibility for the mistakes you make and then fixing them fairly. If you mess up, fess up. It’s astounding how powerful this ownership is. +犯错是人之常情,承认错误是神圣的。认错并勇于担责,再认真弥补过错,没有什么比这更可贵了。是自己搞砸的就勇于承担,这反而能彰显你的强大 +Never get involved in a land war in Asia. +永远不要在亚洲陷入地面战争 + +KK 大叔这句话没读懂 + +You can obsess about serving your customers/audience/clients, or you can obsess about beating the competition. Both work, but of the two, obsessing about your customers will take you further. +你可以专注于你的顾客、听众或客户,也可以沉迷于在竞争中获胜,这两种方法都行之有效,但是专注于服务你的客户会让你走的更远 +Show up. Keep showing up. Somebody successful said: 99% of success is just showing up. +在场,坚持在场,某个成功人士说过:99% 的成功只不过是因为在场 +Separate the processes of creation from improving. You can’t write and edit, or sculpt and polish, or make and analyze at the same time. If you do, the editor stops the creator. While you invent, don’t select. While you sketch, don’t inspect. While you write the first draft, don’t reflect. At the start, the creator mind must be unleashed from judgement. +将创造过程与改进过程分开,你不可能在写做的同时进行编辑,也不可能在凿刻的同时进行打磨,更不可能在制造的同时进行分析。如果你这么做,求善之心就会打断创造之意;创新时就要忘掉已有方案;勾勒草图时就不能太着眼于细处;写作时,先打草稿而不要去抠细节。在新事物之初,创意的思想必须得到无拘无束的释放 +If you are not falling down occasionally, you are just coasting. +如果你从未跌倒过,那么你也就从未努力过 +Perhaps the most counter-intuitive truth of the universe is that the more you give to others, the more you’ll get. Understanding this is the beginning of wisdom. +也许宇宙中最违反直觉的真理就是,你给予他人越多,你收获的就越多,这是智慧的起点 +Friends are better than money. Almost anything money can do, friends can do better. In so many ways a friend with a boat is better than owning a boat. +朋友胜过金钱。金钱几乎可以做任何事情,但朋友可以做得更好。很多时候,自己有条船不如有个有船的朋友 +This is true: It’s hard to cheat an honest man. +相信我,你很难欺骗一个诚实的人 +When an object is lost, 95% of the time it is hiding within arm’s reach of where it was last seen. Search in all possible locations in that radius and you’ll find it. +当一件物品丢失时,95% 的情况下,它都藏在人们最后一次看到它时触手可及的地方。在这个半径范围内搜索所有可能的地点,你就能找到它 +You are what you do. Not what you say, not what you believe, not how you vote, but what you spend your time on. +你做什么就是什么。不是你说什么,不是你相信什么,更不是你支持什么,而是你把时间花在了什么上 +If you lose or forget to bring a cable, adapter or charger, check with your hotel. Most hotels now have a drawer full of cables, adapters and chargers others have left behind, and probably have the one you are missing. You can often claim it after borrowing it. +如果你遗失或忘记带电缆、适配器或充电器,不妨去问问你的酒店。大多数酒店都会有满满一抽屉的电源线、适配器和充电器,这些东西都是别人留下的,没准儿其中就有你的,酒店也并不介意你借用后随身带走 +Hatred is a curse that does not affect the hated. It only poisons the hater. Release a grudge as if it was a poison. +仇恨是一种诅咒,但它不会影响被仇恨的人。它只会毒害仇恨者,把你的怨恨当作毒药一样丢掉吧 +There is no limit on better. Talent is distributed unfairly, but there is no limit on how much we can improve what we start with. +没有最好,只有更好。个人的天分有高有低,但不论高低,自身的提升都永无止境 +Be prepared: When you are 90% done any large project (a house, a film, an event, an app) the rest of the myriad details will take a second 90% to complete. +任何一项大工程(修房子、拍电影、开发 app)完成度为 90% 的时候,你都要做好心理准备:剩余的大量细节工作同样需要 90% 的时间来完成 +When you die you take absolutely nothing with you except your reputation. +当你死的时候,除了你的名誉,你什么都无法带走 +Before you are old, attend as many funerals as you can bear, and listen. Nobody talks about the departed’s achievements. The only thing people will remember is what kind of person you were while you were achieving. +在你年老之前,尽可能多地参加葬礼并听听别人的谈话,没有人会谈论逝者的成就,人们能记住的只有逝者在成功时是什么样的人 +For every dollar you spend purchasing something substantial, expect to pay a dollar in repairs, maintenance, or disposal by the end of its life. +你每花一美元在实体店购买一件东西,将来都要再花一元钱去维修、保养,或是在它报废后处理掉它 +Anything real begins with the fiction of what could be. Imagination is therefore the most potent force in the universe, and a skill you can get better at. It’s the one skill in life that benefits from ignoring what everyone else knows. +任何真实的东西都来源于虚构的想法,想象是宇宙中最强大的力量,也是你可以做的更好的一种能力,生命中可以因不知众人所知而获利 +When crisis and disaster strike, don’t waste them. No problems, no progress. +当危机和灾难来临时,不要错过他们,没有问题就没有进步 +On vacation go to the most remote place on your itinerary first, bypassing the cities. You’ll maximize the shock of otherness in the remote, and then later you’ll welcome the familiar comforts of a city on the way back. +度假时,先绕过城市去行程中最偏远的地方。这样你就能最大程度地体验到异域风情带给你的冲击,而在返程的路上,又可以享受熟悉的城市所带给你的舒适 +When you get an invitation to do something in the future, ask yourself: would you accept this if it was scheduled for tomorrow? Not too many promises will pass that immediacy filter. +当你被邀请在未来的某个时间点做某件事情时,问问自己:如果是明天,你会接受邀请吗?绝大多数邀约都经不住这种迫切性检验 +Don’t say anything about someone in email you would not be comfortable saying to them directly, because eventually they will read it. +如果一些话你不能当面对某人说出口,那么就不要在邮件中对他评头论足,因为他们最终会看到邮件 +If you desperately need a job, you are just another problem for a boss; if you can solve many of the problems the boss has right now, you are hired. To be hired, think like your boss. +如果你只是迫切需要一份工作,那你只是老板的另一个问题;如果你能解决许多老板眼下的问题,那你自然能得到这份工作。要想得到一份工作,就要像老板一样去思考 +Art is in what you leave out. +艺术藏身于你遗忘的地方 +Acquiring things will rarely bring you deep satisfaction. But acquiring experiences will. +获得物品很少能给你带来深刻的满足感,但是经验却能做到 +Rule of 7 in research. You can find out anything if you are willing to go seven levels. If the first source you ask doesn’t know, ask them who you should ask next, and so on down the line. If you are willing to go to the 7th source, you’ll almost always get your answer. +研究的「数字 7 原则」。当你愿意就一个问题深入七层时,总能找到你想要的答案。如果你问的第一层人不知道,那么就问问他们应该去找谁,如此追索下去,你几乎总能得到你的答案 +How to apologize: Quickly, specifically, sincerely. +如何道歉:迅速、具体、真诚 +Don’t ever respond to a solicitation or a proposal on the phone. The urgency is a disguise. +永远不要在电话上面答应一个请求或提议,所谓的急迫不过是一种假象 +When someone is nasty, rude, hateful, or mean with you, pretend they have a disease. That makes it easier to have empathy toward them which can soften the conflict. +当有人对你粗鄙、无礼、刻薄,甚至是下流时,当他们有病就好了,这使得我们更容易对他们产生同情心,进而缓和冲突 +Eliminating clutter makes room for your true treasures. +清理杂物,为真正重要的东西腾出空间 +You really don’t want to be famous. Read the biography of any famous person. +你绝对不会想出名,不信的话可以随便找本名人传记读读 +Experience is overrated. When hiring, hire for aptitude, train for skills. Most really amazing or great things are done by people doing them for the first time. +经验往往被高估了,招聘时应该多看资质,技能是可以培训的。许多令人惊奇和赞叹的事情,都是新手做出来的 +A vacation + a disaster = an adventure. +度假 + 灾难 = 冒险 +Buying tools: Start by buying the absolute cheapest tools you can find. Upgrade the ones you use a lot. If you wind up using some tool for a job, buy the very best you can afford. +购买工具:从最便宜的开始,升级那些使用频次高的。如果你的工具是用于工作,那么买你能买得起的最好的 +Learn how to take a 20-minute power nap without embarrassment. +学会毫不尴尬的打 20 分钟小盹儿 +Following your bliss is a recipe for paralysis if you don’t know what you are passionate about. A better motto for most youth is “master something, anything”. Through mastery of one thing, you can drift towards extensions of that mastery that bring you more joy, and eventually discover where your bliss is. +如果你不知道自己热爱什么,追寻心之所向往往会带你误入歧途,对年轻人来说,更好的格言是:master something, anything,在精通一件事的过程中,你可以顺着带给你更多快乐的方向继续深入,并最终发现你热爱的东西 +I’m positive that in 100 years much of what I take to be true today will be proved to be wrong, maybe even embarrassingly wrong, and I try really hard to identify what it is that I am wrong about today. +我敢肯定,我今天认为正确的许多东西在 100 年后将被证明是错误的,甚至可能是令人尴尬的错误。而我非常努力在做的事情,就是去识别我对今天的错误认知 +Over the long term, the future is decided by optimists. To be an optimist you don’t have to ignore all the many problems we create; you just have to imagine improving our capacity to solve problems. +从长远来说,未来由乐观主义者决定。作为一个乐观主义者并非要对我们制造的问题视而不见,而是要想象如何提升我们解决问题的能力 +The universe is conspiring behind your back to make you a success. This will be much easier to do if you embrace this pronoia. +整个宇宙在背后密谋让你成功,要相信,天助人愿 + +
+ + Read More ~ +
+
+
+ +
+

+ + 财富与幸福指南 + +

+ +
+ + + + +
+ +
+ +若没有在段落文字后面做特别标注,那该段落即摘录自The Almanack of Naval Ravikant: A Guide to Wealth and Happiness + +There’s no shortcut to smart. +聪明没有捷径可走 +The fundamental delusion: There is something out there that will make me happy and fulfilled forever. +最基本的错觉: 有些东西会让我永远快乐和满足 +Hard work is really overrated. How hard you work matters a lot less in the modern economy. What is underrated? Judgment. Judgment is underrated. +在现代经济中,努力工作的重要性大大降低了。什么被低估了?判断,判断被低估了 +Spend more time making the big decisions. There are basically three really big decisions you make in your early life: where you live, who you’re with, and what you do. +花更多的时间做重大决定。在你的早期生活中,基本上有三个真正重大的决定: 你住在哪里,你和谁在一起,你做什么 +Stay out of things that could cause you to lose all of your capital, all of your savings. Don’t gamble everything on one go. Instead, take rationally optimistic bets with big upsides. +远离那些可能导致你失去所有资本、所有储蓄的事情。不要一次性赌光所有的东西。取而代之的是,理性乐观地下注,并从中获得巨大的好处 +Don’t partner with cynics and pessimists. Their beliefs are self-fulfilling. +不要和愤世嫉俗者和悲观主义者合作,他们的信仰是自我实现的 +You’re not going to get rich renting out your time. You must own equity—a piece of a business—to gain your financial freedom. +出租你的时间是不会致富的。你必须拥有股权,一项业务,才能获得财务自由 +Follow your intellectual curiosity more than whatever is “hot” right now. If your curiosity ever leads you to a place where society eventually wants to go, you’ll get paid extremely well. +比起现在所谓的「热门」 ,应该更多地追随你的求知欲。如果你的好奇心曾经引导你到一个社会最终想要去的地方,那么你会得到非常好的报酬 +The less you want something, the less you’re thinking about it, the less you’re obsessing over it, the more you’re going to do it in a natural way. +你想要的东西越少,你对它的思考就越少,你对它的困扰就越少,你就会越自然地去做它 +Learn to sell. Learn to build. If you can do both, you will be unstoppable. +学会销售,学会建设,如果你能同时做到这两点,你将不可阻挡 +If you secretly despise wealth, it will elude you. +如果你私下里鄙视财富,它就会躲避你 +Arm yourself with specific knowledge, accountability, and leverage. Specific knowledge is found by pursuing your genuine curiosity and passion rather than whatever is hot right now. Specific knowledge is knowledge you cannot be trained for. +用具体的知识、责任感和影响力武装自己。具体的知识是通过追求你真正的好奇心和激情而不是任何现在热门的东西找到的。具体的知识是你不能被训练的知识 +If they can train you to do it, then eventually they will train a computer to do it. +如果他们能训练你做一件事,那么最终他们会训练一台电脑来做这件事 +Apply specific knowledge, with leverage, and eventually you will get what you deserve. +运用特定的知识,利用杠杆作用,最终你会得到你应得的东西 +You should be too busy to “do coffee” while still keeping an uncluttered calendar. +你应该忙得没时间“喝咖啡” ,同时保持日程表整洁 +There are no get-rich-quick schemes. Those are just someone else getting rich off you. +没有快速致富的计划,那些只是别人从你身上赚钱而已 +Code and media are permissionless leverage. +代码和媒体是未经许可的杠杆 +People who live far below their means enjoy a freedom that people busy upgrading their lifestyles can’t fathom. +那些生活水平远远低于自己收入的人们享受着一种自由,这是忙于提升自己的生活方式的人们所无法企及的 +By the time people realize they have enough money, they’ve lost their time and their health. +当人们意识到他们有足够的钱时,他们已经失去了时间和健康 +To have peace of mind, you have to have peace of body first. +为了拥有内心的平静,你必须首先拥有身体的平静 +The more secrets you have, the less happy you’re going to be. +你的秘密越多,你就越不快乐 +No exceptions—all screen activities linked to less happiness, all non-screen activities linked to more happiness. +毫无例外,所有的屏幕活动都与较少的快乐有关,所有的非屏幕活动都与较多的快乐有关 +Inspiration is perishable—act on it immediately. +灵感是易逝的,所以立即付诸行动 +To make an original contribution, you have to be irrationally obsessed with something +为了做出原创性的贡献,你必须非理性地沉迷于某些东西 +If there’s something you want to do later, do it now. There is no “later.” +如果你以后有什么想做的事情,现在就应该去做,没有「以后」 +Courage isn’t charging into a machine gun nest. Courage is not caring what other people think. +勇气不是冲进机关枪的巢穴,而是不在乎别人的看法 +Happiness is a choice you make and a skill you develop. +幸福是你的选择,是你发展的一项技能 +Your brain is overvaluing the side with the short-term happiness and trying to avoid the one with short-term pain. +你的大脑高估了短期的幸福,却试图避免短期的痛苦 +Envy is the enemy of happines. +嫉妒是幸福的敌人 +Honesty is a core, core, core value. +诚信是一个非常,非常,非常核心的价值观 +where you build a unique character, a unique brand, a unique mindset, which causes luck to find you. +你要建立一个独特的个性,一个独特的品牌,一个独特的心态,这会让好运气找到你 +Figure out what you’re good at, and start helping other people with it. +找出你擅长的东西,然后去帮助他人 +If you are a trusted, reliable, high-integrity, long-term-thinking dealmaker, when other people want to do deals but don’t know how to do them in a trustworthy manner with strangers, they will literally approach you and give you a cut of the deal just because of the integrity and reputation you’ve built up. +如果你是一个值得信赖的、可靠的、正直的、有长远眼光的交易者,当其他人想做交易,但不知道如何以值得信赖的方式与陌生人做交易时,他们会真正地接近你,并和你进行交易,仅仅因为你已经建立起的诚信和声誉 +All benefits in life come from compound interest, whether in money, relationships, love, health, activities, or habits. +生活中所有的好处都来自于复利,无论是在金钱、人际关系、爱情、健康、活动还是习惯上 +Productize yourself +将自己产品化 +It’s only after you’re bored you have the great ideas. It’s never going to be when you’re stressed, or busy, running around or rushed. +只有当你感到无聊的时候,你才会有好的想法。当你感到压力,或者忙碌,到处跑或者匆忙的时候,这些都不会发生 +Play stupid games, win stupid prizes. +玩愚蠢的游戏,赢得愚蠢的奖品 +Clear accountability is important. Without accountability, you don’t have incentives. Without accountability, you can’t build credibility. But you take risks. You risk failure. You risk humiliation. +明确的问责制很重要。没有责任感,你就没有动力。没有责任感,你就无法建立可信度。但是你要冒险,要冒着失败的风险,冒着被羞辱的风险 +The best jobs are neither decreed nor degreed. They are creative expressions of continuous learners in free markets. +最好的工作既没有规定也没有程度,它们是自由市场中不断学习者的创造性表现 +Reading is faster than listening. Doing is faster than watching. +读比听快,做比看快 +Explain what you learned to someone else. Teaching forces learning. +向别人解释你学到了什么。教学是学习的动力 +Read what you love until you love to read. +读你喜欢的东西,直到你喜欢阅读 +Better is the enemy of done. +完成比完美更重要 + +Angela Zhu 摘抄 + +If you did not make yourself well understood, it is your problem. +如果别人没有听懂你在说什么,一定是你的问题 + +Angela Zhu 摘抄 + +Inspire your people do things, not tell your people do things. +不断激烈启发你的组员做事,而不是告诉他们做什么 + +Angela Zhu 摘抄 + +It does not matter what was your motivation, it only matters how this make your direct reports feel. +你的出发点不重要,重要的是你让别人感觉你想干什么 + +Angela Zhu 摘抄 + +尽可能帮助和服务别人,建立信任,赢得资本 + +Angela Zhu + +To learn something, you should do not reread, summarize and teach it out loud. +要想学点东西,你不应该重读、总结和大声教出来 + +http://www.toolkiit.com/ + +在人生最好的年纪,应该少一点算计,多一点洒脱。遇到喜欢的人就去相处,去恋爱,不要辜负自己的时光,然后人生就会给出你更多的可能性 + +Fenng + +你心里应该有爱,应该有为了爱去放弃一些东西的勇气。如果你做不到这样,那我觉得你很可悲。或者,会成为一个精致的利己主义者,体会不到人生的幸福,和真正的生活的勇气 + +Fenng + +电话、视频会议,线下会议、面对面交流的时候,这是同步事件,需要一定程度上近乎实时反馈。而短信、微信消息、语音消息、留言、邮件,这些都是异步事件,用固定的节奏批量处理就是了 + +Fenng + +When you are old and gray, and look back on your life, you will want to be proud of what you have done. The source of that pride won’t be the things you have acquired or the recognition you have received. It will be the lives you have touched and the difference you have made. +当你白发苍苍、垂垂老矣、回首人生时,你需要为自己做过的事感到自豪。物质生活和满足的占有欲,都不会产生自豪。只有那些受你影响、被你改变过的人和事,才会让你产生自豪感 + +Steven Chu 2009 年哈佛大学毕业演讲 + +In your collaborations, always remember that “credit” is not a conserved quantity. In a successful collaboration, everybody gets 90 percent of the credit. +合作中,不要把功劳都归在自己身上,在一场成功的合作中,每个人都应该获得 90% 的荣誉 + +Steven Chu 2009 年哈佛大学毕业演讲 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 非设计师需要知道的四个设计原则 + +

+ +
+ + + + +
+ +
+ +作者:Anna 4erepawko Mészáros,UI/UX 设计师。 +关注作者: Medium、Twitter + +这篇文章是写给无力邀请专业设计师的所有内容创作者的,以及设计师异常忙碌的团队的非设计师们。如果您按照这些简单的步骤进行操作,我保证您的设计会变得更好。 +这些 Tips 来源于我对身边非设计朋友的多年观察,家人与同事在日常生活中也需要设计他们的东西。比如简历、作品集,Facebook 和 Instagram 上帖子要使用的图片,YouTube 视频的缩略图等。 +所有这些人都向我寻求帮助与建议,希望能让他们的东西看起来更好。我坚信「授人以鱼不如授人以渔」,所以我试图提供更有价值的建议,以便他们在未来也能解决类似的问题。 +随着时间的推移,我意识到我一直在给所有人提供相同的建议,虽然每次所使用的措辞不同,但我所有的建议都可以提炼为以下四个原则。 +这些 Tips 会帮您创造出美丽动人的设计吗?答案是不会!它们只会帮您创造出色、清晰且易于理解的设计。是每个人都可以轻松理解和互动吗?那当然,所以不多说废话,下面我就向您逐一展示。 +对比 +确保所有元素之间有足够的对比度。为什么?因为那些略有差异但是又不够不同东西,创造了一种恐怖谷。人类的眼睛会排斥它们,对它们感到厌恶、难以理解。我们不希望它们出现在我们的设计中,难道不是吗? + +恐怖谷理论,是一个关于人类对机器人和非人类物体的感觉的假设。如果一个非人类实体不够拟人,那么它身上的人类特征会很容易辨认;而当它足够拟人时,他身上的非人类特征则会变得很容易辨认。因此会在人类观察者眼中产生一种古怪的感觉,想想您看到病患者或者尸体时的感觉。 + +因此您设计的元素要么完全相同,要么具有显著差异。 +您可以从下面四个方面来突出对比: +1、颜色:浅色上使用暗色,反之亦然 +示例: 切勿在浅蓝色上使用浅灰色或浅粉红色等灰色组合,它们会造成阅读/互动上的极大困难。 + +2、大小:相邻元素要么大小完全相同,要么大小区别很大 +示例: 不要将 32pt 和 36pt 的文本放在一起;18pt 和 36pt 放在一起会显得更加协调。 + +3、粗细:与大小一样,相邻元素的粗细要么完全相同,要么有明显的区别 +示例: 不要将相同字体的粗体与黑体放在一起,因为它们看起来太相似了;将黑体与细体放在一起会显得很协调。 + +4、风格:不要将一个斜体类型放在另一个斜体类型旁边,或者在一个衬线字体旁边放置另一个衬线字体。应该组合不同的东西。 +示例: 不要将 Times New Roman 与 Georgia 放在一起,它们看起来太相似了,应该组合完全不同的风格。 + +一致性 +确保相似的元素以相似的方式出现。为什么呢?首先,通过确保确保事物一致性,您可以让用户将注意力集中在设计的重要方面,而不是被随时变化的元素分散注意力。 +其次,一致性也增加了用户对您的信任,使事物看起来实际上是设计的,而不是简单快速拼凑出来的。 +一旦你选择了具体的风格,就要毫不犹豫的坚持下去,这里所说的风格包括字体、颜色、阴影、栅格、对齐、装饰风格等等。 + +当您处理许多相邻的不同部分时(比如 YouTube 的视频缩略图或是中型文章的封面),您应该为所有部分选择一种整体风格,并坚持使用。 +奥卡姆剃刀 减少视觉噪音 +在您的设计中,使用的元素越少越好。为什么呢?因为人类的大脑很难在输入过载的情况下处理信息并作出决策。您应该使用尽可能少的装饰元素(字体、颜色、阴影、图标等等)。 +将奥卡姆剃刀应用于所有内容。如果只需要两个元素就能满足需求,那么就不要使用 3 个元素;如果 10 个元素实现所需的功能,那么就不要用 20 个元素。 + +如果您不喜欢古老的英国哲学家风格,更喜欢您在 Netflix(一家美国流媒体提供商)上看到的东西。请将怦然心动的人生整理魔法应用到您的设计中。 + +《怦然心动的人生整理魔法》是美国流媒体提供商Netflix于2019年1月1日首播的一档真人实境秀节目。节目由日本“整理咨询顾问” 近藤麻理惠主创。她在每集节目中拜访一个家庭,帮助他们整理自己的房间。 +近藤麻理惠认为整理房间时应当将物品分为五类:衣物、书籍、纸张文件、杂物和情感纪念品;在整理时拿起每件物品,如果能使自己“怦然心动”则留下,如果不能则要感谢物品的贡献然后与其告别。 + +间距 +元素的位置会发送关于其含义的元级别消息。为什么这很重要?因为了解如何放置元素以及在它们周围预留了多少空间有助于降低设计的复杂性,因此会使人更加愉悦,并且更容易交互。 +在您的设计中使用间距来传达下面 3 个方面的信息: +1、接近度 = 相关性 +与其它元素相比,彼此更接近的事物被认为它们有更强的相关性。这是最重要的,因为我觉得它常常容易被忽视。 +它可以以很多不同的方式应用,比如行与行之间应该有一定的间距,而不是一行中每个单词之间的间距那么小;同样不同段落之间的空间也比段落内的行空间要大。 + +元素之间的间距应该小于元素与组合边缘之间的间距。 + +标签和支撑信息应该位于其相关元素附近。 + +2、留白 +结合奥卡姆剃刀,给您的设计尽可能留白,去整理它们,使它们的意义更加明显。 +如果把太多元素放在有限的空间里,就像同时听三首不同的哥,很难理解别人在说什么。 + +3、重要性与顺序 +这是一个很普通的常识,但是我还是要在这里提到它。 +最重要的事情放在第一位,使它们占据最大的空间,用一系列的事物来传达秩序。 +结束语 +恭喜您!如果您按照这些 Tips 进行设计,那么按照行业标准,它可能看起来非常好。 +For everything else, there is always a designer. + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 00000000..58bcadfd --- /dev/null +++ b/404.html @@ -0,0 +1,89 @@ + + + + + + + Page Not Found + + + +
+
4 0 4
+
+ Page not found +
+
+ + + + + + Back + +
+
+ + + + \ No newline at end of file diff --git a/4jVVEgoF6/index.html b/4jVVEgoF6/index.html new file mode 100644 index 00000000..b6664d06 --- /dev/null +++ b/4jVVEgoF6/index.html @@ -0,0 +1,524 @@ + + + + + + + + 深入理解计算机系统——CPU 是怎样工作的? | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 深入理解计算机系统——CPU 是怎样工作的? +

+ + +
+ +
+
+

参考内容:
+处理器是如何工作的
+《编码:隐匿在计算机软硬件背后的语言》——[美] Charles Petzold

+
+

CPU 大家应该都不会陌生,日常用的手机、电脑中都有 CPU,CPU 作为一个设备的大脑,指挥着其它各种硬件的协同工作,芯片技术也是国内一直没有突破的技术。

+

我们先来看看怎么让电路去运算呢?比如如何让电路运算1 + 1,直接使用下面这个装置就可以了。

+
+

作为一个比较好奇的人,总会想看看那个方框框里面是什么样子的,让我们慢慢解开加法器的外衣。

+
+

这个电路你应该不会陌生,它需要两个开关都闭合时灯泡才会发光,也就是说它有两个输入,开关闭合时我们认为输入为 1,未闭合状态视为 0;而灯泡是否发光就是我们的输出,发光为 1,否则为 0。于是就有了下面这张表。

+ + + + + + + + + + + + + + + + + + + + +
and01
000
101
+

这样的电路我们就把它称之为与(and)门,它接受两个逻辑输入,并会给出我们一个逻辑输出,与它相似的电路还有逻辑或(or)、**异或(xor)**等等,因为太多了,就不一一介绍了,如果感兴趣可以 Google 一下。

+

为了方便我们把上面的电路做一个简化,抽象成下面这个样子,其它的电路也可以通过 Google 找到它们的样子。

+
+

现在直接给出一个可以运算加法的电路,它的样子长下面这样。

+
+

我们也可以给它列一个表,显得更清晰,表中之所以有两位是因为加法有可能会产生进位,而我们使用的是二进制表示,所以10表示的是十进制中的2

+ + + + + + + + + + + + + + + + + + + + +
+01
00001
10110
+

有加法就很好办了,减法实际上是加一个负数,除法可以改写成乘法,乘法又可以改写成加法,现在加法一统天下了。

+

好了,上面说了那么多,还贴了那么多图,只是为了让你相信电路是可以实现我们数学上的运算的,下面就没有那么声情并茂了。

+

我们可以把一个运算抽象为下面这个模型。

+
输入数据 --> 运算 --> 输出数据
+
+

计算机中把各种像上述加法器一样的运算器放在了同一个小盒子里面,组成成一个运算器集合,我们给它取个名字叫算术逻辑单元(ALU:Arithmetic Logical Unit),它是 CPU 的核心组成部分。

+

当然我们不可能只进行加法这种简单运算,一个很长很长的算式需要经过多步运算,小学我们就学过梯等式,它实际上有一大推输入,中间那么多梯子,都是临时步骤,临时步骤、数据一类的东西都需要有个东西给它存起来,所以在 CPU 就拿出一个小盒子当做存储器

+
# 梯等式
+485 - ( 6 × 4 + 32 )
+= 485 - ( 24 + 32 )
+= 485 - 56
+= 429
+
+

现在我们有了存储器和运算器两个干事的人,但是没有一个统筹兼顾的领导者,控制器就充当了这个角色,它需要把控制储存器中的数据发送到 ALU 去进行运算,然后再将运算的结果取出来存到储存器中。总的来说,控制器的工作就是完成协调和指挥整个计算机系统的操作。

+

我们把上面的结构画出来,图中的虚线表示指令流,实线表示数据流,这个结构就是著名的冯 · 诺依曼体系结构,遵循这种结构的计算机都叫做冯诺依曼机,现在所有的机器都是冯诺依曼机。

+
+

请注意,我们现在实际上只有硬件,没有软件是什么事情都干不了了,我们这里所说的软件是一堆指令序列,比如加法指令、传送指令等等组成的序列,也就是我们常说的汇编语言。

+

但是在早期并不是这样的,这台机器上编写的指令序列是无法运行在另一家公司生产的机器上的,即使是同一个公司的机器,如果不是同一代,那也不能运行,所以早期的编程是直接面向硬件的。

+

这时就有人站出来研究如何实现一次编写多处运行了,IBM 首次在它的 360 系统上引入了ISA的概念,即指令集体系结构。

+

指令集体系结构将编程所要了解的硬件信息从硬件系统中抽象了出来,这样开发人员就可以直接面向处理器的 ISA 进行编程了。

+

为什么手机上的软件不能运行在电脑中呢?就是因为个人电脑所使用的 Intel 和 AMD 处理器都是基于 x86 指令集的,而手机大多数都使用的是基于 ARM 指令集的处理器。

+

现在处理器被分为指令集体系结构、处理器微架构、处理器物理实现三个层次。体系结构相当于需求,微架构好比设计,物理实现则是具体的实现过程。

+

比如我们规定指令集中必须有加法指令,这个指令你怎么设计、如何实现是你给的事,我只管给出两个加数你能给我输出一个正确的结果,简单来说就是抽象封装。

+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/4ps_vLvTd/index.html b/4ps_vLvTd/index.html new file mode 100644 index 00000000..e50fc30a --- /dev/null +++ b/4ps_vLvTd/index.html @@ -0,0 +1,529 @@ + + + + + + + + 2020 年个人总结 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 2020 年个人总结 +

+ + +
+ +
+

还是照例给自己写一个年终总结。持续分享,黄金万两。

+

一点感动

+

7 月份回深圳办离职,提前通电话告诉了老叔到达深圳的时间。本来是 8 点就能到的,由于飞机晚点导致我半夜 3 点才到老叔那里。期间老叔每隔半小时就给我打电话问到哪里了,我好几次叫他先睡不用管我,我完全可以就近找个酒店舒舒服服的睡一觉,他都坚持说没事,等我。

+

让我感动的是我坐的航班落地时间大概是两点,本来以为老叔已经睡了,结果一发消息老叔居然没睡还在等我。到家时他已经把早早就做好的啤酒鸭、可乐鸡翅、猪脚脚热好了,拿出了自己泡的茅台酒,陪他喝到了四点半。早上八点半的样子我被吵醒了,我脑袋还是晕乎乎的,老叔就已经做好了鲫鱼粉,那味道可真的香。

+

第二天我请团队同事们吃饭,老叔和之前地产圈的朋友们去喝酒去了,大概十一点的样子老叔给我打电话来了:“小刘,你在哪里?”我一听就知道老头子喝的不少:“老头,你是不是喝醉了?你现在在哪里啊?用不用我去接你?”那一头来了一句:“我现在在树下。”搞得我哭笑不得。我叫他就近找个酒店睡,第二天早上再回来。老头子说:“不行,你在这里我爬都要爬回去,你不用管我,我回去等你。”等到我回去的时候发现他已经在打呼噜了,第二天早上起来还傻乎乎的问我车费是谁付的。

+

11 月份和师兄们去参加惠州白盆珠的 100 km 骑行,拐了个弯先去了一趟深圳。老头看到我到了居然开心的连自己办公室密码给忘了,我俩在那里排列组合试了半小时才打开办公室门。当天晚上和叔唠了很久的嗑,第二天给我说昨天太开心了,5 点钟才睡着。

+
+

老叔是我刚到深圳时所租公寓的管理员,准备抽空专门给老叔写一篇文章

+
+

赚钱理财

+

2020 年其实没有赚什么钱,基本是靠之前攒下来的一些钱在生活,不过这一年确实副业收入要比 19 年多了一些。银行理财依旧和去年一样保持在 4.5%,基金收益在 10% 的样子,因为缺钱早早的把投入基金的钱先取出来了,10% 实际上是 9 个月的成绩,我个人已经满意了。

+

有一段时间帮别人负责抖音信息流广告投放,我就直接用我的信用卡充值广告费再报账,所以有那么几个月我的银行流水比较大,平安银行的积分全部用来换了电话费,一年的电话费没有花自己的钱。招商银行信用卡的积分换了两个被子和一个收纳箱,刚好填补成都冬日的寒冷。

+

6 月份一个平安保险的销售人员给我推销保险,我把我对保险的理解以及当时对市面上少许现有产品的分析给他说了一遍,他认为我已经达到了保险销售人员的水平,告诉了我一个可以分销保险的平台,我当时也没有把这个当回事。

+

隔了两个月,有一个在成都认识的朋友问我保险相关的问题,我就给她做了一些解答,完全当给自己选产品一样给她选,刚好记起保险分销平台这个事就注册提交资料,很快平台审核通过了,最终到手的有 3000 多块钱的佣金。忘记是在什么情况下和这位朋友聊起了化妆品这个话题,于是我顺手花了 900 多给对方买了一瓶精华。

+

通过付费社群知道了知乎好物这个功能,把一篇之前发在知乎上的一篇《大学生书单推荐》插入了购物卡片。之后又整理了豆瓣一个话题下面的好物清单,同样发到知乎上面插入了相应的购物卡片,前前后后总共出了大概 10 单的样子,佣金够自己吃几顿麻辣烫。

+

自己知道一个可以低价充值腾讯、爱奇艺、网易云等平台会员的渠道,加了一点点价格在闲鱼上面转卖,前前后后赚的钱加起来应该也能吃几顿麻辣烫,不过由于利润低且有一点费时间所以就没做了,不过这也算是自己走通的第一个利用信息差赚钱的案例。

+

选择离职

+

离职的想法老早就在心头了,3 月份提的离职申请 4 月底离开,但是在流程上我是 7 月份才离职的,因为领导做出了很大让步,允许我半个月在成都半个月在深圳,既然已经说到这份上我也就答应了。但是在年终奖、季度奖和津贴上面却又受到了极不公平的待遇。所以离职的原因很简单,就是干的不爽加上不算年轻的冲动。

+

冲动是什么呢?趁还不太老的时候去经历,自己一直在没钱但也没有感觉到缺钱的状态下生活,估计大多数白领阶层也是这个状态,没有家庭的压力,没有赡养父母的压力,更没有病痛的折麽。所以我想提前去感受一下缺钱的感觉,这个想法确实是脑子有点病,哈哈哈。

+

干的不爽是为啥呢?不喜欢公司的加班文化,部门每个月都会有一个加班时长排名表,我们团队每个月都是部门垫底的,据说加班时长少于 40 小时会被领导谈话。我个人不反对加班,但是当大家都仅仅简单的在比加班时长的时候,那个氛围就待的很没有意思了。

+

在大企业工作的程序员都会遇到的一个问题。我自己在 5G 测试部门做内部工具开发,回家时大家一听都还觉得挺洋气的,能接触到业界比较前沿的技术与知识,但实际上我连产品最终长什么样子都没有见过,我希望自己能看到一个产品从 0 到 1,并且送到用户手中用户愿意付费的过程。

+

当然离职最大的原因是意识到个人的增长是线性的。我在部门新人里面还是算干的比较好的,算一下自己如果一直干的比较好,部门那个 5G 专家可能就是自己将来的样子,自己还不一定能达到那个水平。如果干的不好的话也有榜样在那里,那一群每天上午还按时在工位做体操的老油条就是自己将来的样子,不喜欢一眼就能望到未来样子的感觉。

+

离职之后的感受是大公司提供的保护壳真特么硬啊,出来之后才明白挣钱确确实实不容易,所以在这里奉劝各位要离职可得想好啊,说实话我现在有一点怀念那种划一天水都依旧有工资的日子呢。

+

一点收获

+

自己从 0 到 1 设计开发的系统得到了同事们的认可,完全靠着「用户怎么用着舒服」这个理念在开发,逐渐得到了其它部门领导的青睐。在师傅的督促与推动下,我在离职前将其做了平台化开发,临走前已经将西安一部门接入系统,我还悄悄在系统里面留下了我的信息,不知道将来可有人能发现或是已经发现。

+

学习了一点互联网广告投放领域的知识,对互联网流量有了比之前更加深刻的理解。中国 8 亿网民里面绝大部分都是穷人,穷人里面又有一大部分是天天只知道刷抖音的懒人,梦想着天上会掉下来馅饼喂到嘴里,你脑子里的常识可能有一亿人都不知道。大部分小公司赚的是穷人的钱,大家向往的互联网巨头又赚的基本是小公司的钱。

+

对京东、淘宝、拼多多这一类电商平台有了新的认识,慨叹电商领域的水简直深不可测,很多小公司依附于电商平台生存,太多套路也不方便在这里讲。自己脑子里将实体经济与互联网经济的逻辑打通了一部分,互联网平台要么自己能提供某种服务,要么就是带的动货才能正向循环。这里做个预测,2021 年会有更多的平台接入电商平台,功能类似于知乎好物。

+

体验了一把纸醉金迷的感觉,对自己的定位又清晰了一点。人这一辈子总要多多少少面对一些诱惑,就好像电影《八佰》里面那个军官一样,自己没有摸过咪咪所以老是好奇摸咪咪的感受,没有经历过总是容易经不住诱惑。我庆幸自己能比较早的去经历,虽然不至于让我明白我想要什么,但至少让我明白了自己不想要什么。

+

人生的第一桶金不是只有钱,还有经历和认知。这一年更多的是见识,是心灵上的收获。

+

新的伙伴

+

到成都后和一群贵州、四川小伙伴待了段时间,他们基本没有上过高中,有的甚至小学都没有毕业。IT 行业工作的人多少是有五险一金、年终奖、季度奖这些玩意的吧,但是这部分人所在的公司可能连社保都不会交。我清楚的记得我刚回成都时,一个小孩子给我炫耀说:“刘哥,我们公司是有社保的”。

+

他们喜欢像抖音一样各种炫耀,但是自己挣得钱又不够自己花的,要是有一个月没有发工资的话那么就还不起花呗了。如果某一个月多挣了一点点钱,就是去酒吧、去嫖娼。我发现这才是目前社会上最大群体的一个缩影。

+

直到最近我才真的明白,你不要和他们讲什么要去做有积累的事,不要和他们讲什么所谓的长期主义,一个每天还在为生活发愁的人,就不要和他去谈什么理想。所以我写了一篇如何通过撸茅台赚钱的文章,在这里我想告诉你这种短期快速套利不会让你有什么积累,会打乱你的时间,就是个奶头乐。

+

得益于校友会这样一个平台,在四川校友会认识了一个师兄,我目前正在师兄手下学习做一点事情。主要的精力都在少儿编程教育上面,惊叹我大一才会的东西现在一个不到十岁的小孩子就会了,已经变成了一个拼娃的时代,家长们生怕邻居孩子会的才艺自己孩子不会,可能这就是「内卷」吧。

+

写在最后

+

2020 年做了一个对我个人来说比较重要的尝试,那就是自己输出一套爬虫课程,这算是第一次自己打造一个产品,这是一件很有意思的事情,因为他人是拿腰包里的钞票为你投票,能拿到一票就开心的不得了。2021 年继续在这个方向尝试,尽可能的去做好做精。

+

在运动健身这一块做的很差,除了去白盆珠参加了一次百公里公益骑行,其它就没有什么值得说出嘴的运动了。顶多就是跑跑步,亦或是骑 10 来公里的单车,一年时间没有打过羽毛球了。2021 争取用自行车上下班,尽量保持平均每周能有 60 公里骑行的运动量。

+

2021 年继续尝试将自己产品化,一直靠兴趣驱动的我,执行力总算得到一点加强。给新的一年设下几个关键词吧:执行、坚持、分享、自律。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/5EoDeie7t/index.html b/5EoDeie7t/index.html new file mode 100644 index 00000000..76a57362 --- /dev/null +++ b/5EoDeie7t/index.html @@ -0,0 +1,466 @@ + + + + + + + + 媒体的谎言|楼下的小餐馆|服务质量的思考 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 媒体的谎言|楼下的小餐馆|服务质量的思考 +

+ + +
+ +
+

上个月很多地方都可以看到一个新闻,90 后安徽女孩 16 岁时到杭州打工,省吃俭用拼命存钱就为了在杭州买房,十年时间存了将近 100 万。但是 2020 年的疫情对她的服装生意影响太大,她先是自己在网上卖淫,后又通过社交软件招募失足妇女卖淫,半年时间获利近 60 万。这个女孩的故事本身是很励志的,但网上的故事版本基本是下图这个样子的,一句话概括就是「女孩卖淫获利 160 万」,简单明了、易传播、方便吃瓜,吃瓜是第一生产力。

+
+

相比之下,那些通过掩盖部分事实来教育国人的文章,到显得不那么可恶了一些。比如都说英国人喜欢在地铁看书看报纸,对比我们国人在地铁都是看手机显得没啥素养,但却很少文章提及伦敦地铁修建于 100 年前,由于隧道小致使安装通信设备的难度极大,所以他们的地铁是没有手机上网条件的。全线实现手机上网后,咱再来看看他们看书还是看手机!

+

媒体总喜欢发一些坏的东西,毕竟更容易吸引眼球且符合人的本能,人还没死但去世的相关新闻已经出来了,故意掩盖部分事实让大众理解扭曲。考虑到「流量就是金钱」这个前提,有时候也能理解现在媒体的做法。在台湾电视剧《我们与恶的距离》中就有一个片段,主角很清楚一个新闻能带来的收视率,但是自家的记者还没有考究到该新闻的真实性,当其它电视台都在报道这个新闻时,自家电视台应该随大流报道,还是坚守新闻人的底线?

+
+

人把自己置身于忙碌当中,有一种麻木的踏实,但丧失了真实,你的青春,也不过只有这些日子。你看到什么,听到什么,做什么,和谁在一起,有一种从心灵深处满溢出来的不懊悔,也不羞耻的平和与喜悦。

+

——电影《无问东西》台词

+
+
+

用了差不多一年时间观察楼下的几个小餐馆,第一个引起我注意的是一个叫做「小鲜肉烧烤」的店,同期在它的隔壁也是一个烧烤店,在离它不到一百米的地方还有一个烧烤店。那时候正值寒冬,烤串拿出来很容易就变凉,大家都知道烤串凉了就不好吃了,这时小鲜肉烧烤自己买了个大棚子,同时还给每个桌子放了一个保温盘。

+

有了大棚子遮风,顾客就不需要忍着寒风吃烤串,而且保温盘让烤串一直都是热的。其它两家则一直是都是让顾客在忍着寒风吃串,我有一次骑行回来因为烤串上的太快了,里面的羊肉串中间那一坨肥肉都凝固成油泥了,骑行腐败的心情大打折扣。

+

小鲜肉烧烤专门有个人在外面守着,那眼睛就跟老鹰一样犀利,顾客还没有坐好菜单就送到面前了,另外两家则是师傅专心烤串,进去了是一脸懵逼不知道找谁,点完餐也不知道交给谁。过完年回来,楼下只剩一家烧烤店了。

+

紧接着小鲜肉烧烤的隔壁被一对夫妻租了去,他们做的是羊肉米线,想来两口子定是雄心勃勃、信心满满的。前三天用了一点小技巧引流,活动期间可以凭点餐券再免费领一份羊肉米线,出于占便宜和好奇的心理,开业第一天我就去了这家店,看的出来米线是在水里泡的太久了,而且表现非常的不光滑,猜测是为了节省成本用了便宜的原料。

+

大概坚持了一周的样子,这家店在饭店就无人问津了,左邻右舍的店铺都忙的不可开交,这家店铺是老公和老婆对坐玩手机,「葛优躺」完美的挂在了脸上,店里显得没有生机更是让人不想踏进去一步。这个商铺过了不到半个月就转手了。

+

接盘侠是一个做冒菜的老板,老板开业第一天就大火,服务好、菜品多、吃法新,生意一度优于旁边的烧烤店。大概过了半个月的样子,估计老板觉得店里比较稳了,于是开始把店里的事情逐渐放给员工,好几次我去那家吃都没有看到老板。

+

没有老板在自然一些细节把控不好,比如从厨房到餐桌的过道肯定是走最多,而那条过道上的污渍即使你有意无视它,它也能非常容易的钻到你眼睛里;顾客选择的是干拌冒菜,但是却在盘子里看到非常多的汤水;煮菜也看得出来做了流程化处理。

+

就这样冒菜店的生意以非常明显的速度在下滑,好在老板意识到问题又回来每天坚守阵地,但总体来说已经比不上刚开店的时候了,现在是和小鲜肉烧烤的人流量基本持平。

+
+

会有意识的去观察这些事物,大概和我现在从事的工作有关系。我现在是一名少儿编程老师,大学做家教教学生编程时,觉得只要自己专业技能够硬,学生问问题都能回答出来就可以,喜欢用一些听起来牛逼的专业词汇给学生讲课。现在想想那时候真是傻,那时候觉得人家听不懂叫牛逼,现在明白人家听不懂叫装逼。

+

开始主动的去反思自己的问题,讲课的流程、节奏、纪律、有趣等等,并且有意识的提升课堂的仪式感。现在讲课的问题还非常多,但也收获了一些学生和家长的认可。一个孩子我接手了之后得到的奖升了一级,家长一激动给我发了「感谢恩师」,笑死我了😂

+

一个学生课上说自己的一个同班同学「还在渣渣学而思学习编程」,虽然孩子只是随口一说,但是这句话让我心中暗喜。除了在外面上课外,我同时还在一些小学上社团课,一个学生想要到外面上课非选我不可,不然就不交钱报名......

+

体会到服务业的一些乐趣,也真正认识到了质量的重要性,质量的轻微变动顾客都能感受到。尽力做好自己可以做的事情,但是也要明白事情不是自己能完全掌控的,就像张小龙说的跟漂流瓶扔出去一个瓶子是一个道理,看到以后发生什么不是我们所能够掌控的。

+

最后放两个学生的趣味作品吧。

+
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/5hyz5Z3oU/index.html b/5hyz5Z3oU/index.html new file mode 100644 index 00000000..47d2d043 --- /dev/null +++ b/5hyz5Z3oU/index.html @@ -0,0 +1,463 @@ + + + + + + + + 扒一下网店代运营公司的套路 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 扒一下网店代运营公司的套路 +

+ + +
+ +
+

前段时间,一个朋友给我发了一个抖音视频,视频是深圳龙岗公安发布的他们抓捕代运营诈骗公司的一些场景,我在龙岗政府在线网站上也看到了这个事件的新闻:9000元请人代运营,只换来几条教学视频,深圳网店主报警。有天傍晚,我独自坐在楼下公园的椅子上看书看电影,无意中被刚下班的牙科医生碰到了,他就坐了下来和我一起聊天,很自然就聊到了他的大众点评店铺的推广,告诉我他接到过好多代运营公司的电话,他自己差点就要给其中一个公司交钱了。

+

咱们先来看一下在百度搜索「网店代运营」的结果,前五条全部都是广告。在抖音短视频信息流广告中,代运营的广告也是不计其数。其它像快手、神马搜索这些产品中也充斥这种代运营广告,这么庞大的广告投入背后一定是有暴利,我这里只是把我知道的代运营套路写出来,如果能让一个人止步骗局那就是没有白写的。

+
+

一般会去找代运营的商家基本都是新手,自己正为店铺没有销量而发愁,现在有家公司告诉你只需要每个月交多少运营费用,自己只负责客服和发货就行了(甚至客服都不用负责),看到市面上有这样好的服务你能不心动吗?而且他们还重点给你强调公司主要是靠运营店铺销售额的提点来盈利,在签合同的时候销售还故意在起提基数和标准上表现出很强硬的态度,这无疑会更加增加商家的信任,但这都不过是骗局的开始。

+

找代运营的群体里面还有一大部分是想发展一个副业的白领、空闲时间比较多的大学生和宝妈,这部分人是很纯正的网店小白,不知道怎么注册网店(不需要多高级的搜索技巧,百度随便都能搜到),不知道怎么上架商品,自己没有货源不知道该怎么办,我这里拿没有货源来介绍一下代运营公司的销售话术。

+

如果对方不知道「一件代发」这个事情,那么销售可能会这样说:“您这边没有货源没有关系,我们公司是做这个的,我们不仅对接了很多的优质厂家,而且我们自己也有大量的货源,都是可以免费为您对接货源的。”如果对方知道一件代发这个东西的话,那就直接给他说一件代发就好了。

+

有了货源之后要卖什么产品呢?这个你是不是也很迷茫?好了,他们一般应该会给你做一场比较「专业」的图文并茂的市场分析,女性群体的基数大、网购行为多、她们的钱好赚,反正最终分析下来的结果就是卖女装很合适。注意,代运营公司对所有咨询如何开网店的客户都是这样说的,因为他们制定了一套非常标准的销售流程,那些话术只需要复制粘贴即可,反正最终都会引导到卖女装去。

+
+

经过一番折腾总算把网店开起来了,有一点良心的代运营公司还有专门的美工给你做一套主图和详情图,没良心的公司顶多就是在网上扒拉几张图片扔给你,甚至从网上不知道哪里卖的几节课程发给你。产品也都上架到店铺了,但只是上架商品的话店铺连访问量都没有,怎么可能会有销量呢?

+

这时他们的「运营师」就会给你出一套针对你店铺的运营方案了,和前面介绍的怎么引导到都卖女装是一样的套路,所有的客户都是同一套话术引导到店铺要继续做推广的方向上去,简单说就是得继续交钱!他们可能会这样说:“我们公司和百度、搜狐、头条等互联网公司都是有合作的,我们会支付给这些平台大量的钱帮助我们的店铺去发软文,这样您相比其它商家的优势就不仅仅只有淘宝(拼多多)平台内的流量了,其它像抖音、百度这些平台的流量都会进到您的店铺,您想一下这是多么庞大的流量和优势!”

+

实际上代运营公司只是专门针对你店铺内容写了一篇软文,然后再用百家号、头条号、搜狐号等发了几篇文章而已,这就是他们所谓的「和百度、搜狐、头条等互联网公司都是有合作的」。为了让你相信这些软文真的生效了,一般会用一些平台去给你的店铺引流,比如新韵网小老弟网红助手,如果你相信了确实是他们的软文推广效果生效了,那你离下一次交钱的机会就不远了。

+

除了这种软文投放的方式还有另外一种销售流程:“因为您的店铺是新店铺,那些基础数据都不好,我们这边可以通过技术手段帮您把店铺基础数据弄好,这样店铺的基础数据上去了之后就可以报官方的一些活动,获取到官方的流量扶持......”。这个在之前写的如何空手利用拼多多(淘宝)赚钱套利?中有介绍如何修改店铺销量,像店铺收藏、商品收藏等也是可以通过上面介绍的新韵网小老弟网红助手来购买的,成本就几百甚至不到一百,但是可能收你几千甚至几万的服务费。

+

这些代运营公司大部分都是在抖音上面投放的广告,体量大一些的公司会在百度投放广告。记得之前在刘鹏老师的星球看到一个投放广告的逻辑:先用极低的价格卖 A 罩杯内衣,然后向这批用户推丰胸产品。最近也看到一个代运营公司搞了个门槛很高的投放方式,自己开发了一个展示货源和网店课程的 APP,然后广告投放方式是推广这个 APP,这样就避免了和其它同行竞争表单还起不来量,而且会主动注册他们 APP 的人意向也比较大,起到了一次清洗客户的作用,听说他们这种 APP 推广的方式线索成本还不到二十,而大多数代运营公司的表单成本已经到了一百多。

+
+

上面说了那么多都是电销的模式,可能你会想自己实地去考察肯定就不会被骗了吧?现实情况是他们欢迎你到公司实地考察,他们有现成的店铺数据这些给你看,而且实地看到他们公司各个团队之间的协作可能会让你被骗的更深,面销会忽悠的更加伤人。

+

可能很多人第一下想到的是报警吧,但实际上报警的作用不是多大,首先双方是有签署合同的,所以这只能算作经济纠纷不属于派出所管辖范围。即使公安局那边立案了,整个过程估计也会把你的耐心给磨没了,破案后你损失的金额能拿回来的可能性也不大。这里只告诉大家「消费者协会」和「市长热线」是比较值得信赖的。

+

总还是觉得中国是人口基数太大了,七八亿的网民里面傻子实在太多了,那些天天只知道刷抖音,梦想着如何快速发财,梦想着如何不劳而获的人,正是代运营公司的潜在客户,人性都是贪婪的,销售稍微帮你放大一下你的贪婪你就输了。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/5rUXZg3sM/index.html b/5rUXZg3sM/index.html new file mode 100644 index 00000000..6c016b62 --- /dev/null +++ b/5rUXZg3sM/index.html @@ -0,0 +1,576 @@ + + + + + + + + 跨域请求是什么?如何解决? | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 跨域请求是什么?如何解决? +

+ + +
+ +
+
+

参考内容:
+JavaScript: Use a Web Proxy for Cross-Domain XMLHttpRequest Calls
+别慌,不就是跨域么!
+跨域资源共享 CORS 详解
+AJAX请求和跨域请求详解(原生JS、Jquery)
+JavaScript跨域总结与解决办法

+
+
+

刚毕业入职,大部分时间还在培训,中间有一段时间的空闲时间,就学习了下 Angular,在学校都是编写的单体应用,所有代码都放在同一个工程下面,到公司使用的是前后端分离了,虽然后端程序也是我自己写的,但是有一些数据是从公司现有接口去拿的,然后就遇到让我纠结了两小时的跨域请求问题,在这里做一个简单的总结输出。

+

什么是跨域请求

+

跨域请求问题是浏览器的同源策略造成的,该策略不允许执行其它网站的脚本,是浏览器施加的安全限制。什么是同源?最初是指网页 A 设置的 Cookie 不能被网页 B 打开,包括三个相同:协议、域名、端口。这个同源是从 URL 判断的,不是从 IP 判断的,如果同一个服务器对应连个域名,这两个域名是不同源的。

+
http://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 非跨域
+
+http://www.nealyang.cn/index.html 调用 http://www.neal.cn/server.php 跨域,主域不同
+
+http://abc.nealyang.cn/index.html 调用 http://def.neal.cn/server.php 跨域,子域名不同
+
+http://www.nealyang.cn:8080/index.html 调用 http://www.nealyang.cn/server.php 跨域,端口不同
+
+https://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 跨域,协议不同
+
+localhost 调用 127.0.0.1 跨域
+
+

同源政策的目的是为了保护用户信息的安全,防止恶意网站窃取数据,随着互联网的发展,同源政策更加严格了,下面三种行为都会受到限制。

+
(1) Cookie、LocalStorage 和 IndexDB 无法读取。
+(2) DOM 无法获得。
+(3) AJAX 请求不能发送。
+
+

所有的现代浏览器都对网络连接进行了安全限制,包括 XMLHttpRequest,如果你的 web 应用程序和其使用的数据在同一个服务器,你不会遇到跨域请求问题。但是当你的 web 应用程序和 web 服务数据不在同一个服务器时,就会被浏览器限制连接了。

+

常用解决方案

+

    对于跨域请求有很多的解决方案,最常用的解决方案是在你的 web 服务器上面设置代理。在设置代理之前就通过,应用程序直接去请求另一个服务器下的数据;设置代理之后,应用程序从自己的 web 服务器中请求数据,再由代理去请求数据,这样 web 服务器拿到数据之后返回给应用程序即可。从浏览器角度看,就是从同一个服务器拿的数据,并没有进行跨域请求。

+
+

通俗易懂的说,你家的宠物狗不会吃别家的食物,因为它担心别人的食物会把自己给药死,所以你的狗狗只管找你要食物,你是它的主人,它绝对相信你,而你可以鉴别别人给的食物是不是安全的。类比,小狗就是浏览器,你就是代理。

+

Angular 中的解决办法

+

上面所说的解决方案在开发过程中不方便操作,每新发一个接口都到服务器中去配置一下,不仅麻烦而且效率低下。首先说一下在 Angular 中一个人比较常用的解决方法,默认你在使用angular-cli构建你的项目,我们可以创建一个代理配置文件proxy.conf.json(假设你的后端服务的访问地址为10.121.163.10:8080),代理配置文件如下:

+
{
+  "/api": {
+    "target": "http://10.121.163.10:8080",
+    "secure": false
+  }
+}
+
+

然后修改package.json文件中的启动命令为"start": "ng serve --proxy-config proxy.conf.json",启动项目时使用npm start即可解决跨域请求问题。

+

上述解决方案仅在开发时使用,你当然可以使用 tomcat、nginx 配置代理,但是这很麻烦,需要打包代码部署,为了保证效率,我们想写完了立刻测试,同时也不想麻烦做后端的同学,在项目发布时,应该把代理配置到服务器中去;修改启动命令也不是必须的,你也可以选择每次使用 ng serve --proxy-config proxy.conf.json命令启动项目;示例代理配置文件内容可以有更多的属性,可以通过网络查阅相关资料。

+

后端解决办法

+

我的后端是是用 tornado 实现的,然后我又写了一个单独的页面用于在大屏幕上展示相关数据,没有用 Angular 了,要通过 AJAX请求数据,又怎么解决跨域请求问题呢?这时就需要设置请求头了,让后端允许跨域请求。

+

这时需要了解一下简单请求非简单请求了,简单请求就是只发送一次请求的请求;非简单请求会发送数据之前先发一次请求做预检,通过预检后才能再发送一次请求用于数据传输。

+

更清晰区别,满足下列两大条件的属于简单请求,而非简单请求就是请求方法为PUTDELETE,或者 Content-Type字段是application/json的请求。

+
+

1.请求方法为 GET、POST、HEAD之一
+2.HTTP头信息不超出字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type,并且 Content-Type 的值仅限于 application/x-www-form-urlencoded、multipart/form-data、text/plain。

+
+

对于简单请求,只需要设置一下响应头就可以了。

+
class TestHandler(tornado.web.RequestHandler):
+    def get(self):
+        self.set_header('Access-Control-Allow-Origin', "*")
+        # 可以把 * 写成具体的域名
+        self.write('cors get success')
+
+

对于复杂请求,需要设置预检方法,如下所示:

+
class CORSHandler(tornado.web.RequestHandler):
+    # 复杂请求方法put
+    def put(self):
+        self.set_header('Access-Control-Allow-Origin', "*")
+        self.write('put success')
+    # 预检方法设置
+    def options(self, *args, **kwargs):
+        #设置预检方法接收源
+        self.set_header('Access-Control-Allow-Origin', "*")
+        #设置预复杂方法自定义请求头h1和h2
+        self.set_header('Access-Control-Allow-Headers', "h1,h2")
+        #设置允许哪些复杂请求方法
+        self.set_header('Access-Control-Allow-Methods', "PUT,DELETE")
+        #设置预检缓存时间秒,缓存时间内发送请求无需再预检
+        self.set_header('Access-Control-Max-Age', 10)
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/6ZjSGnvm9/index.html b/6ZjSGnvm9/index.html new file mode 100644 index 00000000..1a982d8d --- /dev/null +++ b/6ZjSGnvm9/index.html @@ -0,0 +1,471 @@ + + + + + + + + 年度总结 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 年度总结 +
+ + +
+

+ + 2020 年个人总结 + +

+ +
+ + + + +
+ +
+ 还是照例给自己写一个年终总结。持续分享,黄金万两。 +一点感动 +7 月份回深圳办离职,提前通电话告诉了老叔到达深圳的时间。本来是 8 点就能到的,由于飞机晚点导致我半夜 3 点才到老叔那里。期间老叔每隔半小时就给我打电话问到哪里了,我好几次叫他先睡不用管我,我完全可以就近找个酒店舒舒服服的睡一觉,他都坚持说没事,等我。 +让我感动的是我坐的航班落地时间大概是两点,本来以为老叔已经睡了,结果一发消息老叔居然没睡还在等我。到家时他已经把早早就做好的啤酒鸭、可乐鸡翅、猪脚脚热好了,拿出了自己泡的茅台酒,陪他喝到了四点半。早上八点半的样子我被吵醒了,我脑袋还是晕乎乎的,老叔就已经做好了鲫鱼粉,那味道可真的香。 +第二天我请团队同事们吃饭,老叔和之前地产圈的朋友们去喝酒去了,大概十一点的样子老叔给我打电话来了:“小刘,你在哪里?”我一听就知道老头子喝的不少:“老头,你是不是喝醉了?你现在在哪里啊?用不用我去接你?”那一头来了一句:“我现在在树下。”搞得我哭笑不得。我叫他就近找个酒店睡,第二天早上再回来。老头子说:“不行,你在这里我爬都要爬回去,你不用管我,我回去等你。”等到我回去的时候发现他已经在打呼噜了,第二天早上起来还傻乎乎的问我车费是谁付的。 +11 月份和师兄们去参加惠州白盆珠的 100 km 骑行,拐了个弯先去了一趟深圳。老头看到我到了居然开心的连自己办公室密码给忘了,我俩在那里排列组合试了半小时才打开办公室门。当天晚上和叔唠了很久的嗑,第二天给我说昨天太开心了,5 点钟才睡着。 + +老叔是我刚到深圳时所租公寓的管理员,准备抽空专门给老叔写一篇文章 + +赚钱理财 +2020 年其实没有赚什么钱,基本是靠之前攒下来的一些钱在生活,不过这一年确实副业收入要比 19 年多了一些。银行理财依旧和去年一样保持在 4.5%,基金收益在 10% 的样子,因为缺钱早早的把投入基金的钱先取出来了,10% 实际上是 9 个月的成绩,我个人已经满意了。 +有一段时间帮别人负责抖音信息流广告投放,我就直接用我的信用卡充值广告费再报账,所以有那么几个月我的银行流水比较大,平安银行的积分全部用来换了电话费,一年的电话费没有花自己的钱。招商银行信用卡的积分换了两个被子和一个收纳箱,刚好填补成都冬日的寒冷。 +6 月份一个平安保险的销售人员给我推销保险,我把我对保险的理解以及当时对市面上少许现有产品的分析给他说了一遍,他认为我已经达到了保险销售人员的水平,告诉了我一个可以分销保险的平台,我当时也没有把这个当回事。 +隔了两个月,有一个在成都认识的朋友问我保险相关的问题,我就给她做了一些解答,完全当给自己选产品一样给她选,刚好记起保险分销平台这个事就注册提交资料,很快平台审核通过了,最终到手的有 3000 多块钱的佣金。忘记是在什么情况下和这位朋友聊起了化妆品这个话题,于是我顺手花了 900 多给对方买了一瓶精华。 +通过付费社群知道了知乎好物这个功能,把一篇之前发在知乎上的一篇《大学生书单推荐》插入了购物卡片。之后又整理了豆瓣一个话题下面的好物清单,同样发到知乎上面插入了相应的购物卡片,前前后后总共出了大概 10 单的样子,佣金够自己吃几顿麻辣烫。 +自己知道一个可以低价充值腾讯、爱奇艺、网易云等平台会员的渠道,加了一点点价格在闲鱼上面转卖,前前后后赚的钱加起来应该也能吃几顿麻辣烫,不过由于利润低且有一点费时间所以就没做了,不过这也算是自己走通的第一个利用信息差赚钱的案例。 +选择离职 +离职的想法老早就在心头了,3 月份提的离职申请 4 月底离开,但是在流程上我是 7 月份才离职的,因为领导做出了很大让步,允许我半个月在成都半个月在深圳,既然已经说到这份上我也就答应了。但是在年终奖、季度奖和津贴上面却又受到了极不公平的待遇。所以离职的原因很简单,就是干的不爽加上不算年轻的冲动。 +冲动是什么呢?趁还不太老的时候去经历,自己一直在没钱但也没有感觉到缺钱的状态下生活,估计大多数白领阶层也是这个状态,没有家庭的压力,没有赡养父母的压力,更没有病痛的折麽。所以我想提前去感受一下缺钱的感觉,这个想法确实是脑子有点病,哈哈哈。 +干的不爽是为啥呢?不喜欢公司的加班文化,部门每个月都会有一个加班时长排名表,我们团队每个月都是部门垫底的,据说加班时长少于 40 小时会被领导谈话。我个人不反对加班,但是当大家都仅仅简单的在比加班时长的时候,那个氛围就待的很没有意思了。 +在大企业工作的程序员都会遇到的一个问题。我自己在 5G 测试部门做内部工具开发,回家时大家一听都还觉得挺洋气的,能接触到业界比较前沿的技术与知识,但实际上我连产品最终长什么样子都没有见过,我希望自己能看到一个产品从 0 到 1,并且送到用户手中用户愿意付费的过程。 +当然离职最大的原因是意识到个人的增长是线性的。我在部门新人里面还是算干的比较好的,算一下自己如果一直干的比较好,部门那个 5G 专家可能就是自己将来的样子,自己还不一定能达到那个水平。如果干的不好的话也有榜样在那里,那一群每天上午还按时在工位做体操的老油条就是自己将来的样子,不喜欢一眼就能望到未来样子的感觉。 +离职之后的感受是大公司提供的保护壳真特么硬啊,出来之后才明白挣钱确确实实不容易,所以在这里奉劝各位要离职可得想好啊,说实话我现在有一点怀念那种划一天水都依旧有工资的日子呢。 +一点收获 +自己从 0 到 1 设计开发的系统得到了同事们的认可,完全靠着「用户怎么用着舒服」这个理念在开发,逐渐得到了其它部门领导的青睐。在师傅的督促与推动下,我在离职前将其做了平台化开发,临走前已经将西安一部门接入系统,我还悄悄在系统里面留下了我的信息,不知道将来可有人能发现或是已经发现。 +学习了一点互联网广告投放领域的知识,对互联网流量有了比之前更加深刻的理解。中国 8 亿网民里面绝大部分都是穷人,穷人里面又有一大部分是天天只知道刷抖音的懒人,梦想着天上会掉下来馅饼喂到嘴里,你脑子里的常识可能有一亿人都不知道。大部分小公司赚的是穷人的钱,大家向往的互联网巨头又赚的基本是小公司的钱。 +对京东、淘宝、拼多多这一类电商平台有了新的认识,慨叹电商领域的水简直深不可测,很多小公司依附于电商平台生存,太多套路也不方便在这里讲。自己脑子里将实体经济与互联网经济的逻辑打通了一部分,互联网平台要么自己能提供某种服务,要么就是带的动货才能正向循环。这里做个预测,2021 年会有更多的平台接入电商平台,功能类似于知乎好物。 +体验了一把纸醉金迷的感觉,对自己的定位又清晰了一点。人这一辈子总要多多少少面对一些诱惑,就好像电影《八佰》里面那个军官一样,自己没有摸过咪咪所以老是好奇摸咪咪的感受,没有经历过总是容易经不住诱惑。我庆幸自己能比较早的去经历,虽然不至于让我明白我想要什么,但至少让我明白了自己不想要什么。 +人生的第一桶金不是只有钱,还有经历和认知。这一年更多的是见识,是心灵上的收获。 +新的伙伴 +到成都后和一群贵州、四川小伙伴待了段时间,他们基本没有上过高中,有的甚至小学都没有毕业。IT 行业工作的人多少是有五险一金、年终奖、季度奖这些玩意的吧,但是这部分人所在的公司可能连社保都不会交。我清楚的记得我刚回成都时,一个小孩子给我炫耀说:“刘哥,我们公司是有社保的”。 +他们喜欢像抖音一样各种炫耀,但是自己挣得钱又不够自己花的,要是有一个月没有发工资的话那么就还不起花呗了。如果某一个月多挣了一点点钱,就是去酒吧、去嫖娼。我发现这才是目前社会上最大群体的一个缩影。 +直到最近我才真的明白,你不要和他们讲什么要去做有积累的事,不要和他们讲什么所谓的长期主义,一个每天还在为生活发愁的人,就不要和他去谈什么理想。所以我写了一篇如何通过撸茅台赚钱的文章,在这里我想告诉你这种短期快速套利不会让你有什么积累,会打乱你的时间,就是个奶头乐。 +得益于校友会这样一个平台,在四川校友会认识了一个师兄,我目前正在师兄手下学习做一点事情。主要的精力都在少儿编程教育上面,惊叹我大一才会的东西现在一个不到十岁的小孩子就会了,已经变成了一个拼娃的时代,家长们生怕邻居孩子会的才艺自己孩子不会,可能这就是「内卷」吧。 +写在最后 +2020 年做了一个对我个人来说比较重要的尝试,那就是自己输出一套爬虫课程,这算是第一次自己打造一个产品,这是一件很有意思的事情,因为他人是拿腰包里的钞票为你投票,能拿到一票就开心的不得了。2021 年继续在这个方向尝试,尽可能的去做好做精。 +在运动健身这一块做的很差,除了去白盆珠参加了一次百公里公益骑行,其它就没有什么值得说出嘴的运动了。顶多就是跑跑步,亦或是骑 10 来公里的单车,一年时间没有打过羽毛球了。2021 争取用自行车上下班,尽量保持平均每周能有 60 公里骑行的运动量。 +2021 年继续尝试将自己产品化,一直靠兴趣驱动的我,执行力总算得到一点加强。给新的一年设下几个关键词吧:执行、坚持、分享、自律。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 2019 年个人总结 + +

+ +
+ + + + +
+ +
+ 写总结的习惯是从 2015 年开始的,我的大学学费是县政协资助的,叔叔阿姨们唯一的要求就是每年给他们写个总结汇报一下学习情况,毕业后敦促我写总结的人则从外力转为内心。 +一点感动 +上半年我还很年轻,那时候还会经常使用 QQ、Soul、同桌、一罐 等等社交产品,无意结识了一个还在读高中的同性恋女孩子,我没学过心理学不知道用什么专业名词描述她的情况,反正就是心理上有严重的问题,玻璃心、想自杀等等。几次拼了我老命陪她聊到半夜两三点,现在完完全全能正视自己的情况了。 +让我感动的是有次她问我在干啥,我随便拍了一张自己的被汗水打湿衣服发给她,告诉她自己正在打羽毛球。小姑娘给我说我穿的衣服不好看,说我才多大穿的衣服太老了,我也就随口一说叫她给我这个老年人推荐推荐衣服,因为她要上课后面就一直没有回我消息。 +第二天早上睡醒了看到了小姑娘的几十条消息,是半夜两点多发的,给我挑了好几件衣服裤子,还给我画了几张示意图(如下),瞬间收获一份小感动。我也遵从小姑娘的意见买了两件上班穿穿,结果一到部门就是众目睽睽之下给我说穿的好酷,穿几次了都还是会引来大家不一样的目光,个性低调的我还是选择走大众程序员路线,老就老吧。 + + +前几天小姑娘给我发了她暗恋的小姐姐的照片,虽然极少时候还是会上课偷偷玩手机,但也在努力的备战高考。我做的不好的就是她多次给我讲自己在龙岗,我每次都把她当成龙华的,希望写了这篇总结之后不再记错吧。 +赚钱理财 +这个小标题起的有点大,仅说说我自己的实际情况吧。凭着运气,2019 年的银行理财收益在 4.5% 左右,基金收益在 7% 左右。我没有去玩股票,网上各种理财课程可能都会给你讲股票的收益多么多么高,但是他们从来不会给你说玩股票的风险有多高,更不可能给你讲玩股票会严重影响自己的心情,可能连自己的本职工作都会搞砸,所以我不建议职场新人进入股市。 +房东忙的时候我会帮他带房客看房,他也给了我小几千块钱的介绍费,加上每个月没交网费直接用他的,还时不时蹭蹭房东的饭局,也给自己省下来周末出去散步的费用了。上半年也给别人分享过两三个课程,在群里分享过一点小技能,大家给发了点红包,交个朋友、图个开心。 +总的来讲,理财这方面做得很差,没有花什么时间去学习,我们的大学也没有教给学生一点金融知识,这一年只读了几本写给小白的理财书,今年在这个领域要多花一点功夫,希望能入得了门吧。 +写书失败 +快要毕业的时候和电子工业出版社签了一份合同,合同内容就是我要写一本叫做《知识图谱:自顶向下方法》,这本书的计划内容是我的毕业设计,已经写了一百多页的内容了,但现在确定以失败告终。 +一者我手里现有的数据属于机密数据,没办法拿来直接使用;二来书中有很大一部分内容涉及到网络爬虫,上半年网上曝了很多因为抓数据而入狱的案例,出版社和我都害怕;三者知识图谱所需要的数据量很大,而且我写的那个领域又是中国特有的经济责任审计领域,大量数据都得从政府网站来,更害怕了;最重要的原因是自己懒,写书的那几个月确实非常的累,想想自己都还是个菜鸟呐,有啥资本教给别人知识,心里给了自己后退的理由。 +小时候曾夸下海口说要给父亲写个传记,也不知道有没有那么一丢丢可能性实现,写家里的狗时,发现写这样的内容会增加我的多巴胺分泌,以后不开心了就写这样的小故事。 +运动健身 +在深圳校友会骑行社师兄师姐们的带领下,同时也得益于一起入职的小伙伴送了我一辆 MERIDA,我喜欢上了骑行这项运动,基本上每周五都会出去骑几十公里,中间还参加了环漓江骑行和 TREK100 骑行,锻炼的同时也见到了美丽的风景。深圳对自行车是不太友好的,基本没有什么自行车道,所以我们大部分时间都是等到晚上车少,交警下班了之后才开始骑行。 +除了骑行每周一也会打两小时羽毛球,谈不上专业,但至少打的不再是广场球了。偶尔也会出去爬爬山啥的,身体确实比上学时候要好很多,而且多锻炼能让自己的精神面貌更好,精气神好也能稍稍掩盖长得丑的缺点。以前每年再怎么也会因为感冒一类的问题进几次医院,19 年仅一次因为智齿发炎去过医院。 +削减迷茫 +大概在四五月份的时候吧,几乎天天失眠,经常夜里只睡了三四个小时,有时甚至通宵未眠,心里很清楚是因为迷茫了,大概就是「晚上想了千条路,早上醒来走原路」的状态。好在自己的调节能力还不算差,同时也有楼下的叔叔、自己的好朋友能唠唠嗑,差不多两个月就回归正常状态了。 +从几个比我晚入职半年的小伙伴那里了解到,他们现在的情况和我四五月份的情况差不多,我想绝大部分普通人都会经历这个迷茫期吧,大部分人也都能通过时间调节过来,调节不过来的那部分人就成为了媒体比较喜欢的人。 +现在迷茫的雾气已经没有那么浓了,初入社会肯定有很多的不成熟,但谁不是这样过来的呢?更何况我并不像多数程序员那样交友严重同质化,周末也不会死宅在家里不出去,猜测我应该比大多数人更潇洒自在的,嘿嘿。 +新的思想 +大家基本都是看着金庸武侠小说(相关影视作品)长大的,没有人写武侠小说能超过金庸。偶然一天在推特上刷到一条评论,大意是:没有人写武侠小说能超过金庸不正代表着社会的进步吗?金庸的成就如此巨大,一个很重要的历史背景是那时候大家没有那么多小说可看呀,哪里像今天遍地的网络小说。咱们没必要去争论这个观点的对错,重要的是它告诉了我们一个不一样的角度去看待问题。 +上面只是一个特例,思维方式是一点一点改变的,认知水平是一点一点提升的,一年时间修正了不少我此前狭隘的观点,这样的修正还在继续,我也会让这样的修正持续下去。 +写在最后 +巴黎圣母院被烧、凉山火灾、女排十连冠、NBA 事件、无锡高架桥倒塌......等等发生在 2019 年的大事,不知道还有多少朋友会记起来。时间从来不会等谁,网友也都是不长记性的,成熟的一部分无非是经历的多了,失望的多了,然后变得更耐操一点,总之生活依旧得继续,人总会亦悲亦喜,那为啥不把悲缩小喜放大呢? +成功没有银弹、没有捷径,少讲大道理,多解决小问题。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 坟头蹦迪|清理手机上的干扰信息|2018 简短总结 + +

+ +
+ + + + +
+ +
+ 前一周在一席上面看了一场演讲叫「不正经历史研究所所长」,整场演讲都伴随着欢声笑语,讲的是民间文化与经典文化,经典文化实际上是统治阶级强行推动的精致文化,绝大部分老百姓是看不懂这些东西的,反正我自己参观博物馆,如果没有讲解,我是完全看不懂那些玩意的。 +能和生活联系在一起的文化最有活力,比如天天发杨超越祈求不挂科,拜拜马云希望有更多的钱等等。有一句老话叫「人活一口气」,民间文化都基于此,我在别人面前要抬得起头,要的是气派、要的是大气,上个世纪的「四大件」应该就是成功的一个标配,老百姓置办这些物件后,在邻居面前也更能昂首挺胸了。 +昨天了解到一个朋友家乡的有一个非常奇怪的习俗,那就是「坟头蹦迪」,我最开始的想法是,这是不是最近兴起来的习俗,但是一问才知道,他小时候就是这样的了。基于我们的体系是无法理解坟头蹦迪的,你能说它们对于亲人的去世就不伤心吗?他们要的还是能抬起头,来参加追悼会的人们看到这么豪华的场面,第一反应是故人后代是很有成就的,在某种程度上这算是对故人的颂扬。 + +花了几个小时时间把微信清理了一下,只留下了 83 个公众号,这其中还包括一信用卡一类的服务号,也就是说只留下了 70 个左右的公众号。留下的都是小而美的公众号,这些大佬作者的更新频率也很低,比如子柳老师最近才更新了一篇文章,而上一篇文章的更新时间是 7 月 25 日。 +最近在自己身上认识的一个问题是,关注的公众号太多读不过来,虽然在此之前已经筛选掉了很多号,然而自己还是读不过来,所以索性就做了减法。现在像公众号这些,已经变成了获取信息的一个重要渠道,我也确实比周围小伙伴掌握信息更快,大部分时间也自认为比他们更了解真相。 +但是我最近突然有个疑问,我真的比他们更了解真相吗?大部分人是通过垃圾文章获取信息,我虽然没有跟着垃圾文章人云亦云,但是我还是通过网络获取信息的,那么我是不是看到的是另外一种看起来更接近真相的假象呢? +这些信息其实我就算不知道好像也没有什么大碍,顶多是别人谈什么新闻时,我所了解的也就他们知道的那么点而已,更好的方式是让自己的大脑去思考,以时间的维度去追踪事件的发展。 +顺便推荐一个应用叫「刷屏」,我发现自己通过「刷屏」、「知识星球」、「微信公众号」、「朋友圈」四个地方,已经掌握大部分信息了,经常同事给我说某某新闻,而我在两三天前已经见过了。 + +我对国际计量大会修改「1 千克」的定义这件事印象很深,但是这件事我只看到在阮一峰老师的文章中有被提到过;人们更喜欢听自己了解的领域,跟自己同好的很容易产生好感,一些明星结婚、出轨竟然能把微博的服务给搞挂了,说明绝大部分人还是更喜欢撩自己 G 点的信息。 + +另外第二个问题是自己现在比在学校时更喜欢玩手机了,在学校时还能做到不带手机去自习,现在是隔一会儿就看看手机,把本来就碎片化的时间变得更加碎片化了,这种效率导致工作、学习效率低下,所以把微信和 TIM 的通知全部关掉了。 + +写到这里发现有点点像总结,索性就给自己简单总结总结吧。2018 我从校园走向了社会,完成了从学生到职场的转变。最大的改变是思维的提升,知道去投资自己,在学生时代,愿意花几百块钱去买课程,这对我来说是很大的突破,和现在愿意花几百的概念完全不一样,想想那时候一个月生活费总共也没有多少,而我去看了下自己在毕业前花在这方面的钱居然有一千多,如果加上毕业后的开销,那就是两千多了,真感谢那时候的自己。 +这其中的收获是巨大的,后面我偶尔会向朋友推荐一些好的付费课程,但是他们都和大部分人一样,吃一顿饭花几百块钱,而如果花点钱买一堂课提升一下自己,就好像要他命一样,所以后面就不和他们讲了。 + +2018 开始用文字记录技术、生活、感悟,这其中的收获也是不小的,认识了此前只能仰望的大佬,结交了志同道合的朋友,而且也让自己更愿意去思考了。收到电子工业出版社的约稿合同,但是现在书都没写完,明年还写不完的话,那就不写了,主要是懒。 +自负的缺点已经在渐渐改变了,更加懂得了谦虚。眼界不再局限于技术,很多东西我都会去了解,也结交了很多有趣的人,初入职场,好好学习与不同的人沟通。以后如果有能力,希望能给山区带去一点点教育资源。 +2019 依旧坚持每周和同事或者校友打一次羽毛球,常去爬爬山,和有趣的朋友一起疯一疯;多读书,用豆瓣等工具去筛选好书,加大阅读量;常输出,输出是更高层次的输入;尝试去了解金融的逻辑;学习新的技术领域。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 2018 年个人总结 + +

+ +
+ + + + +
+ +
+ 这个总结写的还算认真,回头看,我真的运气很好,遇到了很多大佬,在我还未毕业时并没有嫌弃我傻,教我的不仅仅是技术,还有理财、为人处世,下面是我这一年的成长经历。 +有一段时间因为华为 34 岁以上员工被裁、中兴程序员跳楼等事件的发生,各种蹭热点讨论“中年危机”的文章漫天飞,那时我正忙于找工作。 +当时一个微信群里面大家各种讨论中年危机,都在给自己制造焦虑,刚好群里有个大神可能看大家过于焦虑,就在群里发了几条消息,教大家如何避免中年危机,并且推荐了两本书。 +还是学生的我下意识的就发了一个添加好友请求,庆幸的是他同意了我的好友请求,当然我们没有什么交流,我的问题过于浅显,我明白自己这个水平问问题,会浪费人家时间,当时的想法是看看大神的朋友圈,他平时都接触什么,自己学习一段时间。 +大神推荐的书是李笑来写的《把时间当做朋友》、《财富自由之路》,两本书在学校图书馆都没有,我就给学校图书馆荐购系统提交了这两本书,图书馆效率也挺高,不到一周就把书给买回来了,我立马就借回来阅读。 +书中的内容刷新了我以前狭隘的认知,自己从来没有像书中那样考虑问题,除了对作者的佩服之外,更多的是思考自己这种学生思维局限性太大了,要慢慢的将它摒弃。 +有个定律是你关心什么就会来什么,后面陆续碰到几位像大神一样的人士,并加了他们的微信,但是都仅仅是通过他们朋友圈的蛛丝马迹去找知识,通过他们朋友圈的分享内容,我知道了“简七理财”、“码农翻身”公众号,然后知道了《富爸爸穷爸爸》、《小狗钱钱》,于是我通过微信读书,读完了这两本书,逐渐培养了理财理念。 +后来没隔多久,简七出书了,我第一时间就买了她写的《好好赚钱》(同期还有刘大也出了《码农翻身》一书,我也第一时间买了),简七写的内容通俗易懂,很容易理解。 +刘大在群里开了几次公开课,作为计算机专业的我,被刘大对技术的理解之深给折服了,正是业界浮躁的时候,成千上万人想着人工智能、大数据、区块链,而刘大一直能沉下心来去了解技术的原理,这给了我一个很好的榜样,我也逐渐沉下心来,开始去补最基础的知识,像《深入理解计算机系统》一类书也能尽下心来慢慢去啃(当时没啃完,最近又在啃),这种不浮躁的特质对我的技术成长是很有帮助的。 +此后有一天,另一个大神在朋友圈分享了曹大写的《从校园到职场系列文章》,喜欢深入挖掘信息的我,以曹大公众号为源头,又找到了冯大、池大、二爷等人的公众号。 +作为自由的大四学生,因为不用担心第二天起不来,我那段时间经常熬夜阅读他们的文章,再阅读的过程中我也开始思考自己此前哪些想法狭隘,哪些品质又是值得继续保持的。 +也是那时开始接受知识付费的,那时候已经有小密圈(现在叫知识星球)了,出于对几位大佬的信任,我第一次大胆的花了几百块钱加入了刘大、曹大、冯大、程序员小灰的小密圈,其中的内容比网上蹭热点的文章好不知多少倍,一贯爱捕捉蛛丝马迹的我,又通过评论信息发现了 angela zhu、子柳老师、陈利人老师等,然后去找他们的文章,他们输出的内容要比水军写的文章好太多。 +自己也是从那时候开始坚持写文章记录自己的心得的,通过写文章,我认识了很多优秀的人,比如吴小龙同学、了不起的杰克、java 小咖秀等公众号的作者,和他们交流的很少,但是却很受用,他们的积极向上也影响着我一直保持着乐观豁达的心态。 +自己写的文章也被几个资深程序员赞同,同时还收到了两个出版社发来的出书邀请,让我体会到了无心插柳柳成荫的收获。 +让我坚持一直写文章的动力不是赚钱,而是我切切实实体会到了它给我个人带来的成长,为了自己日后再看时能立刻就找到清晰的逻辑,我把都尽可能把文章写得有理有据,掌握自己的节奏,尽量提高文章质量。此前写的谈一下写作的重要性一文有说写作可以带来的好处。 +现在已经不把自己当新人了,而且有同龄人甚至比我年龄还大的人向我咨询问题时,我也能给出合理建议,都得到了他们的肯定。最近发现和周围伙伴最明显的一个区别就是,对于同一个新闻,我经常早于他们半天甚至一两天知道,而且掌握的信息比他们还准确,我认为这就是整体认知水平的提升。 +想说的是,执行力与信息素养很重要,执行力强的人会与你拉开越来越大的距离,信息素养也是一个关键品质,现在网络上充斥着大量的虚假信息,如何去分别这些信息的真假,在相同条件下如何获得更多的有效信息,是必备的能力。 +当前年龄 23,刚大学毕业几个月,没读研。按十年为期给自己定了几个小小的目标: +父母是地地道道的农民,智能手机都不会用,十年之类给自己和父母把重疾险、意外险之类的保险配置齐全,虽然父辈一直反对买保险。 +提高获取信息的能力,虽然现在对信息的掌握都比周边伙伴要早半天至几天,但是都不是自己的分析结果,学习以时间的纬度跟踪事件的发展。 +学习理财知识,现在只对信用卡、基金有一点点的了解,不管炒不炒股,金融知识都还是要学的,这方面通过看书、阅读、小额实操学习。 +提升自己的技术实力,职业是程序员,前后端都做,但是自己对技术的热情不是多么高涨(至少比身边一半人要高涨),以我对自己的了解,我在技术的道路上成长为小公司一个的架构师应该不成问题,再高层级怕是不行。 +慢慢做到不止一份收入来源,这方面不是多清晰,现在每个月平均会有 200 左右的非工资收入(帮助别人时发的红包等),十年后做到其它收入基本和工资持平。不至于因为钱的问题而忍受心中的不快,至少得有能指着老板的鼻子说“老子不干了”的底气。 +世界那么大,应该去看看,国内除了西北地区,中国很多地方已经留下了我的足迹,旅游不仅仅是玩耍,更是提升见识、获得灵感的有效途径,十年至少得把自己的脚印印到 5 个国家的土地上吧。 +十年之后应该已经结婚了,房子是现在最遥不可及的目标,但是心里莫名有一股自信,这个后面会实现的,虽然不知道哪里来的这股自信。 +最后一个,趁年轻,多学习,做一个终身学习的人,时刻保持学习的态度,多做有利于他人的事,现在水平不高,我能帮助到的大部分都是硕士及以下。努力提高自己,帮助更多的人。更大的目标是能给山区学校带去一些更好的教育资源。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/6ys69eBdL/index.html b/6ys69eBdL/index.html new file mode 100644 index 00000000..a1e3faf8 --- /dev/null +++ b/6ys69eBdL/index.html @@ -0,0 +1,1842 @@ + + + + + + + + 前端 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 前端 +
+ + +
+

+ + Vue 入门避坑——Vue + TypeScript 项目起手式 + +

+ +
+ + + + +
+ +
+ 在此前我使用的前端框架是 Angular,使用过 TypeScript 后你就会讨厌 JS 了,我学习 Vue 时的最新版本是 2.5,相信大部分同学都不会认为 Vue 那样又细又长的代码很美观吧,简单看了一些网络博客后,我毅然决然引入了 TypeScript 进行开发,本文仅整理记录我自己遇到的一些坑。 +使用 Cli +脚手架是一个比较方便的工具,这里需要注意的是@vue/cli和vue-cli是不一样的,推荐使用npm i -g @vue/cli安装。 +安装完成后,可以直接使用vue create your-app创建项目,你可以选择使用默认配置亦或是自己手动选择配置,按提示一步一步向下走即可,它会根据你的选择自己创建比如tsconfig.json等等配置文件。这里推荐使用less开发样式,sass老是在安装的过程中出问题。 +当然你也可以使vue ui命令启动一个本地服务,它是一个 Vue 项目管理器,提供了一个可视化的页面供你管理自己的项目,它的样子如下图所示,还是比较清新的。 + +使用 vue-property-decorator +Vue 官方维护了 vue-class-component 装饰器,vue-property-decorator 则是在vue-class-component基础上增强了更多结合Vue特性的装饰器,它可以让 Vue 组件语法在结合了 TypeScript 语法后变得更加扁平化。 +截止本文时间,vue-property-decorator共提供了 11 个装饰器和 1 个Mixins方法,下面用@Prop举个例子,是不是看起来引起极度舒适。 +import { Vue, Component, Prop } from 'vue-property-decorator' + +@Component +export default class YourComponent extends Vue { + @Prop(Number) readonly propA: number | undefined + @Prop({ default: 'default value' }) readonly propB!: string + @Prop([String, Boolean]) readonly propC: string | boolean | undefined +} + + +// 上面的内容将会被解析成如下格式 + +export default { + props: { + propA: { + type: Number + }, + propB: { + default: 'default value' + }, + propC: { + type: [String, Boolean] + } + } +} + +使用 Vuex +关于怎么使用Vuex此处就不再做过多说明了,需要注意的一点是,如果你需要访问$store属性的话,那么你必须得继承Vue类,坑的地方是在某些情况下即使你没有继承Vue,它也能通过编译,只有在程序运行起来的时候才报错。 +class ExampleApi extends Vue { + + public async getExampleData() { + if (!this.$store.state.exampleData) { + const res = await http.get('url/exampleData'); + if (res.result) { + this.$store.commit('setExampleData', res.data); + return res.data; + } else { + promptUtil.showMessage('get exampleData failed', 'warning'); + } + } else { + return this.$store.state.exampleData; + } + } +} + +使用自己的配置(含代理) +vue.config.js是一个可选的配置文件,如果项目的根目录中存在这个文件,那么它会被@vue/cli-service自动加载,它的配置项说明可以查看配置参考。 +我们再开发过程中都会使用代理来转发请求,代理的配置也是在这个文件中,它的官方说明在devserver-proxy中,下面是一个简单的vue.config.js文件例子。 +module.exports = { + filenameHashing: true, + outputDir: 'dist', + assetsDir: 'asserts', + indexPath: 'index.html', + productionSourceMap: false, + transpileDependencies: [ + 'vue-echarts', + 'resize-detector' + ], + devServer: { + hotOnly: true, + https: false, + proxy: { + &quot;/statistics&quot;: { + target: &quot;http://10.7.213.186:3889&quot;, + secure: false, + pathRewrite: { + &quot;^/statistics&quot;: &quot;&quot;, + }, + changeOrigin: true + }, + &quot;/mail&quot;: { + target: &quot;http://10.7.213.186:8888&quot;, + secure: false, + changeOrigin: true + } + } + } +} + +让 Vue 识别全局方法和变量 +我们在项目中都会使用一些第三方 UI 组件,比如我自己就使用了 Element,但是在使用它的$message、$notify等方法时就直接报错了,究其原因就是$message等属性并没有在 Vue 实例中声明。 +官方对此给出了很明确的解决方案,使用的是 TypeScript 的 模块补充特性,可以查看增强类型以配合插件使用。既然知道是因为没有声明导致的错误,那我们就给它声明一下好了,在src/shims-vue.d.ts文件中添加如下代码即可,如果没有该文件请自行创建。 + +看到网上也有一部分人说的是src/vue-shim.d.ts,反正不管是怎么命名这个文件的,它们的作用是一样的。 + +declare module 'vue/types/vue' { + interface Vue { + $message: any, + $confirm: any, + $prompt: any, + $notify: any + } +} + +这里顺道提一下,src/shims-vue.d.ts文件中的如下代码是为了让你的 IDE 明白以.vue结尾的文件是什么玩意儿。 +declare module '*.vue' { + import Vue from 'vue'; + export default Vue; +} + + +路由懒加载 +Vue Router 官方有关于路由懒加载的说明,但不知道为什么官方给的这个说明在我的项目里面都没有生效,但使用require.ensure()按需加载组件可以生效。 +// base-view 是模块名,写了相同的模块名则代码会被组织到同一个文件中 +const Home = (r: any) =&gt; require.ensure([], () =&gt; r(require('@/views/home.vue')), layzImportError, 'base-view'); + +// 路由加载错误时的提示函数 +function layzImportError() { + alert('路由懒加载错误'); +} + +上面的方式会在编译的时候把文件自动分成多个小文件,编译后的文件会以你自己命名的模块名来命名,如果代码之间有相互依赖,依赖部分代码编译后的文件会以两个模块名相连后进行命名。 +但是需要注意的是,这样拆分小文件之后引入了另外一个新的问题,因为客户端会缓存这些编译后的 js 文件,如果功能 A 同时依赖了a.js和b.js两个文件,但用户在使用其它功能时已经把a.js缓存到本地了,使用功能 A 时需要请求b.js文件,这时程序就很容易报错,因为此时在客户端这两个文件不是同一个版本,所以可能导致a.js调用b.js中的方法已经被删了,进而导致客户端页面异常。 +关于引入第三方包 +项目在引入第三方包的时候经常会报出各种奇奇怪怪的错误,这里仅提供我目前找到的一些解决办法。 +/* + 引入 jquery 等库可以尝试下面这种方式 + 只需要把相应的 js 文件放到指定文件夹即可 +**/ +const $ = require('@/common/js/jquery.min.js'); +const md5 = require('@/common/js/md5.js'); + +引入一些第三方样式文件、UI 组件等,如果引入不成功可以尝试建一个 js 文件,将导入语句都写在 js 文件中,然后再在main.ts文件中导入这个 js 文件,这个方法能解决大部分的问题。例如我先建了一个lib.js,然后在main.ts中引入lib.js就没有报错。 +// src/plugins/lib.js +import Vue from 'vue'; + +// 树形组件 +import 'vue-tree-halower/dist/halower-tree.min.css'; +import {VTree} from 'vue-tree-halower'; +// 饿了么组件 +import Element from 'element-ui'; +import 'element-ui/lib/theme-chalk/index.css'; +// font-awesome 图标 +import '../../node_modules/font-awesome/css/font-awesome.css'; +import VueCookies from 'vue-cookies'; +import VueJWT from 'vuejs-jwt'; + +Vue.use(VueJWT); +Vue.use(VueCookies); +Vue.use(VTree); +Vue.use(Element); + + +// src/main.ts +import App from '@/app.vue'; +import Vue from 'vue'; +import router from './router'; +import store from './store'; +import './registerServiceWorker'; +import './plugins/lib'; + +Vue.config.productionTip = false; + +new Vue({ + router, + store, + render: (h) =&gt; h(App), +}).$mount('#app'); + +因为第三方包写的各有特点,在引入不成功的时候基本也只能是见招拆招,当然如果你的功底比较深厚,你也可以自己写一个index.d.ts文件,实在不行的话,那个特殊的组件不使用 TypeScript 来写也能解决,我目前还没有找一个可以完全解决第三方包引入错误的方法,如果您已经有相关的方法了,希望能与你一起探讨交流。 + +
+ + Read More ~ +
+
+
+ +
+

+ + JavaScript 进阶知识、技巧 + +

+ +
+ + + + +
+ +
+ 对象 +Js 共有number、string、boolean、null、undefined、object六种主要类型,除了object的其它五中类型都属于基本类型,它们本身并不是对象。但是null有时会被当做对象处理,其原因在于不同的对象在底层都表示为二进制,在 js 中二进制前三位都为 0 的话就会被判定为object类型,而null的二进制表示全是 0, 所以使用typeof操作符会返回object,而后续的 Js 版本为了兼容前面埋下的坑,也就没有修复这个 bug。 +&quot;I'm a string&quot;本身是一个字面量,并且是一个不可变的值,如果要在这个字面量上执行一些操作,比如获取长度、访问某个字符等,那就需要将其转换为String类型,在必要的时候 js 会自动帮我们完成这种转换,也就是说我们并不需要用new String('I'm a string')来显示的创建一个对象。类似的像使用42.359.toFixed(2)时,引擎也会自动把数字转换为Number对象。 +null和undefined没有对应的构造形式,它们只有文字形式。相反,Date只有构造,没有文字形式。对于Object、Array、Function和RegExp(正则表达式)来说,无论使用文字形式还是构造形式,它们都是对象,不是字面量。 +Array 类型 +数组类型有一套更加结构化的值存储机制,但是要记住的是,数组也是对象,所以有趣的是你也可以给数组添加属性。 +var myArray = [&quot;foo&quot;, 42, &quot;bar&quot;]; +myArray.baz = &quot;baz&quot;; +myArray.length; // 3 +myArray.baz; // &quot;baz&quot; + +数组类型的length属性是比较有特点的,它的特点在于不是只读的,也就是说你可以修改它的值。因此可以通过设置这个属性从数组末尾删除或添加新的项。 +var colors = [&quot;red&quot;, &quot;blue&quot;, &quot;green&quot;]; +colors.length = 2; +console.info(colors[2]); // undefined +colors.length = 4; +console.info(colors[4]); // undefined +// 向后面追加元素 +colors[colors.length] = &quot;black&quot;; + +数组还有一些很方便的迭代方法,比如every()、filter()、forEach()、map()、some(),这些方法都不会修改数组中包含的值,传入这些方法的函数会接收三个参数:数组项的值、该项在数组中的位置、和数组对象本身。 +Function 类型 +在 ECMAScript 中,每个函数都是Function类的实例,而且都与其它引用类型一样具有属性和方法。由于函数时对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。 +在函数的内部有两个特殊的对象,this和arguments。arguments对象有callee和caller属性。caller用来指向调用它的function对象,若直接在全局环境下调用,则会返回null;callee用来指向当前执行函数,所以我们可以通过下面的方式来实现阶乘函数。 +function factorial(num) { + if (num &lt;= 1) { + return 1; + } else { + return num * arguments.callee(num-1); + } +} + +每个函数都包含两个非继承而来的方法,apply()和call(),这两个方法都是在特定作用域中调用函数,实际上等于设置函数体内this对象的值。首先,apply()方法接收两个参数,一个是在其中运行函数的作用域,另一个是参数数组,其中第二个参数可以是Array的实例,也可以是arguments对象。call()方法与apply()方法的作用相同,它们的区别仅仅在于接收参数的方式不同,在使用call()方法时必须逐个列举出来。 +window.color = &quot;red&quot;; +var o = {color: &quot;blue&quot;}; +function sayColor() { + console.info(this.color); +} +sayColor(); // red +sayColor.call(this); // red +sayColor.call(window); // red +sayColor.call(o); // blue +sayColor.apply(o); // blue + +需要注意的是,在严格模式下未指定环境对象而调用函数,则this值不会转型为window,除非明确把函数添加到某个对象或者调用apply()或call()。 +安全的类型检查 +Js 内置的类型检查机制并不是完全可靠的,比如在 Safari(第5版前),对正则表达式应用typeof操作符会返回function;像instanceof在存在多个全局作用域(包含 frame)的情况下,也会返回不可靠的结果;前文提到的 Js 一开始埋下的坑也会导致类型检查出错。 +我们可以使用toString()方法来达到安全类型检查的目的,在任何值上调用Object原生的toString()方法都会返回一个[object NativeConstructorName]格式的字符串,下面以检查数组为例。 +Object.prototype.toString.call([]); // &quot;[object Array]&quot; +function isArray(val) { + return Object.prototype.toString.call(val) == &quot;[object Array]&quot;; +} + + +作用域安全的构造函数 +构造函数其实就是一个使用new操作符调用的函数,当使用new操作符调用时,构造函数内用到的this对象会指向新创建的对象实例,比如我们有下面的构造函数。 +function Person(name, age) { + this.name = name; + this.age = age; +} + +现在的问题在于,要是我们不使用new操作符呢?会发生什么! +let person = Person('name', 23); +console.info(window.name); // name +console.info(window.age); // 23 + +很明显,这里污染了全局作用域,原因就在于没有使用new操作符调用构造函数,此时它就会被当作一个普通的函数被调用,this就被解析成了window对象。我们需要将构造函数修改为先确认this是否是正确类型的实例,如果不是则创建新的实例并返回。 +function Person(name, age) { + if (this instanceof Person) { + this.name = name; + this.age = age; + } else { + return new Person(name, age); + } +} + +高级定时器 +大部分人都知道使用setTimeout()和setInterval()可以方便的创建定时任务,看起来好像 Js 也是多线程的一样,实际上定时器仅仅是计划代码在未来的某个时间执行,但是执行时机是不能保证的。因为在页面的生命周期中,不同时间可能有其它代码控制着 JavaScript 进程。 +这里需要注意一下setInterval()函数,仅当没有该定时器的任何其他代码实例时,Js 引起才会将定时器代码添加到队列中。这样可以避免定时器代码可能在代码再次被添加到队列之前还没有完成执行,进而导致定时器代码连续运行好几次的问题。但是这也导致了另外的问题:(1)某些间隔会被跳过;(2)多个定时器的代码执行之间的间隔可能会比预期小。 +假设某个click事件处理程序使用setInterval()设置了一个 200ms 间隔的重复定时器。如果这个事件处理程序花了 300ms 多的时间完成,同时定时器代码也花了差不多了的时间,就会同时出现跳过间隔切连续运行定时器代码的情况。 +为了避免setInterval()的重复定时器的这两个缺点,我们可以使用如下模式的链式setTimeout(),代码一看就懂什么意思了。 +setTimeout(function() { + // 处理中 + setTimeout(arguements.callee, interval); +}, interval) + +消息队列与事件循环 +如下图所示,左边的栈存储的是同步任务,就是那些能立即执行、不耗时的任务,如变量和函数的初始化、事件的绑定等等那些不需要回调函数的操作都可归为这一类。 + +右边的堆用来存储声明的变量、对象。下面的队列就是消息队列,一旦某个异步任务有了响应就会被推入队列中。如用户的点击事件、浏览器收到服务的响应和setTimeout中待执行的事件,每个异步任务都和回调函数相关联。 +JS引擎线程用来执行栈中的同步任务,当所有同步任务执行完毕后,栈被清空,然后读取消息队列中的一个待处理任务,并把相关回调函数压入栈中,单线程开始执行新的同步任务。 +来看个例子:执行下面这段代码,执行后,在 5s 内点击两下,过一段时间(&gt; 5s)后,再点击两下,整个过程的输出结果是什么? +setTimeout(function(){ + for(var i = 0; i &lt; 100000000; i++){} + console.log('timer a'); +}, 0) +for(var j = 0; j &lt; 5; j++){ + console.log(j); +} +setTimeout(function(){ + console.log('timer b'); +}, 0) +function waitFiveSeconds(){ + var now = (new Date()).getTime(); + while(((new Date()).getTime() - now) &lt; 5000){} + console.log('finished waiting'); +} +document.addEventListener('click', function(){ + console.log('click'); +}) +console.log('click begin'); +waitFiveSeconds(); + +首先,先执行同步任务。其中waitFiveSeconds是耗时操作,持续执行长达 5s。然后,在 Js 引擎线程执行的时候,'timer a'对应的定时器产生的回调、'timer b'对应的定时器产生的回调和两次 click 对应的回调被先后放入消息队列。由于 Js 引擎线程空闲后,会先查看是否有事件可执行,接着再处理其他异步任务,最后,5s 后的两次 click 事件被放入消息队列,由于此时 Js 引擎线程空闲,便被立即执行了。因此会产生下面的输出顺序。 +0 +1 +2 +3 +4 +click begin +finished waiting +click +click +timer a +timer b +click +click + + +
+ + Read More ~ +
+
+
+ +
+

+ + 深入理解 JavaScript——变量提升与作用域 + +

+ +
+ + + + +
+ +
+ +参考内容: +lhs rhs是啥意思 +《Javasript 高级程序设计(第三版)》 +《你不知道的 JavaScript(上卷)》 + +几乎所有的编程语言都能够存储变量当中的值,并且可以在之后对该值进行访问或修改。很明显需要一套良好的规则来存储这些变量,并且之后可以方便的找到这些变量,这套规则我们称之为作用域。 +编译原理 +我们一般把 js 归为「动态」或「解释执行」语言,但是它也会经历编译阶段,不过它不像传统语言那样是提前编译的,它的编译发生在代码执行前的几微秒内。 +传统语言在执行之前会经历三个步骤:分词/词法分析、解析/语法分析、代码生成,关于这三个步骤的具体工作,可以查看编译原理相关的文献,我们可以把这三个步骤统称为编译。不过 js 引擎要复杂的多,它会在编译的时候对代码进行性能优化,尽管给 js 引擎优化的时间非常少,但是它用尽了各种办法来保证性能最佳。 +我们需要先了解三个名词。引擎:从头到尾负责整个 js 程序的编译及执行过程;编译器:负责词法分析及代码生成;作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。 +var a = 2;,我们以这段程序为例,它首先声明了变量a,然后将2赋值给变量a。前一个阶段在编译器处理,后一个阶段由 js 引擎处理。 +变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。 +变量提升 +用过 js 的人都知道 js 存在变量提升,那么它到底是如何提升的呢?我们看下面的一段代码 +console.log(a); +var a = 2; + +上述代码在a声明之前访问了变量a,按我们的逻辑它应该会抛出 ReferenceError 异常;或是变量提升直接输出 2。但是这两种答案都不对,输出的是undefined。 +回顾一下前文的关于编译的内容,引擎会在解释 js 代码之前对其进行编译,编译阶段的一个重要工作就是找到所有的声明,并用合适的作用域将它们关联起来,包括变量和函数在内的所有声明都会在任何代码被执行之前首先被处理。所以我们前面列出来的代码实际上会变成下面这个样子。 +var a; +console.log(a); +a = 2; + +这个过程就好像变量和函数声明会从它们的代码中出现的位置被移动到最上面一样,这个过程就是提升。但是需要注意的是,函数声明会首先被提升,然后才是变量提升。 +foo(); // 1 +var foo; + +function foo() { + console.info(1); +} + +foo = function() { + console.info(2); +} + +这段代码输出 1 而不是 2 ,它会被引擎理解为下面的形式。 +function foo() { + console.log(1); +} + +foo(); // 1 + +foo = function() { + console.log(2); +}; + +可以看到,虽然var foo出现在function foo()之前,但是它是重复的声明,因此会被忽略掉,因为函数函数声明会提升到普通变量前。所以在在同一个作用域中进行重复定义是一个很糟糕的做法,经常会导致各种奇怪的问题。 +LHS 和 RHS 查询 +LHS 和 RHS 是数学领域内的概念,意为等式左边和等式右边的意思,在我们现在的场景下就是赋值操作符的左侧和右侧。当变量出现在赋值操作符的左边时,就进行 LHS 查询;反之进行 RHS 查询。 +RHS 查询与简单的查找某个变量的值没什么区别,它的意思是取得某某的值。而 LHS 查询则是试图找到变量容器的本身,从而可以对其进行赋值。 +console.info(a);我们深入研究一下这句代码。这里对a的引用是 RHS 引用,因为这里a并没有赋予任何值,相应的需要查找并取得a的值,这样才能传递给console.info()。 +a = 2;对a的引用则是一个 LHS 引用,因为实际上我们并关心a当前的值是什么,只是想为= 2这个赋值操作找到一个目标。 +function foo(a) { + console.info(a); +} +foo(2); + +为了加深印象,我们再来分析一下上述代码中的 RHS 和 LHS 引用。最后一行foo()函数的调用需要对foo进行 RHS 引用。这里有一个很容易被忽略的细节,2 被当作参数传递给foo()函数时,2 会被分配给参数a,为了给参数a(隐式地)分配值,需要进行一次 LHS 查询,也就是说代码中隐含了a = 2的语句。 +前文已经说过了console.info(a);会对a进行一次 RHS 查询,需要注意的是console.info()本身也需要一个引用才能执行,因此会对console对象进行 RHS 查询,并检查得到的值中是否有一个log方法。 +为什么区分 LHS 和 RHS +我们考虑下面的一段代码,就可以为什么要区分 LHS 和 RHS 查询了,而且区分它们是分厂有必要的。 +function foo(a) { + console.info(a + b); + b = a; +} +foo(2); + +第一次对b进行 RHS 查询时是无法找到该变量的,这是一个未声明的变量,在任何相关的作用域中都无法找到它。如果 RHS 查询在所有嵌套作用域中都找不到该变量,引擎就会抛出 ReferenceError 异常。 +引擎在执行 LHS 查询时,如果在全局作用域中也无法找到目标变量,全局作用域就会创建一个具有该名称的变量,并将其返还给引擎。 + +需要注意的是,在严格模式下是禁止自动或隐式地创建全局变量的,因此在严格模式中 LHS 查询失败时,引擎同样会抛出 ReferenceError 异常。 + +接下来,如果 RHS 查询找到了一个变量,但是你尝试对这个值进行不合理的操作,比如对一个非函数类型的值进行函数调用,那么引擎就会抛出另一种叫做 TypeError 的异常。 +作用域链 +执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中,在 Web 浏览器中,全局执行环境被认为是window对象,因此所有的全局变量和函数都是作为window对象的属性和方法创建的。 +每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,而函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境,这个函数调用的压栈出栈是一样的。 +当代码在环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端始终都是当前执行的代码所在环境的变量对象,说的比较抽象,我们可以看下面的示例。 +var color = &quot;blue&quot;; + +function changeColor() { + var anotherColor = &quot;red&quot;; + + function swapColors() { + var tempColor = anotherColor; + anotherColor = color; + color = tempColor; + // 这里可以访问 color、anotherColor 和 tempColor + } + // 这里可以访问 color 和 anotherColor,但不能访问 tempColor + swapColors(); +} +// 这里只能访问 color +changeColor(); + +下面的图形象的展示了上述代码的作用域链,内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。函数参数也被当做变量来对待,因此其访问规则与执行环境中的其它变量相同。 +window + |-----color + |-----changeColor() + |----------anotherColor + |----------swapColors() + |----------tempColor + +作用域链还用于查询标识符,当某个环境中为了读取或写入而引入一个标识符时,必须通过搜索来确定该标识符实际代表什么。搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符,如果在局部环境中找到了该标识符,搜索过程就停止,变量就绪;如果在局部环境没有找到这个标识符,则继续沿作用域链向上搜索,如下所示: +var color = &quot;blue&quot;; + +function getColor() { + var color = &quot;red&quot;; + return color; +} + +console.info(getColor()); // &quot;red&quot; + +在getColor()中沿着作用域链在局部环境中已经找到了color,所以搜索就停止了,也就是说任何位于局部变量color的声明之后的代码,如果不使用window.color都无法访问全局color变量。 + +
+ + Read More ~ +
+
+
+ +
+

+ + JavaScript 性能优化——惰性载入函数 + +

+ +
+ + + + +
+ +
+ +参考资料: +《JavaScript 高级程序设计(第三版)》 +JavaScript专题之惰性函数 +深入理解javascript函数进阶之惰性函数 + +因为不同厂商的浏览器相互之间存在一些行为上的差异,很多 js 代码包含了大量的if语句,将执行引导到正确的分支代码中去,比如下面的例子。 +function createXHR() { + if (typeof XMLHttpRequest != 'undefined') { + return new XMLHttpRequest(); + } else if (typeof ActiveXObject != 'undefined') { + if (typeof arguments.callee.activeXString != 'string') { + var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']; + var i, len; + for (i = 0, len = versions.length; i &lt; len; i++) { + try { + new ActiveXObject(versions[i]); + arguments.callee.activeXString = versions[i]; + } catch (e) { + // skip + } + } + } + return new ActiveXObject(arguments.callee.activeXString); + } else { + throw new Error('No XHR object available.'); + } +} + +我们可以发现,在浏览器每次调用createXHR()的时候,它都要对浏览器所支持的能力仔细检查,但是很明显当第一次检查之后,我们就应该知道浏览器是否支持我们所需要的能力,因此除第一次之外的检查都是多余的。即使只有一个if语句也肯定要比没有if语句慢,所以if语句不必每次都执行,那么代码可以运行的更快一些,惰性载入就是用来解决这种问题的技巧。 +函数重写 +要理解惰性载入函数的原理,我们有必要先理解一下函数重写技术,由于一个函数可以返回另一个函数,因此可以在函数内部用新的函数来覆盖旧的函数。 +function sayHi() { + console.info('Hi'); + sayHi = function() { + console.info('Hello'); + } +} + +我们第一次调用sayHi()函数时,控制台会打印出Hi,全局变量sayHi被重新定义,被赋予了新的函数,从第二次开始之后的调用都会打印出Hello。惰性载入函数的本质就是函数重写,惰性载入的意思就是函数执行的分支只会发生一次。 +惰性载入 +我们来看一个例子(例子来源于冴羽所写的JavaScript专题之惰性函数)。现在需要写一个foo函数,这个函数返回首次调用时的Date对象,注意是首次。 +方案一 +var t; +function foo() { + if (t) return t; + t = new Date() + return t; +} +// 此方案存在两个问题,一是污染了全局变量 +// 二是每次调用都需要进行一次判断 + +方案二 +var foo = (function() { + var t; + return function() { + if (t) return t; + t = new Date(); + return t; + } +})(); +// 使用闭包来避免污染全局变量, +// 但是还是没有解决每次调用都需要进行一次判断的问题 + +方案三 +function foo() { + if (foo.t) return foo.t; + foo.t = new Date(); + return foo.t; +} +// 函数也是一种对象,利用这个特性也可以解决 +// 和方案二一样,还差一个问题没有解决 + +方案四 +var foo = function() { + var t = new Date(); + foo = function() { + return t; + }; + return foo(); +}; +// 利用惰性载入技巧,即重写函数 + +惰性载入函数有两种实现方式,第一种是在函数被调用时再处理函数。在第一次调用的过程中,该函数会被覆盖为另外一种按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行分支了。 +第二种实现方式是在声明函数时就指定适当的函数。这样第一次调用时就不会损失性能了,而是在代码首次加载时会损失一点性能,即是利用闭包写一个自执行的函数。 +改进 createXHR +有了上面的基础,我们就可以将createXHR()改进为下列形式,这样就不用每次调用都进行判断了。 +// 第一种实现方式 +function createXHR() { + if (typeof XMLHttpRequest != 'undefined') { + createXHR = function() { + return new XMLHttpRequest(); + } + } else if (typeof ActiveXObject != 'undefined') { + createXHR = function() { + if (typeof arguments.callee.activeXString != 'string') { + var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']; + var i, len; + for (i = 0, len = versions.length; i &lt; len; i++) { + try { + new ActiveXObject(versions[i]); + arguments.callee.activeXString = versions[i]; + } catch (e) { + // skip + } + } + } + return new ActiveXObject(arguments.callee.activeXString); + }; + } else { + createXHR = function() { + throw new Error('No XHR object available.'); + } + } +} + +// 第二种实现方式 +function createXHR() { + if (typeof XMLHttpRequest != 'undefined') { + return function() { + return new XMLHttpRequest(); + } + } else if (typeof ActiveXObject != 'undefined') { + return function() { + if (typeof arguments.callee.activeXString != 'string') { + var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']; + var i, len; + for (i = 0, len = versions.length; i &lt; len; i++) { + try { + new ActiveXObject(versions[i]); + arguments.callee.activeXString = versions[i]; + } catch (e) { + // skip + } + } + } + return new ActiveXObject(arguments.callee.activeXString); + }; + } else { + return function() { + throw new Error('No XHR object available.'); + } + } +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + 非设计师需要知道的四个设计原则 + +

+ +
+ + + + +
+ +
+ +作者:Anna 4erepawko Mészáros,UI/UX 设计师。 +关注作者: Medium、Twitter + +这篇文章是写给无力邀请专业设计师的所有内容创作者的,以及设计师异常忙碌的团队的非设计师们。如果您按照这些简单的步骤进行操作,我保证您的设计会变得更好。 +这些 Tips 来源于我对身边非设计朋友的多年观察,家人与同事在日常生活中也需要设计他们的东西。比如简历、作品集,Facebook 和 Instagram 上帖子要使用的图片,YouTube 视频的缩略图等。 +所有这些人都向我寻求帮助与建议,希望能让他们的东西看起来更好。我坚信「授人以鱼不如授人以渔」,所以我试图提供更有价值的建议,以便他们在未来也能解决类似的问题。 +随着时间的推移,我意识到我一直在给所有人提供相同的建议,虽然每次所使用的措辞不同,但我所有的建议都可以提炼为以下四个原则。 +这些 Tips 会帮您创造出美丽动人的设计吗?答案是不会!它们只会帮您创造出色、清晰且易于理解的设计。是每个人都可以轻松理解和互动吗?那当然,所以不多说废话,下面我就向您逐一展示。 +对比 +确保所有元素之间有足够的对比度。为什么?因为那些略有差异但是又不够不同东西,创造了一种恐怖谷。人类的眼睛会排斥它们,对它们感到厌恶、难以理解。我们不希望它们出现在我们的设计中,难道不是吗? + +恐怖谷理论,是一个关于人类对机器人和非人类物体的感觉的假设。如果一个非人类实体不够拟人,那么它身上的人类特征会很容易辨认;而当它足够拟人时,他身上的非人类特征则会变得很容易辨认。因此会在人类观察者眼中产生一种古怪的感觉,想想您看到病患者或者尸体时的感觉。 + +因此您设计的元素要么完全相同,要么具有显著差异。 +您可以从下面四个方面来突出对比: +1、颜色:浅色上使用暗色,反之亦然 +示例: 切勿在浅蓝色上使用浅灰色或浅粉红色等灰色组合,它们会造成阅读/互动上的极大困难。 + +2、大小:相邻元素要么大小完全相同,要么大小区别很大 +示例: 不要将 32pt 和 36pt 的文本放在一起;18pt 和 36pt 放在一起会显得更加协调。 + +3、粗细:与大小一样,相邻元素的粗细要么完全相同,要么有明显的区别 +示例: 不要将相同字体的粗体与黑体放在一起,因为它们看起来太相似了;将黑体与细体放在一起会显得很协调。 + +4、风格:不要将一个斜体类型放在另一个斜体类型旁边,或者在一个衬线字体旁边放置另一个衬线字体。应该组合不同的东西。 +示例: 不要将 Times New Roman 与 Georgia 放在一起,它们看起来太相似了,应该组合完全不同的风格。 + +一致性 +确保相似的元素以相似的方式出现。为什么呢?首先,通过确保确保事物一致性,您可以让用户将注意力集中在设计的重要方面,而不是被随时变化的元素分散注意力。 +其次,一致性也增加了用户对您的信任,使事物看起来实际上是设计的,而不是简单快速拼凑出来的。 +一旦你选择了具体的风格,就要毫不犹豫的坚持下去,这里所说的风格包括字体、颜色、阴影、栅格、对齐、装饰风格等等。 + +当您处理许多相邻的不同部分时(比如 YouTube 的视频缩略图或是中型文章的封面),您应该为所有部分选择一种整体风格,并坚持使用。 +奥卡姆剃刀 减少视觉噪音 +在您的设计中,使用的元素越少越好。为什么呢?因为人类的大脑很难在输入过载的情况下处理信息并作出决策。您应该使用尽可能少的装饰元素(字体、颜色、阴影、图标等等)。 +将奥卡姆剃刀应用于所有内容。如果只需要两个元素就能满足需求,那么就不要使用 3 个元素;如果 10 个元素实现所需的功能,那么就不要用 20 个元素。 + +如果您不喜欢古老的英国哲学家风格,更喜欢您在 Netflix(一家美国流媒体提供商)上看到的东西。请将怦然心动的人生整理魔法应用到您的设计中。 + +《怦然心动的人生整理魔法》是美国流媒体提供商Netflix于2019年1月1日首播的一档真人实境秀节目。节目由日本“整理咨询顾问” 近藤麻理惠主创。她在每集节目中拜访一个家庭,帮助他们整理自己的房间。 +近藤麻理惠认为整理房间时应当将物品分为五类:衣物、书籍、纸张文件、杂物和情感纪念品;在整理时拿起每件物品,如果能使自己“怦然心动”则留下,如果不能则要感谢物品的贡献然后与其告别。 + +间距 +元素的位置会发送关于其含义的元级别消息。为什么这很重要?因为了解如何放置元素以及在它们周围预留了多少空间有助于降低设计的复杂性,因此会使人更加愉悦,并且更容易交互。 +在您的设计中使用间距来传达下面 3 个方面的信息: +1、接近度 = 相关性 +与其它元素相比,彼此更接近的事物被认为它们有更强的相关性。这是最重要的,因为我觉得它常常容易被忽视。 +它可以以很多不同的方式应用,比如行与行之间应该有一定的间距,而不是一行中每个单词之间的间距那么小;同样不同段落之间的空间也比段落内的行空间要大。 + +元素之间的间距应该小于元素与组合边缘之间的间距。 + +标签和支撑信息应该位于其相关元素附近。 + +2、留白 +结合奥卡姆剃刀,给您的设计尽可能留白,去整理它们,使它们的意义更加明显。 +如果把太多元素放在有限的空间里,就像同时听三首不同的哥,很难理解别人在说什么。 + +3、重要性与顺序 +这是一个很普通的常识,但是我还是要在这里提到它。 +最重要的事情放在第一位,使它们占据最大的空间,用一系列的事物来传达秩序。 +结束语 +恭喜您!如果您按照这些 Tips 进行设计,那么按照行业标准,它可能看起来非常好。 +For everything else, there is always a designer. + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何保证快速加载网页?——详解浏览器缓存机制 + +

+ +
+ + + + +
+ +
+ +参考内容: +彻底理解浏览器的缓存机制 +彻底弄懂HTTP缓存机制及原理 + +前端开发人员有大部分时间都在调整页面样式,如果页面没有按照自己预期的样式显示,可能想到的第一个解决方案就是清一下浏览器缓存,HTTP 缓存机制作为 Web 性能优化的重要手段,也应该是 Web 开发人员必备的基础知识。我们常说的浏览器缓存机制也就是 HTTP 缓存机制,它是根据 HTTP 报文的缓存标识运行的,所以首先要对 HTTP 报文有一个简单的了解。 +HTTP 报文 +HTTP 报文是浏览器和服务器间进行通信时所发的响应数据,所以 HTTP 报文分为请求(Request)报文和响应(Response)报文两种,浏览器向服务器发送的是请求报文,而服务器向浏览器发送的是响应报文。HTTP 请求报文由请求行、请求头、请求体组成,响应报文则由状态行、响应头、响应正文组成,与缓存有关的规则信息则都包含在请求头和响应头中。 +缓存概述 +浏览器与服务器通过请求响应模式来通信,当浏览器第一次向服务器发送请求并拿到结果后,会根据响应报文中的缓存规则来决定是否缓存结果,其简单的流程如下图: + +浏览器每次发起请求都会先在浏览器缓存中查找该请求的结果和缓存标识,而且每次拿到响应数据后都会将该结果和缓存标识存入缓存中。HTTP 缓存的规则有多种,我们可以根据是否需要重新向服务器发起请求这一维度来分类,即有强制缓存和协商缓存两类,也有人把协商缓存叫对比缓存。 +强制缓存 +我们先自己想一下,使用缓存是不是会有下面几种情况出现。 + + +存在所需缓存并且未失效:直接走本地缓存即可;强制缓存生效; + + +存在所需缓存但已失效:本地缓存失效,携带着缓存标识发起 HTTP 请求;强制缓存失效,使用协商缓存; + + +不存在所需缓存:直接向服务器发起 HTTP 请求;强制缓存失效。 + + +控制强制缓存的字段分别是Expires和Cache-Control,并且Cache-Control的优先级高于Expires。 +Expires +Expires是 HTTP/1.0 控制网页缓存的字段,其值为服务器返回的该缓存到期时间,即下一次请求时,请求时间小于Expires值,就直接使用缓存数据。到了 HTTP/1.1,Expires已经被Cache-Control替代了。 +Expires被替代的原因是因为服务端和客户端的时间可能有误差(比如时区不同或者客户端与服务端有一方时间不准确),这就会导致缓存命中误差,强制缓存就变得毫无意义。 +Cache-Control +Cache-Control是 HTTP/1.1 中最重要的规则,主要取值为: + + + +取值 +规则 + + + + +public +所有内容都可以被缓存,包括客户端和代理服务器,纯前端可认为与private一样。 + + +private +所有内容只有客户端可以缓存,Cache-Control的默认值。 + + +no-cache +客户端可以缓存,但是是否缓存需要与服务器协商决定(协商缓存) + + +no-store +所有内容都不会被缓存,既不是用强制缓存,也不使用协商缓存,为了速度快,实际上缓存越多越好,所以这个慎用 + + +max-age=xxx +缓存内容将在 xxx 秒后失效 + + + +我们可以看看下面这个例子,可以从截图中看到Expires是一个绝对值,而Cache-Control是一个相对值,此处为max-age=3600,即 1 小时后失效。在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control相比于Expires是更好的选择,所以同时存在时只有Cache-Control生效。 + +协商缓存 +协商缓存,顾名思义就是需要双方通过协商来判断是否可以使用缓存。强制缓存失效后,浏览器带着缓存标识向服务器发起请求,由服务器根据缓存标识决定是否可以使用缓存,那自然而然就有协商缓存生效和协商缓存不生效两种情况了。 + +上图是协商缓存生效的流程,如果协商缓存不生效则返回的状态码为 200。协商缓存的标识也是在响应报文的响应头中返回给浏览器的,控制协商缓存的字段有Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高,所以同时存在时只有Etag / If-None-Match生效。 +Last-Modified / If-Modified-Since +你可以往上翻一翻,看一下那张响应报文截图,其中有一个Last-Modified字段,它的值是该资源文件在服务器最后被修改的时间。 +If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值。服务器收到该请求后,发现该请求头有If-Modified-Since字段,则会将If-Modified-Since与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为 200;否则则返回 304,代表资源无更新,可继续使用缓存文件。 + +Etag / If-None-Match +Etag是服务器响应请求时,返回当前资源文件的一个由服务器生成的唯一标识。 +If-None-Match则是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,如果一致则就返回 304,代表资源无更新,可以继续使用缓存文件;否则重新返回资源文件,状态码为200, + +disk cache 与 memory cache +我们可以通过浏览器调试工具查看强制缓存是否生效,如下图所示,状态码为灰色的请求就代表使用了强制缓存,请求对应的 size 显示了该缓存存放的位置,那么什么时候用 disk 什么时候用 memory 呢? + +猜都能猜出来,肯定是优先使用内存(memory)中的缓存,然后才用硬盘(disk)中的缓存。 +内存缓存具有快速读取的特点,它会将编译解析后的文件直接存入该进程的内存中,但是一旦进程关闭了,该进程的内存就会被清空,所以如果你将一个网页关闭后再打开,那么缓存都会走硬盘缓存,而如果你只是刷新网页,那有部分缓存走的就是内存缓存。 +浏览器一般会再 js 和图片等文件解析执行后直接存入内存缓存中,当刷新页面时,这部分文件只需要从内存缓存中读取即可,而 css 文件则会存入硬盘中,所以每次渲染页面都需要从硬盘中读取文件。 +总结 +到这里偷懒一下子了,找到人家画的一张图,看图就行了。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + Bootstrap-table 如何合并相同单元格 + +

+ +
+ + + + +
+ +
+ Bootstrap-table 官方提供了合并单元格方法 mergeCells,它根据四个参数可以合并任意个单元格,我们要做的只是告诉它怎么合并。 +要合并同一列相同的单元格,无非两种办法,一种是一边遍历一边合并,遍历完了再合并。这里采用第二种办法,这里不需要遍历所有数据,因为用户只能看到当前页的数据,所以只遍历当前页的数据更省时间。 +下面是我实现的获取合并信息算法,最终返回的是一个哈希表,比如下面的这个表格,如果要对「性别」这一列进行合并,很明显前面两个“男”需要合并成一个单元格,再去看下 Bootstrap-table 提供的 API,它需要的是从哪个单元格开始,合并多少个单元格,也就是它需要的是两个数值类型的参数。 + + + +姓名 +性别 +年龄 + + + + +张三 +男 +23 + + +李四 +男 +19 + + +王二 +女 +20 + + +麻子 +男 +21 + + + +所以我把哈希表设置为,键存的是索引,值存的是从这个索引开始后面连续有多少个和它一样的单元格,那么上述表格性别这一列所得到的合并信息哈希表就为: +{ + 0: 2, + 2: 1, + 3: 1 +} + +下面算法很简单,使用两个指针遍历指定的列,如果两个指针所指向的数据相同,那么就将键所对应的值进行加一操作,整个方法只会对该列数据遍历一边,所以时间复杂度为 O(n)。 +let getMergeMap = function (data, index: number) { + let preMergeMap = {}; + // 第 0 项为表头,索引从 2 开始为了防止数组越界 + for (let i = 2; i &lt; data.length; i++) { + let preText = $(data[i-1]).find('td')[index].innerText; + let curText = $(data[i]).find('td')[index].innerText; + let key = i - 2; + preMergeMap[key] = 1; + while ((preText == curText) &amp;&amp; (i &lt; data.length-1)) { + preMergeMap[key] = parseInt(preMergeMap[key]) + 1; + i++; + preText = $(data[i - 1]).find('td')[index].innerText; + curText = $(data[i]).find('td')[index].innerText; + } + // while循环跳出后,数组最后一项没有判断 + if (preText == curText) { + preMergeMap[key] = parseInt(preMergeMap[key]) + 1; + } + } + return preMergeMap; +} + +上述算法得到了单列数据的合并信息,下一步就是按照这个信息进行相同单元格的合并了,因此封装了下面的方法按照指定哈希表进行合并。 +let mergeCells = function (preMergeMap: Object, target, fieldName: string) { + for (let prop in preMergeMap) { + let count = preMergeMap[prop]; + target.bootstrapTable('mergeCells', { index: parseInt(prop), field: fieldName, rowspan: count }); + } +} + +到目前为止,我们实现的都只是对单列数据进行合并,要实现对多列数据进行合并,那么只需要对所有列都进行相同的操作即可。 +export let mergeCellsByFields = function (data: Object[], target, fields) { + for (let i = 0; i &lt; fields.length; i++) { + let field = fields[i]; + // 保证 field 与 i 是相对应的 + let preMergeMap = getMergeMap(data, i); + let table = target.bootstrapTable(); + mergeCells(preMergeMap, table, field); + } +} + +因为我在程序中做了一点处理,保证了fields中每个值得索引与对应表头的索引是一样的,因此不需要额外传入索引信息。简单来说就是我所实现的表格会根据fields的顺序,实现列之间的动态排序。你需要注意的是这一点很可能和你不一样。 +到现在已经能够合并所有的列了,查看 Bootstrap-table 的配置信息发现,它有个属性是 onPostBody 它会在 table body 加载完成是触发,所以把这个属性配置成我们的合并单元格方法即可。 +// groups 为要合并的哪些列 +onPostBody: function () { + mergeCellsByFields($('#table' + ' tr'), $('#table'), groups); +} + +再说一点不太相关的,我实现的是让用户可以自己选可以合并多少列,即用了一个可多选的下拉列表框供用户选择,根据用户选择的数量去合并,所以传入了一个groups参数。 +最后推荐一个排序插件 thenBy,你可以用它进行多字段排序,比如用在合并相同单元格的场景,在绘制表格前先对数据进行排序,那么最后合并的结果就是把所有相同的数据聚合到一起了,并且还将它们合并到一起了,起到了一个隐形的过滤查询功能。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 学习 Angulr 容易忽略的知识点 + +

+ +
+ + + + +
+ +
+ +参考内容: +《Angulr5 高级编程(第二版)》 + +函数声明式和表达式 +// 第一种:函数声明式 +myFunc(); +function myFunc(){ + ... +} + +// 第二种:函数表达式 +myFunc(); +let myFunc = function(){ + ... +} + +虽然上面两种函数声明方式在大部分情况下是一样的,第一种可执行,第二种却不可以执行,这是因为浏览器在解析 js 时找到函数声明,并在执行剩余语句之前设置好函数,此过程称为函数提升,但是函数表达式却不会受到提升,因此无法正常工作。 +js 不具备多态性 +js 重不能创建名称相同但参数不同的两个函数,它不具备这个多态性,比如你定义的函数中有两个形参,调用函数时只传一个参数,第二形参的值就是 undefined ,如果传的参数大于 3 个,那么会自动忽略多余的参数。可以使用下列方法来处理函数定义参数数量和用于调用函数实际参数数量之间不匹配的问题。 +// 使用默认参数 +let func = function(age, sex='男'){ + ... +} +func(23); + +// 使用可变长参数 +let func = function(age, sex, ...extraArgs){ + ... +} +func(23, '女', '张三', '深圳'); +// 最后一个参数是一个数组,任何额外的实参都会被赋给这个数组 + +let 和 war 的区别 +使用 let 和 var 声明变量的区别,使用 let 声明变量会把变量的作用范围限定在它所在的代码区域内。而使用 var 所创建的变量的作用域是它所在的函数。 +function func(){ + if(false){ + var age = 23; + } +} + +// 上面的代码会被解析成下面的形式,使用 let 则不会出现这样的结果 + +function func(){ + var age; + if(false){ + age = 23; + } +} + +相等 == 和恒等 === 以及 连接操作符 + +相等操作符尝试将操作数强制转换为相同的类型,再评估是否相等,实质上相等操作符==是测试二者的值是否相等,而与二者的类型无关;如果要测试值和类型是否都相等则应该用恒等操作符===。 +5 == '5' // 结果为 true +5 === '5' // 结果为 false + +在 js 中,连接操作符的优先级高于加法操作,也就是说5 + '5'的结果是55。 +不同的模块指定方式 +import { Name } from &quot;./modules/NameUtil&quot;;// 第一种 +import { Compont } from &quot;@angular/core&quot;;// 第二种 + +上面两种导入模块的方式有所不同,第一种是相对模块,第二种是非相对导入。第一种告诉的 TypeScript 编译器,该模块所在的位置是相对于包含 import 语句的文件而言;第二种非相对导入,编译器会用 node_modules 文件夹中的 npm 包来解析它。 +如果在导入模块时,出现需要导入两个不同模块但是名字却相同的情况,可以使用as关键字给导入的模块取一个别名。 +import { Name as otherName } from &quot;./modules/Name&quot;;//取别名 + +还有一种方法是将模块作为对象导入,如下 import 所示,导入 Name 模块的内容,并创建一个名为 otherName 的对象,然后就可以使用该对象的属性了。 +import * as otherName from &quot;./modules/NameUtil&quot;; +let name = new otherName.Name(&quot;Admin&quot;, &quot;China&quot;);// Name 是 NameUtil 中的类 + +多类型和类型断言 +在 ts 中允许指定多个类型,使用字符|进行分隔。看下面的的方法,其功能是把华氏温度转换为摄氏温度。 +// 使用多类型,该函数可以传入 number 和 string 类型的参数 +static convertFtoC(temp: number | string): string { + /* + 尝试使用 &lt;&gt; 声明一个类型断言,将一个对象转换为指定类型,也可以使用 as 关键字实现下列相同的效果 + let value: number = (temp as number).toPrecision ? temp as number : parseFloat(temp as string); + */ + let value: number = (&lt;number&gt;temp).toPrecision ? &lt;number&gt;temp : parseFloat(&lt;string&gt;temp); + return ((parseFloat(value.toPrecision(2)) - 32) / 1.8).toFixed(1); +} + +元组是固定长度的数组,数组的每一项都是指定的类型;可索引类型可以将键与值关联起来,创建类似于 map 的集合。 +// 元组 +let tuple: [string, string, string]; +tuple = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]; + +// 可索引类型 +let cities: {[index: string] : [string, string]} = {}; +cities[&quot;Beijing&quot;] = [&quot;raining&quot;, &quot;2摄氏度&quot;]; + +数据绑定 +[target]=&quot;expr&quot;// 方括号表示单向绑定,数据从表达式流向目标; + +(target)=&quot;expr&quot;// 圆括号表示单向绑定,数据从目标流向表达式,用于处理事件的绑定; +[(target)]=&quot;expr&quot;// 圆方括号组合表示双向绑定,数据在表达式与目标之间双向流动; +{{ expression }}// 字符串插入绑定。 + +[] 绑定有很多不同的形式,下面介绍不同表现形式的效果。 +&lt;!-- + 标准属性绑定(dom对象有的属性),将 input 的 value 属性绑定到一个表达式的结果 + 因为 model.getProduct(1) 可能返回 null ,所以使用模板空条件操作符 ? 浏览返回结果 + 如果返回不为空,那么将读取 name 属性,否则由 null 合并操作符 || 将结果设置为 None + 字符串插入绑定也可以使用这种表达式 + --&gt; +&lt;input [value]=&quot;model.getProduct(1)?.name || 'None'&quot;&gt; + +&lt;!-- + 元素属性绑定,有时候我们需要绑定的属性在 DOMAPI 上面没有 + 可以使用通过在属性名称前加上 attr 前缀的方式来定义目标 + --&gt; +&lt;td [attr.colspan]=&quot;model.getProducts().length&quot;&gt; + {{ model.getProduct(1)?.name || 'None' }} +&lt;/td&gt; + +&lt;!-- 还有其他的 ngClass,ngStyle 等绑定,理解大体上和上面差不多 --&gt; + +内置指令 +&lt;!-- + ngIf指令,如果表达式求值结果为 true ,那么 ngIf 将宿主元素机器内容包含在 html 文件中 + 指令前面的星号表示这是一条微模板指令 + 组要注意的是,ngIf 会向 html 中添加元素,也会从中删除元素,并非只是显示和隐藏 + 如果只是控制可见性,可以使用属性绑定挥着样式绑定 + --&gt; +&lt;div *ngIf=&quot;expr&quot;&gt;&lt;/div&gt; + +&lt;!-- + ngSwitch指令, + --&gt; +&lt;div [ngSwitch]=&quot;expr&quot;&gt; + &lt;span *ngSwitchCase=&quot;expr&quot;&gt;&lt;/span&gt; + &lt;span *ngSwitchDefault&gt;&lt;/span&gt; +&lt;/div&gt; + +&lt;!-- + ngFor指令,见名知意,为数组中的每个对象生成同一组元素 + ngFor 指令还支持其他的一系列可赋给变量的值,有如下局部模板变量 + + index:当前对象的位置 + odd:如果当前对象的位置为奇数,那么这个布尔值为 true + even:同上相反 + first:如果为第一条记录,那么为 true + last:同上相反 + --&gt; +&lt;div *ngFor=&quot;let item of expr; let i = index&quot;&gt; + {{ i }} +&lt;/div&gt; + +&lt;!-- + ngTemplateOutlet指令,用于重复模板中的内容块 + 其用法如下所示,需要给源元素指定一个 id 值 + + &lt;ng-template #titleTemplate&gt; + &lt;h1&gt;我是重复的元素哦&lt;/h1&gt; + &lt;/ng-template&gt; + &lt;ng-template [ngTemplateOutlet]=&quot;titleTemplate&quot;&gt;&lt;/ng-template&gt; + ...省略若万行 html 代码 + &lt;ng-template [ngTemplateOutlet]=&quot;titleTemplate&quot;&gt;&lt;/ng-template&gt; + --&gt; +&lt;ng-template [ngTemplateOutlet]=&quot;myTempl&quot;&gt;&lt;/ng-template&gt; + +&lt;!-- + 下面两个指令就是见名知意了,不解释 + --&gt; +&lt;div ngClass=&quot;expr&quot;&gt;&lt;/div&gt; +&lt;div ngStyle=&quot;expr&quot;&gt;&lt;/div&gt; + +事件绑定 +事件绑定使用 (target)=&quot;expr&quot;,是单向绑定,数据从目标流向表达式,用于响应宿主元素发送的事件。 +当浏览器触发一个时间时,它将提供一个对象来描述该事件,对于不同类型的事件有不同类型的事件对象,事件对象被赋给一个名为$event的模板变量,但是所有事件对象都有下面三个属性: +type:返回一个 string 值,用于标识已触发事件类型; +target:返回触发事件的对象,一般是 html元素对象。 +timeStamp:返回事件触发事件的 number 值,用 1970.1.1 毫秒数表示。 + +下面举几个例子,作为理解帮助使用。 +&lt;!-- 当数鼠标在上面移动时,就会触发 mouseover 事件 --&gt; +&lt;td *ngFor=&quot;let item of getProducts()&quot; (mouseover)=&quot;selectedProduct = item.name&quot;&gt;&lt;/td&gt; + +&lt;!-- 当用户编辑 input 元素的内容时就会触发 input 事件 --&gt; +&lt;input (input)=&quot;selectedProduct=$event.target.value&quot; /&gt; + +&lt;input (keyup)=&quot;selectedProduct=product.value&quot; /&gt; +&lt;!-- 使用事件过滤,上面的写法按下任何一个键都会触发事件,而下面的写法只有回车事件才会触发事件 --&gt; +&lt;input (keyup.enter=&quot;selectedProduct=product.value&quot;) /&gt; + +表单验证 +Angular 提供了一套可扩展的系统来验证表单元素的内容,总共可以向 input表元素中添加 4 个属性,每个属性定义一条验证规则,如下所示: +required:用于指定必须填写值; +minlength:用于指定最小字符数; +maxlength:用于指定最大字符数,(不能在表单元素直接使用,因为它与同名的 H5 属性冲突); +pattern:该属性用于指定用户填写的值必须匹配正则表达式 + +&lt;!-- + Angular 要求验证的元素必须定义 name 属性 + 由于 Angular 使用的验证属性和 H5 规范使用的验证属性相同, + 所以向表单元素中添加 novalidate 属性,告诉浏览器不要使用原生验证功能 + ngSubmit 绑定表单元素的 submit 事件 + --&gt; +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +Angular 提供了 3 对验证 CSS 类,这些类可以用于样式化表单元素,向用户提供验证反馈,具体说明如下所示。 +ng-untouched ng-touched:如果一个元素未被用户访问,就将其加入到 nguntouched 类中;一旦访问就加入到 ngtouched 类中。 +ng-prisstine ng-dirty:元素内容没有被改变被加入到 ng-prisstine 类中,否则将其加入到 ng-dirty 类中。 +ng-valid ng-invalid:如果满足验证规则定义的条件,就加入到 ng-valid 类中,否则加入到 ng-invalid 类中。 + +在实际使用过程中,直接定义对应的样式即可,如下所示: +&lt;style&gt; +input.ng-dirty.ng-invalid{ + border: 2px solid red; +} +input.ng-dirty.ng-valid{ + border: 2px solid green; +} +&lt;/style&gt; +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +上面的验证方式无法给用户提供更加具体的信息,用户不知道应该做什么,可以使用 ngModel 指令来访问宿主元素的验证状态,当存在验证错误的时候,使用该指令向用户提供指导性信息。 +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + #nameRef=&quot;ngModel&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;ul class=&quot;text-danger list-unstyled&quot; + *ngIf=&quot;name.dirty &amp;&amp; name.invalid&quot;&gt; + &lt;li *ngIf=&quot;name.errors?required&quot;&gt; + you must enter a product name + &lt;/li&gt; + &lt;li *ngIf=&quot;name.errors?.pattern&quot;&gt; + product name can only contain letters and spases + &lt;/li&gt; + &lt;li *ngIf=&quot;name.errors?minlength&quot;&gt; + &lt;!-- + Angular 表单验证错误描述属性 + required:如果属性已被应用于 input 元素,此属性返回 true + minlength.requiredLength:返回满足 minlength 属性所需的字符数 + minlength.actualLength:返回用户输入的字符数 + pattern.requiredPattern:返回使用 pattern 属性指定的正则表达式 + pattern.actualValue:返回元素的内容 + --&gt; + product name must be at least {{ name.errors.minlength.requiredLenth }} characters + &lt;/li&gt; + &lt;/ul&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +如果在用户尝试提交表单时就显示大量的错误信息,给人的体验感就会很差,所以可以让用户提交表单时再验证整个表单,示例代码如下所示。 +export class ProductionCompont { + // ...省略若万行代码 + formSubmited: boolean = false; + + submitForm(form: ngForm) { + this.formSubmited = true; + if(form.valid) { + this.addProduct(this.newProduct); + this.newProduct = new Product(); + form.reset(); + this.formSubmited = true; + } + } +} + +&lt;form novalidate #formRef=&quot;ngForm&quot; (ngSubmit)=&quot;submitForm(formRef)&quot;&gt; + &lt;div *ngIf=&quot;formsubmited &amp;&amp; formRef.invalid&quot;&gt; + there are problems with the form + &lt;/div&gt; + &lt;!-- 禁用提交按钮,验证成功提交按钮才可用 --&gt; + &lt;button [disabled]=&quot;formSubmited &amp;&amp; formRef.valid&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +fromSubmited 属性用于指示表单是否已经提交,并将用于在用户提交整个表单之前阻止表单验证。当用户提交表单时,调用 submitForm 方法,并将 ngForm 对象作为实参传入,ngForm 提供了 reset 方法,该方法可以重置表单的验证状态,使其返回到最初的未访问状态。 +更高级的还有使用基于模型的表单验证,可以自行查阅相关资料。 +使用 json-server 模拟 web 服务 +因为json-server会经常用到,建议使用全局安装命令npm install -g json-server。因为开发后端的同学太慢了,而我们如果要等他们把接口都提供给我们的时候再开发程序的话,那效率就太低了,所以使用 json-server 来模拟后端服务。只需要建好一个 json 文件,比如下面的格式: +{ + &quot;user&quot; : [ + { + &quot;name&quot; : &quot;张三&quot;, + &quot;number&quot; : &quot;1234&quot;, + }, + { + &quot;name&quot; : &quot;王二&quot;, + &quot;number&quot; : &quot;5678&quot;, + } + ], + &quot;praise&quot;: [ + {&quot;info&quot;:&quot;我是一只小老虎呀!&quot;}, + {&quot;info&quot;:&quot;我才是大老虎&quot;} + ] +} + +启动服务使用命令json-server [你的 json 文件路径],然后就可以根据提示访问了,你甚至可以使用http://localhost:3000/user?number=5678去过滤数据。这样就能模拟 web 服务,而不必等后端同学的进度了。 +解决跨域请求问题 +Angular 跨域请求问题可以通过 Angular 自身的代理转发功能解决,在项目文件夹下新建一个 proxy.conf.json 并在其中添加如下内容。 +// 可以通过下列配置解决 +&quot;/api&quot;: { + &quot;target&quot;: &quot;http://10.9.176.120:8888&quot;, +} + +在启动时使用npm start,或者使用ng serve --proxy-config proxy.conf.json,Anular 中的/api请求就会被转发到 http://10.9.176.120:8888/api,从而解决跨域请求问题。 +使用第三方 js 插件 +共有三种方式引入第三方插件,第一种很简单,直接在 html 中引入插件就可以了;第二种在angular.json中进行配置;第三种在 ts 文件中使用 import 导入库即可。 +// 第一种(需要重启服务) +&quot;scripts&quot;: [&quot;src/assets/jquery-3.2.1.js&quot;,&quot;src/assets/jquery.nicescroll.js&quot;,&quot;src/assets/ion.rangeSlider.js&quot;] + +// 第二种 +&lt;script type=&quot;text/javascript&quot; src=&quot;assets/jquery-3.2.1.js&quot;&gt;&lt;/script&gt; +&lt;script type=&quot;text/javascript&quot; src=&quot;assets/jquery.nicescroll.js&quot;&gt;&lt;/script&gt; + +// 第三种 +import &quot;assets/jquery-3.2.1.js&quot;; +import &quot;assets/jquery.nicescroll.js&quot;; +import &quot;assets/ion.rangeSlider.js&quot;; + +深拷贝与浅拷贝 +深拷贝与浅拷贝是围绕引用类型变量说的,其本质区别是不可变性,基本类型是不可变得,而引用类型是可变的。 +直接使用赋值操作符,就是浅拷贝,如果对拷贝源进行操作,会直接影响在拷贝目标上,因为这个赋值行为本质是内存地址的赋值,为了获得与拷贝源完全相同但又不会影响彼此的对象就要使用深拷贝。 +let objA = { + x: 1, + y: -1 +} +let objB = objA; +objA.x++; +console.log(&quot;objA.x:&quot;+objA.x, &quot;objB.x:&quot;+objB.x); +//打印结果如下: +objA.x : 2 +objB.x : 2 + +Typescript 提供了一种方法来实现引用类型的深拷贝,即Object.assign(target, ...source),此方法接受多个参数,第一个参数为拷贝目标,剩余参数为拷贝源,同名属性会进行覆盖。 +let objA = { + x: 1, + y: -1, + c: { + d: 1, + } +} +let objB = {}; +Object.assign(objB, objA); +objA.x++; +console.log(&quot;objA.x:&quot;+objA[&quot;x&quot;], &quot;objB.x:&quot;+objB[&quot;x&quot;]); +//打印结果如下: +objA.x : 2 +objB.x : 1 + +需要注意的是,Typescript 提供的深拷贝方法不能实现嵌套对象的深拷贝,会出现下面的情况。 +let objA = { + x: 1, + y: -1, + c: { + d: 1, + } +} +let objB = {}; +Object.assign(objB, objA); +objA.c.d++; +console.log(&quot;objA.c.d:&quot;+objA[&quot;c&quot;].d, &quot;objB.c.d:&quot;+objB[&quot;c&quot;].d); +//打印结果如下: +objA.c.d : 2 +objB.c.d : 2 + +要实现嵌套对象的深拷贝,可以使用 JSON 对象提供的方法,JSON 对象提供了两个方法,分别为:stringify()和parse(),前者将对象 JSON 化,后者将 JSON 对象化,使用这种方式可以实现嵌套深拷贝,但是也有缺点:破坏原型链,不能拷贝属性值为 function 的属性。 +let objA = { + a: 1, + b: { + c: 1 + } +} +let objB = JSON.parse(JSON.stringify(objA)); +objA.b.c++; +console.log(&quot;objA.b.c:&quot;+objA.b.c, &quot;objB.b.c:&quot;+objB.b.c); + +//打印结果如下: +objA.b.c:2 +objB.b.c:1 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 跨域请求是什么?如何解决? + +

+ +
+ + + + +
+ +
+ +参考内容: +JavaScript: Use a Web Proxy for Cross-Domain XMLHttpRequest Calls +别慌,不就是跨域么! +跨域资源共享 CORS 详解 +AJAX请求和跨域请求详解(原生JS、Jquery) +JavaScript跨域总结与解决办法 + + +刚毕业入职,大部分时间还在培训,中间有一段时间的空闲时间,就学习了下 Angular,在学校都是编写的单体应用,所有代码都放在同一个工程下面,到公司使用的是前后端分离了,虽然后端程序也是我自己写的,但是有一些数据是从公司现有接口去拿的,然后就遇到让我纠结了两小时的跨域请求问题,在这里做一个简单的总结输出。 +什么是跨域请求 +跨域请求问题是浏览器的同源策略造成的,该策略不允许执行其它网站的脚本,是浏览器施加的安全限制。什么是同源?最初是指网页 A 设置的 Cookie 不能被网页 B 打开,包括三个相同:协议、域名、端口。这个同源是从 URL 判断的,不是从 IP 判断的,如果同一个服务器对应连个域名,这两个域名是不同源的。 +http://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 非跨域 + +http://www.nealyang.cn/index.html 调用 http://www.neal.cn/server.php 跨域,主域不同 + +http://abc.nealyang.cn/index.html 调用 http://def.neal.cn/server.php 跨域,子域名不同 + +http://www.nealyang.cn:8080/index.html 调用 http://www.nealyang.cn/server.php 跨域,端口不同 + +https://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 跨域,协议不同 + +localhost 调用 127.0.0.1 跨域 + +同源政策的目的是为了保护用户信息的安全,防止恶意网站窃取数据,随着互联网的发展,同源政策更加严格了,下面三种行为都会受到限制。 +(1) Cookie、LocalStorage 和 IndexDB 无法读取。 +(2) DOM 无法获得。 +(3) AJAX 请求不能发送。 + +所有的现代浏览器都对网络连接进行了安全限制,包括 XMLHttpRequest,如果你的 web 应用程序和其使用的数据在同一个服务器,你不会遇到跨域请求问题。但是当你的 web 应用程序和 web 服务数据不在同一个服务器时,就会被浏览器限制连接了。 +常用解决方案 +    对于跨域请求有很多的解决方案,最常用的解决方案是在你的 web 服务器上面设置代理。在设置代理之前就通过,应用程序直接去请求另一个服务器下的数据;设置代理之后,应用程序从自己的 web 服务器中请求数据,再由代理去请求数据,这样 web 服务器拿到数据之后返回给应用程序即可。从浏览器角度看,就是从同一个服务器拿的数据,并没有进行跨域请求。 + +通俗易懂的说,你家的宠物狗不会吃别家的食物,因为它担心别人的食物会把自己给药死,所以你的狗狗只管找你要食物,你是它的主人,它绝对相信你,而你可以鉴别别人给的食物是不是安全的。类比,小狗就是浏览器,你就是代理。 +Angular 中的解决办法 +上面所说的解决方案在开发过程中不方便操作,每新发一个接口都到服务器中去配置一下,不仅麻烦而且效率低下。首先说一下在 Angular 中一个人比较常用的解决方法,默认你在使用angular-cli构建你的项目,我们可以创建一个代理配置文件proxy.conf.json(假设你的后端服务的访问地址为10.121.163.10:8080),代理配置文件如下: +{ + &quot;/api&quot;: { + &quot;target&quot;: &quot;http://10.121.163.10:8080&quot;, + &quot;secure&quot;: false + } +} + +然后修改package.json文件中的启动命令为&quot;start&quot;: &quot;ng serve --proxy-config proxy.conf.json&quot;,启动项目时使用npm start即可解决跨域请求问题。 +上述解决方案仅在开发时使用,你当然可以使用 tomcat、nginx 配置代理,但是这很麻烦,需要打包代码部署,为了保证效率,我们想写完了立刻测试,同时也不想麻烦做后端的同学,在项目发布时,应该把代理配置到服务器中去;修改启动命令也不是必须的,你也可以选择每次使用 ng serve --proxy-config proxy.conf.json命令启动项目;示例代理配置文件内容可以有更多的属性,可以通过网络查阅相关资料。 +后端解决办法 +我的后端是是用 tornado 实现的,然后我又写了一个单独的页面用于在大屏幕上展示相关数据,没有用 Angular 了,要通过 AJAX请求数据,又怎么解决跨域请求问题呢?这时就需要设置请求头了,让后端允许跨域请求。 +这时需要了解一下简单请求和非简单请求了,简单请求就是只发送一次请求的请求;非简单请求会发送数据之前先发一次请求做预检,通过预检后才能再发送一次请求用于数据传输。 +更清晰区别,满足下列两大条件的属于简单请求,而非简单请求就是请求方法为PUT或DELETE,或者 Content-Type字段是application/json的请求。 + +1.请求方法为 GET、POST、HEAD之一 +2.HTTP头信息不超出字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type,并且 Content-Type 的值仅限于 application/x-www-form-urlencoded、multipart/form-data、text/plain。 + +对于简单请求,只需要设置一下响应头就可以了。 +class TestHandler(tornado.web.RequestHandler): + def get(self): + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + # 可以把 * 写成具体的域名 + self.write('cors get success') + +对于复杂请求,需要设置预检方法,如下所示: +class CORSHandler(tornado.web.RequestHandler): + # 复杂请求方法put + def put(self): + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + self.write('put success') + # 预检方法设置 + def options(self, *args, **kwargs): + #设置预检方法接收源 + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + #设置预复杂方法自定义请求头h1和h2 + self.set_header('Access-Control-Allow-Headers', &quot;h1,h2&quot;) + #设置允许哪些复杂请求方法 + self.set_header('Access-Control-Allow-Methods', &quot;PUT,DELETE&quot;) + #设置预检缓存时间秒,缓存时间内发送请求无需再预检 + self.set_header('Access-Control-Max-Age', 10) + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/7GKaPZAOw/index.html b/7GKaPZAOw/index.html new file mode 100644 index 00000000..ddcbd03f --- /dev/null +++ b/7GKaPZAOw/index.html @@ -0,0 +1,457 @@ + + + + + + + + 买课程囤在那里不看,你怎么可能进步 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 买课程囤在那里不看,你怎么可能进步 +

+ + +
+ +
+
+

文章于 2018-04-15 在微信公众号「刘小绪同学」发布,原文链接:你如果只是一直囤干货,那永远不可能进步,今日重读做了部分改动

+
+

这两天,没事整理了自己的微信收藏和 QQ 收藏文件夹下面的文章。发现了一个在大多数人身上都存在的问题,那就是囤干货

+

我使用微信的时间比较晚,从14年才开始,但是仅仅不到4年的时间,在微信收藏里面就有几百篇文章,有一些传授技能的文章,比如 word、ps 等简单教程;其他一些属于心灵鸡汤类文章(现在反倒不觉得这类文章值得看),还有一些搞笑的文章,当然里面也夹杂着些许的优秀视频。文章数量数 14 年和 15 年最多。

+

现在回头看,我当时就陷入了囤文章的陷阱了,或者说自己是在“假装阅读”。仔细一想,其实生活中大多数人都有类似的举动。早成起床、饭后的一段时间、晚上睡觉前,这些时间大多数人都习惯性的去翻翻公众号、朋友圈,这里面不乏有好文章、干货文章。然而没多少人会静下心来把文章读完,而是在大概读到一半的时候,选择收藏这篇文章,然后在心里告诉自己,明天要好好读一下这篇文章。到了明天,其实又是重复了今天的这样一个过程。

+

记得大概一年前也无意中看到大学认识的一位师弟发的说说,内容如下:

+
+

上面的图是我特地翻出来的,一年后这个师弟又发了一条说说,存满了两个云盘,应该是有 5000 G 左右的资源。

+

我认为这都是在假装学习,而且假装学习的人不计其数。为什么上学的时候,那些每天都静静的待在教室学习的同学,成绩反倒不是很好呢?而且大多数这样的学生在班级都是排在中等,而成绩好的学生却不是学习投入最多的人。背后的原因显而易见。

+

我囤课最严重的时间段是也是 14 年和 15 年,网上有不少干货资源,什么 Linux、各种项目实战、计算机网络等等培训视频不计其数。那时候干的第一件事就是,上百度云把这些资源下下来,而且一个资源往往要下一周甚至更久;然后告诉自己,下周开始每天看一段视频,但是最终的结果是过去了 N 个下周,依然没有去处理这些资源。

+

现在博客、公众号也有一些不知道是为了获取更多人的关注,还是仅仅是做公益,文章末尾会标注:关注公众号,回复“XXXXX”,即可获得多少多少G的资源,这个「多少」一般是在 500 以上。我个人现在是对这类文章没有什么兴趣的,因为几百 G 甚至上千 G 的东西,我是不可能看完的,我清楚自己的能力,我也不否认可能有人有毅力能看完,那肯定是凤毛菱角了,我一个普通人不与凤毛菱角对比。

+

我于 19 年开始使用豆瓣,用上豆瓣之后就冒出来另外一个毛病,总想快速的把一本书看完而不去管有没有真正理解书里面的知识,总花很多时间看更多的电影,仅仅是为了在豆瓣上标记「看过」、「读过」那一刻的快感。

+

走出象牙塔之后事情变得繁杂琐碎,什么事都想做到最好还什么事都想去做,问题是那些边边角角的事情还经常扰乱自己的视线,结果没有一件做的像样的事情。

+

当然,我现在的认识相比几年前,得到了一些提升,至少我现在不糊干囤课之类的事了,仅仅是把干货囤在那里,其时你还是昨天的自己,并没有进步,重要的不是自己买了多少优质的课程,而是去动手动脑学习,最好的学习就是实践。现在我都会把自己收藏的好文章在一周之内处理掉。

+

所以要想真正取得进步,首先就需要告别囤课的习惯,不能假装学习,如果学习是为了给比别人看,那还不如不学。文章仅是我个人的一点感悟,没有文采逻辑和言,希望与你共勉。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/7QbLXmeen/index.html b/7QbLXmeen/index.html new file mode 100644 index 00000000..23641d59 --- /dev/null +++ b/7QbLXmeen/index.html @@ -0,0 +1,429 @@ + + + + + + + + Vue | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + Vue +
+ + +
+

+ + Vue 入门避坑——Vue + TypeScript 项目起手式 + +

+ +
+ + + + +
+ +
+ 在此前我使用的前端框架是 Angular,使用过 TypeScript 后你就会讨厌 JS 了,我学习 Vue 时的最新版本是 2.5,相信大部分同学都不会认为 Vue 那样又细又长的代码很美观吧,简单看了一些网络博客后,我毅然决然引入了 TypeScript 进行开发,本文仅整理记录我自己遇到的一些坑。 +使用 Cli +脚手架是一个比较方便的工具,这里需要注意的是@vue/cli和vue-cli是不一样的,推荐使用npm i -g @vue/cli安装。 +安装完成后,可以直接使用vue create your-app创建项目,你可以选择使用默认配置亦或是自己手动选择配置,按提示一步一步向下走即可,它会根据你的选择自己创建比如tsconfig.json等等配置文件。这里推荐使用less开发样式,sass老是在安装的过程中出问题。 +当然你也可以使vue ui命令启动一个本地服务,它是一个 Vue 项目管理器,提供了一个可视化的页面供你管理自己的项目,它的样子如下图所示,还是比较清新的。 + +使用 vue-property-decorator +Vue 官方维护了 vue-class-component 装饰器,vue-property-decorator 则是在vue-class-component基础上增强了更多结合Vue特性的装饰器,它可以让 Vue 组件语法在结合了 TypeScript 语法后变得更加扁平化。 +截止本文时间,vue-property-decorator共提供了 11 个装饰器和 1 个Mixins方法,下面用@Prop举个例子,是不是看起来引起极度舒适。 +import { Vue, Component, Prop } from 'vue-property-decorator' + +@Component +export default class YourComponent extends Vue { + @Prop(Number) readonly propA: number | undefined + @Prop({ default: 'default value' }) readonly propB!: string + @Prop([String, Boolean]) readonly propC: string | boolean | undefined +} + + +// 上面的内容将会被解析成如下格式 + +export default { + props: { + propA: { + type: Number + }, + propB: { + default: 'default value' + }, + propC: { + type: [String, Boolean] + } + } +} + +使用 Vuex +关于怎么使用Vuex此处就不再做过多说明了,需要注意的一点是,如果你需要访问$store属性的话,那么你必须得继承Vue类,坑的地方是在某些情况下即使你没有继承Vue,它也能通过编译,只有在程序运行起来的时候才报错。 +class ExampleApi extends Vue { + + public async getExampleData() { + if (!this.$store.state.exampleData) { + const res = await http.get('url/exampleData'); + if (res.result) { + this.$store.commit('setExampleData', res.data); + return res.data; + } else { + promptUtil.showMessage('get exampleData failed', 'warning'); + } + } else { + return this.$store.state.exampleData; + } + } +} + +使用自己的配置(含代理) +vue.config.js是一个可选的配置文件,如果项目的根目录中存在这个文件,那么它会被@vue/cli-service自动加载,它的配置项说明可以查看配置参考。 +我们再开发过程中都会使用代理来转发请求,代理的配置也是在这个文件中,它的官方说明在devserver-proxy中,下面是一个简单的vue.config.js文件例子。 +module.exports = { + filenameHashing: true, + outputDir: 'dist', + assetsDir: 'asserts', + indexPath: 'index.html', + productionSourceMap: false, + transpileDependencies: [ + 'vue-echarts', + 'resize-detector' + ], + devServer: { + hotOnly: true, + https: false, + proxy: { + &quot;/statistics&quot;: { + target: &quot;http://10.7.213.186:3889&quot;, + secure: false, + pathRewrite: { + &quot;^/statistics&quot;: &quot;&quot;, + }, + changeOrigin: true + }, + &quot;/mail&quot;: { + target: &quot;http://10.7.213.186:8888&quot;, + secure: false, + changeOrigin: true + } + } + } +} + +让 Vue 识别全局方法和变量 +我们在项目中都会使用一些第三方 UI 组件,比如我自己就使用了 Element,但是在使用它的$message、$notify等方法时就直接报错了,究其原因就是$message等属性并没有在 Vue 实例中声明。 +官方对此给出了很明确的解决方案,使用的是 TypeScript 的 模块补充特性,可以查看增强类型以配合插件使用。既然知道是因为没有声明导致的错误,那我们就给它声明一下好了,在src/shims-vue.d.ts文件中添加如下代码即可,如果没有该文件请自行创建。 + +看到网上也有一部分人说的是src/vue-shim.d.ts,反正不管是怎么命名这个文件的,它们的作用是一样的。 + +declare module 'vue/types/vue' { + interface Vue { + $message: any, + $confirm: any, + $prompt: any, + $notify: any + } +} + +这里顺道提一下,src/shims-vue.d.ts文件中的如下代码是为了让你的 IDE 明白以.vue结尾的文件是什么玩意儿。 +declare module '*.vue' { + import Vue from 'vue'; + export default Vue; +} + + +路由懒加载 +Vue Router 官方有关于路由懒加载的说明,但不知道为什么官方给的这个说明在我的项目里面都没有生效,但使用require.ensure()按需加载组件可以生效。 +// base-view 是模块名,写了相同的模块名则代码会被组织到同一个文件中 +const Home = (r: any) =&gt; require.ensure([], () =&gt; r(require('@/views/home.vue')), layzImportError, 'base-view'); + +// 路由加载错误时的提示函数 +function layzImportError() { + alert('路由懒加载错误'); +} + +上面的方式会在编译的时候把文件自动分成多个小文件,编译后的文件会以你自己命名的模块名来命名,如果代码之间有相互依赖,依赖部分代码编译后的文件会以两个模块名相连后进行命名。 +但是需要注意的是,这样拆分小文件之后引入了另外一个新的问题,因为客户端会缓存这些编译后的 js 文件,如果功能 A 同时依赖了a.js和b.js两个文件,但用户在使用其它功能时已经把a.js缓存到本地了,使用功能 A 时需要请求b.js文件,这时程序就很容易报错,因为此时在客户端这两个文件不是同一个版本,所以可能导致a.js调用b.js中的方法已经被删了,进而导致客户端页面异常。 +关于引入第三方包 +项目在引入第三方包的时候经常会报出各种奇奇怪怪的错误,这里仅提供我目前找到的一些解决办法。 +/* + 引入 jquery 等库可以尝试下面这种方式 + 只需要把相应的 js 文件放到指定文件夹即可 +**/ +const $ = require('@/common/js/jquery.min.js'); +const md5 = require('@/common/js/md5.js'); + +引入一些第三方样式文件、UI 组件等,如果引入不成功可以尝试建一个 js 文件,将导入语句都写在 js 文件中,然后再在main.ts文件中导入这个 js 文件,这个方法能解决大部分的问题。例如我先建了一个lib.js,然后在main.ts中引入lib.js就没有报错。 +// src/plugins/lib.js +import Vue from 'vue'; + +// 树形组件 +import 'vue-tree-halower/dist/halower-tree.min.css'; +import {VTree} from 'vue-tree-halower'; +// 饿了么组件 +import Element from 'element-ui'; +import 'element-ui/lib/theme-chalk/index.css'; +// font-awesome 图标 +import '../../node_modules/font-awesome/css/font-awesome.css'; +import VueCookies from 'vue-cookies'; +import VueJWT from 'vuejs-jwt'; + +Vue.use(VueJWT); +Vue.use(VueCookies); +Vue.use(VTree); +Vue.use(Element); + + +// src/main.ts +import App from '@/app.vue'; +import Vue from 'vue'; +import router from './router'; +import store from './store'; +import './registerServiceWorker'; +import './plugins/lib'; + +Vue.config.productionTip = false; + +new Vue({ + router, + store, + render: (h) =&gt; h(App), +}).$mount('#app'); + +因为第三方包写的各有特点,在引入不成功的时候基本也只能是见招拆招,当然如果你的功底比较深厚,你也可以自己写一个index.d.ts文件,实在不行的话,那个特殊的组件不使用 TypeScript 来写也能解决,我目前还没有找一个可以完全解决第三方包引入错误的方法,如果您已经有相关的方法了,希望能与你一起探讨交流。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/7pUUwIeSw/index.html b/7pUUwIeSw/index.html new file mode 100644 index 00000000..4c4a947b --- /dev/null +++ b/7pUUwIeSw/index.html @@ -0,0 +1,450 @@ + + + + + + + + Linux 创建实时进程 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+ + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/8sxCrqU9f/index.html b/8sxCrqU9f/index.html new file mode 100644 index 00000000..07dd4018 --- /dev/null +++ b/8sxCrqU9f/index.html @@ -0,0 +1,756 @@ + + + + + + + + 二叉树的前序、中序、后序、层序遍历 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 二叉树的前序、中序、后序、层序遍历 +

+ + +
+ +
+
+

参考内容:

+

五分钟让你彻底理解二叉树的非递归遍历

+

Python实现二叉树的非递归遍历

+

二叉树遍历——深度优先(前中后序)+广度优先(层序遍历)

+
+

构造二叉树

+

定义二叉树结构如下

+
struct node
+{
+    int data;
+    node *left;
+    node *right;
+};
+
+

构造如下形态二叉树

+
+
node *init_tree()
+{
+    node *node1 = (node *)malloc(sizeof(node));
+    node *node2 = (node *)malloc(sizeof(node));
+    node *node3 = (node *)malloc(sizeof(node));
+    node *node4 = (node *)malloc(sizeof(node));
+    node *node5 = (node *)malloc(sizeof(node));
+    node *node6 = (node *)malloc(sizeof(node));
+    node *node7 = (node *)malloc(sizeof(node));
+    node *node8 = (node *)malloc(sizeof(node));
+    
+    node1->data = 1;
+    node2->data = 2;
+    node3->data = 3;
+    node4->data = 4;
+    node5->data = 5;
+    node6->data = 6;
+    node7->data = 7;
+    node8->data = 8;
+
+    node1->left = node2;
+    node1->right = node3;
+    
+    node2->left = node4;
+    node2->right = node5;
+    
+    node3->right = node6;
+    
+    node5->left = node7;
+    node5->right= node8;
+
+    return node1;
+}
+
+

前序遍历(递归)

+

前序遍历顺序为根左右。要遍历整个二叉树我们就需要遍历二叉树的每一个子树,对于任何一个子树它的遍历方式均为根左右顺序遍历。即所有子问题均与父问题除规模大小不同外,其余均相同。所以可以采用递归方式实现前序遍历。

+
// 前序遍历 根左右
+void pre_order_traversal(node *root)
+{
+    if(root) {
+        cout<<root->data<<" ";
+        pre_order_traversal(root->left);
+        pre_order_traversal(root->right);
+    }
+}
+
+

遍历结果为:1 2 4 5 7 8 3 6

+

中序遍历(递归)

+

中序遍历顺序为左根右。其与前序遍历仅顺序不同,其余均相同。

+
// 中序遍历 左根右
+void in_order_traversal(node *root)
+{
+    if(root) {
+        in_order_traversal(root->left);
+        cout<<root->data<<" ";
+        in_order_traversal(root->right);
+    }
+}
+
+

遍历结果为:4 2 7 5 8 1 3 6

+

后序遍历(递归)

+

后序遍历顺序为左右根。其与前序、中序遍历仅顺序不同,其余均相同。

+
// 后序遍历 左右根
+void post_order_traversal(node *root)
+{
+    if(root) {
+        post_order_traversal(root->left);
+        post_order_traversal(root->right);
+        cout<<root->data<<" ";
+    }
+}
+
+

遍历结果为:4 7 8 5 2 6 3 1

+

前序遍历方法一(非递归)

+

因为递归实际上是由系统帮我们进行压栈,所以理论上所有递归算法都可以改为循环+栈实现,那么我们先照着上述前序遍历的样子修改为循环+栈的形态。需要注意的是由于栈先进后出的特性,为了保证左孩子在右孩子前被访问,所以应该先右孩子入栈,再左孩子入栈。

+
// 前序遍历 根左右
+void pre_order_traversal(node *root)
+{
+    stack<node *> s;
+    s.push(root);
+
+    while(!s.empty()) {
+
+        node *cur = s.top();
+        s.pop();
+
+        if(cur) {
+            cout<<cur->data<<" ";
+            s.push(cur->right);
+            s.push(cur->left);
+        }
+    }
+}
+
+

遍历结果为:1 2 4 5 7 8 3 6

+

前序遍历方法二(非递归)

+

现在我们换一种思路来实现前序非递归遍历,仔细观察前序遍历的递归调用过程。

+
    +
  1. 先把从根结点开始的所有左子树放入栈中;
  2. +
  3. 弹出栈顶元素
  4. +
  5. 如果栈顶元素有右子树,那么右子树入栈
  6. +
  7. 重复上述过程直到栈为空
  8. +
+

因此我们可以写出遍历代码

+
// 前序遍历 根左右
+void pre_order_traversal(node *root)
+{
+    stack<node *> s;
+    node *cur = root;
+
+    while(cur || !s.empty()) {
+        // 将左子树全部入栈
+        while(cur) {
+            cout<<cur->data<<" ";
+            s.push(cur);
+            cur = cur->left;
+        }
+
+        if(!s.empty()) {
+            cur = s.top();
+            s.pop();
+            cur = cur->right;
+        }
+    }
+}
+
+

遍历结果为:1 2 4 5 7 8 3 6

+

中序遍历(非递归)

+

有了前面的基础,我们再来考虑中序遍历,会发现中序遍历与前序遍历只是打印结点的位置不一样。前序遍历是在结点入栈时打印,中序遍历只需要替换为在结点出栈时打印即可。

+
// 中序遍历 左根右
+void in_order_traversal(node *root)
+{
+    stack<node *> s;
+    node *cur = root;
+
+    while(cur || !s.empty()) {
+        // 将左子树全部入栈
+        while(cur) {
+            s.push(cur);
+            cur = cur->left;
+        }
+
+        if(!s.empty()) {
+            cur = s.top();
+            cout<<cur->data<<" ";
+            s.pop();
+            cur = cur->right;
+        }
+    }
+}
+
+

遍历结果为:4 2 7 5 8 1 3 6

+

后序遍历方法一(非递归)

+

后序遍历相对来说显得更加复杂了。在前序和中序遍历中,只要左子树处理完毕实际上栈顶元素就可以出栈了,但后序遍历需要把左子树和右子树都处理完毕才能出栈,显然我们需要某种方法记录遍历的过程。

+

实际上我们只需要记录下遍历的前一个结点就能解决问题,因为通过前一个结点我们可以做如下判断:

+
    +
  1. 如果前一个结点是当前结点的右子树,那么说明右子树已经遍历完毕可以出栈了
  2. +
  3. 如果前一个结点是当前结点的左子树而且当前结点没有右子树,那么说明可以出栈了
  4. +
  5. 如果当前结点即没有左子树也没有右子树,即为叶子结点,那么说明可以出栈了
  6. +
+

若不属于上述情况,则依次将当前结点的右孩子和做孩子入栈,这样就能保证每次取栈顶元素时,左孩子都在右孩子前面被访问,左孩子和右孩子都在父结点前面被访问。

+
// 后序遍历 左右根
+void post_order_traversal(node *root)
+{
+    stack<node *> s;
+    node *pre = NULL;
+    node *cur = root;
+
+    s.push(cur);
+
+    while(!s.empty()) {
+        cur = s.top();
+        // 叶子结点
+        if((!cur->left && !cur->right) // 叶子结点
+        || pre == cur->right // 前一个结点为当前结点右子树
+        || (pre == cur->left && !cur->right)) { // 前一个结点为当前结点左子树,且没有右子树
+            cout<<cur->data<<" ";
+            pre = cur;
+            s.pop();
+        } else {
+            if(cur->right)
+                s.push(cur->right);
+
+            if(cur->left)
+                s.push(cur->left);
+        }
+    }
+}
+
+

遍历结果为:4 7 8 5 2 6 3 1

+

后序遍历方法二(非递归)

+

后序遍历的顺序是左右根,如果把这个顺序倒过来就是根右左,是不是发现和前序遍历很像?那么我只需要按照根右左的方式遍历完,然后将遍历结果掉一个个儿就可以,而栈就具备掉个儿的功能,因此可写出如下代码。

+
// 后序遍历 左右根
+void post_order_traversal(node *root)
+{
+    stack<node *> s;
+    stack<int> ans;
+    node *cur = root;
+
+    while(cur || !s.empty()) {
+        // 将左子树全部入栈
+        while(cur) {
+            ans.push(cur->data);
+            s.push(cur);
+            cur = cur->right;
+        }
+
+        if(!s.empty()) {
+            cur = s.top();
+            s.pop();
+            cur = cur->left;
+        }
+    }
+
+    while(!ans.empty()) {
+        cout<<ans.top()<<" ";
+        ans.pop();
+    }
+}
+
+

遍历结果为:4 7 8 5 2 6 3 1

+

层序遍历

+

层序遍历即广度优先遍历,使用队列即可实现。

+
// 层序遍历
+void breadth_first_order_traversal(node *root)
+{
+    queue<node *> q;
+    q.push(root);
+	while(!q.empty()){
+		node *cur = q.front();
+		q.pop();
+		if(cur){
+			cout<<cur->data<<" ";
+			q.push(cur->left);
+			q.push(cur->right);
+		}
+	}
+}
+
+

遍历结果为:1 2 3 4 5 6 7 8

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/9Ov7sD1uz/index.html b/9Ov7sD1uz/index.html new file mode 100644 index 00000000..fb3723a6 --- /dev/null +++ b/9Ov7sD1uz/index.html @@ -0,0 +1,695 @@ + + + + + + + + Oracle 安装及 Spring 使用 Oracle | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ Oracle 安装及 Spring 使用 Oracle +

+ + +
+ +
+
+

参考内容:

+

docker安装oracle数据库史上最全步骤(带图文)

+

Mac下oracle数据库客户端

+

Docker安装Oracle

+

docker能安装oracle吗

+

Batch script for add a auto-increased primary key for exist table with records

+
+

Docker 安装 Oracle11g

+

注意:下列安装方式仅适用于x86架构服务器,不适用于arm架构服务器

+
# 拉取 oracle11,镜像有点大,需要花一些时间
+docker pull registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g
+
+# 查看镜像是否拉取成功
+docker images
+
+# 给镜像重新打 tag,原来的名字太长了
+docker tag registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g oracle11g:latest
+
+# 启动 oracle11g 容器
+docker run --name=oracle11g -itd -p 1521:1521
+
+# 进入容器进行配置
+docker exec -it oracle11g /bin/bash
+
+# 切换到 root 用户,密码为:helowin
+su root
+
+# 编辑配置文件
+
+

编辑/etc/profile,在其中增加如下内容:

+
export ORACLE_HOME=/home/oracle/app/oracle/product/11.2.0/dbhome_2
+export ORACLE_SID=helowin
+export PATH=$ORACLE_HOME/bin:$PATH
+
+

编辑完成后,需要刷新上述环境变量才能使用。

+
# 刷新环境变量
+source /etc/profile
+
+# 创建软链接
+ln -s $ORACLE_HOME/bin/sqlplus /usr/bin
+
+# 切换到 oracle 用户
+su - oracle
+
+# 登陆 sqlplus
+sqlplus /nolog
+conn /as sysdba
+
+# 修改 system 用户密码
+alter user system identified by system;
+# 修改 sys 用户密码
+alter user sys identified by system;
+
+# 创建内部管理员账号
+create user test identified by test;
+
+# 将 dba 权限授权给内部管理员账号和密码
+grant connect,resource,dba to test;
+
+# 修改密码规则策略为密码永不过期
+ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED;
+
+# 修改数据库最大连接数据
+alter system set processes=1000 scope=spfile;
+
+

修改上述信息后,需要重新启动数据库才会生效。

+
conn /as sysdba
+
+# 关闭数据库
+shutdown immediate;
+
+# 启动数据库
+startup;
+
+# 退出软链接
+exit;
+
+

客户端连接 Oracle

+

以 Navicat 客户端为例,新建连接时按下图方式填写连接信息即可,密码即为system。需要注意的是,在 Windows 下选择 SID 或是服务名均可连接成功,但是在 Mac 下需要选择 SID 方式才能连接成功。

+
+

Oracle 实现主键自增

+

Oracle 在创建表的时候,不能像 MySQL 那样选择主键直接自增,但是我们可以通过给表创建序列和触发器去实现自增。下文以创建 USER 表为例。

+
-- 删除原有 USER 表
+DROP TABLE "TEST"."USER";
+-- 创建 USER 表
+CREATE TABLE "TEST"."USER" (
+  "id" NUMBER NOT NULL,
+  "gmt_create" DATE NOT NULL,
+  "gmt_modified" DATE NOT NULL,
+  "is_deleted" NUMBER NOT NULL,
+  "login" NVARCHAR2(255) NOT NULL,
+  "passwd" NVARCHAR2(255) NOT NULL,
+  "nick" NVARCHAR2(255) NOT NULL,
+  "phone" NVARCHAR2(255),
+  "head_img" NVARCHAR2(255),
+  "status" NVARCHAR2(255),
+  "remark" NCLOB
+);
+
+-- 删除原有序列
+DROP SEQUENCE "TEST"."USER_SEQ";
+-- 创建 USER_SEQ 序列,最小值为 1
+CREATE SEQUENCE "TEST"."USER_SEQ" 
+-- 最小值为 1
+MINVALUE 1 
+-- 最大值为 9999999999999999999999999999
+MAXVALUE 9999999999999999999999999999 
+-- 每次增加 1
+INCREMENT BY 1 
+-- 将 20 个序列值放入缓存
+CACHE 20;
+
+-- 创建触发器
+CREATE TRIGGER "TEST"."USER_TRIGGER" 
+-- 在插入数据前执行
+BEFORE INSERT ON "TEST"."USER" 
+-- 命名老数据为 OLD,新数据为 NEW
+REFERENCING OLD AS "OLD" NEW AS "NEW" 
+-- 针对表的每一行都执行触发器
+FOR EACH ROW 
+-- 将序列值赋值给 id
+BEGIN
+	:NEW."id" := USER_SEQ.NEXTVAL;
+END;
+/
+
+

需要注意的是,上面的/符号不能少。执行插入语句时可以发现,id会自动增加。

+
INSERT INTO "TEST"."USER" ("gmt_create", "gmt_modified", "is_deleted", "login", "passwd", "nick", "phone", "head_img", "status", "remark") VALUES (TO_DATE('2023-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), TO_DATE('2023-04-01 17:04:30', 'SYYYY-MM-DD HH24:MI:SS'), '0', 'user', '123', 'Jack', '1111', 'head.jpg', '激活', '测试');
+
+

Java Spring+Mybatis 使用 Oracle 及配置分页

+

application.properties文件配置信息:

+
# mybatis
+spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
+spring.datasource.url=jdbc:oracle:thin:@8127.0.0.1:1521:helowin
+spring.datasource.username=system
+spring.datasource.password=system
+mybatis.mapper-locations=classpath*:mybatis/*.xml
+mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
+
+# pageHelper
+pagehelper.helperDialect=oracle
+pagehelper.reasonable=true
+pagehelper.supportMethodsArguments=true
+pagehelper.params=count=countSql
+
+

pom.xml配置文件关键信息。

+
<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <properties>
+        <java.version>1.8</java.version>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+
+        <spring.boot-version>2.1.3.RELEASE</spring.boot-version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring.boot-version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <dependency>
+                <groupId>org.mybatis.spring.boot</groupId>
+                <artifactId>mybatis-spring-boot-starter</artifactId>
+                <version>2.1.0</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.oracle.ojdbc</groupId>
+                <artifactId>ojdbc8</artifactId>
+                <version>19.3.0.0</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.github.pagehelper</groupId>
+                <artifactId>pagehelper-spring-boot-starter</artifactId>
+                <version>1.4.3</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.github.pagehelper</groupId>
+                <artifactId>pagehelper-spring-boot-starter</artifactId>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/9WeHlVlqK9/index.html b/9WeHlVlqK9/index.html new file mode 100644 index 00000000..806770ba --- /dev/null +++ b/9WeHlVlqK9/index.html @@ -0,0 +1,273 @@ + + + + + + + + 软件测试 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 软件测试 +
+ + +
+

+ + 什么是契约测试? + +

+ +
+ + + + +
+ +
+ +参考文章: +聊一聊契约测试 —— ThoughtWorks洞见 +契约测试 +前后端分离了,然后呢? + +契约测试全称为:消费者驱动契约测试,最早由 Martin Fowler 提出。契约这个词从字面上很容易理解,就是双方(多方)达成的共同协议,那又为什么需要契约测试这个东西呢? +在当前微服务大行其道的行业背景下,越来越多的团队采用了前后端分离和微服务架构,我们知道微服务是由单一程序构成的小服务,与其它服务使用 HTTP API 进行通讯,服务可以采用不同的编程语言与数据库,微服务解决了单体应用团队协作开发成本高、系统高可用性差等等问题。 +但是微服务也引入了新的问题,假设 A 团队开发某服务并提供对应的 API,B 团队也在开发另一个服务,但是他们需要调用 A 团队的 API,为了产品的尽快发布,两个团队都争分夺秒,已经进入联调阶段了,然而出现了下面这样的尴尬情况。 + +随着越来越多的微服务加入,它们的调用关系开始变得越来越复杂,如果每次更改都需要和所有调用该 API 的团队协商,那沟通成本也未免太大了,试想下图的沟通成本。 + +为了保证 API 调用的准确性,我们会对外部系统的 API 进行测试,如果外部系统稳定性很差,或者请求时间很长的时候,就会导致我们的测试效率很低,当调用 API 失败时,你甚至无法确定是因为 API 被更改而导致的失败还是运行环境不稳定导致的失败。 +A 团队提供的 API 不稳定,肯定会导致 B 团队效率低下,为了不影响 B 团队的进度,所以构建了测试替身,通过模拟外部 API 的响应行为来增强测试的稳定性和反应速度。 + +但是这样做真的就解决问题了吗?当所有内部测试都通过时,能拍着胸脯说真正的外部 API 就一定没有变化?很简单的一个解决方案就是:部分测试使用测试替身,另一部分测试定期使用真实的外部 API,这样既保证了测试的运行效率、调用端的准确性,又能确保当真实外部系统API改变时能得到反馈。 + +感觉剧情到这里就差不多该结束了,实际上真正的高潮部分开刚刚开始。如果外部 API 的反馈周期很长,那增加真实 API 测试间隔时间就又回到了最初的起点。现在我们回顾一下上面的方案。 +在上面的场景中,我们都是已知外部 API 功能来编写相应的功能测试,并且使用直接调用外部 API 的方式来达到测试的目的,如此就不可避免的带来了两个问题: + +API 调用者(消费者)对服务提供方(生产者)的更改是通过对 API 的测试来感知的; +直接依赖于真实 API 的测试效果受限于 API 的稳定性和反映速度。 + +解决方案首先是依赖关系解耦,去掉直接对外部 API 的依赖,而是内部和外部系统都依赖于一个双方共同认可的约定—“契约”,并且约定内容的变化会被及时感知;其次,将系统之间的集成测试,转换为由契约生成的单元测试,例如通过契约描述的内容,构建测试替身。这样,同时契约替代外部 API 成为信息变更的载体。 +前后照应一下,我们现在再来看一下消费者驱动契约测试。它有两个不可或缺的角色:消费者是服务使用方;生产者(提供者)是服务提供方。采用需求驱动(消费者驱动)的思想。契约文件(比如 json 文件)由双方共同定义规范,一般由消费者生成,生产者根据这份契约去实现。 +契约测试其中一个的典型应用场景是内外部系统之间的测试,另一个典型的例子是前后端分离后的 API 测试。行业内比较成熟的解决方案是 Swagger Specification 和 Pact Specification,这里不做展开讨论。 +我们同样可以把契约测试的思想用到代码的编写中,契约测试通过一个契约文件来解耦依赖,那么对于需要用户定义很多规则的场景,我们同样可以将这些规则像契约文件一样抽取出来,这样就降低了代码之间的耦合度。 +最后敲敲黑板,契约测试不是替代 E2E 测试的终结者,更不是单元测试的升级换代,它更偏向于服务和服务之间的 API 测试,通过解耦服务依赖关系和单元测试来加快测试的运行效率。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/AOlj4J-Zw/index.html b/AOlj4J-Zw/index.html new file mode 100644 index 00000000..dca13210 --- /dev/null +++ b/AOlj4J-Zw/index.html @@ -0,0 +1,866 @@ + + + + + + + + 并查集详解及相关例题解析 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 并查集详解及相关例题解析 +

+ + +
+ +
+
+

参考内容:

+

图论——并查集(详细版)

+
+

并查集(Disjoint-set)是一种精巧的树形数据结构,它主要用于处理一些不相交集合的合并及查询问题。一些常见用途,比如求联通子图、求最小生成树的 Kruskal 算法和求最近公共祖先(LCA)等。

+

并查集的理念是只关注个体属于哪个阵营,并不关心这个阵营中个体内部的关系,比如我们常说的张三是李家沟的,王二是王家坝的。同时并查集借助个体代表集体的思想,用一个元素代表整个群体,就像我们开学都会有学生代表、教师代表讲话一样,在台上讲话的那一个学生就代表了学校所有的学生。

+

并查集基本操作

+

并查集的基本操作主要有初始化 init查询 find合并 union操作。

+

初始化

+

在使用并查集的时候,常常使用一个数组fa来存储每个元素的父节点,在一开始的时候所有元素与其它元素都没有任何关系,即大家相互之间还不认识,所以我们把每个元素的父节点设为自己。

+
#define ARR_LEN 6000
+
+int fa[ARR_LEN];
+
+void init(int n)
+{
+    for(int i = 1; i <= n; i++)
+        fa[i] = i;
+}
+
+

查询

+

查询即找到指定元素的祖先。需要注意的是,这里我们需要找到指定元素的根祖先,不能找到爸爸或者爷爷就停止了,而是要找到查找不下去了为止,所以要不断的去递归下去,直到找到父亲为自己的结点才结束。

+
int find(int i)
+{
+    if(i == fa[i]) // 递归出口
+        return i;
+    else
+        return find(fa[i]); // 不断向上查找祖先
+}
+
+

考虑下面的场景,假如第一次我们需要查询元素5的祖先,第二次需要查询元素4的祖先,会发现第一次查询包含了第二次查询的计算过程,但我们的程序却傻傻的计算了两次,有没有办法去来优化查询过程,让每一次查询都能利用到此前查询计算的便利?

+
+

考虑到并查集并不关心某个元素的爸爸、爷爷是谁,只关心最终的祖先是谁,所以我们可以在查询的过程中顺便做一些修改,比如在查询5的过程中,顺便就把42的父亲给修改为1,即我们在查找过程中进行路经压缩

+
int find(int i)
+{
+    if(i == fa[i]){
+        return i;
+    } else {
+        fa[i] = find(fa[i]); // 进行路径压缩
+        return fa[i];
+    }
+}
+
+

合并

+

合并操作即介绍两个人相互认识,将他们纳入同一个帮派,只需要将俩元素的父亲修改为同一个即可。

+
void union(int i, int j)
+{
+    int fa_i = find(i);
+    int fa_j = find(j);
+    fa[fa_i] = fa_j;
+}
+
+

相关练习题目

+

洛谷 P1551 亲戚

+

题目连接:https://www.luogu.com.cn/problem/P1551

+

题目描述

+

若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

+

规定:xxyy 是亲戚,yyzz 是亲戚,那么 xxzz 也是亲戚。如果 xyx,y 是亲戚,那么 xx 的亲戚都是 yy 的亲戚,yy 的亲戚也都是 xx 的亲戚。

+

输入格式

+

第一行:三个整数 n,m,p(n,m,p5000)n,m,p,(n,m,p≤5000) 分别表示有 nn 个人,mm 个亲戚关系,询问 pp 对亲戚关系。

+

以下 mm 行:每行两个数 MiMj1MiMjnM_i,M_j,1≤M_i,M_j≤n,表示 MiM_iMjM_j 具有亲戚关系。

+

接下来 pp 行:每行两个数 PiPjP_i,P_j,询问 PiP_iPjP_j 是否具有亲戚关系。

+

输出格式

+

pp 行,每行一个YesNo。表示第 ii 个询问的答案为“具有”或“不具有”亲戚关系。

+

输入输出样例

+
# 输入
+6 5 3
+1 2
+1 5
+3 4
+5 2
+1 3
+1 4
+2 3
+5 6
+
+# 输出
+Yes
+Yes
+No
+
+

题目解析

+

可以发现这是一个非常标准的并查集问题,简直和并查集模版如出一辙,因此直接将所有关系读取后进行合并,然后直接查询父亲是否为同一个即可。

+
#include<bits/stdc++.h>
+using namespace std;
+
+#define ARR_LEN 6000
+
+int fa[ARR_LEN];
+
+void init(int n)
+{
+    for(int i = 1; i <= n; i++)
+        fa[i] = i;
+}
+
+int find(int i)
+{
+    if(i == fa[i]){
+        return i;
+    } else {
+        fa[i] = find(fa[i]);
+        return fa[i];
+    }
+}
+
+void union(int i, int j)
+{
+    int fa_i = find(i);
+    int fa_j = find(j);
+    fa[fa_i] = fa_j;
+}
+
+
+int main()
+{
+    int n, m, p;
+	int a, b;
+	
+	cin>> n >> m >> p;
+	
+	init(n);
+
+	for(int i = 0; i < m; i++){
+		cin >> a >> b;
+		union(a, b);
+	}
+	
+	for(int i = 0; i < p; i++){
+		cin >> a >> b;
+		int fa_a = find(a);
+		int fa_b = find(b);
+		
+		if(fa_a == fa_b)
+			cout<<"Yes"<<endl;
+		else
+			cout<<"No"<<endl;
+	}
+}
+
+

杭电 OJ1213 How Many Tables

+

题目连接:https://acm.hdu.edu.cn/showproblem.php?pid=1213

+

题目描述

+

Today is Ignatius' birthday. He invites a lot of friends. Now it's dinner time. Ignatius wants to know how many tables he needs at least. You have to notice that not all the friends know each other, and all the friends do not want to stay with strangers.

+

One important rule for this problem is that if I tell you A knows B, and B knows C, that means A, B, C know each other, so they can stay in one table.

+

For example: If I tell you A knows B, B knows C, and D knows E, so A, B, C can stay in one table, and D, E have to stay in the other one. So Ignatius needs 2 tables at least.

+

输入格式

+

The input starts with an integer T(1<=T<=25)T(1<=T<=25) which indicate the number of test cases. Then TT test cases follow. Each test case starts with two integers NN and M(1<=N,M<=1000)M(1<=N,M<=1000). NN indicates the number of friends, the friends are marked from 11 to NN. Then MM lines follow. Each line consists of two integers AA and B(A!=B)B(A!=B), that means friend AA and friend BB know each other. There will be a blank line between two cases.

+

输出格式

+

For each test case, just output how many tables Ignatius needs at least. Do NOT print any blanks.

+

输入输出样例

+
# 输入
+2
+5 3
+1 2
+2 3
+4 5
+
+5 1
+2 5
+
+# 输出
+2
+4
+
+

题目解析

+

分析可以发现,这个问题要我们做的是统计在所有元素合并之后,统计总共有多个和集合。很轻松就能写出下面的 AC 代码。类似的问题还有杭电 OJ1232 畅通工程

+

读者大人可以在此基础上继续进行延伸,我们实际生活中每个桌子只能坐 8 个人,假设还需要考虑每桌人数的容量,又如何进行改进呢?

+
#include<bits/stdc++.h>
+using namespace std;
+
+#define ARR_LEN 6000
+
+int fa[ARR_LEN];
+
+void init(int n)
+{
+    for(int i = 1; i <= n; i++)
+        fa[i] = i;
+}
+
+int find(int i)
+{
+    if(i == fa[i]){
+        return i;
+    } else {
+        fa[i] = find(fa[i]);
+        return fa[i];
+    }
+}
+
+void union(int i, int j)
+{
+    int fa_i = find(i);
+    int fa_j = find(j);
+    fa[fa_i] = fa_j;
+}
+
+
+int main()
+{
+    int n, m, a, b, t;
+
+    cin>>t;
+    for(int i = 0; i < t; i++){
+        cin>>n>>m;
+        int ans = 0;
+        init(n);
+        for(int i = 0; i < m; i++) {
+            cin>>a>>b;
+            union(a, b);
+        }
+
+        for(int i = 1; i <= n; i++) {
+            // 如果父亲是自己,那么就表示一个独立的集合
+            if(find(i) == i)
+                ans++;
+        }
+
+        cout<<ans<<endl;
+    }
+    
+}
+
+

杭电 OJ1272 小希的迷宫

+

题目连接:https://acm.hdu.edu.cn/showproblem.php?pid=1272

+

题目描述

+

小希设计了一个迷宫让 Gardon 玩,首先她认为所有的通道都应该是双向连通的,就是说如果有一个通道连通了房间 A 和 B,那么既可以通过它从房间 A 走到房间 B,也可以通过它从房间 B 走到房间 A,为了提高难度,小希希望任意两个房间有且仅有一条路径可以相通(除非走了回头路)。小希现在把她的设计图给你,让你帮忙判断她的设计图是否符合她的设计思路。比如下面的例子,前两个是符合条件的,但是最后一个却有两种方法从 5 到达 8。

+
+

输入格式

+

输入包含多组数据,每组数据是一个以 0 0 结尾的整数对列表,表示了一条通道连接的两个房间的编号。房间的编号至少为 1,且不超过 100000。每两组数据之间有一个空行。整个文件以两个 -1 结尾。

+

输出格式

+

对于输入的每一组数据,输出仅包括一行。如果该迷宫符合小希的思路,那么输出Yes,否则输出No

+

输入输出样例

+
# 输入
+6 8  5 3  5 2  6 4
+5 6  0 0
+
+8 1  7 3  6 2  8 9  7 5
+7 4  7 8  7 6  0 0
+
+3 8  6 8  6 4
+5 3  5 6  5 2  0 0
+
+-1 -1
+
+# 输出
+Yes
+Yes
+No
+
+

题目解析

+

其实这个问题就是让我们判断一个连通图中是否存在环,那么问题就转换为寻找出现环的条件。其实不难发现出现下面两种情况时,连通图即存在环。

+
    +
  1. 在查找过程中,发现两个不同元素的父亲是相同的;
  2. +
  3. 若不存在环,则边的数量一定比顶点数量少 1。
  4. +
+
#include<bits/stdc++.h>
+using namespace std;
+
+#define ARR_LEN 100010
+
+int fa[ARR_LEN];
+bool visited[ARR_LEN]; // 用于辅助记录顶点的数量
+int edges, points; // 记录顶点和边的数量
+bool hascycle; // 是否存在环
+
+void init()
+{
+    hascycle = false;
+    edges = 0;
+    points = 0;
+    for(int i = 1; i < ARR_LEN; i++)
+        fa[i] = i, visited[i] = false;
+}
+
+int find(int i)
+{
+    if(i == fa[i]){
+        return i;
+    } else {
+        fa[i] = find(fa[i]);
+        return fa[i];
+    }
+}
+
+void union(int i, int j)
+{
+    int fa_i = find(i);
+    int fa_j = find(j);
+
+    // 两个元素祖先相同,存在环
+    if(fa_i == fa_j) {
+        hascycle = true;
+    } else {
+        visited[i] = true;
+        visited[j] = true;
+        edges++;
+        fa[fa_i] = fa_j;
+    }
+}
+
+
+int main()
+{
+    int a, b;
+
+    init();
+    
+    while(cin>>a>>b) {
+        if(a == 0 && b == 0) {
+            cout<<"Yes"<<endl;
+            continue;
+        }
+
+        if(a == -1 && b == -1) {
+            return 0;
+        }
+
+        union(a, b);
+
+        while(cin>>a>>b){
+            if(a == 0 && b == 0) {
+                break;
+            }
+            union(a, b);
+        }
+
+        if(hascycle) {
+            cout<<"No"<<endl;
+            continue;
+        }
+
+        for(int i = 1; i < ARR_LEN; i++){
+            if(visited[i]) {
+                points++;
+            }
+        }
+
+        if(points == edges + 1) {
+            cout<<"Yes"<<endl;
+        } else {
+            cout<<"No"<<endl;
+        }
+        init();
+    }
+}
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/AThbvq-Gb/index.html b/AThbvq-Gb/index.html new file mode 100644 index 00000000..04c6c8b0 --- /dev/null +++ b/AThbvq-Gb/index.html @@ -0,0 +1,464 @@ + + + + + + + + 匆匆的岁月——成都七中网校远端学生的高中生活 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 匆匆的岁月——成都七中网校远端学生的高中生活 +

+ + +
+ +
+

周三刷星球时看到一条关于成都七中网校的信息,没想到周四就被成都七中网校给刷屏了,看到文章里面的描写,感悟颇多,网校陪伴了自己三年,一个诗意的女孩——西凉忆就是因为网校结识的。

+

我是 12 年入学高中,那一年学校也刚和成都七中开通网校,因此我属于学校第一届全程使用网校资源的学生。记得刚入学时,对这种教学方式并不适应。不止学生不适应,老师也不适应,政治课老师就不使用直播课程,而是选择自己给我们讲。不过后来年级组出了硬性规定,特优班必须使用直播课程。

+

就像教育的水平线中描述的一样,我们被称为远端学生,大家都是第一次使用这样的系统,七中老师估计也很好奇,都在第一节课或者第二节课上抽了远端同学回答问题,后来就很少有抽远端同学回答问题了,估计是因为远程视频效果不好还浪费时间。

+

成都七中本部的学生上课很有激情,一到讨论等环节,虽然很吵但是却很有秩序,而我们这边基本是大家都盯着屏幕,等待对方讨论结束。

+

对方学生的基础我们是没办法比的,大部分能够完全以纯英文交流,而我们远端学生大部分都只能说出“My name is ..., I'm from ..., I like ...”,英语的差距是最大的。我自己是主动找了英语老师谈话,和她做了一个约定,每周我写一篇作文交给她,她帮我批改,这样坚持了两年。

+
+

让我感受到最大差距的是,一个中学有自己的电视台,经常会有像《汉语桥》冠军一类的人物前来演讲,美国第一夫人米歇尔也到七中演讲(那是总统还是奥巴马),作为远端学生有幸蹭了一场名人演讲;七中学生的寒暑假是参加联合国模拟大会、到哈佛做了简短的交流、到华盛顿旅行......

+

而大部分远端的学生,要么是参加学校组织的补课,要么是在家干农活,基本连县城都没有走出去过,和七中相比,完全是天壤之别。

+

现在我依旧还清晰的记得七中的几个老师,做 PPT 从来没有背景图的数学老师,语速超快但又吐字清晰的化学老师,说着一口标准川普的物理老师,有着一头蓬松金黄卷发的历史老师(男)......去看这些老师的背景,也都是名校出身,武汉大学、华中师大等等。

+
+

有一个细节记得很清楚,在一堂数学课中有个题目,题目具体内容忘了,只记得是要求算新华中学的一本录取率,最后的答案是在 74% 左右,这个数字在我眼里很高了,但是那一刻并没有觉得有什么,毕竟这是书上的学校嘛!!

+

想不到的是,下一秒那个做 PPT 从来没有背景图的数学老师说了句:“这个就比较低了啊,我们学校考的很差的时候才是百分之七十多的录取率”。一下让我震惊了,因为我在心里算过自己学校的一本录取率,在此之前不到 10%,而且我所在高中在所有远端学校中还是比较靠前的。

+
+

让我意外的是,七中的老师原来也会骂人、打人,即使打的很轻;学生没完成作业,也会被罚拿个小凳子到教室外面补作业;在全国禁止补课的规定之下,七中也会给高三的学生补课,当然我们也同步补课了。

+

我无法构想如果三年没有使用网校资源会是神马结果,如果仅仅是看数据的话,一本率是按倍数翻的,12 年开始使用网校资源后,学校有了第一个清华,13 年又添了一个清华。我属于 14 届,这届没有清华,最好的是浙大,我进了个普普通通的哈尔滨工程大学。据说 15 届又出了清华。

+

我所在的高中也被央视作为典型案例探讨农村高考。无疑能上央视,那这个学校在当地乃至全国同等水平的学校中是很成功的。

+

无疑这种生活给每个同学都留下了难忘的记忆,如果哪位同学加了七中本部直播班级某位同学的 QQ(那时没有人用微信),那能吹好几天牛逼,七中人在我们眼里就像明星一样。

+

我们当地 14 届县状元高考分数是 635 分,七中本部平均分是 616 分,这差距至今都让我目瞪口呆。前段时间曹大在星球发起了一个作业:十年后的期望目标。我所提交的作业中写了句,如果有能力,希望能给乡村学校带去一点教育资源。

+

我并不认为穷是光荣的事情,但在很多农村人眼里穷是一种资本,一种获取国家福利的资本,如果某次补助没有评到自己头上,那得记恨村长一辈子。我认为这才是造成农村孩子和城里孩子有巨大差距的原因,如果孩子没有从这种思维走出来,那一辈子也不可能有什么大的成就。

+

没想到自己都大学毕业了,却看到成都七中的网校被刷屏了。毫无疑问,这是一件极为有意义的事情,这种内容就应该被刷屏,愿七中和远端学校都越办越好。

+
+

文中照片取自于同学 QQ 空间,记得我原来还有英语老师 Spring 的 QQ,高中时向他请教过自己的英语问题,太久没联系后来 Spring 把我删了 =_=,反正是没了。

+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/AeVQtjKb0/index.html b/AeVQtjKb0/index.html new file mode 100644 index 00000000..63fbc701 --- /dev/null +++ b/AeVQtjKb0/index.html @@ -0,0 +1,455 @@ + + + + + + + + 阅读高于自己的作品,远离精神毒品 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 阅读高于自己的作品,远离精神毒品 +

+ + +
+ +
+

关于阅读与写作的重要性,可能每个人都多少有一些概念,关于写作的重要性可以看我之前系的谈一下写作的重要性。阅读是打开世界的大门,写作可以提升语言组织能力。

+

自己就读的专业是计算机科学与技术,生活在山区一直都没有接触过电脑。读大一大二的时候,我非常崇拜那些随随便便就能做一个网站、写一个复杂算法的大神。那时候在觉得只要专业能力足够强,只要技术掌握的足够深入,那就是 NB,所以我在大二之前从来都没有读过一本技术之外的书籍。

+

现在回忆起那时候的想法真是太狭隘了,一个人掌握了某项专业技能,我们可以把这个人称之为「手艺人」,大学能教给我们一门手艺(可笑的是很多学生连手艺都没有学到),这门手艺可以帮助我们得以生存或者赚到一些钱,但是生活肯定不仅仅是钱,还有很多比钱更重要的事情。

+

白领及以下阶层基本没有自己想去哪里就去哪里的自由,那怎么拓宽自己的视野呢?阅读和交朋友是个很不错的方式,但并不是所有阅读都是有效的,也不是所有朋友都是值得信任的,有的甚至是一种精神毒品。

+

阅读需要挑选高于自己的作品,要能仰视它,才能攀登。阅读那些比自己低下的作品只会让自己更 low。现在的生活节奏很快,碎片化阅读成了很多人的阅读方式;有人抨击碎片化阅读,也有人提倡碎片化阅读,每个人有不同的观点,我个人是赞同碎片化阅读的,像得到、喜马拉雅等平台也把一个大的知识点切分的足够小了,一个小的知识点也就 10 来分钟,很适合碎片化的阅读,还不耽误其它事情。

+

来自互联网的阅读内容和自己大多是平等的,每个人都在使用微信,但我相信很多人都有屏蔽一些人朋友圈的习惯,因为你会选择跟你脾气相同、你喜欢的、跟你水平接近内容去阅读;现在的 APP 也很多,而且很多首次注册都会让你选择自己感兴趣的内容,所以也就会失去挑战自我的机会。

+

阅读是提升认知的重要手段,人与人之间的根本差距在于认知,如果读了一本书之后能让自己的认知得到提升,那么这就没白读;当然,如果读完一本书或是一篇文章之后能让有很大程度的转变,那这种认知的提升我相信是宝贵的财富,而且认识是伴随终身的。能达到这样效果的好文章、好书肯定少之又少,对我个人影响的最大的一本书是《把时间当作朋友》,我很推荐这本书,接触这本书算我认知上的一个转折点。

+

也有一些阅读是日积月累对自己产生影响的。我曾经关注了近 150 个公众号,从里面精挑细选了一部分原创公众号留下,涉及技术、新闻、产品、理财等方面。我现在每天的阅读主要就是公众号阅读和付费加入的几个知识星球,以及得到上面的付费知识。

+

日积月累,我筛选有效信息的能力也更强了,筛选信息能力在这个信息爆炸的时代是很重要的。

+

有一次听到朋友说:“文章太长了,我不想看”。我没说话,只给了一个无奈的眼神,因为他只想要一个结论,因为这可以很轻松的获得一种愉悦感,就像打游戏一样可以快速的获得喜悦,而且对大脑来说,也是最容易接受的,但是慢慢地,大脑就会失去独立思考的能力。

+

从产品角度讲,不得不佩服头条对人性的洞察,为了“懂你”,在自己的产品上加入推荐算法,但实际上是让你把时间花在他们产品的身上,把产品做到这种程度,不得不承认是很牛的。最近奈飞出了一步很棒的纪录片,叫做监视资本主义:智能陷阱 The Social Dilemma,片中将科技的负面清楚的呈现给我们,网络科技在某些方面已经逐渐演变为操纵社会的巨兽。

+

像抖音、快手、头条、微博这些产品我认为基本都是在浪费用户时间(我还没有用过快手和微博,评价它们有点冒昧了),他们无异于精神毒品,吞噬你的时间。我并不是讨厌这些产品,我自己也喜欢体验新的产品,我只是觉得把宝贵的时间放到更有价值的事情上去,那么就会比周边人更加优秀。

+

需要选择的是高于自己内容阅读,而不是把时间都花在那种不需要思考就能得到的愉悦上去;如果把大部分时间都花在轻易就能获得的愉悦感上,那么你应该正在一步步颓废。

+

最后推荐自己写的另一篇文章大学生书单推荐

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/BDX4-XwWJ/index.html b/BDX4-XwWJ/index.html new file mode 100644 index 00000000..c5e62dda --- /dev/null +++ b/BDX4-XwWJ/index.html @@ -0,0 +1,310 @@ + + + + + + + + 信用卡 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 信用卡 +
+ + +
+

+ + 信用卡航司里程和境外返现 + +

+ +
+ + + + +
+ +
+ 在信用卡基础知识入门中已经提到过银行会和各大公司搞联名卡,其实当你把信用卡了解到一定程度之后就会发现银行、航司、电商、酒店都是相互关联的。信用卡可以匹配像希尔顿、万豪等一些酒店的高级会员,酒店高级会员又可以匹配航司高级会员。 +提到航司这里建议各位小伙伴乘机时都先注册一个会员账户积累里程,很多人可能并没有去关注里程这个东西。以乘坐南航航班从成都到广州为例,用 12000 里程就可以兑换一张机票,相当于就省去了一张机票钱,最近是特殊时期一张机票看起来价格并不贵,要搁平时咋样也是会省 1000 来块钱的。 + +我之前也不懂航司里程这些东西,看了下航旅纵横发现我在 19 年总共乘坐了 17 次航班,其中有 4 次乘坐的是海南航空的航班,我并没有注册海南航空的会员,导致这几次航班的里程都没了。在 20 年我还使用几千南航里程和几千凤凰知音里程就换了几本书和一袋核桃,现在才知道当时换的是相当不划算的,一半的机票就被自己玩没了。 +大多数人都只是在春节时需要来回飞一趟,靠乘坐航班积累里程的方式是不现实的,更加靠谱的方式就是通过信用卡。国内银行的白金卡基本都可以兑换航司里程(国外我也不懂),而且国内的信用卡非常乱,比如之前我用的招行金卡有 9 万多的额度,浦发给的一张白金卡才 6000 额度(已注销),所以一般额度有几万的都可以尝试去升级成白金卡。 +比如中行和南航推的联名白金卡消费 10 元就可以积累 1 里程,这些消费本身都是平时会产生的,顺道积累一下里程它不香吗?配上撸货(俗称黄牛)那就是非常可观的量级了,稍微勤奋一点应该是一年二三十万里程没有问题。 +信用卡除了积累里程外通常还会有其它的权益,比如搭配一个信用卡接送机的权益把来回机场的车票钱也给省下。比较重要的是信用卡的延误险权益,一种要求用带险种的卡片去购买机票才可赔付,另外一种是不论以什么方式购买机票只要延误就给赔。应该有小伙伴已经在网上看过有人撸了几百万延误险,结果把自己给搞进去了。再次提醒一下撸羊毛可以,但是别心太狠去宰羊。 +曹大也写过一篇关于里程信息差套利的文章,我也是上个月才发现很多平台的积分都是可以互通的,比如平安银行的万里通积分就和各大航司、运营商、零售等挂上了钩,也可以通过这种方式把各种平台的积分都聚合到一起。 + +信用卡肯定要用起来才会有收益,除了国内日常所产生的消费可以积累一些积分外,还有一个门槛比较高的就是境外消费返现。比如建行出的「MUSE卡鎏金版」境外消费笔笔 1% 返现,精选商户还有 8% 的返现,聪明的小伙伴看到这个应该就能联系到「海外代购」了吧。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 信用卡基础知识入门 + +

+ +
+ + + + +
+ +
+ 相信绝大部分伙伴都使用过「花呗」这个产品吧,当月花下月还很契合现在的提前消费理念。花呗有账单日和还款日之分,需要在每个月的还款日之前将上期的账单还清,否则就会产生逾期记录进而影响自己的征信。当然如果确实没有足够的钱也可以选择账单分期,只不过需要支付一定的分期手续费。花呗的这些机制与信用卡一致,但是相比信用卡花呗就显得很抠门了,抽空对信用卡做了一点研究,就分享到这里一起交流。 +虽然我从毕业开始就一直在用信用卡,但是我也一直没有搞明白信用卡的逻辑。比花呗更长的免息期,送开卡礼、首刷礼,用信用卡消费还给你积分兑换礼品、抵现或话费,偶尔还会给出一些支付立减的优惠(比如我前段时间用支付宝就老是遇到美国运通卡的立减金),银行难道是脑袋发热才这么送钱吗? + +用脚趾头想都知道银行的目的肯定是为了赚钱,但是为啥又白白的把各种权益送给你呢,所以我们有必要了解一下银行为什么要发行信用卡?不管是在线上还是线下消费,只要使用了信用卡进行结账,那么商家就需要给出一定的手续费。比如商家支付了 100 元的手续费,银行会拿到 60 元的利润,银联拿到 5 块钱,剩下的交给支付通道公司,同时还会根据「商户编码」给到你一定的积分。所以当你使用信用卡进行消费时,银行就会赚到钱。 +上一段提到了「商户编码」的概念,这个就像我们参加高考时老师给贴的条形码一样,是用来识别商户的。在教育、慈善一类的商户消费,银行是没有钱赚的,所以银行也不会给到你积分,我们可以把这类称之为「无积分商户」。银行就是根据商户编码来识别你刷的商户类型,具体可以查看刷卡之后小票商户编码 8-12 位。 +国内支持的都是银联卡,不过美国运通的业务已经在国内出现,比如我目前正在使用的招行百夫长信用卡,就是一张美国运通卡,它在国内已经支持了线上消费。国外支持银联的不多,所以很多信用卡都会在银联卡之外给配一张外币卡,有人说外币卡会占用自己的授信额度,如果不出国就不要申请你那张附属卡。 +信用卡是分不同等级的,比如普卡、金卡、白金卡、黑金卡。一般金卡及以下都是直接免年费或是可以通过一定的消费免年费的。白金及以上大部分都需要几千的年费,但是提供的相关权益也非常不错,比如航司里程、体检服务、机场贵宾厅、五星级酒店等等,不过白金及以上的下卡难度也大,具体可以看自己的实际情况去申请。 +现在银行都会和各种公司联合发一些联名卡,比如我手里的平安爱奇艺联名卡,每个月只需要有三笔消费超过 188 元,下个月就可以领一张爱奇艺黄金会员月卡。像我这种视频平台会员权益,日常消费所累积的积分,加上银行平时的一些像「5 倍积分」活动,以及利用账单日、还款日这些免息期,就是妥妥的羊毛党味道。 + +我去年一年的话费都是使用平安的积分充值,相当于白嫖了一年话费。从深圳搬到成都冬天太冷没有被子,又用招行的积分换了被子和一些收纳箱。银行给你这些通道,就是默许你可以撸羊毛,但你千万别贪心把毛拔秃了甚至要宰羊,不然就会很容易把自己给撸进去。 +不要小看撸羊毛这个行业,有的人能撸羊毛年入千万。我认识的人里面也有靠撸羊毛完全能养活自己的,再简单一点也有玩免费机票、酒店的。我觉得这个行业有意思的地方就是你日常生活的每一项都可以撸,电影票、外卖、水电话费、餐饮等等,但是玩信用卡玩着玩着也有一个问题,我现在哪怕在超市买瓶水也会不自觉的计算用哪张卡更划算。 +最后放一张闲鱼的截图做个引子吧,有兴趣的伙伴可以自己去研究,我先暂且写这么多。 + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/CJEg4Tlzw/index.html b/CJEg4Tlzw/index.html new file mode 100644 index 00000000..507a21cf --- /dev/null +++ b/CJEg4Tlzw/index.html @@ -0,0 +1,620 @@ + + + + + + + + JavaScript 进阶知识、技巧 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ JavaScript 进阶知识、技巧 +

+ + +
+ +
+

对象

+

Js 共有number、string、boolean、null、undefined、object六种主要类型,除了object的其它五中类型都属于基本类型,它们本身并不是对象。但是null有时会被当做对象处理,其原因在于不同的对象在底层都表示为二进制,在 js 中二进制前三位都为 0 的话就会被判定为object类型,而null的二进制表示全是 0, 所以使用typeof操作符会返回object,而后续的 Js 版本为了兼容前面埋下的坑,也就没有修复这个 bug。

+

"I'm a string"本身是一个字面量,并且是一个不可变的值,如果要在这个字面量上执行一些操作,比如获取长度、访问某个字符等,那就需要将其转换为String类型,在必要的时候 js 会自动帮我们完成这种转换,也就是说我们并不需要用new String('I'm a string')来显示的创建一个对象。类似的像使用42.359.toFixed(2)时,引擎也会自动把数字转换为Number对象。

+

nullundefined没有对应的构造形式,它们只有文字形式。相反,Date只有构造,没有文字形式。对于Object、Array、FunctionRegExp(正则表达式)来说,无论使用文字形式还是构造形式,它们都是对象,不是字面量。

+

Array 类型

+

数组类型有一套更加结构化的值存储机制,但是要记住的是,数组也是对象,所以有趣的是你也可以给数组添加属性。

+
var myArray = ["foo", 42, "bar"];
+myArray.baz = "baz";
+myArray.length; // 3
+myArray.baz; // "baz"
+
+

数组类型的length属性是比较有特点的,它的特点在于不是只读的,也就是说你可以修改它的值。因此可以通过设置这个属性从数组末尾删除或添加新的项。

+
var colors = ["red", "blue", "green"];
+colors.length = 2;
+console.info(colors[2]); // undefined
+colors.length = 4;
+console.info(colors[4]); // undefined
+// 向后面追加元素
+colors[colors.length] = "black";
+
+

数组还有一些很方便的迭代方法,比如every()、filter()、forEach()、map()、some(),这些方法都不会修改数组中包含的值,传入这些方法的函数会接收三个参数:数组项的值、该项在数组中的位置、和数组对象本身。

+

Function 类型

+

在 ECMAScript 中,每个函数都是Function类的实例,而且都与其它引用类型一样具有属性和方法。由于函数时对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

+

在函数的内部有两个特殊的对象,thisargumentsarguments对象有calleecaller属性。caller用来指向调用它的function对象,若直接在全局环境下调用,则会返回nullcallee用来指向当前执行函数,所以我们可以通过下面的方式来实现阶乘函数。

+
function factorial(num) {
+    if (num <= 1) {
+        return 1;
+    } else {
+        return num * arguments.callee(num-1);
+    }
+}
+
+

每个函数都包含两个非继承而来的方法,apply()call(),这两个方法都是在特定作用域中调用函数,实际上等于设置函数体内this对象的值。首先,apply()方法接收两个参数,一个是在其中运行函数的作用域,另一个是参数数组,其中第二个参数可以是Array的实例,也可以是arguments对象。call()方法与apply()方法的作用相同,它们的区别仅仅在于接收参数的方式不同,在使用call()方法时必须逐个列举出来。

+
window.color = "red";
+var o = {color: "blue"};
+function sayColor() {
+    console.info(this.color);
+}
+sayColor(); // red
+sayColor.call(this); // red
+sayColor.call(window); // red
+sayColor.call(o); // blue
+sayColor.apply(o); // blue
+
+

需要注意的是,在严格模式下未指定环境对象而调用函数,则this值不会转型为window,除非明确把函数添加到某个对象或者调用apply()call()

+

安全的类型检查

+

Js 内置的类型检查机制并不是完全可靠的,比如在 Safari(第5版前),对正则表达式应用typeof操作符会返回function;像instanceof在存在多个全局作用域(包含 frame)的情况下,也会返回不可靠的结果;前文提到的 Js 一开始埋下的坑也会导致类型检查出错。

+

我们可以使用toString()方法来达到安全类型检查的目的,在任何值上调用Object原生的toString()方法都会返回一个[object NativeConstructorName]格式的字符串,下面以检查数组为例。

+
Object.prototype.toString.call([]); // "[object Array]"
+function isArray(val) {
+    return Object.prototype.toString.call(val) == "[object Array]";
+}
+
+
+

作用域安全的构造函数

+

构造函数其实就是一个使用new操作符调用的函数,当使用new操作符调用时,构造函数内用到的this对象会指向新创建的对象实例,比如我们有下面的构造函数。

+
function Person(name, age) {
+    this.name = name;
+    this.age = age;
+}
+
+

现在的问题在于,要是我们不使用new操作符呢?会发生什么!

+
let person = Person('name', 23);
+console.info(window.name); // name
+console.info(window.age); // 23
+
+

很明显,这里污染了全局作用域,原因就在于没有使用new操作符调用构造函数,此时它就会被当作一个普通的函数被调用,this就被解析成了window对象。我们需要将构造函数修改为先确认this是否是正确类型的实例,如果不是则创建新的实例并返回。

+
function Person(name, age) {
+    if (this instanceof Person) {
+        this.name = name;
+        this.age = age;
+    } else {
+        return new Person(name, age);
+    }
+}
+
+

高级定时器

+

大部分人都知道使用setTimeout()setInterval()可以方便的创建定时任务,看起来好像 Js 也是多线程的一样,实际上定时器仅仅是计划代码在未来的某个时间执行,但是执行时机是不能保证的。因为在页面的生命周期中,不同时间可能有其它代码控制着 JavaScript 进程。

+

这里需要注意一下setInterval()函数,仅当没有该定时器的任何其他代码实例时,Js 引起才会将定时器代码添加到队列中。这样可以避免定时器代码可能在代码再次被添加到队列之前还没有完成执行,进而导致定时器代码连续运行好几次的问题。但是这也导致了另外的问题:(1)某些间隔会被跳过;(2)多个定时器的代码执行之间的间隔可能会比预期小。

+

假设某个click事件处理程序使用setInterval()设置了一个 200ms 间隔的重复定时器。如果这个事件处理程序花了 300ms 多的时间完成,同时定时器代码也花了差不多了的时间,就会同时出现跳过间隔切连续运行定时器代码的情况。

+

为了避免setInterval()的重复定时器的这两个缺点,我们可以使用如下模式的链式setTimeout(),代码一看就懂什么意思了。

+
setTimeout(function() {
+    // 处理中
+    setTimeout(arguements.callee, interval);
+}, interval)
+
+

消息队列与事件循环

+

如下图所示,左边的栈存储的是同步任务,就是那些能立即执行、不耗时的任务,如变量和函数的初始化、事件的绑定等等那些不需要回调函数的操作都可归为这一类。

+
+

右边的堆用来存储声明的变量、对象。下面的队列就是消息队列,一旦某个异步任务有了响应就会被推入队列中。如用户的点击事件、浏览器收到服务的响应和setTimeout中待执行的事件,每个异步任务都和回调函数相关联。
+JS引擎线程用来执行栈中的同步任务,当所有同步任务执行完毕后,栈被清空,然后读取消息队列中的一个待处理任务,并把相关回调函数压入栈中,单线程开始执行新的同步任务。

+

来看个例子:执行下面这段代码,执行后,在 5s 内点击两下,过一段时间(> 5s)后,再点击两下,整个过程的输出结果是什么?

+
setTimeout(function(){
+    for(var i = 0; i < 100000000; i++){}
+    console.log('timer a');
+}, 0)
+for(var j = 0; j < 5; j++){
+    console.log(j);
+}
+setTimeout(function(){
+    console.log('timer b');
+}, 0)
+function waitFiveSeconds(){
+    var now = (new Date()).getTime();
+    while(((new Date()).getTime() - now) < 5000){}
+    console.log('finished waiting');
+}
+document.addEventListener('click', function(){
+    console.log('click');
+})
+console.log('click begin');
+waitFiveSeconds();
+
+

首先,先执行同步任务。其中waitFiveSeconds是耗时操作,持续执行长达 5s。然后,在 Js 引擎线程执行的时候,'timer a'对应的定时器产生的回调、'timer b'对应的定时器产生的回调和两次 click 对应的回调被先后放入消息队列。由于 Js 引擎线程空闲后,会先查看是否有事件可执行,接着再处理其他异步任务,最后,5s 后的两次 click 事件被放入消息队列,由于此时 Js 引擎线程空闲,便被立即执行了。因此会产生下面的输出顺序。

+
0
+1
+2
+3
+4
+click begin
+finished waiting
+click
+click
+timer a
+timer b
+click
+click
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/Dj7VgGQ1S/index.html b/Dj7VgGQ1S/index.html new file mode 100644 index 00000000..242bee1f --- /dev/null +++ b/Dj7VgGQ1S/index.html @@ -0,0 +1,466 @@ + + + + + + + + 坟头蹦迪|清理手机上的干扰信息|2018 简短总结 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 坟头蹦迪|清理手机上的干扰信息|2018 简短总结 +

+ + +
+ +
+

前一周在一席上面看了一场演讲叫「不正经历史研究所所长」,整场演讲都伴随着欢声笑语,讲的是民间文化与经典文化,经典文化实际上是统治阶级强行推动的精致文化,绝大部分老百姓是看不懂这些东西的,反正我自己参观博物馆,如果没有讲解,我是完全看不懂那些玩意的。

+

能和生活联系在一起的文化最有活力,比如天天发杨超越祈求不挂科,拜拜马云希望有更多的钱等等。有一句老话叫「人活一口气」,民间文化都基于此,我在别人面前要抬得起头,要的是气派、要的是大气,上个世纪的「四大件」应该就是成功的一个标配,老百姓置办这些物件后,在邻居面前也更能昂首挺胸了。

+

昨天了解到一个朋友家乡的有一个非常奇怪的习俗,那就是「坟头蹦迪」,我最开始的想法是,这是不是最近兴起来的习俗,但是一问才知道,他小时候就是这样的了。基于我们的体系是无法理解坟头蹦迪的,你能说它们对于亲人的去世就不伤心吗?他们要的还是能抬起头,来参加追悼会的人们看到这么豪华的场面,第一反应是故人后代是很有成就的,在某种程度上这算是对故人的颂扬。

+
+

花了几个小时时间把微信清理了一下,只留下了 83 个公众号,这其中还包括一信用卡一类的服务号,也就是说只留下了 70 个左右的公众号。留下的都是小而美的公众号,这些大佬作者的更新频率也很低,比如子柳老师最近才更新了一篇文章,而上一篇文章的更新时间是 7 月 25 日。

+

最近在自己身上认识的一个问题是,关注的公众号太多读不过来,虽然在此之前已经筛选掉了很多号,然而自己还是读不过来,所以索性就做了减法。现在像公众号这些,已经变成了获取信息的一个重要渠道,我也确实比周围小伙伴掌握信息更快,大部分时间也自认为比他们更了解真相。

+

但是我最近突然有个疑问,我真的比他们更了解真相吗?大部分人是通过垃圾文章获取信息,我虽然没有跟着垃圾文章人云亦云,但是我还是通过网络获取信息的,那么我是不是看到的是另外一种看起来更接近真相的假象呢?

+

这些信息其实我就算不知道好像也没有什么大碍,顶多是别人谈什么新闻时,我所了解的也就他们知道的那么点而已,更好的方式是让自己的大脑去思考,以时间的维度去追踪事件的发展。

+

顺便推荐一个应用叫「刷屏」,我发现自己通过「刷屏」、「知识星球」、「微信公众号」、「朋友圈」四个地方,已经掌握大部分信息了,经常同事给我说某某新闻,而我在两三天前已经见过了。

+
+

我对国际计量大会修改「1 千克」的定义这件事印象很深,但是这件事我只看到在阮一峰老师的文章中有被提到过;人们更喜欢听自己了解的领域,跟自己同好的很容易产生好感,一些明星结婚、出轨竟然能把微博的服务给搞挂了,说明绝大部分人还是更喜欢撩自己 G 点的信息。

+
+

另外第二个问题是自己现在比在学校时更喜欢玩手机了,在学校时还能做到不带手机去自习,现在是隔一会儿就看看手机,把本来就碎片化的时间变得更加碎片化了,这种效率导致工作、学习效率低下,所以把微信和 TIM 的通知全部关掉了。

+
+

写到这里发现有点点像总结,索性就给自己简单总结总结吧。2018 我从校园走向了社会,完成了从学生到职场的转变。最大的改变是思维的提升,知道去投资自己,在学生时代,愿意花几百块钱去买课程,这对我来说是很大的突破,和现在愿意花几百的概念完全不一样,想想那时候一个月生活费总共也没有多少,而我去看了下自己在毕业前花在这方面的钱居然有一千多,如果加上毕业后的开销,那就是两千多了,真感谢那时候的自己。

+

这其中的收获是巨大的,后面我偶尔会向朋友推荐一些好的付费课程,但是他们都和大部分人一样,吃一顿饭花几百块钱,而如果花点钱买一堂课提升一下自己,就好像要他命一样,所以后面就不和他们讲了。

+
+

2018 开始用文字记录技术、生活、感悟,这其中的收获也是不小的,认识了此前只能仰望的大佬,结交了志同道合的朋友,而且也让自己更愿意去思考了。收到电子工业出版社的约稿合同,但是现在书都没写完,明年还写不完的话,那就不写了,主要是懒。

+

自负的缺点已经在渐渐改变了,更加懂得了谦虚。眼界不再局限于技术,很多东西我都会去了解,也结交了很多有趣的人,初入职场,好好学习与不同的人沟通。以后如果有能力,希望能给山区带去一点点教育资源。

+

2019 依旧坚持每周和同事或者校友打一次羽毛球,常去爬爬山,和有趣的朋友一起疯一疯;多读书,用豆瓣等工具去筛选好书,加大阅读量;常输出,输出是更高层次的输入;尝试去了解金融的逻辑;学习新的技术领域。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/EEBe6eBp1/index.html b/EEBe6eBp1/index.html new file mode 100644 index 00000000..321c2eea --- /dev/null +++ b/EEBe6eBp1/index.html @@ -0,0 +1,604 @@ + + + + + + + + 爬虫 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 爬虫 +
+ + +
+

+ + 使用订阅号实现微信公众号历史文章爬虫 + +

+ +
+ + + + +
+ +
+ 微信公众号已经成为生活的一部分了,虽然里面有很多作者只是为了蹭热点,撩读者的 G 点,自己从中获得一些收益;但是不乏好的订阅号,像刘大的码农翻身、Fenng的小道消息、曹大的caoz的梦呓等订阅号非常值得阅读。 +平时有时候看到一些好的公众号,也会不自觉去查看该公众号的历史文章,然而每次都看不完,下一次再从微信里面打开历史文章,又需要从头翻起。而且对于写了很多年的大号,每次还翻不到底。有一些平台提供了相关的服务,但是得收几十块钱的费用,倒不是缺几十块钱,主要是觉得这种没必要花的钱不值得去浪费。 +网上搜如何爬微信公众号历史文章,大致给了三种思路,第一是使用搜狗微信搜索文章,但是好像每次能搜到的不多;第二是使用抓包工具;第三种是使用个人订阅号进行抓取。 +简单来说就是使用程序来模拟人的操作,抓取公众号历史文章。首先登录微信公众号个人平台,期间需要管理员扫码才能登录成功。 +def __open_gzh(self): + self.driver.get(BASE_URL) + self.driver.maximize_window() + username_element = self.driver.find_element_by_name(&quot;account&quot;) + password_element = self.driver.find_element_by_name(&quot;password&quot;) + login_btn = self.driver.find_element_by_class_name(&quot;btn_login&quot;) + username_element.send_keys(USERNAME) + password_element.send_keys(PASSWORD) + login_btn.click() + WebDriverWait(driver=self.driver, timeout=200).until( + ec.url_contains(&quot;cgi-bin/home?t=home/index&quot;) + ) + # 一定要设置这一步,不然公众平台菜单栏不会自动展开 + self.driver.maximize_window() + +进入微信公众平台首页后,点击素材管理,然后点击新建图文素材,就会进入到文章写作页面,此时前面打开的微信公众平台首页就不需要了,可以将其关闭。 + +def __open_write_page(self): + management = self.driver.find_element_by_class_name(&quot;weui-desktop-menu_management&quot;) + material_manage = management.find_element_by_css_selector(&quot;a[title='素材管理']&quot;) + material_manage.click() + new_material = self.driver.find_element_by_class_name(&quot;weui-desktop-btn_main&quot;) + new_material.click() + # 关闭公众平台首页 + handles = self.driver.window_handles + self.driver.close() + self.driver.switch_to_window(handles[1]) + +在文章写作页面的工具栏上面有一个超链接按钮,点击超链接即会弹出超链接编辑框,选择查找文章,输入自己喜欢的公众号进行查找,一般第一个就是自己想要的结果,点击对应的公众号,该公众号所有的文章就会通过列表的形式展现出来。 + + + +def __open_official_list(self): + # 超链接 + link_click = self.driver.find_element_by_class_name(&quot;edui-for-link&quot;) + link_click.click() + time.sleep(3) + # 查找文章 + radio = self.driver.find_element_by_class_name(&quot;frm_vertical_lh&quot;).find_elements_by_tag_name(&quot;label&quot;)[1] + radio.click() + # 输入查找关键字 + search_input = self.driver.find_element_by_class_name(&quot;js_acc_search_input&quot;) + search_input.send_keys(OFFICIAL_ACCOUNT) + search_btn = self.driver.find_element_by_class_name(&quot;js_acc_search_btn&quot;) + search_btn.click() + # 等待5秒,待公众号列表加载完毕 + time.sleep(5) + result_list = self.driver.find_element_by_class_name(&quot;js_acc_list&quot;).find_elements_by_tag_name(&quot;div&quot;) + result_list[0].click() + +文章列表已经展现出来了,直接抓取每条文章超链接的信息即可,每抓取完一页就进入下一页,继续抓取文章列表信息,直到所有文章信息都抓取完毕。 + +def __get_article_list(self): + # 等待文章列表加载 + time.sleep(5) + total_page = self.driver.find_element_by_class_name(&quot;search_article_result&quot;)\ + .find_element_by_class_name(&quot;js_article_pagebar&quot;).find_element_by_class_name(&quot;page_nav_area&quot;)\ + .find_element_by_class_name(&quot;page_num&quot;)\ + .find_elements_by_tag_name(&quot;label&quot;)[1].text + total_page = int(total_page) + articles = [] + for i in range(0, total_page-1): + time.sleep(5) + next_page = self.driver.find_element_by_class_name(&quot;search_article_result&quot;)\ + .find_element_by_class_name(&quot;js_article_pagebar&quot;).find_element_by_class_name(&quot;pagination&quot;)\ + .find_element_by_class_name(&quot;page_nav_area&quot;).find_element_by_class_name(&quot;page_next&quot;) + article_list = self.driver.find_element_by_class_name(&quot;js_article_list&quot;)\ + .find_element_by_class_name(&quot; my_link_list&quot;).find_elements_by_tag_name(&quot;li&quot;) + for article in article_list: + article_info = { + &quot;date&quot;: article.find_element_by_class_name(&quot;date&quot;).text, + &quot;title&quot;: article.find_element_by_tag_name(&quot;a&quot;).text, + &quot;link&quot;: article.find_element_by_tag_name(&quot;a&quot;).get_attribute(&quot;href&quot;) + } + articles.append(article_info) + next_page.click() + return articles + +至此,微信公众号历史文章的爬虫已经实现,其实整个过程只不过是用程序来模拟的了人类的操作。需要注意的是,程序不能设置太快,因为微信做了相关限制,所以设太快会在一段时间内无法使用文章查找功能;另外一点是使用选择器选择页面元素的时候,会有一些坑,而且我发现不同账号登录,有很少部分的页面元素虽然直观上是一样的,但是它的 html 代码有细微的差别。 +这个小程序会用到selenium库,和chromedriver,前者直接pip install即可,后者自行下载;另外你还需要一个订阅号才行,本文只实现了关键的文章信息抓取,并没有进行文章信息的持久化存储,完整代码在这里。 + +
+ + Read More ~ +
+
+
+ +
+

+ + Scrapy 爬虫框架入门——抓取豆瓣电影 Top250 + +

+ +
+ + + + +
+ +
+ 最好的学习方式就是输入之后再输出,分享一个自己学习scrapy框架的小案例,方便快速的掌握使用scrapy的基本方法。 +本想从零开始写一个用Scrapy爬取教程,但是官方已经有了样例,一想已经有了,还是不写了,尽量分享在网上不太容易找到的东西。自己近期在封闭培训,更文像蜗牛一样,抱歉。 +Scrapy简介 +Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。 +其最初是为了 页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。 + +如果此前对scrapy没有了解,请先查看下面的官方教程链接。 +架构概览:https://docs.pythontab.com/scrapy/scrapy0.24/topics/architecture.html +Scrapy入门教程:https://docs.pythontab.com/scrapy/scrapy0.24/intro/tutorial.html +爬虫教程 +首先,我们看一下豆瓣TOP250页面,发现可以从中提取电影名称、排名、评分、评论人数、导演、年份、地区、类型、电影描述。 + +Item对象是种简单的容器,保存了爬取到得数据。其提供了类似于词典的API以及用于声明可用字段的简单语法。所以可以声明Item为如下形式。 +class DoubanItem(scrapy.Item): + # 排名 + ranking = scrapy.Field() + # 电影名称 + title = scrapy.Field() + # 评分 + score = scrapy.Field() + # 评论人数 + pople_num = scrapy.Field() + # 导演 + director = scrapy.Field() + # 年份 + year = scrapy.Field() + # 地区 + area = scrapy.Field() + # 类型 + clazz = scrapy.Field() + # 电影描述 + decsription = scrapy.Field() + +我们抓取到相应的网页后,需要从网页中提取自己需要的信息,可以使用xpath语法,我使用的是BeautifulSoup网页解析器,经过BeautifulSoup解析的网页,可以直接使用选择器筛选需要的信息。有一些说明写到代码注释里面去了,就不再赘述。 +Chrome 也可以直接复制选择器或者XPath,如下图所示。 + +class douban_spider(Spider): + + count = 1 + + # 爬虫启动命令 + name = 'douban' + + # 头部信息,伪装自己不是爬虫程序 + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36', + } + + # 爬虫启动链接 + def start_requests(self): + url = 'https://movie.douban.com/top250' + yield Request(url, headers=self.headers) + + # 处理爬取的数据 + def parse(self, response): + + print('第', self.count, '页') + self.count += 1 + + item = DoubanItem() + soup = BeautifulSoup(response.text, 'html.parser') + + # 选出电影列表 + movies = soup.select('#content div div.article ol li') + + for movie in movies: + item['title'] = movie.select('.title')[0].text + item['ranking'] = movie.select('em')[0].text + item['score'] = movie.select('.rating_num')[0].text + item['pople_num'] = movie.select('.star span')[3].text + + # 包含导演、年份、地区、类别 + info = movie.select('.bd p')[0].text + director = info.strip().split('\n')[0].split(' ') + yac = info.strip().split('\n')[1].strip().split(' / ') + + item['director'] = director[0].split(': ')[1] + item['year'] = yac[0] + item['area'] = yac[1] + item['clazz'] = yac[2] + + # 电影描述有为空的,所以需要判断 + if len(movie.select('.inq')) is not 0: + item['decsription'] = movie.select('.inq')[0].text + else: + item['decsription'] = 'None' + yield item + + # 下一页: + # 1,可以在页面中找到下一页的地址 + # 2,自己根据url规律构造地址,这里使用的是第二种方法 + next_url = soup.select('.paginator .next a')[0]['href'] + if next_url: + next_url = 'https://movie.douban.com/top250' + next_url + yield Request(next_url, headers=self.headers) + +然后在项目文件夹内打开cmd命令,运行scrapy crawl douban -o movies.csv就会发现提取的信息就写入指定文件了,下面是爬取的结果,效果很理想。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何抽取实体关系?——基于依存句法分析的事实三元组抽取 + +

+ +
+ + + + +
+ +
+ +参考: +HanLP 自然语言处理 +基于依存分析的开放式中文实体关系抽取方法 +命名实体三元组抽取参考自fact_triple_extraction + +这一段时间一直在做知识图谱,卡在实体关系抽取这里几个月了,在 Github 上面看到有人使用卷积神经网络训练模型进行抽取,自己也尝试了一下,但是一直苦于没有像样数据去训练,而标注训练集又太费时间了,我不太愿意干体力活。另外自己也不会什么机器学习、深度学习之类的技术,而且毕业设计都是有时间要求的,所以采用了一个低档次的方法,基于依存句法分析的实体关系抽取,记录一下心得,方便日后忘记可以再找回来。 +论文给出了 8 种中文关系的表达方式,并且最后给出了一个采用正则表达式语法指出表达,核心就是谓语动词表示关系,即关系表述中一定得有动词。 +状语*动词+补语?宾语? + +我不太赞同把宾语也当作关系表述的一部分,论文指出“p4生于山西”应该抽出(p4,山西,生于山西),我认为关系不应该表述为“生于山西”,所以我把关系表述改为下面的样子了。 +状语*动词+补语? + +这篇文章只是作为一个方法介绍,我自己先看了一遍,能够保证我下次看到这篇文章,可以立马回忆起自己的实现方法,希望你看了也能了解方法,看不懂的话,我表示抱歉,浪费您的时间了,我已经尽可能写到简单了。 +先来看几个简单句子吧: +主谓宾关系:刘小绪 生于 四川 +// 这个三元组很明显:(刘小绪,生于,四川) + + +动补结构:刘小绪 洗 干净 了 衣服 +// 如果套用主谓宾关系就是:(刘小绪,洗,衣服) +// 但是这里描述的是一个状态,是刘小绪把衣服洗干净了 +// “干净”是动词“洗”的补语,所以还应该提取出一个如下三元组 +// (刘小绪,洗干净了,衣服) + +状动结构:父亲 非常 喜欢 跑步 +// 这句和上面很像,主谓宾关系是:父亲喜欢跑步 +// “非常”用于修饰“喜欢” +// (父亲,非常喜欢,跑步) + +介宾关系:刘小绪 就职 于 学校 +// 如果直接把这个三元组抽取为(刘小绪,就职,学校),很别扭 +// “于”和“学校”是介宾关系,它们的关系应该是:就职于 +// (刘小绪,就职于,学校) + +宾语前置:海洋 由 水 组成 +// “海洋”是“组成”的前置宾语 +// “由”是“组成”的状语 +// “水”和“由”是介宾关系 +// 所以上面的句子没有明确的主谓关系,需要我们判断 +// 抽出的三元组应该为:(水,组成,海洋) + +HanLP 提供了两种依存句法分析的器,默认采用的是基于神经网络的依存句法分析器。依存句法分析就是将句子分析成一棵依存句法树,描述各个词语之间的依存关系,即指出词语之间在句法上的搭配关系。 +有了上面所说的依存句法树,其实我们只需要进行各种判断就可以了。先做出下面的一点说明,就拿第一个例子来说。 +原文:刘小绪生于四川 + +# 这是分词结果 +[刘小绪/nr, 生于/v, 四川/ns] + +#这是句法分析结果 +刘小绪 --(主谓关系)--&gt; 生于 +生于 --(核心关系)--&gt; ##核心## +四川 --(动宾关系)--&gt; 生于 + +为了方便理解,也为了方便程序的编写,我把他们组织成了下面的形式,为每一个词语都建一个依存句法字典。 +刘小绪:{} +生于:{主谓关系=[刘小绪], 动宾关系=[四川]} +四川:{} + +然后只需要写出类似于下面的程序段就可以抽出关系了。 +// 主谓宾关系:刘小绪生于四川 +// dic是这个词语的依存句法字典 +if (dic.containsKey(&quot;主谓关系&quot;) &amp;&amp; dic.containsKey(&quot;动宾关系&quot;)){ + + // 当前的词语,用上面的例子来说,relation=“生于” + String relation = curWord.LEMMA; + + + // 用循环遍历,是因为关系列表里面不一定只有一个词语 + for (CoNLLWord entity1: + dic.get(&quot;主谓关系&quot;)) { + + for (CoNLLWord entity2: + dic.get(&quot;动宾关系&quot;)) { + + System.out.println(entity1.LEMMA + &quot;,&quot; + relation + &quot;,&quot; + entity2.LEMMA); + } + + } +} + +对于分词后的每个词语都进行上面程序段的操作。“刘小绪”和“四川”,关系字典都为空。而对于“生于”,关系列表里面既有主谓也有动宾,而自己本身就是动词,主谓宾就出来了。直接从主谓关系中拿出来词语作为 entity1,再拿上自己作为关系,最后拿出动宾关系中的词语作为 entity2。很明确的三元组(刘小绪,生于,四川)就出来了。 +最后给出一个程序运行结果图吧。 + +我个人觉得效果还行,在简单句子上面表现的差强人意,在长句子上面表现的差劲。注意上文使用的第三方包随着时间的推移肯定会改一些接口,源码链接:entity_relation_extraction + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/EWmAbJCJGF/index.html b/EWmAbJCJGF/index.html new file mode 100644 index 00000000..6a61031c --- /dev/null +++ b/EWmAbJCJGF/index.html @@ -0,0 +1,1004 @@ + + + + + + + + TypeScript | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + TypeScript +
+ + +
+

+ + Vue 入门避坑——Vue + TypeScript 项目起手式 + +

+ +
+ + + + +
+ +
+ 在此前我使用的前端框架是 Angular,使用过 TypeScript 后你就会讨厌 JS 了,我学习 Vue 时的最新版本是 2.5,相信大部分同学都不会认为 Vue 那样又细又长的代码很美观吧,简单看了一些网络博客后,我毅然决然引入了 TypeScript 进行开发,本文仅整理记录我自己遇到的一些坑。 +使用 Cli +脚手架是一个比较方便的工具,这里需要注意的是@vue/cli和vue-cli是不一样的,推荐使用npm i -g @vue/cli安装。 +安装完成后,可以直接使用vue create your-app创建项目,你可以选择使用默认配置亦或是自己手动选择配置,按提示一步一步向下走即可,它会根据你的选择自己创建比如tsconfig.json等等配置文件。这里推荐使用less开发样式,sass老是在安装的过程中出问题。 +当然你也可以使vue ui命令启动一个本地服务,它是一个 Vue 项目管理器,提供了一个可视化的页面供你管理自己的项目,它的样子如下图所示,还是比较清新的。 + +使用 vue-property-decorator +Vue 官方维护了 vue-class-component 装饰器,vue-property-decorator 则是在vue-class-component基础上增强了更多结合Vue特性的装饰器,它可以让 Vue 组件语法在结合了 TypeScript 语法后变得更加扁平化。 +截止本文时间,vue-property-decorator共提供了 11 个装饰器和 1 个Mixins方法,下面用@Prop举个例子,是不是看起来引起极度舒适。 +import { Vue, Component, Prop } from 'vue-property-decorator' + +@Component +export default class YourComponent extends Vue { + @Prop(Number) readonly propA: number | undefined + @Prop({ default: 'default value' }) readonly propB!: string + @Prop([String, Boolean]) readonly propC: string | boolean | undefined +} + + +// 上面的内容将会被解析成如下格式 + +export default { + props: { + propA: { + type: Number + }, + propB: { + default: 'default value' + }, + propC: { + type: [String, Boolean] + } + } +} + +使用 Vuex +关于怎么使用Vuex此处就不再做过多说明了,需要注意的一点是,如果你需要访问$store属性的话,那么你必须得继承Vue类,坑的地方是在某些情况下即使你没有继承Vue,它也能通过编译,只有在程序运行起来的时候才报错。 +class ExampleApi extends Vue { + + public async getExampleData() { + if (!this.$store.state.exampleData) { + const res = await http.get('url/exampleData'); + if (res.result) { + this.$store.commit('setExampleData', res.data); + return res.data; + } else { + promptUtil.showMessage('get exampleData failed', 'warning'); + } + } else { + return this.$store.state.exampleData; + } + } +} + +使用自己的配置(含代理) +vue.config.js是一个可选的配置文件,如果项目的根目录中存在这个文件,那么它会被@vue/cli-service自动加载,它的配置项说明可以查看配置参考。 +我们再开发过程中都会使用代理来转发请求,代理的配置也是在这个文件中,它的官方说明在devserver-proxy中,下面是一个简单的vue.config.js文件例子。 +module.exports = { + filenameHashing: true, + outputDir: 'dist', + assetsDir: 'asserts', + indexPath: 'index.html', + productionSourceMap: false, + transpileDependencies: [ + 'vue-echarts', + 'resize-detector' + ], + devServer: { + hotOnly: true, + https: false, + proxy: { + &quot;/statistics&quot;: { + target: &quot;http://10.7.213.186:3889&quot;, + secure: false, + pathRewrite: { + &quot;^/statistics&quot;: &quot;&quot;, + }, + changeOrigin: true + }, + &quot;/mail&quot;: { + target: &quot;http://10.7.213.186:8888&quot;, + secure: false, + changeOrigin: true + } + } + } +} + +让 Vue 识别全局方法和变量 +我们在项目中都会使用一些第三方 UI 组件,比如我自己就使用了 Element,但是在使用它的$message、$notify等方法时就直接报错了,究其原因就是$message等属性并没有在 Vue 实例中声明。 +官方对此给出了很明确的解决方案,使用的是 TypeScript 的 模块补充特性,可以查看增强类型以配合插件使用。既然知道是因为没有声明导致的错误,那我们就给它声明一下好了,在src/shims-vue.d.ts文件中添加如下代码即可,如果没有该文件请自行创建。 + +看到网上也有一部分人说的是src/vue-shim.d.ts,反正不管是怎么命名这个文件的,它们的作用是一样的。 + +declare module 'vue/types/vue' { + interface Vue { + $message: any, + $confirm: any, + $prompt: any, + $notify: any + } +} + +这里顺道提一下,src/shims-vue.d.ts文件中的如下代码是为了让你的 IDE 明白以.vue结尾的文件是什么玩意儿。 +declare module '*.vue' { + import Vue from 'vue'; + export default Vue; +} + + +路由懒加载 +Vue Router 官方有关于路由懒加载的说明,但不知道为什么官方给的这个说明在我的项目里面都没有生效,但使用require.ensure()按需加载组件可以生效。 +// base-view 是模块名,写了相同的模块名则代码会被组织到同一个文件中 +const Home = (r: any) =&gt; require.ensure([], () =&gt; r(require('@/views/home.vue')), layzImportError, 'base-view'); + +// 路由加载错误时的提示函数 +function layzImportError() { + alert('路由懒加载错误'); +} + +上面的方式会在编译的时候把文件自动分成多个小文件,编译后的文件会以你自己命名的模块名来命名,如果代码之间有相互依赖,依赖部分代码编译后的文件会以两个模块名相连后进行命名。 +但是需要注意的是,这样拆分小文件之后引入了另外一个新的问题,因为客户端会缓存这些编译后的 js 文件,如果功能 A 同时依赖了a.js和b.js两个文件,但用户在使用其它功能时已经把a.js缓存到本地了,使用功能 A 时需要请求b.js文件,这时程序就很容易报错,因为此时在客户端这两个文件不是同一个版本,所以可能导致a.js调用b.js中的方法已经被删了,进而导致客户端页面异常。 +关于引入第三方包 +项目在引入第三方包的时候经常会报出各种奇奇怪怪的错误,这里仅提供我目前找到的一些解决办法。 +/* + 引入 jquery 等库可以尝试下面这种方式 + 只需要把相应的 js 文件放到指定文件夹即可 +**/ +const $ = require('@/common/js/jquery.min.js'); +const md5 = require('@/common/js/md5.js'); + +引入一些第三方样式文件、UI 组件等,如果引入不成功可以尝试建一个 js 文件,将导入语句都写在 js 文件中,然后再在main.ts文件中导入这个 js 文件,这个方法能解决大部分的问题。例如我先建了一个lib.js,然后在main.ts中引入lib.js就没有报错。 +// src/plugins/lib.js +import Vue from 'vue'; + +// 树形组件 +import 'vue-tree-halower/dist/halower-tree.min.css'; +import {VTree} from 'vue-tree-halower'; +// 饿了么组件 +import Element from 'element-ui'; +import 'element-ui/lib/theme-chalk/index.css'; +// font-awesome 图标 +import '../../node_modules/font-awesome/css/font-awesome.css'; +import VueCookies from 'vue-cookies'; +import VueJWT from 'vuejs-jwt'; + +Vue.use(VueJWT); +Vue.use(VueCookies); +Vue.use(VTree); +Vue.use(Element); + + +// src/main.ts +import App from '@/app.vue'; +import Vue from 'vue'; +import router from './router'; +import store from './store'; +import './registerServiceWorker'; +import './plugins/lib'; + +Vue.config.productionTip = false; + +new Vue({ + router, + store, + render: (h) =&gt; h(App), +}).$mount('#app'); + +因为第三方包写的各有特点,在引入不成功的时候基本也只能是见招拆招,当然如果你的功底比较深厚,你也可以自己写一个index.d.ts文件,实在不行的话,那个特殊的组件不使用 TypeScript 来写也能解决,我目前还没有找一个可以完全解决第三方包引入错误的方法,如果您已经有相关的方法了,希望能与你一起探讨交流。 + +
+ + Read More ~ +
+
+
+ +
+

+ + Bootstrap-table 如何合并相同单元格 + +

+ +
+ + + + +
+ +
+ Bootstrap-table 官方提供了合并单元格方法 mergeCells,它根据四个参数可以合并任意个单元格,我们要做的只是告诉它怎么合并。 +要合并同一列相同的单元格,无非两种办法,一种是一边遍历一边合并,遍历完了再合并。这里采用第二种办法,这里不需要遍历所有数据,因为用户只能看到当前页的数据,所以只遍历当前页的数据更省时间。 +下面是我实现的获取合并信息算法,最终返回的是一个哈希表,比如下面的这个表格,如果要对「性别」这一列进行合并,很明显前面两个“男”需要合并成一个单元格,再去看下 Bootstrap-table 提供的 API,它需要的是从哪个单元格开始,合并多少个单元格,也就是它需要的是两个数值类型的参数。 + + + +姓名 +性别 +年龄 + + + + +张三 +男 +23 + + +李四 +男 +19 + + +王二 +女 +20 + + +麻子 +男 +21 + + + +所以我把哈希表设置为,键存的是索引,值存的是从这个索引开始后面连续有多少个和它一样的单元格,那么上述表格性别这一列所得到的合并信息哈希表就为: +{ + 0: 2, + 2: 1, + 3: 1 +} + +下面算法很简单,使用两个指针遍历指定的列,如果两个指针所指向的数据相同,那么就将键所对应的值进行加一操作,整个方法只会对该列数据遍历一边,所以时间复杂度为 O(n)。 +let getMergeMap = function (data, index: number) { + let preMergeMap = {}; + // 第 0 项为表头,索引从 2 开始为了防止数组越界 + for (let i = 2; i &lt; data.length; i++) { + let preText = $(data[i-1]).find('td')[index].innerText; + let curText = $(data[i]).find('td')[index].innerText; + let key = i - 2; + preMergeMap[key] = 1; + while ((preText == curText) &amp;&amp; (i &lt; data.length-1)) { + preMergeMap[key] = parseInt(preMergeMap[key]) + 1; + i++; + preText = $(data[i - 1]).find('td')[index].innerText; + curText = $(data[i]).find('td')[index].innerText; + } + // while循环跳出后,数组最后一项没有判断 + if (preText == curText) { + preMergeMap[key] = parseInt(preMergeMap[key]) + 1; + } + } + return preMergeMap; +} + +上述算法得到了单列数据的合并信息,下一步就是按照这个信息进行相同单元格的合并了,因此封装了下面的方法按照指定哈希表进行合并。 +let mergeCells = function (preMergeMap: Object, target, fieldName: string) { + for (let prop in preMergeMap) { + let count = preMergeMap[prop]; + target.bootstrapTable('mergeCells', { index: parseInt(prop), field: fieldName, rowspan: count }); + } +} + +到目前为止,我们实现的都只是对单列数据进行合并,要实现对多列数据进行合并,那么只需要对所有列都进行相同的操作即可。 +export let mergeCellsByFields = function (data: Object[], target, fields) { + for (let i = 0; i &lt; fields.length; i++) { + let field = fields[i]; + // 保证 field 与 i 是相对应的 + let preMergeMap = getMergeMap(data, i); + let table = target.bootstrapTable(); + mergeCells(preMergeMap, table, field); + } +} + +因为我在程序中做了一点处理,保证了fields中每个值得索引与对应表头的索引是一样的,因此不需要额外传入索引信息。简单来说就是我所实现的表格会根据fields的顺序,实现列之间的动态排序。你需要注意的是这一点很可能和你不一样。 +到现在已经能够合并所有的列了,查看 Bootstrap-table 的配置信息发现,它有个属性是 onPostBody 它会在 table body 加载完成是触发,所以把这个属性配置成我们的合并单元格方法即可。 +// groups 为要合并的哪些列 +onPostBody: function () { + mergeCellsByFields($('#table' + ' tr'), $('#table'), groups); +} + +再说一点不太相关的,我实现的是让用户可以自己选可以合并多少列,即用了一个可多选的下拉列表框供用户选择,根据用户选择的数量去合并,所以传入了一个groups参数。 +最后推荐一个排序插件 thenBy,你可以用它进行多字段排序,比如用在合并相同单元格的场景,在绘制表格前先对数据进行排序,那么最后合并的结果就是把所有相同的数据聚合到一起了,并且还将它们合并到一起了,起到了一个隐形的过滤查询功能。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 学习 Angulr 容易忽略的知识点 + +

+ +
+ + + + +
+ +
+ +参考内容: +《Angulr5 高级编程(第二版)》 + +函数声明式和表达式 +// 第一种:函数声明式 +myFunc(); +function myFunc(){ + ... +} + +// 第二种:函数表达式 +myFunc(); +let myFunc = function(){ + ... +} + +虽然上面两种函数声明方式在大部分情况下是一样的,第一种可执行,第二种却不可以执行,这是因为浏览器在解析 js 时找到函数声明,并在执行剩余语句之前设置好函数,此过程称为函数提升,但是函数表达式却不会受到提升,因此无法正常工作。 +js 不具备多态性 +js 重不能创建名称相同但参数不同的两个函数,它不具备这个多态性,比如你定义的函数中有两个形参,调用函数时只传一个参数,第二形参的值就是 undefined ,如果传的参数大于 3 个,那么会自动忽略多余的参数。可以使用下列方法来处理函数定义参数数量和用于调用函数实际参数数量之间不匹配的问题。 +// 使用默认参数 +let func = function(age, sex='男'){ + ... +} +func(23); + +// 使用可变长参数 +let func = function(age, sex, ...extraArgs){ + ... +} +func(23, '女', '张三', '深圳'); +// 最后一个参数是一个数组,任何额外的实参都会被赋给这个数组 + +let 和 war 的区别 +使用 let 和 var 声明变量的区别,使用 let 声明变量会把变量的作用范围限定在它所在的代码区域内。而使用 var 所创建的变量的作用域是它所在的函数。 +function func(){ + if(false){ + var age = 23; + } +} + +// 上面的代码会被解析成下面的形式,使用 let 则不会出现这样的结果 + +function func(){ + var age; + if(false){ + age = 23; + } +} + +相等 == 和恒等 === 以及 连接操作符 + +相等操作符尝试将操作数强制转换为相同的类型,再评估是否相等,实质上相等操作符==是测试二者的值是否相等,而与二者的类型无关;如果要测试值和类型是否都相等则应该用恒等操作符===。 +5 == '5' // 结果为 true +5 === '5' // 结果为 false + +在 js 中,连接操作符的优先级高于加法操作,也就是说5 + '5'的结果是55。 +不同的模块指定方式 +import { Name } from &quot;./modules/NameUtil&quot;;// 第一种 +import { Compont } from &quot;@angular/core&quot;;// 第二种 + +上面两种导入模块的方式有所不同,第一种是相对模块,第二种是非相对导入。第一种告诉的 TypeScript 编译器,该模块所在的位置是相对于包含 import 语句的文件而言;第二种非相对导入,编译器会用 node_modules 文件夹中的 npm 包来解析它。 +如果在导入模块时,出现需要导入两个不同模块但是名字却相同的情况,可以使用as关键字给导入的模块取一个别名。 +import { Name as otherName } from &quot;./modules/Name&quot;;//取别名 + +还有一种方法是将模块作为对象导入,如下 import 所示,导入 Name 模块的内容,并创建一个名为 otherName 的对象,然后就可以使用该对象的属性了。 +import * as otherName from &quot;./modules/NameUtil&quot;; +let name = new otherName.Name(&quot;Admin&quot;, &quot;China&quot;);// Name 是 NameUtil 中的类 + +多类型和类型断言 +在 ts 中允许指定多个类型,使用字符|进行分隔。看下面的的方法,其功能是把华氏温度转换为摄氏温度。 +// 使用多类型,该函数可以传入 number 和 string 类型的参数 +static convertFtoC(temp: number | string): string { + /* + 尝试使用 &lt;&gt; 声明一个类型断言,将一个对象转换为指定类型,也可以使用 as 关键字实现下列相同的效果 + let value: number = (temp as number).toPrecision ? temp as number : parseFloat(temp as string); + */ + let value: number = (&lt;number&gt;temp).toPrecision ? &lt;number&gt;temp : parseFloat(&lt;string&gt;temp); + return ((parseFloat(value.toPrecision(2)) - 32) / 1.8).toFixed(1); +} + +元组是固定长度的数组,数组的每一项都是指定的类型;可索引类型可以将键与值关联起来,创建类似于 map 的集合。 +// 元组 +let tuple: [string, string, string]; +tuple = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]; + +// 可索引类型 +let cities: {[index: string] : [string, string]} = {}; +cities[&quot;Beijing&quot;] = [&quot;raining&quot;, &quot;2摄氏度&quot;]; + +数据绑定 +[target]=&quot;expr&quot;// 方括号表示单向绑定,数据从表达式流向目标; + +(target)=&quot;expr&quot;// 圆括号表示单向绑定,数据从目标流向表达式,用于处理事件的绑定; +[(target)]=&quot;expr&quot;// 圆方括号组合表示双向绑定,数据在表达式与目标之间双向流动; +{{ expression }}// 字符串插入绑定。 + +[] 绑定有很多不同的形式,下面介绍不同表现形式的效果。 +&lt;!-- + 标准属性绑定(dom对象有的属性),将 input 的 value 属性绑定到一个表达式的结果 + 因为 model.getProduct(1) 可能返回 null ,所以使用模板空条件操作符 ? 浏览返回结果 + 如果返回不为空,那么将读取 name 属性,否则由 null 合并操作符 || 将结果设置为 None + 字符串插入绑定也可以使用这种表达式 + --&gt; +&lt;input [value]=&quot;model.getProduct(1)?.name || 'None'&quot;&gt; + +&lt;!-- + 元素属性绑定,有时候我们需要绑定的属性在 DOMAPI 上面没有 + 可以使用通过在属性名称前加上 attr 前缀的方式来定义目标 + --&gt; +&lt;td [attr.colspan]=&quot;model.getProducts().length&quot;&gt; + {{ model.getProduct(1)?.name || 'None' }} +&lt;/td&gt; + +&lt;!-- 还有其他的 ngClass,ngStyle 等绑定,理解大体上和上面差不多 --&gt; + +内置指令 +&lt;!-- + ngIf指令,如果表达式求值结果为 true ,那么 ngIf 将宿主元素机器内容包含在 html 文件中 + 指令前面的星号表示这是一条微模板指令 + 组要注意的是,ngIf 会向 html 中添加元素,也会从中删除元素,并非只是显示和隐藏 + 如果只是控制可见性,可以使用属性绑定挥着样式绑定 + --&gt; +&lt;div *ngIf=&quot;expr&quot;&gt;&lt;/div&gt; + +&lt;!-- + ngSwitch指令, + --&gt; +&lt;div [ngSwitch]=&quot;expr&quot;&gt; + &lt;span *ngSwitchCase=&quot;expr&quot;&gt;&lt;/span&gt; + &lt;span *ngSwitchDefault&gt;&lt;/span&gt; +&lt;/div&gt; + +&lt;!-- + ngFor指令,见名知意,为数组中的每个对象生成同一组元素 + ngFor 指令还支持其他的一系列可赋给变量的值,有如下局部模板变量 + + index:当前对象的位置 + odd:如果当前对象的位置为奇数,那么这个布尔值为 true + even:同上相反 + first:如果为第一条记录,那么为 true + last:同上相反 + --&gt; +&lt;div *ngFor=&quot;let item of expr; let i = index&quot;&gt; + {{ i }} +&lt;/div&gt; + +&lt;!-- + ngTemplateOutlet指令,用于重复模板中的内容块 + 其用法如下所示,需要给源元素指定一个 id 值 + + &lt;ng-template #titleTemplate&gt; + &lt;h1&gt;我是重复的元素哦&lt;/h1&gt; + &lt;/ng-template&gt; + &lt;ng-template [ngTemplateOutlet]=&quot;titleTemplate&quot;&gt;&lt;/ng-template&gt; + ...省略若万行 html 代码 + &lt;ng-template [ngTemplateOutlet]=&quot;titleTemplate&quot;&gt;&lt;/ng-template&gt; + --&gt; +&lt;ng-template [ngTemplateOutlet]=&quot;myTempl&quot;&gt;&lt;/ng-template&gt; + +&lt;!-- + 下面两个指令就是见名知意了,不解释 + --&gt; +&lt;div ngClass=&quot;expr&quot;&gt;&lt;/div&gt; +&lt;div ngStyle=&quot;expr&quot;&gt;&lt;/div&gt; + +事件绑定 +事件绑定使用 (target)=&quot;expr&quot;,是单向绑定,数据从目标流向表达式,用于响应宿主元素发送的事件。 +当浏览器触发一个时间时,它将提供一个对象来描述该事件,对于不同类型的事件有不同类型的事件对象,事件对象被赋给一个名为$event的模板变量,但是所有事件对象都有下面三个属性: +type:返回一个 string 值,用于标识已触发事件类型; +target:返回触发事件的对象,一般是 html元素对象。 +timeStamp:返回事件触发事件的 number 值,用 1970.1.1 毫秒数表示。 + +下面举几个例子,作为理解帮助使用。 +&lt;!-- 当数鼠标在上面移动时,就会触发 mouseover 事件 --&gt; +&lt;td *ngFor=&quot;let item of getProducts()&quot; (mouseover)=&quot;selectedProduct = item.name&quot;&gt;&lt;/td&gt; + +&lt;!-- 当用户编辑 input 元素的内容时就会触发 input 事件 --&gt; +&lt;input (input)=&quot;selectedProduct=$event.target.value&quot; /&gt; + +&lt;input (keyup)=&quot;selectedProduct=product.value&quot; /&gt; +&lt;!-- 使用事件过滤,上面的写法按下任何一个键都会触发事件,而下面的写法只有回车事件才会触发事件 --&gt; +&lt;input (keyup.enter=&quot;selectedProduct=product.value&quot;) /&gt; + +表单验证 +Angular 提供了一套可扩展的系统来验证表单元素的内容,总共可以向 input表元素中添加 4 个属性,每个属性定义一条验证规则,如下所示: +required:用于指定必须填写值; +minlength:用于指定最小字符数; +maxlength:用于指定最大字符数,(不能在表单元素直接使用,因为它与同名的 H5 属性冲突); +pattern:该属性用于指定用户填写的值必须匹配正则表达式 + +&lt;!-- + Angular 要求验证的元素必须定义 name 属性 + 由于 Angular 使用的验证属性和 H5 规范使用的验证属性相同, + 所以向表单元素中添加 novalidate 属性,告诉浏览器不要使用原生验证功能 + ngSubmit 绑定表单元素的 submit 事件 + --&gt; +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +Angular 提供了 3 对验证 CSS 类,这些类可以用于样式化表单元素,向用户提供验证反馈,具体说明如下所示。 +ng-untouched ng-touched:如果一个元素未被用户访问,就将其加入到 nguntouched 类中;一旦访问就加入到 ngtouched 类中。 +ng-prisstine ng-dirty:元素内容没有被改变被加入到 ng-prisstine 类中,否则将其加入到 ng-dirty 类中。 +ng-valid ng-invalid:如果满足验证规则定义的条件,就加入到 ng-valid 类中,否则加入到 ng-invalid 类中。 + +在实际使用过程中,直接定义对应的样式即可,如下所示: +&lt;style&gt; +input.ng-dirty.ng-invalid{ + border: 2px solid red; +} +input.ng-dirty.ng-valid{ + border: 2px solid green; +} +&lt;/style&gt; +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +上面的验证方式无法给用户提供更加具体的信息,用户不知道应该做什么,可以使用 ngModel 指令来访问宿主元素的验证状态,当存在验证错误的时候,使用该指令向用户提供指导性信息。 +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + #nameRef=&quot;ngModel&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;ul class=&quot;text-danger list-unstyled&quot; + *ngIf=&quot;name.dirty &amp;&amp; name.invalid&quot;&gt; + &lt;li *ngIf=&quot;name.errors?required&quot;&gt; + you must enter a product name + &lt;/li&gt; + &lt;li *ngIf=&quot;name.errors?.pattern&quot;&gt; + product name can only contain letters and spases + &lt;/li&gt; + &lt;li *ngIf=&quot;name.errors?minlength&quot;&gt; + &lt;!-- + Angular 表单验证错误描述属性 + required:如果属性已被应用于 input 元素,此属性返回 true + minlength.requiredLength:返回满足 minlength 属性所需的字符数 + minlength.actualLength:返回用户输入的字符数 + pattern.requiredPattern:返回使用 pattern 属性指定的正则表达式 + pattern.actualValue:返回元素的内容 + --&gt; + product name must be at least {{ name.errors.minlength.requiredLenth }} characters + &lt;/li&gt; + &lt;/ul&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +如果在用户尝试提交表单时就显示大量的错误信息,给人的体验感就会很差,所以可以让用户提交表单时再验证整个表单,示例代码如下所示。 +export class ProductionCompont { + // ...省略若万行代码 + formSubmited: boolean = false; + + submitForm(form: ngForm) { + this.formSubmited = true; + if(form.valid) { + this.addProduct(this.newProduct); + this.newProduct = new Product(); + form.reset(); + this.formSubmited = true; + } + } +} + +&lt;form novalidate #formRef=&quot;ngForm&quot; (ngSubmit)=&quot;submitForm(formRef)&quot;&gt; + &lt;div *ngIf=&quot;formsubmited &amp;&amp; formRef.invalid&quot;&gt; + there are problems with the form + &lt;/div&gt; + &lt;!-- 禁用提交按钮,验证成功提交按钮才可用 --&gt; + &lt;button [disabled]=&quot;formSubmited &amp;&amp; formRef.valid&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +fromSubmited 属性用于指示表单是否已经提交,并将用于在用户提交整个表单之前阻止表单验证。当用户提交表单时,调用 submitForm 方法,并将 ngForm 对象作为实参传入,ngForm 提供了 reset 方法,该方法可以重置表单的验证状态,使其返回到最初的未访问状态。 +更高级的还有使用基于模型的表单验证,可以自行查阅相关资料。 +使用 json-server 模拟 web 服务 +因为json-server会经常用到,建议使用全局安装命令npm install -g json-server。因为开发后端的同学太慢了,而我们如果要等他们把接口都提供给我们的时候再开发程序的话,那效率就太低了,所以使用 json-server 来模拟后端服务。只需要建好一个 json 文件,比如下面的格式: +{ + &quot;user&quot; : [ + { + &quot;name&quot; : &quot;张三&quot;, + &quot;number&quot; : &quot;1234&quot;, + }, + { + &quot;name&quot; : &quot;王二&quot;, + &quot;number&quot; : &quot;5678&quot;, + } + ], + &quot;praise&quot;: [ + {&quot;info&quot;:&quot;我是一只小老虎呀!&quot;}, + {&quot;info&quot;:&quot;我才是大老虎&quot;} + ] +} + +启动服务使用命令json-server [你的 json 文件路径],然后就可以根据提示访问了,你甚至可以使用http://localhost:3000/user?number=5678去过滤数据。这样就能模拟 web 服务,而不必等后端同学的进度了。 +解决跨域请求问题 +Angular 跨域请求问题可以通过 Angular 自身的代理转发功能解决,在项目文件夹下新建一个 proxy.conf.json 并在其中添加如下内容。 +// 可以通过下列配置解决 +&quot;/api&quot;: { + &quot;target&quot;: &quot;http://10.9.176.120:8888&quot;, +} + +在启动时使用npm start,或者使用ng serve --proxy-config proxy.conf.json,Anular 中的/api请求就会被转发到 http://10.9.176.120:8888/api,从而解决跨域请求问题。 +使用第三方 js 插件 +共有三种方式引入第三方插件,第一种很简单,直接在 html 中引入插件就可以了;第二种在angular.json中进行配置;第三种在 ts 文件中使用 import 导入库即可。 +// 第一种(需要重启服务) +&quot;scripts&quot;: [&quot;src/assets/jquery-3.2.1.js&quot;,&quot;src/assets/jquery.nicescroll.js&quot;,&quot;src/assets/ion.rangeSlider.js&quot;] + +// 第二种 +&lt;script type=&quot;text/javascript&quot; src=&quot;assets/jquery-3.2.1.js&quot;&gt;&lt;/script&gt; +&lt;script type=&quot;text/javascript&quot; src=&quot;assets/jquery.nicescroll.js&quot;&gt;&lt;/script&gt; + +// 第三种 +import &quot;assets/jquery-3.2.1.js&quot;; +import &quot;assets/jquery.nicescroll.js&quot;; +import &quot;assets/ion.rangeSlider.js&quot;; + +深拷贝与浅拷贝 +深拷贝与浅拷贝是围绕引用类型变量说的,其本质区别是不可变性,基本类型是不可变得,而引用类型是可变的。 +直接使用赋值操作符,就是浅拷贝,如果对拷贝源进行操作,会直接影响在拷贝目标上,因为这个赋值行为本质是内存地址的赋值,为了获得与拷贝源完全相同但又不会影响彼此的对象就要使用深拷贝。 +let objA = { + x: 1, + y: -1 +} +let objB = objA; +objA.x++; +console.log(&quot;objA.x:&quot;+objA.x, &quot;objB.x:&quot;+objB.x); +//打印结果如下: +objA.x : 2 +objB.x : 2 + +Typescript 提供了一种方法来实现引用类型的深拷贝,即Object.assign(target, ...source),此方法接受多个参数,第一个参数为拷贝目标,剩余参数为拷贝源,同名属性会进行覆盖。 +let objA = { + x: 1, + y: -1, + c: { + d: 1, + } +} +let objB = {}; +Object.assign(objB, objA); +objA.x++; +console.log(&quot;objA.x:&quot;+objA[&quot;x&quot;], &quot;objB.x:&quot;+objB[&quot;x&quot;]); +//打印结果如下: +objA.x : 2 +objB.x : 1 + +需要注意的是,Typescript 提供的深拷贝方法不能实现嵌套对象的深拷贝,会出现下面的情况。 +let objA = { + x: 1, + y: -1, + c: { + d: 1, + } +} +let objB = {}; +Object.assign(objB, objA); +objA.c.d++; +console.log(&quot;objA.c.d:&quot;+objA[&quot;c&quot;].d, &quot;objB.c.d:&quot;+objB[&quot;c&quot;].d); +//打印结果如下: +objA.c.d : 2 +objB.c.d : 2 + +要实现嵌套对象的深拷贝,可以使用 JSON 对象提供的方法,JSON 对象提供了两个方法,分别为:stringify()和parse(),前者将对象 JSON 化,后者将 JSON 对象化,使用这种方式可以实现嵌套深拷贝,但是也有缺点:破坏原型链,不能拷贝属性值为 function 的属性。 +let objA = { + a: 1, + b: { + c: 1 + } +} +let objB = JSON.parse(JSON.stringify(objA)); +objA.b.c++; +console.log(&quot;objA.b.c:&quot;+objA.b.c, &quot;objB.b.c:&quot;+objB.b.c); + +//打印结果如下: +objA.b.c:2 +objB.b.c:1 + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/EkpumV6IX/index.html b/EkpumV6IX/index.html new file mode 100644 index 00000000..6ba4d1e1 --- /dev/null +++ b/EkpumV6IX/index.html @@ -0,0 +1,541 @@ + + + + + + + + 那些鲜为人知的微信使用技巧,让微信更便捷高效 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 那些鲜为人知的微信使用技巧,让微信更便捷高效 +

+ + +
+ +
+

本文整理的技巧基于两种目的,一种是实用方便用来解决生活中的小问题的,另外一种是用来装逼去撩小妹妹的,可能大多数人都更喜欢装逼撩妹的技巧吧。

+

被人@时掉表情包

+

在微信群内聊天时如果发送的消息触发了某个关键词就会掉下来表情包,为聊天增添了一些乐趣。我们可以通过人为的方式让别人一@你,就自动掉表情包。操作起来很简单,就是在群聊昵称末尾添加一些不同的字符,比如我在群聊昵称末尾加了「คิดถึง」顶部就掉下来了满屏的星星✨。

+
+

其实把这个泰文翻译一下就知道没有那么神秘了,微信内置的翻译功能将「คิดถึง」解释为「想念」,这下就明白为什么会掉星星✨了吧。「สุขสันต์วันเกิด」解释为「生日快乐」就会掉蛋糕,其它还有很多可以自己翻译一下就可以了。

+

同一条朋友圈别人评论/点赞不再提示

+

如果某个朋友发了一条朋友圈,你在下面评论或是点赞了,那么当你们的共同好友也对同一条朋友圈点赞或评论时,就会在「发现」有消息提示,但很多时候我们并不想要这样的提示,那怎么办呢?点到朋友圈的消息页中,iOS 向左滑动(安卓长按)就可以看到有个「不再通知」按钮,点击之后共同好友再点赞或评论就不会收到提示啦!

+
+

用好微信内置的代办事项

+

微信内置的代办事项功能估计没多少人知道,微信无疑是我们打开频率最高的 APP,把代办事项放在微信就更不容易忘事了。

+
+

可以看到它被置顶在微信的首页界面,每次启动微信都能看到,非常的方便,那么如何把代办事项置顶呢?
+第一步:打开微信 > 我 > 收藏 > 右上角➕号 > 右下角图标 > 代办

+
+

第二步:右上角三个点 > 左下角在聊天中置顶

+
+

重要事件提醒

+

我们经常会在微信上答应别人一些事情,为此可能去找一些清单软件来提醒自己,问题是清单软件又不常打开,常常会导致事情的忘记,给别人留下一个言而无信的形象。其实微信自身就已经带了提醒功能,长按任何一条消息,上面有一个提醒按钮,点击设置提醒时间,到了提醒时间后,会在「服务通知」中有推送的提醒。

+

改善公众号阅读体验

+

微信公众号阅读文章的体验不佳,不支持分组,也不支持进行上一次阅读,这时可以选择公众号任意一篇文章,点击右上角之后找到「在微信读书中打开」,如果没有这个功能,可能需要升级微信或者下载微信读书 App。

+
+

建立一个人的微信群

+

怎么建立只有一个人的微信群呢?选择 -> 添加朋友 -> 面对面建群 -> 输入数字 -> 建群完成。这个只有自己的群就相当于一个信息分组,可以把自己平时的想法、待阅读的文章、写作灵感等发到里面,就自己一个人看,而且还可以对微信群进行置顶操作,不会受到其他微信群信息的影响,处理完的信息也可以像删聊天记录一样删掉,当然也可以选择发给文件助手或者自己。

+

使用微信编辑图片

+

我们有时候为了别人能更方便的理解自己想表达的意思,或者图片上的某些位置不想被别人看到,可以在添加图片的时候选择「预览」,就可以对图片进行编辑了。最实用的就是斗图的时候,对方一张图过来,如果手里没有合适的图片,可以直接基于对方的图片进行编辑,怼回去。

+

利用微信收藏快速拼图

+

选择「收藏」,在里面添加图片,然后选择右上角三个...的按钮,之后选择保存为图片,这样你添加的图片就拼接为了一张长图了,非常方便的操作,有的朋友喜欢分享聊天记录,就可以这么拼接。

+
+

善用「搜一搜」

+

微信的「搜一搜功能很强大,比如有一天你无意在朋友圈看到一条信息,当时没有发现它的价值,过了一段时间因为其他事,突然恍惚记起了以前某条朋友圈提到过相关的情况,想查一查。这时就可以通过「搜一搜」来快速查找了。除此之外,「搜一搜」还能搜索文章,方便你快速查看与某个主题相关的文章;以及表情包搜索等功能,在斗图的时候不会再为没有表情包而烦恼。

+
+

僵尸粉检测

+

相信很多人都遇到过别人给你发一条消息,上面注明是为了清僵尸粉的,但是这样会打扰到绝大部分微信好友,也给别人留下很不好的印象,我很反感收到别人的清粉消息。如果你怀疑某个人删除了你,你试着转账测试一下就可以了。

+
+

推荐几个小程序

+

微信发票助手,经常出差的朋友或者行政小姐姐,常遇到的问题是在开公司发票的时候,需要填写一些信息,一般有记不住的,可以使用这个小程序填写好,之后每次需要的时候打开就好了,或者别人需要的时候,你分享二维码给他就可以了。发票信息填完后,会在「个人信息」底部多一个「我的发票抬头」,很方便的哦。

+
+

微信指数,有时候可能会做一些调查,比如写文章的朋友,文章标题到底是用“旅游”还是“旅行”好呢?这个时候就可以用微信指数来做调查,可以查看不同词汇之间的热度。

+
+

文章截图,IOS 用户比较苦恼的应该就是截长图了吧,有人就专门做了一个小程序,只需要把文章的链接复制过去,就可以自动生成截图,还可以生成 PDF 文件哦。

+
+

蚂蚁清单,很多人都有使用待办事项管理软件,但是有时候又忘记打开,这时不妨把它放到日常打开频率最高的微信里面去,而且还能为手机节省一点空间,蚂蚁清单的体验很好,可以自己去尝试。

+
+

一条人生经验

+

最后说一条人生经验吧,敏感和违法不和谐的话题不要聊。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/FoK6YM8MY/index.html b/FoK6YM8MY/index.html new file mode 100644 index 00000000..9ac119ce --- /dev/null +++ b/FoK6YM8MY/index.html @@ -0,0 +1,465 @@ + + + + + + + + 大叔激励|小宫黛雅|上饶熊孩子|法国小羊做新生|体育强健身心|地震回忆 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 大叔激励|小宫黛雅|上饶熊孩子|法国小羊做新生|体育强健身心|地震回忆 +

+ + +
+ +
+

最近机缘巧合和几个中年大叔聊天,从眼神里面能看出来他们是讲的真心话,平时也非常反感长辈们给自己讲的一些大而空的道理,但仔细想想这些话其实并不是完全没用。

+

不管对方是世俗眼光中的成功人士还是失败人士,他们都会有自己没有实现的理想愿望,成功人士没有好好陪家人孩子,他们可能喜欢特地找个时间,不管接收方是否感动,但至少他自己已经得到心灵的慰藉了;失败人士没有给到家人足够富裕的生活,这时候他们都很喜欢看努力奋斗自强不息......一类词汇堆砌的文章。而老人的精神世界已经不是奋斗了,而是人这一生......,所以如果陪老人说话,你只需要把话题引入到佛身上,然后静静的听就可以了。

+
+

忘了啥时候无意中加了一个高中生,目前正在读高二,与大多数人不同的是,她属于双性恋人群,并且更偏爱小姐姐,出口成脏,上课偷偷玩手机,宛然一个大家脑中所构想的问题少年。

+

但是几个月前突然变了,一下子变得有礼貌起来了,我问她是因为什么变了,她告诉我是因为我说的一句话:不说脏话是对人起码的尊重。这句话让她想明白了,我在这里偷偷说一句,我也不知道当时说的这句话对不对,其实就是随口一说,让一个小娃娃变好了,那也好。

+

在我的循序诱惑下,小姑娘已经不藏手机了,每周日自觉的把手机交上去,每周五手机发下来再玩,所以我和她的聊天记录现在基本上变成了,周日我发一句“加油”,周五下午我会收到一句“突然出现”。

+

小姑娘自己是同性恋的事情不敢给爸妈说,我想这是不是因为父母与孩子天然就有一种屏障,以至于无法与他们进行心灵上的沟通,要想能够得到孩子的信任,父母应该多站在孩子角度考虑考虑问题,尽量不要拿「我都是为你好」去搪塞孩子。

+
+

最近上饶杀熊孩子案很火,女生父亲杀人当然是不对对,但是对于这种校园霸凌这件事,校方与男生家长都摆出无所谓的态度,才导致悲剧的发生,其实校园霸凌的核心不在熊孩子身上,而是在熊家长身上。

+

吴军在其《大学之路》上有写道:“在我的印象中,父母晚上从来不参加应酬,甚至不看什么电视剧,总是非常有规律的学习,我的母亲现在快 80 岁了,依然每天坚持学习,父母们并不知道,他们在对我们兄弟的教育上最成功之处,是以他们的行为潜移默化地影响了我们,让我们渐渐养成了终身学习的习惯”。借句公益广告词——父母是最好的老师。

+
+

最近一个很搞笑的新闻,法国一所小学为了拯救学校不被关闭,招了 15 只羊作为新生。简单来说就是,根据法国政策,当学生数量少于 261 时,学校将被迫停办,而法国人在抗议上也很有创意,反正规定上又没有写招生的物种必须是人,正好我们家有许多只适龄绵羊,都送去上学吧!

+

牧民说到做到,这周二就和校长及家长达成一致,带领着自己家50只绵羊赶到学校招生办公室报道,招生办的老师热情的接待了羊以及送羊报道的牧羊犬们,在检查了所有绵羊的出生证后,最终有15只年龄在3-6岁之间的适龄绵羊顺利通过合法注册,成为小学的一年级新生,让这所小学不至于被停学。

+
+

我们常常把体育锻炼与健身、减肥挂钩,前段时间杭州程序员小伙伴突然精神崩溃,引发了一大波人关注,在校大学生跳楼也是常有的事,但是去细细看一下,是不是很少会看到体育生跳楼。

+

大学生跳楼无非是意志力脆弱、心理自我调节能力差等方面影响的,而体育锻炼恰恰会无意中去缩短了这些短板,我主要不是想说体育锻炼有多么多么好,因为这是大家都知道的,而是想说一件事的作用范围可能比你想象的要大的多。

+
+

今天是母亲节,无意中突然记起了初中的一篇阅读理解——《那支枯萎的康乃馨》,读者大人可以去搜搜读读,从另一个角度看看母亲节送礼这件事,不知道现在的朋友圈孝子还多不多。

+

同样今天是 5.12 汶川地震纪念日,地震发生时我还在读 6 年级,记得整个床都摇的快要倒了,房子上面的瓦片蹭蹭的往下掉,但是学生中间没有一个害怕了,原因只是因为我们并不知道地震这个词。

+

去年的 5.28 日,吉林松原发生了一次小的地震,哈尔滨有震感,作为也算经历过地震的人,自然异常的敏感,熟睡中的我一下就惊醒了,立刻意识到地震了,然后在那三四秒的时间里面,时间好像停住了一样,我都数不清那几秒时间脑海里滑过了多少记忆,我体会到了面对死亡的感觉,几秒过后,根据汶川地震的经验,本次只是小地震,所以我又躺下睡觉了,然后室友们都跑出去了。

+
+

出来工作也有大半年了,之前以为第一份工作是学习技术的,现在才发现第一份工作首先应该学习的是做事态度,在学校如果某件事不想做或者太困难,那么完全可以选择不做,但是在企业不行,一些恶心的事情必须有人得去做,学生总是缺乏应有的责任感,但责任感是优秀员工的基本条件。

+

我之前在公司怼过两次领导,怼完领导之后他反倒对我更好了,给我解决问题的速度超快,现在回想起来可能自己实际上做错了,为啥不能心平气和的去解释,而采取暴力沟通,今后我也要学习怎么做一个圆滑的俗人。

+

保持一个虚心的学习态度是异常重要的,认清自己资质平平的现实。一些人在公司认为经理是个傻逼,总监是个马屁精,董事长屁股决定脑袋,那不过是因为你没到那个位置,没办法理解他们考虑问题的角度而已。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/G1rwEVR-s/index.html b/G1rwEVR-s/index.html new file mode 100644 index 00000000..83b57ff6 --- /dev/null +++ b/G1rwEVR-s/index.html @@ -0,0 +1,563 @@ + + + + + + + + MongoDB 聚合(aggregate)入门 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ MongoDB 聚合(aggregate)入门 +

+ + +
+ +
+

MongoDB 聚合官方文档

+

聚合管道是一个基于数据处理管道概念建模的数据聚合框架,文档进入一个多阶段的处理管道,该管道最终将其转换为聚合后的结果。

+

下面的例子来源于官方文档。第一阶段,$matchstatus字段来过滤文档,并把status字段值为A的文档传递到下一阶段;第二阶段,$group将文档按cust_id进行分组,并针对每一组数据对amount进行求和。

+
db.orders.aggregate([
+   { $match: { status: "A" } },
+   { $group: { _id: "$cust_id", total: { $sum: "$amount" } } }
+])
+
+

管道

+
+

聚合管道包含很多步骤,每一步都会将输入的文档进行转换,但并不是每个阶段都一定需要对每个输入文档生成一个输出文档,比如某些阶段可能生成新的文档或者过滤掉文档。

+

除了$out$merge$geoNear外,其它的阶段都可以在管道中多次出现,更加详细的内容可以查看 Aggregation Pipeline Stages

+
+

管道表达式

+

一些管道阶段采用表达式作为操作元,管道表达式指定了要应用到输入文档的转换,表达式自己是一个文档结构(JSON),表达式也可以包含其它的表达式。

+

表达式仅提供文档在内存中的转换,即管道表达式只能对管道中的当前文档进行操作,不能引用来自其他文档的数据。

+

写聚合表达式式建议直接参考官方文档,下面列出一些我收集的案例,供深入理解使用。

+

案例一:将对象数组转换为单个文档

+
// 转换前
+{
+    "_id": "10217941",
+    "data": [
+        {
+            "count": 2,
+            "score": "0.5"
+        },
+        {
+            "count": 6,
+            "score": "0.3"
+        },
+        {
+            "count": 5,
+            "score": "0.8"
+        }
+    ]
+}
+
+// 转换后
+{
+    "_id": "10217941",
+    "0.3": 6,
+    "0.5": 2,
+    "0.8": 5
+}
+
+

需要说明的是,如果上面data属性中的数据格式为{"k": "0.6", "v": 5},那么下面的聚合表达式就不需要$map,这一点可以查看 $arrayToObject。这个案例的难点在于score中有小数点,这个小数点会让聚合表达式懵逼的。

+
db.collection.aggregate([
+    {
+        "$addFields": {
+            "data": {
+                "$arrayToObject": {
+                    "$map": {
+                        "input": "$data",
+                        "as": "item",
+                        "in": {
+                            "k": "$$item.score",
+                            "v": "$$item.count"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    {
+        "$addFields": {
+            "data._id": "$_id"
+        }
+    },
+    {
+        "$replaceRoot": {
+            "newRoot": "$data"
+        }
+    }
+]);
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/HoPoXlgLN/index.html b/HoPoXlgLN/index.html new file mode 100644 index 00000000..b5d21ced --- /dev/null +++ b/HoPoXlgLN/index.html @@ -0,0 +1,289 @@ + + + + + + + + 线性稳压器 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 线性稳压器 +
+ + +
+

+ + LDO 基础知识 + +

+ +
+ + + + +
+ +
+ +参考内容: +ADI 公司 LDO 电容选型指南 +线性和低压降 (LDO) 稳压器 + +BUCK 电路通过控制占空比来达到降压的目的,添加 LC 二阶低通滤波器将高频部分滤除,即可达到稳定输出直流的目的。但是滤波不能完全滤除高频分量,BUCK 从原理上就决定了其纹波不容易做到很小,其固有的开关频率会导致电源噪声很大,用来给噪声敏感的元器件供电就不合适。 +相比 BUCK 来说,LDO(Low Dropout Regulaor:低压差线性稳压器)输出的电压会更加平稳,可以弥补 BUCK 输出纹波大的缺点。 +总体框图 +线性稳压器主要由四部分组成,基准源用于提供精准的电压基准、导通器件用于控制从 VIN 到 VOUT 的电流大小、误差放大器将强制反馈节点与基准电压匹配、反馈电阻用于调整以改变输出电压。 + +从框图中也可以看到线性稳压器只能用于降压,因此输入电压必须高于输出电压。当然其名字中本身带了低压差的,低压差就意味着少的发热,意味着电源转化效率的提升。线性则是指器件的工作状态,器件的内部模块工作在放大区,放大状态呈线性关系。 +工作原理 +线性稳压器的工作可以模拟为两个电阻器和一个用于 VIN 的电源,其中电源用于给负载供电,通过调整可变电阻(导通器件)的阻值来控制负载电阻所获得的电压,整个系统中唯一不变的恒定的参数就是输出电压 VOUT。 + +其稳压过程如下图所示,当负载电压升高/降低时,采样电路所采到的电压就跟着升高/降低,传递给误差放大器后通过调节导通器件的导通程度来调节输出电压。 + +导通器件 +导通器件常见的有 PMOS、NMOS、BJT 等。BJT 应用于大电流的场景。PMOS 不需要额外的电源轨即可控制其导通程度,但是相比 NMOS 其 RDSon 更大,即 PMOS 架构的 LDO 在芯片本身所消耗的能量会更大。 + +使用 NMOS 作为导通器件时,需要添加辅助电源轨或者使用电荷泵才能将 NMOS 打开。当然电荷泵也有其缺点,虽然电荷泵可以提升 VIN,但是也带来了额外的噪声影响。若采用辅助电源轨时则需要注意,VBIAS 会影响 NMOS 的导通程度,进而影响输出电压的大小。 + +PSRR +PSRR(Power Supply Rejection Ratio)量化了 LDO 抑制任何电源变化传递到其输出信号的能力,也就是 PSRR 决定了输入耦合到输出的噪声有多少。除了 LDO 本身的设计影响 PSRR 外,也可以通过调整 VIN 与 VOUT 之间的差值、输出电容来提高在特定应用(频率)下的 PSRR。 + + +输入输出电容 +为了确保 LDO 稳定工作,会在 LDO 输入输出端增加旁路电容,并且旁路电容的 ESR 需要很小,即在符合最小电容和最大 ESR 的要求下,使用任何质量良好的电容都可采用。在选择电容时还需要注意由于直流电压偏置、温度变化、制造商容差等需要对电容进行一定的降额。 +输出电容除了可以进行滤波外,还会影响负载电流的变化的瞬态响应,采用较大的输出电容可以改善 LDO 对大负载电流变化的瞬态响应。输入电容则可以降低电路对 PCB 布局的敏感性,尤其是在长输入走线或者高源阻抗的情况下。 +多层陶瓷电容、固态钽点解电容、铝电解电容通常用作输入和输出旁路电容。多层陶瓷电容具备 ESR 和 ESL 低、工作温度范围宽的优点,但是陶瓷电容中的介质材料具备压电性,振动或机械冲击可能会转化为电容上的交流噪声电压,在极端情况下可能会产生 mV 级的噪声。 + +压电性是在某些固体材料(晶体、陶瓷、骨头、DNA、蛋白质等)受到机械应力作用后,在材料中聚集电荷的现象。「压电」即由压力产生的电。 + +钽电容的优点是单位体积电容最高(CV 乘积),并且不太容易受到温度、偏执电压、震动效应的影响,在无法容忍压电效应的低噪声应用中,钽电容基本是唯一可行的选择。与陶瓷电容相比,钽电容的泄漏电流要比等值的陶瓷电容大很多倍,不适合超低电流应用。 +铝电解电容往往体积较大、ESR 和 ESL 较高,漏电流相对较高,与钽电容一样不受压电效应影响,适合要求低噪声的应用场合,但是铝电解电容在航天应用中禁止使用。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/K8GZGQszm/index.html b/K8GZGQszm/index.html new file mode 100644 index 00000000..03d3c775 --- /dev/null +++ b/K8GZGQszm/index.html @@ -0,0 +1,459 @@ + + + + + + + + 认知如何提升?我是怎样转变自己思维方式的 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 认知如何提升?我是怎样转变自己思维方式的 +

+ + +
+ +
+

这仅仅是记录自己的一点经历和感悟,回顾一下自己思维的转变过程而已,如果对于还是学生或是初入职场的你有一点帮助,那也是没白写的。

+

相信很多朋友都还记得有段时间因为华为 34 岁以上员工被裁、中兴程序员跳楼等事件的发生,各种蹭热点讨论「中年危机」的文章漫天飞,一时间各种割韭菜教如何利用副业赚钱的课程也层出不穷,那时我正好是大四,忙于找工作。

+

很清晰的记得当时一个程序员微信群里面大家各种讨论中年危机,都在给自己制造焦虑,刚好群里有个大神可能出于善意,看大家过于焦虑,就在群里发了几条消息,教大家如何避免中年危机,并且推荐了两本书。

+

还是学生的我下意识的就发了一个添加好友请求,虽然内心是非常希望对方能够通过好友请求的,但当时很清楚能和这样的人微信交流是一种奢求,而意外的是他居然同意了我的好友请求,当然限于我个人的水平我们没有什么交流,我的问题过于浅显,会浪费人家时间,当时的想法是看看大神的朋友圈,他平时都接触什么,自己学习一段时间。

+

大神推荐的书是李笑来写的《把时间当做朋友》、《财富自由之路》,那时的心态还是宁愿花 300 块钱出去吃一顿饭,也不愿意花几十块钱买一本书,所以我第一时间跑到学校图书馆去查了,但是两本书在学校图书馆都没有,我就给学校图书馆荐购系统提交了这两本书,图书馆效率也挺高,不到一周就把书给买回来了,我立马就借回来阅读。

+

书中的内容刷新了我以前狭隘的认知,从偏远农村出来的自己从来没有像书中那样考虑问题,除了对作者的佩服之外,更多的是思考自己这种学生思维局限性太大了,要慢慢的将它摒弃。

+

有个定律是你关心什么就会来什么,后面陆续碰到几位像大一样的人士,并加了他们的微信,但是都仅仅是通过他们朋友圈的蛛丝马迹去找知识,通过他们朋友圈的分享内容,我知道了「简七理财」、「码农翻身」、「程序员小灰」(我知道的时候还不叫程序员小灰)公众号,然后知道了《富爸爸穷爸爸》、《小狗钱钱》,通过微信读书,读完了这两本书,逐渐培养了理财理念。

+

后来没隔多久,简七出书了,我第一时间就买了她写的《好好赚钱》(同期还有刘大也出了《码农翻身》一书,我也第一时间买了),简七写的内容通俗易懂,很容易理解。

+

刘大在群里开了几次公开课,作为计算机专业的我,被刘大对技术的理解之深给折服了,那段时间正是业界浮躁的时候,成千上万人想着人工智能、大数据、区块链,而刘大一直能沉下心来去了解技术的原理,这给了我一个很好的榜样,我也逐渐沉下心来,开始去补最基础的知识,像《深入理解计算机系统》一类书也能尽下心来慢慢去把它啃完了,这种不浮躁的特质对我的技术成长是很有帮助的。

+

此后有一天,另一个大神在朋友圈分享了曹大写的《从校园到职场系列文章》,喜欢深入挖掘信息的我,以曹大公众号为源头,又找到了冯大、池大、二爷、刘备教授、大牛猫等人的公众号。

+

作为自由的大四学生,因为不用担心第二天起不来,我那段时间经常熬夜阅读他们的文章,在阅读的过程中我也开始思考自己此前哪些想法狭隘,哪些品质又是值得继续保持的。

+

也是那时我开始接受知识付费的,那时候已经有小密圈(现在叫知识星球)了,出于对几位大佬的信任,我第一次大胆的花了几百块钱加入了刘大、曹大、冯大、程序员小灰的小密圈,其中的内容比网上蹭热点的文章好不知多少倍,一贯爱捕捉蛛丝马迹的我,又通过评论信息发现了 angela zhu、子柳老师、陈利人老师等,然后去找他们的文章,他们输出的内容要比水军写的文章好太多。

+

自己也是从那时候开始坚持写文章记录自己的心得的,通过写文章,也认识了很多优秀的人,比如了不起的杰克、java 小咖秀等公众号的作者,和他们交流的很少,但是却很受用,他们的积极向上也影响着我一直保持着乐观豁达的心态。

+

自己写的文章也被几个资深程序员赞同,同时还收到了两个出版社发来的出书邀请,让我体会到了无心插柳柳成荫的收获,选择了和电子工业出版社签了出版合同。

+

让我坚持一直写文章的动力不是赚钱,而是我切切实实体会到了它给我个人带来的成长,为了自己日后再看时能立刻就找到清晰的逻辑,我把都尽可能把文章写得有理有据,掌握自己的节奏,尽量提高文章质量。谈一下写作的重要性一文有说写作带来的好处。

+

现在已经不把自己当新人了,而且有同龄人甚至比我年龄还大的人向我咨询问题时,我也能给出合理建议,都得到了他们的肯定。最近发现和周围伙伴最明显的一个区别就是,对于同一个新闻,我经常早于他们半天甚至一两天知道,而且掌握的信息比他们还准确,我认为这就是整体认知水平的提升。

+

最后想说,执行力与信息素养很重要,执行力强的人会与你拉开越来越大的距离,信息素养也是一个关键品质,现在网络上充斥着大量的虚假信息,如何去分别这些信息的真假,在相同条件下如何获得更多的有效信息,是必备的能力。

+

上面提到的各路大神,他们的文章都很值得阅读,做一个终身学习的人,时刻保持学习的态度。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/KCICs8Iip/index.html b/KCICs8Iip/index.html new file mode 100644 index 00000000..a46b7d4f --- /dev/null +++ b/KCICs8Iip/index.html @@ -0,0 +1,384 @@ + + + + + + + + 记录回忆 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 记录回忆 +
+ + +
+

+ + 家里的狗 + +

+ +
+ + + + +
+ +
+ 为了防止晚上有人来家里偷东西,几乎家家户户都至少会养一只狗。在我的记忆中,我家一开始是没有狗的。 +忘记是哪一年夏天的一个清晨,天还没有大亮,我隐约看见在牛棚后面的空地有个黑影,走近一点仔细一看,原来是一只不知道从哪里来的一只黑狗。 +它惊恐的看着我,眼神中夹杂着恐惧与无助,佝偻的身子比弓还要弯,倒是很像一个活着的牛轭。他的身子还没有草高,露水把全身的毛都打湿了,还沾着一些不知名的植物种子。我和它对视着,恐惧慢慢填满了它的眼球,我害怕吓到它,赶紧走开去告诉妈。 +妈远远看了一眼,让我别管它。随后妈把装着昨晚剩饭的猪食瓢放到牛棚后面的一块石头上,黑狗看见妈带着武器走近早就跑了,我吃早饭时还不时去望望它在不在,有没有吃妈给放在那里的饭。 +妈已经把猪喂完准备下地干活了,仍旧没有再次发现黑狗的踪影,也没见猪食瓢有什么变化,我心里有一点点的失落,黑狗应该是已经逃走了吧。 +晚上吃完饭妈去拿猪食瓢,告诉我里面的饭已经被吃的一粒不剩,我心里开始期待和它的再次见面。第二天早晨果然见到它了,身上已经没有昨天那么湿了,显然没有前一天来这里时钻的草丛多,妈依旧用猪食瓢装着米饭和米汤放在牛棚后的那个石头上。 +就这样过了几日,黑狗走进了我家的屋檐,它的样子实在太丑了。每一根肋骨都清晰的扎眼,看起来爸的手指都比它的小腿粗,感觉下一秒它就会死去。 +我并不喜欢它,甚至还有些讨厌它,我实在找不到更丑的词来形容它,不过是出于心里的怜悯与对生命的敬畏,会在吃饭的时候给它丢几个我不吃的肥肉,被烟熏黑的那一层肉边我也丢给它...... +有一次同村的一个人路过家门口时,看见那只黑狗吓的赶紧往妈身后躲。“有我在,它不敢咬。”,妈说。邻居夸夸妈说:“这个狗儿喂得好肥”。妈自豪的告诉那个人这只狗每天还送林儿(我)上学。 +是的,我也不知道什么时候我已经和大黑狗变得如此亲密了,它每天早上会把我送到山顶的学校,我每天下午回家做完作业会和它一起到田间追逐。在学校也常常会给同学们说大黑狗胸前的那长成了“人”字的一片白毛,我一直相信“人”字是老天爷特地印在它身上,用来告诉我大黑狗是他派来的使者。 +大黑狗来我家时已经很老很老了,是我读三年级的某一天,它像往常一样把我送到学校,但是我下午回家却不见它的踪影,一直等到晚上都没有见它回来。那些天我放学回家第一件事就是朝我和它常去的那些地方大声的唤它。 +不到一个月后的一天早晨,像大黑狗第一次来我家附近时的场景一样,湿漉漉的身子带着些杂草种子,不同的是它身旁还跟着一只背部有些黑毛的小黄狗,小黄狗胸前也有一个很明显的“人”字。我赶紧去用猪食瓢盛满饭放在它面前,它吃了几口就又走了。 +就这样,大黑狗离开了我,给我留下了一只小小的黄奶狗。我不知道它是去找它原来的主人去了,还是觉得自己老了,不愿意让我看见它倒下的样子,反正它就是再也没有回来过。 +小黄狗长成了大黄狗,我对这只大黄狗的印象很浅,只记得爸妈把这只黄狗送给了外婆家附近的亲戚,我们留下了它生的一只小黄狗。外婆知道我们把大黄狗送人,还狠狠的批评了爸妈,说自己来家里的狗不能送人。 +自然小黄狗很快就长成了大黄狗,我像以前一样也偷偷给大黄狗吃肉,逐渐开始懂事的妹妹也会背着爸妈给它肉吃,我和妹都会夹几片我们压根就不吃的肥肉,离开饭桌假装是到外面吃饭,实际上是给大黄狗送肉去了。 +我到离家 30 多公里的镇上读高中,每个月才回家一次。每次离家大黄狗都会送我到集市去赶车,我会在寒暑假的黄昏和它到新修的公路去追逐,带它去它自己一个人不敢去探索的地方。 +上大学后和大黄狗相处的时间更少了,听爸妈说它会经常跑到外婆家,外婆好吃好喝的招待它,招呼都不打一声就又跑回来了。还经常和邻居家的狗到麦子地打闹,要把一大片麦子弄倒才肯回家。 +每学期回家在离家还有四五百米的地方都会听到它的吠叫,因为它把我当陌生人了。但是只要我大喊一声,它就会立刻停止吠叫,飞奔到我这里,兴奋的往我身上爬,把它的前爪往我身上搭;我努力不让它碰到我的衣服,然而每次到家时我都带着一身泥巴做的狗爪印。 +现在大黄狗已经 10 多岁了,它就像大黑狗当年送我一样每天送我妹上学。我也已经走入职场开始工作,待在家里的时间更少了,我不知道它还能活多久,生怕哪次爸妈打电话时会给我说大黄狗死了,只要爸妈没有在电话中提及大黄狗,我都是非常开心的,因为那就代表着它依旧健健康康的活着。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 匆匆的岁月——成都七中网校远端学生的高中生活 + +

+ +
+ + + + +
+ +
+ 周三刷星球时看到一条关于成都七中网校的信息,没想到周四就被成都七中网校给刷屏了,看到文章里面的描写,感悟颇多,网校陪伴了自己三年,一个诗意的女孩——西凉忆就是因为网校结识的。 +我是 12 年入学高中,那一年学校也刚和成都七中开通网校,因此我属于学校第一届全程使用网校资源的学生。记得刚入学时,对这种教学方式并不适应。不止学生不适应,老师也不适应,政治课老师就不使用直播课程,而是选择自己给我们讲。不过后来年级组出了硬性规定,特优班必须使用直播课程。 +就像教育的水平线中描述的一样,我们被称为远端学生,大家都是第一次使用这样的系统,七中老师估计也很好奇,都在第一节课或者第二节课上抽了远端同学回答问题,后来就很少有抽远端同学回答问题了,估计是因为远程视频效果不好还浪费时间。 +成都七中本部的学生上课很有激情,一到讨论等环节,虽然很吵但是却很有秩序,而我们这边基本是大家都盯着屏幕,等待对方讨论结束。 +对方学生的基础我们是没办法比的,大部分能够完全以纯英文交流,而我们远端学生大部分都只能说出“My name is ..., I'm from ..., I like ...”,英语的差距是最大的。我自己是主动找了英语老师谈话,和她做了一个约定,每周我写一篇作文交给她,她帮我批改,这样坚持了两年。 + +让我感受到最大差距的是,一个中学有自己的电视台,经常会有像《汉语桥》冠军一类的人物前来演讲,美国第一夫人米歇尔也到七中演讲(那是总统还是奥巴马),作为远端学生有幸蹭了一场名人演讲;七中学生的寒暑假是参加联合国模拟大会、到哈佛做了简短的交流、到华盛顿旅行...... +而大部分远端的学生,要么是参加学校组织的补课,要么是在家干农活,基本连县城都没有走出去过,和七中相比,完全是天壤之别。 +现在我依旧还清晰的记得七中的几个老师,做 PPT 从来没有背景图的数学老师,语速超快但又吐字清晰的化学老师,说着一口标准川普的物理老师,有着一头蓬松金黄卷发的历史老师(男)......去看这些老师的背景,也都是名校出身,武汉大学、华中师大等等。 + +有一个细节记得很清楚,在一堂数学课中有个题目,题目具体内容忘了,只记得是要求算新华中学的一本录取率,最后的答案是在 74% 左右,这个数字在我眼里很高了,但是那一刻并没有觉得有什么,毕竟这是书上的学校嘛!! +想不到的是,下一秒那个做 PPT 从来没有背景图的数学老师说了句:“这个就比较低了啊,我们学校考的很差的时候才是百分之七十多的录取率”。一下让我震惊了,因为我在心里算过自己学校的一本录取率,在此之前不到 10%,而且我所在高中在所有远端学校中还是比较靠前的。 + +让我意外的是,七中的老师原来也会骂人、打人,即使打的很轻;学生没完成作业,也会被罚拿个小凳子到教室外面补作业;在全国禁止补课的规定之下,七中也会给高三的学生补课,当然我们也同步补课了。 +我无法构想如果三年没有使用网校资源会是神马结果,如果仅仅是看数据的话,一本率是按倍数翻的,12 年开始使用网校资源后,学校有了第一个清华,13 年又添了一个清华。我属于 14 届,这届没有清华,最好的是浙大,我进了个普普通通的哈尔滨工程大学。据说 15 届又出了清华。 +我所在的高中也被央视作为典型案例探讨农村高考。无疑能上央视,那这个学校在当地乃至全国同等水平的学校中是很成功的。 +无疑这种生活给每个同学都留下了难忘的记忆,如果哪位同学加了七中本部直播班级某位同学的 QQ(那时没有人用微信),那能吹好几天牛逼,七中人在我们眼里就像明星一样。 +我们当地 14 届县状元高考分数是 635 分,七中本部平均分是 616 分,这差距至今都让我目瞪口呆。前段时间曹大在星球发起了一个作业:十年后的期望目标。我所提交的作业中写了句,如果有能力,希望能给乡村学校带去一点教育资源。 +我并不认为穷是光荣的事情,但在很多农村人眼里穷是一种资本,一种获取国家福利的资本,如果某次补助没有评到自己头上,那得记恨村长一辈子。我认为这才是造成农村孩子和城里孩子有巨大差距的原因,如果孩子没有从这种思维走出来,那一辈子也不可能有什么大的成就。 +没想到自己都大学毕业了,却看到成都七中的网校被刷屏了。毫无疑问,这是一件极为有意义的事情,这种内容就应该被刷屏,愿七中和远端学校都越办越好。 + +文中照片取自于同学 QQ 空间,记得我原来还有英语老师 Spring 的 QQ,高中时向他请教过自己的英语问题,太久没联系后来 Spring 把我删了 =_=,反正是没了。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 记录在南京大学半天 + +

+ +
+ + + + +
+ +
+ 因为工作需要,到南京出差了半个月,中间利用周末和最好的朋友疯了一天,之后自己又一个人到南京大学鼓楼校区逛了逛。 + +不会勾搭妹子的我总是能勾搭到老爷爷,到南大就勾搭了一个 86 岁高龄的老教授,他毕业于中山大学,年轻时候是做地质工作的。 +我就像个熊孩子一样要爷爷给我讲有趣的故事,要听他讲我们这一代人或者是大部分人都不知道的历史。 +爷爷虽然已经是快到耄耋之年的人了,但是对年轻时候的事记得很清楚,只是对最近的事记不起来。这篇文章仅仅是记录一下爷爷所讲的趣事。 +爷爷年轻时候接到中科院的任务,前往内蒙古考察。在考察期间他们用汽车压死过一只狼,而且当时吃了狼肉,一行 30 多个人都吃过那匹狼的心,但是没有吃过狗肺。 +据爷爷说,狼是很狡猾的动物,他们用汽车去追狼,狼就在原地不跑,等到你离它只有 10 来米的时候,突然拐弯跑了,这样的情况他们一共遇到了 6 次。这和《狼图腾》一书中的描写基本一致,狼有先进的军事文化。 + +爷爷告诉我,南大起源于金陵大学,南京大学的标志性建筑「北大楼」是个教堂的样子,金陵大学本来是个教会大学,现在的「北大楼」就是原来的「钟楼」。 +南大的地下有隧道,是当年毛主席提倡「深挖洞、广积粮、不称霸」时挖的,目的是为了防空。后来被南京食品公司用来存放香蕉,就是那种没有熟的香蕉,在隧道里面放熟了,再拿出来卖。不过现在隧道所有的口都没堵上了,完全废弃了。 +在南大,有一些楼中间有 5 层,然后到两遍就只有 3 层了,整体看来像是个三角形。实际上这些楼当年都是要修 8 层的,因为那时候没钱,建着建着发现没资金了,所以就封顶了。 +但是南大计算中心那栋楼只有 3 层却不是因为没钱,而是因为它旁边是消防大队,本来也是要建 8 层的,消防队说建高了挡住了他们视线,不能及时发现火情。爷爷笑着对我说:“但是也没见他们在上面拿个望远镜望啊!”。 +我们都知道「五四运动」,但是却很少有人知道「四五运动」,这个运动的起源就在南大,当时 300 多学生(我回来查资料说是 400)发起了这个运动,后来演变为全国性的运动,直接带动了半年后四人帮被粉碎。 +那是爷爷是个老师,他说他们教职工是很支持这些学生的,但是不敢公开性的支持。学生们很聪明,把标语刷到火车上,但是所有出南京的火车都被四人帮用水把标语给冲刷掉了,学生们就用沥青往火车上面写,才通过火车把这项运动的信息带到了全国各地。 + +我回来后查了一点资料,「四五运动」的起源是因为周恩来总理的去世,四人帮居然压制人民群众悼念周恩来,诬陷邓小平,而那时的毛主席也已经病到无法行动。 + + +人们把花圈都放到人民英雄纪念碑前悼念周总理,却被四人帮给清理了,北京广大人民群众在“还我花圈,还我战友”的口号下行成了天安门广场大规模的群众抗议运动。 + + +那也是一个诗意的年代,人们通过写诗来表达自己心中的愤怒,把小瓶子挂在树上,蕴意着期待邓小平的归来。那段时间四人帮应该是很难过的,从姚文元的日记就可以看出来。 + +爷爷还给我讲了一点他们的研究,他们研究行政规划的很多人认为,中国现在的行政划分有很多缺点的,中国应该划分 50~80 个省级单位。现在中国的行政级别也有问题,宪法规定行政层级只有三级(这一点我没查),而现在很多地方县下面是镇,镇下面还有乡,严格讲这是违宪的。 +快到午饭时间时,爷爷还教我写了一会儿字,有的简体字很难看,比如「龍飛鳳舞」用繁体字写出来很好看,但是用简体字写出来就特难看。要想练好毛笔字,把三个字写好了就行了,然而我现在只记得一个“飛”字了,这可能就是老师们常说的「你又还给我了」。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/KRW8MVjMZ/index.html b/KRW8MVjMZ/index.html new file mode 100644 index 00000000..fd2ca553 --- /dev/null +++ b/KRW8MVjMZ/index.html @@ -0,0 +1,573 @@ + + + + + + + + 如何求两个数的最大公约数 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 如何求两个数的最大公约数 +

+ + +
+ +
+

求几个整数的最大公约数大致有三种方法,求多个整数的最大公约数可以拆分为求两个整数的最大公约数,所以核心问题还是求两个整数的最大公约数。

+

穷举法

+

很直观就能想到穷举法,先找出两个数字中比较小的那一个min,然后逐个验证从2 ~ min的数字是否能被两个数整除,如果能同时被两个数字整除那就是公约数,找出其中最大的那个公约数就是所求的结果。

+
int gcd(int a, int b){
+    int min = a;
+    if(b < a){
+        min = b;
+    }
+    for(int i = min; i > 2; i--){
+        if(a%i == 0 && b%i == 0){
+            return i;
+        }
+    }
+    return 1;
+}
+
+

辗转相除法

+

辗转相除法是欧几里得想出来的,所以也叫做欧几里得算法。它的证明过程依赖于一个定理:两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数,即gcd(a, b) = gcd(b, a mod b),其中 gcd 表示最大公约数,此处假设 a > b。其证明过程如下所示:

+
设 c = gcd(a, b);
+则存在 m,n,使 a = mc,b = nc;
+令 r = a mod b;
+则存在 k,使 r = a - kb = mc - knc = (m - kn)c;
+所以 gcd(b, a mod b) = gcd(b, r) = gcd(nc, (m-kn)c) = gcd(n, m-kn)c;
+所以 c 为 b 与 a mod b 的公约数;
+
+设 d = gcd(n, m-kn);
+则存在 x,y,使 n = xd,m-kn = yd;
+所以 m = yd + kn = yd + kxd = (y + kx)d;
+所以 a = mc = (y + kx)dc,b = nc = xdc;
+所以 gcd(a, b) = gcd((y+kx)dc, xdc) = gcd(y+kx, x)dc = dc;
+因为 gcd(a, b) = c,所以 d = 1;
+即 gcd(n, m-kn) = 1,所以  gcd(b, a mod b) = c;
+所以 gcd(a, b) = gcd(b, a mod b);
+
+证明 gcd(y+kx, x)dc = dc,即 gcd(y+kx, x) = 1:
+前提条件:gcd(x, y) = 1;
+假设 gcd(y+kx, x) != 1,则肯定 gcd(y+kx, x) > 1,设 gcd(y+kx, x) = i;
+则 y+kx = ui,x = vi;
+则 y = ui - kx = ui - kvi = (u-kv)i
+则 gcd(x, y) = gcd(vi, (u-kv)i) = gcd(v, u-kv)i
+因为 gcd(y+kx, x) = i > 1,gcd(v, u-kv) >= 1;
+所以 gcd(x, y) > 1,与前提条件矛盾;
+所以 gcd(y+kx, x) = 1
+
+

有了上面的基础之后,我们就可以总结出来一个算法实现的步骤了。设 r = a % b;如果 r 为 0 的话,那么 a 和 b 的最大公约数就是 b,否则就是求 b 和 a%b 的最大公约数。

+
// 递归写法
+int gcd(int a, int b){
+    // 用 b 来存储 a%b 的值
+    if(b == 0){ 
+        return a;
+    }
+    return gcd(b, a%b);
+}
+
+// 迭代写法
+int gcd(int a, int b){
+    while(b != 0){
+        int t = b;
+        a = t;
+        b = a % b;
+    }
+    return a;
+}
+
+

可以看到在算法实现过程中并没有先找出来最小的数字,这是因为程序会自动将最较大的那个数字放到 a 的位置,比如将gcd(75, 1000)带入我们的递归算法中则会变成gcd(1000, 75)

+

辗转相减法

+

辗转相减法也叫更相减损术(尼考曼彻斯法),也是一种简便的求两个数的最大公约数的算法,它的特色是做一系列减法,从而求的最大公约数。比如两个自然数 36 和 27,用大数减去小数得 9 和 27,这时 9 小于 27,需要将两数交换即得 27 和 9,继续相减可得 18 和 9,然后 9 和 9,这时就可以得到两数的最大公约数为 9 了。其证明过程如下所示:

+
设 gcd(a, b) = x,a > b;
+则有 a = mx,b = nx,m,n 均为正整数且 m > n;
+c = a - b = mx - nx = (m - n)x;
+因为 a 和 b 均为正整数,所以 c 也能被 x 整除;
+所以 gcd(a, b) = gcd(b, a-b)
+
+

具体的算法实现步骤在第一段已经有一个比较清晰的例子了,这里可以直接给出实现代码。

+
// 递归写法
+int gcd(int a, int b){
+    if(a == b){
+        return a;
+    }
+    return a > b ? gcd(a-b, b) : gcd(a, b-a);
+}
+
+// 迭代写法
+int gcd(int a, int b){
+    while(a != b){
+        a > b ? a = a - b : b = b - a;
+    }
+    return a;
+}
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/L3EbniCYD/index.html b/L3EbniCYD/index.html new file mode 100644 index 00000000..04e57312 --- /dev/null +++ b/L3EbniCYD/index.html @@ -0,0 +1,529 @@ + + + + + + + + LDO 基础知识 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ LDO 基础知识 +

+ + +
+ +
+
+

参考内容:

+

ADI 公司 LDO 电容选型指南

+

线性和低压降 (LDO) 稳压器

+
+

BUCK 电路通过控制占空比来达到降压的目的,添加 LC 二阶低通滤波器将高频部分滤除,即可达到稳定输出直流的目的。但是滤波不能完全滤除高频分量,BUCK 从原理上就决定了其纹波不容易做到很小,其固有的开关频率会导致电源噪声很大,用来给噪声敏感的元器件供电就不合适。

+

相比 BUCK 来说,LDO(Low Dropout Regulaor:低压差线性稳压器)输出的电压会更加平稳,可以弥补 BUCK 输出纹波大的缺点。

+

总体框图

+

线性稳压器主要由四部分组成,基准源用于提供精准的电压基准、导通器件用于控制从 VIN 到 VOUT 的电流大小、误差放大器将强制反馈节点与基准电压匹配、反馈电阻用于调整以改变输出电压。

+
+

从框图中也可以看到线性稳压器只能用于降压,因此输入电压必须高于输出电压。当然其名字中本身带了低压差的,低压差就意味着少的发热,意味着电源转化效率的提升。线性则是指器件的工作状态,器件的内部模块工作在放大区,放大状态呈线性关系。

+

工作原理

+

线性稳压器的工作可以模拟为两个电阻器和一个用于 VIN 的电源,其中电源用于给负载供电,通过调整可变电阻(导通器件)的阻值来控制负载电阻所获得的电压,整个系统中唯一不变的恒定的参数就是输出电压 VOUT。

+
+

其稳压过程如下图所示,当负载电压升高/降低时,采样电路所采到的电压就跟着升高/降低,传递给误差放大器后通过调节导通器件的导通程度来调节输出电压。

+
+

导通器件

+

导通器件常见的有 PMOS、NMOS、BJT 等。BJT 应用于大电流的场景。PMOS 不需要额外的电源轨即可控制其导通程度,但是相比 NMOS 其 RDSon 更大,即 PMOS 架构的 LDO 在芯片本身所消耗的能量会更大。

+
+

使用 NMOS 作为导通器件时,需要添加辅助电源轨或者使用电荷泵才能将 NMOS 打开。当然电荷泵也有其缺点,虽然电荷泵可以提升 VIN,但是也带来了额外的噪声影响。若采用辅助电源轨时则需要注意,VBIAS 会影响 NMOS 的导通程度,进而影响输出电压的大小。

+
+

PSRR

+

PSRR(Power Supply Rejection Ratio)量化了 LDO 抑制任何电源变化传递到其输出信号的能力,也就是 PSRR 决定了输入耦合到输出的噪声有多少。除了 LDO 本身的设计影响 PSRR 外,也可以通过调整 VIN 与 VOUT 之间的差值、输出电容来提高在特定应用(频率)下的 PSRR。

+
+
+

输入输出电容

+

为了确保 LDO 稳定工作,会在 LDO 输入输出端增加旁路电容,并且旁路电容的 ESR 需要很小,即在符合最小电容和最大 ESR 的要求下,使用任何质量良好的电容都可采用。在选择电容时还需要注意由于直流电压偏置、温度变化、制造商容差等需要对电容进行一定的降额。

+

输出电容除了可以进行滤波外,还会影响负载电流的变化的瞬态响应,采用较大的输出电容可以改善 LDO 对大负载电流变化的瞬态响应。输入电容则可以降低电路对 PCB 布局的敏感性,尤其是在长输入走线或者高源阻抗的情况下。

+

多层陶瓷电容、固态钽点解电容、铝电解电容通常用作输入和输出旁路电容。多层陶瓷电容具备 ESR 和 ESL 低、工作温度范围宽的优点,但是陶瓷电容中的介质材料具备压电性,振动或机械冲击可能会转化为电容上的交流噪声电压,在极端情况下可能会产生 mV 级的噪声。

+
+

压电性是在某些固体材料(晶体、陶瓷、骨头、DNA、蛋白质等)受到机械应力作用后,在材料中聚集电荷的现象。「压电」即由压力产生的电。

+
+

钽电容的优点是单位体积电容最高(CV 乘积),并且不太容易受到温度、偏执电压、震动效应的影响,在无法容忍压电效应的低噪声应用中,钽电容基本是唯一可行的选择。与陶瓷电容相比,钽电容的泄漏电流要比等值的陶瓷电容大很多倍,不适合超低电流应用。

+

铝电解电容往往体积较大、ESR 和 ESL 较高,漏电流相对较高,与钽电容一样不受压电效应影响,适合要求低噪声的应用场合,但是铝电解电容在航天应用中禁止使用。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/M4PlaWNdk/index.html b/M4PlaWNdk/index.html new file mode 100644 index 00000000..221844e1 --- /dev/null +++ b/M4PlaWNdk/index.html @@ -0,0 +1,452 @@ + + + + + + + + Oracle | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + Oracle +
+ + +
+

+ + Oracle 安装及 Spring 使用 Oracle + +

+ +
+ + + + +
+ +
+ +参考内容: +docker安装oracle数据库史上最全步骤(带图文) +Mac下oracle数据库客户端 +Docker安装Oracle +docker能安装oracle吗 +Batch script for add a auto-increased primary key for exist table with records + +Docker 安装 Oracle11g +注意:下列安装方式仅适用于x86架构服务器,不适用于arm架构服务器。 +# 拉取 oracle11,镜像有点大,需要花一些时间 +docker pull registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g + +# 查看镜像是否拉取成功 +docker images + +# 给镜像重新打 tag,原来的名字太长了 +docker tag registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g oracle11g:latest + +# 启动 oracle11g 容器 +docker run --name=oracle11g -itd -p 1521:1521 + +# 进入容器进行配置 +docker exec -it oracle11g /bin/bash + +# 切换到 root 用户,密码为:helowin +su root + +# 编辑配置文件 + +编辑/etc/profile,在其中增加如下内容: +export ORACLE_HOME=/home/oracle/app/oracle/product/11.2.0/dbhome_2 +export ORACLE_SID=helowin +export PATH=$ORACLE_HOME/bin:$PATH + +编辑完成后,需要刷新上述环境变量才能使用。 +# 刷新环境变量 +source /etc/profile + +# 创建软链接 +ln -s $ORACLE_HOME/bin/sqlplus /usr/bin + +# 切换到 oracle 用户 +su - oracle + +# 登陆 sqlplus +sqlplus /nolog +conn /as sysdba + +# 修改 system 用户密码 +alter user system identified by system; +# 修改 sys 用户密码 +alter user sys identified by system; + +# 创建内部管理员账号 +create user test identified by test; + +# 将 dba 权限授权给内部管理员账号和密码 +grant connect,resource,dba to test; + +# 修改密码规则策略为密码永不过期 +ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED; + +# 修改数据库最大连接数据 +alter system set processes=1000 scope=spfile; + +修改上述信息后,需要重新启动数据库才会生效。 +conn /as sysdba + +# 关闭数据库 +shutdown immediate; + +# 启动数据库 +startup; + +# 退出软链接 +exit; + +客户端连接 Oracle +以 Navicat 客户端为例,新建连接时按下图方式填写连接信息即可,密码即为system。需要注意的是,在 Windows 下选择 SID 或是服务名均可连接成功,但是在 Mac 下需要选择 SID 方式才能连接成功。 + +Oracle 实现主键自增 +Oracle 在创建表的时候,不能像 MySQL 那样选择主键直接自增,但是我们可以通过给表创建序列和触发器去实现自增。下文以创建 USER 表为例。 +-- 删除原有 USER 表 +DROP TABLE &quot;TEST&quot;.&quot;USER&quot;; +-- 创建 USER 表 +CREATE TABLE &quot;TEST&quot;.&quot;USER&quot; ( + &quot;id&quot; NUMBER NOT NULL, + &quot;gmt_create&quot; DATE NOT NULL, + &quot;gmt_modified&quot; DATE NOT NULL, + &quot;is_deleted&quot; NUMBER NOT NULL, + &quot;login&quot; NVARCHAR2(255) NOT NULL, + &quot;passwd&quot; NVARCHAR2(255) NOT NULL, + &quot;nick&quot; NVARCHAR2(255) NOT NULL, + &quot;phone&quot; NVARCHAR2(255), + &quot;head_img&quot; NVARCHAR2(255), + &quot;status&quot; NVARCHAR2(255), + &quot;remark&quot; NCLOB +); + +-- 删除原有序列 +DROP SEQUENCE &quot;TEST&quot;.&quot;USER_SEQ&quot;; +-- 创建 USER_SEQ 序列,最小值为 1 +CREATE SEQUENCE &quot;TEST&quot;.&quot;USER_SEQ&quot; +-- 最小值为 1 +MINVALUE 1 +-- 最大值为 9999999999999999999999999999 +MAXVALUE 9999999999999999999999999999 +-- 每次增加 1 +INCREMENT BY 1 +-- 将 20 个序列值放入缓存 +CACHE 20; + +-- 创建触发器 +CREATE TRIGGER &quot;TEST&quot;.&quot;USER_TRIGGER&quot; +-- 在插入数据前执行 +BEFORE INSERT ON &quot;TEST&quot;.&quot;USER&quot; +-- 命名老数据为 OLD,新数据为 NEW +REFERENCING OLD AS &quot;OLD&quot; NEW AS &quot;NEW&quot; +-- 针对表的每一行都执行触发器 +FOR EACH ROW +-- 将序列值赋值给 id +BEGIN + :NEW.&quot;id&quot; := USER_SEQ.NEXTVAL; +END; +/ + +需要注意的是,上面的/符号不能少。执行插入语句时可以发现,id会自动增加。 +INSERT INTO &quot;TEST&quot;.&quot;USER&quot; (&quot;gmt_create&quot;, &quot;gmt_modified&quot;, &quot;is_deleted&quot;, &quot;login&quot;, &quot;passwd&quot;, &quot;nick&quot;, &quot;phone&quot;, &quot;head_img&quot;, &quot;status&quot;, &quot;remark&quot;) VALUES (TO_DATE('2023-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), TO_DATE('2023-04-01 17:04:30', 'SYYYY-MM-DD HH24:MI:SS'), '0', 'user', '123', 'Jack', '1111', 'head.jpg', '激活', '测试'); + +Java Spring+Mybatis 使用 Oracle 及配置分页 +application.properties文件配置信息: +# mybatis +spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver +spring.datasource.url=jdbc:oracle:thin:@8127.0.0.1:1521:helowin +spring.datasource.username=system +spring.datasource.password=system +mybatis.mapper-locations=classpath*:mybatis/*.xml +mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl + +# pageHelper +pagehelper.helperDialect=oracle +pagehelper.reasonable=true +pagehelper.supportMethodsArguments=true +pagehelper.params=count=countSql + +pom.xml配置文件关键信息。 +&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; +&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; + xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt; + + &lt;properties&gt; + &lt;java.version&gt;1.8&lt;/java.version&gt; + &lt;maven.compiler.target&gt;1.8&lt;/maven.compiler.target&gt; + &lt;maven.compiler.source&gt;1.8&lt;/maven.compiler.source&gt; + &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt; + &lt;project.reporting.outputEncoding&gt;UTF-8&lt;/project.reporting.outputEncoding&gt; + + &lt;spring.boot-version&gt;2.1.3.RELEASE&lt;/spring.boot-version&gt; + &lt;/properties&gt; + + &lt;dependencyManagement&gt; + &lt;dependencies&gt; + &lt;dependency&gt; + &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; + &lt;artifactId&gt;spring-boot-dependencies&lt;/artifactId&gt; + &lt;version&gt;${spring.boot-version}&lt;/version&gt; + &lt;type&gt;pom&lt;/type&gt; + &lt;scope&gt;import&lt;/scope&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt; + &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt; + &lt;version&gt;2.1.0&lt;/version&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;com.oracle.ojdbc&lt;/groupId&gt; + &lt;artifactId&gt;ojdbc8&lt;/artifactId&gt; + &lt;version&gt;19.3.0.0&lt;/version&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt; + &lt;artifactId&gt;pagehelper-spring-boot-starter&lt;/artifactId&gt; + &lt;version&gt;1.4.3&lt;/version&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt; + &lt;artifactId&gt;pagehelper-spring-boot-starter&lt;/artifactId&gt; + &lt;/dependency&gt; + &lt;/dependencies&gt; + &lt;/dependencyManagement&gt; +&lt;/project&gt; + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/MFBljEI1_/index.html b/MFBljEI1_/index.html new file mode 100644 index 00000000..25543a37 --- /dev/null +++ b/MFBljEI1_/index.html @@ -0,0 +1,457 @@ + + + + + + + + 信用卡航司里程和境外返现 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 信用卡航司里程和境外返现 +

+ + +
+ +
+

信用卡基础知识入门中已经提到过银行会和各大公司搞联名卡,其实当你把信用卡了解到一定程度之后就会发现银行、航司、电商、酒店都是相互关联的。信用卡可以匹配像希尔顿、万豪等一些酒店的高级会员,酒店高级会员又可以匹配航司高级会员。

+

提到航司这里建议各位小伙伴乘机时都先注册一个会员账户积累里程,很多人可能并没有去关注里程这个东西。以乘坐南航航班从成都到广州为例,用 12000 里程就可以兑换一张机票,相当于就省去了一张机票钱,最近是特殊时期一张机票看起来价格并不贵,要搁平时咋样也是会省 1000 来块钱的。

+
1622907373504
+

我之前也不懂航司里程这些东西,看了下航旅纵横发现我在 19 年总共乘坐了 17 次航班,其中有 4 次乘坐的是海南航空的航班,我并没有注册海南航空的会员,导致这几次航班的里程都没了。在 20 年我还使用几千南航里程和几千凤凰知音里程就换了几本书和一袋核桃,现在才知道当时换的是相当不划算的,一半的机票就被自己玩没了。

+

大多数人都只是在春节时需要来回飞一趟,靠乘坐航班积累里程的方式是不现实的,更加靠谱的方式就是通过信用卡。国内银行的白金卡基本都可以兑换航司里程(国外我也不懂),而且国内的信用卡非常乱,比如之前我用的招行金卡有 9 万多的额度,浦发给的一张白金卡才 6000 额度(已注销),所以一般额度有几万的都可以尝试去升级成白金卡。

+

比如中行和南航推的联名白金卡消费 10 元就可以积累 1 里程,这些消费本身都是平时会产生的,顺道积累一下里程它不香吗?配上撸货(俗称黄牛)那就是非常可观的量级了,稍微勤奋一点应该是一年二三十万里程没有问题。

+

信用卡除了积累里程外通常还会有其它的权益,比如搭配一个信用卡接送机的权益把来回机场的车票钱也给省下。比较重要的是信用卡的延误险权益,一种要求用带险种的卡片去购买机票才可赔付,另外一种是不论以什么方式购买机票只要延误就给赔。应该有小伙伴已经在网上看过有人撸了几百万延误险,结果把自己给搞进去了。再次提醒一下撸羊毛可以,但是别心太狠去宰羊。

+

曹大也写过一篇关于里程信息差套利的文章,我也是上个月才发现很多平台的积分都是可以互通的,比如平安银行的万里通积分就和各大航司、运营商、零售等挂上了钩,也可以通过这种方式把各种平台的积分都聚合到一起。

+
1622907413265
+

信用卡肯定要用起来才会有收益,除了国内日常所产生的消费可以积累一些积分外,还有一个门槛比较高的就是境外消费返现。比如建行出的「MUSE卡鎏金版」境外消费笔笔 1% 返现,精选商户还有 8% 的返现,聪明的小伙伴看到这个应该就能联系到「海外代购」了吧。

+
1622907442707
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/ML7-KNp5c/index.html b/ML7-KNp5c/index.html new file mode 100644 index 00000000..fbfbc72f --- /dev/null +++ b/ML7-KNp5c/index.html @@ -0,0 +1,596 @@ + + + + + + + + 赚钱套利 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 赚钱套利 +
+ + +
+

+ + 信用卡航司里程和境外返现 + +

+ +
+ + + + +
+ +
+ 在信用卡基础知识入门中已经提到过银行会和各大公司搞联名卡,其实当你把信用卡了解到一定程度之后就会发现银行、航司、电商、酒店都是相互关联的。信用卡可以匹配像希尔顿、万豪等一些酒店的高级会员,酒店高级会员又可以匹配航司高级会员。 +提到航司这里建议各位小伙伴乘机时都先注册一个会员账户积累里程,很多人可能并没有去关注里程这个东西。以乘坐南航航班从成都到广州为例,用 12000 里程就可以兑换一张机票,相当于就省去了一张机票钱,最近是特殊时期一张机票看起来价格并不贵,要搁平时咋样也是会省 1000 来块钱的。 + +我之前也不懂航司里程这些东西,看了下航旅纵横发现我在 19 年总共乘坐了 17 次航班,其中有 4 次乘坐的是海南航空的航班,我并没有注册海南航空的会员,导致这几次航班的里程都没了。在 20 年我还使用几千南航里程和几千凤凰知音里程就换了几本书和一袋核桃,现在才知道当时换的是相当不划算的,一半的机票就被自己玩没了。 +大多数人都只是在春节时需要来回飞一趟,靠乘坐航班积累里程的方式是不现实的,更加靠谱的方式就是通过信用卡。国内银行的白金卡基本都可以兑换航司里程(国外我也不懂),而且国内的信用卡非常乱,比如之前我用的招行金卡有 9 万多的额度,浦发给的一张白金卡才 6000 额度(已注销),所以一般额度有几万的都可以尝试去升级成白金卡。 +比如中行和南航推的联名白金卡消费 10 元就可以积累 1 里程,这些消费本身都是平时会产生的,顺道积累一下里程它不香吗?配上撸货(俗称黄牛)那就是非常可观的量级了,稍微勤奋一点应该是一年二三十万里程没有问题。 +信用卡除了积累里程外通常还会有其它的权益,比如搭配一个信用卡接送机的权益把来回机场的车票钱也给省下。比较重要的是信用卡的延误险权益,一种要求用带险种的卡片去购买机票才可赔付,另外一种是不论以什么方式购买机票只要延误就给赔。应该有小伙伴已经在网上看过有人撸了几百万延误险,结果把自己给搞进去了。再次提醒一下撸羊毛可以,但是别心太狠去宰羊。 +曹大也写过一篇关于里程信息差套利的文章,我也是上个月才发现很多平台的积分都是可以互通的,比如平安银行的万里通积分就和各大航司、运营商、零售等挂上了钩,也可以通过这种方式把各种平台的积分都聚合到一起。 + +信用卡肯定要用起来才会有收益,除了国内日常所产生的消费可以积累一些积分外,还有一个门槛比较高的就是境外消费返现。比如建行出的「MUSE卡鎏金版」境外消费笔笔 1% 返现,精选商户还有 8% 的返现,聪明的小伙伴看到这个应该就能联系到「海外代购」了吧。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 信用卡基础知识入门 + +

+ +
+ + + + +
+ +
+ 相信绝大部分伙伴都使用过「花呗」这个产品吧,当月花下月还很契合现在的提前消费理念。花呗有账单日和还款日之分,需要在每个月的还款日之前将上期的账单还清,否则就会产生逾期记录进而影响自己的征信。当然如果确实没有足够的钱也可以选择账单分期,只不过需要支付一定的分期手续费。花呗的这些机制与信用卡一致,但是相比信用卡花呗就显得很抠门了,抽空对信用卡做了一点研究,就分享到这里一起交流。 +虽然我从毕业开始就一直在用信用卡,但是我也一直没有搞明白信用卡的逻辑。比花呗更长的免息期,送开卡礼、首刷礼,用信用卡消费还给你积分兑换礼品、抵现或话费,偶尔还会给出一些支付立减的优惠(比如我前段时间用支付宝就老是遇到美国运通卡的立减金),银行难道是脑袋发热才这么送钱吗? + +用脚趾头想都知道银行的目的肯定是为了赚钱,但是为啥又白白的把各种权益送给你呢,所以我们有必要了解一下银行为什么要发行信用卡?不管是在线上还是线下消费,只要使用了信用卡进行结账,那么商家就需要给出一定的手续费。比如商家支付了 100 元的手续费,银行会拿到 60 元的利润,银联拿到 5 块钱,剩下的交给支付通道公司,同时还会根据「商户编码」给到你一定的积分。所以当你使用信用卡进行消费时,银行就会赚到钱。 +上一段提到了「商户编码」的概念,这个就像我们参加高考时老师给贴的条形码一样,是用来识别商户的。在教育、慈善一类的商户消费,银行是没有钱赚的,所以银行也不会给到你积分,我们可以把这类称之为「无积分商户」。银行就是根据商户编码来识别你刷的商户类型,具体可以查看刷卡之后小票商户编码 8-12 位。 +国内支持的都是银联卡,不过美国运通的业务已经在国内出现,比如我目前正在使用的招行百夫长信用卡,就是一张美国运通卡,它在国内已经支持了线上消费。国外支持银联的不多,所以很多信用卡都会在银联卡之外给配一张外币卡,有人说外币卡会占用自己的授信额度,如果不出国就不要申请你那张附属卡。 +信用卡是分不同等级的,比如普卡、金卡、白金卡、黑金卡。一般金卡及以下都是直接免年费或是可以通过一定的消费免年费的。白金及以上大部分都需要几千的年费,但是提供的相关权益也非常不错,比如航司里程、体检服务、机场贵宾厅、五星级酒店等等,不过白金及以上的下卡难度也大,具体可以看自己的实际情况去申请。 +现在银行都会和各种公司联合发一些联名卡,比如我手里的平安爱奇艺联名卡,每个月只需要有三笔消费超过 188 元,下个月就可以领一张爱奇艺黄金会员月卡。像我这种视频平台会员权益,日常消费所累积的积分,加上银行平时的一些像「5 倍积分」活动,以及利用账单日、还款日这些免息期,就是妥妥的羊毛党味道。 + +我去年一年的话费都是使用平安的积分充值,相当于白嫖了一年话费。从深圳搬到成都冬天太冷没有被子,又用招行的积分换了被子和一些收纳箱。银行给你这些通道,就是默许你可以撸羊毛,但你千万别贪心把毛拔秃了甚至要宰羊,不然就会很容易把自己给撸进去。 +不要小看撸羊毛这个行业,有的人能撸羊毛年入千万。我认识的人里面也有靠撸羊毛完全能养活自己的,再简单一点也有玩免费机票、酒店的。我觉得这个行业有意思的地方就是你日常生活的每一项都可以撸,电影票、外卖、水电话费、餐饮等等,但是玩信用卡玩着玩着也有一个问题,我现在哪怕在超市买瓶水也会不自觉的计算用哪张卡更划算。 +最后放一张闲鱼的截图做个引子吧,有兴趣的伙伴可以自己去研究,我先暂且写这么多。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 通过在生财有术捡碎片每月赚顿火锅钱 + +

+ +
+ + + + +
+ +
+ 有段时间看到有人在生财讨论「外卖淘客」的案例,生财有术的公众号也转发了一篇手把手教学的文章:睡后收入:适合小白操作的一个自动赚钱项目,当天晚上读了这篇文章之后便立马操作起来了。 +先是去关注了一堆做外卖淘客的公众号,看一下他们的操作流程和话术。紧接着把自己以前申请的公众号改名字、改头像、改功能介绍,去公众号后台设置关注后自动回复。然后按照上面文章中的介绍去闲鱼引流,但是闲鱼上面放的链接一直无人问津,只是一直机械的去学习同行,优化闲鱼的自动回复话术。 + +大概过了快一周的时候,微信上的一个朋友告诉我她想领红包时候发现我放的二维码已经失效了。虽然一直还没有进一分钱,但是这个朋友的反应让我觉得这个事是有希望的,至少还是有人会惦记外卖红包的,马上又去换了新的二维码继续推。然后第二天又告诉我二维码失效了,加上自己使用的是个人号而不是企业号,每天主动推送消息的次数有限,而且会被折叠隐藏起来,这时就有一点心灰意冷了。 +继续换上新的二维码默默期待,玩了不到两周赚了不到 10 块钱的样子,后面又失效了两次后就放弃了,但是这一小段时间的实践让我验证了闲鱼引流的可行性。 +瞎翻生财帖子的时候发现了卡券回收,一个可以靠信息差赚取认知以外的收入,心想简单转变一下就可以卖各个视频平台的会员卡啊。马上去闲鱼修改链接为腾讯视频、爱奇艺会员卡,发现不允许上架这一类的商品,那就只能靠图片去传递信息了。图片怎么做?石墨文档甩张图片、调一下字体、上个色,截一张图就是一张介绍图片。 + + +这下有效果了,第一天就有人来咨询会员卡怎么卖的了,但是好几天都只是有人咨询却并没有成交的客户,而且每天都只是零零散散的 10 个人左右咨询。想起来在生财精华帖看到过需要刷单,闲鱼成功交易的商品再次上架会提高权重,每天编辑重新发布也会获得更多的推荐量。 +找了三个朋友刷了其中一个爱奇艺链接的单,见效非常的快,当天晚上 9 点多就开始不停的有人咨询,我一直回复到了 12 点多,忘记那天赚了多少钱了,大概是一块鸡排的钱吧。从第二天开始就不断的有人来咨询,已经多到我回复不过来了,因为我还有别的工作要做。 + +晚上回家就设置了闲鱼的自动回复,将之前的外卖话术转变一下引导加我的微信,虽然流量一直都还不错但是却没有一个加我微信的,于是又改为自己手动回复了。连续这样玩了好几天真心觉得累,因为中间的利润太低了,稍微一提高人家就不买了。于是又去找成本更低的货源,总算找到另外一个靠谱且更低价的平台,每天可以稳定的进来一顿早饭钱,但却一直没有增长。 +注意到进来咨询的人大致可以分为两类,一种是给家里长辈买电视版会员的群体,针对这种群体我就狂推年卡,因为年卡是我可控范围内能将彼此利益最大化的商品,这个没什么好说的。另外一种是只要周卡的学生群体,周卡基本没有什么利润,于是稍微转变了一下思路,对于这类群体就问对方是想要看哪个剧?引导对方加微信,我直接帮他找资源,然后随意给我发个红包就行了。 +那段时间进来的很多人都是想看「使徒行者 3」,我一直没看过这部剧也不知道它讲的是什么,但是人家需要就对了,恰好我知道有个影视资源平台更新的速度非常快,看使徒 3 不仅要会员还得付费点播,把影视资源平台直接发给对方,都会发 3-15 元的红包给我。 +后面又尝试过设置自动回复引导加微信,但是一旦设置自动回复效果就没了,使徒3 完了之后效果也有比较明显的下跌,我自己玩了不到两个月时间,如果一直玩下去差不多每月能吃一顿火锅,我个人觉得投入产出比太低了就没再做了。 +目前微信上还剩一个非常信任我的客户,上来就简单粗暴的给我转钱,服务他到变成了一种小乐趣。2 月份发现这种视频会员需求进来的人直接推外卖 CPS 也可以,当时加了差不多 100 个微信,距离停止视频会员项目已经快 3 个月了我才在朋友圈发了几天饿了么的二维码,还是有效果的。 + + +在抖音直营中心工作的朋友告诉我,在投放广告按原有方式定位不到人群时,可以通过设置其它兴趣来定位人群,比如喜欢看可爱宠物视频的人群可能也很喜欢买衣服,喜欢刷剧的人极有可能也喜欢点外卖、吃零食,「外卖淘客」这段时间很火,给各位提供一个引流的思路,也欢迎你加我微信我们一起交流相关内容。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 那些可以自己一个人薅羊毛的小项目 + +

+ +
+ + + + +
+ +
+ 打开知乎、微信这些平台的时候,经常就有意无意冒出来「一部手机怎么赚钱?」、「有什么好的手机赚钱软件?」这一类标题的文章。抖音信息流广告中也有很多类似的台词,「只需一部手机,一件代发,不需要囤货,挑战月入上万」这类广告词都是带你开一个网店,「一部手机,每天只需两小时,一个月轻松回本,开启财富自由模式」...... +上面提到的那些模式有没有赚到钱的?有,而且还不少。但是拿到你手里能不能赚钱?基本不能!90% 以上的人都是在交智商税,或者是因为执行力不够强,自己没有坚持下来,真正赚了钱的普通人少之又少。这一篇文章不玩套路,分享一个任何人可以套利的模式,也就是上面提到的普通人如何用一部手机赚钱? +先把答案放到前面:撸货,这里以市面上比较火热的飞天茅台为例。相信绝大部分人都知道茅台吧,稍微有一点点了解的人就会知道飞天。百度一下飞天茅台的一点点信息,它的出厂价 900 元,再去淘宝、京东这些平台查一下它的价格,茅台官方自营、京东官方自营、天猫超市等是 1499 元一瓶,普通商家卖的都是 2500 元以上一瓶。 + +所以这就看到了套利空间了吧?出厂价不要想了,你肯定拿不到那么低的价格的,要是你能拿到出厂价的价格我们可以做个微信朋友吗?悄悄的留下自己的微信号:Guanngxu。那第二档的价格就是天猫超市、京东这些平台的 1499 元一瓶了,这正是我这篇文章所要写的内容了。茅台是硬通货不用担心卖不出去,只要你手里有茅台就不用怕砸钱,当然前提是你入手的成本不高啊。可怜我之前只知道陪房东、陪老叔喝茅台,居然完全不知道这里面有这么大的套利空间,感觉错过了几个亿。 +那么都有哪些地方可以抢茅台呢?京东、天猫、淘宝这种大家都知道的平台就不用说了,只是基本都需要会员才能抢,觉得开会员的成本比较大?那这时候就可以去找一找低价的渠道了,比如说一些兑换码什么的,还有一些银行和京东的联盟信用卡,当然你也可以联系我呀! +还有一些大家可能会忘的渠道,比如说正在慢慢退出大家视野的苏宁易购,比如说很多人都没有用过的国美 APP,还有更少人知道的酒仙网、华润万家、山姆会员店这些。这些可能都需要开通会员才行,怎么开通呢?比如下面这个小程序二维码可以开通国美的会员,你也可以直接在 APP 里面开通会员,也可以找找别的渠道。 + +所以有这么多的平台可以去预约茅台,你只需要稍微研究一下平台每天放量的时间点,列一个表然后按照时间去抢就行了,抢得到抢不到就看运气呗。但是成年人的事情不能只靠运气啊,没有运气也要创造运气嘛。把自己的七大姑八大姨都整上,大家一起去抢茅台。 +这里就是看你人品的时候了,懂得把利益分享给他人你才会有更多的收获,钱是眼睛能看到的利润,但是还有更多眼睛看不到的利润,比如找别人帮忙就给别人发个红包,别人给到你一个启发、一个以前不知道的信息,给别人发一个红包,这些简单的细节可能逐渐得到对方的信任,因为眼前这个人知道的远比你想象的要多得多,他在自己的领域至少是个小狄公吧。 +除了这些平台还有一些其它线下的平台,这种有一点点地方特色了,比如我现在人在成都,前天去红旗连锁超市买水,发现它的门上贴了一个海报,说积分达到 2000 就可以预约一瓶茅台,积分达到 4000 可以预约两瓶茅台。这种小渠道更多的是靠自己去发现,相比京东一类的大平台,小渠道抢到的概率也更大一些。 + +在哪里抢茅台,怎么去抢的问题都解决了,还有一个钱的问题啊,毕竟需要的本金可是不少的啊!这个就更容易解决了,上面已经提到过信用卡了,去申请一个信用卡就可以轻松解决资金的问题了,而且一些联名卡还可以顺道解决平台会员的问题。 +信用卡除了资金的问题可以解决外,还会带来很多附加的价值,比如我今年一年的话费没有花自己一分钱,全是用平安银行信用卡的积分换的,还有每个月可以兑换的爱奇艺会员,虽然我一直没有用爱奇艺。比如我现在盖的被子、用的体重秤和收纳箱是用招商银行积分换的,找亲朋好友推荐办信用卡还有推荐礼,这也是一部分可以薅的羊毛,当然你愿意找我的话也是非常乐意的。 +这里还有一个问题,学生群体很多银行都是批不了信用卡的,我知道招商银行信用卡学生是可以申请的,信用卡的玩法有很多很多,那些羊毛尤其迎合了喜欢占小便宜的人性特点,如果玩的好的话免费坐坐飞机、住住酒店也是可以的。 +假设你现在抢到了茅台,怎么出手呢?身边那些卖酒的商铺、一些老板等等,也有很多人专门加价收购茅台,比如我自己一个大学同学,3000 抢了两瓶茅台,转手 4700 就被人家给买过去了,简直太抢手了。 + +上面的内容都是以茅台作为例子的,其它还有很多商品也基本是一样的套路,比如下面截图这个篮球。还有其它 Nike 鞋、纪念币这些都是一样的。写到这里一个普通人可以套利的回路差不多就完成了,基本就是一个手机就可以了,如果想扩大收益那就是多个账号一起来。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 一个通过建网站赚钱的暴利项目(灰产勿碰) + +

+ +
+ + + + +
+ +
+ 这个项目的前提是你已经有了一个可以发布文章的网站,网站的主要就是收集投诉相关内容,如果你还不知道怎么去建一个网站,可以看我之前发的如何搭建一个属于自己的博客/企业网站,按照文中的方法建一个网站的成本投入不到 150 元。 +网站建起来了怎么去赚钱呢?相信大家都会想通过 SEO 把自己网站排名优化到足够高,然后利用谷歌联盟或是百度联盟赚广告费,没事的时候更新更新文章,只要保持一个比较好的更新频率,并且能长期坚持下来,那肯定是可以赚钱的,而且收入也会逐渐递增甚至可能呈现指数型增长。 +此时得承认我有点标题党了,这个快速赚钱的套路属于灰产,因为我作为证人协助警察叔叔抓获了一个做这件事情的站长。下面进入正题。 +现在有一些专门做维权的平台,比如聚投诉、新浪旗下的黑猫投诉等,可以进去看看这样的平台可能在维权方面起不到多大的作用,但是它随随便便就能上百度的首页啊。谁最害怕网络上的负面信息?肯定是被投诉公司啊!在信息时代,一条负面信息不知道要损失多少客户。 +我说的就是做一个类似的网站,首先网民都喜欢看这一类的内容,另外这样的网站也很容易进来访问量,有了访问量那么广告费就是一笔不菲的收入,比如你可以去搜「笑捧博客」看下里面的内容,我截了个图放在这里,先告诉你的是它最大的收入并不是广告费。 + +鉴于上面的信息都是网络上公开的内容,这里我就不打码了。可以看到这个网站里面的绝大部分内容都是 XXXXXXX公司-警惕,里面就是简单描述一下事情经过,然后放个合同的照片、再放几张聊天截图,而且这些内容都是受骗用户自己投稿的,完全不用花时间自己去创造内容。 +假设某客户早这个平台上面投诉了 A 公司,A 公司的人看到在百度轻易就能搜到不利于自己公司的信息,想要快速删除这样的内容怎么办?顶部特地留了个「联系站长」看见了吗?假设现在你就是这个站长,你可以像下面那样给对方回话: + +平台的内容都是用户自己发的,我这边会跟进用户投诉的进度,你这边有和客户协商处理的聊天记录吗?或者有给客户的退款记录也行,我去核实如果无误就把这篇帖子屏蔽掉。 + +都把客户逼到去网上发负面信息了,逼到客户去投诉公司了,基本上这样的公司不会有和客户协商处理的聊天记录,不到万不得已他们是不会给客户退款的,一般对方都会回答没有,此时你可以这样说: + +你需要马上处理这个帖子可以先交 600 元的押金,我这边先去后台设置不展示这篇内容,你那边抓紧时间去处理,处理好了联系我退换押金即可。 + +到这里就玩概率了,如果公司很快的就把这个事情处理了,那么对方找你这个站长退还押金也不能不给是不是?但就是有很多公司在一个月内都没有把这样的客户处理好,因为他们本身做的就是割韭菜项目,怎么会轻易退客户钱呢?过了一个月后可以这样说: + +当时给你说的是一个月内处理,与客户沟通处理的聊天截图、退款记录发给我,这边去删除帖子!时间有点久,而且后台设置的一个月就会清理一次数据,进程什么的都已经死了............ + +简单说就是各种扯皮各种赖,几百块钱对方应该也不会太在意,对方顶多骂你两句也拿你没什么办法,是不是轻轻松松的 600 元就到手了!!! +最后再次强调一点,这个被警察叔叔发现了是要来请你的,而且自己搭的网站没有官方授权,都知道投诉电话是 12315,你个人的一个博客网站凭什么能接收这种投诉?再去看看这个「笑捧博客」的服务器在香港,而且这个网站的所有内容最后更新时间是 2020 年 9 月份,知道为什么吗? +因为这个站长已经被公安局请进去了! + +
+ + Read More ~ +
+
+
+ +
+

+ + 大学生可以尝试操作的项目 + +

+ +
+ + + + +
+ +
+ 很多大学生第一次远离爸妈的怀抱都是因为上大学,刚好此时也已经是 18 岁的成年人了,从小耳濡目染的 18 岁成人礼撬动着内心那颗渴望独立的心,通过自己能力赚到钱放在手里那一刻的感觉,人类的文字已经无法描述那一刻的美妙了。 +估计也有不少学生都被一些网上兼职网站给坑过(本人也被坑过),满怀期待的在某个兼职网站提交了自己的个人信息,不一会儿对方就打电话过来和你确定一个时间去面试,此时心中的你是不是还在想自己要准备什么啊?从来没有面试经验的自己能通过面试吗? +到了现场才发现所谓的面试不过只是让你交几百块钱给他们,他们给你在网站上面注册一个账号,这样你就可以去网站上面领取兼职工作了,虽然交了几百块钱有割心头肉的痛感,但是想想多做几天兼职这些钱就挣回来了,而且还能挣更多的钱,心中又充满了期望。 +本文本着童叟无欺的价值观,良心分享几个线下必赚项目。 +收集毕业生的被子 +学校每年都会有大批的毕业生走出校园,本科、硕士(博士一般都在外面有房子)加起来少则几千多则几万,这些学生基本都从外地到学校就读的,每一年这些学生一走都会留下大量的被子等在宿舍,还给给后勤集团留下一大堆的烦恼,那为啥不帮助后勤集团去解决这个烦恼顺道再赚点钱呢? +先辛苦一下去外面的酒店了解他们是不是需要这些被子(不要把酒店的服务想象的那么好,我好像又透露了点什么),别去那种太高端的酒店就行,多问几家比一比价格,别忘了顺道也问下路过的棉花厂。做好记录,毕竟好记性不如烂笔头嘛! +评估一下在自己学校的可行性,价格比较完了觉得可以做那就开始拉上自己的哥们干!为了省点钱可以先找后勤集团领导问问,现在可以为他解决毕业生离校后宿舍被子的问题,可否把这个项目作为勤工俭学工作给点钱,就算不给钱可以不可以给出个车费把被子拉到指定地点。当然如果人家不愿意那就自己出点车费钱啰。 +后面要做的事情就不用我多说了吧!去每个宿舍询问一下,把被子抱走就行,毕业生是陆陆续续走的,所以每个宿舍楼记得多跑一两遍。过程是比较累的,坚持下来赚到一笔可供挥霍的基金绝对没问题。 +给宿舍提供零食 +想象一下自己打游戏打到大半夜饿肚子的感觉,此时楼下的小卖部已经关门了,去校外的超市又太冷、太远了,这一点在北方尤其常见。在南方生活的我有一次肚子饿了,跑到学校的腐败街希望能找点吃的,结果基本上的商家都关门了,要知道那还不到 12 点。 +可能也有同学想过甚至实践过这个项目,我自己同班同学也拉着自己的哥们实践过,但是他们并没有做到多好,并没有赚到多少钱,所以下面说几个值得注意的地方! +选品很重要,先选比较容易出手的薯片、肥宅快乐水、辣条等,水果可以留着下一步做,因为保质期不够长!给同学送货的时候一定不要见到是熟人就瞎唠嗑,那样会浪费给其它顾客送货的时间。能加微信记得把顾客和他室友的微信加上,这样方便收集大家的需求和意见,微信和 Excel 能解决 90% 以上的问题。比较重要的一点是记得不定时做做活动,比如从已下单客户种抽取第二日免单优惠,满 30 元送一瓶可乐等等,要让顾客真正体验到在你这里购买的便捷与实惠。 +有了上面的基础之后你就可以去和货源老板压价了,当你的流量足够大的时候甚至可以和老板谈判先卖出去在付货款,只有把自己的成本压到足够低才有利润可图,这个项目主要看的是执行力! +给新生办电话卡 +这个相信很多同学都多少听过或者尝试过,每年三大运营商的抢人大战尤其激烈,我入学的时候中国移动直接疯狂到给学生免费送卡(那时候实名制还没那么严),在暑假快放假的时候去学校的各个营业厅了解一下情况,问问他们办一张卡都有多少提成之类的(我当时是一张卡 15 元),如果能同时拿到是那个运营商的卡最好,如果拿不到那就拿中国移动的卡(这个也有例外,比如我学校的楼就很神奇,基本每一栋楼里面都接收不到移动信号,图书馆能屏蔽一切信号,因地制宜也很重要)。 +去哪里拉客户呢?新生报到处对不对?错了!你的同行都会想到新生报到处,竞争比较大。新生报到处都是办理各种手续对不对?那里也没有桌子什么的方便填写资料,而且新生报到处是第一站,后续的流程还没走完人家也没那耐心给你这里耗,所以新生报到处绝对不是最佳地点。 +哪里是最佳的地点呢?宿舍。新生宿舍总共就那么三四栋楼,会呆在宿舍的新生基本都是把各种手续已经跑完了,爸妈正陪着他铺床或是简单歇歇就去吃饭呢,正处于一个放松休闲的状态。这时你去给他说你这里有学生优惠的卡,对方是不是该很高兴?只要他们还没有办卡,那他们吃完饭也会去办电话卡的。 +组织租车 +这个项目得根据学校的实际情况了,有些学校所处的位置就很适合这样的项目。比如中北大学,在一个离市区很远的村子里面,周末大家都想去市里玩,我去那里看女朋友的时候可遭罪了,虽然有公交但是超级挤啊,所以很多学生宁愿多花几块甚至十多块去坐黑车。何况有的学校还没有公交。 +那些司机师傅也很焦灼,一面是已经坐上车的顾客在死命的催他,一面是车上还没有坐满跑一趟划不来,旁边还有很多同行在和他竞争,这样的场面即使你没有在学校见过相信也在车站见过吧!所以这个项目就是在司机与同学之间搭起一座桥梁。 +还是上面的话,微信和 Excel 可以解决 90% 以上的问题,你一遍把班次定好让大家交钱预约,一边和司机师傅联系,保证发车的准时性。这个项目在特定环境是刚需,所以很容易在同学之间传开,再稍加一点活动优惠基本就躺赚了,我看到有利用这个项目赚了好几十万的! + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何空手利用拼多多(淘宝)赚钱套利? + +

+ +
+ + + + +
+ +
+ 在开头先说明一下,我自己不懂免费流量的玩法,以下我介绍的内容是我为了帮助公司一些想拿销冠的小伙伴而探索的方法,自己去实践了是有效果的,而且就个体来讲的话是可复制的套路,但我基本没放什么精力在上面,分享出来主要是希望朋友一起交流免费流量的玩法,毕竟对个人来讲付费流量还是太贵了! +估计大部分小伙伴都还不太能接受拼多多吧!拼多多上面的假产品确实不少,但是在「百亿补贴」专场里面有官方的监管,品质上都是可以做到保证的,比如 Airpods Pro 官方价格是 1999 元,而在拼多多的「百亿补贴」里面买下来 1500 都不到,那么这中间就具备了套利空间,转手在闲鱼、转转上面卖 1600 是不是很香? +我自己没有玩过转转,这里简单说一下闲鱼吧!一定要用信用很好的账号,因为这会直接体现到你的商品中。为了让客户相信我们首先要打造好人设,头像、昵称、生日、常住等等信息都填上,注意这些信息也不是瞎填,你的这些信息要给客户体现一种什么性格?什么职业?什么年龄?下面是我自己的一个闲鱼号。 + + +年龄 25 岁,喜欢数码、宅男、喜欢听歌,在互联网行业,是不是给别人的亲切感也比较强?更容易让人信服?而且这样的基础资料也比较符合我上面提到的转卖 Airpods Pro 的形象,总的来说就是信息越完善越容易让人信服。 +当然这属于比较简单的套利方式,一个人最多能注册 3 个账号,如果不是工作室的话维护起更多的账号也会费时费力,我在公司客户里面看到了一个比较 NB 的操作,去淘宝联盟去采集那些有优惠券的商品,就是做淘宝客店铺,去找那些有淘宝返利的商品,然后批量上架到自己的淘宝店铺,淘宝对那些下架前的商品会提高权重,有机会显示到前面,相对来说引流成本就能降低一些!听说还有些工作室直接利用软件批量采集淘宝联盟的商品,然后批量上架到京东商铺去赚返利。 +此处多说一句,我现在所在公司的客户里面有很多是同时开了淘宝和拼多多店铺的,他们在拼多多上卖的要比淘宝便宜,因为开拼多多店铺的成本要比淘宝低,所以拼多多上面也不都是假产品,像我下面截图中的前面有个「品牌」标志的就是真品,我们接到的很多客户是上不去这个的,不过我目前除了拼多多「百亿补贴」里面的东西会买外,其他的商品我还是不会买。 + +上面提了一下开个淘宝店,估计很多人也都只是在淘宝上买过东西,却从来没有在淘宝上卖过东西吧。但是会读到这篇文章的你至少知道先去搜索引擎查看,而大部分人连主动搜索的意识都没有,只知道瞎刷抖音。10 亿网民里面什么素质的人都有,梦想嫁个有钱人的、想快速赚钱一夜暴富的,去支付宝搜一搜「一夜暴富」就知道该怎么做了。 + +今年很多人也都因为疫情原因赋闲,查来查去好像就是开网店靠谱直接一点,因为这比较符合传统的一件产品卖给一个人的思维模式,只是把商铺搬到线上了而已。我自己没有货源怎么办?淘宝看起来好复杂啊,我不会开店怎么办?听说网店也需要做图装修?其实这些知识只需要使用百度搜索一下就能找到,连一点点高级的搜索技巧都不需要,但是就是有太多想挣钱却又懒得去主动学习的人,不然就不会出现那么多割韭菜的项目了,而且韭菜还越割越多! +应该有部分伙伴听说过以前有工作室通过代开通微信公众号赚钱吧?这个「淘宝开店」和「代开通公众号」类似,比公众号要稍复杂一些,有很多公司(包括我现在的公司)都是跑抖音广告去挖掘有开店意向的销售线索,只是帮人家开一个淘宝店就收 1000 以上的佣金。 +我这里还是拿闲鱼举例,下面图片是我截的我闲鱼的图片,随便做了个海报上去,海报内容突出「代开网店」、「淘宝教学」之类的词,一天会进来几十个咨询如何开网店的,基本上每天都能成交 2-3 个,好的时候能成交 5 个以上。这里要说明的是海报是为了规避闲鱼的检测,我试过用一个 Word 表格列出来我能做的内容,但是商品立马就被强制下架了,估计是平台也喜欢漂亮清晰的图片吧! + +为了增加曝光量(毕竟展现和转化是直接相关的),闲鱼上架的商品记得每天都去擦亮一下,如果可以的话邀请朋友帮助自己刷刷单,再用微信把钱转给朋友!因为闲鱼上面的曝光引流时间权重是15日,如果你的商品 15 天都没有卖出去的话,那么系统就会认为这个商品不是优质商品,这一点可以拿我这几天玩闲鱼的一个案例证实。可以看到截图中我上架了 3 个同样的商品,其中第二个是被人买过后我又重新上架了,其他几个商品的曝光量直接比第二个差了一个数量级不止,这还是我已经没有管闲鱼状态下的结果。 + +当然这里面需要用到一些话术,新手基本都不知道有一件代发的产品,他们都会问没有货源怎么办。我就以「没有货源」为例来回答,您没有货源没有关系的,我这边可以帮您对接一件代发的货源,基本上的货源都能涵盖到。如果说的再没有良心一点您没有货源没有关系,我这边毕竟在做这个,所以认识了很多的厂家老板,我可以免费为您对接厂家的货源,这个您放心就好了。 +从上面图中还能看到我除了海报还放了一些关于淘宝运营的书籍上去,想想什么样的人会去搜索这样的书籍?要么就是想学习淘宝运营的,要么就是想要自己开网店。淘宝运营有淘宝大学里面的课程内容,市面上关于淘宝运营的书籍、课程的基本都能从淘宝大学里面找到。所以如果进来的客户想开网店,那么你直接给他引导开网店;而如果对方是想学运营知识,你可以直接把淘宝大学的内容换成从你嘴里出来的内容;如果对方是因为想开网店而学习的话,那么学习这件事是反人性的,这种流量放一段时间也有部分是能成交的。这个「淘宝教学」的项目据我所知有公司已经做了 10 年了,而且公司越做越大。 +这里顺便说一个投放广告(引流)的思路:广告展示的产品是 A 罩杯内衣,但实际要推广的是丰胸产品。给买 A 罩杯的女人推丰胸产品,这个思路适合任何行业的投放!广告投放做人群探索的时候,优化师也常常会用这样的思路去做人群计算,从而让进入 CRM 的销售线索更加精准。 +再分享一个拼多多空手套利的案例,我现在所在的这家公司的第一桶金就是这样来的,当然他们那时候市场也比现在好做多了!想一下我们自己在淘宝、京东上面买东西的时候会关注哪些东西?买家秀、销量、差评率等,拼多多自己留了一个后门可以直接修改销量,这个操作尤其对那些新店铺有用。这个数据除了可以直接吸引买家眼球外,还可以增加店铺权重,另外还可以上报一些活动,比如什么双十一、618 这一类的活动平台都是会设置门槛的,有些店铺就是因为销量不够而达不到上报活动的条件。 +下面粗略介绍一下这个改销量的方法,比如我截图的这个商品(我随便截的,不代表任何观点)销量已经有 2207 件了,商品卖的价格是 58.9 元,假设我们现在要修改这个商品的销量。首先我们去后台把这个商品的价格提高,为了避免平台检测到我们违规操作,每次在原来价格基础上乘以 2,直到我们把价格修改到了 2000 元,然后进入「多多进宝」平台推广该商品,优惠券就可以设置为 1000 元,注意这里优惠券千万别设置多了,不然被别人领取了可就真麻烦了。然后自己去把这个优惠券领了,再回去修改商品价格到 1.01 元,为了能成功的修改商品价格需要更换改商品的类目,因为不同商品的 SKU 允许的最低单价不同,价格修改好了之后就去买购买该商品,下单数量直接设置为 1000。那么咱们可以算一下实际的成本了,1.01 * 1000 = 1010 元,而我们有一个 1000 元的优惠券可以抵消,实际成本就只有 10 块钱,有没有惊讶到? + +接下来再慢慢的把商品价格改回去就可以了,而且这个过程基本不用担心会有别的顾客进来恶意购买,因为新店铺都没有权重,别的顾客是看不到的,而且如果新店铺都给权重的话,那拼多多多的直通车还怎么赚钱啊?需要注意的是每天修改销量的上限别超过 3000,不然店铺很容易被封! +具体的技术大致介绍完了,那么这样的客户从哪里去找?以前你在拼多多上面购买了商品就可以直接看到商家的电话,而且拼多多那时候支持秒退款,可以没有成本的获取销售线索,不过后面拼多多把这个通道给堵上了。那么拼多多里面有一个商家社区,商家社区又细分出来一个新手社区,这里面是可以看到商家店铺的名字的。所以你懂了吧?不要害怕,直接进入店铺进入客服,和商家聊天! +类似的空手套利的后门在淘宝也有,比如很多店铺因为一些原因被官方封禁了,但是淘宝留了一个后门可以解封店铺,这里我就不做详细的技术描述了,抛一个解封店铺方法线索:强制开通第二个淘宝店铺教程(可解封),随随便便收个一两千块钱不是问题! +最后说一下自己的感受吧,以上介绍几个案例其实都利用的是信息差,介绍的两个电商平台的后门是平台自己没有测试出来吗?这是平台故意留给外面的网店代运营公司的,因为平台需要这样的公司给他拉新。公司盈利模式无非就是两种模式,第一种不断的洗小白客户,第二种提升服务质量推出新产品服务好老客户。淘宝开始也是假货横行,但是现在一说到假货都不会想到淘宝了,拼多多走的一些路是淘宝走过的路,在每个阶段都多少会有它的套利机会。 + +我接触的这几个案例都是针对的低端流量,统计局都说了 6 亿人月收入不超过 1000 元,超过一半网民的月收入低于 3000,大部分人那种成天无所事事,却天天梦想发大财掉馅饼的心理正是很多公司利用的心理。自己少说漂亮话多做事! + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/M_3PVzDfm/index.html b/M_3PVzDfm/index.html new file mode 100644 index 00000000..1530e13b --- /dev/null +++ b/M_3PVzDfm/index.html @@ -0,0 +1,548 @@ + + + + + + + + Bootstrap-table 如何合并相同单元格 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ Bootstrap-table 如何合并相同单元格 +

+ + +
+ +
+

Bootstrap-table 官方提供了合并单元格方法 mergeCells,它根据四个参数可以合并任意个单元格,我们要做的只是告诉它怎么合并。

+

要合并同一列相同的单元格,无非两种办法,一种是一边遍历一边合并,遍历完了再合并。这里采用第二种办法,这里不需要遍历所有数据,因为用户只能看到当前页的数据,所以只遍历当前页的数据更省时间。

+

下面是我实现的获取合并信息算法,最终返回的是一个哈希表,比如下面的这个表格,如果要对「性别」这一列进行合并,很明显前面两个“男”需要合并成一个单元格,再去看下 Bootstrap-table 提供的 API,它需要的是从哪个单元格开始,合并多少个单元格,也就是它需要的是两个数值类型的参数。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
姓名性别年龄
张三23
李四19
王二20
麻子21
+

所以我把哈希表设置为,键存的是索引,值存的是从这个索引开始后面连续有多少个和它一样的单元格,那么上述表格性别这一列所得到的合并信息哈希表就为:

+
{
+    0: 2,
+    2: 1,
+    3: 1
+}
+
+

下面算法很简单,使用两个指针遍历指定的列,如果两个指针所指向的数据相同,那么就将键所对应的值进行加一操作,整个方法只会对该列数据遍历一边,所以时间复杂度为 O(n)。

+
let getMergeMap = function (data, index: number) {
+    let preMergeMap = {};
+    // 第 0 项为表头,索引从 2 开始为了防止数组越界
+    for (let i = 2; i < data.length; i++) {
+        let preText = $(data[i-1]).find('td')[index].innerText;
+        let curText = $(data[i]).find('td')[index].innerText;
+        let key = i - 2;
+        preMergeMap[key] = 1;
+        while ((preText == curText) && (i < data.length-1)) {
+            preMergeMap[key] = parseInt(preMergeMap[key]) + 1;
+            i++;
+            preText = $(data[i - 1]).find('td')[index].innerText;
+            curText = $(data[i]).find('td')[index].innerText;
+        }
+        // while循环跳出后,数组最后一项没有判断
+        if (preText == curText) {
+            preMergeMap[key] = parseInt(preMergeMap[key]) + 1;
+        }
+    }
+    return preMergeMap;
+}
+
+

上述算法得到了单列数据的合并信息,下一步就是按照这个信息进行相同单元格的合并了,因此封装了下面的方法按照指定哈希表进行合并。

+
let mergeCells = function (preMergeMap: Object, target, fieldName: string) {
+    for (let prop in preMergeMap) {
+        let count = preMergeMap[prop];
+        target.bootstrapTable('mergeCells', { index: parseInt(prop), field: fieldName, rowspan: count });
+    }
+}
+
+

到目前为止,我们实现的都只是对单列数据进行合并,要实现对多列数据进行合并,那么只需要对所有列都进行相同的操作即可。

+
export let mergeCellsByFields = function (data: Object[], target, fields) {
+    for (let i = 0; i < fields.length; i++) {
+        let field = fields[i];
+        // 保证 field 与 i 是相对应的
+        let preMergeMap = getMergeMap(data, i);
+        let table = target.bootstrapTable();
+        mergeCells(preMergeMap, table, field);
+    }
+}
+
+

因为我在程序中做了一点处理,保证了fields中每个值得索引与对应表头的索引是一样的,因此不需要额外传入索引信息。简单来说就是我所实现的表格会根据fields的顺序,实现列之间的动态排序。你需要注意的是这一点很可能和你不一样。

+

到现在已经能够合并所有的列了,查看 Bootstrap-table 的配置信息发现,它有个属性是 onPostBody 它会在 table body 加载完成是触发,所以把这个属性配置成我们的合并单元格方法即可。

+
// groups 为要合并的哪些列
+onPostBody: function () {
+    mergeCellsByFields($('#table' + ' tr'), $('#table'), groups);
+}
+
+

再说一点不太相关的,我实现的是让用户可以自己选可以合并多少列,即用了一个可多选的下拉列表框供用户选择,根据用户选择的数量去合并,所以传入了一个groups参数。

+

最后推荐一个排序插件 thenBy,你可以用它进行多字段排序,比如用在合并相同单元格的场景,在绘制表格前先对数据进行排序,那么最后合并的结果就是把所有相同的数据聚合到一起了,并且还将它们合并到一起了,起到了一个隐形的过滤查询功能。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/NrUpfcbXq/index.html b/NrUpfcbXq/index.html new file mode 100644 index 00000000..0fc95064 --- /dev/null +++ b/NrUpfcbXq/index.html @@ -0,0 +1,678 @@ + + + + + + + + 动态规划算法优化实例——如何求解换钱的方法数 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 动态规划算法优化实例——如何求解换钱的方法数 +

+ + +
+ +
+

这是我的人生处女面遇到的一个面试题,是在去哪儿网二面遇到的,那时非常的紧张,还没有复习,所以第一次面试理所应当的挂了。文章对问题进行逐步的由简到难进行优化,基本上是代码,看懂代码才能理解,也为类似问题提供了基本的解决思路。

+

题目描述:

+
+

让你把一张整钱找零,即假设你拥有不同且不限量的小额钱币,你需要统计共有多少种方法可以用手中的小额钱币兑等额兑换一张大额钱币。

+即:给定一个元素为正数的集合(元素不重复)代表不同面值的钱币,再给一个整数,代表要找零的钱数,求共有多少种换钱方法?

+
+

递归求解

+

现在有1、5、10元三种面值的纸币,需要找零100元,那么可以做如下分析:

+
用 0 张 5 元换,剩下的用 1、10 元换,最终方法数为 count0;
+用 1 张 5 元换,剩下的用 1、10 元换,最终方法数为 count1;
+......
+用 100 张 5 元换,剩下的用 1、10 元换,最终方法数为 count100;
+
+最终的换钱方法总数就为 count0 + count1 + ...... + count100。
+
+

根据上面的分析可以写出下面的递归解决方案:

+
public static int coin(int money[], int target){
+    if (money == null || money.length == 0 || target < 0){
+        return 0;
+    }else {
+        return slove(money, 0, target);
+    }
+}
+
+// 用money[index, length-1]换钱,返回总的方法数
+private static int slove(int money[], int index, int target){
+    int res = 0;
+    if(index == money.length){
+        if (target == 0){
+            res = 1;
+        }else {
+            res = 0;
+        }
+    }else {
+        for (int i = 0; money[index] * i <= target; i++) {
+            res += slove(money, index+1, target-money[index]*i);
+        }
+    }
+    return res;
+}
+
+

优化递归

+

可以看到,上面的程序在运行时存在大量的重复过程,比如下面两种情况,其后所求结果是一样的。

+
兑换 100 元,已经使用了 0 张 1 元、1 张 2 元,剩下的用 5 元和 10 元兑换;
+兑换 100 元,已经使用了 2 张 1 元、0 张 2 元,剩下的用 5 元和 10 元兑换;
+
+

可以发现,这两种情况后面都是求解同一问题,重复的对同一个问题求解,就造成了时间的浪费,因此我们可以考虑将已经计算过的结果存下来,避免重复的计算,所以有下面的优化方案。

+
public static int coin(int money[], int target){
+    if (money == null || money.length == 0 || target < 0){
+        return 0;
+    }else {
+        /**
+         * map[i][j]表示p(i,j)递归回的值
+         * 其中-1表示该递归过程计算过,但是返回值为0
+         * 0表示该递归过程还为计算过
+         */
+
+        int map[][] = new int[money.length+1][target+1];
+        return slove(money, 0, target, map);
+    }
+}
+
+private static int slove(int money[], int index, int target, int map[][]){
+    int res = 0;
+    if(index == money.length){
+        if (target == 0){
+            res = 1;
+        }else {
+            res = 0;
+        }
+    }else {
+        int val = 0;
+        for (int i = 0; money[index] * i <= target; i++) {
+            val = map[index + 1][target - money[index]*i];
+            if (val != 0){
+                if (val == -1){
+                    res += 0;
+                }else {
+                    res += val;
+                }
+            }else {
+                res += slove(money, index+1, target-money[index]*i, map);
+            }
+        }
+    }
+
+    if (res == 0){
+        map[index][target] = -1;
+    }else {
+        map[index][target] = res;
+    }
+    return res;
+}
+
+

动态规划

+

上面对递归方法的优化已经能看到动态规划的影子了,这是一个二维的动态规划问题,我们定义dp[i][j]的含义为:使用money[0...i]的钱币组成钱数j的方法数。所以可以得出以下面的动态规划解法:

+
public static int coin(int money[], int target){
+    if (money == null || money.length == 0 || target < 0){
+        return 0;
+    }
+
+    int dp[][] = new int[money.length][target+1];
+
+    // 第一列表示组成钱数为0的方法数,所以为1
+    for (int i = 0; i < money.length; i++) {
+        dp[i][0] = 1;
+    }
+    // 第一行表示只使用money[0]一种钱币兑换钱数为i的方法数
+    // 所以是money[0]的倍数的位置为1,否则为0
+    for (int i = 1; money[0] * i <= target; i++) {
+        dp[0][money[0] * i] = 1;
+    }
+
+    for (int i = 1; i < dp.length; i++) {
+        for (int j = 1; j < dp[0].length; j++) {
+            for (int k = 0; j >= money[i] * k; k++) {
+                // dp[i][j]的值即为,用money[0...i-1]的钱
+                // 组成j减去money[i]的倍数的方法数
+                dp[i][j] += dp[i-1][j-money[i]*k];
+            }
+        }
+    }
+
+    return dp[money.length-1][target];
+}
+
+

继续优化

+

可以发现上面的动态规划解法有三层循环,因为是二维的动态规划问题,前两层没办法去掉,但是第三层依旧很耗时间,继续优化可以得到下面的结果。

+
public static int coin(int money[], int target){
+    if (money == null || money.length == 0 || target < 0){
+        return 0;
+    }
+
+    int dp[][] = new int[money.length][target+1];
+
+    for (int i = 0; i < money.length; i++) {
+        dp[i][0] = 1;
+    }
+    for (int i = 1; money[0] * i <= target; i++) {
+        dp[0][money[0] * i] = 1;
+    }
+
+    for (int i = 1; i < money.length; i++) {
+        for (int j = 1; j <= target; j++) {
+            /**
+             * 通过分析可以发现,dp[i][j]的值由两部分组成
+             * 1:用money[0...i-1]的钱组成钱数为j的方法数
+             * 2:用money[0...i]的钱组成钱数为j-money[i]*k(k=1,2,3....)的方法数
+             * 对于第2种情况,实际上累加的值就是dp[i][j-money[i]]
+             * 所以直接使用dp[i][j-money[i]]即可
+             */
+            dp[i][j] = dp[i-1][j];
+            if (j >= money[i]){
+                dp[i][j] += dp[i][j-money[i]];
+            }
+        }
+    }
+
+    return dp[money.length-1][target];
+}
+
+

空间压缩

+

可以看到每次更新dp[i][j],dp[i][j]的值只与前一行和当前行前面的元素有关系,而我们只需要最后的一个结果就行了,那么前面存的元素实际上会造成空间的浪费,进一步可以在空间上进行优化。

+

我们只需要定义一个一位数组,然后对该数组进行滚动更新就可以了,只要按照合适方向去更新数组,同样能达到上面的效果。

+
public static int coin(int money[], int target){
+    if (money == null || money.length == 0 || target < 0){
+        return 0;
+    }
+
+    int dp[] = new int[target+1];
+
+    // 第一行,只用money[0]兑换钱
+    // 所以只能兑换为money[0]的倍数,将这些位置置为1
+    for (int i = 0; money[0]*i <= target; i++) {
+        dp[i] = 1;
+    }
+
+    for (int i = 1; i < money.length; i++) {
+        for (int j = 1; j <= target; j++) {
+
+            // 与前一步相比,少了dp[i][j] = dp[i-1][j];
+            // 因为这里在进行dp[j] += dp[j-money[i]];之前
+            // dp[j]的值就已经是dp[i-1][j]了
+            if (j >= money[i]){
+                dp[j] += dp[j-money[i]];
+            }
+        }
+    }
+
+    return dp[target];
+}
+
+

到这一步就不再有优化空间了,这个问题很值得记录下来,很多笔试、面试题都可以按这个模子进行套,对于只需要最优解的动态规划问题也可以套用上面的空间压缩思路,多总结、多练习总是没有问题的!这个解题思路第一次看到是左程云在牛客网上讲解的,他也写了一本算法相关的书比较不错,叫做程序员代码面试指南,大四、研三、刚入职的新人建议可以买一本读读,对自己编码技能的提升绝对又很大的帮助。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/O-EjxlN4I/index.html b/O-EjxlN4I/index.html new file mode 100644 index 00000000..fe00c7d8 --- /dev/null +++ b/O-EjxlN4I/index.html @@ -0,0 +1,660 @@ + + + + + + + + 顺序、条件、循环语句的底层解释(机器语言级解释) | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 顺序、条件、循环语句的底层解释(机器语言级解释) +

+ + +
+ +
+

初级程序员讲究的是术,只知道如何用关键字去拼凑出领导想要的功能;高级程序员脸的是道,理解了底层的逻辑,不仅把功能做的漂漂亮亮,心法也更上一层楼。

+

顺序结构

+

数据传送指令

+

我们都清楚,绝大多数编译器都把汇编语言作为中间语言,把汇编语言程序变成可运行的二进制文件早就解决了,所以现在的高级语言基本上只需要把自己翻译成汇编语言就可以了。

+

汇编指令总共只有那么多,大多数指令都是对数据进行操作,比如常见的数据传送指令mov。不难理解,被操作数据无非有三种形式,立即数,即用来表示常数值;寄存器,此时的数据即存放在指定寄存器中的内容;内存引用,它会根据计算出来的地址访问某个内存位置。

+

需要注意的是,到了汇编层级,就不像高级语言那样随随便便int就能和long类型的数据相加减,他们在底层所占有的字节是不一样的,汇编指令是区分操作数据大小的,比如数据传送指令,就有下面这些品种(x86-64 对数据传送指令加了一条限制:两个操作数不能都指向内存位置)。

+
+

压栈与弹栈

+

对于栈,我想不必多讲,IT 行业的同学都清楚,它是一种线性数据结构,其中的数据遵循“先进后出”原则,寄存器%rsp保存着栈顶元素的地址,即栈顶指针。一个程序要运行起来,离不开栈这种数据结构。

+

栈使用最多的就是弹栈popq和压栈pushq操作。比如将一个四字值压入栈中,栈顶指针首先要减 8(栈向下增长),然后将值写到新的栈顶地址;而弹栈则需要先将栈顶数据读出,然后再将栈指针加 8。所以pushqpopq指令就可以表示为下面的形式。

+
// 压栈
+subq $8, %rsp
+movq %rbp, (%rsp)
+
+// 弹栈
+movq (%rsp), %rax
+addq $8, %rsp
+
+

其他还有算术、逻辑、加载有效地址、移位等等指令,可以查阅相关文档了解,不作过多介绍,汇编看起来确实枯燥乏味。

+

条件结构

+

前面讲的都是顺序结构,我们的程序中不可能只有顺序结构,条件结构是必不可缺的元素,那么汇编又是如何实现条件结构的呢?

+

首先你需要知道,除了整数寄存器,CPU 还维护着一组条件码寄存器,我们主要是了解如何把高级语言的条件结构转换为汇编语言,不去关注这些条件码寄存器,只需要知道汇编可以通过检测这些寄存器来执行条件分支指令。

+

if-else 语句

+

下面是 C 语言中的if-else语句的通用形式。

+
if(test-expr){
+    then-statement
+}else{
+    else-statement
+}
+
+

汇编语言通常会将上面的 C 语言模板转换为下面的控制流形式,只要使用条件跳转和无条件跳转,这种形式的控制流就可以和汇编代码一一对应,我们以 C 语言形式给出。

+
    t = test-expr;
+    if(!t){
+        goto false;
+    }
+    then-statement;
+    goto done;
+false:
+    else-statement;
+done:
+
+

但是这种条件控制转移形式的代码在现代处理器上可能会很低效。原因是它无法事先确定要跳转到哪个分支,我们的处理器通过流水线来获得高性能,流水线的要求就是事先明确要执行的指令顺序,而这种形式的代码只有当条件分支求值完成后,才能决定走哪一个分支。即使处理器采用了非常精密的分支预测逻辑,但是还是有错误预测的情况,一旦预测错误,那将会浪费 15 ~ 30 个时钟周期,导致性能下降。

+
+

在流水线中,把一条指令分为多个阶段,每个阶段只执行所需操作的一小部分,比如取指令、确定指令类型、读数据、运算、写数据以及更新程序计数器。流水线通过重叠连续指令的步骤来获得高性能,比如在取一条指令的同时,执行它前面指令的算术运算。所以如果事先不知道指令执行顺序,那么事先所做的预备工作就白干了。

+
+

为了提高性能,可以改写成使用条件数据传送的代码,比如下面的例子。

+
v = test-expr ? then-expr : else-expr;
+
+// 使用条件数据传送方法
+v = then-expr;
+ve = else-expr;
+t = test-expr;
+if(!t){
+    v = ve;
+}
+
+

这样改写,就能提高程序的性能了,但是并不是所有的条件表达式都可以使用条件传送来编译,一般只有当两个表达式都很容易计算时,编译器才会采用条件数据传送的方式,大部分都还是使用条件控制转移方式编译。

+

switch 语句

+

switch语句可以根据一个整数索引值进行多重分支,在处理具有多种可能结果的测试时,这种语句特别有用。为了让switch的实现更加高效,使用了一种叫做跳转表的数据结构(Radis 也是用的跳表)。跳转表是一个数组,表项 i 是一个代码段的地址,当开关情况数量比较多的时候,就会使用跳转表。

+

我们举个例子,还是采用 C 语言的形式表是控制流,要理解的是执行switch语句的关键步骤就是通过跳转表来访问代码的位置。

+
void switch_eg(long x, long n, long *dest){
+    long val = x;
+    switch(n){
+        case 100:
+            val *= 13;
+            break;
+        case 102:
+            val += 10;
+        case 103:
+            val += 11;
+            break;
+        case 104:
+        case 105:
+            val *= val;
+            break;
+        default:
+            val = 0;
+    }
+    *dest = val;
+}
+
+

要注意的是,上面的代码中有的分支没有break,这种问题在笔试中会经常遇到,没有break会继续执行下面的语句,即变成了顺序执行。上面的代码会被翻译为下面这种控制流。

+
void switch_eg(long x, long n, long *dest){
+        static void *jt[7] = {
+            &&loc_A, &&loc_def, &&loc_B,
+            &&loc_C, &&loc_D, &&loc_def,
+            &&loc_D
+        };
+        unsigned long index = n - 100;
+        long val;
+        if(index > 6){
+            goto loc_def;
+        }
+        goto *jt[index];
+    loc_A:
+        val = x * 13;
+        goto done;
+    loc_B:
+        x = x + 10;
+    loc_C:
+        val = x + 11;
+        goto done;
+    loc_D:
+        val = x * x;
+        goto done;
+    loc_def:
+        val = 0;
+    done:
+        *dest = val;
+}
+
+

循环结构

+

C 语言中有do-whilewhilefor三种循环结构,它们的通用形式一般都长下面那样。

+
// do-while
+do
+    body-statement
+    while(test-expr);
+    
+// while
+while(test-expr)
+    body-statement
+    
+// for
+for(init-expr; test-expr; update-expr)
+    body-statement
+
+

do-while的特点是body-statement一定会执行一次,所以我们可以将do-while翻译成下面的控制流形式,很容易就能联想到它的汇编形式。

+
loop:
+    body-statement;
+    t = test-expr;
+    if(t){
+        goto loop;
+    }
+
+

while循环我们给出两种形式的控制流,其中一种包含do-while形式,如下所示。

+
// 第一种形式
+t = test-expr;
+if(!t){
+    goto done;
+}
+do
+    body-statement;
+    while(test-expr);
+done:
+
+
+// 第二种形式
+    goto test;
+loop:
+    body-statement;
+test:
+    t = test-expr;
+    if(t){
+        goto loop;
+    }
+
+

面试的时候,有的面试官会问你for循环的执行顺序,现在深入理解了三种循环的机制,再也不怕面试官啦。for循环可以转换成如下的while形式。

+
init-expr;
+while(test-expr){
+    body-statement;
+    update-expr;
+}
+
+

有了这种形式的for循环,我们只需要将其中的while部分再翻译一下就好了,前文给出了两种while翻译的方式,而具体采用哪种方式,取决于编译器优化的等级。

+

总结

+

计算机就是用那么几条简简单单的指令就完成了各种复杂的操作,不得不折服于计算机科学家们的魅力。现在人工智能被炒的很火热,然后人是事件、情感驱动的,而计算机是控制流驱动的,所以从架构上就决定了,冯诺依曼体系计算机实现的都是弱人工智能。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/ORLDJnyfZ/index.html b/ORLDJnyfZ/index.html new file mode 100644 index 00000000..7340f46d --- /dev/null +++ b/ORLDJnyfZ/index.html @@ -0,0 +1,539 @@ + + + + + + + + 讲一个爱情故事,让 HTTPS 简单易懂 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 讲一个爱情故事,让 HTTPS 简单易懂 +

+ + +
+ +
+
+

参考内容
+HTTPS explained with carrier pigeons

+
+

充满各种数学证明的密码学是令人头疼的,一听到密码、黑客、攻击等词的时候,就给人一种神秘又高大上的感觉,但除非你真的从事密码学相关工作,否则你并不需要对密码学有多么深刻的理解。

+

这是一篇适合在饭后的品茶时光中阅读的文章,咱们虚构一个故事来讲解,虽然故事看起来很随性,但是 HTTPS 也是这么工作的。里面有一些术语你也应该听过,因为它们经常出现在技术文献里面。

+

故事背景

+

一天,一个男子到河边抓鱼给母亲吃,而河岸的另一头是一大户人家的小姐和她的丫鬟在散步。突然,一个不小心,对面小姐不慎跌入水中,而丫鬟又不会游泳,这可把小丫鬟急的呀!!!正在抓鱼的男子见此状况,来不及脱掉身上的衣物,就像箭一样窜入水中.....想必看客已经猜到了,小姐被救起,男子抱着迷迷糊糊小姐走上岸的过程中,小姐感觉自己像触电了一样,觉得这个男人很安全,只要靠着他,就算天塌下来也不怕,而男子把小姐放下的那一刻,也很不舍,好像把她放下就失去了活下去的希望。

+

小姐回到家中,给父亲大人说了这件事,父亲很高兴,就叫下人去把这位男子请到家中表示感谢,结果一问,这小伙幼年丧父,现在家中还有病弱的老母亲,连一间屋子都没有,一直和母亲寄住在城外的破庙里面,不过他毕竟救了自己的女儿,父亲让下人拿出了五十两黄金以表谢意,但不允许他和小姐再有任何来往。

+

.....此处省略五千字。

+

我们姑且称小姐为小花,称男子为小明,他们不能相见了,但是又备受相思之苦,因此只能通过写信的方式来传达彼此的思念了。

+

最简单的通信方式

+

如果小花想给小明写信,那么她可以把写好的信让信鸽给小明送去,小明也可以通过信鸽给小花回信,这样他们就能知道彼此的感情了。

+

但是很快这种方式出问题了,因为他们都隐约感觉到收到的来信不是对方写的,因为从信件上看,双方都表示不再喜欢彼此。凭借着对彼此的信任,他们才知道是小花的父亲从中阻挠他们。每次他们写的信都被父亲的下人拦下了,然后换上他们事先准备好的信件,目的就是为了让小花和小明断了感情。

+

HTTP 就是这样的工作方式。

+

对称加密

+

小花是博冠古今的人,这怎么能难倒她呢。他们彼此约定,每次写信都加上密码,让信鸽传送的信件是用密文书写的。他们约定的密码是把每个字母的位置向后移动三位,比如 A → D 、 B → E ,如果他们要给对方写一句 "I love you" ,那么实际上信件上面写的就是 "L oryh brx" 。现在就算父亲把信件拦截了,他也不知道里面的内容是什么,而且也没办法修改为有效的内容,因为他不知道密码,现在小花和小明又能给对方写情书了。

+

这就是对称加密,因为如果你知道如何加密信息,那也能知道如何解密信息。上面所说的加密常称为凯撒密码,在现实生活中,我们使用的密码肯定会更复杂,但是主要思想是一样的。

+

如何确定密钥

+

显然对称加密是比较安全的(只有两个人知道密码的情况下)。在凯撒密码中,密码通常是偏移指定位数的字母,我们使用的是偏移三位。

+

可能你已经发现问题了,在小花和小明开始写信之前,他们就已经没办法相见了,那他们怎么确定密钥呢,如果一开始通过信鸽告诉对方密钥,那父亲就能把信鸽拦下,也能知道他们的密钥,那么父亲也就可以查看他们信件的内容,同时也能修改信件了。

+

这就是典型的中间人攻击,唯一能解决这个问题的办法就是改变现有的加密方式。

+

非对称加密

+

小花想出了更好的办法,当小花想给小明写情书的时候,她将会按照下面的步骤来进行:

+
    +
  • 小花给小明送一只没有携带任何信件的鸽子;
  • +
  • 小明让信鸽带一个没有上锁的空箱子回去,钥匙由小明保管;
  • +
  • 小花把写好的情书放到箱子里面,并锁上箱子
  • +
  • 小明收到箱子后,用钥匙打开箱子就可以了。
  • +
+

使用这种方式,父亲大人就没办法拦截信鸽了,因为他没有箱子的钥匙。同样如果小明想给小花写情书,也采用这种方式。

+

这就是非对称加密,之所以称之为非对称加密,是因为即使你能加密信息(锁箱子),但是你却无法解密信息(开箱子),因为箱子的钥匙在对方那里。在技术领域,把这里的箱子称作公钥,把钥匙称作私钥

+

认证机构

+

细心的你可能发现问题了,当小明收到箱子后,他如何确定这个箱子的主人是谁呢,因为父亲也可以让信鸽带箱子给小明啊,所以父亲如果想知道他们的信件内容,那只需要把箱子偷换掉就好了。

+

小花决定在箱子上面签上自己的名字,因为笔迹是不能模仿的,这样父亲就没办法伪造箱子了。但是依旧有问题,小花和小明在不能相见之前并没有见过彼此写的字,那么小明又如何识别出小花的字迹呢?所以他们的解决办法是,找张三丰替小花签名。

+

众所周知,张三丰是当世的得道高人,他的品德是世人都认可的,大家都把他奉为圣人,而且天下肯定不止一对有情人遇到小花和小红这样的问题。张三丰只会为合法居民签名。

+

张三丰会在小花的盒子上签名,前提是他确定了要签名的是小花。所以父亲大人是无法得到张三丰代表小花签名的盒子,否则小明就会知道这是一个骗局,因为张三丰只在验证了人们的身份后才会代表他们给盒子签名。

+

张三丰在技术领域的角色就是认证机构,你现在阅读这篇文章所使用的浏览器是附带了各种认证机构的签名的。所以当你第一次访问某个网站时,你相信这不是一个钓鱼网站,是因为你相信第三方认证机构,因为他们告诉你这个箱子是合法的。

+

箱子太重了

+

虽然现在小花和小明有了一个可靠的通信系统,但是信鸽带个箱子飞的慢啊,热恋中的人是“一日不见如隔三秋”,信鸽飞慢了怎么行呢。

+

所以他们决定还是采用对称加密的方式来写情书,但是对称加密的密钥要用箱子来传递,也就是用非对称加密方式来传递对称加密密钥,这样就可以同时获得对称加密和非对称加密的优点了,还能避免彼此的缺点。

+

需要注意的是,在网络世界中,信息不会像鸽子传送的那么慢,只不过只用非对称加密技术加密信息要比对称加密慢,所以只用它来交换密钥。

+

以上就是 HTTPS 的工作过程。

+

一个故事

+
+

这个故事你可能早就知道了,我只是在写文章的过程中突然想起了它,就是笛卡尔的爱情故事。

+
+
+

具体细节你可以网上去查,笛卡尔每天给自己喜欢的公主写信,但是信都被国王拦截了,笛卡尔给公主写的第十三封信中只有一个数学方程,但是这个方程国王看不懂,所以就把这封信交给了公主,公主一看方程,立刻着手把方程的图形画了出来,发现这是一颗心的形状。

+
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/QNwNfWJcwj/index.html b/QNwNfWJcwj/index.html new file mode 100644 index 00000000..ca29e8c5 --- /dev/null +++ b/QNwNfWJcwj/index.html @@ -0,0 +1,441 @@ + + + + + + + + 灰产骗局 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 灰产骗局 +
+ + +
+

+ + 扒一下网店代运营公司的套路 + +

+ +
+ + + + +
+ +
+ 前段时间,一个朋友给我发了一个抖音视频,视频是深圳龙岗公安发布的他们抓捕代运营诈骗公司的一些场景,我在龙岗政府在线网站上也看到了这个事件的新闻:9000元请人代运营,只换来几条教学视频,深圳网店主报警。有天傍晚,我独自坐在楼下公园的椅子上看书看电影,无意中被刚下班的牙科医生碰到了,他就坐了下来和我一起聊天,很自然就聊到了他的大众点评店铺的推广,告诉我他接到过好多代运营公司的电话,他自己差点就要给其中一个公司交钱了。 +咱们先来看一下在百度搜索「网店代运营」的结果,前五条全部都是广告。在抖音短视频信息流广告中,代运营的广告也是不计其数。其它像快手、神马搜索这些产品中也充斥这种代运营广告,这么庞大的广告投入背后一定是有暴利,我这里只是把我知道的代运营套路写出来,如果能让一个人止步骗局那就是没有白写的。 + +一般会去找代运营的商家基本都是新手,自己正为店铺没有销量而发愁,现在有家公司告诉你只需要每个月交多少运营费用,自己只负责客服和发货就行了(甚至客服都不用负责),看到市面上有这样好的服务你能不心动吗?而且他们还重点给你强调公司主要是靠运营店铺销售额的提点来盈利,在签合同的时候销售还故意在起提基数和标准上表现出很强硬的态度,这无疑会更加增加商家的信任,但这都不过是骗局的开始。 +找代运营的群体里面还有一大部分是想发展一个副业的白领、空闲时间比较多的大学生和宝妈,这部分人是很纯正的网店小白,不知道怎么注册网店(不需要多高级的搜索技巧,百度随便都能搜到),不知道怎么上架商品,自己没有货源不知道该怎么办,我这里拿没有货源来介绍一下代运营公司的销售话术。 +如果对方不知道「一件代发」这个事情,那么销售可能会这样说:“您这边没有货源没有关系,我们公司是做这个的,我们不仅对接了很多的优质厂家,而且我们自己也有大量的货源,都是可以免费为您对接货源的。”如果对方知道一件代发这个东西的话,那就直接给他说一件代发就好了。 +有了货源之后要卖什么产品呢?这个你是不是也很迷茫?好了,他们一般应该会给你做一场比较「专业」的图文并茂的市场分析,女性群体的基数大、网购行为多、她们的钱好赚,反正最终分析下来的结果就是卖女装很合适。注意,代运营公司对所有咨询如何开网店的客户都是这样说的,因为他们制定了一套非常标准的销售流程,那些话术只需要复制粘贴即可,反正最终都会引导到卖女装去。 + +经过一番折腾总算把网店开起来了,有一点良心的代运营公司还有专门的美工给你做一套主图和详情图,没良心的公司顶多就是在网上扒拉几张图片扔给你,甚至从网上不知道哪里卖的几节课程发给你。产品也都上架到店铺了,但只是上架商品的话店铺连访问量都没有,怎么可能会有销量呢? +这时他们的「运营师」就会给你出一套针对你店铺的运营方案了,和前面介绍的怎么引导到都卖女装是一样的套路,所有的客户都是同一套话术引导到店铺要继续做推广的方向上去,简单说就是得继续交钱!他们可能会这样说:“我们公司和百度、搜狐、头条等互联网公司都是有合作的,我们会支付给这些平台大量的钱帮助我们的店铺去发软文,这样您相比其它商家的优势就不仅仅只有淘宝(拼多多)平台内的流量了,其它像抖音、百度这些平台的流量都会进到您的店铺,您想一下这是多么庞大的流量和优势!” +实际上代运营公司只是专门针对你店铺内容写了一篇软文,然后再用百家号、头条号、搜狐号等发了几篇文章而已,这就是他们所谓的「和百度、搜狐、头条等互联网公司都是有合作的」。为了让你相信这些软文真的生效了,一般会用一些平台去给你的店铺引流,比如新韵网和小老弟网红助手,如果你相信了确实是他们的软文推广效果生效了,那你离下一次交钱的机会就不远了。 +除了这种软文投放的方式还有另外一种销售流程:“因为您的店铺是新店铺,那些基础数据都不好,我们这边可以通过技术手段帮您把店铺基础数据弄好,这样店铺的基础数据上去了之后就可以报官方的一些活动,获取到官方的流量扶持......”。这个在之前写的如何空手利用拼多多(淘宝)赚钱套利?中有介绍如何修改店铺销量,像店铺收藏、商品收藏等也是可以通过上面介绍的新韵网和小老弟网红助手来购买的,成本就几百甚至不到一百,但是可能收你几千甚至几万的服务费。 +这些代运营公司大部分都是在抖音上面投放的广告,体量大一些的公司会在百度投放广告。记得之前在刘鹏老师的星球看到一个投放广告的逻辑:先用极低的价格卖 A 罩杯内衣,然后向这批用户推丰胸产品。最近也看到一个代运营公司搞了个门槛很高的投放方式,自己开发了一个展示货源和网店课程的 APP,然后广告投放方式是推广这个 APP,这样就避免了和其它同行竞争表单还起不来量,而且会主动注册他们 APP 的人意向也比较大,起到了一次清洗客户的作用,听说他们这种 APP 推广的方式线索成本还不到二十,而大多数代运营公司的表单成本已经到了一百多。 + +上面说了那么多都是电销的模式,可能你会想自己实地去考察肯定就不会被骗了吧?现实情况是他们欢迎你到公司实地考察,他们有现成的店铺数据这些给你看,而且实地看到他们公司各个团队之间的协作可能会让你被骗的更深,面销会忽悠的更加伤人。 +可能很多人第一下想到的是报警吧,但实际上报警的作用不是多大,首先双方是有签署合同的,所以这只能算作经济纠纷不属于派出所管辖范围。即使公安局那边立案了,整个过程估计也会把你的耐心给磨没了,破案后你损失的金额能拿回来的可能性也不大。这里只告诉大家「消费者协会」和「市长热线」是比较值得信赖的。 +总还是觉得中国是人口基数太大了,七八亿的网民里面傻子实在太多了,那些天天只知道刷抖音,梦想着如何快速发财,梦想着如何不劳而获的人,正是代运营公司的潜在客户,人性都是贪婪的,销售稍微帮你放大一下你的贪婪你就输了。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 一个通过建网站赚钱的暴利项目(灰产勿碰) + +

+ +
+ + + + +
+ +
+ 这个项目的前提是你已经有了一个可以发布文章的网站,网站的主要就是收集投诉相关内容,如果你还不知道怎么去建一个网站,可以看我之前发的如何搭建一个属于自己的博客/企业网站,按照文中的方法建一个网站的成本投入不到 150 元。 +网站建起来了怎么去赚钱呢?相信大家都会想通过 SEO 把自己网站排名优化到足够高,然后利用谷歌联盟或是百度联盟赚广告费,没事的时候更新更新文章,只要保持一个比较好的更新频率,并且能长期坚持下来,那肯定是可以赚钱的,而且收入也会逐渐递增甚至可能呈现指数型增长。 +此时得承认我有点标题党了,这个快速赚钱的套路属于灰产,因为我作为证人协助警察叔叔抓获了一个做这件事情的站长。下面进入正题。 +现在有一些专门做维权的平台,比如聚投诉、新浪旗下的黑猫投诉等,可以进去看看这样的平台可能在维权方面起不到多大的作用,但是它随随便便就能上百度的首页啊。谁最害怕网络上的负面信息?肯定是被投诉公司啊!在信息时代,一条负面信息不知道要损失多少客户。 +我说的就是做一个类似的网站,首先网民都喜欢看这一类的内容,另外这样的网站也很容易进来访问量,有了访问量那么广告费就是一笔不菲的收入,比如你可以去搜「笑捧博客」看下里面的内容,我截了个图放在这里,先告诉你的是它最大的收入并不是广告费。 + +鉴于上面的信息都是网络上公开的内容,这里我就不打码了。可以看到这个网站里面的绝大部分内容都是 XXXXXXX公司-警惕,里面就是简单描述一下事情经过,然后放个合同的照片、再放几张聊天截图,而且这些内容都是受骗用户自己投稿的,完全不用花时间自己去创造内容。 +假设某客户早这个平台上面投诉了 A 公司,A 公司的人看到在百度轻易就能搜到不利于自己公司的信息,想要快速删除这样的内容怎么办?顶部特地留了个「联系站长」看见了吗?假设现在你就是这个站长,你可以像下面那样给对方回话: + +平台的内容都是用户自己发的,我这边会跟进用户投诉的进度,你这边有和客户协商处理的聊天记录吗?或者有给客户的退款记录也行,我去核实如果无误就把这篇帖子屏蔽掉。 + +都把客户逼到去网上发负面信息了,逼到客户去投诉公司了,基本上这样的公司不会有和客户协商处理的聊天记录,不到万不得已他们是不会给客户退款的,一般对方都会回答没有,此时你可以这样说: + +你需要马上处理这个帖子可以先交 600 元的押金,我这边先去后台设置不展示这篇内容,你那边抓紧时间去处理,处理好了联系我退换押金即可。 + +到这里就玩概率了,如果公司很快的就把这个事情处理了,那么对方找你这个站长退还押金也不能不给是不是?但就是有很多公司在一个月内都没有把这样的客户处理好,因为他们本身做的就是割韭菜项目,怎么会轻易退客户钱呢?过了一个月后可以这样说: + +当时给你说的是一个月内处理,与客户沟通处理的聊天截图、退款记录发给我,这边去删除帖子!时间有点久,而且后台设置的一个月就会清理一次数据,进程什么的都已经死了............ + +简单说就是各种扯皮各种赖,几百块钱对方应该也不会太在意,对方顶多骂你两句也拿你没什么办法,是不是轻轻松松的 600 元就到手了!!! +最后再次强调一点,这个被警察叔叔发现了是要来请你的,而且自己搭的网站没有官方授权,都知道投诉电话是 12315,你个人的一个博客网站凭什么能接收这种投诉?再去看看这个「笑捧博客」的服务器在香港,而且这个网站的所有内容最后更新时间是 2020 年 9 月份,知道为什么吗? +因为这个站长已经被公安局请进去了! + +
+ + Read More ~ +
+
+
+ +
+

+ + 为什么会出现网店代运营服务? + +

+ +
+ + + + +
+ +
+ +本文写于 2020 年 4 月,较于 4 月版本本文已做部分修改 + +最近在深圳待的厌烦了,网店代运营这个行业兴起来没有多久,逮着清明假期索性到成都几个做淘宝和拼多多代运营的公司走了走,主要是想实地感受一下电商代运营是个什么玩意!截止此文章发布时间,我已经在这个公司待了一周了,但限于理解能力有限,对网店代运营这个东西还是一头雾水,下面就先说说我理解的为什么会出现代运营这样一个行业吧。 +一般经过某种生产过程我们可以得到一个或多个产品,然后再把把这些产品拿到集市上去销售,比如农民伯伯种菜拿到菜市场去买。工业的发展、社会的进步等因素使得生产力有显著的提高,但是要生产一个产品依旧离不开生产过程,只是这个生产过程效率变得更高了而已。商品不一定是实体的,比如电视剧里面用钱换取情报,这里的情报也是一种商品,网店代运营公司提供的网店代运营服务也是一种商品,这一点可以类比各种保险。 +以前都是租一个门面把商品摆在店铺里,等着附近赶集市的人前来购买。这种模式的弊端在于,一个人一天能走的距离是有限的,会走到你店铺的人更是少之又少,如此一来店铺的客流量依赖于一个村(镇、市),而一个村(镇、市)再大也不过那么点人,销售量的前提是客流量。淘宝、京东、拼多多、苏宁易购等大型电商平台的兴起,就是致力于解决这一类「做生意难」的问题,通过这些电商平台把互联网大流量的优势发挥出来了,互联网提供了巨大的流量池,怎么利用这个流量池就是靠自己的能力了。 +问题在于很多人不会操作这一类电商平台的后台系统,初学者可能连一个后台怎么操作发货都不会,更别说如何利用增长思维来玩流量。少部分年轻人能自主学会如何操作电商平台的后台系统,大部分中年人士对复杂的后台就望而却步了。而就算你能操作复杂的后台系统,但是如何优化商品信息(标题、关键词、展示图等)来提高销量可能就不一定会了。 +其它诸如站外推广、美工做图、活动上报、刷销量、刷收藏等等一系列操作更不是随随便便找个人就能做的。网店代运营公司提供的这种代运营服务则是为了解决电商运营难的问题,网店代运营公司专注于提供网店代运营服务,而客户则专注于出单发货,这也符合随着社会发展工作会逐渐细分的规律。 +说实话,从几天观察来看,我走过的工资在网店运营领域的实力是非常不值得信赖的,虽然这个行业也不乏佼佼者,但是我看到的大部分都是没有真正创造价值,只是不断的去坑客户。我个人倾向于把专业的事情交给专业的团队去做,第一是能大大节省自己的时间、人力成本,另外也不会被运营这些事情扰乱到自己的好心情,但是要真正寻找到一个靠谱的代运营公司可真是难上加难。 +我拜访的几个公司都有一个通病,那就是前期过于注重销售导致后续服务没有跟上来,或者说的更直白一点就是他们根本就没想过如何去提升自己的服务质量,客户进来洗一遍之后就不管了,这个过程叫做洗小白。也有一个公司老板已经意识到这一块的问题了,但是公司自身实力和公司人员素质都跟不上。 +每个行业的兴起都不是没有原因的,代运营服务的出现的原因是为了解决了「网店运营难」的问题。虽然市场上绝大部分代运营公司都是在割韭菜,但是我相信网店代运营行业会越来越正规化,当然也可以代运营被这绝大多数人给作死。专业的人做专业的事,是社会发展的必然结果。 +行业目前是很乱的,同行之间相互伪造聊天记录截图、伪造转账记录、伪造合同等手段去各平台发布诋毁对方的言论,甚至伪装成新客户去盗取别家公司内部的一些信息。让人觉得搞笑的是,还有直接冒充大一点的公司去骗客户的,我看到公司管理层因为这些事情搞的极为头大,表现出了秀才遇上兵的无奈。 +换个角度看同行恶意竞争这件事,如果一个公司在搜索引擎都搜索不到的话,那只能说明这个公司随时都可能死去,前期承诺了再好的服务也不一定有用,公司死了怎么能提供服务呢?如果一个公司在上面全是负面,或者全是正面的信息都太假了。只不过这个行业的负面新闻太多了! +一个良性的市场竞争环境是行业发展的必要,但是像上述冒充同行公司、伪造事实依据诋毁同行公司的人,把整个市场都搞的乌烟瘴气了。这是非常短视的行为,短期可能会让自己有一定的收入,但是长期来看这是非常不利于行业发展的,这会逐渐让潜在客户甚至签约客户都逐渐对行业失去信心。 +后面专门写一篇文章来揭露我看到的网店代运营行业乱象吧!!!! + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何空手利用拼多多(淘宝)赚钱套利? + +

+ +
+ + + + +
+ +
+ 在开头先说明一下,我自己不懂免费流量的玩法,以下我介绍的内容是我为了帮助公司一些想拿销冠的小伙伴而探索的方法,自己去实践了是有效果的,而且就个体来讲的话是可复制的套路,但我基本没放什么精力在上面,分享出来主要是希望朋友一起交流免费流量的玩法,毕竟对个人来讲付费流量还是太贵了! +估计大部分小伙伴都还不太能接受拼多多吧!拼多多上面的假产品确实不少,但是在「百亿补贴」专场里面有官方的监管,品质上都是可以做到保证的,比如 Airpods Pro 官方价格是 1999 元,而在拼多多的「百亿补贴」里面买下来 1500 都不到,那么这中间就具备了套利空间,转手在闲鱼、转转上面卖 1600 是不是很香? +我自己没有玩过转转,这里简单说一下闲鱼吧!一定要用信用很好的账号,因为这会直接体现到你的商品中。为了让客户相信我们首先要打造好人设,头像、昵称、生日、常住等等信息都填上,注意这些信息也不是瞎填,你的这些信息要给客户体现一种什么性格?什么职业?什么年龄?下面是我自己的一个闲鱼号。 + + +年龄 25 岁,喜欢数码、宅男、喜欢听歌,在互联网行业,是不是给别人的亲切感也比较强?更容易让人信服?而且这样的基础资料也比较符合我上面提到的转卖 Airpods Pro 的形象,总的来说就是信息越完善越容易让人信服。 +当然这属于比较简单的套利方式,一个人最多能注册 3 个账号,如果不是工作室的话维护起更多的账号也会费时费力,我在公司客户里面看到了一个比较 NB 的操作,去淘宝联盟去采集那些有优惠券的商品,就是做淘宝客店铺,去找那些有淘宝返利的商品,然后批量上架到自己的淘宝店铺,淘宝对那些下架前的商品会提高权重,有机会显示到前面,相对来说引流成本就能降低一些!听说还有些工作室直接利用软件批量采集淘宝联盟的商品,然后批量上架到京东商铺去赚返利。 +此处多说一句,我现在所在公司的客户里面有很多是同时开了淘宝和拼多多店铺的,他们在拼多多上卖的要比淘宝便宜,因为开拼多多店铺的成本要比淘宝低,所以拼多多上面也不都是假产品,像我下面截图中的前面有个「品牌」标志的就是真品,我们接到的很多客户是上不去这个的,不过我目前除了拼多多「百亿补贴」里面的东西会买外,其他的商品我还是不会买。 + +上面提了一下开个淘宝店,估计很多人也都只是在淘宝上买过东西,却从来没有在淘宝上卖过东西吧。但是会读到这篇文章的你至少知道先去搜索引擎查看,而大部分人连主动搜索的意识都没有,只知道瞎刷抖音。10 亿网民里面什么素质的人都有,梦想嫁个有钱人的、想快速赚钱一夜暴富的,去支付宝搜一搜「一夜暴富」就知道该怎么做了。 + +今年很多人也都因为疫情原因赋闲,查来查去好像就是开网店靠谱直接一点,因为这比较符合传统的一件产品卖给一个人的思维模式,只是把商铺搬到线上了而已。我自己没有货源怎么办?淘宝看起来好复杂啊,我不会开店怎么办?听说网店也需要做图装修?其实这些知识只需要使用百度搜索一下就能找到,连一点点高级的搜索技巧都不需要,但是就是有太多想挣钱却又懒得去主动学习的人,不然就不会出现那么多割韭菜的项目了,而且韭菜还越割越多! +应该有部分伙伴听说过以前有工作室通过代开通微信公众号赚钱吧?这个「淘宝开店」和「代开通公众号」类似,比公众号要稍复杂一些,有很多公司(包括我现在的公司)都是跑抖音广告去挖掘有开店意向的销售线索,只是帮人家开一个淘宝店就收 1000 以上的佣金。 +我这里还是拿闲鱼举例,下面图片是我截的我闲鱼的图片,随便做了个海报上去,海报内容突出「代开网店」、「淘宝教学」之类的词,一天会进来几十个咨询如何开网店的,基本上每天都能成交 2-3 个,好的时候能成交 5 个以上。这里要说明的是海报是为了规避闲鱼的检测,我试过用一个 Word 表格列出来我能做的内容,但是商品立马就被强制下架了,估计是平台也喜欢漂亮清晰的图片吧! + +为了增加曝光量(毕竟展现和转化是直接相关的),闲鱼上架的商品记得每天都去擦亮一下,如果可以的话邀请朋友帮助自己刷刷单,再用微信把钱转给朋友!因为闲鱼上面的曝光引流时间权重是15日,如果你的商品 15 天都没有卖出去的话,那么系统就会认为这个商品不是优质商品,这一点可以拿我这几天玩闲鱼的一个案例证实。可以看到截图中我上架了 3 个同样的商品,其中第二个是被人买过后我又重新上架了,其他几个商品的曝光量直接比第二个差了一个数量级不止,这还是我已经没有管闲鱼状态下的结果。 + +当然这里面需要用到一些话术,新手基本都不知道有一件代发的产品,他们都会问没有货源怎么办。我就以「没有货源」为例来回答,您没有货源没有关系的,我这边可以帮您对接一件代发的货源,基本上的货源都能涵盖到。如果说的再没有良心一点您没有货源没有关系,我这边毕竟在做这个,所以认识了很多的厂家老板,我可以免费为您对接厂家的货源,这个您放心就好了。 +从上面图中还能看到我除了海报还放了一些关于淘宝运营的书籍上去,想想什么样的人会去搜索这样的书籍?要么就是想学习淘宝运营的,要么就是想要自己开网店。淘宝运营有淘宝大学里面的课程内容,市面上关于淘宝运营的书籍、课程的基本都能从淘宝大学里面找到。所以如果进来的客户想开网店,那么你直接给他引导开网店;而如果对方是想学运营知识,你可以直接把淘宝大学的内容换成从你嘴里出来的内容;如果对方是因为想开网店而学习的话,那么学习这件事是反人性的,这种流量放一段时间也有部分是能成交的。这个「淘宝教学」的项目据我所知有公司已经做了 10 年了,而且公司越做越大。 +这里顺便说一个投放广告(引流)的思路:广告展示的产品是 A 罩杯内衣,但实际要推广的是丰胸产品。给买 A 罩杯的女人推丰胸产品,这个思路适合任何行业的投放!广告投放做人群探索的时候,优化师也常常会用这样的思路去做人群计算,从而让进入 CRM 的销售线索更加精准。 +再分享一个拼多多空手套利的案例,我现在所在的这家公司的第一桶金就是这样来的,当然他们那时候市场也比现在好做多了!想一下我们自己在淘宝、京东上面买东西的时候会关注哪些东西?买家秀、销量、差评率等,拼多多自己留了一个后门可以直接修改销量,这个操作尤其对那些新店铺有用。这个数据除了可以直接吸引买家眼球外,还可以增加店铺权重,另外还可以上报一些活动,比如什么双十一、618 这一类的活动平台都是会设置门槛的,有些店铺就是因为销量不够而达不到上报活动的条件。 +下面粗略介绍一下这个改销量的方法,比如我截图的这个商品(我随便截的,不代表任何观点)销量已经有 2207 件了,商品卖的价格是 58.9 元,假设我们现在要修改这个商品的销量。首先我们去后台把这个商品的价格提高,为了避免平台检测到我们违规操作,每次在原来价格基础上乘以 2,直到我们把价格修改到了 2000 元,然后进入「多多进宝」平台推广该商品,优惠券就可以设置为 1000 元,注意这里优惠券千万别设置多了,不然被别人领取了可就真麻烦了。然后自己去把这个优惠券领了,再回去修改商品价格到 1.01 元,为了能成功的修改商品价格需要更换改商品的类目,因为不同商品的 SKU 允许的最低单价不同,价格修改好了之后就去买购买该商品,下单数量直接设置为 1000。那么咱们可以算一下实际的成本了,1.01 * 1000 = 1010 元,而我们有一个 1000 元的优惠券可以抵消,实际成本就只有 10 块钱,有没有惊讶到? + +接下来再慢慢的把商品价格改回去就可以了,而且这个过程基本不用担心会有别的顾客进来恶意购买,因为新店铺都没有权重,别的顾客是看不到的,而且如果新店铺都给权重的话,那拼多多多的直通车还怎么赚钱啊?需要注意的是每天修改销量的上限别超过 3000,不然店铺很容易被封! +具体的技术大致介绍完了,那么这样的客户从哪里去找?以前你在拼多多上面购买了商品就可以直接看到商家的电话,而且拼多多那时候支持秒退款,可以没有成本的获取销售线索,不过后面拼多多把这个通道给堵上了。那么拼多多里面有一个商家社区,商家社区又细分出来一个新手社区,这里面是可以看到商家店铺的名字的。所以你懂了吧?不要害怕,直接进入店铺进入客服,和商家聊天! +类似的空手套利的后门在淘宝也有,比如很多店铺因为一些原因被官方封禁了,但是淘宝留了一个后门可以解封店铺,这里我就不做详细的技术描述了,抛一个解封店铺方法线索:强制开通第二个淘宝店铺教程(可解封),随随便便收个一两千块钱不是问题! +最后说一下自己的感受吧,以上介绍几个案例其实都利用的是信息差,介绍的两个电商平台的后门是平台自己没有测试出来吗?这是平台故意留给外面的网店代运营公司的,因为平台需要这样的公司给他拉新。公司盈利模式无非就是两种模式,第一种不断的洗小白客户,第二种提升服务质量推出新产品服务好老客户。淘宝开始也是假货横行,但是现在一说到假货都不会想到淘宝了,拼多多走的一些路是淘宝走过的路,在每个阶段都多少会有它的套利机会。 + +我接触的这几个案例都是针对的低端流量,统计局都说了 6 亿人月收入不超过 1000 元,超过一半网民的月收入低于 3000,大部分人那种成天无所事事,却天天梦想发大财掉馅饼的心理正是很多公司利用的心理。自己少说漂亮话多做事! + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/QbHW4oh0b/index.html b/QbHW4oh0b/index.html new file mode 100644 index 00000000..65419c75 --- /dev/null +++ b/QbHW4oh0b/index.html @@ -0,0 +1,480 @@ + + + + + + + + 开关电源 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 开关电源 +
+ + +
+

+ + BUCK 电路基础知识 + +

+ +
+ + + + +
+ +
+ +参考内容: +手撕Buck!Buck公式推导过程 +电力电子的基础应用 +《精通开关电源设计(第二版)》 +Buck电源芯片输出有问题?检查这几样 +原来PWM这么简单! +为什么 DC-DC 芯片设计中都有一个自举电容? +如何克服开关电源中的最小导通时间挑战 + +BUCK 电路构建 +根据高中所学习的物理知识可以很容易的想到,使用一个滑动变阻器即可实现降压和稳压的效果。当负载波动时,通过改变滑动变阻器的阻值,可以调节负载所获得的电压。但是使用滑动变阻器的劣势也很明显,大量的耗能会导致器件温度快速升高。 + + +上面所提到的电路主要缺点在于导通器件(变阻器或三极管)本身存在耗能,那么有没有不会耗能的导通器件呢?首先肯定不能选导线,不然又回到最原始的问题,所有电压都被加到负载上了。有没有能不耗能且能控制加在负载电压的导通器件呢?最常见的机械开关就能做到这个效果。 + +当开关闭合时,负载即获得电压源输出的电压;当开关打开时,负载所获得的电压为 0V。计算平均值可以确定达到了降压的目的,通过控制开关闭合的时间长短,就可以达到调节电压的效果。但仔细想想就会发现不对劲,电路并不会帮助我们计算平均值,负载所获得的电压波形如下图所示,是完美的方波,并不是一条直线。 + +控制开关闭合的时间,即后文要讲的控制占空比 + + +此时很容易就能想到利用电容两端电压不能突变的特点,给负载并联一个电容即可,电容即保证负载可以获得连续的能量流。 + +一旦引入了电容,就需要考虑浪涌电流的问题。根据公式 Q=CV=ItQ=CV=ItQ=CV=It 可得 I=CVtI=\frac{CV}{t}I=tCV​,开关闭合时电压在非常短的时间内升高,所以电流会突然变得很大。 +我们当然可以简单的利用电阻来抑制浪涌电流,但不幸的是电阻总要消耗功率。为了最大限度的提高效率,可以考虑使用电感,电感本身不消耗任何能量,只会进行储能,且其无损限流的能力正好可以用来抑制电容的浪涌电流。 + +引入电感后可以发现当开关打开时,电感没有续流回路,因此需要想办法构造电感的续流回路。续流回路需要保证不论开关打开还是闭合,电流都流向负载,且开关闭合时电源正极与负极回路必须经过电感与负载。这个需求很符合二极管的特点,即只允许单向导通。 + +到目前为止我们构建了非同步 BUCK 电路,考虑到机械开关容易磨损、使用寿命短、有机械惯性(转换频率低)的问题,我们需要将机械开关换成转换频率高的半导体器件,此处我们选择 NMOS 管来替代开关。 + +选择 NMOS 和 PMOS 的主要区别在于驱动电路的设计 + + +可以发现当 NMOS 开关管导通时,续流二极管处于截止状态;当开关管关断时,续流二极管处于导通状态。即二极管的导通和截止和开关管的截止导通是同步的,也就是说二极管起到的是一个开关的作用。而且考虑到电流从二极管流过期间,二极管两端的压降恒定为导通电压 0.7V,二极管所消耗的能量较大。因此我们也可以把二极管换为导通电阻更小的 NMOS 管。 + +为了提高 BUCK 电路的稳定性,防止由于输入纹波带来异常,我们在 BUCK 电路的输入端并联一个电容,用于滤除输入电压的纹波。 + +至此我们就搭建了一个标准的同步 BUCK 电路,我们将其简单变个样子,再加几个标签即可得到下图。在同步 BUCK 电路中需要两个开关管密切配合,以防止整个线路导通,所以它们之间需要保持一定的相位关系,即上管导通下管截止;上管截止下管导通,我们把这种关系称之为同步。 + +信号角度理解 LC +我们以占空比为 0.5 来进行说明,将时域下的方波转换到频域,通过傅立叶变换可以分解出一系列的频率分量。其中包含频率为 0 的分量,即直流分量,也就是我们想要保留的部分,还有频率为 n 倍 fsf_{s}fs​ 的分量。那么如何把我们不想要的部分去掉呢?从滤波角度考虑就需要加入一个低通滤波器。 + +通过加入低通滤波器可以把高频分量滤除,把二阶低通滤波器的截止频率设置在 0 到 fsf_{s}fs​ 之间,即可把 fsf_{s}fs​ 所有以上的部分给滤除。整体达到的效果即通过一个 LC 低通滤波器,配合一个开关网络,将一个数字化的电平重新滤出,得到一个比较平缓的电压输出,这个过程即完成了电压从高到低的转换。其中直流分量的大小受占空比 D 控制,所以通过改变占空比 D 即可改变输出电压大小。 + +稳态分析 +我们需要先强调一下前提,此处我们说的稳态分析,即输入电压输出电压都是稳定,且纹波足够小的状态。下文的所有计算都将基于稳态进行分析,并且是在 (F)CCM(连续导通模式)下计算的。 +我们将一些已知条件列出来: + +输入电压:ViV_{i}Vi​ +输出电压:VoV_{o}Vo​ +负载电阻:RRR +输出电感:LLL +开关频率:fff + +伏秒平衡 +当上管导通下管截止时,电感右边的电压为 ViV_{i}Vi​,左边的电压为 VoV_{o}Vo​,因为同步 BUCK 电路是降压电路,所以 Vi&gt;VoV_{i}&gt;V_{o}Vi​&gt;Vo​,所以电感两端电压即为 Vi−VoV_{i}-V_{o}Vi​−Vo​,也就是说是一个恒定值。由于有 Ldidt=Vi−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =V_{i}-V_{o}Ldtdi​=Vi​−Vo​,所以 didt=Vi−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{V_{i}-V_{o}}{L}dtdi​=LVi​−Vo​​,即电感电流的上升斜率,由于是稳态前提,所以可以确定该值是一个常数。 +当上管截止下管导通时,电感右边电压为 VoV_{o}Vo​,左边电压为 000,所以电感两端电压为 0−Vo0-V_{o}0−Vo​,即 −Vo-V_{o}−Vo​。由于 Ldidt=−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-V_{o}Ldtdi​=−Vo​,所以 didt=−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-\frac{V_{o}}{L}dtdi​=−LVo​​,即电感电流的下降斜率,也是一个常数。 +整个电路处于稳定状态,负载电路恒定,那么在一个周期内,电感电流增加的量肯定等于电感电流减小的量,即充了多少电就要放多少电,不然负载的电流或电压将会发生变化。 +前文已有didt=UL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{{U}}{L}dtdi​=LU​,而 LLL 恒定,那么电感电流的变化速度即与电压成正比关系,即电感电流上升(下降)的斜率与电压成正比关系。而电感电流上升和下降的高度相同,那么上升时间和下降时间就自然构成反比关系。 +TonToff=VoVi−Vo\frac{T_{on} }{T_{off} } = \frac{V_{o}}{V_{i}-V_{o}}Toff​Ton​​=Vi​−Vo​Vo​​,将其进行简单变换即可得到闻名江湖的伏秒平衡法则。 +Ton(Vi−Vo)=ToffVoT_{on}(V_{i}-V_{o}) = T_{off}V_{o}Ton​(Vi​−Vo​)=Toff​Vo​ +占空比 +已知 T=Ton+Toff=1fT=T_{on}+T_{off}=\frac{1}{f}T=Ton​+Toff​=f1​,结合伏秒平衡法则可以计算出: +开通时间:Ton=VoVi∙1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f}Ton​=Vi​Vo​​∙f1​ +关断时间:Toff=Vi−VoVi∙1fT_{off} = \frac{V_{i}-V_{o} }{V_{i} } \bullet \frac{1}{f}Toff​=Vi​Vi​−Vo​​∙f1​ +占空比:D=TonT=VoViD = \frac{T_{on} }{T}=\frac{V_{o} }{V_{i}}D=TTon​​=Vi​Vo​​ +纹波电流 +由于输出电压不变,也就是说输出电容两端的电压没有变化,即输出电容的平均电流为 0。根据输出节点的基尔霍夫电流定律可知,输出节点电流和为 0,那么功率电感的平均电流就等于负载的平均电流,即IL=Io=VoRI_{L} = I_{o} = \frac{V_{o} }{R}IL​=Io​=RVo​​。 + +上文计算电感电流斜率时已经能确定电流波形是个三角波,纹波电流等于在开关导通时电感电流的增大值,也等于在关断时电感电流减小的值,计算任意一个即可得到纹波电流。我们以上管导通时增大的电感电流计算。 + +上管导通时电感两端电压为 Vi−VoV_{i}-V_{o}Vi​−Vo​,导通时间为 Ton=VoVi∙1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f}Ton​=Vi​Vo​​∙f1​,根据 U=LdidtU=L\frac{\mathrm{d} i}{\mathrm{d} t}U=Ldtdi​ 可知: +△IL=di\triangle I_{L} =di△IL​=di +=Ton∙UL=T_{on}\bullet \frac{U}{L}=Ton​∙LU​ +=Vi−VoL∙VoVi∙1f=\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}=LVi​−Vo​​∙Vi​Vo​​∙f1​ +根据理论计算可以发现,电感电流的纹波和负载电流的大小没有关系,但是负载电流与平均电感电流是相等关系。 +功率电感选择 +根据上文的信息进一步可以计算出电感的峰值电流: +ILP=Io+△IL2I_{LP} =I_{o}+\frac{\triangle I_{L}}{2}ILP​=Io​+2△IL​​ +=Io+Vi−Vo2L∙VoVi∙1f=I_{o}+\frac{V_{i}-V_{o}}{2L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}=Io​+2LVi​−Vo​​∙Vi​Vo​​∙f1​ +那么在选择功率电感时,电感的饱和电流就必须要大于这个ILPI_{LP}ILP​,并且需要留有一定的裕量。实际应用时电感的纹波电流应是平均电流的 30%30\%30% 至 50%50\%50% 为宜,我们将这个参数称之为电流纹波率 r。根据电流纹波率范围就可以计算出电感值的范围: +△IL=Vi−VoL∙VoVi∙1f\triangle I_{L} =\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}△IL​=LVi​−Vo​​∙Vi​Vo​​∙f1​ +L=Vi−Vo△IL∙VoVi∙1fL =\frac{V_{i}-V_{o}}{\triangle I_{L}}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}L=△IL​Vi​−Vo​​∙Vi​Vo​​∙f1​ +=Vi−Vo(0.3至0.5)Io∙VoVi∙1f=\frac{V_{i}-V_{o}}{(0.3至0.5) I_{o}}\bullet \frac{V_{o}} {V_{i}}\bullet \frac{1}{f}=(0.3至0.5)Io​Vi​−Vo​​∙Vi​Vo​​∙f1​ +=(Vi−Vo)Vo(0.3至0.5)IoVif=\frac{(V_{i}-V_{o})V_{o}}{(0.3至0.5) I_{o} V_{i} f}=(0.3至0.5)Io​Vi​f(Vi​−Vo​)Vo​​ +为何 r 为 0.3~0.5 +电流纹波率即是电感电流的交流分量与其相对应的直流分量的比值,一旦 r 确定,那么输入输出滤波电容的电流、开关管的有效电流等都确定了,因此 r 的选择会影响器件选择和芯片的成本。使用公式可以表述为: +r=△IIL=2×IACIDCr=\frac{\triangle I}{I_{L}}=2\times \frac{I_{AC}}{I_{DC}}r=IL​△I​=2×IDC​IAC​​ +一般认为,电感体积与其能量处理能量成正比,因为要处理更高的能量就需要更大的磁芯。选择电感磁芯的能量处理能力至少要等于其需存储量,即 E=12×L×Ipk2E=\frac{1}{2} \times L \times I_{pk}^{2}E=21​×L×Ipk2​,下图是 E 与 r 的的函数曲线,可以发现在 r=0.4r=0.4r=0.4 附近有一个拐点。 + +选择的 r 如果较 0.4 低很多,则所需要的电感体积越大;而若继续增大 r,则电感的体积并不会减少多少,即当 r 超过 0.4 后,通过增加 r 来减少电感体积的效果已经不明显了。 +输入纹波 +电源输入功率为 Pi=ViIiP_{i}=V_{i}I_{i}Pi​=Vi​Ii​,负载功率为 Pr=VoIoP_{r}=V_{o}I_{o}Pr​=Vo​Io​,不考虑开关损耗、导通损耗等等因素,那么输入功率和输出功率相等,可得输入平均电流为 Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}Ii​=Vi​Vo​Io​​。 +输入电压纹波就是输入电容上面电压的变化,这个变化可以分为两部分。一部分为电容充放电所导致的电压变化 UqU_{q}Uq​,另一部分为电流流过电容 ESRESRESR 导致的压降 UesrU_{esr}Uesr​。即 △Vi=Uq+User\triangle V_{i} = U_{q} + U_{ser}△Vi​=Uq​+User​。 +∵Q=CiUq=Iit=IiToff\because Q = C_{i}U_{q} = I_{i}t = I_{i}T_{off}∵Q=Ci​Uq​=Ii​t=Ii​Toff​ +∴Uq=IiToffCi\therefore U_{q} = \frac{I_{i}T_{off}}{C_{i}}∴Uq​=Ci​Ii​Toff​​ +∵Toff=Vi−VoVif\because T_{off} = \frac{V_{i}-V_{o} }{V_{i}f }∵Toff​=Vi​fVi​−Vo​​ +且 Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}Ii​=Vi​Vo​Io​​ +∴Uq=VoIoCiVif∙Vi−VoVi\therefore U_{q} = \frac{V_{o}I_{o}}{C_{i}V_{i}f}\bullet \frac{V_{i}-V_{o}}{V_{i}}∴Uq​=Ci​Vi​fVo​Io​​∙Vi​Vi​−Vo​​ +要想知道 ESRESRESR 所造成的纹波,只需要知道流过输入电容的电流即可。当上管断开时,电源输入电流 IiI_{i}Ii​ 全部流入电容 CiC_{i}Ci​。电感电流原本从下管的体二极管续流,当上管导通后,变为了从上管续流。因为此前电感一直处于放电状态,所以切换的那一刻电感电流是最小的,为 IL−△IL2I_{L}-\frac{\triangle I_{L}}{2}IL​−2△IL​​。 +在整个 TonT_{on}Ton​ 时间内,电感都被充电,电感电流一直都在增大,直到 IL+△IL2I_{L}+\frac{\triangle I_{L}}{2}IL​+2△IL​​,并且在 TonT_{on}Ton​ 时间内,电感电流都是走的上 MOS 管通路,所以上 MOS 管最大电流也是 IL+△IL2I_{L}+\frac{\triangle I_{L}}{2}IL​+2△IL​​。 +根据基尔霍夫电流定律可知,输入节点的电流和为 0,那么输入电源电流 IiI_{i}Ii​ 和电容 CiC_{i}Ci​ 的放电电流就等于通过上 MOS 管的电流。所以 CiC_{i}Ci​ 的最大放电电流即为 IL+△IL2−IiI_{L}+\frac{\triangle I_{L}}{2} - I_{i}IL​+2△IL​​−Ii​。我们约定充电为正,放电为负,则放电电流为 Ii−△IL2−ILI_{i} - \frac{\triangle I_{L}}{2} - I_{L}Ii​−2△IL​​−IL​。 + + +上管截止时 ESRESRESR 的压降为 Ii∙ESRI_{i} \bullet ESRIi​∙ESR,上管导通时压降为 (Ii−△IL2−IL)∙ESR(I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESR(Ii​−2△IL​​−IL​)∙ESR,则可得: +User=Ii∙ESR+(Ii−△IL2−IL)∙ESRU_{ser} = I_{i} \bullet ESR + (I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESRUser​=Ii​∙ESR+(Ii​−2△IL​​−IL​)∙ESR +=(IL+△IL2)∙ESR=(I_{L} + \frac{\triangle I_{L}}{2}) \bullet ESR=(IL​+2△IL​​)∙ESR +∵△IL==Vi−VoL∙VoVi∙1f\because \triangle I_{L} ==\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}∵△IL​==LVi​−Vo​​∙Vi​Vo​​∙f1​ +且 IL=IoI_{L} = I_{o}IL​=Io​ +∴User=(Io+(Vi−Vo)Vo2ViLf)∙ESR\therefore U_{ser} = \left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR∴User​=(Io​+2Vi​Lf(Vi​−Vo​)Vo​​)∙ESR +综上所述可得: +△Vi=Uq+Uesr\triangle V_{i} = U_{q} + U_{esr}△Vi​=Uq​+Uesr​ +=VoIoCiVif∙Vi−VoVi+(Io+(Vi−Vo)Vo2ViLf)∙ESR=\frac{V_{o}I_{o}}{C_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}} +\left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR=Ci​Vi​fVo​Io​​∙Vi​Vi​−Vo​​+(Io​+2Vi​Lf(Vi​−Vo​)Vo​​)∙ESR +输入电容选择 +考虑到电容的实际使用情况,陶瓷电容的 ESRESRESR 小,容量小,所以 UqU_{q}Uq​ 对纹波起决定性作用,输入纹波可近似为 UqU_{q}Uq​。若选择铝电解电容,则 ESRESRESR 大,容量大,UesrU_{esr}Uesr​ 对纹波起到决定性作用,输入纹波可以近似为 UesrU_{esr}Uesr​,假设电路设计要求输入纹波不能大于 △Vi\triangle V_{i}△Vi​,则有: +陶瓷电容:Ci≥VoIo△ViVif∙Vi−VoViC_{i} \ge \frac{V_{o}I_{o}}{\triangle V_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}}Ci​≥△Vi​Vi​fVo​Io​​∙Vi​Vi​−Vo​​ +铝电解电容:ESR≤△ViIo+(Vi−Vo)Vo2fLViESR \le \frac{\triangle V_{i}}{I_{o} + \frac{(V_{i}-V_{o})V_{o}}{2fLV_{i}} }ESR≤Io​+2fLVi​(Vi​−Vo​)Vo​​△Vi​​ +输出纹波 +输出纹波与输入纹波同理,亦是 △Vo=Uq+Uesr\triangle V_{o} = U_{q} + U_{esr}△Vo​=Uq​+Uesr​,我们画出负载、功率电感、输出电容三者的电流波形。其中电感的纹波电流是 △IL\triangle I_{L}△IL​,则电容的纹波电流也是 △IL\triangle I_{L}△IL​,又因为电容的平均电流为 0,所以充电电流和放电电流都是 △IL2\frac{\triangle I_{L}}{2}2△IL​​。 +电容充放电的总电荷量 Q 等于电流乘以时间,即图中阴影三角形的面积,三角形底部时间为 T2\frac{T}{2}2T​,高为 △IL2\frac{\triangle I_{L}}{2}2△IL​​,所以总的放电量可以计算出来为 Q=12∙T2∙△IL2Q=\frac{1}{2} \bullet \frac{T}{2} \bullet \frac{\triangle I_{L}}{2}Q=21​∙2T​∙2△IL​​ + +结合 Q=CoUqQ=C_{o}U_{q}Q=Co​Uq​ 可得: +Uq=(Vi−Vo)Vo8ViCoLf2U_{q} = \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} }Uq​=8Vi​Co​Lf2(Vi​−Vo​)Vo​​ +由前面电流波形可知,电容的充电电流最大是 △IL2\frac{\triangle I_{L}}{2}2△IL​​,放电电流最大是 −△IL2-\frac{\triangle I_{L}}{2}−2△IL​​,则可以得到 ESRESRESR 引起的总压降为: +User=△IL2∙ESR−(−△IL2∙ESR)U_{ser} = \frac{\triangle I_{L}}{2} \bullet ESR - (-\frac{\triangle I_{L}}{2} \bullet ESR)User​=2△IL​​∙ESR−(−2△IL​​∙ESR) +∵△IL=Vi−VoL∙VoVi∙1f\because \triangle I_{L} = \frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}∵△IL​=LVi​−Vo​​∙Vi​Vo​​∙f1​ +∴(Vi−Vo)VoViLf∙ESR\therefore \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR∴Vi​Lf(Vi​−Vo​)Vo​​∙ESR +最终可得: +△Uo=Uq+Uesr\bigtriangleup U_{o} = U_{q} + U_{esr}△Uo​=Uq​+Uesr​ +=(Vi−Vo)Vo8ViCoLf2+(Vi−Vo)VoViLf∙ESR=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} } + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR=8Vi​Co​Lf2(Vi​−Vo​)Vo​​+Vi​Lf(Vi​−Vo​)Vo​​∙ESR +=(Vi−Vo)VoViLf∙(ESR+18fCo)=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf}\bullet \left ( ESR + \frac{1}{8fC_{o}} \right )=Vi​Lf(Vi​−Vo​)Vo​​∙(ESR+8fCo​1​) +输出电容选择 +与输入电容选择的方式一致,考虑是容值还是 ESRESRESR 占主导地位,假设要求输出纹波要小于 △Vo\triangle V_{o}△Vo​,则有: +陶瓷电容:Co≥(Vi−Vo)Vo8Vi△VoLf2C_{o} \ge \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}\triangle V_{o}Lf^{2} }Co​≥8Vi​△Vo​Lf2(Vi​−Vo​)Vo​​ +铝电解电容:ESR≤△VoViLf(Vi−Vo)VoESR \le \frac{\triangle V_{o}V_{i}Lf}{\left ( V_{i}-V_{o} \right ) V_{o}}ESR≤(Vi​−Vo​)Vo​△Vo​Vi​Lf​ +电感续流模式 +电感电流曲线不能断续(无突降),因为电流断续会引起实际不可能发生的能量断续现象。但是电流的变化率可以突变,比如从上升斜率(电感储能增加)变为下降斜率(电感储能释放),尽管这样电感电流也必须连续。根据稳定状态下每个周期电流是否回到零,划分为不同的导通模式,并且通过减小负载电流,可以使电路从 CCM 经过 BCM 最终转变为 DCM。 +CCM(连续导通模式) +稳定状态下每个周期,电流都回到某一非零值,称之为连续导通模式(CCM:Continuous Conduction Mode)。CCM 是功率变换中最常见的工作模式,有输出纹波小但功耗高的特点。 +FCCM(强制连续导通模式)只存在于同步 BUCK 中,由于使用 MOS 管将非同步拓扑的二极管取代,MOS 管的导通压降远低于二极管压降,除了显著减小了续流通路的导通损耗外,也允许电感电流反向,即从负载瞬时流出电流。 + +DCM(断续导通模式) +若稳定状态下每个周期中电流都会回到零,那么就称之为断续导通模式(DCM:Discontinuous Conduction Mode),DCM 由于其电感电流的不连续,计算平均电感电流就需要更加详细复杂的公式,这也是 DCM 方程看上去复杂的根本原因。 + +BCM(临界导通模式) +BCM 是临界导通模式(Boundary Conduction Mode),由控制器监控电感电流,一旦检测到电流等于 0,功率开关立即闭合,控制器总是等电感电流「复位」来激活开关。即 BCM 处于 CCM 和 DCM 之间,可以将其视为 CCM 和 DCM 的极端情况,所以 BCM 模式下可以自由的选择 CCM 或 BCM 方程。 + +DC-DC 功能框图 +前文所构建的 BUCK 电路只能是纸上谈兵,还需要解决诸多问题才能应用于实际电路。 +基础驱动与控制 +首先需要解决的问题就是 MOS 管不可能平白无故就打开,所以我们需要添加 MOS 管驱动器。 + +理想情况是上管关闭,下管立刻打开,中间没有任何时间差,但是 MOS 管并非理想开关,从关断到导通存在一个过渡的过程,若同时导通则电源通过上下 MOS 管直接对地短路,很容易就会导致 MOS 损坏,甚至可能会把前一级电源也损坏,所以上下管同时导通的状态必须得避免。 +为了避免上下管直通的情况,实际应用会故意让上管和下管切换时多等一会儿,宁愿出现同时关断的情况,也不能出现同时导通的状态,这个等待的过程就叫做死区时间。 + +需要注意的是,在死区时间内虽然下管没有被导通,但是功率 MOS 管本身存在一个寄生二极管,这个寄生二极管可以像非同步 BUCK 那样帮助电感续流,而且这个时间非常的短暂,所以产生的功耗没有那么大,因此不必担心系统会出问题。 +到目前为止,不知道您有没有发现我们都在自嗨,系统中并没有用来控制上下 MOS 导通和关断的信号。因此需要增加一个振荡器用来产生控制信号,注意我们在前文中使用的是占空比一词,也就是说我们要使用的是 PWM(脉冲宽度调制)。当然你也可以使用 PFM(脉冲频率调制),本文只介绍 PWM 方式。 + +PWM(脉冲宽度调制) +PWM 的全称是脉冲宽度调制(Pulse-width modulation),是通过将有效的电信号分散成离散形式,从而来降低电信号所传递的平均功率的一种方式。其基本实现原理是通过锯齿波/三角波(载波)与所需要合成的波形(调制波)进行比较,然后确定 PWM 所需要输出的极性。因为一般都是用到开关器件上,通常是 ON 或者 OFF,具体如下图所示。 + +将振荡器输出的锯齿波和参考值 VthV_{th}Vth​ 进行比较,就可以输出 PWM 波形了。话不多说,上图就明白了。 + +上图中的锯齿波(橙色)最大为 10,但是我们希望输出平均为 5 的波形(图中紫色的水平线),那么通过比较器进行比较,当锯齿波小于 5 时,PWM 即输出低电平 OFF,当锯齿波大于 5 时,PWM 即输出高电平 ON,此时的占空比即为 50%。 +若是想输出一个电压逐渐抬高的波形,即占空比逐渐增大,那只需要将调制的波形设置为斜坡输出即可达到效果。比如下图中可以看到,占空比从 0% 逐渐增大到 100%。 + +同样的道理,我们可以通过改变调制波形,进一步调制出来其它的波形,比如要调制一个正弦波(sin wave),也就是我们常说的 SPWM,那么就是下面的样子。 + +负反馈环路 +有了调制信号,开关管也可以正常打开与关闭,看起来可以应用到实际电路中了,但是别忘了负载的电阻并不是恒定的,负载的变化必然会引起输出电压的波动。为了减小输出电压的波动,我们可以在输出端添加分压电阻,与误差放大器和基准电压一起构成负反馈回路,这种通过取样输出电压进行闭环反馈的方式称之为电压模式控制。 + +误差放大器的输入端分别为带隙基准源输出电压采样,当输出电压减小/增大时,与基准电压的细微差异都会被误差放大器放大,今儿调节脉冲宽度来达到调节调整输出电压的目的。图中 R2 接地,所以可以很容易计算出输出电压与分压电阻的关系:Vout=Vref(R1+R2)R2V_{out} = \frac{V_{ref}(R_{1}+R_{2})}{R_{2}}Vout​=R2​Vref​(R1​+R2​)​。 +除了输出电压可以用作控制取样信号,还有输入电压、输出电流、输出电感电压、开关器件峰值电流可以作为控制取样信号。使用这些信号可以构成单环、双环或多环反馈系统,进而实现稳压、稳流以及恒定功率的目的,也可以实现过流、过压、均流等功能。 +现在回过头来评判一下电压模式控制的优缺点。单一的反馈电压闭环设计使得调试更加容易、对输出负载的变化有比较好的响应调节、占空比的调节也不会受到什么限制等等都是它的优点,但是其缺点也很明显。由于主电路有较大的输出电容和电感的相移延时作用,输出电压的变小/变大也延时滞后,再经过误差放大器的延时,使得瞬态响应变得更慢。由于电压控制模式不采样电流,逐周期限流保护功能必须另外增加电路来实现。 +峰值电流模式控制在电压模式控制的基础上又增加了电流环,所以峰值电流模式控制是一个双环反馈系统。误差电压信号与一个变化的,其峰值代表输出电感电流峰值的三角波形进行比较,然后得到 PWM 脉冲的关断时刻。所以峰值电流模式控制不是使用电压误差信息直接控制 PWM 脉冲宽度,而是直接控制峰值输出侧的电感电流大小,进而间接的控制 PWM 脉冲宽度。 + +峰值电流在逻辑上与平均电感电流大小变化一致,但是峰值电感电流的大小并不能与平均电感电流的大小一一对应。在占空比不同的情况下,相同的峰值电感电流大小可以对应不同的平均电感电流大小,但平均电感电流大小才是唯一决定输出电压大小的因素。 +为了解决不同占空比对平均电感电流大小的扰动作用,使得所控制的峰值电感电流最后收敛于平均电感电流,需要将电感电流下斜坡斜率的至少一半以上斜率加在实际检测电流的上斜坡上,这一点可以从数学上进行证明(具体咋证明暂不讨论)。 +总结一下峰值电流模式控制 PWM 是双闭环控制系统,电压外环控制电流内环。电流内环是瞬时快速按照逐个脉冲工作的。功率级石油电流内环控制的电流源,而电压外环再控制次功率级电流源。电流内环只负责输出电感的动态变化,电压外环仅需控制输出电容,所以峰值电流模式控制 PWM 具有比电压模式控制大得多的带宽。 +为了防止在应用过程中可能出现的短路等异常场景,DC-DC 少不了过温保护、过流保护、过压保护等保护手段。再设定一定的辅助功能,比如 PG 状态显示、缓启动、欠压保护等即可搭建完整的 DC-DC 电路。 + +异常模式 +参考上文中的电路图,我们把绿色部分称之为控制电路,灰色部分是功率电路,功率电路中最核心的就是上下两个 MOS 管,下文我们讨论不同的异常场景中,控制电路、上管、下管三部分应该处于什么状态,其中控制电路关闭相当于整个芯片重启。 +过压保护 +当输出电压偏高并且达到了过压保护的阈值。过压状态需要控制电路去调整把输出电压降下来,所以不需要重启整个芯片。可以想到输出端已经处于过压状态了,上管如果打开那会加重过压的程度,因此上管需要关闭。若下管打开,则电感、负载、下管形成回路,即电感有续流回路,会把过压状态维持的时间更长,因此下管也需要关闭。综上有:过压保护:关上管、关下管。 +过温保护 +温度过高的情况无非两种,一种是流过芯片的电流太大,即功率太大导致芯片自身发热达到了过温保护的阈值,此时关闭芯片肯定可以解决,另外切断电流回路也是可以解决的,即关闭上管。过温的第二种情况是由于环境温度过高而导致芯片温度过高,此时最好还是关闭芯片吧。综上有:过温保护:关闭芯片。 + +关闭芯片指关闭芯片中的 BUCK 部分,但是基准源部分仍然保持工作 + +过流保护 +过流保护还需要区分是正向过流还是负向过流,因为工作在 FCCM 模式的 DC-DC 在轻载或空载时,可能会有负向过流的情况。存在负向过流的另一原因也是因为同步 BUCK 没有像非同步 BUCK 那样的整流二极管,所以当存在负向过流情况时,直接模拟非同步 BUCK 中的二极管即可。综上有:负向过流保护:关下管。 +若发生正向过流时如何进行保护呢?首先考虑到电流经上管到负载,既然已经过流了那么肯定需要关上管。为了使电流减小的更快,那么就需要将电流流向地,所以需要将下管打开以构成回路。综上有:正向过流保护:关上管、开下管。 +异常排查 +不管系统设计的多好,在实际应用中都可能会或多或少出现问题,比如电感选用不合适、触发 min-on time、触发 min-off time、输出电容 ESR 过大等,下面我们逐一进行讨论。 +min-on time +虽然 MOS 管打开速度很快,但是打开始终是一个过程,要完成一个过程就必须需要一定的时间,当高频且压差大的情况下很容易触发完成「打开」这个过程的最小时间。也就是说占空比已经是实际最小了,占空比无法再降低了,所以查看输出电压纹波可能会出现下面的波形。 + +出现该波形的原因在于,占空比已经无法继续降低,所以电压整体处于逐渐抬高的趋势,当抬高到一定程度时即触发过压保护,上下管都关断,所以电压快速下降。 +min-off time +与 min-on time 相对应的是 min-off time,当开关频率足够高且输入和输出电压接近时即容易出现此问题,此时即达到系统所能达到的最大占空比也无法满足负载所需要的电压,表现为输出电压无法达到设定值,负反馈分压电阻电压也低于电压基准值。 +电感饱和电流过小 +电感电流正常是一个三角波,但是如果电感饱和电流过小,则会电感电流将会变成下图很苗条的样子。因为电感电流饱和所以电流不再线性增加,电流快速增大导致磁通率减小,会导致磁性损耗增大、芯片热耗增大,而且这是一个正反馈过程,整个系统的可靠性会大大降低。 + +输出电容选用不合适 +当输出电容选用过小时,会导致动态响应输出出现抖动。若输出电容的等效串联电阻(ESR)过大,也会导致输出纹波异常增大,这一点从前文的理论计算即可验证。因此在实际使用过程中需要同时考虑电容容值和所选电容的 ESR。 +为什么需要 min-on time +占空比 D 控制相对于输入电压的输出电压,虽然通过提高开关频率有助于减小电感尺寸,但是也必须满足最小导通时间(min-on time)才能使芯片正常工作。那么这个 min-on time 是由哪些因素引起的呢? +因为上管中电流波形前沿的电流尖峰。由于 MOS 管也是由 PN 结组成,存在 PN 结就肯定存在结电容,MOS 管的寄生电容 CgsC_{gs}Cgs​ 和 CgdC_{gd}Cgd​ 会导致上管在导通时电流突然变化,也就是说会出现电流尖峰。如果在这个电流尖峰的时间段内去检测电流的话,很可能就会触发过流保护,因此开关电路的最小导通时间必须大于电流尖峰出现的时间,这个时间我们称之为消隐时间。 + +另一个原因是因为上下管开关完成后,由于键合线存在寄生电感的原因会产生很大的振铃,这个振铃同样可能会导致峰值电流检测出错,需要一个 min-on time 将这个振铃隔离过去。 +为什么需要 min-off time +如下图所示,最简单的需要最小关断时间(min-off time)的原因是,若下管不打开则没有办法给自举电容充电,所以需要在该时间内给自举电容充电,为下一个开关周期做准备。 + +另一个原因是因为没有最小关断时间,即占空比 D 增大到 100%,那么就无法对负向电流、谷值电流进行采样,也就无法实现实现相应的异常保护功能。与 min-on time 一致,电流检测也需要一个 min-off time 隔离振铃。 +为什么需要自举电容 +DC-DC 的上 MOS 管可以是 PMOS,也可以是 NMOS。但是一般因为生产工艺问题,PMOS 导通电流往往做不不到很大,而在相同成本下 NMOS 的导通电流可以做到更大,也就是 RdsonR_{dson}Rdson​ 可以做到相对较低,所以往往更倾向于 NMOS。 +将上管换为 NMOS 后也带来了新的问题,如何打开 NMOS ?如图所示,上管的 S 极连接 PH 点,该点的电压为 +5V,要打开 NMOS 需要 VGS&gt;0V_{GS} &gt; 0VGS​&gt;0,驱动 MOS 管打开的压降需要 5V,那么驱动电压就需要 +10V 才可以打开上管,但是纵观整个电路并没有能达到 +10V 级别的电压,所以需要自举电容来进行升压才能打开上 MOS 管。 + +所以 DC-DC 芯片是否需要自举电容是由芯片所选用的 MOS 管类型决定的,若是 PMOS 则无需自举电容。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/RYkQYSFKV/index.html b/RYkQYSFKV/index.html new file mode 100644 index 00000000..c62457a9 --- /dev/null +++ b/RYkQYSFKV/index.html @@ -0,0 +1,524 @@ + + + + + + + + linux-5.10.157 内核源码编译 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ linux-5.10.157 内核源码编译 +

+ + +
+ +
+
+

参考内容:

+

Linux内核开发_1_编译LInux内核

+

编译linux内核报错:flex: not foundscripts

+

编译kernel5.14报错fatal error: openssl/opensslv.h

+

编译内核错误——*** 没有规则可制作目标“debian/canonical-certs.pem”

+

内核错误:BTF: .tmp_vmlinux.btf: pahole (pahole) is not available

+
+
# 切换到 root 账户
+sudo su
+
+# 查看操作系统版本
+cat /etc/issue
+
+# 查看 Linux 内核版本
+cat /proc/version
+
+# 进入 root 账户目录
+cd /home/root
+
+# 下载 Linux 内核源码
+wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.157.tar.xz
+# Linux 其它版本源码 https://www.kernel.org/
+
+# xz 解压
+xz -d linux-5.10.157.tar.xz
+
+# tar 解压到 /usr/src/linux-5.10.157 目录下
+tar -xf linux-5.10.157.tar -C /usr/src/.
+
+# 进入源码目录
+cd /usr/src/linux-5.10.157
+
+# 查看源码结构
+tree . -L 2
+
+# 若没有 tree 命令,可以执行下面命令
+# apt-get install tree
+
+# 配置编译选项
+make menuconfig
+
+# 若没有 make,可以执行下面命令
+# apt-get install make
+
+# 若执行 make 后报错找不到 curses.h,可以执行下面命令
+# apt-get install libncurses5-dev
+
+# 若报错找不到 flex not found,可以执行下面两条命令
+# apt-get install flex
+# apt-get install bison
+
+# 再次运行 make menuconfig 弹出图形化配置页面后
+# 若使用默认配置,则直接按两次 Esc 键退出即可
+# 此时会在当前目录下生成 .config 文件
+
+# 编译 Linux 源码
+make bzImage -j4
+
+# 在编译过程中若报错 fatal error: openssl/opensslv.h,可执行下面命令
+# apt-get install libssl-dev
+# 若还出现同样的问题,可参考 https://blog.csdn.net/ComputerInBook/article/details/107380796 源码编译安装 openssl
+
+# 若出现「没有规则可制作目标“debian/canonical-certs.pem”」报错
+# 需要删除 .config 中相应的字段,总共有两处
+# 一处为 CONFIG_SYSTEM_TRUSTED_KEYS="debian/canonical-certs.pem"
+# 一处为 CONFIG_SYSTEM_REVOCATION_KEYS="debian/canonical-revoked-certs.pem"
+
+vim .config
+# 删除之后的样子如下(需要保留引号):
+# 一处为 CONFIG_SYSTEM_TRUSTED_KEYS=""
+# 一处为 CONFIG_SYSTEM_REVOCATION_KEYS=""
+
+# 若出现 BTF: .tmp_vmlinux.btf: pahole (pahole) is not available 错误,则执行下面命令
+# apt-get install dwarves
+
+# 若在过程中还出现其它问题,大多是因为缺少相关库导致的,直接用 apt-get install 即可
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/S4taTDbrM/index.html b/S4taTDbrM/index.html new file mode 100644 index 00000000..4a78e21b --- /dev/null +++ b/S4taTDbrM/index.html @@ -0,0 +1,632 @@ + + + + + + + + 正则表达式是如何运行的?——浅析正则表达式原理 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 正则表达式是如何运行的?——浅析正则表达式原理 +

+ + +
+ +
+
+

参考内容:
+《编译原理》
+实现简单的正则表达式引擎
+正则表达式回溯原理
+浅谈正则表达式原理

+
+

最近在一个业务问题中遇到了一个正则表达式性能问题,于是查了点资料去回顾了下正则表达式的原理,简单整理了一下就发到这里吧;另外也是想试试 Apple Pencil 的手感如何,画的太丑不要嫌弃哈。

+

有穷自动机

+

正则表达式的规则不是很多,这些规则也很容易就能理解,但是正则表达式并不能用来直接识别字符串,我们还需要引入一种适合转换为计算机程序的模型,我们引入的就是有穷自动机

+

在编译原理中通过构造有穷自动机把正则表达式编译成识别器,识别器以字符串x作为输入,当x是语言的句子时回答,否则回答不是,这正是我们使用正则表达式时需要达到的效果。

+

有穷自动机分为确定性有穷自动机(DFA)非确定性有穷自动机(NFA),它们都能且仅能识别正则表达式所表示的语言。它们有着各自的优缺点,DFA 导出的识别器时间复杂度是多项式的,它比 NFA 导出的识别器要快的多,但是 DFA 导出的识别器要比与之对应的 NFA 导出的识别器大的多。

+

大部分正则表达式引擎都是使用 NFA 实现的,也有少部分使用 DFA 实现。从我们写正则表达式的角度来讲,DFA 实现的引擎要比 NFA 实现的引擎快的多,但是 DFA 支持的功能没有 NFA 那么强大,比如没有捕获组一类的特性等等。

+

我们可以用带标记的有向图来表示有穷自动机,称之为转换图,其节点是状态,有标记的边表示转换函数。同一个字符可以标记始于同一个状态的两个或多个转换,边可以由输入字符符号标记,其中 NFA 的边还可以用ε标记。

+

之所以一个叫有确定和非确定之分,是因为对于同一个状态与同一个输入符号,NFA 可以到达不同的状态。下面看两张图就能明白上面那一长串的文字了。

+

图中两个圈圈的状态表示接受状态,也就是说到达这个状态就表示匹配成功。细心的你应该发现了两张图所表示的正则表达式是一样的,这就是有穷自动机神奇的地方,每一个 NFA 我们都能通过算法将其转换为 DFA,所以我们先根据正则表达式构建 NFA,然后再转换成相应的 DFA,最后再进行识别。

+
+

上图的画法在正则表达式很简单的时候还可以,如果遇到很复杂的正则表达式画起来还是挺费力的,如果想对自动机有更加深入的认识可以自行查阅相关资料。下面的图片是使用正则可视化工具生成的,对应的正则表达式是^-?\d+(,\d{3})*(\.\d{1,2})?$,它所匹配的字符串是数字/货币金额(支持负数、千分位分隔符)

+
+

回溯

+

NFA 引擎在遇到多个合法的状态时,它会选择其中一个并记住它,当匹配失败时引擎就会回溯到之前记录的位置继续尝试匹配。这种回溯机制正是造成正则表达式性能问题的主要原因。下面我们通过具体的例子来看看什么是回溯。

+
/ab{1,3}c/
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
正则文本
ab{1,3}cabbbc
ab{1,3}cabbbc
ab{1,3}cabbbc
ab{1,3}cabbbc
ab{1,3}cabbbc
ab{1,3}cabbbc
+

上表中展示的是使用ab{1,3}c匹配abbbc的过程,如果把匹配字符串换成abbc,在第五步就会出现匹配失败的情况,第六步会回到上一次匹配正确的位置,进而继续匹配。这里的第六步就是「回溯

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
正则文本备注
ab{1,3}cabbc
ab{1,3}cabbc
ab{1,3}cabbc
ab{1,3}cabbc
ab{1,3}cabbc匹配失败
ab{1,3}cabbc回溯
ab{1,3}cabbc
+

会出现上面这种情况的原因在于正则匹配采用了回溯法。回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。它通常采用最简单的递归来实现,在反复重复上述的步骤后可能找到一个正确的答案,也可能尝试所有的步骤后发现该问题没有答案,回溯法在最坏的情况下会导致一次复杂度为指数时间的计算。

+

上面一段的内容来源于维基百科,精简一下就是深度优先搜索算法。贪婪量词、惰性量词、分支结构等等都是可能产生回溯的地方,在写正则表达式时要注意会引起回溯的地方,避免导致性能问题。

+

John Graham-Cumming 在他的博文 Details of the Cloudflare outage on July 2, 2019 中详细记录了因为一个正则表达式而导致线上事故的例子。该事故就是因为一个有性能问题的正则表达式,引起了灾难性的回溯,进而导致了 CPU 满载。

+
(?:(?:\"|'|\]|\}|\\|\d|(?:nan|infinity|true|false|null|undefined|symbol|math)|\`|\-|\+)+[)]*;?((?:\s|-|~|!|{}|\|\||\+)*.*(?:.*=.*)))
+
+

上面是引起事故的正则表达式,出问题的关键部分在.*(?:.*=.*)中,就是它引起的灾难性回溯导致 CPU 满载。那么我们应该怎么减少或避免回溯呢?无非是提高警惕性,好好写正则表达式;或者使用 DFA 引擎的正则表达式。

+

[0-9] 与 \d 的区别

+

此问题来源于Stackoverflow,题主遇到的问题是\d[0-9]的效率要低很多,并且给出了如下的测试结果,可以看到\d[0-9]慢了差不多一倍。

+
Regular expression \d           took 00:00:00.2141226 result: 5077/10000
+Regular expression [0-9]        took 00:00:00.1357972 result: 5077/10000  63.42 % of first
+Regular expression [0123456789] took 00:00:00.1388997 result: 5077/10000  64.87 % of first
+
+

出现这个性能问题的原因在于\d匹配的不仅仅是0123456789\d匹配的是所有的 Unicode 的数字,你可以从 Unicode Characters in the 'Number, Decimal Digit' Category 中看到所有在 Unicode 中属于数字的字符。

+

此处多提一嘴,[ -~]可以匹配 ASCII 码中所有的可打印字符,你可以查看 ASCII 码中的可显示字符,就是从" "(32)至"~"(126)的字符。

+

工具/资源推荐

+

正则表达式确实很强大,但是它那晦涩的语法也容易让人头疼抓狂,不论是自己还是别人写的正则表达式都挺头大,好的是已经有人整理了常用正则大全,也大神写了个叫做 VerbalExpressions 的小工具,主流开发语言的版本它都提供了,可以让你用类似于自然语言的方式来写正则表达式,下面是它给出的一个 JS 版示例。

+
// Create an example of how to test for correctly formed URLs
+const tester = VerEx()
+    .startOfLine()
+    .then('http')
+    .maybe('s')
+    .then('://')
+    .maybe('www.')
+    .anythingBut(' ')
+    .endOfLine();
+
+// Create an example URL
+const testMe = 'https://www.google.com';
+
+// Use RegExp object's native test() function
+if (tester.test(testMe)) {
+    alert('We have a correct URL'); // This output will fire
+} else {
+    alert('The URL is incorrect');
+}
+
+console.log(tester); // Outputs the actual expression used: /^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$/
+
+

文章大部分内容都是介绍的偏原理方面的知识,如果仅仅是想要学习如何使用正则表达式,可以看正则表达式语法或者 Learn-regex,更为详细的内容推荐看由老姚写的JavaScript 正则表达式迷你书

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/S8r7al_js/index.html b/S8r7al_js/index.html new file mode 100644 index 00000000..e93740cc --- /dev/null +++ b/S8r7al_js/index.html @@ -0,0 +1,541 @@ + + + + + + + + 如何抽取实体关系?——基于依存句法分析的事实三元组抽取 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 如何抽取实体关系?——基于依存句法分析的事实三元组抽取 +

+ + +
+ +
+
+

参考:
+HanLP 自然语言处理
+基于依存分析的开放式中文实体关系抽取方法
+命名实体三元组抽取参考自fact_triple_extraction

+
+

这一段时间一直在做知识图谱,卡在实体关系抽取这里几个月了,在 Github 上面看到有人使用卷积神经网络训练模型进行抽取,自己也尝试了一下,但是一直苦于没有像样数据去训练,而标注训练集又太费时间了,我不太愿意干体力活。另外自己也不会什么机器学习、深度学习之类的技术,而且毕业设计都是有时间要求的,所以采用了一个低档次的方法,基于依存句法分析的实体关系抽取,记录一下心得,方便日后忘记可以再找回来。

+

论文给出了 8 种中文关系的表达方式,并且最后给出了一个采用正则表达式语法指出表达,核心就是谓语动词表示关系,即关系表述中一定得有动词。

+
状语*动词+补语?宾语?
+
+

我不太赞同把宾语也当作关系表述的一部分,论文指出“p4生于山西”应该抽出(p4,山西,生于山西),我认为关系不应该表述为“生于山西”,所以我把关系表述改为下面的样子了。

+
状语*动词+补语?
+
+

这篇文章只是作为一个方法介绍,我自己先看了一遍,能够保证我下次看到这篇文章,可以立马回忆起自己的实现方法,希望你看了也能了解方法,看不懂的话,我表示抱歉,浪费您的时间了,我已经尽可能写到简单了。

+

先来看几个简单句子吧:

+
主谓宾关系:刘小绪 生于 四川
+// 这个三元组很明显:(刘小绪,生于,四川)
+
+
+动补结构:刘小绪 洗 干净 了 衣服
+// 如果套用主谓宾关系就是:(刘小绪,洗,衣服)
+// 但是这里描述的是一个状态,是刘小绪把衣服洗干净了
+// “干净”是动词“洗”的补语,所以还应该提取出一个如下三元组
+// (刘小绪,洗干净了,衣服)
+
+状动结构:父亲 非常 喜欢 跑步
+// 这句和上面很像,主谓宾关系是:父亲喜欢跑步
+// “非常”用于修饰“喜欢”
+// (父亲,非常喜欢,跑步)
+
+介宾关系:刘小绪 就职 于 学校
+// 如果直接把这个三元组抽取为(刘小绪,就职,学校),很别扭
+// “于”和“学校”是介宾关系,它们的关系应该是:就职于
+// (刘小绪,就职于,学校)
+
+宾语前置:海洋 由 水 组成
+// “海洋”是“组成”的前置宾语
+// “由”是“组成”的状语
+// “水”和“由”是介宾关系
+// 所以上面的句子没有明确的主谓关系,需要我们判断
+// 抽出的三元组应该为:(水,组成,海洋)
+
+

HanLP 提供了两种依存句法分析的器,默认采用的是基于神经网络的依存句法分析器。依存句法分析就是将句子分析成一棵依存句法树,描述各个词语之间的依存关系,即指出词语之间在句法上的搭配关系。

+

有了上面所说的依存句法树,其实我们只需要进行各种判断就可以了。先做出下面的一点说明,就拿第一个例子来说。

+
原文:刘小绪生于四川
+
+# 这是分词结果
+[刘小绪/nr, 生于/v, 四川/ns]
+
+#这是句法分析结果
+刘小绪 --(主谓关系)--> 生于
+生于 --(核心关系)--> ##核心##
+四川 --(动宾关系)--> 生于
+
+

为了方便理解,也为了方便程序的编写,我把他们组织成了下面的形式,为每一个词语都建一个依存句法字典。

+
刘小绪:{}
+生于:{主谓关系=[刘小绪], 动宾关系=[四川]}
+四川:{}
+
+

然后只需要写出类似于下面的程序段就可以抽出关系了。

+
// 主谓宾关系:刘小绪生于四川
+// dic是这个词语的依存句法字典
+if (dic.containsKey("主谓关系") && dic.containsKey("动宾关系")){
+    
+    // 当前的词语,用上面的例子来说,relation=“生于”
+    String relation = curWord.LEMMA;
+
+
+    // 用循环遍历,是因为关系列表里面不一定只有一个词语
+    for (CoNLLWord entity1:
+            dic.get("主谓关系")) {
+
+        for (CoNLLWord entity2:
+                dic.get("动宾关系")) {
+
+            System.out.println(entity1.LEMMA + "," + relation + "," + entity2.LEMMA);
+        }
+
+    }
+}
+
+

对于分词后的每个词语都进行上面程序段的操作。“刘小绪”和“四川”,关系字典都为空。而对于“生于”,关系列表里面既有主谓也有动宾,而自己本身就是动词,主谓宾就出来了。直接从主谓关系中拿出来词语作为 entity1,再拿上自己作为关系,最后拿出动宾关系中的词语作为 entity2。很明确的三元组(刘小绪,生于,四川)就出来了。

+

最后给出一个程序运行结果图吧。

+
+

我个人觉得效果还行,在简单句子上面表现的差强人意,在长句子上面表现的差劲。注意上文使用的第三方包随着时间的推移肯定会改一些接口,源码链接:entity_relation_extraction

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/S9me2CdAH/index.html b/S9me2CdAH/index.html new file mode 100644 index 00000000..917363b5 --- /dev/null +++ b/S9me2CdAH/index.html @@ -0,0 +1,585 @@ + + + + + + + + 如何保证快速加载网页?——详解浏览器缓存机制 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 如何保证快速加载网页?——详解浏览器缓存机制 +

+ + +
+ +
+
+

参考内容:
+彻底理解浏览器的缓存机制
+彻底弄懂HTTP缓存机制及原理

+
+

前端开发人员有大部分时间都在调整页面样式,如果页面没有按照自己预期的样式显示,可能想到的第一个解决方案就是清一下浏览器缓存,HTTP 缓存机制作为 Web 性能优化的重要手段,也应该是 Web 开发人员必备的基础知识。我们常说的浏览器缓存机制也就是 HTTP 缓存机制,它是根据 HTTP 报文的缓存标识运行的,所以首先要对 HTTP 报文有一个简单的了解。

+

HTTP 报文

+

HTTP 报文是浏览器和服务器间进行通信时所发的响应数据,所以 HTTP 报文分为请求(Request)报文和响应(Response)报文两种,浏览器向服务器发送的是请求报文,而服务器向浏览器发送的是响应报文。HTTP 请求报文由请求行、请求头、请求体组成,响应报文则由状态行、响应头、响应正文组成,与缓存有关的规则信息则都包含在请求头和响应头中。

+

缓存概述

+

浏览器与服务器通过请求响应模式来通信,当浏览器第一次向服务器发送请求并拿到结果后,会根据响应报文中的缓存规则来决定是否缓存结果,其简单的流程如下图:

+
+

浏览器每次发起请求都会先在浏览器缓存中查找该请求的结果和缓存标识,而且每次拿到响应数据后都会将该结果和缓存标识存入缓存中。HTTP 缓存的规则有多种,我们可以根据是否需要重新向服务器发起请求这一维度来分类,即有强制缓存协商缓存两类,也有人把协商缓存叫对比缓存。

+

强制缓存

+

我们先自己想一下,使用缓存是不是会有下面几种情况出现。

+
    +
  • +

    存在所需缓存并且未失效:直接走本地缓存即可;强制缓存生效;

    +
  • +
  • +

    存在所需缓存但已失效:本地缓存失效,携带着缓存标识发起 HTTP 请求;强制缓存失效,使用协商缓存;

    +
  • +
  • +

    不存在所需缓存:直接向服务器发起 HTTP 请求;强制缓存失效。

    +
  • +
+

控制强制缓存的字段分别是ExpiresCache-Control,并且Cache-Control的优先级高于Expires

+

Expires

+

Expires是 HTTP/1.0 控制网页缓存的字段,其值为服务器返回的该缓存到期时间,即下一次请求时,请求时间小于Expires值,就直接使用缓存数据。到了 HTTP/1.1,Expires已经被Cache-Control替代了。

+

Expires被替代的原因是因为服务端和客户端的时间可能有误差(比如时区不同或者客户端与服务端有一方时间不准确),这就会导致缓存命中误差,强制缓存就变得毫无意义。

+

Cache-Control

+

Cache-Control是 HTTP/1.1 中最重要的规则,主要取值为:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
取值规则
public所有内容都可以被缓存,包括客户端和代理服务器,纯前端可认为与private一样。
private所有内容只有客户端可以缓存,Cache-Control的默认值。
no-cache客户端可以缓存,但是是否缓存需要与服务器协商决定(协商缓存)
no-store所有内容都不会被缓存,既不是用强制缓存,也不使用协商缓存,为了速度快,实际上缓存越多越好,所以这个慎用
max-age=xxx缓存内容将在 xxx 秒后失效
+

我们可以看看下面这个例子,可以从截图中看到Expires是一个绝对值,而Cache-Control是一个相对值,此处为max-age=3600,即 1 小时后失效。在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control相比于Expires是更好的选择,所以同时存在时只有Cache-Control生效。

+
+

协商缓存

+

协商缓存,顾名思义就是需要双方通过协商来判断是否可以使用缓存。强制缓存失效后,浏览器带着缓存标识向服务器发起请求,由服务器根据缓存标识决定是否可以使用缓存,那自然而然就有协商缓存生效和协商缓存不生效两种情况了。

+
+

上图是协商缓存生效的流程,如果协商缓存不生效则返回的状态码为 200。协商缓存的标识也是在响应报文的响应头中返回给浏览器的,控制协商缓存的字段有Last-Modified / If-Modified-SinceEtag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高,所以同时存在时只有Etag / If-None-Match生效。

+

Last-Modified / If-Modified-Since

+

你可以往上翻一翻,看一下那张响应报文截图,其中有一个Last-Modified字段,它的值是该资源文件在服务器最后被修改的时间。

+

If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值。服务器收到该请求后,发现该请求头有If-Modified-Since字段,则会将If-Modified-Since与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为 200;否则则返回 304,代表资源无更新,可继续使用缓存文件。

+
+

Etag / If-None-Match

+

Etag是服务器响应请求时,返回当前资源文件的一个由服务器生成的唯一标识。

+

If-None-Match则是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,如果一致则就返回 304,代表资源无更新,可以继续使用缓存文件;否则重新返回资源文件,状态码为200,

+
+

disk cache 与 memory cache

+

我们可以通过浏览器调试工具查看强制缓存是否生效,如下图所示,状态码为灰色的请求就代表使用了强制缓存,请求对应的 size 显示了该缓存存放的位置,那么什么时候用 disk 什么时候用 memory 呢?

+
+

猜都能猜出来,肯定是优先使用内存(memory)中的缓存,然后才用硬盘(disk)中的缓存。

+

内存缓存具有快速读取的特点,它会将编译解析后的文件直接存入该进程的内存中,但是一旦进程关闭了,该进程的内存就会被清空,所以如果你将一个网页关闭后再打开,那么缓存都会走硬盘缓存,而如果你只是刷新网页,那有部分缓存走的就是内存缓存。

+

浏览器一般会再 js 和图片等文件解析执行后直接存入内存缓存中,当刷新页面时,这部分文件只需要从内存缓存中读取即可,而 css 文件则会存入硬盘中,所以每次渲染页面都需要从硬盘中读取文件。

+

总结

+

到这里偷懒一下子了,找到人家画的一张图,看图就行了。

+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/TgsXu70q2/index.html b/TgsXu70q2/index.html new file mode 100644 index 00000000..d71ef89d --- /dev/null +++ b/TgsXu70q2/index.html @@ -0,0 +1,470 @@ + + + + + + + + 那些经历过的人生谎言 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 那些经历过的人生谎言 +

+ + +
+ +
+

哈佛大学从 1938 年开始做了一个研究,研究内容就是「到底什么样的人活的最幸福?」,研究组在 75 年时间里跟踪了 724 个不同的人,到教授在 TED 演讲的时候仍然有 60 人在世并仍参与研究,大多数人都已经 90 岁了,现在已经在开始研究他们的子孙后代,并且人数达到了 2000 多人。

+

这 724 人后来成了医生、律师、砖匠等,有人成了酒鬼、有人患上了精神分裂症,还有一位成了美国总统。估计也只有哈佛这样的地方才会去搞如此变态的研究,有足够的资金去承受这样理想的研究。人人都想要活的更加幸福,哈佛 75 年研究汇集成几万页的数据,然而结果只有一句话:良好的人际关系能让人更加健康和快乐,和我们所追求的财富、名望、努力工作等都没关系。下面是原视频内容。

+ +

小学语文课本上面有一个课后题要求自己编写《负荆请罪》故事的后续,记得当时老师把这个题目当作家庭作业留给学生了。回家之后只顾着玩泥巴、看电视了,第二天早上第一节课就是语文课,老师一上来就抽不同的同学给大家读自己写的故事后续。

+

在其他同学读故事的时间,我慌忙的写了一段应付老师的抽查。不出所料,我也被抽中起来读自己编的故事了,没想到慌慌张张编的故事居然深得老师之心,还被老师当作范文给大家又读了一遍,因为我在故事里面用了「连忙」、「赶紧」这样的一些词汇,能表现出廉颇和蔺相如之间的客气等等。

+

但我清晰记得那时候只想在被老师抽中之前把作业写了,胡编乱造完全不管写的好不好,只需要有一段话能够证明自己是做了作业的,万万没想到那么简单的一段话被老师赋予了数不清的情感。可类比的是各种网络平台上的文章,尤其是某个公司哪一位老总成功上位,铺天盖地的文章会把这个人肠子都翻出来看看,就好像写手比主人公还要了解自己。

+
+

最开始写文章是大四时候太闲想找点事做,断断续续到现在也算是写了 2 年多了,合起来有 100 来篇文章的样子。偶尔我会把以前写的文章搬到其它平台,比如把以前写的讲磁盘原理和自己做自然语言处理的两篇文章搬到 InfoQ,两篇文章都被平台置顶且精选至首页,而在公众号中讲磁盘原理的那篇文章阅读量是最低的。

+

前不久写了一篇与拼多多等电商平台有关的内容,一发出来公众号后台立马将近 20 个取关,让我这个本就没几个读者朋友的公众号雪上加霜。然后我鼓起勇气把这篇文章复制到满是大佬的生财有术社群里面去,隔了几天就被评为了精华帖,拿到一颗龙珠,8000 元到手。

+

去年年底想起了和师兄们一起刷牛客网题目的时光,随手就把 18 年写的一点文字贴上去了,18 年时候我的技术还很弱鸡,没想到贴上去却获得了还不错的反馈,一篇还被牛客小编标记为精选帖。于是我把 19 年遇到的关于服务器性能优化的一篇文章贴上去,那篇是我花了一个月时间查各种资料再加上自己实践的经验,现在看来都依然觉得写的非常不错,然而这篇文章在牛客网连一个赞都没有。

+

我写文章是兴趣驱动,写的好写得坏都不咋关心,但偶尔会有那么一两篇文章自己花了很多的精力去写,写完还自嗨一下写的不错,结果一发出来就打脸了。也出现过我自己敷衍了事像是一时应付写的文章,却被好几个大 V 看中,希望转载的。

+

忘记 18 年怎么加了「寒泉子」微信的,当时他输出的文章很多是关于 Java 虚拟机性能优化的,没有一篇文章我能读懂的,所以寒泉子的文章对我完全没有作用,围观他的朋友圈只是看大佬是如何成长的,那时候寒泉子创建「笨马科技」公司不久(现在笨马已经获得了高瓴资本的投资)。成功创业且快速的成长让寒泉子得到了更多人的关注,寒泉子的文章相比那时候阅读量提高了很多,但我依旧看不到他文章对我的价值,但我知道看不到好文章价值的原因是因为我的水平不够。

+

说了这么多就是想表达很多文字不是没有价值,只是因为你的水平太低而看不到他的价值(水平太高也会看不到价值),并不一定都是别人写的差。一篇文章给了你一个关键字那他就是有价值的,我们更多的是应该去培养自己抓线索的能力,一个不起眼的词语就能揪出来太多有趣的东西,只是大多数人即使把方法告诉他了都还是不会操作,需要努力提升自己的信息素养。

+
+

读了些书、见了些人、经历了些事,总结了几个人生的高级谎言。中年危机是不思进取的人经历的,尽量不要被社会上那些营销话术给骗了,少看那些什么发展各种副业挣钱的广告,把搞这些的精力好好用到工作上薪资早早的就涨起来了,尤其是玩副业几年都没有玩出花样的人,更应该把心收一收踏踏实实工作。

+

大家都知道「复利效应」这回事,比如 100 块钱按每年 5% 的利息计算,50 年后就能变成 1000 多块钱,看这个收益是不是非常可观?这个谎言的真相是你找不到年化能长期稳定在 5% 的投资产品,市面上各种理财培训都会说这个谎言,一些人自己本来没有赚到钱,却通过教别人怎么赚钱而赚到钱了。

+

30 岁之后身体就不行了、女人生娃一定要在 28 岁之前。乍听起来没有什么问题,但仔细一想就会发现这里采用年龄进行量化就不对,科学的量化应该是采用身体机能的各项参数。年龄只是一个表象的东西,成熟也同样与年龄无关,而与经历相关。

+

观察了一些电信诈骗的案例,有认识的朋友也有从未谋面的网友,自己也协助警方做了一次证人。会主动给你发营业执照、自己工牌一类信息的就是骗子。如果销售多次给你强调自己是正规公司,那他们基本就是不正规公司。凡是你根本不认识的人,但一上来就说要带你赚钱的,简单的当骗子处理即可,百分百不会错杀一个好人。

+

每个人都有一套稳定且自洽的逻辑闭环,自己的观念代表的是自己这个人,观念被否定相当于自己这个人被否定,可能这就是会吵架的一个内在原因,「老顽固」大概也是这么来的。人天生就喜欢呆在舒适区,不同观点发生碰撞就是把稳态打破,别人否定自己是一件极难接受的事情,为什么会难以接受呢?因为打破稳态需要思考,而思考是一件费能量的事情,生物的天性就是要节省能量,这大概和祖先生活的环境有关系,虽然现在的环境已经不需要这样做了,但遗憾的是基因却还没有适应,自己努力克服基因的缺陷吧。

+
+

学生 A 就读于成都一所中法合办的小学,进出这个学校多次之后的感受就是学校有钱,学生家里也都比较有钱,所以大部分学生都属于调皮类型的,综合该校多名学生口中的信息,每个班至少气跑了两名以上的老师。学生 A 在我处学习编程,他们班的同学都属于比较社会的那种,班主任老师也被这帮学生训练的贼有经验。

+

学校有一个厕所坏了,所以物管处就把这个厕所给锁上了,然而这种被锁上的门总是能激起学生的好奇心,很多学生下课就跑去门缝观察 💩,甚至有的学生还翻墙进去观察 💩,里面最有创意的学生应属学生 A。

+

厕所里面被拖把遮住了,学生 A 在门缝寻找到了一个比较好的观察角度,大吼了一句「奥利给溢出来了」,另一个学生并没有看到所以就翻墙进去看,却一不小心掉进坑里了,学生 A 嘴里吼着「XXX 满是奥利给的跑出来了」跑开。估计这是有史以来「奥利给」这么正能量的词语被侮辱的最惨的一次。

+

此事被学生 A 的班主任老师知道了,要说就得佩服这种班级的老师,不打不骂只让学生以「奥利给」为主题写一篇观察日记,要求学生把观察 💩的过程详细描述出来。学生 A 和班主任老师配合的这出戏在学校广为流传,大大提高了学生 A 的名声。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/UGoTtZqQ-y/index.html b/UGoTtZqQ-y/index.html new file mode 100644 index 00000000..e3070e27 --- /dev/null +++ b/UGoTtZqQ-y/index.html @@ -0,0 +1,655 @@ + + + + + + + + 编译原理 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 编译原理 +
+ + +
+

+ + 正则表达式是如何运行的?——浅析正则表达式原理 + +

+ +
+ + + + +
+ +
+ +参考内容: +《编译原理》 +实现简单的正则表达式引擎 +正则表达式回溯原理 +浅谈正则表达式原理 + +最近在一个业务问题中遇到了一个正则表达式性能问题,于是查了点资料去回顾了下正则表达式的原理,简单整理了一下就发到这里吧;另外也是想试试 Apple Pencil 的手感如何,画的太丑不要嫌弃哈。 +有穷自动机 +正则表达式的规则不是很多,这些规则也很容易就能理解,但是正则表达式并不能用来直接识别字符串,我们还需要引入一种适合转换为计算机程序的模型,我们引入的就是有穷自动机。 +在编译原理中通过构造有穷自动机把正则表达式编译成识别器,识别器以字符串x作为输入,当x是语言的句子时回答是,否则回答不是,这正是我们使用正则表达式时需要达到的效果。 +有穷自动机分为确定性有穷自动机(DFA)和非确定性有穷自动机(NFA),它们都能且仅能识别正则表达式所表示的语言。它们有着各自的优缺点,DFA 导出的识别器时间复杂度是多项式的,它比 NFA 导出的识别器要快的多,但是 DFA 导出的识别器要比与之对应的 NFA 导出的识别器大的多。 +大部分正则表达式引擎都是使用 NFA 实现的,也有少部分使用 DFA 实现。从我们写正则表达式的角度来讲,DFA 实现的引擎要比 NFA 实现的引擎快的多,但是 DFA 支持的功能没有 NFA 那么强大,比如没有捕获组一类的特性等等。 +我们可以用带标记的有向图来表示有穷自动机,称之为转换图,其节点是状态,有标记的边表示转换函数。同一个字符可以标记始于同一个状态的两个或多个转换,边可以由输入字符符号标记,其中 NFA 的边还可以用ε标记。 +之所以一个叫有确定和非确定之分,是因为对于同一个状态与同一个输入符号,NFA 可以到达不同的状态。下面看两张图就能明白上面那一长串的文字了。 +图中两个圈圈的状态表示接受状态,也就是说到达这个状态就表示匹配成功。细心的你应该发现了两张图所表示的正则表达式是一样的,这就是有穷自动机神奇的地方,每一个 NFA 我们都能通过算法将其转换为 DFA,所以我们先根据正则表达式构建 NFA,然后再转换成相应的 DFA,最后再进行识别。 + +上图的画法在正则表达式很简单的时候还可以,如果遇到很复杂的正则表达式画起来还是挺费力的,如果想对自动机有更加深入的认识可以自行查阅相关资料。下面的图片是使用正则可视化工具生成的,对应的正则表达式是^-?\d+(,\d{3})*(\.\d{1,2})?$,它所匹配的字符串是数字/货币金额(支持负数、千分位分隔符)。 + +回溯 +NFA 引擎在遇到多个合法的状态时,它会选择其中一个并记住它,当匹配失败时引擎就会回溯到之前记录的位置继续尝试匹配。这种回溯机制正是造成正则表达式性能问题的主要原因。下面我们通过具体的例子来看看什么是回溯。 +/ab{1,3}c/ + + + + +正则 +文本 + + + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + + +上表中展示的是使用ab{1,3}c匹配abbbc的过程,如果把匹配字符串换成abbc,在第五步就会出现匹配失败的情况,第六步会回到上一次匹配正确的位置,进而继续匹配。这里的第六步就是「回溯」 + + + +正则 +文本 +备注 + + + + +ab{1,3}c +abbc + + + +ab{1,3}c +abbc + + + +ab{1,3}c +abbc + + + +ab{1,3}c +abbc + + + +ab{1,3}c +abbc +匹配失败 + + +ab{1,3}c +abbc +回溯 + + +ab{1,3}c +abbc + + + + +会出现上面这种情况的原因在于正则匹配采用了回溯法。回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。它通常采用最简单的递归来实现,在反复重复上述的步骤后可能找到一个正确的答案,也可能尝试所有的步骤后发现该问题没有答案,回溯法在最坏的情况下会导致一次复杂度为指数时间的计算。 +上面一段的内容来源于维基百科,精简一下就是深度优先搜索算法。贪婪量词、惰性量词、分支结构等等都是可能产生回溯的地方,在写正则表达式时要注意会引起回溯的地方,避免导致性能问题。 +John Graham-Cumming 在他的博文 Details of the Cloudflare outage on July 2, 2019 中详细记录了因为一个正则表达式而导致线上事故的例子。该事故就是因为一个有性能问题的正则表达式,引起了灾难性的回溯,进而导致了 CPU 满载。 +(?:(?:\&quot;|'|\]|\}|\\|\d|(?:nan|infinity|true|false|null|undefined|symbol|math)|\`|\-|\+)+[)]*;?((?:\s|-|~|!|{}|\|\||\+)*.*(?:.*=.*))) + +上面是引起事故的正则表达式,出问题的关键部分在.*(?:.*=.*)中,就是它引起的灾难性回溯导致 CPU 满载。那么我们应该怎么减少或避免回溯呢?无非是提高警惕性,好好写正则表达式;或者使用 DFA 引擎的正则表达式。 +[0-9] 与 \d 的区别 +此问题来源于Stackoverflow,题主遇到的问题是\d比[0-9]的效率要低很多,并且给出了如下的测试结果,可以看到\d比[0-9]慢了差不多一倍。 +Regular expression \d took 00:00:00.2141226 result: 5077/10000 +Regular expression [0-9] took 00:00:00.1357972 result: 5077/10000 63.42 % of first +Regular expression [0123456789] took 00:00:00.1388997 result: 5077/10000 64.87 % of first + +出现这个性能问题的原因在于\d匹配的不仅仅是0123456789,\d匹配的是所有的 Unicode 的数字,你可以从 Unicode Characters in the 'Number, Decimal Digit' Category 中看到所有在 Unicode 中属于数字的字符。 +此处多提一嘴,[ -~]可以匹配 ASCII 码中所有的可打印字符,你可以查看 ASCII 码中的可显示字符,就是从&quot; &quot;(32)至&quot;~&quot;(126)的字符。 +工具/资源推荐 +正则表达式确实很强大,但是它那晦涩的语法也容易让人头疼抓狂,不论是自己还是别人写的正则表达式都挺头大,好的是已经有人整理了常用正则大全,也大神写了个叫做 VerbalExpressions 的小工具,主流开发语言的版本它都提供了,可以让你用类似于自然语言的方式来写正则表达式,下面是它给出的一个 JS 版示例。 +// Create an example of how to test for correctly formed URLs +const tester = VerEx() + .startOfLine() + .then('http') + .maybe('s') + .then('://') + .maybe('www.') + .anythingBut(' ') + .endOfLine(); + +// Create an example URL +const testMe = 'https://www.google.com'; + +// Use RegExp object's native test() function +if (tester.test(testMe)) { + alert('We have a correct URL'); // This output will fire +} else { + alert('The URL is incorrect'); +} + +console.log(tester); // Outputs the actual expression used: /^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$/ + +文章大部分内容都是介绍的偏原理方面的知识,如果仅仅是想要学习如何使用正则表达式,可以看正则表达式语法或者 Learn-regex,更为详细的内容推荐看由老姚写的JavaScript 正则表达式迷你书 + +
+ + Read More ~ +
+
+
+ +
+

+ + 正则表达式入门,基础语法详解 + +

+ +
+ + + + +
+ +
+ 这两天一直在时不时的和 Neo4j 图数据库打交道。它的查询语句可以使用正则表达式,有一段时间没有自己写过正则表达式了,现在处于能看懂别人写的正则表达式,但是自己写不出来,语法规则都忘了。为了方便接下来的工作,所以特地复习复习正则表达式的语法。 +正则表达式简介 +正则表达式是用来匹配字符串的一系列匹配符,具备简介高效的特点,在很多语言中都有支持(java、python、javascript、php 等等)。在 windows 的 cmd 命令中也同样支持,例如使用命令 dir j*,那么只会罗列出所有以j开头的文件和文件夹。 +正则表达式基本语法 +正则表达式在在不同语言的支持语法略有不同,本文采用js的进行说明。js 中使用正则表达式的方法为str.match(/表达式/),即需要加两个斜杠。以下所有的代码段第一行为代码,第二行为返回结果,实验是在 chrome 控制台进行的。 +一直认为最好的学习方式就是实际操作,理论谁都能讲一大堆,但是实际做没做出来还真不知道。一个奇葩现象就是教软件工程的老师可能并没有在软件行业待过。 +普通匹配符 +普通匹配符能匹配与之对应的字符,默认区分大小写。 +&quot;Hello Regx&quot;.match(/H/) +[&quot;H&quot;, index: 0, input: &quot;Hello Regx&quot;, groups: undefined] + +正则标记符 + +i :不区分大小写 +g :全局匹配 +m :多行匹配(暂不管它,我用的少) + +参数直接加在最后一个斜杠的后面,比如&quot;Hello Regx&quot;.match(/regx/i),可以加多个参数。 +&quot;Hello Regx&quot;.match(/regx/i) +[&quot;Regx&quot;, index: 6, input: &quot;Hello Regx&quot;, groups: undefined] + +之前是表达式一旦匹配成功,就不再向字符串后面查找了,加上 g 后,表示进行全局查找。最后返回的是一个数组。 +&quot;Hello Regx&quot;.match(/e/g) +(2) [&quot;e&quot;, &quot;e&quot;] + +多匹配符 + +\d :匹配数字,即 0~9 +\w :匹配数字、字母、下划线 +. :匹配除换行的所有字符 + +需要注意的是,上面所有的匹配符都只能匹配一个字符。 +&quot;Hello 2018&quot;.match(/\d/g) +// 使用\d,匹配字符串中的所有数字 +(4) [&quot;2&quot;, &quot;0&quot;, &quot;1&quot;, &quot;8&quot;] + + +&quot;Hello 2018&quot;.match(/\w/g) +// 使用\w,匹配所有的数字和字母,需要注意没有匹配到空格 +(9) [&quot;H&quot;, &quot;e&quot;, &quot;l&quot;, &quot;l&quot;, &quot;o&quot;, &quot;2&quot;, &quot;0&quot;, &quot;1&quot;, &quot;8&quot;] + + +&quot;Hello 2018&quot;.match(/./g) +// 使用.,匹配所有字符,包括空格 +(10) [&quot;H&quot;, &quot;e&quot;, &quot;l&quot;, &quot;l&quot;, &quot;o&quot;, &quot; &quot;, &quot;2&quot;, &quot;0&quot;, &quot;1&quot;, &quot;8&quot;] + + +&quot;Hello 2018&quot;.match(/\d\w./g) +// 分析一下这个为什么匹配到的是201, +// 首先\d找到第一个数字2,匹配成功,紧接着\w匹配到0,然后.匹配到1 +// 整个正则表达式匹配成功,返回201 +[&quot;201&quot;] + + +&quot;Hello 20\n18&quot;.match(/\d\w./g) +// 这里匹配不成功,因为.不能匹配换行符,所以返回null +null + + +&quot;Hello 2018&quot;.match(/\w.\d/g) +// 首先看这个正则式,\w.\d,它要求最后一个字符是数字 +// \w.能一直匹配到空格,但是因为得满足\d,所以第一个匹配成功的是0 2 +// 因为是全局匹配,所以会接着匹配后面的018,也匹配成功 +(2) [&quot;o 2&quot;, &quot;018&quot;] + +自定义匹配符 +比如中国的手机号都是以 1 开头,第二位只能是 3、4、5、7、8,第 3 位只要是数字就行。如何匹配这样的字符串? + +[] :匹配[]中的任意一个字符 + +&quot;152&quot;.match(/1[34578]\d/) +// 第二个字符可以选择中括号中的任意一个 +[&quot;152&quot;, index: 0, input: &quot;152&quot;, groups: undefined] + +如果在 [] 添加了 ^,代表取反。即 [^] 表示除了中括号中的字符都满足。 +&quot;152&quot;.match(/1[^34578]\d/) + +null + + +&quot;1a2&quot;.match(/1[^34578]\d/) +// 只要不是[]中的字符,都满足,包括回车符 +[&quot;1a2&quot;, index: 0, input: &quot;1a2&quot;, groups: undefined] + +修饰匹配次数 +我们的手机号有 11 位,除了前 2 位有要求,其他9位度没有要求,那么是不是正则表达式就应该这样写呢? +1[^34578]\d\d\d\d\d\d\d\d\d + +很明显,这样写太麻烦,肯定有更好的方式,这里就可以修饰一下匹配次数啦。 + +? :最多出现 1 次 ++ :至少出现 1 次 +* :出现任意次数 +{} :分下面四种情况 + +{n}代表前面的匹配符出现 n 次 +{n, m}出现次数在 n~m 之间 +{n, }至少出现 n 次 +{, m}最多出现 m 次 + + + +例子很简单,一看就懂,不浪费时间。 +&quot;15284750845&quot;.match(/1[34578]\d{9}/) +[&quot;15284750845&quot;, index: 0, input: &quot;15284750845&quot;, groups: undefined] + + +&quot;15&quot;.match(/1[34578]\d?/) +[&quot;15&quot;, index: 0, input: &quot;15&quot;, groups: undefined] + + +&quot;152&quot;.match(/1[34578]\d?/) +[&quot;152&quot;, index: 0, input: &quot;152&quot;, groups: undefined] + + +&quot;152&quot;.match(/1[34578]\d+/) +[&quot;152&quot;, index: 0, input: &quot;152&quot;, groups: undefined] + + +&quot;15&quot;.match(/1[34578]\d+/) +null + +完整匹配 +按照上面的写法会出现下面的问题。 +&quot;ya15284750845&quot;.match(/1[34578]\d{9}/) +// 不是电话号码,也能匹配成功,需要进一步改进 +[&quot;15284750845&quot;, index: 2, input: &quot;ya15284750845&quot;, groups: undefined] + + +^ :在 [] 中代表取反,但在外面代表从开始匹配 + +&quot;ya15284750845&quot;.match(/^1[34578]\d{9}/) +// 现在就能从一开始匹配而且还得符合正则式才算匹配成功 +null + + +// 但是依旧会出现下面的问题 +&quot;1528475084523255&quot;.match(/^1[34578]\d{9}/) +// 不是电话号码也能匹配成功,还要改进 +[&quot;15284750845&quot;, index: 0, input: &quot;1528475084523255&quot;, groups: undefined] + + +$ :代表持续匹配到结束 + +&quot;1528475084523255&quot;.match(/^1[34578]\d{9}$/) +// 现在就能保证正确了,有^表示从开始匹配; +// 有$表示持续匹配到结束,即完全匹配 +null + +/* +需要注意的是,一个字符串从开始匹配和从结束匹配都没问题, +不代表整个字符串就没问题,比如 15284750845-15284750845 +这个字符串从开始和从结束匹配都能成功,但实际上是错的 +*/ + +特殊符号 +到这里发现正则表达式确实很强大,仅仅几个简单的符号就能匹配字符串,但是如果我们要匹配的字符本身就是前面用到的符号怎么办呢? + +匹配像$、^等特殊符号时,需要加转义字符\ + +&quot;1.&quot;.match(/./) +//因为.能匹配除换行的所有字符,所以匹配到1 +//但实际上我们想匹配.这个字符 +[&quot;1&quot;, index: 0, input: &quot;1.&quot;, groups: undefined] + + +&quot;1.&quot;.match(/\./) +// 只需要加一个转义字符就可以了,其他类似 +[&quot;.&quot;, index: 1, input: &quot;1.&quot;, groups: undefined] + +条件分支 +比如现在想匹配图片的文件名,包括 jpg、png、jpeg、gif 等等,这是多个选项,所以需要像编程语言一样,应该具备条件分支结构。 + +| :条件分支 +() :有两层含义 + +括号中的内容成为一个独立的整体 +括号的内容可以进行分组,单独匹配,若不需要此功能,则( ?: ) + + + +&quot;1.jpg&quot;.match(/.+\.jpe?g|gif|png/) +// 这样就可以满足条件分支了,不过下面又出问题了 +[&quot;1.jpg&quot;, index: 0, input: &quot;1.jpg&quot;, groups: undefined] + + +&quot;1.png&quot;.match(/.+\.jpe?g|gif|png/) +// 这里没有匹配到.和前面的文件名 +[&quot;png&quot;, index: 2, input: &quot;1.png&quot;, groups: undefined] + + +/* +其实我们想告诉它的是,.和后面的每一个条件分支的值都是一个独立的整体 +但是它把.+\.jpe?g、gif、png当成了各自独立的整体 +我们并不想让它这样切分,所以我们来告诉它怎么分才是正确的 +*/ + + +&quot;1.png&quot;.match(/.+\.(jpe?g|gif|png)/) +// 现在可以匹配成功了,但是它多匹配了一个 +// 因为括号的内容可以进行分组,单独匹配 +(2) [&quot;1.png&quot;, &quot;png&quot;, index: 0, input: &quot;1.png&quot;, groups: undefined] + + +// 所以最终写法如下 +&quot;1.png&quot;.match(/.+\.(?:jpe?g|gif|png)/) +[&quot;1.png&quot;, index: 0, input: &quot;1.png&quot;, groups: undefined] + +贪婪与懒惰 +// 首先看一个例子 +&quot;aabab&quot;.match(/a.*b/) +[&quot;aabab&quot;, index: 0, input: &quot;aabab&quot;, groups: undefined] + + +/* +上面的匹配没有什么问题,但实际上aab也是可以的 +也就是aab也是符合条件的,那又是为什么呢? +*/ + +因为在正则表达式中,默认是贪婪模式,尽可能多的匹配,可以在修饰数量的匹配符后面添加 ?,则代表懒惰。 +// like this (^__^) +&quot;aabab&quot;.match(/a.*?b/) +[&quot;aab&quot;, index: 0, input: &quot;aabab&quot;, groups: undefined] + +到这里应该就差不多了,再深入的,就自我查询知识了。配一张正则表达式速查表。 + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/VJe_7M1YY/index.html b/VJe_7M1YY/index.html new file mode 100644 index 00000000..8c17d4b9 --- /dev/null +++ b/VJe_7M1YY/index.html @@ -0,0 +1,451 @@ + + + + + + + + 谈一下写作的重要性,每个人都应该养成写作的习惯 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 谈一下写作的重要性,每个人都应该养成写作的习惯 +

+ + +
+ +
+

关于写作的重要性,你可能在其他地方也见过一些描述,大致的说法都差不多,如果本文某些字句与你已经见过的文章有雷同,那纯属巧合,我仅从个人这几个月的感受出发来说。

+

我从三月份开始,在公众号上面发一些文章,其实从这几个月的表现来看,是把公众号当作博客来用了,我的初衷没有想着靠公众号赚钱,所以我的分享很随意,主要是技术、认知、阅读方面的东西。如果不喜欢,主动权在你手里,大可取关;如果喜欢,我的文章能让你少走一些弯路,那么我自己的目的达到了,给不给赞赏无所谓,其实心里还是希望你给的,咱没必要把自己放的太清高。

+

今天微信订阅号改版了,新版的订阅号基本形态变成了信息流,关于产品我还不太懂,但是我觉得这次改版对于作者的个人品牌形成不利,没内容的公众号应该会被取关很多粉丝,但是忠实粉丝也更难找到自己喜欢的“博主”了,所以以后,请认准作者。

+

很多人也有撰写博客的习惯,我很赞同这个做法,关于写作的好处我觉得有以下几点,只有认真去做了,才能体会到它带给自己的成长。

+

第一,写作是整理自己思维的过程,写作能力是一种重要的能力,不一定要多好的文采,但是不是每个人都能把语言组织的有条有理。现在的时代,没有铁饭碗,你需要不停的学习才能立于不败之地,很多人觉得写作是浪费时间,其实不然,写作是对已学知识的整理过程,输出其实是更高层次的输入。拿我之前写的朴素贝叶斯实现拼写检查器来说,其中那个贝叶斯公式推导是我花了很多分钟才想出来的,就好像老师教给学生一碗水,那么老师就必须具备一桶水才行。

+

第二,写作是个人品牌的建立过程,可以说微信已经成为了中国互联网的小小代名词,农村大叔大妈手机上面最可能出现的软件就是微信,微信打通中国互联网的最后一环,在这么大的平台上,你分享的内容对别人来说是有帮助的,那么你的个人品牌就已经逐渐在形成了,这是个人影响力的提升。个人品牌在以后一定会很重要,个人品牌在日常生活其实有体现,我相信每个人的微信都会屏蔽几个人的朋友圈信息吧,经常在朋友圈发一些无用信息、垃圾信息,这其实就是个人品牌的损失。

+

第三,通过写作你能交到很多朋友,而且通过这种方式所交到的朋友都是优秀的,他们会对你的成长起到促进作用,而你也会因为和他们交流而在不知不觉中得到提升,真正的朋友是相互促进的。我这几个月交到的朋友,刷新了我的认知,偏见来源于无知,在这个过程,我的认知得到了很大的提升,认知这玩意也不太好描述。举个例子,大概在大二的时候,我看到一篇文章说高中物理中所学的电子、质子等概念是错的,将要被新的知识体系取代,那时二话不说就转发朋友圈了,但没过几天就发现这其实是一个虚假信息;前段时间,中兴被美国制裁了,然后就有一些自媒体作者为了吸引流量,乱写一通什么华为宣布将要退出美国市场的消息,我的第一直觉就是这是虚假信息,然后我去验证了自己猜测的正确性,而周围很多人竟无脑式的选择了相信这条消息,还给我分析为什么华为要退出美国市场。这在我看来就是认知水平的一个体现,或者贴切一点叫信息素养(这个词不是我发明的),我现在对于信息的掌握已经明显快于周围的同学了,而且掌握的也比周围同学更加全面。

+

最后说一点,没必要为了写作而写作,经常在知识星球看到有人问问题,说自己的写不出东西来,怎么办?这就是自己的输入不够,自己体内没有实质的东西,如何能达到输出呢?更别说高质量的输出了。

+

我以后的文章主要是机器学习和提升认知方面的,最近更文有点慢,其一是自己也刚开始接触机器学习不久,要写出一篇比较好的文章,需要几天的输入;其二马上要毕业了,繁忙于各种琐碎的事情无法自拔。

+

总的来说,写作利大于弊,如果你有闲心,看一下我几个月前发的文章,再和我现在的文章做个对比,你能看到我的变化,在文章逻辑、排版等等方面都或多或少的有一些提升,所以我建议你如果空闲时间比较多,也可以尝试尝试写作,自己的成长过程会在字里行间被记录下来。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/WfB5h55oQ/index.html b/WfB5h55oQ/index.html new file mode 100644 index 00000000..4a783306 --- /dev/null +++ b/WfB5h55oQ/index.html @@ -0,0 +1,796 @@ + + + + + + + + 动态规划实例——换零钱的方法数(C++详解版) | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 动态规划实例——换零钱的方法数(C++详解版) +

+ + +
+ +
+
+

原写了 Java 版本的如何求解换钱的方法数,近期进行了一些细节上的补充,以及部分错误更正,将语言换为了 C++ 语言。

+
+

基础题目

+

假设你现在拥有不限量的 1 元、5 元、10 元面值纸币,路人甲希望找你换一些零钱,路人甲拿出的是一张 100 元面值的纸币,试求总共有多少种换零钱的方法?

+

分析:因为总共只有 3 种面值小额纸币,所以将所有可能进行枚举,直接暴力解决即可。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int slove() {
+	int ans = 0;
+	// 10 元张数
+	for(int i = 0; i <= 10; i++) {
+		// 5 元张数
+		for(int j = 0; j <= 20; j++) {
+			// 1 元张数
+			for(int k = 0; k <= 100; k++) {
+				int cur = i*10 + j*5 + k*1;
+				if(cur == 100) {
+					ans++;
+				}
+			}
+		}
+	}
+	return ans;
+}
+
+int main()
+{
+	cout<<slove();
+}
+
+

递归求解

+

基础题目中是拥有固定种类的小额纸币,即使再多几种小额纸币也没关系,大不了在嵌套几个循环就能解决。现在需要将题目的难度加大一点,改为小额纸币的种类和需要换零钱的总额由用户输入,即小额纸币种类和总额都不在固定,那么如何解决?

+

输入共有三行:

+
    +
  • 第一行:小额纸币种类数量
  • +
  • 第二行:不同小额纸币的面值
  • +
  • 第三行:需要换零钱的总额
  • +
+

分析:虽然现在种类和总额都是变量了,但是上文中的基础版本还是被包含在此问题中,所以我们还是以上文中的 1 元、5 元、10 元换 100 元进行分析,找一找除了枚举是否还有其他方法解决。

+

我们先固定一种零钱的数量,剩下的钱用剩余零钱去兑换,即:

+
    +
  • 用 0 张 1 元换,剩下的用 5、10 元换,最终方法数为 count0;
  • +
  • 用 1 张 1 元换,剩下的用 5、10 元换,最终方法数为 count1;
  • +
  • ......
  • +
  • 用 100 张 1 元换,剩下的用 5、10 元换,最终方法数为 count100;
  • +
+

那么最终换钱的方法综述即为count0 + count1 + count2 + ... + count100

+

上面的分析中,我们把原来的大问题拆为了 101 个小问题,且每一个小问题都有很相似的地方,即:

+
    +
  • 求用 5、10 元换 100 元的方法数
  • +
  • 求用 5、10 元换 95 元的方法数
  • +
  • ......
  • +
  • 求用 5、10 元换 0 元的方法数
  • +
+

如果我们对这 101 个小问题再进行同样思路的分析,即再固定 5 元零钱的数量,那么就能把问题划分成了规模更小,但问题类型一样的小小问题。即递归的思路,可以写出如下代码。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int money[1000]; // money 表示所有小额纸币的面值
+int len; // len 表示 money 数组的长度,即:小额纸币种类
+
+// index 表示上文分析中的当前固定第几张
+// target 表示现在要兑换的钱的总额
+int slove(int index, int target) {
+    int ans = 0;
+    if(index == len) {
+        ans = target == 0 ? 1 : 0;
+    } else {
+        for(int i = 0; i*money[index] <= target; i++) {
+            // 剩余待换零钱的总额
+            int cur_total = target-(i * money[index]);
+            ans = ans + slove(index+1, cur_total);
+        }
+    }
+    return ans;
+}
+
+int main()
+{
+    int target;
+    cin>>len; // 零钱种类
+    for(int i = 0; i < len; i++){
+        cin>>money[i];
+    }
+    cin>>target; // 兑换总额
+
+    cout<<slove(0, target);
+}
+
+

优化递归

+

可以发现上文所写的递归代码存在大量的重复过程,比如下面两种情况,后面所求的子问题是完全一样的,导致程序运行时间的浪费。

+
    +
  • 已经使用了 5 张 1 元、0 张 5 元,剩下的 95 元用 5 元和 10 元兑换
  • +
  • 已经使用了 0 张 1 元、1 张 5 元,剩下的 95 元用 5 元 和 10 元兑换
  • +
+

既然前面已经求解过相同的子问题了,那么我们是否可以在第一次求解的时候,将计算结果保存下来,这样下次遇到相同子问题的实际,直接查出来用就可以,省去再次求解的时间。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int money[1000]; // money 表示所有小额纸币的面值
+int len; // len 表示 money 数组的长度,即:小额纸币种类
+
+// 用于存储子问题的解
+int val_map[1000][1000] = { 0 };
+
+// 0 表示该子问题没有算过
+// -1 表示算过,但该子问题无解
+// 其它值,即此子问题的方法数
+
+int slove(int index, int target) {
+    int ans = 0;
+    if(index == len) {
+        ans = target == 0 ? 1 : 0;
+    } else {
+        for(int i = 0; i*money[index] <= target; i++) {
+            // 剩余待换零钱的总额
+            int cur_total = target-(i * money[index]);
+            int pre_val = val_map[index+1][cur_total];
+            // 如果 val 为 0,说明该子问题没有被计算过
+            if(pre_val == 0) {
+                ans = ans + slove(index+1, cur_total);
+            } else {
+                ans += pre_val == -1 ? 0 : pre_val;
+            }
+        }
+    }
+    // 存储计算结果
+    val_map[index][target] = ans == 0 ? -1 : ans;
+    return ans;
+}
+
+int main()
+{
+    int target; // 零钱种类
+    cin>>len;
+    for(int i = 0; i < len; i++){
+        cin>>money[i];
+    }
+    cin>>target;
+
+    cout<<slove(0, target);
+}
+
+

动态规划

+

上面对递归的优化方案已经能看出来动态规划的影子了,沿着前文先计算再查表的思路继续思考,我们能否提前把所有子问题都计算出答案,对每个子问题都进行查表解决。也即将最初的递归方案改为循环的实现。

+
+

所有的递归都能改为循环实现

+
+
#include<bits/stdc++.h>
+using namespace std;
+
+int money[1000]; // money 表示所有小额纸币的面值
+int len; // len 表示 money 数组的长度,即:小额纸币种类
+
+// 用于存储子问题的解
+// val_map[i][j] 表示用 money[0...i] 的小面额零钱组成 j 元的方法数
+int val_map[1000][1000] = { 0 };
+
+int slove(int target) {
+    
+    // 第一列表示组成 0 元的方法数,所以为 1
+    for (int i = 0; i < len; i++) {
+        val_map[i][0] = 1;
+    }
+
+    // 第一行表示只使用 money[0] 一种钱币兑换钱数为i的方法数
+    // 所以是 money[0] 的倍数的位置为 1,否则为 0
+    for (int i = 1; money[0]*i <= target; i++) {
+        val_map[0][money[0]*i] = 1;
+    }
+
+    for (int i = 1; i < len; i++) {
+        for (int j = 1; j <= target; j++) {
+            for (int k = 0; j >= money[i]*k; k++) {
+                /* 
+                val_map[i][j] 的值为:
+                用 money[0...i-1] 的零钱组成 j 减去 money[i] 的倍数的方法数
+                因为相比 val_map[i-1][j],只是多了一种零钱的可选项
+                */
+                val_map[i][j] += val_map[i-1][j-money[i]*k];
+            }
+        }
+    }
+
+    return val_map[len-1][target];
+}
+
+int main()
+{
+    int target;
+    cin>>len;
+    for(int i = 0; i < len; i++){
+        cin>>money[i];
+    }
+    cin>>target;
+
+    cout<<slove(target);
+}
+
+

动归优化

+

在上文第一版动态规划代码的优化中已经能发现,其实val_map[i][j]的值由两部分组成,分别为:

+
    +
  • 用 money[0...i-1] 的零钱组成换 j 元的方法数
  • +
  • 用 money[0...i-1] 的零钱换 j-money[i]*k(k=1,1,2,3....)元的方法数之和
  • +
+

对于第二种情况来说,其累加值实际上就是val_map[i][j-money[i]],即用money[0...i]的零钱换 j-money[i]元的方法数。至于具体为什么累加值与val_map[i][j-money[i]]相等,我们可以借助递归方法时的分析方式进行理解。

+

用 money[0...i-1] 的零钱组成换 j 元的方法数对应

+
    +
  • 用 0 张 money[i] 换,剩下的用 money[0...i-1] 换
  • +
+

用 money[0...i-1] 的零钱换 j-money[i]*k(k=1,1,2,3....)元的方法数之和对应

+
    +
  • 用 1 张 money[i] 换,剩下的用 money[0...i-1] 换
  • +
  • 用 2 张 money[i] 换,剩下的用 money[0...i-1] 换
  • +
  • ......
  • +
+

所以第二部分的值即为val_map[i][j-money[i]]。依据此处的分析,我们可以在原有基础上去掉第三层循环,减少程序运行所花费的时间。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int money[1000];
+int len;
+
+int val_map[1000][1000] = { 0 };
+
+int slove(int target) {
+    
+    for (int i = 0; i < len; i++) {
+        val_map[i][0] = 1;
+    }
+
+    for (int i = 1; money[0]*i <= target; i++) {
+        val_map[0][money[0]*i] = 1;
+    }
+
+    for (int i = 1; i < len; i++) {
+        for (int j = 1; j <= target; j++) {
+            val_map[i][j] = val_map[i-1][j];
+            // 此处需要比较 j 的大小,防止数组越界
+            // 注意条件时 >= ,否则少计算 j 刚好为 money[i] 的情况
+            if(j >= money[i]) {
+                val_map[i][j] += val_map[i][j-money[i]];
+            }
+        }
+    }
+
+    return val_map[len-1][target];
+}
+
+int main()
+{
+    int target;
+    cin>>len;
+    for(int i = 0; i < len; i++){
+        cin>>money[i];
+    }
+    cin>>target;
+
+    cout<<slove(target);
+}
+
+

空间压缩

+

仔细观察能发现,每一次更新val_map[i][j]的值时,它只依赖于上一行和当前这一行前面的元素。对于我们所求解的问题来说,它仅要求我们给出最终的答案即可,那么前面存储中间结果的那些元素实际上就会空间的浪费,因此我们可以思考一下如何在空间上进行压缩。

+

实际上我们只需要定义一个一维的数组,采用一些技巧对该数组进行滚动更新,按照合适的方向去更新数组,同样可以达到上面使用二维数组的效果。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int money[1000];
+int len;
+
+int val_map[1000] = { 0 };
+
+int slove(int target) {
+    
+    // 第一行,只用 money[0] 换零钱
+    // 所以只能换 money[0] 倍数的钱
+    for (int i = 0; money[0]*i <= target; i++) {
+        val_map[money[0] * i] = 1;
+    }
+
+    for (int i = 1; i < len; i++) {
+        for (int j = 1; j <= target; j++) {
+            if(j >= money[i]) {
+                // 在进行下面一步前 val_map[j] 的值就已经是 val_map[i-1][j] 了
+                val_map[j] += val_map[j-money[i]];
+            }
+        }
+    }
+
+    return val_map[target];
+}
+
+int main()
+{
+    int target;
+    cin>>len;
+    for(int i = 0; i < len; i++){
+        cin>>money[i];
+    }
+    cin>>target;
+
+    cout<<slove(target);
+}
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/WnccL2rgJ/index.html b/WnccL2rgJ/index.html new file mode 100644 index 00000000..63f865af --- /dev/null +++ b/WnccL2rgJ/index.html @@ -0,0 +1,900 @@ + + + + + + + + 学习 Angulr 容易忽略的知识点 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 学习 Angulr 容易忽略的知识点 +

+ + +
+ +
+
+

参考内容:
+《Angulr5 高级编程(第二版)》

+
+

函数声明式和表达式

+
// 第一种:函数声明式
+myFunc();
+function myFunc(){
+    ...
+}
+
+// 第二种:函数表达式
+myFunc();
+let myFunc = function(){
+    ...
+}
+
+

虽然上面两种函数声明方式在大部分情况下是一样的,第一种可执行,第二种却不可以执行,这是因为浏览器在解析 js 时找到函数声明,并在执行剩余语句之前设置好函数,此过程称为函数提升,但是函数表达式却不会受到提升,因此无法正常工作。

+

js 不具备多态性

+

js 重不能创建名称相同但参数不同的两个函数,它不具备这个多态性,比如你定义的函数中有两个形参,调用函数时只传一个参数,第二形参的值就是 undefined ,如果传的参数大于 3 个,那么会自动忽略多余的参数。可以使用下列方法来处理函数定义参数数量和用于调用函数实际参数数量之间不匹配的问题。

+
// 使用默认参数
+let func = function(age, sex='男'){
+    ...
+}
+func(23);
+
+// 使用可变长参数
+let func = function(age, sex, ...extraArgs){
+    ...
+}
+func(23, '女', '张三', '深圳');
+// 最后一个参数是一个数组,任何额外的实参都会被赋给这个数组
+
+

let 和 war 的区别

+

使用 let 和 var 声明变量的区别,使用 let 声明变量会把变量的作用范围限定在它所在的代码区域内。而使用 var 所创建的变量的作用域是它所在的函数。

+
function func(){
+    if(false){
+        var age = 23;
+    }
+}
+
+// 上面的代码会被解析成下面的形式,使用 let 则不会出现这样的结果
+
+function func(){
+    var age;
+    if(false){
+        age = 23;
+    }
+}
+
+

相等 == 和恒等 === 以及 连接操作符 +

+

相等操作符尝试将操作数强制转换为相同的类型,再评估是否相等,实质上相等操作符==是测试二者的值是否相等,而与二者的类型无关;如果要测试值和类型是否都相等则应该用恒等操作符===

+
5 == '5' // 结果为 true
+5 === '5' // 结果为 false
+
+

在 js 中,连接操作符的优先级高于加法操作,也就是说5 + '5'的结果是55

+

不同的模块指定方式

+
import { Name } from "./modules/NameUtil";// 第一种
+import { Compont } from "@angular/core";// 第二种
+
+

上面两种导入模块的方式有所不同,第一种是相对模块,第二种是非相对导入。第一种告诉的 TypeScript 编译器,该模块所在的位置是相对于包含 import 语句的文件而言;第二种非相对导入,编译器会用 node_modules 文件夹中的 npm 包来解析它。

+

如果在导入模块时,出现需要导入两个不同模块但是名字却相同的情况,可以使用as关键字给导入的模块取一个别名。

+
import { Name as otherName } from "./modules/Name";//取别名
+
+

还有一种方法是将模块作为对象导入,如下 import 所示,导入 Name 模块的内容,并创建一个名为 otherName 的对象,然后就可以使用该对象的属性了。

+
import * as otherName from "./modules/NameUtil";
+let name = new otherName.Name("Admin", "China");// Name 是 NameUtil 中的类
+
+

多类型和类型断言

+

在 ts 中允许指定多个类型,使用字符|进行分隔。看下面的的方法,其功能是把华氏温度转换为摄氏温度。

+
// 使用多类型,该函数可以传入 number 和 string 类型的参数
+static convertFtoC(temp: number | string): string {
+    /*
+    尝试使用 <> 声明一个类型断言,将一个对象转换为指定类型,也可以使用 as 关键字实现下列相同的效果
+    let value: number = (temp as number).toPrecision ? temp as number : parseFloat(temp as string);
+    */
+    let value: number = (<number>temp).toPrecision ? <number>temp : parseFloat(<string>temp);
+    return ((parseFloat(value.toPrecision(2)) - 32) / 1.8).toFixed(1);
+}
+
+

元组是固定长度的数组,数组的每一项都是指定的类型;可索引类型可以将键与值关联起来,创建类似于 map 的集合。

+
// 元组
+let tuple: [string, string, string];
+tuple = ["a", "b", "c"];
+
+// 可索引类型
+let cities: {[index: string] : [string, string]} = {};
+cities["Beijing"] = ["raining", "2摄氏度"];
+
+

数据绑定

+
[target]="expr"// 方括号表示单向绑定,数据从表达式流向目标;
+
+(target)="expr"// 圆括号表示单向绑定,数据从目标流向表达式,用于处理事件的绑定;
+[(target)]="expr"// 圆方括号组合表示双向绑定,数据在表达式与目标之间双向流动;
+{{ expression }}// 字符串插入绑定。
+
+

[] 绑定有很多不同的形式,下面介绍不同表现形式的效果。

+
<!-- 
+    标准属性绑定(dom对象有的属性),将 input 的 value 属性绑定到一个表达式的结果
+    因为 model.getProduct(1) 可能返回 null ,所以使用模板空条件操作符 ? 浏览返回结果
+    如果返回不为空,那么将读取 name 属性,否则由 null 合并操作符 || 将结果设置为 None
+    字符串插入绑定也可以使用这种表达式
+ -->
+<input [value]="model.getProduct(1)?.name || 'None'">
+
+<!-- 
+    元素属性绑定,有时候我们需要绑定的属性在 DOMAPI 上面没有
+    可以使用通过在属性名称前加上 attr 前缀的方式来定义目标
+ -->
+<td [attr.colspan]="model.getProducts().length">
+    {{ model.getProduct(1)?.name || 'None' }}
+</td>
+
+<!-- 还有其他的 ngClass,ngStyle 等绑定,理解大体上和上面差不多 -->
+
+

内置指令

+
<!-- 
+    ngIf指令,如果表达式求值结果为 true ,那么 ngIf 将宿主元素机器内容包含在 html 文件中
+    指令前面的星号表示这是一条微模板指令
+    组要注意的是,ngIf 会向 html 中添加元素,也会从中删除元素,并非只是显示和隐藏
+    如果只是控制可见性,可以使用属性绑定挥着样式绑定
+ -->
+<div *ngIf="expr"></div>
+
+<!-- 
+    ngSwitch指令,
+ -->
+<div [ngSwitch]="expr">
+    <span *ngSwitchCase="expr"></span>
+    <span *ngSwitchDefault></span>
+</div>
+
+<!-- 
+    ngFor指令,见名知意,为数组中的每个对象生成同一组元素
+    ngFor 指令还支持其他的一系列可赋给变量的值,有如下局部模板变量
+    
+    index:当前对象的位置
+    odd:如果当前对象的位置为奇数,那么这个布尔值为 true
+    even:同上相反
+    first:如果为第一条记录,那么为 true
+    last:同上相反
+ -->
+<div *ngFor="let item of expr; let i = index">
+    {{ i }}
+</div>
+
+<!-- 
+    ngTemplateOutlet指令,用于重复模板中的内容块
+    其用法如下所示,需要给源元素指定一个 id 值
+    
+    <ng-template #titleTemplate>
+        <h1>我是重复的元素哦</h1>
+    </ng-template>
+    <ng-template [ngTemplateOutlet]="titleTemplate"></ng-template>
+    ...省略若万行 html 代码
+    <ng-template [ngTemplateOutlet]="titleTemplate"></ng-template>
+ -->
+<ng-template [ngTemplateOutlet]="myTempl"></ng-template>
+
+<!-- 
+    下面两个指令就是见名知意了,不解释
+ -->
+<div ngClass="expr"></div>
+<div ngStyle="expr"></div>
+
+

事件绑定

+

事件绑定使用 (target)="expr",是单向绑定,数据从目标流向表达式,用于响应宿主元素发送的事件。

+

当浏览器触发一个时间时,它将提供一个对象来描述该事件,对于不同类型的事件有不同类型的事件对象,事件对象被赋给一个名为$event的模板变量,但是所有事件对象都有下面三个属性:

+
type:返回一个 string 值,用于标识已触发事件类型;
+target:返回触发事件的对象,一般是 html元素对象。
+timeStamp:返回事件触发事件的 number 值,用 1970.1.1 毫秒数表示。
+
+

下面举几个例子,作为理解帮助使用。

+
<!-- 当数鼠标在上面移动时,就会触发 mouseover 事件 -->
+<td *ngFor="let item of getProducts()" (mouseover)="selectedProduct = item.name"></td>
+
+<!-- 当用户编辑 input 元素的内容时就会触发 input 事件 -->
+<input (input)="selectedProduct=$event.target.value" />
+
+<input (keyup)="selectedProduct=product.value" />
+<!-- 使用事件过滤,上面的写法按下任何一个键都会触发事件,而下面的写法只有回车事件才会触发事件 -->
+<input (keyup.enter="selectedProduct=product.value") />
+
+

表单验证

+

Angular 提供了一套可扩展的系统来验证表单元素的内容,总共可以向 input表元素中添加 4 个属性,每个属性定义一条验证规则,如下所示:

+
required:用于指定必须填写值;
+minlength:用于指定最小字符数;
+maxlength:用于指定最大字符数,(不能在表单元素直接使用,因为它与同名的 H5 属性冲突);
+pattern:该属性用于指定用户填写的值必须匹配正则表达式
+
+
<!-- 
+    Angular 要求验证的元素必须定义 name 属性
+    由于 Angular 使用的验证属性和 H5 规范使用的验证属性相同,
+    所以向表单元素中添加 novalidate 属性,告诉浏览器不要使用原生验证功能
+    ngSubmit 绑定表单元素的 submit 事件
+ -->
+<form novalidate (ngSubmit)="addProduct(newProduct)">
+    <input class="form-control"
+        name="name"
+        [(ngModel)]="newProduct.name"
+        required
+        minlength="5"
+        pattern="^[A-Za-z]+$" />
+    <button type="submit">提交</button>
+</form>
+
+

Angular 提供了 3 对验证 CSS 类,这些类可以用于样式化表单元素,向用户提供验证反馈,具体说明如下所示。

+
ng-untouched ng-touched:如果一个元素未被用户访问,就将其加入到 nguntouched 类中;一旦访问就加入到 ngtouched 类中。
+ng-prisstine ng-dirty:元素内容没有被改变被加入到 ng-prisstine 类中,否则将其加入到 ng-dirty 类中。
+ng-valid ng-invalid:如果满足验证规则定义的条件,就加入到 ng-valid 类中,否则加入到 ng-invalid 类中。
+
+

在实际使用过程中,直接定义对应的样式即可,如下所示:

+
<style>
+input.ng-dirty.ng-invalid{
+    border: 2px solid red;
+}
+input.ng-dirty.ng-valid{
+    border: 2px solid green;
+}
+</style>
+<form novalidate (ngSubmit)="addProduct(newProduct)">
+    <input class="form-control"
+        name="name"
+        [(ngModel)]="newProduct.name"
+        required
+        minlength="5"
+        pattern="^[A-Za-z]+$" />
+    <button type="submit">提交</button>
+</form>
+
+

上面的验证方式无法给用户提供更加具体的信息,用户不知道应该做什么,可以使用 ngModel 指令来访问宿主元素的验证状态,当存在验证错误的时候,使用该指令向用户提供指导性信息。

+
<form novalidate (ngSubmit)="addProduct(newProduct)">
+    <input class="form-control"
+        #nameRef="ngModel"
+        name="name"
+        [(ngModel)]="newProduct.name"
+        required
+        minlength="5"
+        pattern="^[A-Za-z]+$" />
+    <ul class="text-danger list-unstyled"
+        *ngIf="name.dirty && name.invalid">
+        <li *ngIf="name.errors?required">
+            you must enter a product name
+        </li>
+        <li *ngIf="name.errors?.pattern">
+            product name can only contain letters and spases
+        </li>
+        <li *ngIf="name.errors?minlength">
+            <!-- 
+                Angular 表单验证错误描述属性
+                required:如果属性已被应用于 input 元素,此属性返回 true
+                minlength.requiredLength:返回满足 minlength 属性所需的字符数
+                minlength.actualLength:返回用户输入的字符数
+                pattern.requiredPattern:返回使用 pattern 属性指定的正则表达式
+                pattern.actualValue:返回元素的内容
+             -->
+            product name must be at least {{ name.errors.minlength.requiredLenth }} characters
+        </li>
+    </ul>
+    <button type="submit">提交</button>
+</form>
+
+

如果在用户尝试提交表单时就显示大量的错误信息,给人的体验感就会很差,所以可以让用户提交表单时再验证整个表单,示例代码如下所示。

+
export class ProductionCompont {
+    // ...省略若万行代码
+    formSubmited: boolean = false;
+
+    submitForm(form: ngForm) {
+        this.formSubmited = true;
+        if(form.valid) {
+            this.addProduct(this.newProduct);
+            this.newProduct = new Product();
+            form.reset();
+            this.formSubmited = true;
+        }
+    }
+}
+
+
<form novalidate #formRef="ngForm" (ngSubmit)="submitForm(formRef)">
+    <div *ngIf="formsubmited && formRef.invalid">
+        there are problems with the form
+    </div>
+    <!-- 禁用提交按钮,验证成功提交按钮才可用 -->
+    <button [disabled]="formSubmited && formRef.valid">提交</button>
+</form>
+
+

fromSubmited 属性用于指示表单是否已经提交,并将用于在用户提交整个表单之前阻止表单验证。当用户提交表单时,调用 submitForm 方法,并将 ngForm 对象作为实参传入,ngForm 提供了 reset 方法,该方法可以重置表单的验证状态,使其返回到最初的未访问状态。

+

更高级的还有使用基于模型的表单验证,可以自行查阅相关资料。

+

使用 json-server 模拟 web 服务

+

因为json-server会经常用到,建议使用全局安装命令npm install -g json-server。因为开发后端的同学太慢了,而我们如果要等他们把接口都提供给我们的时候再开发程序的话,那效率就太低了,所以使用 json-server 来模拟后端服务。只需要建好一个 json 文件,比如下面的格式:

+
{
+    "user" : [
+        {
+            "name" : "张三",
+            "number" : "1234",
+        },
+        {
+            "name" : "王二",
+            "number" : "5678",
+        }
+    ],
+    "praise": [
+        {"info":"我是一只小老虎呀!"},
+        {"info":"我才是大老虎"}
+    ]
+}
+
+

启动服务使用命令json-server [你的 json 文件路径],然后就可以根据提示访问了,你甚至可以使用http://localhost:3000/user?number=5678去过滤数据。这样就能模拟 web 服务,而不必等后端同学的进度了。

+

解决跨域请求问题

+

Angular 跨域请求问题可以通过 Angular 自身的代理转发功能解决,在项目文件夹下新建一个 proxy.conf.json 并在其中添加如下内容。

+
// 可以通过下列配置解决
+"/api": {
+    "target": "http://10.9.176.120:8888",
+}
+
+

在启动时使用npm start,或者使用ng serve --proxy-config proxy.conf.json,Anular 中的/api请求就会被转发到 http://10.9.176.120:8888/api,从而解决跨域请求问题。

+

使用第三方 js 插件

+

共有三种方式引入第三方插件,第一种很简单,直接在 html 中引入插件就可以了;第二种在angular.json中进行配置;第三种在 ts 文件中使用 import 导入库即可。

+
// 第一种(需要重启服务)
+"scripts": ["src/assets/jquery-3.2.1.js","src/assets/jquery.nicescroll.js","src/assets/ion.rangeSlider.js"]
+
+// 第二种
+<script type="text/javascript" src="assets/jquery-3.2.1.js"></script>
+<script type="text/javascript" src="assets/jquery.nicescroll.js"></script>
+
+// 第三种
+import "assets/jquery-3.2.1.js";
+import "assets/jquery.nicescroll.js";
+import "assets/ion.rangeSlider.js";
+
+

深拷贝与浅拷贝

+

深拷贝与浅拷贝是围绕引用类型变量说的,其本质区别是不可变性,基本类型是不可变得,而引用类型是可变的。

+

直接使用赋值操作符,就是浅拷贝,如果对拷贝源进行操作,会直接影响在拷贝目标上,因为这个赋值行为本质是内存地址的赋值,为了获得与拷贝源完全相同但又不会影响彼此的对象就要使用深拷贝。

+
let objA = {
+    x: 1,
+    y: -1
+}
+let objB = objA;
+objA.x++;
+console.log("objA.x:"+objA.x, "objB.x:"+objB.x);
+//打印结果如下:
+objA.x : 2 
+objB.x : 2
+
+

Typescript 提供了一种方法来实现引用类型的深拷贝,即Object.assign(target, ...source),此方法接受多个参数,第一个参数为拷贝目标,剩余参数为拷贝源,同名属性会进行覆盖。

+
let objA = {
+    x: 1,
+    y: -1,
+    c: {
+        d: 1,
+    }
+}
+let objB = {};
+Object.assign(objB, objA);
+objA.x++;
+console.log("objA.x:"+objA["x"], "objB.x:"+objB["x"]);
+//打印结果如下:
+objA.x : 2 
+objB.x : 1
+
+

需要注意的是,Typescript 提供的深拷贝方法不能实现嵌套对象的深拷贝,会出现下面的情况。

+
let objA = {
+    x: 1,
+    y: -1,
+    c: {
+        d: 1,
+    }
+}
+let objB = {};
+Object.assign(objB, objA);
+objA.c.d++;
+console.log("objA.c.d:"+objA["c"].d, "objB.c.d:"+objB["c"].d);
+//打印结果如下:
+objA.c.d : 2 
+objB.c.d : 2
+
+

要实现嵌套对象的深拷贝,可以使用 JSON 对象提供的方法,JSON 对象提供了两个方法,分别为:stringify()parse(),前者将对象 JSON 化,后者将 JSON 对象化,使用这种方式可以实现嵌套深拷贝,但是也有缺点:破坏原型链,不能拷贝属性值为 function 的属性。

+
let objA = {
+    a: 1,
+    b: {
+        c: 1
+    }
+}
+let objB = JSON.parse(JSON.stringify(objA));
+objA.b.c++;
+console.log("objA.b.c:"+objA.b.c, "objB.b.c:"+objB.b.c);
+
+//打印结果如下:
+objA.b.c:2
+objB.b.c:1
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/X1-n4nEmj/index.html b/X1-n4nEmj/index.html new file mode 100644 index 00000000..d61f8592 --- /dev/null +++ b/X1-n4nEmj/index.html @@ -0,0 +1,465 @@ + + + + + + + + 家里的狗 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 家里的狗 +

+ + +
+ +
+

为了防止晚上有人来家里偷东西,几乎家家户户都至少会养一只狗。在我的记忆中,我家一开始是没有狗的。

+

忘记是哪一年夏天的一个清晨,天还没有大亮,我隐约看见在牛棚后面的空地有个黑影,走近一点仔细一看,原来是一只不知道从哪里来的一只黑狗。

+

它惊恐的看着我,眼神中夹杂着恐惧与无助,佝偻的身子比弓还要弯,倒是很像一个活着的牛轭。他的身子还没有草高,露水把全身的毛都打湿了,还沾着一些不知名的植物种子。我和它对视着,恐惧慢慢填满了它的眼球,我害怕吓到它,赶紧走开去告诉妈。

+

妈远远看了一眼,让我别管它。随后妈把装着昨晚剩饭的猪食瓢放到牛棚后面的一块石头上,黑狗看见妈带着武器走近早就跑了,我吃早饭时还不时去望望它在不在,有没有吃妈给放在那里的饭。

+

妈已经把猪喂完准备下地干活了,仍旧没有再次发现黑狗的踪影,也没见猪食瓢有什么变化,我心里有一点点的失落,黑狗应该是已经逃走了吧。

+

晚上吃完饭妈去拿猪食瓢,告诉我里面的饭已经被吃的一粒不剩,我心里开始期待和它的再次见面。第二天早晨果然见到它了,身上已经没有昨天那么湿了,显然没有前一天来这里时钻的草丛多,妈依旧用猪食瓢装着米饭和米汤放在牛棚后的那个石头上。

+

就这样过了几日,黑狗走进了我家的屋檐,它的样子实在太丑了。每一根肋骨都清晰的扎眼,看起来爸的手指都比它的小腿粗,感觉下一秒它就会死去。

+

我并不喜欢它,甚至还有些讨厌它,我实在找不到更丑的词来形容它,不过是出于心里的怜悯与对生命的敬畏,会在吃饭的时候给它丢几个我不吃的肥肉,被烟熏黑的那一层肉边我也丢给它......

+

有一次同村的一个人路过家门口时,看见那只黑狗吓的赶紧往妈身后躲。“有我在,它不敢咬。”,妈说。邻居夸夸妈说:“这个狗儿喂得好肥”。妈自豪的告诉那个人这只狗每天还送林儿(我)上学。

+

是的,我也不知道什么时候我已经和大黑狗变得如此亲密了,它每天早上会把我送到山顶的学校,我每天下午回家做完作业会和它一起到田间追逐。在学校也常常会给同学们说大黑狗胸前的那长成了“人”字的一片白毛,我一直相信“人”字是老天爷特地印在它身上,用来告诉我大黑狗是他派来的使者。

+

大黑狗来我家时已经很老很老了,是我读三年级的某一天,它像往常一样把我送到学校,但是我下午回家却不见它的踪影,一直等到晚上都没有见它回来。那些天我放学回家第一件事就是朝我和它常去的那些地方大声的唤它。

+

不到一个月后的一天早晨,像大黑狗第一次来我家附近时的场景一样,湿漉漉的身子带着些杂草种子,不同的是它身旁还跟着一只背部有些黑毛的小黄狗,小黄狗胸前也有一个很明显的“人”字。我赶紧去用猪食瓢盛满饭放在它面前,它吃了几口就又走了。

+

就这样,大黑狗离开了我,给我留下了一只小小的黄奶狗。我不知道它是去找它原来的主人去了,还是觉得自己老了,不愿意让我看见它倒下的样子,反正它就是再也没有回来过。

+

小黄狗长成了大黄狗,我对这只大黄狗的印象很浅,只记得爸妈把这只黄狗送给了外婆家附近的亲戚,我们留下了它生的一只小黄狗。外婆知道我们把大黄狗送人,还狠狠的批评了爸妈,说自己来家里的狗不能送人。

+

自然小黄狗很快就长成了大黄狗,我像以前一样也偷偷给大黄狗吃肉,逐渐开始懂事的妹妹也会背着爸妈给它肉吃,我和妹都会夹几片我们压根就不吃的肥肉,离开饭桌假装是到外面吃饭,实际上是给大黄狗送肉去了。

+

我到离家 30 多公里的镇上读高中,每个月才回家一次。每次离家大黄狗都会送我到集市去赶车,我会在寒暑假的黄昏和它到新修的公路去追逐,带它去它自己一个人不敢去探索的地方。

+

上大学后和大黄狗相处的时间更少了,听爸妈说它会经常跑到外婆家,外婆好吃好喝的招待它,招呼都不打一声就又跑回来了。还经常和邻居家的狗到麦子地打闹,要把一大片麦子弄倒才肯回家。

+

每学期回家在离家还有四五百米的地方都会听到它的吠叫,因为它把我当陌生人了。但是只要我大喊一声,它就会立刻停止吠叫,飞奔到我这里,兴奋的往我身上爬,把它的前爪往我身上搭;我努力不让它碰到我的衣服,然而每次到家时我都带着一身泥巴做的狗爪印。

+

现在大黄狗已经 10 多岁了,它就像大黑狗当年送我一样每天送我妹上学。我也已经走入职场开始工作,待在家里的时间更少了,我不知道它还能活多久,生怕哪次爸妈打电话时会给我说大黄狗死了,只要爸妈没有在电话中提及大黄狗,我都是非常开心的,因为那就代表着它依旧健健康康的活着。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/X5KvKQqL-/index.html b/X5KvKQqL-/index.html new file mode 100644 index 00000000..aa74d730 --- /dev/null +++ b/X5KvKQqL-/index.html @@ -0,0 +1,725 @@ + + + + + + + + 正则表达式入门,基础语法详解 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 正则表达式入门,基础语法详解 +

+ + +
+ +
+

这两天一直在时不时的和 Neo4j 图数据库打交道。它的查询语句可以使用正则表达式,有一段时间没有自己写过正则表达式了,现在处于能看懂别人写的正则表达式,但是自己写不出来,语法规则都忘了。为了方便接下来的工作,所以特地复习复习正则表达式的语法。

+

正则表达式简介

+

正则表达式是用来匹配字符串的一系列匹配符,具备简介高效的特点,在很多语言中都有支持(java、python、javascript、php 等等)。在 windows 的 cmd 命令中也同样支持,例如使用命令 dir j*,那么只会罗列出所有以j开头的文件和文件夹。

+

正则表达式基本语法

+

正则表达式在在不同语言的支持语法略有不同,本文采用js的进行说明。js 中使用正则表达式的方法为str.match(/表达式/),即需要加两个斜杠。以下所有的代码段第一行为代码,第二行为返回结果,实验是在 chrome 控制台进行的。

+

一直认为最好的学习方式就是实际操作,理论谁都能讲一大堆,但是实际做没做出来还真不知道。一个奇葩现象就是教软件工程的老师可能并没有在软件行业待过。

+

普通匹配符

+

普通匹配符能匹配与之对应的字符,默认区分大小写。

+
"Hello Regx".match(/H/)
+["H", index: 0, input: "Hello Regx", groups: undefined]
+
+

正则标记符

+
    +
  • i :不区分大小写
  • +
  • g :全局匹配
  • +
  • m :多行匹配(暂不管它,我用的少)
  • +
+

参数直接加在最后一个斜杠的后面,比如"Hello Regx".match(/regx/i),可以加多个参数。

+
"Hello Regx".match(/regx/i)
+["Regx", index: 6, input: "Hello Regx", groups: undefined]
+
+

之前是表达式一旦匹配成功,就不再向字符串后面查找了,加上 g 后,表示进行全局查找。最后返回的是一个数组。

+
"Hello Regx".match(/e/g)
+(2) ["e", "e"]
+
+

多匹配符

+
    +
  • \d :匹配数字,即 0~9
  • +
  • \w :匹配数字、字母、下划线
  • +
  • . :匹配除换行的所有字符
  • +
+

需要注意的是,上面所有的匹配符都只能匹配一个字符。

+
"Hello 2018".match(/\d/g)
+// 使用\d,匹配字符串中的所有数字
+(4) ["2", "0", "1", "8"]
+
+
+"Hello 2018".match(/\w/g)
+// 使用\w,匹配所有的数字和字母,需要注意没有匹配到空格
+(9) ["H", "e", "l", "l", "o", "2", "0", "1", "8"]
+
+
+"Hello 2018".match(/./g)
+// 使用.,匹配所有字符,包括空格
+(10) ["H", "e", "l", "l", "o", " ", "2", "0", "1", "8"]
+
+
+"Hello 2018".match(/\d\w./g)
+// 分析一下这个为什么匹配到的是201,
+// 首先\d找到第一个数字2,匹配成功,紧接着\w匹配到0,然后.匹配到1
+// 整个正则表达式匹配成功,返回201
+["201"]
+
+
+"Hello 20\n18".match(/\d\w./g)
+// 这里匹配不成功,因为.不能匹配换行符,所以返回null
+null
+
+
+"Hello 2018".match(/\w.\d/g)
+// 首先看这个正则式,\w.\d,它要求最后一个字符是数字
+// \w.能一直匹配到空格,但是因为得满足\d,所以第一个匹配成功的是0 2
+// 因为是全局匹配,所以会接着匹配后面的018,也匹配成功
+(2) ["o 2", "018"]
+
+

自定义匹配符

+

比如中国的手机号都是以 1 开头,第二位只能是 3、4、5、7、8,第 3 位只要是数字就行。如何匹配这样的字符串?

+
    +
  • [] :匹配[]中的任意一个字符
  • +
+
"152".match(/1[34578]\d/)
+// 第二个字符可以选择中括号中的任意一个
+["152", index: 0, input: "152", groups: undefined]
+
+

如果在 [] 添加了 ^,代表取反。即 [^] 表示除了中括号中的字符都满足。

+
"152".match(/1[^34578]\d/)
+
+null
+
+
+"1a2".match(/1[^34578]\d/)
+// 只要不是[]中的字符,都满足,包括回车符
+["1a2", index: 0, input: "1a2", groups: undefined]
+
+

修饰匹配次数

+

我们的手机号有 11 位,除了前 2 位有要求,其他9位度没有要求,那么是不是正则表达式就应该这样写呢?

+
1[^34578]\d\d\d\d\d\d\d\d\d
+
+

很明显,这样写太麻烦,肯定有更好的方式,这里就可以修饰一下匹配次数啦。

+
    +
  • ? :最多出现 1 次
  • +
  • + :至少出现 1 次
  • +
  • * :出现任意次数
  • +
  • {} :分下面四种情况 +
      +
    • {n}代表前面的匹配符出现 n 次
    • +
    • {n, m}出现次数在 n~m 之间
    • +
    • {n, }至少出现 n 次
    • +
    • {, m}最多出现 m 次
    • +
    +
  • +
+

例子很简单,一看就懂,不浪费时间。

+
"15284750845".match(/1[34578]\d{9}/)
+["15284750845", index: 0, input: "15284750845", groups: undefined]
+
+
+"15".match(/1[34578]\d?/)
+["15", index: 0, input: "15", groups: undefined]
+
+
+"152".match(/1[34578]\d?/)
+["152", index: 0, input: "152", groups: undefined]
+
+
+"152".match(/1[34578]\d+/)
+["152", index: 0, input: "152", groups: undefined]
+
+
+"15".match(/1[34578]\d+/)
+null
+
+

完整匹配

+

按照上面的写法会出现下面的问题。

+
"ya15284750845".match(/1[34578]\d{9}/)
+// 不是电话号码,也能匹配成功,需要进一步改进
+["15284750845", index: 2, input: "ya15284750845", groups: undefined]
+
+
    +
  • ^ :在 [] 中代表取反,但在外面代表从开始匹配
  • +
+
"ya15284750845".match(/^1[34578]\d{9}/)
+// 现在就能从一开始匹配而且还得符合正则式才算匹配成功
+null
+
+
+// 但是依旧会出现下面的问题
+"1528475084523255".match(/^1[34578]\d{9}/)
+// 不是电话号码也能匹配成功,还要改进
+["15284750845", index: 0, input: "1528475084523255", groups: undefined]
+
+
    +
  • $ :代表持续匹配到结束
  • +
+
"1528475084523255".match(/^1[34578]\d{9}$/)
+// 现在就能保证正确了,有^表示从开始匹配;
+// 有$表示持续匹配到结束,即完全匹配
+null
+
+/*
+需要注意的是,一个字符串从开始匹配和从结束匹配都没问题,
+不代表整个字符串就没问题,比如 15284750845-15284750845
+这个字符串从开始和从结束匹配都能成功,但实际上是错的
+*/
+
+

特殊符号

+

到这里发现正则表达式确实很强大,仅仅几个简单的符号就能匹配字符串,但是如果我们要匹配的字符本身就是前面用到的符号怎么办呢?

+
    +
  • 匹配像$、^等特殊符号时,需要加转义字符\
  • +
+
"1.".match(/./)
+//因为.能匹配除换行的所有字符,所以匹配到1
+//但实际上我们想匹配.这个字符
+["1", index: 0, input: "1.", groups: undefined]
+
+
+"1.".match(/\./)
+// 只需要加一个转义字符就可以了,其他类似
+[".", index: 1, input: "1.", groups: undefined]
+
+

条件分支

+

比如现在想匹配图片的文件名,包括 jpg、png、jpeg、gif 等等,这是多个选项,所以需要像编程语言一样,应该具备条件分支结构。

+
    +
  • | :条件分支
  • +
  • () :有两层含义 +
      +
    • 括号中的内容成为一个独立的整体
    • +
    • 括号的内容可以进行分组,单独匹配,若不需要此功能,则( ?: )
    • +
    +
  • +
+
"1.jpg".match(/.+\.jpe?g|gif|png/)
+// 这样就可以满足条件分支了,不过下面又出问题了
+["1.jpg", index: 0, input: "1.jpg", groups: undefined]
+
+
+"1.png".match(/.+\.jpe?g|gif|png/)
+// 这里没有匹配到.和前面的文件名
+["png", index: 2, input: "1.png", groups: undefined]
+
+
+/*
+其实我们想告诉它的是,.和后面的每一个条件分支的值都是一个独立的整体
+但是它把.+\.jpe?g、gif、png当成了各自独立的整体
+我们并不想让它这样切分,所以我们来告诉它怎么分才是正确的
+*/
+
+
+"1.png".match(/.+\.(jpe?g|gif|png)/)
+// 现在可以匹配成功了,但是它多匹配了一个
+// 因为括号的内容可以进行分组,单独匹配
+(2) ["1.png", "png", index: 0, input: "1.png", groups: undefined]
+
+
+// 所以最终写法如下
+"1.png".match(/.+\.(?:jpe?g|gif|png)/)
+["1.png", index: 0, input: "1.png", groups: undefined]
+
+

贪婪与懒惰

+
// 首先看一个例子
+"aabab".match(/a.*b/)
+["aabab", index: 0, input: "aabab", groups: undefined]
+
+
+/*
+上面的匹配没有什么问题,但实际上aab也是可以的
+也就是aab也是符合条件的,那又是为什么呢?
+*/
+
+

因为在正则表达式中,默认是贪婪模式,尽可能多的匹配,可以在修饰数量的匹配符后面添加 ?,则代表懒惰。

+
// like this (^__^)
+"aabab".match(/a.*?b/)
+["aab", index: 0, input: "aabab", groups: undefined]
+
+

到这里应该就差不多了,再深入的,就自我查询知识了。配一张正则表达式速查表。

+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/XcHzo7Nc6/index.html b/XcHzo7Nc6/index.html new file mode 100644 index 00000000..cb0582d0 --- /dev/null +++ b/XcHzo7Nc6/index.html @@ -0,0 +1,655 @@ + + + + + + + + 正则表达式 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 正则表达式 +
+ + +
+

+ + 正则表达式是如何运行的?——浅析正则表达式原理 + +

+ +
+ + + + +
+ +
+ +参考内容: +《编译原理》 +实现简单的正则表达式引擎 +正则表达式回溯原理 +浅谈正则表达式原理 + +最近在一个业务问题中遇到了一个正则表达式性能问题,于是查了点资料去回顾了下正则表达式的原理,简单整理了一下就发到这里吧;另外也是想试试 Apple Pencil 的手感如何,画的太丑不要嫌弃哈。 +有穷自动机 +正则表达式的规则不是很多,这些规则也很容易就能理解,但是正则表达式并不能用来直接识别字符串,我们还需要引入一种适合转换为计算机程序的模型,我们引入的就是有穷自动机。 +在编译原理中通过构造有穷自动机把正则表达式编译成识别器,识别器以字符串x作为输入,当x是语言的句子时回答是,否则回答不是,这正是我们使用正则表达式时需要达到的效果。 +有穷自动机分为确定性有穷自动机(DFA)和非确定性有穷自动机(NFA),它们都能且仅能识别正则表达式所表示的语言。它们有着各自的优缺点,DFA 导出的识别器时间复杂度是多项式的,它比 NFA 导出的识别器要快的多,但是 DFA 导出的识别器要比与之对应的 NFA 导出的识别器大的多。 +大部分正则表达式引擎都是使用 NFA 实现的,也有少部分使用 DFA 实现。从我们写正则表达式的角度来讲,DFA 实现的引擎要比 NFA 实现的引擎快的多,但是 DFA 支持的功能没有 NFA 那么强大,比如没有捕获组一类的特性等等。 +我们可以用带标记的有向图来表示有穷自动机,称之为转换图,其节点是状态,有标记的边表示转换函数。同一个字符可以标记始于同一个状态的两个或多个转换,边可以由输入字符符号标记,其中 NFA 的边还可以用ε标记。 +之所以一个叫有确定和非确定之分,是因为对于同一个状态与同一个输入符号,NFA 可以到达不同的状态。下面看两张图就能明白上面那一长串的文字了。 +图中两个圈圈的状态表示接受状态,也就是说到达这个状态就表示匹配成功。细心的你应该发现了两张图所表示的正则表达式是一样的,这就是有穷自动机神奇的地方,每一个 NFA 我们都能通过算法将其转换为 DFA,所以我们先根据正则表达式构建 NFA,然后再转换成相应的 DFA,最后再进行识别。 + +上图的画法在正则表达式很简单的时候还可以,如果遇到很复杂的正则表达式画起来还是挺费力的,如果想对自动机有更加深入的认识可以自行查阅相关资料。下面的图片是使用正则可视化工具生成的,对应的正则表达式是^-?\d+(,\d{3})*(\.\d{1,2})?$,它所匹配的字符串是数字/货币金额(支持负数、千分位分隔符)。 + +回溯 +NFA 引擎在遇到多个合法的状态时,它会选择其中一个并记住它,当匹配失败时引擎就会回溯到之前记录的位置继续尝试匹配。这种回溯机制正是造成正则表达式性能问题的主要原因。下面我们通过具体的例子来看看什么是回溯。 +/ab{1,3}c/ + + + + +正则 +文本 + + + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + + +上表中展示的是使用ab{1,3}c匹配abbbc的过程,如果把匹配字符串换成abbc,在第五步就会出现匹配失败的情况,第六步会回到上一次匹配正确的位置,进而继续匹配。这里的第六步就是「回溯」 + + + +正则 +文本 +备注 + + + + +ab{1,3}c +abbc + + + +ab{1,3}c +abbc + + + +ab{1,3}c +abbc + + + +ab{1,3}c +abbc + + + +ab{1,3}c +abbc +匹配失败 + + +ab{1,3}c +abbc +回溯 + + +ab{1,3}c +abbc + + + + +会出现上面这种情况的原因在于正则匹配采用了回溯法。回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。它通常采用最简单的递归来实现,在反复重复上述的步骤后可能找到一个正确的答案,也可能尝试所有的步骤后发现该问题没有答案,回溯法在最坏的情况下会导致一次复杂度为指数时间的计算。 +上面一段的内容来源于维基百科,精简一下就是深度优先搜索算法。贪婪量词、惰性量词、分支结构等等都是可能产生回溯的地方,在写正则表达式时要注意会引起回溯的地方,避免导致性能问题。 +John Graham-Cumming 在他的博文 Details of the Cloudflare outage on July 2, 2019 中详细记录了因为一个正则表达式而导致线上事故的例子。该事故就是因为一个有性能问题的正则表达式,引起了灾难性的回溯,进而导致了 CPU 满载。 +(?:(?:\&quot;|'|\]|\}|\\|\d|(?:nan|infinity|true|false|null|undefined|symbol|math)|\`|\-|\+)+[)]*;?((?:\s|-|~|!|{}|\|\||\+)*.*(?:.*=.*))) + +上面是引起事故的正则表达式,出问题的关键部分在.*(?:.*=.*)中,就是它引起的灾难性回溯导致 CPU 满载。那么我们应该怎么减少或避免回溯呢?无非是提高警惕性,好好写正则表达式;或者使用 DFA 引擎的正则表达式。 +[0-9] 与 \d 的区别 +此问题来源于Stackoverflow,题主遇到的问题是\d比[0-9]的效率要低很多,并且给出了如下的测试结果,可以看到\d比[0-9]慢了差不多一倍。 +Regular expression \d took 00:00:00.2141226 result: 5077/10000 +Regular expression [0-9] took 00:00:00.1357972 result: 5077/10000 63.42 % of first +Regular expression [0123456789] took 00:00:00.1388997 result: 5077/10000 64.87 % of first + +出现这个性能问题的原因在于\d匹配的不仅仅是0123456789,\d匹配的是所有的 Unicode 的数字,你可以从 Unicode Characters in the 'Number, Decimal Digit' Category 中看到所有在 Unicode 中属于数字的字符。 +此处多提一嘴,[ -~]可以匹配 ASCII 码中所有的可打印字符,你可以查看 ASCII 码中的可显示字符,就是从&quot; &quot;(32)至&quot;~&quot;(126)的字符。 +工具/资源推荐 +正则表达式确实很强大,但是它那晦涩的语法也容易让人头疼抓狂,不论是自己还是别人写的正则表达式都挺头大,好的是已经有人整理了常用正则大全,也大神写了个叫做 VerbalExpressions 的小工具,主流开发语言的版本它都提供了,可以让你用类似于自然语言的方式来写正则表达式,下面是它给出的一个 JS 版示例。 +// Create an example of how to test for correctly formed URLs +const tester = VerEx() + .startOfLine() + .then('http') + .maybe('s') + .then('://') + .maybe('www.') + .anythingBut(' ') + .endOfLine(); + +// Create an example URL +const testMe = 'https://www.google.com'; + +// Use RegExp object's native test() function +if (tester.test(testMe)) { + alert('We have a correct URL'); // This output will fire +} else { + alert('The URL is incorrect'); +} + +console.log(tester); // Outputs the actual expression used: /^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$/ + +文章大部分内容都是介绍的偏原理方面的知识,如果仅仅是想要学习如何使用正则表达式,可以看正则表达式语法或者 Learn-regex,更为详细的内容推荐看由老姚写的JavaScript 正则表达式迷你书 + +
+ + Read More ~ +
+
+
+ +
+

+ + 正则表达式入门,基础语法详解 + +

+ +
+ + + + +
+ +
+ 这两天一直在时不时的和 Neo4j 图数据库打交道。它的查询语句可以使用正则表达式,有一段时间没有自己写过正则表达式了,现在处于能看懂别人写的正则表达式,但是自己写不出来,语法规则都忘了。为了方便接下来的工作,所以特地复习复习正则表达式的语法。 +正则表达式简介 +正则表达式是用来匹配字符串的一系列匹配符,具备简介高效的特点,在很多语言中都有支持(java、python、javascript、php 等等)。在 windows 的 cmd 命令中也同样支持,例如使用命令 dir j*,那么只会罗列出所有以j开头的文件和文件夹。 +正则表达式基本语法 +正则表达式在在不同语言的支持语法略有不同,本文采用js的进行说明。js 中使用正则表达式的方法为str.match(/表达式/),即需要加两个斜杠。以下所有的代码段第一行为代码,第二行为返回结果,实验是在 chrome 控制台进行的。 +一直认为最好的学习方式就是实际操作,理论谁都能讲一大堆,但是实际做没做出来还真不知道。一个奇葩现象就是教软件工程的老师可能并没有在软件行业待过。 +普通匹配符 +普通匹配符能匹配与之对应的字符,默认区分大小写。 +&quot;Hello Regx&quot;.match(/H/) +[&quot;H&quot;, index: 0, input: &quot;Hello Regx&quot;, groups: undefined] + +正则标记符 + +i :不区分大小写 +g :全局匹配 +m :多行匹配(暂不管它,我用的少) + +参数直接加在最后一个斜杠的后面,比如&quot;Hello Regx&quot;.match(/regx/i),可以加多个参数。 +&quot;Hello Regx&quot;.match(/regx/i) +[&quot;Regx&quot;, index: 6, input: &quot;Hello Regx&quot;, groups: undefined] + +之前是表达式一旦匹配成功,就不再向字符串后面查找了,加上 g 后,表示进行全局查找。最后返回的是一个数组。 +&quot;Hello Regx&quot;.match(/e/g) +(2) [&quot;e&quot;, &quot;e&quot;] + +多匹配符 + +\d :匹配数字,即 0~9 +\w :匹配数字、字母、下划线 +. :匹配除换行的所有字符 + +需要注意的是,上面所有的匹配符都只能匹配一个字符。 +&quot;Hello 2018&quot;.match(/\d/g) +// 使用\d,匹配字符串中的所有数字 +(4) [&quot;2&quot;, &quot;0&quot;, &quot;1&quot;, &quot;8&quot;] + + +&quot;Hello 2018&quot;.match(/\w/g) +// 使用\w,匹配所有的数字和字母,需要注意没有匹配到空格 +(9) [&quot;H&quot;, &quot;e&quot;, &quot;l&quot;, &quot;l&quot;, &quot;o&quot;, &quot;2&quot;, &quot;0&quot;, &quot;1&quot;, &quot;8&quot;] + + +&quot;Hello 2018&quot;.match(/./g) +// 使用.,匹配所有字符,包括空格 +(10) [&quot;H&quot;, &quot;e&quot;, &quot;l&quot;, &quot;l&quot;, &quot;o&quot;, &quot; &quot;, &quot;2&quot;, &quot;0&quot;, &quot;1&quot;, &quot;8&quot;] + + +&quot;Hello 2018&quot;.match(/\d\w./g) +// 分析一下这个为什么匹配到的是201, +// 首先\d找到第一个数字2,匹配成功,紧接着\w匹配到0,然后.匹配到1 +// 整个正则表达式匹配成功,返回201 +[&quot;201&quot;] + + +&quot;Hello 20\n18&quot;.match(/\d\w./g) +// 这里匹配不成功,因为.不能匹配换行符,所以返回null +null + + +&quot;Hello 2018&quot;.match(/\w.\d/g) +// 首先看这个正则式,\w.\d,它要求最后一个字符是数字 +// \w.能一直匹配到空格,但是因为得满足\d,所以第一个匹配成功的是0 2 +// 因为是全局匹配,所以会接着匹配后面的018,也匹配成功 +(2) [&quot;o 2&quot;, &quot;018&quot;] + +自定义匹配符 +比如中国的手机号都是以 1 开头,第二位只能是 3、4、5、7、8,第 3 位只要是数字就行。如何匹配这样的字符串? + +[] :匹配[]中的任意一个字符 + +&quot;152&quot;.match(/1[34578]\d/) +// 第二个字符可以选择中括号中的任意一个 +[&quot;152&quot;, index: 0, input: &quot;152&quot;, groups: undefined] + +如果在 [] 添加了 ^,代表取反。即 [^] 表示除了中括号中的字符都满足。 +&quot;152&quot;.match(/1[^34578]\d/) + +null + + +&quot;1a2&quot;.match(/1[^34578]\d/) +// 只要不是[]中的字符,都满足,包括回车符 +[&quot;1a2&quot;, index: 0, input: &quot;1a2&quot;, groups: undefined] + +修饰匹配次数 +我们的手机号有 11 位,除了前 2 位有要求,其他9位度没有要求,那么是不是正则表达式就应该这样写呢? +1[^34578]\d\d\d\d\d\d\d\d\d + +很明显,这样写太麻烦,肯定有更好的方式,这里就可以修饰一下匹配次数啦。 + +? :最多出现 1 次 ++ :至少出现 1 次 +* :出现任意次数 +{} :分下面四种情况 + +{n}代表前面的匹配符出现 n 次 +{n, m}出现次数在 n~m 之间 +{n, }至少出现 n 次 +{, m}最多出现 m 次 + + + +例子很简单,一看就懂,不浪费时间。 +&quot;15284750845&quot;.match(/1[34578]\d{9}/) +[&quot;15284750845&quot;, index: 0, input: &quot;15284750845&quot;, groups: undefined] + + +&quot;15&quot;.match(/1[34578]\d?/) +[&quot;15&quot;, index: 0, input: &quot;15&quot;, groups: undefined] + + +&quot;152&quot;.match(/1[34578]\d?/) +[&quot;152&quot;, index: 0, input: &quot;152&quot;, groups: undefined] + + +&quot;152&quot;.match(/1[34578]\d+/) +[&quot;152&quot;, index: 0, input: &quot;152&quot;, groups: undefined] + + +&quot;15&quot;.match(/1[34578]\d+/) +null + +完整匹配 +按照上面的写法会出现下面的问题。 +&quot;ya15284750845&quot;.match(/1[34578]\d{9}/) +// 不是电话号码,也能匹配成功,需要进一步改进 +[&quot;15284750845&quot;, index: 2, input: &quot;ya15284750845&quot;, groups: undefined] + + +^ :在 [] 中代表取反,但在外面代表从开始匹配 + +&quot;ya15284750845&quot;.match(/^1[34578]\d{9}/) +// 现在就能从一开始匹配而且还得符合正则式才算匹配成功 +null + + +// 但是依旧会出现下面的问题 +&quot;1528475084523255&quot;.match(/^1[34578]\d{9}/) +// 不是电话号码也能匹配成功,还要改进 +[&quot;15284750845&quot;, index: 0, input: &quot;1528475084523255&quot;, groups: undefined] + + +$ :代表持续匹配到结束 + +&quot;1528475084523255&quot;.match(/^1[34578]\d{9}$/) +// 现在就能保证正确了,有^表示从开始匹配; +// 有$表示持续匹配到结束,即完全匹配 +null + +/* +需要注意的是,一个字符串从开始匹配和从结束匹配都没问题, +不代表整个字符串就没问题,比如 15284750845-15284750845 +这个字符串从开始和从结束匹配都能成功,但实际上是错的 +*/ + +特殊符号 +到这里发现正则表达式确实很强大,仅仅几个简单的符号就能匹配字符串,但是如果我们要匹配的字符本身就是前面用到的符号怎么办呢? + +匹配像$、^等特殊符号时,需要加转义字符\ + +&quot;1.&quot;.match(/./) +//因为.能匹配除换行的所有字符,所以匹配到1 +//但实际上我们想匹配.这个字符 +[&quot;1&quot;, index: 0, input: &quot;1.&quot;, groups: undefined] + + +&quot;1.&quot;.match(/\./) +// 只需要加一个转义字符就可以了,其他类似 +[&quot;.&quot;, index: 1, input: &quot;1.&quot;, groups: undefined] + +条件分支 +比如现在想匹配图片的文件名,包括 jpg、png、jpeg、gif 等等,这是多个选项,所以需要像编程语言一样,应该具备条件分支结构。 + +| :条件分支 +() :有两层含义 + +括号中的内容成为一个独立的整体 +括号的内容可以进行分组,单独匹配,若不需要此功能,则( ?: ) + + + +&quot;1.jpg&quot;.match(/.+\.jpe?g|gif|png/) +// 这样就可以满足条件分支了,不过下面又出问题了 +[&quot;1.jpg&quot;, index: 0, input: &quot;1.jpg&quot;, groups: undefined] + + +&quot;1.png&quot;.match(/.+\.jpe?g|gif|png/) +// 这里没有匹配到.和前面的文件名 +[&quot;png&quot;, index: 2, input: &quot;1.png&quot;, groups: undefined] + + +/* +其实我们想告诉它的是,.和后面的每一个条件分支的值都是一个独立的整体 +但是它把.+\.jpe?g、gif、png当成了各自独立的整体 +我们并不想让它这样切分,所以我们来告诉它怎么分才是正确的 +*/ + + +&quot;1.png&quot;.match(/.+\.(jpe?g|gif|png)/) +// 现在可以匹配成功了,但是它多匹配了一个 +// 因为括号的内容可以进行分组,单独匹配 +(2) [&quot;1.png&quot;, &quot;png&quot;, index: 0, input: &quot;1.png&quot;, groups: undefined] + + +// 所以最终写法如下 +&quot;1.png&quot;.match(/.+\.(?:jpe?g|gif|png)/) +[&quot;1.png&quot;, index: 0, input: &quot;1.png&quot;, groups: undefined] + +贪婪与懒惰 +// 首先看一个例子 +&quot;aabab&quot;.match(/a.*b/) +[&quot;aabab&quot;, index: 0, input: &quot;aabab&quot;, groups: undefined] + + +/* +上面的匹配没有什么问题,但实际上aab也是可以的 +也就是aab也是符合条件的,那又是为什么呢? +*/ + +因为在正则表达式中,默认是贪婪模式,尽可能多的匹配,可以在修饰数量的匹配符后面添加 ?,则代表懒惰。 +// like this (^__^) +&quot;aabab&quot;.match(/a.*?b/) +[&quot;aab&quot;, index: 0, input: &quot;aabab&quot;, groups: undefined] + +到这里应该就差不多了,再深入的,就自我查询知识了。配一张正则表达式速查表。 + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/Ykw3uqJED/index.html b/Ykw3uqJED/index.html new file mode 100644 index 00000000..19a5afcb --- /dev/null +++ b/Ykw3uqJED/index.html @@ -0,0 +1,862 @@ + + + + + + + + 随笔思考 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 随笔思考 +
+ + +
+

+ + 如何获得好运气 + +

+ +
+ + + + +
+ +
+ 一点幼稚的思考 +从我自身浅薄的经历做了一点总结,如果某个公司的员工有超过三分之一都是同一个地方的人,那这个公司大概率是个骗子公司,因为常规渠道他们无法招到员工,只能靠亲戚、同学、同乡一类关系拉拢,如果下限再放低一点就是传销。这类公司开早会常常是这样的——主管在前台扯着嗓门儿大喊:“亲爱的家人们,大家早上好!”下面的员工齐声回答:“好!很好!非常好!” +之前在财富与幸福指南中有翻译过一点纳瓦尔的语录,今年 4 月份这本书已经出版了,里面有提到把事情做到极致,让机会自动找到你,让运气成为必然。结合自身经历愈发认可其说法,在一次少儿科创比赛活动中遇到一个小领导,他问我现场的学生都是谁教的,我说是某某老师教的,他突然冒出一句:“某某老师教的学生肯定不会差”。这样随口而出的一句话给了我不小的触动,这个某某老师把事情做到极致,名气在他不知道的情况下就打出去了,这就是运气。 +发现那些让人仰慕的大佬都有一个无所畏惧的特质。面对同一项未知的任务,所有人都不知道如何去完成,强者与弱者唯一的区别是敢不敢接这个任务,强者总是毅然扎进去并着手寻找方案,弱者总是以不知道怎么做、没有学过相关知识等理由推脱。长此以往大家就觉得强者什么都会,口口相传自然就带来了运气。 +当下社会快速的生活节奏让人喘不过气来,很多人长期都是两点一线的生活,每天的生活轨迹完全重合没有一点波动。生活需要变量才会精彩,机会往往是跟随变量出现的,保持年轻的折腾,保持一颗好奇心才会有更多的可能。把乔布斯的话搬出来用一下:Stay Hungry,Stay Foolish。 +不一样的生活 +偶然了解到电鸭社区,它所提倡的只工作,不上班理念让我眼前一亮,数字游民这一新的词汇加到我的字典。原来这世界上还有可以不打卡的工作,他们的工作地点居然完全没有限制,在工作的同时还可以开着房车旅行...... +非常希望能体验一下数字游民生活,上帝彷佛能听见我的愿望一样,立马就把一个机会给了我。牛客网需要一些人审核主站题目,后续是出题、审题等等一系列工作,可能因为我交付的题目质量还不错,她们愿意把一些高利润的工作派给我,一个题目从几百到几十价格不等,有俩月拿到的劳务报酬竟超过了本职工资。牛客让我体验了一把远程工作乐趣,以后如果真的创业失败了,就老老实实找个远程工作苟且吧。 +通过Jina全球社区的负责人 Lisa,了解到一个2050团聚活动,这是我第一次接触如此纯粹的社区,把分散在各行业又比较理想主义的人汇集到了一起。目前对这个社区是很有好感的,容我观察两年再细说体会。 +鼓捣独立博客 +我是快毕业时开始接触公众号的,那时候的环境很单纯,很多作者都把公众号当博客用。认识的一个和我同龄的作者,为了能把某个算法讲清楚经常熬夜 P 图。那时候的程序员小灰还叫玻璃猫,刘欣大大的文章标题比现在也要朴素。我自己那段时间写文章尤其认真,因此也认识了许许多多网友,收到了博文视点和另一家出版社的出版邀请。那时大部分人还不懂什么是 IP,流量一词更普遍的含义还是电话卡用的那个上网流量,10 万+偶有出现但频率不高。 + +刘欣大大的码农翻身依旧朴实,只是文章标题明显有一些刻意了,今年刘欣给一些编程老师免费送去了新书《半小时漫画计算机》 + +慢慢有人开始专门为了 10万+而写文章,抖音一类短视频应用出现后,整个互联网环境开始变得浮躁,大家为了流量不择手段,一些人为了博取流量而瞎编骇人听闻的事情。比如年初在抖音出现的血奴事件,更为可笑的是一些权威媒体居然转发了这个视频,官方媒体为了流量不查证新闻真实性就转发,那便是没有责任感的媒体。 +媒体常常为了流量而故意删减新闻细节,比如之前文章多从自己身上找问题中所提到的新闻。互联网让大家的情绪得以便利的发泄,常常故事听一半就抑制不住心中正义,评论下面骂两句、转发个朋友圈都是没有成本的事情。台剧我们与恶的距离和电影狩猎对这个话题有比较深刻的探讨,值得花时间看一看。 +百度取消了快照功能,必应中国区没了快照;一打开微信不出现视频号的可能性几乎为零;标题党的文章充满了订阅号;公众号不允许外链和无法修改错误很烦心。于是我鼓捣起了独立博客,在独立博客中可以自由的发布和修改文章。我选择的博客写作工具是Gridea,选它并不是因为它好用,而是因为上面有一款主题我比较喜欢,我自己花了些时间将该主题的 bug 修复了,并且在原基础上增加了几个 sidebar card,大致长下面这个样子,我个人还挺喜欢的。 + +有意思的事儿 +只能发送 500 英里的邮件。一所大学的邮件只能发送 500 英里(800 公里),这听起来像是一个胡编乱造的故事。原因是因为这所大学的服务器配置错误,他们将连接到远程服务器的超时设为了零延迟。而程序运行时 0 超时是按 3 毫秒计算的,一旦 3 毫秒内没有收到远程服务器的答复,就认为邮件无法发送。光在 3 毫秒时间前进的距离,刚好就是 500 多英里。 +导致电脑宕机的原因竟然是 Janet Jackson 的一首歌。微软资深工程师 Raymond Chen 披露 WinXP 发布早期,一些用户报告电脑系统会意外崩溃。起初他们以为是 Windows 的 bug,但很欣慰的是发现同行也存在这个问题,最终定位到这个问题的根源竟然是 Janet Jackson 1989 年发行的歌曲《Rhythm Nation》。歌曲中一些旋律会和当时一款 5400 转硬盘发生共振,进而导致电脑崩溃。 +“看黄片被罚”的闹剧何时休?这是一篇旧闻,从网站上的时间看是 2012 年 6 月 17 日。讲述的是延安小夫妻在家看黄碟被抓,生活几乎陷绝境的故事。 +推荐一个视频 +这是一个插画师、立体书设计师在一席的演讲,她把自己的生活都做成了好玩儿的立体书,如同演讲主题在生活里东张西望一样,池塘子是一个地地道道的生活观察家,生活中的那些无形爱都被她做成了立体书。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 媒体的谎言|楼下的小餐馆|服务质量的思考 + +

+ +
+ + + + +
+ +
+ 上个月很多地方都可以看到一个新闻,90 后安徽女孩 16 岁时到杭州打工,省吃俭用拼命存钱就为了在杭州买房,十年时间存了将近 100 万。但是 2020 年的疫情对她的服装生意影响太大,她先是自己在网上卖淫,后又通过社交软件招募失足妇女卖淫,半年时间获利近 60 万。这个女孩的故事本身是很励志的,但网上的故事版本基本是下图这个样子的,一句话概括就是「女孩卖淫获利 160 万」,简单明了、易传播、方便吃瓜,吃瓜是第一生产力。 + +相比之下,那些通过掩盖部分事实来教育国人的文章,到显得不那么可恶了一些。比如都说英国人喜欢在地铁看书看报纸,对比我们国人在地铁都是看手机显得没啥素养,但却很少文章提及伦敦地铁修建于 100 年前,由于隧道小致使安装通信设备的难度极大,所以他们的地铁是没有手机上网条件的。全线实现手机上网后,咱再来看看他们看书还是看手机! +媒体总喜欢发一些坏的东西,毕竟更容易吸引眼球且符合人的本能,人还没死但去世的相关新闻已经出来了,故意掩盖部分事实让大众理解扭曲。考虑到「流量就是金钱」这个前提,有时候也能理解现在媒体的做法。在台湾电视剧《我们与恶的距离》中就有一个片段,主角很清楚一个新闻能带来的收视率,但是自家的记者还没有考究到该新闻的真实性,当其它电视台都在报道这个新闻时,自家电视台应该随大流报道,还是坚守新闻人的底线? + +人把自己置身于忙碌当中,有一种麻木的踏实,但丧失了真实,你的青春,也不过只有这些日子。你看到什么,听到什么,做什么,和谁在一起,有一种从心灵深处满溢出来的不懊悔,也不羞耻的平和与喜悦。 +——电影《无问东西》台词 + + +用了差不多一年时间观察楼下的几个小餐馆,第一个引起我注意的是一个叫做「小鲜肉烧烤」的店,同期在它的隔壁也是一个烧烤店,在离它不到一百米的地方还有一个烧烤店。那时候正值寒冬,烤串拿出来很容易就变凉,大家都知道烤串凉了就不好吃了,这时小鲜肉烧烤自己买了个大棚子,同时还给每个桌子放了一个保温盘。 +有了大棚子遮风,顾客就不需要忍着寒风吃烤串,而且保温盘让烤串一直都是热的。其它两家则一直是都是让顾客在忍着寒风吃串,我有一次骑行回来因为烤串上的太快了,里面的羊肉串中间那一坨肥肉都凝固成油泥了,骑行腐败的心情大打折扣。 +小鲜肉烧烤专门有个人在外面守着,那眼睛就跟老鹰一样犀利,顾客还没有坐好菜单就送到面前了,另外两家则是师傅专心烤串,进去了是一脸懵逼不知道找谁,点完餐也不知道交给谁。过完年回来,楼下只剩一家烧烤店了。 +紧接着小鲜肉烧烤的隔壁被一对夫妻租了去,他们做的是羊肉米线,想来两口子定是雄心勃勃、信心满满的。前三天用了一点小技巧引流,活动期间可以凭点餐券再免费领一份羊肉米线,出于占便宜和好奇的心理,开业第一天我就去了这家店,看的出来米线是在水里泡的太久了,而且表现非常的不光滑,猜测是为了节省成本用了便宜的原料。 +大概坚持了一周的样子,这家店在饭店就无人问津了,左邻右舍的店铺都忙的不可开交,这家店铺是老公和老婆对坐玩手机,「葛优躺」完美的挂在了脸上,店里显得没有生机更是让人不想踏进去一步。这个商铺过了不到半个月就转手了。 +接盘侠是一个做冒菜的老板,老板开业第一天就大火,服务好、菜品多、吃法新,生意一度优于旁边的烧烤店。大概过了半个月的样子,估计老板觉得店里比较稳了,于是开始把店里的事情逐渐放给员工,好几次我去那家吃都没有看到老板。 +没有老板在自然一些细节把控不好,比如从厨房到餐桌的过道肯定是走最多,而那条过道上的污渍即使你有意无视它,它也能非常容易的钻到你眼睛里;顾客选择的是干拌冒菜,但是却在盘子里看到非常多的汤水;煮菜也看得出来做了流程化处理。 +就这样冒菜店的生意以非常明显的速度在下滑,好在老板意识到问题又回来每天坚守阵地,但总体来说已经比不上刚开店的时候了,现在是和小鲜肉烧烤的人流量基本持平。 + +会有意识的去观察这些事物,大概和我现在从事的工作有关系。我现在是一名少儿编程老师,大学做家教教学生编程时,觉得只要自己专业技能够硬,学生问问题都能回答出来就可以,喜欢用一些听起来牛逼的专业词汇给学生讲课。现在想想那时候真是傻,那时候觉得人家听不懂叫牛逼,现在明白人家听不懂叫装逼。 +开始主动的去反思自己的问题,讲课的流程、节奏、纪律、有趣等等,并且有意识的提升课堂的仪式感。现在讲课的问题还非常多,但也收获了一些学生和家长的认可。一个孩子我接手了之后得到的奖升了一级,家长一激动给我发了「感谢恩师」,笑死我了😂 +一个学生课上说自己的一个同班同学「还在渣渣学而思学习编程」,虽然孩子只是随口一说,但是这句话让我心中暗喜。除了在外面上课外,我同时还在一些小学上社团课,一个学生想要到外面上课非选我不可,不然就不交钱报名...... +体会到服务业的一些乐趣,也真正认识到了质量的重要性,质量的轻微变动顾客都能感受到。尽力做好自己可以做的事情,但是也要明白事情不是自己能完全掌控的,就像张小龙说的跟漂流瓶扔出去一个瓶子是一个道理,看到以后发生什么不是我们所能够掌控的。 +最后放两个学生的趣味作品吧。 + + + +
+ + Read More ~ +
+
+
+ +
+

+ + 那些经历过的人生谎言 + +

+ +
+ + + + +
+ +
+ 哈佛大学从 1938 年开始做了一个研究,研究内容就是「到底什么样的人活的最幸福?」,研究组在 75 年时间里跟踪了 724 个不同的人,到教授在 TED 演讲的时候仍然有 60 人在世并仍参与研究,大多数人都已经 90 岁了,现在已经在开始研究他们的子孙后代,并且人数达到了 2000 多人。 +这 724 人后来成了医生、律师、砖匠等,有人成了酒鬼、有人患上了精神分裂症,还有一位成了美国总统。估计也只有哈佛这样的地方才会去搞如此变态的研究,有足够的资金去承受这样理想的研究。人人都想要活的更加幸福,哈佛 75 年研究汇集成几万页的数据,然而结果只有一句话:良好的人际关系能让人更加健康和快乐,和我们所追求的财富、名望、努力工作等都没关系。下面是原视频内容。 + +小学语文课本上面有一个课后题要求自己编写《负荆请罪》故事的后续,记得当时老师把这个题目当作家庭作业留给学生了。回家之后只顾着玩泥巴、看电视了,第二天早上第一节课就是语文课,老师一上来就抽不同的同学给大家读自己写的故事后续。 +在其他同学读故事的时间,我慌忙的写了一段应付老师的抽查。不出所料,我也被抽中起来读自己编的故事了,没想到慌慌张张编的故事居然深得老师之心,还被老师当作范文给大家又读了一遍,因为我在故事里面用了「连忙」、「赶紧」这样的一些词汇,能表现出廉颇和蔺相如之间的客气等等。 +但我清晰记得那时候只想在被老师抽中之前把作业写了,胡编乱造完全不管写的好不好,只需要有一段话能够证明自己是做了作业的,万万没想到那么简单的一段话被老师赋予了数不清的情感。可类比的是各种网络平台上的文章,尤其是某个公司哪一位老总成功上位,铺天盖地的文章会把这个人肠子都翻出来看看,就好像写手比主人公还要了解自己。 + +最开始写文章是大四时候太闲想找点事做,断断续续到现在也算是写了 2 年多了,合起来有 100 来篇文章的样子。偶尔我会把以前写的文章搬到其它平台,比如把以前写的讲磁盘原理和自己做自然语言处理的两篇文章搬到 InfoQ,两篇文章都被平台置顶且精选至首页,而在公众号中讲磁盘原理的那篇文章阅读量是最低的。 +前不久写了一篇与拼多多等电商平台有关的内容,一发出来公众号后台立马将近 20 个取关,让我这个本就没几个读者朋友的公众号雪上加霜。然后我鼓起勇气把这篇文章复制到满是大佬的生财有术社群里面去,隔了几天就被评为了精华帖,拿到一颗龙珠,8000 元到手。 +去年年底想起了和师兄们一起刷牛客网题目的时光,随手就把 18 年写的一点文字贴上去了,18 年时候我的技术还很弱鸡,没想到贴上去却获得了还不错的反馈,一篇还被牛客小编标记为精选帖。于是我把 19 年遇到的关于服务器性能优化的一篇文章贴上去,那篇是我花了一个月时间查各种资料再加上自己实践的经验,现在看来都依然觉得写的非常不错,然而这篇文章在牛客网连一个赞都没有。 +我写文章是兴趣驱动,写的好写得坏都不咋关心,但偶尔会有那么一两篇文章自己花了很多的精力去写,写完还自嗨一下写的不错,结果一发出来就打脸了。也出现过我自己敷衍了事像是一时应付写的文章,却被好几个大 V 看中,希望转载的。 +忘记 18 年怎么加了「寒泉子」微信的,当时他输出的文章很多是关于 Java 虚拟机性能优化的,没有一篇文章我能读懂的,所以寒泉子的文章对我完全没有作用,围观他的朋友圈只是看大佬是如何成长的,那时候寒泉子创建「笨马科技」公司不久(现在笨马已经获得了高瓴资本的投资)。成功创业且快速的成长让寒泉子得到了更多人的关注,寒泉子的文章相比那时候阅读量提高了很多,但我依旧看不到他文章对我的价值,但我知道看不到好文章价值的原因是因为我的水平不够。 +说了这么多就是想表达很多文字不是没有价值,只是因为你的水平太低而看不到他的价值(水平太高也会看不到价值),并不一定都是别人写的差。一篇文章给了你一个关键字那他就是有价值的,我们更多的是应该去培养自己抓线索的能力,一个不起眼的词语就能揪出来太多有趣的东西,只是大多数人即使把方法告诉他了都还是不会操作,需要努力提升自己的信息素养。 + +读了些书、见了些人、经历了些事,总结了几个人生的高级谎言。中年危机是不思进取的人经历的,尽量不要被社会上那些营销话术给骗了,少看那些什么发展各种副业挣钱的广告,把搞这些的精力好好用到工作上薪资早早的就涨起来了,尤其是玩副业几年都没有玩出花样的人,更应该把心收一收踏踏实实工作。 +大家都知道「复利效应」这回事,比如 100 块钱按每年 5% 的利息计算,50 年后就能变成 1000 多块钱,看这个收益是不是非常可观?这个谎言的真相是你找不到年化能长期稳定在 5% 的投资产品,市面上各种理财培训都会说这个谎言,一些人自己本来没有赚到钱,却通过教别人怎么赚钱而赚到钱了。 +30 岁之后身体就不行了、女人生娃一定要在 28 岁之前。乍听起来没有什么问题,但仔细一想就会发现这里采用年龄进行量化就不对,科学的量化应该是采用身体机能的各项参数。年龄只是一个表象的东西,成熟也同样与年龄无关,而与经历相关。 +观察了一些电信诈骗的案例,有认识的朋友也有从未谋面的网友,自己也协助警方做了一次证人。会主动给你发营业执照、自己工牌一类信息的就是骗子。如果销售多次给你强调自己是正规公司,那他们基本就是不正规公司。凡是你根本不认识的人,但一上来就说要带你赚钱的,简单的当骗子处理即可,百分百不会错杀一个好人。 +每个人都有一套稳定且自洽的逻辑闭环,自己的观念代表的是自己这个人,观念被否定相当于自己这个人被否定,可能这就是会吵架的一个内在原因,「老顽固」大概也是这么来的。人天生就喜欢呆在舒适区,不同观点发生碰撞就是把稳态打破,别人否定自己是一件极难接受的事情,为什么会难以接受呢?因为打破稳态需要思考,而思考是一件费能量的事情,生物的天性就是要节省能量,这大概和祖先生活的环境有关系,虽然现在的环境已经不需要这样做了,但遗憾的是基因却还没有适应,自己努力克服基因的缺陷吧。 + +学生 A 就读于成都一所中法合办的小学,进出这个学校多次之后的感受就是学校有钱,学生家里也都比较有钱,所以大部分学生都属于调皮类型的,综合该校多名学生口中的信息,每个班至少气跑了两名以上的老师。学生 A 在我处学习编程,他们班的同学都属于比较社会的那种,班主任老师也被这帮学生训练的贼有经验。 +学校有一个厕所坏了,所以物管处就把这个厕所给锁上了,然而这种被锁上的门总是能激起学生的好奇心,很多学生下课就跑去门缝观察 💩,甚至有的学生还翻墙进去观察 💩,里面最有创意的学生应属学生 A。 +厕所里面被拖把遮住了,学生 A 在门缝寻找到了一个比较好的观察角度,大吼了一句「奥利给溢出来了」,另一个学生并没有看到所以就翻墙进去看,却一不小心掉进坑里了,学生 A 嘴里吼着「XXX 满是奥利给的跑出来了」跑开。估计这是有史以来「奥利给」这么正能量的词语被侮辱的最惨的一次。 +此事被学生 A 的班主任老师知道了,要说就得佩服这种班级的老师,不打不骂只让学生以「奥利给」为主题写一篇观察日记,要求学生把观察 💩的过程详细描述出来。学生 A 和班主任老师配合的这出戏在学校广为流传,大大提高了学生 A 的名声。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 幸福可以慢慢浇灌长大 + +

+ +
+ + + + +
+ +
+ 电影《点球成金》讲的是一个没有资金实力的弱队如何翻身的全程记录,在薪水预算只有强队 10% 的情况下如何去争夺冠军。与大多数体育片不一样的是,这部电影是从管理层入手的,推荐想要创业或是正在创业的人看一看,在明知道自己的做法是对的,但是又没有人支持、没有人看好的情况下,一个领导者应该怎么办?当明知开除某个球员就意味着他职业生涯的结束,甚至会将他推向生活窘迫的深渊,作为一个管理者是否会一时心软而手下留情...... +电影中的彼得(耶鲁大学经济学硕士)采用了一种全新的方法(数据分析)挑选球员,当然这种方法现在看来已经见怪不怪了,军事运筹学中也使用了相当的方法来计算如何赢得一场战斗。把这样的方法应用到自己行业的难点在于「量化」,我觉得企业管理者在这方面可以多思考思考。 +《原则》一书中作者也提到类似的点,将自己平时做决策的过程量化、抽象,将其转换成计算机可以理解的模型,计算机是没有那些乱七八糟的情绪的,只要把模型抽象的足够好就可以降低自己的成本,还能提高容错性。书中提到了对员工采用了性格测试进行分工,我现在是比较认可通过这种方式来决定员工具体工作的,想起来以前参加校园招聘的时候,一度认为那些大企业给员工做性格测试是不是钱多的没处花了,现在想想还是自己太傻。 +出于好奇我也给自己做了个 MBTI 测试,发现我是「表演者」人格类型,了解了一点关于我自己性格上的缺点,一些无伤大雅的问题大可以听之任之,一些需要改进的地方我相信也是可以通过持久的努力改变的。我目前对性格测试也有些怀疑,但我很清楚它比星座说「你是个渴了就会喝水的人」更具科学性一些。 + +我特别羡慕的两个人是读库的创始人老六和自己上大学时遇到的花儿,还不知道老六的朋友可以花一两分钟翻一下为啥人人都爱《读库》的老六?在我看来老六做的东西已经不叫工作了,而是叫事业了,不知道我这一生能不能在某个时间节点达到那样的状态,或是更可能这一生都达不到。刚好前几个小时看完了读库 2020 年(北京站)的视频,老六提到了六个问题应该是大多数人都存在的,比如「没有应变能力的坚持」、「不给时间压力便遥遥无期」,点击读库年会(北京站)完整版视频可以观看。 + +我第一次遇到花儿是在上大学的时候,那时候她还在成都宽窄巷子做青旅,几次到成都我都是到她那个青旅住下,因为在那里总是能遇到很多有趣的人。大概在大二快结束的时候我看到花老板发了朋友圈,才知道她去到大理做青旅了,也是通过朋友圈知道了她还有一个公众号,现在主要是通过花老板的公众号观察她的生活。我羡慕花儿那种把日子过的很幸福的状态,各方面细节可以都可以看到花儿是一个很知足的人,写歌、作曲、唱歌,日子看起来好不快活,花儿的公众号名称叫「微小而确实的日常」。 +日本的杂物管理咨询师山下英子写了一本《断舍离》,从 20 年 1 月看到这本书开始,我一直都在学习和实践断舍离,我个人觉得断舍离的人生整理理念是很受用的,我们的人生就应该多尝试断舍离,更多的东西只会让自己的脑袋更像浆糊,当然这可能和商业行为有些背离。 +商业广告会告诉你他们研发了一种减肥药或是减肥茶的产品,使用之后就能快速的瘦身塑形,但仔细想想要瘦下来不应该是减少碳水化合物的摄入吗?为什么要一边给自己打针又一边狂吃高胆固醇的食品呢?已经看不下去的书、已经挽回不了的爱情这些不都应该舍弃吗?商业广告会告诉你需要做加法,但其实你真正应该做的是减法。 + +从学校毕业已经快要有 3 年了,真正开始积累社会经验是 20 年下半年,大胆在这里分享自己学到的一些东西。看一个人靠谱不靠谱不要听他的嘴巴怎么说,而是要看他怎么做事、怎么做人、别人对他如何评价的。如果想要看一个人的能力如何,看看他之前干成过什么事就可以了,干成的事情是最好的佐证。如果他老是强调自己是多么的可靠,自己的实力是多么的强劲,那他基本就是个不可靠且没啥实力的人。 +一些文章会用「众所周知」、「有研究表明」、「很多学者指出」、「我身边有很多例子」这样的开头来证明某个观点,那大概率文章中内容是在瞎扯淡,如果要你要反驳的话他还能举出来更多的例子,不与这样的作者进行争辩是比较节约时间的。想了一下这个问题以前在我身上也挺严重的,现在已经减轻很多了。 +找工作的时候看一个公司靠谱不靠谱可以看给你缴的社保多少,比如公司实际给你发的薪资是一万,但是缴纳社保的基数却填个 3500,说到底就是公司舍不得给员工花钱,舍不得给员工花钱的公司你还想从他那里得到点什么呢?同样那种挖空心思避税的公司也不值得去,会和税务机关玩猫腻的公司难道还差和员工玩猫腻的胆子吗?我遇到过这种公司,工资分几笔发到手就是为了避税。 + +春节回老家发现了一个有趣的现象,长辈们手机里面必备的两个应用是微信和抖音,他们大多数都很愿意在抖音上面分享自己的生活,即使拍的作品土得掉渣也非常乐于「分享美好生活」。长辈之间有很多许久没有联系的儿时玩伴或是亲戚,有一些都超过 10 年没有联系过了,甚至连对方长什么样子都不太记得了。 +不得不承认抖音的推荐算法确实很牛,十多年没有产生联系的朋友抖音能帮你找到,再通过抖音私信互换微信,老友居然就这样再次取得了联系。用微信打个视频电话问候一下老友,通过了解对方正在做的事情还能缩小信息差,这样的聊天还能让他们的心情变得更好。 +做过一段的信息流广告,真真切切体会到了「如果你没有花钱买产品,那你就是被卖的产品」,这是纪录片《监视资本主义:智能陷阱 The Social Dilemma》中的一句话,以前在阅读高于自己的作品,远离精神毒品中批评过抖音、快手一类的产品,但现在我得改一点这个观念,50 岁以上的人没事时候刷刷抖音挺不错的,年轻人还是别去当被卖的产品了。 + +在深圳有一次和老叔吃饭的时候他讲了一点自己的经历,我已经记不得他年轻时候在哪个公司了,当时他请客在阳光酒店(深圳第一家五星级酒店)吃饭,只需要签个字就行了,那时候非常的牛气。他说正是那个时候把他害了,自己得意忘形不思进取,不然他现在也不至于都 60 多了还要给老板低身下气的工作。 +另一个老叔也分享了他年轻时候在湖北一个国企工作的经历,职位很高且收入不低,非常宠爱自己的小儿子。现在儿子要买车子直接给他打电话:“老爸,我要买车你给我多少钱?”他说自己没有钱,儿子回了句:“那你没有钱还有什么用?” +过年回家姨夫告诉我他前面几十年都活的迷迷糊糊的,一直快到 50 岁了才找到自己的方向。他说人知足而且有某个东西能让自己自信,就会过的比较幸福,自信不应该来源于金钱,要懂得赞美别人,要明白女人也是很幸苦的。我惊讶的发现姨夫作为一个大学生眼里瞧不起的农民工,已经比大学生高出好几个 level 了,而且他的收入也已经比 996 的程序员高出几个 level 了。 +最后推荐一个轻松的视频吧,一席的演讲视频杨小峰:寻找昆虫,里面不仅有很多精美可爱的昆虫照片,还有很多大开脑洞的玩味,而且这个老师有脱口秀的那味道。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 我自己的职场故事与经验 + +

+ +
+ + + + +
+ +
+ 本文首发于牛客网的社畜职场交流圈。 +从毕业后就基本没有看过牛客网了,前段时间再打开牛客网看发现有了很多新的板块,讨论区也变得比以前要活跃很多,话题也从仅仅的找工作面试类延伸到了职场、租房等等,牛客也开放了几个创作者计划的活动,我也用自己的职场故事和经验来参加一下。 +我的经验是第一份工作很重要但是没有大家想象的那么重要。我一直都是一个马大哈,签三方协议的时候面试官告诉他他给我安排的是做 5G 的部门,看到一大堆硕士生拿着简历都在签三方协议,在场的本科生没有几个,我啥都没有细问就直接签了。 +到公司报道的时候才知道部门是做 5G 测试的,心里一下有些失落觉得可能后面的开发之路就毁了。但实际上我想错了,等入职培训完了之后真正进入工作时候才知道,我所在的团队守护着 2G、3G、4G 和 5G 的所有测试架构,团队平时为了提高部门测试效率所开发的工具已经早早的延伸到无线院,而真正的 5G 开发部使用的语言是汇编语言和 C 语言,每个部门只负责 5G 产品的一小部分,具体到某一个人就负责的更细粒度了。 +所以一个有趣的现象就出现了,因为我们团队的人需要维护 5G 的测试架构,竟然对产品的了解要比开发部的人更加全面一点(当然深度肯定是远远不及开发部的人),而且开发部的人也得使用我们团队开发的工具,不管开发部的人怎么怼测试人员,我们都是有办法怼回去的。 +从我个人和一些职场人士的交谈来看,第一份工作并不会决定你将来要做的是什么工作,更多的是学习为人处事的态度,适应社会与职场上不管你喜欢还是不喜欢的规则,但是请注意你的技术栈是由你的工作决定的,你自己是决定不了你的技术栈的。 +第二个经验就是物以稀为贵唯独知识不是。无线院准备做几个工具解决几个部门都存在的问题,要求每个部门出一个人参与一个工具的开发,我们部门被分配做一个 5G 数据度量系统。测试部门的技术栈无非就是 python 什么的,整个部门 200 多人会写前端的不超过 10 个,而这个度量系统后端使用的是 java,前端使用的是 vue,部门再一眼数过去部门没有人用过 Vue,而有 Java 经验的只有我和我师傅,师傅是团队的小组长无法抽身干这些对部门没多少收益的事情(度量一看就知道是给领导们用的)。 +按照正常的安排我这个时候是应该被派到枯燥的基站上玩 3 个月的,而且基站相关的师傅都早早的给我分配好了,因为这个度量系统我稀里糊涂的被派到南京出差了,当然也就免于去干无聊的基站测试工作,顺便还给了大把的时间让我学习以前不知道的 Vue。 +说实话这个度量系统本身没有什么技术性,但是它却让我接触到了 5G 的产品经理、需求教练等等,每次和他们开会让我逐渐有了一点从领导视角去看问题的意识,他们能把问题看的更加全面、透彻,而且大多数时候都是在做取舍。 +记得高中化学老师开了个玩笑,地震过了你就不学逃生知识了?你不知道我们这个山沟沟很容易发生泥石流吗?那一段时间学习的 Vue 技术并没有浪费,我后面从 0 到 1 开发一个系统的时候,刚好就用 Vue 将团队厚重的 Angular 给替代了,也没想到我现在从事少儿编程教育工作居然还用到了那时候的技术(开发平台的前端)。 +希望你别领会错我的意思,不是让你像打散弹枪一样去胡乱学习一大堆知识,而是抓住每一次学习的机会尽量把它理解透彻。 +第三个经验是保持谦卑之心,尽量独立解决问题。进入职场之后每个人都有自己的工作,不再像学校那样你问老师一个问题,老师会把答案告诉你还生怕你学不会,所以遇到问题先去网上搜一搜资料,搜索引擎前 8 页的链接都点开看一下,百度不行就上谷歌,谷歌不行可以去 Github 搜一搜,要是还不行的话就去 StackOverflow 上面提个问题。 +如果你按照我上面说的路径都找不到答案的话,那这时再去请教一下部门的老员工,有了前面的探索你提出来的问题会更加有水平,高水平的提问也会帮助你逐渐赢得老同事们的认可,想一下天天提的问题像个小学生一样,可能也就是自己认为是好学多问。 +不管你认可还是不认可,同时给 A 和 B 抛出同样的一个问题,A 能把问题解决而 B 不能解决问题,那 A 就是要比 B 牛逼。我越来越相信实力是赢得别人认可的基石,能独立解决问题在一定程度上也说明你是个靠谱的人。能解决小问题人家才会把更大的问题交给你,不要嫌弃那些小事情,把小事情做到精致别人就会给你更大的成长机会。 +第四个经验是用心去做事,快快乐乐服务同事。不管你的技术多么牛逼,你敢拍着胸脯说自己写的程序没有 bug 吗?如果同事给你说你的软件哪里有问题,虚心的接受并快快乐乐的帮人家解决问题,他们是你的天使用户,如果你连自己的天使用户都留不住那还怎么留住外面的用户呢? +由于 5G 测试用例实在太多了,所以我和另一个同事一前一后负责开发测试任务管理系统,那是我第一次做复杂交互的前端系统,刚开始的几个版本我写的烂的要命,有的按钮甚至能让用户卡四五十秒,每天都会接到五六个同事的电话说系统太卡了,但是我自己那段时间也没办法啊,技术水平不够完全不知道怎么去优化。 +所以同事不管什么问题我照单全收,还专门列了一个问题表,每次同事在旁边说的时候我打开往里面添加记录,自己的产品不行就先把服务做好嘛。很多费时费力的操作我索性加班帮同事搞定,所以那段时间系统虽然难用的要死,但是没有一个同事直接用邮件或是当面怼我的,虽然我知道他们是脸上笑嘻嘻心里妈卖批,但我眼睛只能看到脸上的笑容。 +除了学习技术优化系统性能外,我还自己看了一些关于设计的文章,不懂设计那我就想着怎么让用户用着舒服呗。逐渐系统都能做到实时响应并且美观大方当然操作还尽量简单,被其它部门的同事看到偶尔会有几个主动询问的,加上师傅的推动很容易就把系统推开了。 +在师傅的敦促下我将其做了平台化开发,离职前已经将西安部门接入系统。那段时间与在美团和去哪儿工作的学长们交流,他们在定级答辩时评委更关心的是你如何把系统推广出去的,里面用到了什么你觉得牛逼的技术在他们眼里并不牛逼。 +回到最开始的第一份工作内容不太重要,我现在是一个少儿编程老师同时做着少儿编程平台的研发工作,本来想好好的做一个少儿编程老师就行了,谁能想到之前的程序员经历让我在新的环境竟然更加有竞争力。借用别人的话来说就是,应该多一点洒脱,人生会给你更多的可能。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 分享一点关于租房经验 + +

+ +
+ + + + +
+ +
+ 在牛客网上看到一个创作者计划,分享关于租房、理财一类的帖子可以获得一定的奖励,这篇文章首发于牛客网,稍微做了一点点补充再发到本博客平台。 +估计大多数小伙伴都是在类似自如、贝壳、蘑菇租房等平台上找房,关于这些平台的经验网上已经能够查到很多了,我就不再去分享关于这些平台的避坑经验了。这里分享一点关于我的稍微偏门一点的租房方法吧。前面提到的自如那些平台至少是有一个作用的,可以通过它们大致了解某个地域租房的价格区间,基本可以定位哪里便宜、哪里热门,基本上自己去找的话都不会比这些平台还要贵。 +豆瓣租房讨论组上面可以找到一些转租、直租的信息,比如我要在深圳南山区租一间小房子,那么我可以去豆瓣搜索「深圳南山租房」,进入相关的讨论组可以看到豆油们的一些讨论信息,除了能找到一些房源信息外还可以通过大家的评论了解一丢丢避坑指南。 + +需要注意的是即使是豆瓣这样纯粹的平台,里面也免不了很多水军或是广告信息,还有一些中介也会在里面发信息,这个就需要自己去甄别过滤无效信息了,实在不行可以加个微信聊一聊就知道了嘛。另外不要轻易把自己的常用电话留到平台去,不然什么安装宽带的、其它包租公司、甚至什么洗衣服洗鞋一类电话有可能会打到你心烦。 +如果已经定了要去哪个城市参加工作,那招你进去的 HR 这个资源是可以使用的,稍微大一点的公司都会有内部社区平台,这种平台应该只有入职之后才能使用。公司的同事也会在上面发一些关于租房的信息,大可以让 HR 帮助自己看一眼截个图或是转发给你,因为都是同一个公司的同事所以遇到坑的可能性要小很多,毕竟抬头不见低头见的,说不定哪一天你们还会一起共事呢,内部人要实在一些。 +如果能联系到已经入职的朋友、师兄师姐、前辈更好,找他们问一问公司同事一般都在哪里租房,哪里的房子住着舒服、房租便宜、通勤方便,这些信息只有他们才掌握的最精准,所以先问一下他们绝对不是脱裤子放屁,运气好的话说不定还有师兄师姐正好要转租房子呢,当然也可以让他们帮你推荐一下房东信息,这样可以快速的帮助你缩小搜索范围。 +假设你在 A 地点工作,可以以 A 地为中心看一看周围 5 公里的地方,一些房好价低的房东不一定懂的如何在网上发布租房信息,如果有时间的话可以去溜达溜达(反正你也是要逛超市买生活用品的嘛),看到一个有意思的小区就去和大爷唠唠嗑,尤其那种看着年龄不小又缺人唠嗑的大爷,他们嘴巴里能吐出来很多有用的信息。 +实际看房的时候需要注意一下房间的朝向和楼层,楼层太低照不到阳光住着压抑,楼层太高夏天可能会非常的热,在深圳那种城市的高楼层空调是极有可能顶不住酷暑的。还得注意一下周围有没有工地什么的,不然太吵睡不好觉也烦心。一些基本生活设施也要对比一下,比如离超市有多远、出门约会是否方便等等。 +签约时问好房间物品损坏应该怎么赔偿,房租到期押金应该怎么退?每天晚上大门是否会锁?忘记带钥匙/房卡怎么处理?钥匙/房卡丢了补办是否要另交费?房间有问题的地方一定要拍个照片留个证据,避免到时候退房的时候有争议被扣押金。像水电这些应该每个人都会问的,我这里就不再赘述了。 +虽然互联网已经很发达了,但是还是有很多房东不知道如何在网上发布招租信息,或者是发布招租信息是要花钱的,这个门槛把一些房东给挡在互联网之外了。他们会在一些社区公示栏、或者在朋友门店显眼的地方贴一些联系方式,这些信息只有自己实际去走动才能拿到。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 用 flomo 管理自己的奇思妙想瀑布流 + +

+ +
+ + + + +
+ +
+ 使用 flomo 已经有一段时间了,太喜欢它的简洁与便捷了。它的微信输入方式可以随时随地记录突然冒出来的灵感;使用微信读书的时候看到一段写的很漂亮的文字,顺手贴到 flomo 的小卡片中便于下一次再回顾;使用 #todo 标签记录一些重要的待办事项...... +基本上的脑力劳动者都会有记笔记的习惯,我也习惯去捡日常零零星星掉下的小拼图块,找到一个适当的时机再用这些小拼图块进行排列组合完成一个小作品,作品可以是一篇文章、可以是某个问题的解决方案,亦或是简单的工具、词句集合。 +我用了「拼图块」这个词是因为我觉得人接收的信息就是碎片化的,尤其在充斥着各种奶头乐 APP 的时代,信息被磨揉的更加细碎无营养。不管是生活中还是工作上遇到的问题,很多都不是简简单单的接收一点碎片化知识就能找到解决方案的。学习新知识也是逐个去吸收小的知识点,再用这些小的知识点构建自己的知识体系,一些让主干更加粗壮,另一些让枝桠更加繁茂。 +flomo 背后的笔记理念就是去捡那些小小的拼图块,这和我现在的理念是保持一致的。借用 flomo 网站上的话说就是我们不可能都成为作家或者发表论文,但是我们都需要记录和思考。写 MEMO(卡片)而不是写文章的价值在于,能让我们更好的思考。 +大部朋友记笔记都仅仅是在辛勤的记录,最重要的思考环节却被忽略了,在之前写的你如果只是一直囤干货,那永远不可能进步中也提过没必要去假装学习。这里没有倡导不去记笔记的意思,而是找到一个适合自己的记录方式,笔记究竟要记什么中有一部分答案。 +我使用的第一个笔记软件是有道云笔记,用了有将近两年的时间,让我放弃它的原因是文件同步老出问题,另一个原因就是速度太慢了。印象笔记更是使用时间三天都没有超过,它那些看似强大实际却毫无用处的功能严重分散了我的注意力,这违背了我记笔记的初衷。 +还有像为知笔记、石墨文档一类的软件其实也还不错,但是和印象笔记、有道云笔记类似,它们的共同问题都是以文章的形式在组织笔记。这让记笔记变成了一件极为费时的事情,我看到一篇文章的字数少于 600 就难受,强迫症患者。 +所以有一段时间我选择了使用本地 VsCode + Markdown 插件方式记笔记,Markdown 的标题语法可以很轻松的将每个小片段分开,不同的文件(名)自然而然就变成标签了。一个新的技术点记到「技术.md」中,一段摘录记到「摘录.md」中。不过因为 VsCode 本身是一个方便程序员使用的文本编辑器,所以我这种方式记笔记总是有一点别扭,具体哪里别扭我自己也说不出来,总之就是用起来差那么点感觉。 +这里不得不提一下现在比较流行的数据库类型软件 Notion,这个工具做的让我有一种只有我想不到没有它做不到的错觉。团队在设计一个数据度量系统的时候,我还多次提出过借鉴 Notion 中 Block 的思想。Notion 是一款很优秀的软件,但仅对记笔记这件事来说它显得大材小用了,功能过于强大、使用过于灵活到变成了我不选择它作为笔记软件的原因。 +一小段时间使用知识星球做为笔记软件,但是它的搜索功能做的太弱了。一直使用到现在的笔记软件是微信,我建了只有我一个人的群,一些突然冒出来的想法、读书时的思考与摘录、todo things 都直接通过对话框发到群里。我看到有很多朋友也用了我类似的方法,选择发送到文件助手,这种方式的好处是可以借用微信强大的「查找聊天内容」功能,虽然有些鸡肋但用起来也还凑合。 + +其实我的记笔记方式是逐渐在向 flomo 靠拢的,虽然它出现的比较晚。flomo(浮墨笔记)看起来像是一个个人版的 twitter,或者就像少楠自己说的是一个加强版的文件传输助手,没有多余的功能去扰乱我的视线,就是一个简简单单的流式布局一元笔记软件。 +大多数习惯于像装抽屉一样去组织文件,windows 的文件系统也是这样设计的,不同的文件夹放不同的类别的文件,看起来好像很符合我们现实生活的打扫房间的场景。但不知道你有没有意识到每次去找一个具体的文件夹都要耗费大把的时间,这种初衷极好的分类整理方式竟然渐渐变成阻止我们去记录绊脚石。 +一款好用的文件系统更多的应该聚焦于搜索上,当用户搜索时能够快速的返回与之相关的文件才是关键,而不是把目光放在文件的分类上面。同样,一款好的笔记软件也应该是这样的,今天我看到一朵花,恍惚记得之前有记过与花相关的笔记,当我去搜索时它能够快速返回我想要的记录就 OK,flomo 在这两点上做的刚好甚得我心。 +当然 flomo 还开放了 API 功能,这让记笔记这件事变得方便且有趣,比如在 iOS 上选中文字发送到 flomo 就变成了一个 MEMO,同样也可以实现在 Mac 上选中文字发送到 flomo,不过我使用最多的还是它的微信输入和随机漫步功能。 + +记笔记是为了更好的让自己思考,不要像微信和 QQ 收藏那样,让 read it later 变成了 read it never。最后说一下可以通过邀请链接注册 flomo 获得 28 天 PRO 会员。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 2020 年个人总结 + +

+ +
+ + + + +
+ +
+ 还是照例给自己写一个年终总结。持续分享,黄金万两。 +一点感动 +7 月份回深圳办离职,提前通电话告诉了老叔到达深圳的时间。本来是 8 点就能到的,由于飞机晚点导致我半夜 3 点才到老叔那里。期间老叔每隔半小时就给我打电话问到哪里了,我好几次叫他先睡不用管我,我完全可以就近找个酒店舒舒服服的睡一觉,他都坚持说没事,等我。 +让我感动的是我坐的航班落地时间大概是两点,本来以为老叔已经睡了,结果一发消息老叔居然没睡还在等我。到家时他已经把早早就做好的啤酒鸭、可乐鸡翅、猪脚脚热好了,拿出了自己泡的茅台酒,陪他喝到了四点半。早上八点半的样子我被吵醒了,我脑袋还是晕乎乎的,老叔就已经做好了鲫鱼粉,那味道可真的香。 +第二天我请团队同事们吃饭,老叔和之前地产圈的朋友们去喝酒去了,大概十一点的样子老叔给我打电话来了:“小刘,你在哪里?”我一听就知道老头子喝的不少:“老头,你是不是喝醉了?你现在在哪里啊?用不用我去接你?”那一头来了一句:“我现在在树下。”搞得我哭笑不得。我叫他就近找个酒店睡,第二天早上再回来。老头子说:“不行,你在这里我爬都要爬回去,你不用管我,我回去等你。”等到我回去的时候发现他已经在打呼噜了,第二天早上起来还傻乎乎的问我车费是谁付的。 +11 月份和师兄们去参加惠州白盆珠的 100 km 骑行,拐了个弯先去了一趟深圳。老头看到我到了居然开心的连自己办公室密码给忘了,我俩在那里排列组合试了半小时才打开办公室门。当天晚上和叔唠了很久的嗑,第二天给我说昨天太开心了,5 点钟才睡着。 + +老叔是我刚到深圳时所租公寓的管理员,准备抽空专门给老叔写一篇文章 + +赚钱理财 +2020 年其实没有赚什么钱,基本是靠之前攒下来的一些钱在生活,不过这一年确实副业收入要比 19 年多了一些。银行理财依旧和去年一样保持在 4.5%,基金收益在 10% 的样子,因为缺钱早早的把投入基金的钱先取出来了,10% 实际上是 9 个月的成绩,我个人已经满意了。 +有一段时间帮别人负责抖音信息流广告投放,我就直接用我的信用卡充值广告费再报账,所以有那么几个月我的银行流水比较大,平安银行的积分全部用来换了电话费,一年的电话费没有花自己的钱。招商银行信用卡的积分换了两个被子和一个收纳箱,刚好填补成都冬日的寒冷。 +6 月份一个平安保险的销售人员给我推销保险,我把我对保险的理解以及当时对市面上少许现有产品的分析给他说了一遍,他认为我已经达到了保险销售人员的水平,告诉了我一个可以分销保险的平台,我当时也没有把这个当回事。 +隔了两个月,有一个在成都认识的朋友问我保险相关的问题,我就给她做了一些解答,完全当给自己选产品一样给她选,刚好记起保险分销平台这个事就注册提交资料,很快平台审核通过了,最终到手的有 3000 多块钱的佣金。忘记是在什么情况下和这位朋友聊起了化妆品这个话题,于是我顺手花了 900 多给对方买了一瓶精华。 +通过付费社群知道了知乎好物这个功能,把一篇之前发在知乎上的一篇《大学生书单推荐》插入了购物卡片。之后又整理了豆瓣一个话题下面的好物清单,同样发到知乎上面插入了相应的购物卡片,前前后后总共出了大概 10 单的样子,佣金够自己吃几顿麻辣烫。 +自己知道一个可以低价充值腾讯、爱奇艺、网易云等平台会员的渠道,加了一点点价格在闲鱼上面转卖,前前后后赚的钱加起来应该也能吃几顿麻辣烫,不过由于利润低且有一点费时间所以就没做了,不过这也算是自己走通的第一个利用信息差赚钱的案例。 +选择离职 +离职的想法老早就在心头了,3 月份提的离职申请 4 月底离开,但是在流程上我是 7 月份才离职的,因为领导做出了很大让步,允许我半个月在成都半个月在深圳,既然已经说到这份上我也就答应了。但是在年终奖、季度奖和津贴上面却又受到了极不公平的待遇。所以离职的原因很简单,就是干的不爽加上不算年轻的冲动。 +冲动是什么呢?趁还不太老的时候去经历,自己一直在没钱但也没有感觉到缺钱的状态下生活,估计大多数白领阶层也是这个状态,没有家庭的压力,没有赡养父母的压力,更没有病痛的折麽。所以我想提前去感受一下缺钱的感觉,这个想法确实是脑子有点病,哈哈哈。 +干的不爽是为啥呢?不喜欢公司的加班文化,部门每个月都会有一个加班时长排名表,我们团队每个月都是部门垫底的,据说加班时长少于 40 小时会被领导谈话。我个人不反对加班,但是当大家都仅仅简单的在比加班时长的时候,那个氛围就待的很没有意思了。 +在大企业工作的程序员都会遇到的一个问题。我自己在 5G 测试部门做内部工具开发,回家时大家一听都还觉得挺洋气的,能接触到业界比较前沿的技术与知识,但实际上我连产品最终长什么样子都没有见过,我希望自己能看到一个产品从 0 到 1,并且送到用户手中用户愿意付费的过程。 +当然离职最大的原因是意识到个人的增长是线性的。我在部门新人里面还是算干的比较好的,算一下自己如果一直干的比较好,部门那个 5G 专家可能就是自己将来的样子,自己还不一定能达到那个水平。如果干的不好的话也有榜样在那里,那一群每天上午还按时在工位做体操的老油条就是自己将来的样子,不喜欢一眼就能望到未来样子的感觉。 +离职之后的感受是大公司提供的保护壳真特么硬啊,出来之后才明白挣钱确确实实不容易,所以在这里奉劝各位要离职可得想好啊,说实话我现在有一点怀念那种划一天水都依旧有工资的日子呢。 +一点收获 +自己从 0 到 1 设计开发的系统得到了同事们的认可,完全靠着「用户怎么用着舒服」这个理念在开发,逐渐得到了其它部门领导的青睐。在师傅的督促与推动下,我在离职前将其做了平台化开发,临走前已经将西安一部门接入系统,我还悄悄在系统里面留下了我的信息,不知道将来可有人能发现或是已经发现。 +学习了一点互联网广告投放领域的知识,对互联网流量有了比之前更加深刻的理解。中国 8 亿网民里面绝大部分都是穷人,穷人里面又有一大部分是天天只知道刷抖音的懒人,梦想着天上会掉下来馅饼喂到嘴里,你脑子里的常识可能有一亿人都不知道。大部分小公司赚的是穷人的钱,大家向往的互联网巨头又赚的基本是小公司的钱。 +对京东、淘宝、拼多多这一类电商平台有了新的认识,慨叹电商领域的水简直深不可测,很多小公司依附于电商平台生存,太多套路也不方便在这里讲。自己脑子里将实体经济与互联网经济的逻辑打通了一部分,互联网平台要么自己能提供某种服务,要么就是带的动货才能正向循环。这里做个预测,2021 年会有更多的平台接入电商平台,功能类似于知乎好物。 +体验了一把纸醉金迷的感觉,对自己的定位又清晰了一点。人这一辈子总要多多少少面对一些诱惑,就好像电影《八佰》里面那个军官一样,自己没有摸过咪咪所以老是好奇摸咪咪的感受,没有经历过总是容易经不住诱惑。我庆幸自己能比较早的去经历,虽然不至于让我明白我想要什么,但至少让我明白了自己不想要什么。 +人生的第一桶金不是只有钱,还有经历和认知。这一年更多的是见识,是心灵上的收获。 +新的伙伴 +到成都后和一群贵州、四川小伙伴待了段时间,他们基本没有上过高中,有的甚至小学都没有毕业。IT 行业工作的人多少是有五险一金、年终奖、季度奖这些玩意的吧,但是这部分人所在的公司可能连社保都不会交。我清楚的记得我刚回成都时,一个小孩子给我炫耀说:“刘哥,我们公司是有社保的”。 +他们喜欢像抖音一样各种炫耀,但是自己挣得钱又不够自己花的,要是有一个月没有发工资的话那么就还不起花呗了。如果某一个月多挣了一点点钱,就是去酒吧、去嫖娼。我发现这才是目前社会上最大群体的一个缩影。 +直到最近我才真的明白,你不要和他们讲什么要去做有积累的事,不要和他们讲什么所谓的长期主义,一个每天还在为生活发愁的人,就不要和他去谈什么理想。所以我写了一篇如何通过撸茅台赚钱的文章,在这里我想告诉你这种短期快速套利不会让你有什么积累,会打乱你的时间,就是个奶头乐。 +得益于校友会这样一个平台,在四川校友会认识了一个师兄,我目前正在师兄手下学习做一点事情。主要的精力都在少儿编程教育上面,惊叹我大一才会的东西现在一个不到十岁的小孩子就会了,已经变成了一个拼娃的时代,家长们生怕邻居孩子会的才艺自己孩子不会,可能这就是「内卷」吧。 +写在最后 +2020 年做了一个对我个人来说比较重要的尝试,那就是自己输出一套爬虫课程,这算是第一次自己打造一个产品,这是一件很有意思的事情,因为他人是拿腰包里的钞票为你投票,能拿到一票就开心的不得了。2021 年继续在这个方向尝试,尽可能的去做好做精。 +在运动健身这一块做的很差,除了去白盆珠参加了一次百公里公益骑行,其它就没有什么值得说出嘴的运动了。顶多就是跑跑步,亦或是骑 10 来公里的单车,一年时间没有打过羽毛球了。2021 争取用自行车上下班,尽量保持平均每周能有 60 公里骑行的运动量。 +2021 年继续尝试将自己产品化,一直靠兴趣驱动的我,执行力总算得到一点加强。给新的一年设下几个关键词吧:执行、坚持、分享、自律。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 说一下戴牙套的感受 + +

+ +
+ + + + +
+ +
+ 快要离开深圳时候和老叔一起吃饭喝酒,老叔看着我突然来了一句:“小刘啊,那个你年终奖也发了,你回去把你的牙齿弄一弄,把那个缝关上,不然你的命就一直不好,漏财!” +说实话,我以前从来没有想过自己的牙齿,还一直觉得自己的那个缝好玩,小时候还常借助门牙那个缝隙,加上自己的舌头抵压,压力会让口水从牙缝挤出去,达到喷射的很远的效果,为数不多的几次口水战我都比较占优势。 +可以但是经叔叔这么一说,我再照镜子时候就不再觉得它好玩了,越看越不美观了。所以有事没事的时候会忍不住去查查正畸方面的信息。回到成都后老叔还是经常给我打电话,每次视频的时候老叔看到我没有戴上牙套就会骂我催我,本来想回到成都就把老头给糊弄过去算了,但自己却是越看越不美观。 +从有矫正的想法到真正戴上牙套中间经历了半年的时间,这半年时间就是查查网上的资料,问问身边有做过正畸的朋友,当然还有一个重要的事情是了解各种正畸方案的价格,像什么陶瓷自锁、金属自锁、隐适美等等,其实这些乱七八糟的事情都是一个心理建设的过程,要突破这个心理障碍还真不容易。 +到现在我已经戴了快一个月的牙套了,刚开始戴上那几天会很疼,连话都不想说。我戴的是进口金属自锁那种大钢牙,刚开始上面那个金属丝还老是滑动容易戳我口腔,如果滑动的不太多我就自己把它拨回去了,如果滑动的太多了就到诊所那里去让牙医搞定,我到诊所就 3 分钟的路程,这一点还是非常便利的。 +做矫正这个事儿可能很多人都觉得麻烦,不能随便吃东西,每吃一顿饭就要刷牙,而且一个大钢牙在嘴里还很丑,牙齿也疼.........就干脆放弃了,但是以我这一个多月的感受告诉你,戴牙套的好处要大于太多坏处。 +人人都想减肥却又管不住自己的嘴,很多人在睡前总是想吃点东西,导致体重增加的非常快,尤其很多所谓无法推脱的社交饭局经常吃到半夜一两点,戴上牙套之后保证你能管好自己嘴,我自己戴上牙套不到一个月已经瘦了 4 斤多了,这是戴牙套之前没有想到的意外收获。 +当然,更加具体的实施方案还得取决于自己的牙齿具体情况,有的人做矫正可能还需要拔几颗智齿,可以顺道消除智齿发炎的地雷。另外一个拔智齿的好处可能会让仙女们比较开心,那就是智齿拔了脸会变小啊,哈哈哈哈哈!! +对我个人来说还有另外一个好处,那就是每天只能吃很少的东西怎么才能让自己更健康的成长,所以抓紧时间去看了《你是你吃出来的》这本书,才知道自己以前对于营养的认知是多么浅显,我忍不住想要对正在看文章的你做个简单的科普! +我们吃东西不仅要关注能量,更要注重七大营养素(碳水化合物、脂类、蛋白质、维生素、矿物质、水、膳食纤维)。像鸡蛋、牛奶、蔬菜、水果、坚果、肉类、(深海)鱼、动物肝脏等,都需要均衡摄入。《你是你吃出来的》一书中做了大量苦口婆心的讲解,也纠正了大家平时的一些错误观点,比如很多人生病了不舒服就咸菜加上喝白粥打发,觉得浓浓的白粥里面很有营养,实际上白粥是没有什么营养的;很多地域晚上都习惯吃面食,因为面食比较容易消化,但面食里面的主要要成分是碳水化合物,非常容易让你血糖快速的升高,碳水化合物摄入过多的结果只会让你越来越胖。 +牙套在嘴里也不太好咀嚼,之前又看到朋友圈的人推荐「若饭」,正好趁这次机会尝试了一下若饭,口味什么的并不是多好,但液体版的若饭对我来说很方便,像考研党、工作狂或者其它比较忙的人,可以买一点若饭在那里备着,一分钟的时间就能解决一顿饭,比上个厕所都要快的多。 +若饭是一种高密度的营养餐,我们大多数人可能觉得自己平时吃的很健康,但估计现实情况和自己认为的恰好相反,比如中国人基本对钠盐的摄入都严重超标,很多人都摄入了太多的碳水化合物,可以看若饭的营养成分表,各方面还是基本兼顾到了的。若饭不仅有液体版,同时也有粉末版供你选择。 + +当然如果能吃到天然的食物最好吃天然新鲜的食物,像若饭这样直接对标美国 Soylent 的产品也不可能全方面兼顾到,比如长期食用可能会影响肠胃功能(我猜的),所以经常吃吃水果、坚果这些小零食,没事儿的时候约几个朋友散散步,偶尔来一顿美食大餐是绝对有必要的。 +最后再推荐你看一个一席的演讲视频:减盐这件大事 + +
+ + Read More ~ +
+
+
+ +
+

+ + 转载——我的喜欢从误会开始|最后的记忆 + +

+ +
+ + + + +
+ +
+ 我的喜欢从误会开始 + +作者:伍什弦 + +我说过 +我不擅长和男生做朋友 +所以 +一点点的小事 +都能让我误会 +误以为你喜欢我 +课堂上 +向我借笔 +下课后 +加我 QQ +那时候 QQ 封面出现点赞 +整个假期 +我们不停的互赞 +空间里互相留言 +这些小事偷偷在我脑中生根 +而我被你深深的吸引 +想多见你 +想靠近你 +在运动会上看到你胜利冲过终点 +这一晚我梦里是你 +连着四晚我梦里都是你 +我知道 +我喜欢你是从这个时候开始 +可是喜欢的种子早在课堂上埋下 +我误会了 +误以为 +你也是喜欢我 +最后的记忆 + +作者:伍什弦 + +我想啊 +人这一生 +大抵都有疯狂的喜欢过一人 +除此一人之外 +剩下的喜欢相比不过十之一二 +读大学前 +以为从小学喜欢到高中的那个人 +当算作我的初恋 +可是他闯入我的心怀 +从此苍山洱海不过一人 +那时节 +他总能出现在我的梦里 +我呢 +总想把最好的都给他 +为他做这样那样的事情 +可是他对这样的我说 +我们永远都是好哥们 +就这一句 +之后近一个月 +我几乎不说一句话 +从不逃课的我逃了不止一节 +整个学期 +我几乎是在图书馆生活的 +读完了 +一部十三本的德川家康 +看了 +不知多少部电影 +那种伤心仍是挥之不去 +不过是随着时间淡淡忘却罢了 +后来 +身边也有喜欢我的人 +开始我也觉得这人还好 +总是一段时间过后 +就没缘由的厌恶 +在还没来得及向我任何表示前 +就已经疏远了他们 +毕业的时候 +我对他说:祝你幸福 +之后班级一起去 K 歌 +我走前 +最后唱了一首 +每每听到都忍不住要流泪的歌 +Someone Like You +回去的路上 +好友告诉我 +我在唱的时候 +他是闭着眼睛听的 +是他第一个鼓掌的 +听到这句话的我 +眼泪不受控制般流下 +我 +站在天桥上大声呼喊 +我再也不喜欢你了 +可真的是这样吗 +无论如何 +青春 +已离我远去 +那感觉如此清晰 +以后再不会如此喜欢一人 +纵有 +江山万里织锦绣 +我心 +除却巫山不是云 + +
+ + Read More ~ +
+
+
+ + + +
+ + + 下一页 + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/Ykw3uqJED/page/2/index.html b/Ykw3uqJED/page/2/index.html new file mode 100644 index 00000000..a810be1e --- /dev/null +++ b/Ykw3uqJED/page/2/index.html @@ -0,0 +1,794 @@ + + + + + + + + 随笔思考 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 随笔思考 +
+ + +
+

+ + 买课程囤在那里不看,你怎么可能进步 + +

+ +
+ + + + +
+ +
+ +文章于 2018-04-15 在微信公众号「刘小绪同学」发布,原文链接:你如果只是一直囤干货,那永远不可能进步,今日重读做了部分改动 + +这两天,没事整理了自己的微信收藏和 QQ 收藏文件夹下面的文章。发现了一个在大多数人身上都存在的问题,那就是囤干货。 +我使用微信的时间比较晚,从14年才开始,但是仅仅不到4年的时间,在微信收藏里面就有几百篇文章,有一些传授技能的文章,比如 word、ps 等简单教程;其他一些属于心灵鸡汤类文章(现在反倒不觉得这类文章值得看),还有一些搞笑的文章,当然里面也夹杂着些许的优秀视频。文章数量数 14 年和 15 年最多。 +现在回头看,我当时就陷入了囤文章的陷阱了,或者说自己是在“假装阅读”。仔细一想,其实生活中大多数人都有类似的举动。早成起床、饭后的一段时间、晚上睡觉前,这些时间大多数人都习惯性的去翻翻公众号、朋友圈,这里面不乏有好文章、干货文章。然而没多少人会静下心来把文章读完,而是在大概读到一半的时候,选择收藏这篇文章,然后在心里告诉自己,明天要好好读一下这篇文章。到了明天,其实又是重复了今天的这样一个过程。 +记得大概一年前也无意中看到大学认识的一位师弟发的说说,内容如下: + +上面的图是我特地翻出来的,一年后这个师弟又发了一条说说,存满了两个云盘,应该是有 5000 G 左右的资源。 +我认为这都是在假装学习,而且假装学习的人不计其数。为什么上学的时候,那些每天都静静的待在教室学习的同学,成绩反倒不是很好呢?而且大多数这样的学生在班级都是排在中等,而成绩好的学生却不是学习投入最多的人。背后的原因显而易见。 +我囤课最严重的时间段是也是 14 年和 15 年,网上有不少干货资源,什么 Linux、各种项目实战、计算机网络等等培训视频不计其数。那时候干的第一件事就是,上百度云把这些资源下下来,而且一个资源往往要下一周甚至更久;然后告诉自己,下周开始每天看一段视频,但是最终的结果是过去了 N 个下周,依然没有去处理这些资源。 +现在博客、公众号也有一些不知道是为了获取更多人的关注,还是仅仅是做公益,文章末尾会标注:关注公众号,回复“XXXXX”,即可获得多少多少G的资源,这个「多少」一般是在 500 以上。我个人现在是对这类文章没有什么兴趣的,因为几百 G 甚至上千 G 的东西,我是不可能看完的,我清楚自己的能力,我也不否认可能有人有毅力能看完,那肯定是凤毛菱角了,我一个普通人不与凤毛菱角对比。 +我于 19 年开始使用豆瓣,用上豆瓣之后就冒出来另外一个毛病,总想快速的把一本书看完而不去管有没有真正理解书里面的知识,总花很多时间看更多的电影,仅仅是为了在豆瓣上标记「看过」、「读过」那一刻的快感。 +走出象牙塔之后事情变得繁杂琐碎,什么事都想做到最好还什么事都想去做,问题是那些边边角角的事情还经常扰乱自己的视线,结果没有一件做的像样的事情。 +当然,我现在的认识相比几年前,得到了一些提升,至少我现在不糊干囤课之类的事了,仅仅是把干货囤在那里,其时你还是昨天的自己,并没有进步,重要的不是自己买了多少优质的课程,而是去动手动脑学习,最好的学习就是实践。现在我都会把自己收藏的好文章在一周之内处理掉。 +所以要想真正取得进步,首先就需要告别囤课的习惯,不能假装学习,如果学习是为了给比别人看,那还不如不学。文章仅是我个人的一点感悟,没有文采逻辑和言,希望与你共勉。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 2020 年国庆观《我和我的家乡》有感所写 + +

+ +
+ + + + +
+ +
+ 上一次在我的微信公众号发文是 5 月 17 日,差不多近 5 个月没有在上面发一篇文章,看了下后台的关注人数居然没有多大的变化,安慰自己是我的读者大人都喜欢我,所以对我不离不弃没有取关,所以写了这篇文章给公众号的读者大人赔礼道歉露个面,表示我还在,想起啥就说点啥,毕竟下次这种双节重逢的日子得等到 2031 年了,过节就放空脑袋不去思考那些复杂的事情 +转眼自己已经毕业两年了,同级读研的同学也都陆陆续续毕业了,一直想着等冬天时候借着旅游的名义回哈尔滨看看老师。让人意外的是老师居然自己飞成都来了,陪着老师在电子科大校园外面散了两个多小时步,仿佛一下子回到了大学时代。 +老师是个极为幽默风趣的人,到电子科大是参加一个什么区块链会议,我询问他这个会议具体的内容是啥。老师有一点不耐烦的说:“就是一个卖铁锹的会议,挖币挖不动了开始教人挖币了”。老师问我现在在干嘛,我思考了几秒说了句:“我现在在卖铁锹”。哈哈哈哈哈哈 ...................... + +老师常说的一个小故事,一个地方金子多,大家都去挖金子,挖金子的人没有赚钱,结果卖铁锹的人赚翻了 + +10 月 1 日去电影院看了《我和我的家乡》,电影里面提到贵州山区因为道路不通畅,直线距离一公里的恋爱居然因为异地恋分手了,这样的地理环境我倒是没见过,不过让我想起了经典电视剧《血色浪漫》里面的片段,我把这段给拿过来了方便读者大人观看。 + +《血色浪漫》是一代人的记忆,主角钟跃民在当知青的时候迷上了「信天游」,陕北一些地区也是隔一条沟可以聊天,但是要见个面得好几十里,剧中这一段对歌真的太经典了,我禁不住又翻看了好几遍,又回想起了大学上《民族音乐》课程的时光了 .................. +随时保持一颗学习的心,脑子里多装一点知识总没坏处的,钟跃民学信天游的时候也没想到可以用来撩妹啊​!说不定你从这篇文章里面看到的几个小故事也可以撩妹呐,要是撩到妹了记得回来感谢我啊​ +电影里面还有两个小细节让我印象深刻,还是讲述贵州山区的那个小节,里面阿花出场时穿的裙子让我想起了花儿。花儿是我 6 年前在宽窄巷子的一家青旅认识的,那时她还是那家青旅的老板,我正在读大冰的《乖,摸摸头》和《阿弥陀佛么么哒》,花老板的生活让我找到了大冰的文字在现实中的影射,可以翻翻她公众号「微笑而确实的日常」里面的历史文章,都挺有意思的。 +花儿的手很巧,什么垃圾到她手里都会活起来,常常做一些小手工卖给旅客,也时常邀请旅客免费与他们一起共进晚餐,餐后背着吉他外面唱自己写的歌,有时候路人会给几十块钱。后面通过朋友圈了解到花老板去西藏生活了一段时间,而后又回到了大理。花老板回到大理后我又去过一次那间青旅,但是已经完全变了样子,旅客之间没了谈天说地,屋子也变得像酒店一样冰冷,和花老板联系确认她不会再回来,就再没联系过了。 +没有找到花老板比较好的照片,这张背影照可以勉强看到裙子的布料,听说云南很多人穿这样布料的裙子。 + +在这个快节奏人人都算计的社会,太多人少了像花老板一样的洒脱,太多人都忘记了这个世界上还有爱,也太多人连为爱放弃的勇气都没有。4 月离开 IT 行业之后我才真正感受到程序员群体的单纯,几个月的经历也更让我明白了家人与朋友的重要,翻一翻你的通讯录去联系一下那些许久没有联系的朋友吧。 +《我和我的家乡》还有一段教室漏雨的剧情,想起来自己读四五年级的时候不也是这样子嘛!学校拿几个木桩和几块木板子用钉子钉住,再加上石棉瓦和塑料薄膜就搭建了一间简易教室。四川的夜晚总是爱悄悄下大雨,我们早上到教室的第一件事不是早读,而是把教室的水先舀出去,这可是同学们比较喜欢的环节啊,不用被老师监督着早读可太轻松了。 +到夏天时候屋顶上爬满了飞蛾,全教室都是飞蛾身上掉下的粉末,这个场景对同学们来说也是不可多得的福利,不用上晚自习可以直接回宿舍睡觉。第二天早晨去教室把那两垃圾桶的飞蛾扫一扫就好了,不过比较难搞的是晚上满屋顶飞蛾遇到半夜暴雨,第二天可就真不好清理教室了! +本来想把自己以前写的文章都搬到知乎去,结果一不小心就触犯了知乎的社区规范了,不仅把我的几篇文章都给删掉了,还把我给禁言了一天。一气之下自己买了个服务器搭建了个博客,不管在什么平台写东西都得遵守别人的规矩,现在我的地盘我做主我自己定规矩! + +最后还是分享最近的一点点思考,执行力和坚持这两样东西很重要,我自己身上严重缺乏这两样东西,说起来很容易做起来却不是一般的难,不知道有多少人是真正明白了它们的重要性。 +任何人的批评都不会让自己开心,但如果一个人还愿意骂你还在鞭策你,那你真的得感谢人家,当对方什么都不愿意给你说的时候就是已经放弃你了,你就又失去了一个帮助自己成长的人! + +
+ + Read More ~ +
+
+
+ +
+

+ + 互相看不起|断舍离|《良医》 + +

+ +
+ + + + +
+ +
+ 以前在朋友圈提到过这样一个现象,重庆人和四川人说的都是四川话,但是大部分重庆人会说他们说的是重庆话,说「川渝是一家」的通常也都是四川人。 +在深圳也有一个很怪的现象,两个客家人谈话会用客家话,两个潮汕人谈话会用潮汕话,两个广东人谈话会用粤语,反正就是尽可能用更小众的语言。 +想了一下,故意用第三方听不懂的语言,实际上是很欠考虑的,如果是刚见面用方言寒暄几句我觉得还行,但是后面谈话就应该使用大家都能听懂的语言了。 +疫情期间大家都没法出去玩,我和老叔倒是出去爬了爬山,村里的荔枝山别人进不去,整座山就我和叔两个人,单从疫情这个角度讲,荔枝山是比大部分地方都要安全的。 +我自己可以在疫情期间爬爬山,结合我自己的感受,加上前段时间的「大奔进故宫」事件。我发现人们并不是痛恨特权,而是痛恨自己没有特权。大部人痛恨的不是腐败,痛恨的是自己没有腐败的机会。 +上面四川和深圳两个例子也差不多是出于这样的优越感,鉴于四川除了成都外,其它地方投资的回报率太低,穷地方的人总会羡慕富有的地方,说川渝一家的人大概率不是成都人。 + +春节期间看了一本《断舍离》,它讲究的是内部的自觉自省,虽然整本书挺啰嗦的,完完全全可以用一篇几千字的文章代替,但是它传达的人生整理理念很值得参考,感兴趣的读者大人可以在微信读书中阅读此书。下面是一段摘自书中的内容。 + +我们习惯于思考「有效性」,却往往忽略了作为「有效性」前提的「必要性」,对物品也常常陷入这样的定式思维,导致家里各种杂物堆积,这些杂物给人的压迫感,以及狭窄空间给人的阻塞感,会让人的思维变得迟钝、行动变得迟缓! + +借助「断舍离」的理念,我删了 400 多个微信好友,处理了一些不会再使用的家具和书籍,才发现之前一直舍不得扔的那些东西扔了对我的积极作用更大,以前写过的一篇你如果只是一直囤干货,那永远也不可能进步,核心思想和断舍离基本一致,遗憾的是自己当时写下这篇文章后,竟然不懂得延伸到其它领域。 +可能一部分人有读书摘抄语录的习惯,我个人在阅读技术书籍或是扫除我知识盲点的时候,我也会通过记笔记来加深自己的理解。想想自己强迫症式的记笔记面面俱到其实也是在浪费时间,大部分笔记自己都不会再去看第二遍的,舍弃一些不必要的形式会让自己的阅读更有收获。 +还发现自己另外一个错误观点,我不管是写字还是看书都比大部分人慢,一直都认为是自己看书的方法不对,现在才发现问题的根本原因。是因为我对具体的领域不熟悉,所以看这个领域的书籍才会速度很慢,如果对这个领域熟悉了,那一目十行甚至几十行的速度应该都可以达到。结论就是书读的少了导致读书的速度慢。 + +推荐一部美剧——《良医》,全剧的场景基本是在医院,但有深度的内容基本都和医院无关,除了最基本的医疗科普外,更多的是对家庭、爱、职场、心理等的探讨,下面是我摘的两句台词。 + +Whenever people want you to do someting they think is wrong, they say it’s “reality”. +当人们想让你做他们认为错误的事时,他们总会说这就是现实。 + + +Very few things that are worthwhile in life come without a cost. +人生中那些有意义的事大多是有代价的。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 2019 年个人总结 + +

+ +
+ + + + +
+ +
+ 写总结的习惯是从 2015 年开始的,我的大学学费是县政协资助的,叔叔阿姨们唯一的要求就是每年给他们写个总结汇报一下学习情况,毕业后敦促我写总结的人则从外力转为内心。 +一点感动 +上半年我还很年轻,那时候还会经常使用 QQ、Soul、同桌、一罐 等等社交产品,无意结识了一个还在读高中的同性恋女孩子,我没学过心理学不知道用什么专业名词描述她的情况,反正就是心理上有严重的问题,玻璃心、想自杀等等。几次拼了我老命陪她聊到半夜两三点,现在完完全全能正视自己的情况了。 +让我感动的是有次她问我在干啥,我随便拍了一张自己的被汗水打湿衣服发给她,告诉她自己正在打羽毛球。小姑娘给我说我穿的衣服不好看,说我才多大穿的衣服太老了,我也就随口一说叫她给我这个老年人推荐推荐衣服,因为她要上课后面就一直没有回我消息。 +第二天早上睡醒了看到了小姑娘的几十条消息,是半夜两点多发的,给我挑了好几件衣服裤子,还给我画了几张示意图(如下),瞬间收获一份小感动。我也遵从小姑娘的意见买了两件上班穿穿,结果一到部门就是众目睽睽之下给我说穿的好酷,穿几次了都还是会引来大家不一样的目光,个性低调的我还是选择走大众程序员路线,老就老吧。 + + +前几天小姑娘给我发了她暗恋的小姐姐的照片,虽然极少时候还是会上课偷偷玩手机,但也在努力的备战高考。我做的不好的就是她多次给我讲自己在龙岗,我每次都把她当成龙华的,希望写了这篇总结之后不再记错吧。 +赚钱理财 +这个小标题起的有点大,仅说说我自己的实际情况吧。凭着运气,2019 年的银行理财收益在 4.5% 左右,基金收益在 7% 左右。我没有去玩股票,网上各种理财课程可能都会给你讲股票的收益多么多么高,但是他们从来不会给你说玩股票的风险有多高,更不可能给你讲玩股票会严重影响自己的心情,可能连自己的本职工作都会搞砸,所以我不建议职场新人进入股市。 +房东忙的时候我会帮他带房客看房,他也给了我小几千块钱的介绍费,加上每个月没交网费直接用他的,还时不时蹭蹭房东的饭局,也给自己省下来周末出去散步的费用了。上半年也给别人分享过两三个课程,在群里分享过一点小技能,大家给发了点红包,交个朋友、图个开心。 +总的来讲,理财这方面做得很差,没有花什么时间去学习,我们的大学也没有教给学生一点金融知识,这一年只读了几本写给小白的理财书,今年在这个领域要多花一点功夫,希望能入得了门吧。 +写书失败 +快要毕业的时候和电子工业出版社签了一份合同,合同内容就是我要写一本叫做《知识图谱:自顶向下方法》,这本书的计划内容是我的毕业设计,已经写了一百多页的内容了,但现在确定以失败告终。 +一者我手里现有的数据属于机密数据,没办法拿来直接使用;二来书中有很大一部分内容涉及到网络爬虫,上半年网上曝了很多因为抓数据而入狱的案例,出版社和我都害怕;三者知识图谱所需要的数据量很大,而且我写的那个领域又是中国特有的经济责任审计领域,大量数据都得从政府网站来,更害怕了;最重要的原因是自己懒,写书的那几个月确实非常的累,想想自己都还是个菜鸟呐,有啥资本教给别人知识,心里给了自己后退的理由。 +小时候曾夸下海口说要给父亲写个传记,也不知道有没有那么一丢丢可能性实现,写家里的狗时,发现写这样的内容会增加我的多巴胺分泌,以后不开心了就写这样的小故事。 +运动健身 +在深圳校友会骑行社师兄师姐们的带领下,同时也得益于一起入职的小伙伴送了我一辆 MERIDA,我喜欢上了骑行这项运动,基本上每周五都会出去骑几十公里,中间还参加了环漓江骑行和 TREK100 骑行,锻炼的同时也见到了美丽的风景。深圳对自行车是不太友好的,基本没有什么自行车道,所以我们大部分时间都是等到晚上车少,交警下班了之后才开始骑行。 +除了骑行每周一也会打两小时羽毛球,谈不上专业,但至少打的不再是广场球了。偶尔也会出去爬爬山啥的,身体确实比上学时候要好很多,而且多锻炼能让自己的精神面貌更好,精气神好也能稍稍掩盖长得丑的缺点。以前每年再怎么也会因为感冒一类的问题进几次医院,19 年仅一次因为智齿发炎去过医院。 +削减迷茫 +大概在四五月份的时候吧,几乎天天失眠,经常夜里只睡了三四个小时,有时甚至通宵未眠,心里很清楚是因为迷茫了,大概就是「晚上想了千条路,早上醒来走原路」的状态。好在自己的调节能力还不算差,同时也有楼下的叔叔、自己的好朋友能唠唠嗑,差不多两个月就回归正常状态了。 +从几个比我晚入职半年的小伙伴那里了解到,他们现在的情况和我四五月份的情况差不多,我想绝大部分普通人都会经历这个迷茫期吧,大部分人也都能通过时间调节过来,调节不过来的那部分人就成为了媒体比较喜欢的人。 +现在迷茫的雾气已经没有那么浓了,初入社会肯定有很多的不成熟,但谁不是这样过来的呢?更何况我并不像多数程序员那样交友严重同质化,周末也不会死宅在家里不出去,猜测我应该比大多数人更潇洒自在的,嘿嘿。 +新的思想 +大家基本都是看着金庸武侠小说(相关影视作品)长大的,没有人写武侠小说能超过金庸。偶然一天在推特上刷到一条评论,大意是:没有人写武侠小说能超过金庸不正代表着社会的进步吗?金庸的成就如此巨大,一个很重要的历史背景是那时候大家没有那么多小说可看呀,哪里像今天遍地的网络小说。咱们没必要去争论这个观点的对错,重要的是它告诉了我们一个不一样的角度去看待问题。 +上面只是一个特例,思维方式是一点一点改变的,认知水平是一点一点提升的,一年时间修正了不少我此前狭隘的观点,这样的修正还在继续,我也会让这样的修正持续下去。 +写在最后 +巴黎圣母院被烧、凉山火灾、女排十连冠、NBA 事件、无锡高架桥倒塌......等等发生在 2019 年的大事,不知道还有多少朋友会记起来。时间从来不会等谁,网友也都是不长记性的,成熟的一部分无非是经历的多了,失望的多了,然后变得更耐操一点,总之生活依旧得继续,人总会亦悲亦喜,那为啥不把悲缩小喜放大呢? +成功没有银弹、没有捷径,少讲大道理,多解决小问题。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 家里的狗 + +

+ +
+ + + + +
+ +
+ 为了防止晚上有人来家里偷东西,几乎家家户户都至少会养一只狗。在我的记忆中,我家一开始是没有狗的。 +忘记是哪一年夏天的一个清晨,天还没有大亮,我隐约看见在牛棚后面的空地有个黑影,走近一点仔细一看,原来是一只不知道从哪里来的一只黑狗。 +它惊恐的看着我,眼神中夹杂着恐惧与无助,佝偻的身子比弓还要弯,倒是很像一个活着的牛轭。他的身子还没有草高,露水把全身的毛都打湿了,还沾着一些不知名的植物种子。我和它对视着,恐惧慢慢填满了它的眼球,我害怕吓到它,赶紧走开去告诉妈。 +妈远远看了一眼,让我别管它。随后妈把装着昨晚剩饭的猪食瓢放到牛棚后面的一块石头上,黑狗看见妈带着武器走近早就跑了,我吃早饭时还不时去望望它在不在,有没有吃妈给放在那里的饭。 +妈已经把猪喂完准备下地干活了,仍旧没有再次发现黑狗的踪影,也没见猪食瓢有什么变化,我心里有一点点的失落,黑狗应该是已经逃走了吧。 +晚上吃完饭妈去拿猪食瓢,告诉我里面的饭已经被吃的一粒不剩,我心里开始期待和它的再次见面。第二天早晨果然见到它了,身上已经没有昨天那么湿了,显然没有前一天来这里时钻的草丛多,妈依旧用猪食瓢装着米饭和米汤放在牛棚后的那个石头上。 +就这样过了几日,黑狗走进了我家的屋檐,它的样子实在太丑了。每一根肋骨都清晰的扎眼,看起来爸的手指都比它的小腿粗,感觉下一秒它就会死去。 +我并不喜欢它,甚至还有些讨厌它,我实在找不到更丑的词来形容它,不过是出于心里的怜悯与对生命的敬畏,会在吃饭的时候给它丢几个我不吃的肥肉,被烟熏黑的那一层肉边我也丢给它...... +有一次同村的一个人路过家门口时,看见那只黑狗吓的赶紧往妈身后躲。“有我在,它不敢咬。”,妈说。邻居夸夸妈说:“这个狗儿喂得好肥”。妈自豪的告诉那个人这只狗每天还送林儿(我)上学。 +是的,我也不知道什么时候我已经和大黑狗变得如此亲密了,它每天早上会把我送到山顶的学校,我每天下午回家做完作业会和它一起到田间追逐。在学校也常常会给同学们说大黑狗胸前的那长成了“人”字的一片白毛,我一直相信“人”字是老天爷特地印在它身上,用来告诉我大黑狗是他派来的使者。 +大黑狗来我家时已经很老很老了,是我读三年级的某一天,它像往常一样把我送到学校,但是我下午回家却不见它的踪影,一直等到晚上都没有见它回来。那些天我放学回家第一件事就是朝我和它常去的那些地方大声的唤它。 +不到一个月后的一天早晨,像大黑狗第一次来我家附近时的场景一样,湿漉漉的身子带着些杂草种子,不同的是它身旁还跟着一只背部有些黑毛的小黄狗,小黄狗胸前也有一个很明显的“人”字。我赶紧去用猪食瓢盛满饭放在它面前,它吃了几口就又走了。 +就这样,大黑狗离开了我,给我留下了一只小小的黄奶狗。我不知道它是去找它原来的主人去了,还是觉得自己老了,不愿意让我看见它倒下的样子,反正它就是再也没有回来过。 +小黄狗长成了大黄狗,我对这只大黄狗的印象很浅,只记得爸妈把这只黄狗送给了外婆家附近的亲戚,我们留下了它生的一只小黄狗。外婆知道我们把大黄狗送人,还狠狠的批评了爸妈,说自己来家里的狗不能送人。 +自然小黄狗很快就长成了大黄狗,我像以前一样也偷偷给大黄狗吃肉,逐渐开始懂事的妹妹也会背着爸妈给它肉吃,我和妹都会夹几片我们压根就不吃的肥肉,离开饭桌假装是到外面吃饭,实际上是给大黄狗送肉去了。 +我到离家 30 多公里的镇上读高中,每个月才回家一次。每次离家大黄狗都会送我到集市去赶车,我会在寒暑假的黄昏和它到新修的公路去追逐,带它去它自己一个人不敢去探索的地方。 +上大学后和大黄狗相处的时间更少了,听爸妈说它会经常跑到外婆家,外婆好吃好喝的招待它,招呼都不打一声就又跑回来了。还经常和邻居家的狗到麦子地打闹,要把一大片麦子弄倒才肯回家。 +每学期回家在离家还有四五百米的地方都会听到它的吠叫,因为它把我当陌生人了。但是只要我大喊一声,它就会立刻停止吠叫,飞奔到我这里,兴奋的往我身上爬,把它的前爪往我身上搭;我努力不让它碰到我的衣服,然而每次到家时我都带着一身泥巴做的狗爪印。 +现在大黄狗已经 10 多岁了,它就像大黑狗当年送我一样每天送我妹上学。我也已经走入职场开始工作,待在家里的时间更少了,我不知道它还能活多久,生怕哪次爸妈打电话时会给我说大黄狗死了,只要爸妈没有在电话中提及大黄狗,我都是非常开心的,因为那就代表着它依旧健健康康的活着。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 信息茧房|如何保持开心|自己的圈子—校友、房东|潮州彩塘抛光厂 + +

+ +
+ + + + +
+ +
+ 好几个月没有发文章了,主要是因为觉得自己太菜了,肚子里的东西太多浮于表面(实际上肚子也没有东西),也写不出来什么深度。不知道大家发现没有,现在很多公众号的味道都变了,一者是肚子里的货已经吐的差不多了,二者是在自媒体疯狂变现的年代,太多作者都开始为流量而写作,已经忘记了原来的初心。好友说长期不发文,突然发会掉粉的,我也想试试会掉下去多少。 +说到为流量写作,其实并不是自媒体作者天天在干的事,专业的记者也在做这些事情。从商业角度来看,一篇有深度而没有阅读量的文章肯定是比不上一篇适合大众口味但阅读量高的文章。 +媒体总是会挑那些吸引眼球的事件来报道,因为负面故事总比中性或正面故事更具有戏剧性,而且人在进化的过程中保留了对一些事物的恐惧感,这些恐惧感根植于我们大脑的深处,它们对我们祖先的生存是有帮助的。在现在的这个时代,你也很容易就把眼球放到那些能够激发我们本能的故事上。 +包含地震、恐怖袭击、战争、疾病、难民等等字眼的标题总是容易成为头版头条(现在朋友圈肯定都在传四川内江的地震),而像“在过去 100 年,死于自然灾害的人数几乎减少了四分之三”一类的标题总是不会收获多少阅读量,就更不具备什么商业价值了。大家都在说信息茧房,人类的本能也是造成信息茧房的原因之一。 + +周四和一个同事一起散步的时候,他问了我一句话:“小老虎,你为什么总是能保持这么开心呢?”(小老虎是在部门大家对我的称呼)我思考了几秒,不知道怎么回答同事的问题。对哦,我是怎么保持每天都这么开心的?是我给他们的错觉还是我确实就这么开心呢?于是给了同事一个简单的答案:“当你变得没心没肺的时候,你就会超开心;另外降低对事物的期望值,这样你就总能收到正反馈,会把你的开心加成。” +像之前一样,我又成长为同事圈子里的小开心果了。其实我也不是一直开心的,可能就是我这个人比较逗比,我一直认为逗比是一种生活态度。但在公司我同样怼大叔、怼领导,不管我是不是真的开心,既然给大家的印象是开开心心的,那就假装我是一直都开心的吧。 +我常常开玩笑说的一句话:“你对它笑,它就会对你笑,如果它不对你笑,那就对它多笑几次”。你对它笑,你肯定希望对方也给你回一个笑,但是我和大多数人不同的是我降低了期望值,我从来不期望对方能给我一个笑容,于是当对方给了你一个笑容的时候,那就是意外地收获,如果是一个大大的甜甜的笑容,就会突然冒出来幸福来的太突然了感觉。降低期望值也是一个很适合长期学习某项技能的方法,过高的期望值总是会让你放弃。 +很多人说情商是为了别人高兴,话外音就是不想委屈自己迁就别人。但是你让别人高兴了就是与人方便,那对方自然会给你方便,自己方便了不就是高兴吗,所以对这个世界好一点,降低对它的期望值,你就总是能开开心心的过日子。 + +毕业这一年认识了很多人,现在我日常接触的圈子差不多有四个,同事这个圈子没啥特别的,团队氛围比较好,时常在晚上悄悄定个会议室,大家一起打王者;推特、微信等软件里面结交的互联网大佬圈我插不上话,不敢说;然后是我两任房东带我进的圈子,和高校毕业人群所建立的圈子完全不一样。 +这群人大部分对我都很好,我目前比较害怕见到现任房东,因为基本上见到他就是出去吃饭。我住在房东隔壁,刚搬过来的时候一出门见到他:“小光,走,去吃饭。”房东的吃饭一般是两场,一场到餐厅点菜吃到 11:00-12:00 的样子,然后再继续下半场烧烤,在房东的带领下,我一个月长了 10 多斤。 +于是我现在出房门的时候,先瞅瞅房东在不在,如果不在就直接坐电梯下楼,如果在就先下到 5 楼,再坐电梯。所以我们现在更多的是没事喝喝茶,偶尔吃吃饭,体重总算控制住了。 +当然这个圈子也有不太好的人,有借了我钱后人就跑的没了踪影的人。但是我很庆幸我能这么早遇到这样的人,因为现在我借出去的并不多,如果再等 10 年我才能遇到这样的人,那我的损失可能就是很多很多倍了。 + +另外一个对我很重要的圈子就是校友会,我不清楚学校其它地区校友会是什么情况,更不清楚其它学校校友会是怎么样的,深圳校友会确实给了我一个温馨的感觉。校友之间都很单纯,学长学姐们都愿意带年轻人,最大有 79 级的师姐,最小的 15 级也已经到来,老人都会给新人讲他们所经历的事情,给年轻人传授经验。 +当然由于学校带着军校的基因,校友里面没有什么非常非常出名的企业家,但是大家都是很尽心尽力的相互帮助。仅仅靠校友情能达到这样的效果,这一点确确实实是出乎我的意料了,校友会目前是对我开心的加成作用很大。 +举个例子,一个学长新开了烧烤店,现在还没有开始对外营业,处于内测阶段。这一周每天店内至少有一半都是校友,店内的设计、装修、监控等等校友都在出力,当然像我这种没资源的学弟只能试吃给出改进意见了,一个人在外地能成为这样大家庭中的一员是很幸福的。 + +高校毕业生一年比一年多,媒体每年的标题都差不多一个意思:史上最难就业季。不得不承认独自一人到外地打工确实辛苦,大家都是独自承受着来自各方的压力,杭州闯红灯小伙的突然崩溃就是一个极端的例子。 +我之前的住的地方,仅仅我知道的就有三个年龄比我还小的女孩被包养,仅从外部观察来看,她们过的其实挺好的,嘴角也常常挂着 45 度的微笑,倒是包养她们的人过的不是多随性。其中一个还开了一家奶茶店,我有幸也喝了几杯免费奶茶。 +另外还有一些像我一样的打工者,我和前任房东也常常喝茶吃饭(现在也是),听他说住在那里的女孩子很多没有男朋友,但是她们晚上经常会带不同的男生回来,我想这对她们来说也是一种释压方式,当然住那里的男生可能只是没有带回来,房东不知道而已。 + +我不是太喜欢天天去研究某个业界名人所讲的话,也对各种各样的产品不是多感冒,不否认有些营销文案、产品功能、讲话内容是公司有意精心为之,但是有没有另外一种可能呢?是领导背错了台词、或者是说错了,而我们却非得去给它找出各种各样的原理。 +周末闲着去感受了一下农民工的圈子,我去的是潮州彩塘镇的抛光厂,才知道我们平时用的那些锅碗瓢盆那么亮不是因为镀上了一层,而是硬生生给磨掉了一层,给磨亮的。最后再说一个,不知道你有没有注意到马路边的人行道上,总是会有一列地砖是有凸起的,有的是条状凸起,有的是圆点凸起,有没有想过为什么是这样的呢? +凸起是盲人走的道路,条状代表直走,圆点代表拐弯。是不是觉得这个世界对每个人都是美好的,既然这个世界对我们这么美好,那干嘛要不开心呢? + +
+ + Read More ~ +
+
+
+ +
+

+ + 智齿|读研否|家庭小江湖|广告乱象 + +

+ +
+ + + + +
+ +
+ 建议读者大人们,如果自己经常一上火牙就疼,或者自己感觉牙已经有点问题了,可以提早预约医院的口腔科查一下,如果有问题早点预防,总是没有坏处的。抽烟的半年洗一次牙,不抽烟的一年洗一次牙。 +因为智齿发炎被狠狠的折磨了近两周,我属于比较能忍得疼痛的人,这不周末才进行了一场春季骑行,唯有美景与美食不可辜负,什么病痛都是浮云,但回想起连续几晚上疼到睡不着觉的滋味,昨天毅然决然斩草除根,给拔掉了,今天就感觉好了很多。 + +前几天考研成绩出来了,估计现在大部分同学都在准备复试,我没有体验过考研的这个过程,毕业这半年有时候还是会想,我也应该体验一下考研的那个过程,已经很久没有体验过把所有时间都投入到一件事情上的快乐了。 +但我还是不太建议读研,我这是无责任建议,毕竟自己没有读过研究生,在研究生实验室待了两年,算是有一半的硕士生经历吧。对于学历我一直以来的观点都是绝大部分人将它的作用放大了,总是认为名校成造就了强能力,而恰恰把因果关系给弄反了,是能力强的人都进了名校(本科)。当然,不否认像医学一类的专业是肯定要考研的,一棒子全打死肯定是不对的。 + +刘大发起的读书活动告一段落了,跟着小伙伴们泛泛的读了一遍《深入理解计算机系统》,只能用“痛并快乐着”来形容这个过程,每周输出一篇读书笔记,没有按时输出就罚钱的规矩很好,人还是需要自己逼自己才行。 +这本书不适合初学者阅读,在豆瓣上的评分接近 10 分,不讲究速成,而是一本内功心法,如果是您是码农的话,读一读绝对会提高一个层级。现在已经开启了另一本书籍的阅读计划,刘大这个活动组织的超好。 + +春节回家发现了一个巨大的变化,我老家那种贫困县地区的村民们,也在开始讨论保险这一类产品了,我是觉得这个改变太大了,说明农民伯伯的经济水平也有很大的提升了。另外通过朋友圈还发现,我认识的大佬们貌似出身都并不是多好,反倒是大部分普通朋友家里的矿更多。 +我们家族每年会组织祭祖活动,在正月初三一大家 50 人左右一同祭拜曾祖曾母,通过这么一个活动把整个大家族的年轻人联系起来,能搭建这样一个平台很棒,我正也在着手将家族信息数字化。 + +说到这里,想说一句家族群是个小江湖,亲戚之间也是暗暗较劲的,母亲不会抢群里几个特定的人发的红包,家里都是山路,车技不好的人很容易就寸步难行,一表叔就因为不到 10 米的距离,整个春节都在亲戚朋友面前抬不起头。 +堂弟现在是民航飞行员在读,而另一个表弟今年正值高考,说要去考炮兵学院,将来好把飞机打下来,这一下可好了,这些话全部伯父被截屏保留了,将来某一天要是这俩兄弟闹矛盾了,估计有的好看。保二爷写的家族群不是群,是江湖...看起来更有趣一点。 +在我身上更可悲的事情发生了,所有长辈一致认同应该由我来管理家族群,想想整个群里充斥的都是是那种要露不露、似露非露、就是不露的视频,或者是用粗糙都无法形容的大而泛的鸡汤文,整个头就大了,这可比解决技术问题难多了。 + +最近愈发觉得“大佬”之间的抄袭严重了,真大佬基本都是原创内容,或者是引用了别人的文字就标注出来,然而总是看到一些“大佬”原封不动发出来,还不表明出处,下面粉丝跟着继续做同样的事,我看到最多的一次是朋友圈连续 10 多条是一样的段子。 + +最后想无责任乱说一点科技相关的东西,5G 是当下的风口浪尖,各大厂商都希望在 5G 上有一席之地,搞芯片的搞芯片、做基站的做基站、整手机的整手机,5G + IPV6 肯定会带来无法想象的未来,5G 会大幅推动智能硬件的应用,但是手机这个应用场景是不是被夸大了呢? +现在的 4G 手机在线看一部高清电影不会有多卡顿的现象,广大吃瓜群众和各大媒体,一直都拿着 5G 手机来吹嘘,吃瓜群众跟随媒体引导的大流,我总觉得当拿到 5G 手机的那一刻,心里肯定会从喜悦急转失望的,就像目前的苹果产品一样。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 坟头蹦迪|清理手机上的干扰信息|2018 简短总结 + +

+ +
+ + + + +
+ +
+ 前一周在一席上面看了一场演讲叫「不正经历史研究所所长」,整场演讲都伴随着欢声笑语,讲的是民间文化与经典文化,经典文化实际上是统治阶级强行推动的精致文化,绝大部分老百姓是看不懂这些东西的,反正我自己参观博物馆,如果没有讲解,我是完全看不懂那些玩意的。 +能和生活联系在一起的文化最有活力,比如天天发杨超越祈求不挂科,拜拜马云希望有更多的钱等等。有一句老话叫「人活一口气」,民间文化都基于此,我在别人面前要抬得起头,要的是气派、要的是大气,上个世纪的「四大件」应该就是成功的一个标配,老百姓置办这些物件后,在邻居面前也更能昂首挺胸了。 +昨天了解到一个朋友家乡的有一个非常奇怪的习俗,那就是「坟头蹦迪」,我最开始的想法是,这是不是最近兴起来的习俗,但是一问才知道,他小时候就是这样的了。基于我们的体系是无法理解坟头蹦迪的,你能说它们对于亲人的去世就不伤心吗?他们要的还是能抬起头,来参加追悼会的人们看到这么豪华的场面,第一反应是故人后代是很有成就的,在某种程度上这算是对故人的颂扬。 + +花了几个小时时间把微信清理了一下,只留下了 83 个公众号,这其中还包括一信用卡一类的服务号,也就是说只留下了 70 个左右的公众号。留下的都是小而美的公众号,这些大佬作者的更新频率也很低,比如子柳老师最近才更新了一篇文章,而上一篇文章的更新时间是 7 月 25 日。 +最近在自己身上认识的一个问题是,关注的公众号太多读不过来,虽然在此之前已经筛选掉了很多号,然而自己还是读不过来,所以索性就做了减法。现在像公众号这些,已经变成了获取信息的一个重要渠道,我也确实比周围小伙伴掌握信息更快,大部分时间也自认为比他们更了解真相。 +但是我最近突然有个疑问,我真的比他们更了解真相吗?大部分人是通过垃圾文章获取信息,我虽然没有跟着垃圾文章人云亦云,但是我还是通过网络获取信息的,那么我是不是看到的是另外一种看起来更接近真相的假象呢? +这些信息其实我就算不知道好像也没有什么大碍,顶多是别人谈什么新闻时,我所了解的也就他们知道的那么点而已,更好的方式是让自己的大脑去思考,以时间的维度去追踪事件的发展。 +顺便推荐一个应用叫「刷屏」,我发现自己通过「刷屏」、「知识星球」、「微信公众号」、「朋友圈」四个地方,已经掌握大部分信息了,经常同事给我说某某新闻,而我在两三天前已经见过了。 + +我对国际计量大会修改「1 千克」的定义这件事印象很深,但是这件事我只看到在阮一峰老师的文章中有被提到过;人们更喜欢听自己了解的领域,跟自己同好的很容易产生好感,一些明星结婚、出轨竟然能把微博的服务给搞挂了,说明绝大部分人还是更喜欢撩自己 G 点的信息。 + +另外第二个问题是自己现在比在学校时更喜欢玩手机了,在学校时还能做到不带手机去自习,现在是隔一会儿就看看手机,把本来就碎片化的时间变得更加碎片化了,这种效率导致工作、学习效率低下,所以把微信和 TIM 的通知全部关掉了。 + +写到这里发现有点点像总结,索性就给自己简单总结总结吧。2018 我从校园走向了社会,完成了从学生到职场的转变。最大的改变是思维的提升,知道去投资自己,在学生时代,愿意花几百块钱去买课程,这对我来说是很大的突破,和现在愿意花几百的概念完全不一样,想想那时候一个月生活费总共也没有多少,而我去看了下自己在毕业前花在这方面的钱居然有一千多,如果加上毕业后的开销,那就是两千多了,真感谢那时候的自己。 +这其中的收获是巨大的,后面我偶尔会向朋友推荐一些好的付费课程,但是他们都和大部分人一样,吃一顿饭花几百块钱,而如果花点钱买一堂课提升一下自己,就好像要他命一样,所以后面就不和他们讲了。 + +2018 开始用文字记录技术、生活、感悟,这其中的收获也是不小的,认识了此前只能仰望的大佬,结交了志同道合的朋友,而且也让自己更愿意去思考了。收到电子工业出版社的约稿合同,但是现在书都没写完,明年还写不完的话,那就不写了,主要是懒。 +自负的缺点已经在渐渐改变了,更加懂得了谦虚。眼界不再局限于技术,很多东西我都会去了解,也结交了很多有趣的人,初入职场,好好学习与不同的人沟通。以后如果有能力,希望能给山区带去一点点教育资源。 +2019 依旧坚持每周和同事或者校友打一次羽毛球,常去爬爬山,和有趣的朋友一起疯一疯;多读书,用豆瓣等工具去筛选好书,加大阅读量;常输出,输出是更高层次的输入;尝试去了解金融的逻辑;学习新的技术领域。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 2018 年个人总结 + +

+ +
+ + + + +
+ +
+ 这个总结写的还算认真,回头看,我真的运气很好,遇到了很多大佬,在我还未毕业时并没有嫌弃我傻,教我的不仅仅是技术,还有理财、为人处世,下面是我这一年的成长经历。 +有一段时间因为华为 34 岁以上员工被裁、中兴程序员跳楼等事件的发生,各种蹭热点讨论“中年危机”的文章漫天飞,那时我正忙于找工作。 +当时一个微信群里面大家各种讨论中年危机,都在给自己制造焦虑,刚好群里有个大神可能看大家过于焦虑,就在群里发了几条消息,教大家如何避免中年危机,并且推荐了两本书。 +还是学生的我下意识的就发了一个添加好友请求,庆幸的是他同意了我的好友请求,当然我们没有什么交流,我的问题过于浅显,我明白自己这个水平问问题,会浪费人家时间,当时的想法是看看大神的朋友圈,他平时都接触什么,自己学习一段时间。 +大神推荐的书是李笑来写的《把时间当做朋友》、《财富自由之路》,两本书在学校图书馆都没有,我就给学校图书馆荐购系统提交了这两本书,图书馆效率也挺高,不到一周就把书给买回来了,我立马就借回来阅读。 +书中的内容刷新了我以前狭隘的认知,自己从来没有像书中那样考虑问题,除了对作者的佩服之外,更多的是思考自己这种学生思维局限性太大了,要慢慢的将它摒弃。 +有个定律是你关心什么就会来什么,后面陆续碰到几位像大神一样的人士,并加了他们的微信,但是都仅仅是通过他们朋友圈的蛛丝马迹去找知识,通过他们朋友圈的分享内容,我知道了“简七理财”、“码农翻身”公众号,然后知道了《富爸爸穷爸爸》、《小狗钱钱》,于是我通过微信读书,读完了这两本书,逐渐培养了理财理念。 +后来没隔多久,简七出书了,我第一时间就买了她写的《好好赚钱》(同期还有刘大也出了《码农翻身》一书,我也第一时间买了),简七写的内容通俗易懂,很容易理解。 +刘大在群里开了几次公开课,作为计算机专业的我,被刘大对技术的理解之深给折服了,正是业界浮躁的时候,成千上万人想着人工智能、大数据、区块链,而刘大一直能沉下心来去了解技术的原理,这给了我一个很好的榜样,我也逐渐沉下心来,开始去补最基础的知识,像《深入理解计算机系统》一类书也能尽下心来慢慢去啃(当时没啃完,最近又在啃),这种不浮躁的特质对我的技术成长是很有帮助的。 +此后有一天,另一个大神在朋友圈分享了曹大写的《从校园到职场系列文章》,喜欢深入挖掘信息的我,以曹大公众号为源头,又找到了冯大、池大、二爷等人的公众号。 +作为自由的大四学生,因为不用担心第二天起不来,我那段时间经常熬夜阅读他们的文章,再阅读的过程中我也开始思考自己此前哪些想法狭隘,哪些品质又是值得继续保持的。 +也是那时开始接受知识付费的,那时候已经有小密圈(现在叫知识星球)了,出于对几位大佬的信任,我第一次大胆的花了几百块钱加入了刘大、曹大、冯大、程序员小灰的小密圈,其中的内容比网上蹭热点的文章好不知多少倍,一贯爱捕捉蛛丝马迹的我,又通过评论信息发现了 angela zhu、子柳老师、陈利人老师等,然后去找他们的文章,他们输出的内容要比水军写的文章好太多。 +自己也是从那时候开始坚持写文章记录自己的心得的,通过写文章,我认识了很多优秀的人,比如吴小龙同学、了不起的杰克、java 小咖秀等公众号的作者,和他们交流的很少,但是却很受用,他们的积极向上也影响着我一直保持着乐观豁达的心态。 +自己写的文章也被几个资深程序员赞同,同时还收到了两个出版社发来的出书邀请,让我体会到了无心插柳柳成荫的收获。 +让我坚持一直写文章的动力不是赚钱,而是我切切实实体会到了它给我个人带来的成长,为了自己日后再看时能立刻就找到清晰的逻辑,我把都尽可能把文章写得有理有据,掌握自己的节奏,尽量提高文章质量。此前写的谈一下写作的重要性一文有说写作可以带来的好处。 +现在已经不把自己当新人了,而且有同龄人甚至比我年龄还大的人向我咨询问题时,我也能给出合理建议,都得到了他们的肯定。最近发现和周围伙伴最明显的一个区别就是,对于同一个新闻,我经常早于他们半天甚至一两天知道,而且掌握的信息比他们还准确,我认为这就是整体认知水平的提升。 +想说的是,执行力与信息素养很重要,执行力强的人会与你拉开越来越大的距离,信息素养也是一个关键品质,现在网络上充斥着大量的虚假信息,如何去分别这些信息的真假,在相同条件下如何获得更多的有效信息,是必备的能力。 +当前年龄 23,刚大学毕业几个月,没读研。按十年为期给自己定了几个小小的目标: +父母是地地道道的农民,智能手机都不会用,十年之类给自己和父母把重疾险、意外险之类的保险配置齐全,虽然父辈一直反对买保险。 +提高获取信息的能力,虽然现在对信息的掌握都比周边伙伴要早半天至几天,但是都不是自己的分析结果,学习以时间的纬度跟踪事件的发展。 +学习理财知识,现在只对信用卡、基金有一点点的了解,不管炒不炒股,金融知识都还是要学的,这方面通过看书、阅读、小额实操学习。 +提升自己的技术实力,职业是程序员,前后端都做,但是自己对技术的热情不是多么高涨(至少比身边一半人要高涨),以我对自己的了解,我在技术的道路上成长为小公司一个的架构师应该不成问题,再高层级怕是不行。 +慢慢做到不止一份收入来源,这方面不是多清晰,现在每个月平均会有 200 左右的非工资收入(帮助别人时发的红包等),十年后做到其它收入基本和工资持平。不至于因为钱的问题而忍受心中的不快,至少得有能指着老板的鼻子说“老子不干了”的底气。 +世界那么大,应该去看看,国内除了西北地区,中国很多地方已经留下了我的足迹,旅游不仅仅是玩耍,更是提升见识、获得灵感的有效途径,十年至少得把自己的脚印印到 5 个国家的土地上吧。 +十年之后应该已经结婚了,房子是现在最遥不可及的目标,但是心里莫名有一股自信,这个后面会实现的,虽然不知道哪里来的这股自信。 +最后一个,趁年轻,多学习,做一个终身学习的人,时刻保持学习的态度,多做有利于他人的事,现在水平不高,我能帮助到的大部分都是硕士及以下。努力提高自己,帮助更多的人。更大的目标是能给山区学校带去一些更好的教育资源。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 记录在南京大学半天 + +

+ +
+ + + + +
+ +
+ 因为工作需要,到南京出差了半个月,中间利用周末和最好的朋友疯了一天,之后自己又一个人到南京大学鼓楼校区逛了逛。 + +不会勾搭妹子的我总是能勾搭到老爷爷,到南大就勾搭了一个 86 岁高龄的老教授,他毕业于中山大学,年轻时候是做地质工作的。 +我就像个熊孩子一样要爷爷给我讲有趣的故事,要听他讲我们这一代人或者是大部分人都不知道的历史。 +爷爷虽然已经是快到耄耋之年的人了,但是对年轻时候的事记得很清楚,只是对最近的事记不起来。这篇文章仅仅是记录一下爷爷所讲的趣事。 +爷爷年轻时候接到中科院的任务,前往内蒙古考察。在考察期间他们用汽车压死过一只狼,而且当时吃了狼肉,一行 30 多个人都吃过那匹狼的心,但是没有吃过狗肺。 +据爷爷说,狼是很狡猾的动物,他们用汽车去追狼,狼就在原地不跑,等到你离它只有 10 来米的时候,突然拐弯跑了,这样的情况他们一共遇到了 6 次。这和《狼图腾》一书中的描写基本一致,狼有先进的军事文化。 + +爷爷告诉我,南大起源于金陵大学,南京大学的标志性建筑「北大楼」是个教堂的样子,金陵大学本来是个教会大学,现在的「北大楼」就是原来的「钟楼」。 +南大的地下有隧道,是当年毛主席提倡「深挖洞、广积粮、不称霸」时挖的,目的是为了防空。后来被南京食品公司用来存放香蕉,就是那种没有熟的香蕉,在隧道里面放熟了,再拿出来卖。不过现在隧道所有的口都没堵上了,完全废弃了。 +在南大,有一些楼中间有 5 层,然后到两遍就只有 3 层了,整体看来像是个三角形。实际上这些楼当年都是要修 8 层的,因为那时候没钱,建着建着发现没资金了,所以就封顶了。 +但是南大计算中心那栋楼只有 3 层却不是因为没钱,而是因为它旁边是消防大队,本来也是要建 8 层的,消防队说建高了挡住了他们视线,不能及时发现火情。爷爷笑着对我说:“但是也没见他们在上面拿个望远镜望啊!”。 +我们都知道「五四运动」,但是却很少有人知道「四五运动」,这个运动的起源就在南大,当时 300 多学生(我回来查资料说是 400)发起了这个运动,后来演变为全国性的运动,直接带动了半年后四人帮被粉碎。 +那是爷爷是个老师,他说他们教职工是很支持这些学生的,但是不敢公开性的支持。学生们很聪明,把标语刷到火车上,但是所有出南京的火车都被四人帮用水把标语给冲刷掉了,学生们就用沥青往火车上面写,才通过火车把这项运动的信息带到了全国各地。 + +我回来后查了一点资料,「四五运动」的起源是因为周恩来总理的去世,四人帮居然压制人民群众悼念周恩来,诬陷邓小平,而那时的毛主席也已经病到无法行动。 + + +人们把花圈都放到人民英雄纪念碑前悼念周总理,却被四人帮给清理了,北京广大人民群众在“还我花圈,还我战友”的口号下行成了天安门广场大规模的群众抗议运动。 + + +那也是一个诗意的年代,人们通过写诗来表达自己心中的愤怒,把小瓶子挂在树上,蕴意着期待邓小平的归来。那段时间四人帮应该是很难过的,从姚文元的日记就可以看出来。 + +爷爷还给我讲了一点他们的研究,他们研究行政规划的很多人认为,中国现在的行政划分有很多缺点的,中国应该划分 50~80 个省级单位。现在中国的行政级别也有问题,宪法规定行政层级只有三级(这一点我没查),而现在很多地方县下面是镇,镇下面还有乡,严格讲这是违宪的。 +快到午饭时间时,爷爷还教我写了一会儿字,有的简体字很难看,比如「龍飛鳳舞」用繁体字写出来很好看,但是用简体字写出来就特难看。要想练好毛笔字,把三个字写好了就行了,然而我现在只记得一个“飛”字了,这可能就是老师们常说的「你又还给我了」。 + +
+ + Read More ~ +
+
+
+ + + +
+ + 上一页 + + + 下一页 + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/Ykw3uqJED/page/3/index.html b/Ykw3uqJED/page/3/index.html new file mode 100644 index 00000000..15593d59 --- /dev/null +++ b/Ykw3uqJED/page/3/index.html @@ -0,0 +1,569 @@ + + + + + + + + 随笔思考 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 随笔思考 +
+ + +
+

+ + 认知如何提升?我是怎样转变自己思维方式的 + +

+ +
+ + + + +
+ +
+ 这仅仅是记录自己的一点经历和感悟,回顾一下自己思维的转变过程而已,如果对于还是学生或是初入职场的你有一点帮助,那也是没白写的。 +相信很多朋友都还记得有段时间因为华为 34 岁以上员工被裁、中兴程序员跳楼等事件的发生,各种蹭热点讨论「中年危机」的文章漫天飞,一时间各种割韭菜教如何利用副业赚钱的课程也层出不穷,那时我正好是大四,忙于找工作。 +很清晰的记得当时一个程序员微信群里面大家各种讨论中年危机,都在给自己制造焦虑,刚好群里有个大神可能出于善意,看大家过于焦虑,就在群里发了几条消息,教大家如何避免中年危机,并且推荐了两本书。 +还是学生的我下意识的就发了一个添加好友请求,虽然内心是非常希望对方能够通过好友请求的,但当时很清楚能和这样的人微信交流是一种奢求,而意外的是他居然同意了我的好友请求,当然限于我个人的水平我们没有什么交流,我的问题过于浅显,会浪费人家时间,当时的想法是看看大神的朋友圈,他平时都接触什么,自己学习一段时间。 +大神推荐的书是李笑来写的《把时间当做朋友》、《财富自由之路》,那时的心态还是宁愿花 300 块钱出去吃一顿饭,也不愿意花几十块钱买一本书,所以我第一时间跑到学校图书馆去查了,但是两本书在学校图书馆都没有,我就给学校图书馆荐购系统提交了这两本书,图书馆效率也挺高,不到一周就把书给买回来了,我立马就借回来阅读。 +书中的内容刷新了我以前狭隘的认知,从偏远农村出来的自己从来没有像书中那样考虑问题,除了对作者的佩服之外,更多的是思考自己这种学生思维局限性太大了,要慢慢的将它摒弃。 +有个定律是你关心什么就会来什么,后面陆续碰到几位像大一样的人士,并加了他们的微信,但是都仅仅是通过他们朋友圈的蛛丝马迹去找知识,通过他们朋友圈的分享内容,我知道了「简七理财」、「码农翻身」、「程序员小灰」(我知道的时候还不叫程序员小灰)公众号,然后知道了《富爸爸穷爸爸》、《小狗钱钱》,通过微信读书,读完了这两本书,逐渐培养了理财理念。 +后来没隔多久,简七出书了,我第一时间就买了她写的《好好赚钱》(同期还有刘大也出了《码农翻身》一书,我也第一时间买了),简七写的内容通俗易懂,很容易理解。 +刘大在群里开了几次公开课,作为计算机专业的我,被刘大对技术的理解之深给折服了,那段时间正是业界浮躁的时候,成千上万人想着人工智能、大数据、区块链,而刘大一直能沉下心来去了解技术的原理,这给了我一个很好的榜样,我也逐渐沉下心来,开始去补最基础的知识,像《深入理解计算机系统》一类书也能尽下心来慢慢去把它啃完了,这种不浮躁的特质对我的技术成长是很有帮助的。 +此后有一天,另一个大神在朋友圈分享了曹大写的《从校园到职场系列文章》,喜欢深入挖掘信息的我,以曹大公众号为源头,又找到了冯大、池大、二爷、刘备教授、大牛猫等人的公众号。 +作为自由的大四学生,因为不用担心第二天起不来,我那段时间经常熬夜阅读他们的文章,在阅读的过程中我也开始思考自己此前哪些想法狭隘,哪些品质又是值得继续保持的。 +也是那时我开始接受知识付费的,那时候已经有小密圈(现在叫知识星球)了,出于对几位大佬的信任,我第一次大胆的花了几百块钱加入了刘大、曹大、冯大、程序员小灰的小密圈,其中的内容比网上蹭热点的文章好不知多少倍,一贯爱捕捉蛛丝马迹的我,又通过评论信息发现了 angela zhu、子柳老师、陈利人老师等,然后去找他们的文章,他们输出的内容要比水军写的文章好太多。 +自己也是从那时候开始坚持写文章记录自己的心得的,通过写文章,也认识了很多优秀的人,比如了不起的杰克、java 小咖秀等公众号的作者,和他们交流的很少,但是却很受用,他们的积极向上也影响着我一直保持着乐观豁达的心态。 +自己写的文章也被几个资深程序员赞同,同时还收到了两个出版社发来的出书邀请,让我体会到了无心插柳柳成荫的收获,选择了和电子工业出版社签了出版合同。 +让我坚持一直写文章的动力不是赚钱,而是我切切实实体会到了它给我个人带来的成长,为了自己日后再看时能立刻就找到清晰的逻辑,我把都尽可能把文章写得有理有据,掌握自己的节奏,尽量提高文章质量。谈一下写作的重要性一文有说写作带来的好处。 +现在已经不把自己当新人了,而且有同龄人甚至比我年龄还大的人向我咨询问题时,我也能给出合理建议,都得到了他们的肯定。最近发现和周围伙伴最明显的一个区别就是,对于同一个新闻,我经常早于他们半天甚至一两天知道,而且掌握的信息比他们还准确,我认为这就是整体认知水平的提升。 +最后想说,执行力与信息素养很重要,执行力强的人会与你拉开越来越大的距离,信息素养也是一个关键品质,现在网络上充斥着大量的虚假信息,如何去分别这些信息的真假,在相同条件下如何获得更多的有效信息,是必备的能力。 +上面提到的各路大神,他们的文章都很值得阅读,做一个终身学习的人,时刻保持学习的态度。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 读大学的几点建议 + +

+ +
+ + + + +
+ +
+ 前天在朋友圈看到一句话:“学生就是无知、狂妄、垃圾的代名词”,让我思考了很多东西。毕业出来也有快两月了,圈子里还有很多学弟学妹,很多同级的同学也都读研了,这里谈谈自己的感受,应该怎么把大学过好,期望不要太高,我自己很普通,大学没有什么出彩的经历。 +很多父母都把孩子的成就与大学挂钩,认为好的大学就是成功的代名词,盲目追求高学历,孩子从小也一直受这些思维的影响,应试能力强的惊人。大家都知道清华北大好,但是你问他哪里好,就回答不上来了,尤其家长,在他们眼里 985 一定比 211 好,211 一定比普通一本好。 +现在大学都会给你传达自己乃名门之后的观念,进校首先讲历史,当然都是挑好的讲,然后讲学校的历史成绩单。不得不说,效果非常好,你一和大学生谈论他的学校时,他会给你说出来学校是某某名人所建,学校在哪方面做的非常好,比如导弹是全国第几,造船位居全国前列等等。但是你一问他本人是哪个专业,回答是学数学的、学计算机的...... +每个学校都有恶心的事,我自己的一个经历,学院一拍脑袋,搞个什么本科生导师制,然后就没有然后了,四年总共见了导师一面。期间最可笑的是,也不知道是教育部还是什么部来检查,学生需要交一个导师沟通表上去,导师在哪个办公室都不知道,那大家怎么办,就模仿导师的口吻给自己下评语,第二天全院的导师沟通表都被打回来了,因为大家模仿的口吻不像导师,要求重新造假。 +上面的类似情况在大部分学校应该都存在,只不过看谁更可笑,某个学生出事了,学校第一想法不是怎么帮自己的学生解决问题,而是想如何把事情压下去,封锁消息。你会发现很多效率像蜗牛一样的机构,其公关效率却像火箭一样。 +现在各个大学的就业率都高的惊人,都不会低于 90%,为啥这么高呢?我也不知道学校是如何统计就业率的,唯一清楚的是,你毕业了,没有签工作,那么辅导员会给你打电话让你随便找个公司把三方协议签了交上去;这算轻的,很多学校是你不交三方协议,就不给你学位证、毕业证,我身边就有好几个随便刻个假章,盖在三方上面,只要交了三方协议的都算就业了的。 +我个人认为大学有的课就应该逃,也看到过文章说学生上课不应该带有批判性思维,什么课有用不应该是学生说了算,大学的课程设置都是专家们讨论的结果,现在最不缺的就是专家,什么人生导师一大把,出来之后,你仍然会发现有的课纯属浪费时间。强调一下,逃课不是去打游戏,是为了把时间利用的比在课堂上更有价值,我大学微积分老师也鼓励我们逃课,现在看来那时还是胆子太小,人家鼓励你逃课,还不敢逃,怂。重要的事再说一遍,逃课是去做比上课更有价值的事情。 +养成自学的习惯,提高自学能力,自学能力太重要了,而且这个时代自学是很容易的,网上有很多视频教程,比学校老师教的还好,而且也更接近于实战,大学教不了你太多东西,仅仅提供了一个平台,只是平台大小的区别而已。经常会听到学生说某个知识点老师没教,潜台词就是这个知识点我就不应该会,而且理直气壮,让人无语。世人都认为学历最重要,实际上真正重要的是学力。 +迷茫的时候就去旅行吧,感受一下不同的文化,见识见识世界的缤纷多彩,你的视野会开阔许多,很多事情必须亲身体验才能感受到它的好处,旅途中你可以结识各种各样的朋友,与他们的思想碰撞,看看其他地方的生活,你可能就不会迷茫了,会找到自己乐趣。 +多结识比自己优秀的人,认识正能量的朋友,大学提供了很多机会,优秀的朋友会在不知不觉中改变你,你也会不知不觉变得更优秀。我在出于兴趣和打发时间,没事写写文章,让我意外的是,对我的改变太大了,通过写作让我认识了一些社会上的优秀人士,通过与他们交谈,我的思维方式有很大的改变,学生思维逐渐摒弃。 +尽量不要透支,学生没有收入来源,基本都是依靠父母每个月给的生活费,很多学生都使用花呗、白条等产品,而且借贷金额还不少,从理财角度来看,每个月的还款额超过自己收入的三分之一,生活就会有压力,何况学生还是没有收入的群体。没必要为了追求时髦而疯狂购买各种新产品,真高品质生活不应该是科技产品堆砌而成,而应该是由惬意、舒心、成长所构建的。 +还是要注重和学院领导、辅导员的关系,这点我是做的最差的,因为我不会拍马屁,看不惯就要说出来,容易得罪人。相信这背后的好处都还是明白一二,什么评奖评优暂且不谈,在保研的时候,这种关系会帮你一个大忙,往大了说就是改变人生的机会。 +写完读了一遍,有的观点还是显得偏激,请自行选择吸收,也欢迎批评指正。总得来说,最重要的就是提升自己的认知水平,思维方式很重要,保持终身学习的态度。有的事要敢想,不要给自己的思维设限制,也不要觉得博士硕士有多么了不起,研究生能做的事,本科生照样能做。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 谈一下写作的重要性,每个人都应该养成写作的习惯 + +

+ +
+ + + + +
+ +
+ 关于写作的重要性,你可能在其他地方也见过一些描述,大致的说法都差不多,如果本文某些字句与你已经见过的文章有雷同,那纯属巧合,我仅从个人这几个月的感受出发来说。 +我从三月份开始,在公众号上面发一些文章,其实从这几个月的表现来看,是把公众号当作博客来用了,我的初衷没有想着靠公众号赚钱,所以我的分享很随意,主要是技术、认知、阅读方面的东西。如果不喜欢,主动权在你手里,大可取关;如果喜欢,我的文章能让你少走一些弯路,那么我自己的目的达到了,给不给赞赏无所谓,其实心里还是希望你给的,咱没必要把自己放的太清高。 +今天微信订阅号改版了,新版的订阅号基本形态变成了信息流,关于产品我还不太懂,但是我觉得这次改版对于作者的个人品牌形成不利,没内容的公众号应该会被取关很多粉丝,但是忠实粉丝也更难找到自己喜欢的“博主”了,所以以后,请认准作者。 +很多人也有撰写博客的习惯,我很赞同这个做法,关于写作的好处我觉得有以下几点,只有认真去做了,才能体会到它带给自己的成长。 +第一,写作是整理自己思维的过程,写作能力是一种重要的能力,不一定要多好的文采,但是不是每个人都能把语言组织的有条有理。现在的时代,没有铁饭碗,你需要不停的学习才能立于不败之地,很多人觉得写作是浪费时间,其实不然,写作是对已学知识的整理过程,输出其实是更高层次的输入。拿我之前写的朴素贝叶斯实现拼写检查器来说,其中那个贝叶斯公式推导是我花了很多分钟才想出来的,就好像老师教给学生一碗水,那么老师就必须具备一桶水才行。 +第二,写作是个人品牌的建立过程,可以说微信已经成为了中国互联网的小小代名词,农村大叔大妈手机上面最可能出现的软件就是微信,微信打通中国互联网的最后一环,在这么大的平台上,你分享的内容对别人来说是有帮助的,那么你的个人品牌就已经逐渐在形成了,这是个人影响力的提升。个人品牌在以后一定会很重要,个人品牌在日常生活其实有体现,我相信每个人的微信都会屏蔽几个人的朋友圈信息吧,经常在朋友圈发一些无用信息、垃圾信息,这其实就是个人品牌的损失。 +第三,通过写作你能交到很多朋友,而且通过这种方式所交到的朋友都是优秀的,他们会对你的成长起到促进作用,而你也会因为和他们交流而在不知不觉中得到提升,真正的朋友是相互促进的。我这几个月交到的朋友,刷新了我的认知,偏见来源于无知,在这个过程,我的认知得到了很大的提升,认知这玩意也不太好描述。举个例子,大概在大二的时候,我看到一篇文章说高中物理中所学的电子、质子等概念是错的,将要被新的知识体系取代,那时二话不说就转发朋友圈了,但没过几天就发现这其实是一个虚假信息;前段时间,中兴被美国制裁了,然后就有一些自媒体作者为了吸引流量,乱写一通什么华为宣布将要退出美国市场的消息,我的第一直觉就是这是虚假信息,然后我去验证了自己猜测的正确性,而周围很多人竟无脑式的选择了相信这条消息,还给我分析为什么华为要退出美国市场。这在我看来就是认知水平的一个体现,或者贴切一点叫信息素养(这个词不是我发明的),我现在对于信息的掌握已经明显快于周围的同学了,而且掌握的也比周围同学更加全面。 +最后说一点,没必要为了写作而写作,经常在知识星球看到有人问问题,说自己的写不出东西来,怎么办?这就是自己的输入不够,自己体内没有实质的东西,如何能达到输出呢?更别说高质量的输出了。 +我以后的文章主要是机器学习和提升认知方面的,最近更文有点慢,其一是自己也刚开始接触机器学习不久,要写出一篇比较好的文章,需要几天的输入;其二马上要毕业了,繁忙于各种琐碎的事情无法自拔。 +总的来说,写作利大于弊,如果你有闲心,看一下我几个月前发的文章,再和我现在的文章做个对比,你能看到我的变化,在文章逻辑、排版等等方面都或多或少的有一些提升,所以我建议你如果空闲时间比较多,也可以尝试尝试写作,自己的成长过程会在字里行间被记录下来。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 年轻不要给自己设限 + +

+ +
+ + + + +
+ +
+ 初入象牙塔时乘坐了 60 多个小时的火车,后面基本都选择了飞机作为出行交通工具,毕业时再次选择了火车这一交通工具回家,再看一次从东北到西南的沿途风景,无奈火车居然能晚点两小时,这篇文章是在火车上为打发时间写的,希望对您有所帮助。 +记得大一入学前,买了一本覃彪喜写的《读大学,究竟读什么》,那时候对于里面有一些观点不赞同,觉得大学这么神圣的地方,怎么被作者写成那样,读完一遍只是抱有一种怀疑的态度,四年之后的今天,我觉得这本书值得一看,大部分内容还是有用的,不过有一些内容还是很偏激的,自己过滤掉就好了。 +现在回头看,大学最需要的应该是经历,我也是大三才算明白这个道理吧(这个道理应该不止学生能实用)。我认为本科阶段是容错率最高的阶段,这个时候你干什么都不怕,犯了错也没有什么大碍,最重要的是犯错(不犯错更好)的那个过程。 +年轻人做什么都是学习,不要给自己设限,在一无所有的年龄就应该多经历,因为这时候的容错率很高,试错成本低就要勇于试错。(这句话可能之前的文章说过,大同小异的话你也能在别的好文章里面见到) +现在的家长,也包括孩子,大多数喜欢拿一些证书、奖杯出来炫耀,而现在大学里面的个性化保研政策看的就是各种奖项。我更看重的是比赛的过程,但是在学校有一个怪现象:我不想办事,只想你给我挂一个名,到出去比赛的时候,看到所报的项目自己不是第一作者,都不愿意去比赛,觉得是在浪费时间。 +我个人在这里面算一股清流了,我很喜欢跟着出去比赛,因为比赛的过程能教会你很多在学校学不到的东西,给不给我奖状无所谓,只要给我报销差旅费就行了,这一点对我这种穷学生来说跟重要,想出去看看世界长长见识,自己又没有钱,学生群体中随随便便就拿出几千块钱的人还是不多,所以这是我找到的最好的长见识的方法了,上大学前连小县城都没出过的我,通过比赛到过佛山、深圳、重庆、日照等地,这对我算是一生的财富。 +写到这里,脑子里面满满的全是回忆,发现想说的太多,全写出来可能会上万字,先不写了,以后分开写个系列的也行,下面说几句干货道理吧,过来人的总结。 +第一,少拿学校的光环往自己身上套,和你没关系,对于我的学校动不动就拿哈军工说事(中国人都喜欢把自己和名家扯上关系,看起来显得有一些历史文化底蕴),完全是不自信的表现。作为唯一一个首批进入211缺不是985的学校,我觉得学校一直在啃老底。类似的文章还有之前写的谈一点关于名校的话题和刘大写的除去大公司的光环,你还剩点啥? +第二,学校教不了你多少东西,大学阶段和高中阶段最大的区别是,高中有人赶着你学,而且还有人给你指明学习的方向,但是大学没有人告诉你学什么,也没有人赶着你学习,所以培养自学能力和判断选择能力很重要,我个人认为这是大学阶段最应该学习到的东西。 +第三,如果大学只学习了课本中的内容,那还不如不上大学,不得不承认,大学课本内容都属于经典中的经典,但是学校的要求太低,所以要自己去练习,而且很多老师所教授的东西属于过时的知识,有的课就应该逃掉,利用这个时间去做更有用的事情。 +第四,真诚待人,学生阶段所交的朋友没多少功利性,能交几个铁哥们最好。我个人觉得比较实用的一个看人标准,你只需要看某个人对待其他人是什么样,就大概知道他对你会是什么样了;好比谈恋爱,你不要妄想渣男渣女到你这里就不渣了(小概率事件)。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 西凉忆 + +

+ +
+ + + + +
+ +
+ +夜雨滴,淅淅沥沥,伶仃至天明。 +忆往景,伤流景。 +世事沧桑敌不过一句悲凉,浮生蹉跎尽在笔墨中泛黄。 +几多欢喜,几多忧愁,曾经挫败,也曾迷茫。 +再回首时,我将深情拥入梦,拥有瞬间的感动却也足够。 +故事中的悲与合,百般萧瑟,千般落寞。 + +《浪淘沙》 +帘外月如钩,好梦难留。 +寻思翻悔几时休,无那安排辞去意,相聚分流。 +人世总多秋,恰上心头。 +平生却道愿堪忧,暗忆欢期眠不得,何恨离愁。 +《南乡子.冷风淅》 +冷风淅,疏雨斜,岸花零落杪雀喑。 +轻舟短棹临野渡,归何处? +隐隐两三烟柳树。 +《忆江南》 +碧云卷 碧云卷,暮日烟霞浓。 +翠楼水阁花树掩,斜晖眽眽又几重。 +望断石桥东。 +《如梦令》 + +乙未十月初五夜无眠,念往日兮,不觉已而四载有余,徒感悲戚,故作此。 + +昨夜修竹风露, +浅睡迷香清雾。 +罗帐月为魂, +痴念已然终误。 +虚度,虚度。 +回忆落空如墓。 +《钗头凤》 +待六月,激情朗,意气凌冠挥缨枪; +将袖扫,任逍遥,马踏平川,舒眉一笑,傲傲傲! +舞三江,梦飞扬,落日无边江不尽; +趁今朝,更须忙,题名金榜,折桂香飘,妙妙妙! + +花翎水裳,丹青风华暗染霜。 +夜未央,几度琉璃徬。 +谁笑我儿女情长,步步断肠。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 大叔激励|小宫黛雅|上饶熊孩子|法国小羊做新生|体育强健身心|地震回忆 + +

+ +
+ + + + +
+ +
+ 最近机缘巧合和几个中年大叔聊天,从眼神里面能看出来他们是讲的真心话,平时也非常反感长辈们给自己讲的一些大而空的道理,但仔细想想这些话其实并不是完全没用。 +不管对方是世俗眼光中的成功人士还是失败人士,他们都会有自己没有实现的理想愿望,成功人士没有好好陪家人孩子,他们可能喜欢特地找个时间,不管接收方是否感动,但至少他自己已经得到心灵的慰藉了;失败人士没有给到家人足够富裕的生活,这时候他们都很喜欢看努力、奋斗、自强不息......一类词汇堆砌的文章。而老人的精神世界已经不是奋斗了,而是人这一生......,所以如果陪老人说话,你只需要把话题引入到佛身上,然后静静的听就可以了。 + +忘了啥时候无意中加了一个高中生,目前正在读高二,与大多数人不同的是,她属于双性恋人群,并且更偏爱小姐姐,出口成脏,上课偷偷玩手机,宛然一个大家脑中所构想的问题少年。 +但是几个月前突然变了,一下子变得有礼貌起来了,我问她是因为什么变了,她告诉我是因为我说的一句话:不说脏话是对人起码的尊重。这句话让她想明白了,我在这里偷偷说一句,我也不知道当时说的这句话对不对,其实就是随口一说,让一个小娃娃变好了,那也好。 +在我的循序诱惑下,小姑娘已经不藏手机了,每周日自觉的把手机交上去,每周五手机发下来再玩,所以我和她的聊天记录现在基本上变成了,周日我发一句“加油”,周五下午我会收到一句“突然出现”。 +小姑娘自己是同性恋的事情不敢给爸妈说,我想这是不是因为父母与孩子天然就有一种屏障,以至于无法与他们进行心灵上的沟通,要想能够得到孩子的信任,父母应该多站在孩子角度考虑考虑问题,尽量不要拿「我都是为你好」去搪塞孩子。 + +最近上饶杀熊孩子案很火,女生父亲杀人当然是不对对,但是对于这种校园霸凌这件事,校方与男生家长都摆出无所谓的态度,才导致悲剧的发生,其实校园霸凌的核心不在熊孩子身上,而是在熊家长身上。 +吴军在其《大学之路》上有写道:“在我的印象中,父母晚上从来不参加应酬,甚至不看什么电视剧,总是非常有规律的学习,我的母亲现在快 80 岁了,依然每天坚持学习,父母们并不知道,他们在对我们兄弟的教育上最成功之处,是以他们的行为潜移默化地影响了我们,让我们渐渐养成了终身学习的习惯”。借句公益广告词——父母是最好的老师。 + +最近一个很搞笑的新闻,法国一所小学为了拯救学校不被关闭,招了 15 只羊作为新生。简单来说就是,根据法国政策,当学生数量少于 261 时,学校将被迫停办,而法国人在抗议上也很有创意,反正规定上又没有写招生的物种必须是人,正好我们家有许多只适龄绵羊,都送去上学吧! +牧民说到做到,这周二就和校长及家长达成一致,带领着自己家50只绵羊赶到学校招生办公室报道,招生办的老师热情的接待了羊以及送羊报道的牧羊犬们,在检查了所有绵羊的出生证后,最终有15只年龄在3-6岁之间的适龄绵羊顺利通过合法注册,成为小学的一年级新生,让这所小学不至于被停学。 + +我们常常把体育锻炼与健身、减肥挂钩,前段时间杭州程序员小伙伴突然精神崩溃,引发了一大波人关注,在校大学生跳楼也是常有的事,但是去细细看一下,是不是很少会看到体育生跳楼。 +大学生跳楼无非是意志力脆弱、心理自我调节能力差等方面影响的,而体育锻炼恰恰会无意中去缩短了这些短板,我主要不是想说体育锻炼有多么多么好,因为这是大家都知道的,而是想说一件事的作用范围可能比你想象的要大的多。 + +今天是母亲节,无意中突然记起了初中的一篇阅读理解——《那支枯萎的康乃馨》,读者大人可以去搜搜读读,从另一个角度看看母亲节送礼这件事,不知道现在的朋友圈孝子还多不多。 +同样今天是 5.12 汶川地震纪念日,地震发生时我还在读 6 年级,记得整个床都摇的快要倒了,房子上面的瓦片蹭蹭的往下掉,但是学生中间没有一个害怕了,原因只是因为我们并不知道地震这个词。 +去年的 5.28 日,吉林松原发生了一次小的地震,哈尔滨有震感,作为也算经历过地震的人,自然异常的敏感,熟睡中的我一下就惊醒了,立刻意识到地震了,然后在那三四秒的时间里面,时间好像停住了一样,我都数不清那几秒时间脑海里滑过了多少记忆,我体会到了面对死亡的感觉,几秒过后,根据汶川地震的经验,本次只是小地震,所以我又躺下睡觉了,然后室友们都跑出去了。 + +出来工作也有大半年了,之前以为第一份工作是学习技术的,现在才发现第一份工作首先应该学习的是做事态度,在学校如果某件事不想做或者太困难,那么完全可以选择不做,但是在企业不行,一些恶心的事情必须有人得去做,学生总是缺乏应有的责任感,但责任感是优秀员工的基本条件。 +我之前在公司怼过两次领导,怼完领导之后他反倒对我更好了,给我解决问题的速度超快,现在回想起来可能自己实际上做错了,为啥不能心平气和的去解释,而采取暴力沟通,今后我也要学习怎么做一个圆滑的俗人。 +保持一个虚心的学习态度是异常重要的,认清自己资质平平的现实。一些人在公司认为经理是个傻逼,总监是个马屁精,董事长屁股决定脑袋,那不过是因为你没到那个位置,没办法理解他们考虑问题的角度而已。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 阅读高于自己的作品,远离精神毒品 + +

+ +
+ + + + +
+ +
+ 关于阅读与写作的重要性,可能每个人都多少有一些概念,关于写作的重要性可以看我之前系的谈一下写作的重要性。阅读是打开世界的大门,写作可以提升语言组织能力。 +自己就读的专业是计算机科学与技术,生活在山区一直都没有接触过电脑。读大一大二的时候,我非常崇拜那些随随便便就能做一个网站、写一个复杂算法的大神。那时候在觉得只要专业能力足够强,只要技术掌握的足够深入,那就是 NB,所以我在大二之前从来都没有读过一本技术之外的书籍。 +现在回忆起那时候的想法真是太狭隘了,一个人掌握了某项专业技能,我们可以把这个人称之为「手艺人」,大学能教给我们一门手艺(可笑的是很多学生连手艺都没有学到),这门手艺可以帮助我们得以生存或者赚到一些钱,但是生活肯定不仅仅是钱,还有很多比钱更重要的事情。 +白领及以下阶层基本没有自己想去哪里就去哪里的自由,那怎么拓宽自己的视野呢?阅读和交朋友是个很不错的方式,但并不是所有阅读都是有效的,也不是所有朋友都是值得信任的,有的甚至是一种精神毒品。 +阅读需要挑选高于自己的作品,要能仰视它,才能攀登。阅读那些比自己低下的作品只会让自己更 low。现在的生活节奏很快,碎片化阅读成了很多人的阅读方式;有人抨击碎片化阅读,也有人提倡碎片化阅读,每个人有不同的观点,我个人是赞同碎片化阅读的,像得到、喜马拉雅等平台也把一个大的知识点切分的足够小了,一个小的知识点也就 10 来分钟,很适合碎片化的阅读,还不耽误其它事情。 +来自互联网的阅读内容和自己大多是平等的,每个人都在使用微信,但我相信很多人都有屏蔽一些人朋友圈的习惯,因为你会选择跟你脾气相同、你喜欢的、跟你水平接近内容去阅读;现在的 APP 也很多,而且很多首次注册都会让你选择自己感兴趣的内容,所以也就会失去挑战自我的机会。 +阅读是提升认知的重要手段,人与人之间的根本差距在于认知,如果读了一本书之后能让自己的认知得到提升,那么这就没白读;当然,如果读完一本书或是一篇文章之后能让有很大程度的转变,那这种认知的提升我相信是宝贵的财富,而且认识是伴随终身的。能达到这样效果的好文章、好书肯定少之又少,对我个人影响的最大的一本书是《把时间当作朋友》,我很推荐这本书,接触这本书算我认知上的一个转折点。 +也有一些阅读是日积月累对自己产生影响的。我曾经关注了近 150 个公众号,从里面精挑细选了一部分原创公众号留下,涉及技术、新闻、产品、理财等方面。我现在每天的阅读主要就是公众号阅读和付费加入的几个知识星球,以及得到上面的付费知识。 +日积月累,我筛选有效信息的能力也更强了,筛选信息能力在这个信息爆炸的时代是很重要的。 +有一次听到朋友说:“文章太长了,我不想看”。我没说话,只给了一个无奈的眼神,因为他只想要一个结论,因为这可以很轻松的获得一种愉悦感,就像打游戏一样可以快速的获得喜悦,而且对大脑来说,也是最容易接受的,但是慢慢地,大脑就会失去独立思考的能力。 +从产品角度讲,不得不佩服头条对人性的洞察,为了“懂你”,在自己的产品上加入推荐算法,但实际上是让你把时间花在他们产品的身上,把产品做到这种程度,不得不承认是很牛的。最近奈飞出了一步很棒的纪录片,叫做监视资本主义:智能陷阱 The Social Dilemma,片中将科技的负面清楚的呈现给我们,网络科技在某些方面已经逐渐演变为操纵社会的巨兽。 +像抖音、快手、头条、微博这些产品我认为基本都是在浪费用户时间(我还没有用过快手和微博,评价它们有点冒昧了),他们无异于精神毒品,吞噬你的时间。我并不是讨厌这些产品,我自己也喜欢体验新的产品,我只是觉得把宝贵的时间放到更有价值的事情上去,那么就会比周边人更加优秀。 +需要选择的是高于自己内容阅读,而不是把时间都花在那种不需要思考就能得到的愉悦上去;如果把大部分时间都花在轻易就能获得的愉悦感上,那么你应该正在一步步颓废。 +最后推荐自己写的另一篇文章大学生书单推荐 + +
+ + Read More ~ +
+
+
+ + + +
+ + 上一页 + + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/ZN0MLPPrG/index.html b/ZN0MLPPrG/index.html new file mode 100644 index 00000000..a58d8ab2 --- /dev/null +++ b/ZN0MLPPrG/index.html @@ -0,0 +1,584 @@ + + + + + + + + KK 给年轻人的 68 条建议 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ KK 给年轻人的 68 条建议 +

+ + +
+ +
+

KK 是凯文·凯利的网名,他曾经担任《连线》杂志的第一任主编,是著名的科技评论家,也是畅销书《失控》的作者。去年的 4 月 28 日是他 68 岁生日,他在个人网站上发表了一篇给年轻人的 68 条建议,文章被翻译成了十几种其它语言,今年 4 月 28 日老爷子又续写了一篇给年轻人的 99 条建议,本文是给年轻人的 68 条建议中文翻译版,翻译除了借助 DeepL 机器翻译工具外,更多参考自KK 在 68 岁生日时给出的 68 条建议

+

Learn how to learn from those you disagree with, or even offend you. See if you can find the truth in what they believe.
+学着从那些你不认可甚至冒犯你的人身上学习,看看能否从他们的信仰中找到真理

+

Being enthusiastic is worth 25 IQ points.
+充满热情可以抵得上 25 点智商

+

Always demand a deadline. A deadline weeds out the extraneous and the ordinary. It prevents you from trying to make it perfect, so you have to make it different. Different is better.
+做任何事都应该设一个 deadline,它可以帮你排除那些无关紧要的事情,也能避免过分要求自己尽善尽美。努力去做到与众不同,差异比完美更好

+

Don’t be afraid to ask a question that may sound stupid because 99% of the time everyone else is thinking of the same question and is too embarrassed to ask it.
+不要害怕自己问的问题看起来很愚蠢,99% 的情况下,其他人和你有一样的问题,只不过他们羞于问而已

+

Being able to listen well is a superpower. While listening to someone you love keep asking them “Is there more?”, until there is no more.
+倾听是一种超能力,当听到你喜欢的人说话时,要不时的追问「还有吗」,直到他们没有更多的东西可讲

+

A worthy goal for a year is to learn enough about a subject so that you can’t believe how ignorant you were a year earlier.
+一个有意义的年度目标是去充分了解一个学科,这样你就会对一年前的无知感到难以置信

+

Gratitude will unlock all other virtues and is something you can get better at.
+感恩可以解锁其它所有的美德,也是你可以继续做的更好的一件事情

+

Treating a person to a meal never fails, and is so easy to do. It’s powerful with old friends and a great way to make new friends.
+请一个人吃饭是非常简单的一件事情,不仅仅是老朋友,这也是结交新朋友的有效方式

+

Don’t trust all-purpose glue.
+不要相信万能药

+

Reading to your children regularly will bond you together and kickstart their imaginations.
+经常给的孩子读书不仅能巩固你们之间的感情,也能帮助孩子开启想象力

+

Never use a credit card for credit. The only kind of credit, or debt, that is acceptable is debt to acquire something whose exchange value is extremely likely to increase, like in a home. The exchange value of most things diminishes or vanishes the moment you purchase them. Don’t be in debt to losers.
+永远不要用信用卡去透支。唯一可以接受的透支或负债,应该是那些通过负债有极大可能获得增值的事物,比如房屋。绝大多数事物在你买下它的那一刻就开始贬值了,别为那些没有未来的事物透支

+

Pros are just amateurs who know how to gracefully recover from their mistakes.
+专业人士不过是善于从挫折中优雅爬起的菜鸟

+

Extraordinary claims should require extraordinary evidence to be believed.
+要想让人相信非同寻常的观点,就需要非同寻常的证据

+

Don’t be the smartest person in the room. Hangout with, and learn from, people smarter than yourself. Even better, find smart people who will disagree with you.
+别成为一群人中最聪明的那一个,和那些比你聪明的人待在一起,向他们学习。如果能找到和你观点相反的聪明人,那就更好了

+

Rule of 3 in conversation. To get to the real reason, ask a person to go deeper than what they just said. Then again, and once more. The third time’s answer is close to the truth.
+对话中的「数字 3 原则」。想要找到一个人真正的意图,那就请他把刚才说的话再深入一些,如此反复直到第三遍,你就能比较接近真相了

+

Don’t be the best. Be the only.
+不做最好的,去做唯一的

+

Everyone is shy. Other people are waiting for you to introduce yourself to them, they are waiting for you to send them an email, they are waiting for you to ask them on a date. Go ahead.
+每个人都很害羞,其他人正等着你向他们介绍你自己,等着你给他们发送邮件,等着你约他们见面。大胆的向前走

+

Don’t take it personally when someone turns you down. Assume they are like you: busy, occupied, distracted. Try again later. It’s amazing how often a second try works.
+别人拒绝你的时候不要往心里去。假设他们和你一样忙碌、腾不出手、心烦意乱,再试一次,第二次成功的几率超乎你的想象

+

The purpose of a habit is to remove that action from self-negotiation. You no longer expend energy deciding whether to do it. You just do it. Good habits can range from telling the truth, to flossing.
+习惯的意义在于无需再为某类行为纠结,不用再消耗精力去觉得是否做这件事。干就完了,讲真话和使用牙线都是很好的习惯

+

Promptness is a sign of respect.
+及时回应是表示尊重的一种方式

+

When you are young spend at least 6 months to one year living as poor as you can, owning as little as you possibly can, eating beans and rice in a tiny room or tent, to experience what your “worst” lifestyle might be. That way any time you have to risk something in the future you won’t be afraid of the worst case scenario.
+当你年轻的时候,应该至少花半年到一年的时间,过尽可能穷的日子,拥有尽可能少的身外之物,居陋室而箪食瓢饮,体验你可能会经历的最穷困潦倒的生活。这样,在未来任何时候,你都不用担心最坏的情况

+

Trust me: There is no “them”.
+相信我,没有「他们」

+
+

个人理解,KK 大叔想表达的意思应该是:太阳底下无新事,每个人都是历史的参与者

+
+

The more you are interested in others, the more interesting they find you. To be interesting, be interested.
+你越有兴趣了解别人,别人就会发现你越有趣,要成为有趣的人,先要对别人感兴趣

+

Optimize your generosity. No one on their deathbed has ever regretted giving too much away.
+常行慷慨之事,没有人会在死的时候后悔给予的太多

+

To make something good, just do it. To make something great, just re-do it, re-do it, re-do it. The secret to making fine things is in remaking them.
+想要做好一件事,干就完了。想要做一件值得称赞的事情,那就重做一遍,重做一遍,再重做一遍。制造好东西的秘诀在于不断的重做

+

The Golden Rule will never fail you. It is the foundation of all other virtues.
+金科玉律永远不会让你失望,它是所有其他美德的基础

+

If you are looking for something in your house, and you finally find it, when you’re done with it, don’t put it back where you found it. Put it back where you first looked for it.
+如果你正在你的房子里寻找什么东西,那么用完后不要放回你找到它的地方,而是放到你最初找它的地方

+

Saving money and investing money are both good habits. Small amounts of money invested regularly for many decades without deliberation is one path to wealth.
+存钱和投资是好习惯。几十年如一日的定期进行小额投资(定投),是一条致富之路

+

To make mistakes is human. To own your mistakes is divine. Nothing elevates a person higher than quickly admitting and taking personal responsibility for the mistakes you make and then fixing them fairly. If you mess up, fess up. It’s astounding how powerful this ownership is.
+犯错是人之常情,承认错误是神圣的。认错并勇于担责,再认真弥补过错,没有什么比这更可贵了。是自己搞砸的就勇于承担,这反而能彰显你的强大

+

Never get involved in a land war in Asia.
+永远不要在亚洲陷入地面战争

+
+

KK 大叔这句话没读懂

+
+

You can obsess about serving your customers/audience/clients, or you can obsess about beating the competition. Both work, but of the two, obsessing about your customers will take you further.
+你可以专注于你的顾客、听众或客户,也可以沉迷于在竞争中获胜,这两种方法都行之有效,但是专注于服务你的客户会让你走的更远

+

Show up. Keep showing up. Somebody successful said: 99% of success is just showing up.
+在场,坚持在场,某个成功人士说过:99% 的成功只不过是因为在场

+

Separate the processes of creation from improving. You can’t write and edit, or sculpt and polish, or make and analyze at the same time. If you do, the editor stops the creator. While you invent, don’t select. While you sketch, don’t inspect. While you write the first draft, don’t reflect. At the start, the creator mind must be unleashed from judgement.
+将创造过程与改进过程分开,你不可能在写做的同时进行编辑,也不可能在凿刻的同时进行打磨,更不可能在制造的同时进行分析。如果你这么做,求善之心就会打断创造之意;创新时就要忘掉已有方案;勾勒草图时就不能太着眼于细处;写作时,先打草稿而不要去抠细节。在新事物之初,创意的思想必须得到无拘无束的释放

+

If you are not falling down occasionally, you are just coasting.
+如果你从未跌倒过,那么你也就从未努力过

+

Perhaps the most counter-intuitive truth of the universe is that the more you give to others, the more you’ll get. Understanding this is the beginning of wisdom.
+也许宇宙中最违反直觉的真理就是,你给予他人越多,你收获的就越多,这是智慧的起点

+

Friends are better than money. Almost anything money can do, friends can do better. In so many ways a friend with a boat is better than owning a boat.
+朋友胜过金钱。金钱几乎可以做任何事情,但朋友可以做得更好。很多时候,自己有条船不如有个有船的朋友

+

This is true: It’s hard to cheat an honest man.
+相信我,你很难欺骗一个诚实的人

+

When an object is lost, 95% of the time it is hiding within arm’s reach of where it was last seen. Search in all possible locations in that radius and you’ll find it.
+当一件物品丢失时,95% 的情况下,它都藏在人们最后一次看到它时触手可及的地方。在这个半径范围内搜索所有可能的地点,你就能找到它

+

You are what you do. Not what you say, not what you believe, not how you vote, but what you spend your time on.
+你做什么就是什么。不是你说什么,不是你相信什么,更不是你支持什么,而是你把时间花在了什么上

+

If you lose or forget to bring a cable, adapter or charger, check with your hotel. Most hotels now have a drawer full of cables, adapters and chargers others have left behind, and probably have the one you are missing. You can often claim it after borrowing it.
+如果你遗失或忘记带电缆、适配器或充电器,不妨去问问你的酒店。大多数酒店都会有满满一抽屉的电源线、适配器和充电器,这些东西都是别人留下的,没准儿其中就有你的,酒店也并不介意你借用后随身带走

+

Hatred is a curse that does not affect the hated. It only poisons the hater. Release a grudge as if it was a poison.
+仇恨是一种诅咒,但它不会影响被仇恨的人。它只会毒害仇恨者,把你的怨恨当作毒药一样丢掉吧

+

There is no limit on better. Talent is distributed unfairly, but there is no limit on how much we can improve what we start with.
+没有最好,只有更好。个人的天分有高有低,但不论高低,自身的提升都永无止境

+

Be prepared: When you are 90% done any large project (a house, a film, an event, an app) the rest of the myriad details will take a second 90% to complete.
+任何一项大工程(修房子、拍电影、开发 app)完成度为 90% 的时候,你都要做好心理准备:剩余的大量细节工作同样需要 90% 的时间来完成

+

When you die you take absolutely nothing with you except your reputation.
+当你死的时候,除了你的名誉,你什么都无法带走

+

Before you are old, attend as many funerals as you can bear, and listen. Nobody talks about the departed’s achievements. The only thing people will remember is what kind of person you were while you were achieving.
+在你年老之前,尽可能多地参加葬礼并听听别人的谈话,没有人会谈论逝者的成就,人们能记住的只有逝者在成功时是什么样的人

+

For every dollar you spend purchasing something substantial, expect to pay a dollar in repairs, maintenance, or disposal by the end of its life.
+你每花一美元在实体店购买一件东西,将来都要再花一元钱去维修、保养,或是在它报废后处理掉它

+

Anything real begins with the fiction of what could be. Imagination is therefore the most potent force in the universe, and a skill you can get better at. It’s the one skill in life that benefits from ignoring what everyone else knows.
+任何真实的东西都来源于虚构的想法,想象是宇宙中最强大的力量,也是你可以做的更好的一种能力,生命中可以因不知众人所知而获利

+

When crisis and disaster strike, don’t waste them. No problems, no progress.
+当危机和灾难来临时,不要错过他们,没有问题就没有进步

+

On vacation go to the most remote place on your itinerary first, bypassing the cities. You’ll maximize the shock of otherness in the remote, and then later you’ll welcome the familiar comforts of a city on the way back.
+度假时,先绕过城市去行程中最偏远的地方。这样你就能最大程度地体验到异域风情带给你的冲击,而在返程的路上,又可以享受熟悉的城市所带给你的舒适

+

When you get an invitation to do something in the future, ask yourself: would you accept this if it was scheduled for tomorrow? Not too many promises will pass that immediacy filter.
+当你被邀请在未来的某个时间点做某件事情时,问问自己:如果是明天,你会接受邀请吗?绝大多数邀约都经不住这种迫切性检验

+

Don’t say anything about someone in email you would not be comfortable saying to them directly, because eventually they will read it.
+如果一些话你不能当面对某人说出口,那么就不要在邮件中对他评头论足,因为他们最终会看到邮件

+

If you desperately need a job, you are just another problem for a boss; if you can solve many of the problems the boss has right now, you are hired. To be hired, think like your boss.
+如果你只是迫切需要一份工作,那你只是老板的另一个问题;如果你能解决许多老板眼下的问题,那你自然能得到这份工作。要想得到一份工作,就要像老板一样去思考

+

Art is in what you leave out.
+艺术藏身于你遗忘的地方

+

Acquiring things will rarely bring you deep satisfaction. But acquiring experiences will.
+获得物品很少能给你带来深刻的满足感,但是经验却能做到

+

Rule of 7 in research. You can find out anything if you are willing to go seven levels. If the first source you ask doesn’t know, ask them who you should ask next, and so on down the line. If you are willing to go to the 7th source, you’ll almost always get your answer.
+研究的「数字 7 原则」。当你愿意就一个问题深入七层时,总能找到你想要的答案。如果你问的第一层人不知道,那么就问问他们应该去找谁,如此追索下去,你几乎总能得到你的答案

+

How to apologize: Quickly, specifically, sincerely.
+如何道歉:迅速、具体、真诚

+

Don’t ever respond to a solicitation or a proposal on the phone. The urgency is a disguise.
+永远不要在电话上面答应一个请求或提议,所谓的急迫不过是一种假象

+

When someone is nasty, rude, hateful, or mean with you, pretend they have a disease. That makes it easier to have empathy toward them which can soften the conflict.
+当有人对你粗鄙、无礼、刻薄,甚至是下流时,当他们有病就好了,这使得我们更容易对他们产生同情心,进而缓和冲突

+

Eliminating clutter makes room for your true treasures.
+清理杂物,为真正重要的东西腾出空间

+

You really don’t want to be famous. Read the biography of any famous person.
+你绝对不会想出名,不信的话可以随便找本名人传记读读

+

Experience is overrated. When hiring, hire for aptitude, train for skills. Most really amazing or great things are done by people doing them for the first time.
+经验往往被高估了,招聘时应该多看资质,技能是可以培训的。许多令人惊奇和赞叹的事情,都是新手做出来的

+

A vacation + a disaster = an adventure.
+度假 + 灾难 = 冒险

+

Buying tools: Start by buying the absolute cheapest tools you can find. Upgrade the ones you use a lot. If you wind up using some tool for a job, buy the very best you can afford.
+购买工具:从最便宜的开始,升级那些使用频次高的。如果你的工具是用于工作,那么买你能买得起的最好的

+

Learn how to take a 20-minute power nap without embarrassment.
+学会毫不尴尬的打 20 分钟小盹儿

+

Following your bliss is a recipe for paralysis if you don’t know what you are passionate about. A better motto for most youth is “master something, anything”. Through mastery of one thing, you can drift towards extensions of that mastery that bring you more joy, and eventually discover where your bliss is.
+如果你不知道自己热爱什么,追寻心之所向往往会带你误入歧途,对年轻人来说,更好的格言是:master something, anything,在精通一件事的过程中,你可以顺着带给你更多快乐的方向继续深入,并最终发现你热爱的东西

+

I’m positive that in 100 years much of what I take to be true today will be proved to be wrong, maybe even embarrassingly wrong, and I try really hard to identify what it is that I am wrong about today.
+我敢肯定,我今天认为正确的许多东西在 100 年后将被证明是错误的,甚至可能是令人尴尬的错误。而我非常努力在做的事情,就是去识别我对今天的错误认知

+

Over the long term, the future is decided by optimists. To be an optimist you don’t have to ignore all the many problems we create; you just have to imagine improving our capacity to solve problems.
+从长远来说,未来由乐观主义者决定。作为一个乐观主义者并非要对我们制造的问题视而不见,而是要想象如何提升我们解决问题的能力

+

The universe is conspiring behind your back to make you a success. This will be much easier to do if you embrace this pronoia.
+整个宇宙在背后密谋让你成功,要相信,天助人愿

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/_4KYecN7b/index.html b/_4KYecN7b/index.html new file mode 100644 index 00000000..a4b3004f --- /dev/null +++ b/_4KYecN7b/index.html @@ -0,0 +1,751 @@ + + + + + + + + 牛客网 NC632 牛牛摆木棒、POJ 1037 美丽的栅栏题解 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 牛客网 NC632 牛牛摆木棒、POJ 1037 美丽的栅栏题解 +

+ + +
+ +
+
+

参考内容:

+

OI题解 - A decorative fence[POJ 1037]

+

poj1037(dP+排列计数)

+
+

本文首发于牛客网:题解 | #牛牛摆木棒#

+

题目

+

牛客网 NC632 牛牛摆木棒POJ1037-A decorative fence(美丽的栅栏)

+

描述

+

有n个木棒,长度为1到n,给定了一个摆放规则。规则是这样的:对于第 i (2in1)(2 \leq i \leq n-1) 个木棒 aia_i(ai>ai1(a_i > a_{i-1} && ai>ai+1)a_i > a_{i+1})(ai<ai1(a_i < a_{i-1} && ai<ai+1)a_i < a_{i+1})。求满足规则的从小到大的第k个排列是什么呢。

+

对于两个排列 s 和 t:如果存在 j 有任意 i<ji<j 使得 si==tis_i == t_isj<tjs_j < t_j,视为排列 s 小于排列 t。

+

示例

+
输入:3,3
+返回值:[2,3,1]
+说明:第一小的排列为:[ 1 , 3 , 2 ]
+     第二小的排列为:[ 2 , 1 , 3 ]
+     第三小的排列为:[ 2 , 3 , 1 ]
+     第四小的排列为:[ 3 , 1 , 2 ]
+     所以答案为:[ 2 , 3 , 1 ]
+
+

备注

+

(1n20,1k(n1)!)(1 \leq n \leq 20, 1 \leq k \leq (n-1)!)

+

题意

+

该问题让我们求:n 的字典序排列中第 k 个波浪形的排列。什么是波浪形排列呢?即对排列中任意一个数字(除开第一个和最后一个)aia_i,只能 aia_iai1a_{i-1}ai+1a_{i+1} 都小或者都大。比如 2 1 31 3 2是波浪形排列,但1 2 3就不是波浪形排列。

+

DFS 枚举

+

最容易想到的解决方案是把 n 的所有排列按字典序列出来,然后再逐一检查是否是波浪形排列,直接取出第 k 个波浪形排列即可。

+

我们以 3 的全排列为例画出如下树形图,非常容易的就能发现只要对这棵树进行深度优先遍历,就能够按字典序得到所有排列。但并不是所有排列都满足波浪形这个条件,所以我们每得到一个排列都需要检查该排列是否为波浪形,直到检查到第 k 个排列为止,返回该排列即可。

+
+
class Solution {
+public:
+
+    // 记录当前已经有多少个波浪形排列
+    long long count = 0;
+    // 记录最后的结果
+    vector<int> res;
+    /**
+     * 
+     * @param n int整型 木棒的个数
+     * @param k long长整型 第k个排列
+     * @return int整型vector
+     */
+    vector<int> stick(int n, long long k) {
+        // 用于标记当前考虑的数字是否已经被选择
+        bool visited[25] = {false};
+        // 用于记录已经选了哪些数
+        vector<int> path;
+        dfs(n, 0, path, visited, k);
+        return res;
+    }
+
+    /**
+     * 
+     * @param n 可选的数字范围
+     * @param deep 递归到第几层了
+     * @param path 已经选的数字
+     * @param visited 记录哪些数已经被选了
+     * @param k 是否已经到第 k 个波浪形排列了
+     */
+    void dfs(int n, int deep, vector<int> path, bool visited[], long long k) {
+        // 递归层数和范围相等,说明所有的数字都考虑完了,因此得到一个排列
+        if(deep == n){
+            // 判断该排列是否为波浪形排列
+            bool flag = true;
+            for(int i = 1; i < n-1; i++){
+                if((path[i] > path[i-1] && path[i] < path[i+1]) ||
+                   (path[i] < path[i-1] && path[i] > path[i+1])){
+                    flag = false;
+                    break;
+                }
+            }
+            // 是波浪形排列,则统计一次
+            if(flag) {
+                count++;
+            }
+            // 判断是否已经到第 k 个排列
+            if(count == k) {
+                // 如果返回结果还没有被赋值,则将该排列赋值给 res
+                // 因为我们使用的是递归,所以 count==k 会被满足多次
+                // 只有第一次满足时才是真正的 k 值,所以必须判断 res 是否为空
+                // 如果不判空,则程序记录的不是正确结果
+                if(res.empty()){
+                    res = path;
+                }
+                // 到第 k 个波浪形排列了,递归返回
+                return ;
+            }
+            // 没有可以选择的数字了,回溯
+            return ;
+        }
+        // 还没有得出一个排列,则继续挑选数字组成排列
+        for(int i = 1; i <= n; i++) {
+            // 如果该数字已经被选择了,则终止本次循环
+            if(visited[i]){
+                continue;
+            }
+            // 选中当前数字加入到排列中
+            path.push_back(i);
+            visited[i] = true;
+            // 下一次递归所传的值不变,只有递归层数需要 +1
+            dfs(n, deep+1, path, visited, k);
+            // 回溯,需要撤销前面的操作
+            path.pop_back();
+            visited[i] = false;
+        }
+    }
+};
+
+

在 C++ 的 algorithm 库中已经提供了一个全排列方法 next_permutation。按照STL文档的描述,next_permutation 函数将按字母表顺序生成给定序列的下一个较大的序列,直到整个序列为减序为止。因此我们可以偷个懒直接使用现有的函数。

+
class Solution {
+public:
+    /**
+     * 
+     * @param n int整型 木棒的个数
+     * @param k long长整型 第k个排列
+     * @return int整型vector
+     */
+    vector<int> stick(int n, long long k) {
+        vector<int> res;
+        // 记录当前已经有多少个波浪形排列
+        long long count = 0;
+        // 构造初始化排列
+        for(int i = 1; i <= n; i++) {
+            res.push_back(i);
+        }
+        do {
+            // 判断当前排列是否为波浪形排列
+            bool flag = true;
+            for(int i = 1; i < n-1; i++) {
+                if((res[i] > res[i-1] && res[i] < res[i+1]) ||
+                   (res[i] < res[i-1] && res[i] > res[i+1])){
+                    flag = false;
+                    break;
+                }
+            }
+            if(flag) {
+                count++;
+            }
+            if(count == k) {
+                break;
+            }
+        } while (next_permutation(res.begin(), res.end()));
+        return res;
+    }
+};
+
+

复杂度分析

+
深度优先遍历
+

我们来看一下这个深度优先遍历的时间复杂度分析,该算法的时间复杂度主要由递归树的结点个数决定。因为程序在叶子结点和非叶子结点的行为时不一样的,所以我们先计算非叶子结点的个数,我们一层一层的去计算它。

+

第 1 层因为只有一个空列表,所以我们不考虑它;
+第 2 层表示的意思是从 n 个数中找出 1 个数,即 An1A_n^1
+第 3 层表示的意思是从 n 个数中找出 2 个数,即 An2A_n^2
+以此类推,全部非叶子结点的总数为:
+An1+An2+AnnA_n^1 + A_n^2 + \cdots A_n^n

+

=n!(n1)!+n!(n2)!++n!= \frac{n!}{(n-1)!} + \frac{n!}{(n-2)!} + \cdots + n!

+

=n!(1(n1)!+1(n2)!++1)= n!\left(\frac{1}{(n-1)!} + \frac{1}{(n-2)!} + \cdots + 1\right)

+

n!(1+12+14++12n1)\leq n!\left(1 + \frac{1}{2} + \frac{1}{4} + \cdots + \frac{1}{2^{n-1}}\right)

+

=n!×2112n)= n! \times 2(1-\frac{1}{2^{n}})

+

<2n!< 2n!

+

每个非叶子结点都在内部循环了 n 次,所以非叶子结点的时间复杂度为 O(2n×n!)O(2n \times n!),去除系数后得到 O(n×n!)O(n \times n!)

+

最后一层叶子结点的个数就是 n!n! 个,但是我们对每个叶子结点都做了一次判断,因此叶子结点的时间复杂度依然是 O(n×n!)O(n \times n!)

+

该问题的 k 控制了遍历的次数,最好情况即 O(n!)O(n!),最差即 O(n×n!)O(n \times n!),平均一下也不过只加了个系数,因此总的时间复杂度为 O(n×n!)O(n \times n!)

+

递归树的深度为 n,需要 O(n)O(n) 的空间;程序运行过程中保存了问题的最终答案,需要 O(n)O(n) 的空间,总共需要 O(2n)O(2n) 的空间,因此该算法的空间复杂度为 O(n)O(n)

+

动态规划

+

上述算法在运行过程中会超时,究其原因就是不论测试数据要求我们求第几个波浪形排列,我们都老老实实的从第一个开始数,当数据比较大时就会出现超时的情况。那么有没有办法能够减少一些不必要的过程呢?比如测试数据要求第 100 个波浪形排列,很明显前面 80 个排列肯定不满足情况,我们能否舍弃一部分搜索直接从第 80 个甚至第 90 个开始呢?

+

我们先不考虑波浪形排列这个条件,如果是求第 k 个全排列的话是非常容易就能算出来的。还是以1 2 3的全排列为例,假设现在要求第 5 个全排列,可以发现只要第一个数确定了,排列数就由剩下数的排列方案决定,以1打头的排列有两个,以2打头的排列也有两个,而现在要求的是第 5 个排列,所以肯定不是以12打头的,这样我们就能直接跳过大部分不合法的排列,节省了时间。

+

仔细想想发现理想是比较丰满,上述方法的问题在于无法确定前面跳过的那部分里面究竟有多少个波浪形排列,因此这种直接计算的方法行不通。但是这个思想我们是可以借用一下的,那我们把一部分数据计算出来,尝试一下能不能找到规律。

+

当 n 为 1 时,总共有 1 个波浪形排列,1 打头的有 1 个;
+当 n 为 2 时,总共有 2 个波浪形排列,1 打头的有 1 个;
+当 n 为 3 时,总共有 4 个波浪形排列,1 打头的有 1 个;
+当 n 为 4 时,总共有 10 个波浪形排列,1 打头的有 2 个;
+当 n 为 5 时,总共有 32 个波浪形排列,1 打头的有 5 个;
+当 n 为 6 时,总共有 122 个波浪形排列,1 打头的有 16 个;

+

列出来了 6 组数据都没有发现规律,这种方式基本得战略性放弃了。我们设置 A[i] 为 i 根木棒所组成的合法方案数,列数据找规律其实就是尝试找到 A[i] 和 A[i-1] 的规律,比如选定了某根木棒 x 作为第 1 根木棒的情况下,则剩下 i-1 根木棒的合法方案数为 A[i-1]。问题在于并不是这 A[i-1] 中每一种方案都能和 x 形成一种新的合法方案。

+

我们把第 1 根木棒比第 2 根木棒长的方案称为 W 方案,第 1 根木棒比第 2 根木棒短的方案称为 M 方案。A[i-1] 中方案中只有第 1 根木棒比 x 要长的 W 方案,以及第 1 根木棒比 x 要短的 M 方案,才能进行组合构成 A[i] 中的合法方案。

+

因此我们可以设A[i] = 0,先枚举 x,然后针对每一个 x 枚举它后面那根木棒 y,如果y > xy < x同理)则有:A[i] = A[i] + 以 y 打头的 W 方案数,但是以 y 打头的 W 方案数,又和 y 的长短有关,因此只能继续将描述方式继续细化了。

+

设 B[i][k] 是 A[i] 中以第 k 短的木棒打头的方案数,则有:

+

A[i]=k=1iB[i][k]A[i] = \sum_{k=1}^i B[i][k]

+

B[i][k]=j=ki1B[i1][j](W)+n=1k1B[i1][n](M)B[i][k] = \sum_{j=k}^{i-1} B[i-1][j](W)+ \sum_{n=1}^{k-1} B[i-1][n](M)

+

公式中(W) 和 (M) 分别表示 W 方案和 M 方案,发现还是无法找出推导关系。设 C[i][k][0] 为 B[i][k] 中的 W 方案数,C[i][k][1] 为 B[i][k] 中的 M 方案数那么则有:

+

B[i][k]=C[i][k][0]+C[i][k][1]B[i][k] = C[i][k][0] + C[i][k][1]

+

C[i][k][1]=j=ki1C[i1][j][0]C[i][k][1] = \sum_{j=k}^{i-1} C[i-1][j][0]

+

C[i][k][0]=n=1k1C[i1][n][1]C[i][k][0] = \sum_{n=1}^{k-1} C[i-1][n][1]

+

至此状态转移方程就出来了,初始条件为:C[1][1][0]=C[1][1][1] = 1,下面就可以开始写代码了。

+
class Solution {
+public:
+    /**
+     * 
+     * @param n int整型 木棒的个数
+     * @param k long长整型 第k个排列
+     * @return int整型vector
+     */
+    vector<int> stick(int n, long long s) {
+        long long dp[21][21][2];
+        memset(dp,0,sizeof(dp));
+        dp[1][1][0] = dp[1][1][1] = 1;
+        for (int i = 2; i <= n; i++){
+            // 枚举第一根木棒的长度
+            for (int k = 1; k <= i; k++){
+                // W 方案枚举第二根木棒的长度
+                for (int m = k; m < i; m++){
+                    dp[i][k][0] += dp[i-1][m][1];
+                }
+                // M 方案枚举第二根木棒的长度
+                for (int m = 1; m <= k-1; m++){
+                    dp[i][k][1] += dp[i-1][m][0];
+                }
+            }
+        }
+        // 标记是否已经使用
+        bool visited[21] = {false};
+        // 保存结果的排列
+        int a[21];
+        // 逐一确定第 i 位
+        for(int i = 1; i <= n; i++) {
+            int k = 0;
+            // 假设第 i 放 j
+            for(int j=1;j<=n;j++) {
+                long long tmp = s;
+                // 已经使用过的数不能再使用了
+                if(!visited[j]) {
+                    // j 是没有使用过的木棒中第 k 短的
+                    k++;
+                    if(i == 1) {
+                        // 确定第一根木棒的长度
+                        tmp -= dp[n][k][0] + dp[n][k][1];
+                    } else if(j < a[i-1] && (i==2 || a[i-2]<a[i-1])) {
+                        // W 类型
+                        tmp -= dp[n-i+1][k][0];
+                    } else if(j > a[i-1] && (i==2 || a[i-2]>a[i-1])) {
+                        // M 类型
+                        tmp -= dp[n-i+1][k][1];
+                    }
+                    if(tmp <= 0) { 
+                        visited[j]=true;
+                        a[i]=j; // 第 i 位为 j
+                        break;
+                    }
+                }
+                s = tmp;
+            }
+        }
+        // 将结果转换为指定格式
+        vector<int> res;
+        for(int i = 1; i <= n; i++) {
+            res.push_back(a[i]);
+        }
+        return res;
+    }
+};
+
+

复杂度分析

+

最开始初始化dp数组时用了 O(2n2)O(2n^2) 的时间,随后填写dp数组花的时间为 O(n3)O(n^3),计算最终答案的时间为 O(n2)O(n^2),将结果转为指定格式的时间为 O(n)O(n),所以该算法的时间复杂度为 O(n3)O(n^3)

+

dp数组占用了 O(2n2)O(2n^2) 的空间,标记数组visited、保存结果的数组a,以及最终转换为指定格式的path向量,各占用了 O(n)O(n) 的空间,取最大值即该算法的空间复杂度为 O(2n2)O(2n^2),去掉系数得到最终空间复杂度 O(n2)O(n^2)

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/_7KBEW1cq7/index.html b/_7KBEW1cq7/index.html new file mode 100644 index 00000000..f4062ac6 --- /dev/null +++ b/_7KBEW1cq7/index.html @@ -0,0 +1,801 @@ + + + + + + + + Python | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + Python +
+ + +
+

+ + 使用订阅号实现微信公众号历史文章爬虫 + +

+ +
+ + + + +
+ +
+ 微信公众号已经成为生活的一部分了,虽然里面有很多作者只是为了蹭热点,撩读者的 G 点,自己从中获得一些收益;但是不乏好的订阅号,像刘大的码农翻身、Fenng的小道消息、曹大的caoz的梦呓等订阅号非常值得阅读。 +平时有时候看到一些好的公众号,也会不自觉去查看该公众号的历史文章,然而每次都看不完,下一次再从微信里面打开历史文章,又需要从头翻起。而且对于写了很多年的大号,每次还翻不到底。有一些平台提供了相关的服务,但是得收几十块钱的费用,倒不是缺几十块钱,主要是觉得这种没必要花的钱不值得去浪费。 +网上搜如何爬微信公众号历史文章,大致给了三种思路,第一是使用搜狗微信搜索文章,但是好像每次能搜到的不多;第二是使用抓包工具;第三种是使用个人订阅号进行抓取。 +简单来说就是使用程序来模拟人的操作,抓取公众号历史文章。首先登录微信公众号个人平台,期间需要管理员扫码才能登录成功。 +def __open_gzh(self): + self.driver.get(BASE_URL) + self.driver.maximize_window() + username_element = self.driver.find_element_by_name(&quot;account&quot;) + password_element = self.driver.find_element_by_name(&quot;password&quot;) + login_btn = self.driver.find_element_by_class_name(&quot;btn_login&quot;) + username_element.send_keys(USERNAME) + password_element.send_keys(PASSWORD) + login_btn.click() + WebDriverWait(driver=self.driver, timeout=200).until( + ec.url_contains(&quot;cgi-bin/home?t=home/index&quot;) + ) + # 一定要设置这一步,不然公众平台菜单栏不会自动展开 + self.driver.maximize_window() + +进入微信公众平台首页后,点击素材管理,然后点击新建图文素材,就会进入到文章写作页面,此时前面打开的微信公众平台首页就不需要了,可以将其关闭。 + +def __open_write_page(self): + management = self.driver.find_element_by_class_name(&quot;weui-desktop-menu_management&quot;) + material_manage = management.find_element_by_css_selector(&quot;a[title='素材管理']&quot;) + material_manage.click() + new_material = self.driver.find_element_by_class_name(&quot;weui-desktop-btn_main&quot;) + new_material.click() + # 关闭公众平台首页 + handles = self.driver.window_handles + self.driver.close() + self.driver.switch_to_window(handles[1]) + +在文章写作页面的工具栏上面有一个超链接按钮,点击超链接即会弹出超链接编辑框,选择查找文章,输入自己喜欢的公众号进行查找,一般第一个就是自己想要的结果,点击对应的公众号,该公众号所有的文章就会通过列表的形式展现出来。 + + + +def __open_official_list(self): + # 超链接 + link_click = self.driver.find_element_by_class_name(&quot;edui-for-link&quot;) + link_click.click() + time.sleep(3) + # 查找文章 + radio = self.driver.find_element_by_class_name(&quot;frm_vertical_lh&quot;).find_elements_by_tag_name(&quot;label&quot;)[1] + radio.click() + # 输入查找关键字 + search_input = self.driver.find_element_by_class_name(&quot;js_acc_search_input&quot;) + search_input.send_keys(OFFICIAL_ACCOUNT) + search_btn = self.driver.find_element_by_class_name(&quot;js_acc_search_btn&quot;) + search_btn.click() + # 等待5秒,待公众号列表加载完毕 + time.sleep(5) + result_list = self.driver.find_element_by_class_name(&quot;js_acc_list&quot;).find_elements_by_tag_name(&quot;div&quot;) + result_list[0].click() + +文章列表已经展现出来了,直接抓取每条文章超链接的信息即可,每抓取完一页就进入下一页,继续抓取文章列表信息,直到所有文章信息都抓取完毕。 + +def __get_article_list(self): + # 等待文章列表加载 + time.sleep(5) + total_page = self.driver.find_element_by_class_name(&quot;search_article_result&quot;)\ + .find_element_by_class_name(&quot;js_article_pagebar&quot;).find_element_by_class_name(&quot;page_nav_area&quot;)\ + .find_element_by_class_name(&quot;page_num&quot;)\ + .find_elements_by_tag_name(&quot;label&quot;)[1].text + total_page = int(total_page) + articles = [] + for i in range(0, total_page-1): + time.sleep(5) + next_page = self.driver.find_element_by_class_name(&quot;search_article_result&quot;)\ + .find_element_by_class_name(&quot;js_article_pagebar&quot;).find_element_by_class_name(&quot;pagination&quot;)\ + .find_element_by_class_name(&quot;page_nav_area&quot;).find_element_by_class_name(&quot;page_next&quot;) + article_list = self.driver.find_element_by_class_name(&quot;js_article_list&quot;)\ + .find_element_by_class_name(&quot; my_link_list&quot;).find_elements_by_tag_name(&quot;li&quot;) + for article in article_list: + article_info = { + &quot;date&quot;: article.find_element_by_class_name(&quot;date&quot;).text, + &quot;title&quot;: article.find_element_by_tag_name(&quot;a&quot;).text, + &quot;link&quot;: article.find_element_by_tag_name(&quot;a&quot;).get_attribute(&quot;href&quot;) + } + articles.append(article_info) + next_page.click() + return articles + +至此,微信公众号历史文章的爬虫已经实现,其实整个过程只不过是用程序来模拟的了人类的操作。需要注意的是,程序不能设置太快,因为微信做了相关限制,所以设太快会在一段时间内无法使用文章查找功能;另外一点是使用选择器选择页面元素的时候,会有一些坑,而且我发现不同账号登录,有很少部分的页面元素虽然直观上是一样的,但是它的 html 代码有细微的差别。 +这个小程序会用到selenium库,和chromedriver,前者直接pip install即可,后者自行下载;另外你还需要一个订阅号才行,本文只实现了关键的文章信息抓取,并没有进行文章信息的持久化存储,完整代码在这里。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 跨域请求是什么?如何解决? + +

+ +
+ + + + +
+ +
+ +参考内容: +JavaScript: Use a Web Proxy for Cross-Domain XMLHttpRequest Calls +别慌,不就是跨域么! +跨域资源共享 CORS 详解 +AJAX请求和跨域请求详解(原生JS、Jquery) +JavaScript跨域总结与解决办法 + + +刚毕业入职,大部分时间还在培训,中间有一段时间的空闲时间,就学习了下 Angular,在学校都是编写的单体应用,所有代码都放在同一个工程下面,到公司使用的是前后端分离了,虽然后端程序也是我自己写的,但是有一些数据是从公司现有接口去拿的,然后就遇到让我纠结了两小时的跨域请求问题,在这里做一个简单的总结输出。 +什么是跨域请求 +跨域请求问题是浏览器的同源策略造成的,该策略不允许执行其它网站的脚本,是浏览器施加的安全限制。什么是同源?最初是指网页 A 设置的 Cookie 不能被网页 B 打开,包括三个相同:协议、域名、端口。这个同源是从 URL 判断的,不是从 IP 判断的,如果同一个服务器对应连个域名,这两个域名是不同源的。 +http://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 非跨域 + +http://www.nealyang.cn/index.html 调用 http://www.neal.cn/server.php 跨域,主域不同 + +http://abc.nealyang.cn/index.html 调用 http://def.neal.cn/server.php 跨域,子域名不同 + +http://www.nealyang.cn:8080/index.html 调用 http://www.nealyang.cn/server.php 跨域,端口不同 + +https://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 跨域,协议不同 + +localhost 调用 127.0.0.1 跨域 + +同源政策的目的是为了保护用户信息的安全,防止恶意网站窃取数据,随着互联网的发展,同源政策更加严格了,下面三种行为都会受到限制。 +(1) Cookie、LocalStorage 和 IndexDB 无法读取。 +(2) DOM 无法获得。 +(3) AJAX 请求不能发送。 + +所有的现代浏览器都对网络连接进行了安全限制,包括 XMLHttpRequest,如果你的 web 应用程序和其使用的数据在同一个服务器,你不会遇到跨域请求问题。但是当你的 web 应用程序和 web 服务数据不在同一个服务器时,就会被浏览器限制连接了。 +常用解决方案 +    对于跨域请求有很多的解决方案,最常用的解决方案是在你的 web 服务器上面设置代理。在设置代理之前就通过,应用程序直接去请求另一个服务器下的数据;设置代理之后,应用程序从自己的 web 服务器中请求数据,再由代理去请求数据,这样 web 服务器拿到数据之后返回给应用程序即可。从浏览器角度看,就是从同一个服务器拿的数据,并没有进行跨域请求。 + +通俗易懂的说,你家的宠物狗不会吃别家的食物,因为它担心别人的食物会把自己给药死,所以你的狗狗只管找你要食物,你是它的主人,它绝对相信你,而你可以鉴别别人给的食物是不是安全的。类比,小狗就是浏览器,你就是代理。 +Angular 中的解决办法 +上面所说的解决方案在开发过程中不方便操作,每新发一个接口都到服务器中去配置一下,不仅麻烦而且效率低下。首先说一下在 Angular 中一个人比较常用的解决方法,默认你在使用angular-cli构建你的项目,我们可以创建一个代理配置文件proxy.conf.json(假设你的后端服务的访问地址为10.121.163.10:8080),代理配置文件如下: +{ + &quot;/api&quot;: { + &quot;target&quot;: &quot;http://10.121.163.10:8080&quot;, + &quot;secure&quot;: false + } +} + +然后修改package.json文件中的启动命令为&quot;start&quot;: &quot;ng serve --proxy-config proxy.conf.json&quot;,启动项目时使用npm start即可解决跨域请求问题。 +上述解决方案仅在开发时使用,你当然可以使用 tomcat、nginx 配置代理,但是这很麻烦,需要打包代码部署,为了保证效率,我们想写完了立刻测试,同时也不想麻烦做后端的同学,在项目发布时,应该把代理配置到服务器中去;修改启动命令也不是必须的,你也可以选择每次使用 ng serve --proxy-config proxy.conf.json命令启动项目;示例代理配置文件内容可以有更多的属性,可以通过网络查阅相关资料。 +后端解决办法 +我的后端是是用 tornado 实现的,然后我又写了一个单独的页面用于在大屏幕上展示相关数据,没有用 Angular 了,要通过 AJAX请求数据,又怎么解决跨域请求问题呢?这时就需要设置请求头了,让后端允许跨域请求。 +这时需要了解一下简单请求和非简单请求了,简单请求就是只发送一次请求的请求;非简单请求会发送数据之前先发一次请求做预检,通过预检后才能再发送一次请求用于数据传输。 +更清晰区别,满足下列两大条件的属于简单请求,而非简单请求就是请求方法为PUT或DELETE,或者 Content-Type字段是application/json的请求。 + +1.请求方法为 GET、POST、HEAD之一 +2.HTTP头信息不超出字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type,并且 Content-Type 的值仅限于 application/x-www-form-urlencoded、multipart/form-data、text/plain。 + +对于简单请求,只需要设置一下响应头就可以了。 +class TestHandler(tornado.web.RequestHandler): + def get(self): + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + # 可以把 * 写成具体的域名 + self.write('cors get success') + +对于复杂请求,需要设置预检方法,如下所示: +class CORSHandler(tornado.web.RequestHandler): + # 复杂请求方法put + def put(self): + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + self.write('put success') + # 预检方法设置 + def options(self, *args, **kwargs): + #设置预检方法接收源 + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + #设置预复杂方法自定义请求头h1和h2 + self.set_header('Access-Control-Allow-Headers', &quot;h1,h2&quot;) + #设置允许哪些复杂请求方法 + self.set_header('Access-Control-Allow-Methods', &quot;PUT,DELETE&quot;) + #设置预检缓存时间秒,缓存时间内发送请求无需再预检 + self.set_header('Access-Control-Max-Age', 10) + + +
+ + Read More ~ +
+
+
+ +
+

+ + Scrapy 爬虫框架入门——抓取豆瓣电影 Top250 + +

+ +
+ + + + +
+ +
+ 最好的学习方式就是输入之后再输出,分享一个自己学习scrapy框架的小案例,方便快速的掌握使用scrapy的基本方法。 +本想从零开始写一个用Scrapy爬取教程,但是官方已经有了样例,一想已经有了,还是不写了,尽量分享在网上不太容易找到的东西。自己近期在封闭培训,更文像蜗牛一样,抱歉。 +Scrapy简介 +Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。 +其最初是为了 页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。 + +如果此前对scrapy没有了解,请先查看下面的官方教程链接。 +架构概览:https://docs.pythontab.com/scrapy/scrapy0.24/topics/architecture.html +Scrapy入门教程:https://docs.pythontab.com/scrapy/scrapy0.24/intro/tutorial.html +爬虫教程 +首先,我们看一下豆瓣TOP250页面,发现可以从中提取电影名称、排名、评分、评论人数、导演、年份、地区、类型、电影描述。 + +Item对象是种简单的容器,保存了爬取到得数据。其提供了类似于词典的API以及用于声明可用字段的简单语法。所以可以声明Item为如下形式。 +class DoubanItem(scrapy.Item): + # 排名 + ranking = scrapy.Field() + # 电影名称 + title = scrapy.Field() + # 评分 + score = scrapy.Field() + # 评论人数 + pople_num = scrapy.Field() + # 导演 + director = scrapy.Field() + # 年份 + year = scrapy.Field() + # 地区 + area = scrapy.Field() + # 类型 + clazz = scrapy.Field() + # 电影描述 + decsription = scrapy.Field() + +我们抓取到相应的网页后,需要从网页中提取自己需要的信息,可以使用xpath语法,我使用的是BeautifulSoup网页解析器,经过BeautifulSoup解析的网页,可以直接使用选择器筛选需要的信息。有一些说明写到代码注释里面去了,就不再赘述。 +Chrome 也可以直接复制选择器或者XPath,如下图所示。 + +class douban_spider(Spider): + + count = 1 + + # 爬虫启动命令 + name = 'douban' + + # 头部信息,伪装自己不是爬虫程序 + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36', + } + + # 爬虫启动链接 + def start_requests(self): + url = 'https://movie.douban.com/top250' + yield Request(url, headers=self.headers) + + # 处理爬取的数据 + def parse(self, response): + + print('第', self.count, '页') + self.count += 1 + + item = DoubanItem() + soup = BeautifulSoup(response.text, 'html.parser') + + # 选出电影列表 + movies = soup.select('#content div div.article ol li') + + for movie in movies: + item['title'] = movie.select('.title')[0].text + item['ranking'] = movie.select('em')[0].text + item['score'] = movie.select('.rating_num')[0].text + item['pople_num'] = movie.select('.star span')[3].text + + # 包含导演、年份、地区、类别 + info = movie.select('.bd p')[0].text + director = info.strip().split('\n')[0].split(' ') + yac = info.strip().split('\n')[1].strip().split(' / ') + + item['director'] = director[0].split(': ')[1] + item['year'] = yac[0] + item['area'] = yac[1] + item['clazz'] = yac[2] + + # 电影描述有为空的,所以需要判断 + if len(movie.select('.inq')) is not 0: + item['decsription'] = movie.select('.inq')[0].text + else: + item['decsription'] = 'None' + yield item + + # 下一页: + # 1,可以在页面中找到下一页的地址 + # 2,自己根据url规律构造地址,这里使用的是第二种方法 + next_url = soup.select('.paginator .next a')[0]['href'] + if next_url: + next_url = 'https://movie.douban.com/top250' + next_url + yield Request(next_url, headers=self.headers) + +然后在项目文件夹内打开cmd命令,运行scrapy crawl douban -o movies.csv就会发现提取的信息就写入指定文件了,下面是爬取的结果,效果很理想。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 知识图谱如何构建?——经济责任审计知识图谱构建案例实战 + +

+ +
+ + + + +
+ +
+ +参考: +汉语言处理包 HanLP:https://github.com/hankcs/HanLP +中文文本分类:https://github.com/gaussic/text-classification-cnn-rnn +农业知识图谱:https://github.com/qq547276542/Agriculture_KnowledgeGraph +事实三元组抽取:https://github.com/twjiang/fact_triple_extraction +中文自然语言处理相关资料:https://github.com/mengxiaoxu/Awesome-Chinese-NLP +开放中文实体关系抽取:http://www.docin.com/p-1715877509.html + +自 2012 年 Google 提出“知识图谱”的概念以来,知识图谱就一直是学术研究的重要方向,现在有很多高校、企业都致力于将这项技术应用到医疗、教育、商业等领域,并且已经取得了些许成果。Google 也宣布将以知识图谱为基础,构建下一代智能搜索引擎。 +现在已经可以在谷歌、百度、搜狗等搜索引擎上面看到知识图谱的应用了。比如在 Google 搜索某个关键词时,会在其结果页面的右边显示该关键字的详细信息。在几个常用的搜索引擎中搜索知识时,返回的答案也变得更加精确,比如搜索“汪涵的妻子”,搜索引擎会直接给出答案“杨乐乐”,方便了用户快速精准的获取想要的信息。不过目前的搜索引擎只有少部分搜索问题能达到这种效果。 +关于知识图谱是什么,我想就不用介绍了,这种通过搜索引擎就能轻松得到的结果写在这里有点浪费篇章,并且我对知识图谱的理解也不深,不敢夸夸其谈,只是把自己这一段时间以来的工作做一个总结。 +本文只相当于以经济责任审计这一特定领域构建了一个知识图谱,仅仅是走了一遍流程,当作入门项目,构建过程中参考甚至抄袭了别人的很多方法与代码,末尾都会给出参考的项目等等。 + +上图是我构建经济责任审计知识图谱的流程,看起来很繁琐,但只要静下心看,个人觉得相对还算清晰,箭头都有指向。下面就一步一步进行说明。 +数据获取 +数据获取主要分为两部分数据,一部分是新闻类数据,我把它用作文本分类模型的训练集;另一部分是实体数据,为了方便,我直接把互动百科抓取的词条文件作为实体,省了属性抽取这一环节。 +因为本文构建的是一个经济责任审计领域的知识图谱,所以作为文本分类模型训练集的数据也应该是经济责任审计领域的。这里主要抓取了审计署、纪检委、新浪网的部分新闻。 + +像上面的图一样,新闻类网站一般都有搜索框,为了简单,所以我直接用搜索框搜索“经济责任审计”,然后从搜索结果中抓取新闻数据,即认为是经济责任审计相关的文本。抓取新闻类网站使用了 chrome 模拟用户进行访问。最终获得了 3500 多条新闻文本。 +领域词汇判定 +领域词汇判定,本文构建的不是开放领域的知识图谱,所以需要采用一种方法来判定所抓取的内容是否属于经济责任审计领域。领域词汇本文的方法实际上是领域句子判定,直接使用了大神的项目。CNN-RNN中文文本分类,基于tensorflow。也看到有人通过改进逻辑回归算法,在进行领域词汇的判定。 +我判定领域词汇的逻辑是这样的,一个词语即使是人类也不一定能确定它是否属于经济责任审计领域,但是每个词语都会有它的含义解释对不对,一个词语的解释就是一段话。我用网上的新闻训练出一个判断一段话属于哪个领域的模型,然后把词语的解释放到模型了里面去,如果模型给出的结果是属于经济责任审计领域,那则认为这个词语属于经济责任审计领域。 +实体关系抽取 +知识图谱的基本单位为(实体1,关系,实体2)这样的三元组,实体是直接从互动百科获取的词条,关系由两部分组成,一部分来自 wikidata 所提供的关系,这一部分直接从 wikidata 爬取即可得到,另一部分使用的是基于依存句法分析的开放式中文实体关系抽取,已经在前面的文章发过了。 +知识存储 +有了实体和实体关系,那么把这些数据进行筛选,然后入库,通过直观的页面展示,就可以了。这里使用的数据库是 neo4j,它作为图形数据库,用于知识图谱的存储非常方便。知识的展示使用了别人的项目,仅仅是把里面的数据换掉了而已,感谢大神的无私。 +当然你也可以选择使用关系型数据库,因为我做的经济责任审计知识图谱不够深入,所以做到最后展示的时候,发现其实用我比较熟悉的 MySql 更好,相比 NOSql 我更熟悉关系型数据库,而且 MySql 有更大的社区在维护,它的 Bug 少、性能也更好。 +最后放几张效果图 + + +下面是以“职业”为关系查询条件所得出的结果。 + +总结一下 +只是对几个月工作的梳理,大多数核心代码都改自现有的代码,所有的数据都来自于网络,与知识图谱相关的公开技术较少,我也只是尝试着做了一下,虽然很菜,也可以对大致的技术路线、流程有一个简单的了解,主要工作都是自然语言处理的内容。后期可以利用现在的知识图谱构建智能问答系统,实现从 what 到 why 的转换。 +以下内容更新于 2020 年 3 月。 +在毕业前收到了电子工业出版社和另一家出版社的写书邀请,我和电子工业出版社签订了写书合同,从还未毕业开始断断续续写作了一年的时间,因为自己的懒惰,加上内容中涉及到大量爬虫,而且爬目标网站是政府网站(不允许爬),另外 19 年网上时不时曝出某某程序员因爬虫而入狱的故事,出版社和我难免不会恐惧,我也正好找到了不再继续写下去的理由。 +花了点时间把以前的程序,书籍已经写成的内容整理了一下,放在了 economic_audit_knowledge_graph 中,所有资料都在里面,希望能帮助到自然语言入门的小伙伴,我自己已经不做这个领域了! + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何抽取实体关系?——基于依存句法分析的事实三元组抽取 + +

+ +
+ + + + +
+ +
+ +参考: +HanLP 自然语言处理 +基于依存分析的开放式中文实体关系抽取方法 +命名实体三元组抽取参考自fact_triple_extraction + +这一段时间一直在做知识图谱,卡在实体关系抽取这里几个月了,在 Github 上面看到有人使用卷积神经网络训练模型进行抽取,自己也尝试了一下,但是一直苦于没有像样数据去训练,而标注训练集又太费时间了,我不太愿意干体力活。另外自己也不会什么机器学习、深度学习之类的技术,而且毕业设计都是有时间要求的,所以采用了一个低档次的方法,基于依存句法分析的实体关系抽取,记录一下心得,方便日后忘记可以再找回来。 +论文给出了 8 种中文关系的表达方式,并且最后给出了一个采用正则表达式语法指出表达,核心就是谓语动词表示关系,即关系表述中一定得有动词。 +状语*动词+补语?宾语? + +我不太赞同把宾语也当作关系表述的一部分,论文指出“p4生于山西”应该抽出(p4,山西,生于山西),我认为关系不应该表述为“生于山西”,所以我把关系表述改为下面的样子了。 +状语*动词+补语? + +这篇文章只是作为一个方法介绍,我自己先看了一遍,能够保证我下次看到这篇文章,可以立马回忆起自己的实现方法,希望你看了也能了解方法,看不懂的话,我表示抱歉,浪费您的时间了,我已经尽可能写到简单了。 +先来看几个简单句子吧: +主谓宾关系:刘小绪 生于 四川 +// 这个三元组很明显:(刘小绪,生于,四川) + + +动补结构:刘小绪 洗 干净 了 衣服 +// 如果套用主谓宾关系就是:(刘小绪,洗,衣服) +// 但是这里描述的是一个状态,是刘小绪把衣服洗干净了 +// “干净”是动词“洗”的补语,所以还应该提取出一个如下三元组 +// (刘小绪,洗干净了,衣服) + +状动结构:父亲 非常 喜欢 跑步 +// 这句和上面很像,主谓宾关系是:父亲喜欢跑步 +// “非常”用于修饰“喜欢” +// (父亲,非常喜欢,跑步) + +介宾关系:刘小绪 就职 于 学校 +// 如果直接把这个三元组抽取为(刘小绪,就职,学校),很别扭 +// “于”和“学校”是介宾关系,它们的关系应该是:就职于 +// (刘小绪,就职于,学校) + +宾语前置:海洋 由 水 组成 +// “海洋”是“组成”的前置宾语 +// “由”是“组成”的状语 +// “水”和“由”是介宾关系 +// 所以上面的句子没有明确的主谓关系,需要我们判断 +// 抽出的三元组应该为:(水,组成,海洋) + +HanLP 提供了两种依存句法分析的器,默认采用的是基于神经网络的依存句法分析器。依存句法分析就是将句子分析成一棵依存句法树,描述各个词语之间的依存关系,即指出词语之间在句法上的搭配关系。 +有了上面所说的依存句法树,其实我们只需要进行各种判断就可以了。先做出下面的一点说明,就拿第一个例子来说。 +原文:刘小绪生于四川 + +# 这是分词结果 +[刘小绪/nr, 生于/v, 四川/ns] + +#这是句法分析结果 +刘小绪 --(主谓关系)--&gt; 生于 +生于 --(核心关系)--&gt; ##核心## +四川 --(动宾关系)--&gt; 生于 + +为了方便理解,也为了方便程序的编写,我把他们组织成了下面的形式,为每一个词语都建一个依存句法字典。 +刘小绪:{} +生于:{主谓关系=[刘小绪], 动宾关系=[四川]} +四川:{} + +然后只需要写出类似于下面的程序段就可以抽出关系了。 +// 主谓宾关系:刘小绪生于四川 +// dic是这个词语的依存句法字典 +if (dic.containsKey(&quot;主谓关系&quot;) &amp;&amp; dic.containsKey(&quot;动宾关系&quot;)){ + + // 当前的词语,用上面的例子来说,relation=“生于” + String relation = curWord.LEMMA; + + + // 用循环遍历,是因为关系列表里面不一定只有一个词语 + for (CoNLLWord entity1: + dic.get(&quot;主谓关系&quot;)) { + + for (CoNLLWord entity2: + dic.get(&quot;动宾关系&quot;)) { + + System.out.println(entity1.LEMMA + &quot;,&quot; + relation + &quot;,&quot; + entity2.LEMMA); + } + + } +} + +对于分词后的每个词语都进行上面程序段的操作。“刘小绪”和“四川”,关系字典都为空。而对于“生于”,关系列表里面既有主谓也有动宾,而自己本身就是动词,主谓宾就出来了。直接从主谓关系中拿出来词语作为 entity1,再拿上自己作为关系,最后拿出动宾关系中的词语作为 entity2。很明确的三元组(刘小绪,生于,四川)就出来了。 +最后给出一个程序运行结果图吧。 + +我个人觉得效果还行,在简单句子上面表现的差强人意,在长句子上面表现的差劲。注意上文使用的第三方包随着时间的推移肯定会改一些接口,源码链接:entity_relation_extraction + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/_9zQ4w6VQ/index.html b/_9zQ4w6VQ/index.html new file mode 100644 index 00000000..897e2eb1 --- /dev/null +++ b/_9zQ4w6VQ/index.html @@ -0,0 +1,419 @@ + + + + + + + + 自然语言处理 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 自然语言处理 +
+ + +
+

+ + 知识图谱如何构建?——经济责任审计知识图谱构建案例实战 + +

+ +
+ + + + +
+ +
+ +参考: +汉语言处理包 HanLP:https://github.com/hankcs/HanLP +中文文本分类:https://github.com/gaussic/text-classification-cnn-rnn +农业知识图谱:https://github.com/qq547276542/Agriculture_KnowledgeGraph +事实三元组抽取:https://github.com/twjiang/fact_triple_extraction +中文自然语言处理相关资料:https://github.com/mengxiaoxu/Awesome-Chinese-NLP +开放中文实体关系抽取:http://www.docin.com/p-1715877509.html + +自 2012 年 Google 提出“知识图谱”的概念以来,知识图谱就一直是学术研究的重要方向,现在有很多高校、企业都致力于将这项技术应用到医疗、教育、商业等领域,并且已经取得了些许成果。Google 也宣布将以知识图谱为基础,构建下一代智能搜索引擎。 +现在已经可以在谷歌、百度、搜狗等搜索引擎上面看到知识图谱的应用了。比如在 Google 搜索某个关键词时,会在其结果页面的右边显示该关键字的详细信息。在几个常用的搜索引擎中搜索知识时,返回的答案也变得更加精确,比如搜索“汪涵的妻子”,搜索引擎会直接给出答案“杨乐乐”,方便了用户快速精准的获取想要的信息。不过目前的搜索引擎只有少部分搜索问题能达到这种效果。 +关于知识图谱是什么,我想就不用介绍了,这种通过搜索引擎就能轻松得到的结果写在这里有点浪费篇章,并且我对知识图谱的理解也不深,不敢夸夸其谈,只是把自己这一段时间以来的工作做一个总结。 +本文只相当于以经济责任审计这一特定领域构建了一个知识图谱,仅仅是走了一遍流程,当作入门项目,构建过程中参考甚至抄袭了别人的很多方法与代码,末尾都会给出参考的项目等等。 + +上图是我构建经济责任审计知识图谱的流程,看起来很繁琐,但只要静下心看,个人觉得相对还算清晰,箭头都有指向。下面就一步一步进行说明。 +数据获取 +数据获取主要分为两部分数据,一部分是新闻类数据,我把它用作文本分类模型的训练集;另一部分是实体数据,为了方便,我直接把互动百科抓取的词条文件作为实体,省了属性抽取这一环节。 +因为本文构建的是一个经济责任审计领域的知识图谱,所以作为文本分类模型训练集的数据也应该是经济责任审计领域的。这里主要抓取了审计署、纪检委、新浪网的部分新闻。 + +像上面的图一样,新闻类网站一般都有搜索框,为了简单,所以我直接用搜索框搜索“经济责任审计”,然后从搜索结果中抓取新闻数据,即认为是经济责任审计相关的文本。抓取新闻类网站使用了 chrome 模拟用户进行访问。最终获得了 3500 多条新闻文本。 +领域词汇判定 +领域词汇判定,本文构建的不是开放领域的知识图谱,所以需要采用一种方法来判定所抓取的内容是否属于经济责任审计领域。领域词汇本文的方法实际上是领域句子判定,直接使用了大神的项目。CNN-RNN中文文本分类,基于tensorflow。也看到有人通过改进逻辑回归算法,在进行领域词汇的判定。 +我判定领域词汇的逻辑是这样的,一个词语即使是人类也不一定能确定它是否属于经济责任审计领域,但是每个词语都会有它的含义解释对不对,一个词语的解释就是一段话。我用网上的新闻训练出一个判断一段话属于哪个领域的模型,然后把词语的解释放到模型了里面去,如果模型给出的结果是属于经济责任审计领域,那则认为这个词语属于经济责任审计领域。 +实体关系抽取 +知识图谱的基本单位为(实体1,关系,实体2)这样的三元组,实体是直接从互动百科获取的词条,关系由两部分组成,一部分来自 wikidata 所提供的关系,这一部分直接从 wikidata 爬取即可得到,另一部分使用的是基于依存句法分析的开放式中文实体关系抽取,已经在前面的文章发过了。 +知识存储 +有了实体和实体关系,那么把这些数据进行筛选,然后入库,通过直观的页面展示,就可以了。这里使用的数据库是 neo4j,它作为图形数据库,用于知识图谱的存储非常方便。知识的展示使用了别人的项目,仅仅是把里面的数据换掉了而已,感谢大神的无私。 +当然你也可以选择使用关系型数据库,因为我做的经济责任审计知识图谱不够深入,所以做到最后展示的时候,发现其实用我比较熟悉的 MySql 更好,相比 NOSql 我更熟悉关系型数据库,而且 MySql 有更大的社区在维护,它的 Bug 少、性能也更好。 +最后放几张效果图 + + +下面是以“职业”为关系查询条件所得出的结果。 + +总结一下 +只是对几个月工作的梳理,大多数核心代码都改自现有的代码,所有的数据都来自于网络,与知识图谱相关的公开技术较少,我也只是尝试着做了一下,虽然很菜,也可以对大致的技术路线、流程有一个简单的了解,主要工作都是自然语言处理的内容。后期可以利用现在的知识图谱构建智能问答系统,实现从 what 到 why 的转换。 +以下内容更新于 2020 年 3 月。 +在毕业前收到了电子工业出版社和另一家出版社的写书邀请,我和电子工业出版社签订了写书合同,从还未毕业开始断断续续写作了一年的时间,因为自己的懒惰,加上内容中涉及到大量爬虫,而且爬目标网站是政府网站(不允许爬),另外 19 年网上时不时曝出某某程序员因爬虫而入狱的故事,出版社和我难免不会恐惧,我也正好找到了不再继续写下去的理由。 +花了点时间把以前的程序,书籍已经写成的内容整理了一下,放在了 economic_audit_knowledge_graph 中,所有资料都在里面,希望能帮助到自然语言入门的小伙伴,我自己已经不做这个领域了! + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何抽取实体关系?——基于依存句法分析的事实三元组抽取 + +

+ +
+ + + + +
+ +
+ +参考: +HanLP 自然语言处理 +基于依存分析的开放式中文实体关系抽取方法 +命名实体三元组抽取参考自fact_triple_extraction + +这一段时间一直在做知识图谱,卡在实体关系抽取这里几个月了,在 Github 上面看到有人使用卷积神经网络训练模型进行抽取,自己也尝试了一下,但是一直苦于没有像样数据去训练,而标注训练集又太费时间了,我不太愿意干体力活。另外自己也不会什么机器学习、深度学习之类的技术,而且毕业设计都是有时间要求的,所以采用了一个低档次的方法,基于依存句法分析的实体关系抽取,记录一下心得,方便日后忘记可以再找回来。 +论文给出了 8 种中文关系的表达方式,并且最后给出了一个采用正则表达式语法指出表达,核心就是谓语动词表示关系,即关系表述中一定得有动词。 +状语*动词+补语?宾语? + +我不太赞同把宾语也当作关系表述的一部分,论文指出“p4生于山西”应该抽出(p4,山西,生于山西),我认为关系不应该表述为“生于山西”,所以我把关系表述改为下面的样子了。 +状语*动词+补语? + +这篇文章只是作为一个方法介绍,我自己先看了一遍,能够保证我下次看到这篇文章,可以立马回忆起自己的实现方法,希望你看了也能了解方法,看不懂的话,我表示抱歉,浪费您的时间了,我已经尽可能写到简单了。 +先来看几个简单句子吧: +主谓宾关系:刘小绪 生于 四川 +// 这个三元组很明显:(刘小绪,生于,四川) + + +动补结构:刘小绪 洗 干净 了 衣服 +// 如果套用主谓宾关系就是:(刘小绪,洗,衣服) +// 但是这里描述的是一个状态,是刘小绪把衣服洗干净了 +// “干净”是动词“洗”的补语,所以还应该提取出一个如下三元组 +// (刘小绪,洗干净了,衣服) + +状动结构:父亲 非常 喜欢 跑步 +// 这句和上面很像,主谓宾关系是:父亲喜欢跑步 +// “非常”用于修饰“喜欢” +// (父亲,非常喜欢,跑步) + +介宾关系:刘小绪 就职 于 学校 +// 如果直接把这个三元组抽取为(刘小绪,就职,学校),很别扭 +// “于”和“学校”是介宾关系,它们的关系应该是:就职于 +// (刘小绪,就职于,学校) + +宾语前置:海洋 由 水 组成 +// “海洋”是“组成”的前置宾语 +// “由”是“组成”的状语 +// “水”和“由”是介宾关系 +// 所以上面的句子没有明确的主谓关系,需要我们判断 +// 抽出的三元组应该为:(水,组成,海洋) + +HanLP 提供了两种依存句法分析的器,默认采用的是基于神经网络的依存句法分析器。依存句法分析就是将句子分析成一棵依存句法树,描述各个词语之间的依存关系,即指出词语之间在句法上的搭配关系。 +有了上面所说的依存句法树,其实我们只需要进行各种判断就可以了。先做出下面的一点说明,就拿第一个例子来说。 +原文:刘小绪生于四川 + +# 这是分词结果 +[刘小绪/nr, 生于/v, 四川/ns] + +#这是句法分析结果 +刘小绪 --(主谓关系)--&gt; 生于 +生于 --(核心关系)--&gt; ##核心## +四川 --(动宾关系)--&gt; 生于 + +为了方便理解,也为了方便程序的编写,我把他们组织成了下面的形式,为每一个词语都建一个依存句法字典。 +刘小绪:{} +生于:{主谓关系=[刘小绪], 动宾关系=[四川]} +四川:{} + +然后只需要写出类似于下面的程序段就可以抽出关系了。 +// 主谓宾关系:刘小绪生于四川 +// dic是这个词语的依存句法字典 +if (dic.containsKey(&quot;主谓关系&quot;) &amp;&amp; dic.containsKey(&quot;动宾关系&quot;)){ + + // 当前的词语,用上面的例子来说,relation=“生于” + String relation = curWord.LEMMA; + + + // 用循环遍历,是因为关系列表里面不一定只有一个词语 + for (CoNLLWord entity1: + dic.get(&quot;主谓关系&quot;)) { + + for (CoNLLWord entity2: + dic.get(&quot;动宾关系&quot;)) { + + System.out.println(entity1.LEMMA + &quot;,&quot; + relation + &quot;,&quot; + entity2.LEMMA); + } + + } +} + +对于分词后的每个词语都进行上面程序段的操作。“刘小绪”和“四川”,关系字典都为空。而对于“生于”,关系列表里面既有主谓也有动宾,而自己本身就是动词,主谓宾就出来了。直接从主谓关系中拿出来词语作为 entity1,再拿上自己作为关系,最后拿出动宾关系中的词语作为 entity2。很明确的三元组(刘小绪,生于,四川)就出来了。 +最后给出一个程序运行结果图吧。 + +我个人觉得效果还行,在简单句子上面表现的差强人意,在长句子上面表现的差劲。注意上文使用的第三方包随着时间的推移肯定会改一些接口,源码链接:entity_relation_extraction + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/_KpeiJK6G/index.html b/_KpeiJK6G/index.html new file mode 100644 index 00000000..bf3484bf --- /dev/null +++ b/_KpeiJK6G/index.html @@ -0,0 +1,462 @@ + + + + + + + + 2020 年国庆观《我和我的家乡》有感所写 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 2020 年国庆观《我和我的家乡》有感所写 +

+ + +
+ +
+

上一次在我的微信公众号发文是 5 月 17 日,差不多近 5 个月没有在上面发一篇文章,看了下后台的关注人数居然没有多大的变化,安慰自己是我的读者大人都喜欢我,所以对我不离不弃没有取关,所以写了这篇文章给公众号的读者大人赔礼道歉露个面,表示我还在,想起啥就说点啥,毕竟下次这种双节重逢的日子得等到 2031 年了,过节就放空脑袋不去思考那些复杂的事情

+

转眼自己已经毕业两年了,同级读研的同学也都陆陆续续毕业了,一直想着等冬天时候借着旅游的名义回哈尔滨看看老师。让人意外的是老师居然自己飞成都来了,陪着老师在电子科大校园外面散了两个多小时步,仿佛一下子回到了大学时代。

+

老师是个极为幽默风趣的人,到电子科大是参加一个什么区块链会议,我询问他这个会议具体的内容是啥。老师有一点不耐烦的说:“就是一个卖铁锹的会议,挖币挖不动了开始教人挖币了”。老师问我现在在干嘛,我思考了几秒说了句:“我现在在卖铁锹”。哈哈哈哈哈哈 ......................

+
+

老师常说的一个小故事,一个地方金子多,大家都去挖金子,挖金子的人没有赚钱,结果卖铁锹的人赚翻了

+
+

10 月 1 日去电影院看了《我和我的家乡》,电影里面提到贵州山区因为道路不通畅,直线距离一公里的恋爱居然因为异地恋分手了,这样的地理环境我倒是没见过,不过让我想起了经典电视剧《血色浪漫》里面的片段,我把这段给拿过来了方便读者大人观看。

+

+

《血色浪漫》是一代人的记忆,主角钟跃民在当知青的时候迷上了「信天游」,陕北一些地区也是隔一条沟可以聊天,但是要见个面得好几十里,剧中这一段对歌真的太经典了,我禁不住又翻看了好几遍,又回想起了大学上《民族音乐》课程的时光了 ..................

+

随时保持一颗学习的心,脑子里多装一点知识总没坏处的,钟跃民学信天游的时候也没想到可以用来撩妹啊​!说不定你从这篇文章里面看到的几个小故事也可以撩妹呐,要是撩到妹了记得回来感谢我啊​

+

电影里面还有两个小细节让我印象深刻,还是讲述贵州山区的那个小节,里面阿花出场时穿的裙子让我想起了花儿。花儿是我 6 年前在宽窄巷子的一家青旅认识的,那时她还是那家青旅的老板,我正在读大冰的《乖,摸摸头》和《阿弥陀佛么么哒》,花老板的生活让我找到了大冰的文字在现实中的影射,可以翻翻她公众号「微笑而确实的日常」里面的历史文章,都挺有意思的。

+

花儿的手很巧,什么垃圾到她手里都会活起来,常常做一些小手工卖给旅客,也时常邀请旅客免费与他们一起共进晚餐,餐后背着吉他外面唱自己写的歌,有时候路人会给几十块钱。后面通过朋友圈了解到花老板去西藏生活了一段时间,而后又回到了大理。花老板回到大理后我又去过一次那间青旅,但是已经完全变了样子,旅客之间没了谈天说地,屋子也变得像酒店一样冰冷,和花老板联系确认她不会再回来,就再没联系过了。

+

没有找到花老板比较好的照片,这张背影照可以勉强看到裙子的布料,听说云南很多人穿这样布料的裙子。

+
+

在这个快节奏人人都算计的社会,太多人少了像花老板一样的洒脱,太多人都忘记了这个世界上还有爱,也太多人连为爱放弃的勇气都没有。4 月离开 IT 行业之后我才真正感受到程序员群体的单纯,几个月的经历也更让我明白了家人与朋友的重要,翻一翻你的通讯录去联系一下那些许久没有联系的朋友吧。

+

《我和我的家乡》还有一段教室漏雨的剧情,想起来自己读四五年级的时候不也是这样子嘛!学校拿几个木桩和几块木板子用钉子钉住,再加上石棉瓦和塑料薄膜就搭建了一间简易教室。四川的夜晚总是爱悄悄下大雨,我们早上到教室的第一件事不是早读,而是把教室的水先舀出去,这可是同学们比较喜欢的环节啊,不用被老师监督着早读可太轻松了。

+

到夏天时候屋顶上爬满了飞蛾,全教室都是飞蛾身上掉下的粉末,这个场景对同学们来说也是不可多得的福利,不用上晚自习可以直接回宿舍睡觉。第二天早晨去教室把那两垃圾桶的飞蛾扫一扫就好了,不过比较难搞的是晚上满屋顶飞蛾遇到半夜暴雨,第二天可就真不好清理教室了!

+

本来想把自己以前写的文章都搬到知乎去,结果一不小心就触犯了知乎的社区规范了,不仅把我的几篇文章都给删掉了,还把我给禁言了一天。一气之下自己买了个服务器搭建了个博客,不管在什么平台写东西都得遵守别人的规矩,现在我的地盘我做主我自己定规矩!

+
+

最后还是分享最近的一点点思考,执行力和坚持这两样东西很重要,我自己身上严重缺乏这两样东西,说起来很容易做起来却不是一般的难,不知道有多少人是真正明白了它们的重要性。

+

任何人的批评都不会让自己开心,但如果一个人还愿意骂你还在鞭策你,那你真的得感谢人家,当对方什么都不愿意给你说的时候就是已经放弃你了,你就又失去了一个帮助自己成长的人!

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/_PaEG890x/index.html b/_PaEG890x/index.html new file mode 100644 index 00000000..c657c21b --- /dev/null +++ b/_PaEG890x/index.html @@ -0,0 +1,469 @@ + + + + + + + + 什么是契约测试? | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 什么是契约测试? +

+ + +
+ +
+
+

参考文章:
+聊一聊契约测试 —— ThoughtWorks洞见
+契约测试
+前后端分离了,然后呢?

+
+

契约测试全称为:消费者驱动契约测试,最早由 Martin Fowler 提出。契约这个词从字面上很容易理解,就是双方(多方)达成的共同协议,那又为什么需要契约测试这个东西呢?

+

在当前微服务大行其道的行业背景下,越来越多的团队采用了前后端分离和微服务架构,我们知道微服务是由单一程序构成的小服务,与其它服务使用 HTTP API 进行通讯,服务可以采用不同的编程语言与数据库,微服务解决了单体应用团队协作开发成本高、系统高可用性差等等问题。

+

但是微服务也引入了新的问题,假设 A 团队开发某服务并提供对应的 API,B 团队也在开发另一个服务,但是他们需要调用 A 团队的 API,为了产品的尽快发布,两个团队都争分夺秒,已经进入联调阶段了,然而出现了下面这样的尴尬情况。

+
+

随着越来越多的微服务加入,它们的调用关系开始变得越来越复杂,如果每次更改都需要和所有调用该 API 的团队协商,那沟通成本也未免太大了,试想下图的沟通成本。

+
+

为了保证 API 调用的准确性,我们会对外部系统的 API 进行测试,如果外部系统稳定性很差,或者请求时间很长的时候,就会导致我们的测试效率很低,当调用 API 失败时,你甚至无法确定是因为 API 被更改而导致的失败还是运行环境不稳定导致的失败。

+

A 团队提供的 API 不稳定,肯定会导致 B 团队效率低下,为了不影响 B 团队的进度,所以构建了测试替身,通过模拟外部 API 的响应行为来增强测试的稳定性和反应速度。

+
+

但是这样做真的就解决问题了吗?当所有内部测试都通过时,能拍着胸脯说真正的外部 API 就一定没有变化?很简单的一个解决方案就是:部分测试使用测试替身,另一部分测试定期使用真实的外部 API,这样既保证了测试的运行效率、调用端的准确性,又能确保当真实外部系统API改变时能得到反馈。

+
+

感觉剧情到这里就差不多该结束了,实际上真正的高潮部分开刚刚开始。如果外部 API 的反馈周期很长,那增加真实 API 测试间隔时间就又回到了最初的起点。现在我们回顾一下上面的方案。

+

在上面的场景中,我们都是已知外部 API 功能来编写相应的功能测试,并且使用直接调用外部 API 的方式来达到测试的目的,如此就不可避免的带来了两个问题:

+
    +
  • API 调用者(消费者)对服务提供方(生产者)的更改是通过对 API 的测试来感知的;
  • +
  • 直接依赖于真实 API 的测试效果受限于 API 的稳定性和反映速度。
  • +
+

解决方案首先是依赖关系解耦,去掉直接对外部 API 的依赖,而是内部和外部系统都依赖于一个双方共同认可的约定—“契约”,并且约定内容的变化会被及时感知;其次,将系统之间的集成测试,转换为由契约生成的单元测试,例如通过契约描述的内容,构建测试替身。这样,同时契约替代外部 API 成为信息变更的载体。

+

前后照应一下,我们现在再来看一下消费者驱动契约测试。它有两个不可或缺的角色:消费者是服务使用方;生产者(提供者)是服务提供方。采用需求驱动(消费者驱动)的思想。契约文件(比如 json 文件)由双方共同定义规范,一般由消费者生成,生产者根据这份契约去实现。

+

契约测试其中一个的典型应用场景是内外部系统之间的测试,另一个典型的例子是前后端分离后的 API 测试。行业内比较成熟的解决方案是 Swagger SpecificationPact Specification,这里不做展开讨论。

+

我们同样可以把契约测试的思想用到代码的编写中,契约测试通过一个契约文件来解耦依赖,那么对于需要用户定义很多规则的场景,我们同样可以将这些规则像契约文件一样抽取出来,这样就降低了代码之间的耦合度。

+

最后敲敲黑板,契约测试不是替代 E2E 测试的终结者,更不是单元测试的升级换代,它更偏向于服务和服务之间的 API 测试,通过解耦服务依赖关系和单元测试来加快测试的运行效率。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/_v3DmjUkj/index.html b/_v3DmjUkj/index.html new file mode 100644 index 00000000..47f7d30f --- /dev/null +++ b/_v3DmjUkj/index.html @@ -0,0 +1,577 @@ + + + + + + + + 磁盘到底是怎样工作的?一文理解硬盘结构 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 磁盘到底是怎样工作的?一文理解硬盘结构 +

+ + +
+ +
+

数据库系统总会涉及到辅助存储(大多都是磁盘),因为它们能够存储大量需要长期保存的数据,因此我们有必要先了解了解磁盘的相关知识。

+

根据机械原理,存储器的容量越大其速度就越慢。但是速度越快的存储器,其单位字节的价格就越贵。现代计算机系统可以包含几个不同的可以存储数据的部件,就形成了存储器的层次结构,但是需要注意的是「虚拟内存」是操作系统与操作系统运用机器硬件的产物,它不是存储器的层次之一。

+

磁盘结构

+

传统的硬盘盘结构是像下面这个样子的,它有一个或多个盘片,用于存储数据。盘片多采用铝合金材料;中间有一个主轴,所有的盘片都绕着这个主轴转动。一个组合臂上面有多个磁头臂,每个磁头臂上面都有一个磁头,负责读写数据。

+
+

磁盘一般有一个或多个盘片。每个盘片可以有两面,即第一个盘片的正面为0面,反面为 1 面;第二个盘片的正面为 2 面......依次类推。磁头的编号也和盘面的编号是一样的,因此有多少个盘面就有多少个磁头。盘面正视图如下图,磁头的传动臂只能在盘片的内外磁道之间移动。因此不管开机还是关机,磁头总是在盘片上面。关机时,磁头停在盘片上面,抖动容易划伤盘面造成数据损失,为了避免这样的情况,所以磁头都是停留在起停区的,起停区是没有数据的。

+
+

每个盘片的盘面被划分成多个狭窄的同心圆环,数据就存储在这样的同心圆环上面,我们将这样的圆环称为磁道 (Track)。每个盘面可以划分多个磁道,最外圈的磁道是0号磁道,向圆心增长依次为1磁道、2磁道......磁盘的数据存放就是从最外圈开始的。

+
+

根据硬盘的规格不同,磁道数可以从几百到成千上万不等。每个磁道可以存储数 Kb 的数据,但是计算机不必要每次都读写这么多数据。因此,再把每个磁道划分为若干个弧段,每个弧段就是一个扇区 (Sector)。扇区是硬盘上存储的物理单位,现在每个扇区可存储 512 字节数据已经成了业界的约定。也就是说,即使计算机只需要某一个字节的数据,但是也得把这个 512 个字节的数据全部读入内存,再选择所需要的那个字节。

+
+

柱面是我们抽象出来的一个逻辑概念,简单来说就是处于同一个垂直区域的磁道称为柱面 ,即各盘面上面相同位置磁道的集合。需要注意的是,磁盘读写数据是按柱面进行的,磁头读写数据时首先在同一柱面内从 0 磁头开始进行操作,依次向下在同一柱面的不同盘面(即磁头上)进行操作,只有在同一柱面所有的磁头全部读写完毕后磁头才转移到下一柱面。因为选取磁头只需通过电子切换即可,而选取柱面则必须通过机械切换。数据的读写是按柱面进行的,而不是按盘面进行,所以把数据存到同一个柱面是很有价值的。

+

磁盘被磁盘控制器所控制(可控制一个或多个),它是一个小处理器,可以完成一些特定的工作。比如将磁头定位到一个特定的半径位置;从磁头所在的柱面选择一个扇区;读取数据等。

+
+

现代硬盘寻道都是采用CHS(Cylinder Head Sector)的方式,硬盘读取数据时,读写磁头沿径向移动,移到要读取的扇区所在磁道的上方,这段时间称为寻道时间(seek time)。因读写磁头的起始位置与目标位置之间的距离不同,寻道时间也不同。磁头到达指定磁道后,然后通过盘片的旋转,使得要读取的扇区转到读写磁头的下方,这段时间称为旋转延迟时间(rotational latencytime)。然后再读写数据,读写数据也需要时间,这段时间称为传输时间(transfer time)。

+

根据上文的信息,我们可以得出磁盘容量的计算公式为:

+
硬盘容量 = 盘面数 × 柱面数 × 扇区数 × 512字节
+
+

笔试题实战

+

下面的题目是腾讯某一年校招笔试中的一个题目,题干信息描述为:数据存储在磁盘上的排列方式会影响I/O服务的性能,一个圆环磁道上有10个物理块,10个数据记录R1~R10存放在这个磁道上,记录的安排顺序如下表所示。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
物理块12345678910
逻辑记录R1R2R3R4R5R6R7R8R9R10
+

假设磁盘的旋转速度为20ms,磁盘当前处在R1的开头处,若系统顺序扫描后将数据放入单缓冲区内,处理数据的时间为4ms(然后再读取下个记录),则处理这10个记录的最长时间是多少?

+
+

答案:磁盘会一直朝某个方向旋转,不会因为处理数据而停止。本题要求顺序处理 R1 到 R10,起始位置在 R1,一周是 20ms,共 10 个记录,所以每个记录的读取时间为 2ms。首先读 R1 并处理 R1,读 R1 花 2ms,读好后磁盘处于 R1 的末尾或 R2 的开头,此时处理 R1,需要 4ms,因为磁盘一直旋转,所以 R1 处理好了后磁盘已经转到 R4 的开始了,这时花的时间为 2+4=6ms。这时候要处理 R2,需要等待磁盘从 R5 一直转到 R2 的开始才行,磁盘转动不可反向,所以要经过 8*2ms 才能转到 R1 的末尾,读取 R2 需要 2ms,再处理 R2 需要 4ms,处理结束后磁盘已经转到 R5 的开头了,这时花的时间为 2*8+2+4=22ms。等待磁盘再转到 R3 又要 8*2ms,加上 R3 自身 2ms 的读取时间和 4ms 的处理时间,花的时间也为 22ms,此时磁盘已经转到 R6 的开头了,写到这里,就可以看到规律了,读取并处理后序记录都为 22ms,所以总时间为 6+22*9=204ms

+
+

如何加速对磁盘的访问

+

对于理解数据库系统系统特别重要的是磁盘被划分为磁盘块(或像操作系统一样称之为页),每个块的大小是 4~64KB。磁盘访问一个磁盘块平均要用 10ms,但是这并不表示某一应用程序将数据请求发送到磁盘控制器后,需要等 10ms 才能得到数据。如果只有一个磁盘,在最坏的情况下,磁盘访问请求的到达个数超过 10ms 一次,那么这些请求就会被无限的阻塞,调度延迟将会变的非常大。因此,我们有必要做一些事情来减少磁盘的平均访问时间。

+

按柱面组织数据:前这一点在前文已经提到过了。因为寻道时间占平均块访问时间的一半,如果我们选择在一个柱面上连续的读取所有块,那么我们只需要考虑一次寻道时间,而忽略其它时间。这样,从磁盘上读写数据的速度就接近于理论上的传输速率。

+

使用多个磁盘:如果我们使用多个磁盘来替代一个磁盘,只要磁盘控制器、总线和内存能以 n 倍速率处理数据传输,则使用 n 个磁盘的效果近似于 1 个磁盘执行了 n 次操作。因此使用多个磁盘可以提高系统的性能。

+

磁盘调度:提高磁盘系统吞吐率的另一个有效方法是让磁盘控制器在若干个请求中选择一个来首先执行,调度大量块请求的一个简单而有效的方法就是电梯算法。回忆一下电梯的运行方式,它并不是严格按先来后到的顺序为乘客服务,而是从建筑物的底层到顶层,然后再返回来。同样,我们把磁盘看作是在做横跨磁盘的扫描,从柱面最内圈到最外圈,然后再返回来,正如电梯做垂直运动一样。

+

预取数据:在一些应用中,我们是可以预测从磁盘请求块的顺序的。因此我们就可以在需要这些块之前就将它们装入主存。这样做的好处是我们能较好的调度磁盘,比如采用前文的电梯算法来减少访问块所需要的平均时间。

+

磁盘故障

+

如果事情都像我们一开始设计的那样进行,那世界肯定会变得特别无聊。磁盘偶尔也会耍耍小脾气,甚至是罢工不干了。比如在读写某个扇区一次尝试没有成功,但是反复尝试后有成功读写了,我们称之为间歇性故障

+

一种更为严重的故障形式是,一个或多个二进制位永久的损坏了,所以不管我们尝试多少次都不可能成功,这种故障称之为介质损坏

+

另一种相关的错误类型称之为写故障,当我们企图写一个扇区时,既不能正确的写,也不能检索先前写入的扇区,发生这种情况的一种可能原因就是在写过程中断电了。

+

当然肯定最严重的就是磁盘崩溃,这种故障中,整个磁盘都变为永久不可读,这是多么可怕的事情。

+

既然会出现上面所述的各种大小故障,那么我们就必须要采取各种措施去应对大大小小的变故,保证系统能正常运行。

+

规避故障

+

我们尝试读一个磁盘块,但是该磁盘块的正确内容没有被传送到磁盘控制器中,就是一个间歇性故障发生了。那么问题是控制器如何能判断传入的内容是否正确呢?答案就是使用校验和,即在每个扇区使用若干个附加位。在读出时如果我们发现校验和对数据位不合适,那么我们就知道有错误;如果校验和正确,磁盘读取仍然有很小的可能是不正确的,但是我们可以通过增加趣多校验位来降低读取不正确发生的概率。

+

此处我们使用奇偶校验来举例,通过设置一个校验位使得二进制集合中 1 的个数总是偶数。比如某个扇区的二进制位序列是 01101000,那么就有奇数个 1,所以奇偶位是 1,这个序列加上它后面的奇偶位,就有 011010001;而如果所给的序列是 11101110,那么奇偶位就是 0。所以每一个加上了奇偶位构成的 9 位序列都有偶数奇偶性。

+

尽管校验和几乎能正确检测出介质故障或读写故障的存在,但是它却不能帮助我们纠正错误。为了处理这个问题,我们可以在一个或多个磁盘中执行一个被称为稳定存储的策略。通常的思想是,扇区时成对的,每一对代表一个扇区内容 X。我们把代表 X 的扇区对分别称为左拷贝 XL和右拷贝XR。这样实际上就是每个扇区的内容都存储了两份,操作XL失败,那么去操作XR就可以了,更何况我们还在每个扇区中有校验和,把错误的概率就大大降低了。

+

到现在为止,我们讨论的都是简单的故障,但是如果发生了磁盘崩溃,其中的数据被永久破坏。而且数据没有备份到另一种介质中,对于银行金融系统这将是巨大的灾难,遇到这种情况我们应该怎么办呢?

+

数据恢复

+

应对磁盘故障最简单的方式就是镜像磁盘,即我们常说的备份。回忆一下写毕业论文时的做法,那时候大部分同学还不会用版本控制器,所以基本采用每天备份一次数据,并且在文件名称中标注日期,以此来达到备份的效果。

+

第二种方式是使用奇偶块,比如一个系统中有 3 个磁盘,那么我们再加一个磁盘作为冗余盘。在冗余盘中,第 i 块由所有数据盘的第 i 块奇偶校验位组成。也就是说,所有第 I 块的第 j 位,包括数据盘和冗余盘,在它们中间必须有偶数个 1,冗余盘的作用就是让这个条件为真。

+

我们举个简单例子,假设快仅由一个字节组成,我们有三个数据盘和一个冗余盘,对应的位序列如下。其中 盘4 为冗余盘,它的位序列是根据前面三个盘计算出来的。

+
盘 1:11110000
+盘 2:10101010
+盘 3:00111000
+盘 4:01100010
+
+

假设现在某个盘崩溃了,那么我们就能根据上面的序列来恢复数据,只需要让每一列 1 的个数为偶数就可以了,但是这种冗余方式也存在很大的不足。

+

第一个缺陷是,如果是两个盘同时崩溃了,那数据也恢复不出来了。第二个问题在于,虽然读数据只需要一次 I/O 操作即可,但是写数据时就不一样了,因为需要根据其他数据盘来计算冗余盘中的位序列,假设共有 n 个盘,其中一个为冗余盘,所以每次写数据时,都需要进行 n+1 次 I/O 操作(读不被写入的 n-1 个盘,被重写数据盘的一次写,冗余盘的一次写),而 I/O操作又是非常耗时的操作,所以这种方法会大大拖慢系统性能。

+

另一种方案是没有明显的冗余盘,而是把每个磁盘作为某些块的冗余盘来处理。比如现在有 4 个盘,0 号磁盘将作为编号为 4、8、12 等柱面的冗余,而 1 号磁盘作为编号为 1、5、9 等块的冗余......

+

一种更为先进的方式使用海明码来帮助从故障中恢复数据,它在多个磁盘崩溃的情况下也能恢复出数据,也是 RAID 的最高等级,由于本人水平有限,用文字表达不清楚,就不作介绍了,嘿嘿。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/a-74x0moT/index.html b/a-74x0moT/index.html new file mode 100644 index 00000000..f9f4c73d --- /dev/null +++ b/a-74x0moT/index.html @@ -0,0 +1,510 @@ + + + + + + + + 如何获得好运气 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 如何获得好运气 +

+ + +
+ +
+

一点幼稚的思考

+

从我自身浅薄的经历做了一点总结,如果某个公司的员工有超过三分之一都是同一个地方的人,那这个公司大概率是个骗子公司,因为常规渠道他们无法招到员工,只能靠亲戚、同学、同乡一类关系拉拢,如果下限再放低一点就是传销。这类公司开早会常常是这样的——主管在前台扯着嗓门儿大喊:“亲爱的家人们,大家早上好!”下面的员工齐声回答:“好!很好!非常好!”

+

之前在财富与幸福指南中有翻译过一点纳瓦尔的语录,今年 4 月份这本书已经出版了,里面有提到把事情做到极致,让机会自动找到你,让运气成为必然。结合自身经历愈发认可其说法,在一次少儿科创比赛活动中遇到一个小领导,他问我现场的学生都是谁教的,我说是某某老师教的,他突然冒出一句:“某某老师教的学生肯定不会差”。这样随口而出的一句话给了我不小的触动,这个某某老师把事情做到极致,名气在他不知道的情况下就打出去了,这就是运气。

+

发现那些让人仰慕的大佬都有一个无所畏惧的特质。面对同一项未知的任务,所有人都不知道如何去完成,强者与弱者唯一的区别是敢不敢接这个任务,强者总是毅然扎进去并着手寻找方案,弱者总是以不知道怎么做、没有学过相关知识等理由推脱。长此以往大家就觉得强者什么都会,口口相传自然就带来了运气。

+

当下社会快速的生活节奏让人喘不过气来,很多人长期都是两点一线的生活,每天的生活轨迹完全重合没有一点波动。生活需要变量才会精彩,机会往往是跟随变量出现的,保持年轻的折腾,保持一颗好奇心才会有更多的可能。把乔布斯的话搬出来用一下:Stay Hungry,Stay Foolish

+

不一样的生活

+

偶然了解到电鸭社区,它所提倡的只工作,不上班理念让我眼前一亮,数字游民这一新的词汇加到我的字典。原来这世界上还有可以不打卡的工作,他们的工作地点居然完全没有限制,在工作的同时还可以开着房车旅行......

+

非常希望能体验一下数字游民生活,上帝彷佛能听见我的愿望一样,立马就把一个机会给了我。牛客网需要一些人审核主站题目,后续是出题、审题等等一系列工作,可能因为我交付的题目质量还不错,她们愿意把一些高利润的工作派给我,一个题目从几百到几十价格不等,有俩月拿到的劳务报酬竟超过了本职工资。牛客让我体验了一把远程工作乐趣,以后如果真的创业失败了,就老老实实找个远程工作苟且吧。

+

通过Jina全球社区的负责人 Lisa,了解到一个2050团聚活动,这是我第一次接触如此纯粹的社区,把分散在各行业又比较理想主义的人汇集到了一起。目前对这个社区是很有好感的,容我观察两年再细说体会。

+

鼓捣独立博客

+

我是快毕业时开始接触公众号的,那时候的环境很单纯,很多作者都把公众号当博客用。认识的一个和我同龄的作者,为了能把某个算法讲清楚经常熬夜 P 图。那时候的程序员小灰还叫玻璃猫刘欣大大的文章标题比现在也要朴素。我自己那段时间写文章尤其认真,因此也认识了许许多多网友,收到了博文视点和另一家出版社的出版邀请。那时大部分人还不懂什么是 IP,流量一词更普遍的含义还是电话卡用的那个上网流量,10 万+偶有出现但频率不高。

+
+

刘欣大大的码农翻身依旧朴实,只是文章标题明显有一些刻意了,今年刘欣给一些编程老师免费送去了新书《半小时漫画计算机》

+
+

慢慢有人开始专门为了 10万+而写文章,抖音一类短视频应用出现后,整个互联网环境开始变得浮躁,大家为了流量不择手段,一些人为了博取流量而瞎编骇人听闻的事情。比如年初在抖音出现的血奴事件,更为可笑的是一些权威媒体居然转发了这个视频,官方媒体为了流量不查证新闻真实性就转发,那便是没有责任感的媒体。

+

媒体常常为了流量而故意删减新闻细节,比如之前文章多从自己身上找问题中所提到的新闻。互联网让大家的情绪得以便利的发泄,常常故事听一半就抑制不住心中正义,评论下面骂两句、转发个朋友圈都是没有成本的事情。台剧我们与恶的距离和电影狩猎对这个话题有比较深刻的探讨,值得花时间看一看。

+

百度取消了快照功能,必应中国区没了快照;一打开微信不出现视频号的可能性几乎为零;标题党的文章充满了订阅号;公众号不允许外链和无法修改错误很烦心。于是我鼓捣起了独立博客,在独立博客中可以自由的发布和修改文章。我选择的博客写作工具是Gridea,选它并不是因为它好用,而是因为上面有一款主题我比较喜欢,我自己花了些时间将该主题的 bug 修复了,并且在原基础上增加了几个 sidebar card,大致长下面这个样子,我个人还挺喜欢的。

+
博客截图
+

有意思的事儿

+

只能发送 500 英里的邮件。一所大学的邮件只能发送 500 英里(800 公里),这听起来像是一个胡编乱造的故事。原因是因为这所大学的服务器配置错误,他们将连接到远程服务器的超时设为了零延迟。而程序运行时 0 超时是按 3 毫秒计算的,一旦 3 毫秒内没有收到远程服务器的答复,就认为邮件无法发送。光在 3 毫秒时间前进的距离,刚好就是 500 多英里。

+

导致电脑宕机的原因竟然是 Janet Jackson 的一首歌。微软资深工程师 Raymond Chen 披露 WinXP 发布早期,一些用户报告电脑系统会意外崩溃。起初他们以为是 Windows 的 bug,但很欣慰的是发现同行也存在这个问题,最终定位到这个问题的根源竟然是 Janet Jackson 1989 年发行的歌曲《Rhythm Nation》。歌曲中一些旋律会和当时一款 5400 转硬盘发生共振,进而导致电脑崩溃。

+

“看黄片被罚”的闹剧何时休?这是一篇旧闻,从网站上的时间看是 2012 年 6 月 17 日。讲述的是延安小夫妻在家看黄碟被抓,生活几乎陷绝境的故事。

+

推荐一个视频

+

这是一个插画师、立体书设计师在一席的演讲,她把自己的生活都做成了好玩儿的立体书,如同演讲主题在生活里东张西望一样,池塘子是一个地地道道的生活观察家,生活中的那些无形爱都被她做成了立体书。

+

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/about/index.html b/about/index.html new file mode 100644 index 00000000..756899de --- /dev/null +++ b/about/index.html @@ -0,0 +1,494 @@ + + + + + + + + 关于 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 关于 +

+ + +
+

🏠 关于本站

+

这是 Guanngxu 的个人博客,内容以开发技术、生活随想随感为主!若我的文章能够给到你一些启发甚至一点帮助,还希望你能告诉我让我小小的开心一下。要是文章里面有很明显的错误或是引用忘记标明出处,也希望你能及时批评指正我!

+

👨‍💻 职业经历

+

2018.08-2020.04:中兴通讯,内部测试(效率)工具开发

+

2020.04-2021.01:信息流广告投放,抖音、快手为主,百度、神马少量

+

2021.01-2024.02:少儿编程教育,C++ 信息学竞赛为主,Python、Scratch 也有

+

2022.03-2024.02:大多是外包项目,航天载荷软件、Web 应用程序、微信小程序、无人机空检平台均有涉及

+

2024.03-至今:DC-DC、LDO 等电源芯片 FAE

+

🎓 教育经历

+

2014.08-2018.06:哈尔滨工程大学-本科-计算机科学与技术

+

🛠 使用的产品

+

阅读:微信读书得到读库

+

笔记:flomo

+

写作:Gridea

+

代码:VS Code

+

📬 联系方式

+

微信:Guanngxu

+

邮箱:heuliuxuguang#gmail.com

+

公众号:刘小绪同学

+

Github: https://github.com/Guanngxu

+

豆瓣:@Guanngxu

+ +
+
+ + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 00000000..0f2c4746 --- /dev/null +++ b/archives/index.html @@ -0,0 +1,1625 @@ + + + + + + + + Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + +
+ +
+

+ +
+ + + 下一页 + +
+ + + + + + +
+ + + \ No newline at end of file diff --git a/archives/page/2/index.html b/archives/page/2/index.html new file mode 100644 index 00000000..767730a0 --- /dev/null +++ b/archives/page/2/index.html @@ -0,0 +1,1182 @@ + + + + + + + + Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + +
+ +
+

+ +
+ + 上一页 + + +
+ + + + + + +
+ + + \ No newline at end of file diff --git a/atRZtjRTE/index.html b/atRZtjRTE/index.html new file mode 100644 index 00000000..46947230 --- /dev/null +++ b/atRZtjRTE/index.html @@ -0,0 +1,670 @@ + + + + + + + + 使用 Git 工具进行项目管理 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 使用 Git 工具进行项目管理 +

+ + +
+ +
+
+

参考内容:

+
+
+

Git工作流实践

+
+
+

Git 工作流程

+
+
+

Git三大特色之WorkFlow(工作流)

+
+
+

Git分支管理策略

+
+
+

使用 Issue 管理软件项目详解

+
+
+

GitLab Issue 创建及使用说明

+
+
+

Git 提交规范

+
+

Git 是软件开发活动中非常流行的版本控制器类工具。随着项目时间的拉长、项目参与人员的更替、软件不同特性功能的发布,从开发人员角度看会发现工程的提交历史、分支管理等非常混乱,项目管理者会因为需求迭代、bug 修复、版本发布等活动无法与代码提交历史一一对应而头疼,混乱的管理常常导致故障泄漏给客户,所以一套规范的规范的 git 工作流程是非常有必要的。

+

Git WorkFlow

+

WorkFlow 的字面意思即工作流,比喻像水流那样顺畅、自然的向前流动,不会发生冲击、对撞、甚至漩涡。工作流不涉及任何命令,它就是团体成员需要自遵守的一套规则,完全由开发者自己定义。

+

当下比较流行的三种工作流程分别为:Git FlowGitHub FlowGitLab Flow。它们有一个共同点:都采用功能驱动开发。其中 Git Flow 出现的最早,GitHub Flow 对其做了一些优化,比较适用于持续的版版发布。GitLab Flow 出现的时间比较晚,所以是综合了前面两个工作流的优点制定的。

+

Git Flow

+

git-flow 的思路非常简洁,通过 5 种分支来管理整个工程。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
分支周期说明
master长期主分支,用于存放对外发布的版本,任何时候在这个分支拿到的,都是稳定的分布版
develop长期开发分支,用于日常开发,存放最新的开发版
feature短期功能分支,它是为了开发某种特定功能,从 develop 分支上面分出来的。开发完成后,要再并入 develop
release短期预发分支,它是指发布正式版本之前(即合并到 master 分支之前),我们可能需要有一个预发布的版本进行测试。预发布分支是从 develop 分支上面分出来的,预发布结束以后,必须合并进 develop 和 master 分支
hotfix短期bug 修补分支,从 master 分支上面分出来的。修补结束以后,再合并进 master 和 develop 分支
+
git-flow
+

Github Flow

+

github-flow 可以认为是 git-flow 的一个简化版,它适用于持续部署的工程,直接将最新的功能部署到 master 分支上,不再操作 develop 分支。同时通过 CI&CD 的使用,不再需要额外的 release 和 hotfix 分支。github 还结合了推送请求(pull request)功能,在合并 feature 分支之前通过PR请求其他成员对代码进行检查。

+
    +
  1. master分支中的任何东西都是可部署的
  2. +
  3. 要开发新的功能特性,从 master 分支中创建一个描述性命名的分支(比如:new-oauth2-scopes)
  4. +
  5. 在本地提交到该分支,并定期将您的工作推送到服务器上的同一个命名分支
  6. +
  7. 当您需要反馈或帮助,或者您认为分支已经准备好合并时,可以提交一个推送请求(PR)
  8. +
  9. 在其他人审阅并签署了该功能后,可以将其合并到 master 中,合并后原来拉出来的分支会被删除
  10. +
  11. 一旦它被合并到 master 分支中,就可以并且应该立即部署
  12. +
+
github-flow
+

github-flow 最大的优点就是简单,非常适合需要持续发布的产品。但是它的问题也很明显,因为它假设 master 分支的更新与产品的发布是一致的,即 master 分支的最新代码,默认就是当前的线上代码。

+

但实际可能并非如此,代码合入 master 分支并不代表着它立刻就能发布。比如小程序提交审核以后需要等待一段时间才能发布,如果在这期间还有新的代码提交,那么 master 分支就会与刚刚发布的版本不一致。另外还有一种情况就是,有的公司只有指定时间才会发布产品,这会导致线上版本严重落后于 master 分支。

+

可以发现针对上述情况,一个 master 分支就不够用了,你可能需要在 master 分支之外新建一个 prodution 分支才能解决问题。

+

Gitlab Flow

+

因为 gitlab-flow 出现的比较晚,所以它同时具备前面两种工作流的优点,并且又摒弃了它们存在的一些缺点。它的最大原则叫做上游优先(upsteam first),即只存在一个主分支 master,它是所有其他分支的上游。只有上游分支采纳的代码变化,才能应用到其他分支。

+

gitlab-flow 分为两种情况,分别适用于持续发布和版本发布项目。对于持续发布项目,它建议在 master 分支以外,再建立不同的环境分支。比如,开发环境的分支是 master,预发环境的分支是 pre-production,生产环境的分支是 production。

+

开发分支是预发分支的上游,预发分支又是生产分支的上游。如果产环境出现了 bug,这时就要新建一个功能分支,先把它合并到 master,确认没有问题,再cherry-pick 到 pre-production,这一步也没有问题,才进入 production。

+
gitlab-flow 持续发布项目
+

对于版本发布的项目,建议的做法是每一个稳定版本,都要从 master 分支拉出一个分支,比如 2-3-stable、2-4-stable 等等。发现问题,就从对应版本分支创建修复分支,完成之后要先合并到 master 分支,然后才能合并到 release 分支。版本记录可以通过 master 上的 tag 来记录。

+
gitlab-flow 版本发布项目
+

Issue 使用

+

Git WorkFlow 主要解决了开发人员的问题,但是对项目管理者问题的解决力度不够,比如客户需求的管理、bug 的跟踪等。Github 和 Gitlab 提供的 Issue 功能可以比较好的解决项目的管理问题。

+

Issue 中文可以翻译为议题事务,是指一项待完成的工作,比如一个 bug、一项功能建议、文档缺失报告等。每个 Issue 应该包含该问题的所有信息和历史,使得后来的人只看这个 Issue,就能了解问题的所有方面和过程。

+
+

Issue 起源于客服部门。用户打电话反馈问题,客服就创建一个工单,后续的每一个处理步骤、每一次与用户的交流,都需要更新工单,全程记录所有信息。因此,Issue 的原始功能是问题追踪和工单管理,后来不断扩展,逐渐演变成全功能的项目管理工具,还可以用于制定和实施软件的开发计划。

+
+

下面以可以免费使用的 Github Issues 来介绍如何使用 Issue。

+

创建 Issue

+

每个 Github 仓库都有一个 Issue 面板,如下图所示是新建 Issue 的界面。左侧输入 Issue 的标题和内容,支持 markdown 语法。右侧分别对应四个配置项,将在下面一一进行介绍。

+
创建issue
+

Assignees

+

Assignee 选择框用于从当前仓库的所有成员之中,指派某个 Issue 的处理人员。

+
Assignees
+

Labels

+

可以为每个 Issue 打上标签,这样方便分类查看和管理,并且能比较好的使用看板进行查看。一般来说一个 Issue 至少应该有两种类型的 Label,即 Issue 的类型和 Issue 的状态(根据需要可打第三种用于表示优先级的 Label),其中 Issue 的状态可以用来构建敏捷看板。

+
labels
+

Milestone

+

Milestone 翻译过来叫里程碑,它的概念和迭代(sprint)或版本(version)差不多。Milestone 是 Issue 的容器,可以很方便的用来规划一个迭代(版本)要做的事情,也能非常方便的统计进度。

+
Milestone
+

Projects

+

Projects 是 Github 2020 年 6 月份提供的功能,它就是项目看板的功能,Gitlab 所提供的类似功能为 Issue boards,感兴趣读者可以自行阅读文档了解。

+
Projects
+

PR&MR

+

至此,可以发现我们可以使用 Issue 管理需求、bug,Milestone 又提供了迭代(版本)的计划管理,通过 Projects 可以创建敏捷看板,用于关注整体项目的进度。前文 Git WorkFlow 从开发者角度提供了项目管理的工作流程,可以思考一下还差什么问题没有解决?

+

最后剩下的问题是:每一个 Issue 都需要提交代码/文档进行解决,那代码/文档如何与 Issue 进行关联呢?其实无论是 GitHub 还是 GitLab,都可以很方便地在 Issue 上创建分支,在该分支上解决完 Issue 所对应的问题后,提交远程分支即可发起合并请求,在 Github 中称为 Pull request(PR),在 Gitlab 中则叫做 Merge request(MR)。

+

Git 提交规范

+

上文已经说了我们可以对每个 Issue 创建分支,既然是分支,那超过一个 commit 是再常见不过的事情了。一些开发人员所写的提交说明常常是fixbug或者是update等非常不规范的说明。

+

不规范的说明很难让人明白这次代码提交究竟是为了什么。在实际工作中,一份清晰简洁规范的 commit message 能够让后续的代码审查、信息查找、版本回退都更加高效可靠,因为我们还需要对提交说明制定一套规范。

+

那么什么样的 commit message 才算是符合规范的说明呢?不同团队可以制定不同的规范,此处推荐使用 Angular Git Commit Guide

+

提交格式指定为提交类型(type)、作用域(scope,可选)和主题(subject),提交类型指定为下面其中一个:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型说明
build对构建系统或者外部依赖项进行了修改
ci对 CI 配置文件或脚本进行了修改
docs对文档进行了修改
feat增加新的特征
fix修复 bug
pref提高性能的代码更改
refactor既不是修复bug也不是添加特征的代码重构
style不影响代码含义的修改,比如空格、格式化、缺失的分号等
test增加确实的测试或者矫正已存在的测试
+

作用域即表示范围,可以是任何指定提交更改位置的内容。主题则包括了对本次修改的简洁描述,有以下三个准则:

+
    +
  1. 使用命令式,现在时态:“改变”不是“已改变”也不是“改变了”
  2. +
  3. 不要大写首字母
  4. +
  5. 不在末尾添加句号
  6. +
+

下图是 NocoBase 的 commit message 截图,可供参考

+
commit_message
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/atom.xml b/atom.xml new file mode 100644 index 00000000..4deb60eb --- /dev/null +++ b/atom.xml @@ -0,0 +1,1933 @@ + + + https://www.guanngxu.com + Guanngxu + 2024-05-11T04:44:37.844Z + https://github.com/jpmonette/feed + + + Keep trying + https://www.guanngxu.com/images/avatar.png + https://www.guanngxu.com/favicon.ico + All rights reserved 2024, Guanngxu + + <![CDATA[LDO 基础知识]]> + https://www.guanngxu.com/L3EbniCYD/ + + + 2024-05-09T00:52:56.000Z + +

参考内容:

+

ADI 公司 LDO 电容选型指南

+

线性和低压降 (LDO) 稳压器

+ +

BUCK 电路通过控制占空比来达到降压的目的,添加 LC 二阶低通滤波器将高频部分滤除,即可达到稳定输出直流的目的。但是滤波不能完全滤除高频分量,BUCK 从原理上就决定了其纹波不容易做到很小,其固有的开关频率会导致电源噪声很大,用来给噪声敏感的元器件供电就不合适。

+

相比 BUCK 来说,LDO(Low Dropout Regulaor:低压差线性稳压器)输出的电压会更加平稳,可以弥补 BUCK 输出纹波大的缺点。

+

总体框图

+

线性稳压器主要由四部分组成,基准源用于提供精准的电压基准、导通器件用于控制从 VIN 到 VOUT 的电流大小、误差放大器将强制反馈节点与基准电压匹配、反馈电阻用于调整以改变输出电压。

+
+

从框图中也可以看到线性稳压器只能用于降压,因此输入电压必须高于输出电压。当然其名字中本身带了低压差的,低压差就意味着少的发热,意味着电源转化效率的提升。线性则是指器件的工作状态,器件的内部模块工作在放大区,放大状态呈线性关系。

+

工作原理

+

线性稳压器的工作可以模拟为两个电阻器和一个用于 VIN 的电源,其中电源用于给负载供电,通过调整可变电阻(导通器件)的阻值来控制负载电阻所获得的电压,整个系统中唯一不变的恒定的参数就是输出电压 VOUT。

+
+

其稳压过程如下图所示,当负载电压升高/降低时,采样电路所采到的电压就跟着升高/降低,传递给误差放大器后通过调节导通器件的导通程度来调节输出电压。

+
+

导通器件

+

导通器件常见的有 PMOS、NMOS、BJT 等。BJT 应用于大电流的场景。PMOS 不需要额外的电源轨即可控制其导通程度,但是相比 NMOS 其 RDSon 更大,即 PMOS 架构的 LDO 在芯片本身所消耗的能量会更大。

+
+

使用 NMOS 作为导通器件时,需要添加辅助电源轨或者使用电荷泵才能将 NMOS 打开。当然电荷泵也有其缺点,虽然电荷泵可以提升 VIN,但是也带来了额外的噪声影响。若采用辅助电源轨时则需要注意,VBIAS 会影响 NMOS 的导通程度,进而影响输出电压的大小。

+
+

PSRR

+

PSRR(Power Supply Rejection Ratio)量化了 LDO 抑制任何电源变化传递到其输出信号的能力,也就是 PSRR 决定了输入耦合到输出的噪声有多少。除了 LDO 本身的设计影响 PSRR 外,也可以通过调整 VIN 与 VOUT 之间的差值、输出电容来提高在特定应用(频率)下的 PSRR。

+
+
+

输入输出电容

+

为了确保 LDO 稳定工作,会在 LDO 输入输出端增加旁路电容,并且旁路电容的 ESR 需要很小,即在符合最小电容和最大 ESR 的要求下,使用任何质量良好的电容都可采用。在选择电容时还需要注意由于直流电压偏置、温度变化、制造商容差等需要对电容进行一定的降额。

+

输出电容除了可以进行滤波外,还会影响负载电流的变化的瞬态响应,采用较大的输出电容可以改善 LDO 对大负载电流变化的瞬态响应。输入电容则可以降低电路对 PCB 布局的敏感性,尤其是在长输入走线或者高源阻抗的情况下。

+

多层陶瓷电容、固态钽点解电容、铝电解电容通常用作输入和输出旁路电容。多层陶瓷电容具备 ESR 和 ESL 低、工作温度范围宽的优点,但是陶瓷电容中的介质材料具备压电性,振动或机械冲击可能会转化为电容上的交流噪声电压,在极端情况下可能会产生 mV 级的噪声。

+
+

压电性是在某些固体材料(晶体、陶瓷、骨头、DNA、蛋白质等)受到机械应力作用后,在材料中聚集电荷的现象。「压电」即由压力产生的电。

+
+

钽电容的优点是单位体积电容最高(CV 乘积),并且不太容易受到温度、偏执电压、震动效应的影响,在无法容忍压电效应的低噪声应用中,钽电容基本是唯一可行的选择。与陶瓷电容相比,钽电容的泄漏电流要比等值的陶瓷电容大很多倍,不适合超低电流应用。

+

铝电解电容往往体积较大、ESR 和 ESL 较高,漏电流相对较高,与钽电容一样不受压电效应影响,适合要求低噪声的应用场合,但是铝电解电容在航天应用中禁止使用。

+]]>
+
+ + <![CDATA[半导体功率器件]]> + https://www.guanngxu.com/xdxGDHzvU/ + + + 2024-05-04T23:48:51.000Z + 高中时候我们在化学课程中学过元素周期表,「氢氦锂铍硼、碳氮氧氟氖......」倒背如流,在元素周期表的中间三、四、五族元素定义为半导体元素,所谓半导体是根据其导电能力来定义的,我们可以通过一定的半导体工艺来改变其导电能力。

+

以硅(Si)为例,硅是处在第四族的元素,它的外部有 4 个电子,所以硅的稳定结构是形成下图所示的及其稳定的共价键结构。

+
+

硅的两侧对应的是三族和五族的元素,三族的元素意味着外层有 3 个电子,五族的元素意味着外层有 5 个电子。如果把三族元素插入到四族的硅当中,由于硅想形成稳定的四个共价键结构,所以会存在一个空置的位置,我们称之为空穴,这个空穴是具备一定的正电荷的能力的,如此就形成了 P 型半导体

+
+

同理,若使用五族元素与硅进行掺杂,就会多出一个可移动的电子,即存在自由电荷,形成了 N 型半导体

+
+

PN 结(普通二极管)

+

当我们把 P 型半导体和 N 型半导体进行组合后,即可得到最基本的二极管(PN 结)。在 P 型半导体中存在高浓度的空穴(正电荷),在 N 型半导体中存在高浓度的电子,浓度高的载流子会自然而然向浓度低的区域进行扩散。

+

由于载流子扩散,最终会形成一个势垒,这是一个空间电荷区,也称之为耗尽层。可以发现 PN 结存在 P 区、耗尽层、N 区三个区域,这几个区域都是呈现电中性的,不管是空穴还是电子想要到达另一个区域,都必须要穿过耗尽层,即耗尽层会阻碍空穴和电子的运动,因此整个 PN 结在没有外界干扰的情况下,是不具备导电能力的。

+
+

当我们从外界施加 N 到 P 的电场时,即 PN 结反偏。此时外界电场与耗尽层电场是同向的,所以在外部电场的作用下,耗尽层的宽度会被加强,于是 PN 结的导电能力就变得更弱,因此就呈现了一个无导电能力的特性。

+

当然导电只是一种相对情况,即便空间电荷区变宽了,也不能百分百保证说就完全没有导电能力,因为还是有一定的空间电荷浓度,在这样的情况下会有微弱的电流流经 PN 结,意味着系统存在一个反向电流,这就是二极管一个比较重要的漏电流参数。

+
+

当外部施加的电场是从 P 到 N 时,即 PN 结正偏。外界电场的效果是使耗尽层变窄,加强了 P 区内空穴往 N 区内移动的能力,扩散电流远大于漂移电流,形成了一个正向导通电流。

+
+

最终二极管将呈现如下的导通特性,当正向电压大于势垒电压时,二极管开始导通。当施加反向电压时,二极管将截止,当反向电压大到一定程度后,二极管就会被反向击穿,即二极管损坏的过程。

+
+

功率二极管

+

既然谈到了「功率」二字,那么更加关注的就是二极管承载电流、电压的能力了。如何把二极管承载电流、电压的能力加强呢?根据上文关于二极管的介绍可以知道,将耗尽层加宽可以承载更大的电压。

+

图中中间 n- 为轻度参杂区域,下面 n+ 为重度参杂区域,这个参杂就导致了耗尽层的加宽,当然也导致导通损耗更大,不过也正因为如此,功率二极管才更加能耐压。

+
+

我们以非同步 BUCK 电路为载体,来说明一下功率二极管的变化过程。

+
+
    +
  1. 图中(1)部分指二极管导通,有一个小小的二极管导通压降,因此曲线没有贴着 x 轴;
  2. +
  3. 图中(2)的位置由于二极管承受的是反向电压,此时它关断了,所以电压为负;
  4. +
  5. 图中(3)二极管需要经历一个从没有电压到有外加电压的变化,当电压加到二极管上时,二极管中的载流子流动的趋势逐渐增大,宏观表现出来是电阻慢慢变小的过程,但是电流保持不变,所有会有一个小尖峰,这一小段时间也会导致整体功率的损耗,开关频率越高,这个导通过程导致的损耗越多;
  6. +
  7. 图中(4)处伴随系统从通到断的状态变化,大规模载流子需要进行重新分配,这个重新分配表现出来就是电流,而且这个电流与主电流相反,所以会看到一个反向的电流,而且这个反向电流会施加在主电路里面。这一段反向电流又分为两部分,下降阶段是之前外加电压时,PN 结中从 P 区域移动到 N 区域的载流子移除(恢复)过程,即从正偏到反偏的过程,正偏时空间电荷区非常非常窄,此时要进入反偏状态,空间电荷区需要加强,载流子需要重新分配,外部激励会移除不必要的空间电荷。电流上升的过程,即二极管又变成一个耐压器件了,也就是空间电荷区加宽,更多的载流子会不均匀的分布在两端。整个过程不可避免的需要移动电荷,而电荷的聚集效应可以认为就是一个电容的效应,当我们需要施加电压时,电压的增加就会需要额外的电荷,电荷不断聚集提供相反电荷,使其电压不断增加,以致增加到刚好截止输出电压为止。
  8. +
+

MOS 管

+

以 NMOS 为例,它以 P 型半导体衬底,以 N 型半导体作为导电沟道,金属部分作为栅极(Gate),氧化部分(SiO2)作为绝缘层,两端分别为源极(Source)和漏极(Drain),从物理结构可以看出 MOS 管的源极和漏极是可以互换的,不像三极管有严格的顺序。

+

在栅极和源极施加电压,随着电压的不断增大,导电沟道将逐渐形成,当导电沟道刚好形成时的电压,称之为开启电压。外加电压继续增大,导电沟道将变得越来越宽,即导电能力越来越强。

+
+

PMOS 相比 NMOS 更加容易驱动,只需要 VGS 小于一定值即可导通。但是 PMOS 的导通电阻比 NMOS 要大,并且成本也比 NMOS 要高,所以比 NMOS 的实际应用场景要少许多。

+
+

功率 MOS 管

+
+

对比前文普通 MOS 管,可以看到源极、栅极、漏极是分开的,顶上那个灰色的板子是金属板。而功率 MOS 管在这个基础上做了一点创新,下图中的阴影部分就是金属板,可以发现总共只有两个金属板,上面的金属板把 N 区和 P 区都给连起来了,所以即使在栅极没有加电压的时候,也会存在一个天然的二极管通道,但是普通 MOS 管是没有体二极管通道存在的。同时由于是功率 MOS 管,所以也会想办法将耗尽层加宽,以增加其耐压能力。

+
+

体二极管和耐压能力的加强是功率 MOS 和普通 MOS 的区别。

+
+

功率 MOS 管的正向导通能力就是涉及「场效应」了,所谓的场效应即意味着外部可以通过电场来控制其内部载流子的浓度,在栅极施加正电压时就会产生一个电子的导电沟道,由于整体是 N 型半导体衬底,所以整体也就形成了一个电子的导电沟道,并且该沟道支持电子的双向移动。

+
+

如下图所示是功率 MOS 管的等效电路模型。其主要损耗由三部分组成,分别为导通损耗、开关损耗(开通损耗和关断损耗)、驱动损耗。其中导通损耗与开关损耗容易理解,驱动损耗作何理解呢?MOS 并不像二极管是一个被动型器件,MOS 管开或关的行为都需要能量作为代价,就好比要打开机械开关需要用手去按压,这个过程所消耗的能量就是驱动损耗。

+
+

晶体管

+

二极管只有一个 P 型半导体和一个 N 型半导体结合,如果再加一个 N 型半导体(或 P 型半导体)即构成了晶体管(三极管),晶体管有集电极、发射极、基极三个极。

+
+

需要注意的是三极管的集电区和发射区掺杂浓度是不一样的,其中基区多子少且做的很薄,而发射区的多子浓度很高,集电区多子浓度相对较低但面积大。不管三极管是正接还是反接,三极管都处于截止状态,这是因为三极管可以看作两个二极管反向相连,不论如何接都会有一个二极管处于截止状态。

+
+

为了能让三极管导通,我们在基极和发射极再施加一个电压,此时二极管开始导通,发射区的自由电子就可以源源不断的流向基区,但是基区的掺杂浓度很低且很薄,基区短时间内吸收不了太多的电子,只有一少部分电子能与空穴复合形成基极电流,而大部分被吸引到了集电区,形成集电极电流,也就是三极管的输出电流。

+
+

流过基极的电流越大,流到基区的自由电子也就越多,相应的被吸引到集电区的电子也就更多,这就是三极管小电流控制大电流的原理。基区做的很薄是为了让发射区的电子更容易进入集电区,浓度很低视为了形成更小的基极电流,这样才会有更多的自由电子流向集电区。

+

IGBT

+

三极管工作时涉及载流子的注入和抽离所以会很慢,由于其性能的关系正在逐步退出历史舞台,因此需要对其进行改进,改进后的器件就是 IGBT,如下图所示。

+
+

可以发现 IGBT 是一个受 MOS 管控制的 BJT,即同时继承了 MOS 管快速和 BJT 大电流的优点。当然,它也有缺点,并且缺点主要来自于 BJT 关断较慢的问题,因为当 MOS 管门级信号撤出时,并不能立马把电流都抽走,所以电流会经历一段下降时间。

+
+]]>
+
+ + <![CDATA[BUCK 电路基础知识]]> + https://www.guanngxu.com/br0hmOiiG/ + + + 2024-03-30T06:47:09.000Z + +

参考内容:

+

手撕Buck!Buck公式推导过程

+

电力电子的基础应用

+

《精通开关电源设计(第二版)》

+

Buck电源芯片输出有问题?检查这几样

+

原来PWM这么简单!

+

为什么 DC-DC 芯片设计中都有一个自举电容?

+

如何克服开关电源中的最小导通时间挑战

+ +

BUCK 电路构建

+

根据高中所学习的物理知识可以很容易的想到,使用一个滑动变阻器即可实现降压和稳压的效果。当负载波动时,通过改变滑动变阻器的阻值,可以调节负载所获得的电压。但是使用滑动变阻器的劣势也很明显,大量的耗能会导致器件温度快速升高。

+
+
+

上面所提到的电路主要缺点在于导通器件(变阻器或三极管)本身存在耗能,那么有没有不会耗能的导通器件呢?首先肯定不能选导线,不然又回到最原始的问题,所有电压都被加到负载上了。有没有能不耗能且能控制加在负载电压的导通器件呢?最常见的机械开关就能做到这个效果。

+
+

当开关闭合时,负载即获得电压源输出的电压;当开关打开时,负载所获得的电压为 0V。计算平均值可以确定达到了降压的目的,通过控制开关闭合的时间长短,就可以达到调节电压的效果。但仔细想想就会发现不对劲,电路并不会帮助我们计算平均值,负载所获得的电压波形如下图所示,是完美的方波,并不是一条直线。

+
+

控制开关闭合的时间,即后文要讲的控制占空比

+
+
+

此时很容易就能想到利用电容两端电压不能突变的特点,给负载并联一个电容即可,电容即保证负载可以获得连续的能量流。

+
+

一旦引入了电容,就需要考虑浪涌电流的问题。根据公式 Q=CV=ItQ=CV=It 可得 I=CVtI=\frac{CV}{t},开关闭合时电压在非常短的时间内升高,所以电流会突然变得很大。

+

我们当然可以简单的利用电阻来抑制浪涌电流,但不幸的是电阻总要消耗功率。为了最大限度的提高效率,可以考虑使用电感,电感本身不消耗任何能量,只会进行储能,且其无损限流的能力正好可以用来抑制电容的浪涌电流。

+
+

引入电感后可以发现当开关打开时,电感没有续流回路,因此需要想办法构造电感的续流回路。续流回路需要保证不论开关打开还是闭合,电流都流向负载,且开关闭合时电源正极与负极回路必须经过电感与负载。这个需求很符合二极管的特点,即只允许单向导通。

+
+

到目前为止我们构建了非同步 BUCK 电路,考虑到机械开关容易磨损、使用寿命短、有机械惯性(转换频率低)的问题,我们需要将机械开关换成转换频率高的半导体器件,此处我们选择 NMOS 管来替代开关。

+
+

选择 NMOS 和 PMOS 的主要区别在于驱动电路的设计

+
+
+

可以发现当 NMOS 开关管导通时,续流二极管处于截止状态;当开关管关断时,续流二极管处于导通状态。即二极管的导通和截止和开关管的截止导通是同步的,也就是说二极管起到的是一个开关的作用。而且考虑到电流从二极管流过期间,二极管两端的压降恒定为导通电压 0.7V,二极管所消耗的能量较大。因此我们也可以把二极管换为导通电阻更小的 NMOS 管。

+
+

为了提高 BUCK 电路的稳定性,防止由于输入纹波带来异常,我们在 BUCK 电路的输入端并联一个电容,用于滤除输入电压的纹波。

+
+

至此我们就搭建了一个标准的同步 BUCK 电路,我们将其简单变个样子,再加几个标签即可得到下图。在同步 BUCK 电路中需要两个开关管密切配合,以防止整个线路导通,所以它们之间需要保持一定的相位关系,即上管导通下管截止;上管截止下管导通,我们把这种关系称之为同步

+
+

信号角度理解 LC

+

我们以占空比为 0.5 来进行说明,将时域下的方波转换到频域,通过傅立叶变换可以分解出一系列的频率分量。其中包含频率为 0 的分量,即直流分量,也就是我们想要保留的部分,还有频率为 n 倍 fsf_{s} 的分量。那么如何把我们不想要的部分去掉呢?从滤波角度考虑就需要加入一个低通滤波器。

+
+

通过加入低通滤波器可以把高频分量滤除,把二阶低通滤波器的截止频率设置在 0 到 fsf_{s} 之间,即可把 fsf_{s} 所有以上的部分给滤除。整体达到的效果即通过一个 LC 低通滤波器,配合一个开关网络,将一个数字化的电平重新滤出,得到一个比较平缓的电压输出,这个过程即完成了电压从高到低的转换。其中直流分量的大小受占空比 D 控制,所以通过改变占空比 D 即可改变输出电压大小。

+
+

稳态分析

+

我们需要先强调一下前提,此处我们说的稳态分析,即输入电压输出电压都是稳定,且纹波足够小的状态。下文的所有计算都将基于稳态进行分析,并且是在 (F)CCM(连续导通模式)下计算的。

+

我们将一些已知条件列出来:

+
    +
  • 输入电压:ViV_{i}
  • +
  • 输出电压:VoV_{o}
  • +
  • 负载电阻:RR
  • +
  • 输出电感:LL
  • +
  • 开关频率:ff
  • +
+

伏秒平衡

+

当上管导通下管截止时,电感右边的电压为 ViV_{i},左边的电压为 VoV_{o},因为同步 BUCK 电路是降压电路,所以 Vi>VoV_{i}>V_{o},所以电感两端电压即为 ViVoV_{i}-V_{o},也就是说是一个恒定值。由于有 Ldidt=ViVoL\frac{\mathrm{d} i}{\mathrm{d} t} =V_{i}-V_{o},所以 didt=ViVoL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{V_{i}-V_{o}}{L},即电感电流的上升斜率,由于是稳态前提,所以可以确定该值是一个常数。

+

当上管截止下管导通时,电感右边电压为 VoV_{o},左边电压为 00,所以电感两端电压为 0Vo0-V_{o},即 Vo-V_{o}。由于 Ldidt=VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-V_{o},所以 didt=VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-\frac{V_{o}}{L},即电感电流的下降斜率,也是一个常数。

+

整个电路处于稳定状态,负载电路恒定,那么在一个周期内,电感电流增加的量肯定等于电感电流减小的量,即充了多少电就要放多少电,不然负载的电流或电压将会发生变化。

+

前文已有didt=UL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{{U}}{L},而 LL 恒定,那么电感电流的变化速度即与电压成正比关系,即电感电流上升(下降)的斜率与电压成正比关系。而电感电流上升和下降的高度相同,那么上升时间和下降时间就自然构成反比关系。

+

TonToff=VoViVo\frac{T_{on} }{T_{off} } = \frac{V_{o}}{V_{i}-V_{o}},将其进行简单变换即可得到闻名江湖的伏秒平衡法则

+

Ton(ViVo)=ToffVoT_{on}(V_{i}-V_{o}) = T_{off}V_{o}

+

占空比

+

已知 T=Ton+Toff=1fT=T_{on}+T_{off}=\frac{1}{f},结合伏秒平衡法则可以计算出:

+

开通时间:Ton=VoVi1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f}

+

关断时间:Toff=ViVoVi1fT_{off} = \frac{V_{i}-V_{o} }{V_{i} } \bullet \frac{1}{f}

+

占空比:D=TonT=VoViD = \frac{T_{on} }{T}=\frac{V_{o} }{V_{i}}

+

纹波电流

+

由于输出电压不变,也就是说输出电容两端的电压没有变化,即输出电容的平均电流为 0。根据输出节点的基尔霍夫电流定律可知,输出节点电流和为 0,那么功率电感的平均电流就等于负载的平均电流,即IL=Io=VoRI_{L} = I_{o} = \frac{V_{o} }{R}

+
+

上文计算电感电流斜率时已经能确定电流波形是个三角波,纹波电流等于在开关导通时电感电流的增大值,也等于在关断时电感电流减小的值,计算任意一个即可得到纹波电流。我们以上管导通时增大的电感电流计算。

+
+

上管导通时电感两端电压为 ViVoV_{i}-V_{o},导通时间为 Ton=VoVi1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f},根据 U=LdidtU=L\frac{\mathrm{d} i}{\mathrm{d} t} 可知:

+

IL=di\triangle I_{L} =di

+

=TonUL=T_{on}\bullet \frac{U}{L}

+

=ViVoLVoVi1f=\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}

+

根据理论计算可以发现,电感电流的纹波和负载电流的大小没有关系,但是负载电流与平均电感电流是相等关系。

+

功率电感选择

+

根据上文的信息进一步可以计算出电感的峰值电流:

+

ILP=Io+IL2I_{LP} =I_{o}+\frac{\triangle I_{L}}{2}

+

=Io+ViVo2LVoVi1f=I_{o}+\frac{V_{i}-V_{o}}{2L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}

+

那么在选择功率电感时,电感的饱和电流就必须要大于这个ILPI_{LP},并且需要留有一定的裕量。实际应用时电感的纹波电流应是平均电流的 30%30\%50%50\% 为宜,我们将这个参数称之为电流纹波率 r。根据电流纹波率范围就可以计算出电感值的范围:

+

IL=ViVoLVoVi1f\triangle I_{L} =\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}

+

L=ViVoILVoVi1fL =\frac{V_{i}-V_{o}}{\triangle I_{L}}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}

+

=ViVo(0.30.5)IoVoVi1f=\frac{V_{i}-V_{o}}{(0.3至0.5) I_{o}}\bullet \frac{V_{o}} {V_{i}}\bullet \frac{1}{f}

+

=(ViVo)Vo(0.30.5)IoVif=\frac{(V_{i}-V_{o})V_{o}}{(0.3至0.5) I_{o} V_{i} f}

+

为何 r 为 0.3~0.5

+

电流纹波率即是电感电流的交流分量与其相对应的直流分量的比值,一旦 r 确定,那么输入输出滤波电容的电流、开关管的有效电流等都确定了,因此 r 的选择会影响器件选择和芯片的成本。使用公式可以表述为:

+

r=IIL=2×IACIDCr=\frac{\triangle I}{I_{L}}=2\times \frac{I_{AC}}{I_{DC}}

+

一般认为,电感体积与其能量处理能量成正比,因为要处理更高的能量就需要更大的磁芯。选择电感磁芯的能量处理能力至少要等于其需存储量,即 E=12×L×Ipk2E=\frac{1}{2} \times L \times I_{pk}^{2},下图是 E 与 r 的的函数曲线,可以发现在 r=0.4r=0.4 附近有一个拐点。

+
+

选择的 r 如果较 0.4 低很多,则所需要的电感体积越大;而若继续增大 r,则电感的体积并不会减少多少,即当 r 超过 0.4 后,通过增加 r 来减少电感体积的效果已经不明显了。

+

输入纹波

+

电源输入功率为 Pi=ViIiP_{i}=V_{i}I_{i},负载功率为 Pr=VoIoP_{r}=V_{o}I_{o},不考虑开关损耗、导通损耗等等因素,那么输入功率和输出功率相等,可得输入平均电流为 Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}

+

输入电压纹波就是输入电容上面电压的变化,这个变化可以分为两部分。一部分为电容充放电所导致的电压变化 UqU_{q},另一部分为电流流过电容 ESRESR 导致的压降 UesrU_{esr}。即 Vi=Uq+User\triangle V_{i} = U_{q} + U_{ser}

+

Q=CiUq=Iit=IiToff\because Q = C_{i}U_{q} = I_{i}t = I_{i}T_{off}

+

Uq=IiToffCi\therefore U_{q} = \frac{I_{i}T_{off}}{C_{i}}

+

Toff=ViVoVif\because T_{off} = \frac{V_{i}-V_{o} }{V_{i}f }

+

Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}

+

Uq=VoIoCiVifViVoVi\therefore U_{q} = \frac{V_{o}I_{o}}{C_{i}V_{i}f}\bullet \frac{V_{i}-V_{o}}{V_{i}}

+

要想知道 ESRESR 所造成的纹波,只需要知道流过输入电容的电流即可。当上管断开时,电源输入电流 IiI_{i} 全部流入电容 CiC_{i}。电感电流原本从下管的体二极管续流,当上管导通后,变为了从上管续流。因为此前电感一直处于放电状态,所以切换的那一刻电感电流是最小的,为 ILIL2I_{L}-\frac{\triangle I_{L}}{2}

+

在整个 TonT_{on} 时间内,电感都被充电,电感电流一直都在增大,直到 IL+IL2I_{L}+\frac{\triangle I_{L}}{2},并且在 TonT_{on} 时间内,电感电流都是走的上 MOS 管通路,所以上 MOS 管最大电流也是 IL+IL2I_{L}+\frac{\triangle I_{L}}{2}

+

根据基尔霍夫电流定律可知,输入节点的电流和为 0,那么输入电源电流 IiI_{i} 和电容 CiC_{i} 的放电电流就等于通过上 MOS 管的电流。所以 CiC_{i} 的最大放电电流即为 IL+IL2IiI_{L}+\frac{\triangle I_{L}}{2} - I_{i}。我们约定充电为正,放电为负,则放电电流为 IiIL2ILI_{i} - \frac{\triangle I_{L}}{2} - I_{L}

+
+
+

上管截止时 ESRESR 的压降为 IiESRI_{i} \bullet ESR,上管导通时压降为 (IiIL2IL)ESR(I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESR,则可得:

+

User=IiESR+(IiIL2IL)ESRU_{ser} = I_{i} \bullet ESR + (I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESR

+

=(IL+IL2)ESR=(I_{L} + \frac{\triangle I_{L}}{2}) \bullet ESR

+

IL==ViVoLVoVi1f\because \triangle I_{L} ==\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}

+

IL=IoI_{L} = I_{o}

+

User=(Io+(ViVo)Vo2ViLf)ESR\therefore U_{ser} = \left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR

+

综上所述可得:

+

Vi=Uq+Uesr\triangle V_{i} = U_{q} + U_{esr}

+

=VoIoCiVifViVoVi+(Io+(ViVo)Vo2ViLf)ESR=\frac{V_{o}I_{o}}{C_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}} +\left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR

+

输入电容选择

+

考虑到电容的实际使用情况,陶瓷电容的 ESRESR 小,容量小,所以 UqU_{q} 对纹波起决定性作用,输入纹波可近似为 UqU_{q}。若选择铝电解电容,则 ESRESR 大,容量大,UesrU_{esr} 对纹波起到决定性作用,输入纹波可以近似为 UesrU_{esr},假设电路设计要求输入纹波不能大于 Vi\triangle V_{i},则有:

+

陶瓷电容:CiVoIoViVifViVoViC_{i} \ge \frac{V_{o}I_{o}}{\triangle V_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}}

+

铝电解电容:ESRViIo+(ViVo)Vo2fLViESR \le \frac{\triangle V_{i}}{I_{o} + \frac{(V_{i}-V_{o})V_{o}}{2fLV_{i}} }

+

输出纹波

+

输出纹波与输入纹波同理,亦是 Vo=Uq+Uesr\triangle V_{o} = U_{q} + U_{esr},我们画出负载、功率电感、输出电容三者的电流波形。其中电感的纹波电流是 IL\triangle I_{L},则电容的纹波电流也是 IL\triangle I_{L},又因为电容的平均电流为 0,所以充电电流和放电电流都是 IL2\frac{\triangle I_{L}}{2}

+

电容充放电的总电荷量 Q 等于电流乘以时间,即图中阴影三角形的面积,三角形底部时间为 T2\frac{T}{2},高为 IL2\frac{\triangle I_{L}}{2},所以总的放电量可以计算出来为 Q=12T2IL2Q=\frac{1}{2} \bullet \frac{T}{2} \bullet \frac{\triangle I_{L}}{2}

+
+

结合 Q=CoUqQ=C_{o}U_{q} 可得:

+

Uq=(ViVo)Vo8ViCoLf2U_{q} = \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} }

+

由前面电流波形可知,电容的充电电流最大是 IL2\frac{\triangle I_{L}}{2},放电电流最大是 IL2-\frac{\triangle I_{L}}{2},则可以得到 ESRESR 引起的总压降为:

+

User=IL2ESR(IL2ESR)U_{ser} = \frac{\triangle I_{L}}{2} \bullet ESR - (-\frac{\triangle I_{L}}{2} \bullet ESR)

+

IL=ViVoLVoVi1f\because \triangle I_{L} = \frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}

+

(ViVo)VoViLfESR\therefore \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR

+

最终可得:

+

Uo=Uq+Uesr\bigtriangleup U_{o} = U_{q} + U_{esr}

+

=(ViVo)Vo8ViCoLf2+(ViVo)VoViLfESR=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} } + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR

+

=(ViVo)VoViLf(ESR+18fCo)=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf}\bullet \left ( ESR + \frac{1}{8fC_{o}} \right )

+

输出电容选择

+

与输入电容选择的方式一致,考虑是容值还是 ESRESR 占主导地位,假设要求输出纹波要小于 Vo\triangle V_{o},则有:

+

陶瓷电容:Co(ViVo)Vo8ViVoLf2C_{o} \ge \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}\triangle V_{o}Lf^{2} }

+

铝电解电容:ESRVoViLf(ViVo)VoESR \le \frac{\triangle V_{o}V_{i}Lf}{\left ( V_{i}-V_{o} \right ) V_{o}}

+

电感续流模式

+

电感电流曲线不能断续(无突降),因为电流断续会引起实际不可能发生的能量断续现象。但是电流的变化率可以突变,比如从上升斜率(电感储能增加)变为下降斜率(电感储能释放),尽管这样电感电流也必须连续。根据稳定状态下每个周期电流是否回到零,划分为不同的导通模式,并且通过减小负载电流,可以使电路从 CCM 经过 BCM 最终转变为 DCM。

+

CCM(连续导通模式)

+

稳定状态下每个周期,电流都回到某一非零值,称之为连续导通模式(CCM:Continuous Conduction Mode)。CCM 是功率变换中最常见的工作模式,有输出纹波小但功耗高的特点。

+

FCCM(强制连续导通模式)只存在于同步 BUCK 中,由于使用 MOS 管将非同步拓扑的二极管取代,MOS 管的导通压降远低于二极管压降,除了显著减小了续流通路的导通损耗外,也允许电感电流反向,即从负载瞬时流出电流。

+
+

DCM(断续导通模式)

+

若稳定状态下每个周期中电流都会回到零,那么就称之为断续导通模式(DCM:Discontinuous Conduction Mode),DCM 由于其电感电流的不连续,计算平均电感电流就需要更加详细复杂的公式,这也是 DCM 方程看上去复杂的根本原因。

+
+

BCM(临界导通模式)

+

BCM 是临界导通模式(Boundary Conduction Mode),由控制器监控电感电流,一旦检测到电流等于 0,功率开关立即闭合,控制器总是等电感电流「复位」来激活开关。即 BCM 处于 CCM 和 DCM 之间,可以将其视为 CCM 和 DCM 的极端情况,所以 BCM 模式下可以自由的选择 CCM 或 BCM 方程。

+
+

DC-DC 功能框图

+

前文所构建的 BUCK 电路只能是纸上谈兵,还需要解决诸多问题才能应用于实际电路。

+

基础驱动与控制

+

首先需要解决的问题就是 MOS 管不可能平白无故就打开,所以我们需要添加 MOS 管驱动器。

+
+

理想情况是上管关闭,下管立刻打开,中间没有任何时间差,但是 MOS 管并非理想开关,从关断到导通存在一个过渡的过程,若同时导通则电源通过上下 MOS 管直接对地短路,很容易就会导致 MOS 损坏,甚至可能会把前一级电源也损坏,所以上下管同时导通的状态必须得避免。

+

为了避免上下管直通的情况,实际应用会故意让上管和下管切换时多等一会儿,宁愿出现同时关断的情况,也不能出现同时导通的状态,这个等待的过程就叫做死区时间

+
+

需要注意的是,在死区时间内虽然下管没有被导通,但是功率 MOS 管本身存在一个寄生二极管,这个寄生二极管可以像非同步 BUCK 那样帮助电感续流,而且这个时间非常的短暂,所以产生的功耗没有那么大,因此不必担心系统会出问题。

+

到目前为止,不知道您有没有发现我们都在自嗨,系统中并没有用来控制上下 MOS 导通和关断的信号。因此需要增加一个振荡器用来产生控制信号,注意我们在前文中使用的是占空比一词,也就是说我们要使用的是 PWM(脉冲宽度调制)。当然你也可以使用 PFM(脉冲频率调制),本文只介绍 PWM 方式。

+
+

PWM(脉冲宽度调制)

+

PWM 的全称是脉冲宽度调制(Pulse-width modulation),是通过将有效的电信号分散成离散形式,从而来降低电信号所传递的平均功率的一种方式。其基本实现原理是通过锯齿波/三角波(载波)与所需要合成的波形(调制波)进行比较,然后确定 PWM 所需要输出的极性。因为一般都是用到开关器件上,通常是 ON 或者 OFF,具体如下图所示。

+
+

将振荡器输出的锯齿波和参考值 VthV_{th} 进行比较,就可以输出 PWM 波形了。话不多说,上图就明白了。

+
+

上图中的锯齿波(橙色)最大为 10,但是我们希望输出平均为 5 的波形(图中紫色的水平线),那么通过比较器进行比较,当锯齿波小于 5 时,PWM 即输出低电平 OFF,当锯齿波大于 5 时,PWM 即输出高电平 ON,此时的占空比即为 50%。

+

若是想输出一个电压逐渐抬高的波形,即占空比逐渐增大,那只需要将调制的波形设置为斜坡输出即可达到效果。比如下图中可以看到,占空比从 0% 逐渐增大到 100%。

+
+

同样的道理,我们可以通过改变调制波形,进一步调制出来其它的波形,比如要调制一个正弦波(sin wave),也就是我们常说的 SPWM,那么就是下面的样子。

+
+

负反馈环路

+

有了调制信号,开关管也可以正常打开与关闭,看起来可以应用到实际电路中了,但是别忘了负载的电阻并不是恒定的,负载的变化必然会引起输出电压的波动。为了减小输出电压的波动,我们可以在输出端添加分压电阻,与误差放大器和基准电压一起构成负反馈回路,这种通过取样输出电压进行闭环反馈的方式称之为电压模式控制

+
+

误差放大器的输入端分别为带隙基准源输出电压采样,当输出电压减小/增大时,与基准电压的细微差异都会被误差放大器放大,今儿调节脉冲宽度来达到调节调整输出电压的目的。图中 R2 接地,所以可以很容易计算出输出电压与分压电阻的关系:Vout=Vref(R1+R2)R2V_{out} = \frac{V_{ref}(R_{1}+R_{2})}{R_{2}}

+

除了输出电压可以用作控制取样信号,还有输入电压、输出电流、输出电感电压、开关器件峰值电流可以作为控制取样信号。使用这些信号可以构成单环、双环或多环反馈系统,进而实现稳压、稳流以及恒定功率的目的,也可以实现过流、过压、均流等功能。

+

现在回过头来评判一下电压模式控制的优缺点。单一的反馈电压闭环设计使得调试更加容易、对输出负载的变化有比较好的响应调节、占空比的调节也不会受到什么限制等等都是它的优点,但是其缺点也很明显。由于主电路有较大的输出电容和电感的相移延时作用,输出电压的变小/变大也延时滞后,再经过误差放大器的延时,使得瞬态响应变得更慢。由于电压控制模式不采样电流,逐周期限流保护功能必须另外增加电路来实现。

+

峰值电流模式控制在电压模式控制的基础上又增加了电流环,所以峰值电流模式控制是一个双环反馈系统。误差电压信号与一个变化的,其峰值代表输出电感电流峰值的三角波形进行比较,然后得到 PWM 脉冲的关断时刻。所以峰值电流模式控制不是使用电压误差信息直接控制 PWM 脉冲宽度,而是直接控制峰值输出侧的电感电流大小,进而间接的控制 PWM 脉冲宽度。

+
+

峰值电流在逻辑上与平均电感电流大小变化一致,但是峰值电感电流的大小并不能与平均电感电流的大小一一对应。在占空比不同的情况下,相同的峰值电感电流大小可以对应不同的平均电感电流大小,但平均电感电流大小才是唯一决定输出电压大小的因素。

+

为了解决不同占空比对平均电感电流大小的扰动作用,使得所控制的峰值电感电流最后收敛于平均电感电流,需要将电感电流下斜坡斜率的至少一半以上斜率加在实际检测电流的上斜坡上,这一点可以从数学上进行证明(具体咋证明暂不讨论)。

+

总结一下峰值电流模式控制 PWM 是双闭环控制系统,电压外环控制电流内环。电流内环是瞬时快速按照逐个脉冲工作的。功率级石油电流内环控制的电流源,而电压外环再控制次功率级电流源。电流内环只负责输出电感的动态变化,电压外环仅需控制输出电容,所以峰值电流模式控制 PWM 具有比电压模式控制大得多的带宽。

+

为了防止在应用过程中可能出现的短路等异常场景,DC-DC 少不了过温保护、过流保护、过压保护等保护手段。再设定一定的辅助功能,比如 PG 状态显示、缓启动、欠压保护等即可搭建完整的 DC-DC 电路。

+
+

异常模式

+

参考上文中的电路图,我们把绿色部分称之为控制电路,灰色部分是功率电路,功率电路中最核心的就是上下两个 MOS 管,下文我们讨论不同的异常场景中,控制电路、上管、下管三部分应该处于什么状态,其中控制电路关闭相当于整个芯片重启。

+

过压保护

+

当输出电压偏高并且达到了过压保护的阈值。过压状态需要控制电路去调整把输出电压降下来,所以不需要重启整个芯片。可以想到输出端已经处于过压状态了,上管如果打开那会加重过压的程度,因此上管需要关闭。若下管打开,则电感、负载、下管形成回路,即电感有续流回路,会把过压状态维持的时间更长,因此下管也需要关闭。综上有:过压保护:关上管、关下管

+

过温保护

+

温度过高的情况无非两种,一种是流过芯片的电流太大,即功率太大导致芯片自身发热达到了过温保护的阈值,此时关闭芯片肯定可以解决,另外切断电流回路也是可以解决的,即关闭上管。过温的第二种情况是由于环境温度过高而导致芯片温度过高,此时最好还是关闭芯片吧。综上有:过温保护:关闭芯片

+
+

关闭芯片指关闭芯片中的 BUCK 部分,但是基准源部分仍然保持工作

+
+

过流保护

+

过流保护还需要区分是正向过流还是负向过流,因为工作在 FCCM 模式的 DC-DC 在轻载或空载时,可能会有负向过流的情况。存在负向过流的另一原因也是因为同步 BUCK 没有像非同步 BUCK 那样的整流二极管,所以当存在负向过流情况时,直接模拟非同步 BUCK 中的二极管即可。综上有:负向过流保护:关下管

+

若发生正向过流时如何进行保护呢?首先考虑到电流经上管到负载,既然已经过流了那么肯定需要关上管。为了使电流减小的更快,那么就需要将电流流向地,所以需要将下管打开以构成回路。综上有:正向过流保护:关上管、开下管

+

异常排查

+

不管系统设计的多好,在实际应用中都可能会或多或少出现问题,比如电感选用不合适、触发 min-on time、触发 min-off time、输出电容 ESR 过大等,下面我们逐一进行讨论。

+

min-on time

+

虽然 MOS 管打开速度很快,但是打开始终是一个过程,要完成一个过程就必须需要一定的时间,当高频且压差大的情况下很容易触发完成「打开」这个过程的最小时间。也就是说占空比已经是实际最小了,占空比无法再降低了,所以查看输出电压纹波可能会出现下面的波形。

+
+

出现该波形的原因在于,占空比已经无法继续降低,所以电压整体处于逐渐抬高的趋势,当抬高到一定程度时即触发过压保护,上下管都关断,所以电压快速下降。

+

min-off time

+

与 min-on time 相对应的是 min-off time,当开关频率足够高且输入和输出电压接近时即容易出现此问题,此时即达到系统所能达到的最大占空比也无法满足负载所需要的电压,表现为输出电压无法达到设定值,负反馈分压电阻电压也低于电压基准值。

+

电感饱和电流过小

+

电感电流正常是一个三角波,但是如果电感饱和电流过小,则会电感电流将会变成下图很苗条的样子。因为电感电流饱和所以电流不再线性增加,电流快速增大导致磁通率减小,会导致磁性损耗增大、芯片热耗增大,而且这是一个正反馈过程,整个系统的可靠性会大大降低。

+
+

输出电容选用不合适

+

当输出电容选用过小时,会导致动态响应输出出现抖动。若输出电容的等效串联电阻(ESR)过大,也会导致输出纹波异常增大,这一点从前文的理论计算即可验证。因此在实际使用过程中需要同时考虑电容容值和所选电容的 ESR。

+

为什么需要 min-on time

+

占空比 D 控制相对于输入电压的输出电压,虽然通过提高开关频率有助于减小电感尺寸,但是也必须满足最小导通时间(min-on time)才能使芯片正常工作。那么这个 min-on time 是由哪些因素引起的呢?

+

因为上管中电流波形前沿的电流尖峰。由于 MOS 管也是由 PN 结组成,存在 PN 结就肯定存在结电容,MOS 管的寄生电容 CgsC_{gs}CgdC_{gd} 会导致上管在导通时电流突然变化,也就是说会出现电流尖峰。如果在这个电流尖峰的时间段内去检测电流的话,很可能就会触发过流保护,因此开关电路的最小导通时间必须大于电流尖峰出现的时间,这个时间我们称之为消隐时间

+
+

另一个原因是因为上下管开关完成后,由于键合线存在寄生电感的原因会产生很大的振铃,这个振铃同样可能会导致峰值电流检测出错,需要一个 min-on time 将这个振铃隔离过去。

+

为什么需要 min-off time

+

如下图所示,最简单的需要最小关断时间(min-off time)的原因是,若下管不打开则没有办法给自举电容充电,所以需要在该时间内给自举电容充电,为下一个开关周期做准备。

+
+

另一个原因是因为没有最小关断时间,即占空比 D 增大到 100%,那么就无法对负向电流、谷值电流进行采样,也就无法实现实现相应的异常保护功能。与 min-on time 一致,电流检测也需要一个 min-off time 隔离振铃。

+

为什么需要自举电容

+

DC-DC 的上 MOS 管可以是 PMOS,也可以是 NMOS。但是一般因为生产工艺问题,PMOS 导通电流往往做不不到很大,而在相同成本下 NMOS 的导通电流可以做到更大,也就是 RdsonR_{dson} 可以做到相对较低,所以往往更倾向于 NMOS。

+

将上管换为 NMOS 后也带来了新的问题,如何打开 NMOS ?如图所示,上管的 S 极连接 PH 点,该点的电压为 +5V,要打开 NMOS 需要 VGS>0V_{GS} > 0,驱动 MOS 管打开的压降需要 5V,那么驱动电压就需要 +10V 才可以打开上管,但是纵观整个电路并没有能达到 +10V 级别的电压,所以需要自举电容来进行升压才能打开上 MOS 管。

+
+

所以 DC-DC 芯片是否需要自举电容是由芯片所选用的 MOS 管类型决定的,若是 PMOS 则无需自举电容。

+]]>
+
+ + <![CDATA[并查集详解及相关例题解析]]> + https://www.guanngxu.com/AOlj4J-Zw/ + + + 2023-11-06T14:18:27.000Z + +

参考内容:

+

图论——并查集(详细版)

+ +

并查集(Disjoint-set)是一种精巧的树形数据结构,它主要用于处理一些不相交集合的合并及查询问题。一些常见用途,比如求联通子图、求最小生成树的 Kruskal 算法和求最近公共祖先(LCA)等。

+

并查集的理念是只关注个体属于哪个阵营,并不关心这个阵营中个体内部的关系,比如我们常说的张三是李家沟的,王二是王家坝的。同时并查集借助个体代表集体的思想,用一个元素代表整个群体,就像我们开学都会有学生代表、教师代表讲话一样,在台上讲话的那一个学生就代表了学校所有的学生。

+

并查集基本操作

+

并查集的基本操作主要有初始化 init查询 find合并 union操作。

+

初始化

+

在使用并查集的时候,常常使用一个数组fa来存储每个元素的父节点,在一开始的时候所有元素与其它元素都没有任何关系,即大家相互之间还不认识,所以我们把每个元素的父节点设为自己。

+
#define ARR_LEN 6000
+
+int fa[ARR_LEN];
+
+void init(int n)
+{
+    for(int i = 1; i <= n; i++)
+        fa[i] = i;
+}
+
+

查询

+

查询即找到指定元素的祖先。需要注意的是,这里我们需要找到指定元素的根祖先,不能找到爸爸或者爷爷就停止了,而是要找到查找不下去了为止,所以要不断的去递归下去,直到找到父亲为自己的结点才结束。

+
int find(int i)
+{
+    if(i == fa[i]) // 递归出口
+        return i;
+    else
+        return find(fa[i]); // 不断向上查找祖先
+}
+
+

考虑下面的场景,假如第一次我们需要查询元素5的祖先,第二次需要查询元素4的祖先,会发现第一次查询包含了第二次查询的计算过程,但我们的程序却傻傻的计算了两次,有没有办法去来优化查询过程,让每一次查询都能利用到此前查询计算的便利?

+
+

考虑到并查集并不关心某个元素的爸爸、爷爷是谁,只关心最终的祖先是谁,所以我们可以在查询的过程中顺便做一些修改,比如在查询5的过程中,顺便就把42的父亲给修改为1,即我们在查找过程中进行路经压缩

+
int find(int i)
+{
+    if(i == fa[i]){
+        return i;
+    } else {
+        fa[i] = find(fa[i]); // 进行路径压缩
+        return fa[i];
+    }
+}
+
+

合并

+

合并操作即介绍两个人相互认识,将他们纳入同一个帮派,只需要将俩元素的父亲修改为同一个即可。

+
void union(int i, int j)
+{
+    int fa_i = find(i);
+    int fa_j = find(j);
+    fa[fa_i] = fa_j;
+}
+
+

相关练习题目

+

洛谷 P1551 亲戚

+

题目连接:https://www.luogu.com.cn/problem/P1551

+

题目描述

+

若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

+

规定:xxyy 是亲戚,yyzz 是亲戚,那么 xxzz 也是亲戚。如果 xyx,y 是亲戚,那么 xx 的亲戚都是 yy 的亲戚,yy 的亲戚也都是 xx 的亲戚。

+

输入格式

+

第一行:三个整数 n,m,p(n,m,p5000)n,m,p,(n,m,p≤5000) 分别表示有 nn 个人,mm 个亲戚关系,询问 pp 对亲戚关系。

+

以下 mm 行:每行两个数 MiMj1MiMjnM_i,M_j,1≤M_i,M_j≤n,表示 MiM_iMjM_j 具有亲戚关系。

+

接下来 pp 行:每行两个数 PiPjP_i,P_j,询问 PiP_iPjP_j 是否具有亲戚关系。

+

输出格式

+

pp 行,每行一个YesNo。表示第 ii 个询问的答案为“具有”或“不具有”亲戚关系。

+

输入输出样例

+
# 输入
+6 5 3
+1 2
+1 5
+3 4
+5 2
+1 3
+1 4
+2 3
+5 6
+
+# 输出
+Yes
+Yes
+No
+
+

题目解析

+

可以发现这是一个非常标准的并查集问题,简直和并查集模版如出一辙,因此直接将所有关系读取后进行合并,然后直接查询父亲是否为同一个即可。

+
#include<bits/stdc++.h>
+using namespace std;
+
+#define ARR_LEN 6000
+
+int fa[ARR_LEN];
+
+void init(int n)
+{
+    for(int i = 1; i <= n; i++)
+        fa[i] = i;
+}
+
+int find(int i)
+{
+    if(i == fa[i]){
+        return i;
+    } else {
+        fa[i] = find(fa[i]);
+        return fa[i];
+    }
+}
+
+void union(int i, int j)
+{
+    int fa_i = find(i);
+    int fa_j = find(j);
+    fa[fa_i] = fa_j;
+}
+
+
+int main()
+{
+    int n, m, p;
+	int a, b;
+	
+	cin>> n >> m >> p;
+	
+	init(n);
+
+	for(int i = 0; i < m; i++){
+		cin >> a >> b;
+		union(a, b);
+	}
+	
+	for(int i = 0; i < p; i++){
+		cin >> a >> b;
+		int fa_a = find(a);
+		int fa_b = find(b);
+		
+		if(fa_a == fa_b)
+			cout<<"Yes"<<endl;
+		else
+			cout<<"No"<<endl;
+	}
+}
+
+

杭电 OJ1213 How Many Tables

+

题目连接:https://acm.hdu.edu.cn/showproblem.php?pid=1213

+

题目描述

+

Today is Ignatius' birthday. He invites a lot of friends. Now it's dinner time. Ignatius wants to know how many tables he needs at least. You have to notice that not all the friends know each other, and all the friends do not want to stay with strangers.

+

One important rule for this problem is that if I tell you A knows B, and B knows C, that means A, B, C know each other, so they can stay in one table.

+

For example: If I tell you A knows B, B knows C, and D knows E, so A, B, C can stay in one table, and D, E have to stay in the other one. So Ignatius needs 2 tables at least.

+

输入格式

+

The input starts with an integer T(1<=T<=25)T(1<=T<=25) which indicate the number of test cases. Then TT test cases follow. Each test case starts with two integers NN and M(1<=N,M<=1000)M(1<=N,M<=1000). NN indicates the number of friends, the friends are marked from 11 to NN. Then MM lines follow. Each line consists of two integers AA and B(A!=B)B(A!=B), that means friend AA and friend BB know each other. There will be a blank line between two cases.

+

输出格式

+

For each test case, just output how many tables Ignatius needs at least. Do NOT print any blanks.

+

输入输出样例

+
# 输入
+2
+5 3
+1 2
+2 3
+4 5
+
+5 1
+2 5
+
+# 输出
+2
+4
+
+

题目解析

+

分析可以发现,这个问题要我们做的是统计在所有元素合并之后,统计总共有多个和集合。很轻松就能写出下面的 AC 代码。类似的问题还有杭电 OJ1232 畅通工程

+

读者大人可以在此基础上继续进行延伸,我们实际生活中每个桌子只能坐 8 个人,假设还需要考虑每桌人数的容量,又如何进行改进呢?

+
#include<bits/stdc++.h>
+using namespace std;
+
+#define ARR_LEN 6000
+
+int fa[ARR_LEN];
+
+void init(int n)
+{
+    for(int i = 1; i <= n; i++)
+        fa[i] = i;
+}
+
+int find(int i)
+{
+    if(i == fa[i]){
+        return i;
+    } else {
+        fa[i] = find(fa[i]);
+        return fa[i];
+    }
+}
+
+void union(int i, int j)
+{
+    int fa_i = find(i);
+    int fa_j = find(j);
+    fa[fa_i] = fa_j;
+}
+
+
+int main()
+{
+    int n, m, a, b, t;
+
+    cin>>t;
+    for(int i = 0; i < t; i++){
+        cin>>n>>m;
+        int ans = 0;
+        init(n);
+        for(int i = 0; i < m; i++) {
+            cin>>a>>b;
+            union(a, b);
+        }
+
+        for(int i = 1; i <= n; i++) {
+            // 如果父亲是自己,那么就表示一个独立的集合
+            if(find(i) == i)
+                ans++;
+        }
+
+        cout<<ans<<endl;
+    }
+    
+}
+
+

杭电 OJ1272 小希的迷宫

+

题目连接:https://acm.hdu.edu.cn/showproblem.php?pid=1272

+

题目描述

+

小希设计了一个迷宫让 Gardon 玩,首先她认为所有的通道都应该是双向连通的,就是说如果有一个通道连通了房间 A 和 B,那么既可以通过它从房间 A 走到房间 B,也可以通过它从房间 B 走到房间 A,为了提高难度,小希希望任意两个房间有且仅有一条路径可以相通(除非走了回头路)。小希现在把她的设计图给你,让你帮忙判断她的设计图是否符合她的设计思路。比如下面的例子,前两个是符合条件的,但是最后一个却有两种方法从 5 到达 8。

+
+

输入格式

+

输入包含多组数据,每组数据是一个以 0 0 结尾的整数对列表,表示了一条通道连接的两个房间的编号。房间的编号至少为 1,且不超过 100000。每两组数据之间有一个空行。整个文件以两个 -1 结尾。

+

输出格式

+

对于输入的每一组数据,输出仅包括一行。如果该迷宫符合小希的思路,那么输出Yes,否则输出No

+

输入输出样例

+
# 输入
+6 8  5 3  5 2  6 4
+5 6  0 0
+
+8 1  7 3  6 2  8 9  7 5
+7 4  7 8  7 6  0 0
+
+3 8  6 8  6 4
+5 3  5 6  5 2  0 0
+
+-1 -1
+
+# 输出
+Yes
+Yes
+No
+
+

题目解析

+

其实这个问题就是让我们判断一个连通图中是否存在环,那么问题就转换为寻找出现环的条件。其实不难发现出现下面两种情况时,连通图即存在环。

+
    +
  1. 在查找过程中,发现两个不同元素的父亲是相同的;
  2. +
  3. 若不存在环,则边的数量一定比顶点数量少 1。
  4. +
+
#include<bits/stdc++.h>
+using namespace std;
+
+#define ARR_LEN 100010
+
+int fa[ARR_LEN];
+bool visited[ARR_LEN]; // 用于辅助记录顶点的数量
+int edges, points; // 记录顶点和边的数量
+bool hascycle; // 是否存在环
+
+void init()
+{
+    hascycle = false;
+    edges = 0;
+    points = 0;
+    for(int i = 1; i < ARR_LEN; i++)
+        fa[i] = i, visited[i] = false;
+}
+
+int find(int i)
+{
+    if(i == fa[i]){
+        return i;
+    } else {
+        fa[i] = find(fa[i]);
+        return fa[i];
+    }
+}
+
+void union(int i, int j)
+{
+    int fa_i = find(i);
+    int fa_j = find(j);
+
+    // 两个元素祖先相同,存在环
+    if(fa_i == fa_j) {
+        hascycle = true;
+    } else {
+        visited[i] = true;
+        visited[j] = true;
+        edges++;
+        fa[fa_i] = fa_j;
+    }
+}
+
+
+int main()
+{
+    int a, b;
+
+    init();
+    
+    while(cin>>a>>b) {
+        if(a == 0 && b == 0) {
+            cout<<"Yes"<<endl;
+            continue;
+        }
+
+        if(a == -1 && b == -1) {
+            return 0;
+        }
+
+        union(a, b);
+
+        while(cin>>a>>b){
+            if(a == 0 && b == 0) {
+                break;
+            }
+            union(a, b);
+        }
+
+        if(hascycle) {
+            cout<<"No"<<endl;
+            continue;
+        }
+
+        for(int i = 1; i < ARR_LEN; i++){
+            if(visited[i]) {
+                points++;
+            }
+        }
+
+        if(points == edges + 1) {
+            cout<<"Yes"<<endl;
+        } else {
+            cout<<"No"<<endl;
+        }
+        init();
+    }
+}
+
+]]>
+
+ + <![CDATA[《算法竞赛进阶指南》165 小猫爬山题解]]> + https://www.guanngxu.com/tZnRnI64b/ + + + 2023-08-20T10:22:34.000Z + +

参考内容:

+

[洛谷][noip][算法竞赛进阶指南]小猫爬山

+

《算法竞赛进阶指南》小猫爬山

+ +

小猫爬山

+

题目描述

+

题目链接:https://www.acwing.com/problem/content/167/

+

​Freda 和 Rainbow 饲养了 N 只小猫。这天,小猫们要去爬山。经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。

+

Freda 和 Rainbow 只好花钱让它们坐索道下山。索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是C1、C2……Cn。当然,每辆缆车上的小猫的重量之和不能超过 W。每租用一辆缆车,Freda 和 Rainbow 就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?

+

输入格式

+

​第一行包含两个用空格隔开的整数,N 和 W。接下来 N 行每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci

+

输出格式

+

输出一个整数,最少需要多少美元,也就是最少需要多少辆缆车。

+

分析解答

+

贪心

+

经过思考发现,我们只需要尽可能的在每辆车上都放更多的小猫,就能以最经济的方式把所有小猫都送下山。所以是一个非常明显的贪心题目,我们将所有小猫按重量排序,尽可能把肥猫先送下山即可。具体实现代码如下:

+
#include<bits/stdc++.h>
+using namespace std;
+
+void cin_arr(int *num, int len)
+{
+	for(int i = 0; i < len; i++){
+		cin>>num[i];
+	}
+}
+
+int slove(int *num, int n, int w)
+{
+	int ans = 0;
+	int remain = 0;
+	int load = 0;
+	
+	sort(num, num+n);
+	
+	while(true){
+		for(int i = 0; i < n; i++){
+            // 连当前最小的猫都装不下,那么就新开一辆车
+			if(num[i] != -1 && remain < num[i]){
+				ans++;
+				remain = w;
+				break;
+			}
+		}
+		
+		for(int i = n-1; i >= 0; i--){
+            // 从大到小查找,尽可能装肥猫
+			if(num[i] != -1 && remain >= num[i]){
+				remain -= num[i];
+                // 运送走的小猫重量以 -1 表示
+				num[i] = -1;
+				load++;
+				break;
+			}
+		}
+		
+		// 如果所有小猫都运走了,那么当前 ans 就是答案
+		if(load >= n)
+			return ans;
+	}
+}
+
+int main()
+{
+    int n, w;
+	int cat[1000000];
+	
+	cin>>n>>w;
+	cin_arr(cat, n);
+
+	cout<<slove(cat, n, w)<<endl;
+}
+
+

经过实际测试发现,盲目使用贪心思想的算法并不正确,例如如下测试用例。

+
6 16
+9 5 5 5 4 3
+
+

贪心的结果是使用 3 辆车,分别为9+55+5+43;而正确的结果却是使用 2 辆车,分别为9+4+35+5+5

+

深度优先搜索

+

既然贪心思想在这里行不通,那么我们就采用暴力搜索,即小猫可以放在现有任意一辆车上。具体实现代码如下:

+
#include<bits/stdc++.h>
+using namespace std;
+
+#define N 2000
+
+int n, w;
+int cat[N];
+int sum[N] = {0}; // 第 i 辆车当前重量
+int ans = N;
+
+void cin_arr(int *num, int len)
+{
+	for(int i = 0; i < len; i++){
+		cin>>num[i];
+	}
+}
+
+void dfs(int cur_cat, int cur_car)
+{
+	if(cur_car > ans) // 求最小值,不符合直接返回
+        return ;
+
+    if(cur_cat == n) { // 所有小猫都上车了
+        ans = cur_car;
+        return ;
+    }
+
+    for(int i = 0; i < cur_car; i++) {
+        if(sum[i] + cat[cur_cat] <= w) { // 当前猫能放进去
+            sum[i] += cat[cur_cat]; // 当前猫占用重量
+            dfs(cur_cat+1, cur_car); // 继续放下一只猫
+            sum[i] -= cat[cur_cat]; // 把已经放进去的猫拿出来,因为是循环,所以放入下一辆车里面
+        }
+    }
+
+    // 新开一辆车,把当前这只猫放到新的车里面
+    sum[cur_car] = cat[cur_cat];
+    dfs(cur_cat+1, cur_car+1);
+    sum[cur_car] = 0; // 把猫拿出来
+}
+
+int main()
+{
+	cin>>n>>w;
+	cin_arr(cat, n);
+    dfs(0, 0);
+	cout<<ans<<endl;
+}
+
+

搜索优化

+

考虑到每次都是在车数量固定的情况下进行搜索的,那么少满足一次(sum[i] + cat[cur_cat] <= w)条件,就会少一次递归的调用,也即少一次搜索。那么如何能尽快使得程序尽快不满足该条件呢?

+

sum[i]减小的速度加快就会减少搜索分支,即每次放更重一点的猫进去,就能达到效果。所以我们可以在进行搜索前将小猫的重量进行降序排序,这样从肥猫开始搜索就会减少分支。

+
#include<bits/stdc++.h>
+using namespace std;
+
+#define N 2000
+
+int n, w;
+int cat[N];
+int sum[N] = {0}; // 第 i 辆车当前重量
+int ans = N;
+
+void cin_arr(int *num, int len)
+{
+	for(int i = 0; i < len; i++){
+		cin>>num[i];
+	}
+}
+
+bool cmp(int a, int b)
+{
+    return a > b;
+}
+
+void dfs(int cur_cat, int cur_car)
+{
+	if(cur_car > ans) // 求最小值,不符合直接返回
+        return ;
+
+    if(cur_cat == n) { // 所有小猫都上车了
+        ans = cur_car;
+        return ;
+    }
+
+    for(int i = 0; i < cur_car; i++) {
+        if(sum[i] + cat[cur_cat] <= w) { // 当前猫能放进去
+            sum[i] += cat[cur_cat]; // 当前猫占用重量
+            dfs(cur_cat+1, cur_car); // 继续放下一只猫
+            sum[i] -= cat[cur_cat]; // 把已经放进去的猫拿出来,因为是循环,所以放入下一辆车里面
+        }
+    }
+
+    // 新开一辆车,把当前这只猫放到新的车里面
+    sum[cur_car] = cat[cur_cat];
+    dfs(cur_cat+1, cur_car+1);
+    sum[cur_car] = 0; // 把猫拿出来
+}
+
+int main()
+{
+	cin>>n>>w;
+	cin_arr(cat, n);
+    sort(cat, cat+n, cmp); // 反排序优化搜索
+    dfs(0, 0);
+	cout<<ans<<endl;
+}
+
+]]>
+
+ + <![CDATA[二叉树的前序、中序、后序、层序遍历]]> + https://www.guanngxu.com/8sxCrqU9f/ + + + 2023-08-19T02:33:54.000Z + +

参考内容:

+

五分钟让你彻底理解二叉树的非递归遍历

+

Python实现二叉树的非递归遍历

+

二叉树遍历——深度优先(前中后序)+广度优先(层序遍历)

+ +

构造二叉树

+

定义二叉树结构如下

+
struct node
+{
+    int data;
+    node *left;
+    node *right;
+};
+
+

构造如下形态二叉树

+
+
node *init_tree()
+{
+    node *node1 = (node *)malloc(sizeof(node));
+    node *node2 = (node *)malloc(sizeof(node));
+    node *node3 = (node *)malloc(sizeof(node));
+    node *node4 = (node *)malloc(sizeof(node));
+    node *node5 = (node *)malloc(sizeof(node));
+    node *node6 = (node *)malloc(sizeof(node));
+    node *node7 = (node *)malloc(sizeof(node));
+    node *node8 = (node *)malloc(sizeof(node));
+    
+    node1->data = 1;
+    node2->data = 2;
+    node3->data = 3;
+    node4->data = 4;
+    node5->data = 5;
+    node6->data = 6;
+    node7->data = 7;
+    node8->data = 8;
+
+    node1->left = node2;
+    node1->right = node3;
+    
+    node2->left = node4;
+    node2->right = node5;
+    
+    node3->right = node6;
+    
+    node5->left = node7;
+    node5->right= node8;
+
+    return node1;
+}
+
+

前序遍历(递归)

+

前序遍历顺序为根左右。要遍历整个二叉树我们就需要遍历二叉树的每一个子树,对于任何一个子树它的遍历方式均为根左右顺序遍历。即所有子问题均与父问题除规模大小不同外,其余均相同。所以可以采用递归方式实现前序遍历。

+
// 前序遍历 根左右
+void pre_order_traversal(node *root)
+{
+    if(root) {
+        cout<<root->data<<" ";
+        pre_order_traversal(root->left);
+        pre_order_traversal(root->right);
+    }
+}
+
+

遍历结果为:1 2 4 5 7 8 3 6

+

中序遍历(递归)

+

中序遍历顺序为左根右。其与前序遍历仅顺序不同,其余均相同。

+
// 中序遍历 左根右
+void in_order_traversal(node *root)
+{
+    if(root) {
+        in_order_traversal(root->left);
+        cout<<root->data<<" ";
+        in_order_traversal(root->right);
+    }
+}
+
+

遍历结果为:4 2 7 5 8 1 3 6

+

后序遍历(递归)

+

后序遍历顺序为左右根。其与前序、中序遍历仅顺序不同,其余均相同。

+
// 后序遍历 左右根
+void post_order_traversal(node *root)
+{
+    if(root) {
+        post_order_traversal(root->left);
+        post_order_traversal(root->right);
+        cout<<root->data<<" ";
+    }
+}
+
+

遍历结果为:4 7 8 5 2 6 3 1

+

前序遍历方法一(非递归)

+

因为递归实际上是由系统帮我们进行压栈,所以理论上所有递归算法都可以改为循环+栈实现,那么我们先照着上述前序遍历的样子修改为循环+栈的形态。需要注意的是由于栈先进后出的特性,为了保证左孩子在右孩子前被访问,所以应该先右孩子入栈,再左孩子入栈。

+
// 前序遍历 根左右
+void pre_order_traversal(node *root)
+{
+    stack<node *> s;
+    s.push(root);
+
+    while(!s.empty()) {
+
+        node *cur = s.top();
+        s.pop();
+
+        if(cur) {
+            cout<<cur->data<<" ";
+            s.push(cur->right);
+            s.push(cur->left);
+        }
+    }
+}
+
+

遍历结果为:1 2 4 5 7 8 3 6

+

前序遍历方法二(非递归)

+

现在我们换一种思路来实现前序非递归遍历,仔细观察前序遍历的递归调用过程。

+
    +
  1. 先把从根结点开始的所有左子树放入栈中;
  2. +
  3. 弹出栈顶元素
  4. +
  5. 如果栈顶元素有右子树,那么右子树入栈
  6. +
  7. 重复上述过程直到栈为空
  8. +
+

因此我们可以写出遍历代码

+
// 前序遍历 根左右
+void pre_order_traversal(node *root)
+{
+    stack<node *> s;
+    node *cur = root;
+
+    while(cur || !s.empty()) {
+        // 将左子树全部入栈
+        while(cur) {
+            cout<<cur->data<<" ";
+            s.push(cur);
+            cur = cur->left;
+        }
+
+        if(!s.empty()) {
+            cur = s.top();
+            s.pop();
+            cur = cur->right;
+        }
+    }
+}
+
+

遍历结果为:1 2 4 5 7 8 3 6

+

中序遍历(非递归)

+

有了前面的基础,我们再来考虑中序遍历,会发现中序遍历与前序遍历只是打印结点的位置不一样。前序遍历是在结点入栈时打印,中序遍历只需要替换为在结点出栈时打印即可。

+
// 中序遍历 左根右
+void in_order_traversal(node *root)
+{
+    stack<node *> s;
+    node *cur = root;
+
+    while(cur || !s.empty()) {
+        // 将左子树全部入栈
+        while(cur) {
+            s.push(cur);
+            cur = cur->left;
+        }
+
+        if(!s.empty()) {
+            cur = s.top();
+            cout<<cur->data<<" ";
+            s.pop();
+            cur = cur->right;
+        }
+    }
+}
+
+

遍历结果为:4 2 7 5 8 1 3 6

+

后序遍历方法一(非递归)

+

后序遍历相对来说显得更加复杂了。在前序和中序遍历中,只要左子树处理完毕实际上栈顶元素就可以出栈了,但后序遍历需要把左子树和右子树都处理完毕才能出栈,显然我们需要某种方法记录遍历的过程。

+

实际上我们只需要记录下遍历的前一个结点就能解决问题,因为通过前一个结点我们可以做如下判断:

+
    +
  1. 如果前一个结点是当前结点的右子树,那么说明右子树已经遍历完毕可以出栈了
  2. +
  3. 如果前一个结点是当前结点的左子树而且当前结点没有右子树,那么说明可以出栈了
  4. +
  5. 如果当前结点即没有左子树也没有右子树,即为叶子结点,那么说明可以出栈了
  6. +
+

若不属于上述情况,则依次将当前结点的右孩子和做孩子入栈,这样就能保证每次取栈顶元素时,左孩子都在右孩子前面被访问,左孩子和右孩子都在父结点前面被访问。

+
// 后序遍历 左右根
+void post_order_traversal(node *root)
+{
+    stack<node *> s;
+    node *pre = NULL;
+    node *cur = root;
+
+    s.push(cur);
+
+    while(!s.empty()) {
+        cur = s.top();
+        // 叶子结点
+        if((!cur->left && !cur->right) // 叶子结点
+        || pre == cur->right // 前一个结点为当前结点右子树
+        || (pre == cur->left && !cur->right)) { // 前一个结点为当前结点左子树,且没有右子树
+            cout<<cur->data<<" ";
+            pre = cur;
+            s.pop();
+        } else {
+            if(cur->right)
+                s.push(cur->right);
+
+            if(cur->left)
+                s.push(cur->left);
+        }
+    }
+}
+
+

遍历结果为:4 7 8 5 2 6 3 1

+

后序遍历方法二(非递归)

+

后序遍历的顺序是左右根,如果把这个顺序倒过来就是根右左,是不是发现和前序遍历很像?那么我只需要按照根右左的方式遍历完,然后将遍历结果掉一个个儿就可以,而栈就具备掉个儿的功能,因此可写出如下代码。

+
// 后序遍历 左右根
+void post_order_traversal(node *root)
+{
+    stack<node *> s;
+    stack<int> ans;
+    node *cur = root;
+
+    while(cur || !s.empty()) {
+        // 将左子树全部入栈
+        while(cur) {
+            ans.push(cur->data);
+            s.push(cur);
+            cur = cur->right;
+        }
+
+        if(!s.empty()) {
+            cur = s.top();
+            s.pop();
+            cur = cur->left;
+        }
+    }
+
+    while(!ans.empty()) {
+        cout<<ans.top()<<" ";
+        ans.pop();
+    }
+}
+
+

遍历结果为:4 7 8 5 2 6 3 1

+

层序遍历

+

层序遍历即广度优先遍历,使用队列即可实现。

+
// 层序遍历
+void breadth_first_order_traversal(node *root)
+{
+    queue<node *> q;
+    q.push(root);
+	while(!q.empty()){
+		node *cur = q.front();
+		q.pop();
+		if(cur){
+			cout<<cur->data<<" ";
+			q.push(cur->left);
+			q.push(cur->right);
+		}
+	}
+}
+
+

遍历结果为:1 2 3 4 5 6 7 8

+]]>
+
+ + <![CDATA[动态规划实例——01 背包详解]]> + https://www.guanngxu.com/sBMrrNbs4/ + + + 2023-04-01T12:07:39.000Z + 题目描述 +

有 n 件物品,每件物品有一个重量和一个价值,分别记为 w1,w2,…,wn 和 c1,c2,…,cn。现在有一个背包,其容量为 wk,要从 n 件物品种任取若干件。要求:(1)重量之和小于或等于 wk;(2)价值之和最大。

+

输入

+

共 3 行,第一行 2 个整数,表示 n 和 wk;第二行 n 个整数表示每一个物品的重量,第三行 n 个整数表示每一个物品的价值。

+

输出

+

一行一个整数,表示符合背包容量的最大价值。

+

样例

+
8 200
+79 58 86 11 28 62 15 68
+83 14 54 79 72 52 48 62
+
+

暴力枚举

+

我们以只有 A、B、C 三件物品的情况为例,对于每一个物品都存在不拿两种情况。以0表示不拿当前物品,以1表示拿当前物品,可以有如下分析结果。

+
+

可能上面的图看起来不够清晰,我们从左至右逐一列举出来观察,一眼就可以看出来规律。其实就是十进制的 0、1、2、3、4、......可枚举的最大值即 2n-1

+
000
+001
+010
+011
+100
+101
+110
+111
+
+

根据上面的分析,我们可以写出如下代码。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int main()
+{
+	int n, wk;
+	int w[10000], c[10000];
+	cin>>n>>wk;
+	for(int i = 0; i < n; i++){
+		cin>>w[i];
+	}
+	for(int i = 0; i < n; i++){
+		cin>>c[i];
+	}
+
+	int ans = 0;
+    int max_val = 1 << n;
+    // 逐一枚举
+	for(int i = 0; i < max_val; i++){
+		int ww = 0, cc = 0;
+		int index = 0;
+		// 转二进制
+		int cur = i;
+		while(cur){
+			int bit = cur % 2;
+            // 若拿第 index 个物品,则累加其重量和价值
+			if(bit){
+				ww += w[index];
+				cc += c[index];
+			}
+			cur = cur >> 1;
+			index++;
+		}
+		//计算最大值
+		if(ww <= wk && ans < cc){
+			ans = cc;
+		}
+	}
+	//输出最大值
+	cout<<ans<<endl;
+}
+
+

递归求解

+

我们把背包容量为wk,有n个物品可以选择的问题表示为slove(wk, n)。那么在背包剩余容量可以装下第 n 个物品时,该问题可以表示为求如下两个问题的最大值

+
    +
  • 选第 n 个物品:c[n-1] + slove(wk-w[n-1], n-1)
  • +
  • 不选第 n 个物品:slove(wk, n-1)
  • +
+

在背包剩余容量无法装下第 n 个物品时,问题直接变为

+
    +
  • 不选第 n 个物品:slove(wk, n-1)
  • +
+

可以发现上述三个子问题可以继续向下拆分为规模更小,但类型一致的子子问题。于是可以写出如下递归求解代码。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int w[30]={0}, c[30]={0};
+
+// wk 背包剩余重量
+// ch 可选项
+int slove(int wk, int ch)
+{
+	if(wk <= 0 || ch <= 0){
+		return 0;
+	}
+	
+    // 若背包剩余容量无法装下 w[ch-1],则直接丢弃第 ch 个物品
+	if(w[ch-1] > wk){
+		return slove(wk, ch-1);
+	}
+	
+    // 若背包剩余容量能装下 w[ch-1],则计算装和不装的最大值
+	int a = c[ch-1] + slove(wk-w[ch-1],ch-1);
+	int b = slove(wk, ch-1);
+	return a > b ? a : b;
+}
+
+int main()
+{
+	int n, wk;
+	cin>>n>>wk;
+	
+	for(int i = 0; i < n; i++){
+		cin>>w[i];
+	}
+	for(int i = 0; i < n; i++){
+		cin>>c[i];
+	}
+	cout<<slove(wk, n);
+}
+
+

动态规划

+

递归在执行过程中会存在重复计算相同子问题的情况,我们可以将其改为用循环实现,即动态规划的写法。dp[i][j]的含义即为:在背包容量为i,可选物品数量为j的情况下,符合背包容量的最大值。具体代码如下所示:

+
#include<bits/stdc++.h>
+using namespace std;
+
+int w[30]={0}, c[30]={0};
+
+int main()
+{
+	int n, wk;
+	cin>>n>>wk;
+	
+	for(int i = 0; i < n; i++){
+		cin>>w[i];
+	}
+	for(int i = 0; i < n; i++){
+		cin>>c[i];
+	}
+
+    int dp[1000001][21] = { 0 };
+
+    for(int i = 1; i <= wk; i++) {
+        for(int j = 1; j <= n; j++) {
+            // 若背包剩余容量无法装下 w[j-1],则直接丢弃第 j 个物品
+            if(w[j-1] > i) {
+                dp[i][j] = dp[i][j-1];
+            } else {
+                // 若背包剩余容量能装下 w[j-1],则计算装和不装的最大值
+                int a = c[j-1] + dp[i-w[j-1]][j-1];
+                int b = dp[i][j-1];
+                dp[i][j] = a > b ? a : b;
+            }
+        }
+    }
+
+	cout<<dp[wk][n];
+}
+
+]]>
+
+ + <![CDATA[Oracle 安装及 Spring 使用 Oracle]]> + https://www.guanngxu.com/9Ov7sD1uz/ + + + 2023-03-30T03:06:09.000Z + +

参考内容:

+

docker安装oracle数据库史上最全步骤(带图文)

+

Mac下oracle数据库客户端

+

Docker安装Oracle

+

docker能安装oracle吗

+

Batch script for add a auto-increased primary key for exist table with records

+ +

Docker 安装 Oracle11g

+

注意:下列安装方式仅适用于x86架构服务器,不适用于arm架构服务器

+
# 拉取 oracle11,镜像有点大,需要花一些时间
+docker pull registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g
+
+# 查看镜像是否拉取成功
+docker images
+
+# 给镜像重新打 tag,原来的名字太长了
+docker tag registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g oracle11g:latest
+
+# 启动 oracle11g 容器
+docker run --name=oracle11g -itd -p 1521:1521
+
+# 进入容器进行配置
+docker exec -it oracle11g /bin/bash
+
+# 切换到 root 用户,密码为:helowin
+su root
+
+# 编辑配置文件
+
+

编辑/etc/profile,在其中增加如下内容:

+
export ORACLE_HOME=/home/oracle/app/oracle/product/11.2.0/dbhome_2
+export ORACLE_SID=helowin
+export PATH=$ORACLE_HOME/bin:$PATH
+
+

编辑完成后,需要刷新上述环境变量才能使用。

+
# 刷新环境变量
+source /etc/profile
+
+# 创建软链接
+ln -s $ORACLE_HOME/bin/sqlplus /usr/bin
+
+# 切换到 oracle 用户
+su - oracle
+
+# 登陆 sqlplus
+sqlplus /nolog
+conn /as sysdba
+
+# 修改 system 用户密码
+alter user system identified by system;
+# 修改 sys 用户密码
+alter user sys identified by system;
+
+# 创建内部管理员账号
+create user test identified by test;
+
+# 将 dba 权限授权给内部管理员账号和密码
+grant connect,resource,dba to test;
+
+# 修改密码规则策略为密码永不过期
+ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED;
+
+# 修改数据库最大连接数据
+alter system set processes=1000 scope=spfile;
+
+

修改上述信息后,需要重新启动数据库才会生效。

+
conn /as sysdba
+
+# 关闭数据库
+shutdown immediate;
+
+# 启动数据库
+startup;
+
+# 退出软链接
+exit;
+
+

客户端连接 Oracle

+

以 Navicat 客户端为例,新建连接时按下图方式填写连接信息即可,密码即为system。需要注意的是,在 Windows 下选择 SID 或是服务名均可连接成功,但是在 Mac 下需要选择 SID 方式才能连接成功。

+
+

Oracle 实现主键自增

+

Oracle 在创建表的时候,不能像 MySQL 那样选择主键直接自增,但是我们可以通过给表创建序列和触发器去实现自增。下文以创建 USER 表为例。

+
-- 删除原有 USER 表
+DROP TABLE "TEST"."USER";
+-- 创建 USER 表
+CREATE TABLE "TEST"."USER" (
+  "id" NUMBER NOT NULL,
+  "gmt_create" DATE NOT NULL,
+  "gmt_modified" DATE NOT NULL,
+  "is_deleted" NUMBER NOT NULL,
+  "login" NVARCHAR2(255) NOT NULL,
+  "passwd" NVARCHAR2(255) NOT NULL,
+  "nick" NVARCHAR2(255) NOT NULL,
+  "phone" NVARCHAR2(255),
+  "head_img" NVARCHAR2(255),
+  "status" NVARCHAR2(255),
+  "remark" NCLOB
+);
+
+-- 删除原有序列
+DROP SEQUENCE "TEST"."USER_SEQ";
+-- 创建 USER_SEQ 序列,最小值为 1
+CREATE SEQUENCE "TEST"."USER_SEQ" 
+-- 最小值为 1
+MINVALUE 1 
+-- 最大值为 9999999999999999999999999999
+MAXVALUE 9999999999999999999999999999 
+-- 每次增加 1
+INCREMENT BY 1 
+-- 将 20 个序列值放入缓存
+CACHE 20;
+
+-- 创建触发器
+CREATE TRIGGER "TEST"."USER_TRIGGER" 
+-- 在插入数据前执行
+BEFORE INSERT ON "TEST"."USER" 
+-- 命名老数据为 OLD,新数据为 NEW
+REFERENCING OLD AS "OLD" NEW AS "NEW" 
+-- 针对表的每一行都执行触发器
+FOR EACH ROW 
+-- 将序列值赋值给 id
+BEGIN
+	:NEW."id" := USER_SEQ.NEXTVAL;
+END;
+/
+
+

需要注意的是,上面的/符号不能少。执行插入语句时可以发现,id会自动增加。

+
INSERT INTO "TEST"."USER" ("gmt_create", "gmt_modified", "is_deleted", "login", "passwd", "nick", "phone", "head_img", "status", "remark") VALUES (TO_DATE('2023-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), TO_DATE('2023-04-01 17:04:30', 'SYYYY-MM-DD HH24:MI:SS'), '0', 'user', '123', 'Jack', '1111', 'head.jpg', '激活', '测试');
+
+

Java Spring+Mybatis 使用 Oracle 及配置分页

+

application.properties文件配置信息:

+
# mybatis
+spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
+spring.datasource.url=jdbc:oracle:thin:@8127.0.0.1:1521:helowin
+spring.datasource.username=system
+spring.datasource.password=system
+mybatis.mapper-locations=classpath*:mybatis/*.xml
+mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
+
+# pageHelper
+pagehelper.helperDialect=oracle
+pagehelper.reasonable=true
+pagehelper.supportMethodsArguments=true
+pagehelper.params=count=countSql
+
+

pom.xml配置文件关键信息。

+
<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <properties>
+        <java.version>1.8</java.version>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+
+        <spring.boot-version>2.1.3.RELEASE</spring.boot-version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring.boot-version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <dependency>
+                <groupId>org.mybatis.spring.boot</groupId>
+                <artifactId>mybatis-spring-boot-starter</artifactId>
+                <version>2.1.0</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.oracle.ojdbc</groupId>
+                <artifactId>ojdbc8</artifactId>
+                <version>19.3.0.0</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.github.pagehelper</groupId>
+                <artifactId>pagehelper-spring-boot-starter</artifactId>
+                <version>1.4.3</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.github.pagehelper</groupId>
+                <artifactId>pagehelper-spring-boot-starter</artifactId>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+
+]]>
+
+ + <![CDATA[动态规划实例——换零钱的方法数(C++详解版)]]> + https://www.guanngxu.com/WfB5h55oQ/ + + + 2023-02-26T02:40:27.000Z + +

原写了 Java 版本的如何求解换钱的方法数,近期进行了一些细节上的补充,以及部分错误更正,将语言换为了 C++ 语言。

+ +

基础题目

+

假设你现在拥有不限量的 1 元、5 元、10 元面值纸币,路人甲希望找你换一些零钱,路人甲拿出的是一张 100 元面值的纸币,试求总共有多少种换零钱的方法?

+

分析:因为总共只有 3 种面值小额纸币,所以将所有可能进行枚举,直接暴力解决即可。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int slove() {
+	int ans = 0;
+	// 10 元张数
+	for(int i = 0; i <= 10; i++) {
+		// 5 元张数
+		for(int j = 0; j <= 20; j++) {
+			// 1 元张数
+			for(int k = 0; k <= 100; k++) {
+				int cur = i*10 + j*5 + k*1;
+				if(cur == 100) {
+					ans++;
+				}
+			}
+		}
+	}
+	return ans;
+}
+
+int main()
+{
+	cout<<slove();
+}
+
+

递归求解

+

基础题目中是拥有固定种类的小额纸币,即使再多几种小额纸币也没关系,大不了在嵌套几个循环就能解决。现在需要将题目的难度加大一点,改为小额纸币的种类和需要换零钱的总额由用户输入,即小额纸币种类和总额都不在固定,那么如何解决?

+

输入共有三行:

+
    +
  • 第一行:小额纸币种类数量
  • +
  • 第二行:不同小额纸币的面值
  • +
  • 第三行:需要换零钱的总额
  • +
+

分析:虽然现在种类和总额都是变量了,但是上文中的基础版本还是被包含在此问题中,所以我们还是以上文中的 1 元、5 元、10 元换 100 元进行分析,找一找除了枚举是否还有其他方法解决。

+

我们先固定一种零钱的数量,剩下的钱用剩余零钱去兑换,即:

+
    +
  • 用 0 张 1 元换,剩下的用 5、10 元换,最终方法数为 count0;
  • +
  • 用 1 张 1 元换,剩下的用 5、10 元换,最终方法数为 count1;
  • +
  • ......
  • +
  • 用 100 张 1 元换,剩下的用 5、10 元换,最终方法数为 count100;
  • +
+

那么最终换钱的方法综述即为count0 + count1 + count2 + ... + count100

+

上面的分析中,我们把原来的大问题拆为了 101 个小问题,且每一个小问题都有很相似的地方,即:

+
    +
  • 求用 5、10 元换 100 元的方法数
  • +
  • 求用 5、10 元换 95 元的方法数
  • +
  • ......
  • +
  • 求用 5、10 元换 0 元的方法数
  • +
+

如果我们对这 101 个小问题再进行同样思路的分析,即再固定 5 元零钱的数量,那么就能把问题划分成了规模更小,但问题类型一样的小小问题。即递归的思路,可以写出如下代码。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int money[1000]; // money 表示所有小额纸币的面值
+int len; // len 表示 money 数组的长度,即:小额纸币种类
+
+// index 表示上文分析中的当前固定第几张
+// target 表示现在要兑换的钱的总额
+int slove(int index, int target) {
+    int ans = 0;
+    if(index == len) {
+        ans = target == 0 ? 1 : 0;
+    } else {
+        for(int i = 0; i*money[index] <= target; i++) {
+            // 剩余待换零钱的总额
+            int cur_total = target-(i * money[index]);
+            ans = ans + slove(index+1, cur_total);
+        }
+    }
+    return ans;
+}
+
+int main()
+{
+    int target;
+    cin>>len; // 零钱种类
+    for(int i = 0; i < len; i++){
+        cin>>money[i];
+    }
+    cin>>target; // 兑换总额
+
+    cout<<slove(0, target);
+}
+
+

优化递归

+

可以发现上文所写的递归代码存在大量的重复过程,比如下面两种情况,后面所求的子问题是完全一样的,导致程序运行时间的浪费。

+
    +
  • 已经使用了 5 张 1 元、0 张 5 元,剩下的 95 元用 5 元和 10 元兑换
  • +
  • 已经使用了 0 张 1 元、1 张 5 元,剩下的 95 元用 5 元 和 10 元兑换
  • +
+

既然前面已经求解过相同的子问题了,那么我们是否可以在第一次求解的时候,将计算结果保存下来,这样下次遇到相同子问题的实际,直接查出来用就可以,省去再次求解的时间。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int money[1000]; // money 表示所有小额纸币的面值
+int len; // len 表示 money 数组的长度,即:小额纸币种类
+
+// 用于存储子问题的解
+int val_map[1000][1000] = { 0 };
+
+// 0 表示该子问题没有算过
+// -1 表示算过,但该子问题无解
+// 其它值,即此子问题的方法数
+
+int slove(int index, int target) {
+    int ans = 0;
+    if(index == len) {
+        ans = target == 0 ? 1 : 0;
+    } else {
+        for(int i = 0; i*money[index] <= target; i++) {
+            // 剩余待换零钱的总额
+            int cur_total = target-(i * money[index]);
+            int pre_val = val_map[index+1][cur_total];
+            // 如果 val 为 0,说明该子问题没有被计算过
+            if(pre_val == 0) {
+                ans = ans + slove(index+1, cur_total);
+            } else {
+                ans += pre_val == -1 ? 0 : pre_val;
+            }
+        }
+    }
+    // 存储计算结果
+    val_map[index][target] = ans == 0 ? -1 : ans;
+    return ans;
+}
+
+int main()
+{
+    int target; // 零钱种类
+    cin>>len;
+    for(int i = 0; i < len; i++){
+        cin>>money[i];
+    }
+    cin>>target;
+
+    cout<<slove(0, target);
+}
+
+

动态规划

+

上面对递归的优化方案已经能看出来动态规划的影子了,沿着前文先计算再查表的思路继续思考,我们能否提前把所有子问题都计算出答案,对每个子问题都进行查表解决。也即将最初的递归方案改为循环的实现。

+
+

所有的递归都能改为循环实现

+
+
#include<bits/stdc++.h>
+using namespace std;
+
+int money[1000]; // money 表示所有小额纸币的面值
+int len; // len 表示 money 数组的长度,即:小额纸币种类
+
+// 用于存储子问题的解
+// val_map[i][j] 表示用 money[0...i] 的小面额零钱组成 j 元的方法数
+int val_map[1000][1000] = { 0 };
+
+int slove(int target) {
+    
+    // 第一列表示组成 0 元的方法数,所以为 1
+    for (int i = 0; i < len; i++) {
+        val_map[i][0] = 1;
+    }
+
+    // 第一行表示只使用 money[0] 一种钱币兑换钱数为i的方法数
+    // 所以是 money[0] 的倍数的位置为 1,否则为 0
+    for (int i = 1; money[0]*i <= target; i++) {
+        val_map[0][money[0]*i] = 1;
+    }
+
+    for (int i = 1; i < len; i++) {
+        for (int j = 1; j <= target; j++) {
+            for (int k = 0; j >= money[i]*k; k++) {
+                /* 
+                val_map[i][j] 的值为:
+                用 money[0...i-1] 的零钱组成 j 减去 money[i] 的倍数的方法数
+                因为相比 val_map[i-1][j],只是多了一种零钱的可选项
+                */
+                val_map[i][j] += val_map[i-1][j-money[i]*k];
+            }
+        }
+    }
+
+    return val_map[len-1][target];
+}
+
+int main()
+{
+    int target;
+    cin>>len;
+    for(int i = 0; i < len; i++){
+        cin>>money[i];
+    }
+    cin>>target;
+
+    cout<<slove(target);
+}
+
+

动归优化

+

在上文第一版动态规划代码的优化中已经能发现,其实val_map[i][j]的值由两部分组成,分别为:

+
    +
  • 用 money[0...i-1] 的零钱组成换 j 元的方法数
  • +
  • 用 money[0...i-1] 的零钱换 j-money[i]*k(k=1,1,2,3....)元的方法数之和
  • +
+

对于第二种情况来说,其累加值实际上就是val_map[i][j-money[i]],即用money[0...i]的零钱换 j-money[i]元的方法数。至于具体为什么累加值与val_map[i][j-money[i]]相等,我们可以借助递归方法时的分析方式进行理解。

+

用 money[0...i-1] 的零钱组成换 j 元的方法数对应

+
    +
  • 用 0 张 money[i] 换,剩下的用 money[0...i-1] 换
  • +
+

用 money[0...i-1] 的零钱换 j-money[i]*k(k=1,1,2,3....)元的方法数之和对应

+
    +
  • 用 1 张 money[i] 换,剩下的用 money[0...i-1] 换
  • +
  • 用 2 张 money[i] 换,剩下的用 money[0...i-1] 换
  • +
  • ......
  • +
+

所以第二部分的值即为val_map[i][j-money[i]]。依据此处的分析,我们可以在原有基础上去掉第三层循环,减少程序运行所花费的时间。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int money[1000];
+int len;
+
+int val_map[1000][1000] = { 0 };
+
+int slove(int target) {
+    
+    for (int i = 0; i < len; i++) {
+        val_map[i][0] = 1;
+    }
+
+    for (int i = 1; money[0]*i <= target; i++) {
+        val_map[0][money[0]*i] = 1;
+    }
+
+    for (int i = 1; i < len; i++) {
+        for (int j = 1; j <= target; j++) {
+            val_map[i][j] = val_map[i-1][j];
+            // 此处需要比较 j 的大小,防止数组越界
+            // 注意条件时 >= ,否则少计算 j 刚好为 money[i] 的情况
+            if(j >= money[i]) {
+                val_map[i][j] += val_map[i][j-money[i]];
+            }
+        }
+    }
+
+    return val_map[len-1][target];
+}
+
+int main()
+{
+    int target;
+    cin>>len;
+    for(int i = 0; i < len; i++){
+        cin>>money[i];
+    }
+    cin>>target;
+
+    cout<<slove(target);
+}
+
+

空间压缩

+

仔细观察能发现,每一次更新val_map[i][j]的值时,它只依赖于上一行和当前这一行前面的元素。对于我们所求解的问题来说,它仅要求我们给出最终的答案即可,那么前面存储中间结果的那些元素实际上就会空间的浪费,因此我们可以思考一下如何在空间上进行压缩。

+

实际上我们只需要定义一个一维的数组,采用一些技巧对该数组进行滚动更新,按照合适的方向去更新数组,同样可以达到上面使用二维数组的效果。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int money[1000];
+int len;
+
+int val_map[1000] = { 0 };
+
+int slove(int target) {
+    
+    // 第一行,只用 money[0] 换零钱
+    // 所以只能换 money[0] 倍数的钱
+    for (int i = 0; money[0]*i <= target; i++) {
+        val_map[money[0] * i] = 1;
+    }
+
+    for (int i = 1; i < len; i++) {
+        for (int j = 1; j <= target; j++) {
+            if(j >= money[i]) {
+                // 在进行下面一步前 val_map[j] 的值就已经是 val_map[i-1][j] 了
+                val_map[j] += val_map[j-money[i]];
+            }
+        }
+    }
+
+    return val_map[target];
+}
+
+int main()
+{
+    int target;
+    cin>>len;
+    for(int i = 0; i < len; i++){
+        cin>>money[i];
+    }
+    cin>>target;
+
+    cout<<slove(target);
+}
+
+]]>
+
+ + <![CDATA[linux-5.10.157 内核源码编译]]> + https://www.guanngxu.com/RYkQYSFKV/ + + + 2022-12-08T08:19:27.000Z + +

参考内容:

+

Linux内核开发_1_编译LInux内核

+

编译linux内核报错:flex: not foundscripts

+

编译kernel5.14报错fatal error: openssl/opensslv.h

+

编译内核错误——*** 没有规则可制作目标“debian/canonical-certs.pem”

+

内核错误:BTF: .tmp_vmlinux.btf: pahole (pahole) is not available

+ +
# 切换到 root 账户
+sudo su
+
+# 查看操作系统版本
+cat /etc/issue
+
+# 查看 Linux 内核版本
+cat /proc/version
+
+# 进入 root 账户目录
+cd /home/root
+
+# 下载 Linux 内核源码
+wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.157.tar.xz
+# Linux 其它版本源码 https://www.kernel.org/
+
+# xz 解压
+xz -d linux-5.10.157.tar.xz
+
+# tar 解压到 /usr/src/linux-5.10.157 目录下
+tar -xf linux-5.10.157.tar -C /usr/src/.
+
+# 进入源码目录
+cd /usr/src/linux-5.10.157
+
+# 查看源码结构
+tree . -L 2
+
+# 若没有 tree 命令,可以执行下面命令
+# apt-get install tree
+
+# 配置编译选项
+make menuconfig
+
+# 若没有 make,可以执行下面命令
+# apt-get install make
+
+# 若执行 make 后报错找不到 curses.h,可以执行下面命令
+# apt-get install libncurses5-dev
+
+# 若报错找不到 flex not found,可以执行下面两条命令
+# apt-get install flex
+# apt-get install bison
+
+# 再次运行 make menuconfig 弹出图形化配置页面后
+# 若使用默认配置,则直接按两次 Esc 键退出即可
+# 此时会在当前目录下生成 .config 文件
+
+# 编译 Linux 源码
+make bzImage -j4
+
+# 在编译过程中若报错 fatal error: openssl/opensslv.h,可执行下面命令
+# apt-get install libssl-dev
+# 若还出现同样的问题,可参考 https://blog.csdn.net/ComputerInBook/article/details/107380796 源码编译安装 openssl
+
+# 若出现「没有规则可制作目标“debian/canonical-certs.pem”」报错
+# 需要删除 .config 中相应的字段,总共有两处
+# 一处为 CONFIG_SYSTEM_TRUSTED_KEYS="debian/canonical-certs.pem"
+# 一处为 CONFIG_SYSTEM_REVOCATION_KEYS="debian/canonical-revoked-certs.pem"
+
+vim .config
+# 删除之后的样子如下(需要保留引号):
+# 一处为 CONFIG_SYSTEM_TRUSTED_KEYS=""
+# 一处为 CONFIG_SYSTEM_REVOCATION_KEYS=""
+
+# 若出现 BTF: .tmp_vmlinux.btf: pahole (pahole) is not available 错误,则执行下面命令
+# apt-get install dwarves
+
+# 若在过程中还出现其它问题,大多是因为缺少相关库导致的,直接用 apt-get install 即可
+
+]]>
+
+
\ No newline at end of file diff --git a/bOFRz9RDX/index.html b/bOFRz9RDX/index.html new file mode 100644 index 00000000..abb7998f --- /dev/null +++ b/bOFRz9RDX/index.html @@ -0,0 +1,604 @@ + + + + + + + + 深入理解 JavaScript——变量提升与作用域 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 深入理解 JavaScript——变量提升与作用域 +

+ + +
+ +
+
+

参考内容:
+lhs rhs是啥意思
+《Javasript 高级程序设计(第三版)》
+《你不知道的 JavaScript(上卷)》

+
+

几乎所有的编程语言都能够存储变量当中的值,并且可以在之后对该值进行访问或修改。很明显需要一套良好的规则来存储这些变量,并且之后可以方便的找到这些变量,这套规则我们称之为作用域

+

编译原理

+

我们一般把 js 归为「动态」或「解释执行」语言,但是它也会经历编译阶段,不过它不像传统语言那样是提前编译的,它的编译发生在代码执行前的几微秒内。

+

传统语言在执行之前会经历三个步骤:分词/词法分析、解析/语法分析、代码生成,关于这三个步骤的具体工作,可以查看编译原理相关的文献,我们可以把这三个步骤统称为编译。不过 js 引擎要复杂的多,它会在编译的时候对代码进行性能优化,尽管给 js 引擎优化的时间非常少,但是它用尽了各种办法来保证性能最佳。

+

我们需要先了解三个名词。引擎:从头到尾负责整个 js 程序的编译及执行过程;编译器:负责词法分析及代码生成;作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。

+

var a = 2;,我们以这段程序为例,它首先声明了变量a,然后将2赋值给变量a。前一个阶段在编译器处理,后一个阶段由 js 引擎处理。

+

变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。

+

变量提升

+

用过 js 的人都知道 js 存在变量提升,那么它到底是如何提升的呢?我们看下面的一段代码

+
console.log(a);
+var a = 2;
+
+

上述代码在a声明之前访问了变量a,按我们的逻辑它应该会抛出 ReferenceError 异常;或是变量提升直接输出 2。但是这两种答案都不对,输出的是undefined

+

回顾一下前文的关于编译的内容,引擎会在解释 js 代码之前对其进行编译,编译阶段的一个重要工作就是找到所有的声明,并用合适的作用域将它们关联起来,包括变量和函数在内的所有声明都会在任何代码被执行之前首先被处理。所以我们前面列出来的代码实际上会变成下面这个样子。

+
var a;
+console.log(a);
+a = 2;
+
+

这个过程就好像变量和函数声明会从它们的代码中出现的位置被移动到最上面一样,这个过程就是提升。但是需要注意的是,函数声明会首先被提升,然后才是变量提升。

+
foo(); // 1
+var foo;
+
+function foo() {
+    console.info(1);
+}
+
+foo = function() {
+    console.info(2);
+}
+
+

这段代码输出 1 而不是 2 ,它会被引擎理解为下面的形式。

+
function foo() {
+    console.log(1);
+}
+
+foo(); // 1
+
+foo = function() {
+    console.log(2);
+};
+
+

可以看到,虽然var foo出现在function foo()之前,但是它是重复的声明,因此会被忽略掉,因为函数函数声明会提升到普通变量前。所以在在同一个作用域中进行重复定义是一个很糟糕的做法,经常会导致各种奇怪的问题。

+

LHS 和 RHS 查询

+

LHS 和 RHS 是数学领域内的概念,意为等式左边和等式右边的意思,在我们现在的场景下就是赋值操作符的左侧和右侧。当变量出现在赋值操作符的左边时,就进行 LHS 查询;反之进行 RHS 查询。

+

RHS 查询与简单的查找某个变量的值没什么区别,它的意思是取得某某的值。而 LHS 查询则是试图找到变量容器的本身,从而可以对其进行赋值。

+

console.info(a);我们深入研究一下这句代码。这里对a的引用是 RHS 引用,因为这里a并没有赋予任何值,相应的需要查找并取得a的值,这样才能传递给console.info()

+

a = 2;a的引用则是一个 LHS 引用,因为实际上我们并关心a当前的值是什么,只是想为= 2这个赋值操作找到一个目标。

+
function foo(a) {
+    console.info(a);
+}
+foo(2);
+
+

为了加深印象,我们再来分析一下上述代码中的 RHS 和 LHS 引用。最后一行foo()函数的调用需要对foo进行 RHS 引用。这里有一个很容易被忽略的细节,2 被当作参数传递给foo()函数时,2 会被分配给参数a,为了给参数a(隐式地)分配值,需要进行一次 LHS 查询,也就是说代码中隐含了a = 2的语句。

+

前文已经说过了console.info(a);会对a进行一次 RHS 查询,需要注意的是console.info()本身也需要一个引用才能执行,因此会对console对象进行 RHS 查询,并检查得到的值中是否有一个log方法。

+

为什么区分 LHS 和 RHS

+

我们考虑下面的一段代码,就可以为什么要区分 LHS 和 RHS 查询了,而且区分它们是分厂有必要的。

+
function foo(a) {
+    console.info(a + b);
+    b = a;
+}
+foo(2);
+
+

第一次对b进行 RHS 查询时是无法找到该变量的,这是一个未声明的变量,在任何相关的作用域中都无法找到它。如果 RHS 查询在所有嵌套作用域中都找不到该变量,引擎就会抛出 ReferenceError 异常。

+

引擎在执行 LHS 查询时,如果在全局作用域中也无法找到目标变量,全局作用域就会创建一个具有该名称的变量,并将其返还给引擎。

+
+

需要注意的是,在严格模式下是禁止自动或隐式地创建全局变量的,因此在严格模式中 LHS 查询失败时,引擎同样会抛出 ReferenceError 异常。

+
+

接下来,如果 RHS 查询找到了一个变量,但是你尝试对这个值进行不合理的操作,比如对一个非函数类型的值进行函数调用,那么引擎就会抛出另一种叫做 TypeError 的异常。

+

作用域链

+

执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中,在 Web 浏览器中,全局执行环境被认为是window对象,因此所有的全局变量和函数都是作为window对象的属性和方法创建的。

+

每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,而函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境,这个函数调用的压栈出栈是一样的。

+

当代码在环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端始终都是当前执行的代码所在环境的变量对象,说的比较抽象,我们可以看下面的示例。

+
var color = "blue";
+
+function changeColor() {
+    var anotherColor = "red";
+
+    function swapColors() {
+        var tempColor = anotherColor;
+        anotherColor = color;
+        color = tempColor;
+        // 这里可以访问 color、anotherColor 和 tempColor
+    }
+    // 这里可以访问 color 和 anotherColor,但不能访问 tempColor
+    swapColors();
+}
+// 这里只能访问 color
+changeColor();
+
+

下面的图形象的展示了上述代码的作用域链,内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。函数参数也被当做变量来对待,因此其访问规则与执行环境中的其它变量相同。

+
window
+  |-----color
+  |-----changeColor()
+            |----------anotherColor
+            |----------swapColors()
+                           |----------tempColor
+
+

作用域链还用于查询标识符,当某个环境中为了读取或写入而引入一个标识符时,必须通过搜索来确定该标识符实际代表什么。搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符,如果在局部环境中找到了该标识符,搜索过程就停止,变量就绪;如果在局部环境没有找到这个标识符,则继续沿作用域链向上搜索,如下所示:

+
var color = "blue";
+
+function getColor() {
+    var color = "red";
+    return color;
+}
+
+console.info(getColor()); // "red"
+
+

getColor()中沿着作用域链在局部环境中已经找到了color,所以搜索就停止了,也就是说任何位于局部变量color的声明之后的代码,如果不使用window.color都无法访问全局color变量。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/bdeuyZAFy/index.html b/bdeuyZAFy/index.html new file mode 100644 index 00000000..39a75c95 --- /dev/null +++ b/bdeuyZAFy/index.html @@ -0,0 +1,529 @@ + + + + + + + + 知识图谱如何构建?——经济责任审计知识图谱构建案例实战 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 知识图谱如何构建?——经济责任审计知识图谱构建案例实战 +

+ + +
+ +
+
+

参考:
+汉语言处理包 HanLP:https://github.com/hankcs/HanLP
+中文文本分类:https://github.com/gaussic/text-classification-cnn-rnn
+农业知识图谱:https://github.com/qq547276542/Agriculture_KnowledgeGraph
+事实三元组抽取:https://github.com/twjiang/fact_triple_extraction
+中文自然语言处理相关资料:https://github.com/mengxiaoxu/Awesome-Chinese-NLP
+开放中文实体关系抽取:http://www.docin.com/p-1715877509.html

+
+

自 2012 年 Google 提出“知识图谱”的概念以来,知识图谱就一直是学术研究的重要方向,现在有很多高校、企业都致力于将这项技术应用到医疗、教育、商业等领域,并且已经取得了些许成果。Google 也宣布将以知识图谱为基础,构建下一代智能搜索引擎。

+

现在已经可以在谷歌、百度、搜狗等搜索引擎上面看到知识图谱的应用了。比如在 Google 搜索某个关键词时,会在其结果页面的右边显示该关键字的详细信息。在几个常用的搜索引擎中搜索知识时,返回的答案也变得更加精确,比如搜索“汪涵的妻子”,搜索引擎会直接给出答案“杨乐乐”,方便了用户快速精准的获取想要的信息。不过目前的搜索引擎只有少部分搜索问题能达到这种效果。

+

关于知识图谱是什么,我想就不用介绍了,这种通过搜索引擎就能轻松得到的结果写在这里有点浪费篇章,并且我对知识图谱的理解也不深,不敢夸夸其谈,只是把自己这一段时间以来的工作做一个总结。

+

本文只相当于以经济责任审计这一特定领域构建了一个知识图谱,仅仅是走了一遍流程,当作入门项目,构建过程中参考甚至抄袭了别人的很多方法与代码,末尾都会给出参考的项目等等。

+
+

上图是我构建经济责任审计知识图谱的流程,看起来很繁琐,但只要静下心看,个人觉得相对还算清晰,箭头都有指向。下面就一步一步进行说明。

+

数据获取

+

数据获取主要分为两部分数据,一部分是新闻类数据,我把它用作文本分类模型的训练集;另一部分是实体数据,为了方便,我直接把互动百科抓取的词条文件作为实体,省了属性抽取这一环节。

+

因为本文构建的是一个经济责任审计领域的知识图谱,所以作为文本分类模型训练集的数据也应该是经济责任审计领域的。这里主要抓取了审计署、纪检委、新浪网的部分新闻。

+
+

像上面的图一样,新闻类网站一般都有搜索框,为了简单,所以我直接用搜索框搜索“经济责任审计”,然后从搜索结果中抓取新闻数据,即认为是经济责任审计相关的文本。抓取新闻类网站使用了 chrome 模拟用户进行访问。最终获得了 3500 多条新闻文本。

+

领域词汇判定

+

领域词汇判定,本文构建的不是开放领域的知识图谱,所以需要采用一种方法来判定所抓取的内容是否属于经济责任审计领域。领域词汇本文的方法实际上是领域句子判定,直接使用了大神的项目。CNN-RNN中文文本分类,基于tensorflow。也看到有人通过改进逻辑回归算法,在进行领域词汇的判定。

+

我判定领域词汇的逻辑是这样的,一个词语即使是人类也不一定能确定它是否属于经济责任审计领域,但是每个词语都会有它的含义解释对不对,一个词语的解释就是一段话。我用网上的新闻训练出一个判断一段话属于哪个领域的模型,然后把词语的解释放到模型了里面去,如果模型给出的结果是属于经济责任审计领域,那则认为这个词语属于经济责任审计领域。

+

实体关系抽取

+

知识图谱的基本单位为(实体1,关系,实体2)这样的三元组,实体是直接从互动百科获取的词条,关系由两部分组成,一部分来自 wikidata 所提供的关系,这一部分直接从 wikidata 爬取即可得到,另一部分使用的是基于依存句法分析的开放式中文实体关系抽取,已经在前面的文章发过了。

+

知识存储

+

有了实体和实体关系,那么把这些数据进行筛选,然后入库,通过直观的页面展示,就可以了。这里使用的数据库是 neo4j,它作为图形数据库,用于知识图谱的存储非常方便。知识的展示使用了别人的项目,仅仅是把里面的数据换掉了而已,感谢大神的无私。

+

当然你也可以选择使用关系型数据库,因为我做的经济责任审计知识图谱不够深入,所以做到最后展示的时候,发现其实用我比较熟悉的 MySql 更好,相比 NOSql 我更熟悉关系型数据库,而且 MySql 有更大的社区在维护,它的 Bug 少、性能也更好。

+

最后放几张效果图

+
+
+

下面是以“职业”为关系查询条件所得出的结果。

+
+

总结一下

+

只是对几个月工作的梳理,大多数核心代码都改自现有的代码,所有的数据都来自于网络,与知识图谱相关的公开技术较少,我也只是尝试着做了一下,虽然很菜,也可以对大致的技术路线、流程有一个简单的了解,主要工作都是自然语言处理的内容。后期可以利用现在的知识图谱构建智能问答系统,实现从 what 到 why 的转换。

+

以下内容更新于 2020 年 3 月。

+

在毕业前收到了电子工业出版社和另一家出版社的写书邀请,我和电子工业出版社签订了写书合同,从还未毕业开始断断续续写作了一年的时间,因为自己的懒惰,加上内容中涉及到大量爬虫,而且爬目标网站是政府网站(不允许爬),另外 19 年网上时不时曝出某某程序员因爬虫而入狱的故事,出版社和我难免不会恐惧,我也正好找到了不再继续写下去的理由。

+

花了点时间把以前的程序,书籍已经写成的内容整理了一下,放在了 economic_audit_knowledge_graph 中,所有资料都在里面,希望能帮助到自然语言入门的小伙伴,我自己已经不做这个领域了!

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/br0hmOiiG/index.html b/br0hmOiiG/index.html new file mode 100644 index 00000000..2d20329e --- /dev/null +++ b/br0hmOiiG/index.html @@ -0,0 +1,762 @@ + + + + + + + + BUCK 电路基础知识 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ BUCK 电路基础知识 +

+ + +
+ +
+
+

参考内容:

+

手撕Buck!Buck公式推导过程

+

电力电子的基础应用

+

《精通开关电源设计(第二版)》

+

Buck电源芯片输出有问题?检查这几样

+

原来PWM这么简单!

+

为什么 DC-DC 芯片设计中都有一个自举电容?

+

如何克服开关电源中的最小导通时间挑战

+
+

BUCK 电路构建

+

根据高中所学习的物理知识可以很容易的想到,使用一个滑动变阻器即可实现降压和稳压的效果。当负载波动时,通过改变滑动变阻器的阻值,可以调节负载所获得的电压。但是使用滑动变阻器的劣势也很明显,大量的耗能会导致器件温度快速升高。

+
+
+

上面所提到的电路主要缺点在于导通器件(变阻器或三极管)本身存在耗能,那么有没有不会耗能的导通器件呢?首先肯定不能选导线,不然又回到最原始的问题,所有电压都被加到负载上了。有没有能不耗能且能控制加在负载电压的导通器件呢?最常见的机械开关就能做到这个效果。

+
+

当开关闭合时,负载即获得电压源输出的电压;当开关打开时,负载所获得的电压为 0V。计算平均值可以确定达到了降压的目的,通过控制开关闭合的时间长短,就可以达到调节电压的效果。但仔细想想就会发现不对劲,电路并不会帮助我们计算平均值,负载所获得的电压波形如下图所示,是完美的方波,并不是一条直线。

+
+

控制开关闭合的时间,即后文要讲的控制占空比

+
+
+

此时很容易就能想到利用电容两端电压不能突变的特点,给负载并联一个电容即可,电容即保证负载可以获得连续的能量流。

+
+

一旦引入了电容,就需要考虑浪涌电流的问题。根据公式 Q=CV=ItQ=CV=It 可得 I=CVtI=\frac{CV}{t},开关闭合时电压在非常短的时间内升高,所以电流会突然变得很大。

+

我们当然可以简单的利用电阻来抑制浪涌电流,但不幸的是电阻总要消耗功率。为了最大限度的提高效率,可以考虑使用电感,电感本身不消耗任何能量,只会进行储能,且其无损限流的能力正好可以用来抑制电容的浪涌电流。

+
+

引入电感后可以发现当开关打开时,电感没有续流回路,因此需要想办法构造电感的续流回路。续流回路需要保证不论开关打开还是闭合,电流都流向负载,且开关闭合时电源正极与负极回路必须经过电感与负载。这个需求很符合二极管的特点,即只允许单向导通。

+
+

到目前为止我们构建了非同步 BUCK 电路,考虑到机械开关容易磨损、使用寿命短、有机械惯性(转换频率低)的问题,我们需要将机械开关换成转换频率高的半导体器件,此处我们选择 NMOS 管来替代开关。

+
+

选择 NMOS 和 PMOS 的主要区别在于驱动电路的设计

+
+
+

可以发现当 NMOS 开关管导通时,续流二极管处于截止状态;当开关管关断时,续流二极管处于导通状态。即二极管的导通和截止和开关管的截止导通是同步的,也就是说二极管起到的是一个开关的作用。而且考虑到电流从二极管流过期间,二极管两端的压降恒定为导通电压 0.7V,二极管所消耗的能量较大。因此我们也可以把二极管换为导通电阻更小的 NMOS 管。

+
+

为了提高 BUCK 电路的稳定性,防止由于输入纹波带来异常,我们在 BUCK 电路的输入端并联一个电容,用于滤除输入电压的纹波。

+
+

至此我们就搭建了一个标准的同步 BUCK 电路,我们将其简单变个样子,再加几个标签即可得到下图。在同步 BUCK 电路中需要两个开关管密切配合,以防止整个线路导通,所以它们之间需要保持一定的相位关系,即上管导通下管截止;上管截止下管导通,我们把这种关系称之为同步

+
+

信号角度理解 LC

+

我们以占空比为 0.5 来进行说明,将时域下的方波转换到频域,通过傅立叶变换可以分解出一系列的频率分量。其中包含频率为 0 的分量,即直流分量,也就是我们想要保留的部分,还有频率为 n 倍 fsf_{s} 的分量。那么如何把我们不想要的部分去掉呢?从滤波角度考虑就需要加入一个低通滤波器。

+
+

通过加入低通滤波器可以把高频分量滤除,把二阶低通滤波器的截止频率设置在 0 到 fsf_{s} 之间,即可把 fsf_{s} 所有以上的部分给滤除。整体达到的效果即通过一个 LC 低通滤波器,配合一个开关网络,将一个数字化的电平重新滤出,得到一个比较平缓的电压输出,这个过程即完成了电压从高到低的转换。其中直流分量的大小受占空比 D 控制,所以通过改变占空比 D 即可改变输出电压大小。

+
+

稳态分析

+

我们需要先强调一下前提,此处我们说的稳态分析,即输入电压输出电压都是稳定,且纹波足够小的状态。下文的所有计算都将基于稳态进行分析,并且是在 (F)CCM(连续导通模式)下计算的。

+

我们将一些已知条件列出来:

+
    +
  • 输入电压:ViV_{i}
  • +
  • 输出电压:VoV_{o}
  • +
  • 负载电阻:RR
  • +
  • 输出电感:LL
  • +
  • 开关频率:ff
  • +
+

伏秒平衡

+

当上管导通下管截止时,电感右边的电压为 ViV_{i},左边的电压为 VoV_{o},因为同步 BUCK 电路是降压电路,所以 Vi>VoV_{i}>V_{o},所以电感两端电压即为 ViVoV_{i}-V_{o},也就是说是一个恒定值。由于有 Ldidt=ViVoL\frac{\mathrm{d} i}{\mathrm{d} t} =V_{i}-V_{o},所以 didt=ViVoL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{V_{i}-V_{o}}{L},即电感电流的上升斜率,由于是稳态前提,所以可以确定该值是一个常数。

+

当上管截止下管导通时,电感右边电压为 VoV_{o},左边电压为 00,所以电感两端电压为 0Vo0-V_{o},即 Vo-V_{o}。由于 Ldidt=VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-V_{o},所以 didt=VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-\frac{V_{o}}{L},即电感电流的下降斜率,也是一个常数。

+

整个电路处于稳定状态,负载电路恒定,那么在一个周期内,电感电流增加的量肯定等于电感电流减小的量,即充了多少电就要放多少电,不然负载的电流或电压将会发生变化。

+

前文已有didt=UL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{{U}}{L},而 LL 恒定,那么电感电流的变化速度即与电压成正比关系,即电感电流上升(下降)的斜率与电压成正比关系。而电感电流上升和下降的高度相同,那么上升时间和下降时间就自然构成反比关系。

+

TonToff=VoViVo\frac{T_{on} }{T_{off} } = \frac{V_{o}}{V_{i}-V_{o}},将其进行简单变换即可得到闻名江湖的伏秒平衡法则

+

Ton(ViVo)=ToffVoT_{on}(V_{i}-V_{o}) = T_{off}V_{o}

+

占空比

+

已知 T=Ton+Toff=1fT=T_{on}+T_{off}=\frac{1}{f},结合伏秒平衡法则可以计算出:

+

开通时间:Ton=VoVi1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f}

+

关断时间:Toff=ViVoVi1fT_{off} = \frac{V_{i}-V_{o} }{V_{i} } \bullet \frac{1}{f}

+

占空比:D=TonT=VoViD = \frac{T_{on} }{T}=\frac{V_{o} }{V_{i}}

+

纹波电流

+

由于输出电压不变,也就是说输出电容两端的电压没有变化,即输出电容的平均电流为 0。根据输出节点的基尔霍夫电流定律可知,输出节点电流和为 0,那么功率电感的平均电流就等于负载的平均电流,即IL=Io=VoRI_{L} = I_{o} = \frac{V_{o} }{R}

+
+

上文计算电感电流斜率时已经能确定电流波形是个三角波,纹波电流等于在开关导通时电感电流的增大值,也等于在关断时电感电流减小的值,计算任意一个即可得到纹波电流。我们以上管导通时增大的电感电流计算。

+
+

上管导通时电感两端电压为 ViVoV_{i}-V_{o},导通时间为 Ton=VoVi1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f},根据 U=LdidtU=L\frac{\mathrm{d} i}{\mathrm{d} t} 可知:

+

IL=di\triangle I_{L} =di

+

=TonUL=T_{on}\bullet \frac{U}{L}

+

=ViVoLVoVi1f=\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}

+

根据理论计算可以发现,电感电流的纹波和负载电流的大小没有关系,但是负载电流与平均电感电流是相等关系。

+

功率电感选择

+

根据上文的信息进一步可以计算出电感的峰值电流:

+

ILP=Io+IL2I_{LP} =I_{o}+\frac{\triangle I_{L}}{2}

+

=Io+ViVo2LVoVi1f=I_{o}+\frac{V_{i}-V_{o}}{2L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}

+

那么在选择功率电感时,电感的饱和电流就必须要大于这个ILPI_{LP},并且需要留有一定的裕量。实际应用时电感的纹波电流应是平均电流的 30%30\%50%50\% 为宜,我们将这个参数称之为电流纹波率 r。根据电流纹波率范围就可以计算出电感值的范围:

+

IL=ViVoLVoVi1f\triangle I_{L} =\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}

+

L=ViVoILVoVi1fL =\frac{V_{i}-V_{o}}{\triangle I_{L}}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}

+

=ViVo(0.30.5)IoVoVi1f=\frac{V_{i}-V_{o}}{(0.3至0.5) I_{o}}\bullet \frac{V_{o}} {V_{i}}\bullet \frac{1}{f}

+

=(ViVo)Vo(0.30.5)IoVif=\frac{(V_{i}-V_{o})V_{o}}{(0.3至0.5) I_{o} V_{i} f}

+

为何 r 为 0.3~0.5

+

电流纹波率即是电感电流的交流分量与其相对应的直流分量的比值,一旦 r 确定,那么输入输出滤波电容的电流、开关管的有效电流等都确定了,因此 r 的选择会影响器件选择和芯片的成本。使用公式可以表述为:

+

r=IIL=2×IACIDCr=\frac{\triangle I}{I_{L}}=2\times \frac{I_{AC}}{I_{DC}}

+

一般认为,电感体积与其能量处理能量成正比,因为要处理更高的能量就需要更大的磁芯。选择电感磁芯的能量处理能力至少要等于其需存储量,即 E=12×L×Ipk2E=\frac{1}{2} \times L \times I_{pk}^{2},下图是 E 与 r 的的函数曲线,可以发现在 r=0.4r=0.4 附近有一个拐点。

+
+

选择的 r 如果较 0.4 低很多,则所需要的电感体积越大;而若继续增大 r,则电感的体积并不会减少多少,即当 r 超过 0.4 后,通过增加 r 来减少电感体积的效果已经不明显了。

+

输入纹波

+

电源输入功率为 Pi=ViIiP_{i}=V_{i}I_{i},负载功率为 Pr=VoIoP_{r}=V_{o}I_{o},不考虑开关损耗、导通损耗等等因素,那么输入功率和输出功率相等,可得输入平均电流为 Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}

+

输入电压纹波就是输入电容上面电压的变化,这个变化可以分为两部分。一部分为电容充放电所导致的电压变化 UqU_{q},另一部分为电流流过电容 ESRESR 导致的压降 UesrU_{esr}。即 Vi=Uq+User\triangle V_{i} = U_{q} + U_{ser}

+

Q=CiUq=Iit=IiToff\because Q = C_{i}U_{q} = I_{i}t = I_{i}T_{off}

+

Uq=IiToffCi\therefore U_{q} = \frac{I_{i}T_{off}}{C_{i}}

+

Toff=ViVoVif\because T_{off} = \frac{V_{i}-V_{o} }{V_{i}f }

+

Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}

+

Uq=VoIoCiVifViVoVi\therefore U_{q} = \frac{V_{o}I_{o}}{C_{i}V_{i}f}\bullet \frac{V_{i}-V_{o}}{V_{i}}

+

要想知道 ESRESR 所造成的纹波,只需要知道流过输入电容的电流即可。当上管断开时,电源输入电流 IiI_{i} 全部流入电容 CiC_{i}。电感电流原本从下管的体二极管续流,当上管导通后,变为了从上管续流。因为此前电感一直处于放电状态,所以切换的那一刻电感电流是最小的,为 ILIL2I_{L}-\frac{\triangle I_{L}}{2}

+

在整个 TonT_{on} 时间内,电感都被充电,电感电流一直都在增大,直到 IL+IL2I_{L}+\frac{\triangle I_{L}}{2},并且在 TonT_{on} 时间内,电感电流都是走的上 MOS 管通路,所以上 MOS 管最大电流也是 IL+IL2I_{L}+\frac{\triangle I_{L}}{2}

+

根据基尔霍夫电流定律可知,输入节点的电流和为 0,那么输入电源电流 IiI_{i} 和电容 CiC_{i} 的放电电流就等于通过上 MOS 管的电流。所以 CiC_{i} 的最大放电电流即为 IL+IL2IiI_{L}+\frac{\triangle I_{L}}{2} - I_{i}。我们约定充电为正,放电为负,则放电电流为 IiIL2ILI_{i} - \frac{\triangle I_{L}}{2} - I_{L}

+
+
+

上管截止时 ESRESR 的压降为 IiESRI_{i} \bullet ESR,上管导通时压降为 (IiIL2IL)ESR(I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESR,则可得:

+

User=IiESR+(IiIL2IL)ESRU_{ser} = I_{i} \bullet ESR + (I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESR

+

=(IL+IL2)ESR=(I_{L} + \frac{\triangle I_{L}}{2}) \bullet ESR

+

IL==ViVoLVoVi1f\because \triangle I_{L} ==\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}

+

IL=IoI_{L} = I_{o}

+

User=(Io+(ViVo)Vo2ViLf)ESR\therefore U_{ser} = \left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR

+

综上所述可得:

+

Vi=Uq+Uesr\triangle V_{i} = U_{q} + U_{esr}

+

=VoIoCiVifViVoVi+(Io+(ViVo)Vo2ViLf)ESR=\frac{V_{o}I_{o}}{C_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}} +\left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR

+

输入电容选择

+

考虑到电容的实际使用情况,陶瓷电容的 ESRESR 小,容量小,所以 UqU_{q} 对纹波起决定性作用,输入纹波可近似为 UqU_{q}。若选择铝电解电容,则 ESRESR 大,容量大,UesrU_{esr} 对纹波起到决定性作用,输入纹波可以近似为 UesrU_{esr},假设电路设计要求输入纹波不能大于 Vi\triangle V_{i},则有:

+

陶瓷电容:CiVoIoViVifViVoViC_{i} \ge \frac{V_{o}I_{o}}{\triangle V_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}}

+

铝电解电容:ESRViIo+(ViVo)Vo2fLViESR \le \frac{\triangle V_{i}}{I_{o} + \frac{(V_{i}-V_{o})V_{o}}{2fLV_{i}} }

+

输出纹波

+

输出纹波与输入纹波同理,亦是 Vo=Uq+Uesr\triangle V_{o} = U_{q} + U_{esr},我们画出负载、功率电感、输出电容三者的电流波形。其中电感的纹波电流是 IL\triangle I_{L},则电容的纹波电流也是 IL\triangle I_{L},又因为电容的平均电流为 0,所以充电电流和放电电流都是 IL2\frac{\triangle I_{L}}{2}

+

电容充放电的总电荷量 Q 等于电流乘以时间,即图中阴影三角形的面积,三角形底部时间为 T2\frac{T}{2},高为 IL2\frac{\triangle I_{L}}{2},所以总的放电量可以计算出来为 Q=12T2IL2Q=\frac{1}{2} \bullet \frac{T}{2} \bullet \frac{\triangle I_{L}}{2}

+
+

结合 Q=CoUqQ=C_{o}U_{q} 可得:

+

Uq=(ViVo)Vo8ViCoLf2U_{q} = \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} }

+

由前面电流波形可知,电容的充电电流最大是 IL2\frac{\triangle I_{L}}{2},放电电流最大是 IL2-\frac{\triangle I_{L}}{2},则可以得到 ESRESR 引起的总压降为:

+

User=IL2ESR(IL2ESR)U_{ser} = \frac{\triangle I_{L}}{2} \bullet ESR - (-\frac{\triangle I_{L}}{2} \bullet ESR)

+

IL=ViVoLVoVi1f\because \triangle I_{L} = \frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}

+

(ViVo)VoViLfESR\therefore \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR

+

最终可得:

+

Uo=Uq+Uesr\bigtriangleup U_{o} = U_{q} + U_{esr}

+

=(ViVo)Vo8ViCoLf2+(ViVo)VoViLfESR=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} } + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR

+

=(ViVo)VoViLf(ESR+18fCo)=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf}\bullet \left ( ESR + \frac{1}{8fC_{o}} \right )

+

输出电容选择

+

与输入电容选择的方式一致,考虑是容值还是 ESRESR 占主导地位,假设要求输出纹波要小于 Vo\triangle V_{o},则有:

+

陶瓷电容:Co(ViVo)Vo8ViVoLf2C_{o} \ge \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}\triangle V_{o}Lf^{2} }

+

铝电解电容:ESRVoViLf(ViVo)VoESR \le \frac{\triangle V_{o}V_{i}Lf}{\left ( V_{i}-V_{o} \right ) V_{o}}

+

电感续流模式

+

电感电流曲线不能断续(无突降),因为电流断续会引起实际不可能发生的能量断续现象。但是电流的变化率可以突变,比如从上升斜率(电感储能增加)变为下降斜率(电感储能释放),尽管这样电感电流也必须连续。根据稳定状态下每个周期电流是否回到零,划分为不同的导通模式,并且通过减小负载电流,可以使电路从 CCM 经过 BCM 最终转变为 DCM。

+

CCM(连续导通模式)

+

稳定状态下每个周期,电流都回到某一非零值,称之为连续导通模式(CCM:Continuous Conduction Mode)。CCM 是功率变换中最常见的工作模式,有输出纹波小但功耗高的特点。

+

FCCM(强制连续导通模式)只存在于同步 BUCK 中,由于使用 MOS 管将非同步拓扑的二极管取代,MOS 管的导通压降远低于二极管压降,除了显著减小了续流通路的导通损耗外,也允许电感电流反向,即从负载瞬时流出电流。

+
+

DCM(断续导通模式)

+

若稳定状态下每个周期中电流都会回到零,那么就称之为断续导通模式(DCM:Discontinuous Conduction Mode),DCM 由于其电感电流的不连续,计算平均电感电流就需要更加详细复杂的公式,这也是 DCM 方程看上去复杂的根本原因。

+
+

BCM(临界导通模式)

+

BCM 是临界导通模式(Boundary Conduction Mode),由控制器监控电感电流,一旦检测到电流等于 0,功率开关立即闭合,控制器总是等电感电流「复位」来激活开关。即 BCM 处于 CCM 和 DCM 之间,可以将其视为 CCM 和 DCM 的极端情况,所以 BCM 模式下可以自由的选择 CCM 或 BCM 方程。

+
+

DC-DC 功能框图

+

前文所构建的 BUCK 电路只能是纸上谈兵,还需要解决诸多问题才能应用于实际电路。

+

基础驱动与控制

+

首先需要解决的问题就是 MOS 管不可能平白无故就打开,所以我们需要添加 MOS 管驱动器。

+
+

理想情况是上管关闭,下管立刻打开,中间没有任何时间差,但是 MOS 管并非理想开关,从关断到导通存在一个过渡的过程,若同时导通则电源通过上下 MOS 管直接对地短路,很容易就会导致 MOS 损坏,甚至可能会把前一级电源也损坏,所以上下管同时导通的状态必须得避免。

+

为了避免上下管直通的情况,实际应用会故意让上管和下管切换时多等一会儿,宁愿出现同时关断的情况,也不能出现同时导通的状态,这个等待的过程就叫做死区时间

+
+

需要注意的是,在死区时间内虽然下管没有被导通,但是功率 MOS 管本身存在一个寄生二极管,这个寄生二极管可以像非同步 BUCK 那样帮助电感续流,而且这个时间非常的短暂,所以产生的功耗没有那么大,因此不必担心系统会出问题。

+

到目前为止,不知道您有没有发现我们都在自嗨,系统中并没有用来控制上下 MOS 导通和关断的信号。因此需要增加一个振荡器用来产生控制信号,注意我们在前文中使用的是占空比一词,也就是说我们要使用的是 PWM(脉冲宽度调制)。当然你也可以使用 PFM(脉冲频率调制),本文只介绍 PWM 方式。

+
+

PWM(脉冲宽度调制)

+

PWM 的全称是脉冲宽度调制(Pulse-width modulation),是通过将有效的电信号分散成离散形式,从而来降低电信号所传递的平均功率的一种方式。其基本实现原理是通过锯齿波/三角波(载波)与所需要合成的波形(调制波)进行比较,然后确定 PWM 所需要输出的极性。因为一般都是用到开关器件上,通常是 ON 或者 OFF,具体如下图所示。

+
+

将振荡器输出的锯齿波和参考值 VthV_{th} 进行比较,就可以输出 PWM 波形了。话不多说,上图就明白了。

+
+

上图中的锯齿波(橙色)最大为 10,但是我们希望输出平均为 5 的波形(图中紫色的水平线),那么通过比较器进行比较,当锯齿波小于 5 时,PWM 即输出低电平 OFF,当锯齿波大于 5 时,PWM 即输出高电平 ON,此时的占空比即为 50%。

+

若是想输出一个电压逐渐抬高的波形,即占空比逐渐增大,那只需要将调制的波形设置为斜坡输出即可达到效果。比如下图中可以看到,占空比从 0% 逐渐增大到 100%。

+
+

同样的道理,我们可以通过改变调制波形,进一步调制出来其它的波形,比如要调制一个正弦波(sin wave),也就是我们常说的 SPWM,那么就是下面的样子。

+
+

负反馈环路

+

有了调制信号,开关管也可以正常打开与关闭,看起来可以应用到实际电路中了,但是别忘了负载的电阻并不是恒定的,负载的变化必然会引起输出电压的波动。为了减小输出电压的波动,我们可以在输出端添加分压电阻,与误差放大器和基准电压一起构成负反馈回路,这种通过取样输出电压进行闭环反馈的方式称之为电压模式控制

+
+

误差放大器的输入端分别为带隙基准源输出电压采样,当输出电压减小/增大时,与基准电压的细微差异都会被误差放大器放大,今儿调节脉冲宽度来达到调节调整输出电压的目的。图中 R2 接地,所以可以很容易计算出输出电压与分压电阻的关系:Vout=Vref(R1+R2)R2V_{out} = \frac{V_{ref}(R_{1}+R_{2})}{R_{2}}

+

除了输出电压可以用作控制取样信号,还有输入电压、输出电流、输出电感电压、开关器件峰值电流可以作为控制取样信号。使用这些信号可以构成单环、双环或多环反馈系统,进而实现稳压、稳流以及恒定功率的目的,也可以实现过流、过压、均流等功能。

+

现在回过头来评判一下电压模式控制的优缺点。单一的反馈电压闭环设计使得调试更加容易、对输出负载的变化有比较好的响应调节、占空比的调节也不会受到什么限制等等都是它的优点,但是其缺点也很明显。由于主电路有较大的输出电容和电感的相移延时作用,输出电压的变小/变大也延时滞后,再经过误差放大器的延时,使得瞬态响应变得更慢。由于电压控制模式不采样电流,逐周期限流保护功能必须另外增加电路来实现。

+

峰值电流模式控制在电压模式控制的基础上又增加了电流环,所以峰值电流模式控制是一个双环反馈系统。误差电压信号与一个变化的,其峰值代表输出电感电流峰值的三角波形进行比较,然后得到 PWM 脉冲的关断时刻。所以峰值电流模式控制不是使用电压误差信息直接控制 PWM 脉冲宽度,而是直接控制峰值输出侧的电感电流大小,进而间接的控制 PWM 脉冲宽度。

+
+

峰值电流在逻辑上与平均电感电流大小变化一致,但是峰值电感电流的大小并不能与平均电感电流的大小一一对应。在占空比不同的情况下,相同的峰值电感电流大小可以对应不同的平均电感电流大小,但平均电感电流大小才是唯一决定输出电压大小的因素。

+

为了解决不同占空比对平均电感电流大小的扰动作用,使得所控制的峰值电感电流最后收敛于平均电感电流,需要将电感电流下斜坡斜率的至少一半以上斜率加在实际检测电流的上斜坡上,这一点可以从数学上进行证明(具体咋证明暂不讨论)。

+

总结一下峰值电流模式控制 PWM 是双闭环控制系统,电压外环控制电流内环。电流内环是瞬时快速按照逐个脉冲工作的。功率级石油电流内环控制的电流源,而电压外环再控制次功率级电流源。电流内环只负责输出电感的动态变化,电压外环仅需控制输出电容,所以峰值电流模式控制 PWM 具有比电压模式控制大得多的带宽。

+

为了防止在应用过程中可能出现的短路等异常场景,DC-DC 少不了过温保护、过流保护、过压保护等保护手段。再设定一定的辅助功能,比如 PG 状态显示、缓启动、欠压保护等即可搭建完整的 DC-DC 电路。

+
+

异常模式

+

参考上文中的电路图,我们把绿色部分称之为控制电路,灰色部分是功率电路,功率电路中最核心的就是上下两个 MOS 管,下文我们讨论不同的异常场景中,控制电路、上管、下管三部分应该处于什么状态,其中控制电路关闭相当于整个芯片重启。

+

过压保护

+

当输出电压偏高并且达到了过压保护的阈值。过压状态需要控制电路去调整把输出电压降下来,所以不需要重启整个芯片。可以想到输出端已经处于过压状态了,上管如果打开那会加重过压的程度,因此上管需要关闭。若下管打开,则电感、负载、下管形成回路,即电感有续流回路,会把过压状态维持的时间更长,因此下管也需要关闭。综上有:过压保护:关上管、关下管

+

过温保护

+

温度过高的情况无非两种,一种是流过芯片的电流太大,即功率太大导致芯片自身发热达到了过温保护的阈值,此时关闭芯片肯定可以解决,另外切断电流回路也是可以解决的,即关闭上管。过温的第二种情况是由于环境温度过高而导致芯片温度过高,此时最好还是关闭芯片吧。综上有:过温保护:关闭芯片

+
+

关闭芯片指关闭芯片中的 BUCK 部分,但是基准源部分仍然保持工作

+
+

过流保护

+

过流保护还需要区分是正向过流还是负向过流,因为工作在 FCCM 模式的 DC-DC 在轻载或空载时,可能会有负向过流的情况。存在负向过流的另一原因也是因为同步 BUCK 没有像非同步 BUCK 那样的整流二极管,所以当存在负向过流情况时,直接模拟非同步 BUCK 中的二极管即可。综上有:负向过流保护:关下管

+

若发生正向过流时如何进行保护呢?首先考虑到电流经上管到负载,既然已经过流了那么肯定需要关上管。为了使电流减小的更快,那么就需要将电流流向地,所以需要将下管打开以构成回路。综上有:正向过流保护:关上管、开下管

+

异常排查

+

不管系统设计的多好,在实际应用中都可能会或多或少出现问题,比如电感选用不合适、触发 min-on time、触发 min-off time、输出电容 ESR 过大等,下面我们逐一进行讨论。

+

min-on time

+

虽然 MOS 管打开速度很快,但是打开始终是一个过程,要完成一个过程就必须需要一定的时间,当高频且压差大的情况下很容易触发完成「打开」这个过程的最小时间。也就是说占空比已经是实际最小了,占空比无法再降低了,所以查看输出电压纹波可能会出现下面的波形。

+
+

出现该波形的原因在于,占空比已经无法继续降低,所以电压整体处于逐渐抬高的趋势,当抬高到一定程度时即触发过压保护,上下管都关断,所以电压快速下降。

+

min-off time

+

与 min-on time 相对应的是 min-off time,当开关频率足够高且输入和输出电压接近时即容易出现此问题,此时即达到系统所能达到的最大占空比也无法满足负载所需要的电压,表现为输出电压无法达到设定值,负反馈分压电阻电压也低于电压基准值。

+

电感饱和电流过小

+

电感电流正常是一个三角波,但是如果电感饱和电流过小,则会电感电流将会变成下图很苗条的样子。因为电感电流饱和所以电流不再线性增加,电流快速增大导致磁通率减小,会导致磁性损耗增大、芯片热耗增大,而且这是一个正反馈过程,整个系统的可靠性会大大降低。

+
+

输出电容选用不合适

+

当输出电容选用过小时,会导致动态响应输出出现抖动。若输出电容的等效串联电阻(ESR)过大,也会导致输出纹波异常增大,这一点从前文的理论计算即可验证。因此在实际使用过程中需要同时考虑电容容值和所选电容的 ESR。

+

为什么需要 min-on time

+

占空比 D 控制相对于输入电压的输出电压,虽然通过提高开关频率有助于减小电感尺寸,但是也必须满足最小导通时间(min-on time)才能使芯片正常工作。那么这个 min-on time 是由哪些因素引起的呢?

+

因为上管中电流波形前沿的电流尖峰。由于 MOS 管也是由 PN 结组成,存在 PN 结就肯定存在结电容,MOS 管的寄生电容 CgsC_{gs}CgdC_{gd} 会导致上管在导通时电流突然变化,也就是说会出现电流尖峰。如果在这个电流尖峰的时间段内去检测电流的话,很可能就会触发过流保护,因此开关电路的最小导通时间必须大于电流尖峰出现的时间,这个时间我们称之为消隐时间

+
+

另一个原因是因为上下管开关完成后,由于键合线存在寄生电感的原因会产生很大的振铃,这个振铃同样可能会导致峰值电流检测出错,需要一个 min-on time 将这个振铃隔离过去。

+

为什么需要 min-off time

+

如下图所示,最简单的需要最小关断时间(min-off time)的原因是,若下管不打开则没有办法给自举电容充电,所以需要在该时间内给自举电容充电,为下一个开关周期做准备。

+
+

另一个原因是因为没有最小关断时间,即占空比 D 增大到 100%,那么就无法对负向电流、谷值电流进行采样,也就无法实现实现相应的异常保护功能。与 min-on time 一致,电流检测也需要一个 min-off time 隔离振铃。

+

为什么需要自举电容

+

DC-DC 的上 MOS 管可以是 PMOS,也可以是 NMOS。但是一般因为生产工艺问题,PMOS 导通电流往往做不不到很大,而在相同成本下 NMOS 的导通电流可以做到更大,也就是 RdsonR_{dson} 可以做到相对较低,所以往往更倾向于 NMOS。

+

将上管换为 NMOS 后也带来了新的问题,如何打开 NMOS ?如图所示,上管的 S 极连接 PH 点,该点的电压为 +5V,要打开 NMOS 需要 VGS>0V_{GS} > 0,驱动 MOS 管打开的压降需要 5V,那么驱动电压就需要 +10V 才可以打开上管,但是纵观整个电路并没有能达到 +10V 级别的电压,所以需要自举电容来进行升压才能打开上 MOS 管。

+
+

所以 DC-DC 芯片是否需要自举电容是由芯片所选用的 MOS 管类型决定的,若是 PMOS 则无需自举电容。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/c4hRhCBs7/index.html b/c4hRhCBs7/index.html new file mode 100644 index 00000000..32cf9935 --- /dev/null +++ b/c4hRhCBs7/index.html @@ -0,0 +1,535 @@ + + + + + + + + 使用订阅号实现微信公众号历史文章爬虫 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 使用订阅号实现微信公众号历史文章爬虫 +

+ + +
+ +
+

微信公众号已经成为生活的一部分了,虽然里面有很多作者只是为了蹭热点,撩读者的 G 点,自己从中获得一些收益;但是不乏好的订阅号,像刘大的码农翻身、Fenng的小道消息、曹大的caoz的梦呓等订阅号非常值得阅读。

+

平时有时候看到一些好的公众号,也会不自觉去查看该公众号的历史文章,然而每次都看不完,下一次再从微信里面打开历史文章,又需要从头翻起。而且对于写了很多年的大号,每次还翻不到底。有一些平台提供了相关的服务,但是得收几十块钱的费用,倒不是缺几十块钱,主要是觉得这种没必要花的钱不值得去浪费。

+

网上搜如何爬微信公众号历史文章,大致给了三种思路,第一是使用搜狗微信搜索文章,但是好像每次能搜到的不多;第二是使用抓包工具;第三种是使用个人订阅号进行抓取。

+

简单来说就是使用程序来模拟人的操作,抓取公众号历史文章。首先登录微信公众号个人平台,期间需要管理员扫码才能登录成功。

+
def __open_gzh(self):
+    self.driver.get(BASE_URL)
+    self.driver.maximize_window()
+    username_element = self.driver.find_element_by_name("account")
+    password_element = self.driver.find_element_by_name("password")
+    login_btn = self.driver.find_element_by_class_name("btn_login")
+    username_element.send_keys(USERNAME)
+    password_element.send_keys(PASSWORD)
+    login_btn.click()
+    WebDriverWait(driver=self.driver, timeout=200).until(
+        ec.url_contains("cgi-bin/home?t=home/index")
+    )
+    # 一定要设置这一步,不然公众平台菜单栏不会自动展开
+    self.driver.maximize_window()
+
+

进入微信公众平台首页后,点击素材管理,然后点击新建图文素材,就会进入到文章写作页面,此时前面打开的微信公众平台首页就不需要了,可以将其关闭。

+
+
def __open_write_page(self):
+    management = self.driver.find_element_by_class_name("weui-desktop-menu_management")
+    material_manage = management.find_element_by_css_selector("a[title='素材管理']")
+    material_manage.click()
+    new_material = self.driver.find_element_by_class_name("weui-desktop-btn_main")
+    new_material.click()
+    # 关闭公众平台首页
+    handles = self.driver.window_handles
+    self.driver.close()
+    self.driver.switch_to_window(handles[1])
+
+

在文章写作页面的工具栏上面有一个超链接按钮,点击超链接即会弹出超链接编辑框,选择查找文章,输入自己喜欢的公众号进行查找,一般第一个就是自己想要的结果,点击对应的公众号,该公众号所有的文章就会通过列表的形式展现出来。

+
+
+
+
def __open_official_list(self):
+    # 超链接
+    link_click = self.driver.find_element_by_class_name("edui-for-link")
+    link_click.click()
+    time.sleep(3)
+    # 查找文章
+    radio = self.driver.find_element_by_class_name("frm_vertical_lh").find_elements_by_tag_name("label")[1]
+    radio.click()
+    # 输入查找关键字
+    search_input = self.driver.find_element_by_class_name("js_acc_search_input")
+    search_input.send_keys(OFFICIAL_ACCOUNT)
+    search_btn = self.driver.find_element_by_class_name("js_acc_search_btn")
+    search_btn.click()
+    # 等待5秒,待公众号列表加载完毕
+    time.sleep(5)
+    result_list = self.driver.find_element_by_class_name("js_acc_list").find_elements_by_tag_name("div")
+    result_list[0].click()
+
+

文章列表已经展现出来了,直接抓取每条文章超链接的信息即可,每抓取完一页就进入下一页,继续抓取文章列表信息,直到所有文章信息都抓取完毕。

+
+
def __get_article_list(self):
+    # 等待文章列表加载
+    time.sleep(5)
+    total_page = self.driver.find_element_by_class_name("search_article_result")\
+        .find_element_by_class_name("js_article_pagebar").find_element_by_class_name("page_nav_area")\
+        .find_element_by_class_name("page_num")\
+        .find_elements_by_tag_name("label")[1].text
+    total_page = int(total_page)
+    articles = []
+    for i in range(0, total_page-1):
+        time.sleep(5)
+        next_page = self.driver.find_element_by_class_name("search_article_result")\
+            .find_element_by_class_name("js_article_pagebar").find_element_by_class_name("pagination")\
+            .find_element_by_class_name("page_nav_area").find_element_by_class_name("page_next")
+        article_list = self.driver.find_element_by_class_name("js_article_list")\
+            .find_element_by_class_name(" my_link_list").find_elements_by_tag_name("li")
+        for article in article_list:
+            article_info = {
+                "date": article.find_element_by_class_name("date").text,
+                "title": article.find_element_by_tag_name("a").text,
+                "link": article.find_element_by_tag_name("a").get_attribute("href")
+                }
+            articles.append(article_info)
+        next_page.click()
+    return articles
+
+

至此,微信公众号历史文章的爬虫已经实现,其实整个过程只不过是用程序来模拟的了人类的操作。需要注意的是,程序不能设置太快,因为微信做了相关限制,所以设太快会在一段时间内无法使用文章查找功能;另外一点是使用选择器选择页面元素的时候,会有一些坑,而且我发现不同账号登录,有很少部分的页面元素虽然直观上是一样的,但是它的 html 代码有细微的差别。

+

这个小程序会用到selenium库,和chromedriver,前者直接pip install即可,后者自行下载;另外你还需要一个订阅号才行,本文只实现了关键的文章信息抓取,并没有进行文章信息的持久化存储,完整代码在这里。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/cCUDwnJ69/index.html b/cCUDwnJ69/index.html new file mode 100644 index 00000000..a933e87f --- /dev/null +++ b/cCUDwnJ69/index.html @@ -0,0 +1,766 @@ + + + + + + + + C 语言拾遗 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ C 语言拾遗 +

+ + +
+ +
+
+

约定:本文所说的标准均为 ANSI (C89) 标准

+
+

三字母词

+

标准定义了几个三字母词,三字母词就是三个字符的序列,合起来表示另一个字符。三字母词使得 C 环境可以在某些缺少一些必需字符的字符集上实现,它使用两个问号开头再尾随一个字符,这种形式一般不会出现在其它表达形式中,这样就不容易引起误解了,下面是一些三字母词的对应关系:

+
??(   [
+??)   ]
+??!   |
+??<   {
+??>   }
+??'   ^
+??=   #
+??/   \
+??-   ~
+
+

所以在一些特殊情况下可能出现下面的情况,希望你不要被意外到。

+
printf("Delete file (are you really sure??): ");
+
+// result is: Delete file (are you really sure]: 
+
+

字符

+

直接操作字符会降低代码的可移植性,应该尽可能使用库函数完成。比如下面的代码试图测试ch是否为一个大写字符,它在使用ASCII字符集的机器上能够运行,但是在使用EBCDIC字符集的机器上将会失败。

+
if( ch >= 'A' && ch <= 'Z')
+
+

使用if(isupper(ch))语句则能保证无论在哪种机器上都能正常运行。

+

字符串比较

+

库函数提供了int strcmp(const char *s1, const char *s2)函数用于比较两个字符串是否相等,需要注意的是在标准中并没有规定用于提示不相等的具体值。它只是说如果第 1 个字符串大于第 2 个字符串就返回一个大于零的值,如果第 1 个字符串小于第 2 个字符串就返回一个小于零的值。一个常见的错误是以为返回值是1-1,分别代表大于和小于。

+

初学者常常会编写下面的表达式。认为如果两个字符串相等,那么它返回的结果将为真。但是这个结果恰好相反,两个字符串相等的情况下返回值是零(假)。

+
if(strcmp(a, b))
+
+

strlen

+

strlen的返回值是一个size_t类型的值,这个类型是在头文件stddef.h中定义的,它是一个无符号整数类型,所以会导致下面表达式的条件永远为真。

+
if(strlen(x) - strlen(y) >= 0) {
+    // do something
+}
+
+

第二点需要注意的是strlen的返回值没有计算\0的长度,所以下面的代码在一些检查严格或老版本的编译器中会报错,其原因在于少分配了一个存储单位。

+
// 假设 str 是一个字符串
+char *cpy = malloc(strlen(str));
+strcpy(cpy, str);
+
+// 正确写法应为
+char *cpy = malloc(strlen(str) + 1);
+strcpy(cpy, str);
+
+

赋值截断

+

表达式a = x = y + 3;xa被赋予相同值的说法是错误的,因为如果x是一个字符型变量,那么y+3的值就会被截去一段,以便容纳于字符型的变量中,那么a所赋的值就是这个被截短后的值。下面也是一个非常常见的错误。

+
char ch;
+// do something
+while((ch = getchar()) != EOF) {
+    // do something
+}
+
+

EOF所需要的位数比字符型值所能提供的位数要多,这也是getchar返回一个整型值而不是字符值的原因。例子中把getchar的返回值存储于字符型变量会导致被截短,然后再把这个被截短的值提升为整型与EOF进行比较,在某些机器的特定场景下就会导致问题。比如在使用有符号字符集的机器上,如果读取了一个的值为\377的字节,上述循环就将终止,因为这个值截短再提升之后与EOF相等。而当这段代码在使用无符号字符集的机器上运行时,这个循环将永远不会终止。

+

指针与数组

+

因为数组和指针都具有指针值,都可以进行间接访问和下标操作,所以很多同学都想当然的将它们认为是一样的,为了说明它们是不相等的,我们可以考虑下面的两个声明:

+
int a[5];
+int *b;
+
+

声明一个数组时,编译器将根据声明所指定的元素数量为数组保留空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为任何指向现有的内存空间。所以在上述声明之后,表达式*a是完全合法的,但是表达式*b将访问内存中某个不确定的位置,或者导致程序终止。

+

硬件操作

+

我们知道其实表示就是内存中一个地址,所以理论上*100 = 25是一种可行的操作,即让内存中位置为100的地方存储25。但实际上这条语句是非法的,因为字面值100的类型是整型,而间接访问操作只能用于指针类型表达式,所以合法的写法必须使用强制转换,即*(int *)100 = 25

+

需要说明的是使用这种技巧的机会是绝无仅有的,只有偶尔需要通过地址访问内存中某个特定的位置才可使用,它并不是访问某个变量,而是访问硬件本身。比如在某些机器上,操作系统需要与输入输出设备控制器通信,启动 I/O 操作并从前面的操作中获得结果,此时这些地址是预先已知的。

+

+= 与 ?: 操作符

+

我们在这里讨论一下+=操作符,它的用法为a += expr,读作把 expr 加到 a,其实际功能相当于表达式a = a + expr的作用,唯一不同的是+=操作符的左操作数a只会求值一次。可能到目前为止没有感觉到设计两种增加一个变量值的方法有什么意义?下面给出代码示例:

+
// 形式 1
+a[ 2 * (y - 6*f(x)) ] = a[ 2 * (y - 6*f(x)) ] + 1;
+
+// 形式 2
+a[ 2 * (y - 6*f(x)) ] += 1;
+
+

在第一种形式中,用于选择增值位置的表达式必须书写两次,一次在赋值号左边,一次在赋值号右边。由于编译器无法知道函数f是否具有副作用,所以它必须两次计算下标表达式的值,而第二种形式的效率会更高,因为下标表达式的值只会被计算一次。同时第二种形式也减少了代码书写错误的概率。

+

同理三目运算符也可以起到类似的效果。

+
// 形式 1
+if(a > 5) {
+    b[ 2 * c + d * (e / 5) ] = 3;
+} else {
+    b[ 2 * c + d * (e / 5) ] = -20;
+}
+
+// 形式 2
+b[ 2 * c + d * (e / 5) ] = a > 5 ? 3 : -20;
+
+

逗号操作符

+

逗号操作符可以将多个表达式分隔开来。这些表达式自左向右逐个进行求值,整个表达式的值就是最后那个表达式的值。例如:

+
if(b + 1, c / 2, d > 0) { // do something}
+
+

当然,正常人不会编写这样的代码,因为对前两个表达式的求值毫无意义,它们的值只是被简单的丢弃了。但是我们可以看看下面的代码:

+
// 形式 1
+a = get_value();
+count_value(a);
+while(a > 0) {
+    // do something
+    a = get_value();
+    count_value(a);
+}
+
+// 形式 2
+while(a = get_value(), count_value(), a > 0) {
+    // do something
+}
+
+

指针

+
int* a, b, c;
+
+

人们会很自然的认为上述语句是把所有三个变量都声明为指向整型的指针,但事实上并非如此,星号实际上只是表达式*a的一部分,只对这个标识符有作用。如果要声明三个指针,那么应该使用下面的形式进行初始化。

+
int *a, *b, *c;
+
+

在声明指针变量时可以为它指定初始值,比如下面的代码段,它声明了一个指针,并用一个字符串常量对其进行初始化。

+
char *msg = "Hello World!";
+
+

需要注意的是,这种类型的声明会让人很容易误解它的意思,看起来初始值似乎是赋给表达式*msg的,但实际上它是赋值给msg本身的,也就是上述声明实际形式如下:

+
char *msg;
+msg = "Hello World!";
+
+

指针常量: int *pipi是一个普通的指向整型的指针, 而变量int const *pci则是一个指向整型常量的指针,你可以修改指针的值,但是不能修改它所指向的值。相比之下int * const cpi则声明cpi为一个指向整型的常量指针。此时指针是常量,它的值无法修改,但是可以修改它所指向的整型的值。在int const * const cpci中,无论是指针本身还是它所指向的值都是常量,无法修改。

+

枚举类型

+

枚举(enumerated) 类型就是指它的的值为符号常量而不是字面值的类型,比如下面的语句声明了Jar_Type类型:

+
enum Jar_Type {
+    CUP,
+    PINT,
+    QUART,
+    HALF_GALLON,
+    GALLON
+};
+
+

需要注意的是,枚举类型实际上是以整型方式存储的,代码段中的符号名实际上都是整型值。在这里CUP的值是0PINT的值是1,依次类推。

+

在适当的时候,可以为这些符号名指定特定的值整型值。并且只对部分符号名进行赋值也是合法的,如果某个符号名没有显示的赋值,那么它的值就比前面一个符号名的值大 1。

+
enum Jar_Type {
+    CUP = 8,
+    PINT = 16,
+    QUART = 32,
+    HALF_GALLON = 64,
+    GALLON = 128
+};
+
+
+

符号名被当作整型处理,这意味着可以把HALF_GALLON这样的值赋给任何整型变量,但是在编程活动中应该避免这种方式使用枚举,因为这样会削弱它们的含义。

+
+

typedef 与 define

+

在实际应用过程中应该使用typedef而不是#define来创建新的类型名,因为#define无法正确的处理指针类型,比如下面的代码段正确的声明了a,但是b却被声明为了一个字符。

+
#define ptr_to_char char *
+ptr_to_char a, b;
+
+

联合(union)

+

联合看起来很像结构体,与结构体不同的是联合的所有成员共用同一块内存,所以在同一时刻联合中的有效成员永远只有一个。我们可以看下面一个例子,当一个variable类型的变量被创建时,解释器就创建一个这样的结构并记录变量类型。然后根据变量类型,把变量的值存储在这三个值字段的其中一个。

+
struct variable {
+    enum { INT, FLOAT, STRING } type;
+    int int_val;
+    float float_val;
+    char *str_val;
+}
+
+

不难发现上述结构的低效之处在于它所使用的内存,每个variable结构存在两个未使用的值字段,造成了内存空间上的不少浪费。使用联合就可以减少这种空间上的浪费,它把这三个值字段的每一个都存储在同一个内存位置。我们知道这三个字段并不会冲突,因为每个变量只可能具有一种类型,所以在具体的某一时刻,联合的这几个字段只有一个被使用。

+
struct variable {
+    enum { INT, FLOAT, STRING } type;
+    union {
+        int i;
+        float f;
+        char *s;
+    } val;
+}
+
+

现在,对于整型变量,我们只需要将type字段设为INT,并把整型值存储于val.i即可。如果联合中各成员的长度不一样,联合的长度就是它最长成员的长度。

+

联合的变量也可以被初始化,但是这个初始值必须是联合第 1 个成员的类型,而且它必须位于一对花括号里面。比如:

+
union {
+    int a;
+    float b;
+    chat c[4];
+} x = { 5 };
+
+

结构体

+

在实际编程活动中,存在链表、二叉树等结点自引用的情况,那么结构体的自引用如何编写呢?

+
struct node {
+    int data;
+    struct node next;
+}
+
+

上述写法是非法的,因为成员next是一个完整的结构,其内部还将包含自己的成员next,这第 2 个成员又是另一个完整结构,它还将包含自己的成员next,如此重复下去将永无止境。正确的自引用写法如下:

+
struct node {
+    int data;
+    struct node *next;
+}
+
+

我们需要注意下面的这个陷阱:

+
/*
+错误写法:因为类型名 node_t 直到声明末尾才定义
+所以在结构中声明的内部 node_t 尚未定义
+*/
+typedef struct {
+    int data;
+    node_t *next;
+} node_t;
+
+// 正确写法
+typedef struct node_tag {
+    int data;
+    struct node_tag *next;
+} node_t;
+
+编译器在实际分配时会按照结构体成员列表的顺序一个接一个的分配内存,并且只有当存储成员需要满足正确的边界对齐要求时,成员之间可能会出现用于填充的额外内存空间。
+
+```c
+struct align {
+    char a;
+    int b;
+    char c;
+}
+
+

如果某个机器的整型值长度为 4 个字节,并且它的起始存储位置必须能够被 4 整除,那么这个结构在内存中的存储将是下面这种形式

+ + + + + + + + + + + + + + + + + +
abbbbc
+

我们可以通过改变成员列表的声明顺序,让那些对边界要求严格的成员首先出现,对边界要求弱的成员最后出现,这样可以减少因为边界对齐而带来的空间损失。

+
struct align {
+    int b;
+    char a;
+    char c;
+}
+
+ + + + + + + + + + + +
bbbbac
+

当程序创建几百个甚至几千个结构时,减少内存浪费的要求就比程序的可读性更为急迫。我们可以使用sizeof操作符来得出一个结构的整体长度。如果必须要确定结构某个成员的实际位置,则可以使用offsetof(type, member)宏,例如:

+
offset(struct align, b);
+
+

一句话

+

标识符:标识符就是变量、函数、类型等的名字,标识符的长度没有限制,但是 ANSI 标准允许编译器忽略第 31 个字符以后的字符,并且允许编译器对用于表示外部名字(由链接器操作的名字)的标识符进行限制,只识别前 6 位不区分大小写的字符。

+

注释:代码中所有的注释都会被预处理器拿掉,取而代之的是一个空格。因此,注释可以出现在任何空格可以出现的地方。

+

类型:C 语言中仅有 4 种基本数据类型,即整型、浮点型、指针和聚合类型(数组、结构等),所有其它的类型都是从这 4 中基本类型的某种组合派生而来。

+

类型长度:标准只规定了short int至少是 16 位,long int至少是 32 位,至于缺省的int是多少位则直接由编译器设计者决定。并且标准也没有规定这 2 个值必须不一样。如果某种机器的环境字长是 32 位,而且也没有什么指令能够更有效的处理更短的整型值,那它很可能把这 3 个整型值都设定为 32 位。

+

位域:基于 int 位域被当作有符号还是无符号数、位域成员的内存是从左向右还是从右向左分配、运行在 32 位整数的位域声明可能在 16 位机器无法运行等原因,注重可移植性的程序应该避免使用位域。

+

结构与指针:什么时候应该向函数传递一个结构而不是一个指向结构的指针呢?很少有这种情况。只有当一个结构特别小(长度和指针相同或更小)时,结构传递方案的效率才不会输给指针传递方案。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/dDolfjfkM/index.html b/dDolfjfkM/index.html new file mode 100644 index 00000000..7cb58702 --- /dev/null +++ b/dDolfjfkM/index.html @@ -0,0 +1,580 @@ + + + + + + + + 转载——我的喜欢从误会开始|最后的记忆 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 转载——我的喜欢从误会开始|最后的记忆 +

+ + +
+ +
+

我的喜欢从误会开始

+
+

作者:伍什弦

+
+

我说过
+我不擅长和男生做朋友
+所以
+一点点的小事
+都能让我误会
+误以为你喜欢我

+

课堂上
+向我借笔
+下课后
+加我 QQ
+那时候 QQ 封面出现点赞
+整个假期
+我们不停的互赞
+空间里互相留言

+

这些小事偷偷在我脑中生根
+而我被你深深的吸引
+想多见你
+想靠近你

+

在运动会上看到你胜利冲过终点
+这一晚我梦里是你
+连着四晚我梦里都是你

+

我知道
+我喜欢你是从这个时候开始
+可是喜欢的种子早在课堂上埋下

+

我误会了
+误以为
+你也是喜欢我

+

最后的记忆

+
+

作者:伍什弦

+
+

我想啊
+人这一生
+大抵都有疯狂的喜欢过一人
+除此一人之外
+剩下的喜欢相比不过十之一二

+

读大学前
+以为从小学喜欢到高中的那个人
+当算作我的初恋
+可是他闯入我的心怀
+从此苍山洱海不过一人

+

那时节
+他总能出现在我的梦里
+我呢
+总想把最好的都给他
+为他做这样那样的事情
+可是他对这样的我说
+我们永远都是好哥们
+就这一句

+

之后近一个月
+我几乎不说一句话
+从不逃课的我逃了不止一节

+

整个学期
+我几乎是在图书馆生活的
+读完了
+一部十三本的德川家康
+看了
+不知多少部电影
+那种伤心仍是挥之不去
+不过是随着时间淡淡忘却罢了

+

后来
+身边也有喜欢我的人
+开始我也觉得这人还好
+总是一段时间过后
+就没缘由的厌恶
+在还没来得及向我任何表示前
+就已经疏远了他们

+

毕业的时候
+我对他说:祝你幸福
+之后班级一起去 K 歌
+我走前
+最后唱了一首
+每每听到都忍不住要流泪的歌
+Someone Like You

+

回去的路上
+好友告诉我
+我在唱的时候
+他是闭着眼睛听的
+是他第一个鼓掌的
+听到这句话的我
+眼泪不受控制般流下
+我
+站在天桥上大声呼喊
+我再也不喜欢你了
+可真的是这样吗

+

无论如何
+青春
+已离我远去
+那感觉如此清晰
+以后再不会如此喜欢一人

+

纵有
+江山万里织锦绣
+我心
+除却巫山不是云

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/dM1xQIc5h/index.html b/dM1xQIc5h/index.html new file mode 100644 index 00000000..cb509422 --- /dev/null +++ b/dM1xQIc5h/index.html @@ -0,0 +1,457 @@ + + + + + + + + 分享一点关于租房经验 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 分享一点关于租房经验 +

+ + +
+ +
+

在牛客网上看到一个创作者计划,分享关于租房、理财一类的帖子可以获得一定的奖励,这篇文章首发于牛客网,稍微做了一点点补充再发到本博客平台。

+

估计大多数小伙伴都是在类似自如、贝壳、蘑菇租房等平台上找房,关于这些平台的经验网上已经能够查到很多了,我就不再去分享关于这些平台的避坑经验了。这里分享一点关于我的稍微偏门一点的租房方法吧。前面提到的自如那些平台至少是有一个作用的,可以通过它们大致了解某个地域租房的价格区间,基本可以定位哪里便宜、哪里热门,基本上自己去找的话都不会比这些平台还要贵。

+

豆瓣租房讨论组上面可以找到一些转租、直租的信息,比如我要在深圳南山区租一间小房子,那么我可以去豆瓣搜索「深圳南山租房」,进入相关的讨论组可以看到豆油们的一些讨论信息,除了能找到一些房源信息外还可以通过大家的评论了解一丢丢避坑指南。

+
+

需要注意的是即使是豆瓣这样纯粹的平台,里面也免不了很多水军或是广告信息,还有一些中介也会在里面发信息,这个就需要自己去甄别过滤无效信息了,实在不行可以加个微信聊一聊就知道了嘛。另外不要轻易把自己的常用电话留到平台去,不然什么安装宽带的、其它包租公司、甚至什么洗衣服洗鞋一类电话有可能会打到你心烦。

+

如果已经定了要去哪个城市参加工作,那招你进去的 HR 这个资源是可以使用的,稍微大一点的公司都会有内部社区平台,这种平台应该只有入职之后才能使用。公司的同事也会在上面发一些关于租房的信息,大可以让 HR 帮助自己看一眼截个图或是转发给你,因为都是同一个公司的同事所以遇到坑的可能性要小很多,毕竟抬头不见低头见的,说不定哪一天你们还会一起共事呢,内部人要实在一些。

+

如果能联系到已经入职的朋友、师兄师姐、前辈更好,找他们问一问公司同事一般都在哪里租房,哪里的房子住着舒服、房租便宜、通勤方便,这些信息只有他们才掌握的最精准,所以先问一下他们绝对不是脱裤子放屁,运气好的话说不定还有师兄师姐正好要转租房子呢,当然也可以让他们帮你推荐一下房东信息,这样可以快速的帮助你缩小搜索范围。

+

假设你在 A 地点工作,可以以 A 地为中心看一看周围 5 公里的地方,一些房好价低的房东不一定懂的如何在网上发布租房信息,如果有时间的话可以去溜达溜达(反正你也是要逛超市买生活用品的嘛),看到一个有意思的小区就去和大爷唠唠嗑,尤其那种看着年龄不小又缺人唠嗑的大爷,他们嘴巴里能吐出来很多有用的信息。

+

实际看房的时候需要注意一下房间的朝向和楼层,楼层太低照不到阳光住着压抑,楼层太高夏天可能会非常的热,在深圳那种城市的高楼层空调是极有可能顶不住酷暑的。还得注意一下周围有没有工地什么的,不然太吵睡不好觉也烦心。一些基本生活设施也要对比一下,比如离超市有多远、出门约会是否方便等等。

+

签约时问好房间物品损坏应该怎么赔偿,房租到期押金应该怎么退?每天晚上大门是否会锁?忘记带钥匙/房卡怎么处理?钥匙/房卡丢了补办是否要另交费?房间有问题的地方一定要拍个照片留个证据,避免到时候退房的时候有争议被扣押金。像水电这些应该每个人都会问的,我这里就不再赘述了。

+

虽然互联网已经很发达了,但是还是有很多房东不知道如何在网上发布招租信息,或者是发布招租信息是要花钱的,这个门槛把一些房东给挡在互联网之外了。他们会在一些社区公示栏、或者在朋友门店显眼的地方贴一些联系方式,这些信息只有自己实际去走动才能拿到。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/dTizl9yei/index.html b/dTizl9yei/index.html new file mode 100644 index 00000000..9ea78945 --- /dev/null +++ b/dTizl9yei/index.html @@ -0,0 +1,275 @@ + + + + + + + + SEO | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + SEO +
+ + +
+

+ + SEO 入门——你必须了解站长平台知识 + +

+ +
+ + + + +
+ +
+ 我自己也刚接触建站不久,10 月 1 日开始购买服务器和域名学习建站,10 月 11 日一查已经被 Google 收录了 20 多个链接了,下面是我个人就我自己的实践总结的一点关于站长平台的知识。 +基本上只要做的大的搜索引擎都会有站长平台的,站长平台是搜索引擎官方提供的辅助网站优化管理的工具,它可以针对我们的网站提供一定的优化方向,而且新站一开始搜索引擎的爬虫可能来都不会来,我们一开始也需要通过站长平台主动给搜索引擎提交我们的站点地图。 +盘点业界主流站长平台 +既然我们讲的是站长平台知识,肯定是得先要去了解一些主流的站长平台,那业内知名的站长平台有哪些呢?下面是 Guanngxu 为大家盘点的主流站长平台。 +谷歌站长平台 +入口:https://search.google.com/search-console +谷歌在搜索引擎领域的地位不言而喻,稍微遗憾的是它已经退出中国市场了,它的技术、算法等在业界都是一流,即使它已经退出中国市场了,但也绝对是一个不可忽略的存在 +百度站长平台 +入口:https://ziyuan.baidu.com/ +目前国内使用最多的站长平台就是百度了,谷歌退出中国之后就百度一家独大,也是目前国内功能最完善的站长平台,对于网站管理、数据检测等都有一定的参考意义。 +搜狗站长平台 +入口:http://zhanzhang.sogou.com/index.php/site/index +搜狗的功能相对来说简单一些,只提供了一些基础的网站优化功能,虽然目前看搜狗搜索的流量也跟不上,搜狗现在已经被腾讯收购,不知道后续是否会有更大的发展,在国内搜狗的流量也还是不小的。 +必应站长平台 +入口:https://www.bing.com/toolbox/webmaster/ +必应是微软旗下的搜索引擎,必应在国内的市场占有率不太高,使用必应的大部分是讨厌百度的广告,又暂时不知道如何科学上网的人群,不过它的站长平台做的相对来说还是可以。 +头条站长平台 +入口:https://zhanzhang.toutiao.com/ +大家都知道字节跳动是互联网界的一匹黑马,字节跳动最近正在抖音等旗下的应用开始测试搜索广告业务,虽然头条站长平台很多功能处于不完善的状态,但是它之前的成绩也是大家有目共睹的,值得期待。 +神马站长平台 +入口:https://zhanzhang.sm.cn/ +神马站长平台是依托于 UC 浏览器和神马搜索衍生出来的一个平台,它大部分的流量都来自于移动端,所以它平台的功能比较偏向于移动端,功能比较基础,几乎没有什么算法和优化的通知。 +利用站长平台我们可以做些什么? +新站一般都不会有搜索引擎的爬虫程序主动来爬取的,人家甚至都不知道你这个小网站的存在,那么我们就需要主动的去告诉各个搜索引擎,让他们知道我们这个小网站的存在。比如百度站长平台,我们可以先到「站点管理」添加一个自己的站点。 + +说一下每个站长平台都具备的一个功能,那就是提交自己网站地图的接口。新站一开始都可以通过这些接口去提交我们网站的链接,将站点提交到各个搜索引擎以后,可以加快蜘蛛抓取我们网站的速度,增加网站被收录的速度。当然也有人说用老的域名比较好,这一点我自己并没有亲身尝试过,不敢妄下断论。 + +像必应站长后台还有个网站扫描功能,谷歌也有类似的网址检查功能,我们可以借用这些工具看看如何优化自己的网站,让自己网站的内容对搜索引擎爬虫程序更加友好。 + + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/ecG5hxBcr/index.html b/ecG5hxBcr/index.html new file mode 100644 index 00000000..5ac5eb21 --- /dev/null +++ b/ecG5hxBcr/index.html @@ -0,0 +1,771 @@ + + + + + + + + 数据库 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 数据库 +
+ + +
+

+ + Oracle 安装及 Spring 使用 Oracle + +

+ +
+ + + + +
+ +
+ +参考内容: +docker安装oracle数据库史上最全步骤(带图文) +Mac下oracle数据库客户端 +Docker安装Oracle +docker能安装oracle吗 +Batch script for add a auto-increased primary key for exist table with records + +Docker 安装 Oracle11g +注意:下列安装方式仅适用于x86架构服务器,不适用于arm架构服务器。 +# 拉取 oracle11,镜像有点大,需要花一些时间 +docker pull registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g + +# 查看镜像是否拉取成功 +docker images + +# 给镜像重新打 tag,原来的名字太长了 +docker tag registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g oracle11g:latest + +# 启动 oracle11g 容器 +docker run --name=oracle11g -itd -p 1521:1521 + +# 进入容器进行配置 +docker exec -it oracle11g /bin/bash + +# 切换到 root 用户,密码为:helowin +su root + +# 编辑配置文件 + +编辑/etc/profile,在其中增加如下内容: +export ORACLE_HOME=/home/oracle/app/oracle/product/11.2.0/dbhome_2 +export ORACLE_SID=helowin +export PATH=$ORACLE_HOME/bin:$PATH + +编辑完成后,需要刷新上述环境变量才能使用。 +# 刷新环境变量 +source /etc/profile + +# 创建软链接 +ln -s $ORACLE_HOME/bin/sqlplus /usr/bin + +# 切换到 oracle 用户 +su - oracle + +# 登陆 sqlplus +sqlplus /nolog +conn /as sysdba + +# 修改 system 用户密码 +alter user system identified by system; +# 修改 sys 用户密码 +alter user sys identified by system; + +# 创建内部管理员账号 +create user test identified by test; + +# 将 dba 权限授权给内部管理员账号和密码 +grant connect,resource,dba to test; + +# 修改密码规则策略为密码永不过期 +ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED; + +# 修改数据库最大连接数据 +alter system set processes=1000 scope=spfile; + +修改上述信息后,需要重新启动数据库才会生效。 +conn /as sysdba + +# 关闭数据库 +shutdown immediate; + +# 启动数据库 +startup; + +# 退出软链接 +exit; + +客户端连接 Oracle +以 Navicat 客户端为例,新建连接时按下图方式填写连接信息即可,密码即为system。需要注意的是,在 Windows 下选择 SID 或是服务名均可连接成功,但是在 Mac 下需要选择 SID 方式才能连接成功。 + +Oracle 实现主键自增 +Oracle 在创建表的时候,不能像 MySQL 那样选择主键直接自增,但是我们可以通过给表创建序列和触发器去实现自增。下文以创建 USER 表为例。 +-- 删除原有 USER 表 +DROP TABLE &quot;TEST&quot;.&quot;USER&quot;; +-- 创建 USER 表 +CREATE TABLE &quot;TEST&quot;.&quot;USER&quot; ( + &quot;id&quot; NUMBER NOT NULL, + &quot;gmt_create&quot; DATE NOT NULL, + &quot;gmt_modified&quot; DATE NOT NULL, + &quot;is_deleted&quot; NUMBER NOT NULL, + &quot;login&quot; NVARCHAR2(255) NOT NULL, + &quot;passwd&quot; NVARCHAR2(255) NOT NULL, + &quot;nick&quot; NVARCHAR2(255) NOT NULL, + &quot;phone&quot; NVARCHAR2(255), + &quot;head_img&quot; NVARCHAR2(255), + &quot;status&quot; NVARCHAR2(255), + &quot;remark&quot; NCLOB +); + +-- 删除原有序列 +DROP SEQUENCE &quot;TEST&quot;.&quot;USER_SEQ&quot;; +-- 创建 USER_SEQ 序列,最小值为 1 +CREATE SEQUENCE &quot;TEST&quot;.&quot;USER_SEQ&quot; +-- 最小值为 1 +MINVALUE 1 +-- 最大值为 9999999999999999999999999999 +MAXVALUE 9999999999999999999999999999 +-- 每次增加 1 +INCREMENT BY 1 +-- 将 20 个序列值放入缓存 +CACHE 20; + +-- 创建触发器 +CREATE TRIGGER &quot;TEST&quot;.&quot;USER_TRIGGER&quot; +-- 在插入数据前执行 +BEFORE INSERT ON &quot;TEST&quot;.&quot;USER&quot; +-- 命名老数据为 OLD,新数据为 NEW +REFERENCING OLD AS &quot;OLD&quot; NEW AS &quot;NEW&quot; +-- 针对表的每一行都执行触发器 +FOR EACH ROW +-- 将序列值赋值给 id +BEGIN + :NEW.&quot;id&quot; := USER_SEQ.NEXTVAL; +END; +/ + +需要注意的是,上面的/符号不能少。执行插入语句时可以发现,id会自动增加。 +INSERT INTO &quot;TEST&quot;.&quot;USER&quot; (&quot;gmt_create&quot;, &quot;gmt_modified&quot;, &quot;is_deleted&quot;, &quot;login&quot;, &quot;passwd&quot;, &quot;nick&quot;, &quot;phone&quot;, &quot;head_img&quot;, &quot;status&quot;, &quot;remark&quot;) VALUES (TO_DATE('2023-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), TO_DATE('2023-04-01 17:04:30', 'SYYYY-MM-DD HH24:MI:SS'), '0', 'user', '123', 'Jack', '1111', 'head.jpg', '激活', '测试'); + +Java Spring+Mybatis 使用 Oracle 及配置分页 +application.properties文件配置信息: +# mybatis +spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver +spring.datasource.url=jdbc:oracle:thin:@8127.0.0.1:1521:helowin +spring.datasource.username=system +spring.datasource.password=system +mybatis.mapper-locations=classpath*:mybatis/*.xml +mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl + +# pageHelper +pagehelper.helperDialect=oracle +pagehelper.reasonable=true +pagehelper.supportMethodsArguments=true +pagehelper.params=count=countSql + +pom.xml配置文件关键信息。 +&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; +&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; + xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt; + + &lt;properties&gt; + &lt;java.version&gt;1.8&lt;/java.version&gt; + &lt;maven.compiler.target&gt;1.8&lt;/maven.compiler.target&gt; + &lt;maven.compiler.source&gt;1.8&lt;/maven.compiler.source&gt; + &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt; + &lt;project.reporting.outputEncoding&gt;UTF-8&lt;/project.reporting.outputEncoding&gt; + + &lt;spring.boot-version&gt;2.1.3.RELEASE&lt;/spring.boot-version&gt; + &lt;/properties&gt; + + &lt;dependencyManagement&gt; + &lt;dependencies&gt; + &lt;dependency&gt; + &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; + &lt;artifactId&gt;spring-boot-dependencies&lt;/artifactId&gt; + &lt;version&gt;${spring.boot-version}&lt;/version&gt; + &lt;type&gt;pom&lt;/type&gt; + &lt;scope&gt;import&lt;/scope&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt; + &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt; + &lt;version&gt;2.1.0&lt;/version&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;com.oracle.ojdbc&lt;/groupId&gt; + &lt;artifactId&gt;ojdbc8&lt;/artifactId&gt; + &lt;version&gt;19.3.0.0&lt;/version&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt; + &lt;artifactId&gt;pagehelper-spring-boot-starter&lt;/artifactId&gt; + &lt;version&gt;1.4.3&lt;/version&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt; + &lt;artifactId&gt;pagehelper-spring-boot-starter&lt;/artifactId&gt; + &lt;/dependency&gt; + &lt;/dependencies&gt; + &lt;/dependencyManagement&gt; +&lt;/project&gt; + + +
+ + Read More ~ +
+
+
+ +
+

+ + Schema 与数据类型优化 + +

+ +
+ + + + +
+ +
+ +参考内容: +《高性能 MySQL(第三版))》 + +选择优化的数据类型 +世面上常见的数据库大多支持了多种多样的数据类型,选择正确的数据类型对于获得高性能至关重要,一般都需要遵循如下的几个原则: + +更小的通常更好:更小的通常更快,因为占用着更少的磁盘、内存和 CPU,并且处理时需要的 CPU 周期也更少; +简单就好:简单数据类型的操作通常需要更少的 CPU 周期; +尽量避免 NULL:如果查询中包含可为 NULL 的列,就会使得索引、索引统计和值比较变得复杂,因此在设计表是最好指定列为 NOT NULL。 + +整数类型 +在 MYSQL 中可以为整数类型指定宽度,例如INT(11),但是这对大多数应用是没有意义的,它不会限制值的合法范围,只是规定了 MySQL 的一些交互工具(如 MySQL 命令行客户端)用来显示字符的个数。对于存储和计算来说INT(1)和INT(20)是相同的。 +字符串类型 +需要注意的是当 MySQL 存储 CHAR 值时,它会删掉所有的末尾空格,因为 CHAR 值会根据需要采用空格进行填充以方便比较,这导致的问题就是你使用 CHAR 存储的string 会变成string。CHAR 的好处在于它是定长的,很适合存储像 MD5 值一样的定长值,定长值的 CHAR 类型不易产生碎片,而且对于非常短的列 CHAR 也会比 VERCHAR 好,比如CHAR(1)只需要一个字节,而VERCHAR(1)则需要两个字节,因为它还需要一个字节来存长度。 +VERCHAR 类型在存储可变长字符串时,会比 CHAR 更节省空间,它需要使用 1 或者 2 个额外的字节记录字符串的长度。但由于行是变长的,当一个行占用的空间增长,并且在页内没有更多的可用空间可以存储,就容易产生碎片。 +使用枚举代替字符串 +有时候可以使用枚举列代替常用的字符串类型,枚举列可以把一些不重复的字符串存储成一个预定义的集合,而且 MySQL 在存储枚举时非常紧凑,会根据列的数量压缩到一个或两个字节。比如下面的例子: +CREATE TABLE enum_test( + e ENUM('fish', 'apple', 'dog') NOT NULL +); + +INSERT INTO enum_test(e) VALUES('fish'), ('dog'), ('apple'); + +SELECT e+0 FROM enum_test; + +# result ++-----+ +| e+0 | ++-----+ +| 1 | +| 2 | +| 3 | ++-----+ + +可以看到使用枚举类型后,上面三行数据实际上存储为了整数,而不是字符串,而且还有一个让人吃惊的地方:枚举字段是按照内部存储的整数而不是定义的字符串进行排序的,这一点需要特别注意,不然在写程序时容易中犯错。当然你也可以在查询时使用FIELD()函数显式地指定排序顺序。 +可以看到上面 +范式和反范式 +关系型数据库有设计范式的概念,这一点在大学的数据库课程中肯定都会提及。因为有比较高的范式,那么就只有很少或者没有重复的数据,因此在 UPDATE 时只需要修改更少的数据;高范式的表通常也更小,因此占用的内存也会更小,操作起来也会更快...... +但是高范式也带来了另一个缺点,比较好的范式通常意味着需要关联,稍微复杂一点的查询就需要使用 JOIN,关联的代价是昂贵的,甚至让一些索引策略失效;而如果不需要关联,即使某个查询需要全表扫描,当数据比内存大时可能会比关联查询快的多。所以一般都会根据实际情况将范式与反范式混用,完全的范式化和完全的反范式化都是实验室才有的东西。 +缓存表和汇总表 +这里的「缓存表」和「汇总表」并没有什么标准的含义。我们用「缓存表」来存储那些可以从其他表获取,但是获取的速度很慢的数据;而「汇总表」则用来保存那些使用 GROUP BY 语句聚合数据的表。毫无疑问,我们存这些冗余数据也是为了性能。 +比如近两年各种应用流行的年终报告,每次网易云音乐的年终报告都会把朋友圈撑满,其它类似于缓存一个用户的朋友数、一个文件的下载次数等等。这些数据实时计算的开销是很大的,而且多数情况下用户也等不起实时计算的时间,一般的解决方案都是通过增加计数器表(缓存表)来解决这个问题。 +计算机科学中总是伴随着双面性,上面的计数器表带来性能提升的同时也带来了并发问题。网站的每一次点击都会导致对计数器的更新,对于任何想要更新这一行的事务来说,这条记录都有一个全局的互斥锁,这会使得这些事务只能串行的进行。每一次点击都会触发下面的语句,但大量的点击伴随着该行数据的互斥锁,想想性能也不会提升到哪里去吧。 +UPDATE hit_counter SET cnt = cnt + 1; + +大多数应用都是读查询居多,为了提升读查询的速度,经常会需要增加一些额外的索引,增加冗余列、汇总表、缓存表等等。但是不要忘了这些技巧也会增加写查询的负担,还会增加开发难度,因此应该根据实际应用场景来做权衡。 +加快 ALTER TABLE 表的速度 +MySQL 执行大部分修改表结构的方法都是用新的结构创建一个空表,然后从旧表中查出所有数据插入到新表,然后删除旧表。在内存不足、表很大、索引多的情况下会花费很长的时间。一个很严重的缺点是大部分 ALTER TABLE 操作将导致 MySQL 服务中断。 +对于常见的场景我们有两种技巧避免服务中断。一种是先在一台不提供服务的机器上执行 ALTER TABLE 操作,然后和提供服务的主库进行切换;另一种技巧是「影子拷贝」,即用要求的表结构创建一张和源表无关的新表,然后通过重命名和删除表的操作交换两张表。 + +
+ + Read More ~ +
+
+
+ +
+

+ + MongoDB 聚合(aggregate)入门 + +

+ +
+ + + + +
+ +
+ MongoDB 聚合官方文档 +聚合管道是一个基于数据处理管道概念建模的数据聚合框架,文档进入一个多阶段的处理管道,该管道最终将其转换为聚合后的结果。 +下面的例子来源于官方文档。第一阶段,$match按status字段来过滤文档,并把status字段值为A的文档传递到下一阶段;第二阶段,$group将文档按cust_id进行分组,并针对每一组数据对amount进行求和。 +db.orders.aggregate([ + { $match: { status: &quot;A&quot; } }, + { $group: { _id: &quot;$cust_id&quot;, total: { $sum: &quot;$amount&quot; } } } +]) + +管道 + +聚合管道包含很多步骤,每一步都会将输入的文档进行转换,但并不是每个阶段都一定需要对每个输入文档生成一个输出文档,比如某些阶段可能生成新的文档或者过滤掉文档。 +除了$out、$merge、$geoNear外,其它的阶段都可以在管道中多次出现,更加详细的内容可以查看 Aggregation Pipeline Stages。 + +管道表达式 +一些管道阶段采用表达式作为操作元,管道表达式指定了要应用到输入文档的转换,表达式自己是一个文档结构(JSON),表达式也可以包含其它的表达式。 +表达式仅提供文档在内存中的转换,即管道表达式只能对管道中的当前文档进行操作,不能引用来自其他文档的数据。 +写聚合表达式式建议直接参考官方文档,下面列出一些我收集的案例,供深入理解使用。 +案例一:将对象数组转换为单个文档 +// 转换前 +{ + &quot;_id&quot;: &quot;10217941&quot;, + &quot;data&quot;: [ + { + &quot;count&quot;: 2, + &quot;score&quot;: &quot;0.5&quot; + }, + { + &quot;count&quot;: 6, + &quot;score&quot;: &quot;0.3&quot; + }, + { + &quot;count&quot;: 5, + &quot;score&quot;: &quot;0.8&quot; + } + ] +} + +// 转换后 +{ + &quot;_id&quot;: &quot;10217941&quot;, + &quot;0.3&quot;: 6, + &quot;0.5&quot;: 2, + &quot;0.8&quot;: 5 +} + +需要说明的是,如果上面data属性中的数据格式为{&quot;k&quot;: &quot;0.6&quot;, &quot;v&quot;: 5},那么下面的聚合表达式就不需要$map,这一点可以查看 $arrayToObject。这个案例的难点在于score中有小数点,这个小数点会让聚合表达式懵逼的。 +db.collection.aggregate([ + { + &quot;$addFields&quot;: { + &quot;data&quot;: { + &quot;$arrayToObject&quot;: { + &quot;$map&quot;: { + &quot;input&quot;: &quot;$data&quot;, + &quot;as&quot;: &quot;item&quot;, + &quot;in&quot;: { + &quot;k&quot;: &quot;$$item.score&quot;, + &quot;v&quot;: &quot;$$item.count&quot; + } + } + } + } + } + }, + { + &quot;$addFields&quot;: { + &quot;data._id&quot;: &quot;$_id&quot; + } + }, + { + &quot;$replaceRoot&quot;: { + &quot;newRoot&quot;: &quot;$data&quot; + } + } +]); + + +
+ + Read More ~ +
+
+
+ +
+

+ + 磁盘到底是怎样工作的?一文理解硬盘结构 + +

+ +
+ + + + +
+ +
+ 数据库系统总会涉及到辅助存储(大多都是磁盘),因为它们能够存储大量需要长期保存的数据,因此我们有必要先了解了解磁盘的相关知识。 +根据机械原理,存储器的容量越大其速度就越慢。但是速度越快的存储器,其单位字节的价格就越贵。现代计算机系统可以包含几个不同的可以存储数据的部件,就形成了存储器的层次结构,但是需要注意的是「虚拟内存」是操作系统与操作系统运用机器硬件的产物,它不是存储器的层次之一。 +磁盘结构 +传统的硬盘盘结构是像下面这个样子的,它有一个或多个盘片,用于存储数据。盘片多采用铝合金材料;中间有一个主轴,所有的盘片都绕着这个主轴转动。一个组合臂上面有多个磁头臂,每个磁头臂上面都有一个磁头,负责读写数据。 + +磁盘一般有一个或多个盘片。每个盘片可以有两面,即第一个盘片的正面为0面,反面为 1 面;第二个盘片的正面为 2 面......依次类推。磁头的编号也和盘面的编号是一样的,因此有多少个盘面就有多少个磁头。盘面正视图如下图,磁头的传动臂只能在盘片的内外磁道之间移动。因此不管开机还是关机,磁头总是在盘片上面。关机时,磁头停在盘片上面,抖动容易划伤盘面造成数据损失,为了避免这样的情况,所以磁头都是停留在起停区的,起停区是没有数据的。 + +每个盘片的盘面被划分成多个狭窄的同心圆环,数据就存储在这样的同心圆环上面,我们将这样的圆环称为磁道 (Track)。每个盘面可以划分多个磁道,最外圈的磁道是0号磁道,向圆心增长依次为1磁道、2磁道......磁盘的数据存放就是从最外圈开始的。 + +根据硬盘的规格不同,磁道数可以从几百到成千上万不等。每个磁道可以存储数 Kb 的数据,但是计算机不必要每次都读写这么多数据。因此,再把每个磁道划分为若干个弧段,每个弧段就是一个扇区 (Sector)。扇区是硬盘上存储的物理单位,现在每个扇区可存储 512 字节数据已经成了业界的约定。也就是说,即使计算机只需要某一个字节的数据,但是也得把这个 512 个字节的数据全部读入内存,再选择所需要的那个字节。 + +柱面是我们抽象出来的一个逻辑概念,简单来说就是处于同一个垂直区域的磁道称为柱面 ,即各盘面上面相同位置磁道的集合。需要注意的是,磁盘读写数据是按柱面进行的,磁头读写数据时首先在同一柱面内从 0 磁头开始进行操作,依次向下在同一柱面的不同盘面(即磁头上)进行操作,只有在同一柱面所有的磁头全部读写完毕后磁头才转移到下一柱面。因为选取磁头只需通过电子切换即可,而选取柱面则必须通过机械切换。数据的读写是按柱面进行的,而不是按盘面进行,所以把数据存到同一个柱面是很有价值的。 +磁盘被磁盘控制器所控制(可控制一个或多个),它是一个小处理器,可以完成一些特定的工作。比如将磁头定位到一个特定的半径位置;从磁头所在的柱面选择一个扇区;读取数据等。 + +现代硬盘寻道都是采用CHS(Cylinder Head Sector)的方式,硬盘读取数据时,读写磁头沿径向移动,移到要读取的扇区所在磁道的上方,这段时间称为寻道时间(seek time)。因读写磁头的起始位置与目标位置之间的距离不同,寻道时间也不同。磁头到达指定磁道后,然后通过盘片的旋转,使得要读取的扇区转到读写磁头的下方,这段时间称为旋转延迟时间(rotational latencytime)。然后再读写数据,读写数据也需要时间,这段时间称为传输时间(transfer time)。 +根据上文的信息,我们可以得出磁盘容量的计算公式为: +硬盘容量 = 盘面数 × 柱面数 × 扇区数 × 512字节 + +笔试题实战 +下面的题目是腾讯某一年校招笔试中的一个题目,题干信息描述为:数据存储在磁盘上的排列方式会影响I/O服务的性能,一个圆环磁道上有10个物理块,10个数据记录R1~R10存放在这个磁道上,记录的安排顺序如下表所示。 + + + +物理块 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 + + + + +逻辑记录 +R1 +R2 +R3 +R4 +R5 +R6 +R7 +R8 +R9 +R10 + + + +假设磁盘的旋转速度为20ms,磁盘当前处在R1的开头处,若系统顺序扫描后将数据放入单缓冲区内,处理数据的时间为4ms(然后再读取下个记录),则处理这10个记录的最长时间是多少? + +答案:磁盘会一直朝某个方向旋转,不会因为处理数据而停止。本题要求顺序处理 R1 到 R10,起始位置在 R1,一周是 20ms,共 10 个记录,所以每个记录的读取时间为 2ms。首先读 R1 并处理 R1,读 R1 花 2ms,读好后磁盘处于 R1 的末尾或 R2 的开头,此时处理 R1,需要 4ms,因为磁盘一直旋转,所以 R1 处理好了后磁盘已经转到 R4 的开始了,这时花的时间为 2+4=6ms。这时候要处理 R2,需要等待磁盘从 R5 一直转到 R2 的开始才行,磁盘转动不可反向,所以要经过 8*2ms 才能转到 R1 的末尾,读取 R2 需要 2ms,再处理 R2 需要 4ms,处理结束后磁盘已经转到 R5 的开头了,这时花的时间为 2*8+2+4=22ms。等待磁盘再转到 R3 又要 8*2ms,加上 R3 自身 2ms 的读取时间和 4ms 的处理时间,花的时间也为 22ms,此时磁盘已经转到 R6 的开头了,写到这里,就可以看到规律了,读取并处理后序记录都为 22ms,所以总时间为 6+22*9=204ms。 + +如何加速对磁盘的访问 +对于理解数据库系统系统特别重要的是磁盘被划分为磁盘块(或像操作系统一样称之为页),每个块的大小是 4~64KB。磁盘访问一个磁盘块平均要用 10ms,但是这并不表示某一应用程序将数据请求发送到磁盘控制器后,需要等 10ms 才能得到数据。如果只有一个磁盘,在最坏的情况下,磁盘访问请求的到达个数超过 10ms 一次,那么这些请求就会被无限的阻塞,调度延迟将会变的非常大。因此,我们有必要做一些事情来减少磁盘的平均访问时间。 +按柱面组织数据:前这一点在前文已经提到过了。因为寻道时间占平均块访问时间的一半,如果我们选择在一个柱面上连续的读取所有块,那么我们只需要考虑一次寻道时间,而忽略其它时间。这样,从磁盘上读写数据的速度就接近于理论上的传输速率。 +使用多个磁盘:如果我们使用多个磁盘来替代一个磁盘,只要磁盘控制器、总线和内存能以 n 倍速率处理数据传输,则使用 n 个磁盘的效果近似于 1 个磁盘执行了 n 次操作。因此使用多个磁盘可以提高系统的性能。 +磁盘调度:提高磁盘系统吞吐率的另一个有效方法是让磁盘控制器在若干个请求中选择一个来首先执行,调度大量块请求的一个简单而有效的方法就是电梯算法。回忆一下电梯的运行方式,它并不是严格按先来后到的顺序为乘客服务,而是从建筑物的底层到顶层,然后再返回来。同样,我们把磁盘看作是在做横跨磁盘的扫描,从柱面最内圈到最外圈,然后再返回来,正如电梯做垂直运动一样。 +预取数据:在一些应用中,我们是可以预测从磁盘请求块的顺序的。因此我们就可以在需要这些块之前就将它们装入主存。这样做的好处是我们能较好的调度磁盘,比如采用前文的电梯算法来减少访问块所需要的平均时间。 +磁盘故障 +如果事情都像我们一开始设计的那样进行,那世界肯定会变得特别无聊。磁盘偶尔也会耍耍小脾气,甚至是罢工不干了。比如在读写某个扇区一次尝试没有成功,但是反复尝试后有成功读写了,我们称之为间歇性故障。 +一种更为严重的故障形式是,一个或多个二进制位永久的损坏了,所以不管我们尝试多少次都不可能成功,这种故障称之为介质损坏。 +另一种相关的错误类型称之为写故障,当我们企图写一个扇区时,既不能正确的写,也不能检索先前写入的扇区,发生这种情况的一种可能原因就是在写过程中断电了。 +当然肯定最严重的就是磁盘崩溃,这种故障中,整个磁盘都变为永久不可读,这是多么可怕的事情。 +既然会出现上面所述的各种大小故障,那么我们就必须要采取各种措施去应对大大小小的变故,保证系统能正常运行。 +规避故障 +我们尝试读一个磁盘块,但是该磁盘块的正确内容没有被传送到磁盘控制器中,就是一个间歇性故障发生了。那么问题是控制器如何能判断传入的内容是否正确呢?答案就是使用校验和,即在每个扇区使用若干个附加位。在读出时如果我们发现校验和对数据位不合适,那么我们就知道有错误;如果校验和正确,磁盘读取仍然有很小的可能是不正确的,但是我们可以通过增加趣多校验位来降低读取不正确发生的概率。 +此处我们使用奇偶校验来举例,通过设置一个校验位使得二进制集合中 1 的个数总是偶数。比如某个扇区的二进制位序列是 01101000,那么就有奇数个 1,所以奇偶位是 1,这个序列加上它后面的奇偶位,就有 011010001;而如果所给的序列是 11101110,那么奇偶位就是 0。所以每一个加上了奇偶位构成的 9 位序列都有偶数奇偶性。 +尽管校验和几乎能正确检测出介质故障或读写故障的存在,但是它却不能帮助我们纠正错误。为了处理这个问题,我们可以在一个或多个磁盘中执行一个被称为稳定存储的策略。通常的思想是,扇区时成对的,每一对代表一个扇区内容 X。我们把代表 X 的扇区对分别称为左拷贝 XL和右拷贝XR。这样实际上就是每个扇区的内容都存储了两份,操作XL失败,那么去操作XR就可以了,更何况我们还在每个扇区中有校验和,把错误的概率就大大降低了。 +到现在为止,我们讨论的都是简单的故障,但是如果发生了磁盘崩溃,其中的数据被永久破坏。而且数据没有备份到另一种介质中,对于银行金融系统这将是巨大的灾难,遇到这种情况我们应该怎么办呢? +数据恢复 +应对磁盘故障最简单的方式就是镜像磁盘,即我们常说的备份。回忆一下写毕业论文时的做法,那时候大部分同学还不会用版本控制器,所以基本采用每天备份一次数据,并且在文件名称中标注日期,以此来达到备份的效果。 +第二种方式是使用奇偶块,比如一个系统中有 3 个磁盘,那么我们再加一个磁盘作为冗余盘。在冗余盘中,第 i 块由所有数据盘的第 i 块奇偶校验位组成。也就是说,所有第 I 块的第 j 位,包括数据盘和冗余盘,在它们中间必须有偶数个 1,冗余盘的作用就是让这个条件为真。 +我们举个简单例子,假设快仅由一个字节组成,我们有三个数据盘和一个冗余盘,对应的位序列如下。其中 盘4 为冗余盘,它的位序列是根据前面三个盘计算出来的。 +盘 1:11110000 +盘 2:10101010 +盘 3:00111000 +盘 4:01100010 + +假设现在某个盘崩溃了,那么我们就能根据上面的序列来恢复数据,只需要让每一列 1 的个数为偶数就可以了,但是这种冗余方式也存在很大的不足。 +第一个缺陷是,如果是两个盘同时崩溃了,那数据也恢复不出来了。第二个问题在于,虽然读数据只需要一次 I/O 操作即可,但是写数据时就不一样了,因为需要根据其他数据盘来计算冗余盘中的位序列,假设共有 n 个盘,其中一个为冗余盘,所以每次写数据时,都需要进行 n+1 次 I/O 操作(读不被写入的 n-1 个盘,被重写数据盘的一次写,冗余盘的一次写),而 I/O操作又是非常耗时的操作,所以这种方法会大大拖慢系统性能。 +另一种方案是没有明显的冗余盘,而是把每个磁盘作为某些块的冗余盘来处理。比如现在有 4 个盘,0 号磁盘将作为编号为 4、8、12 等柱面的冗余,而 1 号磁盘作为编号为 1、5、9 等块的冗余...... +一种更为先进的方式使用海明码来帮助从故障中恢复数据,它在多个磁盘崩溃的情况下也能恢复出数据,也是 RAID 的最高等级,由于本人水平有限,用文字表达不清楚,就不作介绍了,嘿嘿。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/esdygML9J/index.html b/esdygML9J/index.html new file mode 100644 index 00000000..81f241c5 --- /dev/null +++ b/esdygML9J/index.html @@ -0,0 +1,509 @@ + + + + + + + + 为什么计算机处理排序数组比未排序数组快? | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 为什么计算机处理排序数组比未排序数组快? +

+ + +
+ +
+

今天在群里看到一个有意思的问题——为什么处理排序数组比处理没有排序的数组要快,这个问题来源于 StackoverFlow,虽然我看到代码略微知道原因,但是模模糊糊不够清晰,搜了很多博客也讲的不够明白,所以就自己来总结了。

+

首先来看一下问题,下面是很简单的一段代码,随机生成一些数字,对其中大于 128 的元素求和,记录并打印求和所用时间。

+
import java.util.Arrays;
+import java.util.Random;
+
+public class Main
+{
+    public static void main(String[] args)
+    {
+        // Generate data
+        int arraySize = 32768;
+        int data[] = new int[arraySize];
+
+        Random rnd = new Random(0);
+        for (int c = 0; c < arraySize; ++c)
+            data[c] = rnd.nextInt() % 256;
+
+        // !!! With this, the next loop runs faster
+        Arrays.sort(data);
+
+        // Test
+        long start = System.nanoTime();
+        long sum = 0;
+
+        for (int i = 0; i < 100000; ++i)
+        {
+            // Primary loop
+            for (int c = 0; c < arraySize; ++c)
+            {
+                if (data[c] >= 128)
+                    sum += data[c];
+            }
+        }
+
+        System.out.println((System.nanoTime() - start) / 1000000000.0);
+        System.out.println("sum = " + sum);
+    }
+}
+
+

我的运行结果:分别在对数组排序和不排序的前提下测试,在不排序时所用的时间比先排好序所用时间平均要多 10 ms。这不是巧合,而是必然的结果。

+

问题就出在那个if判断上面,在旧文顺序、条件、循环语句的底层解释中其实已经提到了造成这种结果的原因,只是旧文中没有拿出具体的例子来说明。

+

为了把这个问题搞明白,需要先对流水线有一定的了解。计算机是指令流驱动的,执行的是一个一个的指令,而执行一条指令,又要经过取指、译码、执行、访存、写回、更新六个阶段(不同的划分方式所包含的阶段不一样)。

+

六个阶段使用的硬件基本是不一样的,如果一条指令执行完再去执行另一条指令,那么在这段时间里会有很多硬件处于空闲状态,要使计算机的速度变快,那么就不能让硬件停下来,所以有了流水线技术。

+

流水线技术通过将指令重叠来实现几条指令并行处理,下图表示的是三阶段指令时序,即把一个指令分为三个阶段。在第一条指令的 B 阶段,A 阶段相关的硬件是空闲的,于是可以将第二条指令的 A 阶段提前操作。

+
+

很明显,这种设计大幅提高了指令运行的效率,聪明的你可能发现问题了,要是不知道下一条指令是什么怎么办,那提前的阶段也就白干了,那样流水线不就失效了?没错,这就是导致开篇问题的原因。

+

让流水线出问题的情况有三种:

+
    +
  1. 数据相关,后一条指令需要用到前一条指令的运算结果;
  2. +
  3. 控制相关,比如无条件跳转,跳转的地址需要在译码阶段才能知道,所以跳转之后已经被取出的指令流水就需要清空;
  4. +
  5. 结构相关,由于一些指令需要的时钟周期长(比如浮点运算等),长时间占用硬件,导致之后的指令无法进入译码等阶段,即它们在争用同一套硬件。
  6. +
+

代码中的if (data[c] >= 128)翻译成机器语言就是跳转指令,处理器事先并不知道要跳转到哪个分支,那难道就等知道了才开始下一条指令的取指工作吗?处理器选择了假装知道会跳转到哪个分支(不是谦虚,是真的假装知道),如果猜中了是运气好,而没有猜中那就浪费一点时间重新来干。

+

没有排序的数组,元素是随机排列的,每次data[c] >= 128的结果也是随机的,前面的经验就不可参考,所以下一次执行到这里理论上还是会有 50% 的可能会猜错,猜错了肯定就需要花时间来修改犯下的错误,自然就会浪费更多的时间。

+

对于排好序的数组,开始几次也需要靠猜,但是猜着猜着发现有规律啊,每次都是往同一个分支跳转,所以以后基本上每次都能猜中,当遍历到与 128 分界的地方,才会出现猜不中的情况,但是猜几次之后,发现这又有规律啊,每次都是朝着另外一个相同分支走的。

+

虽然都会猜错,但是在排好序的情况下猜错的几率远远小于未排序时的几率,最终呈现的结果就是处理排序数组比未排序数组快,其原因就是流水线发生了大量的控制相关现象,下面通俗一点,加深一下理解。

+
+

远在他方心仪多年的姑娘突然告诉你,其实她也喜欢你,激动的你三天三夜睡不着觉,决定开车前往她的城市,要和她待在一起,但是要去的路上有很多很多岔路,你只能使用的某某地图导航,作为老司机并且怀着立马要见到爱人心情的你,开车超快,什么样罚单都不在乎了。

+

地图定位已经跟不上你的速度了,为了尽快到达,遇到岔路你都是随机选一条路前进,遗憾的是,自己的选择不一定对(我们假设高速可以回退),走错路了就要重新回到分岔点,这就对应着未排序的情况。

+

现在岔路是有规律的,告诉你开始一直朝着一边走,到某个地点后会一直朝着另一边走,你只需要花点时间去探索一下开始朝左边还是右边,到了中间哪个地点会改变方向就可以了,相比之下就能节省不少时间了,尽快见到自己的爱人,这对应着排好序的情况。

+
+

最后的故事改编自两个人的现实生活,一位是自己最好的朋友之一,谈恋爱开心的睡不着觉;另一位是微信上的一位好友,为了对方从北京裸辞飞到了深圳。

+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/f1Aq_KLt0/index.html b/f1Aq_KLt0/index.html new file mode 100644 index 00000000..471f9ef5 --- /dev/null +++ b/f1Aq_KLt0/index.html @@ -0,0 +1,579 @@ + + + + + + + + 财富与幸福指南 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 财富与幸福指南 +

+ + +
+ +
+
+

若没有在段落文字后面做特别标注,那该段落即摘录自The Almanack of Naval Ravikant: A Guide to Wealth and Happiness

+
+

There’s no shortcut to smart.
+聪明没有捷径可走

+

The fundamental delusion: There is something out there that will make me happy and fulfilled forever.
+最基本的错觉: 有些东西会让我永远快乐和满足

+

Hard work is really overrated. How hard you work matters a lot less in the modern economy. What is underrated? Judgment. Judgment is underrated.
+在现代经济中,努力工作的重要性大大降低了。什么被低估了?判断,判断被低估了

+

Spend more time making the big decisions. There are basically three really big decisions you make in your early life: where you live, who you’re with, and what you do.
+花更多的时间做重大决定。在你的早期生活中,基本上有三个真正重大的决定: 你住在哪里,你和谁在一起,你做什么

+

Stay out of things that could cause you to lose all of your capital, all of your savings. Don’t gamble everything on one go. Instead, take rationally optimistic bets with big upsides.
+远离那些可能导致你失去所有资本、所有储蓄的事情。不要一次性赌光所有的东西。取而代之的是,理性乐观地下注,并从中获得巨大的好处

+

Don’t partner with cynics and pessimists. Their beliefs are self-fulfilling.
+不要和愤世嫉俗者和悲观主义者合作,他们的信仰是自我实现的

+

You’re not going to get rich renting out your time. You must own equity—a piece of a business—to gain your financial freedom.
+出租你的时间是不会致富的。你必须拥有股权,一项业务,才能获得财务自由

+

Follow your intellectual curiosity more than whatever is “hot” right now. If your curiosity ever leads you to a place where society eventually wants to go, you’ll get paid extremely well.
+比起现在所谓的「热门」 ,应该更多地追随你的求知欲。如果你的好奇心曾经引导你到一个社会最终想要去的地方,那么你会得到非常好的报酬

+

The less you want something, the less you’re thinking about it, the less you’re obsessing over it, the more you’re going to do it in a natural way.
+你想要的东西越少,你对它的思考就越少,你对它的困扰就越少,你就会越自然地去做它

+

Learn to sell. Learn to build. If you can do both, you will be unstoppable.
+学会销售,学会建设,如果你能同时做到这两点,你将不可阻挡

+

If you secretly despise wealth, it will elude you.
+如果你私下里鄙视财富,它就会躲避你

+

Arm yourself with specific knowledge, accountability, and leverage. Specific knowledge is found by pursuing your genuine curiosity and passion rather than whatever is hot right now. Specific knowledge is knowledge you cannot be trained for.
+用具体的知识、责任感和影响力武装自己。具体的知识是通过追求你真正的好奇心和激情而不是任何现在热门的东西找到的。具体的知识是你不能被训练的知识

+

If they can train you to do it, then eventually they will train a computer to do it.
+如果他们能训练你做一件事,那么最终他们会训练一台电脑来做这件事

+

Apply specific knowledge, with leverage, and eventually you will get what you deserve.
+运用特定的知识,利用杠杆作用,最终你会得到你应得的东西

+

You should be too busy to “do coffee” while still keeping an uncluttered calendar.
+你应该忙得没时间“喝咖啡” ,同时保持日程表整洁

+

There are no get-rich-quick schemes. Those are just someone else getting rich off you.
+没有快速致富的计划,那些只是别人从你身上赚钱而已

+

Code and media are permissionless leverage.
+代码和媒体是未经许可的杠杆

+

People who live far below their means enjoy a freedom that people busy upgrading their lifestyles can’t fathom.
+那些生活水平远远低于自己收入的人们享受着一种自由,这是忙于提升自己的生活方式的人们所无法企及的

+

By the time people realize they have enough money, they’ve lost their time and their health.
+当人们意识到他们有足够的钱时,他们已经失去了时间和健康

+

To have peace of mind, you have to have peace of body first.
+为了拥有内心的平静,你必须首先拥有身体的平静

+

The more secrets you have, the less happy you’re going to be.
+你的秘密越多,你就越不快乐

+

No exceptions—all screen activities linked to less happiness, all non-screen activities linked to more happiness.
+毫无例外,所有的屏幕活动都与较少的快乐有关,所有的非屏幕活动都与较多的快乐有关

+

Inspiration is perishable—act on it immediately.
+灵感是易逝的,所以立即付诸行动

+

To make an original contribution, you have to be irrationally obsessed with something
+为了做出原创性的贡献,你必须非理性地沉迷于某些东西

+

If there’s something you want to do later, do it now. There is no “later.”
+如果你以后有什么想做的事情,现在就应该去做,没有「以后」

+

Courage isn’t charging into a machine gun nest. Courage is not caring what other people think.
+勇气不是冲进机关枪的巢穴,而是不在乎别人的看法

+

Happiness is a choice you make and a skill you develop.
+幸福是你的选择,是你发展的一项技能

+

Your brain is overvaluing the side with the short-term happiness and trying to avoid the one with short-term pain.
+你的大脑高估了短期的幸福,却试图避免短期的痛苦

+

Envy is the enemy of happines.
+嫉妒是幸福的敌人

+

Honesty is a core, core, core value.
+诚信是一个非常,非常,非常核心的价值观

+

where you build a unique character, a unique brand, a unique mindset, which causes luck to find you.
+你要建立一个独特的个性,一个独特的品牌,一个独特的心态,这会让好运气找到你

+

Figure out what you’re good at, and start helping other people with it.
+找出你擅长的东西,然后去帮助他人

+

If you are a trusted, reliable, high-integrity, long-term-thinking dealmaker, when other people want to do deals but don’t know how to do them in a trustworthy manner with strangers, they will literally approach you and give you a cut of the deal just because of the integrity and reputation you’ve built up.
+如果你是一个值得信赖的、可靠的、正直的、有长远眼光的交易者,当其他人想做交易,但不知道如何以值得信赖的方式与陌生人做交易时,他们会真正地接近你,并和你进行交易,仅仅因为你已经建立起的诚信和声誉

+

All benefits in life come from compound interest, whether in money, relationships, love, health, activities, or habits.
+生活中所有的好处都来自于复利,无论是在金钱、人际关系、爱情、健康、活动还是习惯上

+

Productize yourself
+将自己产品化

+

It’s only after you’re bored you have the great ideas. It’s never going to be when you’re stressed, or busy, running around or rushed.
+只有当你感到无聊的时候,你才会有好的想法。当你感到压力,或者忙碌,到处跑或者匆忙的时候,这些都不会发生

+

Play stupid games, win stupid prizes.
+玩愚蠢的游戏,赢得愚蠢的奖品

+

Clear accountability is important. Without accountability, you don’t have incentives. Without accountability, you can’t build credibility. But you take risks. You risk failure. You risk humiliation.
+明确的问责制很重要。没有责任感,你就没有动力。没有责任感,你就无法建立可信度。但是你要冒险,要冒着失败的风险,冒着被羞辱的风险

+

The best jobs are neither decreed nor degreed. They are creative expressions of continuous learners in free markets.
+最好的工作既没有规定也没有程度,它们是自由市场中不断学习者的创造性表现

+

Reading is faster than listening. Doing is faster than watching.
+读比听快,做比看快

+

Explain what you learned to someone else. Teaching forces learning.
+向别人解释你学到了什么。教学是学习的动力

+

Read what you love until you love to read.
+读你喜欢的东西,直到你喜欢阅读

+

Better is the enemy of done.
+完成比完美更重要

+
+

Angela Zhu 摘抄

+
+

If you did not make yourself well understood, it is your problem.
+如果别人没有听懂你在说什么,一定是你的问题

+
+

Angela Zhu 摘抄

+
+

Inspire your people do things, not tell your people do things.
+不断激烈启发你的组员做事,而不是告诉他们做什么

+
+

Angela Zhu 摘抄

+
+

It does not matter what was your motivation, it only matters how this make your direct reports feel.
+你的出发点不重要,重要的是你让别人感觉你想干什么

+
+

Angela Zhu 摘抄

+
+

尽可能帮助和服务别人,建立信任,赢得资本

+
+

Angela Zhu

+
+

To learn something, you should do not reread, summarize and teach it out loud.
+要想学点东西,你不应该重读、总结和大声教出来

+
+

http://www.toolkiit.com/

+
+

在人生最好的年纪,应该少一点算计,多一点洒脱。遇到喜欢的人就去相处,去恋爱,不要辜负自己的时光,然后人生就会给出你更多的可能性

+
+

Fenng

+
+

你心里应该有爱,应该有为了爱去放弃一些东西的勇气。如果你做不到这样,那我觉得你很可悲。或者,会成为一个精致的利己主义者,体会不到人生的幸福,和真正的生活的勇气

+
+

Fenng

+
+

电话、视频会议,线下会议、面对面交流的时候,这是同步事件,需要一定程度上近乎实时反馈。而短信、微信消息、语音消息、留言、邮件,这些都是异步事件,用固定的节奏批量处理就是了

+
+

Fenng

+
+

When you are old and gray, and look back on your life, you will want to be proud of what you have done. The source of that pride won’t be the things you have acquired or the recognition you have received. It will be the lives you have touched and the difference you have made.
+当你白发苍苍、垂垂老矣、回首人生时,你需要为自己做过的事感到自豪。物质生活和满足的占有欲,都不会产生自豪。只有那些受你影响、被你改变过的人和事,才会让你产生自豪感

+
+

Steven Chu 2009 年哈佛大学毕业演讲

+
+

In your collaborations, always remember that “credit” is not a conserved quantity. In a successful collaboration, everybody gets 90 percent of the credit.
+合作中,不要把功劳都归在自己身上,在一场成功的合作中,每个人都应该获得 90% 的荣誉

+
+

Steven Chu 2009 年哈佛大学毕业演讲

+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/f2t0BU6fkn/index.html b/f2t0BU6fkn/index.html new file mode 100644 index 00000000..a9090d28 --- /dev/null +++ b/f2t0BU6fkn/index.html @@ -0,0 +1,480 @@ + + + + + + + + DC-DC | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + DC-DC +
+ + +
+

+ + BUCK 电路基础知识 + +

+ +
+ + + + +
+ +
+ +参考内容: +手撕Buck!Buck公式推导过程 +电力电子的基础应用 +《精通开关电源设计(第二版)》 +Buck电源芯片输出有问题?检查这几样 +原来PWM这么简单! +为什么 DC-DC 芯片设计中都有一个自举电容? +如何克服开关电源中的最小导通时间挑战 + +BUCK 电路构建 +根据高中所学习的物理知识可以很容易的想到,使用一个滑动变阻器即可实现降压和稳压的效果。当负载波动时,通过改变滑动变阻器的阻值,可以调节负载所获得的电压。但是使用滑动变阻器的劣势也很明显,大量的耗能会导致器件温度快速升高。 + + +上面所提到的电路主要缺点在于导通器件(变阻器或三极管)本身存在耗能,那么有没有不会耗能的导通器件呢?首先肯定不能选导线,不然又回到最原始的问题,所有电压都被加到负载上了。有没有能不耗能且能控制加在负载电压的导通器件呢?最常见的机械开关就能做到这个效果。 + +当开关闭合时,负载即获得电压源输出的电压;当开关打开时,负载所获得的电压为 0V。计算平均值可以确定达到了降压的目的,通过控制开关闭合的时间长短,就可以达到调节电压的效果。但仔细想想就会发现不对劲,电路并不会帮助我们计算平均值,负载所获得的电压波形如下图所示,是完美的方波,并不是一条直线。 + +控制开关闭合的时间,即后文要讲的控制占空比 + + +此时很容易就能想到利用电容两端电压不能突变的特点,给负载并联一个电容即可,电容即保证负载可以获得连续的能量流。 + +一旦引入了电容,就需要考虑浪涌电流的问题。根据公式 Q=CV=ItQ=CV=ItQ=CV=It 可得 I=CVtI=\frac{CV}{t}I=tCV​,开关闭合时电压在非常短的时间内升高,所以电流会突然变得很大。 +我们当然可以简单的利用电阻来抑制浪涌电流,但不幸的是电阻总要消耗功率。为了最大限度的提高效率,可以考虑使用电感,电感本身不消耗任何能量,只会进行储能,且其无损限流的能力正好可以用来抑制电容的浪涌电流。 + +引入电感后可以发现当开关打开时,电感没有续流回路,因此需要想办法构造电感的续流回路。续流回路需要保证不论开关打开还是闭合,电流都流向负载,且开关闭合时电源正极与负极回路必须经过电感与负载。这个需求很符合二极管的特点,即只允许单向导通。 + +到目前为止我们构建了非同步 BUCK 电路,考虑到机械开关容易磨损、使用寿命短、有机械惯性(转换频率低)的问题,我们需要将机械开关换成转换频率高的半导体器件,此处我们选择 NMOS 管来替代开关。 + +选择 NMOS 和 PMOS 的主要区别在于驱动电路的设计 + + +可以发现当 NMOS 开关管导通时,续流二极管处于截止状态;当开关管关断时,续流二极管处于导通状态。即二极管的导通和截止和开关管的截止导通是同步的,也就是说二极管起到的是一个开关的作用。而且考虑到电流从二极管流过期间,二极管两端的压降恒定为导通电压 0.7V,二极管所消耗的能量较大。因此我们也可以把二极管换为导通电阻更小的 NMOS 管。 + +为了提高 BUCK 电路的稳定性,防止由于输入纹波带来异常,我们在 BUCK 电路的输入端并联一个电容,用于滤除输入电压的纹波。 + +至此我们就搭建了一个标准的同步 BUCK 电路,我们将其简单变个样子,再加几个标签即可得到下图。在同步 BUCK 电路中需要两个开关管密切配合,以防止整个线路导通,所以它们之间需要保持一定的相位关系,即上管导通下管截止;上管截止下管导通,我们把这种关系称之为同步。 + +信号角度理解 LC +我们以占空比为 0.5 来进行说明,将时域下的方波转换到频域,通过傅立叶变换可以分解出一系列的频率分量。其中包含频率为 0 的分量,即直流分量,也就是我们想要保留的部分,还有频率为 n 倍 fsf_{s}fs​ 的分量。那么如何把我们不想要的部分去掉呢?从滤波角度考虑就需要加入一个低通滤波器。 + +通过加入低通滤波器可以把高频分量滤除,把二阶低通滤波器的截止频率设置在 0 到 fsf_{s}fs​ 之间,即可把 fsf_{s}fs​ 所有以上的部分给滤除。整体达到的效果即通过一个 LC 低通滤波器,配合一个开关网络,将一个数字化的电平重新滤出,得到一个比较平缓的电压输出,这个过程即完成了电压从高到低的转换。其中直流分量的大小受占空比 D 控制,所以通过改变占空比 D 即可改变输出电压大小。 + +稳态分析 +我们需要先强调一下前提,此处我们说的稳态分析,即输入电压输出电压都是稳定,且纹波足够小的状态。下文的所有计算都将基于稳态进行分析,并且是在 (F)CCM(连续导通模式)下计算的。 +我们将一些已知条件列出来: + +输入电压:ViV_{i}Vi​ +输出电压:VoV_{o}Vo​ +负载电阻:RRR +输出电感:LLL +开关频率:fff + +伏秒平衡 +当上管导通下管截止时,电感右边的电压为 ViV_{i}Vi​,左边的电压为 VoV_{o}Vo​,因为同步 BUCK 电路是降压电路,所以 Vi&gt;VoV_{i}&gt;V_{o}Vi​&gt;Vo​,所以电感两端电压即为 Vi−VoV_{i}-V_{o}Vi​−Vo​,也就是说是一个恒定值。由于有 Ldidt=Vi−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =V_{i}-V_{o}Ldtdi​=Vi​−Vo​,所以 didt=Vi−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{V_{i}-V_{o}}{L}dtdi​=LVi​−Vo​​,即电感电流的上升斜率,由于是稳态前提,所以可以确定该值是一个常数。 +当上管截止下管导通时,电感右边电压为 VoV_{o}Vo​,左边电压为 000,所以电感两端电压为 0−Vo0-V_{o}0−Vo​,即 −Vo-V_{o}−Vo​。由于 Ldidt=−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-V_{o}Ldtdi​=−Vo​,所以 didt=−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-\frac{V_{o}}{L}dtdi​=−LVo​​,即电感电流的下降斜率,也是一个常数。 +整个电路处于稳定状态,负载电路恒定,那么在一个周期内,电感电流增加的量肯定等于电感电流减小的量,即充了多少电就要放多少电,不然负载的电流或电压将会发生变化。 +前文已有didt=UL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{{U}}{L}dtdi​=LU​,而 LLL 恒定,那么电感电流的变化速度即与电压成正比关系,即电感电流上升(下降)的斜率与电压成正比关系。而电感电流上升和下降的高度相同,那么上升时间和下降时间就自然构成反比关系。 +TonToff=VoVi−Vo\frac{T_{on} }{T_{off} } = \frac{V_{o}}{V_{i}-V_{o}}Toff​Ton​​=Vi​−Vo​Vo​​,将其进行简单变换即可得到闻名江湖的伏秒平衡法则。 +Ton(Vi−Vo)=ToffVoT_{on}(V_{i}-V_{o}) = T_{off}V_{o}Ton​(Vi​−Vo​)=Toff​Vo​ +占空比 +已知 T=Ton+Toff=1fT=T_{on}+T_{off}=\frac{1}{f}T=Ton​+Toff​=f1​,结合伏秒平衡法则可以计算出: +开通时间:Ton=VoVi∙1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f}Ton​=Vi​Vo​​∙f1​ +关断时间:Toff=Vi−VoVi∙1fT_{off} = \frac{V_{i}-V_{o} }{V_{i} } \bullet \frac{1}{f}Toff​=Vi​Vi​−Vo​​∙f1​ +占空比:D=TonT=VoViD = \frac{T_{on} }{T}=\frac{V_{o} }{V_{i}}D=TTon​​=Vi​Vo​​ +纹波电流 +由于输出电压不变,也就是说输出电容两端的电压没有变化,即输出电容的平均电流为 0。根据输出节点的基尔霍夫电流定律可知,输出节点电流和为 0,那么功率电感的平均电流就等于负载的平均电流,即IL=Io=VoRI_{L} = I_{o} = \frac{V_{o} }{R}IL​=Io​=RVo​​。 + +上文计算电感电流斜率时已经能确定电流波形是个三角波,纹波电流等于在开关导通时电感电流的增大值,也等于在关断时电感电流减小的值,计算任意一个即可得到纹波电流。我们以上管导通时增大的电感电流计算。 + +上管导通时电感两端电压为 Vi−VoV_{i}-V_{o}Vi​−Vo​,导通时间为 Ton=VoVi∙1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f}Ton​=Vi​Vo​​∙f1​,根据 U=LdidtU=L\frac{\mathrm{d} i}{\mathrm{d} t}U=Ldtdi​ 可知: +△IL=di\triangle I_{L} =di△IL​=di +=Ton∙UL=T_{on}\bullet \frac{U}{L}=Ton​∙LU​ +=Vi−VoL∙VoVi∙1f=\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}=LVi​−Vo​​∙Vi​Vo​​∙f1​ +根据理论计算可以发现,电感电流的纹波和负载电流的大小没有关系,但是负载电流与平均电感电流是相等关系。 +功率电感选择 +根据上文的信息进一步可以计算出电感的峰值电流: +ILP=Io+△IL2I_{LP} =I_{o}+\frac{\triangle I_{L}}{2}ILP​=Io​+2△IL​​ +=Io+Vi−Vo2L∙VoVi∙1f=I_{o}+\frac{V_{i}-V_{o}}{2L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}=Io​+2LVi​−Vo​​∙Vi​Vo​​∙f1​ +那么在选择功率电感时,电感的饱和电流就必须要大于这个ILPI_{LP}ILP​,并且需要留有一定的裕量。实际应用时电感的纹波电流应是平均电流的 30%30\%30% 至 50%50\%50% 为宜,我们将这个参数称之为电流纹波率 r。根据电流纹波率范围就可以计算出电感值的范围: +△IL=Vi−VoL∙VoVi∙1f\triangle I_{L} =\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}△IL​=LVi​−Vo​​∙Vi​Vo​​∙f1​ +L=Vi−Vo△IL∙VoVi∙1fL =\frac{V_{i}-V_{o}}{\triangle I_{L}}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}L=△IL​Vi​−Vo​​∙Vi​Vo​​∙f1​ +=Vi−Vo(0.3至0.5)Io∙VoVi∙1f=\frac{V_{i}-V_{o}}{(0.3至0.5) I_{o}}\bullet \frac{V_{o}} {V_{i}}\bullet \frac{1}{f}=(0.3至0.5)Io​Vi​−Vo​​∙Vi​Vo​​∙f1​ +=(Vi−Vo)Vo(0.3至0.5)IoVif=\frac{(V_{i}-V_{o})V_{o}}{(0.3至0.5) I_{o} V_{i} f}=(0.3至0.5)Io​Vi​f(Vi​−Vo​)Vo​​ +为何 r 为 0.3~0.5 +电流纹波率即是电感电流的交流分量与其相对应的直流分量的比值,一旦 r 确定,那么输入输出滤波电容的电流、开关管的有效电流等都确定了,因此 r 的选择会影响器件选择和芯片的成本。使用公式可以表述为: +r=△IIL=2×IACIDCr=\frac{\triangle I}{I_{L}}=2\times \frac{I_{AC}}{I_{DC}}r=IL​△I​=2×IDC​IAC​​ +一般认为,电感体积与其能量处理能量成正比,因为要处理更高的能量就需要更大的磁芯。选择电感磁芯的能量处理能力至少要等于其需存储量,即 E=12×L×Ipk2E=\frac{1}{2} \times L \times I_{pk}^{2}E=21​×L×Ipk2​,下图是 E 与 r 的的函数曲线,可以发现在 r=0.4r=0.4r=0.4 附近有一个拐点。 + +选择的 r 如果较 0.4 低很多,则所需要的电感体积越大;而若继续增大 r,则电感的体积并不会减少多少,即当 r 超过 0.4 后,通过增加 r 来减少电感体积的效果已经不明显了。 +输入纹波 +电源输入功率为 Pi=ViIiP_{i}=V_{i}I_{i}Pi​=Vi​Ii​,负载功率为 Pr=VoIoP_{r}=V_{o}I_{o}Pr​=Vo​Io​,不考虑开关损耗、导通损耗等等因素,那么输入功率和输出功率相等,可得输入平均电流为 Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}Ii​=Vi​Vo​Io​​。 +输入电压纹波就是输入电容上面电压的变化,这个变化可以分为两部分。一部分为电容充放电所导致的电压变化 UqU_{q}Uq​,另一部分为电流流过电容 ESRESRESR 导致的压降 UesrU_{esr}Uesr​。即 △Vi=Uq+User\triangle V_{i} = U_{q} + U_{ser}△Vi​=Uq​+User​。 +∵Q=CiUq=Iit=IiToff\because Q = C_{i}U_{q} = I_{i}t = I_{i}T_{off}∵Q=Ci​Uq​=Ii​t=Ii​Toff​ +∴Uq=IiToffCi\therefore U_{q} = \frac{I_{i}T_{off}}{C_{i}}∴Uq​=Ci​Ii​Toff​​ +∵Toff=Vi−VoVif\because T_{off} = \frac{V_{i}-V_{o} }{V_{i}f }∵Toff​=Vi​fVi​−Vo​​ +且 Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}Ii​=Vi​Vo​Io​​ +∴Uq=VoIoCiVif∙Vi−VoVi\therefore U_{q} = \frac{V_{o}I_{o}}{C_{i}V_{i}f}\bullet \frac{V_{i}-V_{o}}{V_{i}}∴Uq​=Ci​Vi​fVo​Io​​∙Vi​Vi​−Vo​​ +要想知道 ESRESRESR 所造成的纹波,只需要知道流过输入电容的电流即可。当上管断开时,电源输入电流 IiI_{i}Ii​ 全部流入电容 CiC_{i}Ci​。电感电流原本从下管的体二极管续流,当上管导通后,变为了从上管续流。因为此前电感一直处于放电状态,所以切换的那一刻电感电流是最小的,为 IL−△IL2I_{L}-\frac{\triangle I_{L}}{2}IL​−2△IL​​。 +在整个 TonT_{on}Ton​ 时间内,电感都被充电,电感电流一直都在增大,直到 IL+△IL2I_{L}+\frac{\triangle I_{L}}{2}IL​+2△IL​​,并且在 TonT_{on}Ton​ 时间内,电感电流都是走的上 MOS 管通路,所以上 MOS 管最大电流也是 IL+△IL2I_{L}+\frac{\triangle I_{L}}{2}IL​+2△IL​​。 +根据基尔霍夫电流定律可知,输入节点的电流和为 0,那么输入电源电流 IiI_{i}Ii​ 和电容 CiC_{i}Ci​ 的放电电流就等于通过上 MOS 管的电流。所以 CiC_{i}Ci​ 的最大放电电流即为 IL+△IL2−IiI_{L}+\frac{\triangle I_{L}}{2} - I_{i}IL​+2△IL​​−Ii​。我们约定充电为正,放电为负,则放电电流为 Ii−△IL2−ILI_{i} - \frac{\triangle I_{L}}{2} - I_{L}Ii​−2△IL​​−IL​。 + + +上管截止时 ESRESRESR 的压降为 Ii∙ESRI_{i} \bullet ESRIi​∙ESR,上管导通时压降为 (Ii−△IL2−IL)∙ESR(I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESR(Ii​−2△IL​​−IL​)∙ESR,则可得: +User=Ii∙ESR+(Ii−△IL2−IL)∙ESRU_{ser} = I_{i} \bullet ESR + (I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESRUser​=Ii​∙ESR+(Ii​−2△IL​​−IL​)∙ESR +=(IL+△IL2)∙ESR=(I_{L} + \frac{\triangle I_{L}}{2}) \bullet ESR=(IL​+2△IL​​)∙ESR +∵△IL==Vi−VoL∙VoVi∙1f\because \triangle I_{L} ==\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}∵△IL​==LVi​−Vo​​∙Vi​Vo​​∙f1​ +且 IL=IoI_{L} = I_{o}IL​=Io​ +∴User=(Io+(Vi−Vo)Vo2ViLf)∙ESR\therefore U_{ser} = \left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR∴User​=(Io​+2Vi​Lf(Vi​−Vo​)Vo​​)∙ESR +综上所述可得: +△Vi=Uq+Uesr\triangle V_{i} = U_{q} + U_{esr}△Vi​=Uq​+Uesr​ +=VoIoCiVif∙Vi−VoVi+(Io+(Vi−Vo)Vo2ViLf)∙ESR=\frac{V_{o}I_{o}}{C_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}} +\left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR=Ci​Vi​fVo​Io​​∙Vi​Vi​−Vo​​+(Io​+2Vi​Lf(Vi​−Vo​)Vo​​)∙ESR +输入电容选择 +考虑到电容的实际使用情况,陶瓷电容的 ESRESRESR 小,容量小,所以 UqU_{q}Uq​ 对纹波起决定性作用,输入纹波可近似为 UqU_{q}Uq​。若选择铝电解电容,则 ESRESRESR 大,容量大,UesrU_{esr}Uesr​ 对纹波起到决定性作用,输入纹波可以近似为 UesrU_{esr}Uesr​,假设电路设计要求输入纹波不能大于 △Vi\triangle V_{i}△Vi​,则有: +陶瓷电容:Ci≥VoIo△ViVif∙Vi−VoViC_{i} \ge \frac{V_{o}I_{o}}{\triangle V_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}}Ci​≥△Vi​Vi​fVo​Io​​∙Vi​Vi​−Vo​​ +铝电解电容:ESR≤△ViIo+(Vi−Vo)Vo2fLViESR \le \frac{\triangle V_{i}}{I_{o} + \frac{(V_{i}-V_{o})V_{o}}{2fLV_{i}} }ESR≤Io​+2fLVi​(Vi​−Vo​)Vo​​△Vi​​ +输出纹波 +输出纹波与输入纹波同理,亦是 △Vo=Uq+Uesr\triangle V_{o} = U_{q} + U_{esr}△Vo​=Uq​+Uesr​,我们画出负载、功率电感、输出电容三者的电流波形。其中电感的纹波电流是 △IL\triangle I_{L}△IL​,则电容的纹波电流也是 △IL\triangle I_{L}△IL​,又因为电容的平均电流为 0,所以充电电流和放电电流都是 △IL2\frac{\triangle I_{L}}{2}2△IL​​。 +电容充放电的总电荷量 Q 等于电流乘以时间,即图中阴影三角形的面积,三角形底部时间为 T2\frac{T}{2}2T​,高为 △IL2\frac{\triangle I_{L}}{2}2△IL​​,所以总的放电量可以计算出来为 Q=12∙T2∙△IL2Q=\frac{1}{2} \bullet \frac{T}{2} \bullet \frac{\triangle I_{L}}{2}Q=21​∙2T​∙2△IL​​ + +结合 Q=CoUqQ=C_{o}U_{q}Q=Co​Uq​ 可得: +Uq=(Vi−Vo)Vo8ViCoLf2U_{q} = \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} }Uq​=8Vi​Co​Lf2(Vi​−Vo​)Vo​​ +由前面电流波形可知,电容的充电电流最大是 △IL2\frac{\triangle I_{L}}{2}2△IL​​,放电电流最大是 −△IL2-\frac{\triangle I_{L}}{2}−2△IL​​,则可以得到 ESRESRESR 引起的总压降为: +User=△IL2∙ESR−(−△IL2∙ESR)U_{ser} = \frac{\triangle I_{L}}{2} \bullet ESR - (-\frac{\triangle I_{L}}{2} \bullet ESR)User​=2△IL​​∙ESR−(−2△IL​​∙ESR) +∵△IL=Vi−VoL∙VoVi∙1f\because \triangle I_{L} = \frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}∵△IL​=LVi​−Vo​​∙Vi​Vo​​∙f1​ +∴(Vi−Vo)VoViLf∙ESR\therefore \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR∴Vi​Lf(Vi​−Vo​)Vo​​∙ESR +最终可得: +△Uo=Uq+Uesr\bigtriangleup U_{o} = U_{q} + U_{esr}△Uo​=Uq​+Uesr​ +=(Vi−Vo)Vo8ViCoLf2+(Vi−Vo)VoViLf∙ESR=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} } + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR=8Vi​Co​Lf2(Vi​−Vo​)Vo​​+Vi​Lf(Vi​−Vo​)Vo​​∙ESR +=(Vi−Vo)VoViLf∙(ESR+18fCo)=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf}\bullet \left ( ESR + \frac{1}{8fC_{o}} \right )=Vi​Lf(Vi​−Vo​)Vo​​∙(ESR+8fCo​1​) +输出电容选择 +与输入电容选择的方式一致,考虑是容值还是 ESRESRESR 占主导地位,假设要求输出纹波要小于 △Vo\triangle V_{o}△Vo​,则有: +陶瓷电容:Co≥(Vi−Vo)Vo8Vi△VoLf2C_{o} \ge \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}\triangle V_{o}Lf^{2} }Co​≥8Vi​△Vo​Lf2(Vi​−Vo​)Vo​​ +铝电解电容:ESR≤△VoViLf(Vi−Vo)VoESR \le \frac{\triangle V_{o}V_{i}Lf}{\left ( V_{i}-V_{o} \right ) V_{o}}ESR≤(Vi​−Vo​)Vo​△Vo​Vi​Lf​ +电感续流模式 +电感电流曲线不能断续(无突降),因为电流断续会引起实际不可能发生的能量断续现象。但是电流的变化率可以突变,比如从上升斜率(电感储能增加)变为下降斜率(电感储能释放),尽管这样电感电流也必须连续。根据稳定状态下每个周期电流是否回到零,划分为不同的导通模式,并且通过减小负载电流,可以使电路从 CCM 经过 BCM 最终转变为 DCM。 +CCM(连续导通模式) +稳定状态下每个周期,电流都回到某一非零值,称之为连续导通模式(CCM:Continuous Conduction Mode)。CCM 是功率变换中最常见的工作模式,有输出纹波小但功耗高的特点。 +FCCM(强制连续导通模式)只存在于同步 BUCK 中,由于使用 MOS 管将非同步拓扑的二极管取代,MOS 管的导通压降远低于二极管压降,除了显著减小了续流通路的导通损耗外,也允许电感电流反向,即从负载瞬时流出电流。 + +DCM(断续导通模式) +若稳定状态下每个周期中电流都会回到零,那么就称之为断续导通模式(DCM:Discontinuous Conduction Mode),DCM 由于其电感电流的不连续,计算平均电感电流就需要更加详细复杂的公式,这也是 DCM 方程看上去复杂的根本原因。 + +BCM(临界导通模式) +BCM 是临界导通模式(Boundary Conduction Mode),由控制器监控电感电流,一旦检测到电流等于 0,功率开关立即闭合,控制器总是等电感电流「复位」来激活开关。即 BCM 处于 CCM 和 DCM 之间,可以将其视为 CCM 和 DCM 的极端情况,所以 BCM 模式下可以自由的选择 CCM 或 BCM 方程。 + +DC-DC 功能框图 +前文所构建的 BUCK 电路只能是纸上谈兵,还需要解决诸多问题才能应用于实际电路。 +基础驱动与控制 +首先需要解决的问题就是 MOS 管不可能平白无故就打开,所以我们需要添加 MOS 管驱动器。 + +理想情况是上管关闭,下管立刻打开,中间没有任何时间差,但是 MOS 管并非理想开关,从关断到导通存在一个过渡的过程,若同时导通则电源通过上下 MOS 管直接对地短路,很容易就会导致 MOS 损坏,甚至可能会把前一级电源也损坏,所以上下管同时导通的状态必须得避免。 +为了避免上下管直通的情况,实际应用会故意让上管和下管切换时多等一会儿,宁愿出现同时关断的情况,也不能出现同时导通的状态,这个等待的过程就叫做死区时间。 + +需要注意的是,在死区时间内虽然下管没有被导通,但是功率 MOS 管本身存在一个寄生二极管,这个寄生二极管可以像非同步 BUCK 那样帮助电感续流,而且这个时间非常的短暂,所以产生的功耗没有那么大,因此不必担心系统会出问题。 +到目前为止,不知道您有没有发现我们都在自嗨,系统中并没有用来控制上下 MOS 导通和关断的信号。因此需要增加一个振荡器用来产生控制信号,注意我们在前文中使用的是占空比一词,也就是说我们要使用的是 PWM(脉冲宽度调制)。当然你也可以使用 PFM(脉冲频率调制),本文只介绍 PWM 方式。 + +PWM(脉冲宽度调制) +PWM 的全称是脉冲宽度调制(Pulse-width modulation),是通过将有效的电信号分散成离散形式,从而来降低电信号所传递的平均功率的一种方式。其基本实现原理是通过锯齿波/三角波(载波)与所需要合成的波形(调制波)进行比较,然后确定 PWM 所需要输出的极性。因为一般都是用到开关器件上,通常是 ON 或者 OFF,具体如下图所示。 + +将振荡器输出的锯齿波和参考值 VthV_{th}Vth​ 进行比较,就可以输出 PWM 波形了。话不多说,上图就明白了。 + +上图中的锯齿波(橙色)最大为 10,但是我们希望输出平均为 5 的波形(图中紫色的水平线),那么通过比较器进行比较,当锯齿波小于 5 时,PWM 即输出低电平 OFF,当锯齿波大于 5 时,PWM 即输出高电平 ON,此时的占空比即为 50%。 +若是想输出一个电压逐渐抬高的波形,即占空比逐渐增大,那只需要将调制的波形设置为斜坡输出即可达到效果。比如下图中可以看到,占空比从 0% 逐渐增大到 100%。 + +同样的道理,我们可以通过改变调制波形,进一步调制出来其它的波形,比如要调制一个正弦波(sin wave),也就是我们常说的 SPWM,那么就是下面的样子。 + +负反馈环路 +有了调制信号,开关管也可以正常打开与关闭,看起来可以应用到实际电路中了,但是别忘了负载的电阻并不是恒定的,负载的变化必然会引起输出电压的波动。为了减小输出电压的波动,我们可以在输出端添加分压电阻,与误差放大器和基准电压一起构成负反馈回路,这种通过取样输出电压进行闭环反馈的方式称之为电压模式控制。 + +误差放大器的输入端分别为带隙基准源输出电压采样,当输出电压减小/增大时,与基准电压的细微差异都会被误差放大器放大,今儿调节脉冲宽度来达到调节调整输出电压的目的。图中 R2 接地,所以可以很容易计算出输出电压与分压电阻的关系:Vout=Vref(R1+R2)R2V_{out} = \frac{V_{ref}(R_{1}+R_{2})}{R_{2}}Vout​=R2​Vref​(R1​+R2​)​。 +除了输出电压可以用作控制取样信号,还有输入电压、输出电流、输出电感电压、开关器件峰值电流可以作为控制取样信号。使用这些信号可以构成单环、双环或多环反馈系统,进而实现稳压、稳流以及恒定功率的目的,也可以实现过流、过压、均流等功能。 +现在回过头来评判一下电压模式控制的优缺点。单一的反馈电压闭环设计使得调试更加容易、对输出负载的变化有比较好的响应调节、占空比的调节也不会受到什么限制等等都是它的优点,但是其缺点也很明显。由于主电路有较大的输出电容和电感的相移延时作用,输出电压的变小/变大也延时滞后,再经过误差放大器的延时,使得瞬态响应变得更慢。由于电压控制模式不采样电流,逐周期限流保护功能必须另外增加电路来实现。 +峰值电流模式控制在电压模式控制的基础上又增加了电流环,所以峰值电流模式控制是一个双环反馈系统。误差电压信号与一个变化的,其峰值代表输出电感电流峰值的三角波形进行比较,然后得到 PWM 脉冲的关断时刻。所以峰值电流模式控制不是使用电压误差信息直接控制 PWM 脉冲宽度,而是直接控制峰值输出侧的电感电流大小,进而间接的控制 PWM 脉冲宽度。 + +峰值电流在逻辑上与平均电感电流大小变化一致,但是峰值电感电流的大小并不能与平均电感电流的大小一一对应。在占空比不同的情况下,相同的峰值电感电流大小可以对应不同的平均电感电流大小,但平均电感电流大小才是唯一决定输出电压大小的因素。 +为了解决不同占空比对平均电感电流大小的扰动作用,使得所控制的峰值电感电流最后收敛于平均电感电流,需要将电感电流下斜坡斜率的至少一半以上斜率加在实际检测电流的上斜坡上,这一点可以从数学上进行证明(具体咋证明暂不讨论)。 +总结一下峰值电流模式控制 PWM 是双闭环控制系统,电压外环控制电流内环。电流内环是瞬时快速按照逐个脉冲工作的。功率级石油电流内环控制的电流源,而电压外环再控制次功率级电流源。电流内环只负责输出电感的动态变化,电压外环仅需控制输出电容,所以峰值电流模式控制 PWM 具有比电压模式控制大得多的带宽。 +为了防止在应用过程中可能出现的短路等异常场景,DC-DC 少不了过温保护、过流保护、过压保护等保护手段。再设定一定的辅助功能,比如 PG 状态显示、缓启动、欠压保护等即可搭建完整的 DC-DC 电路。 + +异常模式 +参考上文中的电路图,我们把绿色部分称之为控制电路,灰色部分是功率电路,功率电路中最核心的就是上下两个 MOS 管,下文我们讨论不同的异常场景中,控制电路、上管、下管三部分应该处于什么状态,其中控制电路关闭相当于整个芯片重启。 +过压保护 +当输出电压偏高并且达到了过压保护的阈值。过压状态需要控制电路去调整把输出电压降下来,所以不需要重启整个芯片。可以想到输出端已经处于过压状态了,上管如果打开那会加重过压的程度,因此上管需要关闭。若下管打开,则电感、负载、下管形成回路,即电感有续流回路,会把过压状态维持的时间更长,因此下管也需要关闭。综上有:过压保护:关上管、关下管。 +过温保护 +温度过高的情况无非两种,一种是流过芯片的电流太大,即功率太大导致芯片自身发热达到了过温保护的阈值,此时关闭芯片肯定可以解决,另外切断电流回路也是可以解决的,即关闭上管。过温的第二种情况是由于环境温度过高而导致芯片温度过高,此时最好还是关闭芯片吧。综上有:过温保护:关闭芯片。 + +关闭芯片指关闭芯片中的 BUCK 部分,但是基准源部分仍然保持工作 + +过流保护 +过流保护还需要区分是正向过流还是负向过流,因为工作在 FCCM 模式的 DC-DC 在轻载或空载时,可能会有负向过流的情况。存在负向过流的另一原因也是因为同步 BUCK 没有像非同步 BUCK 那样的整流二极管,所以当存在负向过流情况时,直接模拟非同步 BUCK 中的二极管即可。综上有:负向过流保护:关下管。 +若发生正向过流时如何进行保护呢?首先考虑到电流经上管到负载,既然已经过流了那么肯定需要关上管。为了使电流减小的更快,那么就需要将电流流向地,所以需要将下管打开以构成回路。综上有:正向过流保护:关上管、开下管。 +异常排查 +不管系统设计的多好,在实际应用中都可能会或多或少出现问题,比如电感选用不合适、触发 min-on time、触发 min-off time、输出电容 ESR 过大等,下面我们逐一进行讨论。 +min-on time +虽然 MOS 管打开速度很快,但是打开始终是一个过程,要完成一个过程就必须需要一定的时间,当高频且压差大的情况下很容易触发完成「打开」这个过程的最小时间。也就是说占空比已经是实际最小了,占空比无法再降低了,所以查看输出电压纹波可能会出现下面的波形。 + +出现该波形的原因在于,占空比已经无法继续降低,所以电压整体处于逐渐抬高的趋势,当抬高到一定程度时即触发过压保护,上下管都关断,所以电压快速下降。 +min-off time +与 min-on time 相对应的是 min-off time,当开关频率足够高且输入和输出电压接近时即容易出现此问题,此时即达到系统所能达到的最大占空比也无法满足负载所需要的电压,表现为输出电压无法达到设定值,负反馈分压电阻电压也低于电压基准值。 +电感饱和电流过小 +电感电流正常是一个三角波,但是如果电感饱和电流过小,则会电感电流将会变成下图很苗条的样子。因为电感电流饱和所以电流不再线性增加,电流快速增大导致磁通率减小,会导致磁性损耗增大、芯片热耗增大,而且这是一个正反馈过程,整个系统的可靠性会大大降低。 + +输出电容选用不合适 +当输出电容选用过小时,会导致动态响应输出出现抖动。若输出电容的等效串联电阻(ESR)过大,也会导致输出纹波异常增大,这一点从前文的理论计算即可验证。因此在实际使用过程中需要同时考虑电容容值和所选电容的 ESR。 +为什么需要 min-on time +占空比 D 控制相对于输入电压的输出电压,虽然通过提高开关频率有助于减小电感尺寸,但是也必须满足最小导通时间(min-on time)才能使芯片正常工作。那么这个 min-on time 是由哪些因素引起的呢? +因为上管中电流波形前沿的电流尖峰。由于 MOS 管也是由 PN 结组成,存在 PN 结就肯定存在结电容,MOS 管的寄生电容 CgsC_{gs}Cgs​ 和 CgdC_{gd}Cgd​ 会导致上管在导通时电流突然变化,也就是说会出现电流尖峰。如果在这个电流尖峰的时间段内去检测电流的话,很可能就会触发过流保护,因此开关电路的最小导通时间必须大于电流尖峰出现的时间,这个时间我们称之为消隐时间。 + +另一个原因是因为上下管开关完成后,由于键合线存在寄生电感的原因会产生很大的振铃,这个振铃同样可能会导致峰值电流检测出错,需要一个 min-on time 将这个振铃隔离过去。 +为什么需要 min-off time +如下图所示,最简单的需要最小关断时间(min-off time)的原因是,若下管不打开则没有办法给自举电容充电,所以需要在该时间内给自举电容充电,为下一个开关周期做准备。 + +另一个原因是因为没有最小关断时间,即占空比 D 增大到 100%,那么就无法对负向电流、谷值电流进行采样,也就无法实现实现相应的异常保护功能。与 min-on time 一致,电流检测也需要一个 min-off time 隔离振铃。 +为什么需要自举电容 +DC-DC 的上 MOS 管可以是 PMOS,也可以是 NMOS。但是一般因为生产工艺问题,PMOS 导通电流往往做不不到很大,而在相同成本下 NMOS 的导通电流可以做到更大,也就是 RdsonR_{dson}Rdson​ 可以做到相对较低,所以往往更倾向于 NMOS。 +将上管换为 NMOS 后也带来了新的问题,如何打开 NMOS ?如图所示,上管的 S 极连接 PH 点,该点的电压为 +5V,要打开 NMOS 需要 VGS&gt;0V_{GS} &gt; 0VGS​&gt;0,驱动 MOS 管打开的压降需要 5V,那么驱动电压就需要 +10V 才可以打开上管,但是纵观整个电路并没有能达到 +10V 级别的电压,所以需要自举电容来进行升压才能打开上 MOS 管。 + +所以 DC-DC 芯片是否需要自举电容是由芯片所选用的 MOS 管类型决定的,若是 PMOS 则无需自举电容。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 00000000..b4b65e2f Binary files /dev/null and b/favicon.ico differ diff --git a/g-HWNOeTH/index.html b/g-HWNOeTH/index.html new file mode 100644 index 00000000..dc19272a --- /dev/null +++ b/g-HWNOeTH/index.html @@ -0,0 +1,480 @@ + + + + + + + + BUCK | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + BUCK +
+ + +
+

+ + BUCK 电路基础知识 + +

+ +
+ + + + +
+ +
+ +参考内容: +手撕Buck!Buck公式推导过程 +电力电子的基础应用 +《精通开关电源设计(第二版)》 +Buck电源芯片输出有问题?检查这几样 +原来PWM这么简单! +为什么 DC-DC 芯片设计中都有一个自举电容? +如何克服开关电源中的最小导通时间挑战 + +BUCK 电路构建 +根据高中所学习的物理知识可以很容易的想到,使用一个滑动变阻器即可实现降压和稳压的效果。当负载波动时,通过改变滑动变阻器的阻值,可以调节负载所获得的电压。但是使用滑动变阻器的劣势也很明显,大量的耗能会导致器件温度快速升高。 + + +上面所提到的电路主要缺点在于导通器件(变阻器或三极管)本身存在耗能,那么有没有不会耗能的导通器件呢?首先肯定不能选导线,不然又回到最原始的问题,所有电压都被加到负载上了。有没有能不耗能且能控制加在负载电压的导通器件呢?最常见的机械开关就能做到这个效果。 + +当开关闭合时,负载即获得电压源输出的电压;当开关打开时,负载所获得的电压为 0V。计算平均值可以确定达到了降压的目的,通过控制开关闭合的时间长短,就可以达到调节电压的效果。但仔细想想就会发现不对劲,电路并不会帮助我们计算平均值,负载所获得的电压波形如下图所示,是完美的方波,并不是一条直线。 + +控制开关闭合的时间,即后文要讲的控制占空比 + + +此时很容易就能想到利用电容两端电压不能突变的特点,给负载并联一个电容即可,电容即保证负载可以获得连续的能量流。 + +一旦引入了电容,就需要考虑浪涌电流的问题。根据公式 Q=CV=ItQ=CV=ItQ=CV=It 可得 I=CVtI=\frac{CV}{t}I=tCV​,开关闭合时电压在非常短的时间内升高,所以电流会突然变得很大。 +我们当然可以简单的利用电阻来抑制浪涌电流,但不幸的是电阻总要消耗功率。为了最大限度的提高效率,可以考虑使用电感,电感本身不消耗任何能量,只会进行储能,且其无损限流的能力正好可以用来抑制电容的浪涌电流。 + +引入电感后可以发现当开关打开时,电感没有续流回路,因此需要想办法构造电感的续流回路。续流回路需要保证不论开关打开还是闭合,电流都流向负载,且开关闭合时电源正极与负极回路必须经过电感与负载。这个需求很符合二极管的特点,即只允许单向导通。 + +到目前为止我们构建了非同步 BUCK 电路,考虑到机械开关容易磨损、使用寿命短、有机械惯性(转换频率低)的问题,我们需要将机械开关换成转换频率高的半导体器件,此处我们选择 NMOS 管来替代开关。 + +选择 NMOS 和 PMOS 的主要区别在于驱动电路的设计 + + +可以发现当 NMOS 开关管导通时,续流二极管处于截止状态;当开关管关断时,续流二极管处于导通状态。即二极管的导通和截止和开关管的截止导通是同步的,也就是说二极管起到的是一个开关的作用。而且考虑到电流从二极管流过期间,二极管两端的压降恒定为导通电压 0.7V,二极管所消耗的能量较大。因此我们也可以把二极管换为导通电阻更小的 NMOS 管。 + +为了提高 BUCK 电路的稳定性,防止由于输入纹波带来异常,我们在 BUCK 电路的输入端并联一个电容,用于滤除输入电压的纹波。 + +至此我们就搭建了一个标准的同步 BUCK 电路,我们将其简单变个样子,再加几个标签即可得到下图。在同步 BUCK 电路中需要两个开关管密切配合,以防止整个线路导通,所以它们之间需要保持一定的相位关系,即上管导通下管截止;上管截止下管导通,我们把这种关系称之为同步。 + +信号角度理解 LC +我们以占空比为 0.5 来进行说明,将时域下的方波转换到频域,通过傅立叶变换可以分解出一系列的频率分量。其中包含频率为 0 的分量,即直流分量,也就是我们想要保留的部分,还有频率为 n 倍 fsf_{s}fs​ 的分量。那么如何把我们不想要的部分去掉呢?从滤波角度考虑就需要加入一个低通滤波器。 + +通过加入低通滤波器可以把高频分量滤除,把二阶低通滤波器的截止频率设置在 0 到 fsf_{s}fs​ 之间,即可把 fsf_{s}fs​ 所有以上的部分给滤除。整体达到的效果即通过一个 LC 低通滤波器,配合一个开关网络,将一个数字化的电平重新滤出,得到一个比较平缓的电压输出,这个过程即完成了电压从高到低的转换。其中直流分量的大小受占空比 D 控制,所以通过改变占空比 D 即可改变输出电压大小。 + +稳态分析 +我们需要先强调一下前提,此处我们说的稳态分析,即输入电压输出电压都是稳定,且纹波足够小的状态。下文的所有计算都将基于稳态进行分析,并且是在 (F)CCM(连续导通模式)下计算的。 +我们将一些已知条件列出来: + +输入电压:ViV_{i}Vi​ +输出电压:VoV_{o}Vo​ +负载电阻:RRR +输出电感:LLL +开关频率:fff + +伏秒平衡 +当上管导通下管截止时,电感右边的电压为 ViV_{i}Vi​,左边的电压为 VoV_{o}Vo​,因为同步 BUCK 电路是降压电路,所以 Vi&gt;VoV_{i}&gt;V_{o}Vi​&gt;Vo​,所以电感两端电压即为 Vi−VoV_{i}-V_{o}Vi​−Vo​,也就是说是一个恒定值。由于有 Ldidt=Vi−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =V_{i}-V_{o}Ldtdi​=Vi​−Vo​,所以 didt=Vi−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{V_{i}-V_{o}}{L}dtdi​=LVi​−Vo​​,即电感电流的上升斜率,由于是稳态前提,所以可以确定该值是一个常数。 +当上管截止下管导通时,电感右边电压为 VoV_{o}Vo​,左边电压为 000,所以电感两端电压为 0−Vo0-V_{o}0−Vo​,即 −Vo-V_{o}−Vo​。由于 Ldidt=−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-V_{o}Ldtdi​=−Vo​,所以 didt=−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-\frac{V_{o}}{L}dtdi​=−LVo​​,即电感电流的下降斜率,也是一个常数。 +整个电路处于稳定状态,负载电路恒定,那么在一个周期内,电感电流增加的量肯定等于电感电流减小的量,即充了多少电就要放多少电,不然负载的电流或电压将会发生变化。 +前文已有didt=UL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{{U}}{L}dtdi​=LU​,而 LLL 恒定,那么电感电流的变化速度即与电压成正比关系,即电感电流上升(下降)的斜率与电压成正比关系。而电感电流上升和下降的高度相同,那么上升时间和下降时间就自然构成反比关系。 +TonToff=VoVi−Vo\frac{T_{on} }{T_{off} } = \frac{V_{o}}{V_{i}-V_{o}}Toff​Ton​​=Vi​−Vo​Vo​​,将其进行简单变换即可得到闻名江湖的伏秒平衡法则。 +Ton(Vi−Vo)=ToffVoT_{on}(V_{i}-V_{o}) = T_{off}V_{o}Ton​(Vi​−Vo​)=Toff​Vo​ +占空比 +已知 T=Ton+Toff=1fT=T_{on}+T_{off}=\frac{1}{f}T=Ton​+Toff​=f1​,结合伏秒平衡法则可以计算出: +开通时间:Ton=VoVi∙1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f}Ton​=Vi​Vo​​∙f1​ +关断时间:Toff=Vi−VoVi∙1fT_{off} = \frac{V_{i}-V_{o} }{V_{i} } \bullet \frac{1}{f}Toff​=Vi​Vi​−Vo​​∙f1​ +占空比:D=TonT=VoViD = \frac{T_{on} }{T}=\frac{V_{o} }{V_{i}}D=TTon​​=Vi​Vo​​ +纹波电流 +由于输出电压不变,也就是说输出电容两端的电压没有变化,即输出电容的平均电流为 0。根据输出节点的基尔霍夫电流定律可知,输出节点电流和为 0,那么功率电感的平均电流就等于负载的平均电流,即IL=Io=VoRI_{L} = I_{o} = \frac{V_{o} }{R}IL​=Io​=RVo​​。 + +上文计算电感电流斜率时已经能确定电流波形是个三角波,纹波电流等于在开关导通时电感电流的增大值,也等于在关断时电感电流减小的值,计算任意一个即可得到纹波电流。我们以上管导通时增大的电感电流计算。 + +上管导通时电感两端电压为 Vi−VoV_{i}-V_{o}Vi​−Vo​,导通时间为 Ton=VoVi∙1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f}Ton​=Vi​Vo​​∙f1​,根据 U=LdidtU=L\frac{\mathrm{d} i}{\mathrm{d} t}U=Ldtdi​ 可知: +△IL=di\triangle I_{L} =di△IL​=di +=Ton∙UL=T_{on}\bullet \frac{U}{L}=Ton​∙LU​ +=Vi−VoL∙VoVi∙1f=\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}=LVi​−Vo​​∙Vi​Vo​​∙f1​ +根据理论计算可以发现,电感电流的纹波和负载电流的大小没有关系,但是负载电流与平均电感电流是相等关系。 +功率电感选择 +根据上文的信息进一步可以计算出电感的峰值电流: +ILP=Io+△IL2I_{LP} =I_{o}+\frac{\triangle I_{L}}{2}ILP​=Io​+2△IL​​ +=Io+Vi−Vo2L∙VoVi∙1f=I_{o}+\frac{V_{i}-V_{o}}{2L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}=Io​+2LVi​−Vo​​∙Vi​Vo​​∙f1​ +那么在选择功率电感时,电感的饱和电流就必须要大于这个ILPI_{LP}ILP​,并且需要留有一定的裕量。实际应用时电感的纹波电流应是平均电流的 30%30\%30% 至 50%50\%50% 为宜,我们将这个参数称之为电流纹波率 r。根据电流纹波率范围就可以计算出电感值的范围: +△IL=Vi−VoL∙VoVi∙1f\triangle I_{L} =\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}△IL​=LVi​−Vo​​∙Vi​Vo​​∙f1​ +L=Vi−Vo△IL∙VoVi∙1fL =\frac{V_{i}-V_{o}}{\triangle I_{L}}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}L=△IL​Vi​−Vo​​∙Vi​Vo​​∙f1​ +=Vi−Vo(0.3至0.5)Io∙VoVi∙1f=\frac{V_{i}-V_{o}}{(0.3至0.5) I_{o}}\bullet \frac{V_{o}} {V_{i}}\bullet \frac{1}{f}=(0.3至0.5)Io​Vi​−Vo​​∙Vi​Vo​​∙f1​ +=(Vi−Vo)Vo(0.3至0.5)IoVif=\frac{(V_{i}-V_{o})V_{o}}{(0.3至0.5) I_{o} V_{i} f}=(0.3至0.5)Io​Vi​f(Vi​−Vo​)Vo​​ +为何 r 为 0.3~0.5 +电流纹波率即是电感电流的交流分量与其相对应的直流分量的比值,一旦 r 确定,那么输入输出滤波电容的电流、开关管的有效电流等都确定了,因此 r 的选择会影响器件选择和芯片的成本。使用公式可以表述为: +r=△IIL=2×IACIDCr=\frac{\triangle I}{I_{L}}=2\times \frac{I_{AC}}{I_{DC}}r=IL​△I​=2×IDC​IAC​​ +一般认为,电感体积与其能量处理能量成正比,因为要处理更高的能量就需要更大的磁芯。选择电感磁芯的能量处理能力至少要等于其需存储量,即 E=12×L×Ipk2E=\frac{1}{2} \times L \times I_{pk}^{2}E=21​×L×Ipk2​,下图是 E 与 r 的的函数曲线,可以发现在 r=0.4r=0.4r=0.4 附近有一个拐点。 + +选择的 r 如果较 0.4 低很多,则所需要的电感体积越大;而若继续增大 r,则电感的体积并不会减少多少,即当 r 超过 0.4 后,通过增加 r 来减少电感体积的效果已经不明显了。 +输入纹波 +电源输入功率为 Pi=ViIiP_{i}=V_{i}I_{i}Pi​=Vi​Ii​,负载功率为 Pr=VoIoP_{r}=V_{o}I_{o}Pr​=Vo​Io​,不考虑开关损耗、导通损耗等等因素,那么输入功率和输出功率相等,可得输入平均电流为 Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}Ii​=Vi​Vo​Io​​。 +输入电压纹波就是输入电容上面电压的变化,这个变化可以分为两部分。一部分为电容充放电所导致的电压变化 UqU_{q}Uq​,另一部分为电流流过电容 ESRESRESR 导致的压降 UesrU_{esr}Uesr​。即 △Vi=Uq+User\triangle V_{i} = U_{q} + U_{ser}△Vi​=Uq​+User​。 +∵Q=CiUq=Iit=IiToff\because Q = C_{i}U_{q} = I_{i}t = I_{i}T_{off}∵Q=Ci​Uq​=Ii​t=Ii​Toff​ +∴Uq=IiToffCi\therefore U_{q} = \frac{I_{i}T_{off}}{C_{i}}∴Uq​=Ci​Ii​Toff​​ +∵Toff=Vi−VoVif\because T_{off} = \frac{V_{i}-V_{o} }{V_{i}f }∵Toff​=Vi​fVi​−Vo​​ +且 Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}Ii​=Vi​Vo​Io​​ +∴Uq=VoIoCiVif∙Vi−VoVi\therefore U_{q} = \frac{V_{o}I_{o}}{C_{i}V_{i}f}\bullet \frac{V_{i}-V_{o}}{V_{i}}∴Uq​=Ci​Vi​fVo​Io​​∙Vi​Vi​−Vo​​ +要想知道 ESRESRESR 所造成的纹波,只需要知道流过输入电容的电流即可。当上管断开时,电源输入电流 IiI_{i}Ii​ 全部流入电容 CiC_{i}Ci​。电感电流原本从下管的体二极管续流,当上管导通后,变为了从上管续流。因为此前电感一直处于放电状态,所以切换的那一刻电感电流是最小的,为 IL−△IL2I_{L}-\frac{\triangle I_{L}}{2}IL​−2△IL​​。 +在整个 TonT_{on}Ton​ 时间内,电感都被充电,电感电流一直都在增大,直到 IL+△IL2I_{L}+\frac{\triangle I_{L}}{2}IL​+2△IL​​,并且在 TonT_{on}Ton​ 时间内,电感电流都是走的上 MOS 管通路,所以上 MOS 管最大电流也是 IL+△IL2I_{L}+\frac{\triangle I_{L}}{2}IL​+2△IL​​。 +根据基尔霍夫电流定律可知,输入节点的电流和为 0,那么输入电源电流 IiI_{i}Ii​ 和电容 CiC_{i}Ci​ 的放电电流就等于通过上 MOS 管的电流。所以 CiC_{i}Ci​ 的最大放电电流即为 IL+△IL2−IiI_{L}+\frac{\triangle I_{L}}{2} - I_{i}IL​+2△IL​​−Ii​。我们约定充电为正,放电为负,则放电电流为 Ii−△IL2−ILI_{i} - \frac{\triangle I_{L}}{2} - I_{L}Ii​−2△IL​​−IL​。 + + +上管截止时 ESRESRESR 的压降为 Ii∙ESRI_{i} \bullet ESRIi​∙ESR,上管导通时压降为 (Ii−△IL2−IL)∙ESR(I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESR(Ii​−2△IL​​−IL​)∙ESR,则可得: +User=Ii∙ESR+(Ii−△IL2−IL)∙ESRU_{ser} = I_{i} \bullet ESR + (I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESRUser​=Ii​∙ESR+(Ii​−2△IL​​−IL​)∙ESR +=(IL+△IL2)∙ESR=(I_{L} + \frac{\triangle I_{L}}{2}) \bullet ESR=(IL​+2△IL​​)∙ESR +∵△IL==Vi−VoL∙VoVi∙1f\because \triangle I_{L} ==\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}∵△IL​==LVi​−Vo​​∙Vi​Vo​​∙f1​ +且 IL=IoI_{L} = I_{o}IL​=Io​ +∴User=(Io+(Vi−Vo)Vo2ViLf)∙ESR\therefore U_{ser} = \left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR∴User​=(Io​+2Vi​Lf(Vi​−Vo​)Vo​​)∙ESR +综上所述可得: +△Vi=Uq+Uesr\triangle V_{i} = U_{q} + U_{esr}△Vi​=Uq​+Uesr​ +=VoIoCiVif∙Vi−VoVi+(Io+(Vi−Vo)Vo2ViLf)∙ESR=\frac{V_{o}I_{o}}{C_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}} +\left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR=Ci​Vi​fVo​Io​​∙Vi​Vi​−Vo​​+(Io​+2Vi​Lf(Vi​−Vo​)Vo​​)∙ESR +输入电容选择 +考虑到电容的实际使用情况,陶瓷电容的 ESRESRESR 小,容量小,所以 UqU_{q}Uq​ 对纹波起决定性作用,输入纹波可近似为 UqU_{q}Uq​。若选择铝电解电容,则 ESRESRESR 大,容量大,UesrU_{esr}Uesr​ 对纹波起到决定性作用,输入纹波可以近似为 UesrU_{esr}Uesr​,假设电路设计要求输入纹波不能大于 △Vi\triangle V_{i}△Vi​,则有: +陶瓷电容:Ci≥VoIo△ViVif∙Vi−VoViC_{i} \ge \frac{V_{o}I_{o}}{\triangle V_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}}Ci​≥△Vi​Vi​fVo​Io​​∙Vi​Vi​−Vo​​ +铝电解电容:ESR≤△ViIo+(Vi−Vo)Vo2fLViESR \le \frac{\triangle V_{i}}{I_{o} + \frac{(V_{i}-V_{o})V_{o}}{2fLV_{i}} }ESR≤Io​+2fLVi​(Vi​−Vo​)Vo​​△Vi​​ +输出纹波 +输出纹波与输入纹波同理,亦是 △Vo=Uq+Uesr\triangle V_{o} = U_{q} + U_{esr}△Vo​=Uq​+Uesr​,我们画出负载、功率电感、输出电容三者的电流波形。其中电感的纹波电流是 △IL\triangle I_{L}△IL​,则电容的纹波电流也是 △IL\triangle I_{L}△IL​,又因为电容的平均电流为 0,所以充电电流和放电电流都是 △IL2\frac{\triangle I_{L}}{2}2△IL​​。 +电容充放电的总电荷量 Q 等于电流乘以时间,即图中阴影三角形的面积,三角形底部时间为 T2\frac{T}{2}2T​,高为 △IL2\frac{\triangle I_{L}}{2}2△IL​​,所以总的放电量可以计算出来为 Q=12∙T2∙△IL2Q=\frac{1}{2} \bullet \frac{T}{2} \bullet \frac{\triangle I_{L}}{2}Q=21​∙2T​∙2△IL​​ + +结合 Q=CoUqQ=C_{o}U_{q}Q=Co​Uq​ 可得: +Uq=(Vi−Vo)Vo8ViCoLf2U_{q} = \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} }Uq​=8Vi​Co​Lf2(Vi​−Vo​)Vo​​ +由前面电流波形可知,电容的充电电流最大是 △IL2\frac{\triangle I_{L}}{2}2△IL​​,放电电流最大是 −△IL2-\frac{\triangle I_{L}}{2}−2△IL​​,则可以得到 ESRESRESR 引起的总压降为: +User=△IL2∙ESR−(−△IL2∙ESR)U_{ser} = \frac{\triangle I_{L}}{2} \bullet ESR - (-\frac{\triangle I_{L}}{2} \bullet ESR)User​=2△IL​​∙ESR−(−2△IL​​∙ESR) +∵△IL=Vi−VoL∙VoVi∙1f\because \triangle I_{L} = \frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}∵△IL​=LVi​−Vo​​∙Vi​Vo​​∙f1​ +∴(Vi−Vo)VoViLf∙ESR\therefore \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR∴Vi​Lf(Vi​−Vo​)Vo​​∙ESR +最终可得: +△Uo=Uq+Uesr\bigtriangleup U_{o} = U_{q} + U_{esr}△Uo​=Uq​+Uesr​ +=(Vi−Vo)Vo8ViCoLf2+(Vi−Vo)VoViLf∙ESR=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} } + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR=8Vi​Co​Lf2(Vi​−Vo​)Vo​​+Vi​Lf(Vi​−Vo​)Vo​​∙ESR +=(Vi−Vo)VoViLf∙(ESR+18fCo)=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf}\bullet \left ( ESR + \frac{1}{8fC_{o}} \right )=Vi​Lf(Vi​−Vo​)Vo​​∙(ESR+8fCo​1​) +输出电容选择 +与输入电容选择的方式一致,考虑是容值还是 ESRESRESR 占主导地位,假设要求输出纹波要小于 △Vo\triangle V_{o}△Vo​,则有: +陶瓷电容:Co≥(Vi−Vo)Vo8Vi△VoLf2C_{o} \ge \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}\triangle V_{o}Lf^{2} }Co​≥8Vi​△Vo​Lf2(Vi​−Vo​)Vo​​ +铝电解电容:ESR≤△VoViLf(Vi−Vo)VoESR \le \frac{\triangle V_{o}V_{i}Lf}{\left ( V_{i}-V_{o} \right ) V_{o}}ESR≤(Vi​−Vo​)Vo​△Vo​Vi​Lf​ +电感续流模式 +电感电流曲线不能断续(无突降),因为电流断续会引起实际不可能发生的能量断续现象。但是电流的变化率可以突变,比如从上升斜率(电感储能增加)变为下降斜率(电感储能释放),尽管这样电感电流也必须连续。根据稳定状态下每个周期电流是否回到零,划分为不同的导通模式,并且通过减小负载电流,可以使电路从 CCM 经过 BCM 最终转变为 DCM。 +CCM(连续导通模式) +稳定状态下每个周期,电流都回到某一非零值,称之为连续导通模式(CCM:Continuous Conduction Mode)。CCM 是功率变换中最常见的工作模式,有输出纹波小但功耗高的特点。 +FCCM(强制连续导通模式)只存在于同步 BUCK 中,由于使用 MOS 管将非同步拓扑的二极管取代,MOS 管的导通压降远低于二极管压降,除了显著减小了续流通路的导通损耗外,也允许电感电流反向,即从负载瞬时流出电流。 + +DCM(断续导通模式) +若稳定状态下每个周期中电流都会回到零,那么就称之为断续导通模式(DCM:Discontinuous Conduction Mode),DCM 由于其电感电流的不连续,计算平均电感电流就需要更加详细复杂的公式,这也是 DCM 方程看上去复杂的根本原因。 + +BCM(临界导通模式) +BCM 是临界导通模式(Boundary Conduction Mode),由控制器监控电感电流,一旦检测到电流等于 0,功率开关立即闭合,控制器总是等电感电流「复位」来激活开关。即 BCM 处于 CCM 和 DCM 之间,可以将其视为 CCM 和 DCM 的极端情况,所以 BCM 模式下可以自由的选择 CCM 或 BCM 方程。 + +DC-DC 功能框图 +前文所构建的 BUCK 电路只能是纸上谈兵,还需要解决诸多问题才能应用于实际电路。 +基础驱动与控制 +首先需要解决的问题就是 MOS 管不可能平白无故就打开,所以我们需要添加 MOS 管驱动器。 + +理想情况是上管关闭,下管立刻打开,中间没有任何时间差,但是 MOS 管并非理想开关,从关断到导通存在一个过渡的过程,若同时导通则电源通过上下 MOS 管直接对地短路,很容易就会导致 MOS 损坏,甚至可能会把前一级电源也损坏,所以上下管同时导通的状态必须得避免。 +为了避免上下管直通的情况,实际应用会故意让上管和下管切换时多等一会儿,宁愿出现同时关断的情况,也不能出现同时导通的状态,这个等待的过程就叫做死区时间。 + +需要注意的是,在死区时间内虽然下管没有被导通,但是功率 MOS 管本身存在一个寄生二极管,这个寄生二极管可以像非同步 BUCK 那样帮助电感续流,而且这个时间非常的短暂,所以产生的功耗没有那么大,因此不必担心系统会出问题。 +到目前为止,不知道您有没有发现我们都在自嗨,系统中并没有用来控制上下 MOS 导通和关断的信号。因此需要增加一个振荡器用来产生控制信号,注意我们在前文中使用的是占空比一词,也就是说我们要使用的是 PWM(脉冲宽度调制)。当然你也可以使用 PFM(脉冲频率调制),本文只介绍 PWM 方式。 + +PWM(脉冲宽度调制) +PWM 的全称是脉冲宽度调制(Pulse-width modulation),是通过将有效的电信号分散成离散形式,从而来降低电信号所传递的平均功率的一种方式。其基本实现原理是通过锯齿波/三角波(载波)与所需要合成的波形(调制波)进行比较,然后确定 PWM 所需要输出的极性。因为一般都是用到开关器件上,通常是 ON 或者 OFF,具体如下图所示。 + +将振荡器输出的锯齿波和参考值 VthV_{th}Vth​ 进行比较,就可以输出 PWM 波形了。话不多说,上图就明白了。 + +上图中的锯齿波(橙色)最大为 10,但是我们希望输出平均为 5 的波形(图中紫色的水平线),那么通过比较器进行比较,当锯齿波小于 5 时,PWM 即输出低电平 OFF,当锯齿波大于 5 时,PWM 即输出高电平 ON,此时的占空比即为 50%。 +若是想输出一个电压逐渐抬高的波形,即占空比逐渐增大,那只需要将调制的波形设置为斜坡输出即可达到效果。比如下图中可以看到,占空比从 0% 逐渐增大到 100%。 + +同样的道理,我们可以通过改变调制波形,进一步调制出来其它的波形,比如要调制一个正弦波(sin wave),也就是我们常说的 SPWM,那么就是下面的样子。 + +负反馈环路 +有了调制信号,开关管也可以正常打开与关闭,看起来可以应用到实际电路中了,但是别忘了负载的电阻并不是恒定的,负载的变化必然会引起输出电压的波动。为了减小输出电压的波动,我们可以在输出端添加分压电阻,与误差放大器和基准电压一起构成负反馈回路,这种通过取样输出电压进行闭环反馈的方式称之为电压模式控制。 + +误差放大器的输入端分别为带隙基准源输出电压采样,当输出电压减小/增大时,与基准电压的细微差异都会被误差放大器放大,今儿调节脉冲宽度来达到调节调整输出电压的目的。图中 R2 接地,所以可以很容易计算出输出电压与分压电阻的关系:Vout=Vref(R1+R2)R2V_{out} = \frac{V_{ref}(R_{1}+R_{2})}{R_{2}}Vout​=R2​Vref​(R1​+R2​)​。 +除了输出电压可以用作控制取样信号,还有输入电压、输出电流、输出电感电压、开关器件峰值电流可以作为控制取样信号。使用这些信号可以构成单环、双环或多环反馈系统,进而实现稳压、稳流以及恒定功率的目的,也可以实现过流、过压、均流等功能。 +现在回过头来评判一下电压模式控制的优缺点。单一的反馈电压闭环设计使得调试更加容易、对输出负载的变化有比较好的响应调节、占空比的调节也不会受到什么限制等等都是它的优点,但是其缺点也很明显。由于主电路有较大的输出电容和电感的相移延时作用,输出电压的变小/变大也延时滞后,再经过误差放大器的延时,使得瞬态响应变得更慢。由于电压控制模式不采样电流,逐周期限流保护功能必须另外增加电路来实现。 +峰值电流模式控制在电压模式控制的基础上又增加了电流环,所以峰值电流模式控制是一个双环反馈系统。误差电压信号与一个变化的,其峰值代表输出电感电流峰值的三角波形进行比较,然后得到 PWM 脉冲的关断时刻。所以峰值电流模式控制不是使用电压误差信息直接控制 PWM 脉冲宽度,而是直接控制峰值输出侧的电感电流大小,进而间接的控制 PWM 脉冲宽度。 + +峰值电流在逻辑上与平均电感电流大小变化一致,但是峰值电感电流的大小并不能与平均电感电流的大小一一对应。在占空比不同的情况下,相同的峰值电感电流大小可以对应不同的平均电感电流大小,但平均电感电流大小才是唯一决定输出电压大小的因素。 +为了解决不同占空比对平均电感电流大小的扰动作用,使得所控制的峰值电感电流最后收敛于平均电感电流,需要将电感电流下斜坡斜率的至少一半以上斜率加在实际检测电流的上斜坡上,这一点可以从数学上进行证明(具体咋证明暂不讨论)。 +总结一下峰值电流模式控制 PWM 是双闭环控制系统,电压外环控制电流内环。电流内环是瞬时快速按照逐个脉冲工作的。功率级石油电流内环控制的电流源,而电压外环再控制次功率级电流源。电流内环只负责输出电感的动态变化,电压外环仅需控制输出电容,所以峰值电流模式控制 PWM 具有比电压模式控制大得多的带宽。 +为了防止在应用过程中可能出现的短路等异常场景,DC-DC 少不了过温保护、过流保护、过压保护等保护手段。再设定一定的辅助功能,比如 PG 状态显示、缓启动、欠压保护等即可搭建完整的 DC-DC 电路。 + +异常模式 +参考上文中的电路图,我们把绿色部分称之为控制电路,灰色部分是功率电路,功率电路中最核心的就是上下两个 MOS 管,下文我们讨论不同的异常场景中,控制电路、上管、下管三部分应该处于什么状态,其中控制电路关闭相当于整个芯片重启。 +过压保护 +当输出电压偏高并且达到了过压保护的阈值。过压状态需要控制电路去调整把输出电压降下来,所以不需要重启整个芯片。可以想到输出端已经处于过压状态了,上管如果打开那会加重过压的程度,因此上管需要关闭。若下管打开,则电感、负载、下管形成回路,即电感有续流回路,会把过压状态维持的时间更长,因此下管也需要关闭。综上有:过压保护:关上管、关下管。 +过温保护 +温度过高的情况无非两种,一种是流过芯片的电流太大,即功率太大导致芯片自身发热达到了过温保护的阈值,此时关闭芯片肯定可以解决,另外切断电流回路也是可以解决的,即关闭上管。过温的第二种情况是由于环境温度过高而导致芯片温度过高,此时最好还是关闭芯片吧。综上有:过温保护:关闭芯片。 + +关闭芯片指关闭芯片中的 BUCK 部分,但是基准源部分仍然保持工作 + +过流保护 +过流保护还需要区分是正向过流还是负向过流,因为工作在 FCCM 模式的 DC-DC 在轻载或空载时,可能会有负向过流的情况。存在负向过流的另一原因也是因为同步 BUCK 没有像非同步 BUCK 那样的整流二极管,所以当存在负向过流情况时,直接模拟非同步 BUCK 中的二极管即可。综上有:负向过流保护:关下管。 +若发生正向过流时如何进行保护呢?首先考虑到电流经上管到负载,既然已经过流了那么肯定需要关上管。为了使电流减小的更快,那么就需要将电流流向地,所以需要将下管打开以构成回路。综上有:正向过流保护:关上管、开下管。 +异常排查 +不管系统设计的多好,在实际应用中都可能会或多或少出现问题,比如电感选用不合适、触发 min-on time、触发 min-off time、输出电容 ESR 过大等,下面我们逐一进行讨论。 +min-on time +虽然 MOS 管打开速度很快,但是打开始终是一个过程,要完成一个过程就必须需要一定的时间,当高频且压差大的情况下很容易触发完成「打开」这个过程的最小时间。也就是说占空比已经是实际最小了,占空比无法再降低了,所以查看输出电压纹波可能会出现下面的波形。 + +出现该波形的原因在于,占空比已经无法继续降低,所以电压整体处于逐渐抬高的趋势,当抬高到一定程度时即触发过压保护,上下管都关断,所以电压快速下降。 +min-off time +与 min-on time 相对应的是 min-off time,当开关频率足够高且输入和输出电压接近时即容易出现此问题,此时即达到系统所能达到的最大占空比也无法满足负载所需要的电压,表现为输出电压无法达到设定值,负反馈分压电阻电压也低于电压基准值。 +电感饱和电流过小 +电感电流正常是一个三角波,但是如果电感饱和电流过小,则会电感电流将会变成下图很苗条的样子。因为电感电流饱和所以电流不再线性增加,电流快速增大导致磁通率减小,会导致磁性损耗增大、芯片热耗增大,而且这是一个正反馈过程,整个系统的可靠性会大大降低。 + +输出电容选用不合适 +当输出电容选用过小时,会导致动态响应输出出现抖动。若输出电容的等效串联电阻(ESR)过大,也会导致输出纹波异常增大,这一点从前文的理论计算即可验证。因此在实际使用过程中需要同时考虑电容容值和所选电容的 ESR。 +为什么需要 min-on time +占空比 D 控制相对于输入电压的输出电压,虽然通过提高开关频率有助于减小电感尺寸,但是也必须满足最小导通时间(min-on time)才能使芯片正常工作。那么这个 min-on time 是由哪些因素引起的呢? +因为上管中电流波形前沿的电流尖峰。由于 MOS 管也是由 PN 结组成,存在 PN 结就肯定存在结电容,MOS 管的寄生电容 CgsC_{gs}Cgs​ 和 CgdC_{gd}Cgd​ 会导致上管在导通时电流突然变化,也就是说会出现电流尖峰。如果在这个电流尖峰的时间段内去检测电流的话,很可能就会触发过流保护,因此开关电路的最小导通时间必须大于电流尖峰出现的时间,这个时间我们称之为消隐时间。 + +另一个原因是因为上下管开关完成后,由于键合线存在寄生电感的原因会产生很大的振铃,这个振铃同样可能会导致峰值电流检测出错,需要一个 min-on time 将这个振铃隔离过去。 +为什么需要 min-off time +如下图所示,最简单的需要最小关断时间(min-off time)的原因是,若下管不打开则没有办法给自举电容充电,所以需要在该时间内给自举电容充电,为下一个开关周期做准备。 + +另一个原因是因为没有最小关断时间,即占空比 D 增大到 100%,那么就无法对负向电流、谷值电流进行采样,也就无法实现实现相应的异常保护功能。与 min-on time 一致,电流检测也需要一个 min-off time 隔离振铃。 +为什么需要自举电容 +DC-DC 的上 MOS 管可以是 PMOS,也可以是 NMOS。但是一般因为生产工艺问题,PMOS 导通电流往往做不不到很大,而在相同成本下 NMOS 的导通电流可以做到更大,也就是 RdsonR_{dson}Rdson​ 可以做到相对较低,所以往往更倾向于 NMOS。 +将上管换为 NMOS 后也带来了新的问题,如何打开 NMOS ?如图所示,上管的 S 极连接 PH 点,该点的电压为 +5V,要打开 NMOS 需要 VGS&gt;0V_{GS} &gt; 0VGS​&gt;0,驱动 MOS 管打开的压降需要 5V,那么驱动电压就需要 +10V 才可以打开上管,但是纵观整个电路并没有能达到 +10V 级别的电压,所以需要自举电容来进行升压才能打开上 MOS 管。 + +所以 DC-DC 芯片是否需要自举电容是由芯片所选用的 MOS 管类型决定的,若是 PMOS 则无需自举电容。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/ghceoN-RR/index.html b/ghceoN-RR/index.html new file mode 100644 index 00000000..3bdcc369 --- /dev/null +++ b/ghceoN-RR/index.html @@ -0,0 +1,555 @@ + + + + + + + + 为什么宏定义要使用 do {...} while (0) ? | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 为什么宏定义要使用 do {...} while (0) ? +

+ + +
+ +
+
+

参考内容:
+do {…} while (0) in macros
+do {...} while (0) 在宏定义中的作用
+do{}while(0)只执行一次无意义?你可能真的没理解

+
+

近期参与的项目属于嵌入式软件领域,自然而然就得用 C 语言进行开发,开发过程中发现引入的第三方库里面有一些奇奇怪怪的写法,比如大佬们都喜欢使用do {...} while(0)的宏定义,在 Stack Overflow 上也有人提出了这个问题。之前从事 Linux 内核开发的谷歌大佬 Robert Love 给出了如下的解释:

+
+

do {…} while(0) is the only construct in C that lets you define macros that always work the same way, so that a semicolon after your macro always has the same effect, regardless of how the macro is used (with particularly emphasis on the issue of nesting the macro in an if without curly-brackets).

+

do {...} while(0) 在 C 中是唯一的构造程序,让你定义的宏总是以相同的方式工作,这样不管怎么使用宏(尤其在没有用大括号包围调用宏的语句),宏后面的分号也是相同的效果。

+
+

这句话读起来有些拗口,只觉得大佬的表述曲高和寡,翻译翻译就是:使用do {...} while(0)构造后的宏定义不会受到大括号、分号等影响,总能按照我们期望的方式调用运行。下面我们举几个实际的例子来加深理解。

+
// 现有如下宏定义
+#define foo(x) bar(x); baz(x)
+
+// 1. 可以这样调用
+foo(wolf);
+
+// 上述调用将会被展开为下列代码,完美没有问题
+bar(wolf); baz(wolf);
+
+
+// 2. 如果我们像下面这样调用呢?
+if (!feral)
+    foo(wolf);
+
+// 上述调用将会被展开为下列代码,很明显这是错误的,并且是很容易犯的错误
+if (!feral)
+    bar(wolf);
+baz(wolf);
+
+

为了避免上面例子所出现的问题,我们可以考虑使用{ }直接把整个宏包裹起来,如下所示:

+
// 修改宏定义为
+#define foo(x) { bar(x); baz(x) }
+
+// 3. 例 1 调用
+if (!feral)
+    foo(wolf);
+
+// 现在上述调用将展开为下列代码
+if (!feral) {
+    bar(wolf);
+    baz(wolf);
+};
+
+
+// 4. 我们再考虑一下如下调用呢
+if (!feral)
+    foo(wolf);
+else
+    bin(wolf);
+
+// 上述调用将会被展开为下列代码,很明显又出现了语法错误
+if (!feral) {
+    bar(wolf);
+    baz(wolf);
+};
+else
+    bin(wolf);
+
+

我们继续考虑比使用{ }直接把整个宏包裹起来更好的方法,即本文标题所说的使用do {...} while (0),即上述宏将定义为如下形式。

+
// 终极版宏定义
+#define foo(x) do { bar(x); baz(x) } while (0)
+
+// 5. 例 4 调用
+if (!feral)
+    foo(wolf);
+else
+    bin(wolf);
+
+// 现在上述调用将展开为下列形式,很完美
+if (!feral)
+    do { bar(wolf); baz(wolf) } while (0);
+else
+    bin(wolf);
+
+

do {...} while (0)除了在宏定义中可以发挥完美的作用外,在某些情况下还可以当作goto使用。因为goto不符合软件工程的结构化,并且容易使得代码晦涩难懂,所以很多公司都不倡导使用甚至禁止使用。那么我们可以使用do {...} while (0)来做同样的事情。

+
#include <stdio.h>
+#include <stdlib.h>
+int main()
+{
+   char *str;
+   /* 最初的内存分配 */
+   str = (char *) malloc(15);
+   if(str != NULL)
+     goto loop;
+   printf("hello world\n");
+loop:
+   printf("malloc success\n");
+   return 0;
+}
+
+

上述代码我们可以修改为下列形式,使用do {...} while (0)将函数主体包裹起来,而break语句则替代了goto语句的作用,并且代码的可读性与可维护性都比上述goto方式更好。

+
#include <stdio.h>
+#include <stdlib.h>
+int main()
+{
+  do{
+      char *str;
+      /* 最初的内存分配 */
+      str = (char *) malloc(15);
+      if(str != NULL)
+       break;
+      printf("hello world\n");
+  }while(0);
+   printf("malloc success\n");
+   return 0;
+}
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/h1zgmhimT/index.html b/h1zgmhimT/index.html new file mode 100644 index 00000000..449e61df --- /dev/null +++ b/h1zgmhimT/index.html @@ -0,0 +1,466 @@ + + + + + + + + 智齿|读研否|家庭小江湖|广告乱象 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 智齿|读研否|家庭小江湖|广告乱象 +

+ + +
+ +
+

建议读者大人们,如果自己经常一上火牙就疼,或者自己感觉牙已经有点问题了,可以提早预约医院的口腔科查一下,如果有问题早点预防,总是没有坏处的。抽烟的半年洗一次牙,不抽烟的一年洗一次牙。

+

因为智齿发炎被狠狠的折磨了近两周,我属于比较能忍得疼痛的人,这不周末才进行了一场春季骑行,唯有美景与美食不可辜负,什么病痛都是浮云,但回想起连续几晚上疼到睡不着觉的滋味,昨天毅然决然斩草除根,给拔掉了,今天就感觉好了很多。

+
+

前几天考研成绩出来了,估计现在大部分同学都在准备复试,我没有体验过考研的这个过程,毕业这半年有时候还是会想,我也应该体验一下考研的那个过程,已经很久没有体验过把所有时间都投入到一件事情上的快乐了。

+

但我还是不太建议读研,我这是无责任建议,毕竟自己没有读过研究生,在研究生实验室待了两年,算是有一半的硕士生经历吧。对于学历我一直以来的观点都是绝大部分人将它的作用放大了,总是认为名校成造就了强能力,而恰恰把因果关系给弄反了,是能力强的人都进了名校(本科)。当然,不否认像医学一类的专业是肯定要考研的,一棒子全打死肯定是不对的。

+
+

刘大发起的读书活动告一段落了,跟着小伙伴们泛泛的读了一遍《深入理解计算机系统》,只能用“痛并快乐着”来形容这个过程,每周输出一篇读书笔记,没有按时输出就罚钱的规矩很好,人还是需要自己逼自己才行。

+

这本书不适合初学者阅读,在豆瓣上的评分接近 10 分,不讲究速成,而是一本内功心法,如果是您是码农的话,读一读绝对会提高一个层级。现在已经开启了另一本书籍的阅读计划,刘大这个活动组织的超好。

+
+

春节回家发现了一个巨大的变化,我老家那种贫困县地区的村民们,也在开始讨论保险这一类产品了,我是觉得这个改变太大了,说明农民伯伯的经济水平也有很大的提升了。另外通过朋友圈还发现,我认识的大佬们貌似出身都并不是多好,反倒是大部分普通朋友家里的矿更多。

+

我们家族每年会组织祭祖活动,在正月初三一大家 50 人左右一同祭拜曾祖曾母,通过这么一个活动把整个大家族的年轻人联系起来,能搭建这样一个平台很棒,我正也在着手将家族信息数字化。

+
+

说到这里,想说一句家族群是个小江湖,亲戚之间也是暗暗较劲的,母亲不会抢群里几个特定的人发的红包,家里都是山路,车技不好的人很容易就寸步难行,一表叔就因为不到 10 米的距离,整个春节都在亲戚朋友面前抬不起头。

+

堂弟现在是民航飞行员在读,而另一个表弟今年正值高考,说要去考炮兵学院,将来好把飞机打下来,这一下可好了,这些话全部伯父被截屏保留了,将来某一天要是这俩兄弟闹矛盾了,估计有的好看。保二爷写的家族群不是群,是江湖...看起来更有趣一点。

+

在我身上更可悲的事情发生了,所有长辈一致认同应该由我来管理家族群,想想整个群里充斥的都是是那种要露不露、似露非露、就是不露的视频,或者是用粗糙都无法形容的大而泛的鸡汤文,整个头就大了,这可比解决技术问题难多了。

+
+

最近愈发觉得“大佬”之间的抄袭严重了,真大佬基本都是原创内容,或者是引用了别人的文字就标注出来,然而总是看到一些“大佬”原封不动发出来,还不表明出处,下面粉丝跟着继续做同样的事,我看到最多的一次是朋友圈连续 10 多条是一样的段子。

+
+

最后想无责任乱说一点科技相关的东西,5G 是当下的风口浪尖,各大厂商都希望在 5G 上有一席之地,搞芯片的搞芯片、做基站的做基站、整手机的整手机,5G + IPV6 肯定会带来无法想象的未来,5G 会大幅推动智能硬件的应用,但是手机这个应用场景是不是被夸大了呢?

+

现在的 4G 手机在线看一部高清电影不会有多卡顿的现象,广大吃瓜群众和各大媒体,一直都拿着 5G 手机来吹嘘,吃瓜群众跟随媒体引导的大流,我总觉得当拿到 5G 手机的那一刻,心里肯定会从喜悦急转失望的,就像目前的苹果产品一样。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/hF3_PWTirK/index.html b/hF3_PWTirK/index.html new file mode 100644 index 00000000..530ac5bb --- /dev/null +++ b/hF3_PWTirK/index.html @@ -0,0 +1,708 @@ + + + + + + + + Java | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + Java +
+ + +
+

+ + Oracle 安装及 Spring 使用 Oracle + +

+ +
+ + + + +
+ +
+ +参考内容: +docker安装oracle数据库史上最全步骤(带图文) +Mac下oracle数据库客户端 +Docker安装Oracle +docker能安装oracle吗 +Batch script for add a auto-increased primary key for exist table with records + +Docker 安装 Oracle11g +注意:下列安装方式仅适用于x86架构服务器,不适用于arm架构服务器。 +# 拉取 oracle11,镜像有点大,需要花一些时间 +docker pull registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g + +# 查看镜像是否拉取成功 +docker images + +# 给镜像重新打 tag,原来的名字太长了 +docker tag registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g oracle11g:latest + +# 启动 oracle11g 容器 +docker run --name=oracle11g -itd -p 1521:1521 + +# 进入容器进行配置 +docker exec -it oracle11g /bin/bash + +# 切换到 root 用户,密码为:helowin +su root + +# 编辑配置文件 + +编辑/etc/profile,在其中增加如下内容: +export ORACLE_HOME=/home/oracle/app/oracle/product/11.2.0/dbhome_2 +export ORACLE_SID=helowin +export PATH=$ORACLE_HOME/bin:$PATH + +编辑完成后,需要刷新上述环境变量才能使用。 +# 刷新环境变量 +source /etc/profile + +# 创建软链接 +ln -s $ORACLE_HOME/bin/sqlplus /usr/bin + +# 切换到 oracle 用户 +su - oracle + +# 登陆 sqlplus +sqlplus /nolog +conn /as sysdba + +# 修改 system 用户密码 +alter user system identified by system; +# 修改 sys 用户密码 +alter user sys identified by system; + +# 创建内部管理员账号 +create user test identified by test; + +# 将 dba 权限授权给内部管理员账号和密码 +grant connect,resource,dba to test; + +# 修改密码规则策略为密码永不过期 +ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED; + +# 修改数据库最大连接数据 +alter system set processes=1000 scope=spfile; + +修改上述信息后,需要重新启动数据库才会生效。 +conn /as sysdba + +# 关闭数据库 +shutdown immediate; + +# 启动数据库 +startup; + +# 退出软链接 +exit; + +客户端连接 Oracle +以 Navicat 客户端为例,新建连接时按下图方式填写连接信息即可,密码即为system。需要注意的是,在 Windows 下选择 SID 或是服务名均可连接成功,但是在 Mac 下需要选择 SID 方式才能连接成功。 + +Oracle 实现主键自增 +Oracle 在创建表的时候,不能像 MySQL 那样选择主键直接自增,但是我们可以通过给表创建序列和触发器去实现自增。下文以创建 USER 表为例。 +-- 删除原有 USER 表 +DROP TABLE &quot;TEST&quot;.&quot;USER&quot;; +-- 创建 USER 表 +CREATE TABLE &quot;TEST&quot;.&quot;USER&quot; ( + &quot;id&quot; NUMBER NOT NULL, + &quot;gmt_create&quot; DATE NOT NULL, + &quot;gmt_modified&quot; DATE NOT NULL, + &quot;is_deleted&quot; NUMBER NOT NULL, + &quot;login&quot; NVARCHAR2(255) NOT NULL, + &quot;passwd&quot; NVARCHAR2(255) NOT NULL, + &quot;nick&quot; NVARCHAR2(255) NOT NULL, + &quot;phone&quot; NVARCHAR2(255), + &quot;head_img&quot; NVARCHAR2(255), + &quot;status&quot; NVARCHAR2(255), + &quot;remark&quot; NCLOB +); + +-- 删除原有序列 +DROP SEQUENCE &quot;TEST&quot;.&quot;USER_SEQ&quot;; +-- 创建 USER_SEQ 序列,最小值为 1 +CREATE SEQUENCE &quot;TEST&quot;.&quot;USER_SEQ&quot; +-- 最小值为 1 +MINVALUE 1 +-- 最大值为 9999999999999999999999999999 +MAXVALUE 9999999999999999999999999999 +-- 每次增加 1 +INCREMENT BY 1 +-- 将 20 个序列值放入缓存 +CACHE 20; + +-- 创建触发器 +CREATE TRIGGER &quot;TEST&quot;.&quot;USER_TRIGGER&quot; +-- 在插入数据前执行 +BEFORE INSERT ON &quot;TEST&quot;.&quot;USER&quot; +-- 命名老数据为 OLD,新数据为 NEW +REFERENCING OLD AS &quot;OLD&quot; NEW AS &quot;NEW&quot; +-- 针对表的每一行都执行触发器 +FOR EACH ROW +-- 将序列值赋值给 id +BEGIN + :NEW.&quot;id&quot; := USER_SEQ.NEXTVAL; +END; +/ + +需要注意的是,上面的/符号不能少。执行插入语句时可以发现,id会自动增加。 +INSERT INTO &quot;TEST&quot;.&quot;USER&quot; (&quot;gmt_create&quot;, &quot;gmt_modified&quot;, &quot;is_deleted&quot;, &quot;login&quot;, &quot;passwd&quot;, &quot;nick&quot;, &quot;phone&quot;, &quot;head_img&quot;, &quot;status&quot;, &quot;remark&quot;) VALUES (TO_DATE('2023-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), TO_DATE('2023-04-01 17:04:30', 'SYYYY-MM-DD HH24:MI:SS'), '0', 'user', '123', 'Jack', '1111', 'head.jpg', '激活', '测试'); + +Java Spring+Mybatis 使用 Oracle 及配置分页 +application.properties文件配置信息: +# mybatis +spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver +spring.datasource.url=jdbc:oracle:thin:@8127.0.0.1:1521:helowin +spring.datasource.username=system +spring.datasource.password=system +mybatis.mapper-locations=classpath*:mybatis/*.xml +mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl + +# pageHelper +pagehelper.helperDialect=oracle +pagehelper.reasonable=true +pagehelper.supportMethodsArguments=true +pagehelper.params=count=countSql + +pom.xml配置文件关键信息。 +&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; +&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; + xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt; + + &lt;properties&gt; + &lt;java.version&gt;1.8&lt;/java.version&gt; + &lt;maven.compiler.target&gt;1.8&lt;/maven.compiler.target&gt; + &lt;maven.compiler.source&gt;1.8&lt;/maven.compiler.source&gt; + &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt; + &lt;project.reporting.outputEncoding&gt;UTF-8&lt;/project.reporting.outputEncoding&gt; + + &lt;spring.boot-version&gt;2.1.3.RELEASE&lt;/spring.boot-version&gt; + &lt;/properties&gt; + + &lt;dependencyManagement&gt; + &lt;dependencies&gt; + &lt;dependency&gt; + &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; + &lt;artifactId&gt;spring-boot-dependencies&lt;/artifactId&gt; + &lt;version&gt;${spring.boot-version}&lt;/version&gt; + &lt;type&gt;pom&lt;/type&gt; + &lt;scope&gt;import&lt;/scope&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt; + &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt; + &lt;version&gt;2.1.0&lt;/version&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;com.oracle.ojdbc&lt;/groupId&gt; + &lt;artifactId&gt;ojdbc8&lt;/artifactId&gt; + &lt;version&gt;19.3.0.0&lt;/version&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt; + &lt;artifactId&gt;pagehelper-spring-boot-starter&lt;/artifactId&gt; + &lt;version&gt;1.4.3&lt;/version&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt; + &lt;artifactId&gt;pagehelper-spring-boot-starter&lt;/artifactId&gt; + &lt;/dependency&gt; + &lt;/dependencies&gt; + &lt;/dependencyManagement&gt; +&lt;/project&gt; + + +
+ + Read More ~ +
+
+
+ +
+

+ + Java 垃圾回收机制详解 + +

+ +
+ + + + +
+ +
+ 最近主要时间都放在知识图谱构建中,但是还是需要给自己充电。想在近段时间好好把 JVM 的垃圾回收好好看一下,学习然后输出,是我新找到的有效学习方法,希望你看了之后能有收获。 +什么是垃圾 +垃圾回收(常称做GC——Garbage Collection)诞生于1960年 MIT 的 Lisp 语言,垃圾回收机制也已经用在了很多编程语言中,比如 Java、Python、C# 等。我们这里主要说 Java 中的垃圾回收。 +在JVM中,程序计数器、虚拟机栈、本地方法栈都是随线程生而生,随线程灭而灭;栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理;常说的垃圾回收主要集中在堆和方法区,这部分内存是随着程序运行动态分配的。 +既然要回收垃圾,那么我们首先需要知道的就是,什么样的对象是垃圾。一般有两种方式: +引用计数 +每个对象有一个引用计数属性,新增一个引用时计数加 1,引用释放时计数减 1,当引用计数变为 0 的时候,这个对象就可以回收了。但是这个方法无法解决对象循环引用的问题。 + // 对象循环引用示例 + + Object objectA = new Object(); + Object objectB = new Object(); + + objectA.instance = objectB; + objectB.instance = objectA; + + objectA = null; + objectB = null; + +假设我们有上面的代码。程序启动后,objectA和objectB两个对象被创建并在堆中分配内存,它们都相互持有对方的引用,但是除了它们相互持有的引用之外,再无别的引用。而实际上,引用已经被置空,这两个对象不可能再被访问了,但是因为它们相互引用着对方,导致它们的引用计数都不为 0,因此引用计数算法无法通知GC回收它们,造成了内存的浪费。如下图:对象之间的引用形成一个有环图。 + +可达性分析 +或者叫根搜索算法,在主流的JVM中,都是使用的这种方法来判断对象是否存活的。这个算法的思路很简单,它把内存中的每一个对象都看作一个结点,然后定义了一些可以作为根结点的对象,我们称之为「GC Roots」。果一个对象中有另一个对象的引用,那么就认这个对象有一条指向另一个对象的边。 + +像上面这张图,JVM会起一个线程从所有的GC Roots开始往下遍历,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收。(这里多说一句,我们的JVM一起动,就至少有两个线程启动,一个是垃圾回收线程,一个是我们自己的主线程。) +那么现在问题就变成了——什么样的对象可以当作 GC Roots?共有四种对象可以作为 GC Roots。 +虚拟机栈中的引用的对象 +们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种对象可以作为 GC Roots。 +全局的静态的对象 +也就是使用了static关键字定义了的对象,这种对象的引用保存在共有的方法区中,因为虚拟机栈是线程私有的,如果保存在栈里,就不叫全局了,很显然,这种对象是要作为 GC Roots 的。 +常量引用 +就是使用了static final关键字,由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也作为GC Roots。 +本地方法栈中JNI引用的对象 +有时候单纯的java代码不能满足我们的需求,就可能需要调用 C 或 C++ 代码(Java 本身就是用 C 和 C++ 写的嘛),因此会使用native方法,JVM 内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为 GC Roots。 +垃圾回收算法 +有意思的是在 JVM 规范中,并没有明确指明 GC 的运作方式,各个厂商可以采用不同的方式去实现垃圾收集器。这篇文章简单介绍常见的垃圾回收算法。 +标记-清除算法 +标记-清除算法分两个步骤,分别为「标记」和「清除」,字如其人。它是一个最基础的垃圾回收算法,更高级的垃圾回收算法都是基于它改进的。 +它的运行过程是这样的:首先标记出所有需要回收的对象,标记完成后,再统一回收掉所有被标记的对象。 + +标记-清除算法的缺点有两个,一个是空间问题,标记清除之后会产生大量的不连续内存碎片。内存碎片太多,程序在之后的运行过程中就有可能找不到足够的连续内存来分配较大的对象,进而不得不提前触发另一次垃圾回收,导致程序效率降低。标记-清除算法的另一个缺点是效率问题,标记和清除的效率都不高,两次扫描耗时严重。 +复制算法 +复制算法把内存按容量划分为大小相等的两块,每次只使用其中的一块。如果正在用的这块没有足够的可使用空间了,那么就将还活着的对象复制到另一块去,再把使用过的内存一次性清掉。 + +这样就实现了简单高效的做法,每一次进行内存回收时,就不用再去考虑内存碎片这些复杂的情况,只需要移动堆顶指针就可以。但是缺点也很明显,可使用内存只有原来的一半了,而且持续复制生命力很旺盛的对象也会让效率降低哇。复制算法适用于存活对象少、垃圾对象多的情况,这种情况在新生代比较常见。 +标记-压缩算法 +在老年代,大部分对象都是存活的对象,复制算法在这里就不靠谱了,所以有人提出了标记压缩算法,标记过程和标记清除算法一样,但是清理时不是简单的清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存,需要移动对象的成本。 + +分代算法 +前面的几种垃圾回收算法中,没有一种可以完全替代其他算法,他们具备各自的特点与优势,因此更好的方法是根据垃圾对象的特性来选择合适的回收算法。 +分代算法的思想就是将内存空间根据对象的特点不同进行划分,选择合适的垃圾回收算法来提高回收效率。分代的思想已经被现有的虚拟机广泛采用。 +分区算法 +分区算法就是将整个堆空间再划分为连续的不同小区间,每一个小区间独立使用,也独立回收。 + +一般在相同条件下,堆空间越大,那么一次GC的时间就越长,因此而产生的停顿时间也就越长。为了控制GC的停顿时间,根据目标停顿时间,每次合理回收若干个小区间,而不是整个堆空间,进而减少一个GC的停顿时间。 +垃圾收集器 +上面讲的是垃圾收集算法,讲的是理论,垃圾收集器就是这些理论的具体实现。下面介绍一些垃圾收集器 +Serial收集器 +串行收集器是高效率的、古老的收集器,它只用了一个线程去回收垃圾。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩算法。串行是指 GC 过程和用户过程是串行的,在垃圾收集过程中会 stop the world,JVM在后台自动发起垃圾回收,会在用户不可见的情况下,把用户的线程全部停掉,就是 GC 停顿,给用户带来不良体验。 +红色代表 GC 线程,灰色代表用户线程,下同。 + +ParNew收集器 +ParNew 收集器就是 Serial 收集器的多线程版本,除了多线程以外,其余行为都和 Serial 收集器一样。新生代并行收集,老年代串行收集;新生代使用复制算法、老年代使用标记-压缩算法。 + +Parallel Scavenge收集器 +Parallel Scavenge 收集器类似于 ParNew 收集器,因为与吞吐量密切,也称为吞吐量收集器。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制 GC 的时间不大于多少毫秒或者比例。Parallel Scavenge 收集器以高吞吐量为目标,减少垃圾收集时间,让用户代码获得更长的运行时间;GC 停顿时间的缩短,是用牺牲吞吐量和新生代空间来换取的。 +Parallel Old收集器 +Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,使用多线程和「标记-压缩」算法,在 JDK1.6 版本才开始提供。 +CMS收集器 +CMS(Concorrect mask sweep)收集器是一种以获取最短停顿时间为目标的收集器;也称为并发低停顿收集器。常用在 WEB、B/S 架构的服务系统中,因为这类应用很注重响应速度,尽可能减少系统停顿时间,给用户带来较好的体验。从名字上就可以看出来,它是基于「标记-清除」算法实现的,整个过程分为 4 步: +初始标记 +初始标记仅仅标记 GC Roots 能直接关联到的对象,所以速度很快,需要停止服务(Stop The World)。 +并发标记 +并发标记是进行 GC Roots Tracing 的过程,为了标记上一步集合中的存活对象,因为用户程序这段时间也在运行,所以并不能保证可以标记出所有的存活对象。 +重新标记 +重新标记阶段是为了修正并发标记阶段因用户程序继续运作而导致标记变动的那一部分对象,采用多线程并行来提升效率,会停止服务,时间上远比并发标记短,较初始标记稍长。 +并发清除 +这个阶段即并发收集垃圾对象,可以与用户线程一起工作。 +虽然 CMS 收集器线程可以和用户线程一起进行,但是它肯定会占用 CPU 资源,拖慢应用程序是肯定的,总的吞吐量会降低。 + +G1收集器 +(看下垃圾回收算法中的分区算法)这是目前最新的前沿成果,它基于“标记-压缩”算法,可以进行空间整理,不会产生碎片。前面的垃圾收集器,收集范围都是整个新生代或老年代,但是 G1 收集器不是这样,使用 G1 收集器时,java堆的内存模型不同,它还保留有新生代和老年代的概念,它们都是一部分区域(可以不连续)的集合。除此之外,G1 收集器还能建立可预测的停顿时间模型,可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒。G1 跟踪各个区域(Region)获得其收集价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。G1 垃圾回收也分4步: +初始标记 +仅标记 GC Roots 能直接关联到的对象。 +并发标记 +进行 GC Roots Tracing 的过程,并不能保证可以标记出所有的存活对象。这个阶段若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收。 +最终标记 +为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录,G1 中采用了比 CMS 更快的初始快照算法: snapshot-at-the-beginning (SATB)。 +筛选回收 +首先排序各个 Region 的回收价值和成本,然后根据用户期望的 GC 停顿时间来制定回收计划,最后按计划回收一些价值高的 Region 中垃圾对象,回收时采用&quot;复制&quot;算法,从一个或多个 Region 复制存活对象到堆上的另一个空的 Region,并且在此过程中压缩和释放内存。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何抽取实体关系?——基于依存句法分析的事实三元组抽取 + +

+ +
+ + + + +
+ +
+ +参考: +HanLP 自然语言处理 +基于依存分析的开放式中文实体关系抽取方法 +命名实体三元组抽取参考自fact_triple_extraction + +这一段时间一直在做知识图谱,卡在实体关系抽取这里几个月了,在 Github 上面看到有人使用卷积神经网络训练模型进行抽取,自己也尝试了一下,但是一直苦于没有像样数据去训练,而标注训练集又太费时间了,我不太愿意干体力活。另外自己也不会什么机器学习、深度学习之类的技术,而且毕业设计都是有时间要求的,所以采用了一个低档次的方法,基于依存句法分析的实体关系抽取,记录一下心得,方便日后忘记可以再找回来。 +论文给出了 8 种中文关系的表达方式,并且最后给出了一个采用正则表达式语法指出表达,核心就是谓语动词表示关系,即关系表述中一定得有动词。 +状语*动词+补语?宾语? + +我不太赞同把宾语也当作关系表述的一部分,论文指出“p4生于山西”应该抽出(p4,山西,生于山西),我认为关系不应该表述为“生于山西”,所以我把关系表述改为下面的样子了。 +状语*动词+补语? + +这篇文章只是作为一个方法介绍,我自己先看了一遍,能够保证我下次看到这篇文章,可以立马回忆起自己的实现方法,希望你看了也能了解方法,看不懂的话,我表示抱歉,浪费您的时间了,我已经尽可能写到简单了。 +先来看几个简单句子吧: +主谓宾关系:刘小绪 生于 四川 +// 这个三元组很明显:(刘小绪,生于,四川) + + +动补结构:刘小绪 洗 干净 了 衣服 +// 如果套用主谓宾关系就是:(刘小绪,洗,衣服) +// 但是这里描述的是一个状态,是刘小绪把衣服洗干净了 +// “干净”是动词“洗”的补语,所以还应该提取出一个如下三元组 +// (刘小绪,洗干净了,衣服) + +状动结构:父亲 非常 喜欢 跑步 +// 这句和上面很像,主谓宾关系是:父亲喜欢跑步 +// “非常”用于修饰“喜欢” +// (父亲,非常喜欢,跑步) + +介宾关系:刘小绪 就职 于 学校 +// 如果直接把这个三元组抽取为(刘小绪,就职,学校),很别扭 +// “于”和“学校”是介宾关系,它们的关系应该是:就职于 +// (刘小绪,就职于,学校) + +宾语前置:海洋 由 水 组成 +// “海洋”是“组成”的前置宾语 +// “由”是“组成”的状语 +// “水”和“由”是介宾关系 +// 所以上面的句子没有明确的主谓关系,需要我们判断 +// 抽出的三元组应该为:(水,组成,海洋) + +HanLP 提供了两种依存句法分析的器,默认采用的是基于神经网络的依存句法分析器。依存句法分析就是将句子分析成一棵依存句法树,描述各个词语之间的依存关系,即指出词语之间在句法上的搭配关系。 +有了上面所说的依存句法树,其实我们只需要进行各种判断就可以了。先做出下面的一点说明,就拿第一个例子来说。 +原文:刘小绪生于四川 + +# 这是分词结果 +[刘小绪/nr, 生于/v, 四川/ns] + +#这是句法分析结果 +刘小绪 --(主谓关系)--&gt; 生于 +生于 --(核心关系)--&gt; ##核心## +四川 --(动宾关系)--&gt; 生于 + +为了方便理解,也为了方便程序的编写,我把他们组织成了下面的形式,为每一个词语都建一个依存句法字典。 +刘小绪:{} +生于:{主谓关系=[刘小绪], 动宾关系=[四川]} +四川:{} + +然后只需要写出类似于下面的程序段就可以抽出关系了。 +// 主谓宾关系:刘小绪生于四川 +// dic是这个词语的依存句法字典 +if (dic.containsKey(&quot;主谓关系&quot;) &amp;&amp; dic.containsKey(&quot;动宾关系&quot;)){ + + // 当前的词语,用上面的例子来说,relation=“生于” + String relation = curWord.LEMMA; + + + // 用循环遍历,是因为关系列表里面不一定只有一个词语 + for (CoNLLWord entity1: + dic.get(&quot;主谓关系&quot;)) { + + for (CoNLLWord entity2: + dic.get(&quot;动宾关系&quot;)) { + + System.out.println(entity1.LEMMA + &quot;,&quot; + relation + &quot;,&quot; + entity2.LEMMA); + } + + } +} + +对于分词后的每个词语都进行上面程序段的操作。“刘小绪”和“四川”,关系字典都为空。而对于“生于”,关系列表里面既有主谓也有动宾,而自己本身就是动词,主谓宾就出来了。直接从主谓关系中拿出来词语作为 entity1,再拿上自己作为关系,最后拿出动宾关系中的词语作为 entity2。很明确的三元组(刘小绪,生于,四川)就出来了。 +最后给出一个程序运行结果图吧。 + +我个人觉得效果还行,在简单句子上面表现的差强人意,在长句子上面表现的差劲。注意上文使用的第三方包随着时间的推移肯定会改一些接口,源码链接:entity_relation_extraction + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/hVl3D7IfG/index.html b/hVl3D7IfG/index.html new file mode 100644 index 00000000..32ce353f --- /dev/null +++ b/hVl3D7IfG/index.html @@ -0,0 +1,473 @@ + + + + + + + + 一个通过建网站赚钱的暴利项目(灰产勿碰) | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 一个通过建网站赚钱的暴利项目(灰产勿碰) +

+ + +
+ +
+

这个项目的前提是你已经有了一个可以发布文章的网站,网站的主要就是收集投诉相关内容,如果你还不知道怎么去建一个网站,可以看我之前发的如何搭建一个属于自己的博客/企业网站,按照文中的方法建一个网站的成本投入不到 150 元。

+

网站建起来了怎么去赚钱呢?相信大家都会想通过 SEO 把自己网站排名优化到足够高,然后利用谷歌联盟或是百度联盟赚广告费,没事的时候更新更新文章,只要保持一个比较好的更新频率,并且能长期坚持下来,那肯定是可以赚钱的,而且收入也会逐渐递增甚至可能呈现指数型增长。

+

此时得承认我有点标题党了,这个快速赚钱的套路属于灰产,因为我作为证人协助警察叔叔抓获了一个做这件事情的站长。下面进入正题。

+

现在有一些专门做维权的平台,比如聚投诉、新浪旗下的黑猫投诉等,可以进去看看这样的平台可能在维权方面起不到多大的作用,但是它随随便便就能上百度的首页啊。谁最害怕网络上的负面信息?肯定是被投诉公司啊!在信息时代,一条负面信息不知道要损失多少客户。

+

我说的就是做一个类似的网站,首先网民都喜欢看这一类的内容,另外这样的网站也很容易进来访问量,有了访问量那么广告费就是一笔不菲的收入,比如你可以去搜「笑捧博客」看下里面的内容,我截了个图放在这里,先告诉你的是它最大的收入并不是广告费。

+
+

鉴于上面的信息都是网络上公开的内容,这里我就不打码了。可以看到这个网站里面的绝大部分内容都是 XXXXXXX公司-警惕,里面就是简单描述一下事情经过,然后放个合同的照片、再放几张聊天截图,而且这些内容都是受骗用户自己投稿的,完全不用花时间自己去创造内容。

+

假设某客户早这个平台上面投诉了 A 公司,A 公司的人看到在百度轻易就能搜到不利于自己公司的信息,想要快速删除这样的内容怎么办?顶部特地留了个「联系站长」看见了吗?假设现在你就是这个站长,你可以像下面那样给对方回话:

+
+

平台的内容都是用户自己发的,我这边会跟进用户投诉的进度,你这边有和客户协商处理的聊天记录吗?或者有给客户的退款记录也行,我去核实如果无误就把这篇帖子屏蔽掉。

+
+

都把客户逼到去网上发负面信息了,逼到客户去投诉公司了,基本上这样的公司不会有和客户协商处理的聊天记录,不到万不得已他们是不会给客户退款的,一般对方都会回答没有,此时你可以这样说:

+
+

你需要马上处理这个帖子可以先交 600 元的押金,我这边先去后台设置不展示这篇内容,你那边抓紧时间去处理,处理好了联系我退换押金即可。

+
+

到这里就玩概率了,如果公司很快的就把这个事情处理了,那么对方找你这个站长退还押金也不能不给是不是?但就是有很多公司在一个月内都没有把这样的客户处理好,因为他们本身做的就是割韭菜项目,怎么会轻易退客户钱呢?过了一个月后可以这样说:

+
+

当时给你说的是一个月内处理,与客户沟通处理的聊天截图、退款记录发给我,这边去删除帖子!时间有点久,而且后台设置的一个月就会清理一次数据,进程什么的都已经死了............

+
+

简单说就是各种扯皮各种赖,几百块钱对方应该也不会太在意,对方顶多骂你两句也拿你没什么办法,是不是轻轻松松的 600 元就到手了!!!

+

最后再次强调一点,这个被警察叔叔发现了是要来请你的,而且自己搭的网站没有官方授权,都知道投诉电话是 12315,你个人的一个博客网站凭什么能接收这种投诉?再去看看这个「笑捧博客」的服务器在香港,而且这个网站的所有内容最后更新时间是 2020 年 9 月份,知道为什么吗?

+

因为这个站长已经被公安局请进去了!

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/hnydo1Gn3/index.html b/hnydo1Gn3/index.html new file mode 100644 index 00000000..2eb0dab5 --- /dev/null +++ b/hnydo1Gn3/index.html @@ -0,0 +1,506 @@ + + + + + + + + 大学生可以尝试操作的项目 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 大学生可以尝试操作的项目 +

+ + +
+ +
+

很多大学生第一次远离爸妈的怀抱都是因为上大学,刚好此时也已经是 18 岁的成年人了,从小耳濡目染的 18 岁成人礼撬动着内心那颗渴望独立的心,通过自己能力赚到钱放在手里那一刻的感觉,人类的文字已经无法描述那一刻的美妙了。

+

估计也有不少学生都被一些网上兼职网站给坑过(本人也被坑过),满怀期待的在某个兼职网站提交了自己的个人信息,不一会儿对方就打电话过来和你确定一个时间去面试,此时心中的你是不是还在想自己要准备什么啊?从来没有面试经验的自己能通过面试吗?

+

到了现场才发现所谓的面试不过只是让你交几百块钱给他们,他们给你在网站上面注册一个账号,这样你就可以去网站上面领取兼职工作了,虽然交了几百块钱有割心头肉的痛感,但是想想多做几天兼职这些钱就挣回来了,而且还能挣更多的钱,心中又充满了期望。

+

本文本着童叟无欺的价值观,良心分享几个线下必赚项目。

+

收集毕业生的被子

+

学校每年都会有大批的毕业生走出校园,本科、硕士(博士一般都在外面有房子)加起来少则几千多则几万,这些学生基本都从外地到学校就读的,每一年这些学生一走都会留下大量的被子等在宿舍,还给给后勤集团留下一大堆的烦恼,那为啥不帮助后勤集团去解决这个烦恼顺道再赚点钱呢?

+

先辛苦一下去外面的酒店了解他们是不是需要这些被子(不要把酒店的服务想象的那么好,我好像又透露了点什么),别去那种太高端的酒店就行,多问几家比一比价格,别忘了顺道也问下路过的棉花厂。做好记录,毕竟好记性不如烂笔头嘛!

+

评估一下在自己学校的可行性,价格比较完了觉得可以做那就开始拉上自己的哥们干!为了省点钱可以先找后勤集团领导问问,现在可以为他解决毕业生离校后宿舍被子的问题,可否把这个项目作为勤工俭学工作给点钱,就算不给钱可以不可以给出个车费把被子拉到指定地点。当然如果人家不愿意那就自己出点车费钱啰。

+

后面要做的事情就不用我多说了吧!去每个宿舍询问一下,把被子抱走就行,毕业生是陆陆续续走的,所以每个宿舍楼记得多跑一两遍。过程是比较累的,坚持下来赚到一笔可供挥霍的基金绝对没问题。

+

给宿舍提供零食

+

想象一下自己打游戏打到大半夜饿肚子的感觉,此时楼下的小卖部已经关门了,去校外的超市又太冷、太远了,这一点在北方尤其常见。在南方生活的我有一次肚子饿了,跑到学校的腐败街希望能找点吃的,结果基本上的商家都关门了,要知道那还不到 12 点。

+

可能也有同学想过甚至实践过这个项目,我自己同班同学也拉着自己的哥们实践过,但是他们并没有做到多好,并没有赚到多少钱,所以下面说几个值得注意的地方!

+

选品很重要,先选比较容易出手的薯片、肥宅快乐水、辣条等,水果可以留着下一步做,因为保质期不够长!给同学送货的时候一定不要见到是熟人就瞎唠嗑,那样会浪费给其它顾客送货的时间。能加微信记得把顾客和他室友的微信加上,这样方便收集大家的需求和意见,微信和 Excel 能解决 90% 以上的问题。比较重要的一点是记得不定时做做活动,比如从已下单客户种抽取第二日免单优惠,满 30 元送一瓶可乐等等,要让顾客真正体验到在你这里购买的便捷与实惠。

+

有了上面的基础之后你就可以去和货源老板压价了,当你的流量足够大的时候甚至可以和老板谈判先卖出去在付货款,只有把自己的成本压到足够低才有利润可图,这个项目主要看的是执行力!

+

给新生办电话卡

+

这个相信很多同学都多少听过或者尝试过,每年三大运营商的抢人大战尤其激烈,我入学的时候中国移动直接疯狂到给学生免费送卡(那时候实名制还没那么严),在暑假快放假的时候去学校的各个营业厅了解一下情况,问问他们办一张卡都有多少提成之类的(我当时是一张卡 15 元),如果能同时拿到是那个运营商的卡最好,如果拿不到那就拿中国移动的卡(这个也有例外,比如我学校的楼就很神奇,基本每一栋楼里面都接收不到移动信号,图书馆能屏蔽一切信号,因地制宜也很重要)。

+

去哪里拉客户呢?新生报到处对不对?错了!你的同行都会想到新生报到处,竞争比较大。新生报到处都是办理各种手续对不对?那里也没有桌子什么的方便填写资料,而且新生报到处是第一站,后续的流程还没走完人家也没那耐心给你这里耗,所以新生报到处绝对不是最佳地点。

+

哪里是最佳的地点呢?宿舍。新生宿舍总共就那么三四栋楼,会呆在宿舍的新生基本都是把各种手续已经跑完了,爸妈正陪着他铺床或是简单歇歇就去吃饭呢,正处于一个放松休闲的状态。这时你去给他说你这里有学生优惠的卡,对方是不是该很高兴?只要他们还没有办卡,那他们吃完饭也会去办电话卡的。

+

组织租车

+

这个项目得根据学校的实际情况了,有些学校所处的位置就很适合这样的项目。比如中北大学,在一个离市区很远的村子里面,周末大家都想去市里玩,我去那里看女朋友的时候可遭罪了,虽然有公交但是超级挤啊,所以很多学生宁愿多花几块甚至十多块去坐黑车。何况有的学校还没有公交。

+

那些司机师傅也很焦灼,一面是已经坐上车的顾客在死命的催他,一面是车上还没有坐满跑一趟划不来,旁边还有很多同行在和他竞争,这样的场面即使你没有在学校见过相信也在车站见过吧!所以这个项目就是在司机与同学之间搭起一座桥梁。

+

还是上面的话,微信和 Excel 可以解决 90% 以上的问题,你一遍把班次定好让大家交钱预约,一边和司机师傅联系,保证发车的准时性。这个项目在特定环境是刚需,所以很容易在同学之间传开,再稍加一点活动优惠基本就躺赚了,我看到有利用这个项目赚了好几十万的!

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/images/avatar.png b/images/avatar.png new file mode 100644 index 00000000..b4b65e2f Binary files /dev/null and b/images/avatar.png differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..cfd8bcd8 --- /dev/null +++ b/index.html @@ -0,0 +1,2854 @@ + + + + + + + + Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ + +
+

+ + LDO 基础知识 + +

+ +
+ + + + +
+ +
+ +参考内容: +ADI 公司 LDO 电容选型指南 +线性和低压降 (LDO) 稳压器 + +BUCK 电路通过控制占空比来达到降压的目的,添加 LC 二阶低通滤波器将高频部分滤除,即可达到稳定输出直流的目的。但是滤波不能完全滤除高频分量,BUCK 从原理上就决定了其纹波不容易做到很小,其固有的开关频率会导致电源噪声很大,用来给噪声敏感的元器件供电就不合适。 +相比 BUCK 来说,LDO(Low Dropout Regulaor:低压差线性稳压器)输出的电压会更加平稳,可以弥补 BUCK 输出纹波大的缺点。 +总体框图 +线性稳压器主要由四部分组成,基准源用于提供精准的电压基准、导通器件用于控制从 VIN 到 VOUT 的电流大小、误差放大器将强制反馈节点与基准电压匹配、反馈电阻用于调整以改变输出电压。 + +从框图中也可以看到线性稳压器只能用于降压,因此输入电压必须高于输出电压。当然其名字中本身带了低压差的,低压差就意味着少的发热,意味着电源转化效率的提升。线性则是指器件的工作状态,器件的内部模块工作在放大区,放大状态呈线性关系。 +工作原理 +线性稳压器的工作可以模拟为两个电阻器和一个用于 VIN 的电源,其中电源用于给负载供电,通过调整可变电阻(导通器件)的阻值来控制负载电阻所获得的电压,整个系统中唯一不变的恒定的参数就是输出电压 VOUT。 + +其稳压过程如下图所示,当负载电压升高/降低时,采样电路所采到的电压就跟着升高/降低,传递给误差放大器后通过调节导通器件的导通程度来调节输出电压。 + +导通器件 +导通器件常见的有 PMOS、NMOS、BJT 等。BJT 应用于大电流的场景。PMOS 不需要额外的电源轨即可控制其导通程度,但是相比 NMOS 其 RDSon 更大,即 PMOS 架构的 LDO 在芯片本身所消耗的能量会更大。 + +使用 NMOS 作为导通器件时,需要添加辅助电源轨或者使用电荷泵才能将 NMOS 打开。当然电荷泵也有其缺点,虽然电荷泵可以提升 VIN,但是也带来了额外的噪声影响。若采用辅助电源轨时则需要注意,VBIAS 会影响 NMOS 的导通程度,进而影响输出电压的大小。 + +PSRR +PSRR(Power Supply Rejection Ratio)量化了 LDO 抑制任何电源变化传递到其输出信号的能力,也就是 PSRR 决定了输入耦合到输出的噪声有多少。除了 LDO 本身的设计影响 PSRR 外,也可以通过调整 VIN 与 VOUT 之间的差值、输出电容来提高在特定应用(频率)下的 PSRR。 + + +输入输出电容 +为了确保 LDO 稳定工作,会在 LDO 输入输出端增加旁路电容,并且旁路电容的 ESR 需要很小,即在符合最小电容和最大 ESR 的要求下,使用任何质量良好的电容都可采用。在选择电容时还需要注意由于直流电压偏置、温度变化、制造商容差等需要对电容进行一定的降额。 +输出电容除了可以进行滤波外,还会影响负载电流的变化的瞬态响应,采用较大的输出电容可以改善 LDO 对大负载电流变化的瞬态响应。输入电容则可以降低电路对 PCB 布局的敏感性,尤其是在长输入走线或者高源阻抗的情况下。 +多层陶瓷电容、固态钽点解电容、铝电解电容通常用作输入和输出旁路电容。多层陶瓷电容具备 ESR 和 ESL 低、工作温度范围宽的优点,但是陶瓷电容中的介质材料具备压电性,振动或机械冲击可能会转化为电容上的交流噪声电压,在极端情况下可能会产生 mV 级的噪声。 + +压电性是在某些固体材料(晶体、陶瓷、骨头、DNA、蛋白质等)受到机械应力作用后,在材料中聚集电荷的现象。「压电」即由压力产生的电。 + +钽电容的优点是单位体积电容最高(CV 乘积),并且不太容易受到温度、偏执电压、震动效应的影响,在无法容忍压电效应的低噪声应用中,钽电容基本是唯一可行的选择。与陶瓷电容相比,钽电容的泄漏电流要比等值的陶瓷电容大很多倍,不适合超低电流应用。 +铝电解电容往往体积较大、ESR 和 ESL 较高,漏电流相对较高,与钽电容一样不受压电效应影响,适合要求低噪声的应用场合,但是铝电解电容在航天应用中禁止使用。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 半导体功率器件 + +

+ +
+ + + + +
+ +
+ 高中时候我们在化学课程中学过元素周期表,「氢氦锂铍硼、碳氮氧氟氖......」倒背如流,在元素周期表的中间三、四、五族元素定义为半导体元素,所谓半导体是根据其导电能力来定义的,我们可以通过一定的半导体工艺来改变其导电能力。 +以硅(Si)为例,硅是处在第四族的元素,它的外部有 4 个电子,所以硅的稳定结构是形成下图所示的及其稳定的共价键结构。 + +硅的两侧对应的是三族和五族的元素,三族的元素意味着外层有 3 个电子,五族的元素意味着外层有 5 个电子。如果把三族元素插入到四族的硅当中,由于硅想形成稳定的四个共价键结构,所以会存在一个空置的位置,我们称之为空穴,这个空穴是具备一定的正电荷的能力的,如此就形成了 P 型半导体。 + +同理,若使用五族元素与硅进行掺杂,就会多出一个可移动的电子,即存在自由电荷,形成了 N 型半导体。 + +PN 结(普通二极管) +当我们把 P 型半导体和 N 型半导体进行组合后,即可得到最基本的二极管(PN 结)。在 P 型半导体中存在高浓度的空穴(正电荷),在 N 型半导体中存在高浓度的电子,浓度高的载流子会自然而然向浓度低的区域进行扩散。 +由于载流子扩散,最终会形成一个势垒,这是一个空间电荷区,也称之为耗尽层。可以发现 PN 结存在 P 区、耗尽层、N 区三个区域,这几个区域都是呈现电中性的,不管是空穴还是电子想要到达另一个区域,都必须要穿过耗尽层,即耗尽层会阻碍空穴和电子的运动,因此整个 PN 结在没有外界干扰的情况下,是不具备导电能力的。 + +当我们从外界施加 N 到 P 的电场时,即 PN 结反偏。此时外界电场与耗尽层电场是同向的,所以在外部电场的作用下,耗尽层的宽度会被加强,于是 PN 结的导电能力就变得更弱,因此就呈现了一个无导电能力的特性。 +当然导电只是一种相对情况,即便空间电荷区变宽了,也不能百分百保证说就完全没有导电能力,因为还是有一定的空间电荷浓度,在这样的情况下会有微弱的电流流经 PN 结,意味着系统存在一个反向电流,这就是二极管一个比较重要的漏电流参数。 + +当外部施加的电场是从 P 到 N 时,即 PN 结正偏。外界电场的效果是使耗尽层变窄,加强了 P 区内空穴往 N 区内移动的能力,扩散电流远大于漂移电流,形成了一个正向导通电流。 + +最终二极管将呈现如下的导通特性,当正向电压大于势垒电压时,二极管开始导通。当施加反向电压时,二极管将截止,当反向电压大到一定程度后,二极管就会被反向击穿,即二极管损坏的过程。 + +功率二极管 +既然谈到了「功率」二字,那么更加关注的就是二极管承载电流、电压的能力了。如何把二极管承载电流、电压的能力加强呢?根据上文关于二极管的介绍可以知道,将耗尽层加宽可以承载更大的电压。 +图中中间 n- 为轻度参杂区域,下面 n+ 为重度参杂区域,这个参杂就导致了耗尽层的加宽,当然也导致导通损耗更大,不过也正因为如此,功率二极管才更加能耐压。 + +我们以非同步 BUCK 电路为载体,来说明一下功率二极管的变化过程。 + + +图中(1)部分指二极管导通,有一个小小的二极管导通压降,因此曲线没有贴着 x 轴; +图中(2)的位置由于二极管承受的是反向电压,此时它关断了,所以电压为负; +图中(3)二极管需要经历一个从没有电压到有外加电压的变化,当电压加到二极管上时,二极管中的载流子流动的趋势逐渐增大,宏观表现出来是电阻慢慢变小的过程,但是电流保持不变,所有会有一个小尖峰,这一小段时间也会导致整体功率的损耗,开关频率越高,这个导通过程导致的损耗越多; +图中(4)处伴随系统从通到断的状态变化,大规模载流子需要进行重新分配,这个重新分配表现出来就是电流,而且这个电流与主电流相反,所以会看到一个反向的电流,而且这个反向电流会施加在主电路里面。这一段反向电流又分为两部分,下降阶段是之前外加电压时,PN 结中从 P 区域移动到 N 区域的载流子移除(恢复)过程,即从正偏到反偏的过程,正偏时空间电荷区非常非常窄,此时要进入反偏状态,空间电荷区需要加强,载流子需要重新分配,外部激励会移除不必要的空间电荷。电流上升的过程,即二极管又变成一个耐压器件了,也就是空间电荷区加宽,更多的载流子会不均匀的分布在两端。整个过程不可避免的需要移动电荷,而电荷的聚集效应可以认为就是一个电容的效应,当我们需要施加电压时,电压的增加就会需要额外的电荷,电荷不断聚集提供相反电荷,使其电压不断增加,以致增加到刚好截止输出电压为止。 + +MOS 管 +以 NMOS 为例,它以 P 型半导体衬底,以 N 型半导体作为导电沟道,金属部分作为栅极(Gate),氧化部分(SiO2)作为绝缘层,两端分别为源极(Source)和漏极(Drain),从物理结构可以看出 MOS 管的源极和漏极是可以互换的,不像三极管有严格的顺序。 +在栅极和源极施加电压,随着电压的不断增大,导电沟道将逐渐形成,当导电沟道刚好形成时的电压,称之为开启电压。外加电压继续增大,导电沟道将变得越来越宽,即导电能力越来越强。 + +PMOS 相比 NMOS 更加容易驱动,只需要 VGS 小于一定值即可导通。但是 PMOS 的导通电阻比 NMOS 要大,并且成本也比 NMOS 要高,所以比 NMOS 的实际应用场景要少许多。 + +功率 MOS 管 + +对比前文普通 MOS 管,可以看到源极、栅极、漏极是分开的,顶上那个灰色的板子是金属板。而功率 MOS 管在这个基础上做了一点创新,下图中的阴影部分就是金属板,可以发现总共只有两个金属板,上面的金属板把 N 区和 P 区都给连起来了,所以即使在栅极没有加电压的时候,也会存在一个天然的二极管通道,但是普通 MOS 管是没有体二极管通道存在的。同时由于是功率 MOS 管,所以也会想办法将耗尽层加宽,以增加其耐压能力。 + +体二极管和耐压能力的加强是功率 MOS 和普通 MOS 的区别。 + +功率 MOS 管的正向导通能力就是涉及「场效应」了,所谓的场效应即意味着外部可以通过电场来控制其内部载流子的浓度,在栅极施加正电压时就会产生一个电子的导电沟道,由于整体是 N 型半导体衬底,所以整体也就形成了一个电子的导电沟道,并且该沟道支持电子的双向移动。 + +如下图所示是功率 MOS 管的等效电路模型。其主要损耗由三部分组成,分别为导通损耗、开关损耗(开通损耗和关断损耗)、驱动损耗。其中导通损耗与开关损耗容易理解,驱动损耗作何理解呢?MOS 并不像二极管是一个被动型器件,MOS 管开或关的行为都需要能量作为代价,就好比要打开机械开关需要用手去按压,这个过程所消耗的能量就是驱动损耗。 + +晶体管 +二极管只有一个 P 型半导体和一个 N 型半导体结合,如果再加一个 N 型半导体(或 P 型半导体)即构成了晶体管(三极管),晶体管有集电极、发射极、基极三个极。 + +需要注意的是三极管的集电区和发射区掺杂浓度是不一样的,其中基区多子少且做的很薄,而发射区的多子浓度很高,集电区多子浓度相对较低但面积大。不管三极管是正接还是反接,三极管都处于截止状态,这是因为三极管可以看作两个二极管反向相连,不论如何接都会有一个二极管处于截止状态。 + +为了能让三极管导通,我们在基极和发射极再施加一个电压,此时二极管开始导通,发射区的自由电子就可以源源不断的流向基区,但是基区的掺杂浓度很低且很薄,基区短时间内吸收不了太多的电子,只有一少部分电子能与空穴复合形成基极电流,而大部分被吸引到了集电区,形成集电极电流,也就是三极管的输出电流。 + +流过基极的电流越大,流到基区的自由电子也就越多,相应的被吸引到集电区的电子也就更多,这就是三极管小电流控制大电流的原理。基区做的很薄是为了让发射区的电子更容易进入集电区,浓度很低视为了形成更小的基极电流,这样才会有更多的自由电子流向集电区。 +IGBT +三极管工作时涉及载流子的注入和抽离所以会很慢,由于其性能的关系正在逐步退出历史舞台,因此需要对其进行改进,改进后的器件就是 IGBT,如下图所示。 + +可以发现 IGBT 是一个受 MOS 管控制的 BJT,即同时继承了 MOS 管快速和 BJT 大电流的优点。当然,它也有缺点,并且缺点主要来自于 BJT 关断较慢的问题,因为当 MOS 管门级信号撤出时,并不能立马把电流都抽走,所以电流会经历一段下降时间。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + BUCK 电路基础知识 + +

+ +
+ + + + +
+ +
+ +参考内容: +手撕Buck!Buck公式推导过程 +电力电子的基础应用 +《精通开关电源设计(第二版)》 +Buck电源芯片输出有问题?检查这几样 +原来PWM这么简单! +为什么 DC-DC 芯片设计中都有一个自举电容? +如何克服开关电源中的最小导通时间挑战 + +BUCK 电路构建 +根据高中所学习的物理知识可以很容易的想到,使用一个滑动变阻器即可实现降压和稳压的效果。当负载波动时,通过改变滑动变阻器的阻值,可以调节负载所获得的电压。但是使用滑动变阻器的劣势也很明显,大量的耗能会导致器件温度快速升高。 + + +上面所提到的电路主要缺点在于导通器件(变阻器或三极管)本身存在耗能,那么有没有不会耗能的导通器件呢?首先肯定不能选导线,不然又回到最原始的问题,所有电压都被加到负载上了。有没有能不耗能且能控制加在负载电压的导通器件呢?最常见的机械开关就能做到这个效果。 + +当开关闭合时,负载即获得电压源输出的电压;当开关打开时,负载所获得的电压为 0V。计算平均值可以确定达到了降压的目的,通过控制开关闭合的时间长短,就可以达到调节电压的效果。但仔细想想就会发现不对劲,电路并不会帮助我们计算平均值,负载所获得的电压波形如下图所示,是完美的方波,并不是一条直线。 + +控制开关闭合的时间,即后文要讲的控制占空比 + + +此时很容易就能想到利用电容两端电压不能突变的特点,给负载并联一个电容即可,电容即保证负载可以获得连续的能量流。 + +一旦引入了电容,就需要考虑浪涌电流的问题。根据公式 Q=CV=ItQ=CV=ItQ=CV=It 可得 I=CVtI=\frac{CV}{t}I=tCV​,开关闭合时电压在非常短的时间内升高,所以电流会突然变得很大。 +我们当然可以简单的利用电阻来抑制浪涌电流,但不幸的是电阻总要消耗功率。为了最大限度的提高效率,可以考虑使用电感,电感本身不消耗任何能量,只会进行储能,且其无损限流的能力正好可以用来抑制电容的浪涌电流。 + +引入电感后可以发现当开关打开时,电感没有续流回路,因此需要想办法构造电感的续流回路。续流回路需要保证不论开关打开还是闭合,电流都流向负载,且开关闭合时电源正极与负极回路必须经过电感与负载。这个需求很符合二极管的特点,即只允许单向导通。 + +到目前为止我们构建了非同步 BUCK 电路,考虑到机械开关容易磨损、使用寿命短、有机械惯性(转换频率低)的问题,我们需要将机械开关换成转换频率高的半导体器件,此处我们选择 NMOS 管来替代开关。 + +选择 NMOS 和 PMOS 的主要区别在于驱动电路的设计 + + +可以发现当 NMOS 开关管导通时,续流二极管处于截止状态;当开关管关断时,续流二极管处于导通状态。即二极管的导通和截止和开关管的截止导通是同步的,也就是说二极管起到的是一个开关的作用。而且考虑到电流从二极管流过期间,二极管两端的压降恒定为导通电压 0.7V,二极管所消耗的能量较大。因此我们也可以把二极管换为导通电阻更小的 NMOS 管。 + +为了提高 BUCK 电路的稳定性,防止由于输入纹波带来异常,我们在 BUCK 电路的输入端并联一个电容,用于滤除输入电压的纹波。 + +至此我们就搭建了一个标准的同步 BUCK 电路,我们将其简单变个样子,再加几个标签即可得到下图。在同步 BUCK 电路中需要两个开关管密切配合,以防止整个线路导通,所以它们之间需要保持一定的相位关系,即上管导通下管截止;上管截止下管导通,我们把这种关系称之为同步。 + +信号角度理解 LC +我们以占空比为 0.5 来进行说明,将时域下的方波转换到频域,通过傅立叶变换可以分解出一系列的频率分量。其中包含频率为 0 的分量,即直流分量,也就是我们想要保留的部分,还有频率为 n 倍 fsf_{s}fs​ 的分量。那么如何把我们不想要的部分去掉呢?从滤波角度考虑就需要加入一个低通滤波器。 + +通过加入低通滤波器可以把高频分量滤除,把二阶低通滤波器的截止频率设置在 0 到 fsf_{s}fs​ 之间,即可把 fsf_{s}fs​ 所有以上的部分给滤除。整体达到的效果即通过一个 LC 低通滤波器,配合一个开关网络,将一个数字化的电平重新滤出,得到一个比较平缓的电压输出,这个过程即完成了电压从高到低的转换。其中直流分量的大小受占空比 D 控制,所以通过改变占空比 D 即可改变输出电压大小。 + +稳态分析 +我们需要先强调一下前提,此处我们说的稳态分析,即输入电压输出电压都是稳定,且纹波足够小的状态。下文的所有计算都将基于稳态进行分析,并且是在 (F)CCM(连续导通模式)下计算的。 +我们将一些已知条件列出来: + +输入电压:ViV_{i}Vi​ +输出电压:VoV_{o}Vo​ +负载电阻:RRR +输出电感:LLL +开关频率:fff + +伏秒平衡 +当上管导通下管截止时,电感右边的电压为 ViV_{i}Vi​,左边的电压为 VoV_{o}Vo​,因为同步 BUCK 电路是降压电路,所以 Vi&gt;VoV_{i}&gt;V_{o}Vi​&gt;Vo​,所以电感两端电压即为 Vi−VoV_{i}-V_{o}Vi​−Vo​,也就是说是一个恒定值。由于有 Ldidt=Vi−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =V_{i}-V_{o}Ldtdi​=Vi​−Vo​,所以 didt=Vi−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{V_{i}-V_{o}}{L}dtdi​=LVi​−Vo​​,即电感电流的上升斜率,由于是稳态前提,所以可以确定该值是一个常数。 +当上管截止下管导通时,电感右边电压为 VoV_{o}Vo​,左边电压为 000,所以电感两端电压为 0−Vo0-V_{o}0−Vo​,即 −Vo-V_{o}−Vo​。由于 Ldidt=−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-V_{o}Ldtdi​=−Vo​,所以 didt=−VoL\frac{\mathrm{d} i}{\mathrm{d} t} =-\frac{V_{o}}{L}dtdi​=−LVo​​,即电感电流的下降斜率,也是一个常数。 +整个电路处于稳定状态,负载电路恒定,那么在一个周期内,电感电流增加的量肯定等于电感电流减小的量,即充了多少电就要放多少电,不然负载的电流或电压将会发生变化。 +前文已有didt=UL\frac{\mathrm{d} i}{\mathrm{d} t} =\frac{{U}}{L}dtdi​=LU​,而 LLL 恒定,那么电感电流的变化速度即与电压成正比关系,即电感电流上升(下降)的斜率与电压成正比关系。而电感电流上升和下降的高度相同,那么上升时间和下降时间就自然构成反比关系。 +TonToff=VoVi−Vo\frac{T_{on} }{T_{off} } = \frac{V_{o}}{V_{i}-V_{o}}Toff​Ton​​=Vi​−Vo​Vo​​,将其进行简单变换即可得到闻名江湖的伏秒平衡法则。 +Ton(Vi−Vo)=ToffVoT_{on}(V_{i}-V_{o}) = T_{off}V_{o}Ton​(Vi​−Vo​)=Toff​Vo​ +占空比 +已知 T=Ton+Toff=1fT=T_{on}+T_{off}=\frac{1}{f}T=Ton​+Toff​=f1​,结合伏秒平衡法则可以计算出: +开通时间:Ton=VoVi∙1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f}Ton​=Vi​Vo​​∙f1​ +关断时间:Toff=Vi−VoVi∙1fT_{off} = \frac{V_{i}-V_{o} }{V_{i} } \bullet \frac{1}{f}Toff​=Vi​Vi​−Vo​​∙f1​ +占空比:D=TonT=VoViD = \frac{T_{on} }{T}=\frac{V_{o} }{V_{i}}D=TTon​​=Vi​Vo​​ +纹波电流 +由于输出电压不变,也就是说输出电容两端的电压没有变化,即输出电容的平均电流为 0。根据输出节点的基尔霍夫电流定律可知,输出节点电流和为 0,那么功率电感的平均电流就等于负载的平均电流,即IL=Io=VoRI_{L} = I_{o} = \frac{V_{o} }{R}IL​=Io​=RVo​​。 + +上文计算电感电流斜率时已经能确定电流波形是个三角波,纹波电流等于在开关导通时电感电流的增大值,也等于在关断时电感电流减小的值,计算任意一个即可得到纹波电流。我们以上管导通时增大的电感电流计算。 + +上管导通时电感两端电压为 Vi−VoV_{i}-V_{o}Vi​−Vo​,导通时间为 Ton=VoVi∙1fT_{on} = \frac{V_{o} }{V_{i} } \bullet \frac{1}{f}Ton​=Vi​Vo​​∙f1​,根据 U=LdidtU=L\frac{\mathrm{d} i}{\mathrm{d} t}U=Ldtdi​ 可知: +△IL=di\triangle I_{L} =di△IL​=di +=Ton∙UL=T_{on}\bullet \frac{U}{L}=Ton​∙LU​ +=Vi−VoL∙VoVi∙1f=\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}=LVi​−Vo​​∙Vi​Vo​​∙f1​ +根据理论计算可以发现,电感电流的纹波和负载电流的大小没有关系,但是负载电流与平均电感电流是相等关系。 +功率电感选择 +根据上文的信息进一步可以计算出电感的峰值电流: +ILP=Io+△IL2I_{LP} =I_{o}+\frac{\triangle I_{L}}{2}ILP​=Io​+2△IL​​ +=Io+Vi−Vo2L∙VoVi∙1f=I_{o}+\frac{V_{i}-V_{o}}{2L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}=Io​+2LVi​−Vo​​∙Vi​Vo​​∙f1​ +那么在选择功率电感时,电感的饱和电流就必须要大于这个ILPI_{LP}ILP​,并且需要留有一定的裕量。实际应用时电感的纹波电流应是平均电流的 30%30\%30% 至 50%50\%50% 为宜,我们将这个参数称之为电流纹波率 r。根据电流纹波率范围就可以计算出电感值的范围: +△IL=Vi−VoL∙VoVi∙1f\triangle I_{L} =\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}△IL​=LVi​−Vo​​∙Vi​Vo​​∙f1​ +L=Vi−Vo△IL∙VoVi∙1fL =\frac{V_{i}-V_{o}}{\triangle I_{L}}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}L=△IL​Vi​−Vo​​∙Vi​Vo​​∙f1​ +=Vi−Vo(0.3至0.5)Io∙VoVi∙1f=\frac{V_{i}-V_{o}}{(0.3至0.5) I_{o}}\bullet \frac{V_{o}} {V_{i}}\bullet \frac{1}{f}=(0.3至0.5)Io​Vi​−Vo​​∙Vi​Vo​​∙f1​ +=(Vi−Vo)Vo(0.3至0.5)IoVif=\frac{(V_{i}-V_{o})V_{o}}{(0.3至0.5) I_{o} V_{i} f}=(0.3至0.5)Io​Vi​f(Vi​−Vo​)Vo​​ +为何 r 为 0.3~0.5 +电流纹波率即是电感电流的交流分量与其相对应的直流分量的比值,一旦 r 确定,那么输入输出滤波电容的电流、开关管的有效电流等都确定了,因此 r 的选择会影响器件选择和芯片的成本。使用公式可以表述为: +r=△IIL=2×IACIDCr=\frac{\triangle I}{I_{L}}=2\times \frac{I_{AC}}{I_{DC}}r=IL​△I​=2×IDC​IAC​​ +一般认为,电感体积与其能量处理能量成正比,因为要处理更高的能量就需要更大的磁芯。选择电感磁芯的能量处理能力至少要等于其需存储量,即 E=12×L×Ipk2E=\frac{1}{2} \times L \times I_{pk}^{2}E=21​×L×Ipk2​,下图是 E 与 r 的的函数曲线,可以发现在 r=0.4r=0.4r=0.4 附近有一个拐点。 + +选择的 r 如果较 0.4 低很多,则所需要的电感体积越大;而若继续增大 r,则电感的体积并不会减少多少,即当 r 超过 0.4 后,通过增加 r 来减少电感体积的效果已经不明显了。 +输入纹波 +电源输入功率为 Pi=ViIiP_{i}=V_{i}I_{i}Pi​=Vi​Ii​,负载功率为 Pr=VoIoP_{r}=V_{o}I_{o}Pr​=Vo​Io​,不考虑开关损耗、导通损耗等等因素,那么输入功率和输出功率相等,可得输入平均电流为 Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}Ii​=Vi​Vo​Io​​。 +输入电压纹波就是输入电容上面电压的变化,这个变化可以分为两部分。一部分为电容充放电所导致的电压变化 UqU_{q}Uq​,另一部分为电流流过电容 ESRESRESR 导致的压降 UesrU_{esr}Uesr​。即 △Vi=Uq+User\triangle V_{i} = U_{q} + U_{ser}△Vi​=Uq​+User​。 +∵Q=CiUq=Iit=IiToff\because Q = C_{i}U_{q} = I_{i}t = I_{i}T_{off}∵Q=Ci​Uq​=Ii​t=Ii​Toff​ +∴Uq=IiToffCi\therefore U_{q} = \frac{I_{i}T_{off}}{C_{i}}∴Uq​=Ci​Ii​Toff​​ +∵Toff=Vi−VoVif\because T_{off} = \frac{V_{i}-V_{o} }{V_{i}f }∵Toff​=Vi​fVi​−Vo​​ +且 Ii=VoIoViI_{i}=\frac{V_{o}I_{o}}{V_{i}}Ii​=Vi​Vo​Io​​ +∴Uq=VoIoCiVif∙Vi−VoVi\therefore U_{q} = \frac{V_{o}I_{o}}{C_{i}V_{i}f}\bullet \frac{V_{i}-V_{o}}{V_{i}}∴Uq​=Ci​Vi​fVo​Io​​∙Vi​Vi​−Vo​​ +要想知道 ESRESRESR 所造成的纹波,只需要知道流过输入电容的电流即可。当上管断开时,电源输入电流 IiI_{i}Ii​ 全部流入电容 CiC_{i}Ci​。电感电流原本从下管的体二极管续流,当上管导通后,变为了从上管续流。因为此前电感一直处于放电状态,所以切换的那一刻电感电流是最小的,为 IL−△IL2I_{L}-\frac{\triangle I_{L}}{2}IL​−2△IL​​。 +在整个 TonT_{on}Ton​ 时间内,电感都被充电,电感电流一直都在增大,直到 IL+△IL2I_{L}+\frac{\triangle I_{L}}{2}IL​+2△IL​​,并且在 TonT_{on}Ton​ 时间内,电感电流都是走的上 MOS 管通路,所以上 MOS 管最大电流也是 IL+△IL2I_{L}+\frac{\triangle I_{L}}{2}IL​+2△IL​​。 +根据基尔霍夫电流定律可知,输入节点的电流和为 0,那么输入电源电流 IiI_{i}Ii​ 和电容 CiC_{i}Ci​ 的放电电流就等于通过上 MOS 管的电流。所以 CiC_{i}Ci​ 的最大放电电流即为 IL+△IL2−IiI_{L}+\frac{\triangle I_{L}}{2} - I_{i}IL​+2△IL​​−Ii​。我们约定充电为正,放电为负,则放电电流为 Ii−△IL2−ILI_{i} - \frac{\triangle I_{L}}{2} - I_{L}Ii​−2△IL​​−IL​。 + + +上管截止时 ESRESRESR 的压降为 Ii∙ESRI_{i} \bullet ESRIi​∙ESR,上管导通时压降为 (Ii−△IL2−IL)∙ESR(I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESR(Ii​−2△IL​​−IL​)∙ESR,则可得: +User=Ii∙ESR+(Ii−△IL2−IL)∙ESRU_{ser} = I_{i} \bullet ESR + (I_{i} - \frac{\triangle I_{L}}{2} - I_{L}) \bullet ESRUser​=Ii​∙ESR+(Ii​−2△IL​​−IL​)∙ESR +=(IL+△IL2)∙ESR=(I_{L} + \frac{\triangle I_{L}}{2}) \bullet ESR=(IL​+2△IL​​)∙ESR +∵△IL==Vi−VoL∙VoVi∙1f\because \triangle I_{L} ==\frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}∵△IL​==LVi​−Vo​​∙Vi​Vo​​∙f1​ +且 IL=IoI_{L} = I_{o}IL​=Io​ +∴User=(Io+(Vi−Vo)Vo2ViLf)∙ESR\therefore U_{ser} = \left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR∴User​=(Io​+2Vi​Lf(Vi​−Vo​)Vo​​)∙ESR +综上所述可得: +△Vi=Uq+Uesr\triangle V_{i} = U_{q} + U_{esr}△Vi​=Uq​+Uesr​ +=VoIoCiVif∙Vi−VoVi+(Io+(Vi−Vo)Vo2ViLf)∙ESR=\frac{V_{o}I_{o}}{C_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}} +\left ( I_{o} + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{2V_{i}Lf} \right )\bullet ESR=Ci​Vi​fVo​Io​​∙Vi​Vi​−Vo​​+(Io​+2Vi​Lf(Vi​−Vo​)Vo​​)∙ESR +输入电容选择 +考虑到电容的实际使用情况,陶瓷电容的 ESRESRESR 小,容量小,所以 UqU_{q}Uq​ 对纹波起决定性作用,输入纹波可近似为 UqU_{q}Uq​。若选择铝电解电容,则 ESRESRESR 大,容量大,UesrU_{esr}Uesr​ 对纹波起到决定性作用,输入纹波可以近似为 UesrU_{esr}Uesr​,假设电路设计要求输入纹波不能大于 △Vi\triangle V_{i}△Vi​,则有: +陶瓷电容:Ci≥VoIo△ViVif∙Vi−VoViC_{i} \ge \frac{V_{o}I_{o}}{\triangle V_{i}V_{i}f} \bullet \frac{V_{i}-V_{o}}{V_{i}}Ci​≥△Vi​Vi​fVo​Io​​∙Vi​Vi​−Vo​​ +铝电解电容:ESR≤△ViIo+(Vi−Vo)Vo2fLViESR \le \frac{\triangle V_{i}}{I_{o} + \frac{(V_{i}-V_{o})V_{o}}{2fLV_{i}} }ESR≤Io​+2fLVi​(Vi​−Vo​)Vo​​△Vi​​ +输出纹波 +输出纹波与输入纹波同理,亦是 △Vo=Uq+Uesr\triangle V_{o} = U_{q} + U_{esr}△Vo​=Uq​+Uesr​,我们画出负载、功率电感、输出电容三者的电流波形。其中电感的纹波电流是 △IL\triangle I_{L}△IL​,则电容的纹波电流也是 △IL\triangle I_{L}△IL​,又因为电容的平均电流为 0,所以充电电流和放电电流都是 △IL2\frac{\triangle I_{L}}{2}2△IL​​。 +电容充放电的总电荷量 Q 等于电流乘以时间,即图中阴影三角形的面积,三角形底部时间为 T2\frac{T}{2}2T​,高为 △IL2\frac{\triangle I_{L}}{2}2△IL​​,所以总的放电量可以计算出来为 Q=12∙T2∙△IL2Q=\frac{1}{2} \bullet \frac{T}{2} \bullet \frac{\triangle I_{L}}{2}Q=21​∙2T​∙2△IL​​ + +结合 Q=CoUqQ=C_{o}U_{q}Q=Co​Uq​ 可得: +Uq=(Vi−Vo)Vo8ViCoLf2U_{q} = \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} }Uq​=8Vi​Co​Lf2(Vi​−Vo​)Vo​​ +由前面电流波形可知,电容的充电电流最大是 △IL2\frac{\triangle I_{L}}{2}2△IL​​,放电电流最大是 −△IL2-\frac{\triangle I_{L}}{2}−2△IL​​,则可以得到 ESRESRESR 引起的总压降为: +User=△IL2∙ESR−(−△IL2∙ESR)U_{ser} = \frac{\triangle I_{L}}{2} \bullet ESR - (-\frac{\triangle I_{L}}{2} \bullet ESR)User​=2△IL​​∙ESR−(−2△IL​​∙ESR) +∵△IL=Vi−VoL∙VoVi∙1f\because \triangle I_{L} = \frac{V_{i}-V_{o}}{L}\bullet \frac{V_{o}}{V_{i}}\bullet \frac{1}{f}∵△IL​=LVi​−Vo​​∙Vi​Vo​​∙f1​ +∴(Vi−Vo)VoViLf∙ESR\therefore \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR∴Vi​Lf(Vi​−Vo​)Vo​​∙ESR +最终可得: +△Uo=Uq+Uesr\bigtriangleup U_{o} = U_{q} + U_{esr}△Uo​=Uq​+Uesr​ +=(Vi−Vo)Vo8ViCoLf2+(Vi−Vo)VoViLf∙ESR=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}C_{o}Lf^{2} } + \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf} \bullet ESR=8Vi​Co​Lf2(Vi​−Vo​)Vo​​+Vi​Lf(Vi​−Vo​)Vo​​∙ESR +=(Vi−Vo)VoViLf∙(ESR+18fCo)=\frac{\left ( V_{i}-V_{o} \right ) V_{o}}{V_{i}Lf}\bullet \left ( ESR + \frac{1}{8fC_{o}} \right )=Vi​Lf(Vi​−Vo​)Vo​​∙(ESR+8fCo​1​) +输出电容选择 +与输入电容选择的方式一致,考虑是容值还是 ESRESRESR 占主导地位,假设要求输出纹波要小于 △Vo\triangle V_{o}△Vo​,则有: +陶瓷电容:Co≥(Vi−Vo)Vo8Vi△VoLf2C_{o} \ge \frac{\left ( V_{i}-V_{o} \right ) V_{o}}{8V_{i}\triangle V_{o}Lf^{2} }Co​≥8Vi​△Vo​Lf2(Vi​−Vo​)Vo​​ +铝电解电容:ESR≤△VoViLf(Vi−Vo)VoESR \le \frac{\triangle V_{o}V_{i}Lf}{\left ( V_{i}-V_{o} \right ) V_{o}}ESR≤(Vi​−Vo​)Vo​△Vo​Vi​Lf​ +电感续流模式 +电感电流曲线不能断续(无突降),因为电流断续会引起实际不可能发生的能量断续现象。但是电流的变化率可以突变,比如从上升斜率(电感储能增加)变为下降斜率(电感储能释放),尽管这样电感电流也必须连续。根据稳定状态下每个周期电流是否回到零,划分为不同的导通模式,并且通过减小负载电流,可以使电路从 CCM 经过 BCM 最终转变为 DCM。 +CCM(连续导通模式) +稳定状态下每个周期,电流都回到某一非零值,称之为连续导通模式(CCM:Continuous Conduction Mode)。CCM 是功率变换中最常见的工作模式,有输出纹波小但功耗高的特点。 +FCCM(强制连续导通模式)只存在于同步 BUCK 中,由于使用 MOS 管将非同步拓扑的二极管取代,MOS 管的导通压降远低于二极管压降,除了显著减小了续流通路的导通损耗外,也允许电感电流反向,即从负载瞬时流出电流。 + +DCM(断续导通模式) +若稳定状态下每个周期中电流都会回到零,那么就称之为断续导通模式(DCM:Discontinuous Conduction Mode),DCM 由于其电感电流的不连续,计算平均电感电流就需要更加详细复杂的公式,这也是 DCM 方程看上去复杂的根本原因。 + +BCM(临界导通模式) +BCM 是临界导通模式(Boundary Conduction Mode),由控制器监控电感电流,一旦检测到电流等于 0,功率开关立即闭合,控制器总是等电感电流「复位」来激活开关。即 BCM 处于 CCM 和 DCM 之间,可以将其视为 CCM 和 DCM 的极端情况,所以 BCM 模式下可以自由的选择 CCM 或 BCM 方程。 + +DC-DC 功能框图 +前文所构建的 BUCK 电路只能是纸上谈兵,还需要解决诸多问题才能应用于实际电路。 +基础驱动与控制 +首先需要解决的问题就是 MOS 管不可能平白无故就打开,所以我们需要添加 MOS 管驱动器。 + +理想情况是上管关闭,下管立刻打开,中间没有任何时间差,但是 MOS 管并非理想开关,从关断到导通存在一个过渡的过程,若同时导通则电源通过上下 MOS 管直接对地短路,很容易就会导致 MOS 损坏,甚至可能会把前一级电源也损坏,所以上下管同时导通的状态必须得避免。 +为了避免上下管直通的情况,实际应用会故意让上管和下管切换时多等一会儿,宁愿出现同时关断的情况,也不能出现同时导通的状态,这个等待的过程就叫做死区时间。 + +需要注意的是,在死区时间内虽然下管没有被导通,但是功率 MOS 管本身存在一个寄生二极管,这个寄生二极管可以像非同步 BUCK 那样帮助电感续流,而且这个时间非常的短暂,所以产生的功耗没有那么大,因此不必担心系统会出问题。 +到目前为止,不知道您有没有发现我们都在自嗨,系统中并没有用来控制上下 MOS 导通和关断的信号。因此需要增加一个振荡器用来产生控制信号,注意我们在前文中使用的是占空比一词,也就是说我们要使用的是 PWM(脉冲宽度调制)。当然你也可以使用 PFM(脉冲频率调制),本文只介绍 PWM 方式。 + +PWM(脉冲宽度调制) +PWM 的全称是脉冲宽度调制(Pulse-width modulation),是通过将有效的电信号分散成离散形式,从而来降低电信号所传递的平均功率的一种方式。其基本实现原理是通过锯齿波/三角波(载波)与所需要合成的波形(调制波)进行比较,然后确定 PWM 所需要输出的极性。因为一般都是用到开关器件上,通常是 ON 或者 OFF,具体如下图所示。 + +将振荡器输出的锯齿波和参考值 VthV_{th}Vth​ 进行比较,就可以输出 PWM 波形了。话不多说,上图就明白了。 + +上图中的锯齿波(橙色)最大为 10,但是我们希望输出平均为 5 的波形(图中紫色的水平线),那么通过比较器进行比较,当锯齿波小于 5 时,PWM 即输出低电平 OFF,当锯齿波大于 5 时,PWM 即输出高电平 ON,此时的占空比即为 50%。 +若是想输出一个电压逐渐抬高的波形,即占空比逐渐增大,那只需要将调制的波形设置为斜坡输出即可达到效果。比如下图中可以看到,占空比从 0% 逐渐增大到 100%。 + +同样的道理,我们可以通过改变调制波形,进一步调制出来其它的波形,比如要调制一个正弦波(sin wave),也就是我们常说的 SPWM,那么就是下面的样子。 + +负反馈环路 +有了调制信号,开关管也可以正常打开与关闭,看起来可以应用到实际电路中了,但是别忘了负载的电阻并不是恒定的,负载的变化必然会引起输出电压的波动。为了减小输出电压的波动,我们可以在输出端添加分压电阻,与误差放大器和基准电压一起构成负反馈回路,这种通过取样输出电压进行闭环反馈的方式称之为电压模式控制。 + +误差放大器的输入端分别为带隙基准源输出电压采样,当输出电压减小/增大时,与基准电压的细微差异都会被误差放大器放大,今儿调节脉冲宽度来达到调节调整输出电压的目的。图中 R2 接地,所以可以很容易计算出输出电压与分压电阻的关系:Vout=Vref(R1+R2)R2V_{out} = \frac{V_{ref}(R_{1}+R_{2})}{R_{2}}Vout​=R2​Vref​(R1​+R2​)​。 +除了输出电压可以用作控制取样信号,还有输入电压、输出电流、输出电感电压、开关器件峰值电流可以作为控制取样信号。使用这些信号可以构成单环、双环或多环反馈系统,进而实现稳压、稳流以及恒定功率的目的,也可以实现过流、过压、均流等功能。 +现在回过头来评判一下电压模式控制的优缺点。单一的反馈电压闭环设计使得调试更加容易、对输出负载的变化有比较好的响应调节、占空比的调节也不会受到什么限制等等都是它的优点,但是其缺点也很明显。由于主电路有较大的输出电容和电感的相移延时作用,输出电压的变小/变大也延时滞后,再经过误差放大器的延时,使得瞬态响应变得更慢。由于电压控制模式不采样电流,逐周期限流保护功能必须另外增加电路来实现。 +峰值电流模式控制在电压模式控制的基础上又增加了电流环,所以峰值电流模式控制是一个双环反馈系统。误差电压信号与一个变化的,其峰值代表输出电感电流峰值的三角波形进行比较,然后得到 PWM 脉冲的关断时刻。所以峰值电流模式控制不是使用电压误差信息直接控制 PWM 脉冲宽度,而是直接控制峰值输出侧的电感电流大小,进而间接的控制 PWM 脉冲宽度。 + +峰值电流在逻辑上与平均电感电流大小变化一致,但是峰值电感电流的大小并不能与平均电感电流的大小一一对应。在占空比不同的情况下,相同的峰值电感电流大小可以对应不同的平均电感电流大小,但平均电感电流大小才是唯一决定输出电压大小的因素。 +为了解决不同占空比对平均电感电流大小的扰动作用,使得所控制的峰值电感电流最后收敛于平均电感电流,需要将电感电流下斜坡斜率的至少一半以上斜率加在实际检测电流的上斜坡上,这一点可以从数学上进行证明(具体咋证明暂不讨论)。 +总结一下峰值电流模式控制 PWM 是双闭环控制系统,电压外环控制电流内环。电流内环是瞬时快速按照逐个脉冲工作的。功率级石油电流内环控制的电流源,而电压外环再控制次功率级电流源。电流内环只负责输出电感的动态变化,电压外环仅需控制输出电容,所以峰值电流模式控制 PWM 具有比电压模式控制大得多的带宽。 +为了防止在应用过程中可能出现的短路等异常场景,DC-DC 少不了过温保护、过流保护、过压保护等保护手段。再设定一定的辅助功能,比如 PG 状态显示、缓启动、欠压保护等即可搭建完整的 DC-DC 电路。 + +异常模式 +参考上文中的电路图,我们把绿色部分称之为控制电路,灰色部分是功率电路,功率电路中最核心的就是上下两个 MOS 管,下文我们讨论不同的异常场景中,控制电路、上管、下管三部分应该处于什么状态,其中控制电路关闭相当于整个芯片重启。 +过压保护 +当输出电压偏高并且达到了过压保护的阈值。过压状态需要控制电路去调整把输出电压降下来,所以不需要重启整个芯片。可以想到输出端已经处于过压状态了,上管如果打开那会加重过压的程度,因此上管需要关闭。若下管打开,则电感、负载、下管形成回路,即电感有续流回路,会把过压状态维持的时间更长,因此下管也需要关闭。综上有:过压保护:关上管、关下管。 +过温保护 +温度过高的情况无非两种,一种是流过芯片的电流太大,即功率太大导致芯片自身发热达到了过温保护的阈值,此时关闭芯片肯定可以解决,另外切断电流回路也是可以解决的,即关闭上管。过温的第二种情况是由于环境温度过高而导致芯片温度过高,此时最好还是关闭芯片吧。综上有:过温保护:关闭芯片。 + +关闭芯片指关闭芯片中的 BUCK 部分,但是基准源部分仍然保持工作 + +过流保护 +过流保护还需要区分是正向过流还是负向过流,因为工作在 FCCM 模式的 DC-DC 在轻载或空载时,可能会有负向过流的情况。存在负向过流的另一原因也是因为同步 BUCK 没有像非同步 BUCK 那样的整流二极管,所以当存在负向过流情况时,直接模拟非同步 BUCK 中的二极管即可。综上有:负向过流保护:关下管。 +若发生正向过流时如何进行保护呢?首先考虑到电流经上管到负载,既然已经过流了那么肯定需要关上管。为了使电流减小的更快,那么就需要将电流流向地,所以需要将下管打开以构成回路。综上有:正向过流保护:关上管、开下管。 +异常排查 +不管系统设计的多好,在实际应用中都可能会或多或少出现问题,比如电感选用不合适、触发 min-on time、触发 min-off time、输出电容 ESR 过大等,下面我们逐一进行讨论。 +min-on time +虽然 MOS 管打开速度很快,但是打开始终是一个过程,要完成一个过程就必须需要一定的时间,当高频且压差大的情况下很容易触发完成「打开」这个过程的最小时间。也就是说占空比已经是实际最小了,占空比无法再降低了,所以查看输出电压纹波可能会出现下面的波形。 + +出现该波形的原因在于,占空比已经无法继续降低,所以电压整体处于逐渐抬高的趋势,当抬高到一定程度时即触发过压保护,上下管都关断,所以电压快速下降。 +min-off time +与 min-on time 相对应的是 min-off time,当开关频率足够高且输入和输出电压接近时即容易出现此问题,此时即达到系统所能达到的最大占空比也无法满足负载所需要的电压,表现为输出电压无法达到设定值,负反馈分压电阻电压也低于电压基准值。 +电感饱和电流过小 +电感电流正常是一个三角波,但是如果电感饱和电流过小,则会电感电流将会变成下图很苗条的样子。因为电感电流饱和所以电流不再线性增加,电流快速增大导致磁通率减小,会导致磁性损耗增大、芯片热耗增大,而且这是一个正反馈过程,整个系统的可靠性会大大降低。 + +输出电容选用不合适 +当输出电容选用过小时,会导致动态响应输出出现抖动。若输出电容的等效串联电阻(ESR)过大,也会导致输出纹波异常增大,这一点从前文的理论计算即可验证。因此在实际使用过程中需要同时考虑电容容值和所选电容的 ESR。 +为什么需要 min-on time +占空比 D 控制相对于输入电压的输出电压,虽然通过提高开关频率有助于减小电感尺寸,但是也必须满足最小导通时间(min-on time)才能使芯片正常工作。那么这个 min-on time 是由哪些因素引起的呢? +因为上管中电流波形前沿的电流尖峰。由于 MOS 管也是由 PN 结组成,存在 PN 结就肯定存在结电容,MOS 管的寄生电容 CgsC_{gs}Cgs​ 和 CgdC_{gd}Cgd​ 会导致上管在导通时电流突然变化,也就是说会出现电流尖峰。如果在这个电流尖峰的时间段内去检测电流的话,很可能就会触发过流保护,因此开关电路的最小导通时间必须大于电流尖峰出现的时间,这个时间我们称之为消隐时间。 + +另一个原因是因为上下管开关完成后,由于键合线存在寄生电感的原因会产生很大的振铃,这个振铃同样可能会导致峰值电流检测出错,需要一个 min-on time 将这个振铃隔离过去。 +为什么需要 min-off time +如下图所示,最简单的需要最小关断时间(min-off time)的原因是,若下管不打开则没有办法给自举电容充电,所以需要在该时间内给自举电容充电,为下一个开关周期做准备。 + +另一个原因是因为没有最小关断时间,即占空比 D 增大到 100%,那么就无法对负向电流、谷值电流进行采样,也就无法实现实现相应的异常保护功能。与 min-on time 一致,电流检测也需要一个 min-off time 隔离振铃。 +为什么需要自举电容 +DC-DC 的上 MOS 管可以是 PMOS,也可以是 NMOS。但是一般因为生产工艺问题,PMOS 导通电流往往做不不到很大,而在相同成本下 NMOS 的导通电流可以做到更大,也就是 RdsonR_{dson}Rdson​ 可以做到相对较低,所以往往更倾向于 NMOS。 +将上管换为 NMOS 后也带来了新的问题,如何打开 NMOS ?如图所示,上管的 S 极连接 PH 点,该点的电压为 +5V,要打开 NMOS 需要 VGS&gt;0V_{GS} &gt; 0VGS​&gt;0,驱动 MOS 管打开的压降需要 5V,那么驱动电压就需要 +10V 才可以打开上管,但是纵观整个电路并没有能达到 +10V 级别的电压,所以需要自举电容来进行升压才能打开上 MOS 管。 + +所以 DC-DC 芯片是否需要自举电容是由芯片所选用的 MOS 管类型决定的,若是 PMOS 则无需自举电容。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 并查集详解及相关例题解析 + +

+ +
+ + + + +
+ +
+ +参考内容: +图论——并查集(详细版) + +并查集(Disjoint-set)是一种精巧的树形数据结构,它主要用于处理一些不相交集合的合并及查询问题。一些常见用途,比如求联通子图、求最小生成树的 Kruskal 算法和求最近公共祖先(LCA)等。 +并查集的理念是只关注个体属于哪个阵营,并不关心这个阵营中个体内部的关系,比如我们常说的张三是李家沟的,王二是王家坝的。同时并查集借助个体代表集体的思想,用一个元素代表整个群体,就像我们开学都会有学生代表、教师代表讲话一样,在台上讲话的那一个学生就代表了学校所有的学生。 +并查集基本操作 +并查集的基本操作主要有初始化 init、查询 find和合并 union操作。 +初始化 +在使用并查集的时候,常常使用一个数组fa来存储每个元素的父节点,在一开始的时候所有元素与其它元素都没有任何关系,即大家相互之间还不认识,所以我们把每个元素的父节点设为自己。 +#define ARR_LEN 6000 + +int fa[ARR_LEN]; + +void init(int n) +{ + for(int i = 1; i &lt;= n; i++) + fa[i] = i; +} + +查询 +查询即找到指定元素的祖先。需要注意的是,这里我们需要找到指定元素的根祖先,不能找到爸爸或者爷爷就停止了,而是要找到查找不下去了为止,所以要不断的去递归下去,直到找到父亲为自己的结点才结束。 +int find(int i) +{ + if(i == fa[i]) // 递归出口 + return i; + else + return find(fa[i]); // 不断向上查找祖先 +} + +考虑下面的场景,假如第一次我们需要查询元素5的祖先,第二次需要查询元素4的祖先,会发现第一次查询包含了第二次查询的计算过程,但我们的程序却傻傻的计算了两次,有没有办法去来优化查询过程,让每一次查询都能利用到此前查询计算的便利? + +考虑到并查集并不关心某个元素的爸爸、爷爷是谁,只关心最终的祖先是谁,所以我们可以在查询的过程中顺便做一些修改,比如在查询5的过程中,顺便就把4和2的父亲给修改为1,即我们在查找过程中进行路经压缩 +int find(int i) +{ + if(i == fa[i]){ + return i; + } else { + fa[i] = find(fa[i]); // 进行路径压缩 + return fa[i]; + } +} + +合并 +合并操作即介绍两个人相互认识,将他们纳入同一个帮派,只需要将俩元素的父亲修改为同一个即可。 +void union(int i, int j) +{ + int fa_i = find(i); + int fa_j = find(j); + fa[fa_i] = fa_j; +} + +相关练习题目 +洛谷 P1551 亲戚 +题目连接:https://www.luogu.com.cn/problem/P1551 +题目描述 +若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。 +规定:xxx 和 yyy 是亲戚,yyy 和 zzz 是亲戚,那么 xxx 和 zzz 也是亲戚。如果 x,yx,yx,y 是亲戚,那么 xxx 的亲戚都是 yyy 的亲戚,yyy 的亲戚也都是 xxx 的亲戚。 +输入格式 +第一行:三个整数 n,m,p,(n,m,p≤5000)n,m,p,(n,m,p≤5000)n,m,p,(n,m,p≤5000) 分别表示有 nnn 个人,mmm 个亲戚关系,询问 ppp 对亲戚关系。 +以下 mmm 行:每行两个数 Mi,Mj,1≤Mi,Mj≤nM_i,M_j,1≤M_i,M_j≤nMi​,Mj​,1≤Mi​,Mj​≤n,表示 MiM_iMi​ 和 MjM_jMj​ 具有亲戚关系。 +接下来 ppp 行:每行两个数 Pi,PjP_i,P_jPi​,Pj​,询问 PiP_iPi​ 和 PjP_jPj​ 是否具有亲戚关系。 +输出格式 +ppp 行,每行一个Yes或No。表示第 iii 个询问的答案为“具有”或“不具有”亲戚关系。 +输入输出样例 +# 输入 +6 5 3 +1 2 +1 5 +3 4 +5 2 +1 3 +1 4 +2 3 +5 6 + +# 输出 +Yes +Yes +No + +题目解析 +可以发现这是一个非常标准的并查集问题,简直和并查集模版如出一辙,因此直接将所有关系读取后进行合并,然后直接查询父亲是否为同一个即可。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +#define ARR_LEN 6000 + +int fa[ARR_LEN]; + +void init(int n) +{ + for(int i = 1; i &lt;= n; i++) + fa[i] = i; +} + +int find(int i) +{ + if(i == fa[i]){ + return i; + } else { + fa[i] = find(fa[i]); + return fa[i]; + } +} + +void union(int i, int j) +{ + int fa_i = find(i); + int fa_j = find(j); + fa[fa_i] = fa_j; +} + + +int main() +{ + int n, m, p; + int a, b; + + cin&gt;&gt; n &gt;&gt; m &gt;&gt; p; + + init(n); + + for(int i = 0; i &lt; m; i++){ + cin &gt;&gt; a &gt;&gt; b; + union(a, b); + } + + for(int i = 0; i &lt; p; i++){ + cin &gt;&gt; a &gt;&gt; b; + int fa_a = find(a); + int fa_b = find(b); + + if(fa_a == fa_b) + cout&lt;&lt;&quot;Yes&quot;&lt;&lt;endl; + else + cout&lt;&lt;&quot;No&quot;&lt;&lt;endl; + } +} + +杭电 OJ1213 How Many Tables +题目连接:https://acm.hdu.edu.cn/showproblem.php?pid=1213 +题目描述 +Today is Ignatius' birthday. He invites a lot of friends. Now it's dinner time. Ignatius wants to know how many tables he needs at least. You have to notice that not all the friends know each other, and all the friends do not want to stay with strangers. +One important rule for this problem is that if I tell you A knows B, and B knows C, that means A, B, C know each other, so they can stay in one table. +For example: If I tell you A knows B, B knows C, and D knows E, so A, B, C can stay in one table, and D, E have to stay in the other one. So Ignatius needs 2 tables at least. +输入格式 +The input starts with an integer T(1&lt;=T&lt;=25)T(1&lt;=T&lt;=25)T(1&lt;=T&lt;=25) which indicate the number of test cases. Then TTT test cases follow. Each test case starts with two integers NNN and M(1&lt;=N,M&lt;=1000)M(1&lt;=N,M&lt;=1000)M(1&lt;=N,M&lt;=1000). NNN indicates the number of friends, the friends are marked from 111 to NNN. Then MMM lines follow. Each line consists of two integers AAA and B(A!=B)B(A!=B)B(A!=B), that means friend AAA and friend BBB know each other. There will be a blank line between two cases. +输出格式 +For each test case, just output how many tables Ignatius needs at least. Do NOT print any blanks. +输入输出样例 +# 输入 +2 +5 3 +1 2 +2 3 +4 5 + +5 1 +2 5 + +# 输出 +2 +4 + +题目解析 +分析可以发现,这个问题要我们做的是统计在所有元素合并之后,统计总共有多个和集合。很轻松就能写出下面的 AC 代码。类似的问题还有杭电 OJ1232 畅通工程。 +读者大人可以在此基础上继续进行延伸,我们实际生活中每个桌子只能坐 8 个人,假设还需要考虑每桌人数的容量,又如何进行改进呢? +#include&lt;bits/stdc++.h&gt; +using namespace std; + +#define ARR_LEN 6000 + +int fa[ARR_LEN]; + +void init(int n) +{ + for(int i = 1; i &lt;= n; i++) + fa[i] = i; +} + +int find(int i) +{ + if(i == fa[i]){ + return i; + } else { + fa[i] = find(fa[i]); + return fa[i]; + } +} + +void union(int i, int j) +{ + int fa_i = find(i); + int fa_j = find(j); + fa[fa_i] = fa_j; +} + + +int main() +{ + int n, m, a, b, t; + + cin&gt;&gt;t; + for(int i = 0; i &lt; t; i++){ + cin&gt;&gt;n&gt;&gt;m; + int ans = 0; + init(n); + for(int i = 0; i &lt; m; i++) { + cin&gt;&gt;a&gt;&gt;b; + union(a, b); + } + + for(int i = 1; i &lt;= n; i++) { + // 如果父亲是自己,那么就表示一个独立的集合 + if(find(i) == i) + ans++; + } + + cout&lt;&lt;ans&lt;&lt;endl; + } + +} + +杭电 OJ1272 小希的迷宫 +题目连接:https://acm.hdu.edu.cn/showproblem.php?pid=1272 +题目描述 +小希设计了一个迷宫让 Gardon 玩,首先她认为所有的通道都应该是双向连通的,就是说如果有一个通道连通了房间 A 和 B,那么既可以通过它从房间 A 走到房间 B,也可以通过它从房间 B 走到房间 A,为了提高难度,小希希望任意两个房间有且仅有一条路径可以相通(除非走了回头路)。小希现在把她的设计图给你,让你帮忙判断她的设计图是否符合她的设计思路。比如下面的例子,前两个是符合条件的,但是最后一个却有两种方法从 5 到达 8。 + +输入格式 +输入包含多组数据,每组数据是一个以 0 0 结尾的整数对列表,表示了一条通道连接的两个房间的编号。房间的编号至少为 1,且不超过 100000。每两组数据之间有一个空行。整个文件以两个 -1 结尾。 +输出格式 +对于输入的每一组数据,输出仅包括一行。如果该迷宫符合小希的思路,那么输出Yes,否则输出No。 +输入输出样例 +# 输入 +6 8 5 3 5 2 6 4 +5 6 0 0 + +8 1 7 3 6 2 8 9 7 5 +7 4 7 8 7 6 0 0 + +3 8 6 8 6 4 +5 3 5 6 5 2 0 0 + +-1 -1 + +# 输出 +Yes +Yes +No + +题目解析 +其实这个问题就是让我们判断一个连通图中是否存在环,那么问题就转换为寻找出现环的条件。其实不难发现出现下面两种情况时,连通图即存在环。 + +在查找过程中,发现两个不同元素的父亲是相同的; +若不存在环,则边的数量一定比顶点数量少 1。 + +#include&lt;bits/stdc++.h&gt; +using namespace std; + +#define ARR_LEN 100010 + +int fa[ARR_LEN]; +bool visited[ARR_LEN]; // 用于辅助记录顶点的数量 +int edges, points; // 记录顶点和边的数量 +bool hascycle; // 是否存在环 + +void init() +{ + hascycle = false; + edges = 0; + points = 0; + for(int i = 1; i &lt; ARR_LEN; i++) + fa[i] = i, visited[i] = false; +} + +int find(int i) +{ + if(i == fa[i]){ + return i; + } else { + fa[i] = find(fa[i]); + return fa[i]; + } +} + +void union(int i, int j) +{ + int fa_i = find(i); + int fa_j = find(j); + + // 两个元素祖先相同,存在环 + if(fa_i == fa_j) { + hascycle = true; + } else { + visited[i] = true; + visited[j] = true; + edges++; + fa[fa_i] = fa_j; + } +} + + +int main() +{ + int a, b; + + init(); + + while(cin&gt;&gt;a&gt;&gt;b) { + if(a == 0 &amp;&amp; b == 0) { + cout&lt;&lt;&quot;Yes&quot;&lt;&lt;endl; + continue; + } + + if(a == -1 &amp;&amp; b == -1) { + return 0; + } + + union(a, b); + + while(cin&gt;&gt;a&gt;&gt;b){ + if(a == 0 &amp;&amp; b == 0) { + break; + } + union(a, b); + } + + if(hascycle) { + cout&lt;&lt;&quot;No&quot;&lt;&lt;endl; + continue; + } + + for(int i = 1; i &lt; ARR_LEN; i++){ + if(visited[i]) { + points++; + } + } + + if(points == edges + 1) { + cout&lt;&lt;&quot;Yes&quot;&lt;&lt;endl; + } else { + cout&lt;&lt;&quot;No&quot;&lt;&lt;endl; + } + init(); + } +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + 《算法竞赛进阶指南》165 小猫爬山题解 + +

+ +
+ + + + +
+ +
+ +参考内容: +[洛谷][noip][算法竞赛进阶指南]小猫爬山 +《算法竞赛进阶指南》小猫爬山 + +小猫爬山 +题目描述 +题目链接:https://www.acwing.com/problem/content/167/ +​Freda 和 Rainbow 饲养了 N 只小猫。这天,小猫们要去爬山。经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕&gt;_&lt;)。 +Freda 和 Rainbow 只好花钱让它们坐索道下山。索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是C1、C2……Cn。当然,每辆缆车上的小猫的重量之和不能超过 W。每租用一辆缆车,Freda 和 Rainbow 就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山? +输入格式 +​第一行包含两个用空格隔开的整数,N 和 W。接下来 N 行每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。 +输出格式 +输出一个整数,最少需要多少美元,也就是最少需要多少辆缆车。 +分析解答 +贪心 +经过思考发现,我们只需要尽可能的在每辆车上都放更多的小猫,就能以最经济的方式把所有小猫都送下山。所以是一个非常明显的贪心题目,我们将所有小猫按重量排序,尽可能把肥猫先送下山即可。具体实现代码如下: +#include&lt;bits/stdc++.h&gt; +using namespace std; + +void cin_arr(int *num, int len) +{ + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;num[i]; + } +} + +int slove(int *num, int n, int w) +{ + int ans = 0; + int remain = 0; + int load = 0; + + sort(num, num+n); + + while(true){ + for(int i = 0; i &lt; n; i++){ + // 连当前最小的猫都装不下,那么就新开一辆车 + if(num[i] != -1 &amp;&amp; remain &lt; num[i]){ + ans++; + remain = w; + break; + } + } + + for(int i = n-1; i &gt;= 0; i--){ + // 从大到小查找,尽可能装肥猫 + if(num[i] != -1 &amp;&amp; remain &gt;= num[i]){ + remain -= num[i]; + // 运送走的小猫重量以 -1 表示 + num[i] = -1; + load++; + break; + } + } + + // 如果所有小猫都运走了,那么当前 ans 就是答案 + if(load &gt;= n) + return ans; + } +} + +int main() +{ + int n, w; + int cat[1000000]; + + cin&gt;&gt;n&gt;&gt;w; + cin_arr(cat, n); + + cout&lt;&lt;slove(cat, n, w)&lt;&lt;endl; +} + +经过实际测试发现,盲目使用贪心思想的算法并不正确,例如如下测试用例。 +6 16 +9 5 5 5 4 3 + +贪心的结果是使用 3 辆车,分别为9+5、5+5+4、3;而正确的结果却是使用 2 辆车,分别为9+4+3和5+5+5。 +深度优先搜索 +既然贪心思想在这里行不通,那么我们就采用暴力搜索,即小猫可以放在现有任意一辆车上。具体实现代码如下: +#include&lt;bits/stdc++.h&gt; +using namespace std; + +#define N 2000 + +int n, w; +int cat[N]; +int sum[N] = {0}; // 第 i 辆车当前重量 +int ans = N; + +void cin_arr(int *num, int len) +{ + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;num[i]; + } +} + +void dfs(int cur_cat, int cur_car) +{ + if(cur_car &gt; ans) // 求最小值,不符合直接返回 + return ; + + if(cur_cat == n) { // 所有小猫都上车了 + ans = cur_car; + return ; + } + + for(int i = 0; i &lt; cur_car; i++) { + if(sum[i] + cat[cur_cat] &lt;= w) { // 当前猫能放进去 + sum[i] += cat[cur_cat]; // 当前猫占用重量 + dfs(cur_cat+1, cur_car); // 继续放下一只猫 + sum[i] -= cat[cur_cat]; // 把已经放进去的猫拿出来,因为是循环,所以放入下一辆车里面 + } + } + + // 新开一辆车,把当前这只猫放到新的车里面 + sum[cur_car] = cat[cur_cat]; + dfs(cur_cat+1, cur_car+1); + sum[cur_car] = 0; // 把猫拿出来 +} + +int main() +{ + cin&gt;&gt;n&gt;&gt;w; + cin_arr(cat, n); + dfs(0, 0); + cout&lt;&lt;ans&lt;&lt;endl; +} + +搜索优化 +考虑到每次都是在车数量固定的情况下进行搜索的,那么少满足一次(sum[i] + cat[cur_cat] &lt;= w)条件,就会少一次递归的调用,也即少一次搜索。那么如何能尽快使得程序尽快不满足该条件呢? +让sum[i]减小的速度加快就会减少搜索分支,即每次放更重一点的猫进去,就能达到效果。所以我们可以在进行搜索前将小猫的重量进行降序排序,这样从肥猫开始搜索就会减少分支。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +#define N 2000 + +int n, w; +int cat[N]; +int sum[N] = {0}; // 第 i 辆车当前重量 +int ans = N; + +void cin_arr(int *num, int len) +{ + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;num[i]; + } +} + +bool cmp(int a, int b) +{ + return a &gt; b; +} + +void dfs(int cur_cat, int cur_car) +{ + if(cur_car &gt; ans) // 求最小值,不符合直接返回 + return ; + + if(cur_cat == n) { // 所有小猫都上车了 + ans = cur_car; + return ; + } + + for(int i = 0; i &lt; cur_car; i++) { + if(sum[i] + cat[cur_cat] &lt;= w) { // 当前猫能放进去 + sum[i] += cat[cur_cat]; // 当前猫占用重量 + dfs(cur_cat+1, cur_car); // 继续放下一只猫 + sum[i] -= cat[cur_cat]; // 把已经放进去的猫拿出来,因为是循环,所以放入下一辆车里面 + } + } + + // 新开一辆车,把当前这只猫放到新的车里面 + sum[cur_car] = cat[cur_cat]; + dfs(cur_cat+1, cur_car+1); + sum[cur_car] = 0; // 把猫拿出来 +} + +int main() +{ + cin&gt;&gt;n&gt;&gt;w; + cin_arr(cat, n); + sort(cat, cat+n, cmp); // 反排序优化搜索 + dfs(0, 0); + cout&lt;&lt;ans&lt;&lt;endl; +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + 二叉树的前序、中序、后序、层序遍历 + +

+ +
+ + + + +
+ +
+ +参考内容: +五分钟让你彻底理解二叉树的非递归遍历 +Python实现二叉树的非递归遍历 +二叉树遍历——深度优先(前中后序)+广度优先(层序遍历) + +构造二叉树 +定义二叉树结构如下 +struct node +{ + int data; + node *left; + node *right; +}; + +构造如下形态二叉树 + +node *init_tree() +{ + node *node1 = (node *)malloc(sizeof(node)); + node *node2 = (node *)malloc(sizeof(node)); + node *node3 = (node *)malloc(sizeof(node)); + node *node4 = (node *)malloc(sizeof(node)); + node *node5 = (node *)malloc(sizeof(node)); + node *node6 = (node *)malloc(sizeof(node)); + node *node7 = (node *)malloc(sizeof(node)); + node *node8 = (node *)malloc(sizeof(node)); + + node1-&gt;data = 1; + node2-&gt;data = 2; + node3-&gt;data = 3; + node4-&gt;data = 4; + node5-&gt;data = 5; + node6-&gt;data = 6; + node7-&gt;data = 7; + node8-&gt;data = 8; + + node1-&gt;left = node2; + node1-&gt;right = node3; + + node2-&gt;left = node4; + node2-&gt;right = node5; + + node3-&gt;right = node6; + + node5-&gt;left = node7; + node5-&gt;right= node8; + + return node1; +} + +前序遍历(递归) +前序遍历顺序为根左右。要遍历整个二叉树我们就需要遍历二叉树的每一个子树,对于任何一个子树它的遍历方式均为根左右顺序遍历。即所有子问题均与父问题除规模大小不同外,其余均相同。所以可以采用递归方式实现前序遍历。 +// 前序遍历 根左右 +void pre_order_traversal(node *root) +{ + if(root) { + cout&lt;&lt;root-&gt;data&lt;&lt;&quot; &quot;; + pre_order_traversal(root-&gt;left); + pre_order_traversal(root-&gt;right); + } +} + +遍历结果为:1 2 4 5 7 8 3 6 +中序遍历(递归) +中序遍历顺序为左根右。其与前序遍历仅顺序不同,其余均相同。 +// 中序遍历 左根右 +void in_order_traversal(node *root) +{ + if(root) { + in_order_traversal(root-&gt;left); + cout&lt;&lt;root-&gt;data&lt;&lt;&quot; &quot;; + in_order_traversal(root-&gt;right); + } +} + +遍历结果为:4 2 7 5 8 1 3 6 +后序遍历(递归) +后序遍历顺序为左右根。其与前序、中序遍历仅顺序不同,其余均相同。 +// 后序遍历 左右根 +void post_order_traversal(node *root) +{ + if(root) { + post_order_traversal(root-&gt;left); + post_order_traversal(root-&gt;right); + cout&lt;&lt;root-&gt;data&lt;&lt;&quot; &quot;; + } +} + +遍历结果为:4 7 8 5 2 6 3 1 +前序遍历方法一(非递归) +因为递归实际上是由系统帮我们进行压栈,所以理论上所有递归算法都可以改为循环+栈实现,那么我们先照着上述前序遍历的样子修改为循环+栈的形态。需要注意的是由于栈先进后出的特性,为了保证左孩子在右孩子前被访问,所以应该先右孩子入栈,再左孩子入栈。 +// 前序遍历 根左右 +void pre_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + s.push(root); + + while(!s.empty()) { + + node *cur = s.top(); + s.pop(); + + if(cur) { + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + s.push(cur-&gt;right); + s.push(cur-&gt;left); + } + } +} + +遍历结果为:1 2 4 5 7 8 3 6 +前序遍历方法二(非递归) +现在我们换一种思路来实现前序非递归遍历,仔细观察前序遍历的递归调用过程。 + +先把从根结点开始的所有左子树放入栈中; +弹出栈顶元素 +如果栈顶元素有右子树,那么右子树入栈 +重复上述过程直到栈为空 + +因此我们可以写出遍历代码 +// 前序遍历 根左右 +void pre_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + node *cur = root; + + while(cur || !s.empty()) { + // 将左子树全部入栈 + while(cur) { + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + s.push(cur); + cur = cur-&gt;left; + } + + if(!s.empty()) { + cur = s.top(); + s.pop(); + cur = cur-&gt;right; + } + } +} + +遍历结果为:1 2 4 5 7 8 3 6 +中序遍历(非递归) +有了前面的基础,我们再来考虑中序遍历,会发现中序遍历与前序遍历只是打印结点的位置不一样。前序遍历是在结点入栈时打印,中序遍历只需要替换为在结点出栈时打印即可。 +// 中序遍历 左根右 +void in_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + node *cur = root; + + while(cur || !s.empty()) { + // 将左子树全部入栈 + while(cur) { + s.push(cur); + cur = cur-&gt;left; + } + + if(!s.empty()) { + cur = s.top(); + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + s.pop(); + cur = cur-&gt;right; + } + } +} + +遍历结果为:4 2 7 5 8 1 3 6 +后序遍历方法一(非递归) +后序遍历相对来说显得更加复杂了。在前序和中序遍历中,只要左子树处理完毕实际上栈顶元素就可以出栈了,但后序遍历需要把左子树和右子树都处理完毕才能出栈,显然我们需要某种方法记录遍历的过程。 +实际上我们只需要记录下遍历的前一个结点就能解决问题,因为通过前一个结点我们可以做如下判断: + +如果前一个结点是当前结点的右子树,那么说明右子树已经遍历完毕可以出栈了 +如果前一个结点是当前结点的左子树而且当前结点没有右子树,那么说明可以出栈了 +如果当前结点即没有左子树也没有右子树,即为叶子结点,那么说明可以出栈了 + +若不属于上述情况,则依次将当前结点的右孩子和做孩子入栈,这样就能保证每次取栈顶元素时,左孩子都在右孩子前面被访问,左孩子和右孩子都在父结点前面被访问。 +// 后序遍历 左右根 +void post_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + node *pre = NULL; + node *cur = root; + + s.push(cur); + + while(!s.empty()) { + cur = s.top(); + // 叶子结点 + if((!cur-&gt;left &amp;&amp; !cur-&gt;right) // 叶子结点 + || pre == cur-&gt;right // 前一个结点为当前结点右子树 + || (pre == cur-&gt;left &amp;&amp; !cur-&gt;right)) { // 前一个结点为当前结点左子树,且没有右子树 + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + pre = cur; + s.pop(); + } else { + if(cur-&gt;right) + s.push(cur-&gt;right); + + if(cur-&gt;left) + s.push(cur-&gt;left); + } + } +} + +遍历结果为:4 7 8 5 2 6 3 1 +后序遍历方法二(非递归) +后序遍历的顺序是左右根,如果把这个顺序倒过来就是根右左,是不是发现和前序遍历很像?那么我只需要按照根右左的方式遍历完,然后将遍历结果掉一个个儿就可以,而栈就具备掉个儿的功能,因此可写出如下代码。 +// 后序遍历 左右根 +void post_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + stack&lt;int&gt; ans; + node *cur = root; + + while(cur || !s.empty()) { + // 将左子树全部入栈 + while(cur) { + ans.push(cur-&gt;data); + s.push(cur); + cur = cur-&gt;right; + } + + if(!s.empty()) { + cur = s.top(); + s.pop(); + cur = cur-&gt;left; + } + } + + while(!ans.empty()) { + cout&lt;&lt;ans.top()&lt;&lt;&quot; &quot;; + ans.pop(); + } +} + +遍历结果为:4 7 8 5 2 6 3 1 +层序遍历 +层序遍历即广度优先遍历,使用队列即可实现。 +// 层序遍历 +void breadth_first_order_traversal(node *root) +{ + queue&lt;node *&gt; q; + q.push(root); + while(!q.empty()){ + node *cur = q.front(); + q.pop(); + if(cur){ + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + q.push(cur-&gt;left); + q.push(cur-&gt;right); + } + } +} + +遍历结果为:1 2 3 4 5 6 7 8 + +
+ + Read More ~ +
+
+
+ +
+

+ + 动态规划实例——01 背包详解 + +

+ +
+ + + + +
+ +
+ 题目描述 +有 n 件物品,每件物品有一个重量和一个价值,分别记为 w1,w2,…,wn 和 c1,c2,…,cn。现在有一个背包,其容量为 wk,要从 n 件物品种任取若干件。要求:(1)重量之和小于或等于 wk;(2)价值之和最大。 +输入: +共 3 行,第一行 2 个整数,表示 n 和 wk;第二行 n 个整数表示每一个物品的重量,第三行 n 个整数表示每一个物品的价值。 +输出: +一行一个整数,表示符合背包容量的最大价值。 +样例: +8 200 +79 58 86 11 28 62 15 68 +83 14 54 79 72 52 48 62 + +暴力枚举 +我们以只有 A、B、C 三件物品的情况为例,对于每一个物品都存在拿和不拿两种情况。以0表示不拿当前物品,以1表示拿当前物品,可以有如下分析结果。 + +可能上面的图看起来不够清晰,我们从左至右逐一列举出来观察,一眼就可以看出来规律。其实就是十进制的 0、1、2、3、4、......可枚举的最大值即 2n-1。 +000 +001 +010 +011 +100 +101 +110 +111 + +根据上面的分析,我们可以写出如下代码。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int main() +{ + int n, wk; + int w[10000], c[10000]; + cin&gt;&gt;n&gt;&gt;wk; + for(int i = 0; i &lt; n; i++){ + cin&gt;&gt;w[i]; + } + for(int i = 0; i &lt; n; i++){ + cin&gt;&gt;c[i]; + } + + int ans = 0; + int max_val = 1 &lt;&lt; n; + // 逐一枚举 + for(int i = 0; i &lt; max_val; i++){ + int ww = 0, cc = 0; + int index = 0; + // 转二进制 + int cur = i; + while(cur){ + int bit = cur % 2; + // 若拿第 index 个物品,则累加其重量和价值 + if(bit){ + ww += w[index]; + cc += c[index]; + } + cur = cur &gt;&gt; 1; + index++; + } + //计算最大值 + if(ww &lt;= wk &amp;&amp; ans &lt; cc){ + ans = cc; + } + } + //输出最大值 + cout&lt;&lt;ans&lt;&lt;endl; +} + +递归求解 +我们把背包容量为wk,有n个物品可以选择的问题表示为slove(wk, n)。那么在背包剩余容量可以装下第 n 个物品时,该问题可以表示为求如下两个问题的最大值 + +选第 n 个物品:c[n-1] + slove(wk-w[n-1], n-1) +不选第 n 个物品:slove(wk, n-1) + +在背包剩余容量无法装下第 n 个物品时,问题直接变为 + +不选第 n 个物品:slove(wk, n-1) + +可以发现上述三个子问题可以继续向下拆分为规模更小,但类型一致的子子问题。于是可以写出如下递归求解代码。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int w[30]={0}, c[30]={0}; + +// wk 背包剩余重量 +// ch 可选项 +int slove(int wk, int ch) +{ + if(wk &lt;= 0 || ch &lt;= 0){ + return 0; + } + + // 若背包剩余容量无法装下 w[ch-1],则直接丢弃第 ch 个物品 + if(w[ch-1] &gt; wk){ + return slove(wk, ch-1); + } + + // 若背包剩余容量能装下 w[ch-1],则计算装和不装的最大值 + int a = c[ch-1] + slove(wk-w[ch-1],ch-1); + int b = slove(wk, ch-1); + return a &gt; b ? a : b; +} + +int main() +{ + int n, wk; + cin&gt;&gt;n&gt;&gt;wk; + + for(int i = 0; i &lt; n; i++){ + cin&gt;&gt;w[i]; + } + for(int i = 0; i &lt; n; i++){ + cin&gt;&gt;c[i]; + } + cout&lt;&lt;slove(wk, n); +} + +动态规划 +递归在执行过程中会存在重复计算相同子问题的情况,我们可以将其改为用循环实现,即动态规划的写法。dp[i][j]的含义即为:在背包容量为i,可选物品数量为j的情况下,符合背包容量的最大值。具体代码如下所示: +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int w[30]={0}, c[30]={0}; + +int main() +{ + int n, wk; + cin&gt;&gt;n&gt;&gt;wk; + + for(int i = 0; i &lt; n; i++){ + cin&gt;&gt;w[i]; + } + for(int i = 0; i &lt; n; i++){ + cin&gt;&gt;c[i]; + } + + int dp[1000001][21] = { 0 }; + + for(int i = 1; i &lt;= wk; i++) { + for(int j = 1; j &lt;= n; j++) { + // 若背包剩余容量无法装下 w[j-1],则直接丢弃第 j 个物品 + if(w[j-1] &gt; i) { + dp[i][j] = dp[i][j-1]; + } else { + // 若背包剩余容量能装下 w[j-1],则计算装和不装的最大值 + int a = c[j-1] + dp[i-w[j-1]][j-1]; + int b = dp[i][j-1]; + dp[i][j] = a &gt; b ? a : b; + } + } + } + + cout&lt;&lt;dp[wk][n]; +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + Oracle 安装及 Spring 使用 Oracle + +

+ +
+ + + + +
+ +
+ +参考内容: +docker安装oracle数据库史上最全步骤(带图文) +Mac下oracle数据库客户端 +Docker安装Oracle +docker能安装oracle吗 +Batch script for add a auto-increased primary key for exist table with records + +Docker 安装 Oracle11g +注意:下列安装方式仅适用于x86架构服务器,不适用于arm架构服务器。 +# 拉取 oracle11,镜像有点大,需要花一些时间 +docker pull registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g + +# 查看镜像是否拉取成功 +docker images + +# 给镜像重新打 tag,原来的名字太长了 +docker tag registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g oracle11g:latest + +# 启动 oracle11g 容器 +docker run --name=oracle11g -itd -p 1521:1521 + +# 进入容器进行配置 +docker exec -it oracle11g /bin/bash + +# 切换到 root 用户,密码为:helowin +su root + +# 编辑配置文件 + +编辑/etc/profile,在其中增加如下内容: +export ORACLE_HOME=/home/oracle/app/oracle/product/11.2.0/dbhome_2 +export ORACLE_SID=helowin +export PATH=$ORACLE_HOME/bin:$PATH + +编辑完成后,需要刷新上述环境变量才能使用。 +# 刷新环境变量 +source /etc/profile + +# 创建软链接 +ln -s $ORACLE_HOME/bin/sqlplus /usr/bin + +# 切换到 oracle 用户 +su - oracle + +# 登陆 sqlplus +sqlplus /nolog +conn /as sysdba + +# 修改 system 用户密码 +alter user system identified by system; +# 修改 sys 用户密码 +alter user sys identified by system; + +# 创建内部管理员账号 +create user test identified by test; + +# 将 dba 权限授权给内部管理员账号和密码 +grant connect,resource,dba to test; + +# 修改密码规则策略为密码永不过期 +ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED; + +# 修改数据库最大连接数据 +alter system set processes=1000 scope=spfile; + +修改上述信息后,需要重新启动数据库才会生效。 +conn /as sysdba + +# 关闭数据库 +shutdown immediate; + +# 启动数据库 +startup; + +# 退出软链接 +exit; + +客户端连接 Oracle +以 Navicat 客户端为例,新建连接时按下图方式填写连接信息即可,密码即为system。需要注意的是,在 Windows 下选择 SID 或是服务名均可连接成功,但是在 Mac 下需要选择 SID 方式才能连接成功。 + +Oracle 实现主键自增 +Oracle 在创建表的时候,不能像 MySQL 那样选择主键直接自增,但是我们可以通过给表创建序列和触发器去实现自增。下文以创建 USER 表为例。 +-- 删除原有 USER 表 +DROP TABLE &quot;TEST&quot;.&quot;USER&quot;; +-- 创建 USER 表 +CREATE TABLE &quot;TEST&quot;.&quot;USER&quot; ( + &quot;id&quot; NUMBER NOT NULL, + &quot;gmt_create&quot; DATE NOT NULL, + &quot;gmt_modified&quot; DATE NOT NULL, + &quot;is_deleted&quot; NUMBER NOT NULL, + &quot;login&quot; NVARCHAR2(255) NOT NULL, + &quot;passwd&quot; NVARCHAR2(255) NOT NULL, + &quot;nick&quot; NVARCHAR2(255) NOT NULL, + &quot;phone&quot; NVARCHAR2(255), + &quot;head_img&quot; NVARCHAR2(255), + &quot;status&quot; NVARCHAR2(255), + &quot;remark&quot; NCLOB +); + +-- 删除原有序列 +DROP SEQUENCE &quot;TEST&quot;.&quot;USER_SEQ&quot;; +-- 创建 USER_SEQ 序列,最小值为 1 +CREATE SEQUENCE &quot;TEST&quot;.&quot;USER_SEQ&quot; +-- 最小值为 1 +MINVALUE 1 +-- 最大值为 9999999999999999999999999999 +MAXVALUE 9999999999999999999999999999 +-- 每次增加 1 +INCREMENT BY 1 +-- 将 20 个序列值放入缓存 +CACHE 20; + +-- 创建触发器 +CREATE TRIGGER &quot;TEST&quot;.&quot;USER_TRIGGER&quot; +-- 在插入数据前执行 +BEFORE INSERT ON &quot;TEST&quot;.&quot;USER&quot; +-- 命名老数据为 OLD,新数据为 NEW +REFERENCING OLD AS &quot;OLD&quot; NEW AS &quot;NEW&quot; +-- 针对表的每一行都执行触发器 +FOR EACH ROW +-- 将序列值赋值给 id +BEGIN + :NEW.&quot;id&quot; := USER_SEQ.NEXTVAL; +END; +/ + +需要注意的是,上面的/符号不能少。执行插入语句时可以发现,id会自动增加。 +INSERT INTO &quot;TEST&quot;.&quot;USER&quot; (&quot;gmt_create&quot;, &quot;gmt_modified&quot;, &quot;is_deleted&quot;, &quot;login&quot;, &quot;passwd&quot;, &quot;nick&quot;, &quot;phone&quot;, &quot;head_img&quot;, &quot;status&quot;, &quot;remark&quot;) VALUES (TO_DATE('2023-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), TO_DATE('2023-04-01 17:04:30', 'SYYYY-MM-DD HH24:MI:SS'), '0', 'user', '123', 'Jack', '1111', 'head.jpg', '激活', '测试'); + +Java Spring+Mybatis 使用 Oracle 及配置分页 +application.properties文件配置信息: +# mybatis +spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver +spring.datasource.url=jdbc:oracle:thin:@8127.0.0.1:1521:helowin +spring.datasource.username=system +spring.datasource.password=system +mybatis.mapper-locations=classpath*:mybatis/*.xml +mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl + +# pageHelper +pagehelper.helperDialect=oracle +pagehelper.reasonable=true +pagehelper.supportMethodsArguments=true +pagehelper.params=count=countSql + +pom.xml配置文件关键信息。 +&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; +&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; + xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt; + + &lt;properties&gt; + &lt;java.version&gt;1.8&lt;/java.version&gt; + &lt;maven.compiler.target&gt;1.8&lt;/maven.compiler.target&gt; + &lt;maven.compiler.source&gt;1.8&lt;/maven.compiler.source&gt; + &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt; + &lt;project.reporting.outputEncoding&gt;UTF-8&lt;/project.reporting.outputEncoding&gt; + + &lt;spring.boot-version&gt;2.1.3.RELEASE&lt;/spring.boot-version&gt; + &lt;/properties&gt; + + &lt;dependencyManagement&gt; + &lt;dependencies&gt; + &lt;dependency&gt; + &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; + &lt;artifactId&gt;spring-boot-dependencies&lt;/artifactId&gt; + &lt;version&gt;${spring.boot-version}&lt;/version&gt; + &lt;type&gt;pom&lt;/type&gt; + &lt;scope&gt;import&lt;/scope&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt; + &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt; + &lt;version&gt;2.1.0&lt;/version&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;com.oracle.ojdbc&lt;/groupId&gt; + &lt;artifactId&gt;ojdbc8&lt;/artifactId&gt; + &lt;version&gt;19.3.0.0&lt;/version&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt; + &lt;artifactId&gt;pagehelper-spring-boot-starter&lt;/artifactId&gt; + &lt;version&gt;1.4.3&lt;/version&gt; + &lt;/dependency&gt; + + &lt;dependency&gt; + &lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt; + &lt;artifactId&gt;pagehelper-spring-boot-starter&lt;/artifactId&gt; + &lt;/dependency&gt; + &lt;/dependencies&gt; + &lt;/dependencyManagement&gt; +&lt;/project&gt; + + +
+ + Read More ~ +
+
+
+ +
+

+ + 动态规划实例——换零钱的方法数(C++详解版) + +

+ +
+ + + + +
+ +
+ +原写了 Java 版本的如何求解换钱的方法数,近期进行了一些细节上的补充,以及部分错误更正,将语言换为了 C++ 语言。 + +基础题目 +假设你现在拥有不限量的 1 元、5 元、10 元面值纸币,路人甲希望找你换一些零钱,路人甲拿出的是一张 100 元面值的纸币,试求总共有多少种换零钱的方法? +分析:因为总共只有 3 种面值小额纸币,所以将所有可能进行枚举,直接暴力解决即可。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int slove() { + int ans = 0; + // 10 元张数 + for(int i = 0; i &lt;= 10; i++) { + // 5 元张数 + for(int j = 0; j &lt;= 20; j++) { + // 1 元张数 + for(int k = 0; k &lt;= 100; k++) { + int cur = i*10 + j*5 + k*1; + if(cur == 100) { + ans++; + } + } + } + } + return ans; +} + +int main() +{ + cout&lt;&lt;slove(); +} + +递归求解 +基础题目中是拥有固定种类的小额纸币,即使再多几种小额纸币也没关系,大不了在嵌套几个循环就能解决。现在需要将题目的难度加大一点,改为小额纸币的种类和需要换零钱的总额由用户输入,即小额纸币种类和总额都不在固定,那么如何解决? +输入共有三行: + +第一行:小额纸币种类数量 +第二行:不同小额纸币的面值 +第三行:需要换零钱的总额 + +分析:虽然现在种类和总额都是变量了,但是上文中的基础版本还是被包含在此问题中,所以我们还是以上文中的 1 元、5 元、10 元换 100 元进行分析,找一找除了枚举是否还有其他方法解决。 +我们先固定一种零钱的数量,剩下的钱用剩余零钱去兑换,即: + +用 0 张 1 元换,剩下的用 5、10 元换,最终方法数为 count0; +用 1 张 1 元换,剩下的用 5、10 元换,最终方法数为 count1; +...... +用 100 张 1 元换,剩下的用 5、10 元换,最终方法数为 count100; + +那么最终换钱的方法综述即为count0 + count1 + count2 + ... + count100。 +上面的分析中,我们把原来的大问题拆为了 101 个小问题,且每一个小问题都有很相似的地方,即: + +求用 5、10 元换 100 元的方法数 +求用 5、10 元换 95 元的方法数 +...... +求用 5、10 元换 0 元的方法数 + +如果我们对这 101 个小问题再进行同样思路的分析,即再固定 5 元零钱的数量,那么就能把问题划分成了规模更小,但问题类型一样的小小问题。即递归的思路,可以写出如下代码。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int money[1000]; // money 表示所有小额纸币的面值 +int len; // len 表示 money 数组的长度,即:小额纸币种类 + +// index 表示上文分析中的当前固定第几张 +// target 表示现在要兑换的钱的总额 +int slove(int index, int target) { + int ans = 0; + if(index == len) { + ans = target == 0 ? 1 : 0; + } else { + for(int i = 0; i*money[index] &lt;= target; i++) { + // 剩余待换零钱的总额 + int cur_total = target-(i * money[index]); + ans = ans + slove(index+1, cur_total); + } + } + return ans; +} + +int main() +{ + int target; + cin&gt;&gt;len; // 零钱种类 + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;money[i]; + } + cin&gt;&gt;target; // 兑换总额 + + cout&lt;&lt;slove(0, target); +} + +优化递归 +可以发现上文所写的递归代码存在大量的重复过程,比如下面两种情况,后面所求的子问题是完全一样的,导致程序运行时间的浪费。 + +已经使用了 5 张 1 元、0 张 5 元,剩下的 95 元用 5 元和 10 元兑换 +已经使用了 0 张 1 元、1 张 5 元,剩下的 95 元用 5 元 和 10 元兑换 + +既然前面已经求解过相同的子问题了,那么我们是否可以在第一次求解的时候,将计算结果保存下来,这样下次遇到相同子问题的实际,直接查出来用就可以,省去再次求解的时间。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int money[1000]; // money 表示所有小额纸币的面值 +int len; // len 表示 money 数组的长度,即:小额纸币种类 + +// 用于存储子问题的解 +int val_map[1000][1000] = { 0 }; + +// 0 表示该子问题没有算过 +// -1 表示算过,但该子问题无解 +// 其它值,即此子问题的方法数 + +int slove(int index, int target) { + int ans = 0; + if(index == len) { + ans = target == 0 ? 1 : 0; + } else { + for(int i = 0; i*money[index] &lt;= target; i++) { + // 剩余待换零钱的总额 + int cur_total = target-(i * money[index]); + int pre_val = val_map[index+1][cur_total]; + // 如果 val 为 0,说明该子问题没有被计算过 + if(pre_val == 0) { + ans = ans + slove(index+1, cur_total); + } else { + ans += pre_val == -1 ? 0 : pre_val; + } + } + } + // 存储计算结果 + val_map[index][target] = ans == 0 ? -1 : ans; + return ans; +} + +int main() +{ + int target; // 零钱种类 + cin&gt;&gt;len; + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;money[i]; + } + cin&gt;&gt;target; + + cout&lt;&lt;slove(0, target); +} + +动态规划 +上面对递归的优化方案已经能看出来动态规划的影子了,沿着前文先计算再查表的思路继续思考,我们能否提前把所有子问题都计算出答案,对每个子问题都进行查表解决。也即将最初的递归方案改为循环的实现。 + +所有的递归都能改为循环实现 + +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int money[1000]; // money 表示所有小额纸币的面值 +int len; // len 表示 money 数组的长度,即:小额纸币种类 + +// 用于存储子问题的解 +// val_map[i][j] 表示用 money[0...i] 的小面额零钱组成 j 元的方法数 +int val_map[1000][1000] = { 0 }; + +int slove(int target) { + + // 第一列表示组成 0 元的方法数,所以为 1 + for (int i = 0; i &lt; len; i++) { + val_map[i][0] = 1; + } + + // 第一行表示只使用 money[0] 一种钱币兑换钱数为i的方法数 + // 所以是 money[0] 的倍数的位置为 1,否则为 0 + for (int i = 1; money[0]*i &lt;= target; i++) { + val_map[0][money[0]*i] = 1; + } + + for (int i = 1; i &lt; len; i++) { + for (int j = 1; j &lt;= target; j++) { + for (int k = 0; j &gt;= money[i]*k; k++) { + /* + val_map[i][j] 的值为: + 用 money[0...i-1] 的零钱组成 j 减去 money[i] 的倍数的方法数 + 因为相比 val_map[i-1][j],只是多了一种零钱的可选项 + */ + val_map[i][j] += val_map[i-1][j-money[i]*k]; + } + } + } + + return val_map[len-1][target]; +} + +int main() +{ + int target; + cin&gt;&gt;len; + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;money[i]; + } + cin&gt;&gt;target; + + cout&lt;&lt;slove(target); +} + +动归优化 +在上文第一版动态规划代码的优化中已经能发现,其实val_map[i][j]的值由两部分组成,分别为: + +用 money[0...i-1] 的零钱组成换 j 元的方法数 +用 money[0...i-1] 的零钱换 j-money[i]*k(k=1,1,2,3....)元的方法数之和 + +对于第二种情况来说,其累加值实际上就是val_map[i][j-money[i]],即用money[0...i]的零钱换 j-money[i]元的方法数。至于具体为什么累加值与val_map[i][j-money[i]]相等,我们可以借助递归方法时的分析方式进行理解。 +用 money[0...i-1] 的零钱组成换 j 元的方法数对应: + +用 0 张 money[i] 换,剩下的用 money[0...i-1] 换 + +用 money[0...i-1] 的零钱换 j-money[i]*k(k=1,1,2,3....)元的方法数之和对应: + +用 1 张 money[i] 换,剩下的用 money[0...i-1] 换 +用 2 张 money[i] 换,剩下的用 money[0...i-1] 换 +...... + +所以第二部分的值即为val_map[i][j-money[i]]。依据此处的分析,我们可以在原有基础上去掉第三层循环,减少程序运行所花费的时间。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int money[1000]; +int len; + +int val_map[1000][1000] = { 0 }; + +int slove(int target) { + + for (int i = 0; i &lt; len; i++) { + val_map[i][0] = 1; + } + + for (int i = 1; money[0]*i &lt;= target; i++) { + val_map[0][money[0]*i] = 1; + } + + for (int i = 1; i &lt; len; i++) { + for (int j = 1; j &lt;= target; j++) { + val_map[i][j] = val_map[i-1][j]; + // 此处需要比较 j 的大小,防止数组越界 + // 注意条件时 &gt;= ,否则少计算 j 刚好为 money[i] 的情况 + if(j &gt;= money[i]) { + val_map[i][j] += val_map[i][j-money[i]]; + } + } + } + + return val_map[len-1][target]; +} + +int main() +{ + int target; + cin&gt;&gt;len; + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;money[i]; + } + cin&gt;&gt;target; + + cout&lt;&lt;slove(target); +} + +空间压缩 +仔细观察能发现,每一次更新val_map[i][j]的值时,它只依赖于上一行和当前这一行前面的元素。对于我们所求解的问题来说,它仅要求我们给出最终的答案即可,那么前面存储中间结果的那些元素实际上就会空间的浪费,因此我们可以思考一下如何在空间上进行压缩。 +实际上我们只需要定义一个一维的数组,采用一些技巧对该数组进行滚动更新,按照合适的方向去更新数组,同样可以达到上面使用二维数组的效果。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int money[1000]; +int len; + +int val_map[1000] = { 0 }; + +int slove(int target) { + + // 第一行,只用 money[0] 换零钱 + // 所以只能换 money[0] 倍数的钱 + for (int i = 0; money[0]*i &lt;= target; i++) { + val_map[money[0] * i] = 1; + } + + for (int i = 1; i &lt; len; i++) { + for (int j = 1; j &lt;= target; j++) { + if(j &gt;= money[i]) { + // 在进行下面一步前 val_map[j] 的值就已经是 val_map[i-1][j] 了 + val_map[j] += val_map[j-money[i]]; + } + } + } + + return val_map[target]; +} + +int main() +{ + int target; + cin&gt;&gt;len; + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;money[i]; + } + cin&gt;&gt;target; + + cout&lt;&lt;slove(target); +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + linux-5.10.157 内核源码编译 + +

+ +
+ + + + +
+ +
+ +参考内容: +Linux内核开发_1_编译LInux内核 +编译linux内核报错:flex: not foundscripts +编译kernel5.14报错fatal error: openssl/opensslv.h +编译内核错误——*** 没有规则可制作目标“debian/canonical-certs.pem” +内核错误:BTF: .tmp_vmlinux.btf: pahole (pahole) is not available + +# 切换到 root 账户 +sudo su + +# 查看操作系统版本 +cat /etc/issue + +# 查看 Linux 内核版本 +cat /proc/version + +# 进入 root 账户目录 +cd /home/root + +# 下载 Linux 内核源码 +wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.157.tar.xz +# Linux 其它版本源码 https://www.kernel.org/ + +# xz 解压 +xz -d linux-5.10.157.tar.xz + +# tar 解压到 /usr/src/linux-5.10.157 目录下 +tar -xf linux-5.10.157.tar -C /usr/src/. + +# 进入源码目录 +cd /usr/src/linux-5.10.157 + +# 查看源码结构 +tree . -L 2 + +# 若没有 tree 命令,可以执行下面命令 +# apt-get install tree + +# 配置编译选项 +make menuconfig + +# 若没有 make,可以执行下面命令 +# apt-get install make + +# 若执行 make 后报错找不到 curses.h,可以执行下面命令 +# apt-get install libncurses5-dev + +# 若报错找不到 flex not found,可以执行下面两条命令 +# apt-get install flex +# apt-get install bison + +# 再次运行 make menuconfig 弹出图形化配置页面后 +# 若使用默认配置,则直接按两次 Esc 键退出即可 +# 此时会在当前目录下生成 .config 文件 + +# 编译 Linux 源码 +make bzImage -j4 + +# 在编译过程中若报错 fatal error: openssl/opensslv.h,可执行下面命令 +# apt-get install libssl-dev +# 若还出现同样的问题,可参考 https://blog.csdn.net/ComputerInBook/article/details/107380796 源码编译安装 openssl + +# 若出现「没有规则可制作目标“debian/canonical-certs.pem”」报错 +# 需要删除 .config 中相应的字段,总共有两处 +# 一处为 CONFIG_SYSTEM_TRUSTED_KEYS=&quot;debian/canonical-certs.pem&quot; +# 一处为 CONFIG_SYSTEM_REVOCATION_KEYS=&quot;debian/canonical-revoked-certs.pem&quot; + +vim .config +# 删除之后的样子如下(需要保留引号): +# 一处为 CONFIG_SYSTEM_TRUSTED_KEYS=&quot;&quot; +# 一处为 CONFIG_SYSTEM_REVOCATION_KEYS=&quot;&quot; + +# 若出现 BTF: .tmp_vmlinux.btf: pahole (pahole) is not available 错误,则执行下面命令 +# apt-get install dwarves + +# 若在过程中还出现其它问题,大多是因为缺少相关库导致的,直接用 apt-get install 即可 + + +
+ + Read More ~ +
+
+
+ + + +
+ + + 下一页 + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/iqW8DWbw7/index.html b/iqW8DWbw7/index.html new file mode 100644 index 00000000..38e0db8f --- /dev/null +++ b/iqW8DWbw7/index.html @@ -0,0 +1,325 @@ + + + + + + + + MongoDB | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + MongoDB +
+ + +
+

+ + MongoDB 聚合(aggregate)入门 + +

+ +
+ + + + +
+ +
+ MongoDB 聚合官方文档 +聚合管道是一个基于数据处理管道概念建模的数据聚合框架,文档进入一个多阶段的处理管道,该管道最终将其转换为聚合后的结果。 +下面的例子来源于官方文档。第一阶段,$match按status字段来过滤文档,并把status字段值为A的文档传递到下一阶段;第二阶段,$group将文档按cust_id进行分组,并针对每一组数据对amount进行求和。 +db.orders.aggregate([ + { $match: { status: &quot;A&quot; } }, + { $group: { _id: &quot;$cust_id&quot;, total: { $sum: &quot;$amount&quot; } } } +]) + +管道 + +聚合管道包含很多步骤,每一步都会将输入的文档进行转换,但并不是每个阶段都一定需要对每个输入文档生成一个输出文档,比如某些阶段可能生成新的文档或者过滤掉文档。 +除了$out、$merge、$geoNear外,其它的阶段都可以在管道中多次出现,更加详细的内容可以查看 Aggregation Pipeline Stages。 + +管道表达式 +一些管道阶段采用表达式作为操作元,管道表达式指定了要应用到输入文档的转换,表达式自己是一个文档结构(JSON),表达式也可以包含其它的表达式。 +表达式仅提供文档在内存中的转换,即管道表达式只能对管道中的当前文档进行操作,不能引用来自其他文档的数据。 +写聚合表达式式建议直接参考官方文档,下面列出一些我收集的案例,供深入理解使用。 +案例一:将对象数组转换为单个文档 +// 转换前 +{ + &quot;_id&quot;: &quot;10217941&quot;, + &quot;data&quot;: [ + { + &quot;count&quot;: 2, + &quot;score&quot;: &quot;0.5&quot; + }, + { + &quot;count&quot;: 6, + &quot;score&quot;: &quot;0.3&quot; + }, + { + &quot;count&quot;: 5, + &quot;score&quot;: &quot;0.8&quot; + } + ] +} + +// 转换后 +{ + &quot;_id&quot;: &quot;10217941&quot;, + &quot;0.3&quot;: 6, + &quot;0.5&quot;: 2, + &quot;0.8&quot;: 5 +} + +需要说明的是,如果上面data属性中的数据格式为{&quot;k&quot;: &quot;0.6&quot;, &quot;v&quot;: 5},那么下面的聚合表达式就不需要$map,这一点可以查看 $arrayToObject。这个案例的难点在于score中有小数点,这个小数点会让聚合表达式懵逼的。 +db.collection.aggregate([ + { + &quot;$addFields&quot;: { + &quot;data&quot;: { + &quot;$arrayToObject&quot;: { + &quot;$map&quot;: { + &quot;input&quot;: &quot;$data&quot;, + &quot;as&quot;: &quot;item&quot;, + &quot;in&quot;: { + &quot;k&quot;: &quot;$$item.score&quot;, + &quot;v&quot;: &quot;$$item.count&quot; + } + } + } + } + } + }, + { + &quot;$addFields&quot;: { + &quot;data._id&quot;: &quot;$_id&quot; + } + }, + { + &quot;$replaceRoot&quot;: { + &quot;newRoot&quot;: &quot;$data&quot; + } + } +]); + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/iwTJpCGZp/index.html b/iwTJpCGZp/index.html new file mode 100644 index 00000000..66fa6dd8 --- /dev/null +++ b/iwTJpCGZp/index.html @@ -0,0 +1,697 @@ + + + + + + + + Git 基本原理及常用命令速查 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ Git 基本原理及常用命令速查 +

+ + +
+ +
+
+

参考内容:Pro Git book

+
+

如果你只是想查看 Git 常用命令可以选择直接到文章底部「Git 常用命令」阅读,文章大部分内容是 Git 进阶知识,均是自己的读书笔记,如果还想在此基础上再上一层楼,那可以直接看 Pro Git book

+

Git 历史

+

版本控制器是一种记录一个或若干文件内容变化,以便将来查阅特定版本的修订情况。也就是说,版本控制器记录了一个可供考证的历史数据,通过该数据可以知道文件是怎么一步一步发展到今天这个样子的。

+

最初 Linux 项目使用 BitKeeper 来管理和维护代码,但是到了 2005 年,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了免费使用 BitKeeper 的权力。那 Linux 开源社区的解决方案就是自己搞一个版本控制器,所以就有了 Git。

+

简单说就是 Linus 被逼的去开发了这一款叫做 Git 的版本控制器,因为 Linus 本身就是内核专家与文件专家,所以 Git 也就自然而然具备了非凡的存储能力与性能。

+

安装

+

关于如何安装 git 可以查看 Pro Git book,安装完成后需要进行一些必要的配置,比如用户信息、文本编辑器、差异分析工具等等,我们可以通过git config --list来查看配置信息。比如我们要配置用户和邮箱,就可以像下面这样输入命令。

+
$ git config --global user.name "John Doe"
+$ git config --global user.email johndoe@example.com
+
+

Git 原理

+

Git 和大多数版本控制器有一个重要的区别,就是它直接记录快照,而非差异比较,其它大部分系统以文件变更列表的方式存储信息,而 Git 则存储每个文件与初始版本的差异。换句话说,只要你的文件有改动,那么 Git 就会将该文件复制一份,正因为 Git 的这个特性,所以 Git 仓库很容易就变得非常大;为了高效,如果文件没有修改,那么 Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。Git 对待数据更像是一个快照流

+

Git 有三个区,分别为:仓库、工作目录、暂存区。基本的 Git 流程为:1)在工作目录中修改文件;2)暂存文件,将文件的快照放入暂存区域;3)提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。那么相应的 Git 就有三种状态:已提交(committed)、已修改(modified)和已暂存(staged),你的文件可能处于其中之一。

+
image
+

Git 基础

+

工作目录中的文件不外乎处于两种状态:已跟踪或未跟踪。已跟踪是指那些纳入了版本控制的文件,在上一次快照中有它们的记录;工作目录中除了已跟踪文件以外的所有文件都属于未跟踪文件,们既不存在于上次快照的记录中,也没有放入暂存区。

+
image
+

查看文件状态

+

如果需要查看哪些文件处于什么状态,可以使用git status命令,这个命令显示的信息十分详细,如果你喜欢简洁一点的信息,那么可以在其后添加一个-s,其报告格式类似于下面这样。

+
$ git status -s
+ M README
+MM Rakefile
+A  lib/git.rb
+M  lib/simplegit.rb
+?? LICENSE.txt
+
+

??表示新添加的未跟踪文件;修改过的文件前面有M标记,右边的表示还没有放入暂存区,左边的表示已经放入暂存区了。当然你可能不希望每个文件都出现在未跟踪列表中,比如编译过程临时创建的文件、日志文件等等,所以可以通过创建一个名为.gitignore 的文件,列出要忽略的文件模式,它支持标准的glob模式匹配(shell 所使用的简化了的正则表达式),在 gitignore 中有一个十分详细的针对数十种项目及语言的.gitignore文件列表。

+

git status对于具体修改显示的过于模糊,如果想查看具体修改了什么地方,可以使用git diff命令,比如git diff README.md。需要注意的是git diff本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动,如果需要查看已经暂存起来的变化,则要加上--staged或者--cached,比如git diff --cached README.md

+

删除文件

+

当然我们不可避免的需要删除某个文件,如果你仅仅是简单的从工作目录中手工删除文件,那它并没有真正的从 Git 中删除,Git 会将这次删除识别为一次改动。更好的方式是使用git rm命令来完成删除文件的工作,比如git rm README.md就会从已跟踪文件中删除,并且连带从工作目录中删除指定文件。

+

如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项-f(译注:即 force 的首字母)。 这是一种安全特性,用于防止误删还没有添加到快照的数据,这样的数据不能被 Git 恢复。

+

另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。 换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。 当你忘记添加 .gitignore 文件,不小心把一个很大的日志文件或一堆 .a 这样的编译生成文件添加到暂存区时,这一做法尤其有用。这时就需要使用--cached选项了,比如git rm --cached README

+

查看历史

+

我们或许因为某种原因需要回顾一下提交历史,这时git log就派上用场了,默认不用任何参数的话,git log会按提交时间列出所有的更新,最近的更新排在最上面,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。

+

git log提供的选项很多,更详细的内容可以查看 Git 基础 - 查看提交历史。除了不带选项的命令,我个人更常用的命令还有另外两个,分别为:git log --pretty=oneline它将每个提交放在一行显示,在查看的提交数很大时非常有用;git log --graph或者git log --pretty=oneline --graph用于显示 ASCII 图形表示的分支合并历史。

+

撤销操作

+

在任何一个阶段我们都可能有想要撤销的操作,我们只需要掌握几个基本的撤销操作就能够应对日常的工作了。

+

第一种情况:取消上一次提交。有时候当我们提交完之后才发现漏掉了几个文件没有添加,或者是提交信息写错了,此时可以使用带--amend选项的提交命令尝试重新提交,即git commit --amend。这个命令会将暂存区的文件全部提交,如果自上次提交以来你还没一做任何修改(比如,在上次提交后马上执行了此命令),那么快照将会保持不变,而所修改的只是提交信息。

+

第二种情况:取消暂存的文件。假设你修改了两个文件并且想要将它们作为两次独立提交,但是却不小心输入了git add *暂存了它们两个,如何取消其中一个暂存呢?其实在运行git status时已经给出提示了。

+
$ git status
+On branch master
+Changes to be committed:
+  (use "git reset HEAD <file>..." to unstage)
+
+	renamed:    README.md -> README
+    modified:   CONTRIBUTING.md
+
+

所以如果我们想要取消CONTRIBUTING.md的暂存,那么就可以用git reset HEAD CONTRIBUTING.md命令来完成。

+

第三种情况:撤销对文件的修改。有时候我们可能并不想保留对某个(若干)文件的修改,git status也给出了详细的提示,告诉我们如何将文件还原成上次提交时的样子,即git checkout -- <file>,比如输入命令git checkout -- CONTRIBUTING.md,就会将CONTRIBUTING.md重置到上一次提交时的样子。

+

需要注意的是git checkout -- <file>是一个比较危险的命令,因为它仅仅是拷贝了另一个文件来覆盖当前文件,所以你对那个文件的所有修改都会消失,而且不可恢复。

+

远程仓库

+

前面我们都是在讲本地操作,远程仓库的使用是必不可少的技能。可以使用git remote命令查看每一个远程服务器的简写,对于已经克隆的仓库,它至少会包含一个origin,这是 Git 给克隆仓库服务器取的默认名字,它和其它服务器并没有什么区别,只是很少人会去修改这个默认名字而已。

+

如果想要给一个远程仓库重新取一个简写名,那么可以运行git remote rename来完成,比如git remote rename pb paul就是将pb重命名为paul。值得注意的是这样同样也会修改你的远程分支名字,那些过去引用pb/master的现在全引用paul/master

+

当想要将自己的成果分享给他人时,就需要将其推送到上游,使用git push [remote-name] [branch-name]即可,比如你想要将master分支推送到origin服务器时,就可以运行git push origin master

+

除了分享自己的成果,我们也需要获取他人的成果,即从仓库拉取自己没有的信息,比如git fetch origin,需要注意的是git fetch命令会将数据拉取到你的本地仓库,但它并不会自动合并或修改你当前的工作,所以你还需要git merge来合并分支,实际上有一个git pull命令可以帮我们把这两个步骤都做了,你可以简单的将git pull理解为git fetch后面紧接着一个git merge

+

分支管理

+

Git 的分支模型是它的必杀技特性,它处理分支的方式是难以置信的轻量,创建分支几乎是在一瞬间完成,而且在不同分支间的切换也非常的便捷,要理解 Git 的分支,我们必须要再次回顾 Git 是如何保存数据的。

+

下图是我们的一个工作流,可以看到所谓的分支实际上就是一个可以移动的指针而已,masterv1.0都仅仅是一个指针,而创建分支、切换分支等操作也都只是对指针的操作,因此就不奇怪为什么 Git 这么快了。
+image

+

那么 Git 又是如何知道当前在哪一个分支上呢?它仅仅是用了一个名为HEAD的特殊指针,你可以将HEAD想象为当前分支的别名,HEAD指向哪个分支,就表示当前处于哪个分支。

+

分支创建与切换

+

我们可以使用git branch [branch-name]来创建一个新的分支,比如git branch testing;如果使用不带选项的git branch,那么它会列出当前所有的分支,这里需要注意的是master分支也不是特殊分支,它是运行git init时自动创建的默认分支,因为大家都懒得去改它,所以它就好像变得特殊了一样。

+

git branch [branch-name]只是创建了一个新分支,并不会切换到这个分支上面去,分支的切换说白了就是移动HEAD指针,我们只需要使用git checkout testing就可以切换到testing分支上去了。

+

当然我们可以使用git checkout -b [branch-name]来创建一个分支并同时切换到这个分支,把这个命令与git commit -a -m来对比,你就会发现它们的类似之处。

+

分支的合并与删除

+

当我们零时在一个新分支上解决了问题后,需要将其合并到master分支,只需要切换到master再运行git merge命令即可,Git 会自动找到这两个分支的共同祖先,然后做一个简单的三方合并。

+

当然理想情况下是直接合并成功,但是不免会遇到合并冲突的情况,一旦遇到冲突了,Git 会像下面这样来标记冲突内容,你需要做的是选择由=======分割的令部分的其中一个或者自行合并,当<<<<<<<=======,和>>>>>>>这些行被完全删除了,你需要对每个文件使用git add将其标记为冲突已解决。

+
<<<<<<< HEAD:index.html
+<div id="footer">contact : email.support@github.com</div>
+=======
+<div id="footer">
+ please contact us at support@github.com
+</div>
+>>>>>>> testing:index.html
+
+

当合并完分支后,之前的分支一般就不会再要了,这时你可以运行git branch -d [branch-name]来删除指定分支,比如使用git branch -d testing来删除testing分支。

+

远程分支

+

远程分支以(remote)/(branch)的形式来命名。如下图所示,如果你克隆一个仓库下来,那么这个仓库除了会有一个本地的分支指针,还会有一个远程分支指针。如果你在本地的master分支做了一些工作,但是你并没有与origin服务器连接,那么你的origin/master指针就不会移动。

+
image
+

在这之前我们已经讲过通过推送分享自己的成果,在运行git push origin master命令时,Git 会自动的将master分支名字展开为refs/heads/master:refs/heads/master,即意味着推送本地的master分支来更新远程仓库上的master分支,所以你也可以运行git push origin master:testing来做类似的事,如果远程仓库没有testing分支,那它会自己创建一个新的testing分支。

+

我们肯定需要创建一个跟踪远程仓库的其它分支,最简单的就是运行git checkout -b [new-branch] [remote-name]/[branch],该命令会以远端[branch]分支的内容来创建本地的[new-branch]分支,Git 也对该命令做了一个简化,git checkout --track [remote-name]/[branch],该命令就会在本地创建一个[branch]分支用于跟踪远端的[branch]分支。

+

当然,我们还需要了解一个删除远程分支的命令git push origin --delete [branch],需要注意的是这个命令基本上只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。

+

Git 常用命令

+

挑了一些比较重要 Git 命令,我把个人常用的命令使用代码块标记出来了。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
命令作用
git init将一个目录转变成一个 Git 仓库
git clone从远程克隆一个仓库到本地,它是多个命令的组合
git add将内容从工作目录添加到暂存区
git commit将暂存区文件在数据库中创建一个快照,然后将分支指针移到其上
git commit -a -m [msg]git addgit commit的组合
git status展示工作区及暂存区域中不同状态的文件
git status -sgit status展示的内容更加简洁
git diff对比工作目录文件和暂存区快照之间的差异
git diff --cached对比已暂存的差异
git reset根据你传递给动作的参数来执行撤销操作
git rm从工作区,或者暂存区移除文件
git clean从工作区中移除不想要的文件的命令
git checkout切换分支,或者检出内容到工作目录
git branch列出你所有的分支、创建新分支、删除分支及重命名分支
git checkout -b [branch]创建新分支并切换到该分支
git log展示历史记录
git log --pretty=oneline简洁版历史记录
git merge合并一个或者多个分支到已检出的分支中
git stash临时地保存一些还没有提交的工作
git pullgit fetch 和 git merge 命令的组合体
git push将本地工作内容推送到远程仓库
git push origin local_branch:remote_branchgit push更加详细的推送
git checkout --track [remote-name]/[branch]在本地创建一个分支用于跟踪远程同名分支
git remote -v显示所有远程仓库地址
git remote set-url origin [url]设置远程仓库地址为url
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/j7DVou3EA/index.html b/j7DVou3EA/index.html new file mode 100644 index 00000000..6ab5b265 --- /dev/null +++ b/j7DVou3EA/index.html @@ -0,0 +1,330 @@ + + + + + + + + Nginx | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + Nginx +
+ + +
+

+ + 如何加快 Nginx 的文件传输?——Linux 中的零拷贝技术 + +

+ +
+ + + + +
+ +
+ +参考内容: +Two new system calls: splice() and sync_file_range() +Linux 中的零拷贝技术1 +Linux 中的零拷贝技术2 +Zero Copy I: User-Mode Perspective +Linux man-pages splice() +Nginx AIO 机制与 sendfile 机制 +sendfile 适用场景 +扯淡 Nginx 的 sendfile 零拷贝的概念 +浅析 Linux 中的零拷贝技术 +Linux man-pages sendfile + +今天在看 Nginx 配置的时候,看到了一个sendfile配置项,它可以配置在http、server、location三个块中,出于好奇就去查了一下sendfile的作用。 +文件下载是服务器的基本功能,其基本流程就是循环的从磁盘读取文件内容到缓冲区,再将缓冲区内容发送到socket文件,程序员基本都会写出类似下面看起来比较高效的程序。 +while((n = read(diskfd, buf, BUF_SIZE)) &gt; 0) + write(sockfd, buf , n); + +上面程序中我们使用了read和write两个系统调用,看起来也已经没有什么优化空间了。这里的read和write屏蔽了系统内部的操作,我们并不知道操作系统做了什么,现实情况却是由于 Linux 的 I/O 操作默认是缓冲 I/O,上面的程序发生了多次不必要的数据拷贝与上下文切换。 +上述两行代码执行流程大致可以描述如下: + +系统调用read产生一个上下文切换,从用户态切换到内核态; +DMA 执行拷贝(现在都是 DMA 了吧!),把文件数据拷贝到内核缓冲区; +文件数据从内核缓冲区拷贝到用户缓冲区; +read调用返回,从内核态切换为用户态; +系统调用write产生一个上下文切换,从用户态切换到内核态; +把步骤 3 读到的数据从用户缓冲区拷贝到 Socket 缓冲区; +系统调用write返回,从内核态切换到用户态; +DMA 从 Socket 缓冲区把数据拷贝到协议栈。 + + +可以看到两行程序共发生了 4 次拷贝和 4 次上下文切换,其中 DMA 进行的数据拷贝不需要 CPU 访问数据,所以整个过程需要 CPU 访问两次数据。很明显中间有些拷贝和上下文切换是不需要的,sendfile就是来解决这个问题的,它是从 2.1 版本内核开始引入的,这里放个 2.6 版本的源码。 +系统调用sendfile是将in_fd的内容发送到out_fd,描述符out_fd在 Linux 2.6.33 之前,必须指向套接字文件,自 2.6.33 开始,out_fd可以是任何文件;in_fd只能是支持mmap的文件(mmap是一种内存映射方法,在被调用进程的虚拟地址空间中创建一个新的指定文件的映射)。 +所以当 Nginx 是一个静态服务器时,开启sendfile配置项是可以大大提高 Nginx 性能的,但是当把 Nginx 作为一个反向代理服务器时,sendfile则没有什么用,因为当 Nginx 时反向代理服务器时,in_fd就是一个套接字,这不符合sendfile的参数要求。 + +可以看到现在我们只需要一次拷贝就可以完成功能了,但是能否把这一次拷贝也省略掉呢?我们可以借助硬件来实现,仅仅需要把缓冲区描述符和文件长度传过去,这样 DMA 直接将缓冲区的数据打包发送到网络中就可以了。 +这样就实现了零拷贝技术,需要注意的是这里所说的零拷贝是相对操作系统而言的,即在内核空间不存在冗余数据。数据的实际走向是从硬盘到内存,再从内存到设备。 +Nginx 中还有一个aio配置,它的作用是启用内核级别的异步 I/O 功能,要使aio生效需要将directio开启(directio对大文件的读取速度有优化作用),aio很适合大文件的传送。需要注意的是sendfile和aio是互斥的,不可同时兼得二者,因此我们可以设置一个文件大小限制,超过该阀值使用aio,低于该阀值使用sendfile。 +location /video/ { + sendfile on; + sendfile_max_chunk 256k; + aio threads; + directio 512k; + output_buffers 1 128k; +} + +上面已经提到了零拷贝技术,它可以有效的改善数据传输的性能,但是由于存储体系结构非常复杂,而且网络协议栈有时需要对数据进行必要的处理,所以零拷贝技术有可能会产生很多负面影响,甚至会导致零拷贝技术自身的优点完全丧失。 +零拷贝就是一种避免 CPU 将一块存储拷贝到另一块存储的技术。它可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效的提高数据传输效率,而且零拷贝技术也减少了内核态与用户态之间切换所带来的开销。进行大量的数据拷贝操作是一件简单的任务,从操作系统的角度来看,如果 CPU 一直被占用着去执行这项简单的任务,是极其浪费资源的。如果是高速网络环境下,很可能就出现这样的场景。 +零拷贝技术分类 +现在的零拷贝技术种类很多,也并没有一个适合于所有场景的零拷贝零拷贝技术,概括起来总共有下面几种: + + +直接 I/O:对于这种数据传输方式来说,应用程序可以直接访问硬件存储,操作系统只是辅助数据传输,这类零拷贝技术可以让数据在应用程序空间和磁盘之间直接传输,不需要操作系统提供的页缓存支持。关于直接 I/O 可以参看Linux 中直接 I/O 机制的介绍。 + + +避免数据在内核态与用户态之间传输:在一些场景中,应用程序在数据进行传输的过程中不需要对数据进行访问,那么将数据从页缓存拷贝到用户进程的缓冲区是完全没有必要的,Linux 中提供的类似系统调用主要有mmap()、sendfile()和splice()。 + + +对数据在页缓存和用户进程之间的传输进行优化:这类零拷贝技术侧重于灵活地处理数据在用户进程的缓冲区和操作系统页缓存之间的拷贝操作,此类方法延续了传统的通信方式,但是更加灵活。在 Linux 中主要利用了「写时复制」技术。 + + +前两类方法的目的主要是为了避免在用户态和内核态的缓冲区间拷贝数据,第三类方法则是对数据传输本身进行优化。我们知道硬件和软件之间可以通过 DMA 来解放 CPU,但是在用户空间和内核空间并没有这种工具,所以此类方法主要是改善数据在用户地址空间和操作系统内核地址空间之间传递的效率。 +避免在内核与用户空间拷贝 +Linux 主要提供了mmap()、sendfile()、splice()三个系统调用来避免数据在内核空间与用户空间进行不必要的拷贝,在Nginx 文件操作优化对sendfile()已经做了比较详细的介绍了,这里就不再赘述了,下面主要介绍mmap()和splice()。 +mmap() +当调用mmap()之后,数据会先通过 DMA 拷贝到操作系统的缓冲区,然后应用程序和操作系统共享这个缓冲区,这样用户空间与内核空间就不需要任何数据拷贝了,当大量数据需要传输的时候,这样做就会有一个比较好的效率。 +但是这种改进是需要代价的,当对文件进行了内存映射,然后调用write()系统调用,如果此时其它进程截断了这个文件,那么write()系统调用将会被总线错误信号SIGBUG中断,因为此时正在存储的是一个错误的存储访问,这个信号将会导致进程被杀死。 +一般可以通过文件租借锁来解决这个问题,我们可以通过内核给文件加读或者写的租借锁,当另外一个进程尝试对用户正在进行传输的文件进行截断时,内核会给用户发一个实时RT_SIGNAL_LEASE信号,这个信号会告诉用户内核破坏了用户加在那个文件上的写或者读租借锁,write()系统调用就会被中断,并且进程会被SIGBUS信号杀死。需要注意的是文件租借锁需要在对文件进行内存映射之前设置。 +splice() +和sendfile()类似,splice()也需要两个已经打开的文件描述符,并且其中的一个描述符必须是表示管道设备的描述符,它可以在操作系统地址空间中整块地移动数据,从而减少大多数数据拷贝操作。适用于可以确定数据传输路径的用户应用程序,不需要利用用户地址空间的缓冲区进行显示的数据传输操作。 +splice()不局限于sendfile()的功能,也就是说sendfile()是splice()的一个子集,在 Linux 2.6.23 中,sendfile()这种机制的实现已经没有了,但是这个 API 以及相应的功能还存在,只不过内部已经使用了splice()这种机制来实现了。 +写时复制 +在某些情况下,Linux 操作系统内核中的页缓存可能会被多个应用程序所共享,操作系统有可能会将用户应用程序地址空间缓冲区中的页面映射到操作系统内核地址空间中去。如果某个应用程序想要对这共享的数据调用write()系统调用,那么它就可能破坏内核缓冲区中的共享数据,传统的write()系统调用并没有提供任何显示的加锁操作,Linux 中引入了写时复制这样一种技术用来保护数据。 +写时复制的基本思想是如果有多个应用程序需要同时访问同一块数据,那么可以为这些应用程序分配指向这块数据的指针,在每一个应用程序看来,它们都拥有这块数据的一份数据拷贝,当其中一个应用程序需要对自己的这份数据拷贝进行修改的时候,就需要将数据真正地拷贝到该应用程序的地址空间中去,也就是说,该应用程序拥有了一份真正的私有数据拷贝,这样做是为了避免该应用程序对这块数据做的更改被其他应用程序看到。这个过程对于应用程序来说是透明的,如果应用程序永远不会对所访问的这块数据进行任何更改,那么就永远不需要将数据拷贝到应用程序自己的地址空间中去。这也是写时复制的最主要的优点。 +写时复制的实现需要 MMU 的支持,MMU 需要知晓进程地址空间中哪些特殊的页面是只读的,当需要往这些页面中写数据的时候,MMU 就会发出一个异常给操作系统内核,操作系统内核就会分配新的物理存储空间,即将被写入数据的页面需要与新的物理存储位置相对应。它最大好处就是可以节约内存,不过对于操作系统内核来说,写时复制增加了其处理过程的复杂性。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/jSM-A51qWd/index.html b/jSM-A51qWd/index.html new file mode 100644 index 00000000..1649e5dd --- /dev/null +++ b/jSM-A51qWd/index.html @@ -0,0 +1,289 @@ + + + + + + + + LDO | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + LDO +
+ + +
+

+ + LDO 基础知识 + +

+ +
+ + + + +
+ +
+ +参考内容: +ADI 公司 LDO 电容选型指南 +线性和低压降 (LDO) 稳压器 + +BUCK 电路通过控制占空比来达到降压的目的,添加 LC 二阶低通滤波器将高频部分滤除,即可达到稳定输出直流的目的。但是滤波不能完全滤除高频分量,BUCK 从原理上就决定了其纹波不容易做到很小,其固有的开关频率会导致电源噪声很大,用来给噪声敏感的元器件供电就不合适。 +相比 BUCK 来说,LDO(Low Dropout Regulaor:低压差线性稳压器)输出的电压会更加平稳,可以弥补 BUCK 输出纹波大的缺点。 +总体框图 +线性稳压器主要由四部分组成,基准源用于提供精准的电压基准、导通器件用于控制从 VIN 到 VOUT 的电流大小、误差放大器将强制反馈节点与基准电压匹配、反馈电阻用于调整以改变输出电压。 + +从框图中也可以看到线性稳压器只能用于降压,因此输入电压必须高于输出电压。当然其名字中本身带了低压差的,低压差就意味着少的发热,意味着电源转化效率的提升。线性则是指器件的工作状态,器件的内部模块工作在放大区,放大状态呈线性关系。 +工作原理 +线性稳压器的工作可以模拟为两个电阻器和一个用于 VIN 的电源,其中电源用于给负载供电,通过调整可变电阻(导通器件)的阻值来控制负载电阻所获得的电压,整个系统中唯一不变的恒定的参数就是输出电压 VOUT。 + +其稳压过程如下图所示,当负载电压升高/降低时,采样电路所采到的电压就跟着升高/降低,传递给误差放大器后通过调节导通器件的导通程度来调节输出电压。 + +导通器件 +导通器件常见的有 PMOS、NMOS、BJT 等。BJT 应用于大电流的场景。PMOS 不需要额外的电源轨即可控制其导通程度,但是相比 NMOS 其 RDSon 更大,即 PMOS 架构的 LDO 在芯片本身所消耗的能量会更大。 + +使用 NMOS 作为导通器件时,需要添加辅助电源轨或者使用电荷泵才能将 NMOS 打开。当然电荷泵也有其缺点,虽然电荷泵可以提升 VIN,但是也带来了额外的噪声影响。若采用辅助电源轨时则需要注意,VBIAS 会影响 NMOS 的导通程度,进而影响输出电压的大小。 + +PSRR +PSRR(Power Supply Rejection Ratio)量化了 LDO 抑制任何电源变化传递到其输出信号的能力,也就是 PSRR 决定了输入耦合到输出的噪声有多少。除了 LDO 本身的设计影响 PSRR 外,也可以通过调整 VIN 与 VOUT 之间的差值、输出电容来提高在特定应用(频率)下的 PSRR。 + + +输入输出电容 +为了确保 LDO 稳定工作,会在 LDO 输入输出端增加旁路电容,并且旁路电容的 ESR 需要很小,即在符合最小电容和最大 ESR 的要求下,使用任何质量良好的电容都可采用。在选择电容时还需要注意由于直流电压偏置、温度变化、制造商容差等需要对电容进行一定的降额。 +输出电容除了可以进行滤波外,还会影响负载电流的变化的瞬态响应,采用较大的输出电容可以改善 LDO 对大负载电流变化的瞬态响应。输入电容则可以降低电路对 PCB 布局的敏感性,尤其是在长输入走线或者高源阻抗的情况下。 +多层陶瓷电容、固态钽点解电容、铝电解电容通常用作输入和输出旁路电容。多层陶瓷电容具备 ESR 和 ESL 低、工作温度范围宽的优点,但是陶瓷电容中的介质材料具备压电性,振动或机械冲击可能会转化为电容上的交流噪声电压,在极端情况下可能会产生 mV 级的噪声。 + +压电性是在某些固体材料(晶体、陶瓷、骨头、DNA、蛋白质等)受到机械应力作用后,在材料中聚集电荷的现象。「压电」即由压力产生的电。 + +钽电容的优点是单位体积电容最高(CV 乘积),并且不太容易受到温度、偏执电压、震动效应的影响,在无法容忍压电效应的低噪声应用中,钽电容基本是唯一可行的选择。与陶瓷电容相比,钽电容的泄漏电流要比等值的陶瓷电容大很多倍,不适合超低电流应用。 +铝电解电容往往体积较大、ESR 和 ESL 较高,漏电流相对较高,与钽电容一样不受压电效应影响,适合要求低噪声的应用场合,但是铝电解电容在航天应用中禁止使用。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/kLV2cAImc/index.html b/kLV2cAImc/index.html new file mode 100644 index 00000000..6bc3cd3b --- /dev/null +++ b/kLV2cAImc/index.html @@ -0,0 +1,545 @@ + + + + + + + + KMP 算法详解 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ KMP 算法详解 +

+ + +
+ +
+
+

参考内容:

+

通俗易懂的 KMP 算法详解

+
+

许许多多的软件都有搜索文本的功能,这个搜索的过程其实就是一个字符串匹配的过程。由此我们可以提出来一个问题。

+

给你两个字符串spp的长度不超过s的长度,并且ps都不是空串),请问s中是否包含p,若包含请给出第一次包含的开始位置?比如:

+
    +
  • s="hello, string"p="str",那么s包含p
  • +
  • 比如s="github"p="str",那么s不包含p
  • +
+

暴力求解

+

分析题目我们很容易就能想到解决方法。设置两个指针,分别为ij,并且将两个指针都初始化为0。对比s[i]p[j]的值,如果两个值相等,那么同时将ij移动到下一个位置。否则i回退到1j回退到0,继续上述过程。如果在下一次匹配过程中还是出现了不想等的字符,那么i回退到2j回退到0,继续......。直到某次匹配中,j达到越界位置,那么就可以判定s包含p,此时i的值即开始位置,否则s就不包含p

+
#include<bits/stdc++.h>
+using namespace std;
+
+int main()
+{
+	char p[300], s[300];
+	cin>>p>>s;
+	int len_p = strlen(p);
+	int len_s = strlen(s);
+	if(len_p > len_s){
+		cout<<-1<<endl;
+		return 0;
+	}
+	
+	int j = 0, ans = -1;
+	for(int i = 0; i < len_s; i++){
+		if(p[j] == s[i]){
+			if(j == 0){
+				ans = i;
+			}
+			if(j == len_p-1){
+				break;
+			}
+			j++;
+		} else {
+			j = 0;
+			ans = -1;
+		}
+	}
+	
+	if(j == len_p-1){
+		cout<<ans<<endl;
+	} else {
+		cout<<-1<<endl;
+	}
+	
+}
+
+

当然,你也可以使用 C 语言所提供的库函数进行实现,比如strstrstrcmpstrncmp等,此处不再赘述相关实现。

+

优化分析

+

我们来看一下s="aaaaaaaaab"p="aaab"的匹配过程。

+
+
+

当我们发现第一次匹配失败时,我们只能选择下一个开头,重新从开头继续匹配。此时指向s的指针就会回退,并且此前已经匹配的部分将全部丢弃,因此暴力求解的方法是很低效的。

+

我们肉眼可以直观的看到如果匹配失败了,并不需要全部回退,也就是我们可以借助此前匹配过程留下的信息,帮助我们加速匹配过程。很明显能加速匹配过程的办法就是尽量少回退甚至不回退指针,那么我们一起来探究一下如何在少回退指针的前提下,还能保证不错过正确答案。

+

因为我们是在s中寻找是否存在p,即当前i指针前面的字符都已经对比过了,所以理论上我们是可以不回退i指针的,那么保证i指针不回退了,又如何调整回退j指针的规则呢?

+
    +
  • 肯定j指针需要回退
  • +
  • 需要保证j指针回退后,s[i]p[j]之前的所有字符都是匹配的
  • +
  • 指针j不能因为回退少了,导致我们错过正确答案
  • +
+

下面我们举几个例子。

+
+

那如果已经匹配的部分存在多个前缀和后缀匹配的情况怎么办呢?

+
+

总结一下:在保证指向s的指针不回退的时候,我们需要找到前面已经匹配部分的前后缀最大匹配长度,而且这个前后缀最大匹配长度是可以直接通过p的值计算出来的(即next数组)。

+ +
+
+ + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/krqSF7tYC/index.html b/krqSF7tYC/index.html new file mode 100644 index 00000000..67b3a296 --- /dev/null +++ b/krqSF7tYC/index.html @@ -0,0 +1,2374 @@ + + + + + + + + 算法 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 算法 +
+ + +
+

+ + 并查集详解及相关例题解析 + +

+ +
+ + + + +
+ +
+ +参考内容: +图论——并查集(详细版) + +并查集(Disjoint-set)是一种精巧的树形数据结构,它主要用于处理一些不相交集合的合并及查询问题。一些常见用途,比如求联通子图、求最小生成树的 Kruskal 算法和求最近公共祖先(LCA)等。 +并查集的理念是只关注个体属于哪个阵营,并不关心这个阵营中个体内部的关系,比如我们常说的张三是李家沟的,王二是王家坝的。同时并查集借助个体代表集体的思想,用一个元素代表整个群体,就像我们开学都会有学生代表、教师代表讲话一样,在台上讲话的那一个学生就代表了学校所有的学生。 +并查集基本操作 +并查集的基本操作主要有初始化 init、查询 find和合并 union操作。 +初始化 +在使用并查集的时候,常常使用一个数组fa来存储每个元素的父节点,在一开始的时候所有元素与其它元素都没有任何关系,即大家相互之间还不认识,所以我们把每个元素的父节点设为自己。 +#define ARR_LEN 6000 + +int fa[ARR_LEN]; + +void init(int n) +{ + for(int i = 1; i &lt;= n; i++) + fa[i] = i; +} + +查询 +查询即找到指定元素的祖先。需要注意的是,这里我们需要找到指定元素的根祖先,不能找到爸爸或者爷爷就停止了,而是要找到查找不下去了为止,所以要不断的去递归下去,直到找到父亲为自己的结点才结束。 +int find(int i) +{ + if(i == fa[i]) // 递归出口 + return i; + else + return find(fa[i]); // 不断向上查找祖先 +} + +考虑下面的场景,假如第一次我们需要查询元素5的祖先,第二次需要查询元素4的祖先,会发现第一次查询包含了第二次查询的计算过程,但我们的程序却傻傻的计算了两次,有没有办法去来优化查询过程,让每一次查询都能利用到此前查询计算的便利? + +考虑到并查集并不关心某个元素的爸爸、爷爷是谁,只关心最终的祖先是谁,所以我们可以在查询的过程中顺便做一些修改,比如在查询5的过程中,顺便就把4和2的父亲给修改为1,即我们在查找过程中进行路经压缩 +int find(int i) +{ + if(i == fa[i]){ + return i; + } else { + fa[i] = find(fa[i]); // 进行路径压缩 + return fa[i]; + } +} + +合并 +合并操作即介绍两个人相互认识,将他们纳入同一个帮派,只需要将俩元素的父亲修改为同一个即可。 +void union(int i, int j) +{ + int fa_i = find(i); + int fa_j = find(j); + fa[fa_i] = fa_j; +} + +相关练习题目 +洛谷 P1551 亲戚 +题目连接:https://www.luogu.com.cn/problem/P1551 +题目描述 +若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。 +规定:xxx 和 yyy 是亲戚,yyy 和 zzz 是亲戚,那么 xxx 和 zzz 也是亲戚。如果 x,yx,yx,y 是亲戚,那么 xxx 的亲戚都是 yyy 的亲戚,yyy 的亲戚也都是 xxx 的亲戚。 +输入格式 +第一行:三个整数 n,m,p,(n,m,p≤5000)n,m,p,(n,m,p≤5000)n,m,p,(n,m,p≤5000) 分别表示有 nnn 个人,mmm 个亲戚关系,询问 ppp 对亲戚关系。 +以下 mmm 行:每行两个数 Mi,Mj,1≤Mi,Mj≤nM_i,M_j,1≤M_i,M_j≤nMi​,Mj​,1≤Mi​,Mj​≤n,表示 MiM_iMi​ 和 MjM_jMj​ 具有亲戚关系。 +接下来 ppp 行:每行两个数 Pi,PjP_i,P_jPi​,Pj​,询问 PiP_iPi​ 和 PjP_jPj​ 是否具有亲戚关系。 +输出格式 +ppp 行,每行一个Yes或No。表示第 iii 个询问的答案为“具有”或“不具有”亲戚关系。 +输入输出样例 +# 输入 +6 5 3 +1 2 +1 5 +3 4 +5 2 +1 3 +1 4 +2 3 +5 6 + +# 输出 +Yes +Yes +No + +题目解析 +可以发现这是一个非常标准的并查集问题,简直和并查集模版如出一辙,因此直接将所有关系读取后进行合并,然后直接查询父亲是否为同一个即可。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +#define ARR_LEN 6000 + +int fa[ARR_LEN]; + +void init(int n) +{ + for(int i = 1; i &lt;= n; i++) + fa[i] = i; +} + +int find(int i) +{ + if(i == fa[i]){ + return i; + } else { + fa[i] = find(fa[i]); + return fa[i]; + } +} + +void union(int i, int j) +{ + int fa_i = find(i); + int fa_j = find(j); + fa[fa_i] = fa_j; +} + + +int main() +{ + int n, m, p; + int a, b; + + cin&gt;&gt; n &gt;&gt; m &gt;&gt; p; + + init(n); + + for(int i = 0; i &lt; m; i++){ + cin &gt;&gt; a &gt;&gt; b; + union(a, b); + } + + for(int i = 0; i &lt; p; i++){ + cin &gt;&gt; a &gt;&gt; b; + int fa_a = find(a); + int fa_b = find(b); + + if(fa_a == fa_b) + cout&lt;&lt;&quot;Yes&quot;&lt;&lt;endl; + else + cout&lt;&lt;&quot;No&quot;&lt;&lt;endl; + } +} + +杭电 OJ1213 How Many Tables +题目连接:https://acm.hdu.edu.cn/showproblem.php?pid=1213 +题目描述 +Today is Ignatius' birthday. He invites a lot of friends. Now it's dinner time. Ignatius wants to know how many tables he needs at least. You have to notice that not all the friends know each other, and all the friends do not want to stay with strangers. +One important rule for this problem is that if I tell you A knows B, and B knows C, that means A, B, C know each other, so they can stay in one table. +For example: If I tell you A knows B, B knows C, and D knows E, so A, B, C can stay in one table, and D, E have to stay in the other one. So Ignatius needs 2 tables at least. +输入格式 +The input starts with an integer T(1&lt;=T&lt;=25)T(1&lt;=T&lt;=25)T(1&lt;=T&lt;=25) which indicate the number of test cases. Then TTT test cases follow. Each test case starts with two integers NNN and M(1&lt;=N,M&lt;=1000)M(1&lt;=N,M&lt;=1000)M(1&lt;=N,M&lt;=1000). NNN indicates the number of friends, the friends are marked from 111 to NNN. Then MMM lines follow. Each line consists of two integers AAA and B(A!=B)B(A!=B)B(A!=B), that means friend AAA and friend BBB know each other. There will be a blank line between two cases. +输出格式 +For each test case, just output how many tables Ignatius needs at least. Do NOT print any blanks. +输入输出样例 +# 输入 +2 +5 3 +1 2 +2 3 +4 5 + +5 1 +2 5 + +# 输出 +2 +4 + +题目解析 +分析可以发现,这个问题要我们做的是统计在所有元素合并之后,统计总共有多个和集合。很轻松就能写出下面的 AC 代码。类似的问题还有杭电 OJ1232 畅通工程。 +读者大人可以在此基础上继续进行延伸,我们实际生活中每个桌子只能坐 8 个人,假设还需要考虑每桌人数的容量,又如何进行改进呢? +#include&lt;bits/stdc++.h&gt; +using namespace std; + +#define ARR_LEN 6000 + +int fa[ARR_LEN]; + +void init(int n) +{ + for(int i = 1; i &lt;= n; i++) + fa[i] = i; +} + +int find(int i) +{ + if(i == fa[i]){ + return i; + } else { + fa[i] = find(fa[i]); + return fa[i]; + } +} + +void union(int i, int j) +{ + int fa_i = find(i); + int fa_j = find(j); + fa[fa_i] = fa_j; +} + + +int main() +{ + int n, m, a, b, t; + + cin&gt;&gt;t; + for(int i = 0; i &lt; t; i++){ + cin&gt;&gt;n&gt;&gt;m; + int ans = 0; + init(n); + for(int i = 0; i &lt; m; i++) { + cin&gt;&gt;a&gt;&gt;b; + union(a, b); + } + + for(int i = 1; i &lt;= n; i++) { + // 如果父亲是自己,那么就表示一个独立的集合 + if(find(i) == i) + ans++; + } + + cout&lt;&lt;ans&lt;&lt;endl; + } + +} + +杭电 OJ1272 小希的迷宫 +题目连接:https://acm.hdu.edu.cn/showproblem.php?pid=1272 +题目描述 +小希设计了一个迷宫让 Gardon 玩,首先她认为所有的通道都应该是双向连通的,就是说如果有一个通道连通了房间 A 和 B,那么既可以通过它从房间 A 走到房间 B,也可以通过它从房间 B 走到房间 A,为了提高难度,小希希望任意两个房间有且仅有一条路径可以相通(除非走了回头路)。小希现在把她的设计图给你,让你帮忙判断她的设计图是否符合她的设计思路。比如下面的例子,前两个是符合条件的,但是最后一个却有两种方法从 5 到达 8。 + +输入格式 +输入包含多组数据,每组数据是一个以 0 0 结尾的整数对列表,表示了一条通道连接的两个房间的编号。房间的编号至少为 1,且不超过 100000。每两组数据之间有一个空行。整个文件以两个 -1 结尾。 +输出格式 +对于输入的每一组数据,输出仅包括一行。如果该迷宫符合小希的思路,那么输出Yes,否则输出No。 +输入输出样例 +# 输入 +6 8 5 3 5 2 6 4 +5 6 0 0 + +8 1 7 3 6 2 8 9 7 5 +7 4 7 8 7 6 0 0 + +3 8 6 8 6 4 +5 3 5 6 5 2 0 0 + +-1 -1 + +# 输出 +Yes +Yes +No + +题目解析 +其实这个问题就是让我们判断一个连通图中是否存在环,那么问题就转换为寻找出现环的条件。其实不难发现出现下面两种情况时,连通图即存在环。 + +在查找过程中,发现两个不同元素的父亲是相同的; +若不存在环,则边的数量一定比顶点数量少 1。 + +#include&lt;bits/stdc++.h&gt; +using namespace std; + +#define ARR_LEN 100010 + +int fa[ARR_LEN]; +bool visited[ARR_LEN]; // 用于辅助记录顶点的数量 +int edges, points; // 记录顶点和边的数量 +bool hascycle; // 是否存在环 + +void init() +{ + hascycle = false; + edges = 0; + points = 0; + for(int i = 1; i &lt; ARR_LEN; i++) + fa[i] = i, visited[i] = false; +} + +int find(int i) +{ + if(i == fa[i]){ + return i; + } else { + fa[i] = find(fa[i]); + return fa[i]; + } +} + +void union(int i, int j) +{ + int fa_i = find(i); + int fa_j = find(j); + + // 两个元素祖先相同,存在环 + if(fa_i == fa_j) { + hascycle = true; + } else { + visited[i] = true; + visited[j] = true; + edges++; + fa[fa_i] = fa_j; + } +} + + +int main() +{ + int a, b; + + init(); + + while(cin&gt;&gt;a&gt;&gt;b) { + if(a == 0 &amp;&amp; b == 0) { + cout&lt;&lt;&quot;Yes&quot;&lt;&lt;endl; + continue; + } + + if(a == -1 &amp;&amp; b == -1) { + return 0; + } + + union(a, b); + + while(cin&gt;&gt;a&gt;&gt;b){ + if(a == 0 &amp;&amp; b == 0) { + break; + } + union(a, b); + } + + if(hascycle) { + cout&lt;&lt;&quot;No&quot;&lt;&lt;endl; + continue; + } + + for(int i = 1; i &lt; ARR_LEN; i++){ + if(visited[i]) { + points++; + } + } + + if(points == edges + 1) { + cout&lt;&lt;&quot;Yes&quot;&lt;&lt;endl; + } else { + cout&lt;&lt;&quot;No&quot;&lt;&lt;endl; + } + init(); + } +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + KMP 算法详解 + +

+ +
+ + + + +
+ +
+ +参考内容: +通俗易懂的 KMP 算法详解 + +许许多多的软件都有搜索文本的功能,这个搜索的过程其实就是一个字符串匹配的过程。由此我们可以提出来一个问题。 +给你两个字符串s和p(p的长度不超过s的长度,并且p和s都不是空串),请问s中是否包含p,若包含请给出第一次包含的开始位置?比如: + +s=&quot;hello, string&quot;,p=&quot;str&quot;,那么s包含p; +比如s=&quot;github&quot;,p=&quot;str&quot;,那么s不包含p。 + +暴力求解 +分析题目我们很容易就能想到解决方法。设置两个指针,分别为i和j,并且将两个指针都初始化为0。对比s[i]和p[j]的值,如果两个值相等,那么同时将i和j移动到下一个位置。否则i回退到1,j回退到0,继续上述过程。如果在下一次匹配过程中还是出现了不想等的字符,那么i回退到2,j回退到0,继续......。直到某次匹配中,j达到越界位置,那么就可以判定s包含p,此时i的值即开始位置,否则s就不包含p。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int main() +{ + char p[300], s[300]; + cin&gt;&gt;p&gt;&gt;s; + int len_p = strlen(p); + int len_s = strlen(s); + if(len_p &gt; len_s){ + cout&lt;&lt;-1&lt;&lt;endl; + return 0; + } + + int j = 0, ans = -1; + for(int i = 0; i &lt; len_s; i++){ + if(p[j] == s[i]){ + if(j == 0){ + ans = i; + } + if(j == len_p-1){ + break; + } + j++; + } else { + j = 0; + ans = -1; + } + } + + if(j == len_p-1){ + cout&lt;&lt;ans&lt;&lt;endl; + } else { + cout&lt;&lt;-1&lt;&lt;endl; + } + +} + +当然,你也可以使用 C 语言所提供的库函数进行实现,比如strstr、strcmp、strncmp等,此处不再赘述相关实现。 +优化分析 +我们来看一下s=&quot;aaaaaaaaab&quot;和p=&quot;aaab&quot;的匹配过程。 + + +当我们发现第一次匹配失败时,我们只能选择下一个开头,重新从开头继续匹配。此时指向s的指针就会回退,并且此前已经匹配的部分将全部丢弃,因此暴力求解的方法是很低效的。 +我们肉眼可以直观的看到如果匹配失败了,并不需要全部回退,也就是我们可以借助此前匹配过程留下的信息,帮助我们加速匹配过程。很明显能加速匹配过程的办法就是尽量少回退甚至不回退指针,那么我们一起来探究一下如何在少回退指针的前提下,还能保证不错过正确答案。 +因为我们是在s中寻找是否存在p,即当前i指针前面的字符都已经对比过了,所以理论上我们是可以不回退i指针的,那么保证i指针不回退了,又如何调整回退j指针的规则呢? + +肯定j指针需要回退 +需要保证j指针回退后,s[i]和p[j]之前的所有字符都是匹配的 +指针j不能因为回退少了,导致我们错过正确答案 + +下面我们举几个例子。 + +那如果已经匹配的部分存在多个前缀和后缀匹配的情况怎么办呢? + +总结一下:在保证指向s的指针不回退的时候,我们需要找到前面已经匹配部分的前后缀最大匹配长度,而且这个前后缀最大匹配长度是可以直接通过p的值计算出来的(即next数组)。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 《算法竞赛进阶指南》165 小猫爬山题解 + +

+ +
+ + + + +
+ +
+ +参考内容: +[洛谷][noip][算法竞赛进阶指南]小猫爬山 +《算法竞赛进阶指南》小猫爬山 + +小猫爬山 +题目描述 +题目链接:https://www.acwing.com/problem/content/167/ +​Freda 和 Rainbow 饲养了 N 只小猫。这天,小猫们要去爬山。经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕&gt;_&lt;)。 +Freda 和 Rainbow 只好花钱让它们坐索道下山。索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是C1、C2……Cn。当然,每辆缆车上的小猫的重量之和不能超过 W。每租用一辆缆车,Freda 和 Rainbow 就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山? +输入格式 +​第一行包含两个用空格隔开的整数,N 和 W。接下来 N 行每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。 +输出格式 +输出一个整数,最少需要多少美元,也就是最少需要多少辆缆车。 +分析解答 +贪心 +经过思考发现,我们只需要尽可能的在每辆车上都放更多的小猫,就能以最经济的方式把所有小猫都送下山。所以是一个非常明显的贪心题目,我们将所有小猫按重量排序,尽可能把肥猫先送下山即可。具体实现代码如下: +#include&lt;bits/stdc++.h&gt; +using namespace std; + +void cin_arr(int *num, int len) +{ + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;num[i]; + } +} + +int slove(int *num, int n, int w) +{ + int ans = 0; + int remain = 0; + int load = 0; + + sort(num, num+n); + + while(true){ + for(int i = 0; i &lt; n; i++){ + // 连当前最小的猫都装不下,那么就新开一辆车 + if(num[i] != -1 &amp;&amp; remain &lt; num[i]){ + ans++; + remain = w; + break; + } + } + + for(int i = n-1; i &gt;= 0; i--){ + // 从大到小查找,尽可能装肥猫 + if(num[i] != -1 &amp;&amp; remain &gt;= num[i]){ + remain -= num[i]; + // 运送走的小猫重量以 -1 表示 + num[i] = -1; + load++; + break; + } + } + + // 如果所有小猫都运走了,那么当前 ans 就是答案 + if(load &gt;= n) + return ans; + } +} + +int main() +{ + int n, w; + int cat[1000000]; + + cin&gt;&gt;n&gt;&gt;w; + cin_arr(cat, n); + + cout&lt;&lt;slove(cat, n, w)&lt;&lt;endl; +} + +经过实际测试发现,盲目使用贪心思想的算法并不正确,例如如下测试用例。 +6 16 +9 5 5 5 4 3 + +贪心的结果是使用 3 辆车,分别为9+5、5+5+4、3;而正确的结果却是使用 2 辆车,分别为9+4+3和5+5+5。 +深度优先搜索 +既然贪心思想在这里行不通,那么我们就采用暴力搜索,即小猫可以放在现有任意一辆车上。具体实现代码如下: +#include&lt;bits/stdc++.h&gt; +using namespace std; + +#define N 2000 + +int n, w; +int cat[N]; +int sum[N] = {0}; // 第 i 辆车当前重量 +int ans = N; + +void cin_arr(int *num, int len) +{ + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;num[i]; + } +} + +void dfs(int cur_cat, int cur_car) +{ + if(cur_car &gt; ans) // 求最小值,不符合直接返回 + return ; + + if(cur_cat == n) { // 所有小猫都上车了 + ans = cur_car; + return ; + } + + for(int i = 0; i &lt; cur_car; i++) { + if(sum[i] + cat[cur_cat] &lt;= w) { // 当前猫能放进去 + sum[i] += cat[cur_cat]; // 当前猫占用重量 + dfs(cur_cat+1, cur_car); // 继续放下一只猫 + sum[i] -= cat[cur_cat]; // 把已经放进去的猫拿出来,因为是循环,所以放入下一辆车里面 + } + } + + // 新开一辆车,把当前这只猫放到新的车里面 + sum[cur_car] = cat[cur_cat]; + dfs(cur_cat+1, cur_car+1); + sum[cur_car] = 0; // 把猫拿出来 +} + +int main() +{ + cin&gt;&gt;n&gt;&gt;w; + cin_arr(cat, n); + dfs(0, 0); + cout&lt;&lt;ans&lt;&lt;endl; +} + +搜索优化 +考虑到每次都是在车数量固定的情况下进行搜索的,那么少满足一次(sum[i] + cat[cur_cat] &lt;= w)条件,就会少一次递归的调用,也即少一次搜索。那么如何能尽快使得程序尽快不满足该条件呢? +让sum[i]减小的速度加快就会减少搜索分支,即每次放更重一点的猫进去,就能达到效果。所以我们可以在进行搜索前将小猫的重量进行降序排序,这样从肥猫开始搜索就会减少分支。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +#define N 2000 + +int n, w; +int cat[N]; +int sum[N] = {0}; // 第 i 辆车当前重量 +int ans = N; + +void cin_arr(int *num, int len) +{ + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;num[i]; + } +} + +bool cmp(int a, int b) +{ + return a &gt; b; +} + +void dfs(int cur_cat, int cur_car) +{ + if(cur_car &gt; ans) // 求最小值,不符合直接返回 + return ; + + if(cur_cat == n) { // 所有小猫都上车了 + ans = cur_car; + return ; + } + + for(int i = 0; i &lt; cur_car; i++) { + if(sum[i] + cat[cur_cat] &lt;= w) { // 当前猫能放进去 + sum[i] += cat[cur_cat]; // 当前猫占用重量 + dfs(cur_cat+1, cur_car); // 继续放下一只猫 + sum[i] -= cat[cur_cat]; // 把已经放进去的猫拿出来,因为是循环,所以放入下一辆车里面 + } + } + + // 新开一辆车,把当前这只猫放到新的车里面 + sum[cur_car] = cat[cur_cat]; + dfs(cur_cat+1, cur_car+1); + sum[cur_car] = 0; // 把猫拿出来 +} + +int main() +{ + cin&gt;&gt;n&gt;&gt;w; + cin_arr(cat, n); + sort(cat, cat+n, cmp); // 反排序优化搜索 + dfs(0, 0); + cout&lt;&lt;ans&lt;&lt;endl; +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + 二叉树的前序、中序、后序、层序遍历 + +

+ +
+ + + + +
+ +
+ +参考内容: +五分钟让你彻底理解二叉树的非递归遍历 +Python实现二叉树的非递归遍历 +二叉树遍历——深度优先(前中后序)+广度优先(层序遍历) + +构造二叉树 +定义二叉树结构如下 +struct node +{ + int data; + node *left; + node *right; +}; + +构造如下形态二叉树 + +node *init_tree() +{ + node *node1 = (node *)malloc(sizeof(node)); + node *node2 = (node *)malloc(sizeof(node)); + node *node3 = (node *)malloc(sizeof(node)); + node *node4 = (node *)malloc(sizeof(node)); + node *node5 = (node *)malloc(sizeof(node)); + node *node6 = (node *)malloc(sizeof(node)); + node *node7 = (node *)malloc(sizeof(node)); + node *node8 = (node *)malloc(sizeof(node)); + + node1-&gt;data = 1; + node2-&gt;data = 2; + node3-&gt;data = 3; + node4-&gt;data = 4; + node5-&gt;data = 5; + node6-&gt;data = 6; + node7-&gt;data = 7; + node8-&gt;data = 8; + + node1-&gt;left = node2; + node1-&gt;right = node3; + + node2-&gt;left = node4; + node2-&gt;right = node5; + + node3-&gt;right = node6; + + node5-&gt;left = node7; + node5-&gt;right= node8; + + return node1; +} + +前序遍历(递归) +前序遍历顺序为根左右。要遍历整个二叉树我们就需要遍历二叉树的每一个子树,对于任何一个子树它的遍历方式均为根左右顺序遍历。即所有子问题均与父问题除规模大小不同外,其余均相同。所以可以采用递归方式实现前序遍历。 +// 前序遍历 根左右 +void pre_order_traversal(node *root) +{ + if(root) { + cout&lt;&lt;root-&gt;data&lt;&lt;&quot; &quot;; + pre_order_traversal(root-&gt;left); + pre_order_traversal(root-&gt;right); + } +} + +遍历结果为:1 2 4 5 7 8 3 6 +中序遍历(递归) +中序遍历顺序为左根右。其与前序遍历仅顺序不同,其余均相同。 +// 中序遍历 左根右 +void in_order_traversal(node *root) +{ + if(root) { + in_order_traversal(root-&gt;left); + cout&lt;&lt;root-&gt;data&lt;&lt;&quot; &quot;; + in_order_traversal(root-&gt;right); + } +} + +遍历结果为:4 2 7 5 8 1 3 6 +后序遍历(递归) +后序遍历顺序为左右根。其与前序、中序遍历仅顺序不同,其余均相同。 +// 后序遍历 左右根 +void post_order_traversal(node *root) +{ + if(root) { + post_order_traversal(root-&gt;left); + post_order_traversal(root-&gt;right); + cout&lt;&lt;root-&gt;data&lt;&lt;&quot; &quot;; + } +} + +遍历结果为:4 7 8 5 2 6 3 1 +前序遍历方法一(非递归) +因为递归实际上是由系统帮我们进行压栈,所以理论上所有递归算法都可以改为循环+栈实现,那么我们先照着上述前序遍历的样子修改为循环+栈的形态。需要注意的是由于栈先进后出的特性,为了保证左孩子在右孩子前被访问,所以应该先右孩子入栈,再左孩子入栈。 +// 前序遍历 根左右 +void pre_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + s.push(root); + + while(!s.empty()) { + + node *cur = s.top(); + s.pop(); + + if(cur) { + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + s.push(cur-&gt;right); + s.push(cur-&gt;left); + } + } +} + +遍历结果为:1 2 4 5 7 8 3 6 +前序遍历方法二(非递归) +现在我们换一种思路来实现前序非递归遍历,仔细观察前序遍历的递归调用过程。 + +先把从根结点开始的所有左子树放入栈中; +弹出栈顶元素 +如果栈顶元素有右子树,那么右子树入栈 +重复上述过程直到栈为空 + +因此我们可以写出遍历代码 +// 前序遍历 根左右 +void pre_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + node *cur = root; + + while(cur || !s.empty()) { + // 将左子树全部入栈 + while(cur) { + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + s.push(cur); + cur = cur-&gt;left; + } + + if(!s.empty()) { + cur = s.top(); + s.pop(); + cur = cur-&gt;right; + } + } +} + +遍历结果为:1 2 4 5 7 8 3 6 +中序遍历(非递归) +有了前面的基础,我们再来考虑中序遍历,会发现中序遍历与前序遍历只是打印结点的位置不一样。前序遍历是在结点入栈时打印,中序遍历只需要替换为在结点出栈时打印即可。 +// 中序遍历 左根右 +void in_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + node *cur = root; + + while(cur || !s.empty()) { + // 将左子树全部入栈 + while(cur) { + s.push(cur); + cur = cur-&gt;left; + } + + if(!s.empty()) { + cur = s.top(); + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + s.pop(); + cur = cur-&gt;right; + } + } +} + +遍历结果为:4 2 7 5 8 1 3 6 +后序遍历方法一(非递归) +后序遍历相对来说显得更加复杂了。在前序和中序遍历中,只要左子树处理完毕实际上栈顶元素就可以出栈了,但后序遍历需要把左子树和右子树都处理完毕才能出栈,显然我们需要某种方法记录遍历的过程。 +实际上我们只需要记录下遍历的前一个结点就能解决问题,因为通过前一个结点我们可以做如下判断: + +如果前一个结点是当前结点的右子树,那么说明右子树已经遍历完毕可以出栈了 +如果前一个结点是当前结点的左子树而且当前结点没有右子树,那么说明可以出栈了 +如果当前结点即没有左子树也没有右子树,即为叶子结点,那么说明可以出栈了 + +若不属于上述情况,则依次将当前结点的右孩子和做孩子入栈,这样就能保证每次取栈顶元素时,左孩子都在右孩子前面被访问,左孩子和右孩子都在父结点前面被访问。 +// 后序遍历 左右根 +void post_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + node *pre = NULL; + node *cur = root; + + s.push(cur); + + while(!s.empty()) { + cur = s.top(); + // 叶子结点 + if((!cur-&gt;left &amp;&amp; !cur-&gt;right) // 叶子结点 + || pre == cur-&gt;right // 前一个结点为当前结点右子树 + || (pre == cur-&gt;left &amp;&amp; !cur-&gt;right)) { // 前一个结点为当前结点左子树,且没有右子树 + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + pre = cur; + s.pop(); + } else { + if(cur-&gt;right) + s.push(cur-&gt;right); + + if(cur-&gt;left) + s.push(cur-&gt;left); + } + } +} + +遍历结果为:4 7 8 5 2 6 3 1 +后序遍历方法二(非递归) +后序遍历的顺序是左右根,如果把这个顺序倒过来就是根右左,是不是发现和前序遍历很像?那么我只需要按照根右左的方式遍历完,然后将遍历结果掉一个个儿就可以,而栈就具备掉个儿的功能,因此可写出如下代码。 +// 后序遍历 左右根 +void post_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + stack&lt;int&gt; ans; + node *cur = root; + + while(cur || !s.empty()) { + // 将左子树全部入栈 + while(cur) { + ans.push(cur-&gt;data); + s.push(cur); + cur = cur-&gt;right; + } + + if(!s.empty()) { + cur = s.top(); + s.pop(); + cur = cur-&gt;left; + } + } + + while(!ans.empty()) { + cout&lt;&lt;ans.top()&lt;&lt;&quot; &quot;; + ans.pop(); + } +} + +遍历结果为:4 7 8 5 2 6 3 1 +层序遍历 +层序遍历即广度优先遍历,使用队列即可实现。 +// 层序遍历 +void breadth_first_order_traversal(node *root) +{ + queue&lt;node *&gt; q; + q.push(root); + while(!q.empty()){ + node *cur = q.front(); + q.pop(); + if(cur){ + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + q.push(cur-&gt;left); + q.push(cur-&gt;right); + } + } +} + +遍历结果为:1 2 3 4 5 6 7 8 + +
+ + Read More ~ +
+
+
+ +
+

+ + 动态规划实例——01 背包详解 + +

+ +
+ + + + +
+ +
+ 题目描述 +有 n 件物品,每件物品有一个重量和一个价值,分别记为 w1,w2,…,wn 和 c1,c2,…,cn。现在有一个背包,其容量为 wk,要从 n 件物品种任取若干件。要求:(1)重量之和小于或等于 wk;(2)价值之和最大。 +输入: +共 3 行,第一行 2 个整数,表示 n 和 wk;第二行 n 个整数表示每一个物品的重量,第三行 n 个整数表示每一个物品的价值。 +输出: +一行一个整数,表示符合背包容量的最大价值。 +样例: +8 200 +79 58 86 11 28 62 15 68 +83 14 54 79 72 52 48 62 + +暴力枚举 +我们以只有 A、B、C 三件物品的情况为例,对于每一个物品都存在拿和不拿两种情况。以0表示不拿当前物品,以1表示拿当前物品,可以有如下分析结果。 + +可能上面的图看起来不够清晰,我们从左至右逐一列举出来观察,一眼就可以看出来规律。其实就是十进制的 0、1、2、3、4、......可枚举的最大值即 2n-1。 +000 +001 +010 +011 +100 +101 +110 +111 + +根据上面的分析,我们可以写出如下代码。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int main() +{ + int n, wk; + int w[10000], c[10000]; + cin&gt;&gt;n&gt;&gt;wk; + for(int i = 0; i &lt; n; i++){ + cin&gt;&gt;w[i]; + } + for(int i = 0; i &lt; n; i++){ + cin&gt;&gt;c[i]; + } + + int ans = 0; + int max_val = 1 &lt;&lt; n; + // 逐一枚举 + for(int i = 0; i &lt; max_val; i++){ + int ww = 0, cc = 0; + int index = 0; + // 转二进制 + int cur = i; + while(cur){ + int bit = cur % 2; + // 若拿第 index 个物品,则累加其重量和价值 + if(bit){ + ww += w[index]; + cc += c[index]; + } + cur = cur &gt;&gt; 1; + index++; + } + //计算最大值 + if(ww &lt;= wk &amp;&amp; ans &lt; cc){ + ans = cc; + } + } + //输出最大值 + cout&lt;&lt;ans&lt;&lt;endl; +} + +递归求解 +我们把背包容量为wk,有n个物品可以选择的问题表示为slove(wk, n)。那么在背包剩余容量可以装下第 n 个物品时,该问题可以表示为求如下两个问题的最大值 + +选第 n 个物品:c[n-1] + slove(wk-w[n-1], n-1) +不选第 n 个物品:slove(wk, n-1) + +在背包剩余容量无法装下第 n 个物品时,问题直接变为 + +不选第 n 个物品:slove(wk, n-1) + +可以发现上述三个子问题可以继续向下拆分为规模更小,但类型一致的子子问题。于是可以写出如下递归求解代码。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int w[30]={0}, c[30]={0}; + +// wk 背包剩余重量 +// ch 可选项 +int slove(int wk, int ch) +{ + if(wk &lt;= 0 || ch &lt;= 0){ + return 0; + } + + // 若背包剩余容量无法装下 w[ch-1],则直接丢弃第 ch 个物品 + if(w[ch-1] &gt; wk){ + return slove(wk, ch-1); + } + + // 若背包剩余容量能装下 w[ch-1],则计算装和不装的最大值 + int a = c[ch-1] + slove(wk-w[ch-1],ch-1); + int b = slove(wk, ch-1); + return a &gt; b ? a : b; +} + +int main() +{ + int n, wk; + cin&gt;&gt;n&gt;&gt;wk; + + for(int i = 0; i &lt; n; i++){ + cin&gt;&gt;w[i]; + } + for(int i = 0; i &lt; n; i++){ + cin&gt;&gt;c[i]; + } + cout&lt;&lt;slove(wk, n); +} + +动态规划 +递归在执行过程中会存在重复计算相同子问题的情况,我们可以将其改为用循环实现,即动态规划的写法。dp[i][j]的含义即为:在背包容量为i,可选物品数量为j的情况下,符合背包容量的最大值。具体代码如下所示: +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int w[30]={0}, c[30]={0}; + +int main() +{ + int n, wk; + cin&gt;&gt;n&gt;&gt;wk; + + for(int i = 0; i &lt; n; i++){ + cin&gt;&gt;w[i]; + } + for(int i = 0; i &lt; n; i++){ + cin&gt;&gt;c[i]; + } + + int dp[1000001][21] = { 0 }; + + for(int i = 1; i &lt;= wk; i++) { + for(int j = 1; j &lt;= n; j++) { + // 若背包剩余容量无法装下 w[j-1],则直接丢弃第 j 个物品 + if(w[j-1] &gt; i) { + dp[i][j] = dp[i][j-1]; + } else { + // 若背包剩余容量能装下 w[j-1],则计算装和不装的最大值 + int a = c[j-1] + dp[i-w[j-1]][j-1]; + int b = dp[i][j-1]; + dp[i][j] = a &gt; b ? a : b; + } + } + } + + cout&lt;&lt;dp[wk][n]; +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + 动态规划实例——换零钱的方法数(C++详解版) + +

+ +
+ + + + +
+ +
+ +原写了 Java 版本的如何求解换钱的方法数,近期进行了一些细节上的补充,以及部分错误更正,将语言换为了 C++ 语言。 + +基础题目 +假设你现在拥有不限量的 1 元、5 元、10 元面值纸币,路人甲希望找你换一些零钱,路人甲拿出的是一张 100 元面值的纸币,试求总共有多少种换零钱的方法? +分析:因为总共只有 3 种面值小额纸币,所以将所有可能进行枚举,直接暴力解决即可。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int slove() { + int ans = 0; + // 10 元张数 + for(int i = 0; i &lt;= 10; i++) { + // 5 元张数 + for(int j = 0; j &lt;= 20; j++) { + // 1 元张数 + for(int k = 0; k &lt;= 100; k++) { + int cur = i*10 + j*5 + k*1; + if(cur == 100) { + ans++; + } + } + } + } + return ans; +} + +int main() +{ + cout&lt;&lt;slove(); +} + +递归求解 +基础题目中是拥有固定种类的小额纸币,即使再多几种小额纸币也没关系,大不了在嵌套几个循环就能解决。现在需要将题目的难度加大一点,改为小额纸币的种类和需要换零钱的总额由用户输入,即小额纸币种类和总额都不在固定,那么如何解决? +输入共有三行: + +第一行:小额纸币种类数量 +第二行:不同小额纸币的面值 +第三行:需要换零钱的总额 + +分析:虽然现在种类和总额都是变量了,但是上文中的基础版本还是被包含在此问题中,所以我们还是以上文中的 1 元、5 元、10 元换 100 元进行分析,找一找除了枚举是否还有其他方法解决。 +我们先固定一种零钱的数量,剩下的钱用剩余零钱去兑换,即: + +用 0 张 1 元换,剩下的用 5、10 元换,最终方法数为 count0; +用 1 张 1 元换,剩下的用 5、10 元换,最终方法数为 count1; +...... +用 100 张 1 元换,剩下的用 5、10 元换,最终方法数为 count100; + +那么最终换钱的方法综述即为count0 + count1 + count2 + ... + count100。 +上面的分析中,我们把原来的大问题拆为了 101 个小问题,且每一个小问题都有很相似的地方,即: + +求用 5、10 元换 100 元的方法数 +求用 5、10 元换 95 元的方法数 +...... +求用 5、10 元换 0 元的方法数 + +如果我们对这 101 个小问题再进行同样思路的分析,即再固定 5 元零钱的数量,那么就能把问题划分成了规模更小,但问题类型一样的小小问题。即递归的思路,可以写出如下代码。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int money[1000]; // money 表示所有小额纸币的面值 +int len; // len 表示 money 数组的长度,即:小额纸币种类 + +// index 表示上文分析中的当前固定第几张 +// target 表示现在要兑换的钱的总额 +int slove(int index, int target) { + int ans = 0; + if(index == len) { + ans = target == 0 ? 1 : 0; + } else { + for(int i = 0; i*money[index] &lt;= target; i++) { + // 剩余待换零钱的总额 + int cur_total = target-(i * money[index]); + ans = ans + slove(index+1, cur_total); + } + } + return ans; +} + +int main() +{ + int target; + cin&gt;&gt;len; // 零钱种类 + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;money[i]; + } + cin&gt;&gt;target; // 兑换总额 + + cout&lt;&lt;slove(0, target); +} + +优化递归 +可以发现上文所写的递归代码存在大量的重复过程,比如下面两种情况,后面所求的子问题是完全一样的,导致程序运行时间的浪费。 + +已经使用了 5 张 1 元、0 张 5 元,剩下的 95 元用 5 元和 10 元兑换 +已经使用了 0 张 1 元、1 张 5 元,剩下的 95 元用 5 元 和 10 元兑换 + +既然前面已经求解过相同的子问题了,那么我们是否可以在第一次求解的时候,将计算结果保存下来,这样下次遇到相同子问题的实际,直接查出来用就可以,省去再次求解的时间。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int money[1000]; // money 表示所有小额纸币的面值 +int len; // len 表示 money 数组的长度,即:小额纸币种类 + +// 用于存储子问题的解 +int val_map[1000][1000] = { 0 }; + +// 0 表示该子问题没有算过 +// -1 表示算过,但该子问题无解 +// 其它值,即此子问题的方法数 + +int slove(int index, int target) { + int ans = 0; + if(index == len) { + ans = target == 0 ? 1 : 0; + } else { + for(int i = 0; i*money[index] &lt;= target; i++) { + // 剩余待换零钱的总额 + int cur_total = target-(i * money[index]); + int pre_val = val_map[index+1][cur_total]; + // 如果 val 为 0,说明该子问题没有被计算过 + if(pre_val == 0) { + ans = ans + slove(index+1, cur_total); + } else { + ans += pre_val == -1 ? 0 : pre_val; + } + } + } + // 存储计算结果 + val_map[index][target] = ans == 0 ? -1 : ans; + return ans; +} + +int main() +{ + int target; // 零钱种类 + cin&gt;&gt;len; + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;money[i]; + } + cin&gt;&gt;target; + + cout&lt;&lt;slove(0, target); +} + +动态规划 +上面对递归的优化方案已经能看出来动态规划的影子了,沿着前文先计算再查表的思路继续思考,我们能否提前把所有子问题都计算出答案,对每个子问题都进行查表解决。也即将最初的递归方案改为循环的实现。 + +所有的递归都能改为循环实现 + +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int money[1000]; // money 表示所有小额纸币的面值 +int len; // len 表示 money 数组的长度,即:小额纸币种类 + +// 用于存储子问题的解 +// val_map[i][j] 表示用 money[0...i] 的小面额零钱组成 j 元的方法数 +int val_map[1000][1000] = { 0 }; + +int slove(int target) { + + // 第一列表示组成 0 元的方法数,所以为 1 + for (int i = 0; i &lt; len; i++) { + val_map[i][0] = 1; + } + + // 第一行表示只使用 money[0] 一种钱币兑换钱数为i的方法数 + // 所以是 money[0] 的倍数的位置为 1,否则为 0 + for (int i = 1; money[0]*i &lt;= target; i++) { + val_map[0][money[0]*i] = 1; + } + + for (int i = 1; i &lt; len; i++) { + for (int j = 1; j &lt;= target; j++) { + for (int k = 0; j &gt;= money[i]*k; k++) { + /* + val_map[i][j] 的值为: + 用 money[0...i-1] 的零钱组成 j 减去 money[i] 的倍数的方法数 + 因为相比 val_map[i-1][j],只是多了一种零钱的可选项 + */ + val_map[i][j] += val_map[i-1][j-money[i]*k]; + } + } + } + + return val_map[len-1][target]; +} + +int main() +{ + int target; + cin&gt;&gt;len; + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;money[i]; + } + cin&gt;&gt;target; + + cout&lt;&lt;slove(target); +} + +动归优化 +在上文第一版动态规划代码的优化中已经能发现,其实val_map[i][j]的值由两部分组成,分别为: + +用 money[0...i-1] 的零钱组成换 j 元的方法数 +用 money[0...i-1] 的零钱换 j-money[i]*k(k=1,1,2,3....)元的方法数之和 + +对于第二种情况来说,其累加值实际上就是val_map[i][j-money[i]],即用money[0...i]的零钱换 j-money[i]元的方法数。至于具体为什么累加值与val_map[i][j-money[i]]相等,我们可以借助递归方法时的分析方式进行理解。 +用 money[0...i-1] 的零钱组成换 j 元的方法数对应: + +用 0 张 money[i] 换,剩下的用 money[0...i-1] 换 + +用 money[0...i-1] 的零钱换 j-money[i]*k(k=1,1,2,3....)元的方法数之和对应: + +用 1 张 money[i] 换,剩下的用 money[0...i-1] 换 +用 2 张 money[i] 换,剩下的用 money[0...i-1] 换 +...... + +所以第二部分的值即为val_map[i][j-money[i]]。依据此处的分析,我们可以在原有基础上去掉第三层循环,减少程序运行所花费的时间。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int money[1000]; +int len; + +int val_map[1000][1000] = { 0 }; + +int slove(int target) { + + for (int i = 0; i &lt; len; i++) { + val_map[i][0] = 1; + } + + for (int i = 1; money[0]*i &lt;= target; i++) { + val_map[0][money[0]*i] = 1; + } + + for (int i = 1; i &lt; len; i++) { + for (int j = 1; j &lt;= target; j++) { + val_map[i][j] = val_map[i-1][j]; + // 此处需要比较 j 的大小,防止数组越界 + // 注意条件时 &gt;= ,否则少计算 j 刚好为 money[i] 的情况 + if(j &gt;= money[i]) { + val_map[i][j] += val_map[i][j-money[i]]; + } + } + } + + return val_map[len-1][target]; +} + +int main() +{ + int target; + cin&gt;&gt;len; + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;money[i]; + } + cin&gt;&gt;target; + + cout&lt;&lt;slove(target); +} + +空间压缩 +仔细观察能发现,每一次更新val_map[i][j]的值时,它只依赖于上一行和当前这一行前面的元素。对于我们所求解的问题来说,它仅要求我们给出最终的答案即可,那么前面存储中间结果的那些元素实际上就会空间的浪费,因此我们可以思考一下如何在空间上进行压缩。 +实际上我们只需要定义一个一维的数组,采用一些技巧对该数组进行滚动更新,按照合适的方向去更新数组,同样可以达到上面使用二维数组的效果。 +#include&lt;bits/stdc++.h&gt; +using namespace std; + +int money[1000]; +int len; + +int val_map[1000] = { 0 }; + +int slove(int target) { + + // 第一行,只用 money[0] 换零钱 + // 所以只能换 money[0] 倍数的钱 + for (int i = 0; money[0]*i &lt;= target; i++) { + val_map[money[0] * i] = 1; + } + + for (int i = 1; i &lt; len; i++) { + for (int j = 1; j &lt;= target; j++) { + if(j &gt;= money[i]) { + // 在进行下面一步前 val_map[j] 的值就已经是 val_map[i-1][j] 了 + val_map[j] += val_map[j-money[i]]; + } + } + } + + return val_map[target]; +} + +int main() +{ + int target; + cin&gt;&gt;len; + for(int i = 0; i &lt; len; i++){ + cin&gt;&gt;money[i]; + } + cin&gt;&gt;target; + + cout&lt;&lt;slove(target); +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + 牛客网 NC632 牛牛摆木棒、POJ 1037 美丽的栅栏题解 + +

+ +
+ + + + +
+ +
+ +参考内容: +OI题解 - A decorative fence[POJ 1037] +poj1037(dP+排列计数) + +本文首发于牛客网:题解 | #牛牛摆木棒# +题目 +牛客网 NC632 牛牛摆木棒、POJ1037-A decorative fence(美丽的栅栏) +描述 +有n个木棒,长度为1到n,给定了一个摆放规则。规则是这样的:对于第 i (2≤i≤n−1)(2 \leq i \leq n-1)(2≤i≤n−1) 个木棒 aia_iai​,(ai&gt;ai−1(a_i &gt; a_{i-1}(ai​&gt;ai−1​ &amp;&amp; ai&gt;ai+1)a_i &gt; a_{i+1})ai​&gt;ai+1​) 或 (ai&lt;ai−1(a_i &lt; a_{i-1}(ai​&lt;ai−1​ &amp;&amp; ai&lt;ai+1)a_i &lt; a_{i+1})ai​&lt;ai+1​)。求满足规则的从小到大的第k个排列是什么呢。 +对于两个排列 s 和 t:如果存在 j 有任意 i&lt;ji&lt;ji&lt;j 使得 si==tis_i == t_isi​==ti​ 且 sj&lt;tjs_j &lt; t_jsj​&lt;tj​,视为排列 s 小于排列 t。 +示例 +输入:3,3 +返回值:[2,3,1] +说明:第一小的排列为:[ 1 , 3 , 2 ] + 第二小的排列为:[ 2 , 1 , 3 ] + 第三小的排列为:[ 2 , 3 , 1 ] + 第四小的排列为:[ 3 , 1 , 2 ] + 所以答案为:[ 2 , 3 , 1 ] + +备注 +(1≤n≤20,1≤k≤(n−1)!)(1 \leq n \leq 20, 1 \leq k \leq (n-1)!)(1≤n≤20,1≤k≤(n−1)!) +题意 +该问题让我们求:n 的字典序排列中第 k 个波浪形的排列。什么是波浪形排列呢?即对排列中任意一个数字(除开第一个和最后一个)aia_iai​,只能 aia_iai​ 比 ai−1a_{i-1}ai−1​ 和 ai+1a_{i+1}ai+1​ 都小或者都大。比如 2 1 3和1 3 2是波浪形排列,但1 2 3就不是波浪形排列。 +DFS 枚举 +最容易想到的解决方案是把 n 的所有排列按字典序列出来,然后再逐一检查是否是波浪形排列,直接取出第 k 个波浪形排列即可。 +我们以 3 的全排列为例画出如下树形图,非常容易的就能发现只要对这棵树进行深度优先遍历,就能够按字典序得到所有排列。但并不是所有排列都满足波浪形这个条件,所以我们每得到一个排列都需要检查该排列是否为波浪形,直到检查到第 k 个排列为止,返回该排列即可。 + +class Solution { +public: + + // 记录当前已经有多少个波浪形排列 + long long count = 0; + // 记录最后的结果 + vector&lt;int&gt; res; + /** + * + * @param n int整型 木棒的个数 + * @param k long长整型 第k个排列 + * @return int整型vector + */ + vector&lt;int&gt; stick(int n, long long k) { + // 用于标记当前考虑的数字是否已经被选择 + bool visited[25] = {false}; + // 用于记录已经选了哪些数 + vector&lt;int&gt; path; + dfs(n, 0, path, visited, k); + return res; + } + + /** + * + * @param n 可选的数字范围 + * @param deep 递归到第几层了 + * @param path 已经选的数字 + * @param visited 记录哪些数已经被选了 + * @param k 是否已经到第 k 个波浪形排列了 + */ + void dfs(int n, int deep, vector&lt;int&gt; path, bool visited[], long long k) { + // 递归层数和范围相等,说明所有的数字都考虑完了,因此得到一个排列 + if(deep == n){ + // 判断该排列是否为波浪形排列 + bool flag = true; + for(int i = 1; i &lt; n-1; i++){ + if((path[i] &gt; path[i-1] &amp;&amp; path[i] &lt; path[i+1]) || + (path[i] &lt; path[i-1] &amp;&amp; path[i] &gt; path[i+1])){ + flag = false; + break; + } + } + // 是波浪形排列,则统计一次 + if(flag) { + count++; + } + // 判断是否已经到第 k 个排列 + if(count == k) { + // 如果返回结果还没有被赋值,则将该排列赋值给 res + // 因为我们使用的是递归,所以 count==k 会被满足多次 + // 只有第一次满足时才是真正的 k 值,所以必须判断 res 是否为空 + // 如果不判空,则程序记录的不是正确结果 + if(res.empty()){ + res = path; + } + // 到第 k 个波浪形排列了,递归返回 + return ; + } + // 没有可以选择的数字了,回溯 + return ; + } + // 还没有得出一个排列,则继续挑选数字组成排列 + for(int i = 1; i &lt;= n; i++) { + // 如果该数字已经被选择了,则终止本次循环 + if(visited[i]){ + continue; + } + // 选中当前数字加入到排列中 + path.push_back(i); + visited[i] = true; + // 下一次递归所传的值不变,只有递归层数需要 +1 + dfs(n, deep+1, path, visited, k); + // 回溯,需要撤销前面的操作 + path.pop_back(); + visited[i] = false; + } + } +}; + +在 C++ 的 algorithm 库中已经提供了一个全排列方法 next_permutation。按照STL文档的描述,next_permutation 函数将按字母表顺序生成给定序列的下一个较大的序列,直到整个序列为减序为止。因此我们可以偷个懒直接使用现有的函数。 +class Solution { +public: + /** + * + * @param n int整型 木棒的个数 + * @param k long长整型 第k个排列 + * @return int整型vector + */ + vector&lt;int&gt; stick(int n, long long k) { + vector&lt;int&gt; res; + // 记录当前已经有多少个波浪形排列 + long long count = 0; + // 构造初始化排列 + for(int i = 1; i &lt;= n; i++) { + res.push_back(i); + } + do { + // 判断当前排列是否为波浪形排列 + bool flag = true; + for(int i = 1; i &lt; n-1; i++) { + if((res[i] &gt; res[i-1] &amp;&amp; res[i] &lt; res[i+1]) || + (res[i] &lt; res[i-1] &amp;&amp; res[i] &gt; res[i+1])){ + flag = false; + break; + } + } + if(flag) { + count++; + } + if(count == k) { + break; + } + } while (next_permutation(res.begin(), res.end())); + return res; + } +}; + +复杂度分析 + +我们来看一下这个深度优先遍历的时间复杂度分析,该算法的时间复杂度主要由递归树的结点个数决定。因为程序在叶子结点和非叶子结点的行为时不一样的,所以我们先计算非叶子结点的个数,我们一层一层的去计算它。 +第 1 层因为只有一个空列表,所以我们不考虑它; +第 2 层表示的意思是从 n 个数中找出 1 个数,即 An1A_n^1An1​; +第 3 层表示的意思是从 n 个数中找出 2 个数,即 An2A_n^2An2​; +以此类推,全部非叶子结点的总数为: +An1+An2+⋯AnnA_n^1 + A_n^2 + \cdots A_n^nAn1​+An2​+⋯Ann​ +=n!(n−1)!+n!(n−2)!+⋯+n!= \frac{n!}{(n-1)!} + \frac{n!}{(n-2)!} + \cdots + n!=(n−1)!n!​+(n−2)!n!​+⋯+n! +=n!(1(n−1)!+1(n−2)!+⋯+1)= n!\left(\frac{1}{(n-1)!} + \frac{1}{(n-2)!} + \cdots + 1\right)=n!((n−1)!1​+(n−2)!1​+⋯+1) +≤n!(1+12+14+⋯+12n−1)\leq n!\left(1 + \frac{1}{2} + \frac{1}{4} + \cdots + \frac{1}{2^{n-1}}\right)≤n!(1+21​+41​+⋯+2n−11​) +=n!×2(1−12n)= n! \times 2(1-\frac{1}{2^{n}})=n!×2(1−2n1​) +&lt;2n!&lt; 2n!&lt;2n! +每个非叶子结点都在内部循环了 n 次,所以非叶子结点的时间复杂度为 O(2n×n!)O(2n \times n!)O(2n×n!),去除系数后得到 O(n×n!)O(n \times n!)O(n×n!) 。 +最后一层叶子结点的个数就是 n!n!n! 个,但是我们对每个叶子结点都做了一次判断,因此叶子结点的时间复杂度依然是 O(n×n!)O(n \times n!)O(n×n!) 。 +该问题的 k 控制了遍历的次数,最好情况即 O(n!)O(n!)O(n!),最差即 O(n×n!)O(n \times n!)O(n×n!),平均一下也不过只加了个系数,因此总的时间复杂度为 O(n×n!)O(n \times n!)O(n×n!)。 +递归树的深度为 n,需要 O(n)O(n)O(n) 的空间;程序运行过程中保存了问题的最终答案,需要 O(n)O(n)O(n) 的空间,总共需要 O(2n)O(2n)O(2n) 的空间,因此该算法的空间复杂度为 O(n)O(n)O(n)。 +动态规划 +上述算法在运行过程中会超时,究其原因就是不论测试数据要求我们求第几个波浪形排列,我们都老老实实的从第一个开始数,当数据比较大时就会出现超时的情况。那么有没有办法能够减少一些不必要的过程呢?比如测试数据要求第 100 个波浪形排列,很明显前面 80 个排列肯定不满足情况,我们能否舍弃一部分搜索直接从第 80 个甚至第 90 个开始呢? +我们先不考虑波浪形排列这个条件,如果是求第 k 个全排列的话是非常容易就能算出来的。还是以1 2 3的全排列为例,假设现在要求第 5 个全排列,可以发现只要第一个数确定了,排列数就由剩下数的排列方案决定,以1打头的排列有两个,以2打头的排列也有两个,而现在要求的是第 5 个排列,所以肯定不是以1或2打头的,这样我们就能直接跳过大部分不合法的排列,节省了时间。 +仔细想想发现理想是比较丰满,上述方法的问题在于无法确定前面跳过的那部分里面究竟有多少个波浪形排列,因此这种直接计算的方法行不通。但是这个思想我们是可以借用一下的,那我们把一部分数据计算出来,尝试一下能不能找到规律。 +当 n 为 1 时,总共有 1 个波浪形排列,1 打头的有 1 个; +当 n 为 2 时,总共有 2 个波浪形排列,1 打头的有 1 个; +当 n 为 3 时,总共有 4 个波浪形排列,1 打头的有 1 个; +当 n 为 4 时,总共有 10 个波浪形排列,1 打头的有 2 个; +当 n 为 5 时,总共有 32 个波浪形排列,1 打头的有 5 个; +当 n 为 6 时,总共有 122 个波浪形排列,1 打头的有 16 个; +列出来了 6 组数据都没有发现规律,这种方式基本得战略性放弃了。我们设置 A[i] 为 i 根木棒所组成的合法方案数,列数据找规律其实就是尝试找到 A[i] 和 A[i-1] 的规律,比如选定了某根木棒 x 作为第 1 根木棒的情况下,则剩下 i-1 根木棒的合法方案数为 A[i-1]。问题在于并不是这 A[i-1] 中每一种方案都能和 x 形成一种新的合法方案。 +我们把第 1 根木棒比第 2 根木棒长的方案称为 W 方案,第 1 根木棒比第 2 根木棒短的方案称为 M 方案。A[i-1] 中方案中只有第 1 根木棒比 x 要长的 W 方案,以及第 1 根木棒比 x 要短的 M 方案,才能进行组合构成 A[i] 中的合法方案。 +因此我们可以设A[i] = 0,先枚举 x,然后针对每一个 x 枚举它后面那根木棒 y,如果y &gt; x(y &lt; x同理)则有:A[i] = A[i] + 以 y 打头的 W 方案数,但是以 y 打头的 W 方案数,又和 y 的长短有关,因此只能继续将描述方式继续细化了。 +设 B[i][k] 是 A[i] 中以第 k 短的木棒打头的方案数,则有: +A[i]=∑k=1iB[i][k]A[i] = \sum_{k=1}^i B[i][k]A[i]=∑k=1i​B[i][k] +B[i][k]=∑j=ki−1B[i−1][j](W)+∑n=1k−1B[i−1][n](M)B[i][k] = \sum_{j=k}^{i-1} B[i-1][j](W)+ \sum_{n=1}^{k-1} B[i-1][n](M)B[i][k]=∑j=ki−1​B[i−1][j](W)+∑n=1k−1​B[i−1][n](M) +公式中(W) 和 (M) 分别表示 W 方案和 M 方案,发现还是无法找出推导关系。设 C[i][k][0] 为 B[i][k] 中的 W 方案数,C[i][k][1] 为 B[i][k] 中的 M 方案数那么则有: +B[i][k]=C[i][k][0]+C[i][k][1]B[i][k] = C[i][k][0] + C[i][k][1]B[i][k]=C[i][k][0]+C[i][k][1] +C[i][k][1]=∑j=ki−1C[i−1][j][0]C[i][k][1] = \sum_{j=k}^{i-1} C[i-1][j][0]C[i][k][1]=∑j=ki−1​C[i−1][j][0] +C[i][k][0]=∑n=1k−1C[i−1][n][1]C[i][k][0] = \sum_{n=1}^{k-1} C[i-1][n][1]C[i][k][0]=∑n=1k−1​C[i−1][n][1] +至此状态转移方程就出来了,初始条件为:C[1][1][0]=C[1][1][1] = 1,下面就可以开始写代码了。 +class Solution { +public: + /** + * + * @param n int整型 木棒的个数 + * @param k long长整型 第k个排列 + * @return int整型vector + */ + vector&lt;int&gt; stick(int n, long long s) { + long long dp[21][21][2]; + memset(dp,0,sizeof(dp)); + dp[1][1][0] = dp[1][1][1] = 1; + for (int i = 2; i &lt;= n; i++){ + // 枚举第一根木棒的长度 + for (int k = 1; k &lt;= i; k++){ + // W 方案枚举第二根木棒的长度 + for (int m = k; m &lt; i; m++){ + dp[i][k][0] += dp[i-1][m][1]; + } + // M 方案枚举第二根木棒的长度 + for (int m = 1; m &lt;= k-1; m++){ + dp[i][k][1] += dp[i-1][m][0]; + } + } + } + // 标记是否已经使用 + bool visited[21] = {false}; + // 保存结果的排列 + int a[21]; + // 逐一确定第 i 位 + for(int i = 1; i &lt;= n; i++) { + int k = 0; + // 假设第 i 放 j + for(int j=1;j&lt;=n;j++) { + long long tmp = s; + // 已经使用过的数不能再使用了 + if(!visited[j]) { + // j 是没有使用过的木棒中第 k 短的 + k++; + if(i == 1) { + // 确定第一根木棒的长度 + tmp -= dp[n][k][0] + dp[n][k][1]; + } else if(j &lt; a[i-1] &amp;&amp; (i==2 || a[i-2]&lt;a[i-1])) { + // W 类型 + tmp -= dp[n-i+1][k][0]; + } else if(j &gt; a[i-1] &amp;&amp; (i==2 || a[i-2]&gt;a[i-1])) { + // M 类型 + tmp -= dp[n-i+1][k][1]; + } + if(tmp &lt;= 0) { + visited[j]=true; + a[i]=j; // 第 i 位为 j + break; + } + } + s = tmp; + } + } + // 将结果转换为指定格式 + vector&lt;int&gt; res; + for(int i = 1; i &lt;= n; i++) { + res.push_back(a[i]); + } + return res; + } +}; + +复杂度分析 +最开始初始化dp数组时用了 O(2n2)O(2n^2)O(2n2) 的时间,随后填写dp数组花的时间为 O(n3)O(n^3)O(n3),计算最终答案的时间为 O(n2)O(n^2)O(n2),将结果转为指定格式的时间为 O(n)O(n)O(n),所以该算法的时间复杂度为 O(n3)O(n^3)O(n3)。 +dp数组占用了 O(2n2)O(2n^2)O(2n2) 的空间,标记数组visited、保存结果的数组a,以及最终转换为指定格式的path向量,各占用了 O(n)O(n)O(n) 的空间,取最大值即该算法的空间复杂度为 O(2n2)O(2n^2)O(2n2),去掉系数得到最终空间复杂度 O(n2)O(n^2)O(n2)。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何求两个数的最大公约数 + +

+ +
+ + + + +
+ +
+ 求几个整数的最大公约数大致有三种方法,求多个整数的最大公约数可以拆分为求两个整数的最大公约数,所以核心问题还是求两个整数的最大公约数。 +穷举法 +很直观就能想到穷举法,先找出两个数字中比较小的那一个min,然后逐个验证从2 ~ min的数字是否能被两个数整除,如果能同时被两个数字整除那就是公约数,找出其中最大的那个公约数就是所求的结果。 +int gcd(int a, int b){ + int min = a; + if(b &lt; a){ + min = b; + } + for(int i = min; i &gt; 2; i--){ + if(a%i == 0 &amp;&amp; b%i == 0){ + return i; + } + } + return 1; +} + +辗转相除法 +辗转相除法是欧几里得想出来的,所以也叫做欧几里得算法。它的证明过程依赖于一个定理:两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数,即gcd(a, b) = gcd(b, a mod b),其中 gcd 表示最大公约数,此处假设 a &gt; b。其证明过程如下所示: +设 c = gcd(a, b); +则存在 m,n,使 a = mc,b = nc; +令 r = a mod b; +则存在 k,使 r = a - kb = mc - knc = (m - kn)c; +所以 gcd(b, a mod b) = gcd(b, r) = gcd(nc, (m-kn)c) = gcd(n, m-kn)c; +所以 c 为 b 与 a mod b 的公约数; + +设 d = gcd(n, m-kn); +则存在 x,y,使 n = xd,m-kn = yd; +所以 m = yd + kn = yd + kxd = (y + kx)d; +所以 a = mc = (y + kx)dc,b = nc = xdc; +所以 gcd(a, b) = gcd((y+kx)dc, xdc) = gcd(y+kx, x)dc = dc; +因为 gcd(a, b) = c,所以 d = 1; +即 gcd(n, m-kn) = 1,所以 gcd(b, a mod b) = c; +所以 gcd(a, b) = gcd(b, a mod b); + +证明 gcd(y+kx, x)dc = dc,即 gcd(y+kx, x) = 1: +前提条件:gcd(x, y) = 1; +假设 gcd(y+kx, x) != 1,则肯定 gcd(y+kx, x) &gt; 1,设 gcd(y+kx, x) = i; +则 y+kx = ui,x = vi; +则 y = ui - kx = ui - kvi = (u-kv)i +则 gcd(x, y) = gcd(vi, (u-kv)i) = gcd(v, u-kv)i +因为 gcd(y+kx, x) = i &gt; 1,gcd(v, u-kv) &gt;= 1; +所以 gcd(x, y) &gt; 1,与前提条件矛盾; +所以 gcd(y+kx, x) = 1 + +有了上面的基础之后,我们就可以总结出来一个算法实现的步骤了。设 r = a % b;如果 r 为 0 的话,那么 a 和 b 的最大公约数就是 b,否则就是求 b 和 a%b 的最大公约数。 +// 递归写法 +int gcd(int a, int b){ + // 用 b 来存储 a%b 的值 + if(b == 0){ + return a; + } + return gcd(b, a%b); +} + +// 迭代写法 +int gcd(int a, int b){ + while(b != 0){ + int t = b; + a = t; + b = a % b; + } + return a; +} + +可以看到在算法实现过程中并没有先找出来最小的数字,这是因为程序会自动将最较大的那个数字放到 a 的位置,比如将gcd(75, 1000)带入我们的递归算法中则会变成gcd(1000, 75)。 +辗转相减法 +辗转相减法也叫更相减损术(尼考曼彻斯法),也是一种简便的求两个数的最大公约数的算法,它的特色是做一系列减法,从而求的最大公约数。比如两个自然数 36 和 27,用大数减去小数得 9 和 27,这时 9 小于 27,需要将两数交换即得 27 和 9,继续相减可得 18 和 9,然后 9 和 9,这时就可以得到两数的最大公约数为 9 了。其证明过程如下所示: +设 gcd(a, b) = x,a &gt; b; +则有 a = mx,b = nx,m,n 均为正整数且 m &gt; n; +c = a - b = mx - nx = (m - n)x; +因为 a 和 b 均为正整数,所以 c 也能被 x 整除; +所以 gcd(a, b) = gcd(b, a-b) + +具体的算法实现步骤在第一段已经有一个比较清晰的例子了,这里可以直接给出实现代码。 +// 递归写法 +int gcd(int a, int b){ + if(a == b){ + return a; + } + return a &gt; b ? gcd(a-b, b) : gcd(a, b-a); +} + +// 迭代写法 +int gcd(int a, int b){ + while(a != b){ + a &gt; b ? a = a - b : b = b - a; + } + return a; +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + Bootstrap-table 如何合并相同单元格 + +

+ +
+ + + + +
+ +
+ Bootstrap-table 官方提供了合并单元格方法 mergeCells,它根据四个参数可以合并任意个单元格,我们要做的只是告诉它怎么合并。 +要合并同一列相同的单元格,无非两种办法,一种是一边遍历一边合并,遍历完了再合并。这里采用第二种办法,这里不需要遍历所有数据,因为用户只能看到当前页的数据,所以只遍历当前页的数据更省时间。 +下面是我实现的获取合并信息算法,最终返回的是一个哈希表,比如下面的这个表格,如果要对「性别」这一列进行合并,很明显前面两个“男”需要合并成一个单元格,再去看下 Bootstrap-table 提供的 API,它需要的是从哪个单元格开始,合并多少个单元格,也就是它需要的是两个数值类型的参数。 + + + +姓名 +性别 +年龄 + + + + +张三 +男 +23 + + +李四 +男 +19 + + +王二 +女 +20 + + +麻子 +男 +21 + + + +所以我把哈希表设置为,键存的是索引,值存的是从这个索引开始后面连续有多少个和它一样的单元格,那么上述表格性别这一列所得到的合并信息哈希表就为: +{ + 0: 2, + 2: 1, + 3: 1 +} + +下面算法很简单,使用两个指针遍历指定的列,如果两个指针所指向的数据相同,那么就将键所对应的值进行加一操作,整个方法只会对该列数据遍历一边,所以时间复杂度为 O(n)。 +let getMergeMap = function (data, index: number) { + let preMergeMap = {}; + // 第 0 项为表头,索引从 2 开始为了防止数组越界 + for (let i = 2; i &lt; data.length; i++) { + let preText = $(data[i-1]).find('td')[index].innerText; + let curText = $(data[i]).find('td')[index].innerText; + let key = i - 2; + preMergeMap[key] = 1; + while ((preText == curText) &amp;&amp; (i &lt; data.length-1)) { + preMergeMap[key] = parseInt(preMergeMap[key]) + 1; + i++; + preText = $(data[i - 1]).find('td')[index].innerText; + curText = $(data[i]).find('td')[index].innerText; + } + // while循环跳出后,数组最后一项没有判断 + if (preText == curText) { + preMergeMap[key] = parseInt(preMergeMap[key]) + 1; + } + } + return preMergeMap; +} + +上述算法得到了单列数据的合并信息,下一步就是按照这个信息进行相同单元格的合并了,因此封装了下面的方法按照指定哈希表进行合并。 +let mergeCells = function (preMergeMap: Object, target, fieldName: string) { + for (let prop in preMergeMap) { + let count = preMergeMap[prop]; + target.bootstrapTable('mergeCells', { index: parseInt(prop), field: fieldName, rowspan: count }); + } +} + +到目前为止,我们实现的都只是对单列数据进行合并,要实现对多列数据进行合并,那么只需要对所有列都进行相同的操作即可。 +export let mergeCellsByFields = function (data: Object[], target, fields) { + for (let i = 0; i &lt; fields.length; i++) { + let field = fields[i]; + // 保证 field 与 i 是相对应的 + let preMergeMap = getMergeMap(data, i); + let table = target.bootstrapTable(); + mergeCells(preMergeMap, table, field); + } +} + +因为我在程序中做了一点处理,保证了fields中每个值得索引与对应表头的索引是一样的,因此不需要额外传入索引信息。简单来说就是我所实现的表格会根据fields的顺序,实现列之间的动态排序。你需要注意的是这一点很可能和你不一样。 +到现在已经能够合并所有的列了,查看 Bootstrap-table 的配置信息发现,它有个属性是 onPostBody 它会在 table body 加载完成是触发,所以把这个属性配置成我们的合并单元格方法即可。 +// groups 为要合并的哪些列 +onPostBody: function () { + mergeCellsByFields($('#table' + ' tr'), $('#table'), groups); +} + +再说一点不太相关的,我实现的是让用户可以自己选可以合并多少列,即用了一个可多选的下拉列表框供用户选择,根据用户选择的数量去合并,所以传入了一个groups参数。 +最后推荐一个排序插件 thenBy,你可以用它进行多字段排序,比如用在合并相同单元格的场景,在绘制表格前先对数据进行排序,那么最后合并的结果就是把所有相同的数据聚合到一起了,并且还将它们合并到一起了,起到了一个隐形的过滤查询功能。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 为什么计算机处理排序数组比未排序数组快? + +

+ +
+ + + + +
+ +
+ 今天在群里看到一个有意思的问题——为什么处理排序数组比处理没有排序的数组要快,这个问题来源于 StackoverFlow,虽然我看到代码略微知道原因,但是模模糊糊不够清晰,搜了很多博客也讲的不够明白,所以就自己来总结了。 +首先来看一下问题,下面是很简单的一段代码,随机生成一些数字,对其中大于 128 的元素求和,记录并打印求和所用时间。 +import java.util.Arrays; +import java.util.Random; + +public class Main +{ + public static void main(String[] args) + { + // Generate data + int arraySize = 32768; + int data[] = new int[arraySize]; + + Random rnd = new Random(0); + for (int c = 0; c &lt; arraySize; ++c) + data[c] = rnd.nextInt() % 256; + + // !!! With this, the next loop runs faster + Arrays.sort(data); + + // Test + long start = System.nanoTime(); + long sum = 0; + + for (int i = 0; i &lt; 100000; ++i) + { + // Primary loop + for (int c = 0; c &lt; arraySize; ++c) + { + if (data[c] &gt;= 128) + sum += data[c]; + } + } + + System.out.println((System.nanoTime() - start) / 1000000000.0); + System.out.println(&quot;sum = &quot; + sum); + } +} + +我的运行结果:分别在对数组排序和不排序的前提下测试,在不排序时所用的时间比先排好序所用时间平均要多 10 ms。这不是巧合,而是必然的结果。 +问题就出在那个if判断上面,在旧文顺序、条件、循环语句的底层解释中其实已经提到了造成这种结果的原因,只是旧文中没有拿出具体的例子来说明。 +为了把这个问题搞明白,需要先对流水线有一定的了解。计算机是指令流驱动的,执行的是一个一个的指令,而执行一条指令,又要经过取指、译码、执行、访存、写回、更新六个阶段(不同的划分方式所包含的阶段不一样)。 +六个阶段使用的硬件基本是不一样的,如果一条指令执行完再去执行另一条指令,那么在这段时间里会有很多硬件处于空闲状态,要使计算机的速度变快,那么就不能让硬件停下来,所以有了流水线技术。 +流水线技术通过将指令重叠来实现几条指令并行处理,下图表示的是三阶段指令时序,即把一个指令分为三个阶段。在第一条指令的 B 阶段,A 阶段相关的硬件是空闲的,于是可以将第二条指令的 A 阶段提前操作。 + +很明显,这种设计大幅提高了指令运行的效率,聪明的你可能发现问题了,要是不知道下一条指令是什么怎么办,那提前的阶段也就白干了,那样流水线不就失效了?没错,这就是导致开篇问题的原因。 +让流水线出问题的情况有三种: + +数据相关,后一条指令需要用到前一条指令的运算结果; +控制相关,比如无条件跳转,跳转的地址需要在译码阶段才能知道,所以跳转之后已经被取出的指令流水就需要清空; +结构相关,由于一些指令需要的时钟周期长(比如浮点运算等),长时间占用硬件,导致之后的指令无法进入译码等阶段,即它们在争用同一套硬件。 + +代码中的if (data[c] &gt;= 128)翻译成机器语言就是跳转指令,处理器事先并不知道要跳转到哪个分支,那难道就等知道了才开始下一条指令的取指工作吗?处理器选择了假装知道会跳转到哪个分支(不是谦虚,是真的假装知道),如果猜中了是运气好,而没有猜中那就浪费一点时间重新来干。 +没有排序的数组,元素是随机排列的,每次data[c] &gt;= 128的结果也是随机的,前面的经验就不可参考,所以下一次执行到这里理论上还是会有 50% 的可能会猜错,猜错了肯定就需要花时间来修改犯下的错误,自然就会浪费更多的时间。 +对于排好序的数组,开始几次也需要靠猜,但是猜着猜着发现有规律啊,每次都是往同一个分支跳转,所以以后基本上每次都能猜中,当遍历到与 128 分界的地方,才会出现猜不中的情况,但是猜几次之后,发现这又有规律啊,每次都是朝着另外一个相同分支走的。 +虽然都会猜错,但是在排好序的情况下猜错的几率远远小于未排序时的几率,最终呈现的结果就是处理排序数组比未排序数组快,其原因就是流水线发生了大量的控制相关现象,下面通俗一点,加深一下理解。 + +远在他方心仪多年的姑娘突然告诉你,其实她也喜欢你,激动的你三天三夜睡不着觉,决定开车前往她的城市,要和她待在一起,但是要去的路上有很多很多岔路,你只能使用的某某地图导航,作为老司机并且怀着立马要见到爱人心情的你,开车超快,什么样罚单都不在乎了。 +地图定位已经跟不上你的速度了,为了尽快到达,遇到岔路你都是随机选一条路前进,遗憾的是,自己的选择不一定对(我们假设高速可以回退),走错路了就要重新回到分岔点,这就对应着未排序的情况。 +现在岔路是有规律的,告诉你开始一直朝着一边走,到某个地点后会一直朝着另一边走,你只需要花点时间去探索一下开始朝左边还是右边,到了中间哪个地点会改变方向就可以了,相比之下就能节省不少时间了,尽快见到自己的爱人,这对应着排好序的情况。 + +最后的故事改编自两个人的现实生活,一位是自己最好的朋友之一,谈恋爱开心的睡不着觉;另一位是微信上的一位好友,为了对方从北京裸辞飞到了深圳。 + + +
+ + Read More ~ +
+
+
+ + + +
+ + + 下一页 + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/krqSF7tYC/page/2/index.html b/krqSF7tYC/page/2/index.html new file mode 100644 index 00000000..10bf8315 --- /dev/null +++ b/krqSF7tYC/page/2/index.html @@ -0,0 +1,445 @@ + + + + + + + + 算法 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 算法 +
+ + +
+

+ + 动态规划算法优化实例——如何求解换钱的方法数 + +

+ +
+ + + + +
+ +
+ 这是我的人生处女面遇到的一个面试题,是在去哪儿网二面遇到的,那时非常的紧张,还没有复习,所以第一次面试理所应当的挂了。文章对问题进行逐步的由简到难进行优化,基本上是代码,看懂代码才能理解,也为类似问题提供了基本的解决思路。 +题目描述: + +让你把一张整钱找零,即假设你拥有不同且不限量的小额钱币,你需要统计共有多少种方法可以用手中的小额钱币兑等额兑换一张大额钱币。 +即:给定一个元素为正数的集合(元素不重复)代表不同面值的钱币,再给一个整数,代表要找零的钱数,求共有多少种换钱方法? + +递归求解 +现在有1、5、10元三种面值的纸币,需要找零100元,那么可以做如下分析: +用 0 张 5 元换,剩下的用 1、10 元换,最终方法数为 count0; +用 1 张 5 元换,剩下的用 1、10 元换,最终方法数为 count1; +...... +用 100 张 5 元换,剩下的用 1、10 元换,最终方法数为 count100; + +最终的换钱方法总数就为 count0 + count1 + ...... + count100。 + +根据上面的分析可以写出下面的递归解决方案: +public static int coin(int money[], int target){ + if (money == null || money.length == 0 || target &lt; 0){ + return 0; + }else { + return slove(money, 0, target); + } +} + +// 用money[index, length-1]换钱,返回总的方法数 +private static int slove(int money[], int index, int target){ + int res = 0; + if(index == money.length){ + if (target == 0){ + res = 1; + }else { + res = 0; + } + }else { + for (int i = 0; money[index] * i &lt;= target; i++) { + res += slove(money, index+1, target-money[index]*i); + } + } + return res; +} + +优化递归 +可以看到,上面的程序在运行时存在大量的重复过程,比如下面两种情况,其后所求结果是一样的。 +兑换 100 元,已经使用了 0 张 1 元、1 张 2 元,剩下的用 5 元和 10 元兑换; +兑换 100 元,已经使用了 2 张 1 元、0 张 2 元,剩下的用 5 元和 10 元兑换; + +可以发现,这两种情况后面都是求解同一问题,重复的对同一个问题求解,就造成了时间的浪费,因此我们可以考虑将已经计算过的结果存下来,避免重复的计算,所以有下面的优化方案。 +public static int coin(int money[], int target){ + if (money == null || money.length == 0 || target &lt; 0){ + return 0; + }else { + /** + * map[i][j]表示p(i,j)递归回的值 + * 其中-1表示该递归过程计算过,但是返回值为0 + * 0表示该递归过程还为计算过 + */ + + int map[][] = new int[money.length+1][target+1]; + return slove(money, 0, target, map); + } +} + +private static int slove(int money[], int index, int target, int map[][]){ + int res = 0; + if(index == money.length){ + if (target == 0){ + res = 1; + }else { + res = 0; + } + }else { + int val = 0; + for (int i = 0; money[index] * i &lt;= target; i++) { + val = map[index + 1][target - money[index]*i]; + if (val != 0){ + if (val == -1){ + res += 0; + }else { + res += val; + } + }else { + res += slove(money, index+1, target-money[index]*i, map); + } + } + } + + if (res == 0){ + map[index][target] = -1; + }else { + map[index][target] = res; + } + return res; +} + +动态规划 +上面对递归方法的优化已经能看到动态规划的影子了,这是一个二维的动态规划问题,我们定义dp[i][j]的含义为:使用money[0...i]的钱币组成钱数j的方法数。所以可以得出以下面的动态规划解法: +public static int coin(int money[], int target){ + if (money == null || money.length == 0 || target &lt; 0){ + return 0; + } + + int dp[][] = new int[money.length][target+1]; + + // 第一列表示组成钱数为0的方法数,所以为1 + for (int i = 0; i &lt; money.length; i++) { + dp[i][0] = 1; + } + // 第一行表示只使用money[0]一种钱币兑换钱数为i的方法数 + // 所以是money[0]的倍数的位置为1,否则为0 + for (int i = 1; money[0] * i &lt;= target; i++) { + dp[0][money[0] * i] = 1; + } + + for (int i = 1; i &lt; dp.length; i++) { + for (int j = 1; j &lt; dp[0].length; j++) { + for (int k = 0; j &gt;= money[i] * k; k++) { + // dp[i][j]的值即为,用money[0...i-1]的钱 + // 组成j减去money[i]的倍数的方法数 + dp[i][j] += dp[i-1][j-money[i]*k]; + } + } + } + + return dp[money.length-1][target]; +} + +继续优化 +可以发现上面的动态规划解法有三层循环,因为是二维的动态规划问题,前两层没办法去掉,但是第三层依旧很耗时间,继续优化可以得到下面的结果。 +public static int coin(int money[], int target){ + if (money == null || money.length == 0 || target &lt; 0){ + return 0; + } + + int dp[][] = new int[money.length][target+1]; + + for (int i = 0; i &lt; money.length; i++) { + dp[i][0] = 1; + } + for (int i = 1; money[0] * i &lt;= target; i++) { + dp[0][money[0] * i] = 1; + } + + for (int i = 1; i &lt; money.length; i++) { + for (int j = 1; j &lt;= target; j++) { + /** + * 通过分析可以发现,dp[i][j]的值由两部分组成 + * 1:用money[0...i-1]的钱组成钱数为j的方法数 + * 2:用money[0...i]的钱组成钱数为j-money[i]*k(k=1,2,3....)的方法数 + * 对于第2种情况,实际上累加的值就是dp[i][j-money[i]] + * 所以直接使用dp[i][j-money[i]]即可 + */ + dp[i][j] = dp[i-1][j]; + if (j &gt;= money[i]){ + dp[i][j] += dp[i][j-money[i]]; + } + } + } + + return dp[money.length-1][target]; +} + +空间压缩 +可以看到每次更新dp[i][j],dp[i][j]的值只与前一行和当前行前面的元素有关系,而我们只需要最后的一个结果就行了,那么前面存的元素实际上会造成空间的浪费,进一步可以在空间上进行优化。 +我们只需要定义一个一位数组,然后对该数组进行滚动更新就可以了,只要按照合适方向去更新数组,同样能达到上面的效果。 +public static int coin(int money[], int target){ + if (money == null || money.length == 0 || target &lt; 0){ + return 0; + } + + int dp[] = new int[target+1]; + + // 第一行,只用money[0]兑换钱 + // 所以只能兑换为money[0]的倍数,将这些位置置为1 + for (int i = 0; money[0]*i &lt;= target; i++) { + dp[i] = 1; + } + + for (int i = 1; i &lt; money.length; i++) { + for (int j = 1; j &lt;= target; j++) { + + // 与前一步相比,少了dp[i][j] = dp[i-1][j]; + // 因为这里在进行dp[j] += dp[j-money[i]];之前 + // dp[j]的值就已经是dp[i-1][j]了 + if (j &gt;= money[i]){ + dp[j] += dp[j-money[i]]; + } + } + } + + return dp[target]; +} + +到这一步就不再有优化空间了,这个问题很值得记录下来,很多笔试、面试题都可以按这个模子进行套,对于只需要最优解的动态规划问题也可以套用上面的空间压缩思路,多总结、多练习总是没有问题的!这个解题思路第一次看到是左程云在牛客网上讲解的,他也写了一本算法相关的书比较不错,叫做程序员代码面试指南,大四、研三、刚入职的新人建议可以买一本读读,对自己编码技能的提升绝对又很大的帮助。 + +
+ + Read More ~ +
+
+
+ + + +
+ + 上一页 + + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/lSehulG6w/index.html b/lSehulG6w/index.html new file mode 100644 index 00000000..0e8c00fa --- /dev/null +++ b/lSehulG6w/index.html @@ -0,0 +1,521 @@ + + + + + + + + 2019 年个人总结 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 2019 年个人总结 +

+ + +
+ +
+

写总结的习惯是从 2015 年开始的,我的大学学费是县政协资助的,叔叔阿姨们唯一的要求就是每年给他们写个总结汇报一下学习情况,毕业后敦促我写总结的人则从外力转为内心。

+

一点感动

+

上半年我还很年轻,那时候还会经常使用 QQ、Soul、同桌、一罐 等等社交产品,无意结识了一个还在读高中的同性恋女孩子,我没学过心理学不知道用什么专业名词描述她的情况,反正就是心理上有严重的问题,玻璃心、想自杀等等。几次拼了我老命陪她聊到半夜两三点,现在完完全全能正视自己的情况了。

+

让我感动的是有次她问我在干啥,我随便拍了一张自己的被汗水打湿衣服发给她,告诉她自己正在打羽毛球。小姑娘给我说我穿的衣服不好看,说我才多大穿的衣服太老了,我也就随口一说叫她给我这个老年人推荐推荐衣服,因为她要上课后面就一直没有回我消息。

+

第二天早上睡醒了看到了小姑娘的几十条消息,是半夜两点多发的,给我挑了好几件衣服裤子,还给我画了几张示意图(如下),瞬间收获一份小感动。我也遵从小姑娘的意见买了两件上班穿穿,结果一到部门就是众目睽睽之下给我说穿的好酷,穿几次了都还是会引来大家不一样的目光,个性低调的我还是选择走大众程序员路线,老就老吧。

+
+
+

前几天小姑娘给我发了她暗恋的小姐姐的照片,虽然极少时候还是会上课偷偷玩手机,但也在努力的备战高考。我做的不好的就是她多次给我讲自己在龙岗,我每次都把她当成龙华的,希望写了这篇总结之后不再记错吧。

+

赚钱理财

+

这个小标题起的有点大,仅说说我自己的实际情况吧。凭着运气,2019 年的银行理财收益在 4.5% 左右,基金收益在 7% 左右。我没有去玩股票,网上各种理财课程可能都会给你讲股票的收益多么多么高,但是他们从来不会给你说玩股票的风险有多高,更不可能给你讲玩股票会严重影响自己的心情,可能连自己的本职工作都会搞砸,所以我不建议职场新人进入股市。

+

房东忙的时候我会帮他带房客看房,他也给了我小几千块钱的介绍费,加上每个月没交网费直接用他的,还时不时蹭蹭房东的饭局,也给自己省下来周末出去散步的费用了。上半年也给别人分享过两三个课程,在群里分享过一点小技能,大家给发了点红包,交个朋友、图个开心。

+

总的来讲,理财这方面做得很差,没有花什么时间去学习,我们的大学也没有教给学生一点金融知识,这一年只读了几本写给小白的理财书,今年在这个领域要多花一点功夫,希望能入得了门吧。

+

写书失败

+

快要毕业的时候和电子工业出版社签了一份合同,合同内容就是我要写一本叫做《知识图谱:自顶向下方法》,这本书的计划内容是我的毕业设计,已经写了一百多页的内容了,但现在确定以失败告终。

+

一者我手里现有的数据属于机密数据,没办法拿来直接使用;二来书中有很大一部分内容涉及到网络爬虫,上半年网上曝了很多因为抓数据而入狱的案例,出版社和我都害怕;三者知识图谱所需要的数据量很大,而且我写的那个领域又是中国特有的经济责任审计领域,大量数据都得从政府网站来,更害怕了;最重要的原因是自己懒,写书的那几个月确实非常的累,想想自己都还是个菜鸟呐,有啥资本教给别人知识,心里给了自己后退的理由。

+

小时候曾夸下海口说要给父亲写个传记,也不知道有没有那么一丢丢可能性实现,写家里的狗时,发现写这样的内容会增加我的多巴胺分泌,以后不开心了就写这样的小故事。

+

运动健身

+

在深圳校友会骑行社师兄师姐们的带领下,同时也得益于一起入职的小伙伴送了我一辆 MERIDA,我喜欢上了骑行这项运动,基本上每周五都会出去骑几十公里,中间还参加了环漓江骑行和 TREK100 骑行,锻炼的同时也见到了美丽的风景。深圳对自行车是不太友好的,基本没有什么自行车道,所以我们大部分时间都是等到晚上车少,交警下班了之后才开始骑行。

+

除了骑行每周一也会打两小时羽毛球,谈不上专业,但至少打的不再是广场球了。偶尔也会出去爬爬山啥的,身体确实比上学时候要好很多,而且多锻炼能让自己的精神面貌更好,精气神好也能稍稍掩盖长得丑的缺点。以前每年再怎么也会因为感冒一类的问题进几次医院,19 年仅一次因为智齿发炎去过医院。

+

削减迷茫

+

大概在四五月份的时候吧,几乎天天失眠,经常夜里只睡了三四个小时,有时甚至通宵未眠,心里很清楚是因为迷茫了,大概就是「晚上想了千条路,早上醒来走原路」的状态。好在自己的调节能力还不算差,同时也有楼下的叔叔、自己的好朋友能唠唠嗑,差不多两个月就回归正常状态了。

+

从几个比我晚入职半年的小伙伴那里了解到,他们现在的情况和我四五月份的情况差不多,我想绝大部分普通人都会经历这个迷茫期吧,大部分人也都能通过时间调节过来,调节不过来的那部分人就成为了媒体比较喜欢的人。

+

现在迷茫的雾气已经没有那么浓了,初入社会肯定有很多的不成熟,但谁不是这样过来的呢?更何况我并不像多数程序员那样交友严重同质化,周末也不会死宅在家里不出去,猜测我应该比大多数人更潇洒自在的,嘿嘿。

+

新的思想

+

大家基本都是看着金庸武侠小说(相关影视作品)长大的,没有人写武侠小说能超过金庸。偶然一天在推特上刷到一条评论,大意是:没有人写武侠小说能超过金庸不正代表着社会的进步吗?金庸的成就如此巨大,一个很重要的历史背景是那时候大家没有那么多小说可看呀,哪里像今天遍地的网络小说。咱们没必要去争论这个观点的对错,重要的是它告诉了我们一个不一样的角度去看待问题。

+

上面只是一个特例,思维方式是一点一点改变的,认知水平是一点一点提升的,一年时间修正了不少我此前狭隘的观点,这样的修正还在继续,我也会让这样的修正持续下去。

+

写在最后

+

巴黎圣母院被烧、凉山火灾、女排十连冠、NBA 事件、无锡高架桥倒塌......等等发生在 2019 年的大事,不知道还有多少朋友会记起来。时间从来不会等谁,网友也都是不长记性的,成熟的一部分无非是经历的多了,失望的多了,然后变得更耐操一点,总之生活依旧得继续,人总会亦悲亦喜,那为啥不把悲缩小喜放大呢?

+

成功没有银弹、没有捷径,少讲大道理,多解决小问题

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/lZGv3YBvB/index.html b/lZGv3YBvB/index.html new file mode 100644 index 00000000..7ab05c8e --- /dev/null +++ b/lZGv3YBvB/index.html @@ -0,0 +1,621 @@ + + + + + + + + Vim 常用命令快捷查询 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ Vim 常用命令快捷查询 +

+ + +
+ +
+
+

参考内容
+Learning Vim The Pragmatic Way
+《鸟哥的 Linux 私房菜》

+
+

Vim 可以认为是 Vi 的高级版本,Vim 可以用颜色或下划线的方式来显示一些特殊信息,您可以认为 Vi 是一个文本处理工具,而 Vim 是一个程序开发工具,现在大部分 Linux 的发行版都以 Vim 替换 Vi 了。在 Linux 命令行模式下有很多编辑器,但是 Vi 文本编辑器是所有 Unix-like 系统都会内置的,因此学会 Vi/Vim 的使用时非常有必要的,对于 Vi 的三种模式(命令模式、编辑模式、命令行模式)这里就不在做说明了,下面是一些比较常用的命令。

+

一般命令模式下

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
命令说明
h、j、k、l与键盘的方向键一一对应,分别为左、下、上、右,在键盘上着几个字母是排在一起的
Ctrl+f、Ctrl+b分别对应键盘的「Page Down」、「Page Up」,我更习惯于这两个键,而不是前面的组合键
0、$分别对应键盘的「Home」、「End」,即移动到该行的最前面/后面字符处
n<Enter>n 为数字,光标向下移动 n 行
/word、?word向光标之上/下寻找一个字符串名称为 word 的字符串
n、N如果我们刚刚执行了上面上面的 /word 或 ?word 查找操作,那么 n 则表示重复前一个查找操作,可以简单理解为向下继续查找下一个名称为 word 的字符串,N 则与 n 刚好相反
:n1,n2s/word1/word2/g在第 n1 行与 n2 行之间寻找 word1 这个字符串,并将这个字符串替换为 word2,如果前面的 n1,n2 使用 1,$ 代替则表示从第一行到最后一行,最后的 g 后面可以加个 c,即 :1,$s/word1/word2/gc,这样就会在替换钱显示提示字符给用户确认(confirm)
x、X分别对应键盘的「Del」、「Backspace」键
dd、yy删除/复制光标所在的那一整行
p、Pp 将已复制的数据在光标下一行粘贴,P 粘贴在光标上一行
u恢复前一个操作,类似于 Windows 下的 Ctrl+Z
Ctrl+r重做上一个操作
.小数点,重复上一个操作
+

命令行模式下

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
命令说明
:w将编辑的数据写入硬盘中
:w!若文件属性为只读,强制写入该文件,不过到底能不能写入,还是跟文件权限有关系
:q、:q!与 w 一样,q 为关闭的意思
:r [filename]在编辑的数据中读入另一个文件的数据,即将[filename]这个文件的内容追加到光标所在行的后面
:w [filename]将编辑的数据保存为另一个文件
:set nu/nonu显示/不显示行号
+

编辑模式下

+ + + + + + + + + + + + + + + + + + + + + +
组合键作用
[ctrl]+x -> [ctrl]+n通过目前正在编辑的这个文件的内容文字作为关键字,予以自动补全
[ctrl]+x -> [ctrl]+f以当前目录内的文件名作为关键字补全
[ctrl]+x -> [ctrl]+o以扩展名作为语法补充,以 Vim 内置的关键字予以补全
+

当我们在使用 Vim 编辑器的时候,Vim 会在与被编辑的文件目录下再建立一个名为.filename.swp的文件,我们对文件的操作都会记录到这个 swp 文件中去,如果系统因为某些原因掉线了,就可以利用这个 swp 文件来恢复内容。如果存在对应的 swp 文件,那么 Vim 就会主动判断当前这个文件可能有问题,会给出相应的提示。

+

我们也可以给 Vim 环境设置一些个性化的参数,虽然在命令行模式下可以使用:set来设置,但是这样每次设置实在是太麻烦,因此我们可以设置一些全局的参数。Vim 的整体设置值一般放在/etc/vimrc中,我们一般通过修改~/.vimrc这个文件(默认不存在)来设置一些自己的参数,比如:

+
" 该文件的双引号是注释
+set nu "在每一行的最前面显示行号
+set autoindent " 自动缩进
+set ruler " 可显示最后一行的状态
+set bg=dark " 显示不同的底色色调
+syntax on "进行语法检验,颜色显示,比如 C 语言等
+
+

最后附上一张命令速查卡,此图来源于Learning Vim The Pragmatic Way,PDF 版下载链接在这里

+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/mVmVUsbMe/index.html b/mVmVUsbMe/index.html new file mode 100644 index 00000000..e0737ab1 --- /dev/null +++ b/mVmVUsbMe/index.html @@ -0,0 +1,914 @@ + + + + + + + + 工具教程 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 工具教程 +
+ + +
+

+ + 工具箱 + +

+ +
+ + + + +
+ +
+ 这里收集自己平时发现的实用、精美小工具,列表内容会持续更新的。 + + + +名称 +分类 +简介 + + + + +fotor +在线图片编辑工具 +一个在线图片编辑工具,具备常用的按比例裁剪、按形状裁剪、调整大小等等功能 + + +TWsaver +下载工具 +推特视频下载工具,只需要把推文链接贴上去,它就可以将推文的视频下载下来 + + +Dark Reader、Night Eye +Chrome 插件 +可以把任何网站都变成黑夜模式的工具,相比之下 Dark Reader 看起来更舒服一点 + + +FasterChrome +Chrome 插件 +当用户鼠标悬停在链接上面,就开始预加载网页,使得真正点击的时候,页面瞬间就能加载。 + + +screenrecord +在线录屏 +一个录屏工具,在线录制是它最大的优点,省去了安装录屏软件的麻烦 + + +Cmder +终端工具 +一个 Windows 下的终端工具,可以像在 linux 下一样敲命令,提供了 mini 版和 full 版,如果遇到问题可以看这篇文章 + + +Visualgo +数据结构资料 +该网站将一些基础的、常见的算法用视频来展示,方便学习者的理解,是不错的数据结构、算法学习资源 + + +Unsplash、Pexels +资源素材 +图片非常精美,而且都可以免费使用,不用担心版权问题 + + +Mixkit +资源素材 +免费可直接下载的视频资源库,包括商业、城市、自然、生活方式等常用类型的视频,里面都是非常干净、原创度很高的视频素材 + + +Facebook Design +资源素材 +由 Facebook 设计团队出的资源网站,这个网站有很多设备模型,比如:手机、电脑、iPad、Apple Watch,同时还包括 + + +Pixabay +资源素材 +它除了图片素材外,也包含了很多视频素材,视频素材同样精美、干净 + + +优品 PPT +资源素材 +里面有很多精美的 PPT 模板,优点是该网站提供直接免费下载方式,并不需要注册账户、付费等等繁杂的流程 + + +icons.download +资源素材 +一个完全免费的开源矢量图标,它提供了16种款式,212个图标,有实体和轮廓,尖锐和圆润,4种宽度,免费且可商用 + + +Countrymeters +世界人口时钟 +可以实时查看世界人口的变化,也可以缩小反馈查看具体的国家、地区等等,也提供了世界五大死亡原因 + + +敏感词防和谐 +敏感词防和谐工具 +这个工具通过在每个字符之间插入零宽字符来防止敏感词被和谐,每年都会经历的论文查重也可以通过插入这样的零宽字符来避免 + + +Gridea +博客工具 +一个静态博客写作客户端,可以实现博客文章一键部署到 𝖦𝗂𝗍𝗁𝗎𝖻 𝖯𝖺𝗀𝖾𝗌 或 Coding Pages,并且支持 Markdown 语法,但是软件做的比较卡 + + +Tailwind CSS +CSS 框架 +一个实用的CSS框架,用于快速构建自定义设计,它提供了底层的实用工具类,让您可以在不离开HTML的情况下构建完全定制的设计。 + + +Linux-command +文档 +Linux 常用命令都能通过它搜索到,并且伴随着简明易懂的示例,它也提供了 Chrome 插件 + + +Instant.page +Js 库 +当用户鼠标悬停在链接上面,就开始预加载网页,从而使得用户真正点击的时候,页面瞬间就能加载。 + + +ThenBy +Js 库 +一个可以实现多字段排序的 js 库 + + +IconGo +图标库 +开源的图标搜索引擎 + + +RegExr +正则工具 +在线正则表达式学习测试工具,提供正则式编辑、学习、创建和测试 + + +sms-activate +接码平台 +很棒的接码平台 + + +Openverse +素材资源 +一个图片和音频的搜索引擎,据说包含超过6亿件作品,都可以自由使用,不用付费 + + +中国地铁信息概览 +数据资源 +可视化显示全国各个城市的地铁概况,包括每日的客流量 + + +REMIX ICON +图标库 +一个很不错的图标库 + + +docsmall +文件压缩工具 +图片压缩、GIF 压缩、PDF 压缩、PDF 合并/分割 + + +Smartphones +产品边框模版 +上传图片即可将图片镶嵌在不同场景的模版下,适合产品宣传一类 + + + + +
+ + Read More ~ +
+
+
+ +
+

+ + 用 flomo 管理自己的奇思妙想瀑布流 + +

+ +
+ + + + +
+ +
+ 使用 flomo 已经有一段时间了,太喜欢它的简洁与便捷了。它的微信输入方式可以随时随地记录突然冒出来的灵感;使用微信读书的时候看到一段写的很漂亮的文字,顺手贴到 flomo 的小卡片中便于下一次再回顾;使用 #todo 标签记录一些重要的待办事项...... +基本上的脑力劳动者都会有记笔记的习惯,我也习惯去捡日常零零星星掉下的小拼图块,找到一个适当的时机再用这些小拼图块进行排列组合完成一个小作品,作品可以是一篇文章、可以是某个问题的解决方案,亦或是简单的工具、词句集合。 +我用了「拼图块」这个词是因为我觉得人接收的信息就是碎片化的,尤其在充斥着各种奶头乐 APP 的时代,信息被磨揉的更加细碎无营养。不管是生活中还是工作上遇到的问题,很多都不是简简单单的接收一点碎片化知识就能找到解决方案的。学习新知识也是逐个去吸收小的知识点,再用这些小的知识点构建自己的知识体系,一些让主干更加粗壮,另一些让枝桠更加繁茂。 +flomo 背后的笔记理念就是去捡那些小小的拼图块,这和我现在的理念是保持一致的。借用 flomo 网站上的话说就是我们不可能都成为作家或者发表论文,但是我们都需要记录和思考。写 MEMO(卡片)而不是写文章的价值在于,能让我们更好的思考。 +大部朋友记笔记都仅仅是在辛勤的记录,最重要的思考环节却被忽略了,在之前写的你如果只是一直囤干货,那永远不可能进步中也提过没必要去假装学习。这里没有倡导不去记笔记的意思,而是找到一个适合自己的记录方式,笔记究竟要记什么中有一部分答案。 +我使用的第一个笔记软件是有道云笔记,用了有将近两年的时间,让我放弃它的原因是文件同步老出问题,另一个原因就是速度太慢了。印象笔记更是使用时间三天都没有超过,它那些看似强大实际却毫无用处的功能严重分散了我的注意力,这违背了我记笔记的初衷。 +还有像为知笔记、石墨文档一类的软件其实也还不错,但是和印象笔记、有道云笔记类似,它们的共同问题都是以文章的形式在组织笔记。这让记笔记变成了一件极为费时的事情,我看到一篇文章的字数少于 600 就难受,强迫症患者。 +所以有一段时间我选择了使用本地 VsCode + Markdown 插件方式记笔记,Markdown 的标题语法可以很轻松的将每个小片段分开,不同的文件(名)自然而然就变成标签了。一个新的技术点记到「技术.md」中,一段摘录记到「摘录.md」中。不过因为 VsCode 本身是一个方便程序员使用的文本编辑器,所以我这种方式记笔记总是有一点别扭,具体哪里别扭我自己也说不出来,总之就是用起来差那么点感觉。 +这里不得不提一下现在比较流行的数据库类型软件 Notion,这个工具做的让我有一种只有我想不到没有它做不到的错觉。团队在设计一个数据度量系统的时候,我还多次提出过借鉴 Notion 中 Block 的思想。Notion 是一款很优秀的软件,但仅对记笔记这件事来说它显得大材小用了,功能过于强大、使用过于灵活到变成了我不选择它作为笔记软件的原因。 +一小段时间使用知识星球做为笔记软件,但是它的搜索功能做的太弱了。一直使用到现在的笔记软件是微信,我建了只有我一个人的群,一些突然冒出来的想法、读书时的思考与摘录、todo things 都直接通过对话框发到群里。我看到有很多朋友也用了我类似的方法,选择发送到文件助手,这种方式的好处是可以借用微信强大的「查找聊天内容」功能,虽然有些鸡肋但用起来也还凑合。 + +其实我的记笔记方式是逐渐在向 flomo 靠拢的,虽然它出现的比较晚。flomo(浮墨笔记)看起来像是一个个人版的 twitter,或者就像少楠自己说的是一个加强版的文件传输助手,没有多余的功能去扰乱我的视线,就是一个简简单单的流式布局一元笔记软件。 +大多数习惯于像装抽屉一样去组织文件,windows 的文件系统也是这样设计的,不同的文件夹放不同的类别的文件,看起来好像很符合我们现实生活的打扫房间的场景。但不知道你有没有意识到每次去找一个具体的文件夹都要耗费大把的时间,这种初衷极好的分类整理方式竟然渐渐变成阻止我们去记录绊脚石。 +一款好用的文件系统更多的应该聚焦于搜索上,当用户搜索时能够快速的返回与之相关的文件才是关键,而不是把目光放在文件的分类上面。同样,一款好的笔记软件也应该是这样的,今天我看到一朵花,恍惚记得之前有记过与花相关的笔记,当我去搜索时它能够快速返回我想要的记录就 OK,flomo 在这两点上做的刚好甚得我心。 +当然 flomo 还开放了 API 功能,这让记笔记这件事变得方便且有趣,比如在 iOS 上选中文字发送到 flomo 就变成了一个 MEMO,同样也可以实现在 Mac 上选中文字发送到 flomo,不过我使用最多的还是它的微信输入和随机漫步功能。 + +记笔记是为了更好的让自己思考,不要像微信和 QQ 收藏那样,让 read it later 变成了 read it never。最后说一下可以通过邀请链接注册 flomo 获得 28 天 PRO 会员。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何搭建一个属于自己的博客/企业网站 + +

+ +
+ + + + +
+ +
+ +参考内容: +如何做博客/企业站以及注意事项 +Typecho支持Emoji表情方法 + +说明:此篇文章得益于王红星的指导,喜欢直接粗暴一点的朋友可以跳过前面,直接从程序的选择开始阅读。 +我的博客搭建之路 +说起来有点惭愧,我自己是计算机科学与技术专业出身,虽然博客系统在我眼里是很简单的系统,但是我却一直畏惧从零开始搭建一个系统的麻烦性,因为但是安装程序的运行环境就会耗掉我大量的时间,再加上我写代码、测试、上线等工作少说也得要四五天!所以我一直都在 CSDN 一类的平台写作,然而这些平台为了利益把体验做的越来越差! +还在大学时听说过 WordPress 可以搭建博客,不过总是迈不出行动的步伐,认为一旦涉及到服务器的工作就不会简单。直到我在 Twitter 发现有人推荐 Gridea,才发现搭建博客系统原来可以这么简单,主要还是免费的。 +于是我借用 Gridea 和 Github Pages 搭建了一个博客系统,EryouHao 开发的这个写作软件用起来相当便捷。但是 Github Pages 在国内总是速度很慢,而且图片资源经常加载不出来,而且 Gridea 是用 Vue 技术开发的,打包成桌面软件后总会出现卡顿情况,自己写作的那个劲又逐渐褪去了。在2020 年国庆观《我和我的家乡》有感所写中也提到在知乎一不小心就违反了社区规范,索性花功夫研究如何搭建自己的博客系统,也才有了这篇简单的教程。 +一个博客/企业站需要什么 +可以在网络上访问的东西背后都有一套程序支撑,博客系统(企业站点)就是运行在某处的一套程序。要把一套程序运行起来,肯定需要运行程序的空间和驱动程序运行的系统,比如我们的手里的电脑就是一种运行程序的空间,你使用的系统(Windows、macOS、Linux)就是驱动程序运行的系统,下面推荐的服务器都已经把把空间和驱动给你安装好了,所以完全不用担心自己不懂如何安装。 +程序运行起来了还得让外面的人看到才行,所以我们还需要一个域名(类似于www.baidu.com一样的东西),我们把这个域名绑定到服务器上,别人在浏览器输入这个域名就能看到我们的博客(程序)了。 +如果希望自己的博客打开速度很快,除了选择比较好的空间外,还可以购买 CDN 服务;希望自己的文章能更好的被百度之类搜索引擎收录的话,可以购买独立 IP 主机。 +程序的选择 +WordPress 应该是目前全球使用的最广泛的开源程序,结构良好,功能强大,社区内容丰富。但是对于博客(企业站点)来说,WordPress 就显得比较臃肿。Typecho 是一个轻量、简洁、快速的程序,除了官方论坛,在https://typecho.me/也有很多主题和插件。虽然 Typecho 各种主题、插件没有 WordPress 丰富,但是对于搭建博客系统已经足够了。 +网上也有文章对两个系统做了对比,比如这篇:个人博客平台选择 Typecho 还是 WordPress ?Typecho 是原生支持 Markdown 语法的,可能是喜欢 Markdown 写作同学的福音,具体选 Typecho 还是 WordPress 可以凭自己的感觉,两个程序之间也是可以互相迁移的。 +WordPress 安装包下载:https://wordpress.org/download/(12.4M) +Typecho 安装包下载:http://typecho.org/(400K) +空间的选择 +空间类型包括服务器、VPS和虚拟主机,它们的价格是逐渐减少的。对大部分人来说并不是越贵越好,比如服务器和 VPS 还需要自己懂一些运维知识才行。仅仅只是搭建博客(企业站点)的话,一个虚拟机就足够使用了,又便宜又不费心。 +空间地理位置是需要重点考虑的一个因素,如果你做的是英文站点、都是国外用户,那可能一个美国主机更适合你;如果你的用户是国内的话,那大陆主机和香港主机可能更适合你。 +王红星在他的文章中推荐了两个主机商,分别是衡天主机和戈戈主机。我自己选的是衡天主机,有问题可以随时通过他们的客服询问或是提工单,体验很不错!偷偷告诉你,在购买前可以搜搜优惠码!阿里云也很不错,只是稍微有点贵! +域名注册 +国内有腾讯云、阿里云、爱名网等域名注册商,国外有 Name、Namecheap、Godaddy、Dynadot 等域名注册商。有些域名商默认会提供域名保护功能,有的则需要购买该功能。各个域名注册商的价格也都差不多,根据自己的实际需求选一个就可以了。 +我自己选的是腾讯云,它第一年域名的优惠力度比较大,比如我要注册guanngxu.com这个域名,可以看到都是有优惠的。 +腾讯云:https://dnspod.cloud.tencent.com/ +阿里云:https://wanwang.aliyun.com/ +爱名网:https://www.22.cn/ + +域名解析 +我们所有的准备工作都做好了,下一步就是安装程序搭建博客系统了,下面我以衡天主机安装 Typecho 为例进行一个简略的讲解,结合我下面的内容和网上的资料,应该很快就能搭建自己的博客系统了。 +主机购买后就知道它的 IP 地址了,首先我们去域名注册商处选择把域名解析到我们买的服务器上面。以腾讯云为例,在「我的域名」页面点击「解析」--&gt;「快速添加网站/邮件解析」后会弹出如下页面。 + +选择「网站解析」的「立即设置」后会弹出另一个页面,你只需要在这个页面把你购买的主机 IP 地址填进去就可以了。稍等几分钟直接在浏览器输入域名访问,如果浏览器出现了页面而不是报找不到服务器 IP 地址,那么域名解析就完成了。 + +安装 Typecho +进入到云主机管理面板后,点击「文件管理器」可以看到如下图的目录,其中public_html目录就是咱们博客程序的目录,我们把下载好的 Typecho 包上传到这个目录并解压。 + +需要注意的是解压后的目录是build目录,我们需要将解压后的目录移动到上一级,,保证public_html目录看到的是下图这个样子的,然后再输入域名去访问自己的博客。 + +如果不出意外,输入域名后出现的应该是下面的页面,点击「我准备好了,开始下一步」发现了什么?需要填写数据库信息。我们再回到衡天云主机管理面板,滑到「数据库管理」选择「新建数据库」,设置数据库名、数据库用户名、密码等,点击「创建」即可,这些信息就是安装 Typecho 需要的信息,再回到之前的页面把这些信息填进去,自己的博客系统就搭建完成了。 + +那么现在你就拥有了一个自己的博客系统,如果样子不太好看你可以去官方论坛或https://typecho.me/里面找自己喜欢的主题,如果有能力甚至可以自己修改或原创主题,一些插件在上面也都是可以找到的。 +Typecho 官方也提供了相应的安装文档,如何使用插件、如何调整网站外观等常见问题在官方链接http://docs.typecho.org/doku.php也都已经有说明了,此处便不再作赘述! +让 Typecho 支持 Emoji 表情 +21 世纪的互联网时代怎么能少了 Emoji 表情呢?由于编码问题,Typecho 默认是不支持 Emoji 表情的,所以我们只需要将编码改成支持 Emoji 表情的编码就可以了,具体一点就是把原来的utf8编码修改为utf8mb4,修改编码的方式如下: +修改数据库编码 +在衡天云主机管理面板选择「phpMyAdmin」,然后选择你刚才建立的数据库,选择「操作」--&gt;「排序规则」--&gt;「utf8mb4_unicode_ci」--&gt;「执行」 +修改表编码 +在 phpMyAdmin 面板点击「SQL」,直接运行下面的语句就可以了。要注意你所建表的前缀,我建表所填写的前缀是gx,所以我都是gx_xxxxxxxxx,默认前缀是typecho。 +alter table gx_comments convert to character set utf8mb4 collate utf8mb4_unicode_ci; +alter table gx_contents convert to character set utf8mb4 collate utf8mb4_unicode_ci; +alter table gx_fields convert to character set utf8mb4 collate utf8mb4_unicode_ci; +alter table gx_metas convert to character set utf8mb4 collate utf8mb4_unicode_ci; +alter table gx_options convert to character set utf8mb4 collate utf8mb4_unicode_ci; +alter table gx_relationships convert to character set utf8mb4 collate utf8mb4_unicode_ci; +alter table gx_users convert to character set utf8mb4 collate utf8mb4_unicode_ci; + +修改数据库配置文件 +数据库配置文件在public_html文件夹下,文件名为config.inc.php,其中有一行的内容是charset =&gt; utf8,将它修改为charset =&gt; utf8mb4就可以了。 +看看有了小表情是不是很可爱? + + +
+ + Read More ~ +
+
+
+ +
+

+ + 那些鲜为人知的微信使用技巧,让微信更便捷高效 + +

+ +
+ + + + +
+ +
+ 本文整理的技巧基于两种目的,一种是实用方便用来解决生活中的小问题的,另外一种是用来装逼去撩小妹妹的,可能大多数人都更喜欢装逼撩妹的技巧吧。 +被人@时掉表情包 +在微信群内聊天时如果发送的消息触发了某个关键词就会掉下来表情包,为聊天增添了一些乐趣。我们可以通过人为的方式让别人一@你,就自动掉表情包。操作起来很简单,就是在群聊昵称末尾添加一些不同的字符,比如我在群聊昵称末尾加了「คิดถึง」顶部就掉下来了满屏的星星✨。 + +其实把这个泰文翻译一下就知道没有那么神秘了,微信内置的翻译功能将「คิดถึง」解释为「想念」,这下就明白为什么会掉星星✨了吧。「สุขสันต์วันเกิด」解释为「生日快乐」就会掉蛋糕,其它还有很多可以自己翻译一下就可以了。 +同一条朋友圈别人评论/点赞不再提示 +如果某个朋友发了一条朋友圈,你在下面评论或是点赞了,那么当你们的共同好友也对同一条朋友圈点赞或评论时,就会在「发现」有消息提示,但很多时候我们并不想要这样的提示,那怎么办呢?点到朋友圈的消息页中,iOS 向左滑动(安卓长按)就可以看到有个「不再通知」按钮,点击之后共同好友再点赞或评论就不会收到提示啦! + +用好微信内置的代办事项 +微信内置的代办事项功能估计没多少人知道,微信无疑是我们打开频率最高的 APP,把代办事项放在微信就更不容易忘事了。 + +可以看到它被置顶在微信的首页界面,每次启动微信都能看到,非常的方便,那么如何把代办事项置顶呢? +第一步:打开微信 &gt; 我 &gt; 收藏 &gt; 右上角➕号 &gt; 右下角图标 &gt; 代办 + +第二步:右上角三个点 &gt; 左下角在聊天中置顶 + +重要事件提醒 +我们经常会在微信上答应别人一些事情,为此可能去找一些清单软件来提醒自己,问题是清单软件又不常打开,常常会导致事情的忘记,给别人留下一个言而无信的形象。其实微信自身就已经带了提醒功能,长按任何一条消息,上面有一个提醒按钮,点击设置提醒时间,到了提醒时间后,会在「服务通知」中有推送的提醒。 +改善公众号阅读体验 +微信公众号阅读文章的体验不佳,不支持分组,也不支持进行上一次阅读,这时可以选择公众号任意一篇文章,点击右上角之后找到「在微信读书中打开」,如果没有这个功能,可能需要升级微信或者下载微信读书 App。 + +建立一个人的微信群 +怎么建立只有一个人的微信群呢?选择 -&gt; 添加朋友 -&gt; 面对面建群 -&gt; 输入数字 -&gt; 建群完成。这个只有自己的群就相当于一个信息分组,可以把自己平时的想法、待阅读的文章、写作灵感等发到里面,就自己一个人看,而且还可以对微信群进行置顶操作,不会受到其他微信群信息的影响,处理完的信息也可以像删聊天记录一样删掉,当然也可以选择发给文件助手或者自己。 +使用微信编辑图片 +我们有时候为了别人能更方便的理解自己想表达的意思,或者图片上的某些位置不想被别人看到,可以在添加图片的时候选择「预览」,就可以对图片进行编辑了。最实用的就是斗图的时候,对方一张图过来,如果手里没有合适的图片,可以直接基于对方的图片进行编辑,怼回去。 +利用微信收藏快速拼图 +选择「收藏」,在里面添加图片,然后选择右上角三个...的按钮,之后选择保存为图片,这样你添加的图片就拼接为了一张长图了,非常方便的操作,有的朋友喜欢分享聊天记录,就可以这么拼接。 + +善用「搜一搜」 +微信的「搜一搜功能很强大,比如有一天你无意在朋友圈看到一条信息,当时没有发现它的价值,过了一段时间因为其他事,突然恍惚记起了以前某条朋友圈提到过相关的情况,想查一查。这时就可以通过「搜一搜」来快速查找了。除此之外,「搜一搜」还能搜索文章,方便你快速查看与某个主题相关的文章;以及表情包搜索等功能,在斗图的时候不会再为没有表情包而烦恼。 + +僵尸粉检测 +相信很多人都遇到过别人给你发一条消息,上面注明是为了清僵尸粉的,但是这样会打扰到绝大部分微信好友,也给别人留下很不好的印象,我很反感收到别人的清粉消息。如果你怀疑某个人删除了你,你试着转账测试一下就可以了。 + +推荐几个小程序 +微信发票助手,经常出差的朋友或者行政小姐姐,常遇到的问题是在开公司发票的时候,需要填写一些信息,一般有记不住的,可以使用这个小程序填写好,之后每次需要的时候打开就好了,或者别人需要的时候,你分享二维码给他就可以了。发票信息填完后,会在「个人信息」底部多一个「我的发票抬头」,很方便的哦。 + +微信指数,有时候可能会做一些调查,比如写文章的朋友,文章标题到底是用“旅游”还是“旅行”好呢?这个时候就可以用微信指数来做调查,可以查看不同词汇之间的热度。 + +文章截图,IOS 用户比较苦恼的应该就是截长图了吧,有人就专门做了一个小程序,只需要把文章的链接复制过去,就可以自动生成截图,还可以生成 PDF 文件哦。 + +蚂蚁清单,很多人都有使用待办事项管理软件,但是有时候又忘记打开,这时不妨把它放到日常打开频率最高的微信里面去,而且还能为手机节省一点空间,蚂蚁清单的体验很好,可以自己去尝试。 + +一条人生经验 +最后说一条人生经验吧,敏感和违法不和谐的话题不要聊。 + +
+ + Read More ~ +
+
+
+ +
+

+ + Vim 常用命令快捷查询 + +

+ +
+ + + + +
+ +
+ +参考内容 +Learning Vim The Pragmatic Way +《鸟哥的 Linux 私房菜》 + +Vim 可以认为是 Vi 的高级版本,Vim 可以用颜色或下划线的方式来显示一些特殊信息,您可以认为 Vi 是一个文本处理工具,而 Vim 是一个程序开发工具,现在大部分 Linux 的发行版都以 Vim 替换 Vi 了。在 Linux 命令行模式下有很多编辑器,但是 Vi 文本编辑器是所有 Unix-like 系统都会内置的,因此学会 Vi/Vim 的使用时非常有必要的,对于 Vi 的三种模式(命令模式、编辑模式、命令行模式)这里就不在做说明了,下面是一些比较常用的命令。 +一般命令模式下 + + + +命令 +说明 + + + + +h、j、k、l +与键盘的方向键一一对应,分别为左、下、上、右,在键盘上着几个字母是排在一起的 + + +Ctrl+f、Ctrl+b +分别对应键盘的「Page Down」、「Page Up」,我更习惯于这两个键,而不是前面的组合键 + + +0、$ +分别对应键盘的「Home」、「End」,即移动到该行的最前面/后面字符处 + + +n&lt;Enter&gt; +n 为数字,光标向下移动 n 行 + + +/word、?word +向光标之上/下寻找一个字符串名称为 word 的字符串 + + +n、N +如果我们刚刚执行了上面上面的 /word 或 ?word 查找操作,那么 n 则表示重复前一个查找操作,可以简单理解为向下继续查找下一个名称为 word 的字符串,N 则与 n 刚好相反 + + +:n1,n2s/word1/word2/g +在第 n1 行与 n2 行之间寻找 word1 这个字符串,并将这个字符串替换为 word2,如果前面的 n1,n2 使用 1,$ 代替则表示从第一行到最后一行,最后的 g 后面可以加个 c,即 :1,$s/word1/word2/gc,这样就会在替换钱显示提示字符给用户确认(confirm) + + +x、X +分别对应键盘的「Del」、「Backspace」键 + + +dd、yy +删除/复制光标所在的那一整行 + + +p、P +p 将已复制的数据在光标下一行粘贴,P 粘贴在光标上一行 + + +u +恢复前一个操作,类似于 Windows 下的 Ctrl+Z + + +Ctrl+r +重做上一个操作 + + +. +小数点,重复上一个操作 + + + +命令行模式下 + + + +命令 +说明 + + + + +:w +将编辑的数据写入硬盘中 + + +:w! +若文件属性为只读,强制写入该文件,不过到底能不能写入,还是跟文件权限有关系 + + +:q、:q! +与 w 一样,q 为关闭的意思 + + +:r [filename] +在编辑的数据中读入另一个文件的数据,即将[filename]这个文件的内容追加到光标所在行的后面 + + +:w [filename] +将编辑的数据保存为另一个文件 + + +:set nu/nonu +显示/不显示行号 + + + +编辑模式下 + + + +组合键 +作用 + + + + +[ctrl]+x -&gt; [ctrl]+n +通过目前正在编辑的这个文件的内容文字作为关键字,予以自动补全 + + +[ctrl]+x -&gt; [ctrl]+f +以当前目录内的文件名作为关键字补全 + + +[ctrl]+x -&gt; [ctrl]+o +以扩展名作为语法补充,以 Vim 内置的关键字予以补全 + + + +当我们在使用 Vim 编辑器的时候,Vim 会在与被编辑的文件目录下再建立一个名为.filename.swp的文件,我们对文件的操作都会记录到这个 swp 文件中去,如果系统因为某些原因掉线了,就可以利用这个 swp 文件来恢复内容。如果存在对应的 swp 文件,那么 Vim 就会主动判断当前这个文件可能有问题,会给出相应的提示。 +我们也可以给 Vim 环境设置一些个性化的参数,虽然在命令行模式下可以使用:set来设置,但是这样每次设置实在是太麻烦,因此我们可以设置一些全局的参数。Vim 的整体设置值一般放在/etc/vimrc中,我们一般通过修改~/.vimrc这个文件(默认不存在)来设置一些自己的参数,比如: +&quot; 该文件的双引号是注释 +set nu &quot;在每一行的最前面显示行号 +set autoindent &quot; 自动缩进 +set ruler &quot; 可显示最后一行的状态 +set bg=dark &quot; 显示不同的底色色调 +syntax on &quot;进行语法检验,颜色显示,比如 C 语言等 + +最后附上一张命令速查卡,此图来源于Learning Vim The Pragmatic Way,PDF 版下载链接在这里。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 使用订阅号实现微信公众号历史文章爬虫 + +

+ +
+ + + + +
+ +
+ 微信公众号已经成为生活的一部分了,虽然里面有很多作者只是为了蹭热点,撩读者的 G 点,自己从中获得一些收益;但是不乏好的订阅号,像刘大的码农翻身、Fenng的小道消息、曹大的caoz的梦呓等订阅号非常值得阅读。 +平时有时候看到一些好的公众号,也会不自觉去查看该公众号的历史文章,然而每次都看不完,下一次再从微信里面打开历史文章,又需要从头翻起。而且对于写了很多年的大号,每次还翻不到底。有一些平台提供了相关的服务,但是得收几十块钱的费用,倒不是缺几十块钱,主要是觉得这种没必要花的钱不值得去浪费。 +网上搜如何爬微信公众号历史文章,大致给了三种思路,第一是使用搜狗微信搜索文章,但是好像每次能搜到的不多;第二是使用抓包工具;第三种是使用个人订阅号进行抓取。 +简单来说就是使用程序来模拟人的操作,抓取公众号历史文章。首先登录微信公众号个人平台,期间需要管理员扫码才能登录成功。 +def __open_gzh(self): + self.driver.get(BASE_URL) + self.driver.maximize_window() + username_element = self.driver.find_element_by_name(&quot;account&quot;) + password_element = self.driver.find_element_by_name(&quot;password&quot;) + login_btn = self.driver.find_element_by_class_name(&quot;btn_login&quot;) + username_element.send_keys(USERNAME) + password_element.send_keys(PASSWORD) + login_btn.click() + WebDriverWait(driver=self.driver, timeout=200).until( + ec.url_contains(&quot;cgi-bin/home?t=home/index&quot;) + ) + # 一定要设置这一步,不然公众平台菜单栏不会自动展开 + self.driver.maximize_window() + +进入微信公众平台首页后,点击素材管理,然后点击新建图文素材,就会进入到文章写作页面,此时前面打开的微信公众平台首页就不需要了,可以将其关闭。 + +def __open_write_page(self): + management = self.driver.find_element_by_class_name(&quot;weui-desktop-menu_management&quot;) + material_manage = management.find_element_by_css_selector(&quot;a[title='素材管理']&quot;) + material_manage.click() + new_material = self.driver.find_element_by_class_name(&quot;weui-desktop-btn_main&quot;) + new_material.click() + # 关闭公众平台首页 + handles = self.driver.window_handles + self.driver.close() + self.driver.switch_to_window(handles[1]) + +在文章写作页面的工具栏上面有一个超链接按钮,点击超链接即会弹出超链接编辑框,选择查找文章,输入自己喜欢的公众号进行查找,一般第一个就是自己想要的结果,点击对应的公众号,该公众号所有的文章就会通过列表的形式展现出来。 + + + +def __open_official_list(self): + # 超链接 + link_click = self.driver.find_element_by_class_name(&quot;edui-for-link&quot;) + link_click.click() + time.sleep(3) + # 查找文章 + radio = self.driver.find_element_by_class_name(&quot;frm_vertical_lh&quot;).find_elements_by_tag_name(&quot;label&quot;)[1] + radio.click() + # 输入查找关键字 + search_input = self.driver.find_element_by_class_name(&quot;js_acc_search_input&quot;) + search_input.send_keys(OFFICIAL_ACCOUNT) + search_btn = self.driver.find_element_by_class_name(&quot;js_acc_search_btn&quot;) + search_btn.click() + # 等待5秒,待公众号列表加载完毕 + time.sleep(5) + result_list = self.driver.find_element_by_class_name(&quot;js_acc_list&quot;).find_elements_by_tag_name(&quot;div&quot;) + result_list[0].click() + +文章列表已经展现出来了,直接抓取每条文章超链接的信息即可,每抓取完一页就进入下一页,继续抓取文章列表信息,直到所有文章信息都抓取完毕。 + +def __get_article_list(self): + # 等待文章列表加载 + time.sleep(5) + total_page = self.driver.find_element_by_class_name(&quot;search_article_result&quot;)\ + .find_element_by_class_name(&quot;js_article_pagebar&quot;).find_element_by_class_name(&quot;page_nav_area&quot;)\ + .find_element_by_class_name(&quot;page_num&quot;)\ + .find_elements_by_tag_name(&quot;label&quot;)[1].text + total_page = int(total_page) + articles = [] + for i in range(0, total_page-1): + time.sleep(5) + next_page = self.driver.find_element_by_class_name(&quot;search_article_result&quot;)\ + .find_element_by_class_name(&quot;js_article_pagebar&quot;).find_element_by_class_name(&quot;pagination&quot;)\ + .find_element_by_class_name(&quot;page_nav_area&quot;).find_element_by_class_name(&quot;page_next&quot;) + article_list = self.driver.find_element_by_class_name(&quot;js_article_list&quot;)\ + .find_element_by_class_name(&quot; my_link_list&quot;).find_elements_by_tag_name(&quot;li&quot;) + for article in article_list: + article_info = { + &quot;date&quot;: article.find_element_by_class_name(&quot;date&quot;).text, + &quot;title&quot;: article.find_element_by_tag_name(&quot;a&quot;).text, + &quot;link&quot;: article.find_element_by_tag_name(&quot;a&quot;).get_attribute(&quot;href&quot;) + } + articles.append(article_info) + next_page.click() + return articles + +至此,微信公众号历史文章的爬虫已经实现,其实整个过程只不过是用程序来模拟的了人类的操作。需要注意的是,程序不能设置太快,因为微信做了相关限制,所以设太快会在一段时间内无法使用文章查找功能;另外一点是使用选择器选择页面元素的时候,会有一些坑,而且我发现不同账号登录,有很少部分的页面元素虽然直观上是一样的,但是它的 html 代码有细微的差别。 +这个小程序会用到selenium库,和chromedriver,前者直接pip install即可,后者自行下载;另外你还需要一个订阅号才行,本文只实现了关键的文章信息抓取,并没有进行文章信息的持久化存储,完整代码在这里。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/mZ_iBKmVWo/index.html b/mZ_iBKmVWo/index.html new file mode 100644 index 00000000..1a2f4c7a --- /dev/null +++ b/mZ_iBKmVWo/index.html @@ -0,0 +1,1580 @@ + + + + + + + + 操作系统 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 操作系统 +
+ + + + +
+

+ + linux-5.10.157 内核源码编译 + +

+ +
+ + + + +
+ +
+ +参考内容: +Linux内核开发_1_编译LInux内核 +编译linux内核报错:flex: not foundscripts +编译kernel5.14报错fatal error: openssl/opensslv.h +编译内核错误——*** 没有规则可制作目标“debian/canonical-certs.pem” +内核错误:BTF: .tmp_vmlinux.btf: pahole (pahole) is not available + +# 切换到 root 账户 +sudo su + +# 查看操作系统版本 +cat /etc/issue + +# 查看 Linux 内核版本 +cat /proc/version + +# 进入 root 账户目录 +cd /home/root + +# 下载 Linux 内核源码 +wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.157.tar.xz +# Linux 其它版本源码 https://www.kernel.org/ + +# xz 解压 +xz -d linux-5.10.157.tar.xz + +# tar 解压到 /usr/src/linux-5.10.157 目录下 +tar -xf linux-5.10.157.tar -C /usr/src/. + +# 进入源码目录 +cd /usr/src/linux-5.10.157 + +# 查看源码结构 +tree . -L 2 + +# 若没有 tree 命令,可以执行下面命令 +# apt-get install tree + +# 配置编译选项 +make menuconfig + +# 若没有 make,可以执行下面命令 +# apt-get install make + +# 若执行 make 后报错找不到 curses.h,可以执行下面命令 +# apt-get install libncurses5-dev + +# 若报错找不到 flex not found,可以执行下面两条命令 +# apt-get install flex +# apt-get install bison + +# 再次运行 make menuconfig 弹出图形化配置页面后 +# 若使用默认配置,则直接按两次 Esc 键退出即可 +# 此时会在当前目录下生成 .config 文件 + +# 编译 Linux 源码 +make bzImage -j4 + +# 在编译过程中若报错 fatal error: openssl/opensslv.h,可执行下面命令 +# apt-get install libssl-dev +# 若还出现同样的问题,可参考 https://blog.csdn.net/ComputerInBook/article/details/107380796 源码编译安装 openssl + +# 若出现「没有规则可制作目标“debian/canonical-certs.pem”」报错 +# 需要删除 .config 中相应的字段,总共有两处 +# 一处为 CONFIG_SYSTEM_TRUSTED_KEYS=&quot;debian/canonical-certs.pem&quot; +# 一处为 CONFIG_SYSTEM_REVOCATION_KEYS=&quot;debian/canonical-revoked-certs.pem&quot; + +vim .config +# 删除之后的样子如下(需要保留引号): +# 一处为 CONFIG_SYSTEM_TRUSTED_KEYS=&quot;&quot; +# 一处为 CONFIG_SYSTEM_REVOCATION_KEYS=&quot;&quot; + +# 若出现 BTF: .tmp_vmlinux.btf: pahole (pahole) is not available 错误,则执行下面命令 +# apt-get install dwarves + +# 若在过程中还出现其它问题,大多是因为缺少相关库导致的,直接用 apt-get install 即可 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 预处理器 + +

+ +
+ + + + +
+ +
+ C 语言的编译需要经过很多步骤,其中第一个步骤称为预处理阶段。这个阶段的主要任务包括删除注释、插入被#include指令包含的文件的内容、定义和替换#define指令定义的符号以及确定代码的部分内容是否应该跟绝一些条件编译指令进行编译。 +#define +#define指令就是为数值命名一个符号。比如#define name stuff指令,有了它之后,每当有符号name出现在这条指令后面时,预处理器就会把它替换成stuff,比如下面几个例子: +// 为关键字 register 创建了一个简短的别名 +#define reg register +// 声明了一个更具描述性的符号用来替代实现无限循环的 for 语句 +#define do_forever for(;;) +// 定义了一个简短记法,在 switch 语句中使用,可以自动把一个 break 放在每个 case 之前 +#define CASE break;case + +当然如果定义中的stuff非常长,那么也可以将它分成几行,除了最后一行之外,每行的末尾都需要加一个反斜杠。比如: +#define log_debug printf(&quot;File[%s]line[%d]:&quot; \ + &quot; x=[%d], y=[%d], z=[%d]&quot;, \ + __FILE__, __LINE__, \ + x, y, z) + +// 那么我们将可以很方便的插入一条调试语句打印 +x *= 2; +y += x; +z = x * y; +log_debug; + +很容易就发现上面的log_debug定义无法进行泛化,当然设计者也考虑到了这个问题,所以#define机制包括了一个规定,即允许把参数替换到文本中,这种实现一般称为宏,其声明方式如下: +define name(parameter-list) stuff + +需要注意的是parameter-list是一个由逗号分隔的符号列表,他们可能出现在stuff中。参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。下面我们看一个具体的列子,以此了解宏定义的机制,并将它逐步优化改进: +#define SQUARE(x) x * x + +// 使用 +SQUARE(5) +// 效果:5 * 5 + +考虑一下下面的代码段: +a = 5; +printf(&quot;%d\n&quot;, SQUARE(a + 1)); + +乍一看觉得这段代码将打印36这个值。但实际它却会打印11,我们仔细观察一下被替换的宏文本,即参数x被文本a + 1替换: +a = 5; +printf(&quot;%d\n&quot;, a + 1 * a + 1); + +很容易想到对参数 x 加一个括号解决上述问题,即: +#define SQUARE(x) (x) * (x) + +// 上述打印将会被替换为 +a = 5; +printf(&quot;%d\n&quot;, (a + 1) * (a + 1)); + +类似的我们可以再定义一个DOUBLE宏,即: +#define DOUBLE(x) (x) + (x) + +但是考虑下面的使用方式: +a = 5; +printf(&quot;%d\n&quot;, 10 * DOUBLE(5)); + +看上去它应该打印的结果是100,但事实上它打印的是55,我们再通过宏替换产生的文本观察问题: +printf(&quot;%d\n&quot;, 10 * (5) + (5)); + +所以我们需要在整个表达式两边加上一对括号。所有用于对数值表达式进行求值的宏定义都应该使用下面这种方式加上括号,避免在使用宏时,由于参数中的操作符或邻近的操作符之间不可预料的相互作用。 +#define DOUBLE(x) ((x) + (x)) + +宏与函数 +宏非常频繁的用于执行简单的计算,比如在两个表达式中寻找其中较大(小)的一个: +#define MAX(a, b) ((a) &gt; (b) ? (a) : (b)) + +那么为什么不使用函数来完成这个任务呢?首先用于调用和从函数返回的代码很可能比实际执行这个小型计算工作的代码更大,所以使用宏比使用函数在程序的规模和速度方面都更胜一筹。 +更为重要的是函数必须声明为一种特定的类型,所以它只能在类型合适的表达式上使用。但是上面的这个宏可以用于整型、长整型、单浮点型、双浮点型以及任何其它可以使用&gt;操作符比较值大小的类型,即宏与类型无关。 +当然宏也有它的不利之处,因为每次在使用宏时,一份宏定义代码的拷贝都将插入到程序中,除非宏的定义非常短,否则使用宏将会大幅增加程序的长度。 +也有一些任务根本无法使用函数实现,比如下面这个宏的第二个参数是一种类型,它无法作为函数参数进行传递。 +#define MALLOC(n, type) ((type *)malloc((n) * sizeof(type))) + +当宏参数在宏定义中出现的次数超过一次时,如果这个参数具有副作用,那么当使用这个宏的时候就可能出现危险,导致一些不可预料的后果。比如x++就是一个具有副作用的表达式,它会改变x的值,直接会导致下面的代码段出现不可预知的后果: +#define MAX(a, b) ((a) &gt; (b) &gt; (a) : (b)) + +x = 5; +y = 8; +z = MAX(x++, y++); +// z = ((x++) &gt; (y++) &gt; (x++) : (y++)) + + + + +属性 +#define 宏 +函数 + + + + +代码长度 +每次使用时,宏代码都被插入到程序中。除了非常小的宏志伟,程序的长度将大幅度增长 +函数代码只出现于一个地方,每次使用这个函数时,都调用那个地方的同一份代码 + + +执行速度 +更快 +存在函数调用/返回的额外开销 + + +操作符优先级 +宏参数的求值是在所有周围表达式的上下文环境里,除非它们加上括号,否则邻近操作符的优先级可能回产生不可预料的结果 +函数参数只在函数调用时求值一次,它的结果传递给参数。表达式的求值结果更容易预测 + + +参数求值 +参数每次用于宏定义时,它们都将重新求值。由于多次求值,具有副作用的参数可能会产生不可预料的结果 +参数在函数被调用前求值一次。在函数中多次使用参数并不会导致多种求值过程。参数的副作用不会造成任何特殊的问题 + + +参数类型 +宏与类型无关。只要对参数的操作是合法的,它可以使用于任何参数类型 +函数的参数是与类型有关的。如果参数的类型不同,就需要使用不同的函数,即使它们执行的任务时相同的 + + + +文件包含 +我们知道#include指令可以使另一个文件的内容被编译,就像它实际出现于#include指令出现的位置一样。这种替换的执行方式很简单:预处理器删除这条指令,并用包含头文件的内容取而代之。这样一个头文件如果被包含到 10 个源文件中,它实际上被编译了 10 次。 +基于这种替换的方式,当出现嵌套#include文件被多次包含时,就会出现问题: +#include &quot;a.h&quot; +#include &quot;b.h&quot; + +// 如果 a.h 和 b.h 中都包含一个 #include x.h +// 那么 x.h 在此处就出现了两次 + +这种多重包含在绝大多数情况下出现于大型程序中,它往往需要很多头文件,所以要发现这种情况并不容易。但是我们可以使用条件编译来解决这个问题: +#ifndef _HEADER_NAME_H_ +#define _HEADER_NAME_H_ + +/* +* All the stuff that you want in the header file +*/ + +#endif + + +
+ + Read More ~ +
+
+
+ +
+

+ + C 语言拾遗 + +

+ +
+ + + + +
+ +
+ +约定:本文所说的标准均为 ANSI (C89) 标准 + +三字母词 +标准定义了几个三字母词,三字母词就是三个字符的序列,合起来表示另一个字符。三字母词使得 C 环境可以在某些缺少一些必需字符的字符集上实现,它使用两个问号开头再尾随一个字符,这种形式一般不会出现在其它表达形式中,这样就不容易引起误解了,下面是一些三字母词的对应关系: +??( [ +??) ] +??! | +??&lt; { +??&gt; } +??' ^ +??= # +??/ \ +??- ~ + +所以在一些特殊情况下可能出现下面的情况,希望你不要被意外到。 +printf(&quot;Delete file (are you really sure??): &quot;); + +// result is: Delete file (are you really sure]: + +字符 +直接操作字符会降低代码的可移植性,应该尽可能使用库函数完成。比如下面的代码试图测试ch是否为一个大写字符,它在使用ASCII字符集的机器上能够运行,但是在使用EBCDIC字符集的机器上将会失败。 +if( ch &gt;= 'A' &amp;&amp; ch &lt;= 'Z') + +使用if(isupper(ch))语句则能保证无论在哪种机器上都能正常运行。 +字符串比较 +库函数提供了int strcmp(const char *s1, const char *s2)函数用于比较两个字符串是否相等,需要注意的是在标准中并没有规定用于提示不相等的具体值。它只是说如果第 1 个字符串大于第 2 个字符串就返回一个大于零的值,如果第 1 个字符串小于第 2 个字符串就返回一个小于零的值。一个常见的错误是以为返回值是1和-1,分别代表大于和小于。 +初学者常常会编写下面的表达式。认为如果两个字符串相等,那么它返回的结果将为真。但是这个结果恰好相反,两个字符串相等的情况下返回值是零(假)。 +if(strcmp(a, b)) + +strlen +strlen的返回值是一个size_t类型的值,这个类型是在头文件stddef.h中定义的,它是一个无符号整数类型,所以会导致下面表达式的条件永远为真。 +if(strlen(x) - strlen(y) &gt;= 0) { + // do something +} + +第二点需要注意的是strlen的返回值没有计算\0的长度,所以下面的代码在一些检查严格或老版本的编译器中会报错,其原因在于少分配了一个存储单位。 +// 假设 str 是一个字符串 +char *cpy = malloc(strlen(str)); +strcpy(cpy, str); + +// 正确写法应为 +char *cpy = malloc(strlen(str) + 1); +strcpy(cpy, str); + +赋值截断 +表达式a = x = y + 3;中x和a被赋予相同值的说法是错误的,因为如果x是一个字符型变量,那么y+3的值就会被截去一段,以便容纳于字符型的变量中,那么a所赋的值就是这个被截短后的值。下面也是一个非常常见的错误。 +char ch; +// do something +while((ch = getchar()) != EOF) { + // do something +} + +EOF所需要的位数比字符型值所能提供的位数要多,这也是getchar返回一个整型值而不是字符值的原因。例子中把getchar的返回值存储于字符型变量会导致被截短,然后再把这个被截短的值提升为整型与EOF进行比较,在某些机器的特定场景下就会导致问题。比如在使用有符号字符集的机器上,如果读取了一个的值为\377的字节,上述循环就将终止,因为这个值截短再提升之后与EOF相等。而当这段代码在使用无符号字符集的机器上运行时,这个循环将永远不会终止。 +指针与数组 +因为数组和指针都具有指针值,都可以进行间接访问和下标操作,所以很多同学都想当然的将它们认为是一样的,为了说明它们是不相等的,我们可以考虑下面的两个声明: +int a[5]; +int *b; + +声明一个数组时,编译器将根据声明所指定的元素数量为数组保留空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为任何指向现有的内存空间。所以在上述声明之后,表达式*a是完全合法的,但是表达式*b将访问内存中某个不确定的位置,或者导致程序终止。 +硬件操作 +我们知道其实表示就是内存中一个地址,所以理论上*100 = 25是一种可行的操作,即让内存中位置为100的地方存储25。但实际上这条语句是非法的,因为字面值100的类型是整型,而间接访问操作只能用于指针类型表达式,所以合法的写法必须使用强制转换,即*(int *)100 = 25。 +需要说明的是使用这种技巧的机会是绝无仅有的,只有偶尔需要通过地址访问内存中某个特定的位置才可使用,它并不是访问某个变量,而是访问硬件本身。比如在某些机器上,操作系统需要与输入输出设备控制器通信,启动 I/O 操作并从前面的操作中获得结果,此时这些地址是预先已知的。 ++= 与 ?: 操作符 +我们在这里讨论一下+=操作符,它的用法为a += expr,读作把 expr 加到 a,其实际功能相当于表达式a = a + expr的作用,唯一不同的是+=操作符的左操作数a只会求值一次。可能到目前为止没有感觉到设计两种增加一个变量值的方法有什么意义?下面给出代码示例: +// 形式 1 +a[ 2 * (y - 6*f(x)) ] = a[ 2 * (y - 6*f(x)) ] + 1; + +// 形式 2 +a[ 2 * (y - 6*f(x)) ] += 1; + +在第一种形式中,用于选择增值位置的表达式必须书写两次,一次在赋值号左边,一次在赋值号右边。由于编译器无法知道函数f是否具有副作用,所以它必须两次计算下标表达式的值,而第二种形式的效率会更高,因为下标表达式的值只会被计算一次。同时第二种形式也减少了代码书写错误的概率。 +同理三目运算符也可以起到类似的效果。 +// 形式 1 +if(a &gt; 5) { + b[ 2 * c + d * (e / 5) ] = 3; +} else { + b[ 2 * c + d * (e / 5) ] = -20; +} + +// 形式 2 +b[ 2 * c + d * (e / 5) ] = a &gt; 5 ? 3 : -20; + +逗号操作符 +逗号操作符可以将多个表达式分隔开来。这些表达式自左向右逐个进行求值,整个表达式的值就是最后那个表达式的值。例如: +if(b + 1, c / 2, d &gt; 0) { // do something} + +当然,正常人不会编写这样的代码,因为对前两个表达式的求值毫无意义,它们的值只是被简单的丢弃了。但是我们可以看看下面的代码: +// 形式 1 +a = get_value(); +count_value(a); +while(a &gt; 0) { + // do something + a = get_value(); + count_value(a); +} + +// 形式 2 +while(a = get_value(), count_value(), a &gt; 0) { + // do something +} + +指针 +int* a, b, c; + +人们会很自然的认为上述语句是把所有三个变量都声明为指向整型的指针,但事实上并非如此,星号实际上只是表达式*a的一部分,只对这个标识符有作用。如果要声明三个指针,那么应该使用下面的形式进行初始化。 +int *a, *b, *c; + +在声明指针变量时可以为它指定初始值,比如下面的代码段,它声明了一个指针,并用一个字符串常量对其进行初始化。 +char *msg = &quot;Hello World!&quot;; + +需要注意的是,这种类型的声明会让人很容易误解它的意思,看起来初始值似乎是赋给表达式*msg的,但实际上它是赋值给msg本身的,也就是上述声明实际形式如下: +char *msg; +msg = &quot;Hello World!&quot;; + +指针常量: int *pi中pi是一个普通的指向整型的指针, 而变量int const *pci则是一个指向整型常量的指针,你可以修改指针的值,但是不能修改它所指向的值。相比之下int * const cpi则声明cpi为一个指向整型的常量指针。此时指针是常量,它的值无法修改,但是可以修改它所指向的整型的值。在int const * const cpci中,无论是指针本身还是它所指向的值都是常量,无法修改。 +枚举类型 +枚举(enumerated) 类型就是指它的的值为符号常量而不是字面值的类型,比如下面的语句声明了Jar_Type类型: +enum Jar_Type { + CUP, + PINT, + QUART, + HALF_GALLON, + GALLON +}; + +需要注意的是,枚举类型实际上是以整型方式存储的,代码段中的符号名实际上都是整型值。在这里CUP的值是0,PINT的值是1,依次类推。 +在适当的时候,可以为这些符号名指定特定的值整型值。并且只对部分符号名进行赋值也是合法的,如果某个符号名没有显示的赋值,那么它的值就比前面一个符号名的值大 1。 +enum Jar_Type { + CUP = 8, + PINT = 16, + QUART = 32, + HALF_GALLON = 64, + GALLON = 128 +}; + + +符号名被当作整型处理,这意味着可以把HALF_GALLON这样的值赋给任何整型变量,但是在编程活动中应该避免这种方式使用枚举,因为这样会削弱它们的含义。 + +typedef 与 define +在实际应用过程中应该使用typedef而不是#define来创建新的类型名,因为#define无法正确的处理指针类型,比如下面的代码段正确的声明了a,但是b却被声明为了一个字符。 +#define ptr_to_char char * +ptr_to_char a, b; + +联合(union) +联合看起来很像结构体,与结构体不同的是联合的所有成员共用同一块内存,所以在同一时刻联合中的有效成员永远只有一个。我们可以看下面一个例子,当一个variable类型的变量被创建时,解释器就创建一个这样的结构并记录变量类型。然后根据变量类型,把变量的值存储在这三个值字段的其中一个。 +struct variable { + enum { INT, FLOAT, STRING } type; + int int_val; + float float_val; + char *str_val; +} + +不难发现上述结构的低效之处在于它所使用的内存,每个variable结构存在两个未使用的值字段,造成了内存空间上的不少浪费。使用联合就可以减少这种空间上的浪费,它把这三个值字段的每一个都存储在同一个内存位置。我们知道这三个字段并不会冲突,因为每个变量只可能具有一种类型,所以在具体的某一时刻,联合的这几个字段只有一个被使用。 +struct variable { + enum { INT, FLOAT, STRING } type; + union { + int i; + float f; + char *s; + } val; +} + +现在,对于整型变量,我们只需要将type字段设为INT,并把整型值存储于val.i即可。如果联合中各成员的长度不一样,联合的长度就是它最长成员的长度。 +联合的变量也可以被初始化,但是这个初始值必须是联合第 1 个成员的类型,而且它必须位于一对花括号里面。比如: +union { + int a; + float b; + chat c[4]; +} x = { 5 }; + +结构体 +在实际编程活动中,存在链表、二叉树等结点自引用的情况,那么结构体的自引用如何编写呢? +struct node { + int data; + struct node next; +} + +上述写法是非法的,因为成员next是一个完整的结构,其内部还将包含自己的成员next,这第 2 个成员又是另一个完整结构,它还将包含自己的成员next,如此重复下去将永无止境。正确的自引用写法如下: +struct node { + int data; + struct node *next; +} + +我们需要注意下面的这个陷阱: +/* +错误写法:因为类型名 node_t 直到声明末尾才定义 +所以在结构中声明的内部 node_t 尚未定义 +*/ +typedef struct { + int data; + node_t *next; +} node_t; + +// 正确写法 +typedef struct node_tag { + int data; + struct node_tag *next; +} node_t; + +编译器在实际分配时会按照结构体成员列表的顺序一个接一个的分配内存,并且只有当存储成员需要满足正确的边界对齐要求时,成员之间可能会出现用于填充的额外内存空间。 + +```c +struct align { + char a; + int b; + char c; +} + +如果某个机器的整型值长度为 4 个字节,并且它的起始存储位置必须能够被 4 整除,那么这个结构在内存中的存储将是下面这种形式 + + + +a + + + +b +b +b +b +c + + + + + + +我们可以通过改变成员列表的声明顺序,让那些对边界要求严格的成员首先出现,对边界要求弱的成员最后出现,这样可以减少因为边界对齐而带来的空间损失。 +struct align { + int b; + char a; + char c; +} + + + + +b +b +b +b +a +c + + + +当程序创建几百个甚至几千个结构时,减少内存浪费的要求就比程序的可读性更为急迫。我们可以使用sizeof操作符来得出一个结构的整体长度。如果必须要确定结构某个成员的实际位置,则可以使用offsetof(type, member)宏,例如: +offset(struct align, b); + +一句话 +标识符:标识符就是变量、函数、类型等的名字,标识符的长度没有限制,但是 ANSI 标准允许编译器忽略第 31 个字符以后的字符,并且允许编译器对用于表示外部名字(由链接器操作的名字)的标识符进行限制,只识别前 6 位不区分大小写的字符。 +注释:代码中所有的注释都会被预处理器拿掉,取而代之的是一个空格。因此,注释可以出现在任何空格可以出现的地方。 +类型:C 语言中仅有 4 种基本数据类型,即整型、浮点型、指针和聚合类型(数组、结构等),所有其它的类型都是从这 4 中基本类型的某种组合派生而来。 +类型长度:标准只规定了short int至少是 16 位,long int至少是 32 位,至于缺省的int是多少位则直接由编译器设计者决定。并且标准也没有规定这 2 个值必须不一样。如果某种机器的环境字长是 32 位,而且也没有什么指令能够更有效的处理更短的整型值,那它很可能把这 3 个整型值都设定为 32 位。 +位域:基于 int 位域被当作有符号还是无符号数、位域成员的内存是从左向右还是从右向左分配、运行在 32 位整数的位域声明可能在 16 位机器无法运行等原因,注重可移植性的程序应该避免使用位域。 +结构与指针:什么时候应该向函数传递一个结构而不是一个指向结构的指针呢?很少有这种情况。只有当一个结构特别小(长度和指针相同或更小)时,结构传递方案的效率才不会输给指针传递方案。 + +
+ + Read More ~ +
+
+
+ +
+

+ + Java 垃圾回收机制详解 + +

+ +
+ + + + +
+ +
+ 最近主要时间都放在知识图谱构建中,但是还是需要给自己充电。想在近段时间好好把 JVM 的垃圾回收好好看一下,学习然后输出,是我新找到的有效学习方法,希望你看了之后能有收获。 +什么是垃圾 +垃圾回收(常称做GC——Garbage Collection)诞生于1960年 MIT 的 Lisp 语言,垃圾回收机制也已经用在了很多编程语言中,比如 Java、Python、C# 等。我们这里主要说 Java 中的垃圾回收。 +在JVM中,程序计数器、虚拟机栈、本地方法栈都是随线程生而生,随线程灭而灭;栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理;常说的垃圾回收主要集中在堆和方法区,这部分内存是随着程序运行动态分配的。 +既然要回收垃圾,那么我们首先需要知道的就是,什么样的对象是垃圾。一般有两种方式: +引用计数 +每个对象有一个引用计数属性,新增一个引用时计数加 1,引用释放时计数减 1,当引用计数变为 0 的时候,这个对象就可以回收了。但是这个方法无法解决对象循环引用的问题。 + // 对象循环引用示例 + + Object objectA = new Object(); + Object objectB = new Object(); + + objectA.instance = objectB; + objectB.instance = objectA; + + objectA = null; + objectB = null; + +假设我们有上面的代码。程序启动后,objectA和objectB两个对象被创建并在堆中分配内存,它们都相互持有对方的引用,但是除了它们相互持有的引用之外,再无别的引用。而实际上,引用已经被置空,这两个对象不可能再被访问了,但是因为它们相互引用着对方,导致它们的引用计数都不为 0,因此引用计数算法无法通知GC回收它们,造成了内存的浪费。如下图:对象之间的引用形成一个有环图。 + +可达性分析 +或者叫根搜索算法,在主流的JVM中,都是使用的这种方法来判断对象是否存活的。这个算法的思路很简单,它把内存中的每一个对象都看作一个结点,然后定义了一些可以作为根结点的对象,我们称之为「GC Roots」。果一个对象中有另一个对象的引用,那么就认这个对象有一条指向另一个对象的边。 + +像上面这张图,JVM会起一个线程从所有的GC Roots开始往下遍历,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收。(这里多说一句,我们的JVM一起动,就至少有两个线程启动,一个是垃圾回收线程,一个是我们自己的主线程。) +那么现在问题就变成了——什么样的对象可以当作 GC Roots?共有四种对象可以作为 GC Roots。 +虚拟机栈中的引用的对象 +们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种对象可以作为 GC Roots。 +全局的静态的对象 +也就是使用了static关键字定义了的对象,这种对象的引用保存在共有的方法区中,因为虚拟机栈是线程私有的,如果保存在栈里,就不叫全局了,很显然,这种对象是要作为 GC Roots 的。 +常量引用 +就是使用了static final关键字,由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也作为GC Roots。 +本地方法栈中JNI引用的对象 +有时候单纯的java代码不能满足我们的需求,就可能需要调用 C 或 C++ 代码(Java 本身就是用 C 和 C++ 写的嘛),因此会使用native方法,JVM 内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为 GC Roots。 +垃圾回收算法 +有意思的是在 JVM 规范中,并没有明确指明 GC 的运作方式,各个厂商可以采用不同的方式去实现垃圾收集器。这篇文章简单介绍常见的垃圾回收算法。 +标记-清除算法 +标记-清除算法分两个步骤,分别为「标记」和「清除」,字如其人。它是一个最基础的垃圾回收算法,更高级的垃圾回收算法都是基于它改进的。 +它的运行过程是这样的:首先标记出所有需要回收的对象,标记完成后,再统一回收掉所有被标记的对象。 + +标记-清除算法的缺点有两个,一个是空间问题,标记清除之后会产生大量的不连续内存碎片。内存碎片太多,程序在之后的运行过程中就有可能找不到足够的连续内存来分配较大的对象,进而不得不提前触发另一次垃圾回收,导致程序效率降低。标记-清除算法的另一个缺点是效率问题,标记和清除的效率都不高,两次扫描耗时严重。 +复制算法 +复制算法把内存按容量划分为大小相等的两块,每次只使用其中的一块。如果正在用的这块没有足够的可使用空间了,那么就将还活着的对象复制到另一块去,再把使用过的内存一次性清掉。 + +这样就实现了简单高效的做法,每一次进行内存回收时,就不用再去考虑内存碎片这些复杂的情况,只需要移动堆顶指针就可以。但是缺点也很明显,可使用内存只有原来的一半了,而且持续复制生命力很旺盛的对象也会让效率降低哇。复制算法适用于存活对象少、垃圾对象多的情况,这种情况在新生代比较常见。 +标记-压缩算法 +在老年代,大部分对象都是存活的对象,复制算法在这里就不靠谱了,所以有人提出了标记压缩算法,标记过程和标记清除算法一样,但是清理时不是简单的清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存,需要移动对象的成本。 + +分代算法 +前面的几种垃圾回收算法中,没有一种可以完全替代其他算法,他们具备各自的特点与优势,因此更好的方法是根据垃圾对象的特性来选择合适的回收算法。 +分代算法的思想就是将内存空间根据对象的特点不同进行划分,选择合适的垃圾回收算法来提高回收效率。分代的思想已经被现有的虚拟机广泛采用。 +分区算法 +分区算法就是将整个堆空间再划分为连续的不同小区间,每一个小区间独立使用,也独立回收。 + +一般在相同条件下,堆空间越大,那么一次GC的时间就越长,因此而产生的停顿时间也就越长。为了控制GC的停顿时间,根据目标停顿时间,每次合理回收若干个小区间,而不是整个堆空间,进而减少一个GC的停顿时间。 +垃圾收集器 +上面讲的是垃圾收集算法,讲的是理论,垃圾收集器就是这些理论的具体实现。下面介绍一些垃圾收集器 +Serial收集器 +串行收集器是高效率的、古老的收集器,它只用了一个线程去回收垃圾。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩算法。串行是指 GC 过程和用户过程是串行的,在垃圾收集过程中会 stop the world,JVM在后台自动发起垃圾回收,会在用户不可见的情况下,把用户的线程全部停掉,就是 GC 停顿,给用户带来不良体验。 +红色代表 GC 线程,灰色代表用户线程,下同。 + +ParNew收集器 +ParNew 收集器就是 Serial 收集器的多线程版本,除了多线程以外,其余行为都和 Serial 收集器一样。新生代并行收集,老年代串行收集;新生代使用复制算法、老年代使用标记-压缩算法。 + +Parallel Scavenge收集器 +Parallel Scavenge 收集器类似于 ParNew 收集器,因为与吞吐量密切,也称为吞吐量收集器。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制 GC 的时间不大于多少毫秒或者比例。Parallel Scavenge 收集器以高吞吐量为目标,减少垃圾收集时间,让用户代码获得更长的运行时间;GC 停顿时间的缩短,是用牺牲吞吐量和新生代空间来换取的。 +Parallel Old收集器 +Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,使用多线程和「标记-压缩」算法,在 JDK1.6 版本才开始提供。 +CMS收集器 +CMS(Concorrect mask sweep)收集器是一种以获取最短停顿时间为目标的收集器;也称为并发低停顿收集器。常用在 WEB、B/S 架构的服务系统中,因为这类应用很注重响应速度,尽可能减少系统停顿时间,给用户带来较好的体验。从名字上就可以看出来,它是基于「标记-清除」算法实现的,整个过程分为 4 步: +初始标记 +初始标记仅仅标记 GC Roots 能直接关联到的对象,所以速度很快,需要停止服务(Stop The World)。 +并发标记 +并发标记是进行 GC Roots Tracing 的过程,为了标记上一步集合中的存活对象,因为用户程序这段时间也在运行,所以并不能保证可以标记出所有的存活对象。 +重新标记 +重新标记阶段是为了修正并发标记阶段因用户程序继续运作而导致标记变动的那一部分对象,采用多线程并行来提升效率,会停止服务,时间上远比并发标记短,较初始标记稍长。 +并发清除 +这个阶段即并发收集垃圾对象,可以与用户线程一起工作。 +虽然 CMS 收集器线程可以和用户线程一起进行,但是它肯定会占用 CPU 资源,拖慢应用程序是肯定的,总的吞吐量会降低。 + +G1收集器 +(看下垃圾回收算法中的分区算法)这是目前最新的前沿成果,它基于“标记-压缩”算法,可以进行空间整理,不会产生碎片。前面的垃圾收集器,收集范围都是整个新生代或老年代,但是 G1 收集器不是这样,使用 G1 收集器时,java堆的内存模型不同,它还保留有新生代和老年代的概念,它们都是一部分区域(可以不连续)的集合。除此之外,G1 收集器还能建立可预测的停顿时间模型,可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒。G1 跟踪各个区域(Region)获得其收集价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。G1 垃圾回收也分4步: +初始标记 +仅标记 GC Roots 能直接关联到的对象。 +并发标记 +进行 GC Roots Tracing 的过程,并不能保证可以标记出所有的存活对象。这个阶段若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收。 +最终标记 +为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录,G1 中采用了比 CMS 更快的初始快照算法: snapshot-at-the-beginning (SATB)。 +筛选回收 +首先排序各个 Region 的回收价值和成本,然后根据用户期望的 GC 停顿时间来制定回收计划,最后按计划回收一些价值高的 Region 中垃圾对象,回收时采用&quot;复制&quot;算法,从一个或多个 Region 复制存活对象到堆上的另一个空的 Region,并且在此过程中压缩和释放内存。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何加快 Nginx 的文件传输?——Linux 中的零拷贝技术 + +

+ +
+ + + + +
+ +
+ +参考内容: +Two new system calls: splice() and sync_file_range() +Linux 中的零拷贝技术1 +Linux 中的零拷贝技术2 +Zero Copy I: User-Mode Perspective +Linux man-pages splice() +Nginx AIO 机制与 sendfile 机制 +sendfile 适用场景 +扯淡 Nginx 的 sendfile 零拷贝的概念 +浅析 Linux 中的零拷贝技术 +Linux man-pages sendfile + +今天在看 Nginx 配置的时候,看到了一个sendfile配置项,它可以配置在http、server、location三个块中,出于好奇就去查了一下sendfile的作用。 +文件下载是服务器的基本功能,其基本流程就是循环的从磁盘读取文件内容到缓冲区,再将缓冲区内容发送到socket文件,程序员基本都会写出类似下面看起来比较高效的程序。 +while((n = read(diskfd, buf, BUF_SIZE)) &gt; 0) + write(sockfd, buf , n); + +上面程序中我们使用了read和write两个系统调用,看起来也已经没有什么优化空间了。这里的read和write屏蔽了系统内部的操作,我们并不知道操作系统做了什么,现实情况却是由于 Linux 的 I/O 操作默认是缓冲 I/O,上面的程序发生了多次不必要的数据拷贝与上下文切换。 +上述两行代码执行流程大致可以描述如下: + +系统调用read产生一个上下文切换,从用户态切换到内核态; +DMA 执行拷贝(现在都是 DMA 了吧!),把文件数据拷贝到内核缓冲区; +文件数据从内核缓冲区拷贝到用户缓冲区; +read调用返回,从内核态切换为用户态; +系统调用write产生一个上下文切换,从用户态切换到内核态; +把步骤 3 读到的数据从用户缓冲区拷贝到 Socket 缓冲区; +系统调用write返回,从内核态切换到用户态; +DMA 从 Socket 缓冲区把数据拷贝到协议栈。 + + +可以看到两行程序共发生了 4 次拷贝和 4 次上下文切换,其中 DMA 进行的数据拷贝不需要 CPU 访问数据,所以整个过程需要 CPU 访问两次数据。很明显中间有些拷贝和上下文切换是不需要的,sendfile就是来解决这个问题的,它是从 2.1 版本内核开始引入的,这里放个 2.6 版本的源码。 +系统调用sendfile是将in_fd的内容发送到out_fd,描述符out_fd在 Linux 2.6.33 之前,必须指向套接字文件,自 2.6.33 开始,out_fd可以是任何文件;in_fd只能是支持mmap的文件(mmap是一种内存映射方法,在被调用进程的虚拟地址空间中创建一个新的指定文件的映射)。 +所以当 Nginx 是一个静态服务器时,开启sendfile配置项是可以大大提高 Nginx 性能的,但是当把 Nginx 作为一个反向代理服务器时,sendfile则没有什么用,因为当 Nginx 时反向代理服务器时,in_fd就是一个套接字,这不符合sendfile的参数要求。 + +可以看到现在我们只需要一次拷贝就可以完成功能了,但是能否把这一次拷贝也省略掉呢?我们可以借助硬件来实现,仅仅需要把缓冲区描述符和文件长度传过去,这样 DMA 直接将缓冲区的数据打包发送到网络中就可以了。 +这样就实现了零拷贝技术,需要注意的是这里所说的零拷贝是相对操作系统而言的,即在内核空间不存在冗余数据。数据的实际走向是从硬盘到内存,再从内存到设备。 +Nginx 中还有一个aio配置,它的作用是启用内核级别的异步 I/O 功能,要使aio生效需要将directio开启(directio对大文件的读取速度有优化作用),aio很适合大文件的传送。需要注意的是sendfile和aio是互斥的,不可同时兼得二者,因此我们可以设置一个文件大小限制,超过该阀值使用aio,低于该阀值使用sendfile。 +location /video/ { + sendfile on; + sendfile_max_chunk 256k; + aio threads; + directio 512k; + output_buffers 1 128k; +} + +上面已经提到了零拷贝技术,它可以有效的改善数据传输的性能,但是由于存储体系结构非常复杂,而且网络协议栈有时需要对数据进行必要的处理,所以零拷贝技术有可能会产生很多负面影响,甚至会导致零拷贝技术自身的优点完全丧失。 +零拷贝就是一种避免 CPU 将一块存储拷贝到另一块存储的技术。它可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效的提高数据传输效率,而且零拷贝技术也减少了内核态与用户态之间切换所带来的开销。进行大量的数据拷贝操作是一件简单的任务,从操作系统的角度来看,如果 CPU 一直被占用着去执行这项简单的任务,是极其浪费资源的。如果是高速网络环境下,很可能就出现这样的场景。 +零拷贝技术分类 +现在的零拷贝技术种类很多,也并没有一个适合于所有场景的零拷贝零拷贝技术,概括起来总共有下面几种: + + +直接 I/O:对于这种数据传输方式来说,应用程序可以直接访问硬件存储,操作系统只是辅助数据传输,这类零拷贝技术可以让数据在应用程序空间和磁盘之间直接传输,不需要操作系统提供的页缓存支持。关于直接 I/O 可以参看Linux 中直接 I/O 机制的介绍。 + + +避免数据在内核态与用户态之间传输:在一些场景中,应用程序在数据进行传输的过程中不需要对数据进行访问,那么将数据从页缓存拷贝到用户进程的缓冲区是完全没有必要的,Linux 中提供的类似系统调用主要有mmap()、sendfile()和splice()。 + + +对数据在页缓存和用户进程之间的传输进行优化:这类零拷贝技术侧重于灵活地处理数据在用户进程的缓冲区和操作系统页缓存之间的拷贝操作,此类方法延续了传统的通信方式,但是更加灵活。在 Linux 中主要利用了「写时复制」技术。 + + +前两类方法的目的主要是为了避免在用户态和内核态的缓冲区间拷贝数据,第三类方法则是对数据传输本身进行优化。我们知道硬件和软件之间可以通过 DMA 来解放 CPU,但是在用户空间和内核空间并没有这种工具,所以此类方法主要是改善数据在用户地址空间和操作系统内核地址空间之间传递的效率。 +避免在内核与用户空间拷贝 +Linux 主要提供了mmap()、sendfile()、splice()三个系统调用来避免数据在内核空间与用户空间进行不必要的拷贝,在Nginx 文件操作优化对sendfile()已经做了比较详细的介绍了,这里就不再赘述了,下面主要介绍mmap()和splice()。 +mmap() +当调用mmap()之后,数据会先通过 DMA 拷贝到操作系统的缓冲区,然后应用程序和操作系统共享这个缓冲区,这样用户空间与内核空间就不需要任何数据拷贝了,当大量数据需要传输的时候,这样做就会有一个比较好的效率。 +但是这种改进是需要代价的,当对文件进行了内存映射,然后调用write()系统调用,如果此时其它进程截断了这个文件,那么write()系统调用将会被总线错误信号SIGBUG中断,因为此时正在存储的是一个错误的存储访问,这个信号将会导致进程被杀死。 +一般可以通过文件租借锁来解决这个问题,我们可以通过内核给文件加读或者写的租借锁,当另外一个进程尝试对用户正在进行传输的文件进行截断时,内核会给用户发一个实时RT_SIGNAL_LEASE信号,这个信号会告诉用户内核破坏了用户加在那个文件上的写或者读租借锁,write()系统调用就会被中断,并且进程会被SIGBUS信号杀死。需要注意的是文件租借锁需要在对文件进行内存映射之前设置。 +splice() +和sendfile()类似,splice()也需要两个已经打开的文件描述符,并且其中的一个描述符必须是表示管道设备的描述符,它可以在操作系统地址空间中整块地移动数据,从而减少大多数数据拷贝操作。适用于可以确定数据传输路径的用户应用程序,不需要利用用户地址空间的缓冲区进行显示的数据传输操作。 +splice()不局限于sendfile()的功能,也就是说sendfile()是splice()的一个子集,在 Linux 2.6.23 中,sendfile()这种机制的实现已经没有了,但是这个 API 以及相应的功能还存在,只不过内部已经使用了splice()这种机制来实现了。 +写时复制 +在某些情况下,Linux 操作系统内核中的页缓存可能会被多个应用程序所共享,操作系统有可能会将用户应用程序地址空间缓冲区中的页面映射到操作系统内核地址空间中去。如果某个应用程序想要对这共享的数据调用write()系统调用,那么它就可能破坏内核缓冲区中的共享数据,传统的write()系统调用并没有提供任何显示的加锁操作,Linux 中引入了写时复制这样一种技术用来保护数据。 +写时复制的基本思想是如果有多个应用程序需要同时访问同一块数据,那么可以为这些应用程序分配指向这块数据的指针,在每一个应用程序看来,它们都拥有这块数据的一份数据拷贝,当其中一个应用程序需要对自己的这份数据拷贝进行修改的时候,就需要将数据真正地拷贝到该应用程序的地址空间中去,也就是说,该应用程序拥有了一份真正的私有数据拷贝,这样做是为了避免该应用程序对这块数据做的更改被其他应用程序看到。这个过程对于应用程序来说是透明的,如果应用程序永远不会对所访问的这块数据进行任何更改,那么就永远不需要将数据拷贝到应用程序自己的地址空间中去。这也是写时复制的最主要的优点。 +写时复制的实现需要 MMU 的支持,MMU 需要知晓进程地址空间中哪些特殊的页面是只读的,当需要往这些页面中写数据的时候,MMU 就会发出一个异常给操作系统内核,操作系统内核就会分配新的物理存储空间,即将被写入数据的页面需要与新的物理存储位置相对应。它最大好处就是可以节约内存,不过对于操作系统内核来说,写时复制增加了其处理过程的复杂性。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 深入理解计算机系统——CPU 是怎样工作的? + +

+ +
+ + + + +
+ +
+ +参考内容: +处理器是如何工作的 +《编码:隐匿在计算机软硬件背后的语言》——[美] Charles Petzold + +CPU 大家应该都不会陌生,日常用的手机、电脑中都有 CPU,CPU 作为一个设备的大脑,指挥着其它各种硬件的协同工作,芯片技术也是国内一直没有突破的技术。 +我们先来看看怎么让电路去运算呢?比如如何让电路运算1 + 1,直接使用下面这个装置就可以了。 + +作为一个比较好奇的人,总会想看看那个方框框里面是什么样子的,让我们慢慢解开加法器的外衣。 + +这个电路你应该不会陌生,它需要两个开关都闭合时灯泡才会发光,也就是说它有两个输入,开关闭合时我们认为输入为 1,未闭合状态视为 0;而灯泡是否发光就是我们的输出,发光为 1,否则为 0。于是就有了下面这张表。 + + + +and +0 +1 + + + + +0 +0 +0 + + +1 +0 +1 + + + +这样的电路我们就把它称之为与(and)门,它接受两个逻辑输入,并会给出我们一个逻辑输出,与它相似的电路还有逻辑或(or)、**异或(xor)**等等,因为太多了,就不一一介绍了,如果感兴趣可以 Google 一下。 +为了方便我们把上面的电路做一个简化,抽象成下面这个样子,其它的电路也可以通过 Google 找到它们的样子。 + +现在直接给出一个可以运算加法的电路,它的样子长下面这样。 + +我们也可以给它列一个表,显得更清晰,表中之所以有两位是因为加法有可能会产生进位,而我们使用的是二进制表示,所以10表示的是十进制中的2。 + + + ++ +0 +1 + + + + +0 +00 +01 + + +1 +01 +10 + + + +有加法就很好办了,减法实际上是加一个负数,除法可以改写成乘法,乘法又可以改写成加法,现在加法一统天下了。 +好了,上面说了那么多,还贴了那么多图,只是为了让你相信电路是可以实现我们数学上的运算的,下面就没有那么声情并茂了。 +我们可以把一个运算抽象为下面这个模型。 +输入数据 --&gt; 运算 --&gt; 输出数据 + +计算机中把各种像上述加法器一样的运算器放在了同一个小盒子里面,组成成一个运算器集合,我们给它取个名字叫算术逻辑单元(ALU:Arithmetic Logical Unit),它是 CPU 的核心组成部分。 +当然我们不可能只进行加法这种简单运算,一个很长很长的算式需要经过多步运算,小学我们就学过梯等式,它实际上有一大推输入,中间那么多梯子,都是临时步骤,临时步骤、数据一类的东西都需要有个东西给它存起来,所以在 CPU 就拿出一个小盒子当做存储器。 +# 梯等式 +485 - ( 6 × 4 + 32 ) += 485 - ( 24 + 32 ) += 485 - 56 += 429 + +现在我们有了存储器和运算器两个干事的人,但是没有一个统筹兼顾的领导者,控制器就充当了这个角色,它需要把控制储存器中的数据发送到 ALU 去进行运算,然后再将运算的结果取出来存到储存器中。总的来说,控制器的工作就是完成协调和指挥整个计算机系统的操作。 +我们把上面的结构画出来,图中的虚线表示指令流,实线表示数据流,这个结构就是著名的冯 · 诺依曼体系结构,遵循这种结构的计算机都叫做冯诺依曼机,现在所有的机器都是冯诺依曼机。 + +请注意,我们现在实际上只有硬件,没有软件是什么事情都干不了了,我们这里所说的软件是一堆指令序列,比如加法指令、传送指令等等组成的序列,也就是我们常说的汇编语言。 +但是在早期并不是这样的,这台机器上编写的指令序列是无法运行在另一家公司生产的机器上的,即使是同一个公司的机器,如果不是同一代,那也不能运行,所以早期的编程是直接面向硬件的。 +这时就有人站出来研究如何实现一次编写多处运行了,IBM 首次在它的 360 系统上引入了ISA的概念,即指令集体系结构。 +指令集体系结构将编程所要了解的硬件信息从硬件系统中抽象了出来,这样开发人员就可以直接面向处理器的 ISA 进行编程了。 +为什么手机上的软件不能运行在电脑中呢?就是因为个人电脑所使用的 Intel 和 AMD 处理器都是基于 x86 指令集的,而手机大多数都使用的是基于 ARM 指令集的处理器。 +现在处理器被分为指令集体系结构、处理器微架构、处理器物理实现三个层次。体系结构相当于需求,微架构好比设计,物理实现则是具体的实现过程。 +比如我们规定指令集中必须有加法指令,这个指令你怎么设计、如何实现是你给的事,我只管给出两个加数你能给我输出一个正确的结果,简单来说就是抽象封装。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 为什么计算机处理排序数组比未排序数组快? + +

+ +
+ + + + +
+ +
+ 今天在群里看到一个有意思的问题——为什么处理排序数组比处理没有排序的数组要快,这个问题来源于 StackoverFlow,虽然我看到代码略微知道原因,但是模模糊糊不够清晰,搜了很多博客也讲的不够明白,所以就自己来总结了。 +首先来看一下问题,下面是很简单的一段代码,随机生成一些数字,对其中大于 128 的元素求和,记录并打印求和所用时间。 +import java.util.Arrays; +import java.util.Random; + +public class Main +{ + public static void main(String[] args) + { + // Generate data + int arraySize = 32768; + int data[] = new int[arraySize]; + + Random rnd = new Random(0); + for (int c = 0; c &lt; arraySize; ++c) + data[c] = rnd.nextInt() % 256; + + // !!! With this, the next loop runs faster + Arrays.sort(data); + + // Test + long start = System.nanoTime(); + long sum = 0; + + for (int i = 0; i &lt; 100000; ++i) + { + // Primary loop + for (int c = 0; c &lt; arraySize; ++c) + { + if (data[c] &gt;= 128) + sum += data[c]; + } + } + + System.out.println((System.nanoTime() - start) / 1000000000.0); + System.out.println(&quot;sum = &quot; + sum); + } +} + +我的运行结果:分别在对数组排序和不排序的前提下测试,在不排序时所用的时间比先排好序所用时间平均要多 10 ms。这不是巧合,而是必然的结果。 +问题就出在那个if判断上面,在旧文顺序、条件、循环语句的底层解释中其实已经提到了造成这种结果的原因,只是旧文中没有拿出具体的例子来说明。 +为了把这个问题搞明白,需要先对流水线有一定的了解。计算机是指令流驱动的,执行的是一个一个的指令,而执行一条指令,又要经过取指、译码、执行、访存、写回、更新六个阶段(不同的划分方式所包含的阶段不一样)。 +六个阶段使用的硬件基本是不一样的,如果一条指令执行完再去执行另一条指令,那么在这段时间里会有很多硬件处于空闲状态,要使计算机的速度变快,那么就不能让硬件停下来,所以有了流水线技术。 +流水线技术通过将指令重叠来实现几条指令并行处理,下图表示的是三阶段指令时序,即把一个指令分为三个阶段。在第一条指令的 B 阶段,A 阶段相关的硬件是空闲的,于是可以将第二条指令的 A 阶段提前操作。 + +很明显,这种设计大幅提高了指令运行的效率,聪明的你可能发现问题了,要是不知道下一条指令是什么怎么办,那提前的阶段也就白干了,那样流水线不就失效了?没错,这就是导致开篇问题的原因。 +让流水线出问题的情况有三种: + +数据相关,后一条指令需要用到前一条指令的运算结果; +控制相关,比如无条件跳转,跳转的地址需要在译码阶段才能知道,所以跳转之后已经被取出的指令流水就需要清空; +结构相关,由于一些指令需要的时钟周期长(比如浮点运算等),长时间占用硬件,导致之后的指令无法进入译码等阶段,即它们在争用同一套硬件。 + +代码中的if (data[c] &gt;= 128)翻译成机器语言就是跳转指令,处理器事先并不知道要跳转到哪个分支,那难道就等知道了才开始下一条指令的取指工作吗?处理器选择了假装知道会跳转到哪个分支(不是谦虚,是真的假装知道),如果猜中了是运气好,而没有猜中那就浪费一点时间重新来干。 +没有排序的数组,元素是随机排列的,每次data[c] &gt;= 128的结果也是随机的,前面的经验就不可参考,所以下一次执行到这里理论上还是会有 50% 的可能会猜错,猜错了肯定就需要花时间来修改犯下的错误,自然就会浪费更多的时间。 +对于排好序的数组,开始几次也需要靠猜,但是猜着猜着发现有规律啊,每次都是往同一个分支跳转,所以以后基本上每次都能猜中,当遍历到与 128 分界的地方,才会出现猜不中的情况,但是猜几次之后,发现这又有规律啊,每次都是朝着另外一个相同分支走的。 +虽然都会猜错,但是在排好序的情况下猜错的几率远远小于未排序时的几率,最终呈现的结果就是处理排序数组比未排序数组快,其原因就是流水线发生了大量的控制相关现象,下面通俗一点,加深一下理解。 + +远在他方心仪多年的姑娘突然告诉你,其实她也喜欢你,激动的你三天三夜睡不着觉,决定开车前往她的城市,要和她待在一起,但是要去的路上有很多很多岔路,你只能使用的某某地图导航,作为老司机并且怀着立马要见到爱人心情的你,开车超快,什么样罚单都不在乎了。 +地图定位已经跟不上你的速度了,为了尽快到达,遇到岔路你都是随机选一条路前进,遗憾的是,自己的选择不一定对(我们假设高速可以回退),走错路了就要重新回到分岔点,这就对应着未排序的情况。 +现在岔路是有规律的,告诉你开始一直朝着一边走,到某个地点后会一直朝着另一边走,你只需要花点时间去探索一下开始朝左边还是右边,到了中间哪个地点会改变方向就可以了,相比之下就能节省不少时间了,尽快见到自己的爱人,这对应着排好序的情况。 + +最后的故事改编自两个人的现实生活,一位是自己最好的朋友之一,谈恋爱开心的睡不着觉;另一位是微信上的一位好友,为了对方从北京裸辞飞到了深圳。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 顺序、条件、循环语句的底层解释(机器语言级解释) + +

+ +
+ + + + +
+ +
+ 初级程序员讲究的是术,只知道如何用关键字去拼凑出领导想要的功能;高级程序员脸的是道,理解了底层的逻辑,不仅把功能做的漂漂亮亮,心法也更上一层楼。 +顺序结构 +数据传送指令 +我们都清楚,绝大多数编译器都把汇编语言作为中间语言,把汇编语言程序变成可运行的二进制文件早就解决了,所以现在的高级语言基本上只需要把自己翻译成汇编语言就可以了。 +汇编指令总共只有那么多,大多数指令都是对数据进行操作,比如常见的数据传送指令mov。不难理解,被操作数据无非有三种形式,立即数,即用来表示常数值;寄存器,此时的数据即存放在指定寄存器中的内容;内存引用,它会根据计算出来的地址访问某个内存位置。 +需要注意的是,到了汇编层级,就不像高级语言那样随随便便int就能和long类型的数据相加减,他们在底层所占有的字节是不一样的,汇编指令是区分操作数据大小的,比如数据传送指令,就有下面这些品种(x86-64 对数据传送指令加了一条限制:两个操作数不能都指向内存位置)。 + +压栈与弹栈 +对于栈,我想不必多讲,IT 行业的同学都清楚,它是一种线性数据结构,其中的数据遵循“先进后出”原则,寄存器%rsp保存着栈顶元素的地址,即栈顶指针。一个程序要运行起来,离不开栈这种数据结构。 +栈使用最多的就是弹栈popq和压栈pushq操作。比如将一个四字值压入栈中,栈顶指针首先要减 8(栈向下增长),然后将值写到新的栈顶地址;而弹栈则需要先将栈顶数据读出,然后再将栈指针加 8。所以pushq和popq指令就可以表示为下面的形式。 +// 压栈 +subq $8, %rsp +movq %rbp, (%rsp) + +// 弹栈 +movq (%rsp), %rax +addq $8, %rsp + +其他还有算术、逻辑、加载有效地址、移位等等指令,可以查阅相关文档了解,不作过多介绍,汇编看起来确实枯燥乏味。 +条件结构 +前面讲的都是顺序结构,我们的程序中不可能只有顺序结构,条件结构是必不可缺的元素,那么汇编又是如何实现条件结构的呢? +首先你需要知道,除了整数寄存器,CPU 还维护着一组条件码寄存器,我们主要是了解如何把高级语言的条件结构转换为汇编语言,不去关注这些条件码寄存器,只需要知道汇编可以通过检测这些寄存器来执行条件分支指令。 +if-else 语句 +下面是 C 语言中的if-else语句的通用形式。 +if(test-expr){ + then-statement +}else{ + else-statement +} + +汇编语言通常会将上面的 C 语言模板转换为下面的控制流形式,只要使用条件跳转和无条件跳转,这种形式的控制流就可以和汇编代码一一对应,我们以 C 语言形式给出。 + t = test-expr; + if(!t){ + goto false; + } + then-statement; + goto done; +false: + else-statement; +done: + +但是这种条件控制转移形式的代码在现代处理器上可能会很低效。原因是它无法事先确定要跳转到哪个分支,我们的处理器通过流水线来获得高性能,流水线的要求就是事先明确要执行的指令顺序,而这种形式的代码只有当条件分支求值完成后,才能决定走哪一个分支。即使处理器采用了非常精密的分支预测逻辑,但是还是有错误预测的情况,一旦预测错误,那将会浪费 15 ~ 30 个时钟周期,导致性能下降。 + +在流水线中,把一条指令分为多个阶段,每个阶段只执行所需操作的一小部分,比如取指令、确定指令类型、读数据、运算、写数据以及更新程序计数器。流水线通过重叠连续指令的步骤来获得高性能,比如在取一条指令的同时,执行它前面指令的算术运算。所以如果事先不知道指令执行顺序,那么事先所做的预备工作就白干了。 + +为了提高性能,可以改写成使用条件数据传送的代码,比如下面的例子。 +v = test-expr ? then-expr : else-expr; + +// 使用条件数据传送方法 +v = then-expr; +ve = else-expr; +t = test-expr; +if(!t){ + v = ve; +} + +这样改写,就能提高程序的性能了,但是并不是所有的条件表达式都可以使用条件传送来编译,一般只有当两个表达式都很容易计算时,编译器才会采用条件数据传送的方式,大部分都还是使用条件控制转移方式编译。 +switch 语句 +switch语句可以根据一个整数索引值进行多重分支,在处理具有多种可能结果的测试时,这种语句特别有用。为了让switch的实现更加高效,使用了一种叫做跳转表的数据结构(Radis 也是用的跳表)。跳转表是一个数组,表项 i 是一个代码段的地址,当开关情况数量比较多的时候,就会使用跳转表。 +我们举个例子,还是采用 C 语言的形式表是控制流,要理解的是执行switch语句的关键步骤就是通过跳转表来访问代码的位置。 +void switch_eg(long x, long n, long *dest){ + long val = x; + switch(n){ + case 100: + val *= 13; + break; + case 102: + val += 10; + case 103: + val += 11; + break; + case 104: + case 105: + val *= val; + break; + default: + val = 0; + } + *dest = val; +} + +要注意的是,上面的代码中有的分支没有break,这种问题在笔试中会经常遇到,没有break会继续执行下面的语句,即变成了顺序执行。上面的代码会被翻译为下面这种控制流。 +void switch_eg(long x, long n, long *dest){ + static void *jt[7] = { + &amp;&amp;loc_A, &amp;&amp;loc_def, &amp;&amp;loc_B, + &amp;&amp;loc_C, &amp;&amp;loc_D, &amp;&amp;loc_def, + &amp;&amp;loc_D + }; + unsigned long index = n - 100; + long val; + if(index &gt; 6){ + goto loc_def; + } + goto *jt[index]; + loc_A: + val = x * 13; + goto done; + loc_B: + x = x + 10; + loc_C: + val = x + 11; + goto done; + loc_D: + val = x * x; + goto done; + loc_def: + val = 0; + done: + *dest = val; +} + +循环结构 +C 语言中有do-while、while和for三种循环结构,它们的通用形式一般都长下面那样。 +// do-while +do + body-statement + while(test-expr); + +// while +while(test-expr) + body-statement + +// for +for(init-expr; test-expr; update-expr) + body-statement + +do-while的特点是body-statement一定会执行一次,所以我们可以将do-while翻译成下面的控制流形式,很容易就能联想到它的汇编形式。 +loop: + body-statement; + t = test-expr; + if(t){ + goto loop; + } + +while循环我们给出两种形式的控制流,其中一种包含do-while形式,如下所示。 +// 第一种形式 +t = test-expr; +if(!t){ + goto done; +} +do + body-statement; + while(test-expr); +done: + + +// 第二种形式 + goto test; +loop: + body-statement; +test: + t = test-expr; + if(t){ + goto loop; + } + +面试的时候,有的面试官会问你for循环的执行顺序,现在深入理解了三种循环的机制,再也不怕面试官啦。for循环可以转换成如下的while形式。 +init-expr; +while(test-expr){ + body-statement; + update-expr; +} + +有了这种形式的for循环,我们只需要将其中的while部分再翻译一下就好了,前文给出了两种while翻译的方式,而具体采用哪种方式,取决于编译器优化的等级。 +总结 +计算机就是用那么几条简简单单的指令就完成了各种复杂的操作,不得不折服于计算机科学家们的魅力。现在人工智能被炒的很火热,然后人是事件、情感驱动的,而计算机是控制流驱动的,所以从架构上就决定了,冯诺依曼体系计算机实现的都是弱人工智能。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 深入理解计算机系统——函数调用与空间分配 + +

+ +
+ + + + +
+ +
+ 我们在编程序的时候,都会把某一个特定功能封装在一个函数里面,对外暴露一个接口,而隐藏了函数行为的具体实现,一个大型的复杂系统里面包含了很多这样的小函数,我们称之为过程。 +过程是相对独立的小模块,系统的运行需要这些过程的紧密合作,这种合作就是函数调用。 +在一个函数执行时调用别的函数,比如 P 调用 Q,需要执行一些特定的动作。传递控制,在调用 Q 之前,控制权在 P 的手里,既然要调用 Q,那么就需要把控制权交给 Q;传递数据,就是函数传参;分配与释放内存,在开始时,Q 可能需要位局部变量分配空间,结束时又必须释放这些存储空间。 +大多数语言都使用栈提供的先进后出机制来管理内存,x86-64 可以通过通用寄存器传递最多 6 个整数值(整数或地址),如果超过 6 个,那就需要在栈中分配内存,并且通过栈传递参数时,所有数据的大小都要向 8 的倍数对齐。将控制权从 P 转交给 Q,只需要将 PC(程序计数器)的值置为 Q 代码的起始位置,并记录好 P 执行的位置,方便 Q 执行完了,继续执行 P 剩余的代码。 +在函数的传参、执行中,多多少少都需要空间来保存变量,局部数据能保存在寄存器中就会保存在寄存器中,如果寄存器不够,将会保存在内存中。除了寄存器不够用的情况,还有数组、结构体和地址等局部变量都必须保存在内存中。分配内存很简单,只需要减小栈指针的值就行了,同样释放也只需要增加栈指针。 +在函数执行过程中,处理栈指针%rsp,其它寄存器都被分类为被调用者保存寄存器,即当过程 P 调用过程 Q 时,Q 必须保存这些寄存器的值,保证它们的值在 Q 返回到 P 时与 Q 被调用时是一样的。 +所以递归也就不难理解了,初学算法总觉得递归有点奇妙,怎么自己调用自己,而实际上对于计算机来说,它和调用其它函数没什么区别,在计算机眼里,没有自身与其它函数的区别,所有被调用者都是其它人。 +数组是编程中不可或缺的一种结构,“数组是分配在连续的内存中”这句话已经烂熟于心了,历史上,C 语言只支持大小在编译时就能确定的多维数组,这个多多少少有一些不便利,所以在ISO C99标准中就引入了新的功能,允许数组的维度是表达式。 +int A[expr1][expr2] + +因为数组是连续的内存,所以很容易就能访问到指定位置的元素,它通过首地址加上偏移量即可计算出对应元素的地址,这个偏移量一定意义上就是由索引给出。 +比如现在有一个数组A,那么A[i]就等同于表达式* (A + i),这是一个指针运算。C 语言的一大特性就是指针,既是优点也是难点,单操作符&amp;和*可以产生指针和简介引用指针,也就是,对于一个表示某个对象的表达式expr,&amp;expr给出该对象地址的一个指针,而对于一个表示地址的表达式Aexpr,*Aexpr给出该地址的值。 +即使我们创建嵌套(多维)数组,上面的一般原则也是成立的,比如下面的例子。 +int A[5][3]; + +// 上面声明等价于下面 +typedef int row3_t[3]; +row3_t A[5]; + +这个数组在内存的中就是下面那个样子的。 + +还有一个重要的概念叫做数据对齐,即很多计算机系统要求某种类型的对象的地址必须是某个值 K(一般是2、4 或 8)的倍数,这种限制简化了处理器和内存接口之间的设计,甚至有的系统没有进行数据对齐,程序就无法正常运行。 +比如现在有一个如下的结构体。 +struct S1 { + int i; + char c; + int j; +} + +如果编译器用最小的 9 字节分配,那么将是下面的这个样子。 + +但是上面这种结构无法满足 i 和 j 的 4 字节对齐要求,所以编译器会在 c 和 j 之间插入 3 个字节的间隙。 + +在极客时间专栏中有这样一段代码。 +int main(int argc, char *argv[]){ + int i = 0; + int arr[3] = {0}; + for(; i &lt;= 3; i++){ + arr[i] = 0; + printf(&quot;Hello world!\n&quot;); + } + return 0; +} + +这段代码神奇的是在某种情况下会一直循环的输出Hello world,并不会结束,在计算机系统漫游(补充)中也提到过。 +造成上面这种结果是因为函数体内的局部变量存在栈中,并且是连续压栈,而 Linux 中栈又是从高向低增长。数组arr中是 3 个元素,加上 i 是 4 个元素,刚好满足 8 字节对齐(编译器 64 位系统下默认会 8 字节对齐),变量i在数组arr之前,即i的地址与arr相邻且比它大。 +代码中很明显访问数组时越界了,当i为 3 时,实际上正好访问到变量i的地址,而循环体中又有一句arr[i] = 0;,即又把i的值设置为了 0,由此就导致了死循环。 + +
+ + Read More ~ +
+
+
+ + + +
+ + + 下一页 + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/mZ_iBKmVWo/page/2/index.html b/mZ_iBKmVWo/page/2/index.html new file mode 100644 index 00000000..98c00ea2 --- /dev/null +++ b/mZ_iBKmVWo/page/2/index.html @@ -0,0 +1,612 @@ + + + + + + + + 操作系统 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 操作系统 +
+ + +
+

+ + 深入理解计算机系统——信息的表示与处理 + +

+ +
+ + + + +
+ +
+ +参考内容: +《深入理解计算机系统》(第三版) + +字数据大小 +前面已经提到过信息=位+上下文,但是基本上的计算机都没有将位作为最小的可寻址单位,而是将字节作为了最小的可寻址单位,内存就是一个非常大的字节数组,它的的每个字节都由一个唯一的数字来标识(这个数字是不需要存的),所有可能的地址集合就是虚拟地址空间。 +我们常说的 32 位、64 位指的是一台计算机的字长,用于指明指针数据的的标称大小。有的面试官在面试的时候会问这样一个问题:在 C/C++ 中指针的大小是多少?如果你一下就回答出来时多少个字节了,那基本上不必再问了,因为一个指针的大小取决于计算机的字长,所以应该分 32 位机还是 64 位机的情况。 +字长还会决定一个极为重要的系统参数——虚拟地址空间。比如现在有一个 32 位机,每一位可以取值 1 或 总共 32 位,能组合的出局就有 232 个,所以它能访问 232 个地址,其大小也就是 4G,因此你如果给 32 位机装上 8G 的内存条,是起不了多大作用的。 +我们平时所说的 32 位程序和 64 位程序并不是指机器的字长,它们的区别在于程序时如何编译的,而不是其运行的机器类型,高版本都应该做到向后兼容,所以 32 位程序一般都能运行在 64 位机器上,而 64 位程序时不能运行在 32 位机上面的。下面两种伪指令就分别用于编译 32 位程序和 64 位程序。 +gcc -m32 prog.c +gcc -m64 prog.c + +C 语言在 32 位机和 64 位机上所表现的差别在于long数据类型,一般在 32 位机上是 4 个字节,而在 64 位机上是 8 个字节,而作为程序员要力图程序能在不同的机器上进行编译执行,要做到这一点就需要保证程序对不同数据类型的确切大小不敏感。 + +曾经某运营商的一个基站版本因为数据范围的不同而造成了巨大的损失,在编程环境中使用的是 32 位机,而基站所使用的处理器没有 32 位,最后表现的效果就是大概每隔 40 天,基站就自动复位了。定位到这个问题都花费了巨大的财力和人力资源。 + +寻址及字节顺序 +上文已经提到,有很多的对象实际上不止占用一个字节,而是占用了多个字节,此时就涉及到如何排列这些字节了,以及如何存储这些字节。以11001100 11001100为例,它占用了两个字节,我们可以选择将这两个字节放在连续的内存中,也可以将两个字节分开放在不连续的内存中;另外我们可以将左边的字节当做起始位置,也可以将右边的字节当做起始位置(更专业的称为大端法和小端法)。 +对于字节的排列,到底是用大端法还是小端法,没有技术上的争论,只有社会政治论题的争论,而且机器它对程序员是完全不可见的。几乎所有的机器都将多字节对象存储为连续的字节序列,所使用字节中最小的地址作为对象的地址。 +那么什么时候需要注意字节的顺序规则呢,那就是编写网络应用程序的时候,试想你传输的数据是用大端法表示的,而用户的计算机采用的是小端法,那还会有用户使用你的产品吗。所以编写网络程序时需要遵循已经建立的关于字节顺序的规则。 +整数表示 +程序员对二进制不会不知道,比如 11111111表示的是 255(不考虑补码),很容易就能转换为我们所熟悉的 10 进制数据。这种方式我们默认它是无符号数,如果要加入有符号数就开始变得有趣了。 +几乎所有的计算机都是采用有补码来表示有符号整数的,它与无符号整数的区别在于最高位被解释为负权,举个例子:将1111看做补码的话,它的值就为:-23 + 22 + 21 + 20 = -1。 +在程序中不可避免的会使用强制类型转换,C 语言中强制类型转换并没有改变数据的位值,只是改变了解释这些位的方式。比如将无符号数(unsigned) 53191 转换为有符号数的结果为 -12345,它们的位值是完全没有相同的。 +最容易入坑的地方是,对两个不同类型的数据进行运算时,C 语言将会隐式的将有符号数转换为无符号数,所以就有下面这样一个神奇的结果。 +// u 代表无符号数 +-1 &lt; 0u +// 结果为 0 +// 因为 -1 的补码表示为:11...11 +// 转换为无符号数后就是范围内最大的数 + +如果需要扩展一个数的位表示,那么放心的扩展就好了,小的数据类型都能安全的向大的数据类型转换,补码表示的数会在前面补上符号位,原码表示的直接在前面补上 0 即可,而需要注意的是从大往小转,这会不可避免的截断位,造成信息的丢失,所以千万不要这么干。 +加法、乘法运算 +在编程入门的时候可能都知道两个正数相加的结果可能为负数,还有一个更奇怪的现象就是:x &lt; y和 x - y &lt; 0两个表达式可能会得出不一样的结果,这些神奇的结果都和计算机整数的底层表示和运算有着密切的关系。 +C 语言中有无符号数与有符号数之分,而在 Java 中只有有符号数,下面的内容还是基于 C 语言进行说明,毕竟更 C 比 Java 更接近底层嘛。 +无符号加法 +假设我们使用 w 位来表示无符号数,那么两个加数取值范围即为:0 ≤ x, y <2w,理论上它们的和的范围为:0 ≤ sum < 2w+1,因为只有 w 位表示无符号数(要把和表示出来就需要 w+1 位),所以超过 zw的部分就会造成溢出,如下图所示。 + +对于无符号数的溢出,计算机采用的处理方式是丢掉最高位,直观的结果就是,当发生溢出了,就将采用取模运算(或者说是减去 2w),举个例子。 +只用 4 为来表示无符号数,即 w = 4,现在有 x [1001] 和 y [1100] 相加,其结果应为:[10101] ,但是没有 5 位用来表示,所以丢掉最高位的1,剩下的值为 5 [0101],也就是 21 mod 16 = 5。 +那么如何检测是否发生溢出呢?设求和结果为 s,对于加法有 x + y ≥ x 恒成立,即只要没有发生溢出,肯定有 s ≥ x。另一方面,如果确实发生溢出了,就有 s = x + y - 2w,又有 y - 2w < 0,因此 s = x + y - 2w < x。 +补码加法 +和前面一样,对于两个给定范围的加数 - 2w-1 ≤ x, y ≤ 2w-1 - 1,它们的和的范围就在 - 2w ≤ sum ≤ 2w - 2。要想把整个和的范围表示出来,依旧需要 w+1 位才行,而现在只有 w 位,因此还是需要采用将溢出部分截断。 + +可以发现,当发生正溢出时,截断的结果是从和数中减去了 2w;而当发生负溢出时,截断结果是把和数加上 2w。 +那么对于补码加法如何检测溢出结果呢?通过分析可以发现,当且仅当 x > 0, y > 0,但和 s ≤ 0 时为正溢出;当且仅当 x < 0, y < 0,但 s ≥ 0 时发生负溢出。 +无符号乘法 +有了前面的基础,乘法就变得简单一些了,对于溢出情况,计算机仍然采用的是求模,比如 0 ≤ x, y ≤ 2w - 1,它们乘积的范围为 0 到 22w - 2w+1 + 1 之间,这可能需要 2w 位来表示,溢出部分直接截掉,如下所示。 + +补码乘法 +对于补码,两个乘数的范围为:- 2w-1 ≤ x, y ≤ 2w-1 + 1,那么其乘积表示范围就为 - 22w-2 + 2w-1 到 22w-2 之间,补码乘法和无符号乘法基本是一样的,只是在无符号基础上多加了一步转换,即将无符号数转换为补码。 + +乘以常数 +我们知道,计算机做加减法、位级运算的速度最快(1 个指令周期),而做乘除法很慢(10 个甚至更多指令周期),平时编写的程序中常常会乘以一个常数,为了使程序运行的更快,编译器可能会帮我们做一些处理。 +首先我们考虑常数是 2 的幂。x * 21 可以表示为 x &lt;&lt; 1,x * 22 可以表示为 x &lt;&lt; 2,依次类推即可。 +对于不是 2 的幂的常数,比如 x * 14 可以表示为:(x&lt;&lt;3) + (x&lt;&lt;2) + (x&lt;&lt;1),因为 14 = 23 + 22 + 21;聪明的你可能发现 14 还有另一种表示方法,即 14 = 24 - 21,这种表示比前一种表示方法又少了运算量,所以 x * 14 还可以表示为:(x&lt;&lt;4) - (x&lt;&lt;1)。 +实际上,这里有一个通用的解决方案,对于任何一个常数 K,其二进制可以表示为一组 0 和 1 交替的序列:[(0...0)(1...1)(0...0)(1...1)],14可以表示为:[(0...0)(111)(0)],考虑一组从位位置 n 到位位置 m 的连续的 1 (n ≥ m),(对于 14 有 n = 3,m = 1)可以有两种形式来计算位对乘积的影响。 + +这个优化不是一定的,大多数编译器只在需要少量移位、加减法就足够的时候才使用这种优化。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 磁盘到底是怎样工作的?一文理解硬盘结构 + +

+ +
+ + + + +
+ +
+ 数据库系统总会涉及到辅助存储(大多都是磁盘),因为它们能够存储大量需要长期保存的数据,因此我们有必要先了解了解磁盘的相关知识。 +根据机械原理,存储器的容量越大其速度就越慢。但是速度越快的存储器,其单位字节的价格就越贵。现代计算机系统可以包含几个不同的可以存储数据的部件,就形成了存储器的层次结构,但是需要注意的是「虚拟内存」是操作系统与操作系统运用机器硬件的产物,它不是存储器的层次之一。 +磁盘结构 +传统的硬盘盘结构是像下面这个样子的,它有一个或多个盘片,用于存储数据。盘片多采用铝合金材料;中间有一个主轴,所有的盘片都绕着这个主轴转动。一个组合臂上面有多个磁头臂,每个磁头臂上面都有一个磁头,负责读写数据。 + +磁盘一般有一个或多个盘片。每个盘片可以有两面,即第一个盘片的正面为0面,反面为 1 面;第二个盘片的正面为 2 面......依次类推。磁头的编号也和盘面的编号是一样的,因此有多少个盘面就有多少个磁头。盘面正视图如下图,磁头的传动臂只能在盘片的内外磁道之间移动。因此不管开机还是关机,磁头总是在盘片上面。关机时,磁头停在盘片上面,抖动容易划伤盘面造成数据损失,为了避免这样的情况,所以磁头都是停留在起停区的,起停区是没有数据的。 + +每个盘片的盘面被划分成多个狭窄的同心圆环,数据就存储在这样的同心圆环上面,我们将这样的圆环称为磁道 (Track)。每个盘面可以划分多个磁道,最外圈的磁道是0号磁道,向圆心增长依次为1磁道、2磁道......磁盘的数据存放就是从最外圈开始的。 + +根据硬盘的规格不同,磁道数可以从几百到成千上万不等。每个磁道可以存储数 Kb 的数据,但是计算机不必要每次都读写这么多数据。因此,再把每个磁道划分为若干个弧段,每个弧段就是一个扇区 (Sector)。扇区是硬盘上存储的物理单位,现在每个扇区可存储 512 字节数据已经成了业界的约定。也就是说,即使计算机只需要某一个字节的数据,但是也得把这个 512 个字节的数据全部读入内存,再选择所需要的那个字节。 + +柱面是我们抽象出来的一个逻辑概念,简单来说就是处于同一个垂直区域的磁道称为柱面 ,即各盘面上面相同位置磁道的集合。需要注意的是,磁盘读写数据是按柱面进行的,磁头读写数据时首先在同一柱面内从 0 磁头开始进行操作,依次向下在同一柱面的不同盘面(即磁头上)进行操作,只有在同一柱面所有的磁头全部读写完毕后磁头才转移到下一柱面。因为选取磁头只需通过电子切换即可,而选取柱面则必须通过机械切换。数据的读写是按柱面进行的,而不是按盘面进行,所以把数据存到同一个柱面是很有价值的。 +磁盘被磁盘控制器所控制(可控制一个或多个),它是一个小处理器,可以完成一些特定的工作。比如将磁头定位到一个特定的半径位置;从磁头所在的柱面选择一个扇区;读取数据等。 + +现代硬盘寻道都是采用CHS(Cylinder Head Sector)的方式,硬盘读取数据时,读写磁头沿径向移动,移到要读取的扇区所在磁道的上方,这段时间称为寻道时间(seek time)。因读写磁头的起始位置与目标位置之间的距离不同,寻道时间也不同。磁头到达指定磁道后,然后通过盘片的旋转,使得要读取的扇区转到读写磁头的下方,这段时间称为旋转延迟时间(rotational latencytime)。然后再读写数据,读写数据也需要时间,这段时间称为传输时间(transfer time)。 +根据上文的信息,我们可以得出磁盘容量的计算公式为: +硬盘容量 = 盘面数 × 柱面数 × 扇区数 × 512字节 + +笔试题实战 +下面的题目是腾讯某一年校招笔试中的一个题目,题干信息描述为:数据存储在磁盘上的排列方式会影响I/O服务的性能,一个圆环磁道上有10个物理块,10个数据记录R1~R10存放在这个磁道上,记录的安排顺序如下表所示。 + + + +物理块 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 + + + + +逻辑记录 +R1 +R2 +R3 +R4 +R5 +R6 +R7 +R8 +R9 +R10 + + + +假设磁盘的旋转速度为20ms,磁盘当前处在R1的开头处,若系统顺序扫描后将数据放入单缓冲区内,处理数据的时间为4ms(然后再读取下个记录),则处理这10个记录的最长时间是多少? + +答案:磁盘会一直朝某个方向旋转,不会因为处理数据而停止。本题要求顺序处理 R1 到 R10,起始位置在 R1,一周是 20ms,共 10 个记录,所以每个记录的读取时间为 2ms。首先读 R1 并处理 R1,读 R1 花 2ms,读好后磁盘处于 R1 的末尾或 R2 的开头,此时处理 R1,需要 4ms,因为磁盘一直旋转,所以 R1 处理好了后磁盘已经转到 R4 的开始了,这时花的时间为 2+4=6ms。这时候要处理 R2,需要等待磁盘从 R5 一直转到 R2 的开始才行,磁盘转动不可反向,所以要经过 8*2ms 才能转到 R1 的末尾,读取 R2 需要 2ms,再处理 R2 需要 4ms,处理结束后磁盘已经转到 R5 的开头了,这时花的时间为 2*8+2+4=22ms。等待磁盘再转到 R3 又要 8*2ms,加上 R3 自身 2ms 的读取时间和 4ms 的处理时间,花的时间也为 22ms,此时磁盘已经转到 R6 的开头了,写到这里,就可以看到规律了,读取并处理后序记录都为 22ms,所以总时间为 6+22*9=204ms。 + +如何加速对磁盘的访问 +对于理解数据库系统系统特别重要的是磁盘被划分为磁盘块(或像操作系统一样称之为页),每个块的大小是 4~64KB。磁盘访问一个磁盘块平均要用 10ms,但是这并不表示某一应用程序将数据请求发送到磁盘控制器后,需要等 10ms 才能得到数据。如果只有一个磁盘,在最坏的情况下,磁盘访问请求的到达个数超过 10ms 一次,那么这些请求就会被无限的阻塞,调度延迟将会变的非常大。因此,我们有必要做一些事情来减少磁盘的平均访问时间。 +按柱面组织数据:前这一点在前文已经提到过了。因为寻道时间占平均块访问时间的一半,如果我们选择在一个柱面上连续的读取所有块,那么我们只需要考虑一次寻道时间,而忽略其它时间。这样,从磁盘上读写数据的速度就接近于理论上的传输速率。 +使用多个磁盘:如果我们使用多个磁盘来替代一个磁盘,只要磁盘控制器、总线和内存能以 n 倍速率处理数据传输,则使用 n 个磁盘的效果近似于 1 个磁盘执行了 n 次操作。因此使用多个磁盘可以提高系统的性能。 +磁盘调度:提高磁盘系统吞吐率的另一个有效方法是让磁盘控制器在若干个请求中选择一个来首先执行,调度大量块请求的一个简单而有效的方法就是电梯算法。回忆一下电梯的运行方式,它并不是严格按先来后到的顺序为乘客服务,而是从建筑物的底层到顶层,然后再返回来。同样,我们把磁盘看作是在做横跨磁盘的扫描,从柱面最内圈到最外圈,然后再返回来,正如电梯做垂直运动一样。 +预取数据:在一些应用中,我们是可以预测从磁盘请求块的顺序的。因此我们就可以在需要这些块之前就将它们装入主存。这样做的好处是我们能较好的调度磁盘,比如采用前文的电梯算法来减少访问块所需要的平均时间。 +磁盘故障 +如果事情都像我们一开始设计的那样进行,那世界肯定会变得特别无聊。磁盘偶尔也会耍耍小脾气,甚至是罢工不干了。比如在读写某个扇区一次尝试没有成功,但是反复尝试后有成功读写了,我们称之为间歇性故障。 +一种更为严重的故障形式是,一个或多个二进制位永久的损坏了,所以不管我们尝试多少次都不可能成功,这种故障称之为介质损坏。 +另一种相关的错误类型称之为写故障,当我们企图写一个扇区时,既不能正确的写,也不能检索先前写入的扇区,发生这种情况的一种可能原因就是在写过程中断电了。 +当然肯定最严重的就是磁盘崩溃,这种故障中,整个磁盘都变为永久不可读,这是多么可怕的事情。 +既然会出现上面所述的各种大小故障,那么我们就必须要采取各种措施去应对大大小小的变故,保证系统能正常运行。 +规避故障 +我们尝试读一个磁盘块,但是该磁盘块的正确内容没有被传送到磁盘控制器中,就是一个间歇性故障发生了。那么问题是控制器如何能判断传入的内容是否正确呢?答案就是使用校验和,即在每个扇区使用若干个附加位。在读出时如果我们发现校验和对数据位不合适,那么我们就知道有错误;如果校验和正确,磁盘读取仍然有很小的可能是不正确的,但是我们可以通过增加趣多校验位来降低读取不正确发生的概率。 +此处我们使用奇偶校验来举例,通过设置一个校验位使得二进制集合中 1 的个数总是偶数。比如某个扇区的二进制位序列是 01101000,那么就有奇数个 1,所以奇偶位是 1,这个序列加上它后面的奇偶位,就有 011010001;而如果所给的序列是 11101110,那么奇偶位就是 0。所以每一个加上了奇偶位构成的 9 位序列都有偶数奇偶性。 +尽管校验和几乎能正确检测出介质故障或读写故障的存在,但是它却不能帮助我们纠正错误。为了处理这个问题,我们可以在一个或多个磁盘中执行一个被称为稳定存储的策略。通常的思想是,扇区时成对的,每一对代表一个扇区内容 X。我们把代表 X 的扇区对分别称为左拷贝 XL和右拷贝XR。这样实际上就是每个扇区的内容都存储了两份,操作XL失败,那么去操作XR就可以了,更何况我们还在每个扇区中有校验和,把错误的概率就大大降低了。 +到现在为止,我们讨论的都是简单的故障,但是如果发生了磁盘崩溃,其中的数据被永久破坏。而且数据没有备份到另一种介质中,对于银行金融系统这将是巨大的灾难,遇到这种情况我们应该怎么办呢? +数据恢复 +应对磁盘故障最简单的方式就是镜像磁盘,即我们常说的备份。回忆一下写毕业论文时的做法,那时候大部分同学还不会用版本控制器,所以基本采用每天备份一次数据,并且在文件名称中标注日期,以此来达到备份的效果。 +第二种方式是使用奇偶块,比如一个系统中有 3 个磁盘,那么我们再加一个磁盘作为冗余盘。在冗余盘中,第 i 块由所有数据盘的第 i 块奇偶校验位组成。也就是说,所有第 I 块的第 j 位,包括数据盘和冗余盘,在它们中间必须有偶数个 1,冗余盘的作用就是让这个条件为真。 +我们举个简单例子,假设快仅由一个字节组成,我们有三个数据盘和一个冗余盘,对应的位序列如下。其中 盘4 为冗余盘,它的位序列是根据前面三个盘计算出来的。 +盘 1:11110000 +盘 2:10101010 +盘 3:00111000 +盘 4:01100010 + +假设现在某个盘崩溃了,那么我们就能根据上面的序列来恢复数据,只需要让每一列 1 的个数为偶数就可以了,但是这种冗余方式也存在很大的不足。 +第一个缺陷是,如果是两个盘同时崩溃了,那数据也恢复不出来了。第二个问题在于,虽然读数据只需要一次 I/O 操作即可,但是写数据时就不一样了,因为需要根据其他数据盘来计算冗余盘中的位序列,假设共有 n 个盘,其中一个为冗余盘,所以每次写数据时,都需要进行 n+1 次 I/O 操作(读不被写入的 n-1 个盘,被重写数据盘的一次写,冗余盘的一次写),而 I/O操作又是非常耗时的操作,所以这种方法会大大拖慢系统性能。 +另一种方案是没有明显的冗余盘,而是把每个磁盘作为某些块的冗余盘来处理。比如现在有 4 个盘,0 号磁盘将作为编号为 4、8、12 等柱面的冗余,而 1 号磁盘作为编号为 1、5、9 等块的冗余...... +一种更为先进的方式使用海明码来帮助从故障中恢复数据,它在多个磁盘崩溃的情况下也能恢复出数据,也是 RAID 的最高等级,由于本人水平有限,用文字表达不清楚,就不作介绍了,嘿嘿。 + +
+ + Read More ~ +
+
+
+ +
+

+ + FastDFS 分布式文件系统简介 + +

+ +
+ + + + +
+ +
+ 文章内容是刘欣大大(《码农翻身》作者,公众号:码农翻身)的直播课内容,主要是了解一下分布式文件系统,学习FastDFS的一些设计思想,学习它怎么实现高效、简洁、轻量级的一个系统的 +FastDFS分布式文件系统简介 +国内知名的系统级开源软件凤毛菱角,FastDFS就是其中的一个,其用户包括我们所熟知的支付宝、京东商城、迅雷、58同城、赶集网等等,它是个人所开发的软件,作者是余庆。 +我们已经进入互联网时代,互联网给我们的生活带来便捷的同时,也给我们带来了诸多挑战。 + +对于海量文件的存储,一个机器不够,那么就用多台机器来存储。 + +如果一个文件只存储一份,那么如果存储这个文件的机器坏掉了,文件自然就丢失了,解决办法就是将文件进行备份,相信大多数人都有备份重要文件的习惯。FastDFS也是如此,为了防止单点的失败,肯定是需要冗余备份的。 +FastDFS把应用服务器分为若干个组,每一组里面可以有多台机器(一般采用3台),每一台机器叫做存储服务器(storage server)。同一组内之间的数据是互为备份的,也就是说用户把文件传到任一服务器,都会在同组内其它两个服务器进行备份,因此一个组的存储空间大小是由该组内存储空间最小的那台机器是一样的(和木桶原理一样)。为了不造成存储空间的浪费,同一个组内的三台机器最好都一样。 + +每个存储服务器(storage server)的存储就够又是怎样的呢?展开来看,它可以分为多个目录,每个目录以M开头,用00、01、02......来划分,一般无需划分这么多目录,只用一个目录就可以了。 +在每个根目录下面又划分了两级目录。如图所示,在/data/fastdfs0下又划分出两级目录,每一级有256个目录,这样算下来总共就有65535个目录了。存储文件时,就是通过两次哈希来确定放在哪一个目录下面。 + +那么问题就来了,有这么多组,到底该选择哪个组的服务器进行存储呢?或者说,访问的时候到底访问哪一个组呢? +FastDFS提供的解决思路是引入一个跟踪服务器(tracker server),它用于记录每一个组内的存储服务器信息,存储信息是每个storage主动回报给tracker,有了这些信息之后,tracker就可以做调度工作了,看看谁的存储空间大,就把文件放过去。 + +FastDFS的特点 + +组与组之间是相互独立的 +同一个组内的storage server之间需要相互备份 + +文件存放到一个storage之后,需要备份到别的服务器 + + +tracker之间是不交互的 + +每个storgae server都需要向所有的tracker去主动报告信息 +tracker与tracker之间是不知道彼此的存在的 + + + +如何上传文件 +为方便下载文件的理解,这里假设上传的文件为:Group1/M00/00/0C/wKjGgVgbV2-ABdo-AAAAHw.jpg +如下面的时序图可以看到客户端是如何上传文件到服务器的。首先client向tracker发送上传链接请求,然后由tracker进行调度,查询可用的storage,并把该storgae对应的ip和端口发送给client;拿到了存储服务器信息,client就直接将文件上传到storage即可;storage会生成新的文件名再写入到磁盘,完成之后再把新的文件信息返回给client,client最后把文件信息保存到本地。需要注意的是,storage会定时向tracker回报信息。 + +如何进行选择服务器 + +tracker不止一个,客户端选择哪一个做上传文件? + +tracker之间是对等的,任选一个都可以 + + +tracker如何选择group? + +round robin(轮询) +load balance(选择最大剩余空间group上传) +specify group(制定group上传) + + +如何选定storage? + +round robin,所有server轮询使用(默认) +根据ip地址进行排序选择第一个storage(ip地址最小者) +根据优先级进行排序(上传优先级由stoage来设置,参数为upload_priority) + + +如何选择storage path + +round robin,轮询(默认) +load balance,选择使用剩余空间最大的存储路径 + + + +如何选择存放目录 + +选定存放目录? + +storage会生成一个file_id,采用Base64编码,字段包括:storage ip、文件创建时间、文件大小、文件CRC32校验和随机数 +每个存储目录下面有两个256 * 256个子目录,storage会按文件file_id进行两次hash,然后将文件以file_id为文件名存储到子目录下 + + + +需要注意的是:file_id由cilent来保存,如果没有保存,你就不知道你上传的文件去那里了 +Storage server之间的文件同步 + +同一组内的storage之间是对等的,文件上传、删除等操作可以在任意一台storage上进行 +文件同步只在同组内的stroage之间进行,采用push方式,即源服务器同步给目标服务器 +源头数据才需要同步,备份数据不需要再次同步,否则就构成环路了 +新增一台storage时,由已有的一台storage将已有的所有数据(包括源头数据和备份数据)同步给该新增服务器 + +Storage的最后最早同步被同步时间 +这个标题有一些拗口,现在有三台服务器A、B、C,每个服务器都需要记录其他两台服务器向自己进行同步操作的最后时间。比如下图中的服务器A,B在9:31向A同步了所有的文件、C在9:33向A同步了所有的文件,那么A服务器的最后最早被同步时间就是9:31。其他两个服务器也是一样。 +最后最早被同步时间的意义在于判断一个文件是否存在于某个storage上。比如这里的A服务器最后最早被同步时间为9:31,那么如果一个文件的创建时间为9:30,就可以肯定这个文件在服务器A上肯定有。 +Stroage会定期将每台机器的同步时间告诉给tracker,tracker在client需要下载一个文件时,要判断一个storage是否有该文件,只需要解析文件的创建时间,然后与该值作比较,若该值大于创建时间,说明storage存在这个文件,可以从该storage下载。 + +但是这个算法有缺陷,比如下面的情况:W文件的创建时间是9:33,服务器C已经把9:33之前的文件都同步给B了,因此B服务器里面其实已经有W文件了,但是根据最后最早被同步时间,会认为B中没有W文件。因此这个算法虽然简单,但是牺牲了部分文件。 + +如何下载文件 +首先由client发送下载连接请求,请求的东西本质上就是Group1/M00/00/0C/wKjGgVgbV2-ABdo-AAAAHw.jpg;tracker将查询到的可用storage server(按下文的四个原则进行选择)的ip和端口发送给client;现在client有本地保存的文件信息,也有服务器的地址和端口,那么直接访问对应的服务器下载文件即可。 + +如何选择一个可供下载的storage server +共下面四个原则,从上到小条件越来越宽松 + +该文件上传到的源storage(文件直接上传到该服务器上) +文件创建时间戳 &amp;lt; storage被同步到的文件时间戳,这意味着当前文件已经被同步过来了 +文件创建时间戳 = storage被同步到的文件时间戳,并且(当前时间-文件创建时间戳)&amp;gt; 一个文件同步完场需要的最大时间(5分钟) +(当前时间 - 文件创建时间)&amp;gt; 文件同步延迟阀值,比如我们把阀值设置为1天,表示文件同步在一天内肯定可以完成 + +FastDFS的使用 +用户通过浏览器或者手机端访问web服务器,web服务器把请求转发给应用服务器,应用服务器收到请求后,通过fastDFS API和FastDFS文件系统进行交互。但是这么设计会造成应用服务器的压力,因为上传和下载都经过应用服务器。 + +为了避免应用服务器压力过大,可以让客户端直接使用Http访问,不通过应用服务器。 + +FastDFS其他内容 +防止盗链 +为了防止辛辛苦苦上传的文件被别人盗去,可以通过给URL设置token来解决。FastDFS的防止盗链配置如下: +# 是否做tokrn检查,缺省值为false + +http.anti\_steal.check\_token=true + +# 生成token的有效时长/秒 + +http.anti\_steal.token\_ttl=900 + +# 生成token的密钥,尽量设置长一些 + +http.anti\_steal.secret\_key=@#$%\*+\*&amp;amp;amp;!~ + +FastDFS生成token策略为:token = md5(文件名,密钥,时间戳) + + +合并存储 + +海量小文件的缺点 + +元数据管理低效,磁盘文件系统中,目录项、索引节点(inode)和数据(data)保存在介质不同的位置上 +数据存储分散 +磁盘的大量随机访问降低效率(小文件有可能这个在这个磁道,那个在那个磁道,就会造成大量的随机访问,大量小文件对I/O是非常不友好的) + + +FastDFS提供的合并存储功能 + +默认大文件64M +每个文件空间称为slot(256bytes = slot = 16MB) + + + +也就是说对于小文件,FastDFS会采用把多个小文件合并为一个大文件的方式来存储,默认建一个大小为64M的大文件,然后再分成多个槽,最小的槽是256bytes,因此如果一个文件小于256bytes,那么它也会占256bytes的大小。就好像我们在医院见到的中药柜子一样,每个抽屉里面再分成多个小格子,根据药材包的大小来选择不同大小的格子。 +没有合并时的文件ID + +合并时的文件ID + +此处不再深入探讨存储合并的机制,因为它带来了一系列新的问题,比如同步时不仅需要记录大文件的名称,还需要进入小文件的名称,一下子变得麻烦多了;原来空闲空间管理直接通过操作系统就能计算出来,但是现在不行了,因为是创建了一个64M的块,这个块里面还有空闲空间,计算起来就很麻烦了。 +总结 + +FastDFS是穷人的解决方案 +FastDFS把简洁和高效做到了极致,非常节约资源,中小型网站完全用得起 + + +
+ + Read More ~ +
+
+
+ + + +
+ + 上一页 + + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/media/js/jquery.sticky-sidebar.min.js b/media/js/jquery.sticky-sidebar.min.js new file mode 100644 index 00000000..a0e0c2b6 --- /dev/null +++ b/media/js/jquery.sticky-sidebar.min.js @@ -0,0 +1,8 @@ +/** + * sticky-sidebar - A JavaScript plugin for making smart and high performance. + * @version v3.3.1 + * @link https://github.com/abouolia/sticky-sidebar + * @author Ahmed Bouhuolia + * @license The MIT License (MIT) +**/ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.StickySidebar=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule?t.default:t}function e(t,e){return e={exports:{}},t(e,e.exports),e.exports}"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;var i=e(function(t,e){!function(t,i){i(e)}(0,function(t){function e(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function t(t,e){for(var i=0;i1&&void 0!==arguments[1]?arguments[1]:{};if(e(this,o),this.options=o.extend(n,s),this.sidebar="string"==typeof t?document.querySelector(t):t,void 0===this.sidebar)throw new Error("There is no specific sidebar element.");this.sidebarInner=!1,this.container=this.sidebar.parentElement,this.affixedType="STATIC",this.direction="down",this.support={transform:!1,transform3d:!1},this._initialized=!1,this._reStyle=!1,this._breakpoint=!1,this.dimensions={translateY:0,maxTranslateY:0,topSpacing:0,lastTopSpacing:0,bottomSpacing:0,lastBottomSpacing:0,sidebarHeight:0,sidebarWidth:0,containerTop:0,containerHeight:0,viewportHeight:0,viewportTop:0,lastViewportTop:0},["handleEvent"].forEach(function(t){i[t]=i[t].bind(i)}),this.initialize()}return i(o,[{key:"initialize",value:function(){var t=this;if(this._setSupportFeatures(),this.options.innerWrapperSelector&&(this.sidebarInner=this.sidebar.querySelector(this.options.innerWrapperSelector),null===this.sidebarInner&&(this.sidebarInner=!1)),!this.sidebarInner){var e=document.createElement("div");for(e.setAttribute("class","inner-wrapper-sticky"),this.sidebar.appendChild(e);this.sidebar.firstChild!=e;)e.appendChild(this.sidebar.firstChild);this.sidebarInner=this.sidebar.querySelector(".inner-wrapper-sticky")}if(this.options.containerSelector){var i=document.querySelectorAll(this.options.containerSelector);if((i=Array.prototype.slice.call(i)).forEach(function(e,i){e.contains(t.sidebar)&&(t.container=e)}),!i.length)throw new Error("The container does not contains on the sidebar.")}"function"!=typeof this.options.topSpacing&&(this.options.topSpacing=parseInt(this.options.topSpacing)||0),"function"!=typeof this.options.bottomSpacing&&(this.options.bottomSpacing=parseInt(this.options.bottomSpacing)||0),this._widthBreakpoint(),this.calcDimensions(),this.stickyPosition(),this.bindEvents(),this._initialized=!0}},{key:"bindEvents",value:function(){window.addEventListener("resize",this,{passive:!0,capture:!1}),window.addEventListener("scroll",this,{passive:!0,capture:!1}),this.sidebar.addEventListener("update"+t,this),this.options.resizeSensor&&"undefined"!=typeof ResizeSensor&&(new ResizeSensor(this.sidebarInner,this.handleEvent),new ResizeSensor(this.container,this.handleEvent))}},{key:"handleEvent",value:function(t){this.updateSticky(t)}},{key:"calcDimensions",value:function(){if(!this._breakpoint){var t=this.dimensions;t.containerTop=o.offsetRelative(this.container).top,t.containerHeight=this.container.clientHeight,t.containerBottom=t.containerTop+t.containerHeight,t.sidebarHeight=this.sidebarInner.offsetHeight,t.sidebarWidth=this.sidebarInner.offsetWidth,t.viewportHeight=window.innerHeight,t.maxTranslateY=t.containerHeight-t.sidebarHeight,this._calcDimensionsWithScroll()}}},{key:"_calcDimensionsWithScroll",value:function(){var t=this.dimensions;t.sidebarLeft=o.offsetRelative(this.sidebar).left,t.viewportTop=document.documentElement.scrollTop||document.body.scrollTop,t.viewportBottom=t.viewportTop+t.viewportHeight,t.viewportLeft=document.documentElement.scrollLeft||document.body.scrollLeft,t.topSpacing=this.options.topSpacing,t.bottomSpacing=this.options.bottomSpacing,"function"==typeof t.topSpacing&&(t.topSpacing=parseInt(t.topSpacing(this.sidebar))||0),"function"==typeof t.bottomSpacing&&(t.bottomSpacing=parseInt(t.bottomSpacing(this.sidebar))||0),"VIEWPORT-TOP"===this.affixedType?t.topSpacing=t.containerBottom?(t.translateY=t.containerBottom-e,o="CONTAINER-BOTTOM"):i>=t.containerTop&&(t.translateY=i-t.containerTop,o="VIEWPORT-TOP"):t.containerBottom<=n?(t.translateY=t.containerBottom-e,o="CONTAINER-BOTTOM"):e+t.translateY<=n?(t.translateY=n-e,o="VIEWPORT-BOTTOM"):t.containerTop+t.translateY<=i&&0!==t.translateY&&t.maxTranslateY!==t.translateY&&(o="VIEWPORT-UNBOTTOM"),o}},{key:"_getAffixTypeScrollingUp",value:function(){var t=this.dimensions,e=t.sidebarHeight+t.containerTop,i=t.viewportTop+t.topSpacing,n=t.viewportBottom-t.bottomSpacing,o=this.affixedType;return i<=t.translateY+t.containerTop?(t.translateY=i-t.containerTop,o="VIEWPORT-TOP"):t.containerBottom<=n?(t.translateY=t.containerBottom-e,o="CONTAINER-BOTTOM"):this.isSidebarFitsViewport()||t.containerTop<=i&&0!==t.translateY&&t.maxTranslateY!==t.translateY&&(o="VIEWPORT-UNBOTTOM"),o}},{key:"_getStyle",value:function(t){if(void 0!==t){var e={inner:{},outer:{}},i=this.dimensions;switch(t){case"VIEWPORT-TOP":e.inner={position:"fixed",top:i.topSpacing,left:i.sidebarLeft-i.viewportLeft,width:i.sidebarWidth};break;case"VIEWPORT-BOTTOM":e.inner={position:"fixed",top:"auto",left:i.sidebarLeft,bottom:i.bottomSpacing,width:i.sidebarWidth};break;case"CONTAINER-BOTTOM":case"VIEWPORT-UNBOTTOM":var n=this._getTranslate(0,i.translateY+"px");e.inner=n?{transform:n}:{position:"absolute",top:i.translateY,width:i.sidebarWidth}}switch(t){case"VIEWPORT-TOP":case"VIEWPORT-BOTTOM":case"VIEWPORT-UNBOTTOM":case"CONTAINER-BOTTOM":e.outer={height:i.sidebarHeight,position:"relative"}}return e.outer=o.extend({height:"",position:""},e.outer),e.inner=o.extend({position:"relative",top:"",left:"",bottom:"",width:"",transform:""},e.inner),e}}},{key:"stickyPosition",value:function(e){if(!this._breakpoint){e=this._reStyle||e||!1;var i=this.getAffixType(),n=this._getStyle(i);if((this.affixedType!=i||e)&&i){var s="affix."+i.toLowerCase().replace("viewport-","")+t;o.eventTrigger(this.sidebar,s),"STATIC"===i?o.removeClass(this.sidebar,this.options.stickyClass):o.addClass(this.sidebar,this.options.stickyClass);for(var r in n.outer){var a="number"==typeof n.outer[r]?"px":"";this.sidebar.style[r]=n.outer[r]+a}for(var p in n.inner){var c="number"==typeof n.inner[p]?"px":"";this.sidebarInner.style[p]=n.inner[p]+c}var l="affixed."+i.toLowerCase().replace("viewport-","")+t;o.eventTrigger(this.sidebar,l)}else this._initialized&&(this.sidebarInner.style.left=n.inner.left);this.affixedType=i}}},{key:"_widthBreakpoint",value:function(){window.innerWidth<=this.options.minWidth?(this._breakpoint=!0,this.affixedType="STATIC",this.sidebar.removeAttribute("style"),o.removeClass(this.sidebar,this.options.stickyClass),this.sidebarInner.removeAttribute("style")):this._breakpoint=!1}},{key:"updateSticky",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this._running||(this._running=!0,function(e){requestAnimationFrame(function(){switch(e){case"scroll":t._calcDimensionsWithScroll(),t.observeScrollDir(),t.stickyPosition();break;case"resize":default:t._widthBreakpoint(),t.calcDimensions(),t.stickyPosition(!0)}t._running=!1})}(e.type))}},{key:"_setSupportFeatures",value:function(){var t=this.support;t.transform=o.supportTransform(),t.transform3d=o.supportTransform(!0)}},{key:"_getTranslate",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;return this.support.transform3d?"translate3d("+t+", "+e+", "+i+")":!!this.support.translate&&"translate("+t+", "+e+")"}},{key:"destroy",value:function(){window.removeEventListener("resize",this,{capture:!1}),window.removeEventListener("scroll",this,{capture:!1}),this.sidebar.classList.remove(this.options.stickyClass),this.sidebar.style.minHeight="",this.sidebar.removeEventListener("update"+t,this);var e={inner:{},outer:{}};e.inner={position:"",top:"",left:"",bottom:"",width:"",transform:""},e.outer={height:"",position:""};for(var i in e.outer)this.sidebar.style[i]=e.outer[i];for(var n in e.inner)this.sidebarInner.style[n]=e.inner[n];this.options.resizeSensor&&"undefined"!=typeof ResizeSensor&&(ResizeSensor.detach(this.sidebarInner,this.handleEvent),ResizeSensor.detach(this.container,this.handleEvent))}}],[{key:"supportTransform",value:function(t){var e=!1,i=t?"perspective":"transform",n=i.charAt(0).toUpperCase()+i.slice(1),o=["Webkit","Moz","O","ms"],s=document.createElement("support").style;return(i+" "+o.join(n+" ")+n).split(" ").forEach(function(t,i){if(void 0!==s[t])return e=t,!1}),e}},{key:"eventTrigger",value:function(t,e,i){try{var n=new CustomEvent(e,{detail:i})}catch(t){(n=document.createEvent("CustomEvent")).initCustomEvent(e,!0,!0,i)}t.dispatchEvent(n)}},{key:"extend",value:function(t,e){var i={};for(var n in t)void 0!==e[n]?i[n]=e[n]:i[n]=t[n];return i}},{key:"offsetRelative",value:function(t){var e={left:0,top:0};do{var i=t.offsetTop,n=t.offsetLeft;isNaN(i)||(e.top+=i),isNaN(n)||(e.left+=n),t="BODY"===t.tagName?t.parentElement:t.offsetParent}while(t);return e}},{key:"addClass",value:function(t,e){o.hasClass(t,e)||(t.classList?t.classList.add(e):t.className+=" "+e)}},{key:"removeClass",value:function(t,e){o.hasClass(t,e)&&(t.classList?t.classList.remove(e):t.className=t.className.replace(new RegExp("(^|\\b)"+e.split(" ").join("|")+"(\\b|$)","gi")," "))}},{key:"hasClass",value:function(t,e){return t.classList?t.classList.contains(e):new RegExp("(^| )"+e+"( |$)","gi").test(t.className)}},{key:"defaults",get:function(){return n}}]),o}()}();t.default=n,window.StickySidebar=n})});return t(i),t(e(function(t,e){!function(t,e){e(i)}(0,function(t){var e=function(t){return t&&t.__esModule?t:{default:t}}(t);!function(){if("undefined"!=typeof window){var t=window.$||window.jQuery||window.Zepto;if(t){t.fn.stickySidebar=function(i){return this.each(function(){var n=t(this),o=t(this).data("stickySidebar");if(o||(o=new e.default(this,"object"==typeof i&&i),n.data("stickySidebar",o)),"string"==typeof i){if(void 0===o[i]&&-1===["destroy","updateSticky"].indexOf(i))throw new Error('No method named "'+i+'"');o[i]()}})},t.fn.stickySidebar.Constructor=e.default;var i=t.fn.stickySidebar;t.fn.stickySidebar.noConflict=function(){return t.fn.stickySidebar=i,this}}}}()})}))}); \ No newline at end of file diff --git a/nFAi4g10e/index.html b/nFAi4g10e/index.html new file mode 100644 index 00000000..575f1626 --- /dev/null +++ b/nFAi4g10e/index.html @@ -0,0 +1,535 @@ + + + + + + + + 高精度算法 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 高精度算法 +

+ + +
+
+
#include<bits/stdc++.h>
+using namespace std;
+
+#define N 200
+
+int main()
+{
+	char a[N], b[N];
+	int num1[N] = {}, num2[N] = {};
+	int len_a, len_b;
+	bool flag = false;
+	
+	cin>>a>>b;
+	len_a = strlen(a);
+	len_b = strlen(b);
+	
+	// 转换字符为数字,并将所有数位移至右对齐
+	for(int i = 0; i < len_a; i++)
+		num1[N-i-1] = a[len_a-1-i] - '0';
+	for(int i = 0; i < len_b; i++)
+		num2[N-i-1] = b[len_b-1-i] - '0';
+	
+	// 逐位相加
+	for(int i = 0; i < N; i++)
+		num1[i] += num2[i];
+	
+	// 从个位开始逐一进位
+	for(int i = N-1; i > 0; i--) {
+		num1[i-1] += num1[i] / 10;
+		num1[i] = num1[i] % 10;
+	}
+	
+	// 输出结果
+	for(int i = 0; i < N; i++){
+		if(num1[i] != 0)
+			flag = true;
+		if(flag)
+			cout<<num1[i];
+	}
+}
+
+
#include<bits/stdc++.h>
+using namespace std;
+
+#define N 300
+
+int main()
+{
+	char a[N], b[N], c[N];
+	int num1[N] = {}, num2[N] = {};
+	int len_a, len_b;
+	bool flag = false, symbol = false;
+	
+	cin>>a>>b;
+	
+	len_a = strlen(a);
+	len_b = strlen(b);
+	
+	// 如果相减结果为 0
+	if(len_a == len_b && strcmp(a, b) == 0){
+		cout<<0<<endl;
+		return 0;
+	}
+	
+	// 判断最终结果的符号
+	if(len_a < len_b || (len_a == len_b && strcmp(a, b) < 0))
+	   symbol = true;
+	
+	// 如果 a 比 b 小,那么调换顺序
+	if(symbol) {
+		memcpy(c, a, N);
+		memcpy(a, b, N);
+		memcpy(b, c, N);
+		// 重新更新长度信息
+		len_a = strlen(a);
+		len_b = strlen(b);
+	}
+	
+	
+	   
+	// 转换字符为数字,并将所有数位移至右对齐
+	for(int i = 0; i < len_a; i++)
+		num1[N-i-1] = a[len_a-1-i] - '0';
+	for(int i = 0; i < len_b; i++)
+		num2[N-i-1] = b[len_b-1-i] - '0';
+	
+	// 逐位相加
+	for(int i = 0; i < N; i++)
+		num1[i] -= num2[i];
+	
+	// 从个位开始逐一借位
+	for(int i = N-1; i > 0; i--) {
+		if(num1[i] < 0){
+			num1[i] += 10;
+			num1[i-1] -= 1;
+		}
+	}
+	
+	// 输出结果
+	if(symbol)
+		cout<<"-";
+	for(int i = 0; i < N; i++){
+		if(num1[i] != 0)
+			flag = true;
+		if(flag)
+			cout<<num1[i];
+	}
+}
+
+ +
+
+ + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/nUXTnR04_/index.html b/nUXTnR04_/index.html new file mode 100644 index 00000000..efb6bb71 --- /dev/null +++ b/nUXTnR04_/index.html @@ -0,0 +1,474 @@ + + + + + + + + 大学生书单推荐 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 大学生书单推荐 +

+ + +
+ +
+

经常阅读的人和不常读书的人是不一样的,都说「书中自有黄金屋,书中自有颜如玉」,但是多数人都跑到抖音、快手、微博这些平台上面如饥似渴的找黄金,在这个嘈杂的社会中能静心读书变成了一件幸福的事情,也尝试了一下牛客网的「书单」功能,但是有的书我在里面搜索不到,所以直接在这里发了。

+

在开头先说一下,有的书籍其实没必要去买实体书,尤其在一二线城市打拼的青年,如果在搬家时有太多的书籍简直就是一场噩梦。但是实体书也有另外一个好处,纸张的触感、可以拿笔在上面书写是电子书无法代替的,而且大部分人喜欢用手机去看电子书,很容易就被微信弹出来的消息给打扰了,在全民浮躁的年代能够聚精会神专注的做一件事简直是奢侈。

+

书也不是你读的越多越好,而是要看你把什么读进去了,内化成为自己的知识才算读进去了;也不是每一本书都要读完,如果发现一本书不适合自己那么撇到一边完全没有问题,何必浪费时间去做那些形式上的表演呢?我个人比较喜欢实体书的原因是,我看实体书的时候能够连续不间断读两个小时,只有专注去读一本书的时候才能让自己有所收获。

+

我推荐的书籍不一定适合你,但是你也要有意识的去读书,从自己喜欢的内容开始去阅读,什么言情啊、武侠啊、文学啊都可以,重要的是去阅读,逐渐的你就会爱上阅读,自己喜欢的内容会带领你逐步的去探索领域的边界,进而拓展到其它领域,靠自己的探索去获得新知识能够让你记忆更深刻,希望你也能变成一个读书人。

+

首先推荐李笑来的《把时间当作朋友》和《财富自由之路》,这两本书写的真不错,我身边有好多人都是因为这两本书而改变了人生轨迹,虽然李笑来的人设倒下了,但是不影响这两本书的质量,也不要因为一次人设倒下就全盘否定一个人,其实李笑来那一波操作之后人们反倒更加崇拜他了。
+​
+季羡林先生的书值得一读,大概读几本季老先生的文集就可以了,从文字里面去感受季老求真务实的态度,什么才是做学术的态度,可能读几本季老的书会抑制不住多读几本,大师之所以为大师大师,不仅仅是因为天资聪慧,还有他的为人处世、求真务实。

+

朱光潜先生《给青年的十二封信》一类的小册子非常值得读一读。比如我在读《谈美》的时候,本以为会是一次艰涩深奥的学习过程,但是没想到老先生用朴实的笔触,深入浅出地把我带进了一个栩栩如生的抽象世界,从青年人所熟知的文艺作品和生活事例出发,将美学的诸多原理娓娓道来。而且书虽取名为《谈美》,但又不仅是讲美学的书,入世出世的态度、青年人培养生活情趣和人生趣味的指南等等都有涉及。
+​
+《小狗钱钱》、《24 堂财富课——与女儿谈创业》、《穷爸爸富爸爸》、《好好赚钱》中可以选一两本阅读,大学只教了我们生存的技能,并没有教我们如何理财,而这又是每个人不得不面对的问题,这几本入门书籍,可以帮助您建立财富理念。再读一本《指数基金投资》,定投指数基金是最较适合小白的投资方式,尤其对于没有时间研究股票和基金的上班族来说,定投指数基金是一个很不错的方式。如果在学生时代有一点闲钱去尝试的话,就是一件更好的事情,因为学生时代几百几千看起来很多,但是当你参加工作了之后这点钱根本就不会放在眼里,试错成本低的阶段尽量多去尝试。
+​
+《浪潮之巅》、《数学之美》、《大学之路》等等,吴军博士的书籍都值得一看,《数学之美》是比较好的人工智能入门书籍,晦涩难懂的道理在吴军博士的文字中变的简单了,感受一下心态、格局等的提升会给人生带来多么重要的改变,我个人目前还没有读完。吴军老师还出了《见识》、《态度》、《格局》几本书,同时他在「得到」平台上的课程也是很值得学习的,不过建议得到和书籍选一个就行了,他新出的书籍很大一部分就是在得到上面稿子整理的。
+​
+像《长乐路》、《明朝那些事儿》、《以纸为桥》、《显微镜下的成都》、《邓小平时代》一类的书籍是可以读读的,应该去了解一下历史的细节,你所看到的不一定是真实的,光芒的背后是另一片黑暗,历史是有规律的,我们的生活可以在历史中找到影子。其中《邓小平时代》我有完整的版本,如果有需要可以微信联系我发你。
+​
+《人类简史》、《人性的弱点》、《心灵七游戏》等心理、哲学类书籍选几本读读,哲学是所有学科的抽象,到一个新的高度看看世界,提高自己的情商,让整个世界都为您张开拥抱的双臂。别好不容易谈个女朋友,因为情商不够又分了。
+​
+读不动名著小说,可以读《偷影子的人》,每个人都有相似的童年,只是大部分人都很羞涩,不好意思说出来而已,主人公就好像在经历自己曾经经历过的生活一样,从没有想过普普通通的童年居然能被描写的如此动情。《月亮与六便士》也是不错的选择。
+​
+都知道莫言先生获得了诺贝尔文学奖,我以前是很害怕读这种大家的书的,巧合看了电影《红高粱》产生了对原著的好奇,于是就去看了《红高粱家族》,结果一看就停不下来了。莫言把人性描述的淋漓尽致,每个人的性格、特点都异常鲜明,电影只是书籍的一部分,书籍比电影更精彩。高密东北乡在莫言的笔下算是绝了,莫言的语言驾驭能力,让各色人物活灵活现,就连那纯种的高粱也时刻在你的脑海里摇曳一番,姿态万千。
+​
+大部分学生都出生于农村家庭,农村的局限性不仅限制了我们的父辈,也限制了我们自己的,好不容易打破限制走到了大城市,一定不能被父母的传统思维影响到了,对父母不是要言听计从,能自己独立思考是成年人的标志,我们要学会对父母灌输给我们的思想取舍。《你当像鸟飞往你的山》英文名为《Educated》,是美国一位 30 多岁名叫塔拉的回忆录,一个从大山、摩门教家庭走出的女孩完成了最终的蜕变。每个人都有自己爹山,你当像鸟一样飞往你的山,过程中经历的转变、蜕变、虚伪、背叛,就是教育。这本书也是比尔盖茨年度荐书第一名、亚马逊年度编辑选书第一名......
+​
+大家应该都看过《美丽心灵》这部电影,电影中的主角纳什是博弈论的发明者,也是普林斯顿大学的教授,普林斯顿除了纳什外,还有很多我们所熟知的科学大牛。比如同为计算机科学之父和人工智能之父的艾伦·图灵,现代计算机之父冯·诺伊曼,出版了《了不起的盖茨比》的著名作家菲茨杰拉德......《自由的老虎》是一个进入普林斯顿的中国女孩用详实的档案与第一手采访资料,谱写的这些大牛挣脱束缚、追寻内心自由与价值的故事。
+​
+如果有可能的话,在大学最好认认真真谈一场恋爱,学习如何去爱一个人,在人生最好的年纪多一点洒脱,离开高中后大家就逐渐变得会算计了,心中有爱生活才会幸福。《霍乱时期的爱情》可以读一读,刚好和今年的新冠疫情有些契合。对了,像《非洲小扎》这样饶有趣味的异国生活小故事集读起来也很不错。
+​
+《荒诞医学史》和读库出版的《医学大神》全套十四册,非常值得一读,有的人大学白读了,这十四本小册子有助于培养自己的科学思维,多点医学知识总不是坏事,江湖骗子骗你的难度也会增加。科学的每一次进步都来之不易,现在人人都知道的勤洗手、用酒精消毒等常识,在前几个世纪居然是被科学界极力反对的做法,如果让我在本文里面只选择一套书籍,那么我会毫不犹豫的选择《医学大神》。在这里我建议你直接订阅读库的全年内容,这里有一篇为啥人人都爱《读库》的老六?的文章,你读完之后就会产生对读库的购买欲望了。
+​
+张立宪的真诚平实少有人及,《闪开,让我歌唱八十年代》中那些呈现细节的文字细腻的像个女子!可以读出来均是老六脑子里的一些记忆,记忆这东西多少都会出现点偏差,不必拿着学术精神去考究,那样就会失去读此书的欢乐了,对那个不属于我的时代是挺好奇的,里面还提到了很多不为人知的经典。

+

前一段已经提到读库了,如果你不太喜欢自己去挑选书籍的话,那么我强烈推荐你订阅「读库2021」,因为读库没有电子版所以你只能买纸质版,很多有深度的内容你在别的地方是看不到的。而且有意思的是每次打开读库就像打开盲盒一样开心。

+

比如东东枪采访郭德纲,梅兰芳先生特辑,生物基因究竟是怎么回事儿,这些东西,专门用几万字老老实实的笔锋讲清楚,完全没有那些华丽的东西,也没有那些拼流量的技巧,就是老老实实的文章说老老实实的事儿,在这个人人都抢流量的时代是难能可贵的。

+

《The Almanack of Naval Ravikant:A Guide to Wealth and Happiness》、《原则》、《奈飞文化手册》几本书介绍了很多人生应该践行的准则。前面两本算是创始人的人生原则清单, 指出不少我们应该避免的事情,也列出来了很多我们应该坚持的东西,比如写作、用计算机节省人力等等。《奈飞文化手册》我觉得算是集体智慧,虽然内容是站在企业角度写的,但对个人也有很大的人生指导意义。
+​
+最后再推荐一本《你是你吃出来的》吧,我们中国人的饮食基本是不健康的。身体所必须的营养一定要注意均衡摄入,不能因为不想吃饭或是学校的饭难吃就不吃,吃东西不仅要关注能量,更要注重七大营养素(碳水化合物、脂类、蛋白质、维生素、矿物质、水、膳食纤维)。鸡蛋、牛奶、蔬菜、水果、坚果、肉类、(深海)鱼、动物肝脏、米饭/面食等,都需要均衡摄入。《你是你吃出来的》里面做了大量苦口婆心的讲解,也纠正了大家平时的一些错误观点,比如很多人生病了不舒服就喝喝粥打发,觉得粥里面很有营养,实际上粥是没有什么营养的;粉条、土豆、红薯这些都是算作主食这一类的,尤其很多人把粉条当做菜来吃是绝对错误的,某一餐有粉条话就应该主动减少米饭的摄入量;很多地方晚上都习惯吃面食,觉得面食容易消化,但面食里面的主要要成分是碳水化合物,碳水化合物摄入过多的结果只会让你越来越胖。
+​
+「一席」上的大部分演讲是值得听的,让您对中国社会有一个更好的了解;「TED」、「网易公开课」上国外高校的视频可以多看看,讲的真心不错;国内出观众、大部分二流学校的演讲能不去就不去,还不如把这种时间拿去睡睡觉,补充补充精力。

+

多阅读高于自己的作品,远离精神毒品,互联网时代的阅读不再限于书籍了,良心大佬的博客也是一个不错的选择,只有阅读高于自己的作品才能获得成长,少刷抖音、微博、知乎一类的产品,里面是有一些有趣的人生,但是一刷就是几个小时,太浪费时间。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/ndHrBQqET/index.html b/ndHrBQqET/index.html new file mode 100644 index 00000000..5452698f --- /dev/null +++ b/ndHrBQqET/index.html @@ -0,0 +1,607 @@ + + + + + + + + 社会经验 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 社会经验 +
+ + +
+

+ + 那些经历过的人生谎言 + +

+ +
+ + + + +
+ +
+ 哈佛大学从 1938 年开始做了一个研究,研究内容就是「到底什么样的人活的最幸福?」,研究组在 75 年时间里跟踪了 724 个不同的人,到教授在 TED 演讲的时候仍然有 60 人在世并仍参与研究,大多数人都已经 90 岁了,现在已经在开始研究他们的子孙后代,并且人数达到了 2000 多人。 +这 724 人后来成了医生、律师、砖匠等,有人成了酒鬼、有人患上了精神分裂症,还有一位成了美国总统。估计也只有哈佛这样的地方才会去搞如此变态的研究,有足够的资金去承受这样理想的研究。人人都想要活的更加幸福,哈佛 75 年研究汇集成几万页的数据,然而结果只有一句话:良好的人际关系能让人更加健康和快乐,和我们所追求的财富、名望、努力工作等都没关系。下面是原视频内容。 + +小学语文课本上面有一个课后题要求自己编写《负荆请罪》故事的后续,记得当时老师把这个题目当作家庭作业留给学生了。回家之后只顾着玩泥巴、看电视了,第二天早上第一节课就是语文课,老师一上来就抽不同的同学给大家读自己写的故事后续。 +在其他同学读故事的时间,我慌忙的写了一段应付老师的抽查。不出所料,我也被抽中起来读自己编的故事了,没想到慌慌张张编的故事居然深得老师之心,还被老师当作范文给大家又读了一遍,因为我在故事里面用了「连忙」、「赶紧」这样的一些词汇,能表现出廉颇和蔺相如之间的客气等等。 +但我清晰记得那时候只想在被老师抽中之前把作业写了,胡编乱造完全不管写的好不好,只需要有一段话能够证明自己是做了作业的,万万没想到那么简单的一段话被老师赋予了数不清的情感。可类比的是各种网络平台上的文章,尤其是某个公司哪一位老总成功上位,铺天盖地的文章会把这个人肠子都翻出来看看,就好像写手比主人公还要了解自己。 + +最开始写文章是大四时候太闲想找点事做,断断续续到现在也算是写了 2 年多了,合起来有 100 来篇文章的样子。偶尔我会把以前写的文章搬到其它平台,比如把以前写的讲磁盘原理和自己做自然语言处理的两篇文章搬到 InfoQ,两篇文章都被平台置顶且精选至首页,而在公众号中讲磁盘原理的那篇文章阅读量是最低的。 +前不久写了一篇与拼多多等电商平台有关的内容,一发出来公众号后台立马将近 20 个取关,让我这个本就没几个读者朋友的公众号雪上加霜。然后我鼓起勇气把这篇文章复制到满是大佬的生财有术社群里面去,隔了几天就被评为了精华帖,拿到一颗龙珠,8000 元到手。 +去年年底想起了和师兄们一起刷牛客网题目的时光,随手就把 18 年写的一点文字贴上去了,18 年时候我的技术还很弱鸡,没想到贴上去却获得了还不错的反馈,一篇还被牛客小编标记为精选帖。于是我把 19 年遇到的关于服务器性能优化的一篇文章贴上去,那篇是我花了一个月时间查各种资料再加上自己实践的经验,现在看来都依然觉得写的非常不错,然而这篇文章在牛客网连一个赞都没有。 +我写文章是兴趣驱动,写的好写得坏都不咋关心,但偶尔会有那么一两篇文章自己花了很多的精力去写,写完还自嗨一下写的不错,结果一发出来就打脸了。也出现过我自己敷衍了事像是一时应付写的文章,却被好几个大 V 看中,希望转载的。 +忘记 18 年怎么加了「寒泉子」微信的,当时他输出的文章很多是关于 Java 虚拟机性能优化的,没有一篇文章我能读懂的,所以寒泉子的文章对我完全没有作用,围观他的朋友圈只是看大佬是如何成长的,那时候寒泉子创建「笨马科技」公司不久(现在笨马已经获得了高瓴资本的投资)。成功创业且快速的成长让寒泉子得到了更多人的关注,寒泉子的文章相比那时候阅读量提高了很多,但我依旧看不到他文章对我的价值,但我知道看不到好文章价值的原因是因为我的水平不够。 +说了这么多就是想表达很多文字不是没有价值,只是因为你的水平太低而看不到他的价值(水平太高也会看不到价值),并不一定都是别人写的差。一篇文章给了你一个关键字那他就是有价值的,我们更多的是应该去培养自己抓线索的能力,一个不起眼的词语就能揪出来太多有趣的东西,只是大多数人即使把方法告诉他了都还是不会操作,需要努力提升自己的信息素养。 + +读了些书、见了些人、经历了些事,总结了几个人生的高级谎言。中年危机是不思进取的人经历的,尽量不要被社会上那些营销话术给骗了,少看那些什么发展各种副业挣钱的广告,把搞这些的精力好好用到工作上薪资早早的就涨起来了,尤其是玩副业几年都没有玩出花样的人,更应该把心收一收踏踏实实工作。 +大家都知道「复利效应」这回事,比如 100 块钱按每年 5% 的利息计算,50 年后就能变成 1000 多块钱,看这个收益是不是非常可观?这个谎言的真相是你找不到年化能长期稳定在 5% 的投资产品,市面上各种理财培训都会说这个谎言,一些人自己本来没有赚到钱,却通过教别人怎么赚钱而赚到钱了。 +30 岁之后身体就不行了、女人生娃一定要在 28 岁之前。乍听起来没有什么问题,但仔细一想就会发现这里采用年龄进行量化就不对,科学的量化应该是采用身体机能的各项参数。年龄只是一个表象的东西,成熟也同样与年龄无关,而与经历相关。 +观察了一些电信诈骗的案例,有认识的朋友也有从未谋面的网友,自己也协助警方做了一次证人。会主动给你发营业执照、自己工牌一类信息的就是骗子。如果销售多次给你强调自己是正规公司,那他们基本就是不正规公司。凡是你根本不认识的人,但一上来就说要带你赚钱的,简单的当骗子处理即可,百分百不会错杀一个好人。 +每个人都有一套稳定且自洽的逻辑闭环,自己的观念代表的是自己这个人,观念被否定相当于自己这个人被否定,可能这就是会吵架的一个内在原因,「老顽固」大概也是这么来的。人天生就喜欢呆在舒适区,不同观点发生碰撞就是把稳态打破,别人否定自己是一件极难接受的事情,为什么会难以接受呢?因为打破稳态需要思考,而思考是一件费能量的事情,生物的天性就是要节省能量,这大概和祖先生活的环境有关系,虽然现在的环境已经不需要这样做了,但遗憾的是基因却还没有适应,自己努力克服基因的缺陷吧。 + +学生 A 就读于成都一所中法合办的小学,进出这个学校多次之后的感受就是学校有钱,学生家里也都比较有钱,所以大部分学生都属于调皮类型的,综合该校多名学生口中的信息,每个班至少气跑了两名以上的老师。学生 A 在我处学习编程,他们班的同学都属于比较社会的那种,班主任老师也被这帮学生训练的贼有经验。 +学校有一个厕所坏了,所以物管处就把这个厕所给锁上了,然而这种被锁上的门总是能激起学生的好奇心,很多学生下课就跑去门缝观察 💩,甚至有的学生还翻墙进去观察 💩,里面最有创意的学生应属学生 A。 +厕所里面被拖把遮住了,学生 A 在门缝寻找到了一个比较好的观察角度,大吼了一句「奥利给溢出来了」,另一个学生并没有看到所以就翻墙进去看,却一不小心掉进坑里了,学生 A 嘴里吼着「XXX 满是奥利给的跑出来了」跑开。估计这是有史以来「奥利给」这么正能量的词语被侮辱的最惨的一次。 +此事被学生 A 的班主任老师知道了,要说就得佩服这种班级的老师,不打不骂只让学生以「奥利给」为主题写一篇观察日记,要求学生把观察 💩的过程详细描述出来。学生 A 和班主任老师配合的这出戏在学校广为流传,大大提高了学生 A 的名声。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 幸福可以慢慢浇灌长大 + +

+ +
+ + + + +
+ +
+ 电影《点球成金》讲的是一个没有资金实力的弱队如何翻身的全程记录,在薪水预算只有强队 10% 的情况下如何去争夺冠军。与大多数体育片不一样的是,这部电影是从管理层入手的,推荐想要创业或是正在创业的人看一看,在明知道自己的做法是对的,但是又没有人支持、没有人看好的情况下,一个领导者应该怎么办?当明知开除某个球员就意味着他职业生涯的结束,甚至会将他推向生活窘迫的深渊,作为一个管理者是否会一时心软而手下留情...... +电影中的彼得(耶鲁大学经济学硕士)采用了一种全新的方法(数据分析)挑选球员,当然这种方法现在看来已经见怪不怪了,军事运筹学中也使用了相当的方法来计算如何赢得一场战斗。把这样的方法应用到自己行业的难点在于「量化」,我觉得企业管理者在这方面可以多思考思考。 +《原则》一书中作者也提到类似的点,将自己平时做决策的过程量化、抽象,将其转换成计算机可以理解的模型,计算机是没有那些乱七八糟的情绪的,只要把模型抽象的足够好就可以降低自己的成本,还能提高容错性。书中提到了对员工采用了性格测试进行分工,我现在是比较认可通过这种方式来决定员工具体工作的,想起来以前参加校园招聘的时候,一度认为那些大企业给员工做性格测试是不是钱多的没处花了,现在想想还是自己太傻。 +出于好奇我也给自己做了个 MBTI 测试,发现我是「表演者」人格类型,了解了一点关于我自己性格上的缺点,一些无伤大雅的问题大可以听之任之,一些需要改进的地方我相信也是可以通过持久的努力改变的。我目前对性格测试也有些怀疑,但我很清楚它比星座说「你是个渴了就会喝水的人」更具科学性一些。 + +我特别羡慕的两个人是读库的创始人老六和自己上大学时遇到的花儿,还不知道老六的朋友可以花一两分钟翻一下为啥人人都爱《读库》的老六?在我看来老六做的东西已经不叫工作了,而是叫事业了,不知道我这一生能不能在某个时间节点达到那样的状态,或是更可能这一生都达不到。刚好前几个小时看完了读库 2020 年(北京站)的视频,老六提到了六个问题应该是大多数人都存在的,比如「没有应变能力的坚持」、「不给时间压力便遥遥无期」,点击读库年会(北京站)完整版视频可以观看。 + +我第一次遇到花儿是在上大学的时候,那时候她还在成都宽窄巷子做青旅,几次到成都我都是到她那个青旅住下,因为在那里总是能遇到很多有趣的人。大概在大二快结束的时候我看到花老板发了朋友圈,才知道她去到大理做青旅了,也是通过朋友圈知道了她还有一个公众号,现在主要是通过花老板的公众号观察她的生活。我羡慕花儿那种把日子过的很幸福的状态,各方面细节可以都可以看到花儿是一个很知足的人,写歌、作曲、唱歌,日子看起来好不快活,花儿的公众号名称叫「微小而确实的日常」。 +日本的杂物管理咨询师山下英子写了一本《断舍离》,从 20 年 1 月看到这本书开始,我一直都在学习和实践断舍离,我个人觉得断舍离的人生整理理念是很受用的,我们的人生就应该多尝试断舍离,更多的东西只会让自己的脑袋更像浆糊,当然这可能和商业行为有些背离。 +商业广告会告诉你他们研发了一种减肥药或是减肥茶的产品,使用之后就能快速的瘦身塑形,但仔细想想要瘦下来不应该是减少碳水化合物的摄入吗?为什么要一边给自己打针又一边狂吃高胆固醇的食品呢?已经看不下去的书、已经挽回不了的爱情这些不都应该舍弃吗?商业广告会告诉你需要做加法,但其实你真正应该做的是减法。 + +从学校毕业已经快要有 3 年了,真正开始积累社会经验是 20 年下半年,大胆在这里分享自己学到的一些东西。看一个人靠谱不靠谱不要听他的嘴巴怎么说,而是要看他怎么做事、怎么做人、别人对他如何评价的。如果想要看一个人的能力如何,看看他之前干成过什么事就可以了,干成的事情是最好的佐证。如果他老是强调自己是多么的可靠,自己的实力是多么的强劲,那他基本就是个不可靠且没啥实力的人。 +一些文章会用「众所周知」、「有研究表明」、「很多学者指出」、「我身边有很多例子」这样的开头来证明某个观点,那大概率文章中内容是在瞎扯淡,如果要你要反驳的话他还能举出来更多的例子,不与这样的作者进行争辩是比较节约时间的。想了一下这个问题以前在我身上也挺严重的,现在已经减轻很多了。 +找工作的时候看一个公司靠谱不靠谱可以看给你缴的社保多少,比如公司实际给你发的薪资是一万,但是缴纳社保的基数却填个 3500,说到底就是公司舍不得给员工花钱,舍不得给员工花钱的公司你还想从他那里得到点什么呢?同样那种挖空心思避税的公司也不值得去,会和税务机关玩猫腻的公司难道还差和员工玩猫腻的胆子吗?我遇到过这种公司,工资分几笔发到手就是为了避税。 + +春节回老家发现了一个有趣的现象,长辈们手机里面必备的两个应用是微信和抖音,他们大多数都很愿意在抖音上面分享自己的生活,即使拍的作品土得掉渣也非常乐于「分享美好生活」。长辈之间有很多许久没有联系的儿时玩伴或是亲戚,有一些都超过 10 年没有联系过了,甚至连对方长什么样子都不太记得了。 +不得不承认抖音的推荐算法确实很牛,十多年没有产生联系的朋友抖音能帮你找到,再通过抖音私信互换微信,老友居然就这样再次取得了联系。用微信打个视频电话问候一下老友,通过了解对方正在做的事情还能缩小信息差,这样的聊天还能让他们的心情变得更好。 +做过一段的信息流广告,真真切切体会到了「如果你没有花钱买产品,那你就是被卖的产品」,这是纪录片《监视资本主义:智能陷阱 The Social Dilemma》中的一句话,以前在阅读高于自己的作品,远离精神毒品中批评过抖音、快手一类的产品,但现在我得改一点这个观念,50 岁以上的人没事时候刷刷抖音挺不错的,年轻人还是别去当被卖的产品了。 + +在深圳有一次和老叔吃饭的时候他讲了一点自己的经历,我已经记不得他年轻时候在哪个公司了,当时他请客在阳光酒店(深圳第一家五星级酒店)吃饭,只需要签个字就行了,那时候非常的牛气。他说正是那个时候把他害了,自己得意忘形不思进取,不然他现在也不至于都 60 多了还要给老板低身下气的工作。 +另一个老叔也分享了他年轻时候在湖北一个国企工作的经历,职位很高且收入不低,非常宠爱自己的小儿子。现在儿子要买车子直接给他打电话:“老爸,我要买车你给我多少钱?”他说自己没有钱,儿子回了句:“那你没有钱还有什么用?” +过年回家姨夫告诉我他前面几十年都活的迷迷糊糊的,一直快到 50 岁了才找到自己的方向。他说人知足而且有某个东西能让自己自信,就会过的比较幸福,自信不应该来源于金钱,要懂得赞美别人,要明白女人也是很幸苦的。我惊讶的发现姨夫作为一个大学生眼里瞧不起的农民工,已经比大学生高出好几个 level 了,而且他的收入也已经比 996 的程序员高出几个 level 了。 +最后推荐一个轻松的视频吧,一席的演讲视频杨小峰:寻找昆虫,里面不仅有很多精美可爱的昆虫照片,还有很多大开脑洞的玩味,而且这个老师有脱口秀的那味道。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 我自己的职场故事与经验 + +

+ +
+ + + + +
+ +
+ 本文首发于牛客网的社畜职场交流圈。 +从毕业后就基本没有看过牛客网了,前段时间再打开牛客网看发现有了很多新的板块,讨论区也变得比以前要活跃很多,话题也从仅仅的找工作面试类延伸到了职场、租房等等,牛客也开放了几个创作者计划的活动,我也用自己的职场故事和经验来参加一下。 +我的经验是第一份工作很重要但是没有大家想象的那么重要。我一直都是一个马大哈,签三方协议的时候面试官告诉他他给我安排的是做 5G 的部门,看到一大堆硕士生拿着简历都在签三方协议,在场的本科生没有几个,我啥都没有细问就直接签了。 +到公司报道的时候才知道部门是做 5G 测试的,心里一下有些失落觉得可能后面的开发之路就毁了。但实际上我想错了,等入职培训完了之后真正进入工作时候才知道,我所在的团队守护着 2G、3G、4G 和 5G 的所有测试架构,团队平时为了提高部门测试效率所开发的工具已经早早的延伸到无线院,而真正的 5G 开发部使用的语言是汇编语言和 C 语言,每个部门只负责 5G 产品的一小部分,具体到某一个人就负责的更细粒度了。 +所以一个有趣的现象就出现了,因为我们团队的人需要维护 5G 的测试架构,竟然对产品的了解要比开发部的人更加全面一点(当然深度肯定是远远不及开发部的人),而且开发部的人也得使用我们团队开发的工具,不管开发部的人怎么怼测试人员,我们都是有办法怼回去的。 +从我个人和一些职场人士的交谈来看,第一份工作并不会决定你将来要做的是什么工作,更多的是学习为人处事的态度,适应社会与职场上不管你喜欢还是不喜欢的规则,但是请注意你的技术栈是由你的工作决定的,你自己是决定不了你的技术栈的。 +第二个经验就是物以稀为贵唯独知识不是。无线院准备做几个工具解决几个部门都存在的问题,要求每个部门出一个人参与一个工具的开发,我们部门被分配做一个 5G 数据度量系统。测试部门的技术栈无非就是 python 什么的,整个部门 200 多人会写前端的不超过 10 个,而这个度量系统后端使用的是 java,前端使用的是 vue,部门再一眼数过去部门没有人用过 Vue,而有 Java 经验的只有我和我师傅,师傅是团队的小组长无法抽身干这些对部门没多少收益的事情(度量一看就知道是给领导们用的)。 +按照正常的安排我这个时候是应该被派到枯燥的基站上玩 3 个月的,而且基站相关的师傅都早早的给我分配好了,因为这个度量系统我稀里糊涂的被派到南京出差了,当然也就免于去干无聊的基站测试工作,顺便还给了大把的时间让我学习以前不知道的 Vue。 +说实话这个度量系统本身没有什么技术性,但是它却让我接触到了 5G 的产品经理、需求教练等等,每次和他们开会让我逐渐有了一点从领导视角去看问题的意识,他们能把问题看的更加全面、透彻,而且大多数时候都是在做取舍。 +记得高中化学老师开了个玩笑,地震过了你就不学逃生知识了?你不知道我们这个山沟沟很容易发生泥石流吗?那一段时间学习的 Vue 技术并没有浪费,我后面从 0 到 1 开发一个系统的时候,刚好就用 Vue 将团队厚重的 Angular 给替代了,也没想到我现在从事少儿编程教育工作居然还用到了那时候的技术(开发平台的前端)。 +希望你别领会错我的意思,不是让你像打散弹枪一样去胡乱学习一大堆知识,而是抓住每一次学习的机会尽量把它理解透彻。 +第三个经验是保持谦卑之心,尽量独立解决问题。进入职场之后每个人都有自己的工作,不再像学校那样你问老师一个问题,老师会把答案告诉你还生怕你学不会,所以遇到问题先去网上搜一搜资料,搜索引擎前 8 页的链接都点开看一下,百度不行就上谷歌,谷歌不行可以去 Github 搜一搜,要是还不行的话就去 StackOverflow 上面提个问题。 +如果你按照我上面说的路径都找不到答案的话,那这时再去请教一下部门的老员工,有了前面的探索你提出来的问题会更加有水平,高水平的提问也会帮助你逐渐赢得老同事们的认可,想一下天天提的问题像个小学生一样,可能也就是自己认为是好学多问。 +不管你认可还是不认可,同时给 A 和 B 抛出同样的一个问题,A 能把问题解决而 B 不能解决问题,那 A 就是要比 B 牛逼。我越来越相信实力是赢得别人认可的基石,能独立解决问题在一定程度上也说明你是个靠谱的人。能解决小问题人家才会把更大的问题交给你,不要嫌弃那些小事情,把小事情做到精致别人就会给你更大的成长机会。 +第四个经验是用心去做事,快快乐乐服务同事。不管你的技术多么牛逼,你敢拍着胸脯说自己写的程序没有 bug 吗?如果同事给你说你的软件哪里有问题,虚心的接受并快快乐乐的帮人家解决问题,他们是你的天使用户,如果你连自己的天使用户都留不住那还怎么留住外面的用户呢? +由于 5G 测试用例实在太多了,所以我和另一个同事一前一后负责开发测试任务管理系统,那是我第一次做复杂交互的前端系统,刚开始的几个版本我写的烂的要命,有的按钮甚至能让用户卡四五十秒,每天都会接到五六个同事的电话说系统太卡了,但是我自己那段时间也没办法啊,技术水平不够完全不知道怎么去优化。 +所以同事不管什么问题我照单全收,还专门列了一个问题表,每次同事在旁边说的时候我打开往里面添加记录,自己的产品不行就先把服务做好嘛。很多费时费力的操作我索性加班帮同事搞定,所以那段时间系统虽然难用的要死,但是没有一个同事直接用邮件或是当面怼我的,虽然我知道他们是脸上笑嘻嘻心里妈卖批,但我眼睛只能看到脸上的笑容。 +除了学习技术优化系统性能外,我还自己看了一些关于设计的文章,不懂设计那我就想着怎么让用户用着舒服呗。逐渐系统都能做到实时响应并且美观大方当然操作还尽量简单,被其它部门的同事看到偶尔会有几个主动询问的,加上师傅的推动很容易就把系统推开了。 +在师傅的敦促下我将其做了平台化开发,离职前已经将西安部门接入系统。那段时间与在美团和去哪儿工作的学长们交流,他们在定级答辩时评委更关心的是你如何把系统推广出去的,里面用到了什么你觉得牛逼的技术在他们眼里并不牛逼。 +回到最开始的第一份工作内容不太重要,我现在是一个少儿编程老师同时做着少儿编程平台的研发工作,本来想好好的做一个少儿编程老师就行了,谁能想到之前的程序员经历让我在新的环境竟然更加有竞争力。借用别人的话来说就是,应该多一点洒脱,人生会给你更多的可能。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 分享一点关于租房经验 + +

+ +
+ + + + +
+ +
+ 在牛客网上看到一个创作者计划,分享关于租房、理财一类的帖子可以获得一定的奖励,这篇文章首发于牛客网,稍微做了一点点补充再发到本博客平台。 +估计大多数小伙伴都是在类似自如、贝壳、蘑菇租房等平台上找房,关于这些平台的经验网上已经能够查到很多了,我就不再去分享关于这些平台的避坑经验了。这里分享一点关于我的稍微偏门一点的租房方法吧。前面提到的自如那些平台至少是有一个作用的,可以通过它们大致了解某个地域租房的价格区间,基本可以定位哪里便宜、哪里热门,基本上自己去找的话都不会比这些平台还要贵。 +豆瓣租房讨论组上面可以找到一些转租、直租的信息,比如我要在深圳南山区租一间小房子,那么我可以去豆瓣搜索「深圳南山租房」,进入相关的讨论组可以看到豆油们的一些讨论信息,除了能找到一些房源信息外还可以通过大家的评论了解一丢丢避坑指南。 + +需要注意的是即使是豆瓣这样纯粹的平台,里面也免不了很多水军或是广告信息,还有一些中介也会在里面发信息,这个就需要自己去甄别过滤无效信息了,实在不行可以加个微信聊一聊就知道了嘛。另外不要轻易把自己的常用电话留到平台去,不然什么安装宽带的、其它包租公司、甚至什么洗衣服洗鞋一类电话有可能会打到你心烦。 +如果已经定了要去哪个城市参加工作,那招你进去的 HR 这个资源是可以使用的,稍微大一点的公司都会有内部社区平台,这种平台应该只有入职之后才能使用。公司的同事也会在上面发一些关于租房的信息,大可以让 HR 帮助自己看一眼截个图或是转发给你,因为都是同一个公司的同事所以遇到坑的可能性要小很多,毕竟抬头不见低头见的,说不定哪一天你们还会一起共事呢,内部人要实在一些。 +如果能联系到已经入职的朋友、师兄师姐、前辈更好,找他们问一问公司同事一般都在哪里租房,哪里的房子住着舒服、房租便宜、通勤方便,这些信息只有他们才掌握的最精准,所以先问一下他们绝对不是脱裤子放屁,运气好的话说不定还有师兄师姐正好要转租房子呢,当然也可以让他们帮你推荐一下房东信息,这样可以快速的帮助你缩小搜索范围。 +假设你在 A 地点工作,可以以 A 地为中心看一看周围 5 公里的地方,一些房好价低的房东不一定懂的如何在网上发布租房信息,如果有时间的话可以去溜达溜达(反正你也是要逛超市买生活用品的嘛),看到一个有意思的小区就去和大爷唠唠嗑,尤其那种看着年龄不小又缺人唠嗑的大爷,他们嘴巴里能吐出来很多有用的信息。 +实际看房的时候需要注意一下房间的朝向和楼层,楼层太低照不到阳光住着压抑,楼层太高夏天可能会非常的热,在深圳那种城市的高楼层空调是极有可能顶不住酷暑的。还得注意一下周围有没有工地什么的,不然太吵睡不好觉也烦心。一些基本生活设施也要对比一下,比如离超市有多远、出门约会是否方便等等。 +签约时问好房间物品损坏应该怎么赔偿,房租到期押金应该怎么退?每天晚上大门是否会锁?忘记带钥匙/房卡怎么处理?钥匙/房卡丢了补办是否要另交费?房间有问题的地方一定要拍个照片留个证据,避免到时候退房的时候有争议被扣押金。像水电这些应该每个人都会问的,我这里就不再赘述了。 +虽然互联网已经很发达了,但是还是有很多房东不知道如何在网上发布招租信息,或者是发布招租信息是要花钱的,这个门槛把一些房东给挡在互联网之外了。他们会在一些社区公示栏、或者在朋友门店显眼的地方贴一些联系方式,这些信息只有自己实际去走动才能拿到。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 那些可以自己一个人薅羊毛的小项目 + +

+ +
+ + + + +
+ +
+ 打开知乎、微信这些平台的时候,经常就有意无意冒出来「一部手机怎么赚钱?」、「有什么好的手机赚钱软件?」这一类标题的文章。抖音信息流广告中也有很多类似的台词,「只需一部手机,一件代发,不需要囤货,挑战月入上万」这类广告词都是带你开一个网店,「一部手机,每天只需两小时,一个月轻松回本,开启财富自由模式」...... +上面提到的那些模式有没有赚到钱的?有,而且还不少。但是拿到你手里能不能赚钱?基本不能!90% 以上的人都是在交智商税,或者是因为执行力不够强,自己没有坚持下来,真正赚了钱的普通人少之又少。这一篇文章不玩套路,分享一个任何人可以套利的模式,也就是上面提到的普通人如何用一部手机赚钱? +先把答案放到前面:撸货,这里以市面上比较火热的飞天茅台为例。相信绝大部分人都知道茅台吧,稍微有一点点了解的人就会知道飞天。百度一下飞天茅台的一点点信息,它的出厂价 900 元,再去淘宝、京东这些平台查一下它的价格,茅台官方自营、京东官方自营、天猫超市等是 1499 元一瓶,普通商家卖的都是 2500 元以上一瓶。 + +所以这就看到了套利空间了吧?出厂价不要想了,你肯定拿不到那么低的价格的,要是你能拿到出厂价的价格我们可以做个微信朋友吗?悄悄的留下自己的微信号:Guanngxu。那第二档的价格就是天猫超市、京东这些平台的 1499 元一瓶了,这正是我这篇文章所要写的内容了。茅台是硬通货不用担心卖不出去,只要你手里有茅台就不用怕砸钱,当然前提是你入手的成本不高啊。可怜我之前只知道陪房东、陪老叔喝茅台,居然完全不知道这里面有这么大的套利空间,感觉错过了几个亿。 +那么都有哪些地方可以抢茅台呢?京东、天猫、淘宝这种大家都知道的平台就不用说了,只是基本都需要会员才能抢,觉得开会员的成本比较大?那这时候就可以去找一找低价的渠道了,比如说一些兑换码什么的,还有一些银行和京东的联盟信用卡,当然你也可以联系我呀! +还有一些大家可能会忘的渠道,比如说正在慢慢退出大家视野的苏宁易购,比如说很多人都没有用过的国美 APP,还有更少人知道的酒仙网、华润万家、山姆会员店这些。这些可能都需要开通会员才行,怎么开通呢?比如下面这个小程序二维码可以开通国美的会员,你也可以直接在 APP 里面开通会员,也可以找找别的渠道。 + +所以有这么多的平台可以去预约茅台,你只需要稍微研究一下平台每天放量的时间点,列一个表然后按照时间去抢就行了,抢得到抢不到就看运气呗。但是成年人的事情不能只靠运气啊,没有运气也要创造运气嘛。把自己的七大姑八大姨都整上,大家一起去抢茅台。 +这里就是看你人品的时候了,懂得把利益分享给他人你才会有更多的收获,钱是眼睛能看到的利润,但是还有更多眼睛看不到的利润,比如找别人帮忙就给别人发个红包,别人给到你一个启发、一个以前不知道的信息,给别人发一个红包,这些简单的细节可能逐渐得到对方的信任,因为眼前这个人知道的远比你想象的要多得多,他在自己的领域至少是个小狄公吧。 +除了这些平台还有一些其它线下的平台,这种有一点点地方特色了,比如我现在人在成都,前天去红旗连锁超市买水,发现它的门上贴了一个海报,说积分达到 2000 就可以预约一瓶茅台,积分达到 4000 可以预约两瓶茅台。这种小渠道更多的是靠自己去发现,相比京东一类的大平台,小渠道抢到的概率也更大一些。 + +在哪里抢茅台,怎么去抢的问题都解决了,还有一个钱的问题啊,毕竟需要的本金可是不少的啊!这个就更容易解决了,上面已经提到过信用卡了,去申请一个信用卡就可以轻松解决资金的问题了,而且一些联名卡还可以顺道解决平台会员的问题。 +信用卡除了资金的问题可以解决外,还会带来很多附加的价值,比如我今年一年的话费没有花自己一分钱,全是用平安银行信用卡的积分换的,还有每个月可以兑换的爱奇艺会员,虽然我一直没有用爱奇艺。比如我现在盖的被子、用的体重秤和收纳箱是用招商银行积分换的,找亲朋好友推荐办信用卡还有推荐礼,这也是一部分可以薅的羊毛,当然你愿意找我的话也是非常乐意的。 +这里还有一个问题,学生群体很多银行都是批不了信用卡的,我知道招商银行信用卡学生是可以申请的,信用卡的玩法有很多很多,那些羊毛尤其迎合了喜欢占小便宜的人性特点,如果玩的好的话免费坐坐飞机、住住酒店也是可以的。 +假设你现在抢到了茅台,怎么出手呢?身边那些卖酒的商铺、一些老板等等,也有很多人专门加价收购茅台,比如我自己一个大学同学,3000 抢了两瓶茅台,转手 4700 就被人家给买过去了,简直太抢手了。 + +上面的内容都是以茅台作为例子的,其它还有很多商品也基本是一样的套路,比如下面截图这个篮球。还有其它 Nike 鞋、纪念币这些都是一样的。写到这里一个普通人可以套利的回路差不多就完成了,基本就是一个手机就可以了,如果想扩大收益那就是多个账号一起来。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 扒一下网店代运营公司的套路 + +

+ +
+ + + + +
+ +
+ 前段时间,一个朋友给我发了一个抖音视频,视频是深圳龙岗公安发布的他们抓捕代运营诈骗公司的一些场景,我在龙岗政府在线网站上也看到了这个事件的新闻:9000元请人代运营,只换来几条教学视频,深圳网店主报警。有天傍晚,我独自坐在楼下公园的椅子上看书看电影,无意中被刚下班的牙科医生碰到了,他就坐了下来和我一起聊天,很自然就聊到了他的大众点评店铺的推广,告诉我他接到过好多代运营公司的电话,他自己差点就要给其中一个公司交钱了。 +咱们先来看一下在百度搜索「网店代运营」的结果,前五条全部都是广告。在抖音短视频信息流广告中,代运营的广告也是不计其数。其它像快手、神马搜索这些产品中也充斥这种代运营广告,这么庞大的广告投入背后一定是有暴利,我这里只是把我知道的代运营套路写出来,如果能让一个人止步骗局那就是没有白写的。 + +一般会去找代运营的商家基本都是新手,自己正为店铺没有销量而发愁,现在有家公司告诉你只需要每个月交多少运营费用,自己只负责客服和发货就行了(甚至客服都不用负责),看到市面上有这样好的服务你能不心动吗?而且他们还重点给你强调公司主要是靠运营店铺销售额的提点来盈利,在签合同的时候销售还故意在起提基数和标准上表现出很强硬的态度,这无疑会更加增加商家的信任,但这都不过是骗局的开始。 +找代运营的群体里面还有一大部分是想发展一个副业的白领、空闲时间比较多的大学生和宝妈,这部分人是很纯正的网店小白,不知道怎么注册网店(不需要多高级的搜索技巧,百度随便都能搜到),不知道怎么上架商品,自己没有货源不知道该怎么办,我这里拿没有货源来介绍一下代运营公司的销售话术。 +如果对方不知道「一件代发」这个事情,那么销售可能会这样说:“您这边没有货源没有关系,我们公司是做这个的,我们不仅对接了很多的优质厂家,而且我们自己也有大量的货源,都是可以免费为您对接货源的。”如果对方知道一件代发这个东西的话,那就直接给他说一件代发就好了。 +有了货源之后要卖什么产品呢?这个你是不是也很迷茫?好了,他们一般应该会给你做一场比较「专业」的图文并茂的市场分析,女性群体的基数大、网购行为多、她们的钱好赚,反正最终分析下来的结果就是卖女装很合适。注意,代运营公司对所有咨询如何开网店的客户都是这样说的,因为他们制定了一套非常标准的销售流程,那些话术只需要复制粘贴即可,反正最终都会引导到卖女装去。 + +经过一番折腾总算把网店开起来了,有一点良心的代运营公司还有专门的美工给你做一套主图和详情图,没良心的公司顶多就是在网上扒拉几张图片扔给你,甚至从网上不知道哪里卖的几节课程发给你。产品也都上架到店铺了,但只是上架商品的话店铺连访问量都没有,怎么可能会有销量呢? +这时他们的「运营师」就会给你出一套针对你店铺的运营方案了,和前面介绍的怎么引导到都卖女装是一样的套路,所有的客户都是同一套话术引导到店铺要继续做推广的方向上去,简单说就是得继续交钱!他们可能会这样说:“我们公司和百度、搜狐、头条等互联网公司都是有合作的,我们会支付给这些平台大量的钱帮助我们的店铺去发软文,这样您相比其它商家的优势就不仅仅只有淘宝(拼多多)平台内的流量了,其它像抖音、百度这些平台的流量都会进到您的店铺,您想一下这是多么庞大的流量和优势!” +实际上代运营公司只是专门针对你店铺内容写了一篇软文,然后再用百家号、头条号、搜狐号等发了几篇文章而已,这就是他们所谓的「和百度、搜狐、头条等互联网公司都是有合作的」。为了让你相信这些软文真的生效了,一般会用一些平台去给你的店铺引流,比如新韵网和小老弟网红助手,如果你相信了确实是他们的软文推广效果生效了,那你离下一次交钱的机会就不远了。 +除了这种软文投放的方式还有另外一种销售流程:“因为您的店铺是新店铺,那些基础数据都不好,我们这边可以通过技术手段帮您把店铺基础数据弄好,这样店铺的基础数据上去了之后就可以报官方的一些活动,获取到官方的流量扶持......”。这个在之前写的如何空手利用拼多多(淘宝)赚钱套利?中有介绍如何修改店铺销量,像店铺收藏、商品收藏等也是可以通过上面介绍的新韵网和小老弟网红助手来购买的,成本就几百甚至不到一百,但是可能收你几千甚至几万的服务费。 +这些代运营公司大部分都是在抖音上面投放的广告,体量大一些的公司会在百度投放广告。记得之前在刘鹏老师的星球看到一个投放广告的逻辑:先用极低的价格卖 A 罩杯内衣,然后向这批用户推丰胸产品。最近也看到一个代运营公司搞了个门槛很高的投放方式,自己开发了一个展示货源和网店课程的 APP,然后广告投放方式是推广这个 APP,这样就避免了和其它同行竞争表单还起不来量,而且会主动注册他们 APP 的人意向也比较大,起到了一次清洗客户的作用,听说他们这种 APP 推广的方式线索成本还不到二十,而大多数代运营公司的表单成本已经到了一百多。 + +上面说了那么多都是电销的模式,可能你会想自己实地去考察肯定就不会被骗了吧?现实情况是他们欢迎你到公司实地考察,他们有现成的店铺数据这些给你看,而且实地看到他们公司各个团队之间的协作可能会让你被骗的更深,面销会忽悠的更加伤人。 +可能很多人第一下想到的是报警吧,但实际上报警的作用不是多大,首先双方是有签署合同的,所以这只能算作经济纠纷不属于派出所管辖范围。即使公安局那边立案了,整个过程估计也会把你的耐心给磨没了,破案后你损失的金额能拿回来的可能性也不大。这里只告诉大家「消费者协会」和「市长热线」是比较值得信赖的。 +总还是觉得中国是人口基数太大了,七八亿的网民里面傻子实在太多了,那些天天只知道刷抖音,梦想着如何快速发财,梦想着如何不劳而获的人,正是代运营公司的潜在客户,人性都是贪婪的,销售稍微帮你放大一下你的贪婪你就输了。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 一个通过建网站赚钱的暴利项目(灰产勿碰) + +

+ +
+ + + + +
+ +
+ 这个项目的前提是你已经有了一个可以发布文章的网站,网站的主要就是收集投诉相关内容,如果你还不知道怎么去建一个网站,可以看我之前发的如何搭建一个属于自己的博客/企业网站,按照文中的方法建一个网站的成本投入不到 150 元。 +网站建起来了怎么去赚钱呢?相信大家都会想通过 SEO 把自己网站排名优化到足够高,然后利用谷歌联盟或是百度联盟赚广告费,没事的时候更新更新文章,只要保持一个比较好的更新频率,并且能长期坚持下来,那肯定是可以赚钱的,而且收入也会逐渐递增甚至可能呈现指数型增长。 +此时得承认我有点标题党了,这个快速赚钱的套路属于灰产,因为我作为证人协助警察叔叔抓获了一个做这件事情的站长。下面进入正题。 +现在有一些专门做维权的平台,比如聚投诉、新浪旗下的黑猫投诉等,可以进去看看这样的平台可能在维权方面起不到多大的作用,但是它随随便便就能上百度的首页啊。谁最害怕网络上的负面信息?肯定是被投诉公司啊!在信息时代,一条负面信息不知道要损失多少客户。 +我说的就是做一个类似的网站,首先网民都喜欢看这一类的内容,另外这样的网站也很容易进来访问量,有了访问量那么广告费就是一笔不菲的收入,比如你可以去搜「笑捧博客」看下里面的内容,我截了个图放在这里,先告诉你的是它最大的收入并不是广告费。 + +鉴于上面的信息都是网络上公开的内容,这里我就不打码了。可以看到这个网站里面的绝大部分内容都是 XXXXXXX公司-警惕,里面就是简单描述一下事情经过,然后放个合同的照片、再放几张聊天截图,而且这些内容都是受骗用户自己投稿的,完全不用花时间自己去创造内容。 +假设某客户早这个平台上面投诉了 A 公司,A 公司的人看到在百度轻易就能搜到不利于自己公司的信息,想要快速删除这样的内容怎么办?顶部特地留了个「联系站长」看见了吗?假设现在你就是这个站长,你可以像下面那样给对方回话: + +平台的内容都是用户自己发的,我这边会跟进用户投诉的进度,你这边有和客户协商处理的聊天记录吗?或者有给客户的退款记录也行,我去核实如果无误就把这篇帖子屏蔽掉。 + +都把客户逼到去网上发负面信息了,逼到客户去投诉公司了,基本上这样的公司不会有和客户协商处理的聊天记录,不到万不得已他们是不会给客户退款的,一般对方都会回答没有,此时你可以这样说: + +你需要马上处理这个帖子可以先交 600 元的押金,我这边先去后台设置不展示这篇内容,你那边抓紧时间去处理,处理好了联系我退换押金即可。 + +到这里就玩概率了,如果公司很快的就把这个事情处理了,那么对方找你这个站长退还押金也不能不给是不是?但就是有很多公司在一个月内都没有把这样的客户处理好,因为他们本身做的就是割韭菜项目,怎么会轻易退客户钱呢?过了一个月后可以这样说: + +当时给你说的是一个月内处理,与客户沟通处理的聊天截图、退款记录发给我,这边去删除帖子!时间有点久,而且后台设置的一个月就会清理一次数据,进程什么的都已经死了............ + +简单说就是各种扯皮各种赖,几百块钱对方应该也不会太在意,对方顶多骂你两句也拿你没什么办法,是不是轻轻松松的 600 元就到手了!!! +最后再次强调一点,这个被警察叔叔发现了是要来请你的,而且自己搭的网站没有官方授权,都知道投诉电话是 12315,你个人的一个博客网站凭什么能接收这种投诉?再去看看这个「笑捧博客」的服务器在香港,而且这个网站的所有内容最后更新时间是 2020 年 9 月份,知道为什么吗? +因为这个站长已经被公安局请进去了! + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/nui27fvUb/index.html b/nui27fvUb/index.html new file mode 100644 index 00000000..7d0e5434 --- /dev/null +++ b/nui27fvUb/index.html @@ -0,0 +1,469 @@ + + + + + + + + 幸福可以慢慢浇灌长大 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 幸福可以慢慢浇灌长大 +

+ + +
+ +
+

电影《点球成金》讲的是一个没有资金实力的弱队如何翻身的全程记录,在薪水预算只有强队 10% 的情况下如何去争夺冠军。与大多数体育片不一样的是,这部电影是从管理层入手的,推荐想要创业或是正在创业的人看一看,在明知道自己的做法是对的,但是又没有人支持、没有人看好的情况下,一个领导者应该怎么办?当明知开除某个球员就意味着他职业生涯的结束,甚至会将他推向生活窘迫的深渊,作为一个管理者是否会一时心软而手下留情......

+

电影中的彼得(耶鲁大学经济学硕士)采用了一种全新的方法(数据分析)挑选球员,当然这种方法现在看来已经见怪不怪了,军事运筹学中也使用了相当的方法来计算如何赢得一场战斗。把这样的方法应用到自己行业的难点在于「量化」,我觉得企业管理者在这方面可以多思考思考。

+

《原则》一书中作者也提到类似的点,将自己平时做决策的过程量化、抽象,将其转换成计算机可以理解的模型,计算机是没有那些乱七八糟的情绪的,只要把模型抽象的足够好就可以降低自己的成本,还能提高容错性。书中提到了对员工采用了性格测试进行分工,我现在是比较认可通过这种方式来决定员工具体工作的,想起来以前参加校园招聘的时候,一度认为那些大企业给员工做性格测试是不是钱多的没处花了,现在想想还是自己太傻。

+

出于好奇我也给自己做了个 MBTI 测试,发现我是「表演者」人格类型,了解了一点关于我自己性格上的缺点,一些无伤大雅的问题大可以听之任之,一些需要改进的地方我相信也是可以通过持久的努力改变的。我目前对性格测试也有些怀疑,但我很清楚它比星座说「你是个渴了就会喝水的人」更具科学性一些。

+
+

我特别羡慕的两个人是读库的创始人老六和自己上大学时遇到的花儿,还不知道老六的朋友可以花一两分钟翻一下为啥人人都爱《读库》的老六?在我看来老六做的东西已经不叫工作了,而是叫事业了,不知道我这一生能不能在某个时间节点达到那样的状态,或是更可能这一生都达不到。刚好前几个小时看完了读库 2020 年(北京站)的视频,老六提到了六个问题应该是大多数人都存在的,比如「没有应变能力的坚持」、「不给时间压力便遥遥无期」,点击读库年会(北京站)完整版视频可以观看。

+ +

我第一次遇到花儿是在上大学的时候,那时候她还在成都宽窄巷子做青旅,几次到成都我都是到她那个青旅住下,因为在那里总是能遇到很多有趣的人。大概在大二快结束的时候我看到花老板发了朋友圈,才知道她去到大理做青旅了,也是通过朋友圈知道了她还有一个公众号,现在主要是通过花老板的公众号观察她的生活。我羡慕花儿那种把日子过的很幸福的状态,各方面细节可以都可以看到花儿是一个很知足的人,写歌、作曲、唱歌,日子看起来好不快活,花儿的公众号名称叫「微小而确实的日常」。

+

日本的杂物管理咨询师山下英子写了一本《断舍离》,从 20 年 1 月看到这本书开始,我一直都在学习和实践断舍离,我个人觉得断舍离的人生整理理念是很受用的,我们的人生就应该多尝试断舍离,更多的东西只会让自己的脑袋更像浆糊,当然这可能和商业行为有些背离。

+

商业广告会告诉你他们研发了一种减肥药或是减肥茶的产品,使用之后就能快速的瘦身塑形,但仔细想想要瘦下来不应该是减少碳水化合物的摄入吗?为什么要一边给自己打针又一边狂吃高胆固醇的食品呢?已经看不下去的书、已经挽回不了的爱情这些不都应该舍弃吗?商业广告会告诉你需要做加法,但其实你真正应该做的是减法。

+
+

从学校毕业已经快要有 3 年了,真正开始积累社会经验是 20 年下半年,大胆在这里分享自己学到的一些东西。看一个人靠谱不靠谱不要听他的嘴巴怎么说,而是要看他怎么做事、怎么做人、别人对他如何评价的。如果想要看一个人的能力如何,看看他之前干成过什么事就可以了,干成的事情是最好的佐证。如果他老是强调自己是多么的可靠,自己的实力是多么的强劲,那他基本就是个不可靠且没啥实力的人。

+

一些文章会用「众所周知」、「有研究表明」、「很多学者指出」、「我身边有很多例子」这样的开头来证明某个观点,那大概率文章中内容是在瞎扯淡,如果要你要反驳的话他还能举出来更多的例子,不与这样的作者进行争辩是比较节约时间的。想了一下这个问题以前在我身上也挺严重的,现在已经减轻很多了。

+

找工作的时候看一个公司靠谱不靠谱可以看给你缴的社保多少,比如公司实际给你发的薪资是一万,但是缴纳社保的基数却填个 3500,说到底就是公司舍不得给员工花钱,舍不得给员工花钱的公司你还想从他那里得到点什么呢?同样那种挖空心思避税的公司也不值得去,会和税务机关玩猫腻的公司难道还差和员工玩猫腻的胆子吗?我遇到过这种公司,工资分几笔发到手就是为了避税。

+
+

春节回老家发现了一个有趣的现象,长辈们手机里面必备的两个应用是微信和抖音,他们大多数都很愿意在抖音上面分享自己的生活,即使拍的作品土得掉渣也非常乐于「分享美好生活」。长辈之间有很多许久没有联系的儿时玩伴或是亲戚,有一些都超过 10 年没有联系过了,甚至连对方长什么样子都不太记得了。

+

不得不承认抖音的推荐算法确实很牛,十多年没有产生联系的朋友抖音能帮你找到,再通过抖音私信互换微信,老友居然就这样再次取得了联系。用微信打个视频电话问候一下老友,通过了解对方正在做的事情还能缩小信息差,这样的聊天还能让他们的心情变得更好。

+

做过一段的信息流广告,真真切切体会到了「如果你没有花钱买产品,那你就是被卖的产品」,这是纪录片《监视资本主义:智能陷阱 The Social Dilemma》中的一句话,以前在阅读高于自己的作品,远离精神毒品中批评过抖音、快手一类的产品,但现在我得改一点这个观念,50 岁以上的人没事时候刷刷抖音挺不错的,年轻人还是别去当被卖的产品了。

+
+

在深圳有一次和老叔吃饭的时候他讲了一点自己的经历,我已经记不得他年轻时候在哪个公司了,当时他请客在阳光酒店(深圳第一家五星级酒店)吃饭,只需要签个字就行了,那时候非常的牛气。他说正是那个时候把他害了,自己得意忘形不思进取,不然他现在也不至于都 60 多了还要给老板低身下气的工作。

+

另一个老叔也分享了他年轻时候在湖北一个国企工作的经历,职位很高且收入不低,非常宠爱自己的小儿子。现在儿子要买车子直接给他打电话:“老爸,我要买车你给我多少钱?”他说自己没有钱,儿子回了句:“那你没有钱还有什么用?”

+

过年回家姨夫告诉我他前面几十年都活的迷迷糊糊的,一直快到 50 岁了才找到自己的方向。他说人知足而且有某个东西能让自己自信,就会过的比较幸福,自信不应该来源于金钱,要懂得赞美别人,要明白女人也是很幸苦的。我惊讶的发现姨夫作为一个大学生眼里瞧不起的农民工,已经比大学生高出好几个 level 了,而且他的收入也已经比 996 的程序员高出几个 level 了。

+

最后推荐一个轻松的视频吧,一席的演讲视频杨小峰:寻找昆虫,里面不仅有很多精美可爱的昆虫照片,还有很多大开脑洞的玩味,而且这个老师有脱口秀的那味道。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/oNwbCz1HH/index.html b/oNwbCz1HH/index.html new file mode 100644 index 00000000..0c26acb4 --- /dev/null +++ b/oNwbCz1HH/index.html @@ -0,0 +1,551 @@ + + + + + + + + 深入理解计算机系统——信息的表示与处理 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 深入理解计算机系统——信息的表示与处理 +

+ + +
+ +
+
+

参考内容:
+《深入理解计算机系统》(第三版)

+
+

字数据大小

+

前面已经提到过信息=位+上下文,但是基本上的计算机都没有将位作为最小的可寻址单位,而是将字节作为了最小的可寻址单位,内存就是一个非常大的字节数组,它的的每个字节都由一个唯一的数字来标识(这个数字是不需要存的),所有可能的地址集合就是虚拟地址空间。

+

我们常说的 32 位、64 位指的是一台计算机的字长,用于指明指针数据的的标称大小。有的面试官在面试的时候会问这样一个问题:在 C/C++ 中指针的大小是多少?如果你一下就回答出来时多少个字节了,那基本上不必再问了,因为一个指针的大小取决于计算机的字长,所以应该分 32 位机还是 64 位机的情况。

+

字长还会决定一个极为重要的系统参数——虚拟地址空间。比如现在有一个 32 位机,每一位可以取值 1 或 总共 32 位,能组合的出局就有 232 个,所以它能访问 232 个地址,其大小也就是 4G,因此你如果给 32 位机装上 8G 的内存条,是起不了多大作用的。

+

我们平时所说的 32 位程序和 64 位程序并不是指机器的字长,它们的区别在于程序时如何编译的,而不是其运行的机器类型,高版本都应该做到向后兼容,所以 32 位程序一般都能运行在 64 位机器上,而 64 位程序时不能运行在 32 位机上面的。下面两种伪指令就分别用于编译 32 位程序和 64 位程序。

+
gcc -m32 prog.c
+gcc -m64 prog.c
+
+

C 语言在 32 位机和 64 位机上所表现的差别在于long数据类型,一般在 32 位机上是 4 个字节,而在 64 位机上是 8 个字节,而作为程序员要力图程序能在不同的机器上进行编译执行,要做到这一点就需要保证程序对不同数据类型的确切大小不敏感。

+
+

曾经某运营商的一个基站版本因为数据范围的不同而造成了巨大的损失,在编程环境中使用的是 32 位机,而基站所使用的处理器没有 32 位,最后表现的效果就是大概每隔 40 天,基站就自动复位了。定位到这个问题都花费了巨大的财力和人力资源。

+
+

寻址及字节顺序

+

上文已经提到,有很多的对象实际上不止占用一个字节,而是占用了多个字节,此时就涉及到如何排列这些字节了,以及如何存储这些字节。以11001100 11001100为例,它占用了两个字节,我们可以选择将这两个字节放在连续的内存中,也可以将两个字节分开放在不连续的内存中;另外我们可以将左边的字节当做起始位置,也可以将右边的字节当做起始位置(更专业的称为大端法和小端法)。

+

对于字节的排列,到底是用大端法还是小端法,没有技术上的争论,只有社会政治论题的争论,而且机器它对程序员是完全不可见的。几乎所有的机器都将多字节对象存储为连续的字节序列,所使用字节中最小的地址作为对象的地址。

+

那么什么时候需要注意字节的顺序规则呢,那就是编写网络应用程序的时候,试想你传输的数据是用大端法表示的,而用户的计算机采用的是小端法,那还会有用户使用你的产品吗。所以编写网络程序时需要遵循已经建立的关于字节顺序的规则。

+

整数表示

+

程序员对二进制不会不知道,比如 11111111表示的是 255(不考虑补码),很容易就能转换为我们所熟悉的 10 进制数据。这种方式我们默认它是无符号数,如果要加入有符号数就开始变得有趣了。

+

几乎所有的计算机都是采用有补码来表示有符号整数的,它与无符号整数的区别在于最高位被解释为负权,举个例子:将1111看做补码的话,它的值就为:-23 + 22 + 21 + 20 = -1。

+

在程序中不可避免的会使用强制类型转换,C 语言中强制类型转换并没有改变数据的位值,只是改变了解释这些位的方式。比如将无符号数(unsigned) 53191 转换为有符号数的结果为 -12345,它们的位值是完全没有相同的。

+

最容易入坑的地方是,对两个不同类型的数据进行运算时,C 语言将会隐式的将有符号数转换为无符号数,所以就有下面这样一个神奇的结果。

+
// u 代表无符号数
+-1 < 0u
+// 结果为 0
+// 因为 -1 的补码表示为:11...11
+// 转换为无符号数后就是范围内最大的数
+
+

如果需要扩展一个数的位表示,那么放心的扩展就好了,小的数据类型都能安全的向大的数据类型转换,补码表示的数会在前面补上符号位,原码表示的直接在前面补上 0 即可,而需要注意的是从大往小转,这会不可避免的截断位,造成信息的丢失,所以千万不要这么干。

+

加法、乘法运算

+

在编程入门的时候可能都知道两个正数相加的结果可能为负数,还有一个更奇怪的现象就是:x < yx - y < 0两个表达式可能会得出不一样的结果,这些神奇的结果都和计算机整数的底层表示和运算有着密切的关系。

+

C 语言中有无符号数与有符号数之分,而在 Java 中只有有符号数,下面的内容还是基于 C 语言进行说明,毕竟更 C 比 Java 更接近底层嘛。

+

无符号加法

+

假设我们使用 w 位来表示无符号数,那么两个加数取值范围即为:0 ≤ x, y <2w,理论上它们的和的范围为:0 ≤ sum < 2w+1,因为只有 w 位表示无符号数(要把和表示出来就需要 w+1 位),所以超过 zw的部分就会造成溢出,如下图所示。

+
+

对于无符号数的溢出,计算机采用的处理方式是丢掉最高位,直观的结果就是,当发生溢出了,就将采用取模运算(或者说是减去 2w),举个例子。

+

只用 4 为来表示无符号数,即 w = 4,现在有 x [1001] 和 y [1100] 相加,其结果应为:[10101] ,但是没有 5 位用来表示,所以丢掉最高位的1,剩下的值为 5 [0101],也就是 21 mod 16 = 5。

+

那么如何检测是否发生溢出呢?设求和结果为 s,对于加法有 x + y ≥ x 恒成立,即只要没有发生溢出,肯定有 s ≥ x。另一方面,如果确实发生溢出了,就有 s = x + y - 2w,又有 y - 2w < 0,因此 s = x + y - 2w < x。

+

补码加法

+

和前面一样,对于两个给定范围的加数 - 2w-1 ≤ x, y ≤ 2w-1 - 1,它们的和的范围就在 - 2w ≤ sum ≤ 2w - 2。要想把整个和的范围表示出来,依旧需要 w+1 位才行,而现在只有 w 位,因此还是需要采用将溢出部分截断。

+
+

可以发现,当发生正溢出时,截断的结果是从和数中减去了 2w;而当发生负溢出时,截断结果是把和数加上 2w

+

那么对于补码加法如何检测溢出结果呢?通过分析可以发现,当且仅当 x > 0, y > 0,但和 s ≤ 0 时为正溢出;当且仅当 x < 0, y < 0,但 s ≥ 0 时发生负溢出。

+

无符号乘法

+

有了前面的基础,乘法就变得简单一些了,对于溢出情况,计算机仍然采用的是求模,比如 0 ≤ x, y ≤ 2w - 1,它们乘积的范围为 0 到 22w - 2w+1 + 1 之间,这可能需要 2w 位来表示,溢出部分直接截掉,如下所示。

+
+

补码乘法

+

对于补码,两个乘数的范围为:- 2w-1 ≤ x, y ≤ 2w-1 + 1,那么其乘积表示范围就为 - 22w-2 + 2w-1 到 22w-2 之间,补码乘法和无符号乘法基本是一样的,只是在无符号基础上多加了一步转换,即将无符号数转换为补码。

+
+

乘以常数

+

我们知道,计算机做加减法、位级运算的速度最快(1 个指令周期),而做乘除法很慢(10 个甚至更多指令周期),平时编写的程序中常常会乘以一个常数,为了使程序运行的更快,编译器可能会帮我们做一些处理。

+

首先我们考虑常数是 2 的幂。x * 21 可以表示为 x << 1,x * 22 可以表示为 x << 2,依次类推即可。

+

对于不是 2 的幂的常数,比如 x * 14 可以表示为:(x<<3) + (x<<2) + (x<<1),因为 14 = 23 + 22 + 21;聪明的你可能发现 14 还有另一种表示方法,即 14 = 24 - 21,这种表示比前一种表示方法又少了运算量,所以 x * 14 还可以表示为:(x<<4) - (x<<1)

+

实际上,这里有一个通用的解决方案,对于任何一个常数 K,其二进制可以表示为一组 0 和 1 交替的序列:[(0...0)(1...1)(0...0)(1...1)],14可以表示为:[(0...0)(111)(0)],考虑一组从位位置 n 到位位置 m 的连续的 1 (n ≥ m),(对于 14 有 n = 3,m = 1)可以有两种形式来计算位对乘积的影响。

+
+

这个优化不是一定的,大多数编译器只在需要少量移位、加减法就足够的时候才使用这种优化。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/ovndYzDa7N/index.html b/ovndYzDa7N/index.html new file mode 100644 index 00000000..1c63ef9f --- /dev/null +++ b/ovndYzDa7N/index.html @@ -0,0 +1,1499 @@ + + + + + + + + JavaScript | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + JavaScript +
+ + +
+

+ + Vue 入门避坑——Vue + TypeScript 项目起手式 + +

+ +
+ + + + +
+ +
+ 在此前我使用的前端框架是 Angular,使用过 TypeScript 后你就会讨厌 JS 了,我学习 Vue 时的最新版本是 2.5,相信大部分同学都不会认为 Vue 那样又细又长的代码很美观吧,简单看了一些网络博客后,我毅然决然引入了 TypeScript 进行开发,本文仅整理记录我自己遇到的一些坑。 +使用 Cli +脚手架是一个比较方便的工具,这里需要注意的是@vue/cli和vue-cli是不一样的,推荐使用npm i -g @vue/cli安装。 +安装完成后,可以直接使用vue create your-app创建项目,你可以选择使用默认配置亦或是自己手动选择配置,按提示一步一步向下走即可,它会根据你的选择自己创建比如tsconfig.json等等配置文件。这里推荐使用less开发样式,sass老是在安装的过程中出问题。 +当然你也可以使vue ui命令启动一个本地服务,它是一个 Vue 项目管理器,提供了一个可视化的页面供你管理自己的项目,它的样子如下图所示,还是比较清新的。 + +使用 vue-property-decorator +Vue 官方维护了 vue-class-component 装饰器,vue-property-decorator 则是在vue-class-component基础上增强了更多结合Vue特性的装饰器,它可以让 Vue 组件语法在结合了 TypeScript 语法后变得更加扁平化。 +截止本文时间,vue-property-decorator共提供了 11 个装饰器和 1 个Mixins方法,下面用@Prop举个例子,是不是看起来引起极度舒适。 +import { Vue, Component, Prop } from 'vue-property-decorator' + +@Component +export default class YourComponent extends Vue { + @Prop(Number) readonly propA: number | undefined + @Prop({ default: 'default value' }) readonly propB!: string + @Prop([String, Boolean]) readonly propC: string | boolean | undefined +} + + +// 上面的内容将会被解析成如下格式 + +export default { + props: { + propA: { + type: Number + }, + propB: { + default: 'default value' + }, + propC: { + type: [String, Boolean] + } + } +} + +使用 Vuex +关于怎么使用Vuex此处就不再做过多说明了,需要注意的一点是,如果你需要访问$store属性的话,那么你必须得继承Vue类,坑的地方是在某些情况下即使你没有继承Vue,它也能通过编译,只有在程序运行起来的时候才报错。 +class ExampleApi extends Vue { + + public async getExampleData() { + if (!this.$store.state.exampleData) { + const res = await http.get('url/exampleData'); + if (res.result) { + this.$store.commit('setExampleData', res.data); + return res.data; + } else { + promptUtil.showMessage('get exampleData failed', 'warning'); + } + } else { + return this.$store.state.exampleData; + } + } +} + +使用自己的配置(含代理) +vue.config.js是一个可选的配置文件,如果项目的根目录中存在这个文件,那么它会被@vue/cli-service自动加载,它的配置项说明可以查看配置参考。 +我们再开发过程中都会使用代理来转发请求,代理的配置也是在这个文件中,它的官方说明在devserver-proxy中,下面是一个简单的vue.config.js文件例子。 +module.exports = { + filenameHashing: true, + outputDir: 'dist', + assetsDir: 'asserts', + indexPath: 'index.html', + productionSourceMap: false, + transpileDependencies: [ + 'vue-echarts', + 'resize-detector' + ], + devServer: { + hotOnly: true, + https: false, + proxy: { + &quot;/statistics&quot;: { + target: &quot;http://10.7.213.186:3889&quot;, + secure: false, + pathRewrite: { + &quot;^/statistics&quot;: &quot;&quot;, + }, + changeOrigin: true + }, + &quot;/mail&quot;: { + target: &quot;http://10.7.213.186:8888&quot;, + secure: false, + changeOrigin: true + } + } + } +} + +让 Vue 识别全局方法和变量 +我们在项目中都会使用一些第三方 UI 组件,比如我自己就使用了 Element,但是在使用它的$message、$notify等方法时就直接报错了,究其原因就是$message等属性并没有在 Vue 实例中声明。 +官方对此给出了很明确的解决方案,使用的是 TypeScript 的 模块补充特性,可以查看增强类型以配合插件使用。既然知道是因为没有声明导致的错误,那我们就给它声明一下好了,在src/shims-vue.d.ts文件中添加如下代码即可,如果没有该文件请自行创建。 + +看到网上也有一部分人说的是src/vue-shim.d.ts,反正不管是怎么命名这个文件的,它们的作用是一样的。 + +declare module 'vue/types/vue' { + interface Vue { + $message: any, + $confirm: any, + $prompt: any, + $notify: any + } +} + +这里顺道提一下,src/shims-vue.d.ts文件中的如下代码是为了让你的 IDE 明白以.vue结尾的文件是什么玩意儿。 +declare module '*.vue' { + import Vue from 'vue'; + export default Vue; +} + + +路由懒加载 +Vue Router 官方有关于路由懒加载的说明,但不知道为什么官方给的这个说明在我的项目里面都没有生效,但使用require.ensure()按需加载组件可以生效。 +// base-view 是模块名,写了相同的模块名则代码会被组织到同一个文件中 +const Home = (r: any) =&gt; require.ensure([], () =&gt; r(require('@/views/home.vue')), layzImportError, 'base-view'); + +// 路由加载错误时的提示函数 +function layzImportError() { + alert('路由懒加载错误'); +} + +上面的方式会在编译的时候把文件自动分成多个小文件,编译后的文件会以你自己命名的模块名来命名,如果代码之间有相互依赖,依赖部分代码编译后的文件会以两个模块名相连后进行命名。 +但是需要注意的是,这样拆分小文件之后引入了另外一个新的问题,因为客户端会缓存这些编译后的 js 文件,如果功能 A 同时依赖了a.js和b.js两个文件,但用户在使用其它功能时已经把a.js缓存到本地了,使用功能 A 时需要请求b.js文件,这时程序就很容易报错,因为此时在客户端这两个文件不是同一个版本,所以可能导致a.js调用b.js中的方法已经被删了,进而导致客户端页面异常。 +关于引入第三方包 +项目在引入第三方包的时候经常会报出各种奇奇怪怪的错误,这里仅提供我目前找到的一些解决办法。 +/* + 引入 jquery 等库可以尝试下面这种方式 + 只需要把相应的 js 文件放到指定文件夹即可 +**/ +const $ = require('@/common/js/jquery.min.js'); +const md5 = require('@/common/js/md5.js'); + +引入一些第三方样式文件、UI 组件等,如果引入不成功可以尝试建一个 js 文件,将导入语句都写在 js 文件中,然后再在main.ts文件中导入这个 js 文件,这个方法能解决大部分的问题。例如我先建了一个lib.js,然后在main.ts中引入lib.js就没有报错。 +// src/plugins/lib.js +import Vue from 'vue'; + +// 树形组件 +import 'vue-tree-halower/dist/halower-tree.min.css'; +import {VTree} from 'vue-tree-halower'; +// 饿了么组件 +import Element from 'element-ui'; +import 'element-ui/lib/theme-chalk/index.css'; +// font-awesome 图标 +import '../../node_modules/font-awesome/css/font-awesome.css'; +import VueCookies from 'vue-cookies'; +import VueJWT from 'vuejs-jwt'; + +Vue.use(VueJWT); +Vue.use(VueCookies); +Vue.use(VTree); +Vue.use(Element); + + +// src/main.ts +import App from '@/app.vue'; +import Vue from 'vue'; +import router from './router'; +import store from './store'; +import './registerServiceWorker'; +import './plugins/lib'; + +Vue.config.productionTip = false; + +new Vue({ + router, + store, + render: (h) =&gt; h(App), +}).$mount('#app'); + +因为第三方包写的各有特点,在引入不成功的时候基本也只能是见招拆招,当然如果你的功底比较深厚,你也可以自己写一个index.d.ts文件,实在不行的话,那个特殊的组件不使用 TypeScript 来写也能解决,我目前还没有找一个可以完全解决第三方包引入错误的方法,如果您已经有相关的方法了,希望能与你一起探讨交流。 + +
+ + Read More ~ +
+
+
+ +
+

+ + JavaScript 进阶知识、技巧 + +

+ +
+ + + + +
+ +
+ 对象 +Js 共有number、string、boolean、null、undefined、object六种主要类型,除了object的其它五中类型都属于基本类型,它们本身并不是对象。但是null有时会被当做对象处理,其原因在于不同的对象在底层都表示为二进制,在 js 中二进制前三位都为 0 的话就会被判定为object类型,而null的二进制表示全是 0, 所以使用typeof操作符会返回object,而后续的 Js 版本为了兼容前面埋下的坑,也就没有修复这个 bug。 +&quot;I'm a string&quot;本身是一个字面量,并且是一个不可变的值,如果要在这个字面量上执行一些操作,比如获取长度、访问某个字符等,那就需要将其转换为String类型,在必要的时候 js 会自动帮我们完成这种转换,也就是说我们并不需要用new String('I'm a string')来显示的创建一个对象。类似的像使用42.359.toFixed(2)时,引擎也会自动把数字转换为Number对象。 +null和undefined没有对应的构造形式,它们只有文字形式。相反,Date只有构造,没有文字形式。对于Object、Array、Function和RegExp(正则表达式)来说,无论使用文字形式还是构造形式,它们都是对象,不是字面量。 +Array 类型 +数组类型有一套更加结构化的值存储机制,但是要记住的是,数组也是对象,所以有趣的是你也可以给数组添加属性。 +var myArray = [&quot;foo&quot;, 42, &quot;bar&quot;]; +myArray.baz = &quot;baz&quot;; +myArray.length; // 3 +myArray.baz; // &quot;baz&quot; + +数组类型的length属性是比较有特点的,它的特点在于不是只读的,也就是说你可以修改它的值。因此可以通过设置这个属性从数组末尾删除或添加新的项。 +var colors = [&quot;red&quot;, &quot;blue&quot;, &quot;green&quot;]; +colors.length = 2; +console.info(colors[2]); // undefined +colors.length = 4; +console.info(colors[4]); // undefined +// 向后面追加元素 +colors[colors.length] = &quot;black&quot;; + +数组还有一些很方便的迭代方法,比如every()、filter()、forEach()、map()、some(),这些方法都不会修改数组中包含的值,传入这些方法的函数会接收三个参数:数组项的值、该项在数组中的位置、和数组对象本身。 +Function 类型 +在 ECMAScript 中,每个函数都是Function类的实例,而且都与其它引用类型一样具有属性和方法。由于函数时对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。 +在函数的内部有两个特殊的对象,this和arguments。arguments对象有callee和caller属性。caller用来指向调用它的function对象,若直接在全局环境下调用,则会返回null;callee用来指向当前执行函数,所以我们可以通过下面的方式来实现阶乘函数。 +function factorial(num) { + if (num &lt;= 1) { + return 1; + } else { + return num * arguments.callee(num-1); + } +} + +每个函数都包含两个非继承而来的方法,apply()和call(),这两个方法都是在特定作用域中调用函数,实际上等于设置函数体内this对象的值。首先,apply()方法接收两个参数,一个是在其中运行函数的作用域,另一个是参数数组,其中第二个参数可以是Array的实例,也可以是arguments对象。call()方法与apply()方法的作用相同,它们的区别仅仅在于接收参数的方式不同,在使用call()方法时必须逐个列举出来。 +window.color = &quot;red&quot;; +var o = {color: &quot;blue&quot;}; +function sayColor() { + console.info(this.color); +} +sayColor(); // red +sayColor.call(this); // red +sayColor.call(window); // red +sayColor.call(o); // blue +sayColor.apply(o); // blue + +需要注意的是,在严格模式下未指定环境对象而调用函数,则this值不会转型为window,除非明确把函数添加到某个对象或者调用apply()或call()。 +安全的类型检查 +Js 内置的类型检查机制并不是完全可靠的,比如在 Safari(第5版前),对正则表达式应用typeof操作符会返回function;像instanceof在存在多个全局作用域(包含 frame)的情况下,也会返回不可靠的结果;前文提到的 Js 一开始埋下的坑也会导致类型检查出错。 +我们可以使用toString()方法来达到安全类型检查的目的,在任何值上调用Object原生的toString()方法都会返回一个[object NativeConstructorName]格式的字符串,下面以检查数组为例。 +Object.prototype.toString.call([]); // &quot;[object Array]&quot; +function isArray(val) { + return Object.prototype.toString.call(val) == &quot;[object Array]&quot;; +} + + +作用域安全的构造函数 +构造函数其实就是一个使用new操作符调用的函数,当使用new操作符调用时,构造函数内用到的this对象会指向新创建的对象实例,比如我们有下面的构造函数。 +function Person(name, age) { + this.name = name; + this.age = age; +} + +现在的问题在于,要是我们不使用new操作符呢?会发生什么! +let person = Person('name', 23); +console.info(window.name); // name +console.info(window.age); // 23 + +很明显,这里污染了全局作用域,原因就在于没有使用new操作符调用构造函数,此时它就会被当作一个普通的函数被调用,this就被解析成了window对象。我们需要将构造函数修改为先确认this是否是正确类型的实例,如果不是则创建新的实例并返回。 +function Person(name, age) { + if (this instanceof Person) { + this.name = name; + this.age = age; + } else { + return new Person(name, age); + } +} + +高级定时器 +大部分人都知道使用setTimeout()和setInterval()可以方便的创建定时任务,看起来好像 Js 也是多线程的一样,实际上定时器仅仅是计划代码在未来的某个时间执行,但是执行时机是不能保证的。因为在页面的生命周期中,不同时间可能有其它代码控制着 JavaScript 进程。 +这里需要注意一下setInterval()函数,仅当没有该定时器的任何其他代码实例时,Js 引起才会将定时器代码添加到队列中。这样可以避免定时器代码可能在代码再次被添加到队列之前还没有完成执行,进而导致定时器代码连续运行好几次的问题。但是这也导致了另外的问题:(1)某些间隔会被跳过;(2)多个定时器的代码执行之间的间隔可能会比预期小。 +假设某个click事件处理程序使用setInterval()设置了一个 200ms 间隔的重复定时器。如果这个事件处理程序花了 300ms 多的时间完成,同时定时器代码也花了差不多了的时间,就会同时出现跳过间隔切连续运行定时器代码的情况。 +为了避免setInterval()的重复定时器的这两个缺点,我们可以使用如下模式的链式setTimeout(),代码一看就懂什么意思了。 +setTimeout(function() { + // 处理中 + setTimeout(arguements.callee, interval); +}, interval) + +消息队列与事件循环 +如下图所示,左边的栈存储的是同步任务,就是那些能立即执行、不耗时的任务,如变量和函数的初始化、事件的绑定等等那些不需要回调函数的操作都可归为这一类。 + +右边的堆用来存储声明的变量、对象。下面的队列就是消息队列,一旦某个异步任务有了响应就会被推入队列中。如用户的点击事件、浏览器收到服务的响应和setTimeout中待执行的事件,每个异步任务都和回调函数相关联。 +JS引擎线程用来执行栈中的同步任务,当所有同步任务执行完毕后,栈被清空,然后读取消息队列中的一个待处理任务,并把相关回调函数压入栈中,单线程开始执行新的同步任务。 +来看个例子:执行下面这段代码,执行后,在 5s 内点击两下,过一段时间(&gt; 5s)后,再点击两下,整个过程的输出结果是什么? +setTimeout(function(){ + for(var i = 0; i &lt; 100000000; i++){} + console.log('timer a'); +}, 0) +for(var j = 0; j &lt; 5; j++){ + console.log(j); +} +setTimeout(function(){ + console.log('timer b'); +}, 0) +function waitFiveSeconds(){ + var now = (new Date()).getTime(); + while(((new Date()).getTime() - now) &lt; 5000){} + console.log('finished waiting'); +} +document.addEventListener('click', function(){ + console.log('click'); +}) +console.log('click begin'); +waitFiveSeconds(); + +首先,先执行同步任务。其中waitFiveSeconds是耗时操作,持续执行长达 5s。然后,在 Js 引擎线程执行的时候,'timer a'对应的定时器产生的回调、'timer b'对应的定时器产生的回调和两次 click 对应的回调被先后放入消息队列。由于 Js 引擎线程空闲后,会先查看是否有事件可执行,接着再处理其他异步任务,最后,5s 后的两次 click 事件被放入消息队列,由于此时 Js 引擎线程空闲,便被立即执行了。因此会产生下面的输出顺序。 +0 +1 +2 +3 +4 +click begin +finished waiting +click +click +timer a +timer b +click +click + + +
+ + Read More ~ +
+
+
+ +
+

+ + 深入理解 JavaScript——变量提升与作用域 + +

+ +
+ + + + +
+ +
+ +参考内容: +lhs rhs是啥意思 +《Javasript 高级程序设计(第三版)》 +《你不知道的 JavaScript(上卷)》 + +几乎所有的编程语言都能够存储变量当中的值,并且可以在之后对该值进行访问或修改。很明显需要一套良好的规则来存储这些变量,并且之后可以方便的找到这些变量,这套规则我们称之为作用域。 +编译原理 +我们一般把 js 归为「动态」或「解释执行」语言,但是它也会经历编译阶段,不过它不像传统语言那样是提前编译的,它的编译发生在代码执行前的几微秒内。 +传统语言在执行之前会经历三个步骤:分词/词法分析、解析/语法分析、代码生成,关于这三个步骤的具体工作,可以查看编译原理相关的文献,我们可以把这三个步骤统称为编译。不过 js 引擎要复杂的多,它会在编译的时候对代码进行性能优化,尽管给 js 引擎优化的时间非常少,但是它用尽了各种办法来保证性能最佳。 +我们需要先了解三个名词。引擎:从头到尾负责整个 js 程序的编译及执行过程;编译器:负责词法分析及代码生成;作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。 +var a = 2;,我们以这段程序为例,它首先声明了变量a,然后将2赋值给变量a。前一个阶段在编译器处理,后一个阶段由 js 引擎处理。 +变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。 +变量提升 +用过 js 的人都知道 js 存在变量提升,那么它到底是如何提升的呢?我们看下面的一段代码 +console.log(a); +var a = 2; + +上述代码在a声明之前访问了变量a,按我们的逻辑它应该会抛出 ReferenceError 异常;或是变量提升直接输出 2。但是这两种答案都不对,输出的是undefined。 +回顾一下前文的关于编译的内容,引擎会在解释 js 代码之前对其进行编译,编译阶段的一个重要工作就是找到所有的声明,并用合适的作用域将它们关联起来,包括变量和函数在内的所有声明都会在任何代码被执行之前首先被处理。所以我们前面列出来的代码实际上会变成下面这个样子。 +var a; +console.log(a); +a = 2; + +这个过程就好像变量和函数声明会从它们的代码中出现的位置被移动到最上面一样,这个过程就是提升。但是需要注意的是,函数声明会首先被提升,然后才是变量提升。 +foo(); // 1 +var foo; + +function foo() { + console.info(1); +} + +foo = function() { + console.info(2); +} + +这段代码输出 1 而不是 2 ,它会被引擎理解为下面的形式。 +function foo() { + console.log(1); +} + +foo(); // 1 + +foo = function() { + console.log(2); +}; + +可以看到,虽然var foo出现在function foo()之前,但是它是重复的声明,因此会被忽略掉,因为函数函数声明会提升到普通变量前。所以在在同一个作用域中进行重复定义是一个很糟糕的做法,经常会导致各种奇怪的问题。 +LHS 和 RHS 查询 +LHS 和 RHS 是数学领域内的概念,意为等式左边和等式右边的意思,在我们现在的场景下就是赋值操作符的左侧和右侧。当变量出现在赋值操作符的左边时,就进行 LHS 查询;反之进行 RHS 查询。 +RHS 查询与简单的查找某个变量的值没什么区别,它的意思是取得某某的值。而 LHS 查询则是试图找到变量容器的本身,从而可以对其进行赋值。 +console.info(a);我们深入研究一下这句代码。这里对a的引用是 RHS 引用,因为这里a并没有赋予任何值,相应的需要查找并取得a的值,这样才能传递给console.info()。 +a = 2;对a的引用则是一个 LHS 引用,因为实际上我们并关心a当前的值是什么,只是想为= 2这个赋值操作找到一个目标。 +function foo(a) { + console.info(a); +} +foo(2); + +为了加深印象,我们再来分析一下上述代码中的 RHS 和 LHS 引用。最后一行foo()函数的调用需要对foo进行 RHS 引用。这里有一个很容易被忽略的细节,2 被当作参数传递给foo()函数时,2 会被分配给参数a,为了给参数a(隐式地)分配值,需要进行一次 LHS 查询,也就是说代码中隐含了a = 2的语句。 +前文已经说过了console.info(a);会对a进行一次 RHS 查询,需要注意的是console.info()本身也需要一个引用才能执行,因此会对console对象进行 RHS 查询,并检查得到的值中是否有一个log方法。 +为什么区分 LHS 和 RHS +我们考虑下面的一段代码,就可以为什么要区分 LHS 和 RHS 查询了,而且区分它们是分厂有必要的。 +function foo(a) { + console.info(a + b); + b = a; +} +foo(2); + +第一次对b进行 RHS 查询时是无法找到该变量的,这是一个未声明的变量,在任何相关的作用域中都无法找到它。如果 RHS 查询在所有嵌套作用域中都找不到该变量,引擎就会抛出 ReferenceError 异常。 +引擎在执行 LHS 查询时,如果在全局作用域中也无法找到目标变量,全局作用域就会创建一个具有该名称的变量,并将其返还给引擎。 + +需要注意的是,在严格模式下是禁止自动或隐式地创建全局变量的,因此在严格模式中 LHS 查询失败时,引擎同样会抛出 ReferenceError 异常。 + +接下来,如果 RHS 查询找到了一个变量,但是你尝试对这个值进行不合理的操作,比如对一个非函数类型的值进行函数调用,那么引擎就会抛出另一种叫做 TypeError 的异常。 +作用域链 +执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中,在 Web 浏览器中,全局执行环境被认为是window对象,因此所有的全局变量和函数都是作为window对象的属性和方法创建的。 +每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,而函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境,这个函数调用的压栈出栈是一样的。 +当代码在环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端始终都是当前执行的代码所在环境的变量对象,说的比较抽象,我们可以看下面的示例。 +var color = &quot;blue&quot;; + +function changeColor() { + var anotherColor = &quot;red&quot;; + + function swapColors() { + var tempColor = anotherColor; + anotherColor = color; + color = tempColor; + // 这里可以访问 color、anotherColor 和 tempColor + } + // 这里可以访问 color 和 anotherColor,但不能访问 tempColor + swapColors(); +} +// 这里只能访问 color +changeColor(); + +下面的图形象的展示了上述代码的作用域链,内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。函数参数也被当做变量来对待,因此其访问规则与执行环境中的其它变量相同。 +window + |-----color + |-----changeColor() + |----------anotherColor + |----------swapColors() + |----------tempColor + +作用域链还用于查询标识符,当某个环境中为了读取或写入而引入一个标识符时,必须通过搜索来确定该标识符实际代表什么。搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符,如果在局部环境中找到了该标识符,搜索过程就停止,变量就绪;如果在局部环境没有找到这个标识符,则继续沿作用域链向上搜索,如下所示: +var color = &quot;blue&quot;; + +function getColor() { + var color = &quot;red&quot;; + return color; +} + +console.info(getColor()); // &quot;red&quot; + +在getColor()中沿着作用域链在局部环境中已经找到了color,所以搜索就停止了,也就是说任何位于局部变量color的声明之后的代码,如果不使用window.color都无法访问全局color变量。 + +
+ + Read More ~ +
+
+
+ +
+

+ + JavaScript 性能优化——惰性载入函数 + +

+ +
+ + + + +
+ +
+ +参考资料: +《JavaScript 高级程序设计(第三版)》 +JavaScript专题之惰性函数 +深入理解javascript函数进阶之惰性函数 + +因为不同厂商的浏览器相互之间存在一些行为上的差异,很多 js 代码包含了大量的if语句,将执行引导到正确的分支代码中去,比如下面的例子。 +function createXHR() { + if (typeof XMLHttpRequest != 'undefined') { + return new XMLHttpRequest(); + } else if (typeof ActiveXObject != 'undefined') { + if (typeof arguments.callee.activeXString != 'string') { + var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']; + var i, len; + for (i = 0, len = versions.length; i &lt; len; i++) { + try { + new ActiveXObject(versions[i]); + arguments.callee.activeXString = versions[i]; + } catch (e) { + // skip + } + } + } + return new ActiveXObject(arguments.callee.activeXString); + } else { + throw new Error('No XHR object available.'); + } +} + +我们可以发现,在浏览器每次调用createXHR()的时候,它都要对浏览器所支持的能力仔细检查,但是很明显当第一次检查之后,我们就应该知道浏览器是否支持我们所需要的能力,因此除第一次之外的检查都是多余的。即使只有一个if语句也肯定要比没有if语句慢,所以if语句不必每次都执行,那么代码可以运行的更快一些,惰性载入就是用来解决这种问题的技巧。 +函数重写 +要理解惰性载入函数的原理,我们有必要先理解一下函数重写技术,由于一个函数可以返回另一个函数,因此可以在函数内部用新的函数来覆盖旧的函数。 +function sayHi() { + console.info('Hi'); + sayHi = function() { + console.info('Hello'); + } +} + +我们第一次调用sayHi()函数时,控制台会打印出Hi,全局变量sayHi被重新定义,被赋予了新的函数,从第二次开始之后的调用都会打印出Hello。惰性载入函数的本质就是函数重写,惰性载入的意思就是函数执行的分支只会发生一次。 +惰性载入 +我们来看一个例子(例子来源于冴羽所写的JavaScript专题之惰性函数)。现在需要写一个foo函数,这个函数返回首次调用时的Date对象,注意是首次。 +方案一 +var t; +function foo() { + if (t) return t; + t = new Date() + return t; +} +// 此方案存在两个问题,一是污染了全局变量 +// 二是每次调用都需要进行一次判断 + +方案二 +var foo = (function() { + var t; + return function() { + if (t) return t; + t = new Date(); + return t; + } +})(); +// 使用闭包来避免污染全局变量, +// 但是还是没有解决每次调用都需要进行一次判断的问题 + +方案三 +function foo() { + if (foo.t) return foo.t; + foo.t = new Date(); + return foo.t; +} +// 函数也是一种对象,利用这个特性也可以解决 +// 和方案二一样,还差一个问题没有解决 + +方案四 +var foo = function() { + var t = new Date(); + foo = function() { + return t; + }; + return foo(); +}; +// 利用惰性载入技巧,即重写函数 + +惰性载入函数有两种实现方式,第一种是在函数被调用时再处理函数。在第一次调用的过程中,该函数会被覆盖为另外一种按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行分支了。 +第二种实现方式是在声明函数时就指定适当的函数。这样第一次调用时就不会损失性能了,而是在代码首次加载时会损失一点性能,即是利用闭包写一个自执行的函数。 +改进 createXHR +有了上面的基础,我们就可以将createXHR()改进为下列形式,这样就不用每次调用都进行判断了。 +// 第一种实现方式 +function createXHR() { + if (typeof XMLHttpRequest != 'undefined') { + createXHR = function() { + return new XMLHttpRequest(); + } + } else if (typeof ActiveXObject != 'undefined') { + createXHR = function() { + if (typeof arguments.callee.activeXString != 'string') { + var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']; + var i, len; + for (i = 0, len = versions.length; i &lt; len; i++) { + try { + new ActiveXObject(versions[i]); + arguments.callee.activeXString = versions[i]; + } catch (e) { + // skip + } + } + } + return new ActiveXObject(arguments.callee.activeXString); + }; + } else { + createXHR = function() { + throw new Error('No XHR object available.'); + } + } +} + +// 第二种实现方式 +function createXHR() { + if (typeof XMLHttpRequest != 'undefined') { + return function() { + return new XMLHttpRequest(); + } + } else if (typeof ActiveXObject != 'undefined') { + return function() { + if (typeof arguments.callee.activeXString != 'string') { + var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']; + var i, len; + for (i = 0, len = versions.length; i &lt; len; i++) { + try { + new ActiveXObject(versions[i]); + arguments.callee.activeXString = versions[i]; + } catch (e) { + // skip + } + } + } + return new ActiveXObject(arguments.callee.activeXString); + }; + } else { + return function() { + throw new Error('No XHR object available.'); + } + } +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + Bootstrap-table 如何合并相同单元格 + +

+ +
+ + + + +
+ +
+ Bootstrap-table 官方提供了合并单元格方法 mergeCells,它根据四个参数可以合并任意个单元格,我们要做的只是告诉它怎么合并。 +要合并同一列相同的单元格,无非两种办法,一种是一边遍历一边合并,遍历完了再合并。这里采用第二种办法,这里不需要遍历所有数据,因为用户只能看到当前页的数据,所以只遍历当前页的数据更省时间。 +下面是我实现的获取合并信息算法,最终返回的是一个哈希表,比如下面的这个表格,如果要对「性别」这一列进行合并,很明显前面两个“男”需要合并成一个单元格,再去看下 Bootstrap-table 提供的 API,它需要的是从哪个单元格开始,合并多少个单元格,也就是它需要的是两个数值类型的参数。 + + + +姓名 +性别 +年龄 + + + + +张三 +男 +23 + + +李四 +男 +19 + + +王二 +女 +20 + + +麻子 +男 +21 + + + +所以我把哈希表设置为,键存的是索引,值存的是从这个索引开始后面连续有多少个和它一样的单元格,那么上述表格性别这一列所得到的合并信息哈希表就为: +{ + 0: 2, + 2: 1, + 3: 1 +} + +下面算法很简单,使用两个指针遍历指定的列,如果两个指针所指向的数据相同,那么就将键所对应的值进行加一操作,整个方法只会对该列数据遍历一边,所以时间复杂度为 O(n)。 +let getMergeMap = function (data, index: number) { + let preMergeMap = {}; + // 第 0 项为表头,索引从 2 开始为了防止数组越界 + for (let i = 2; i &lt; data.length; i++) { + let preText = $(data[i-1]).find('td')[index].innerText; + let curText = $(data[i]).find('td')[index].innerText; + let key = i - 2; + preMergeMap[key] = 1; + while ((preText == curText) &amp;&amp; (i &lt; data.length-1)) { + preMergeMap[key] = parseInt(preMergeMap[key]) + 1; + i++; + preText = $(data[i - 1]).find('td')[index].innerText; + curText = $(data[i]).find('td')[index].innerText; + } + // while循环跳出后,数组最后一项没有判断 + if (preText == curText) { + preMergeMap[key] = parseInt(preMergeMap[key]) + 1; + } + } + return preMergeMap; +} + +上述算法得到了单列数据的合并信息,下一步就是按照这个信息进行相同单元格的合并了,因此封装了下面的方法按照指定哈希表进行合并。 +let mergeCells = function (preMergeMap: Object, target, fieldName: string) { + for (let prop in preMergeMap) { + let count = preMergeMap[prop]; + target.bootstrapTable('mergeCells', { index: parseInt(prop), field: fieldName, rowspan: count }); + } +} + +到目前为止,我们实现的都只是对单列数据进行合并,要实现对多列数据进行合并,那么只需要对所有列都进行相同的操作即可。 +export let mergeCellsByFields = function (data: Object[], target, fields) { + for (let i = 0; i &lt; fields.length; i++) { + let field = fields[i]; + // 保证 field 与 i 是相对应的 + let preMergeMap = getMergeMap(data, i); + let table = target.bootstrapTable(); + mergeCells(preMergeMap, table, field); + } +} + +因为我在程序中做了一点处理,保证了fields中每个值得索引与对应表头的索引是一样的,因此不需要额外传入索引信息。简单来说就是我所实现的表格会根据fields的顺序,实现列之间的动态排序。你需要注意的是这一点很可能和你不一样。 +到现在已经能够合并所有的列了,查看 Bootstrap-table 的配置信息发现,它有个属性是 onPostBody 它会在 table body 加载完成是触发,所以把这个属性配置成我们的合并单元格方法即可。 +// groups 为要合并的哪些列 +onPostBody: function () { + mergeCellsByFields($('#table' + ' tr'), $('#table'), groups); +} + +再说一点不太相关的,我实现的是让用户可以自己选可以合并多少列,即用了一个可多选的下拉列表框供用户选择,根据用户选择的数量去合并,所以传入了一个groups参数。 +最后推荐一个排序插件 thenBy,你可以用它进行多字段排序,比如用在合并相同单元格的场景,在绘制表格前先对数据进行排序,那么最后合并的结果就是把所有相同的数据聚合到一起了,并且还将它们合并到一起了,起到了一个隐形的过滤查询功能。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 学习 Angulr 容易忽略的知识点 + +

+ +
+ + + + +
+ +
+ +参考内容: +《Angulr5 高级编程(第二版)》 + +函数声明式和表达式 +// 第一种:函数声明式 +myFunc(); +function myFunc(){ + ... +} + +// 第二种:函数表达式 +myFunc(); +let myFunc = function(){ + ... +} + +虽然上面两种函数声明方式在大部分情况下是一样的,第一种可执行,第二种却不可以执行,这是因为浏览器在解析 js 时找到函数声明,并在执行剩余语句之前设置好函数,此过程称为函数提升,但是函数表达式却不会受到提升,因此无法正常工作。 +js 不具备多态性 +js 重不能创建名称相同但参数不同的两个函数,它不具备这个多态性,比如你定义的函数中有两个形参,调用函数时只传一个参数,第二形参的值就是 undefined ,如果传的参数大于 3 个,那么会自动忽略多余的参数。可以使用下列方法来处理函数定义参数数量和用于调用函数实际参数数量之间不匹配的问题。 +// 使用默认参数 +let func = function(age, sex='男'){ + ... +} +func(23); + +// 使用可变长参数 +let func = function(age, sex, ...extraArgs){ + ... +} +func(23, '女', '张三', '深圳'); +// 最后一个参数是一个数组,任何额外的实参都会被赋给这个数组 + +let 和 war 的区别 +使用 let 和 var 声明变量的区别,使用 let 声明变量会把变量的作用范围限定在它所在的代码区域内。而使用 var 所创建的变量的作用域是它所在的函数。 +function func(){ + if(false){ + var age = 23; + } +} + +// 上面的代码会被解析成下面的形式,使用 let 则不会出现这样的结果 + +function func(){ + var age; + if(false){ + age = 23; + } +} + +相等 == 和恒等 === 以及 连接操作符 + +相等操作符尝试将操作数强制转换为相同的类型,再评估是否相等,实质上相等操作符==是测试二者的值是否相等,而与二者的类型无关;如果要测试值和类型是否都相等则应该用恒等操作符===。 +5 == '5' // 结果为 true +5 === '5' // 结果为 false + +在 js 中,连接操作符的优先级高于加法操作,也就是说5 + '5'的结果是55。 +不同的模块指定方式 +import { Name } from &quot;./modules/NameUtil&quot;;// 第一种 +import { Compont } from &quot;@angular/core&quot;;// 第二种 + +上面两种导入模块的方式有所不同,第一种是相对模块,第二种是非相对导入。第一种告诉的 TypeScript 编译器,该模块所在的位置是相对于包含 import 语句的文件而言;第二种非相对导入,编译器会用 node_modules 文件夹中的 npm 包来解析它。 +如果在导入模块时,出现需要导入两个不同模块但是名字却相同的情况,可以使用as关键字给导入的模块取一个别名。 +import { Name as otherName } from &quot;./modules/Name&quot;;//取别名 + +还有一种方法是将模块作为对象导入,如下 import 所示,导入 Name 模块的内容,并创建一个名为 otherName 的对象,然后就可以使用该对象的属性了。 +import * as otherName from &quot;./modules/NameUtil&quot;; +let name = new otherName.Name(&quot;Admin&quot;, &quot;China&quot;);// Name 是 NameUtil 中的类 + +多类型和类型断言 +在 ts 中允许指定多个类型,使用字符|进行分隔。看下面的的方法,其功能是把华氏温度转换为摄氏温度。 +// 使用多类型,该函数可以传入 number 和 string 类型的参数 +static convertFtoC(temp: number | string): string { + /* + 尝试使用 &lt;&gt; 声明一个类型断言,将一个对象转换为指定类型,也可以使用 as 关键字实现下列相同的效果 + let value: number = (temp as number).toPrecision ? temp as number : parseFloat(temp as string); + */ + let value: number = (&lt;number&gt;temp).toPrecision ? &lt;number&gt;temp : parseFloat(&lt;string&gt;temp); + return ((parseFloat(value.toPrecision(2)) - 32) / 1.8).toFixed(1); +} + +元组是固定长度的数组,数组的每一项都是指定的类型;可索引类型可以将键与值关联起来,创建类似于 map 的集合。 +// 元组 +let tuple: [string, string, string]; +tuple = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]; + +// 可索引类型 +let cities: {[index: string] : [string, string]} = {}; +cities[&quot;Beijing&quot;] = [&quot;raining&quot;, &quot;2摄氏度&quot;]; + +数据绑定 +[target]=&quot;expr&quot;// 方括号表示单向绑定,数据从表达式流向目标; + +(target)=&quot;expr&quot;// 圆括号表示单向绑定,数据从目标流向表达式,用于处理事件的绑定; +[(target)]=&quot;expr&quot;// 圆方括号组合表示双向绑定,数据在表达式与目标之间双向流动; +{{ expression }}// 字符串插入绑定。 + +[] 绑定有很多不同的形式,下面介绍不同表现形式的效果。 +&lt;!-- + 标准属性绑定(dom对象有的属性),将 input 的 value 属性绑定到一个表达式的结果 + 因为 model.getProduct(1) 可能返回 null ,所以使用模板空条件操作符 ? 浏览返回结果 + 如果返回不为空,那么将读取 name 属性,否则由 null 合并操作符 || 将结果设置为 None + 字符串插入绑定也可以使用这种表达式 + --&gt; +&lt;input [value]=&quot;model.getProduct(1)?.name || 'None'&quot;&gt; + +&lt;!-- + 元素属性绑定,有时候我们需要绑定的属性在 DOMAPI 上面没有 + 可以使用通过在属性名称前加上 attr 前缀的方式来定义目标 + --&gt; +&lt;td [attr.colspan]=&quot;model.getProducts().length&quot;&gt; + {{ model.getProduct(1)?.name || 'None' }} +&lt;/td&gt; + +&lt;!-- 还有其他的 ngClass,ngStyle 等绑定,理解大体上和上面差不多 --&gt; + +内置指令 +&lt;!-- + ngIf指令,如果表达式求值结果为 true ,那么 ngIf 将宿主元素机器内容包含在 html 文件中 + 指令前面的星号表示这是一条微模板指令 + 组要注意的是,ngIf 会向 html 中添加元素,也会从中删除元素,并非只是显示和隐藏 + 如果只是控制可见性,可以使用属性绑定挥着样式绑定 + --&gt; +&lt;div *ngIf=&quot;expr&quot;&gt;&lt;/div&gt; + +&lt;!-- + ngSwitch指令, + --&gt; +&lt;div [ngSwitch]=&quot;expr&quot;&gt; + &lt;span *ngSwitchCase=&quot;expr&quot;&gt;&lt;/span&gt; + &lt;span *ngSwitchDefault&gt;&lt;/span&gt; +&lt;/div&gt; + +&lt;!-- + ngFor指令,见名知意,为数组中的每个对象生成同一组元素 + ngFor 指令还支持其他的一系列可赋给变量的值,有如下局部模板变量 + + index:当前对象的位置 + odd:如果当前对象的位置为奇数,那么这个布尔值为 true + even:同上相反 + first:如果为第一条记录,那么为 true + last:同上相反 + --&gt; +&lt;div *ngFor=&quot;let item of expr; let i = index&quot;&gt; + {{ i }} +&lt;/div&gt; + +&lt;!-- + ngTemplateOutlet指令,用于重复模板中的内容块 + 其用法如下所示,需要给源元素指定一个 id 值 + + &lt;ng-template #titleTemplate&gt; + &lt;h1&gt;我是重复的元素哦&lt;/h1&gt; + &lt;/ng-template&gt; + &lt;ng-template [ngTemplateOutlet]=&quot;titleTemplate&quot;&gt;&lt;/ng-template&gt; + ...省略若万行 html 代码 + &lt;ng-template [ngTemplateOutlet]=&quot;titleTemplate&quot;&gt;&lt;/ng-template&gt; + --&gt; +&lt;ng-template [ngTemplateOutlet]=&quot;myTempl&quot;&gt;&lt;/ng-template&gt; + +&lt;!-- + 下面两个指令就是见名知意了,不解释 + --&gt; +&lt;div ngClass=&quot;expr&quot;&gt;&lt;/div&gt; +&lt;div ngStyle=&quot;expr&quot;&gt;&lt;/div&gt; + +事件绑定 +事件绑定使用 (target)=&quot;expr&quot;,是单向绑定,数据从目标流向表达式,用于响应宿主元素发送的事件。 +当浏览器触发一个时间时,它将提供一个对象来描述该事件,对于不同类型的事件有不同类型的事件对象,事件对象被赋给一个名为$event的模板变量,但是所有事件对象都有下面三个属性: +type:返回一个 string 值,用于标识已触发事件类型; +target:返回触发事件的对象,一般是 html元素对象。 +timeStamp:返回事件触发事件的 number 值,用 1970.1.1 毫秒数表示。 + +下面举几个例子,作为理解帮助使用。 +&lt;!-- 当数鼠标在上面移动时,就会触发 mouseover 事件 --&gt; +&lt;td *ngFor=&quot;let item of getProducts()&quot; (mouseover)=&quot;selectedProduct = item.name&quot;&gt;&lt;/td&gt; + +&lt;!-- 当用户编辑 input 元素的内容时就会触发 input 事件 --&gt; +&lt;input (input)=&quot;selectedProduct=$event.target.value&quot; /&gt; + +&lt;input (keyup)=&quot;selectedProduct=product.value&quot; /&gt; +&lt;!-- 使用事件过滤,上面的写法按下任何一个键都会触发事件,而下面的写法只有回车事件才会触发事件 --&gt; +&lt;input (keyup.enter=&quot;selectedProduct=product.value&quot;) /&gt; + +表单验证 +Angular 提供了一套可扩展的系统来验证表单元素的内容,总共可以向 input表元素中添加 4 个属性,每个属性定义一条验证规则,如下所示: +required:用于指定必须填写值; +minlength:用于指定最小字符数; +maxlength:用于指定最大字符数,(不能在表单元素直接使用,因为它与同名的 H5 属性冲突); +pattern:该属性用于指定用户填写的值必须匹配正则表达式 + +&lt;!-- + Angular 要求验证的元素必须定义 name 属性 + 由于 Angular 使用的验证属性和 H5 规范使用的验证属性相同, + 所以向表单元素中添加 novalidate 属性,告诉浏览器不要使用原生验证功能 + ngSubmit 绑定表单元素的 submit 事件 + --&gt; +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +Angular 提供了 3 对验证 CSS 类,这些类可以用于样式化表单元素,向用户提供验证反馈,具体说明如下所示。 +ng-untouched ng-touched:如果一个元素未被用户访问,就将其加入到 nguntouched 类中;一旦访问就加入到 ngtouched 类中。 +ng-prisstine ng-dirty:元素内容没有被改变被加入到 ng-prisstine 类中,否则将其加入到 ng-dirty 类中。 +ng-valid ng-invalid:如果满足验证规则定义的条件,就加入到 ng-valid 类中,否则加入到 ng-invalid 类中。 + +在实际使用过程中,直接定义对应的样式即可,如下所示: +&lt;style&gt; +input.ng-dirty.ng-invalid{ + border: 2px solid red; +} +input.ng-dirty.ng-valid{ + border: 2px solid green; +} +&lt;/style&gt; +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +上面的验证方式无法给用户提供更加具体的信息,用户不知道应该做什么,可以使用 ngModel 指令来访问宿主元素的验证状态,当存在验证错误的时候,使用该指令向用户提供指导性信息。 +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + #nameRef=&quot;ngModel&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;ul class=&quot;text-danger list-unstyled&quot; + *ngIf=&quot;name.dirty &amp;&amp; name.invalid&quot;&gt; + &lt;li *ngIf=&quot;name.errors?required&quot;&gt; + you must enter a product name + &lt;/li&gt; + &lt;li *ngIf=&quot;name.errors?.pattern&quot;&gt; + product name can only contain letters and spases + &lt;/li&gt; + &lt;li *ngIf=&quot;name.errors?minlength&quot;&gt; + &lt;!-- + Angular 表单验证错误描述属性 + required:如果属性已被应用于 input 元素,此属性返回 true + minlength.requiredLength:返回满足 minlength 属性所需的字符数 + minlength.actualLength:返回用户输入的字符数 + pattern.requiredPattern:返回使用 pattern 属性指定的正则表达式 + pattern.actualValue:返回元素的内容 + --&gt; + product name must be at least {{ name.errors.minlength.requiredLenth }} characters + &lt;/li&gt; + &lt;/ul&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +如果在用户尝试提交表单时就显示大量的错误信息,给人的体验感就会很差,所以可以让用户提交表单时再验证整个表单,示例代码如下所示。 +export class ProductionCompont { + // ...省略若万行代码 + formSubmited: boolean = false; + + submitForm(form: ngForm) { + this.formSubmited = true; + if(form.valid) { + this.addProduct(this.newProduct); + this.newProduct = new Product(); + form.reset(); + this.formSubmited = true; + } + } +} + +&lt;form novalidate #formRef=&quot;ngForm&quot; (ngSubmit)=&quot;submitForm(formRef)&quot;&gt; + &lt;div *ngIf=&quot;formsubmited &amp;&amp; formRef.invalid&quot;&gt; + there are problems with the form + &lt;/div&gt; + &lt;!-- 禁用提交按钮,验证成功提交按钮才可用 --&gt; + &lt;button [disabled]=&quot;formSubmited &amp;&amp; formRef.valid&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +fromSubmited 属性用于指示表单是否已经提交,并将用于在用户提交整个表单之前阻止表单验证。当用户提交表单时,调用 submitForm 方法,并将 ngForm 对象作为实参传入,ngForm 提供了 reset 方法,该方法可以重置表单的验证状态,使其返回到最初的未访问状态。 +更高级的还有使用基于模型的表单验证,可以自行查阅相关资料。 +使用 json-server 模拟 web 服务 +因为json-server会经常用到,建议使用全局安装命令npm install -g json-server。因为开发后端的同学太慢了,而我们如果要等他们把接口都提供给我们的时候再开发程序的话,那效率就太低了,所以使用 json-server 来模拟后端服务。只需要建好一个 json 文件,比如下面的格式: +{ + &quot;user&quot; : [ + { + &quot;name&quot; : &quot;张三&quot;, + &quot;number&quot; : &quot;1234&quot;, + }, + { + &quot;name&quot; : &quot;王二&quot;, + &quot;number&quot; : &quot;5678&quot;, + } + ], + &quot;praise&quot;: [ + {&quot;info&quot;:&quot;我是一只小老虎呀!&quot;}, + {&quot;info&quot;:&quot;我才是大老虎&quot;} + ] +} + +启动服务使用命令json-server [你的 json 文件路径],然后就可以根据提示访问了,你甚至可以使用http://localhost:3000/user?number=5678去过滤数据。这样就能模拟 web 服务,而不必等后端同学的进度了。 +解决跨域请求问题 +Angular 跨域请求问题可以通过 Angular 自身的代理转发功能解决,在项目文件夹下新建一个 proxy.conf.json 并在其中添加如下内容。 +// 可以通过下列配置解决 +&quot;/api&quot;: { + &quot;target&quot;: &quot;http://10.9.176.120:8888&quot;, +} + +在启动时使用npm start,或者使用ng serve --proxy-config proxy.conf.json,Anular 中的/api请求就会被转发到 http://10.9.176.120:8888/api,从而解决跨域请求问题。 +使用第三方 js 插件 +共有三种方式引入第三方插件,第一种很简单,直接在 html 中引入插件就可以了;第二种在angular.json中进行配置;第三种在 ts 文件中使用 import 导入库即可。 +// 第一种(需要重启服务) +&quot;scripts&quot;: [&quot;src/assets/jquery-3.2.1.js&quot;,&quot;src/assets/jquery.nicescroll.js&quot;,&quot;src/assets/ion.rangeSlider.js&quot;] + +// 第二种 +&lt;script type=&quot;text/javascript&quot; src=&quot;assets/jquery-3.2.1.js&quot;&gt;&lt;/script&gt; +&lt;script type=&quot;text/javascript&quot; src=&quot;assets/jquery.nicescroll.js&quot;&gt;&lt;/script&gt; + +// 第三种 +import &quot;assets/jquery-3.2.1.js&quot;; +import &quot;assets/jquery.nicescroll.js&quot;; +import &quot;assets/ion.rangeSlider.js&quot;; + +深拷贝与浅拷贝 +深拷贝与浅拷贝是围绕引用类型变量说的,其本质区别是不可变性,基本类型是不可变得,而引用类型是可变的。 +直接使用赋值操作符,就是浅拷贝,如果对拷贝源进行操作,会直接影响在拷贝目标上,因为这个赋值行为本质是内存地址的赋值,为了获得与拷贝源完全相同但又不会影响彼此的对象就要使用深拷贝。 +let objA = { + x: 1, + y: -1 +} +let objB = objA; +objA.x++; +console.log(&quot;objA.x:&quot;+objA.x, &quot;objB.x:&quot;+objB.x); +//打印结果如下: +objA.x : 2 +objB.x : 2 + +Typescript 提供了一种方法来实现引用类型的深拷贝,即Object.assign(target, ...source),此方法接受多个参数,第一个参数为拷贝目标,剩余参数为拷贝源,同名属性会进行覆盖。 +let objA = { + x: 1, + y: -1, + c: { + d: 1, + } +} +let objB = {}; +Object.assign(objB, objA); +objA.x++; +console.log(&quot;objA.x:&quot;+objA[&quot;x&quot;], &quot;objB.x:&quot;+objB[&quot;x&quot;]); +//打印结果如下: +objA.x : 2 +objB.x : 1 + +需要注意的是,Typescript 提供的深拷贝方法不能实现嵌套对象的深拷贝,会出现下面的情况。 +let objA = { + x: 1, + y: -1, + c: { + d: 1, + } +} +let objB = {}; +Object.assign(objB, objA); +objA.c.d++; +console.log(&quot;objA.c.d:&quot;+objA[&quot;c&quot;].d, &quot;objB.c.d:&quot;+objB[&quot;c&quot;].d); +//打印结果如下: +objA.c.d : 2 +objB.c.d : 2 + +要实现嵌套对象的深拷贝,可以使用 JSON 对象提供的方法,JSON 对象提供了两个方法,分别为:stringify()和parse(),前者将对象 JSON 化,后者将 JSON 对象化,使用这种方式可以实现嵌套深拷贝,但是也有缺点:破坏原型链,不能拷贝属性值为 function 的属性。 +let objA = { + a: 1, + b: { + c: 1 + } +} +let objB = JSON.parse(JSON.stringify(objA)); +objA.b.c++; +console.log(&quot;objA.b.c:&quot;+objA.b.c, &quot;objB.b.c:&quot;+objB.b.c); + +//打印结果如下: +objA.b.c:2 +objB.b.c:1 + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/pCdNazHt7/index.html b/pCdNazHt7/index.html new file mode 100644 index 00000000..8160076a --- /dev/null +++ b/pCdNazHt7/index.html @@ -0,0 +1,309 @@ + + + + + + + + 电路基础 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 电路基础 +
+ + +
+

+ + 半导体功率器件 + +

+ +
+ + + + +
+ +
+ 高中时候我们在化学课程中学过元素周期表,「氢氦锂铍硼、碳氮氧氟氖......」倒背如流,在元素周期表的中间三、四、五族元素定义为半导体元素,所谓半导体是根据其导电能力来定义的,我们可以通过一定的半导体工艺来改变其导电能力。 +以硅(Si)为例,硅是处在第四族的元素,它的外部有 4 个电子,所以硅的稳定结构是形成下图所示的及其稳定的共价键结构。 + +硅的两侧对应的是三族和五族的元素,三族的元素意味着外层有 3 个电子,五族的元素意味着外层有 5 个电子。如果把三族元素插入到四族的硅当中,由于硅想形成稳定的四个共价键结构,所以会存在一个空置的位置,我们称之为空穴,这个空穴是具备一定的正电荷的能力的,如此就形成了 P 型半导体。 + +同理,若使用五族元素与硅进行掺杂,就会多出一个可移动的电子,即存在自由电荷,形成了 N 型半导体。 + +PN 结(普通二极管) +当我们把 P 型半导体和 N 型半导体进行组合后,即可得到最基本的二极管(PN 结)。在 P 型半导体中存在高浓度的空穴(正电荷),在 N 型半导体中存在高浓度的电子,浓度高的载流子会自然而然向浓度低的区域进行扩散。 +由于载流子扩散,最终会形成一个势垒,这是一个空间电荷区,也称之为耗尽层。可以发现 PN 结存在 P 区、耗尽层、N 区三个区域,这几个区域都是呈现电中性的,不管是空穴还是电子想要到达另一个区域,都必须要穿过耗尽层,即耗尽层会阻碍空穴和电子的运动,因此整个 PN 结在没有外界干扰的情况下,是不具备导电能力的。 + +当我们从外界施加 N 到 P 的电场时,即 PN 结反偏。此时外界电场与耗尽层电场是同向的,所以在外部电场的作用下,耗尽层的宽度会被加强,于是 PN 结的导电能力就变得更弱,因此就呈现了一个无导电能力的特性。 +当然导电只是一种相对情况,即便空间电荷区变宽了,也不能百分百保证说就完全没有导电能力,因为还是有一定的空间电荷浓度,在这样的情况下会有微弱的电流流经 PN 结,意味着系统存在一个反向电流,这就是二极管一个比较重要的漏电流参数。 + +当外部施加的电场是从 P 到 N 时,即 PN 结正偏。外界电场的效果是使耗尽层变窄,加强了 P 区内空穴往 N 区内移动的能力,扩散电流远大于漂移电流,形成了一个正向导通电流。 + +最终二极管将呈现如下的导通特性,当正向电压大于势垒电压时,二极管开始导通。当施加反向电压时,二极管将截止,当反向电压大到一定程度后,二极管就会被反向击穿,即二极管损坏的过程。 + +功率二极管 +既然谈到了「功率」二字,那么更加关注的就是二极管承载电流、电压的能力了。如何把二极管承载电流、电压的能力加强呢?根据上文关于二极管的介绍可以知道,将耗尽层加宽可以承载更大的电压。 +图中中间 n- 为轻度参杂区域,下面 n+ 为重度参杂区域,这个参杂就导致了耗尽层的加宽,当然也导致导通损耗更大,不过也正因为如此,功率二极管才更加能耐压。 + +我们以非同步 BUCK 电路为载体,来说明一下功率二极管的变化过程。 + + +图中(1)部分指二极管导通,有一个小小的二极管导通压降,因此曲线没有贴着 x 轴; +图中(2)的位置由于二极管承受的是反向电压,此时它关断了,所以电压为负; +图中(3)二极管需要经历一个从没有电压到有外加电压的变化,当电压加到二极管上时,二极管中的载流子流动的趋势逐渐增大,宏观表现出来是电阻慢慢变小的过程,但是电流保持不变,所有会有一个小尖峰,这一小段时间也会导致整体功率的损耗,开关频率越高,这个导通过程导致的损耗越多; +图中(4)处伴随系统从通到断的状态变化,大规模载流子需要进行重新分配,这个重新分配表现出来就是电流,而且这个电流与主电流相反,所以会看到一个反向的电流,而且这个反向电流会施加在主电路里面。这一段反向电流又分为两部分,下降阶段是之前外加电压时,PN 结中从 P 区域移动到 N 区域的载流子移除(恢复)过程,即从正偏到反偏的过程,正偏时空间电荷区非常非常窄,此时要进入反偏状态,空间电荷区需要加强,载流子需要重新分配,外部激励会移除不必要的空间电荷。电流上升的过程,即二极管又变成一个耐压器件了,也就是空间电荷区加宽,更多的载流子会不均匀的分布在两端。整个过程不可避免的需要移动电荷,而电荷的聚集效应可以认为就是一个电容的效应,当我们需要施加电压时,电压的增加就会需要额外的电荷,电荷不断聚集提供相反电荷,使其电压不断增加,以致增加到刚好截止输出电压为止。 + +MOS 管 +以 NMOS 为例,它以 P 型半导体衬底,以 N 型半导体作为导电沟道,金属部分作为栅极(Gate),氧化部分(SiO2)作为绝缘层,两端分别为源极(Source)和漏极(Drain),从物理结构可以看出 MOS 管的源极和漏极是可以互换的,不像三极管有严格的顺序。 +在栅极和源极施加电压,随着电压的不断增大,导电沟道将逐渐形成,当导电沟道刚好形成时的电压,称之为开启电压。外加电压继续增大,导电沟道将变得越来越宽,即导电能力越来越强。 + +PMOS 相比 NMOS 更加容易驱动,只需要 VGS 小于一定值即可导通。但是 PMOS 的导通电阻比 NMOS 要大,并且成本也比 NMOS 要高,所以比 NMOS 的实际应用场景要少许多。 + +功率 MOS 管 + +对比前文普通 MOS 管,可以看到源极、栅极、漏极是分开的,顶上那个灰色的板子是金属板。而功率 MOS 管在这个基础上做了一点创新,下图中的阴影部分就是金属板,可以发现总共只有两个金属板,上面的金属板把 N 区和 P 区都给连起来了,所以即使在栅极没有加电压的时候,也会存在一个天然的二极管通道,但是普通 MOS 管是没有体二极管通道存在的。同时由于是功率 MOS 管,所以也会想办法将耗尽层加宽,以增加其耐压能力。 + +体二极管和耐压能力的加强是功率 MOS 和普通 MOS 的区别。 + +功率 MOS 管的正向导通能力就是涉及「场效应」了,所谓的场效应即意味着外部可以通过电场来控制其内部载流子的浓度,在栅极施加正电压时就会产生一个电子的导电沟道,由于整体是 N 型半导体衬底,所以整体也就形成了一个电子的导电沟道,并且该沟道支持电子的双向移动。 + +如下图所示是功率 MOS 管的等效电路模型。其主要损耗由三部分组成,分别为导通损耗、开关损耗(开通损耗和关断损耗)、驱动损耗。其中导通损耗与开关损耗容易理解,驱动损耗作何理解呢?MOS 并不像二极管是一个被动型器件,MOS 管开或关的行为都需要能量作为代价,就好比要打开机械开关需要用手去按压,这个过程所消耗的能量就是驱动损耗。 + +晶体管 +二极管只有一个 P 型半导体和一个 N 型半导体结合,如果再加一个 N 型半导体(或 P 型半导体)即构成了晶体管(三极管),晶体管有集电极、发射极、基极三个极。 + +需要注意的是三极管的集电区和发射区掺杂浓度是不一样的,其中基区多子少且做的很薄,而发射区的多子浓度很高,集电区多子浓度相对较低但面积大。不管三极管是正接还是反接,三极管都处于截止状态,这是因为三极管可以看作两个二极管反向相连,不论如何接都会有一个二极管处于截止状态。 + +为了能让三极管导通,我们在基极和发射极再施加一个电压,此时二极管开始导通,发射区的自由电子就可以源源不断的流向基区,但是基区的掺杂浓度很低且很薄,基区短时间内吸收不了太多的电子,只有一少部分电子能与空穴复合形成基极电流,而大部分被吸引到了集电区,形成集电极电流,也就是三极管的输出电流。 + +流过基极的电流越大,流到基区的自由电子也就越多,相应的被吸引到集电区的电子也就更多,这就是三极管小电流控制大电流的原理。基区做的很薄是为了让发射区的电子更容易进入集电区,浓度很低视为了形成更小的基极电流,这样才会有更多的自由电子流向集电区。 +IGBT +三极管工作时涉及载流子的注入和抽离所以会很慢,由于其性能的关系正在逐步退出历史舞台,因此需要对其进行改进,改进后的器件就是 IGBT,如下图所示。 + +可以发现 IGBT 是一个受 MOS 管控制的 BJT,即同时继承了 MOS 管快速和 BJT 大电流的优点。当然,它也有缺点,并且缺点主要来自于 BJT 关断较慢的问题,因为当 MOS 管门级信号撤出时,并不能立马把电流都抽走,所以电流会经历一段下降时间。 + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 00000000..81b39c2a --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,2137 @@ + + + + + + + + Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ + +
+

+ + 预处理器 + +

+ +
+ + + + +
+ +
+ C 语言的编译需要经过很多步骤,其中第一个步骤称为预处理阶段。这个阶段的主要任务包括删除注释、插入被#include指令包含的文件的内容、定义和替换#define指令定义的符号以及确定代码的部分内容是否应该跟绝一些条件编译指令进行编译。 +#define +#define指令就是为数值命名一个符号。比如#define name stuff指令,有了它之后,每当有符号name出现在这条指令后面时,预处理器就会把它替换成stuff,比如下面几个例子: +// 为关键字 register 创建了一个简短的别名 +#define reg register +// 声明了一个更具描述性的符号用来替代实现无限循环的 for 语句 +#define do_forever for(;;) +// 定义了一个简短记法,在 switch 语句中使用,可以自动把一个 break 放在每个 case 之前 +#define CASE break;case + +当然如果定义中的stuff非常长,那么也可以将它分成几行,除了最后一行之外,每行的末尾都需要加一个反斜杠。比如: +#define log_debug printf(&quot;File[%s]line[%d]:&quot; \ + &quot; x=[%d], y=[%d], z=[%d]&quot;, \ + __FILE__, __LINE__, \ + x, y, z) + +// 那么我们将可以很方便的插入一条调试语句打印 +x *= 2; +y += x; +z = x * y; +log_debug; + +很容易就发现上面的log_debug定义无法进行泛化,当然设计者也考虑到了这个问题,所以#define机制包括了一个规定,即允许把参数替换到文本中,这种实现一般称为宏,其声明方式如下: +define name(parameter-list) stuff + +需要注意的是parameter-list是一个由逗号分隔的符号列表,他们可能出现在stuff中。参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。下面我们看一个具体的列子,以此了解宏定义的机制,并将它逐步优化改进: +#define SQUARE(x) x * x + +// 使用 +SQUARE(5) +// 效果:5 * 5 + +考虑一下下面的代码段: +a = 5; +printf(&quot;%d\n&quot;, SQUARE(a + 1)); + +乍一看觉得这段代码将打印36这个值。但实际它却会打印11,我们仔细观察一下被替换的宏文本,即参数x被文本a + 1替换: +a = 5; +printf(&quot;%d\n&quot;, a + 1 * a + 1); + +很容易想到对参数 x 加一个括号解决上述问题,即: +#define SQUARE(x) (x) * (x) + +// 上述打印将会被替换为 +a = 5; +printf(&quot;%d\n&quot;, (a + 1) * (a + 1)); + +类似的我们可以再定义一个DOUBLE宏,即: +#define DOUBLE(x) (x) + (x) + +但是考虑下面的使用方式: +a = 5; +printf(&quot;%d\n&quot;, 10 * DOUBLE(5)); + +看上去它应该打印的结果是100,但事实上它打印的是55,我们再通过宏替换产生的文本观察问题: +printf(&quot;%d\n&quot;, 10 * (5) + (5)); + +所以我们需要在整个表达式两边加上一对括号。所有用于对数值表达式进行求值的宏定义都应该使用下面这种方式加上括号,避免在使用宏时,由于参数中的操作符或邻近的操作符之间不可预料的相互作用。 +#define DOUBLE(x) ((x) + (x)) + +宏与函数 +宏非常频繁的用于执行简单的计算,比如在两个表达式中寻找其中较大(小)的一个: +#define MAX(a, b) ((a) &gt; (b) ? (a) : (b)) + +那么为什么不使用函数来完成这个任务呢?首先用于调用和从函数返回的代码很可能比实际执行这个小型计算工作的代码更大,所以使用宏比使用函数在程序的规模和速度方面都更胜一筹。 +更为重要的是函数必须声明为一种特定的类型,所以它只能在类型合适的表达式上使用。但是上面的这个宏可以用于整型、长整型、单浮点型、双浮点型以及任何其它可以使用&gt;操作符比较值大小的类型,即宏与类型无关。 +当然宏也有它的不利之处,因为每次在使用宏时,一份宏定义代码的拷贝都将插入到程序中,除非宏的定义非常短,否则使用宏将会大幅增加程序的长度。 +也有一些任务根本无法使用函数实现,比如下面这个宏的第二个参数是一种类型,它无法作为函数参数进行传递。 +#define MALLOC(n, type) ((type *)malloc((n) * sizeof(type))) + +当宏参数在宏定义中出现的次数超过一次时,如果这个参数具有副作用,那么当使用这个宏的时候就可能出现危险,导致一些不可预料的后果。比如x++就是一个具有副作用的表达式,它会改变x的值,直接会导致下面的代码段出现不可预知的后果: +#define MAX(a, b) ((a) &gt; (b) &gt; (a) : (b)) + +x = 5; +y = 8; +z = MAX(x++, y++); +// z = ((x++) &gt; (y++) &gt; (x++) : (y++)) + + + + +属性 +#define 宏 +函数 + + + + +代码长度 +每次使用时,宏代码都被插入到程序中。除了非常小的宏志伟,程序的长度将大幅度增长 +函数代码只出现于一个地方,每次使用这个函数时,都调用那个地方的同一份代码 + + +执行速度 +更快 +存在函数调用/返回的额外开销 + + +操作符优先级 +宏参数的求值是在所有周围表达式的上下文环境里,除非它们加上括号,否则邻近操作符的优先级可能回产生不可预料的结果 +函数参数只在函数调用时求值一次,它的结果传递给参数。表达式的求值结果更容易预测 + + +参数求值 +参数每次用于宏定义时,它们都将重新求值。由于多次求值,具有副作用的参数可能会产生不可预料的结果 +参数在函数被调用前求值一次。在函数中多次使用参数并不会导致多种求值过程。参数的副作用不会造成任何特殊的问题 + + +参数类型 +宏与类型无关。只要对参数的操作是合法的,它可以使用于任何参数类型 +函数的参数是与类型有关的。如果参数的类型不同,就需要使用不同的函数,即使它们执行的任务时相同的 + + + +文件包含 +我们知道#include指令可以使另一个文件的内容被编译,就像它实际出现于#include指令出现的位置一样。这种替换的执行方式很简单:预处理器删除这条指令,并用包含头文件的内容取而代之。这样一个头文件如果被包含到 10 个源文件中,它实际上被编译了 10 次。 +基于这种替换的方式,当出现嵌套#include文件被多次包含时,就会出现问题: +#include &quot;a.h&quot; +#include &quot;b.h&quot; + +// 如果 a.h 和 b.h 中都包含一个 #include x.h +// 那么 x.h 在此处就出现了两次 + +这种多重包含在绝大多数情况下出现于大型程序中,它往往需要很多头文件,所以要发现这种情况并不容易。但是我们可以使用条件编译来解决这个问题: +#ifndef _HEADER_NAME_H_ +#define _HEADER_NAME_H_ + +/* +* All the stuff that you want in the header file +*/ + +#endif + + +
+ + Read More ~ +
+
+
+ +
+

+ + C 语言拾遗 + +

+ +
+ + + + +
+ +
+ +约定:本文所说的标准均为 ANSI (C89) 标准 + +三字母词 +标准定义了几个三字母词,三字母词就是三个字符的序列,合起来表示另一个字符。三字母词使得 C 环境可以在某些缺少一些必需字符的字符集上实现,它使用两个问号开头再尾随一个字符,这种形式一般不会出现在其它表达形式中,这样就不容易引起误解了,下面是一些三字母词的对应关系: +??( [ +??) ] +??! | +??&lt; { +??&gt; } +??' ^ +??= # +??/ \ +??- ~ + +所以在一些特殊情况下可能出现下面的情况,希望你不要被意外到。 +printf(&quot;Delete file (are you really sure??): &quot;); + +// result is: Delete file (are you really sure]: + +字符 +直接操作字符会降低代码的可移植性,应该尽可能使用库函数完成。比如下面的代码试图测试ch是否为一个大写字符,它在使用ASCII字符集的机器上能够运行,但是在使用EBCDIC字符集的机器上将会失败。 +if( ch &gt;= 'A' &amp;&amp; ch &lt;= 'Z') + +使用if(isupper(ch))语句则能保证无论在哪种机器上都能正常运行。 +字符串比较 +库函数提供了int strcmp(const char *s1, const char *s2)函数用于比较两个字符串是否相等,需要注意的是在标准中并没有规定用于提示不相等的具体值。它只是说如果第 1 个字符串大于第 2 个字符串就返回一个大于零的值,如果第 1 个字符串小于第 2 个字符串就返回一个小于零的值。一个常见的错误是以为返回值是1和-1,分别代表大于和小于。 +初学者常常会编写下面的表达式。认为如果两个字符串相等,那么它返回的结果将为真。但是这个结果恰好相反,两个字符串相等的情况下返回值是零(假)。 +if(strcmp(a, b)) + +strlen +strlen的返回值是一个size_t类型的值,这个类型是在头文件stddef.h中定义的,它是一个无符号整数类型,所以会导致下面表达式的条件永远为真。 +if(strlen(x) - strlen(y) &gt;= 0) { + // do something +} + +第二点需要注意的是strlen的返回值没有计算\0的长度,所以下面的代码在一些检查严格或老版本的编译器中会报错,其原因在于少分配了一个存储单位。 +// 假设 str 是一个字符串 +char *cpy = malloc(strlen(str)); +strcpy(cpy, str); + +// 正确写法应为 +char *cpy = malloc(strlen(str) + 1); +strcpy(cpy, str); + +赋值截断 +表达式a = x = y + 3;中x和a被赋予相同值的说法是错误的,因为如果x是一个字符型变量,那么y+3的值就会被截去一段,以便容纳于字符型的变量中,那么a所赋的值就是这个被截短后的值。下面也是一个非常常见的错误。 +char ch; +// do something +while((ch = getchar()) != EOF) { + // do something +} + +EOF所需要的位数比字符型值所能提供的位数要多,这也是getchar返回一个整型值而不是字符值的原因。例子中把getchar的返回值存储于字符型变量会导致被截短,然后再把这个被截短的值提升为整型与EOF进行比较,在某些机器的特定场景下就会导致问题。比如在使用有符号字符集的机器上,如果读取了一个的值为\377的字节,上述循环就将终止,因为这个值截短再提升之后与EOF相等。而当这段代码在使用无符号字符集的机器上运行时,这个循环将永远不会终止。 +指针与数组 +因为数组和指针都具有指针值,都可以进行间接访问和下标操作,所以很多同学都想当然的将它们认为是一样的,为了说明它们是不相等的,我们可以考虑下面的两个声明: +int a[5]; +int *b; + +声明一个数组时,编译器将根据声明所指定的元素数量为数组保留空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为任何指向现有的内存空间。所以在上述声明之后,表达式*a是完全合法的,但是表达式*b将访问内存中某个不确定的位置,或者导致程序终止。 +硬件操作 +我们知道其实表示就是内存中一个地址,所以理论上*100 = 25是一种可行的操作,即让内存中位置为100的地方存储25。但实际上这条语句是非法的,因为字面值100的类型是整型,而间接访问操作只能用于指针类型表达式,所以合法的写法必须使用强制转换,即*(int *)100 = 25。 +需要说明的是使用这种技巧的机会是绝无仅有的,只有偶尔需要通过地址访问内存中某个特定的位置才可使用,它并不是访问某个变量,而是访问硬件本身。比如在某些机器上,操作系统需要与输入输出设备控制器通信,启动 I/O 操作并从前面的操作中获得结果,此时这些地址是预先已知的。 ++= 与 ?: 操作符 +我们在这里讨论一下+=操作符,它的用法为a += expr,读作把 expr 加到 a,其实际功能相当于表达式a = a + expr的作用,唯一不同的是+=操作符的左操作数a只会求值一次。可能到目前为止没有感觉到设计两种增加一个变量值的方法有什么意义?下面给出代码示例: +// 形式 1 +a[ 2 * (y - 6*f(x)) ] = a[ 2 * (y - 6*f(x)) ] + 1; + +// 形式 2 +a[ 2 * (y - 6*f(x)) ] += 1; + +在第一种形式中,用于选择增值位置的表达式必须书写两次,一次在赋值号左边,一次在赋值号右边。由于编译器无法知道函数f是否具有副作用,所以它必须两次计算下标表达式的值,而第二种形式的效率会更高,因为下标表达式的值只会被计算一次。同时第二种形式也减少了代码书写错误的概率。 +同理三目运算符也可以起到类似的效果。 +// 形式 1 +if(a &gt; 5) { + b[ 2 * c + d * (e / 5) ] = 3; +} else { + b[ 2 * c + d * (e / 5) ] = -20; +} + +// 形式 2 +b[ 2 * c + d * (e / 5) ] = a &gt; 5 ? 3 : -20; + +逗号操作符 +逗号操作符可以将多个表达式分隔开来。这些表达式自左向右逐个进行求值,整个表达式的值就是最后那个表达式的值。例如: +if(b + 1, c / 2, d &gt; 0) { // do something} + +当然,正常人不会编写这样的代码,因为对前两个表达式的求值毫无意义,它们的值只是被简单的丢弃了。但是我们可以看看下面的代码: +// 形式 1 +a = get_value(); +count_value(a); +while(a &gt; 0) { + // do something + a = get_value(); + count_value(a); +} + +// 形式 2 +while(a = get_value(), count_value(), a &gt; 0) { + // do something +} + +指针 +int* a, b, c; + +人们会很自然的认为上述语句是把所有三个变量都声明为指向整型的指针,但事实上并非如此,星号实际上只是表达式*a的一部分,只对这个标识符有作用。如果要声明三个指针,那么应该使用下面的形式进行初始化。 +int *a, *b, *c; + +在声明指针变量时可以为它指定初始值,比如下面的代码段,它声明了一个指针,并用一个字符串常量对其进行初始化。 +char *msg = &quot;Hello World!&quot;; + +需要注意的是,这种类型的声明会让人很容易误解它的意思,看起来初始值似乎是赋给表达式*msg的,但实际上它是赋值给msg本身的,也就是上述声明实际形式如下: +char *msg; +msg = &quot;Hello World!&quot;; + +指针常量: int *pi中pi是一个普通的指向整型的指针, 而变量int const *pci则是一个指向整型常量的指针,你可以修改指针的值,但是不能修改它所指向的值。相比之下int * const cpi则声明cpi为一个指向整型的常量指针。此时指针是常量,它的值无法修改,但是可以修改它所指向的整型的值。在int const * const cpci中,无论是指针本身还是它所指向的值都是常量,无法修改。 +枚举类型 +枚举(enumerated) 类型就是指它的的值为符号常量而不是字面值的类型,比如下面的语句声明了Jar_Type类型: +enum Jar_Type { + CUP, + PINT, + QUART, + HALF_GALLON, + GALLON +}; + +需要注意的是,枚举类型实际上是以整型方式存储的,代码段中的符号名实际上都是整型值。在这里CUP的值是0,PINT的值是1,依次类推。 +在适当的时候,可以为这些符号名指定特定的值整型值。并且只对部分符号名进行赋值也是合法的,如果某个符号名没有显示的赋值,那么它的值就比前面一个符号名的值大 1。 +enum Jar_Type { + CUP = 8, + PINT = 16, + QUART = 32, + HALF_GALLON = 64, + GALLON = 128 +}; + + +符号名被当作整型处理,这意味着可以把HALF_GALLON这样的值赋给任何整型变量,但是在编程活动中应该避免这种方式使用枚举,因为这样会削弱它们的含义。 + +typedef 与 define +在实际应用过程中应该使用typedef而不是#define来创建新的类型名,因为#define无法正确的处理指针类型,比如下面的代码段正确的声明了a,但是b却被声明为了一个字符。 +#define ptr_to_char char * +ptr_to_char a, b; + +联合(union) +联合看起来很像结构体,与结构体不同的是联合的所有成员共用同一块内存,所以在同一时刻联合中的有效成员永远只有一个。我们可以看下面一个例子,当一个variable类型的变量被创建时,解释器就创建一个这样的结构并记录变量类型。然后根据变量类型,把变量的值存储在这三个值字段的其中一个。 +struct variable { + enum { INT, FLOAT, STRING } type; + int int_val; + float float_val; + char *str_val; +} + +不难发现上述结构的低效之处在于它所使用的内存,每个variable结构存在两个未使用的值字段,造成了内存空间上的不少浪费。使用联合就可以减少这种空间上的浪费,它把这三个值字段的每一个都存储在同一个内存位置。我们知道这三个字段并不会冲突,因为每个变量只可能具有一种类型,所以在具体的某一时刻,联合的这几个字段只有一个被使用。 +struct variable { + enum { INT, FLOAT, STRING } type; + union { + int i; + float f; + char *s; + } val; +} + +现在,对于整型变量,我们只需要将type字段设为INT,并把整型值存储于val.i即可。如果联合中各成员的长度不一样,联合的长度就是它最长成员的长度。 +联合的变量也可以被初始化,但是这个初始值必须是联合第 1 个成员的类型,而且它必须位于一对花括号里面。比如: +union { + int a; + float b; + chat c[4]; +} x = { 5 }; + +结构体 +在实际编程活动中,存在链表、二叉树等结点自引用的情况,那么结构体的自引用如何编写呢? +struct node { + int data; + struct node next; +} + +上述写法是非法的,因为成员next是一个完整的结构,其内部还将包含自己的成员next,这第 2 个成员又是另一个完整结构,它还将包含自己的成员next,如此重复下去将永无止境。正确的自引用写法如下: +struct node { + int data; + struct node *next; +} + +我们需要注意下面的这个陷阱: +/* +错误写法:因为类型名 node_t 直到声明末尾才定义 +所以在结构中声明的内部 node_t 尚未定义 +*/ +typedef struct { + int data; + node_t *next; +} node_t; + +// 正确写法 +typedef struct node_tag { + int data; + struct node_tag *next; +} node_t; + +编译器在实际分配时会按照结构体成员列表的顺序一个接一个的分配内存,并且只有当存储成员需要满足正确的边界对齐要求时,成员之间可能会出现用于填充的额外内存空间。 + +```c +struct align { + char a; + int b; + char c; +} + +如果某个机器的整型值长度为 4 个字节,并且它的起始存储位置必须能够被 4 整除,那么这个结构在内存中的存储将是下面这种形式 + + + +a + + + +b +b +b +b +c + + + + + + +我们可以通过改变成员列表的声明顺序,让那些对边界要求严格的成员首先出现,对边界要求弱的成员最后出现,这样可以减少因为边界对齐而带来的空间损失。 +struct align { + int b; + char a; + char c; +} + + + + +b +b +b +b +a +c + + + +当程序创建几百个甚至几千个结构时,减少内存浪费的要求就比程序的可读性更为急迫。我们可以使用sizeof操作符来得出一个结构的整体长度。如果必须要确定结构某个成员的实际位置,则可以使用offsetof(type, member)宏,例如: +offset(struct align, b); + +一句话 +标识符:标识符就是变量、函数、类型等的名字,标识符的长度没有限制,但是 ANSI 标准允许编译器忽略第 31 个字符以后的字符,并且允许编译器对用于表示外部名字(由链接器操作的名字)的标识符进行限制,只识别前 6 位不区分大小写的字符。 +注释:代码中所有的注释都会被预处理器拿掉,取而代之的是一个空格。因此,注释可以出现在任何空格可以出现的地方。 +类型:C 语言中仅有 4 种基本数据类型,即整型、浮点型、指针和聚合类型(数组、结构等),所有其它的类型都是从这 4 中基本类型的某种组合派生而来。 +类型长度:标准只规定了short int至少是 16 位,long int至少是 32 位,至于缺省的int是多少位则直接由编译器设计者决定。并且标准也没有规定这 2 个值必须不一样。如果某种机器的环境字长是 32 位,而且也没有什么指令能够更有效的处理更短的整型值,那它很可能把这 3 个整型值都设定为 32 位。 +位域:基于 int 位域被当作有符号还是无符号数、位域成员的内存是从左向右还是从右向左分配、运行在 32 位整数的位域声明可能在 16 位机器无法运行等原因,注重可移植性的程序应该避免使用位域。 +结构与指针:什么时候应该向函数传递一个结构而不是一个指向结构的指针呢?很少有这种情况。只有当一个结构特别小(长度和指针相同或更小)时,结构传递方案的效率才不会输给指针传递方案。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 为什么宏定义要使用 do {...} while (0) ? + +

+ +
+ + + + +
+ +
+ +参考内容: +do {…} while (0) in macros +do {...} while (0) 在宏定义中的作用 +do{}while(0)只执行一次无意义?你可能真的没理解 + +近期参与的项目属于嵌入式软件领域,自然而然就得用 C 语言进行开发,开发过程中发现引入的第三方库里面有一些奇奇怪怪的写法,比如大佬们都喜欢使用do {...} while(0)的宏定义,在 Stack Overflow 上也有人提出了这个问题。之前从事 Linux 内核开发的谷歌大佬 Robert Love 给出了如下的解释: + +do {…} while(0) is the only construct in C that lets you define macros that always work the same way, so that a semicolon after your macro always has the same effect, regardless of how the macro is used (with particularly emphasis on the issue of nesting the macro in an if without curly-brackets). +do {...} while(0) 在 C 中是唯一的构造程序,让你定义的宏总是以相同的方式工作,这样不管怎么使用宏(尤其在没有用大括号包围调用宏的语句),宏后面的分号也是相同的效果。 + +这句话读起来有些拗口,只觉得大佬的表述曲高和寡,翻译翻译就是:使用do {...} while(0)构造后的宏定义不会受到大括号、分号等影响,总能按照我们期望的方式调用运行。下面我们举几个实际的例子来加深理解。 +// 现有如下宏定义 +#define foo(x) bar(x); baz(x) + +// 1. 可以这样调用 +foo(wolf); + +// 上述调用将会被展开为下列代码,完美没有问题 +bar(wolf); baz(wolf); + + +// 2. 如果我们像下面这样调用呢? +if (!feral) + foo(wolf); + +// 上述调用将会被展开为下列代码,很明显这是错误的,并且是很容易犯的错误 +if (!feral) + bar(wolf); +baz(wolf); + +为了避免上面例子所出现的问题,我们可以考虑使用{ }直接把整个宏包裹起来,如下所示: +// 修改宏定义为 +#define foo(x) { bar(x); baz(x) } + +// 3. 例 1 调用 +if (!feral) + foo(wolf); + +// 现在上述调用将展开为下列代码 +if (!feral) { + bar(wolf); + baz(wolf); +}; + + +// 4. 我们再考虑一下如下调用呢 +if (!feral) + foo(wolf); +else + bin(wolf); + +// 上述调用将会被展开为下列代码,很明显又出现了语法错误 +if (!feral) { + bar(wolf); + baz(wolf); +}; +else + bin(wolf); + +我们继续考虑比使用{ }直接把整个宏包裹起来更好的方法,即本文标题所说的使用do {...} while (0),即上述宏将定义为如下形式。 +// 终极版宏定义 +#define foo(x) do { bar(x); baz(x) } while (0) + +// 5. 例 4 调用 +if (!feral) + foo(wolf); +else + bin(wolf); + +// 现在上述调用将展开为下列形式,很完美 +if (!feral) + do { bar(wolf); baz(wolf) } while (0); +else + bin(wolf); + +do {...} while (0)除了在宏定义中可以发挥完美的作用外,在某些情况下还可以当作goto使用。因为goto不符合软件工程的结构化,并且容易使得代码晦涩难懂,所以很多公司都不倡导使用甚至禁止使用。那么我们可以使用do {...} while (0)来做同样的事情。 +#include &lt;stdio.h&gt; +#include &lt;stdlib.h&gt; +int main() +{ + char *str; + /* 最初的内存分配 */ + str = (char *) malloc(15); + if(str != NULL) + goto loop; + printf(&quot;hello world\n&quot;); +loop: + printf(&quot;malloc success\n&quot;); + return 0; +} + +上述代码我们可以修改为下列形式,使用do {...} while (0)将函数主体包裹起来,而break语句则替代了goto语句的作用,并且代码的可读性与可维护性都比上述goto方式更好。 +#include &lt;stdio.h&gt; +#include &lt;stdlib.h&gt; +int main() +{ + do{ + char *str; + /* 最初的内存分配 */ + str = (char *) malloc(15); + if(str != NULL) + break; + printf(&quot;hello world\n&quot;); + }while(0); + printf(&quot;malloc success\n&quot;); + return 0; +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何获得好运气 + +

+ +
+ + + + +
+ +
+ 一点幼稚的思考 +从我自身浅薄的经历做了一点总结,如果某个公司的员工有超过三分之一都是同一个地方的人,那这个公司大概率是个骗子公司,因为常规渠道他们无法招到员工,只能靠亲戚、同学、同乡一类关系拉拢,如果下限再放低一点就是传销。这类公司开早会常常是这样的——主管在前台扯着嗓门儿大喊:“亲爱的家人们,大家早上好!”下面的员工齐声回答:“好!很好!非常好!” +之前在财富与幸福指南中有翻译过一点纳瓦尔的语录,今年 4 月份这本书已经出版了,里面有提到把事情做到极致,让机会自动找到你,让运气成为必然。结合自身经历愈发认可其说法,在一次少儿科创比赛活动中遇到一个小领导,他问我现场的学生都是谁教的,我说是某某老师教的,他突然冒出一句:“某某老师教的学生肯定不会差”。这样随口而出的一句话给了我不小的触动,这个某某老师把事情做到极致,名气在他不知道的情况下就打出去了,这就是运气。 +发现那些让人仰慕的大佬都有一个无所畏惧的特质。面对同一项未知的任务,所有人都不知道如何去完成,强者与弱者唯一的区别是敢不敢接这个任务,强者总是毅然扎进去并着手寻找方案,弱者总是以不知道怎么做、没有学过相关知识等理由推脱。长此以往大家就觉得强者什么都会,口口相传自然就带来了运气。 +当下社会快速的生活节奏让人喘不过气来,很多人长期都是两点一线的生活,每天的生活轨迹完全重合没有一点波动。生活需要变量才会精彩,机会往往是跟随变量出现的,保持年轻的折腾,保持一颗好奇心才会有更多的可能。把乔布斯的话搬出来用一下:Stay Hungry,Stay Foolish。 +不一样的生活 +偶然了解到电鸭社区,它所提倡的只工作,不上班理念让我眼前一亮,数字游民这一新的词汇加到我的字典。原来这世界上还有可以不打卡的工作,他们的工作地点居然完全没有限制,在工作的同时还可以开着房车旅行...... +非常希望能体验一下数字游民生活,上帝彷佛能听见我的愿望一样,立马就把一个机会给了我。牛客网需要一些人审核主站题目,后续是出题、审题等等一系列工作,可能因为我交付的题目质量还不错,她们愿意把一些高利润的工作派给我,一个题目从几百到几十价格不等,有俩月拿到的劳务报酬竟超过了本职工资。牛客让我体验了一把远程工作乐趣,以后如果真的创业失败了,就老老实实找个远程工作苟且吧。 +通过Jina全球社区的负责人 Lisa,了解到一个2050团聚活动,这是我第一次接触如此纯粹的社区,把分散在各行业又比较理想主义的人汇集到了一起。目前对这个社区是很有好感的,容我观察两年再细说体会。 +鼓捣独立博客 +我是快毕业时开始接触公众号的,那时候的环境很单纯,很多作者都把公众号当博客用。认识的一个和我同龄的作者,为了能把某个算法讲清楚经常熬夜 P 图。那时候的程序员小灰还叫玻璃猫,刘欣大大的文章标题比现在也要朴素。我自己那段时间写文章尤其认真,因此也认识了许许多多网友,收到了博文视点和另一家出版社的出版邀请。那时大部分人还不懂什么是 IP,流量一词更普遍的含义还是电话卡用的那个上网流量,10 万+偶有出现但频率不高。 + +刘欣大大的码农翻身依旧朴实,只是文章标题明显有一些刻意了,今年刘欣给一些编程老师免费送去了新书《半小时漫画计算机》 + +慢慢有人开始专门为了 10万+而写文章,抖音一类短视频应用出现后,整个互联网环境开始变得浮躁,大家为了流量不择手段,一些人为了博取流量而瞎编骇人听闻的事情。比如年初在抖音出现的血奴事件,更为可笑的是一些权威媒体居然转发了这个视频,官方媒体为了流量不查证新闻真实性就转发,那便是没有责任感的媒体。 +媒体常常为了流量而故意删减新闻细节,比如之前文章多从自己身上找问题中所提到的新闻。互联网让大家的情绪得以便利的发泄,常常故事听一半就抑制不住心中正义,评论下面骂两句、转发个朋友圈都是没有成本的事情。台剧我们与恶的距离和电影狩猎对这个话题有比较深刻的探讨,值得花时间看一看。 +百度取消了快照功能,必应中国区没了快照;一打开微信不出现视频号的可能性几乎为零;标题党的文章充满了订阅号;公众号不允许外链和无法修改错误很烦心。于是我鼓捣起了独立博客,在独立博客中可以自由的发布和修改文章。我选择的博客写作工具是Gridea,选它并不是因为它好用,而是因为上面有一款主题我比较喜欢,我自己花了些时间将该主题的 bug 修复了,并且在原基础上增加了几个 sidebar card,大致长下面这个样子,我个人还挺喜欢的。 + +有意思的事儿 +只能发送 500 英里的邮件。一所大学的邮件只能发送 500 英里(800 公里),这听起来像是一个胡编乱造的故事。原因是因为这所大学的服务器配置错误,他们将连接到远程服务器的超时设为了零延迟。而程序运行时 0 超时是按 3 毫秒计算的,一旦 3 毫秒内没有收到远程服务器的答复,就认为邮件无法发送。光在 3 毫秒时间前进的距离,刚好就是 500 多英里。 +导致电脑宕机的原因竟然是 Janet Jackson 的一首歌。微软资深工程师 Raymond Chen 披露 WinXP 发布早期,一些用户报告电脑系统会意外崩溃。起初他们以为是 Windows 的 bug,但很欣慰的是发现同行也存在这个问题,最终定位到这个问题的根源竟然是 Janet Jackson 1989 年发行的歌曲《Rhythm Nation》。歌曲中一些旋律会和当时一款 5400 转硬盘发生共振,进而导致电脑崩溃。 +“看黄片被罚”的闹剧何时休?这是一篇旧闻,从网站上的时间看是 2012 年 6 月 17 日。讲述的是延安小夫妻在家看黄碟被抓,生活几乎陷绝境的故事。 +推荐一个视频 +这是一个插画师、立体书设计师在一席的演讲,她把自己的生活都做成了好玩儿的立体书,如同演讲主题在生活里东张西望一样,池塘子是一个地地道道的生活观察家,生活中的那些无形爱都被她做成了立体书。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 使用 Git 工具进行项目管理 + +

+ +
+ + + + +
+ +
+ +参考内容: + + +Git工作流实践 + + +Git 工作流程 + + +Git三大特色之WorkFlow(工作流) + + +Git分支管理策略 + + +使用 Issue 管理软件项目详解 + + +GitLab Issue 创建及使用说明 + + +Git 提交规范 + +Git 是软件开发活动中非常流行的版本控制器类工具。随着项目时间的拉长、项目参与人员的更替、软件不同特性功能的发布,从开发人员角度看会发现工程的提交历史、分支管理等非常混乱,项目管理者会因为需求迭代、bug 修复、版本发布等活动无法与代码提交历史一一对应而头疼,混乱的管理常常导致故障泄漏给客户,所以一套规范的规范的 git 工作流程是非常有必要的。 +Git WorkFlow +WorkFlow 的字面意思即工作流,比喻像水流那样顺畅、自然的向前流动,不会发生冲击、对撞、甚至漩涡。工作流不涉及任何命令,它就是团体成员需要自遵守的一套规则,完全由开发者自己定义。 +当下比较流行的三种工作流程分别为:Git Flow、GitHub Flow、GitLab Flow。它们有一个共同点:都采用功能驱动开发。其中 Git Flow 出现的最早,GitHub Flow 对其做了一些优化,比较适用于持续的版版发布。GitLab Flow 出现的时间比较晚,所以是综合了前面两个工作流的优点制定的。 +Git Flow +git-flow 的思路非常简洁,通过 5 种分支来管理整个工程。 + + + +分支 +周期 +说明 + + + + +master +长期 +主分支,用于存放对外发布的版本,任何时候在这个分支拿到的,都是稳定的分布版 + + +develop +长期 +开发分支,用于日常开发,存放最新的开发版 + + +feature +短期 +功能分支,它是为了开发某种特定功能,从 develop 分支上面分出来的。开发完成后,要再并入 develop + + +release +短期 +预发分支,它是指发布正式版本之前(即合并到 master 分支之前),我们可能需要有一个预发布的版本进行测试。预发布分支是从 develop 分支上面分出来的,预发布结束以后,必须合并进 develop 和 master 分支 + + +hotfix +短期 +bug 修补分支,从 master 分支上面分出来的。修补结束以后,再合并进 master 和 develop 分支 + + + + +Github Flow +github-flow 可以认为是 git-flow 的一个简化版,它适用于持续部署的工程,直接将最新的功能部署到 master 分支上,不再操作 develop 分支。同时通过 CI&amp;CD 的使用,不再需要额外的 release 和 hotfix 分支。github 还结合了推送请求(pull request)功能,在合并 feature 分支之前通过PR请求其他成员对代码进行检查。 + +master分支中的任何东西都是可部署的 +要开发新的功能特性,从 master 分支中创建一个描述性命名的分支(比如:new-oauth2-scopes) +在本地提交到该分支,并定期将您的工作推送到服务器上的同一个命名分支 +当您需要反馈或帮助,或者您认为分支已经准备好合并时,可以提交一个推送请求(PR) +在其他人审阅并签署了该功能后,可以将其合并到 master 中,合并后原来拉出来的分支会被删除 +一旦它被合并到 master 分支中,就可以并且应该立即部署 + + +github-flow 最大的优点就是简单,非常适合需要持续发布的产品。但是它的问题也很明显,因为它假设 master 分支的更新与产品的发布是一致的,即 master 分支的最新代码,默认就是当前的线上代码。 +但实际可能并非如此,代码合入 master 分支并不代表着它立刻就能发布。比如小程序提交审核以后需要等待一段时间才能发布,如果在这期间还有新的代码提交,那么 master 分支就会与刚刚发布的版本不一致。另外还有一种情况就是,有的公司只有指定时间才会发布产品,这会导致线上版本严重落后于 master 分支。 +可以发现针对上述情况,一个 master 分支就不够用了,你可能需要在 master 分支之外新建一个 prodution 分支才能解决问题。 +Gitlab Flow +因为 gitlab-flow 出现的比较晚,所以它同时具备前面两种工作流的优点,并且又摒弃了它们存在的一些缺点。它的最大原则叫做上游优先(upsteam first),即只存在一个主分支 master,它是所有其他分支的上游。只有上游分支采纳的代码变化,才能应用到其他分支。 +gitlab-flow 分为两种情况,分别适用于持续发布和版本发布项目。对于持续发布项目,它建议在 master 分支以外,再建立不同的环境分支。比如,开发环境的分支是 master,预发环境的分支是 pre-production,生产环境的分支是 production。 +开发分支是预发分支的上游,预发分支又是生产分支的上游。如果产环境出现了 bug,这时就要新建一个功能分支,先把它合并到 master,确认没有问题,再cherry-pick 到 pre-production,这一步也没有问题,才进入 production。 + +对于版本发布的项目,建议的做法是每一个稳定版本,都要从 master 分支拉出一个分支,比如 2-3-stable、2-4-stable 等等。发现问题,就从对应版本分支创建修复分支,完成之后要先合并到 master 分支,然后才能合并到 release 分支。版本记录可以通过 master 上的 tag 来记录。 + +Issue 使用 +Git WorkFlow 主要解决了开发人员的问题,但是对项目管理者问题的解决力度不够,比如客户需求的管理、bug 的跟踪等。Github 和 Gitlab 提供的 Issue 功能可以比较好的解决项目的管理问题。 +Issue 中文可以翻译为议题或事务,是指一项待完成的工作,比如一个 bug、一项功能建议、文档缺失报告等。每个 Issue 应该包含该问题的所有信息和历史,使得后来的人只看这个 Issue,就能了解问题的所有方面和过程。 + +Issue 起源于客服部门。用户打电话反馈问题,客服就创建一个工单,后续的每一个处理步骤、每一次与用户的交流,都需要更新工单,全程记录所有信息。因此,Issue 的原始功能是问题追踪和工单管理,后来不断扩展,逐渐演变成全功能的项目管理工具,还可以用于制定和实施软件的开发计划。 + +下面以可以免费使用的 Github Issues 来介绍如何使用 Issue。 +创建 Issue +每个 Github 仓库都有一个 Issue 面板,如下图所示是新建 Issue 的界面。左侧输入 Issue 的标题和内容,支持 markdown 语法。右侧分别对应四个配置项,将在下面一一进行介绍。 + +Assignees +Assignee 选择框用于从当前仓库的所有成员之中,指派某个 Issue 的处理人员。 + +Labels +可以为每个 Issue 打上标签,这样方便分类查看和管理,并且能比较好的使用看板进行查看。一般来说一个 Issue 至少应该有两种类型的 Label,即 Issue 的类型和 Issue 的状态(根据需要可打第三种用于表示优先级的 Label),其中 Issue 的状态可以用来构建敏捷看板。 + +Milestone +Milestone 翻译过来叫里程碑,它的概念和迭代(sprint)或版本(version)差不多。Milestone 是 Issue 的容器,可以很方便的用来规划一个迭代(版本)要做的事情,也能非常方便的统计进度。 + +Projects +Projects 是 Github 2020 年 6 月份提供的功能,它就是项目看板的功能,Gitlab 所提供的类似功能为 Issue boards,感兴趣读者可以自行阅读文档了解。 + +PR&amp;MR +至此,可以发现我们可以使用 Issue 管理需求、bug,Milestone 又提供了迭代(版本)的计划管理,通过 Projects 可以创建敏捷看板,用于关注整体项目的进度。前文 Git WorkFlow 从开发者角度提供了项目管理的工作流程,可以思考一下还差什么问题没有解决? +最后剩下的问题是:每一个 Issue 都需要提交代码/文档进行解决,那代码/文档如何与 Issue 进行关联呢?其实无论是 GitHub 还是 GitLab,都可以很方便地在 Issue 上创建分支,在该分支上解决完 Issue 所对应的问题后,提交远程分支即可发起合并请求,在 Github 中称为 Pull request(PR),在 Gitlab 中则叫做 Merge request(MR)。 +Git 提交规范 +上文已经说了我们可以对每个 Issue 创建分支,既然是分支,那超过一个 commit 是再常见不过的事情了。一些开发人员所写的提交说明常常是fixbug或者是update等非常不规范的说明。 +不规范的说明很难让人明白这次代码提交究竟是为了什么。在实际工作中,一份清晰简洁规范的 commit message 能够让后续的代码审查、信息查找、版本回退都更加高效可靠,因为我们还需要对提交说明制定一套规范。 +那么什么样的 commit message 才算是符合规范的说明呢?不同团队可以制定不同的规范,此处推荐使用 Angular Git Commit Guide +提交格式指定为提交类型(type)、作用域(scope,可选)和主题(subject),提交类型指定为下面其中一个: + + + +类型 +说明 + + + + +build +对构建系统或者外部依赖项进行了修改 + + +ci +对 CI 配置文件或脚本进行了修改 + + +docs +对文档进行了修改 + + +feat +增加新的特征 + + +fix +修复 bug + + +pref +提高性能的代码更改 + + +refactor +既不是修复bug也不是添加特征的代码重构 + + +style +不影响代码含义的修改,比如空格、格式化、缺失的分号等 + + +test +增加确实的测试或者矫正已存在的测试 + + + +作用域即表示范围,可以是任何指定提交更改位置的内容。主题则包括了对本次修改的简洁描述,有以下三个准则: + +使用命令式,现在时态:“改变”不是“已改变”也不是“改变了” +不要大写首字母 +不在末尾添加句号 + +下图是 NocoBase 的 commit message 截图,可供参考 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 牛客网 NC632 牛牛摆木棒、POJ 1037 美丽的栅栏题解 + +

+ +
+ + + + +
+ +
+ +参考内容: +OI题解 - A decorative fence[POJ 1037] +poj1037(dP+排列计数) + +本文首发于牛客网:题解 | #牛牛摆木棒# +题目 +牛客网 NC632 牛牛摆木棒、POJ1037-A decorative fence(美丽的栅栏) +描述 +有n个木棒,长度为1到n,给定了一个摆放规则。规则是这样的:对于第 i (2≤i≤n−1)(2 \leq i \leq n-1)(2≤i≤n−1) 个木棒 aia_iai​,(ai&gt;ai−1(a_i &gt; a_{i-1}(ai​&gt;ai−1​ &amp;&amp; ai&gt;ai+1)a_i &gt; a_{i+1})ai​&gt;ai+1​) 或 (ai&lt;ai−1(a_i &lt; a_{i-1}(ai​&lt;ai−1​ &amp;&amp; ai&lt;ai+1)a_i &lt; a_{i+1})ai​&lt;ai+1​)。求满足规则的从小到大的第k个排列是什么呢。 +对于两个排列 s 和 t:如果存在 j 有任意 i&lt;ji&lt;ji&lt;j 使得 si==tis_i == t_isi​==ti​ 且 sj&lt;tjs_j &lt; t_jsj​&lt;tj​,视为排列 s 小于排列 t。 +示例 +输入:3,3 +返回值:[2,3,1] +说明:第一小的排列为:[ 1 , 3 , 2 ] + 第二小的排列为:[ 2 , 1 , 3 ] + 第三小的排列为:[ 2 , 3 , 1 ] + 第四小的排列为:[ 3 , 1 , 2 ] + 所以答案为:[ 2 , 3 , 1 ] + +备注 +(1≤n≤20,1≤k≤(n−1)!)(1 \leq n \leq 20, 1 \leq k \leq (n-1)!)(1≤n≤20,1≤k≤(n−1)!) +题意 +该问题让我们求:n 的字典序排列中第 k 个波浪形的排列。什么是波浪形排列呢?即对排列中任意一个数字(除开第一个和最后一个)aia_iai​,只能 aia_iai​ 比 ai−1a_{i-1}ai−1​ 和 ai+1a_{i+1}ai+1​ 都小或者都大。比如 2 1 3和1 3 2是波浪形排列,但1 2 3就不是波浪形排列。 +DFS 枚举 +最容易想到的解决方案是把 n 的所有排列按字典序列出来,然后再逐一检查是否是波浪形排列,直接取出第 k 个波浪形排列即可。 +我们以 3 的全排列为例画出如下树形图,非常容易的就能发现只要对这棵树进行深度优先遍历,就能够按字典序得到所有排列。但并不是所有排列都满足波浪形这个条件,所以我们每得到一个排列都需要检查该排列是否为波浪形,直到检查到第 k 个排列为止,返回该排列即可。 + +class Solution { +public: + + // 记录当前已经有多少个波浪形排列 + long long count = 0; + // 记录最后的结果 + vector&lt;int&gt; res; + /** + * + * @param n int整型 木棒的个数 + * @param k long长整型 第k个排列 + * @return int整型vector + */ + vector&lt;int&gt; stick(int n, long long k) { + // 用于标记当前考虑的数字是否已经被选择 + bool visited[25] = {false}; + // 用于记录已经选了哪些数 + vector&lt;int&gt; path; + dfs(n, 0, path, visited, k); + return res; + } + + /** + * + * @param n 可选的数字范围 + * @param deep 递归到第几层了 + * @param path 已经选的数字 + * @param visited 记录哪些数已经被选了 + * @param k 是否已经到第 k 个波浪形排列了 + */ + void dfs(int n, int deep, vector&lt;int&gt; path, bool visited[], long long k) { + // 递归层数和范围相等,说明所有的数字都考虑完了,因此得到一个排列 + if(deep == n){ + // 判断该排列是否为波浪形排列 + bool flag = true; + for(int i = 1; i &lt; n-1; i++){ + if((path[i] &gt; path[i-1] &amp;&amp; path[i] &lt; path[i+1]) || + (path[i] &lt; path[i-1] &amp;&amp; path[i] &gt; path[i+1])){ + flag = false; + break; + } + } + // 是波浪形排列,则统计一次 + if(flag) { + count++; + } + // 判断是否已经到第 k 个排列 + if(count == k) { + // 如果返回结果还没有被赋值,则将该排列赋值给 res + // 因为我们使用的是递归,所以 count==k 会被满足多次 + // 只有第一次满足时才是真正的 k 值,所以必须判断 res 是否为空 + // 如果不判空,则程序记录的不是正确结果 + if(res.empty()){ + res = path; + } + // 到第 k 个波浪形排列了,递归返回 + return ; + } + // 没有可以选择的数字了,回溯 + return ; + } + // 还没有得出一个排列,则继续挑选数字组成排列 + for(int i = 1; i &lt;= n; i++) { + // 如果该数字已经被选择了,则终止本次循环 + if(visited[i]){ + continue; + } + // 选中当前数字加入到排列中 + path.push_back(i); + visited[i] = true; + // 下一次递归所传的值不变,只有递归层数需要 +1 + dfs(n, deep+1, path, visited, k); + // 回溯,需要撤销前面的操作 + path.pop_back(); + visited[i] = false; + } + } +}; + +在 C++ 的 algorithm 库中已经提供了一个全排列方法 next_permutation。按照STL文档的描述,next_permutation 函数将按字母表顺序生成给定序列的下一个较大的序列,直到整个序列为减序为止。因此我们可以偷个懒直接使用现有的函数。 +class Solution { +public: + /** + * + * @param n int整型 木棒的个数 + * @param k long长整型 第k个排列 + * @return int整型vector + */ + vector&lt;int&gt; stick(int n, long long k) { + vector&lt;int&gt; res; + // 记录当前已经有多少个波浪形排列 + long long count = 0; + // 构造初始化排列 + for(int i = 1; i &lt;= n; i++) { + res.push_back(i); + } + do { + // 判断当前排列是否为波浪形排列 + bool flag = true; + for(int i = 1; i &lt; n-1; i++) { + if((res[i] &gt; res[i-1] &amp;&amp; res[i] &lt; res[i+1]) || + (res[i] &lt; res[i-1] &amp;&amp; res[i] &gt; res[i+1])){ + flag = false; + break; + } + } + if(flag) { + count++; + } + if(count == k) { + break; + } + } while (next_permutation(res.begin(), res.end())); + return res; + } +}; + +复杂度分析 + +我们来看一下这个深度优先遍历的时间复杂度分析,该算法的时间复杂度主要由递归树的结点个数决定。因为程序在叶子结点和非叶子结点的行为时不一样的,所以我们先计算非叶子结点的个数,我们一层一层的去计算它。 +第 1 层因为只有一个空列表,所以我们不考虑它; +第 2 层表示的意思是从 n 个数中找出 1 个数,即 An1A_n^1An1​; +第 3 层表示的意思是从 n 个数中找出 2 个数,即 An2A_n^2An2​; +以此类推,全部非叶子结点的总数为: +An1+An2+⋯AnnA_n^1 + A_n^2 + \cdots A_n^nAn1​+An2​+⋯Ann​ +=n!(n−1)!+n!(n−2)!+⋯+n!= \frac{n!}{(n-1)!} + \frac{n!}{(n-2)!} + \cdots + n!=(n−1)!n!​+(n−2)!n!​+⋯+n! +=n!(1(n−1)!+1(n−2)!+⋯+1)= n!\left(\frac{1}{(n-1)!} + \frac{1}{(n-2)!} + \cdots + 1\right)=n!((n−1)!1​+(n−2)!1​+⋯+1) +≤n!(1+12+14+⋯+12n−1)\leq n!\left(1 + \frac{1}{2} + \frac{1}{4} + \cdots + \frac{1}{2^{n-1}}\right)≤n!(1+21​+41​+⋯+2n−11​) +=n!×2(1−12n)= n! \times 2(1-\frac{1}{2^{n}})=n!×2(1−2n1​) +&lt;2n!&lt; 2n!&lt;2n! +每个非叶子结点都在内部循环了 n 次,所以非叶子结点的时间复杂度为 O(2n×n!)O(2n \times n!)O(2n×n!),去除系数后得到 O(n×n!)O(n \times n!)O(n×n!) 。 +最后一层叶子结点的个数就是 n!n!n! 个,但是我们对每个叶子结点都做了一次判断,因此叶子结点的时间复杂度依然是 O(n×n!)O(n \times n!)O(n×n!) 。 +该问题的 k 控制了遍历的次数,最好情况即 O(n!)O(n!)O(n!),最差即 O(n×n!)O(n \times n!)O(n×n!),平均一下也不过只加了个系数,因此总的时间复杂度为 O(n×n!)O(n \times n!)O(n×n!)。 +递归树的深度为 n,需要 O(n)O(n)O(n) 的空间;程序运行过程中保存了问题的最终答案,需要 O(n)O(n)O(n) 的空间,总共需要 O(2n)O(2n)O(2n) 的空间,因此该算法的空间复杂度为 O(n)O(n)O(n)。 +动态规划 +上述算法在运行过程中会超时,究其原因就是不论测试数据要求我们求第几个波浪形排列,我们都老老实实的从第一个开始数,当数据比较大时就会出现超时的情况。那么有没有办法能够减少一些不必要的过程呢?比如测试数据要求第 100 个波浪形排列,很明显前面 80 个排列肯定不满足情况,我们能否舍弃一部分搜索直接从第 80 个甚至第 90 个开始呢? +我们先不考虑波浪形排列这个条件,如果是求第 k 个全排列的话是非常容易就能算出来的。还是以1 2 3的全排列为例,假设现在要求第 5 个全排列,可以发现只要第一个数确定了,排列数就由剩下数的排列方案决定,以1打头的排列有两个,以2打头的排列也有两个,而现在要求的是第 5 个排列,所以肯定不是以1或2打头的,这样我们就能直接跳过大部分不合法的排列,节省了时间。 +仔细想想发现理想是比较丰满,上述方法的问题在于无法确定前面跳过的那部分里面究竟有多少个波浪形排列,因此这种直接计算的方法行不通。但是这个思想我们是可以借用一下的,那我们把一部分数据计算出来,尝试一下能不能找到规律。 +当 n 为 1 时,总共有 1 个波浪形排列,1 打头的有 1 个; +当 n 为 2 时,总共有 2 个波浪形排列,1 打头的有 1 个; +当 n 为 3 时,总共有 4 个波浪形排列,1 打头的有 1 个; +当 n 为 4 时,总共有 10 个波浪形排列,1 打头的有 2 个; +当 n 为 5 时,总共有 32 个波浪形排列,1 打头的有 5 个; +当 n 为 6 时,总共有 122 个波浪形排列,1 打头的有 16 个; +列出来了 6 组数据都没有发现规律,这种方式基本得战略性放弃了。我们设置 A[i] 为 i 根木棒所组成的合法方案数,列数据找规律其实就是尝试找到 A[i] 和 A[i-1] 的规律,比如选定了某根木棒 x 作为第 1 根木棒的情况下,则剩下 i-1 根木棒的合法方案数为 A[i-1]。问题在于并不是这 A[i-1] 中每一种方案都能和 x 形成一种新的合法方案。 +我们把第 1 根木棒比第 2 根木棒长的方案称为 W 方案,第 1 根木棒比第 2 根木棒短的方案称为 M 方案。A[i-1] 中方案中只有第 1 根木棒比 x 要长的 W 方案,以及第 1 根木棒比 x 要短的 M 方案,才能进行组合构成 A[i] 中的合法方案。 +因此我们可以设A[i] = 0,先枚举 x,然后针对每一个 x 枚举它后面那根木棒 y,如果y &gt; x(y &lt; x同理)则有:A[i] = A[i] + 以 y 打头的 W 方案数,但是以 y 打头的 W 方案数,又和 y 的长短有关,因此只能继续将描述方式继续细化了。 +设 B[i][k] 是 A[i] 中以第 k 短的木棒打头的方案数,则有: +A[i]=∑k=1iB[i][k]A[i] = \sum_{k=1}^i B[i][k]A[i]=∑k=1i​B[i][k] +B[i][k]=∑j=ki−1B[i−1][j](W)+∑n=1k−1B[i−1][n](M)B[i][k] = \sum_{j=k}^{i-1} B[i-1][j](W)+ \sum_{n=1}^{k-1} B[i-1][n](M)B[i][k]=∑j=ki−1​B[i−1][j](W)+∑n=1k−1​B[i−1][n](M) +公式中(W) 和 (M) 分别表示 W 方案和 M 方案,发现还是无法找出推导关系。设 C[i][k][0] 为 B[i][k] 中的 W 方案数,C[i][k][1] 为 B[i][k] 中的 M 方案数那么则有: +B[i][k]=C[i][k][0]+C[i][k][1]B[i][k] = C[i][k][0] + C[i][k][1]B[i][k]=C[i][k][0]+C[i][k][1] +C[i][k][1]=∑j=ki−1C[i−1][j][0]C[i][k][1] = \sum_{j=k}^{i-1} C[i-1][j][0]C[i][k][1]=∑j=ki−1​C[i−1][j][0] +C[i][k][0]=∑n=1k−1C[i−1][n][1]C[i][k][0] = \sum_{n=1}^{k-1} C[i-1][n][1]C[i][k][0]=∑n=1k−1​C[i−1][n][1] +至此状态转移方程就出来了,初始条件为:C[1][1][0]=C[1][1][1] = 1,下面就可以开始写代码了。 +class Solution { +public: + /** + * + * @param n int整型 木棒的个数 + * @param k long长整型 第k个排列 + * @return int整型vector + */ + vector&lt;int&gt; stick(int n, long long s) { + long long dp[21][21][2]; + memset(dp,0,sizeof(dp)); + dp[1][1][0] = dp[1][1][1] = 1; + for (int i = 2; i &lt;= n; i++){ + // 枚举第一根木棒的长度 + for (int k = 1; k &lt;= i; k++){ + // W 方案枚举第二根木棒的长度 + for (int m = k; m &lt; i; m++){ + dp[i][k][0] += dp[i-1][m][1]; + } + // M 方案枚举第二根木棒的长度 + for (int m = 1; m &lt;= k-1; m++){ + dp[i][k][1] += dp[i-1][m][0]; + } + } + } + // 标记是否已经使用 + bool visited[21] = {false}; + // 保存结果的排列 + int a[21]; + // 逐一确定第 i 位 + for(int i = 1; i &lt;= n; i++) { + int k = 0; + // 假设第 i 放 j + for(int j=1;j&lt;=n;j++) { + long long tmp = s; + // 已经使用过的数不能再使用了 + if(!visited[j]) { + // j 是没有使用过的木棒中第 k 短的 + k++; + if(i == 1) { + // 确定第一根木棒的长度 + tmp -= dp[n][k][0] + dp[n][k][1]; + } else if(j &lt; a[i-1] &amp;&amp; (i==2 || a[i-2]&lt;a[i-1])) { + // W 类型 + tmp -= dp[n-i+1][k][0]; + } else if(j &gt; a[i-1] &amp;&amp; (i==2 || a[i-2]&gt;a[i-1])) { + // M 类型 + tmp -= dp[n-i+1][k][1]; + } + if(tmp &lt;= 0) { + visited[j]=true; + a[i]=j; // 第 i 位为 j + break; + } + } + s = tmp; + } + } + // 将结果转换为指定格式 + vector&lt;int&gt; res; + for(int i = 1; i &lt;= n; i++) { + res.push_back(a[i]); + } + return res; + } +}; + +复杂度分析 +最开始初始化dp数组时用了 O(2n2)O(2n^2)O(2n2) 的时间,随后填写dp数组花的时间为 O(n3)O(n^3)O(n3),计算最终答案的时间为 O(n2)O(n^2)O(n2),将结果转为指定格式的时间为 O(n)O(n)O(n),所以该算法的时间复杂度为 O(n3)O(n^3)O(n3)。 +dp数组占用了 O(2n2)O(2n^2)O(2n2) 的空间,标记数组visited、保存结果的数组a,以及最终转换为指定格式的path向量,各占用了 O(n)O(n)O(n) 的空间,取最大值即该算法的空间复杂度为 O(2n2)O(2n^2)O(2n2),去掉系数得到最终空间复杂度 O(n2)O(n^2)O(n2)。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 媒体的谎言|楼下的小餐馆|服务质量的思考 + +

+ +
+ + + + +
+ +
+ 上个月很多地方都可以看到一个新闻,90 后安徽女孩 16 岁时到杭州打工,省吃俭用拼命存钱就为了在杭州买房,十年时间存了将近 100 万。但是 2020 年的疫情对她的服装生意影响太大,她先是自己在网上卖淫,后又通过社交软件招募失足妇女卖淫,半年时间获利近 60 万。这个女孩的故事本身是很励志的,但网上的故事版本基本是下图这个样子的,一句话概括就是「女孩卖淫获利 160 万」,简单明了、易传播、方便吃瓜,吃瓜是第一生产力。 + +相比之下,那些通过掩盖部分事实来教育国人的文章,到显得不那么可恶了一些。比如都说英国人喜欢在地铁看书看报纸,对比我们国人在地铁都是看手机显得没啥素养,但却很少文章提及伦敦地铁修建于 100 年前,由于隧道小致使安装通信设备的难度极大,所以他们的地铁是没有手机上网条件的。全线实现手机上网后,咱再来看看他们看书还是看手机! +媒体总喜欢发一些坏的东西,毕竟更容易吸引眼球且符合人的本能,人还没死但去世的相关新闻已经出来了,故意掩盖部分事实让大众理解扭曲。考虑到「流量就是金钱」这个前提,有时候也能理解现在媒体的做法。在台湾电视剧《我们与恶的距离》中就有一个片段,主角很清楚一个新闻能带来的收视率,但是自家的记者还没有考究到该新闻的真实性,当其它电视台都在报道这个新闻时,自家电视台应该随大流报道,还是坚守新闻人的底线? + +人把自己置身于忙碌当中,有一种麻木的踏实,但丧失了真实,你的青春,也不过只有这些日子。你看到什么,听到什么,做什么,和谁在一起,有一种从心灵深处满溢出来的不懊悔,也不羞耻的平和与喜悦。 +——电影《无问东西》台词 + + +用了差不多一年时间观察楼下的几个小餐馆,第一个引起我注意的是一个叫做「小鲜肉烧烤」的店,同期在它的隔壁也是一个烧烤店,在离它不到一百米的地方还有一个烧烤店。那时候正值寒冬,烤串拿出来很容易就变凉,大家都知道烤串凉了就不好吃了,这时小鲜肉烧烤自己买了个大棚子,同时还给每个桌子放了一个保温盘。 +有了大棚子遮风,顾客就不需要忍着寒风吃烤串,而且保温盘让烤串一直都是热的。其它两家则一直是都是让顾客在忍着寒风吃串,我有一次骑行回来因为烤串上的太快了,里面的羊肉串中间那一坨肥肉都凝固成油泥了,骑行腐败的心情大打折扣。 +小鲜肉烧烤专门有个人在外面守着,那眼睛就跟老鹰一样犀利,顾客还没有坐好菜单就送到面前了,另外两家则是师傅专心烤串,进去了是一脸懵逼不知道找谁,点完餐也不知道交给谁。过完年回来,楼下只剩一家烧烤店了。 +紧接着小鲜肉烧烤的隔壁被一对夫妻租了去,他们做的是羊肉米线,想来两口子定是雄心勃勃、信心满满的。前三天用了一点小技巧引流,活动期间可以凭点餐券再免费领一份羊肉米线,出于占便宜和好奇的心理,开业第一天我就去了这家店,看的出来米线是在水里泡的太久了,而且表现非常的不光滑,猜测是为了节省成本用了便宜的原料。 +大概坚持了一周的样子,这家店在饭店就无人问津了,左邻右舍的店铺都忙的不可开交,这家店铺是老公和老婆对坐玩手机,「葛优躺」完美的挂在了脸上,店里显得没有生机更是让人不想踏进去一步。这个商铺过了不到半个月就转手了。 +接盘侠是一个做冒菜的老板,老板开业第一天就大火,服务好、菜品多、吃法新,生意一度优于旁边的烧烤店。大概过了半个月的样子,估计老板觉得店里比较稳了,于是开始把店里的事情逐渐放给员工,好几次我去那家吃都没有看到老板。 +没有老板在自然一些细节把控不好,比如从厨房到餐桌的过道肯定是走最多,而那条过道上的污渍即使你有意无视它,它也能非常容易的钻到你眼睛里;顾客选择的是干拌冒菜,但是却在盘子里看到非常多的汤水;煮菜也看得出来做了流程化处理。 +就这样冒菜店的生意以非常明显的速度在下滑,好在老板意识到问题又回来每天坚守阵地,但总体来说已经比不上刚开店的时候了,现在是和小鲜肉烧烤的人流量基本持平。 + +会有意识的去观察这些事物,大概和我现在从事的工作有关系。我现在是一名少儿编程老师,大学做家教教学生编程时,觉得只要自己专业技能够硬,学生问问题都能回答出来就可以,喜欢用一些听起来牛逼的专业词汇给学生讲课。现在想想那时候真是傻,那时候觉得人家听不懂叫牛逼,现在明白人家听不懂叫装逼。 +开始主动的去反思自己的问题,讲课的流程、节奏、纪律、有趣等等,并且有意识的提升课堂的仪式感。现在讲课的问题还非常多,但也收获了一些学生和家长的认可。一个孩子我接手了之后得到的奖升了一级,家长一激动给我发了「感谢恩师」,笑死我了😂 +一个学生课上说自己的一个同班同学「还在渣渣学而思学习编程」,虽然孩子只是随口一说,但是这句话让我心中暗喜。除了在外面上课外,我同时还在一些小学上社团课,一个学生想要到外面上课非选我不可,不然就不交钱报名...... +体会到服务业的一些乐趣,也真正认识到了质量的重要性,质量的轻微变动顾客都能感受到。尽力做好自己可以做的事情,但是也要明白事情不是自己能完全掌控的,就像张小龙说的跟漂流瓶扔出去一个瓶子是一个道理,看到以后发生什么不是我们所能够掌控的。 +最后放两个学生的趣味作品吧。 + + + +
+ + Read More ~ +
+
+
+ +
+

+ + 信用卡航司里程和境外返现 + +

+ +
+ + + + +
+ +
+ 在信用卡基础知识入门中已经提到过银行会和各大公司搞联名卡,其实当你把信用卡了解到一定程度之后就会发现银行、航司、电商、酒店都是相互关联的。信用卡可以匹配像希尔顿、万豪等一些酒店的高级会员,酒店高级会员又可以匹配航司高级会员。 +提到航司这里建议各位小伙伴乘机时都先注册一个会员账户积累里程,很多人可能并没有去关注里程这个东西。以乘坐南航航班从成都到广州为例,用 12000 里程就可以兑换一张机票,相当于就省去了一张机票钱,最近是特殊时期一张机票看起来价格并不贵,要搁平时咋样也是会省 1000 来块钱的。 + +我之前也不懂航司里程这些东西,看了下航旅纵横发现我在 19 年总共乘坐了 17 次航班,其中有 4 次乘坐的是海南航空的航班,我并没有注册海南航空的会员,导致这几次航班的里程都没了。在 20 年我还使用几千南航里程和几千凤凰知音里程就换了几本书和一袋核桃,现在才知道当时换的是相当不划算的,一半的机票就被自己玩没了。 +大多数人都只是在春节时需要来回飞一趟,靠乘坐航班积累里程的方式是不现实的,更加靠谱的方式就是通过信用卡。国内银行的白金卡基本都可以兑换航司里程(国外我也不懂),而且国内的信用卡非常乱,比如之前我用的招行金卡有 9 万多的额度,浦发给的一张白金卡才 6000 额度(已注销),所以一般额度有几万的都可以尝试去升级成白金卡。 +比如中行和南航推的联名白金卡消费 10 元就可以积累 1 里程,这些消费本身都是平时会产生的,顺道积累一下里程它不香吗?配上撸货(俗称黄牛)那就是非常可观的量级了,稍微勤奋一点应该是一年二三十万里程没有问题。 +信用卡除了积累里程外通常还会有其它的权益,比如搭配一个信用卡接送机的权益把来回机场的车票钱也给省下。比较重要的是信用卡的延误险权益,一种要求用带险种的卡片去购买机票才可赔付,另外一种是不论以什么方式购买机票只要延误就给赔。应该有小伙伴已经在网上看过有人撸了几百万延误险,结果把自己给搞进去了。再次提醒一下撸羊毛可以,但是别心太狠去宰羊。 +曹大也写过一篇关于里程信息差套利的文章,我也是上个月才发现很多平台的积分都是可以互通的,比如平安银行的万里通积分就和各大航司、运营商、零售等挂上了钩,也可以通过这种方式把各种平台的积分都聚合到一起。 + +信用卡肯定要用起来才会有收益,除了国内日常所产生的消费可以积累一些积分外,还有一个门槛比较高的就是境外消费返现。比如建行出的「MUSE卡鎏金版」境外消费笔笔 1% 返现,精选商户还有 8% 的返现,聪明的小伙伴看到这个应该就能联系到「海外代购」了吧。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 信用卡基础知识入门 + +

+ +
+ + + + +
+ +
+ 相信绝大部分伙伴都使用过「花呗」这个产品吧,当月花下月还很契合现在的提前消费理念。花呗有账单日和还款日之分,需要在每个月的还款日之前将上期的账单还清,否则就会产生逾期记录进而影响自己的征信。当然如果确实没有足够的钱也可以选择账单分期,只不过需要支付一定的分期手续费。花呗的这些机制与信用卡一致,但是相比信用卡花呗就显得很抠门了,抽空对信用卡做了一点研究,就分享到这里一起交流。 +虽然我从毕业开始就一直在用信用卡,但是我也一直没有搞明白信用卡的逻辑。比花呗更长的免息期,送开卡礼、首刷礼,用信用卡消费还给你积分兑换礼品、抵现或话费,偶尔还会给出一些支付立减的优惠(比如我前段时间用支付宝就老是遇到美国运通卡的立减金),银行难道是脑袋发热才这么送钱吗? + +用脚趾头想都知道银行的目的肯定是为了赚钱,但是为啥又白白的把各种权益送给你呢,所以我们有必要了解一下银行为什么要发行信用卡?不管是在线上还是线下消费,只要使用了信用卡进行结账,那么商家就需要给出一定的手续费。比如商家支付了 100 元的手续费,银行会拿到 60 元的利润,银联拿到 5 块钱,剩下的交给支付通道公司,同时还会根据「商户编码」给到你一定的积分。所以当你使用信用卡进行消费时,银行就会赚到钱。 +上一段提到了「商户编码」的概念,这个就像我们参加高考时老师给贴的条形码一样,是用来识别商户的。在教育、慈善一类的商户消费,银行是没有钱赚的,所以银行也不会给到你积分,我们可以把这类称之为「无积分商户」。银行就是根据商户编码来识别你刷的商户类型,具体可以查看刷卡之后小票商户编码 8-12 位。 +国内支持的都是银联卡,不过美国运通的业务已经在国内出现,比如我目前正在使用的招行百夫长信用卡,就是一张美国运通卡,它在国内已经支持了线上消费。国外支持银联的不多,所以很多信用卡都会在银联卡之外给配一张外币卡,有人说外币卡会占用自己的授信额度,如果不出国就不要申请你那张附属卡。 +信用卡是分不同等级的,比如普卡、金卡、白金卡、黑金卡。一般金卡及以下都是直接免年费或是可以通过一定的消费免年费的。白金及以上大部分都需要几千的年费,但是提供的相关权益也非常不错,比如航司里程、体检服务、机场贵宾厅、五星级酒店等等,不过白金及以上的下卡难度也大,具体可以看自己的实际情况去申请。 +现在银行都会和各种公司联合发一些联名卡,比如我手里的平安爱奇艺联名卡,每个月只需要有三笔消费超过 188 元,下个月就可以领一张爱奇艺黄金会员月卡。像我这种视频平台会员权益,日常消费所累积的积分,加上银行平时的一些像「5 倍积分」活动,以及利用账单日、还款日这些免息期,就是妥妥的羊毛党味道。 + +我去年一年的话费都是使用平安的积分充值,相当于白嫖了一年话费。从深圳搬到成都冬天太冷没有被子,又用招行的积分换了被子和一些收纳箱。银行给你这些通道,就是默许你可以撸羊毛,但你千万别贪心把毛拔秃了甚至要宰羊,不然就会很容易把自己给撸进去。 +不要小看撸羊毛这个行业,有的人能撸羊毛年入千万。我认识的人里面也有靠撸羊毛完全能养活自己的,再简单一点也有玩免费机票、酒店的。我觉得这个行业有意思的地方就是你日常生活的每一项都可以撸,电影票、外卖、水电话费、餐饮等等,但是玩信用卡玩着玩着也有一个问题,我现在哪怕在超市买瓶水也会不自觉的计算用哪张卡更划算。 +最后放一张闲鱼的截图做个引子吧,有兴趣的伙伴可以自己去研究,我先暂且写这么多。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + KK 给年轻人的 68 条建议 + +

+ +
+ + + + +
+ +
+ KK 是凯文·凯利的网名,他曾经担任《连线》杂志的第一任主编,是著名的科技评论家,也是畅销书《失控》的作者。去年的 4 月 28 日是他 68 岁生日,他在个人网站上发表了一篇给年轻人的 68 条建议,文章被翻译成了十几种其它语言,今年 4 月 28 日老爷子又续写了一篇给年轻人的 99 条建议,本文是给年轻人的 68 条建议中文翻译版,翻译除了借助 DeepL 机器翻译工具外,更多参考自KK 在 68 岁生日时给出的 68 条建议。 +Learn how to learn from those you disagree with, or even offend you. See if you can find the truth in what they believe. +学着从那些你不认可甚至冒犯你的人身上学习,看看能否从他们的信仰中找到真理 +Being enthusiastic is worth 25 IQ points. +充满热情可以抵得上 25 点智商 +Always demand a deadline. A deadline weeds out the extraneous and the ordinary. It prevents you from trying to make it perfect, so you have to make it different. Different is better. +做任何事都应该设一个 deadline,它可以帮你排除那些无关紧要的事情,也能避免过分要求自己尽善尽美。努力去做到与众不同,差异比完美更好 +Don’t be afraid to ask a question that may sound stupid because 99% of the time everyone else is thinking of the same question and is too embarrassed to ask it. +不要害怕自己问的问题看起来很愚蠢,99% 的情况下,其他人和你有一样的问题,只不过他们羞于问而已 +Being able to listen well is a superpower. While listening to someone you love keep asking them “Is there more?”, until there is no more. +倾听是一种超能力,当听到你喜欢的人说话时,要不时的追问「还有吗」,直到他们没有更多的东西可讲 +A worthy goal for a year is to learn enough about a subject so that you can’t believe how ignorant you were a year earlier. +一个有意义的年度目标是去充分了解一个学科,这样你就会对一年前的无知感到难以置信 +Gratitude will unlock all other virtues and is something you can get better at. +感恩可以解锁其它所有的美德,也是你可以继续做的更好的一件事情 +Treating a person to a meal never fails, and is so easy to do. It’s powerful with old friends and a great way to make new friends. +请一个人吃饭是非常简单的一件事情,不仅仅是老朋友,这也是结交新朋友的有效方式 +Don’t trust all-purpose glue. +不要相信万能药 +Reading to your children regularly will bond you together and kickstart their imaginations. +经常给的孩子读书不仅能巩固你们之间的感情,也能帮助孩子开启想象力 +Never use a credit card for credit. The only kind of credit, or debt, that is acceptable is debt to acquire something whose exchange value is extremely likely to increase, like in a home. The exchange value of most things diminishes or vanishes the moment you purchase them. Don’t be in debt to losers. +永远不要用信用卡去透支。唯一可以接受的透支或负债,应该是那些通过负债有极大可能获得增值的事物,比如房屋。绝大多数事物在你买下它的那一刻就开始贬值了,别为那些没有未来的事物透支 +Pros are just amateurs who know how to gracefully recover from their mistakes. +专业人士不过是善于从挫折中优雅爬起的菜鸟 +Extraordinary claims should require extraordinary evidence to be believed. +要想让人相信非同寻常的观点,就需要非同寻常的证据 +Don’t be the smartest person in the room. Hangout with, and learn from, people smarter than yourself. Even better, find smart people who will disagree with you. +别成为一群人中最聪明的那一个,和那些比你聪明的人待在一起,向他们学习。如果能找到和你观点相反的聪明人,那就更好了 +Rule of 3 in conversation. To get to the real reason, ask a person to go deeper than what they just said. Then again, and once more. The third time’s answer is close to the truth. +对话中的「数字 3 原则」。想要找到一个人真正的意图,那就请他把刚才说的话再深入一些,如此反复直到第三遍,你就能比较接近真相了 +Don’t be the best. Be the only. +不做最好的,去做唯一的 +Everyone is shy. Other people are waiting for you to introduce yourself to them, they are waiting for you to send them an email, they are waiting for you to ask them on a date. Go ahead. +每个人都很害羞,其他人正等着你向他们介绍你自己,等着你给他们发送邮件,等着你约他们见面。大胆的向前走 +Don’t take it personally when someone turns you down. Assume they are like you: busy, occupied, distracted. Try again later. It’s amazing how often a second try works. +别人拒绝你的时候不要往心里去。假设他们和你一样忙碌、腾不出手、心烦意乱,再试一次,第二次成功的几率超乎你的想象 +The purpose of a habit is to remove that action from self-negotiation. You no longer expend energy deciding whether to do it. You just do it. Good habits can range from telling the truth, to flossing. +习惯的意义在于无需再为某类行为纠结,不用再消耗精力去觉得是否做这件事。干就完了,讲真话和使用牙线都是很好的习惯 +Promptness is a sign of respect. +及时回应是表示尊重的一种方式 +When you are young spend at least 6 months to one year living as poor as you can, owning as little as you possibly can, eating beans and rice in a tiny room or tent, to experience what your “worst” lifestyle might be. That way any time you have to risk something in the future you won’t be afraid of the worst case scenario. +当你年轻的时候,应该至少花半年到一年的时间,过尽可能穷的日子,拥有尽可能少的身外之物,居陋室而箪食瓢饮,体验你可能会经历的最穷困潦倒的生活。这样,在未来任何时候,你都不用担心最坏的情况 +Trust me: There is no “them”. +相信我,没有「他们」 + +个人理解,KK 大叔想表达的意思应该是:太阳底下无新事,每个人都是历史的参与者 + +The more you are interested in others, the more interesting they find you. To be interesting, be interested. +你越有兴趣了解别人,别人就会发现你越有趣,要成为有趣的人,先要对别人感兴趣 +Optimize your generosity. No one on their deathbed has ever regretted giving too much away. +常行慷慨之事,没有人会在死的时候后悔给予的太多 +To make something good, just do it. To make something great, just re-do it, re-do it, re-do it. The secret to making fine things is in remaking them. +想要做好一件事,干就完了。想要做一件值得称赞的事情,那就重做一遍,重做一遍,再重做一遍。制造好东西的秘诀在于不断的重做 +The Golden Rule will never fail you. It is the foundation of all other virtues. +金科玉律永远不会让你失望,它是所有其他美德的基础 +If you are looking for something in your house, and you finally find it, when you’re done with it, don’t put it back where you found it. Put it back where you first looked for it. +如果你正在你的房子里寻找什么东西,那么用完后不要放回你找到它的地方,而是放到你最初找它的地方 +Saving money and investing money are both good habits. Small amounts of money invested regularly for many decades without deliberation is one path to wealth. +存钱和投资是好习惯。几十年如一日的定期进行小额投资(定投),是一条致富之路 +To make mistakes is human. To own your mistakes is divine. Nothing elevates a person higher than quickly admitting and taking personal responsibility for the mistakes you make and then fixing them fairly. If you mess up, fess up. It’s astounding how powerful this ownership is. +犯错是人之常情,承认错误是神圣的。认错并勇于担责,再认真弥补过错,没有什么比这更可贵了。是自己搞砸的就勇于承担,这反而能彰显你的强大 +Never get involved in a land war in Asia. +永远不要在亚洲陷入地面战争 + +KK 大叔这句话没读懂 + +You can obsess about serving your customers/audience/clients, or you can obsess about beating the competition. Both work, but of the two, obsessing about your customers will take you further. +你可以专注于你的顾客、听众或客户,也可以沉迷于在竞争中获胜,这两种方法都行之有效,但是专注于服务你的客户会让你走的更远 +Show up. Keep showing up. Somebody successful said: 99% of success is just showing up. +在场,坚持在场,某个成功人士说过:99% 的成功只不过是因为在场 +Separate the processes of creation from improving. You can’t write and edit, or sculpt and polish, or make and analyze at the same time. If you do, the editor stops the creator. While you invent, don’t select. While you sketch, don’t inspect. While you write the first draft, don’t reflect. At the start, the creator mind must be unleashed from judgement. +将创造过程与改进过程分开,你不可能在写做的同时进行编辑,也不可能在凿刻的同时进行打磨,更不可能在制造的同时进行分析。如果你这么做,求善之心就会打断创造之意;创新时就要忘掉已有方案;勾勒草图时就不能太着眼于细处;写作时,先打草稿而不要去抠细节。在新事物之初,创意的思想必须得到无拘无束的释放 +If you are not falling down occasionally, you are just coasting. +如果你从未跌倒过,那么你也就从未努力过 +Perhaps the most counter-intuitive truth of the universe is that the more you give to others, the more you’ll get. Understanding this is the beginning of wisdom. +也许宇宙中最违反直觉的真理就是,你给予他人越多,你收获的就越多,这是智慧的起点 +Friends are better than money. Almost anything money can do, friends can do better. In so many ways a friend with a boat is better than owning a boat. +朋友胜过金钱。金钱几乎可以做任何事情,但朋友可以做得更好。很多时候,自己有条船不如有个有船的朋友 +This is true: It’s hard to cheat an honest man. +相信我,你很难欺骗一个诚实的人 +When an object is lost, 95% of the time it is hiding within arm’s reach of where it was last seen. Search in all possible locations in that radius and you’ll find it. +当一件物品丢失时,95% 的情况下,它都藏在人们最后一次看到它时触手可及的地方。在这个半径范围内搜索所有可能的地点,你就能找到它 +You are what you do. Not what you say, not what you believe, not how you vote, but what you spend your time on. +你做什么就是什么。不是你说什么,不是你相信什么,更不是你支持什么,而是你把时间花在了什么上 +If you lose or forget to bring a cable, adapter or charger, check with your hotel. Most hotels now have a drawer full of cables, adapters and chargers others have left behind, and probably have the one you are missing. You can often claim it after borrowing it. +如果你遗失或忘记带电缆、适配器或充电器,不妨去问问你的酒店。大多数酒店都会有满满一抽屉的电源线、适配器和充电器,这些东西都是别人留下的,没准儿其中就有你的,酒店也并不介意你借用后随身带走 +Hatred is a curse that does not affect the hated. It only poisons the hater. Release a grudge as if it was a poison. +仇恨是一种诅咒,但它不会影响被仇恨的人。它只会毒害仇恨者,把你的怨恨当作毒药一样丢掉吧 +There is no limit on better. Talent is distributed unfairly, but there is no limit on how much we can improve what we start with. +没有最好,只有更好。个人的天分有高有低,但不论高低,自身的提升都永无止境 +Be prepared: When you are 90% done any large project (a house, a film, an event, an app) the rest of the myriad details will take a second 90% to complete. +任何一项大工程(修房子、拍电影、开发 app)完成度为 90% 的时候,你都要做好心理准备:剩余的大量细节工作同样需要 90% 的时间来完成 +When you die you take absolutely nothing with you except your reputation. +当你死的时候,除了你的名誉,你什么都无法带走 +Before you are old, attend as many funerals as you can bear, and listen. Nobody talks about the departed’s achievements. The only thing people will remember is what kind of person you were while you were achieving. +在你年老之前,尽可能多地参加葬礼并听听别人的谈话,没有人会谈论逝者的成就,人们能记住的只有逝者在成功时是什么样的人 +For every dollar you spend purchasing something substantial, expect to pay a dollar in repairs, maintenance, or disposal by the end of its life. +你每花一美元在实体店购买一件东西,将来都要再花一元钱去维修、保养,或是在它报废后处理掉它 +Anything real begins with the fiction of what could be. Imagination is therefore the most potent force in the universe, and a skill you can get better at. It’s the one skill in life that benefits from ignoring what everyone else knows. +任何真实的东西都来源于虚构的想法,想象是宇宙中最强大的力量,也是你可以做的更好的一种能力,生命中可以因不知众人所知而获利 +When crisis and disaster strike, don’t waste them. No problems, no progress. +当危机和灾难来临时,不要错过他们,没有问题就没有进步 +On vacation go to the most remote place on your itinerary first, bypassing the cities. You’ll maximize the shock of otherness in the remote, and then later you’ll welcome the familiar comforts of a city on the way back. +度假时,先绕过城市去行程中最偏远的地方。这样你就能最大程度地体验到异域风情带给你的冲击,而在返程的路上,又可以享受熟悉的城市所带给你的舒适 +When you get an invitation to do something in the future, ask yourself: would you accept this if it was scheduled for tomorrow? Not too many promises will pass that immediacy filter. +当你被邀请在未来的某个时间点做某件事情时,问问自己:如果是明天,你会接受邀请吗?绝大多数邀约都经不住这种迫切性检验 +Don’t say anything about someone in email you would not be comfortable saying to them directly, because eventually they will read it. +如果一些话你不能当面对某人说出口,那么就不要在邮件中对他评头论足,因为他们最终会看到邮件 +If you desperately need a job, you are just another problem for a boss; if you can solve many of the problems the boss has right now, you are hired. To be hired, think like your boss. +如果你只是迫切需要一份工作,那你只是老板的另一个问题;如果你能解决许多老板眼下的问题,那你自然能得到这份工作。要想得到一份工作,就要像老板一样去思考 +Art is in what you leave out. +艺术藏身于你遗忘的地方 +Acquiring things will rarely bring you deep satisfaction. But acquiring experiences will. +获得物品很少能给你带来深刻的满足感,但是经验却能做到 +Rule of 7 in research. You can find out anything if you are willing to go seven levels. If the first source you ask doesn’t know, ask them who you should ask next, and so on down the line. If you are willing to go to the 7th source, you’ll almost always get your answer. +研究的「数字 7 原则」。当你愿意就一个问题深入七层时,总能找到你想要的答案。如果你问的第一层人不知道,那么就问问他们应该去找谁,如此追索下去,你几乎总能得到你的答案 +How to apologize: Quickly, specifically, sincerely. +如何道歉:迅速、具体、真诚 +Don’t ever respond to a solicitation or a proposal on the phone. The urgency is a disguise. +永远不要在电话上面答应一个请求或提议,所谓的急迫不过是一种假象 +When someone is nasty, rude, hateful, or mean with you, pretend they have a disease. That makes it easier to have empathy toward them which can soften the conflict. +当有人对你粗鄙、无礼、刻薄,甚至是下流时,当他们有病就好了,这使得我们更容易对他们产生同情心,进而缓和冲突 +Eliminating clutter makes room for your true treasures. +清理杂物,为真正重要的东西腾出空间 +You really don’t want to be famous. Read the biography of any famous person. +你绝对不会想出名,不信的话可以随便找本名人传记读读 +Experience is overrated. When hiring, hire for aptitude, train for skills. Most really amazing or great things are done by people doing them for the first time. +经验往往被高估了,招聘时应该多看资质,技能是可以培训的。许多令人惊奇和赞叹的事情,都是新手做出来的 +A vacation + a disaster = an adventure. +度假 + 灾难 = 冒险 +Buying tools: Start by buying the absolute cheapest tools you can find. Upgrade the ones you use a lot. If you wind up using some tool for a job, buy the very best you can afford. +购买工具:从最便宜的开始,升级那些使用频次高的。如果你的工具是用于工作,那么买你能买得起的最好的 +Learn how to take a 20-minute power nap without embarrassment. +学会毫不尴尬的打 20 分钟小盹儿 +Following your bliss is a recipe for paralysis if you don’t know what you are passionate about. A better motto for most youth is “master something, anything”. Through mastery of one thing, you can drift towards extensions of that mastery that bring you more joy, and eventually discover where your bliss is. +如果你不知道自己热爱什么,追寻心之所向往往会带你误入歧途,对年轻人来说,更好的格言是:master something, anything,在精通一件事的过程中,你可以顺着带给你更多快乐的方向继续深入,并最终发现你热爱的东西 +I’m positive that in 100 years much of what I take to be true today will be proved to be wrong, maybe even embarrassingly wrong, and I try really hard to identify what it is that I am wrong about today. +我敢肯定,我今天认为正确的许多东西在 100 年后将被证明是错误的,甚至可能是令人尴尬的错误。而我非常努力在做的事情,就是去识别我对今天的错误认知 +Over the long term, the future is decided by optimists. To be an optimist you don’t have to ignore all the many problems we create; you just have to imagine improving our capacity to solve problems. +从长远来说,未来由乐观主义者决定。作为一个乐观主义者并非要对我们制造的问题视而不见,而是要想象如何提升我们解决问题的能力 +The universe is conspiring behind your back to make you a success. This will be much easier to do if you embrace this pronoia. +整个宇宙在背后密谋让你成功,要相信,天助人愿 + +
+ + Read More ~ +
+
+
+ + + +
+ + 上一页 + + + 下一页 + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/page/3/index.html b/page/3/index.html new file mode 100644 index 00000000..79b73e71 --- /dev/null +++ b/page/3/index.html @@ -0,0 +1,1400 @@ + + + + + + + + Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ + +
+

+ + 通过在生财有术捡碎片每月赚顿火锅钱 + +

+ +
+ + + + +
+ +
+ 有段时间看到有人在生财讨论「外卖淘客」的案例,生财有术的公众号也转发了一篇手把手教学的文章:睡后收入:适合小白操作的一个自动赚钱项目,当天晚上读了这篇文章之后便立马操作起来了。 +先是去关注了一堆做外卖淘客的公众号,看一下他们的操作流程和话术。紧接着把自己以前申请的公众号改名字、改头像、改功能介绍,去公众号后台设置关注后自动回复。然后按照上面文章中的介绍去闲鱼引流,但是闲鱼上面放的链接一直无人问津,只是一直机械的去学习同行,优化闲鱼的自动回复话术。 + +大概过了快一周的时候,微信上的一个朋友告诉我她想领红包时候发现我放的二维码已经失效了。虽然一直还没有进一分钱,但是这个朋友的反应让我觉得这个事是有希望的,至少还是有人会惦记外卖红包的,马上又去换了新的二维码继续推。然后第二天又告诉我二维码失效了,加上自己使用的是个人号而不是企业号,每天主动推送消息的次数有限,而且会被折叠隐藏起来,这时就有一点心灰意冷了。 +继续换上新的二维码默默期待,玩了不到两周赚了不到 10 块钱的样子,后面又失效了两次后就放弃了,但是这一小段时间的实践让我验证了闲鱼引流的可行性。 +瞎翻生财帖子的时候发现了卡券回收,一个可以靠信息差赚取认知以外的收入,心想简单转变一下就可以卖各个视频平台的会员卡啊。马上去闲鱼修改链接为腾讯视频、爱奇艺会员卡,发现不允许上架这一类的商品,那就只能靠图片去传递信息了。图片怎么做?石墨文档甩张图片、调一下字体、上个色,截一张图就是一张介绍图片。 + + +这下有效果了,第一天就有人来咨询会员卡怎么卖的了,但是好几天都只是有人咨询却并没有成交的客户,而且每天都只是零零散散的 10 个人左右咨询。想起来在生财精华帖看到过需要刷单,闲鱼成功交易的商品再次上架会提高权重,每天编辑重新发布也会获得更多的推荐量。 +找了三个朋友刷了其中一个爱奇艺链接的单,见效非常的快,当天晚上 9 点多就开始不停的有人咨询,我一直回复到了 12 点多,忘记那天赚了多少钱了,大概是一块鸡排的钱吧。从第二天开始就不断的有人来咨询,已经多到我回复不过来了,因为我还有别的工作要做。 + +晚上回家就设置了闲鱼的自动回复,将之前的外卖话术转变一下引导加我的微信,虽然流量一直都还不错但是却没有一个加我微信的,于是又改为自己手动回复了。连续这样玩了好几天真心觉得累,因为中间的利润太低了,稍微一提高人家就不买了。于是又去找成本更低的货源,总算找到另外一个靠谱且更低价的平台,每天可以稳定的进来一顿早饭钱,但却一直没有增长。 +注意到进来咨询的人大致可以分为两类,一种是给家里长辈买电视版会员的群体,针对这种群体我就狂推年卡,因为年卡是我可控范围内能将彼此利益最大化的商品,这个没什么好说的。另外一种是只要周卡的学生群体,周卡基本没有什么利润,于是稍微转变了一下思路,对于这类群体就问对方是想要看哪个剧?引导对方加微信,我直接帮他找资源,然后随意给我发个红包就行了。 +那段时间进来的很多人都是想看「使徒行者 3」,我一直没看过这部剧也不知道它讲的是什么,但是人家需要就对了,恰好我知道有个影视资源平台更新的速度非常快,看使徒 3 不仅要会员还得付费点播,把影视资源平台直接发给对方,都会发 3-15 元的红包给我。 +后面又尝试过设置自动回复引导加微信,但是一旦设置自动回复效果就没了,使徒3 完了之后效果也有比较明显的下跌,我自己玩了不到两个月时间,如果一直玩下去差不多每月能吃一顿火锅,我个人觉得投入产出比太低了就没再做了。 +目前微信上还剩一个非常信任我的客户,上来就简单粗暴的给我转钱,服务他到变成了一种小乐趣。2 月份发现这种视频会员需求进来的人直接推外卖 CPS 也可以,当时加了差不多 100 个微信,距离停止视频会员项目已经快 3 个月了我才在朋友圈发了几天饿了么的二维码,还是有效果的。 + + +在抖音直营中心工作的朋友告诉我,在投放广告按原有方式定位不到人群时,可以通过设置其它兴趣来定位人群,比如喜欢看可爱宠物视频的人群可能也很喜欢买衣服,喜欢刷剧的人极有可能也喜欢点外卖、吃零食,「外卖淘客」这段时间很火,给各位提供一个引流的思路,也欢迎你加我微信我们一起交流相关内容。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 那些经历过的人生谎言 + +

+ +
+ + + + +
+ +
+ 哈佛大学从 1938 年开始做了一个研究,研究内容就是「到底什么样的人活的最幸福?」,研究组在 75 年时间里跟踪了 724 个不同的人,到教授在 TED 演讲的时候仍然有 60 人在世并仍参与研究,大多数人都已经 90 岁了,现在已经在开始研究他们的子孙后代,并且人数达到了 2000 多人。 +这 724 人后来成了医生、律师、砖匠等,有人成了酒鬼、有人患上了精神分裂症,还有一位成了美国总统。估计也只有哈佛这样的地方才会去搞如此变态的研究,有足够的资金去承受这样理想的研究。人人都想要活的更加幸福,哈佛 75 年研究汇集成几万页的数据,然而结果只有一句话:良好的人际关系能让人更加健康和快乐,和我们所追求的财富、名望、努力工作等都没关系。下面是原视频内容。 + +小学语文课本上面有一个课后题要求自己编写《负荆请罪》故事的后续,记得当时老师把这个题目当作家庭作业留给学生了。回家之后只顾着玩泥巴、看电视了,第二天早上第一节课就是语文课,老师一上来就抽不同的同学给大家读自己写的故事后续。 +在其他同学读故事的时间,我慌忙的写了一段应付老师的抽查。不出所料,我也被抽中起来读自己编的故事了,没想到慌慌张张编的故事居然深得老师之心,还被老师当作范文给大家又读了一遍,因为我在故事里面用了「连忙」、「赶紧」这样的一些词汇,能表现出廉颇和蔺相如之间的客气等等。 +但我清晰记得那时候只想在被老师抽中之前把作业写了,胡编乱造完全不管写的好不好,只需要有一段话能够证明自己是做了作业的,万万没想到那么简单的一段话被老师赋予了数不清的情感。可类比的是各种网络平台上的文章,尤其是某个公司哪一位老总成功上位,铺天盖地的文章会把这个人肠子都翻出来看看,就好像写手比主人公还要了解自己。 + +最开始写文章是大四时候太闲想找点事做,断断续续到现在也算是写了 2 年多了,合起来有 100 来篇文章的样子。偶尔我会把以前写的文章搬到其它平台,比如把以前写的讲磁盘原理和自己做自然语言处理的两篇文章搬到 InfoQ,两篇文章都被平台置顶且精选至首页,而在公众号中讲磁盘原理的那篇文章阅读量是最低的。 +前不久写了一篇与拼多多等电商平台有关的内容,一发出来公众号后台立马将近 20 个取关,让我这个本就没几个读者朋友的公众号雪上加霜。然后我鼓起勇气把这篇文章复制到满是大佬的生财有术社群里面去,隔了几天就被评为了精华帖,拿到一颗龙珠,8000 元到手。 +去年年底想起了和师兄们一起刷牛客网题目的时光,随手就把 18 年写的一点文字贴上去了,18 年时候我的技术还很弱鸡,没想到贴上去却获得了还不错的反馈,一篇还被牛客小编标记为精选帖。于是我把 19 年遇到的关于服务器性能优化的一篇文章贴上去,那篇是我花了一个月时间查各种资料再加上自己实践的经验,现在看来都依然觉得写的非常不错,然而这篇文章在牛客网连一个赞都没有。 +我写文章是兴趣驱动,写的好写得坏都不咋关心,但偶尔会有那么一两篇文章自己花了很多的精力去写,写完还自嗨一下写的不错,结果一发出来就打脸了。也出现过我自己敷衍了事像是一时应付写的文章,却被好几个大 V 看中,希望转载的。 +忘记 18 年怎么加了「寒泉子」微信的,当时他输出的文章很多是关于 Java 虚拟机性能优化的,没有一篇文章我能读懂的,所以寒泉子的文章对我完全没有作用,围观他的朋友圈只是看大佬是如何成长的,那时候寒泉子创建「笨马科技」公司不久(现在笨马已经获得了高瓴资本的投资)。成功创业且快速的成长让寒泉子得到了更多人的关注,寒泉子的文章相比那时候阅读量提高了很多,但我依旧看不到他文章对我的价值,但我知道看不到好文章价值的原因是因为我的水平不够。 +说了这么多就是想表达很多文字不是没有价值,只是因为你的水平太低而看不到他的价值(水平太高也会看不到价值),并不一定都是别人写的差。一篇文章给了你一个关键字那他就是有价值的,我们更多的是应该去培养自己抓线索的能力,一个不起眼的词语就能揪出来太多有趣的东西,只是大多数人即使把方法告诉他了都还是不会操作,需要努力提升自己的信息素养。 + +读了些书、见了些人、经历了些事,总结了几个人生的高级谎言。中年危机是不思进取的人经历的,尽量不要被社会上那些营销话术给骗了,少看那些什么发展各种副业挣钱的广告,把搞这些的精力好好用到工作上薪资早早的就涨起来了,尤其是玩副业几年都没有玩出花样的人,更应该把心收一收踏踏实实工作。 +大家都知道「复利效应」这回事,比如 100 块钱按每年 5% 的利息计算,50 年后就能变成 1000 多块钱,看这个收益是不是非常可观?这个谎言的真相是你找不到年化能长期稳定在 5% 的投资产品,市面上各种理财培训都会说这个谎言,一些人自己本来没有赚到钱,却通过教别人怎么赚钱而赚到钱了。 +30 岁之后身体就不行了、女人生娃一定要在 28 岁之前。乍听起来没有什么问题,但仔细一想就会发现这里采用年龄进行量化就不对,科学的量化应该是采用身体机能的各项参数。年龄只是一个表象的东西,成熟也同样与年龄无关,而与经历相关。 +观察了一些电信诈骗的案例,有认识的朋友也有从未谋面的网友,自己也协助警方做了一次证人。会主动给你发营业执照、自己工牌一类信息的就是骗子。如果销售多次给你强调自己是正规公司,那他们基本就是不正规公司。凡是你根本不认识的人,但一上来就说要带你赚钱的,简单的当骗子处理即可,百分百不会错杀一个好人。 +每个人都有一套稳定且自洽的逻辑闭环,自己的观念代表的是自己这个人,观念被否定相当于自己这个人被否定,可能这就是会吵架的一个内在原因,「老顽固」大概也是这么来的。人天生就喜欢呆在舒适区,不同观点发生碰撞就是把稳态打破,别人否定自己是一件极难接受的事情,为什么会难以接受呢?因为打破稳态需要思考,而思考是一件费能量的事情,生物的天性就是要节省能量,这大概和祖先生活的环境有关系,虽然现在的环境已经不需要这样做了,但遗憾的是基因却还没有适应,自己努力克服基因的缺陷吧。 + +学生 A 就读于成都一所中法合办的小学,进出这个学校多次之后的感受就是学校有钱,学生家里也都比较有钱,所以大部分学生都属于调皮类型的,综合该校多名学生口中的信息,每个班至少气跑了两名以上的老师。学生 A 在我处学习编程,他们班的同学都属于比较社会的那种,班主任老师也被这帮学生训练的贼有经验。 +学校有一个厕所坏了,所以物管处就把这个厕所给锁上了,然而这种被锁上的门总是能激起学生的好奇心,很多学生下课就跑去门缝观察 💩,甚至有的学生还翻墙进去观察 💩,里面最有创意的学生应属学生 A。 +厕所里面被拖把遮住了,学生 A 在门缝寻找到了一个比较好的观察角度,大吼了一句「奥利给溢出来了」,另一个学生并没有看到所以就翻墙进去看,却一不小心掉进坑里了,学生 A 嘴里吼着「XXX 满是奥利给的跑出来了」跑开。估计这是有史以来「奥利给」这么正能量的词语被侮辱的最惨的一次。 +此事被学生 A 的班主任老师知道了,要说就得佩服这种班级的老师,不打不骂只让学生以「奥利给」为主题写一篇观察日记,要求学生把观察 💩的过程详细描述出来。学生 A 和班主任老师配合的这出戏在学校广为流传,大大提高了学生 A 的名声。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 幸福可以慢慢浇灌长大 + +

+ +
+ + + + +
+ +
+ 电影《点球成金》讲的是一个没有资金实力的弱队如何翻身的全程记录,在薪水预算只有强队 10% 的情况下如何去争夺冠军。与大多数体育片不一样的是,这部电影是从管理层入手的,推荐想要创业或是正在创业的人看一看,在明知道自己的做法是对的,但是又没有人支持、没有人看好的情况下,一个领导者应该怎么办?当明知开除某个球员就意味着他职业生涯的结束,甚至会将他推向生活窘迫的深渊,作为一个管理者是否会一时心软而手下留情...... +电影中的彼得(耶鲁大学经济学硕士)采用了一种全新的方法(数据分析)挑选球员,当然这种方法现在看来已经见怪不怪了,军事运筹学中也使用了相当的方法来计算如何赢得一场战斗。把这样的方法应用到自己行业的难点在于「量化」,我觉得企业管理者在这方面可以多思考思考。 +《原则》一书中作者也提到类似的点,将自己平时做决策的过程量化、抽象,将其转换成计算机可以理解的模型,计算机是没有那些乱七八糟的情绪的,只要把模型抽象的足够好就可以降低自己的成本,还能提高容错性。书中提到了对员工采用了性格测试进行分工,我现在是比较认可通过这种方式来决定员工具体工作的,想起来以前参加校园招聘的时候,一度认为那些大企业给员工做性格测试是不是钱多的没处花了,现在想想还是自己太傻。 +出于好奇我也给自己做了个 MBTI 测试,发现我是「表演者」人格类型,了解了一点关于我自己性格上的缺点,一些无伤大雅的问题大可以听之任之,一些需要改进的地方我相信也是可以通过持久的努力改变的。我目前对性格测试也有些怀疑,但我很清楚它比星座说「你是个渴了就会喝水的人」更具科学性一些。 + +我特别羡慕的两个人是读库的创始人老六和自己上大学时遇到的花儿,还不知道老六的朋友可以花一两分钟翻一下为啥人人都爱《读库》的老六?在我看来老六做的东西已经不叫工作了,而是叫事业了,不知道我这一生能不能在某个时间节点达到那样的状态,或是更可能这一生都达不到。刚好前几个小时看完了读库 2020 年(北京站)的视频,老六提到了六个问题应该是大多数人都存在的,比如「没有应变能力的坚持」、「不给时间压力便遥遥无期」,点击读库年会(北京站)完整版视频可以观看。 + +我第一次遇到花儿是在上大学的时候,那时候她还在成都宽窄巷子做青旅,几次到成都我都是到她那个青旅住下,因为在那里总是能遇到很多有趣的人。大概在大二快结束的时候我看到花老板发了朋友圈,才知道她去到大理做青旅了,也是通过朋友圈知道了她还有一个公众号,现在主要是通过花老板的公众号观察她的生活。我羡慕花儿那种把日子过的很幸福的状态,各方面细节可以都可以看到花儿是一个很知足的人,写歌、作曲、唱歌,日子看起来好不快活,花儿的公众号名称叫「微小而确实的日常」。 +日本的杂物管理咨询师山下英子写了一本《断舍离》,从 20 年 1 月看到这本书开始,我一直都在学习和实践断舍离,我个人觉得断舍离的人生整理理念是很受用的,我们的人生就应该多尝试断舍离,更多的东西只会让自己的脑袋更像浆糊,当然这可能和商业行为有些背离。 +商业广告会告诉你他们研发了一种减肥药或是减肥茶的产品,使用之后就能快速的瘦身塑形,但仔细想想要瘦下来不应该是减少碳水化合物的摄入吗?为什么要一边给自己打针又一边狂吃高胆固醇的食品呢?已经看不下去的书、已经挽回不了的爱情这些不都应该舍弃吗?商业广告会告诉你需要做加法,但其实你真正应该做的是减法。 + +从学校毕业已经快要有 3 年了,真正开始积累社会经验是 20 年下半年,大胆在这里分享自己学到的一些东西。看一个人靠谱不靠谱不要听他的嘴巴怎么说,而是要看他怎么做事、怎么做人、别人对他如何评价的。如果想要看一个人的能力如何,看看他之前干成过什么事就可以了,干成的事情是最好的佐证。如果他老是强调自己是多么的可靠,自己的实力是多么的强劲,那他基本就是个不可靠且没啥实力的人。 +一些文章会用「众所周知」、「有研究表明」、「很多学者指出」、「我身边有很多例子」这样的开头来证明某个观点,那大概率文章中内容是在瞎扯淡,如果要你要反驳的话他还能举出来更多的例子,不与这样的作者进行争辩是比较节约时间的。想了一下这个问题以前在我身上也挺严重的,现在已经减轻很多了。 +找工作的时候看一个公司靠谱不靠谱可以看给你缴的社保多少,比如公司实际给你发的薪资是一万,但是缴纳社保的基数却填个 3500,说到底就是公司舍不得给员工花钱,舍不得给员工花钱的公司你还想从他那里得到点什么呢?同样那种挖空心思避税的公司也不值得去,会和税务机关玩猫腻的公司难道还差和员工玩猫腻的胆子吗?我遇到过这种公司,工资分几笔发到手就是为了避税。 + +春节回老家发现了一个有趣的现象,长辈们手机里面必备的两个应用是微信和抖音,他们大多数都很愿意在抖音上面分享自己的生活,即使拍的作品土得掉渣也非常乐于「分享美好生活」。长辈之间有很多许久没有联系的儿时玩伴或是亲戚,有一些都超过 10 年没有联系过了,甚至连对方长什么样子都不太记得了。 +不得不承认抖音的推荐算法确实很牛,十多年没有产生联系的朋友抖音能帮你找到,再通过抖音私信互换微信,老友居然就这样再次取得了联系。用微信打个视频电话问候一下老友,通过了解对方正在做的事情还能缩小信息差,这样的聊天还能让他们的心情变得更好。 +做过一段的信息流广告,真真切切体会到了「如果你没有花钱买产品,那你就是被卖的产品」,这是纪录片《监视资本主义:智能陷阱 The Social Dilemma》中的一句话,以前在阅读高于自己的作品,远离精神毒品中批评过抖音、快手一类的产品,但现在我得改一点这个观念,50 岁以上的人没事时候刷刷抖音挺不错的,年轻人还是别去当被卖的产品了。 + +在深圳有一次和老叔吃饭的时候他讲了一点自己的经历,我已经记不得他年轻时候在哪个公司了,当时他请客在阳光酒店(深圳第一家五星级酒店)吃饭,只需要签个字就行了,那时候非常的牛气。他说正是那个时候把他害了,自己得意忘形不思进取,不然他现在也不至于都 60 多了还要给老板低身下气的工作。 +另一个老叔也分享了他年轻时候在湖北一个国企工作的经历,职位很高且收入不低,非常宠爱自己的小儿子。现在儿子要买车子直接给他打电话:“老爸,我要买车你给我多少钱?”他说自己没有钱,儿子回了句:“那你没有钱还有什么用?” +过年回家姨夫告诉我他前面几十年都活的迷迷糊糊的,一直快到 50 岁了才找到自己的方向。他说人知足而且有某个东西能让自己自信,就会过的比较幸福,自信不应该来源于金钱,要懂得赞美别人,要明白女人也是很幸苦的。我惊讶的发现姨夫作为一个大学生眼里瞧不起的农民工,已经比大学生高出好几个 level 了,而且他的收入也已经比 996 的程序员高出几个 level 了。 +最后推荐一个轻松的视频吧,一席的演讲视频杨小峰:寻找昆虫,里面不仅有很多精美可爱的昆虫照片,还有很多大开脑洞的玩味,而且这个老师有脱口秀的那味道。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 我自己的职场故事与经验 + +

+ +
+ + + + +
+ +
+ 本文首发于牛客网的社畜职场交流圈。 +从毕业后就基本没有看过牛客网了,前段时间再打开牛客网看发现有了很多新的板块,讨论区也变得比以前要活跃很多,话题也从仅仅的找工作面试类延伸到了职场、租房等等,牛客也开放了几个创作者计划的活动,我也用自己的职场故事和经验来参加一下。 +我的经验是第一份工作很重要但是没有大家想象的那么重要。我一直都是一个马大哈,签三方协议的时候面试官告诉他他给我安排的是做 5G 的部门,看到一大堆硕士生拿着简历都在签三方协议,在场的本科生没有几个,我啥都没有细问就直接签了。 +到公司报道的时候才知道部门是做 5G 测试的,心里一下有些失落觉得可能后面的开发之路就毁了。但实际上我想错了,等入职培训完了之后真正进入工作时候才知道,我所在的团队守护着 2G、3G、4G 和 5G 的所有测试架构,团队平时为了提高部门测试效率所开发的工具已经早早的延伸到无线院,而真正的 5G 开发部使用的语言是汇编语言和 C 语言,每个部门只负责 5G 产品的一小部分,具体到某一个人就负责的更细粒度了。 +所以一个有趣的现象就出现了,因为我们团队的人需要维护 5G 的测试架构,竟然对产品的了解要比开发部的人更加全面一点(当然深度肯定是远远不及开发部的人),而且开发部的人也得使用我们团队开发的工具,不管开发部的人怎么怼测试人员,我们都是有办法怼回去的。 +从我个人和一些职场人士的交谈来看,第一份工作并不会决定你将来要做的是什么工作,更多的是学习为人处事的态度,适应社会与职场上不管你喜欢还是不喜欢的规则,但是请注意你的技术栈是由你的工作决定的,你自己是决定不了你的技术栈的。 +第二个经验就是物以稀为贵唯独知识不是。无线院准备做几个工具解决几个部门都存在的问题,要求每个部门出一个人参与一个工具的开发,我们部门被分配做一个 5G 数据度量系统。测试部门的技术栈无非就是 python 什么的,整个部门 200 多人会写前端的不超过 10 个,而这个度量系统后端使用的是 java,前端使用的是 vue,部门再一眼数过去部门没有人用过 Vue,而有 Java 经验的只有我和我师傅,师傅是团队的小组长无法抽身干这些对部门没多少收益的事情(度量一看就知道是给领导们用的)。 +按照正常的安排我这个时候是应该被派到枯燥的基站上玩 3 个月的,而且基站相关的师傅都早早的给我分配好了,因为这个度量系统我稀里糊涂的被派到南京出差了,当然也就免于去干无聊的基站测试工作,顺便还给了大把的时间让我学习以前不知道的 Vue。 +说实话这个度量系统本身没有什么技术性,但是它却让我接触到了 5G 的产品经理、需求教练等等,每次和他们开会让我逐渐有了一点从领导视角去看问题的意识,他们能把问题看的更加全面、透彻,而且大多数时候都是在做取舍。 +记得高中化学老师开了个玩笑,地震过了你就不学逃生知识了?你不知道我们这个山沟沟很容易发生泥石流吗?那一段时间学习的 Vue 技术并没有浪费,我后面从 0 到 1 开发一个系统的时候,刚好就用 Vue 将团队厚重的 Angular 给替代了,也没想到我现在从事少儿编程教育工作居然还用到了那时候的技术(开发平台的前端)。 +希望你别领会错我的意思,不是让你像打散弹枪一样去胡乱学习一大堆知识,而是抓住每一次学习的机会尽量把它理解透彻。 +第三个经验是保持谦卑之心,尽量独立解决问题。进入职场之后每个人都有自己的工作,不再像学校那样你问老师一个问题,老师会把答案告诉你还生怕你学不会,所以遇到问题先去网上搜一搜资料,搜索引擎前 8 页的链接都点开看一下,百度不行就上谷歌,谷歌不行可以去 Github 搜一搜,要是还不行的话就去 StackOverflow 上面提个问题。 +如果你按照我上面说的路径都找不到答案的话,那这时再去请教一下部门的老员工,有了前面的探索你提出来的问题会更加有水平,高水平的提问也会帮助你逐渐赢得老同事们的认可,想一下天天提的问题像个小学生一样,可能也就是自己认为是好学多问。 +不管你认可还是不认可,同时给 A 和 B 抛出同样的一个问题,A 能把问题解决而 B 不能解决问题,那 A 就是要比 B 牛逼。我越来越相信实力是赢得别人认可的基石,能独立解决问题在一定程度上也说明你是个靠谱的人。能解决小问题人家才会把更大的问题交给你,不要嫌弃那些小事情,把小事情做到精致别人就会给你更大的成长机会。 +第四个经验是用心去做事,快快乐乐服务同事。不管你的技术多么牛逼,你敢拍着胸脯说自己写的程序没有 bug 吗?如果同事给你说你的软件哪里有问题,虚心的接受并快快乐乐的帮人家解决问题,他们是你的天使用户,如果你连自己的天使用户都留不住那还怎么留住外面的用户呢? +由于 5G 测试用例实在太多了,所以我和另一个同事一前一后负责开发测试任务管理系统,那是我第一次做复杂交互的前端系统,刚开始的几个版本我写的烂的要命,有的按钮甚至能让用户卡四五十秒,每天都会接到五六个同事的电话说系统太卡了,但是我自己那段时间也没办法啊,技术水平不够完全不知道怎么去优化。 +所以同事不管什么问题我照单全收,还专门列了一个问题表,每次同事在旁边说的时候我打开往里面添加记录,自己的产品不行就先把服务做好嘛。很多费时费力的操作我索性加班帮同事搞定,所以那段时间系统虽然难用的要死,但是没有一个同事直接用邮件或是当面怼我的,虽然我知道他们是脸上笑嘻嘻心里妈卖批,但我眼睛只能看到脸上的笑容。 +除了学习技术优化系统性能外,我还自己看了一些关于设计的文章,不懂设计那我就想着怎么让用户用着舒服呗。逐渐系统都能做到实时响应并且美观大方当然操作还尽量简单,被其它部门的同事看到偶尔会有几个主动询问的,加上师傅的推动很容易就把系统推开了。 +在师傅的敦促下我将其做了平台化开发,离职前已经将西安部门接入系统。那段时间与在美团和去哪儿工作的学长们交流,他们在定级答辩时评委更关心的是你如何把系统推广出去的,里面用到了什么你觉得牛逼的技术在他们眼里并不牛逼。 +回到最开始的第一份工作内容不太重要,我现在是一个少儿编程老师同时做着少儿编程平台的研发工作,本来想好好的做一个少儿编程老师就行了,谁能想到之前的程序员经历让我在新的环境竟然更加有竞争力。借用别人的话来说就是,应该多一点洒脱,人生会给你更多的可能。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 分享一点关于租房经验 + +

+ +
+ + + + +
+ +
+ 在牛客网上看到一个创作者计划,分享关于租房、理财一类的帖子可以获得一定的奖励,这篇文章首发于牛客网,稍微做了一点点补充再发到本博客平台。 +估计大多数小伙伴都是在类似自如、贝壳、蘑菇租房等平台上找房,关于这些平台的经验网上已经能够查到很多了,我就不再去分享关于这些平台的避坑经验了。这里分享一点关于我的稍微偏门一点的租房方法吧。前面提到的自如那些平台至少是有一个作用的,可以通过它们大致了解某个地域租房的价格区间,基本可以定位哪里便宜、哪里热门,基本上自己去找的话都不会比这些平台还要贵。 +豆瓣租房讨论组上面可以找到一些转租、直租的信息,比如我要在深圳南山区租一间小房子,那么我可以去豆瓣搜索「深圳南山租房」,进入相关的讨论组可以看到豆油们的一些讨论信息,除了能找到一些房源信息外还可以通过大家的评论了解一丢丢避坑指南。 + +需要注意的是即使是豆瓣这样纯粹的平台,里面也免不了很多水军或是广告信息,还有一些中介也会在里面发信息,这个就需要自己去甄别过滤无效信息了,实在不行可以加个微信聊一聊就知道了嘛。另外不要轻易把自己的常用电话留到平台去,不然什么安装宽带的、其它包租公司、甚至什么洗衣服洗鞋一类电话有可能会打到你心烦。 +如果已经定了要去哪个城市参加工作,那招你进去的 HR 这个资源是可以使用的,稍微大一点的公司都会有内部社区平台,这种平台应该只有入职之后才能使用。公司的同事也会在上面发一些关于租房的信息,大可以让 HR 帮助自己看一眼截个图或是转发给你,因为都是同一个公司的同事所以遇到坑的可能性要小很多,毕竟抬头不见低头见的,说不定哪一天你们还会一起共事呢,内部人要实在一些。 +如果能联系到已经入职的朋友、师兄师姐、前辈更好,找他们问一问公司同事一般都在哪里租房,哪里的房子住着舒服、房租便宜、通勤方便,这些信息只有他们才掌握的最精准,所以先问一下他们绝对不是脱裤子放屁,运气好的话说不定还有师兄师姐正好要转租房子呢,当然也可以让他们帮你推荐一下房东信息,这样可以快速的帮助你缩小搜索范围。 +假设你在 A 地点工作,可以以 A 地为中心看一看周围 5 公里的地方,一些房好价低的房东不一定懂的如何在网上发布租房信息,如果有时间的话可以去溜达溜达(反正你也是要逛超市买生活用品的嘛),看到一个有意思的小区就去和大爷唠唠嗑,尤其那种看着年龄不小又缺人唠嗑的大爷,他们嘴巴里能吐出来很多有用的信息。 +实际看房的时候需要注意一下房间的朝向和楼层,楼层太低照不到阳光住着压抑,楼层太高夏天可能会非常的热,在深圳那种城市的高楼层空调是极有可能顶不住酷暑的。还得注意一下周围有没有工地什么的,不然太吵睡不好觉也烦心。一些基本生活设施也要对比一下,比如离超市有多远、出门约会是否方便等等。 +签约时问好房间物品损坏应该怎么赔偿,房租到期押金应该怎么退?每天晚上大门是否会锁?忘记带钥匙/房卡怎么处理?钥匙/房卡丢了补办是否要另交费?房间有问题的地方一定要拍个照片留个证据,避免到时候退房的时候有争议被扣押金。像水电这些应该每个人都会问的,我这里就不再赘述了。 +虽然互联网已经很发达了,但是还是有很多房东不知道如何在网上发布招租信息,或者是发布招租信息是要花钱的,这个门槛把一些房东给挡在互联网之外了。他们会在一些社区公示栏、或者在朋友门店显眼的地方贴一些联系方式,这些信息只有自己实际去走动才能拿到。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 用 flomo 管理自己的奇思妙想瀑布流 + +

+ +
+ + + + +
+ +
+ 使用 flomo 已经有一段时间了,太喜欢它的简洁与便捷了。它的微信输入方式可以随时随地记录突然冒出来的灵感;使用微信读书的时候看到一段写的很漂亮的文字,顺手贴到 flomo 的小卡片中便于下一次再回顾;使用 #todo 标签记录一些重要的待办事项...... +基本上的脑力劳动者都会有记笔记的习惯,我也习惯去捡日常零零星星掉下的小拼图块,找到一个适当的时机再用这些小拼图块进行排列组合完成一个小作品,作品可以是一篇文章、可以是某个问题的解决方案,亦或是简单的工具、词句集合。 +我用了「拼图块」这个词是因为我觉得人接收的信息就是碎片化的,尤其在充斥着各种奶头乐 APP 的时代,信息被磨揉的更加细碎无营养。不管是生活中还是工作上遇到的问题,很多都不是简简单单的接收一点碎片化知识就能找到解决方案的。学习新知识也是逐个去吸收小的知识点,再用这些小的知识点构建自己的知识体系,一些让主干更加粗壮,另一些让枝桠更加繁茂。 +flomo 背后的笔记理念就是去捡那些小小的拼图块,这和我现在的理念是保持一致的。借用 flomo 网站上的话说就是我们不可能都成为作家或者发表论文,但是我们都需要记录和思考。写 MEMO(卡片)而不是写文章的价值在于,能让我们更好的思考。 +大部朋友记笔记都仅仅是在辛勤的记录,最重要的思考环节却被忽略了,在之前写的你如果只是一直囤干货,那永远不可能进步中也提过没必要去假装学习。这里没有倡导不去记笔记的意思,而是找到一个适合自己的记录方式,笔记究竟要记什么中有一部分答案。 +我使用的第一个笔记软件是有道云笔记,用了有将近两年的时间,让我放弃它的原因是文件同步老出问题,另一个原因就是速度太慢了。印象笔记更是使用时间三天都没有超过,它那些看似强大实际却毫无用处的功能严重分散了我的注意力,这违背了我记笔记的初衷。 +还有像为知笔记、石墨文档一类的软件其实也还不错,但是和印象笔记、有道云笔记类似,它们的共同问题都是以文章的形式在组织笔记。这让记笔记变成了一件极为费时的事情,我看到一篇文章的字数少于 600 就难受,强迫症患者。 +所以有一段时间我选择了使用本地 VsCode + Markdown 插件方式记笔记,Markdown 的标题语法可以很轻松的将每个小片段分开,不同的文件(名)自然而然就变成标签了。一个新的技术点记到「技术.md」中,一段摘录记到「摘录.md」中。不过因为 VsCode 本身是一个方便程序员使用的文本编辑器,所以我这种方式记笔记总是有一点别扭,具体哪里别扭我自己也说不出来,总之就是用起来差那么点感觉。 +这里不得不提一下现在比较流行的数据库类型软件 Notion,这个工具做的让我有一种只有我想不到没有它做不到的错觉。团队在设计一个数据度量系统的时候,我还多次提出过借鉴 Notion 中 Block 的思想。Notion 是一款很优秀的软件,但仅对记笔记这件事来说它显得大材小用了,功能过于强大、使用过于灵活到变成了我不选择它作为笔记软件的原因。 +一小段时间使用知识星球做为笔记软件,但是它的搜索功能做的太弱了。一直使用到现在的笔记软件是微信,我建了只有我一个人的群,一些突然冒出来的想法、读书时的思考与摘录、todo things 都直接通过对话框发到群里。我看到有很多朋友也用了我类似的方法,选择发送到文件助手,这种方式的好处是可以借用微信强大的「查找聊天内容」功能,虽然有些鸡肋但用起来也还凑合。 + +其实我的记笔记方式是逐渐在向 flomo 靠拢的,虽然它出现的比较晚。flomo(浮墨笔记)看起来像是一个个人版的 twitter,或者就像少楠自己说的是一个加强版的文件传输助手,没有多余的功能去扰乱我的视线,就是一个简简单单的流式布局一元笔记软件。 +大多数习惯于像装抽屉一样去组织文件,windows 的文件系统也是这样设计的,不同的文件夹放不同的类别的文件,看起来好像很符合我们现实生活的打扫房间的场景。但不知道你有没有意识到每次去找一个具体的文件夹都要耗费大把的时间,这种初衷极好的分类整理方式竟然渐渐变成阻止我们去记录绊脚石。 +一款好用的文件系统更多的应该聚焦于搜索上,当用户搜索时能够快速的返回与之相关的文件才是关键,而不是把目光放在文件的分类上面。同样,一款好的笔记软件也应该是这样的,今天我看到一朵花,恍惚记得之前有记过与花相关的笔记,当我去搜索时它能够快速返回我想要的记录就 OK,flomo 在这两点上做的刚好甚得我心。 +当然 flomo 还开放了 API 功能,这让记笔记这件事变得方便且有趣,比如在 iOS 上选中文字发送到 flomo 就变成了一个 MEMO,同样也可以实现在 Mac 上选中文字发送到 flomo,不过我使用最多的还是它的微信输入和随机漫步功能。 + +记笔记是为了更好的让自己思考,不要像微信和 QQ 收藏那样,让 read it later 变成了 read it never。最后说一下可以通过邀请链接注册 flomo 获得 28 天 PRO 会员。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何求两个数的最大公约数 + +

+ +
+ + + + +
+ +
+ 求几个整数的最大公约数大致有三种方法,求多个整数的最大公约数可以拆分为求两个整数的最大公约数,所以核心问题还是求两个整数的最大公约数。 +穷举法 +很直观就能想到穷举法,先找出两个数字中比较小的那一个min,然后逐个验证从2 ~ min的数字是否能被两个数整除,如果能同时被两个数字整除那就是公约数,找出其中最大的那个公约数就是所求的结果。 +int gcd(int a, int b){ + int min = a; + if(b &lt; a){ + min = b; + } + for(int i = min; i &gt; 2; i--){ + if(a%i == 0 &amp;&amp; b%i == 0){ + return i; + } + } + return 1; +} + +辗转相除法 +辗转相除法是欧几里得想出来的,所以也叫做欧几里得算法。它的证明过程依赖于一个定理:两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数,即gcd(a, b) = gcd(b, a mod b),其中 gcd 表示最大公约数,此处假设 a &gt; b。其证明过程如下所示: +设 c = gcd(a, b); +则存在 m,n,使 a = mc,b = nc; +令 r = a mod b; +则存在 k,使 r = a - kb = mc - knc = (m - kn)c; +所以 gcd(b, a mod b) = gcd(b, r) = gcd(nc, (m-kn)c) = gcd(n, m-kn)c; +所以 c 为 b 与 a mod b 的公约数; + +设 d = gcd(n, m-kn); +则存在 x,y,使 n = xd,m-kn = yd; +所以 m = yd + kn = yd + kxd = (y + kx)d; +所以 a = mc = (y + kx)dc,b = nc = xdc; +所以 gcd(a, b) = gcd((y+kx)dc, xdc) = gcd(y+kx, x)dc = dc; +因为 gcd(a, b) = c,所以 d = 1; +即 gcd(n, m-kn) = 1,所以 gcd(b, a mod b) = c; +所以 gcd(a, b) = gcd(b, a mod b); + +证明 gcd(y+kx, x)dc = dc,即 gcd(y+kx, x) = 1: +前提条件:gcd(x, y) = 1; +假设 gcd(y+kx, x) != 1,则肯定 gcd(y+kx, x) &gt; 1,设 gcd(y+kx, x) = i; +则 y+kx = ui,x = vi; +则 y = ui - kx = ui - kvi = (u-kv)i +则 gcd(x, y) = gcd(vi, (u-kv)i) = gcd(v, u-kv)i +因为 gcd(y+kx, x) = i &gt; 1,gcd(v, u-kv) &gt;= 1; +所以 gcd(x, y) &gt; 1,与前提条件矛盾; +所以 gcd(y+kx, x) = 1 + +有了上面的基础之后,我们就可以总结出来一个算法实现的步骤了。设 r = a % b;如果 r 为 0 的话,那么 a 和 b 的最大公约数就是 b,否则就是求 b 和 a%b 的最大公约数。 +// 递归写法 +int gcd(int a, int b){ + // 用 b 来存储 a%b 的值 + if(b == 0){ + return a; + } + return gcd(b, a%b); +} + +// 迭代写法 +int gcd(int a, int b){ + while(b != 0){ + int t = b; + a = t; + b = a % b; + } + return a; +} + +可以看到在算法实现过程中并没有先找出来最小的数字,这是因为程序会自动将最较大的那个数字放到 a 的位置,比如将gcd(75, 1000)带入我们的递归算法中则会变成gcd(1000, 75)。 +辗转相减法 +辗转相减法也叫更相减损术(尼考曼彻斯法),也是一种简便的求两个数的最大公约数的算法,它的特色是做一系列减法,从而求的最大公约数。比如两个自然数 36 和 27,用大数减去小数得 9 和 27,这时 9 小于 27,需要将两数交换即得 27 和 9,继续相减可得 18 和 9,然后 9 和 9,这时就可以得到两数的最大公约数为 9 了。其证明过程如下所示: +设 gcd(a, b) = x,a &gt; b; +则有 a = mx,b = nx,m,n 均为正整数且 m &gt; n; +c = a - b = mx - nx = (m - n)x; +因为 a 和 b 均为正整数,所以 c 也能被 x 整除; +所以 gcd(a, b) = gcd(b, a-b) + +具体的算法实现步骤在第一段已经有一个比较清晰的例子了,这里可以直接给出实现代码。 +// 递归写法 +int gcd(int a, int b){ + if(a == b){ + return a; + } + return a &gt; b ? gcd(a-b, b) : gcd(a, b-a); +} + +// 迭代写法 +int gcd(int a, int b){ + while(a != b){ + a &gt; b ? a = a - b : b = b - a; + } + return a; +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + 2020 年个人总结 + +

+ +
+ + + + +
+ +
+ 还是照例给自己写一个年终总结。持续分享,黄金万两。 +一点感动 +7 月份回深圳办离职,提前通电话告诉了老叔到达深圳的时间。本来是 8 点就能到的,由于飞机晚点导致我半夜 3 点才到老叔那里。期间老叔每隔半小时就给我打电话问到哪里了,我好几次叫他先睡不用管我,我完全可以就近找个酒店舒舒服服的睡一觉,他都坚持说没事,等我。 +让我感动的是我坐的航班落地时间大概是两点,本来以为老叔已经睡了,结果一发消息老叔居然没睡还在等我。到家时他已经把早早就做好的啤酒鸭、可乐鸡翅、猪脚脚热好了,拿出了自己泡的茅台酒,陪他喝到了四点半。早上八点半的样子我被吵醒了,我脑袋还是晕乎乎的,老叔就已经做好了鲫鱼粉,那味道可真的香。 +第二天我请团队同事们吃饭,老叔和之前地产圈的朋友们去喝酒去了,大概十一点的样子老叔给我打电话来了:“小刘,你在哪里?”我一听就知道老头子喝的不少:“老头,你是不是喝醉了?你现在在哪里啊?用不用我去接你?”那一头来了一句:“我现在在树下。”搞得我哭笑不得。我叫他就近找个酒店睡,第二天早上再回来。老头子说:“不行,你在这里我爬都要爬回去,你不用管我,我回去等你。”等到我回去的时候发现他已经在打呼噜了,第二天早上起来还傻乎乎的问我车费是谁付的。 +11 月份和师兄们去参加惠州白盆珠的 100 km 骑行,拐了个弯先去了一趟深圳。老头看到我到了居然开心的连自己办公室密码给忘了,我俩在那里排列组合试了半小时才打开办公室门。当天晚上和叔唠了很久的嗑,第二天给我说昨天太开心了,5 点钟才睡着。 + +老叔是我刚到深圳时所租公寓的管理员,准备抽空专门给老叔写一篇文章 + +赚钱理财 +2020 年其实没有赚什么钱,基本是靠之前攒下来的一些钱在生活,不过这一年确实副业收入要比 19 年多了一些。银行理财依旧和去年一样保持在 4.5%,基金收益在 10% 的样子,因为缺钱早早的把投入基金的钱先取出来了,10% 实际上是 9 个月的成绩,我个人已经满意了。 +有一段时间帮别人负责抖音信息流广告投放,我就直接用我的信用卡充值广告费再报账,所以有那么几个月我的银行流水比较大,平安银行的积分全部用来换了电话费,一年的电话费没有花自己的钱。招商银行信用卡的积分换了两个被子和一个收纳箱,刚好填补成都冬日的寒冷。 +6 月份一个平安保险的销售人员给我推销保险,我把我对保险的理解以及当时对市面上少许现有产品的分析给他说了一遍,他认为我已经达到了保险销售人员的水平,告诉了我一个可以分销保险的平台,我当时也没有把这个当回事。 +隔了两个月,有一个在成都认识的朋友问我保险相关的问题,我就给她做了一些解答,完全当给自己选产品一样给她选,刚好记起保险分销平台这个事就注册提交资料,很快平台审核通过了,最终到手的有 3000 多块钱的佣金。忘记是在什么情况下和这位朋友聊起了化妆品这个话题,于是我顺手花了 900 多给对方买了一瓶精华。 +通过付费社群知道了知乎好物这个功能,把一篇之前发在知乎上的一篇《大学生书单推荐》插入了购物卡片。之后又整理了豆瓣一个话题下面的好物清单,同样发到知乎上面插入了相应的购物卡片,前前后后总共出了大概 10 单的样子,佣金够自己吃几顿麻辣烫。 +自己知道一个可以低价充值腾讯、爱奇艺、网易云等平台会员的渠道,加了一点点价格在闲鱼上面转卖,前前后后赚的钱加起来应该也能吃几顿麻辣烫,不过由于利润低且有一点费时间所以就没做了,不过这也算是自己走通的第一个利用信息差赚钱的案例。 +选择离职 +离职的想法老早就在心头了,3 月份提的离职申请 4 月底离开,但是在流程上我是 7 月份才离职的,因为领导做出了很大让步,允许我半个月在成都半个月在深圳,既然已经说到这份上我也就答应了。但是在年终奖、季度奖和津贴上面却又受到了极不公平的待遇。所以离职的原因很简单,就是干的不爽加上不算年轻的冲动。 +冲动是什么呢?趁还不太老的时候去经历,自己一直在没钱但也没有感觉到缺钱的状态下生活,估计大多数白领阶层也是这个状态,没有家庭的压力,没有赡养父母的压力,更没有病痛的折麽。所以我想提前去感受一下缺钱的感觉,这个想法确实是脑子有点病,哈哈哈。 +干的不爽是为啥呢?不喜欢公司的加班文化,部门每个月都会有一个加班时长排名表,我们团队每个月都是部门垫底的,据说加班时长少于 40 小时会被领导谈话。我个人不反对加班,但是当大家都仅仅简单的在比加班时长的时候,那个氛围就待的很没有意思了。 +在大企业工作的程序员都会遇到的一个问题。我自己在 5G 测试部门做内部工具开发,回家时大家一听都还觉得挺洋气的,能接触到业界比较前沿的技术与知识,但实际上我连产品最终长什么样子都没有见过,我希望自己能看到一个产品从 0 到 1,并且送到用户手中用户愿意付费的过程。 +当然离职最大的原因是意识到个人的增长是线性的。我在部门新人里面还是算干的比较好的,算一下自己如果一直干的比较好,部门那个 5G 专家可能就是自己将来的样子,自己还不一定能达到那个水平。如果干的不好的话也有榜样在那里,那一群每天上午还按时在工位做体操的老油条就是自己将来的样子,不喜欢一眼就能望到未来样子的感觉。 +离职之后的感受是大公司提供的保护壳真特么硬啊,出来之后才明白挣钱确确实实不容易,所以在这里奉劝各位要离职可得想好啊,说实话我现在有一点怀念那种划一天水都依旧有工资的日子呢。 +一点收获 +自己从 0 到 1 设计开发的系统得到了同事们的认可,完全靠着「用户怎么用着舒服」这个理念在开发,逐渐得到了其它部门领导的青睐。在师傅的督促与推动下,我在离职前将其做了平台化开发,临走前已经将西安一部门接入系统,我还悄悄在系统里面留下了我的信息,不知道将来可有人能发现或是已经发现。 +学习了一点互联网广告投放领域的知识,对互联网流量有了比之前更加深刻的理解。中国 8 亿网民里面绝大部分都是穷人,穷人里面又有一大部分是天天只知道刷抖音的懒人,梦想着天上会掉下来馅饼喂到嘴里,你脑子里的常识可能有一亿人都不知道。大部分小公司赚的是穷人的钱,大家向往的互联网巨头又赚的基本是小公司的钱。 +对京东、淘宝、拼多多这一类电商平台有了新的认识,慨叹电商领域的水简直深不可测,很多小公司依附于电商平台生存,太多套路也不方便在这里讲。自己脑子里将实体经济与互联网经济的逻辑打通了一部分,互联网平台要么自己能提供某种服务,要么就是带的动货才能正向循环。这里做个预测,2021 年会有更多的平台接入电商平台,功能类似于知乎好物。 +体验了一把纸醉金迷的感觉,对自己的定位又清晰了一点。人这一辈子总要多多少少面对一些诱惑,就好像电影《八佰》里面那个军官一样,自己没有摸过咪咪所以老是好奇摸咪咪的感受,没有经历过总是容易经不住诱惑。我庆幸自己能比较早的去经历,虽然不至于让我明白我想要什么,但至少让我明白了自己不想要什么。 +人生的第一桶金不是只有钱,还有经历和认知。这一年更多的是见识,是心灵上的收获。 +新的伙伴 +到成都后和一群贵州、四川小伙伴待了段时间,他们基本没有上过高中,有的甚至小学都没有毕业。IT 行业工作的人多少是有五险一金、年终奖、季度奖这些玩意的吧,但是这部分人所在的公司可能连社保都不会交。我清楚的记得我刚回成都时,一个小孩子给我炫耀说:“刘哥,我们公司是有社保的”。 +他们喜欢像抖音一样各种炫耀,但是自己挣得钱又不够自己花的,要是有一个月没有发工资的话那么就还不起花呗了。如果某一个月多挣了一点点钱,就是去酒吧、去嫖娼。我发现这才是目前社会上最大群体的一个缩影。 +直到最近我才真的明白,你不要和他们讲什么要去做有积累的事,不要和他们讲什么所谓的长期主义,一个每天还在为生活发愁的人,就不要和他去谈什么理想。所以我写了一篇如何通过撸茅台赚钱的文章,在这里我想告诉你这种短期快速套利不会让你有什么积累,会打乱你的时间,就是个奶头乐。 +得益于校友会这样一个平台,在四川校友会认识了一个师兄,我目前正在师兄手下学习做一点事情。主要的精力都在少儿编程教育上面,惊叹我大一才会的东西现在一个不到十岁的小孩子就会了,已经变成了一个拼娃的时代,家长们生怕邻居孩子会的才艺自己孩子不会,可能这就是「内卷」吧。 +写在最后 +2020 年做了一个对我个人来说比较重要的尝试,那就是自己输出一套爬虫课程,这算是第一次自己打造一个产品,这是一件很有意思的事情,因为他人是拿腰包里的钞票为你投票,能拿到一票就开心的不得了。2021 年继续在这个方向尝试,尽可能的去做好做精。 +在运动健身这一块做的很差,除了去白盆珠参加了一次百公里公益骑行,其它就没有什么值得说出嘴的运动了。顶多就是跑跑步,亦或是骑 10 来公里的单车,一年时间没有打过羽毛球了。2021 争取用自行车上下班,尽量保持平均每周能有 60 公里骑行的运动量。 +2021 年继续尝试将自己产品化,一直靠兴趣驱动的我,执行力总算得到一点加强。给新的一年设下几个关键词吧:执行、坚持、分享、自律。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 那些可以自己一个人薅羊毛的小项目 + +

+ +
+ + + + +
+ +
+ 打开知乎、微信这些平台的时候,经常就有意无意冒出来「一部手机怎么赚钱?」、「有什么好的手机赚钱软件?」这一类标题的文章。抖音信息流广告中也有很多类似的台词,「只需一部手机,一件代发,不需要囤货,挑战月入上万」这类广告词都是带你开一个网店,「一部手机,每天只需两小时,一个月轻松回本,开启财富自由模式」...... +上面提到的那些模式有没有赚到钱的?有,而且还不少。但是拿到你手里能不能赚钱?基本不能!90% 以上的人都是在交智商税,或者是因为执行力不够强,自己没有坚持下来,真正赚了钱的普通人少之又少。这一篇文章不玩套路,分享一个任何人可以套利的模式,也就是上面提到的普通人如何用一部手机赚钱? +先把答案放到前面:撸货,这里以市面上比较火热的飞天茅台为例。相信绝大部分人都知道茅台吧,稍微有一点点了解的人就会知道飞天。百度一下飞天茅台的一点点信息,它的出厂价 900 元,再去淘宝、京东这些平台查一下它的价格,茅台官方自营、京东官方自营、天猫超市等是 1499 元一瓶,普通商家卖的都是 2500 元以上一瓶。 + +所以这就看到了套利空间了吧?出厂价不要想了,你肯定拿不到那么低的价格的,要是你能拿到出厂价的价格我们可以做个微信朋友吗?悄悄的留下自己的微信号:Guanngxu。那第二档的价格就是天猫超市、京东这些平台的 1499 元一瓶了,这正是我这篇文章所要写的内容了。茅台是硬通货不用担心卖不出去,只要你手里有茅台就不用怕砸钱,当然前提是你入手的成本不高啊。可怜我之前只知道陪房东、陪老叔喝茅台,居然完全不知道这里面有这么大的套利空间,感觉错过了几个亿。 +那么都有哪些地方可以抢茅台呢?京东、天猫、淘宝这种大家都知道的平台就不用说了,只是基本都需要会员才能抢,觉得开会员的成本比较大?那这时候就可以去找一找低价的渠道了,比如说一些兑换码什么的,还有一些银行和京东的联盟信用卡,当然你也可以联系我呀! +还有一些大家可能会忘的渠道,比如说正在慢慢退出大家视野的苏宁易购,比如说很多人都没有用过的国美 APP,还有更少人知道的酒仙网、华润万家、山姆会员店这些。这些可能都需要开通会员才行,怎么开通呢?比如下面这个小程序二维码可以开通国美的会员,你也可以直接在 APP 里面开通会员,也可以找找别的渠道。 + +所以有这么多的平台可以去预约茅台,你只需要稍微研究一下平台每天放量的时间点,列一个表然后按照时间去抢就行了,抢得到抢不到就看运气呗。但是成年人的事情不能只靠运气啊,没有运气也要创造运气嘛。把自己的七大姑八大姨都整上,大家一起去抢茅台。 +这里就是看你人品的时候了,懂得把利益分享给他人你才会有更多的收获,钱是眼睛能看到的利润,但是还有更多眼睛看不到的利润,比如找别人帮忙就给别人发个红包,别人给到你一个启发、一个以前不知道的信息,给别人发一个红包,这些简单的细节可能逐渐得到对方的信任,因为眼前这个人知道的远比你想象的要多得多,他在自己的领域至少是个小狄公吧。 +除了这些平台还有一些其它线下的平台,这种有一点点地方特色了,比如我现在人在成都,前天去红旗连锁超市买水,发现它的门上贴了一个海报,说积分达到 2000 就可以预约一瓶茅台,积分达到 4000 可以预约两瓶茅台。这种小渠道更多的是靠自己去发现,相比京东一类的大平台,小渠道抢到的概率也更大一些。 + +在哪里抢茅台,怎么去抢的问题都解决了,还有一个钱的问题啊,毕竟需要的本金可是不少的啊!这个就更容易解决了,上面已经提到过信用卡了,去申请一个信用卡就可以轻松解决资金的问题了,而且一些联名卡还可以顺道解决平台会员的问题。 +信用卡除了资金的问题可以解决外,还会带来很多附加的价值,比如我今年一年的话费没有花自己一分钱,全是用平安银行信用卡的积分换的,还有每个月可以兑换的爱奇艺会员,虽然我一直没有用爱奇艺。比如我现在盖的被子、用的体重秤和收纳箱是用招商银行积分换的,找亲朋好友推荐办信用卡还有推荐礼,这也是一部分可以薅的羊毛,当然你愿意找我的话也是非常乐意的。 +这里还有一个问题,学生群体很多银行都是批不了信用卡的,我知道招商银行信用卡学生是可以申请的,信用卡的玩法有很多很多,那些羊毛尤其迎合了喜欢占小便宜的人性特点,如果玩的好的话免费坐坐飞机、住住酒店也是可以的。 +假设你现在抢到了茅台,怎么出手呢?身边那些卖酒的商铺、一些老板等等,也有很多人专门加价收购茅台,比如我自己一个大学同学,3000 抢了两瓶茅台,转手 4700 就被人家给买过去了,简直太抢手了。 + +上面的内容都是以茅台作为例子的,其它还有很多商品也基本是一样的套路,比如下面截图这个篮球。还有其它 Nike 鞋、纪念币这些都是一样的。写到这里一个普通人可以套利的回路差不多就完成了,基本就是一个手机就可以了,如果想扩大收益那就是多个账号一起来。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 财富与幸福指南 + +

+ +
+ + + + +
+ +
+ +若没有在段落文字后面做特别标注,那该段落即摘录自The Almanack of Naval Ravikant: A Guide to Wealth and Happiness + +There’s no shortcut to smart. +聪明没有捷径可走 +The fundamental delusion: There is something out there that will make me happy and fulfilled forever. +最基本的错觉: 有些东西会让我永远快乐和满足 +Hard work is really overrated. How hard you work matters a lot less in the modern economy. What is underrated? Judgment. Judgment is underrated. +在现代经济中,努力工作的重要性大大降低了。什么被低估了?判断,判断被低估了 +Spend more time making the big decisions. There are basically three really big decisions you make in your early life: where you live, who you’re with, and what you do. +花更多的时间做重大决定。在你的早期生活中,基本上有三个真正重大的决定: 你住在哪里,你和谁在一起,你做什么 +Stay out of things that could cause you to lose all of your capital, all of your savings. Don’t gamble everything on one go. Instead, take rationally optimistic bets with big upsides. +远离那些可能导致你失去所有资本、所有储蓄的事情。不要一次性赌光所有的东西。取而代之的是,理性乐观地下注,并从中获得巨大的好处 +Don’t partner with cynics and pessimists. Their beliefs are self-fulfilling. +不要和愤世嫉俗者和悲观主义者合作,他们的信仰是自我实现的 +You’re not going to get rich renting out your time. You must own equity—a piece of a business—to gain your financial freedom. +出租你的时间是不会致富的。你必须拥有股权,一项业务,才能获得财务自由 +Follow your intellectual curiosity more than whatever is “hot” right now. If your curiosity ever leads you to a place where society eventually wants to go, you’ll get paid extremely well. +比起现在所谓的「热门」 ,应该更多地追随你的求知欲。如果你的好奇心曾经引导你到一个社会最终想要去的地方,那么你会得到非常好的报酬 +The less you want something, the less you’re thinking about it, the less you’re obsessing over it, the more you’re going to do it in a natural way. +你想要的东西越少,你对它的思考就越少,你对它的困扰就越少,你就会越自然地去做它 +Learn to sell. Learn to build. If you can do both, you will be unstoppable. +学会销售,学会建设,如果你能同时做到这两点,你将不可阻挡 +If you secretly despise wealth, it will elude you. +如果你私下里鄙视财富,它就会躲避你 +Arm yourself with specific knowledge, accountability, and leverage. Specific knowledge is found by pursuing your genuine curiosity and passion rather than whatever is hot right now. Specific knowledge is knowledge you cannot be trained for. +用具体的知识、责任感和影响力武装自己。具体的知识是通过追求你真正的好奇心和激情而不是任何现在热门的东西找到的。具体的知识是你不能被训练的知识 +If they can train you to do it, then eventually they will train a computer to do it. +如果他们能训练你做一件事,那么最终他们会训练一台电脑来做这件事 +Apply specific knowledge, with leverage, and eventually you will get what you deserve. +运用特定的知识,利用杠杆作用,最终你会得到你应得的东西 +You should be too busy to “do coffee” while still keeping an uncluttered calendar. +你应该忙得没时间“喝咖啡” ,同时保持日程表整洁 +There are no get-rich-quick schemes. Those are just someone else getting rich off you. +没有快速致富的计划,那些只是别人从你身上赚钱而已 +Code and media are permissionless leverage. +代码和媒体是未经许可的杠杆 +People who live far below their means enjoy a freedom that people busy upgrading their lifestyles can’t fathom. +那些生活水平远远低于自己收入的人们享受着一种自由,这是忙于提升自己的生活方式的人们所无法企及的 +By the time people realize they have enough money, they’ve lost their time and their health. +当人们意识到他们有足够的钱时,他们已经失去了时间和健康 +To have peace of mind, you have to have peace of body first. +为了拥有内心的平静,你必须首先拥有身体的平静 +The more secrets you have, the less happy you’re going to be. +你的秘密越多,你就越不快乐 +No exceptions—all screen activities linked to less happiness, all non-screen activities linked to more happiness. +毫无例外,所有的屏幕活动都与较少的快乐有关,所有的非屏幕活动都与较多的快乐有关 +Inspiration is perishable—act on it immediately. +灵感是易逝的,所以立即付诸行动 +To make an original contribution, you have to be irrationally obsessed with something +为了做出原创性的贡献,你必须非理性地沉迷于某些东西 +If there’s something you want to do later, do it now. There is no “later.” +如果你以后有什么想做的事情,现在就应该去做,没有「以后」 +Courage isn’t charging into a machine gun nest. Courage is not caring what other people think. +勇气不是冲进机关枪的巢穴,而是不在乎别人的看法 +Happiness is a choice you make and a skill you develop. +幸福是你的选择,是你发展的一项技能 +Your brain is overvaluing the side with the short-term happiness and trying to avoid the one with short-term pain. +你的大脑高估了短期的幸福,却试图避免短期的痛苦 +Envy is the enemy of happines. +嫉妒是幸福的敌人 +Honesty is a core, core, core value. +诚信是一个非常,非常,非常核心的价值观 +where you build a unique character, a unique brand, a unique mindset, which causes luck to find you. +你要建立一个独特的个性,一个独特的品牌,一个独特的心态,这会让好运气找到你 +Figure out what you’re good at, and start helping other people with it. +找出你擅长的东西,然后去帮助他人 +If you are a trusted, reliable, high-integrity, long-term-thinking dealmaker, when other people want to do deals but don’t know how to do them in a trustworthy manner with strangers, they will literally approach you and give you a cut of the deal just because of the integrity and reputation you’ve built up. +如果你是一个值得信赖的、可靠的、正直的、有长远眼光的交易者,当其他人想做交易,但不知道如何以值得信赖的方式与陌生人做交易时,他们会真正地接近你,并和你进行交易,仅仅因为你已经建立起的诚信和声誉 +All benefits in life come from compound interest, whether in money, relationships, love, health, activities, or habits. +生活中所有的好处都来自于复利,无论是在金钱、人际关系、爱情、健康、活动还是习惯上 +Productize yourself +将自己产品化 +It’s only after you’re bored you have the great ideas. It’s never going to be when you’re stressed, or busy, running around or rushed. +只有当你感到无聊的时候,你才会有好的想法。当你感到压力,或者忙碌,到处跑或者匆忙的时候,这些都不会发生 +Play stupid games, win stupid prizes. +玩愚蠢的游戏,赢得愚蠢的奖品 +Clear accountability is important. Without accountability, you don’t have incentives. Without accountability, you can’t build credibility. But you take risks. You risk failure. You risk humiliation. +明确的问责制很重要。没有责任感,你就没有动力。没有责任感,你就无法建立可信度。但是你要冒险,要冒着失败的风险,冒着被羞辱的风险 +The best jobs are neither decreed nor degreed. They are creative expressions of continuous learners in free markets. +最好的工作既没有规定也没有程度,它们是自由市场中不断学习者的创造性表现 +Reading is faster than listening. Doing is faster than watching. +读比听快,做比看快 +Explain what you learned to someone else. Teaching forces learning. +向别人解释你学到了什么。教学是学习的动力 +Read what you love until you love to read. +读你喜欢的东西,直到你喜欢阅读 +Better is the enemy of done. +完成比完美更重要 + +Angela Zhu 摘抄 + +If you did not make yourself well understood, it is your problem. +如果别人没有听懂你在说什么,一定是你的问题 + +Angela Zhu 摘抄 + +Inspire your people do things, not tell your people do things. +不断激烈启发你的组员做事,而不是告诉他们做什么 + +Angela Zhu 摘抄 + +It does not matter what was your motivation, it only matters how this make your direct reports feel. +你的出发点不重要,重要的是你让别人感觉你想干什么 + +Angela Zhu 摘抄 + +尽可能帮助和服务别人,建立信任,赢得资本 + +Angela Zhu + +To learn something, you should do not reread, summarize and teach it out loud. +要想学点东西,你不应该重读、总结和大声教出来 + +http://www.toolkiit.com/ + +在人生最好的年纪,应该少一点算计,多一点洒脱。遇到喜欢的人就去相处,去恋爱,不要辜负自己的时光,然后人生就会给出你更多的可能性 + +Fenng + +你心里应该有爱,应该有为了爱去放弃一些东西的勇气。如果你做不到这样,那我觉得你很可悲。或者,会成为一个精致的利己主义者,体会不到人生的幸福,和真正的生活的勇气 + +Fenng + +电话、视频会议,线下会议、面对面交流的时候,这是同步事件,需要一定程度上近乎实时反馈。而短信、微信消息、语音消息、留言、邮件,这些都是异步事件,用固定的节奏批量处理就是了 + +Fenng + +When you are old and gray, and look back on your life, you will want to be proud of what you have done. The source of that pride won’t be the things you have acquired or the recognition you have received. It will be the lives you have touched and the difference you have made. +当你白发苍苍、垂垂老矣、回首人生时,你需要为自己做过的事感到自豪。物质生活和满足的占有欲,都不会产生自豪。只有那些受你影响、被你改变过的人和事,才会让你产生自豪感 + +Steven Chu 2009 年哈佛大学毕业演讲 + +In your collaborations, always remember that “credit” is not a conserved quantity. In a successful collaboration, everybody gets 90 percent of the credit. +合作中,不要把功劳都归在自己身上,在一场成功的合作中,每个人都应该获得 90% 的荣誉 + +Steven Chu 2009 年哈佛大学毕业演讲 + + +
+ + Read More ~ +
+
+
+ + + +
+ + 上一页 + + + 下一页 + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/page/4/index.html b/page/4/index.html new file mode 100644 index 00000000..6fb68c06 --- /dev/null +++ b/page/4/index.html @@ -0,0 +1,1551 @@ + + + + + + + + Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ + +
+

+ + 说一下戴牙套的感受 + +

+ +
+ + + + +
+ +
+ 快要离开深圳时候和老叔一起吃饭喝酒,老叔看着我突然来了一句:“小刘啊,那个你年终奖也发了,你回去把你的牙齿弄一弄,把那个缝关上,不然你的命就一直不好,漏财!” +说实话,我以前从来没有想过自己的牙齿,还一直觉得自己的那个缝好玩,小时候还常借助门牙那个缝隙,加上自己的舌头抵压,压力会让口水从牙缝挤出去,达到喷射的很远的效果,为数不多的几次口水战我都比较占优势。 +可以但是经叔叔这么一说,我再照镜子时候就不再觉得它好玩了,越看越不美观了。所以有事没事的时候会忍不住去查查正畸方面的信息。回到成都后老叔还是经常给我打电话,每次视频的时候老叔看到我没有戴上牙套就会骂我催我,本来想回到成都就把老头给糊弄过去算了,但自己却是越看越不美观。 +从有矫正的想法到真正戴上牙套中间经历了半年的时间,这半年时间就是查查网上的资料,问问身边有做过正畸的朋友,当然还有一个重要的事情是了解各种正畸方案的价格,像什么陶瓷自锁、金属自锁、隐适美等等,其实这些乱七八糟的事情都是一个心理建设的过程,要突破这个心理障碍还真不容易。 +到现在我已经戴了快一个月的牙套了,刚开始戴上那几天会很疼,连话都不想说。我戴的是进口金属自锁那种大钢牙,刚开始上面那个金属丝还老是滑动容易戳我口腔,如果滑动的不太多我就自己把它拨回去了,如果滑动的太多了就到诊所那里去让牙医搞定,我到诊所就 3 分钟的路程,这一点还是非常便利的。 +做矫正这个事儿可能很多人都觉得麻烦,不能随便吃东西,每吃一顿饭就要刷牙,而且一个大钢牙在嘴里还很丑,牙齿也疼.........就干脆放弃了,但是以我这一个多月的感受告诉你,戴牙套的好处要大于太多坏处。 +人人都想减肥却又管不住自己的嘴,很多人在睡前总是想吃点东西,导致体重增加的非常快,尤其很多所谓无法推脱的社交饭局经常吃到半夜一两点,戴上牙套之后保证你能管好自己嘴,我自己戴上牙套不到一个月已经瘦了 4 斤多了,这是戴牙套之前没有想到的意外收获。 +当然,更加具体的实施方案还得取决于自己的牙齿具体情况,有的人做矫正可能还需要拔几颗智齿,可以顺道消除智齿发炎的地雷。另外一个拔智齿的好处可能会让仙女们比较开心,那就是智齿拔了脸会变小啊,哈哈哈哈哈!! +对我个人来说还有另外一个好处,那就是每天只能吃很少的东西怎么才能让自己更健康的成长,所以抓紧时间去看了《你是你吃出来的》这本书,才知道自己以前对于营养的认知是多么浅显,我忍不住想要对正在看文章的你做个简单的科普! +我们吃东西不仅要关注能量,更要注重七大营养素(碳水化合物、脂类、蛋白质、维生素、矿物质、水、膳食纤维)。像鸡蛋、牛奶、蔬菜、水果、坚果、肉类、(深海)鱼、动物肝脏等,都需要均衡摄入。《你是你吃出来的》一书中做了大量苦口婆心的讲解,也纠正了大家平时的一些错误观点,比如很多人生病了不舒服就咸菜加上喝白粥打发,觉得浓浓的白粥里面很有营养,实际上白粥是没有什么营养的;很多地域晚上都习惯吃面食,因为面食比较容易消化,但面食里面的主要要成分是碳水化合物,非常容易让你血糖快速的升高,碳水化合物摄入过多的结果只会让你越来越胖。 +牙套在嘴里也不太好咀嚼,之前又看到朋友圈的人推荐「若饭」,正好趁这次机会尝试了一下若饭,口味什么的并不是多好,但液体版的若饭对我来说很方便,像考研党、工作狂或者其它比较忙的人,可以买一点若饭在那里备着,一分钟的时间就能解决一顿饭,比上个厕所都要快的多。 +若饭是一种高密度的营养餐,我们大多数人可能觉得自己平时吃的很健康,但估计现实情况和自己认为的恰好相反,比如中国人基本对钠盐的摄入都严重超标,很多人都摄入了太多的碳水化合物,可以看若饭的营养成分表,各方面还是基本兼顾到了的。若饭不仅有液体版,同时也有粉末版供你选择。 + +当然如果能吃到天然的食物最好吃天然新鲜的食物,像若饭这样直接对标美国 Soylent 的产品也不可能全方面兼顾到,比如长期食用可能会影响肠胃功能(我猜的),所以经常吃吃水果、坚果这些小零食,没事儿的时候约几个朋友散散步,偶尔来一顿美食大餐是绝对有必要的。 +最后再推荐你看一个一席的演讲视频:减盐这件大事 + +
+ + Read More ~ +
+
+
+ +
+

+ + 扒一下网店代运营公司的套路 + +

+ +
+ + + + +
+ +
+ 前段时间,一个朋友给我发了一个抖音视频,视频是深圳龙岗公安发布的他们抓捕代运营诈骗公司的一些场景,我在龙岗政府在线网站上也看到了这个事件的新闻:9000元请人代运营,只换来几条教学视频,深圳网店主报警。有天傍晚,我独自坐在楼下公园的椅子上看书看电影,无意中被刚下班的牙科医生碰到了,他就坐了下来和我一起聊天,很自然就聊到了他的大众点评店铺的推广,告诉我他接到过好多代运营公司的电话,他自己差点就要给其中一个公司交钱了。 +咱们先来看一下在百度搜索「网店代运营」的结果,前五条全部都是广告。在抖音短视频信息流广告中,代运营的广告也是不计其数。其它像快手、神马搜索这些产品中也充斥这种代运营广告,这么庞大的广告投入背后一定是有暴利,我这里只是把我知道的代运营套路写出来,如果能让一个人止步骗局那就是没有白写的。 + +一般会去找代运营的商家基本都是新手,自己正为店铺没有销量而发愁,现在有家公司告诉你只需要每个月交多少运营费用,自己只负责客服和发货就行了(甚至客服都不用负责),看到市面上有这样好的服务你能不心动吗?而且他们还重点给你强调公司主要是靠运营店铺销售额的提点来盈利,在签合同的时候销售还故意在起提基数和标准上表现出很强硬的态度,这无疑会更加增加商家的信任,但这都不过是骗局的开始。 +找代运营的群体里面还有一大部分是想发展一个副业的白领、空闲时间比较多的大学生和宝妈,这部分人是很纯正的网店小白,不知道怎么注册网店(不需要多高级的搜索技巧,百度随便都能搜到),不知道怎么上架商品,自己没有货源不知道该怎么办,我这里拿没有货源来介绍一下代运营公司的销售话术。 +如果对方不知道「一件代发」这个事情,那么销售可能会这样说:“您这边没有货源没有关系,我们公司是做这个的,我们不仅对接了很多的优质厂家,而且我们自己也有大量的货源,都是可以免费为您对接货源的。”如果对方知道一件代发这个东西的话,那就直接给他说一件代发就好了。 +有了货源之后要卖什么产品呢?这个你是不是也很迷茫?好了,他们一般应该会给你做一场比较「专业」的图文并茂的市场分析,女性群体的基数大、网购行为多、她们的钱好赚,反正最终分析下来的结果就是卖女装很合适。注意,代运营公司对所有咨询如何开网店的客户都是这样说的,因为他们制定了一套非常标准的销售流程,那些话术只需要复制粘贴即可,反正最终都会引导到卖女装去。 + +经过一番折腾总算把网店开起来了,有一点良心的代运营公司还有专门的美工给你做一套主图和详情图,没良心的公司顶多就是在网上扒拉几张图片扔给你,甚至从网上不知道哪里卖的几节课程发给你。产品也都上架到店铺了,但只是上架商品的话店铺连访问量都没有,怎么可能会有销量呢? +这时他们的「运营师」就会给你出一套针对你店铺的运营方案了,和前面介绍的怎么引导到都卖女装是一样的套路,所有的客户都是同一套话术引导到店铺要继续做推广的方向上去,简单说就是得继续交钱!他们可能会这样说:“我们公司和百度、搜狐、头条等互联网公司都是有合作的,我们会支付给这些平台大量的钱帮助我们的店铺去发软文,这样您相比其它商家的优势就不仅仅只有淘宝(拼多多)平台内的流量了,其它像抖音、百度这些平台的流量都会进到您的店铺,您想一下这是多么庞大的流量和优势!” +实际上代运营公司只是专门针对你店铺内容写了一篇软文,然后再用百家号、头条号、搜狐号等发了几篇文章而已,这就是他们所谓的「和百度、搜狐、头条等互联网公司都是有合作的」。为了让你相信这些软文真的生效了,一般会用一些平台去给你的店铺引流,比如新韵网和小老弟网红助手,如果你相信了确实是他们的软文推广效果生效了,那你离下一次交钱的机会就不远了。 +除了这种软文投放的方式还有另外一种销售流程:“因为您的店铺是新店铺,那些基础数据都不好,我们这边可以通过技术手段帮您把店铺基础数据弄好,这样店铺的基础数据上去了之后就可以报官方的一些活动,获取到官方的流量扶持......”。这个在之前写的如何空手利用拼多多(淘宝)赚钱套利?中有介绍如何修改店铺销量,像店铺收藏、商品收藏等也是可以通过上面介绍的新韵网和小老弟网红助手来购买的,成本就几百甚至不到一百,但是可能收你几千甚至几万的服务费。 +这些代运营公司大部分都是在抖音上面投放的广告,体量大一些的公司会在百度投放广告。记得之前在刘鹏老师的星球看到一个投放广告的逻辑:先用极低的价格卖 A 罩杯内衣,然后向这批用户推丰胸产品。最近也看到一个代运营公司搞了个门槛很高的投放方式,自己开发了一个展示货源和网店课程的 APP,然后广告投放方式是推广这个 APP,这样就避免了和其它同行竞争表单还起不来量,而且会主动注册他们 APP 的人意向也比较大,起到了一次清洗客户的作用,听说他们这种 APP 推广的方式线索成本还不到二十,而大多数代运营公司的表单成本已经到了一百多。 + +上面说了那么多都是电销的模式,可能你会想自己实地去考察肯定就不会被骗了吧?现实情况是他们欢迎你到公司实地考察,他们有现成的店铺数据这些给你看,而且实地看到他们公司各个团队之间的协作可能会让你被骗的更深,面销会忽悠的更加伤人。 +可能很多人第一下想到的是报警吧,但实际上报警的作用不是多大,首先双方是有签署合同的,所以这只能算作经济纠纷不属于派出所管辖范围。即使公安局那边立案了,整个过程估计也会把你的耐心给磨没了,破案后你损失的金额能拿回来的可能性也不大。这里只告诉大家「消费者协会」和「市长热线」是比较值得信赖的。 +总还是觉得中国是人口基数太大了,七八亿的网民里面傻子实在太多了,那些天天只知道刷抖音,梦想着如何快速发财,梦想着如何不劳而获的人,正是代运营公司的潜在客户,人性都是贪婪的,销售稍微帮你放大一下你的贪婪你就输了。 + +
+ + Read More ~ +
+
+
+ +
+

+ + Schema 与数据类型优化 + +

+ +
+ + + + +
+ +
+ +参考内容: +《高性能 MySQL(第三版))》 + +选择优化的数据类型 +世面上常见的数据库大多支持了多种多样的数据类型,选择正确的数据类型对于获得高性能至关重要,一般都需要遵循如下的几个原则: + +更小的通常更好:更小的通常更快,因为占用着更少的磁盘、内存和 CPU,并且处理时需要的 CPU 周期也更少; +简单就好:简单数据类型的操作通常需要更少的 CPU 周期; +尽量避免 NULL:如果查询中包含可为 NULL 的列,就会使得索引、索引统计和值比较变得复杂,因此在设计表是最好指定列为 NOT NULL。 + +整数类型 +在 MYSQL 中可以为整数类型指定宽度,例如INT(11),但是这对大多数应用是没有意义的,它不会限制值的合法范围,只是规定了 MySQL 的一些交互工具(如 MySQL 命令行客户端)用来显示字符的个数。对于存储和计算来说INT(1)和INT(20)是相同的。 +字符串类型 +需要注意的是当 MySQL 存储 CHAR 值时,它会删掉所有的末尾空格,因为 CHAR 值会根据需要采用空格进行填充以方便比较,这导致的问题就是你使用 CHAR 存储的string 会变成string。CHAR 的好处在于它是定长的,很适合存储像 MD5 值一样的定长值,定长值的 CHAR 类型不易产生碎片,而且对于非常短的列 CHAR 也会比 VERCHAR 好,比如CHAR(1)只需要一个字节,而VERCHAR(1)则需要两个字节,因为它还需要一个字节来存长度。 +VERCHAR 类型在存储可变长字符串时,会比 CHAR 更节省空间,它需要使用 1 或者 2 个额外的字节记录字符串的长度。但由于行是变长的,当一个行占用的空间增长,并且在页内没有更多的可用空间可以存储,就容易产生碎片。 +使用枚举代替字符串 +有时候可以使用枚举列代替常用的字符串类型,枚举列可以把一些不重复的字符串存储成一个预定义的集合,而且 MySQL 在存储枚举时非常紧凑,会根据列的数量压缩到一个或两个字节。比如下面的例子: +CREATE TABLE enum_test( + e ENUM('fish', 'apple', 'dog') NOT NULL +); + +INSERT INTO enum_test(e) VALUES('fish'), ('dog'), ('apple'); + +SELECT e+0 FROM enum_test; + +# result ++-----+ +| e+0 | ++-----+ +| 1 | +| 2 | +| 3 | ++-----+ + +可以看到使用枚举类型后,上面三行数据实际上存储为了整数,而不是字符串,而且还有一个让人吃惊的地方:枚举字段是按照内部存储的整数而不是定义的字符串进行排序的,这一点需要特别注意,不然在写程序时容易中犯错。当然你也可以在查询时使用FIELD()函数显式地指定排序顺序。 +可以看到上面 +范式和反范式 +关系型数据库有设计范式的概念,这一点在大学的数据库课程中肯定都会提及。因为有比较高的范式,那么就只有很少或者没有重复的数据,因此在 UPDATE 时只需要修改更少的数据;高范式的表通常也更小,因此占用的内存也会更小,操作起来也会更快...... +但是高范式也带来了另一个缺点,比较好的范式通常意味着需要关联,稍微复杂一点的查询就需要使用 JOIN,关联的代价是昂贵的,甚至让一些索引策略失效;而如果不需要关联,即使某个查询需要全表扫描,当数据比内存大时可能会比关联查询快的多。所以一般都会根据实际情况将范式与反范式混用,完全的范式化和完全的反范式化都是实验室才有的东西。 +缓存表和汇总表 +这里的「缓存表」和「汇总表」并没有什么标准的含义。我们用「缓存表」来存储那些可以从其他表获取,但是获取的速度很慢的数据;而「汇总表」则用来保存那些使用 GROUP BY 语句聚合数据的表。毫无疑问,我们存这些冗余数据也是为了性能。 +比如近两年各种应用流行的年终报告,每次网易云音乐的年终报告都会把朋友圈撑满,其它类似于缓存一个用户的朋友数、一个文件的下载次数等等。这些数据实时计算的开销是很大的,而且多数情况下用户也等不起实时计算的时间,一般的解决方案都是通过增加计数器表(缓存表)来解决这个问题。 +计算机科学中总是伴随着双面性,上面的计数器表带来性能提升的同时也带来了并发问题。网站的每一次点击都会导致对计数器的更新,对于任何想要更新这一行的事务来说,这条记录都有一个全局的互斥锁,这会使得这些事务只能串行的进行。每一次点击都会触发下面的语句,但大量的点击伴随着该行数据的互斥锁,想想性能也不会提升到哪里去吧。 +UPDATE hit_counter SET cnt = cnt + 1; + +大多数应用都是读查询居多,为了提升读查询的速度,经常会需要增加一些额外的索引,增加冗余列、汇总表、缓存表等等。但是不要忘了这些技巧也会增加写查询的负担,还会增加开发难度,因此应该根据实际应用场景来做权衡。 +加快 ALTER TABLE 表的速度 +MySQL 执行大部分修改表结构的方法都是用新的结构创建一个空表,然后从旧表中查出所有数据插入到新表,然后删除旧表。在内存不足、表很大、索引多的情况下会花费很长的时间。一个很严重的缺点是大部分 ALTER TABLE 操作将导致 MySQL 服务中断。 +对于常见的场景我们有两种技巧避免服务中断。一种是先在一台不提供服务的机器上执行 ALTER TABLE 操作,然后和提供服务的主库进行切换;另一种技巧是「影子拷贝」,即用要求的表结构创建一张和源表无关的新表,然后通过重命名和删除表的操作交换两张表。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 转载——我的喜欢从误会开始|最后的记忆 + +

+ +
+ + + + +
+ +
+ 我的喜欢从误会开始 + +作者:伍什弦 + +我说过 +我不擅长和男生做朋友 +所以 +一点点的小事 +都能让我误会 +误以为你喜欢我 +课堂上 +向我借笔 +下课后 +加我 QQ +那时候 QQ 封面出现点赞 +整个假期 +我们不停的互赞 +空间里互相留言 +这些小事偷偷在我脑中生根 +而我被你深深的吸引 +想多见你 +想靠近你 +在运动会上看到你胜利冲过终点 +这一晚我梦里是你 +连着四晚我梦里都是你 +我知道 +我喜欢你是从这个时候开始 +可是喜欢的种子早在课堂上埋下 +我误会了 +误以为 +你也是喜欢我 +最后的记忆 + +作者:伍什弦 + +我想啊 +人这一生 +大抵都有疯狂的喜欢过一人 +除此一人之外 +剩下的喜欢相比不过十之一二 +读大学前 +以为从小学喜欢到高中的那个人 +当算作我的初恋 +可是他闯入我的心怀 +从此苍山洱海不过一人 +那时节 +他总能出现在我的梦里 +我呢 +总想把最好的都给他 +为他做这样那样的事情 +可是他对这样的我说 +我们永远都是好哥们 +就这一句 +之后近一个月 +我几乎不说一句话 +从不逃课的我逃了不止一节 +整个学期 +我几乎是在图书馆生活的 +读完了 +一部十三本的德川家康 +看了 +不知多少部电影 +那种伤心仍是挥之不去 +不过是随着时间淡淡忘却罢了 +后来 +身边也有喜欢我的人 +开始我也觉得这人还好 +总是一段时间过后 +就没缘由的厌恶 +在还没来得及向我任何表示前 +就已经疏远了他们 +毕业的时候 +我对他说:祝你幸福 +之后班级一起去 K 歌 +我走前 +最后唱了一首 +每每听到都忍不住要流泪的歌 +Someone Like You +回去的路上 +好友告诉我 +我在唱的时候 +他是闭着眼睛听的 +是他第一个鼓掌的 +听到这句话的我 +眼泪不受控制般流下 +我 +站在天桥上大声呼喊 +我再也不喜欢你了 +可真的是这样吗 +无论如何 +青春 +已离我远去 +那感觉如此清晰 +以后再不会如此喜欢一人 +纵有 +江山万里织锦绣 +我心 +除却巫山不是云 + +
+ + Read More ~ +
+
+
+ +
+

+ + 一个通过建网站赚钱的暴利项目(灰产勿碰) + +

+ +
+ + + + +
+ +
+ 这个项目的前提是你已经有了一个可以发布文章的网站,网站的主要就是收集投诉相关内容,如果你还不知道怎么去建一个网站,可以看我之前发的如何搭建一个属于自己的博客/企业网站,按照文中的方法建一个网站的成本投入不到 150 元。 +网站建起来了怎么去赚钱呢?相信大家都会想通过 SEO 把自己网站排名优化到足够高,然后利用谷歌联盟或是百度联盟赚广告费,没事的时候更新更新文章,只要保持一个比较好的更新频率,并且能长期坚持下来,那肯定是可以赚钱的,而且收入也会逐渐递增甚至可能呈现指数型增长。 +此时得承认我有点标题党了,这个快速赚钱的套路属于灰产,因为我作为证人协助警察叔叔抓获了一个做这件事情的站长。下面进入正题。 +现在有一些专门做维权的平台,比如聚投诉、新浪旗下的黑猫投诉等,可以进去看看这样的平台可能在维权方面起不到多大的作用,但是它随随便便就能上百度的首页啊。谁最害怕网络上的负面信息?肯定是被投诉公司啊!在信息时代,一条负面信息不知道要损失多少客户。 +我说的就是做一个类似的网站,首先网民都喜欢看这一类的内容,另外这样的网站也很容易进来访问量,有了访问量那么广告费就是一笔不菲的收入,比如你可以去搜「笑捧博客」看下里面的内容,我截了个图放在这里,先告诉你的是它最大的收入并不是广告费。 + +鉴于上面的信息都是网络上公开的内容,这里我就不打码了。可以看到这个网站里面的绝大部分内容都是 XXXXXXX公司-警惕,里面就是简单描述一下事情经过,然后放个合同的照片、再放几张聊天截图,而且这些内容都是受骗用户自己投稿的,完全不用花时间自己去创造内容。 +假设某客户早这个平台上面投诉了 A 公司,A 公司的人看到在百度轻易就能搜到不利于自己公司的信息,想要快速删除这样的内容怎么办?顶部特地留了个「联系站长」看见了吗?假设现在你就是这个站长,你可以像下面那样给对方回话: + +平台的内容都是用户自己发的,我这边会跟进用户投诉的进度,你这边有和客户协商处理的聊天记录吗?或者有给客户的退款记录也行,我去核实如果无误就把这篇帖子屏蔽掉。 + +都把客户逼到去网上发负面信息了,逼到客户去投诉公司了,基本上这样的公司不会有和客户协商处理的聊天记录,不到万不得已他们是不会给客户退款的,一般对方都会回答没有,此时你可以这样说: + +你需要马上处理这个帖子可以先交 600 元的押金,我这边先去后台设置不展示这篇内容,你那边抓紧时间去处理,处理好了联系我退换押金即可。 + +到这里就玩概率了,如果公司很快的就把这个事情处理了,那么对方找你这个站长退还押金也不能不给是不是?但就是有很多公司在一个月内都没有把这样的客户处理好,因为他们本身做的就是割韭菜项目,怎么会轻易退客户钱呢?过了一个月后可以这样说: + +当时给你说的是一个月内处理,与客户沟通处理的聊天截图、退款记录发给我,这边去删除帖子!时间有点久,而且后台设置的一个月就会清理一次数据,进程什么的都已经死了............ + +简单说就是各种扯皮各种赖,几百块钱对方应该也不会太在意,对方顶多骂你两句也拿你没什么办法,是不是轻轻松松的 600 元就到手了!!! +最后再次强调一点,这个被警察叔叔发现了是要来请你的,而且自己搭的网站没有官方授权,都知道投诉电话是 12315,你个人的一个博客网站凭什么能接收这种投诉?再去看看这个「笑捧博客」的服务器在香港,而且这个网站的所有内容最后更新时间是 2020 年 9 月份,知道为什么吗? +因为这个站长已经被公安局请进去了! + +
+ + Read More ~ +
+
+
+ +
+

+ + 大学生可以尝试操作的项目 + +

+ +
+ + + + +
+ +
+ 很多大学生第一次远离爸妈的怀抱都是因为上大学,刚好此时也已经是 18 岁的成年人了,从小耳濡目染的 18 岁成人礼撬动着内心那颗渴望独立的心,通过自己能力赚到钱放在手里那一刻的感觉,人类的文字已经无法描述那一刻的美妙了。 +估计也有不少学生都被一些网上兼职网站给坑过(本人也被坑过),满怀期待的在某个兼职网站提交了自己的个人信息,不一会儿对方就打电话过来和你确定一个时间去面试,此时心中的你是不是还在想自己要准备什么啊?从来没有面试经验的自己能通过面试吗? +到了现场才发现所谓的面试不过只是让你交几百块钱给他们,他们给你在网站上面注册一个账号,这样你就可以去网站上面领取兼职工作了,虽然交了几百块钱有割心头肉的痛感,但是想想多做几天兼职这些钱就挣回来了,而且还能挣更多的钱,心中又充满了期望。 +本文本着童叟无欺的价值观,良心分享几个线下必赚项目。 +收集毕业生的被子 +学校每年都会有大批的毕业生走出校园,本科、硕士(博士一般都在外面有房子)加起来少则几千多则几万,这些学生基本都从外地到学校就读的,每一年这些学生一走都会留下大量的被子等在宿舍,还给给后勤集团留下一大堆的烦恼,那为啥不帮助后勤集团去解决这个烦恼顺道再赚点钱呢? +先辛苦一下去外面的酒店了解他们是不是需要这些被子(不要把酒店的服务想象的那么好,我好像又透露了点什么),别去那种太高端的酒店就行,多问几家比一比价格,别忘了顺道也问下路过的棉花厂。做好记录,毕竟好记性不如烂笔头嘛! +评估一下在自己学校的可行性,价格比较完了觉得可以做那就开始拉上自己的哥们干!为了省点钱可以先找后勤集团领导问问,现在可以为他解决毕业生离校后宿舍被子的问题,可否把这个项目作为勤工俭学工作给点钱,就算不给钱可以不可以给出个车费把被子拉到指定地点。当然如果人家不愿意那就自己出点车费钱啰。 +后面要做的事情就不用我多说了吧!去每个宿舍询问一下,把被子抱走就行,毕业生是陆陆续续走的,所以每个宿舍楼记得多跑一两遍。过程是比较累的,坚持下来赚到一笔可供挥霍的基金绝对没问题。 +给宿舍提供零食 +想象一下自己打游戏打到大半夜饿肚子的感觉,此时楼下的小卖部已经关门了,去校外的超市又太冷、太远了,这一点在北方尤其常见。在南方生活的我有一次肚子饿了,跑到学校的腐败街希望能找点吃的,结果基本上的商家都关门了,要知道那还不到 12 点。 +可能也有同学想过甚至实践过这个项目,我自己同班同学也拉着自己的哥们实践过,但是他们并没有做到多好,并没有赚到多少钱,所以下面说几个值得注意的地方! +选品很重要,先选比较容易出手的薯片、肥宅快乐水、辣条等,水果可以留着下一步做,因为保质期不够长!给同学送货的时候一定不要见到是熟人就瞎唠嗑,那样会浪费给其它顾客送货的时间。能加微信记得把顾客和他室友的微信加上,这样方便收集大家的需求和意见,微信和 Excel 能解决 90% 以上的问题。比较重要的一点是记得不定时做做活动,比如从已下单客户种抽取第二日免单优惠,满 30 元送一瓶可乐等等,要让顾客真正体验到在你这里购买的便捷与实惠。 +有了上面的基础之后你就可以去和货源老板压价了,当你的流量足够大的时候甚至可以和老板谈判先卖出去在付货款,只有把自己的成本压到足够低才有利润可图,这个项目主要看的是执行力! +给新生办电话卡 +这个相信很多同学都多少听过或者尝试过,每年三大运营商的抢人大战尤其激烈,我入学的时候中国移动直接疯狂到给学生免费送卡(那时候实名制还没那么严),在暑假快放假的时候去学校的各个营业厅了解一下情况,问问他们办一张卡都有多少提成之类的(我当时是一张卡 15 元),如果能同时拿到是那个运营商的卡最好,如果拿不到那就拿中国移动的卡(这个也有例外,比如我学校的楼就很神奇,基本每一栋楼里面都接收不到移动信号,图书馆能屏蔽一切信号,因地制宜也很重要)。 +去哪里拉客户呢?新生报到处对不对?错了!你的同行都会想到新生报到处,竞争比较大。新生报到处都是办理各种手续对不对?那里也没有桌子什么的方便填写资料,而且新生报到处是第一站,后续的流程还没走完人家也没那耐心给你这里耗,所以新生报到处绝对不是最佳地点。 +哪里是最佳的地点呢?宿舍。新生宿舍总共就那么三四栋楼,会呆在宿舍的新生基本都是把各种手续已经跑完了,爸妈正陪着他铺床或是简单歇歇就去吃饭呢,正处于一个放松休闲的状态。这时你去给他说你这里有学生优惠的卡,对方是不是该很高兴?只要他们还没有办卡,那他们吃完饭也会去办电话卡的。 +组织租车 +这个项目得根据学校的实际情况了,有些学校所处的位置就很适合这样的项目。比如中北大学,在一个离市区很远的村子里面,周末大家都想去市里玩,我去那里看女朋友的时候可遭罪了,虽然有公交但是超级挤啊,所以很多学生宁愿多花几块甚至十多块去坐黑车。何况有的学校还没有公交。 +那些司机师傅也很焦灼,一面是已经坐上车的顾客在死命的催他,一面是车上还没有坐满跑一趟划不来,旁边还有很多同行在和他竞争,这样的场面即使你没有在学校见过相信也在车站见过吧!所以这个项目就是在司机与同学之间搭起一座桥梁。 +还是上面的话,微信和 Excel 可以解决 90% 以上的问题,你一遍把班次定好让大家交钱预约,一边和司机师傅联系,保证发车的准时性。这个项目在特定环境是刚需,所以很容易在同学之间传开,再稍加一点活动优惠基本就躺赚了,我看到有利用这个项目赚了好几十万的! + +
+ + Read More ~ +
+
+
+ +
+

+ + 为什么会出现网店代运营服务? + +

+ +
+ + + + +
+ +
+ +本文写于 2020 年 4 月,较于 4 月版本本文已做部分修改 + +最近在深圳待的厌烦了,网店代运营这个行业兴起来没有多久,逮着清明假期索性到成都几个做淘宝和拼多多代运营的公司走了走,主要是想实地感受一下电商代运营是个什么玩意!截止此文章发布时间,我已经在这个公司待了一周了,但限于理解能力有限,对网店代运营这个东西还是一头雾水,下面就先说说我理解的为什么会出现代运营这样一个行业吧。 +一般经过某种生产过程我们可以得到一个或多个产品,然后再把把这些产品拿到集市上去销售,比如农民伯伯种菜拿到菜市场去买。工业的发展、社会的进步等因素使得生产力有显著的提高,但是要生产一个产品依旧离不开生产过程,只是这个生产过程效率变得更高了而已。商品不一定是实体的,比如电视剧里面用钱换取情报,这里的情报也是一种商品,网店代运营公司提供的网店代运营服务也是一种商品,这一点可以类比各种保险。 +以前都是租一个门面把商品摆在店铺里,等着附近赶集市的人前来购买。这种模式的弊端在于,一个人一天能走的距离是有限的,会走到你店铺的人更是少之又少,如此一来店铺的客流量依赖于一个村(镇、市),而一个村(镇、市)再大也不过那么点人,销售量的前提是客流量。淘宝、京东、拼多多、苏宁易购等大型电商平台的兴起,就是致力于解决这一类「做生意难」的问题,通过这些电商平台把互联网大流量的优势发挥出来了,互联网提供了巨大的流量池,怎么利用这个流量池就是靠自己的能力了。 +问题在于很多人不会操作这一类电商平台的后台系统,初学者可能连一个后台怎么操作发货都不会,更别说如何利用增长思维来玩流量。少部分年轻人能自主学会如何操作电商平台的后台系统,大部分中年人士对复杂的后台就望而却步了。而就算你能操作复杂的后台系统,但是如何优化商品信息(标题、关键词、展示图等)来提高销量可能就不一定会了。 +其它诸如站外推广、美工做图、活动上报、刷销量、刷收藏等等一系列操作更不是随随便便找个人就能做的。网店代运营公司提供的这种代运营服务则是为了解决电商运营难的问题,网店代运营公司专注于提供网店代运营服务,而客户则专注于出单发货,这也符合随着社会发展工作会逐渐细分的规律。 +说实话,从几天观察来看,我走过的工资在网店运营领域的实力是非常不值得信赖的,虽然这个行业也不乏佼佼者,但是我看到的大部分都是没有真正创造价值,只是不断的去坑客户。我个人倾向于把专业的事情交给专业的团队去做,第一是能大大节省自己的时间、人力成本,另外也不会被运营这些事情扰乱到自己的好心情,但是要真正寻找到一个靠谱的代运营公司可真是难上加难。 +我拜访的几个公司都有一个通病,那就是前期过于注重销售导致后续服务没有跟上来,或者说的更直白一点就是他们根本就没想过如何去提升自己的服务质量,客户进来洗一遍之后就不管了,这个过程叫做洗小白。也有一个公司老板已经意识到这一块的问题了,但是公司自身实力和公司人员素质都跟不上。 +每个行业的兴起都不是没有原因的,代运营服务的出现的原因是为了解决了「网店运营难」的问题。虽然市场上绝大部分代运营公司都是在割韭菜,但是我相信网店代运营行业会越来越正规化,当然也可以代运营被这绝大多数人给作死。专业的人做专业的事,是社会发展的必然结果。 +行业目前是很乱的,同行之间相互伪造聊天记录截图、伪造转账记录、伪造合同等手段去各平台发布诋毁对方的言论,甚至伪装成新客户去盗取别家公司内部的一些信息。让人觉得搞笑的是,还有直接冒充大一点的公司去骗客户的,我看到公司管理层因为这些事情搞的极为头大,表现出了秀才遇上兵的无奈。 +换个角度看同行恶意竞争这件事,如果一个公司在搜索引擎都搜索不到的话,那只能说明这个公司随时都可能死去,前期承诺了再好的服务也不一定有用,公司死了怎么能提供服务呢?如果一个公司在上面全是负面,或者全是正面的信息都太假了。只不过这个行业的负面新闻太多了! +一个良性的市场竞争环境是行业发展的必要,但是像上述冒充同行公司、伪造事实依据诋毁同行公司的人,把整个市场都搞的乌烟瘴气了。这是非常短视的行为,短期可能会让自己有一定的收入,但是长期来看这是非常不利于行业发展的,这会逐渐让潜在客户甚至签约客户都逐渐对行业失去信心。 +后面专门写一篇文章来揭露我看到的网店代运营行业乱象吧!!!! + +
+ + Read More ~ +
+
+
+ +
+

+ + Git 基本原理及常用命令速查 + +

+ +
+ + + + +
+ +
+ +参考内容:Pro Git book + +如果你只是想查看 Git 常用命令可以选择直接到文章底部「Git 常用命令」阅读,文章大部分内容是 Git 进阶知识,均是自己的读书笔记,如果还想在此基础上再上一层楼,那可以直接看 Pro Git book。 +Git 历史 +版本控制器是一种记录一个或若干文件内容变化,以便将来查阅特定版本的修订情况。也就是说,版本控制器记录了一个可供考证的历史数据,通过该数据可以知道文件是怎么一步一步发展到今天这个样子的。 +最初 Linux 项目使用 BitKeeper 来管理和维护代码,但是到了 2005 年,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了免费使用 BitKeeper 的权力。那 Linux 开源社区的解决方案就是自己搞一个版本控制器,所以就有了 Git。 +简单说就是 Linus 被逼的去开发了这一款叫做 Git 的版本控制器,因为 Linus 本身就是内核专家与文件专家,所以 Git 也就自然而然具备了非凡的存储能力与性能。 +安装 +关于如何安装 git 可以查看 Pro Git book,安装完成后需要进行一些必要的配置,比如用户信息、文本编辑器、差异分析工具等等,我们可以通过git config --list来查看配置信息。比如我们要配置用户和邮箱,就可以像下面这样输入命令。 +$ git config --global user.name &quot;John Doe&quot; +$ git config --global user.email johndoe@example.com + +Git 原理 +Git 和大多数版本控制器有一个重要的区别,就是它直接记录快照,而非差异比较,其它大部分系统以文件变更列表的方式存储信息,而 Git 则存储每个文件与初始版本的差异。换句话说,只要你的文件有改动,那么 Git 就会将该文件复制一份,正因为 Git 的这个特性,所以 Git 仓库很容易就变得非常大;为了高效,如果文件没有修改,那么 Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。Git 对待数据更像是一个快照流。 +Git 有三个区,分别为:仓库、工作目录、暂存区。基本的 Git 流程为:1)在工作目录中修改文件;2)暂存文件,将文件的快照放入暂存区域;3)提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。那么相应的 Git 就有三种状态:已提交(committed)、已修改(modified)和已暂存(staged),你的文件可能处于其中之一。 + +Git 基础 +工作目录中的文件不外乎处于两种状态:已跟踪或未跟踪。已跟踪是指那些纳入了版本控制的文件,在上一次快照中有它们的记录;工作目录中除了已跟踪文件以外的所有文件都属于未跟踪文件,们既不存在于上次快照的记录中,也没有放入暂存区。 + +查看文件状态 +如果需要查看哪些文件处于什么状态,可以使用git status命令,这个命令显示的信息十分详细,如果你喜欢简洁一点的信息,那么可以在其后添加一个-s,其报告格式类似于下面这样。 +$ git status -s + M README +MM Rakefile +A lib/git.rb +M lib/simplegit.rb +?? LICENSE.txt + +??表示新添加的未跟踪文件;修改过的文件前面有M标记,右边的表示还没有放入暂存区,左边的表示已经放入暂存区了。当然你可能不希望每个文件都出现在未跟踪列表中,比如编译过程临时创建的文件、日志文件等等,所以可以通过创建一个名为.gitignore 的文件,列出要忽略的文件模式,它支持标准的glob模式匹配(shell 所使用的简化了的正则表达式),在 gitignore 中有一个十分详细的针对数十种项目及语言的.gitignore文件列表。 +git status对于具体修改显示的过于模糊,如果想查看具体修改了什么地方,可以使用git diff命令,比如git diff README.md。需要注意的是git diff本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动,如果需要查看已经暂存起来的变化,则要加上--staged或者--cached,比如git diff --cached README.md。 +删除文件 +当然我们不可避免的需要删除某个文件,如果你仅仅是简单的从工作目录中手工删除文件,那它并没有真正的从 Git 中删除,Git 会将这次删除识别为一次改动。更好的方式是使用git rm命令来完成删除文件的工作,比如git rm README.md就会从已跟踪文件中删除,并且连带从工作目录中删除指定文件。 +如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项-f(译注:即 force 的首字母)。 这是一种安全特性,用于防止误删还没有添加到快照的数据,这样的数据不能被 Git 恢复。 +另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。 换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。 当你忘记添加 .gitignore 文件,不小心把一个很大的日志文件或一堆 .a 这样的编译生成文件添加到暂存区时,这一做法尤其有用。这时就需要使用--cached选项了,比如git rm --cached README。 +查看历史 +我们或许因为某种原因需要回顾一下提交历史,这时git log就派上用场了,默认不用任何参数的话,git log会按提交时间列出所有的更新,最近的更新排在最上面,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。 +git log提供的选项很多,更详细的内容可以查看 Git 基础 - 查看提交历史。除了不带选项的命令,我个人更常用的命令还有另外两个,分别为:git log --pretty=oneline它将每个提交放在一行显示,在查看的提交数很大时非常有用;git log --graph或者git log --pretty=oneline --graph用于显示 ASCII 图形表示的分支合并历史。 +撤销操作 +在任何一个阶段我们都可能有想要撤销的操作,我们只需要掌握几个基本的撤销操作就能够应对日常的工作了。 +第一种情况:取消上一次提交。有时候当我们提交完之后才发现漏掉了几个文件没有添加,或者是提交信息写错了,此时可以使用带--amend选项的提交命令尝试重新提交,即git commit --amend。这个命令会将暂存区的文件全部提交,如果自上次提交以来你还没一做任何修改(比如,在上次提交后马上执行了此命令),那么快照将会保持不变,而所修改的只是提交信息。 +第二种情况:取消暂存的文件。假设你修改了两个文件并且想要将它们作为两次独立提交,但是却不小心输入了git add *暂存了它们两个,如何取消其中一个暂存呢?其实在运行git status时已经给出提示了。 +$ git status +On branch master +Changes to be committed: + (use &quot;git reset HEAD &lt;file&gt;...&quot; to unstage) + + renamed: README.md -&gt; README + modified: CONTRIBUTING.md + +所以如果我们想要取消CONTRIBUTING.md的暂存,那么就可以用git reset HEAD CONTRIBUTING.md命令来完成。 +第三种情况:撤销对文件的修改。有时候我们可能并不想保留对某个(若干)文件的修改,git status也给出了详细的提示,告诉我们如何将文件还原成上次提交时的样子,即git checkout -- &lt;file&gt;,比如输入命令git checkout -- CONTRIBUTING.md,就会将CONTRIBUTING.md重置到上一次提交时的样子。 +需要注意的是git checkout -- &lt;file&gt;是一个比较危险的命令,因为它仅仅是拷贝了另一个文件来覆盖当前文件,所以你对那个文件的所有修改都会消失,而且不可恢复。 +远程仓库 +前面我们都是在讲本地操作,远程仓库的使用是必不可少的技能。可以使用git remote命令查看每一个远程服务器的简写,对于已经克隆的仓库,它至少会包含一个origin,这是 Git 给克隆仓库服务器取的默认名字,它和其它服务器并没有什么区别,只是很少人会去修改这个默认名字而已。 +如果想要给一个远程仓库重新取一个简写名,那么可以运行git remote rename来完成,比如git remote rename pb paul就是将pb重命名为paul。值得注意的是这样同样也会修改你的远程分支名字,那些过去引用pb/master的现在全引用paul/master。 +当想要将自己的成果分享给他人时,就需要将其推送到上游,使用git push [remote-name] [branch-name]即可,比如你想要将master分支推送到origin服务器时,就可以运行git push origin master。 +除了分享自己的成果,我们也需要获取他人的成果,即从仓库拉取自己没有的信息,比如git fetch origin,需要注意的是git fetch命令会将数据拉取到你的本地仓库,但它并不会自动合并或修改你当前的工作,所以你还需要git merge来合并分支,实际上有一个git pull命令可以帮我们把这两个步骤都做了,你可以简单的将git pull理解为git fetch后面紧接着一个git merge。 +分支管理 +Git 的分支模型是它的必杀技特性,它处理分支的方式是难以置信的轻量,创建分支几乎是在一瞬间完成,而且在不同分支间的切换也非常的便捷,要理解 Git 的分支,我们必须要再次回顾 Git 是如何保存数据的。 +下图是我们的一个工作流,可以看到所谓的分支实际上就是一个可以移动的指针而已,master、v1.0都仅仅是一个指针,而创建分支、切换分支等操作也都只是对指针的操作,因此就不奇怪为什么 Git 这么快了。 + +那么 Git 又是如何知道当前在哪一个分支上呢?它仅仅是用了一个名为HEAD的特殊指针,你可以将HEAD想象为当前分支的别名,HEAD指向哪个分支,就表示当前处于哪个分支。 +分支创建与切换 +我们可以使用git branch [branch-name]来创建一个新的分支,比如git branch testing;如果使用不带选项的git branch,那么它会列出当前所有的分支,这里需要注意的是master分支也不是特殊分支,它是运行git init时自动创建的默认分支,因为大家都懒得去改它,所以它就好像变得特殊了一样。 +git branch [branch-name]只是创建了一个新分支,并不会切换到这个分支上面去,分支的切换说白了就是移动HEAD指针,我们只需要使用git checkout testing就可以切换到testing分支上去了。 +当然我们可以使用git checkout -b [branch-name]来创建一个分支并同时切换到这个分支,把这个命令与git commit -a -m来对比,你就会发现它们的类似之处。 +分支的合并与删除 +当我们零时在一个新分支上解决了问题后,需要将其合并到master分支,只需要切换到master再运行git merge命令即可,Git 会自动找到这两个分支的共同祖先,然后做一个简单的三方合并。 +当然理想情况下是直接合并成功,但是不免会遇到合并冲突的情况,一旦遇到冲突了,Git 会像下面这样来标记冲突内容,你需要做的是选择由=======分割的令部分的其中一个或者自行合并,当&lt;&lt;&lt;&lt;&lt;&lt;&lt;,=======,和&gt;&gt;&gt;&gt;&gt;&gt;&gt;这些行被完全删除了,你需要对每个文件使用git add将其标记为冲突已解决。 +&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD:index.html +&lt;div id=&quot;footer&quot;&gt;contact : email.support@github.com&lt;/div&gt; +======= +&lt;div id=&quot;footer&quot;&gt; + please contact us at support@github.com +&lt;/div&gt; +&gt;&gt;&gt;&gt;&gt;&gt;&gt; testing:index.html + +当合并完分支后,之前的分支一般就不会再要了,这时你可以运行git branch -d [branch-name]来删除指定分支,比如使用git branch -d testing来删除testing分支。 +远程分支 +远程分支以(remote)/(branch)的形式来命名。如下图所示,如果你克隆一个仓库下来,那么这个仓库除了会有一个本地的分支指针,还会有一个远程分支指针。如果你在本地的master分支做了一些工作,但是你并没有与origin服务器连接,那么你的origin/master指针就不会移动。 + +在这之前我们已经讲过通过推送分享自己的成果,在运行git push origin master命令时,Git 会自动的将master分支名字展开为refs/heads/master:refs/heads/master,即意味着推送本地的master分支来更新远程仓库上的master分支,所以你也可以运行git push origin master:testing来做类似的事,如果远程仓库没有testing分支,那它会自己创建一个新的testing分支。 +我们肯定需要创建一个跟踪远程仓库的其它分支,最简单的就是运行git checkout -b [new-branch] [remote-name]/[branch],该命令会以远端[branch]分支的内容来创建本地的[new-branch]分支,Git 也对该命令做了一个简化,git checkout --track [remote-name]/[branch],该命令就会在本地创建一个[branch]分支用于跟踪远端的[branch]分支。 +当然,我们还需要了解一个删除远程分支的命令git push origin --delete [branch],需要注意的是这个命令基本上只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。 +Git 常用命令 +挑了一些比较重要 Git 命令,我把个人常用的命令使用代码块标记出来了。 + + + +命令 +作用 + + + + +git init +将一个目录转变成一个 Git 仓库 + + +git clone +从远程克隆一个仓库到本地,它是多个命令的组合, + + +git add +将内容从工作目录添加到暂存区 + + +git commit +将暂存区文件在数据库中创建一个快照,然后将分支指针移到其上 + + +git commit -a -m [msg] +git add和git commit的组合 + + +git status +展示工作区及暂存区域中不同状态的文件 + + +git status -s +比git status展示的内容更加简洁 + + +git diff +对比工作目录文件和暂存区快照之间的差异 + + +git diff --cached +对比已暂存的差异 + + +git reset +根据你传递给动作的参数来执行撤销操作 + + +git rm +从工作区,或者暂存区移除文件 + + +git clean +从工作区中移除不想要的文件的命令 + + +git checkout +切换分支,或者检出内容到工作目录 + + +git branch +列出你所有的分支、创建新分支、删除分支及重命名分支 + + +git checkout -b [branch] +创建新分支并切换到该分支 + + +git log +展示历史记录 + + +git log --pretty=oneline +简洁版历史记录 + + +git merge +合并一个或者多个分支到已检出的分支中 + + +git stash +临时地保存一些还没有提交的工作 + + +git pull +git fetch 和 git merge 命令的组合体 + + +git push +将本地工作内容推送到远程仓库 + + +git push origin local_branch:remote_branch +比git push更加详细的推送 + + +git checkout --track [remote-name]/[branch] +在本地创建一个分支用于跟踪远程同名分支 + + +git remote -v +显示所有远程仓库地址 + + +git remote set-url origin [url] +设置远程仓库地址为url + + + + +
+ + Read More ~ +
+
+
+ +
+

+ + Java 垃圾回收机制详解 + +

+ +
+ + + + +
+ +
+ 最近主要时间都放在知识图谱构建中,但是还是需要给自己充电。想在近段时间好好把 JVM 的垃圾回收好好看一下,学习然后输出,是我新找到的有效学习方法,希望你看了之后能有收获。 +什么是垃圾 +垃圾回收(常称做GC——Garbage Collection)诞生于1960年 MIT 的 Lisp 语言,垃圾回收机制也已经用在了很多编程语言中,比如 Java、Python、C# 等。我们这里主要说 Java 中的垃圾回收。 +在JVM中,程序计数器、虚拟机栈、本地方法栈都是随线程生而生,随线程灭而灭;栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理;常说的垃圾回收主要集中在堆和方法区,这部分内存是随着程序运行动态分配的。 +既然要回收垃圾,那么我们首先需要知道的就是,什么样的对象是垃圾。一般有两种方式: +引用计数 +每个对象有一个引用计数属性,新增一个引用时计数加 1,引用释放时计数减 1,当引用计数变为 0 的时候,这个对象就可以回收了。但是这个方法无法解决对象循环引用的问题。 + // 对象循环引用示例 + + Object objectA = new Object(); + Object objectB = new Object(); + + objectA.instance = objectB; + objectB.instance = objectA; + + objectA = null; + objectB = null; + +假设我们有上面的代码。程序启动后,objectA和objectB两个对象被创建并在堆中分配内存,它们都相互持有对方的引用,但是除了它们相互持有的引用之外,再无别的引用。而实际上,引用已经被置空,这两个对象不可能再被访问了,但是因为它们相互引用着对方,导致它们的引用计数都不为 0,因此引用计数算法无法通知GC回收它们,造成了内存的浪费。如下图:对象之间的引用形成一个有环图。 + +可达性分析 +或者叫根搜索算法,在主流的JVM中,都是使用的这种方法来判断对象是否存活的。这个算法的思路很简单,它把内存中的每一个对象都看作一个结点,然后定义了一些可以作为根结点的对象,我们称之为「GC Roots」。果一个对象中有另一个对象的引用,那么就认这个对象有一条指向另一个对象的边。 + +像上面这张图,JVM会起一个线程从所有的GC Roots开始往下遍历,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收。(这里多说一句,我们的JVM一起动,就至少有两个线程启动,一个是垃圾回收线程,一个是我们自己的主线程。) +那么现在问题就变成了——什么样的对象可以当作 GC Roots?共有四种对象可以作为 GC Roots。 +虚拟机栈中的引用的对象 +们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种对象可以作为 GC Roots。 +全局的静态的对象 +也就是使用了static关键字定义了的对象,这种对象的引用保存在共有的方法区中,因为虚拟机栈是线程私有的,如果保存在栈里,就不叫全局了,很显然,这种对象是要作为 GC Roots 的。 +常量引用 +就是使用了static final关键字,由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也作为GC Roots。 +本地方法栈中JNI引用的对象 +有时候单纯的java代码不能满足我们的需求,就可能需要调用 C 或 C++ 代码(Java 本身就是用 C 和 C++ 写的嘛),因此会使用native方法,JVM 内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为 GC Roots。 +垃圾回收算法 +有意思的是在 JVM 规范中,并没有明确指明 GC 的运作方式,各个厂商可以采用不同的方式去实现垃圾收集器。这篇文章简单介绍常见的垃圾回收算法。 +标记-清除算法 +标记-清除算法分两个步骤,分别为「标记」和「清除」,字如其人。它是一个最基础的垃圾回收算法,更高级的垃圾回收算法都是基于它改进的。 +它的运行过程是这样的:首先标记出所有需要回收的对象,标记完成后,再统一回收掉所有被标记的对象。 + +标记-清除算法的缺点有两个,一个是空间问题,标记清除之后会产生大量的不连续内存碎片。内存碎片太多,程序在之后的运行过程中就有可能找不到足够的连续内存来分配较大的对象,进而不得不提前触发另一次垃圾回收,导致程序效率降低。标记-清除算法的另一个缺点是效率问题,标记和清除的效率都不高,两次扫描耗时严重。 +复制算法 +复制算法把内存按容量划分为大小相等的两块,每次只使用其中的一块。如果正在用的这块没有足够的可使用空间了,那么就将还活着的对象复制到另一块去,再把使用过的内存一次性清掉。 + +这样就实现了简单高效的做法,每一次进行内存回收时,就不用再去考虑内存碎片这些复杂的情况,只需要移动堆顶指针就可以。但是缺点也很明显,可使用内存只有原来的一半了,而且持续复制生命力很旺盛的对象也会让效率降低哇。复制算法适用于存活对象少、垃圾对象多的情况,这种情况在新生代比较常见。 +标记-压缩算法 +在老年代,大部分对象都是存活的对象,复制算法在这里就不靠谱了,所以有人提出了标记压缩算法,标记过程和标记清除算法一样,但是清理时不是简单的清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存,需要移动对象的成本。 + +分代算法 +前面的几种垃圾回收算法中,没有一种可以完全替代其他算法,他们具备各自的特点与优势,因此更好的方法是根据垃圾对象的特性来选择合适的回收算法。 +分代算法的思想就是将内存空间根据对象的特点不同进行划分,选择合适的垃圾回收算法来提高回收效率。分代的思想已经被现有的虚拟机广泛采用。 +分区算法 +分区算法就是将整个堆空间再划分为连续的不同小区间,每一个小区间独立使用,也独立回收。 + +一般在相同条件下,堆空间越大,那么一次GC的时间就越长,因此而产生的停顿时间也就越长。为了控制GC的停顿时间,根据目标停顿时间,每次合理回收若干个小区间,而不是整个堆空间,进而减少一个GC的停顿时间。 +垃圾收集器 +上面讲的是垃圾收集算法,讲的是理论,垃圾收集器就是这些理论的具体实现。下面介绍一些垃圾收集器 +Serial收集器 +串行收集器是高效率的、古老的收集器,它只用了一个线程去回收垃圾。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩算法。串行是指 GC 过程和用户过程是串行的,在垃圾收集过程中会 stop the world,JVM在后台自动发起垃圾回收,会在用户不可见的情况下,把用户的线程全部停掉,就是 GC 停顿,给用户带来不良体验。 +红色代表 GC 线程,灰色代表用户线程,下同。 + +ParNew收集器 +ParNew 收集器就是 Serial 收集器的多线程版本,除了多线程以外,其余行为都和 Serial 收集器一样。新生代并行收集,老年代串行收集;新生代使用复制算法、老年代使用标记-压缩算法。 + +Parallel Scavenge收集器 +Parallel Scavenge 收集器类似于 ParNew 收集器,因为与吞吐量密切,也称为吞吐量收集器。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制 GC 的时间不大于多少毫秒或者比例。Parallel Scavenge 收集器以高吞吐量为目标,减少垃圾收集时间,让用户代码获得更长的运行时间;GC 停顿时间的缩短,是用牺牲吞吐量和新生代空间来换取的。 +Parallel Old收集器 +Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,使用多线程和「标记-压缩」算法,在 JDK1.6 版本才开始提供。 +CMS收集器 +CMS(Concorrect mask sweep)收集器是一种以获取最短停顿时间为目标的收集器;也称为并发低停顿收集器。常用在 WEB、B/S 架构的服务系统中,因为这类应用很注重响应速度,尽可能减少系统停顿时间,给用户带来较好的体验。从名字上就可以看出来,它是基于「标记-清除」算法实现的,整个过程分为 4 步: +初始标记 +初始标记仅仅标记 GC Roots 能直接关联到的对象,所以速度很快,需要停止服务(Stop The World)。 +并发标记 +并发标记是进行 GC Roots Tracing 的过程,为了标记上一步集合中的存活对象,因为用户程序这段时间也在运行,所以并不能保证可以标记出所有的存活对象。 +重新标记 +重新标记阶段是为了修正并发标记阶段因用户程序继续运作而导致标记变动的那一部分对象,采用多线程并行来提升效率,会停止服务,时间上远比并发标记短,较初始标记稍长。 +并发清除 +这个阶段即并发收集垃圾对象,可以与用户线程一起工作。 +虽然 CMS 收集器线程可以和用户线程一起进行,但是它肯定会占用 CPU 资源,拖慢应用程序是肯定的,总的吞吐量会降低。 + +G1收集器 +(看下垃圾回收算法中的分区算法)这是目前最新的前沿成果,它基于“标记-压缩”算法,可以进行空间整理,不会产生碎片。前面的垃圾收集器,收集范围都是整个新生代或老年代,但是 G1 收集器不是这样,使用 G1 收集器时,java堆的内存模型不同,它还保留有新生代和老年代的概念,它们都是一部分区域(可以不连续)的集合。除此之外,G1 收集器还能建立可预测的停顿时间模型,可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒。G1 跟踪各个区域(Region)获得其收集价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。G1 垃圾回收也分4步: +初始标记 +仅标记 GC Roots 能直接关联到的对象。 +并发标记 +进行 GC Roots Tracing 的过程,并不能保证可以标记出所有的存活对象。这个阶段若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收。 +最终标记 +为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录,G1 中采用了比 CMS 更快的初始快照算法: snapshot-at-the-beginning (SATB)。 +筛选回收 +首先排序各个 Region 的回收价值和成本,然后根据用户期望的 GC 停顿时间来制定回收计划,最后按计划回收一些价值高的 Region 中垃圾对象,回收时采用&quot;复制&quot;算法,从一个或多个 Region 复制存活对象到堆上的另一个空的 Region,并且在此过程中压缩和释放内存。 + +
+ + Read More ~ +
+
+
+ +
+

+ + SEO 入门——你必须了解站长平台知识 + +

+ +
+ + + + +
+ +
+ 我自己也刚接触建站不久,10 月 1 日开始购买服务器和域名学习建站,10 月 11 日一查已经被 Google 收录了 20 多个链接了,下面是我个人就我自己的实践总结的一点关于站长平台的知识。 +基本上只要做的大的搜索引擎都会有站长平台的,站长平台是搜索引擎官方提供的辅助网站优化管理的工具,它可以针对我们的网站提供一定的优化方向,而且新站一开始搜索引擎的爬虫可能来都不会来,我们一开始也需要通过站长平台主动给搜索引擎提交我们的站点地图。 +盘点业界主流站长平台 +既然我们讲的是站长平台知识,肯定是得先要去了解一些主流的站长平台,那业内知名的站长平台有哪些呢?下面是 Guanngxu 为大家盘点的主流站长平台。 +谷歌站长平台 +入口:https://search.google.com/search-console +谷歌在搜索引擎领域的地位不言而喻,稍微遗憾的是它已经退出中国市场了,它的技术、算法等在业界都是一流,即使它已经退出中国市场了,但也绝对是一个不可忽略的存在 +百度站长平台 +入口:https://ziyuan.baidu.com/ +目前国内使用最多的站长平台就是百度了,谷歌退出中国之后就百度一家独大,也是目前国内功能最完善的站长平台,对于网站管理、数据检测等都有一定的参考意义。 +搜狗站长平台 +入口:http://zhanzhang.sogou.com/index.php/site/index +搜狗的功能相对来说简单一些,只提供了一些基础的网站优化功能,虽然目前看搜狗搜索的流量也跟不上,搜狗现在已经被腾讯收购,不知道后续是否会有更大的发展,在国内搜狗的流量也还是不小的。 +必应站长平台 +入口:https://www.bing.com/toolbox/webmaster/ +必应是微软旗下的搜索引擎,必应在国内的市场占有率不太高,使用必应的大部分是讨厌百度的广告,又暂时不知道如何科学上网的人群,不过它的站长平台做的相对来说还是可以。 +头条站长平台 +入口:https://zhanzhang.toutiao.com/ +大家都知道字节跳动是互联网界的一匹黑马,字节跳动最近正在抖音等旗下的应用开始测试搜索广告业务,虽然头条站长平台很多功能处于不完善的状态,但是它之前的成绩也是大家有目共睹的,值得期待。 +神马站长平台 +入口:https://zhanzhang.sm.cn/ +神马站长平台是依托于 UC 浏览器和神马搜索衍生出来的一个平台,它大部分的流量都来自于移动端,所以它平台的功能比较偏向于移动端,功能比较基础,几乎没有什么算法和优化的通知。 +利用站长平台我们可以做些什么? +新站一般都不会有搜索引擎的爬虫程序主动来爬取的,人家甚至都不知道你这个小网站的存在,那么我们就需要主动的去告诉各个搜索引擎,让他们知道我们这个小网站的存在。比如百度站长平台,我们可以先到「站点管理」添加一个自己的站点。 + +说一下每个站长平台都具备的一个功能,那就是提交自己网站地图的接口。新站一开始都可以通过这些接口去提交我们网站的链接,将站点提交到各个搜索引擎以后,可以加快蜘蛛抓取我们网站的速度,增加网站被收录的速度。当然也有人说用老的域名比较好,这一点我自己并没有亲身尝试过,不敢妄下断论。 + +像必应站长后台还有个网站扫描功能,谷歌也有类似的网址检查功能,我们可以借用这些工具看看如何优化自己的网站,让自己网站的内容对搜索引擎爬虫程序更加友好。 + + + +
+ + Read More ~ +
+
+
+ + + +
+ + 上一页 + + + 下一页 + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/page/5/index.html b/page/5/index.html new file mode 100644 index 00000000..44a9a3ac --- /dev/null +++ b/page/5/index.html @@ -0,0 +1,1505 @@ + + + + + + + + Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ + +
+

+ + 买课程囤在那里不看,你怎么可能进步 + +

+ +
+ + + + +
+ +
+ +文章于 2018-04-15 在微信公众号「刘小绪同学」发布,原文链接:你如果只是一直囤干货,那永远不可能进步,今日重读做了部分改动 + +这两天,没事整理了自己的微信收藏和 QQ 收藏文件夹下面的文章。发现了一个在大多数人身上都存在的问题,那就是囤干货。 +我使用微信的时间比较晚,从14年才开始,但是仅仅不到4年的时间,在微信收藏里面就有几百篇文章,有一些传授技能的文章,比如 word、ps 等简单教程;其他一些属于心灵鸡汤类文章(现在反倒不觉得这类文章值得看),还有一些搞笑的文章,当然里面也夹杂着些许的优秀视频。文章数量数 14 年和 15 年最多。 +现在回头看,我当时就陷入了囤文章的陷阱了,或者说自己是在“假装阅读”。仔细一想,其实生活中大多数人都有类似的举动。早成起床、饭后的一段时间、晚上睡觉前,这些时间大多数人都习惯性的去翻翻公众号、朋友圈,这里面不乏有好文章、干货文章。然而没多少人会静下心来把文章读完,而是在大概读到一半的时候,选择收藏这篇文章,然后在心里告诉自己,明天要好好读一下这篇文章。到了明天,其实又是重复了今天的这样一个过程。 +记得大概一年前也无意中看到大学认识的一位师弟发的说说,内容如下: + +上面的图是我特地翻出来的,一年后这个师弟又发了一条说说,存满了两个云盘,应该是有 5000 G 左右的资源。 +我认为这都是在假装学习,而且假装学习的人不计其数。为什么上学的时候,那些每天都静静的待在教室学习的同学,成绩反倒不是很好呢?而且大多数这样的学生在班级都是排在中等,而成绩好的学生却不是学习投入最多的人。背后的原因显而易见。 +我囤课最严重的时间段是也是 14 年和 15 年,网上有不少干货资源,什么 Linux、各种项目实战、计算机网络等等培训视频不计其数。那时候干的第一件事就是,上百度云把这些资源下下来,而且一个资源往往要下一周甚至更久;然后告诉自己,下周开始每天看一段视频,但是最终的结果是过去了 N 个下周,依然没有去处理这些资源。 +现在博客、公众号也有一些不知道是为了获取更多人的关注,还是仅仅是做公益,文章末尾会标注:关注公众号,回复“XXXXX”,即可获得多少多少G的资源,这个「多少」一般是在 500 以上。我个人现在是对这类文章没有什么兴趣的,因为几百 G 甚至上千 G 的东西,我是不可能看完的,我清楚自己的能力,我也不否认可能有人有毅力能看完,那肯定是凤毛菱角了,我一个普通人不与凤毛菱角对比。 +我于 19 年开始使用豆瓣,用上豆瓣之后就冒出来另外一个毛病,总想快速的把一本书看完而不去管有没有真正理解书里面的知识,总花很多时间看更多的电影,仅仅是为了在豆瓣上标记「看过」、「读过」那一刻的快感。 +走出象牙塔之后事情变得繁杂琐碎,什么事都想做到最好还什么事都想去做,问题是那些边边角角的事情还经常扰乱自己的视线,结果没有一件做的像样的事情。 +当然,我现在的认识相比几年前,得到了一些提升,至少我现在不糊干囤课之类的事了,仅仅是把干货囤在那里,其时你还是昨天的自己,并没有进步,重要的不是自己买了多少优质的课程,而是去动手动脑学习,最好的学习就是实践。现在我都会把自己收藏的好文章在一周之内处理掉。 +所以要想真正取得进步,首先就需要告别囤课的习惯,不能假装学习,如果学习是为了给比别人看,那还不如不学。文章仅是我个人的一点感悟,没有文采逻辑和言,希望与你共勉。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何搭建一个属于自己的博客/企业网站 + +

+ +
+ + + + +
+ +
+ +参考内容: +如何做博客/企业站以及注意事项 +Typecho支持Emoji表情方法 + +说明:此篇文章得益于王红星的指导,喜欢直接粗暴一点的朋友可以跳过前面,直接从程序的选择开始阅读。 +我的博客搭建之路 +说起来有点惭愧,我自己是计算机科学与技术专业出身,虽然博客系统在我眼里是很简单的系统,但是我却一直畏惧从零开始搭建一个系统的麻烦性,因为但是安装程序的运行环境就会耗掉我大量的时间,再加上我写代码、测试、上线等工作少说也得要四五天!所以我一直都在 CSDN 一类的平台写作,然而这些平台为了利益把体验做的越来越差! +还在大学时听说过 WordPress 可以搭建博客,不过总是迈不出行动的步伐,认为一旦涉及到服务器的工作就不会简单。直到我在 Twitter 发现有人推荐 Gridea,才发现搭建博客系统原来可以这么简单,主要还是免费的。 +于是我借用 Gridea 和 Github Pages 搭建了一个博客系统,EryouHao 开发的这个写作软件用起来相当便捷。但是 Github Pages 在国内总是速度很慢,而且图片资源经常加载不出来,而且 Gridea 是用 Vue 技术开发的,打包成桌面软件后总会出现卡顿情况,自己写作的那个劲又逐渐褪去了。在2020 年国庆观《我和我的家乡》有感所写中也提到在知乎一不小心就违反了社区规范,索性花功夫研究如何搭建自己的博客系统,也才有了这篇简单的教程。 +一个博客/企业站需要什么 +可以在网络上访问的东西背后都有一套程序支撑,博客系统(企业站点)就是运行在某处的一套程序。要把一套程序运行起来,肯定需要运行程序的空间和驱动程序运行的系统,比如我们的手里的电脑就是一种运行程序的空间,你使用的系统(Windows、macOS、Linux)就是驱动程序运行的系统,下面推荐的服务器都已经把把空间和驱动给你安装好了,所以完全不用担心自己不懂如何安装。 +程序运行起来了还得让外面的人看到才行,所以我们还需要一个域名(类似于www.baidu.com一样的东西),我们把这个域名绑定到服务器上,别人在浏览器输入这个域名就能看到我们的博客(程序)了。 +如果希望自己的博客打开速度很快,除了选择比较好的空间外,还可以购买 CDN 服务;希望自己的文章能更好的被百度之类搜索引擎收录的话,可以购买独立 IP 主机。 +程序的选择 +WordPress 应该是目前全球使用的最广泛的开源程序,结构良好,功能强大,社区内容丰富。但是对于博客(企业站点)来说,WordPress 就显得比较臃肿。Typecho 是一个轻量、简洁、快速的程序,除了官方论坛,在https://typecho.me/也有很多主题和插件。虽然 Typecho 各种主题、插件没有 WordPress 丰富,但是对于搭建博客系统已经足够了。 +网上也有文章对两个系统做了对比,比如这篇:个人博客平台选择 Typecho 还是 WordPress ?Typecho 是原生支持 Markdown 语法的,可能是喜欢 Markdown 写作同学的福音,具体选 Typecho 还是 WordPress 可以凭自己的感觉,两个程序之间也是可以互相迁移的。 +WordPress 安装包下载:https://wordpress.org/download/(12.4M) +Typecho 安装包下载:http://typecho.org/(400K) +空间的选择 +空间类型包括服务器、VPS和虚拟主机,它们的价格是逐渐减少的。对大部分人来说并不是越贵越好,比如服务器和 VPS 还需要自己懂一些运维知识才行。仅仅只是搭建博客(企业站点)的话,一个虚拟机就足够使用了,又便宜又不费心。 +空间地理位置是需要重点考虑的一个因素,如果你做的是英文站点、都是国外用户,那可能一个美国主机更适合你;如果你的用户是国内的话,那大陆主机和香港主机可能更适合你。 +王红星在他的文章中推荐了两个主机商,分别是衡天主机和戈戈主机。我自己选的是衡天主机,有问题可以随时通过他们的客服询问或是提工单,体验很不错!偷偷告诉你,在购买前可以搜搜优惠码!阿里云也很不错,只是稍微有点贵! +域名注册 +国内有腾讯云、阿里云、爱名网等域名注册商,国外有 Name、Namecheap、Godaddy、Dynadot 等域名注册商。有些域名商默认会提供域名保护功能,有的则需要购买该功能。各个域名注册商的价格也都差不多,根据自己的实际需求选一个就可以了。 +我自己选的是腾讯云,它第一年域名的优惠力度比较大,比如我要注册guanngxu.com这个域名,可以看到都是有优惠的。 +腾讯云:https://dnspod.cloud.tencent.com/ +阿里云:https://wanwang.aliyun.com/ +爱名网:https://www.22.cn/ + +域名解析 +我们所有的准备工作都做好了,下一步就是安装程序搭建博客系统了,下面我以衡天主机安装 Typecho 为例进行一个简略的讲解,结合我下面的内容和网上的资料,应该很快就能搭建自己的博客系统了。 +主机购买后就知道它的 IP 地址了,首先我们去域名注册商处选择把域名解析到我们买的服务器上面。以腾讯云为例,在「我的域名」页面点击「解析」--&gt;「快速添加网站/邮件解析」后会弹出如下页面。 + +选择「网站解析」的「立即设置」后会弹出另一个页面,你只需要在这个页面把你购买的主机 IP 地址填进去就可以了。稍等几分钟直接在浏览器输入域名访问,如果浏览器出现了页面而不是报找不到服务器 IP 地址,那么域名解析就完成了。 + +安装 Typecho +进入到云主机管理面板后,点击「文件管理器」可以看到如下图的目录,其中public_html目录就是咱们博客程序的目录,我们把下载好的 Typecho 包上传到这个目录并解压。 + +需要注意的是解压后的目录是build目录,我们需要将解压后的目录移动到上一级,,保证public_html目录看到的是下图这个样子的,然后再输入域名去访问自己的博客。 + +如果不出意外,输入域名后出现的应该是下面的页面,点击「我准备好了,开始下一步」发现了什么?需要填写数据库信息。我们再回到衡天云主机管理面板,滑到「数据库管理」选择「新建数据库」,设置数据库名、数据库用户名、密码等,点击「创建」即可,这些信息就是安装 Typecho 需要的信息,再回到之前的页面把这些信息填进去,自己的博客系统就搭建完成了。 + +那么现在你就拥有了一个自己的博客系统,如果样子不太好看你可以去官方论坛或https://typecho.me/里面找自己喜欢的主题,如果有能力甚至可以自己修改或原创主题,一些插件在上面也都是可以找到的。 +Typecho 官方也提供了相应的安装文档,如何使用插件、如何调整网站外观等常见问题在官方链接http://docs.typecho.org/doku.php也都已经有说明了,此处便不再作赘述! +让 Typecho 支持 Emoji 表情 +21 世纪的互联网时代怎么能少了 Emoji 表情呢?由于编码问题,Typecho 默认是不支持 Emoji 表情的,所以我们只需要将编码改成支持 Emoji 表情的编码就可以了,具体一点就是把原来的utf8编码修改为utf8mb4,修改编码的方式如下: +修改数据库编码 +在衡天云主机管理面板选择「phpMyAdmin」,然后选择你刚才建立的数据库,选择「操作」--&gt;「排序规则」--&gt;「utf8mb4_unicode_ci」--&gt;「执行」 +修改表编码 +在 phpMyAdmin 面板点击「SQL」,直接运行下面的语句就可以了。要注意你所建表的前缀,我建表所填写的前缀是gx,所以我都是gx_xxxxxxxxx,默认前缀是typecho。 +alter table gx_comments convert to character set utf8mb4 collate utf8mb4_unicode_ci; +alter table gx_contents convert to character set utf8mb4 collate utf8mb4_unicode_ci; +alter table gx_fields convert to character set utf8mb4 collate utf8mb4_unicode_ci; +alter table gx_metas convert to character set utf8mb4 collate utf8mb4_unicode_ci; +alter table gx_options convert to character set utf8mb4 collate utf8mb4_unicode_ci; +alter table gx_relationships convert to character set utf8mb4 collate utf8mb4_unicode_ci; +alter table gx_users convert to character set utf8mb4 collate utf8mb4_unicode_ci; + +修改数据库配置文件 +数据库配置文件在public_html文件夹下,文件名为config.inc.php,其中有一行的内容是charset =&gt; utf8,将它修改为charset =&gt; utf8mb4就可以了。 +看看有了小表情是不是很可爱? + + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何空手利用拼多多(淘宝)赚钱套利? + +

+ +
+ + + + +
+ +
+ 在开头先说明一下,我自己不懂免费流量的玩法,以下我介绍的内容是我为了帮助公司一些想拿销冠的小伙伴而探索的方法,自己去实践了是有效果的,而且就个体来讲的话是可复制的套路,但我基本没放什么精力在上面,分享出来主要是希望朋友一起交流免费流量的玩法,毕竟对个人来讲付费流量还是太贵了! +估计大部分小伙伴都还不太能接受拼多多吧!拼多多上面的假产品确实不少,但是在「百亿补贴」专场里面有官方的监管,品质上都是可以做到保证的,比如 Airpods Pro 官方价格是 1999 元,而在拼多多的「百亿补贴」里面买下来 1500 都不到,那么这中间就具备了套利空间,转手在闲鱼、转转上面卖 1600 是不是很香? +我自己没有玩过转转,这里简单说一下闲鱼吧!一定要用信用很好的账号,因为这会直接体现到你的商品中。为了让客户相信我们首先要打造好人设,头像、昵称、生日、常住等等信息都填上,注意这些信息也不是瞎填,你的这些信息要给客户体现一种什么性格?什么职业?什么年龄?下面是我自己的一个闲鱼号。 + + +年龄 25 岁,喜欢数码、宅男、喜欢听歌,在互联网行业,是不是给别人的亲切感也比较强?更容易让人信服?而且这样的基础资料也比较符合我上面提到的转卖 Airpods Pro 的形象,总的来说就是信息越完善越容易让人信服。 +当然这属于比较简单的套利方式,一个人最多能注册 3 个账号,如果不是工作室的话维护起更多的账号也会费时费力,我在公司客户里面看到了一个比较 NB 的操作,去淘宝联盟去采集那些有优惠券的商品,就是做淘宝客店铺,去找那些有淘宝返利的商品,然后批量上架到自己的淘宝店铺,淘宝对那些下架前的商品会提高权重,有机会显示到前面,相对来说引流成本就能降低一些!听说还有些工作室直接利用软件批量采集淘宝联盟的商品,然后批量上架到京东商铺去赚返利。 +此处多说一句,我现在所在公司的客户里面有很多是同时开了淘宝和拼多多店铺的,他们在拼多多上卖的要比淘宝便宜,因为开拼多多店铺的成本要比淘宝低,所以拼多多上面也不都是假产品,像我下面截图中的前面有个「品牌」标志的就是真品,我们接到的很多客户是上不去这个的,不过我目前除了拼多多「百亿补贴」里面的东西会买外,其他的商品我还是不会买。 + +上面提了一下开个淘宝店,估计很多人也都只是在淘宝上买过东西,却从来没有在淘宝上卖过东西吧。但是会读到这篇文章的你至少知道先去搜索引擎查看,而大部分人连主动搜索的意识都没有,只知道瞎刷抖音。10 亿网民里面什么素质的人都有,梦想嫁个有钱人的、想快速赚钱一夜暴富的,去支付宝搜一搜「一夜暴富」就知道该怎么做了。 + +今年很多人也都因为疫情原因赋闲,查来查去好像就是开网店靠谱直接一点,因为这比较符合传统的一件产品卖给一个人的思维模式,只是把商铺搬到线上了而已。我自己没有货源怎么办?淘宝看起来好复杂啊,我不会开店怎么办?听说网店也需要做图装修?其实这些知识只需要使用百度搜索一下就能找到,连一点点高级的搜索技巧都不需要,但是就是有太多想挣钱却又懒得去主动学习的人,不然就不会出现那么多割韭菜的项目了,而且韭菜还越割越多! +应该有部分伙伴听说过以前有工作室通过代开通微信公众号赚钱吧?这个「淘宝开店」和「代开通公众号」类似,比公众号要稍复杂一些,有很多公司(包括我现在的公司)都是跑抖音广告去挖掘有开店意向的销售线索,只是帮人家开一个淘宝店就收 1000 以上的佣金。 +我这里还是拿闲鱼举例,下面图片是我截的我闲鱼的图片,随便做了个海报上去,海报内容突出「代开网店」、「淘宝教学」之类的词,一天会进来几十个咨询如何开网店的,基本上每天都能成交 2-3 个,好的时候能成交 5 个以上。这里要说明的是海报是为了规避闲鱼的检测,我试过用一个 Word 表格列出来我能做的内容,但是商品立马就被强制下架了,估计是平台也喜欢漂亮清晰的图片吧! + +为了增加曝光量(毕竟展现和转化是直接相关的),闲鱼上架的商品记得每天都去擦亮一下,如果可以的话邀请朋友帮助自己刷刷单,再用微信把钱转给朋友!因为闲鱼上面的曝光引流时间权重是15日,如果你的商品 15 天都没有卖出去的话,那么系统就会认为这个商品不是优质商品,这一点可以拿我这几天玩闲鱼的一个案例证实。可以看到截图中我上架了 3 个同样的商品,其中第二个是被人买过后我又重新上架了,其他几个商品的曝光量直接比第二个差了一个数量级不止,这还是我已经没有管闲鱼状态下的结果。 + +当然这里面需要用到一些话术,新手基本都不知道有一件代发的产品,他们都会问没有货源怎么办。我就以「没有货源」为例来回答,您没有货源没有关系的,我这边可以帮您对接一件代发的货源,基本上的货源都能涵盖到。如果说的再没有良心一点您没有货源没有关系,我这边毕竟在做这个,所以认识了很多的厂家老板,我可以免费为您对接厂家的货源,这个您放心就好了。 +从上面图中还能看到我除了海报还放了一些关于淘宝运营的书籍上去,想想什么样的人会去搜索这样的书籍?要么就是想学习淘宝运营的,要么就是想要自己开网店。淘宝运营有淘宝大学里面的课程内容,市面上关于淘宝运营的书籍、课程的基本都能从淘宝大学里面找到。所以如果进来的客户想开网店,那么你直接给他引导开网店;而如果对方是想学运营知识,你可以直接把淘宝大学的内容换成从你嘴里出来的内容;如果对方是因为想开网店而学习的话,那么学习这件事是反人性的,这种流量放一段时间也有部分是能成交的。这个「淘宝教学」的项目据我所知有公司已经做了 10 年了,而且公司越做越大。 +这里顺便说一个投放广告(引流)的思路:广告展示的产品是 A 罩杯内衣,但实际要推广的是丰胸产品。给买 A 罩杯的女人推丰胸产品,这个思路适合任何行业的投放!广告投放做人群探索的时候,优化师也常常会用这样的思路去做人群计算,从而让进入 CRM 的销售线索更加精准。 +再分享一个拼多多空手套利的案例,我现在所在的这家公司的第一桶金就是这样来的,当然他们那时候市场也比现在好做多了!想一下我们自己在淘宝、京东上面买东西的时候会关注哪些东西?买家秀、销量、差评率等,拼多多自己留了一个后门可以直接修改销量,这个操作尤其对那些新店铺有用。这个数据除了可以直接吸引买家眼球外,还可以增加店铺权重,另外还可以上报一些活动,比如什么双十一、618 这一类的活动平台都是会设置门槛的,有些店铺就是因为销量不够而达不到上报活动的条件。 +下面粗略介绍一下这个改销量的方法,比如我截图的这个商品(我随便截的,不代表任何观点)销量已经有 2207 件了,商品卖的价格是 58.9 元,假设我们现在要修改这个商品的销量。首先我们去后台把这个商品的价格提高,为了避免平台检测到我们违规操作,每次在原来价格基础上乘以 2,直到我们把价格修改到了 2000 元,然后进入「多多进宝」平台推广该商品,优惠券就可以设置为 1000 元,注意这里优惠券千万别设置多了,不然被别人领取了可就真麻烦了。然后自己去把这个优惠券领了,再回去修改商品价格到 1.01 元,为了能成功的修改商品价格需要更换改商品的类目,因为不同商品的 SKU 允许的最低单价不同,价格修改好了之后就去买购买该商品,下单数量直接设置为 1000。那么咱们可以算一下实际的成本了,1.01 * 1000 = 1010 元,而我们有一个 1000 元的优惠券可以抵消,实际成本就只有 10 块钱,有没有惊讶到? + +接下来再慢慢的把商品价格改回去就可以了,而且这个过程基本不用担心会有别的顾客进来恶意购买,因为新店铺都没有权重,别的顾客是看不到的,而且如果新店铺都给权重的话,那拼多多多的直通车还怎么赚钱啊?需要注意的是每天修改销量的上限别超过 3000,不然店铺很容易被封! +具体的技术大致介绍完了,那么这样的客户从哪里去找?以前你在拼多多上面购买了商品就可以直接看到商家的电话,而且拼多多那时候支持秒退款,可以没有成本的获取销售线索,不过后面拼多多把这个通道给堵上了。那么拼多多里面有一个商家社区,商家社区又细分出来一个新手社区,这里面是可以看到商家店铺的名字的。所以你懂了吧?不要害怕,直接进入店铺进入客服,和商家聊天! +类似的空手套利的后门在淘宝也有,比如很多店铺因为一些原因被官方封禁了,但是淘宝留了一个后门可以解封店铺,这里我就不做详细的技术描述了,抛一个解封店铺方法线索:强制开通第二个淘宝店铺教程(可解封),随随便便收个一两千块钱不是问题! +最后说一下自己的感受吧,以上介绍几个案例其实都利用的是信息差,介绍的两个电商平台的后门是平台自己没有测试出来吗?这是平台故意留给外面的网店代运营公司的,因为平台需要这样的公司给他拉新。公司盈利模式无非就是两种模式,第一种不断的洗小白客户,第二种提升服务质量推出新产品服务好老客户。淘宝开始也是假货横行,但是现在一说到假货都不会想到淘宝了,拼多多走的一些路是淘宝走过的路,在每个阶段都多少会有它的套利机会。 + +我接触的这几个案例都是针对的低端流量,统计局都说了 6 亿人月收入不超过 1000 元,超过一半网民的月收入低于 3000,大部分人那种成天无所事事,却天天梦想发大财掉馅饼的心理正是很多公司利用的心理。自己少说漂亮话多做事! + +
+ + Read More ~ +
+
+
+ +
+

+ + 2020 年国庆观《我和我的家乡》有感所写 + +

+ +
+ + + + +
+ +
+ 上一次在我的微信公众号发文是 5 月 17 日,差不多近 5 个月没有在上面发一篇文章,看了下后台的关注人数居然没有多大的变化,安慰自己是我的读者大人都喜欢我,所以对我不离不弃没有取关,所以写了这篇文章给公众号的读者大人赔礼道歉露个面,表示我还在,想起啥就说点啥,毕竟下次这种双节重逢的日子得等到 2031 年了,过节就放空脑袋不去思考那些复杂的事情 +转眼自己已经毕业两年了,同级读研的同学也都陆陆续续毕业了,一直想着等冬天时候借着旅游的名义回哈尔滨看看老师。让人意外的是老师居然自己飞成都来了,陪着老师在电子科大校园外面散了两个多小时步,仿佛一下子回到了大学时代。 +老师是个极为幽默风趣的人,到电子科大是参加一个什么区块链会议,我询问他这个会议具体的内容是啥。老师有一点不耐烦的说:“就是一个卖铁锹的会议,挖币挖不动了开始教人挖币了”。老师问我现在在干嘛,我思考了几秒说了句:“我现在在卖铁锹”。哈哈哈哈哈哈 ...................... + +老师常说的一个小故事,一个地方金子多,大家都去挖金子,挖金子的人没有赚钱,结果卖铁锹的人赚翻了 + +10 月 1 日去电影院看了《我和我的家乡》,电影里面提到贵州山区因为道路不通畅,直线距离一公里的恋爱居然因为异地恋分手了,这样的地理环境我倒是没见过,不过让我想起了经典电视剧《血色浪漫》里面的片段,我把这段给拿过来了方便读者大人观看。 + +《血色浪漫》是一代人的记忆,主角钟跃民在当知青的时候迷上了「信天游」,陕北一些地区也是隔一条沟可以聊天,但是要见个面得好几十里,剧中这一段对歌真的太经典了,我禁不住又翻看了好几遍,又回想起了大学上《民族音乐》课程的时光了 .................. +随时保持一颗学习的心,脑子里多装一点知识总没坏处的,钟跃民学信天游的时候也没想到可以用来撩妹啊​!说不定你从这篇文章里面看到的几个小故事也可以撩妹呐,要是撩到妹了记得回来感谢我啊​ +电影里面还有两个小细节让我印象深刻,还是讲述贵州山区的那个小节,里面阿花出场时穿的裙子让我想起了花儿。花儿是我 6 年前在宽窄巷子的一家青旅认识的,那时她还是那家青旅的老板,我正在读大冰的《乖,摸摸头》和《阿弥陀佛么么哒》,花老板的生活让我找到了大冰的文字在现实中的影射,可以翻翻她公众号「微笑而确实的日常」里面的历史文章,都挺有意思的。 +花儿的手很巧,什么垃圾到她手里都会活起来,常常做一些小手工卖给旅客,也时常邀请旅客免费与他们一起共进晚餐,餐后背着吉他外面唱自己写的歌,有时候路人会给几十块钱。后面通过朋友圈了解到花老板去西藏生活了一段时间,而后又回到了大理。花老板回到大理后我又去过一次那间青旅,但是已经完全变了样子,旅客之间没了谈天说地,屋子也变得像酒店一样冰冷,和花老板联系确认她不会再回来,就再没联系过了。 +没有找到花老板比较好的照片,这张背影照可以勉强看到裙子的布料,听说云南很多人穿这样布料的裙子。 + +在这个快节奏人人都算计的社会,太多人少了像花老板一样的洒脱,太多人都忘记了这个世界上还有爱,也太多人连为爱放弃的勇气都没有。4 月离开 IT 行业之后我才真正感受到程序员群体的单纯,几个月的经历也更让我明白了家人与朋友的重要,翻一翻你的通讯录去联系一下那些许久没有联系的朋友吧。 +《我和我的家乡》还有一段教室漏雨的剧情,想起来自己读四五年级的时候不也是这样子嘛!学校拿几个木桩和几块木板子用钉子钉住,再加上石棉瓦和塑料薄膜就搭建了一间简易教室。四川的夜晚总是爱悄悄下大雨,我们早上到教室的第一件事不是早读,而是把教室的水先舀出去,这可是同学们比较喜欢的环节啊,不用被老师监督着早读可太轻松了。 +到夏天时候屋顶上爬满了飞蛾,全教室都是飞蛾身上掉下的粉末,这个场景对同学们来说也是不可多得的福利,不用上晚自习可以直接回宿舍睡觉。第二天早晨去教室把那两垃圾桶的飞蛾扫一扫就好了,不过比较难搞的是晚上满屋顶飞蛾遇到半夜暴雨,第二天可就真不好清理教室了! +本来想把自己以前写的文章都搬到知乎去,结果一不小心就触犯了知乎的社区规范了,不仅把我的几篇文章都给删掉了,还把我给禁言了一天。一气之下自己买了个服务器搭建了个博客,不管在什么平台写东西都得遵守别人的规矩,现在我的地盘我做主我自己定规矩! + +最后还是分享最近的一点点思考,执行力和坚持这两样东西很重要,我自己身上严重缺乏这两样东西,说起来很容易做起来却不是一般的难,不知道有多少人是真正明白了它们的重要性。 +任何人的批评都不会让自己开心,但如果一个人还愿意骂你还在鞭策你,那你真的得感谢人家,当对方什么都不愿意给你说的时候就是已经放弃你了,你就又失去了一个帮助自己成长的人! + +
+ + Read More ~ +
+
+
+ +
+

+ + 大学生书单推荐 + +

+ +
+ + + + +
+ +
+ 经常阅读的人和不常读书的人是不一样的,都说「书中自有黄金屋,书中自有颜如玉」,但是多数人都跑到抖音、快手、微博这些平台上面如饥似渴的找黄金,在这个嘈杂的社会中能静心读书变成了一件幸福的事情,也尝试了一下牛客网的「书单」功能,但是有的书我在里面搜索不到,所以直接在这里发了。 +在开头先说一下,有的书籍其实没必要去买实体书,尤其在一二线城市打拼的青年,如果在搬家时有太多的书籍简直就是一场噩梦。但是实体书也有另外一个好处,纸张的触感、可以拿笔在上面书写是电子书无法代替的,而且大部分人喜欢用手机去看电子书,很容易就被微信弹出来的消息给打扰了,在全民浮躁的年代能够聚精会神专注的做一件事简直是奢侈。 +书也不是你读的越多越好,而是要看你把什么读进去了,内化成为自己的知识才算读进去了;也不是每一本书都要读完,如果发现一本书不适合自己那么撇到一边完全没有问题,何必浪费时间去做那些形式上的表演呢?我个人比较喜欢实体书的原因是,我看实体书的时候能够连续不间断读两个小时,只有专注去读一本书的时候才能让自己有所收获。 +我推荐的书籍不一定适合你,但是你也要有意识的去读书,从自己喜欢的内容开始去阅读,什么言情啊、武侠啊、文学啊都可以,重要的是去阅读,逐渐的你就会爱上阅读,自己喜欢的内容会带领你逐步的去探索领域的边界,进而拓展到其它领域,靠自己的探索去获得新知识能够让你记忆更深刻,希望你也能变成一个读书人。 +首先推荐李笑来的《把时间当作朋友》和《财富自由之路》,这两本书写的真不错,我身边有好多人都是因为这两本书而改变了人生轨迹,虽然李笑来的人设倒下了,但是不影响这两本书的质量,也不要因为一次人设倒下就全盘否定一个人,其实李笑来那一波操作之后人们反倒更加崇拜他了。 +​ +季羡林先生的书值得一读,大概读几本季老先生的文集就可以了,从文字里面去感受季老求真务实的态度,什么才是做学术的态度,可能读几本季老的书会抑制不住多读几本,大师之所以为大师大师,不仅仅是因为天资聪慧,还有他的为人处世、求真务实。 +朱光潜先生《给青年的十二封信》一类的小册子非常值得读一读。比如我在读《谈美》的时候,本以为会是一次艰涩深奥的学习过程,但是没想到老先生用朴实的笔触,深入浅出地把我带进了一个栩栩如生的抽象世界,从青年人所熟知的文艺作品和生活事例出发,将美学的诸多原理娓娓道来。而且书虽取名为《谈美》,但又不仅是讲美学的书,入世出世的态度、青年人培养生活情趣和人生趣味的指南等等都有涉及。 +​ +《小狗钱钱》、《24 堂财富课——与女儿谈创业》、《穷爸爸富爸爸》、《好好赚钱》中可以选一两本阅读,大学只教了我们生存的技能,并没有教我们如何理财,而这又是每个人不得不面对的问题,这几本入门书籍,可以帮助您建立财富理念。再读一本《指数基金投资》,定投指数基金是最较适合小白的投资方式,尤其对于没有时间研究股票和基金的上班族来说,定投指数基金是一个很不错的方式。如果在学生时代有一点闲钱去尝试的话,就是一件更好的事情,因为学生时代几百几千看起来很多,但是当你参加工作了之后这点钱根本就不会放在眼里,试错成本低的阶段尽量多去尝试。 +​ +《浪潮之巅》、《数学之美》、《大学之路》等等,吴军博士的书籍都值得一看,《数学之美》是比较好的人工智能入门书籍,晦涩难懂的道理在吴军博士的文字中变的简单了,感受一下心态、格局等的提升会给人生带来多么重要的改变,我个人目前还没有读完。吴军老师还出了《见识》、《态度》、《格局》几本书,同时他在「得到」平台上的课程也是很值得学习的,不过建议得到和书籍选一个就行了,他新出的书籍很大一部分就是在得到上面稿子整理的。 +​ +像《长乐路》、《明朝那些事儿》、《以纸为桥》、《显微镜下的成都》、《邓小平时代》一类的书籍是可以读读的,应该去了解一下历史的细节,你所看到的不一定是真实的,光芒的背后是另一片黑暗,历史是有规律的,我们的生活可以在历史中找到影子。其中《邓小平时代》我有完整的版本,如果有需要可以微信联系我发你。 +​ +《人类简史》、《人性的弱点》、《心灵七游戏》等心理、哲学类书籍选几本读读,哲学是所有学科的抽象,到一个新的高度看看世界,提高自己的情商,让整个世界都为您张开拥抱的双臂。别好不容易谈个女朋友,因为情商不够又分了。 +​ +读不动名著小说,可以读《偷影子的人》,每个人都有相似的童年,只是大部分人都很羞涩,不好意思说出来而已,主人公就好像在经历自己曾经经历过的生活一样,从没有想过普普通通的童年居然能被描写的如此动情。《月亮与六便士》也是不错的选择。 +​ +都知道莫言先生获得了诺贝尔文学奖,我以前是很害怕读这种大家的书的,巧合看了电影《红高粱》产生了对原著的好奇,于是就去看了《红高粱家族》,结果一看就停不下来了。莫言把人性描述的淋漓尽致,每个人的性格、特点都异常鲜明,电影只是书籍的一部分,书籍比电影更精彩。高密东北乡在莫言的笔下算是绝了,莫言的语言驾驭能力,让各色人物活灵活现,就连那纯种的高粱也时刻在你的脑海里摇曳一番,姿态万千。 +​ +大部分学生都出生于农村家庭,农村的局限性不仅限制了我们的父辈,也限制了我们自己的,好不容易打破限制走到了大城市,一定不能被父母的传统思维影响到了,对父母不是要言听计从,能自己独立思考是成年人的标志,我们要学会对父母灌输给我们的思想取舍。《你当像鸟飞往你的山》英文名为《Educated》,是美国一位 30 多岁名叫塔拉的回忆录,一个从大山、摩门教家庭走出的女孩完成了最终的蜕变。每个人都有自己爹山,你当像鸟一样飞往你的山,过程中经历的转变、蜕变、虚伪、背叛,就是教育。这本书也是比尔盖茨年度荐书第一名、亚马逊年度编辑选书第一名...... +​ +大家应该都看过《美丽心灵》这部电影,电影中的主角纳什是博弈论的发明者,也是普林斯顿大学的教授,普林斯顿除了纳什外,还有很多我们所熟知的科学大牛。比如同为计算机科学之父和人工智能之父的艾伦·图灵,现代计算机之父冯·诺伊曼,出版了《了不起的盖茨比》的著名作家菲茨杰拉德......《自由的老虎》是一个进入普林斯顿的中国女孩用详实的档案与第一手采访资料,谱写的这些大牛挣脱束缚、追寻内心自由与价值的故事。 +​ +如果有可能的话,在大学最好认认真真谈一场恋爱,学习如何去爱一个人,在人生最好的年纪多一点洒脱,离开高中后大家就逐渐变得会算计了,心中有爱生活才会幸福。《霍乱时期的爱情》可以读一读,刚好和今年的新冠疫情有些契合。对了,像《非洲小扎》这样饶有趣味的异国生活小故事集读起来也很不错。 +​ +《荒诞医学史》和读库出版的《医学大神》全套十四册,非常值得一读,有的人大学白读了,这十四本小册子有助于培养自己的科学思维,多点医学知识总不是坏事,江湖骗子骗你的难度也会增加。科学的每一次进步都来之不易,现在人人都知道的勤洗手、用酒精消毒等常识,在前几个世纪居然是被科学界极力反对的做法,如果让我在本文里面只选择一套书籍,那么我会毫不犹豫的选择《医学大神》。在这里我建议你直接订阅读库的全年内容,这里有一篇为啥人人都爱《读库》的老六?的文章,你读完之后就会产生对读库的购买欲望了。 +​ +张立宪的真诚平实少有人及,《闪开,让我歌唱八十年代》中那些呈现细节的文字细腻的像个女子!可以读出来均是老六脑子里的一些记忆,记忆这东西多少都会出现点偏差,不必拿着学术精神去考究,那样就会失去读此书的欢乐了,对那个不属于我的时代是挺好奇的,里面还提到了很多不为人知的经典。 +前一段已经提到读库了,如果你不太喜欢自己去挑选书籍的话,那么我强烈推荐你订阅「读库2021」,因为读库没有电子版所以你只能买纸质版,很多有深度的内容你在别的地方是看不到的。而且有意思的是每次打开读库就像打开盲盒一样开心。 +比如东东枪采访郭德纲,梅兰芳先生特辑,生物基因究竟是怎么回事儿,这些东西,专门用几万字老老实实的笔锋讲清楚,完全没有那些华丽的东西,也没有那些拼流量的技巧,就是老老实实的文章说老老实实的事儿,在这个人人都抢流量的时代是难能可贵的。 +《The Almanack of Naval Ravikant:A Guide to Wealth and Happiness》、《原则》、《奈飞文化手册》几本书介绍了很多人生应该践行的准则。前面两本算是创始人的人生原则清单, 指出不少我们应该避免的事情,也列出来了很多我们应该坚持的东西,比如写作、用计算机节省人力等等。《奈飞文化手册》我觉得算是集体智慧,虽然内容是站在企业角度写的,但对个人也有很大的人生指导意义。 +​ +最后再推荐一本《你是你吃出来的》吧,我们中国人的饮食基本是不健康的。身体所必须的营养一定要注意均衡摄入,不能因为不想吃饭或是学校的饭难吃就不吃,吃东西不仅要关注能量,更要注重七大营养素(碳水化合物、脂类、蛋白质、维生素、矿物质、水、膳食纤维)。鸡蛋、牛奶、蔬菜、水果、坚果、肉类、(深海)鱼、动物肝脏、米饭/面食等,都需要均衡摄入。《你是你吃出来的》里面做了大量苦口婆心的讲解,也纠正了大家平时的一些错误观点,比如很多人生病了不舒服就喝喝粥打发,觉得粥里面很有营养,实际上粥是没有什么营养的;粉条、土豆、红薯这些都是算作主食这一类的,尤其很多人把粉条当做菜来吃是绝对错误的,某一餐有粉条话就应该主动减少米饭的摄入量;很多地方晚上都习惯吃面食,觉得面食容易消化,但面食里面的主要要成分是碳水化合物,碳水化合物摄入过多的结果只会让你越来越胖。 +​ +「一席」上的大部分演讲是值得听的,让您对中国社会有一个更好的了解;「TED」、「网易公开课」上国外高校的视频可以多看看,讲的真心不错;国内出观众、大部分二流学校的演讲能不去就不去,还不如把这种时间拿去睡睡觉,补充补充精力。 +多阅读高于自己的作品,远离精神毒品,互联网时代的阅读不再限于书籍了,良心大佬的博客也是一个不错的选择,只有阅读高于自己的作品才能获得成长,少刷抖音、微博、知乎一类的产品,里面是有一些有趣的人生,但是一刷就是几个小时,太浪费时间。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 那些鲜为人知的微信使用技巧,让微信更便捷高效 + +

+ +
+ + + + +
+ +
+ 本文整理的技巧基于两种目的,一种是实用方便用来解决生活中的小问题的,另外一种是用来装逼去撩小妹妹的,可能大多数人都更喜欢装逼撩妹的技巧吧。 +被人@时掉表情包 +在微信群内聊天时如果发送的消息触发了某个关键词就会掉下来表情包,为聊天增添了一些乐趣。我们可以通过人为的方式让别人一@你,就自动掉表情包。操作起来很简单,就是在群聊昵称末尾添加一些不同的字符,比如我在群聊昵称末尾加了「คิดถึง」顶部就掉下来了满屏的星星✨。 + +其实把这个泰文翻译一下就知道没有那么神秘了,微信内置的翻译功能将「คิดถึง」解释为「想念」,这下就明白为什么会掉星星✨了吧。「สุขสันต์วันเกิด」解释为「生日快乐」就会掉蛋糕,其它还有很多可以自己翻译一下就可以了。 +同一条朋友圈别人评论/点赞不再提示 +如果某个朋友发了一条朋友圈,你在下面评论或是点赞了,那么当你们的共同好友也对同一条朋友圈点赞或评论时,就会在「发现」有消息提示,但很多时候我们并不想要这样的提示,那怎么办呢?点到朋友圈的消息页中,iOS 向左滑动(安卓长按)就可以看到有个「不再通知」按钮,点击之后共同好友再点赞或评论就不会收到提示啦! + +用好微信内置的代办事项 +微信内置的代办事项功能估计没多少人知道,微信无疑是我们打开频率最高的 APP,把代办事项放在微信就更不容易忘事了。 + +可以看到它被置顶在微信的首页界面,每次启动微信都能看到,非常的方便,那么如何把代办事项置顶呢? +第一步:打开微信 &gt; 我 &gt; 收藏 &gt; 右上角➕号 &gt; 右下角图标 &gt; 代办 + +第二步:右上角三个点 &gt; 左下角在聊天中置顶 + +重要事件提醒 +我们经常会在微信上答应别人一些事情,为此可能去找一些清单软件来提醒自己,问题是清单软件又不常打开,常常会导致事情的忘记,给别人留下一个言而无信的形象。其实微信自身就已经带了提醒功能,长按任何一条消息,上面有一个提醒按钮,点击设置提醒时间,到了提醒时间后,会在「服务通知」中有推送的提醒。 +改善公众号阅读体验 +微信公众号阅读文章的体验不佳,不支持分组,也不支持进行上一次阅读,这时可以选择公众号任意一篇文章,点击右上角之后找到「在微信读书中打开」,如果没有这个功能,可能需要升级微信或者下载微信读书 App。 + +建立一个人的微信群 +怎么建立只有一个人的微信群呢?选择 -&gt; 添加朋友 -&gt; 面对面建群 -&gt; 输入数字 -&gt; 建群完成。这个只有自己的群就相当于一个信息分组,可以把自己平时的想法、待阅读的文章、写作灵感等发到里面,就自己一个人看,而且还可以对微信群进行置顶操作,不会受到其他微信群信息的影响,处理完的信息也可以像删聊天记录一样删掉,当然也可以选择发给文件助手或者自己。 +使用微信编辑图片 +我们有时候为了别人能更方便的理解自己想表达的意思,或者图片上的某些位置不想被别人看到,可以在添加图片的时候选择「预览」,就可以对图片进行编辑了。最实用的就是斗图的时候,对方一张图过来,如果手里没有合适的图片,可以直接基于对方的图片进行编辑,怼回去。 +利用微信收藏快速拼图 +选择「收藏」,在里面添加图片,然后选择右上角三个...的按钮,之后选择保存为图片,这样你添加的图片就拼接为了一张长图了,非常方便的操作,有的朋友喜欢分享聊天记录,就可以这么拼接。 + +善用「搜一搜」 +微信的「搜一搜功能很强大,比如有一天你无意在朋友圈看到一条信息,当时没有发现它的价值,过了一段时间因为其他事,突然恍惚记起了以前某条朋友圈提到过相关的情况,想查一查。这时就可以通过「搜一搜」来快速查找了。除此之外,「搜一搜」还能搜索文章,方便你快速查看与某个主题相关的文章;以及表情包搜索等功能,在斗图的时候不会再为没有表情包而烦恼。 + +僵尸粉检测 +相信很多人都遇到过别人给你发一条消息,上面注明是为了清僵尸粉的,但是这样会打扰到绝大部分微信好友,也给别人留下很不好的印象,我很反感收到别人的清粉消息。如果你怀疑某个人删除了你,你试着转账测试一下就可以了。 + +推荐几个小程序 +微信发票助手,经常出差的朋友或者行政小姐姐,常遇到的问题是在开公司发票的时候,需要填写一些信息,一般有记不住的,可以使用这个小程序填写好,之后每次需要的时候打开就好了,或者别人需要的时候,你分享二维码给他就可以了。发票信息填完后,会在「个人信息」底部多一个「我的发票抬头」,很方便的哦。 + +微信指数,有时候可能会做一些调查,比如写文章的朋友,文章标题到底是用“旅游”还是“旅行”好呢?这个时候就可以用微信指数来做调查,可以查看不同词汇之间的热度。 + +文章截图,IOS 用户比较苦恼的应该就是截长图了吧,有人就专门做了一个小程序,只需要把文章的链接复制过去,就可以自动生成截图,还可以生成 PDF 文件哦。 + +蚂蚁清单,很多人都有使用待办事项管理软件,但是有时候又忘记打开,这时不妨把它放到日常打开频率最高的微信里面去,而且还能为手机节省一点空间,蚂蚁清单的体验很好,可以自己去尝试。 + +一条人生经验 +最后说一条人生经验吧,敏感和违法不和谐的话题不要聊。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 互相看不起|断舍离|《良医》 + +

+ +
+ + + + +
+ +
+ 以前在朋友圈提到过这样一个现象,重庆人和四川人说的都是四川话,但是大部分重庆人会说他们说的是重庆话,说「川渝是一家」的通常也都是四川人。 +在深圳也有一个很怪的现象,两个客家人谈话会用客家话,两个潮汕人谈话会用潮汕话,两个广东人谈话会用粤语,反正就是尽可能用更小众的语言。 +想了一下,故意用第三方听不懂的语言,实际上是很欠考虑的,如果是刚见面用方言寒暄几句我觉得还行,但是后面谈话就应该使用大家都能听懂的语言了。 +疫情期间大家都没法出去玩,我和老叔倒是出去爬了爬山,村里的荔枝山别人进不去,整座山就我和叔两个人,单从疫情这个角度讲,荔枝山是比大部分地方都要安全的。 +我自己可以在疫情期间爬爬山,结合我自己的感受,加上前段时间的「大奔进故宫」事件。我发现人们并不是痛恨特权,而是痛恨自己没有特权。大部人痛恨的不是腐败,痛恨的是自己没有腐败的机会。 +上面四川和深圳两个例子也差不多是出于这样的优越感,鉴于四川除了成都外,其它地方投资的回报率太低,穷地方的人总会羡慕富有的地方,说川渝一家的人大概率不是成都人。 + +春节期间看了一本《断舍离》,它讲究的是内部的自觉自省,虽然整本书挺啰嗦的,完完全全可以用一篇几千字的文章代替,但是它传达的人生整理理念很值得参考,感兴趣的读者大人可以在微信读书中阅读此书。下面是一段摘自书中的内容。 + +我们习惯于思考「有效性」,却往往忽略了作为「有效性」前提的「必要性」,对物品也常常陷入这样的定式思维,导致家里各种杂物堆积,这些杂物给人的压迫感,以及狭窄空间给人的阻塞感,会让人的思维变得迟钝、行动变得迟缓! + +借助「断舍离」的理念,我删了 400 多个微信好友,处理了一些不会再使用的家具和书籍,才发现之前一直舍不得扔的那些东西扔了对我的积极作用更大,以前写过的一篇你如果只是一直囤干货,那永远也不可能进步,核心思想和断舍离基本一致,遗憾的是自己当时写下这篇文章后,竟然不懂得延伸到其它领域。 +可能一部分人有读书摘抄语录的习惯,我个人在阅读技术书籍或是扫除我知识盲点的时候,我也会通过记笔记来加深自己的理解。想想自己强迫症式的记笔记面面俱到其实也是在浪费时间,大部分笔记自己都不会再去看第二遍的,舍弃一些不必要的形式会让自己的阅读更有收获。 +还发现自己另外一个错误观点,我不管是写字还是看书都比大部分人慢,一直都认为是自己看书的方法不对,现在才发现问题的根本原因。是因为我对具体的领域不熟悉,所以看这个领域的书籍才会速度很慢,如果对这个领域熟悉了,那一目十行甚至几十行的速度应该都可以达到。结论就是书读的少了导致读书的速度慢。 + +推荐一部美剧——《良医》,全剧的场景基本是在医院,但有深度的内容基本都和医院无关,除了最基本的医疗科普外,更多的是对家庭、爱、职场、心理等的探讨,下面是我摘的两句台词。 + +Whenever people want you to do someting they think is wrong, they say it’s “reality”. +当人们想让你做他们认为错误的事时,他们总会说这就是现实。 + + +Very few things that are worthwhile in life come without a cost. +人生中那些有意义的事大多是有代价的。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + Vue 入门避坑——Vue + TypeScript 项目起手式 + +

+ +
+ + + + +
+ +
+ 在此前我使用的前端框架是 Angular,使用过 TypeScript 后你就会讨厌 JS 了,我学习 Vue 时的最新版本是 2.5,相信大部分同学都不会认为 Vue 那样又细又长的代码很美观吧,简单看了一些网络博客后,我毅然决然引入了 TypeScript 进行开发,本文仅整理记录我自己遇到的一些坑。 +使用 Cli +脚手架是一个比较方便的工具,这里需要注意的是@vue/cli和vue-cli是不一样的,推荐使用npm i -g @vue/cli安装。 +安装完成后,可以直接使用vue create your-app创建项目,你可以选择使用默认配置亦或是自己手动选择配置,按提示一步一步向下走即可,它会根据你的选择自己创建比如tsconfig.json等等配置文件。这里推荐使用less开发样式,sass老是在安装的过程中出问题。 +当然你也可以使vue ui命令启动一个本地服务,它是一个 Vue 项目管理器,提供了一个可视化的页面供你管理自己的项目,它的样子如下图所示,还是比较清新的。 + +使用 vue-property-decorator +Vue 官方维护了 vue-class-component 装饰器,vue-property-decorator 则是在vue-class-component基础上增强了更多结合Vue特性的装饰器,它可以让 Vue 组件语法在结合了 TypeScript 语法后变得更加扁平化。 +截止本文时间,vue-property-decorator共提供了 11 个装饰器和 1 个Mixins方法,下面用@Prop举个例子,是不是看起来引起极度舒适。 +import { Vue, Component, Prop } from 'vue-property-decorator' + +@Component +export default class YourComponent extends Vue { + @Prop(Number) readonly propA: number | undefined + @Prop({ default: 'default value' }) readonly propB!: string + @Prop([String, Boolean]) readonly propC: string | boolean | undefined +} + + +// 上面的内容将会被解析成如下格式 + +export default { + props: { + propA: { + type: Number + }, + propB: { + default: 'default value' + }, + propC: { + type: [String, Boolean] + } + } +} + +使用 Vuex +关于怎么使用Vuex此处就不再做过多说明了,需要注意的一点是,如果你需要访问$store属性的话,那么你必须得继承Vue类,坑的地方是在某些情况下即使你没有继承Vue,它也能通过编译,只有在程序运行起来的时候才报错。 +class ExampleApi extends Vue { + + public async getExampleData() { + if (!this.$store.state.exampleData) { + const res = await http.get('url/exampleData'); + if (res.result) { + this.$store.commit('setExampleData', res.data); + return res.data; + } else { + promptUtil.showMessage('get exampleData failed', 'warning'); + } + } else { + return this.$store.state.exampleData; + } + } +} + +使用自己的配置(含代理) +vue.config.js是一个可选的配置文件,如果项目的根目录中存在这个文件,那么它会被@vue/cli-service自动加载,它的配置项说明可以查看配置参考。 +我们再开发过程中都会使用代理来转发请求,代理的配置也是在这个文件中,它的官方说明在devserver-proxy中,下面是一个简单的vue.config.js文件例子。 +module.exports = { + filenameHashing: true, + outputDir: 'dist', + assetsDir: 'asserts', + indexPath: 'index.html', + productionSourceMap: false, + transpileDependencies: [ + 'vue-echarts', + 'resize-detector' + ], + devServer: { + hotOnly: true, + https: false, + proxy: { + &quot;/statistics&quot;: { + target: &quot;http://10.7.213.186:3889&quot;, + secure: false, + pathRewrite: { + &quot;^/statistics&quot;: &quot;&quot;, + }, + changeOrigin: true + }, + &quot;/mail&quot;: { + target: &quot;http://10.7.213.186:8888&quot;, + secure: false, + changeOrigin: true + } + } + } +} + +让 Vue 识别全局方法和变量 +我们在项目中都会使用一些第三方 UI 组件,比如我自己就使用了 Element,但是在使用它的$message、$notify等方法时就直接报错了,究其原因就是$message等属性并没有在 Vue 实例中声明。 +官方对此给出了很明确的解决方案,使用的是 TypeScript 的 模块补充特性,可以查看增强类型以配合插件使用。既然知道是因为没有声明导致的错误,那我们就给它声明一下好了,在src/shims-vue.d.ts文件中添加如下代码即可,如果没有该文件请自行创建。 + +看到网上也有一部分人说的是src/vue-shim.d.ts,反正不管是怎么命名这个文件的,它们的作用是一样的。 + +declare module 'vue/types/vue' { + interface Vue { + $message: any, + $confirm: any, + $prompt: any, + $notify: any + } +} + +这里顺道提一下,src/shims-vue.d.ts文件中的如下代码是为了让你的 IDE 明白以.vue结尾的文件是什么玩意儿。 +declare module '*.vue' { + import Vue from 'vue'; + export default Vue; +} + + +路由懒加载 +Vue Router 官方有关于路由懒加载的说明,但不知道为什么官方给的这个说明在我的项目里面都没有生效,但使用require.ensure()按需加载组件可以生效。 +// base-view 是模块名,写了相同的模块名则代码会被组织到同一个文件中 +const Home = (r: any) =&gt; require.ensure([], () =&gt; r(require('@/views/home.vue')), layzImportError, 'base-view'); + +// 路由加载错误时的提示函数 +function layzImportError() { + alert('路由懒加载错误'); +} + +上面的方式会在编译的时候把文件自动分成多个小文件,编译后的文件会以你自己命名的模块名来命名,如果代码之间有相互依赖,依赖部分代码编译后的文件会以两个模块名相连后进行命名。 +但是需要注意的是,这样拆分小文件之后引入了另外一个新的问题,因为客户端会缓存这些编译后的 js 文件,如果功能 A 同时依赖了a.js和b.js两个文件,但用户在使用其它功能时已经把a.js缓存到本地了,使用功能 A 时需要请求b.js文件,这时程序就很容易报错,因为此时在客户端这两个文件不是同一个版本,所以可能导致a.js调用b.js中的方法已经被删了,进而导致客户端页面异常。 +关于引入第三方包 +项目在引入第三方包的时候经常会报出各种奇奇怪怪的错误,这里仅提供我目前找到的一些解决办法。 +/* + 引入 jquery 等库可以尝试下面这种方式 + 只需要把相应的 js 文件放到指定文件夹即可 +**/ +const $ = require('@/common/js/jquery.min.js'); +const md5 = require('@/common/js/md5.js'); + +引入一些第三方样式文件、UI 组件等,如果引入不成功可以尝试建一个 js 文件,将导入语句都写在 js 文件中,然后再在main.ts文件中导入这个 js 文件,这个方法能解决大部分的问题。例如我先建了一个lib.js,然后在main.ts中引入lib.js就没有报错。 +// src/plugins/lib.js +import Vue from 'vue'; + +// 树形组件 +import 'vue-tree-halower/dist/halower-tree.min.css'; +import {VTree} from 'vue-tree-halower'; +// 饿了么组件 +import Element from 'element-ui'; +import 'element-ui/lib/theme-chalk/index.css'; +// font-awesome 图标 +import '../../node_modules/font-awesome/css/font-awesome.css'; +import VueCookies from 'vue-cookies'; +import VueJWT from 'vuejs-jwt'; + +Vue.use(VueJWT); +Vue.use(VueCookies); +Vue.use(VTree); +Vue.use(Element); + + +// src/main.ts +import App from '@/app.vue'; +import Vue from 'vue'; +import router from './router'; +import store from './store'; +import './registerServiceWorker'; +import './plugins/lib'; + +Vue.config.productionTip = false; + +new Vue({ + router, + store, + render: (h) =&gt; h(App), +}).$mount('#app'); + +因为第三方包写的各有特点,在引入不成功的时候基本也只能是见招拆招,当然如果你的功底比较深厚,你也可以自己写一个index.d.ts文件,实在不行的话,那个特殊的组件不使用 TypeScript 来写也能解决,我目前还没有找一个可以完全解决第三方包引入错误的方法,如果您已经有相关的方法了,希望能与你一起探讨交流。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 2019 年个人总结 + +

+ +
+ + + + +
+ +
+ 写总结的习惯是从 2015 年开始的,我的大学学费是县政协资助的,叔叔阿姨们唯一的要求就是每年给他们写个总结汇报一下学习情况,毕业后敦促我写总结的人则从外力转为内心。 +一点感动 +上半年我还很年轻,那时候还会经常使用 QQ、Soul、同桌、一罐 等等社交产品,无意结识了一个还在读高中的同性恋女孩子,我没学过心理学不知道用什么专业名词描述她的情况,反正就是心理上有严重的问题,玻璃心、想自杀等等。几次拼了我老命陪她聊到半夜两三点,现在完完全全能正视自己的情况了。 +让我感动的是有次她问我在干啥,我随便拍了一张自己的被汗水打湿衣服发给她,告诉她自己正在打羽毛球。小姑娘给我说我穿的衣服不好看,说我才多大穿的衣服太老了,我也就随口一说叫她给我这个老年人推荐推荐衣服,因为她要上课后面就一直没有回我消息。 +第二天早上睡醒了看到了小姑娘的几十条消息,是半夜两点多发的,给我挑了好几件衣服裤子,还给我画了几张示意图(如下),瞬间收获一份小感动。我也遵从小姑娘的意见买了两件上班穿穿,结果一到部门就是众目睽睽之下给我说穿的好酷,穿几次了都还是会引来大家不一样的目光,个性低调的我还是选择走大众程序员路线,老就老吧。 + + +前几天小姑娘给我发了她暗恋的小姐姐的照片,虽然极少时候还是会上课偷偷玩手机,但也在努力的备战高考。我做的不好的就是她多次给我讲自己在龙岗,我每次都把她当成龙华的,希望写了这篇总结之后不再记错吧。 +赚钱理财 +这个小标题起的有点大,仅说说我自己的实际情况吧。凭着运气,2019 年的银行理财收益在 4.5% 左右,基金收益在 7% 左右。我没有去玩股票,网上各种理财课程可能都会给你讲股票的收益多么多么高,但是他们从来不会给你说玩股票的风险有多高,更不可能给你讲玩股票会严重影响自己的心情,可能连自己的本职工作都会搞砸,所以我不建议职场新人进入股市。 +房东忙的时候我会帮他带房客看房,他也给了我小几千块钱的介绍费,加上每个月没交网费直接用他的,还时不时蹭蹭房东的饭局,也给自己省下来周末出去散步的费用了。上半年也给别人分享过两三个课程,在群里分享过一点小技能,大家给发了点红包,交个朋友、图个开心。 +总的来讲,理财这方面做得很差,没有花什么时间去学习,我们的大学也没有教给学生一点金融知识,这一年只读了几本写给小白的理财书,今年在这个领域要多花一点功夫,希望能入得了门吧。 +写书失败 +快要毕业的时候和电子工业出版社签了一份合同,合同内容就是我要写一本叫做《知识图谱:自顶向下方法》,这本书的计划内容是我的毕业设计,已经写了一百多页的内容了,但现在确定以失败告终。 +一者我手里现有的数据属于机密数据,没办法拿来直接使用;二来书中有很大一部分内容涉及到网络爬虫,上半年网上曝了很多因为抓数据而入狱的案例,出版社和我都害怕;三者知识图谱所需要的数据量很大,而且我写的那个领域又是中国特有的经济责任审计领域,大量数据都得从政府网站来,更害怕了;最重要的原因是自己懒,写书的那几个月确实非常的累,想想自己都还是个菜鸟呐,有啥资本教给别人知识,心里给了自己后退的理由。 +小时候曾夸下海口说要给父亲写个传记,也不知道有没有那么一丢丢可能性实现,写家里的狗时,发现写这样的内容会增加我的多巴胺分泌,以后不开心了就写这样的小故事。 +运动健身 +在深圳校友会骑行社师兄师姐们的带领下,同时也得益于一起入职的小伙伴送了我一辆 MERIDA,我喜欢上了骑行这项运动,基本上每周五都会出去骑几十公里,中间还参加了环漓江骑行和 TREK100 骑行,锻炼的同时也见到了美丽的风景。深圳对自行车是不太友好的,基本没有什么自行车道,所以我们大部分时间都是等到晚上车少,交警下班了之后才开始骑行。 +除了骑行每周一也会打两小时羽毛球,谈不上专业,但至少打的不再是广场球了。偶尔也会出去爬爬山啥的,身体确实比上学时候要好很多,而且多锻炼能让自己的精神面貌更好,精气神好也能稍稍掩盖长得丑的缺点。以前每年再怎么也会因为感冒一类的问题进几次医院,19 年仅一次因为智齿发炎去过医院。 +削减迷茫 +大概在四五月份的时候吧,几乎天天失眠,经常夜里只睡了三四个小时,有时甚至通宵未眠,心里很清楚是因为迷茫了,大概就是「晚上想了千条路,早上醒来走原路」的状态。好在自己的调节能力还不算差,同时也有楼下的叔叔、自己的好朋友能唠唠嗑,差不多两个月就回归正常状态了。 +从几个比我晚入职半年的小伙伴那里了解到,他们现在的情况和我四五月份的情况差不多,我想绝大部分普通人都会经历这个迷茫期吧,大部分人也都能通过时间调节过来,调节不过来的那部分人就成为了媒体比较喜欢的人。 +现在迷茫的雾气已经没有那么浓了,初入社会肯定有很多的不成熟,但谁不是这样过来的呢?更何况我并不像多数程序员那样交友严重同质化,周末也不会死宅在家里不出去,猜测我应该比大多数人更潇洒自在的,嘿嘿。 +新的思想 +大家基本都是看着金庸武侠小说(相关影视作品)长大的,没有人写武侠小说能超过金庸。偶然一天在推特上刷到一条评论,大意是:没有人写武侠小说能超过金庸不正代表着社会的进步吗?金庸的成就如此巨大,一个很重要的历史背景是那时候大家没有那么多小说可看呀,哪里像今天遍地的网络小说。咱们没必要去争论这个观点的对错,重要的是它告诉了我们一个不一样的角度去看待问题。 +上面只是一个特例,思维方式是一点一点改变的,认知水平是一点一点提升的,一年时间修正了不少我此前狭隘的观点,这样的修正还在继续,我也会让这样的修正持续下去。 +写在最后 +巴黎圣母院被烧、凉山火灾、女排十连冠、NBA 事件、无锡高架桥倒塌......等等发生在 2019 年的大事,不知道还有多少朋友会记起来。时间从来不会等谁,网友也都是不长记性的,成熟的一部分无非是经历的多了,失望的多了,然后变得更耐操一点,总之生活依旧得继续,人总会亦悲亦喜,那为啥不把悲缩小喜放大呢? +成功没有银弹、没有捷径,少讲大道理,多解决小问题。 + +
+ + Read More ~ +
+
+
+ +
+

+ + MongoDB 聚合(aggregate)入门 + +

+ +
+ + + + +
+ +
+ MongoDB 聚合官方文档 +聚合管道是一个基于数据处理管道概念建模的数据聚合框架,文档进入一个多阶段的处理管道,该管道最终将其转换为聚合后的结果。 +下面的例子来源于官方文档。第一阶段,$match按status字段来过滤文档,并把status字段值为A的文档传递到下一阶段;第二阶段,$group将文档按cust_id进行分组,并针对每一组数据对amount进行求和。 +db.orders.aggregate([ + { $match: { status: &quot;A&quot; } }, + { $group: { _id: &quot;$cust_id&quot;, total: { $sum: &quot;$amount&quot; } } } +]) + +管道 + +聚合管道包含很多步骤,每一步都会将输入的文档进行转换,但并不是每个阶段都一定需要对每个输入文档生成一个输出文档,比如某些阶段可能生成新的文档或者过滤掉文档。 +除了$out、$merge、$geoNear外,其它的阶段都可以在管道中多次出现,更加详细的内容可以查看 Aggregation Pipeline Stages。 + +管道表达式 +一些管道阶段采用表达式作为操作元,管道表达式指定了要应用到输入文档的转换,表达式自己是一个文档结构(JSON),表达式也可以包含其它的表达式。 +表达式仅提供文档在内存中的转换,即管道表达式只能对管道中的当前文档进行操作,不能引用来自其他文档的数据。 +写聚合表达式式建议直接参考官方文档,下面列出一些我收集的案例,供深入理解使用。 +案例一:将对象数组转换为单个文档 +// 转换前 +{ + &quot;_id&quot;: &quot;10217941&quot;, + &quot;data&quot;: [ + { + &quot;count&quot;: 2, + &quot;score&quot;: &quot;0.5&quot; + }, + { + &quot;count&quot;: 6, + &quot;score&quot;: &quot;0.3&quot; + }, + { + &quot;count&quot;: 5, + &quot;score&quot;: &quot;0.8&quot; + } + ] +} + +// 转换后 +{ + &quot;_id&quot;: &quot;10217941&quot;, + &quot;0.3&quot;: 6, + &quot;0.5&quot;: 2, + &quot;0.8&quot;: 5 +} + +需要说明的是,如果上面data属性中的数据格式为{&quot;k&quot;: &quot;0.6&quot;, &quot;v&quot;: 5},那么下面的聚合表达式就不需要$map,这一点可以查看 $arrayToObject。这个案例的难点在于score中有小数点,这个小数点会让聚合表达式懵逼的。 +db.collection.aggregate([ + { + &quot;$addFields&quot;: { + &quot;data&quot;: { + &quot;$arrayToObject&quot;: { + &quot;$map&quot;: { + &quot;input&quot;: &quot;$data&quot;, + &quot;as&quot;: &quot;item&quot;, + &quot;in&quot;: { + &quot;k&quot;: &quot;$$item.score&quot;, + &quot;v&quot;: &quot;$$item.count&quot; + } + } + } + } + } + }, + { + &quot;$addFields&quot;: { + &quot;data._id&quot;: &quot;$_id&quot; + } + }, + { + &quot;$replaceRoot&quot;: { + &quot;newRoot&quot;: &quot;$data&quot; + } + } +]); + + +
+ + Read More ~ +
+
+
+ + + +
+ + 上一页 + + + 下一页 + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/page/6/index.html b/page/6/index.html new file mode 100644 index 00000000..b2210c81 --- /dev/null +++ b/page/6/index.html @@ -0,0 +1,1941 @@ + + + + + + + + Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ + +
+

+ + 正则表达式是如何运行的?——浅析正则表达式原理 + +

+ +
+ + + + +
+ +
+ +参考内容: +《编译原理》 +实现简单的正则表达式引擎 +正则表达式回溯原理 +浅谈正则表达式原理 + +最近在一个业务问题中遇到了一个正则表达式性能问题,于是查了点资料去回顾了下正则表达式的原理,简单整理了一下就发到这里吧;另外也是想试试 Apple Pencil 的手感如何,画的太丑不要嫌弃哈。 +有穷自动机 +正则表达式的规则不是很多,这些规则也很容易就能理解,但是正则表达式并不能用来直接识别字符串,我们还需要引入一种适合转换为计算机程序的模型,我们引入的就是有穷自动机。 +在编译原理中通过构造有穷自动机把正则表达式编译成识别器,识别器以字符串x作为输入,当x是语言的句子时回答是,否则回答不是,这正是我们使用正则表达式时需要达到的效果。 +有穷自动机分为确定性有穷自动机(DFA)和非确定性有穷自动机(NFA),它们都能且仅能识别正则表达式所表示的语言。它们有着各自的优缺点,DFA 导出的识别器时间复杂度是多项式的,它比 NFA 导出的识别器要快的多,但是 DFA 导出的识别器要比与之对应的 NFA 导出的识别器大的多。 +大部分正则表达式引擎都是使用 NFA 实现的,也有少部分使用 DFA 实现。从我们写正则表达式的角度来讲,DFA 实现的引擎要比 NFA 实现的引擎快的多,但是 DFA 支持的功能没有 NFA 那么强大,比如没有捕获组一类的特性等等。 +我们可以用带标记的有向图来表示有穷自动机,称之为转换图,其节点是状态,有标记的边表示转换函数。同一个字符可以标记始于同一个状态的两个或多个转换,边可以由输入字符符号标记,其中 NFA 的边还可以用ε标记。 +之所以一个叫有确定和非确定之分,是因为对于同一个状态与同一个输入符号,NFA 可以到达不同的状态。下面看两张图就能明白上面那一长串的文字了。 +图中两个圈圈的状态表示接受状态,也就是说到达这个状态就表示匹配成功。细心的你应该发现了两张图所表示的正则表达式是一样的,这就是有穷自动机神奇的地方,每一个 NFA 我们都能通过算法将其转换为 DFA,所以我们先根据正则表达式构建 NFA,然后再转换成相应的 DFA,最后再进行识别。 + +上图的画法在正则表达式很简单的时候还可以,如果遇到很复杂的正则表达式画起来还是挺费力的,如果想对自动机有更加深入的认识可以自行查阅相关资料。下面的图片是使用正则可视化工具生成的,对应的正则表达式是^-?\d+(,\d{3})*(\.\d{1,2})?$,它所匹配的字符串是数字/货币金额(支持负数、千分位分隔符)。 + +回溯 +NFA 引擎在遇到多个合法的状态时,它会选择其中一个并记住它,当匹配失败时引擎就会回溯到之前记录的位置继续尝试匹配。这种回溯机制正是造成正则表达式性能问题的主要原因。下面我们通过具体的例子来看看什么是回溯。 +/ab{1,3}c/ + + + + +正则 +文本 + + + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + +ab{1,3}c +abbbc + + + +上表中展示的是使用ab{1,3}c匹配abbbc的过程,如果把匹配字符串换成abbc,在第五步就会出现匹配失败的情况,第六步会回到上一次匹配正确的位置,进而继续匹配。这里的第六步就是「回溯」 + + + +正则 +文本 +备注 + + + + +ab{1,3}c +abbc + + + +ab{1,3}c +abbc + + + +ab{1,3}c +abbc + + + +ab{1,3}c +abbc + + + +ab{1,3}c +abbc +匹配失败 + + +ab{1,3}c +abbc +回溯 + + +ab{1,3}c +abbc + + + + +会出现上面这种情况的原因在于正则匹配采用了回溯法。回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。它通常采用最简单的递归来实现,在反复重复上述的步骤后可能找到一个正确的答案,也可能尝试所有的步骤后发现该问题没有答案,回溯法在最坏的情况下会导致一次复杂度为指数时间的计算。 +上面一段的内容来源于维基百科,精简一下就是深度优先搜索算法。贪婪量词、惰性量词、分支结构等等都是可能产生回溯的地方,在写正则表达式时要注意会引起回溯的地方,避免导致性能问题。 +John Graham-Cumming 在他的博文 Details of the Cloudflare outage on July 2, 2019 中详细记录了因为一个正则表达式而导致线上事故的例子。该事故就是因为一个有性能问题的正则表达式,引起了灾难性的回溯,进而导致了 CPU 满载。 +(?:(?:\&quot;|'|\]|\}|\\|\d|(?:nan|infinity|true|false|null|undefined|symbol|math)|\`|\-|\+)+[)]*;?((?:\s|-|~|!|{}|\|\||\+)*.*(?:.*=.*))) + +上面是引起事故的正则表达式,出问题的关键部分在.*(?:.*=.*)中,就是它引起的灾难性回溯导致 CPU 满载。那么我们应该怎么减少或避免回溯呢?无非是提高警惕性,好好写正则表达式;或者使用 DFA 引擎的正则表达式。 +[0-9] 与 \d 的区别 +此问题来源于Stackoverflow,题主遇到的问题是\d比[0-9]的效率要低很多,并且给出了如下的测试结果,可以看到\d比[0-9]慢了差不多一倍。 +Regular expression \d took 00:00:00.2141226 result: 5077/10000 +Regular expression [0-9] took 00:00:00.1357972 result: 5077/10000 63.42 % of first +Regular expression [0123456789] took 00:00:00.1388997 result: 5077/10000 64.87 % of first + +出现这个性能问题的原因在于\d匹配的不仅仅是0123456789,\d匹配的是所有的 Unicode 的数字,你可以从 Unicode Characters in the 'Number, Decimal Digit' Category 中看到所有在 Unicode 中属于数字的字符。 +此处多提一嘴,[ -~]可以匹配 ASCII 码中所有的可打印字符,你可以查看 ASCII 码中的可显示字符,就是从&quot; &quot;(32)至&quot;~&quot;(126)的字符。 +工具/资源推荐 +正则表达式确实很强大,但是它那晦涩的语法也容易让人头疼抓狂,不论是自己还是别人写的正则表达式都挺头大,好的是已经有人整理了常用正则大全,也大神写了个叫做 VerbalExpressions 的小工具,主流开发语言的版本它都提供了,可以让你用类似于自然语言的方式来写正则表达式,下面是它给出的一个 JS 版示例。 +// Create an example of how to test for correctly formed URLs +const tester = VerEx() + .startOfLine() + .then('http') + .maybe('s') + .then('://') + .maybe('www.') + .anythingBut(' ') + .endOfLine(); + +// Create an example URL +const testMe = 'https://www.google.com'; + +// Use RegExp object's native test() function +if (tester.test(testMe)) { + alert('We have a correct URL'); // This output will fire +} else { + alert('The URL is incorrect'); +} + +console.log(tester); // Outputs the actual expression used: /^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$/ + +文章大部分内容都是介绍的偏原理方面的知识,如果仅仅是想要学习如何使用正则表达式,可以看正则表达式语法或者 Learn-regex,更为详细的内容推荐看由老姚写的JavaScript 正则表达式迷你书 + +
+ + Read More ~ +
+
+
+ +
+

+ + Vim 常用命令快捷查询 + +

+ +
+ + + + +
+ +
+ +参考内容 +Learning Vim The Pragmatic Way +《鸟哥的 Linux 私房菜》 + +Vim 可以认为是 Vi 的高级版本,Vim 可以用颜色或下划线的方式来显示一些特殊信息,您可以认为 Vi 是一个文本处理工具,而 Vim 是一个程序开发工具,现在大部分 Linux 的发行版都以 Vim 替换 Vi 了。在 Linux 命令行模式下有很多编辑器,但是 Vi 文本编辑器是所有 Unix-like 系统都会内置的,因此学会 Vi/Vim 的使用时非常有必要的,对于 Vi 的三种模式(命令模式、编辑模式、命令行模式)这里就不在做说明了,下面是一些比较常用的命令。 +一般命令模式下 + + + +命令 +说明 + + + + +h、j、k、l +与键盘的方向键一一对应,分别为左、下、上、右,在键盘上着几个字母是排在一起的 + + +Ctrl+f、Ctrl+b +分别对应键盘的「Page Down」、「Page Up」,我更习惯于这两个键,而不是前面的组合键 + + +0、$ +分别对应键盘的「Home」、「End」,即移动到该行的最前面/后面字符处 + + +n&lt;Enter&gt; +n 为数字,光标向下移动 n 行 + + +/word、?word +向光标之上/下寻找一个字符串名称为 word 的字符串 + + +n、N +如果我们刚刚执行了上面上面的 /word 或 ?word 查找操作,那么 n 则表示重复前一个查找操作,可以简单理解为向下继续查找下一个名称为 word 的字符串,N 则与 n 刚好相反 + + +:n1,n2s/word1/word2/g +在第 n1 行与 n2 行之间寻找 word1 这个字符串,并将这个字符串替换为 word2,如果前面的 n1,n2 使用 1,$ 代替则表示从第一行到最后一行,最后的 g 后面可以加个 c,即 :1,$s/word1/word2/gc,这样就会在替换钱显示提示字符给用户确认(confirm) + + +x、X +分别对应键盘的「Del」、「Backspace」键 + + +dd、yy +删除/复制光标所在的那一整行 + + +p、P +p 将已复制的数据在光标下一行粘贴,P 粘贴在光标上一行 + + +u +恢复前一个操作,类似于 Windows 下的 Ctrl+Z + + +Ctrl+r +重做上一个操作 + + +. +小数点,重复上一个操作 + + + +命令行模式下 + + + +命令 +说明 + + + + +:w +将编辑的数据写入硬盘中 + + +:w! +若文件属性为只读,强制写入该文件,不过到底能不能写入,还是跟文件权限有关系 + + +:q、:q! +与 w 一样,q 为关闭的意思 + + +:r [filename] +在编辑的数据中读入另一个文件的数据,即将[filename]这个文件的内容追加到光标所在行的后面 + + +:w [filename] +将编辑的数据保存为另一个文件 + + +:set nu/nonu +显示/不显示行号 + + + +编辑模式下 + + + +组合键 +作用 + + + + +[ctrl]+x -&gt; [ctrl]+n +通过目前正在编辑的这个文件的内容文字作为关键字,予以自动补全 + + +[ctrl]+x -&gt; [ctrl]+f +以当前目录内的文件名作为关键字补全 + + +[ctrl]+x -&gt; [ctrl]+o +以扩展名作为语法补充,以 Vim 内置的关键字予以补全 + + + +当我们在使用 Vim 编辑器的时候,Vim 会在与被编辑的文件目录下再建立一个名为.filename.swp的文件,我们对文件的操作都会记录到这个 swp 文件中去,如果系统因为某些原因掉线了,就可以利用这个 swp 文件来恢复内容。如果存在对应的 swp 文件,那么 Vim 就会主动判断当前这个文件可能有问题,会给出相应的提示。 +我们也可以给 Vim 环境设置一些个性化的参数,虽然在命令行模式下可以使用:set来设置,但是这样每次设置实在是太麻烦,因此我们可以设置一些全局的参数。Vim 的整体设置值一般放在/etc/vimrc中,我们一般通过修改~/.vimrc这个文件(默认不存在)来设置一些自己的参数,比如: +&quot; 该文件的双引号是注释 +set nu &quot;在每一行的最前面显示行号 +set autoindent &quot; 自动缩进 +set ruler &quot; 可显示最后一行的状态 +set bg=dark &quot; 显示不同的底色色调 +syntax on &quot;进行语法检验,颜色显示,比如 C 语言等 + +最后附上一张命令速查卡,此图来源于Learning Vim The Pragmatic Way,PDF 版下载链接在这里。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何加快 Nginx 的文件传输?——Linux 中的零拷贝技术 + +

+ +
+ + + + +
+ +
+ +参考内容: +Two new system calls: splice() and sync_file_range() +Linux 中的零拷贝技术1 +Linux 中的零拷贝技术2 +Zero Copy I: User-Mode Perspective +Linux man-pages splice() +Nginx AIO 机制与 sendfile 机制 +sendfile 适用场景 +扯淡 Nginx 的 sendfile 零拷贝的概念 +浅析 Linux 中的零拷贝技术 +Linux man-pages sendfile + +今天在看 Nginx 配置的时候,看到了一个sendfile配置项,它可以配置在http、server、location三个块中,出于好奇就去查了一下sendfile的作用。 +文件下载是服务器的基本功能,其基本流程就是循环的从磁盘读取文件内容到缓冲区,再将缓冲区内容发送到socket文件,程序员基本都会写出类似下面看起来比较高效的程序。 +while((n = read(diskfd, buf, BUF_SIZE)) &gt; 0) + write(sockfd, buf , n); + +上面程序中我们使用了read和write两个系统调用,看起来也已经没有什么优化空间了。这里的read和write屏蔽了系统内部的操作,我们并不知道操作系统做了什么,现实情况却是由于 Linux 的 I/O 操作默认是缓冲 I/O,上面的程序发生了多次不必要的数据拷贝与上下文切换。 +上述两行代码执行流程大致可以描述如下: + +系统调用read产生一个上下文切换,从用户态切换到内核态; +DMA 执行拷贝(现在都是 DMA 了吧!),把文件数据拷贝到内核缓冲区; +文件数据从内核缓冲区拷贝到用户缓冲区; +read调用返回,从内核态切换为用户态; +系统调用write产生一个上下文切换,从用户态切换到内核态; +把步骤 3 读到的数据从用户缓冲区拷贝到 Socket 缓冲区; +系统调用write返回,从内核态切换到用户态; +DMA 从 Socket 缓冲区把数据拷贝到协议栈。 + + +可以看到两行程序共发生了 4 次拷贝和 4 次上下文切换,其中 DMA 进行的数据拷贝不需要 CPU 访问数据,所以整个过程需要 CPU 访问两次数据。很明显中间有些拷贝和上下文切换是不需要的,sendfile就是来解决这个问题的,它是从 2.1 版本内核开始引入的,这里放个 2.6 版本的源码。 +系统调用sendfile是将in_fd的内容发送到out_fd,描述符out_fd在 Linux 2.6.33 之前,必须指向套接字文件,自 2.6.33 开始,out_fd可以是任何文件;in_fd只能是支持mmap的文件(mmap是一种内存映射方法,在被调用进程的虚拟地址空间中创建一个新的指定文件的映射)。 +所以当 Nginx 是一个静态服务器时,开启sendfile配置项是可以大大提高 Nginx 性能的,但是当把 Nginx 作为一个反向代理服务器时,sendfile则没有什么用,因为当 Nginx 时反向代理服务器时,in_fd就是一个套接字,这不符合sendfile的参数要求。 + +可以看到现在我们只需要一次拷贝就可以完成功能了,但是能否把这一次拷贝也省略掉呢?我们可以借助硬件来实现,仅仅需要把缓冲区描述符和文件长度传过去,这样 DMA 直接将缓冲区的数据打包发送到网络中就可以了。 +这样就实现了零拷贝技术,需要注意的是这里所说的零拷贝是相对操作系统而言的,即在内核空间不存在冗余数据。数据的实际走向是从硬盘到内存,再从内存到设备。 +Nginx 中还有一个aio配置,它的作用是启用内核级别的异步 I/O 功能,要使aio生效需要将directio开启(directio对大文件的读取速度有优化作用),aio很适合大文件的传送。需要注意的是sendfile和aio是互斥的,不可同时兼得二者,因此我们可以设置一个文件大小限制,超过该阀值使用aio,低于该阀值使用sendfile。 +location /video/ { + sendfile on; + sendfile_max_chunk 256k; + aio threads; + directio 512k; + output_buffers 1 128k; +} + +上面已经提到了零拷贝技术,它可以有效的改善数据传输的性能,但是由于存储体系结构非常复杂,而且网络协议栈有时需要对数据进行必要的处理,所以零拷贝技术有可能会产生很多负面影响,甚至会导致零拷贝技术自身的优点完全丧失。 +零拷贝就是一种避免 CPU 将一块存储拷贝到另一块存储的技术。它可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效的提高数据传输效率,而且零拷贝技术也减少了内核态与用户态之间切换所带来的开销。进行大量的数据拷贝操作是一件简单的任务,从操作系统的角度来看,如果 CPU 一直被占用着去执行这项简单的任务,是极其浪费资源的。如果是高速网络环境下,很可能就出现这样的场景。 +零拷贝技术分类 +现在的零拷贝技术种类很多,也并没有一个适合于所有场景的零拷贝零拷贝技术,概括起来总共有下面几种: + + +直接 I/O:对于这种数据传输方式来说,应用程序可以直接访问硬件存储,操作系统只是辅助数据传输,这类零拷贝技术可以让数据在应用程序空间和磁盘之间直接传输,不需要操作系统提供的页缓存支持。关于直接 I/O 可以参看Linux 中直接 I/O 机制的介绍。 + + +避免数据在内核态与用户态之间传输:在一些场景中,应用程序在数据进行传输的过程中不需要对数据进行访问,那么将数据从页缓存拷贝到用户进程的缓冲区是完全没有必要的,Linux 中提供的类似系统调用主要有mmap()、sendfile()和splice()。 + + +对数据在页缓存和用户进程之间的传输进行优化:这类零拷贝技术侧重于灵活地处理数据在用户进程的缓冲区和操作系统页缓存之间的拷贝操作,此类方法延续了传统的通信方式,但是更加灵活。在 Linux 中主要利用了「写时复制」技术。 + + +前两类方法的目的主要是为了避免在用户态和内核态的缓冲区间拷贝数据,第三类方法则是对数据传输本身进行优化。我们知道硬件和软件之间可以通过 DMA 来解放 CPU,但是在用户空间和内核空间并没有这种工具,所以此类方法主要是改善数据在用户地址空间和操作系统内核地址空间之间传递的效率。 +避免在内核与用户空间拷贝 +Linux 主要提供了mmap()、sendfile()、splice()三个系统调用来避免数据在内核空间与用户空间进行不必要的拷贝,在Nginx 文件操作优化对sendfile()已经做了比较详细的介绍了,这里就不再赘述了,下面主要介绍mmap()和splice()。 +mmap() +当调用mmap()之后,数据会先通过 DMA 拷贝到操作系统的缓冲区,然后应用程序和操作系统共享这个缓冲区,这样用户空间与内核空间就不需要任何数据拷贝了,当大量数据需要传输的时候,这样做就会有一个比较好的效率。 +但是这种改进是需要代价的,当对文件进行了内存映射,然后调用write()系统调用,如果此时其它进程截断了这个文件,那么write()系统调用将会被总线错误信号SIGBUG中断,因为此时正在存储的是一个错误的存储访问,这个信号将会导致进程被杀死。 +一般可以通过文件租借锁来解决这个问题,我们可以通过内核给文件加读或者写的租借锁,当另外一个进程尝试对用户正在进行传输的文件进行截断时,内核会给用户发一个实时RT_SIGNAL_LEASE信号,这个信号会告诉用户内核破坏了用户加在那个文件上的写或者读租借锁,write()系统调用就会被中断,并且进程会被SIGBUS信号杀死。需要注意的是文件租借锁需要在对文件进行内存映射之前设置。 +splice() +和sendfile()类似,splice()也需要两个已经打开的文件描述符,并且其中的一个描述符必须是表示管道设备的描述符,它可以在操作系统地址空间中整块地移动数据,从而减少大多数数据拷贝操作。适用于可以确定数据传输路径的用户应用程序,不需要利用用户地址空间的缓冲区进行显示的数据传输操作。 +splice()不局限于sendfile()的功能,也就是说sendfile()是splice()的一个子集,在 Linux 2.6.23 中,sendfile()这种机制的实现已经没有了,但是这个 API 以及相应的功能还存在,只不过内部已经使用了splice()这种机制来实现了。 +写时复制 +在某些情况下,Linux 操作系统内核中的页缓存可能会被多个应用程序所共享,操作系统有可能会将用户应用程序地址空间缓冲区中的页面映射到操作系统内核地址空间中去。如果某个应用程序想要对这共享的数据调用write()系统调用,那么它就可能破坏内核缓冲区中的共享数据,传统的write()系统调用并没有提供任何显示的加锁操作,Linux 中引入了写时复制这样一种技术用来保护数据。 +写时复制的基本思想是如果有多个应用程序需要同时访问同一块数据,那么可以为这些应用程序分配指向这块数据的指针,在每一个应用程序看来,它们都拥有这块数据的一份数据拷贝,当其中一个应用程序需要对自己的这份数据拷贝进行修改的时候,就需要将数据真正地拷贝到该应用程序的地址空间中去,也就是说,该应用程序拥有了一份真正的私有数据拷贝,这样做是为了避免该应用程序对这块数据做的更改被其他应用程序看到。这个过程对于应用程序来说是透明的,如果应用程序永远不会对所访问的这块数据进行任何更改,那么就永远不需要将数据拷贝到应用程序自己的地址空间中去。这也是写时复制的最主要的优点。 +写时复制的实现需要 MMU 的支持,MMU 需要知晓进程地址空间中哪些特殊的页面是只读的,当需要往这些页面中写数据的时候,MMU 就会发出一个异常给操作系统内核,操作系统内核就会分配新的物理存储空间,即将被写入数据的页面需要与新的物理存储位置相对应。它最大好处就是可以节约内存,不过对于操作系统内核来说,写时复制增加了其处理过程的复杂性。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 家里的狗 + +

+ +
+ + + + +
+ +
+ 为了防止晚上有人来家里偷东西,几乎家家户户都至少会养一只狗。在我的记忆中,我家一开始是没有狗的。 +忘记是哪一年夏天的一个清晨,天还没有大亮,我隐约看见在牛棚后面的空地有个黑影,走近一点仔细一看,原来是一只不知道从哪里来的一只黑狗。 +它惊恐的看着我,眼神中夹杂着恐惧与无助,佝偻的身子比弓还要弯,倒是很像一个活着的牛轭。他的身子还没有草高,露水把全身的毛都打湿了,还沾着一些不知名的植物种子。我和它对视着,恐惧慢慢填满了它的眼球,我害怕吓到它,赶紧走开去告诉妈。 +妈远远看了一眼,让我别管它。随后妈把装着昨晚剩饭的猪食瓢放到牛棚后面的一块石头上,黑狗看见妈带着武器走近早就跑了,我吃早饭时还不时去望望它在不在,有没有吃妈给放在那里的饭。 +妈已经把猪喂完准备下地干活了,仍旧没有再次发现黑狗的踪影,也没见猪食瓢有什么变化,我心里有一点点的失落,黑狗应该是已经逃走了吧。 +晚上吃完饭妈去拿猪食瓢,告诉我里面的饭已经被吃的一粒不剩,我心里开始期待和它的再次见面。第二天早晨果然见到它了,身上已经没有昨天那么湿了,显然没有前一天来这里时钻的草丛多,妈依旧用猪食瓢装着米饭和米汤放在牛棚后的那个石头上。 +就这样过了几日,黑狗走进了我家的屋檐,它的样子实在太丑了。每一根肋骨都清晰的扎眼,看起来爸的手指都比它的小腿粗,感觉下一秒它就会死去。 +我并不喜欢它,甚至还有些讨厌它,我实在找不到更丑的词来形容它,不过是出于心里的怜悯与对生命的敬畏,会在吃饭的时候给它丢几个我不吃的肥肉,被烟熏黑的那一层肉边我也丢给它...... +有一次同村的一个人路过家门口时,看见那只黑狗吓的赶紧往妈身后躲。“有我在,它不敢咬。”,妈说。邻居夸夸妈说:“这个狗儿喂得好肥”。妈自豪的告诉那个人这只狗每天还送林儿(我)上学。 +是的,我也不知道什么时候我已经和大黑狗变得如此亲密了,它每天早上会把我送到山顶的学校,我每天下午回家做完作业会和它一起到田间追逐。在学校也常常会给同学们说大黑狗胸前的那长成了“人”字的一片白毛,我一直相信“人”字是老天爷特地印在它身上,用来告诉我大黑狗是他派来的使者。 +大黑狗来我家时已经很老很老了,是我读三年级的某一天,它像往常一样把我送到学校,但是我下午回家却不见它的踪影,一直等到晚上都没有见它回来。那些天我放学回家第一件事就是朝我和它常去的那些地方大声的唤它。 +不到一个月后的一天早晨,像大黑狗第一次来我家附近时的场景一样,湿漉漉的身子带着些杂草种子,不同的是它身旁还跟着一只背部有些黑毛的小黄狗,小黄狗胸前也有一个很明显的“人”字。我赶紧去用猪食瓢盛满饭放在它面前,它吃了几口就又走了。 +就这样,大黑狗离开了我,给我留下了一只小小的黄奶狗。我不知道它是去找它原来的主人去了,还是觉得自己老了,不愿意让我看见它倒下的样子,反正它就是再也没有回来过。 +小黄狗长成了大黄狗,我对这只大黄狗的印象很浅,只记得爸妈把这只黄狗送给了外婆家附近的亲戚,我们留下了它生的一只小黄狗。外婆知道我们把大黄狗送人,还狠狠的批评了爸妈,说自己来家里的狗不能送人。 +自然小黄狗很快就长成了大黄狗,我像以前一样也偷偷给大黄狗吃肉,逐渐开始懂事的妹妹也会背着爸妈给它肉吃,我和妹都会夹几片我们压根就不吃的肥肉,离开饭桌假装是到外面吃饭,实际上是给大黄狗送肉去了。 +我到离家 30 多公里的镇上读高中,每个月才回家一次。每次离家大黄狗都会送我到集市去赶车,我会在寒暑假的黄昏和它到新修的公路去追逐,带它去它自己一个人不敢去探索的地方。 +上大学后和大黄狗相处的时间更少了,听爸妈说它会经常跑到外婆家,外婆好吃好喝的招待它,招呼都不打一声就又跑回来了。还经常和邻居家的狗到麦子地打闹,要把一大片麦子弄倒才肯回家。 +每学期回家在离家还有四五百米的地方都会听到它的吠叫,因为它把我当陌生人了。但是只要我大喊一声,它就会立刻停止吠叫,飞奔到我这里,兴奋的往我身上爬,把它的前爪往我身上搭;我努力不让它碰到我的衣服,然而每次到家时我都带着一身泥巴做的狗爪印。 +现在大黄狗已经 10 多岁了,它就像大黑狗当年送我一样每天送我妹上学。我也已经走入职场开始工作,待在家里的时间更少了,我不知道它还能活多久,生怕哪次爸妈打电话时会给我说大黄狗死了,只要爸妈没有在电话中提及大黄狗,我都是非常开心的,因为那就代表着它依旧健健康康的活着。 + +
+ + Read More ~ +
+
+
+ +
+

+ + JavaScript 进阶知识、技巧 + +

+ +
+ + + + +
+ +
+ 对象 +Js 共有number、string、boolean、null、undefined、object六种主要类型,除了object的其它五中类型都属于基本类型,它们本身并不是对象。但是null有时会被当做对象处理,其原因在于不同的对象在底层都表示为二进制,在 js 中二进制前三位都为 0 的话就会被判定为object类型,而null的二进制表示全是 0, 所以使用typeof操作符会返回object,而后续的 Js 版本为了兼容前面埋下的坑,也就没有修复这个 bug。 +&quot;I'm a string&quot;本身是一个字面量,并且是一个不可变的值,如果要在这个字面量上执行一些操作,比如获取长度、访问某个字符等,那就需要将其转换为String类型,在必要的时候 js 会自动帮我们完成这种转换,也就是说我们并不需要用new String('I'm a string')来显示的创建一个对象。类似的像使用42.359.toFixed(2)时,引擎也会自动把数字转换为Number对象。 +null和undefined没有对应的构造形式,它们只有文字形式。相反,Date只有构造,没有文字形式。对于Object、Array、Function和RegExp(正则表达式)来说,无论使用文字形式还是构造形式,它们都是对象,不是字面量。 +Array 类型 +数组类型有一套更加结构化的值存储机制,但是要记住的是,数组也是对象,所以有趣的是你也可以给数组添加属性。 +var myArray = [&quot;foo&quot;, 42, &quot;bar&quot;]; +myArray.baz = &quot;baz&quot;; +myArray.length; // 3 +myArray.baz; // &quot;baz&quot; + +数组类型的length属性是比较有特点的,它的特点在于不是只读的,也就是说你可以修改它的值。因此可以通过设置这个属性从数组末尾删除或添加新的项。 +var colors = [&quot;red&quot;, &quot;blue&quot;, &quot;green&quot;]; +colors.length = 2; +console.info(colors[2]); // undefined +colors.length = 4; +console.info(colors[4]); // undefined +// 向后面追加元素 +colors[colors.length] = &quot;black&quot;; + +数组还有一些很方便的迭代方法,比如every()、filter()、forEach()、map()、some(),这些方法都不会修改数组中包含的值,传入这些方法的函数会接收三个参数:数组项的值、该项在数组中的位置、和数组对象本身。 +Function 类型 +在 ECMAScript 中,每个函数都是Function类的实例,而且都与其它引用类型一样具有属性和方法。由于函数时对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。 +在函数的内部有两个特殊的对象,this和arguments。arguments对象有callee和caller属性。caller用来指向调用它的function对象,若直接在全局环境下调用,则会返回null;callee用来指向当前执行函数,所以我们可以通过下面的方式来实现阶乘函数。 +function factorial(num) { + if (num &lt;= 1) { + return 1; + } else { + return num * arguments.callee(num-1); + } +} + +每个函数都包含两个非继承而来的方法,apply()和call(),这两个方法都是在特定作用域中调用函数,实际上等于设置函数体内this对象的值。首先,apply()方法接收两个参数,一个是在其中运行函数的作用域,另一个是参数数组,其中第二个参数可以是Array的实例,也可以是arguments对象。call()方法与apply()方法的作用相同,它们的区别仅仅在于接收参数的方式不同,在使用call()方法时必须逐个列举出来。 +window.color = &quot;red&quot;; +var o = {color: &quot;blue&quot;}; +function sayColor() { + console.info(this.color); +} +sayColor(); // red +sayColor.call(this); // red +sayColor.call(window); // red +sayColor.call(o); // blue +sayColor.apply(o); // blue + +需要注意的是,在严格模式下未指定环境对象而调用函数,则this值不会转型为window,除非明确把函数添加到某个对象或者调用apply()或call()。 +安全的类型检查 +Js 内置的类型检查机制并不是完全可靠的,比如在 Safari(第5版前),对正则表达式应用typeof操作符会返回function;像instanceof在存在多个全局作用域(包含 frame)的情况下,也会返回不可靠的结果;前文提到的 Js 一开始埋下的坑也会导致类型检查出错。 +我们可以使用toString()方法来达到安全类型检查的目的,在任何值上调用Object原生的toString()方法都会返回一个[object NativeConstructorName]格式的字符串,下面以检查数组为例。 +Object.prototype.toString.call([]); // &quot;[object Array]&quot; +function isArray(val) { + return Object.prototype.toString.call(val) == &quot;[object Array]&quot;; +} + + +作用域安全的构造函数 +构造函数其实就是一个使用new操作符调用的函数,当使用new操作符调用时,构造函数内用到的this对象会指向新创建的对象实例,比如我们有下面的构造函数。 +function Person(name, age) { + this.name = name; + this.age = age; +} + +现在的问题在于,要是我们不使用new操作符呢?会发生什么! +let person = Person('name', 23); +console.info(window.name); // name +console.info(window.age); // 23 + +很明显,这里污染了全局作用域,原因就在于没有使用new操作符调用构造函数,此时它就会被当作一个普通的函数被调用,this就被解析成了window对象。我们需要将构造函数修改为先确认this是否是正确类型的实例,如果不是则创建新的实例并返回。 +function Person(name, age) { + if (this instanceof Person) { + this.name = name; + this.age = age; + } else { + return new Person(name, age); + } +} + +高级定时器 +大部分人都知道使用setTimeout()和setInterval()可以方便的创建定时任务,看起来好像 Js 也是多线程的一样,实际上定时器仅仅是计划代码在未来的某个时间执行,但是执行时机是不能保证的。因为在页面的生命周期中,不同时间可能有其它代码控制着 JavaScript 进程。 +这里需要注意一下setInterval()函数,仅当没有该定时器的任何其他代码实例时,Js 引起才会将定时器代码添加到队列中。这样可以避免定时器代码可能在代码再次被添加到队列之前还没有完成执行,进而导致定时器代码连续运行好几次的问题。但是这也导致了另外的问题:(1)某些间隔会被跳过;(2)多个定时器的代码执行之间的间隔可能会比预期小。 +假设某个click事件处理程序使用setInterval()设置了一个 200ms 间隔的重复定时器。如果这个事件处理程序花了 300ms 多的时间完成,同时定时器代码也花了差不多了的时间,就会同时出现跳过间隔切连续运行定时器代码的情况。 +为了避免setInterval()的重复定时器的这两个缺点,我们可以使用如下模式的链式setTimeout(),代码一看就懂什么意思了。 +setTimeout(function() { + // 处理中 + setTimeout(arguements.callee, interval); +}, interval) + +消息队列与事件循环 +如下图所示,左边的栈存储的是同步任务,就是那些能立即执行、不耗时的任务,如变量和函数的初始化、事件的绑定等等那些不需要回调函数的操作都可归为这一类。 + +右边的堆用来存储声明的变量、对象。下面的队列就是消息队列,一旦某个异步任务有了响应就会被推入队列中。如用户的点击事件、浏览器收到服务的响应和setTimeout中待执行的事件,每个异步任务都和回调函数相关联。 +JS引擎线程用来执行栈中的同步任务,当所有同步任务执行完毕后,栈被清空,然后读取消息队列中的一个待处理任务,并把相关回调函数压入栈中,单线程开始执行新的同步任务。 +来看个例子:执行下面这段代码,执行后,在 5s 内点击两下,过一段时间(&gt; 5s)后,再点击两下,整个过程的输出结果是什么? +setTimeout(function(){ + for(var i = 0; i &lt; 100000000; i++){} + console.log('timer a'); +}, 0) +for(var j = 0; j &lt; 5; j++){ + console.log(j); +} +setTimeout(function(){ + console.log('timer b'); +}, 0) +function waitFiveSeconds(){ + var now = (new Date()).getTime(); + while(((new Date()).getTime() - now) &lt; 5000){} + console.log('finished waiting'); +} +document.addEventListener('click', function(){ + console.log('click'); +}) +console.log('click begin'); +waitFiveSeconds(); + +首先,先执行同步任务。其中waitFiveSeconds是耗时操作,持续执行长达 5s。然后,在 Js 引擎线程执行的时候,'timer a'对应的定时器产生的回调、'timer b'对应的定时器产生的回调和两次 click 对应的回调被先后放入消息队列。由于 Js 引擎线程空闲后,会先查看是否有事件可执行,接着再处理其他异步任务,最后,5s 后的两次 click 事件被放入消息队列,由于此时 Js 引擎线程空闲,便被立即执行了。因此会产生下面的输出顺序。 +0 +1 +2 +3 +4 +click begin +finished waiting +click +click +timer a +timer b +click +click + + +
+ + Read More ~ +
+
+
+ +
+

+ + 信息茧房|如何保持开心|自己的圈子—校友、房东|潮州彩塘抛光厂 + +

+ +
+ + + + +
+ +
+ 好几个月没有发文章了,主要是因为觉得自己太菜了,肚子里的东西太多浮于表面(实际上肚子也没有东西),也写不出来什么深度。不知道大家发现没有,现在很多公众号的味道都变了,一者是肚子里的货已经吐的差不多了,二者是在自媒体疯狂变现的年代,太多作者都开始为流量而写作,已经忘记了原来的初心。好友说长期不发文,突然发会掉粉的,我也想试试会掉下去多少。 +说到为流量写作,其实并不是自媒体作者天天在干的事,专业的记者也在做这些事情。从商业角度来看,一篇有深度而没有阅读量的文章肯定是比不上一篇适合大众口味但阅读量高的文章。 +媒体总是会挑那些吸引眼球的事件来报道,因为负面故事总比中性或正面故事更具有戏剧性,而且人在进化的过程中保留了对一些事物的恐惧感,这些恐惧感根植于我们大脑的深处,它们对我们祖先的生存是有帮助的。在现在的这个时代,你也很容易就把眼球放到那些能够激发我们本能的故事上。 +包含地震、恐怖袭击、战争、疾病、难民等等字眼的标题总是容易成为头版头条(现在朋友圈肯定都在传四川内江的地震),而像“在过去 100 年,死于自然灾害的人数几乎减少了四分之三”一类的标题总是不会收获多少阅读量,就更不具备什么商业价值了。大家都在说信息茧房,人类的本能也是造成信息茧房的原因之一。 + +周四和一个同事一起散步的时候,他问了我一句话:“小老虎,你为什么总是能保持这么开心呢?”(小老虎是在部门大家对我的称呼)我思考了几秒,不知道怎么回答同事的问题。对哦,我是怎么保持每天都这么开心的?是我给他们的错觉还是我确实就这么开心呢?于是给了同事一个简单的答案:“当你变得没心没肺的时候,你就会超开心;另外降低对事物的期望值,这样你就总能收到正反馈,会把你的开心加成。” +像之前一样,我又成长为同事圈子里的小开心果了。其实我也不是一直开心的,可能就是我这个人比较逗比,我一直认为逗比是一种生活态度。但在公司我同样怼大叔、怼领导,不管我是不是真的开心,既然给大家的印象是开开心心的,那就假装我是一直都开心的吧。 +我常常开玩笑说的一句话:“你对它笑,它就会对你笑,如果它不对你笑,那就对它多笑几次”。你对它笑,你肯定希望对方也给你回一个笑,但是我和大多数人不同的是我降低了期望值,我从来不期望对方能给我一个笑容,于是当对方给了你一个笑容的时候,那就是意外地收获,如果是一个大大的甜甜的笑容,就会突然冒出来幸福来的太突然了感觉。降低期望值也是一个很适合长期学习某项技能的方法,过高的期望值总是会让你放弃。 +很多人说情商是为了别人高兴,话外音就是不想委屈自己迁就别人。但是你让别人高兴了就是与人方便,那对方自然会给你方便,自己方便了不就是高兴吗,所以对这个世界好一点,降低对它的期望值,你就总是能开开心心的过日子。 + +毕业这一年认识了很多人,现在我日常接触的圈子差不多有四个,同事这个圈子没啥特别的,团队氛围比较好,时常在晚上悄悄定个会议室,大家一起打王者;推特、微信等软件里面结交的互联网大佬圈我插不上话,不敢说;然后是我两任房东带我进的圈子,和高校毕业人群所建立的圈子完全不一样。 +这群人大部分对我都很好,我目前比较害怕见到现任房东,因为基本上见到他就是出去吃饭。我住在房东隔壁,刚搬过来的时候一出门见到他:“小光,走,去吃饭。”房东的吃饭一般是两场,一场到餐厅点菜吃到 11:00-12:00 的样子,然后再继续下半场烧烤,在房东的带领下,我一个月长了 10 多斤。 +于是我现在出房门的时候,先瞅瞅房东在不在,如果不在就直接坐电梯下楼,如果在就先下到 5 楼,再坐电梯。所以我们现在更多的是没事喝喝茶,偶尔吃吃饭,体重总算控制住了。 +当然这个圈子也有不太好的人,有借了我钱后人就跑的没了踪影的人。但是我很庆幸我能这么早遇到这样的人,因为现在我借出去的并不多,如果再等 10 年我才能遇到这样的人,那我的损失可能就是很多很多倍了。 + +另外一个对我很重要的圈子就是校友会,我不清楚学校其它地区校友会是什么情况,更不清楚其它学校校友会是怎么样的,深圳校友会确实给了我一个温馨的感觉。校友之间都很单纯,学长学姐们都愿意带年轻人,最大有 79 级的师姐,最小的 15 级也已经到来,老人都会给新人讲他们所经历的事情,给年轻人传授经验。 +当然由于学校带着军校的基因,校友里面没有什么非常非常出名的企业家,但是大家都是很尽心尽力的相互帮助。仅仅靠校友情能达到这样的效果,这一点确确实实是出乎我的意料了,校友会目前是对我开心的加成作用很大。 +举个例子,一个学长新开了烧烤店,现在还没有开始对外营业,处于内测阶段。这一周每天店内至少有一半都是校友,店内的设计、装修、监控等等校友都在出力,当然像我这种没资源的学弟只能试吃给出改进意见了,一个人在外地能成为这样大家庭中的一员是很幸福的。 + +高校毕业生一年比一年多,媒体每年的标题都差不多一个意思:史上最难就业季。不得不承认独自一人到外地打工确实辛苦,大家都是独自承受着来自各方的压力,杭州闯红灯小伙的突然崩溃就是一个极端的例子。 +我之前的住的地方,仅仅我知道的就有三个年龄比我还小的女孩被包养,仅从外部观察来看,她们过的其实挺好的,嘴角也常常挂着 45 度的微笑,倒是包养她们的人过的不是多随性。其中一个还开了一家奶茶店,我有幸也喝了几杯免费奶茶。 +另外还有一些像我一样的打工者,我和前任房东也常常喝茶吃饭(现在也是),听他说住在那里的女孩子很多没有男朋友,但是她们晚上经常会带不同的男生回来,我想这对她们来说也是一种释压方式,当然住那里的男生可能只是没有带回来,房东不知道而已。 + +我不是太喜欢天天去研究某个业界名人所讲的话,也对各种各样的产品不是多感冒,不否认有些营销文案、产品功能、讲话内容是公司有意精心为之,但是有没有另外一种可能呢?是领导背错了台词、或者是说错了,而我们却非得去给它找出各种各样的原理。 +周末闲着去感受了一下农民工的圈子,我去的是潮州彩塘镇的抛光厂,才知道我们平时用的那些锅碗瓢盆那么亮不是因为镀上了一层,而是硬生生给磨掉了一层,给磨亮的。最后再说一个,不知道你有没有注意到马路边的人行道上,总是会有一列地砖是有凸起的,有的是条状凸起,有的是圆点凸起,有没有想过为什么是这样的呢? +凸起是盲人走的道路,条状代表直走,圆点代表拐弯。是不是觉得这个世界对每个人都是美好的,既然这个世界对我们这么美好,那干嘛要不开心呢? + +
+ + Read More ~ +
+
+
+ +
+

+ + 深入理解 JavaScript——变量提升与作用域 + +

+ +
+ + + + +
+ +
+ +参考内容: +lhs rhs是啥意思 +《Javasript 高级程序设计(第三版)》 +《你不知道的 JavaScript(上卷)》 + +几乎所有的编程语言都能够存储变量当中的值,并且可以在之后对该值进行访问或修改。很明显需要一套良好的规则来存储这些变量,并且之后可以方便的找到这些变量,这套规则我们称之为作用域。 +编译原理 +我们一般把 js 归为「动态」或「解释执行」语言,但是它也会经历编译阶段,不过它不像传统语言那样是提前编译的,它的编译发生在代码执行前的几微秒内。 +传统语言在执行之前会经历三个步骤:分词/词法分析、解析/语法分析、代码生成,关于这三个步骤的具体工作,可以查看编译原理相关的文献,我们可以把这三个步骤统称为编译。不过 js 引擎要复杂的多,它会在编译的时候对代码进行性能优化,尽管给 js 引擎优化的时间非常少,但是它用尽了各种办法来保证性能最佳。 +我们需要先了解三个名词。引擎:从头到尾负责整个 js 程序的编译及执行过程;编译器:负责词法分析及代码生成;作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。 +var a = 2;,我们以这段程序为例,它首先声明了变量a,然后将2赋值给变量a。前一个阶段在编译器处理,后一个阶段由 js 引擎处理。 +变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。 +变量提升 +用过 js 的人都知道 js 存在变量提升,那么它到底是如何提升的呢?我们看下面的一段代码 +console.log(a); +var a = 2; + +上述代码在a声明之前访问了变量a,按我们的逻辑它应该会抛出 ReferenceError 异常;或是变量提升直接输出 2。但是这两种答案都不对,输出的是undefined。 +回顾一下前文的关于编译的内容,引擎会在解释 js 代码之前对其进行编译,编译阶段的一个重要工作就是找到所有的声明,并用合适的作用域将它们关联起来,包括变量和函数在内的所有声明都会在任何代码被执行之前首先被处理。所以我们前面列出来的代码实际上会变成下面这个样子。 +var a; +console.log(a); +a = 2; + +这个过程就好像变量和函数声明会从它们的代码中出现的位置被移动到最上面一样,这个过程就是提升。但是需要注意的是,函数声明会首先被提升,然后才是变量提升。 +foo(); // 1 +var foo; + +function foo() { + console.info(1); +} + +foo = function() { + console.info(2); +} + +这段代码输出 1 而不是 2 ,它会被引擎理解为下面的形式。 +function foo() { + console.log(1); +} + +foo(); // 1 + +foo = function() { + console.log(2); +}; + +可以看到,虽然var foo出现在function foo()之前,但是它是重复的声明,因此会被忽略掉,因为函数函数声明会提升到普通变量前。所以在在同一个作用域中进行重复定义是一个很糟糕的做法,经常会导致各种奇怪的问题。 +LHS 和 RHS 查询 +LHS 和 RHS 是数学领域内的概念,意为等式左边和等式右边的意思,在我们现在的场景下就是赋值操作符的左侧和右侧。当变量出现在赋值操作符的左边时,就进行 LHS 查询;反之进行 RHS 查询。 +RHS 查询与简单的查找某个变量的值没什么区别,它的意思是取得某某的值。而 LHS 查询则是试图找到变量容器的本身,从而可以对其进行赋值。 +console.info(a);我们深入研究一下这句代码。这里对a的引用是 RHS 引用,因为这里a并没有赋予任何值,相应的需要查找并取得a的值,这样才能传递给console.info()。 +a = 2;对a的引用则是一个 LHS 引用,因为实际上我们并关心a当前的值是什么,只是想为= 2这个赋值操作找到一个目标。 +function foo(a) { + console.info(a); +} +foo(2); + +为了加深印象,我们再来分析一下上述代码中的 RHS 和 LHS 引用。最后一行foo()函数的调用需要对foo进行 RHS 引用。这里有一个很容易被忽略的细节,2 被当作参数传递给foo()函数时,2 会被分配给参数a,为了给参数a(隐式地)分配值,需要进行一次 LHS 查询,也就是说代码中隐含了a = 2的语句。 +前文已经说过了console.info(a);会对a进行一次 RHS 查询,需要注意的是console.info()本身也需要一个引用才能执行,因此会对console对象进行 RHS 查询,并检查得到的值中是否有一个log方法。 +为什么区分 LHS 和 RHS +我们考虑下面的一段代码,就可以为什么要区分 LHS 和 RHS 查询了,而且区分它们是分厂有必要的。 +function foo(a) { + console.info(a + b); + b = a; +} +foo(2); + +第一次对b进行 RHS 查询时是无法找到该变量的,这是一个未声明的变量,在任何相关的作用域中都无法找到它。如果 RHS 查询在所有嵌套作用域中都找不到该变量,引擎就会抛出 ReferenceError 异常。 +引擎在执行 LHS 查询时,如果在全局作用域中也无法找到目标变量,全局作用域就会创建一个具有该名称的变量,并将其返还给引擎。 + +需要注意的是,在严格模式下是禁止自动或隐式地创建全局变量的,因此在严格模式中 LHS 查询失败时,引擎同样会抛出 ReferenceError 异常。 + +接下来,如果 RHS 查询找到了一个变量,但是你尝试对这个值进行不合理的操作,比如对一个非函数类型的值进行函数调用,那么引擎就会抛出另一种叫做 TypeError 的异常。 +作用域链 +执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中,在 Web 浏览器中,全局执行环境被认为是window对象,因此所有的全局变量和函数都是作为window对象的属性和方法创建的。 +每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,而函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境,这个函数调用的压栈出栈是一样的。 +当代码在环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端始终都是当前执行的代码所在环境的变量对象,说的比较抽象,我们可以看下面的示例。 +var color = &quot;blue&quot;; + +function changeColor() { + var anotherColor = &quot;red&quot;; + + function swapColors() { + var tempColor = anotherColor; + anotherColor = color; + color = tempColor; + // 这里可以访问 color、anotherColor 和 tempColor + } + // 这里可以访问 color 和 anotherColor,但不能访问 tempColor + swapColors(); +} +// 这里只能访问 color +changeColor(); + +下面的图形象的展示了上述代码的作用域链,内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。函数参数也被当做变量来对待,因此其访问规则与执行环境中的其它变量相同。 +window + |-----color + |-----changeColor() + |----------anotherColor + |----------swapColors() + |----------tempColor + +作用域链还用于查询标识符,当某个环境中为了读取或写入而引入一个标识符时,必须通过搜索来确定该标识符实际代表什么。搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符,如果在局部环境中找到了该标识符,搜索过程就停止,变量就绪;如果在局部环境没有找到这个标识符,则继续沿作用域链向上搜索,如下所示: +var color = &quot;blue&quot;; + +function getColor() { + var color = &quot;red&quot;; + return color; +} + +console.info(getColor()); // &quot;red&quot; + +在getColor()中沿着作用域链在局部环境中已经找到了color,所以搜索就停止了,也就是说任何位于局部变量color的声明之后的代码,如果不使用window.color都无法访问全局color变量。 + +
+ + Read More ~ +
+
+
+ +
+

+ + JavaScript 性能优化——惰性载入函数 + +

+ +
+ + + + +
+ +
+ +参考资料: +《JavaScript 高级程序设计(第三版)》 +JavaScript专题之惰性函数 +深入理解javascript函数进阶之惰性函数 + +因为不同厂商的浏览器相互之间存在一些行为上的差异,很多 js 代码包含了大量的if语句,将执行引导到正确的分支代码中去,比如下面的例子。 +function createXHR() { + if (typeof XMLHttpRequest != 'undefined') { + return new XMLHttpRequest(); + } else if (typeof ActiveXObject != 'undefined') { + if (typeof arguments.callee.activeXString != 'string') { + var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']; + var i, len; + for (i = 0, len = versions.length; i &lt; len; i++) { + try { + new ActiveXObject(versions[i]); + arguments.callee.activeXString = versions[i]; + } catch (e) { + // skip + } + } + } + return new ActiveXObject(arguments.callee.activeXString); + } else { + throw new Error('No XHR object available.'); + } +} + +我们可以发现,在浏览器每次调用createXHR()的时候,它都要对浏览器所支持的能力仔细检查,但是很明显当第一次检查之后,我们就应该知道浏览器是否支持我们所需要的能力,因此除第一次之外的检查都是多余的。即使只有一个if语句也肯定要比没有if语句慢,所以if语句不必每次都执行,那么代码可以运行的更快一些,惰性载入就是用来解决这种问题的技巧。 +函数重写 +要理解惰性载入函数的原理,我们有必要先理解一下函数重写技术,由于一个函数可以返回另一个函数,因此可以在函数内部用新的函数来覆盖旧的函数。 +function sayHi() { + console.info('Hi'); + sayHi = function() { + console.info('Hello'); + } +} + +我们第一次调用sayHi()函数时,控制台会打印出Hi,全局变量sayHi被重新定义,被赋予了新的函数,从第二次开始之后的调用都会打印出Hello。惰性载入函数的本质就是函数重写,惰性载入的意思就是函数执行的分支只会发生一次。 +惰性载入 +我们来看一个例子(例子来源于冴羽所写的JavaScript专题之惰性函数)。现在需要写一个foo函数,这个函数返回首次调用时的Date对象,注意是首次。 +方案一 +var t; +function foo() { + if (t) return t; + t = new Date() + return t; +} +// 此方案存在两个问题,一是污染了全局变量 +// 二是每次调用都需要进行一次判断 + +方案二 +var foo = (function() { + var t; + return function() { + if (t) return t; + t = new Date(); + return t; + } +})(); +// 使用闭包来避免污染全局变量, +// 但是还是没有解决每次调用都需要进行一次判断的问题 + +方案三 +function foo() { + if (foo.t) return foo.t; + foo.t = new Date(); + return foo.t; +} +// 函数也是一种对象,利用这个特性也可以解决 +// 和方案二一样,还差一个问题没有解决 + +方案四 +var foo = function() { + var t = new Date(); + foo = function() { + return t; + }; + return foo(); +}; +// 利用惰性载入技巧,即重写函数 + +惰性载入函数有两种实现方式,第一种是在函数被调用时再处理函数。在第一次调用的过程中,该函数会被覆盖为另外一种按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行分支了。 +第二种实现方式是在声明函数时就指定适当的函数。这样第一次调用时就不会损失性能了,而是在代码首次加载时会损失一点性能,即是利用闭包写一个自执行的函数。 +改进 createXHR +有了上面的基础,我们就可以将createXHR()改进为下列形式,这样就不用每次调用都进行判断了。 +// 第一种实现方式 +function createXHR() { + if (typeof XMLHttpRequest != 'undefined') { + createXHR = function() { + return new XMLHttpRequest(); + } + } else if (typeof ActiveXObject != 'undefined') { + createXHR = function() { + if (typeof arguments.callee.activeXString != 'string') { + var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']; + var i, len; + for (i = 0, len = versions.length; i &lt; len; i++) { + try { + new ActiveXObject(versions[i]); + arguments.callee.activeXString = versions[i]; + } catch (e) { + // skip + } + } + } + return new ActiveXObject(arguments.callee.activeXString); + }; + } else { + createXHR = function() { + throw new Error('No XHR object available.'); + } + } +} + +// 第二种实现方式 +function createXHR() { + if (typeof XMLHttpRequest != 'undefined') { + return function() { + return new XMLHttpRequest(); + } + } else if (typeof ActiveXObject != 'undefined') { + return function() { + if (typeof arguments.callee.activeXString != 'string') { + var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']; + var i, len; + for (i = 0, len = versions.length; i &lt; len; i++) { + try { + new ActiveXObject(versions[i]); + arguments.callee.activeXString = versions[i]; + } catch (e) { + // skip + } + } + } + return new ActiveXObject(arguments.callee.activeXString); + }; + } else { + return function() { + throw new Error('No XHR object available.'); + } + } +} + + +
+ + Read More ~ +
+
+
+ +
+

+ + 非设计师需要知道的四个设计原则 + +

+ +
+ + + + +
+ +
+ +作者:Anna 4erepawko Mészáros,UI/UX 设计师。 +关注作者: Medium、Twitter + +这篇文章是写给无力邀请专业设计师的所有内容创作者的,以及设计师异常忙碌的团队的非设计师们。如果您按照这些简单的步骤进行操作,我保证您的设计会变得更好。 +这些 Tips 来源于我对身边非设计朋友的多年观察,家人与同事在日常生活中也需要设计他们的东西。比如简历、作品集,Facebook 和 Instagram 上帖子要使用的图片,YouTube 视频的缩略图等。 +所有这些人都向我寻求帮助与建议,希望能让他们的东西看起来更好。我坚信「授人以鱼不如授人以渔」,所以我试图提供更有价值的建议,以便他们在未来也能解决类似的问题。 +随着时间的推移,我意识到我一直在给所有人提供相同的建议,虽然每次所使用的措辞不同,但我所有的建议都可以提炼为以下四个原则。 +这些 Tips 会帮您创造出美丽动人的设计吗?答案是不会!它们只会帮您创造出色、清晰且易于理解的设计。是每个人都可以轻松理解和互动吗?那当然,所以不多说废话,下面我就向您逐一展示。 +对比 +确保所有元素之间有足够的对比度。为什么?因为那些略有差异但是又不够不同东西,创造了一种恐怖谷。人类的眼睛会排斥它们,对它们感到厌恶、难以理解。我们不希望它们出现在我们的设计中,难道不是吗? + +恐怖谷理论,是一个关于人类对机器人和非人类物体的感觉的假设。如果一个非人类实体不够拟人,那么它身上的人类特征会很容易辨认;而当它足够拟人时,他身上的非人类特征则会变得很容易辨认。因此会在人类观察者眼中产生一种古怪的感觉,想想您看到病患者或者尸体时的感觉。 + +因此您设计的元素要么完全相同,要么具有显著差异。 +您可以从下面四个方面来突出对比: +1、颜色:浅色上使用暗色,反之亦然 +示例: 切勿在浅蓝色上使用浅灰色或浅粉红色等灰色组合,它们会造成阅读/互动上的极大困难。 + +2、大小:相邻元素要么大小完全相同,要么大小区别很大 +示例: 不要将 32pt 和 36pt 的文本放在一起;18pt 和 36pt 放在一起会显得更加协调。 + +3、粗细:与大小一样,相邻元素的粗细要么完全相同,要么有明显的区别 +示例: 不要将相同字体的粗体与黑体放在一起,因为它们看起来太相似了;将黑体与细体放在一起会显得很协调。 + +4、风格:不要将一个斜体类型放在另一个斜体类型旁边,或者在一个衬线字体旁边放置另一个衬线字体。应该组合不同的东西。 +示例: 不要将 Times New Roman 与 Georgia 放在一起,它们看起来太相似了,应该组合完全不同的风格。 + +一致性 +确保相似的元素以相似的方式出现。为什么呢?首先,通过确保确保事物一致性,您可以让用户将注意力集中在设计的重要方面,而不是被随时变化的元素分散注意力。 +其次,一致性也增加了用户对您的信任,使事物看起来实际上是设计的,而不是简单快速拼凑出来的。 +一旦你选择了具体的风格,就要毫不犹豫的坚持下去,这里所说的风格包括字体、颜色、阴影、栅格、对齐、装饰风格等等。 + +当您处理许多相邻的不同部分时(比如 YouTube 的视频缩略图或是中型文章的封面),您应该为所有部分选择一种整体风格,并坚持使用。 +奥卡姆剃刀 减少视觉噪音 +在您的设计中,使用的元素越少越好。为什么呢?因为人类的大脑很难在输入过载的情况下处理信息并作出决策。您应该使用尽可能少的装饰元素(字体、颜色、阴影、图标等等)。 +将奥卡姆剃刀应用于所有内容。如果只需要两个元素就能满足需求,那么就不要使用 3 个元素;如果 10 个元素实现所需的功能,那么就不要用 20 个元素。 + +如果您不喜欢古老的英国哲学家风格,更喜欢您在 Netflix(一家美国流媒体提供商)上看到的东西。请将怦然心动的人生整理魔法应用到您的设计中。 + +《怦然心动的人生整理魔法》是美国流媒体提供商Netflix于2019年1月1日首播的一档真人实境秀节目。节目由日本“整理咨询顾问” 近藤麻理惠主创。她在每集节目中拜访一个家庭,帮助他们整理自己的房间。 +近藤麻理惠认为整理房间时应当将物品分为五类:衣物、书籍、纸张文件、杂物和情感纪念品;在整理时拿起每件物品,如果能使自己“怦然心动”则留下,如果不能则要感谢物品的贡献然后与其告别。 + +间距 +元素的位置会发送关于其含义的元级别消息。为什么这很重要?因为了解如何放置元素以及在它们周围预留了多少空间有助于降低设计的复杂性,因此会使人更加愉悦,并且更容易交互。 +在您的设计中使用间距来传达下面 3 个方面的信息: +1、接近度 = 相关性 +与其它元素相比,彼此更接近的事物被认为它们有更强的相关性。这是最重要的,因为我觉得它常常容易被忽视。 +它可以以很多不同的方式应用,比如行与行之间应该有一定的间距,而不是一行中每个单词之间的间距那么小;同样不同段落之间的空间也比段落内的行空间要大。 + +元素之间的间距应该小于元素与组合边缘之间的间距。 + +标签和支撑信息应该位于其相关元素附近。 + +2、留白 +结合奥卡姆剃刀,给您的设计尽可能留白,去整理它们,使它们的意义更加明显。 +如果把太多元素放在有限的空间里,就像同时听三首不同的哥,很难理解别人在说什么。 + +3、重要性与顺序 +这是一个很普通的常识,但是我还是要在这里提到它。 +最重要的事情放在第一位,使它们占据最大的空间,用一系列的事物来传达秩序。 +结束语 +恭喜您!如果您按照这些 Tips 进行设计,那么按照行业标准,它可能看起来非常好。 +For everything else, there is always a designer. + +
+ + Read More ~ +
+
+
+ +
+

+ + 深入理解计算机系统——CPU 是怎样工作的? + +

+ +
+ + + + +
+ +
+ +参考内容: +处理器是如何工作的 +《编码:隐匿在计算机软硬件背后的语言》——[美] Charles Petzold + +CPU 大家应该都不会陌生,日常用的手机、电脑中都有 CPU,CPU 作为一个设备的大脑,指挥着其它各种硬件的协同工作,芯片技术也是国内一直没有突破的技术。 +我们先来看看怎么让电路去运算呢?比如如何让电路运算1 + 1,直接使用下面这个装置就可以了。 + +作为一个比较好奇的人,总会想看看那个方框框里面是什么样子的,让我们慢慢解开加法器的外衣。 + +这个电路你应该不会陌生,它需要两个开关都闭合时灯泡才会发光,也就是说它有两个输入,开关闭合时我们认为输入为 1,未闭合状态视为 0;而灯泡是否发光就是我们的输出,发光为 1,否则为 0。于是就有了下面这张表。 + + + +and +0 +1 + + + + +0 +0 +0 + + +1 +0 +1 + + + +这样的电路我们就把它称之为与(and)门,它接受两个逻辑输入,并会给出我们一个逻辑输出,与它相似的电路还有逻辑或(or)、**异或(xor)**等等,因为太多了,就不一一介绍了,如果感兴趣可以 Google 一下。 +为了方便我们把上面的电路做一个简化,抽象成下面这个样子,其它的电路也可以通过 Google 找到它们的样子。 + +现在直接给出一个可以运算加法的电路,它的样子长下面这样。 + +我们也可以给它列一个表,显得更清晰,表中之所以有两位是因为加法有可能会产生进位,而我们使用的是二进制表示,所以10表示的是十进制中的2。 + + + ++ +0 +1 + + + + +0 +00 +01 + + +1 +01 +10 + + + +有加法就很好办了,减法实际上是加一个负数,除法可以改写成乘法,乘法又可以改写成加法,现在加法一统天下了。 +好了,上面说了那么多,还贴了那么多图,只是为了让你相信电路是可以实现我们数学上的运算的,下面就没有那么声情并茂了。 +我们可以把一个运算抽象为下面这个模型。 +输入数据 --&gt; 运算 --&gt; 输出数据 + +计算机中把各种像上述加法器一样的运算器放在了同一个小盒子里面,组成成一个运算器集合,我们给它取个名字叫算术逻辑单元(ALU:Arithmetic Logical Unit),它是 CPU 的核心组成部分。 +当然我们不可能只进行加法这种简单运算,一个很长很长的算式需要经过多步运算,小学我们就学过梯等式,它实际上有一大推输入,中间那么多梯子,都是临时步骤,临时步骤、数据一类的东西都需要有个东西给它存起来,所以在 CPU 就拿出一个小盒子当做存储器。 +# 梯等式 +485 - ( 6 × 4 + 32 ) += 485 - ( 24 + 32 ) += 485 - 56 += 429 + +现在我们有了存储器和运算器两个干事的人,但是没有一个统筹兼顾的领导者,控制器就充当了这个角色,它需要把控制储存器中的数据发送到 ALU 去进行运算,然后再将运算的结果取出来存到储存器中。总的来说,控制器的工作就是完成协调和指挥整个计算机系统的操作。 +我们把上面的结构画出来,图中的虚线表示指令流,实线表示数据流,这个结构就是著名的冯 · 诺依曼体系结构,遵循这种结构的计算机都叫做冯诺依曼机,现在所有的机器都是冯诺依曼机。 + +请注意,我们现在实际上只有硬件,没有软件是什么事情都干不了了,我们这里所说的软件是一堆指令序列,比如加法指令、传送指令等等组成的序列,也就是我们常说的汇编语言。 +但是在早期并不是这样的,这台机器上编写的指令序列是无法运行在另一家公司生产的机器上的,即使是同一个公司的机器,如果不是同一代,那也不能运行,所以早期的编程是直接面向硬件的。 +这时就有人站出来研究如何实现一次编写多处运行了,IBM 首次在它的 360 系统上引入了ISA的概念,即指令集体系结构。 +指令集体系结构将编程所要了解的硬件信息从硬件系统中抽象了出来,这样开发人员就可以直接面向处理器的 ISA 进行编程了。 +为什么手机上的软件不能运行在电脑中呢?就是因为个人电脑所使用的 Intel 和 AMD 处理器都是基于 x86 指令集的,而手机大多数都使用的是基于 ARM 指令集的处理器。 +现在处理器被分为指令集体系结构、处理器微架构、处理器物理实现三个层次。体系结构相当于需求,微架构好比设计,物理实现则是具体的实现过程。 +比如我们规定指令集中必须有加法指令,这个指令你怎么设计、如何实现是你给的事,我只管给出两个加数你能给我输出一个正确的结果,简单来说就是抽象封装。 + + +
+ + Read More ~ +
+
+
+ + + +
+ + 上一页 + + + 下一页 + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/page/7/index.html b/page/7/index.html new file mode 100644 index 00000000..f5bb2e13 --- /dev/null +++ b/page/7/index.html @@ -0,0 +1,1558 @@ + + + + + + + + Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ + +
+

+ + 如何保证快速加载网页?——详解浏览器缓存机制 + +

+ +
+ + + + +
+ +
+ +参考内容: +彻底理解浏览器的缓存机制 +彻底弄懂HTTP缓存机制及原理 + +前端开发人员有大部分时间都在调整页面样式,如果页面没有按照自己预期的样式显示,可能想到的第一个解决方案就是清一下浏览器缓存,HTTP 缓存机制作为 Web 性能优化的重要手段,也应该是 Web 开发人员必备的基础知识。我们常说的浏览器缓存机制也就是 HTTP 缓存机制,它是根据 HTTP 报文的缓存标识运行的,所以首先要对 HTTP 报文有一个简单的了解。 +HTTP 报文 +HTTP 报文是浏览器和服务器间进行通信时所发的响应数据,所以 HTTP 报文分为请求(Request)报文和响应(Response)报文两种,浏览器向服务器发送的是请求报文,而服务器向浏览器发送的是响应报文。HTTP 请求报文由请求行、请求头、请求体组成,响应报文则由状态行、响应头、响应正文组成,与缓存有关的规则信息则都包含在请求头和响应头中。 +缓存概述 +浏览器与服务器通过请求响应模式来通信,当浏览器第一次向服务器发送请求并拿到结果后,会根据响应报文中的缓存规则来决定是否缓存结果,其简单的流程如下图: + +浏览器每次发起请求都会先在浏览器缓存中查找该请求的结果和缓存标识,而且每次拿到响应数据后都会将该结果和缓存标识存入缓存中。HTTP 缓存的规则有多种,我们可以根据是否需要重新向服务器发起请求这一维度来分类,即有强制缓存和协商缓存两类,也有人把协商缓存叫对比缓存。 +强制缓存 +我们先自己想一下,使用缓存是不是会有下面几种情况出现。 + + +存在所需缓存并且未失效:直接走本地缓存即可;强制缓存生效; + + +存在所需缓存但已失效:本地缓存失效,携带着缓存标识发起 HTTP 请求;强制缓存失效,使用协商缓存; + + +不存在所需缓存:直接向服务器发起 HTTP 请求;强制缓存失效。 + + +控制强制缓存的字段分别是Expires和Cache-Control,并且Cache-Control的优先级高于Expires。 +Expires +Expires是 HTTP/1.0 控制网页缓存的字段,其值为服务器返回的该缓存到期时间,即下一次请求时,请求时间小于Expires值,就直接使用缓存数据。到了 HTTP/1.1,Expires已经被Cache-Control替代了。 +Expires被替代的原因是因为服务端和客户端的时间可能有误差(比如时区不同或者客户端与服务端有一方时间不准确),这就会导致缓存命中误差,强制缓存就变得毫无意义。 +Cache-Control +Cache-Control是 HTTP/1.1 中最重要的规则,主要取值为: + + + +取值 +规则 + + + + +public +所有内容都可以被缓存,包括客户端和代理服务器,纯前端可认为与private一样。 + + +private +所有内容只有客户端可以缓存,Cache-Control的默认值。 + + +no-cache +客户端可以缓存,但是是否缓存需要与服务器协商决定(协商缓存) + + +no-store +所有内容都不会被缓存,既不是用强制缓存,也不使用协商缓存,为了速度快,实际上缓存越多越好,所以这个慎用 + + +max-age=xxx +缓存内容将在 xxx 秒后失效 + + + +我们可以看看下面这个例子,可以从截图中看到Expires是一个绝对值,而Cache-Control是一个相对值,此处为max-age=3600,即 1 小时后失效。在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control相比于Expires是更好的选择,所以同时存在时只有Cache-Control生效。 + +协商缓存 +协商缓存,顾名思义就是需要双方通过协商来判断是否可以使用缓存。强制缓存失效后,浏览器带着缓存标识向服务器发起请求,由服务器根据缓存标识决定是否可以使用缓存,那自然而然就有协商缓存生效和协商缓存不生效两种情况了。 + +上图是协商缓存生效的流程,如果协商缓存不生效则返回的状态码为 200。协商缓存的标识也是在响应报文的响应头中返回给浏览器的,控制协商缓存的字段有Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高,所以同时存在时只有Etag / If-None-Match生效。 +Last-Modified / If-Modified-Since +你可以往上翻一翻,看一下那张响应报文截图,其中有一个Last-Modified字段,它的值是该资源文件在服务器最后被修改的时间。 +If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值。服务器收到该请求后,发现该请求头有If-Modified-Since字段,则会将If-Modified-Since与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为 200;否则则返回 304,代表资源无更新,可继续使用缓存文件。 + +Etag / If-None-Match +Etag是服务器响应请求时,返回当前资源文件的一个由服务器生成的唯一标识。 +If-None-Match则是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,如果一致则就返回 304,代表资源无更新,可以继续使用缓存文件;否则重新返回资源文件,状态码为200, + +disk cache 与 memory cache +我们可以通过浏览器调试工具查看强制缓存是否生效,如下图所示,状态码为灰色的请求就代表使用了强制缓存,请求对应的 size 显示了该缓存存放的位置,那么什么时候用 disk 什么时候用 memory 呢? + +猜都能猜出来,肯定是优先使用内存(memory)中的缓存,然后才用硬盘(disk)中的缓存。 +内存缓存具有快速读取的特点,它会将编译解析后的文件直接存入该进程的内存中,但是一旦进程关闭了,该进程的内存就会被清空,所以如果你将一个网页关闭后再打开,那么缓存都会走硬盘缓存,而如果你只是刷新网页,那有部分缓存走的就是内存缓存。 +浏览器一般会再 js 和图片等文件解析执行后直接存入内存缓存中,当刷新页面时,这部分文件只需要从内存缓存中读取即可,而 css 文件则会存入硬盘中,所以每次渲染页面都需要从硬盘中读取文件。 +总结 +到这里偷懒一下子了,找到人家画的一张图,看图就行了。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 什么是契约测试? + +

+ +
+ + + + +
+ +
+ +参考文章: +聊一聊契约测试 —— ThoughtWorks洞见 +契约测试 +前后端分离了,然后呢? + +契约测试全称为:消费者驱动契约测试,最早由 Martin Fowler 提出。契约这个词从字面上很容易理解,就是双方(多方)达成的共同协议,那又为什么需要契约测试这个东西呢? +在当前微服务大行其道的行业背景下,越来越多的团队采用了前后端分离和微服务架构,我们知道微服务是由单一程序构成的小服务,与其它服务使用 HTTP API 进行通讯,服务可以采用不同的编程语言与数据库,微服务解决了单体应用团队协作开发成本高、系统高可用性差等等问题。 +但是微服务也引入了新的问题,假设 A 团队开发某服务并提供对应的 API,B 团队也在开发另一个服务,但是他们需要调用 A 团队的 API,为了产品的尽快发布,两个团队都争分夺秒,已经进入联调阶段了,然而出现了下面这样的尴尬情况。 + +随着越来越多的微服务加入,它们的调用关系开始变得越来越复杂,如果每次更改都需要和所有调用该 API 的团队协商,那沟通成本也未免太大了,试想下图的沟通成本。 + +为了保证 API 调用的准确性,我们会对外部系统的 API 进行测试,如果外部系统稳定性很差,或者请求时间很长的时候,就会导致我们的测试效率很低,当调用 API 失败时,你甚至无法确定是因为 API 被更改而导致的失败还是运行环境不稳定导致的失败。 +A 团队提供的 API 不稳定,肯定会导致 B 团队效率低下,为了不影响 B 团队的进度,所以构建了测试替身,通过模拟外部 API 的响应行为来增强测试的稳定性和反应速度。 + +但是这样做真的就解决问题了吗?当所有内部测试都通过时,能拍着胸脯说真正的外部 API 就一定没有变化?很简单的一个解决方案就是:部分测试使用测试替身,另一部分测试定期使用真实的外部 API,这样既保证了测试的运行效率、调用端的准确性,又能确保当真实外部系统API改变时能得到反馈。 + +感觉剧情到这里就差不多该结束了,实际上真正的高潮部分开刚刚开始。如果外部 API 的反馈周期很长,那增加真实 API 测试间隔时间就又回到了最初的起点。现在我们回顾一下上面的方案。 +在上面的场景中,我们都是已知外部 API 功能来编写相应的功能测试,并且使用直接调用外部 API 的方式来达到测试的目的,如此就不可避免的带来了两个问题: + +API 调用者(消费者)对服务提供方(生产者)的更改是通过对 API 的测试来感知的; +直接依赖于真实 API 的测试效果受限于 API 的稳定性和反映速度。 + +解决方案首先是依赖关系解耦,去掉直接对外部 API 的依赖,而是内部和外部系统都依赖于一个双方共同认可的约定—“契约”,并且约定内容的变化会被及时感知;其次,将系统之间的集成测试,转换为由契约生成的单元测试,例如通过契约描述的内容,构建测试替身。这样,同时契约替代外部 API 成为信息变更的载体。 +前后照应一下,我们现在再来看一下消费者驱动契约测试。它有两个不可或缺的角色:消费者是服务使用方;生产者(提供者)是服务提供方。采用需求驱动(消费者驱动)的思想。契约文件(比如 json 文件)由双方共同定义规范,一般由消费者生成,生产者根据这份契约去实现。 +契约测试其中一个的典型应用场景是内外部系统之间的测试,另一个典型的例子是前后端分离后的 API 测试。行业内比较成熟的解决方案是 Swagger Specification 和 Pact Specification,这里不做展开讨论。 +我们同样可以把契约测试的思想用到代码的编写中,契约测试通过一个契约文件来解耦依赖,那么对于需要用户定义很多规则的场景,我们同样可以将这些规则像契约文件一样抽取出来,这样就降低了代码之间的耦合度。 +最后敲敲黑板,契约测试不是替代 E2E 测试的终结者,更不是单元测试的升级换代,它更偏向于服务和服务之间的 API 测试,通过解耦服务依赖关系和单元测试来加快测试的运行效率。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 智齿|读研否|家庭小江湖|广告乱象 + +

+ +
+ + + + +
+ +
+ 建议读者大人们,如果自己经常一上火牙就疼,或者自己感觉牙已经有点问题了,可以提早预约医院的口腔科查一下,如果有问题早点预防,总是没有坏处的。抽烟的半年洗一次牙,不抽烟的一年洗一次牙。 +因为智齿发炎被狠狠的折磨了近两周,我属于比较能忍得疼痛的人,这不周末才进行了一场春季骑行,唯有美景与美食不可辜负,什么病痛都是浮云,但回想起连续几晚上疼到睡不着觉的滋味,昨天毅然决然斩草除根,给拔掉了,今天就感觉好了很多。 + +前几天考研成绩出来了,估计现在大部分同学都在准备复试,我没有体验过考研的这个过程,毕业这半年有时候还是会想,我也应该体验一下考研的那个过程,已经很久没有体验过把所有时间都投入到一件事情上的快乐了。 +但我还是不太建议读研,我这是无责任建议,毕竟自己没有读过研究生,在研究生实验室待了两年,算是有一半的硕士生经历吧。对于学历我一直以来的观点都是绝大部分人将它的作用放大了,总是认为名校成造就了强能力,而恰恰把因果关系给弄反了,是能力强的人都进了名校(本科)。当然,不否认像医学一类的专业是肯定要考研的,一棒子全打死肯定是不对的。 + +刘大发起的读书活动告一段落了,跟着小伙伴们泛泛的读了一遍《深入理解计算机系统》,只能用“痛并快乐着”来形容这个过程,每周输出一篇读书笔记,没有按时输出就罚钱的规矩很好,人还是需要自己逼自己才行。 +这本书不适合初学者阅读,在豆瓣上的评分接近 10 分,不讲究速成,而是一本内功心法,如果是您是码农的话,读一读绝对会提高一个层级。现在已经开启了另一本书籍的阅读计划,刘大这个活动组织的超好。 + +春节回家发现了一个巨大的变化,我老家那种贫困县地区的村民们,也在开始讨论保险这一类产品了,我是觉得这个改变太大了,说明农民伯伯的经济水平也有很大的提升了。另外通过朋友圈还发现,我认识的大佬们貌似出身都并不是多好,反倒是大部分普通朋友家里的矿更多。 +我们家族每年会组织祭祖活动,在正月初三一大家 50 人左右一同祭拜曾祖曾母,通过这么一个活动把整个大家族的年轻人联系起来,能搭建这样一个平台很棒,我正也在着手将家族信息数字化。 + +说到这里,想说一句家族群是个小江湖,亲戚之间也是暗暗较劲的,母亲不会抢群里几个特定的人发的红包,家里都是山路,车技不好的人很容易就寸步难行,一表叔就因为不到 10 米的距离,整个春节都在亲戚朋友面前抬不起头。 +堂弟现在是民航飞行员在读,而另一个表弟今年正值高考,说要去考炮兵学院,将来好把飞机打下来,这一下可好了,这些话全部伯父被截屏保留了,将来某一天要是这俩兄弟闹矛盾了,估计有的好看。保二爷写的家族群不是群,是江湖...看起来更有趣一点。 +在我身上更可悲的事情发生了,所有长辈一致认同应该由我来管理家族群,想想整个群里充斥的都是是那种要露不露、似露非露、就是不露的视频,或者是用粗糙都无法形容的大而泛的鸡汤文,整个头就大了,这可比解决技术问题难多了。 + +最近愈发觉得“大佬”之间的抄袭严重了,真大佬基本都是原创内容,或者是引用了别人的文字就标注出来,然而总是看到一些“大佬”原封不动发出来,还不表明出处,下面粉丝跟着继续做同样的事,我看到最多的一次是朋友圈连续 10 多条是一样的段子。 + +最后想无责任乱说一点科技相关的东西,5G 是当下的风口浪尖,各大厂商都希望在 5G 上有一席之地,搞芯片的搞芯片、做基站的做基站、整手机的整手机,5G + IPV6 肯定会带来无法想象的未来,5G 会大幅推动智能硬件的应用,但是手机这个应用场景是不是被夸大了呢? +现在的 4G 手机在线看一部高清电影不会有多卡顿的现象,广大吃瓜群众和各大媒体,一直都拿着 5G 手机来吹嘘,吃瓜群众跟随媒体引导的大流,我总觉得当拿到 5G 手机的那一刻,心里肯定会从喜悦急转失望的,就像目前的苹果产品一样。 + +
+ + Read More ~ +
+
+
+ +
+

+ + Bootstrap-table 如何合并相同单元格 + +

+ +
+ + + + +
+ +
+ Bootstrap-table 官方提供了合并单元格方法 mergeCells,它根据四个参数可以合并任意个单元格,我们要做的只是告诉它怎么合并。 +要合并同一列相同的单元格,无非两种办法,一种是一边遍历一边合并,遍历完了再合并。这里采用第二种办法,这里不需要遍历所有数据,因为用户只能看到当前页的数据,所以只遍历当前页的数据更省时间。 +下面是我实现的获取合并信息算法,最终返回的是一个哈希表,比如下面的这个表格,如果要对「性别」这一列进行合并,很明显前面两个“男”需要合并成一个单元格,再去看下 Bootstrap-table 提供的 API,它需要的是从哪个单元格开始,合并多少个单元格,也就是它需要的是两个数值类型的参数。 + + + +姓名 +性别 +年龄 + + + + +张三 +男 +23 + + +李四 +男 +19 + + +王二 +女 +20 + + +麻子 +男 +21 + + + +所以我把哈希表设置为,键存的是索引,值存的是从这个索引开始后面连续有多少个和它一样的单元格,那么上述表格性别这一列所得到的合并信息哈希表就为: +{ + 0: 2, + 2: 1, + 3: 1 +} + +下面算法很简单,使用两个指针遍历指定的列,如果两个指针所指向的数据相同,那么就将键所对应的值进行加一操作,整个方法只会对该列数据遍历一边,所以时间复杂度为 O(n)。 +let getMergeMap = function (data, index: number) { + let preMergeMap = {}; + // 第 0 项为表头,索引从 2 开始为了防止数组越界 + for (let i = 2; i &lt; data.length; i++) { + let preText = $(data[i-1]).find('td')[index].innerText; + let curText = $(data[i]).find('td')[index].innerText; + let key = i - 2; + preMergeMap[key] = 1; + while ((preText == curText) &amp;&amp; (i &lt; data.length-1)) { + preMergeMap[key] = parseInt(preMergeMap[key]) + 1; + i++; + preText = $(data[i - 1]).find('td')[index].innerText; + curText = $(data[i]).find('td')[index].innerText; + } + // while循环跳出后,数组最后一项没有判断 + if (preText == curText) { + preMergeMap[key] = parseInt(preMergeMap[key]) + 1; + } + } + return preMergeMap; +} + +上述算法得到了单列数据的合并信息,下一步就是按照这个信息进行相同单元格的合并了,因此封装了下面的方法按照指定哈希表进行合并。 +let mergeCells = function (preMergeMap: Object, target, fieldName: string) { + for (let prop in preMergeMap) { + let count = preMergeMap[prop]; + target.bootstrapTable('mergeCells', { index: parseInt(prop), field: fieldName, rowspan: count }); + } +} + +到目前为止,我们实现的都只是对单列数据进行合并,要实现对多列数据进行合并,那么只需要对所有列都进行相同的操作即可。 +export let mergeCellsByFields = function (data: Object[], target, fields) { + for (let i = 0; i &lt; fields.length; i++) { + let field = fields[i]; + // 保证 field 与 i 是相对应的 + let preMergeMap = getMergeMap(data, i); + let table = target.bootstrapTable(); + mergeCells(preMergeMap, table, field); + } +} + +因为我在程序中做了一点处理,保证了fields中每个值得索引与对应表头的索引是一样的,因此不需要额外传入索引信息。简单来说就是我所实现的表格会根据fields的顺序,实现列之间的动态排序。你需要注意的是这一点很可能和你不一样。 +到现在已经能够合并所有的列了,查看 Bootstrap-table 的配置信息发现,它有个属性是 onPostBody 它会在 table body 加载完成是触发,所以把这个属性配置成我们的合并单元格方法即可。 +// groups 为要合并的哪些列 +onPostBody: function () { + mergeCellsByFields($('#table' + ' tr'), $('#table'), groups); +} + +再说一点不太相关的,我实现的是让用户可以自己选可以合并多少列,即用了一个可多选的下拉列表框供用户选择,根据用户选择的数量去合并,所以传入了一个groups参数。 +最后推荐一个排序插件 thenBy,你可以用它进行多字段排序,比如用在合并相同单元格的场景,在绘制表格前先对数据进行排序,那么最后合并的结果就是把所有相同的数据聚合到一起了,并且还将它们合并到一起了,起到了一个隐形的过滤查询功能。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 坟头蹦迪|清理手机上的干扰信息|2018 简短总结 + +

+ +
+ + + + +
+ +
+ 前一周在一席上面看了一场演讲叫「不正经历史研究所所长」,整场演讲都伴随着欢声笑语,讲的是民间文化与经典文化,经典文化实际上是统治阶级强行推动的精致文化,绝大部分老百姓是看不懂这些东西的,反正我自己参观博物馆,如果没有讲解,我是完全看不懂那些玩意的。 +能和生活联系在一起的文化最有活力,比如天天发杨超越祈求不挂科,拜拜马云希望有更多的钱等等。有一句老话叫「人活一口气」,民间文化都基于此,我在别人面前要抬得起头,要的是气派、要的是大气,上个世纪的「四大件」应该就是成功的一个标配,老百姓置办这些物件后,在邻居面前也更能昂首挺胸了。 +昨天了解到一个朋友家乡的有一个非常奇怪的习俗,那就是「坟头蹦迪」,我最开始的想法是,这是不是最近兴起来的习俗,但是一问才知道,他小时候就是这样的了。基于我们的体系是无法理解坟头蹦迪的,你能说它们对于亲人的去世就不伤心吗?他们要的还是能抬起头,来参加追悼会的人们看到这么豪华的场面,第一反应是故人后代是很有成就的,在某种程度上这算是对故人的颂扬。 + +花了几个小时时间把微信清理了一下,只留下了 83 个公众号,这其中还包括一信用卡一类的服务号,也就是说只留下了 70 个左右的公众号。留下的都是小而美的公众号,这些大佬作者的更新频率也很低,比如子柳老师最近才更新了一篇文章,而上一篇文章的更新时间是 7 月 25 日。 +最近在自己身上认识的一个问题是,关注的公众号太多读不过来,虽然在此之前已经筛选掉了很多号,然而自己还是读不过来,所以索性就做了减法。现在像公众号这些,已经变成了获取信息的一个重要渠道,我也确实比周围小伙伴掌握信息更快,大部分时间也自认为比他们更了解真相。 +但是我最近突然有个疑问,我真的比他们更了解真相吗?大部分人是通过垃圾文章获取信息,我虽然没有跟着垃圾文章人云亦云,但是我还是通过网络获取信息的,那么我是不是看到的是另外一种看起来更接近真相的假象呢? +这些信息其实我就算不知道好像也没有什么大碍,顶多是别人谈什么新闻时,我所了解的也就他们知道的那么点而已,更好的方式是让自己的大脑去思考,以时间的维度去追踪事件的发展。 +顺便推荐一个应用叫「刷屏」,我发现自己通过「刷屏」、「知识星球」、「微信公众号」、「朋友圈」四个地方,已经掌握大部分信息了,经常同事给我说某某新闻,而我在两三天前已经见过了。 + +我对国际计量大会修改「1 千克」的定义这件事印象很深,但是这件事我只看到在阮一峰老师的文章中有被提到过;人们更喜欢听自己了解的领域,跟自己同好的很容易产生好感,一些明星结婚、出轨竟然能把微博的服务给搞挂了,说明绝大部分人还是更喜欢撩自己 G 点的信息。 + +另外第二个问题是自己现在比在学校时更喜欢玩手机了,在学校时还能做到不带手机去自习,现在是隔一会儿就看看手机,把本来就碎片化的时间变得更加碎片化了,这种效率导致工作、学习效率低下,所以把微信和 TIM 的通知全部关掉了。 + +写到这里发现有点点像总结,索性就给自己简单总结总结吧。2018 我从校园走向了社会,完成了从学生到职场的转变。最大的改变是思维的提升,知道去投资自己,在学生时代,愿意花几百块钱去买课程,这对我来说是很大的突破,和现在愿意花几百的概念完全不一样,想想那时候一个月生活费总共也没有多少,而我去看了下自己在毕业前花在这方面的钱居然有一千多,如果加上毕业后的开销,那就是两千多了,真感谢那时候的自己。 +这其中的收获是巨大的,后面我偶尔会向朋友推荐一些好的付费课程,但是他们都和大部分人一样,吃一顿饭花几百块钱,而如果花点钱买一堂课提升一下自己,就好像要他命一样,所以后面就不和他们讲了。 + +2018 开始用文字记录技术、生活、感悟,这其中的收获也是不小的,认识了此前只能仰望的大佬,结交了志同道合的朋友,而且也让自己更愿意去思考了。收到电子工业出版社的约稿合同,但是现在书都没写完,明年还写不完的话,那就不写了,主要是懒。 +自负的缺点已经在渐渐改变了,更加懂得了谦虚。眼界不再局限于技术,很多东西我都会去了解,也结交了很多有趣的人,初入职场,好好学习与不同的人沟通。以后如果有能力,希望能给山区带去一点点教育资源。 +2019 依旧坚持每周和同事或者校友打一次羽毛球,常去爬爬山,和有趣的朋友一起疯一疯;多读书,用豆瓣等工具去筛选好书,加大阅读量;常输出,输出是更高层次的输入;尝试去了解金融的逻辑;学习新的技术领域。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 2018 年个人总结 + +

+ +
+ + + + +
+ +
+ 这个总结写的还算认真,回头看,我真的运气很好,遇到了很多大佬,在我还未毕业时并没有嫌弃我傻,教我的不仅仅是技术,还有理财、为人处世,下面是我这一年的成长经历。 +有一段时间因为华为 34 岁以上员工被裁、中兴程序员跳楼等事件的发生,各种蹭热点讨论“中年危机”的文章漫天飞,那时我正忙于找工作。 +当时一个微信群里面大家各种讨论中年危机,都在给自己制造焦虑,刚好群里有个大神可能看大家过于焦虑,就在群里发了几条消息,教大家如何避免中年危机,并且推荐了两本书。 +还是学生的我下意识的就发了一个添加好友请求,庆幸的是他同意了我的好友请求,当然我们没有什么交流,我的问题过于浅显,我明白自己这个水平问问题,会浪费人家时间,当时的想法是看看大神的朋友圈,他平时都接触什么,自己学习一段时间。 +大神推荐的书是李笑来写的《把时间当做朋友》、《财富自由之路》,两本书在学校图书馆都没有,我就给学校图书馆荐购系统提交了这两本书,图书馆效率也挺高,不到一周就把书给买回来了,我立马就借回来阅读。 +书中的内容刷新了我以前狭隘的认知,自己从来没有像书中那样考虑问题,除了对作者的佩服之外,更多的是思考自己这种学生思维局限性太大了,要慢慢的将它摒弃。 +有个定律是你关心什么就会来什么,后面陆续碰到几位像大神一样的人士,并加了他们的微信,但是都仅仅是通过他们朋友圈的蛛丝马迹去找知识,通过他们朋友圈的分享内容,我知道了“简七理财”、“码农翻身”公众号,然后知道了《富爸爸穷爸爸》、《小狗钱钱》,于是我通过微信读书,读完了这两本书,逐渐培养了理财理念。 +后来没隔多久,简七出书了,我第一时间就买了她写的《好好赚钱》(同期还有刘大也出了《码农翻身》一书,我也第一时间买了),简七写的内容通俗易懂,很容易理解。 +刘大在群里开了几次公开课,作为计算机专业的我,被刘大对技术的理解之深给折服了,正是业界浮躁的时候,成千上万人想着人工智能、大数据、区块链,而刘大一直能沉下心来去了解技术的原理,这给了我一个很好的榜样,我也逐渐沉下心来,开始去补最基础的知识,像《深入理解计算机系统》一类书也能尽下心来慢慢去啃(当时没啃完,最近又在啃),这种不浮躁的特质对我的技术成长是很有帮助的。 +此后有一天,另一个大神在朋友圈分享了曹大写的《从校园到职场系列文章》,喜欢深入挖掘信息的我,以曹大公众号为源头,又找到了冯大、池大、二爷等人的公众号。 +作为自由的大四学生,因为不用担心第二天起不来,我那段时间经常熬夜阅读他们的文章,再阅读的过程中我也开始思考自己此前哪些想法狭隘,哪些品质又是值得继续保持的。 +也是那时开始接受知识付费的,那时候已经有小密圈(现在叫知识星球)了,出于对几位大佬的信任,我第一次大胆的花了几百块钱加入了刘大、曹大、冯大、程序员小灰的小密圈,其中的内容比网上蹭热点的文章好不知多少倍,一贯爱捕捉蛛丝马迹的我,又通过评论信息发现了 angela zhu、子柳老师、陈利人老师等,然后去找他们的文章,他们输出的内容要比水军写的文章好太多。 +自己也是从那时候开始坚持写文章记录自己的心得的,通过写文章,我认识了很多优秀的人,比如吴小龙同学、了不起的杰克、java 小咖秀等公众号的作者,和他们交流的很少,但是却很受用,他们的积极向上也影响着我一直保持着乐观豁达的心态。 +自己写的文章也被几个资深程序员赞同,同时还收到了两个出版社发来的出书邀请,让我体会到了无心插柳柳成荫的收获。 +让我坚持一直写文章的动力不是赚钱,而是我切切实实体会到了它给我个人带来的成长,为了自己日后再看时能立刻就找到清晰的逻辑,我把都尽可能把文章写得有理有据,掌握自己的节奏,尽量提高文章质量。此前写的谈一下写作的重要性一文有说写作可以带来的好处。 +现在已经不把自己当新人了,而且有同龄人甚至比我年龄还大的人向我咨询问题时,我也能给出合理建议,都得到了他们的肯定。最近发现和周围伙伴最明显的一个区别就是,对于同一个新闻,我经常早于他们半天甚至一两天知道,而且掌握的信息比他们还准确,我认为这就是整体认知水平的提升。 +想说的是,执行力与信息素养很重要,执行力强的人会与你拉开越来越大的距离,信息素养也是一个关键品质,现在网络上充斥着大量的虚假信息,如何去分别这些信息的真假,在相同条件下如何获得更多的有效信息,是必备的能力。 +当前年龄 23,刚大学毕业几个月,没读研。按十年为期给自己定了几个小小的目标: +父母是地地道道的农民,智能手机都不会用,十年之类给自己和父母把重疾险、意外险之类的保险配置齐全,虽然父辈一直反对买保险。 +提高获取信息的能力,虽然现在对信息的掌握都比周边伙伴要早半天至几天,但是都不是自己的分析结果,学习以时间的纬度跟踪事件的发展。 +学习理财知识,现在只对信用卡、基金有一点点的了解,不管炒不炒股,金融知识都还是要学的,这方面通过看书、阅读、小额实操学习。 +提升自己的技术实力,职业是程序员,前后端都做,但是自己对技术的热情不是多么高涨(至少比身边一半人要高涨),以我对自己的了解,我在技术的道路上成长为小公司一个的架构师应该不成问题,再高层级怕是不行。 +慢慢做到不止一份收入来源,这方面不是多清晰,现在每个月平均会有 200 左右的非工资收入(帮助别人时发的红包等),十年后做到其它收入基本和工资持平。不至于因为钱的问题而忍受心中的不快,至少得有能指着老板的鼻子说“老子不干了”的底气。 +世界那么大,应该去看看,国内除了西北地区,中国很多地方已经留下了我的足迹,旅游不仅仅是玩耍,更是提升见识、获得灵感的有效途径,十年至少得把自己的脚印印到 5 个国家的土地上吧。 +十年之后应该已经结婚了,房子是现在最遥不可及的目标,但是心里莫名有一股自信,这个后面会实现的,虽然不知道哪里来的这股自信。 +最后一个,趁年轻,多学习,做一个终身学习的人,时刻保持学习的态度,多做有利于他人的事,现在水平不高,我能帮助到的大部分都是硕士及以下。努力提高自己,帮助更多的人。更大的目标是能给山区学校带去一些更好的教育资源。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 匆匆的岁月——成都七中网校远端学生的高中生活 + +

+ +
+ + + + +
+ +
+ 周三刷星球时看到一条关于成都七中网校的信息,没想到周四就被成都七中网校给刷屏了,看到文章里面的描写,感悟颇多,网校陪伴了自己三年,一个诗意的女孩——西凉忆就是因为网校结识的。 +我是 12 年入学高中,那一年学校也刚和成都七中开通网校,因此我属于学校第一届全程使用网校资源的学生。记得刚入学时,对这种教学方式并不适应。不止学生不适应,老师也不适应,政治课老师就不使用直播课程,而是选择自己给我们讲。不过后来年级组出了硬性规定,特优班必须使用直播课程。 +就像教育的水平线中描述的一样,我们被称为远端学生,大家都是第一次使用这样的系统,七中老师估计也很好奇,都在第一节课或者第二节课上抽了远端同学回答问题,后来就很少有抽远端同学回答问题了,估计是因为远程视频效果不好还浪费时间。 +成都七中本部的学生上课很有激情,一到讨论等环节,虽然很吵但是却很有秩序,而我们这边基本是大家都盯着屏幕,等待对方讨论结束。 +对方学生的基础我们是没办法比的,大部分能够完全以纯英文交流,而我们远端学生大部分都只能说出“My name is ..., I'm from ..., I like ...”,英语的差距是最大的。我自己是主动找了英语老师谈话,和她做了一个约定,每周我写一篇作文交给她,她帮我批改,这样坚持了两年。 + +让我感受到最大差距的是,一个中学有自己的电视台,经常会有像《汉语桥》冠军一类的人物前来演讲,美国第一夫人米歇尔也到七中演讲(那是总统还是奥巴马),作为远端学生有幸蹭了一场名人演讲;七中学生的寒暑假是参加联合国模拟大会、到哈佛做了简短的交流、到华盛顿旅行...... +而大部分远端的学生,要么是参加学校组织的补课,要么是在家干农活,基本连县城都没有走出去过,和七中相比,完全是天壤之别。 +现在我依旧还清晰的记得七中的几个老师,做 PPT 从来没有背景图的数学老师,语速超快但又吐字清晰的化学老师,说着一口标准川普的物理老师,有着一头蓬松金黄卷发的历史老师(男)......去看这些老师的背景,也都是名校出身,武汉大学、华中师大等等。 + +有一个细节记得很清楚,在一堂数学课中有个题目,题目具体内容忘了,只记得是要求算新华中学的一本录取率,最后的答案是在 74% 左右,这个数字在我眼里很高了,但是那一刻并没有觉得有什么,毕竟这是书上的学校嘛!! +想不到的是,下一秒那个做 PPT 从来没有背景图的数学老师说了句:“这个就比较低了啊,我们学校考的很差的时候才是百分之七十多的录取率”。一下让我震惊了,因为我在心里算过自己学校的一本录取率,在此之前不到 10%,而且我所在高中在所有远端学校中还是比较靠前的。 + +让我意外的是,七中的老师原来也会骂人、打人,即使打的很轻;学生没完成作业,也会被罚拿个小凳子到教室外面补作业;在全国禁止补课的规定之下,七中也会给高三的学生补课,当然我们也同步补课了。 +我无法构想如果三年没有使用网校资源会是神马结果,如果仅仅是看数据的话,一本率是按倍数翻的,12 年开始使用网校资源后,学校有了第一个清华,13 年又添了一个清华。我属于 14 届,这届没有清华,最好的是浙大,我进了个普普通通的哈尔滨工程大学。据说 15 届又出了清华。 +我所在的高中也被央视作为典型案例探讨农村高考。无疑能上央视,那这个学校在当地乃至全国同等水平的学校中是很成功的。 +无疑这种生活给每个同学都留下了难忘的记忆,如果哪位同学加了七中本部直播班级某位同学的 QQ(那时没有人用微信),那能吹好几天牛逼,七中人在我们眼里就像明星一样。 +我们当地 14 届县状元高考分数是 635 分,七中本部平均分是 616 分,这差距至今都让我目瞪口呆。前段时间曹大在星球发起了一个作业:十年后的期望目标。我所提交的作业中写了句,如果有能力,希望能给乡村学校带去一点教育资源。 +我并不认为穷是光荣的事情,但在很多农村人眼里穷是一种资本,一种获取国家福利的资本,如果某次补助没有评到自己头上,那得记恨村长一辈子。我认为这才是造成农村孩子和城里孩子有巨大差距的原因,如果孩子没有从这种思维走出来,那一辈子也不可能有什么大的成就。 +没想到自己都大学毕业了,却看到成都七中的网校被刷屏了。毫无疑问,这是一件极为有意义的事情,这种内容就应该被刷屏,愿七中和远端学校都越办越好。 + +文中照片取自于同学 QQ 空间,记得我原来还有英语老师 Spring 的 QQ,高中时向他请教过自己的英语问题,太久没联系后来 Spring 把我删了 =_=,反正是没了。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 记录在南京大学半天 + +

+ +
+ + + + +
+ +
+ 因为工作需要,到南京出差了半个月,中间利用周末和最好的朋友疯了一天,之后自己又一个人到南京大学鼓楼校区逛了逛。 + +不会勾搭妹子的我总是能勾搭到老爷爷,到南大就勾搭了一个 86 岁高龄的老教授,他毕业于中山大学,年轻时候是做地质工作的。 +我就像个熊孩子一样要爷爷给我讲有趣的故事,要听他讲我们这一代人或者是大部分人都不知道的历史。 +爷爷虽然已经是快到耄耋之年的人了,但是对年轻时候的事记得很清楚,只是对最近的事记不起来。这篇文章仅仅是记录一下爷爷所讲的趣事。 +爷爷年轻时候接到中科院的任务,前往内蒙古考察。在考察期间他们用汽车压死过一只狼,而且当时吃了狼肉,一行 30 多个人都吃过那匹狼的心,但是没有吃过狗肺。 +据爷爷说,狼是很狡猾的动物,他们用汽车去追狼,狼就在原地不跑,等到你离它只有 10 来米的时候,突然拐弯跑了,这样的情况他们一共遇到了 6 次。这和《狼图腾》一书中的描写基本一致,狼有先进的军事文化。 + +爷爷告诉我,南大起源于金陵大学,南京大学的标志性建筑「北大楼」是个教堂的样子,金陵大学本来是个教会大学,现在的「北大楼」就是原来的「钟楼」。 +南大的地下有隧道,是当年毛主席提倡「深挖洞、广积粮、不称霸」时挖的,目的是为了防空。后来被南京食品公司用来存放香蕉,就是那种没有熟的香蕉,在隧道里面放熟了,再拿出来卖。不过现在隧道所有的口都没堵上了,完全废弃了。 +在南大,有一些楼中间有 5 层,然后到两遍就只有 3 层了,整体看来像是个三角形。实际上这些楼当年都是要修 8 层的,因为那时候没钱,建着建着发现没资金了,所以就封顶了。 +但是南大计算中心那栋楼只有 3 层却不是因为没钱,而是因为它旁边是消防大队,本来也是要建 8 层的,消防队说建高了挡住了他们视线,不能及时发现火情。爷爷笑着对我说:“但是也没见他们在上面拿个望远镜望啊!”。 +我们都知道「五四运动」,但是却很少有人知道「四五运动」,这个运动的起源就在南大,当时 300 多学生(我回来查资料说是 400)发起了这个运动,后来演变为全国性的运动,直接带动了半年后四人帮被粉碎。 +那是爷爷是个老师,他说他们教职工是很支持这些学生的,但是不敢公开性的支持。学生们很聪明,把标语刷到火车上,但是所有出南京的火车都被四人帮用水把标语给冲刷掉了,学生们就用沥青往火车上面写,才通过火车把这项运动的信息带到了全国各地。 + +我回来后查了一点资料,「四五运动」的起源是因为周恩来总理的去世,四人帮居然压制人民群众悼念周恩来,诬陷邓小平,而那时的毛主席也已经病到无法行动。 + + +人们把花圈都放到人民英雄纪念碑前悼念周总理,却被四人帮给清理了,北京广大人民群众在“还我花圈,还我战友”的口号下行成了天安门广场大规模的群众抗议运动。 + + +那也是一个诗意的年代,人们通过写诗来表达自己心中的愤怒,把小瓶子挂在树上,蕴意着期待邓小平的归来。那段时间四人帮应该是很难过的,从姚文元的日记就可以看出来。 + +爷爷还给我讲了一点他们的研究,他们研究行政规划的很多人认为,中国现在的行政划分有很多缺点的,中国应该划分 50~80 个省级单位。现在中国的行政级别也有问题,宪法规定行政层级只有三级(这一点我没查),而现在很多地方县下面是镇,镇下面还有乡,严格讲这是违宪的。 +快到午饭时间时,爷爷还教我写了一会儿字,有的简体字很难看,比如「龍飛鳳舞」用繁体字写出来很好看,但是用简体字写出来就特难看。要想练好毛笔字,把三个字写好了就行了,然而我现在只记得一个“飛”字了,这可能就是老师们常说的「你又还给我了」。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 为什么计算机处理排序数组比未排序数组快? + +

+ +
+ + + + +
+ +
+ 今天在群里看到一个有意思的问题——为什么处理排序数组比处理没有排序的数组要快,这个问题来源于 StackoverFlow,虽然我看到代码略微知道原因,但是模模糊糊不够清晰,搜了很多博客也讲的不够明白,所以就自己来总结了。 +首先来看一下问题,下面是很简单的一段代码,随机生成一些数字,对其中大于 128 的元素求和,记录并打印求和所用时间。 +import java.util.Arrays; +import java.util.Random; + +public class Main +{ + public static void main(String[] args) + { + // Generate data + int arraySize = 32768; + int data[] = new int[arraySize]; + + Random rnd = new Random(0); + for (int c = 0; c &lt; arraySize; ++c) + data[c] = rnd.nextInt() % 256; + + // !!! With this, the next loop runs faster + Arrays.sort(data); + + // Test + long start = System.nanoTime(); + long sum = 0; + + for (int i = 0; i &lt; 100000; ++i) + { + // Primary loop + for (int c = 0; c &lt; arraySize; ++c) + { + if (data[c] &gt;= 128) + sum += data[c]; + } + } + + System.out.println((System.nanoTime() - start) / 1000000000.0); + System.out.println(&quot;sum = &quot; + sum); + } +} + +我的运行结果:分别在对数组排序和不排序的前提下测试,在不排序时所用的时间比先排好序所用时间平均要多 10 ms。这不是巧合,而是必然的结果。 +问题就出在那个if判断上面,在旧文顺序、条件、循环语句的底层解释中其实已经提到了造成这种结果的原因,只是旧文中没有拿出具体的例子来说明。 +为了把这个问题搞明白,需要先对流水线有一定的了解。计算机是指令流驱动的,执行的是一个一个的指令,而执行一条指令,又要经过取指、译码、执行、访存、写回、更新六个阶段(不同的划分方式所包含的阶段不一样)。 +六个阶段使用的硬件基本是不一样的,如果一条指令执行完再去执行另一条指令,那么在这段时间里会有很多硬件处于空闲状态,要使计算机的速度变快,那么就不能让硬件停下来,所以有了流水线技术。 +流水线技术通过将指令重叠来实现几条指令并行处理,下图表示的是三阶段指令时序,即把一个指令分为三个阶段。在第一条指令的 B 阶段,A 阶段相关的硬件是空闲的,于是可以将第二条指令的 A 阶段提前操作。 + +很明显,这种设计大幅提高了指令运行的效率,聪明的你可能发现问题了,要是不知道下一条指令是什么怎么办,那提前的阶段也就白干了,那样流水线不就失效了?没错,这就是导致开篇问题的原因。 +让流水线出问题的情况有三种: + +数据相关,后一条指令需要用到前一条指令的运算结果; +控制相关,比如无条件跳转,跳转的地址需要在译码阶段才能知道,所以跳转之后已经被取出的指令流水就需要清空; +结构相关,由于一些指令需要的时钟周期长(比如浮点运算等),长时间占用硬件,导致之后的指令无法进入译码等阶段,即它们在争用同一套硬件。 + +代码中的if (data[c] &gt;= 128)翻译成机器语言就是跳转指令,处理器事先并不知道要跳转到哪个分支,那难道就等知道了才开始下一条指令的取指工作吗?处理器选择了假装知道会跳转到哪个分支(不是谦虚,是真的假装知道),如果猜中了是运气好,而没有猜中那就浪费一点时间重新来干。 +没有排序的数组,元素是随机排列的,每次data[c] &gt;= 128的结果也是随机的,前面的经验就不可参考,所以下一次执行到这里理论上还是会有 50% 的可能会猜错,猜错了肯定就需要花时间来修改犯下的错误,自然就会浪费更多的时间。 +对于排好序的数组,开始几次也需要靠猜,但是猜着猜着发现有规律啊,每次都是往同一个分支跳转,所以以后基本上每次都能猜中,当遍历到与 128 分界的地方,才会出现猜不中的情况,但是猜几次之后,发现这又有规律啊,每次都是朝着另外一个相同分支走的。 +虽然都会猜错,但是在排好序的情况下猜错的几率远远小于未排序时的几率,最终呈现的结果就是处理排序数组比未排序数组快,其原因就是流水线发生了大量的控制相关现象,下面通俗一点,加深一下理解。 + +远在他方心仪多年的姑娘突然告诉你,其实她也喜欢你,激动的你三天三夜睡不着觉,决定开车前往她的城市,要和她待在一起,但是要去的路上有很多很多岔路,你只能使用的某某地图导航,作为老司机并且怀着立马要见到爱人心情的你,开车超快,什么样罚单都不在乎了。 +地图定位已经跟不上你的速度了,为了尽快到达,遇到岔路你都是随机选一条路前进,遗憾的是,自己的选择不一定对(我们假设高速可以回退),走错路了就要重新回到分岔点,这就对应着未排序的情况。 +现在岔路是有规律的,告诉你开始一直朝着一边走,到某个地点后会一直朝着另一边走,你只需要花点时间去探索一下开始朝左边还是右边,到了中间哪个地点会改变方向就可以了,相比之下就能节省不少时间了,尽快见到自己的爱人,这对应着排好序的情况。 + +最后的故事改编自两个人的现实生活,一位是自己最好的朋友之一,谈恋爱开心的睡不着觉;另一位是微信上的一位好友,为了对方从北京裸辞飞到了深圳。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 顺序、条件、循环语句的底层解释(机器语言级解释) + +

+ +
+ + + + +
+ +
+ 初级程序员讲究的是术,只知道如何用关键字去拼凑出领导想要的功能;高级程序员脸的是道,理解了底层的逻辑,不仅把功能做的漂漂亮亮,心法也更上一层楼。 +顺序结构 +数据传送指令 +我们都清楚,绝大多数编译器都把汇编语言作为中间语言,把汇编语言程序变成可运行的二进制文件早就解决了,所以现在的高级语言基本上只需要把自己翻译成汇编语言就可以了。 +汇编指令总共只有那么多,大多数指令都是对数据进行操作,比如常见的数据传送指令mov。不难理解,被操作数据无非有三种形式,立即数,即用来表示常数值;寄存器,此时的数据即存放在指定寄存器中的内容;内存引用,它会根据计算出来的地址访问某个内存位置。 +需要注意的是,到了汇编层级,就不像高级语言那样随随便便int就能和long类型的数据相加减,他们在底层所占有的字节是不一样的,汇编指令是区分操作数据大小的,比如数据传送指令,就有下面这些品种(x86-64 对数据传送指令加了一条限制:两个操作数不能都指向内存位置)。 + +压栈与弹栈 +对于栈,我想不必多讲,IT 行业的同学都清楚,它是一种线性数据结构,其中的数据遵循“先进后出”原则,寄存器%rsp保存着栈顶元素的地址,即栈顶指针。一个程序要运行起来,离不开栈这种数据结构。 +栈使用最多的就是弹栈popq和压栈pushq操作。比如将一个四字值压入栈中,栈顶指针首先要减 8(栈向下增长),然后将值写到新的栈顶地址;而弹栈则需要先将栈顶数据读出,然后再将栈指针加 8。所以pushq和popq指令就可以表示为下面的形式。 +// 压栈 +subq $8, %rsp +movq %rbp, (%rsp) + +// 弹栈 +movq (%rsp), %rax +addq $8, %rsp + +其他还有算术、逻辑、加载有效地址、移位等等指令,可以查阅相关文档了解,不作过多介绍,汇编看起来确实枯燥乏味。 +条件结构 +前面讲的都是顺序结构,我们的程序中不可能只有顺序结构,条件结构是必不可缺的元素,那么汇编又是如何实现条件结构的呢? +首先你需要知道,除了整数寄存器,CPU 还维护着一组条件码寄存器,我们主要是了解如何把高级语言的条件结构转换为汇编语言,不去关注这些条件码寄存器,只需要知道汇编可以通过检测这些寄存器来执行条件分支指令。 +if-else 语句 +下面是 C 语言中的if-else语句的通用形式。 +if(test-expr){ + then-statement +}else{ + else-statement +} + +汇编语言通常会将上面的 C 语言模板转换为下面的控制流形式,只要使用条件跳转和无条件跳转,这种形式的控制流就可以和汇编代码一一对应,我们以 C 语言形式给出。 + t = test-expr; + if(!t){ + goto false; + } + then-statement; + goto done; +false: + else-statement; +done: + +但是这种条件控制转移形式的代码在现代处理器上可能会很低效。原因是它无法事先确定要跳转到哪个分支,我们的处理器通过流水线来获得高性能,流水线的要求就是事先明确要执行的指令顺序,而这种形式的代码只有当条件分支求值完成后,才能决定走哪一个分支。即使处理器采用了非常精密的分支预测逻辑,但是还是有错误预测的情况,一旦预测错误,那将会浪费 15 ~ 30 个时钟周期,导致性能下降。 + +在流水线中,把一条指令分为多个阶段,每个阶段只执行所需操作的一小部分,比如取指令、确定指令类型、读数据、运算、写数据以及更新程序计数器。流水线通过重叠连续指令的步骤来获得高性能,比如在取一条指令的同时,执行它前面指令的算术运算。所以如果事先不知道指令执行顺序,那么事先所做的预备工作就白干了。 + +为了提高性能,可以改写成使用条件数据传送的代码,比如下面的例子。 +v = test-expr ? then-expr : else-expr; + +// 使用条件数据传送方法 +v = then-expr; +ve = else-expr; +t = test-expr; +if(!t){ + v = ve; +} + +这样改写,就能提高程序的性能了,但是并不是所有的条件表达式都可以使用条件传送来编译,一般只有当两个表达式都很容易计算时,编译器才会采用条件数据传送的方式,大部分都还是使用条件控制转移方式编译。 +switch 语句 +switch语句可以根据一个整数索引值进行多重分支,在处理具有多种可能结果的测试时,这种语句特别有用。为了让switch的实现更加高效,使用了一种叫做跳转表的数据结构(Radis 也是用的跳表)。跳转表是一个数组,表项 i 是一个代码段的地址,当开关情况数量比较多的时候,就会使用跳转表。 +我们举个例子,还是采用 C 语言的形式表是控制流,要理解的是执行switch语句的关键步骤就是通过跳转表来访问代码的位置。 +void switch_eg(long x, long n, long *dest){ + long val = x; + switch(n){ + case 100: + val *= 13; + break; + case 102: + val += 10; + case 103: + val += 11; + break; + case 104: + case 105: + val *= val; + break; + default: + val = 0; + } + *dest = val; +} + +要注意的是,上面的代码中有的分支没有break,这种问题在笔试中会经常遇到,没有break会继续执行下面的语句,即变成了顺序执行。上面的代码会被翻译为下面这种控制流。 +void switch_eg(long x, long n, long *dest){ + static void *jt[7] = { + &amp;&amp;loc_A, &amp;&amp;loc_def, &amp;&amp;loc_B, + &amp;&amp;loc_C, &amp;&amp;loc_D, &amp;&amp;loc_def, + &amp;&amp;loc_D + }; + unsigned long index = n - 100; + long val; + if(index &gt; 6){ + goto loc_def; + } + goto *jt[index]; + loc_A: + val = x * 13; + goto done; + loc_B: + x = x + 10; + loc_C: + val = x + 11; + goto done; + loc_D: + val = x * x; + goto done; + loc_def: + val = 0; + done: + *dest = val; +} + +循环结构 +C 语言中有do-while、while和for三种循环结构,它们的通用形式一般都长下面那样。 +// do-while +do + body-statement + while(test-expr); + +// while +while(test-expr) + body-statement + +// for +for(init-expr; test-expr; update-expr) + body-statement + +do-while的特点是body-statement一定会执行一次,所以我们可以将do-while翻译成下面的控制流形式,很容易就能联想到它的汇编形式。 +loop: + body-statement; + t = test-expr; + if(t){ + goto loop; + } + +while循环我们给出两种形式的控制流,其中一种包含do-while形式,如下所示。 +// 第一种形式 +t = test-expr; +if(!t){ + goto done; +} +do + body-statement; + while(test-expr); +done: + + +// 第二种形式 + goto test; +loop: + body-statement; +test: + t = test-expr; + if(t){ + goto loop; + } + +面试的时候,有的面试官会问你for循环的执行顺序,现在深入理解了三种循环的机制,再也不怕面试官啦。for循环可以转换成如下的while形式。 +init-expr; +while(test-expr){ + body-statement; + update-expr; +} + +有了这种形式的for循环,我们只需要将其中的while部分再翻译一下就好了,前文给出了两种while翻译的方式,而具体采用哪种方式,取决于编译器优化的等级。 +总结 +计算机就是用那么几条简简单单的指令就完成了各种复杂的操作,不得不折服于计算机科学家们的魅力。现在人工智能被炒的很火热,然后人是事件、情感驱动的,而计算机是控制流驱动的,所以从架构上就决定了,冯诺依曼体系计算机实现的都是弱人工智能。 + +
+ + Read More ~ +
+
+
+ + + +
+ + 上一页 + + + 下一页 + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/page/8/index.html b/page/8/index.html new file mode 100644 index 00000000..9640a6bf --- /dev/null +++ b/page/8/index.html @@ -0,0 +1,1940 @@ + + + + + + + + Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ + +
+

+ + 深入理解计算机系统——函数调用与空间分配 + +

+ +
+ + + + +
+ +
+ 我们在编程序的时候,都会把某一个特定功能封装在一个函数里面,对外暴露一个接口,而隐藏了函数行为的具体实现,一个大型的复杂系统里面包含了很多这样的小函数,我们称之为过程。 +过程是相对独立的小模块,系统的运行需要这些过程的紧密合作,这种合作就是函数调用。 +在一个函数执行时调用别的函数,比如 P 调用 Q,需要执行一些特定的动作。传递控制,在调用 Q 之前,控制权在 P 的手里,既然要调用 Q,那么就需要把控制权交给 Q;传递数据,就是函数传参;分配与释放内存,在开始时,Q 可能需要位局部变量分配空间,结束时又必须释放这些存储空间。 +大多数语言都使用栈提供的先进后出机制来管理内存,x86-64 可以通过通用寄存器传递最多 6 个整数值(整数或地址),如果超过 6 个,那就需要在栈中分配内存,并且通过栈传递参数时,所有数据的大小都要向 8 的倍数对齐。将控制权从 P 转交给 Q,只需要将 PC(程序计数器)的值置为 Q 代码的起始位置,并记录好 P 执行的位置,方便 Q 执行完了,继续执行 P 剩余的代码。 +在函数的传参、执行中,多多少少都需要空间来保存变量,局部数据能保存在寄存器中就会保存在寄存器中,如果寄存器不够,将会保存在内存中。除了寄存器不够用的情况,还有数组、结构体和地址等局部变量都必须保存在内存中。分配内存很简单,只需要减小栈指针的值就行了,同样释放也只需要增加栈指针。 +在函数执行过程中,处理栈指针%rsp,其它寄存器都被分类为被调用者保存寄存器,即当过程 P 调用过程 Q 时,Q 必须保存这些寄存器的值,保证它们的值在 Q 返回到 P 时与 Q 被调用时是一样的。 +所以递归也就不难理解了,初学算法总觉得递归有点奇妙,怎么自己调用自己,而实际上对于计算机来说,它和调用其它函数没什么区别,在计算机眼里,没有自身与其它函数的区别,所有被调用者都是其它人。 +数组是编程中不可或缺的一种结构,“数组是分配在连续的内存中”这句话已经烂熟于心了,历史上,C 语言只支持大小在编译时就能确定的多维数组,这个多多少少有一些不便利,所以在ISO C99标准中就引入了新的功能,允许数组的维度是表达式。 +int A[expr1][expr2] + +因为数组是连续的内存,所以很容易就能访问到指定位置的元素,它通过首地址加上偏移量即可计算出对应元素的地址,这个偏移量一定意义上就是由索引给出。 +比如现在有一个数组A,那么A[i]就等同于表达式* (A + i),这是一个指针运算。C 语言的一大特性就是指针,既是优点也是难点,单操作符&amp;和*可以产生指针和简介引用指针,也就是,对于一个表示某个对象的表达式expr,&amp;expr给出该对象地址的一个指针,而对于一个表示地址的表达式Aexpr,*Aexpr给出该地址的值。 +即使我们创建嵌套(多维)数组,上面的一般原则也是成立的,比如下面的例子。 +int A[5][3]; + +// 上面声明等价于下面 +typedef int row3_t[3]; +row3_t A[5]; + +这个数组在内存的中就是下面那个样子的。 + +还有一个重要的概念叫做数据对齐,即很多计算机系统要求某种类型的对象的地址必须是某个值 K(一般是2、4 或 8)的倍数,这种限制简化了处理器和内存接口之间的设计,甚至有的系统没有进行数据对齐,程序就无法正常运行。 +比如现在有一个如下的结构体。 +struct S1 { + int i; + char c; + int j; +} + +如果编译器用最小的 9 字节分配,那么将是下面的这个样子。 + +但是上面这种结构无法满足 i 和 j 的 4 字节对齐要求,所以编译器会在 c 和 j 之间插入 3 个字节的间隙。 + +在极客时间专栏中有这样一段代码。 +int main(int argc, char *argv[]){ + int i = 0; + int arr[3] = {0}; + for(; i &lt;= 3; i++){ + arr[i] = 0; + printf(&quot;Hello world!\n&quot;); + } + return 0; +} + +这段代码神奇的是在某种情况下会一直循环的输出Hello world,并不会结束,在计算机系统漫游(补充)中也提到过。 +造成上面这种结果是因为函数体内的局部变量存在栈中,并且是连续压栈,而 Linux 中栈又是从高向低增长。数组arr中是 3 个元素,加上 i 是 4 个元素,刚好满足 8 字节对齐(编译器 64 位系统下默认会 8 字节对齐),变量i在数组arr之前,即i的地址与arr相邻且比它大。 +代码中很明显访问数组时越界了,当i为 3 时,实际上正好访问到变量i的地址,而循环体中又有一句arr[i] = 0;,即又把i的值设置为了 0,由此就导致了死循环。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 认知如何提升?我是怎样转变自己思维方式的 + +

+ +
+ + + + +
+ +
+ 这仅仅是记录自己的一点经历和感悟,回顾一下自己思维的转变过程而已,如果对于还是学生或是初入职场的你有一点帮助,那也是没白写的。 +相信很多朋友都还记得有段时间因为华为 34 岁以上员工被裁、中兴程序员跳楼等事件的发生,各种蹭热点讨论「中年危机」的文章漫天飞,一时间各种割韭菜教如何利用副业赚钱的课程也层出不穷,那时我正好是大四,忙于找工作。 +很清晰的记得当时一个程序员微信群里面大家各种讨论中年危机,都在给自己制造焦虑,刚好群里有个大神可能出于善意,看大家过于焦虑,就在群里发了几条消息,教大家如何避免中年危机,并且推荐了两本书。 +还是学生的我下意识的就发了一个添加好友请求,虽然内心是非常希望对方能够通过好友请求的,但当时很清楚能和这样的人微信交流是一种奢求,而意外的是他居然同意了我的好友请求,当然限于我个人的水平我们没有什么交流,我的问题过于浅显,会浪费人家时间,当时的想法是看看大神的朋友圈,他平时都接触什么,自己学习一段时间。 +大神推荐的书是李笑来写的《把时间当做朋友》、《财富自由之路》,那时的心态还是宁愿花 300 块钱出去吃一顿饭,也不愿意花几十块钱买一本书,所以我第一时间跑到学校图书馆去查了,但是两本书在学校图书馆都没有,我就给学校图书馆荐购系统提交了这两本书,图书馆效率也挺高,不到一周就把书给买回来了,我立马就借回来阅读。 +书中的内容刷新了我以前狭隘的认知,从偏远农村出来的自己从来没有像书中那样考虑问题,除了对作者的佩服之外,更多的是思考自己这种学生思维局限性太大了,要慢慢的将它摒弃。 +有个定律是你关心什么就会来什么,后面陆续碰到几位像大一样的人士,并加了他们的微信,但是都仅仅是通过他们朋友圈的蛛丝马迹去找知识,通过他们朋友圈的分享内容,我知道了「简七理财」、「码农翻身」、「程序员小灰」(我知道的时候还不叫程序员小灰)公众号,然后知道了《富爸爸穷爸爸》、《小狗钱钱》,通过微信读书,读完了这两本书,逐渐培养了理财理念。 +后来没隔多久,简七出书了,我第一时间就买了她写的《好好赚钱》(同期还有刘大也出了《码农翻身》一书,我也第一时间买了),简七写的内容通俗易懂,很容易理解。 +刘大在群里开了几次公开课,作为计算机专业的我,被刘大对技术的理解之深给折服了,那段时间正是业界浮躁的时候,成千上万人想着人工智能、大数据、区块链,而刘大一直能沉下心来去了解技术的原理,这给了我一个很好的榜样,我也逐渐沉下心来,开始去补最基础的知识,像《深入理解计算机系统》一类书也能尽下心来慢慢去把它啃完了,这种不浮躁的特质对我的技术成长是很有帮助的。 +此后有一天,另一个大神在朋友圈分享了曹大写的《从校园到职场系列文章》,喜欢深入挖掘信息的我,以曹大公众号为源头,又找到了冯大、池大、二爷、刘备教授、大牛猫等人的公众号。 +作为自由的大四学生,因为不用担心第二天起不来,我那段时间经常熬夜阅读他们的文章,在阅读的过程中我也开始思考自己此前哪些想法狭隘,哪些品质又是值得继续保持的。 +也是那时我开始接受知识付费的,那时候已经有小密圈(现在叫知识星球)了,出于对几位大佬的信任,我第一次大胆的花了几百块钱加入了刘大、曹大、冯大、程序员小灰的小密圈,其中的内容比网上蹭热点的文章好不知多少倍,一贯爱捕捉蛛丝马迹的我,又通过评论信息发现了 angela zhu、子柳老师、陈利人老师等,然后去找他们的文章,他们输出的内容要比水军写的文章好太多。 +自己也是从那时候开始坚持写文章记录自己的心得的,通过写文章,也认识了很多优秀的人,比如了不起的杰克、java 小咖秀等公众号的作者,和他们交流的很少,但是却很受用,他们的积极向上也影响着我一直保持着乐观豁达的心态。 +自己写的文章也被几个资深程序员赞同,同时还收到了两个出版社发来的出书邀请,让我体会到了无心插柳柳成荫的收获,选择了和电子工业出版社签了出版合同。 +让我坚持一直写文章的动力不是赚钱,而是我切切实实体会到了它给我个人带来的成长,为了自己日后再看时能立刻就找到清晰的逻辑,我把都尽可能把文章写得有理有据,掌握自己的节奏,尽量提高文章质量。谈一下写作的重要性一文有说写作带来的好处。 +现在已经不把自己当新人了,而且有同龄人甚至比我年龄还大的人向我咨询问题时,我也能给出合理建议,都得到了他们的肯定。最近发现和周围伙伴最明显的一个区别就是,对于同一个新闻,我经常早于他们半天甚至一两天知道,而且掌握的信息比他们还准确,我认为这就是整体认知水平的提升。 +最后想说,执行力与信息素养很重要,执行力强的人会与你拉开越来越大的距离,信息素养也是一个关键品质,现在网络上充斥着大量的虚假信息,如何去分别这些信息的真假,在相同条件下如何获得更多的有效信息,是必备的能力。 +上面提到的各路大神,他们的文章都很值得阅读,做一个终身学习的人,时刻保持学习的态度。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 深入理解计算机系统——信息的表示与处理 + +

+ +
+ + + + +
+ +
+ +参考内容: +《深入理解计算机系统》(第三版) + +字数据大小 +前面已经提到过信息=位+上下文,但是基本上的计算机都没有将位作为最小的可寻址单位,而是将字节作为了最小的可寻址单位,内存就是一个非常大的字节数组,它的的每个字节都由一个唯一的数字来标识(这个数字是不需要存的),所有可能的地址集合就是虚拟地址空间。 +我们常说的 32 位、64 位指的是一台计算机的字长,用于指明指针数据的的标称大小。有的面试官在面试的时候会问这样一个问题:在 C/C++ 中指针的大小是多少?如果你一下就回答出来时多少个字节了,那基本上不必再问了,因为一个指针的大小取决于计算机的字长,所以应该分 32 位机还是 64 位机的情况。 +字长还会决定一个极为重要的系统参数——虚拟地址空间。比如现在有一个 32 位机,每一位可以取值 1 或 总共 32 位,能组合的出局就有 232 个,所以它能访问 232 个地址,其大小也就是 4G,因此你如果给 32 位机装上 8G 的内存条,是起不了多大作用的。 +我们平时所说的 32 位程序和 64 位程序并不是指机器的字长,它们的区别在于程序时如何编译的,而不是其运行的机器类型,高版本都应该做到向后兼容,所以 32 位程序一般都能运行在 64 位机器上,而 64 位程序时不能运行在 32 位机上面的。下面两种伪指令就分别用于编译 32 位程序和 64 位程序。 +gcc -m32 prog.c +gcc -m64 prog.c + +C 语言在 32 位机和 64 位机上所表现的差别在于long数据类型,一般在 32 位机上是 4 个字节,而在 64 位机上是 8 个字节,而作为程序员要力图程序能在不同的机器上进行编译执行,要做到这一点就需要保证程序对不同数据类型的确切大小不敏感。 + +曾经某运营商的一个基站版本因为数据范围的不同而造成了巨大的损失,在编程环境中使用的是 32 位机,而基站所使用的处理器没有 32 位,最后表现的效果就是大概每隔 40 天,基站就自动复位了。定位到这个问题都花费了巨大的财力和人力资源。 + +寻址及字节顺序 +上文已经提到,有很多的对象实际上不止占用一个字节,而是占用了多个字节,此时就涉及到如何排列这些字节了,以及如何存储这些字节。以11001100 11001100为例,它占用了两个字节,我们可以选择将这两个字节放在连续的内存中,也可以将两个字节分开放在不连续的内存中;另外我们可以将左边的字节当做起始位置,也可以将右边的字节当做起始位置(更专业的称为大端法和小端法)。 +对于字节的排列,到底是用大端法还是小端法,没有技术上的争论,只有社会政治论题的争论,而且机器它对程序员是完全不可见的。几乎所有的机器都将多字节对象存储为连续的字节序列,所使用字节中最小的地址作为对象的地址。 +那么什么时候需要注意字节的顺序规则呢,那就是编写网络应用程序的时候,试想你传输的数据是用大端法表示的,而用户的计算机采用的是小端法,那还会有用户使用你的产品吗。所以编写网络程序时需要遵循已经建立的关于字节顺序的规则。 +整数表示 +程序员对二进制不会不知道,比如 11111111表示的是 255(不考虑补码),很容易就能转换为我们所熟悉的 10 进制数据。这种方式我们默认它是无符号数,如果要加入有符号数就开始变得有趣了。 +几乎所有的计算机都是采用有补码来表示有符号整数的,它与无符号整数的区别在于最高位被解释为负权,举个例子:将1111看做补码的话,它的值就为:-23 + 22 + 21 + 20 = -1。 +在程序中不可避免的会使用强制类型转换,C 语言中强制类型转换并没有改变数据的位值,只是改变了解释这些位的方式。比如将无符号数(unsigned) 53191 转换为有符号数的结果为 -12345,它们的位值是完全没有相同的。 +最容易入坑的地方是,对两个不同类型的数据进行运算时,C 语言将会隐式的将有符号数转换为无符号数,所以就有下面这样一个神奇的结果。 +// u 代表无符号数 +-1 &lt; 0u +// 结果为 0 +// 因为 -1 的补码表示为:11...11 +// 转换为无符号数后就是范围内最大的数 + +如果需要扩展一个数的位表示,那么放心的扩展就好了,小的数据类型都能安全的向大的数据类型转换,补码表示的数会在前面补上符号位,原码表示的直接在前面补上 0 即可,而需要注意的是从大往小转,这会不可避免的截断位,造成信息的丢失,所以千万不要这么干。 +加法、乘法运算 +在编程入门的时候可能都知道两个正数相加的结果可能为负数,还有一个更奇怪的现象就是:x &lt; y和 x - y &lt; 0两个表达式可能会得出不一样的结果,这些神奇的结果都和计算机整数的底层表示和运算有着密切的关系。 +C 语言中有无符号数与有符号数之分,而在 Java 中只有有符号数,下面的内容还是基于 C 语言进行说明,毕竟更 C 比 Java 更接近底层嘛。 +无符号加法 +假设我们使用 w 位来表示无符号数,那么两个加数取值范围即为:0 ≤ x, y <2w,理论上它们的和的范围为:0 ≤ sum < 2w+1,因为只有 w 位表示无符号数(要把和表示出来就需要 w+1 位),所以超过 zw的部分就会造成溢出,如下图所示。 + +对于无符号数的溢出,计算机采用的处理方式是丢掉最高位,直观的结果就是,当发生溢出了,就将采用取模运算(或者说是减去 2w),举个例子。 +只用 4 为来表示无符号数,即 w = 4,现在有 x [1001] 和 y [1100] 相加,其结果应为:[10101] ,但是没有 5 位用来表示,所以丢掉最高位的1,剩下的值为 5 [0101],也就是 21 mod 16 = 5。 +那么如何检测是否发生溢出呢?设求和结果为 s,对于加法有 x + y ≥ x 恒成立,即只要没有发生溢出,肯定有 s ≥ x。另一方面,如果确实发生溢出了,就有 s = x + y - 2w,又有 y - 2w < 0,因此 s = x + y - 2w < x。 +补码加法 +和前面一样,对于两个给定范围的加数 - 2w-1 ≤ x, y ≤ 2w-1 - 1,它们的和的范围就在 - 2w ≤ sum ≤ 2w - 2。要想把整个和的范围表示出来,依旧需要 w+1 位才行,而现在只有 w 位,因此还是需要采用将溢出部分截断。 + +可以发现,当发生正溢出时,截断的结果是从和数中减去了 2w;而当发生负溢出时,截断结果是把和数加上 2w。 +那么对于补码加法如何检测溢出结果呢?通过分析可以发现,当且仅当 x > 0, y > 0,但和 s ≤ 0 时为正溢出;当且仅当 x < 0, y < 0,但 s ≥ 0 时发生负溢出。 +无符号乘法 +有了前面的基础,乘法就变得简单一些了,对于溢出情况,计算机仍然采用的是求模,比如 0 ≤ x, y ≤ 2w - 1,它们乘积的范围为 0 到 22w - 2w+1 + 1 之间,这可能需要 2w 位来表示,溢出部分直接截掉,如下所示。 + +补码乘法 +对于补码,两个乘数的范围为:- 2w-1 ≤ x, y ≤ 2w-1 + 1,那么其乘积表示范围就为 - 22w-2 + 2w-1 到 22w-2 之间,补码乘法和无符号乘法基本是一样的,只是在无符号基础上多加了一步转换,即将无符号数转换为补码。 + +乘以常数 +我们知道,计算机做加减法、位级运算的速度最快(1 个指令周期),而做乘除法很慢(10 个甚至更多指令周期),平时编写的程序中常常会乘以一个常数,为了使程序运行的更快,编译器可能会帮我们做一些处理。 +首先我们考虑常数是 2 的幂。x * 21 可以表示为 x &lt;&lt; 1,x * 22 可以表示为 x &lt;&lt; 2,依次类推即可。 +对于不是 2 的幂的常数,比如 x * 14 可以表示为:(x&lt;&lt;3) + (x&lt;&lt;2) + (x&lt;&lt;1),因为 14 = 23 + 22 + 21;聪明的你可能发现 14 还有另一种表示方法,即 14 = 24 - 21,这种表示比前一种表示方法又少了运算量,所以 x * 14 还可以表示为:(x&lt;&lt;4) - (x&lt;&lt;1)。 +实际上,这里有一个通用的解决方案,对于任何一个常数 K,其二进制可以表示为一组 0 和 1 交替的序列:[(0...0)(1...1)(0...0)(1...1)],14可以表示为:[(0...0)(111)(0)],考虑一组从位位置 n 到位位置 m 的连续的 1 (n ≥ m),(对于 14 有 n = 3,m = 1)可以有两种形式来计算位对乘积的影响。 + +这个优化不是一定的,大多数编译器只在需要少量移位、加减法就足够的时候才使用这种优化。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 使用订阅号实现微信公众号历史文章爬虫 + +

+ +
+ + + + +
+ +
+ 微信公众号已经成为生活的一部分了,虽然里面有很多作者只是为了蹭热点,撩读者的 G 点,自己从中获得一些收益;但是不乏好的订阅号,像刘大的码农翻身、Fenng的小道消息、曹大的caoz的梦呓等订阅号非常值得阅读。 +平时有时候看到一些好的公众号,也会不自觉去查看该公众号的历史文章,然而每次都看不完,下一次再从微信里面打开历史文章,又需要从头翻起。而且对于写了很多年的大号,每次还翻不到底。有一些平台提供了相关的服务,但是得收几十块钱的费用,倒不是缺几十块钱,主要是觉得这种没必要花的钱不值得去浪费。 +网上搜如何爬微信公众号历史文章,大致给了三种思路,第一是使用搜狗微信搜索文章,但是好像每次能搜到的不多;第二是使用抓包工具;第三种是使用个人订阅号进行抓取。 +简单来说就是使用程序来模拟人的操作,抓取公众号历史文章。首先登录微信公众号个人平台,期间需要管理员扫码才能登录成功。 +def __open_gzh(self): + self.driver.get(BASE_URL) + self.driver.maximize_window() + username_element = self.driver.find_element_by_name(&quot;account&quot;) + password_element = self.driver.find_element_by_name(&quot;password&quot;) + login_btn = self.driver.find_element_by_class_name(&quot;btn_login&quot;) + username_element.send_keys(USERNAME) + password_element.send_keys(PASSWORD) + login_btn.click() + WebDriverWait(driver=self.driver, timeout=200).until( + ec.url_contains(&quot;cgi-bin/home?t=home/index&quot;) + ) + # 一定要设置这一步,不然公众平台菜单栏不会自动展开 + self.driver.maximize_window() + +进入微信公众平台首页后,点击素材管理,然后点击新建图文素材,就会进入到文章写作页面,此时前面打开的微信公众平台首页就不需要了,可以将其关闭。 + +def __open_write_page(self): + management = self.driver.find_element_by_class_name(&quot;weui-desktop-menu_management&quot;) + material_manage = management.find_element_by_css_selector(&quot;a[title='素材管理']&quot;) + material_manage.click() + new_material = self.driver.find_element_by_class_name(&quot;weui-desktop-btn_main&quot;) + new_material.click() + # 关闭公众平台首页 + handles = self.driver.window_handles + self.driver.close() + self.driver.switch_to_window(handles[1]) + +在文章写作页面的工具栏上面有一个超链接按钮,点击超链接即会弹出超链接编辑框,选择查找文章,输入自己喜欢的公众号进行查找,一般第一个就是自己想要的结果,点击对应的公众号,该公众号所有的文章就会通过列表的形式展现出来。 + + + +def __open_official_list(self): + # 超链接 + link_click = self.driver.find_element_by_class_name(&quot;edui-for-link&quot;) + link_click.click() + time.sleep(3) + # 查找文章 + radio = self.driver.find_element_by_class_name(&quot;frm_vertical_lh&quot;).find_elements_by_tag_name(&quot;label&quot;)[1] + radio.click() + # 输入查找关键字 + search_input = self.driver.find_element_by_class_name(&quot;js_acc_search_input&quot;) + search_input.send_keys(OFFICIAL_ACCOUNT) + search_btn = self.driver.find_element_by_class_name(&quot;js_acc_search_btn&quot;) + search_btn.click() + # 等待5秒,待公众号列表加载完毕 + time.sleep(5) + result_list = self.driver.find_element_by_class_name(&quot;js_acc_list&quot;).find_elements_by_tag_name(&quot;div&quot;) + result_list[0].click() + +文章列表已经展现出来了,直接抓取每条文章超链接的信息即可,每抓取完一页就进入下一页,继续抓取文章列表信息,直到所有文章信息都抓取完毕。 + +def __get_article_list(self): + # 等待文章列表加载 + time.sleep(5) + total_page = self.driver.find_element_by_class_name(&quot;search_article_result&quot;)\ + .find_element_by_class_name(&quot;js_article_pagebar&quot;).find_element_by_class_name(&quot;page_nav_area&quot;)\ + .find_element_by_class_name(&quot;page_num&quot;)\ + .find_elements_by_tag_name(&quot;label&quot;)[1].text + total_page = int(total_page) + articles = [] + for i in range(0, total_page-1): + time.sleep(5) + next_page = self.driver.find_element_by_class_name(&quot;search_article_result&quot;)\ + .find_element_by_class_name(&quot;js_article_pagebar&quot;).find_element_by_class_name(&quot;pagination&quot;)\ + .find_element_by_class_name(&quot;page_nav_area&quot;).find_element_by_class_name(&quot;page_next&quot;) + article_list = self.driver.find_element_by_class_name(&quot;js_article_list&quot;)\ + .find_element_by_class_name(&quot; my_link_list&quot;).find_elements_by_tag_name(&quot;li&quot;) + for article in article_list: + article_info = { + &quot;date&quot;: article.find_element_by_class_name(&quot;date&quot;).text, + &quot;title&quot;: article.find_element_by_tag_name(&quot;a&quot;).text, + &quot;link&quot;: article.find_element_by_tag_name(&quot;a&quot;).get_attribute(&quot;href&quot;) + } + articles.append(article_info) + next_page.click() + return articles + +至此,微信公众号历史文章的爬虫已经实现,其实整个过程只不过是用程序来模拟的了人类的操作。需要注意的是,程序不能设置太快,因为微信做了相关限制,所以设太快会在一段时间内无法使用文章查找功能;另外一点是使用选择器选择页面元素的时候,会有一些坑,而且我发现不同账号登录,有很少部分的页面元素虽然直观上是一样的,但是它的 html 代码有细微的差别。 +这个小程序会用到selenium库,和chromedriver,前者直接pip install即可,后者自行下载;另外你还需要一个订阅号才行,本文只实现了关键的文章信息抓取,并没有进行文章信息的持久化存储,完整代码在这里。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 磁盘到底是怎样工作的?一文理解硬盘结构 + +

+ +
+ + + + +
+ +
+ 数据库系统总会涉及到辅助存储(大多都是磁盘),因为它们能够存储大量需要长期保存的数据,因此我们有必要先了解了解磁盘的相关知识。 +根据机械原理,存储器的容量越大其速度就越慢。但是速度越快的存储器,其单位字节的价格就越贵。现代计算机系统可以包含几个不同的可以存储数据的部件,就形成了存储器的层次结构,但是需要注意的是「虚拟内存」是操作系统与操作系统运用机器硬件的产物,它不是存储器的层次之一。 +磁盘结构 +传统的硬盘盘结构是像下面这个样子的,它有一个或多个盘片,用于存储数据。盘片多采用铝合金材料;中间有一个主轴,所有的盘片都绕着这个主轴转动。一个组合臂上面有多个磁头臂,每个磁头臂上面都有一个磁头,负责读写数据。 + +磁盘一般有一个或多个盘片。每个盘片可以有两面,即第一个盘片的正面为0面,反面为 1 面;第二个盘片的正面为 2 面......依次类推。磁头的编号也和盘面的编号是一样的,因此有多少个盘面就有多少个磁头。盘面正视图如下图,磁头的传动臂只能在盘片的内外磁道之间移动。因此不管开机还是关机,磁头总是在盘片上面。关机时,磁头停在盘片上面,抖动容易划伤盘面造成数据损失,为了避免这样的情况,所以磁头都是停留在起停区的,起停区是没有数据的。 + +每个盘片的盘面被划分成多个狭窄的同心圆环,数据就存储在这样的同心圆环上面,我们将这样的圆环称为磁道 (Track)。每个盘面可以划分多个磁道,最外圈的磁道是0号磁道,向圆心增长依次为1磁道、2磁道......磁盘的数据存放就是从最外圈开始的。 + +根据硬盘的规格不同,磁道数可以从几百到成千上万不等。每个磁道可以存储数 Kb 的数据,但是计算机不必要每次都读写这么多数据。因此,再把每个磁道划分为若干个弧段,每个弧段就是一个扇区 (Sector)。扇区是硬盘上存储的物理单位,现在每个扇区可存储 512 字节数据已经成了业界的约定。也就是说,即使计算机只需要某一个字节的数据,但是也得把这个 512 个字节的数据全部读入内存,再选择所需要的那个字节。 + +柱面是我们抽象出来的一个逻辑概念,简单来说就是处于同一个垂直区域的磁道称为柱面 ,即各盘面上面相同位置磁道的集合。需要注意的是,磁盘读写数据是按柱面进行的,磁头读写数据时首先在同一柱面内从 0 磁头开始进行操作,依次向下在同一柱面的不同盘面(即磁头上)进行操作,只有在同一柱面所有的磁头全部读写完毕后磁头才转移到下一柱面。因为选取磁头只需通过电子切换即可,而选取柱面则必须通过机械切换。数据的读写是按柱面进行的,而不是按盘面进行,所以把数据存到同一个柱面是很有价值的。 +磁盘被磁盘控制器所控制(可控制一个或多个),它是一个小处理器,可以完成一些特定的工作。比如将磁头定位到一个特定的半径位置;从磁头所在的柱面选择一个扇区;读取数据等。 + +现代硬盘寻道都是采用CHS(Cylinder Head Sector)的方式,硬盘读取数据时,读写磁头沿径向移动,移到要读取的扇区所在磁道的上方,这段时间称为寻道时间(seek time)。因读写磁头的起始位置与目标位置之间的距离不同,寻道时间也不同。磁头到达指定磁道后,然后通过盘片的旋转,使得要读取的扇区转到读写磁头的下方,这段时间称为旋转延迟时间(rotational latencytime)。然后再读写数据,读写数据也需要时间,这段时间称为传输时间(transfer time)。 +根据上文的信息,我们可以得出磁盘容量的计算公式为: +硬盘容量 = 盘面数 × 柱面数 × 扇区数 × 512字节 + +笔试题实战 +下面的题目是腾讯某一年校招笔试中的一个题目,题干信息描述为:数据存储在磁盘上的排列方式会影响I/O服务的性能,一个圆环磁道上有10个物理块,10个数据记录R1~R10存放在这个磁道上,记录的安排顺序如下表所示。 + + + +物理块 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 + + + + +逻辑记录 +R1 +R2 +R3 +R4 +R5 +R6 +R7 +R8 +R9 +R10 + + + +假设磁盘的旋转速度为20ms,磁盘当前处在R1的开头处,若系统顺序扫描后将数据放入单缓冲区内,处理数据的时间为4ms(然后再读取下个记录),则处理这10个记录的最长时间是多少? + +答案:磁盘会一直朝某个方向旋转,不会因为处理数据而停止。本题要求顺序处理 R1 到 R10,起始位置在 R1,一周是 20ms,共 10 个记录,所以每个记录的读取时间为 2ms。首先读 R1 并处理 R1,读 R1 花 2ms,读好后磁盘处于 R1 的末尾或 R2 的开头,此时处理 R1,需要 4ms,因为磁盘一直旋转,所以 R1 处理好了后磁盘已经转到 R4 的开始了,这时花的时间为 2+4=6ms。这时候要处理 R2,需要等待磁盘从 R5 一直转到 R2 的开始才行,磁盘转动不可反向,所以要经过 8*2ms 才能转到 R1 的末尾,读取 R2 需要 2ms,再处理 R2 需要 4ms,处理结束后磁盘已经转到 R5 的开头了,这时花的时间为 2*8+2+4=22ms。等待磁盘再转到 R3 又要 8*2ms,加上 R3 自身 2ms 的读取时间和 4ms 的处理时间,花的时间也为 22ms,此时磁盘已经转到 R6 的开头了,写到这里,就可以看到规律了,读取并处理后序记录都为 22ms,所以总时间为 6+22*9=204ms。 + +如何加速对磁盘的访问 +对于理解数据库系统系统特别重要的是磁盘被划分为磁盘块(或像操作系统一样称之为页),每个块的大小是 4~64KB。磁盘访问一个磁盘块平均要用 10ms,但是这并不表示某一应用程序将数据请求发送到磁盘控制器后,需要等 10ms 才能得到数据。如果只有一个磁盘,在最坏的情况下,磁盘访问请求的到达个数超过 10ms 一次,那么这些请求就会被无限的阻塞,调度延迟将会变的非常大。因此,我们有必要做一些事情来减少磁盘的平均访问时间。 +按柱面组织数据:前这一点在前文已经提到过了。因为寻道时间占平均块访问时间的一半,如果我们选择在一个柱面上连续的读取所有块,那么我们只需要考虑一次寻道时间,而忽略其它时间。这样,从磁盘上读写数据的速度就接近于理论上的传输速率。 +使用多个磁盘:如果我们使用多个磁盘来替代一个磁盘,只要磁盘控制器、总线和内存能以 n 倍速率处理数据传输,则使用 n 个磁盘的效果近似于 1 个磁盘执行了 n 次操作。因此使用多个磁盘可以提高系统的性能。 +磁盘调度:提高磁盘系统吞吐率的另一个有效方法是让磁盘控制器在若干个请求中选择一个来首先执行,调度大量块请求的一个简单而有效的方法就是电梯算法。回忆一下电梯的运行方式,它并不是严格按先来后到的顺序为乘客服务,而是从建筑物的底层到顶层,然后再返回来。同样,我们把磁盘看作是在做横跨磁盘的扫描,从柱面最内圈到最外圈,然后再返回来,正如电梯做垂直运动一样。 +预取数据:在一些应用中,我们是可以预测从磁盘请求块的顺序的。因此我们就可以在需要这些块之前就将它们装入主存。这样做的好处是我们能较好的调度磁盘,比如采用前文的电梯算法来减少访问块所需要的平均时间。 +磁盘故障 +如果事情都像我们一开始设计的那样进行,那世界肯定会变得特别无聊。磁盘偶尔也会耍耍小脾气,甚至是罢工不干了。比如在读写某个扇区一次尝试没有成功,但是反复尝试后有成功读写了,我们称之为间歇性故障。 +一种更为严重的故障形式是,一个或多个二进制位永久的损坏了,所以不管我们尝试多少次都不可能成功,这种故障称之为介质损坏。 +另一种相关的错误类型称之为写故障,当我们企图写一个扇区时,既不能正确的写,也不能检索先前写入的扇区,发生这种情况的一种可能原因就是在写过程中断电了。 +当然肯定最严重的就是磁盘崩溃,这种故障中,整个磁盘都变为永久不可读,这是多么可怕的事情。 +既然会出现上面所述的各种大小故障,那么我们就必须要采取各种措施去应对大大小小的变故,保证系统能正常运行。 +规避故障 +我们尝试读一个磁盘块,但是该磁盘块的正确内容没有被传送到磁盘控制器中,就是一个间歇性故障发生了。那么问题是控制器如何能判断传入的内容是否正确呢?答案就是使用校验和,即在每个扇区使用若干个附加位。在读出时如果我们发现校验和对数据位不合适,那么我们就知道有错误;如果校验和正确,磁盘读取仍然有很小的可能是不正确的,但是我们可以通过增加趣多校验位来降低读取不正确发生的概率。 +此处我们使用奇偶校验来举例,通过设置一个校验位使得二进制集合中 1 的个数总是偶数。比如某个扇区的二进制位序列是 01101000,那么就有奇数个 1,所以奇偶位是 1,这个序列加上它后面的奇偶位,就有 011010001;而如果所给的序列是 11101110,那么奇偶位就是 0。所以每一个加上了奇偶位构成的 9 位序列都有偶数奇偶性。 +尽管校验和几乎能正确检测出介质故障或读写故障的存在,但是它却不能帮助我们纠正错误。为了处理这个问题,我们可以在一个或多个磁盘中执行一个被称为稳定存储的策略。通常的思想是,扇区时成对的,每一对代表一个扇区内容 X。我们把代表 X 的扇区对分别称为左拷贝 XL和右拷贝XR。这样实际上就是每个扇区的内容都存储了两份,操作XL失败,那么去操作XR就可以了,更何况我们还在每个扇区中有校验和,把错误的概率就大大降低了。 +到现在为止,我们讨论的都是简单的故障,但是如果发生了磁盘崩溃,其中的数据被永久破坏。而且数据没有备份到另一种介质中,对于银行金融系统这将是巨大的灾难,遇到这种情况我们应该怎么办呢? +数据恢复 +应对磁盘故障最简单的方式就是镜像磁盘,即我们常说的备份。回忆一下写毕业论文时的做法,那时候大部分同学还不会用版本控制器,所以基本采用每天备份一次数据,并且在文件名称中标注日期,以此来达到备份的效果。 +第二种方式是使用奇偶块,比如一个系统中有 3 个磁盘,那么我们再加一个磁盘作为冗余盘。在冗余盘中,第 i 块由所有数据盘的第 i 块奇偶校验位组成。也就是说,所有第 I 块的第 j 位,包括数据盘和冗余盘,在它们中间必须有偶数个 1,冗余盘的作用就是让这个条件为真。 +我们举个简单例子,假设快仅由一个字节组成,我们有三个数据盘和一个冗余盘,对应的位序列如下。其中 盘4 为冗余盘,它的位序列是根据前面三个盘计算出来的。 +盘 1:11110000 +盘 2:10101010 +盘 3:00111000 +盘 4:01100010 + +假设现在某个盘崩溃了,那么我们就能根据上面的序列来恢复数据,只需要让每一列 1 的个数为偶数就可以了,但是这种冗余方式也存在很大的不足。 +第一个缺陷是,如果是两个盘同时崩溃了,那数据也恢复不出来了。第二个问题在于,虽然读数据只需要一次 I/O 操作即可,但是写数据时就不一样了,因为需要根据其他数据盘来计算冗余盘中的位序列,假设共有 n 个盘,其中一个为冗余盘,所以每次写数据时,都需要进行 n+1 次 I/O 操作(读不被写入的 n-1 个盘,被重写数据盘的一次写,冗余盘的一次写),而 I/O操作又是非常耗时的操作,所以这种方法会大大拖慢系统性能。 +另一种方案是没有明显的冗余盘,而是把每个磁盘作为某些块的冗余盘来处理。比如现在有 4 个盘,0 号磁盘将作为编号为 4、8、12 等柱面的冗余,而 1 号磁盘作为编号为 1、5、9 等块的冗余...... +一种更为先进的方式使用海明码来帮助从故障中恢复数据,它在多个磁盘崩溃的情况下也能恢复出数据,也是 RAID 的最高等级,由于本人水平有限,用文字表达不清楚,就不作介绍了,嘿嘿。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 学习 Angulr 容易忽略的知识点 + +

+ +
+ + + + +
+ +
+ +参考内容: +《Angulr5 高级编程(第二版)》 + +函数声明式和表达式 +// 第一种:函数声明式 +myFunc(); +function myFunc(){ + ... +} + +// 第二种:函数表达式 +myFunc(); +let myFunc = function(){ + ... +} + +虽然上面两种函数声明方式在大部分情况下是一样的,第一种可执行,第二种却不可以执行,这是因为浏览器在解析 js 时找到函数声明,并在执行剩余语句之前设置好函数,此过程称为函数提升,但是函数表达式却不会受到提升,因此无法正常工作。 +js 不具备多态性 +js 重不能创建名称相同但参数不同的两个函数,它不具备这个多态性,比如你定义的函数中有两个形参,调用函数时只传一个参数,第二形参的值就是 undefined ,如果传的参数大于 3 个,那么会自动忽略多余的参数。可以使用下列方法来处理函数定义参数数量和用于调用函数实际参数数量之间不匹配的问题。 +// 使用默认参数 +let func = function(age, sex='男'){ + ... +} +func(23); + +// 使用可变长参数 +let func = function(age, sex, ...extraArgs){ + ... +} +func(23, '女', '张三', '深圳'); +// 最后一个参数是一个数组,任何额外的实参都会被赋给这个数组 + +let 和 war 的区别 +使用 let 和 var 声明变量的区别,使用 let 声明变量会把变量的作用范围限定在它所在的代码区域内。而使用 var 所创建的变量的作用域是它所在的函数。 +function func(){ + if(false){ + var age = 23; + } +} + +// 上面的代码会被解析成下面的形式,使用 let 则不会出现这样的结果 + +function func(){ + var age; + if(false){ + age = 23; + } +} + +相等 == 和恒等 === 以及 连接操作符 + +相等操作符尝试将操作数强制转换为相同的类型,再评估是否相等,实质上相等操作符==是测试二者的值是否相等,而与二者的类型无关;如果要测试值和类型是否都相等则应该用恒等操作符===。 +5 == '5' // 结果为 true +5 === '5' // 结果为 false + +在 js 中,连接操作符的优先级高于加法操作,也就是说5 + '5'的结果是55。 +不同的模块指定方式 +import { Name } from &quot;./modules/NameUtil&quot;;// 第一种 +import { Compont } from &quot;@angular/core&quot;;// 第二种 + +上面两种导入模块的方式有所不同,第一种是相对模块,第二种是非相对导入。第一种告诉的 TypeScript 编译器,该模块所在的位置是相对于包含 import 语句的文件而言;第二种非相对导入,编译器会用 node_modules 文件夹中的 npm 包来解析它。 +如果在导入模块时,出现需要导入两个不同模块但是名字却相同的情况,可以使用as关键字给导入的模块取一个别名。 +import { Name as otherName } from &quot;./modules/Name&quot;;//取别名 + +还有一种方法是将模块作为对象导入,如下 import 所示,导入 Name 模块的内容,并创建一个名为 otherName 的对象,然后就可以使用该对象的属性了。 +import * as otherName from &quot;./modules/NameUtil&quot;; +let name = new otherName.Name(&quot;Admin&quot;, &quot;China&quot;);// Name 是 NameUtil 中的类 + +多类型和类型断言 +在 ts 中允许指定多个类型,使用字符|进行分隔。看下面的的方法,其功能是把华氏温度转换为摄氏温度。 +// 使用多类型,该函数可以传入 number 和 string 类型的参数 +static convertFtoC(temp: number | string): string { + /* + 尝试使用 &lt;&gt; 声明一个类型断言,将一个对象转换为指定类型,也可以使用 as 关键字实现下列相同的效果 + let value: number = (temp as number).toPrecision ? temp as number : parseFloat(temp as string); + */ + let value: number = (&lt;number&gt;temp).toPrecision ? &lt;number&gt;temp : parseFloat(&lt;string&gt;temp); + return ((parseFloat(value.toPrecision(2)) - 32) / 1.8).toFixed(1); +} + +元组是固定长度的数组,数组的每一项都是指定的类型;可索引类型可以将键与值关联起来,创建类似于 map 的集合。 +// 元组 +let tuple: [string, string, string]; +tuple = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]; + +// 可索引类型 +let cities: {[index: string] : [string, string]} = {}; +cities[&quot;Beijing&quot;] = [&quot;raining&quot;, &quot;2摄氏度&quot;]; + +数据绑定 +[target]=&quot;expr&quot;// 方括号表示单向绑定,数据从表达式流向目标; + +(target)=&quot;expr&quot;// 圆括号表示单向绑定,数据从目标流向表达式,用于处理事件的绑定; +[(target)]=&quot;expr&quot;// 圆方括号组合表示双向绑定,数据在表达式与目标之间双向流动; +{{ expression }}// 字符串插入绑定。 + +[] 绑定有很多不同的形式,下面介绍不同表现形式的效果。 +&lt;!-- + 标准属性绑定(dom对象有的属性),将 input 的 value 属性绑定到一个表达式的结果 + 因为 model.getProduct(1) 可能返回 null ,所以使用模板空条件操作符 ? 浏览返回结果 + 如果返回不为空,那么将读取 name 属性,否则由 null 合并操作符 || 将结果设置为 None + 字符串插入绑定也可以使用这种表达式 + --&gt; +&lt;input [value]=&quot;model.getProduct(1)?.name || 'None'&quot;&gt; + +&lt;!-- + 元素属性绑定,有时候我们需要绑定的属性在 DOMAPI 上面没有 + 可以使用通过在属性名称前加上 attr 前缀的方式来定义目标 + --&gt; +&lt;td [attr.colspan]=&quot;model.getProducts().length&quot;&gt; + {{ model.getProduct(1)?.name || 'None' }} +&lt;/td&gt; + +&lt;!-- 还有其他的 ngClass,ngStyle 等绑定,理解大体上和上面差不多 --&gt; + +内置指令 +&lt;!-- + ngIf指令,如果表达式求值结果为 true ,那么 ngIf 将宿主元素机器内容包含在 html 文件中 + 指令前面的星号表示这是一条微模板指令 + 组要注意的是,ngIf 会向 html 中添加元素,也会从中删除元素,并非只是显示和隐藏 + 如果只是控制可见性,可以使用属性绑定挥着样式绑定 + --&gt; +&lt;div *ngIf=&quot;expr&quot;&gt;&lt;/div&gt; + +&lt;!-- + ngSwitch指令, + --&gt; +&lt;div [ngSwitch]=&quot;expr&quot;&gt; + &lt;span *ngSwitchCase=&quot;expr&quot;&gt;&lt;/span&gt; + &lt;span *ngSwitchDefault&gt;&lt;/span&gt; +&lt;/div&gt; + +&lt;!-- + ngFor指令,见名知意,为数组中的每个对象生成同一组元素 + ngFor 指令还支持其他的一系列可赋给变量的值,有如下局部模板变量 + + index:当前对象的位置 + odd:如果当前对象的位置为奇数,那么这个布尔值为 true + even:同上相反 + first:如果为第一条记录,那么为 true + last:同上相反 + --&gt; +&lt;div *ngFor=&quot;let item of expr; let i = index&quot;&gt; + {{ i }} +&lt;/div&gt; + +&lt;!-- + ngTemplateOutlet指令,用于重复模板中的内容块 + 其用法如下所示,需要给源元素指定一个 id 值 + + &lt;ng-template #titleTemplate&gt; + &lt;h1&gt;我是重复的元素哦&lt;/h1&gt; + &lt;/ng-template&gt; + &lt;ng-template [ngTemplateOutlet]=&quot;titleTemplate&quot;&gt;&lt;/ng-template&gt; + ...省略若万行 html 代码 + &lt;ng-template [ngTemplateOutlet]=&quot;titleTemplate&quot;&gt;&lt;/ng-template&gt; + --&gt; +&lt;ng-template [ngTemplateOutlet]=&quot;myTempl&quot;&gt;&lt;/ng-template&gt; + +&lt;!-- + 下面两个指令就是见名知意了,不解释 + --&gt; +&lt;div ngClass=&quot;expr&quot;&gt;&lt;/div&gt; +&lt;div ngStyle=&quot;expr&quot;&gt;&lt;/div&gt; + +事件绑定 +事件绑定使用 (target)=&quot;expr&quot;,是单向绑定,数据从目标流向表达式,用于响应宿主元素发送的事件。 +当浏览器触发一个时间时,它将提供一个对象来描述该事件,对于不同类型的事件有不同类型的事件对象,事件对象被赋给一个名为$event的模板变量,但是所有事件对象都有下面三个属性: +type:返回一个 string 值,用于标识已触发事件类型; +target:返回触发事件的对象,一般是 html元素对象。 +timeStamp:返回事件触发事件的 number 值,用 1970.1.1 毫秒数表示。 + +下面举几个例子,作为理解帮助使用。 +&lt;!-- 当数鼠标在上面移动时,就会触发 mouseover 事件 --&gt; +&lt;td *ngFor=&quot;let item of getProducts()&quot; (mouseover)=&quot;selectedProduct = item.name&quot;&gt;&lt;/td&gt; + +&lt;!-- 当用户编辑 input 元素的内容时就会触发 input 事件 --&gt; +&lt;input (input)=&quot;selectedProduct=$event.target.value&quot; /&gt; + +&lt;input (keyup)=&quot;selectedProduct=product.value&quot; /&gt; +&lt;!-- 使用事件过滤,上面的写法按下任何一个键都会触发事件,而下面的写法只有回车事件才会触发事件 --&gt; +&lt;input (keyup.enter=&quot;selectedProduct=product.value&quot;) /&gt; + +表单验证 +Angular 提供了一套可扩展的系统来验证表单元素的内容,总共可以向 input表元素中添加 4 个属性,每个属性定义一条验证规则,如下所示: +required:用于指定必须填写值; +minlength:用于指定最小字符数; +maxlength:用于指定最大字符数,(不能在表单元素直接使用,因为它与同名的 H5 属性冲突); +pattern:该属性用于指定用户填写的值必须匹配正则表达式 + +&lt;!-- + Angular 要求验证的元素必须定义 name 属性 + 由于 Angular 使用的验证属性和 H5 规范使用的验证属性相同, + 所以向表单元素中添加 novalidate 属性,告诉浏览器不要使用原生验证功能 + ngSubmit 绑定表单元素的 submit 事件 + --&gt; +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +Angular 提供了 3 对验证 CSS 类,这些类可以用于样式化表单元素,向用户提供验证反馈,具体说明如下所示。 +ng-untouched ng-touched:如果一个元素未被用户访问,就将其加入到 nguntouched 类中;一旦访问就加入到 ngtouched 类中。 +ng-prisstine ng-dirty:元素内容没有被改变被加入到 ng-prisstine 类中,否则将其加入到 ng-dirty 类中。 +ng-valid ng-invalid:如果满足验证规则定义的条件,就加入到 ng-valid 类中,否则加入到 ng-invalid 类中。 + +在实际使用过程中,直接定义对应的样式即可,如下所示: +&lt;style&gt; +input.ng-dirty.ng-invalid{ + border: 2px solid red; +} +input.ng-dirty.ng-valid{ + border: 2px solid green; +} +&lt;/style&gt; +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +上面的验证方式无法给用户提供更加具体的信息,用户不知道应该做什么,可以使用 ngModel 指令来访问宿主元素的验证状态,当存在验证错误的时候,使用该指令向用户提供指导性信息。 +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + #nameRef=&quot;ngModel&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;ul class=&quot;text-danger list-unstyled&quot; + *ngIf=&quot;name.dirty &amp;&amp; name.invalid&quot;&gt; + &lt;li *ngIf=&quot;name.errors?required&quot;&gt; + you must enter a product name + &lt;/li&gt; + &lt;li *ngIf=&quot;name.errors?.pattern&quot;&gt; + product name can only contain letters and spases + &lt;/li&gt; + &lt;li *ngIf=&quot;name.errors?minlength&quot;&gt; + &lt;!-- + Angular 表单验证错误描述属性 + required:如果属性已被应用于 input 元素,此属性返回 true + minlength.requiredLength:返回满足 minlength 属性所需的字符数 + minlength.actualLength:返回用户输入的字符数 + pattern.requiredPattern:返回使用 pattern 属性指定的正则表达式 + pattern.actualValue:返回元素的内容 + --&gt; + product name must be at least {{ name.errors.minlength.requiredLenth }} characters + &lt;/li&gt; + &lt;/ul&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +如果在用户尝试提交表单时就显示大量的错误信息,给人的体验感就会很差,所以可以让用户提交表单时再验证整个表单,示例代码如下所示。 +export class ProductionCompont { + // ...省略若万行代码 + formSubmited: boolean = false; + + submitForm(form: ngForm) { + this.formSubmited = true; + if(form.valid) { + this.addProduct(this.newProduct); + this.newProduct = new Product(); + form.reset(); + this.formSubmited = true; + } + } +} + +&lt;form novalidate #formRef=&quot;ngForm&quot; (ngSubmit)=&quot;submitForm(formRef)&quot;&gt; + &lt;div *ngIf=&quot;formsubmited &amp;&amp; formRef.invalid&quot;&gt; + there are problems with the form + &lt;/div&gt; + &lt;!-- 禁用提交按钮,验证成功提交按钮才可用 --&gt; + &lt;button [disabled]=&quot;formSubmited &amp;&amp; formRef.valid&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +fromSubmited 属性用于指示表单是否已经提交,并将用于在用户提交整个表单之前阻止表单验证。当用户提交表单时,调用 submitForm 方法,并将 ngForm 对象作为实参传入,ngForm 提供了 reset 方法,该方法可以重置表单的验证状态,使其返回到最初的未访问状态。 +更高级的还有使用基于模型的表单验证,可以自行查阅相关资料。 +使用 json-server 模拟 web 服务 +因为json-server会经常用到,建议使用全局安装命令npm install -g json-server。因为开发后端的同学太慢了,而我们如果要等他们把接口都提供给我们的时候再开发程序的话,那效率就太低了,所以使用 json-server 来模拟后端服务。只需要建好一个 json 文件,比如下面的格式: +{ + &quot;user&quot; : [ + { + &quot;name&quot; : &quot;张三&quot;, + &quot;number&quot; : &quot;1234&quot;, + }, + { + &quot;name&quot; : &quot;王二&quot;, + &quot;number&quot; : &quot;5678&quot;, + } + ], + &quot;praise&quot;: [ + {&quot;info&quot;:&quot;我是一只小老虎呀!&quot;}, + {&quot;info&quot;:&quot;我才是大老虎&quot;} + ] +} + +启动服务使用命令json-server [你的 json 文件路径],然后就可以根据提示访问了,你甚至可以使用http://localhost:3000/user?number=5678去过滤数据。这样就能模拟 web 服务,而不必等后端同学的进度了。 +解决跨域请求问题 +Angular 跨域请求问题可以通过 Angular 自身的代理转发功能解决,在项目文件夹下新建一个 proxy.conf.json 并在其中添加如下内容。 +// 可以通过下列配置解决 +&quot;/api&quot;: { + &quot;target&quot;: &quot;http://10.9.176.120:8888&quot;, +} + +在启动时使用npm start,或者使用ng serve --proxy-config proxy.conf.json,Anular 中的/api请求就会被转发到 http://10.9.176.120:8888/api,从而解决跨域请求问题。 +使用第三方 js 插件 +共有三种方式引入第三方插件,第一种很简单,直接在 html 中引入插件就可以了;第二种在angular.json中进行配置;第三种在 ts 文件中使用 import 导入库即可。 +// 第一种(需要重启服务) +&quot;scripts&quot;: [&quot;src/assets/jquery-3.2.1.js&quot;,&quot;src/assets/jquery.nicescroll.js&quot;,&quot;src/assets/ion.rangeSlider.js&quot;] + +// 第二种 +&lt;script type=&quot;text/javascript&quot; src=&quot;assets/jquery-3.2.1.js&quot;&gt;&lt;/script&gt; +&lt;script type=&quot;text/javascript&quot; src=&quot;assets/jquery.nicescroll.js&quot;&gt;&lt;/script&gt; + +// 第三种 +import &quot;assets/jquery-3.2.1.js&quot;; +import &quot;assets/jquery.nicescroll.js&quot;; +import &quot;assets/ion.rangeSlider.js&quot;; + +深拷贝与浅拷贝 +深拷贝与浅拷贝是围绕引用类型变量说的,其本质区别是不可变性,基本类型是不可变得,而引用类型是可变的。 +直接使用赋值操作符,就是浅拷贝,如果对拷贝源进行操作,会直接影响在拷贝目标上,因为这个赋值行为本质是内存地址的赋值,为了获得与拷贝源完全相同但又不会影响彼此的对象就要使用深拷贝。 +let objA = { + x: 1, + y: -1 +} +let objB = objA; +objA.x++; +console.log(&quot;objA.x:&quot;+objA.x, &quot;objB.x:&quot;+objB.x); +//打印结果如下: +objA.x : 2 +objB.x : 2 + +Typescript 提供了一种方法来实现引用类型的深拷贝,即Object.assign(target, ...source),此方法接受多个参数,第一个参数为拷贝目标,剩余参数为拷贝源,同名属性会进行覆盖。 +let objA = { + x: 1, + y: -1, + c: { + d: 1, + } +} +let objB = {}; +Object.assign(objB, objA); +objA.x++; +console.log(&quot;objA.x:&quot;+objA[&quot;x&quot;], &quot;objB.x:&quot;+objB[&quot;x&quot;]); +//打印结果如下: +objA.x : 2 +objB.x : 1 + +需要注意的是,Typescript 提供的深拷贝方法不能实现嵌套对象的深拷贝,会出现下面的情况。 +let objA = { + x: 1, + y: -1, + c: { + d: 1, + } +} +let objB = {}; +Object.assign(objB, objA); +objA.c.d++; +console.log(&quot;objA.c.d:&quot;+objA[&quot;c&quot;].d, &quot;objB.c.d:&quot;+objB[&quot;c&quot;].d); +//打印结果如下: +objA.c.d : 2 +objB.c.d : 2 + +要实现嵌套对象的深拷贝,可以使用 JSON 对象提供的方法,JSON 对象提供了两个方法,分别为:stringify()和parse(),前者将对象 JSON 化,后者将 JSON 对象化,使用这种方式可以实现嵌套深拷贝,但是也有缺点:破坏原型链,不能拷贝属性值为 function 的属性。 +let objA = { + a: 1, + b: { + c: 1 + } +} +let objB = JSON.parse(JSON.stringify(objA)); +objA.b.c++; +console.log(&quot;objA.b.c:&quot;+objA.b.c, &quot;objB.b.c:&quot;+objB.b.c); + +//打印结果如下: +objA.b.c:2 +objB.b.c:1 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 读大学的几点建议 + +

+ +
+ + + + +
+ +
+ 前天在朋友圈看到一句话:“学生就是无知、狂妄、垃圾的代名词”,让我思考了很多东西。毕业出来也有快两月了,圈子里还有很多学弟学妹,很多同级的同学也都读研了,这里谈谈自己的感受,应该怎么把大学过好,期望不要太高,我自己很普通,大学没有什么出彩的经历。 +很多父母都把孩子的成就与大学挂钩,认为好的大学就是成功的代名词,盲目追求高学历,孩子从小也一直受这些思维的影响,应试能力强的惊人。大家都知道清华北大好,但是你问他哪里好,就回答不上来了,尤其家长,在他们眼里 985 一定比 211 好,211 一定比普通一本好。 +现在大学都会给你传达自己乃名门之后的观念,进校首先讲历史,当然都是挑好的讲,然后讲学校的历史成绩单。不得不说,效果非常好,你一和大学生谈论他的学校时,他会给你说出来学校是某某名人所建,学校在哪方面做的非常好,比如导弹是全国第几,造船位居全国前列等等。但是你一问他本人是哪个专业,回答是学数学的、学计算机的...... +每个学校都有恶心的事,我自己的一个经历,学院一拍脑袋,搞个什么本科生导师制,然后就没有然后了,四年总共见了导师一面。期间最可笑的是,也不知道是教育部还是什么部来检查,学生需要交一个导师沟通表上去,导师在哪个办公室都不知道,那大家怎么办,就模仿导师的口吻给自己下评语,第二天全院的导师沟通表都被打回来了,因为大家模仿的口吻不像导师,要求重新造假。 +上面的类似情况在大部分学校应该都存在,只不过看谁更可笑,某个学生出事了,学校第一想法不是怎么帮自己的学生解决问题,而是想如何把事情压下去,封锁消息。你会发现很多效率像蜗牛一样的机构,其公关效率却像火箭一样。 +现在各个大学的就业率都高的惊人,都不会低于 90%,为啥这么高呢?我也不知道学校是如何统计就业率的,唯一清楚的是,你毕业了,没有签工作,那么辅导员会给你打电话让你随便找个公司把三方协议签了交上去;这算轻的,很多学校是你不交三方协议,就不给你学位证、毕业证,我身边就有好几个随便刻个假章,盖在三方上面,只要交了三方协议的都算就业了的。 +我个人认为大学有的课就应该逃,也看到过文章说学生上课不应该带有批判性思维,什么课有用不应该是学生说了算,大学的课程设置都是专家们讨论的结果,现在最不缺的就是专家,什么人生导师一大把,出来之后,你仍然会发现有的课纯属浪费时间。强调一下,逃课不是去打游戏,是为了把时间利用的比在课堂上更有价值,我大学微积分老师也鼓励我们逃课,现在看来那时还是胆子太小,人家鼓励你逃课,还不敢逃,怂。重要的事再说一遍,逃课是去做比上课更有价值的事情。 +养成自学的习惯,提高自学能力,自学能力太重要了,而且这个时代自学是很容易的,网上有很多视频教程,比学校老师教的还好,而且也更接近于实战,大学教不了你太多东西,仅仅提供了一个平台,只是平台大小的区别而已。经常会听到学生说某个知识点老师没教,潜台词就是这个知识点我就不应该会,而且理直气壮,让人无语。世人都认为学历最重要,实际上真正重要的是学力。 +迷茫的时候就去旅行吧,感受一下不同的文化,见识见识世界的缤纷多彩,你的视野会开阔许多,很多事情必须亲身体验才能感受到它的好处,旅途中你可以结识各种各样的朋友,与他们的思想碰撞,看看其他地方的生活,你可能就不会迷茫了,会找到自己乐趣。 +多结识比自己优秀的人,认识正能量的朋友,大学提供了很多机会,优秀的朋友会在不知不觉中改变你,你也会不知不觉变得更优秀。我在出于兴趣和打发时间,没事写写文章,让我意外的是,对我的改变太大了,通过写作让我认识了一些社会上的优秀人士,通过与他们交谈,我的思维方式有很大的改变,学生思维逐渐摒弃。 +尽量不要透支,学生没有收入来源,基本都是依靠父母每个月给的生活费,很多学生都使用花呗、白条等产品,而且借贷金额还不少,从理财角度来看,每个月的还款额超过自己收入的三分之一,生活就会有压力,何况学生还是没有收入的群体。没必要为了追求时髦而疯狂购买各种新产品,真高品质生活不应该是科技产品堆砌而成,而应该是由惬意、舒心、成长所构建的。 +还是要注重和学院领导、辅导员的关系,这点我是做的最差的,因为我不会拍马屁,看不惯就要说出来,容易得罪人。相信这背后的好处都还是明白一二,什么评奖评优暂且不谈,在保研的时候,这种关系会帮你一个大忙,往大了说就是改变人生的机会。 +写完读了一遍,有的观点还是显得偏激,请自行选择吸收,也欢迎批评指正。总得来说,最重要的就是提升自己的认知水平,思维方式很重要,保持终身学习的态度。有的事要敢想,不要给自己的思维设限制,也不要觉得博士硕士有多么了不起,研究生能做的事,本科生照样能做。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 跨域请求是什么?如何解决? + +

+ +
+ + + + +
+ +
+ +参考内容: +JavaScript: Use a Web Proxy for Cross-Domain XMLHttpRequest Calls +别慌,不就是跨域么! +跨域资源共享 CORS 详解 +AJAX请求和跨域请求详解(原生JS、Jquery) +JavaScript跨域总结与解决办法 + + +刚毕业入职,大部分时间还在培训,中间有一段时间的空闲时间,就学习了下 Angular,在学校都是编写的单体应用,所有代码都放在同一个工程下面,到公司使用的是前后端分离了,虽然后端程序也是我自己写的,但是有一些数据是从公司现有接口去拿的,然后就遇到让我纠结了两小时的跨域请求问题,在这里做一个简单的总结输出。 +什么是跨域请求 +跨域请求问题是浏览器的同源策略造成的,该策略不允许执行其它网站的脚本,是浏览器施加的安全限制。什么是同源?最初是指网页 A 设置的 Cookie 不能被网页 B 打开,包括三个相同:协议、域名、端口。这个同源是从 URL 判断的,不是从 IP 判断的,如果同一个服务器对应连个域名,这两个域名是不同源的。 +http://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 非跨域 + +http://www.nealyang.cn/index.html 调用 http://www.neal.cn/server.php 跨域,主域不同 + +http://abc.nealyang.cn/index.html 调用 http://def.neal.cn/server.php 跨域,子域名不同 + +http://www.nealyang.cn:8080/index.html 调用 http://www.nealyang.cn/server.php 跨域,端口不同 + +https://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 跨域,协议不同 + +localhost 调用 127.0.0.1 跨域 + +同源政策的目的是为了保护用户信息的安全,防止恶意网站窃取数据,随着互联网的发展,同源政策更加严格了,下面三种行为都会受到限制。 +(1) Cookie、LocalStorage 和 IndexDB 无法读取。 +(2) DOM 无法获得。 +(3) AJAX 请求不能发送。 + +所有的现代浏览器都对网络连接进行了安全限制,包括 XMLHttpRequest,如果你的 web 应用程序和其使用的数据在同一个服务器,你不会遇到跨域请求问题。但是当你的 web 应用程序和 web 服务数据不在同一个服务器时,就会被浏览器限制连接了。 +常用解决方案 +    对于跨域请求有很多的解决方案,最常用的解决方案是在你的 web 服务器上面设置代理。在设置代理之前就通过,应用程序直接去请求另一个服务器下的数据;设置代理之后,应用程序从自己的 web 服务器中请求数据,再由代理去请求数据,这样 web 服务器拿到数据之后返回给应用程序即可。从浏览器角度看,就是从同一个服务器拿的数据,并没有进行跨域请求。 + +通俗易懂的说,你家的宠物狗不会吃别家的食物,因为它担心别人的食物会把自己给药死,所以你的狗狗只管找你要食物,你是它的主人,它绝对相信你,而你可以鉴别别人给的食物是不是安全的。类比,小狗就是浏览器,你就是代理。 +Angular 中的解决办法 +上面所说的解决方案在开发过程中不方便操作,每新发一个接口都到服务器中去配置一下,不仅麻烦而且效率低下。首先说一下在 Angular 中一个人比较常用的解决方法,默认你在使用angular-cli构建你的项目,我们可以创建一个代理配置文件proxy.conf.json(假设你的后端服务的访问地址为10.121.163.10:8080),代理配置文件如下: +{ + &quot;/api&quot;: { + &quot;target&quot;: &quot;http://10.121.163.10:8080&quot;, + &quot;secure&quot;: false + } +} + +然后修改package.json文件中的启动命令为&quot;start&quot;: &quot;ng serve --proxy-config proxy.conf.json&quot;,启动项目时使用npm start即可解决跨域请求问题。 +上述解决方案仅在开发时使用,你当然可以使用 tomcat、nginx 配置代理,但是这很麻烦,需要打包代码部署,为了保证效率,我们想写完了立刻测试,同时也不想麻烦做后端的同学,在项目发布时,应该把代理配置到服务器中去;修改启动命令也不是必须的,你也可以选择每次使用 ng serve --proxy-config proxy.conf.json命令启动项目;示例代理配置文件内容可以有更多的属性,可以通过网络查阅相关资料。 +后端解决办法 +我的后端是是用 tornado 实现的,然后我又写了一个单独的页面用于在大屏幕上展示相关数据,没有用 Angular 了,要通过 AJAX请求数据,又怎么解决跨域请求问题呢?这时就需要设置请求头了,让后端允许跨域请求。 +这时需要了解一下简单请求和非简单请求了,简单请求就是只发送一次请求的请求;非简单请求会发送数据之前先发一次请求做预检,通过预检后才能再发送一次请求用于数据传输。 +更清晰区别,满足下列两大条件的属于简单请求,而非简单请求就是请求方法为PUT或DELETE,或者 Content-Type字段是application/json的请求。 + +1.请求方法为 GET、POST、HEAD之一 +2.HTTP头信息不超出字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type,并且 Content-Type 的值仅限于 application/x-www-form-urlencoded、multipart/form-data、text/plain。 + +对于简单请求,只需要设置一下响应头就可以了。 +class TestHandler(tornado.web.RequestHandler): + def get(self): + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + # 可以把 * 写成具体的域名 + self.write('cors get success') + +对于复杂请求,需要设置预检方法,如下所示: +class CORSHandler(tornado.web.RequestHandler): + # 复杂请求方法put + def put(self): + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + self.write('put success') + # 预检方法设置 + def options(self, *args, **kwargs): + #设置预检方法接收源 + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + #设置预复杂方法自定义请求头h1和h2 + self.set_header('Access-Control-Allow-Headers', &quot;h1,h2&quot;) + #设置允许哪些复杂请求方法 + self.set_header('Access-Control-Allow-Methods', &quot;PUT,DELETE&quot;) + #设置预检缓存时间秒,缓存时间内发送请求无需再预检 + self.set_header('Access-Control-Max-Age', 10) + + +
+ + Read More ~ +
+
+
+ +
+

+ + 讲一个爱情故事,让 HTTPS 简单易懂 + +

+ +
+ + + + +
+ +
+ +参考内容 +HTTPS explained with carrier pigeons + +充满各种数学证明的密码学是令人头疼的,一听到密码、黑客、攻击等词的时候,就给人一种神秘又高大上的感觉,但除非你真的从事密码学相关工作,否则你并不需要对密码学有多么深刻的理解。 +这是一篇适合在饭后的品茶时光中阅读的文章,咱们虚构一个故事来讲解,虽然故事看起来很随性,但是 HTTPS 也是这么工作的。里面有一些术语你也应该听过,因为它们经常出现在技术文献里面。 +故事背景 +一天,一个男子到河边抓鱼给母亲吃,而河岸的另一头是一大户人家的小姐和她的丫鬟在散步。突然,一个不小心,对面小姐不慎跌入水中,而丫鬟又不会游泳,这可把小丫鬟急的呀!!!正在抓鱼的男子见此状况,来不及脱掉身上的衣物,就像箭一样窜入水中.....想必看客已经猜到了,小姐被救起,男子抱着迷迷糊糊小姐走上岸的过程中,小姐感觉自己像触电了一样,觉得这个男人很安全,只要靠着他,就算天塌下来也不怕,而男子把小姐放下的那一刻,也很不舍,好像把她放下就失去了活下去的希望。 +小姐回到家中,给父亲大人说了这件事,父亲很高兴,就叫下人去把这位男子请到家中表示感谢,结果一问,这小伙幼年丧父,现在家中还有病弱的老母亲,连一间屋子都没有,一直和母亲寄住在城外的破庙里面,不过他毕竟救了自己的女儿,父亲让下人拿出了五十两黄金以表谢意,但不允许他和小姐再有任何来往。 +.....此处省略五千字。 +我们姑且称小姐为小花,称男子为小明,他们不能相见了,但是又备受相思之苦,因此只能通过写信的方式来传达彼此的思念了。 +最简单的通信方式 +如果小花想给小明写信,那么她可以把写好的信让信鸽给小明送去,小明也可以通过信鸽给小花回信,这样他们就能知道彼此的感情了。 +但是很快这种方式出问题了,因为他们都隐约感觉到收到的来信不是对方写的,因为从信件上看,双方都表示不再喜欢彼此。凭借着对彼此的信任,他们才知道是小花的父亲从中阻挠他们。每次他们写的信都被父亲的下人拦下了,然后换上他们事先准备好的信件,目的就是为了让小花和小明断了感情。 +HTTP 就是这样的工作方式。 +对称加密 +小花是博冠古今的人,这怎么能难倒她呢。他们彼此约定,每次写信都加上密码,让信鸽传送的信件是用密文书写的。他们约定的密码是把每个字母的位置向后移动三位,比如 A → D 、 B → E ,如果他们要给对方写一句 &quot;I love you&quot; ,那么实际上信件上面写的就是 &quot;L oryh brx&quot; 。现在就算父亲把信件拦截了,他也不知道里面的内容是什么,而且也没办法修改为有效的内容,因为他不知道密码,现在小花和小明又能给对方写情书了。 +这就是对称加密,因为如果你知道如何加密信息,那也能知道如何解密信息。上面所说的加密常称为凯撒密码,在现实生活中,我们使用的密码肯定会更复杂,但是主要思想是一样的。 +如何确定密钥 +显然对称加密是比较安全的(只有两个人知道密码的情况下)。在凯撒密码中,密码通常是偏移指定位数的字母,我们使用的是偏移三位。 +可能你已经发现问题了,在小花和小明开始写信之前,他们就已经没办法相见了,那他们怎么确定密钥呢,如果一开始通过信鸽告诉对方密钥,那父亲就能把信鸽拦下,也能知道他们的密钥,那么父亲也就可以查看他们信件的内容,同时也能修改信件了。 +这就是典型的中间人攻击,唯一能解决这个问题的办法就是改变现有的加密方式。 +非对称加密 +小花想出了更好的办法,当小花想给小明写情书的时候,她将会按照下面的步骤来进行: + +小花给小明送一只没有携带任何信件的鸽子; +小明让信鸽带一个没有上锁的空箱子回去,钥匙由小明保管; +小花把写好的情书放到箱子里面,并锁上箱子 +小明收到箱子后,用钥匙打开箱子就可以了。 + +使用这种方式,父亲大人就没办法拦截信鸽了,因为他没有箱子的钥匙。同样如果小明想给小花写情书,也采用这种方式。 +这就是非对称加密,之所以称之为非对称加密,是因为即使你能加密信息(锁箱子),但是你却无法解密信息(开箱子),因为箱子的钥匙在对方那里。在技术领域,把这里的箱子称作公钥,把钥匙称作私钥。 +认证机构 +细心的你可能发现问题了,当小明收到箱子后,他如何确定这个箱子的主人是谁呢,因为父亲也可以让信鸽带箱子给小明啊,所以父亲如果想知道他们的信件内容,那只需要把箱子偷换掉就好了。 +小花决定在箱子上面签上自己的名字,因为笔迹是不能模仿的,这样父亲就没办法伪造箱子了。但是依旧有问题,小花和小明在不能相见之前并没有见过彼此写的字,那么小明又如何识别出小花的字迹呢?所以他们的解决办法是,找张三丰替小花签名。 +众所周知,张三丰是当世的得道高人,他的品德是世人都认可的,大家都把他奉为圣人,而且天下肯定不止一对有情人遇到小花和小红这样的问题。张三丰只会为合法居民签名。 +张三丰会在小花的盒子上签名,前提是他确定了要签名的是小花。所以父亲大人是无法得到张三丰代表小花签名的盒子,否则小明就会知道这是一个骗局,因为张三丰只在验证了人们的身份后才会代表他们给盒子签名。 +张三丰在技术领域的角色就是认证机构,你现在阅读这篇文章所使用的浏览器是附带了各种认证机构的签名的。所以当你第一次访问某个网站时,你相信这不是一个钓鱼网站,是因为你相信第三方认证机构,因为他们告诉你这个箱子是合法的。 +箱子太重了 +虽然现在小花和小明有了一个可靠的通信系统,但是信鸽带个箱子飞的慢啊,热恋中的人是“一日不见如隔三秋”,信鸽飞慢了怎么行呢。 +所以他们决定还是采用对称加密的方式来写情书,但是对称加密的密钥要用箱子来传递,也就是用非对称加密方式来传递对称加密密钥,这样就可以同时获得对称加密和非对称加密的优点了,还能避免彼此的缺点。 +需要注意的是,在网络世界中,信息不会像鸽子传送的那么慢,只不过只用非对称加密技术加密信息要比对称加密慢,所以只用它来交换密钥。 +以上就是 HTTPS 的工作过程。 +一个故事 + +这个故事你可能早就知道了,我只是在写文章的过程中突然想起了它,就是笛卡尔的爱情故事。 + + +具体细节你可以网上去查,笛卡尔每天给自己喜欢的公主写信,但是信都被国王拦截了,笛卡尔给公主写的第十三封信中只有一个数学方程,但是这个方程国王看不懂,所以就把这封信交给了公主,公主一看方程,立刻着手把方程的图形画了出来,发现这是一颗心的形状。 + + + +
+ + Read More ~ +
+
+
+ +
+

+ + Scrapy 爬虫框架入门——抓取豆瓣电影 Top250 + +

+ +
+ + + + +
+ +
+ 最好的学习方式就是输入之后再输出,分享一个自己学习scrapy框架的小案例,方便快速的掌握使用scrapy的基本方法。 +本想从零开始写一个用Scrapy爬取教程,但是官方已经有了样例,一想已经有了,还是不写了,尽量分享在网上不太容易找到的东西。自己近期在封闭培训,更文像蜗牛一样,抱歉。 +Scrapy简介 +Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。 +其最初是为了 页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。 + +如果此前对scrapy没有了解,请先查看下面的官方教程链接。 +架构概览:https://docs.pythontab.com/scrapy/scrapy0.24/topics/architecture.html +Scrapy入门教程:https://docs.pythontab.com/scrapy/scrapy0.24/intro/tutorial.html +爬虫教程 +首先,我们看一下豆瓣TOP250页面,发现可以从中提取电影名称、排名、评分、评论人数、导演、年份、地区、类型、电影描述。 + +Item对象是种简单的容器,保存了爬取到得数据。其提供了类似于词典的API以及用于声明可用字段的简单语法。所以可以声明Item为如下形式。 +class DoubanItem(scrapy.Item): + # 排名 + ranking = scrapy.Field() + # 电影名称 + title = scrapy.Field() + # 评分 + score = scrapy.Field() + # 评论人数 + pople_num = scrapy.Field() + # 导演 + director = scrapy.Field() + # 年份 + year = scrapy.Field() + # 地区 + area = scrapy.Field() + # 类型 + clazz = scrapy.Field() + # 电影描述 + decsription = scrapy.Field() + +我们抓取到相应的网页后,需要从网页中提取自己需要的信息,可以使用xpath语法,我使用的是BeautifulSoup网页解析器,经过BeautifulSoup解析的网页,可以直接使用选择器筛选需要的信息。有一些说明写到代码注释里面去了,就不再赘述。 +Chrome 也可以直接复制选择器或者XPath,如下图所示。 + +class douban_spider(Spider): + + count = 1 + + # 爬虫启动命令 + name = 'douban' + + # 头部信息,伪装自己不是爬虫程序 + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36', + } + + # 爬虫启动链接 + def start_requests(self): + url = 'https://movie.douban.com/top250' + yield Request(url, headers=self.headers) + + # 处理爬取的数据 + def parse(self, response): + + print('第', self.count, '页') + self.count += 1 + + item = DoubanItem() + soup = BeautifulSoup(response.text, 'html.parser') + + # 选出电影列表 + movies = soup.select('#content div div.article ol li') + + for movie in movies: + item['title'] = movie.select('.title')[0].text + item['ranking'] = movie.select('em')[0].text + item['score'] = movie.select('.rating_num')[0].text + item['pople_num'] = movie.select('.star span')[3].text + + # 包含导演、年份、地区、类别 + info = movie.select('.bd p')[0].text + director = info.strip().split('\n')[0].split(' ') + yac = info.strip().split('\n')[1].strip().split(' / ') + + item['director'] = director[0].split(': ')[1] + item['year'] = yac[0] + item['area'] = yac[1] + item['clazz'] = yac[2] + + # 电影描述有为空的,所以需要判断 + if len(movie.select('.inq')) is not 0: + item['decsription'] = movie.select('.inq')[0].text + else: + item['decsription'] = 'None' + yield item + + # 下一页: + # 1,可以在页面中找到下一页的地址 + # 2,自己根据url规律构造地址,这里使用的是第二种方法 + next_url = soup.select('.paginator .next a')[0]['href'] + if next_url: + next_url = 'https://movie.douban.com/top250' + next_url + yield Request(next_url, headers=self.headers) + +然后在项目文件夹内打开cmd命令,运行scrapy crawl douban -o movies.csv就会发现提取的信息就写入指定文件了,下面是爬取的结果,效果很理想。 + + +
+ + Read More ~ +
+
+
+ + + +
+ + 上一页 + + + 下一页 + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/page/9/index.html b/page/9/index.html new file mode 100644 index 00000000..1e4b569d --- /dev/null +++ b/page/9/index.html @@ -0,0 +1,1779 @@ + + + + + + + + Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ + +
+

+ + 谈一下写作的重要性,每个人都应该养成写作的习惯 + +

+ +
+ + + + +
+ +
+ 关于写作的重要性,你可能在其他地方也见过一些描述,大致的说法都差不多,如果本文某些字句与你已经见过的文章有雷同,那纯属巧合,我仅从个人这几个月的感受出发来说。 +我从三月份开始,在公众号上面发一些文章,其实从这几个月的表现来看,是把公众号当作博客来用了,我的初衷没有想着靠公众号赚钱,所以我的分享很随意,主要是技术、认知、阅读方面的东西。如果不喜欢,主动权在你手里,大可取关;如果喜欢,我的文章能让你少走一些弯路,那么我自己的目的达到了,给不给赞赏无所谓,其实心里还是希望你给的,咱没必要把自己放的太清高。 +今天微信订阅号改版了,新版的订阅号基本形态变成了信息流,关于产品我还不太懂,但是我觉得这次改版对于作者的个人品牌形成不利,没内容的公众号应该会被取关很多粉丝,但是忠实粉丝也更难找到自己喜欢的“博主”了,所以以后,请认准作者。 +很多人也有撰写博客的习惯,我很赞同这个做法,关于写作的好处我觉得有以下几点,只有认真去做了,才能体会到它带给自己的成长。 +第一,写作是整理自己思维的过程,写作能力是一种重要的能力,不一定要多好的文采,但是不是每个人都能把语言组织的有条有理。现在的时代,没有铁饭碗,你需要不停的学习才能立于不败之地,很多人觉得写作是浪费时间,其实不然,写作是对已学知识的整理过程,输出其实是更高层次的输入。拿我之前写的朴素贝叶斯实现拼写检查器来说,其中那个贝叶斯公式推导是我花了很多分钟才想出来的,就好像老师教给学生一碗水,那么老师就必须具备一桶水才行。 +第二,写作是个人品牌的建立过程,可以说微信已经成为了中国互联网的小小代名词,农村大叔大妈手机上面最可能出现的软件就是微信,微信打通中国互联网的最后一环,在这么大的平台上,你分享的内容对别人来说是有帮助的,那么你的个人品牌就已经逐渐在形成了,这是个人影响力的提升。个人品牌在以后一定会很重要,个人品牌在日常生活其实有体现,我相信每个人的微信都会屏蔽几个人的朋友圈信息吧,经常在朋友圈发一些无用信息、垃圾信息,这其实就是个人品牌的损失。 +第三,通过写作你能交到很多朋友,而且通过这种方式所交到的朋友都是优秀的,他们会对你的成长起到促进作用,而你也会因为和他们交流而在不知不觉中得到提升,真正的朋友是相互促进的。我这几个月交到的朋友,刷新了我的认知,偏见来源于无知,在这个过程,我的认知得到了很大的提升,认知这玩意也不太好描述。举个例子,大概在大二的时候,我看到一篇文章说高中物理中所学的电子、质子等概念是错的,将要被新的知识体系取代,那时二话不说就转发朋友圈了,但没过几天就发现这其实是一个虚假信息;前段时间,中兴被美国制裁了,然后就有一些自媒体作者为了吸引流量,乱写一通什么华为宣布将要退出美国市场的消息,我的第一直觉就是这是虚假信息,然后我去验证了自己猜测的正确性,而周围很多人竟无脑式的选择了相信这条消息,还给我分析为什么华为要退出美国市场。这在我看来就是认知水平的一个体现,或者贴切一点叫信息素养(这个词不是我发明的),我现在对于信息的掌握已经明显快于周围的同学了,而且掌握的也比周围同学更加全面。 +最后说一点,没必要为了写作而写作,经常在知识星球看到有人问问题,说自己的写不出东西来,怎么办?这就是自己的输入不够,自己体内没有实质的东西,如何能达到输出呢?更别说高质量的输出了。 +我以后的文章主要是机器学习和提升认知方面的,最近更文有点慢,其一是自己也刚开始接触机器学习不久,要写出一篇比较好的文章,需要几天的输入;其二马上要毕业了,繁忙于各种琐碎的事情无法自拔。 +总的来说,写作利大于弊,如果你有闲心,看一下我几个月前发的文章,再和我现在的文章做个对比,你能看到我的变化,在文章逻辑、排版等等方面都或多或少的有一些提升,所以我建议你如果空闲时间比较多,也可以尝试尝试写作,自己的成长过程会在字里行间被记录下来。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 动态规划算法优化实例——如何求解换钱的方法数 + +

+ +
+ + + + +
+ +
+ 这是我的人生处女面遇到的一个面试题,是在去哪儿网二面遇到的,那时非常的紧张,还没有复习,所以第一次面试理所应当的挂了。文章对问题进行逐步的由简到难进行优化,基本上是代码,看懂代码才能理解,也为类似问题提供了基本的解决思路。 +题目描述: + +让你把一张整钱找零,即假设你拥有不同且不限量的小额钱币,你需要统计共有多少种方法可以用手中的小额钱币兑等额兑换一张大额钱币。 +即:给定一个元素为正数的集合(元素不重复)代表不同面值的钱币,再给一个整数,代表要找零的钱数,求共有多少种换钱方法? + +递归求解 +现在有1、5、10元三种面值的纸币,需要找零100元,那么可以做如下分析: +用 0 张 5 元换,剩下的用 1、10 元换,最终方法数为 count0; +用 1 张 5 元换,剩下的用 1、10 元换,最终方法数为 count1; +...... +用 100 张 5 元换,剩下的用 1、10 元换,最终方法数为 count100; + +最终的换钱方法总数就为 count0 + count1 + ...... + count100。 + +根据上面的分析可以写出下面的递归解决方案: +public static int coin(int money[], int target){ + if (money == null || money.length == 0 || target &lt; 0){ + return 0; + }else { + return slove(money, 0, target); + } +} + +// 用money[index, length-1]换钱,返回总的方法数 +private static int slove(int money[], int index, int target){ + int res = 0; + if(index == money.length){ + if (target == 0){ + res = 1; + }else { + res = 0; + } + }else { + for (int i = 0; money[index] * i &lt;= target; i++) { + res += slove(money, index+1, target-money[index]*i); + } + } + return res; +} + +优化递归 +可以看到,上面的程序在运行时存在大量的重复过程,比如下面两种情况,其后所求结果是一样的。 +兑换 100 元,已经使用了 0 张 1 元、1 张 2 元,剩下的用 5 元和 10 元兑换; +兑换 100 元,已经使用了 2 张 1 元、0 张 2 元,剩下的用 5 元和 10 元兑换; + +可以发现,这两种情况后面都是求解同一问题,重复的对同一个问题求解,就造成了时间的浪费,因此我们可以考虑将已经计算过的结果存下来,避免重复的计算,所以有下面的优化方案。 +public static int coin(int money[], int target){ + if (money == null || money.length == 0 || target &lt; 0){ + return 0; + }else { + /** + * map[i][j]表示p(i,j)递归回的值 + * 其中-1表示该递归过程计算过,但是返回值为0 + * 0表示该递归过程还为计算过 + */ + + int map[][] = new int[money.length+1][target+1]; + return slove(money, 0, target, map); + } +} + +private static int slove(int money[], int index, int target, int map[][]){ + int res = 0; + if(index == money.length){ + if (target == 0){ + res = 1; + }else { + res = 0; + } + }else { + int val = 0; + for (int i = 0; money[index] * i &lt;= target; i++) { + val = map[index + 1][target - money[index]*i]; + if (val != 0){ + if (val == -1){ + res += 0; + }else { + res += val; + } + }else { + res += slove(money, index+1, target-money[index]*i, map); + } + } + } + + if (res == 0){ + map[index][target] = -1; + }else { + map[index][target] = res; + } + return res; +} + +动态规划 +上面对递归方法的优化已经能看到动态规划的影子了,这是一个二维的动态规划问题,我们定义dp[i][j]的含义为:使用money[0...i]的钱币组成钱数j的方法数。所以可以得出以下面的动态规划解法: +public static int coin(int money[], int target){ + if (money == null || money.length == 0 || target &lt; 0){ + return 0; + } + + int dp[][] = new int[money.length][target+1]; + + // 第一列表示组成钱数为0的方法数,所以为1 + for (int i = 0; i &lt; money.length; i++) { + dp[i][0] = 1; + } + // 第一行表示只使用money[0]一种钱币兑换钱数为i的方法数 + // 所以是money[0]的倍数的位置为1,否则为0 + for (int i = 1; money[0] * i &lt;= target; i++) { + dp[0][money[0] * i] = 1; + } + + for (int i = 1; i &lt; dp.length; i++) { + for (int j = 1; j &lt; dp[0].length; j++) { + for (int k = 0; j &gt;= money[i] * k; k++) { + // dp[i][j]的值即为,用money[0...i-1]的钱 + // 组成j减去money[i]的倍数的方法数 + dp[i][j] += dp[i-1][j-money[i]*k]; + } + } + } + + return dp[money.length-1][target]; +} + +继续优化 +可以发现上面的动态规划解法有三层循环,因为是二维的动态规划问题,前两层没办法去掉,但是第三层依旧很耗时间,继续优化可以得到下面的结果。 +public static int coin(int money[], int target){ + if (money == null || money.length == 0 || target &lt; 0){ + return 0; + } + + int dp[][] = new int[money.length][target+1]; + + for (int i = 0; i &lt; money.length; i++) { + dp[i][0] = 1; + } + for (int i = 1; money[0] * i &lt;= target; i++) { + dp[0][money[0] * i] = 1; + } + + for (int i = 1; i &lt; money.length; i++) { + for (int j = 1; j &lt;= target; j++) { + /** + * 通过分析可以发现,dp[i][j]的值由两部分组成 + * 1:用money[0...i-1]的钱组成钱数为j的方法数 + * 2:用money[0...i]的钱组成钱数为j-money[i]*k(k=1,2,3....)的方法数 + * 对于第2种情况,实际上累加的值就是dp[i][j-money[i]] + * 所以直接使用dp[i][j-money[i]]即可 + */ + dp[i][j] = dp[i-1][j]; + if (j &gt;= money[i]){ + dp[i][j] += dp[i][j-money[i]]; + } + } + } + + return dp[money.length-1][target]; +} + +空间压缩 +可以看到每次更新dp[i][j],dp[i][j]的值只与前一行和当前行前面的元素有关系,而我们只需要最后的一个结果就行了,那么前面存的元素实际上会造成空间的浪费,进一步可以在空间上进行优化。 +我们只需要定义一个一位数组,然后对该数组进行滚动更新就可以了,只要按照合适方向去更新数组,同样能达到上面的效果。 +public static int coin(int money[], int target){ + if (money == null || money.length == 0 || target &lt; 0){ + return 0; + } + + int dp[] = new int[target+1]; + + // 第一行,只用money[0]兑换钱 + // 所以只能兑换为money[0]的倍数,将这些位置置为1 + for (int i = 0; money[0]*i &lt;= target; i++) { + dp[i] = 1; + } + + for (int i = 1; i &lt; money.length; i++) { + for (int j = 1; j &lt;= target; j++) { + + // 与前一步相比,少了dp[i][j] = dp[i-1][j]; + // 因为这里在进行dp[j] += dp[j-money[i]];之前 + // dp[j]的值就已经是dp[i-1][j]了 + if (j &gt;= money[i]){ + dp[j] += dp[j-money[i]]; + } + } + } + + return dp[target]; +} + +到这一步就不再有优化空间了,这个问题很值得记录下来,很多笔试、面试题都可以按这个模子进行套,对于只需要最优解的动态规划问题也可以套用上面的空间压缩思路,多总结、多练习总是没有问题的!这个解题思路第一次看到是左程云在牛客网上讲解的,他也写了一本算法相关的书比较不错,叫做程序员代码面试指南,大四、研三、刚入职的新人建议可以买一本读读,对自己编码技能的提升绝对又很大的帮助。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 年轻不要给自己设限 + +

+ +
+ + + + +
+ +
+ 初入象牙塔时乘坐了 60 多个小时的火车,后面基本都选择了飞机作为出行交通工具,毕业时再次选择了火车这一交通工具回家,再看一次从东北到西南的沿途风景,无奈火车居然能晚点两小时,这篇文章是在火车上为打发时间写的,希望对您有所帮助。 +记得大一入学前,买了一本覃彪喜写的《读大学,究竟读什么》,那时候对于里面有一些观点不赞同,觉得大学这么神圣的地方,怎么被作者写成那样,读完一遍只是抱有一种怀疑的态度,四年之后的今天,我觉得这本书值得一看,大部分内容还是有用的,不过有一些内容还是很偏激的,自己过滤掉就好了。 +现在回头看,大学最需要的应该是经历,我也是大三才算明白这个道理吧(这个道理应该不止学生能实用)。我认为本科阶段是容错率最高的阶段,这个时候你干什么都不怕,犯了错也没有什么大碍,最重要的是犯错(不犯错更好)的那个过程。 +年轻人做什么都是学习,不要给自己设限,在一无所有的年龄就应该多经历,因为这时候的容错率很高,试错成本低就要勇于试错。(这句话可能之前的文章说过,大同小异的话你也能在别的好文章里面见到) +现在的家长,也包括孩子,大多数喜欢拿一些证书、奖杯出来炫耀,而现在大学里面的个性化保研政策看的就是各种奖项。我更看重的是比赛的过程,但是在学校有一个怪现象:我不想办事,只想你给我挂一个名,到出去比赛的时候,看到所报的项目自己不是第一作者,都不愿意去比赛,觉得是在浪费时间。 +我个人在这里面算一股清流了,我很喜欢跟着出去比赛,因为比赛的过程能教会你很多在学校学不到的东西,给不给我奖状无所谓,只要给我报销差旅费就行了,这一点对我这种穷学生来说跟重要,想出去看看世界长长见识,自己又没有钱,学生群体中随随便便就拿出几千块钱的人还是不多,所以这是我找到的最好的长见识的方法了,上大学前连小县城都没出过的我,通过比赛到过佛山、深圳、重庆、日照等地,这对我算是一生的财富。 +写到这里,脑子里面满满的全是回忆,发现想说的太多,全写出来可能会上万字,先不写了,以后分开写个系列的也行,下面说几句干货道理吧,过来人的总结。 +第一,少拿学校的光环往自己身上套,和你没关系,对于我的学校动不动就拿哈军工说事(中国人都喜欢把自己和名家扯上关系,看起来显得有一些历史文化底蕴),完全是不自信的表现。作为唯一一个首批进入211缺不是985的学校,我觉得学校一直在啃老底。类似的文章还有之前写的谈一点关于名校的话题和刘大写的除去大公司的光环,你还剩点啥? +第二,学校教不了你多少东西,大学阶段和高中阶段最大的区别是,高中有人赶着你学,而且还有人给你指明学习的方向,但是大学没有人告诉你学什么,也没有人赶着你学习,所以培养自学能力和判断选择能力很重要,我个人认为这是大学阶段最应该学习到的东西。 +第三,如果大学只学习了课本中的内容,那还不如不上大学,不得不承认,大学课本内容都属于经典中的经典,但是学校的要求太低,所以要自己去练习,而且很多老师所教授的东西属于过时的知识,有的课就应该逃掉,利用这个时间去做更有用的事情。 +第四,真诚待人,学生阶段所交的朋友没多少功利性,能交几个铁哥们最好。我个人觉得比较实用的一个看人标准,你只需要看某个人对待其他人是什么样,就大概知道他对你会是什么样了;好比谈恋爱,你不要妄想渣男渣女到你这里就不渣了(小概率事件)。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 西凉忆 + +

+ +
+ + + + +
+ +
+ +夜雨滴,淅淅沥沥,伶仃至天明。 +忆往景,伤流景。 +世事沧桑敌不过一句悲凉,浮生蹉跎尽在笔墨中泛黄。 +几多欢喜,几多忧愁,曾经挫败,也曾迷茫。 +再回首时,我将深情拥入梦,拥有瞬间的感动却也足够。 +故事中的悲与合,百般萧瑟,千般落寞。 + +《浪淘沙》 +帘外月如钩,好梦难留。 +寻思翻悔几时休,无那安排辞去意,相聚分流。 +人世总多秋,恰上心头。 +平生却道愿堪忧,暗忆欢期眠不得,何恨离愁。 +《南乡子.冷风淅》 +冷风淅,疏雨斜,岸花零落杪雀喑。 +轻舟短棹临野渡,归何处? +隐隐两三烟柳树。 +《忆江南》 +碧云卷 碧云卷,暮日烟霞浓。 +翠楼水阁花树掩,斜晖眽眽又几重。 +望断石桥东。 +《如梦令》 + +乙未十月初五夜无眠,念往日兮,不觉已而四载有余,徒感悲戚,故作此。 + +昨夜修竹风露, +浅睡迷香清雾。 +罗帐月为魂, +痴念已然终误。 +虚度,虚度。 +回忆落空如墓。 +《钗头凤》 +待六月,激情朗,意气凌冠挥缨枪; +将袖扫,任逍遥,马踏平川,舒眉一笑,傲傲傲! +舞三江,梦飞扬,落日无边江不尽; +趁今朝,更须忙,题名金榜,折桂香飘,妙妙妙! + +花翎水裳,丹青风华暗染霜。 +夜未央,几度琉璃徬。 +谁笑我儿女情长,步步断肠。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 大叔激励|小宫黛雅|上饶熊孩子|法国小羊做新生|体育强健身心|地震回忆 + +

+ +
+ + + + +
+ +
+ 最近机缘巧合和几个中年大叔聊天,从眼神里面能看出来他们是讲的真心话,平时也非常反感长辈们给自己讲的一些大而空的道理,但仔细想想这些话其实并不是完全没用。 +不管对方是世俗眼光中的成功人士还是失败人士,他们都会有自己没有实现的理想愿望,成功人士没有好好陪家人孩子,他们可能喜欢特地找个时间,不管接收方是否感动,但至少他自己已经得到心灵的慰藉了;失败人士没有给到家人足够富裕的生活,这时候他们都很喜欢看努力、奋斗、自强不息......一类词汇堆砌的文章。而老人的精神世界已经不是奋斗了,而是人这一生......,所以如果陪老人说话,你只需要把话题引入到佛身上,然后静静的听就可以了。 + +忘了啥时候无意中加了一个高中生,目前正在读高二,与大多数人不同的是,她属于双性恋人群,并且更偏爱小姐姐,出口成脏,上课偷偷玩手机,宛然一个大家脑中所构想的问题少年。 +但是几个月前突然变了,一下子变得有礼貌起来了,我问她是因为什么变了,她告诉我是因为我说的一句话:不说脏话是对人起码的尊重。这句话让她想明白了,我在这里偷偷说一句,我也不知道当时说的这句话对不对,其实就是随口一说,让一个小娃娃变好了,那也好。 +在我的循序诱惑下,小姑娘已经不藏手机了,每周日自觉的把手机交上去,每周五手机发下来再玩,所以我和她的聊天记录现在基本上变成了,周日我发一句“加油”,周五下午我会收到一句“突然出现”。 +小姑娘自己是同性恋的事情不敢给爸妈说,我想这是不是因为父母与孩子天然就有一种屏障,以至于无法与他们进行心灵上的沟通,要想能够得到孩子的信任,父母应该多站在孩子角度考虑考虑问题,尽量不要拿「我都是为你好」去搪塞孩子。 + +最近上饶杀熊孩子案很火,女生父亲杀人当然是不对对,但是对于这种校园霸凌这件事,校方与男生家长都摆出无所谓的态度,才导致悲剧的发生,其实校园霸凌的核心不在熊孩子身上,而是在熊家长身上。 +吴军在其《大学之路》上有写道:“在我的印象中,父母晚上从来不参加应酬,甚至不看什么电视剧,总是非常有规律的学习,我的母亲现在快 80 岁了,依然每天坚持学习,父母们并不知道,他们在对我们兄弟的教育上最成功之处,是以他们的行为潜移默化地影响了我们,让我们渐渐养成了终身学习的习惯”。借句公益广告词——父母是最好的老师。 + +最近一个很搞笑的新闻,法国一所小学为了拯救学校不被关闭,招了 15 只羊作为新生。简单来说就是,根据法国政策,当学生数量少于 261 时,学校将被迫停办,而法国人在抗议上也很有创意,反正规定上又没有写招生的物种必须是人,正好我们家有许多只适龄绵羊,都送去上学吧! +牧民说到做到,这周二就和校长及家长达成一致,带领着自己家50只绵羊赶到学校招生办公室报道,招生办的老师热情的接待了羊以及送羊报道的牧羊犬们,在检查了所有绵羊的出生证后,最终有15只年龄在3-6岁之间的适龄绵羊顺利通过合法注册,成为小学的一年级新生,让这所小学不至于被停学。 + +我们常常把体育锻炼与健身、减肥挂钩,前段时间杭州程序员小伙伴突然精神崩溃,引发了一大波人关注,在校大学生跳楼也是常有的事,但是去细细看一下,是不是很少会看到体育生跳楼。 +大学生跳楼无非是意志力脆弱、心理自我调节能力差等方面影响的,而体育锻炼恰恰会无意中去缩短了这些短板,我主要不是想说体育锻炼有多么多么好,因为这是大家都知道的,而是想说一件事的作用范围可能比你想象的要大的多。 + +今天是母亲节,无意中突然记起了初中的一篇阅读理解——《那支枯萎的康乃馨》,读者大人可以去搜搜读读,从另一个角度看看母亲节送礼这件事,不知道现在的朋友圈孝子还多不多。 +同样今天是 5.12 汶川地震纪念日,地震发生时我还在读 6 年级,记得整个床都摇的快要倒了,房子上面的瓦片蹭蹭的往下掉,但是学生中间没有一个害怕了,原因只是因为我们并不知道地震这个词。 +去年的 5.28 日,吉林松原发生了一次小的地震,哈尔滨有震感,作为也算经历过地震的人,自然异常的敏感,熟睡中的我一下就惊醒了,立刻意识到地震了,然后在那三四秒的时间里面,时间好像停住了一样,我都数不清那几秒时间脑海里滑过了多少记忆,我体会到了面对死亡的感觉,几秒过后,根据汶川地震的经验,本次只是小地震,所以我又躺下睡觉了,然后室友们都跑出去了。 + +出来工作也有大半年了,之前以为第一份工作是学习技术的,现在才发现第一份工作首先应该学习的是做事态度,在学校如果某件事不想做或者太困难,那么完全可以选择不做,但是在企业不行,一些恶心的事情必须有人得去做,学生总是缺乏应有的责任感,但责任感是优秀员工的基本条件。 +我之前在公司怼过两次领导,怼完领导之后他反倒对我更好了,给我解决问题的速度超快,现在回想起来可能自己实际上做错了,为啥不能心平气和的去解释,而采取暴力沟通,今后我也要学习怎么做一个圆滑的俗人。 +保持一个虚心的学习态度是异常重要的,认清自己资质平平的现实。一些人在公司认为经理是个傻逼,总监是个马屁精,董事长屁股决定脑袋,那不过是因为你没到那个位置,没办法理解他们考虑问题的角度而已。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 知识图谱如何构建?——经济责任审计知识图谱构建案例实战 + +

+ +
+ + + + +
+ +
+ +参考: +汉语言处理包 HanLP:https://github.com/hankcs/HanLP +中文文本分类:https://github.com/gaussic/text-classification-cnn-rnn +农业知识图谱:https://github.com/qq547276542/Agriculture_KnowledgeGraph +事实三元组抽取:https://github.com/twjiang/fact_triple_extraction +中文自然语言处理相关资料:https://github.com/mengxiaoxu/Awesome-Chinese-NLP +开放中文实体关系抽取:http://www.docin.com/p-1715877509.html + +自 2012 年 Google 提出“知识图谱”的概念以来,知识图谱就一直是学术研究的重要方向,现在有很多高校、企业都致力于将这项技术应用到医疗、教育、商业等领域,并且已经取得了些许成果。Google 也宣布将以知识图谱为基础,构建下一代智能搜索引擎。 +现在已经可以在谷歌、百度、搜狗等搜索引擎上面看到知识图谱的应用了。比如在 Google 搜索某个关键词时,会在其结果页面的右边显示该关键字的详细信息。在几个常用的搜索引擎中搜索知识时,返回的答案也变得更加精确,比如搜索“汪涵的妻子”,搜索引擎会直接给出答案“杨乐乐”,方便了用户快速精准的获取想要的信息。不过目前的搜索引擎只有少部分搜索问题能达到这种效果。 +关于知识图谱是什么,我想就不用介绍了,这种通过搜索引擎就能轻松得到的结果写在这里有点浪费篇章,并且我对知识图谱的理解也不深,不敢夸夸其谈,只是把自己这一段时间以来的工作做一个总结。 +本文只相当于以经济责任审计这一特定领域构建了一个知识图谱,仅仅是走了一遍流程,当作入门项目,构建过程中参考甚至抄袭了别人的很多方法与代码,末尾都会给出参考的项目等等。 + +上图是我构建经济责任审计知识图谱的流程,看起来很繁琐,但只要静下心看,个人觉得相对还算清晰,箭头都有指向。下面就一步一步进行说明。 +数据获取 +数据获取主要分为两部分数据,一部分是新闻类数据,我把它用作文本分类模型的训练集;另一部分是实体数据,为了方便,我直接把互动百科抓取的词条文件作为实体,省了属性抽取这一环节。 +因为本文构建的是一个经济责任审计领域的知识图谱,所以作为文本分类模型训练集的数据也应该是经济责任审计领域的。这里主要抓取了审计署、纪检委、新浪网的部分新闻。 + +像上面的图一样,新闻类网站一般都有搜索框,为了简单,所以我直接用搜索框搜索“经济责任审计”,然后从搜索结果中抓取新闻数据,即认为是经济责任审计相关的文本。抓取新闻类网站使用了 chrome 模拟用户进行访问。最终获得了 3500 多条新闻文本。 +领域词汇判定 +领域词汇判定,本文构建的不是开放领域的知识图谱,所以需要采用一种方法来判定所抓取的内容是否属于经济责任审计领域。领域词汇本文的方法实际上是领域句子判定,直接使用了大神的项目。CNN-RNN中文文本分类,基于tensorflow。也看到有人通过改进逻辑回归算法,在进行领域词汇的判定。 +我判定领域词汇的逻辑是这样的,一个词语即使是人类也不一定能确定它是否属于经济责任审计领域,但是每个词语都会有它的含义解释对不对,一个词语的解释就是一段话。我用网上的新闻训练出一个判断一段话属于哪个领域的模型,然后把词语的解释放到模型了里面去,如果模型给出的结果是属于经济责任审计领域,那则认为这个词语属于经济责任审计领域。 +实体关系抽取 +知识图谱的基本单位为(实体1,关系,实体2)这样的三元组,实体是直接从互动百科获取的词条,关系由两部分组成,一部分来自 wikidata 所提供的关系,这一部分直接从 wikidata 爬取即可得到,另一部分使用的是基于依存句法分析的开放式中文实体关系抽取,已经在前面的文章发过了。 +知识存储 +有了实体和实体关系,那么把这些数据进行筛选,然后入库,通过直观的页面展示,就可以了。这里使用的数据库是 neo4j,它作为图形数据库,用于知识图谱的存储非常方便。知识的展示使用了别人的项目,仅仅是把里面的数据换掉了而已,感谢大神的无私。 +当然你也可以选择使用关系型数据库,因为我做的经济责任审计知识图谱不够深入,所以做到最后展示的时候,发现其实用我比较熟悉的 MySql 更好,相比 NOSql 我更熟悉关系型数据库,而且 MySql 有更大的社区在维护,它的 Bug 少、性能也更好。 +最后放几张效果图 + + +下面是以“职业”为关系查询条件所得出的结果。 + +总结一下 +只是对几个月工作的梳理,大多数核心代码都改自现有的代码,所有的数据都来自于网络,与知识图谱相关的公开技术较少,我也只是尝试着做了一下,虽然很菜,也可以对大致的技术路线、流程有一个简单的了解,主要工作都是自然语言处理的内容。后期可以利用现在的知识图谱构建智能问答系统,实现从 what 到 why 的转换。 +以下内容更新于 2020 年 3 月。 +在毕业前收到了电子工业出版社和另一家出版社的写书邀请,我和电子工业出版社签订了写书合同,从还未毕业开始断断续续写作了一年的时间,因为自己的懒惰,加上内容中涉及到大量爬虫,而且爬目标网站是政府网站(不允许爬),另外 19 年网上时不时曝出某某程序员因爬虫而入狱的故事,出版社和我难免不会恐惧,我也正好找到了不再继续写下去的理由。 +花了点时间把以前的程序,书籍已经写成的内容整理了一下,放在了 economic_audit_knowledge_graph 中,所有资料都在里面,希望能帮助到自然语言入门的小伙伴,我自己已经不做这个领域了! + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何抽取实体关系?——基于依存句法分析的事实三元组抽取 + +

+ +
+ + + + +
+ +
+ +参考: +HanLP 自然语言处理 +基于依存分析的开放式中文实体关系抽取方法 +命名实体三元组抽取参考自fact_triple_extraction + +这一段时间一直在做知识图谱,卡在实体关系抽取这里几个月了,在 Github 上面看到有人使用卷积神经网络训练模型进行抽取,自己也尝试了一下,但是一直苦于没有像样数据去训练,而标注训练集又太费时间了,我不太愿意干体力活。另外自己也不会什么机器学习、深度学习之类的技术,而且毕业设计都是有时间要求的,所以采用了一个低档次的方法,基于依存句法分析的实体关系抽取,记录一下心得,方便日后忘记可以再找回来。 +论文给出了 8 种中文关系的表达方式,并且最后给出了一个采用正则表达式语法指出表达,核心就是谓语动词表示关系,即关系表述中一定得有动词。 +状语*动词+补语?宾语? + +我不太赞同把宾语也当作关系表述的一部分,论文指出“p4生于山西”应该抽出(p4,山西,生于山西),我认为关系不应该表述为“生于山西”,所以我把关系表述改为下面的样子了。 +状语*动词+补语? + +这篇文章只是作为一个方法介绍,我自己先看了一遍,能够保证我下次看到这篇文章,可以立马回忆起自己的实现方法,希望你看了也能了解方法,看不懂的话,我表示抱歉,浪费您的时间了,我已经尽可能写到简单了。 +先来看几个简单句子吧: +主谓宾关系:刘小绪 生于 四川 +// 这个三元组很明显:(刘小绪,生于,四川) + + +动补结构:刘小绪 洗 干净 了 衣服 +// 如果套用主谓宾关系就是:(刘小绪,洗,衣服) +// 但是这里描述的是一个状态,是刘小绪把衣服洗干净了 +// “干净”是动词“洗”的补语,所以还应该提取出一个如下三元组 +// (刘小绪,洗干净了,衣服) + +状动结构:父亲 非常 喜欢 跑步 +// 这句和上面很像,主谓宾关系是:父亲喜欢跑步 +// “非常”用于修饰“喜欢” +// (父亲,非常喜欢,跑步) + +介宾关系:刘小绪 就职 于 学校 +// 如果直接把这个三元组抽取为(刘小绪,就职,学校),很别扭 +// “于”和“学校”是介宾关系,它们的关系应该是:就职于 +// (刘小绪,就职于,学校) + +宾语前置:海洋 由 水 组成 +// “海洋”是“组成”的前置宾语 +// “由”是“组成”的状语 +// “水”和“由”是介宾关系 +// 所以上面的句子没有明确的主谓关系,需要我们判断 +// 抽出的三元组应该为:(水,组成,海洋) + +HanLP 提供了两种依存句法分析的器,默认采用的是基于神经网络的依存句法分析器。依存句法分析就是将句子分析成一棵依存句法树,描述各个词语之间的依存关系,即指出词语之间在句法上的搭配关系。 +有了上面所说的依存句法树,其实我们只需要进行各种判断就可以了。先做出下面的一点说明,就拿第一个例子来说。 +原文:刘小绪生于四川 + +# 这是分词结果 +[刘小绪/nr, 生于/v, 四川/ns] + +#这是句法分析结果 +刘小绪 --(主谓关系)--&gt; 生于 +生于 --(核心关系)--&gt; ##核心## +四川 --(动宾关系)--&gt; 生于 + +为了方便理解,也为了方便程序的编写,我把他们组织成了下面的形式,为每一个词语都建一个依存句法字典。 +刘小绪:{} +生于:{主谓关系=[刘小绪], 动宾关系=[四川]} +四川:{} + +然后只需要写出类似于下面的程序段就可以抽出关系了。 +// 主谓宾关系:刘小绪生于四川 +// dic是这个词语的依存句法字典 +if (dic.containsKey(&quot;主谓关系&quot;) &amp;&amp; dic.containsKey(&quot;动宾关系&quot;)){ + + // 当前的词语,用上面的例子来说,relation=“生于” + String relation = curWord.LEMMA; + + + // 用循环遍历,是因为关系列表里面不一定只有一个词语 + for (CoNLLWord entity1: + dic.get(&quot;主谓关系&quot;)) { + + for (CoNLLWord entity2: + dic.get(&quot;动宾关系&quot;)) { + + System.out.println(entity1.LEMMA + &quot;,&quot; + relation + &quot;,&quot; + entity2.LEMMA); + } + + } +} + +对于分词后的每个词语都进行上面程序段的操作。“刘小绪”和“四川”,关系字典都为空。而对于“生于”,关系列表里面既有主谓也有动宾,而自己本身就是动词,主谓宾就出来了。直接从主谓关系中拿出来词语作为 entity1,再拿上自己作为关系,最后拿出动宾关系中的词语作为 entity2。很明确的三元组(刘小绪,生于,四川)就出来了。 +最后给出一个程序运行结果图吧。 + +我个人觉得效果还行,在简单句子上面表现的差强人意,在长句子上面表现的差劲。注意上文使用的第三方包随着时间的推移肯定会改一些接口,源码链接:entity_relation_extraction + +
+ + Read More ~ +
+
+
+ +
+

+ + 阅读高于自己的作品,远离精神毒品 + +

+ +
+ + + + +
+ +
+ 关于阅读与写作的重要性,可能每个人都多少有一些概念,关于写作的重要性可以看我之前系的谈一下写作的重要性。阅读是打开世界的大门,写作可以提升语言组织能力。 +自己就读的专业是计算机科学与技术,生活在山区一直都没有接触过电脑。读大一大二的时候,我非常崇拜那些随随便便就能做一个网站、写一个复杂算法的大神。那时候在觉得只要专业能力足够强,只要技术掌握的足够深入,那就是 NB,所以我在大二之前从来都没有读过一本技术之外的书籍。 +现在回忆起那时候的想法真是太狭隘了,一个人掌握了某项专业技能,我们可以把这个人称之为「手艺人」,大学能教给我们一门手艺(可笑的是很多学生连手艺都没有学到),这门手艺可以帮助我们得以生存或者赚到一些钱,但是生活肯定不仅仅是钱,还有很多比钱更重要的事情。 +白领及以下阶层基本没有自己想去哪里就去哪里的自由,那怎么拓宽自己的视野呢?阅读和交朋友是个很不错的方式,但并不是所有阅读都是有效的,也不是所有朋友都是值得信任的,有的甚至是一种精神毒品。 +阅读需要挑选高于自己的作品,要能仰视它,才能攀登。阅读那些比自己低下的作品只会让自己更 low。现在的生活节奏很快,碎片化阅读成了很多人的阅读方式;有人抨击碎片化阅读,也有人提倡碎片化阅读,每个人有不同的观点,我个人是赞同碎片化阅读的,像得到、喜马拉雅等平台也把一个大的知识点切分的足够小了,一个小的知识点也就 10 来分钟,很适合碎片化的阅读,还不耽误其它事情。 +来自互联网的阅读内容和自己大多是平等的,每个人都在使用微信,但我相信很多人都有屏蔽一些人朋友圈的习惯,因为你会选择跟你脾气相同、你喜欢的、跟你水平接近内容去阅读;现在的 APP 也很多,而且很多首次注册都会让你选择自己感兴趣的内容,所以也就会失去挑战自我的机会。 +阅读是提升认知的重要手段,人与人之间的根本差距在于认知,如果读了一本书之后能让自己的认知得到提升,那么这就没白读;当然,如果读完一本书或是一篇文章之后能让有很大程度的转变,那这种认知的提升我相信是宝贵的财富,而且认识是伴随终身的。能达到这样效果的好文章、好书肯定少之又少,对我个人影响的最大的一本书是《把时间当作朋友》,我很推荐这本书,接触这本书算我认知上的一个转折点。 +也有一些阅读是日积月累对自己产生影响的。我曾经关注了近 150 个公众号,从里面精挑细选了一部分原创公众号留下,涉及技术、新闻、产品、理财等方面。我现在每天的阅读主要就是公众号阅读和付费加入的几个知识星球,以及得到上面的付费知识。 +日积月累,我筛选有效信息的能力也更强了,筛选信息能力在这个信息爆炸的时代是很重要的。 +有一次听到朋友说:“文章太长了,我不想看”。我没说话,只给了一个无奈的眼神,因为他只想要一个结论,因为这可以很轻松的获得一种愉悦感,就像打游戏一样可以快速的获得喜悦,而且对大脑来说,也是最容易接受的,但是慢慢地,大脑就会失去独立思考的能力。 +从产品角度讲,不得不佩服头条对人性的洞察,为了“懂你”,在自己的产品上加入推荐算法,但实际上是让你把时间花在他们产品的身上,把产品做到这种程度,不得不承认是很牛的。最近奈飞出了一步很棒的纪录片,叫做监视资本主义:智能陷阱 The Social Dilemma,片中将科技的负面清楚的呈现给我们,网络科技在某些方面已经逐渐演变为操纵社会的巨兽。 +像抖音、快手、头条、微博这些产品我认为基本都是在浪费用户时间(我还没有用过快手和微博,评价它们有点冒昧了),他们无异于精神毒品,吞噬你的时间。我并不是讨厌这些产品,我自己也喜欢体验新的产品,我只是觉得把宝贵的时间放到更有价值的事情上去,那么就会比周边人更加优秀。 +需要选择的是高于自己内容阅读,而不是把时间都花在那种不需要思考就能得到的愉悦上去;如果把大部分时间都花在轻易就能获得的愉悦感上,那么你应该正在一步步颓废。 +最后推荐自己写的另一篇文章大学生书单推荐 + +
+ + Read More ~ +
+
+
+ +
+

+ + 正则表达式入门,基础语法详解 + +

+ +
+ + + + +
+ +
+ 这两天一直在时不时的和 Neo4j 图数据库打交道。它的查询语句可以使用正则表达式,有一段时间没有自己写过正则表达式了,现在处于能看懂别人写的正则表达式,但是自己写不出来,语法规则都忘了。为了方便接下来的工作,所以特地复习复习正则表达式的语法。 +正则表达式简介 +正则表达式是用来匹配字符串的一系列匹配符,具备简介高效的特点,在很多语言中都有支持(java、python、javascript、php 等等)。在 windows 的 cmd 命令中也同样支持,例如使用命令 dir j*,那么只会罗列出所有以j开头的文件和文件夹。 +正则表达式基本语法 +正则表达式在在不同语言的支持语法略有不同,本文采用js的进行说明。js 中使用正则表达式的方法为str.match(/表达式/),即需要加两个斜杠。以下所有的代码段第一行为代码,第二行为返回结果,实验是在 chrome 控制台进行的。 +一直认为最好的学习方式就是实际操作,理论谁都能讲一大堆,但是实际做没做出来还真不知道。一个奇葩现象就是教软件工程的老师可能并没有在软件行业待过。 +普通匹配符 +普通匹配符能匹配与之对应的字符,默认区分大小写。 +&quot;Hello Regx&quot;.match(/H/) +[&quot;H&quot;, index: 0, input: &quot;Hello Regx&quot;, groups: undefined] + +正则标记符 + +i :不区分大小写 +g :全局匹配 +m :多行匹配(暂不管它,我用的少) + +参数直接加在最后一个斜杠的后面,比如&quot;Hello Regx&quot;.match(/regx/i),可以加多个参数。 +&quot;Hello Regx&quot;.match(/regx/i) +[&quot;Regx&quot;, index: 6, input: &quot;Hello Regx&quot;, groups: undefined] + +之前是表达式一旦匹配成功,就不再向字符串后面查找了,加上 g 后,表示进行全局查找。最后返回的是一个数组。 +&quot;Hello Regx&quot;.match(/e/g) +(2) [&quot;e&quot;, &quot;e&quot;] + +多匹配符 + +\d :匹配数字,即 0~9 +\w :匹配数字、字母、下划线 +. :匹配除换行的所有字符 + +需要注意的是,上面所有的匹配符都只能匹配一个字符。 +&quot;Hello 2018&quot;.match(/\d/g) +// 使用\d,匹配字符串中的所有数字 +(4) [&quot;2&quot;, &quot;0&quot;, &quot;1&quot;, &quot;8&quot;] + + +&quot;Hello 2018&quot;.match(/\w/g) +// 使用\w,匹配所有的数字和字母,需要注意没有匹配到空格 +(9) [&quot;H&quot;, &quot;e&quot;, &quot;l&quot;, &quot;l&quot;, &quot;o&quot;, &quot;2&quot;, &quot;0&quot;, &quot;1&quot;, &quot;8&quot;] + + +&quot;Hello 2018&quot;.match(/./g) +// 使用.,匹配所有字符,包括空格 +(10) [&quot;H&quot;, &quot;e&quot;, &quot;l&quot;, &quot;l&quot;, &quot;o&quot;, &quot; &quot;, &quot;2&quot;, &quot;0&quot;, &quot;1&quot;, &quot;8&quot;] + + +&quot;Hello 2018&quot;.match(/\d\w./g) +// 分析一下这个为什么匹配到的是201, +// 首先\d找到第一个数字2,匹配成功,紧接着\w匹配到0,然后.匹配到1 +// 整个正则表达式匹配成功,返回201 +[&quot;201&quot;] + + +&quot;Hello 20\n18&quot;.match(/\d\w./g) +// 这里匹配不成功,因为.不能匹配换行符,所以返回null +null + + +&quot;Hello 2018&quot;.match(/\w.\d/g) +// 首先看这个正则式,\w.\d,它要求最后一个字符是数字 +// \w.能一直匹配到空格,但是因为得满足\d,所以第一个匹配成功的是0 2 +// 因为是全局匹配,所以会接着匹配后面的018,也匹配成功 +(2) [&quot;o 2&quot;, &quot;018&quot;] + +自定义匹配符 +比如中国的手机号都是以 1 开头,第二位只能是 3、4、5、7、8,第 3 位只要是数字就行。如何匹配这样的字符串? + +[] :匹配[]中的任意一个字符 + +&quot;152&quot;.match(/1[34578]\d/) +// 第二个字符可以选择中括号中的任意一个 +[&quot;152&quot;, index: 0, input: &quot;152&quot;, groups: undefined] + +如果在 [] 添加了 ^,代表取反。即 [^] 表示除了中括号中的字符都满足。 +&quot;152&quot;.match(/1[^34578]\d/) + +null + + +&quot;1a2&quot;.match(/1[^34578]\d/) +// 只要不是[]中的字符,都满足,包括回车符 +[&quot;1a2&quot;, index: 0, input: &quot;1a2&quot;, groups: undefined] + +修饰匹配次数 +我们的手机号有 11 位,除了前 2 位有要求,其他9位度没有要求,那么是不是正则表达式就应该这样写呢? +1[^34578]\d\d\d\d\d\d\d\d\d + +很明显,这样写太麻烦,肯定有更好的方式,这里就可以修饰一下匹配次数啦。 + +? :最多出现 1 次 ++ :至少出现 1 次 +* :出现任意次数 +{} :分下面四种情况 + +{n}代表前面的匹配符出现 n 次 +{n, m}出现次数在 n~m 之间 +{n, }至少出现 n 次 +{, m}最多出现 m 次 + + + +例子很简单,一看就懂,不浪费时间。 +&quot;15284750845&quot;.match(/1[34578]\d{9}/) +[&quot;15284750845&quot;, index: 0, input: &quot;15284750845&quot;, groups: undefined] + + +&quot;15&quot;.match(/1[34578]\d?/) +[&quot;15&quot;, index: 0, input: &quot;15&quot;, groups: undefined] + + +&quot;152&quot;.match(/1[34578]\d?/) +[&quot;152&quot;, index: 0, input: &quot;152&quot;, groups: undefined] + + +&quot;152&quot;.match(/1[34578]\d+/) +[&quot;152&quot;, index: 0, input: &quot;152&quot;, groups: undefined] + + +&quot;15&quot;.match(/1[34578]\d+/) +null + +完整匹配 +按照上面的写法会出现下面的问题。 +&quot;ya15284750845&quot;.match(/1[34578]\d{9}/) +// 不是电话号码,也能匹配成功,需要进一步改进 +[&quot;15284750845&quot;, index: 2, input: &quot;ya15284750845&quot;, groups: undefined] + + +^ :在 [] 中代表取反,但在外面代表从开始匹配 + +&quot;ya15284750845&quot;.match(/^1[34578]\d{9}/) +// 现在就能从一开始匹配而且还得符合正则式才算匹配成功 +null + + +// 但是依旧会出现下面的问题 +&quot;1528475084523255&quot;.match(/^1[34578]\d{9}/) +// 不是电话号码也能匹配成功,还要改进 +[&quot;15284750845&quot;, index: 0, input: &quot;1528475084523255&quot;, groups: undefined] + + +$ :代表持续匹配到结束 + +&quot;1528475084523255&quot;.match(/^1[34578]\d{9}$/) +// 现在就能保证正确了,有^表示从开始匹配; +// 有$表示持续匹配到结束,即完全匹配 +null + +/* +需要注意的是,一个字符串从开始匹配和从结束匹配都没问题, +不代表整个字符串就没问题,比如 15284750845-15284750845 +这个字符串从开始和从结束匹配都能成功,但实际上是错的 +*/ + +特殊符号 +到这里发现正则表达式确实很强大,仅仅几个简单的符号就能匹配字符串,但是如果我们要匹配的字符本身就是前面用到的符号怎么办呢? + +匹配像$、^等特殊符号时,需要加转义字符\ + +&quot;1.&quot;.match(/./) +//因为.能匹配除换行的所有字符,所以匹配到1 +//但实际上我们想匹配.这个字符 +[&quot;1&quot;, index: 0, input: &quot;1.&quot;, groups: undefined] + + +&quot;1.&quot;.match(/\./) +// 只需要加一个转义字符就可以了,其他类似 +[&quot;.&quot;, index: 1, input: &quot;1.&quot;, groups: undefined] + +条件分支 +比如现在想匹配图片的文件名,包括 jpg、png、jpeg、gif 等等,这是多个选项,所以需要像编程语言一样,应该具备条件分支结构。 + +| :条件分支 +() :有两层含义 + +括号中的内容成为一个独立的整体 +括号的内容可以进行分组,单独匹配,若不需要此功能,则( ?: ) + + + +&quot;1.jpg&quot;.match(/.+\.jpe?g|gif|png/) +// 这样就可以满足条件分支了,不过下面又出问题了 +[&quot;1.jpg&quot;, index: 0, input: &quot;1.jpg&quot;, groups: undefined] + + +&quot;1.png&quot;.match(/.+\.jpe?g|gif|png/) +// 这里没有匹配到.和前面的文件名 +[&quot;png&quot;, index: 2, input: &quot;1.png&quot;, groups: undefined] + + +/* +其实我们想告诉它的是,.和后面的每一个条件分支的值都是一个独立的整体 +但是它把.+\.jpe?g、gif、png当成了各自独立的整体 +我们并不想让它这样切分,所以我们来告诉它怎么分才是正确的 +*/ + + +&quot;1.png&quot;.match(/.+\.(jpe?g|gif|png)/) +// 现在可以匹配成功了,但是它多匹配了一个 +// 因为括号的内容可以进行分组,单独匹配 +(2) [&quot;1.png&quot;, &quot;png&quot;, index: 0, input: &quot;1.png&quot;, groups: undefined] + + +// 所以最终写法如下 +&quot;1.png&quot;.match(/.+\.(?:jpe?g|gif|png)/) +[&quot;1.png&quot;, index: 0, input: &quot;1.png&quot;, groups: undefined] + +贪婪与懒惰 +// 首先看一个例子 +&quot;aabab&quot;.match(/a.*b/) +[&quot;aabab&quot;, index: 0, input: &quot;aabab&quot;, groups: undefined] + + +/* +上面的匹配没有什么问题,但实际上aab也是可以的 +也就是aab也是符合条件的,那又是为什么呢? +*/ + +因为在正则表达式中,默认是贪婪模式,尽可能多的匹配,可以在修饰数量的匹配符后面添加 ?,则代表懒惰。 +// like this (^__^) +&quot;aabab&quot;.match(/a.*?b/) +[&quot;aab&quot;, index: 0, input: &quot;aabab&quot;, groups: undefined] + +到这里应该就差不多了,再深入的,就自我查询知识了。配一张正则表达式速查表。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + FastDFS 分布式文件系统简介 + +

+ +
+ + + + +
+ +
+ 文章内容是刘欣大大(《码农翻身》作者,公众号:码农翻身)的直播课内容,主要是了解一下分布式文件系统,学习FastDFS的一些设计思想,学习它怎么实现高效、简洁、轻量级的一个系统的 +FastDFS分布式文件系统简介 +国内知名的系统级开源软件凤毛菱角,FastDFS就是其中的一个,其用户包括我们所熟知的支付宝、京东商城、迅雷、58同城、赶集网等等,它是个人所开发的软件,作者是余庆。 +我们已经进入互联网时代,互联网给我们的生活带来便捷的同时,也给我们带来了诸多挑战。 + +对于海量文件的存储,一个机器不够,那么就用多台机器来存储。 + +如果一个文件只存储一份,那么如果存储这个文件的机器坏掉了,文件自然就丢失了,解决办法就是将文件进行备份,相信大多数人都有备份重要文件的习惯。FastDFS也是如此,为了防止单点的失败,肯定是需要冗余备份的。 +FastDFS把应用服务器分为若干个组,每一组里面可以有多台机器(一般采用3台),每一台机器叫做存储服务器(storage server)。同一组内之间的数据是互为备份的,也就是说用户把文件传到任一服务器,都会在同组内其它两个服务器进行备份,因此一个组的存储空间大小是由该组内存储空间最小的那台机器是一样的(和木桶原理一样)。为了不造成存储空间的浪费,同一个组内的三台机器最好都一样。 + +每个存储服务器(storage server)的存储就够又是怎样的呢?展开来看,它可以分为多个目录,每个目录以M开头,用00、01、02......来划分,一般无需划分这么多目录,只用一个目录就可以了。 +在每个根目录下面又划分了两级目录。如图所示,在/data/fastdfs0下又划分出两级目录,每一级有256个目录,这样算下来总共就有65535个目录了。存储文件时,就是通过两次哈希来确定放在哪一个目录下面。 + +那么问题就来了,有这么多组,到底该选择哪个组的服务器进行存储呢?或者说,访问的时候到底访问哪一个组呢? +FastDFS提供的解决思路是引入一个跟踪服务器(tracker server),它用于记录每一个组内的存储服务器信息,存储信息是每个storage主动回报给tracker,有了这些信息之后,tracker就可以做调度工作了,看看谁的存储空间大,就把文件放过去。 + +FastDFS的特点 + +组与组之间是相互独立的 +同一个组内的storage server之间需要相互备份 + +文件存放到一个storage之后,需要备份到别的服务器 + + +tracker之间是不交互的 + +每个storgae server都需要向所有的tracker去主动报告信息 +tracker与tracker之间是不知道彼此的存在的 + + + +如何上传文件 +为方便下载文件的理解,这里假设上传的文件为:Group1/M00/00/0C/wKjGgVgbV2-ABdo-AAAAHw.jpg +如下面的时序图可以看到客户端是如何上传文件到服务器的。首先client向tracker发送上传链接请求,然后由tracker进行调度,查询可用的storage,并把该storgae对应的ip和端口发送给client;拿到了存储服务器信息,client就直接将文件上传到storage即可;storage会生成新的文件名再写入到磁盘,完成之后再把新的文件信息返回给client,client最后把文件信息保存到本地。需要注意的是,storage会定时向tracker回报信息。 + +如何进行选择服务器 + +tracker不止一个,客户端选择哪一个做上传文件? + +tracker之间是对等的,任选一个都可以 + + +tracker如何选择group? + +round robin(轮询) +load balance(选择最大剩余空间group上传) +specify group(制定group上传) + + +如何选定storage? + +round robin,所有server轮询使用(默认) +根据ip地址进行排序选择第一个storage(ip地址最小者) +根据优先级进行排序(上传优先级由stoage来设置,参数为upload_priority) + + +如何选择storage path + +round robin,轮询(默认) +load balance,选择使用剩余空间最大的存储路径 + + + +如何选择存放目录 + +选定存放目录? + +storage会生成一个file_id,采用Base64编码,字段包括:storage ip、文件创建时间、文件大小、文件CRC32校验和随机数 +每个存储目录下面有两个256 * 256个子目录,storage会按文件file_id进行两次hash,然后将文件以file_id为文件名存储到子目录下 + + + +需要注意的是:file_id由cilent来保存,如果没有保存,你就不知道你上传的文件去那里了 +Storage server之间的文件同步 + +同一组内的storage之间是对等的,文件上传、删除等操作可以在任意一台storage上进行 +文件同步只在同组内的stroage之间进行,采用push方式,即源服务器同步给目标服务器 +源头数据才需要同步,备份数据不需要再次同步,否则就构成环路了 +新增一台storage时,由已有的一台storage将已有的所有数据(包括源头数据和备份数据)同步给该新增服务器 + +Storage的最后最早同步被同步时间 +这个标题有一些拗口,现在有三台服务器A、B、C,每个服务器都需要记录其他两台服务器向自己进行同步操作的最后时间。比如下图中的服务器A,B在9:31向A同步了所有的文件、C在9:33向A同步了所有的文件,那么A服务器的最后最早被同步时间就是9:31。其他两个服务器也是一样。 +最后最早被同步时间的意义在于判断一个文件是否存在于某个storage上。比如这里的A服务器最后最早被同步时间为9:31,那么如果一个文件的创建时间为9:30,就可以肯定这个文件在服务器A上肯定有。 +Stroage会定期将每台机器的同步时间告诉给tracker,tracker在client需要下载一个文件时,要判断一个storage是否有该文件,只需要解析文件的创建时间,然后与该值作比较,若该值大于创建时间,说明storage存在这个文件,可以从该storage下载。 + +但是这个算法有缺陷,比如下面的情况:W文件的创建时间是9:33,服务器C已经把9:33之前的文件都同步给B了,因此B服务器里面其实已经有W文件了,但是根据最后最早被同步时间,会认为B中没有W文件。因此这个算法虽然简单,但是牺牲了部分文件。 + +如何下载文件 +首先由client发送下载连接请求,请求的东西本质上就是Group1/M00/00/0C/wKjGgVgbV2-ABdo-AAAAHw.jpg;tracker将查询到的可用storage server(按下文的四个原则进行选择)的ip和端口发送给client;现在client有本地保存的文件信息,也有服务器的地址和端口,那么直接访问对应的服务器下载文件即可。 + +如何选择一个可供下载的storage server +共下面四个原则,从上到小条件越来越宽松 + +该文件上传到的源storage(文件直接上传到该服务器上) +文件创建时间戳 &amp;lt; storage被同步到的文件时间戳,这意味着当前文件已经被同步过来了 +文件创建时间戳 = storage被同步到的文件时间戳,并且(当前时间-文件创建时间戳)&amp;gt; 一个文件同步完场需要的最大时间(5分钟) +(当前时间 - 文件创建时间)&amp;gt; 文件同步延迟阀值,比如我们把阀值设置为1天,表示文件同步在一天内肯定可以完成 + +FastDFS的使用 +用户通过浏览器或者手机端访问web服务器,web服务器把请求转发给应用服务器,应用服务器收到请求后,通过fastDFS API和FastDFS文件系统进行交互。但是这么设计会造成应用服务器的压力,因为上传和下载都经过应用服务器。 + +为了避免应用服务器压力过大,可以让客户端直接使用Http访问,不通过应用服务器。 + +FastDFS其他内容 +防止盗链 +为了防止辛辛苦苦上传的文件被别人盗去,可以通过给URL设置token来解决。FastDFS的防止盗链配置如下: +# 是否做tokrn检查,缺省值为false + +http.anti\_steal.check\_token=true + +# 生成token的有效时长/秒 + +http.anti\_steal.token\_ttl=900 + +# 生成token的密钥,尽量设置长一些 + +http.anti\_steal.secret\_key=@#$%\*+\*&amp;amp;amp;!~ + +FastDFS生成token策略为:token = md5(文件名,密钥,时间戳) + + +合并存储 + +海量小文件的缺点 + +元数据管理低效,磁盘文件系统中,目录项、索引节点(inode)和数据(data)保存在介质不同的位置上 +数据存储分散 +磁盘的大量随机访问降低效率(小文件有可能这个在这个磁道,那个在那个磁道,就会造成大量的随机访问,大量小文件对I/O是非常不友好的) + + +FastDFS提供的合并存储功能 + +默认大文件64M +每个文件空间称为slot(256bytes = slot = 16MB) + + + +也就是说对于小文件,FastDFS会采用把多个小文件合并为一个大文件的方式来存储,默认建一个大小为64M的大文件,然后再分成多个槽,最小的槽是256bytes,因此如果一个文件小于256bytes,那么它也会占256bytes的大小。就好像我们在医院见到的中药柜子一样,每个抽屉里面再分成多个小格子,根据药材包的大小来选择不同大小的格子。 +没有合并时的文件ID + +合并时的文件ID + +此处不再深入探讨存储合并的机制,因为它带来了一系列新的问题,比如同步时不仅需要记录大文件的名称,还需要进入小文件的名称,一下子变得麻烦多了;原来空闲空间管理直接通过操作系统就能计算出来,但是现在不行了,因为是创建了一个64M的块,这个块里面还有空闲空间,计算起来就很麻烦了。 +总结 + +FastDFS是穷人的解决方案 +FastDFS把简洁和高效做到了极致,非常节约资源,中小型网站完全用得起 + + +
+ + Read More ~ +
+
+
+ + + +
+ + 上一页 + + +
+ + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/pkJGoZIqgD/index.html b/pkJGoZIqgD/index.html new file mode 100644 index 00000000..b0f2e39c --- /dev/null +++ b/pkJGoZIqgD/index.html @@ -0,0 +1,774 @@ + + + + + + + + Angular | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + Angular +
+ + +
+

+ + 学习 Angulr 容易忽略的知识点 + +

+ +
+ + + + +
+ +
+ +参考内容: +《Angulr5 高级编程(第二版)》 + +函数声明式和表达式 +// 第一种:函数声明式 +myFunc(); +function myFunc(){ + ... +} + +// 第二种:函数表达式 +myFunc(); +let myFunc = function(){ + ... +} + +虽然上面两种函数声明方式在大部分情况下是一样的,第一种可执行,第二种却不可以执行,这是因为浏览器在解析 js 时找到函数声明,并在执行剩余语句之前设置好函数,此过程称为函数提升,但是函数表达式却不会受到提升,因此无法正常工作。 +js 不具备多态性 +js 重不能创建名称相同但参数不同的两个函数,它不具备这个多态性,比如你定义的函数中有两个形参,调用函数时只传一个参数,第二形参的值就是 undefined ,如果传的参数大于 3 个,那么会自动忽略多余的参数。可以使用下列方法来处理函数定义参数数量和用于调用函数实际参数数量之间不匹配的问题。 +// 使用默认参数 +let func = function(age, sex='男'){ + ... +} +func(23); + +// 使用可变长参数 +let func = function(age, sex, ...extraArgs){ + ... +} +func(23, '女', '张三', '深圳'); +// 最后一个参数是一个数组,任何额外的实参都会被赋给这个数组 + +let 和 war 的区别 +使用 let 和 var 声明变量的区别,使用 let 声明变量会把变量的作用范围限定在它所在的代码区域内。而使用 var 所创建的变量的作用域是它所在的函数。 +function func(){ + if(false){ + var age = 23; + } +} + +// 上面的代码会被解析成下面的形式,使用 let 则不会出现这样的结果 + +function func(){ + var age; + if(false){ + age = 23; + } +} + +相等 == 和恒等 === 以及 连接操作符 + +相等操作符尝试将操作数强制转换为相同的类型,再评估是否相等,实质上相等操作符==是测试二者的值是否相等,而与二者的类型无关;如果要测试值和类型是否都相等则应该用恒等操作符===。 +5 == '5' // 结果为 true +5 === '5' // 结果为 false + +在 js 中,连接操作符的优先级高于加法操作,也就是说5 + '5'的结果是55。 +不同的模块指定方式 +import { Name } from &quot;./modules/NameUtil&quot;;// 第一种 +import { Compont } from &quot;@angular/core&quot;;// 第二种 + +上面两种导入模块的方式有所不同,第一种是相对模块,第二种是非相对导入。第一种告诉的 TypeScript 编译器,该模块所在的位置是相对于包含 import 语句的文件而言;第二种非相对导入,编译器会用 node_modules 文件夹中的 npm 包来解析它。 +如果在导入模块时,出现需要导入两个不同模块但是名字却相同的情况,可以使用as关键字给导入的模块取一个别名。 +import { Name as otherName } from &quot;./modules/Name&quot;;//取别名 + +还有一种方法是将模块作为对象导入,如下 import 所示,导入 Name 模块的内容,并创建一个名为 otherName 的对象,然后就可以使用该对象的属性了。 +import * as otherName from &quot;./modules/NameUtil&quot;; +let name = new otherName.Name(&quot;Admin&quot;, &quot;China&quot;);// Name 是 NameUtil 中的类 + +多类型和类型断言 +在 ts 中允许指定多个类型,使用字符|进行分隔。看下面的的方法,其功能是把华氏温度转换为摄氏温度。 +// 使用多类型,该函数可以传入 number 和 string 类型的参数 +static convertFtoC(temp: number | string): string { + /* + 尝试使用 &lt;&gt; 声明一个类型断言,将一个对象转换为指定类型,也可以使用 as 关键字实现下列相同的效果 + let value: number = (temp as number).toPrecision ? temp as number : parseFloat(temp as string); + */ + let value: number = (&lt;number&gt;temp).toPrecision ? &lt;number&gt;temp : parseFloat(&lt;string&gt;temp); + return ((parseFloat(value.toPrecision(2)) - 32) / 1.8).toFixed(1); +} + +元组是固定长度的数组,数组的每一项都是指定的类型;可索引类型可以将键与值关联起来,创建类似于 map 的集合。 +// 元组 +let tuple: [string, string, string]; +tuple = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]; + +// 可索引类型 +let cities: {[index: string] : [string, string]} = {}; +cities[&quot;Beijing&quot;] = [&quot;raining&quot;, &quot;2摄氏度&quot;]; + +数据绑定 +[target]=&quot;expr&quot;// 方括号表示单向绑定,数据从表达式流向目标; + +(target)=&quot;expr&quot;// 圆括号表示单向绑定,数据从目标流向表达式,用于处理事件的绑定; +[(target)]=&quot;expr&quot;// 圆方括号组合表示双向绑定,数据在表达式与目标之间双向流动; +{{ expression }}// 字符串插入绑定。 + +[] 绑定有很多不同的形式,下面介绍不同表现形式的效果。 +&lt;!-- + 标准属性绑定(dom对象有的属性),将 input 的 value 属性绑定到一个表达式的结果 + 因为 model.getProduct(1) 可能返回 null ,所以使用模板空条件操作符 ? 浏览返回结果 + 如果返回不为空,那么将读取 name 属性,否则由 null 合并操作符 || 将结果设置为 None + 字符串插入绑定也可以使用这种表达式 + --&gt; +&lt;input [value]=&quot;model.getProduct(1)?.name || 'None'&quot;&gt; + +&lt;!-- + 元素属性绑定,有时候我们需要绑定的属性在 DOMAPI 上面没有 + 可以使用通过在属性名称前加上 attr 前缀的方式来定义目标 + --&gt; +&lt;td [attr.colspan]=&quot;model.getProducts().length&quot;&gt; + {{ model.getProduct(1)?.name || 'None' }} +&lt;/td&gt; + +&lt;!-- 还有其他的 ngClass,ngStyle 等绑定,理解大体上和上面差不多 --&gt; + +内置指令 +&lt;!-- + ngIf指令,如果表达式求值结果为 true ,那么 ngIf 将宿主元素机器内容包含在 html 文件中 + 指令前面的星号表示这是一条微模板指令 + 组要注意的是,ngIf 会向 html 中添加元素,也会从中删除元素,并非只是显示和隐藏 + 如果只是控制可见性,可以使用属性绑定挥着样式绑定 + --&gt; +&lt;div *ngIf=&quot;expr&quot;&gt;&lt;/div&gt; + +&lt;!-- + ngSwitch指令, + --&gt; +&lt;div [ngSwitch]=&quot;expr&quot;&gt; + &lt;span *ngSwitchCase=&quot;expr&quot;&gt;&lt;/span&gt; + &lt;span *ngSwitchDefault&gt;&lt;/span&gt; +&lt;/div&gt; + +&lt;!-- + ngFor指令,见名知意,为数组中的每个对象生成同一组元素 + ngFor 指令还支持其他的一系列可赋给变量的值,有如下局部模板变量 + + index:当前对象的位置 + odd:如果当前对象的位置为奇数,那么这个布尔值为 true + even:同上相反 + first:如果为第一条记录,那么为 true + last:同上相反 + --&gt; +&lt;div *ngFor=&quot;let item of expr; let i = index&quot;&gt; + {{ i }} +&lt;/div&gt; + +&lt;!-- + ngTemplateOutlet指令,用于重复模板中的内容块 + 其用法如下所示,需要给源元素指定一个 id 值 + + &lt;ng-template #titleTemplate&gt; + &lt;h1&gt;我是重复的元素哦&lt;/h1&gt; + &lt;/ng-template&gt; + &lt;ng-template [ngTemplateOutlet]=&quot;titleTemplate&quot;&gt;&lt;/ng-template&gt; + ...省略若万行 html 代码 + &lt;ng-template [ngTemplateOutlet]=&quot;titleTemplate&quot;&gt;&lt;/ng-template&gt; + --&gt; +&lt;ng-template [ngTemplateOutlet]=&quot;myTempl&quot;&gt;&lt;/ng-template&gt; + +&lt;!-- + 下面两个指令就是见名知意了,不解释 + --&gt; +&lt;div ngClass=&quot;expr&quot;&gt;&lt;/div&gt; +&lt;div ngStyle=&quot;expr&quot;&gt;&lt;/div&gt; + +事件绑定 +事件绑定使用 (target)=&quot;expr&quot;,是单向绑定,数据从目标流向表达式,用于响应宿主元素发送的事件。 +当浏览器触发一个时间时,它将提供一个对象来描述该事件,对于不同类型的事件有不同类型的事件对象,事件对象被赋给一个名为$event的模板变量,但是所有事件对象都有下面三个属性: +type:返回一个 string 值,用于标识已触发事件类型; +target:返回触发事件的对象,一般是 html元素对象。 +timeStamp:返回事件触发事件的 number 值,用 1970.1.1 毫秒数表示。 + +下面举几个例子,作为理解帮助使用。 +&lt;!-- 当数鼠标在上面移动时,就会触发 mouseover 事件 --&gt; +&lt;td *ngFor=&quot;let item of getProducts()&quot; (mouseover)=&quot;selectedProduct = item.name&quot;&gt;&lt;/td&gt; + +&lt;!-- 当用户编辑 input 元素的内容时就会触发 input 事件 --&gt; +&lt;input (input)=&quot;selectedProduct=$event.target.value&quot; /&gt; + +&lt;input (keyup)=&quot;selectedProduct=product.value&quot; /&gt; +&lt;!-- 使用事件过滤,上面的写法按下任何一个键都会触发事件,而下面的写法只有回车事件才会触发事件 --&gt; +&lt;input (keyup.enter=&quot;selectedProduct=product.value&quot;) /&gt; + +表单验证 +Angular 提供了一套可扩展的系统来验证表单元素的内容,总共可以向 input表元素中添加 4 个属性,每个属性定义一条验证规则,如下所示: +required:用于指定必须填写值; +minlength:用于指定最小字符数; +maxlength:用于指定最大字符数,(不能在表单元素直接使用,因为它与同名的 H5 属性冲突); +pattern:该属性用于指定用户填写的值必须匹配正则表达式 + +&lt;!-- + Angular 要求验证的元素必须定义 name 属性 + 由于 Angular 使用的验证属性和 H5 规范使用的验证属性相同, + 所以向表单元素中添加 novalidate 属性,告诉浏览器不要使用原生验证功能 + ngSubmit 绑定表单元素的 submit 事件 + --&gt; +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +Angular 提供了 3 对验证 CSS 类,这些类可以用于样式化表单元素,向用户提供验证反馈,具体说明如下所示。 +ng-untouched ng-touched:如果一个元素未被用户访问,就将其加入到 nguntouched 类中;一旦访问就加入到 ngtouched 类中。 +ng-prisstine ng-dirty:元素内容没有被改变被加入到 ng-prisstine 类中,否则将其加入到 ng-dirty 类中。 +ng-valid ng-invalid:如果满足验证规则定义的条件,就加入到 ng-valid 类中,否则加入到 ng-invalid 类中。 + +在实际使用过程中,直接定义对应的样式即可,如下所示: +&lt;style&gt; +input.ng-dirty.ng-invalid{ + border: 2px solid red; +} +input.ng-dirty.ng-valid{ + border: 2px solid green; +} +&lt;/style&gt; +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +上面的验证方式无法给用户提供更加具体的信息,用户不知道应该做什么,可以使用 ngModel 指令来访问宿主元素的验证状态,当存在验证错误的时候,使用该指令向用户提供指导性信息。 +&lt;form novalidate (ngSubmit)=&quot;addProduct(newProduct)&quot;&gt; + &lt;input class=&quot;form-control&quot; + #nameRef=&quot;ngModel&quot; + name=&quot;name&quot; + [(ngModel)]=&quot;newProduct.name&quot; + required + minlength=&quot;5&quot; + pattern=&quot;^[A-Za-z]+$&quot; /&gt; + &lt;ul class=&quot;text-danger list-unstyled&quot; + *ngIf=&quot;name.dirty &amp;&amp; name.invalid&quot;&gt; + &lt;li *ngIf=&quot;name.errors?required&quot;&gt; + you must enter a product name + &lt;/li&gt; + &lt;li *ngIf=&quot;name.errors?.pattern&quot;&gt; + product name can only contain letters and spases + &lt;/li&gt; + &lt;li *ngIf=&quot;name.errors?minlength&quot;&gt; + &lt;!-- + Angular 表单验证错误描述属性 + required:如果属性已被应用于 input 元素,此属性返回 true + minlength.requiredLength:返回满足 minlength 属性所需的字符数 + minlength.actualLength:返回用户输入的字符数 + pattern.requiredPattern:返回使用 pattern 属性指定的正则表达式 + pattern.actualValue:返回元素的内容 + --&gt; + product name must be at least {{ name.errors.minlength.requiredLenth }} characters + &lt;/li&gt; + &lt;/ul&gt; + &lt;button type=&quot;submit&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +如果在用户尝试提交表单时就显示大量的错误信息,给人的体验感就会很差,所以可以让用户提交表单时再验证整个表单,示例代码如下所示。 +export class ProductionCompont { + // ...省略若万行代码 + formSubmited: boolean = false; + + submitForm(form: ngForm) { + this.formSubmited = true; + if(form.valid) { + this.addProduct(this.newProduct); + this.newProduct = new Product(); + form.reset(); + this.formSubmited = true; + } + } +} + +&lt;form novalidate #formRef=&quot;ngForm&quot; (ngSubmit)=&quot;submitForm(formRef)&quot;&gt; + &lt;div *ngIf=&quot;formsubmited &amp;&amp; formRef.invalid&quot;&gt; + there are problems with the form + &lt;/div&gt; + &lt;!-- 禁用提交按钮,验证成功提交按钮才可用 --&gt; + &lt;button [disabled]=&quot;formSubmited &amp;&amp; formRef.valid&quot;&gt;提交&lt;/button&gt; +&lt;/form&gt; + +fromSubmited 属性用于指示表单是否已经提交,并将用于在用户提交整个表单之前阻止表单验证。当用户提交表单时,调用 submitForm 方法,并将 ngForm 对象作为实参传入,ngForm 提供了 reset 方法,该方法可以重置表单的验证状态,使其返回到最初的未访问状态。 +更高级的还有使用基于模型的表单验证,可以自行查阅相关资料。 +使用 json-server 模拟 web 服务 +因为json-server会经常用到,建议使用全局安装命令npm install -g json-server。因为开发后端的同学太慢了,而我们如果要等他们把接口都提供给我们的时候再开发程序的话,那效率就太低了,所以使用 json-server 来模拟后端服务。只需要建好一个 json 文件,比如下面的格式: +{ + &quot;user&quot; : [ + { + &quot;name&quot; : &quot;张三&quot;, + &quot;number&quot; : &quot;1234&quot;, + }, + { + &quot;name&quot; : &quot;王二&quot;, + &quot;number&quot; : &quot;5678&quot;, + } + ], + &quot;praise&quot;: [ + {&quot;info&quot;:&quot;我是一只小老虎呀!&quot;}, + {&quot;info&quot;:&quot;我才是大老虎&quot;} + ] +} + +启动服务使用命令json-server [你的 json 文件路径],然后就可以根据提示访问了,你甚至可以使用http://localhost:3000/user?number=5678去过滤数据。这样就能模拟 web 服务,而不必等后端同学的进度了。 +解决跨域请求问题 +Angular 跨域请求问题可以通过 Angular 自身的代理转发功能解决,在项目文件夹下新建一个 proxy.conf.json 并在其中添加如下内容。 +// 可以通过下列配置解决 +&quot;/api&quot;: { + &quot;target&quot;: &quot;http://10.9.176.120:8888&quot;, +} + +在启动时使用npm start,或者使用ng serve --proxy-config proxy.conf.json,Anular 中的/api请求就会被转发到 http://10.9.176.120:8888/api,从而解决跨域请求问题。 +使用第三方 js 插件 +共有三种方式引入第三方插件,第一种很简单,直接在 html 中引入插件就可以了;第二种在angular.json中进行配置;第三种在 ts 文件中使用 import 导入库即可。 +// 第一种(需要重启服务) +&quot;scripts&quot;: [&quot;src/assets/jquery-3.2.1.js&quot;,&quot;src/assets/jquery.nicescroll.js&quot;,&quot;src/assets/ion.rangeSlider.js&quot;] + +// 第二种 +&lt;script type=&quot;text/javascript&quot; src=&quot;assets/jquery-3.2.1.js&quot;&gt;&lt;/script&gt; +&lt;script type=&quot;text/javascript&quot; src=&quot;assets/jquery.nicescroll.js&quot;&gt;&lt;/script&gt; + +// 第三种 +import &quot;assets/jquery-3.2.1.js&quot;; +import &quot;assets/jquery.nicescroll.js&quot;; +import &quot;assets/ion.rangeSlider.js&quot;; + +深拷贝与浅拷贝 +深拷贝与浅拷贝是围绕引用类型变量说的,其本质区别是不可变性,基本类型是不可变得,而引用类型是可变的。 +直接使用赋值操作符,就是浅拷贝,如果对拷贝源进行操作,会直接影响在拷贝目标上,因为这个赋值行为本质是内存地址的赋值,为了获得与拷贝源完全相同但又不会影响彼此的对象就要使用深拷贝。 +let objA = { + x: 1, + y: -1 +} +let objB = objA; +objA.x++; +console.log(&quot;objA.x:&quot;+objA.x, &quot;objB.x:&quot;+objB.x); +//打印结果如下: +objA.x : 2 +objB.x : 2 + +Typescript 提供了一种方法来实现引用类型的深拷贝,即Object.assign(target, ...source),此方法接受多个参数,第一个参数为拷贝目标,剩余参数为拷贝源,同名属性会进行覆盖。 +let objA = { + x: 1, + y: -1, + c: { + d: 1, + } +} +let objB = {}; +Object.assign(objB, objA); +objA.x++; +console.log(&quot;objA.x:&quot;+objA[&quot;x&quot;], &quot;objB.x:&quot;+objB[&quot;x&quot;]); +//打印结果如下: +objA.x : 2 +objB.x : 1 + +需要注意的是,Typescript 提供的深拷贝方法不能实现嵌套对象的深拷贝,会出现下面的情况。 +let objA = { + x: 1, + y: -1, + c: { + d: 1, + } +} +let objB = {}; +Object.assign(objB, objA); +objA.c.d++; +console.log(&quot;objA.c.d:&quot;+objA[&quot;c&quot;].d, &quot;objB.c.d:&quot;+objB[&quot;c&quot;].d); +//打印结果如下: +objA.c.d : 2 +objB.c.d : 2 + +要实现嵌套对象的深拷贝,可以使用 JSON 对象提供的方法,JSON 对象提供了两个方法,分别为:stringify()和parse(),前者将对象 JSON 化,后者将 JSON 对象化,使用这种方式可以实现嵌套深拷贝,但是也有缺点:破坏原型链,不能拷贝属性值为 function 的属性。 +let objA = { + a: 1, + b: { + c: 1 + } +} +let objB = JSON.parse(JSON.stringify(objA)); +objA.b.c++; +console.log(&quot;objA.b.c:&quot;+objA.b.c, &quot;objB.b.c:&quot;+objB.b.c); + +//打印结果如下: +objA.b.c:2 +objB.b.c:1 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 跨域请求是什么?如何解决? + +

+ +
+ + + + +
+ +
+ +参考内容: +JavaScript: Use a Web Proxy for Cross-Domain XMLHttpRequest Calls +别慌,不就是跨域么! +跨域资源共享 CORS 详解 +AJAX请求和跨域请求详解(原生JS、Jquery) +JavaScript跨域总结与解决办法 + + +刚毕业入职,大部分时间还在培训,中间有一段时间的空闲时间,就学习了下 Angular,在学校都是编写的单体应用,所有代码都放在同一个工程下面,到公司使用的是前后端分离了,虽然后端程序也是我自己写的,但是有一些数据是从公司现有接口去拿的,然后就遇到让我纠结了两小时的跨域请求问题,在这里做一个简单的总结输出。 +什么是跨域请求 +跨域请求问题是浏览器的同源策略造成的,该策略不允许执行其它网站的脚本,是浏览器施加的安全限制。什么是同源?最初是指网页 A 设置的 Cookie 不能被网页 B 打开,包括三个相同:协议、域名、端口。这个同源是从 URL 判断的,不是从 IP 判断的,如果同一个服务器对应连个域名,这两个域名是不同源的。 +http://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 非跨域 + +http://www.nealyang.cn/index.html 调用 http://www.neal.cn/server.php 跨域,主域不同 + +http://abc.nealyang.cn/index.html 调用 http://def.neal.cn/server.php 跨域,子域名不同 + +http://www.nealyang.cn:8080/index.html 调用 http://www.nealyang.cn/server.php 跨域,端口不同 + +https://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 跨域,协议不同 + +localhost 调用 127.0.0.1 跨域 + +同源政策的目的是为了保护用户信息的安全,防止恶意网站窃取数据,随着互联网的发展,同源政策更加严格了,下面三种行为都会受到限制。 +(1) Cookie、LocalStorage 和 IndexDB 无法读取。 +(2) DOM 无法获得。 +(3) AJAX 请求不能发送。 + +所有的现代浏览器都对网络连接进行了安全限制,包括 XMLHttpRequest,如果你的 web 应用程序和其使用的数据在同一个服务器,你不会遇到跨域请求问题。但是当你的 web 应用程序和 web 服务数据不在同一个服务器时,就会被浏览器限制连接了。 +常用解决方案 +    对于跨域请求有很多的解决方案,最常用的解决方案是在你的 web 服务器上面设置代理。在设置代理之前就通过,应用程序直接去请求另一个服务器下的数据;设置代理之后,应用程序从自己的 web 服务器中请求数据,再由代理去请求数据,这样 web 服务器拿到数据之后返回给应用程序即可。从浏览器角度看,就是从同一个服务器拿的数据,并没有进行跨域请求。 + +通俗易懂的说,你家的宠物狗不会吃别家的食物,因为它担心别人的食物会把自己给药死,所以你的狗狗只管找你要食物,你是它的主人,它绝对相信你,而你可以鉴别别人给的食物是不是安全的。类比,小狗就是浏览器,你就是代理。 +Angular 中的解决办法 +上面所说的解决方案在开发过程中不方便操作,每新发一个接口都到服务器中去配置一下,不仅麻烦而且效率低下。首先说一下在 Angular 中一个人比较常用的解决方法,默认你在使用angular-cli构建你的项目,我们可以创建一个代理配置文件proxy.conf.json(假设你的后端服务的访问地址为10.121.163.10:8080),代理配置文件如下: +{ + &quot;/api&quot;: { + &quot;target&quot;: &quot;http://10.121.163.10:8080&quot;, + &quot;secure&quot;: false + } +} + +然后修改package.json文件中的启动命令为&quot;start&quot;: &quot;ng serve --proxy-config proxy.conf.json&quot;,启动项目时使用npm start即可解决跨域请求问题。 +上述解决方案仅在开发时使用,你当然可以使用 tomcat、nginx 配置代理,但是这很麻烦,需要打包代码部署,为了保证效率,我们想写完了立刻测试,同时也不想麻烦做后端的同学,在项目发布时,应该把代理配置到服务器中去;修改启动命令也不是必须的,你也可以选择每次使用 ng serve --proxy-config proxy.conf.json命令启动项目;示例代理配置文件内容可以有更多的属性,可以通过网络查阅相关资料。 +后端解决办法 +我的后端是是用 tornado 实现的,然后我又写了一个单独的页面用于在大屏幕上展示相关数据,没有用 Angular 了,要通过 AJAX请求数据,又怎么解决跨域请求问题呢?这时就需要设置请求头了,让后端允许跨域请求。 +这时需要了解一下简单请求和非简单请求了,简单请求就是只发送一次请求的请求;非简单请求会发送数据之前先发一次请求做预检,通过预检后才能再发送一次请求用于数据传输。 +更清晰区别,满足下列两大条件的属于简单请求,而非简单请求就是请求方法为PUT或DELETE,或者 Content-Type字段是application/json的请求。 + +1.请求方法为 GET、POST、HEAD之一 +2.HTTP头信息不超出字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type,并且 Content-Type 的值仅限于 application/x-www-form-urlencoded、multipart/form-data、text/plain。 + +对于简单请求,只需要设置一下响应头就可以了。 +class TestHandler(tornado.web.RequestHandler): + def get(self): + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + # 可以把 * 写成具体的域名 + self.write('cors get success') + +对于复杂请求,需要设置预检方法,如下所示: +class CORSHandler(tornado.web.RequestHandler): + # 复杂请求方法put + def put(self): + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + self.write('put success') + # 预检方法设置 + def options(self, *args, **kwargs): + #设置预检方法接收源 + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + #设置预复杂方法自定义请求头h1和h2 + self.set_header('Access-Control-Allow-Headers', &quot;h1,h2&quot;) + #设置允许哪些复杂请求方法 + self.set_header('Access-Control-Allow-Methods', &quot;PUT,DELETE&quot;) + #设置预检缓存时间秒,缓存时间内发送请求无需再预检 + self.set_header('Access-Control-Max-Age', 10) + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/post-images/0-yES6ENP.jpeg b/post-images/0-yES6ENP.jpeg new file mode 100644 index 00000000..471f1438 Binary files /dev/null and b/post-images/0-yES6ENP.jpeg differ diff --git a/post-images/05lwXg0GQ.jpeg b/post-images/05lwXg0GQ.jpeg new file mode 100644 index 00000000..68756f63 Binary files /dev/null and b/post-images/05lwXg0GQ.jpeg differ diff --git a/post-images/0WoAZKl_S.png b/post-images/0WoAZKl_S.png new file mode 100644 index 00000000..3afc7c10 Binary files /dev/null and b/post-images/0WoAZKl_S.png differ diff --git a/post-images/0i4w5ceIc.jpeg b/post-images/0i4w5ceIc.jpeg new file mode 100644 index 00000000..13551791 Binary files /dev/null and b/post-images/0i4w5ceIc.jpeg differ diff --git a/post-images/1661346387745.png b/post-images/1661346387745.png new file mode 100644 index 00000000..11c9ce92 Binary files /dev/null and b/post-images/1661346387745.png differ diff --git a/post-images/1662135675136.jpg b/post-images/1662135675136.jpg new file mode 100644 index 00000000..2e439a71 Binary files /dev/null and b/post-images/1662135675136.jpg differ diff --git a/post-images/1662135913988.png b/post-images/1662135913988.png new file mode 100644 index 00000000..60789e40 Binary files /dev/null and b/post-images/1662135913988.png differ diff --git a/post-images/1662135934030.png b/post-images/1662135934030.png new file mode 100644 index 00000000..0aca7e6c Binary files /dev/null and b/post-images/1662135934030.png differ diff --git a/post-images/1662135958844.png b/post-images/1662135958844.png new file mode 100644 index 00000000..445eb1a1 Binary files /dev/null and b/post-images/1662135958844.png differ diff --git a/post-images/1662135976962.png b/post-images/1662135976962.png new file mode 100644 index 00000000..1f415657 Binary files /dev/null and b/post-images/1662135976962.png differ diff --git a/post-images/1662135993137.png b/post-images/1662135993137.png new file mode 100644 index 00000000..19bc7a58 Binary files /dev/null and b/post-images/1662135993137.png differ diff --git a/post-images/1662136031883.png b/post-images/1662136031883.png new file mode 100644 index 00000000..4e460e58 Binary files /dev/null and b/post-images/1662136031883.png differ diff --git a/post-images/1662136047560.png b/post-images/1662136047560.png new file mode 100644 index 00000000..e5983878 Binary files /dev/null and b/post-images/1662136047560.png differ diff --git a/post-images/1662136101954.png b/post-images/1662136101954.png new file mode 100644 index 00000000..f3936569 Binary files /dev/null and b/post-images/1662136101954.png differ diff --git a/post-images/1662136126088.png b/post-images/1662136126088.png new file mode 100644 index 00000000..24678536 Binary files /dev/null and b/post-images/1662136126088.png differ diff --git a/post-images/1662138223900.png b/post-images/1662138223900.png new file mode 100644 index 00000000..c68e3a17 Binary files /dev/null and b/post-images/1662138223900.png differ diff --git a/post-images/1662198693380.png b/post-images/1662198693380.png new file mode 100644 index 00000000..bd1ea054 Binary files /dev/null and b/post-images/1662198693380.png differ diff --git a/post-images/1662198743912.jpeg b/post-images/1662198743912.jpeg new file mode 100644 index 00000000..ad374d8c Binary files /dev/null and b/post-images/1662198743912.jpeg differ diff --git a/post-images/1662198779488.jpeg b/post-images/1662198779488.jpeg new file mode 100644 index 00000000..c28aa75e Binary files /dev/null and b/post-images/1662198779488.jpeg differ diff --git a/post-images/1662305564343.jpeg b/post-images/1662305564343.jpeg new file mode 100644 index 00000000..2278c3de Binary files /dev/null and b/post-images/1662305564343.jpeg differ diff --git a/post-images/1662305578099.jpeg b/post-images/1662305578099.jpeg new file mode 100644 index 00000000..de08bfa7 Binary files /dev/null and b/post-images/1662305578099.jpeg differ diff --git a/post-images/1662305999826.jpeg b/post-images/1662305999826.jpeg new file mode 100644 index 00000000..ae2b3d28 Binary files /dev/null and b/post-images/1662305999826.jpeg differ diff --git a/post-images/1662306018034.jpeg b/post-images/1662306018034.jpeg new file mode 100644 index 00000000..446744f5 Binary files /dev/null and b/post-images/1662306018034.jpeg differ diff --git a/post-images/1662306035071.jpeg b/post-images/1662306035071.jpeg new file mode 100644 index 00000000..116aa8a3 Binary files /dev/null and b/post-images/1662306035071.jpeg differ diff --git a/post-images/1662966242181.png b/post-images/1662966242181.png new file mode 100755 index 00000000..403f963e Binary files /dev/null and b/post-images/1662966242181.png differ diff --git a/post-images/1662966257159.png b/post-images/1662966257159.png new file mode 100755 index 00000000..cfe74ea6 Binary files /dev/null and b/post-images/1662966257159.png differ diff --git a/post-images/1662966274719.png b/post-images/1662966274719.png new file mode 100755 index 00000000..1f575c00 Binary files /dev/null and b/post-images/1662966274719.png differ diff --git a/post-images/1662966290819.png b/post-images/1662966290819.png new file mode 100755 index 00000000..ebef672f Binary files /dev/null and b/post-images/1662966290819.png differ diff --git a/post-images/1662966711742.png b/post-images/1662966711742.png new file mode 100644 index 00000000..c1e24203 Binary files /dev/null and b/post-images/1662966711742.png differ diff --git a/post-images/1662966924063.png b/post-images/1662966924063.png new file mode 100644 index 00000000..76102951 Binary files /dev/null and b/post-images/1662966924063.png differ diff --git a/post-images/1662966934808.png b/post-images/1662966934808.png new file mode 100755 index 00000000..af1d3c68 Binary files /dev/null and b/post-images/1662966934808.png differ diff --git a/post-images/1662966952501.png b/post-images/1662966952501.png new file mode 100755 index 00000000..692aa3f7 Binary files /dev/null and b/post-images/1662966952501.png differ diff --git a/post-images/1662966965042.png b/post-images/1662966965042.png new file mode 100755 index 00000000..23e77895 Binary files /dev/null and b/post-images/1662966965042.png differ diff --git a/post-images/1662966995316.png b/post-images/1662966995316.png new file mode 100644 index 00000000..00168707 Binary files /dev/null and b/post-images/1662966995316.png differ diff --git a/post-images/1662967841826.png b/post-images/1662967841826.png new file mode 100644 index 00000000..abe5f5a5 Binary files /dev/null and b/post-images/1662967841826.png differ diff --git a/post-images/1662968391191.png b/post-images/1662968391191.png new file mode 100644 index 00000000..9af243cf Binary files /dev/null and b/post-images/1662968391191.png differ diff --git a/post-images/1662968421298.png b/post-images/1662968421298.png new file mode 100644 index 00000000..6e82069a Binary files /dev/null and b/post-images/1662968421298.png differ diff --git a/post-images/1662968771756.png b/post-images/1662968771756.png new file mode 100644 index 00000000..daf6ae28 Binary files /dev/null and b/post-images/1662968771756.png differ diff --git a/post-images/1662968796618.png b/post-images/1662968796618.png new file mode 100644 index 00000000..918d070b Binary files /dev/null and b/post-images/1662968796618.png differ diff --git a/post-images/1662968821862.png b/post-images/1662968821862.png new file mode 100644 index 00000000..3410e220 Binary files /dev/null and b/post-images/1662968821862.png differ diff --git a/post-images/1662968844950.png b/post-images/1662968844950.png new file mode 100644 index 00000000..d9799a4a Binary files /dev/null and b/post-images/1662968844950.png differ diff --git a/post-images/1662968870264.png b/post-images/1662968870264.png new file mode 100644 index 00000000..7515af0d Binary files /dev/null and b/post-images/1662968870264.png differ diff --git a/post-images/1663251213484.png b/post-images/1663251213484.png new file mode 100644 index 00000000..37c2bff9 Binary files /dev/null and b/post-images/1663251213484.png differ diff --git a/post-images/1663251488955.png b/post-images/1663251488955.png new file mode 100644 index 00000000..d4704eed Binary files /dev/null and b/post-images/1663251488955.png differ diff --git a/post-images/1663427281939.jpeg b/post-images/1663427281939.jpeg new file mode 100644 index 00000000..71f0fd91 Binary files /dev/null and b/post-images/1663427281939.jpeg differ diff --git a/post-images/1663427632402.jpeg b/post-images/1663427632402.jpeg new file mode 100644 index 00000000..6fa0c873 Binary files /dev/null and b/post-images/1663427632402.jpeg differ diff --git a/post-images/1663427658756.png b/post-images/1663427658756.png new file mode 100644 index 00000000..03a7ddd3 Binary files /dev/null and b/post-images/1663427658756.png differ diff --git a/post-images/1663428162274.png b/post-images/1663428162274.png new file mode 100644 index 00000000..24eacf90 Binary files /dev/null and b/post-images/1663428162274.png differ diff --git a/post-images/1663428236756.png b/post-images/1663428236756.png new file mode 100644 index 00000000..c45a7dca Binary files /dev/null and b/post-images/1663428236756.png differ diff --git a/post-images/1663428250026.jpeg b/post-images/1663428250026.jpeg new file mode 100644 index 00000000..b3dc034e Binary files /dev/null and b/post-images/1663428250026.jpeg differ diff --git a/post-images/1663428892672.png b/post-images/1663428892672.png new file mode 100644 index 00000000..aa73b932 Binary files /dev/null and b/post-images/1663428892672.png differ diff --git a/post-images/1663428950061.png b/post-images/1663428950061.png new file mode 100644 index 00000000..d7c864de Binary files /dev/null and b/post-images/1663428950061.png differ diff --git a/post-images/1663428964776.png b/post-images/1663428964776.png new file mode 100644 index 00000000..0eaec83b Binary files /dev/null and b/post-images/1663428964776.png differ diff --git a/post-images/1663428985304.png b/post-images/1663428985304.png new file mode 100644 index 00000000..458f295d Binary files /dev/null and b/post-images/1663428985304.png differ diff --git a/post-images/1663429003176.png b/post-images/1663429003176.png new file mode 100644 index 00000000..e23eeae4 Binary files /dev/null and b/post-images/1663429003176.png differ diff --git a/post-images/1663429020621.png b/post-images/1663429020621.png new file mode 100644 index 00000000..20de8db9 Binary files /dev/null and b/post-images/1663429020621.png differ diff --git a/post-images/1663429033931.png b/post-images/1663429033931.png new file mode 100644 index 00000000..e59546bb Binary files /dev/null and b/post-images/1663429033931.png differ diff --git a/post-images/1663502348311.png b/post-images/1663502348311.png new file mode 100644 index 00000000..1225c757 Binary files /dev/null and b/post-images/1663502348311.png differ diff --git a/post-images/1663502383283.png b/post-images/1663502383283.png new file mode 100644 index 00000000..77c38a2c Binary files /dev/null and b/post-images/1663502383283.png differ diff --git a/post-images/1663502407592.png b/post-images/1663502407592.png new file mode 100644 index 00000000..80564e7b Binary files /dev/null and b/post-images/1663502407592.png differ diff --git a/post-images/1663502440533.png b/post-images/1663502440533.png new file mode 100644 index 00000000..0deb7930 Binary files /dev/null and b/post-images/1663502440533.png differ diff --git a/post-images/1663502463504.png b/post-images/1663502463504.png new file mode 100644 index 00000000..15feabed Binary files /dev/null and b/post-images/1663502463504.png differ diff --git a/post-images/1663502486367.png b/post-images/1663502486367.png new file mode 100644 index 00000000..1ebe7dad Binary files /dev/null and b/post-images/1663502486367.png differ diff --git a/post-images/1663502520393.png b/post-images/1663502520393.png new file mode 100644 index 00000000..d53b2d00 Binary files /dev/null and b/post-images/1663502520393.png differ diff --git a/post-images/1663502544214.png b/post-images/1663502544214.png new file mode 100644 index 00000000..e23cc968 Binary files /dev/null and b/post-images/1663502544214.png differ diff --git a/post-images/1663502568096.png b/post-images/1663502568096.png new file mode 100644 index 00000000..57e7f49b Binary files /dev/null and b/post-images/1663502568096.png differ diff --git a/post-images/1663502595628.png b/post-images/1663502595628.png new file mode 100644 index 00000000..e963c2ed Binary files /dev/null and b/post-images/1663502595628.png differ diff --git a/post-images/1663502619129.png b/post-images/1663502619129.png new file mode 100644 index 00000000..b4149a2a Binary files /dev/null and b/post-images/1663502619129.png differ diff --git a/post-images/1663502646605.png b/post-images/1663502646605.png new file mode 100644 index 00000000..af1c40d5 Binary files /dev/null and b/post-images/1663502646605.png differ diff --git a/post-images/1663502671397.png b/post-images/1663502671397.png new file mode 100644 index 00000000..a446d790 Binary files /dev/null and b/post-images/1663502671397.png differ diff --git a/post-images/1663502835621.png b/post-images/1663502835621.png new file mode 100644 index 00000000..a72d3e9f Binary files /dev/null and b/post-images/1663502835621.png differ diff --git a/post-images/1663502870224.png b/post-images/1663502870224.png new file mode 100644 index 00000000..653ca8fd Binary files /dev/null and b/post-images/1663502870224.png differ diff --git a/post-images/1663503527214.png b/post-images/1663503527214.png new file mode 100644 index 00000000..7ae1c4d0 Binary files /dev/null and b/post-images/1663503527214.png differ diff --git a/post-images/1663503579976.png b/post-images/1663503579976.png new file mode 100644 index 00000000..1a3245a5 Binary files /dev/null and b/post-images/1663503579976.png differ diff --git a/post-images/1663503605851.png b/post-images/1663503605851.png new file mode 100644 index 00000000..ac9af99a Binary files /dev/null and b/post-images/1663503605851.png differ diff --git a/post-images/1663503628120.png b/post-images/1663503628120.png new file mode 100644 index 00000000..406fe571 Binary files /dev/null and b/post-images/1663503628120.png differ diff --git a/post-images/1663504191411.png b/post-images/1663504191411.png new file mode 100755 index 00000000..5ad2f465 Binary files /dev/null and b/post-images/1663504191411.png differ diff --git a/post-images/1663504203463.png b/post-images/1663504203463.png new file mode 100755 index 00000000..9c04b67a Binary files /dev/null and b/post-images/1663504203463.png differ diff --git a/post-images/1663504213984.png b/post-images/1663504213984.png new file mode 100755 index 00000000..d4d2c43d Binary files /dev/null and b/post-images/1663504213984.png differ diff --git a/post-images/1663504226314.png b/post-images/1663504226314.png new file mode 100755 index 00000000..b2a08117 Binary files /dev/null and b/post-images/1663504226314.png differ diff --git a/post-images/1663504240785.png b/post-images/1663504240785.png new file mode 100755 index 00000000..a9bc7ef7 Binary files /dev/null and b/post-images/1663504240785.png differ diff --git a/post-images/1663650948820.png b/post-images/1663650948820.png new file mode 100644 index 00000000..4411278c Binary files /dev/null and b/post-images/1663650948820.png differ diff --git a/post-images/1663651007518.png b/post-images/1663651007518.png new file mode 100644 index 00000000..9310190d Binary files /dev/null and b/post-images/1663651007518.png differ diff --git a/post-images/1663651130823.png b/post-images/1663651130823.png new file mode 100644 index 00000000..5e6961d6 Binary files /dev/null and b/post-images/1663651130823.png differ diff --git a/post-images/1663651155973.png b/post-images/1663651155973.png new file mode 100644 index 00000000..95b7ea23 Binary files /dev/null and b/post-images/1663651155973.png differ diff --git a/post-images/1663651186953.png b/post-images/1663651186953.png new file mode 100644 index 00000000..c708adfe Binary files /dev/null and b/post-images/1663651186953.png differ diff --git a/post-images/1663651215716.png b/post-images/1663651215716.png new file mode 100644 index 00000000..39cb05d3 Binary files /dev/null and b/post-images/1663651215716.png differ diff --git a/post-images/1663651254125.png b/post-images/1663651254125.png new file mode 100644 index 00000000..be289653 Binary files /dev/null and b/post-images/1663651254125.png differ diff --git a/post-images/1663651288620.png b/post-images/1663651288620.png new file mode 100644 index 00000000..be905f2d Binary files /dev/null and b/post-images/1663651288620.png differ diff --git a/post-images/1663651327218.png b/post-images/1663651327218.png new file mode 100644 index 00000000..8dfed6a3 Binary files /dev/null and b/post-images/1663651327218.png differ diff --git a/post-images/1663651360127.png b/post-images/1663651360127.png new file mode 100644 index 00000000..b32bef87 Binary files /dev/null and b/post-images/1663651360127.png differ diff --git a/post-images/1663910258316.png b/post-images/1663910258316.png new file mode 100644 index 00000000..152dbc1a Binary files /dev/null and b/post-images/1663910258316.png differ diff --git a/post-images/1663916295017.png b/post-images/1663916295017.png new file mode 100644 index 00000000..9a242c0f Binary files /dev/null and b/post-images/1663916295017.png differ diff --git a/post-images/1663916361609.jpeg b/post-images/1663916361609.jpeg new file mode 100644 index 00000000..08569b8e Binary files /dev/null and b/post-images/1663916361609.jpeg differ diff --git a/post-images/1663916372576.gif b/post-images/1663916372576.gif new file mode 100644 index 00000000..d979e9da Binary files /dev/null and b/post-images/1663916372576.gif differ diff --git a/post-images/1663922254645.jpeg b/post-images/1663922254645.jpeg new file mode 100644 index 00000000..0b5bbf2e Binary files /dev/null and b/post-images/1663922254645.jpeg differ diff --git a/post-images/1663942651564.png b/post-images/1663942651564.png new file mode 100644 index 00000000..b153e414 Binary files /dev/null and b/post-images/1663942651564.png differ diff --git a/post-images/1663991342084.png b/post-images/1663991342084.png new file mode 100644 index 00000000..2ffcbddf Binary files /dev/null and b/post-images/1663991342084.png differ diff --git a/post-images/1663991352079.png b/post-images/1663991352079.png new file mode 100644 index 00000000..b6e4dbe4 Binary files /dev/null and b/post-images/1663991352079.png differ diff --git a/post-images/1663991363527.png b/post-images/1663991363527.png new file mode 100644 index 00000000..3ac8e6a5 Binary files /dev/null and b/post-images/1663991363527.png differ diff --git a/post-images/1663991374182.png b/post-images/1663991374182.png new file mode 100644 index 00000000..f83b9387 Binary files /dev/null and b/post-images/1663991374182.png differ diff --git a/post-images/1663991930709.png b/post-images/1663991930709.png new file mode 100644 index 00000000..bb9a5284 Binary files /dev/null and b/post-images/1663991930709.png differ diff --git a/post-images/1663992194728.png b/post-images/1663992194728.png new file mode 100644 index 00000000..ed8a450a Binary files /dev/null and b/post-images/1663992194728.png differ diff --git a/post-images/1663992229526.png b/post-images/1663992229526.png new file mode 100644 index 00000000..a4ceaf28 Binary files /dev/null and b/post-images/1663992229526.png differ diff --git a/post-images/1664009813448.jpg b/post-images/1664009813448.jpg new file mode 100755 index 00000000..e343c0e6 Binary files /dev/null and b/post-images/1664009813448.jpg differ diff --git a/post-images/1664009825716.png b/post-images/1664009825716.png new file mode 100755 index 00000000..cdb07d9a Binary files /dev/null and b/post-images/1664009825716.png differ diff --git a/post-images/1664010005879.png b/post-images/1664010005879.png new file mode 100755 index 00000000..ecef94dd Binary files /dev/null and b/post-images/1664010005879.png differ diff --git a/post-images/1664010082124.png b/post-images/1664010082124.png new file mode 100755 index 00000000..85e3cfc7 Binary files /dev/null and b/post-images/1664010082124.png differ diff --git a/post-images/1664011369776.jpg b/post-images/1664011369776.jpg new file mode 100755 index 00000000..00c6cb4f Binary files /dev/null and b/post-images/1664011369776.jpg differ diff --git a/post-images/1664011386407.jpg b/post-images/1664011386407.jpg new file mode 100755 index 00000000..f2bb593e Binary files /dev/null and b/post-images/1664011386407.jpg differ diff --git a/post-images/1664011399489.jpg b/post-images/1664011399489.jpg new file mode 100755 index 00000000..c53abf01 Binary files /dev/null and b/post-images/1664011399489.jpg differ diff --git a/post-images/1664021341303.png b/post-images/1664021341303.png new file mode 100755 index 00000000..9456af65 Binary files /dev/null and b/post-images/1664021341303.png differ diff --git a/post-images/1664021406357.png b/post-images/1664021406357.png new file mode 100755 index 00000000..ba817ffa Binary files /dev/null and b/post-images/1664021406357.png differ diff --git a/post-images/1664021417426.png b/post-images/1664021417426.png new file mode 100755 index 00000000..14e03e4e Binary files /dev/null and b/post-images/1664021417426.png differ diff --git a/post-images/1664021445583.png b/post-images/1664021445583.png new file mode 100755 index 00000000..f6b8c6c1 Binary files /dev/null and b/post-images/1664021445583.png differ diff --git a/post-images/1664021473144.png b/post-images/1664021473144.png new file mode 100755 index 00000000..4da261f8 Binary files /dev/null and b/post-images/1664021473144.png differ diff --git a/post-images/1664021485061.png b/post-images/1664021485061.png new file mode 100755 index 00000000..23103f31 Binary files /dev/null and b/post-images/1664021485061.png differ diff --git a/post-images/1664021653704.png b/post-images/1664021653704.png new file mode 100644 index 00000000..08f82ac2 Binary files /dev/null and b/post-images/1664021653704.png differ diff --git a/post-images/1664022241978.png b/post-images/1664022241978.png new file mode 100644 index 00000000..99433cf3 Binary files /dev/null and b/post-images/1664022241978.png differ diff --git a/post-images/1664022264795.png b/post-images/1664022264795.png new file mode 100644 index 00000000..b00de988 Binary files /dev/null and b/post-images/1664022264795.png differ diff --git a/post-images/1664022288385.png b/post-images/1664022288385.png new file mode 100644 index 00000000..28d3d967 Binary files /dev/null and b/post-images/1664022288385.png differ diff --git a/post-images/1664022309780.png b/post-images/1664022309780.png new file mode 100644 index 00000000..08f2b659 Binary files /dev/null and b/post-images/1664022309780.png differ diff --git a/post-images/1664022352273.png b/post-images/1664022352273.png new file mode 100644 index 00000000..5862a631 Binary files /dev/null and b/post-images/1664022352273.png differ diff --git a/post-images/1664022379745.png b/post-images/1664022379745.png new file mode 100644 index 00000000..93b53c40 Binary files /dev/null and b/post-images/1664022379745.png differ diff --git a/post-images/1664022457834.jpg b/post-images/1664022457834.jpg new file mode 100755 index 00000000..d3e43cbd Binary files /dev/null and b/post-images/1664022457834.jpg differ diff --git a/post-images/1664022468442.jpg b/post-images/1664022468442.jpg new file mode 100755 index 00000000..f0d7a683 Binary files /dev/null and b/post-images/1664022468442.jpg differ diff --git a/post-images/1664022489134.jpg b/post-images/1664022489134.jpg new file mode 100755 index 00000000..c198e64a Binary files /dev/null and b/post-images/1664022489134.jpg differ diff --git a/post-images/1664022499947.jpg b/post-images/1664022499947.jpg new file mode 100755 index 00000000..68024d6c Binary files /dev/null and b/post-images/1664022499947.jpg differ diff --git a/post-images/1664022509629.jpg b/post-images/1664022509629.jpg new file mode 100755 index 00000000..06a0c613 Binary files /dev/null and b/post-images/1664022509629.jpg differ diff --git a/post-images/1664022522882.jpg b/post-images/1664022522882.jpg new file mode 100755 index 00000000..037921eb Binary files /dev/null and b/post-images/1664022522882.jpg differ diff --git a/post-images/1664022537123.jpg b/post-images/1664022537123.jpg new file mode 100755 index 00000000..c4c0ed04 Binary files /dev/null and b/post-images/1664022537123.jpg differ diff --git a/post-images/1664022547502.jpg b/post-images/1664022547502.jpg new file mode 100755 index 00000000..91372997 Binary files /dev/null and b/post-images/1664022547502.jpg differ diff --git a/post-images/1664022557070.jpg b/post-images/1664022557070.jpg new file mode 100755 index 00000000..c0b10f66 Binary files /dev/null and b/post-images/1664022557070.jpg differ diff --git a/post-images/1664022567603.jpg b/post-images/1664022567603.jpg new file mode 100755 index 00000000..d3350332 Binary files /dev/null and b/post-images/1664022567603.jpg differ diff --git a/post-images/1664022914272.png b/post-images/1664022914272.png new file mode 100644 index 00000000..2fe5589b Binary files /dev/null and b/post-images/1664022914272.png differ diff --git a/post-images/1664023037004.png b/post-images/1664023037004.png new file mode 100644 index 00000000..25be6ed4 Binary files /dev/null and b/post-images/1664023037004.png differ diff --git a/post-images/1664023363514.png b/post-images/1664023363514.png new file mode 100755 index 00000000..1672052a Binary files /dev/null and b/post-images/1664023363514.png differ diff --git a/post-images/1664023388089.png b/post-images/1664023388089.png new file mode 100755 index 00000000..63bba453 Binary files /dev/null and b/post-images/1664023388089.png differ diff --git a/post-images/1664023399833.png b/post-images/1664023399833.png new file mode 100755 index 00000000..96e2b6dd Binary files /dev/null and b/post-images/1664023399833.png differ diff --git a/post-images/1664023582027.png b/post-images/1664023582027.png new file mode 100755 index 00000000..d1c9cade Binary files /dev/null and b/post-images/1664023582027.png differ diff --git a/post-images/1664023593362.png b/post-images/1664023593362.png new file mode 100755 index 00000000..cd87012c Binary files /dev/null and b/post-images/1664023593362.png differ diff --git a/post-images/1664023603134.png b/post-images/1664023603134.png new file mode 100755 index 00000000..10719e89 Binary files /dev/null and b/post-images/1664023603134.png differ diff --git a/post-images/1664023613568.png b/post-images/1664023613568.png new file mode 100755 index 00000000..6a1202bd Binary files /dev/null and b/post-images/1664023613568.png differ diff --git a/post-images/1664023624906.png b/post-images/1664023624906.png new file mode 100755 index 00000000..b18c8872 Binary files /dev/null and b/post-images/1664023624906.png differ diff --git a/post-images/1664023841320.png b/post-images/1664023841320.png new file mode 100644 index 00000000..c6573378 Binary files /dev/null and b/post-images/1664023841320.png differ diff --git a/post-images/1664023864778.png b/post-images/1664023864778.png new file mode 100644 index 00000000..3638c6b6 Binary files /dev/null and b/post-images/1664023864778.png differ diff --git a/post-images/1664023902298.png b/post-images/1664023902298.png new file mode 100644 index 00000000..75e845d3 Binary files /dev/null and b/post-images/1664023902298.png differ diff --git a/post-images/1664023926667.png b/post-images/1664023926667.png new file mode 100644 index 00000000..d85c7e9d Binary files /dev/null and b/post-images/1664023926667.png differ diff --git a/post-images/1664023947863.png b/post-images/1664023947863.png new file mode 100644 index 00000000..11debad6 Binary files /dev/null and b/post-images/1664023947863.png differ diff --git a/post-images/1664023975435.png b/post-images/1664023975435.png new file mode 100644 index 00000000..d90a53dd Binary files /dev/null and b/post-images/1664023975435.png differ diff --git a/post-images/1664024000488.png b/post-images/1664024000488.png new file mode 100644 index 00000000..34419929 Binary files /dev/null and b/post-images/1664024000488.png differ diff --git a/post-images/1664024022493.png b/post-images/1664024022493.png new file mode 100644 index 00000000..edb4b24f Binary files /dev/null and b/post-images/1664024022493.png differ diff --git a/post-images/1664024050546.png b/post-images/1664024050546.png new file mode 100644 index 00000000..6c54cad2 Binary files /dev/null and b/post-images/1664024050546.png differ diff --git a/post-images/1664024080876.png b/post-images/1664024080876.png new file mode 100644 index 00000000..bec6d612 Binary files /dev/null and b/post-images/1664024080876.png differ diff --git a/post-images/1664024101940.png b/post-images/1664024101940.png new file mode 100644 index 00000000..2815b0ff Binary files /dev/null and b/post-images/1664024101940.png differ diff --git a/post-images/1664024124906.png b/post-images/1664024124906.png new file mode 100644 index 00000000..d96cab52 Binary files /dev/null and b/post-images/1664024124906.png differ diff --git a/post-images/1664024147272.png b/post-images/1664024147272.png new file mode 100644 index 00000000..e9c897ba Binary files /dev/null and b/post-images/1664024147272.png differ diff --git a/post-images/1664024167536.png b/post-images/1664024167536.png new file mode 100644 index 00000000..94f049ec Binary files /dev/null and b/post-images/1664024167536.png differ diff --git a/post-images/1664024610361.png b/post-images/1664024610361.png new file mode 100644 index 00000000..5565b2f6 Binary files /dev/null and b/post-images/1664024610361.png differ diff --git a/post-images/1664027505176.jpeg b/post-images/1664027505176.jpeg new file mode 100644 index 00000000..44de3d3a Binary files /dev/null and b/post-images/1664027505176.jpeg differ diff --git a/post-images/1664027520929.png b/post-images/1664027520929.png new file mode 100644 index 00000000..535fe74e Binary files /dev/null and b/post-images/1664027520929.png differ diff --git a/post-images/1664027533415.png b/post-images/1664027533415.png new file mode 100644 index 00000000..7d4d5dc2 Binary files /dev/null and b/post-images/1664027533415.png differ diff --git a/post-images/1664027544475.jpeg b/post-images/1664027544475.jpeg new file mode 100644 index 00000000..cc68c23a Binary files /dev/null and b/post-images/1664027544475.jpeg differ diff --git a/post-images/1664027556151.jpeg b/post-images/1664027556151.jpeg new file mode 100644 index 00000000..fe6841f8 Binary files /dev/null and b/post-images/1664027556151.jpeg differ diff --git a/post-images/1664027566462.jpeg b/post-images/1664027566462.jpeg new file mode 100644 index 00000000..92d6a9f5 Binary files /dev/null and b/post-images/1664027566462.jpeg differ diff --git a/post-images/1664028331831.jpeg b/post-images/1664028331831.jpeg new file mode 100644 index 00000000..5ce670c5 Binary files /dev/null and b/post-images/1664028331831.jpeg differ diff --git a/post-images/1664028341763.jpeg b/post-images/1664028341763.jpeg new file mode 100644 index 00000000..f7c410ad Binary files /dev/null and b/post-images/1664028341763.jpeg differ diff --git a/post-images/1664028351554.jpeg b/post-images/1664028351554.jpeg new file mode 100644 index 00000000..8c225aea Binary files /dev/null and b/post-images/1664028351554.jpeg differ diff --git a/post-images/1664028361674.png b/post-images/1664028361674.png new file mode 100644 index 00000000..6d67706c Binary files /dev/null and b/post-images/1664028361674.png differ diff --git a/post-images/1664028371367.jpeg b/post-images/1664028371367.jpeg new file mode 100644 index 00000000..49ffec84 Binary files /dev/null and b/post-images/1664028371367.jpeg differ diff --git a/post-images/1669816632950.png b/post-images/1669816632950.png new file mode 100644 index 00000000..d6bc4458 Binary files /dev/null and b/post-images/1669816632950.png differ diff --git a/post-images/1680320187761.png b/post-images/1680320187761.png new file mode 100644 index 00000000..04ff030b Binary files /dev/null and b/post-images/1680320187761.png differ diff --git a/post-images/1680354878357.png b/post-images/1680354878357.png new file mode 100644 index 00000000..8785e3b9 Binary files /dev/null and b/post-images/1680354878357.png differ diff --git a/post-images/1692412832905.png b/post-images/1692412832905.png new file mode 100644 index 00000000..2fa8d182 Binary files /dev/null and b/post-images/1692412832905.png differ diff --git a/post-images/1692989484136.png b/post-images/1692989484136.png new file mode 100644 index 00000000..41117a98 Binary files /dev/null and b/post-images/1692989484136.png differ diff --git a/post-images/1695520974997.png b/post-images/1695520974997.png new file mode 100644 index 00000000..5dec8ab1 Binary files /dev/null and b/post-images/1695520974997.png differ diff --git a/post-images/1695521190397.png b/post-images/1695521190397.png new file mode 100644 index 00000000..15214a0b Binary files /dev/null and b/post-images/1695521190397.png differ diff --git a/post-images/1695530079291.png b/post-images/1695530079291.png new file mode 100644 index 00000000..f4c60ab4 Binary files /dev/null and b/post-images/1695530079291.png differ diff --git a/post-images/1695530699746.png b/post-images/1695530699746.png new file mode 100644 index 00000000..eeb1b073 Binary files /dev/null and b/post-images/1695530699746.png differ diff --git a/post-images/1695531180353.png b/post-images/1695531180353.png new file mode 100644 index 00000000..402b3bd3 Binary files /dev/null and b/post-images/1695531180353.png differ diff --git a/post-images/1699361177206.jpg b/post-images/1699361177206.jpg new file mode 100644 index 00000000..92e137a6 Binary files /dev/null and b/post-images/1699361177206.jpg differ diff --git a/post-images/1699361213869.jpeg b/post-images/1699361213869.jpeg new file mode 100644 index 00000000..af70cc94 Binary files /dev/null and b/post-images/1699361213869.jpeg differ diff --git a/post-images/1711781310453.jpg b/post-images/1711781310453.jpg new file mode 100644 index 00000000..f2887402 Binary files /dev/null and b/post-images/1711781310453.jpg differ diff --git a/post-images/1711781498998.jpg b/post-images/1711781498998.jpg new file mode 100644 index 00000000..bb7fa4de Binary files /dev/null and b/post-images/1711781498998.jpg differ diff --git a/post-images/1711781557960.jpg b/post-images/1711781557960.jpg new file mode 100644 index 00000000..e0a6b908 Binary files /dev/null and b/post-images/1711781557960.jpg differ diff --git a/post-images/1711781595001.jpg b/post-images/1711781595001.jpg new file mode 100644 index 00000000..42159a30 Binary files /dev/null and b/post-images/1711781595001.jpg differ diff --git a/post-images/1711781625155.jpg b/post-images/1711781625155.jpg new file mode 100644 index 00000000..d2b6f3a1 Binary files /dev/null and b/post-images/1711781625155.jpg differ diff --git a/post-images/1711781655714.jpg b/post-images/1711781655714.jpg new file mode 100644 index 00000000..7da15b8a Binary files /dev/null and b/post-images/1711781655714.jpg differ diff --git a/post-images/1711781693401.jpg b/post-images/1711781693401.jpg new file mode 100644 index 00000000..85ba1f05 Binary files /dev/null and b/post-images/1711781693401.jpg differ diff --git a/post-images/1711781771969.jpg b/post-images/1711781771969.jpg new file mode 100644 index 00000000..4b031423 Binary files /dev/null and b/post-images/1711781771969.jpg differ diff --git a/post-images/1711781797528.jpg b/post-images/1711781797528.jpg new file mode 100644 index 00000000..821338dc Binary files /dev/null and b/post-images/1711781797528.jpg differ diff --git a/post-images/1711781834212.jpg b/post-images/1711781834212.jpg new file mode 100644 index 00000000..ef6ff758 Binary files /dev/null and b/post-images/1711781834212.jpg differ diff --git a/post-images/1711784293569.png b/post-images/1711784293569.png new file mode 100644 index 00000000..a5d5a934 Binary files /dev/null and b/post-images/1711784293569.png differ diff --git a/post-images/1711863336941.jpg b/post-images/1711863336941.jpg new file mode 100644 index 00000000..754052fa Binary files /dev/null and b/post-images/1711863336941.jpg differ diff --git a/post-images/1711896560058.jpg b/post-images/1711896560058.jpg new file mode 100644 index 00000000..4bdacb9d Binary files /dev/null and b/post-images/1711896560058.jpg differ diff --git a/post-images/1711935512613.jpg b/post-images/1711935512613.jpg new file mode 100644 index 00000000..b03f3ffd Binary files /dev/null and b/post-images/1711935512613.jpg differ diff --git a/post-images/1711935552214.jpg b/post-images/1711935552214.jpg new file mode 100644 index 00000000..5341d39a Binary files /dev/null and b/post-images/1711935552214.jpg differ diff --git a/post-images/1711935715363.jpg b/post-images/1711935715363.jpg new file mode 100644 index 00000000..1205a7e6 Binary files /dev/null and b/post-images/1711935715363.jpg differ diff --git a/post-images/1711935784068.jpg b/post-images/1711935784068.jpg new file mode 100644 index 00000000..613f1d81 Binary files /dev/null and b/post-images/1711935784068.jpg differ diff --git a/post-images/1711935921653.jpg b/post-images/1711935921653.jpg new file mode 100644 index 00000000..9a0b3d7e Binary files /dev/null and b/post-images/1711935921653.jpg differ diff --git a/post-images/1712312501391.jpg b/post-images/1712312501391.jpg new file mode 100644 index 00000000..340efd40 Binary files /dev/null and b/post-images/1712312501391.jpg differ diff --git a/post-images/1712312632037.jpg b/post-images/1712312632037.jpg new file mode 100644 index 00000000..3db17304 Binary files /dev/null and b/post-images/1712312632037.jpg differ diff --git a/post-images/1712312709005.jpg b/post-images/1712312709005.jpg new file mode 100644 index 00000000..73a6f2ce Binary files /dev/null and b/post-images/1712312709005.jpg differ diff --git a/post-images/1712314865450.jpg b/post-images/1712314865450.jpg new file mode 100644 index 00000000..b0ffe127 Binary files /dev/null and b/post-images/1712314865450.jpg differ diff --git a/post-images/1712315404588.jpg b/post-images/1712315404588.jpg new file mode 100644 index 00000000..706e30e8 Binary files /dev/null and b/post-images/1712315404588.jpg differ diff --git a/post-images/1712729509776.jpg b/post-images/1712729509776.jpg new file mode 100644 index 00000000..b294c86e Binary files /dev/null and b/post-images/1712729509776.jpg differ diff --git a/post-images/1712734586550.jpg b/post-images/1712734586550.jpg new file mode 100644 index 00000000..7f73f6c6 Binary files /dev/null and b/post-images/1712734586550.jpg differ diff --git a/post-images/1712743847232.jpg b/post-images/1712743847232.jpg new file mode 100644 index 00000000..a80e188f Binary files /dev/null and b/post-images/1712743847232.jpg differ diff --git a/post-images/1712752733550.jpeg b/post-images/1712752733550.jpeg new file mode 100644 index 00000000..01d4b1fc Binary files /dev/null and b/post-images/1712752733550.jpeg differ diff --git a/post-images/1712762565371.jpg b/post-images/1712762565371.jpg new file mode 100644 index 00000000..ea1acec7 Binary files /dev/null and b/post-images/1712762565371.jpg differ diff --git a/post-images/1712762753467.jpg b/post-images/1712762753467.jpg new file mode 100644 index 00000000..1cafd39b Binary files /dev/null and b/post-images/1712762753467.jpg differ diff --git a/post-images/1712762878594.jpg b/post-images/1712762878594.jpg new file mode 100644 index 00000000..c3dc9b05 Binary files /dev/null and b/post-images/1712762878594.jpg differ diff --git a/post-images/1712762940847.jpg b/post-images/1712762940847.jpg new file mode 100644 index 00000000..e2d1b27e Binary files /dev/null and b/post-images/1712762940847.jpg differ diff --git a/post-images/1712815809665.png b/post-images/1712815809665.png new file mode 100644 index 00000000..92ebbaf6 Binary files /dev/null and b/post-images/1712815809665.png differ diff --git a/post-images/1712816238227.png b/post-images/1712816238227.png new file mode 100644 index 00000000..49516834 Binary files /dev/null and b/post-images/1712816238227.png differ diff --git a/post-images/1712818793115.webp b/post-images/1712818793115.webp new file mode 100644 index 00000000..ede0df16 Binary files /dev/null and b/post-images/1712818793115.webp differ diff --git a/post-images/1712821592970.png b/post-images/1712821592970.png new file mode 100644 index 00000000..e69d151e Binary files /dev/null and b/post-images/1712821592970.png differ diff --git a/post-images/1712823368706.png b/post-images/1712823368706.png new file mode 100644 index 00000000..25a38ec2 Binary files /dev/null and b/post-images/1712823368706.png differ diff --git a/post-images/1712823602990.png b/post-images/1712823602990.png new file mode 100644 index 00000000..1f104142 Binary files /dev/null and b/post-images/1712823602990.png differ diff --git a/post-images/1712905322762.png b/post-images/1712905322762.png new file mode 100644 index 00000000..53312e64 Binary files /dev/null and b/post-images/1712905322762.png differ diff --git a/post-images/1712907208290.png b/post-images/1712907208290.png new file mode 100644 index 00000000..18cff204 Binary files /dev/null and b/post-images/1712907208290.png differ diff --git a/post-images/1712910487982.jpg b/post-images/1712910487982.jpg new file mode 100644 index 00000000..37461e5c Binary files /dev/null and b/post-images/1712910487982.jpg differ diff --git a/post-images/1714867782240.png b/post-images/1714867782240.png new file mode 100644 index 00000000..006fd7cd Binary files /dev/null and b/post-images/1714867782240.png differ diff --git a/post-images/1714869273668.png b/post-images/1714869273668.png new file mode 100644 index 00000000..60484c32 Binary files /dev/null and b/post-images/1714869273668.png differ diff --git a/post-images/1714869331525.png b/post-images/1714869331525.png new file mode 100644 index 00000000..419a9207 Binary files /dev/null and b/post-images/1714869331525.png differ diff --git a/post-images/1714869482718.png b/post-images/1714869482718.png new file mode 100644 index 00000000..f9f9bca8 Binary files /dev/null and b/post-images/1714869482718.png differ diff --git a/post-images/1714869495723.png b/post-images/1714869495723.png new file mode 100644 index 00000000..ab7b5f6f Binary files /dev/null and b/post-images/1714869495723.png differ diff --git a/post-images/1714869605240.png b/post-images/1714869605240.png new file mode 100644 index 00000000..b8164a01 Binary files /dev/null and b/post-images/1714869605240.png differ diff --git a/post-images/1714870536761.png b/post-images/1714870536761.png new file mode 100644 index 00000000..8af97d5b Binary files /dev/null and b/post-images/1714870536761.png differ diff --git a/post-images/1714870915602.png b/post-images/1714870915602.png new file mode 100644 index 00000000..656685c5 Binary files /dev/null and b/post-images/1714870915602.png differ diff --git a/post-images/1714871441450.png b/post-images/1714871441450.png new file mode 100644 index 00000000..a8144b27 Binary files /dev/null and b/post-images/1714871441450.png differ diff --git a/post-images/1714874810469.jpg b/post-images/1714874810469.jpg new file mode 100644 index 00000000..cd7d49cb Binary files /dev/null and b/post-images/1714874810469.jpg differ diff --git a/post-images/1714874938612.jpg b/post-images/1714874938612.jpg new file mode 100644 index 00000000..8d0572c3 Binary files /dev/null and b/post-images/1714874938612.jpg differ diff --git a/post-images/1714875019305.jpg b/post-images/1714875019305.jpg new file mode 100644 index 00000000..486afc16 Binary files /dev/null and b/post-images/1714875019305.jpg differ diff --git a/post-images/1715221368832.jpg b/post-images/1715221368832.jpg new file mode 100644 index 00000000..82ebf6aa Binary files /dev/null and b/post-images/1715221368832.jpg differ diff --git a/post-images/1715224425238.jpg b/post-images/1715224425238.jpg new file mode 100644 index 00000000..e694ab1a Binary files /dev/null and b/post-images/1715224425238.jpg differ diff --git a/post-images/1715224847762.jpg b/post-images/1715224847762.jpg new file mode 100644 index 00000000..8501cd70 Binary files /dev/null and b/post-images/1715224847762.jpg differ diff --git a/post-images/1715242112851.jpg b/post-images/1715242112851.jpg new file mode 100644 index 00000000..cbe9f364 Binary files /dev/null and b/post-images/1715242112851.jpg differ diff --git a/post-images/1715242122909.jpg b/post-images/1715242122909.jpg new file mode 100644 index 00000000..fdd36f69 Binary files /dev/null and b/post-images/1715242122909.jpg differ diff --git a/post-images/1715309697553.png b/post-images/1715309697553.png new file mode 100644 index 00000000..703fc0cc Binary files /dev/null and b/post-images/1715309697553.png differ diff --git a/post-images/1715330662896.png b/post-images/1715330662896.png new file mode 100644 index 00000000..ab455f4c Binary files /dev/null and b/post-images/1715330662896.png differ diff --git a/post-images/1715335896388.jpg b/post-images/1715335896388.jpg new file mode 100644 index 00000000..486afc16 Binary files /dev/null and b/post-images/1715335896388.jpg differ diff --git a/post-images/1715354184895.jpg b/post-images/1715354184895.jpg new file mode 100644 index 00000000..a7fe1224 Binary files /dev/null and b/post-images/1715354184895.jpg differ diff --git a/post-images/1715354684295.png b/post-images/1715354684295.png new file mode 100644 index 00000000..08351dc2 Binary files /dev/null and b/post-images/1715354684295.png differ diff --git a/post-images/1715387535669.png b/post-images/1715387535669.png new file mode 100644 index 00000000..a1275e76 Binary files /dev/null and b/post-images/1715387535669.png differ diff --git a/post-images/1715388986257.jpg b/post-images/1715388986257.jpg new file mode 100644 index 00000000..8b50271b Binary files /dev/null and b/post-images/1715388986257.jpg differ diff --git a/post-images/1715389375099.png b/post-images/1715389375099.png new file mode 100644 index 00000000..94fd4e1b Binary files /dev/null and b/post-images/1715389375099.png differ diff --git a/post-images/1715389968409.png b/post-images/1715389968409.png new file mode 100644 index 00000000..9ffb7cd8 Binary files /dev/null and b/post-images/1715389968409.png differ diff --git a/post-images/1715391233428.jpg b/post-images/1715391233428.jpg new file mode 100644 index 00000000..8a6d55c7 Binary files /dev/null and b/post-images/1715391233428.jpg differ diff --git a/post-images/1715394337004.png b/post-images/1715394337004.png new file mode 100644 index 00000000..8e1a8c45 Binary files /dev/null and b/post-images/1715394337004.png differ diff --git a/post-images/1715394348459.png b/post-images/1715394348459.png new file mode 100644 index 00000000..d5bf2137 Binary files /dev/null and b/post-images/1715394348459.png differ diff --git a/post-images/1715397133346.png b/post-images/1715397133346.png new file mode 100644 index 00000000..26996298 Binary files /dev/null and b/post-images/1715397133346.png differ diff --git a/post-images/1715401686567.png b/post-images/1715401686567.png new file mode 100644 index 00000000..7ee7831f Binary files /dev/null and b/post-images/1715401686567.png differ diff --git a/post-images/1715402444954.png b/post-images/1715402444954.png new file mode 100644 index 00000000..43ee11e1 Binary files /dev/null and b/post-images/1715402444954.png differ diff --git a/post-images/1M5AYjF7f.png b/post-images/1M5AYjF7f.png new file mode 100755 index 00000000..34bce099 Binary files /dev/null and b/post-images/1M5AYjF7f.png differ diff --git a/post-images/2-Lad22RM.png b/post-images/2-Lad22RM.png new file mode 100644 index 00000000..b04780d9 Binary files /dev/null and b/post-images/2-Lad22RM.png differ diff --git a/post-images/25MtObxIW.jpg b/post-images/25MtObxIW.jpg new file mode 100755 index 00000000..453c2128 Binary files /dev/null and b/post-images/25MtObxIW.jpg differ diff --git a/post-images/4jVVEgoF6.jpeg b/post-images/4jVVEgoF6.jpeg new file mode 100644 index 00000000..471f1438 Binary files /dev/null and b/post-images/4jVVEgoF6.jpeg differ diff --git a/post-images/4ps_vLvTd.jpeg b/post-images/4ps_vLvTd.jpeg new file mode 100644 index 00000000..de1f9c9a Binary files /dev/null and b/post-images/4ps_vLvTd.jpeg differ diff --git a/post-images/5EoDeie7t.jpeg b/post-images/5EoDeie7t.jpeg new file mode 100644 index 00000000..9c1643ec Binary files /dev/null and b/post-images/5EoDeie7t.jpeg differ diff --git a/post-images/5hyz5Z3oU.png b/post-images/5hyz5Z3oU.png new file mode 100644 index 00000000..47aa402b Binary files /dev/null and b/post-images/5hyz5Z3oU.png differ diff --git a/post-images/5rUXZg3sM.jpeg b/post-images/5rUXZg3sM.jpeg new file mode 100644 index 00000000..e4825392 Binary files /dev/null and b/post-images/5rUXZg3sM.jpeg differ diff --git a/post-images/7GKaPZAOw.jpeg b/post-images/7GKaPZAOw.jpeg new file mode 100644 index 00000000..d1e99eeb Binary files /dev/null and b/post-images/7GKaPZAOw.jpeg differ diff --git a/post-images/7pUUwIeSw.jpeg b/post-images/7pUUwIeSw.jpeg new file mode 100644 index 00000000..b110b91c Binary files /dev/null and b/post-images/7pUUwIeSw.jpeg differ diff --git a/post-images/8sxCrqU9f.png b/post-images/8sxCrqU9f.png new file mode 100644 index 00000000..2fa8d182 Binary files /dev/null and b/post-images/8sxCrqU9f.png differ diff --git a/post-images/9Ov7sD1uz.png b/post-images/9Ov7sD1uz.png new file mode 100644 index 00000000..4c13fe91 Binary files /dev/null and b/post-images/9Ov7sD1uz.png differ diff --git a/post-images/AOlj4J-Zw.webp b/post-images/AOlj4J-Zw.webp new file mode 100644 index 00000000..e608bc5c Binary files /dev/null and b/post-images/AOlj4J-Zw.webp differ diff --git a/post-images/AThbvq-Gb.jpeg b/post-images/AThbvq-Gb.jpeg new file mode 100755 index 00000000..edff8515 Binary files /dev/null and b/post-images/AThbvq-Gb.jpeg differ diff --git a/post-images/AeVQtjKb0.png b/post-images/AeVQtjKb0.png new file mode 100644 index 00000000..d7b5996e Binary files /dev/null and b/post-images/AeVQtjKb0.png differ diff --git a/post-images/CJEg4Tlzw.png b/post-images/CJEg4Tlzw.png new file mode 100755 index 00000000..34bce099 Binary files /dev/null and b/post-images/CJEg4Tlzw.png differ diff --git a/post-images/Dj7VgGQ1S.jpeg b/post-images/Dj7VgGQ1S.jpeg new file mode 100644 index 00000000..2d1203fa Binary files /dev/null and b/post-images/Dj7VgGQ1S.jpeg differ diff --git a/post-images/EkpumV6IX.png b/post-images/EkpumV6IX.png new file mode 100644 index 00000000..27059efc Binary files /dev/null and b/post-images/EkpumV6IX.png differ diff --git a/post-images/FoK6YM8MY.jpg b/post-images/FoK6YM8MY.jpg new file mode 100755 index 00000000..98e22374 Binary files /dev/null and b/post-images/FoK6YM8MY.jpg differ diff --git a/post-images/G1rwEVR-s.jpeg b/post-images/G1rwEVR-s.jpeg new file mode 100644 index 00000000..61a6d12f Binary files /dev/null and b/post-images/G1rwEVR-s.jpeg differ diff --git a/post-images/G1rwEVR-s.jpg b/post-images/G1rwEVR-s.jpg new file mode 100755 index 00000000..1624a0bc Binary files /dev/null and b/post-images/G1rwEVR-s.jpg differ diff --git a/post-images/K8GZGQszm.jpg b/post-images/K8GZGQszm.jpg new file mode 100755 index 00000000..ee7dde00 Binary files /dev/null and b/post-images/K8GZGQszm.jpg differ diff --git a/post-images/KRW8MVjMZ.jpeg b/post-images/KRW8MVjMZ.jpeg new file mode 100644 index 00000000..b8033c64 Binary files /dev/null and b/post-images/KRW8MVjMZ.jpeg differ diff --git a/post-images/L3EbniCYD.png b/post-images/L3EbniCYD.png new file mode 100644 index 00000000..0c5fd3f4 Binary files /dev/null and b/post-images/L3EbniCYD.png differ diff --git a/post-images/MFBljEI1_.jpeg b/post-images/MFBljEI1_.jpeg new file mode 100644 index 00000000..551559c8 Binary files /dev/null and b/post-images/MFBljEI1_.jpeg differ diff --git a/post-images/M_3PVzDfm.jpeg b/post-images/M_3PVzDfm.jpeg new file mode 100644 index 00000000..e4825392 Binary files /dev/null and b/post-images/M_3PVzDfm.jpeg differ diff --git a/post-images/NrUpfcbXq.jpeg b/post-images/NrUpfcbXq.jpeg new file mode 100644 index 00000000..9f905af9 Binary files /dev/null and b/post-images/NrUpfcbXq.jpeg differ diff --git a/post-images/O-EjxlN4I.jpeg b/post-images/O-EjxlN4I.jpeg new file mode 100644 index 00000000..471f1438 Binary files /dev/null and b/post-images/O-EjxlN4I.jpeg differ diff --git a/post-images/ORLDJnyfZ.jpeg b/post-images/ORLDJnyfZ.jpeg new file mode 100644 index 00000000..d90c5f5e Binary files /dev/null and b/post-images/ORLDJnyfZ.jpeg differ diff --git a/post-images/RYkQYSFKV.jpeg b/post-images/RYkQYSFKV.jpeg new file mode 100644 index 00000000..471f1438 Binary files /dev/null and b/post-images/RYkQYSFKV.jpeg differ diff --git a/post-images/S4taTDbrM.png b/post-images/S4taTDbrM.png new file mode 100755 index 00000000..cdb07d9a Binary files /dev/null and b/post-images/S4taTDbrM.png differ diff --git a/post-images/S8r7al_js.png b/post-images/S8r7al_js.png new file mode 100644 index 00000000..92b2e299 Binary files /dev/null and b/post-images/S8r7al_js.png differ diff --git a/post-images/S9me2CdAH.jpeg b/post-images/S9me2CdAH.jpeg new file mode 100644 index 00000000..dff29ec7 Binary files /dev/null and b/post-images/S9me2CdAH.jpeg differ diff --git a/post-images/TgsXu70q2.jpeg b/post-images/TgsXu70q2.jpeg new file mode 100644 index 00000000..f287d6d3 Binary files /dev/null and b/post-images/TgsXu70q2.jpeg differ diff --git a/post-images/VJe_7M1YY.jpeg b/post-images/VJe_7M1YY.jpeg new file mode 100644 index 00000000..081015b4 Binary files /dev/null and b/post-images/VJe_7M1YY.jpeg differ diff --git a/post-images/WfB5h55oQ.jpeg b/post-images/WfB5h55oQ.jpeg new file mode 100644 index 00000000..4203e6b3 Binary files /dev/null and b/post-images/WfB5h55oQ.jpeg differ diff --git a/post-images/WnccL2rgJ.jpeg b/post-images/WnccL2rgJ.jpeg new file mode 100644 index 00000000..d458fc05 Binary files /dev/null and b/post-images/WnccL2rgJ.jpeg differ diff --git a/post-images/X1-n4nEmj.png b/post-images/X1-n4nEmj.png new file mode 100644 index 00000000..348a66d6 Binary files /dev/null and b/post-images/X1-n4nEmj.png differ diff --git a/post-images/X5KvKQqL-.jpeg b/post-images/X5KvKQqL-.jpeg new file mode 100644 index 00000000..dbabf2c3 Binary files /dev/null and b/post-images/X5KvKQqL-.jpeg differ diff --git a/post-images/ZN0MLPPrG.jpeg b/post-images/ZN0MLPPrG.jpeg new file mode 100644 index 00000000..0be697f4 Binary files /dev/null and b/post-images/ZN0MLPPrG.jpeg differ diff --git a/post-images/_4KYecN7b.png b/post-images/_4KYecN7b.png new file mode 100644 index 00000000..c68e3a17 Binary files /dev/null and b/post-images/_4KYecN7b.png differ diff --git a/post-images/_KpeiJK6G.png b/post-images/_KpeiJK6G.png new file mode 100644 index 00000000..b609d5b4 Binary files /dev/null and b/post-images/_KpeiJK6G.png differ diff --git a/post-images/_PaEG890x.jpeg b/post-images/_PaEG890x.jpeg new file mode 100644 index 00000000..6097fdd7 Binary files /dev/null and b/post-images/_PaEG890x.jpeg differ diff --git a/post-images/_v3DmjUkj.jpeg b/post-images/_v3DmjUkj.jpeg new file mode 100644 index 00000000..2b6fd82b Binary files /dev/null and b/post-images/_v3DmjUkj.jpeg differ diff --git a/post-images/a-74x0moT.jpeg b/post-images/a-74x0moT.jpeg new file mode 100644 index 00000000..e3d8ee7c Binary files /dev/null and b/post-images/a-74x0moT.jpeg differ diff --git a/post-images/about.png b/post-images/about.png new file mode 100755 index 00000000..d4ee36e9 Binary files /dev/null and b/post-images/about.png differ diff --git a/post-images/atRZtjRTE.jpg b/post-images/atRZtjRTE.jpg new file mode 100644 index 00000000..2e439a71 Binary files /dev/null and b/post-images/atRZtjRTE.jpg differ diff --git a/post-images/bOFRz9RDX.png b/post-images/bOFRz9RDX.png new file mode 100755 index 00000000..34bce099 Binary files /dev/null and b/post-images/bOFRz9RDX.png differ diff --git a/post-images/bdeuyZAFy.jpeg b/post-images/bdeuyZAFy.jpeg new file mode 100644 index 00000000..21aba82c Binary files /dev/null and b/post-images/bdeuyZAFy.jpeg differ diff --git a/post-images/br0hmOiiG.jpeg b/post-images/br0hmOiiG.jpeg new file mode 100644 index 00000000..b0de8bf0 Binary files /dev/null and b/post-images/br0hmOiiG.jpeg differ diff --git a/post-images/c4hRhCBs7.png b/post-images/c4hRhCBs7.png new file mode 100644 index 00000000..5ed0bd9a Binary files /dev/null and b/post-images/c4hRhCBs7.png differ diff --git a/post-images/cCUDwnJ69.png b/post-images/cCUDwnJ69.png new file mode 100644 index 00000000..6ccca1a4 Binary files /dev/null and b/post-images/cCUDwnJ69.png differ diff --git a/post-images/dDolfjfkM.jpeg b/post-images/dDolfjfkM.jpeg new file mode 100644 index 00000000..2a5e9b5d Binary files /dev/null and b/post-images/dDolfjfkM.jpeg differ diff --git a/post-images/dM1xQIc5h.jpeg b/post-images/dM1xQIc5h.jpeg new file mode 100644 index 00000000..bc025dd1 Binary files /dev/null and b/post-images/dM1xQIc5h.jpeg differ diff --git a/post-images/esdygML9J.jpeg b/post-images/esdygML9J.jpeg new file mode 100644 index 00000000..2f5d5c4e Binary files /dev/null and b/post-images/esdygML9J.jpeg differ diff --git a/post-images/f1Aq_KLt0.jpeg b/post-images/f1Aq_KLt0.jpeg new file mode 100644 index 00000000..be7eb157 Binary files /dev/null and b/post-images/f1Aq_KLt0.jpeg differ diff --git a/post-images/ghceoN-RR.jpeg b/post-images/ghceoN-RR.jpeg new file mode 100644 index 00000000..17810fc7 Binary files /dev/null and b/post-images/ghceoN-RR.jpeg differ diff --git a/post-images/h1zgmhimT.jpeg b/post-images/h1zgmhimT.jpeg new file mode 100644 index 00000000..9336b671 Binary files /dev/null and b/post-images/h1zgmhimT.jpeg differ diff --git a/post-images/hVl3D7IfG.png b/post-images/hVl3D7IfG.png new file mode 100644 index 00000000..29c5b1d0 Binary files /dev/null and b/post-images/hVl3D7IfG.png differ diff --git a/post-images/hnydo1Gn3.jpeg b/post-images/hnydo1Gn3.jpeg new file mode 100644 index 00000000..ac118a3c Binary files /dev/null and b/post-images/hnydo1Gn3.jpeg differ diff --git a/post-images/iwTJpCGZp.jpeg b/post-images/iwTJpCGZp.jpeg new file mode 100644 index 00000000..263e236b Binary files /dev/null and b/post-images/iwTJpCGZp.jpeg differ diff --git a/post-images/iwTJpCGZp.png b/post-images/iwTJpCGZp.png new file mode 100644 index 00000000..9c8f6396 Binary files /dev/null and b/post-images/iwTJpCGZp.png differ diff --git a/post-images/kLV2cAImc.png b/post-images/kLV2cAImc.png new file mode 100644 index 00000000..eeb1b073 Binary files /dev/null and b/post-images/kLV2cAImc.png differ diff --git a/post-images/lSehulG6w.jpeg b/post-images/lSehulG6w.jpeg new file mode 100644 index 00000000..04ecae63 Binary files /dev/null and b/post-images/lSehulG6w.jpeg differ diff --git a/post-images/lZGv3YBvB.jpg b/post-images/lZGv3YBvB.jpg new file mode 100755 index 00000000..a924c08a Binary files /dev/null and b/post-images/lZGv3YBvB.jpg differ diff --git a/post-images/nUXTnR04_.jpeg b/post-images/nUXTnR04_.jpeg new file mode 100644 index 00000000..4256b645 Binary files /dev/null and b/post-images/nUXTnR04_.jpeg differ diff --git a/post-images/nui27fvUb.jpeg b/post-images/nui27fvUb.jpeg new file mode 100644 index 00000000..ff5ae8fc Binary files /dev/null and b/post-images/nui27fvUb.jpeg differ diff --git a/post-images/oNwbCz1HH.jpeg b/post-images/oNwbCz1HH.jpeg new file mode 100644 index 00000000..471f1438 Binary files /dev/null and b/post-images/oNwbCz1HH.jpeg differ diff --git a/post-images/pyLslIFOK.png b/post-images/pyLslIFOK.png new file mode 100644 index 00000000..ab969b0a Binary files /dev/null and b/post-images/pyLslIFOK.png differ diff --git a/post-images/q24QUn6OD.jpeg b/post-images/q24QUn6OD.jpeg new file mode 100644 index 00000000..551559c8 Binary files /dev/null and b/post-images/q24QUn6OD.jpeg differ diff --git a/post-images/qxVW9efzD.png b/post-images/qxVW9efzD.png new file mode 100644 index 00000000..de5db7ab Binary files /dev/null and b/post-images/qxVW9efzD.png differ diff --git a/post-images/qzip9Ct6U.webp b/post-images/qzip9Ct6U.webp new file mode 100644 index 00000000..66162d7a Binary files /dev/null and b/post-images/qzip9Ct6U.webp differ diff --git a/post-images/s1Mfu1JcV.jpeg b/post-images/s1Mfu1JcV.jpeg new file mode 100644 index 00000000..57574dba Binary files /dev/null and b/post-images/s1Mfu1JcV.jpeg differ diff --git a/post-images/s3CKW7x7C.png b/post-images/s3CKW7x7C.png new file mode 100644 index 00000000..6ccca1a4 Binary files /dev/null and b/post-images/s3CKW7x7C.png differ diff --git a/post-images/sBMrrNbs4.jpeg b/post-images/sBMrrNbs4.jpeg new file mode 100644 index 00000000..2e1f3c77 Binary files /dev/null and b/post-images/sBMrrNbs4.jpeg differ diff --git a/post-images/sEJgU0CCS.jpeg b/post-images/sEJgU0CCS.jpeg new file mode 100644 index 00000000..254816bd Binary files /dev/null and b/post-images/sEJgU0CCS.jpeg differ diff --git a/post-images/sPxxvjVav.jpeg b/post-images/sPxxvjVav.jpeg new file mode 100644 index 00000000..3722c8e7 Binary files /dev/null and b/post-images/sPxxvjVav.jpeg differ diff --git a/post-images/tF9vlkh8U.jpeg b/post-images/tF9vlkh8U.jpeg new file mode 100755 index 00000000..85b124b6 Binary files /dev/null and b/post-images/tF9vlkh8U.jpeg differ diff --git a/post-images/tZnRnI64b.jpeg b/post-images/tZnRnI64b.jpeg new file mode 100644 index 00000000..22777a1b Binary files /dev/null and b/post-images/tZnRnI64b.jpeg differ diff --git a/post-images/tZpY6JWhD.jpg b/post-images/tZpY6JWhD.jpg new file mode 100644 index 00000000..93fc728c Binary files /dev/null and b/post-images/tZpY6JWhD.jpg differ diff --git a/post-images/tZpY6JWhD.png b/post-images/tZpY6JWhD.png new file mode 100644 index 00000000..b0c7edca Binary files /dev/null and b/post-images/tZpY6JWhD.png differ diff --git a/post-images/uDVEq7OW-.jpeg b/post-images/uDVEq7OW-.jpeg new file mode 100644 index 00000000..b221b790 Binary files /dev/null and b/post-images/uDVEq7OW-.jpeg differ diff --git a/post-images/uHEHy2zPz.jpeg b/post-images/uHEHy2zPz.jpeg new file mode 100644 index 00000000..471f1438 Binary files /dev/null and b/post-images/uHEHy2zPz.jpeg differ diff --git a/post-images/udNd3A-Sd.jpeg b/post-images/udNd3A-Sd.jpeg new file mode 100644 index 00000000..4db4f5c9 Binary files /dev/null and b/post-images/udNd3A-Sd.jpeg differ diff --git a/post-images/ulgA83fCg.jpeg b/post-images/ulgA83fCg.jpeg new file mode 100755 index 00000000..6509e641 Binary files /dev/null and b/post-images/ulgA83fCg.jpeg differ diff --git a/post-images/w-PjYn9Fz.jpg b/post-images/w-PjYn9Fz.jpg new file mode 100755 index 00000000..f695f442 Binary files /dev/null and b/post-images/w-PjYn9Fz.jpg differ diff --git a/post-images/wTPQ_ONKy.png b/post-images/wTPQ_ONKy.png new file mode 100644 index 00000000..ab969b0a Binary files /dev/null and b/post-images/wTPQ_ONKy.png differ diff --git a/post-images/wZWdqj3Rd.jpeg b/post-images/wZWdqj3Rd.jpeg new file mode 100644 index 00000000..eeb5345d Binary files /dev/null and b/post-images/wZWdqj3Rd.jpeg differ diff --git a/post-images/wZWdqj3Rd.jpg b/post-images/wZWdqj3Rd.jpg new file mode 100755 index 00000000..abf51c36 Binary files /dev/null and b/post-images/wZWdqj3Rd.jpg differ diff --git a/post-images/wfWg7oC32.jpg b/post-images/wfWg7oC32.jpg new file mode 100755 index 00000000..c970e126 Binary files /dev/null and b/post-images/wfWg7oC32.jpg differ diff --git a/post-images/xdxGDHzvU.png b/post-images/xdxGDHzvU.png new file mode 100644 index 00000000..4c946314 Binary files /dev/null and b/post-images/xdxGDHzvU.png differ diff --git a/post-images/xhU3PP5t5.jpeg b/post-images/xhU3PP5t5.jpeg new file mode 100644 index 00000000..16fe1d14 Binary files /dev/null and b/post-images/xhU3PP5t5.jpeg differ diff --git a/post-images/xxKgB7-Zw.png b/post-images/xxKgB7-Zw.png new file mode 100644 index 00000000..87c0cd9b Binary files /dev/null and b/post-images/xxKgB7-Zw.png differ diff --git a/pyLslIFOK/index.html b/pyLslIFOK/index.html new file mode 100644 index 00000000..e2b440e2 --- /dev/null +++ b/pyLslIFOK/index.html @@ -0,0 +1,456 @@ + + + + + + + + 为什么会出现网店代运营服务? | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 为什么会出现网店代运营服务? +

+ + +
+ +
+
+

本文写于 2020 年 4 月,较于 4 月版本本文已做部分修改

+
+

最近在深圳待的厌烦了,网店代运营这个行业兴起来没有多久,逮着清明假期索性到成都几个做淘宝和拼多多代运营的公司走了走,主要是想实地感受一下电商代运营是个什么玩意!截止此文章发布时间,我已经在这个公司待了一周了,但限于理解能力有限,对网店代运营这个东西还是一头雾水,下面就先说说我理解的为什么会出现代运营这样一个行业吧。

+

一般经过某种生产过程我们可以得到一个或多个产品,然后再把把这些产品拿到集市上去销售,比如农民伯伯种菜拿到菜市场去买。工业的发展、社会的进步等因素使得生产力有显著的提高,但是要生产一个产品依旧离不开生产过程,只是这个生产过程效率变得更高了而已。商品不一定是实体的,比如电视剧里面用钱换取情报,这里的情报也是一种商品,网店代运营公司提供的网店代运营服务也是一种商品,这一点可以类比各种保险。

+

以前都是租一个门面把商品摆在店铺里,等着附近赶集市的人前来购买。这种模式的弊端在于,一个人一天能走的距离是有限的,会走到你店铺的人更是少之又少,如此一来店铺的客流量依赖于一个村(镇、市),而一个村(镇、市)再大也不过那么点人,销售量的前提是客流量。淘宝、京东、拼多多、苏宁易购等大型电商平台的兴起,就是致力于解决这一类「做生意难」的问题,通过这些电商平台把互联网大流量的优势发挥出来了,互联网提供了巨大的流量池,怎么利用这个流量池就是靠自己的能力了。

+

问题在于很多人不会操作这一类电商平台的后台系统,初学者可能连一个后台怎么操作发货都不会,更别说如何利用增长思维来玩流量。少部分年轻人能自主学会如何操作电商平台的后台系统,大部分中年人士对复杂的后台就望而却步了。而就算你能操作复杂的后台系统,但是如何优化商品信息(标题、关键词、展示图等)来提高销量可能就不一定会了。

+

其它诸如站外推广、美工做图、活动上报、刷销量、刷收藏等等一系列操作更不是随随便便找个人就能做的。网店代运营公司提供的这种代运营服务则是为了解决电商运营难的问题,网店代运营公司专注于提供网店代运营服务,而客户则专注于出单发货,这也符合随着社会发展工作会逐渐细分的规律。

+

说实话,从几天观察来看,我走过的工资在网店运营领域的实力是非常不值得信赖的,虽然这个行业也不乏佼佼者,但是我看到的大部分都是没有真正创造价值,只是不断的去坑客户。我个人倾向于把专业的事情交给专业的团队去做,第一是能大大节省自己的时间、人力成本,另外也不会被运营这些事情扰乱到自己的好心情,但是要真正寻找到一个靠谱的代运营公司可真是难上加难。

+

我拜访的几个公司都有一个通病,那就是前期过于注重销售导致后续服务没有跟上来,或者说的更直白一点就是他们根本就没想过如何去提升自己的服务质量,客户进来洗一遍之后就不管了,这个过程叫做洗小白。也有一个公司老板已经意识到这一块的问题了,但是公司自身实力和公司人员素质都跟不上。

+

每个行业的兴起都不是没有原因的,代运营服务的出现的原因是为了解决了「网店运营难」的问题。虽然市场上绝大部分代运营公司都是在割韭菜,但是我相信网店代运营行业会越来越正规化,当然也可以代运营被这绝大多数人给作死。专业的人做专业的事,是社会发展的必然结果。

+

行业目前是很乱的,同行之间相互伪造聊天记录截图、伪造转账记录、伪造合同等手段去各平台发布诋毁对方的言论,甚至伪装成新客户去盗取别家公司内部的一些信息。让人觉得搞笑的是,还有直接冒充大一点的公司去骗客户的,我看到公司管理层因为这些事情搞的极为头大,表现出了秀才遇上兵的无奈。

+

换个角度看同行恶意竞争这件事,如果一个公司在搜索引擎都搜索不到的话,那只能说明这个公司随时都可能死去,前期承诺了再好的服务也不一定有用,公司死了怎么能提供服务呢?如果一个公司在上面全是负面,或者全是正面的信息都太假了。只不过这个行业的负面新闻太多了!

+

一个良性的市场竞争环境是行业发展的必要,但是像上述冒充同行公司、伪造事实依据诋毁同行公司的人,把整个市场都搞的乌烟瘴气了。这是非常短视的行为,短期可能会让自己有一定的收入,但是长期来看这是非常不利于行业发展的,这会逐渐让潜在客户甚至签约客户都逐渐对行业失去信心。

+

后面专门写一篇文章来揭露我看到的网店代运营行业乱象吧!!!!

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/q24QUn6OD/index.html b/q24QUn6OD/index.html new file mode 100644 index 00000000..3917fa0a --- /dev/null +++ b/q24QUn6OD/index.html @@ -0,0 +1,459 @@ + + + + + + + + 信用卡基础知识入门 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 信用卡基础知识入门 +

+ + +
+ +
+

相信绝大部分伙伴都使用过「花呗」这个产品吧,当月花下月还很契合现在的提前消费理念。花呗有账单日和还款日之分,需要在每个月的还款日之前将上期的账单还清,否则就会产生逾期记录进而影响自己的征信。当然如果确实没有足够的钱也可以选择账单分期,只不过需要支付一定的分期手续费。花呗的这些机制与信用卡一致,但是相比信用卡花呗就显得很抠门了,抽空对信用卡做了一点研究,就分享到这里一起交流。

+

虽然我从毕业开始就一直在用信用卡,但是我也一直没有搞明白信用卡的逻辑。比花呗更长的免息期,送开卡礼、首刷礼,用信用卡消费还给你积分兑换礼品、抵现或话费,偶尔还会给出一些支付立减的优惠(比如我前段时间用支付宝就老是遇到美国运通卡的立减金),银行难道是脑袋发热才这么送钱吗?

+
1621411367738
+

用脚趾头想都知道银行的目的肯定是为了赚钱,但是为啥又白白的把各种权益送给你呢,所以我们有必要了解一下银行为什么要发行信用卡?不管是在线上还是线下消费,只要使用了信用卡进行结账,那么商家就需要给出一定的手续费。比如商家支付了 100 元的手续费,银行会拿到 60 元的利润,银联拿到 5 块钱,剩下的交给支付通道公司,同时还会根据「商户编码」给到你一定的积分。所以当你使用信用卡进行消费时,银行就会赚到钱。

+

上一段提到了「商户编码」的概念,这个就像我们参加高考时老师给贴的条形码一样,是用来识别商户的。在教育、慈善一类的商户消费,银行是没有钱赚的,所以银行也不会给到你积分,我们可以把这类称之为「无积分商户」。银行就是根据商户编码来识别你刷的商户类型,具体可以查看刷卡之后小票商户编码 8-12 位。

+

国内支持的都是银联卡,不过美国运通的业务已经在国内出现,比如我目前正在使用的招行百夫长信用卡,就是一张美国运通卡,它在国内已经支持了线上消费。国外支持银联的不多,所以很多信用卡都会在银联卡之外给配一张外币卡,有人说外币卡会占用自己的授信额度,如果不出国就不要申请你那张附属卡。

+

信用卡是分不同等级的,比如普卡、金卡、白金卡、黑金卡。一般金卡及以下都是直接免年费或是可以通过一定的消费免年费的。白金及以上大部分都需要几千的年费,但是提供的相关权益也非常不错,比如航司里程、体检服务、机场贵宾厅、五星级酒店等等,不过白金及以上的下卡难度也大,具体可以看自己的实际情况去申请。

+

现在银行都会和各种公司联合发一些联名卡,比如我手里的平安爱奇艺联名卡,每个月只需要有三笔消费超过 188 元,下个月就可以领一张爱奇艺黄金会员月卡。像我这种视频平台会员权益,日常消费所累积的积分,加上银行平时的一些像「5 倍积分」活动,以及利用账单日、还款日这些免息期,就是妥妥的羊毛党味道。

+
1662306018034
+

我去年一年的话费都是使用平安的积分充值,相当于白嫖了一年话费。从深圳搬到成都冬天太冷没有被子,又用招行的积分换了被子和一些收纳箱。银行给你这些通道,就是默许你可以撸羊毛,但你千万别贪心把毛拔秃了甚至要宰羊,不然就会很容易把自己给撸进去。

+

不要小看撸羊毛这个行业,有的人能撸羊毛年入千万。我认识的人里面也有靠撸羊毛完全能养活自己的,再简单一点也有玩免费机票、酒店的。我觉得这个行业有意思的地方就是你日常生活的每一项都可以撸,电影票、外卖、水电话费、餐饮等等,但是玩信用卡玩着玩着也有一个问题,我现在哪怕在超市买瓶水也会不自觉的计算用哪张卡更划算。

+

最后放一张闲鱼的截图做个引子吧,有兴趣的伙伴可以自己去研究,我先暂且写这么多。

+
1621411397012
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/qxVW9efzD/index.html b/qxVW9efzD/index.html new file mode 100644 index 00000000..bd48c8cc --- /dev/null +++ b/qxVW9efzD/index.html @@ -0,0 +1,620 @@ + + + + + + + + Java 垃圾回收机制详解 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ Java 垃圾回收机制详解 +

+ + +
+ +
+

最近主要时间都放在知识图谱构建中,但是还是需要给自己充电。想在近段时间好好把 JVM 的垃圾回收好好看一下,学习然后输出,是我新找到的有效学习方法,希望你看了之后能有收获。

+

什么是垃圾

+

垃圾回收(常称做GC——Garbage Collection)诞生于1960年 MIT 的 Lisp 语言,垃圾回收机制也已经用在了很多编程语言中,比如 Java、Python、C# 等。我们这里主要说 Java 中的垃圾回收。

+

在JVM中,程序计数器、虚拟机栈、本地方法栈都是随线程生而生,随线程灭而灭;栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理;常说的垃圾回收主要集中在堆和方法区,这部分内存是随着程序运行动态分配的。

+

既然要回收垃圾,那么我们首先需要知道的就是,什么样的对象是垃圾。一般有两种方式:

+

引用计数

+

每个对象有一个引用计数属性,新增一个引用时计数加 1,引用释放时计数减 1,当引用计数变为 0 的时候,这个对象就可以回收了。但是这个方法无法解决对象循环引用的问题。

+
    // 对象循环引用示例
+    
+    Object objectA = new Object();
+    Object objectB = new Object();
+    
+    objectA.instance = objectB;
+    objectB.instance = objectA;
+    
+    objectA = null;
+    objectB = null;
+
+

假设我们有上面的代码。程序启动后,objectA和objectB两个对象被创建并在堆中分配内存,它们都相互持有对方的引用,但是除了它们相互持有的引用之外,再无别的引用。而实际上,引用已经被置空,这两个对象不可能再被访问了,但是因为它们相互引用着对方,导致它们的引用计数都不为 0,因此引用计数算法无法通知GC回收它们,造成了内存的浪费。如下图:对象之间的引用形成一个有环图。

+
+

可达性分析

+

或者叫根搜索算法,在主流的JVM中,都是使用的这种方法来判断对象是否存活的。这个算法的思路很简单,它把内存中的每一个对象都看作一个结点,然后定义了一些可以作为根结点的对象,我们称之为「GC Roots」。果一个对象中有另一个对象的引用,那么就认这个对象有一条指向另一个对象的边。

+
+

像上面这张图,JVM会起一个线程从所有的GC Roots开始往下遍历,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收。(这里多说一句,我们的JVM一起动,就至少有两个线程启动,一个是垃圾回收线程,一个是我们自己的主线程。

+

那么现在问题就变成了——什么样的对象可以当作 GC Roots?共有四种对象可以作为 GC Roots。

+

虚拟机栈中的引用的对象

+

们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种对象可以作为 GC Roots。

+

全局的静态的对象

+

也就是使用了static关键字定义了的对象,这种对象的引用保存在共有的方法区中,因为虚拟机栈是线程私有的,如果保存在栈里,就不叫全局了,很显然,这种对象是要作为 GC Roots 的。

+

常量引用

+

就是使用了static final关键字,由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也作为GC Roots。

+

本地方法栈中JNI引用的对象

+

有时候单纯的java代码不能满足我们的需求,就可能需要调用 C 或 C++ 代码(Java 本身就是用 C 和 C++ 写的嘛),因此会使用native方法,JVM 内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为 GC Roots。

+

垃圾回收算法

+

有意思的是在 JVM 规范中,并没有明确指明 GC 的运作方式,各个厂商可以采用不同的方式去实现垃圾收集器。这篇文章简单介绍常见的垃圾回收算法。

+

标记-清除算法

+

标记-清除算法分两个步骤,分别为「标记」和「清除」,字如其人。它是一个最基础的垃圾回收算法,更高级的垃圾回收算法都是基于它改进的。

+

它的运行过程是这样的:首先标记出所有需要回收的对象,标记完成后,再统一回收掉所有被标记的对象。

+
+

标记-清除算法的缺点有两个,一个是空间问题,标记清除之后会产生大量的不连续内存碎片。内存碎片太多,程序在之后的运行过程中就有可能找不到足够的连续内存来分配较大的对象,进而不得不提前触发另一次垃圾回收,导致程序效率降低。标记-清除算法的另一个缺点是效率问题,标记和清除的效率都不高,两次扫描耗时严重。

+

复制算法

+

复制算法把内存按容量划分为大小相等的两块,每次只使用其中的一块。如果正在用的这块没有足够的可使用空间了,那么就将还活着的对象复制到另一块去,再把使用过的内存一次性清掉。

+
+

这样就实现了简单高效的做法,每一次进行内存回收时,就不用再去考虑内存碎片这些复杂的情况,只需要移动堆顶指针就可以。但是缺点也很明显,可使用内存只有原来的一半了,而且持续复制生命力很旺盛的对象也会让效率降低哇。复制算法适用于存活对象少、垃圾对象多的情况,这种情况在新生代比较常见。

+

标记-压缩算法

+

在老年代,大部分对象都是存活的对象,复制算法在这里就不靠谱了,所以有人提出了标记压缩算法,标记过程和标记清除算法一样,但是清理时不是简单的清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存,需要移动对象的成本。

+
+

分代算法

+

前面的几种垃圾回收算法中,没有一种可以完全替代其他算法,他们具备各自的特点与优势,因此更好的方法是根据垃圾对象的特性来选择合适的回收算法。

+

分代算法的思想就是将内存空间根据对象的特点不同进行划分,选择合适的垃圾回收算法来提高回收效率。分代的思想已经被现有的虚拟机广泛采用。

+

分区算法

+

分区算法就是将整个堆空间再划分为连续的不同小区间,每一个小区间独立使用,也独立回收。

+
+

一般在相同条件下,堆空间越大,那么一次GC的时间就越长,因此而产生的停顿时间也就越长。为了控制GC的停顿时间,根据目标停顿时间,每次合理回收若干个小区间,而不是整个堆空间,进而减少一个GC的停顿时间。

+

垃圾收集器

+

上面讲的是垃圾收集算法,讲的是理论,垃圾收集器就是这些理论的具体实现。下面介绍一些垃圾收集器

+

Serial收集器

+

串行收集器是高效率的、古老的收集器,它只用了一个线程去回收垃圾。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩算法。串行是指 GC 过程和用户过程是串行的,在垃圾收集过程中会 stop the world,JVM在后台自动发起垃圾回收,会在用户不可见的情况下,把用户的线程全部停掉,就是 GC 停顿,给用户带来不良体验。

+

红色代表 GC 线程,灰色代表用户线程,下同。

+
+

ParNew收集器

+

ParNew 收集器就是 Serial 收集器的多线程版本,除了多线程以外,其余行为都和 Serial 收集器一样。新生代并行收集,老年代串行收集;新生代使用复制算法、老年代使用标记-压缩算法。

+
+

Parallel Scavenge收集器

+

Parallel Scavenge 收集器类似于 ParNew 收集器,因为与吞吐量密切,也称为吞吐量收集器。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制 GC 的时间不大于多少毫秒或者比例。Parallel Scavenge 收集器以高吞吐量为目标,减少垃圾收集时间,让用户代码获得更长的运行时间;GC 停顿时间的缩短,是用牺牲吞吐量和新生代空间来换取的。

+

Parallel Old收集器

+

Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,使用多线程和「标记-压缩」算法,在 JDK1.6 版本才开始提供。

+

CMS收集器

+

CMS(Concorrect mask sweep)收集器是一种以获取最短停顿时间为目标的收集器;也称为并发低停顿收集器。常用在 WEB、B/S 架构的服务系统中,因为这类应用很注重响应速度,尽可能减少系统停顿时间,给用户带来较好的体验。从名字上就可以看出来,它是基于「标记-清除」算法实现的,整个过程分为 4 步:

+

初始标记

+

初始标记仅仅标记 GC Roots 能直接关联到的对象,所以速度很快,需要停止服务(Stop The World)

+

并发标记

+

并发标记是进行 GC Roots Tracing 的过程,为了标记上一步集合中的存活对象,因为用户程序这段时间也在运行,所以并不能保证可以标记出所有的存活对象。

+

重新标记

+

重新标记阶段是为了修正并发标记阶段因用户程序继续运作而导致标记变动的那一部分对象,采用多线程并行来提升效率,会停止服务,时间上远比并发标记短,较初始标记稍长。

+

并发清除

+

这个阶段即并发收集垃圾对象,可以与用户线程一起工作。

+

虽然 CMS 收集器线程可以和用户线程一起进行,但是它肯定会占用 CPU 资源,拖慢应用程序是肯定的,总的吞吐量会降低。

+
+

G1收集器

+

(看下垃圾回收算法中的分区算法)这是目前最新的前沿成果,它基于“标记-压缩”算法,可以进行空间整理,不会产生碎片。前面的垃圾收集器,收集范围都是整个新生代或老年代,但是 G1 收集器不是这样,使用 G1 收集器时,java堆的内存模型不同,它还保留有新生代和老年代的概念,它们都是一部分区域(可以不连续)的集合。除此之外,G1 收集器还能建立可预测的停顿时间模型,可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒。G1 跟踪各个区域(Region)获得其收集价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。G1 垃圾回收也分4步:

+

初始标记

+

仅标记 GC Roots 能直接关联到的对象。

+

并发标记

+

进行 GC Roots Tracing 的过程,并不能保证可以标记出所有的存活对象。这个阶段若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收。

+

最终标记

+

为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录,G1 中采用了比 CMS 更快的初始快照算法: snapshot-at-the-beginning (SATB)。

+

筛选回收

+

首先排序各个 Region 的回收价值和成本,然后根据用户期望的 GC 停顿时间来制定回收计划,最后按计划回收一些价值高的 Region 中垃圾对象,回收时采用"复制"算法,从一个或多个 Region 复制存活对象到堆上的另一个空的 Region,并且在此过程中压缩和释放内存。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/qzip9Ct6U/index.html b/qzip9Ct6U/index.html new file mode 100644 index 00000000..f1b76152 --- /dev/null +++ b/qzip9Ct6U/index.html @@ -0,0 +1,671 @@ + + + + + + + + Vue 入门避坑——Vue + TypeScript 项目起手式 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ Vue 入门避坑——Vue + TypeScript 项目起手式 +

+ + +
+ +
+

在此前我使用的前端框架是 Angular,使用过 TypeScript 后你就会讨厌 JS 了,我学习 Vue 时的最新版本是 2.5,相信大部分同学都不会认为 Vue 那样又细又长的代码很美观吧,简单看了一些网络博客后,我毅然决然引入了 TypeScript 进行开发,本文仅整理记录我自己遇到的一些坑。

+

使用 Cli

+

脚手架是一个比较方便的工具,这里需要注意的是@vue/clivue-cli是不一样的,推荐使用npm i -g @vue/cli安装。

+

安装完成后,可以直接使用vue create your-app创建项目,你可以选择使用默认配置亦或是自己手动选择配置,按提示一步一步向下走即可,它会根据你的选择自己创建比如tsconfig.json等等配置文件。这里推荐使用less开发样式,sass老是在安装的过程中出问题。

+

当然你也可以使vue ui命令启动一个本地服务,它是一个 Vue 项目管理器,提供了一个可视化的页面供你管理自己的项目,它的样子如下图所示,还是比较清新的。

+
+

使用 vue-property-decorator

+

Vue 官方维护了 vue-class-component 装饰器,vue-property-decorator 则是在vue-class-component基础上增强了更多结合Vue特性的装饰器,它可以让 Vue 组件语法在结合了 TypeScript 语法后变得更加扁平化。

+

截止本文时间,vue-property-decorator共提供了 11 个装饰器和 1 个Mixins方法,下面用@Prop举个例子,是不是看起来引起极度舒适。

+
import { Vue, Component, Prop } from 'vue-property-decorator'
+
+@Component
+export default class YourComponent extends Vue {
+    @Prop(Number) readonly propA: number | undefined
+    @Prop({ default: 'default value' }) readonly propB!: string
+    @Prop([String, Boolean]) readonly propC: string | boolean | undefined
+}
+
+
+// 上面的内容将会被解析成如下格式
+
+export default {
+    props: {
+        propA: {
+            type: Number
+        },
+        propB: {
+            default: 'default value'
+        },
+        propC: {
+            type: [String, Boolean]
+        }
+    }
+}
+
+

使用 Vuex

+

关于怎么使用Vuex此处就不再做过多说明了,需要注意的一点是,如果你需要访问$store属性的话,那么你必须得继承Vue类,坑的地方是在某些情况下即使你没有继承Vue,它也能通过编译,只有在程序运行起来的时候才报错。

+
class ExampleApi extends Vue {
+
+    public async getExampleData() {
+        if (!this.$store.state.exampleData) {
+            const res = await http.get('url/exampleData');
+            if (res.result) {
+                this.$store.commit('setExampleData', res.data);
+                return res.data;
+            } else {
+            promptUtil.showMessage('get exampleData failed', 'warning');
+            }
+        } else {
+            return this.$store.state.exampleData;
+        }
+    }
+}
+
+

使用自己的配置(含代理)

+

vue.config.js是一个可选的配置文件,如果项目的根目录中存在这个文件,那么它会被@vue/cli-service自动加载,它的配置项说明可以查看配置参考

+

我们再开发过程中都会使用代理来转发请求,代理的配置也是在这个文件中,它的官方说明在devserver-proxy中,下面是一个简单的vue.config.js文件例子。

+
module.exports = {
+    filenameHashing: true,
+    outputDir: 'dist',
+    assetsDir: 'asserts',
+    indexPath: 'index.html',
+    productionSourceMap: false,
+    transpileDependencies: [
+        'vue-echarts',
+        'resize-detector'
+    ],
+    devServer: {
+        hotOnly: true,
+        https: false,
+        proxy: {
+            "/statistics": {
+                target: "http://10.7.213.186:3889",
+                secure: false,
+                pathRewrite: {
+                    "^/statistics": "",
+                },
+                changeOrigin: true
+            },
+            "/mail": {
+                target: "http://10.7.213.186:8888",
+                secure: false,
+                changeOrigin: true
+            }
+        }
+    }
+}
+
+

让 Vue 识别全局方法和变量

+

我们在项目中都会使用一些第三方 UI 组件,比如我自己就使用了 Element,但是在使用它的$message$notify等方法时就直接报错了,究其原因就是$message等属性并没有在 Vue 实例中声明。

+

官方对此给出了很明确的解决方案,使用的是 TypeScript 的 模块补充特性,可以查看增强类型以配合插件使用。既然知道是因为没有声明导致的错误,那我们就给它声明一下好了,在src/shims-vue.d.ts文件中添加如下代码即可,如果没有该文件请自行创建。

+
+

看到网上也有一部分人说的是src/vue-shim.d.ts,反正不管是怎么命名这个文件的,它们的作用是一样的。

+
+
declare module 'vue/types/vue' {
+    interface Vue {
+        $message: any,
+        $confirm: any,
+        $prompt: any,
+        $notify: any
+    }
+}
+
+

这里顺道提一下,src/shims-vue.d.ts文件中的如下代码是为了让你的 IDE 明白以.vue结尾的文件是什么玩意儿。

+
declare module '*.vue' {
+    import Vue from 'vue';
+    export default Vue;
+}
+
+
+

路由懒加载

+

Vue Router 官方有关于路由懒加载的说明,但不知道为什么官方给的这个说明在我的项目里面都没有生效,但使用require.ensure()按需加载组件可以生效。

+
// base-view 是模块名,写了相同的模块名则代码会被组织到同一个文件中
+const Home = (r: any) => require.ensure([], () => r(require('@/views/home.vue')), layzImportError, 'base-view');
+
+// 路由加载错误时的提示函数
+function layzImportError() {
+    alert('路由懒加载错误');
+}
+
+

上面的方式会在编译的时候把文件自动分成多个小文件,编译后的文件会以你自己命名的模块名来命名,如果代码之间有相互依赖,依赖部分代码编译后的文件会以两个模块名相连后进行命名。

+

但是需要注意的是,这样拆分小文件之后引入了另外一个新的问题,因为客户端会缓存这些编译后的 js 文件,如果功能 A 同时依赖了a.jsb.js两个文件,但用户在使用其它功能时已经把a.js缓存到本地了,使用功能 A 时需要请求b.js文件,这时程序就很容易报错,因为此时在客户端这两个文件不是同一个版本,所以可能导致a.js调用b.js中的方法已经被删了,进而导致客户端页面异常。

+

关于引入第三方包

+

项目在引入第三方包的时候经常会报出各种奇奇怪怪的错误,这里仅提供我目前找到的一些解决办法。

+
/*
+ 引入 jquery 等库可以尝试下面这种方式
+ 只需要把相应的 js 文件放到指定文件夹即可
+**/
+const $ = require('@/common/js/jquery.min.js');
+const md5 = require('@/common/js/md5.js');
+
+

引入一些第三方样式文件、UI 组件等,如果引入不成功可以尝试建一个 js 文件,将导入语句都写在 js 文件中,然后再在main.ts文件中导入这个 js 文件,这个方法能解决大部分的问题。例如我先建了一个lib.js,然后在main.ts中引入lib.js就没有报错。

+
// src/plugins/lib.js
+import Vue from 'vue';
+
+// 树形组件
+import 'vue-tree-halower/dist/halower-tree.min.css';
+import {VTree} from 'vue-tree-halower';
+// 饿了么组件
+import Element from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+// font-awesome 图标
+import '../../node_modules/font-awesome/css/font-awesome.css';
+import VueCookies from 'vue-cookies';
+import VueJWT from 'vuejs-jwt';
+
+Vue.use(VueJWT);
+Vue.use(VueCookies);
+Vue.use(VTree);
+Vue.use(Element);
+
+
+// src/main.ts
+import App from '@/app.vue';
+import Vue from 'vue';
+import router from './router';
+import store from './store';
+import './registerServiceWorker';
+import './plugins/lib';
+
+Vue.config.productionTip = false;
+
+new Vue({
+    router,
+    store,
+    render: (h) => h(App),
+}).$mount('#app');
+
+

因为第三方包写的各有特点,在引入不成功的时候基本也只能是见招拆招,当然如果你的功底比较深厚,你也可以自己写一个index.d.ts文件,实在不行的话,那个特殊的组件不使用 TypeScript 来写也能解决,我目前还没有找一个可以完全解决第三方包引入错误的方法,如果您已经有相关的方法了,希望能与你一起探讨交流。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/s1Mfu1JcV/index.html b/s1Mfu1JcV/index.html new file mode 100644 index 00000000..f056b020 --- /dev/null +++ b/s1Mfu1JcV/index.html @@ -0,0 +1,472 @@ + + + + + + + + 2018 年个人总结 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 2018 年个人总结 +

+ + +
+ +
+

这个总结写的还算认真,回头看,我真的运气很好,遇到了很多大佬,在我还未毕业时并没有嫌弃我傻,教我的不仅仅是技术,还有理财、为人处世,下面是我这一年的成长经历。

+

有一段时间因为华为 34 岁以上员工被裁、中兴程序员跳楼等事件的发生,各种蹭热点讨论“中年危机”的文章漫天飞,那时我正忙于找工作。

+

当时一个微信群里面大家各种讨论中年危机,都在给自己制造焦虑,刚好群里有个大神可能看大家过于焦虑,就在群里发了几条消息,教大家如何避免中年危机,并且推荐了两本书。

+

还是学生的我下意识的就发了一个添加好友请求,庆幸的是他同意了我的好友请求,当然我们没有什么交流,我的问题过于浅显,我明白自己这个水平问问题,会浪费人家时间,当时的想法是看看大神的朋友圈,他平时都接触什么,自己学习一段时间。

+

大神推荐的书是李笑来写的《把时间当做朋友》、《财富自由之路》,两本书在学校图书馆都没有,我就给学校图书馆荐购系统提交了这两本书,图书馆效率也挺高,不到一周就把书给买回来了,我立马就借回来阅读。

+

书中的内容刷新了我以前狭隘的认知,自己从来没有像书中那样考虑问题,除了对作者的佩服之外,更多的是思考自己这种学生思维局限性太大了,要慢慢的将它摒弃。

+

有个定律是你关心什么就会来什么,后面陆续碰到几位像大神一样的人士,并加了他们的微信,但是都仅仅是通过他们朋友圈的蛛丝马迹去找知识,通过他们朋友圈的分享内容,我知道了“简七理财”、“码农翻身”公众号,然后知道了《富爸爸穷爸爸》、《小狗钱钱》,于是我通过微信读书,读完了这两本书,逐渐培养了理财理念。

+

后来没隔多久,简七出书了,我第一时间就买了她写的《好好赚钱》(同期还有刘大也出了《码农翻身》一书,我也第一时间买了),简七写的内容通俗易懂,很容易理解。

+

刘大在群里开了几次公开课,作为计算机专业的我,被刘大对技术的理解之深给折服了,正是业界浮躁的时候,成千上万人想着人工智能、大数据、区块链,而刘大一直能沉下心来去了解技术的原理,这给了我一个很好的榜样,我也逐渐沉下心来,开始去补最基础的知识,像《深入理解计算机系统》一类书也能尽下心来慢慢去啃(当时没啃完,最近又在啃),这种不浮躁的特质对我的技术成长是很有帮助的。

+

此后有一天,另一个大神在朋友圈分享了曹大写的《从校园到职场系列文章》,喜欢深入挖掘信息的我,以曹大公众号为源头,又找到了冯大、池大、二爷等人的公众号。

+

作为自由的大四学生,因为不用担心第二天起不来,我那段时间经常熬夜阅读他们的文章,再阅读的过程中我也开始思考自己此前哪些想法狭隘,哪些品质又是值得继续保持的。

+

也是那时开始接受知识付费的,那时候已经有小密圈(现在叫知识星球)了,出于对几位大佬的信任,我第一次大胆的花了几百块钱加入了刘大、曹大、冯大、程序员小灰的小密圈,其中的内容比网上蹭热点的文章好不知多少倍,一贯爱捕捉蛛丝马迹的我,又通过评论信息发现了 angela zhu、子柳老师、陈利人老师等,然后去找他们的文章,他们输出的内容要比水军写的文章好太多。

+

自己也是从那时候开始坚持写文章记录自己的心得的,通过写文章,我认识了很多优秀的人,比如吴小龙同学、了不起的杰克、java 小咖秀等公众号的作者,和他们交流的很少,但是却很受用,他们的积极向上也影响着我一直保持着乐观豁达的心态。

+

自己写的文章也被几个资深程序员赞同,同时还收到了两个出版社发来的出书邀请,让我体会到了无心插柳柳成荫的收获。

+

让我坚持一直写文章的动力不是赚钱,而是我切切实实体会到了它给我个人带来的成长,为了自己日后再看时能立刻就找到清晰的逻辑,我把都尽可能把文章写得有理有据,掌握自己的节奏,尽量提高文章质量。此前写的谈一下写作的重要性一文有说写作可以带来的好处。

+

现在已经不把自己当新人了,而且有同龄人甚至比我年龄还大的人向我咨询问题时,我也能给出合理建议,都得到了他们的肯定。最近发现和周围伙伴最明显的一个区别就是,对于同一个新闻,我经常早于他们半天甚至一两天知道,而且掌握的信息比他们还准确,我认为这就是整体认知水平的提升。

+

想说的是,执行力与信息素养很重要,执行力强的人会与你拉开越来越大的距离,信息素养也是一个关键品质,现在网络上充斥着大量的虚假信息,如何去分别这些信息的真假,在相同条件下如何获得更多的有效信息,是必备的能力。

+

当前年龄 23,刚大学毕业几个月,没读研。按十年为期给自己定了几个小小的目标:

+

父母是地地道道的农民,智能手机都不会用,十年之类给自己和父母把重疾险、意外险之类的保险配置齐全,虽然父辈一直反对买保险。

+

提高获取信息的能力,虽然现在对信息的掌握都比周边伙伴要早半天至几天,但是都不是自己的分析结果,学习以时间的纬度跟踪事件的发展。

+

学习理财知识,现在只对信用卡、基金有一点点的了解,不管炒不炒股,金融知识都还是要学的,这方面通过看书、阅读、小额实操学习。

+

提升自己的技术实力,职业是程序员,前后端都做,但是自己对技术的热情不是多么高涨(至少比身边一半人要高涨),以我对自己的了解,我在技术的道路上成长为小公司一个的架构师应该不成问题,再高层级怕是不行。

+

慢慢做到不止一份收入来源,这方面不是多清晰,现在每个月平均会有 200 左右的非工资收入(帮助别人时发的红包等),十年后做到其它收入基本和工资持平。不至于因为钱的问题而忍受心中的不快,至少得有能指着老板的鼻子说“老子不干了”的底气。

+

世界那么大,应该去看看,国内除了西北地区,中国很多地方已经留下了我的足迹,旅游不仅仅是玩耍,更是提升见识、获得灵感的有效途径,十年至少得把自己的脚印印到 5 个国家的土地上吧。

+

十年之后应该已经结婚了,房子是现在最遥不可及的目标,但是心里莫名有一股自信,这个后面会实现的,虽然不知道哪里来的这股自信。

+

最后一个,趁年轻,多学习,做一个终身学习的人,时刻保持学习的态度,多做有利于他人的事,现在水平不高,我能帮助到的大部分都是硕士及以下。努力提高自己,帮助更多的人。更大的目标是能给山区学校带去一些更好的教育资源。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/s3CKW7x7C/index.html b/s3CKW7x7C/index.html new file mode 100644 index 00000000..305ca9e4 --- /dev/null +++ b/s3CKW7x7C/index.html @@ -0,0 +1,625 @@ + + + + + + + + 预处理器 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 预处理器 +

+ + +
+ +
+

C 语言的编译需要经过很多步骤,其中第一个步骤称为预处理阶段。这个阶段的主要任务包括删除注释、插入被#include指令包含的文件的内容、定义和替换#define指令定义的符号以及确定代码的部分内容是否应该跟绝一些条件编译指令进行编译。

+

#define

+

#define指令就是为数值命名一个符号。比如#define name stuff指令,有了它之后,每当有符号name出现在这条指令后面时,预处理器就会把它替换成stuff,比如下面几个例子:

+
// 为关键字 register 创建了一个简短的别名
+#define reg             register
+// 声明了一个更具描述性的符号用来替代实现无限循环的 for 语句
+#define do_forever      for(;;)
+// 定义了一个简短记法,在 switch 语句中使用,可以自动把一个 break 放在每个 case 之前
+#define CASE            break;case
+
+

当然如果定义中的stuff非常长,那么也可以将它分成几行,除了最后一行之外,每行的末尾都需要加一个反斜杠。比如:

+
#define log_debug   printf("File[%s]line[%d]:" \
+                    " x=[%d], y=[%d], z=[%d]", \
+                    __FILE__, __LINE__, \
+                    x, y, z)
+
+// 那么我们将可以很方便的插入一条调试语句打印
+x *= 2;
+y += x;
+z = x * y;
+log_debug;
+
+

很容易就发现上面的log_debug定义无法进行泛化,当然设计者也考虑到了这个问题,所以#define机制包括了一个规定,即允许把参数替换到文本中,这种实现一般称为,其声明方式如下:

+
define name(parameter-list) stuff
+
+

需要注意的是parameter-list是一个由逗号分隔的符号列表,他们可能出现在stuff中。参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。下面我们看一个具体的列子,以此了解宏定义的机制,并将它逐步优化改进:

+
#define SQUARE(x)   x * x
+
+// 使用
+SQUARE(5)
+// 效果:5 * 5
+
+

考虑一下下面的代码段:

+
a = 5;
+printf("%d\n", SQUARE(a + 1));
+
+

乍一看觉得这段代码将打印36这个值。但实际它却会打印11,我们仔细观察一下被替换的宏文本,即参数x被文本a + 1替换:

+
a = 5;
+printf("%d\n", a + 1 * a + 1);
+
+

很容易想到对参数 x 加一个括号解决上述问题,即:

+
#define SQUARE(x)   (x) * (x)
+
+// 上述打印将会被替换为
+a = 5;
+printf("%d\n", (a + 1) * (a + 1));
+
+

类似的我们可以再定义一个DOUBLE宏,即:

+
#define DOUBLE(x)   (x) + (x)
+
+

但是考虑下面的使用方式:

+
a = 5;
+printf("%d\n", 10 * DOUBLE(5));
+
+

看上去它应该打印的结果是100,但事实上它打印的是55,我们再通过宏替换产生的文本观察问题:

+
printf("%d\n", 10 * (5) + (5));
+
+

所以我们需要在整个表达式两边加上一对括号。所有用于对数值表达式进行求值的宏定义都应该使用下面这种方式加上括号,避免在使用宏时,由于参数中的操作符或邻近的操作符之间不可预料的相互作用。

+
#define DOUBLE(x)   ((x) + (x))
+
+

宏与函数

+

宏非常频繁的用于执行简单的计算,比如在两个表达式中寻找其中较大(小)的一个:

+
#define MAX(a, b)   ((a) > (b) ? (a) : (b))
+
+

那么为什么不使用函数来完成这个任务呢?首先用于调用和从函数返回的代码很可能比实际执行这个小型计算工作的代码更大,所以使用宏比使用函数在程序的规模和速度方面都更胜一筹。

+

更为重要的是函数必须声明为一种特定的类型,所以它只能在类型合适的表达式上使用。但是上面的这个宏可以用于整型、长整型、单浮点型、双浮点型以及任何其它可以使用>操作符比较值大小的类型,即宏与类型无关

+

当然宏也有它的不利之处,因为每次在使用宏时,一份宏定义代码的拷贝都将插入到程序中,除非宏的定义非常短,否则使用宏将会大幅增加程序的长度。

+

也有一些任务根本无法使用函数实现,比如下面这个宏的第二个参数是一种类型,它无法作为函数参数进行传递。

+
#define MALLOC(n, type) ((type *)malloc((n) * sizeof(type)))
+
+

当宏参数在宏定义中出现的次数超过一次时,如果这个参数具有副作用,那么当使用这个宏的时候就可能出现危险,导致一些不可预料的后果。比如x++就是一个具有副作用的表达式,它会改变x的值,直接会导致下面的代码段出现不可预知的后果:

+
#define MAX(a, b)   ((a) > (b) > (a) : (b))
+
+x = 5;
+y = 8;
+z = MAX(x++, y++);
+// z = ((x++) > (y++) > (x++) : (y++))
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
属性#define 宏函数
代码长度每次使用时,宏代码都被插入到程序中。除了非常小的宏志伟,程序的长度将大幅度增长函数代码只出现于一个地方,每次使用这个函数时,都调用那个地方的同一份代码
执行速度更快存在函数调用/返回的额外开销
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非它们加上括号,否则邻近操作符的优先级可能回产生不可预料的结果函数参数只在函数调用时求值一次,它的结果传递给参数。表达式的求值结果更容易预测
参数求值参数每次用于宏定义时,它们都将重新求值。由于多次求值,具有副作用的参数可能会产生不可预料的结果参数在函数被调用前求值一次。在函数中多次使用参数并不会导致多种求值过程。参数的副作用不会造成任何特殊的问题
参数类型宏与类型无关。只要对参数的操作是合法的,它可以使用于任何参数类型函数的参数是与类型有关的。如果参数的类型不同,就需要使用不同的函数,即使它们执行的任务时相同的
+

文件包含

+

我们知道#include指令可以使另一个文件的内容被编译,就像它实际出现于#include指令出现的位置一样。这种替换的执行方式很简单:预处理器删除这条指令,并用包含头文件的内容取而代之。这样一个头文件如果被包含到 10 个源文件中,它实际上被编译了 10 次。

+

基于这种替换的方式,当出现嵌套#include文件被多次包含时,就会出现问题:

+
#include "a.h"
+#include "b.h"
+
+// 如果 a.h 和 b.h 中都包含一个 #include x.h
+// 那么 x.h 在此处就出现了两次
+
+

这种多重包含在绝大多数情况下出现于大型程序中,它往往需要很多头文件,所以要发现这种情况并不容易。但是我们可以使用条件编译来解决这个问题:

+
#ifndef _HEADER_NAME_H_
+#define _HEADER_NAME_H_
+
+/*
+* All the stuff that you want in the header file
+*/
+
+#endif
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/sBMrrNbs4/index.html b/sBMrrNbs4/index.html new file mode 100644 index 00000000..85bcecf8 --- /dev/null +++ b/sBMrrNbs4/index.html @@ -0,0 +1,642 @@ + + + + + + + + 动态规划实例——01 背包详解 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 动态规划实例——01 背包详解 +

+ + +
+ +
+

题目描述

+

有 n 件物品,每件物品有一个重量和一个价值,分别记为 w1,w2,…,wn 和 c1,c2,…,cn。现在有一个背包,其容量为 wk,要从 n 件物品种任取若干件。要求:(1)重量之和小于或等于 wk;(2)价值之和最大。

+

输入

+

共 3 行,第一行 2 个整数,表示 n 和 wk;第二行 n 个整数表示每一个物品的重量,第三行 n 个整数表示每一个物品的价值。

+

输出

+

一行一个整数,表示符合背包容量的最大价值。

+

样例

+
8 200
+79 58 86 11 28 62 15 68
+83 14 54 79 72 52 48 62
+
+

暴力枚举

+

我们以只有 A、B、C 三件物品的情况为例,对于每一个物品都存在不拿两种情况。以0表示不拿当前物品,以1表示拿当前物品,可以有如下分析结果。

+
+

可能上面的图看起来不够清晰,我们从左至右逐一列举出来观察,一眼就可以看出来规律。其实就是十进制的 0、1、2、3、4、......可枚举的最大值即 2n-1

+
000
+001
+010
+011
+100
+101
+110
+111
+
+

根据上面的分析,我们可以写出如下代码。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int main()
+{
+	int n, wk;
+	int w[10000], c[10000];
+	cin>>n>>wk;
+	for(int i = 0; i < n; i++){
+		cin>>w[i];
+	}
+	for(int i = 0; i < n; i++){
+		cin>>c[i];
+	}
+
+	int ans = 0;
+    int max_val = 1 << n;
+    // 逐一枚举
+	for(int i = 0; i < max_val; i++){
+		int ww = 0, cc = 0;
+		int index = 0;
+		// 转二进制
+		int cur = i;
+		while(cur){
+			int bit = cur % 2;
+            // 若拿第 index 个物品,则累加其重量和价值
+			if(bit){
+				ww += w[index];
+				cc += c[index];
+			}
+			cur = cur >> 1;
+			index++;
+		}
+		//计算最大值
+		if(ww <= wk && ans < cc){
+			ans = cc;
+		}
+	}
+	//输出最大值
+	cout<<ans<<endl;
+}
+
+

递归求解

+

我们把背包容量为wk,有n个物品可以选择的问题表示为slove(wk, n)。那么在背包剩余容量可以装下第 n 个物品时,该问题可以表示为求如下两个问题的最大值

+
    +
  • 选第 n 个物品:c[n-1] + slove(wk-w[n-1], n-1)
  • +
  • 不选第 n 个物品:slove(wk, n-1)
  • +
+

在背包剩余容量无法装下第 n 个物品时,问题直接变为

+
    +
  • 不选第 n 个物品:slove(wk, n-1)
  • +
+

可以发现上述三个子问题可以继续向下拆分为规模更小,但类型一致的子子问题。于是可以写出如下递归求解代码。

+
#include<bits/stdc++.h>
+using namespace std;
+
+int w[30]={0}, c[30]={0};
+
+// wk 背包剩余重量
+// ch 可选项
+int slove(int wk, int ch)
+{
+	if(wk <= 0 || ch <= 0){
+		return 0;
+	}
+	
+    // 若背包剩余容量无法装下 w[ch-1],则直接丢弃第 ch 个物品
+	if(w[ch-1] > wk){
+		return slove(wk, ch-1);
+	}
+	
+    // 若背包剩余容量能装下 w[ch-1],则计算装和不装的最大值
+	int a = c[ch-1] + slove(wk-w[ch-1],ch-1);
+	int b = slove(wk, ch-1);
+	return a > b ? a : b;
+}
+
+int main()
+{
+	int n, wk;
+	cin>>n>>wk;
+	
+	for(int i = 0; i < n; i++){
+		cin>>w[i];
+	}
+	for(int i = 0; i < n; i++){
+		cin>>c[i];
+	}
+	cout<<slove(wk, n);
+}
+
+

动态规划

+

递归在执行过程中会存在重复计算相同子问题的情况,我们可以将其改为用循环实现,即动态规划的写法。dp[i][j]的含义即为:在背包容量为i,可选物品数量为j的情况下,符合背包容量的最大值。具体代码如下所示:

+
#include<bits/stdc++.h>
+using namespace std;
+
+int w[30]={0}, c[30]={0};
+
+int main()
+{
+	int n, wk;
+	cin>>n>>wk;
+	
+	for(int i = 0; i < n; i++){
+		cin>>w[i];
+	}
+	for(int i = 0; i < n; i++){
+		cin>>c[i];
+	}
+
+    int dp[1000001][21] = { 0 };
+
+    for(int i = 1; i <= wk; i++) {
+        for(int j = 1; j <= n; j++) {
+            // 若背包剩余容量无法装下 w[j-1],则直接丢弃第 j 个物品
+            if(w[j-1] > i) {
+                dp[i][j] = dp[i][j-1];
+            } else {
+                // 若背包剩余容量能装下 w[j-1],则计算装和不装的最大值
+                int a = c[j-1] + dp[i-w[j-1]][j-1];
+                int b = dp[i][j-1];
+                dp[i][j] = a > b ? a : b;
+            }
+        }
+    }
+
+	cout<<dp[wk][n];
+}
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/sEJgU0CCS/index.html b/sEJgU0CCS/index.html new file mode 100644 index 00000000..eee23b1f --- /dev/null +++ b/sEJgU0CCS/index.html @@ -0,0 +1,466 @@ + + + + + + + + 我自己的职场故事与经验 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 我自己的职场故事与经验 +

+ + +
+ +
+

本文首发于牛客网社畜职场交流圈

+

从毕业后就基本没有看过牛客网了,前段时间再打开牛客网看发现有了很多新的板块,讨论区也变得比以前要活跃很多,话题也从仅仅的找工作面试类延伸到了职场、租房等等,牛客也开放了几个创作者计划的活动,我也用自己的职场故事和经验来参加一下。

+

我的经验是第一份工作很重要但是没有大家想象的那么重要。我一直都是一个马大哈,签三方协议的时候面试官告诉他他给我安排的是做 5G 的部门,看到一大堆硕士生拿着简历都在签三方协议,在场的本科生没有几个,我啥都没有细问就直接签了。

+

到公司报道的时候才知道部门是做 5G 测试的,心里一下有些失落觉得可能后面的开发之路就毁了。但实际上我想错了,等入职培训完了之后真正进入工作时候才知道,我所在的团队守护着 2G、3G、4G 和 5G 的所有测试架构,团队平时为了提高部门测试效率所开发的工具已经早早的延伸到无线院,而真正的 5G 开发部使用的语言是汇编语言和 C 语言,每个部门只负责 5G 产品的一小部分,具体到某一个人就负责的更细粒度了。

+

所以一个有趣的现象就出现了,因为我们团队的人需要维护 5G 的测试架构,竟然对产品的了解要比开发部的人更加全面一点(当然深度肯定是远远不及开发部的人),而且开发部的人也得使用我们团队开发的工具,不管开发部的人怎么怼测试人员,我们都是有办法怼回去的。

+

从我个人和一些职场人士的交谈来看,第一份工作并不会决定你将来要做的是什么工作,更多的是学习为人处事的态度,适应社会与职场上不管你喜欢还是不喜欢的规则,但是请注意你的技术栈是由你的工作决定的,你自己是决定不了你的技术栈的。

+

第二个经验就是物以稀为贵唯独知识不是。无线院准备做几个工具解决几个部门都存在的问题,要求每个部门出一个人参与一个工具的开发,我们部门被分配做一个 5G 数据度量系统。测试部门的技术栈无非就是 python 什么的,整个部门 200 多人会写前端的不超过 10 个,而这个度量系统后端使用的是 java,前端使用的是 vue,部门再一眼数过去部门没有人用过 Vue,而有 Java 经验的只有我和我师傅,师傅是团队的小组长无法抽身干这些对部门没多少收益的事情(度量一看就知道是给领导们用的)。

+

按照正常的安排我这个时候是应该被派到枯燥的基站上玩 3 个月的,而且基站相关的师傅都早早的给我分配好了,因为这个度量系统我稀里糊涂的被派到南京出差了,当然也就免于去干无聊的基站测试工作,顺便还给了大把的时间让我学习以前不知道的 Vue。

+

说实话这个度量系统本身没有什么技术性,但是它却让我接触到了 5G 的产品经理、需求教练等等,每次和他们开会让我逐渐有了一点从领导视角去看问题的意识,他们能把问题看的更加全面、透彻,而且大多数时候都是在做取舍。

+

记得高中化学老师开了个玩笑,地震过了你就不学逃生知识了?你不知道我们这个山沟沟很容易发生泥石流吗?那一段时间学习的 Vue 技术并没有浪费,我后面从 0 到 1 开发一个系统的时候,刚好就用 Vue 将团队厚重的 Angular 给替代了,也没想到我现在从事少儿编程教育工作居然还用到了那时候的技术(开发平台的前端)。

+

希望你别领会错我的意思,不是让你像打散弹枪一样去胡乱学习一大堆知识,而是抓住每一次学习的机会尽量把它理解透彻。

+

第三个经验是保持谦卑之心,尽量独立解决问题。进入职场之后每个人都有自己的工作,不再像学校那样你问老师一个问题,老师会把答案告诉你还生怕你学不会,所以遇到问题先去网上搜一搜资料,搜索引擎前 8 页的链接都点开看一下,百度不行就上谷歌,谷歌不行可以去 Github 搜一搜,要是还不行的话就去 StackOverflow 上面提个问题。

+

如果你按照我上面说的路径都找不到答案的话,那这时再去请教一下部门的老员工,有了前面的探索你提出来的问题会更加有水平,高水平的提问也会帮助你逐渐赢得老同事们的认可,想一下天天提的问题像个小学生一样,可能也就是自己认为是好学多问。

+

不管你认可还是不认可,同时给 A 和 B 抛出同样的一个问题,A 能把问题解决而 B 不能解决问题,那 A 就是要比 B 牛逼。我越来越相信实力是赢得别人认可的基石,能独立解决问题在一定程度上也说明你是个靠谱的人。能解决小问题人家才会把更大的问题交给你,不要嫌弃那些小事情,把小事情做到精致别人就会给你更大的成长机会。

+

第四个经验是用心去做事,快快乐乐服务同事。不管你的技术多么牛逼,你敢拍着胸脯说自己写的程序没有 bug 吗?如果同事给你说你的软件哪里有问题,虚心的接受并快快乐乐的帮人家解决问题,他们是你的天使用户,如果你连自己的天使用户都留不住那还怎么留住外面的用户呢?

+

由于 5G 测试用例实在太多了,所以我和另一个同事一前一后负责开发测试任务管理系统,那是我第一次做复杂交互的前端系统,刚开始的几个版本我写的烂的要命,有的按钮甚至能让用户卡四五十秒,每天都会接到五六个同事的电话说系统太卡了,但是我自己那段时间也没办法啊,技术水平不够完全不知道怎么去优化。

+

所以同事不管什么问题我照单全收,还专门列了一个问题表,每次同事在旁边说的时候我打开往里面添加记录,自己的产品不行就先把服务做好嘛。很多费时费力的操作我索性加班帮同事搞定,所以那段时间系统虽然难用的要死,但是没有一个同事直接用邮件或是当面怼我的,虽然我知道他们是脸上笑嘻嘻心里妈卖批,但我眼睛只能看到脸上的笑容。

+

除了学习技术优化系统性能外,我还自己看了一些关于设计的文章,不懂设计那我就想着怎么让用户用着舒服呗。逐渐系统都能做到实时响应并且美观大方当然操作还尽量简单,被其它部门的同事看到偶尔会有几个主动询问的,加上师傅的推动很容易就把系统推开了。

+

在师傅的敦促下我将其做了平台化开发,离职前已经将西安部门接入系统。那段时间与在美团和去哪儿工作的学长们交流,他们在定级答辩时评委更关心的是你如何把系统推广出去的,里面用到了什么你觉得牛逼的技术在他们眼里并不牛逼。

+

回到最开始的第一份工作内容不太重要,我现在是一个少儿编程老师同时做着少儿编程平台的研发工作,本来想好好的做一个少儿编程老师就行了,谁能想到之前的程序员经历让我在新的环境竟然更加有竞争力。借用别人的话来说就是,应该多一点洒脱,人生会给你更多的可能。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/sPxxvjVav/index.html b/sPxxvjVav/index.html new file mode 100644 index 00000000..c0afa779 --- /dev/null +++ b/sPxxvjVav/index.html @@ -0,0 +1,554 @@ + + + + + + + + 非设计师需要知道的四个设计原则 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 非设计师需要知道的四个设计原则 +

+ + +
+ +
+
+

作者:Anna 4erepawko Mészáros,UI/UX 设计师。
+关注作者: MediumTwitter

+
+

这篇文章是写给无力邀请专业设计师的所有内容创作者的,以及设计师异常忙碌的团队的非设计师们。如果您按照这些简单的步骤进行操作,我保证您的设计会变得更好。

+

这些 Tips 来源于我对身边非设计朋友的多年观察,家人与同事在日常生活中也需要设计他们的东西。比如简历、作品集,Facebook 和 Instagram 上帖子要使用的图片,YouTube 视频的缩略图等。

+

所有这些人都向我寻求帮助与建议,希望能让他们的东西看起来更好。我坚信「授人以鱼不如授人以渔」,所以我试图提供更有价值的建议,以便他们在未来也能解决类似的问题。

+

随着时间的推移,我意识到我一直在给所有人提供相同的建议,虽然每次所使用的措辞不同,但我所有的建议都可以提炼为以下四个原则。

+

这些 Tips 会帮您创造出美丽动人的设计吗?答案是不会!它们只会帮您创造出色、清晰且易于理解的设计。是每个人都可以轻松理解和互动吗?那当然,所以不多说废话,下面我就向您逐一展示。

+

对比

+

确保所有元素之间有足够的对比度。为什么?因为那些略有差异但是又不够不同东西,创造了一种恐怖谷。人类的眼睛会排斥它们,对它们感到厌恶、难以理解。我们不希望它们出现在我们的设计中,难道不是吗?

+
+

恐怖谷理论,是一个关于人类对机器人和非人类物体的感觉的假设。如果一个非人类实体不够拟人,那么它身上的人类特征会很容易辨认;而当它足够拟人时,他身上的非人类特征则会变得很容易辨认。因此会在人类观察者眼中产生一种古怪的感觉,想想您看到病患者或者尸体时的感觉。

+
+

因此您设计的元素要么完全相同,要么具有显著差异。

+

您可以从下面四个方面来突出对比:

+

1、颜色:浅色上使用暗色,反之亦然

+

示例: 切勿在浅蓝色上使用浅灰色或浅粉红色等灰色组合,它们会造成阅读/互动上的极大困难。

+
+

2、大小:相邻元素要么大小完全相同,要么大小区别很大

+

示例: 不要将 32pt 和 36pt 的文本放在一起;18pt 和 36pt 放在一起会显得更加协调。

+
+

3、粗细:与大小一样,相邻元素的粗细要么完全相同,要么有明显的区别

+

示例: 不要将相同字体的粗体与黑体放在一起,因为它们看起来太相似了;将黑体与细体放在一起会显得很协调。

+
+

4、风格:不要将一个斜体类型放在另一个斜体类型旁边,或者在一个衬线字体旁边放置另一个衬线字体。应该组合不同的东西。

+

示例: 不要将 Times New Roman 与 Georgia 放在一起,它们看起来太相似了,应该组合完全不同的风格。

+
+

一致性

+

确保相似的元素以相似的方式出现。为什么呢?首先,通过确保确保事物一致性,您可以让用户将注意力集中在设计的重要方面,而不是被随时变化的元素分散注意力。

+

其次,一致性也增加了用户对您的信任,使事物看起来实际上是设计的,而不是简单快速拼凑出来的。

+

一旦你选择了具体的风格,就要毫不犹豫的坚持下去,这里所说的风格包括字体、颜色、阴影、栅格、对齐、装饰风格等等。

+
+

当您处理许多相邻的不同部分时(比如 YouTube 的视频缩略图或是中型文章的封面),您应该为所有部分选择一种整体风格,并坚持使用。

+

奥卡姆剃刀 减少视觉噪音

+

在您的设计中,使用的元素越少越好。为什么呢?因为人类的大脑很难在输入过载的情况下处理信息并作出决策。您应该使用尽可能少的装饰元素(字体、颜色、阴影、图标等等)。

+

将奥卡姆剃刀应用于所有内容。如果只需要两个元素就能满足需求,那么就不要使用 3 个元素;如果 10 个元素实现所需的功能,那么就不要用 20 个元素。

+
+

如果您不喜欢古老的英国哲学家风格,更喜欢您在 Netflix(一家美国流媒体提供商)上看到的东西。请将怦然心动的人生整理魔法应用到您的设计中。

+
+

《怦然心动的人生整理魔法》是美国流媒体提供商Netflix于2019年1月1日首播的一档真人实境秀节目。节目由日本“整理咨询顾问” 近藤麻理惠主创。她在每集节目中拜访一个家庭,帮助他们整理自己的房间。
+近藤麻理惠认为整理房间时应当将物品分为五类:衣物、书籍、纸张文件、杂物和情感纪念品;在整理时拿起每件物品,如果能使自己“怦然心动”则留下,如果不能则要感谢物品的贡献然后与其告别。

+
+

间距

+

元素的位置会发送关于其含义的元级别消息。为什么这很重要?因为了解如何放置元素以及在它们周围预留了多少空间有助于降低设计的复杂性,因此会使人更加愉悦,并且更容易交互。

+

在您的设计中使用间距来传达下面 3 个方面的信息:

+

1、接近度 = 相关性

+

与其它元素相比,彼此更接近的事物被认为它们有更强的相关性。这是最重要的,因为我觉得它常常容易被忽视。

+

它可以以很多不同的方式应用,比如行与行之间应该有一定的间距,而不是一行中每个单词之间的间距那么小;同样不同段落之间的空间也比段落内的行空间要大。

+
+

元素之间的间距应该小于元素与组合边缘之间的间距。

+
+

标签和支撑信息应该位于其相关元素附近。

+
+

2、留白

+

结合奥卡姆剃刀,给您的设计尽可能留白,去整理它们,使它们的意义更加明显。

+

如果把太多元素放在有限的空间里,就像同时听三首不同的哥,很难理解别人在说什么。

+
+

3、重要性与顺序

+

这是一个很普通的常识,但是我还是要在这里提到它。

+

最重要的事情放在第一位,使它们占据最大的空间,用一系列的事物来传达秩序。

+

结束语

+

恭喜您!如果您按照这些 Tips 进行设计,那么按照行业标准,它可能看起来非常好。

+

For everything else, there is always a designer.

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/sQhfLtj4g/index.html b/sQhfLtj4g/index.html new file mode 100644 index 00000000..8cf11748 --- /dev/null +++ b/sQhfLtj4g/index.html @@ -0,0 +1,655 @@ + + + + + + + + 计算机网络 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 计算机网络 +
+ + +
+

+ + 如何加快 Nginx 的文件传输?——Linux 中的零拷贝技术 + +

+ +
+ + + + +
+ +
+ +参考内容: +Two new system calls: splice() and sync_file_range() +Linux 中的零拷贝技术1 +Linux 中的零拷贝技术2 +Zero Copy I: User-Mode Perspective +Linux man-pages splice() +Nginx AIO 机制与 sendfile 机制 +sendfile 适用场景 +扯淡 Nginx 的 sendfile 零拷贝的概念 +浅析 Linux 中的零拷贝技术 +Linux man-pages sendfile + +今天在看 Nginx 配置的时候,看到了一个sendfile配置项,它可以配置在http、server、location三个块中,出于好奇就去查了一下sendfile的作用。 +文件下载是服务器的基本功能,其基本流程就是循环的从磁盘读取文件内容到缓冲区,再将缓冲区内容发送到socket文件,程序员基本都会写出类似下面看起来比较高效的程序。 +while((n = read(diskfd, buf, BUF_SIZE)) &gt; 0) + write(sockfd, buf , n); + +上面程序中我们使用了read和write两个系统调用,看起来也已经没有什么优化空间了。这里的read和write屏蔽了系统内部的操作,我们并不知道操作系统做了什么,现实情况却是由于 Linux 的 I/O 操作默认是缓冲 I/O,上面的程序发生了多次不必要的数据拷贝与上下文切换。 +上述两行代码执行流程大致可以描述如下: + +系统调用read产生一个上下文切换,从用户态切换到内核态; +DMA 执行拷贝(现在都是 DMA 了吧!),把文件数据拷贝到内核缓冲区; +文件数据从内核缓冲区拷贝到用户缓冲区; +read调用返回,从内核态切换为用户态; +系统调用write产生一个上下文切换,从用户态切换到内核态; +把步骤 3 读到的数据从用户缓冲区拷贝到 Socket 缓冲区; +系统调用write返回,从内核态切换到用户态; +DMA 从 Socket 缓冲区把数据拷贝到协议栈。 + + +可以看到两行程序共发生了 4 次拷贝和 4 次上下文切换,其中 DMA 进行的数据拷贝不需要 CPU 访问数据,所以整个过程需要 CPU 访问两次数据。很明显中间有些拷贝和上下文切换是不需要的,sendfile就是来解决这个问题的,它是从 2.1 版本内核开始引入的,这里放个 2.6 版本的源码。 +系统调用sendfile是将in_fd的内容发送到out_fd,描述符out_fd在 Linux 2.6.33 之前,必须指向套接字文件,自 2.6.33 开始,out_fd可以是任何文件;in_fd只能是支持mmap的文件(mmap是一种内存映射方法,在被调用进程的虚拟地址空间中创建一个新的指定文件的映射)。 +所以当 Nginx 是一个静态服务器时,开启sendfile配置项是可以大大提高 Nginx 性能的,但是当把 Nginx 作为一个反向代理服务器时,sendfile则没有什么用,因为当 Nginx 时反向代理服务器时,in_fd就是一个套接字,这不符合sendfile的参数要求。 + +可以看到现在我们只需要一次拷贝就可以完成功能了,但是能否把这一次拷贝也省略掉呢?我们可以借助硬件来实现,仅仅需要把缓冲区描述符和文件长度传过去,这样 DMA 直接将缓冲区的数据打包发送到网络中就可以了。 +这样就实现了零拷贝技术,需要注意的是这里所说的零拷贝是相对操作系统而言的,即在内核空间不存在冗余数据。数据的实际走向是从硬盘到内存,再从内存到设备。 +Nginx 中还有一个aio配置,它的作用是启用内核级别的异步 I/O 功能,要使aio生效需要将directio开启(directio对大文件的读取速度有优化作用),aio很适合大文件的传送。需要注意的是sendfile和aio是互斥的,不可同时兼得二者,因此我们可以设置一个文件大小限制,超过该阀值使用aio,低于该阀值使用sendfile。 +location /video/ { + sendfile on; + sendfile_max_chunk 256k; + aio threads; + directio 512k; + output_buffers 1 128k; +} + +上面已经提到了零拷贝技术,它可以有效的改善数据传输的性能,但是由于存储体系结构非常复杂,而且网络协议栈有时需要对数据进行必要的处理,所以零拷贝技术有可能会产生很多负面影响,甚至会导致零拷贝技术自身的优点完全丧失。 +零拷贝就是一种避免 CPU 将一块存储拷贝到另一块存储的技术。它可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效的提高数据传输效率,而且零拷贝技术也减少了内核态与用户态之间切换所带来的开销。进行大量的数据拷贝操作是一件简单的任务,从操作系统的角度来看,如果 CPU 一直被占用着去执行这项简单的任务,是极其浪费资源的。如果是高速网络环境下,很可能就出现这样的场景。 +零拷贝技术分类 +现在的零拷贝技术种类很多,也并没有一个适合于所有场景的零拷贝零拷贝技术,概括起来总共有下面几种: + + +直接 I/O:对于这种数据传输方式来说,应用程序可以直接访问硬件存储,操作系统只是辅助数据传输,这类零拷贝技术可以让数据在应用程序空间和磁盘之间直接传输,不需要操作系统提供的页缓存支持。关于直接 I/O 可以参看Linux 中直接 I/O 机制的介绍。 + + +避免数据在内核态与用户态之间传输:在一些场景中,应用程序在数据进行传输的过程中不需要对数据进行访问,那么将数据从页缓存拷贝到用户进程的缓冲区是完全没有必要的,Linux 中提供的类似系统调用主要有mmap()、sendfile()和splice()。 + + +对数据在页缓存和用户进程之间的传输进行优化:这类零拷贝技术侧重于灵活地处理数据在用户进程的缓冲区和操作系统页缓存之间的拷贝操作,此类方法延续了传统的通信方式,但是更加灵活。在 Linux 中主要利用了「写时复制」技术。 + + +前两类方法的目的主要是为了避免在用户态和内核态的缓冲区间拷贝数据,第三类方法则是对数据传输本身进行优化。我们知道硬件和软件之间可以通过 DMA 来解放 CPU,但是在用户空间和内核空间并没有这种工具,所以此类方法主要是改善数据在用户地址空间和操作系统内核地址空间之间传递的效率。 +避免在内核与用户空间拷贝 +Linux 主要提供了mmap()、sendfile()、splice()三个系统调用来避免数据在内核空间与用户空间进行不必要的拷贝,在Nginx 文件操作优化对sendfile()已经做了比较详细的介绍了,这里就不再赘述了,下面主要介绍mmap()和splice()。 +mmap() +当调用mmap()之后,数据会先通过 DMA 拷贝到操作系统的缓冲区,然后应用程序和操作系统共享这个缓冲区,这样用户空间与内核空间就不需要任何数据拷贝了,当大量数据需要传输的时候,这样做就会有一个比较好的效率。 +但是这种改进是需要代价的,当对文件进行了内存映射,然后调用write()系统调用,如果此时其它进程截断了这个文件,那么write()系统调用将会被总线错误信号SIGBUG中断,因为此时正在存储的是一个错误的存储访问,这个信号将会导致进程被杀死。 +一般可以通过文件租借锁来解决这个问题,我们可以通过内核给文件加读或者写的租借锁,当另外一个进程尝试对用户正在进行传输的文件进行截断时,内核会给用户发一个实时RT_SIGNAL_LEASE信号,这个信号会告诉用户内核破坏了用户加在那个文件上的写或者读租借锁,write()系统调用就会被中断,并且进程会被SIGBUS信号杀死。需要注意的是文件租借锁需要在对文件进行内存映射之前设置。 +splice() +和sendfile()类似,splice()也需要两个已经打开的文件描述符,并且其中的一个描述符必须是表示管道设备的描述符,它可以在操作系统地址空间中整块地移动数据,从而减少大多数数据拷贝操作。适用于可以确定数据传输路径的用户应用程序,不需要利用用户地址空间的缓冲区进行显示的数据传输操作。 +splice()不局限于sendfile()的功能,也就是说sendfile()是splice()的一个子集,在 Linux 2.6.23 中,sendfile()这种机制的实现已经没有了,但是这个 API 以及相应的功能还存在,只不过内部已经使用了splice()这种机制来实现了。 +写时复制 +在某些情况下,Linux 操作系统内核中的页缓存可能会被多个应用程序所共享,操作系统有可能会将用户应用程序地址空间缓冲区中的页面映射到操作系统内核地址空间中去。如果某个应用程序想要对这共享的数据调用write()系统调用,那么它就可能破坏内核缓冲区中的共享数据,传统的write()系统调用并没有提供任何显示的加锁操作,Linux 中引入了写时复制这样一种技术用来保护数据。 +写时复制的基本思想是如果有多个应用程序需要同时访问同一块数据,那么可以为这些应用程序分配指向这块数据的指针,在每一个应用程序看来,它们都拥有这块数据的一份数据拷贝,当其中一个应用程序需要对自己的这份数据拷贝进行修改的时候,就需要将数据真正地拷贝到该应用程序的地址空间中去,也就是说,该应用程序拥有了一份真正的私有数据拷贝,这样做是为了避免该应用程序对这块数据做的更改被其他应用程序看到。这个过程对于应用程序来说是透明的,如果应用程序永远不会对所访问的这块数据进行任何更改,那么就永远不需要将数据拷贝到应用程序自己的地址空间中去。这也是写时复制的最主要的优点。 +写时复制的实现需要 MMU 的支持,MMU 需要知晓进程地址空间中哪些特殊的页面是只读的,当需要往这些页面中写数据的时候,MMU 就会发出一个异常给操作系统内核,操作系统内核就会分配新的物理存储空间,即将被写入数据的页面需要与新的物理存储位置相对应。它最大好处就是可以节约内存,不过对于操作系统内核来说,写时复制增加了其处理过程的复杂性。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 如何保证快速加载网页?——详解浏览器缓存机制 + +

+ +
+ + + + +
+ +
+ +参考内容: +彻底理解浏览器的缓存机制 +彻底弄懂HTTP缓存机制及原理 + +前端开发人员有大部分时间都在调整页面样式,如果页面没有按照自己预期的样式显示,可能想到的第一个解决方案就是清一下浏览器缓存,HTTP 缓存机制作为 Web 性能优化的重要手段,也应该是 Web 开发人员必备的基础知识。我们常说的浏览器缓存机制也就是 HTTP 缓存机制,它是根据 HTTP 报文的缓存标识运行的,所以首先要对 HTTP 报文有一个简单的了解。 +HTTP 报文 +HTTP 报文是浏览器和服务器间进行通信时所发的响应数据,所以 HTTP 报文分为请求(Request)报文和响应(Response)报文两种,浏览器向服务器发送的是请求报文,而服务器向浏览器发送的是响应报文。HTTP 请求报文由请求行、请求头、请求体组成,响应报文则由状态行、响应头、响应正文组成,与缓存有关的规则信息则都包含在请求头和响应头中。 +缓存概述 +浏览器与服务器通过请求响应模式来通信,当浏览器第一次向服务器发送请求并拿到结果后,会根据响应报文中的缓存规则来决定是否缓存结果,其简单的流程如下图: + +浏览器每次发起请求都会先在浏览器缓存中查找该请求的结果和缓存标识,而且每次拿到响应数据后都会将该结果和缓存标识存入缓存中。HTTP 缓存的规则有多种,我们可以根据是否需要重新向服务器发起请求这一维度来分类,即有强制缓存和协商缓存两类,也有人把协商缓存叫对比缓存。 +强制缓存 +我们先自己想一下,使用缓存是不是会有下面几种情况出现。 + + +存在所需缓存并且未失效:直接走本地缓存即可;强制缓存生效; + + +存在所需缓存但已失效:本地缓存失效,携带着缓存标识发起 HTTP 请求;强制缓存失效,使用协商缓存; + + +不存在所需缓存:直接向服务器发起 HTTP 请求;强制缓存失效。 + + +控制强制缓存的字段分别是Expires和Cache-Control,并且Cache-Control的优先级高于Expires。 +Expires +Expires是 HTTP/1.0 控制网页缓存的字段,其值为服务器返回的该缓存到期时间,即下一次请求时,请求时间小于Expires值,就直接使用缓存数据。到了 HTTP/1.1,Expires已经被Cache-Control替代了。 +Expires被替代的原因是因为服务端和客户端的时间可能有误差(比如时区不同或者客户端与服务端有一方时间不准确),这就会导致缓存命中误差,强制缓存就变得毫无意义。 +Cache-Control +Cache-Control是 HTTP/1.1 中最重要的规则,主要取值为: + + + +取值 +规则 + + + + +public +所有内容都可以被缓存,包括客户端和代理服务器,纯前端可认为与private一样。 + + +private +所有内容只有客户端可以缓存,Cache-Control的默认值。 + + +no-cache +客户端可以缓存,但是是否缓存需要与服务器协商决定(协商缓存) + + +no-store +所有内容都不会被缓存,既不是用强制缓存,也不使用协商缓存,为了速度快,实际上缓存越多越好,所以这个慎用 + + +max-age=xxx +缓存内容将在 xxx 秒后失效 + + + +我们可以看看下面这个例子,可以从截图中看到Expires是一个绝对值,而Cache-Control是一个相对值,此处为max-age=3600,即 1 小时后失效。在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control相比于Expires是更好的选择,所以同时存在时只有Cache-Control生效。 + +协商缓存 +协商缓存,顾名思义就是需要双方通过协商来判断是否可以使用缓存。强制缓存失效后,浏览器带着缓存标识向服务器发起请求,由服务器根据缓存标识决定是否可以使用缓存,那自然而然就有协商缓存生效和协商缓存不生效两种情况了。 + +上图是协商缓存生效的流程,如果协商缓存不生效则返回的状态码为 200。协商缓存的标识也是在响应报文的响应头中返回给浏览器的,控制协商缓存的字段有Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高,所以同时存在时只有Etag / If-None-Match生效。 +Last-Modified / If-Modified-Since +你可以往上翻一翻,看一下那张响应报文截图,其中有一个Last-Modified字段,它的值是该资源文件在服务器最后被修改的时间。 +If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值。服务器收到该请求后,发现该请求头有If-Modified-Since字段,则会将If-Modified-Since与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为 200;否则则返回 304,代表资源无更新,可继续使用缓存文件。 + +Etag / If-None-Match +Etag是服务器响应请求时,返回当前资源文件的一个由服务器生成的唯一标识。 +If-None-Match则是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,如果一致则就返回 304,代表资源无更新,可以继续使用缓存文件;否则重新返回资源文件,状态码为200, + +disk cache 与 memory cache +我们可以通过浏览器调试工具查看强制缓存是否生效,如下图所示,状态码为灰色的请求就代表使用了强制缓存,请求对应的 size 显示了该缓存存放的位置,那么什么时候用 disk 什么时候用 memory 呢? + +猜都能猜出来,肯定是优先使用内存(memory)中的缓存,然后才用硬盘(disk)中的缓存。 +内存缓存具有快速读取的特点,它会将编译解析后的文件直接存入该进程的内存中,但是一旦进程关闭了,该进程的内存就会被清空,所以如果你将一个网页关闭后再打开,那么缓存都会走硬盘缓存,而如果你只是刷新网页,那有部分缓存走的就是内存缓存。 +浏览器一般会再 js 和图片等文件解析执行后直接存入内存缓存中,当刷新页面时,这部分文件只需要从内存缓存中读取即可,而 css 文件则会存入硬盘中,所以每次渲染页面都需要从硬盘中读取文件。 +总结 +到这里偷懒一下子了,找到人家画的一张图,看图就行了。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 跨域请求是什么?如何解决? + +

+ +
+ + + + +
+ +
+ +参考内容: +JavaScript: Use a Web Proxy for Cross-Domain XMLHttpRequest Calls +别慌,不就是跨域么! +跨域资源共享 CORS 详解 +AJAX请求和跨域请求详解(原生JS、Jquery) +JavaScript跨域总结与解决办法 + + +刚毕业入职,大部分时间还在培训,中间有一段时间的空闲时间,就学习了下 Angular,在学校都是编写的单体应用,所有代码都放在同一个工程下面,到公司使用的是前后端分离了,虽然后端程序也是我自己写的,但是有一些数据是从公司现有接口去拿的,然后就遇到让我纠结了两小时的跨域请求问题,在这里做一个简单的总结输出。 +什么是跨域请求 +跨域请求问题是浏览器的同源策略造成的,该策略不允许执行其它网站的脚本,是浏览器施加的安全限制。什么是同源?最初是指网页 A 设置的 Cookie 不能被网页 B 打开,包括三个相同:协议、域名、端口。这个同源是从 URL 判断的,不是从 IP 判断的,如果同一个服务器对应连个域名,这两个域名是不同源的。 +http://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 非跨域 + +http://www.nealyang.cn/index.html 调用 http://www.neal.cn/server.php 跨域,主域不同 + +http://abc.nealyang.cn/index.html 调用 http://def.neal.cn/server.php 跨域,子域名不同 + +http://www.nealyang.cn:8080/index.html 调用 http://www.nealyang.cn/server.php 跨域,端口不同 + +https://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 跨域,协议不同 + +localhost 调用 127.0.0.1 跨域 + +同源政策的目的是为了保护用户信息的安全,防止恶意网站窃取数据,随着互联网的发展,同源政策更加严格了,下面三种行为都会受到限制。 +(1) Cookie、LocalStorage 和 IndexDB 无法读取。 +(2) DOM 无法获得。 +(3) AJAX 请求不能发送。 + +所有的现代浏览器都对网络连接进行了安全限制,包括 XMLHttpRequest,如果你的 web 应用程序和其使用的数据在同一个服务器,你不会遇到跨域请求问题。但是当你的 web 应用程序和 web 服务数据不在同一个服务器时,就会被浏览器限制连接了。 +常用解决方案 +    对于跨域请求有很多的解决方案,最常用的解决方案是在你的 web 服务器上面设置代理。在设置代理之前就通过,应用程序直接去请求另一个服务器下的数据;设置代理之后,应用程序从自己的 web 服务器中请求数据,再由代理去请求数据,这样 web 服务器拿到数据之后返回给应用程序即可。从浏览器角度看,就是从同一个服务器拿的数据,并没有进行跨域请求。 + +通俗易懂的说,你家的宠物狗不会吃别家的食物,因为它担心别人的食物会把自己给药死,所以你的狗狗只管找你要食物,你是它的主人,它绝对相信你,而你可以鉴别别人给的食物是不是安全的。类比,小狗就是浏览器,你就是代理。 +Angular 中的解决办法 +上面所说的解决方案在开发过程中不方便操作,每新发一个接口都到服务器中去配置一下,不仅麻烦而且效率低下。首先说一下在 Angular 中一个人比较常用的解决方法,默认你在使用angular-cli构建你的项目,我们可以创建一个代理配置文件proxy.conf.json(假设你的后端服务的访问地址为10.121.163.10:8080),代理配置文件如下: +{ + &quot;/api&quot;: { + &quot;target&quot;: &quot;http://10.121.163.10:8080&quot;, + &quot;secure&quot;: false + } +} + +然后修改package.json文件中的启动命令为&quot;start&quot;: &quot;ng serve --proxy-config proxy.conf.json&quot;,启动项目时使用npm start即可解决跨域请求问题。 +上述解决方案仅在开发时使用,你当然可以使用 tomcat、nginx 配置代理,但是这很麻烦,需要打包代码部署,为了保证效率,我们想写完了立刻测试,同时也不想麻烦做后端的同学,在项目发布时,应该把代理配置到服务器中去;修改启动命令也不是必须的,你也可以选择每次使用 ng serve --proxy-config proxy.conf.json命令启动项目;示例代理配置文件内容可以有更多的属性,可以通过网络查阅相关资料。 +后端解决办法 +我的后端是是用 tornado 实现的,然后我又写了一个单独的页面用于在大屏幕上展示相关数据,没有用 Angular 了,要通过 AJAX请求数据,又怎么解决跨域请求问题呢?这时就需要设置请求头了,让后端允许跨域请求。 +这时需要了解一下简单请求和非简单请求了,简单请求就是只发送一次请求的请求;非简单请求会发送数据之前先发一次请求做预检,通过预检后才能再发送一次请求用于数据传输。 +更清晰区别,满足下列两大条件的属于简单请求,而非简单请求就是请求方法为PUT或DELETE,或者 Content-Type字段是application/json的请求。 + +1.请求方法为 GET、POST、HEAD之一 +2.HTTP头信息不超出字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type,并且 Content-Type 的值仅限于 application/x-www-form-urlencoded、multipart/form-data、text/plain。 + +对于简单请求,只需要设置一下响应头就可以了。 +class TestHandler(tornado.web.RequestHandler): + def get(self): + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + # 可以把 * 写成具体的域名 + self.write('cors get success') + +对于复杂请求,需要设置预检方法,如下所示: +class CORSHandler(tornado.web.RequestHandler): + # 复杂请求方法put + def put(self): + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + self.write('put success') + # 预检方法设置 + def options(self, *args, **kwargs): + #设置预检方法接收源 + self.set_header('Access-Control-Allow-Origin', &quot;*&quot;) + #设置预复杂方法自定义请求头h1和h2 + self.set_header('Access-Control-Allow-Headers', &quot;h1,h2&quot;) + #设置允许哪些复杂请求方法 + self.set_header('Access-Control-Allow-Methods', &quot;PUT,DELETE&quot;) + #设置预检缓存时间秒,缓存时间内发送请求无需再预检 + self.set_header('Access-Control-Max-Age', 10) + + +
+ + Read More ~ +
+
+
+ +
+

+ + 讲一个爱情故事,让 HTTPS 简单易懂 + +

+ +
+ + + + +
+ +
+ +参考内容 +HTTPS explained with carrier pigeons + +充满各种数学证明的密码学是令人头疼的,一听到密码、黑客、攻击等词的时候,就给人一种神秘又高大上的感觉,但除非你真的从事密码学相关工作,否则你并不需要对密码学有多么深刻的理解。 +这是一篇适合在饭后的品茶时光中阅读的文章,咱们虚构一个故事来讲解,虽然故事看起来很随性,但是 HTTPS 也是这么工作的。里面有一些术语你也应该听过,因为它们经常出现在技术文献里面。 +故事背景 +一天,一个男子到河边抓鱼给母亲吃,而河岸的另一头是一大户人家的小姐和她的丫鬟在散步。突然,一个不小心,对面小姐不慎跌入水中,而丫鬟又不会游泳,这可把小丫鬟急的呀!!!正在抓鱼的男子见此状况,来不及脱掉身上的衣物,就像箭一样窜入水中.....想必看客已经猜到了,小姐被救起,男子抱着迷迷糊糊小姐走上岸的过程中,小姐感觉自己像触电了一样,觉得这个男人很安全,只要靠着他,就算天塌下来也不怕,而男子把小姐放下的那一刻,也很不舍,好像把她放下就失去了活下去的希望。 +小姐回到家中,给父亲大人说了这件事,父亲很高兴,就叫下人去把这位男子请到家中表示感谢,结果一问,这小伙幼年丧父,现在家中还有病弱的老母亲,连一间屋子都没有,一直和母亲寄住在城外的破庙里面,不过他毕竟救了自己的女儿,父亲让下人拿出了五十两黄金以表谢意,但不允许他和小姐再有任何来往。 +.....此处省略五千字。 +我们姑且称小姐为小花,称男子为小明,他们不能相见了,但是又备受相思之苦,因此只能通过写信的方式来传达彼此的思念了。 +最简单的通信方式 +如果小花想给小明写信,那么她可以把写好的信让信鸽给小明送去,小明也可以通过信鸽给小花回信,这样他们就能知道彼此的感情了。 +但是很快这种方式出问题了,因为他们都隐约感觉到收到的来信不是对方写的,因为从信件上看,双方都表示不再喜欢彼此。凭借着对彼此的信任,他们才知道是小花的父亲从中阻挠他们。每次他们写的信都被父亲的下人拦下了,然后换上他们事先准备好的信件,目的就是为了让小花和小明断了感情。 +HTTP 就是这样的工作方式。 +对称加密 +小花是博冠古今的人,这怎么能难倒她呢。他们彼此约定,每次写信都加上密码,让信鸽传送的信件是用密文书写的。他们约定的密码是把每个字母的位置向后移动三位,比如 A → D 、 B → E ,如果他们要给对方写一句 &quot;I love you&quot; ,那么实际上信件上面写的就是 &quot;L oryh brx&quot; 。现在就算父亲把信件拦截了,他也不知道里面的内容是什么,而且也没办法修改为有效的内容,因为他不知道密码,现在小花和小明又能给对方写情书了。 +这就是对称加密,因为如果你知道如何加密信息,那也能知道如何解密信息。上面所说的加密常称为凯撒密码,在现实生活中,我们使用的密码肯定会更复杂,但是主要思想是一样的。 +如何确定密钥 +显然对称加密是比较安全的(只有两个人知道密码的情况下)。在凯撒密码中,密码通常是偏移指定位数的字母,我们使用的是偏移三位。 +可能你已经发现问题了,在小花和小明开始写信之前,他们就已经没办法相见了,那他们怎么确定密钥呢,如果一开始通过信鸽告诉对方密钥,那父亲就能把信鸽拦下,也能知道他们的密钥,那么父亲也就可以查看他们信件的内容,同时也能修改信件了。 +这就是典型的中间人攻击,唯一能解决这个问题的办法就是改变现有的加密方式。 +非对称加密 +小花想出了更好的办法,当小花想给小明写情书的时候,她将会按照下面的步骤来进行: + +小花给小明送一只没有携带任何信件的鸽子; +小明让信鸽带一个没有上锁的空箱子回去,钥匙由小明保管; +小花把写好的情书放到箱子里面,并锁上箱子 +小明收到箱子后,用钥匙打开箱子就可以了。 + +使用这种方式,父亲大人就没办法拦截信鸽了,因为他没有箱子的钥匙。同样如果小明想给小花写情书,也采用这种方式。 +这就是非对称加密,之所以称之为非对称加密,是因为即使你能加密信息(锁箱子),但是你却无法解密信息(开箱子),因为箱子的钥匙在对方那里。在技术领域,把这里的箱子称作公钥,把钥匙称作私钥。 +认证机构 +细心的你可能发现问题了,当小明收到箱子后,他如何确定这个箱子的主人是谁呢,因为父亲也可以让信鸽带箱子给小明啊,所以父亲如果想知道他们的信件内容,那只需要把箱子偷换掉就好了。 +小花决定在箱子上面签上自己的名字,因为笔迹是不能模仿的,这样父亲就没办法伪造箱子了。但是依旧有问题,小花和小明在不能相见之前并没有见过彼此写的字,那么小明又如何识别出小花的字迹呢?所以他们的解决办法是,找张三丰替小花签名。 +众所周知,张三丰是当世的得道高人,他的品德是世人都认可的,大家都把他奉为圣人,而且天下肯定不止一对有情人遇到小花和小红这样的问题。张三丰只会为合法居民签名。 +张三丰会在小花的盒子上签名,前提是他确定了要签名的是小花。所以父亲大人是无法得到张三丰代表小花签名的盒子,否则小明就会知道这是一个骗局,因为张三丰只在验证了人们的身份后才会代表他们给盒子签名。 +张三丰在技术领域的角色就是认证机构,你现在阅读这篇文章所使用的浏览器是附带了各种认证机构的签名的。所以当你第一次访问某个网站时,你相信这不是一个钓鱼网站,是因为你相信第三方认证机构,因为他们告诉你这个箱子是合法的。 +箱子太重了 +虽然现在小花和小明有了一个可靠的通信系统,但是信鸽带个箱子飞的慢啊,热恋中的人是“一日不见如隔三秋”,信鸽飞慢了怎么行呢。 +所以他们决定还是采用对称加密的方式来写情书,但是对称加密的密钥要用箱子来传递,也就是用非对称加密方式来传递对称加密密钥,这样就可以同时获得对称加密和非对称加密的优点了,还能避免彼此的缺点。 +需要注意的是,在网络世界中,信息不会像鸽子传送的那么慢,只不过只用非对称加密技术加密信息要比对称加密慢,所以只用它来交换密钥。 +以上就是 HTTPS 的工作过程。 +一个故事 + +这个故事你可能早就知道了,我只是在写文章的过程中突然想起了它,就是笛卡尔的爱情故事。 + + +具体细节你可以网上去查,笛卡尔每天给自己喜欢的公主写信,但是信都被国王拦截了,笛卡尔给公主写的第十三封信中只有一个数学方程,但是这个方程国王看不懂,所以就把这封信交给了公主,公主一看方程,立刻着手把方程的图形画了出来,发现这是一颗心的形状。 + + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/styles/main.css b/styles/main.css new file mode 100644 index 00000000..d81693ea --- /dev/null +++ b/styles/main.css @@ -0,0 +1,1348 @@ +/*! modern-normalize | MIT License | https://github.com/sindresorhus/modern-normalize */ +/* Document + ========================================================================== */ +/** + * Use a better box model (opinionated). + */ +html { + box-sizing: border-box; +} +*, +*::before, +*::after { + box-sizing: inherit; +} +/** + * Use a more readable tab size (opinionated). + */ +:root { + -moz-tab-size: 4; + tab-size: 4; +} +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ +html { + line-height: 1.15; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ +} +/* Sections + ========================================================================== */ +/** + * Remove the margin in all browsers. + */ +body { + margin: 0; +} +/** + * Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) + */ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; +} +/* Grouping content + ========================================================================== */ +/** + * Add the correct height in Firefox. + */ +hr { + height: 0; +} +/* Text-level semantics + ========================================================================== */ +/** + * Add the correct text decoration in Chrome, Edge, and Safari. + */ +abbr[title] { + text-decoration: underline dotted; +} +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ +b, +strong { + font-weight: bolder; +} +/** + * 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) + * 2. Correct the odd `em` font sizing in all browsers. + */ +code, +kbd, +samp, +pre { + font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} +/** + * Add the correct font size in all browsers. + */ +small { + font-size: 80%; +} +/** + * Prevent `sub` and `sup` elements from affecting the line height in all browsers. + */ +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sub { + bottom: -0.25em; +} +sup { + top: -0.5em; +} +/* Forms + ========================================================================== */ +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + line-height: 1.15; + /* 1 */ + margin: 0; + /* 2 */ +} +/** + * Remove the inheritance of text transform in Edge and Firefox. + * 1. Remove the inheritance of text transform in Firefox. + */ +button, +select { + /* 1 */ + text-transform: none; +} +/** + * Correct the inability to style clickable types in iOS and Safari. + */ +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; +} +/** + * Remove the inner border and padding in Firefox. + */ +button::-moz-focus-inner, +[type='button']::-moz-focus-inner, +[type='reset']::-moz-focus-inner, +[type='submit']::-moz-focus-inner { + border-style: none; + padding: 0; +} +/** + * Restore the focus styles unset by the previous rule. + */ +button:-moz-focusring, +[type='button']:-moz-focusring, +[type='reset']:-moz-focusring, +[type='submit']:-moz-focusring { + outline: 1px dotted ButtonText; +} +/** + * Correct the padding in Firefox. + */ +fieldset { + padding: 0.35em 0.75em 0.625em; +} +/** + * Remove the padding so developers are not caught out when they zero out `fieldset` elements in all browsers. + */ +legend { + padding: 0; +} +/** + * Add the correct vertical alignment in Chrome and Firefox. + */ +progress { + vertical-align: baseline; +} +/** + * Correct the cursor style of increment and decrement buttons in Safari. + */ +[type='number']::-webkit-inner-spin-button, +[type='number']::-webkit-outer-spin-button { + height: auto; +} +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ +[type='search']::-webkit-search-decoration { + -webkit-appearance: none; +} +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} +/* Interactive + ========================================================================== */ +/* + * Add the correct display in Chrome and Safari. + */ +summary { + display: list-item; +} +* { + -webkit-tap-highlight-color: transparent; +} +body { + box-sizing: border-box; + font-weight: 300; + font-size: 14px; + font-family: "Myriad Pro", "PingFang SC", "Helvetica Neue", Helvetica, Arial, sans-serif; +} +a { + color: #515a6e; + text-decoration: none; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; + -ms-transition: all 0.3s ease; + -o-transition: all 0.3s ease; + transition: all 0.3s ease; +} +a:focus { + outline: 0; +} +b { + font-weight: normal; +} +i { + font-style: normal; +} +p { + margin: 0; +} +h1, +h2, +h4 { + margin: 0; + font-weight: normal; +} +h3 { + font-weight: 400; + font-size: 17px; +} +h4 { + font-size: 15px; +} +ul { + margin: 0; + padding: 0; + list-style: none; +} +img { + border: 0; +} +code { + font-size: 90%; + font-family: Menlo, monospace; +} +p > code { + color: #ff3502; + background: #f8f5ec; +} +pre { + overflow-x: scroll; + padding: 10pt 15pt; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + background-color: #f5f5f5; + color: #333; + text-align: left; + font-size: 13px; +} +.i-card { + position: relative; + padding: 20px 20px 20px; + border-radius: 10px; + background-color: #fff; + box-shadow: 0 0 30px rgba(200, 200, 200, 0.25); +} +.i-card + .i-card { + margin-top: 30px; +} +.i-card .i-card-title { + margin-bottom: 10px; + font-weight: 400; +} +.btn { + display: inline-block; + padding: 5px 30px; + border: 1px solid rgba(46, 159, 255, 0.4); + border-radius: 100px; + color: #2e9fff; + text-align: center; + font-weight: 400; + font-size: 12px; + cursor: pointer; + transition: all 200ms ease-in-out; +} +.btn:hover { + border: 1px solid #2e9fff; + background-color: rgba(46, 159, 255, 0.05); + color: #48abff; +} +.btn.btn-block { + display: block; +} +.btn.btn-primary { + border: 1px solid rgba(46, 159, 255, 0.4); + color: #2e9fff; +} +.btn.btn-primary:hover { + border: 1px solid #2e9fff; + background-color: rgba(46, 159, 255, 0.05); +} +.btn.btn-success { + border: 1px solid rgba(25, 190, 107, 0.4); + color: #19be6b; +} +.btn.btn-success:hover { + border: 1px solid #19be6b; + background-color: rgba(25, 190, 107, 0.05); +} +.btn.btn-warning { + border: 1px solid rgba(255, 153, 0, 0.4); + color: #ff9900; +} +.btn.btn-warning:hover { + border: 1px solid #ff9900; + background-color: rgba(255, 153, 0, 0.05); +} +.btn.btn-error { + border: 1px solid rgba(237, 64, 20, 0.4); + color: #ed4014; +} +.btn.btn-error:hover { + border: 1px solid #ed4014; + background-color: rgba(237, 64, 20, 0.05); +} +.btn.btn-text { + padding-right: 0; + padding-left: 0; + border: none; + font-size: 14px; +} +.btn.btn-text:hover { + border: none; + background-color: #fff; +} +.i-tag { + display: inline-block; + margin-bottom: 5px; + padding: 3px 10px; + border-radius: 4px; + background-color: rgba(46, 159, 255, 0.1); + color: #2e9fff; + white-space: nowrap; + font-size: 12px; +} +.i-tag + .i-tag { + margin-left: 5px; +} +.i-tag.lg { + padding: 5px 15px; +} +.i-tag.i-tag-primary { + background-color: rgba(46, 159, 255, 0.1); + color: #298fe6; +} +.i-tag.i-tag-primary:hover { + background-color: rgba(46, 159, 255, 0.2); +} +.i-tag.i-tag-success { + background-color: rgba(25, 190, 107, 0.1); + color: #17ab60; +} +.i-tag.i-tag-success:hover { + background-color: rgba(25, 190, 107, 0.2); +} +.i-tag.i-tag-info { + background-color: rgba(45, 183, 245, 0.1); + color: #29a5dd; +} +.i-tag.i-tag-info:hover { + background-color: rgba(45, 183, 245, 0.2); +} +.i-tag.i-tag-warning { + background-color: rgba(255, 153, 0, 0.1); + color: #e68a00; +} +.i-tag.i-tag-warning:hover { + background-color: rgba(255, 153, 0, 0.2); +} +.i-tag.i-tag-error { + background-color: rgba(237, 64, 20, 0.1); + color: #d53a12; +} +.i-tag.i-tag-error:hover { + background-color: rgba(237, 64, 20, 0.2); +} +.i-tag.i-tag-banana { + background-color: rgba(249, 214, 84, 0.1); + color: #e0c14c; +} +.i-tag.i-tag-banana:hover { + background-color: rgba(249, 214, 84, 0.2); +} +.i-tag.i-tag-other_1 { + background-color: rgba(113, 137, 191, 0.1); + color: #667bac; +} +.i-tag.i-tag-other_1:hover { + background-color: rgba(113, 137, 191, 0.2); +} +.i-tag.i-tag-other_2 { + background-color: rgba(223, 117, 153, 0.1); + color: #c9698a; +} +.i-tag.i-tag-other_2:hover { + background-color: rgba(223, 117, 153, 0.2); +} +.i-tag.i-tag-other_3 { + background-color: rgba(255, 199, 133, 0.1); + color: #e6b378; +} +.i-tag.i-tag-other_3:hover { + background-color: rgba(255, 199, 133, 0.2); +} +.i-tag.i-tag-other_4 { + background-color: rgba(114, 214, 201, 0.1); + color: #67c1b5; +} +.i-tag.i-tag-other_4:hover { + background-color: rgba(114, 214, 201, 0.2); +} +body { + display: flex; + background-color: #fcfcfc; +} +.main { + display: flex; + flex: 1; + flex-direction: column; + padding-top: 80px; + width: 100%; +} +.main .main-container { + display: flex; + flex: 1; + margin: 0 auto; + width: 960px; +} +.main .main-container .main-container-left { + flex: 1; + max-width: 660px; +} +.main .main-container .main-container-middle { + flex-shrink: 0; + width: 20px; +} +.main .main-container .main-container-right { + flex-shrink: 0; + width: 280px; +} +@media (max-width: 960px) { + .main .main-container { + flex-direction: column; + justify-content: flex-start; + padding: 0 20px; + width: 100%; + } + .main .main-container .main-container-left { + max-width: 100%; + } + .main .main-container .main-container-middle { + display: none; + } + .main .main-container .main-container-right { + display: none; + } +} +.header { + position: fixed; + top: 0; + z-index: 9999; + display: flex; + width: 100%; + height: 50px; + background-color: #fff; + box-shadow: 0 0 30px rgba(200, 200, 200, 0.25); +} +.header .nav { + display: flex; + align-items: center; + justify-content: space-between; + margin: 0 auto; + width: 960px; +} +.header .nav .logo { + display: flex; + align-items: center; +} +.header .nav .logo .avatar { + display: block; + padding: 5px; + height: 50px; + border-radius: 25px; +} +.header .nav .logo .site-title h1 { + margin-right: 25px; + color: #17233d; + font-weight: 100; + font-size: 24px; +} +.header .nav .menu-btn { + display: none; +} +.header .nav .menu-container { + display: block; +} +.header .nav .menu-container ul { + display: flex; +} +.header .nav .menu-container ul li + li { + margin-left: 50px; +} +.header .nav .menu-container ul li a { + color: #17233d; + font-weight: 400; +} +.header .nav .menu-container ul li a:hover { + color: rgba(23, 35, 61, 0.7); +} +@media (max-width: 960px) { + .header .nav { + position: relative; + padding: 0 20px; + width: 100%; + } + .header .nav .logo { + display: none; + } + .header .nav .menu-btn { + display: block; + cursor: pointer; + } + .header .nav .menu-container { + position: absolute; + top: 50px; + left: 0; + display: none; + width: 100%; + border-top: 1px solid #aaa; + } + .header .nav .menu-container ul { + flex-direction: column; + } + .header .nav .menu-container ul li { + border-bottom: 1px solid #ddd; + background-color: #fff; + text-align: center; + } + .header .nav .menu-container ul li + li { + margin-left: 0; + } + .header .nav .menu-container ul li a { + display: block; + padding: 10px 0; + } +} +.post-container { + display: flex; +} +.post-container .post { + display: flex; + align-items: flex-start; + flex-direction: column; +} +.post-container .post .post-title { + margin-bottom: 10px; +} +.post-container .post .post-title a:hover { + border-bottom: 1px solid #2e9fff; +} +.post-container .post .post-info { + display: flex; + align-items: center; + flex-wrap: wrap; +} +.post-container .post .post-info .post-time { + color: rgba(81, 90, 110, 0.9); +} +.post-container .post .post-info .post-tag { + margin: 5px; +} +.post-container .post .post-article { + display: flex; + flex-direction: row-reverse; + width: 100%; +} +.post-container .post .post-article .post-feature-image { + display: block; + margin-left: 20px; + width: 250px; + height: 150px; + border-radius: 10px; + background-position: center center; + background-size: cover; + box-shadow: 0 0 3px #ddd; +} +.post-container .post .post-article .post-content { + display: flex; + align-items: flex-start; + flex: 1; + flex-direction: column; + justify-content: space-between; + padding: 10px 0; +} +.post-container .post .post-article .post-content .post-content-content { + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + word-break: break-all; + -webkit-line-clamp: 6; +} +.post-container .post .post-article .post-content a.btn { + margin-top: 20px; + padding: 0; +} +@media (max-width: 960px) { + .post-container .post .post-article .post-feature-image { + display: none; + } +} +.site-footer { + padding: 30px 0; + color: #bbb; + text-align: center; + font-size: 12px; +} +.pagination-container { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100px; +} +.pagination-container .page-btn { + margin: 0 10px; +} +.archives-container .timeline { + position: relative; + margin: 0 auto; + padding-left: 20px; + max-width: 740px; + width: 100%; +} +.archives-container .timeline::after { + position: absolute; + top: 0; + left: 0; + width: 4px; + height: 100%; + background: #f5f5f5; + content: " "; +} +.archives-container .timeline li .year { + position: relative; + margin: 80px 0 0; + color: #555; + font-weight: 700; + font-size: 26px; +} +.archives-container .timeline li .year::before { + position: absolute; + top: 50%; + left: -23px; + z-index: 2; + margin-top: -5px; + width: 10px; + height: 10px; + border-radius: 50%; + background: #ddd; + content: " "; +} +.archives-container .timeline li ul.year-wrapper li { + position: relative; + display: flex; + padding: 30px 0 10px; + border-bottom: 1px dashed #ccc; + list-style: none; + line-height: 30px; + color: #9f9f9f; +} +.archives-container .timeline li ul.year-wrapper li .date { + position: relative; + flex-shrink: 0; + margin-right: 20px; + color: rgba(23, 35, 61, 0.7); + font-size: 12px; + line-height: 30px; +} +.archives-container .timeline li ul.year-wrapper li .date::before { + position: absolute; + top: 50%; + left: -22px; + z-index: 2; + margin-top: -4px; + width: 8px; + height: 8px; + border: 1px solid #fff; + border-radius: 50%; + background: #ddd; + content: " "; +} +.archives-container .timeline li ul.year-wrapper li .fa { + line-height: 30px; + color: #9f9f9f; + margin-left: 15px; +} +.archives-container .timeline li ul.year-wrapper li .archives-tags { + line-height: 30px; + font-size: 12px; + color: #9f9f9f; +} +.archives-container .timeline li ul.year-wrapper li .archives-tags:hover { + color: gray; +} +.archives-container .timeline li ul.year-wrapper li .title { + font-size: 16px; + line-height: 30px; +} +.archives-container .timeline li ul.year-wrapper li .title:hover { + border-bottom: 1px solid #2e9fff; +} +.tags-container { + display: flex; + justify-content: center; +} +.tags-container .tags-group { + display: flex; + align-items: flex-start; + flex-wrap: wrap; + padding: 30px; + max-width: 600px; +} +.tags-container .tags-group .tags-tag { + margin: 20px; + padding: 5px 15px; + font-size: 14px; +} +.tags-container .tags-group .tags-tag.sm { + transform: scale(1.3); +} +.tags-container .tags-group .tags-tag.lg { + transform: scale(1.6); +} +.tags-container .tags-group .tags-tag.xl { + transform: scale(1.9); +} +.post-detail { + flex: 1; +} +.post-detail .post { + display: flex; + flex-direction: column; +} +.post-detail .post .post-title { + padding: 24px; + text-align: center; + font-weight: 600; + font-size: 32px; +} +.post-detail .post .post-info { + padding-bottom: 24px; + text-align: center; + font-size: 12px; +} +.post-detail .post .post-info .read-time { + color: #9a9a9a; + display: inline-block; + margin-left: 15px; +} +.post-detail .post .post-info .post-tag { + margin: 5px; +} +.post-detail .post .post-feature-image { + margin-bottom: 24px; + padding-top: 56.25%; + border-radius: 2px; + background-position: center; + background-size: cover; +} +.post-detail .post .post-content { + overflow: hidden; +} +.post-detail .post .post-content strong { + color: #ff3502; +} +.post-detail .post .post-content a { + transition: all 0.3s; +} +.post-detail .post .post-content ol, +.post-detail .post .post-content ul { + font-size: 16px; +} +.post-detail .post .post-content ul { + list-style-type: lower-alpha; +} +.post-detail .post .post-content img { + display: block; + margin: 24px auto; + max-width: 100%; + border-radius: 4px; + box-shadow: 0 0 30px #ddd; +} +.post-detail .post .post-content p { + margin-bottom: 24px; + letter-spacing: 0.05em; + font-size: 16px; + line-height: 1.725; +} +.post-detail .post .post-content hr { + margin: 20px auto; + width: 70%; + border: none; + background-color: #ddd; + height: 1px; +} +.post-detail .post .post-content video { + width: 100%; +} +.post-detail .post .post-content iframe { + width: 100%; + min-height: 300px; +} +.post-detail .post .post-content p code, +.post-detail .post .post-content li code { + padding: 3px 5px; + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 2px 2px; + text-indent: 0; + word-wrap: break-word; + font-size: 14px; + font-family: monospace; + line-height: initial; +} +.post-detail .post .post-content pre { + margin-bottom: 24px; +} +.post-detail .post .post-content pre code { + text-align: left; + tab-size: 4; + border-radius: 5px; + word-wrap: break-word; + white-space: pre-wrap; + font-size: 12px; + font-family: 'Source Code Pro', Consolas, Menlo, Monaco, 'Courier New', monospace; + line-height: 1.375; +} +.post-detail .post .post-content blockquote { + padding-left: 10px; + border-left: 2px solid #006cff; + background: #f3f5f7; + margin: 0; +} +.post-detail .post .post-content blockquote p { + margin-bottom: 0; +} +.post-detail .post .post-content blockquote + p { + margin-top: 15px; +} +.post-detail .post .post-content blockquote + table { + margin-top: 15px; +} +.post-detail .post .post-content table { + text-align: left; + margin: 20px auto; + border-collapse: collapse; +} +.post-detail .post .post-content table, +.post-detail .post .post-content td, +.post-detail .post .post-content th { + border: 1px solid #dedede !important; + font-size: 100%; + padding: 0 5px; + min-width: 45px; +} +.post-detail .post .post-content table code { + padding: 0px 3px; + margin: 0px 3px; + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 2px 2px; + text-indent: 0; + word-wrap: break-word; + font-size: 14px; + font-family: 'Source Code Pro', Consolas, Menlo, Monaco, 'Courier New', monospace; + line-height: initial; + color: #ff3502; + background: #f8f5ec; +} +.post-detail .post .post-content thead { + text-align: left; + background: rgba(0, 0, 0, 0.05); +} +.post-detail .post .post-content thead td { + text-align: left; + font-size: 90%; + border: 1px solid #dfdfdf; + padding: 4px 8px; +} +.post-detail .post .post-content tbody td { + text-align: left; + font-size: 90%; + border: 1px solid #dfdfdf; + padding: 4px 8px; +} +.post-detail .post .post-content ul, +.post-detail .post .post-content ol { + margin-bottom: 16px; + padding-left: 24px; + line-height: 1.725; +} +.post-detail .post .post-content a, +.post-detail .post .post-content a:hover { + text-decoration: none; + color: #ff3502; +} +.post-detail .post .post-content a:hover { + text-decoration: underline; +} +.post-detail .post .post-content h1 { + text-align: center; + font-size: 180%; + margin: 60px 10px 30px 10px; + font-weight: bold; +} +.post-detail .post .post-content h2 { + text-align: center; + font-size: 160%; + margin: 60px 10px 30px 10px; + font-weight: normal; +} +.post-detail .post .post-content h3 { + text-align: left; + font-size: 160%; + margin: 40px 10px 20px 10px; + font-weight: bold; +} +.post-detail .post .post-content h4 { + text-align: left; + font-size: 140%; + margin: 40px 10px 20px 10px; + font-weight: bold; +} +.post-detail .post .post-content h5 { + text-align: left; + font-size: 120%; + margin: 20px 10px 20px 10px; + font-weight: bold; +} +.post-detail .post .post-content h6 { + text-align: left; + font-size: 100%; + margin: 20px 10px 20px 10px; + font-weight: bold; +} +.next-post { + padding: 24px 32px; + text-align: center; +} +.next-post .next { + margin-bottom: 24px; +} +.next-post .post-title { + font-weight: bold; + font-size: 20px; +} +#gitalk-container, +#disqus_thread { + padding: 0 30px; +} +.hljs { + font-size: 14px; + display: block; + overflow-x: auto; + color: #333; + position: relative; + border-radius: 2px; + counter-reset: line; + line-height: 26px; + white-space: normal; +} +.hljs code { + text-align: left; + font-size: 14px; + display: block; + white-space: pre-wrap; + position: relative; + font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; +} +.hljs code::before { + position: absolute; + min-width: 1.5em; + text-align: right; + left: -2.5em; + counter-increment: line; + content: counter(line); + display: inline; + margin-right: 12px; + color: rgba(0, 0, 0, 0.15); +} +.hljs_nowrap code { + white-space: pre; + display: -webkit-box; + display: -webkit-flex; + display: flex; +} +.hljs_nowrap code span.hljs_outer::after { + content: " "; +} +.hljs-comment, +.hljs-quote { + color: #afafaf; + font-style: italic; +} +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #ca7d37; +} +.hljs-number, +.hljs-literal, +.hljs-variable, +.hljs-template-variable, +.hljs-tag .hljs-attr { + color: #0e9ce5; +} +.hljs-string, +.hljs-doctag { + color: #d14; +} +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #d14; +} +.hljs-subst { + font-weight: normal; +} +.hljs-type, +.hljs-class .hljs-title { + color: #0e9ce5; +} +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #0e9ce5; + font-weight: normal; +} +.hljs-regexp, +.hljs-link { + color: #ca7d37; +} +.hljs-symbol, +.hljs-bullet { + color: #d14; +} +.hljs-built_in, +.hljs-builtin-name { + color: #ca7d37; +} +.hljs-meta { + color: #afafaf; +} +.hljs-deletion { + background: #fdd; +} +.hljs-addition { + background: #dfd; +} +.hljs-emphasis { + font-style: italic; +} +.hljs-strong { + font-weight: bold; +} +.id_card { + display: flex; + align-items: center; + flex-direction: column; +} +.id_card .id_card-avatar img { + display: block; + padding: 15px; + width: 100px; + height: 100px; + border: 1px solid #ddd; + border-radius: 50%; + background-position: center center; + background-origin: content-box; + background-size: cover; + background-repeat: no-repeat; + box-shadow: 0 0 30px rgba(200, 200, 200, 0.25); +} +.id_card .id_card-title { + margin-top: 10px; + font-weight: 100; + font-size: 24px; +} +.id_card .id_card-description { + margin-top: 10px; + width: 100%; + color: #979797; + text-align: center; + font-size: 12px; + line-height: 1.5em; +} +.id_card .id_card-sns { + text-align: center; +} +.id_card .id_card-sns .fa { + margin: 20px 10px 0; + font-size: 18px; +} +.id_card .id_card-sns svg { + display: inline-block; + margin-bottom: -2px; + width: 16px; + height: 16px; + margin: 0px 10px; +} +.new_posts_card { + display: flex; + align-items: center; + flex-direction: column; + margin-top: 30px; +} +.new_posts_card .new_posts_card-title { + margin-top: 10px; + font-weight: 100; + font-size: 24px; +} +.new_posts_card .new-posts h4 { + margin-top: 15px; +} +.new_posts_card .new-posts h4 a:hover { + border-bottom: 1px solid #2e9fff; +} +.tags_card { + margin-top: 30px; + display: flex; + align-items: center; + flex-direction: column; +} +.tags_card .tags_card-title { + margin-top: 10px; + font-weight: 100; + font-size: 24px; +} +.tags_card .tags-card-container { + margin-top: 20px; + text-align: justify; +} +.toc-card { + margin-top: 30px; +} +.toc-card .toc-content .markdownIt-TOC { + margin-left: 0; + list-style: none; +} +.toc-card .toc-content a.on { + color: #2e9fff; +} +.toc-card .toc-content ul, +.toc-card .toc-content ol { + margin: initial; + margin-top: 0; + margin-right: 0; + margin-bottom: 10px; + margin-left: 25px; + padding: initial; + padding: 0; + list-style: disc; +} +.toc-card .toc-content ul li, +.toc-card .toc-content ol li { + margin: 5px 0; +} +.toc-card .toc-content ul ul, +.toc-card .toc-content ol ul, +.toc-card .toc-content ul ol, +.toc-card .toc-content ol ol { + list-style: circle; +} +.toc-card .toc-content ul ul ul, +.toc-card .toc-content ol ul ul, +.toc-card .toc-content ul ol ul, +.toc-card .toc-content ol ol ul, +.toc-card .toc-content ul ul ol, +.toc-card .toc-content ol ul ol, +.toc-card .toc-content ul ol ol, +.toc-card .toc-content ol ol ol { + list-style: disc; +} +.toc-card .toc-content ul ul ul ul, +.toc-card .toc-content ol ul ul ul, +.toc-card .toc-content ul ol ul ul, +.toc-card .toc-content ol ol ul ul, +.toc-card .toc-content ul ul ol ul, +.toc-card .toc-content ol ul ol ul, +.toc-card .toc-content ul ol ol ul, +.toc-card .toc-content ol ol ol ul, +.toc-card .toc-content ul ul ul ol, +.toc-card .toc-content ol ul ul ol, +.toc-card .toc-content ul ol ul ol, +.toc-card .toc-content ol ol ul ol, +.toc-card .toc-content ul ul ol ol, +.toc-card .toc-content ol ul ol ol, +.toc-card .toc-content ul ol ol ol, +.toc-card .toc-content ol ol ol ol { + list-style: circle; +} +.toc-card .toc-content ul ul ul ul ul, +.toc-card .toc-content ol ul ul ul ul, +.toc-card .toc-content ul ol ul ul ul, +.toc-card .toc-content ol ol ul ul ul, +.toc-card .toc-content ul ul ol ul ul, +.toc-card .toc-content ol ul ol ul ul, +.toc-card .toc-content ul ol ol ul ul, +.toc-card .toc-content ol ol ol ul ul, +.toc-card .toc-content ul ul ul ol ul, +.toc-card .toc-content ol ul ul ol ul, +.toc-card .toc-content ul ol ul ol ul, +.toc-card .toc-content ol ol ul ol ul, +.toc-card .toc-content ul ul ol ol ul, +.toc-card .toc-content ol ul ol ol ul, +.toc-card .toc-content ul ol ol ol ul, +.toc-card .toc-content ol ol ol ol ul, +.toc-card .toc-content ul ul ul ul ol, +.toc-card .toc-content ol ul ul ul ol, +.toc-card .toc-content ul ol ul ul ol, +.toc-card .toc-content ol ol ul ul ol, +.toc-card .toc-content ul ul ol ul ol, +.toc-card .toc-content ol ul ol ul ol, +.toc-card .toc-content ul ol ol ul ol, +.toc-card .toc-content ol ol ol ul ol, +.toc-card .toc-content ul ul ul ol ol, +.toc-card .toc-content ol ul ul ol ol, +.toc-card .toc-content ul ol ul ol ol, +.toc-card .toc-content ol ol ul ol ol, +.toc-card .toc-content ul ul ol ol ol, +.toc-card .toc-content ol ul ol ol ol, +.toc-card .toc-content ul ol ol ol ol, +.toc-card .toc-content ol ol ol ol ol { + list-style: disc; +} +.toc-card .toc-content ul ul ul ul ul ul, +.toc-card .toc-content ol ul ul ul ul ul, +.toc-card .toc-content ul ol ul ul ul ul, +.toc-card .toc-content ol ol ul ul ul ul, +.toc-card .toc-content ul ul ol ul ul ul, +.toc-card .toc-content ol ul ol ul ul ul, +.toc-card .toc-content ul ol ol ul ul ul, +.toc-card .toc-content ol ol ol ul ul ul, +.toc-card .toc-content ul ul ul ol ul ul, +.toc-card .toc-content ol ul ul ol ul ul, +.toc-card .toc-content ul ol ul ol ul ul, +.toc-card .toc-content ol ol ul ol ul ul, +.toc-card .toc-content ul ul ol ol ul ul, +.toc-card .toc-content ol ul ol ol ul ul, +.toc-card .toc-content ul ol ol ol ul ul, +.toc-card .toc-content ol ol ol ol ul ul, +.toc-card .toc-content ul ul ul ul ol ul, +.toc-card .toc-content ol ul ul ul ol ul, +.toc-card .toc-content ul ol ul ul ol ul, +.toc-card .toc-content ol ol ul ul ol ul, +.toc-card .toc-content ul ul ol ul ol ul, +.toc-card .toc-content ol ul ol ul ol ul, +.toc-card .toc-content ul ol ol ul ol ul, +.toc-card .toc-content ol ol ol ul ol ul, +.toc-card .toc-content ul ul ul ol ol ul, +.toc-card .toc-content ol ul ul ol ol ul, +.toc-card .toc-content ul ol ul ol ol ul, +.toc-card .toc-content ol ol ul ol ol ul, +.toc-card .toc-content ul ul ol ol ol ul, +.toc-card .toc-content ol ul ol ol ol ul, +.toc-card .toc-content ul ol ol ol ol ul, +.toc-card .toc-content ol ol ol ol ol ul, +.toc-card .toc-content ul ul ul ul ul ol, +.toc-card .toc-content ol ul ul ul ul ol, +.toc-card .toc-content ul ol ul ul ul ol, +.toc-card .toc-content ol ol ul ul ul ol, +.toc-card .toc-content ul ul ol ul ul ol, +.toc-card .toc-content ol ul ol ul ul ol, +.toc-card .toc-content ul ol ol ul ul ol, +.toc-card .toc-content ol ol ol ul ul ol, +.toc-card .toc-content ul ul ul ol ul ol, +.toc-card .toc-content ol ul ul ol ul ol, +.toc-card .toc-content ul ol ul ol ul ol, +.toc-card .toc-content ol ol ul ol ul ol, +.toc-card .toc-content ul ul ol ol ul ol, +.toc-card .toc-content ol ul ol ol ul ol, +.toc-card .toc-content ul ol ol ol ul ol, +.toc-card .toc-content ol ol ol ol ul ol, +.toc-card .toc-content ul ul ul ul ol ol, +.toc-card .toc-content ol ul ul ul ol ol, +.toc-card .toc-content ul ol ul ul ol ol, +.toc-card .toc-content ol ol ul ul ol ol, +.toc-card .toc-content ul ul ol ul ol ol, +.toc-card .toc-content ol ul ol ul ol ol, +.toc-card .toc-content ul ol ol ul ol ol, +.toc-card .toc-content ol ol ol ul ol ol, +.toc-card .toc-content ul ul ul ol ol ol, +.toc-card .toc-content ol ul ul ol ol ol, +.toc-card .toc-content ul ol ul ol ol ol, +.toc-card .toc-content ol ol ul ol ol ol, +.toc-card .toc-content ul ul ol ol ol ol, +.toc-card .toc-content ol ul ol ol ol ol, +.toc-card .toc-content ul ol ol ol ol ol, +.toc-card .toc-content ol ol ol ol ol ol { + list-style: circle; +} +.toc-card .toc-content li > ol, +.toc-card .toc-content li > ul { + margin-left: 18px; +} +.toc-card .toc-content ul ul, +.toc-card .toc-content ol ul, +.toc-card .toc-content ul ol, +.toc-card .toc-content ol ol { + margin-bottom: 0; +} diff --git a/tF9vlkh8U/index.html b/tF9vlkh8U/index.html new file mode 100644 index 00000000..ca484cf1 --- /dev/null +++ b/tF9vlkh8U/index.html @@ -0,0 +1,465 @@ + + + + + + + + 那些可以自己一个人薅羊毛的小项目 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 那些可以自己一个人薅羊毛的小项目 +

+ + +
+ +
+

打开知乎、微信这些平台的时候,经常就有意无意冒出来「一部手机怎么赚钱?」、「有什么好的手机赚钱软件?」这一类标题的文章。抖音信息流广告中也有很多类似的台词,「只需一部手机,一件代发,不需要囤货,挑战月入上万」这类广告词都是带你开一个网店,「一部手机,每天只需两小时,一个月轻松回本,开启财富自由模式」......

+

上面提到的那些模式有没有赚到钱的?有,而且还不少。但是拿到你手里能不能赚钱?基本不能!90% 以上的人都是在交智商税,或者是因为执行力不够强,自己没有坚持下来,真正赚了钱的普通人少之又少。这一篇文章不玩套路,分享一个任何人可以套利的模式,也就是上面提到的普通人如何用一部手机赚钱?

+

先把答案放到前面:撸货,这里以市面上比较火热的飞天茅台为例。相信绝大部分人都知道茅台吧,稍微有一点点了解的人就会知道飞天。百度一下飞天茅台的一点点信息,它的出厂价 900 元,再去淘宝、京东这些平台查一下它的价格,茅台官方自营、京东官方自营、天猫超市等是 1499 元一瓶,普通商家卖的都是 2500 元以上一瓶。

+
+

所以这就看到了套利空间了吧?出厂价不要想了,你肯定拿不到那么低的价格的,要是你能拿到出厂价的价格我们可以做个微信朋友吗?悄悄的留下自己的微信号:Guanngxu。那第二档的价格就是天猫超市、京东这些平台的 1499 元一瓶了,这正是我这篇文章所要写的内容了。茅台是硬通货不用担心卖不出去,只要你手里有茅台就不用怕砸钱,当然前提是你入手的成本不高啊。可怜我之前只知道陪房东、陪老叔喝茅台,居然完全不知道这里面有这么大的套利空间,感觉错过了几个亿。

+

那么都有哪些地方可以抢茅台呢?京东、天猫、淘宝这种大家都知道的平台就不用说了,只是基本都需要会员才能抢,觉得开会员的成本比较大?那这时候就可以去找一找低价的渠道了,比如说一些兑换码什么的,还有一些银行和京东的联盟信用卡,当然你也可以联系我呀!

+

还有一些大家可能会忘的渠道,比如说正在慢慢退出大家视野的苏宁易购,比如说很多人都没有用过的国美 APP,还有更少人知道的酒仙网、华润万家、山姆会员店这些。这些可能都需要开通会员才行,怎么开通呢?比如下面这个小程序二维码可以开通国美的会员,你也可以直接在 APP 里面开通会员,也可以找找别的渠道。

+
+

所以有这么多的平台可以去预约茅台,你只需要稍微研究一下平台每天放量的时间点,列一个表然后按照时间去抢就行了,抢得到抢不到就看运气呗。但是成年人的事情不能只靠运气啊,没有运气也要创造运气嘛。把自己的七大姑八大姨都整上,大家一起去抢茅台。

+

这里就是看你人品的时候了,懂得把利益分享给他人你才会有更多的收获,钱是眼睛能看到的利润,但是还有更多眼睛看不到的利润,比如找别人帮忙就给别人发个红包,别人给到你一个启发、一个以前不知道的信息,给别人发一个红包,这些简单的细节可能逐渐得到对方的信任,因为眼前这个人知道的远比你想象的要多得多,他在自己的领域至少是个小狄公吧。

+

除了这些平台还有一些其它线下的平台,这种有一点点地方特色了,比如我现在人在成都,前天去红旗连锁超市买水,发现它的门上贴了一个海报,说积分达到 2000 就可以预约一瓶茅台,积分达到 4000 可以预约两瓶茅台。这种小渠道更多的是靠自己去发现,相比京东一类的大平台,小渠道抢到的概率也更大一些。

+
+

在哪里抢茅台,怎么去抢的问题都解决了,还有一个钱的问题啊,毕竟需要的本金可是不少的啊!这个就更容易解决了,上面已经提到过信用卡了,去申请一个信用卡就可以轻松解决资金的问题了,而且一些联名卡还可以顺道解决平台会员的问题。

+

信用卡除了资金的问题可以解决外,还会带来很多附加的价值,比如我今年一年的话费没有花自己一分钱,全是用平安银行信用卡的积分换的,还有每个月可以兑换的爱奇艺会员,虽然我一直没有用爱奇艺。比如我现在盖的被子、用的体重秤和收纳箱是用招商银行积分换的,找亲朋好友推荐办信用卡还有推荐礼,这也是一部分可以薅的羊毛,当然你愿意找我的话也是非常乐意的。

+

这里还有一个问题,学生群体很多银行都是批不了信用卡的,我知道招商银行信用卡学生是可以申请的,信用卡的玩法有很多很多,那些羊毛尤其迎合了喜欢占小便宜的人性特点,如果玩的好的话免费坐坐飞机、住住酒店也是可以的。

+

假设你现在抢到了茅台,怎么出手呢?身边那些卖酒的商铺、一些老板等等,也有很多人专门加价收购茅台,比如我自己一个大学同学,3000 抢了两瓶茅台,转手 4700 就被人家给买过去了,简直太抢手了。

+
+

上面的内容都是以茅台作为例子的,其它还有很多商品也基本是一样的套路,比如下面截图这个篮球。还有其它 Nike 鞋、纪念币这些都是一样的。写到这里一个普通人可以套利的回路差不多就完成了,基本就是一个手机就可以了,如果想扩大收益那就是多个账号一起来。

+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/tTjJd5E88/index.html b/tTjJd5E88/index.html new file mode 100644 index 00000000..e4a2ab95 --- /dev/null +++ b/tTjJd5E88/index.html @@ -0,0 +1,426 @@ + + + + + + + + 随笔-创业/教育 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 随笔-创业/教育 +

+ + +
+

教育有一个特点,用户评价好不是好。

+ +
+
+ + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/tTktM2p5B/index.html b/tTktM2p5B/index.html new file mode 100644 index 00000000..5511643e --- /dev/null +++ b/tTktM2p5B/index.html @@ -0,0 +1,492 @@ + + + + + + + + 随笔思考 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 随笔思考 +

+ + +
+

最近在烟台出差,趁着周末去烟台山逛了逛。看到灯塔的那一刻,突然想起来小时候写作文,常常用「灯塔」来比喻对自己影响很深的人。但其实这才是我人生第一次近距离观察灯塔,上大学前我根本不知道灯塔长什么样子,所写的文字也不过是机械的搬了些所谓的「美句」。

+

小学教材会讲过马路要走人行道,红灯停绿灯行。但生活在农村的孩子压根就见不到这些城市中再普通不过的事物,我小时候想象过无数次红绿灯和斑马线的样子,

+

断舍离

+

很多时候我们的问题都是又过度的消费引起的,聪明的商家为了引诱人们多多消费,推出令人眼花缭乱的问题解决方案,但真正的有效方案是:减少消费。如果你长胖了,不是要买减肥药、吃蛋白粉,而是要少吃东西。如果你头痛和感到巨大压力,不是要购买头痛药和抗抑郁药,而要更多地睡觉和走路,并且不在深夜浏览社交媒体。

+

一些思考

+

我比较抵触抖音、快手这样的奶头乐事物,因为这些平台的内容基本都是以猎奇为导向,但是回家发现姨夫一家通过自己的努力,在兄弟姐妹中算是实现了从农村到城市的跨越,但从他们身上我发现去一个新的地方,不是简单的一套房子、一份工作那么简单。作为一名异乡人,他们的社会关系还全部在老家,想要嵌入本地生活需要花费极大的非财力成本。

+

上大学时杨福家院士来校开了一次讲座,当时他直言不讳的指出来我们学校没有一个有意义的雕塑,他最喜欢的是《思想者》雕塑,但是在我们学校却没有找到一个雕塑有思考的动作。最近愈发觉得不仅仅是雕塑不会思考,连实实在在的人也已经不会思考了。全民媒体时代的知识分子都是网络知识分子,现在人们已经习惯了什么东西都去网上搜答案,习惯了把自己当成一台检索机器。

+

生活中不乏给别人挑错的朋友,也有很多网友乐意给某个软件、平台挑 bug,读书的时候亦是热衷于去挑书里面的错别字。我以前也是这样的一个人,以为自己能给一本公认的好书挑出来错别字,能给大家都赞赏的软件挑出来 bug,就能做编辑、做产品经理了。这完全是错误的想法,挑三五个错很容易,但自己能做到只能有三五个错吗?

+

对城市的理解

+

陆铭老师的《向心城市》一书提供了比较多的数据支撑,应该是普通人理解城市最好也是最简单的教材,人才和劳动力逐渐向大城市集中是必然的趋势,

+

一席演讲困在时间里的村庄展示了大量因为城市化而被遗弃的荒村,

+ +

探索开源

+

已经忘记是从什么渠道了解到开源社JinaAI 这两个组织了。「开源」两个字眼自大学就时不时出现在视野中,也一直很好奇到底什么是开源,抱着学习开源的心态加入了开源社社区和 JinaAI 社区。为 JinaAI 的宣传工作做了一点点自己的贡献,承担了少许开源社官网开发、COSCon'22中国开源年会的工作,希望新的一年能理解到开源更多的含义。

+

信用卡一文中提到了黄牛这个行业,旁窥过程中发现了很多有意思的事情。国内购物平台基本是淘宝(天猫)、京东、拼多多三家占据,京东

+

我一直有一个比较傻里傻气的疑问。菜市场不同商品的价格都会跟随市场波动,上午一个价、下午一个价是习以为常的事情。但是电脑、手机的价格只要官方发布定价后,很长时间都不会有变动,

+
+

参考内容
+Consume Less. Create More. It’s More Fun.

+
+ +
+
+ + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/tZnRnI64b/index.html b/tZnRnI64b/index.html new file mode 100644 index 00000000..1a460d84 --- /dev/null +++ b/tZnRnI64b/index.html @@ -0,0 +1,681 @@ + + + + + + + + 《算法竞赛进阶指南》165 小猫爬山题解 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 《算法竞赛进阶指南》165 小猫爬山题解 +

+ + +
+ +
+
+

参考内容:

+

[洛谷][noip][算法竞赛进阶指南]小猫爬山

+

《算法竞赛进阶指南》小猫爬山

+
+

小猫爬山

+

题目描述

+

题目链接:https://www.acwing.com/problem/content/167/

+

​Freda 和 Rainbow 饲养了 N 只小猫。这天,小猫们要去爬山。经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。

+

Freda 和 Rainbow 只好花钱让它们坐索道下山。索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是C1、C2……Cn。当然,每辆缆车上的小猫的重量之和不能超过 W。每租用一辆缆车,Freda 和 Rainbow 就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?

+

输入格式

+

​第一行包含两个用空格隔开的整数,N 和 W。接下来 N 行每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci

+

输出格式

+

输出一个整数,最少需要多少美元,也就是最少需要多少辆缆车。

+

分析解答

+

贪心

+

经过思考发现,我们只需要尽可能的在每辆车上都放更多的小猫,就能以最经济的方式把所有小猫都送下山。所以是一个非常明显的贪心题目,我们将所有小猫按重量排序,尽可能把肥猫先送下山即可。具体实现代码如下:

+
#include<bits/stdc++.h>
+using namespace std;
+
+void cin_arr(int *num, int len)
+{
+	for(int i = 0; i < len; i++){
+		cin>>num[i];
+	}
+}
+
+int slove(int *num, int n, int w)
+{
+	int ans = 0;
+	int remain = 0;
+	int load = 0;
+	
+	sort(num, num+n);
+	
+	while(true){
+		for(int i = 0; i < n; i++){
+            // 连当前最小的猫都装不下,那么就新开一辆车
+			if(num[i] != -1 && remain < num[i]){
+				ans++;
+				remain = w;
+				break;
+			}
+		}
+		
+		for(int i = n-1; i >= 0; i--){
+            // 从大到小查找,尽可能装肥猫
+			if(num[i] != -1 && remain >= num[i]){
+				remain -= num[i];
+                // 运送走的小猫重量以 -1 表示
+				num[i] = -1;
+				load++;
+				break;
+			}
+		}
+		
+		// 如果所有小猫都运走了,那么当前 ans 就是答案
+		if(load >= n)
+			return ans;
+	}
+}
+
+int main()
+{
+    int n, w;
+	int cat[1000000];
+	
+	cin>>n>>w;
+	cin_arr(cat, n);
+
+	cout<<slove(cat, n, w)<<endl;
+}
+
+

经过实际测试发现,盲目使用贪心思想的算法并不正确,例如如下测试用例。

+
6 16
+9 5 5 5 4 3
+
+

贪心的结果是使用 3 辆车,分别为9+55+5+43;而正确的结果却是使用 2 辆车,分别为9+4+35+5+5

+

深度优先搜索

+

既然贪心思想在这里行不通,那么我们就采用暴力搜索,即小猫可以放在现有任意一辆车上。具体实现代码如下:

+
#include<bits/stdc++.h>
+using namespace std;
+
+#define N 2000
+
+int n, w;
+int cat[N];
+int sum[N] = {0}; // 第 i 辆车当前重量
+int ans = N;
+
+void cin_arr(int *num, int len)
+{
+	for(int i = 0; i < len; i++){
+		cin>>num[i];
+	}
+}
+
+void dfs(int cur_cat, int cur_car)
+{
+	if(cur_car > ans) // 求最小值,不符合直接返回
+        return ;
+
+    if(cur_cat == n) { // 所有小猫都上车了
+        ans = cur_car;
+        return ;
+    }
+
+    for(int i = 0; i < cur_car; i++) {
+        if(sum[i] + cat[cur_cat] <= w) { // 当前猫能放进去
+            sum[i] += cat[cur_cat]; // 当前猫占用重量
+            dfs(cur_cat+1, cur_car); // 继续放下一只猫
+            sum[i] -= cat[cur_cat]; // 把已经放进去的猫拿出来,因为是循环,所以放入下一辆车里面
+        }
+    }
+
+    // 新开一辆车,把当前这只猫放到新的车里面
+    sum[cur_car] = cat[cur_cat];
+    dfs(cur_cat+1, cur_car+1);
+    sum[cur_car] = 0; // 把猫拿出来
+}
+
+int main()
+{
+	cin>>n>>w;
+	cin_arr(cat, n);
+    dfs(0, 0);
+	cout<<ans<<endl;
+}
+
+

搜索优化

+

考虑到每次都是在车数量固定的情况下进行搜索的,那么少满足一次(sum[i] + cat[cur_cat] <= w)条件,就会少一次递归的调用,也即少一次搜索。那么如何能尽快使得程序尽快不满足该条件呢?

+

sum[i]减小的速度加快就会减少搜索分支,即每次放更重一点的猫进去,就能达到效果。所以我们可以在进行搜索前将小猫的重量进行降序排序,这样从肥猫开始搜索就会减少分支。

+
#include<bits/stdc++.h>
+using namespace std;
+
+#define N 2000
+
+int n, w;
+int cat[N];
+int sum[N] = {0}; // 第 i 辆车当前重量
+int ans = N;
+
+void cin_arr(int *num, int len)
+{
+	for(int i = 0; i < len; i++){
+		cin>>num[i];
+	}
+}
+
+bool cmp(int a, int b)
+{
+    return a > b;
+}
+
+void dfs(int cur_cat, int cur_car)
+{
+	if(cur_car > ans) // 求最小值,不符合直接返回
+        return ;
+
+    if(cur_cat == n) { // 所有小猫都上车了
+        ans = cur_car;
+        return ;
+    }
+
+    for(int i = 0; i < cur_car; i++) {
+        if(sum[i] + cat[cur_cat] <= w) { // 当前猫能放进去
+            sum[i] += cat[cur_cat]; // 当前猫占用重量
+            dfs(cur_cat+1, cur_car); // 继续放下一只猫
+            sum[i] -= cat[cur_cat]; // 把已经放进去的猫拿出来,因为是循环,所以放入下一辆车里面
+        }
+    }
+
+    // 新开一辆车,把当前这只猫放到新的车里面
+    sum[cur_car] = cat[cur_cat];
+    dfs(cur_cat+1, cur_car+1);
+    sum[cur_car] = 0; // 把猫拿出来
+}
+
+int main()
+{
+	cin>>n>>w;
+	cin_arr(cat, n);
+    sort(cat, cat+n, cmp); // 反排序优化搜索
+    dfs(0, 0);
+	cout<<ans<<endl;
+}
+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/tZpY6JWhD/index.html b/tZpY6JWhD/index.html new file mode 100644 index 00000000..a18ed350 --- /dev/null +++ b/tZpY6JWhD/index.html @@ -0,0 +1,526 @@ + + + + + + + + 西凉忆 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 西凉忆 +

+ + +
+ +
+
+

夜雨滴,淅淅沥沥,伶仃至天明。
+忆往景,伤流景。
+世事沧桑敌不过一句悲凉,浮生蹉跎尽在笔墨中泛黄。
+几多欢喜,几多忧愁,曾经挫败,也曾迷茫。
+再回首时,我将深情拥入梦,拥有瞬间的感动却也足够。
+故事中的悲与合,百般萧瑟,千般落寞。

+
+

《浪淘沙》

+

帘外月如钩,好梦难留。
+寻思翻悔几时休,无那安排辞去意,相聚分流。

+

人世总多秋,恰上心头。
+平生却道愿堪忧,暗忆欢期眠不得,何恨离愁。

+

《南乡子.冷风淅》

+

冷风淅,疏雨斜,岸花零落杪雀喑。
+轻舟短棹临野渡,归何处?
+隐隐两三烟柳树。

+

《忆江南》

+

碧云卷 碧云卷,暮日烟霞浓。
+翠楼水阁花树掩,斜晖眽眽又几重。
+望断石桥东。

+

《如梦令》

+
+

乙未十月初五夜无眠,念往日兮,不觉已而四载有余,徒感悲戚,故作此。

+
+

昨夜修竹风露,
+浅睡迷香清雾。
+罗帐月为魂,
+痴念已然终误。
+虚度,虚度。
+回忆落空如墓。

+

《钗头凤》

+

待六月,激情朗,意气凌冠挥缨枪;
+将袖扫,任逍遥,马踏平川,舒眉一笑,傲傲傲!

+

舞三江,梦飞扬,落日无边江不尽;
+趁今朝,更须忙,题名金榜,折桂香飘,妙妙妙!

+
+

花翎水裳,丹青风华暗染霜。
+夜未央,几度琉璃徬。
+谁笑我儿女情长,步步断肠。

+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 00000000..91492d74 --- /dev/null +++ b/tags/index.html @@ -0,0 +1,322 @@ + + + + + + + + Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + + + + + +
+ + + \ No newline at end of file diff --git a/u3bra3rlo/index.html b/u3bra3rlo/index.html new file mode 100644 index 00000000..fc808253 --- /dev/null +++ b/u3bra3rlo/index.html @@ -0,0 +1,381 @@ + + + + + + + + 健康生活 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 健康生活 +
+ + +
+

+ + 说一下戴牙套的感受 + +

+ +
+ + + + +
+ +
+ 快要离开深圳时候和老叔一起吃饭喝酒,老叔看着我突然来了一句:“小刘啊,那个你年终奖也发了,你回去把你的牙齿弄一弄,把那个缝关上,不然你的命就一直不好,漏财!” +说实话,我以前从来没有想过自己的牙齿,还一直觉得自己的那个缝好玩,小时候还常借助门牙那个缝隙,加上自己的舌头抵压,压力会让口水从牙缝挤出去,达到喷射的很远的效果,为数不多的几次口水战我都比较占优势。 +可以但是经叔叔这么一说,我再照镜子时候就不再觉得它好玩了,越看越不美观了。所以有事没事的时候会忍不住去查查正畸方面的信息。回到成都后老叔还是经常给我打电话,每次视频的时候老叔看到我没有戴上牙套就会骂我催我,本来想回到成都就把老头给糊弄过去算了,但自己却是越看越不美观。 +从有矫正的想法到真正戴上牙套中间经历了半年的时间,这半年时间就是查查网上的资料,问问身边有做过正畸的朋友,当然还有一个重要的事情是了解各种正畸方案的价格,像什么陶瓷自锁、金属自锁、隐适美等等,其实这些乱七八糟的事情都是一个心理建设的过程,要突破这个心理障碍还真不容易。 +到现在我已经戴了快一个月的牙套了,刚开始戴上那几天会很疼,连话都不想说。我戴的是进口金属自锁那种大钢牙,刚开始上面那个金属丝还老是滑动容易戳我口腔,如果滑动的不太多我就自己把它拨回去了,如果滑动的太多了就到诊所那里去让牙医搞定,我到诊所就 3 分钟的路程,这一点还是非常便利的。 +做矫正这个事儿可能很多人都觉得麻烦,不能随便吃东西,每吃一顿饭就要刷牙,而且一个大钢牙在嘴里还很丑,牙齿也疼.........就干脆放弃了,但是以我这一个多月的感受告诉你,戴牙套的好处要大于太多坏处。 +人人都想减肥却又管不住自己的嘴,很多人在睡前总是想吃点东西,导致体重增加的非常快,尤其很多所谓无法推脱的社交饭局经常吃到半夜一两点,戴上牙套之后保证你能管好自己嘴,我自己戴上牙套不到一个月已经瘦了 4 斤多了,这是戴牙套之前没有想到的意外收获。 +当然,更加具体的实施方案还得取决于自己的牙齿具体情况,有的人做矫正可能还需要拔几颗智齿,可以顺道消除智齿发炎的地雷。另外一个拔智齿的好处可能会让仙女们比较开心,那就是智齿拔了脸会变小啊,哈哈哈哈哈!! +对我个人来说还有另外一个好处,那就是每天只能吃很少的东西怎么才能让自己更健康的成长,所以抓紧时间去看了《你是你吃出来的》这本书,才知道自己以前对于营养的认知是多么浅显,我忍不住想要对正在看文章的你做个简单的科普! +我们吃东西不仅要关注能量,更要注重七大营养素(碳水化合物、脂类、蛋白质、维生素、矿物质、水、膳食纤维)。像鸡蛋、牛奶、蔬菜、水果、坚果、肉类、(深海)鱼、动物肝脏等,都需要均衡摄入。《你是你吃出来的》一书中做了大量苦口婆心的讲解,也纠正了大家平时的一些错误观点,比如很多人生病了不舒服就咸菜加上喝白粥打发,觉得浓浓的白粥里面很有营养,实际上白粥是没有什么营养的;很多地域晚上都习惯吃面食,因为面食比较容易消化,但面食里面的主要要成分是碳水化合物,非常容易让你血糖快速的升高,碳水化合物摄入过多的结果只会让你越来越胖。 +牙套在嘴里也不太好咀嚼,之前又看到朋友圈的人推荐「若饭」,正好趁这次机会尝试了一下若饭,口味什么的并不是多好,但液体版的若饭对我来说很方便,像考研党、工作狂或者其它比较忙的人,可以买一点若饭在那里备着,一分钟的时间就能解决一顿饭,比上个厕所都要快的多。 +若饭是一种高密度的营养餐,我们大多数人可能觉得自己平时吃的很健康,但估计现实情况和自己认为的恰好相反,比如中国人基本对钠盐的摄入都严重超标,很多人都摄入了太多的碳水化合物,可以看若饭的营养成分表,各方面还是基本兼顾到了的。若饭不仅有液体版,同时也有粉末版供你选择。 + +当然如果能吃到天然的食物最好吃天然新鲜的食物,像若饭这样直接对标美国 Soylent 的产品也不可能全方面兼顾到,比如长期食用可能会影响肠胃功能(我猜的),所以经常吃吃水果、坚果这些小零食,没事儿的时候约几个朋友散散步,偶尔来一顿美食大餐是绝对有必要的。 +最后再推荐你看一个一席的演讲视频:减盐这件大事 + +
+ + Read More ~ +
+
+
+ +
+

+ + 互相看不起|断舍离|《良医》 + +

+ +
+ + + + +
+ +
+ 以前在朋友圈提到过这样一个现象,重庆人和四川人说的都是四川话,但是大部分重庆人会说他们说的是重庆话,说「川渝是一家」的通常也都是四川人。 +在深圳也有一个很怪的现象,两个客家人谈话会用客家话,两个潮汕人谈话会用潮汕话,两个广东人谈话会用粤语,反正就是尽可能用更小众的语言。 +想了一下,故意用第三方听不懂的语言,实际上是很欠考虑的,如果是刚见面用方言寒暄几句我觉得还行,但是后面谈话就应该使用大家都能听懂的语言了。 +疫情期间大家都没法出去玩,我和老叔倒是出去爬了爬山,村里的荔枝山别人进不去,整座山就我和叔两个人,单从疫情这个角度讲,荔枝山是比大部分地方都要安全的。 +我自己可以在疫情期间爬爬山,结合我自己的感受,加上前段时间的「大奔进故宫」事件。我发现人们并不是痛恨特权,而是痛恨自己没有特权。大部人痛恨的不是腐败,痛恨的是自己没有腐败的机会。 +上面四川和深圳两个例子也差不多是出于这样的优越感,鉴于四川除了成都外,其它地方投资的回报率太低,穷地方的人总会羡慕富有的地方,说川渝一家的人大概率不是成都人。 + +春节期间看了一本《断舍离》,它讲究的是内部的自觉自省,虽然整本书挺啰嗦的,完完全全可以用一篇几千字的文章代替,但是它传达的人生整理理念很值得参考,感兴趣的读者大人可以在微信读书中阅读此书。下面是一段摘自书中的内容。 + +我们习惯于思考「有效性」,却往往忽略了作为「有效性」前提的「必要性」,对物品也常常陷入这样的定式思维,导致家里各种杂物堆积,这些杂物给人的压迫感,以及狭窄空间给人的阻塞感,会让人的思维变得迟钝、行动变得迟缓! + +借助「断舍离」的理念,我删了 400 多个微信好友,处理了一些不会再使用的家具和书籍,才发现之前一直舍不得扔的那些东西扔了对我的积极作用更大,以前写过的一篇你如果只是一直囤干货,那永远也不可能进步,核心思想和断舍离基本一致,遗憾的是自己当时写下这篇文章后,竟然不懂得延伸到其它领域。 +可能一部分人有读书摘抄语录的习惯,我个人在阅读技术书籍或是扫除我知识盲点的时候,我也会通过记笔记来加深自己的理解。想想自己强迫症式的记笔记面面俱到其实也是在浪费时间,大部分笔记自己都不会再去看第二遍的,舍弃一些不必要的形式会让自己的阅读更有收获。 +还发现自己另外一个错误观点,我不管是写字还是看书都比大部分人慢,一直都认为是自己看书的方法不对,现在才发现问题的根本原因。是因为我对具体的领域不熟悉,所以看这个领域的书籍才会速度很慢,如果对这个领域熟悉了,那一目十行甚至几十行的速度应该都可以达到。结论就是书读的少了导致读书的速度慢。 + +推荐一部美剧——《良医》,全剧的场景基本是在医院,但有深度的内容基本都和医院无关,除了最基本的医疗科普外,更多的是对家庭、爱、职场、心理等的探讨,下面是我摘的两句台词。 + +Whenever people want you to do someting they think is wrong, they say it’s “reality”. +当人们想让你做他们认为错误的事时,他们总会说这就是现实。 + + +Very few things that are worthwhile in life come without a cost. +人生中那些有意义的事大多是有代价的。 + + +
+ + Read More ~ +
+
+
+ +
+

+ + 智齿|读研否|家庭小江湖|广告乱象 + +

+ +
+ + + + +
+ +
+ 建议读者大人们,如果自己经常一上火牙就疼,或者自己感觉牙已经有点问题了,可以提早预约医院的口腔科查一下,如果有问题早点预防,总是没有坏处的。抽烟的半年洗一次牙,不抽烟的一年洗一次牙。 +因为智齿发炎被狠狠的折磨了近两周,我属于比较能忍得疼痛的人,这不周末才进行了一场春季骑行,唯有美景与美食不可辜负,什么病痛都是浮云,但回想起连续几晚上疼到睡不着觉的滋味,昨天毅然决然斩草除根,给拔掉了,今天就感觉好了很多。 + +前几天考研成绩出来了,估计现在大部分同学都在准备复试,我没有体验过考研的这个过程,毕业这半年有时候还是会想,我也应该体验一下考研的那个过程,已经很久没有体验过把所有时间都投入到一件事情上的快乐了。 +但我还是不太建议读研,我这是无责任建议,毕竟自己没有读过研究生,在研究生实验室待了两年,算是有一半的硕士生经历吧。对于学历我一直以来的观点都是绝大部分人将它的作用放大了,总是认为名校成造就了强能力,而恰恰把因果关系给弄反了,是能力强的人都进了名校(本科)。当然,不否认像医学一类的专业是肯定要考研的,一棒子全打死肯定是不对的。 + +刘大发起的读书活动告一段落了,跟着小伙伴们泛泛的读了一遍《深入理解计算机系统》,只能用“痛并快乐着”来形容这个过程,每周输出一篇读书笔记,没有按时输出就罚钱的规矩很好,人还是需要自己逼自己才行。 +这本书不适合初学者阅读,在豆瓣上的评分接近 10 分,不讲究速成,而是一本内功心法,如果是您是码农的话,读一读绝对会提高一个层级。现在已经开启了另一本书籍的阅读计划,刘大这个活动组织的超好。 + +春节回家发现了一个巨大的变化,我老家那种贫困县地区的村民们,也在开始讨论保险这一类产品了,我是觉得这个改变太大了,说明农民伯伯的经济水平也有很大的提升了。另外通过朋友圈还发现,我认识的大佬们貌似出身都并不是多好,反倒是大部分普通朋友家里的矿更多。 +我们家族每年会组织祭祖活动,在正月初三一大家 50 人左右一同祭拜曾祖曾母,通过这么一个活动把整个大家族的年轻人联系起来,能搭建这样一个平台很棒,我正也在着手将家族信息数字化。 + +说到这里,想说一句家族群是个小江湖,亲戚之间也是暗暗较劲的,母亲不会抢群里几个特定的人发的红包,家里都是山路,车技不好的人很容易就寸步难行,一表叔就因为不到 10 米的距离,整个春节都在亲戚朋友面前抬不起头。 +堂弟现在是民航飞行员在读,而另一个表弟今年正值高考,说要去考炮兵学院,将来好把飞机打下来,这一下可好了,这些话全部伯父被截屏保留了,将来某一天要是这俩兄弟闹矛盾了,估计有的好看。保二爷写的家族群不是群,是江湖...看起来更有趣一点。 +在我身上更可悲的事情发生了,所有长辈一致认同应该由我来管理家族群,想想整个群里充斥的都是是那种要露不露、似露非露、就是不露的视频,或者是用粗糙都无法形容的大而泛的鸡汤文,整个头就大了,这可比解决技术问题难多了。 + +最近愈发觉得“大佬”之间的抄袭严重了,真大佬基本都是原创内容,或者是引用了别人的文字就标注出来,然而总是看到一些“大佬”原封不动发出来,还不表明出处,下面粉丝跟着继续做同样的事,我看到最多的一次是朋友圈连续 10 多条是一样的段子。 + +最后想无责任乱说一点科技相关的东西,5G 是当下的风口浪尖,各大厂商都希望在 5G 上有一席之地,搞芯片的搞芯片、做基站的做基站、整手机的整手机,5G + IPV6 肯定会带来无法想象的未来,5G 会大幅推动智能硬件的应用,但是手机这个应用场景是不是被夸大了呢? +现在的 4G 手机在线看一部高清电影不会有多卡顿的现象,广大吃瓜群众和各大媒体,一直都拿着 5G 手机来吹嘘,吃瓜群众跟随媒体引导的大流,我总觉得当拿到 5G 手机的那一刻,心里肯定会从喜悦急转失望的,就像目前的苹果产品一样。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/uAv93EeJY/index.html b/uAv93EeJY/index.html new file mode 100644 index 00000000..ac83b004 --- /dev/null +++ b/uAv93EeJY/index.html @@ -0,0 +1,582 @@ + + + + + + + + 工具箱 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 工具箱 +

+ + +
+

这里收集自己平时发现的实用、精美小工具,列表内容会持续更新的。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
名称分类简介
fotor在线图片编辑工具一个在线图片编辑工具,具备常用的按比例裁剪、按形状裁剪、调整大小等等功能
TWsaver下载工具推特视频下载工具,只需要把推文链接贴上去,它就可以将推文的视频下载下来
Dark ReaderNight EyeChrome 插件可以把任何网站都变成黑夜模式的工具,相比之下 Dark Reader 看起来更舒服一点
FasterChromeChrome 插件当用户鼠标悬停在链接上面,就开始预加载网页,使得真正点击的时候,页面瞬间就能加载。
screenrecord在线录屏一个录屏工具,在线录制是它最大的优点,省去了安装录屏软件的麻烦
Cmder终端工具一个 Windows 下的终端工具,可以像在 linux 下一样敲命令,提供了 mini 版和 full 版,如果遇到问题可以看这篇文章
Visualgo数据结构资料该网站将一些基础的、常见的算法用视频来展示,方便学习者的理解,是不错的数据结构、算法学习资源
UnsplashPexels资源素材图片非常精美,而且都可以免费使用,不用担心版权问题
Mixkit资源素材免费可直接下载的视频资源库,包括商业、城市、自然、生活方式等常用类型的视频,里面都是非常干净、原创度很高的视频素材
Facebook Design资源素材由 Facebook 设计团队出的资源网站,这个网站有很多设备模型,比如:手机、电脑、iPad、Apple Watch,同时还包括
Pixabay资源素材它除了图片素材外,也包含了很多视频素材,视频素材同样精美、干净
优品 PPT资源素材里面有很多精美的 PPT 模板,优点是该网站提供直接免费下载方式,并不需要注册账户、付费等等繁杂的流程
icons.download资源素材一个完全免费的开源矢量图标,它提供了16种款式,212个图标,有实体和轮廓,尖锐和圆润,4种宽度,免费且可商用
Countrymeters世界人口时钟可以实时查看世界人口的变化,也可以缩小反馈查看具体的国家、地区等等,也提供了世界五大死亡原因
敏感词防和谐敏感词防和谐工具这个工具通过在每个字符之间插入零宽字符来防止敏感词被和谐,每年都会经历的论文查重也可以通过插入这样的零宽字符来避免
Gridea博客工具一个静态博客写作客户端,可以实现博客文章一键部署到 𝖦𝗂𝗍𝗁𝗎𝖻 𝖯𝖺𝗀𝖾𝗌 或 Coding Pages,并且支持 Markdown 语法,但是软件做的比较卡
Tailwind CSSCSS 框架一个实用的CSS框架,用于快速构建自定义设计,它提供了底层的实用工具类,让您可以在不离开HTML的情况下构建完全定制的设计。
Linux-command文档Linux 常用命令都能通过它搜索到,并且伴随着简明易懂的示例,它也提供了 Chrome 插件
Instant.pageJs 库当用户鼠标悬停在链接上面,就开始预加载网页,从而使得用户真正点击的时候,页面瞬间就能加载。
ThenByJs 库一个可以实现多字段排序的 js 库
IconGo图标库开源的图标搜索引擎
RegExr正则工具在线正则表达式学习测试工具,提供正则式编辑、学习、创建和测试
sms-activate接码平台很棒的接码平台
Openverse素材资源一个图片和音频的搜索引擎,据说包含超过6亿件作品,都可以自由使用,不用付费
中国地铁信息概览数据资源可视化显示全国各个城市的地铁概况,包括每日的客流量
REMIX ICON图标库一个很不错的图标库
docsmall文件压缩工具图片压缩、GIF 压缩、PDF 压缩、PDF 合并/分割
Smartphones产品边框模版上传图片即可将图片镶嵌在不同场景的模版下,适合产品宣传一类
+ +
+
+ + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/uDVEq7OW-/index.html b/uDVEq7OW-/index.html new file mode 100644 index 00000000..34b7de5a --- /dev/null +++ b/uDVEq7OW-/index.html @@ -0,0 +1,585 @@ + + + + + + + + Scrapy 爬虫框架入门——抓取豆瓣电影 Top250 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ Scrapy 爬虫框架入门——抓取豆瓣电影 Top250 +

+ + +
+ +
+

最好的学习方式就是输入之后再输出,分享一个自己学习scrapy框架的小案例,方便快速的掌握使用scrapy的基本方法。

+

本想从零开始写一个用Scrapy爬取教程,但是官方已经有了样例,一想已经有了,还是不写了,尽量分享在网上不太容易找到的东西。自己近期在封闭培训,更文像蜗牛一样,抱歉。

+

Scrapy简介

+

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。

+

其最初是为了 页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。

+
+

如果此前对scrapy没有了解,请先查看下面的官方教程链接。

+

架构概览:https://docs.pythontab.com/scrapy/scrapy0.24/topics/architecture.html

+

Scrapy入门教程:https://docs.pythontab.com/scrapy/scrapy0.24/intro/tutorial.html

+

爬虫教程

+

首先,我们看一下豆瓣TOP250页面,发现可以从中提取电影名称、排名、评分、评论人数、导演、年份、地区、类型、电影描述。

+
+

Item对象是种简单的容器,保存了爬取到得数据。其提供了类似于词典的API以及用于声明可用字段的简单语法。所以可以声明Item为如下形式。

+
class DoubanItem(scrapy.Item):
+    # 排名
+    ranking = scrapy.Field()
+    # 电影名称
+    title = scrapy.Field()
+    # 评分
+    score = scrapy.Field()
+    # 评论人数
+    pople_num = scrapy.Field()
+    # 导演
+    director = scrapy.Field()
+    # 年份
+    year = scrapy.Field()
+    # 地区
+    area = scrapy.Field()
+    # 类型
+    clazz = scrapy.Field()
+    # 电影描述
+    decsription = scrapy.Field()
+
+

我们抓取到相应的网页后,需要从网页中提取自己需要的信息,可以使用xpath语法,我使用的是BeautifulSoup网页解析器,经过BeautifulSoup解析的网页,可以直接使用选择器筛选需要的信息。有一些说明写到代码注释里面去了,就不再赘述。

+

Chrome 也可以直接复制选择器或者XPath,如下图所示。

+
+
class douban_spider(Spider):
+
+    count = 1
+
+    # 爬虫启动命令
+    name = 'douban'
+
+    # 头部信息,伪装自己不是爬虫程序
+    headers = {
+        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36',
+    }
+
+    # 爬虫启动链接
+    def start_requests(self):
+        url = 'https://movie.douban.com/top250'
+        yield Request(url, headers=self.headers)
+
+    # 处理爬取的数据
+    def parse(self, response):
+
+        print('第', self.count, '页')
+        self.count += 1
+
+        item = DoubanItem()
+        soup = BeautifulSoup(response.text, 'html.parser')
+
+        # 选出电影列表
+        movies = soup.select('#content div div.article ol li')
+
+        for movie in movies:
+            item['title'] = movie.select('.title')[0].text
+            item['ranking'] = movie.select('em')[0].text
+            item['score'] = movie.select('.rating_num')[0].text
+            item['pople_num'] = movie.select('.star span')[3].text
+
+            # 包含导演、年份、地区、类别
+            info = movie.select('.bd p')[0].text
+            director = info.strip().split('\n')[0].split('   ')
+            yac = info.strip().split('\n')[1].strip().split(' / ')
+
+            item['director'] = director[0].split(': ')[1]
+            item['year'] = yac[0]
+            item['area'] = yac[1]
+            item['clazz'] = yac[2]
+
+            # 电影描述有为空的,所以需要判断
+            if len(movie.select('.inq')) is not 0:
+                item['decsription'] = movie.select('.inq')[0].text
+            else:
+                item['decsription'] = 'None'
+            yield item
+
+        # 下一页:
+        # 1,可以在页面中找到下一页的地址
+        # 2,自己根据url规律构造地址,这里使用的是第二种方法
+        next_url = soup.select('.paginator .next a')[0]['href']
+        if next_url:
+            next_url = 'https://movie.douban.com/top250' + next_url
+            yield Request(next_url, headers=self.headers)
+
+

然后在项目文件夹内打开cmd命令,运行scrapy crawl douban -o movies.csv就会发现提取的信息就写入指定文件了,下面是爬取的结果,效果很理想。

+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/uHEHy2zPz/index.html b/uHEHy2zPz/index.html new file mode 100644 index 00000000..54c14247 --- /dev/null +++ b/uHEHy2zPz/index.html @@ -0,0 +1,645 @@ + + + + + + + + FastDFS 分布式文件系统简介 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ FastDFS 分布式文件系统简介 +

+ + +
+ +
+

文章内容是刘欣大大(《码农翻身》作者,公众号:码农翻身)的直播课内容,主要是了解一下分布式文件系统,学习FastDFS的一些设计思想,学习它怎么实现高效、简洁、轻量级的一个系统的

+

FastDFS分布式文件系统简介

+

国内知名的系统级开源软件凤毛菱角,FastDFS就是其中的一个,其用户包括我们所熟知的支付宝、京东商城、迅雷、58同城、赶集网等等,它是个人所开发的软件,作者是余庆。

+

我们已经进入互联网时代,互联网给我们的生活带来便捷的同时,也给我们带来了诸多挑战。

+
+

对于海量文件的存储,一个机器不够,那么就用多台机器来存储。

+
+

如果一个文件只存储一份,那么如果存储这个文件的机器坏掉了,文件自然就丢失了,解决办法就是将文件进行备份,相信大多数人都有备份重要文件的习惯。FastDFS也是如此,为了防止单点的失败,肯定是需要冗余备份的。

+

FastDFS把应用服务器分为若干个组,每一组里面可以有多台机器(一般采用3台),每一台机器叫做存储服务器(storage server)。同一组内之间的数据是互为备份的,也就是说用户把文件传到任一服务器,都会在同组内其它两个服务器进行备份,因此一个组的存储空间大小是由该组内存储空间最小的那台机器是一样的(和木桶原理一样)。为了不造成存储空间的浪费,同一个组内的三台机器最好都一样。

+
+

每个存储服务器(storage server)的存储就够又是怎样的呢?展开来看,它可以分为多个目录,每个目录以M开头,用00、01、02......来划分,一般无需划分这么多目录,只用一个目录就可以了。

+

在每个根目录下面又划分了两级目录。如图所示,在/data/fastdfs0下又划分出两级目录,每一级有256个目录,这样算下来总共就有65535个目录了。存储文件时,就是通过两次哈希来确定放在哪一个目录下面。

+
+

那么问题就来了,有这么多组,到底该选择哪个组的服务器进行存储呢?或者说,访问的时候到底访问哪一个组呢?

+

FastDFS提供的解决思路是引入一个跟踪服务器(tracker server),它用于记录每一个组内的存储服务器信息,存储信息是每个storage主动回报给tracker,有了这些信息之后,tracker就可以做调度工作了,看看谁的存储空间大,就把文件放过去。

+
+

FastDFS的特点

+
    +
  • 组与组之间是相互独立的
  • +
  • 同一个组内的storage server之间需要相互备份 +
      +
    • 文件存放到一个storage之后,需要备份到别的服务器
    • +
    +
  • +
  • tracker之间是不交互的 +
      +
    • 每个storgae server都需要向所有的tracker去主动报告信息
    • +
    • tracker与tracker之间是不知道彼此的存在的
    • +
    +
  • +
+

如何上传文件

+

为方便下载文件的理解,这里假设上传的文件为:Group1/M00/00/0C/wKjGgVgbV2-ABdo-AAAAHw.jpg

+

如下面的时序图可以看到客户端是如何上传文件到服务器的。首先client向tracker发送上传链接请求,然后由tracker进行调度,查询可用的storage,并把该storgae对应的ip和端口发送给client;拿到了存储服务器信息,client就直接将文件上传到storage即可;storage会生成新的文件名再写入到磁盘,完成之后再把新的文件信息返回给client,client最后把文件信息保存到本地。需要注意的是,storage会定时向tracker回报信息。

+
+

如何进行选择服务器

+
    +
  • tracker不止一个,客户端选择哪一个做上传文件? +
      +
    • tracker之间是对等的,任选一个都可以
    • +
    +
  • +
  • tracker如何选择group? +
      +
    • round robin(轮询)
    • +
    • load balance(选择最大剩余空间group上传)
    • +
    • specify group(制定group上传)
    • +
    +
  • +
  • 如何选定storage? +
      +
    • round robin,所有server轮询使用(默认)
    • +
    • 根据ip地址进行排序选择第一个storage(ip地址最小者)
    • +
    • 根据优先级进行排序(上传优先级由stoage来设置,参数为upload_priority)
    • +
    +
  • +
  • 如何选择storage path +
      +
    • round robin,轮询(默认)
    • +
    • load balance,选择使用剩余空间最大的存储路径
    • +
    +
  • +
+

如何选择存放目录

+
    +
  • 选定存放目录? +
      +
    • storage会生成一个file_id,采用Base64编码,字段包括:storage ip、文件创建时间、文件大小、文件CRC32校验和随机数
    • +
    • 每个存储目录下面有两个256 * 256个子目录,storage会按文件file_id进行两次hash,然后将文件以file_id为文件名存储到子目录下
    • +
    +
  • +
+

需要注意的是:file_id由cilent来保存,如果没有保存,你就不知道你上传的文件去那里了

+

Storage server之间的文件同步

+
    +
  • 同一组内的storage之间是对等的,文件上传、删除等操作可以在任意一台storage上进行
  • +
  • 文件同步只在同组内的stroage之间进行,采用push方式,即源服务器同步给目标服务器
  • +
  • 源头数据才需要同步,备份数据不需要再次同步,否则就构成环路了
  • +
  • 新增一台storage时,由已有的一台storage将已有的所有数据(包括源头数据和备份数据)同步给该新增服务器
  • +
+

Storage的最后最早同步被同步时间

+

这个标题有一些拗口,现在有三台服务器A、B、C,每个服务器都需要记录其他两台服务器向自己进行同步操作的最后时间。比如下图中的服务器A,B在9:31向A同步了所有的文件、C在9:33向A同步了所有的文件,那么A服务器的最后最早被同步时间就是9:31。其他两个服务器也是一样。

+

最后最早被同步时间的意义在于判断一个文件是否存在于某个storage上。比如这里的A服务器最后最早被同步时间为9:31,那么如果一个文件的创建时间为9:30,就可以肯定这个文件在服务器A上肯定有。

+

Stroage会定期将每台机器的同步时间告诉给tracker,tracker在client需要下载一个文件时,要判断一个storage是否有该文件,只需要解析文件的创建时间,然后与该值作比较,若该值大于创建时间,说明storage存在这个文件,可以从该storage下载。

+
+

但是这个算法有缺陷,比如下面的情况:W文件的创建时间是9:33,服务器C已经把9:33之前的文件都同步给B了,因此B服务器里面其实已经有W文件了,但是根据最后最早被同步时间,会认为B中没有W文件。因此这个算法虽然简单,但是牺牲了部分文件。

+
+

如何下载文件

+

首先由client发送下载连接请求,请求的东西本质上就是Group1/M00/00/0C/wKjGgVgbV2-ABdo-AAAAHw.jpg;tracker将查询到的可用storage server(按下文的四个原则进行选择)的ip和端口发送给client;现在client有本地保存的文件信息,也有服务器的地址和端口,那么直接访问对应的服务器下载文件即可。

+
+

如何选择一个可供下载的storage server

+

共下面四个原则,从上到小条件越来越宽松

+
    +
  • 该文件上传到的源storage(文件直接上传到该服务器上)
  • +
  • 文件创建时间戳 &lt; storage被同步到的文件时间戳,这意味着当前文件已经被同步过来了
  • +
  • 文件创建时间戳 = storage被同步到的文件时间戳,并且(当前时间-文件创建时间戳)&gt; 一个文件同步完场需要的最大时间(5分钟)
  • +
  • (当前时间 - 文件创建时间)&gt; 文件同步延迟阀值,比如我们把阀值设置为1天,表示文件同步在一天内肯定可以完成
  • +
+

FastDFS的使用

+

用户通过浏览器或者手机端访问web服务器,web服务器把请求转发给应用服务器,应用服务器收到请求后,通过fastDFS API和FastDFS文件系统进行交互。但是这么设计会造成应用服务器的压力,因为上传和下载都经过应用服务器。

+
+

为了避免应用服务器压力过大,可以让客户端直接使用Http访问,不通过应用服务器。

+
+

FastDFS其他内容

+

防止盗链

+

为了防止辛辛苦苦上传的文件被别人盗去,可以通过给URL设置token来解决。FastDFS的防止盗链配置如下:

+
# 是否做tokrn检查,缺省值为false
+
+http.anti\_steal.check\_token=true
+
+# 生成token的有效时长/秒
+
+http.anti\_steal.token\_ttl=900
+
+# 生成token的密钥,尽量设置长一些
+
+http.anti\_steal.secret\_key=@#$%\*+\*&amp;amp;!~
+
+FastDFS生成token策略为:token = md5(文件名,密钥,时间戳)
+
+
+

合并存储

+
    +
  • 海量小文件的缺点 +
      +
    • 元数据管理低效,磁盘文件系统中,目录项、索引节点(inode)和数据(data)保存在介质不同的位置上
    • +
    • 数据存储分散
    • +
    • 磁盘的大量随机访问降低效率(小文件有可能这个在这个磁道,那个在那个磁道,就会造成大量的随机访问,大量小文件对I/O是非常不友好的)
    • +
    +
  • +
  • FastDFS提供的合并存储功能 +
      +
    • 默认大文件64M
    • +
    • 每个文件空间称为slot(256bytes = slot = 16MB)
    • +
    +
  • +
+

也就是说对于小文件,FastDFS会采用把多个小文件合并为一个大文件的方式来存储,默认建一个大小为64M的大文件,然后再分成多个槽,最小的槽是256bytes,因此如果一个文件小于256bytes,那么它也会占256bytes的大小。就好像我们在医院见到的中药柜子一样,每个抽屉里面再分成多个小格子,根据药材包的大小来选择不同大小的格子。

+

没有合并时的文件ID

+
+

合并时的文件ID

+
+

此处不再深入探讨存储合并的机制,因为它带来了一系列新的问题,比如同步时不仅需要记录大文件的名称,还需要进入小文件的名称,一下子变得麻烦多了;原来空闲空间管理直接通过操作系统就能计算出来,但是现在不行了,因为是创建了一个64M的块,这个块里面还有空闲空间,计算起来就很麻烦了。

+

总结

+
    +
  • FastDFS是穷人的解决方案
  • +
  • FastDFS把简洁和高效做到了极致,非常节约资源,中小型网站完全用得起
  • +
+ +
+
+ + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/udNd3A-Sd/index.html b/udNd3A-Sd/index.html new file mode 100644 index 00000000..12c0326a --- /dev/null +++ b/udNd3A-Sd/index.html @@ -0,0 +1,452 @@ + + + + + + + + 年轻不要给自己设限 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 年轻不要给自己设限 +

+ + +
+ +
+

初入象牙塔时乘坐了 60 多个小时的火车,后面基本都选择了飞机作为出行交通工具,毕业时再次选择了火车这一交通工具回家,再看一次从东北到西南的沿途风景,无奈火车居然能晚点两小时,这篇文章是在火车上为打发时间写的,希望对您有所帮助。

+

记得大一入学前,买了一本覃彪喜写的《读大学,究竟读什么》,那时候对于里面有一些观点不赞同,觉得大学这么神圣的地方,怎么被作者写成那样,读完一遍只是抱有一种怀疑的态度,四年之后的今天,我觉得这本书值得一看,大部分内容还是有用的,不过有一些内容还是很偏激的,自己过滤掉就好了。

+

现在回头看,大学最需要的应该是经历,我也是大三才算明白这个道理吧(这个道理应该不止学生能实用)。我认为本科阶段是容错率最高的阶段,这个时候你干什么都不怕,犯了错也没有什么大碍,最重要的是犯错(不犯错更好)的那个过程。

+

年轻人做什么都是学习,不要给自己设限,在一无所有的年龄就应该多经历,因为这时候的容错率很高,试错成本低就要勇于试错。(这句话可能之前的文章说过,大同小异的话你也能在别的好文章里面见到)

+

现在的家长,也包括孩子,大多数喜欢拿一些证书、奖杯出来炫耀,而现在大学里面的个性化保研政策看的就是各种奖项。我更看重的是比赛的过程,但是在学校有一个怪现象:我不想办事,只想你给我挂一个名,到出去比赛的时候,看到所报的项目自己不是第一作者,都不愿意去比赛,觉得是在浪费时间。

+

我个人在这里面算一股清流了,我很喜欢跟着出去比赛,因为比赛的过程能教会你很多在学校学不到的东西,给不给我奖状无所谓,只要给我报销差旅费就行了,这一点对我这种穷学生来说跟重要,想出去看看世界长长见识,自己又没有钱,学生群体中随随便便就拿出几千块钱的人还是不多,所以这是我找到的最好的长见识的方法了,上大学前连小县城都没出过的我,通过比赛到过佛山、深圳、重庆、日照等地,这对我算是一生的财富。

+

写到这里,脑子里面满满的全是回忆,发现想说的太多,全写出来可能会上万字,先不写了,以后分开写个系列的也行,下面说几句干货道理吧,过来人的总结。

+

第一,少拿学校的光环往自己身上套,和你没关系,对于我的学校动不动就拿哈军工说事(中国人都喜欢把自己和名家扯上关系,看起来显得有一些历史文化底蕴),完全是不自信的表现。作为唯一一个首批进入211缺不是985的学校,我觉得学校一直在啃老底。类似的文章还有之前写的谈一点关于名校的话题和刘大写的除去大公司的光环,你还剩点啥?

+

第二,学校教不了你多少东西,大学阶段和高中阶段最大的区别是,高中有人赶着你学,而且还有人给你指明学习的方向,但是大学没有人告诉你学什么,也没有人赶着你学习,所以培养自学能力和判断选择能力很重要,我个人认为这是大学阶段最应该学习到的东西。

+

第三,如果大学只学习了课本中的内容,那还不如不上大学,不得不承认,大学课本内容都属于经典中的经典,但是学校的要求太低,所以要自己去练习,而且很多老师所教授的东西属于过时的知识,有的课就应该逃掉,利用这个时间去做更有用的事情。

+

第四,真诚待人,学生阶段所交的朋友没多少功利性,能交几个铁哥们最好。我个人觉得比较实用的一个看人标准,你只需要看某个人对待其他人是什么样,就大概知道他对你会是什么样了;好比谈恋爱,你不要妄想渣男渣女到你这里就不渣了(小概率事件)。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/ulgA83fCg/index.html b/ulgA83fCg/index.html new file mode 100644 index 00000000..401d91a4 --- /dev/null +++ b/ulgA83fCg/index.html @@ -0,0 +1,460 @@ + + + + + + + + 通过在生财有术捡碎片每月赚顿火锅钱 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 通过在生财有术捡碎片每月赚顿火锅钱 +

+ + +
+ +
+

有段时间看到有人在生财讨论「外卖淘客」的案例,生财有术的公众号也转发了一篇手把手教学的文章:睡后收入:适合小白操作的一个自动赚钱项目,当天晚上读了这篇文章之后便立马操作起来了。

+

先是去关注了一堆做外卖淘客的公众号,看一下他们的操作流程和话术。紧接着把自己以前申请的公众号改名字、改头像、改功能介绍,去公众号后台设置关注后自动回复。然后按照上面文章中的介绍去闲鱼引流,但是闲鱼上面放的链接一直无人问津,只是一直机械的去学习同行,优化闲鱼的自动回复话术。

+
+

大概过了快一周的时候,微信上的一个朋友告诉我她想领红包时候发现我放的二维码已经失效了。虽然一直还没有进一分钱,但是这个朋友的反应让我觉得这个事是有希望的,至少还是有人会惦记外卖红包的,马上又去换了新的二维码继续推。然后第二天又告诉我二维码失效了,加上自己使用的是个人号而不是企业号,每天主动推送消息的次数有限,而且会被折叠隐藏起来,这时就有一点心灰意冷了。

+

继续换上新的二维码默默期待,玩了不到两周赚了不到 10 块钱的样子,后面又失效了两次后就放弃了,但是这一小段时间的实践让我验证了闲鱼引流的可行性。

+

瞎翻生财帖子的时候发现了卡券回收,一个可以靠信息差赚取认知以外的收入,心想简单转变一下就可以卖各个视频平台的会员卡啊。马上去闲鱼修改链接为腾讯视频、爱奇艺会员卡,发现不允许上架这一类的商品,那就只能靠图片去传递信息了。图片怎么做?石墨文档甩张图片、调一下字体、上个色,截一张图就是一张介绍图片。

+
+
+

这下有效果了,第一天就有人来咨询会员卡怎么卖的了,但是好几天都只是有人咨询却并没有成交的客户,而且每天都只是零零散散的 10 个人左右咨询。想起来在生财精华帖看到过需要刷单,闲鱼成功交易的商品再次上架会提高权重,每天编辑重新发布也会获得更多的推荐量。

+

找了三个朋友刷了其中一个爱奇艺链接的单,见效非常的快,当天晚上 9 点多就开始不停的有人咨询,我一直回复到了 12 点多,忘记那天赚了多少钱了,大概是一块鸡排的钱吧。从第二天开始就不断的有人来咨询,已经多到我回复不过来了,因为我还有别的工作要做。

+
+

晚上回家就设置了闲鱼的自动回复,将之前的外卖话术转变一下引导加我的微信,虽然流量一直都还不错但是却没有一个加我微信的,于是又改为自己手动回复了。连续这样玩了好几天真心觉得累,因为中间的利润太低了,稍微一提高人家就不买了。于是又去找成本更低的货源,总算找到另外一个靠谱且更低价的平台,每天可以稳定的进来一顿早饭钱,但却一直没有增长。

+

注意到进来咨询的人大致可以分为两类,一种是给家里长辈买电视版会员的群体,针对这种群体我就狂推年卡,因为年卡是我可控范围内能将彼此利益最大化的商品,这个没什么好说的。另外一种是只要周卡的学生群体,周卡基本没有什么利润,于是稍微转变了一下思路,对于这类群体就问对方是想要看哪个剧?引导对方加微信,我直接帮他找资源,然后随意给我发个红包就行了。

+

那段时间进来的很多人都是想看「使徒行者 3」,我一直没看过这部剧也不知道它讲的是什么,但是人家需要就对了,恰好我知道有个影视资源平台更新的速度非常快,看使徒 3 不仅要会员还得付费点播,把影视资源平台直接发给对方,都会发 3-15 元的红包给我。

+

后面又尝试过设置自动回复引导加微信,但是一旦设置自动回复效果就没了,使徒3 完了之后效果也有比较明显的下跌,我自己玩了不到两个月时间,如果一直玩下去差不多每月能吃一顿火锅,我个人觉得投入产出比太低了就没再做了。

+

目前微信上还剩一个非常信任我的客户,上来就简单粗暴的给我转钱,服务他到变成了一种小乐趣。2 月份发现这种视频会员需求进来的人直接推外卖 CPS 也可以,当时加了差不多 100 个微信,距离停止视频会员项目已经快 3 个月了我才在朋友圈发了几天饿了么的二维码,还是有效果的。

+
+
+

在抖音直营中心工作的朋友告诉我,在投放广告按原有方式定位不到人群时,可以通过设置其它兴趣来定位人群,比如喜欢看可爱宠物视频的人群可能也很喜欢买衣服,喜欢刷剧的人极有可能也喜欢点外卖、吃零食,「外卖淘客」这段时间很火,给各位提供一个引流的思路,也欢迎你加我微信我们一起交流相关内容。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/umBdDP7Ll/index.html b/umBdDP7Ll/index.html new file mode 100644 index 00000000..96643e2d --- /dev/null +++ b/umBdDP7Ll/index.html @@ -0,0 +1,1168 @@ + + + + + + + + C | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + C +
+ + + + +
+

+ + 二叉树的前序、中序、后序、层序遍历 + +

+ +
+ + + + +
+ +
+ +参考内容: +五分钟让你彻底理解二叉树的非递归遍历 +Python实现二叉树的非递归遍历 +二叉树遍历——深度优先(前中后序)+广度优先(层序遍历) + +构造二叉树 +定义二叉树结构如下 +struct node +{ + int data; + node *left; + node *right; +}; + +构造如下形态二叉树 + +node *init_tree() +{ + node *node1 = (node *)malloc(sizeof(node)); + node *node2 = (node *)malloc(sizeof(node)); + node *node3 = (node *)malloc(sizeof(node)); + node *node4 = (node *)malloc(sizeof(node)); + node *node5 = (node *)malloc(sizeof(node)); + node *node6 = (node *)malloc(sizeof(node)); + node *node7 = (node *)malloc(sizeof(node)); + node *node8 = (node *)malloc(sizeof(node)); + + node1-&gt;data = 1; + node2-&gt;data = 2; + node3-&gt;data = 3; + node4-&gt;data = 4; + node5-&gt;data = 5; + node6-&gt;data = 6; + node7-&gt;data = 7; + node8-&gt;data = 8; + + node1-&gt;left = node2; + node1-&gt;right = node3; + + node2-&gt;left = node4; + node2-&gt;right = node5; + + node3-&gt;right = node6; + + node5-&gt;left = node7; + node5-&gt;right= node8; + + return node1; +} + +前序遍历(递归) +前序遍历顺序为根左右。要遍历整个二叉树我们就需要遍历二叉树的每一个子树,对于任何一个子树它的遍历方式均为根左右顺序遍历。即所有子问题均与父问题除规模大小不同外,其余均相同。所以可以采用递归方式实现前序遍历。 +// 前序遍历 根左右 +void pre_order_traversal(node *root) +{ + if(root) { + cout&lt;&lt;root-&gt;data&lt;&lt;&quot; &quot;; + pre_order_traversal(root-&gt;left); + pre_order_traversal(root-&gt;right); + } +} + +遍历结果为:1 2 4 5 7 8 3 6 +中序遍历(递归) +中序遍历顺序为左根右。其与前序遍历仅顺序不同,其余均相同。 +// 中序遍历 左根右 +void in_order_traversal(node *root) +{ + if(root) { + in_order_traversal(root-&gt;left); + cout&lt;&lt;root-&gt;data&lt;&lt;&quot; &quot;; + in_order_traversal(root-&gt;right); + } +} + +遍历结果为:4 2 7 5 8 1 3 6 +后序遍历(递归) +后序遍历顺序为左右根。其与前序、中序遍历仅顺序不同,其余均相同。 +// 后序遍历 左右根 +void post_order_traversal(node *root) +{ + if(root) { + post_order_traversal(root-&gt;left); + post_order_traversal(root-&gt;right); + cout&lt;&lt;root-&gt;data&lt;&lt;&quot; &quot;; + } +} + +遍历结果为:4 7 8 5 2 6 3 1 +前序遍历方法一(非递归) +因为递归实际上是由系统帮我们进行压栈,所以理论上所有递归算法都可以改为循环+栈实现,那么我们先照着上述前序遍历的样子修改为循环+栈的形态。需要注意的是由于栈先进后出的特性,为了保证左孩子在右孩子前被访问,所以应该先右孩子入栈,再左孩子入栈。 +// 前序遍历 根左右 +void pre_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + s.push(root); + + while(!s.empty()) { + + node *cur = s.top(); + s.pop(); + + if(cur) { + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + s.push(cur-&gt;right); + s.push(cur-&gt;left); + } + } +} + +遍历结果为:1 2 4 5 7 8 3 6 +前序遍历方法二(非递归) +现在我们换一种思路来实现前序非递归遍历,仔细观察前序遍历的递归调用过程。 + +先把从根结点开始的所有左子树放入栈中; +弹出栈顶元素 +如果栈顶元素有右子树,那么右子树入栈 +重复上述过程直到栈为空 + +因此我们可以写出遍历代码 +// 前序遍历 根左右 +void pre_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + node *cur = root; + + while(cur || !s.empty()) { + // 将左子树全部入栈 + while(cur) { + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + s.push(cur); + cur = cur-&gt;left; + } + + if(!s.empty()) { + cur = s.top(); + s.pop(); + cur = cur-&gt;right; + } + } +} + +遍历结果为:1 2 4 5 7 8 3 6 +中序遍历(非递归) +有了前面的基础,我们再来考虑中序遍历,会发现中序遍历与前序遍历只是打印结点的位置不一样。前序遍历是在结点入栈时打印,中序遍历只需要替换为在结点出栈时打印即可。 +// 中序遍历 左根右 +void in_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + node *cur = root; + + while(cur || !s.empty()) { + // 将左子树全部入栈 + while(cur) { + s.push(cur); + cur = cur-&gt;left; + } + + if(!s.empty()) { + cur = s.top(); + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + s.pop(); + cur = cur-&gt;right; + } + } +} + +遍历结果为:4 2 7 5 8 1 3 6 +后序遍历方法一(非递归) +后序遍历相对来说显得更加复杂了。在前序和中序遍历中,只要左子树处理完毕实际上栈顶元素就可以出栈了,但后序遍历需要把左子树和右子树都处理完毕才能出栈,显然我们需要某种方法记录遍历的过程。 +实际上我们只需要记录下遍历的前一个结点就能解决问题,因为通过前一个结点我们可以做如下判断: + +如果前一个结点是当前结点的右子树,那么说明右子树已经遍历完毕可以出栈了 +如果前一个结点是当前结点的左子树而且当前结点没有右子树,那么说明可以出栈了 +如果当前结点即没有左子树也没有右子树,即为叶子结点,那么说明可以出栈了 + +若不属于上述情况,则依次将当前结点的右孩子和做孩子入栈,这样就能保证每次取栈顶元素时,左孩子都在右孩子前面被访问,左孩子和右孩子都在父结点前面被访问。 +// 后序遍历 左右根 +void post_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + node *pre = NULL; + node *cur = root; + + s.push(cur); + + while(!s.empty()) { + cur = s.top(); + // 叶子结点 + if((!cur-&gt;left &amp;&amp; !cur-&gt;right) // 叶子结点 + || pre == cur-&gt;right // 前一个结点为当前结点右子树 + || (pre == cur-&gt;left &amp;&amp; !cur-&gt;right)) { // 前一个结点为当前结点左子树,且没有右子树 + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + pre = cur; + s.pop(); + } else { + if(cur-&gt;right) + s.push(cur-&gt;right); + + if(cur-&gt;left) + s.push(cur-&gt;left); + } + } +} + +遍历结果为:4 7 8 5 2 6 3 1 +后序遍历方法二(非递归) +后序遍历的顺序是左右根,如果把这个顺序倒过来就是根右左,是不是发现和前序遍历很像?那么我只需要按照根右左的方式遍历完,然后将遍历结果掉一个个儿就可以,而栈就具备掉个儿的功能,因此可写出如下代码。 +// 后序遍历 左右根 +void post_order_traversal(node *root) +{ + stack&lt;node *&gt; s; + stack&lt;int&gt; ans; + node *cur = root; + + while(cur || !s.empty()) { + // 将左子树全部入栈 + while(cur) { + ans.push(cur-&gt;data); + s.push(cur); + cur = cur-&gt;right; + } + + if(!s.empty()) { + cur = s.top(); + s.pop(); + cur = cur-&gt;left; + } + } + + while(!ans.empty()) { + cout&lt;&lt;ans.top()&lt;&lt;&quot; &quot;; + ans.pop(); + } +} + +遍历结果为:4 7 8 5 2 6 3 1 +层序遍历 +层序遍历即广度优先遍历,使用队列即可实现。 +// 层序遍历 +void breadth_first_order_traversal(node *root) +{ + queue&lt;node *&gt; q; + q.push(root); + while(!q.empty()){ + node *cur = q.front(); + q.pop(); + if(cur){ + cout&lt;&lt;cur-&gt;data&lt;&lt;&quot; &quot;; + q.push(cur-&gt;left); + q.push(cur-&gt;right); + } + } +} + +遍历结果为:1 2 3 4 5 6 7 8 + +
+ + Read More ~ +
+
+
+ +
+

+ + 预处理器 + +

+ +
+ + + + +
+ +
+ C 语言的编译需要经过很多步骤,其中第一个步骤称为预处理阶段。这个阶段的主要任务包括删除注释、插入被#include指令包含的文件的内容、定义和替换#define指令定义的符号以及确定代码的部分内容是否应该跟绝一些条件编译指令进行编译。 +#define +#define指令就是为数值命名一个符号。比如#define name stuff指令,有了它之后,每当有符号name出现在这条指令后面时,预处理器就会把它替换成stuff,比如下面几个例子: +// 为关键字 register 创建了一个简短的别名 +#define reg register +// 声明了一个更具描述性的符号用来替代实现无限循环的 for 语句 +#define do_forever for(;;) +// 定义了一个简短记法,在 switch 语句中使用,可以自动把一个 break 放在每个 case 之前 +#define CASE break;case + +当然如果定义中的stuff非常长,那么也可以将它分成几行,除了最后一行之外,每行的末尾都需要加一个反斜杠。比如: +#define log_debug printf(&quot;File[%s]line[%d]:&quot; \ + &quot; x=[%d], y=[%d], z=[%d]&quot;, \ + __FILE__, __LINE__, \ + x, y, z) + +// 那么我们将可以很方便的插入一条调试语句打印 +x *= 2; +y += x; +z = x * y; +log_debug; + +很容易就发现上面的log_debug定义无法进行泛化,当然设计者也考虑到了这个问题,所以#define机制包括了一个规定,即允许把参数替换到文本中,这种实现一般称为宏,其声明方式如下: +define name(parameter-list) stuff + +需要注意的是parameter-list是一个由逗号分隔的符号列表,他们可能出现在stuff中。参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。下面我们看一个具体的列子,以此了解宏定义的机制,并将它逐步优化改进: +#define SQUARE(x) x * x + +// 使用 +SQUARE(5) +// 效果:5 * 5 + +考虑一下下面的代码段: +a = 5; +printf(&quot;%d\n&quot;, SQUARE(a + 1)); + +乍一看觉得这段代码将打印36这个值。但实际它却会打印11,我们仔细观察一下被替换的宏文本,即参数x被文本a + 1替换: +a = 5; +printf(&quot;%d\n&quot;, a + 1 * a + 1); + +很容易想到对参数 x 加一个括号解决上述问题,即: +#define SQUARE(x) (x) * (x) + +// 上述打印将会被替换为 +a = 5; +printf(&quot;%d\n&quot;, (a + 1) * (a + 1)); + +类似的我们可以再定义一个DOUBLE宏,即: +#define DOUBLE(x) (x) + (x) + +但是考虑下面的使用方式: +a = 5; +printf(&quot;%d\n&quot;, 10 * DOUBLE(5)); + +看上去它应该打印的结果是100,但事实上它打印的是55,我们再通过宏替换产生的文本观察问题: +printf(&quot;%d\n&quot;, 10 * (5) + (5)); + +所以我们需要在整个表达式两边加上一对括号。所有用于对数值表达式进行求值的宏定义都应该使用下面这种方式加上括号,避免在使用宏时,由于参数中的操作符或邻近的操作符之间不可预料的相互作用。 +#define DOUBLE(x) ((x) + (x)) + +宏与函数 +宏非常频繁的用于执行简单的计算,比如在两个表达式中寻找其中较大(小)的一个: +#define MAX(a, b) ((a) &gt; (b) ? (a) : (b)) + +那么为什么不使用函数来完成这个任务呢?首先用于调用和从函数返回的代码很可能比实际执行这个小型计算工作的代码更大,所以使用宏比使用函数在程序的规模和速度方面都更胜一筹。 +更为重要的是函数必须声明为一种特定的类型,所以它只能在类型合适的表达式上使用。但是上面的这个宏可以用于整型、长整型、单浮点型、双浮点型以及任何其它可以使用&gt;操作符比较值大小的类型,即宏与类型无关。 +当然宏也有它的不利之处,因为每次在使用宏时,一份宏定义代码的拷贝都将插入到程序中,除非宏的定义非常短,否则使用宏将会大幅增加程序的长度。 +也有一些任务根本无法使用函数实现,比如下面这个宏的第二个参数是一种类型,它无法作为函数参数进行传递。 +#define MALLOC(n, type) ((type *)malloc((n) * sizeof(type))) + +当宏参数在宏定义中出现的次数超过一次时,如果这个参数具有副作用,那么当使用这个宏的时候就可能出现危险,导致一些不可预料的后果。比如x++就是一个具有副作用的表达式,它会改变x的值,直接会导致下面的代码段出现不可预知的后果: +#define MAX(a, b) ((a) &gt; (b) &gt; (a) : (b)) + +x = 5; +y = 8; +z = MAX(x++, y++); +// z = ((x++) &gt; (y++) &gt; (x++) : (y++)) + + + + +属性 +#define 宏 +函数 + + + + +代码长度 +每次使用时,宏代码都被插入到程序中。除了非常小的宏志伟,程序的长度将大幅度增长 +函数代码只出现于一个地方,每次使用这个函数时,都调用那个地方的同一份代码 + + +执行速度 +更快 +存在函数调用/返回的额外开销 + + +操作符优先级 +宏参数的求值是在所有周围表达式的上下文环境里,除非它们加上括号,否则邻近操作符的优先级可能回产生不可预料的结果 +函数参数只在函数调用时求值一次,它的结果传递给参数。表达式的求值结果更容易预测 + + +参数求值 +参数每次用于宏定义时,它们都将重新求值。由于多次求值,具有副作用的参数可能会产生不可预料的结果 +参数在函数被调用前求值一次。在函数中多次使用参数并不会导致多种求值过程。参数的副作用不会造成任何特殊的问题 + + +参数类型 +宏与类型无关。只要对参数的操作是合法的,它可以使用于任何参数类型 +函数的参数是与类型有关的。如果参数的类型不同,就需要使用不同的函数,即使它们执行的任务时相同的 + + + +文件包含 +我们知道#include指令可以使另一个文件的内容被编译,就像它实际出现于#include指令出现的位置一样。这种替换的执行方式很简单:预处理器删除这条指令,并用包含头文件的内容取而代之。这样一个头文件如果被包含到 10 个源文件中,它实际上被编译了 10 次。 +基于这种替换的方式,当出现嵌套#include文件被多次包含时,就会出现问题: +#include &quot;a.h&quot; +#include &quot;b.h&quot; + +// 如果 a.h 和 b.h 中都包含一个 #include x.h +// 那么 x.h 在此处就出现了两次 + +这种多重包含在绝大多数情况下出现于大型程序中,它往往需要很多头文件,所以要发现这种情况并不容易。但是我们可以使用条件编译来解决这个问题: +#ifndef _HEADER_NAME_H_ +#define _HEADER_NAME_H_ + +/* +* All the stuff that you want in the header file +*/ + +#endif + + +
+ + Read More ~ +
+
+
+ +
+

+ + C 语言拾遗 + +

+ +
+ + + + +
+ +
+ +约定:本文所说的标准均为 ANSI (C89) 标准 + +三字母词 +标准定义了几个三字母词,三字母词就是三个字符的序列,合起来表示另一个字符。三字母词使得 C 环境可以在某些缺少一些必需字符的字符集上实现,它使用两个问号开头再尾随一个字符,这种形式一般不会出现在其它表达形式中,这样就不容易引起误解了,下面是一些三字母词的对应关系: +??( [ +??) ] +??! | +??&lt; { +??&gt; } +??' ^ +??= # +??/ \ +??- ~ + +所以在一些特殊情况下可能出现下面的情况,希望你不要被意外到。 +printf(&quot;Delete file (are you really sure??): &quot;); + +// result is: Delete file (are you really sure]: + +字符 +直接操作字符会降低代码的可移植性,应该尽可能使用库函数完成。比如下面的代码试图测试ch是否为一个大写字符,它在使用ASCII字符集的机器上能够运行,但是在使用EBCDIC字符集的机器上将会失败。 +if( ch &gt;= 'A' &amp;&amp; ch &lt;= 'Z') + +使用if(isupper(ch))语句则能保证无论在哪种机器上都能正常运行。 +字符串比较 +库函数提供了int strcmp(const char *s1, const char *s2)函数用于比较两个字符串是否相等,需要注意的是在标准中并没有规定用于提示不相等的具体值。它只是说如果第 1 个字符串大于第 2 个字符串就返回一个大于零的值,如果第 1 个字符串小于第 2 个字符串就返回一个小于零的值。一个常见的错误是以为返回值是1和-1,分别代表大于和小于。 +初学者常常会编写下面的表达式。认为如果两个字符串相等,那么它返回的结果将为真。但是这个结果恰好相反,两个字符串相等的情况下返回值是零(假)。 +if(strcmp(a, b)) + +strlen +strlen的返回值是一个size_t类型的值,这个类型是在头文件stddef.h中定义的,它是一个无符号整数类型,所以会导致下面表达式的条件永远为真。 +if(strlen(x) - strlen(y) &gt;= 0) { + // do something +} + +第二点需要注意的是strlen的返回值没有计算\0的长度,所以下面的代码在一些检查严格或老版本的编译器中会报错,其原因在于少分配了一个存储单位。 +// 假设 str 是一个字符串 +char *cpy = malloc(strlen(str)); +strcpy(cpy, str); + +// 正确写法应为 +char *cpy = malloc(strlen(str) + 1); +strcpy(cpy, str); + +赋值截断 +表达式a = x = y + 3;中x和a被赋予相同值的说法是错误的,因为如果x是一个字符型变量,那么y+3的值就会被截去一段,以便容纳于字符型的变量中,那么a所赋的值就是这个被截短后的值。下面也是一个非常常见的错误。 +char ch; +// do something +while((ch = getchar()) != EOF) { + // do something +} + +EOF所需要的位数比字符型值所能提供的位数要多,这也是getchar返回一个整型值而不是字符值的原因。例子中把getchar的返回值存储于字符型变量会导致被截短,然后再把这个被截短的值提升为整型与EOF进行比较,在某些机器的特定场景下就会导致问题。比如在使用有符号字符集的机器上,如果读取了一个的值为\377的字节,上述循环就将终止,因为这个值截短再提升之后与EOF相等。而当这段代码在使用无符号字符集的机器上运行时,这个循环将永远不会终止。 +指针与数组 +因为数组和指针都具有指针值,都可以进行间接访问和下标操作,所以很多同学都想当然的将它们认为是一样的,为了说明它们是不相等的,我们可以考虑下面的两个声明: +int a[5]; +int *b; + +声明一个数组时,编译器将根据声明所指定的元素数量为数组保留空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为任何指向现有的内存空间。所以在上述声明之后,表达式*a是完全合法的,但是表达式*b将访问内存中某个不确定的位置,或者导致程序终止。 +硬件操作 +我们知道其实表示就是内存中一个地址,所以理论上*100 = 25是一种可行的操作,即让内存中位置为100的地方存储25。但实际上这条语句是非法的,因为字面值100的类型是整型,而间接访问操作只能用于指针类型表达式,所以合法的写法必须使用强制转换,即*(int *)100 = 25。 +需要说明的是使用这种技巧的机会是绝无仅有的,只有偶尔需要通过地址访问内存中某个特定的位置才可使用,它并不是访问某个变量,而是访问硬件本身。比如在某些机器上,操作系统需要与输入输出设备控制器通信,启动 I/O 操作并从前面的操作中获得结果,此时这些地址是预先已知的。 ++= 与 ?: 操作符 +我们在这里讨论一下+=操作符,它的用法为a += expr,读作把 expr 加到 a,其实际功能相当于表达式a = a + expr的作用,唯一不同的是+=操作符的左操作数a只会求值一次。可能到目前为止没有感觉到设计两种增加一个变量值的方法有什么意义?下面给出代码示例: +// 形式 1 +a[ 2 * (y - 6*f(x)) ] = a[ 2 * (y - 6*f(x)) ] + 1; + +// 形式 2 +a[ 2 * (y - 6*f(x)) ] += 1; + +在第一种形式中,用于选择增值位置的表达式必须书写两次,一次在赋值号左边,一次在赋值号右边。由于编译器无法知道函数f是否具有副作用,所以它必须两次计算下标表达式的值,而第二种形式的效率会更高,因为下标表达式的值只会被计算一次。同时第二种形式也减少了代码书写错误的概率。 +同理三目运算符也可以起到类似的效果。 +// 形式 1 +if(a &gt; 5) { + b[ 2 * c + d * (e / 5) ] = 3; +} else { + b[ 2 * c + d * (e / 5) ] = -20; +} + +// 形式 2 +b[ 2 * c + d * (e / 5) ] = a &gt; 5 ? 3 : -20; + +逗号操作符 +逗号操作符可以将多个表达式分隔开来。这些表达式自左向右逐个进行求值,整个表达式的值就是最后那个表达式的值。例如: +if(b + 1, c / 2, d &gt; 0) { // do something} + +当然,正常人不会编写这样的代码,因为对前两个表达式的求值毫无意义,它们的值只是被简单的丢弃了。但是我们可以看看下面的代码: +// 形式 1 +a = get_value(); +count_value(a); +while(a &gt; 0) { + // do something + a = get_value(); + count_value(a); +} + +// 形式 2 +while(a = get_value(), count_value(), a &gt; 0) { + // do something +} + +指针 +int* a, b, c; + +人们会很自然的认为上述语句是把所有三个变量都声明为指向整型的指针,但事实上并非如此,星号实际上只是表达式*a的一部分,只对这个标识符有作用。如果要声明三个指针,那么应该使用下面的形式进行初始化。 +int *a, *b, *c; + +在声明指针变量时可以为它指定初始值,比如下面的代码段,它声明了一个指针,并用一个字符串常量对其进行初始化。 +char *msg = &quot;Hello World!&quot;; + +需要注意的是,这种类型的声明会让人很容易误解它的意思,看起来初始值似乎是赋给表达式*msg的,但实际上它是赋值给msg本身的,也就是上述声明实际形式如下: +char *msg; +msg = &quot;Hello World!&quot;; + +指针常量: int *pi中pi是一个普通的指向整型的指针, 而变量int const *pci则是一个指向整型常量的指针,你可以修改指针的值,但是不能修改它所指向的值。相比之下int * const cpi则声明cpi为一个指向整型的常量指针。此时指针是常量,它的值无法修改,但是可以修改它所指向的整型的值。在int const * const cpci中,无论是指针本身还是它所指向的值都是常量,无法修改。 +枚举类型 +枚举(enumerated) 类型就是指它的的值为符号常量而不是字面值的类型,比如下面的语句声明了Jar_Type类型: +enum Jar_Type { + CUP, + PINT, + QUART, + HALF_GALLON, + GALLON +}; + +需要注意的是,枚举类型实际上是以整型方式存储的,代码段中的符号名实际上都是整型值。在这里CUP的值是0,PINT的值是1,依次类推。 +在适当的时候,可以为这些符号名指定特定的值整型值。并且只对部分符号名进行赋值也是合法的,如果某个符号名没有显示的赋值,那么它的值就比前面一个符号名的值大 1。 +enum Jar_Type { + CUP = 8, + PINT = 16, + QUART = 32, + HALF_GALLON = 64, + GALLON = 128 +}; + + +符号名被当作整型处理,这意味着可以把HALF_GALLON这样的值赋给任何整型变量,但是在编程活动中应该避免这种方式使用枚举,因为这样会削弱它们的含义。 + +typedef 与 define +在实际应用过程中应该使用typedef而不是#define来创建新的类型名,因为#define无法正确的处理指针类型,比如下面的代码段正确的声明了a,但是b却被声明为了一个字符。 +#define ptr_to_char char * +ptr_to_char a, b; + +联合(union) +联合看起来很像结构体,与结构体不同的是联合的所有成员共用同一块内存,所以在同一时刻联合中的有效成员永远只有一个。我们可以看下面一个例子,当一个variable类型的变量被创建时,解释器就创建一个这样的结构并记录变量类型。然后根据变量类型,把变量的值存储在这三个值字段的其中一个。 +struct variable { + enum { INT, FLOAT, STRING } type; + int int_val; + float float_val; + char *str_val; +} + +不难发现上述结构的低效之处在于它所使用的内存,每个variable结构存在两个未使用的值字段,造成了内存空间上的不少浪费。使用联合就可以减少这种空间上的浪费,它把这三个值字段的每一个都存储在同一个内存位置。我们知道这三个字段并不会冲突,因为每个变量只可能具有一种类型,所以在具体的某一时刻,联合的这几个字段只有一个被使用。 +struct variable { + enum { INT, FLOAT, STRING } type; + union { + int i; + float f; + char *s; + } val; +} + +现在,对于整型变量,我们只需要将type字段设为INT,并把整型值存储于val.i即可。如果联合中各成员的长度不一样,联合的长度就是它最长成员的长度。 +联合的变量也可以被初始化,但是这个初始值必须是联合第 1 个成员的类型,而且它必须位于一对花括号里面。比如: +union { + int a; + float b; + chat c[4]; +} x = { 5 }; + +结构体 +在实际编程活动中,存在链表、二叉树等结点自引用的情况,那么结构体的自引用如何编写呢? +struct node { + int data; + struct node next; +} + +上述写法是非法的,因为成员next是一个完整的结构,其内部还将包含自己的成员next,这第 2 个成员又是另一个完整结构,它还将包含自己的成员next,如此重复下去将永无止境。正确的自引用写法如下: +struct node { + int data; + struct node *next; +} + +我们需要注意下面的这个陷阱: +/* +错误写法:因为类型名 node_t 直到声明末尾才定义 +所以在结构中声明的内部 node_t 尚未定义 +*/ +typedef struct { + int data; + node_t *next; +} node_t; + +// 正确写法 +typedef struct node_tag { + int data; + struct node_tag *next; +} node_t; + +编译器在实际分配时会按照结构体成员列表的顺序一个接一个的分配内存,并且只有当存储成员需要满足正确的边界对齐要求时,成员之间可能会出现用于填充的额外内存空间。 + +```c +struct align { + char a; + int b; + char c; +} + +如果某个机器的整型值长度为 4 个字节,并且它的起始存储位置必须能够被 4 整除,那么这个结构在内存中的存储将是下面这种形式 + + + +a + + + +b +b +b +b +c + + + + + + +我们可以通过改变成员列表的声明顺序,让那些对边界要求严格的成员首先出现,对边界要求弱的成员最后出现,这样可以减少因为边界对齐而带来的空间损失。 +struct align { + int b; + char a; + char c; +} + + + + +b +b +b +b +a +c + + + +当程序创建几百个甚至几千个结构时,减少内存浪费的要求就比程序的可读性更为急迫。我们可以使用sizeof操作符来得出一个结构的整体长度。如果必须要确定结构某个成员的实际位置,则可以使用offsetof(type, member)宏,例如: +offset(struct align, b); + +一句话 +标识符:标识符就是变量、函数、类型等的名字,标识符的长度没有限制,但是 ANSI 标准允许编译器忽略第 31 个字符以后的字符,并且允许编译器对用于表示外部名字(由链接器操作的名字)的标识符进行限制,只识别前 6 位不区分大小写的字符。 +注释:代码中所有的注释都会被预处理器拿掉,取而代之的是一个空格。因此,注释可以出现在任何空格可以出现的地方。 +类型:C 语言中仅有 4 种基本数据类型,即整型、浮点型、指针和聚合类型(数组、结构等),所有其它的类型都是从这 4 中基本类型的某种组合派生而来。 +类型长度:标准只规定了short int至少是 16 位,long int至少是 32 位,至于缺省的int是多少位则直接由编译器设计者决定。并且标准也没有规定这 2 个值必须不一样。如果某种机器的环境字长是 32 位,而且也没有什么指令能够更有效的处理更短的整型值,那它很可能把这 3 个整型值都设定为 32 位。 +位域:基于 int 位域被当作有符号还是无符号数、位域成员的内存是从左向右还是从右向左分配、运行在 32 位整数的位域声明可能在 16 位机器无法运行等原因,注重可移植性的程序应该避免使用位域。 +结构与指针:什么时候应该向函数传递一个结构而不是一个指向结构的指针呢?很少有这种情况。只有当一个结构特别小(长度和指针相同或更小)时,结构传递方案的效率才不会输给指针传递方案。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 为什么宏定义要使用 do {...} while (0) ? + +

+ +
+ + + + +
+ +
+ +参考内容: +do {…} while (0) in macros +do {...} while (0) 在宏定义中的作用 +do{}while(0)只执行一次无意义?你可能真的没理解 + +近期参与的项目属于嵌入式软件领域,自然而然就得用 C 语言进行开发,开发过程中发现引入的第三方库里面有一些奇奇怪怪的写法,比如大佬们都喜欢使用do {...} while(0)的宏定义,在 Stack Overflow 上也有人提出了这个问题。之前从事 Linux 内核开发的谷歌大佬 Robert Love 给出了如下的解释: + +do {…} while(0) is the only construct in C that lets you define macros that always work the same way, so that a semicolon after your macro always has the same effect, regardless of how the macro is used (with particularly emphasis on the issue of nesting the macro in an if without curly-brackets). +do {...} while(0) 在 C 中是唯一的构造程序,让你定义的宏总是以相同的方式工作,这样不管怎么使用宏(尤其在没有用大括号包围调用宏的语句),宏后面的分号也是相同的效果。 + +这句话读起来有些拗口,只觉得大佬的表述曲高和寡,翻译翻译就是:使用do {...} while(0)构造后的宏定义不会受到大括号、分号等影响,总能按照我们期望的方式调用运行。下面我们举几个实际的例子来加深理解。 +// 现有如下宏定义 +#define foo(x) bar(x); baz(x) + +// 1. 可以这样调用 +foo(wolf); + +// 上述调用将会被展开为下列代码,完美没有问题 +bar(wolf); baz(wolf); + + +// 2. 如果我们像下面这样调用呢? +if (!feral) + foo(wolf); + +// 上述调用将会被展开为下列代码,很明显这是错误的,并且是很容易犯的错误 +if (!feral) + bar(wolf); +baz(wolf); + +为了避免上面例子所出现的问题,我们可以考虑使用{ }直接把整个宏包裹起来,如下所示: +// 修改宏定义为 +#define foo(x) { bar(x); baz(x) } + +// 3. 例 1 调用 +if (!feral) + foo(wolf); + +// 现在上述调用将展开为下列代码 +if (!feral) { + bar(wolf); + baz(wolf); +}; + + +// 4. 我们再考虑一下如下调用呢 +if (!feral) + foo(wolf); +else + bin(wolf); + +// 上述调用将会被展开为下列代码,很明显又出现了语法错误 +if (!feral) { + bar(wolf); + baz(wolf); +}; +else + bin(wolf); + +我们继续考虑比使用{ }直接把整个宏包裹起来更好的方法,即本文标题所说的使用do {...} while (0),即上述宏将定义为如下形式。 +// 终极版宏定义 +#define foo(x) do { bar(x); baz(x) } while (0) + +// 5. 例 4 调用 +if (!feral) + foo(wolf); +else + bin(wolf); + +// 现在上述调用将展开为下列形式,很完美 +if (!feral) + do { bar(wolf); baz(wolf) } while (0); +else + bin(wolf); + +do {...} while (0)除了在宏定义中可以发挥完美的作用外,在某些情况下还可以当作goto使用。因为goto不符合软件工程的结构化,并且容易使得代码晦涩难懂,所以很多公司都不倡导使用甚至禁止使用。那么我们可以使用do {...} while (0)来做同样的事情。 +#include &lt;stdio.h&gt; +#include &lt;stdlib.h&gt; +int main() +{ + char *str; + /* 最初的内存分配 */ + str = (char *) malloc(15); + if(str != NULL) + goto loop; + printf(&quot;hello world\n&quot;); +loop: + printf(&quot;malloc success\n&quot;); + return 0; +} + +上述代码我们可以修改为下列形式,使用do {...} while (0)将函数主体包裹起来,而break语句则替代了goto语句的作用,并且代码的可读性与可维护性都比上述goto方式更好。 +#include &lt;stdio.h&gt; +#include &lt;stdlib.h&gt; +int main() +{ + do{ + char *str; + /* 最初的内存分配 */ + str = (char *) malloc(15); + if(str != NULL) + break; + printf(&quot;hello world\n&quot;); + }while(0); + printf(&quot;malloc success\n&quot;); + return 0; +} + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/vnEfA3xxL/index.html b/vnEfA3xxL/index.html new file mode 100644 index 00000000..42097d4a --- /dev/null +++ b/vnEfA3xxL/index.html @@ -0,0 +1,820 @@ + + + + + + + + 嵌入式 | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + 嵌入式 +
+ + + + +
+

+ + linux-5.10.157 内核源码编译 + +

+ +
+ + + + +
+ +
+ +参考内容: +Linux内核开发_1_编译LInux内核 +编译linux内核报错:flex: not foundscripts +编译kernel5.14报错fatal error: openssl/opensslv.h +编译内核错误——*** 没有规则可制作目标“debian/canonical-certs.pem” +内核错误:BTF: .tmp_vmlinux.btf: pahole (pahole) is not available + +# 切换到 root 账户 +sudo su + +# 查看操作系统版本 +cat /etc/issue + +# 查看 Linux 内核版本 +cat /proc/version + +# 进入 root 账户目录 +cd /home/root + +# 下载 Linux 内核源码 +wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.157.tar.xz +# Linux 其它版本源码 https://www.kernel.org/ + +# xz 解压 +xz -d linux-5.10.157.tar.xz + +# tar 解压到 /usr/src/linux-5.10.157 目录下 +tar -xf linux-5.10.157.tar -C /usr/src/. + +# 进入源码目录 +cd /usr/src/linux-5.10.157 + +# 查看源码结构 +tree . -L 2 + +# 若没有 tree 命令,可以执行下面命令 +# apt-get install tree + +# 配置编译选项 +make menuconfig + +# 若没有 make,可以执行下面命令 +# apt-get install make + +# 若执行 make 后报错找不到 curses.h,可以执行下面命令 +# apt-get install libncurses5-dev + +# 若报错找不到 flex not found,可以执行下面两条命令 +# apt-get install flex +# apt-get install bison + +# 再次运行 make menuconfig 弹出图形化配置页面后 +# 若使用默认配置,则直接按两次 Esc 键退出即可 +# 此时会在当前目录下生成 .config 文件 + +# 编译 Linux 源码 +make bzImage -j4 + +# 在编译过程中若报错 fatal error: openssl/opensslv.h,可执行下面命令 +# apt-get install libssl-dev +# 若还出现同样的问题,可参考 https://blog.csdn.net/ComputerInBook/article/details/107380796 源码编译安装 openssl + +# 若出现「没有规则可制作目标“debian/canonical-certs.pem”」报错 +# 需要删除 .config 中相应的字段,总共有两处 +# 一处为 CONFIG_SYSTEM_TRUSTED_KEYS=&quot;debian/canonical-certs.pem&quot; +# 一处为 CONFIG_SYSTEM_REVOCATION_KEYS=&quot;debian/canonical-revoked-certs.pem&quot; + +vim .config +# 删除之后的样子如下(需要保留引号): +# 一处为 CONFIG_SYSTEM_TRUSTED_KEYS=&quot;&quot; +# 一处为 CONFIG_SYSTEM_REVOCATION_KEYS=&quot;&quot; + +# 若出现 BTF: .tmp_vmlinux.btf: pahole (pahole) is not available 错误,则执行下面命令 +# apt-get install dwarves + +# 若在过程中还出现其它问题,大多是因为缺少相关库导致的,直接用 apt-get install 即可 + + +
+ + Read More ~ +
+
+
+ +
+

+ + C 语言拾遗 + +

+ +
+ + + + +
+ +
+ +约定:本文所说的标准均为 ANSI (C89) 标准 + +三字母词 +标准定义了几个三字母词,三字母词就是三个字符的序列,合起来表示另一个字符。三字母词使得 C 环境可以在某些缺少一些必需字符的字符集上实现,它使用两个问号开头再尾随一个字符,这种形式一般不会出现在其它表达形式中,这样就不容易引起误解了,下面是一些三字母词的对应关系: +??( [ +??) ] +??! | +??&lt; { +??&gt; } +??' ^ +??= # +??/ \ +??- ~ + +所以在一些特殊情况下可能出现下面的情况,希望你不要被意外到。 +printf(&quot;Delete file (are you really sure??): &quot;); + +// result is: Delete file (are you really sure]: + +字符 +直接操作字符会降低代码的可移植性,应该尽可能使用库函数完成。比如下面的代码试图测试ch是否为一个大写字符,它在使用ASCII字符集的机器上能够运行,但是在使用EBCDIC字符集的机器上将会失败。 +if( ch &gt;= 'A' &amp;&amp; ch &lt;= 'Z') + +使用if(isupper(ch))语句则能保证无论在哪种机器上都能正常运行。 +字符串比较 +库函数提供了int strcmp(const char *s1, const char *s2)函数用于比较两个字符串是否相等,需要注意的是在标准中并没有规定用于提示不相等的具体值。它只是说如果第 1 个字符串大于第 2 个字符串就返回一个大于零的值,如果第 1 个字符串小于第 2 个字符串就返回一个小于零的值。一个常见的错误是以为返回值是1和-1,分别代表大于和小于。 +初学者常常会编写下面的表达式。认为如果两个字符串相等,那么它返回的结果将为真。但是这个结果恰好相反,两个字符串相等的情况下返回值是零(假)。 +if(strcmp(a, b)) + +strlen +strlen的返回值是一个size_t类型的值,这个类型是在头文件stddef.h中定义的,它是一个无符号整数类型,所以会导致下面表达式的条件永远为真。 +if(strlen(x) - strlen(y) &gt;= 0) { + // do something +} + +第二点需要注意的是strlen的返回值没有计算\0的长度,所以下面的代码在一些检查严格或老版本的编译器中会报错,其原因在于少分配了一个存储单位。 +// 假设 str 是一个字符串 +char *cpy = malloc(strlen(str)); +strcpy(cpy, str); + +// 正确写法应为 +char *cpy = malloc(strlen(str) + 1); +strcpy(cpy, str); + +赋值截断 +表达式a = x = y + 3;中x和a被赋予相同值的说法是错误的,因为如果x是一个字符型变量,那么y+3的值就会被截去一段,以便容纳于字符型的变量中,那么a所赋的值就是这个被截短后的值。下面也是一个非常常见的错误。 +char ch; +// do something +while((ch = getchar()) != EOF) { + // do something +} + +EOF所需要的位数比字符型值所能提供的位数要多,这也是getchar返回一个整型值而不是字符值的原因。例子中把getchar的返回值存储于字符型变量会导致被截短,然后再把这个被截短的值提升为整型与EOF进行比较,在某些机器的特定场景下就会导致问题。比如在使用有符号字符集的机器上,如果读取了一个的值为\377的字节,上述循环就将终止,因为这个值截短再提升之后与EOF相等。而当这段代码在使用无符号字符集的机器上运行时,这个循环将永远不会终止。 +指针与数组 +因为数组和指针都具有指针值,都可以进行间接访问和下标操作,所以很多同学都想当然的将它们认为是一样的,为了说明它们是不相等的,我们可以考虑下面的两个声明: +int a[5]; +int *b; + +声明一个数组时,编译器将根据声明所指定的元素数量为数组保留空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为任何指向现有的内存空间。所以在上述声明之后,表达式*a是完全合法的,但是表达式*b将访问内存中某个不确定的位置,或者导致程序终止。 +硬件操作 +我们知道其实表示就是内存中一个地址,所以理论上*100 = 25是一种可行的操作,即让内存中位置为100的地方存储25。但实际上这条语句是非法的,因为字面值100的类型是整型,而间接访问操作只能用于指针类型表达式,所以合法的写法必须使用强制转换,即*(int *)100 = 25。 +需要说明的是使用这种技巧的机会是绝无仅有的,只有偶尔需要通过地址访问内存中某个特定的位置才可使用,它并不是访问某个变量,而是访问硬件本身。比如在某些机器上,操作系统需要与输入输出设备控制器通信,启动 I/O 操作并从前面的操作中获得结果,此时这些地址是预先已知的。 ++= 与 ?: 操作符 +我们在这里讨论一下+=操作符,它的用法为a += expr,读作把 expr 加到 a,其实际功能相当于表达式a = a + expr的作用,唯一不同的是+=操作符的左操作数a只会求值一次。可能到目前为止没有感觉到设计两种增加一个变量值的方法有什么意义?下面给出代码示例: +// 形式 1 +a[ 2 * (y - 6*f(x)) ] = a[ 2 * (y - 6*f(x)) ] + 1; + +// 形式 2 +a[ 2 * (y - 6*f(x)) ] += 1; + +在第一种形式中,用于选择增值位置的表达式必须书写两次,一次在赋值号左边,一次在赋值号右边。由于编译器无法知道函数f是否具有副作用,所以它必须两次计算下标表达式的值,而第二种形式的效率会更高,因为下标表达式的值只会被计算一次。同时第二种形式也减少了代码书写错误的概率。 +同理三目运算符也可以起到类似的效果。 +// 形式 1 +if(a &gt; 5) { + b[ 2 * c + d * (e / 5) ] = 3; +} else { + b[ 2 * c + d * (e / 5) ] = -20; +} + +// 形式 2 +b[ 2 * c + d * (e / 5) ] = a &gt; 5 ? 3 : -20; + +逗号操作符 +逗号操作符可以将多个表达式分隔开来。这些表达式自左向右逐个进行求值,整个表达式的值就是最后那个表达式的值。例如: +if(b + 1, c / 2, d &gt; 0) { // do something} + +当然,正常人不会编写这样的代码,因为对前两个表达式的求值毫无意义,它们的值只是被简单的丢弃了。但是我们可以看看下面的代码: +// 形式 1 +a = get_value(); +count_value(a); +while(a &gt; 0) { + // do something + a = get_value(); + count_value(a); +} + +// 形式 2 +while(a = get_value(), count_value(), a &gt; 0) { + // do something +} + +指针 +int* a, b, c; + +人们会很自然的认为上述语句是把所有三个变量都声明为指向整型的指针,但事实上并非如此,星号实际上只是表达式*a的一部分,只对这个标识符有作用。如果要声明三个指针,那么应该使用下面的形式进行初始化。 +int *a, *b, *c; + +在声明指针变量时可以为它指定初始值,比如下面的代码段,它声明了一个指针,并用一个字符串常量对其进行初始化。 +char *msg = &quot;Hello World!&quot;; + +需要注意的是,这种类型的声明会让人很容易误解它的意思,看起来初始值似乎是赋给表达式*msg的,但实际上它是赋值给msg本身的,也就是上述声明实际形式如下: +char *msg; +msg = &quot;Hello World!&quot;; + +指针常量: int *pi中pi是一个普通的指向整型的指针, 而变量int const *pci则是一个指向整型常量的指针,你可以修改指针的值,但是不能修改它所指向的值。相比之下int * const cpi则声明cpi为一个指向整型的常量指针。此时指针是常量,它的值无法修改,但是可以修改它所指向的整型的值。在int const * const cpci中,无论是指针本身还是它所指向的值都是常量,无法修改。 +枚举类型 +枚举(enumerated) 类型就是指它的的值为符号常量而不是字面值的类型,比如下面的语句声明了Jar_Type类型: +enum Jar_Type { + CUP, + PINT, + QUART, + HALF_GALLON, + GALLON +}; + +需要注意的是,枚举类型实际上是以整型方式存储的,代码段中的符号名实际上都是整型值。在这里CUP的值是0,PINT的值是1,依次类推。 +在适当的时候,可以为这些符号名指定特定的值整型值。并且只对部分符号名进行赋值也是合法的,如果某个符号名没有显示的赋值,那么它的值就比前面一个符号名的值大 1。 +enum Jar_Type { + CUP = 8, + PINT = 16, + QUART = 32, + HALF_GALLON = 64, + GALLON = 128 +}; + + +符号名被当作整型处理,这意味着可以把HALF_GALLON这样的值赋给任何整型变量,但是在编程活动中应该避免这种方式使用枚举,因为这样会削弱它们的含义。 + +typedef 与 define +在实际应用过程中应该使用typedef而不是#define来创建新的类型名,因为#define无法正确的处理指针类型,比如下面的代码段正确的声明了a,但是b却被声明为了一个字符。 +#define ptr_to_char char * +ptr_to_char a, b; + +联合(union) +联合看起来很像结构体,与结构体不同的是联合的所有成员共用同一块内存,所以在同一时刻联合中的有效成员永远只有一个。我们可以看下面一个例子,当一个variable类型的变量被创建时,解释器就创建一个这样的结构并记录变量类型。然后根据变量类型,把变量的值存储在这三个值字段的其中一个。 +struct variable { + enum { INT, FLOAT, STRING } type; + int int_val; + float float_val; + char *str_val; +} + +不难发现上述结构的低效之处在于它所使用的内存,每个variable结构存在两个未使用的值字段,造成了内存空间上的不少浪费。使用联合就可以减少这种空间上的浪费,它把这三个值字段的每一个都存储在同一个内存位置。我们知道这三个字段并不会冲突,因为每个变量只可能具有一种类型,所以在具体的某一时刻,联合的这几个字段只有一个被使用。 +struct variable { + enum { INT, FLOAT, STRING } type; + union { + int i; + float f; + char *s; + } val; +} + +现在,对于整型变量,我们只需要将type字段设为INT,并把整型值存储于val.i即可。如果联合中各成员的长度不一样,联合的长度就是它最长成员的长度。 +联合的变量也可以被初始化,但是这个初始值必须是联合第 1 个成员的类型,而且它必须位于一对花括号里面。比如: +union { + int a; + float b; + chat c[4]; +} x = { 5 }; + +结构体 +在实际编程活动中,存在链表、二叉树等结点自引用的情况,那么结构体的自引用如何编写呢? +struct node { + int data; + struct node next; +} + +上述写法是非法的,因为成员next是一个完整的结构,其内部还将包含自己的成员next,这第 2 个成员又是另一个完整结构,它还将包含自己的成员next,如此重复下去将永无止境。正确的自引用写法如下: +struct node { + int data; + struct node *next; +} + +我们需要注意下面的这个陷阱: +/* +错误写法:因为类型名 node_t 直到声明末尾才定义 +所以在结构中声明的内部 node_t 尚未定义 +*/ +typedef struct { + int data; + node_t *next; +} node_t; + +// 正确写法 +typedef struct node_tag { + int data; + struct node_tag *next; +} node_t; + +编译器在实际分配时会按照结构体成员列表的顺序一个接一个的分配内存,并且只有当存储成员需要满足正确的边界对齐要求时,成员之间可能会出现用于填充的额外内存空间。 + +```c +struct align { + char a; + int b; + char c; +} + +如果某个机器的整型值长度为 4 个字节,并且它的起始存储位置必须能够被 4 整除,那么这个结构在内存中的存储将是下面这种形式 + + + +a + + + +b +b +b +b +c + + + + + + +我们可以通过改变成员列表的声明顺序,让那些对边界要求严格的成员首先出现,对边界要求弱的成员最后出现,这样可以减少因为边界对齐而带来的空间损失。 +struct align { + int b; + char a; + char c; +} + + + + +b +b +b +b +a +c + + + +当程序创建几百个甚至几千个结构时,减少内存浪费的要求就比程序的可读性更为急迫。我们可以使用sizeof操作符来得出一个结构的整体长度。如果必须要确定结构某个成员的实际位置,则可以使用offsetof(type, member)宏,例如: +offset(struct align, b); + +一句话 +标识符:标识符就是变量、函数、类型等的名字,标识符的长度没有限制,但是 ANSI 标准允许编译器忽略第 31 个字符以后的字符,并且允许编译器对用于表示外部名字(由链接器操作的名字)的标识符进行限制,只识别前 6 位不区分大小写的字符。 +注释:代码中所有的注释都会被预处理器拿掉,取而代之的是一个空格。因此,注释可以出现在任何空格可以出现的地方。 +类型:C 语言中仅有 4 种基本数据类型,即整型、浮点型、指针和聚合类型(数组、结构等),所有其它的类型都是从这 4 中基本类型的某种组合派生而来。 +类型长度:标准只规定了short int至少是 16 位,long int至少是 32 位,至于缺省的int是多少位则直接由编译器设计者决定。并且标准也没有规定这 2 个值必须不一样。如果某种机器的环境字长是 32 位,而且也没有什么指令能够更有效的处理更短的整型值,那它很可能把这 3 个整型值都设定为 32 位。 +位域:基于 int 位域被当作有符号还是无符号数、位域成员的内存是从左向右还是从右向左分配、运行在 32 位整数的位域声明可能在 16 位机器无法运行等原因,注重可移植性的程序应该避免使用位域。 +结构与指针:什么时候应该向函数传递一个结构而不是一个指向结构的指针呢?很少有这种情况。只有当一个结构特别小(长度和指针相同或更小)时,结构传递方案的效率才不会输给指针传递方案。 + +
+ + Read More ~ +
+
+
+ +
+

+ + 为什么宏定义要使用 do {...} while (0) ? + +

+ +
+ + + + +
+ +
+ +参考内容: +do {…} while (0) in macros +do {...} while (0) 在宏定义中的作用 +do{}while(0)只执行一次无意义?你可能真的没理解 + +近期参与的项目属于嵌入式软件领域,自然而然就得用 C 语言进行开发,开发过程中发现引入的第三方库里面有一些奇奇怪怪的写法,比如大佬们都喜欢使用do {...} while(0)的宏定义,在 Stack Overflow 上也有人提出了这个问题。之前从事 Linux 内核开发的谷歌大佬 Robert Love 给出了如下的解释: + +do {…} while(0) is the only construct in C that lets you define macros that always work the same way, so that a semicolon after your macro always has the same effect, regardless of how the macro is used (with particularly emphasis on the issue of nesting the macro in an if without curly-brackets). +do {...} while(0) 在 C 中是唯一的构造程序,让你定义的宏总是以相同的方式工作,这样不管怎么使用宏(尤其在没有用大括号包围调用宏的语句),宏后面的分号也是相同的效果。 + +这句话读起来有些拗口,只觉得大佬的表述曲高和寡,翻译翻译就是:使用do {...} while(0)构造后的宏定义不会受到大括号、分号等影响,总能按照我们期望的方式调用运行。下面我们举几个实际的例子来加深理解。 +// 现有如下宏定义 +#define foo(x) bar(x); baz(x) + +// 1. 可以这样调用 +foo(wolf); + +// 上述调用将会被展开为下列代码,完美没有问题 +bar(wolf); baz(wolf); + + +// 2. 如果我们像下面这样调用呢? +if (!feral) + foo(wolf); + +// 上述调用将会被展开为下列代码,很明显这是错误的,并且是很容易犯的错误 +if (!feral) + bar(wolf); +baz(wolf); + +为了避免上面例子所出现的问题,我们可以考虑使用{ }直接把整个宏包裹起来,如下所示: +// 修改宏定义为 +#define foo(x) { bar(x); baz(x) } + +// 3. 例 1 调用 +if (!feral) + foo(wolf); + +// 现在上述调用将展开为下列代码 +if (!feral) { + bar(wolf); + baz(wolf); +}; + + +// 4. 我们再考虑一下如下调用呢 +if (!feral) + foo(wolf); +else + bin(wolf); + +// 上述调用将会被展开为下列代码,很明显又出现了语法错误 +if (!feral) { + bar(wolf); + baz(wolf); +}; +else + bin(wolf); + +我们继续考虑比使用{ }直接把整个宏包裹起来更好的方法,即本文标题所说的使用do {...} while (0),即上述宏将定义为如下形式。 +// 终极版宏定义 +#define foo(x) do { bar(x); baz(x) } while (0) + +// 5. 例 4 调用 +if (!feral) + foo(wolf); +else + bin(wolf); + +// 现在上述调用将展开为下列形式,很完美 +if (!feral) + do { bar(wolf); baz(wolf) } while (0); +else + bin(wolf); + +do {...} while (0)除了在宏定义中可以发挥完美的作用外,在某些情况下还可以当作goto使用。因为goto不符合软件工程的结构化,并且容易使得代码晦涩难懂,所以很多公司都不倡导使用甚至禁止使用。那么我们可以使用do {...} while (0)来做同样的事情。 +#include &lt;stdio.h&gt; +#include &lt;stdlib.h&gt; +int main() +{ + char *str; + /* 最初的内存分配 */ + str = (char *) malloc(15); + if(str != NULL) + goto loop; + printf(&quot;hello world\n&quot;); +loop: + printf(&quot;malloc success\n&quot;); + return 0; +} + +上述代码我们可以修改为下列形式,使用do {...} while (0)将函数主体包裹起来,而break语句则替代了goto语句的作用,并且代码的可读性与可维护性都比上述goto方式更好。 +#include &lt;stdio.h&gt; +#include &lt;stdlib.h&gt; +int main() +{ + do{ + char *str; + /* 最初的内存分配 */ + str = (char *) malloc(15); + if(str != NULL) + break; + printf(&quot;hello world\n&quot;); + }while(0); + printf(&quot;malloc success\n&quot;); + return 0; +} + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/w-PjYn9Fz/index.html b/w-PjYn9Fz/index.html new file mode 100644 index 00000000..20901151 --- /dev/null +++ b/w-PjYn9Fz/index.html @@ -0,0 +1,467 @@ + + + + + + + + 信息茧房|如何保持开心|自己的圈子—校友、房东|潮州彩塘抛光厂 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 信息茧房|如何保持开心|自己的圈子—校友、房东|潮州彩塘抛光厂 +

+ + +
+ +
+

好几个月没有发文章了,主要是因为觉得自己太菜了,肚子里的东西太多浮于表面(实际上肚子也没有东西),也写不出来什么深度。不知道大家发现没有,现在很多公众号的味道都变了,一者是肚子里的货已经吐的差不多了,二者是在自媒体疯狂变现的年代,太多作者都开始为流量而写作,已经忘记了原来的初心。好友说长期不发文,突然发会掉粉的,我也想试试会掉下去多少。

+

说到为流量写作,其实并不是自媒体作者天天在干的事,专业的记者也在做这些事情。从商业角度来看,一篇有深度而没有阅读量的文章肯定是比不上一篇适合大众口味但阅读量高的文章。

+

媒体总是会挑那些吸引眼球的事件来报道,因为负面故事总比中性或正面故事更具有戏剧性,而且人在进化的过程中保留了对一些事物的恐惧感,这些恐惧感根植于我们大脑的深处,它们对我们祖先的生存是有帮助的。在现在的这个时代,你也很容易就把眼球放到那些能够激发我们本能的故事上。

+

包含地震、恐怖袭击、战争、疾病、难民等等字眼的标题总是容易成为头版头条(现在朋友圈肯定都在传四川内江的地震),而像“在过去 100 年,死于自然灾害的人数几乎减少了四分之三”一类的标题总是不会收获多少阅读量,就更不具备什么商业价值了。大家都在说信息茧房,人类的本能也是造成信息茧房的原因之一。

+
+

周四和一个同事一起散步的时候,他问了我一句话:“小老虎,你为什么总是能保持这么开心呢?”(小老虎是在部门大家对我的称呼)我思考了几秒,不知道怎么回答同事的问题。对哦,我是怎么保持每天都这么开心的?是我给他们的错觉还是我确实就这么开心呢?于是给了同事一个简单的答案:“当你变得没心没肺的时候,你就会超开心;另外降低对事物的期望值,这样你就总能收到正反馈,会把你的开心加成。”

+

像之前一样,我又成长为同事圈子里的小开心果了。其实我也不是一直开心的,可能就是我这个人比较逗比,我一直认为逗比是一种生活态度。但在公司我同样怼大叔、怼领导,不管我是不是真的开心,既然给大家的印象是开开心心的,那就假装我是一直都开心的吧。

+

我常常开玩笑说的一句话:“你对它笑,它就会对你笑,如果它不对你笑,那就对它多笑几次”。你对它笑,你肯定希望对方也给你回一个笑,但是我和大多数人不同的是我降低了期望值,我从来不期望对方能给我一个笑容,于是当对方给了你一个笑容的时候,那就是意外地收获,如果是一个大大的甜甜的笑容,就会突然冒出来幸福来的太突然了感觉。降低期望值也是一个很适合长期学习某项技能的方法,过高的期望值总是会让你放弃。

+

很多人说情商是为了别人高兴,话外音就是不想委屈自己迁就别人。但是你让别人高兴了就是与人方便,那对方自然会给你方便,自己方便了不就是高兴吗,所以对这个世界好一点,降低对它的期望值,你就总是能开开心心的过日子。

+
+

毕业这一年认识了很多人,现在我日常接触的圈子差不多有四个,同事这个圈子没啥特别的,团队氛围比较好,时常在晚上悄悄定个会议室,大家一起打王者;推特、微信等软件里面结交的互联网大佬圈我插不上话,不敢说;然后是我两任房东带我进的圈子,和高校毕业人群所建立的圈子完全不一样。

+

这群人大部分对我都很好,我目前比较害怕见到现任房东,因为基本上见到他就是出去吃饭。我住在房东隔壁,刚搬过来的时候一出门见到他:“小光,走,去吃饭。”房东的吃饭一般是两场,一场到餐厅点菜吃到 11:00-12:00 的样子,然后再继续下半场烧烤,在房东的带领下,我一个月长了 10 多斤。

+

于是我现在出房门的时候,先瞅瞅房东在不在,如果不在就直接坐电梯下楼,如果在就先下到 5 楼,再坐电梯。所以我们现在更多的是没事喝喝茶,偶尔吃吃饭,体重总算控制住了。

+

当然这个圈子也有不太好的人,有借了我钱后人就跑的没了踪影的人。但是我很庆幸我能这么早遇到这样的人,因为现在我借出去的并不多,如果再等 10 年我才能遇到这样的人,那我的损失可能就是很多很多倍了。

+
+

另外一个对我很重要的圈子就是校友会,我不清楚学校其它地区校友会是什么情况,更不清楚其它学校校友会是怎么样的,深圳校友会确实给了我一个温馨的感觉。校友之间都很单纯,学长学姐们都愿意带年轻人,最大有 79 级的师姐,最小的 15 级也已经到来,老人都会给新人讲他们所经历的事情,给年轻人传授经验。

+

当然由于学校带着军校的基因,校友里面没有什么非常非常出名的企业家,但是大家都是很尽心尽力的相互帮助。仅仅靠校友情能达到这样的效果,这一点确确实实是出乎我的意料了,校友会目前是对我开心的加成作用很大。

+

举个例子,一个学长新开了烧烤店,现在还没有开始对外营业,处于内测阶段。这一周每天店内至少有一半都是校友,店内的设计、装修、监控等等校友都在出力,当然像我这种没资源的学弟只能试吃给出改进意见了,一个人在外地能成为这样大家庭中的一员是很幸福的。

+
+

高校毕业生一年比一年多,媒体每年的标题都差不多一个意思:史上最难就业季。不得不承认独自一人到外地打工确实辛苦,大家都是独自承受着来自各方的压力,杭州闯红灯小伙的突然崩溃就是一个极端的例子。

+

我之前的住的地方,仅仅我知道的就有三个年龄比我还小的女孩被包养,仅从外部观察来看,她们过的其实挺好的,嘴角也常常挂着 45 度的微笑,倒是包养她们的人过的不是多随性。其中一个还开了一家奶茶店,我有幸也喝了几杯免费奶茶。

+

另外还有一些像我一样的打工者,我和前任房东也常常喝茶吃饭(现在也是),听他说住在那里的女孩子很多没有男朋友,但是她们晚上经常会带不同的男生回来,我想这对她们来说也是一种释压方式,当然住那里的男生可能只是没有带回来,房东不知道而已。

+
+

我不是太喜欢天天去研究某个业界名人所讲的话,也对各种各样的产品不是多感冒,不否认有些营销文案、产品功能、讲话内容是公司有意精心为之,但是有没有另外一种可能呢?是领导背错了台词、或者是说错了,而我们却非得去给它找出各种各样的原理。

+

周末闲着去感受了一下农民工的圈子,我去的是潮州彩塘镇的抛光厂,才知道我们平时用的那些锅碗瓢盆那么亮不是因为镀上了一层,而是硬生生给磨掉了一层,给磨亮的。最后再说一个,不知道你有没有注意到马路边的人行道上,总是会有一列地砖是有凸起的,有的是条状凸起,有的是圆点凸起,有没有想过为什么是这样的呢?

+

凸起是盲人走的道路,条状代表直走,圆点代表拐弯。是不是觉得这个世界对每个人都是美好的,既然这个世界对我们这么美好,那干嘛要不开心呢?

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/wI3Nw7REg/index.html b/wI3Nw7REg/index.html new file mode 100644 index 00000000..d58120a9 --- /dev/null +++ b/wI3Nw7REg/index.html @@ -0,0 +1,491 @@ + + + + + + + + 2022年个人总结 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 2022年个人总结 +

+ + +
+

断舍离

+

很多时候我们的问题都是又过度的消费引起的,聪明的商家为了引诱人们多多消费,推出令人眼花缭乱的问题解决方案,但真正的有效方案是:减少消费。如果你长胖了,不是要买减肥药、吃蛋白粉,而是要少吃东西。如果你头痛和感到巨大压力,不是要购买头痛药和抗抑郁药,而要更多地睡觉和走路,并且不在深夜浏览社交媒体。

+

一些感悟

+

我一直比较抵触抖音、快手这样的奶头乐事物,因为上面的视频基本都是以猎奇为导向,但是回家发现

+

姨夫一家通过自己的努力,在兄弟姐妹中算是实现了从农村到城市的跨越,但从他们身上我发现去一个新的地方,不是简单的一套房子、一份工作那么简单。作为一名异乡人,他们的社会关系还全部在老家,想要嵌入本地生活需要花费极大的非财力成本。

+

上大学时杨福家院士来校开了一次讲座,当时他直言不讳的指出来我们学校没有一个有意义的雕塑,他最喜欢的是《思想者》雕塑,但是在我们学校却没有找到一个雕塑有思考的动作。最近愈发觉得不仅仅是雕塑不会思考,连实实在在的人也已经不会思考了。全民媒体时代的知识分子都是网络知识分子,现在人们已经习惯了什么东西都去网上搜答案,习惯了把自己当成一台检索机器。

+

生活中不乏给别人挑错的朋友,也有很多网友乐意给某个软件、平台挑 bug,读书的时候亦是热衷于去挑书里面的错别字。我以前也是这样的一个人,以为自己能给一本公认的好书挑出来错别字,能给大家都赞赏的软件挑出来 bug,就能做编辑、做产品经理了。这完全是错误的想法,挑三五个错很容易,但自己能做到只能有三五个错吗?

+

对城市的理解

+

陆铭老师的《向心城市》一书提供了比较多的数据支撑,应该是普通人理解城市最好也是最简单的教材,人才和劳动力逐渐向大城市集中是必然的趋势,

+

一席演讲困在时间里的村庄展示了大量因为城市化而被遗弃的荒村,

+ +

探索开源

+

已经忘记是从什么渠道了解到开源社JinaAI 这两个组织了。「开源」两个字眼自大学就时不时出现在视野中,也一直很好奇到底什么是开源,抱着学习开源的心态加入了开源社社区和 JinaAI 社区。为 JinaAI 的宣传工作做了一点点自己的贡献,承担了少许开源社官网开发、COSCon'22中国开源年会的工作,希望新的一年能理解到开源更多的含义。

+

信用卡一文中提到了黄牛这个行业,旁窥过程中发现了很多有意思的事情。国内购物平台基本是淘宝(天猫)、京东、拼多多三家占据,京东

+

我一直有一个比较傻里傻气的疑问。菜市场不同商品的价格都会跟随市场波动,上午一个价、下午一个价是习以为常的事情。但是电脑、手机的价格只要官方发布定价后,很长时间都不会有变动,

+
+

参考内容
+Consume Less. Create More. It’s More Fun.

+
+ +
+
+ + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/wTPQ_ONKy/index.html b/wTPQ_ONKy/index.html new file mode 100644 index 00000000..0c48b260 --- /dev/null +++ b/wTPQ_ONKy/index.html @@ -0,0 +1,475 @@ + + + + + + + + 如何空手利用拼多多(淘宝)赚钱套利? | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 如何空手利用拼多多(淘宝)赚钱套利? +

+ + +
+ +
+

在开头先说明一下,我自己不懂免费流量的玩法,以下我介绍的内容是我为了帮助公司一些想拿销冠的小伙伴而探索的方法,自己去实践了是有效果的,而且就个体来讲的话是可复制的套路,但我基本没放什么精力在上面,分享出来主要是希望朋友一起交流免费流量的玩法,毕竟对个人来讲付费流量还是太贵了!

+

估计大部分小伙伴都还不太能接受拼多多吧!拼多多上面的假产品确实不少,但是在「百亿补贴」专场里面有官方的监管,品质上都是可以做到保证的,比如 Airpods Pro 官方价格是 1999 元,而在拼多多的「百亿补贴」里面买下来 1500 都不到,那么这中间就具备了套利空间,转手在闲鱼、转转上面卖 1600 是不是很香?

+

我自己没有玩过转转,这里简单说一下闲鱼吧!一定要用信用很好的账号,因为这会直接体现到你的商品中。为了让客户相信我们首先要打造好人设,头像、昵称、生日、常住等等信息都填上,注意这些信息也不是瞎填,你的这些信息要给客户体现一种什么性格?什么职业?什么年龄?下面是我自己的一个闲鱼号。

+
+
+

年龄 25 岁,喜欢数码、宅男、喜欢听歌,在互联网行业,是不是给别人的亲切感也比较强?更容易让人信服?而且这样的基础资料也比较符合我上面提到的转卖 Airpods Pro 的形象,总的来说就是信息越完善越容易让人信服。

+

当然这属于比较简单的套利方式,一个人最多能注册 3 个账号,如果不是工作室的话维护起更多的账号也会费时费力,我在公司客户里面看到了一个比较 NB 的操作,去淘宝联盟去采集那些有优惠券的商品,就是做淘宝客店铺,去找那些有淘宝返利的商品,然后批量上架到自己的淘宝店铺,淘宝对那些下架前的商品会提高权重,有机会显示到前面,相对来说引流成本就能降低一些!听说还有些工作室直接利用软件批量采集淘宝联盟的商品,然后批量上架到京东商铺去赚返利。

+

此处多说一句,我现在所在公司的客户里面有很多是同时开了淘宝和拼多多店铺的,他们在拼多多上卖的要比淘宝便宜,因为开拼多多店铺的成本要比淘宝低,所以拼多多上面也不都是假产品,像我下面截图中的前面有个「品牌」标志的就是真品,我们接到的很多客户是上不去这个的,不过我目前除了拼多多「百亿补贴」里面的东西会买外,其他的商品我还是不会买。

+
+

上面提了一下开个淘宝店,估计很多人也都只是在淘宝上买过东西,却从来没有在淘宝上卖过东西吧。但是会读到这篇文章的你至少知道先去搜索引擎查看,而大部分人连主动搜索的意识都没有,只知道瞎刷抖音。10 亿网民里面什么素质的人都有,梦想嫁个有钱人的、想快速赚钱一夜暴富的,去支付宝搜一搜「一夜暴富」就知道该怎么做了。

+
+

今年很多人也都因为疫情原因赋闲,查来查去好像就是开网店靠谱直接一点,因为这比较符合传统的一件产品卖给一个人的思维模式,只是把商铺搬到线上了而已。我自己没有货源怎么办?淘宝看起来好复杂啊,我不会开店怎么办?听说网店也需要做图装修?其实这些知识只需要使用百度搜索一下就能找到,连一点点高级的搜索技巧都不需要,但是就是有太多想挣钱却又懒得去主动学习的人,不然就不会出现那么多割韭菜的项目了,而且韭菜还越割越多!

+

应该有部分伙伴听说过以前有工作室通过代开通微信公众号赚钱吧?这个「淘宝开店」和「代开通公众号」类似,比公众号要稍复杂一些,有很多公司(包括我现在的公司)都是跑抖音广告去挖掘有开店意向的销售线索,只是帮人家开一个淘宝店就收 1000 以上的佣金。

+

我这里还是拿闲鱼举例,下面图片是我截的我闲鱼的图片,随便做了个海报上去,海报内容突出「代开网店」、「淘宝教学」之类的词,一天会进来几十个咨询如何开网店的,基本上每天都能成交 2-3 个,好的时候能成交 5 个以上。这里要说明的是海报是为了规避闲鱼的检测,我试过用一个 Word 表格列出来我能做的内容,但是商品立马就被强制下架了,估计是平台也喜欢漂亮清晰的图片吧!

+
+

为了增加曝光量(毕竟展现和转化是直接相关的),闲鱼上架的商品记得每天都去擦亮一下,如果可以的话邀请朋友帮助自己刷刷单,再用微信把钱转给朋友!因为闲鱼上面的曝光引流时间权重是15日,如果你的商品 15 天都没有卖出去的话,那么系统就会认为这个商品不是优质商品,这一点可以拿我这几天玩闲鱼的一个案例证实。可以看到截图中我上架了 3 个同样的商品,其中第二个是被人买过后我又重新上架了,其他几个商品的曝光量直接比第二个差了一个数量级不止,这还是我已经没有管闲鱼状态下的结果。

+
+

当然这里面需要用到一些话术,新手基本都不知道有一件代发的产品,他们都会问没有货源怎么办。我就以「没有货源」为例来回答,您没有货源没有关系的,我这边可以帮您对接一件代发的货源,基本上的货源都能涵盖到。如果说的再没有良心一点您没有货源没有关系,我这边毕竟在做这个,所以认识了很多的厂家老板,我可以免费为您对接厂家的货源,这个您放心就好了

+

从上面图中还能看到我除了海报还放了一些关于淘宝运营的书籍上去,想想什么样的人会去搜索这样的书籍?要么就是想学习淘宝运营的,要么就是想要自己开网店。淘宝运营有淘宝大学里面的课程内容,市面上关于淘宝运营的书籍、课程的基本都能从淘宝大学里面找到。所以如果进来的客户想开网店,那么你直接给他引导开网店;而如果对方是想学运营知识,你可以直接把淘宝大学的内容换成从你嘴里出来的内容;如果对方是因为想开网店而学习的话,那么学习这件事是反人性的,这种流量放一段时间也有部分是能成交的。这个「淘宝教学」的项目据我所知有公司已经做了 10 年了,而且公司越做越大。

+

这里顺便说一个投放广告(引流)的思路:广告展示的产品是 A 罩杯内衣,但实际要推广的是丰胸产品。给买 A 罩杯的女人推丰胸产品,这个思路适合任何行业的投放!广告投放做人群探索的时候,优化师也常常会用这样的思路去做人群计算,从而让进入 CRM 的销售线索更加精准。

+

再分享一个拼多多空手套利的案例,我现在所在的这家公司的第一桶金就是这样来的,当然他们那时候市场也比现在好做多了!想一下我们自己在淘宝、京东上面买东西的时候会关注哪些东西?买家秀、销量、差评率等,拼多多自己留了一个后门可以直接修改销量,这个操作尤其对那些新店铺有用。这个数据除了可以直接吸引买家眼球外,还可以增加店铺权重,另外还可以上报一些活动,比如什么双十一、618 这一类的活动平台都是会设置门槛的,有些店铺就是因为销量不够而达不到上报活动的条件。

+

下面粗略介绍一下这个改销量的方法,比如我截图的这个商品(我随便截的,不代表任何观点)销量已经有 2207 件了,商品卖的价格是 58.9 元,假设我们现在要修改这个商品的销量。首先我们去后台把这个商品的价格提高,为了避免平台检测到我们违规操作,每次在原来价格基础上乘以 2,直到我们把价格修改到了 2000 元,然后进入「多多进宝」平台推广该商品,优惠券就可以设置为 1000 元,注意这里优惠券千万别设置多了,不然被别人领取了可就真麻烦了。然后自己去把这个优惠券领了,再回去修改商品价格到 1.01 元,为了能成功的修改商品价格需要更换改商品的类目,因为不同商品的 SKU 允许的最低单价不同,价格修改好了之后就去买购买该商品,下单数量直接设置为 1000。那么咱们可以算一下实际的成本了,1.01 * 1000 = 1010 元,而我们有一个 1000 元的优惠券可以抵消,实际成本就只有 10 块钱,有没有惊讶到?

+
+

接下来再慢慢的把商品价格改回去就可以了,而且这个过程基本不用担心会有别的顾客进来恶意购买,因为新店铺都没有权重,别的顾客是看不到的,而且如果新店铺都给权重的话,那拼多多多的直通车还怎么赚钱啊?需要注意的是每天修改销量的上限别超过 3000,不然店铺很容易被封!

+

具体的技术大致介绍完了,那么这样的客户从哪里去找?以前你在拼多多上面购买了商品就可以直接看到商家的电话,而且拼多多那时候支持秒退款,可以没有成本的获取销售线索,不过后面拼多多把这个通道给堵上了。那么拼多多里面有一个商家社区,商家社区又细分出来一个新手社区,这里面是可以看到商家店铺的名字的。所以你懂了吧?不要害怕,直接进入店铺进入客服,和商家聊天!

+

类似的空手套利的后门在淘宝也有,比如很多店铺因为一些原因被官方封禁了,但是淘宝留了一个后门可以解封店铺,这里我就不做详细的技术描述了,抛一个解封店铺方法线索:强制开通第二个淘宝店铺教程(可解封),随随便便收个一两千块钱不是问题!

+

最后说一下自己的感受吧,以上介绍几个案例其实都利用的是信息差,介绍的两个电商平台的后门是平台自己没有测试出来吗?这是平台故意留给外面的网店代运营公司的,因为平台需要这样的公司给他拉新。公司盈利模式无非就是两种模式,第一种不断的洗小白客户,第二种提升服务质量推出新产品服务好老客户。淘宝开始也是假货横行,但是现在一说到假货都不会想到淘宝了,拼多多走的一些路是淘宝走过的路,在每个阶段都多少会有它的套利机会。

+
+

我接触的这几个案例都是针对的低端流量,统计局都说了 6 亿人月收入不超过 1000 元,超过一半网民的月收入低于 3000,大部分人那种成天无所事事,却天天梦想发大财掉馅饼的心理正是很多公司利用的心理。自己少说漂亮话多做事!

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/wZWdqj3Rd/index.html b/wZWdqj3Rd/index.html new file mode 100644 index 00000000..769e65b1 --- /dev/null +++ b/wZWdqj3Rd/index.html @@ -0,0 +1,545 @@ + + + + + + + + Schema 与数据类型优化 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ Schema 与数据类型优化 +

+ + +
+ +
+
+

参考内容:
+《高性能 MySQL(第三版))》

+
+

选择优化的数据类型

+

世面上常见的数据库大多支持了多种多样的数据类型,选择正确的数据类型对于获得高性能至关重要,一般都需要遵循如下的几个原则:

+
    +
  1. 更小的通常更好:更小的通常更快,因为占用着更少的磁盘、内存和 CPU,并且处理时需要的 CPU 周期也更少;
  2. +
  3. 简单就好:简单数据类型的操作通常需要更少的 CPU 周期;
  4. +
  5. 尽量避免 NULL:如果查询中包含可为 NULL 的列,就会使得索引、索引统计和值比较变得复杂,因此在设计表是最好指定列为 NOT NULL。
  6. +
+

整数类型

+

在 MYSQL 中可以为整数类型指定宽度,例如INT(11),但是这对大多数应用是没有意义的,它不会限制值的合法范围,只是规定了 MySQL 的一些交互工具(如 MySQL 命令行客户端)用来显示字符的个数。对于存储和计算来说INT(1)INT(20)是相同的。

+

字符串类型

+

需要注意的是当 MySQL 存储 CHAR 值时,它会删掉所有的末尾空格,因为 CHAR 值会根据需要采用空格进行填充以方便比较,这导致的问题就是你使用 CHAR 存储的string 会变成string。CHAR 的好处在于它是定长的,很适合存储像 MD5 值一样的定长值,定长值的 CHAR 类型不易产生碎片,而且对于非常短的列 CHAR 也会比 VERCHAR 好,比如CHAR(1)只需要一个字节,而VERCHAR(1)则需要两个字节,因为它还需要一个字节来存长度。

+

VERCHAR 类型在存储可变长字符串时,会比 CHAR 更节省空间,它需要使用 1 或者 2 个额外的字节记录字符串的长度。但由于行是变长的,当一个行占用的空间增长,并且在页内没有更多的可用空间可以存储,就容易产生碎片。

+

使用枚举代替字符串

+

有时候可以使用枚举列代替常用的字符串类型,枚举列可以把一些不重复的字符串存储成一个预定义的集合,而且 MySQL 在存储枚举时非常紧凑,会根据列的数量压缩到一个或两个字节。比如下面的例子:

+
CREATE TABLE enum_test(
+    e ENUM('fish', 'apple', 'dog') NOT NULL
+);
+
+INSERT INTO enum_test(e) VALUES('fish'), ('dog'), ('apple');
+
+SELECT e+0 FROM enum_test;
+
+# result
++-----+
+| e+0 |
++-----+
+|   1 |
+|   2 |
+|   3 |
++-----+
+
+

可以看到使用枚举类型后,上面三行数据实际上存储为了整数,而不是字符串,而且还有一个让人吃惊的地方:枚举字段是按照内部存储的整数而不是定义的字符串进行排序的,这一点需要特别注意,不然在写程序时容易中犯错。当然你也可以在查询时使用FIELD()函数显式地指定排序顺序。

+

可以看到上面

+

范式和反范式

+

关系型数据库有设计范式的概念,这一点在大学的数据库课程中肯定都会提及。因为有比较高的范式,那么就只有很少或者没有重复的数据,因此在 UPDATE 时只需要修改更少的数据;高范式的表通常也更小,因此占用的内存也会更小,操作起来也会更快......

+

但是高范式也带来了另一个缺点,比较好的范式通常意味着需要关联,稍微复杂一点的查询就需要使用 JOIN,关联的代价是昂贵的,甚至让一些索引策略失效;而如果不需要关联,即使某个查询需要全表扫描,当数据比内存大时可能会比关联查询快的多。所以一般都会根据实际情况将范式与反范式混用,完全的范式化和完全的反范式化都是实验室才有的东西。

+

缓存表和汇总表

+

这里的「缓存表」和「汇总表」并没有什么标准的含义。我们用「缓存表」来存储那些可以从其他表获取,但是获取的速度很慢的数据;而「汇总表」则用来保存那些使用 GROUP BY 语句聚合数据的表。毫无疑问,我们存这些冗余数据也是为了性能。

+

比如近两年各种应用流行的年终报告,每次网易云音乐的年终报告都会把朋友圈撑满,其它类似于缓存一个用户的朋友数、一个文件的下载次数等等。这些数据实时计算的开销是很大的,而且多数情况下用户也等不起实时计算的时间,一般的解决方案都是通过增加计数器表(缓存表)来解决这个问题。

+

计算机科学中总是伴随着双面性,上面的计数器表带来性能提升的同时也带来了并发问题。网站的每一次点击都会导致对计数器的更新,对于任何想要更新这一行的事务来说,这条记录都有一个全局的互斥锁,这会使得这些事务只能串行的进行。每一次点击都会触发下面的语句,但大量的点击伴随着该行数据的互斥锁,想想性能也不会提升到哪里去吧。

+
UPDATE hit_counter SET cnt = cnt + 1;
+
+

大多数应用都是读查询居多,为了提升读查询的速度,经常会需要增加一些额外的索引,增加冗余列、汇总表、缓存表等等。但是不要忘了这些技巧也会增加写查询的负担,还会增加开发难度,因此应该根据实际应用场景来做权衡。

+

加快 ALTER TABLE 表的速度

+

MySQL 执行大部分修改表结构的方法都是用新的结构创建一个空表,然后从旧表中查出所有数据插入到新表,然后删除旧表。在内存不足、表很大、索引多的情况下会花费很长的时间。一个很严重的缺点是大部分 ALTER TABLE 操作将导致 MySQL 服务中断。

+

对于常见的场景我们有两种技巧避免服务中断。一种是先在一台不提供服务的机器上执行 ALTER TABLE 操作,然后和提供服务的主库进行切换;另一种技巧是「影子拷贝」,即用要求的表结构创建一张和源表无关的新表,然后通过重命名和删除表的操作交换两张表。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/wfWg7oC32/index.html b/wfWg7oC32/index.html new file mode 100644 index 00000000..402a34d3 --- /dev/null +++ b/wfWg7oC32/index.html @@ -0,0 +1,454 @@ + + + + + + + + 读大学的几点建议 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 读大学的几点建议 +

+ + +
+ +
+

前天在朋友圈看到一句话:“学生就是无知、狂妄、垃圾的代名词”,让我思考了很多东西。毕业出来也有快两月了,圈子里还有很多学弟学妹,很多同级的同学也都读研了,这里谈谈自己的感受,应该怎么把大学过好,期望不要太高,我自己很普通,大学没有什么出彩的经历。

+

很多父母都把孩子的成就与大学挂钩,认为好的大学就是成功的代名词,盲目追求高学历,孩子从小也一直受这些思维的影响,应试能力强的惊人。大家都知道清华北大好,但是你问他哪里好,就回答不上来了,尤其家长,在他们眼里 985 一定比 211 好,211 一定比普通一本好。

+

现在大学都会给你传达自己乃名门之后的观念,进校首先讲历史,当然都是挑好的讲,然后讲学校的历史成绩单。不得不说,效果非常好,你一和大学生谈论他的学校时,他会给你说出来学校是某某名人所建,学校在哪方面做的非常好,比如导弹是全国第几,造船位居全国前列等等。但是你一问他本人是哪个专业,回答是学数学的、学计算机的......

+

每个学校都有恶心的事,我自己的一个经历,学院一拍脑袋,搞个什么本科生导师制,然后就没有然后了,四年总共见了导师一面。期间最可笑的是,也不知道是教育部还是什么部来检查,学生需要交一个导师沟通表上去,导师在哪个办公室都不知道,那大家怎么办,就模仿导师的口吻给自己下评语,第二天全院的导师沟通表都被打回来了,因为大家模仿的口吻不像导师,要求重新造假。

+

上面的类似情况在大部分学校应该都存在,只不过看谁更可笑,某个学生出事了,学校第一想法不是怎么帮自己的学生解决问题,而是想如何把事情压下去,封锁消息。你会发现很多效率像蜗牛一样的机构,其公关效率却像火箭一样。

+

现在各个大学的就业率都高的惊人,都不会低于 90%,为啥这么高呢?我也不知道学校是如何统计就业率的,唯一清楚的是,你毕业了,没有签工作,那么辅导员会给你打电话让你随便找个公司把三方协议签了交上去;这算轻的,很多学校是你不交三方协议,就不给你学位证、毕业证,我身边就有好几个随便刻个假章,盖在三方上面,只要交了三方协议的都算就业了的。

+

我个人认为大学有的课就应该逃,也看到过文章说学生上课不应该带有批判性思维,什么课有用不应该是学生说了算,大学的课程设置都是专家们讨论的结果,现在最不缺的就是专家,什么人生导师一大把,出来之后,你仍然会发现有的课纯属浪费时间。强调一下,逃课不是去打游戏,是为了把时间利用的比在课堂上更有价值,我大学微积分老师也鼓励我们逃课,现在看来那时还是胆子太小,人家鼓励你逃课,还不敢逃,怂。重要的事再说一遍,逃课是去做比上课更有价值的事情。

+

养成自学的习惯,提高自学能力,自学能力太重要了,而且这个时代自学是很容易的,网上有很多视频教程,比学校老师教的还好,而且也更接近于实战,大学教不了你太多东西,仅仅提供了一个平台,只是平台大小的区别而已。经常会听到学生说某个知识点老师没教,潜台词就是这个知识点我就不应该会,而且理直气壮,让人无语。世人都认为学历最重要,实际上真正重要的是学力。

+

迷茫的时候就去旅行吧,感受一下不同的文化,见识见识世界的缤纷多彩,你的视野会开阔许多,很多事情必须亲身体验才能感受到它的好处,旅途中你可以结识各种各样的朋友,与他们的思想碰撞,看看其他地方的生活,你可能就不会迷茫了,会找到自己乐趣。

+

多结识比自己优秀的人,认识正能量的朋友,大学提供了很多机会,优秀的朋友会在不知不觉中改变你,你也会不知不觉变得更优秀。我在出于兴趣和打发时间,没事写写文章,让我意外的是,对我的改变太大了,通过写作让我认识了一些社会上的优秀人士,通过与他们交谈,我的思维方式有很大的改变,学生思维逐渐摒弃。

+

尽量不要透支,学生没有收入来源,基本都是依靠父母每个月给的生活费,很多学生都使用花呗、白条等产品,而且借贷金额还不少,从理财角度来看,每个月的还款额超过自己收入的三分之一,生活就会有压力,何况学生还是没有收入的群体。没必要为了追求时髦而疯狂购买各种新产品,真高品质生活不应该是科技产品堆砌而成,而应该是由惬意、舒心、成长所构建的。

+

还是要注重和学院领导、辅导员的关系,这点我是做的最差的,因为我不会拍马屁,看不惯就要说出来,容易得罪人。相信这背后的好处都还是明白一二,什么评奖评优暂且不谈,在保研的时候,这种关系会帮你一个大忙,往大了说就是改变人生的机会。

+

写完读了一遍,有的观点还是显得偏激,请自行选择吸收,也欢迎批评指正。总得来说,最重要的就是提升自己的认知水平,思维方式很重要,保持终身学习的态度。有的事要敢想,不要给自己的思维设限制,也不要觉得博士硕士有多么了不起,研究生能做的事,本科生照样能做。

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/wgJXq4UXE/index.html b/wgJXq4UXE/index.html new file mode 100644 index 00000000..8a34fc01 --- /dev/null +++ b/wgJXq4UXE/index.html @@ -0,0 +1,645 @@ + + + + + + + + Git | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + Git +
+ + +
+

+ + 使用 Git 工具进行项目管理 + +

+ +
+ + + + +
+ +
+ +参考内容: + + +Git工作流实践 + + +Git 工作流程 + + +Git三大特色之WorkFlow(工作流) + + +Git分支管理策略 + + +使用 Issue 管理软件项目详解 + + +GitLab Issue 创建及使用说明 + + +Git 提交规范 + +Git 是软件开发活动中非常流行的版本控制器类工具。随着项目时间的拉长、项目参与人员的更替、软件不同特性功能的发布,从开发人员角度看会发现工程的提交历史、分支管理等非常混乱,项目管理者会因为需求迭代、bug 修复、版本发布等活动无法与代码提交历史一一对应而头疼,混乱的管理常常导致故障泄漏给客户,所以一套规范的规范的 git 工作流程是非常有必要的。 +Git WorkFlow +WorkFlow 的字面意思即工作流,比喻像水流那样顺畅、自然的向前流动,不会发生冲击、对撞、甚至漩涡。工作流不涉及任何命令,它就是团体成员需要自遵守的一套规则,完全由开发者自己定义。 +当下比较流行的三种工作流程分别为:Git Flow、GitHub Flow、GitLab Flow。它们有一个共同点:都采用功能驱动开发。其中 Git Flow 出现的最早,GitHub Flow 对其做了一些优化,比较适用于持续的版版发布。GitLab Flow 出现的时间比较晚,所以是综合了前面两个工作流的优点制定的。 +Git Flow +git-flow 的思路非常简洁,通过 5 种分支来管理整个工程。 + + + +分支 +周期 +说明 + + + + +master +长期 +主分支,用于存放对外发布的版本,任何时候在这个分支拿到的,都是稳定的分布版 + + +develop +长期 +开发分支,用于日常开发,存放最新的开发版 + + +feature +短期 +功能分支,它是为了开发某种特定功能,从 develop 分支上面分出来的。开发完成后,要再并入 develop + + +release +短期 +预发分支,它是指发布正式版本之前(即合并到 master 分支之前),我们可能需要有一个预发布的版本进行测试。预发布分支是从 develop 分支上面分出来的,预发布结束以后,必须合并进 develop 和 master 分支 + + +hotfix +短期 +bug 修补分支,从 master 分支上面分出来的。修补结束以后,再合并进 master 和 develop 分支 + + + + +Github Flow +github-flow 可以认为是 git-flow 的一个简化版,它适用于持续部署的工程,直接将最新的功能部署到 master 分支上,不再操作 develop 分支。同时通过 CI&amp;CD 的使用,不再需要额外的 release 和 hotfix 分支。github 还结合了推送请求(pull request)功能,在合并 feature 分支之前通过PR请求其他成员对代码进行检查。 + +master分支中的任何东西都是可部署的 +要开发新的功能特性,从 master 分支中创建一个描述性命名的分支(比如:new-oauth2-scopes) +在本地提交到该分支,并定期将您的工作推送到服务器上的同一个命名分支 +当您需要反馈或帮助,或者您认为分支已经准备好合并时,可以提交一个推送请求(PR) +在其他人审阅并签署了该功能后,可以将其合并到 master 中,合并后原来拉出来的分支会被删除 +一旦它被合并到 master 分支中,就可以并且应该立即部署 + + +github-flow 最大的优点就是简单,非常适合需要持续发布的产品。但是它的问题也很明显,因为它假设 master 分支的更新与产品的发布是一致的,即 master 分支的最新代码,默认就是当前的线上代码。 +但实际可能并非如此,代码合入 master 分支并不代表着它立刻就能发布。比如小程序提交审核以后需要等待一段时间才能发布,如果在这期间还有新的代码提交,那么 master 分支就会与刚刚发布的版本不一致。另外还有一种情况就是,有的公司只有指定时间才会发布产品,这会导致线上版本严重落后于 master 分支。 +可以发现针对上述情况,一个 master 分支就不够用了,你可能需要在 master 分支之外新建一个 prodution 分支才能解决问题。 +Gitlab Flow +因为 gitlab-flow 出现的比较晚,所以它同时具备前面两种工作流的优点,并且又摒弃了它们存在的一些缺点。它的最大原则叫做上游优先(upsteam first),即只存在一个主分支 master,它是所有其他分支的上游。只有上游分支采纳的代码变化,才能应用到其他分支。 +gitlab-flow 分为两种情况,分别适用于持续发布和版本发布项目。对于持续发布项目,它建议在 master 分支以外,再建立不同的环境分支。比如,开发环境的分支是 master,预发环境的分支是 pre-production,生产环境的分支是 production。 +开发分支是预发分支的上游,预发分支又是生产分支的上游。如果产环境出现了 bug,这时就要新建一个功能分支,先把它合并到 master,确认没有问题,再cherry-pick 到 pre-production,这一步也没有问题,才进入 production。 + +对于版本发布的项目,建议的做法是每一个稳定版本,都要从 master 分支拉出一个分支,比如 2-3-stable、2-4-stable 等等。发现问题,就从对应版本分支创建修复分支,完成之后要先合并到 master 分支,然后才能合并到 release 分支。版本记录可以通过 master 上的 tag 来记录。 + +Issue 使用 +Git WorkFlow 主要解决了开发人员的问题,但是对项目管理者问题的解决力度不够,比如客户需求的管理、bug 的跟踪等。Github 和 Gitlab 提供的 Issue 功能可以比较好的解决项目的管理问题。 +Issue 中文可以翻译为议题或事务,是指一项待完成的工作,比如一个 bug、一项功能建议、文档缺失报告等。每个 Issue 应该包含该问题的所有信息和历史,使得后来的人只看这个 Issue,就能了解问题的所有方面和过程。 + +Issue 起源于客服部门。用户打电话反馈问题,客服就创建一个工单,后续的每一个处理步骤、每一次与用户的交流,都需要更新工单,全程记录所有信息。因此,Issue 的原始功能是问题追踪和工单管理,后来不断扩展,逐渐演变成全功能的项目管理工具,还可以用于制定和实施软件的开发计划。 + +下面以可以免费使用的 Github Issues 来介绍如何使用 Issue。 +创建 Issue +每个 Github 仓库都有一个 Issue 面板,如下图所示是新建 Issue 的界面。左侧输入 Issue 的标题和内容,支持 markdown 语法。右侧分别对应四个配置项,将在下面一一进行介绍。 + +Assignees +Assignee 选择框用于从当前仓库的所有成员之中,指派某个 Issue 的处理人员。 + +Labels +可以为每个 Issue 打上标签,这样方便分类查看和管理,并且能比较好的使用看板进行查看。一般来说一个 Issue 至少应该有两种类型的 Label,即 Issue 的类型和 Issue 的状态(根据需要可打第三种用于表示优先级的 Label),其中 Issue 的状态可以用来构建敏捷看板。 + +Milestone +Milestone 翻译过来叫里程碑,它的概念和迭代(sprint)或版本(version)差不多。Milestone 是 Issue 的容器,可以很方便的用来规划一个迭代(版本)要做的事情,也能非常方便的统计进度。 + +Projects +Projects 是 Github 2020 年 6 月份提供的功能,它就是项目看板的功能,Gitlab 所提供的类似功能为 Issue boards,感兴趣读者可以自行阅读文档了解。 + +PR&amp;MR +至此,可以发现我们可以使用 Issue 管理需求、bug,Milestone 又提供了迭代(版本)的计划管理,通过 Projects 可以创建敏捷看板,用于关注整体项目的进度。前文 Git WorkFlow 从开发者角度提供了项目管理的工作流程,可以思考一下还差什么问题没有解决? +最后剩下的问题是:每一个 Issue 都需要提交代码/文档进行解决,那代码/文档如何与 Issue 进行关联呢?其实无论是 GitHub 还是 GitLab,都可以很方便地在 Issue 上创建分支,在该分支上解决完 Issue 所对应的问题后,提交远程分支即可发起合并请求,在 Github 中称为 Pull request(PR),在 Gitlab 中则叫做 Merge request(MR)。 +Git 提交规范 +上文已经说了我们可以对每个 Issue 创建分支,既然是分支,那超过一个 commit 是再常见不过的事情了。一些开发人员所写的提交说明常常是fixbug或者是update等非常不规范的说明。 +不规范的说明很难让人明白这次代码提交究竟是为了什么。在实际工作中,一份清晰简洁规范的 commit message 能够让后续的代码审查、信息查找、版本回退都更加高效可靠,因为我们还需要对提交说明制定一套规范。 +那么什么样的 commit message 才算是符合规范的说明呢?不同团队可以制定不同的规范,此处推荐使用 Angular Git Commit Guide +提交格式指定为提交类型(type)、作用域(scope,可选)和主题(subject),提交类型指定为下面其中一个: + + + +类型 +说明 + + + + +build +对构建系统或者外部依赖项进行了修改 + + +ci +对 CI 配置文件或脚本进行了修改 + + +docs +对文档进行了修改 + + +feat +增加新的特征 + + +fix +修复 bug + + +pref +提高性能的代码更改 + + +refactor +既不是修复bug也不是添加特征的代码重构 + + +style +不影响代码含义的修改,比如空格、格式化、缺失的分号等 + + +test +增加确实的测试或者矫正已存在的测试 + + + +作用域即表示范围,可以是任何指定提交更改位置的内容。主题则包括了对本次修改的简洁描述,有以下三个准则: + +使用命令式,现在时态:“改变”不是“已改变”也不是“改变了” +不要大写首字母 +不在末尾添加句号 + +下图是 NocoBase 的 commit message 截图,可供参考 + + +
+ + Read More ~ +
+
+
+ +
+

+ + Git 基本原理及常用命令速查 + +

+ +
+ + + + +
+ +
+ +参考内容:Pro Git book + +如果你只是想查看 Git 常用命令可以选择直接到文章底部「Git 常用命令」阅读,文章大部分内容是 Git 进阶知识,均是自己的读书笔记,如果还想在此基础上再上一层楼,那可以直接看 Pro Git book。 +Git 历史 +版本控制器是一种记录一个或若干文件内容变化,以便将来查阅特定版本的修订情况。也就是说,版本控制器记录了一个可供考证的历史数据,通过该数据可以知道文件是怎么一步一步发展到今天这个样子的。 +最初 Linux 项目使用 BitKeeper 来管理和维护代码,但是到了 2005 年,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了免费使用 BitKeeper 的权力。那 Linux 开源社区的解决方案就是自己搞一个版本控制器,所以就有了 Git。 +简单说就是 Linus 被逼的去开发了这一款叫做 Git 的版本控制器,因为 Linus 本身就是内核专家与文件专家,所以 Git 也就自然而然具备了非凡的存储能力与性能。 +安装 +关于如何安装 git 可以查看 Pro Git book,安装完成后需要进行一些必要的配置,比如用户信息、文本编辑器、差异分析工具等等,我们可以通过git config --list来查看配置信息。比如我们要配置用户和邮箱,就可以像下面这样输入命令。 +$ git config --global user.name &quot;John Doe&quot; +$ git config --global user.email johndoe@example.com + +Git 原理 +Git 和大多数版本控制器有一个重要的区别,就是它直接记录快照,而非差异比较,其它大部分系统以文件变更列表的方式存储信息,而 Git 则存储每个文件与初始版本的差异。换句话说,只要你的文件有改动,那么 Git 就会将该文件复制一份,正因为 Git 的这个特性,所以 Git 仓库很容易就变得非常大;为了高效,如果文件没有修改,那么 Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。Git 对待数据更像是一个快照流。 +Git 有三个区,分别为:仓库、工作目录、暂存区。基本的 Git 流程为:1)在工作目录中修改文件;2)暂存文件,将文件的快照放入暂存区域;3)提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。那么相应的 Git 就有三种状态:已提交(committed)、已修改(modified)和已暂存(staged),你的文件可能处于其中之一。 + +Git 基础 +工作目录中的文件不外乎处于两种状态:已跟踪或未跟踪。已跟踪是指那些纳入了版本控制的文件,在上一次快照中有它们的记录;工作目录中除了已跟踪文件以外的所有文件都属于未跟踪文件,们既不存在于上次快照的记录中,也没有放入暂存区。 + +查看文件状态 +如果需要查看哪些文件处于什么状态,可以使用git status命令,这个命令显示的信息十分详细,如果你喜欢简洁一点的信息,那么可以在其后添加一个-s,其报告格式类似于下面这样。 +$ git status -s + M README +MM Rakefile +A lib/git.rb +M lib/simplegit.rb +?? LICENSE.txt + +??表示新添加的未跟踪文件;修改过的文件前面有M标记,右边的表示还没有放入暂存区,左边的表示已经放入暂存区了。当然你可能不希望每个文件都出现在未跟踪列表中,比如编译过程临时创建的文件、日志文件等等,所以可以通过创建一个名为.gitignore 的文件,列出要忽略的文件模式,它支持标准的glob模式匹配(shell 所使用的简化了的正则表达式),在 gitignore 中有一个十分详细的针对数十种项目及语言的.gitignore文件列表。 +git status对于具体修改显示的过于模糊,如果想查看具体修改了什么地方,可以使用git diff命令,比如git diff README.md。需要注意的是git diff本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动,如果需要查看已经暂存起来的变化,则要加上--staged或者--cached,比如git diff --cached README.md。 +删除文件 +当然我们不可避免的需要删除某个文件,如果你仅仅是简单的从工作目录中手工删除文件,那它并没有真正的从 Git 中删除,Git 会将这次删除识别为一次改动。更好的方式是使用git rm命令来完成删除文件的工作,比如git rm README.md就会从已跟踪文件中删除,并且连带从工作目录中删除指定文件。 +如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项-f(译注:即 force 的首字母)。 这是一种安全特性,用于防止误删还没有添加到快照的数据,这样的数据不能被 Git 恢复。 +另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。 换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。 当你忘记添加 .gitignore 文件,不小心把一个很大的日志文件或一堆 .a 这样的编译生成文件添加到暂存区时,这一做法尤其有用。这时就需要使用--cached选项了,比如git rm --cached README。 +查看历史 +我们或许因为某种原因需要回顾一下提交历史,这时git log就派上用场了,默认不用任何参数的话,git log会按提交时间列出所有的更新,最近的更新排在最上面,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。 +git log提供的选项很多,更详细的内容可以查看 Git 基础 - 查看提交历史。除了不带选项的命令,我个人更常用的命令还有另外两个,分别为:git log --pretty=oneline它将每个提交放在一行显示,在查看的提交数很大时非常有用;git log --graph或者git log --pretty=oneline --graph用于显示 ASCII 图形表示的分支合并历史。 +撤销操作 +在任何一个阶段我们都可能有想要撤销的操作,我们只需要掌握几个基本的撤销操作就能够应对日常的工作了。 +第一种情况:取消上一次提交。有时候当我们提交完之后才发现漏掉了几个文件没有添加,或者是提交信息写错了,此时可以使用带--amend选项的提交命令尝试重新提交,即git commit --amend。这个命令会将暂存区的文件全部提交,如果自上次提交以来你还没一做任何修改(比如,在上次提交后马上执行了此命令),那么快照将会保持不变,而所修改的只是提交信息。 +第二种情况:取消暂存的文件。假设你修改了两个文件并且想要将它们作为两次独立提交,但是却不小心输入了git add *暂存了它们两个,如何取消其中一个暂存呢?其实在运行git status时已经给出提示了。 +$ git status +On branch master +Changes to be committed: + (use &quot;git reset HEAD &lt;file&gt;...&quot; to unstage) + + renamed: README.md -&gt; README + modified: CONTRIBUTING.md + +所以如果我们想要取消CONTRIBUTING.md的暂存,那么就可以用git reset HEAD CONTRIBUTING.md命令来完成。 +第三种情况:撤销对文件的修改。有时候我们可能并不想保留对某个(若干)文件的修改,git status也给出了详细的提示,告诉我们如何将文件还原成上次提交时的样子,即git checkout -- &lt;file&gt;,比如输入命令git checkout -- CONTRIBUTING.md,就会将CONTRIBUTING.md重置到上一次提交时的样子。 +需要注意的是git checkout -- &lt;file&gt;是一个比较危险的命令,因为它仅仅是拷贝了另一个文件来覆盖当前文件,所以你对那个文件的所有修改都会消失,而且不可恢复。 +远程仓库 +前面我们都是在讲本地操作,远程仓库的使用是必不可少的技能。可以使用git remote命令查看每一个远程服务器的简写,对于已经克隆的仓库,它至少会包含一个origin,这是 Git 给克隆仓库服务器取的默认名字,它和其它服务器并没有什么区别,只是很少人会去修改这个默认名字而已。 +如果想要给一个远程仓库重新取一个简写名,那么可以运行git remote rename来完成,比如git remote rename pb paul就是将pb重命名为paul。值得注意的是这样同样也会修改你的远程分支名字,那些过去引用pb/master的现在全引用paul/master。 +当想要将自己的成果分享给他人时,就需要将其推送到上游,使用git push [remote-name] [branch-name]即可,比如你想要将master分支推送到origin服务器时,就可以运行git push origin master。 +除了分享自己的成果,我们也需要获取他人的成果,即从仓库拉取自己没有的信息,比如git fetch origin,需要注意的是git fetch命令会将数据拉取到你的本地仓库,但它并不会自动合并或修改你当前的工作,所以你还需要git merge来合并分支,实际上有一个git pull命令可以帮我们把这两个步骤都做了,你可以简单的将git pull理解为git fetch后面紧接着一个git merge。 +分支管理 +Git 的分支模型是它的必杀技特性,它处理分支的方式是难以置信的轻量,创建分支几乎是在一瞬间完成,而且在不同分支间的切换也非常的便捷,要理解 Git 的分支,我们必须要再次回顾 Git 是如何保存数据的。 +下图是我们的一个工作流,可以看到所谓的分支实际上就是一个可以移动的指针而已,master、v1.0都仅仅是一个指针,而创建分支、切换分支等操作也都只是对指针的操作,因此就不奇怪为什么 Git 这么快了。 + +那么 Git 又是如何知道当前在哪一个分支上呢?它仅仅是用了一个名为HEAD的特殊指针,你可以将HEAD想象为当前分支的别名,HEAD指向哪个分支,就表示当前处于哪个分支。 +分支创建与切换 +我们可以使用git branch [branch-name]来创建一个新的分支,比如git branch testing;如果使用不带选项的git branch,那么它会列出当前所有的分支,这里需要注意的是master分支也不是特殊分支,它是运行git init时自动创建的默认分支,因为大家都懒得去改它,所以它就好像变得特殊了一样。 +git branch [branch-name]只是创建了一个新分支,并不会切换到这个分支上面去,分支的切换说白了就是移动HEAD指针,我们只需要使用git checkout testing就可以切换到testing分支上去了。 +当然我们可以使用git checkout -b [branch-name]来创建一个分支并同时切换到这个分支,把这个命令与git commit -a -m来对比,你就会发现它们的类似之处。 +分支的合并与删除 +当我们零时在一个新分支上解决了问题后,需要将其合并到master分支,只需要切换到master再运行git merge命令即可,Git 会自动找到这两个分支的共同祖先,然后做一个简单的三方合并。 +当然理想情况下是直接合并成功,但是不免会遇到合并冲突的情况,一旦遇到冲突了,Git 会像下面这样来标记冲突内容,你需要做的是选择由=======分割的令部分的其中一个或者自行合并,当&lt;&lt;&lt;&lt;&lt;&lt;&lt;,=======,和&gt;&gt;&gt;&gt;&gt;&gt;&gt;这些行被完全删除了,你需要对每个文件使用git add将其标记为冲突已解决。 +&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD:index.html +&lt;div id=&quot;footer&quot;&gt;contact : email.support@github.com&lt;/div&gt; +======= +&lt;div id=&quot;footer&quot;&gt; + please contact us at support@github.com +&lt;/div&gt; +&gt;&gt;&gt;&gt;&gt;&gt;&gt; testing:index.html + +当合并完分支后,之前的分支一般就不会再要了,这时你可以运行git branch -d [branch-name]来删除指定分支,比如使用git branch -d testing来删除testing分支。 +远程分支 +远程分支以(remote)/(branch)的形式来命名。如下图所示,如果你克隆一个仓库下来,那么这个仓库除了会有一个本地的分支指针,还会有一个远程分支指针。如果你在本地的master分支做了一些工作,但是你并没有与origin服务器连接,那么你的origin/master指针就不会移动。 + +在这之前我们已经讲过通过推送分享自己的成果,在运行git push origin master命令时,Git 会自动的将master分支名字展开为refs/heads/master:refs/heads/master,即意味着推送本地的master分支来更新远程仓库上的master分支,所以你也可以运行git push origin master:testing来做类似的事,如果远程仓库没有testing分支,那它会自己创建一个新的testing分支。 +我们肯定需要创建一个跟踪远程仓库的其它分支,最简单的就是运行git checkout -b [new-branch] [remote-name]/[branch],该命令会以远端[branch]分支的内容来创建本地的[new-branch]分支,Git 也对该命令做了一个简化,git checkout --track [remote-name]/[branch],该命令就会在本地创建一个[branch]分支用于跟踪远端的[branch]分支。 +当然,我们还需要了解一个删除远程分支的命令git push origin --delete [branch],需要注意的是这个命令基本上只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。 +Git 常用命令 +挑了一些比较重要 Git 命令,我把个人常用的命令使用代码块标记出来了。 + + + +命令 +作用 + + + + +git init +将一个目录转变成一个 Git 仓库 + + +git clone +从远程克隆一个仓库到本地,它是多个命令的组合, + + +git add +将内容从工作目录添加到暂存区 + + +git commit +将暂存区文件在数据库中创建一个快照,然后将分支指针移到其上 + + +git commit -a -m [msg] +git add和git commit的组合 + + +git status +展示工作区及暂存区域中不同状态的文件 + + +git status -s +比git status展示的内容更加简洁 + + +git diff +对比工作目录文件和暂存区快照之间的差异 + + +git diff --cached +对比已暂存的差异 + + +git reset +根据你传递给动作的参数来执行撤销操作 + + +git rm +从工作区,或者暂存区移除文件 + + +git clean +从工作区中移除不想要的文件的命令 + + +git checkout +切换分支,或者检出内容到工作目录 + + +git branch +列出你所有的分支、创建新分支、删除分支及重命名分支 + + +git checkout -b [branch] +创建新分支并切换到该分支 + + +git log +展示历史记录 + + +git log --pretty=oneline +简洁版历史记录 + + +git merge +合并一个或者多个分支到已检出的分支中 + + +git stash +临时地保存一些还没有提交的工作 + + +git pull +git fetch 和 git merge 命令的组合体 + + +git push +将本地工作内容推送到远程仓库 + + +git push origin local_branch:remote_branch +比git push更加详细的推送 + + +git checkout --track [remote-name]/[branch] +在本地创建一个分支用于跟踪远程同名分支 + + +git remote -v +显示所有远程仓库地址 + + +git remote set-url origin [url] +设置远程仓库地址为url + + + + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/wjM4wf9IE/index.html b/wjM4wf9IE/index.html new file mode 100644 index 00000000..47f4b8d2 --- /dev/null +++ b/wjM4wf9IE/index.html @@ -0,0 +1,300 @@ + + + + + + + + MySQL | Guanngxu + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+ 标签:# + MySQL +
+ + +
+

+ + Schema 与数据类型优化 + +

+ +
+ + + + +
+ +
+ +参考内容: +《高性能 MySQL(第三版))》 + +选择优化的数据类型 +世面上常见的数据库大多支持了多种多样的数据类型,选择正确的数据类型对于获得高性能至关重要,一般都需要遵循如下的几个原则: + +更小的通常更好:更小的通常更快,因为占用着更少的磁盘、内存和 CPU,并且处理时需要的 CPU 周期也更少; +简单就好:简单数据类型的操作通常需要更少的 CPU 周期; +尽量避免 NULL:如果查询中包含可为 NULL 的列,就会使得索引、索引统计和值比较变得复杂,因此在设计表是最好指定列为 NOT NULL。 + +整数类型 +在 MYSQL 中可以为整数类型指定宽度,例如INT(11),但是这对大多数应用是没有意义的,它不会限制值的合法范围,只是规定了 MySQL 的一些交互工具(如 MySQL 命令行客户端)用来显示字符的个数。对于存储和计算来说INT(1)和INT(20)是相同的。 +字符串类型 +需要注意的是当 MySQL 存储 CHAR 值时,它会删掉所有的末尾空格,因为 CHAR 值会根据需要采用空格进行填充以方便比较,这导致的问题就是你使用 CHAR 存储的string 会变成string。CHAR 的好处在于它是定长的,很适合存储像 MD5 值一样的定长值,定长值的 CHAR 类型不易产生碎片,而且对于非常短的列 CHAR 也会比 VERCHAR 好,比如CHAR(1)只需要一个字节,而VERCHAR(1)则需要两个字节,因为它还需要一个字节来存长度。 +VERCHAR 类型在存储可变长字符串时,会比 CHAR 更节省空间,它需要使用 1 或者 2 个额外的字节记录字符串的长度。但由于行是变长的,当一个行占用的空间增长,并且在页内没有更多的可用空间可以存储,就容易产生碎片。 +使用枚举代替字符串 +有时候可以使用枚举列代替常用的字符串类型,枚举列可以把一些不重复的字符串存储成一个预定义的集合,而且 MySQL 在存储枚举时非常紧凑,会根据列的数量压缩到一个或两个字节。比如下面的例子: +CREATE TABLE enum_test( + e ENUM('fish', 'apple', 'dog') NOT NULL +); + +INSERT INTO enum_test(e) VALUES('fish'), ('dog'), ('apple'); + +SELECT e+0 FROM enum_test; + +# result ++-----+ +| e+0 | ++-----+ +| 1 | +| 2 | +| 3 | ++-----+ + +可以看到使用枚举类型后,上面三行数据实际上存储为了整数,而不是字符串,而且还有一个让人吃惊的地方:枚举字段是按照内部存储的整数而不是定义的字符串进行排序的,这一点需要特别注意,不然在写程序时容易中犯错。当然你也可以在查询时使用FIELD()函数显式地指定排序顺序。 +可以看到上面 +范式和反范式 +关系型数据库有设计范式的概念,这一点在大学的数据库课程中肯定都会提及。因为有比较高的范式,那么就只有很少或者没有重复的数据,因此在 UPDATE 时只需要修改更少的数据;高范式的表通常也更小,因此占用的内存也会更小,操作起来也会更快...... +但是高范式也带来了另一个缺点,比较好的范式通常意味着需要关联,稍微复杂一点的查询就需要使用 JOIN,关联的代价是昂贵的,甚至让一些索引策略失效;而如果不需要关联,即使某个查询需要全表扫描,当数据比内存大时可能会比关联查询快的多。所以一般都会根据实际情况将范式与反范式混用,完全的范式化和完全的反范式化都是实验室才有的东西。 +缓存表和汇总表 +这里的「缓存表」和「汇总表」并没有什么标准的含义。我们用「缓存表」来存储那些可以从其他表获取,但是获取的速度很慢的数据;而「汇总表」则用来保存那些使用 GROUP BY 语句聚合数据的表。毫无疑问,我们存这些冗余数据也是为了性能。 +比如近两年各种应用流行的年终报告,每次网易云音乐的年终报告都会把朋友圈撑满,其它类似于缓存一个用户的朋友数、一个文件的下载次数等等。这些数据实时计算的开销是很大的,而且多数情况下用户也等不起实时计算的时间,一般的解决方案都是通过增加计数器表(缓存表)来解决这个问题。 +计算机科学中总是伴随着双面性,上面的计数器表带来性能提升的同时也带来了并发问题。网站的每一次点击都会导致对计数器的更新,对于任何想要更新这一行的事务来说,这条记录都有一个全局的互斥锁,这会使得这些事务只能串行的进行。每一次点击都会触发下面的语句,但大量的点击伴随着该行数据的互斥锁,想想性能也不会提升到哪里去吧。 +UPDATE hit_counter SET cnt = cnt + 1; + +大多数应用都是读查询居多,为了提升读查询的速度,经常会需要增加一些额外的索引,增加冗余列、汇总表、缓存表等等。但是不要忘了这些技巧也会增加写查询的负担,还会增加开发难度,因此应该根据实际应用场景来做权衡。 +加快 ALTER TABLE 表的速度 +MySQL 执行大部分修改表结构的方法都是用新的结构创建一个空表,然后从旧表中查出所有数据插入到新表,然后删除旧表。在内存不足、表很大、索引多的情况下会花费很长的时间。一个很严重的缺点是大部分 ALTER TABLE 操作将导致 MySQL 服务中断。 +对于常见的场景我们有两种技巧避免服务中断。一种是先在一台不提供服务的机器上执行 ALTER TABLE 操作,然后和提供服务的主库进行切换;另一种技巧是「影子拷贝」,即用要求的表结构创建一张和源表无关的新表,然后通过重命名和删除表的操作交换两张表。 + +
+ + Read More ~ +
+
+
+ + + + + + +
+ +
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/xdxGDHzvU/index.html b/xdxGDHzvU/index.html new file mode 100644 index 00000000..ce9d71cf --- /dev/null +++ b/xdxGDHzvU/index.html @@ -0,0 +1,550 @@ + + + + + + + + 半导体功率器件 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 半导体功率器件 +

+ + +
+ +
+

高中时候我们在化学课程中学过元素周期表,「氢氦锂铍硼、碳氮氧氟氖......」倒背如流,在元素周期表的中间三、四、五族元素定义为半导体元素,所谓半导体是根据其导电能力来定义的,我们可以通过一定的半导体工艺来改变其导电能力。

+

以硅(Si)为例,硅是处在第四族的元素,它的外部有 4 个电子,所以硅的稳定结构是形成下图所示的及其稳定的共价键结构。

+
+

硅的两侧对应的是三族和五族的元素,三族的元素意味着外层有 3 个电子,五族的元素意味着外层有 5 个电子。如果把三族元素插入到四族的硅当中,由于硅想形成稳定的四个共价键结构,所以会存在一个空置的位置,我们称之为空穴,这个空穴是具备一定的正电荷的能力的,如此就形成了 P 型半导体

+
+

同理,若使用五族元素与硅进行掺杂,就会多出一个可移动的电子,即存在自由电荷,形成了 N 型半导体

+
+

PN 结(普通二极管)

+

当我们把 P 型半导体和 N 型半导体进行组合后,即可得到最基本的二极管(PN 结)。在 P 型半导体中存在高浓度的空穴(正电荷),在 N 型半导体中存在高浓度的电子,浓度高的载流子会自然而然向浓度低的区域进行扩散。

+

由于载流子扩散,最终会形成一个势垒,这是一个空间电荷区,也称之为耗尽层。可以发现 PN 结存在 P 区、耗尽层、N 区三个区域,这几个区域都是呈现电中性的,不管是空穴还是电子想要到达另一个区域,都必须要穿过耗尽层,即耗尽层会阻碍空穴和电子的运动,因此整个 PN 结在没有外界干扰的情况下,是不具备导电能力的。

+
+

当我们从外界施加 N 到 P 的电场时,即 PN 结反偏。此时外界电场与耗尽层电场是同向的,所以在外部电场的作用下,耗尽层的宽度会被加强,于是 PN 结的导电能力就变得更弱,因此就呈现了一个无导电能力的特性。

+

当然导电只是一种相对情况,即便空间电荷区变宽了,也不能百分百保证说就完全没有导电能力,因为还是有一定的空间电荷浓度,在这样的情况下会有微弱的电流流经 PN 结,意味着系统存在一个反向电流,这就是二极管一个比较重要的漏电流参数。

+
+

当外部施加的电场是从 P 到 N 时,即 PN 结正偏。外界电场的效果是使耗尽层变窄,加强了 P 区内空穴往 N 区内移动的能力,扩散电流远大于漂移电流,形成了一个正向导通电流。

+
+

最终二极管将呈现如下的导通特性,当正向电压大于势垒电压时,二极管开始导通。当施加反向电压时,二极管将截止,当反向电压大到一定程度后,二极管就会被反向击穿,即二极管损坏的过程。

+
+

功率二极管

+

既然谈到了「功率」二字,那么更加关注的就是二极管承载电流、电压的能力了。如何把二极管承载电流、电压的能力加强呢?根据上文关于二极管的介绍可以知道,将耗尽层加宽可以承载更大的电压。

+

图中中间 n- 为轻度参杂区域,下面 n+ 为重度参杂区域,这个参杂就导致了耗尽层的加宽,当然也导致导通损耗更大,不过也正因为如此,功率二极管才更加能耐压。

+
+

我们以非同步 BUCK 电路为载体,来说明一下功率二极管的变化过程。

+
+
    +
  1. 图中(1)部分指二极管导通,有一个小小的二极管导通压降,因此曲线没有贴着 x 轴;
  2. +
  3. 图中(2)的位置由于二极管承受的是反向电压,此时它关断了,所以电压为负;
  4. +
  5. 图中(3)二极管需要经历一个从没有电压到有外加电压的变化,当电压加到二极管上时,二极管中的载流子流动的趋势逐渐增大,宏观表现出来是电阻慢慢变小的过程,但是电流保持不变,所有会有一个小尖峰,这一小段时间也会导致整体功率的损耗,开关频率越高,这个导通过程导致的损耗越多;
  6. +
  7. 图中(4)处伴随系统从通到断的状态变化,大规模载流子需要进行重新分配,这个重新分配表现出来就是电流,而且这个电流与主电流相反,所以会看到一个反向的电流,而且这个反向电流会施加在主电路里面。这一段反向电流又分为两部分,下降阶段是之前外加电压时,PN 结中从 P 区域移动到 N 区域的载流子移除(恢复)过程,即从正偏到反偏的过程,正偏时空间电荷区非常非常窄,此时要进入反偏状态,空间电荷区需要加强,载流子需要重新分配,外部激励会移除不必要的空间电荷。电流上升的过程,即二极管又变成一个耐压器件了,也就是空间电荷区加宽,更多的载流子会不均匀的分布在两端。整个过程不可避免的需要移动电荷,而电荷的聚集效应可以认为就是一个电容的效应,当我们需要施加电压时,电压的增加就会需要额外的电荷,电荷不断聚集提供相反电荷,使其电压不断增加,以致增加到刚好截止输出电压为止。
  8. +
+

MOS 管

+

以 NMOS 为例,它以 P 型半导体衬底,以 N 型半导体作为导电沟道,金属部分作为栅极(Gate),氧化部分(SiO2)作为绝缘层,两端分别为源极(Source)和漏极(Drain),从物理结构可以看出 MOS 管的源极和漏极是可以互换的,不像三极管有严格的顺序。

+

在栅极和源极施加电压,随着电压的不断增大,导电沟道将逐渐形成,当导电沟道刚好形成时的电压,称之为开启电压。外加电压继续增大,导电沟道将变得越来越宽,即导电能力越来越强。

+
+

PMOS 相比 NMOS 更加容易驱动,只需要 VGS 小于一定值即可导通。但是 PMOS 的导通电阻比 NMOS 要大,并且成本也比 NMOS 要高,所以比 NMOS 的实际应用场景要少许多。

+
+

功率 MOS 管

+
+

对比前文普通 MOS 管,可以看到源极、栅极、漏极是分开的,顶上那个灰色的板子是金属板。而功率 MOS 管在这个基础上做了一点创新,下图中的阴影部分就是金属板,可以发现总共只有两个金属板,上面的金属板把 N 区和 P 区都给连起来了,所以即使在栅极没有加电压的时候,也会存在一个天然的二极管通道,但是普通 MOS 管是没有体二极管通道存在的。同时由于是功率 MOS 管,所以也会想办法将耗尽层加宽,以增加其耐压能力。

+
+

体二极管和耐压能力的加强是功率 MOS 和普通 MOS 的区别。

+
+

功率 MOS 管的正向导通能力就是涉及「场效应」了,所谓的场效应即意味着外部可以通过电场来控制其内部载流子的浓度,在栅极施加正电压时就会产生一个电子的导电沟道,由于整体是 N 型半导体衬底,所以整体也就形成了一个电子的导电沟道,并且该沟道支持电子的双向移动。

+
+

如下图所示是功率 MOS 管的等效电路模型。其主要损耗由三部分组成,分别为导通损耗、开关损耗(开通损耗和关断损耗)、驱动损耗。其中导通损耗与开关损耗容易理解,驱动损耗作何理解呢?MOS 并不像二极管是一个被动型器件,MOS 管开或关的行为都需要能量作为代价,就好比要打开机械开关需要用手去按压,这个过程所消耗的能量就是驱动损耗。

+
+

晶体管

+

二极管只有一个 P 型半导体和一个 N 型半导体结合,如果再加一个 N 型半导体(或 P 型半导体)即构成了晶体管(三极管),晶体管有集电极、发射极、基极三个极。

+
+

需要注意的是三极管的集电区和发射区掺杂浓度是不一样的,其中基区多子少且做的很薄,而发射区的多子浓度很高,集电区多子浓度相对较低但面积大。不管三极管是正接还是反接,三极管都处于截止状态,这是因为三极管可以看作两个二极管反向相连,不论如何接都会有一个二极管处于截止状态。

+
+

为了能让三极管导通,我们在基极和发射极再施加一个电压,此时二极管开始导通,发射区的自由电子就可以源源不断的流向基区,但是基区的掺杂浓度很低且很薄,基区短时间内吸收不了太多的电子,只有一少部分电子能与空穴复合形成基极电流,而大部分被吸引到了集电区,形成集电极电流,也就是三极管的输出电流。

+
+

流过基极的电流越大,流到基区的自由电子也就越多,相应的被吸引到集电区的电子也就更多,这就是三极管小电流控制大电流的原理。基区做的很薄是为了让发射区的电子更容易进入集电区,浓度很低视为了形成更小的基极电流,这样才会有更多的自由电子流向集电区。

+

IGBT

+

三极管工作时涉及载流子的注入和抽离所以会很慢,由于其性能的关系正在逐步退出历史舞台,因此需要对其进行改进,改进后的器件就是 IGBT,如下图所示。

+
+

可以发现 IGBT 是一个受 MOS 管控制的 BJT,即同时继承了 MOS 管快速和 BJT 大电流的优点。当然,它也有缺点,并且缺点主要来自于 BJT 关断较慢的问题,因为当 MOS 管门级信号撤出时,并不能立马把电流都抽走,所以电流会经历一段下降时间。

+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/xhU3PP5t5/index.html b/xhU3PP5t5/index.html new file mode 100644 index 00000000..114815f2 --- /dev/null +++ b/xhU3PP5t5/index.html @@ -0,0 +1,461 @@ + + + + + + + + 说一下戴牙套的感受 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 说一下戴牙套的感受 +

+ + +
+ +
+

快要离开深圳时候和老叔一起吃饭喝酒,老叔看着我突然来了一句:“小刘啊,那个你年终奖也发了,你回去把你的牙齿弄一弄,把那个缝关上,不然你的命就一直不好,漏财!”

+

说实话,我以前从来没有想过自己的牙齿,还一直觉得自己的那个缝好玩,小时候还常借助门牙那个缝隙,加上自己的舌头抵压,压力会让口水从牙缝挤出去,达到喷射的很远的效果,为数不多的几次口水战我都比较占优势。

+

可以但是经叔叔这么一说,我再照镜子时候就不再觉得它好玩了,越看越不美观了。所以有事没事的时候会忍不住去查查正畸方面的信息。回到成都后老叔还是经常给我打电话,每次视频的时候老叔看到我没有戴上牙套就会骂我催我,本来想回到成都就把老头给糊弄过去算了,但自己却是越看越不美观。

+

从有矫正的想法到真正戴上牙套中间经历了半年的时间,这半年时间就是查查网上的资料,问问身边有做过正畸的朋友,当然还有一个重要的事情是了解各种正畸方案的价格,像什么陶瓷自锁、金属自锁、隐适美等等,其实这些乱七八糟的事情都是一个心理建设的过程,要突破这个心理障碍还真不容易。

+

到现在我已经戴了快一个月的牙套了,刚开始戴上那几天会很疼,连话都不想说。我戴的是进口金属自锁那种大钢牙,刚开始上面那个金属丝还老是滑动容易戳我口腔,如果滑动的不太多我就自己把它拨回去了,如果滑动的太多了就到诊所那里去让牙医搞定,我到诊所就 3 分钟的路程,这一点还是非常便利的。

+

做矫正这个事儿可能很多人都觉得麻烦,不能随便吃东西,每吃一顿饭就要刷牙,而且一个大钢牙在嘴里还很丑,牙齿也疼.........就干脆放弃了,但是以我这一个多月的感受告诉你,戴牙套的好处要大于太多坏处。

+

人人都想减肥却又管不住自己的嘴,很多人在睡前总是想吃点东西,导致体重增加的非常快,尤其很多所谓无法推脱的社交饭局经常吃到半夜一两点,戴上牙套之后保证你能管好自己嘴,我自己戴上牙套不到一个月已经瘦了 4 斤多了,这是戴牙套之前没有想到的意外收获。

+

当然,更加具体的实施方案还得取决于自己的牙齿具体情况,有的人做矫正可能还需要拔几颗智齿,可以顺道消除智齿发炎的地雷。另外一个拔智齿的好处可能会让仙女们比较开心,那就是智齿拔了脸会变小啊,哈哈哈哈哈!!

+

对我个人来说还有另外一个好处,那就是每天只能吃很少的东西怎么才能让自己更健康的成长,所以抓紧时间去看了《你是你吃出来的》这本书,才知道自己以前对于营养的认知是多么浅显,我忍不住想要对正在看文章的你做个简单的科普!

+

我们吃东西不仅要关注能量,更要注重七大营养素(碳水化合物、脂类、蛋白质、维生素、矿物质、水、膳食纤维)。像鸡蛋、牛奶、蔬菜、水果、坚果、肉类、(深海)鱼、动物肝脏等,都需要均衡摄入。《你是你吃出来的》一书中做了大量苦口婆心的讲解,也纠正了大家平时的一些错误观点,比如很多人生病了不舒服就咸菜加上喝白粥打发,觉得浓浓的白粥里面很有营养,实际上白粥是没有什么营养的;很多地域晚上都习惯吃面食,因为面食比较容易消化,但面食里面的主要要成分是碳水化合物,非常容易让你血糖快速的升高,碳水化合物摄入过多的结果只会让你越来越胖。

+

牙套在嘴里也不太好咀嚼,之前又看到朋友圈的人推荐「若饭」,正好趁这次机会尝试了一下若饭,口味什么的并不是多好,但液体版的若饭对我来说很方便,像考研党、工作狂或者其它比较忙的人,可以买一点若饭在那里备着,一分钟的时间就能解决一顿饭,比上个厕所都要快的多。

+

若饭是一种高密度的营养餐,我们大多数人可能觉得自己平时吃的很健康,但估计现实情况和自己认为的恰好相反,比如中国人基本对钠盐的摄入都严重超标,很多人都摄入了太多的碳水化合物,可以看若饭的营养成分表,各方面还是基本兼顾到了的。若饭不仅有液体版,同时也有粉末版供你选择。

+
+

当然如果能吃到天然的食物最好吃天然新鲜的食物,像若饭这样直接对标美国 Soylent 的产品也不可能全方面兼顾到,比如长期食用可能会影响肠胃功能(我猜的),所以经常吃吃水果、坚果这些小零食,没事儿的时候约几个朋友散散步,偶尔来一顿美食大餐是绝对有必要的。

+

最后再推荐你看一个一席的演讲视频:减盐这件大事

+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/xxKgB7-Zw/index.html b/xxKgB7-Zw/index.html new file mode 100644 index 00000000..23714008 --- /dev/null +++ b/xxKgB7-Zw/index.html @@ -0,0 +1,470 @@ + + + + + + + + 互相看不起|断舍离|《良医》 | Guanngxu + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ +
+
+

+ 互相看不起|断舍离|《良医》 +

+ + +
+ +
+

以前在朋友圈提到过这样一个现象,重庆人和四川人说的都是四川话,但是大部分重庆人会说他们说的是重庆话,说「川渝是一家」的通常也都是四川人。

+

在深圳也有一个很怪的现象,两个客家人谈话会用客家话,两个潮汕人谈话会用潮汕话,两个广东人谈话会用粤语,反正就是尽可能用更小众的语言。

+

想了一下,故意用第三方听不懂的语言,实际上是很欠考虑的,如果是刚见面用方言寒暄几句我觉得还行,但是后面谈话就应该使用大家都能听懂的语言了。

+

疫情期间大家都没法出去玩,我和老叔倒是出去爬了爬山,村里的荔枝山别人进不去,整座山就我和叔两个人,单从疫情这个角度讲,荔枝山是比大部分地方都要安全的。

+

我自己可以在疫情期间爬爬山,结合我自己的感受,加上前段时间的「大奔进故宫」事件。我发现人们并不是痛恨特权,而是痛恨自己没有特权。大部人痛恨的不是腐败,痛恨的是自己没有腐败的机会。

+

上面四川和深圳两个例子也差不多是出于这样的优越感,鉴于四川除了成都外,其它地方投资的回报率太低,穷地方的人总会羡慕富有的地方,说川渝一家的人大概率不是成都人。

+
+

春节期间看了一本《断舍离》,它讲究的是内部的自觉自省,虽然整本书挺啰嗦的,完完全全可以用一篇几千字的文章代替,但是它传达的人生整理理念很值得参考,感兴趣的读者大人可以在微信读书中阅读此书。下面是一段摘自书中的内容。

+
+

我们习惯于思考「有效性」,却往往忽略了作为「有效性」前提的「必要性」,对物品也常常陷入这样的定式思维,导致家里各种杂物堆积,这些杂物给人的压迫感,以及狭窄空间给人的阻塞感,会让人的思维变得迟钝、行动变得迟缓!

+
+

借助「断舍离」的理念,我删了 400 多个微信好友,处理了一些不会再使用的家具和书籍,才发现之前一直舍不得扔的那些东西扔了对我的积极作用更大,以前写过的一篇你如果只是一直囤干货,那永远也不可能进步,核心思想和断舍离基本一致,遗憾的是自己当时写下这篇文章后,竟然不懂得延伸到其它领域。

+

可能一部分人有读书摘抄语录的习惯,我个人在阅读技术书籍或是扫除我知识盲点的时候,我也会通过记笔记来加深自己的理解。想想自己强迫症式的记笔记面面俱到其实也是在浪费时间,大部分笔记自己都不会再去看第二遍的,舍弃一些不必要的形式会让自己的阅读更有收获。

+

还发现自己另外一个错误观点,我不管是写字还是看书都比大部分人慢,一直都认为是自己看书的方法不对,现在才发现问题的根本原因。是因为我对具体的领域不熟悉,所以看这个领域的书籍才会速度很慢,如果对这个领域熟悉了,那一目十行甚至几十行的速度应该都可以达到。结论就是书读的少了导致读书的速度慢。

+
+

推荐一部美剧——《良医》,全剧的场景基本是在医院,但有深度的内容基本都和医院无关,除了最基本的医疗科普外,更多的是对家庭、爱、职场、心理等的探讨,下面是我摘的两句台词。

+
+

Whenever people want you to do someting they think is wrong, they say it’s “reality”.
+当人们想让你做他们认为错误的事时,他们总会说这就是现实。

+
+
+

Very few things that are worthwhile in life come without a cost.
+人生中那些有意义的事大多是有代价的。

+
+ +
+
+ + + + +
+
+
+ +
+ + + + + + +
+ + + + + + + +
+ + + + + \ No newline at end of file