java to go 基础知识与区别入土指南

文章目录

  • java to go 基础知识与区别入土指南
    • @[toc]
    • 基础篇
      • 关于基础类型
      • 关于复合数据类型
        • 关于切片
        • 关于Map
        • 关于sync.map
        • 关于结构体
      • 关于函数/方法
      • 关于defer
      • 关于协程
      • 关于Channel
      • 关于sync

基础篇


关于基础类型


在基础类型上,go语言与Java略有不同,以整型为例,在java中只有一个int代表整形,而在go语言中,整形可以分为很多种,int8、int16、int32、int64,不同的数字代表占用不同大小的比特位,这种方式无疑是可以更加友好于储存空间,可以对不同类型的机器进行优化。但副作用也是明显的,比如在进行四则运算的时候可能会因为疏忽而出现移除的情况。

基础类型有:整型int、浮点型float、布尔型bool、复数complex、字符串string、

关于复合数据类型


go的符合数据类型与Java的类似,主要有数组、map、切片、结构体。

关于切片


在go中切片(slice)是一个以数组为底层结构的可变长度的数组,与Java中的ArrayList有些相似。

  • 底层数据结构

切片的底层数据结构是由一个数组指针加两个整型变量组成的,数组指针指向一个数组,两个整型变量分别代表当前切片的长度和切片的容量。

type slice struct {array unsafe.Pointerlen   intcap   int
}

len:切片的长度

cap:切片的容量

array:数组指针

既然切片的长度可变,那就涉及到扩容,扩容分三种情况:

  • cap不超过1024:容量扩容为原来容量的2倍
  • cap超过1024:容量扩容为原来的1.25倍
  • 一次性添加的元素超过扩容后的容量:扩容的容量并不确定,比如原容量为2,append3个int型数,容量最终为6。原因是先是预估了下扩容后的容量为5,然后再进行内存对齐,最终结果就是容量为6。

关于Map


go中的Map与java中的HashMap一样,都是通过拉链法消除hash冲突,不过go语言中链表不会变为红黑树。

  • 底层数据结构
type hmap struct {// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.// Make sure this stays in sync with the compiler's definition.count     int // # live cells == size of map.  Must be first (used by len() builtin)flags     uint8B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for detailshash0     uint32 // hash seedbuckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growingnevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)extra *mapextra // optional fields
}

count:元素个数

B:用于确定bucket个数(2^B个)

noverflow:溢出的bucket个数

hash0:散列种子

buckets:存储kv的数组

oldbuckets:旧bucket的地址

nevacuate:搬迁进度

  • 如何计算key的位置?

通过计算key的哈希值,低位用于寻找key属于哪个数组,高位用于寻找key属于数组上链表的哪一个

  • 如何进行扩容?

扩容的时机是:map的长度/2^B > 6.5 。此时map会新建一个长度为原来2倍的bucket,原来的bucket指向oldbuckets,然后在存在访问旧的元素的时候将元素迁移到新的bucket并将旧的buckets置nil。

关于sync.map


sync.map是map的线程安全版本,主要是通过加锁来保证线程安全。

  • 底层数据结构
type Map struct {mu Mutexread atomic.Value // readOnlydirty map[interface{}]*entrymisses int
}

mu:锁

read:只读类型的map

dirty:可写类型map

misses:计数器

sync.map的加锁是全局加锁,类似于Java中的HashTable全局锁而不是ConcurrentHashMap中的分段所,效率很低,所以go中通过两个map来采取空间换时间的策略来提高效率,一个是只读的map,一个是可写的map,在读取数据是先读只读map,这样就可以减少加锁的次数来提高速度。两个map的key指向同一个entity

  • 如何读?

sync.map的读是首先读只读的map(read),如果读到了key则返回,如果读不到则要去可写的map(dirty)中去读取,不管读没读到,都将misses加一。值得一提的是,去dirty中读的时候采用的是双重检验锁。且如果misses >= dirty的值就将dirty全量更新到read(CAS)。

  • 如何删?

sync删除元素先是读只读的map(read),如果读到了则将key对应的value置nil。如果没读到,同样的以双检锁的方式读可写的map(dirty)并直接删除key。同样对misses加一。

  • 如何增?

