假设我们需要实例化一个MySQL对象,在GO语言中,可以使用下面两种方式
func NewMySQL(a TypeA,b TypeB,c TypeC) MySQL
在GO语言中,函数是没有默认值的,这就意味着,假设NewMySQL函数有10个参数,所有的使用方必须传递满这是个参数,如果这勉强还能接受
那么,假设不值10个参数,后果可想而知。可以说这种办法几乎不可取,函数签名随着参数的个数变化不断变化
Type Params{ a TypeA b TypeB c TypeC } func NewMySQL(p Params) MySQL
这种方式比第一种好一点了,至少看起来是不用传递多个参数,函数签名也不用发生改变。这里我们把MySQL的实例化条件复杂一点,结构体层级化,看下使用方视角
opt := Params{ Endpoint: { IP: "xxxx", Port: "xxx", .... }, Timeout:{ ReadTimeout: "xxx", WriteTimeout: "xxx", ConnectTimeout: "xxx", .... }, .... //更多陌生的选项 } obj := new NewMySQL(opt);
这种方式对使用者不友好,创建对象时需要知道的细节太多,其次,代码的可读性也差。当然还有一个更致命的缺点,默认值问题
在GO里面变量声明的时候就等于初始值(int是0,string是"",bool为false),举例:当接受到的参数为0时候,不确定是用户有意传递的0还是用户没有传递结构体变量导致的0,程序很难做出判断。
建造者(Builder)模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成,现在我们来分析其基本结构和实现方法。
建造者(Builder)模式的主要角色如下。
假设一个场景,在一个基本的MVC框架中,视图层承担着渲染数据的职责,假如要实现一个框架的视图层,必须满足
View
:func render(typ string, data interface{}) { switch typ { case "json": bytedata, _ := json.Marshal(data) fmt.Println(string(bytedata)) case "xml": bytedata, _ := xml.Marshal(data) fmt.Println(string(bytedata)) //...继续延续其他类型 default: panic("type err") } }
这样做代码比较简单,但是,这样的实现存在很多问题。
扩展
和维护
,如果再添加一个excel类型的输出,switch分支将变得老长老长针对接口编程
而不是针对实现编程。因为这样的视图层在我们的前辈也一样会碰到,只不过他们把这种问题解决掉了,留给了我们后人一些很经典的解决问题的设计模式
。接下面,使用设计模式中的工厂模式
解决这个问题工厂模式主要是为创建对象提供了接口。工厂模式按照《Java与模式》中的提法分为三类:
其实严格意义上来说,这并不算一种设计模式,或者可以说这是一种很好的封装方式。比如,可以解决我们上面的问题,我们可以做出以下优化
变化
的动画抽到一个地方,编写一个专门管理视单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。
许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在数据库连接中,我们只希望进程内所有的访问DB都是通过一个数据库连接进行的。
单例模式第一种实现方式
// 单例模式 package design import "fmt" type singleton struct { Name string } var instance *singleton func GetInstance() *singleton { if instance == nil { fmt.Println("init") //这里每实例化一次,输出init instance = &singleton{} } return instance }
这种实现方式在没有并发的情况下是正常工作的
package main import ( "learn/design" ) func main() { //串行执行正常 design.GetInstance() //输出init design.GetInstance() //不输出init }
但是一旦出现并发的情况,这种实现方式是就不能正常工作了
package main import ( "learn/design" "sync" ) func main() { //并发不正常 var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() design.GetInstance() //每一次,均输出init }() } wg.Wait() }
上面实现的第一种方式在并发情况下并不能正常work,这里使用once方式支持并发执行一次,一般比较常用的就是这种方式了
// 单例模式 package design import ( "fmt" "sync" ) type singleton struct { Name string } var instance *singleton va
对URL保留字符不进行编码直接作为参数传递
这一类错误在于没有对RFC中规定的保留字符进行编码,而错误的认为只有中文才需要编码。RFC中保留的字符也不能作为参数在URL中直接传递,需要进行编码,否者将会出现意想不到的结果。
部分转换规则如下:
空格 | ! | # | $ | % | + | @ | : | = | ? |
---|---|---|---|---|---|---|---|---|---|
%20 | %21 | %23 | %24 | %25 | %2B | %40 | %3A | %3D | %3F |
两次urldecode导致+号变成空格
1. 生成一个有+号的字符串'abc+d',然后使用urlencode编码
<?php
$str = 'abc+d';//希望传递的字符串
$param = urlencode($str);//采用urlencode编码
$url = "http://127.0.0.1:80001/test.php?param="$param;//生成访问链接
生成的url打印出来是
http://127.0.0.1:8001/test.php?param=abc%2Bd
2. 使用浏览器访问url,写一个程序获取这个参数
<?php
$param = urldecode($_GET['param']);
var_dump($param);
结果收到的参数是'abc d',可以发现,+号被替换成了空格
错误原因:在使用浏览器访问url链接的时候,浏览器会自动帮我们进行urldecode,这个时候php接收到的已经是'abc +d',如果再使用PHP进行urldecode一次,那么+号会被解析成空格
对url编码和解码使用不用版本的RFC
这一类错误经常出现在不同系统间的交互中,同样也是+号的问题。在RFC3896协议中,空格编码为%20,而在,在PHP中有两个函数实现。
RFC | 编码函数 | 解码函数 | 空格编码 | +号解码 |
---|---|---|---|---|
RFC 3986 | rawurlencode | rawurldecode | '+' | ' |
问题:输入一个整型数组,数据元素有正数也有负数,求元素组合成连续子数组之和最大的子数组。
描述:输入的数组为1, -2, 3, 10, -4, 7, 2, -5,最大和的连续子数组为3, 10, -4, 7, 2,其最大和为18。
第一种解法最简单,最暴力,最容易理解的一种方法。这种解法的思想就是,从第一个元素开始,然后和后面的所有元素组合开有没有可能形成最大值。代码很简单
#include<stdio.h>
#define MAX_LEN 8
int main(){
int arr[MAX_LEN] = {1, -2, 3, 10, -4, 7, 2, -5};
int i,j;
int sum = 0;
for(i=0; i < MAX_LEN; i++){
int _tmpSum = arr[i];
for(j=i+1; j < MAX_LEN; j++){
_tmpSum += arr[j];
if(_tmpSum > sum){
sum = _tmpSum;
}
}
}
printf("%d", sum);
}
这种算法的时间复杂度为O(n^2)。所以这种方法虽然简单,单一般不采用。
计算机领域经常使用的一种求解思想,分而治之。想想二分查找,一半一半得淘汰,这样效率会提高很多。我们同样也可以用这种思路求解最大子数组问题。假定我们要寻找数组A[low .. high]的最大子数组,使用分治技术我们将A划分为两个等规模子数组。首先找到数组A的中央位置,比如mid,然后分解成两个子数组A[low .. mid]和A[mid+1 .. high]。这样如果存在一个最大的子元素系列A[i .. j]那么它一定满足下面三种情况之一
1. 完全位于子数组A[low .. mid]中,此时low<=i<=j<=mid
2. 完全位于子数组A[mid+1
写这篇文章的缘由是最近在关注RPC框架序列化的一些原理。但是在安装Protobuf的时候,发现网上的教程都太老了,加上目前Protobuf官方已经支持PHP了,不再需要使用第三方插件了。
在PRC框架中,数据的传输发生在客户端和服务端,而我们知道基于TCP协议最终传输的是二进制的0/1序列。所以,基于TCP传输协议的RPC服务自然也需要将数据结构转换成二进制,和二进制转换成数据结构的功能。所以,原则上,基于网络的数据传输只能传输二进制表示的字符串
序列化:将数据结构或对象转换成二进制串的过程
反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
但是,传输的二进制序列是完全没有意义的,除非有一套解析二进制串的协议。没错,这个协议可以就是目前我们大家熟知的xml,json协议。当然。除了这两者,还有其他的的序列化和反序列化协议。
XML是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点。XML历史悠久,其1.0版本早在1998年就形成标准,并被广泛使用至今。XML的格式如下
<note>
<to>George</to>
<from>John</from>
<msg>Don't forget the meeting!</msg>
</note>
可以看出这种序列化协议的优点是可读性和易调试行。但是这种协议的缺点也很明显:额外空间开销大,序列化之后的数据量剧增。
JSON是一种轻量级的数据交换格式。采用完全独立于编程语言的文本格式来存储和表示数据。如果你跟浏览器Web应用打交道的话,那么JSON一定是应用最广泛的,它的数据格式如下
{
"to":"George",
"from":"John",
"msg":"Don't forget the meeting!"
}
这种序列化协议有很大的优势:
1. 这样表示非常符合工程师对对象的理解,尤其是js工程师
2. 和xml一样,可读性强
3. 和xml相比,更加节省空间,解析速度更快
DNS(Domain Name System,域名系统)是整个互联网的核心组件,负责域名到IP地址的转换工作。比如说,你要访问www.baidu.com
就得先通过DNS服务将www.baidu.com
翻译成IP地址115.239.211.112
。有的人可能会想,这个不是很简单吗,写一个TXT文本文件保持映射关系不就可以了吗。没错,早期的DNS就是这样的。
在20世纪70年代,当时的计算机网络只有几百台主机。他们确实是采用一个名为HOSTS.TXT
的文件容纳所有的主机信息,这个文件包含了主机名字——地址的映射关系(你可以想象Linux的/etc/hosts)。这样维持了一段时间后,随着网络规模的爆发,暴露了以下问题
1. 流量和负载的爆发增长,一台主机根本扛不住
2. 名字冲突问题,当时注册名字是比较随意的
3. 一致性问题,当时变更是通过邮件形式通知的,然后变更配置中心
毫无疑问,HOSTS.TXT
方案最终以失败告终。但是当时的人继续探索,他们希望创造出一个系统,以解决单一主机表系统本身所固有的问题。所以今天我们看到了及其复杂的DNS系统
被上述HOSTS.TXT
文件坑惨了之后,DNS被设计成了一个分布式数据库,它允许整个数据库的各个部分进行本地控制。同时,整个网络也能通过客户-服务器的方式访问每个部分的数据。首先为了解决名字冲突,名字被设计成了树状结构,所以我们现在的域名看起来是非常规范的。
DNS分布式数据库是以域名为索引的,每个域名实际上就是一棵很大的逆向树中的路径。这棵逆向树就称为域名空间。这棵树的结构如下:
域名空间:这整个树就构成了域名空间,当然,现实生活中可能还有子树
域名:树中的一个节点到根节点对的顺序连接表示域名。并且用“.”分割路径上出现的名字。比如map
节点对应的域名为map.baidu.com
(我们忽略了路上的根的名字)
域:一个域就是这个域名空间(整棵树)中的一个子树。域的名字就是子树根节点到树根的域名。图中的灰色圆框就是baidu.com
域。百度掌管着这个域
这样设计一个很大的好处就是域名分散管理,像ba
快速排序的基本思想就是分治法,就是将一个大的数组按照某一中间值分成两个子集,一组是每个元素都大于中间值,另一组是每个元素都小于中间值,然后递归调用该过程,最后可完成排序。步骤:
1、先从数列中取出一个数作为基准数,可以是第一个元素或最后一个元素。
2、分两个子集,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3、再对左右子集间重复第二步,直到各子集只有一个数,排序结束
整个过程可以用下图的竹竿按从低到高的顺序排序,首先我们找出第一根杆子为标准,剩下的高杆移到右边,低杆移到左边(不用管两遍的顺序,只要符合低左高右即可)。然后针对左边再次进行同样的动作,最后就可以保证左边是从低到高排列的,再针对右边也进行同样的动作(图中没有画),最终所有的竹竿都是有序的。
这个图可以看出,只要不断递归左右两个子集,最终递归结果就是整个数组有序。所以快速排序的核心是找到一个快速的左右子集划分方法,以及找到一个合适的基准元素,这两个因素影响了你写的快速排序到底快不快。
前面说到,找到划分方法和基准元素,快速排序基本上就实现了。
如果要实现以某一个元素为基准,将数组划分成左右两个子集。方法其实有很多种,有的可能时间复杂度高,有的可能耗费空间多。
《啊哈,算法》里介绍的一种方法,这种方法的效率比较高。这种方法就是假设有两个探针,分别是左探针和右探针。一开始分别指向数组的首地址和末地址。然后右探针开始向中间靠拢,直到发现一个小于基准值的元素,然后左探针也向中间靠拢,直到发现一个大于基准值的元素,这是交换两个探针的数组,直到两个探针相遇结束。整个过程可以用下图表示,仔细观察下基准元素4是如何插入到中间的
对照上图,很容易就可以写出相应的算法,实现上面过程的函数是partition函数,需要注意的是,看上面的图,最终和基准元素交换的是i和j相遇的地方。
#include<stdio.h>
void partition(int arr[], int left, int right);
void swap(int *a, int *b);
v
堆排序,毫无以为,是在堆的数据结构上进行排序,注意,我们这里谈论的堆是二叉堆。所以首先了解二叉堆,然后在二叉堆的基础上排序是很容易的一件事情。
二叉堆的结构就是完全二叉树或者近似完全二叉树。在二叉树的结构上保持一定的有序性就是二叉堆,根据这种有序性的不同,分为最大堆
和最小堆
最大堆:任一节点的关键字是其子树所有节点的最大值
最小堆:任一节点的关键字是其子树所有节点的最小值
举一个例子,下面这两个都是最大堆
我们知道,二叉树的存储结构最适合的还是每个节点都是一个具有左右孩子和自身数据的结构体
struct Node{
int data;
int *left;
int *right;
}
但是,完全二叉树不需要这样表示,完全二叉树可以直接用数组表示,这样更节省空间。在一个完全二叉树中,只要知道一个节点的数组下标(i),就能知道它的父节点(i/2),左孩子(2*i+1),右孩子(2*i+2),比如下图中,字母表示二叉堆中节点数值,底下一条内存条表示二叉堆的实际存储情况。
二叉堆的存储形式即如上图所示,只不过和满二叉树不同的地方在于,二叉堆的数字是保持一定的大小关系。
因为最大堆和最小堆的原理是一样的,我们举最大堆的例子。建立二叉堆我们使用的方法是先建立一个满二叉树,在满二叉树的基础上我们不断的调整节点,保证根节点大于其他子节点即可,最后就可以形成一个二叉堆。
建立一个满二叉树很简单,申明一个数字即可
int arr[] = {19, 20, 40, 56, 3, 9, 50};
这时候形成的二叉树如图所示
注意,这个时候的二叉树还不能形成一个二叉堆,因为我们发现最大的数字56不在根节点中。
剩下很关键的步骤,就是不断的调整节点,做法是:首先调整最右边的子树,即只看40,9,50三个元素,根节点40与它的左右孩子9,50对比找出其中的较大者,发现是50,所以50跟40位置互换,这样最右子树就满足二叉堆的条件了。然后这个过程左子树也递归进行,最后就会形成二叉堆。整个递归过程可以用下面的图表示
注意
笔者在学习这几个名词的时候,也是被百度到的相关文章迷惑。涉及到的主要名词包括
1. CGI协议
2. CGI脚本
3. PHP-CGI
4. FastCGI协议
5. PHP-FPM
要真正理解这些名词,如果我们硬生生的解释,也很难记住。我们可以从web服务器开发的历程来看,看看他们为什么会出现,以及他们解决了什么问题。
早些年的服务器很简单,你访问一个网站,比如www.helloworld.com
,网站只返回你一个静态HTML页面。为了简单起见,我们假设网站值返回hello标题,这样流程可以用下图表示
这时候server是采用C语言编写的,为了说明问题,最简化一个服务器脚本。基本上一个简的C脚本就可以了,把这个服务器脚本命名为hello_server.c
,代码可以到github上下载
在Linux环境中使用gcc编译hello_server.c
vagrant@kfz:~$ gcc hello_world.c
然后采用curl
工具测试
vagrant@kfz:~$ curl "127.0.0.1:8887"
GET / HTTP/1.1
User-Agent: curl/7.49.1
Host: 127.0.0.1:8887
Accept: */*
Hello World
这里我们的重点不在HTTP响应的格式上,所以我们直接输出了Hello World
,如果是请求静态页面,我们也是一样的思路,读取静态文件的内容然后返还给客户端。
思考:当Web时代越来越火爆,我们希望Web服务器有更多的功能,比如写博客,聊天,等等。同时,越来越多的不同领域的开发者想在web时代大显身手。很多服务器开发者发现有了以下缺点
#服务器功能方面
Web服务器功能会随着这种逻辑的增长而增长,服务器会变的不专一
#语言支持方便
越来越多的PHP,Python开发者想开发服务端程序,编写更多的功能,发现自己的语言无从下手
既然Web服务器想做的专一,但又要支持Web的飞速发展,同时还想让其他语言开发者也