哪位大侠用过go-git的包啊,麻烦指点一下。

回复

有问必答Samuier 发起了问题 • 1 人关注 • 0 个回复 • 858 次浏览 • 2016-11-23 17:35 • 来自相关话题

[译]如何避免golang的坑

文章分享astaxie 发表了文章 • 4 个评论 • 2915 次浏览 • 2016-11-23 14:15 • 来自相关话题

如何避免golang的坑

坑是系统,程序或编程语言中有效的结构,它可以按指定的方式工作,但是以反直觉的方式工作,并且总是引发错误,因为很容易被调用,并且总是触发异常

Go编程语言有一些坑,有很多好文章解释这些坑。我发现这些... 查看全部

如何避免golang的坑


坑是系统,程序或编程语言中有效的结构,它可以按指定的方式工作,但是以反直觉的方式工作,并且总是引发错误,因为很容易被调用,并且总是触发异常


Go编程语言有一些坑,有很多好文章解释这些坑。我发现这些文章非常重要,特别是对于go新手来说,因为我看到人们经常踩这些坑。


然而有一个问题困扰了我很长时间 - 为什么我从来没踩过这些坑?其中最有名的,像“nil interface”或“slice append”问题从来没有困扰我。我从第一天写go就在某种程度上避免了这些问题。为什么会这样?


答案其实很简单。我很幸运,我读过一些关于Go数据结构的内部表示的文章,并学习过Go内部工作机制的一些基础知识。这些知识足构建避开那些坑的直觉。


记住,“坑是有效的构造,但是是反直觉的”?这就对了。您只有两个选项:



  • “修复”语言

  • 修正直觉


第二个实际上视为构建直觉会更好。一旦你有一个清晰的认识,如何interface或slice如何工作,几乎是不可能犯这些错误。


这种方式对我起作用,也应该对别人起作用。这就是为什么我决定在这篇文章中收集一些Go内部运行机制的基础知识,并帮助人们建立关于不同结构的内存表示的直觉。


让我们从基本的了解如何在内存中表示事物开始。以下是我们将要学习的内容:



  • 指针

  • 数组和切片

  • Append

  • 接口

  • 空接口


指针


Go非常接近硬件。 当创建64位整数(int64)变量时,您确切知道它需要多少内存,您可以使用unsafe.Sizeof()计算任何其他类型的大小。


我经常使用内存块的可视化方式来“看到”变量,数组和数据结构的大小。 视觉表示给你一个简单的方法来获得关于类型的直觉,并通常有助于推理其行为和性能。


让我们以显示golang的大多数基本类型来热身:

假设你使用32位机器(现在可能是false),你可以看到int64的内存是int32的两倍。


还有更复杂指针的内部表示,它是内存中的一个块,其中包含内存中的一些其他区域的地址,而该地址存储实际数据。 当你听到花哨的词如“dereferencing a pointer”时,它实际上意味着“通过存储在指针变量中的地址获得实际的内存块”。 你可以想象它是这样的:

存中的地址通常由十六进制值表示,因此图片中的地址是“0x ...”。 但是知道“指针的值”可能在一个地方,而“由指针引用的实际数据” - 在另一个地方,将在未来帮助我们避免错误。


现在,Go中的初学者的“坑”之一,是由于没有带指针语言的先验知识,因为功能参数的“值传递”导致的。 你可能知道,在Go中,一切都是通过“值”,举例来说通过复制。一旦你试图可视化这种复制过程就更容易理解了:

在第一种情况下,你复制所有这些内存块 - 在现实中,内存大小通常远远超过2 - 很可能是200万个内存块,你必须复制它们,这是最昂贵的操作之一。 但在第二种情况下,你只复制一个内存块 - 它包含实际数据的地址,非常快并且开销低。


现在,你可以看到,在函数Foo()中修改p不会在第一种情况下修改原始数据,但是在第二种情况下肯定会修改,因为存储在p中的地址引用了原始数据块。


好吧,如果你知道为何了解go内部表示可以帮助你避免常见问题了吧,让我们深入一点。


数组和切片


新手经常混淆切片与数组。 让我们来看看数组。


数组


var arr [5]int
var arr [5]int{1,2,3,4,5}
var arr [...]int{1,2,3,4,5}

数组只是连续的内存块,如果你检查Go运行时源代码(src / runtime / malloc.go),你可能会看到创建一个数组本质上是分配给定大小的一块内存。 类似malloc,只是更聪明:)