sync.map首先从只读的map(read)查找,如果读到了且key没被删除(value不为nil),直接更新对应的entity。如果没有读到或者被删除了,则双检锁读可写的map(dirty),如果读到了则更新entity,如果还没读到则要判断dirty是否要比read新,如果比read新就新建一个entity存数据,否则就将read拷贝到dirty再新建一个entity存数据。统一misses加一。

关于结构体


go语言中的结构体与C语言中的结构体基本相同,是一个可以存储多个不同类型变量的集合,类似于Java中的一个实体类。

关于函数/方法


  • 多返回值:go函数最大的特点就是多返回值。

  • 取消重载:go中取消了重载,即一个名称只能有一个函数

  • 取消继承:go中相对于Java取消了继承的概念,但是可以通过匿名组合的方式来实现继承。子类只需要把基类作为成员变量放在定义中即可实现继承的功能。

  • 接口:go仍然保留了接口的概念,接口相比于Java,一个类只需要实现接口所有的函数就是实现了接口,不需要现实的声明

  • 多态:Java中在通过超类引用子类时,通过被引用对象的类型来决定调用那个子类的方法,所以Java可以通过继承和接口两种方法实现多态。而在go语言中不允许不同类型的赋值,所以并不支持类似于Java的多态,但是interface可以用不同类型赋值,所以可以通过interface来实现多态。

  • 异常与错误:在Java中,异常和错误都是Throwable的子类异常(NullPointerException等)是可以被捕获的,捕获后可以被程序员用补救措施进行补救,使程序不被停止。而错误(StackOverflow等)是不可以被捕获的,遇到错误会使程序停止。而在Go中取消了异常的概念,只保留了错误的概念(其实是只有一个异常panic的概念),遇到错误是会立刻执行defer中被延迟的函数,然后退出程序;而异常的概念得益于多返回值,可以用一个错误类型来进行抛出(返回)。

关于defer


defer对于一个Javaer是一个新概念,defer语句是在程序返回时执行的代码,在go中,函数返回语句return并不是原子性的,首先是将要返回的值进行赋值,然后再进行返回,通过下面的程序可以看出defer就是在这两个操作之间执行的。

func main() {i := 10i = f(i)fmt.Printf("this is three %d\n", i)
}func f(i int) int {defer func(i *int) {*i++fmt.Printf("this is two %d\n", *i)}(&i)i++fmt.Printf("this is one %d\n", i)return i
}//结果
this is one 11
this is two 12
this is three 11

对于defer来说,他的执行顺序是先进后出的,也就是说如果在一个函数中存在多个defer,写在最顶层的defer是最后执行的。

  • 可捕获的错误recover:

前文说过,go中的错误是不可捕获的,但是得益于defer,错误得以捕获并处理,将recover函数写在defer中,可以在函数发生panic的时候对错误进行处理,程序的panic被处理后,函数会从发生panic的地方退出,并继续执行接下来的逻辑,不会直接使整个程序退出。但是值得一提的是,recover并不能处理所有的错误,比如死锁等语言级错误是无法处理的。

关于协程


goroutines(协程)是go语言一大特色,协程使得go语言更加高效,但是协程并不是提高了运行速度,而是提高了并发度以及减少了切换时的资源调度。在解释协程为何可以提高并发度之前,我们需要搞懂go语言的GMP模型。

  • GMP模型:

    • G:Goroutines协程。
    • P:Processor处理器,不过这个处理器不同于物理的处理器,是一个抽象的概念,可以理解为一个虚拟的处理器。
    • M:Mchine系统线程,是操作系统层面的线程。

    整个GMP模型在系统层面看来就是一个服务器有一个CPU,一个CPU含有多个核心,一个核心有多个进程,一个进程可以有多个线程(M),一个线程绑定一个P,一个P有多个G。但是一个核心同时只有一个进程在跑,一个进程在同一时间也只有一个线程在跑,所以其实一个核心同时也就只有一个Goroutines在跑。所以上文所说的协程并不是通过提高运行速度来提示效率是成立的。

  • 如何提升并发度和效率

