Golang基础 For Javaer

Posted by Steven on 2021-07-06
Estimated Reading Time 20 Minutes
Words 5.5k In Total
Viewed Times

主要针对从Java转Go的人,比较适用(Javaer转Go有一点小的门槛)

Go的中文api文档:https://studygolang.com/pkgdoc

Go中文社区网站:https://studygolang.com/

一、Go和Java的区别

1、关于Java

Java的用途

首先我们来回顾下Java的主要用途和应用场景:

  • 用途一:服务器后端系统开发(web后端、微服务后端支付系统、业务系统、管理后台,各种后台交互的接口服务)。

  • 用途二:大数据框架的底层实现和Java的API支持。(Hadoop)。

  • 用途三:其它中间件的底层开发。(Tomcat、RocketMq、Hbase、Kafka(部分)、SpringCloud,Dubbo…)。

Java的优势

  • .做服务端系统性能高。

  • .有虚拟机,跨平台。

  • .功能强大,支持的类库多,生态圈类库多,开发框架和工具更易找。

  • .市场占有率高,约60%的中国程序员都是做Java相关的工作。

2、关于Go

Go的出生原因

1
2
3
Go语言是Google内部公司大佬开发的,主要起因于Google公司有大量的C程序项目,但是开发起来效率太低,维护成本高,于是就开发了Go语言来提高效率,而且性能只是差一点。

(Go是2007年开始研发,2009推出发布)

3、宏观看Go与Java的差异

Go语言与Java的差异之处:

  • .无虚拟机,不跨平台(这里的平台指操作系统)(可以运行多个平台,每个平台打不同的二进制程序包),需要打包编译成对应服务器操作系统版本(windows/linux)的可执行程序(比如windows是exe)。(注:说go跨平台的是指32位和64位相同操作系统之间的跨平台)
  • .因为Go程序直接打包成操作系统可执行的文件,没有虚拟机在中间转换的一层,所以理论上执行效率会更高(理论上更高,实际情况需具体分析)
  • .相比Java的语言和代码编写风格,Go更简洁,可以用更少的代码实现同样的功能。
  • .Go语言底层也是C实现的,又做了高并发的设计(Java出生时(1995)还没有多核cpu,所以他的并发支持后来添加上去的,Go(2009)出生时已经有了多核cpu的电脑,它在设计语言时就考虑了充分利用多核cpu(英特尔2005首次推出多核)的性能),所以性能高,高并发的支持(高并发支持其中指的一个就是充分利用多核cpu的性能资源,比如go程序默认使用所有cpu(除非自己设置使用多少))也好。
  • .天然的适用一些特定系统的开发,比如区块链类系统(如以太坊底层系统、以太坊上层应用程序),云计算和容器(Docker,K8s底层都是go开发的)开发的(大公司自研运维管理项目也大多是用go做底层的开发),网络编程(类似于java的Netty)。

4、Go和Java的语言类型区别

计算机编程语言按照运行的方式可以分为编译型编程语言和解释型编译语言。

我来举一个例子,你要教别人一门沟通交流的语言,比如英语。

编译型的教的方式就是录(这里的录相当于计算机中把程序编译成二进制可执行文件)一个视频课程,语音课程,把每一句英语发音录下来,这样学生学的时候只要播放你的录音,然后跟着读就行,你只需要录制一次,学生就可以无数次听。

解释性的教的方式就是你亲自到学生家里给他补习,你当面教他,你读(读相当于每次执行都重新用解释器解释一遍)一句他学一句,

这样的话,你想要教他一句你必须就得先读一句,每次教都得重新一遍一遍的读。

这两种教学方式还有一个差别,你录(编译)视频语音教他,你录的英语他就只能学英语,空间环境一变,他现在要去日本,要学日语,你的视频语音教程因为已经录好了,是英语类型(英语类型类比操作系统类型)的,所以,你就得再录一套日语的语音教程。

