github上面的活跃的dotfiles项目[yadr](https

这里主要就是官方readme文档的 Vim部分 的记录。基本的vim操作就不详细介绍了。

参考: http://skwp.github.io/dotfiles/

有什么?

yadr提供了

导航

  • ,z 切换到前一个缓冲文件(buffer),相当于 :bp[revious]
  • ,x 切换到后一个缓冲,相当于命令行的 :bn[ext]
  • Alt+jAlt+k 下跳或者上跳一个函数;好像不起作用
  • Ctrl+o 跳转到旧的指针位置,这是vim的标准快捷键,非常有用
  • Ctrl+i Ctrl+o的逆操作,也是标准按键

搜索与代码导航

  • ,f 跳转到类定义,需要系统安装了ctags,并创建tags file。

ctags是一个独立的软件,不包含在vim之中,使用yum安装即可,在项目目录下运行 ctags -R . 后会创建tags file,这是就可以使用跳转到定义的功能了。

  • ,F,f相同不过在新建垂直分屏(vsp)显示定义
  • ,gf或者 ctrl+f 跳转到光标坐在变量名对应的文件,但是在一个新的分栏中显示,这在java中比较好用
  • gF 标准快捷键,打开文件
  • K 搜索光标所在单词,并在quickfix窗口显示结果,这个功能需要安装silver serach, sudo yum install -y the_silver_searcher
  • ,K 没太多用,Grep the current word up to next exclamation point (useful for ruby foo! methods)
  • ,hl 开关搜索结果的高亮,相当于 :set noh与其逆操作
  • ,gg或者,ag: 搜索,键入这个命令后,会出现一个输入符,在双引号中输入字符后回车会搜索包含这段字符的行
  • ,gd 搜索包含字符的函数定义, grep def,不太用
  • ,gcf grep current file的缩写,查找对本文件的引用
  • // 清空搜索
  • ,,w ,<Esc>的alias,EasyMotion,highlights jump-points on the screen and lets you type to get there
  • ,mc 多标签,这个功能和sublime中的比较像,在一个单词上面使用,mc会记录这个单词,然后使用Ctrl-n或者Ctrl+p选择前一个或者后一个相同的单词的,或者使用Ctrl+x跳过一个。 这个比较有用
  • gK 打开光标所在单词同名的文档?不常用

文件导航

  • ,t 文件选择器,不常- 用,键入指令后可以输入字符筛选文件
  • ,b 打开缓存区的一个文件,比较常用
  • Cmd-Shift-M 跳转到方法,Liux下好像没用。
  • ,jm jump to models,可能在rails里面比较有用
  • Alt+Shift+N nerd tree toogle,好像没什么用
  • Ctrl+\ 在侧边栏文件树显示当前文件,和sublime的sidebar有点像
  • Cmd-Shift-P 清空ctrlp 缓存,没用过

常用编辑快捷键

  • Ctrl+space 自动补全- , Tab键
  • ,#; ,"; ,'; ,]; ,); ,}; 这些优点麻烦的样子,待补充
  • ,. 跳转到上一个编辑地点。和'.的作用相同。
  • ,ci change inside any set of quotes/brackets/etc。其作用是删除”, {}, ()之间的字符并进入插入模式

多标签,多窗口,分割模式

  • alt+n - 快速跳转到指定的tab
  • Ctrl-h,j,k,l 在分栏窗口中上下左右移动。
  • Q 关闭当前的window,非常方便
  • vvCtrl-w,v一样,vertical split, 上下分栏
  • ssCtrl-w,s一样,horizontal split,左右分栏
  • ,qo 打开quickfix, qo => quickfix open, 有用
  • ,qc 关闭quickfix, qc => quickfix close, 有用,yadr配置的vim在保存文件时会做语法检查,如果检查不通过会在quickfix窗口显示错误,并且不会自动消失,这时候也许需要 ,qc来关闭它。

其它常用

  • Ctrl-p 循环历史剪切板,这个很有用,p是粘贴,Ctrl+p会粘贴之前的剪切板内容
  • ,yr view yanking, 查看历史复制记录, q退出查看
  • crs, crc, cru cr 应该是 coerce(强制), s是snake, c是camelcase, u是UPPER,转换变量大小写下划线形式,比较有意思,可以使用:help abolish查看更多
  • :NR NarrowRgn,看名字不知道是干什么的,其实是选中一段代码,然后:NRvim会新开一个split把选中的代码放在心的split中操作,操作完后,wq会把结果覆盖掉原来选中的代码。也挺有用的。
  • ,ig toggle visual indentation guids
  • ,cf 拷贝当前文件的完整路径到系统剪切板
  • ,cn 拷贝文件名
  • ,yw yw是从复制从光标位置开始到单词结束,,yw是在任意位置复制整个单词
  • ,ow 使用yank buffer中内容覆盖当前所在的单词,ow => overwrite
  • ,ocf 打开所有git标记为修改过的文件,在splits中打开。使用git版本控制下的文件才有用, ocf => open changed files
  • ,w 去掉尾部多余的空格,应该很有用, StripTrailingWhitespaces
  • sj 把单行的hash表格式化为多行的
  • sk unsplit a link 应该不怎么用
  • ,he he => html escape
  • ,hu hu => html unescape
  • Alt+Shift-A align things, 好像没什么用
  • :ColorToggle 顾名思义
  • :Gitv git log browser
  • ,hi 显示当前高亮的组
  • ,gt Go Tidy, 格式化html代码
  • :Wrap 折叠长行,这个比较有用,特别是quickfix中的提示经常跑出去了 // 好像对quickfix部分不起作用 -_-
  • Cmd-/ toggle comments,在Liux下使用alt
  • gcp comment a paragraph

vim相关

,vr 重新加载vim, vim reload

A标签默认是inline元素,所以标签内容的长短决定了鼠标hover的范围,在有些时候,比如某些导航列表中,需要hover父标签(li)时能hover到a标签。

在制作一个API文档的侧边栏导航列表的时候,html大概是这样的

<div class="toc-macro absolute">
<ul class="toc-indentation">
<li><a href="#id-1">1. 绑定手机号</a></li>
<li><a href="#id-2">2. 查询手机号码是否可以绑定</a></li>
<li><a href="#id-3">3. 添加邮箱</a></li>
<li><a href="#id-4">4. 检查邮箱是否可以添加</a></li>
</ul>
</div>

其他的样式就不具体写了,其实现的效果是在右侧边栏实现一个列表导航。一些主要的样式大概如下:

.toc-macro{ position: fixed; top: 100px; }
.toc-macro ul {list-style-type:none; }
.toc-macro a { text-decoration: none; }
.toc-macro li { padding: 6px 8px 2px;text-decoration:none; }
li:hover { background-color: #efefef;color: #7DB75C; }
.toc-macro a:hover { background-color: #efefef;color: #7DB75C; }

其中使用了决定定位时导航不会随着滚动条而滚动,使我们在查看API文档时总是能看到这个导航。
这样子基本符合我们的需求了,但是有一个小小的问题,那就是鼠标放到导航上如果没有放到a链接上面,li标签会变色,但是由于鼠标没有在a上所以还是箭头的形状。。。这个小小的瑕疵需要解决一下。

首先我们想到的是把a标签的宽度设置到 100%:

.toc-macro a { display:inline-block; width: 100%; }

但是这样似乎并没有按照我们所想的表现出来。我们需要再加一些东西,让a标签的inline-block可以把宽度填充

.toc-macro li { vertical-align: middle; line-height: 15px;}

另外还有一种实现思路那就是把div下所有标签的display属性设置为block,同时a的height设置为100%:

.toc-macro ul { display: block; }
.toc-macro li { display: block;}
.toc-macro a { display:block; height:100%; }

这样实现应该也是可行的,不过没有测试过了。

Go语言提供了一套“新颖的”错误系统,与Java中的异常系统不一样,Go中没有抛出异常的概念

基础知识

如果有一定面向对象语言编程经验的话,肯定会对异常/错误系统有一定的了解。如果使用Java比较多的话,一定会认为抛异常是编程语言天生的一部分,因为在Java中抛异常真的是太家常便饭,无处不在了;但是在Go语言中,根本没有异常这个东西!

看一段官方的QA是怎么解释这个问题的:

We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional.

Go takes a different approach. For plain error handling, Go’s multi-value returns make it easy to report an error without overloading the return value. A canonical error type, coupled with Go’s other features, makes error handling pleasant but quite different from that in other languages.

Go also has a couple of built-in functions to signal and recover from truly exceptional conditions. The recovery mechanism is executed only as part of a function’s state being torn down after an error, which is sufficient to handle catastrophe but requires no extra control structures and, when used well, can result in clean error-handling code.

多值返回为Go提供了一个新的错误处理机制,如果对为什么Go不提供异常体系感兴趣可以在 这里 找到更多的细节。

在之前文章中,已经使用过Go语言的多值返回来返回详细的错误描述。按照规定,错误的类型通常为error,这是一个内建的简单接口

type error interface {
Error() string
}

可以通过实现这个接口定制错误,例如打开文件时的错误:

type PathError struct {
Op string
Path string
Err error // 由系统调用返回
}

fanc (pe *PathError) Error() string {
// 返回定制的错误信息
return pe.Op + " " + pe.Path + ": " + pe.Err.Error()
}

effective go中有一个示例非常有利于我们理解错误的使用,这段代码的作用是创建一个文件,如果创建失败,检查是不是磁盘空间不足,如果是则删除掉一些临时文件,再尝试创建一次:

for try:=0; try<2; try++ {
file, err := os.Create(filename)
if err == nill {
return //没有错误
}
// 检查是不是空间不足
if e,ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC {
deleteTempFiles() // 删除一些临时文件
continue // 再创建一次
}
return
}

可以查看源码中PathError的实现 https://golang.org/src/os/error.go

Panic函数

函数可以通过将error作为额外的返回值来向调用者报告错误,对于一些“致命”的错误,他们导致程序不能继续运行了,Go内建了一个Panic函数,它会产生一个运行时错误并重孩子程序。该函数接受一个任意类型的实参在程序终止时打印。

只需要在需要它的地方调用一下panix()就可以了。

func init() {
if user == "" {
panic("user变量必须有值")
}
}

这是一个示例,但是如果是设计库函数则应该避免使用panic,一个常见的使用panic的场景是初始化,如上面的示例。

恢复

panic函数被调用后,程序将终止当前函数的执行。并开始追溯Go程的栈,运行任何被推迟的函数。我如果回溯到栈顶,程序会终止。
我们可以通过使用内建的recover函数来重新取回Go程的控制权。
调用recover将停止回溯,并返回传入panic的是西安。因为回溯只有被推迟执行的函数中的代码在执行,所以recover也只能在被推迟的函数中才有效。

一个应用:在服务器中终止失败的Go程而无需杀死其他正在执行的Go程:

func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work)
}
}

