go小程序

1. 有一个 Go 函数,每次调用一次,就返回一个由英文大写字母构成的随机字符串。(用来生成一个短网址)那么用 Go 怎么实现好呢?

package main

import "fmt"
import "rand"

var keyGen func() string

func init() {
    keys := make(chan string)
    go func() {
        for {
            var buf [8]byte
            for i:=0 ; i<8 ; i++ {
                buf[i] = byte(rand.Intn(26)) + byte('A')
            }
            keys <- string(buf[:])
        }
    } ()
    keyGen = func() string {
        return <-keys
    }
}

func main() {
    fmt.Println(keyGen())
    fmt.Println(keyGen())
    fmt.Println(keyGen())
}

这里使用了一个 goroutine ,不断的产生随机串,送去一个 chan 。然后 keyGen 是由 init 函数初始化的 closure ,它每次从 chan 里读到一个生成好的串。

这种实现手法,应该算是 go 程序和 C/C++ 程序最大的不同吧。如果用 C 实现,几乎不会有人采用多线程方案来生成它们,但是 go 里使用 goroutine 却是一件很自然的事情。

2. 要实现一个特殊的 map ,支持 push 和 pop 两个操作。看起来是这样的:

type myMap interface {
    push(key string, e interface {}) interface{} 
    pop(key string) interface{}
}

当 push 的时候,如果 map 中 key 已存在,返回原来对应的 value ;若 key 不存在,则创建一个新的 key 把 value 放进去。

而 pop 操作,返回 key 对应的 value ,并把 key/value 对从 map 中删除。

鉴于 Go 程序中通常会使用大量 goroutine ,所以,这个 map 应该是线程安全的。那么用 Go 怎么实现它呢?最简单也就是最传统的方式是使用锁,即 sync.Mutex ,代码如下:

package main

import (
    "fmt"
    "sync"
)

type myMap struct {
    m map[string] interface {}
    sync.Mutex
}

func (m *myMap) push(key string, e interface {}) interface {} {
    m.Lock()
    defer m.Unlock()
    if v,exist := m.m[key] ; exist {
        return v;
    }
    m.m[key] = e
    return nil
}

func (m *myMap) pop(key string) interface {} {
    m.Lock()
    defer m.Unlock()
    if v,exist := m.m[key] ; exist {
        m.m[key] = nil, false
        return v
    }
    return nil
}

func newMap() *myMap {
    return &myMap { m: make(map[string] interface {}) }
}

func main() {
    m := newMap()
    fmt.Println(m.push("hello","world"))
    fmt.Println(m.push("hello","world"))
    fmt.Println(m.pop("hello"))
    fmt.Println(m.pop("hello"))
}

这里,newMap() 方法会返回一个 myMap 指针。其实按之前的定义,返回 muMap interface 也可以,它们在功能上是等价的。 但这里不可以返回 myMap 结构。因为,其中包含有一个其它包里的结构 sync.Mutex ,它是不可以被复制的。Go 里面没有 C++ 中重载赋值运算那些污七八糟的语法糖,所以用指针就好了。反正有 gc 不用担心。

其实,这个东东也可以把所有对 map 的操作放到同一个 goroutine 里完成,就不需要显式的锁了。不过具体到这个需求上,这么实现的意义实在有限。下面列出代码来,只是说可以这么做而已。

package main

import "fmt"

type myMap interface {
    push(key string, e interface {}) interface{} 
    pop(key string) interface{}
}

type myMapPair struct {
    key string
    value interface {}
}

type mapChan struct {
    push_req chan * myMapPair
    push_rep chan interface{}
    pop_req chan string
    pop_rep chan interface{}
}

func (c *mapChan) push(key string, e interface{}) interface{} {
    c.push_req <- & myMapPair {key,e}
    return <- c.push_rep
}

func (c *mapChan) pop(key string) interface {} {
    c.pop_req <- key
    return <- c.pop_rep
}

func newMap() myMap {
    c := mapChan { 
        push_req : make (chan * myMapPair),
        push_rep : make (chan interface{}),
        pop_req : make (chan string),
        pop_rep : make (chan interface{}),
    }
    m := make(map[string] interface {})
    go func() {
        for {
            select {
            case r := <- c.push_req :
                if v , exist := m[r.key] ; exist {
                    c.push_rep <- v
                } else {
                    m[r.key] = r.value
                    c.push_rep <- nil
                }
            case r := <- c.pop_req:
                if v,exist := m[r] ; exist {
                    m[r] = nil, false
                    c.pop_rep <- v
                } else {
                    c.pop_rep <- nil
                }
            }
        }
    } ()
    return &c   
}

