前言:
总的来说,Golang具备高级语言的很多特性,虽然语法层面和Java有些不同,但是编程逻辑是一致的。
简单来说,Golang也具备类型转换、字符串操作、数组、Map、文件及IO、反射、接口、包、线程同步、阻塞队列等常用功能。不同点在于:
1)Go保留了指针,而且结构体还是值类型而非引用类型,传参时要注意;Java虽然没有指针,但是引用类型就相当于指针,这样做对编程来说非常友好,而且也很安全,但代价是需要复杂的JVM来支持。还有一款追求极致性能和安全性的语言:Rust,它不需要复杂的VM,但是它需要复杂的语法呀(手动囧 ╯▽╰)。Golang属于原始的形态,它唯一的优势的不需要复杂的VM,和C语言算是有些共性。
2)Go的异常处理与Java的方式不一样,个人觉得有好有坏,try-catch是正统的、健壮的实现、全能的选手,但没有一定功力的人用起来会觉得别扭(实际上我也比较讨厌声明式异常,总是要让我try-catch会让程序很难看,但作为一个老司机,我很少用声明式异常,更多的是用运行时异常和各种异常拦截器),Go将error作为函数的结果返回,是个小心思,想绕过声明式异常,但是始终绕不过运行时异常,而且判断 if err==nil {...} else {...} 跟 try {...} catch(e) {..} 没有本质区别。
3)包管理,Golang依赖于文件夹(相当于Package=private、public),而Java支持文件和文件夹作为多级Package,甚至可以文件套文件(类中类),而不需要文件夹,更灵活、更好用。
Anyway,虽然Golang的语法和生态库还不尽如人意,但是它链接了以前C语言那一块的市场和资源,既可以做系统底层的工具开发,还可以做Web类业务级应用。而且还能在一定程度上跨平台,还具备优异的性能和不需要VM打包后独立运行的能力,还具备C语言不具备的很多高级语言的特性,这已经算是一种巨大的突破!!
我已经很多年没有碰C/C++了,但是C/C++发展这么多年,那是一块巨大的宝库,弃之可惜。既然Golang能够链接它们,也能搞一搞系统层面的东西,那就需要抓住。同时也看了Mozilla、微软、Linux社区大力支持的Rust语言,从语法层面可见,Rust所推崇的高效、可靠是名副其实,但是“兼顾开发效率”只是相对于C语言说的——其语法较为复杂,开发效率完全不如Python、Java,也不如Golang。作为编程老手,掌握Rust并不难(需要一段时间熟练),但种种原因,我还是选择Golang,Golang现在的社区要比Rust大10倍以上。
正文:
1、类型转换
1)一个未知类型(interface{})如何转换成map型数组?实际类型:[]map[interface {}]interface {}
分析:
方法1:用到interface assertion方法,语法为 obj.(type)强制类型转换,例如p, ok := t.(bool),p, ok := t.(int64),转换和判断可以一次完成:
if s, ok := command.(string); ok {
args, err = parseCommand(s)
if err != nil {
return nil, err
}
} else if a, ok := command.([]string); ok {
args = a
}
首先转换成数组:[]interface{},再将数组元素转换成:map[interface {}]interface {}
config := viper.Get("programs")
arr := config.([]interface{})
for _, elem := range arr {
fmt.Printf("元素的反射类型是%v\n", reflect.TypeOf(elem))
val, ok := elem.(map[interface{}]interface{})
if ok {
fmt.Printf("转换后类型%T \n", val, val)
}
}
方法2:如果interface{}包含多种类型,则用到switch type来判断:
switch reflect.TypeOf(origin).Kind() {
case reflect.Slice, reflect.Array:
s := reflect.ValueOf(origin)
for i := 0; i < s.Len(); i++ {
fmt.Println(s.Index(i))
}
case reflect.String:
s := reflect.ValueOf(origin)
fmt.Println(s.String(), "I am a string type variable.")
case reflect.Int:
s := reflect.ValueOf(origin)
t := s.Int()
fmt.Println(t, " I am a int type variable.")
}
参见 https://blog.csdn.net/qq_18293213/article/details/103722973
2、格式化字符
示例如下:
package main
import (
"fmt"
"os"
)
type point struct {
x, y int
}
func main() {
// Go提供了几种打印格式,用来格式化一般的Go值,例如
// 下面的%v打印了一个point结构体的对象的值
p := point{1, 2}
fmt.Printf("%v\n", p)
// 如果所格式化的值是一个结构体对象,那么`%+v`的格式化输出
// 将包括结构体的成员名称和值
fmt.Printf("%+v\n", p)
// `%#v`格式化输出将输出一个值的Go语法表示方式。
fmt.Printf("%#v\n", p)
// 使用`%T`来输出一个值的数据类型
fmt.Printf("%T\n", p)
// 格式化布尔型变量
fmt.Printf("%t\n", true)
// 有很多的方式可以格式化整型,使用`%d`是一种
// 标准的以10进制来输出整型的方式
fmt.Printf("%d\n", 123)
// 这种方式输出整型的二进制表示方式
fmt.Printf("%b\n", 14)
// 这里打印出该整型数值所对应的字符
fmt.Printf("%c\n", 33)
// 使用`%x`输出一个值的16进制表示方式
fmt.Printf("%x\n", 456)
// 浮点型数值也有几种格式化方法。最基本的一种是`%f`
fmt.Printf("%f\n", 78.9)
// `%e`和`%E`使用科学计数法来输出整型
fmt.Printf("%e\n", 123400000.0)
fmt.Printf("%E\n", 123400000.0)
// 使用`%s`输出基本的字符串
fmt.Printf("%s\n", "\"string\"")
// 输出像Go源码中那样带双引号的字符串,需使用`%q`
fmt.Printf("%q\n", "\"string\"")
// `%x`以16进制输出字符串,每个字符串的字节用两个字符输出
fmt.Printf("%x\n", "hex this")
// 使用`%p`输出一个指针的值
fmt.Printf("%p\n", &p)
// 当输出数字的时候,经常需要去控制输出的宽度和精度。
// 可以使用一个位于%后面的数字来控制输出的宽度,默认
// 情况下输出是右对齐的,左边加上空格
fmt.Printf("|%6d|%6d|\n", 12, 345)
// 你也可以指定浮点数的输出宽度,同时你还可以指定浮点数
// 的输出精度
fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45)
// To left-justify, use the `-` flag.
fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45)
// 你也可以指定输出字符串的宽度来保证它们输出对齐。默认
// 情况下,输出是右对齐的
fmt.Printf("|%6s|%6s|\n", "foo", "b")
// 为了使用左对齐你可以在宽度之前加上`-`号
fmt.Printf("|%-6s|%-6s|\n", "foo", "b")
// `Printf`函数的输出是输出到命令行`os.Stdout`的,你
// 可以用`Sprintf`来将格式化后的字符串赋值给一个变量
s := fmt.Sprintf("a %s", "string")
fmt.Println(s)
// 你也可以使用`Fprintf`来将格式化后的值输出到`io.Writers`
fmt.Fprintf(os.Stderr, "an %s\n", "error")
}
3、数组后面追加数组
config.Programs = append(config.Programs, cProgtmp.Programs...)
4、数值指针赋值
声明:retryTimes *int32
赋值 retryTimes: new(int32)
5、将对象2的值合并到对象1(常用于配置合并、数据合并)
方法如下:
func copy(c1, c2 interface{}) (*interface{}, error) {
v1 := reflect.ValueOf(c1) //初始化为c1保管的具体值的v1
v2 := reflect.ValueOf(c2) //初始化为c2保管的具体值的v2
v1_elem := reflect.ValueOf(&c1).Elem() //返回 c1 指针保管的值
for i := 0; i < v1.NumField(); i++ {
field := v1.Field(i) //返回结构体的第i个字段
field2 := v2.Field(i) //返回结构体的第i个字段
//field.Interface() 当前持有的值
//reflect.Zero 根据类型获取对应的 零值
//这个必须调用 Interface 方法 否则为 reflect.Value 构造体的对比 而不是两个值的对比
//这个地方不要用等号去对比 因为golang 切片类型是不支持 对比的
if reflect.DeepEqual(field.Interface(), reflect.Zero(field.Type()).Interface()) {
//如果第一个构造体某个字段对应类型的默认值
if !reflect.DeepEqual(field2.Interface(), reflect.Zero(field2.Type()).Interface()) {
//如果第二个构造体 这个字段不为空
if !v1_elem.Field(i).CanSet() { //如果不可以设置值 直接返回
fmt.Println("not set value")
return nil, errors.New("can not set value")
}
v1_elem.Field(i).Set(field2) //设置值
}
}
}
return &c1, nil
}
6、for循环时,如何改变数组对象的值
示例如下,必须要用数组下标引用才行:
for k, section := range cfg.Programs {
section.Command = cmd
section.ProcessName = procName
section.ProcessNum = i
section.NumprocsStart = i - 1
cfg.Programs[k] = section
cfg.Programs[k].Name = "New Name"
}
7、异常堆栈信息
func errors.Wrap(err error, message string) error
Wrap returns an error annotating err with a stack trace at the point Wrap is called, and the supplied message. If err is nil, Wrap returns nil.
示例:errors.Wrap(err, "failed to read config")
当前时间
秒:time.Now().Unix()
毫秒:time.Now().UnixNano() / 1e6
秒 转time对象:
func time.Unix(sec int64, nsec int64) time.Time
Unix returns the local Time corresponding to the given Unix time, sec seconds and nsec nanoseconds since January 1, 1970 UTC. It is valid to pass nsec outside the range [0, 999999999]. Not all sec values have a corresponding time value. One such value is 1<<63-1 (the largest int64 value).
示例:
毫秒转time对象:
time.Unix(timeMS/1e3, 0)
时间的最小值:
time.Unix(0, 0)
指定变量x作为秒数:
time.sleep( time.Duration(x-2)*time.Second )
格式化:
1)now.Format("2006-01-02-15-04-05.000")、et.Format("15:04:05")、et.Format("20060101")等
1、ReplaceAll
func strings.ReplaceAll(s string, old string, new string) string
ReplaceAll returns a copy of the string s with all non-overlapping instances of old replaced by new. If old is empty, it matches at the beginning of the string and after each UTF-8 sequence, yielding up to k+1 replacements for a k-rune string.
2、TrimSpace
func strings.TrimSpace(s string) string
TrimSpace returns a slice of the string s, with all leading and trailing white space removed, as defined by Unicode.
1、新建文件夹:
err := os.MkdirAll(filePath, os.ModePerm)
func os.MkdirAll(path string, perm os.FileMode) error
MkdirAll creates a directory named path, along with any necessary parents, and returns nil, or else returns an error. The permission bits perm (before umask) are used for all directories that MkdirAll creates. If path is already a directory, MkdirAll does nothing and returns nil.
2、创建文件
f, err := os.Create(x.OutFile)
func os.Create(name string) (*os.File, error)
Create creates or truncates the named file. If the file already exists, it is truncated. If the file does not exist, it is created with mode 0666 (before umask). If successful, methods on the returned File can be used for I/O; the associated file descriptor has mode O_RDWR. If there is an error, it will be of type *PathError.
3、获取文件信息
stat, err := os.Stat(x.OutFile)
4、判断是否为文件:
func IsFile(fp string) bool {
f, e := os.Stat(fp)
if e != nil {
return false
}
return !f.IsDir()
}
5、删除文件或目录
func os.Remove(name string) error
Remove removes the named file or directory. If there is an error, it will be of type *PathError.
RemoveAll:
func os.RemoveAll(path string) error
RemoveAll removes path and any children it contains. It removes everything it can but returns the first error it encounters. If the path does not exist, RemoveAll returns nil (no error). If there is an error, it will be of type *PathError.
6、重命名文件
func os.Rename(oldpath string, newpath string) error
Rename renames (moves) oldpath to newpath. If newpath already exists and is not a directory, Rename replaces it. OS-specific restrictions may apply when oldpath and newpath are in different directories. If there is an error, it will be of type *LinkError.
7、以Append方式打开文件流
示例1:
func (l *FileLogger) openFile(trunc bool) error {
if l.file != nil {
l.file.Close()
}
var err error
fileInfo, err := os.Stat(l.name)
if trunc || err != nil {
l.file, err = os.Create(l.name)
} else {
l.fileSize = fileInfo.Size()
l.file, err = os.OpenFile(l.name, os.O_RDWR|os.O_APPEND, 0666)
}
if err != nil {
fmt.Printf("Fail to open log file --%s-- with error %v\n", l.name, err)
}
return err
}
示例2:(文件不存在则自动创建)
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
8、打开文件流并写入字符串
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Errorf("can not create monitor log: %s, %v", filePath, err)
return ""
}
defer file.Close()
//写入文件时,使用带缓存的 *Writer
write := bufio.NewWriter(file)
_, err = write.WriteString("\n----------------------TailFile: " + readFile + "\n")
if err == nil {
write.WriteString(readLines)
write.Flush()
} else {
log.Errorf("can not write log file: %s, %v", filePath, err)
}
9、HTTP 文件下载
func LogDownload(w http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
req.ParseForm()
var filename string
if len(req.Form["filename"]) > 0 {
filename = req.Form["filename"][0]
}
filePath := dataPath + "/" + filename[0:10] + "/" + filename
_, e := os.Stat(filePath)
if e != nil {
r := map[string]bool{"success": false}
json.NewEncoder(w).Encode(r)
return
}
file, _ := os.Open(filePath)
defer file.Close()
fileHeader := make([]byte, 512)
file.Read(fileHeader)
fileStat, _ := file.Stat()
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
w.Header().Set("Content-Type", http.DetectContentType(fileHeader))
w.Header().Set("Content-Length", strconv.FormatInt(fileStat.Size(), 10))
file.Seek(0, 0)
io.Copy(w, file)
}
10、读取文件最后100行
思路:先seek到最后一个byte,然后依次向前取1 byte,如果遇到换行符,行数就+1,直到获取100行为止。
func ReadLastRows(filepath string, num int) (string, error) {
fileHandle, err := os.Open(filepath)
if err != nil {
return "", err
}
defer fileHandle.Close()
line := ""
var cursor int64 = 0
stat, _ := fileHandle.Stat()
filesize := stat.Size()
cou := 0
var off int64 = 0
for {
cursor -= 1
off, _ = fileHandle.Seek(cursor, io.SeekEnd)
char := make([]byte, 1)
fileHandle.Read(char)
if cursor != -1 && (char[0] == 10 || char[0] == 13) { // stop if we find a line
cou++
if cou >= num {
// fmt.Println("line: ", cou, cursor, off)
line = getString(fileHandle, off)
break
}
}
if cursor == -filesize { // stop if we are at the begining
line = getString(fileHandle, off)
break
}
}
return line, nil
}
func getString(fileHandle *os.File, off int64) string {
buf := make([]byte, 102400) // 100 KB
n, err := fileHandle.ReadAt(buf, off)
if err != io.EOF {
fmt.Println(err)
}
buf = buf[:n]
return fmt.Sprintf("%s", buf)
}
优化思路:上面的代码每次 seek 1 byte,也可以每次 seek 1024 byte,然后在内存中统计里面有多少个换行符,这样会不会更快?(我没试过)
1、int 转 int32、int64、float64
float64(x)、int32(x)
2、int 转字符串
strconv.Itoa(x)
3、float 转字符串
fmt.Sprintf("%.1f", x)
4、通用类型转字符串:fmt.Sprintf(%d整型数字、%s字符串、v%值原样输出)
s := fmt.Sprintf("%v", section.Order)
5、字符串转int:
i, err := strconv.Atoi(x)
if err != nil {
return "", fmt.Errorf("can't convert %s to integer", x)
}
+ (点击以下标题显示正文内容)
线程同步、等待线程结束
1、等待线程结束
func (pm *Manager) StopAllProcesses() {
var wg sync.WaitGroup
pm.ForEachProcess(func(proc *Process) {
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
proc.Stop(true)
}(&wg)
})
wg.Wait()
}
2、锁
有互斥锁(sync.Mutex)和 读写锁(sync.RWMutex)
对于读写锁RWMutex,默认Lock()为写锁,读锁单独为RLock()。
lock sync.RWMutex
全局锁:
p.lock.Lock()
if p.inStart {
log.WithFields(log.Fields{"program": p.GetName()}).Info("START-ERR 1: Don't start program again, program is already started")
p.lock.Unlock()
return
}
p.inStart = true
p.stopByUser = false
p.lock.Unlock()
只读锁:
func (p *Process) Signal(sig os.Signal, sigChildren bool) error {
p.lock.RLock()
defer p.lock.RUnlock()
return p.sendSignal(sig, sigChildren)
}
示例如下:
import (
"reflect"
"sort"
)
// ProcessNameSorter sort the process info by program name
type ProcessNameSorter struct {
processes []ProcessInfo
}
// NewProcessNameSorter creates new ProcessNameSorter object
func NewProcessNameSorter(processes []ProcessInfo) *ProcessNameSorter {
return &ProcessNameSorter{processes: processes}
}
// Len returns amount of programs
func (pns *ProcessNameSorter) Len() int {
return len(pns.processes)
}
// Less returns true if program name of i-th process is less than the program name of j-th process
func (pns *ProcessNameSorter) Less(i, j int) bool {
return pns.processes[i].Name < pns.processes[j].Name
}
// Swap i-th program and j-th program
func (pns *ProcessNameSorter) Swap(i, j int) {
swapF := reflect.Swapper(pns.processes)
swapF(i,j)
}
// SortProcessInfos sorts the process information by program name
func SortProcessInfos(processes []ProcessInfo) {
sorter := NewProcessNameSorter(processes)
sort.Sort(sorter)
}
sort.Sort( list ) ,调用标准库的sort.Sort必须要先实现 Len(), Less(i, j int), Swap(i, j int) 三个方法.