func safelyDo(work *Work) {
// defer 函数是被推迟执行的
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}

如果do函数中调用panic函数,回溯会执行defer的函数,这时recover会起作用,打印错误信息后退出Go程。
可以使用上面的思路简化错误处理。

effective go 中的部分笔记,这是第四部分

部分笔记摘要,参考: https://go-zh.org/doc/effective_go.html

并发

通过通信共享内存

并发编程中很麻烦的一点是对共享变量访问的控制,在一般环境中,使用同步互斥锁机制等实现互斥的访问,使得代码繁琐而且难以理解。Go语言提出了一个独特的机制:信道。
Go将共享的值通过信道传递。在给定的时间点,只有一个Go程能访问信道中的值,在设计上杜绝了数据在同一时间点被放线程访问。

不要通过共享内存来通信,而应通过通信来共享内存。

为了方便理解,可以把Go的并发处理方式看做类型安全的Unix管道的实现。

Go程/Goroutines

Go程是Golang提出的术语,Go程有简单的模型,它是与其他Go程并发运行在同一地址空间的函数。Go程可以看作是轻量级的线程,它的开销几乎只有栈空间的分配,它的使用很廉价。
Go程在多线程操作系统上可以实现多路复用,后面会详细讲到。多路复用在多线程编程中非常重要。