// newarray allocates an array of n elements of type typ.
func newarray(typ *_type, n int) unsafe.Pointer {
if n < 0 || uintptr(n) > maxSliceCap(typ.size) {
panic(plainError("runtime: allocation size out of range"))
}
return mallocgc(typ.size*uintptr(n), typ, true)
}

这对我们意味着什么? 这意味着我们可以简单地将数组表示为一组在内存中相邻的块:

数组元素总是用其类型的零值初始化,在[5] int的情况下为0。 我们可以索引它们,并使用len()内置命令获取长度。 当你通过索引引用数组中的单个元素并执行这样的操作时:


var arr [5]int
arr[4] = 42

你正在获取第五(4 + 1)元素并更改其值:

现在我们来探索切片。


切片


第一眼看到切片与数组类似,声明方式真的类似:


var foo []int

但是如果我们去Go源代码(src/runtime/slice.go),我们会看到,Go的切片是具有三个字段的结构体 - 指向数组的指针,长度和容量:


type slice struct {
array unsafe.Pointer
len int
cap int
}

当你创建一个新的切片,Go运行时将创建这个三块对象在内存中的指针设置为nil,len和cap设置为0.让我们直观地表示:

让我们使用make来初始化给定大小的切片:


foo = make([]int, 5)

将创建一个具有5个元素的底层数组的切片,初始化为0,并将len和cap设置为5. Cap意味着容量,并有助于为未来增长预留更多空间。 您可以使用make([] int,len,cap)语法来指定容量。你几乎没有必要设置cap,但重要的是要了解cap的概念。


foo = make([]int, 3, 5)

我们看看两者的图示:

现在,当您更新切片的一些元素时,实际上是更改底层数组中的值。


foo = make([]int, 5)
foo[3] = 42
foo[4] = 100


很简单。 但是,如果你创建另一个子切片并更改一些元素,会发生什么? 咱们试试吧:


foo = make([]int, 5)
foo[3] = 42
foo[4] = 100
bar := foo[1:4]
bar[1] = 99


通过修改bar,你实际修改了底层数组,它也被slice foo引用。你可能写这样的代码:


var digitRegexp = regexp.MustCompile("[0-9]+")
func FindDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
return digitRegexp.Find(b)
}

读10MB的数据到切片,并且只搜索3位,你可以假设你返回3个字节,但实际上,底层数组将保存在内存中。

这可能是你最常见的Go坑之一。 但是一旦你有这种内部片段表示的图像,我敢打赌,几乎不可能会再踩坑!


Append


有一些坑与内置的通用函数append()相关。 append函数本质上做一个操作 - 添加一个值到切片,但在内部它做了很多复杂的工作,以智能和高效的方式分配内存。


让我们来看下面的代码:


a := make([]int, 32)
a = append(a, 1)

记住cap 代表成长的能力。 append检查该切片是否有更多的容量用于增长,如果没有,则分配更多的内存。 分配内存是一个相当昂贵的操作,因此append尝试对该操作进行预估,一次增加原始容量的两倍。 一次分配较多的内存通常比多次分配较少的内存更高效和更快。


由于许多原因,分配更多的内存通常意味着分配新内存并从旧数组拷贝数据到新数组。 这意味着切片中基础数组的地址也将改变。 让我们想象一下:

很容易看到两个底层数组 - 旧的和新的。 旧的数组将被GC释放,除非另一个slice引用它。 这种情况是append的坑之一。 如果你创建子切片b,然后append一个值到a(假设他们都共享共同的底层数组)?


a := make([]int, 32)
b := a[1:16]
a = append(a, 1)
a[2] = 42

你会得到如下结果:

你会有两个不同的底层数组,这对初学者来说可能是相当不经意的。 所以,作为一个经验法则,当你使用子切片,特别是sublices与append时,要小心。


顺便说一下,append通过将它的容量增加一倍来增加slice,最多只有1024,之后它将使用所谓的内存大小类来保证增长不超过〜12.5%。 请求64字节为32字节数组是确定,但如果你的切片是4GB,分配另一个4GB添加1元素是相当昂贵的,所以这是有道理的。


接口


新手需要一些时间来正确使用Go中的接口,特别是在有基于类的语言经验后。 混乱产生原因之一是在接口的上下文中nil关键字的不同含义。


为了帮助理解这个主题,让我们再来看看Go源代码。 这里是一个来自src/runtime/runtime2.go的代码:


type iface struct {
tab *itab
data unsafe.Pointer
}

itab代表接口表,也是一种保存有关接口和底层类型的所需元信息的结构:


type itab struct {
inter *interfacetype
_type *_type
link *itab
bad int32
unused int32
fun [1]uintptr // variable sized
}

我们不会学习接口类型断言如何工作,重要的是理解接口是接口和静态类型信息的复合,加上指向实际变量(iface中的字段数据)的指针。 让我们创建error接口的变量err并直观地表示它:


var err error


事实上,你在这张图片中看到的是nil接口。 当在返回error类型的函数中返回nil时,将返回此对象。 该对象中有关于接口(itab.inter)的信息,但在data和itab.type字段中为nil。 此对象将在if err == nil {}条件中求值为true。


func foo() error {
var err error // nil
return err
}
err := foo()
if err == nil {...} // true

臭名昭著的坑是返回一个* os.PathError变量,它是nil。


func foo() error {
var err *os.PathError // nil
return err
}
err := foo()
if err == nil {...} // false

这两段代码很相似,除非你知道接口内部是什么结构。 让我们继续用图表示这个 os.PathError类型(包装了error接口)的nil变量:
![](https://cdn-images-1.medium.com/max/800/0
1cosW9WkQq1AqKHm.png)
你可以清楚地看到* os.PathError变量 - 它只是一个值为nil内存块。 但是我们foo()函数返回值实际错误是一个非常复杂的结构,包含相关接口的信息,该块内存的底层类型和内存地址,并都设置为nil。


在这两种情况下,我们都返回nil,但“有一个变量的值等于nil的接口”和“没有变量的接口”之间有一个巨大的区别。 有了这些接口的内部结构的知识,就可以弄清楚以下两个例子:

现在更难踩坑了吧。


空接口


关于空接口 - interface {}。 在Go源代码(src/runtime/ malloc.go它实现使用自己的结构 - eface:


type eface struct {
_type *_type
data unsafe.Pointer
}

正如你看到的,它类似于iface,但缺乏接口表。 它不需要一个接口表,因为空接口可以是由任何静态类型实现。 所以当你包装一些东西 - 显式或隐式(通过传递作为一个函数的参数,例如) - 到interface {},你实际上使用这个结构:


func foo() interface{} {
foo := int64(42)
return foo
}


interface{}相关的坑之一你不能轻易地分配接口切片的具体类型,反之亦然。 就像是这样:


func foo() []interface{} {
return []int{1,2,3}
}

编译时会报错:


$ go build
cannot use []int literal (type []int) as type []interface {} in return argument

为什么我可以在单变量上做这个转换,但不能切片上做同样的事情? 但是一旦你知道什么是空接口(再看看上面的图片),过程就变得很清楚,这个“转换”实际上是一个相当昂贵的操作,涉及分配一堆内存。 Go设计中常用的方法之一是“如果你想做一些昂贵的操作- 明确地做”。

希望以上内容对你有意义。


结论


不是每个坑都可以通过学习内部机制避免。 其中一些坑只是和你过去的经验不同,我们都有某种不同的背景和经验。 然而,有很多的坑,可以简单地通过了解Go如何工作而成功避免。 我希望这篇文章中的解释将帮助你建立起程序内部机制的直觉,并将使你成为一个更好的开发人员。 Go是你的朋友,对它了解的越多越好。


如果你有兴趣阅读更多关于Go内部机制,这里有一个链接列表帮助你:


有用之物的永恒来源:)


go有没有webkit的包?

有问必答xieyanke 回复了问题 • 3 人关注 • 1 个回复 • 940 次浏览 • 2016-11-23 14:13 • 来自相关话题

长春招聘,不是标题党,只说实在话。。亲,你来看看嘛

招聘应聘xieyanke 回复了问题 • 3 人关注 • 3 个回复 • 1111 次浏览 • 2016-11-23 14:06 • 来自相关话题

golang调用oracle的问题

有问必答astaxie 回复了问题 • 2 人关注 • 1 个回复 • 1165 次浏览 • 2016-11-23 13:08 • 来自相关话题

golang 内部包引用

有问必答astaxie 回复了问题 • 3 人关注 • 1 个回复 • 1509 次浏览 • 2016-11-23 13:07 • 来自相关话题

Golang中国微信号终于和gocn网站对接了

文章分享astaxie 发表了文章 • 6 个评论 • 962 次浏览 • 2016-11-23 12:44 • 来自相关话题

