概述
对于语言设计之争, 唯一需要牢记的一句话是: 如果把 C 变成 C++, 那么 C 就消失了。
Go 是一个轻量级的简洁的支持并发的现代语言, 可以用于探索性个人项目, 这是我想学这门语言的主要原因。 对于有一定编程经验的人来说, 学习一种新语言的方式是, 先概览下语言特性, 然后编写一个中等规模的程序, 尽可能地运用到大部分重要特性。
Go 的主要语言特性:
- 静态类型 + 类型推导
- 简化的控制结构: if , for , switch
- 可命名的多返回值的函数
- 内置容器支持: 数组、Slice、Map
- 简化的指针与引用
- 简化的对象特性: 结构体与非侵入式接口
- 内置的并发支持: go, Goroutine & Channel
- 反射、匿名函数与闭包;
- 便捷地 CGo 交互;
- 内置 GC ;
- 包管理。
针对熟练 Java 开发者,快速上手 Go 语言。
学习新语言的最佳方式,就是找一个实际项目来研究,再写一个实用程序来练手。
Go 语法
1. 变量名在前,变量类型在后;变量名(方法名、函数名、类名)采用驼峰形式。比如:
type JsonResp struct {
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
2. 语句结束不用分号。
3. 条件语句不用括号。
if runMode == engine_common.RUNMODE_TRACE {
processor = append(processor, sendMongo.NewSendDetectToMongo(w.mongoDao))
}
4. 强制类型转换
edgeName := p["type"].(string)
var a int32 = 10
var b int64 = int64(a)
var a int =10
var b *int =&a
var c *int64 = (*int64)(unsafe.Pointer(b))
5. 定义枚举常量,可以用于 switch 语句;常量名全大写。
const {
}
const (
RUNMODE_TRACE = "trace" // 联调模式,日志输出至kafka、日志、控制台
RUNMODE_DEBUG = "debug" // 本地调试模式,日志输出至日志、控制台
RUNMODE_RELEASE = "release" // 发布模式,用于正式发布,仅输出至kafka
RUNMODE_MOCK = "mock" // 测试模式,用于测试相关流程,输出到go channel
)
switch runMode {
case engine_common.RUNMODE_TRACE:
processor = append(processor, aggregator.NewIncidentMsgAggregatorWithSink(w.config.AggregationTime,
sink.NewKafkaSink(w.incidentKafka),
sink.NewLogSink()))
case engine_common.RUNMODE_RELEASE:
processor = append(processor, aggregator.NewIncidentMsgAggregatorWithSink(w.config.AggregationTime,
sink.NewKafkaSink(w.incidentKafka)))
case engine_common.RUNMODE_DEBUG:
a.AddSink(sink.NewConsoleSink())
a.AddSink(sink.NewLogSink())
case engine_common.RUNMODE_MOCK:
a.AddSink(sink.NewLogSink())
a.AddSink(sink.NewChannelSink(param.MockChannel))
}
6. 定义通用对象
type Any interface{}
type List []Any
7. 遍历列表
Path []map[string]common.Any `json:"path"`
for _, p := range ctx.Detect.Path {
if p["type"] == "DETECT_POINT" {
detect_id = p["dst_id"].(string)
}
}
8. 定义映射
var m = map[int]string{
ERROR_SUCCESS: "ok",
ERROR_FAIL: "fail",
ERROR_INVALID_PATH: "请求路径参数不合法",
ERROR_INVALID_QUERY: "请求查询参数错误",
ERROR_RESULT_NOT_EXIST: "没有找到符合条件的记录",
}
// 多重 map
elements map[string]map[string]common.Any
// 创建 map
edge_prop := make(map[string]common.Any)
9. 定义函数
左括号必须与 func 在同一行。函数名首字母大写。
func 方法名(方法签名)返回对象 {
}
func GetMsg(code int) string {
msg, ok := m[code]
if ok {
return msg
}
return m[ERROR_SUCCESS]
}
10. 定义类、构造器、方法
类名首字母大写。
type 类名或结构名 struct {
}
func New类名() *类名 {
return &类名{属性名:属性值}
}
func (m *类名) 方法名(方法签名) 返回值类型 {
}
示例:
// EngineConfig 配置项
type EngineConfig struct {
GraphEngineComponent // 组件配置
GraphEngineRule // 本地规则配置,初次从本地加载的数据
CustomConfig `toml:"custom_config"` // 自定义配置,控制参数
LogConfig `toml:"log_config"` // 日志配置
ServiceConfig `toml:"service_config"` // 服务配置
MonitorService `toml:"monitor_service"` // 監控
// 合并本地和云端配置后的规则配置参数,云端参数优先
graphEngineUpdatedRule atomic.Value
// 单例nacos客户端,Go 保留指针,但不能对指针运算,只能引用
nacosClient *nacos.Client
}
type CenterServer struct {
servers map[string] ipc.Server players []*Player
rooms []*Room
mutex sync.RWMutex
}
func NewCenterServer() *CenterServer {
servers := make(map[string] ipc.Server) players := make([]*Player, 0)
return &CenterServer{servers:servers, players:players}
}
# 定义类的方法, (m *EngineConfig) 是对象的隐藏指针。
func (m *EngineConfig) GetIncidentMaxRange() int64 {
timeSlice := m.graphEngineUpdatedRule.Load().(GraphEngineRule).TimeSliceData
if timeSlice.MaxRange.Duration == 0 {
return int64(time.Minute * 5 / time.Millisecond) // 默认间隔5min
} else {
return int64(timeSlice.MaxRange.Duration / time.Millisecond)
}
}
12. 定义接口
只要实现这个接口的方法,就能赋值给这个接口的引用。即无论是黑猫还是白狗,能抓住老鼠的就是鼠的天敌。
type IWorkFlowFactory interface {
GetName() string
Clean() bool
Create(name string, params interface{}) (IWorkFlow, error)
}
type WorkFlowFactory struct {
config *config.EngineConfig
globalContext context.Context
mongoDao *mongo.MongoDao // 仅trace模式时会用到
// code...
}
func (w *WorkFlowFactory) GetName() string {
return "WorkFlowFactory"
}
func (w *WorkFlowFactory) Clean() bool {
// code...
log.Info("workflow factory clean successful")
return true
}
func (w *WorkFlowFactory) Create(name string, params interface{}) (define.IWorkFlow, error) {
// code
}
13. 类型自动推导: a := 5
14. 错误处理
if param, ok := params.(*service_define.StartParam); !ok {
return nil, errors.New("Input type error ")
}
nebulaDao := dao.NewNebulaDaoWithSpace(w.nebulaClient, nebulaSpace)
if nil == nebulaDao {
return nil, errors.New("Init nebula dao error ")
}
15. 格式化
fmt.Errorf("detect.path[%d].dst_id=%v missing in detect.element, detectId=%v", j, p["dst_id"].(string), ctx.Detect.Id)
编程实战
下面的程序用于计算一个目录下所有文件或目录的大小。
$ go build -o dirsize dirsize_module.go time2.go
$ dirsize
dirsize_module.go
package main
import (
"fmt"
// "log"
"os"
"sort"
"time"
)
import "C"
type ShortFileInfo struct {
fileName string
size int64
}
type FileInfoSlice []ShortFileInfo
func (fileInfo *ShortFileInfo) Desc() string {
return fmt.Sprintf("{%s:%s}", fileInfo.fileName, readableSize(fileInfo.size))
}
func (fileInfos FileInfoSlice) Len() int {
return len(fileInfos)
}
func (fileInfos FileInfoSlice) Swap(i, j int) {
fileInfos[i], fileInfos[j] = fileInfos[j], fileInfos[i]
}
func (fileInfos FileInfoSlice) Less(i, j int) bool {
return fileInfos[i].size > fileInfos[j].size
}
type IOError struct {
msg string
err error
}
func (ioe IOError) Error() string {
return "Error: " + ioe.msg + "\nCause: " + ioe.err.Error()
}
func produceFiles(dirName string) (fileInfos []os.FileInfo, e error) {
path, err := os.Open(dirName)
if err != nil {
//log.Fatal(err)
return nil , IOError{"failed to open " + dirName, err}
}
defer path.Close()
fileInfos, readerr := path.Readdir(0)
if readerr != nil {
//log.Fatal(readerr)
return nil, IOError{"failed to read " + dirName, readerr}
}
return fileInfos, nil
}
func procFile(fileInfo os.FileInfo, baseDirName string, channelBuffer chan ShortFileInfo) {
var filesize int64
fileName := fileInfo.Name()
if fileInfo.IsDir() {
filesize = totalFilesizeInDir(fileInfo, baseDirName)
} else {
filesize = fileInfo.Size()
}
shortFileInfo := ShortFileInfo{fileName, filesize}
fmt.Println(time.Now().String() + " store: " + shortFileInfo.Desc())
channelBuffer <- shortFileInfo
}
func totalFilesizeInDir(fileInfo os.FileInfo, baseDirName string) int64 {
var filesize int64 = 0
fileInfos, _ := produceFiles(baseDirName + "/" + fileInfo.Name())
for _, subfileInfo := range fileInfos {
if subfileInfo.IsDir() {
filesize += totalFilesizeInDir(subfileInfo, baseDirName+"/"+fileInfo.Name())
} else {
filesize += subfileInfo.Size()
}
}
return filesize
}
func sleep(ns int) {
time.Sleep(time.Duration(time.Second) * time.Duration(ns))
}
const (
B int64 = 1
KB int64 = 1024
MB int64 = 1024 * 1024
GB int64 = 1024 * 1024 * 1024
TB int64 = 1024 * 1024 * 1024 * 1024
)
const formatF string = "%8.4f"
func readableSize(sizeInBytes int64) string {
switch {
case B <= sizeInBytes && sizeInBytes < KB:
return fmt.Sprintf("%dB", sizeInBytes)
case KB <= sizeInBytes && sizeInBytes < MB:
return fmt.Sprintf(formatF+"KB", float64(sizeInBytes)/float64(KB))
case MB <= sizeInBytes && sizeInBytes < GB:
return fmt.Sprintf(formatF+"MB", float64(sizeInBytes)/float64(MB))
case GB <= sizeInBytes && sizeInBytes < TB:
return fmt.Sprintf(formatF+"GB", float64(sizeInBytes)/float64(GB))
case TB <= sizeInBytes:
return fmt.Sprintf(formatF+"TB", float64(sizeInBytes)/float64(TB))
default:
return "0"
}
}
//export mainProc
func mainProc() {
start_1 := time.Now().Unix()
baseDirName := "/home/lovesqcc"
//baseDirName := "/notExist"
//baseDirName := "/"
fileList, err := produceFiles(baseDirName)
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
fileNumber := len(fileList)
channelBuffer := make(chan ShortFileInfo, fileNumber)
fileInfoMap := make(map[string]int64, fileNumber)
for _, fileInfo := range fileList {
go procFile(fileInfo, baseDirName, channelBuffer)
}
var fileInfos = make([]ShortFileInfo, fileNumber+1)
timeout := make(chan int, 1)
go func() {
secs := 3
sleep(secs)
timeout <- secs
}()
for count := 0; count <= fileNumber; {
select {
case fileInfo := <-channelBuffer:
fmt.Println(time.Now().String() + " fetch: " + fileInfo.Desc())
fileInfoMap[fileInfo.fileName] = fileInfo.size
fileInfos[count] = fileInfo
count++
case secs := <- timeout:
fmt.Printf("%d s timout ! Exit Loop\n", secs)
os.Exit(1)
default:
if count == fileNumber {
close(channelBuffer)
}
fmt.Println("Waiting for data ...")
}
}
sort.Sort(FileInfoSlice(fileInfos))
for _, fileInfo := range FileInfoSlice(fileInfos) {
fmt.Println("File " + fileInfo.fileName + " : " + readableSize(fileInfo.size))
}
var totalSize int64
totalSize = 0
for _, filesize := range fileInfoMap {
totalSize += filesize
}
fmt.Printf("Total size in %s:%dB %s\n", baseDirName, totalSize, readableSize(totalSize))
end_1 := time.Now().Unix()
fmt.Printf("start=%d, end=%d\n",start_1, end_1)
fmt.Printf("Time cost: %dms\n", (end_1-start_1)*1000)
}
time2.go
package main
/*
#include<stdio.h>
#include<time.h>
extern int mainProc();
int printTimeCost() {
printf("exec printTimeCost");
time_t start = 0, end = 0;
long duration = 0;
start = time(NULL);
mainProc();
end = time(NULL);
duration = (end - start) *1000;
return duration;
}
*/
import "C"
import "fmt"
func main() {
fmt.Printf("cost: %dms\n", C.int(C.printTimeCost()))
}
参考资料
- 《Go语言编程》 许式伟著。
- Go 轻松学: https://github.com/jemygraw/TechDoc
- Go 实例学习: https://gobyexample.com/
- Go 并发之美: http://www.cnblogs.com/yuxingfirst/archive/2012/11/28/2792366.html
- Go 文档: https://go-zh.org/doc/
- C-Go 互操作 : http://tonybai.com/2012/09/26/interoperability-between-go-and-c/
- Go 标准库: http://studygolang.com/pkgdoc
- Why Go is Not Good: http://yager.io/programming/go.html