而现场教他,你也会日语的话,你只需要读(读相当于解释器解释)日语给他听,让他学就行了,是不用考虑语言环境(操作系统类型环境)不同的问题的。

现在我们再来看编程语言,我们的程序执行有两种方式,一种是编译成操作系统可执行的二进制可执行程序,这样相当于编译一次,之后每次执行都不用再编译了,但是因为不同操作系统对于二进制文件的执行规范不同,不同的操作系统你要编译成不同的可执行文件。

解释型语言就是多了一个解释器,解释器我们可以类比为一个老师,你执行一行代码我们类比为学一句话的读音,解释器解释一句,就是老师先读一句,你跟着才能读一句,也就是解释器每解释一行代码为可运行的代码,操作系统执行一行代码,这样的话每次执行都需要解释器重新解释一遍,执行几次就得解释几次。

Go是编译型的语言,运行在不同的平台需要打包成不同操作系统类型下的可执行文件。

Java是半编译,半解释型语言。编译是指他的代码都会编译成class类型的文件,class类型的文件只需要编译一次,可以在不同的操作系统的Java虚拟机上执行 ,半解释是指在Java虚拟机中,他还是需要一句一句的将class的二进制代码解释成对应操作系统可执行的代码。

5、Go语言目前的主要应用场景

1
2
3
4
5
6
7
8
9
1、和Java一样,Go语言最多的应用场景就是服务器后端系统的开发,包括Web后端,微服务后端接口。

2、Go非常适用需要高性能高并发的网络编程,这里的网络编程是指不需要界面,底层只是用Socket相互传输数据的系统,类似于Java中Netty的用途。

3、一些云计算容器,比如Docker,K8s,底层就是Go语言开发的,也可以用做底层自研运维项目的开发。

4、一些游戏系统的开发,可以用Go语言。

5、区块链的一些底层软件和一些应用软件。(区块链程序的第一开发语言)

6、Go和Java微观对比

1、GoPath和Java的ClassPath

我们先来看看关于Java的classpath:

在我们的开发环境中,一个web程序(war包)有一个classpath,这个classpath在IDEA的开发工具中目录体现为src目录和resource目录,实际上在真正的war包中他定位的是指WEB-INF下的classes文件夹下的资源(比如class文件)。

我们编译后的文件都放在classpath(类路径)下。我们多个项目程序会有多个classpath目录。

在Go语言中,GoPath在同一系统上的同一用户,一般规定只有一个,无论这个用户创建多少个go项目,都只有一个GoPath,并且这些项目都放在GoPath下的src目录下。

GoPath下有三个目录:

1.bin (用于存放项目编译后的可执行文件)

2.pkg (用于存放类库文件,比如.a结尾的包模块)

3.src (用于存放项目代码源文件)

注意:当我们在windows上开发Go程序时,需要新建一个文件夹(文件夹名任意)作为GOPATH的文件目录,在其中新建三个文件夹分别是:bin,pkg,src。如果是在集成开发工具上开发的话,需要在设置中把GOPATH路径设置为你自定义的那个文件夹,之后产生的文件和相关内容都会在其中。

如果是在linux上想跑测试开发执行go程序,需要在/etc/profile添加名为GOPATH的环境变量,目录设置好自己新建的。

例如:全局用户设置GOPATH环境变量

1
2
3
4
5
vi /etc/profile
#添加如下 目录可以灵活修改
export GOPATH=/pub/go/gopath
//立即刷新环境变量生效
source /etc/profile

单用户设置GOPATH环境变量

1
2
3
4
5
6
vi   ~/.bash_profile

#添加如下 目录可以自己灵活修改
export GOPATH=/home/user/local/soft/go/gopath
//立即刷新环境变量生效
source vi ~/.bash_profile

注意:这是在linux上开发go程序才需要的,如果只是生产运行程序的话是不需要任何东西的,直接运行二进制可执行程序包即可,他所有的依赖全部打进包中了。