在Java这种没有协程的语言中,提升效率的方法是通过多线程来减少在进行IO时CPU的等待时间,从而提升系统的运行效率。不过这种方式仍然存在优化空间,提升效率的根本是减少CPU不干活的时间,那么CPU不干活的时间都有哪些呢,一方面就是上文提到的IO时,CPU是闲置的,另一种方式是用超线程的技术来减少CPU不工作时间,但是这个需要硬件支持,那还有哪些比较大的CPU不工作时间呢,答案就是线程切换时。在线程进行切换时,我们需要切换上下文,这一步虽然不像进程切换时刷新页表那么耗时,但是也是需要一定的时间的,而go语言中协程就是通过优化切换上下文的时间进一步提高CPU利用率,从而提高效率。

想要说清楚协程如何节省切换时间首先要说清楚以下几点:协程的调度模型、协程与线程在内存模型上的区别、协程切换过程、协程切换时机。下面就一一解释。

  • 协程的调度模型

在go中,我们使用go创建一个协程的时候,这个协程会被放入一个公共队列里。每一个P都拥有一个存放协程的队列,一个P又绑定一个M,所以实际情况就是,一个M通过P的协程队列来获取协程并运行,当P的队列里没有协程的时候,P会去公共队列里获取协程,如果公共队列里也没有协程,那么P会去拿其他P队列里的协程来运行。

  • 线程与协程的内存模型

在一般的操作系统中,一个线程占用的空间为2M;在JMM中,一个线程独有的内存区域是程序计数器PC、虚拟机栈、本地方法栈,这个区域的默认大小是1M。而在go语言中,一个goroutine初始默认值为2kB,只有JMM的1/512,操作系统的1/1024。

  • 线程与协程的切换过程

线程的上下文切换过程:用户态切换到内核态、刷新所有寄存器(4个数据寄存器 :AX、BX、CX、DX;2个变址和指针寄存器SI和DI;2个指针寄存器SP和BP;6个段寄存器ES、CS、SS、DS、FS和GS;1个指令指针寄存器IP ;1个标志寄存器Flags)、内核态切换到用户态。

go协程上下文切换过程:刷新四个寄存器(PC、SP、BP、DX)

  • 协程切换时机

阻塞 I/O、select操作、阻塞在channel、等待锁、主动调用 runtime.Gosched()

  • 小结

根据上文所说的几点,我们可以总结出协程是如何节省切换时间的。

  1. 因为GMP模型,所以协程是跑在用户态的,所以切换时不需要进行内核态与用户态的切换,这是第一处节省时间的地方。
  2. 因为一个协程初始堆栈大小为2kb,相比于正常的线程小的多得多,占有内存小说明需要copy的数据少,所以节省时间。这是第二处节省时间的地方。
  3. 因为协程切换只需要刷新4个寄存器,而线程需要刷新16个寄存器,刷新的寄存器少,所以更快。这是第三处节省时间的地方

其实从上面的几点还可以看出协程的其他优势:比如因为协程只有2kb,创建1024个协程占用的空间才相当于一个线程占用的空间,所以可以大量创建协程提高并发度。协程切换的时机相对固定,所以保存上下文的时候也更轻量。

关于Channel


channel是用于协程之间通信的重要方式,是go编程思想中“不要通过共享内存来通信,而应该通过通信来共享内存”的实践。两个协程可以通过channel来发送和接受数据,同时channel是有大小的,当channel中的数据满了之后,发送方会阻塞,当channel中数据空了之后,接收方会阻塞。

  • 底层数据结构
type hchan struct {qcount   uint           // total data in the queuedataqsiz uint           // size of the circular queuebuf      unsafe.Pointer // points to an array of dataqsiz elementselemsize uint16closed   uint32elemtype *_type // element typesendx    uint   // send indexrecvx    uint   // receive indexrecvq    waitq  // list of recv waiterssendq    waitq  // list of send waiterslock mutex
}

qcount:队列中的总数据

dataqsiz:循环队列的大小

buf:循环链表组成的缓冲队列

sendx:发送的index

recvx:接收的index

