0%

B-Tree
  在计算机科学中,B树(B-Tree)是一种自平衡的树,能够保持数据有序.这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数(logN)时间内完成.B树,概括来说是一个一般化的二叉查找树,一个结点可以拥有2个以上的子节点(注意,二叉树每个节点最多只有两个子节点).与自平衡二叉查找树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘,B树减少定位记录时所经历过的中间过程,从而加快存取速度.B树这种数据结构可以用来描述外部存储,这种数据结构常被应用于数据库和文件系统的实现上.

B-Tree运用的理念

  • 保持键值有序,以顺序遍历
  • 使用层次化的索引来最小化磁盘读取
  • 使用不完全填充的块来加速插入和删除(减少平衡操作)
  • 通过优雅的遍历算法来保持索引平衡

B-Tree的弊端

  • 除非完全重建数据库,否则无法改变键值的最大长度

B-Tree的一些术语

定义

一个m阶的B树是一个有以下属性的树

1.每一个节点最多有m个子节点
2.每一个非叶子节点(除根结点)最少有[m/2]个子节点
3.如果根节点不是叶子节点,那么它至少有两个子节点
4.有k个子节点的非叶子节点拥有k-1个键
5.所有叶子节点都在同一层
每一个内部节点(非叶子节点)的键将节点的子树分开.例如,如果一个内部节点有3个子节点(3个子树),那么它就必须有两个键:a1和a2.左子树的所有值都必须小于a1,中间子树的所有值都必须在a1和a2之间,右边子树的所有值都必须大于a2.

upload successful
上图是一个n阶B树的一部分,其中叶子节点最多有n个结点,内部节点(非叶子节点)有3个子节点,2个k键.node1指向的子树中,b1-bn所有值都小于a1,node2指向的子树中,c1-cn所有值都介于a1和a2之间,node3指向的子树中,d1-dn所有值都大于a2.

内部节点
  内部节点是除根结点和叶子结点之外的所有节点.它们通常被表示为一组有序的元素和指向子节点的指针.每一个内部节点拥有最多U个,最少L个子节点.元素的数量总是比子节点的数量少一(元素的数量在 L-1 和U-1之间),U必须等于2L或者2L-1;因此,每一个内部节点都至少是半满的.U和L之间的关系意味着两个半满的节点可以合并成一个合法的节点,一个全满的节点可以被分成两个合法的节点(如果父节点有空间容纳移来的一个元素).这些特性使得在B树中删除或插入新的值时可以调整树来保持B树的性质.

根节点

根节点拥有子节点数量的上限和内部节点相同,但是没有下限.例如,当整个树中的元素数量小于L-1时,根节点是唯一的节点并且没有任何子节点.

叶子节点

叶子节点对元素的数量有相同的限制,但是没有子节点,也没有指向子节点的指针.

算法

搜索
  B树的搜索和二叉搜索树类似。从根节点开始,从上到下递归的遍历树。在每一层上,搜索的范围被减小到包含了搜索值的子树中。子树值的范围被它的父节点的键确定。

插入
  所有的插入都从根节点开始。要插入一个新的元素,首先搜索这棵树找到新元素应该被添加到的对应节点。将新元素插入到这一节点中的步骤如下:

1.如果节点拥有的元素数量小于最大值,那么有空间容纳新的元素。将新元素插入到这一节点,且保持节点中元素有序。
2.否则的话这一节点已经满了,将它平均地分裂成两个节点:
(1) 从该节点的原有元素和新的元素中选择出中位数
(2) 小于这一中位数的元素放入左边节点,大于这一中位数的元素放入右边节点,中位数作为分隔值。
(3) 分隔值被插入到父节点中,这可能会造成父节点分裂,分裂父节点时可能又会使它的父节点分裂,以此类推。如果没有父节点(这一节点是根节点),就创建一个新的根节点(增加了树的高度)。

upload successful
  如果分裂一直上升到根节点,那么一个新的根节点会被创建,它有一个分隔值和两个子节点。这就是根节点并不像内部节点一样有最少子节点数量限制的原因。每个节点中元素的最大数量是 U-1。当一个节点分裂时,一个元素被移动到它的父节点,但是一个新的元素增加了进来。所以最大的元素数量 U-1 必须能够被分成两个合法的节点。如果 U-1 是奇数,那么 U=2L ,总共有 2L-1 个元素,一个新的节点有 L-1 个元素,另外一个有 L 个元素,都是合法的节点。如果 U-1 是偶数,那么 U=2L-1,总共有 2L-2 个元素。 一半是 L-1,正好是节点允许的最小元素数量。

删除

有两种常用的删除策略

  1. 定位并删除元素,然后调整树使它满足约束条件;
  2. 从上到下处理这棵树,在进入一个节点之前,调整树使得之后一旦遇到了要删除的键,它可以被直接删除而不需要再进行调整

B+Tree
  B+Tree是一种数据结构,通常用于数据库操作系统的文件系统中.B+Tree的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数(logN)时间复杂度.B+Tree元素自底向上插入,这与二叉树相反.

第一个:http://www.iteye.com(www.iteye.com)
不解释。

第二个:http://www.infoq.com/cn/(英文主站:www.infoq.com)
非常著名的架构师站点,主要面向企业架构,做JaveEE方向的同学应该经常看。

