下一份工作主要的语言上是Golang,我自己只有Java 背景,感觉Java的万物Object思想已经入脑了。Java转Go,在这里记录一下学习过程。
代码资料:看了一遍刘丹冰的B站视频《8小时转职Golang工程师》,迅速过了一遍语法,然后跟着他后面的即时通讯系统项目开始手打,完全跟着他的代码走。
笔记风格:偏记录,最开始可能会非常琐碎,因为Java简直是我的mother tongue, 应该会像刚学英文那样,凡事都想和中文比较一下。刚开始写Go,一定会想用熟悉的Java 概念联想记忆。并且前期应该会犯很多理解错误,随着Go语言的掌握,会不断改正。
学习方法:我对自己的了解是学什么都是一个学习曲线前期平缓后期陡升的过程。我的方法可能这不是最高效的学习方法,但是以我对自己的了解,这会是最适合我的方式。
本期课件
视频 :38-即时通信系统基础server构建
代码 :
server.go
go
package main
import (
"fmt"
"net"
)
type Server struct {
Ip string
Port int
}
func NewServer(ip string, port int) *Server {
server := &Server{
Ip: ip,
Port: port,
}
return server
}
func (this *Server) Handler(conn net.Conn) {
fmt.Println("链接建立成功")
}
func (this *Server) Start() {
// socket listen
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port))
if err != nil {
fmt.Println("net.Listen error: ", err)
return
}
defer listener.Close()
for {
//accept
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener.Accept error: ", err)
continue
}
//do handler
go this.Handler(conn)
}
//close listen socket
}
main.go
go
package main
func main() {
server := NewServer("127.0.0.1", 8888)
server.Start()
}
逐行分析
-
package main是一个非常特殊的声明。是程序的入口,和Java的Main类意思差不多。Java 的一个jar包入口是指定一个含main方法的主类,但是Go 中是用这种package级别的声明实现的,这个声明会告诉Go 编译器,当前文件会编译成一个可执行的二进制程序。上面代码中server.go和main.go都是package main声明,那么它们在编译的时候则会打成一个可执行文件。另外,这个声明必须和 main 函数配合使用。编译器在看到 package main 后,会去寻找这个包下的 main 函数。
比如编译指令:
go build -o server main.go server.go,程序会找到 func main() 开始执行 -
import语句:引入fmt等包的访问权限,如果引入了包又没有使用,则编译报错,需要去掉引用。 -
NewServerstruct不像Java Class 一样有隐式的构造函数,语法上:Go 没有构造函数。但是这个方法是Go语言的习惯,用New + 结构体名的函数作为工厂函数,对struct进行实例化。该方法返回的是指针,即指向实例化的对象的地址的指针。 -
& 取地址符。Server{...}:表示创建一个 Server 类型的值(通常在栈分配空间)。&Server{...}:创建一个 Server 类型的值,并立即取出它的内存地址(通常在堆上)。 -
this对于上面两个函数,this都是局部变量名称,虽然是合法的,但不是推荐的。因为可能会产生误会,this在Java 中是一个关键字,表示当前对象的引用。Go 中this 和that都没什么区别,没有关键字魔法,就是一个变量名。但是IDE通常建议变量名不用this -
if err != nilGo 支持函数有多返回值,那么在设计中许多方法带有err 作为返回值的一部分,在调用结束后用这行代码,进行异常处理。 -
func (this *Server) Start()方法定义, (this *Server) 这一部分表示了函数的接收方,即Server 结构体,这个部分定义了结构体和方法之间的关系,即它只能被Server 对象调用。如果没有这个接收方,则说明是普通函数,不属于任何struct的函数,像Java的static 方法。 -
defer listener.Close()语句没有异常捕获处理。defer 表示在方法结束时关闭资源。如果关闭出错,错误被丢弃了,但程序正常运行。对于 net.Listener(网络监听器)来说,忽略 Close 的错误通常是安全且可以接受的。关闭的本质:关闭监听器主要是为了释放操作系统占用的端口(File Descriptor)。即使关闭时报错了,通常也是因为资源已经由于某种原因处于非正常状态。不可逆性:在 defer 中执行关闭,通常意味着整个业务逻辑已经结束。即便此时知道关闭失败了,你往往也无能为力(总不能为了关闭失败而让整个程序 Panic 吧)。 -
go 关键字开一个协程执行回调方法,主要是异步、非阻塞。在for 循环中,每一次accept会返回一个全新,独立的连接,然后对于每一个conn,开一个goroutine去执行回调方法。在 Java 的传统 IO 编程中,如果为每个连接开一个线程,当连接数达到几千个时,服务器的 CPU 就会因为频繁的上下文切换而耗尽。Go 的协程模型可以处理更多的并发连接数。
一些问题
-
Go中的package到底在做什么事?
"同目录即同包"的设计确实 Go 语言的一大特色,Go可以把同包的文件放在一起。在编译的时候,其实只要在一个package下,对于编译器来说仿佛一个大go文件。这里似乎很像PHP?执行到哪里,就把哪个PHP文件加载进来。
与之对比的是Java包名和目录名也是一一对应的,但是需要显式地import需要的源文件。Java在编译后每一个类都是一个class文件,其实编译效率是比较低的。
-
为什么有两个go文件都使用了package main声明?
因为Go 的语言特色,其实同包下最后都会揉成一个文件,那么我觉得分成不同的go文件,主要是程序员代码清晰,separation of concerns用的,好维护。
-
Go是OOP语言吗?
Java是面向对象而生的语言,Go 是具有OOP性质的"组合派"语言。
Java 的基本单元是class,Go 则是struct。后者通过结构体嵌套实现"继承"
-
Go 的指针与Java的引用
Java 是值传递,传递对象是传递的是对象引用的拷贝。Go 也是值传递,传递的是对象内存地址的指针。从这个角度上,Java和Go是一样的。Java 引用:是"逻辑地址"或"身份标签"。JVM 屏蔽了物理细节,换取了自动化管理的极致简便。引用的真正内存地址是黑盒的,JVM在GC的时候可能会给对象变更地址,只是程序员无感。
Go 指针:是"物理地址"或"内存坐标"。Go 暴露了物理细节,换取了性能调优的极致透明。
-
Go 的异常处理,如果不对error进行处理会发生什么?
Java:异常是"意外",可以选择不理它,直到它炸到顶层。或者用try-catch块包住进行处理。Go:错误是"结果"的一部分,必须像对待业务数据一样对待它,也可以透传到上层,但是如果直接忽略,编译器会报错。Go 这种异常处理方式强制程序员在"案发"的时候做处理,而不会像Java 那样如果try不及时,需要沿着堆栈找caused by
-
Go 的struct 成员和func之间似乎没有Java类中成员方法那样的约束?
Java 的语言设计中,类是具有约束性的,成员和方法都是属于类的。但是在Go 语言中,package才是约束的边界,所以在上面代码中,func和Server结构体之间非常松散,没有物理上的强制约束,但是有接收方进行逻辑耦合。Go 语言中将数据和行为分离,所以只要在同一个package内,func可以定义在任意位置。但是Go 是不支持重载的,这点和Java不同
今日学习感悟:学了其它语言才意识到JVM默默地在做了些什么,JVM,你真的,我哭死......