go 里的多态
面向对象中的 “多态” 指的同一个行为具有多种不同表现形式或形态的能力,具体是指一个类实例(对象)的相同方法在不同情形有不同表现形式。
多态也使得不同内部结构的对象可以共享相同的外部接口,也就是都是一套外部模板,内部实际是什么,只要符合规格就可以。在 Go 语言中,多态是通过接口来实现的:
type AnimalSounder interface {MakeDNA()
}func MakeSomeDNA(animalSounder AnimalSounder) { // 参数是AnimalSounder接口类型animalSounder.MakeDNA()
}
java 和go 的区别
1. 基础对比
特性 | Go | Java |
---|---|---|
语言定位 | 系统编程 + 高并发服务 | 企业级应用 + 跨平台开发 |
类型系统 | 静态类型,简洁 | 静态类型,严格 |
编译方式 | 直接编译成机器码(无 JVM) | 编译成字节码,运行在 JVM 上 |
运行效率 | 接近 C,启动快 | 有 JVM 开销,启动稍慢 |
语法复杂度 | 简洁,语法元素少 | 语法丰富,关键字和特性多 |
2. 面向对象
特性 | Go | Java |
---|---|---|
类 (class) | 没有 class,用 struct | 有 class |
继承 | 不支持继承,强调组合 | 支持继承(单继承,多接口) |
多态 | 接口(隐式实现) | 接口(显式实现)+ 继承 |
封装 | 通过首字母大小写控制可见性 | public / private / protected |
3. 并发模型
特性 | Go | Java |
---|---|---|
并发模型 | goroutine + channel(CSP 模型) | 线程 + 线程池(共享内存+锁) |
线程开销 | goroutine 超轻量(KB 级) | Java 线程较重(MB 级) |
原生支持 | 内置 go 关键字和 channel |
通过 Thread 、ExecutorService |
并发编程难度 | 简单直观 | 需要锁、同步工具类 |
👉 Go 天生为高并发服务优化,而 Java 并发需要更多代码。
4. 内存 & GC
特性 | Go | Java |
---|---|---|
内存管理 | 自动 GC,无手动释放 | 自动 GC |
GC 特性 | 追求低延迟(适合服务端) | 高吞吐(可调 GC 策略) |
指针 | 有指针,但安全(不能运算) | 没有指针,完全引用 |
5. 开发生态
特性 | Go | Java |
---|---|---|
标准库 | 内置网络、并发支持强 | 丰富的 API,覆盖广 |
框架 | 偏轻量(gin, gRPC) | 大量成熟框架(Spring, Hibernate) |
社区 | 云原生 / 微服务强 | 企业级应用主流 |
6. 应用场景
- Go:高并发服务器、微服务、云原生、容器(Docker、K8s)、区块链、DevOps 工具
- Java:企业级系统、金融、电商、大型 Web 应用、Android
7. 总结一句话
- Go:快、轻量、并发强 → 更适合 云原生、高并发服务
- Java:成熟、完整、生态丰富 → 更适合 企业级应用、大型系统
go 里基本数据类型包括哪些
Go 的基本数据类型可以分为几大类:
1. 布尔类型
-
bool
- 取值:
true
或false
- 默认值:
false
- 取值:
2. 数值类型
整型
-
有符号整数:
int8
,int16
,int32
,int64
,int
-
无符号整数:
uint8
,uint16
,uint32
,uint64
,uint
-
特殊整型:
byte
→uint8
的别名rune
→int32
的别名,表示 Unicode 码点uintptr
→ 足够存放指针的无符号整数类型
浮点型
float32
,float64
复数类型
complex64
(由两个float32
组成)complex128
(由两个float64
组成)
3. 字符串类型
-
string
- 由 UTF-8 编码的字节序列组成
- 不可变,一旦创建不能修改
4. 派生/复合类型(常用但不是最基础的)
- 数组 (
[n]T
) - 切片 (
[]T
) - 字典(map) (
map[K]V
) - 结构体(struct)
- 指针(*T)
- 通道(chan T)
- 接口(interface)
- 函数类型(func)
go 语言遍历字符串的方式
1. 按 字节(byte) 遍历
s := "Hello, 世界"for i := 0; i < len(s); i++ {fmt.Printf("index: %d, byte: %x\n", i, s[i])
}
s[i]
取到的是 单个字节 (uint8
)。- 如果字符串里有中文、Emoji 这种多字节字符,遍历出来的只是 UTF-8 编码的每个字节。
2. 按 字符(rune,Unicode 码点) 遍历
s := "Hello, 世界"for i, r := range s {fmt.Printf("index: %d, rune: %c\n", i, r)
}
range
会自动把字符串解码成 Unicode 码点 (rune
,本质是int32
)。i
是该字符在原始字符串里的 字节索引。r
是解码后的 Unicode 码点。- 推荐这种方式来正确处理中文、Emoji。
3. 转换成 []rune
后遍历
s := "Hello, 世界"
runes := []rune(s)for i, r := range runes {fmt.Printf("index: %d, rune: %c\n", i, r)
}
- 和
range
类似,但能按 字符索引(而不是字节索引)访问。 - 更方便做字符串切割、反转等操作。
byte 和 rune 类型的区别
1. 类型定义
-
byte
:uint8
的别名,本质上是一个 无符号的 8 位整数,范围是0~255
。
常用于表示 原始数据 或者 单个 ASCII 字符。 -
rune
:int32
的别名,本质上是一个 有符号的 32 位整数,范围是-2^31 ~ 2^31-1
。
通常用来表示 一个 Unicode 码点(Unicode Code Point)。
2. 在字符串中的作用
Go 的字符串底层是 只读的 []byte
。
- 如果用
byte
遍历字符串,拿到的是 UTF-8 编码后的字节序列,不一定能正确对应到“字符”。 - 如果用
rune
遍历字符串,Go 会自动解码 UTF-8,拿到的是 完整的 Unicode 字符(码点)。
3. 示例代码
package mainimport ("fmt"
)func main() {s := "你好Go"// 按 byte 遍历fmt.Println("按 byte 遍历:")for i := 0; i < len(s); i++ {fmt.Printf("%c ", s[i])}fmt.Println()// 按 rune 遍历fmt.Println("按 rune 遍历:")for _, r := range s {fmt.Printf("%c ", r)}
}
输出:
按 byte 遍历:
ä ½ ¥ å ¥½ G o
按 rune 遍历:
你 好 G o
byte
遍历时,UTF-8 编码的多字节字符(中文)被拆开,打印结果是乱码。rune
遍历时,能正确得到你
、好
。
4. 总结对比表
特性 | byte |
rune |
---|---|---|
本质 | uint8 |
int32 |
表示范围 | 0–255 | -2^31 ~ 2^31-1(足够放下所有 Unicode) |
适用场景 | 原始字节操作,ASCII 字符 | Unicode 字符,文本处理 |
遍历字符串 | 拆开 UTF-8 字节,可能乱码 | 正确解码 UTF-8,得到字符 |
GMP 代表什么 好,这个问题直接切到 Go 调度器的核心 👍
在 Go 的 GMP 调度模型里:
1. 三个核心概念
-
G(Goroutine)
- 就是 Go 里的协程。
- 每个 G 包含执行栈、状态、任务函数等信息。
- G 只是运行的任务单元,本身不会执行。
-
M(Machine)
- 操作系统的线程(内核线程)。
- 真正执行代码的实体。
- M 在执行时必须绑定一个 P,才能运行 goroutine。
-
P(Processor)
- 调度器中的逻辑处理器,负责调度 G 到 M 上运行。
- 每个 P 维护一个本地队列,存放等待执行的 G。
- P 的数量决定了 Go 程序的最大并行度(默认等于 CPU 核数,可用
GOMAXPROCS
设置)。
GMP 调度流程
✅ 调度流程
-
创建 goroutine
- 执行
go func()
时,Go 运行时会把一个新的 G 对象放入 当前 P 的本地队列。
- 执行
-
创建 Goroutine:
- 当通过 go func() 创建新的 Goroutine 时,G 会首先被加入到与当前 P 关联的本地队列中。
- 如果 P 的本地队列已满(超过 256 个 G),则新的 G 会被放入全局队列。
-
** 调度与执行 **:
- 每个 M 与一个 P 绑定,M 从 P 的本地队列中获取一个 G 来执行。
- 如果 P 的本地队列为空,M 会尝试从全局队列或其他 P 的本地队列中偷取(work stealing)任务执行。
-
系统调用与阻塞:
- 当 G 执行过程中发生阻塞或系统调用,M 也会被阻塞。这时,P 会解绑当前的 M,并尝试寻找或创建新的 M 来继续执行其他 G。阻塞结束后,原来的 M 会尝试重新绑定一个 P 继续执行。