那如何运行一个Go程呢?很简单,只需要在要调用的函数前添加”go”关键字就能在Go程中运行这个函数,函数调用结束时,Go程也就自动退出。

go list.Sort()
// 使用函数字面量运行
go func() {
// do something here
}

信道/Channels

前面已经提到了信道的概念,在多线程中,具体怎么使用呢,下面就详细介绍信道(channel)

之前讲过,在Go中切片,映射和信道都使用make来分配内存,make返回的值充当对底层数据结构的引用。信道的声明和初始化示例如下:

// 前两个是无缓冲的正数类型信道
c1 := make(chan int)
c2 := make(chan int, 0)
// 指向文件指针的带缓冲区的信道
c3 := make(chan *os.File, 100)

若不指定第二个参数,信道就是无缓冲的同步信道。使用一个同步信道,一个示例如下:

c1 := make(chan int) // 一个不带缓冲的,同步信道
go func() {
time.Sleep(500 * time.Millisecond)
// send one value to channl
c1 <- 1
}()
// do something here
fmt.Println("wait for goroutine to quir")
<-c1 // 主线程在此等待Go程结束,丢弃信道中的值,因为该值只是用作同步的标识
fmt.Println("go routine done")

看完无缓冲的信道,下面看一下有缓冲的信道。有缓冲的信道可以看作是信号量,操作系统里面学过信号量的东西。信号量可以看作是资源不足时用来限制线程执行,使其等待的机制。常用来限制吞吐量,限制调用的process的数量等。
比如要对Web请求使用Go程处理:

func Serve(queue chan *Request) {
for req := range queue {
sem <- 1
// 考虑到闭包的性质,这里传入值 *Request,
// 如果不使用 *Request则所有Go程处理的是同一个变量req
go func(req *Request) {
process(req)
<-sem
}(req)
}

// 另一种方式是,重新声明一个变量
for req := range queue {
// 参考js中for循环中使用timeInterval函数时要重新声明变量
req := req // 为该Go程创建 req 的新实例。
sem <- 1
go func() {
process(req)
<-sem
}()
}
}

信道中的信道/channels of channels

这部分比较麻烦,待续

并行化/Parallelization

在多CPU核心上实现并行计算。

并发是用可独立执行的组件构造程序的方法, 而并行则是为了效率在多CPU上平行地进行计算。Go仍然是种并发而非并行的语言,且Go的模型并不适合所有的并行问题

目前Go运行时的实现默认并不会并行执行代码,它只为用户层代码提供单一的处理核心。 任意数量的Go程都可能在系统调用中被阻塞,而在任意时刻默认只有一个会执行用户层代码。 它应当变得更智能,而且它将来肯定会变得更智能。但现在,若你希望CPU并行执行, 就必须告诉运行时你希望同时有多少Go程能执行代码。有两种途径可以使用:

  1. 在运行你的工作时将 GOMAXPROCS 环境变量设为你要使用的核心数
  2. 导入 runtime 包并调用 runtime.GOMAXPROCS(NCPU)。 runtime.NumCPU() 的值可能很有用,它会返回当前机器的逻辑CPU核心数。

当然,随着调度算法和运行时的改进,将来会不再需要这种方法。

一个缓冲区泄漏示例/A leak buffer

客户端Go程从某些来源,可能是网络中循环接收数据。为避免分配和释放缓冲区, 它保存了一个空闲链表,使用一个带缓冲信道表示。若信道为空,就会分配新的缓冲区。 一旦消息缓冲区就绪,它将通过 serverChan 被发送到服务器。 serverChan.

var freeList = make(chan *Buffer, 100)
var serverChan = make(chan *Buffer)

func client() {
for {
var b *Buffer
// 若缓冲区可用就用它,不可用就分配个新的。
select {
case b = <-freeList:
// 获取一个,不做别的。
default:
// 非空闲,因此分配一个新的。
b = new(Buffer)
}
load(b) // 从网络中读取下一条消息。
serverChan <- b // 发送至服务器。
}
}

服务器从客户端循环接收每个消息,处理它们,并将缓冲区返回给空闲列表。

func server() {
for {
b := <-serverChan // 等待工作。
process(b)
// 若缓冲区有空间就重用它。
select {
case freeList <- b:
// 将缓冲区放大空闲列表中,不做别的。
default:
// 空闲列表已满,保持就好。
}
}
}

客户端试图从 freeList 中获取缓冲区;若没有缓冲区可用, 它就将分配一个新的。服务器将 b 放回空闲列表 freeList 中直到列表已满,此时缓冲区将被丢弃,并被垃圾回收器回收。(select 语句中的 default 子句在没有条件符合时执行,这也就意味着 selects 永远不会被阻塞。)依靠带缓冲的信道和垃圾回收器的记录, 我们仅用短短几行代码就构建了一个可能导致缓冲区槽位泄露的空闲列表。(没懂TT)

上一段的英文如下,总觉得中文的翻译不太对

The client attempts to retrieve a buffer from freeList; if none is available, it allocates a fresh one. The server’s send to freeList puts b back on the free list unless the list is full, in which case the buffer is dropped on the floor to be reclaimed by the garbage collector. (The default clauses in the select statements execute when no other case is ready, meaning that the selects never block.) This implementation builds a leaky bucket free list in just a few lines, relying on the buffered channel and the garbage collector for bookkeeping.

