讲讲go语言的结构体

Python025

讲讲go语言的结构体,第1张

作为C语言家族的一员,go和c一样也支持结构体。可以类比于java的一个POJO。

在学习定义结构体之前,先学习下定义一个新类型

新类型 T1 是基于 Go 原生类型 int 定义的新自定义类型,而新类型 T2 则是 基于刚刚定义的类型 T1,定义的新类型。

这里要引入一个底层类型的概念。

如果一个新类型是基于某个 Go 原生类型定义的, 那么我们就叫 Go 原生类型为新类型的底层类型

在上面的例子中,int就是T1的底层类型。

但是T1不是T2的底层类型,只有原生类型才可以作为底层类型,所以T2的底层类型还是int

底层类型是很重要的,因为对两个变量进行显式的类型转换,只有底层类型相同的变量间才能相互转换。底层类型是判断两个类型本质上是否相同的根本。

这种类型定义方式通常用在 项目的渐进式重构,还有对已有包的二次封装方面

类型别名表示新类型和原类型完全等价,实际上就是同一种类型。只不过名字不同而已。

一般我们都是定义一个有名的结构体。

字段名的大小写决定了字段是否包外可用。只有大写的字段可以被包外引用。

还有一个点提一下

如果换行来写

Age: 66,后面这个都好不能省略

还有一个点,观察e3的赋值

new返回的是一个指针。然后指针可以直接点号赋值。这说明go默认进行了取值操作

e3.Age 等价于 (*e3).Age

如上定义了一个空的结构体Empty。打印了元素e的内存大小是0。

有什么用呢?

基于空结构体类型内存零开销这样的特性,我们在日常 Go 开发中会经常使用空 结构体类型元素,作为一种“事件”信息进行 Goroutine 之间的通信

这种以空结构体为元素类建立的 channel,是目前能实现的、内存占用最小的 Goroutine 间通信方式。

这种形式需要说的是几个语法糖。

语法糖1:

对于结构体字段,可以省略字段名,只写结构体名。默认字段名就是结构体名

这种方式称为 嵌入字段

语法糖2:

如果是以嵌入字段形式写的结构体

可以省略嵌入的Reader字段,而直接访问ReaderName

此时book是一个各个属性全是对应类型零值的一个实例。不是nil。这种情况在Go中称为零值可用。不像java会导致npe

结构体定义时可以在字段后面追加标签说明。

tag的格式为反单引号

tag的作用是可以使用[反射]来检视字段的标签信息。

具体的作用还要看使用的场景。

比如这里的tag是为了帮助 encoding/json 标准包在解析对象时可以利用的规则。比如omitempty表示该字段没有值就不打印出来。

map 是Go语言中基础的数据结构,在日常的使用中经常被用到。但是它底层是如何实现的呢?

总体来说golang的map是hashmap,是使用数组+链表的形式实现的,使用拉链法消除hash冲突。

golang的map由两种重要的结构,hmap和bmap(下文中都有解释),主要就是hmap中包含一个指向bmap数组的指针,key经过hash函数之后得到一个数,这个数低位用于选择bmap(当作bmap数组指针的下表),高位用于放在bmap的[8]uint8数组中,用于快速试错。然后一个bmap可以指向下一个bmap(拉链)。

Golang中map的底层实现是一个散列表,因此实现map的过程实际上就是实现散表的过程。在这个散列表中,主要出现的结构体有两个,一个叫 hmap (a header for a go map),一个叫 bmap (a bucket for a Go map,通常叫其bucket)。这两种结构的样子分别如下所示:

hmap :

图中有很多字段,但是便于理解map的架构,你只需要关心的只有一个,就是标红的字段: buckets数组 。Golang的map中用于存储的结构是bucket数组。而bucket(即bmap)的结构是怎样的呢?

bucket :

相比于hmap,bucket的结构显得简单一些,标红的字段依然是“核心”,我们使用的map中的key和value就存储在这里。“高位哈希值”数组记录的是当前bucket中key相关的“索引”,稍后会详细叙述。还有一个字段是一个指向扩容后的bucket的指针,使得bucket会形成一个链表结构。例如下图:

由此看出hmap和bucket的关系是这样的:

而bucket又是一个链表,所以,整体的结构应该是这样的:

哈希表的特点是会有一个哈希函数,对你传来的key进行哈希运算,得到唯一的值,一般情况下都是一个数值。Golang的map中也有这么一个哈希函数,也会算出唯一的值,对于这个值的使用,Golang也是很有意思。

Golang把求得的值按照用途一分为二:高位和低位。

