千锋教育高教产品研发部
Go语言是由Google公司发布的一种静态型、编译型的开源编程语言,是新时代的“C语言”。纵观这几年来的发展趋势,Go语言已经成为云计算时代重要的基础编程语言。
2007年,Google准备推出一种既不损失性能又可以降低代码复杂性的编程语言;同年9月,Rob Pike将这门编程语言正式命名为Go;2008年5月,Google全力支持Go编程语言的研发;2009年11月,Google将代码全部开源,并被评为当年的年度语言;2012年3月28日,Go发布第一个正式的稳定版本。与此同时,Go团队承诺新版本都会兼容旧版本。
Go语言的编译速度明显优于Java和C++,还拥有接近C语言的运行效率及接近PHP的开发效率。Go语言将运行效率和开发效率进行了完美的融合。Go语言支持当前所有的编程范式,包括过程式编程、面向对象编程、面向接口编程、函数式编程。开发者们可根据需求自由组合。
Go语言不使用虚拟机,Go语言的代码可以直接输出为目标平台的二进制可执行文件。Go语言拥有自己的链接器,不依赖任何系统提供的编译器和链接器。因此编译出的二进制可执行文件几乎可以运行在任何系统环境中。
Go是一种非常高效的语言,从语言层原生支持并发,使用起来非常简单。Go的并发是基于Goroutine的。Goroutine类似于线程,但并非线程,是Go面向线程的轻量级方法。创建Goroutine的成本很低,只需几千个字节的额外内存。通常一台普通的桌面主机运行上百个线程就会负载过大,同样的主机却可以运行上千甚至上万个Goroutine。Goroutine之间可以通过channel实现通信。Goroutine以及基于channel的并发性方法可最大限度地使用CPU资源。
Go拥有强大的编译检查、严格的编码规范、很强的稳定性,此外Go还提供了软件生命周期(如开发、测试、部署、维护等)的各个环节的工具,如go tool、go fmt、go test。
Go发布之后,很多公司开始用Go重构基础架构,特别是云计算公司。很多公司直接采用Go进行开发,最近热火朝天的Docker就是采用Go语言进行开发的。
(1)只有package名称为main的包可以包含main()函数。(2)一个可执行程序有且仅有一个main包。
新定义的变量是需要内存的,于是有人设计了新的算法来取代中间变量
匿名变量既不占用命名空间,也不会分配内存。
复合数据类型(派生数据类型):数组(array)、切片(slice)、映射(map)、函数(function)、结构体(struct)、通道(channel)、接口(interface)、指针(pointer)。
• 多行字符串需要使用反引号“`”,多用于内嵌源码和内嵌数据。• 在反引号中的所有代码不会被编译器识别,而只是作为字符串的一部分。
字符串中的每一个元素叫作“字符”,定义字符时使用单引号。Go语言的字符有两种
常量定义后未被使用,不会在编译时报错。
Go语言现阶段没有提供枚举,可以使用常量组模拟枚举。假设数字0、1和2分别代表未知性别、女性和男性。
iota,特殊常量值,是一个系统定义的可以被编译器修改的常量值。iota只能被用在常量的赋值中,在每一个const关键字出现时,被重置为0,然后每出现一个常量,iota所代表的数值会自动增加1。iota可以理解成常量组中常量的计数器,不论该常量的值是什么,只要有一个常量,那么iota就加1。iota可以被用作枚举值
类型别名是Go1.9版本添加的新功能。说到类型别名,无非是给类型名取一个有特殊含义的外号而已,
该语句是将StringAlias定义为string的一个别名。使用StringAlias与string等效。别名类型只会在代码中存在,编译完成时,不会有别名类型。
• 不需使用括号将条件包含起来。
• 大括号{}必须存在,即使只有一行语句。• 左括号必须在if或else的同一行。• 在if之后,条件语句之前,可以添加变量初始化语句,使用“;”进行分隔。
switch语句执行的过程自上而下,直到找到case匹配项,匹配项中无须使用break,因为Go语言中的switch默认给每个case自带break。因此匹配成功后不会向下执行其他的 case 分支,而是跳出整个 switch。可以添加fallthrough(中文含义是:贯穿),强制执行后面的case分支。fallthrough必须放在case分支的最后一行。如果它出现在中间的某个地方,编译器就会报错。
switch后的表达式可以省略,默认是switch true。
switch语句还可以被用于type switch(类型转换)来判断某个interface变量中实际存储的变量类型。
函数的功能就像榨汁机一样,帮助人们做重复的任务。函数是组织好的、可重复使用的执行特定任务的代码块。它可以提高应用程序的模块性和代码的重复利用率。Go 语言从设计上对函数进行了优化和改进,让函数使用起来更加方便。因为Go语言的函数本身可以作为值进行传递,既支持匿名函数和闭包,又能满足接口,所以 Go 语言的函数属于一等公民。
匿名函数没有函数名,只有函数体,函数可以作为一种类型被赋值给变量,匿名函数也往往以变量方式被传递。
函数 + 引用环境 = 闭包。
函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有“记忆性”。函数是编译器静态的概念,而闭包是运行期动态的概念。对象是附有行为的数据,而闭包是附有数据的行为。
(2)抽象。闭包是数据和行为的组合,这使得闭包具有较好的抽象能力。(3)简化代码。一个编程语言需要以下特性来支持闭包。
由于闭包函数“捕获”了和它在同一作用域的其他常量和变量,所以当闭包在任何地方被调用,闭包都可以使用这些常量或者变量。它不关心这些变量是否已经超出作用域,只要闭包还在使用这些变量,这些变量就依然存在。
该语法格式定义了一个接受任何数目、任何类型参数的函数。这里特殊的语法是三个点“...”,在一个变量后面加上三个点,表示从该处开始接受可变参数。
(1)在每一次调用自己时,必须是(在某种意义上)更接近于解。(2)必须有一个终止处理或计算的准则。
• 使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层,每当函数返回,栈就会减一层。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。• 使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。
指针是存储另一个变量的内存地址的变量。变量是一种使用方便的占位符,变量都指向计算机的内存地址。一个指针变量可以指向任何一个值的内存地址。
在Go语言中使用取地址符(&)来获取变量的地址,一个变量前使用&,会返回该变量的内存地址。
Go语言指针的特点如下。• Go语言指针的最大特点是:指针不能运算(不同于C语言)。• 在Go语言中如果对指针进行运算会报错。
声明指针,*T是指针变量的类型,它指向T类型的值。
号用于指定变量是一个指针。
在Go语言中,当一个指针被定义后没有分配到任何变量时,它的值为nil。nil指针也称为空指针。nil在概念上和其他语言的null、None、NULL一样,都指代零值或空值。
将基本数据类型的指针作为函数的参数,可以实现对传入数据的修改,这是因为指针作为函数的参数只是复制了一个指针,指针指向的内存没有发生改变。
指针数组:就是元素为指针类型的数组。
访问指向指针的指针变量值需要使用两个 * 号。
默认情况下,Go语言使用的是值传递,即在调用过程中不会影响到原内容数据。每次调用函数,都将实参复制一份再传递到函数中。每次都复制一份,性能会下降,但是Go 语言中使用指针和值传递配合就避免了性能降低问题,也就是通过传指针参数来解决实参复制的问题。
引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到原内容数据。严格来说Go语言只有值传递这一种传参方式,Go语言是没有引用传递的。Go语言中可以借助传指针来实现引用传递的效果。函数参数使用指针参数,传参的时候其实是复制一份指针参数,也就是复制了一份变量地址。
函数的参数如果是指针,当函数调用时,虽然参数仍然是按复制传递的,但是此时仅仅只是复制一个指针,也就是一个内存地址,这样就不用担心实参复制造成的内存浪费、时间开销、性能降低。
• 传指针使得多个函数能操作同一个对象。• 传指针更轻量级(8 bytes),只需要传内存地址。如果参数是非指针参数,那么值传递的过程中,每次在复制上面就会花费相对较多的系统开销(内存和时间)。所以要传递大的结构体的时候,用指针是一个明智的选择。Go语言中slice、map、chan类型的实现机制都类似指针,所以可以直接传递,而不必取地址后传递指针。
Go语言中所有的传参都是值传递(传值),都是一个副本。副本的内容有的是值类型(int、string、bool、array、struct属于值类型),这样在函数中就无法修改原内容数据;有的是引用类型(pointer、 slice、map、chan属于引用类型),这样就可以修改原内容数据。
在实际编程时,应尽量使用函数来提高代码的复用性,对于占用内存较大的变量应尽量使用指针来减少资源的消耗。
在Go语言中也有这样的“箱子”,就是内置容器,开发者可以将变量放在容器里一起操作。
因为数组的内存是一段连续的存储区域,所以数组的检索速度是非常快的,但是数组也有一定的缺陷,就是定义后长度不能更改。
忽略声明中数组的长度并将其替换为“…”,编译器可以找到长度。
Go语言中的数组并非引用类型,而是值类型。当它们被分配给一个新变量时,会将原始数组复制出一份分配给新变量。因此对新变量进行更改,原始数组不会有反应。
Go语言中数组的长度不可改变,但在很多应用场景中,在初始定义数组时,数组的长度并不可预知,这样的序列集合无法满足要求。Go中提供了另外一种内置类型“切片(slice)”,弥补了数组的缺陷。切片是可变长度的序列,序列中每个元素都是相同的类型。切片的语法和数组很像。从底层来看,切片引用了数组的对象。切片可以追加元素,在追加时可能使切片的容量增大。与数组相比,切片不需要设定长度,在[]中不用设定值,相对来说比较自由。切片的数据结构可理解为一个结构体,这个结构体包含了三个元素。• 指针,指向数组中切片指定的开始位置。• 长度,即切片的长度。• 容量,也就是切片开始位置到数组的最后位置的长度。
切片没有自己的任何数据。它只是底层数组的一个引用。对切片所做的任何修改都将反映在底层数组中。数组是值类型,而切片是引用类型,
当使用append()追加元素到切片时,如果容量不够(也就是(cap-len) == 0),Go就会创建一个新的内存地址来储存元素。
如果字符串涉及中文,遍历字符推荐使用rune。因为一个byte存不下一个汉语文字的unicode值。
大写英文字母的正则表达式,除了可以写成[A-Z],还可以写成[\x41-\x5A]。因为在ASCII码字典中A-Z被排在了65~90号(也就是ASCII码的第66位到第91位),换算成十六进制就是0x41-0x5A。
4)中文的正则表达式为:[\u4E00-\u9FA5]。因为中文在Unicode编码字典中排在4E00到9FA5。
Go语言采用更灵活的“结构体”替代了“类”。
结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正分配内存。因此只有在定义结构体并实例化后才能使用结构体。
结构体作为函数参数,若复制一份传递到函数中,在函数中对参数进行修改,不会影响到实际参数,证明结构体是值类型。如例7-3所示。
结构体作为函数的参数及返回值有两种形式:值传递和引用传递。
匿名结构体就是没有名字的结构体,无须通过type关键字定义就可以直接使用。
匿名字段就是在结构体中的字段没有名字,只包含一个没有字段名的类型。这些字段被称为匿名字段。
函数(function)是一段具有独立功能的代码,可以被反复多次调用,从而实现代码复用。而方法(method)是一个类的行为功能,只有该类的对象才能调用。
Go语言的方法(method)是一种作用于特定类型变量的函数。这种特定类型变量叫作接受者(receiver)。接受者的概念类似于传统面向对象语言中的this或self关键字。Go语言的接受者强调了方法具有作用对象,而函数没有作用对象。一个方法就是一个包含了接受者的函数。
接受者中的变量在命名时,官方建议使用接受者类型的第一个小写字母
动态类型语言的好处很多,Python代码写起来很快。但是缺陷也是显而易见的:错误往往要在运行时才能被发现。相反,静态类型语言往往在编译时就发现这类错误:如果某个变量的类型没有显式地声明实现了某个接口,那么,这个变量就不能用在一个要求实现了这个接口的地方。
error本质上是一个接口类型,其中包含一个Error()方法,错误值可以存储在变量中,通过函数返回。它必须是函数返回的最后一个值。
在Go语言中处理错误的方式通常是将返回的错误与nil进行比较。nil值表示没有发生错误,而非nil值表示出现错误。如果不是nil,需打印输出错误。
Go语言设计者认为,将异常与流程控制混在一起会让代码变得混乱。
bufio实现了带缓冲的I/O操作,达到高效读写。bufio包对io包下的对象Reader、Writer进行包装,分别实现了io.Reader和io.Writer接口,提供了数据缓冲功能,能够一定程度减少大块数据读写带来的开销,所以bufio比直接读写更快。
把文件读取进缓冲区之后,再读取的时候就可以避免文件系统的输出,从而提高速度;在进行写操作时,先把文件写入缓冲区,然后由缓冲区写入文件系统。
冲区的设计是为了存储多次的写入,最后一口气把缓冲区内容写入文件。当发起一次读写操作时,计算机会首先尝试从缓冲区获取数据;只有当缓冲区没有数据时,才会从数据源获取数据更新缓冲。
服务器用RSA生成公钥和私钥,把公钥放在证书里发送给客户端,私钥自己保存。客户端首先向一个权威的服务器求证证书的合法性,如果证书合法,客户端产生一段随机数,这段随机数就作为通信的密钥,称为对称密钥。这段随机数以公钥加密,然后发送到服务器,服务器用密钥解密获取对称密钥,最后,双方以对称密钥进行加密解密通信。
模板就是在写动态页面时不变的部分,服务端程序渲染可变部分生成动态网页,Go语言提供了html/template包来支持模板渲染。Go提供的html/template包对HTML模板提供了丰富的模板语言,主要用于Web应用程序。
模板中的变量通过{{.}} 来访问。{{.}} 称为管道和root。在模板文件内
Go官方提供了database包,database包下有sql/driver。该包用来定义操作数据库的接口,这保证了无论使用哪种数据库,操作方式都是相同的。但Go官方并没有提供连接数据库的driver,如果要操作数据库,还需要第三方的driver包。
匿名导入包——只导入包但是不使用包内的类型和数据,使用匿名的方式(在包路径前添加下画线“_”)导入MySQL驱动。匿名导入包与其他方式导入包一样,会让导入包编译到可执行文件中。通常来说,导入包后就能调用该包中的数据和方法。但是对于数据库操作来说,开发者不应该直接使用导入的驱动包所提供的方法,而应该使用 sql.DB对象所提供的统一的方法。因此在导入MySQL驱动时,使用了匿名导入包的方式。在导入一个数据库驱动后,该驱动会自行初始化并注册到Golang的database/sql上下文中,这样就可以通过 database/sql 包所提供的方法来访问数据库了。
sql.Open()返回的sql.DB对象是Goroutine并发安全的。sql.DB 通过数据库驱动为开发者提供管理底层数据库连接的打开和关闭操作。sql.DB 帮助开发者管理数据库连接池。正在使用的连接被标记为繁忙,用完后回到连接池等待下次使用。所以,如果开发者没有把连接释放回连接池,会导致过多连接使系统资源耗尽。sql.DB的设计目标就是作为长连接(一次连接多次数据交互)使用,不宜频繁开关。比较好的做法是,为每个不同的datastore建一个DB对象,保持这些对象打开。如果需要短连接(一次连接一次数据交互),就把DB作为参数传入function,而不要在function中开关。
并发(Concurrency)是同时处理许多个任务,实际上是把任务在不同的时间点交给处理器进行处理,在微观层面,任务不会同时运行。2.并行并行(Parallelism)是把每一个任务分配给每一个处理器独立完成,多个任务一定是同时运行
线程也叫轻量级进程,通常一个进程包含若干个线程。线程可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,比如音乐进程,可以一边查看排行榜一边听音乐,互不影响。
;进程是一个容器。线程就好比车间里的工人。一个进程可以包括多个线程,线程是容器中的工作单位。
协程(Coroutine),最初在1963年被提出,又称为微线程,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程,一个线程也可以拥有多个协程。
协程是编译器级的,进程和线程是操作系统级的。协程不被操作系统内核管理,而完全由程序控制,因此没有线程切换的开销。和多线程比,线程数量越多,协程的性能优势就越明显。协程的最大优势在于其轻量级,可以轻松创建上万个而不会导致系统资源衰竭。
Go语言中的协程叫作Goroutine。Goroutine由Go程序运行时(runtime)调度和管理,Go程序会智能地将Goroutine中的任务合理地分配给每个CPU。创建Goroutine的成本很小,每个Goroutine的堆栈只有几kb,且堆栈可以根据应用程序的需要增长和收缩。
Goroutine能并行执行,Coroutine只能顺序执行;Goroutine可在多线程环境产生,Coroutine只能发生在单线程。Coroutine程序需要主动交出控制权,系统才能获得控制权并将控制权交给其他Coroutine。
使用go关键字创建Goroutine时,被调用的函数往往没有返回值,如果有返回值也会被忽略。如果需要在Goroutine中返回数据,必须使用channel,通过channel把数据从Goroutine中作为返回值传出。
所有Goroutine在main()函数结束时会一同结束
在Go程序运行时,runtime实现了一个小型的任务调度器。此调度器的工作原理类似于操作系统调度线程,Go程序调度器可以高效地将CPU资源分配给每一个任务。在多个Goroutine的情况下,可以使用runtime.Gosched()交出控制权。
Go1.5版本之前,默认使用单核执行。Go1.5版本开始,默认执行runtime.GOMAXPROCS(逻辑CPU数量),让代码并发执行,最大效率地利用CPU。
channel即Go的通道,是协程之间的通信机制。一个channel是一条通信管道,它可以让一个协程通过它给另一个协程发送数据。每个channel都需要指定数据类型,即channel可发送数据的类型。如果使用channel发送int类型数据,可以写成chan int。数据发送的方式如同水在管道中的流动。
Go语言中提倡使用channel的方式代替共享内存。换言之,Go语言主张通过数据传递来实现共享内存,而不是通过共享内存来实现数据传递。
使用channel时要考虑发生死锁(deadlock)的可能。如果Goroutine在一个channel上发送数据,其他的Goroutine应该接收得到数据;如果没有接收,那么程序将在运行时出现死锁。如果Goroutine正在等待从channel接收数据,其他一些Goroutine将会在该channel上写入数据;如果没有写入,程序将会死锁。
发送方如果数据写入完毕,需要关闭channel,用于通知接收方数据传递完毕。通常情况是发送方主动关闭channel。接收方通过多重返回值判断channel是否关闭,如果返回值是false,则表示channel已经被关闭。往关闭的channel中写入数据会报错:panic: send on closed channel。但是可以从关闭后的channel中读取数据,返回数据的默认值和false。
NewTimer()创建一个新的计时器,它会在至少持续时间d之后将当前时间发送到其channel上。
Hash(哈希或散列)算法是IT领域非常基础也非常重要的一类算法,可以将任意长度的二进制值(明文)映射为较短的固定长度的二进制值(Hash值),并且不同的明文很难映射为相同的Hash值。Hash值在应用中又被称为数字指纹(fingerprint)或数字摘要(digest)、消息摘要。
目前MD5和SHA-1已经不够安全,推荐至少使用SHA-256算法。比特币系统就是使用SHA-256算法。
SHA256('1'),加密后长度为256位,32字节。
Hash是将目标文本转换成具有相同长度的、不可逆的杂凑字符串,而加密(Encrypt)是将目标文本转换成具有不同长度的、可逆的密文。
对称加密(也叫私钥加密算法)指加密和解密使用相同密钥的加密算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄露密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信的安全性至关重要。对称加密算法的优点是计算量小,加密速度快,加密效率高。不足之处是,参与方需要提前持有密钥,一旦有人泄露则系统安全性被破坏;另外,如何在不安全通道中提前分发密钥也是个问题,密钥管理非常困难。
高级加密标准算法(Advanced Encryption Standard,AES)
非对称加密又叫作公开密钥加密(Public Key Cryptography)或公钥加密,指加密和解密使用不同密钥的加密算法。公钥加密需要两个密钥,一个是公开密钥,另一个是私有密钥;一个用于加密,另一个用于解密。RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的所有密码攻击,已被ISO推荐为公钥数据加密标准。其他常见的公钥加密算法有:ElGamal、背包算法、Rabin(RSA的特例)、椭圆曲线加密算法(EllipticCurve Cryptography,ECC)。
非对称加密的缺点是加解密速度远远慢于对称加密,在某些极端情况下,需要的时间甚至是对称加密的1000倍。非对称加密与对称加密的对比如表13.2所示。
公钥的作用是加密消息和验证签名,而私钥的作用是解密消息和进行数字签名
RSA算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,想要对其乘积进行因式分解极其困难,因此可以将乘积公开作为加密密钥。
Base64是一种基于64个可打印字符来表示二进制数据的编码方式。Base64使用了26个小写字母、26个大写字母、10个数字以及2个符号(如“+”和“/”),用于在电子邮件这样的基于文本的媒介中传输二进制数据。Base64通常用于编码邮件中的附件。
Beego可以用来快速开发API、Web、后端服务等各种应用,主要设计灵感来源于Tornado、Sinatra、Flask这三个框架,但是结合了Go本身的一些特性(interface、struct继承等)。该框架采用模块封装,使用简单,容易学习,方便技术开发者快速学习并进行实际开发。对程序员来说,Beego掌握起来非常简单,只需要关注业务逻辑实现即可,框架自动为项目需求提供不同的模块功能。
智能化:Beego框架封装了路由模块,支持智能路由、智能监控,并可以监控内存消耗、CPU使用以及Goroutine的运行状况,方便开发者对线上应用进行监控分析。
高性能:Beego采用Go原生的HTTP请求、Goroutine的并发效率应付大流量Web应用和API应用。
model:model层可以解释为实体层或者数据层,在model层中实现用户和业务数据的处理。和数据库表相关的一些主要操作会在这一目录中实现,执行后的结果数据返回给控制器层。向数据库中插入新数据、删除数据库表数据、修改某一条数据、从数据库中查询业务数据等都是在 model层实现。
routers:该层是路由层。所谓路由就是分发的意思,当前端浏览器发送一个HTTP请求到后台Web项目时,程序必须能够根据浏览器的请求URL进行不同的业务处理,从接收到前端请求到判断执行具体的业务逻辑的过程的工作,就由routers来实现。
views:views中存放的就是应用中存放html模板页面的目录。所谓模板,就是页面框架和布局已经使用html写好了,只需要在进行访问和展示时,将获取到的数据动态填充到页面中,能够提高渲染效率。因此,使用模板是非常常见的一种方式。
1)后台:Beego框架。(2)前端:Vue框架。(3)数据库:MySQL数据库+Redis数据库。
ORM(Object Relationship Mapping),通常翻译为对象关系映射。ORM模式是一种解决面向对象与关系数据库互不匹配现象的技术。ORM中间件能在任何一个应用的业务逻辑层和数据库层之间充当桥梁。一
这个世界没有天才,有的只是“刻意练习”。所谓天才,就是练习次数最多的人。