-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
150 lines (150 loc) · 71.6 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[排序算法-非比较排序]]></title>
<url>%2F2018%2F09%2F03%2F%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95-%E9%9D%9E%E6%AF%94%E8%BE%83%E6%8E%92%E5%BA%8F%2F</url>
<content type="text"><![CDATA[计数排序桶排序基数排序]]></content>
<tags>
<tag>标签1</tag>
<tag>标签2</tag>
</tags>
</entry>
<entry>
<title><![CDATA[跳跃表]]></title>
<url>%2F2018%2F08%2F30%2F%E8%B7%B3%E8%B7%83%E8%A1%A8%2F</url>
<content type="text"><![CDATA[]]></content>
<tags>
<tag>标签1</tag>
<tag>标签2</tag>
</tags>
</entry>
<entry>
<title><![CDATA[B树与B+树]]></title>
<url>%2F2018%2F08%2F30%2FB%E6%A0%91%E4%B8%8EB%2B%E6%A0%91%2F</url>
<content type="text"><![CDATA[在计算机科学中,B树(英语:B-tree)是一种自平衡的树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成。B树,概括来说是一个一般化的二叉查找树(binary search tree),可以拥有多于2个子节点。与自平衡二叉查找树不同,B树为系统大块数据的读写操作做了优化。B树减少定位记录时所经历的中间过程,从而加快存取速度。B树这种数据结构可以用来描述外部存储。这种数据结构常被应用在数据库和文件系统的实现上。(wikipedia) B树一棵m阶的B-Tree,或者为空树,或者满足下列特性: 树中每个结点至多有m棵子树; 若根结点不是叶子结点,则至少有两棵子树; 除根节点之外的所有非终端结点至少有[m/2]棵子树; 所有非终端结点中包含下列信息数据:(n,A0,K1,A1,K2,A2……Kn,An)其中,n为关键字的数目,K(i)为关键字,且K(i) < K(i+1), Ai为指向子树根结点的指针,且指针A(i-1)所指子树中所有结点的关键字均小于Ki,Ai所指子树中所有结点的关键字均大于Ki; 所有叶子结点都出现在同一层次上;下图是一个M=4 阶的B树:可以看到B树是2-3树的一种扩展,他允许一个节点有多于2个的元素。B树的插入及平衡化操作和2-3树很相似,这里就不介绍了。下面是往B树中依次插入6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4的演示动画: B+树B+树是对B树的一种变形树,它与B树的差异在于: 有k个子结点的结点必然有k个关键码; 非叶结点仅具有索引作用,跟记录有关的信息均存放在叶结点中。 树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录。如下图,是一个B+树:下图是B+树的插入动画: B和B+树的区别在于,B+树的非叶子结点只包含导航信息,不包含实际的值,所有的叶子结点和相连的节点使用链表相连,便于区间查找和遍历。B+ 树的优点在于: 由于B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的key。 数据存放的更加紧密,具有更好的空间局部性。因此访问叶子节点上关联的数据也具有更好的缓存命中率。 B+树的叶子结点都是相链的,因此对整棵树的便利只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。但是B树也有优点,其优点在于,由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。下面是B 树和B+树的区别图:分析对B树和B+树的分析和对前面讲解的2-3树的分析类似, 对于一颗节点为N度为M的子树,查找和插入需要logM-1N ~ logM/2N次比较。这个很好证明,对于度为M的B树,每一个节点的子节点个数为M/2 到 M-1之间,所以树的高度在logM-1N至logM/2N之间。 这种效率是很高的,对于N=62*1000000000个节点,如果度为1024,则logM/2N <=4,即在620亿个元素中,如果这棵树的度为1024,则只需要小于4次即可定位到该节点,然后再采用二分查找即可找到要找的值。 应用B树和B+广泛应用于文件存储系统以及数据库系统中,在讲解应用之前,我们看一下常见的存储结构:我们计算机的主存基本都是随机访问存储器(Random-Access Memory,RAM),他分为两类:静态随机访问存储器(SRAM)和动态随机访问存储器(DRAM)。SRAM比DRAM快,但是也贵的多,一般作为CPU的高速缓存,DRAM通常作为内存。这类存储器他们的结构和存储原理比较复杂,基本是使用电信号来保存信息的,不存在机器操作,所以访问速度非常快,具体的访问原理可以查看CSAPP,另外,他们是易失的,即如果断电,保存DRAM和SRAM保存的信息就会丢失。 我们使用的更多的是使用磁盘,磁盘能够保存大量的数据,从GB一直到TB级,但是 他的读取速度比较慢,因为涉及到机器操作,读取速度为毫秒级,从DRAM读速度比从磁盘度快10万倍,从SRAM读速度比从磁盘读快100万倍。下面来看下磁盘的结构:如上图,磁盘由盘片构成,每个盘片有两面,又称为盘面(Surface),这些盘面覆盖有磁性材料。盘片中央有一个可以旋转的主轴(spindle),他使得盘片以固定的旋转速率旋转,通常是5400转每分钟(Revolution Per Minute,RPM)或者是7200RPM。磁盘包含一个多多个这样的盘片并封装在一个密封的容器内。上图左,展示了一个典型的磁盘表面结构。每个表面是由一组成为磁道(track)的同心圆组成的,每个磁道被划分为了一组扇区(sector).每个扇区包含相等数量的数据位,通常是(512)子节。扇区之间由一些间隔(gap)隔开,不存储数据。 以上是磁盘的物理结构,现在来看下磁盘的读写操作:如上图,磁盘用读/写头来读写存储在磁性表面的位,而读写头连接到一个传动臂的一端。通过沿着半径轴前后移动传动臂,驱动器可以将读写头定位到任何磁道上,这称之为寻道操作。一旦定位到磁道后,盘片转动,磁道上的每个位经过磁头时,读写磁头就可以感知到位的值,也可以修改值。对磁盘的访问时间分为 寻道时间,旋转时间,以及传送时间。 由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,因此为了提高效率,要尽量减少磁盘I/O,减少读写操作。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理: 当一个数据被用到时,其附近的数据也通常会马上被使用。 程序运行期间所需要的数据通常比较集中。 由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高I/O效率。 预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。 文件系统及数据库系统的设计者利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入。为了达到这个目的,在实际实现B-Tree还需要使用如下技巧: 每次新建一个节点的同时,直接申请一个页的空间( 512或者1024),这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。如,将B树的度M设置为1024,这样在前面的例子中,600亿个元素中只需要小于4次查找即可定位到某一存储位置。 同时在B+树中,内节点只存储导航用到的key,并不存储具体值,这样内节点个数较少,能够全部读取到主存中,外接点存储key及值,并且顺序排列,具有良好的空间局部性。所以B及B+树比较适合与文件系统的数据结构。下面是一颗B树,用来进行内容存储。另外B/B+树也经常用做数据库的索引,这方面推荐您直接看张洋的MySQL索引背后的数据结构及算法原理这篇文章,这篇文章对MySQL中的如何使用B+树进行索引有比较详细的介绍,推荐阅读。 总结在前面两篇文章介绍了平衡查找树中的2-3树,红黑树之后,本文介绍了文件系统和数据库系统中常用的B/B+ 树,他通过对每个节点存储个数的扩展,使得对连续的数据能够进行较快的定位和访问,能够有效减少查找时间,提高存储的空间局部性从而减少IO操作。他广泛用于文件系统及数据库中,如: Windows:HPFS文件系统 Mac:HFS,HFS+文件系统 Linux:ResiserFS,XFS,Ext3FS,JFS文件系统 数据库:ORACLE,MYSQL,SQLSERVER等中希望本文对您了解B/B+ 树有所帮助。 参考阅读 MySQL索引使用的数据结构:B-Tree和B+Tree B树与B+树 MySQL索引背后的数据结构及算法原理 浅谈算法和数据结构: 十 平衡查找树之B树]]></content>
<categories>
<category>数据结构</category>
</categories>
<tags>
<tag>B树</tag>
<tag>B+树</tag>
</tags>
</entry>
<entry>
<title><![CDATA[nginx]]></title>
<url>%2F2018%2F08%2F30%2Fnginx%2F</url>
<content type="text"><![CDATA[参考阅读 nginx负载均衡原理]]></content>
<categories>
<category>面试</category>
</categories>
<tags>
<tag>nginx</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[转]写给工程师的十条精进原则]]></title>
<url>%2F2018%2F08%2F28%2F%E8%BD%AC-%E5%86%99%E7%BB%99%E5%B7%A5%E7%A8%8B%E5%B8%88%E7%9A%84%E5%8D%81%E6%9D%A1%E7%B2%BE%E8%BF%9B%E5%8E%9F%E5%88%99%2F</url>
<content type="text"><![CDATA[引言时间回到8年前,我人生中的第一份实习工作,是在某互联网公司的无线搜索部做一个C++工程师。当时的我可谓意气风发,想要大干一场,结果第一次上线就写了人生中第一个Casestudy。由于对部署环境的不了解,把SVN库里的配置文件错误地发到线上,并且上完线就去吃晚饭了,等吃饭回来发现师傅在焦头烂额地回滚配置。那次故障造成了一个核心服务20分钟不可用,影响了几百万的用户。 这仅仅是一个开始,在后来半年的时间里,我几乎把所有职场新人可能犯的错误都犯了个遍。架构师让我调研一个抓取性能提升方案,我闷头搞了两周,也没有得出任何结论;本来安排好的开发计划,由于我临时要回去写论文,搞得经理措手不及;参加项目座谈会,全程“打酱油”……那段时间,自己也很苦恼,几乎每天晚上11点多才走,很累很辛苦,但依然拿不到想要的结果。 8年过去了,自己从一个职场小白逐步成长为一名技术Leader。我发现团队中的很多同学在不停地重复犯着自己当年类似的错误。他们并不是不努力,到底是哪里出了问题?经过一段时间的观察与思考后,我想我找到了答案。那就是:我们大多数同学在工作中缺乏原则的指导。原则,犹如指引行动的“灯塔”,它连接着我们的价值观与行动。不久前,桥水基金创始人雷·达里奥在《原则》一书中所传达的理念,引爆了朋友圈。每个人都应该有自己的原则,当我们需要作出选择时,一定要坚持以原则为中心。但是在现实生活中,我们往往缺少对原则的总结,对于很多人来说这是一门“只可意会不可言传”的玄学,是属于老司机的秘密,其实不然。 “追求卓越”是美团的价值观。作为一名技术人员,我们应该如何践行呢?本文总结了十条精进原则,希望能够给大家带来一些启发,更好地指导我们的行动。 原则一:Owner意识“Owner意识”主要体现在两个层面:一是认真负责的态度,二是积极主动的精神。 认真负责是工作的底线。首先,要对我们交付的结果负责。项目中每一个设计文档、每一行代码都需要认真完成,要对它的质量负责。如果设计文档逻辑混乱,代码没有注释,测试时发现一堆Bug,影响的不仅仅是RD的工程交付质量,还会对协同工作的RD、QA、PM等产生不好的影响。久而久之,团队的整体交付质量、工作效率也会逐步下降,甚至会导致团队成员之间产生不信任感。其次,我们要对开发的系统负责。系统的架构是否需要改进,接口文档是否完善,日志是否完整,数据库是否需要扩容,缓存空间够不够等等,这些都是需要落地的事情。作为系统Owner,请一定要认真履行。 积极主动是“Owner意识”更高一级的要求。RD每天要面对大量的工作,而且很多并不在计划内,这就需要具备一种积极主动的精神。例如我们每天可能会面对大量的技术咨询,如果客户提出的问题很长时间得不到回应的话,就会带来不好的客户体验。很多同学说忙于自己的工作没有时间处理,有同学觉得这件事不是很重要,也有很多同学是看到了,但是不知道怎么回答,更有甚者,看到了干脆装没看见。这些都是缺乏Owner意识的体现。正确的做法是积极主动地推动问题的解决,如果时间无法排开或者不知道如何解决,可以直接将问题反馈给能解决的同学。 积极主动还可以表现在更多方面。比如很多同学会自发地梳理负责服务的现状,根据接口在性能方面暴露的问题提出改进意见并持续推动解决;也有同学在跨团队沟通中主动承担起主R的角色,积极发现问题、暴露问题,推动合作团队的进度,保证项目顺利推进。这些同学无一不是团队的中坚力量。所以,我们在做好自己份内工作的同时,也应该积极主动地投入到“份外”的工作中去。一分耕耘一分收获,不要给自己设限,努力成为一个更加优秀的人。 原则二:时间观念相信大家都有时间观念,但是真正能执行到位的可能并没有那么多。互联网是一个快速发展的行业,RD的研发效率是一个公司硬实力的重要体现。项目的按期交付是一项很重要的执行能力,在很大程度上决定着领导和同事对自己靠谱程度的评价。大家可能会问:难度几乎相同的项目,为什么有的同学经常Delay,而有的同学每次都能按时上线?一个很重要的原因,就是这些按时交付的同学往往具备如下两个特质:做事有计划,工作分主次。 工作安排要有计划性。通常,RD在设计评审之后就能预估出精确的开发时间,进而再合理地安排开发、联调、测试计划。如果是项目负责人,那么就会涉及协调FE、QA、PM等多个工种的同学共同完成工作。凡事预则立,不预则废。在计划制定过程中,要尽可能把每一项拆细一点(至少到pd粒度)。事实证明,粒度越细,计划就越精准,实际开发时间与计划之间的误差就会越小。 此外,务必要规定明确的可检查的产出,并在计划中设置一些关键的时间点进行核对。无数血淋淋的事实告诉我们,很多项目延期都是因为在一些关键交付点上双方存在分歧造成的。例如后台RD的接口文档计划在周五提供,FE认为是周五上午,而RD认为是周五下班前提交,无形中会给排期带来了1pd的误差。所以,我们要做到计划粒度足够细,关键时间点要可检查。 工作安排要分清楚主次。我们每天要面对很多的事情,要学会分辨这些工作的主次。可以尝试使用“艾森豪威尔法则”(四象限法则),把工作按照重要、紧急程度分成四象限。优先做重要紧急的事情;重要不紧急的事情可以暂缓做,但是要持续推进;紧急不重要的事情可以酌情委托给最合适的人做;不重要不紧急的事情可以考虑不做。很多项目无法按期交付的原因,都是因为执行人分不清主次。比如在开发中需要使用到ES,一些不熟悉ES的同学可能想系统性地学习一下这方面的知识,就会一头扎进ES的汪洋中。最后才发现,原本一天就能完成的工作被严重拖后。实际工作中,我们应当避免这种“本末倒置”的工作方式。在本例中,“系统性地学习ES”是一件重要但不紧急的事情。要学会分辨出这些干扰的工作项,保证重要紧急的事情能够按时交付。 原则三:以终为始“以终为始”(Begin With The End In Mind),是史蒂芬·柯维在《高效能人士的七个习惯》中提到的一个习惯。它是以所有事物都经过两次创造的原则(第一次为心智上的创造,第二次为实际的创造)为基础的。直观的表达就是:先想清楚目标,然后努力实现。 在工作中,很多RD往往只是埋头走路,很少抬头看天。每次季度总结的时候,罗列了很多项目,付出很多努力。但是具体这些项目取得了哪些收益,对业务有哪些提升,却很难说出来。这就说明在工作中并没有遵守“以终为始”这一原则。此外,很多同学在做需求的过程中,对于目标与收益关注不够,系统上线之后,也没有持续地跟进使用效果。这一点在技术优化项目中体现的尤为明显。 例如在一个接口性能优化的项目中,经过RD的努力优化,系统TP99缩短了60%,支持QPS提升了2倍。但是系统到底需要优化到什么程度呢?是不是缩短60%,提升2倍就能满足需求呢?在优化之前,很多同学常常忘记设置一个预设的目标(TP99小于多少,支持QPS大于多少)。我们必须清楚,优化一定是有原因的,比如预期某节假日流量会暴增或者某接口超时比例过高,如果不进行优化,系统可能会存在宕机风险。解决特定的问题才是技术优化的最终目的,所以要根据问题设定目标,再进行优化。 “以终为始”,这一原则还可以作用于我们的学习中。很多同学看过很多技术文章,但是总是感觉自己依然一无所知。很重要的一个原因,就是没有带着目标去学习。在这个信息爆炸的时代,如果只是碎片化地接收各个公众号推送的文章,效果几乎可以忽略不计。在学习之前,我们一定要问自己,这次学习的目标是什么?是想把Redis的持久化原理搞清楚,还是把Redis的主从同步机制弄明白,亦或是想学习整个Redis Cluster的架构体系。如果我们能够带着问题与目标,再进行相关的资料搜集与学习,就会事半功倍。这种学习模式的效果会比碎片化阅读好很多。 原则四:闭环思维你是否遇到过这样的场景:参加了一个设计(或需求)评审,大家兴致勃勃地提了很多合理的意见,等到再次评审的时候,却发现第一次提的很多问题都没有得到改进,很多讨论过的问题需要从头再开始讨论。这种情况就是一种典型的工作不闭环。 之前看过一句话:一个人是否靠谱,就看他能否做到凡事有交代,件件有着落,事事有回音。这就是闭环思维的重要性。它强调的是一种即时反馈闭环,如果别人给我们分配了一个任务,不管完成的结果如何,一定要在规定的时间内给出明确的反馈。 例如在跨部门的沟通会议中,虽然各方达成了一致,会议发起者已经将最终的记录周知大家。但是,到这一步其实并没有完成真正的闭环,在落地执行过程中很可能还存在一些潜在的问题。例如,会议纪要是否经各方仔细核对并确认过?会议中明确的To Do进展是什么?完成结果有没有Check的机制?如果这些没有做到的话,就会陷入“沟通-发现问题-再沟通-再发现问题”的恶性循环中。 真正的闭环,要求我们对工作中的事情都能够养成良好的思维习惯,沟通要有结论,通知要有反馈,To Do要有验收。 “闭环思维”还要求能够定期主动进行阶段性的反馈。刚参加工作时,我接了一个工期为两个月的项目。整个项目需要独自完成,自己每天按照计划,有条不紊地进行开发。大概过了两周之后,Leader询问项目进度,虽然我已经跟他说没问题。然而,Leader告诉我,因为我每天对着电脑也不说话,让他心里很没底。 这时,我才意识到一个很重要的问题,我跟Leader之间存在信息不对称。从那以后,我就时不时得跟他汇报一下进度,哪怕就只有简短的一句话,也可以明显感觉,他对我的信心增加了很多。特别是我做Leader之后,对这种闭环反馈的理解,就更加深刻了。从Leader的角度看,其实只是想知道项目是否在正常推进,是否遇到问题需要他协助解决。 原则五:保持敬畏“君子之心,常怀敬畏”,保持敬畏之心能够让我们少犯错误。在工作中存在各种各样的规范,例如代码规范、设计规范、上线规范等等。我们必须明白,这些规范的制定一定是基于某些客观原因的,它们都是历史上无数Case积累而来的经验。团队里的每一个成员都应该学习并严格遵守,这一点对于新人尤其重要。 当我们进入到一个新的团队,请先暂时忘掉之前的习惯,要尽快学习团队既有的规范,并且让自己与团队保持一致。以编码风格为例,很多同学往往习惯于自己之前的代码写作风格,在做新公司第一个项目时,也按照自己的习惯进行变量、包的命名等等。结果在代码Review过程中,被提了很多修改意见,不得不返工重写,得不偿失。如果能够保持敬畏之心,提前了解编码规范,这种问题完全可以避免。 类似的问题,还包括对上线流程的不了解,对回滚操作不熟悉,对SRE线上变更过程不了解等等。除了这些显而易见的规范,还有一些约定俗成的规则。个人建议是:如果有事情拿不准,不妨多问问其他同事,不要凭自己的感觉做事情。 保持敬畏之心并不意味着要“因循守旧”。在我们充分了解这些规范和约定之后,如果觉得存在不妥之处,可以跟全组同学讨论,是否采纳新的建议,然后及时去更新迭代。其实,让规范与约定与时俱进,也是另一种形式的敬畏。 原则六:事不过二“事不过二”,是我们团队一贯坚持的原则,它可以解读为两层含义。 一层含义是“所有的评审与问题讨论,不要超过两次”。之所以有这样的要求,是因为我们发现,很多RD都把时间花费在一些无休止的评审与问题讨论中,真正投入到实际开发中的时间反而很少。在实际工作场景中,我们经常会遇到一些不是很成熟的需求评审。这些需求文档,要么是背景与目标含糊不清,要么是产品方案描述不够细化,或者存在歧义。RD与PM被迫反复进行讨论,我曾经遇到过一个需求评审,进行了三次还被打回。 同样的问题,在设计评审中也屡见不鲜。方案固然需要经过反复的讨论,但是如果迟迟不能达成一致,就会耗费很多RD与PM的宝贵时间,这就与提升研发效率的理念背道而驰。因此我们团队规定:所有的评审最多两次。通过这种方式,倒逼利益相关方尽可能地做好需求与方案设计。评审会议组织前,尝试与所有相关人员达成一致,询问对方的意见,并进行有针对性的讨论,这样能够大大提升评审会议的效率和质量。如果在第一次评审中不通过,那么就只有一次机会进行复审。一旦两次不通过,就需要进行Casestudy。 “事不过二”原则的另一层含义,是“同样的错误不能犯第二次”。每次故障之后,Casestudy都必须进行深刻的总结复盘,对故障原因进行5Why分析,给出明确可执行的To Do List。每次季度总结会,大家自我反省问题所在,在下个季度必须有所改善,不能再犯类似的错误。孔子云:“不迁怒,不贰过”,在错误中反思与成长,才能让我们成为更优秀的人。 原则七:设计优先“设计优先”这条原则,相对来说更加具体一些。之所以单列一项,是因为架构设计太重要了。Uncle Bob曾说过:“软件架构的目标,是为了让构建与维护系统的所需人力资源最小化。” 架构设计,并不仅仅关系到系统的质量,还关乎团队的效能问题。很多团队也有明文规定,开发周期在3pd以上的项目必须有设计文档,开发周期在5pd以上的项目必须有设计评审。在具体的执行过程中,由于各种原因,设计往往并不能达到预期的效果。究其原因,有的是因为项目周期紧,来不及设计的足够详细;有的是因为RD主观上认为项目比较简单,设计草草了事。无数事实证明,忽略了前期设计,往往会导致后续开发周期被大幅拉长,给项目带来了很大的Delay风险。而且最可怕的是,不当的设计会给项目带来巨大的后期维护成本,我们不得不腾出时间,专门进行项目的优化与重构。因此,无论什么时候都要记住“设计优先”这一原则。磨刀不误砍柴工,前期良好的设计,会给项目开发以及后期维护带来极大的收益。 “设计优先”这一原则,要求写别人看得懂的设计。我们了解一个系统最直接的途径就是结合设计文档与代码。在实际工作中,很多同学的设计文档让大家看得一头雾水,通篇下来,看不出系统整体的设计思路。其实,设计的过程是一种智力上的创造,我们更希望它能成为个人与集体智慧的结晶。如何才能让我们的设计变得通俗易懂?我个人认为,设计应该尽量使用比较合理的逻辑,进而把设计中的一些点组织起来。比如可以使用从抽象到具体,由总到分的结构来组织材料。在设计过程中,要以需求为出发点,通过合理的抽象把问题简化,讲清楚各个模块之间的关系,再详细分述模块的实现细节。做完设计之后,可以发给比较资深的RD或者PM审阅一下,根据他们的反馈再进行完善。好的设计,一定是逻辑清晰易懂、细节落地可执行的。 原则八:P/PC平衡“P/PC平衡”原则,即产出与产能平衡原则。伊索寓言中讲述了一个《生金蛋的鹅》的故事。产出好比“金蛋”,产能好比“会下金蛋的鹅”。“重蛋轻鹅”的人,最终可能连产蛋的资产都保不住;“重鹅轻蛋”的人,最终可能会被饿死。产出与产能必须平衡,才能达到真正的高效能。为了让大家更清晰的了解这一原则,本文举两个例子。 从系统的角度看,每一个系统都是通过持续不断地叠加功能,来实现其产出,而系统的产能是通过系统架构的可扩展性、稳定性等一系列特性来表征。为了达到产出与产能的平衡,需要在不断支持业务需求的过程中,持续进行技术架构层面的优化。如果一味地做业务需求,经过一定的时间,系统会越来越慢,最终影响业务的稳定性;反之,一个没有任何业务产出的系统,最终会消亡。 再从RD的角度来看这个问题,RD通过做需求来给公司创造价值,实现自己的产出。而RD的产能是指技术能力、软素质、身体健康状况,有这些资本后,我们才能进行持续的产出。在日常工作中,我发现很多RD往往只重视产出。他们也在很努力地做项目,但是每一个项目所使用的方法,还是沿用自己先前一贯的思路。最终,不仅项目做得一般,还会抱怨自己得不到任何成长。这就是P/PC不平衡的体现。如果能在做项目的过程中,通过学习总结持续提升自己的技术能力和软素质,并将其应用于项目实施交付中,相信一定会取得双赢的结果。 “P/PC平衡”原则还适用于很多其他的领域,例如团队、家庭等,我本人也非常推崇这一原则。希望大家也能将其作为自身的一项基本原则,努力寻找到产出与产能的平衡点。 原则九:善于提问“善于提问”,首先要勤于提问。求知欲源于好奇心,是人类的一种本能。在工作中要养成勤于提问的好习惯,不懂就问,不要因为自己一时懒惰或者碍于情面,就放弃提问的机会。当遇到不同的观点时,也要礼貌地问出来。波克定理告诉我们,只有在争辩中,才可能诞生最好的主意和最好的决定。 在设计评审、代码评审这类体现集体智慧的活动中,遇到有问题的地方一定要提出来。我经常看到,很多同学评审全程一言不发,这就是浪费大家的时间。设计评审的目的,是让大家针对方案提出改进意见并达成一致,如果全程“打酱油”,那就失去了评审的意义。我们鼓励大家多提问,把自己内心的疑惑表达出来,然后通过交流的方式得到答案。 “善于提问”,还要懂得如何提问。为什么同样是参加设计评审,有的同学就能提出很好的问题,而有的同学却提不出任何问题?除了知识储备、专业技能、经验等方面的差异外,还有一点很重要:这就是批判性思维。 批判性思维主张通过批判性思考达到理性思维,即对事物本质的认知和掌握。关于如何进行批判性思维,大家可以参考一些经典的图书如《批判性思维》、《学会提问》等。在工作中面临一项决策时,会有各种各样的意见摆在你面前,所以我们必须要学会使用批判性思维来进行分析,每个人的论据是否可靠,论证是否合理,是否有隐含的立场。同样,在阅读一篇技术博客的时候,也要使用批判性的思维,多问几个为什么,作者得出的结论是否合理?论据是否充分?只有这样,才能不断地获取真正的知识。 原则十:空杯心态“满招损,谦受益”,“空杯心态”是最后一项原则。我觉得这也是一个人能够持续成长的前提。做技术的人,骨子里通常有股傲气,并且会随着资历、成绩的提升而不断增加。初入职场的小白,可能会非常谦虚,但是工作几年之后,专业技能逐步提升,可能还取得了一些小成就,人就会越来越自信。这时候,如果不能始终保持“空杯心态”,这种自信就会逐步演变为自满。自满的人,往往表现为工作中把别人的建议当成是批评,不接受任何反对意见,学习上也缺乏求知的动力,总是拿自己的长处去跟别人的短处做比较。其实每个人多少都会有一些自满,可怕的是不知道甚至不愿承认自满。 保持“空杯心态”这一原则要求我们时刻进行自我检视与反省。在工作中,多去跟不同级别的同学聊一聊,或者做一个360度评估,这有助于我们更加客观地评价自己。在横向对比中,多向那些优秀的同学看齐,学习他人的优点。很多同学在设计评审或者代码Review过程中,针对别人提出的问题与建议,往往都采用一种对立的态度。错误地认为别人是在挑刺,是在针对自己。诚然,在某些方面,我们可能确实比其他人想得深入,但是这不代表在所有方面都能考虑周全。对于别人的建议,建议使用“善于提问”原则里提到的批判性思维仔细分析一下,虚心地吸取那些好的建议。 工作学习就像“练级打怪”,技能储备的越多,就越容易走到最后。保持空杯心态,可以让我们发现很多以前注意不到的新能力,我们要做的就是努力学习它,将它们转化为自己能力库的一部分。 总结以上,是我总结的工作与学习的十条基本原则。其中有的侧重于个人做事情的方法,如“Owner意识”、“时间观念”、“以终为始”、”闭环思维”;有的侧重于团队工作标准规范,如“保持敬畏”、“事不过二”、“设计优先”;有的侧重于团队或个人效能提升,如“P/PC平衡”、“善于提问”、“空杯心态”。这些原则是我多年在工作与学习中,不断总结得来的经验。希望在大家面临选择时,这些原则能够起到一定的帮助和指导作用。 以原则为中心地工作与生活,让自己与团队变得更加强大。 作者介绍云鹏,2014年加入美团,先后参与了美团酒店供应链体系、分布式调度系统的建设,现在负责美团旅行客户关系管理系统、基础信息服务的建设工作。 ———- END ———-[^_^]: 参考阅读 写给工程师的十条精进原则]]></content>
<tags>
<tag>标签1</tag>
<tag>标签2</tag>
</tags>
</entry>
<entry>
<title><![CDATA[常用设计模式]]></title>
<url>%2F2018%2F08%2F26%2F%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%2F</url>
<content type="text"><![CDATA[[^_^]: > 常用设计模式Singleton(单例模式)一句话总结:一个类在Java虚拟机中只有一个对象,并提供一个全局访问点。 生活中例子:太阳、月亮、国家主席等。 解决什么问题:对象的唯一性,性能浪费太多。 项目里面怎么用:数据库连接对象,属性配置文件的读取对象。 模式结构:分为饿汉式和懒汉式(如果考虑性能问题的话,就使用懒汉式,因为懒汉式是在方法里面进行初始化的),构造器私 有化,对外提供方法加同步关键字。 框架里面使用:Struts1的Action。 JDK里面使用:java.lang.Runtime#getRuntimejava.awt.Desktop#getDesktop。饿汉式代码:1234567public class HurgrySingleton { private static HurgrySingleton hurgry=new HurgrySingleton(); private HurgrySingleton(){}; public static HurgrySingleton getSinletonHurgry(){ return hurgry; } } 懒汉式代码:12345678910public class LarzySingleton { private static LarzySingleton larzy=null; private LarzySingleton(){}; public static synchronized Larzy getSinletonLarzy(){ if(larzy==null){ larzy=new LarzySingleton(); } return larzy; } } Factory(简单的工厂模式)一句话总结:用一个方法来代替new关键字 生活中的例子:制衣厂、面包厂等生产厂。 解决什么问题:对象产生过多,或者经常有子类替换生成。 项目里面怎么用:对于经常生成的对象,或者父子类替换的对象。 模式结构:写一个对外声明的方法,方法里面使用new关键字代替。 框架里面使用:spring的核心就是工厂模式。 JDK里面使用:newInstance。 工厂模式代码:123456789public class UserFactory { public static User createUser(int i){ //如果输入的是1,就创建它的子类,否则就创建父类 if(i==1){ return new Alices(); } return new User(); } } Proxy(代理模式)一句话总结:为其他对象提供一个代理,以控制对当前对象的访问。 生活中的例子:房屋中介、婚姻介绍所。 解决什么问题:不能直接访问该对象,或者太大的资源耗费多。 项目里面怎么用:权限,或者大对象的访问权限。 模式结构:代理类和被代理类实现同一个接口,用户访问的时候先访问代理对象,然后让代理对象去访问被代理对象。 框架里面使用:Spring里面的AOP实现。 JDK里面使用:java.lang.reflect.Proxy。 代理模式代码:创建一个接口:123public interface SellHouse { void sell(double money); } 创建一个被代理类:123456public class Hoster implements SellHouse { @Override public void sell(double money) { System.out.println("祝你居住愉快"); } } 创建一个代理类:1234567891011public class Medium implements SellHouse { SellHouse hoster=new Hoster(); @Override public void sell(double money) { if(money>=1000){ hoster.sell(money); }else{ System.out.println("你的价格太低了"); } } } 测试类:123456public class Renter { public static void main(String[] args) { SellHouse renter=new Medium(); renter.sell(500); } } Adapter(适配器模式)一句话总结:将两个原来不兼容的类兼容起来一起工作。 生活中的例子:变压器、充电器 解决什么问题:已经存在的相同功能的代码,但是接口不兼容,不能直接调用。 项目里面怎么用:在使用旧的API的时候,没有源码,和新的不能兼容。 模式结构:分为类适配器和对象适配,一般常用的就是对象适配器,因为组合由于继承。 框架里面使用:单元测试里面的asserEquels。 JDK里面使用:java.util.Arrays#asListjava.io.InputStreamReader(InputStream) java.io.outputStreamWriter(OutputStream)。 Strategy(策略模式)一句话总结:定义一系列算法并可以互相替换。 生活中的例子:图片的格式,压缩文件的格式。 解决什么问题:做一件事情有很多种方法。 项目里面怎么用:购物车里面的付款方式。 模式结构:声明一个顶级接口,定义一个策略方法,具体的实例都要实现这个接口。 框架里面使用:hibernate的主键生成策略。 JDK里面使用:java.util.Comparator#compare。 策略模式代码:定义一个顶级接口: 123public interface Person { void repast(); } 具体的实例类1:123456public class African implements Person { @Override public void repast() { System.out.println("非洲人用手吃饭"); } } 具体的实例类2:123456public class America implements Person { @Override public void repast() { System.out.println("美国人用刀叉吃饭"); } } 具体的实例类3: 123456public class Chinese implements Person { @Override public void repast() { System.out.println("中国人用筷子吃饭"); } } 测试类: 12345678910public class Test { public static void main(String[] args) { Person chinese=new Chinese(); Person america=new America(); Person african=new African(); chinese.repast(); america.repast(); african.repast(); } } Template(模板模式)一句话总结:父类定义流程,子类实现流程。 生活中的例子:iphone生产有多个国家,但流程只有一个。 解决什么问题:业务有多种,但都有规定的流程。 项目里面怎么用:一般基类的实现都是模板模式,BaseDAO,bBaseService。 模式结构:定义一个抽象父类定义流程,或者常用方法和常量,子类继承父类,实现具体的细节方法。 框架里面使用:hibernate里面的方言,是跨数据库的基础。 JDK里面使用:IO流里面的InputStream,Writer等。 模板模式代码: //定义一个父类,定义流程 123456789101112public abstract class IPhoneTemplate { public void createIPhone(){ setupCpu(); setupAll(); check(); box(); } protected abstract void box(); protected abstract boolean check(); protected abstract void setupAll(); protected abstract void setupCpu(); } //子类实现父类的细节方法1 12345678910111213141516171819public class ChinaIPhone extends IPhoneTemplate { @Override protected void box() { System.out.println("box()"); } @Override protected boolean check() { System.out.println("check()"); return true; } @Override protected void setupAll() { System.out.println("setupAll()"); } @Override protected void setupCpu() { System.out.println("setupCpu()"); } } //子类实现父类的细节方法2 12345678910111213141516171819public class AfricanIPhone extends IPhoneTemplate { @Override protected void box() { System.out.println("box()"); } @Override protected boolean check() { System.out.println("check()"); return true; } @Override protected void setupAll() { System.out.println("setupAll()"); } @Override protected void setupCpu() { System.out.println("setupCpu()"); } } [^_^]: 参考阅读 常用设计模式(面试)]]></content>
<categories>
<category>面试</category>
</categories>
</entry>
<entry>
<title><![CDATA[python基础知识]]></title>
<url>%2F2018%2F08%2F26%2Fpython%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%2F</url>
<content type="text"><![CDATA[可变与不可变类型python中的对象分为:可变对象和不可变对象可变对象:列表(list)、字典(dict )不可变对象:int、string、float、tuple对不可变类型的变量重新赋值,实际上是重新创建一个不可变类型的对象,并将原来的变量重新指向新创建的对象(如果没有其他变量引用原有对象的话(即引用计数为0),原有对象就会被回收)。 可变与不可变类型作用python函数的参数传递:在python中规定函数的参数传递为引用传递,也就是说传递给函数的参数是这个变量所指向的内存地址。但是在C中,参数传递可以有值传递和引用传递,当需要修改外面参数的值的时候就才用引用传递(在参数前面加一个*,表示传递参数所指的内存地址),但不需要修改外部参数值的时候就采用值传递。 那么在python中如何实现和值传递和引用传递相似的功能呢?———可变类型和不可变类型就发挥作用了。当你需要实现函数参数引用传递的功能的时候,你就将类型为list ,dict的变量传递给相应的函数。当你需要实现函数值传递功能的时候,你就可以将类型为int,float,string,tuple类型的变量传递给函数当做参数。 听说python只允许引用传递是为方便内存管理,因为python使用的内存回收机制是计数器回收,就是每块内存上有一个计数器,表示当前有多少个对象指向该内存。每当一个变量不再使用时,就让该计数器-1,有新对象指向该内存时就让计数器+1,当计时器为0时,就可以收回这块内存了。当然我觉得它肯定不止用了计数器吧,应该还有其他的技术,比如分代回收什么的。不再讨论之列,就这样了 深拷贝和浅拷贝的实现方式以及区别Python中直接赋值python中的直接赋值相当于传递对象的引用而已,原对象改变,被赋值的对象也会改变。 Python中浅拷贝copy浅拷贝,没有拷贝子对象(这里应该指的是可变对象),只是将新对象给了一个新的首地址,里面的数据和地址都和原始数据相同,所以原始数据改变,子对象会改变。 Python中深拷贝python中的深拷贝,包含对原始对象的子对象的深拷贝,所以,对原拷贝对象的改变不会对深拷贝后对象没有任何影响。 总结1、赋值:简单地拷贝对象的引用,两个对象的id相同。2、浅拷贝:创建一个新的组合对象,这个新对象与原对象共享内存中的子对象。3、深拷贝:创建一个新的组合对象,同时递归地拷贝所有子对象,新的组合对象与原对象没有任何关联。虽然实际上会共享不可变的子对象,但不影响它们的相互独立性。 浅拷贝和深拷贝的不同仅仅是对组合对象来说,所谓的组合对象就是包含了其它对象的对象,如列表,类实例。而对于数字、字符串以及其它“原子”类型,没有拷贝一说,产生的都是原对象的引用。 __new__()与__init__()的区别new方法是类创建实例的方法, 创建对象时调用,返回当前对象的一个实例init()方法是类实例创建之后调用,用于对当前对象的一些初始化,没有返回值。new()方法和init()方法所接收的参数是一样的。需要注意的几点:1、init()函数并不相当于C++或者C#中的构造函数,因为在执行init()函数的时候,实例已经构造出来了2、子类可以不重写init()方法,实例化子类时可以自动调用超类中已定义的init()方法。但是如果重写了init(),实例化子类时将不会再 隐式的去调用超类中已定义的init()代码。3、如果重写了init(),为了能使用或扩展超类中的行为,最好显式的调用超类的init()方法 python装饰器详解12345678910111213141516171819202122#既不需要侵入,也不需要函数重复执行import timedef deco(func): def wrapper(): startTime = time.time() func() endTime = time.time() msecs = (endTime - startTime)*1000 print("time is %d ms" %msecs) return wrapper@decodef func(): print("hello") time.sleep(1) print("world")if __name__ == '__main__': f = func #这里f被赋值为func,执行f()就是执行func() f() dict按value排序1sorted(iterable[, cmp[, key[, reverse]]]) iterable:是可迭代类型类型;cmp:用于比较的函数,比较什么由key决定,有默认值,迭代集合中的一项;key:用列表元素的某个属性和函数进行作为关键字,有默认值,迭代集合中的一项;reverse:排序规则. reverse = True 或者 reverse = False,有默认值,默认为升序排列(False)。12345#第一种sorted(a.items(), key=lambda x:x[1])#第二种b = zip(a.values(), a.keys())sorted(b) 参考阅读 Python基础面试中常常问道的问题 python装饰器详解 12步轻松搞定python装饰器]]></content>
<categories>
<category>面试</category>
</categories>
<tags>
<tag>python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[海量数据处理]]></title>
<url>%2F2018%2F08%2F21%2F%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%2F</url>
<content type="text"><![CDATA[[^_^]: > 海量数据处理处理方法Hashing适用范围:快速查找,删除的基本数据结构,通常需要总数据量可以放入内存基本原理及要点: hash函数选择,针对字符串,整数,排列,具体相应的hash方法。碰撞处理,一种是open hashing,也称为拉链法;另一种就是closed hashing,也称开地址法,opened addressing。数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,最常用的一种方法——拉链法,我们可以理解为“链表的数组” ####bit-map适用范围:可进行数据的快速查找,判重,删除,一般来说数据范围是int的10倍以下基本原理及要点:使用bit数组来表示某些元素是否存在,比如8位电话号码扩展:bloom filter可以看做是对bit-map的扩展 双层桶划分事实上,与其说双层桶划分是一种数据结构,不如说它是一种算法设计思想。面对一堆大量的数据我们无法处理的时候,我们可以将其分成一个个小的单元,然后根据一定的策略来处理这些小单元,从而达到目的。适用范围:第k大,中位数,不重复或重复的数字基本原理及要点:因为元素范围很大,不能利用直接寻址表,所以通过多次划分,逐步确定范围,然后最后在一个可以接受的范围内进行, 分治才是其根本。问题实例: 堆适用范围:海量数据前n大,并且n比较小,堆可以放入内存 (适合大数据量,求前n小,n的大小比较小的情况,这样可以扫描一遍即可得到所有的前n元素,效率很高)扩展:双堆,一个最大堆与一个最小堆结合,可以用来维护中位数。问题实例: 100w个数中找最大的前100个数。 倒排索引(Inverted index)适用范围:搜索引擎,关键字查询基本原理及要点:为何叫倒排索引?一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。以英文为例,下面是要被索引的文本:T0 = “it is what it is”T1 = “what is it”T2 = “it is a banana”我们就能得到下面的反向文件索引:“a”: {2}“banana”: {2}“is”: {0, 1, 2}“it”: {0, 1, 2}“what”: {0, 1}检索的条件”what”, “is” 和 “it” 将对应集合的交集。正向索引开发出来用来存储每个文档的单词的列表。正向索引的查询往往满足每个文档有序频繁的全文查询和每个单词在校验文档中的验证这样的查询。在正向索引中,文档占据了中心的位置,每个文档指向了一个它所包含的索引项的序列。也就是说文档指向了它包含的那些单词,而反向索引则是单词指向了包含它的文档,很容易看到这个反向的关系。 问题实例Hashing海量日志数据,提取出某日访问百度次数最多的那个IP。IP是32位的,最多有个2^32个IP。同样可以采用映射的方法,比如模1000,把整个大文件映射为1000个小文件,再找出每个小文中出现频率最大的IP(可以采用hash_map进行频率统计,然后再找出频率最大的几个)及相应的频率。然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。 搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。),请你统计最热门的10个查询串,要求使用的内存不能超过1G。 第一步借用hash统计进行预处理: 先对这批海量数据预处理(维护一个Key为Query字串,Value为该Query出现次数,即Hashmap(Query,Value),每次读取一个Query,如果该字串不在Table中,那么加入该字串,并且将Value值设为1;如果该字串在Table中,那么将该字串的计数加一即可。最终我们在O(N)(N为1千万,因为要遍历整个数组一遍才能统计处每个query出现的次数)的时间复杂度内用Hash表完成了统计; 第二步借用堆排序找出最热门的10个查询串:时间复杂度为N’*logK。维护一个K(该题目中是10)大小的小根堆,然后遍历3百万个Query,分别和根元素进行对比(对比value的值),找出10个value值最大的query 最终的时间复杂度是:O(N) + N’*O(logK),(N为1000万,N’为300万)或者:采用trie树,关键字域存该查询串出现的次数,没有出现为0。最后用10个元素的最小推来对出现频率进行排序。 有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。第一步分而治之/hash映射到顺序读文件中,对于每个词x,取hash(x)%5000,然后按照该值存到5000个小文件(记为x0,x1,…x4999)中。这样每个文件大概是200k左右。如果其中的有的文件超过了1M大小,还可以按照类似的方法继续往下分,直到分解得到的小文件的大小都不超过1M。 第二步hash统计对每个小文件,统计每个文件中出现的词以及相应的频率(可以采用trie树/hash_map等),并取出出现频率最大的100个词(可以用含100个结点的最小堆),并把100个词及相应的频率存入文件,这样又得到了5000个文件。 第三步堆/归并排序就是把这5000个文件进行归并(也可以采用堆排序)的过程了。(如果内存允许可以将这5000个文件中的所有元素合并起来,利用堆获得top 100) 给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url?可以估计每个文件安的大小为5G×64=320G,远远大于内存限制的4G。所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法。遍历文件a,对每个url求取hash(url)%1000,然后根据所取得的值将url分别存储到1000个小文件(记为a0,a1,…,a999)中。这样每个小文件的大约为300M。遍历文件b,采取和a相同的方式将url分别存储到1000小文件(记为b0,b1,…,b999)。这样处理后,所有可能相同的url都在对应的小文件(a0vsb0,a1vsb1,…,a999vsb999)中,不对应的小文件不可能有相同的url。然后我们只要求出1000对小文件中相同的url即可。求每对小文件中相同的url时,可以把其中一个小文件的url存储到hash_set中。然后遍历另一个小文件的每个url,看其是否在刚才构建的hash_set中,如果是,那么就是共同的url,存到文件里面就可以了。 位图存储(bitmap) 已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数(共有都少个不同的号码)。8位最多99 999 999(0-99 999 999共1亿个数),每个数字对应一个Bit位,所以只需要99MBit==1.2MBytes,这样,就用了小小的1.2M左右的内存表示了所有的8位数的电话) 2.5亿个整数(int)中找出不重复的整数的个数,内存足够大。将bit-map扩展一下,用2bit表示一个数即可,0表示未出现,1表示出现一次,2表示出现2次及以上。或者我们不用2bit来进行表示,我们用两个bit-map即可模拟实现这个2bit-map。 (每个整数用两位,存储所有的整数需要2^32*2=1GB的内存) 2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。整数个数为2^32,我们可以将这2^32个数,划分为2^8个区域(比如用单个文件代表一个区域),然后将数据分离到不同的区域,然后不同的区域在利用bitmap(占用4MB,内存可以存下)就可以直接解决了。也就是说只要有足够的磁盘空间,就可以很方便的解决。 5亿个int找它们的中位数 (指将统计总体当中的各个变量值按大小顺序排列起来,形成一个数列,处于变量数列中间位置的变量值就称为中位数)首先我们将int划分为2^16个区域(肯定是按大小的),然后读取数据统计落到各个区域里的数的个数,之后我们根据统计结果就可以判断中位数落到那个区域,同时知道这个区域中的第几大数刚好是中位数。 然后第二次扫描我们只统计落在这个区域中的那些数就可以了。 腾讯面试题:给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中?方案1:申请512M的内存(2^32/8=512MB),一个bit位代表一个unsigned int值。读入40亿个数,设置相应的bit位,读入要查询的数,查看相应bit位是否为1,为1表示存在,为0表示不存在。 方案2:因为2^32为40亿多,所以给定一个数可能在,也可能不在其中;这里我们把40亿个数中的每一个用32位的二进制来表示假设这40亿个数开始放在一个文件中。然后将这40亿个数分成两类: 1. 最高位为0 2. 最高位为1并将这两类分别写入到两个文件中,其中一个文件中数的个数<=20亿,而另一个>=20亿(这相当于折半了);与要查找的数的最高位比较并接着进入相应的文件再查找再然后把这个文件为又分成两类: 1.次最高位为0 2.次最高位为1并将这两类分别写入到两个文件中,其中一个文件中数的个数<=10亿,而另一个>=10亿(这相当于折半了); 与要查找的数的次最高位比较并接着进入相应的文件再查找。 ……. 以此类推,就可以找到了,而且时间复杂度为O(logn)。 [^_^]: > 参考阅读 - []()]]></content>
<categories>
<category>面试</category>
</categories>
<tags>
<tag>标签1</tag>
<tag>标签2</tag>
</tags>
</entry>
<entry>
<title><![CDATA[进程和和线程]]></title>
<url>%2F2018%2F08%2F20%2F%E8%BF%9B%E7%A8%8B%E5%92%8C%E5%92%8C%E7%BA%BF%E7%A8%8B%2F</url>
<content type="text"><![CDATA[[^_^]: > 进程和和线程定义进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 关系一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。 区别进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.2) 线程的划分尺度小于进程,使得多线程程序的并发性高。3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。 优缺点线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。 [^_^]: > 参考阅读 - []()]]></content>
<categories>
<category>操作系统</category>
</categories>
<tags>
<tag>进程</tag>
<tag>线程</tag>
</tags>
</entry>
<entry>
<title><![CDATA[kafka]]></title>
<url>%2F2018%2F08%2F20%2Fkafka%2F</url>
<content type="text"></content>
</entry>
<entry>
<title><![CDATA[hexo]]></title>
<url>%2F2018%2F08%2F19%2Fhexo%E6%96%87%E6%A1%A3%E7%BC%96%E5%86%99%2F</url>
<content type="text"><![CDATA[Hexo基本操作init$ hexo init [folder]新建一个网站。如果没有设置 folder ,Hexo 默认在目前的文件夹建立网站。 new$ hexo new [layout] 新建一篇文章。如果没有设置 layout 的话,默认使用 _config.yml 中的 default_layout 参数代替。如果标题包含空格的话,请使用引号括起来。(自测引号也是可以的) generate$ hexo generate生成静态文件。选项 描述-d, –deploy 文件生成后立即部署网站-w, –watch 监视文件变动该命令可以简写为:$ hexo g publish$ hexo publish [layout] 发表草稿。 server$ hexo server启动服务器。默认情况下,访问网址为: http://localhost:4000/。选项 描述:-p, –port 重设端口-s, –static 只使用静态文件-l, –log 启动日记记录,使用覆盖记录格式 deploy$ hexo deploy部署网站。参数 描述-g, –generate 部署之前预先生成静态文件该命令可以简写为:$ hexo d render$ hexo render [file2] …渲染文件。参数 描述:-o, –output 设置输出路径 migrate$ hexo migrate 从其他博客系统 迁移内容。 clean$ hexo clean清除缓存文件 (db.json) 和已生成的静态文件 (public)。 在某些情况(尤其是更换主题后),如果发现您对站点的更改无论如何也不生效,您可能需要运行该命令。 list$ hexo list 列出网站资料。 version$ hexo version显示 Hexo 版本。 markdown语法GFM]]></content>
</entry>
<entry>
<title><![CDATA[排序算法-比较排序]]></title>
<url>%2F2018%2F08%2F19%2F%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95-%E6%AF%94%E8%BE%83%E6%8E%92%E5%BA%8F%2F</url>
<content type="text"><![CDATA[快速排序123456789101112131415161718192021222324252627int sort(vector<int> arr) { quickSort(arr, 0, arr.size()-1);}void quickSort(vector<int> &arr, int l, int r) { if (l < r) { int pivot = partition(arr, l, r); quickSort(arr, l, pivot-1); quickSort(arr, pivot+1, r); }}int partition(vector<int> &arr, int l, int r) { int pivot; pivot = arr[l]; while (l < r) { while (l<r && arr[r] >= pivot) { --r; } swap(arr[l], arr[r]); while(l<r && arr[l]<=pivot) { ++l; } swap(arr[l], swap(r)); } return l;} 归并排序1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950//合并两个已排好序的数组A[left...mid]和A[mid+1...right]void Merge(int A[], int left, int mid, int right) { int len = right - left + 1; int *temp = new int[len]; //辅助空间O(n) int index = 0; int i = left; //前一数组的起始元素 int j = mid + 1; //后一数组的起始元素 while (i <= mid && j <= right) { temp[index++] = A[i] <= A[j] ? A[i++] : A[j++]; //带等号保证归并排序的稳定性 } while (i <= mid) { temp[index++] = A[i++]; } while (j <= right) { temp[index++] = A[j++]; } for (int k = 0; k < len; k++) { A[left++] = temp[k]; }}//递归实现的归并排序(自顶向下)void MergeSortRecursion(int A[], int left, int right) { //当待排序的序列长度为1时,递归开始回溯,进行merge操作 if (left == right) return; int mid = (left + right) / 2; MergeSortRecursion(A, left, mid); MergeSortRecursion(A, mid + 1, right); Merge(A, left, mid, right);}//非递归(迭代)实现的归并排序(自底向上)void MergeSortIteration(int A[], int len){ //子数组索引,前一个为A[left...mid],后一个子数组为A[mid+1...right] int left, mid, right; //子数组的大小i初始为1,每轮翻倍 for (int i = 1; i < len; i *= 2) { left = 0; //后一个子数组存在(需要归并) while (left + i < len) { mid = left + i - 1; //后一个子数组大小可能不够 right = mid + i < len ? mid + i : len - 1; Merge(A, left, mid, right); //前一个子数组索引向后移动 left = right + 1; } }} [^_^]: 参考阅读 排序–快速排序及其优化 面试官,您要的快排 面试题:快速排序与归并排序的区别]]></content>
<categories>
<category>面试</category>
</categories>
<tags>
<tag>快速排序</tag>
<tag>归并排序</tag>
</tags>
</entry>
<entry>
<title><![CDATA[三次握手和四次挥手]]></title>
<url>%2F2018%2F08%2F18%2F%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B%E5%92%8C%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B%2F</url>
<content type="text"><![CDATA[三次握手:1、客户端向服务器端发送报文SYN=1,ACK=0;客户端进入SYN-SEND状态。2、服务端收到SYN=1,ACK=0的请求报文,向客户端返回确认报文SYN=1,ACK=1,服务端进入SYN-REVD状态。3、客户端接收确认报文,需再向服务端发送一个确认收到的报文ACK=1;客户端进入ESTABLISHED状态。 四次挥手:1、客户端发起、请求断开链接。发送报文FIN=1,当FIN=1的时候,表明此报文的发送方已经完成了数据的发送,没有新的数据要传送,并要求释放链接。客户端进入FIN-WAIT-1状态。2、 服务器收到客户端的请求断开链接的报文之后,返回确认信息。ACK=1,服务器进入CLOSE-WAIT状态。此时客户端不能给服务器发送信息报文,只能接收。3、 当服务器也没有了可以传的信息之后,给客户端发送请求结束的报文。FIN=1,ACK=1,服务器进入LAST-ACK状态。4、 客户端接收到FIN=1的报文之后,返回确认报文,ACK=1,发送完毕之后,客户端进入等待状态,等待两个时间周期。链接关闭。注意:为什么要等两个时间周期:超时重传机制客户端最后一个确认收到的ACK=1的报文如果在传输的过程中丢失,服务端没有收到确认报文,就会超时重传,重新发送FIN=1的报文,如果不等两个时间周期,重新发的FIN=1的报文客户端不会收到。 1、为什么连接的时候是三次握手,关闭的时候却是四次握手?答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,”你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。 2、为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。]]></content>
<categories>
<category>计算机网络</category>
</categories>
<tags>
<tag>三次握手</tag>
<tag>四次挥手</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Linux中添加-删除用户并设置权限Ⅱ]]></title>
<url>%2F2018%2F08%2F17%2FLinux%E4%B8%AD%E6%B7%BB%E5%8A%A0-%E5%88%A0%E9%99%A4%E7%94%A8%E6%88%B7%E5%B9%B6%E8%AE%BE%E7%BD%AE%E6%9D%83%E9%99%90%E2%85%A1%2F</url>
<content type="text"><![CDATA[在“Linux中添加/删除用户并设置权限”一文中,我们讲了如何添加及删除用户,可是后来,我们仍遇到了不少问题: 1.进入系统后命令行只有一个’$’ 这样的美元符号,而且环境变量文件已经都复制到用户主目录下,怎样才能恢复成如xxx@主机名:~$ 这样的格式呢?1vim /etc/passwd 看到自己的用户名,如:1test:x:():()::/home/test: 在后面加上/bin/bash,就行了。即:1test:x:():()::/home/test:/bin/bash 如果出现”/etc/passwd” E212: Can’t open file for writing,说明权限有问题,退出后,执行chattr -i /etc/passwd,即可更改。 2.无法使用root权限123sudo -i[sudo] password for test: test is not in the sudoers file. This incident will be reported. (1)添加sudo文件的写权限,命令是:1chmod u+w /etc/sudoers (2)编辑sudoers文件12345vim /etc/sudoers``找到这一行``root ALL=(ALL:ALL) ALL 在它下面添加:1xxx ALL=(ALL:ALL) ALL #这里的xxx是你的用户名 ps:这里说下你可以sudoers添加下面四行中任意一条即可。1234youuser ALL=(ALL) ALL%youuser ALL=(ALL) ALLyouuser ALL=(ALL) NOPASSWD: ALL%youuser ALL=(ALL) NOPASSWD: ALL 第一行:允许用户youuser执行sudo命令(需要输入密码).第二行:允许用户组youuser里面的用户执行sudo命令(需要输入密码).第三行:允许用户youuser执行sudo命令,并且在执行的时候不输入密码.第四行:允许用户组youuser里面的用户执行sudo命令,并且在执行的时候不输入密码。 (3)撤销sudoers文件写权限,命令:1chmod u-w /etc/sudoers 这样普通用户就可以使用sudo了.]]></content>
</entry>
<entry>
<title><![CDATA[Linux中添加-删除用户并设置权限]]></title>
<url>%2F2018%2F08%2F17%2FLinux%E4%B8%AD%E6%B7%BB%E5%8A%A0-%E5%88%A0%E9%99%A4%E7%94%A8%E6%88%B7%E5%B9%B6%E8%AE%BE%E7%BD%AE%E6%9D%83%E9%99%90%2F</url>
<content type="text"><![CDATA[创建建用户(在root用户下)12useradd -d /home/test -m test #增加用户test,并制定test用户的主目录为/home/testpasswd test #为test用户设置密码 更改用户相应的权限设置:123usermod -s /sbin/nologin test #限定用户test不能telnet,只能ftpusermod -s /bin/bash test #用户test恢复正常usermod -d /home/test test #更改用户test的主目录为/test 限制用户只能访问/home/test,不能访问其他路径修改/etc/vsftpd/vsftpd.conf如下:123chroot_list_enable=YES #限制访问自身目录# (default follows)chroot_list_file=/etc/vsftpd/vsftpd.chroot_list 编辑 vsftpd.chroot_list文件,将受限制的用户添加进去,每个用户名一行 改完配置文件,不要忘记重启vsftpd服务器1reboot 5.如果需要允许用户修改密码,但是又没有telnet登录系统的权限:1usermod -s /usr/bin/passwd test #用户telnet后将直接进入改密界面 6.如果要删除用户,用下面代码:1userdel -r newuser 因为需要彻底删除用户,所以加上-r的选项,在删除用户的同时一起把这个用户的宿主目录和邮件目录删除。]]></content>
</entry>
<entry>
<title><![CDATA[Linux命令提示符显示当前完整路径]]></title>
<url>%2F2018%2F08%2F16%2FLinux%E5%91%BD%E4%BB%A4%E6%8F%90%E7%A4%BA%E7%AC%A6%E6%98%BE%E7%A4%BA%E5%BD%93%E5%89%8D%E5%AE%8C%E6%95%B4%E8%B7%AF%E5%BE%84%2F</url>
<content type="text"><![CDATA[问题:linux下,命令行显示路径仅最后一个文件名,非常不方便,想显示完整路径。环境背景:linux,无root权限,可sudo(为了服务器安全,一般只给管理员root账号和密码,普通账号仅sudo权限)方法:修改环境变量PS1,vi编辑/etc/profile文件在最后加上一行语句。 命令行提示符完全显示完整的工作目录名称:export PS1=’[\u@\h $PWD]\$ ‘ 命令行提示符只列出最后一个目录:export PS1=’[\u@\h \W]$ ‘ 命令行提示符显示完整工作目录,当前用户目录会以 ~代替:export PS1=’[\u@\h \w]$ ‘修改完成后,执行: source /etc/profile 使配置生效即可。 命令释义:\u 显示当前用户账号\h 显示当前主机名\W 只显示当前路径最后一个目录\w 显示当前绝对路径(当前用户目录会以 ~代替)$PWD 显示当前全路径\$ 显示命令行’$’或者’#’符号]]></content>
</entry>
<entry>
<title><![CDATA[Hello World]]></title>
<url>%2F2018%2F07%2F25%2Fhello-world%2F</url>
<content type="text"><![CDATA[Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new "My New Post" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment]]></content>
</entry>
</search>