goroutine
channel
Just for fun.
就个人来说,程序员写公众号,最重要的是什么?
不是粉丝,不是出名,不是收益(不在意是假的),而是学习、总结、创作、分享(至少可以实践费曼学习技巧)。
定好初心,然后出发,坚持(出发难,坚持更不简单),剩下会得到什么则交给上帝决定。
这里每个人的答案各不一样,什么样的初心,就会写出什么样的文章。
看别人写的文章似乎觉得很容易,那是看起来容易……
写作有两种:一种是抄,一种是原创。
抄呢看着不难,实际操作起来估计也不简单,要高质量的抄,一得选对好文章,二也得调整润色等等让人看不出来是抄的;而要大批量的抄,至少得会写个爬虫吧。
目前为止,自己发过的文章都是坚持原创:自己选好题,自己构思结构,自己写内容,自己写例子。原创有多难呢,有时一篇文章写个一周都觉得写不好,写写改改,一个最简单的文章估计至少也得一两个小时,也许是不熟练的缘故,真是很佩服那些天天都能发文的人。
写文章带来的好处:为了能更好的输出,自然就需要更多的输入,输入的素材可以是自己觉得未吃透的某个专题深入,也可以是自己感兴趣想学习的最新技术,另外写作过程其实就是实践费曼学习技巧的过程,因此对技术和知识可以有更深刻的理解,这样的过程自然而然就给自己带来了进步,过程中可能没感觉,但一段时间后就会感觉到。
除了自我不断进步的内部正向反馈,还有就是假如写了一篇自己感觉不错的文章,也受到别人的认可,那么这其中带来的满足感是很充盈的,进而促使自己坚持下去,然后写更好的文章,这可以算是外部的正向反馈。
文章除了公众号,还可以发到更多的平台,增加曝光度,目前同步发布的有:
还有很多别的平台,不过一开始的时候可以先选好一两个目标平台,等整个发文过程熟悉熟练后,再添加更多别的平台同步发布。
多平台写作必须得用Markdown,一次写作,同时发布到多个平台。这其中个人用的Markdown编辑器是Typora,功能全,兼容性好,所见即所得,不需要非常精通Markdown语法,可以傻瓜式可视化编辑排版,也可以进入专业模式直接撸Markdown语法。
目前很多平台是支持Markdown发文的,Markdown的文章写好了可以直接发布,对于公众号、头条号等不支持Markdown的,可以用http://md.aclickall.com/在线一键排版了,然后拷贝到公众号、头条号里。
另外对于公众号还可以使用微小宝这个软件做辅助编辑,里面包含各种模版,可以拿来用用,不过呢一开始时用得挺欢,后面就返璞归真,就只是文章了,估计是懒或用得不熟练的缘故。
先回顾下面向对象的三个基本特性:
Java作为面向对象的王者,以下示例完美的展现了面向对象的三个基本特征。
1 | public class Main { |
那么Go语言呢?我们一起来探索下Go语言的面向对象,先从类的封装开始。
员工薪酬示例中,Java里的Employee类是这样的:
1 | public abstract class Employee implements Payroll { |
这里暂时先关注员工类的三个属性,Go里面它确实没有类,要表示上面的员工对象,它是用struct的(和C很像):
1 | type Employee struct { |
看起来简洁多了,注意到Go里面没有private public的修饰符,它是用属性的首个字母大小写来区分private public,小写字母开头的是私有属性,大写字母开头表示公开属性。对象的实例化和使用对比:
1 | //由于Employee是抽象类,不能实例化,直接用子类TechEmployee实例化 |
1 | //这里的object.是包名,Employee放在object目录下 |
另外,struct中是没有方法(行为)的,少了这个不是等于封装特征缺了只脚吗?当然不是了,Go语言里对应面向对象里的成员方法不是定义在struct里面,而是直接定义在struct外面,和struct平级,这里定义一个类似Java中的toString方法:
1 | type Employee struct { |
这里(e *Employee)叫做方法的接收者,有点怪异,我们可以这样理解:
接下来继承,先看看Java的:
1 | public class TechEmployee extends Employee { |
同样,Go里面也没有像Java中类似extend继承的语法,Go是用了类似Java里组合的东西来让语法看起来像继承:
1 | type TechEmployee struct { |
对应的实例化和使用:
1 | //实例化时,是传了个employee |
关于多态,必须要提接口,终于Go里也是有接口的了:
1 | public interface Payroll { |
1 | type Payroll interface { |
接口的实现:
1 | public class Machine implements Payroll { |
1 | type TechEmployee struct { |
可以看出,Java里比较直观,语法里直接写着实现xxx接口,Go相比的话,没那么直观,但更灵活,它没有指定实现哪个接口,而是如果定义了一个相同名字和返回值的方法,就认为是实现了对应拥有这个方法的接口,这里假如接口有两个方法,对应也必须要两个方法都有定义了,才认为是实现了接口。
最后,看一下集成使用的对比:
1 | List<Payroll> all = new ArrayList<>(); |
1 | var all [3] object.Payroll |
1 | rec := map[string]int{"width": 1, "height": 3} |
1 | rec1 := make(map[string]int) |
以上两种声明和初始化方式效果是一样的,根据实际情况选用。试试访问不存在的Key看会怎样?
1 | fmt.Println(rec["point"]) |
结果会输出:0,不会报错!它返回了int的默认值:0。
那怎么判断一个Key到底是否存在呢?可如下实现:
1 | if val, ok := rec["point"]; ok { |
1 | for k, v := range rec { |
1 | delete(rec, "width") |
Go作者之一是Thompson,他发明了后来衍生出C语言的B程序语言,作为鼻祖,设计的新语言指针必须有。Go里的指针和C的指针语法使用上基本一样,区别是Go里不能对指针进行算术运算。
1 | func pointers() { |
上面的new可以看成是C里面的malloc,为类型分配对应的内存空间,然后返回对应内存的指针即内存地址,new函数相当于下面的函数功效:
1 | func newInt() *int { |
1 | var firstArray [2] int |
这是第一种数组声明方式,未初始化的元素值默认为0,需要指明数组的长度,然后使用下标索引方式初始化和访问元素。
我们知道Java中数组还有这种初始化方式,直接初始化具体元素,数组长度自动获得:
1 | int[] intArray = new int[]{1, 2, 3, 4, 5}; |
对应在Go中是这样的:
1 | secondArray := [...]int{1, 2, 3, 4, 5} |
有点怪异,不习惯,第一次使用还有可能会把[…]中的…省略了,如果省略了,那就不是一个数组了,而是一个slice,可以用下面的代码验证一下:
1 | fmt.Println(reflect.TypeOf([]int{1, 2})) |
输出结果是:[]int和[2]int。看着又是很相似的结果,但[]int是slice,[2]int是数组,中括号中有数字表示是数组。
说到slice,slice是什么?先看下slice怎么得到:
1 | sliceOfArray = secondArray[0:1] |
上面的输出结果也是[]int,和上一段代码:fmt.Println(reflect.TypeOf([]int{1, 2}))输出是一样的。这个slice和Python里的列表slice很像(Java数组没有slice功能,只能用apache-commons中的ArrayUtils实现类似功能):
1 | int_list = [1, 2, 3, 4, 5] |
从表面使用上看的话,就是取子数组或子列表的功能,但更深入看,Python中的列表slice操作返回的是一个新的子列表,而Go中slice操作返回的不是一个新的子数组,而是一个和原数组共用内存的slice,这样的设计可以减少切片操作时的内存消耗,提高性能,这里我们可以把生成的slice看做的原数组的一个子片断视图,它本身不存数据,访问到的元素,还是原数组里的元素。
1 | msg := "hello world" |
单行的字符串初始化和大部分语言一样,使用双引号,支持和python一样的多行,但是用反单引号,就是键盘上按键区最左上角的那个符号。
1 | msg := "hello world" |
上面两个打印字符串长度的代码会输出多少?11和4吗?错了,答案是11和16,len方法取得的结果是字符串所占用的字节数,go语言中的字符串使用的是可变长的UTF8编码,ASCII码占用1个字节,其它字符是2-4个字节,对于中文字符是3个字节。那如何取得我们预想的字符串长度:11和4?可以使用如下的两个方法:
1 | fmt.Println(utf8.RuneCountInString(msg)) |
这里的rune可以认为用于表示一个utf8编码,如果想把汉字字符串,一个个打印出来,可用如下方法实现:
1 | cnMsg := "你好世界" |
1 | msg := "hello world" |
对于ASCII码字符串可以使用上面的方法进行类似Java substring的操作,注意最后一个返回的是h的ASCII码值。对于中文字符如果也像上面的方式操作,打印出来的结果会是乱码,因为上面的操作是基于字节索引的,中文的substring要像下面这样操作,先转成rune表示的UTF8编码数组,然后基于UTF8编码索引:
1 | cnMsg := "你好世界" |
关于IDE,开发时推荐JetBrains的GoLand,JetBrains系列IDE的好无需多言了,不过建议第一次跑hello world时,还是用简单的文本编辑器(sublime text/vs code/atom等)和go的命令,手动操作后加深理解。
安装完后go后,默认的GOPATH可以通过下面的命令获得:
1 | go env GOPATH |
如果只输go env的话会把go的所有相关环境变量打印出来。
默认的GOPATH一般都在用户目录下,一般要设置修改成自己的工作目录:
windows在系统属性中添加环境变量GOPATH=D:\workspace。
linux
1 | export GOPATH=$HOME/workspace |
设置上述GOPATH环境变量的同时,可以把%GOPATH%\bin(windows)或$GOPATH/bin(linux)追加到path环境变量中,方便生成的go程序使用命令行调用时,可以在任意目录执行。
上一步设置好的GOPATH就是go的workspace了,go的workspace目录结构通常是这样的:
workspace
-src
-pkg
-bin
可以把pkg认为像java里的放jar包的目录一样,是生成的库文件。
bin目录放生成的可执行文件。
关键看src,src的目录结构一般如下:
src目录下面第一层是代码版本管理库的服务器名称,像如图所示的github.com,也可以是gitee.com,或者是xx.com,只是表示代码版本管理库的服务器名称,可以是git,也可以是svn。
再下一层,对于github来说,一般是对应用户,如图github.com下面有两个用户一个是ethereum,一个是golang,然后再下一层就是具体的项目,这里不是必须这样,如果svn就可以直接是具体的项目名了。
当到了项目一层目录时,再里面的子目录和文件,就和别的语言里差不多了,具体项目的工程文件在这个目录里。
用JetBrains GoLang打开golang的example项目时,长这样:
1 | package main |
hello world的代码看上去还好,比java简洁,和python一样语句不用分号来结束。
1 | var i int = 0 |
上面是一个最完整的变量声明语句,这个看着就稍微有点奇怪了,有点像javascript,后面又多了个类型声明,习惯了java里类型声明在前面,或者像javascript,python一样没有类型声明。
不过就如刚才所说,上面是一个最完整的变量声明语句,实际还可以这样声明使用:
1 | var i = 0 |
嗯,这还差不多,很javascript了。变量的类型由变量的初始化值决定。
还可以更简单吗?
1 | i := 0 |
啧啧,nice!不过注意,这种写法是声明变量的同时进行初始化,当已经写过一个i := 0时,后面就不能再来个i := 1了,因为这样表示声明了两个变量,名字都为i,:=在这里是声明变量且赋值初始化。
再来个惊叹的:
1 | x, y := 1, 2 |
上面的两个语句做了什么?第一行是声明并初始化了两个整型变量x和y,第二行把x和y的值对调!对调两个变量的值竟然可以这么简单!对比下java的版本:
1 | int x = 1; |
1 | i := 10 |
条件分支看起来就差不多了,和java相比省略了括号,别的差不多。
1 | for i := 0; i < 5; i++ { |
for循环看起来也差不多,和java相比,少了括号。
1 | word := []string{"h", "e", "l", "l", "o"} |
关于range,可以用来遍历数组,列表,键值对,这里仅以数组为例说明。
当对数组使用range时,会返回两个变量,第一个变量是数组的索引,第二个变量是数组对应索引的值。
这和java里的for … in …有点像,但这个支持取索引,和javascript中的each也挺像。
上面第二个for写法第一个变量是一个下划线,表示对index不感兴趣,这里就用下划线来占位。
类似,第三个for对value不感兴趣,就把value用下划线来占位。
第四个for其实效果和第三个for一样,如果只写一个变量,默认是index。
1 | for { |
go语言很奇葩,竟然没有while,上面的三个for分别对应java里的:
1 | while (true) { |
go语言的初体验先到此为止,语法上确实有它简单的地方,也有它奇葩的地方,看看,写写,跑跑,go语言之路走起。
最初接触区块链相关时,看了很多文章,真的是云里雾里,不知所云,区块链到底是什么?能干什么?看半天还是没明白,相信很多人都有类似的感觉。
对数据结构有印象的同学,应该脑子里会浮现出现链表,是的,它们俩很像。
所以从这个角度讲,区块链本身这个词就描述了它的数据结构属性,它是拿来存数据的。
具体来讲,一个区块,包含区块头和区块体,区块头包含了时间戳和上一个区块的哈希等信息,区块体则是一个交易列表,整个区块进行哈希计算后生成自己的哈希,后面的区块按同样的方式包含了上一个区块的哈希,这样就把所有区块串起来变成一条链。
由于每个区块都包含了前一个区块的哈希,如果想要篡改第3个区块,那么第3个区块后面的区块也要全部重新计算哈希全部篡改过去,这样的结构保证了区块链数据很难被篡改。
那区块链一般拿来存什么的?最常见的应用就是账本,我们可以很容易的把区块链的结构和账本类比对应起来,一个区块相当于账本里的一页,一页的账目流水相当于区块中的交易列表,我们按时间顺序记账,唯一区别是,区块链可以无限增长,账本是有限的,用完就得换一本。所以区块链也常常被称为超级账本,有些开源项目的名称就会含有ledger这个词。
如果区块链只是一种数据结构,那么它就不会被那么多人追捧了。上面提到区块链数据很难被篡改,那只是很难,如果区块链数据是中心化存储的,那只要对这个中心化存储有控制权的人完全可以任意修改,区块链的创新之处在于,它有很多去中心化的节点,每个节点上都有一条自己的链,不同节点通过同步,保证和网络上别的节点上的链处于一致状态,那些链状态不正确的节点会被其它节点剔出网络,这样某些恶意个人或组织,想篡改数据就得控制网络上大量的节点,这是非常非常困难,几乎不可能的。
聪明的同学可能会问,这么多节点,每个节点上都有链,那谁来记账,也就是谁来生成新的区块呢?以谁的为准呢?这就引出了共识机制,以比特币为例,每个节点都可以参与记账,但比特币网络使用了叫POW工作量证明的共识机制,通俗讲就是出一道有难度的数学计算题,第一个计算出来的节点获得记账权,然后这个节点把内存里的交易按时间排序打包到区块追加到自己的链上,同时广播出去,别的节点这时只需同步即可,这个过程,比特币也叫做挖矿。
除了最常见的记账应用,如果要应用在别的领域,能实现吗?要修改底层实现吗?不需要,区块链2.0引入了智能合约,智能合约就是拿来在区块链上扩展实现各种不同的应用,可以把智能合约当作区块链上的二次开发语言,它可以定义业务对象的属性,状态,以及不同个体或组织对业务对象可以做哪些操作(即区块中的交易),通过智能合约把业务合约化,自动化。
聪明的同学又问了,这链上的资产类业务对象,任何人都可以操作吗?归属于谁的?继续以比特币为例,张三拥有100个比特币,这100个比特币怎么确认是张三的,怎么保证只能由张三花掉?这里用到了RSA的公钥和私钥,100个比特币在数据层面是一个input,input里包含了张三的公钥信息,100的数量,还有用张三公钥生成的一个锁定脚本,这个锁定脚本只能用张三的私钥去解锁,这样就保证了资产的归属和使用权。
总结一下,区块链的主要特征:
因此区块链也被称为下一代互联网:可信的价值互联网。
我们想象一下,现实世界中的各种资产对象,放到链上存储和跟踪,然后不同的个人和组织可以方便的在一个公共的可信网络和存储里按照共同认可的规则互相协作,不同的企业,机构,组织之间的壁垒被打破,业务运作流转更自动化,更高效,更可信。再想想我们当前的状态:这个企业自个儿弄个系统,另外一个机构又弄一个系统,各自有各自的数据,谁也不知道对方数据到底有没有被改过,再来系统对接,联调,甚至跨国机构、组织,想想就觉得费劲。