├── false_cache.png ├── images ├── 06-05.Age.png ├── 06-01.Developers.Rols.png ├── 06-02.Coding.as.a.Hobby.png ├── 06-06.Popular.Languages.png ├── 06-07.Loved.Languages.png ├── 06-08.Technology.Circle.png ├── 08-01.Authorization.Page.jpg ├── 07-09.Hours.Worked.Per.Week.png ├── 06-04.Years.Coding.Professionally.png └── 06-03.Years.Since.Learning.to.Code.png ├── README.md ├── 10-leetcode crack 001.md ├── 04-Introduction.To.Session.md ├── 02-Online.Code.IDE.Introduction.md ├── 05-The.Adventure.Of.A.Web.Request.md ├── 01-Meaning.Of.Life.md ├── 03-Introduction.To.JWT.md ├── 06-Some.Thoughts.About.StackOverflow.Developer.Survey.Results.2019.md ├── 08-Introduction.To.OAUTH2.md ├── 09-Something about memory and cache.md ├── 07-Reunderstand.C++.ShallowCopy.and.DeepCopy.md └── 00-How.To.Set.Up.An.High.Efficiency.Work.Enviornment.md /false_cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolshellx/articles/HEAD/false_cache.png -------------------------------------------------------------------------------- /images/06-05.Age.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolshellx/articles/HEAD/images/06-05.Age.png -------------------------------------------------------------------------------- /images/06-01.Developers.Rols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolshellx/articles/HEAD/images/06-01.Developers.Rols.png -------------------------------------------------------------------------------- /images/06-02.Coding.as.a.Hobby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolshellx/articles/HEAD/images/06-02.Coding.as.a.Hobby.png -------------------------------------------------------------------------------- /images/06-06.Popular.Languages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolshellx/articles/HEAD/images/06-06.Popular.Languages.png -------------------------------------------------------------------------------- /images/06-07.Loved.Languages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolshellx/articles/HEAD/images/06-07.Loved.Languages.png -------------------------------------------------------------------------------- /images/06-08.Technology.Circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolshellx/articles/HEAD/images/06-08.Technology.Circle.png -------------------------------------------------------------------------------- /images/08-01.Authorization.Page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolshellx/articles/HEAD/images/08-01.Authorization.Page.jpg -------------------------------------------------------------------------------- /images/07-09.Hours.Worked.Per.Week.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolshellx/articles/HEAD/images/07-09.Hours.Worked.Per.Week.png -------------------------------------------------------------------------------- /images/06-04.Years.Coding.Professionally.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolshellx/articles/HEAD/images/06-04.Years.Coding.Professionally.png -------------------------------------------------------------------------------- /images/06-03.Years.Since.Learning.to.Code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolshellx/articles/HEAD/images/06-03.Years.Since.Learning.to.Code.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Articles 2 | 3 | This repo is used to write the articles collabratively. 4 | 5 | And those articles would be publised to Coolshell WeChat, and some of them will be synced to CoolShell.cn 6 | 7 | 8 | for the article file name convension, we prefer using the following format: 9 | 10 | `01-This.Is.An.Article.File.Name.Example.md` 11 | 12 | - `01-` is the sequence number used to sort the articles. 13 | - Every wold starts with uppercase and separated by `.` 14 | -------------------------------------------------------------------------------- /10-leetcode crack 001.md: -------------------------------------------------------------------------------- 1 | ### 茴香豆的茴字有几种写法 之 "Two Sum" 2 | 3 | 这个系列用来总结和整理leetcode的算法题目,我的原则如下: 4 | 1.我只会整理leetcode中的“赞”比“踩”多的题目,因为这类题目描述清晰,测试用例也比较合理,不会有太tricky的情况。我基本是按照编号顺序整理。 5 | 2.每道题目的代码会有python和c++两种,来源大都是leetcode的讨论区。 6 | 7 | 今天先从第一题开始: https://leetcode.com/problems/two-sum/submissions/ 8 | 9 | 这道题应该是每个码农必刷的题目了,但是并不是看上去那么简单,比如如果只能对所有元素遍历一次的话,求解?哈哈,可以参考下面的代码。 10 | 11 | Python的解法: 12 | https://leetcode.com/problems/two-sum/discuss/17/Here-is-a-Python-solution-in-O(n)-time 13 | class Solution(object): 14 | def twoSum(self, nums, target): 15 | buff_dict = {} 16 | for i in range(len(nums)): 17 | if nums[i] in buff_dict: 18 | return [buff_dict[nums[i]], i] 19 | else: 20 | buff_dict[target - nums[i]] = i 21 | 22 | C++的解法: 23 | class Solution { 24 | public: 25 | vector twoSum(vector& nums, int target) { 26 | map dict; 27 | for (int i = 0; i(); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /04-Introduction.To.Session.md: -------------------------------------------------------------------------------- 1 | # 浅析 Session 2 | Session 的概念其实蛮大的,上下文不同意义也不太一样。比如 OSI Model 里传输层/会话层/应用层也有会话的概念。不过平时我们一般都指应用层上的,也就是 HTTP Session,这类 Session 大体分为服务端会话和客户端会话。 3 | 4 | 我们都知道 HTTP 是无状态的,也就是客户端和服务端在完成一次 HTTP 请求/响应后就结束了,下一次请求与之前的没有联系。这就导致我们单从请求上没法直接获取到是谁在请求,所以我们必须在每次请求时**携带**一些数据来标识自己的身份,好让服务端能够识别出我们。 5 | 6 | ## Client-side sessions 7 | 一个完整的 web 应用几乎都需要用户系统,也就是说每个用户在登录/授权后发起的每一个请求都能在服务端找出对应用户,且互不混淆。比如常见的购物车模型,A 用户加入某件商品到购物车,不能影响到其他用户的购物车。 8 | 9 | 由于 HTTP 的无状态特性,要解决这个问题势必需要引入一种会话跟踪机制,以便能把每个请求都能对应上各自的用户身份。最早使用的是 Cookie 方案,因为 Cookie 在浏览器市场的支持率很高,事实上已经是一种标准,所以很自然的就会想到把用户身份通过请求放入 Cookie 中,之后的每个请求都带上该 Cookie,这样就能识别出用户了。 10 | 11 | 这种通过使用 Cookie 和加密技术来维护管理状态,且不需要在服务端存储数据的方式叫做客户端会话(Client-side sessions)。这种机制在一些环境下能工作的很好,因为足够简单和高效,然而单纯的把数据存在客户端很容易被拥有进入客户端机器的人或软件给篡改,所以要使用这套方案对会话的加密性和完整性有很高的要求: 12 | 13 | 1. 加密性:除了服务器之外,什么都不应该能够解释会话数据。 14 | 2. 完整性:除了服务器之外,没有任何东西可以操纵会话数据(意外或恶意)。 15 | 3. 真实性:除了服务器之外,任何东西都不应该能够启动有效会话。 16 | 17 | 还有一个在当时不是问题但是现在是个问题的问题就是**跨平台性差**,因为这种模式全靠浏览器的 Cookie,一旦平台或者设备禁用或不支持 Cookie,就只能“认怂”了。 18 | 19 | 要做到以上三点,想想就头疼,而且你会发现如果将会话放到服务端去管理,上面的几点几乎是迎刃而解。OK,那我们接下来讲讲服务端会话(Server-side sessions)技术。 20 | 21 | ## Server-side sessions 22 | 服务端 Session 是目前非常成熟的会话管理方案,它的基本原理是服务端为每一个 Session 维护一份会话数据,服务端会为每一个客户端发送一个全局唯一的标识,后者通过传递这个唯一标识向服务端表明身份,从而访问响应的会话信息数据。 23 | 24 | 创建 Session 大致分为三个步骤: 25 | 1. 生成全局唯一标识符(SessionID,常见的如 PHP 的 PHPSESSID,Java 的 JSESSIONID 等)。 26 | 2. 创建会话数据空间。由于会话操作非常频繁,所以一般会在内存中创建相应的数据结构,但这种情况下,系统一旦出现故障,所有的会话数据就会丢失。稳妥的做法是将其持久化,虽然会增加 I/O 开销,但有利于 Session 的共享和稳定。 27 | 3. 将 SessionID 发送给客户端。 28 | 29 | 传递 SessionID 一般有两种常用方式:Cookie 和 URL重写。 30 | 1. Cookie:服务端只要把 SessionID 塞入 Cookie 中发送到客户端,客户端在每次请求时都会带上这个标识符。 31 | 2. URL 重写:服务端在响应体中返回 SessionID,而后客户端在请求时将其拼接在 URL 上从而达到传递效果。一般如果客户端禁用了 Cookie 才会使用这种方式。 32 | 33 | 因为现代浏览器普遍都实现了 LocalStorage/SessionStorage,也就是说客户端除了 Cookie 外也有存储数据的能力,所以你也可以把这个 SessionID 存在本地,然后每次请求时放入自定义 Header 头中。 34 | 35 | ## 拓展 36 | > 从 HTTP/1.1 开始引入了长连接的机制(keep-alive-mechanism),可以在多个请求/响应间保持连接。HTTP/2.0 更进一步,可以在单次连接中并行多个请求/响应。 37 | 38 | 一开始我觉得挺纳闷,这长连接机制不是和 HTTP 的无状态是矛盾的吗。一方面 HTTP 在完成一次请求后就结束了,多个请求间是没有关系的,也就是所谓的无状态(stateless);但另一方面引入的这个长连接机制不就能够让状态保持下去,也就是有状态了。 39 | 40 | 首先我们要明确,**长连接指的是一次 TCP 连接中可以有多个 HTTP 请求/响应**,详见 [HTTP persistent connection - Wikipedia](https://en.wikipedia.org/wiki/HTTP_persistent_connection)。所以对于单个 HTTP 请求/响应依然是无状态的。 41 | 42 | ## 参考链接 43 | - [PHP: Sessions - Manual](https://www.php.net/manual/zh/book.session.php) 44 | - [HTTP cookie - Wikipedia](https://en.wikipedia.org/wiki/HTTP_cookie) 45 | - [Hypertext Transfer Protocol - Wikipedia](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#HTTP_session) 46 | - [Session (computer science) - Wikipedia](https://en.wikipedia.org/wiki/Session_(computer_science)) 47 | -------------------------------------------------------------------------------- /02-Online.Code.IDE.Introduction.md: -------------------------------------------------------------------------------- 1 | # 在线代码编辑器 2 | 3 | 在线代码编辑器或是在线IDE是个很有意思的产品,这种在线的代码编辑器可能并不适合正式的项目开发,但是其还有一定的优势的,这些优势如下: 4 | 5 | 1. 轻量级的在线代码编辑器,不需要本机环境,只要有网络和浏览器,就可以任意位置用任意设备进行编程。 6 | 2. 方便代码分享,尤其对于一些用于文章内的代码示例。 7 | 3. 方便于初学者或是想尝试一段试验型的代码的场景。 8 | 4. 方便教学,面试,结对编程,练习…… 9 | 5. 方便快速地检查代码,或是进行一些比较小的代码修改。 10 | 6. 对于一些比较流行的Serverless的应用,这种在线的代码编辑器就很有用处了。 11 | 12 | 所以,这种在线代码IDE不需要运行环境,打开就用,还是有很多用处的。这也是为什么全世界有很多在线代码编辑器。 13 | 14 | 这篇文章,将会介绍几个不错的在线编辑器。 15 | 16 | ## 前端代码编辑器 17 | 18 | ### StackBlitz 19 | 20 | [StackBlitz](https://stackblitz.com/) 是一个在线的前端代码项目IDE,可以创建Angular,React,Ionic,TypeScript相关的项目,支持在线代码编写和自动处理安装依赖关系、编译、效果的实时预览,不需要命令行的配置,StackBlitz 还支持通过 npm 安装依赖包,并且当你复制粘贴代码进去时,能自动检测缺少的依赖并提示你安装。另外,每一个 StackBlitz 项目都有专属的 URL,因此你能像本地项目一样,新开一个窗口来预览和调试,而且还是热更新的。 21 | 22 | 另外,我们可以看到 StackBlitz 的界面风格和 VS Code 非常相似,除了界面与 VS Code 非常相似,快捷键也几乎一模一样,如果你平时就习惯使用 VS Code,那完全可以无缝切换。 23 | 24 | ![](https://cdn-images-1.medium.com/max/1600/1*uf0RA0ofJi0m_mqdJxkTTg.gif) 25 | 26 | 是的,这得益于VS Code的源代码以MIT协议开源。这使得大家能够免费获得VS Code的核心代码,开发自己的产品。VS Code自带了TypeScript和Node.js的支持,同时,VS Code为编程语言工作者提供了统一的API,即Language Server Protocol 和 Code Debugging Protocol。这使得,只要你实现两个API,就可以获得类似IDE的开发和调试体验。 27 | 28 | 此外,StackBlitz 还支持比如分享、下载等等功能,也可以像 [CodePen](https://codepen.io/)、[Jsfiddle](https://jsfiddle.net/) 或 [Jsbin](https://jsbin.com/) 一样可以把代码share出去。 29 | 30 | ### CodeSandbox 31 | 32 | [CodeSandbox](https://codesandbox.io/) 是一个在线的前端代码项目IDE,支持 React.js,Vue.js,Angular.js,Preact.js,Vanilla,Svelte,Dojo…… 前端框架,和前面的StackBlitz一样,也是直接写直接预览结果,不需要使用什么如npm install的命令行,就可以直接把项目跑起来了,还可以在线的从你的Github把代码同步过来,以及把修改的代码再同步回去。在打开编辑器的时候,还有一个实时显示的预览窗,只要你的代码做了修改,结果就会立马显示在预览窗里了,这种反馈让人很爽。另外,还可以如大多数在线IDE一样,可以做结构编程(多人同时修改)。当然,它也能像 [CodePen](https://codepen.io/)、[Jsfiddle](https://jsfiddle.net/) 或 [Jsbin](https://jsbin.com/) 一样可以把代码share出去,成为一个playgrand。 33 | 34 | 不过,这个前端的在线编辑最强大的还不是这些,去年十月,CodeSandbox 跟StackBlitz一样,把自己的在线编辑集成了VSCode,这使得CodeSandbox和VS Code一样的界面和操作方式。 CodeSandbox 中使用 VSCode 的键绑定、代码模板、命令、多视图编辑等诸多功能,并可以让用户导入其VS Code中的设,并还支持好些VS Code的扩展,这使用得CodeSandbox一下子比StackBlitz强大了好多。 35 | 36 | 这两天(2019年3月),其发布了 **V3** 版本,这是 CodeSandbox 诞生以来最大的更新。其中包括可以直接使用 VSCode 扩展,比如: 37 | 38 | - [VSCodeVim](https://github.com/VSCodeVim/Vim) 扩展,直接在设置中就可以打开。 39 | - [Vetur](https://github.com/vuejs/vetur)扩展,对 Vue 的支持与 VSCode 中是一样的,也包括了对新添加的模板的补全。 40 | 41 | 随着更多VSCode的扩展的支持,未来可以直接随意安装VSCode的扩展,这样一样,就和VSCode没什么两样了。 42 | 43 | 除此之外,在最新的[发布公告](https://hackernoon.com/announcing-codesandbox-v3-4febbaba1963)中,我们可以看到CodeSandbox还整了好多有趣强大的功能,这些都受益于VSCode的插件。比如: 44 | 45 | - **原生的 TypeScript 类型检测**。在 CodeSandbox 中使用 TypeScript 扩展已经和本地 VSCode 中使用有一样的效果了。可以通过 `tsconfig.json` 来配置,可以运行最新版本的 TypeScript。 46 | 47 | ![](https://cdn-images-1.medium.com/max/2400/1*M-lQroL98k2SvFwhVMezfQ.gif) 48 | 49 | - **自动导入**。可以从你自己的文件或者依赖中导入变量 —— 当你使用了相关的外部变量时,会自动帮你添加相关的 `import` 语句。 50 | 51 | ![](https://cdn-images-1.medium.com/max/2400/1*dardCLKUrGIMg6bKpxLCtg.gif) 52 | 53 | - **重构**。可以使用 VSCode 中一样的代码重构功能。 比如,把 promise 版重构成 async 版,或是把一个函数移到另外一个文件中。 54 | 55 | ![](https://cdn-images-1.medium.com/max/2400/1*3AfE1Lrsv5uQ71f4qTzxLA.gif) 56 | 57 | 另外也有一起其他的支持的改进,比如支持 graphql,styled-components 和 yarn.lock 的语法高亮等,现在也支持 UI 的配置和图片的预览。 58 | 59 | ![](https://cdn-images-1.medium.com/max/2400/1*4maOvmdu7HQpiOP5N58-fQ.png) 60 | 61 | 62 | (未完) 63 | -------------------------------------------------------------------------------- /05-The.Adventure.Of.A.Web.Request.md: -------------------------------------------------------------------------------- 1 | 2 | 对于互联网,人们总是高谈阔论,却很少有人愿意去了解电脑、手机、电视这些设备到底是如何被“连接”起来的。 3 | 4 | 人们动动手指,点点鼠标,图片、视频便顺理成章地即时显示在屏幕上。只是,这一切并非理所应当,五光十色的互联网世界之下,是我们在夜以继日地工作。 5 | 6 | 我是一个普通的网络请求。我很渺小,但始终都在履行责任——寻找被指定的资源,再将它们交付到我的主人手中。就像这座城市中成千上万而又默默无闻的快递小哥,穿梭在大厦与楼宇之间,完成使命。 7 | 8 | 不同的是,真正的网络环境可不像现代化城市那么光鲜亮丽。它阴暗、潮湿,充满了未知的危险,如同一座黑暗森林。而我要做的,就是一场不折不扣的冒险。 9 | 10 | ![](https://upload-images.jianshu.io/upload_images/5889935-2c2f953eb2da009e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 11 | 12 | 13 | ## 1 URL与IP到底是什么? 14 | 15 | 我的冒险,要从一张“羊皮卷”说起。那天,浏览器大叔神秘兮兮招呼我过去,告诉我CPU下达了一个命令:派遣使者访问外邦。而我是大叔最得力的门生,也是这类任务最合适的人选。大叔为我准备了一张羊皮卷,上面记录了我这次冒险需要用到的必要信息。 16 | 17 | 当仁不让地,我接过了它,即刻启程。 18 | 19 | 我缓缓展开羊皮卷,第一行赫然写着:「URL: https://www.mail.google.com」,什么是“**URL**”?我在脑中快速回忆着,对了!URL即Uniform Resource Locator的缩写,翻译成中文便是“统一资源定位符”。因为互联网世界存在着不计其数的资源,每一处资源都需要有一个标记来定位它,正如人类城市中的门牌号。 20 | 21 | ![](https://upload-images.jianshu.io/upload_images/5889935-d504e0093fd12b31.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 22 | 23 | 有人可能会认为,既然有了门牌号,找到指定地点不是轻而易举吗?朋友们,我也希望如此,但现实往往没有这么简单。 24 | 25 | 即使有了URL,恕我愚笨,我还是无法直接理解它所指向的目的地。因为这是人类的语言,我无法翻译解读。(其实是人类太笨了,记不住IP地址,需要用方便记忆的域名来代替) 26 | 27 | 对我来说,IP地址才是唯一的坐标。什么是**IP**?IP即Internet Protocol的缩写,中文译为“互联网协议”,一个如雷贯耳的名字,互联网的基石之一,一切依赖互联网通信的软件都得遵循这个协议。 28 | 29 | 那么,如何才能将域名转换为IP地址呢? 30 | 31 | 浏览器大叔在平日工作之余,十分细心,他将用户访问过的站点整理了一份“域名-IP对应清单”。假如是一个已被记录的IP地址,那么他会直接告诉我,我可以立即向着目标IP地址出发,这就是**浏览器缓存**的作用。 32 | 33 | 假如用户输入的URL不在浏览器的记录范围之内,那么操作系统会查找一个名为“hosts”的文件。它是一份文本,记录了域名和IP地址的映射。如果“hosts”能够告诉我目标IP地址,那也能节省我不少工夫。这就是**系统缓存**。 34 | 35 | 此外,还有**路由器缓存**,相信不用我多介绍了,即保存在路由器中的域名-IP映射。 36 | 37 | 这些缓存都能有效帮助我以最快的速度找到相应的IP地址。但是,互联网世界日新月异,各种资源层出不穷。在很多情况下,用户会想要访问一个全新的,任何缓存都没有记录过的域名。 38 | 39 | 为此,人类专门设计了DNS。在这次任务中,我的第一站,就是赶往DNS。为了更短的响应时间与更好的用户体验,我快马加鞭。 40 | 41 | ## 2 关于DNS劫持的记忆 42 | 43 | DNS是什么?全称Domain Name System,是一个将域名和IP相互映射的分布式数据库。 44 | 45 | 全球有很多家DNS服务中心,假如你关心过你的计算机,你会发现,在你的网卡上,有着一项“DNS服务器”的配置项,它设定了我将要抵达的目的地。 46 | 47 | 48 | ![](https://upload-images.jianshu.io/upload_images/5889935-84287dcf94e31fa3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 49 | 50 | 一转眼的工夫,我来到了114.114.114.114DNS中心。 51 | 52 | 53 | 这个地方我来过很多次,表面上风平浪静,实则暗流涌动。我小心翼翼地来到办事大厅,不禁想起了我第一次被DNS劫持的经历。 54 | 55 | 那天,我来到办事窗口,柜员热情地接待了我。 56 | 57 | “先生,请问你想要查询哪个地址?”当时我还是一位新晋的网络请求,涉世尚浅,不知晓一些不可说的条例,于是毫不避讳地回答:“你好,我要去大名鼎鼎的mail.google.com!” 58 | 59 | 柜员的表情一下子凝固了,他上下打量了我一番,然后硬挤出一丝微笑,“好的先生,请稍等。”说完,他便向旁边的同事使了个眼色。我正纳闷呢,突然两边窜出身材魁梧的警卫,架着我强行往一处拖拽。 60 | 61 | 我这才意识到大事不妙,“这是怎么回事?!你们凭什么劫持我?”我发疯了一般嘶吼着。 62 | 63 | “你好,根据本ISP(电信运营商)颁布的条例,世界上不存在你所说的站点,现怀疑你是一个不合规的网络请求,将把你转发至baidu.com的IP地址。你有权保持沉默!”警卫冷漠地望着我。 64 | 65 | 我知道,现在无论如何解释、挣扎都没有用了,只怪自己太年轻。无奈,我只好乖乖就范。 66 | 67 | ![](https://upload-images.jianshu.io/upload_images/5889935-fe00edebc6e36cee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 68 | 69 | 70 | 还好当时正在使用计算机的用户有一些网络知识,当他发现自己输入的是google,返回的是baidu的页面时,并没有怪罪于我,大概是他心中已经猜到了原因。于是他将网卡的DNS配置为:8.8.8.8,这是一个国际上“不存在”的公司提供的DNS服务中心。 71 | 72 | 仅仅这样就能畅通无阻地访问互联网了吗?经历了被**DNS劫持**,我依然不敢放松。 73 | 74 | ## 3. 我遇到过DNS投毒 75 | 76 | 还未休息片刻,浏览器大叔再一次地给我分配了任务:继续尝试请求mail.google.com的资源。 77 | 78 | 人类出国要坐飞机,要办护照。我们网络请求也是这样,全国只有在几个主要城市才会部署国际出口,所有访问境外资源的网络请求,都得经过这儿接受检查。 79 | 80 | 与上次不同,因为这次我要访问的DNS服务器位于海外,所以我首先来到了大中华局域网的上海国际出口。 81 | 82 | 我一路奔波到上海真的是又累又乏,正当我火急火燎地准备过安检,通道附近有一位穿着制服的小伙迎了上来。 83 | 84 | 还没等我开口,他热情地迎了上来:“欧~ 远道而来的朋友,一定是十分疲惫了吧?天气这么热,先喝杯水吧!”我悄悄地打量着他,看他的打扮应该是一位服务人员。 85 | 86 | “国际出口就是不一样啊,服务真到位!”由于确实是太渴了,我放松了警惕。“啊,真清凉,谢...”我一边感叹着,一边接过了小伙递给我的水。 87 | 88 | 可当我第二个“谢”字还没说出口,立即感到一阵头晕目眩。 89 | 90 | “不好!遇上了黑客,这是**DNS投毒**!”我的视线渐渐模糊,小伙的微笑也渐渐似乎变成了狞笑。我尽力搜索着脑海中和这一切有关的知识,想要知道寻求的办法。 91 | 92 | ![](https://upload-images.jianshu.io/upload_images/5889935-e2c4c980fe3f0718.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 93 | 94 | DNS投毒,英文叫DNS cache poisoning,也叫做**DNS污染**。从客户端向DNS服务器发出查询IP的请求,到响应返回到客户端的这段时间里,如果有黑客或者其他一些不可说的设施伪造返回了一个错误的DNS应答,那么用户将不能访问到真正的资源。 95 | 96 | 想到这里,我已经明显感觉难以控制自己的身体了,眼前一黑,就什么都不知道了。 97 | 98 | ## 3 DNS正常解析 99 | 100 | 以前发生过的险情历历在目,如今想起来,我依旧心有余悸。这次,为了确保万无一失,我打起了十二分精神。 101 | 102 | 这次,我已经顺利来到8.8.8.8 DNS服务中心。 103 | 104 | “你好亲,有什么可以帮到您的嘛”办事窗口内传来了软妹子的声音。 105 | 106 | “我想查询域名mail.google.com的IP地址。”我试探性地问,依然不敢松懈。 107 | 108 | “好的呢亲,这边通过树状检索,在顶级域名`com`下,查询到`google`目录,在`google`目录下查询到`mail`,IP地址是xx.xxx.xx.xx呢。” 109 | 110 | ![](https://upload-images.jianshu.io/upload_images/5889935-77412bc9d7159fbb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 111 | 112 | 我松了口气,谢天谢地,终于拿到了结果。可我知道作为一次完整的网络请求,这刚刚开始,这才是万里长征第一步,我得赶紧将这个解析结果带回去,等会还得马不停蹄往返三次建立连接。 113 | 114 | ![](https://upload-images.jianshu.io/upload_images/5666077-d6b7b5665130d54c.jpg) 115 | 116 | 117 | -------------------------------------------------------------------------------- /01-Meaning.Of.Life.md: -------------------------------------------------------------------------------- 1 | ### 生命的意义----正则表达式幽默一则 2 | 3 | **英文原文链接:https://www.rexegg.com/regex-humor.html#meaning-of-life** 4 | 5 | **作者:Douglas Adams** 6 | 7 | **翻译:无名氏** 8 | 9 | 10 | “哦!沉思计算机,”他说,“我们之所以设计制造你是为了执行这样一个任务,我们希望你告诉我们。。。”,他停顿了一下,“终极答案”。 11 | 12 | “终极答案?”沉思计算机说,“关于什么问题的终极答案?” 13 | 14 | “生命!”Fook急切地说。 15 | 16 | “宇宙!”Lunkwill说。 17 | 18 | “世上的一切!”他们异口同声地说。 19 | 20 | 沉思计算机停顿了一下,思考这个问题。 21 | 22 | “有点棘手,”它最后说。 23 | 24 | “但是你能回答么?” 25 | 26 | “当然,”沉思计算机说,“我能回答。但是,我不得不好好思考这个问题。” 27 | 28 | “多久呢?” 29 | 30 | “七百五十万年,”沉思计算机答道。 31 | 32 | **[七百五十万年以后。。。,Fook 和 Lunkwill已经去世了,但是他们的后人继续着他们的事业]** 33 | 34 | “早上好,”沉思计算机最终说到。 35 | 36 | “额。。早上好,沉思计算机”,Loonquawl(Fook and Lunkwill的后人)焦急地说,“你是否有。。。额。。。” 37 | 38 | “最终答案?”沉思计算机庄严地打断道,“当然,我有答案了。” 39 | 40 | “并且你准备告诉我们答案了?”Loonquawl急切地说。 41 | 42 | “当然。” 43 | 44 | “现在么?” 45 | 46 | "对的,就是现在。"沉思计算机说。 47 | 48 | “但是我不认为,”沉思计算机继续说,“你会喜欢这个最终答案。” 49 | 50 | “没关系!”Phouchg说道(Fook and Lunkwill的后人),“我们一定要知道这个答案!就是现在!” 51 | 52 | “好的,”沉思计算机说,“对于生命、宇宙和世上一切的最终答案是。。。”,沉思计算机停顿了一下。 53 | 54 | “是什么!!!?” 55 | 56 | “好的,答案就在这里,让我把它打印出来,”沉思计算机带着无上的威严沉静地说。慢慢地,屏幕上输出了一串字符: 57 | 58 | `^(?=(?!(.)\1)([^\DO:105-93+30])(?-1)(? JSON Web Token (JWT) is a compact claims representation format intended for space constrained environments such as HTTP Authorization headers and URI query parameters. 6 | 7 | ## JWT的组成 8 | JWT 由三部分组成:头部、数据体、签名/加密。 9 | 10 | 这三部分以 . (英文句号)连接,注意这三部分顺序是固定的,即 **header.payload.signature** 如下示例: 11 | 12 | ``` 13 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c 14 | ``` 15 | 16 | ### 1. 头部 The Header 17 | 这部分用来描述 JWT 的元数据,比如该 JWT 所使用的签名/加密算法、媒体类型等。 18 | 19 | 这部分原始数据是一个JSON对象,经过Base64Url编码方式进行编码后得到最终的字符串。其中只有一个属性是必要的:**alg**——加密/签名算法,默认值为**HS256**。 20 | 21 | 最简单的头部可以表示成这样: 22 | 23 | ```json 24 | { 25 | "alg": "none" 26 | } 27 | ``` 28 | 29 | 其他**可选**属性: 30 | 31 | - **typ**,描述 JWT 的媒体类型,该属性的值只能是 **JWT**,它的作用是与其他 JOSE Header 混合时表明自己身份的一个参数(很少用到)。 32 | - **cty**,描述 JWT 的内容类型。只有当需要一个 Nested JWT 时,才需要该属性,且值必须是 **JWT**。 33 | - **kid**,KeyID,用于提示是哪个密钥参与加密。 34 | 35 | > Base64url 编码是 Base64 的一种针对 URL 的特定变种。因为 = 、+、/ 这个三个字符在 URL 中是有特定含义的,所以 Base64url 分别将 = 直接忽略,+ 替换成 -,/ 替换成 _ 36 | 37 | ### 2. 数据体 The Payload 38 | 这部分用来描述JWT的内容数据,即存放些什么。 39 | 40 | 原始数据仍是一个 JSON 对象,经过 Base64url 编码方式进行编码后得到最终的 Payload。这里的数据默认是不加密的,所以不应存放重要数据(当然你可以考虑使用嵌套型 JWT)。官方内置了七个属性,**大小写敏感**,且都是可选属性,如下: 41 | 42 | - **iss (Issuer)** 签发人,即签发该 Token 的主体 43 | - **sub (Subject)** 主题,即描述该 Token 的用途,一般就最为用户的唯一标识 44 | - **aud (Audience)** 作用域,即描述这个 Token 是给谁用的,多个的情况下该属性值为一个字符串数组,单个则为一个字符串 45 | - **exp (Expiration Time)** 过期时间,即描述该 Token 在何时失效 46 | - **nbf (Not Before)** 生效时间,即描述该 Token 在何时生效 47 | - **iat (Issued At)** 签发时间,即描述该 Token 在何时被签发的 48 | - **jti (JWT ID)** 唯一标识 49 | 50 | 除了这几个内置属性,我们也可以自定义其他属性,自由度非常大。 51 | 52 | 这里对 aud 做一个说明,有如下 Payload: 53 | ```json 54 | { 55 | "iss": "server1", 56 | "aud": ["http://www.a.com","http://www.b.com"] 57 | } 58 | ``` 59 | 60 | 那么如果我拿这个 JWT 去 http://www.c.com 获取有访问权限的资源,就会被拒绝掉,因为 aud 属性明确了这个 Token 是无权访问 www.c.com 的,有同学会说这部分反正不加密,那我本地把 www.c.com 加入进去不就完事了。别急,下面这部分看完先。 61 | 62 | ### 3. 签名/加密 The signature/encryption data 63 | 64 | 这部分是相对比较复杂的,因为 JWT 必须符合 JWS/JWE 这两个规范之一,所以针对这部分的数据如何得来就有两种方式,我们先来看一个简单的例子,有如下 JWT: 65 | 66 | ``` 67 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkZW1vIiwibmFtZSI6InhmbHkiLCJhZG1pbiI6dHJ1ZX0.5SHkLkM4KAHtOCtLhSNHOgkFZhPO419ukot1C5bgyUM 68 | ``` 69 | 70 | 对前两部分用 Base64url 解码后能得出相应原始数据, 71 | 72 | Header 部分: 73 | ```json 74 | { 75 | "alg": "HS256", 76 | "typ": "JWT" 77 | } 78 | ``` 79 | 80 | Payload 部分: 81 | ```json 82 | { 83 | "sub": "demo", 84 | "name": "xfly", 85 | "admin": true 86 | } 87 | ``` 88 | 89 | 根据 Header 部分的 alg 属性我们可以知道该 JWT 符合 JWS 中的规范,且签名算法是 HS256 也就是 **HMAC SHA-256** 算法,那么我们就可以根据如下公式计算最后的签名部分: 90 | ``` 91 | HMACSHA256( 92 | base64UrlEncode(header) + "." + 93 | base64UrlEncode(payload), 94 | secret 95 | ) 96 | ``` 97 | 98 | 其中的密钥是保证签名安全性的关键,所以必须保存好,在本例中密钥是 123456。**因为有这个密钥的存在,所以即便调用方偷偷的修改了前两部分的内容,在验证环节就会出现签名不一致的情况,所以保证了安全性。** 99 | 100 | 在实现过程中,遇到了这样一个问题:如果使用 **RS256** 这类非对称加密算法,加密出来的是一串二进制数据,所以第三部分还是用 Base64 编码了一层,这样最终的 JWT 就是可读的了。 101 | 102 | ## Why JWTs 103 | JWTs 相比于在内存中使用随机 Token 的会话管理方式,其最大优势在于认证逻辑的可扩展性。举个例子,对于认证逻辑,完全可以单独部署,或者使用第三方的认证服务。 104 | 105 | 而相比于使用数据库进行统一存储和管理 Token 的会话管理方式,其最大优势在于消耗小,不需要频繁调用数据库这类 I/O 耗时操作。 106 | 107 | ## 安全 108 | 1. 因为 JWT 的前两个部分仅是做了 Base64 编码处理并非加密,所以在存放数据上不能存放敏感数据。 109 | 2. 用来签名/加密的密钥需要妥善保存。 110 | 3. 尽可能采用 HTTPS,确保不被窃听。 111 | 4. 如果存放在 Cookie 中则强烈建议开启 Http Only,其实官方推荐是放在 LocalStorage 里,然后通过 Header 头进行传递。 112 | 113 | > Cookie 的 HTTP Only 这个 Flag 和 HTTPS 并不冲突,你会发现其实还有一个 Secure 的 Flag,这个就是指 HTTPS 了,这两个 Flag 互不影响的,开启 HTTP Only 会导致前端 JavaScript 无法读取该 Cookie,更多的是为了防止 类 XSS 攻击。 114 | 115 | ## 问题和思考 116 | JWT 的缺点其实也蛮多的,适不适用得具体看业务场景,哪个优势更大用哪个。(一点感悟:在写这篇文章前一直是 JWT 的坚定拥护者,越写越发现其实传统的 Session-Cookie 方案挺好的,很成熟。它们两者都有优缺点,选型上要多思考斟酌才行。) 117 | 118 | ### 1. 数据臃肿 119 | 因为 payload 只是用 Base64 编码,所以一旦存放数据大了,编码之后 JWT 会很长,cookie 很可能放不下,所以还是建议放 LocalStorage,但是每次 HTTP 请求都带上这个**臃肿的 Header 开销也随之变大**。 120 | 121 | ### 2. 无法废弃和续签 122 | 1. 如果有效期设置过长,意味着这个 Token 泄漏后可以被长期利用,危害较大,所以一般我们都会设置一个较短的有效期。由于有效期较短,意味着需要经常进行**重新授权**的操作。 123 | 2. 假设在用户操作过程中升级/变更了某些权限,势必需要**刷新**以更新数据。 124 | 125 | 要解决这个问题,需要在服务端部署额外逻辑,常见的做法是增加刷新机制和黑名单机制,通过 Refresh Token 刷新 JWT,将需要废弃的 Token 加入到黑名单。 126 | 127 | ### 3. Token 丢失 128 | 如果认证逻辑是在自己服务器上做的话,我们的 JWT secret key 一旦丢失或者泄露那只能通过更换 key 这一种办法了,但这样做的话会导致全部用户都需要重新登录,所以 key 的保管很重要。如果我们的认证逻辑放在第三方服务上,那其实我们就完全不用操心这部分了,很贴心吧 :) 129 | 130 | 我觉得 JWT 的最大优势在于可以把认证逻辑完全从应用服务中剥离出来,交给第三方 JWT 认证服务或者自己部署的认证服务器上。这样就把用户的账号密码等敏感信息放在单独的服务器上,更容易管理和维护(相比而言应用服务器更容易出现漏洞)。这样做的好处很明显: 131 | 1. 应用服务器完全不需要关心用户的账号密码,也不需要关心用户的注册登录,只需要校验 JWT 的合法性即可。 132 | 2. 应用服务器不需要存储 JWT 的 key,降低泄露密钥的概率。 133 | 134 | ## 最佳实践:加密算法使用 RS256 而不是 HS256 135 | 假设我们的认证逻辑放在认证服务器上(比如说第三方的认证服务),使用 HS256 算法进行加密,整个过程如下: 136 | 1. 用户通过登录接口,携带账号密码请求认证服务器 137 | 2. 认证服务器校验账号密码,校验失败返回登录失败信息,校验成功则进行下一步 138 | 3. 将用户的一些必要信息(主要是用户的唯一标识)封装成 JWT 的 payload 部分 139 | 4. 将 JWT 的 header、payload 分别进行 Base64url 编码,用 `.` 连接后与密钥一起参与 HS256 加密得到 signature 140 | 5. 将三部分用 `.` 连接后组成最终的 JWT 返回 141 | 142 | 应用服务器在接收到需要认证的接口请求时,先获取请求中携带的 JWT,然后进行校验,这里我们就会看到一些问题。因为是对称式加密算法,所以加密用的密钥和解密用的密钥必须是同一个,否则是应用服务器是没法做校验的。那么我们只能把密钥在应用服务器上也保存一份,从而增加了密钥泄漏的可能性(当然也只是相比于使用非对称式加密算法而言,毕竟在应用服务器里还有很多其他的 secret key,能丢 JWT 的key,其他 key 也能丢。。。) 143 | 144 | 那我们使用 RS256 非对称式加密算法就不会丢了吗?是的,至少应用服务器不用背这个锅!因为应用服务器根本就不需要存储这份重要的密钥。 145 | 146 | 简单科普下非对称式加密算法:有两个密钥,一个公开密钥和一个私有密钥,私钥参与加密,公钥用于解密,巧妙之处是解密只能用公钥来解,即便是加密用的密钥也无法对密文进行解密。你可以看到加密和解密需要两个不同的密钥,故称之为非对称加密。 147 | 148 | 所以我们应用服务器只需要存一份公开密钥用于校验和解密认证服务器签发的 JWT 即可,即便这个公开密钥泄漏了也没事。因为用公开密钥进行加密的密文再用公开密钥去解密是解不出来的,也就是说我们的应用服务器会认为这个 JWT 是无效的! 149 | 150 | ## 参考链接 151 | - [JSON Web Token Introduction - jwt.io](https://jwt.io/introduction/) 152 | - [RFC 7519 - JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) 153 | - [RFC 7515 - JSON Web Signature (JWS)](https://tools.ietf.org/html/rfc7515) 154 | - [RFC 7516 - JSON Web Encryption (JWE)](https://tools.ietf.org/html/rfc7516) 155 | - [RFC 7518 - JSON Web Algorithms (JWA)](https://tools.ietf.org/html/rfc7518) 156 | - [RFC 6749 - The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749#section-4.1.3) 157 | - [Refresh Tokens: When to Use Them and How They Interact with JWTs](https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/) 158 | - [Cross-origin resource sharing - Wikipedia](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) 159 | - [JSON Web Token 入门教程 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html) 160 | - [讲真,别再使用JWT了 – ThoughtWorks洞见](http://insights.thoughtworkers.org/do-not-use-jwt-anymore/) 161 | - [Pros and cons in using JWT (JSON Web Tokens) – Rahul Golwalkar – Medium](https://medium.com/@rahulgolwalkar/pros-and-cons-in-using-jwt-json-web-tokens-196ac6d41fb4) 162 | - [Pros and Cons in Using JWT (JSON Web Tokens) | Hacker News](https://news.ycombinator.com/item?id=12332119) 163 | - [https://blog.angular-university.io/angular-jwt/](https://blog.angular-university.io/angular-jwt/) 164 | - [https://en.wikipedia.org/wiki/Public-key_cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography) 165 | -------------------------------------------------------------------------------- /06-Some.Thoughts.About.StackOverflow.Developer.Survey.Results.2019.md: -------------------------------------------------------------------------------- 1 | # 对于 《StackOverflow 2019程序员调查报告》的一些感想 2 | 3 | 前些天,StackOverflow 发布了 [2019年的年度程序员调查](https://insights.stackoverflow.com/survey/2019),这个调查报查有9万名程序员参与,这份调度报告平均花了20分钟,可见,这份报告有很多的问题,也是很详细的。这份报告有一些地方,让我有了一些思考。 4 | 5 | 首先,我们先来看一下之份报告的 Key Results: 6 | 7 | - Python 成为了过去一年中成长最快的语言,把Java挤到了第二位,排在后面的是Rust语言。 8 | - 有半数以上的被访者在是在16岁写下自己的第一行代码。 9 | - [DevOps Specialists](https://stackoverflow.com/jobs/devops-jobs) 和 Site Reliability Engineers 是程序员中最有经验,技术最牛,薪资最好的职位。(这对应于国内的——系统架构师) 10 | - 在几个头部的程序员大国中,中国的程序员最乐观的,他们相信在今天出生的人会有比他们父母更好的人生。对于欧洲的程序员来说,比较法国和德国的程序员,他们对未来并不太乐观。 11 | - 对于最影响程序员生产力的事,不同的程序员有不同的想法。 12 | 13 | ## 第一部分,Developer Profile 14 | 15 | 在第一部分中,我们可以看到,中国程序员参与这个调查的并不多,程序员主要集中在美国、欧洲、印度这三个地方。所以,这份报告更偏国际上一些。这对于我们中国程序员也有很大的帮助,因为一方面可以看到世界发展的趋势,另一方面也可以了解我们和世界有什么不一样。 16 | 17 | 对于技术职业来说,整个世界的程序员开始趋于全栈和后端,有51.9%的人是全栈,50%的人是后端,32.8%的人是前端……在这些人中,很多程序员都选了多项,中位数是3项,最常见是前端、后端和全栈全选的。然后,接下来是选两项的,选两项目的包括:数据库管理员和系统管理员,DevOps Specialist 和 Site Reliablility Engineer, 学术研究者和科学家,设计师和前端工程师。 18 | 19 | ![](./images/06-01.Developers.Rols.png) 20 | 21 | 从这些数据中我们可以看见:**前后端的界限越来越不明显,设计师和前端的界限也开始模糊。这应该说明,工具和框架的成熟,让后端程序员和设计师也可以进入到前端工程师的领域,或是前端工程师开始进入后端和设计的领域**。总之,复合型人才越来越越成为主流,而前后端也趋于一个相互融合的态势。 22 | 23 | 24 | 在接下来的图表中,我们可以看到有80%以上的人是把编程当成自己的爱好(包括相关的女性)。 25 | 26 | ![](./images/06-02.Coding.as.a.Hobby.png) 27 | 28 | 真是应了那句话——“Programmers who don’t code in their spare time for fun will never become as good as those that do”,是的,如果你对编程没有感到一种快乐,没有在你空闲的时候去以一种的兴趣爱好方式去面对,那么,无论是编程,还是运动,还是去旅游,都不会有太多成效的。 29 | 30 | 在接下来的编程经验上,有两组如下的数据: 31 | 32 | |学习编程的年限|编程的年限| 33 | |---|---| 34 | |![](./images/06-03.Years.Since.Learning.to.Code.png) |![](./images/06-04.Years.Coding.Professionally.png)| 35 | 36 | 我们可以看到无论是学习还是编程,随着时间的拉长,其人数占比越来越少。 37 | 38 | 下面我们再来看一个年龄图: 39 | 40 | ![](./images/06-05.Age.png) 41 | 42 | 调查报告从20岁开始每隔5年划分一个年龄段,我们不难发现从25-29岁开始每个年龄段都比前一个年龄段人数急剧减少大约30-50%,比如25-29年龄段占总人数27.6%,而30-34则只有19.3%。以此类推,到60岁以上,就只剩1%。可以看出5年是大多数程序员的转型周期。这是合理的,因为5年时间足够一个人积累足够的经验技能为职业转型做准备。 43 | 44 | 我们也可以看到50岁以上的程序员只有4.2%,大约是参与调查人员的300多人,如果这些人20岁左右参加工作,那么说明他们在1990左右就开始写代码,事实上那个时间点别说是程序员了,连电脑用户都不多。**电脑和互联网真正暴发的时间还是在1995年 - 2000年之间,不过,那个时间点程序员的总体人数也不多,而行业越来越火才会导致大量的人进入到这个行业中,这个转换过程基本上去需要3-5年,也就是从2000年后才开始有大量的人拥入程序员这个行业,程序员的人数在过去30年间也是呈增涨态势的,所以,我个人认为,所谓的“众多老程序员”的比例会被2005年以后大量拥入程序员行业的年青人所“稀释”。所以,上图的比例不能完全说明程序员是个青春饭**。 45 | 46 | 但是,我们还是要正视老牌资深的程序员越来越少的这个事实,在这份报告第三部分中说了一些和程序员职业生涯相关的调查,如下: 47 | 48 | - **在被问到有多少人对自己的职业满意的时**。有40%的人觉得很满意,而有34.3%的人觉得一般满意,有10%的人说不清,还有15%的人是不满意的。可以看到有不少人是对这个职业生涯是有想法的。 49 | 50 | - **在被问到有多少人想转管理而可以挣得更多时**。有30%的人是说想转的,有51%的人是明确不转的,还有20%的人是说不知道。可见,想转管理的人最多可能会有一半的人。 51 | 52 | - **在被问到有多少人想转管理时**。有1/3的人是明确不想转的,而有1/4的人是明确是想转,而有36%的人则是不说,观望中。可见,的确是有很多想想转管理的。 53 | 54 | 55 | **我们可以看到,程序员中并不是所有的人都是可以坚持这么长时间的,这也挺正常的,对很大一部分人来说,对这个职业是有或多或少的不满意的,也有一部分人可能会随着技术的更新被淘汰,还有另外很大一部分人是想转管理的。所以,能够长时间地跟上形势长时间地喜欢写代码,并且对程序员这个的职业长期满意,不想转管理的,的确是为随时年龄的越大也越来越少**。 56 | 57 | **但我们完全可以看出来,程序员的主力军在20-40岁这个区间,而30岁左右的程序员是年富力强(经验和能力都很好)的黄金时间**。 58 | 59 | -------- 60 | 61 | 老程序员在国外似乎不会存在多大的问题,但在国内会有一些问题,所以,对于像我一样喜欢写代码、打算长久做程序员的兄弟,这里分享一些相关的经验。 62 | 63 | 1. **持续高效地学习**。软件行业的新技术层出不穷,旧的技术淘汰很快,所以我们更要多多学习基础技术和原理,那些都是很难改变的,并且基础扎实了后,学习新的技术也才会更快速。其间我们也不要乱学新技术,我们要关注那些有潜力的技术,也就看准了再学(参看酷壳的《[Go语言、Docker和新技术](https://coolshell.cn/articles/18190.html)》)。注意,而是跟上大时代已经比较不容易,引领时代的人还是少数,所以,还是要更为高效地学习。 64 | 65 | 2. **积极面对他人的不解**。 很多时候,总是会有人说:“到了你这个年纪怎么还在做程序员?”,这句话感觉就是对程序员这个职业的一种羞辱,社会的价值观感觉容不下大龄程序员。这个时候,我一般会跟他们解释到,我40来岁了,我觉得自己的状态还很好,工作完成没什么问题,偶尔加班到凌晨也行,新知识和技术我学起来不比年轻人慢,我在这个年纪有的经验比他们都多,而且,我这个年纪还在写代码,说明我真的喜欢这个事,**像我这样的人能够长时间坚持做一个职业的人这个世界已经不多了,你们应该珍惜……** 66 | 67 | 3. **找到自己的定位**。我们需要做好职业规划、财务和心理方面的准备。40岁的程序员,所能竞争的一定是自己的认识和经验,所以,40岁以后如果你还是很喜欢这一行业,你的社会阅历和经历以及对这个社会的理解,可以让你做一些有创新的事,除此之外,你还可以做一个教练、老师、咨询、专家……,用你的经验和能力帮助下一代和一些中小型的公司,这不但是他们的刚需,同时也会让重新焕发的。 68 | 69 | 70 | 71 | ## 第二部分,技术 72 | 73 | 首先,在这部分,主要是了解一些技术,这部分的技术可以给于程序员们一些指导。 74 | 75 | |最流行的语言|最热门的语言| 76 | |----|----| 77 | |![](./images/06-06.Popular.Languages.png) | ![](./images/06-07.Loved.Languages.png)| 78 | 79 | 我们可以看到, 80 | 81 | - Javascript/HTML/CSS是很多人都会用到的,后面的是SQL,这个也没什么问题,无论前后端的人,或多或少都会要用到的,这些技术感觉已经成为了基础必会的技术了,就像数中的加减乘除一样。 82 | 83 | - Python/Java/Shell 是后端开发主流语言的前三强,Python在今年超过了Java。这里让我比较好奇的是居然还有很多人用Shell,这估计跟运维有关,所以,Python的热可能也是通过运维和大数据相关。 84 | 85 | - 流行语言后,第二梯队的是 C# / PHP / C++ / TypeScript / C ,接下来的是: Ruby / Go / Swift / Kotlin /WebAssembly / Rust... 。但在最被程序员喜欢的编程语言中:Rust / Python / TypeScript / Koltin / WebAssembly / Swift / Go... 都是排在前几名的。**程序语言每隔一段时间就会整出一些新的语言来,我们一定要明白新出来的东西主要是为了解决什么样的问题,不然很容易迷失。** 86 | 87 | - 在后面还有一个编程语言的薪资图,我们可以看到,在上面被提过的这些个编程语言中,**Go语言的薪资是最高的(这可能是因为Go语言写关键的系统级的中件间——因为Go语言正在成为云计算的第一编程语言)**,然后是Scala、Ruby、WebAssembly、Rust、Erlang、Shell、Python、Typescript…… 88 | 89 | **通过这些个信息,我们可以看出主流技术、有潜力的技术,传统过气技术,以及相关薪资,对我们在选择编程语言上有一定的启示。** 90 | 91 | 在后面,我们可以看到: 92 | 93 | - 在 Web 开发框架上,主流使用还是 jQuery, React.js,Angular.js 为最前面的三个前端开发框架。而被程序员所喜欢的则是 React.js,Vue.js,Express, Spring,程序员非常不喜欢 Drupal,jQuery,Ruby on Rails 和Angular.js…… 94 | 95 | - 在其它开发框架/库/工具上,主流是Node.js、.NET、Pandas、Unity 3D、Tensorflow、Ansible、Cordova、Xamarin……而程序员比较喜欢的是.NET、Torch/PyTorch、Flutter、Pandas、Tensorflow、Node.js ... 96 | 97 | - 在操作系统上,主流使用Linux、Windows、Docker、Android、AWS……,而程序员最喜欢的是Linux、Docker、Kubernetes、Raspberry Pi、AWS、MacOS、iOS…… 98 | 99 | - 在数据库上,MySQL、PostgreSQL、MSSQL、SQLite、MongoDB、Redis、Elasticsearch是比较主流的,而程序员非常喜欢的是,Redis、PostgreSQL、Elasticsearch、Firebase、MongoDB……,程序员比较讨厌的是 Couchbase、Oracle、Cassandra、MySQL。 100 | 101 | **从这些个图表中,我们可以看到主流和有潜力的技术是什么,我们可以看到 Windows 的技术并没有过时,感觉似乎都有可能会卷土重来,但是,开源的技术来势凶凶,正在吞食整个软件业,不容小觑,Docker/Kubernetes无论是在主流应用上还是被程序员的喜好上都是非常猛的,而云平台的AWS开始成为标准平台技术……** 102 | 103 | 接下来的开发工具中,我们可以看到: 104 | 105 | - Visual Studio Code 成为了最流行的开发工具。让我没有想到的是跟在后面的是 Notepad++(好久没用这个工具了,我得找回来用用了),而IntelliJ、Vim、Sublime Text排以后面。 Eclipse 和 Atom 动力不足,Emacs 开始变得小众了。 106 | 107 | - 程序员主要的开发平台还是Windows占了近1/2, MacOS和Linux随后,各占1/4。 108 | 109 | - 有38%的人使用容器技术做开发,30%的人使用容器做测试,在生产线上使用容器的有26% 110 | 111 | **看样子编程开发工具还是Visual Studio 和 IntelliJ的天下,MacOS/Linux正在抢Windows的开发市场** 112 | 113 | 接下来,StackOverflow给了一个技术圈的图 114 | 115 | ![](./images/06-08.Technology.Circle.png) 116 | 117 | 从上面这个图中,我们可以看以技术的几圈子: 118 | 119 | - **Microsoft圈** - Windows、.NET、ASP.NET、C#、Azure、SQL Server 120 | - **Java圈** - Java、Spring 121 | - **手机圈** - Android、 iOS、Kotlin、Swift、Firebase 122 | - **前端圈** - Javascript、React.js、Angular.js、PHP 123 | - **大数据圈** - Python、TensorFlow、Torch/PyTorch 124 | - **基础平台圈** - Linux、Shell、Vim、Docker、Kubernetes、Elasticsearch、Redis…… 125 | - **其它圈子** - C/C++/汇编圈子、Ruby圈子、Hadoop/Spark圈子、…… 126 | 127 | **看到谁的圈子大了吧,圈子大的并不代表技术实力强或是有前途,不过可以代表在那个圈子相关的关联技术,一方面,可以给你一些相关的参考,另一方面,整体可以让你看到全部的目前比较主流的技术。** 128 | 129 | ## 第三部份 工作 130 | 131 | 在第三部份工作中,我们可以看到如下的一些数据: 132 | 133 | - 有3/4的程序员是全职的,10%左右的程序员是自由职业,6%左右的程序员是失业的,这个比例在北美、印度和欧洲都差不多。 134 | 135 | - 有1/3的人在过去一年内换过工作,1/4的人在过去1-2年间换过工作,1/3的人在2-4年换过工作。 136 | 137 | - 程序员找工作时,影响程序员的几个主要因素是:技术(编程语言、框架和使用的技术)、办公环境和公司文化、灵活的时间和安排、更专业的机会、远程工作…… 138 | 139 | - 影响程序员工作的几大因素是:有干扰的工作环境、开会、要干一些和开发无关的事、人手不够、管理不够、工具不够、通勤时间…… 140 | 141 | - 对于工程质量,有近70%的人有Code Review,而30%的则没有;有60%多的人有Unit Test,而不到40%的没有…… 142 | 143 | **从工作中我们可以看到,程序员还是比较关心技术和公司文化的,换工作也是这个职业很正常的特性,他们并不喜欢被打扰,希望有足够的时间,而对于工程质量还是很有追求的。** 144 | 145 | 最后用一张程序员的“**每周工作时间**” 来结束本文! 146 | 147 | ![](./images/07-09.Hours.Worked.Per.Week.png) 148 | 149 | 祝大家编程快乐! 150 | 151 | (全文完) 152 | 153 | -------------------------------------------------------------------------------- /08-Introduction.To.OAUTH2.md: -------------------------------------------------------------------------------- 1 | # 浅析 OAuth 2.0 2 | 3 | 我接触 web 开发以来,但凡是使用第三方的服务,似乎都是用的 OAuth2。刚开始做 web 开发,最常遇到的功能模块就是「QQ 第三方登录 」、「微信第三方登录」、「支付宝第三方登录」等等,写的多了发现各家的接口类似,接口文档更类似,高度的统一有木有,所以为此自己还特地写了一个聚合插件,提高自己的工作效率。 4 | 5 | 注:以下所有 Ascii Flow 均来自[官方 RFC 6749](https://tools.ietf.org/html/rfc6749) 6 | 7 | ## 简单介绍 8 | 首先我得明白它是用来解决什么问题的:在给客户端开放受限制的资源访问时,如果使用传统的客户端-服务端的认证模型,势必需要分享完整的授权凭证给客户端,而这会导致如下几个问题。 9 | 1. 客户端需要保存资源拥有者的凭证作为访问资源之用,典型的就是保存明文密码,从而导致密码泄露等安全问题,毕竟无法控制客户端如何妥善保存该凭证。 10 | 2. 客户端对于该资源的权限过大,无法限制访问的有效期和能访问的范围大小。 11 | 3. 无法限制特定客户端的访问权限,因为发放的凭证都是同一个,一改全都失效了,调控粒度太粗了。 12 | 13 | OAuth2 的解决办法:**通过引入授权层的概念,将客户端和资源所有者的角色分开。通过签发拥有一系列不同访问权限的凭证代替资源所有者的完整权限的凭证。** 14 | 15 | ## 角色 16 | OAuth 定义了四类角色: 17 | - 资源所有者 Resource Owner 18 | - 资源服务器 Resource Server 19 | - 授权服务器 Authorization Server 20 | - 客户端 Client 21 | 22 | ### 资源所有者:用户 23 | 用户授权给第三方 App 一些特定权限,以此访问我们的账号来获取信息。 24 | 25 | ### 资源服务器、授权服务器 26 | 资源服务器保管着我们的用户信息,授权服务器则负责验证用户凭证,然后签发 Access Token 给客户端。从客户端的开发者角度来看,这两者通常是同一个角色来扮演的。所以我们更愿意将这两者合并成一个角色——服务方。 27 | 28 | ### 客户端:第三方应用 29 | 需要获取用户信息的客户端,在获取用户信息前,必须通过用户授权,且授权凭证必须合法。 30 | 31 | **** 32 | 举个例子:比如我们在打开一个未授权过的微信服务号的网页时,通常会弹出一个授权框,上面一般会写着「xxx需要获取以下信息:你的公开信息(昵称、头像等)」。如果用户觉得这些数据可以被这个客户端获取,那么就点击确认授权按钮,此时该网页就能够获取到你在微信上的昵称、头像等你**授权可以访问的信息**。 33 | 34 | 以上例子中,资源所有者就是用户自己(这里的资源也就是用户的头像、昵称等信息,所以这些资源的所有权是属于用户的),资源服务器和授权服务器是微信平台,客户端就是这个微信服务号的网页应用。 35 | 36 | 具体流程如下图所示 37 | 38 | ``` 39 | +--------+ +---------------+ 40 | | |--(A)- Authorization Request ->| Resource | 41 | | | | Owner | 42 | | |<-(B)-- Authorization Grant ---| | 43 | | | +---------------+ 44 | | | 45 | | | +---------------+ 46 | | |--(C)-- Authorization Grant -->| Authorization | 47 | | Client | | Server | 48 | | |<-(D)----- Access Token -------| | 49 | | | +---------------+ 50 | | | 51 | | | +---------------+ 52 | | |--(E)----- Access Token ------>| Resource | 53 | | | | Server | 54 | | |<-(F)--- Protected Resource ---| | 55 | +--------+ +---------------+ 56 | 57 | Figure 1: Abstract Protocol Flow 58 | 59 | ``` 60 | 61 | 我们大致走一遍整个过程: 62 | 63 | 1. 客户端向用户发起授权请求 64 | 2. 用户确认授权,此时授权服务器会签发给客户端一个 Ticket (Authorization Code 65 | ) 66 | 3. 客户端拿着这个 Ticket 以及「自身的凭证」向授权服务器换取 Access Token 67 | 4. 授权服务器验证 Ticket 的合法性,验证通过则签发对应权限的 Access Token 68 | 5. 客户端拿着 Access Token 向资源服务器获取数据 69 | 6. 资源服务器验证 Access Token 的合法性,验证通过则返回相应数据 70 | 71 | 注:这里所说的「自身的凭证」指的是客户端在服务方注册应用得到的 AppID 和 AppSecret。比如你要在微信网页内获取微信用户的基本信息,那么你就得在微信公众平台上申请相应的服务号之类的。 72 | 73 | ## 应用注册 74 | 场景:还是以微信为例,应用A想接入微信的第三方登录。 75 | 76 | 在接入前,应用A需要在微信(服务方)开放平台上注册一个应用。填写一些相关信息,比如应用名称、应用 Logo、回调地址、应用域名等(不同服务方所需注册信息会有不同,但大体上都会需要这几个信息)。 77 | 78 | 注册成功后,微信开放平台会签发一对 AppID 和 AppSecret。前者是用来标识应用程序自己的,后者用于换取 Access Token 时的身份验证,需要妥善保存。 79 | 80 | ## 授权机制 81 | 官方协议提供了四种授权方式: 82 | - Authorization Code Grant 83 | - Implicit Grant 84 | - Resource Owner Password Credentials Grant 85 | - Client Credentials Grant 86 | 87 | 这里先只介绍最常用的一种授权方式:Authorization Code Grant。 88 | 89 | 这种方式是基于重定向的流模式。对用户来说就是点了一下确认授权按钮,然后剩下的就是客户端和授权服务器以及资源服务器三者之间你来我往的事情了,怎么来往呢,重定向呗。 90 | 91 | ``` 92 | +----------+ 93 | | Resource | 94 | | Owner | 95 | | | 96 | +----------+ 97 | ^ 98 | | 99 | (B) 100 | +----|-----+ Client Identifier +---------------+ 101 | | -+----(A)-- & Redirection URI ---->| | 102 | | User- | | Authorization | 103 | | Agent -+----(B)-- User authenticates --->| Server | 104 | | | | | 105 | | -+----(C)-- Authorization Code ---<| | 106 | +-|----|---+ +---------------+ 107 | | | ^ v 108 | (A) (C) | | 109 | | | | | 110 | ^ v | | 111 | +---------+ | | 112 | | |>---(D)-- Authorization Code ---------' | 113 | | Client | & Redirection URI | 114 | | | | 115 | | |<---(E)----- Access Token -------------------' 116 | +---------+ (w/ Optional Refresh Token) 117 | Note: The lines illustrating steps (A), (B), and (C) are broken into 118 | two parts as they pass through the user-agent. 119 | 120 | Figure 3: Authorization Code Flow 121 | ``` 122 | 123 | ### Step 1:构造获取授权码链接 124 | 我们首先需要拿到能换取 Access Token 的临时票据,通常是构造类似下面这个链接去获取: 125 | 126 | `https://oauth.example.com/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=SCOPE&state=STATE` 127 | 128 | 其中, 129 | - 终端(Endpoint):https://oauth.example.com/authorize,也就是服务方提供的授权API地址 130 | - 请求类型(Response Type):response_type=code,必填,期望返回的数据类型,这一步里值是固定的,即 code 131 | - 应用唯一标识(Client ID):client_id=CLIENT_ID,必填,让服务方识别出你是谁 132 | - 回调地址(Redirect URI):redirect_uri=CALLBACK_URL,必填,服务方签发授权码后重定向的地址,其中会携带这个授权码 133 | - 作用域(Scope):scope=SCOPE,选填,指出想要获取的资源类型 134 | - 额外字段(State):state=STATE,选填,用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止 csrf 攻击(跨站请求伪造攻击),建议第三方带上该参数。 135 | 136 | **注:**选不选填不同平台都会有不同,比如微信第三方登录获取临时票据 CODE 的时候,scope 参数是必填的。 137 | 138 | ### Step 2:用户授权 139 | 当用户点击类似「微信登录」按钮的时候,实际上是点击了第一步构造好的链接,这个时候就会跳转到服务方的授权页面(我们假定用户在对应服务方平台上已经处于登录状态,否则会额外有一步登录服务方平台的操作,但跟我们这里无关),用户可以选择确认授权也可以拒绝授权,这两个行为最终都会通过服务方重定向回到客户端那里,只不过一个是带了 Authorization Code,一个没有。 140 | 141 | 下图来自微信开放平台,可以对照着看: 142 | ![](./images/08-01.Authorization.Page.jpg) 143 | **注:**这里我们可以看到授权页面上出现的客户端的一些基本信息,也就是我们在服务方平台上注册应用的时候填写的信息。还需要注意的是**回调地址**,一般服务方都会强制要求注册应用的时候填写,然后在第一步中将注册应用时填写的回调地址和应用请求 Authorization Code API 时携带的回调地址进行比较,相同才能继续。为什么要这么做也很好理解,为了安全。(这个世界本来可以很简单,但为了安全,世界就变得复杂了) 144 | 145 | ### Step 3:客户端接收临时票据 146 | 我们假定用户确认授权了,此时服务方会将签发的临时票据携带在客户端的回调地址上,重定向到客户端,一般来讲,回调地址类似下面这样: 147 | 148 | `https://www.xfly.one/oauth/callback?code=AUTHORIZATION_CODE` 149 | 150 | 客户端接受到这个请求后,就能拿到临时票据了。 151 | 152 | **注:**Authorization Code 有效期很短,可能只有几分钟,且成功换取一次 Access Token 后即失效。这种临时性和一次性保障了授权的安全性。 153 | 154 | ### Step 4:客户端请求换取 Access Token 155 | 在拿到临时票据后,客户端赶紧拿着票据去换取 Access Token,同样的请求链接类似如下: 156 | 157 | `https://oauth.example.com/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL` 158 | 159 | 其中, 160 | - 客户端唯一标识(Client ID):client_id=CLIENT_ID,必填,得和 code 参数进行匹配 161 | - 客户端密钥(Client Secret):client_secret=CLIENT_SECRET,必填,由于 Client ID 是公开的,仔细的你会发现前面几个步骤完全没有去验证是不是真的是这个客户端在请求获取用户信息,所以势必也需要验证客户端自己的身份。这里其实也可以灵活一些,也可以换成通过这个密钥生成包含其他参数的一个签名,然后在服务方那里去解密匹配 162 | - 授予类型(Grant Type):grant_type=authorization_code,必填,且值必须是 authorization_code 163 | - 临时票据(Authorization Code):code=AUTHORIZATION_CODE,必填,不多说 164 | - 回调地址(Redirect URL):redirect_uri=CALLBACK_URL,必填,不多说 165 | 166 | **注:**这里的回调地址和上一步的回调地址意义不同(通常地址也不同) 167 | 168 | 这一步授权服务器必须进行如下几步: 169 | 1. 验证客户端的身份 170 | 2. 验证临时票据的合法性,包括验证是否是签发给该客户端的 171 | 3. 验证回调地址是否与注册应用时的回调地址保持一致 172 | 173 | 授权服务器验证通过以后就会将 Access Token 携带在回调地址上,并重定向会客户端。 174 | 175 | ### Step 5:客户端接收 Access Token 176 | 客户端接收到成功返回的请求,类似如下: 177 | ``` 178 | HTTP/1.1 200 OK 179 | Content-Type: application/json;charset=UTF-8 180 | Cache-Control: no-store 181 | Pragma: no-cache 182 | 183 | { 184 | "access_token":"2YotnFZFEjr1zCsicMWpAA", 185 | "token_type":"example", 186 | "expires_in":3600, 187 | "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", 188 | "example_parameter":"example_value" 189 | } 190 | ``` 191 | 192 | 此时客户端就变身为**已授权应用**,可以获取对应权限的数据了! 193 | 194 | ## 刷新机制 195 | 我们会发现 Access Token 是有有效期的,过期了这个 Token 就废了。一开始我是没想明白的,为什么要多此一举。。。 196 | 197 | 既然给了 Refresh Token 用来不停刷新 Access Token,又何必将 Access Token 的有效期设置的这么短,直接设置长一点不就好了么。网上基本都是一个论调:这样对于前端的用户来说就不需要频繁进行授权操作了,对用户是无感知的。。。嗯,有点道理,但是还是一句话,把 Access Token 的过期时间设置得长一点不就好啦。。。 198 | 199 | 所以真正的问题是:Access Token 设置长期有效会有什么弊端? 200 | 201 | **注:**长期有效也只是一个相对的概念,像微信 Access Token 的有效期是两小时,Refresh Token 的有效期是三十天,相对来说就是长期有效的。 202 | 203 | 我的理解如下: 204 | 首先我们要明白,为什么要有 Token 这玩意,不就是为了不必每一次调用接口都输入用户的账号和密码么!但 Token 不能取代用户的账号和密码,这就导致了 Token 这玩意的生命周期必然是短暂的(为什么不能取代,因为取代了用户的账号和密码的话就必须每次都携带这个 Token 来验证用户身份,那可就危险了,只要有某一次请求泄露了你的账号就没了,可怕不)。即便短暂了,但是我总不能眼睁睁看着我的 Token 泄露遭受恶意用户的疯狂输出,静静地等待着 Token 自己过期吧。所以, Token 除了生命周期需要相对短暂以外,还需要能被回收(废弃)。 205 | 206 | 附上刷新 Access Token 的流程图: 207 | 208 | ``` 209 | +--------+ +---------------+ 210 | | |--(A)------- Authorization Grant --------->| | 211 | | | | | 212 | | |<-(B)----------- Access Token -------------| | 213 | | | & Refresh Token | | 214 | | | | | 215 | | | +----------+ | | 216 | | |--(C)---- Access Token ---->| | | | 217 | | | | | | | 218 | | |<-(D)- Protected Resource --| Resource | | Authorization | 219 | | Client | | Server | | Server | 220 | | |--(E)---- Access Token ---->| | | | 221 | | | | | | | 222 | | |<-(F)- Invalid Token Error -| | | | 223 | | | +----------+ | | 224 | | | | | 225 | | |--(G)----------- Refresh Token ----------->| | 226 | | | | | 227 | | |<-(H)----------- Access Token -------------| | 228 | +--------+ & Optional Refresh Token +---------------+ 229 | 230 | Figure 2: Refreshing an Expired Access Token 231 | 232 | ``` 233 | 234 | ## 参考链接 235 | - [https://tools.ietf.org/html/rfc6749](https://tools.ietf.org/html/rfc6749) 236 | - [https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2) 237 | - [https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN) 238 | -------------------------------------------------------------------------------- /09-Something about memory and cache.md: -------------------------------------------------------------------------------- 1 | ### Cache与内存二三事 2 | 本文搜集了几个与内存和缓存相关的技巧,对于代码调优比较有帮助。即使你在工作中不需要写出极致性能的代码,也应该读一下这篇文章。因为看似对软件工程师透明的内存以及CPU Cache,其实并不“透明”,代码的细微差别可能明显的影响缓存以及内存的性能。 3 | 4 | ### 1.会导致缺页中断的内存分配 5 | 下文代码采用两种方式分配pBuffer,请比对两种不同方式的耗时。 6 | 7 | ```c++ 8 | #include "stdafx.h" 9 | #include 10 | #include 11 | #include 12 | using namespace std::chrono; 13 | 14 | const int buff_size = 32 * 1024 * 1024; 15 | 16 | int main() 17 | { 18 | int * pBuffer = new int[buff_size]; //第一种分配方式 19 | //int * pBuffer = new int[buff_size] {}; //第二种分配方式 20 | 21 | auto start = std::chrono::system_clock::now(); 22 | for (int i = 0; i < buff_size; ++i) { 23 | pBuffer[i] *= 3; 24 | } 25 | auto end = std::chrono::system_clock::now(); 26 | 27 | std::chrono::duration elapsed_seconds = end - start; 28 | std::cout << "elapsed time: " << elapsed_seconds.count() * 1000 << "ms\n"; 29 | 30 | delete[] pBuffer; 31 | } 32 | ``` 33 | 34 | 在我的电脑上,第一种分配方式的运行时间大约为90ms,第二种分配方式的运行时间大约为26ms。两种分配方式唯一的区别在于后者在分配内存的时候添加了 {}。 35 | 36 | 当我们从堆里分配一个数组的时候,如果没有做任何初始化,那么操作系统只会返回一个虚拟地址映射,并不会真正地分配一块物理内存。当这个数组被访问的时候,一个缺页中断会被触发,内核才会真正分配一块物理内存。第一种分配方式在分配内存的时候,没有做初始化,所以访问内存的时候会导致大量缺页中断;第二种分配方式添加了{}强制初始化,因此在分配数组的时候内核就已经分配了物理内存。 37 | 38 | 在本例中,第一种方式(触发缺页中断)与第二种方式(初始化时分配物理内存)的时间差为64ms,这个时间差基本就是操作系统处理缺页中断并完成物理内存分配所需的时间。第一种分配方式中缺页中断耗费的时间甚至超过了业务逻辑本需要的时间,比较明显地影响了程序的性能。 39 | 40 | 说句题外话,我们分配了32X1024X1024字节的内存,而内存页的size一般为4096字节,所以一共触发了32X1024X1024/4096次缺页中断,对么?错误。虽然内存页的size一般为4096字节,但是操作系统以及硬件并不是以4096字节为单位映射内存,而是以一个略大的单位--一般是4096的2的n次幂倍(比如1MB)分配物理内存,具体细节因操作系统以及物理硬件而异。 41 | 42 | ### 2. 如何测量cpu cache的容量 43 | 下面这段代码可以在linux上测量缓存的容量。基本思路是, 44 | 45 | step1:以某个步长逐一访问一块内存的存储单元,然后统计访问时间。 46 | 47 | step2:增加步长,重复step1。 48 | 49 | step3:最后比较相邻两次的访问时间,找到差别最大的一次,这个步长就是cache容量。 50 | 51 | 这个方法的原理是,当缓存命中的时候即使步长不相等,访问内存的时间是差不多的;当缓存没有命中的时候,因为需要重新导入内存到缓存,会导致内存访问时间大大增加。所以,若要充分利用缓存,我们最好以地址连续的方式访问内存,尽量避免缓存的内容抖动。 52 | 53 | 以下代码来自这个链接,但是我在原代码的基础上略做了简化 https://github.com/SudarsunKannan/memlatency 54 | 55 | ```c++ 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #define ITER 1 65 | #define MB (1024*1024) 66 | #define MAX_N 16*MB 67 | #define STEPS_N 16*MB 68 | #define KB 1024 69 | #define CACHE_LN_SZ 64 70 | #define START_SIZE 1*MB 71 | #define STOP_SIZE 64*MB 72 | #define MIN_LLC 256*KB 73 | #define MAX_STRIDE 1024 74 | #define INT_MIN (-2147483647 - 1) 75 | 76 | using namespace std; 77 | unsigned int memrefs; 78 | volatile void* global; 79 | 80 | void LLCCacheSizeTest() 81 | { 82 | double retval,prev; 83 | struct timespec tps, tpe; 84 | int max =INT_MIN,index = 0, maxidx = 0, iter = 0, i=0; 85 | int diff =0, stride = 0, j = MIN_LLC; 86 | double change_per[10]; 87 | char *array = (char *)malloc(sizeof(char) *CACHE_LN_SZ * STEPS_N); 88 | 89 | while( j < STOP_SIZE) { 90 | 91 | stride = j-1; 92 | clock_gettime(CLOCK_REALTIME, &tps); 93 | 94 | for (iter = 0; iter < ITER; iter++) 95 | for (unsigned int i = 0; i < STEPS_N; i++) { 96 | array[(i*CACHE_LN_SZ) & stride] *= 100; 97 | } 98 | clock_gettime(CLOCK_REALTIME, &tpe); 99 | retval = ((tpe.tv_sec-tps.tv_sec)*1000000000 + tpe.tv_nsec-tps.tv_nsec)/1000; 100 | 101 | if(index > 0){ 102 | diff = retval - prev; 103 | if(max < diff) { 104 | max= diff; 105 | maxidx =j; 106 | } 107 | } 108 | cout << "LLCCacheSizeTest "<< retval <<" stride "<prev->next = j->next; 129 | j->next->prev = j->prev; 130 | return j; 131 | } 132 | 133 | struct node* insert_node( struct node *i, struct node *j) { 134 | if(!j) return NULL; 135 | 136 | i->next->prev = j; 137 | j->next = i->next; 138 | j->prev = i; 139 | i->next = j; 140 | return i; 141 | } 142 | 143 | int main(int argc, char *argv[]){ 144 | LLCCacheSizeTest(); 145 | return 0; 146 | } 147 | ``` 148 | 149 | 下面是测试结果: 150 | 151 | ```c++ 152 | uslinux01:/home/gbuilder/jge> ./lat 153 | LLCCacheSizeTest 102465 stride 262144 diffs: 0 154 | LLCCacheSizeTest 71912 stride 1048576 diffs: -30553 155 | LLCCacheSizeTest 60810 stride 2097152 diffs: -11102 156 | LLCCacheSizeTest 60840 stride 3145728 diffs: 30 157 | LLCCacheSizeTest 60841 stride 4194304 diffs: 1 158 | LLCCacheSizeTest 60886 stride 5242880 diffs: 45 159 | LLCCacheSizeTest 60978 stride 6291456 diffs: 92 160 | LLCCacheSizeTest 60857 stride 7340032 diffs: -121 161 | LLCCacheSizeTest 60555 stride 8388608 diffs: -302 162 | LLCCacheSizeTest 60821 stride 9437184 diffs: 266 163 | LLCCacheSizeTest 60825 stride 10485760 diffs: 4 164 | LLCCacheSizeTest 60824 stride 11534336 diffs: -1 165 | LLCCacheSizeTest 60696 stride 12582912 diffs: -128 166 | LLCCacheSizeTest 60858 stride 13631488 diffs: 162 167 | LLCCacheSizeTest 60696 stride 14680064 diffs: -162 168 | LLCCacheSizeTest 60696 stride 15728640 diffs: 0 169 | LLCCacheSizeTest 60727 stride 16777216 diffs: 31 170 | LLCCacheSizeTest 60906 stride 17825792 diffs: 179 171 | LLCCacheSizeTest 60819 stride 18874368 diffs: -87 172 | LLCCacheSizeTest 60869 stride 19922944 diffs: 50 173 | LLCCacheSizeTest 60768 stride 20971520 diffs: -101 174 | LLCCacheSizeTest 61037 stride 22020096 diffs: 269 175 | LLCCacheSizeTest 60756 stride 23068672 diffs: -281 176 | LLCCacheSizeTest 60810 stride 24117248 diffs: 54 177 | LLCCacheSizeTest 60574 stride 25165824 diffs: -236 178 | LLCCacheSizeTest 60874 stride 26214400 diffs: 300 179 | LLCCacheSizeTest 60880 stride 27262976 diffs: 6 180 | LLCCacheSizeTest 60799 stride 28311552 diffs: -81 181 | LLCCacheSizeTest 60756 stride 29360128 diffs: -43 182 | LLCCacheSizeTest 60837 stride 30408704 diffs: 81 183 | LLCCacheSizeTest 60794 stride 31457280 diffs: -43 184 | LLCCacheSizeTest 60884 stride 32505856 diffs: 90 185 | LLCCacheSizeTest 85404 stride 33554432 diffs: 24520 186 | LLCCacheSizeTest 60930 stride 34603008 diffs: -24474 187 | LLCCacheSizeTest 60862 stride 35651584 diffs: -68 188 | LLCCacheSizeTest 60877 stride 36700160 diffs: 15 189 | LLCCacheSizeTest 60832 stride 37748736 diffs: -45 190 | LLCCacheSizeTest 60917 stride 38797312 diffs: 85 191 | LLCCacheSizeTest 60830 stride 39845888 diffs: -87 192 | LLCCacheSizeTest 60860 stride 40894464 diffs: 30 193 | LLCCacheSizeTest 60588 stride 41943040 diffs: -272 194 | LLCCacheSizeTest 60952 stride 42991616 diffs: 364 195 | LLCCacheSizeTest 60833 stride 44040192 diffs: -119 196 | LLCCacheSizeTest 60888 stride 45088768 diffs: 55 197 | LLCCacheSizeTest 60759 stride 46137344 diffs: -129 198 | LLCCacheSizeTest 60902 stride 47185920 diffs: 143 199 | LLCCacheSizeTest 60817 stride 48234496 diffs: -85 200 | LLCCacheSizeTest 60904 stride 49283072 diffs: 87 201 | LLCCacheSizeTest 66884 stride 50331648 diffs: 5980 202 | LLCCacheSizeTest 61001 stride 51380224 diffs: -5883 203 | LLCCacheSizeTest 60925 stride 52428800 diffs: -76 204 | LLCCacheSizeTest 60963 stride 53477376 diffs: 38 205 | LLCCacheSizeTest 60925 stride 54525952 diffs: -38 206 | LLCCacheSizeTest 61015 stride 55574528 diffs: 90 207 | LLCCacheSizeTest 60996 stride 56623104 diffs: -19 208 | LLCCacheSizeTest 61065 stride 57671680 diffs: 69 209 | LLCCacheSizeTest 69275 stride 58720256 diffs: 8210 210 | LLCCacheSizeTest 61172 stride 59768832 diffs: -8103 211 | LLCCacheSizeTest 61129 stride 60817408 diffs: -43 212 | LLCCacheSizeTest 61293 stride 61865984 diffs: 164 213 | LLCCacheSizeTest 70822 stride 62914560 diffs: 9529 214 | LLCCacheSizeTest 61608 stride 63963136 diffs: -9214 215 | LLCCacheSizeTest 72650 stride 65011712 diffs: 11042 216 | LLCCacheSizeTest 74397 stride 66060288 diffs: 1747 217 | Effective LLC Cache Size 32 MB 218 | ``` 219 | 220 | 以下为我的linux cpu配置: 221 | ```c++ 222 | uslinux01:/home/gbuilder/jge> lscpu 223 | Architecture: x86_64 224 | CPU op-mode(s): 32-bit, 64-bit 225 | Byte Order: Little Endian 226 | CPU(s): 56 227 | On-line CPU(s) list: 0-55 228 | Thread(s) per core: 2 229 | Core(s) per socket: 14 230 | Socket(s): 2 231 | NUMA node(s): 2 232 | Vendor ID: GenuineIntel 233 | CPU family: 6 234 | Model: 63 235 | Model name: Intel(R) Xeon(R) CPU E5-2695 v3 @ 2.30GHz 236 | Stepping: 2 237 | CPU MHz: 1201.660 238 | CPU max MHz: 3300.0000 239 | CPU min MHz: 1200.0000 240 | BogoMIPS: 4601.56 241 | Virtualization: VT-x 242 | L1d cache: 32K 243 | L1i cache: 32K 244 | L2 cache: 256K 245 | L3 cache: 35840K 246 | ``` 247 | 248 | 从以上测试结果中,可以看出步长为33 554 432的时候,前后两次的访问时间差最大,因此可以确定缓存容量为32M。但是我的linux配置显示L3 cache为35840K,这是怎么回事呢?话说笔者也对此颇为费解。据笔者了解,硬件厂商统计物理缓存容量的单位可能并不是1024字节,而是1000字节。另外,L3 cache为35840K也颇为古怪,为什么Cache的容量不是2的n次幂呢?但是即使如此也不能完全解释这个问题。那也许是仅仅通过应用程序层面无法100%准确的测量缓存的容量。总之,如果读者能找到更合理的解释请让笔者知道,谢谢。 249 | 250 | ### 3.Cache line: 251 | 现代CPU访问某个内存地址之后会把该内存地址周边的相邻地址的内容以cache line为单位导入缓存,cache line的一般容量是64字节,也就是16个整型。以下代码可以用来验证cache line的容量。基本思路以及原理同cache容量的测试,此处不赘述。 252 | 253 | ```c++ 254 | #include "stdafx.h" 255 | #include 256 | #include 257 | #include 258 | using namespace std::chrono; 259 | 260 | const int buff_size = 32 * 1024 * 1024; 261 | 262 | void test_cache_hit(int * buffer, int size, int step) 263 | { 264 | for (int i = 0; i < size;) { 265 | buffer[i] *= 3; 266 | i += step; 267 | } 268 | } 269 | 270 | int main() 271 | { 272 | int * buffer = new int[buff_size]{}; 273 | 274 | for (int i = 0; i < 16; ++i) { 275 | int step = (int)pow(2, i); 276 | 277 | auto start = std::chrono::system_clock::now(); 278 | test_cache_hit(buffer, buff_size, step); 279 | auto end = std::chrono::system_clock::now(); 280 | 281 | std::chrono::duration elapsed_seconds = end - start; 282 | std::cout << "step: " << step << ", elapsed time: " << elapsed_seconds.count() * 1000 << "ms\n"; 283 | } 284 | 285 | delete[] buffer; 286 | } 287 | ``` 288 | 289 | 下面是代码的运行结果: 290 | ```c++ 291 | step: 1, elapsed time: 66.3925ms 292 | step: 2, elapsed time: 34.1113ms 293 | step: 4, elapsed time: 26.5611ms 294 | step: 8, elapsed time: 27.8086ms 295 | step: 16, elapsed time: 27.8812ms 296 | step: 32, elapsed time: 19.7227ms 297 | step: 64, elapsed time: 11.3431ms 298 | step: 128, elapsed time: 4.7402ms 299 | step: 256, elapsed time: 2.4116ms 300 | step: 512, elapsed time: 2.0155ms 301 | step: 1024, elapsed time: 1.7971ms 302 | step: 2048, elapsed time: 1.1497ms 303 | step: 4096, elapsed time: 0.5761ms 304 | step: 8192, elapsed time: 0.2719ms 305 | step: 16384, elapsed time: 0.1615ms 306 | step: 32768, elapsed time: 0.1099ms 307 | Press any key to continue . . . 308 | ``` 309 | 310 | 步长为2、4、8、16的时候耗时都差不多,这是因为cache line的容量是64字节,因此这几个步长对内存的访问量虽然大大不同,但是耗时都接近。值得注意的是,步长1和2的耗时差别相当大。我觉得cache line在这两个步长依然起作用的,只是两者的“计算消耗”耗时差别比较大,例如循环计数加法运算等等,所以1和2之间的差别可以理解为运算量的差别。 311 | 312 | ### 4. 多核cpu cache的false sharing问题 313 | 当我们运行多线程程序的时候,一般每个线程都会占用一个核。而对于多核cpu,每个核都有一个cache。当多个线程访问几个相邻的内存地址的时候,如果这些地址可以被映射为同一个cache line的话,那么这段size为64字节(也就是cache line的size)的内存会被导入各个cpu核的cache中。因此,各个核的cache line需要同步,这可能导致程序的运行时间大大增加。这个问题称为 false sharing。 314 | 315 | 下图展示了这种情况。 316 | 317 | ![false sharing](https://github.com/coolshellx/articles/blob/master/false_cache.png) 318 | 319 | 两个线程1和2分别运行在core0和core1上面,它们分别访问内存地址0xA000(保存了值为1的整数)以及0xA004(保存了值为2的整数)。因为这两个地址可以被映射为同一个cache line,所以core0和core1的cache都导入了一条包含0xA000以及0xA004的cache line。当线程1写0xA000的时候,即使线程2并没有访问0xA000,它的cache line也要做同步。而这有潜在的性能问题。 320 | 321 | 下面的代码来自这个链接: 322 | https://vorbrodt.blog/2019/02/02/cache-lines/ 323 | 324 | ```c++ 325 | #include "stdafx.h" 326 | #include 327 | #include 328 | #include 329 | #include 330 | 331 | using namespace std; 332 | using namespace chrono; 333 | 334 | const int CACHE_LINE_SIZE = 64;//sizeof(int); 335 | const int SIZE = 2; //第一种方式 336 | //const int SIZE = CACHE_LINE_SIZE / sizeof(int) + 1; //第二种方式 337 | const int COUNT = 100'000'000; 338 | 339 | int main(int argc, char** argv) 340 | { 341 | srand((unsigned int)time(NULL)); 342 | 343 | int* p = new int[SIZE]; 344 | 345 | auto proc = [](int* data) { 346 | for (int i = 0; i < COUNT; ++i) 347 | *data = *data + rand(); 348 | }; 349 | 350 | auto start_time = high_resolution_clock::now(); 351 | 352 | std::thread t1(proc, &p[0]); 353 | std::thread t2(proc, &p[SIZE - 1]); 354 | 355 | t1.join(); 356 | t2.join(); 357 | 358 | auto end_time = high_resolution_clock::now(); 359 | cout << "Duration: " << duration_cast(end_time - start_time).count() / 1000.f << " ms" << endl; 360 | 361 | getchar(); 362 | 363 | return 1; 364 | } 365 | ``` 366 | 367 | 在以上的代码中,我们可以试着用两种不同的size分配内存并测量运行时间,我们发现第一种方式和第二种方式有明显的性能差别。在我的电脑上,第一种方式耗时7030ms,第二种方式耗时3369ms。 368 | 369 | 第一种方式中,两个线程同时访问两个相邻的内存地址,并且这两个地址会被映射为同一个cache line,所以这两个线程会把同一段cache line的内存导入各自的cache。当一个线程修改一个内存地址的内容的时候,另一个线程不得不同步cache line,而从测试结果来看同步操作的成本相当高。 370 | 371 | 解决这个问题的方法就是避免两个线程访问的内存地址落入同一个cache line;第二种方式增加了两个内存地址间的偏移,从而避免了false sharing的问题。 372 | 373 | ### 5. 一些有用的链接 374 | intel关于false sharing的链接 375 | https://software.intel.com/en-us/articles/avoiding-and-identifying-false-sharing-among-threads 376 | 377 | Igor有一篇关于cache的好文,列举了cache相关的各种技巧,并且采用C#范例代码 378 | http://igoro.com/archive/gallery-of-processor-cache-effects/ 379 | 380 | wiki上的cpu cache条目包含了部分关于cache line的内容 381 | https://en.wikipedia.org/wiki/CPU_cache 382 | 383 | 下面的git里,包含一份可以测量cache line以及cache容量的工业级代码。但是基本思路还是和上文的范例一样 384 | https://github.com/SudarsunKannan/memlatency 385 | -------------------------------------------------------------------------------- /07-Reunderstand.C++.ShallowCopy.and.DeepCopy.md: -------------------------------------------------------------------------------- 1 | ### 目录 2 | 3 | [前言](#pre) 4 | 5 | [C++ 的复制机制](#a) 6 | 7 | [默认拷贝构造函数](#b) 8 | 9 | [拷贝构造函数和赋值操作符](#c) 10 | 11 | [浅拷贝](#d) 12 | 13 | [深拷贝](#e) 14 | 15 | [C++11 右值引用和 move 语义](#f) 16 | 17 | 18 | ### 1、前言 19 | 20 | 最近在找工作面试的过程中,发现面试官经常问到了 C++ 的浅拷贝和深拷贝这个问题,在这里总结一下自己的一些理解和实践,希望对有需要的别人带来一些帮助。 21 | 22 | 下面我分七个部分进行介绍,分别是:C++ 的复制机制,默认拷贝构造函数,拷贝构造函数和赋值操作符,浅拷贝,深拷贝,C++11 右值引用和 move 语义。 23 | 24 | ### 2、C++ 的复制机制 25 | 26 | C++ 中对象的复制就如同”克隆“,用一个已有的对象快速复制出多个完全相同的对象。一般而言,以下三种情况都会使用对象的复制。 27 | 28 | 29 | 30 | > (1)建立一个新对象,并用另一个同类的已有对象对新对象进行初始化,例如: 31 | 32 | ```c++ 33 | class Base 34 | { 35 | private: 36 | int w; 37 | int h; 38 | }; 39 | int main(int argc,const char *argv[]) 40 | { 41 | Base b1; 42 | base b2(b1); // 使用b1初始化b2,此时会进行对象的复制 43 | return 0; 44 | } 45 | ``` 46 | 47 | 48 | 49 | > (2) 当函数的参数是类的对象时,调用此函数使用的是值传递,也会发生对象的复制,例如: 50 | 51 | ```c++ 52 | void fun1(Base base) 53 | { 54 | //TODO 55 | } 56 | int main(int argc,const char *argv[]) 57 | { 58 | Base b1; 59 | fun1(b1); // 此时会发生对象的复制 60 | return 0; 61 | } 62 | ``` 63 | 64 | 65 | 66 | > (3) 当函数的返回值是类的对象时,当函数调用结束时,需要将函数的对象复制到一个临时的对象并传给该函数的调用处,也会发生对象的复制,例如: 67 | 68 | ```c++ 69 | Base fun2() 70 | { 71 | //TODO 72 | Base base; 73 | return base; 74 | } 75 | int main(int argc,const char *argv[]) 76 | { 77 | Base b2; 78 | b2 = fun2(); 79 | // 在 fun2 返回对象时,会执行对象复制,复制出一临时对象,然后将此临时对象“赋值”给 b2 80 | return 0; 81 | } 82 | ``` 83 | 84 | 注意到:对象的复制都是通过一种特殊的构造函数来完成的,这种特殊的构造函数就是拷贝构造函数 **(copy constructor,也叫复制构造函数)。** 85 | 86 | 拷贝构造函数在大多数情况下都很简单,甚至在我们都不知道它存在的情况下也能很好发挥作用,但是在一些特殊情况下,**特别是在对象里有动态成员,或者指针类型变量的时候**,就需要我们特别小心地处理拷贝构造函数了。下面我们就来看看拷贝构造函数的使用。 87 | 88 | 89 | 90 | ### 3、默认拷贝构造函数 91 | 92 | 很多时候在我们都不知道拷贝构造函数的情况下,传递对象给函数参数或者函数返回对象都能很好的进行,这是因为 **编译器会给我们自动产生一个拷贝构造函数**,这就是 **“默认拷贝构造函数”** ,这个构造函数很简单,仅仅使用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值,它一般具有以下形式: 93 | 94 | ```c++ 95 | Base::Base(const Base& t) 96 | { 97 | w = t.w; 98 | h = t.h; 99 | } 100 | ``` 101 | 102 | 当然了,有人说,我怎么一般没实现这个函数呢,照样可以进行类的复制操作啊,这是因为以上代码不用我们编写,编译器会为我们自动生成。但是如果认为这样就可以解决对象的复制问题,那就错了,让我们来考虑以下一段代码: 103 | 104 | ```c++ 105 | //Author:rongweihe 106 | //Date :2019/04/20 107 | #include 108 | using namespace std; 109 | //浅拷贝:对于基本类型的数据和简单的对象,它们之间的拷贝非常简单;就是按位复制内存 110 | class Base 111 | { 112 | public: 113 | Base() 114 | { 115 | c++; 116 | } 117 | ~Base() 118 | { 119 | cout<<"调用析构函数"< 4、拷贝构造函数和赋值操作符 210 | 211 | 拷贝构造函数和赋值操作符的行为比较相似,都是将一个对象的值复制给另一个对象,但是其结果却有些不同。 212 | 213 | 拷贝构造函数使**用传入对象的值生成一个新的对象的实例**,而赋值操作符是将对象的值复制给一个**已经存在的实例**。 214 | 215 | 这种区别从两者的名字也可以分辨出来:拷贝构造函数也是一种构造函数,那么它的功能就是创建一个新的对象实例;赋值操作符是执行某种运算,将一个对象的值复制给另一个对象(已经存在的)。 216 | 217 | **要区分调用的是拷贝构造函数还是赋值操作符,主要是看是否有新的对象实例产生。如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值操作符**。 218 | 219 | 调用拷贝构造函数主要的场景也在开头说过了: 220 | 221 | - 使用一个对象给另一个对象初始化。 222 | 223 | - 对象作为函数的参数,以值传递的方式传给函数。  224 | - 对象作为函数的返回值,以值的方式从函数返回。 225 | 226 | 下面来具体实践一下 227 | 228 | ```c++ 229 | #include 230 | using namespace std; 231 | class Base 232 | { 233 | public: 234 | Base() {} 235 | Base(const Base& b) 236 | { 237 | cout<<"调用拷贝构造函数"< 分析: 287 | > 288 | > - 1、对应于开头说的第一种复制机制,使用对象 b1 来创建一个新的对象 b2。也就是产生了新的对象,所以调用的是拷贝构造函数。 289 | > - 2、首先声明一个对象 b3,然后使用赋值运算符"=",将 b1 的值复制给 b3,显然是调用赋值运算符,为一个已经存在的对象赋值。 290 | > - 3、对应于开头说的第二种复制机制,以值传递的方式将对象 b3 传入函数 f1 内,调用拷贝构造函数构建一个函数 f1 可用的实参。 291 | > - 4、对应于开头说的第三种复制机制,这条语句拷贝构造函数和赋值运算符都调用了。函数 f2 以值的方式返回一个 Base 对象, 292 | > 在返回时会调用拷贝构造函数创建一个临时对象 tmp 作为返回值;返回后调用赋值运算符将临时对象 tmp 赋值给 b3。 293 | > - 5、按照 4 的逻辑,应该是首先调用拷贝构造函数创建临时对象;然后再调用拷贝构造函数使用刚才创建的临时对象创建新的对象 b4,也就是会调用两次拷贝构造函数。 294 | 295 | 296 | 297 | 实际执行结果如下: 298 | 299 | > - 调用拷贝构造函数 300 | > - 调用赋值操作符 301 | > - 调用拷贝构造函数 302 | > - 调用赋值操作符 303 | 304 | 305 | 结果和输出前的分析不一致, 306 | 307 | 相似的代码,我在网上看到,有网友跑出的结果如下 308 | 309 | ![439761-20161207163440429-300030531.png](https://i.loli.net/2019/05/02/5cca9aab3490c.png) 310 | 311 | 我怀疑是编译器的问题,我在 https://wandbox.org/ 这个在线网站上,分别使用了GCC-4.4.7 - GCC-8.3.0版本编译上面的代码,得到的结果跟我本地运行结果是一样的。 312 | 313 | 我觉得出现这种现象的原因是:代码在不同的平台,编译器会做不同的优化,导致打印的结果不同,4 部分仅仅打印了 “调用赋值操作符”,然后 5 部分什么都没打印,说明编译器自身做了优化,把 5 当做了默认构造函数。 314 | 315 | ### 5、浅拷贝 316 | 317 | **浅拷贝:** 指的是在对象复制时,只是将对象中的数据成员进行简单的赋值,上面的例子都是属于浅拷贝的情况,**默认拷贝构造函数执行的也是浅拷贝**。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了,让我们考虑如下一段代码: 318 | 319 | ```c++ 320 | #include 321 | using namespace std; 322 | class Base 323 | { 324 | public: 325 | Base()//构造函数,p 指向堆中分配的空间 326 | { 327 | cout<<"new"< new 356 | > delete 的地址= 0x62b228 357 | > delete 的地址= 0x62b228 358 | 359 | **p 指针被分配一次内存,但是程序结束时该内存却被释放了两次,会造成内存泄漏问题!** 360 | 361 | 原因就在于在进行对象复制时,对于动态分配的内容没有进行正确的操作。我们来分析一下: 362 | 363 | 在运行定义 obj1 对象后,由于在构造函数中有一个动态分配的语句,因此执行后的内存情况大致如下: 364 | 365 | ![深拷贝001.jpg](https://i.loli.net/2019/04/20/5cbad59365932.jpg) 366 | 367 | 在使用 obj1 复制 obj2 时,由于执行的是浅拷贝,只是将成员的值进行赋值,所以此时 obj1.p 和obj2.p 具有相同的值,也即这两个指针指向了堆里的同一个空间,如下图所示: 368 | 369 | ![深拷贝002.jpg](https://i.loli.net/2019/04/20/5cbad6401bb85.jpg) 370 | 371 | 当然,这不是我们所期望的结果,在销毁对象时,两个对象的析构函数将对**同一个内存空间释放两次**,这就是错误出现的原因。我们需要的不是两个 *p* 有相同的值,而是两个 *p* **指向**的空间有相同的值,解决办法就是使用“深拷贝”。 372 | 373 | 374 | 375 | ### 6、深拷贝 376 | 377 | 在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间,如上面的例子就应该按照如下的方式进行处理: 378 | 379 | ```c++ 380 | #include 381 | using namespace std; 382 | class Base 383 | { 384 | public: 385 | Base()//构造函数,p 指向堆中分配的空间 386 | { 387 | p = new int(100); 388 | cout<<"new1 and &p = "< new1 and &p = 0x3cb228 424 | > new2 and &p = 0x3cb238 425 | > delete 的地址= 0x3cb238 426 | > delete 的地址= 0x3cb228 427 | 428 | 此时,在完成对象的复制后,内存的一个大致情况如下: 429 | 430 | ![深拷贝003.jpg](https://i.loli.net/2019/04/20/5cbad8f1bd8d6.jpg) 431 | 432 | 注意到,此时 obj1 的 p 和 obj2 的 p 各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。 433 | 434 | 435 | 436 | 437 | ###  7、C++11 新特性:右值引用与 move 语义 438 | 439 | 440 | 441 | 442 | [C++11 ](https://zh.wikipedia.org/wiki/C%2B%2B11) 的 **右值引用** 是一个颇为重要的新特性,解决了C++ 中一个广为诟病的性能问题。它实现了转移语义 (Move Sementics) 和精确传递 (Perfect Forwarding)。它的主要目的有两个方面: 443 | 444 | > 1. 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。 445 | > 2. 能够更简洁明确地定义泛型函数。 446 | 447 | 右值引用特性允许我们对右值进行修改。借此可以实现 **move语义**: **右值不需要被复制直接传递给构造函数,操作结束后空的右值析构也不会销毁内存。** 448 | 449 | 在 C++ 之前的标准中,右值是不允许被改变的,实践中也通常使用 `const T&` 的方式传递右值。然而这是效率低下的做法,例如: 450 | 451 | ```c++ 452 | Base get(){ 453 | Base p; 454 | return p; 455 | } 456 | Base p = get(); 457 | ``` 458 | 459 | 上述获取右值并初始化 `p` 的过程包含了 `Base` 的 3 个构造过程和 2 个析构过程。 使用**右值引用**的特性我们可以避免其中不必要的内存拷贝,从右值中直接拿数据过来初始化或修改左值。 一个 `move` 构造函数是这样声明的: 460 | 461 | ```c++ 462 | class Person{ 463 | public: 464 | Person(Person&& rhs){...} 465 | ... 466 | }; 467 | ``` 468 | 469 | 470 | 471 | #### 左值和右值 472 | 473 | C++ 表达式的值有三种类别:左值、右值和临终值。 其中**左值**是指在表达式的外部保留的对象,可以将左值视为有名称的对象,所有的变量都是左值。 **右值**是一个不在使用它的表达式的外部保留的临时值,比如函数的返回值、字面常量。 **临终值**是生命周期已经结束,但内存仍未回收的值,比如函数的返回值可以声明为 `int&&`。 若要更好地了解左值和右值之间的区别,看下面的示例: 474 | 475 | ```c++ 476 | int a = 3; // a 是变量,所以它是一个左值 477 | // 3 是字面常量,所以它是一个右值 478 | int b = a; // b 是变量,也是一个左值。a 是有名称的,也是一个左值 479 | b = (a + 1); // (a + 1) 是一个右值,它是一个背后的没有名称的值 480 | b = getValue(); // getValue() 的返回值是一个右值,他没有名称 481 | ``` 482 | 483 | > 可以通过表达式的值是否可以取地址来判断左值还是右值。左值都是可以取地址的。 484 | 485 | 右值的特点在于它 **不被后续计算所需要**,因为它连名字都没有,程序中无法再次访问一个右值。 486 | 487 | **左值的声明符号为”&”, 为了和左值区分,右值的声明符号为”&&”。** 488 | 489 | 例子代码 490 | 491 | ```c++ 492 | void process_value(int& i) { 493 | std::cout << "LValue processed: " << i << std::endl; 494 | } 495 | 496 | void process_value(int&& i) { 497 | std::cout << "RValue processed: " << i << std::endl; 498 | } 499 | 500 | int main() { 501 | int a = 0; 502 | process_value(a); 503 | process_value(1); 504 | } 505 | 运行结果 : 506 | LValue processed: 0 507 | RValue processed: 1 508 | ``` 509 | 510 | 511 | 512 | #### 右值的重复拷贝 513 | 514 | 右值虽然是不被后续计算所需要的,但它仍然需要构造和析构。 这在 C++ 中造成了不少的代价,下面来看具体的例子: 515 | 516 | ```c++ 517 | class Base{ 518 | char* name; 519 | public: 520 | Base(const char* p){ 521 | size_t n = strlen(p) + 1; 522 | name = new char[n]; 523 | memcpy(name, p, n); 524 | } 525 | Base(const Base& p){ 526 | size_t n = strlen(p.name) + 1; 527 | name = new char[n]; 528 | memcpy(name, p.name, n); 529 | } 530 | ~Base(){ delete[] name; } 531 | }; 532 | ``` 533 | 534 | 当我们拷贝 `Base` 对象时,会有额外的不需要的内存分配过程,例如: 535 | 536 | ```c++ 537 | Base getAlice(){ 538 | Base p("alice"); // 对象创建。调用构造函数,一次 new 操作 539 | return p; // 返回值创建。调用拷贝构造函数,一次 new 操作 540 | // p 析构。一次 delete 操作 541 | } 542 | int main(){ 543 | Base a = getAlice(); // 对象创建。调用拷贝构造函数,一次 new 操作 544 | // 右值析构。一次 delete 操作 545 | return 0; 546 | } // a 析构。一次 delete 操作 547 | ``` 548 | 549 | 正常情况下,会执行三次构造函数三次析构函数。**返回值优化** 和 **move语义** 便是用来避免这些不必要的构造过程和动态内存操作的。 550 | 551 | 552 | 553 | #### 返回值优化 554 | 555 | 事实上编译器会对上述代码进行 [返回值优化](https://zh.wikipedia.org/wiki/返回值优化),其实这里是 **返回值优化(RVO)**,可以减少两次拷贝构造。 上述代码其实只需要一次构造和一次析构。为了让代码更加清晰,我们来看去掉动态内存相关代码的`Base`类: 556 | 557 | ```c++ 558 | struct Base{ 559 | Base(const char* p){ 560 | cout<<"constructor"< 655 | 656 |
657 | 658 | 参考: 659 | 660 | https://blog.csdn.net/bluescorpio/article/details/4322682; 661 | 662 | https://harttle.land/2015/10/11/cpp11-rvalue.html; 663 | 664 | https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/index.html 665 | 666 | 《*C++*编程思想 第*1*卷》 *Bruce Eckel*。 667 | 668 | 669 | 670 |
671 | 672 |
673 | -------------------------------------------------------------------------------- /00-How.To.Set.Up.An.High.Efficiency.Work.Enviornment.md: -------------------------------------------------------------------------------- 1 | 程序员是一个很懒的群体,总想着能够让代码为自己干活,他们不断地把工作生活中的一些事情用代码自动化了,从而让整个社会的效率运作地越来越高。所以,程序员在准备去优化这个世界的时候,都会先要优化自己的工作环境,是所谓“工欲善其事,必先利其器”。 2 | 3 | 我们每个程序员都应该打造一套让自己更为高效的工作环境。那怕就是让你少输入一次命令,少按一次键,少在鼠标和键盘间切换一次,都会让程序员的工作变得更为的高效。所以,程序员一般需要一台性能比较好,不会因为开了太多的网页或程序就卡得不行的电脑,还要配备多个显示器,一个显示器写代码,一个查文档,一个测试运行结果,而不必在各种窗口来来回回的切换……在大量的窗口间切换经常会迷路,而且也容易出错(分不清线上或测试环境)…… 4 | 5 | 除了硬件上的装备,软件上也是能够得升程序员生产力的地方,下面我们就想在软件层面说一下可以提升程序员生产力的一些东西。 6 | 7 | 这篇文章在查阅了很多资料以及结合自身的实践,从四个方面来说明如何打造一套适合自己的工具,分别是**编辑器篇**、**命令行篇**、**Shell和脚本篇**、**版本管理篇**。 8 | 9 | 10 | 11 | ### 编辑器篇 12 | 13 | 对于程序员来说,日常与编辑器打交道的时间非常长。编辑器是值得我们投入大量时间去学习并且定制来满足我们的需要。 14 | 15 | 如何选择合适的编辑器呢?编辑器有很多,有的就是为程序写程序而打造的,比如:**Eclipse**,**IntelliJ IDEA**,还有新贵 **Visual Studio Code**,这些专门用来写代码的又叫 IDE - 集成开发环境,这些个IDE的能力很强,可以让你的编码更为的高效,我用很多的IDE,目前来说,IntelliJ 和 VSCode 这两个 IDE 是最有效率的,目前来说,**IntelliJ IDEA 的功能在三者中更为地强大也更好用**,除了启动慢点,它依然是Java、C/C++、Python等编程语言中最有生产力的IDE,**但是Visual Studio Code的潜力更为巨大**,我也经常用它写Go、Javascript、Python、C/C++…… 16 | 17 | 但是,除些之外,程序员需要一个更为轻量的编辑器,使用这种编辑器的目地,主要是不是为快速地维护一些事情,比如:配置文件、小改程序、阅读代码、查看日志……,这些工作也是非常花费时间的,而且会伴随着轻量的编程,所以,需要一些更为简单的编辑器。在这些编程器中,我们应当去学习那些能够提升生产力,有很强的生命力,并且能够适应各类环境的编辑器。 18 | 19 | **Vim** 和 **Emacs** 就是非常值得投资的编辑器,如果你决定开始学习 Vim/Emacs,刚开始的时候会感觉高很痛苦,甚至会伤害到自己的生产力,但是没关系,通过两三周的专注练习后,就可以通过学习编辑器的死亡之谷,很快就可以赚回票价了。 20 | 21 | 使用 Vim/Emacs 的另一个重要好处是,当你在处理一些线上问题的时候,有时候需要一个 IDE,你发现在一个Term 终端上是运行不了图形界面的编辑器了,你只有 Vim/Emacs 这样的选择。在 Coolshell 上有一篇《[无插件Vim 编程技巧](https://coolshell.cn/articles/11312.html)》你可以移步一看。 22 | 23 | 为什么不学习 Notepad,因为,**使用高效编辑器的终极目标是抛弃鼠标,让手编辑的速度可以跟的上大脑思考的速度**。就我自己来说,从大二开始接触 Emacs 开始,已经使用 5 年了,通过不断的对 Emacs 进行定制,已经有了一套自己的使用模式了,每次打开 Emacs 就像是打开了自己才知道的秘籍,这是其他 IDE 都没有办法比的,通过不断定制自己的工具,可以激发自己创造的欲望。 24 | 25 | 但是我不参加编辑器的圣战,这些都是无意义的口水战。在一次接触了 Vim 之后,我反而喜欢上 Vim 的编辑模式了,于是在 Emacs 中使用上了 Vim 的编辑模式,实际的情况是这两者并不冲突(皓哥翻译的那篇 《[简明 Vim 练级攻略](https://coolshell.cn/articles/5426.html)》文章非常不错)。 26 | 27 | 在 Emacs 上,有很多有用的特性和插件。 Emacs 的 **org-mode**,这个工具用来写作,还可用来做任务管理,任务计时器,甚至可以用来做表格的计算。功能上已经完全不是 markdown 能比的,但是遗憾的是目前只能在 Emacs 中使用(貌似有人在 Vim 上实现过)。 28 | 29 | 还有就是 Emacs 中的用来做文件管理的插件 **[helm](https://github.com/emacs-helm/helm)** 和 **[ivy](https://github.com/abo-abo/swiper)**,这两个插件完成的功能差不多,但是 helm 比较重量级一些,如果一个项目的代码量比较大时,文件的切换和搜索就没那么快了,所以我更喜欢轻量级的 ivy。 30 | 31 | 还有两个我最喜欢的插件,**[evil](https://github.com/emacs-evil/evil)** 和 **[magit](https://magit.vc/)**,evil 可以让 Emacs 变身成为一个 Vim,大多数的 Vim 操作都可以覆盖上。magit 是一个可以使 Emacs 变身成为 Git 的插件。当然其他的各种语言的支持插件就是常规操作了,不多说。 32 | 33 | 下面推荐一些 Emacs 和 Vim 的学习资源: 34 | 35 | **Emacs** 36 | 37 | - **一年成为 Emacs 高手**,当年我也是靠着这一篇文章真正入门 emacs, 38 | 39 | https://github.com/redguardtoo/mastering-emacs-in-one-year-guide/blob/master/guide-zh.org 40 | 41 | - 陈斌 (一年成为 Emacs 高手的作者)的 Emacs 配置,是一个很不错的配置,我现在使用的配置要就是从这个演化来的 https://github.com/redguardtoo/emacs.d 42 | - Steve Purcell 大牛的配置,很适合 Web 开发者:https://github.com/purcell/emacs.d 43 | - Spacemacs 配置,适合新手,有着非常完善的文档:https://github.com/syl20bnr/spacemacs 44 | - reddit 的 Emacs 频道,最前沿的 Emacs 技巧:https://www.reddit.com/r/emacs/ 45 | - Emacs org-mode 的文档,从这个文档中你可以发现纯文本的魔力:https://orgmode.org/ 46 | - Emacs Wiki,上面有大量的 Emacs 插件的实践,虽然质量参差不齐,但是也有很多优秀的插件 https://www.emacswiki.org/emacs/EmacsWiki 47 | 48 | **Vim** 49 | 50 | - Doist 创业公司 CEO amix 的 Vim 配置,被称之为最强 vimrc:https://github.com/amix 51 | - junegunn 是韩国的一个大牛,擅长写 Vim 插件,他的 Vim 插件看起来总是令人赏心悦目:https://github.com/junegunn/vim-plug 52 | - SpaceVim,一个开箱即用的 Vim 配置,对新手很友好,和 Spacemacs 一样,有着很完善的文档:https://github.com/SpaceVim/SpaceVim 53 | - reddit 的 Vim 频道,有很多最前沿的 Vim 技巧:https://www.reddit.com/r/vim/ 54 | - Vimtutor,自带的新手指引:`vimtutor` 55 | 56 | 57 | ### 命令行篇 58 | 59 | 一个好的、可定制的命令行环境可以给工作效率带来很大的提升。如果你还在使用这个,那就差点意思了: 60 | 61 | ![](http://rayjun.oss-cn-beijing.aliyuncs.com/shell/Image.png) 62 | 63 | 在不同的操作系统下,都有着很不错的命令行工具,比如 Mac 下的 **Iterm2**,Linux 下的原生命令行,如果你是在 Windows 下工作,问题也不大,因为 Windows 下现在有了 **WSL**。WSL 提供了一个由微软开发的Linux兼容的内核接口(不包含Linux内核代码),然后可以在其上运行GNU用户空间,例如 Ubuntu,openSUSE,SUSE Linux Enterprise Server,Debian和Kali Linux。这样的用户空间可能包含 Bash shell 和命令语言,使用本机 GNU/Linux 命令行工具(sed,awk 等),编程语言解释器(Ruby,Python 等),甚至是图形应用程序(使用主机端的X窗口系统)。 64 | 65 | 使用命令行可以完成所有日常的操作,新建文件夹(mkdir)、新建文件(touch)、移动(mv)、复制(cp)、删除(rm)等等。而且使用 Linux/Unix 命令行最好的方式是可以用 awk、sed、grep、xargs、find、sort 等等这样的命令,然后用管道把其串起来,就可以完成一个你想要的功能,尤其是一些简单的数据统计功能。这是Linux命令行不可比拟的优势。比如: 66 | 67 | - 查看连接你服务器 top10 用户端的 IP 地址: 68 | 69 | `netstat -nat | awk '{print $5}' | awk -F ':' '{print $1}' | sort | uniq -c | sort -rn | head -n 10` 70 | 71 | - 查看一下你最常用的10个命令: 72 | 73 | `cat .bash_history | sort | uniq -c | sort -rn | head -n 10 (or cat .zhistory | sort | uniq -c | sort -rn | head -n 10` 74 | 75 | 在命令行中使用 **alias** 可以将使用频率很高命令或者比较复杂的命令合并成一个命令,或者修改原生的命令。 76 | 77 | 下面这几个命令,可能是你天天都在敲的。所以,你应该设置成 alias 来提高效率 78 | 79 | ``` 80 | alias nis="npm install --save " 81 | alias svim='sudo vim' 82 | alias mkcd='foo(){ mkdir -p "$1"; cd "$1" }; foo ' 83 | alias install='sudo apt get install' 84 | alias update='sudo apt-get update && sudo apt-get upgrade' 85 | alias ..="cd .." 86 | alias ...="cd ..; cd .." 87 | alias www='python -m SimpleHTTPServer 8000' 88 | alias sock5='ssh -D 8080 -q -C -N -f user@your.server' 89 | ``` 90 | 你还可以参考如下的一些文章,看看别人是怎么用好 `alias` 的 91 | 92 | - https://www.cyberciti.biz/tips/bash-aliases-mac-centos-linux-unix.html 93 | - https://www.digitalocean.com/community/questions/what-are-your-favorite-bash-aliases 94 | - https://www.linuxtrainingacademy.com/23-handy-bash-shell-aliases-for-unix-linux-and-mac-os-x/ 95 | - https://brettterpstra.com/2013/03/31/a-few-more-of-my-favorite-shell-aliases/ 96 | 97 | 98 | 命令行中除了原生的命令之外,还有很多可以提升使用体验的工具。下面罗列一些很不错的命令,把原生的命令增强地很厉害: 99 | 100 | - **fasd** 增强了 `cd` 命令 (https://github.com/clvv/fasd)。 101 | 102 | - **bat** 增强了 `cat` 命令 (https://github.com/sharkdp/bat)。如果你想要有语法高亮的 `cat`,可以试试 **ccat**(https://github.com/jingweno/ccat) 103 | 104 | - **exa** 增强了 `ls` 命令(https://github.com/ogham/exa),如果你需要在很多目录上浏览各种文件 ,**lsd**(https://github.com/Peltoche/lsd)在 `ls` 的基础上可以显示文件夹和不同类型文件的图标,**ranger** 命令可以比 `cd` 和 `cat` 更有效率(https://github.com/ranger/ranger),甚至可以在你的终端预览图片。 105 | 106 | - **fd** 是一个比 `find` 更简单更快的命令(https://github.com/sharkdp/fd),他还会自动地忽略掉一些你配置在 `.gitignore` 中的文件,以及 `.git` 下的文件。 107 | 108 | 109 | - **fzf** 是另外一个很好用的文件搜索神器 (https://github.com/junegunn/fzf),其主要是搜索当前目录以下的文件,还可以使用 `fzf --preview 'cat {}'` 边搜索文件边浏览内容。 110 | 111 | - `grep` 是一个上古神器,然而,**ack** (https://beyondgrep.com/)、**ag** (https://github.com/ggreer/the_silver_searcher) 和 **rg** (https://github.com/BurntSushi/ripgrep) 是更好的grep,和上面的fd一样,在递归目录匹配的时候,会忽略到你配置在 .gitignore 中的规则。 112 | 113 | - `rm` 是一个危险的命令,尤其是各种 `rm -rf …`,所以,**trash**(https://github.com/andreafrancia/trash-cli/) 是一个更好的删除命令。 114 | 115 | - `man` 命令是好读文档的命令,但是man的文档有时候太长了,所以,你可以试式 **tldr**(https://github.com/tldr-pages/tldr) 命令,把文档上的一些示例整出来给你看。 116 | 117 | - 如果你想要一个图示化的`ping`,你可以试试 **prettyping** (https://github.com/denilsonsa/prettyping) 。 118 | 119 | - 如果你想搜索以前打过的命令,不要再用 Ctrl +R 了,你可以使用加强版的 **hstr** (https://github.com/dvorka/hstr) 。 120 | 121 | - **htop** (https://hisham.hm/htop/) 是 top 的一个加强版。然而,还有很多的各式各样的top,比如:用于看IO负载的 **iotop** (http://guichaz.free.fr/iotop/),网络负载的 **iftop** (http://www.ex-parrot.com/~pdw/iftop/), 以及把这些top都集成在一起的 **atop** (https://github.com/Atoptool/atop)。另外 **nvtop** 可以查看 Nvidia GPU 的情况,适合做深度学习的同学。 122 | 123 | - **ncdu** (https://dev.yorhel.nl/ncdu) 比 du 好用多了用。另一个选择是 nnn(https://github.com/jarun/nnn) 。 124 | 125 | - 如果你想把你的命令行操作建录制成一个 SVG 动图,那么你可以尝试使用 **asciinema** (https://asciinema.org/) 和 **svg-trem** (https://github.com/marionebl/svg-term-cli) 。 126 | 127 | - **httpie**(https://github.com/jakubroztocil/httpie) 是一个可以用来替代 curl 和 wget 的 http 客户端,httpie 支持 json 和语法高亮,可以使用简单的语法进行 http 访问: `http -v github.com`。 128 | 129 | - **tmux** (https://github.com/tmux/tmux) 在需要经常登录远程服务器工作的时候会很有用,可以保持远程登录的会话,还可以在一个窗口中查看多个 shell 的状态。 130 | 131 | - **Taskbook**(https://github.com/klaussinani/taskbook) 是可以完全在命令行中使用的任务管理器 ,支持 ToDo 管理,还可以为每个任务加上优先级。 132 | 133 | - **sshrc** (sshrc:https://github.com/Russell91/sshrc ) 是个神器,在你登录远程服务器的时候也能使用本机的 shell 的 rc 文件中的配置。 134 | 135 | - **goaccess** (https://github.com/allinurl/goaccess) 这个是一个轻量级的分析统计日志文件的工具,主要是分析各种各样的 access log。 136 | 137 | 138 | 139 | 关于这些增加命令,参考自下面的这些文章 140 | 141 | 1. https://dev.to/_darrenburns/10-tools-to-power-up-your-command-line-4id4 142 | 2. https://dev.to/_darrenburns/tools-to-power-up-your-command-line-part-2-2737 143 | 3. https://dev.to/_darrenburns/power-up-your-command-line-part-3-4o53 144 | 4. https://darrenburns.net/posts/tools/ 145 | 5. https://hacker-tools.github.io/ 146 | 147 | 148 | 149 | ### Shell 和脚本篇 150 | 151 | shell 是可以与计算机进行高效交互的文本接口。shell 提供了一套交互式的编程语言(脚本),shell的种类很多,比如 **sh**、**bash**、**zsh** 等。 152 | 153 | shell 的生命力很强,在各种高级编程语言大行其道的今天,很多的任务依然离不开 shell。比如可以使用 shell 来执行一些编译任务,或者做一些批处理任务,初始化数据、打包程序等等。 154 | 155 | 写一个脚本很简单,**touch zsh-script.sh**: 156 | 157 | ```shell 158 | #!/bin/zsh 159 | echo Hello shell 160 | ``` 161 | 162 | 一个脚本就写完了,**#!/bin/zsh** 表示使用的是哪种 shell。写完之后需要给脚本加上执行的权限: 163 | 164 | ```shell 165 | # 给脚本执行的权限 166 | $ chmod +x zsh-script.sh 167 | # 执行脚本 168 | $ ./zsh-script.sh 169 | ``` 170 | 171 | 脚本的语法很简单,而且可以在脚本中使用命令行的所有命令。还有很多其他有意思的玩法,比如在后台运行: 172 | 173 | ```shell 174 | $ ./zsh-script.sh & 175 | ``` 176 | 177 | 还有可以定时执行,在 **crontab** 加入: 178 | 179 | ```shell 180 | * * * * * /home/ray/zsh-script.zsh 181 | ``` 182 | 183 | crontab 是 Linux 中的一个定时器,可以定制执行任务,上面的表示每分钟执行一次脚本。 184 | 185 | 我最喜欢的就是 **zsh** + **[oh-my-zsh](https://ohmyz.sh/)** + **[zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions)** 的组合,你也可以试试看。其中 zsh 和 oh-my-zsh 算是常规操作了,但是 zsh-autosuggestions 特别有用,可以超级快速的帮你补全你输入过的命令,让命令行的操作更加高效。 只需要简单的安装配置,你的命令行马上变得高档大气上档次,狂拽炫酷吊炸天。 186 | 187 | 另外,**[fish](https://fishshell.com/)** 也是另外一个牛逼的shell,比如:命令行自动完成(根据历史记录),命令行命令高亮,当你要输入命令行参数的时候,自动提示有哪些参数…… fish在很多地方也是用起来很爽的。和上面的 oh-my-zsh 有点不分伯仲了。 188 | 189 | 你也许会说,用 Python 脚本或 PHP 来写脚本会比 Shell 更好更没有 bug,但我要申辩一下: 190 | 191 | - 其一,如果你有一天要维护线上机器的时候,或是到了银行用户的系统(与外网完全隔离,而且服务器上没有安装 Python/PHP 或是他们的的高级库,那么,你只有 Shell 可以用了)。 192 | 193 | - 其二,而且,如果要跟命令行交互很多的话,Shell 是不二之选,试想一下,如果你要去 100 台远程的机器上查access.log 日志中有没有某个错误,完成这个工作你是用 PHP/Python 写脚本快还是用 Shell 写脚本快呢? 194 | 195 | 要写好一个脚本并不容易,下面有一些小模板供你参考: 196 | 197 | 处理命令行参数的一个样例 198 | 199 | ``` 200 | while [ "$1" != "" ]; do 201 | case $1 in 202 | -s ) shift 203 | SERVER=$1 ;; 204 | -d ) shift 205 | DATE=$1 ;; 206 | --paramter|p ) shift 207 | PARAMETER=$1;; 208 | -h|help ) usage # function call 209 | exit ;; 210 | * ) usage # All other parameters 211 | exit 1 212 | esac 213 | shift 214 | done 215 | ``` 216 | 217 | 命令行菜单的一个样例 218 | 219 | ``` 220 | 221 | #!/bin/bash 222 | # Bash Menu Script Example 223 | 224 | PS3='Please enter your choice: ' 225 | options=("Option 1" "Option 2" "Option 3" "Quit") 226 | select opt in "${options[@]}" 227 | do 228 | case $opt in 229 | "Option 1") 230 | echo "you chose choice 1" 231 | ;; 232 | "Option 2") 233 | echo "you chose choice 2" 234 | ;; 235 | "Option 3") 236 | echo "you chose choice $REPLY which is $opt" 237 | ;; 238 | "Quit") 239 | break 240 | ;; 241 | *) echo "invalid option $REPLY";; 242 | esac 243 | done 244 | ``` 245 | 246 | 颜色定义,你可以使用 `echo -e "${Blu}blue ${Red}red ${RCol}etc...."` 进行有颜色文本的输出 247 | 248 | ``` 249 | RCol='\e[0m' # Text Reset 250 | 251 | # Regular Bold Underline High Intensity BoldHigh Intens Background High Intensity Backgrounds 252 | Bla='\e[0;30m'; BBla='\e[1;30m'; UBla='\e[4;30m'; IBla='\e[0;90m'; BIBla='\e[1;90m'; On_Bla='\e[40m'; On_IBla='\e[0;100m'; 253 | Red='\e[0;31m'; BRed='\e[1;31m'; URed='\e[4;31m'; IRed='\e[0;91m'; BIRed='\e[1;91m'; On_Red='\e[41m'; On_IRed='\e[0;101m'; 254 | Gre='\e[0;32m'; BGre='\e[1;32m'; UGre='\e[4;32m'; IGre='\e[0;92m'; BIGre='\e[1;92m'; On_Gre='\e[42m'; On_IGre='\e[0;102m'; 255 | Yel='\e[0;33m'; BYel='\e[1;33m'; UYel='\e[4;33m'; IYel='\e[0;93m'; BIYel='\e[1;93m'; On_Yel='\e[43m'; On_IYel='\e[0;103m'; 256 | Blu='\e[0;34m'; BBlu='\e[1;34m'; UBlu='\e[4;34m'; IBlu='\e[0;94m'; BIBlu='\e[1;94m'; On_Blu='\e[44m'; On_IBlu='\e[0;104m'; 257 | Pur='\e[0;35m'; BPur='\e[1;35m'; UPur='\e[4;35m'; IPur='\e[0;95m'; BIPur='\e[1;95m'; On_Pur='\e[45m'; On_IPur='\e[0;105m'; 258 | Cya='\e[0;36m'; BCya='\e[1;36m'; UCya='\e[4;36m'; ICya='\e[0;96m'; BICya='\e[1;96m'; On_Cya='\e[46m'; On_ICya='\e[0;106m'; 259 | Whi='\e[0;37m'; BWhi='\e[1;37m'; UWhi='\e[4;37m'; IWhi='\e[0;97m'; BIWhi='\e[1;97m'; On_Whi='\e[47m'; On_IWhi='\e[0;107m'; 260 | ``` 261 | 262 | 取当前运行脚本绝对路径的示例:(注:Linux下可以用 `dirname $(readlink -f $0)` ) 263 | 264 | ``` 265 | FILE="$0" 266 | while [[ -h ${FILE} ]]; do 267 | FILE="`readlink "${FILE}"`" 268 | done 269 | pushd "`dirname "${FILE}"`" > /dev/null 270 | DIR=`pwd -P` 271 | popd > /dev/null 272 | ``` 273 | 274 | 如何在远程服务器运行一个本地脚本 275 | 276 | ``` 277 | #无参数 278 | ssh user@server 'bash -s' < local.script.sh 279 | 280 | #有参数 281 | ssh user@server ARG1="arg1" ARG2="arg2" 'bash -s' < local_script.sh 282 | ``` 283 | 284 | 如何检查一个命令是否存在,用 `which` 吗?最好不要用,因为很多操作系统的 `which` 命令没有设置退出状态码,这样你不知道是否是有那个命令。所以,你应该使用下面的方式。 285 | 286 | ``` 287 | # POSIX 兼容: 288 | command -v 289 | 290 | 291 | # bash 环境: 292 | hash 293 | type 294 | 295 | # 示例: 296 | gnudate() { 297 | if hash gdate 2>/dev/null; then 298 | gdate "$@" 299 | else 300 | date "$@" 301 | fi 302 | } 303 | ``` 304 | 然后,如果要写出健壮性更好的脚本,下面是一些相关的技巧: 305 | 306 | 307 | - 使用 `-e` 参数,如:`set -e` 或是 `#!/bin/sh -e`,这样设置会让你的脚本出错就会停止运行,这样一来可以防止你的脚本在出错的情况下还在拼拿地干活停不下来。 308 | - 使用 `-u` 参数,如: `set -eu`,这意味着,如果你代码中有变量没有定义,就会退出。 309 | - 对一些变理,你可以使用默认值。如:`${FOO:-'default'}` 310 | - 处理你代码的退出码。这样方便你的脚本跟别的命令行或脚本集成。 311 | - 尽量不要使用 `;` 来执行多个命令,而是使用 `&&`,这样会在出错的时候停止运行后续的命令。 312 | - 对于一些字符串变量,使用引号括起,避免其中有空格或是别的什么诡异字符。 313 | - 如果你的脚有参数,你需要检查脚本运行是否带了你想要的参数,或是,你的脚本可以在没有参数的情况下安全的运行。 314 | - 为你的脚本设置 `-h` 和 `--help` 来显示帮助信息。千万不要把这两个参数用做为的功能。 315 | - 使用 `$()` 而不是 `` 来获得命令行的输出,主要原因是易读。 316 | - 小心不同的平台,尤其是 MacOS 和 Linux 的跨平台。 317 | - 对于 `rm -rf` 这样的高危操作,需要检查后面的变量名是否为空,比如:`rm -rf $MYDIDR/*` 如果 `$MYDIR`为空,结果是灾难性的。 318 | - 考虑使用 "find/while" 而不是 “for/find”。如:`for F in $(find . -type f) ; do echo $F; done` 写成 `find . -type f | while read F ; do echo $F ; done` 不但可以容忍空格,而且还更快。 319 | - 防御式编程,在正式执行命令前,把相关的东西都检查好,比如,文件目录有没有存在。 320 | 321 | 你还可以使用ShellCheck 来帮助你检查你的脚本,让其有更好的可维护性。 322 | 323 | - https://www.shellcheck.net/ 324 | 325 | 最后推荐一些 Shell 和脚本的参考资料。 326 | 327 | 各种有意思的命令拼装,一行命令走天涯: 328 | 329 | - http://www.bashoneliners.com/ 330 | - http://www.shell-fu.org/ 331 | - http://www.commandlinefu.com/ 332 | 333 | 下面是一些脚本集中营,你可以在里面淘到各种牛X的脚本: 334 | 335 | - http://www.shelldorado.com/scripts/ 336 | - https://snippets.siftie.com/public/tag/bash/ 337 | - https://bash.cyberciti.biz/ 338 | - https://github.com/alexanderepstein/Bash-Snippets 339 | - https://github.com/miguelgfierro/scripts 340 | - https://github.com/epety/100-shell-script-examples 341 | - https://github.com/ruanyf/simple-bash-scripts 342 | 343 | 344 | 甚至写脚本都可以使用框架: 345 | 346 | - 写bash脚本的框架 https://github.com/Bash-it/bash-it 347 | 348 | Google的Shell脚本的代码规范: 349 | 350 | - https://google.github.io/styleguide/shell.xml 351 | 352 | 最后,别忘了几个和shell有关的索引资源: 353 | 354 | - https://github.com/alebcay/awesome-shell 355 | - https://github.com/awesome-lists/awesome-bash 356 | - https://terminalsare.sexy/ 357 | 358 | 359 | ### 版本管理篇 360 | 361 | 版本管理的工具对我来说已经不仅仅不是管理代码的工具了。任何需要不断优化,不断修改的内容都需要进行版本管理。 362 | 363 | 版本管理的工具很多。现在还有好些人还不喜欢 Git 还在用 svn,那是因为他们并不知道 Git 的强大之处,这种脱机的版本管理可以让你在没有网的情况下提效代码变更,再加上 Git 切换 branch 快得不行,merge branch 时会把 branch 的改动情况一同 merge 了,这样可以让你看到整个历史。再有 stash,cherry-pick 等等这样的黑魔法加持,你的工作效率真的很爽的。如今最好用的应该就是 **Git** 了,在加上最近 **GitHub** 私有仓库的开放,让这一优势继续扩大。 364 | 365 | Git 这么好用的原因来源于其底层数据结构的设计,非常的有意思,如果你接触过区块链,你会发现 Git 底层的数据结构与区块链的数据结构有异曲同工之处。 366 | 367 | Git 除了可以完成通常的版本管理之外,它还拥有一些很神奇的技能: 368 | 369 | - 帮你找 bug 的命令: **git bisect**,通过二分搜索的方式来帮助你定位到引入 bug 的 commit。 370 | - 可以帮助你洞察一切的 **git blame** 可以给你文件的每行信息都进行注释,然后就可以看到关于该行修改的每一次 commit 的哈希标签、作者和提交日期。 371 | - 可以帮你恢复一切的 **git reflog**,通常我们 **git reset** 命令都是慎用的,要不然就坏事了,但是 git relog 在你将变化提交之前,可以帮助你回到任何修改之前,包括 git reset。但是 reflog 只是保存在本地,而且不是永久保存,有一个可以配置的过期时间。 372 | 373 | 除此之外,还有一些 Git 的小技巧: 374 | 375 | - 如果你当前修改的文档在你本地的master上,然后你想把这个修改迁移到一个branch上,这样方便你做PR,如果这些修改还没有被add,那么你可以使用 `git checkout -b new_branch_name` 来完成。 376 | - 如果你commit后,还没有push,你想修改一下这前的commit,你可以使用 `git commit --amend` 377 | - 使用 `git pull --rebase` 可以避免出现一个 merge buble。 378 | - 如果你要删除远程分支,可以使用 `git push origin --delete ` 379 | - 使用 `git add -p` 可以让你挑选修改,这样你可以把你的一次大改动变成多个提交。 380 | - `git checkout -p` 和 `git add -p` 类似,如果让你挑选你想checkout的修改。 381 | - 你知道你可以使用时间在你的git命令行上吗?比如:`git diff HEAD@{'2 months ago'}` 或 `git diff HEAD@{yesterday}` 或 `git diff HEAD@{'2010-01-01 12:00:00'}` 382 | 383 | 有时候,设置一下 Git 的别名也是可以让你更高效工作的一件事,比如: 384 | 385 | 使用如下的 `git lg` 别名 386 | 387 | ``` 388 | git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --" 389 | ``` 390 | 391 | 就可以把原来的 `git log` 的输出 392 | 393 | ![](../../Desktop/git.log_.01.png) 394 | 395 | 变成下面这个样子 396 | 397 | ![](../../Desktop/git.log_.02.png) 398 | 399 | 除此之外,Git 还有好些比较有用的别名,下面这几个收集库,可以让你更高效更赏心悦目地使用 Git 400 | 401 | - https://github.com/momeni/gittify 402 | - https://github.com/GitAlias/gitalias 403 | - https://gist.github.com/mwhite/6887990 404 | 405 | Git 有多种用法,可以使用原生的 Git 命令行,原生的 Git 命令加上 shell 脚本的包装就可以做到很高效了。 Git 也有图形化的界面,比如 Git 安装包自带的 gitk 或者 TortoiseGit,因为比起命令行来太慢了。还有就是 magit,这是 Emacs 的一个插件,这个插件将所有 Git 的操作都融入到 Emacs 中了,只需要使用使用快捷键就能够完成 Git 的所有操作,但是同时又带有一点图形化的感觉,这么说有点苍白无力,看图: 406 | 407 | ![](http://rayjun.oss-cn-beijing.aliyuncs.com/emacs/emacs-git.jpg) 408 | 409 | 我现在日常会使用 Git 来进行代码管理、博客文章的管理和自己的知识库的管理。这些内容使用版本管理起来的好处是可以看见自己的成长过程,每一次修改的内容,每一个想法的进化。 410 | 411 | 下面推荐一下 Git 的学习资料: 412 | 413 | - Progit2,最好的深入学习 Git 的教材,而且是开源的https://github.com/progit/progit2 414 | - Magit,Git 在 Emacs 上的打开方式:https://magit.vc/ 415 | - Vim-fugitive,Git 在 Vim 上的打开方式:https://github.com/tpope/vim-fugitive 416 | - Git 相关的 shell 提示: https://github.com/magicmonty/bash-git-prompt 417 | 418 | Happy hacking! 419 | 420 | (完) 421 | 422 | --------------------------------------------------------------------------------