effective go 中的部分笔记,这是第三部分

部分笔记摘要,参考: https://go-zh.org/doc/effective_go.html

空白标识符

空白标识符是指”_”,是一个特殊的变量标识,在之前的for…range 循环中已经使用过。

为未使用的变量或者包使用

在Go中如果定义了一个变量没有使用,或者导入一个没有使用的包会在编译时报错,未使用的包会让程序体积变大并拖慢编译速度,初始化了而不是用的变量会留下某种隐患。
但是某些时候我们确实需要丢弃一些没有的变量,比如在for range循环中,可能需要丢弃键或者值,或者程序写到中途某些变量已经定义只是还没有使用等等,这时就需要空白标识符了。

// 检查某个路径是否存在
if _, err := os.Stat(paht); os.IsNotExist(err) {
fmt.Println("path not exist")
}

对于未使用的变量和未使用的导入包,可以把它们先赋值给空白标识符”_”关闭编译器的错误,在使用了这些变量后再删掉这些代码:

fd = 32
....
_ = fd
var _ = fmt.Printf // 用于调试使用,结束时删除

接口检查

如果只需要判断某个类型是否实现了某个接口而不需要实际使用接口本身,可以使用空白标识符:

if _, ok := val.(Milk); ok {
fmt.Printf("value %v has implement interface milk", val)
}

// 接口检查示例
type iMilk interface {
getName() string
}
type Milk struct {
name string
}
func (m Milk) getName() string {
return m.name
}
func main() {
var m interface {}
m = Milk{"yili"}
if _,ok := m.(iMilk); ok {
fmt.Printf("value %v has implement iMilk\n ", m)
} else {
fmt.Println("has not implememt")
}
}

内嵌/Embedding

Go语言并不提供典型的类与继承系统,所以也没有子类化的概念。继承是一个重要的概念,但是很多时候更推荐的做法是使用组合,Go提供的内嵌和组合概念差不多,可以将类型内嵌到结构体或者接口中,实现某种组合功能。

一种典型的内嵌的ReadWriter接口的定义:

type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// 一个结合了Reader和Writer的接口
type ReadWriter interface {
Reader
Writer
}

// 也可以内嵌到一个结构体中
type ReadWriter struct {
r *Reader
w *Writer
}

内嵌的接口一般需要实现被内嵌接口的方法,满足其需求,比如要提供Read方法:

func (re *ReadWriter) Read(p []byte) (n int, err error) {
return rw.r.Read(p)
}

而通过直接内嵌结构体,我们就能避免如此繁琐。 内嵌类型的方法可以直接引用
当内嵌一个类型时,该类型的方法会成为外部类型的方法, 但当它们被调用时,该方法的接收者是内部类型,而非外部的。在我们的例子中,当 bufio.ReadWriter 的 Read 方法被调用时, 它与之前写的转发方法具有同样的效果;接收者是 ReadWriter 的 reader 字段,而非 ReadWriter 本身:

type Logger struct {
content string
}
func (l Logger) Log(s string) {
l.content += s
fmt.Println(s)
}
type infoLog struct {
count int
*Logger
}
func main() {
var logger = infoLog{0, &Logger{"sss"} }
// 可以直接调用内嵌类型的方法
logger.Log("embedding function invoke")
}

内嵌类型可能会引起命名冲突的问题。更深层次的命名字段或方法会被覆盖。若相同的嵌套层级上出现同名冲突,通常会产生一个错误。但是如果冲突的名字不会被使用就没有问题……

并发部分有点多,单独做一篇吧

