Fork me on GitHub

Golang源码学习之heap

最近刷题遇到了「堆」相关的使用,正好学习一下在Golang中heap的使用和实现。

heap包使用

Golang标准库中实现了heap包,位于container/heap。堆需要支持的操作抽象定义在heap.Interface接口中,自定义的数据结构实现该接口就可以实现堆了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// An IntHeap is a min-heap of ints.
type IntHeap []int

// sort.Interface接口实现
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] } // 升序,小顶堆
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

func (h *IntHeap) Push(x any) {
// Push and Pop use pointer receivers because they modify the slice's length,
// not just its contents.
*h = append(*h, x.(int))
}

func (h *IntHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}

使用示例

heap.Interface 接口

要维持堆的性质数据结构必需可排序,因此嵌入了sort.Interface接口。
PushPop对应入堆和出堆操作中增减元素实现,堆调整的逻辑由标准库实现。

1
2
3
4
5
type Interface interface {
sort.Interface
Push(x any) // add x as element Len()
Pop() any // remove and return element Len() - 1.
}

其他函数

1
2
3
4
5
6
7
8
9
10
// 初始化建堆
func Init(h Interface)
// 弹出堆顶元素
func Pop(h Interface) any
// 入堆
func Push(h Interface, x any)
// 移除位置i的元素
func Remove(h Interface, i int) any
// 修改i位置元素值之后,调整堆
func Fix(h Interface, i int)

heap包实现

三个基本操作Init,Push,Pop的实现调用了up/down方法来调整堆元素顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func Init(h Interface) {
// heapify
n := h.Len()
for i := n/2 - 1; i >= 0; i-- {
down(h, i, n)
}
}

// Push pushes the element x onto the heap.
// The complexity is O(log n) where n = h.Len().
func Push(h Interface, x any) {
h.Push(x)
up(h, h.Len()-1)
}

// Pop removes and returns the minimum element (according to Less) from the heap.
// The complexity is O(log n) where n = h.Len().
// Pop is equivalent to [Remove](h, 0).
func Pop(h Interface) any {
n := h.Len() - 1
h.Swap(0, n) // 遵循接口Pop() any方法调用约定,把堆顶元素放到n的位置
down(h, 0, n)
return h.Pop()
}

up方法,用于push之后从堆底向上调整

1
2
3
4
5
6
7
8
9
10
func up(h Interface, j int) {
for {
i := (j - 1) / 2 // parent
if i == j || !h.Less(j, i) {
break
}
h.Swap(i, j)
j = i
}
}

down方法,用于初始化或者pop从堆上层向下调整

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func down(h Interface, i0, n int) bool {
i := i0
for {
j1 := 2*i + 1
if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
break
}
j := j1 // left child
if j2 := j1 + 1; j2 < n && h.Less(j2, j1) {
j = j2 // = 2*i + 2 // right child
}
if !h.Less(j, i) {
break
}
h.Swap(i, j)
i = j
}
return i > i0
}

计算左右孩子节点公式,注意右孩子j2有可能会越界

1
2
3
4
5
6
7
当前索引位置 i
左孩子 j1 = 2*i + 1
右孩子 j2 = 2*i + 2 = j1 + 1

例子:
1. 长度为偶数的堆 [100,200],索引位置0->[1,2],右孩子索引2越界
2. 长度为奇数的堆 [100,200,300],索引位置0->[1,2]

堆元素顺序判断条件!h.Less(j, i)看起来有点难理解,其实是为了Less方法实现在直觉上跟sort包排序保持一致,比如

1
2
3
4
5
1. 升序 => 小顶堆
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }

2. 降序 => 大顶堆
func (h IntHeap) Less(i, j int) bool { return h[i] > h[j] }

Golang源码学习之sort

本文主要介绍go语言sort包内部分排序函数的实现,学习如何在实际的工业代码中如何应用那些基础的排序算法。

Sort方法

sort包内的Sort方法位于源文件sort/sort.go,是一个通用的非稳定排序的方法。

API设计

函数签名

1
func Sort(data Interface)