如图所示,蓝色为高位,红色为低位。 然后低位用于寻找当前key属于hmap中的哪个bucket,而高位用于寻找bucket中的哪个key。上文中提到:bucket中有个属性字段是“高位哈希值”数组,这里存的就是蓝色的高位值,用来声明当前bucket中有哪些“key”,便于搜索查找。 需要特别指出的一点是:我们map中的key/value值都是存到同一个数组中的。数组中的顺序是这样的:

并不是key0/value0/key1/value1的形式,这样做的好处是:在key和value的长度不同的时候,可 以消除padding(内存对齐)带来的空间浪费 。

现在,我们可以得到Go语言map的整个的结构图了:(hash结果的低位用于选择把KV放在bmap数组中的哪一个bmap中,高位用于key的快速预览,用于快速试错)

map的扩容

当以上的哈希表增长的时候,Go语言会将bucket数组的数量扩充一倍,产生一个新的bucket数组,并将旧数组的数据迁移至新数组。

加载因子

判断扩充的条件,就是哈希表中的加载因子(即loadFactor)。

加载因子是一个阈值,一般表示为:散列包含的元素数 除以 位置总数。是一种“产生冲突机会”和“空间使用”的平衡与折中:加载因子越小,说明空间空置率高,空间使用率小,但是加载因子越大,说明空间利用率上去了,但是“产生冲突机会”高了。

每种哈希表的都会有一个加载因子,数值超过加载因子就会为哈希表扩容。

Golang的map的加载因子的公式是:map长度 / 2^B(这是代表bmap数组的长度,B是取的低位的位数)阈值是6.5。其中B可以理解为已扩容的次数。

当Go的map长度增长到大于加载因子所需的map长度时,Go语言就会将产生一个新的bucket数组,然后把旧的bucket数组移到一个属性字段oldbucket中。注意:并不是立刻把旧的数组中的元素转义到新的bucket当中,而是,只有当访问到具体的某个bucket的时候,会把bucket中的数据转移到新的bucket中。

如下图所示:当扩容的时候,Go的map结构体中,会保存旧的数据,和新生成的数组

上面部分代表旧的有数据的bucket,下面部分代表新生成的新的bucket。蓝色代表存有数据的bucket,橘黄色代表空的bucket。

扩容时map并不会立即把新数据做迁移,而是当访问原来旧bucket的数据的时候,才把旧数据做迁移,如下图:

注意:这里并不会直接删除旧的bucket,而是把原来的引用去掉,利用GC清除内存。

map中数据的删除

如果理解了map的整体结构,那么查找、更新、删除的基本步骤应该都很清楚了。这里不再赘述。

值得注意的是,找到了map中的数据之后,针对key和value分别做如下操作:

1

2

3

4

1、如果``key``是一个指针类型的,则直接将其置为空,等待GC清除;

2、如果是值类型的,则清除相关内存。

3、同理,对``value``做相同的操作。

4、最后把key对应的高位值对应的数组index置为空。

官方:基因本体(GO)知识库是有关基因功能的全球最大信息来源。 这些知识既是人类可读的,也是机器可读的,并且是生物医学研究中大规模分子生物学和遗传学实验的计算分析的基础。

在读懂基因本体论(Gene Ontology)前,我们先看看什么是本体论:

本体论(Ontology )是探究世界的本原或基质的哲学理论 。

本体论通常处理的问题:存在哪些本质,如何将这些本质分组,在层次结构内关联以及如何根据相似性和差异进行细分 。

基因本体论(Gene Ontology)包含生物学领域知识体系本质的表示形式,本体通常由一组类(或术语或概念)组成,它们之间具有关系。 基因本体论(GO)从三个方面(GO domains)描述了我们对生物学领域的了解:

理解了上述的概念,现在举个例子,如果站在基因本体论GO的角度来解释一个基因的话:

基因产物:细胞色素C(cytochrome c)

分子功能:氧化还原酶活性

细胞组分:线粒体基质

生物过程:氧化磷酸化

自定义同义词类型也用于本体中。 例如,许多同义词被指定为系统同义词。 此类型的同义词是术语名称的确切同义词。

GO以图的形式构建,术语作为同种的节点,术语间的关系(对象属性)作为连接。

GO图中的节点与其他节点可以具有任意数量和类型的关系, 就像层次结构,例如,家谱或一个物种的分类法

一个节点可能与多个子节点(更特定的节点)具有连接,也可以具有多个父节点(较宽的节点)

利用关系与关系间的连接可以推断相应的分组注释,节点间关系的推断,这个会在后面详细研究:

上图表示:A is a B,B is part of C,所以可以推断 A is part of C

节点间总体与部分关系:

一个节点可能与一个节点有一部分关系。 下图说明了这一点:

上图: mitochondrion 是两个节点的父节点:it is an organelle and it is part of the cytoplasm ; organelle 有两个子节点: mitochondrion is an organelle, and organelle membrane is part of organelle

我们将上面的关系图简化表示为 箭头导向性图 ,这是图中常见的关系表示:

接下我们详细看看GO是怎样来描述这几种关系的:

如果我们说 A is a B ,则意味着节点A是节点B的子类型。例如,有丝分裂细胞周期是细胞周期,或者裂解酶活性是催化活性。

应该注意的是,a并不代表是实例。 从本体论上来说,一个实例是某个事物的具体示例。 例如 猫是哺乳动物,但加菲猫是猫的实例,而不是猫的亚型。 GO中的术语表示实体或现象的类别,而不是特定的表现形式(或实例)。 但是,如果我们知道猫是哺乳动物,则可以说猫的每个实例都是哺乳动物。

使用 is a 对批注进行分组是 安全的 。例如,如果将基因产物X注释为具有酪氨酸激酶活性,并且本体论证明酪氨酸激酶活性是激酶活性的一种(类型),那么我们可以安全地得出结论,基因产物X具有激酶活性。

利用上面得到结论,我们可以将 is a 关系和其他关系类型结合来推断,下图表示了可以推断的关系:

关系的一部分用于表示整个部分的关系。 part of 只有当B一定是A的一部分时,才会在A和B之间部分关系:无论B存在于何处,它都是A的一部分,B的存在意味着A的存在。但是,考虑到A的出现,我们不能肯定地说B的存在。

使用的 part of 进行分组注释是 安全的 。 例如,如果将基因产物X标注为位于线粒体内膜上,而本体论记录了线粒体内膜与线粒体之间的关系的一部分,则可以安全地得出结论X位于线粒体内。

利用上面得到结论,我们可以将 part of 关系和其他关系类型结合来推断,下图表示了可以推断的关系:

has part 是对关系部分的逻辑补充,它从父级的角度代表了“部分-整体”关系。

与 part of 一样,GO关系 has part 仅在A始终将B作为一部分的情况下使用,即A必定具有B的部分。 但是,如果B存在,我们不能肯定地说A存在。 即所有A都有B部分,但是A只是B的一部分。

使用 has part 注释进行分组是 不正确的 。 例如,我们可以在本体论中断言受体酪氨酸激酶活性具有部分激酶活性。 然而,将所有注释归类到受体酪氨酸激酶活性下的激酶活性将是不正确的。

利用上面得到结论,我们可以将 has part 关系和其他关系类型结合来推断,下图表示了可以推断的关系:

一种过程直接影响另一种过程或质量的表现,即前者调节后者。 调节的目标可以是另一种过程,例如调节途径或酶促反应,或者可以是质量,例如细胞大小或pH。 与 part of 关系类似,该关系专门用于表示必定的调节:如果同时存在A和B,则B总是调节A,但是A可能不总是受B调节,即所有B都调节A一些A受B调节。

如果将基因产物X注释为参与调节糖酵解的过程,则不能得出结论X参与糖酵解是 不正确的 。 但是,某些工具使用调节关系来对批注进行分组, 这可用于基因集富集, 所得的基因集包括与分组术语有因果关系的过程中涉及的基因。

利用上面得到结论,我们可以将 regulates 关系和其他关系类型结合来推断,下图表示了可以推断的关系:

GO的结构可以用下图来表示,这个图也叫有向无环图(Directed Acyclic Graph ,DAG)。

如上图所示,三个GO域(细胞成分,生物学过程和分子功能)分别由一个单独的根本体术语表示。

一个域中的所有术语都可以将其父源追溯到一个根术语,通过到本体根的中间术语可能存在许多不同的路径。

这三个根节点是不相关的,并且没有公共的父节点,这意味着来自不同本体的术语之间没有任何关系。但是,GO本体之间也存在其他关系,例如,分子功能术语“细胞周期蛋白依赖性蛋白激酶活性”是生物过程“细胞周期”的一部分。GO本体间相关 http://geneontology.org/docs/ontology-relations/ 。

某些基于图的软件可能需要一个根节点。在这种情况下,可以将“假”术语添加为三个现有根节点的代。

GO只代表生物学的当前认知,因此随着生物学知识的积累,它会不断地被修订和扩展。也就是说目前的GO术语不一定代表某个基因产物所有的功能,组分或参加的过程,只是现阶段对它的认知。

每周更新一次,由GOC本体团队与请求更新的科学家共同完成的。