第三个:http://www.ibm.com/developerworks/cn/,IBM developerWorks CN
IBM的网站,文章范围很广,但主要也是偏向Java方向。另,这个站的稿费还算比较可观,200元/千字,想赚点辛苦费的同学可以考虑一下,就是周期太长(登稿大概要3个多月,发放稿费能拖到一年之后,鄙视)

第四个:http://www.oschina.net/,开源中国
关注最新的开源进展。社区比较杂,比JE还乱,但招聘区比JE好不少,比较多猎头活跃。

第五个:http://www.blogjava.net/,BlogJava
这个开始有点凑数的嫌疑了,其实去的并不多。BlogJava远不如博客园主站热闹啊。

第六个:蓝色理想论坛的JS版
做JS、UI/UE的人估计都知道,它人气也许比不上51js,但肯定无愧于它的论坛的标题“经典论坛”。这个也是我去得历史最久的站,不过在这里混了超过11年都没混出个靠谱的JS水平,我确实是有够菜的= =#

至于神马“床上等你”、“51cto”、“itpub”、“赛迪网”之类的,倒是非常出名,但是我自己基本不去就不推了。下面是一些并非众人皆知,但是个人觉得优秀、值得推荐的网站,大多是博客:

第一个:http://coolshell.cn/,酷壳。
我当作娱乐站点来看,博主的观点我并不全部同意,但博主的文风我确实很佩服,实在太欢乐了。很多非常有趣好玩的文章,譬如这个。

第二个:http://rednaxelafx.iteye.com/
在JE撒迦算是名人了,无需他人为其卖广告,但其博客确实值得关注。另,推一下他创建的HLLVM圈。

第三个:ZOJ / POJ
大学时留下来的恶趣味,不适合大众,工作多年,我自己去得越来越少了……人老了真的会忘掉激情么

第五个:http://www.open-open.com
这个是后面编辑、补充的,站长是JE的同学(见本帖第一页最后一楼),应当支持一下。

1.java虚拟机运行时数据区域

Java虚拟机在执行Java程序过程中,会把它所管理的内存分为若干个不同的数据区域。根据《Java虚拟机规范》的规定,其所管理的内存将会包括以下几个运行时数据区域

upload successful

1.1 程序计数器

  程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器,通过改变程序计数器的值,来选取将要执行的字节码指令。在多线程环境下,各个线程通过轮流切换、分配处理器时间来执行,因此每个线程需要一个独立的计数器,以便线程切换回来后,能够知道程序执行的位置。此内存区域是java虚拟机中唯一一个不会出现OOM的区域。
  如果线程正在执行的是一个Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器的值则应该为空(Undefined)

1.2 虚拟机栈

  栈是一种特殊的线性表,仅能在线性表的一端进行操作,称为入栈和出栈。

upload successful
  虚拟机栈描述的是Java方法执行的内存模型,每个方法被调用的时候,Java虚拟机就会同步创建一个栈帧压入栈中,每一个方法执行完闭就对应着一个栈帧出栈。可以思考一下,为什么递归调用容易出现StackOverflowError异常。
  调用Java方法时虚拟机会同步生成栈帧,那么栈帧中存放哪些信息呢?

  • 局部变量表(Local Variables)
  • 操作数栈(Opreand Stack)
  • 动态链接 (Dynamic Linking) (指向运行时常量的方法引用)
  • 方法出口(方法调用完成)分为常规方法调用完成 突然方法调用完成
  • 一些附加信息

第三章Jvm内存分配与垃圾收集

** 问题 **
1.java虚拟机如何分配内存(内存分配原则)
2.垃圾收集主要针对哪些区域
3.何时会触发GC
4.触发GC时虚拟机怎样判断哪些对象存活,哪些对象已经死亡
5.垃圾收集算法有哪些
6.介绍下主流的虚拟机中包含哪些垃圾收集器
7.HotSpot虚拟机垃圾收集算法细节

JVM主流垃圾收集器

upload successful

Jdk主流垃圾收集器
Serial( [‘sɪəriəl] )(串行)收集器,Serial Old收集器

upload successful

Serial/Serial Old收集器运行示意图

  如图,Serial收集器是一个单线程工作的收集器,新生代采用标记-复制算法,老年代采用标记-整理算法,垃圾收集时需要暂停所有用户线程。
  Serrial收集器是HotSpot虚拟机运行在客户端模式下的默认收集器,有着优于其他收集器的地方,那就是简单而高效(与其他收集器的单线程相比),对于内存资源受限的环境,它是所有收集器里额外内存消耗最小的;对于单核处理器或处理器核心数较少的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。

ParNew 收集器

upload successful

ParNew/Serial Old收集器运行示意图

  ParNew收集器实际上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一致。
  ParNew收集器,在单核处理器的环境中绝对不会有比Serial收集器更好的效果,但是随着可以被使用的处理器核心数量的增加,ParNew对于垃圾收集时系统资源的高效利用还是很有好处的。它默认开启的收集线程数与处理器核心数量相同,在处理器核心非常多(譬如32个,现在CPU都是多核加超线程设计,服务器达到或超过32个逻辑核心的情况非常普遍)的环境中,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。