大家可以扫描下面这个二维码关注我们的微信公众号,

查看全部

大家可以扫描下面这个二维码关注我们的微信公众号,



然后你就可以发送查询关键字给公众号了,



go编译后,如何直接在其他没有go环境的机器上执行,像linux rpm包一样直接执行。

有问必答sasaxie 回复了问题 • 7 人关注 • 5 个回复 • 4301 次浏览 • 2016-11-23 10:50 • 来自相关话题

监控系统中到底是pull还是push方案好?

有问必答codinghxl 回复了问题 • 10 人关注 • 6 个回复 • 2111 次浏览 • 2016-11-22 17:54 • 来自相关话题

beego api项目问题求教

有问必答Xanthus 回复了问题 • 2 人关注 • 3 个回复 • 746 次浏览 • 2016-11-22 16:38 • 来自相关话题

使用 HTTP2 协议的APNS(苹果消息推送) ,消息发送超时问题

有问必答bopjiang 回复了问题 • 6 人关注 • 2 个回复 • 2401 次浏览 • 2016-11-22 16:36 • 来自相关话题

通过哪些方法判断goroutine泄漏?有没有具体的调试步骤的文章?

有问必答guang7night 回复了问题 • 3 人关注 • 1 个回复 • 788 次浏览 • 2016-11-22 15:49 • 来自相关话题

go调用Hadoop的分布式文件系统 (HDFS),可行吗?

技术讨论vr 回复了问题 • 3 人关注 • 3 个回复 • 1446 次浏览 • 2016-11-22 14:57 • 来自相关话题

在Beego中使用Jade模板

文章分享astaxie 发表了文章 • 1 个评论 • 1160 次浏览 • 2016-11-21 21:20 • 来自相关话题

Jade是一个高性能的HTML模板引擎,它受到Haml的影响,是使用JavaScript实现的。Jade在客户端也有支持,它的代码比html可读性要高很多,Jade是一个比较常用的HTML模板。 Beego是一个go语言的web应用程序开源web框架,而... 查看全部

Jade是一个高性能的HTML模板引擎,它受到Haml的影响,是使用JavaScript实现的。Jade在客户端也有支持,它的代码比html可读性要高很多,Jade是一个比较常用的HTML模板。
Beego是一个go语言的web应用程序开源web框架,而Beego从1.7.0开始支持更加复杂的模板引擎,当然也包括了对于jade的支持,支持更复杂模板引擎的PR地址https://github.com/astaxie/beego/pull/1940
在介绍Jade的使用之前先来看下Go下面的html/template包。


html/template


在Go语言中,html/template包是一个很强大的html模板包,结合text/template的模板语法,基本上可以满足大部分的HTML模板需求,无论是Beego中是默认支持的两种模板格式.tpl和.html,以及jade和ace都是可以解析为html/template中的Template对象使用,就是说我们所使用的html模板最终都是基于html/template包实现的。
html/template使用实例:


package main
import (
"html/template"
)
type User struct {
Name string
}
func main() {
t := template.New("template example")
t, _ = t.Parse("hello {{.Name}}!")
p := User{Name: "jjz"}
t.Execute(os.Stdout, p)
}

上面的例子会输出字符串:hello jjz。
通过上面的例子我们可以看到,如何新建一个模板,再使用模板函数Parse()从字符串中加载模板内容,使用模板函数Execute()可以给模板替换字段。替换模板字段的语法,{{}}中的字段就是要替换字段,{{. Name}}表示需要替换的字段名称。
Beego使用复杂模板的方式很简单,增加一个模板引擎函数,在项目运行的时候遍历views中的文件,把指定的文件解析为template.Template对象,返回该对象提供使用,这个模板引擎函数就是:beego.AddTemplateEngine


AddTemplateEngine


beego.AddTemplateEngine是一个用来把指定的文件,转换为template.Template的对象的函数。它的第一个参数是文件的后缀名,在views中的含有此后缀名的文件都会进入该方法进行处理。他的第二个参数是一个函数,用来处理文件的转换,最后会返回一个template.Template的对象。有了这个函数,我们还少一个把jade文件解析为template.Template的方法,还好有人已经做了jade的Go语言实现。


jade.go


jade.go是Jade的Go语言实现。


jade.go的使用,首先安装jade.go:


go get github.com/Joker/jade

jade.go使用示例:


func main() {
tpl, err := jade.Parse("name_of_tpl", "doctype 5: html: body: p Hello world!")
if err != nil {
return
}
fmt.Printf( "%s", tpl )
}

输出字符串:


<!DOCTYPE html>
<html>
<body>
<p>Hello world!</p>
</body>
</html>

有了jade.go就可以在Beego中使用,把jade文件转换为Template对象,在beego中添加jade.go解析jade模板的方法:


func addJadeTemplate() {
beego.AddTemplateEngine("jade", func(root, path string, funcs template.FuncMap) (*template.Template, error) {
jadePath := filepath.Join(root, path)
content, err := utils.ReadFile(jadePath)
fmt.Println(content)
if err != nil {
return nil, fmt.Errorf("error loading jade template: %v", err)
}
tpl, err := jade.Parse("name_of_tpl", content)
if err != nil {
return nil, fmt.Errorf("error loading jade template: %v", err)
}
fmt.Println("html:\n%s",tpl)
tmp := template.New("Person template")
tmp, err = tmp.Parse(tpl)
if err != nil {
return nil, fmt.Errorf("error loading jade template: %v", err)
}
fmt.Println(tmp)
return tmp, err

})
}

jade.go目前只支持使用字符串的方式接卸jade模板,因此需要先把.jade文件的内容以字符串的方式读取出来。读取文件的方法:


func ReadFile(path string) (str string, err error) {
fi, err := os.Open(path)
defer fi.Close()
fd, err := ioutil.ReadAll(fi)
str = string(fd)
return
}

jade.go解析出的结果也是一个字符串,因此在代码中需要把字符串转换为template.Template对象,使用了 Template.Parse()方法。
解析jade模板的引擎方法需要在main()中调用,添加完jade的模板转换引擎之后,就可以在Beego中使用jade模板了。


jade模板的使用


首先定义一个页面home.jade:


doctype html
html
head
title pageTitle
body
h1 jade
.content {{.content}}

其中{{.content}}是需要替换的字段,Controller层代码:


func (c *MainController) Jade() {
c.Data["content"] = "this is jade template"
c.TplName = "home.jade"
}

运行之后生成的页面代码:


<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>pageTitle</title>
</head>
<body>
<h1>jade</h1>
<div class="content">this is jade template</div>
</body>
</html>

通过添加一个解析模板的引擎就可以在beego中使用jade的模板,beego从1.7.0之后开始支持复杂的模板引擎,不仅仅是对于jade的支持,也包括对于其他模板引擎的支持。
除了jade之外,在这个PR中推荐使用的是aceHTML模板引擎。


ace


ace是一个Go语言的HTML模板引擎,它借鉴了Slim和Jade,在Go语言里有超高的人气。


ace模板在beego中的使用与使用Jade类似,首先安装ace的依赖包:


go get github.com/yosssi/ace

在main函数中添加ace模板解析引擎:


func addAceTemplate()  {
beego.AddTemplateEngine("ace", func(root, path string, funcs template.FuncMap) (*template.Template, error) {
aceOptions := &ace.Options{DynamicReload: true, FuncMap: funcs}
aceBasePath := filepath.Join(root, "base")
aceInnerPath := filepath.Join(root, strings.TrimSuffix(path, ".ace"))
tpl, err := ace.Load(aceBasePath, aceInnerPath, aceOptions)
if err != nil {
return nil, fmt.Errorf("error loading ace template: %v", err)
}
return tpl, nil
})
}

注意使用ace模板需要指定一个base.ace,另外使用ace.Load()函数可以直接返回一个template.Template对象,不需要再做其他转换的工作。


添加完模板引擎之后就可以直接在views中添加.ace文件使用,新建一个home.ace文件:


= doctype html
html lang=en
head
meta charset=utf-8
title Base and Inner Template
body
h1 ace
.content {{.content}}

同样的{{.content}}是也需要在controller层本替换成需要的内容,controller层代码:


func (c *MainController)Ace() {
c.Data["content"] = "this is ace template"
c.TplName = "home.ace"
}

示例代码地址:https://github.com/jjz/go/tree/master/template


原文:https://segmentfault.com/a/1190000007532274

go中unix域套接字使用,type UnixAddr struct { Name string Net string } 这个结构体中的两个参数应该怎么设置啊

有问必答astaxie 回复了问题 • 2 人关注 • 3 个回复 • 1020 次浏览 • 2016-11-21 20:32 • 来自相关话题