如果是在windows下的cmd,dos窗口运行相关的go命令和程序,则需要在windows的【此电脑】–>【右键】–>【属性】–>【高级系统设置】–>【环境变量】-【新建一个系统变量】–>【变量名为GOPATH,路径为你自己指定的自定义文件夹】(如果是在IDEA中开发,不需要在此配置环境变量,只需要在IDEA中配置好GOPATH的目录设置即可)

2、Go与Java的文件结构对比

Go文件结构模板
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
//主程序必须是写成main包名
package main

//导入别的类库
import "fmt"

//全局常量定义
const num = 10

//全局变量定义
var name string = "li_ming"

//类型定义
type P struct {

}

//初始化函数
func init() {

}

//main函数:程序入口
func main() {
fmt.Printf("Hello World!!!");
}
Java文件结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//包名
package my_package;

//导入包中的类
import java.io.*;

public Class MainTest{
//main方法:程序入口
public void static main(String[] args) {

}
}
//people类
Class People {
//成员变量
public String name;
public int age;

//成员方法
public void doSomething() {

}

}

7、Go和Java常用包的对比

Go中文API文档地址:
https://studygolang.com/pkgdoc

1
2
3
4
5
6
7
8
9
10

Go Java

IO流操作: bufio/os java.lang.io
字符串操作: strings java.lang.String
容器 container(heap/list/ring) java.lang.Collection
锁 sync juc
时间 time java.time/java.lang.Date
算数操作 math java.math
底层Unsafe unsafe unsafe类

8、Go的常用基础数据类型和Java的基础数据类型对比

1、go中的常用基础数据类型有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1.布尔型:关键字【bool】: true   false
2.有符号整形:头一位是代表正负
int 默认整形 4或8字节 32位或64位
int8 1字节 8位
int16 2字节 16位
int32 4字节 32位
in64 8字节 64位
【int是32还是64位取决于操作系统的位数,现在电脑一般都是64位的了,所以一般都是64位】
3.无符号整形
uint 4或8字节 32位或64位
uint8 1字节 8位
uint16 2字节 16位
uint32 4字节 32位
uint64 8字节 64位
4.浮点型
注:go语言没有float类型,只有float32和float64。
float32 32位浮点数
float64 64位浮点数
5.字符串 string
6. byte 等同uint8,只是类似于一个别名的东西
rune 等同int32 只是一个别名,强调表示编码概念对应的数字

2、go中派生数据类型有:

1
2
3
4
5
6
7
8
9
注:这里简单列举一下
指针 Pointer
数组 Array[]
结构体 struct
进程管道: channel
函数 func
切片 slice
接口 interface
哈希 map

9、Go和Java的常量对比

go中的常量和java中的常量含义有一个本质的区别:
go中的常量是指在编译期间就能确定的量(数据),
而java中的常量是指被赋值一次后就不能修改的量(数据)。
所以两者不一样,因为Java中的常量也是JVM跑起来后赋值的,只不过不允许更改;
go的常量在编译后就确实是什么数值了。

1).go的常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

package main

import(
//包含print函数
"fmt"
)
func main() {
//const 常量名 常量类型 = 常量值 显示推断类型
const name string = "const_li_ming"
//隐式推断类型
const name2 ="const_xiao_hong"
fmt.Println("name = ",name)
fmt.Println("name2 = ",name2)
}

2).Java的常量

1
2
3
4
5
6
7
public class MyTest {
//【访问修饰符】 【静态修饰符】final修饰符 常量类型 常量名 = 常量值;
public static final String TAG = "A"; //一般设置为static静态
public static void main(String[] args) {
System.out.println("tag= "+TAG);
}
}

10、Go与Java的赋值对比

1、go的赋值

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
//Go方法内的赋值符号可以用  := ,也可以用 =,方法外只能用 = 。
//例如:
package main

import(
//包含print函数
"fmt"
)

//方法外只能用 = 赋值
var my_name string = "my_name"
var my_name2 = "my_name2"
//my_name3:="my_name3" 不在方法内,错误