Parallel Scavenge收集器
Parallel Old收集器
CMS收集器

CM收集器执行过程
  CMS(Concurrent Mark Sweep)收集器是一种以获取最短停顿时间为目标的垃圾收集器。它是基于标记——清除算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为四个步骤:
  1.初始标记(Stop the World)
  2.并发标记
  3.重新标记(Stop the World)
  4.并发清除

  初始标记仅仅是标记GC Roots能直接关联到的对象,速度很快;并发标记就是从GC Roots的直接关联对象开始遍历整个对象图,这个过程耗时较长但是不需要停顿用户线程。

upload successful

遍历对象图过程

而重新标记阶段则是为了修正并发标记阶段用户线程对对象图引用关系的影响。

upload successful

并发标记阶段,用户线程对对象图的影响
最后就是并发清除阶段,清理删除掉标记阶段被标记为已死亡的对象,由于不需要移动存活对象,所以这个阶段也是与用户线程同时并发执行的。

upload successful

CMS垃圾收集器,运行过程
**CMS垃圾收集器优缺点** 优点:并发收集、低停顿 缺点:CMS收集器对处理器资源非常敏感(因为需要并发回收垃圾);无法处理"浮动垃圾"(Floating Garbage),有可能出现"并发失败"(Concurrent Mode Failure),进而导致Full GC的产生;标记——清除算法会生成大量空间碎片,空间碎片过多时,将会给大对象分配时带来很多麻烦,往往会出现老年代还有很多剩余空间,但是无法找到连续的内存空间来分配当前对象,而不得不提起触发Full GC。

补充
增量式并发收集器(Incremental Concurrent Mark Sweep/i-CMS)
  首先,CMS收集器对处理器资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程(或者说处理器的资源),而导致应用程序变慢,降级总吞吐量。CMS默认启动的回收线程数是**(处理器核心线程数+3)/4** ,也就是说,如果处理器核心数在四个或以上,并发回收时垃圾收集线程只占用不超过25%的处理器运算资源,并且会随着处理器核心数量的增加而下降。但是当处理器核心数量不足四个时,CMS对用户程序的影响就可能变得很大。如果应用本来的处理器负载就很高,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然大幅降低。为了缓解这种情况,虚拟机提供了一种称为“增量式并发收集器”(Incremental Concurrent Mark Sweep/i-CMS)的CMS收集器变种,所做的事情和以前单核处理器年代PC机操作系统靠抢占式多任务来模拟多核并行多任务的思想一样,是在并发标记、清理的时候让收集器线程、用户线程交替运行,尽量减少垃圾收集线程的独占资源的时间,这样整个垃圾收集的过程会更长,但对用户程序的影响就会显得较少一些,直观感受是速度变慢的时间更多了,但速度下降幅度就没有那么明显。实践证明增量式的CMS收集器效果很一般,从JDK 7开始,i-CMS模式已经被声明为“deprecated”,即已过时不再提倡用户使用,到JDK 9发布后i-CMS模式被完全废弃。

虚拟机参数说明:
-XX:CMSInitiatingOccupancyFraction
设置CMS收集器在老年代使用了多少空间后(百分比)触发垃圾收集操作,JDK5默认设置为68%,JDK6默认阈值为92%。
-XX:+UseCMS-CompactAtFullCollection (默认是开启的,此参数从JDK 9开始废弃)
用于在CMS收集器不得不进行Full GC时开启内存碎片的合并整理过程。
-XX:CMSFullGCsBefore-Compaction (此参数从JDK 9开始废弃)
这个参数的作用是要求CMS收集器在执行过若干次(数量由参数值决定)不整理空间的Full GC之后,下一次进入Full GC前会先进行碎片整理(默认值为0,表示每次进入Full GC时都进行碎片整理)。

G1收集器

1.查询日志中含有某个关键字的信息
cat app.log |grep ‘error’

2.查询日志尾部最后10行的日志
tail -n 10 app.log

3.查询10行之后的所有日志
tail -n +10 app.log

4.查询日志文件中的头10行日志
head -n 10 app.log

5.查询日志文件除了最后10行的其他所有日志
head -n -10 app.log

6.查询日志中含有某个关键字的信息,显示出行号(在1的基础上修改)
cat -n app.log |grep ‘error’

7.显示92行,后20行的日志 即 92-102
cat -n app.log |tail -n +92|head -n 20

8.根据日期时间段查询(前提日志总必须打印日期,先通过grep确定是否有该时间点)
sed -n ‘/2018-06-01 16:00:20/,/2018-06-01 16:06:36/p’ app.log

9.使用more和less命令(分页查看,使用空格翻页)
cat -n app.log |grep “error” |more

10.吧日志保存到文件
cat -n app.log |grep “error” > temp.txt

11.将指定时间的日志导入到文件中
cat -n logs/catalina.out ‘/2018-06-08 00:00:20/,/2018-06-06 16:06:36/p’ > pay.out

12.显示1000行到3000行之间到内容

cat -n app.log | head -n 3000 | tail -n +3000