go由于没有泛型,要实现通用排序只能另辟蹊径。
sort使用接口类型sort.Interface作为方法参数,sort.Interface接口定义了交换和比较的方法,按照比较方法的结果进行排序。

1
2
3
4
5
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}

Sort方法实现

一般不稳定排序我们会先考虑快排,不过需要解决以下两个问题:

  • 如何避免递归调用过深
  • 如何避免最差情况时间复杂度

下面我们看一下go源码中使用了什么方法来解决这些问题,以及它使用了哪些优化手段。

阅读更多...

大数据工作流组件oozie简介

Oozie是一个管理 Apache Hadoop 作业的工作流调度系统。

Oozie的 workflow jobs 是由 actions 组成的 有向无环图(DAG)。

Oozie的 coordinator jobs 是由时间 (频率)和数据可用性触发的重复的 workflow jobs 。

Oozie与Hadoop生态圈的其他部分集成在一起,支持多种类型的Hadoop作业(如Java map-reduce、流式map-reduce、Pig、Hive、Sqoop和Distcp)以及特定于系统的工作(如Java程序和shell脚本),不同作业对应不同的workflow action。

阅读更多...

Gin web框架

gin是一个基于Golang标准库net/http封装的HTTPweb框架。
它提供了方便的路由注册功能,支持捕获URL参数,提供了中间件机制来串连请求处理流程,提供了方便的数据获取和输出方法,所有这些功能提升了开发web服务的效率。
本文将从以下六个方面介绍gin的实现。

Engine

基于Golang标准库net/http的web服务都需要实现http.Handler接口,而Engine就是gin实现该接口的类,它同时也实现了注册路由、run服务等方法。
ServeHTTP是HTTP请求的入口,而主要逻辑在handleHTTPRequest方法中实现。

1
2
3
4
5
6
7
// 标准库HTTP接口
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// 接口实现
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request)
func (engine *Engine) handleHTTPRequest(c *Context)

以下是几个比较重要的成员。
RouterGroup是路由注册的类,收集路由信息和对应的HandlerChain,实际注册还是调用的Engine.addRoute方法。
trees是底层组织路由的结构。
pool是分配gin.Context的池,优化内存分配。
Render用于数据渲染。

1
2
3
4
5
6
7
8
9
type Engine struct {
RouterGroup // 路由,engine作为根路由

pool sync.Pool // context池
trees methodTrees // 路由

HTMLRender render.HTMLRender // 渲染数据
FuncMap template.FuncMap // 渲染数据
}

路由

gin定义了两个接口来处理路由信息
IRoutes包含设置中间件、设置HTTP处理函数以及静态文件等方法,应用程序通过它来注册应用的路由处理逻辑。
IRouter继承了IRoutes,另外添加了一个Group方法来按URL路径层次组织router。

RouterGroup实现了IRouter接口,字段包含路径以及请求处理链,保存了Engine的指针,注册路由是通过调用engine.addRoute实现的。

1
2
3
4
5
6
type RouterGroup struct {
Handlers HandlersChain // 处理链
basePath string
engine *Engine
root bool // 如果是根,那么返回的IRoutes接口对象为engine;否则返回自身
}

addRoute方法根据不同HTTP method获取路由的根节点,然后按路径加入到基数树中。

1
2
3
4
5
6
7
8
9
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
root := engine.trees.get(method)
if root == nil {
root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers)
}
阅读更多...

Golang failpoint工具

failpoint是用于测试时注入错误的工具。
来源是BSD系统的failpoint功能,已有的go语言实现有etcd的gofail。

它与gofail的区别是用标记函数代替了注释,标记函数的好处在于IDE可以识别并且编译时可以进行语法检查。

分析

使用

注入错误

1
2
3
4
5
6
7
8
9
package main

import "github.com/pingcap/failpoint"

func main() {
failpoint.Inject("testPanic", func() {
panic("failpoint triggerd")
})
}

打开错误failpoint-ctl enable
关闭failpoint-ctl disable

源码

enable和disable命令对应code目录下面的两个功能模块

  • rewriter
  • restorer
阅读更多...
  • © 2015-2024 RivenZoo
  • Powered by Hexo Theme Ayer
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信