func main() {
fmt.Println("name = ",my_name)
fmt.Println("name2 = ",my_name2)
}
//Go支持多变量同时赋值:
package main

import(
//包含print函数
"fmt"
)

func main() {
//多变量同时赋值
var name,name2 = "li_ming","xiao_hong"
fmt.Println("name = ",name)
fmt.Println("name2 = ",name2)
}

//Go的丢弃赋值
package main

import(
//包含print函数
"fmt"
)

func main() {
//丢弃赋值 把 1和2丢弃 只取3
//在必须一次取两个以上的值的场景下,又不想要其中一个值的时候使用,比如从map中取key,value
var _,_,num = 1,2,3
fmt.Println("num = ",num)
}

2.java的赋值

1
2
3
4
5
6
7
8
9
public class MyTest {
public static void main(String[] args) {
//直接用 = 赋值
String name = "li_ming";
int i = 10;
System.out.println("name ="+name);
System.out.println("i ="+i);
}
}

11、Go和Java的访问权限设置区别

首先我们来回忆一下,Java的权限访问修饰符有哪些?

public 全局可见

protected 继承相关的类可见

default 同包可见

private 私有的,本类可见

关于Java中的访问权限修饰符,是用于修饰变量,方法,类的,被修饰的对象被不同的访问权限修饰符修饰后,其它程序代码要想访问它,必须在规定的访问范围内才可以,比如同包,同类,父子类,全局均可访问。

那么,Go中的访问权限设置又有什么区别呢?

要理解这个问题,首先我们要来看一下一个Go程序的程序文件组织结构是什么样子的?

一个可运行的编译后的Go程序,必须有一个入口,程序从入口开始执行,这个入口必须是main包,并且从main包的main函数开始执行。

但是,为了开发的效率和管理开发任务的协调简单化,对于代码质量的可复用,可扩展等特性的要求,我们一般采用面向对象的,文件分模块式的开发。

比如,我是一个游戏程序,我的main函数启动后,首先要启动UI界面,那么关于UI界面相关的代码我们一般会专门分出一个模块去开发,然后这个模块有很多个程序文件,这里关于UI模块比如有3个文件,a.go,b.go,c.go,那么我们在实际当中会建一个以ui为名的包文件夹,然后把a.go,b.go,c.go全部放到ui这个包文件夹下,然后因为这个包没有main包,没有main函数,所以它打出来的一个程序文件就是以.a结尾的工具包,类似于Java中的jar包,工具包文件名为 ui.a。

参考如下:

1
2
3
4
5
6
7
----com.tencent.mygame.ui

------------------------------------a.go

------------------------------------b.go

------------------------------------c.go

a.go文件如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
//这里的ui,也就是package后面的名称尽量和包文件夹的名称一致,不一致也可以
package ui

//相关方法和业务

func main() {

}
//启动游戏UI
func StartGameUI() {

}

这里需要注意一个点,在程序中的 package后面的 ui包名可以和文件夹com.tencent.mygame.ui中最后一层的ui文件夹名称不一致,

我们一般按规范写是要求写一致的,不一致时的区别如下:

我们把ui.a打包完毕后,我们就可以在别的程序中用import导入这个包模块 ,然后使用其中的内容了。

上面两个ui不同之处在于,在我们import 的代码后面,需要写的模块名称是在 ${gopath}/src/下的文件夹名,也就是com.tencent.mygame.ui中的ui。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
//游戏主程序
package main

//这里的ui是com.tencent.mygame.ui的最后一层文件夹名
import "ui"

//相关方法和业务

func main() {
//这里的ui不是文件夹名,而是之前a.go程序中package后面写的包名
ui.StartGameUI()
}

接下来进入主题,我们的go语言关于访问修饰符的是指的限制什么权限,以及如何实现?

我们之前可以看出来,实战中的go程序是有一个main程序import很多其它包模块,每个模块实现对应的功能,最后统一在main程序中组合来完成整个软件程序,那么有一些其它模块的函数和变量,我只想在本程序文件中调用,不想被其它程序import能调用到,如何实现?

