内存, mem

内存, mem 内存对于缓存更新策略,要区分Write-Through和Write-Back两种策略。前者更新内容直接写内存并不同时更新Cache,但要置Cache失效,后者先更新Cache,随后异步更新内存。通常X86 CPU更新内存都使用Write-Back策略。 虚拟内存 虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存 (一个连续完整的地址空间) ,而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大型程序的编写变得更容易,对真正的物理内存 (例如RAM) 的使用也更有效率。 注意: 虚拟内存不只是「用磁盘空间来扩展物理内存」的意思——这只是扩充内存级别以使其包含硬盘驱动器而已。把内存扩展到磁盘只是使用虚拟内存技术的一个结果,它的作用也可以通过覆盖或者把处于不活动状态的程序以及它们的数据全部交换到磁盘上等方式来实现。对虚拟内存的定义是基于对地址空间的重定义的,即把地址空间定义为「连续的虚拟内存地址」,以借此「欺骗」程序,使它们以为自己正在使用一大块的「连续」地址。 为什么要使用虚拟内存 a. 产生的背景 (摘自书籍Rechnerarchitektur : Von der digitalen Logik zum Parallelrechner 作者Andrew S. Tanenbaum )早期的计算机内存容量非常小,通常只能存储几千个Wörter 1(即数据单元),但价格却是十分昂贵。(之后的Beispiele是指一些关于早期计算机的具体例子) 所造成的不便: a. 使用这些容量很小的内存,导致了程序运行的速度很慢,原因是在于存放相对高效的算法需要占用的内存空间相对较大,所以程序员们只能使用执行效率较低的,但是占用内存容量较小的算法。à对于这个问题的传统解决方案是,(物理上,或者说在硬件上)新增加一个(或多个)内存空间(例如,硬盘- Plattenspeicher)。b. 而同时还造成的一个不便之处,是在这种内存容量很小的情况下,程序员需要人为地将程序分解成为多个部分(Overlays), 然后分开来存储进内存中。在执行程序时,就首先执行第1个Overlay, 执行完毕后,继而调用第2个Overlay,以此类推。à进展:尽管这个方法在那个时代已经很广泛地普及化了,但是由于使用Overlay进行管理执行程序的办法需要大量的人力投入,这其中的成本仍是不容小觑的。于是在1961年,一位来自英国曼彻斯特的研究人员提议,不如将程序通过Overlays执行这个方法设计成自动化的模式,这样就无需程序员费劳管理程序的划分与安置的任务。这个新的方法所使用到的一个新的概念即是虚拟内存virtueller Speicher。b.使用虚拟内存的优势: (摘自https://blog.csdn.net/vernice/article/details/41783601) 1 程序员无需操心如何存储(数据,程序等等的)内容。2 通过分布在同一个物理内存空间的两个不同的区间,虚拟内存里的数据和程序可以彼此共享使用3 它使得应用程序在系统中可以更自如地在物理地址中被执行。虚拟内存的操作过程是怎么样的 假如说这台机器不适用虚拟内存这个概念,那么程序就会报错,例如"该指定的地址范围在内存中不存在",继而程序终止执行。而反过来若是这台机器是适用虚拟内存的,那么这个任务会按照如下步骤来执行: 1 寻找位于存储盘上,介于地址8192和12287之间的数据单元。2 将这段原来地址为8192~12287的数据单元加载到内存中。3 而这段数据对应到内存中的地址将被定义为0~4095。4 而程序将继续正常地执行下去。 人们将这项自动划分区间(Overlays)的技术命名为Paging (deuts. Seitenauslagerung),而这一段段从硬盘中读出来的程序区间即被命名为Page (deuts. Seiten)。 虚拟地址通过对虚拟地址空间到物理地址空间的一个动态映射来实现。而虚拟的地址空间也会相应地划分成Pages, 然后整个虚拟地址空间就相当于一张划分地址空间的表格,与实际的物理内存地址相互对应。 (只是这个图像的内存中没有包含多个4KB的内存空间,而是指设置了一个,所以这个具例中,我们只能一次对应一个4KB的空间。) 而这个对应的关系实际上也可以更为的复杂,这是完全可以做到的。我们一方面将存储程序的地址称为虚拟地址空间Adressraum (Virtual Address Space),另一方面将真正存在的,有线路控制的内存地址称作物理地址空间(Physical Address Space)。依据对应的物理地址空间规划虚拟地址空间上的内容区划。这里我们假设首先是有足够的空间来装得下虚拟的地址空间的。这样人们就会获得一种假象,这种假象会让人感到好像有十分充足的(物理)内存空间一样,但实际上这些都是因为虚拟内存这个概念的应用,而现实中物理内存的实际容量远远少于人们所需要的。 有了这个优势,程序就可以任意地将虚拟地址空间里的数据加载出来,或者将新的数据存储到虚拟的内存空间中去,而人们也完全无需去顾虑是否物理内存的容量是否够用。 (有的)程序员甚至可以完全不需要知道虚拟内存的存在。计算机于是看上去是有一个很大很大的内存的。 而实现虚拟内存的一个本质前提是要有一个可以存放整个程序和数据的硬盘。这一个硬盘可以是可以转动的那种硬盘,也可以是固态硬盘。在书中我们统一将"Platte"或者说"Festplatte" 理解为固态硬盘。(将存在硬盘里的程序作为原件,而将程序数据转运到内存中所要做的即是一个复制的功能)自然的,在执行操作后要保持对硬盘里的程序(即原件)的更新。当在内存中原数据的"副本"改变后,反过去就要对硬盘的程序进行改动。 内存管理单元,内存控制单元 (MMU) 什么是MMU MMU(Memory Management Unit)主要用来管理虚拟存储器、物理存储器的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权、多任务多进程操作系统。 (来自百度百科,对其几个点不熟悉,因此可以只考虑加粗部分) ...

2019-06-11 · 6 min · 1192 words · -

angular pwa

angular pwa ng new project0 ng add @angular/pwa –project project0 ng build –prod cd project0 && http-server -p 8080 -c-1 dist/project0/ curl http://127.0.0.1:8080/ngsw/state chrome://serviceworker-internals/ ng new my-app --routing

2019-06-09 · 1 min · 28 words · -

rock 5b

rock 5b yay -S rkdeveloptool # 按住板子上的 Maskrom mode 键, 把 usb type a to c 插头插到电脑上, 放开 maskrom mode 键 lsusb # result: Bus 001 Device 112: ID 2207:350b Fuzhou Rockchip Electronics Company sudo rkdeveloptool ld 默认用户名密码 rock/rock https://wiki.radxa.com/Rock5/install/usb-install-emmc

2019-06-09 · 1 min · 40 words · -

cut command

cut command echo abc | cut -c 2- # bc echo abcdef | cut -c 1-3 # abc

2019-06-09 · 1 min · 18 words · -

angular material

angular material yarn add @angular/material @angular/cdk @angular/animations app.module.ts import { MatSliderModule } from '@angular/material/slider'; import 'hammerjs'; … @NgModule ({.... imports: [..., MatSliderModule, …] }) app.component.html styles.css @import ‘@angular/material/prebuilt-themes/deeppurple-amber.css’; https://material.angular.io/ https://material.angular.cn/guides https://github.com/stbui/angular-material-app/tree/master/src/app https://material.io/ https://material.angular.io/components/categories

2019-06-08 · 1 min · 33 words · -

nginx 部署静态页面

nginx plain html 部署静态页面 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> hello </body> </html> server{ listen 80; server_name hello.wiloon.dev; root /var/www; index hello.html; }

2019-06-08 · 1 min · 25 words · -

chrome 启动参数

chrome 启动参数 chromium \ --user-data-dir=/path/to/cache/dir \ --incognito \ --no-first-run \ --process-per-site \ --app='https://baidu.com'

2019-06-06 · 1 min · 13 words · -

Intel Compute Stick

Intel Compute Stick Intel® Compute Stick STCK1A32WFC Intel Atom® Processor Z3735F (2M Cache, up to 1.83 GHz) Intel® Compute Stick STK1A32SC Intel Atom® x5-Z8300 Processor (2M Cache, up to 1.84 GHz) 2GB DDR3L-1600 Intel® Compute Stick STK1AW32SC Intel Atom® x5-Z8300 Processor (2M Cache, up to 1.84 GHz) Intel® Compute Stick STK2m364CC Intel® Core™ m3-6Y30 Processor (4M Cache, up to 2.20 GHz) 安装 bios https://www.intel.cn/content/www/cn/zh/support/articles/000006039/mini-pcs/intel-compute-sticks.html

2019-06-06 · 1 min · 64 words · -

call javascript in angular 7

call javascript in angular 7 https://stackoverflow.com/questions/53482324/how-can-i-use-javascript-code-in-angular-7

2019-06-02 · 1 min · 6 words · -

angular deploy to nginx

angular deploy to nginx ng build --aot “` server { listen 8081; server_name localhost; location / { root C:/website/angular/ng-prime/dist; // 这是angular生成的dist文件夹存放的位置 index index.html; try_files $uri $uri/ /index.html; // 注意此句,一定要加上。否则配置的子路由等无法使用 } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } " https://www.cnblogs.com/kingkangstudy/p/8085642.html

2019-06-02 · 1 min · 46 words · -

markdown > PPT

‘markdown > PPT’ https://github.com/ksky521/nodeppt https://github.com/hakimel/reveal.js/

2019-05-31 · 1 min · 5 words · -

Java诊断工具 – Arthas

Java诊断工具 – Arthas Alibaba 开源的Java诊断工具 https://alibaba.github.io/arthas/

2019-05-30 · 1 min · 6 words · -

apr、apr-util, apr-iconv

apr、apr-util, apr-iconv https://my.oschina.net/shawnplaying/blog/1518144 安装Apache的时候,为什么要安装apr和apr-util呢 要测APR给tomcat带来的好处最好的方法是在慢速网络上 (模拟Internet) ,将Tomcat线程数开到300以上的水平,然后模拟一大堆并发请求。如果不配APR,基本上300个线程狠快就会用满,以后的请求就只好等待。但是配上APR之后,并发的线程数量明显下降,从原来的300可能会马上下降到只有几十,新的请求会毫无阻塞的进来。 APR对于Tomcat最大的作用就是socket调度。 你在局域网环境测,就算是400个并发,也是一瞬间就处理/传输完毕,但是在真实的Internet环境下,页面处理时间只占0.1%都不到,绝大部分时间都用来页面传输。如果不用APR,一个线程同一时间只能处理一个用户,势必会造成阻塞。所以生产环境下用apr是非常必要的。 注: APR(Apache portable Run-time libraries,Apache可移植运行库)的目的如其名称一样,主要为上层的应用程序提供一个可以跨越多操作系统平台使用的底层支持接口库。 在早期的Apache版本中,应用程序本身必须能够处理各种具体操作系统平台的细节,并针对不同的平台调用不同的处理函数。随着Apache的进一步开发,Apache组织决定将这些通用的函数独立出来并发展成为一个新的项目。这样,APR的开发就从Apache中独立出来,Apache仅仅是使用APR而已。 一般情况下,APR开发包很容易理解为仅仅是一个开发包,不过事实上并不是。目前,完整的APR实际上包含了三个开发包: apr、apr-util以及apr-iconv,每一个开发包分别独立开发,并拥有自己的版本。

2019-05-30 · 1 min · 17 words · -

点数估算

点数估算 敏捷开发点数估算 为什么用点数比用小时和天数更好? 故事点数是通过对比以前开发过的大小相似的用户故事得到的。这种对比相对大小的估算方式,在有大量样本数据的情况下,比独立估算每个用户故事要准确得多。 举个例子,我们可以很容易的说出,从大连到长春的距离是从大连到沈阳的两倍,而不是大连到长春的距离是676.1千米, 大连到沈阳的距离是378.9千米。 (数据来自百度地图) 这样,团队不用花太多的时间来估算每个用户故事所要花费的准确时间和天数,就可以快速完成所有用户故事的估算。 不同的开发团队,是否可以使用统一的故事点数基准? 不同的开发团队,对于故事点数有不同的度量基准,取决于各个团队所要估计的用户故事。除非他们是在开发相同的系统,否则团队A开发1个点的工作量和团队B在不同系统中开发1个点的工作量是不同的。这种差异将会影响团队的迭代交付速率。 如果有一个很大的项目,需要分成多个小团队来共同开发,人们很可能想去尝试定义一种点数标准应用到所有小团队。这有悖于估算用户故事点数的目的,每个小团队都会有自己的主观衡量标准。 我们如何估算试探性研究(Spike)的用户故事? 为了弄明白如何实现一个特定的功能,或者验证某种概念,我们需要试探性研究故事 (Spike) 。由于很难知道到底总共需要多少工作量,通常我们要提前在团队中达成共识,对这种研究做出一定时间限制。这些用户故事可是通过观察交付速率趋势图,转换成大致的点数。 例如,如果需要计划一周的时间来完成一个试探性研究,而交付速率是16个点 (迭代周期为两周) ,那么就可以估算这个故事为8个点。 用户点数是否和业务价值有关? 用户故事点数是对实现用户故事所需要工作量的团队内部度量。无论如何,与用户故事所能提供多少业务价值没有关系。 很可能在同一个系统中,1个点数的用户故事会比4个点的故事有更大的业务价值。业务价值最好是留给产品经理和相关的业务决策者来衡量。 在介绍敏捷估算的方法之前,我们先来回顾一下基于人天的传统估算的思路。传统的工作量估算是估计一个绝对值,单位是人天或者人时。 比如: David喝完一小杯热咖啡花费1.2个小时 (工作量 1.2人时) David喝完一大杯热咖啡花费2.4个小时 (工作量 2.4人时) 由于人的能力是有差异的,所以David的工作量对于Tom来讲可能就不适用,Tom喝完一小杯热咖啡可能需要1.5小时。这样一来,工作量、参与人以及完成这些工作的时间周期就是强相关的,因为强相关会带来如下挑战: 做计划时必须把人和周期关联到具体的任务上,会让计划很复杂。 团队成员的分工发生变化时对计划的影响比较大,管理和维护计划成本高。 (这是甘特图的价值所在 ) 由于第二条的原因,这种工作量的估算方式不利于团队协作。 接下来,我们来看看敏捷估算的思路。 在探讨具体的思路之前,我们先思考一下做估算的目的什么,通常有两个目的: 核算成本和周期,我们要了解这这个项目或产品的投资回报。 做计划,根据项目的需要,我们要知道什么时间点应该交付什么内容才可以满足市场、用户或客户的期望。 做敏捷估算时,请先忘掉人天或人时,敏捷估算关注的是工作量的规模 (大小) ,而不关心谁来做,不关心花多长时间做完。它的规模计量单位使用的是一个抽象的单位——故事点,故事点是一个相对值,是一个相对倍数,和人天,人时没有关系,它和公里、吨、摄氏度类似,只是一个计量单位而已。我们可以定义喝一小杯热咖啡花费的工作量为参考基准,是 1 个故事点。中杯看起来是小杯的2倍大,所以我们可以估算喝一中杯热咖啡花费的工作量是小杯的两倍, 是 2个故事点,大杯是小杯的三倍,所以工作量是3个故事点。 敏捷估算的步骤: 找一个参考基准,作为一个故事点。比如: 把开发一个简单的查询页面工作量作为基准,定义为一个故事点。 拿其它的故事和基准进行比较,估算他们之间的倍数,从而得到其它故事的故事点数。比如: 查看个人基本信息这个故事和开发一个简单的查询页面的规模差不多大,所以它也是1个点,录入个人基本资料的这个故事要复杂一些,大概时3个点。 3 . 累计产品backlog中的所有故事,得到所有故事总的故事点数。 得到了总的故事点规模,我们还要知道团队速度。团队速度是指: 1个敏捷团队在一个迭代中完成的故事点总数。比如: 某Scrum团队1个迭代可以完成 80个故事点,那么80个点就是他们的速度。 有了总的规模,我们也知道了团队一个迭代的速度,我们就可以很容易推算多少个迭代可以做完。 如下图所示,总的规模是1600个点,团队总共8个人,每个迭代完成80个点,我们就可以推算20个迭代完成。 每个迭代2周,所以40周可以完成,8个人40周的成本投入也可以很容易得出。 敏捷估算要点小结: 相对估算,使用故事点作为单位,故事点是一个相对倍数。 估算规模,规模的计量单位是故事点,规模和时间、周期无关,和人天,人时无关。 敏捷估算关注团队的速度,不关注单个人的速度。 通过总规模和团队速度,推算周期 http://www.yugusoft.com/article/%E6%95%8F%E6%8D%B7%E5%BC%80%E5%8F%91%E7%82%B9%E6%95%B0%E4%BC%B0%E7%AE%97.htm?f=blog_page https://cloud.tencent.com/developer/article/1104793

2019-05-28 · 1 min · 68 words · -

golang gin

golang gin 重定向 https://www.cnblogs.com/zisefeizhu/p/12739223.html package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { r := gin.Default() //http重定向 r.GET("/index", func(c *gin.Context) { //c.JSON(http.StatusOK, gin.H{ // "status": "ok", //}) //跳转到sogo c.Redirect(http.StatusMovedPermanently, "https://www.sogo.com") }) //路由重定向 r.GET("/luyou", func(c *gin.Context) { //跳转到/luyou2对应的路由处理函数 c.Request.URL.Path = "/luyou2" //把请求的URL修改 r.HandleContext(c) //继续后续处理 }) r.GET("/luyou2", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message":"路由重定向", }) }) r.Run(":9090") }

2019-05-25 · 1 min · 56 words · -

gin

gin https://github.com/gin-gonic/gin#quick-start package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { router := gin.Default() router.GET("/path0", func(c *gin.Context) { firstname := c.DefaultQuery("params0", "Guest") lastname := c.Query("params1") // shortcut for c.Request.URL.Query().Get("lastname") c.String(http.StatusOK, "Hello %s %s", firstname, lastname) }) router.Run(":8080") }

2019-05-23 · 1 min · 39 words · -

kafka consumer

kafka consumer 按照 Kafka 默认的消费逻辑设定,一个分区只能被同一个消费组(ConsumerGroup)内的一个消费者消费。 即同一个partition内的消息只能被同一个组中的一个consumer消费。当消费者数量多于partition的数量时,多余的消费者空闲 assignment, 分配策略 Kafka提供了消费者客户端参数partition.assignment.strategy用来设置消费者与订阅主题之间的分区分配策略。 默认情况下,此参数的值为:org.apache.kafka.clients.consumer.RangeAssignor,即采用RangeAssignor分配策略。除此之外,Kafka中还提供了另外两种分配策略: RoundRobinAssignor和StickyAssignor。消费者客户端参数partition.asssignment.strategy可以配置多个分配策略,彼此之间以逗号分隔。 RangeAssignor RangeAssignor 策略的原理是按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进行平均分配,以保证分区尽可能均匀地分配给所有的消费者。对于每一个topic,RangeAssignor策略会将消费组内所有订阅这个topic的消费者按照名称的字典序排序,然后为每个消费者划分固定的分区范围,如果不够平均分配,那么字典序靠前的消费者会被多分配一个分区。 range assignor 在某些情况下会分配不均匀, 有可能会出现部分消费者过载的情况. RoundRobinAssignor 把 topic 和 consumer 排序, 依次给 topic 分配 consumer, 同一个 topic 中的不同 partition 分被均匀分配给不同的 consumer. RoundRobinAssignor 策略的原理是将消费组内所有消费者以及消费者所订阅的所有topic的partition按照字典序排序,然后通过轮询消费者方式逐个将分区分配给每个消费者。RoundRobinAssignor策略对应的partition.assignment.strategy参数值为:org.apache.kafka.clients.consumer.RoundRobinAssignor。 如果同一个消费组内所有的消费者的订阅信息都是相同的,那么RoundRobinAssignor策略的分区分配会是均匀的。 如果同一个消费组内的消费者所订阅的Topic 是不相同的,那么在执行分区分配的时候就不是完全的轮询分配,有可能会导致分区分配的不均匀。如果某个消费者没有订阅消费组内的某个topic,那么在分配分区的时候此消费者将分配不到这个topic的任何分区。 StickyAssignor “sticky”这个单词可以翻译为“粘性的”,Kafka从0.11.x版本开始引入这种分配策略,它主要有两个目的: ① 分区的分配要尽可能的均匀; ② 分区的分配尽可能的与上次分配的保持相同。 当两者发生冲突时,第一个目标优先于第二个目标。鉴于这两个目标,StickyAssignor策略的具体实现要比RangeAssignor和RoundRobinAssignor这两种分配策略要复杂很多。 从结果上看StickyAssignor策略比另外两者分配策略而言显得更加的优异,这个策略的代码实现也是异常复杂,如果大家在一个 group 里面,不同的 Consumer 订阅不同的 topic, 那么设置Sticky 分配策略还是很有必要的。 properties.put("enable.auto.commit", "true"); properties.put("auto.commit.interval.ms", "1000"); properties.put("auto.offset.reset", "latest"); properties.put("session.timeout.ms", "30000"); properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); properties.put("value.deserializer", "org.apache.kafka.common.serialization.ByteArrayDeserializer"); Consumer Group 主要用于实现高伸缩性,高容错性的 Consumer 机制。因此,消息的接收是基于 Consumer Group 的。组内多个 Consumer 实例可以同时读取 Kafka 消息,同一时刻一条消息只能被一个消费者消费,而且一旦某一个 consumer “挂了”, Consumer Group 会立即将已经崩溃的 Consumer 负责的分区转交给其他 Consumer 来负责。从而保证 Consumer Group 能够正常工作。 ...

2019-05-21 · 3 min · 557 words · -

css scss sass less stylus

css scss sass less stylus Sass和Less语法严谨、Stylus相对自由。因为Less长得更像 css,所以它可能学习起来更容易。 Sass 和 Compass、Stylus 和 Nib 都是好基友。 Sass 和 Stylus 都具有类语言的逻辑方式处理: 条件、循环等,而 Less 需要通过When等关键词模拟这些功能,这方面 Less 比不上 Sass 和 Stylus。 Less 在丰富性以及特色上都不及 Sass 和 Stylus Stylus,它的语法自由度很高,而且写出来的代码非常简洁. https://zhuanlan.zhihu.com/p/23382462

2019-05-19 · 1 min · 32 words · -

vscode, code-server

vscode, code-server code server 是 coder 公司基于微软开源的 Visual Studio Code 开发的一款产品。 code server 的目标是为开发者构建一个便捷统一的开发环境,让开发者能从任意设备、任意位置通过浏览器来进行代码的编写。从而免去了常规的 IDE 开发流程中的环境搭建的问题。 code server 有哪些优点? 环境统一 code server 解决的第一个问题就是跨设备的环境一致性。因为 code server 始终运行在一个远程的云端环境,因此他的开发环境始终是一致的,不会出现不同平台或者不同设备运行相同的代码而出现各种问题的情况。 我相信有不少程序员遇到过类似的问题。比如,同样的代码在 MacOS 上运行正常,在 Windows 上运行报错;或者在同事 A 的电脑上运行正常,而在同事 B 的电脑上运行报错。 而 code server 解决了这个问题,对于同一个项目的代码开发,不管是谁,运行代码的环境都是 code server 所在的服务器环境,这有效的避免的环境不同带来的问题,让程序员把精力更多地放在代码编写上,而不是去解决各种平台切换带来的问题上。 podman run -d --name code-server \ -p 8080:8080 \ -v "code-server-config:/root/.config" \ -v "code-server-project:/home/coder/project" \ -v "code-server-ssh:/root/.ssh" \ -v "code-server-data:/data" \ -u "$(id -u):$(id -g)" \ -e "DOCKER_USER=root" \ --memory=2g \ --cpus=1 \ codercom/code-server:3.12.0 --auth none nginx 配置 location / { proxy_pass http://wyse5070.wiloon.com:8080; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Accept-Encoding gzip; } ...

2019-05-19 · 1 min · 107 words · -

nginx gzip

nginx gzip https://juejin.im/post/5b518d1a6fb9a04fe548e8fc

2019-05-16 · 1 min · 3 words · -