recvq:等待接收的队列

sendq:等待发送的队列

lock:互斥锁

  • channel未满的情况

当channel未满的时候,发送和接受都是加锁的,流程都是加锁 -> send数据到buf/recv数据到buf -> 释放锁

  • channel已满的情况:

当channel已满的时候,发送方会被阻塞,具体情况是,发送方的协程被出去等待状态,让出GMP中的M让其它协程使用,同时发送方的协程被抽象成含有发送方协程的和要发送的数据的结构体,放入sendq队列中等待唤醒。此时若出现channel中的数据被消费,那么就进入唤醒流程,此时channel会将sendq中数据图送到缓冲队列中,将等待的协程唤醒,并放到P的队列中。

  • channel已空的情况

当channel已空的时候,接收方会被阻塞,具体的情况是,接收方的协程被放于等待状态,让出GMP中的M让其他协程使用,同时接收方的协程被抽象成含有接收方协程的指针和空元素的结构体,放入revcq队列中等待唤醒。此时若出现向channel添加数据,此时并不会对channel加锁,而是直接将发送方的数据copy到接收方的栈中。

关于sync


  • 关于sync.Mutex

mutex是一个互斥锁,底层原理是通过一个信号量来标志是否加锁,mutex有两种模式:正常模式和饥饿模式。

正常模式下,所有等待锁的goroutine按照FIFO顺序等待。唤醒的goroutine不会直接拥有锁,而是会和新请求锁的goroutine竞争锁的拥有。新请求锁的goroutine具有优势:它正在CPU上执行,而且可能有好几个,所以刚刚唤醒的goroutine有很大可能在锁竞争中失败。在这种情况下,这个被唤醒的goroutine会加入到等待队列的前面。 如果一个等待的goroutine超过1ms没有获取锁,那么它将会把锁转变为饥饿模式。

饥饿模式下,锁的所有权将从unlock的gorutine直接交给交给等待队列中的第一个。新来的goroutine将不会尝试去获得锁,即使锁看起来是unlock状态, 也不会去尝试自旋操作,而是放在等待队列的尾部。如果一个等待的goroutine获取了锁,并且满足一以下其中的任何一个条件:(1)它是队列中的最后一个;(2)它等待的时候小于1ms。它会将锁的状态转换为正常状态。

  • 关于sync.RWMutex

    RWMutex是一个读写锁,他是通过包装Mutex来的。不过RWMutex不会导致读饥饿或者写饥饿。RWMutex通过一个readerCount变量的正负代表是否有写锁竞争,同时保证不出现饥饿的情况。

    • 读锁加锁:对readerCount变量+1
    • 读锁解锁:对readerCount变量-1,如果仍为负数则判断是否有写锁在等待,如果自己是最后一个读锁就唤醒写锁
    • 写锁加锁:首先加一个互斥锁,然后给readerCount减掉一个常量(rwmutexMaxReaders),使其变为负数,这样就标志由写锁在等待,然后如果仍然有读锁未解锁继续等待所有读锁解锁完毕
    • 写锁解锁:首先给readerCount加上一个常量(rwmutexMaxReaders),使其变为正数,然后唤醒需要读的协程,然后释放互斥锁
  • 关于sync.WaitGroup

sync通过一个计数器来决定是否阻塞当前线程,计数器不为0的时候就进行阻塞,底层原理就是一个信号量,里面存折等待的数量和计数器

  • 关于sync.Once

once用一个互斥锁来保证只有一个协程调用,然后通过一个标志位来表示有没有协程使用过