原文地址:https://www.cnblogs.com/tygtyg/p/10448631.html

  MySQL事务主要用于处理多个相关联的数据操作。比如,在人员管理系统中,当删除一个人员时,既需要删除人员的基本信息,也要删除与该人员相关的信息,如信箱、文章等。这一系列等数据库操作语句就构成一个事务!

  • 在MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务。
  • 事务处理可以用来维护数据库的完整性,保证多个SQL语句要么全部执行,要么全部不执行。
  • 事务用来管理insert、update、delete语句

事务的基本要素(ACID)

1. 原子性(Atomicity)

事务开启后的所有操作,要么全部执行,要么全部不执行,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(rollback)到事务开始前的状态,就像整个事务从来没有执行过一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。

2. 一致性(Consistency)

事务开始前和结束后,数据库的完整性约束没有被破坏。比如A向B转账,不应该A账户扣了钱,B账户却没有收到。

3. 隔离性(Isolation)

数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉而导致数据的不一致。事务隔离分为四个不同级别,包括读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

4. 持久性(Durability)

事务处理结束后,对数据的修改就是永久性的,即便系统故障也不会丢失。

1
在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显式地开启一个事务务须使用命令 BEGIN 或 START TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。

事务的并发问题

脏读(dirty read)

A事务读取B事务尚未提交的数据,并在这个数据的基础上操作。如果B事务回滚,那么A事务读取到的就是脏数据。

upload successful
在这个场景中,B事务希望取款500元而后又做了撤销动作,而A事务往相同的账户中存入100元,就因为A事务读取了B事务未提交的数据,因而造成账户白白丢失500元。

不可重复读(unrepeatable read)

事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据做了更新并提交,导致事务A多次读取同一数据时,结果不一致。

upload successful
在这个场景中,A在取款事务的过程中,B往该账户转账100元,A再次读取账户的余额发生不一致。

幻读(phantom read)

系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

事务的隔离级别

事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted)
读已提交(read committed)
可重复读(repeatable-read)
串行化(serializable)

MySQL数据库默认的事务隔离级别为repeatable-read

upload successful

举例验证各个隔离级别

1.读未提交