func main() {
    m := newMap()
    fmt.Println(m.push("hello","world"))
    fmt.Println(m.push("hello","world"))
    fmt.Println(m.pop("hello"))
    fmt.Println(m.pop("hello"))
}

这里建立了四个 chan ,也就是两对请求/回应通道,让单一的 goroutine 来处理两种对 map 操作的请求。这个处理的 goroutine 可以看成是对其它 goroutine 提供的服务。能这么方便的使用这种模式,在大多数编程语言里并不多见。Erlang 可以算一个。但 Go 的优势在于,允许你使用可能对你更为习惯的命令式编程方式,而不需要转换思维到函数式编程。

3. http server 在支持断点续传时碰到个小的算法问题。就是我希望用户在完整下载完一次指定文件后,就让这个文件 URL 失效。而如果用户不断的从中间开始分段下载的话,很难从统计下载字节数来判定整个文件是否下载完一次。

我希望把下载完一次的标准定为,文件的每个字节都至少被请求一次。这个问题用线段树来实现最为贴切。当然还会有各种其它方案,就不展开讨论了。

这里的代码有很大的优化余地,但是就具体应用来说,性能方面也足够了。严谨点来说,我在服务器上额外增加了一些判断,防止被恶意攻击。比如如果分段 太多(恶意的每间隔一个字节请求一个字节)对于大文件会导致这个数据结构占据过多内存。这时直接掐断服务即可,这里不展开讨论。

今天贴的代码和 C 很接近。没什么特别有特色的地方。不过,Go 的 Slice 的确很好用,还有 Built-in 的函数 copy ,这些都使得 Go 比 C 在处理数据块上更方便和安全。

package main

import "fmt"

type segment struct {
	length int64
	data []int64
}

func newSegment(length int64) * segment {
	s := &segment { length , make([]int64,1,8) }
	s.data[0] = length
	return s
}

func (s *segment) insert(from int64 , size int64) (large bool) {
	large = false
	if size <= 0 {
		panic("invalid argment")
	}
	if from > s.length {
		panic("invalid argment")
	}
	if from < 0 {
		panic("invalid argment")
	}
	end := from + size
	if end > s.length {
		panic("invalid argment")
	}
	var i int
	for i=0;i<len(s.data);i++ {
		if from <= s.data[i] {
			break
		}
	}
	if i%2 == 0 {
		if end <= s.data[i] {
			if cap(s.data) < len(s.data)+2 {
				t:=make([]int64, len(s.data)+2)
				copy(t,s.data)
				s.data = t
			} else {
				s.data = s.data[:len(s.data)+2]
			}
			copy(s.data[i+2:],s.data[i:])
			s.data[i] = from
			s.data[i+1] = end
		} else {
			s.data[i] = from
			if end <= s.data[i+1] {
				return
			} else if end <= s.data[i+2] {
				s.data[i+1] = end;
				return
			} else {
				var j int
				for j=i+3;j<len(s.data);j++ {
					if end <= s.data[j] {
						break
					}
				}
				i++
				if j%2 == 0 {
					s.data[i] = end
					i++
				}
				copy(s.data[i:],s.data[j:])
				s.data = s.data[:len(s.data)-(j-i)]
			}
		}
	} else {
		if end <= s.data[i] {
			return
		}
		if end <= s.data[i+1] {
			s.data[i] = end
			return
		}
		var j int
		for j=i+2;j<len(s.data);j++ {
			if end <= s.data[j] {
				break
			}
		}
		if j%2 == 0 {
			s.data[i] = end
			i++
		}
		copy(s.data[i:],s.data[j:])
		s.data = s.data[:len(s.data)-(j-i)]
	}

	return
}

func (s *segment) white() int64 {
	t := s.data[0]
	for i:=2;i<len(s.data);i+=2 {
		t+=s.data[i]-s.data[i-1]
	}
	return t
}

func (s *segment) dump() {
	fmt.Printf("[0,%d) ",s.data[0])
	for i:=2;i<len(s.data);i+=2 {
		fmt.Printf("[%d,%d) ",s.data[i-1],s.data[i])
	}
	fmt.Printf("%d\n",s.white())
}

func (s *segment)ins(from int64,end int64) {
	fmt.Printf("insert %d,%d ",from,end)
	s.insert(from,end-from)
	s.dump()
}

func main() {
	s := newSegment(100)
	s.ins(0,100)
	s.ins(20,50)
	s.ins(30,40)
	s.ins(0,10)
	s.ins(45,60)
	s.ins(70,80)
	s.ins(5,75)
	s.ins(15,75)
}

你可能感兴趣的:(go小程序)