其实sync的本质上都是通过信号量来保证协程间的同步。还是通过共享内存来通信了…(逃

java to go 基础知识与区别入土指南相关推荐

  1. java的一些基础知识(引用BlogJava 落花飞雪)

     java的一些基础知识(引用BlogJava 落花飞雪) <Practical Java>笔记 1. 声明是什么? String s = "Hello world!" ...

  2. 黑马程序员 一、java 概述与基础知识

    获取更多资源关注Java帮帮IT资源分享网 一.黑马程序员-java 概述与基础知识 1.何为编程? 编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果 的过程. 为了使 ...

  3. Java 网络编程基础知识

    Java 网络编程基础知识 基础概念 计算机网络的基本概念 ​ 网络:多台计算机使用网络设备互联在一起,计算机之间可以进行通信,这样就组成了一个计算机网络. ​ 网络设备:集线器(HUB),路由器,交 ...

  4. java 网络爬虫 正则表达式_【干货】Java网络爬虫基础知识

    原标题:[干货]Java网络爬虫基础知识 引言 Java 网络爬虫具有很好的扩展性可伸缩性,其是目前搜索引擎开发的重要组成部分.例如,著名的网络爬虫工具 Nutch 便是采用 Java 开发,该工具以 ...

  5. 第76节:Java中的基础知识

    第76节:Java中的基础知识 设置环境,安装操作系统,安装备份,就是镜像,jdk配置环境,eclipse下载解压即可使用,下载tomcat 折佣动态代理解决网站的字符集编码问题 使用request. ...

  6. Java核心技术卷一基础知识第10版demo实例

    Java核心技术卷一基础知识第10版demo实例 第三章 JAVA的基本程序设计结构 3.7输入与输出 3.7.1读取输入 3.8控制流程 3.8.3循环 3.10数组 3.10.6多维数组 第四章 ...

  7. 【Java】【基础知识】【Java的基本使用】

    [Java][基础知识][Java的基本使用] 基于jdk8 仅个人理解,或有疏漏 基于 java疯狂讲义 第三版和第四版 java核心技术卷一 第十版和第十一版 廖雪峰java课程 一.基本数据与结 ...

  8. JAVA编程语言的基础知识(2)

    下面介绍的是java编程相关的基础知识: 1.EJB与JAVA BEAN的区别? Java Bean 是可复用的组件,对Java Bean并没有严格的规范,理论上讲,任何一个Java类都可以是一个Be ...

  9. [Java]String类基础知识与常用方法总结

    这篇文章用于记录个人学习过程中Java中String类的一些基础知识和方法.主要记录了String类的特性.常用方法,以及和基本数据类型.包装类互转方面的内容. 源码部分 我们先简易看下Java13中 ...

最新文章

  1. 当专业动画师用GAN帮自己“偷懒”,几分钟就完成了几周的工作
  2. 清华大学矣晓沅:“九歌”——基于深度学习的中国古典诗歌自动生成系统
  3. json字符串中的大括号转义传到后台_json转义问题
  4. 好朋友简简单单,好情谊清清爽爽,好缘份久久长长
  5. HDU - 2819 Swap(二分图完备匹配+路径输出)
  6. jre for mac 删除_在 Mac 的 Docker Desktop 中运行 K8s
  7. linux无盘工作站互不干扰,Linux环境下无盘工作站的架设和实现二
  8. android EditText获取光标位置并安插字符删除字符
  9. 几个常用的python脚本_几个很实用的python脚本
  10. Unity3d + UGUI 的多分辨率适配
  11. asp.net身份认证
  12. 如何实现一次选择多个文件上传,而且异步上传
  13. win10鼠标指针修改
  14. ARP防火墙单机个人版 “此版本已过期,请下载最新版”
  15. Mac 配置maven
  16. Pixelmator Pro for Mac(图像编辑软件)
  17. graphpad prism怎么添加图例_Graphpad Prism:如何制作柱状图
  18. pdf怎么合并在一起?
  19. 微信公众号最佳实践 ( 9.1)会员卡
  20. ubuntu16.04不能访问新加卷

热门文章

  1. 用注册表恢复正常的IE
  2. Xshell6 评估期已过——解决办法
  3. 各数据库导入/导出Access教程--图解
  4. crack-jar手游,曾用过的工具
  5. OPEN-VOCABULARY OBJECT DETECTION VIAVISION AND LANGUAGE KNOWLEDGE DISTILLATION
  6. 转载:详解Quartus导出网表文件:.qxp和.vqm
  7. 移动前端不得不了解的html5 head 头标签
  8. java基础-接口与实现
  9. 虚拟机软件Parallels Desktop和VMware Fusion哪个好
  10. Linux安装系统注意事项及系统初始化