(1)打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询表account的初始值:
upload successful
(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:
upload successful
(3)这时,虽然客户端B的事务还没提交,但是客户端A就可以查询到B已经更新的数据:
upload successful
(4)一旦客户端B的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端A查询到的数据其实就是脏数据:
upload successful
(5)在客户端A执行更新语句update account set balance = balance - 50 where id =1,lilei的balance没有变成350,居然是400,是不是很奇怪,数据不一致啊,如果你这么想就太天真 了,在应用程序中,我们会用400-50=350,并不知道其他会话回滚了,要想解决这个问题可以采用读已提交的隔离级别

upload successful

2.读已提交

(1)打开一个客户端A,并设置当前事务模式为read committed(读已提交),查询表account的所有记录:
upload successful
(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:
upload successful
(3)这时,客户端B的事务还没提交,客户端A不能查询到B已经更新的数据,解决了脏读问题:
upload successful
(4)客户端B的事务提交
upload successful
(5)客户端A执行与上一步相同的查询,结果 与上一步不一致,即产生了不可重复读的问题
upload successful

3.可重复读

(1)打开一个客户端A,并设置当前事务模式为repeatable read,查询表account的所有记录
upload successful
(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account并提交
upload successful
(3)在客户端A查询表account的所有记录,与步骤(1)查询结果一致,没有出现不可重复读的问题
upload successful
(4)在客户端A,接着执行update balance = balance - 50 where id = 1,balance没有变成400-50=350,lilei的balance值用的是步骤(2)中的350来算的,所以是300,数据的一致性倒是没有被破坏。可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本);insert、update和delete会更新版本号,是当前读(当前版本)
upload successful
(5)重新打开客户端B,插入一条新数据后提交
upload successful
(6)在客户端A查询表account的所有记录,没有 查出 新增数据,所以没有出现幻读
upload successful

4.串行化

(1)打开一个客户端A,并设置当前事务模式为serializable,查询表account的初始值:
upload successful
(2)打开一个客户端B,并设置当前事务模式为serializable,插入一条记录报错,表被锁了插入失败,mysql中事务隔离级别为serializable时会锁表,因此不会出现幻读的情况,这种隔离级别并发性极低,开发中很少会用到。

upload successful

**补充:
1、事务隔离级别为读提交时,写数据只会锁住相应的行
2、事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
3、事务隔离级别为串行化时,读写数据都会锁住整张表
4、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
5、MYSQL MVCC实现机制参考链接:https://www.cnblogs.com/moershiwei/p/9766916.html
6、关于next-key 锁可以参考链接:https://blog.csdn.net/bigtree_3721/article/details/73731377 **

原文地址:https://www.cnblogs.com/wyaokai/p/10921323.html

创建Docker网络

旧版本的docker容相互之间是依靠link建立关系。
新版本的docker推荐创建自有网络,再将需要互联的容器配置到相同的网络中。
接下来,我们创建一个名为”docker_net”的网络:

  • 子网 172.15.0.0/16
    • IP段 172.15.0.0
    • 掩码 255.255.0.0
    • IP范围 172.15.0.1~172.15.255.254
    • IP广播 172.15.255.255

upload successful

运行docker镜像,并将docker容器添加到docker_net网络中。

1
docker run --network docker_net --ip 172.15.0.50 -p 80:80 51f345e513a1

查看docker容器IPAddress

upload successful

golang的fmt包实现了格式化I/O函数,类似于C的printf和scanf。

1
2
3
4
5
#定义示例类型和变量
type Human struct {
Name string
}
var people = Human{Name:"lwl"}

普通占位符

占位符 说明 举例 输出
%v 相应值的默认格式 fmt.Printf(“%v”,people) {lwl}
%+v 打印结构体时,会添加字段名 fmt.Printf(“%+v”,people) {Name:lwl}
%#v 相应值的Go语法表示 fmt.Printf(“%#v”,people) main.Human{Name:”lwl”}
%T 相应值的类型的Go语法表示 fmt.Pintf(“%T”,people) main.Human
%% 字面上的百分号,并非值的占位符 fmt.Printf(“%%”) %

布尔占位符

占位符 说明 举例 输出
%t true或false fmt.Printf(“%t”,true) true

整数占位符

占位符 说明 举例 输出
%b 二进制表示 fmt.Printf(“%b”,5) 101
%c 相应Unicode码点所表示的字符 fmt.Printf(“%c”,0x4E2D)
%d 十进制表示 fmt.Printf(“%d”,0x12) 18
%o 八进制表示 fmt.Printf(“%o”,10) 12
%q 单引号围绕的字符字面值,由Go语法安全地转义 fmt.Printf(“%q”,0x4E2D) ‘中’
%x 十六进制表示,字母形式为小写a-f fmt.Printf(“%x”,13) d
%X 十六进制表示,字母形式为大写A-F fmt.Printf(“%X”,13) D
%U Unicode格式:U+1234,等同于”U+%04X” fmt.Printf(“%U”,0x4E2D) U+4E2D

符点数和复数的组成部分(实部和虚部)

占位符 说明 举例 输出
%b 无小数部分的,指数为二的幂的科学计数法,与strconv.FormatFloat 的’b’转换格式一致.例如-123456p-78
%e 科学计数法,例如 -1234.456e+78 fmt.Printf(“%e”,10.2) 1.020000e+01
%E 科学计数法,例如 -1234.456E+78 fmt.Printf(“%E”,10.2) 1.020000E+01
%f 有小数点而无指数,例如 123.456|fmt.Printf(“%f”,10.2) 10.200000
%g 根据情况选择%e 或 %f 以产生更紧凑的(无末尾的0)输出 fmt.Printf(“%g”,10.20) 10.2
%G 根据情况选择%E 或 %f 以产生更紧凑的(无末尾的0)输出 fmt.Printf(“%G”,10.20+2i) (10.2+2i)

字符串与字节切片

占位符 说明 举例 输出
%s 输出字符串表示(string类型或[]byte) fmt.Printf(“%s”,[]byte(“Go语言”)) Go语言
%q 双引号围绕的字符串,由Go语法安全地转义 fmt.Printf(“%q”,”Go语言”) “Go语言”
%x 十六进制,小写字母,每字节两个字符 fmt.Printf(“%x”,”golang”) 676f6c616e67
%X 十六进制,大写字母,每字节两个字符 fmt.Printf(“%X”,”golang”) 676F6C616E67

指针

占位符 说明 举例 输出
%p 十六进制表示,前缀0x fmt.Printf(“%p”,&people) 0xc0000ac000

其它标记

占位符 说明 举例 输出
+ 总打印数值的正负号;对于%q(%+q)保证只输出ASCII编码的字符 fmt.Printf(“%+q”,”中文”) “\u4e2d\u6587”
- 在右侧而非左侧填充空格(左对齐该区域)
# 备用格式:为八进制添加前导0(%#0);为十六进制添加前导0x(%#x)或0X(%#X),为%p(%#p)去掉前导0x;如果可能的话,%q(%#q)会打印原始(即反引号围绕的)字符串;如果是可打印字符,%U(%#U)会写出该字符的Unicode编码形式(如字符x会被打印成U+0078 ‘x’)
‘ ‘ (空格)为数值中省略的正负号流出空白(% d);以十六进制(% x,% X)打印字符串或切片时,在字节之间用空格隔开
0 填充前导的0而非空格;对于数字,这会将填充移到正负号之后

golang没有 ‘%u’ 点位符,若整数为无符号类型,默认就会被打印成无符号的。

宽度与精度的控制格式以Unicode码点为单位。宽度为该数值占用区域的最小宽度;精度为小数点之后的位数。
操作数的类型为int时,宽度与精度都可用字符 ‘*’ 表示。

对于 %g/%G 而言,精度为所有数字的总数,例如:123.45,%.4g 会打印123.5,(而 %6.2f 会打印123.45)。

%e 和 %f 的默认精度为6

对大多数的数值类型而言,宽度为输出的最小字符数,如果必要的话会为已格式化的形式填充空格。

而以字符串类型,精度为输出的最大字符数,如果必要的话会直接截断。

前言:
  学习任何一门语言都需要掌握它都数据类型,就像java语言中包含8种基本数据类型、有变量常量之分,go语言也如此。文档主要记录《GO语言学习笔记》一书都学习笔记、demo,以便后期回顾学习。

2.类型篇
2.1变量

go语言用关键字var定义变量,跟java不同,go语言类型放在变量名后。如果显示提供初始化值,则可以省略变量类型,由编译器推断。同时,可一次定义多个变量,包括用不同初始值定义不同类型。

1
2
3
4
5
6
var x int 	//定义变量,指定类型为int,如果没有初始化值,运行时会自动初始化为二进制零值

var y = false //显示提供初始化值false,可以省略变量类型(bool),由编译器推断

var a,b int //一次定义相同类型都多个变量
var a,s=100,"abc" //用不同初始值定义不同类型

除var关键字外,还可以使用更加简短的变量定义和初始化语法。

1
2
3
4
func main(){
x := 100
a,s := 1,"abc"
}

简短模式定义变量时,需要注意以下限制:

  • 定义变量,同时显示初始化
  • 不能提供数据类型
  • 只能用在函数内部
2.2命名

  同其他编程语言一样,对变量、常量、函数、自定义类型进行命名,通常优先选用有实际含义,易于理解对字母或单词组合。
命名建议:

  • 以字母或下划线开始,由多个字母、数字和下划线组合而成
  • 区分大小写
  • 使用驼峰命名格式
  • 局部变量优先使用短名
  • 不可以使用go语言保留的关键字
  • 不建议使用与预定义常量、类型、内置函数相同的名字
  • 专有名词通常会全部大写,例如HTML

  go语言没有java语言中的访问修饰符(public、protected、private),因此采用首字母大小写来决定其作用域。首字母大写的为导出成员,可被包外引用,首字母小写仅能在包内访问。
空标识符
  和Python类似,Go也有哥名为“_”的特殊成员。通常作为忽略占位符使用,可作为表达式左值,用“_”接收的值,表示舍弃不用,故无法读取其内容。

1
2
3
4
5
import "strconv"
func main(){ //go语言main函数不能有任何参数
x,_ := strconv.Atoi("12") //忽略Atoi的err返回值
println(x)
}
2.3常量

  同java语言一样,go语言也有常量,常量用来表示运行时恒定不变的值。常量值必须是编译期可确定的字符、字符串、数字或布尔值。可指定常量类型,或由编译器通过初始化值推断。
go语言常量值由const修饰:

1
2
const x,y int=123,0x22	//定义两个常量值x,y 其中y为16进制表示
const s = "hello,world!" //定义常量s并且省略其类型,由编译器通过初始化值推断

  可在函数代码块中定义常量,不曾使用的常量不会引发编译错误(变量定义未使用,编译器会报错)。

1
2
3
4
5
6
7
8
9
func main(){
const x = 123
println(x)
const y = 1.23 //未使用,不会引发编译错误
{
const x = "abc" //在不同作用域可以定义同名常量,编译器不会报错
println(x)
}
}

  如果显示指定类型,必须确保常量左右值类型一致,需要时可以做显示类型转换。右值不能超出左边类型取值范围,否则报溢出错误。常量值也可以是某些编译器能计算出结果的表达式,如unsafe.Sizeof、len、cap等。

1
2
3
4
5
6
import "unsafe"
//常量组
const(
ptrSize = unsafe.Sizeof(uintptr(0))
strSize = len("hello,world!")
)

  在常量组中,如果不指定常量的类型和初始化值,则与上一行非空常量右值相同。

1
2
3
4
5
6
7
8
9
10
11
import "fmt"
func main(){
const(
x uint16 = 120
y //y未指定类型和初始化值,与上一行非空常量右值相同,即y=120
s = "abc"
z //与s类型、右值相同
)
fmt.Printf("%T,%v\n",y,y) //输出类型和值
fmt.Printf("%T,%v\n",z,z)
}

输出结果:

1
uint16,120 string,abc

关于占位符的介绍,请参考:Go语言fmt格式“占位符”
枚举:
  Go并没有明确意义上的enum定义,不过可借助iota标识符实现一组自增常量值来实现枚举类型。

1
2
3
4
5
6
7
8
9
10
11
const(
x = iota //0
y //1
z //2
)
const(
_ = iota //0
KB = 1<<(10*iota) //1<<(10*1)
MB //1<<(10*2)
GB //1<<(10*3)
)

  自增作用范围为常量组。可在多常量定义中使用多个iota,它们各自单独计数,只须确保组中每行常量的列数量相同即可。

1
2
3
4
5
const(
_,_ = iota,iota*10 //0,0*10
a,b //1,1*10
c,d //2,2*10
)

如中断iota自增,则必须显示恢复。且后续自增值按行序递增,而非C语言 enum那样按上一取值递增。

1
2
3
4
5
6
7
8
const(
a = iota //0
b //1
c = 100 //100
d //100 与上一行常量右值表达式相同
e = iota //4(恢复iota自增,计数包括c、d)
f //5
)

自增默认数据类型为int,可显示指定类型

1
2
3
4
5
const(
a = iota //int
b float32 = iota //float32
c = iota //int(如不显示指定iota,则与b数据类型相同)
)

常量除“只读”外,和变量究竟有什么不同?

1
2
3
4
5
6
var x = 0x100
const y = 0x200
func main(){
println(&x,x)
println(&y,y) //编译器报错:cannot take the address of y
}

  不同于变量在运行期分配存储内存(非优化状态),常量通常会被编译器在预处理阶段直接展开,作为指令数据使用。数字常量不会分配存储空间,无法像变量那样通过内存寻址来取值,因此无法获取地址。

2.4基本类型
类型 长度(字节数) 默认值 说明
bool 1 false 布尔型的值只可以是常量truefalse,默认值false
byte 1 0 类似uint8
int 4或8 0 默认整数类型,根据目标平台,32位或64位
uint 4或8 0 默认无符号整数类型,依据目标平台,32位或64位
int8,uint8 1 0 int8取值范围-128~127,uint8取值范围0-255
int16,uint16 2 0 int16取值范围-3276832767,uint16取值范围065535
int32,uint32 4 0 int32取值范围-21474836482147483647,uint32取值范围04294967295
int64,uint64 8 0 int64取值范围-9223372036854775808 ~ 9223372036854775807,uint64取值范围0 ~ 18446744073709551615
float32 4 0.0 32位浮点数
float64 8 0.0 64位浮点数(默认浮点数类型)
complex64 8 32位实数和虚数
complex128 16 64位实数和虚数
rune 4 0 Unicode Code Point,类似int32
uintptr 4,8 0 无符号整型,用于存储指针
string “” 字符串,默认值为空字符串而非NULL
array 数组
struct 结构体
func nil 函数类型
interface nil 接口
map nil 字典,引用类型
slice nil 切片,引用类型
channel nil 通道,引用类型

支持八进制、十六进制以及科学记数法。标准库math定义了各数字类型的取值范围。

1
2
3
4
5
6
7
import("fmt" "math")
func main(){
a,b,c := 100,0144,0x64 //a 十进制 100,b 八进制 100,c 十六进制 100
fmt.Println(a,b,c)
fmt.Printf("0b%b, %#o, %#x\n",a,a,a) // %b 二进制占位符 %o 八进制表示,%#o 为八进制添加前导0, %x 十六进制表示
fmt.Println(math.MinInt8,math.MaxInt8)
}

输出结果:

1
2
3
4
100,100,100
0b1100100, 0144, 0x64
-128 127

标准库strconv可以在不同进制(字符串)间转换

1
2
3
4
5
6
7
8
9
10
a, err := strconv.ParseInt("1100100",2,32)
if err != nil{
println(err.Error())
}
b, _ := strconv.ParseInt("0144",8,32)
c, _ := strconv.ParseInt("64",16,32)
println(a,b,c)
println("0b" + strconv.FormatInt(a,2))
println("0"+strconv.FormatInt(a,8))
println("0x" + strconv.FormatInt(a,16))

输出结果:

1
2
3
4
5
100,100,100
0b1100100
0144
0x64

别名:在官方的语言规范中,专门提到两个别名。byte 是 uint8别名;rune是int32别名。别名类型无须进行类型转换,可直接赋值。

1
2
3
4
5
6
7
8
9
func test(x byte){
println(x)
}
func main(){
var a byte = 0x11
var b uint8 = a
var c uint8 = a+b
test(c)
}

但这并不表示,拥有相同底层结构但就属于别名。就算在64位平台上int和int64结构完全一致,也分属不同类型,须显示转换。

1
2
3
4
5
6
7
8
func add(x,y int) int {
return x+y
}
func main() {
var x int = 100
var y int64 = x //编译器报错:cannot use x(type int) as type int64 in assignment
add(x,y)//编译器报错:cannot use y(type int64)as type int in argument to add
}
2.5引用类型

所谓引用类型(reference type) 特指 slice、map、channel这三种预定义类型。
  相比数字、数组等类型,引用类型拥有更复杂但存储结构。除分配内存外,它们还须初始化一系列属性,诸如指针、长度,甚至包括哈希分布、数据队列等。
  内置函数new按指定类型长度分配零值内存,返回指针,并不关心类型内部构造和初始化方式。而引用类型则必须使用make函数创建,编译器会将make转换为目标类型专用的创建函数(或指令),以确保完成全部内存分配和相关属性初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//slice
func mkslice() []int {
s := make([]int,0,10)
s = append(s, 100)
return s
}
//map
func mkmap() map[string]int {
m := make(map[string]int)
m["a"] = 1
return m
}
func main(){
m := mkmap()
println(m["a"])
s := mkslice()
println(s[0])
}
2.6类型转换
  1. 隐式转换造成的问题远大于它带来的好处。。。
      除常量、别名类型以及未命名类型外,Go强制要求使用显示类型转换。加上不支持操作符重载,所以我们总是能确定语句及表达式的明确含义。
    1
    2
    3
    a := 10
    b := byte(a)
    c := a + int(b) //混合类型表达式,必须确保类型一致,也可写成 c := byte(a) + b
  2. 同样不能将非bool类型结果当作true/false使用
    1
    2
    3
    4
    5
    6
    func main(){
    x := 100
    var b bool = x //错误:cannot use x(type int)as type bool in assignment
    if x { //错误:non-bool x(type int)used as if condition
    }
    }
  3. 语法歧义
    如果转换的目标是指针、单向通道或没有返回值的函数类型,那么必须使用括号,以免造成语法分解错误。
    1
    2
    3
    4
    5
    6
    func main(){
    x := 100
    //p := *int(&x) //错误:Invalid indirect of 'int(&x)' (type 'int')
    //Cannot convert expression of type *int to type int
    p := (*int)(&x) //转换的目标是指针,必须使用括号
    }
2.7自定义类型

使用关键字type定义用户自定义类型,包括基于现有基础类型创建,或者是结构体、函数类型等。

1
2
3
4
5
6
7
8
9
10
11
import ("fmt")
type flags byte
const(
read flags = 1<<iota
write
exec
)
func main(){
f := read|exec //位运算 “或”
fmt.Printf("%was b\n",f) //输出二进制标记位
}

输出:

1
101

和var、const类似,多个type定义可合并成组,可在函数或代码块内定义局部类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main(){
type(//定义组
user struct{//结构体
name string
age uint8
}
event func(string) bool //函数类型
)
u := user{"Tom",20}
fmt.Println(u)
var f event = func(s string) bool{
println(s)
return s != ""
}
f("abc")
}

未命名类型
  与有明确标识符等bool、int、string等类型相比,数组、切片、字典、通道等类型与具体元素类型或长度等属性相关,故称作未命名类型(unnamed type)。当然,可用type为其提供具体名称,将其改变为命名类型(named type)。
具有相同声明等未命名类型被视为同一类型。

  • 具有相同基类型的指针
  • 具有相同元素类型和长度的数组(array)
  • 具有相同元素类型的切片(slice)
  • 具有相同健值类型的字典(map)
  • 具有相同数据类型及操作方向的通道(channel)
  • 具有相同字段序列(字段名、字段类型、标签,以及字段顺序)的结构体(struct)
  • 具有相同签名(参数和返回值列表,不包括参数名)的函数(func)
  • 具有相同方法集(方法名、方法签名,不包括顺序)的接口(interface)

容易被忽视的是struct tag,它也属于类型组成部分,而不仅仅是元数据描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
func main(){
var a struct{//匿名结构类型
x int `x`
s string `s`
}
var b struct{
x int
s string
}
b = a //错误:cannot use a type struct{x int "x";s string "s"} as type struct{x int;s string}in assignment

fmt.Println(b)
}

说明:计算机中的最小存储单位为位(bit),一个字节(byte)占8位。
原码、反码、补码是计算机中对数字对二进制表示方法。

原码: 最高位作为符号位(0表示正数,1表示负数),非符号位为该数字绝对值对二进制表示。
如:
127的原码为 [0111 1111]
-127的原码为 [1111 1111]
反码: 正数反码与原码一致;负数,符号位不变,其余各位取反,则得到这个数字都反码表示形式。
如:
127的反码为[0111 1111]
-127的反码为[1000 0000]
补码: 正数的补码与原码一致负数的补码是该数的反码加1
如:
127的补码为[0111 1111]
-127的补码为[1000 0001]

移码: 移码是在补码的基础上,符号位取反
如:
127的移码为[1111 1111]
-127的移码为[0000 0001]

详情见下表:

数值 原码 反码 补码 移码
127 0111 1111 0111 1111 0111 1111 1111 1111
-127 1111 1111 1000 0000 1000 0001 0000 0001

补充

  1. 计算机中的运算均是基于补码的。
  2. java中四个整数类型均采用二进制的补码表示

接下来以byte说明,为什么采用二进制补码表示数值范围。
  byte占8个bit位,如果采用原码表示正整数(含0),范围为0-255,即2^8=256,一共256种状态,从全0到全1的各种排序组合。如果要表示负数,则符号位要占用一位(最高位 0表示正数,1表示负数),因此其绝对值最大范围为0-127,即2^7=128,一共正负各128种状态,即 -127到-0和0到127,这样总体上一个字节只有255种状态,因为0具有正0负0之分,显然浪费来一个编码。因此人们想到了另一种编码把负0利用起来,即当遇到负数时,采用补码来表示就可以解决这个问题,而遇到正数或0时还是用原码表示。因此这个负0通过补码算法处理后用来表示-128,这样一个字节的数值范围为-128-127,为256种状态。

upload successful

Github Fork 过程概述

在Github上有很多优秀的开源项目,相信每一位热衷于技术的朋友都会在Github上Fork一些感兴趣的项目,然后在本地修改并提交。但是自己修改的同时,如何保证本地代码跟在Github上的源项目同步更新呢。本文就以其中一种方案讲解Github Fork项目后如何更新。

** 1.新注册一个github账户 a,并新建HelloWorld 公共仓库 **

upload successful
** 2.登录另一个账户 b,并fork HelloWorld 仓库 **

upload successful
** 3.将b账户中的HelloWorld仓库git clone 到本地**

upload successful
** 4.查看关联的所有的远程仓库名称及地址 git remote -v **

upload successful
我们看到只有我们自己的远程仓库,此时需要增加源分支地址到项目远程分支列表中。

** 5.增加源分支地址到项目远程分支列表中(此处是关键),先得将原来的仓库指定为upstream(名称可以随意指定),命令为:
git remote add upstream https://github.com/被fork的仓库.git**

upload successful
** 6.在源项目代码更新后,利用fetch和merge合并upstream的master分支**

upload successful
** 7.打开本地项目的README.md文件发现文件已经更新为源项目最新文件**

upload successful
** 8.将更新内容推送到我们自己的远程仓库**

upload successful