一些vim插件需要提供Lua支持,特别是常见的补全插件,前段时间安装的[yadr](https

下载源码

首先,编译安装嘛,先下载源码, 可是呢这两sourceforge挂掉了,vim是托管在sf上的,导致下载页面也不能访问了,甚至vim的官网 www.vim.org 也不能访问了,幸好vim在github上有一个备份 https://github.com/vim/vim 或者直接访问vim.org的ftp站: ftp://ftp.vim.org/pub/vim/Unix/

// 使用下面之一的方法下载源码
// git下载的话体积会大一些,好处是以后可以方便地更新
git clone git@github.com:vim/vim.git
wget -O ftp://ftp.vim.org/pub/vim/Unix/vim-7.4.tar.bz2
wget ftp://ftp.vim.org/pub/vim/Unix/vim-7.4.tar.bz2

tar xjvf vim-7.4.tar.bz2
cd vim74

编译

vim的编译其实很简单,就configure->make->make install 这样的流程。但是要添加 Lua支持,就有一些麻烦了。

configure的配置大概是这样的:

./configure --prefix=/data/vim74 --with-features=huge --with-luajit --enable-luainterp=yes

首先如果不在 configure配置那手动打开 --enable-fail-if-missing 这个选项,你会发现,configure没有问题,make没有问题,make install也OK,但是运行生成的vim: vim --version会发现Lua前面还是一个”-“(表示没有Lua支持)
因为其实configure那根本就没有找到lua的支持,只是默认跳过了 --enable-luainterp=yes 选项。。。
所以应该这样运行configure:

./configure --prefix=/data/vim74 --with-features=huge --with-luajit --enable-luainterp=yes --enable-fail-if-missing

如果你的机器没有安装lua 和luajit的话会在检查lua支持那里中断了。

安装Lua和LuaJit

Lua的安装倒是比较方便,官方repo里面就有,当然要想安装新版的也可以自己编译安装。

sudo yum install lua lua-devel -y

luajit不在centos的官方repo里面,我们需要编译安装;

wget http://luajit.org/download/LuaJIT-2.0.4.tar.gz
tar -xzvf LuaJIT-2.0.4.tar.gz
cd LuaJIT-2.0.4
// 使用默认安装路径
make
sudo make install

编译安装

安装好lua支持后,再运行configure就不会报错了

./configure --prefix=/data/vim74 --with-features=huge --with-luajit --enable-luainterp=yes --enable-fail-if-missing
make
sudo make install

运行

运行编译的vim:

/data/vim74/bin/vim

可能你会发现这样的错误:

/data/vim74/bin/vim: error while loading shared libraries: libluajit-5.1.so.2: cannot open shared object file: No such file or directory

显然是安装的luajit有问题。我们找一下luajit这个.so文件在哪里

sudo find / -name libluajit-5.1.so.2

发现下面有这个文件:

  1. /home/wuxu/tars/ngx_openresty-1.7.10.2/build/luajit-root/data/openresty/luajit/lib/libluajit-5.1.so.2
  2. /usr/local/lib/libluajit-5.1.so.2
  3. /data/openresty/luajit/lib/libluajit-5.1.so.2

显然1,3是openresty编译的,第二个才是我们安装luajit支持,我们需要给这个.so生成一个软链接,让vim能找到它:

sudo ln -s /usr/local/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2

这样再运行vim就不会有问题了~也有Lua支持了,不过Vim的自动补全还没用过呢,学会了再做记录吧。

在php中经常会使用memcache或者memcached来做缓存系统,为了方便操作经常会对PHP提供的接口进行一层封装

todo

对于频繁的字符串操作,可以使用bytes.Buffer接口提供的方法快速拼接

和一般语言类似,Go也为string重载了”+”操作符,可以使用它进行字符串拼接,但是在C#和Java中对于大量的字符串拼接操作推荐使用StringBuilder这样的类,同样对于Go中的大量字符串拼接也不推荐直接使用”+”操作。

可以使用bytes.Buffer提供的方法进行快速拼接:

import (
"bytes"
"fmt"
)
func concat() string {
var buffer bytes.Buffer

for i:=0; i<1000; i++ {
buffer.WriteString("abcd")
}
str := buffer.String()
// do something to str
...
return str
}

这样可以获得O(n)的时间复杂度。

参考: stackoverflow.com/

前段时间去php.net上闲逛发现PHP7的beta1版本出来了,就尝试着编译安装了一下

之前在鸟哥的博客里看到了他们做的PHP的性能测试,相对于PHP5.6都是有很大的提升的,并且PHP的主版本号已经是2004年发布PHP5后,11年来首次更新,肯定PHP7是有很大的改变的,下面那就来试试PHP7的beta版吧,正式版大概在今年年底会发布。

下载源码

测试版本的PHP下载页面: https://downloads.php.net/~ab/

php7 beta1: https://downloads.php.net/~ab/php-7.0.0beta1.tar.gz

wget https://downloads.php.net/~ab/php-7.0.0beta1.tar.gz
或者
curl -O https://downloads.php.net/~ab/php-7.0.0beta1.tar.gz
阅读全文 »

effective go 中的部分笔记,这是第二部分

部分笔记摘要,参考: https://go-zh.org/doc/effective_go.html

方法/Methods

与一般的面向对象语言不同;一般的面向对象语言定义类,然后在类中定义属性和方法,通过类的继承来抽象一套机制,但是在Go中,首先是定义结构,然后为已经命名的结构(除了指针或接口)定义方法,这里有一个方法接收者的概念,为一个结构体绑定方法的常用用法如下:

type Book struct {
price float64
}

func (p Book) Disount(c float64) float64 {
// 注意int和float64类型不一样,
// 在Go中并不能直接操作,需要进行类型转换
// 但是float64 可以与字面量值直接相乘... p.price*100不会有问题
return p.price * c
}

对一些方法,传入的参数是值的拷贝而不是地址,经常需要在方法的结尾放回新的结果,可以通过把参数的指针传入,直接修改参数的地址指向的值,常见的io.Write接口就是在指针添加方法。

type byteSlice []byte
func (slice *byteSlice) Write(data []byte) (n int, err error) {
...
}

var b byteSlice
fmt.Fprintf(&b, "this is a string", 7)

以指针或值为接收者的区别在于:值方法可通过指针和值调用, 而指针方法只能通过指针来调用

接口与其他类型/interface

Go中的接口为指定对象的行为提供了一种方法:如果某样东西可以完成这个, 那么它就可以用在这里。我们已经见过许多简单的示例了;通过实现 String 方法,我们可以自定义打印函数,而通过 Write 方法,Fprintf 则能对任何对象产生输出。

Go中的的接口只是需要实现它的类接受了同名的方法即可,这是很特别的。

类型转换

下面的代码:

type squence []int
func (s squence) String stirng {
// do some thing to s
return ftm.Sprint(s)
}

如果忽略类型名,squence和[]int其实是相同的,只不过squence有一个新的类型名而已,所以在他们之间进行类型转换是可以的:

is := []int(s)

在Go语言中,为了访问不同的方法集会经常进行类型转换。甚至对int和float之间的简单运算都会用到类型转换。

接口转换与类型断言

对于混合类型,我们经常需要将一种接口转换为另一种接口,要判断一个值是否实现了某个接口,需要使用到类型断言。类型断言接受一个接口值,并从中提取指定的明确的明确类型的值。与其他语言判断类型不同,它的用法如下:

var.(typeName)
str.(string)

// 对于使用接口定义的值,可以使用下面的方式
// 其中iv是用 var iv interface{} 定义的,Milk是一中类型
switch str := iv.(type) {
case string:
fmt.Println(str)
case Milk:
fmt.Println("milk", str)
default:
fmt.Println("interface type:", str)
}

这是一个看起来很奇怪的用法,不知道Go中有没有提供想instanceof这样的关键字。

通用性/Generality

这部分还不是很了解

若某种现有的类型仅实现了一个接口,且除此之外并无可导出的方法,则该类型本身就无需导出。 仅导出该接口能让我们更专注于其行为而非实现,其它属性不同的实现则能镜像该原始类型的行为。 这也能够避免为每个通用接口的实例重复编写文档。

在这种情况下,构造函数应当返回一个接口值而非实现的类型。

接口和方法

Go中几乎任何类型(除了指针和接口)都能添加方法(添加的示例已经看过很多了,就不赘述了),因此几乎任何类型都能满足一个接口(即使是基于int的类型也能用来满足一个特别的接口),常见的用法就是任何实现了ServeHTTP方法的对象都能处理HTTP请求

甚至可以为func添加方法,当然需要先将函数定义为一种新类型

Handler接口定义如下:

type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

下面是一个实现该接口的示例:

import "net/http"

type milkServer struct {
n int
}
// 注意参数中的http不能少,参考Go的包机制
func (m *MilkServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
m.n++
// do some thing
fmt.Fprintf(w, "request count at %d\n", m.n)
...
}

// 添加绑定
m := new(Milk)
http.Handle("/count", m)
// 也可以直接绑定一个与ServeHTTP签名相同的函数
func ArgsFunc(w http.ResponseWriter, req *http.Request) {
...
}
http.HandleFunc("args", ArgsFunc)

当然也可以绑定到一个信道上面,然后使用信道新更新一些内部状态:

type Chan chan *http.Request
func (c Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c <- req
fmt.Fprint(w, "request received")
}