import后是否能调用对应包中的对象(变量,结构体,函数之类的)就是go关于访问权限的定义,import后,可以访问,说明是开启了访问权限,不可以访问,是说明关闭了其它程序访问的权限。

在go中,为了遵循实现简洁,快速的原则,用默认的规范来规定访问权限设置。

默认规范是:某种类型(包括变量,结构体,函数,类型等)的名称定义首字母大写就是在其它包可以访问,首字母非大写,就是只能在自己的程序中访问。

这样我们就能理解为什么导入fmt包后,他的PrintF函数的首字母P是大写的。

参照如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package ui

import "fmt"

func main() {
//这里的P是大写
//所有调用别的包下的函数,都是首字母大写
fmt.Printf("aa")
}
//这里的Person的首字母P也是表示外部程序导入该包后可以使用此Person类
type Person struct{

}
//这里的D同上
var Data string = "li_ming"

12、Go与Java程序文件的后缀名对比

1
2
3
4
5
6
7
8
9
10
Java的编译文件是.class结尾,多个.class打成的一个可执行文件是.jar结尾,.jar不能直接在windows和linux上执行,得用java命令在JVM中执行。

Go语言的程序文件后缀为.go,有main包main函数的,.go文件打包成二进制对应操作系统的可执行程序,如windows上的.exe结尾的可执行程序。

Java的类库会以.jar结尾,Go语言非main包没有main函数的程序编译打包会打成一个类库,以.a结尾,也就是说Go语言的类库以.a结尾。

Go的类库如下:
包名.a
my_util.a
注:my_util是最顶层文件夹名,里面包含着一个个程序文件。

二、指针和取地址符&

Java中,T t =new T(); 这里的t就是一个指针,根据这个t就可以找到T对象,只不过在Java中,没有办法输出这个地址,但是Go中不一样

Go中有指针描述符 *,取地址符&

1、取地址符&

Go语言中的这个符号就是取地址符(没有其他含义),而在C++中即是引用,同时也是取地址符

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
var i int = 8
fmt.Println(&i)
fmt.Println(i)
}

//打印:
0xc000018080
8

2、指针

C语言的指针只要是计算机相关专业的伙伴都知道,C指针比较繁琐的地方在于 指针运算 。比如:指针加一就直接索引了数组的下一个元素了,但是 Go语言里面是没有指针运算 的。

Go Vs Java

1、指针

Go的指针是只读

1
2
3
4
5
6
7
8
var i int = 8	

var pi *int //申明一个类型,这个类型不是int类型,而是指向int类型指针的这种类型 可以认为pi里面装了个内存地址,这个地址指向内存只能中的int类型
pi = &i
fmt.Println(pi)

//打印
0xc000018080

所以说

指针 是一个值,这个值,这个值就是一个地址

在Java类型中,是没有一种引用类型是指向基础类型变量的,比如int,如果想用就要用Integer包装类型

对于Go语言来说,指针可以指向任何类型

那Go中的指针到底有什么用呢?很多时候是用来传递参数的,就是往函数里面穿参数,只有一种方式,就是传值。不像java中传值、传引用

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
import "fmt"

type P struct {
name string
}

func m(p P) {
p.name = "lisi"
}

func m2(p *P) {
p.name = "lisi"
}

func main() {

var pp = P{name: "zhangsan"}
m(pp)
fmt.Println(pp)
//对于JAVA来说,这里的pp的name就会是lisi,传递的是引用
//但是对于Go来说 结果为{zhangsan}
//Go 中的这种传递,相当于把这个对象整个拷贝一份,原来的还是不动
m2(&pp)

fmt.Println(pp)
}

*的另外一种用法:解引用

*后面跟的是一个指针,这样就可以访问到指针对应的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import "fmt"


func f(v *int) {
*v = 8 //将指针指向的值直接改成8

}

func main() {

m := 0
f(&m)
fmt.Println("M:", m)
}

3、函数


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !