Go与Java的对比
2025-12-29 19:26:39 0 举报
AI智能生成
在编程领域,Go语言与Java都是广受欢迎的编程语言,各自具有独特的核心特点和用例。Go语言,被誉为21世纪的C语言,以其简洁、高效、快速编译而著称。它支持并发处理,利用goroutines和channels轻松地实现多任务处理,这极大地提升了程序运行时的性能和效率。在文件类型方面,Go原生支持编译为单个二进制文件,这对于部署和分发项目非常有利。另外,它还支持文本文件和JSON格式的互操作性,使得数据处理变得简单明了。 相比之下,Java自1995年问世以来,已经成为企业级应用开发的主流选择。它的主要优点是跨平台兼容性,依赖于Java虚拟机(JVM),使得Java应用能在各种平台上无需修改即可运行。Java拥有强大的库生态系统和成熟的社区支持,尤其在大型系统和安卓应用开发方面有着明显优势。Java文件类型多以`.java`扩展名表示源代码文件,通过JVM编译成平台无关的`.class`字节码文件。 在修饰语上,Go语言常被形容为“简洁”、“高性能”、“网络导向”,而Java则经常被描述为“稳定”、“跨平台”、“面向对象”。每种语言都有自己擅长的领域,Go适合快速、高效的服务端编程,而Java则在大型分布式系统和企业应用中占据主导地位。
作者其他创作
大纲/内容
Go简介
Go与Java的初步比较
Go和Java都是图灵完备的环境,这意味着(几乎)任何程序都可以用二者之一编写,差别只在于开发花费的工作量和程序大小以及性能。
应该注意的是,Go语言和Go开发体验更接近于C语言而不是Java,风格和语法方面也是。Go的标准库更像C语言的附加库。
Go相比C语言的一个特殊之处是Go的程序构建方式。在C语言中是使用Make(或者变体)工具,而在Go中是使用Go构建工具。Go的方式更优越、更易于使用。在一些复杂项目中,这些项目不仅需要Go源文件,还需要构建其他文件。Make文件通常用于构建Go构建器无法玩成的多步骤过程脚本。这类似Java中使用Ant和Maven
应该注意的是,Go语言和Go开发体验更接近于C语言而不是Java,风格和语法方面也是。Go的标准库更像C语言的附加库。
Go相比C语言的一个特殊之处是Go的程序构建方式。在C语言中是使用Make(或者变体)工具,而在Go中是使用Go构建工具。Go的方式更优越、更易于使用。在一些复杂项目中,这些项目不仅需要Go源文件,还需要构建其他文件。Make文件通常用于构建Go构建器无法玩成的多步骤过程脚本。这类似Java中使用Ant和Maven
Go是编译型语言(Java是解释型语言)。
类似C和C++,Go程序在执行之前需要完全构建。所有源文件都被编译成目标计算机架构的机器语言。另外,所有代码都被编译到目标操作系统。与之相反,Java被编译成虚拟机语言(也叫字节码),由Java虚拟机(Java Virtual Machine, JVM)解释。为了提高性能,字节码经常在运行时被动态编译成机器语言。JVM本身是为具体的操作系统和硬件架构而构建的。
一旦构建玩成,Go程序的运行只需一个操作系统。Java程序需要另外在计算机装有JRE才能运行。许多Java程序可能还需要额外的第三方代码。
Go方法的优势时快速程序启动和自包含程序,这两点更适合容器式部署
类似C和C++,Go程序在执行之前需要完全构建。所有源文件都被编译成目标计算机架构的机器语言。另外,所有代码都被编译到目标操作系统。与之相反,Java被编译成虚拟机语言(也叫字节码),由Java虚拟机(Java Virtual Machine, JVM)解释。为了提高性能,字节码经常在运行时被动态编译成机器语言。JVM本身是为具体的操作系统和硬件架构而构建的。
一旦构建玩成,Go程序的运行只需一个操作系统。Java程序需要另外在计算机装有JRE才能运行。许多Java程序可能还需要额外的第三方代码。
Go方法的优势时快速程序启动和自包含程序,这两点更适合容器式部署
Go与Java的程序结构类似。
这两种语言均支持包含方法和字段的数据结构的概念。Java中称为Class(类),Go中称为Struct(结构体)。这些结构被手机到称为Package(包)的分组中,在这两门语言中,包都可以被分层组织。
Java包只包含类型声明。Go包中可包含变量、常量、函数等基本声明以及派生类型声明。
两种语言都通过导入包来访问不同包中的代码。在Java中,导入类型可选择使用非限定类型(String与java.lang.String)。在Go中,所有导入名称必须始终限定。
这两种语言均支持包含方法和字段的数据结构的概念。Java中称为Class(类),Go中称为Struct(结构体)。这些结构被手机到称为Package(包)的分组中,在这两门语言中,包都可以被分层组织。
Java包只包含类型声明。Go包中可包含变量、常量、函数等基本声明以及派生类型声明。
两种语言都通过导入包来访问不同包中的代码。在Java中,导入类型可选择使用非限定类型(String与java.lang.String)。在Go中,所有导入名称必须始终限定。
Go与Java有影响代码结构的代码风格差异。
Go与Java的一些代码风格差异如下。
# 声明时,Java将类型放在前面,Go将类型放在后面。例如
Java: int x,y,z;
Go: var x, y, z int
# Java方法只能返回一个值。Go函数可以返回多个值。
# Java方法和字段必须在其所属的类型中声明。Go方法在所属类型外定义。Go支持独立于任何类型的函数和变量。Java没有真正的静态共享变量,静态属性只是一些类的字段。Go支持在可执行映像中分配的
# Go有完全闭包(可捕获可变数据),Java仅支持部分闭包(只能捕获不可变数据),这可以使Go中的一等函数更加强大
# Go缺少用户定义的反省。一些内置类型(如切片和映射)是泛型的.Java支持任何引用类型的泛型。(注意:Go有一个已获批准的提案,未来将添加泛型类型)
# Java只允许对其他类型(类、枚举和接口)进行类型扩展,而Go可以基于任何现有类型创建新类型,包括基本类型(如整型和浮点型)和其他用户定义的类型。Go支持任何自定义类型的方法
# Go接口的工作方式与Java完全不同。在Java中,如果通过接口使用(方法调用)类或枚举,则类必须显式实现接口。在Go中,任何类型都可以简单地通过实现接口的方法来实现接口,不需要声明实现接口的意图,声明只是作为调用方法的副产品出现。Java中的许多标准(继承的)行为(例如toString()方法)在Go中都是由实现公共接口的类型(等效于Stringer接口的String()方法)提供的。
Go与Java的一些代码风格差异如下。
# 声明时,Java将类型放在前面,Go将类型放在后面。例如
Java: int x,y,z;
Go: var x, y, z int
# Java方法只能返回一个值。Go函数可以返回多个值。
# Java方法和字段必须在其所属的类型中声明。Go方法在所属类型外定义。Go支持独立于任何类型的函数和变量。Java没有真正的静态共享变量,静态属性只是一些类的字段。Go支持在可执行映像中分配的
# Go有完全闭包(可捕获可变数据),Java仅支持部分闭包(只能捕获不可变数据),这可以使Go中的一等函数更加强大
# Go缺少用户定义的反省。一些内置类型(如切片和映射)是泛型的.Java支持任何引用类型的泛型。(注意:Go有一个已获批准的提案,未来将添加泛型类型)
# Java只允许对其他类型(类、枚举和接口)进行类型扩展,而Go可以基于任何现有类型创建新类型,包括基本类型(如整型和浮点型)和其他用户定义的类型。Go支持任何自定义类型的方法
# Go接口的工作方式与Java完全不同。在Java中,如果通过接口使用(方法调用)类或枚举,则类必须显式实现接口。在Go中,任何类型都可以简单地通过实现接口的方法来实现接口,不需要声明实现接口的意图,声明只是作为调用方法的副产品出现。Java中的许多标准(继承的)行为(例如toString()方法)在Go中都是由实现公共接口的类型(等效于Stringer接口的String()方法)提供的。
Go和Java都是过程语言。
命令式程序是指那些通过随时间显式改变状态并测试该状态来工作的程序。它们式无处不在的冯 · 诺依曼计算机架构的直接反映。过程程序是由过程(Go中的函数和Java中的方法)组成的命令式程序。两种语言都提供以下过程语言的关键功能。
# 可以执行表达式,通常是对变量赋值
# 可以执行一系列(0+)语句(通常称为基本块)
# 通常单个语句也可以隐式地充当块
# 可以在代码流中创建单分支(if)、双分支(if-else)、n分支(if/ else if / else, switch)的条件分支
# 可以使用循环语句
# Java有while、do和for语句,Go将它们综合为一个for语句
# 可以定义可重用代码,这些代码可从多个位置调用
# Java有方法,Go有函数,且部分函数就是方法
所有程序都可以仅使用这些结构编写
命令式程序是指那些通过随时间显式改变状态并测试该状态来工作的程序。它们式无处不在的冯 · 诺依曼计算机架构的直接反映。过程程序是由过程(Go中的函数和Java中的方法)组成的命令式程序。两种语言都提供以下过程语言的关键功能。
# 可以执行表达式,通常是对变量赋值
# 可以执行一系列(0+)语句(通常称为基本块)
# 通常单个语句也可以隐式地充当块
# 可以在代码流中创建单分支(if)、双分支(if-else)、n分支(if/ else if / else, switch)的条件分支
# 可以使用循环语句
# Java有while、do和for语句,Go将它们综合为一个for语句
# 可以定义可重用代码,这些代码可从多个位置调用
# Java有方法,Go有函数,且部分函数就是方法
所有程序都可以仅使用这些结构编写
Java是一门面向对象的语言,但Go不是完全面向对象的。
与所有面向对象的语言一样,Java是一种基于类的语言。所有代码(方法)和数据(字段)都封装在某个类实现中。Java类支持继承,因为它们可以扩展超类(从Object开始)。Go允许组合(一个结构体可以嵌入另一个结构),这 通常可以获得继承的一些代码重用的好处,但不是全部。
Java提供对方法和字段的封装(通过可见性:公有、保护、包私有、私有)的完全控制。Go没有提供这些选项。Go结构体在具有字段和关联方法类似于类,但它们不支持子类化。此外,Go仅支持等效的公有和包私有可见性。
在Java中,类和接口都支持多态方法调度。在Go中,只有接口支持多态方法调度。Go中没有抽象基类的对应项。同样,组合可提供该特性的子集。
注意,Java虽然通常被认为是面向对象的,但并不是面向对象编程风格的完美示例。例如,它具有原始数据类型。
与所有面向对象的语言一样,Java是一种基于类的语言。所有代码(方法)和数据(字段)都封装在某个类实现中。Java类支持继承,因为它们可以扩展超类(从Object开始)。Go允许组合(一个结构体可以嵌入另一个结构),这 通常可以获得继承的一些代码重用的好处,但不是全部。
Java提供对方法和字段的封装(通过可见性:公有、保护、包私有、私有)的完全控制。Go没有提供这些选项。Go结构体在具有字段和关联方法类似于类,但它们不支持子类化。此外,Go仅支持等效的公有和包私有可见性。
在Java中,类和接口都支持多态方法调度。在Go中,只有接口支持多态方法调度。Go中没有抽象基类的对应项。同样,组合可提供该特性的子集。
注意,Java虽然通常被认为是面向对象的,但并不是面向对象编程风格的完美示例。例如,它具有原始数据类型。
Java是一门高度函数式语言,Go不是。
从JDK8开始,Java已经很好地支持了函数式编程(FP)。FP式指仅使用具有本地数据的函数进行编程,不存在全局和可变状态。Java支持创建一等函数文本(称为Lambda)并将它们传递给其他要调用的代码。Java还允许将外部(或显式)循环(while、for等)替换为内部循环(内部方法).例如,Java Stream支持提供这一点。
Go也有一等函数文本,但缺乏对内部循环的类似支持,循环通常是外部的。一等函数以更好的方式提供类似Lambda的函数。没有内部循环被认为是Go的一个有点,因为它会生成更清晰的代码。
从JDK8开始,Java已经很好地支持了函数式编程(FP)。FP式指仅使用具有本地数据的函数进行编程,不存在全局和可变状态。Java支持创建一等函数文本(称为Lambda)并将它们传递给其他要调用的代码。Java还允许将外部(或显式)循环(while、for等)替换为内部循环(内部方法).例如,Java Stream支持提供这一点。
Go也有一等函数文本,但缺乏对内部循环的类似支持,循环通常是外部的。一等函数以更好的方式提供类似Lambda的函数。没有内部循环被认为是Go的一个有点,因为它会生成更清晰的代码。
Java是一门高度声明性语言,Go不是。
通过注解和特性(如Stream)的组合,可以用声明性风格编写Java代码。这代表代码说明了要做什么,但没有明确说明如何完成。运行时将声明转换为实现预期结果的行为。Go不提倡这种编程风格,必须按照明确说明如何实现行为的方式编写代码。因此,Go代码比典型的Java代码更清晰,但有时更大且更重复
通过注解和特性(如Stream)的组合,可以用声明性风格编写Java代码。这代表代码说明了要做什么,但没有明确说明如何完成。运行时将声明转换为实现预期结果的行为。Go不提倡这种编程风格,必须按照明确说明如何实现行为的方式编写代码。因此,Go代码比典型的Java代码更清晰,但有时更大且更重复
很多Java特性是注解驱动的。
许多Java库(尤其是那些称为框架的库),例如Spring,都充分利用了Java的注解。注解通常提供在运行时使用的元数据,以修改库提供的行为。严格来说Go没有注解,因此缺少此项功能。Go代码通常也因此更加明确,这通常被认为是一种美德。Go可以使用代码生成来获得与注解相似的结果。Go确实有一种简单的注解形式,称为标签(tag),可用于自定义一些库行为,例如JSON或XML格式化。
注解的使用可将配置决策绑定到源代码。有时,这是一个缺点,因为需要将决策延迟到运行时。在这种情况下,Go和Java经常使用类似的方法(例如命令行或配置文件参数).
Go不支持异常。
Java有异常(实际上是可抛出的异常或错误),可以引发异常以报告异常情况。异常的使用在Java中很普遍,通常用于报告可预测和不可预测的故障。将错误作为方法的返回值很少见。
Go对这些角色做了重大分割。所有由函数返回值报告的失败,必须由调用方明确测试。这很有效,因为Go函数可以很轻松地返回多个值,例如一个结果和一个错误。
Go有panic,其与Java错误的作用类似。它们被抛出的频率低得多。与Java不同,panic值不是类型的层次结构,只是开发人员选择的值的包装,通常是error类型的实例。Go中从不声明函数引发的panic值的类型(即没有与Java的throws等效的语句),这通常意味着代码没有那么冗长。 许多Java代码都遵循这样的模式:只抛出不需要声明的RuntimeException实例
许多Java库(尤其是那些称为框架的库),例如Spring,都充分利用了Java的注解。注解通常提供在运行时使用的元数据,以修改库提供的行为。严格来说Go没有注解,因此缺少此项功能。Go代码通常也因此更加明确,这通常被认为是一种美德。Go可以使用代码生成来获得与注解相似的结果。Go确实有一种简单的注解形式,称为标签(tag),可用于自定义一些库行为,例如JSON或XML格式化。
注解的使用可将配置决策绑定到源代码。有时,这是一个缺点,因为需要将决策延迟到运行时。在这种情况下,Go和Java经常使用类似的方法(例如命令行或配置文件参数).
Go不支持异常。
Java有异常(实际上是可抛出的异常或错误),可以引发异常以报告异常情况。异常的使用在Java中很普遍,通常用于报告可预测和不可预测的故障。将错误作为方法的返回值很少见。
Go对这些角色做了重大分割。所有由函数返回值报告的失败,必须由调用方明确测试。这很有效,因为Go函数可以很轻松地返回多个值,例如一个结果和一个错误。
Go有panic,其与Java错误的作用类似。它们被抛出的频率低得多。与Java不同,panic值不是类型的层次结构,只是开发人员选择的值的包装,通常是error类型的实例。Go中从不声明函数引发的panic值的类型(即没有与Java的throws等效的语句),这通常意味着代码没有那么冗长。 许多Java代码都遵循这样的模式:只抛出不需要声明的RuntimeException实例
Java和Go都是用内存管理(垃圾收集器)。
两种语言均使用栈和堆来存放数据。栈通常用于函数局部变量,堆用于其他动态创建的数据。在Java中,所有对象都被分配在堆上。在Go中,只有可以在函数生命周期之外使用的数据才会分配到堆上。在Java和Go中,堆都是有垃圾收集机制的。堆对象由代码显式分配,但总是由垃圾收集器回收。
Java没有指向对象的指针的概念,只有对位于堆中的对象的引用。Go允许访问任何数据值的指针(或地址)。在大多数情况下,Go的指针可以像Java引用一样使用。
Go中的垃圾收集实现比Java更简单。与Java不同,Go有几个调整垃圾收集的选项,让它更好地工作。
两种语言均使用栈和堆来存放数据。栈通常用于函数局部变量,堆用于其他动态创建的数据。在Java中,所有对象都被分配在堆上。在Go中,只有可以在函数生命周期之外使用的数据才会分配到堆上。在Java和Go中,堆都是有垃圾收集机制的。堆对象由代码显式分配,但总是由垃圾收集器回收。
Java没有指向对象的指针的概念,只有对位于堆中的对象的引用。Go允许访问任何数据值的指针(或地址)。在大多数情况下,Go的指针可以像Java引用一样使用。
Go中的垃圾收集实现比Java更简单。与Java不同,Go有几个调整垃圾收集的选项,让它更好地工作。
Go和Java都支持并发,但方式不同。
Java有线程的概念,也就是由库提供的可执行路径。Go有Go协程(GR)的概念,也就是由语言自身提供的可执行路径。GR可被视为轻量级线程。Go运行时支持使用的GR数量比JRE支持的线程数量更多(多大成千上万个)。
Java支持语言的同步控制。Go有类似的库函数。Go和Java都支持原子值的概念,可以跨线程/GR安全更新。两者都支持显式锁定库。
Go提供了通信顺序进程(CSP)的概念,作为GR在没有显式同步和锁定的情况下交互的主要方式。GR通过通道进行通信,这些通道实际上是管道FIFO队列,并结合了select语句进行查询。
Java有线程的概念,也就是由库提供的可执行路径。Go有Go协程(GR)的概念,也就是由语言自身提供的可执行路径。GR可被视为轻量级线程。Go运行时支持使用的GR数量比JRE支持的线程数量更多(多大成千上万个)。
Java支持语言的同步控制。Go有类似的库函数。Go和Java都支持原子值的概念,可以跨线程/GR安全更新。两者都支持显式锁定库。
Go提供了通信顺序进程(CSP)的概念,作为GR在没有显式同步和锁定的情况下交互的主要方式。GR通过通道进行通信,这些通道实际上是管道FIFO队列,并结合了select语句进行查询。
Go的运行时比JRE简单。
Go的运行时比JRE提供的更少。Go没有JVM的对应项,但还有类似的组件,如垃圾收集。Go没有字节码解释器。Go有大量的标准库,Go社区提供了更多。但是Java的标准库和社区库可以说在函数的广度和深度上都远远超过了当前的Go库。尽管如此,Go库仍然足够丰富,可以开发许多有用的应用程序,尤其是应用程序服务器。
所有使用的库(仅此而已)都被嵌入到Go的可执行文件。可执行文件是运行程序所需的一切。Java库在首次运行时动态加载。这使得Go程序二进制文件通常大于Java的二进制文件,但当JVM和所有依赖类被加载后,Java的总内存消耗量通常更大。因为Java是解释型的,其可能动态生成字节码,而后执行它。这可以通过运行时编写字节码或动态加载预写的字节码(如类)来玩成,带来了极大的灵活性。Go因为是预构建的,所以不能这样做
Go的运行时比JRE提供的更少。Go没有JVM的对应项,但还有类似的组件,如垃圾收集。Go没有字节码解释器。Go有大量的标准库,Go社区提供了更多。但是Java的标准库和社区库可以说在函数的广度和深度上都远远超过了当前的Go库。尽管如此,Go库仍然足够丰富,可以开发许多有用的应用程序,尤其是应用程序服务器。
所有使用的库(仅此而已)都被嵌入到Go的可执行文件。可执行文件是运行程序所需的一切。Java库在首次运行时动态加载。这使得Go程序二进制文件通常大于Java的二进制文件,但当JVM和所有依赖类被加载后,Java的总内存消耗量通常更大。因为Java是解释型的,其可能动态生成字节码,而后执行它。这可以通过运行时编写字节码或动态加载预写的字节码(如类)来玩成,带来了极大的灵活性。Go因为是预构建的,所以不能这样做
Go程序构建过程是不同的。
Java程序是运行时构建的类的组合,通常来自多个源代码(供应商)。这使得Java程序非常灵活。尤其是通过网络下载时,这是Java的主要用力。Go程序是在执行之前静态构建的,启动时,可执行映像中的所有代码都可用,以一定的灵活性为代价提供了更好的完整性和可预测性,使得Go更适合容器化部署。
Go程序通常由go构建器构建,这是一个结合了编译器、依赖管理器、链接器和可执行构建器等组件的工具,包含在标准Go安装中。Java类是单独编译的(通过javac工具,随JDK一起提供),然后经常被汇编成保存相关类的档案(JAR/WAR).程序从这些档案中的一个或多个加载。档案的创建(尤其是包括任何依赖项)通常由独立于标准JRE的程序(例如Maven)玩成
Java程序是运行时构建的类的组合,通常来自多个源代码(供应商)。这使得Java程序非常灵活。尤其是通过网络下载时,这是Java的主要用力。Go程序是在执行之前静态构建的,启动时,可执行映像中的所有代码都可用,以一定的灵活性为代价提供了更好的完整性和可预测性,使得Go更适合容器化部署。
Go程序通常由go构建器构建,这是一个结合了编译器、依赖管理器、链接器和可执行构建器等组件的工具,包含在标准Go安装中。Java类是单独编译的(通过javac工具,随JDK一起提供),然后经常被汇编成保存相关类的档案(JAR/WAR).程序从这些档案中的一个或多个加载。档案的创建(尤其是包括任何依赖项)通常由独立于标准JRE的程序(例如Maven)玩成
Java有而Go没有的特性
多重赋值。
在一跳语句中,Java将一个值赋值给多个便变量。例如:
int x,y, z;
x = y = z = 10;
最接近的Go方式如下:
var x,y,z int = 10,10, 10
在Go中,赋值类型和变量可以不同。
在一跳语句中,Java将一个值赋值给多个便变量。例如:
int x,y, z;
x = y = z = 10;
最接近的Go方式如下:
var x,y,z int = 10,10, 10
在Go中,赋值类型和变量可以不同。
子主题
语句和操作符。
Go和Java的操作符有不同优先级。Go的优先级更少,更自然。如果不确定优先级,那么可以在表达式中使用括号来保证运算顺序符合需求。
一个重大区别是Go中的x++(表示x=x+1)和x--(表示x=x-1)是语句,而不是操作符。Go中根本没有--x或++x
Go不支持三元表达式。需要使用if/else语句。
例如,获取最大值的Java表达式
int z = x > y ? x : y;
在Go中需要采用下面形式:
var z = y
if x > y {
z = x
}
二者类似,但不同。也可以采用类似下面的形式:
var z int
if x > y { z = <some expensive int expression> }
else { z = <some other expensive int expression> }
注意,前面的if/else必须在一个源代码行中输入。Go不支持赋值表达式,只有赋值语句
Go和Java的操作符有不同优先级。Go的优先级更少,更自然。如果不确定优先级,那么可以在表达式中使用括号来保证运算顺序符合需求。
一个重大区别是Go中的x++(表示x=x+1)和x--(表示x=x-1)是语句,而不是操作符。Go中根本没有--x或++x
Go不支持三元表达式。需要使用if/else语句。
例如,获取最大值的Java表达式
int z = x > y ? x : y;
在Go中需要采用下面形式:
var z = y
if x > y {
z = x
}
二者类似,但不同。也可以采用类似下面的形式:
var z int
if x > y { z = <some expensive int expression> }
else { z = <some other expensive int expression> }
注意,前面的if/else必须在一个源代码行中输入。Go不支持赋值表达式,只有赋值语句
while与do语句。
Go的for语句(行为像while)替代了Java的while语句。Java的do语句无直接对应项,但可使用for语句替代。注意:Java的for语句可也用作while语句,例如
int x = 0';
for(;x < 10) { ...; x++;}
与下面语句的作用相同
int x = 0; while(x < 0) { ...; x++; }
Go的for语句(行为像while)替代了Java的while语句。Java的do语句无直接对应项,但可使用for语句替代。注意:Java的for语句可也用作while语句,例如
int x = 0';
for(;x < 10) { ...; x++;}
与下面语句的作用相同
int x = 0; while(x < 0) { ...; x++; }
throw语句/throws子句。
Go没有throw语句(或者throws子句)。Go的panic(...)函数作用与throw动作相似
Go没有throw语句(或者throws子句)。Go的panic(...)函数作用与throw动作相似
strictfp、transient、volatile、synchronized、abstract、static
Go没有这些Java修饰符的对应项。多数是因为没有必要,因为Java中使用它们解决的问题,在Go中可以通过其他方式解决。例如,将声明的值调整为顶级(即包)值来实现静态值的对应功能
Go没有这些Java修饰符的对应项。多数是因为没有必要,因为Java中使用它们解决的问题,在Go中可以通过其他方式解决。例如,将声明的值调整为顶级(即包)值来实现静态值的对应功能
对象、类、内部类、Lambda、this、super、显式构造函数。
Go不像Java那样完全支持面向对象编程,因此,不支持这些Java的结构。这些特性使用起来类似大多数OOP特性。因此,Go更适合被描述为基于对象的语言。使用Go确实能实现OOP的关键目标,但其实现方式不同于严格的OOP语言通常所使用的方式。
Go不支持真正的类(如,Java的class定义)。Go确实支持结构体,其像类,但不支持继承。Go确实允许嵌套结构体,这有些像内部类。
关于类型声明Go没有extends或者implements子句。Go没有这些子句所提供的继承。Go确实针对接口类型有隐式形式的implements。
Go不支持Java的Lambda(函数签名编译成类实例)。相反,Go支持作为参数传递的一等函数(通常字面值)。Go不支持方法引用(作为参数传递的Lambda的简单名称).
Go支持接口的方式与Java不同。Go接口允许鸭子(duck)类型。Go的接口不要求显式实现(Go不需要implements子句)。任何类型匹配了接口的所有方法,则隐式实现了接口。通常,Go的方法更灵活。
Java8及更高版本允许在接口中实现(具体)方法。Go不支持。Java允许在接口中声明常量;Go不允许。Java允许在接口中声明子类型。Go则不允许。下面是OOP的一些特性:
1.一个对象有一个标识符(以便区分其他对象)
2.一个对象可能(通常确实)具有状态(即实例数据、属性或字段)
3.一个对象可能(通常确实)具有行为(即成员函数或方法)
4.一个对象由称为类的模板描述/定义
5.类可按层次结构(继承)排列;实例时层次结构中类的组合
6.对象实例被封装;状态通常仅对方法可见
7.可在类层次结构的任何层级中声明变量;子类实例可被赋值给这些变量(多态性)
Java支持(但非强制)上述所有特性。Go则不是。Go堆上述特性的支持如下所示:
1.一个结构体实例有一个地址,通常可以作为它的表示(但不一定总是明显的);结构体实例与对象实例相似但不完全相同
2.一个结构体实例可能(通常确实)具有状态
3.一个结构体实例可能(经常)具有行为。
4.如同类,一个结构体实例由称为结构体类型的模板描述/定义
5.不直接支持;结构体可嵌入提供类似组合的其他结构体
6.支持但不常用(结构体字段通常是公有的)
7.不支持
Go不像Java那样完全支持面向对象编程,因此,不支持这些Java的结构。这些特性使用起来类似大多数OOP特性。因此,Go更适合被描述为基于对象的语言。使用Go确实能实现OOP的关键目标,但其实现方式不同于严格的OOP语言通常所使用的方式。
Go不支持真正的类(如,Java的class定义)。Go确实支持结构体,其像类,但不支持继承。Go确实允许嵌套结构体,这有些像内部类。
关于类型声明Go没有extends或者implements子句。Go没有这些子句所提供的继承。Go确实针对接口类型有隐式形式的implements。
Go不支持Java的Lambda(函数签名编译成类实例)。相反,Go支持作为参数传递的一等函数(通常字面值)。Go不支持方法引用(作为参数传递的Lambda的简单名称).
Go支持接口的方式与Java不同。Go接口允许鸭子(duck)类型。Go的接口不要求显式实现(Go不需要implements子句)。任何类型匹配了接口的所有方法,则隐式实现了接口。通常,Go的方法更灵活。
Java8及更高版本允许在接口中实现(具体)方法。Go不支持。Java允许在接口中声明常量;Go不允许。Java允许在接口中声明子类型。Go则不允许。下面是OOP的一些特性:
1.一个对象有一个标识符(以便区分其他对象)
2.一个对象可能(通常确实)具有状态(即实例数据、属性或字段)
3.一个对象可能(通常确实)具有行为(即成员函数或方法)
4.一个对象由称为类的模板描述/定义
5.类可按层次结构(继承)排列;实例时层次结构中类的组合
6.对象实例被封装;状态通常仅对方法可见
7.可在类层次结构的任何层级中声明变量;子类实例可被赋值给这些变量(多态性)
Java支持(但非强制)上述所有特性。Go则不是。Go堆上述特性的支持如下所示:
1.一个结构体实例有一个地址,通常可以作为它的表示(但不一定总是明显的);结构体实例与对象实例相似但不完全相同
2.一个结构体实例可能(通常确实)具有状态
3.一个结构体实例可能(经常)具有行为。
4.如同类,一个结构体实例由称为结构体类型的模板描述/定义
5.不直接支持;结构体可嵌入提供类似组合的其他结构体
6.支持但不常用(结构体字段通常是公有的)
7.不支持
历史上,OOP语言源自计算机模拟和改善人机交互的需求。OOP语言是基于实现模拟对象间传递消息来影响行为的想法而构思的。随着OOP、改进的行为重用可能性(即继承)而变得众所周知,它成为越来越受欢迎的编程风格。大多数现代语言具备该能力。
对许多人来说,Go缺乏完整的OOP可能是其最大的缺点。但是你一旦习惯了Go编程的用法,就不会怀念最初想象的OOP特性。Go是一种涉及良好且功能丰富的语言,其特性使其支持OOP的目标,而不包括其他语言(如Java)的复杂的OOP特性。
注意,OOP不是编写好的程序所必需的。所有现有的C程序证明了这一点,其中一些是大型和功能丰富的程序,例如操作系统和Web浏览器。事实上,有时OOP思维会在程序中强制使用不适当的结构。再一次说明,Go是一种类似C的语言。
OOP也不是实现高水平的重用所必需的。函数可以很好地扮演这个角色,特别是Go中地一等函数
对许多人来说,Go缺乏完整的OOP可能是其最大的缺点。但是你一旦习惯了Go编程的用法,就不会怀念最初想象的OOP特性。Go是一种涉及良好且功能丰富的语言,其特性使其支持OOP的目标,而不包括其他语言(如Java)的复杂的OOP特性。
注意,OOP不是编写好的程序所必需的。所有现有的C程序证明了这一点,其中一些是大型和功能丰富的程序,例如操作系统和Web浏览器。事实上,有时OOP思维会在程序中强制使用不适当的结构。再一次说明,Go是一种类似C的语言。
OOP也不是实现高水平的重用所必需的。函数可以很好地扮演这个角色,特别是Go中地一等函数
泛型和方法。
GGo当前不支持任意类型地泛型和方法。此处,泛型指能够容纳/使用多类型。Java中,Object类型变量是泛型,它可以保存任何引用类型的值。它可保存任何引用类型的值。Go中,interface{}类型的变量是泛型,因为它可以保存任何类型的值。
Java5改进了概念,可以通过指定声明的类型(例如容器类)支持特定(而不是)所有类型(例如字符串或数字),指定的类型作为容器类型的修饰符,例如List<String>(而不仅List)类型。Go的内置集合类型(切片、映射和通道)是该方式的泛型。
最初,Java不支持特定类型的泛型。它们在Java5中被引入,主要是为了缓解该语言中存在的集合的某些可用性问题。Java的泛型设计有一些由于向后兼容性而强加给它的负面特性或妥协。
目前,有一个将泛型添加到Go中的天已被批准,原因与添加到Java中的原因大致相同。看来,在该问题上Go将追随Java的脚步。
Java(和Go)定义的泛型主要是消除重复编码的语法糖。在Java中,它们根本不会影响运行时的代码(因为运行时类型擦除)。在Go中,它们可能会导致可执行文件中存在更多二进制代码,但不会比手动模拟的更多
GGo当前不支持任意类型地泛型和方法。此处,泛型指能够容纳/使用多类型。Java中,Object类型变量是泛型,它可以保存任何引用类型的值。它可保存任何引用类型的值。Go中,interface{}类型的变量是泛型,因为它可以保存任何类型的值。
Java5改进了概念,可以通过指定声明的类型(例如容器类)支持特定(而不是)所有类型(例如字符串或数字),指定的类型作为容器类型的修饰符,例如List<String>(而不仅List)类型。Go的内置集合类型(切片、映射和通道)是该方式的泛型。
最初,Java不支持特定类型的泛型。它们在Java5中被引入,主要是为了缓解该语言中存在的集合的某些可用性问题。Java的泛型设计有一些由于向后兼容性而强加给它的负面特性或妥协。
目前,有一个将泛型添加到Go中的天已被批准,原因与添加到Java中的原因大致相同。看来,在该问题上Go将追随Java的脚步。
Java(和Go)定义的泛型主要是消除重复编码的语法糖。在Java中,它们根本不会影响运行时的代码(因为运行时类型擦除)。在Go中,它们可能会导致可执行文件中存在更多二进制代码,但不会比手动模拟的更多
广泛的函数式编程能力。
Go确实支持一等函数,但不支持典型的通用实用函数(如map、reduce、select、exclude、forEach、find等),尽管大多数函数(强烈支持函数式编程范式)语言和Java(通过Lambda和STream)提供该类函数。该遗漏是Go语言设计者深思熟虑后的决定。当包含泛型时,Go可能会添加其中一些实用函数。
Go确实支持一等函数,但不支持典型的通用实用函数(如map、reduce、select、exclude、forEach、find等),尽管大多数函数(强烈支持函数式编程范式)语言和Java(通过Lambda和STream)提供该类函数。该遗漏是Go语言设计者深思熟虑后的决定。当包含泛型时,Go可能会添加其中一些实用函数。
原始值的装箱。
Java集合(数组除外)不能包含原始值,只能包含对象。因此,Java为每种原始类型提供了包装器类型。为了使集合更易于使用,Java会自动将原始类型包装(装箱)到包装器类型中以将其插入集合,并在从集合中取出该值时解包(拆箱)该值。Go支持可以包含原始类型的集合,因此不需要这样的装箱。注意,需要使用装箱时Java在内存使用方面的效率比Go低的一个领域。
Java集合(数组除外)不能包含原始值,只能包含对象。因此,Java为每种原始类型提供了包装器类型。为了使集合更易于使用,Java会自动将原始类型包装(装箱)到包装器类型中以将其插入集合,并在从集合中取出该值时解包(拆箱)该值。Go支持可以包含原始类型的集合,因此不需要这样的装箱。注意,需要使用装箱时Java在内存使用方面的效率比Go低的一个领域。
源码注解。
Go无注解。Go结构体字段可以由标签,起到类似但更受限的作用。注解、函数流以及Lambda,一起使Java(至少部分)成为一种声明式语言。Go几乎是纯粹的命令式语言。这是一种选择。这会使得Go代码更清晰但冗长。
注意:Go有类似Java的编译时注释的概念,其中源文件可包含特殊的注释(成为构建约束),构建器将解释这些注释以更改代码的处理方式。例如,在源代码的开始之处通过如下注释来指定生成代码的目标操作系统
// +build Linux,386
将仅为Linux操作系统和386架构构建代码。有另一种(通常是首选的)语法:前面的注释也可以写成:
// go:build linux,386
注意,一些如目标系统或硬件架构的限制条件可嵌入Go源文件名字,例如
xxx_windows.go
将仅针对Windows操作系统构建
Go无注解。Go结构体字段可以由标签,起到类似但更受限的作用。注解、函数流以及Lambda,一起使Java(至少部分)成为一种声明式语言。Go几乎是纯粹的命令式语言。这是一种选择。这会使得Go代码更清晰但冗长。
注意:Go有类似Java的编译时注释的概念,其中源文件可包含特殊的注释(成为构建约束),构建器将解释这些注释以更改代码的处理方式。例如,在源代码的开始之处通过如下注释来指定生成代码的目标操作系统
// +build Linux,386
将仅为Linux操作系统和386架构构建代码。有另一种(通常是首选的)语法:前面的注释也可以写成:
// go:build linux,386
注意,一些如目标系统或硬件架构的限制条件可嵌入Go源文件名字,例如
xxx_windows.go
将仅针对Windows操作系统构建
多种可见性。
Java支持四种可见性:
1.private——只在包含类型中的代码可见
2.default——只在同一包中的代码可见
3.protected——只在同一包或类型的子类中的代码可见
4.public——所有代码可见
Go仅支持default(Go中通常称为private或者package)和public可见性的对应项。Gopher经常将public可见性称为"导出可见性",将private可见性称为"不导出可见性"。
Java支持四种可见性:
1.private——只在包含类型中的代码可见
2.default——只在同一包中的代码可见
3.protected——只在同一包或类型的子类中的代码可见
4.public——所有代码可见
Go仅支持default(Go中通常称为private或者package)和public可见性的对应项。Gopher经常将public可见性称为"导出可见性",将private可见性称为"不导出可见性"。
重载/覆盖函数。
在Java中,在同一范围内可定义具备不同签名(不同数量或不同类型的参数)的同名函数。这种函数称为(通过参数多态性的形式)重载函数。Go不允许重载。Java中,可以在继承层次结构中重新定义具有相同名称和签名的函数。此类重新定义的函数(通过继承多态性)被调用覆盖。由于Go不支持继承,因此不允许这种覆盖。
在Java中,在同一范围内可定义具备不同签名(不同数量或不同类型的参数)的同名函数。这种函数称为(通过参数多态性的形式)重载函数。Go不允许重载。Java中,可以在继承层次结构中重新定义具有相同名称和签名的函数。此类重新定义的函数(通过继承多态性)被调用覆盖。由于Go不支持继承,因此不允许这种覆盖。
正式的枚举。
Java有正式的枚举类型,它是专用的类类型,具有离散的静态实例,以便进行相同(==)操作符的比较。Go不支持,它使用基于证书类型常量的iota操作符。在Java中枚举值可基于多种类型(但整数类型常见);在Go中,只允许整数类型。
注意,Java的枚举是类,像其他类一样可有字段和方法。它们还支持继承。Go的枚举无类似特性
Java有正式的枚举类型,它是专用的类类型,具有离散的静态实例,以便进行相同(==)操作符的比较。Go不支持,它使用基于证书类型常量的iota操作符。在Java中枚举值可基于多种类型(但整数类型常见);在Go中,只允许整数类型。
注意,Java的枚举是类,像其他类一样可有字段和方法。它们还支持继承。Go的枚举无类似特性
内置二进制数据自序列化。
Java具备将数据与对象序列化(转化成字节序列,在该用例中通常称为八位字节)成二进制形式的能力。Data{Input|Output}流和(子类)Object{Input|Output}流类型提供该功能的API。序列化过的数据通常写进文件,或者通过网络传输,或者有时保存进数据库。序列化可以为其他短暂对象提供持久性形式。序列化也是大多数远程过程调用(Remote Procedure。 RPC)机制的基础。
Java支持基本值、数组和任何数组结构(类实例)的序列化,这些数据类型可以包含基本类型或任何标有serializable(可序列化)接口的类型以及这些类型的任何集合。Java甚至支持带有循环引用的结构。Go没有提供与此完全对象序列化的直接对应项。在Go中,通常会将数据序列化为某些文本格式(比如JSON或者XML),并保存/发送该格式。与二进制表示相比,使用文本通常效率较低(需要更多字节和时间)。这些文本形式通常不支持数据结构内的循环引用。针对二进制数据,Go社区提供支持,例如谷歌Protocol Buffers。使用标准的Go库,可以创建自定义二进制格式,尽管有点繁琐
Java具备将数据与对象序列化(转化成字节序列,在该用例中通常称为八位字节)成二进制形式的能力。Data{Input|Output}流和(子类)Object{Input|Output}流类型提供该功能的API。序列化过的数据通常写进文件,或者通过网络传输,或者有时保存进数据库。序列化可以为其他短暂对象提供持久性形式。序列化也是大多数远程过程调用(Remote Procedure。 RPC)机制的基础。
Java支持基本值、数组和任何数组结构(类实例)的序列化,这些数据类型可以包含基本类型或任何标有serializable(可序列化)接口的类型以及这些类型的任何集合。Java甚至支持带有循环引用的结构。Go没有提供与此完全对象序列化的直接对应项。在Go中,通常会将数据序列化为某些文本格式(比如JSON或者XML),并保存/发送该格式。与二进制表示相比,使用文本通常效率较低(需要更多字节和时间)。这些文本形式通常不支持数据结构内的循环引用。针对二进制数据,Go社区提供支持,例如谷歌Protocol Buffers。使用标准的Go库,可以创建自定义二进制格式,尽管有点繁琐
并发集合。
Java有很多集合实现,每个集合针对不同用力提供了微妙优化。就像Python和Javascript等其他语言一样,Go采用较简单的方法,在所有用力中使用单个集合实现,比如列表或映射。这在运行时可能不是最优的,但它非常易学和易用。除了标准项外,Java还具有多种并发(在多线程中使用时性能良好、低争用的)类型和集合。ConcurrentHashMap可能时最流行的例子。Go有一些标准库的对应项,例如sync.Map类型。一般来说,这种并发类型在Go中使用频率较低。Go经常使用替代方案。例如通道。
Java有很多集合实现,每个集合针对不同用力提供了微妙优化。就像Python和Javascript等其他语言一样,Go采用较简单的方法,在所有用力中使用单个集合实现,比如列表或映射。这在运行时可能不是最优的,但它非常易学和易用。除了标准项外,Java还具有多种并发(在多线程中使用时性能良好、低争用的)类型和集合。ConcurrentHashMap可能时最流行的例子。Go有一些标准库的对应项,例如sync.Map类型。一般来说,这种并发类型在Go中使用频率较低。Go经常使用替代方案。例如通道。
Go与Java的深度比较
Go是一种比Java简单得多的语言,Go甚至可以说是一种比C更简单的语言。例如《Java语言规范》目前约有800页,而《Go语言规范》目前大约有85页。显然,Java的语言复杂性要比Go高得多。
Go标准库也是如此。就所提供的类型和函数的数量以及纯粹的代码行而言,Go标准库比Java标准库要小得多。在某些方面,Go库的功能较少,但即便如此,Go库通常也足以编写许多有用的程序。
与Java社区一样,标准库中未包含的功能通常由社区成员提供。个人认为:Java库尤其是社区提供的库,通常比许多相应的Go库更成熟。
Java库通常也更重量级(做得更多),并且可以说比相应的Go库更难学习和使用。一般来说,Go库对应典型的Go用例来说更合适,因此,Go并不缺乏它的适用性。考虑到标准Java库的大型代码库的大小,Java9被迫将它们拆分为可选模块,从而减少了Java运行时占用空间。此外,许多旧库已被弃用(现在已删除一些)以进一步减小运行时的大小。
Go标准库也是如此。就所提供的类型和函数的数量以及纯粹的代码行而言,Go标准库比Java标准库要小得多。在某些方面,Go库的功能较少,但即便如此,Go库通常也足以编写许多有用的程序。
与Java社区一样,标准库中未包含的功能通常由社区成员提供。个人认为:Java库尤其是社区提供的库,通常比许多相应的Go库更成熟。
Java库通常也更重量级(做得更多),并且可以说比相应的Go库更难学习和使用。一般来说,Go库对应典型的Go用例来说更合适,因此,Go并不缺乏它的适用性。考虑到标准Java库的大型代码库的大小,Java9被迫将它们拆分为可选模块,从而减少了Java运行时占用空间。此外,许多旧库已被弃用(现在已删除一些)以进一步减小运行时的大小。
Go社区主要由Google和众多个人或小型团队组成。有较少经过审查的组织,如Apache软件基金会和Spring,该类组织为Java提供第三方库和框架。
GGo和Java5支持类似,但不同的语句和数据类型。Go和Java支持布尔值、字符、整数、和浮点数。在Go中,字符称为rune,4个字节;在Java中,字符称为char,2字节。二者都是用Unicode编码。通常Go的rune应用优于Java的char类型,由于字符变量可代表任何合法的Unicode字符。
Java和Go都支持字符串类型,他们实际上是字符数组。在Go中,字符串是一种基本类型。Go在字符串中使用Unicode转换格式(UTF-8),从而相比Java对应的字符串使用更少的字节,尤其是英文文本。
每种语言的类型操作符都是类似的。Go还支持复数浮点数据,而Java不支持。Java支持大形式的整数和十进制浮点数。Go支持大形式的整数和二进制浮点数。Go和Java支持同类值数组。Java在类中聚合异类值,Go使用结构体。
Java支持对类实例的引用。Go使用指针,其可定位任何类型的值。Java和Go共享很多类似语句。两者都有赋值语句。两者都有增强(操作符参与)赋值。Go有多重(又名元组)赋值。两者都有条件语句,例如if和switch。Go增加select。两个语言都支持循环.Java有while、do和for语句。而Go只有for。二者均有变量声明语句。Go为局部变量添加了方便的声明和赋值组合。Go提供基于任何现有类型的通用类型声明。Java只能声明类、接口或枚举类型。
Go和Java都具有异常功能。Java可以抛出和捕获Throwable实例。Go可以引发panic并从其中回复。
GGo和Java5支持类似,但不同的语句和数据类型。Go和Java支持布尔值、字符、整数、和浮点数。在Go中,字符称为rune,4个字节;在Java中,字符称为char,2字节。二者都是用Unicode编码。通常Go的rune应用优于Java的char类型,由于字符变量可代表任何合法的Unicode字符。
Java和Go都支持字符串类型,他们实际上是字符数组。在Go中,字符串是一种基本类型。Go在字符串中使用Unicode转换格式(UTF-8),从而相比Java对应的字符串使用更少的字节,尤其是英文文本。
每种语言的类型操作符都是类似的。Go还支持复数浮点数据,而Java不支持。Java支持大形式的整数和十进制浮点数。Go支持大形式的整数和二进制浮点数。Go和Java支持同类值数组。Java在类中聚合异类值,Go使用结构体。
Java支持对类实例的引用。Go使用指针,其可定位任何类型的值。Java和Go共享很多类似语句。两者都有赋值语句。两者都有增强(操作符参与)赋值。Go有多重(又名元组)赋值。两者都有条件语句,例如if和switch。Go增加select。两个语言都支持循环.Java有while、do和for语句。而Go只有for。二者均有变量声明语句。Go为局部变量添加了方便的声明和赋值组合。Go提供基于任何现有类型的通用类型声明。Java只能声明类、接口或枚举类型。
Go和Java都具有异常功能。Java可以抛出和捕获Throwable实例。Go可以引发panic并从其中回复。
Go在理念上有一些不同于Java的地方:
1.Go倾向于遵循“少即是多”的理念。
创建Java的最初动机是简化C++的复杂性。创建Go的动机类似,但目的是简化C(以及Java)。例如,Go语言中做某事通常只有一种方案(而Java通常有集中).注意,Java的大部分语法都源自C++语法,而C++语法是C语法的超级,因此,Java语法也基于C语法。具体来说,Java的大部分语句都基于C++语义,Go更针对C功能及其支持库。
2.Go的创建是为了适应像C语言这样的利基市场。
相比Go与C++(C++是C语言大超集,Java源自C++),Go与C之间有更多的共同点。它旨在称为一种类似C的系统编程语言,但具有改进的安全性和语义,以满足现代计算机系统的需求,特别是改进了多核处理器的易用性。Java与次类似,但旨在支持更广泛的用例集。Go在其源语法和格式(符号、操作符、标点符号和空格的使用)上与C(以及Java)类似,由于Java也是基于C的,Go和Java在这方面也非常相似。
3.Go语法更简单。
例如,当可以隐含语句终止符时,Go的分号(;)可省略(不存在)。注意,使用省略的语句终止符时惯用的,也是首选的。与Java相比,这可使代码更简洁,更容易读/写。此外,Go删除了众多Java括号((...))的使用。在关联类型之外定义方法可以使代码更具可读性。
4.Go的优化点/目标与Java不同。
Java更像是一种应用程序(尤其时商业)语言。Go更加面向系统。这些优化点强烈影响Go语言的涉及/性质。像所有图灵完备的语言一样,Java和Go有重叠的适用性领域,其中任何一个都是合适的选择。
5.Go通常比Java更具命令性和明确性。
Java,尤其是在使用Java8(及更高版本)特性的情况下,可以比Go更具声明性和抽象性。在某些方面,Go更像Java的第一(1.0)版本。而不是Java的当前版本
1.Go倾向于遵循“少即是多”的理念。
创建Java的最初动机是简化C++的复杂性。创建Go的动机类似,但目的是简化C(以及Java)。例如,Go语言中做某事通常只有一种方案(而Java通常有集中).注意,Java的大部分语法都源自C++语法,而C++语法是C语法的超级,因此,Java语法也基于C语法。具体来说,Java的大部分语句都基于C++语义,Go更针对C功能及其支持库。
2.Go的创建是为了适应像C语言这样的利基市场。
相比Go与C++(C++是C语言大超集,Java源自C++),Go与C之间有更多的共同点。它旨在称为一种类似C的系统编程语言,但具有改进的安全性和语义,以满足现代计算机系统的需求,特别是改进了多核处理器的易用性。Java与次类似,但旨在支持更广泛的用例集。Go在其源语法和格式(符号、操作符、标点符号和空格的使用)上与C(以及Java)类似,由于Java也是基于C的,Go和Java在这方面也非常相似。
3.Go语法更简单。
例如,当可以隐含语句终止符时,Go的分号(;)可省略(不存在)。注意,使用省略的语句终止符时惯用的,也是首选的。与Java相比,这可使代码更简洁,更容易读/写。此外,Go删除了众多Java括号((...))的使用。在关联类型之外定义方法可以使代码更具可读性。
4.Go的优化点/目标与Java不同。
Java更像是一种应用程序(尤其时商业)语言。Go更加面向系统。这些优化点强烈影响Go语言的涉及/性质。像所有图灵完备的语言一样,Java和Go有重叠的适用性领域,其中任何一个都是合适的选择。
5.Go通常比Java更具命令性和明确性。
Java,尤其是在使用Java8(及更高版本)特性的情况下,可以比Go更具声明性和抽象性。在某些方面,Go更像Java的第一(1.0)版本。而不是Java的当前版本
6.在Go中,大多数行为都是显式(希望时显而易见的)编码的。
行为并不隐藏在函数式编程特性中,如Java Stream和Lambda支持的特性。这可以使得Go代码风格更具重复性。错误时显式处理的,比如在每个函数返回时,而不是像Java中那样远程地或系统地异常处理。
除了结构体字段标签之外,Go没有像Java那样的注解概念。同样,这是为了让Go代码更加透明和清晰。注释就像任何声明性/后处理方法一样,倾向于隐藏或延迟行为。
Java注解驱动方法的优秀例子是Spring MVC和JAX-RS如何在Web应用服务器中定义REST API端点。注解通常由第三方框架在运行时解释,而不是在编译时。另一个示例是如何为对象关系映射器ORM定义数据库实体。在这种有限的情况下,Go通过结构体标签提供选项,这些标签通常用于为这些工具提供建议。社区提供的GORM ORM就是一个例子,其他应用中的内置JSON和XML处理器等也使用标签。
7.Go支持源代码生成器概念。
生成器是编写Go代码的Go代码。生成器可由Go构建器有条件地运行,生成器的用例很多。例如可以使用生成器来机械地创建模仿Java泛型但通过预处理器完成的集合类型(比如为List<T>、Stack<t>)等的每个所需T/K生成的类型。Go社区提供了这样的选择。
8.Go支持指针,Java支持引用。
对计算机而言,指针和引用是类似的。但对人类而言是不同的。引用相比指针是更抽象的概念。指针式保存其他值的机器地址的变量。引用是保存其他值的定位器(可以是地址或其他)的变量。
在Java中,引用总是在使用时自动解引用(赋值除外).指针可能是也可能不是。使用指针,可以获取某些数据的地址并将其保存在指针变量中,并将指针转换为其他类型,例如整数。这对引用是不可能的。
与C(或C++)不同,Java和Go都限制指针/引用来处理特定类型的数据。没有类似C的void指针,此外,也没有类似C中允许的指针算法。因此Go和Java一样,可以说比C更安全,由于寻址错误而失败的可能性更小。
行为并不隐藏在函数式编程特性中,如Java Stream和Lambda支持的特性。这可以使得Go代码风格更具重复性。错误时显式处理的,比如在每个函数返回时,而不是像Java中那样远程地或系统地异常处理。
除了结构体字段标签之外,Go没有像Java那样的注解概念。同样,这是为了让Go代码更加透明和清晰。注释就像任何声明性/后处理方法一样,倾向于隐藏或延迟行为。
Java注解驱动方法的优秀例子是Spring MVC和JAX-RS如何在Web应用服务器中定义REST API端点。注解通常由第三方框架在运行时解释,而不是在编译时。另一个示例是如何为对象关系映射器ORM定义数据库实体。在这种有限的情况下,Go通过结构体标签提供选项,这些标签通常用于为这些工具提供建议。社区提供的GORM ORM就是一个例子,其他应用中的内置JSON和XML处理器等也使用标签。
7.Go支持源代码生成器概念。
生成器是编写Go代码的Go代码。生成器可由Go构建器有条件地运行,生成器的用例很多。例如可以使用生成器来机械地创建模仿Java泛型但通过预处理器完成的集合类型(比如为List<T>、Stack<t>)等的每个所需T/K生成的类型。Go社区提供了这样的选择。
8.Go支持指针,Java支持引用。
对计算机而言,指针和引用是类似的。但对人类而言是不同的。引用相比指针是更抽象的概念。指针式保存其他值的机器地址的变量。引用是保存其他值的定位器(可以是地址或其他)的变量。
在Java中,引用总是在使用时自动解引用(赋值除外).指针可能是也可能不是。使用指针,可以获取某些数据的地址并将其保存在指针变量中,并将指针转换为其他类型,例如整数。这对引用是不可能的。
与C(或C++)不同,Java和Go都限制指针/引用来处理特定类型的数据。没有类似C的void指针,此外,也没有类似C中允许的指针算法。因此Go和Java一样,可以说比C更安全,由于寻址错误而失败的可能性更小。
Go的关键特性
在最基本的层面上,Go源代码与Java一样,是一个字符流,通常被视为一系列行。Go源代码文件时按照UTF-8编码编写的(在Java中通常也是如此)。Go没有像Java那样的预处理器,可以将Unicode转义符处理为原始字符,所有Unicode字符都被视为相同。转义符只能出现在字符串或字符文本中,而不能出现在标识符或其他地方。
与在Java中一样,字符被分组为空白字符(空格、制表符、新行等的序列)和token的结构,Go编译器通过解析它们以处理Go代码。Go经常使用空格作为token分隔符。除新航外。空白符序列被视为单个空格字符。在Go中,新航可以隐式生成分号(;)表明语句结束,因此有点特殊。当遇到行尾时,Go词法分析器会自动添加一个分号,并且允许在前一个token后添加分行好。通常,在一些大括号({...})或圆括号((...))括起来的列表中,可以在逗号(,)之后拆分行。
虽然方便,但这也限制了某些token相对于彼此必需出现的位置。因此,Go比Java更严格地将源代码语句排列成行。最明显地就是引入块地左大括号({)必须与任何前缀语句位于同一行(而不是跟随在后)。
人们一般认为Go程序是一个token流,通常按代码行顺序排列。token通常是标识符,其中一些时保留字、分隔符、标点符号或操作符。上面代码块是一个简单的Go源文件,该文件通常称为main.go,位于称为main的目录中。该源文件是一个完整的程序。程序入口点指定位于main包(1)中(所有入口点都必须如此)。与Java一样,main()函数(3~5)是必须的入口点。在这里,main类似于Java的static方法。此方法使用导入的Println标准库函数(2,4)来显示消息.与Java一样,Go有字符串字面量(4)。区别在于Go中字符串是内置的(与java.lang.String库相比)类型。
与在Java中一样,字符被分组为空白字符(空格、制表符、新行等的序列)和token的结构,Go编译器通过解析它们以处理Go代码。Go经常使用空格作为token分隔符。除新航外。空白符序列被视为单个空格字符。在Go中,新航可以隐式生成分号(;)表明语句结束,因此有点特殊。当遇到行尾时,Go词法分析器会自动添加一个分号,并且允许在前一个token后添加分行好。通常,在一些大括号({...})或圆括号((...))括起来的列表中,可以在逗号(,)之后拆分行。
虽然方便,但这也限制了某些token相对于彼此必需出现的位置。因此,Go比Java更严格地将源代码语句排列成行。最明显地就是引入块地左大括号({)必须与任何前缀语句位于同一行(而不是跟随在后)。
人们一般认为Go程序是一个token流,通常按代码行顺序排列。token通常是标识符,其中一些时保留字、分隔符、标点符号或操作符。上面代码块是一个简单的Go源文件,该文件通常称为main.go,位于称为main的目录中。该源文件是一个完整的程序。程序入口点指定位于main包(1)中(所有入口点都必须如此)。与Java一样,main()函数(3~5)是必须的入口点。在这里,main类似于Java的static方法。此方法使用导入的Println标准库函数(2,4)来显示消息.与Java一样,Go有字符串字面量(4)。区别在于Go中字符串是内置的(与java.lang.String库相比)类型。
注意,与Java不同,在Go中,main的命令行参数由库函数调用访问,而不是作为main函数的参数访问。此示例中没有访问它们。词法分析器在缺少分号但预期的行尾注入分号。这种形式是合法的Go代码,但不是习惯用法。在Go习惯用法中,语法结尾的分号通常被习惯性省略,除非要在一行中输入多个语句,但该情况很少见。Java中的等效程序如上面代码所示.
注意:在Java中,main()是公有的,但在Go中却不是。由于Go不要求函数是某种类型(如System)的成员(又名方法),因此可以直接使用打印函数,但是必须限定所属包(在本例中为fmt),而不是由所属类限定。在Go中,许多函数的行为类似于Java中的static函数(没有关联的实例)。
注意:在Java中,main()是公有的,但在Go中却不是。由于Go不要求函数是某种类型(如System)的成员(又名方法),因此可以直接使用打印函数,但是必须限定所属包(在本例中为fmt),而不是由所属类限定。在Go中,许多函数的行为类似于Java中的static函数(没有关联的实例)。
Java不要求代码位于包中(可以使用默认包),但强烈建议使用包,并且通常会提供包.因此,Java示例通常类似于上述代码如果没有编译器自动导入java.lang.*包,上例将是import java.lang.*;除了封闭的Main类,上述代码看起来更像Go版本。注意,在Go代码中,不需要封闭类(Main)来创建可以运行的程序。这可以减少基本程序所需的行数。但确实有一个明显的缺点,main函数必须位于main包中,并且程序(或源代码树)中只能有一个main包。而在Java中,每个具有main方法的类都可以作为不同的程序。
简单的Go示例。
上例代码将启动一个HTTP服务器(通过ListenAndServe),该服务器为每个“/message”路径的请求返回一条随机消息(对于任何HTTP方法,这并不常见)。服务器在用户中之前将一直运行,并会自动返回许多错误和成功状态。此示例很简短。服务器功能的核心代码只需要四行。对大多数Java库或框架来说,如此简单是不可能的。在示例中,即时你对Go语言知之甚少,代码也通常是一目了然的。这证明了Go语言及其运行时库的简单性和透明性
上例代码将启动一个HTTP服务器(通过ListenAndServe),该服务器为每个“/message”路径的请求返回一条随机消息(对于任何HTTP方法,这并不常见)。服务器在用户中之前将一直运行,并会自动返回许多错误和成功状态。此示例很简短。服务器功能的核心代码只需要四行。对大多数Java库或框架来说,如此简单是不可能的。在示例中,即时你对Go语言知之甚少,代码也通常是一目了然的。这证明了Go语言及其运行时库的简单性和透明性
构建Go程序。
通常,任何Go代码都是从源代码构建的,包括所有需要的源文件(编写的源代码和任何其他库)。这可确保任何源文件的所有更改(编辑)都包含在生成的EXE中。它还允许进行潜在的跨源文件优化。
在C/C++中,通常需要Make工具来确保重新编译与更改相关的其他关键中的所有代码。Java编译器会有条件地(基于源文件时间戳)冲洗那边一其他类所依赖的类/Go和Java都依赖包结构来查找所有引用的源文件。虽然Go的方式可能看起来效率较低且可能速度较慢,但Go编译器通常速度非常快,除了大型程序之外,人们很少注意到每次编译所有源代码所需的时间。编译器可以创建类似于Java JAR文件的预编译包归档,以便在它检测到包的源文件自上次编译依赖未发生更改,缩短构建时间。一些开发环境可能包括将某些源(特别是库类型)预编译为"目标"文件以缩短构建时间,但Go应用程序开发人员通常不使用这种方法。社区开发人员通常对库使用这种方法。go install 命令可以做到这一点。它创建包含预编译代码的存档文件(扩展名为.a)通常这些存档文件放在pkg目录中。与Java和大多数编译(相对解释)语言一样,所有Go源代码都由Go编译器按以下步骤进行处理:
1.词法分析——逐个字符读取源,并识别token
2.解析和验证——逐个token读取源,并生成抽象语法树(Abstract Syntax Tree, AST)。
3.可选(但一贯的)优化——重新排列AST以使结构更好(通常执行速度更快,但生成的代码可能更少)
4.代码生成——创建机器码并将其保存到目标文件中,在Java中,字节码(JVM的机器码)被写入类文件中
注意,在第一步中,没有以分号结尾的语句都将添加上分号。Go构建器的行为与第三方Java构建工具Maven或Gradle非常相似,因为它解析依赖库(适用于Go编译器和代码链接器)并创建完整的可运行代码集(在Java中,通常以一个或多个JAR/WAR文件的形式,在Go中以EXE文件的形式)。Go构建器添加了以下步骤:
1.外部引用链接——代码中使用的所有刚刚编译的源代码和外部库都会被解析并集成在一起
2.可执行文件构建——构建特定于操作系统的可执行文件
3.可选执行——在生产或测试环境中启动可执行文件
构建的源代码可以是应用程序代码或任何依赖项(或库).依赖项获取通常作为前序的手动前置步骤(即使用go get/ go install)获取为源文件或更常见的存档文件。Go模块可以使依赖项版本的选择更具可预测性。
Go构建器可以说比Java编译器(javac)更完整。Java编译器假定程序在运行时由JVM实时(JIT)汇编,因此不存在静态链接和程序构建阶段。在Java中,这种运行时链接可能会导致在编译时与运行时使用不同的库,这可能会出现问题。Go无此现象。
Go允许为多种操作系统和不同的硬件架构构建代码。随着时间的推移,确切的集合会随着Go版本的变化而变化。某些操作系统或硬件架构可能未包含在标准的Go安装包中
通常,任何Go代码都是从源代码构建的,包括所有需要的源文件(编写的源代码和任何其他库)。这可确保任何源文件的所有更改(编辑)都包含在生成的EXE中。它还允许进行潜在的跨源文件优化。
在C/C++中,通常需要Make工具来确保重新编译与更改相关的其他关键中的所有代码。Java编译器会有条件地(基于源文件时间戳)冲洗那边一其他类所依赖的类/Go和Java都依赖包结构来查找所有引用的源文件。虽然Go的方式可能看起来效率较低且可能速度较慢,但Go编译器通常速度非常快,除了大型程序之外,人们很少注意到每次编译所有源代码所需的时间。编译器可以创建类似于Java JAR文件的预编译包归档,以便在它检测到包的源文件自上次编译依赖未发生更改,缩短构建时间。一些开发环境可能包括将某些源(特别是库类型)预编译为"目标"文件以缩短构建时间,但Go应用程序开发人员通常不使用这种方法。社区开发人员通常对库使用这种方法。go install 命令可以做到这一点。它创建包含预编译代码的存档文件(扩展名为.a)通常这些存档文件放在pkg目录中。与Java和大多数编译(相对解释)语言一样,所有Go源代码都由Go编译器按以下步骤进行处理:
1.词法分析——逐个字符读取源,并识别token
2.解析和验证——逐个token读取源,并生成抽象语法树(Abstract Syntax Tree, AST)。
3.可选(但一贯的)优化——重新排列AST以使结构更好(通常执行速度更快,但生成的代码可能更少)
4.代码生成——创建机器码并将其保存到目标文件中,在Java中,字节码(JVM的机器码)被写入类文件中
注意,在第一步中,没有以分号结尾的语句都将添加上分号。Go构建器的行为与第三方Java构建工具Maven或Gradle非常相似,因为它解析依赖库(适用于Go编译器和代码链接器)并创建完整的可运行代码集(在Java中,通常以一个或多个JAR/WAR文件的形式,在Go中以EXE文件的形式)。Go构建器添加了以下步骤:
1.外部引用链接——代码中使用的所有刚刚编译的源代码和外部库都会被解析并集成在一起
2.可执行文件构建——构建特定于操作系统的可执行文件
3.可选执行——在生产或测试环境中启动可执行文件
构建的源代码可以是应用程序代码或任何依赖项(或库).依赖项获取通常作为前序的手动前置步骤(即使用go get/ go install)获取为源文件或更常见的存档文件。Go模块可以使依赖项版本的选择更具可预测性。
Go构建器可以说比Java编译器(javac)更完整。Java编译器假定程序在运行时由JVM实时(JIT)汇编,因此不存在静态链接和程序构建阶段。在Java中,这种运行时链接可能会导致在编译时与运行时使用不同的库,这可能会出现问题。Go无此现象。
Go允许为多种操作系统和不同的硬件架构构建代码。随着时间的推移,确切的集合会随着Go版本的变化而变化。某些操作系统或硬件架构可能未包含在标准的Go安装包中
运行Go程序。
Go编译器创建可执行二进制文件EXE。生成的EXE可以从操作系统命令运行。与Java不同,Java必须有Java运行时环境JRE机器Java虚拟机才能运行程序。Go EXE是独立的,只需要标准的操作系统功能,所有其他必需的库都嵌入在EXE中。EXE还嵌入了Go Runtime GRT。这个GRT类似于JRE,但比JRE小得多。GRT没有正式定义,但它至少包括内置的库函数、垃圾收集器和对go协程(类似于轻量级Java线程)的支持。
这意味着Go没有与包含源代码字节码(目标代码)的Java.class(对象)等价的文件,也没有包含这些类文件集合的Java归档(jar)文件。
给定一个构建成EXE的Go程序(在Windows环境下),名为myprog.exe,要给他运行,只需执行
myprog arg1 ... argN
但在Java中,假设该类已编译到当前目录中的MyProg.class仍需执行以下操作:
java -cp ,; MyProg arg1...argN
在这里,我们启动了必需包含在操作系统路径中的JVM java.exe并向其提供了任何必需的类目录或JAR文件的位置。与Python和其他语言解释器一样,JVM将开发的程序(.class)作为参数并运行它,而不是操作系统来完成
Go编译器创建可执行二进制文件EXE。生成的EXE可以从操作系统命令运行。与Java不同,Java必须有Java运行时环境JRE机器Java虚拟机才能运行程序。Go EXE是独立的,只需要标准的操作系统功能,所有其他必需的库都嵌入在EXE中。EXE还嵌入了Go Runtime GRT。这个GRT类似于JRE,但比JRE小得多。GRT没有正式定义,但它至少包括内置的库函数、垃圾收集器和对go协程(类似于轻量级Java线程)的支持。
这意味着Go没有与包含源代码字节码(目标代码)的Java.class(对象)等价的文件,也没有包含这些类文件集合的Java归档(jar)文件。
给定一个构建成EXE的Go程序(在Windows环境下),名为myprog.exe,要给他运行,只需执行
myprog arg1 ... argN
但在Java中,假设该类已编译到当前目录中的MyProg.class仍需执行以下操作:
java -cp ,; MyProg arg1...argN
在这里,我们启动了必需包含在操作系统路径中的JVM java.exe并向其提供了任何必需的类目录或JAR文件的位置。与Python和其他语言解释器一样,JVM将开发的程序(.class)作为参数并运行它,而不是操作系统来完成
字节码与实码。
Go方法与Java方法形成了鲜明的对比,Java的编译器生成与操作系统和硬件无关的字节码对象文件。JVM负责解释字节码或将字节码转换未操作系统或硬件相关的代码。这种转换通常是在运行时(相对编译/构建时)由JIT或HotSpot根据使用情况优化字节码编译器完成的,这些字节码编译器是JVM的一部分。
这种差异体现了Go相对于Java 的一个优势。生成Go程序时,所有代码都可运行的形式解析到其影响中。操作系统只需要将文件读入内存,就可以立即开始执行。在Java中,代码以增量方式(读取许多较小的文件)构建在内存中,并且代码在运行时需要进行JIT即时编译和链接。这种增量读取和JIT行为会显著降低程序启动速度,但是一旦启动完成,Java代码就可以像Go代码一样快速运行。此外,在Java中,一些必需的类文件可能不可用,将导致程序突然崩溃。这在Go中不可能发生。
所以,人们可能会问:哪个更快?Java还是Go?
答案是,正如生活中的许多事情一样,这得看情况!基于上述原因,Go程序往往启动得更快。加载后,区别就不是那么明显了。Go是静态编译得,这意味着它也是静态化得。所有优化都是由Go编译器仅基于源代码自身的信息完成的。使用Java JIT编译器时,情况类似,但有优化在运行时完成的。但是Java还有一个HotSpot编译器,它使用运行时信息来改进优化。当运行时条件随时间而变化时,它甚至可以重新优化代码。因此,从长远来看,可以期望Java代码得到更好的优化,从而有可能运行得更快。
但是程序运行时并不总是依赖于它自己的代码。很多时候,第三方服务(如数据库系统和远程服务器)可以主导程序的执行时间,再多的优化也无法弥补这一点。但是更好地使用并发编程模式可能会有帮助。
与C/C++等早期语言相比,Java最初的优势之一是它相对易于使用,并且内置了对操作系统线程的支持。凭借Go协程,Go本质上比Java做得更好。因此,在可以进行高度并发编程得情况下,可以预期Go在一般情况下优于Java。
Java提供对Java编译器(javac)得运行时访问。这允许Java代码创建Java源代码,然后对其进行编译。因为Java可以在运行时加载类,所以这允许一种自扩展得Java程序形式。
Go方法与Java方法形成了鲜明的对比,Java的编译器生成与操作系统和硬件无关的字节码对象文件。JVM负责解释字节码或将字节码转换未操作系统或硬件相关的代码。这种转换通常是在运行时(相对编译/构建时)由JIT或HotSpot根据使用情况优化字节码编译器完成的,这些字节码编译器是JVM的一部分。
这种差异体现了Go相对于Java 的一个优势。生成Go程序时,所有代码都可运行的形式解析到其影响中。操作系统只需要将文件读入内存,就可以立即开始执行。在Java中,代码以增量方式(读取许多较小的文件)构建在内存中,并且代码在运行时需要进行JIT即时编译和链接。这种增量读取和JIT行为会显著降低程序启动速度,但是一旦启动完成,Java代码就可以像Go代码一样快速运行。此外,在Java中,一些必需的类文件可能不可用,将导致程序突然崩溃。这在Go中不可能发生。
所以,人们可能会问:哪个更快?Java还是Go?
答案是,正如生活中的许多事情一样,这得看情况!基于上述原因,Go程序往往启动得更快。加载后,区别就不是那么明显了。Go是静态编译得,这意味着它也是静态化得。所有优化都是由Go编译器仅基于源代码自身的信息完成的。使用Java JIT编译器时,情况类似,但有优化在运行时完成的。但是Java还有一个HotSpot编译器,它使用运行时信息来改进优化。当运行时条件随时间而变化时,它甚至可以重新优化代码。因此,从长远来看,可以期望Java代码得到更好的优化,从而有可能运行得更快。
但是程序运行时并不总是依赖于它自己的代码。很多时候,第三方服务(如数据库系统和远程服务器)可以主导程序的执行时间,再多的优化也无法弥补这一点。但是更好地使用并发编程模式可能会有帮助。
与C/C++等早期语言相比,Java最初的优势之一是它相对易于使用,并且内置了对操作系统线程的支持。凭借Go协程,Go本质上比Java做得更好。因此,在可以进行高度并发编程得情况下,可以预期Go在一般情况下优于Java。
Java提供对Java编译器(javac)得运行时访问。这允许Java代码创建Java源代码,然后对其进行编译。因为Java可以在运行时加载类,所以这允许一种自扩展得Java程序形式。
GO有一些类似的支持,可以通过各种Go标准库子包来处理Go代码,但Go无法在运行时可靠地扩展程序。
Go确实对动态插件(可能有动态代码)的支持有限(依赖于操作系统),库支持也不完整。动态插件是否最终会称为完全受支持的共鞥你尚未确定。Go代码可以动态编译和构建,然后可以启动生成的可执行文件(作为单独的操作系统进程)。这与Java方法有一些相似之处,但插件必需在不同的进程中运行。Java的javac编译器还允许在编译期间运行一些外部代码,允许修改抽象语法树(AST),AST是编译器对解析的Java类的内部表示。这允许编译时处理注释。例如Lombok工具使用了此功能,该工具可以自动执行一些常见的Java编码操作。
Go也有类似的支持,例如,该支持用于内置的Go格式化和linting工具中,但任何开发人员都可以利用它来构建功能强大的Go语言处理工具。
虽然Go通常与操作系统无关,但基于操作系统类型,它不一定是无偏见的。与Java一样,Go被设计为在基于Unix的系统上运行。Go支持Windows,但不将其作为主要的操作系统类型,。这种偏见体现在几个方面,例如命令行处理和文件系统访问。Go提供对运行时操作系统类型和硬件架构的访问,以便代码可以根据需要进行调整。
Java和Go都可以在运行时给代码添加测量功能(测量/配置文件).Java管理扩展Java Management Extension, JMX通常允许添加静态和动态测量。Go的选项更加静态。两者都允许远程访问这些测量值
Go确实对动态插件(可能有动态代码)的支持有限(依赖于操作系统),库支持也不完整。动态插件是否最终会称为完全受支持的共鞥你尚未确定。Go代码可以动态编译和构建,然后可以启动生成的可执行文件(作为单独的操作系统进程)。这与Java方法有一些相似之处,但插件必需在不同的进程中运行。Java的javac编译器还允许在编译期间运行一些外部代码,允许修改抽象语法树(AST),AST是编译器对解析的Java类的内部表示。这允许编译时处理注释。例如Lombok工具使用了此功能,该工具可以自动执行一些常见的Java编码操作。
Go也有类似的支持,例如,该支持用于内置的Go格式化和linting工具中,但任何开发人员都可以利用它来构建功能强大的Go语言处理工具。
虽然Go通常与操作系统无关,但基于操作系统类型,它不一定是无偏见的。与Java一样,Go被设计为在基于Unix的系统上运行。Go支持Windows,但不将其作为主要的操作系统类型,。这种偏见体现在几个方面,例如命令行处理和文件系统访问。Go提供对运行时操作系统类型和硬件架构的访问,以便代码可以根据需要进行调整。
Java和Go都可以在运行时给代码添加测量功能(测量/配置文件).Java管理扩展Java Management Extension, JMX通常允许添加静态和动态测量。Go的选项更加静态。两者都允许远程访问这些测量值
Go运行程序而非类。
Go没有Java虚拟机(JVM)或Java运行时环境(JRE-JVM加上标准Java类库)的直接对应项。Go有运行时,它提供了支持Go语义所需的函数。这包括其集合类型和Go协程的库。它还包括一个垃圾收集器,用于管理堆内存分配。Go运行时(通常为几MB)比JRE(通常为几百MB)小得多。
Go的代码、任何库和运行时都被构建(链接)到操作系统(OS)运行的单个可执行文件中。这与Java程序汇编和链接的JIT方式形成了鲜明的对比。Go在构建时使用早期(静态)链接。Java则在运行时做后期(动态链接).
Go方法生成的可执行文件时独立的。(无须安装其他必备组件, 如JRE),这使得它部署起来比典型的Java更容易。这就是Go在容器化(如Docker、k8s)环境中如此流行的原因之一。
Go正变得更加独立。例如,Go1.16增加了将文字内容(例如HTML、CSS或JavaScript文件等文本目录)嵌入EXE主体的功能,在过去,这需要交付独立的文件。如果充分利用,完整的解决方案(如Web服务器)可以作为单个二进制文件提供。这种独也立具有额外的好处,那就是只有在运行时才有可能发现丢失的库或数据。这确实意味着可执行文件可能比仅作为归档文件(jar)交付的Java程序大得多。这些Java程序假设已经存在可用的JRE,即时是最小的Go可执行映像也可能有几兆字节。虽然可以通过在构建代码时不适用调试信息来减少代码量,但不建议做。
Go方法要求为每个目标操作系统构建可执行文件。在Java中,类文件可以跨操作系统移植(它们在运行时被即时编译为本机代码)。这形成了Java著名的"一次编写,到处运行"不是程序本身,Java为每个受支持的组合构建一个版本的JVM。
幸运的是,Go语言本身通常与操作系统和硬件架构无关,它的大多数库也是如此,很少有库依赖于架构。少数依赖于操作系统的标准库也适用于一组流行的操作系统。如Linux、iOS和Windows。通常依赖于操作系统的第三方库也是如此。因此大多数Go程序都可以跨多个操作系统移植,代价是需构建多次——每个操作系统一次
Go没有Java虚拟机(JVM)或Java运行时环境(JRE-JVM加上标准Java类库)的直接对应项。Go有运行时,它提供了支持Go语义所需的函数。这包括其集合类型和Go协程的库。它还包括一个垃圾收集器,用于管理堆内存分配。Go运行时(通常为几MB)比JRE(通常为几百MB)小得多。
Go的代码、任何库和运行时都被构建(链接)到操作系统(OS)运行的单个可执行文件中。这与Java程序汇编和链接的JIT方式形成了鲜明的对比。Go在构建时使用早期(静态)链接。Java则在运行时做后期(动态链接).
Go方法生成的可执行文件时独立的。(无须安装其他必备组件, 如JRE),这使得它部署起来比典型的Java更容易。这就是Go在容器化(如Docker、k8s)环境中如此流行的原因之一。
Go正变得更加独立。例如,Go1.16增加了将文字内容(例如HTML、CSS或JavaScript文件等文本目录)嵌入EXE主体的功能,在过去,这需要交付独立的文件。如果充分利用,完整的解决方案(如Web服务器)可以作为单个二进制文件提供。这种独也立具有额外的好处,那就是只有在运行时才有可能发现丢失的库或数据。这确实意味着可执行文件可能比仅作为归档文件(jar)交付的Java程序大得多。这些Java程序假设已经存在可用的JRE,即时是最小的Go可执行映像也可能有几兆字节。虽然可以通过在构建代码时不适用调试信息来减少代码量,但不建议做。
Go方法要求为每个目标操作系统构建可执行文件。在Java中,类文件可以跨操作系统移植(它们在运行时被即时编译为本机代码)。这形成了Java著名的"一次编写,到处运行"不是程序本身,Java为每个受支持的组合构建一个版本的JVM。
幸运的是,Go语言本身通常与操作系统和硬件架构无关,它的大多数库也是如此,很少有库依赖于架构。少数依赖于操作系统的标准库也适用于一组流行的操作系统。如Linux、iOS和Windows。通常依赖于操作系统的第三方库也是如此。因此大多数Go程序都可以跨多个操作系统移植,代价是需构建多次——每个操作系统一次
Go内存管理。
Go可以为多个位置的值分配空间:
代码映像——用于顶级值
调用栈——用于许多函数或块局部变量
堆——用于动态值,或可通过闭包访问的值,或动态大小/长度
在使用动态内存分配的计算机程序中,最大的错误源之一是内存管理不当。许多故障,如内存泄漏、内存块重用不当、内存过早释放等,往往会导致灾难性的程序故障。与Java一样,Go通过提供自动内存管理来避免这些问题。
与Java一样,Go提供了自动堆内存管理功能,可提供下列关键功能:
1.为对象分配空间(Go中任何数据类型的实例)
2.自动回收任何未引用(通常称为死的或不可访问的)对象的空间
对象在函数调用栈上动态分配,或者像在Java中那样在堆中动态分配。与Java一样、Go提供了垃圾收集(GC)堆内存分配/释放。
所有基于堆的对象都是GC的。当所属函数返回或拥有块退出时,将释放所有基于栈的对象。无论是堆还是栈,都没有程序员可访问的方式来释放。与Java一样,Go对堆对象的唯一控制是指向不需要的对象的指针设置为nil。
Java GC实现将对每个即将被回收的对象调用finialize()方法。对于许多类型,此函数不执行任何操作,但它可以执行清理活动.Go提供了类似的功能,但并非所有分配都通用。任何在GC时需要清理的已分配对象都必需用Go运行时显式注册,以便清理。为此,可以使用:
runtime.SetFinalizer(x *object, fx(x *object))
其中object是任何类型,x为这种类型的指针,并在Go协程中对其运行fx,x值将自动取消注册,并且可以在下一次GC中释放。
与Java一样,Go具有GC机制,当GC运行时,代码会暂停。GC可能发生在任何堆分配上,并且发生时间通常是不可预测的。这是使用垃圾收集的主要缺点。Java有几个GC实现的原因之一是试图根据程序的性质(批处理/命令行、交互、服务器等)调整这些暂停。注意,Go和Java一样,有一个API,即runtime.GC()。允许在能更好地容忍暂停时强制执行GC,这可以创造更多的可预测性。
Go实现可以使用,并且通常使用的最简单的GC方法称为mark-sweep。它有两个阶段:
1.mark(标记)——所有对象都标记为不可访问,然后从每个引用根访问的所有对象都标记为可访问
根是在任何活动调用堆中具有指针字段以及任何类似指针和结构体的任何顶级指针或对象(结构体).从每个根目录进行参考树遍历。
2.sweep(清除)——释放(或回收)仍标记为无法访问的所有对象
为了防止GC期间这些根的任何变化,可能需要暂停所有活动的Go协程。这通常被称为stop-the-world(STW)。因此实际上Go程序在这段时间内没有做任何工作。Go团队一直在努力减少STW暂停时间。在现代机器上,暂停时间大多数都在一毫秒内,因此通常是可以接受的。
Go可以为多个位置的值分配空间:
代码映像——用于顶级值
调用栈——用于许多函数或块局部变量
堆——用于动态值,或可通过闭包访问的值,或动态大小/长度
在使用动态内存分配的计算机程序中,最大的错误源之一是内存管理不当。许多故障,如内存泄漏、内存块重用不当、内存过早释放等,往往会导致灾难性的程序故障。与Java一样,Go通过提供自动内存管理来避免这些问题。
与Java一样,Go提供了自动堆内存管理功能,可提供下列关键功能:
1.为对象分配空间(Go中任何数据类型的实例)
2.自动回收任何未引用(通常称为死的或不可访问的)对象的空间
对象在函数调用栈上动态分配,或者像在Java中那样在堆中动态分配。与Java一样、Go提供了垃圾收集(GC)堆内存分配/释放。
所有基于堆的对象都是GC的。当所属函数返回或拥有块退出时,将释放所有基于栈的对象。无论是堆还是栈,都没有程序员可访问的方式来释放。与Java一样,Go对堆对象的唯一控制是指向不需要的对象的指针设置为nil。
Java GC实现将对每个即将被回收的对象调用finialize()方法。对于许多类型,此函数不执行任何操作,但它可以执行清理活动.Go提供了类似的功能,但并非所有分配都通用。任何在GC时需要清理的已分配对象都必需用Go运行时显式注册,以便清理。为此,可以使用:
runtime.SetFinalizer(x *object, fx(x *object))
其中object是任何类型,x为这种类型的指针,并在Go协程中对其运行fx,x值将自动取消注册,并且可以在下一次GC中释放。
与Java一样,Go具有GC机制,当GC运行时,代码会暂停。GC可能发生在任何堆分配上,并且发生时间通常是不可预测的。这是使用垃圾收集的主要缺点。Java有几个GC实现的原因之一是试图根据程序的性质(批处理/命令行、交互、服务器等)调整这些暂停。注意,Go和Java一样,有一个API,即runtime.GC()。允许在能更好地容忍暂停时强制执行GC,这可以创造更多的可预测性。
Go实现可以使用,并且通常使用的最简单的GC方法称为mark-sweep。它有两个阶段:
1.mark(标记)——所有对象都标记为不可访问,然后从每个引用根访问的所有对象都标记为可访问
根是在任何活动调用堆中具有指针字段以及任何类似指针和结构体的任何顶级指针或对象(结构体).从每个根目录进行参考树遍历。
2.sweep(清除)——释放(或回收)仍标记为无法访问的所有对象
为了防止GC期间这些根的任何变化,可能需要暂停所有活动的Go协程。这通常被称为stop-the-world(STW)。因此实际上Go程序在这段时间内没有做任何工作。Go团队一直在努力减少STW暂停时间。在现代机器上,暂停时间大多数都在一毫秒内,因此通常是可以接受的。
Go算法的评级依据:
# 最大stop-the-world时间——应该尽可能小。
# GC消耗的总运行时的百分比——应该尽可能地小
# 通常,很难同时优化这两个值。
应该注意的是,Go GC使用的机制与Java GC使用的几种机制(随时间和运行时上下文而变化)不同。因为Go支持指针(而不是像Java那样支持引用),所以它不能轻松地堆中移动对象。因此,Go不适用Java中常见的清理(又名压缩)收集器。Go的方法会导致更多的堆碎片,从而降低内存的使用效率。如前所述。Java允许在几个GC实现中进行选择。Go没有。因为JVM用例随着时间的推移而发展,所以Java GC选项随着时间的推移而发展(删除和添加收集器),表明JVM似乎无法提供“一刀切”选项.
# 最大stop-the-world时间——应该尽可能小。
# GC消耗的总运行时的百分比——应该尽可能地小
# 通常,很难同时优化这两个值。
应该注意的是,Go GC使用的机制与Java GC使用的几种机制(随时间和运行时上下文而变化)不同。因为Go支持指针(而不是像Java那样支持引用),所以它不能轻松地堆中移动对象。因此,Go不适用Java中常见的清理(又名压缩)收集器。Go的方法会导致更多的堆碎片,从而降低内存的使用效率。如前所述。Java允许在几个GC实现中进行选择。Go没有。因为JVM用例随着时间的推移而发展,所以Java GC选项随着时间的推移而发展(删除和添加收集器),表明JVM似乎无法提供“一刀切”选项.
Go堆上的对象通常有两个部分:
#头——至少包含mark-sweep指示器,通常还包含数据的大小,还可能存在其他值,如类型或调试/分析信息
#数据——实际数据。
由于存在对象头,因此大多数系统中所有堆对象都有一个最小大小,通常为8或16个字节,即使数据较小,例如单个布尔值。通常以这个最小大小的块分配内存。因此为了获得最佳的堆使用率,应避免在堆上单独放置许多小值(例如标量值),而不是将他们作为大数组的一部分。
在Java中,数据的占与堆的位置是显而易见的。new操作符创建的任何内容都在堆上。其他所有内容都在栈上。通常这意味着所有原始标量变量都在栈上,并且所有对象都在堆中。注意,由于需装箱,因此对于集合中的基元类型,Java的内存效率通常可能低于Go。
在Go中,数据声明可能并不总是显而易见的,数据可以位于栈上或堆中,这具体取决于它们的引用方式以及Go运行时的工作方式。栈堆于函数局部变量(即,尽在创建它们的函数生命内存在的,没有指向它们的外部指针,或未被闭包使用)是最佳选择。其他数据通常需要堆。大数据值也需要堆分配。
注意:Go从堆中分配Go协程调用栈。每个Go协程都有自己的调用栈。栈开始时很小,并根据需要增长。在Java中,线程调用的栈也来自堆,但它们开始时要大得多(通常为几兆字节)。这严重限制了可以存在的线程调用栈数(相对于Go协程调用栈数).
栈与堆的混合分配会影响Go程序的性能。Go提供了分析工具来帮助确定该比率并指导任何调整。
Go和Java管理内存使用的方式(尤其是堆)是完全不同的。由于细节通常取决于实现,并可能会发生变化,因此它们没有很好地文档记录。这些差异可能意味着具有相似数据结构的类似的Go和Java程序,可能会消耗明显不同的运行时内存量。这也意味着内存不足的情况可能会以不同的方式发生。目前,JVM具有比Go更多的选项来管理内存使用。Go较高的内存碎片也会影响这一点。
许多对象由Go new函数分配,该函数分配空间来保存值(通常,但并非总是,在堆上,就像在Java中一样),并将其初始化为二进制零(根据类型解释为"零"值)。new函数总是返回指向所分配值得指针,如果内存不足则会导致panic。
许多标量值(例如数字和布尔值)和仅标量的结构体在栈上分配。大多数集合(如切片和映射)都分配在堆上。通常,任何已获取地址的值也必须分配在堆上。这是因为在声明值得块返回很久之后,该地址可以保存和使用。
#头——至少包含mark-sweep指示器,通常还包含数据的大小,还可能存在其他值,如类型或调试/分析信息
#数据——实际数据。
由于存在对象头,因此大多数系统中所有堆对象都有一个最小大小,通常为8或16个字节,即使数据较小,例如单个布尔值。通常以这个最小大小的块分配内存。因此为了获得最佳的堆使用率,应避免在堆上单独放置许多小值(例如标量值),而不是将他们作为大数组的一部分。
在Java中,数据的占与堆的位置是显而易见的。new操作符创建的任何内容都在堆上。其他所有内容都在栈上。通常这意味着所有原始标量变量都在栈上,并且所有对象都在堆中。注意,由于需装箱,因此对于集合中的基元类型,Java的内存效率通常可能低于Go。
在Go中,数据声明可能并不总是显而易见的,数据可以位于栈上或堆中,这具体取决于它们的引用方式以及Go运行时的工作方式。栈堆于函数局部变量(即,尽在创建它们的函数生命内存在的,没有指向它们的外部指针,或未被闭包使用)是最佳选择。其他数据通常需要堆。大数据值也需要堆分配。
注意:Go从堆中分配Go协程调用栈。每个Go协程都有自己的调用栈。栈开始时很小,并根据需要增长。在Java中,线程调用的栈也来自堆,但它们开始时要大得多(通常为几兆字节)。这严重限制了可以存在的线程调用栈数(相对于Go协程调用栈数).
栈与堆的混合分配会影响Go程序的性能。Go提供了分析工具来帮助确定该比率并指导任何调整。
Go和Java管理内存使用的方式(尤其是堆)是完全不同的。由于细节通常取决于实现,并可能会发生变化,因此它们没有很好地文档记录。这些差异可能意味着具有相似数据结构的类似的Go和Java程序,可能会消耗明显不同的运行时内存量。这也意味着内存不足的情况可能会以不同的方式发生。目前,JVM具有比Go更多的选项来管理内存使用。Go较高的内存碎片也会影响这一点。
许多对象由Go new函数分配,该函数分配空间来保存值(通常,但并非总是,在堆上,就像在Java中一样),并将其初始化为二进制零(根据类型解释为"零"值)。new函数总是返回指向所分配值得指针,如果内存不足则会导致panic。
许多标量值(例如数字和布尔值)和仅标量的结构体在栈上分配。大多数集合(如切片和映射)都分配在堆上。通常,任何已获取地址的值也必须分配在堆上。这是因为在声明值得块返回很久之后,该地址可以保存和使用。
Go还是用make函数创建内置得结构体、映射和通道类型。内置的make函数与new函数的不同之处在于,它们基于其参数初始化(有点像Java中的调用构造函数)值,并且返回值本身,而不是指向它的指针。例如,考虑一个类似切片的结构体,它可以被定义为(概念上,不是真正的Go切片,不是合法的Go):
type Slice[t any] struct {
Values *[Cap]T // 实际数据,可以被共享
Len, Cap int // 当前和最大长度
}
这里(比如)make(new (Slice[int]), 10,100)将创建并返回此结构和支持数组,并设置所有字段。
type Slice[t any] struct {
Values *[Cap]T // 实际数据,可以被共享
Len, Cap int // 当前和最大长度
}
这里(比如)make(new (Slice[int]), 10,100)将创建并返回此结构和支持数组,并设置所有字段。
Go标识符。
Go和Java一样,使用标识符来标记编程结构。与Java一样,Go标识符具有一组语法规则。Go的规则与Java的规则类似,因此可以使用已有的Java经验(编译器将报告任何问题)。
在Go中,可以标记的命名结构是:
# 包(Package)——所有顶级类型、功能和值都包含在某些包中
# 类型(Type)——所有变量都有某种类型,所有函数都有某种类型的参数和返回值
# 变量(Variable)——变量是具有存储位置且可随时间变化的命名值
# 字段(Field)——是包含在结构(结构体)中的变量
# 函数(Function,声明或内置)——函数是独立的代码块,或是结构或接口(仅原型)成员。函数可以由其他函数调用
# 常量(Constant)——常量是值不能改变的命名量;编译器知道它们,但通常运行时不存在。
# 语句关键字(Statement keyword)——语句是指语句的声明或嵌套分组,或可表示用Go语言表达的动作。大多数语句(赋值除外)都是用关键字引入的。
注意,在Go中,与在Java中一样,每个变量都必须有一个声明的静态类型。这意味着改类型在编译时是已知的,并且不能随着代码的运行而更改。与Java一样,Go允许接口类型的变量的动态(运行时)类型更改为符合(实现)接口类型的任何类型。Go没有等价的可设置为子类实例的类类型变量。
Java有一个Go只部分支持的功能,即变量可以被分配实现变量类型的任何类型(或子类型)。这通常被称为继承多态性(一个关键的面向对象编程的特性)。这适用于类和接口类型。在Go中,这种多态性仅适用于接口类型。Go中没有结构体类型继承的概念,因此如果Go变量具有接口类型,则只能为其分配实现该接口的所有方法的任何类型的实例。通常,这比Java的多态性更灵活,但不那么严格。
Go和Java一样,使用标识符来标记编程结构。与Java一样,Go标识符具有一组语法规则。Go的规则与Java的规则类似,因此可以使用已有的Java经验(编译器将报告任何问题)。
在Go中,可以标记的命名结构是:
# 包(Package)——所有顶级类型、功能和值都包含在某些包中
# 类型(Type)——所有变量都有某种类型,所有函数都有某种类型的参数和返回值
# 变量(Variable)——变量是具有存储位置且可随时间变化的命名值
# 字段(Field)——是包含在结构(结构体)中的变量
# 函数(Function,声明或内置)——函数是独立的代码块,或是结构或接口(仅原型)成员。函数可以由其他函数调用
# 常量(Constant)——常量是值不能改变的命名量;编译器知道它们,但通常运行时不存在。
# 语句关键字(Statement keyword)——语句是指语句的声明或嵌套分组,或可表示用Go语言表达的动作。大多数语句(赋值除外)都是用关键字引入的。
注意,在Go中,与在Java中一样,每个变量都必须有一个声明的静态类型。这意味着改类型在编译时是已知的,并且不能随着代码的运行而更改。与Java一样,Go允许接口类型的变量的动态(运行时)类型更改为符合(实现)接口类型的任何类型。Go没有等价的可设置为子类实例的类类型变量。
Java有一个Go只部分支持的功能,即变量可以被分配实现变量类型的任何类型(或子类型)。这通常被称为继承多态性(一个关键的面向对象编程的特性)。这适用于类和接口类型。在Go中,这种多态性仅适用于接口类型。Go中没有结构体类型继承的概念,因此如果Go变量具有接口类型,则只能为其分配实现该接口的所有方法的任何类型的实例。通常,这比Java的多态性更灵活,但不那么严格。
Go协程(并发执行单元)。
Java最重要的特性之一是支持相对简单(相对于C和C++)的多线程,它通过标准库中提供的线程类型(例如线程)和语言特性(例如线程)和语言特性(例如同步方法/块)内置于语言。Go使用Go协程提供了类似特性,Go协程是一种轻量级的类似线程的方法,用于运行于通道相结合的代码。
Java最重要的特性之一是支持相对简单(相对于C和C++)的多线程,它通过标准库中提供的线程类型(例如线程)和语言特性(例如线程)和语言特性(例如同步方法/块)内置于语言。Go使用Go协程提供了类似特性,Go协程是一种轻量级的类似线程的方法,用于运行于通道相结合的代码。
并发问题。
在讨论在Go中执行并发编程的机制之前,让我们先介绍一下并发编程可能导致的问题。Java和Go都是用共享内存模式(所有线程都可以访问相同的内存位置).因此临界区(Critical Section,CS)很常见,当访问变量时,并行访问可能会影响代码区域。Java语言具有synchronized块以帮助控制对CS的访问。Java允许任何对象成为此类CS上的门(又名条件)。
与Java一样,Go也具有内存访问顺序特性。Java用有点复杂的happens-before,HB关系来解释这一点。Go内存访问也有一个HB关系。必须小心,特别是当涉及多个Go协程时,以确保代码尊重所有HB关系。可以使用Go通道,原子访问和锁定函数实现这一目标。
在讨论在Go中执行并发编程的机制之前,让我们先介绍一下并发编程可能导致的问题。Java和Go都是用共享内存模式(所有线程都可以访问相同的内存位置).因此临界区(Critical Section,CS)很常见,当访问变量时,并行访问可能会影响代码区域。Java语言具有synchronized块以帮助控制对CS的访问。Java允许任何对象成为此类CS上的门(又名条件)。
与Java一样,Go也具有内存访问顺序特性。Java用有点复杂的happens-before,HB关系来解释这一点。Go内存访问也有一个HB关系。必须小心,特别是当涉及多个Go协程时,以确保代码尊重所有HB关系。可以使用Go通道,原子访问和锁定函数实现这一目标。
Go并发。
GGo通过Go协程特性支持并发编程,该特性使异步或并行处理相对容易(对于Java)。Java中最相似的概念是线程。Go协程可以引入相同的临界区问题。注意,并行性和并发不是一回事。并发意味着能够并行运行,这并不意味着代码始终并行运行。并发通常意味着代码的行为与是否并行无关。通常,这是代码涉及的一个功能。在多核处理器系统上,代码可以真正并行运行。但前提是其设计支持并发。有时,可以通过在单个处理器上多路复用不同的代码执行线程(通常称为多任务或分时处理)来模拟并行行为。
注意,大多数现代计算机至少包含两个内核,因此并行处理时可能的。服务器级计算机通常包含数十个(甚至数百个)内核。
Go协程只是一个普通的Go函数,使用go语句创建并启动一个Go协程。go语句立即返回。Go协程函数与调用方异步运行,并可能与调用方并行运行。Go协程与通道相结合,提供了通信顺序进程(CSP)的实现。CSP的基本概念是独立的执行路径(在Java中称为线程,在Go中称为Go协程),可以在受控的方式(通常是先进先出,如通过通道)下相互传递数据来交互,这通常比管理CS更容易、更安全,是Java同步方法的替代方法。
使用CSP,每个线程不会同时共享数据(注意,Go中没有任何内容可以阻止这种情况,但通常不需要),而是使用一种消息传递形式.数据由源Go协程发送(传输),并由目标/处理器Go协程"接收".这可以防止出现临界区的可能性.通过缓冲此类消息,发送方和接收方可以异步工作。
CSP就像Actor系统。Actor系统也在actor之间发送消息。actor通常是具有特殊方法的对象,该方法在分配的线程上运行并接收任何消息。actor本身通常不是线程,而是共享由某些actor运行时管理的线程。这使actor系统具有比Go协程更好的实例规模。actor运行时负责向actor路由/传递消息。在Go中,通道扮演者这个角色。
Java社区提供了几个很好的actor库/框架。例如Akka。Go默认提供此功能,在Java中,它是一个附加组件。CSP和Actor都通过按顺序(不间断)地处理消息来简化编程。处理器在准备好接收之前不会收到新消息。它们还允许一次只允许一个线程访问任何数据。与Java线程相比,Go协程时轻量级的(使用更少的资源)。Go协程就像Java中的Green Threads(绿色线程,由运行时而不是操作系统创建的类似线程的函数,通常比本机操作系统线程更轻,并且提供更快的上下文切换).
通常,每个操作系统线程可以有很多绿色线程。Go协程也是如此。实际上可以使用的最大Java线程数通常在几千个左右,Java线程通常需要几兆字节的内存来支持其状态。相比之下,Go协程通常只需要几千字节的内存。这是一个三个数量级的差异。而通常可以使用成千上万(在大型系统中,甚至数百万)个Go协程。如何实现Go协程的细节可能会随着Go版本的变化而变化。这里不做深入介绍。值得注意的一个方面是,每个Go协程都有自己的调用堆栈(它占了Go协程消耗的大部分资源).
GGo通过Go协程特性支持并发编程,该特性使异步或并行处理相对容易(对于Java)。Java中最相似的概念是线程。Go协程可以引入相同的临界区问题。注意,并行性和并发不是一回事。并发意味着能够并行运行,这并不意味着代码始终并行运行。并发通常意味着代码的行为与是否并行无关。通常,这是代码涉及的一个功能。在多核处理器系统上,代码可以真正并行运行。但前提是其设计支持并发。有时,可以通过在单个处理器上多路复用不同的代码执行线程(通常称为多任务或分时处理)来模拟并行行为。
注意,大多数现代计算机至少包含两个内核,因此并行处理时可能的。服务器级计算机通常包含数十个(甚至数百个)内核。
Go协程只是一个普通的Go函数,使用go语句创建并启动一个Go协程。go语句立即返回。Go协程函数与调用方异步运行,并可能与调用方并行运行。Go协程与通道相结合,提供了通信顺序进程(CSP)的实现。CSP的基本概念是独立的执行路径(在Java中称为线程,在Go中称为Go协程),可以在受控的方式(通常是先进先出,如通过通道)下相互传递数据来交互,这通常比管理CS更容易、更安全,是Java同步方法的替代方法。
使用CSP,每个线程不会同时共享数据(注意,Go中没有任何内容可以阻止这种情况,但通常不需要),而是使用一种消息传递形式.数据由源Go协程发送(传输),并由目标/处理器Go协程"接收".这可以防止出现临界区的可能性.通过缓冲此类消息,发送方和接收方可以异步工作。
CSP就像Actor系统。Actor系统也在actor之间发送消息。actor通常是具有特殊方法的对象,该方法在分配的线程上运行并接收任何消息。actor本身通常不是线程,而是共享由某些actor运行时管理的线程。这使actor系统具有比Go协程更好的实例规模。actor运行时负责向actor路由/传递消息。在Go中,通道扮演者这个角色。
Java社区提供了几个很好的actor库/框架。例如Akka。Go默认提供此功能,在Java中,它是一个附加组件。CSP和Actor都通过按顺序(不间断)地处理消息来简化编程。处理器在准备好接收之前不会收到新消息。它们还允许一次只允许一个线程访问任何数据。与Java线程相比,Go协程时轻量级的(使用更少的资源)。Go协程就像Java中的Green Threads(绿色线程,由运行时而不是操作系统创建的类似线程的函数,通常比本机操作系统线程更轻,并且提供更快的上下文切换).
通常,每个操作系统线程可以有很多绿色线程。Go协程也是如此。实际上可以使用的最大Java线程数通常在几千个左右,Java线程通常需要几兆字节的内存来支持其状态。相比之下,Go协程通常只需要几千字节的内存。这是一个三个数量级的差异。而通常可以使用成千上万(在大型系统中,甚至数百万)个Go协程。如何实现Go协程的细节可能会随着Go版本的变化而变化。这里不做深入介绍。值得注意的一个方面是,每个Go协程都有自己的调用堆栈(它占了Go协程消耗的大部分资源).
与Java线程不同,Java线程的堆栈通常只会增长且通常为几兆字节,而Go协程堆栈可以根据需要随时间增长和收缩。因此,一个Go协程消耗的堆栈正是它所需要的,仅此而已。这是Go协程是轻量级的原因之一。尤其是相对Java线程而言。另外一方面是,在Go操作系统中,线程是按需创建和终止的。并保留在池中以供重用,因此通常只有所需的线程来支持活动的Go协程。
考虑一下,如果所有的Go协程都是CPU绑定的(即它们不执行太多的IO操作),则只需要有与处理器内核一样多的线程(其他线程,如果不采用多任务处理,则必然是空闲的)。由于代码完全与CPU绑定(至少在很长一段时间内)很少见,因此需要额外的线程来支持并发CPU和IO密集型Go协程。
在Go中,Go协程调度程序通常维护一个线程池。如图所示。它将Go协程分配给池中的非活动线程。这种关联不是静态的,而是会随着时间而变化。它根据需要添加新的IO线程,但通常会比计算机中处理器(内核)的时机数量来限制CPU线程。一般来说,Go协程与线程的比例可能很大。
考虑一下,如果所有的Go协程都是CPU绑定的(即它们不执行太多的IO操作),则只需要有与处理器内核一样多的线程(其他线程,如果不采用多任务处理,则必然是空闲的)。由于代码完全与CPU绑定(至少在很长一段时间内)很少见,因此需要额外的线程来支持并发CPU和IO密集型Go协程。
在Go中,Go协程调度程序通常维护一个线程池。如图所示。它将Go协程分配给池中的非活动线程。这种关联不是静态的,而是会随着时间而变化。它根据需要添加新的IO线程,但通常会比计算机中处理器(内核)的时机数量来限制CPU线程。一般来说,Go协程与线程的比例可能很大。
如果Go协程做了一些事情来组织其继续执行(或自愿放弃其线程),则其线程将被分离并提供给另一个Go协程。如果Go协程阻塞操作系统调用(如执行文件或socket IO操作)的问题,则Go调度程序也可以分离线程。因此,调度程序可能有两个线程池:一个用于CPU绑定的Go协程,另一个用于IO绑定的Go协程。Go提供了有限的方法来控制用于执行Go协程的操作系统线程。可以使用GOMAXPROCS环境值和等效的runtime.GOMAXPROCS(nint)函数设置运行Go协程的最大可用的CPU(或内核)。Go通过使用Contexts提供了以编程方式取消或超时长时间运行的异步进程(如Go协程中的循环以及网络或数据库请求)的能力。Context还提供了一个通道,用于通知侦听器此类长时间运行的进程已完成(正常或通过超时).runtime.Goexit()函数在运行所有延迟函数后终止调用Go协程.注意,main函数在Go协程上运行。当main函数的Go协程退出(返回)时EXE结束。这就像所有非守护进程线程结束时的JVM结束。runtime.Gosched()函数使当前Go协程自愿放弃其线程,但保持可运行状态。这就像在Java中使用了Thread.yield()一样。在长时间运行的代码段(如循环)中放弃是好的选择。由于Go协程比Java线程更轻量级,因此相比Java支持,Go对池和重用的支持较少;通常根据需要创建新的Go协程。Go协程不像线程那样提供身份,也没有类似的方法来管理它们。通道通常取代了Java中使用的线程局部变量。
Go协程示例。
与Java一样,没有语言手段来测试Go协程的完成,但是确实存在标准的库函数可完成此目的。Java使用Thread.join()方法来执行此操作。在Go中,执行此操作的常用方法是通过WaitGroups(WG).WG实际上是一个递增/递减计数器,客户端可以等待它递减到零。通常做法如图所示。在每个Go协程启动之前,WG将递增。此增量必须位于Go协程主体之外才能正常工作。当Go协程结束时,WG会递减(通过Done)。然后,启动中的Go协程等待(暂停)所有(可以有任意数量的Go协程)已启动的Go协程结束。
与Java一样,没有语言手段来测试Go协程的完成,但是确实存在标准的库函数可完成此目的。Java使用Thread.join()方法来执行此操作。在Go中,执行此操作的常用方法是通过WaitGroups(WG).WG实际上是一个递增/递减计数器,客户端可以等待它递减到零。通常做法如图所示。在每个Go协程启动之前,WG将递增。此增量必须位于Go协程主体之外才能正常工作。当Go协程结束时,WG会递减(通过Done)。然后,启动中的Go协程等待(暂停)所有(可以有任意数量的Go协程)已启动的Go协程结束。
Go通道可以做类似的事情:
下面以一个完全可运行示例展示前面方法的略有不同的表达。控制台输出如下:
start....
go 4 running
go 0 running
go 3 running
go 3 done
go 2 running
go 2 done
go 0 done
go 1 running
go 1 done
end....
start....
go 4 running
go 0 running
go 3 running
go 3 done
go 2 running
go 2 done
go 0 done
go 1 running
go 1 done
end....
如果将完成的通道数量减少到1,则会得到如下输出
start....
go 4 running
go 4 done
go 0 running
go 0 done
go 1 running
go 1 done
go 2 running
go 2 done
go 3 running
go 3 done
end....
start....
go 4 running
go 4 done
go 0 running
go 0 done
go 1 running
go 1 done
go 2 running
go 2 done
go 3 running
go 3 done
end....
输出什么?可能输出的行相同,但排列顺序不同(不太可能与前面顺序打印结果相同)。但有可能只有一些one或two的行出来。这是因为Go调度程序只能运行准备就绪的Go协程(而Sleep使它们没有准备就绪),运行顺序可以任意,并且可以随时在它们之间切换。此外,main函数(也在一个Go协程中运行)可以结束,这就导致程序在其他Go协程完成之前结束。因此,Go协程的行为通常对应于Java中的守护线程。
注意,Go协程启动的顺序不可预测。
务必注意,Go协程无法将结果返回给其调用方,因此必须以其他方式报告在Go协程中发生的结果(或错误).在此示例中,它们被记录(并且程序被终止),但通常有一个通道将此类错误(或结果)报告给通道侦听器。回到关键部分。与Java不同,在Go中没有同步的语句或块。通常使用锁接口(Locker interface)。它本质上是:
type Locker interface {
Lock() //更好的名称:WaitUntilAvailableAndLock()
Unlock() // 更好的名称: UnlockAndThusMakeAvailable()
}
sync.Mutex类型实现了此接口,它可用于对临界区的访问。它的用法基本如下:
var mx sync.Mutex
func SomeAction() {
mx.Lock();
defer mx.unLock()
// 做一些关键的事情
}
注意:锁不允许同一个Go协程重新进入,就像synchronized Java线程那样。因此,需谨慎使用以防自锁。
通道可以执行类似操作:
var ch = make(chan bool, 1) // 一次只允许接收一条消息
func SomeAction() {
cj <- true
defer func() {
<- ch // 放弃该值
}()
// 做一些关键的事情
}
如果没有空间接收该值,则在顶部块发送。由于通道只有一个值得空间,因此只能允许一个用户。底部得接收将删除该值。通过增加通道大小,可以允许有限数量得Go协程同时进入操作。Go还有其他办法,可以避免在临界区周围锁定。通过通道,通常可以消除锁定。数据是通过通道在消费者之间传输得,因此根本不存在临界区。通道通常是首选。
务必注意,Go协程无法将结果返回给其调用方,因此必须以其他方式报告在Go协程中发生的结果(或错误).在此示例中,它们被记录(并且程序被终止),但通常有一个通道将此类错误(或结果)报告给通道侦听器。回到关键部分。与Java不同,在Go中没有同步的语句或块。通常使用锁接口(Locker interface)。它本质上是:
type Locker interface {
Lock() //更好的名称:WaitUntilAvailableAndLock()
Unlock() // 更好的名称: UnlockAndThusMakeAvailable()
}
sync.Mutex类型实现了此接口,它可用于对临界区的访问。它的用法基本如下:
var mx sync.Mutex
func SomeAction() {
mx.Lock();
defer mx.unLock()
// 做一些关键的事情
}
注意:锁不允许同一个Go协程重新进入,就像synchronized Java线程那样。因此,需谨慎使用以防自锁。
通道可以执行类似操作:
var ch = make(chan bool, 1) // 一次只允许接收一条消息
func SomeAction() {
cj <- true
defer func() {
<- ch // 放弃该值
}()
// 做一些关键的事情
}
如果没有空间接收该值,则在顶部块发送。由于通道只有一个值得空间,因此只能允许一个用户。底部得接收将删除该值。通过增加通道大小,可以允许有限数量得Go协程同时进入操作。Go还有其他办法,可以避免在临界区周围锁定。通过通道,通常可以消除锁定。数据是通过通道在消费者之间传输得,因此根本不存在临界区。通道通常是首选。
错误与panic
概述。
代码,尤其是函数中的代码,可能有以下集中退出情况:
1.成功——功能如期实现
2.失败——由于某些可预测的情况,功能未如期实现
3.严重故障(亦称panic)——由于某些意外的或者罕见的情况或错误代码,功能未实现。在像Java这样的语言中,每个函数只有一个返回值,上述情况1和2经常混在一起,返回值本身决定了结果。例如,String.indexOf函数要么返回目标索引,要么返回小于0的值来表示目标未找到。对于返回对象的函数,通常用返回null来表示故障(如果null是一个合法值,这样是有问题的)。这通常是很多空指针异常的原因。
代码,尤其是函数中的代码,可能有以下集中退出情况:
1.成功——功能如期实现
2.失败——由于某些可预测的情况,功能未如期实现
3.严重故障(亦称panic)——由于某些意外的或者罕见的情况或错误代码,功能未实现。在像Java这样的语言中,每个函数只有一个返回值,上述情况1和2经常混在一起,返回值本身决定了结果。例如,String.indexOf函数要么返回目标索引,要么返回小于0的值来表示目标未找到。对于返回对象的函数,通常用返回null来表示故障(如果null是一个合法值,这样是有问题的)。这通常是很多空指针异常的原因。
Go错误。
Go函数可返回零个或者多个值。很多Go函数会返回(至少)一个错误值。这是一个常见的例子。
func DoSomething() (err error) {...}
这意味着DoSomething函数可返回一个error(一个内置的Go接口类型),在本例中(便捷地、习惯性地)命名为err。err值可以是nil或者error实例。一个更复杂的例子如下:
func DoSomething() (err error) {
err = DoSomePart()
if err != nil {
return
}
return
}
Go有一种常用且不冗长的方式来编码这种模式,它结合了复制和if测试:
if err = DoSomePart(); err != nil {
return
}
每个可能失败的函数都遵循该模式。尽管相比运行异常以报告故障的经典Java代码繁琐了些,但这遵循了Go的更透明、更显而易见的风格。注意,返回值没有明确值。因为返回值命名为err,err被赋值。另外的方式(不推荐)是:
func DoSomething() error {
xxx := DoSomePart() // 非常规名称
if xxx != nil {
return xxx // 显式返回
}
return xxx
}
在大多数情况下,Go更推荐使用第一种方式,从函数中返回错误值。在Java中该模式被看作不好的做法,因为它强制调用方测试返回的错误。在Go中,该模式被看作最佳做法,让程序员必须记着测试返回的错误。这是Go与Java在编程风格方面的主要区别,很多Javaer刚接触Go时很难适应它。对于一些简单的函数,一个成功/失败标志就足够了,返回的错误值由布尔值替代。这通常是Go的内置操作情况。例如映射查找和类型断言。
Go函数可返回零个或者多个值。很多Go函数会返回(至少)一个错误值。这是一个常见的例子。
func DoSomething() (err error) {...}
这意味着DoSomething函数可返回一个error(一个内置的Go接口类型),在本例中(便捷地、习惯性地)命名为err。err值可以是nil或者error实例。一个更复杂的例子如下:
func DoSomething() (err error) {
err = DoSomePart()
if err != nil {
return
}
return
}
Go有一种常用且不冗长的方式来编码这种模式,它结合了复制和if测试:
if err = DoSomePart(); err != nil {
return
}
每个可能失败的函数都遵循该模式。尽管相比运行异常以报告故障的经典Java代码繁琐了些,但这遵循了Go的更透明、更显而易见的风格。注意,返回值没有明确值。因为返回值命名为err,err被赋值。另外的方式(不推荐)是:
func DoSomething() error {
xxx := DoSomePart() // 非常规名称
if xxx != nil {
return xxx // 显式返回
}
return xxx
}
在大多数情况下,Go更推荐使用第一种方式,从函数中返回错误值。在Java中该模式被看作不好的做法,因为它强制调用方测试返回的错误。在Go中,该模式被看作最佳做法,让程序员必须记着测试返回的错误。这是Go与Java在编程风格方面的主要区别,很多Javaer刚接触Go时很难适应它。对于一些简单的函数,一个成功/失败标志就足够了,返回的错误值由布尔值替代。这通常是Go的内置操作情况。例如映射查找和类型断言。
Go panic
在Java中,通过抛出异常来表明遇到了较严重的故障。对于何时应该抛出异常和何时应该返回错误(例如,当读取超过文件末尾时),通常会产生混淆,Java(和许多社区)库代码对此的选择也不一致。
Go通过使用多值函数使这种行为更加一致,这些函数总是返回一个错误值作为最终(或唯一返回值)。然后将错误值与nil进行比较来确定是否发生了错误。通常,没有错误的情况下,其他返回值才是有意义的数值。只有当函数发生灾难性的故障(如内存不足、被零除、索引越界、无效参数等)时,才会引发panic。
Java支持Exception的概念(从技术上讲,Throwable是Exception的超类)。异常是发生意外/不常见的情况被抛出的对象。例如,当0被作为除数时,将由JVM抛出DivideByZeroException。另外一个更严重的例子是,当JVM无法满足new操作时,将引发OutOfMemoryErorr.Java在try语句的catch块中处理Throwable。在Java代码中,抛出和捕获Throwable实例很常见。
Go的panic由类似含义,但很少使用。panic非常像Throwable,由代码(自写的或库中的)通过Go内置的panic(<value>)函数引发。该值可以是任何类型(但通常是string或(首选)error实例),不应使用nil值。
Go代码很少引发panic。在大多数情况下,代码应该返回error。只有在意外情况下,error很难报告此类情况时,才使用panic,例如对应Java的OutOfMemoryError情况。Go不像Java那样具有Exception类型。取而代之的是,它有panic参数(更像Java的Error与RuntimeException的混合)。Go没有Java中对RuntimeException与non-RuntimeException Throwable的区分。所有的异常都被映射到单独的panic值。永远不要声明函数可能抛出的panic参数。
Java有try/finally与try/catch/finally语句集。Go没有,它使用延迟函数来达到finally的效果。Go使用了一个不同但类似的机制来捕获panic。与Java很像,如果没有被捕获,panic通常导致程序在输出跟踪(错误和堆栈)信息后退出。要在Go中捕获panic,可使用内置的recover()函数,该函数返回最新的panic(在特定的Go协程中)发送的值,为此,延迟函数中必须调用recover()函数。就像Java catch子句可以检查抛出的异常一样,延迟函数可以检查值,进行一些更正,然后再次返回或引发panic。与Java中一样,延迟函数可以在当前调用栈的任何位置。如图所示。
在Java中,通过抛出异常来表明遇到了较严重的故障。对于何时应该抛出异常和何时应该返回错误(例如,当读取超过文件末尾时),通常会产生混淆,Java(和许多社区)库代码对此的选择也不一致。
Go通过使用多值函数使这种行为更加一致,这些函数总是返回一个错误值作为最终(或唯一返回值)。然后将错误值与nil进行比较来确定是否发生了错误。通常,没有错误的情况下,其他返回值才是有意义的数值。只有当函数发生灾难性的故障(如内存不足、被零除、索引越界、无效参数等)时,才会引发panic。
Java支持Exception的概念(从技术上讲,Throwable是Exception的超类)。异常是发生意外/不常见的情况被抛出的对象。例如,当0被作为除数时,将由JVM抛出DivideByZeroException。另外一个更严重的例子是,当JVM无法满足new操作时,将引发OutOfMemoryErorr.Java在try语句的catch块中处理Throwable。在Java代码中,抛出和捕获Throwable实例很常见。
Go的panic由类似含义,但很少使用。panic非常像Throwable,由代码(自写的或库中的)通过Go内置的panic(<value>)函数引发。该值可以是任何类型(但通常是string或(首选)error实例),不应使用nil值。
Go代码很少引发panic。在大多数情况下,代码应该返回error。只有在意外情况下,error很难报告此类情况时,才使用panic,例如对应Java的OutOfMemoryError情况。Go不像Java那样具有Exception类型。取而代之的是,它有panic参数(更像Java的Error与RuntimeException的混合)。Go没有Java中对RuntimeException与non-RuntimeException Throwable的区分。所有的异常都被映射到单独的panic值。永远不要声明函数可能抛出的panic参数。
Java有try/finally与try/catch/finally语句集。Go没有,它使用延迟函数来达到finally的效果。Go使用了一个不同但类似的机制来捕获panic。与Java很像,如果没有被捕获,panic通常导致程序在输出跟踪(错误和堆栈)信息后退出。要在Go中捕获panic,可使用内置的recover()函数,该函数返回最新的panic(在特定的Go协程中)发送的值,为此,延迟函数中必须调用recover()函数。就像Java catch子句可以检查抛出的异常一样,延迟函数可以检查值,进行一些更正,然后再次返回或引发panic。与Java中一样,延迟函数可以在当前调用栈的任何位置。如图所示。
通常,Go库和Go运行时会引发panic。自己编写的代码也应如此。使用panic的一个常见情景是:如果函数的输入参数值非法,则此时应使用panic报告而不是使用错误返回,该情况被视为编程错误,而不是代码应从中回复的情况。注意,不是所有的Gopher都遵从此约定,可能会存在没有验证参数并生成panic的情况。这通常会导致后续发生一些其他问题,因为代码依赖于有效输入。注意,通常应该避免在panic恢复的延迟函数中生成新的panic。这就如同在Java中,应避免在catch或finally子句中抛出异常一样。捕获Go协程中的panic是至关重要的。Go协程中未处理的panic可能会摧毁Go程序,所以,最好不让它们出现,这需要系统原则,为此,建议所有的Go协程都由辅助函数创建,如图所示。
注意,如果客户端不需要错误报告,则可以省略错误通道。运行结果如下:
got "panic:panic happened!"
注意,如果客户端不需要错误报告,则可以省略错误通道。运行结果如下:
got "panic:panic happened!"
错误与panic演示。
内置的error类型很简单。很多第三方包对其进行了扩展。例如JuJu Errors。
控制台会输出如下:
MultiError error:MultiError one;two;three
MultiError value:MultiError one;two;three
内置的error类型很简单。很多第三方包对其进行了扩展。例如JuJu Errors。
控制台会输出如下:
MultiError error:MultiError one;two;three
MultiError value:MultiError one;two;three
或者当一个错误是由另一个错误导致的时(类似Java中的所有Throwable都可以有原因)。
如果被下列代码调用:
fmt.Printf("ErrorWithCause error:%s\n", ewc.Error())
fmt.Printf("ErrorWithCause value:%v\n\n", ewc)
会输出:
ErrorWithCause error: ErrorWithCause{error cause}
ErrorWithCause value: ErrorWithCause{error cause}
注意,下面的方法会使任何数据类型都能充当error
func (x <sometype>) Error() string
这是因为error类型实际定义为:
type error interface {
Error() string
}
如果被下列代码调用:
fmt.Printf("ErrorWithCause error:%s\n", ewc.Error())
fmt.Printf("ErrorWithCause value:%v\n\n", ewc)
会输出:
ErrorWithCause error: ErrorWithCause{error cause}
ErrorWithCause value: ErrorWithCause{error cause}
注意,下面的方法会使任何数据类型都能充当error
func (x <sometype>) Error() string
这是因为error类型实际定义为:
type error interface {
Error() string
}
Go的errors包有几个有用的功能函数:
# errors.Is(<error>, <type>): 拆开错误封装直到与提供的类型匹配,如果找到则返回true
# errors.As(<error>, <*type>):拆开错误封装直到与提供的变量类型匹配,将错误强制转换为该类型,然后赋值给变量,如果找到则返回true
# errors.Unrap(<error>):返回任何被封装的错误,(如Java异常的任何原因),实际错误类型必须具有Unwrap(<error>)方法。
可以在Go中模拟Java的异常行为。例如,为了引入类似try/catch/finally的行为,可以实现类似下面的小型库。这里,Go函数代替了Java的try/catch、try/finally和try、catch/finally语句。每个函数子句都作为(通常)函数字面量提供。没有像Java中那样对每个异常类型进行捕获,因为Go对所有问题只有一个panic。所有函数返回try子句的错误。既然try和catch子句可能有错误,因此有时会返回错误对类型TryCatchError。注意,直接在延迟函数中发布recover()函数很重要,而不是在triageRecover(...)
# errors.Is(<error>, <type>): 拆开错误封装直到与提供的类型匹配,如果找到则返回true
# errors.As(<error>, <*type>):拆开错误封装直到与提供的变量类型匹配,将错误强制转换为该类型,然后赋值给变量,如果找到则返回true
# errors.Unrap(<error>):返回任何被封装的错误,(如Java异常的任何原因),实际错误类型必须具有Unwrap(<error>)方法。
可以在Go中模拟Java的异常行为。例如,为了引入类似try/catch/finally的行为,可以实现类似下面的小型库。这里,Go函数代替了Java的try/catch、try/finally和try、catch/finally语句。每个函数子句都作为(通常)函数字面量提供。没有像Java中那样对每个异常类型进行捕获,因为Go对所有问题只有一个panic。所有函数返回try子句的错误。既然try和catch子句可能有错误,因此有时会返回错误对类型TryCatchError。注意,直接在延迟函数中发布recover()函数很重要,而不是在triageRecover(...)
输出如下:
in try
in catch forced panic: <nil> <nil>
in finally
TCF returned: TryCatchError[forced panic <nil>]
in try
in finally
TCF returned :TryCatchError[try err <nil>]
in try
in catch forced panic: <nil> <nil>
TCF returned: TryCatchError[forced panic <nil>]
in try
TCF returned: <nil>
in try
in catch forced panic: <nil> <nil>
in finally
TCF returned: TryCatchError[forced panic <nil>]
in try
in finally
TCF returned :TryCatchError[try err <nil>]
in try
in catch forced panic: <nil> <nil>
TCF returned: TryCatchError[forced panic <nil>]
in try
TCF returned: <nil>
接口应用
接口是核心。
同在Java中一样。在Go中用接口做参数和返回类型很重要。它支持很多选项,例如,用模拟对象替换普通对象,这对于测试至关重要。尤其是在将结构体类型传入或传出函数时,请查看是否可以用接口类型替换结构体。如果函数只使用结构体的方法,而不是用其字段,则通常可以这样做。
type Xxx struct {
...
}
func (x *Xxx) DoSomethingGood() {
...
}
func (x *Xxx) DoSomethingBad() (err error) {
...
}
可以创建如下接口:
type DoGooder interface {
DoSomethingGood()
}
type DoBader interface {
DoSomethingBad() error
}
然后在一些客户端使用Xxx,比如说以下面方式:
func DoWork(xxx *Xxx) {
xxx.DoSomethingGood()
}
可以将其转换为:
func DoWork(dg DoGooder) {
dg.DoSomethiingGood()
}
改写后,DoWork的调用方可以发送Xxx的实例或任何其他具有DoSomethingGood()方法的类型。有时,需要调用结构体类型的多个方法,有两个主要方案:
1.为函数提供多个参数,每个参数对应一个所需要的接口类型,并且调用方为所有参数传入相同的对象
2.创建组合接口并传入该类型
方案二通常优于方案一。
方案一,可以这样定义函数:
func DoWork(dg DoGooder, db DoBader) {
dg.DoSomethingGood()
db.DoSomethingBad()
}
然后以下列方式调用:
var xxx *Xxx
DoWork(xxx, xxx)
同在Java中一样。在Go中用接口做参数和返回类型很重要。它支持很多选项,例如,用模拟对象替换普通对象,这对于测试至关重要。尤其是在将结构体类型传入或传出函数时,请查看是否可以用接口类型替换结构体。如果函数只使用结构体的方法,而不是用其字段,则通常可以这样做。
type Xxx struct {
...
}
func (x *Xxx) DoSomethingGood() {
...
}
func (x *Xxx) DoSomethingBad() (err error) {
...
}
可以创建如下接口:
type DoGooder interface {
DoSomethingGood()
}
type DoBader interface {
DoSomethingBad() error
}
然后在一些客户端使用Xxx,比如说以下面方式:
func DoWork(xxx *Xxx) {
xxx.DoSomethingGood()
}
可以将其转换为:
func DoWork(dg DoGooder) {
dg.DoSomethiingGood()
}
改写后,DoWork的调用方可以发送Xxx的实例或任何其他具有DoSomethingGood()方法的类型。有时,需要调用结构体类型的多个方法,有两个主要方案:
1.为函数提供多个参数,每个参数对应一个所需要的接口类型,并且调用方为所有参数传入相同的对象
2.创建组合接口并传入该类型
方案二通常优于方案一。
方案一,可以这样定义函数:
func DoWork(dg DoGooder, db DoBader) {
dg.DoSomethingGood()
db.DoSomethingBad()
}
然后以下列方式调用:
var xxx *Xxx
DoWork(xxx, xxx)
方案二,可以这样定义组合接口:
type DoGoodAndBad interface {
DoGooder
DoBader
}
在函数中以下列方式使用组合接口:
func DoWork(dgb DoGoodAndBad) {
dgb,DoSomethingGood()
dgb.DoSomethingBad()
}
然后以下列方式调用
var xxx *Xxx
DoWork(xxx)
令人惊讶的时,它也可以这样调用(使用对象,而不是指向对象的指针)
var xxx Xxx
DoWork(xxx)
Go编译器会检测传入的时指向对象的指针还是对象,并决定相应操作。但这仅针对接口类型的参数。类似地,对于已有返回结构体类型的函数,可将其更改为返回多个接口或者返回组合接口的形式。接口有一个问题,可能是很大的问题,由于Go不允许对同一类型使用重载(相同名称、不同签名)函数,因此可以使用相同的方法名创建多个接口,通常使用不同的参数或返回类型。但不能将它们组合到新接口中。这也意味着一个类型不能同时实现这些不同的接口。因此行为而保留该名称。例如,io.Writer接口基本上声明Write方法(及其特定参数)仅表示他认为的含义,由于与io.Writer接口冲突,因此其他接口无法再为其他目的创建名为Writer的方法。
例如,可以创建如下接口:
type MyWriter interface {
// vs io.Writer: Writer([]byte) (int, error)
write([]byte, int) error
}
不可能创建一个同时实现MyWriter和io.Writer接口的类型。避免此问题的一种方法是创建名称较长(通常是多字)的方法,将较短的名称留给Go协程开发人员使用
type DoGoodAndBad interface {
DoGooder
DoBader
}
在函数中以下列方式使用组合接口:
func DoWork(dgb DoGoodAndBad) {
dgb,DoSomethingGood()
dgb.DoSomethingBad()
}
然后以下列方式调用
var xxx *Xxx
DoWork(xxx)
令人惊讶的时,它也可以这样调用(使用对象,而不是指向对象的指针)
var xxx Xxx
DoWork(xxx)
Go编译器会检测传入的时指向对象的指针还是对象,并决定相应操作。但这仅针对接口类型的参数。类似地,对于已有返回结构体类型的函数,可将其更改为返回多个接口或者返回组合接口的形式。接口有一个问题,可能是很大的问题,由于Go不允许对同一类型使用重载(相同名称、不同签名)函数,因此可以使用相同的方法名创建多个接口,通常使用不同的参数或返回类型。但不能将它们组合到新接口中。这也意味着一个类型不能同时实现这些不同的接口。因此行为而保留该名称。例如,io.Writer接口基本上声明Write方法(及其特定参数)仅表示他认为的含义,由于与io.Writer接口冲突,因此其他接口无法再为其他目的创建名为Writer的方法。
例如,可以创建如下接口:
type MyWriter interface {
// vs io.Writer: Writer([]byte) (int, error)
write([]byte, int) error
}
不可能创建一个同时实现MyWriter和io.Writer接口的类型。避免此问题的一种方法是创建名称较长(通常是多字)的方法,将较短的名称留给Go协程开发人员使用
有关依赖注入。
为了进一步使用接口,应该尽可能地使用依赖注入(Dependency Injection, DI)。DI是一种设计方法,在这种方法中,代码与其依赖一起被提供,而不是自己获取它们(换句话说,让其他人提供所有依赖项)。DI将创建依赖项地责任与依赖它地代码相分离。DI实现通常要求注入的类型符合某些接口类型。该方法提供了极大的灵活性,尤其是在测试代码或配置对象之间的复杂关系时,第二种情况在Java中非常普遍,以至于创建了一个主要框架,如Spring以及SpringBoot来提供它,还存在其他选项,例如谷歌的Guice。
维基对DI的定义如下:
依赖注入将客户端依赖项的创建于客户端的行为分离,这允许程序设计松耦合并遵循依赖倒置和单一责任原则。
维基对Spring的DI的定义如下:
Spring框架的核心是它的控制反转(IoC)容器,并提供了一种使用反射来配置和管理Java对象的一致方法。容器负责管理特定对象的对象生命周期:创建这些对象,调用它们的初始化方法,并通过将它们连接在一起来配置这些对象。容器创建的对象也称为受托管对象或bean。可以通过依赖项查找或依赖项注入来获取对象。
那么什么是依赖?当一个对象具有下列特性(至少):
1.有状态或行为
2.状态应该被封装(对任何用户隐藏),以便实现可以更改。因此,最好将行为表示为接口
3.被一些依赖或代码使用
在Spring案例中,有一个DI容器管理所谓的bean(可以链接在一起的POJO)。容器的作用通常类似于一个映射,提供可在运行时解析的命名对象。在大多数情况下,容器基于工厂方法上的注释(比如@Bean)或外部定义比如在XML中创建bean实例。DI通常通过注释来指示容器将源POJO链接到目标POJO。容器完成有序的必备的bean的创建和注入,通常bean是单例对象(在整个应用程序中共享一个实例)。容器通常不是程序执行期间进出的对象来源。通常容器扮演主程序的角色,创建bean,然后在程序启动时将它们连接在一起。
Java的DI框架通常使用反射来生成待注入的对象。它们通常采用由应用程序开发人员定义的POJO,并将其封装进增加了额外函数(例如日志或数据库事务管理)的代理中,代理概念的关键在于,代理的客户端无法仅通过代理接口将其与它所代理的对象区分开来,它完全实现了代理对象的行为契约,因此是对象即插即用的替代品。在大多数情况下,POJO类必须实现一个或多个接口。这些接口可以在运行时动态定义具体的实现。
为了进一步使用接口,应该尽可能地使用依赖注入(Dependency Injection, DI)。DI是一种设计方法,在这种方法中,代码与其依赖一起被提供,而不是自己获取它们(换句话说,让其他人提供所有依赖项)。DI将创建依赖项地责任与依赖它地代码相分离。DI实现通常要求注入的类型符合某些接口类型。该方法提供了极大的灵活性,尤其是在测试代码或配置对象之间的复杂关系时,第二种情况在Java中非常普遍,以至于创建了一个主要框架,如Spring以及SpringBoot来提供它,还存在其他选项,例如谷歌的Guice。
维基对DI的定义如下:
依赖注入将客户端依赖项的创建于客户端的行为分离,这允许程序设计松耦合并遵循依赖倒置和单一责任原则。
维基对Spring的DI的定义如下:
Spring框架的核心是它的控制反转(IoC)容器,并提供了一种使用反射来配置和管理Java对象的一致方法。容器负责管理特定对象的对象生命周期:创建这些对象,调用它们的初始化方法,并通过将它们连接在一起来配置这些对象。容器创建的对象也称为受托管对象或bean。可以通过依赖项查找或依赖项注入来获取对象。
那么什么是依赖?当一个对象具有下列特性(至少):
1.有状态或行为
2.状态应该被封装(对任何用户隐藏),以便实现可以更改。因此,最好将行为表示为接口
3.被一些依赖或代码使用
在Spring案例中,有一个DI容器管理所谓的bean(可以链接在一起的POJO)。容器的作用通常类似于一个映射,提供可在运行时解析的命名对象。在大多数情况下,容器基于工厂方法上的注释(比如@Bean)或外部定义比如在XML中创建bean实例。DI通常通过注释来指示容器将源POJO链接到目标POJO。容器完成有序的必备的bean的创建和注入,通常bean是单例对象(在整个应用程序中共享一个实例)。容器通常不是程序执行期间进出的对象来源。通常容器扮演主程序的角色,创建bean,然后在程序启动时将它们连接在一起。
Java的DI框架通常使用反射来生成待注入的对象。它们通常采用由应用程序开发人员定义的POJO,并将其封装进增加了额外函数(例如日志或数据库事务管理)的代理中,代理概念的关键在于,代理的客户端无法仅通过代理接口将其与它所代理的对象区分开来,它完全实现了代理对象的行为契约,因此是对象即插即用的替代品。在大多数情况下,POJO类必须实现一个或多个接口。这些接口可以在运行时动态定义具体的实现。
Go目前不支持这种代理的动态创建,因为似乎不可能在运行时使用反射来定义类型,但可使用反射实现符合接口的对象。这就是为什么经常使用代码生成方法的部分原因,也许将来会改变。Go确实支持创建客户端可能知道的类似代理外观(facade)对象。将术语POGO定义为POJO的Go等价物。POGO通常实现为Go结构体。Go没有标准的DI容器实现。Go社区提供了一些,例如Uber的Dig和Google的Wire。
Dig描述如下:
Go的基于反射的依赖注入工具包,优点如下:
# 增强了应用程序框架
# 在程序启动期间解析对象图
Wire描述如下:
一种代码生成工具,使用依赖注入自动连接组件。组件之间的依赖关系在Wire中表示为函数参数,鼓励显式初始化而不是全局变量。由于Wire在运行时没有运行时状态或反射,因此用于Wire的代码即使对于手工编写的初始化也很有用。这两个容器实例以实际例子形式讲解了Go容器的主要实现方法:
1.使用反射(如Spring的做法)在POGO中设置字段将其连接到一起
2.使用代码生成来创建POGO连接在一起的逻辑(非常像在main中手动完成的逻辑)。
Dig描述如下:
Go的基于反射的依赖注入工具包,优点如下:
# 增强了应用程序框架
# 在程序启动期间解析对象图
Wire描述如下:
一种代码生成工具,使用依赖注入自动连接组件。组件之间的依赖关系在Wire中表示为函数参数,鼓励显式初始化而不是全局变量。由于Wire在运行时没有运行时状态或反射,因此用于Wire的代码即使对于手工编写的初始化也很有用。这两个容器实例以实际例子形式讲解了Go容器的主要实现方法:
1.使用反射(如Spring的做法)在POGO中设置字段将其连接到一起
2.使用代码生成来创建POGO连接在一起的逻辑(非常像在main中手动完成的逻辑)。
DI容器特别适合于提供依赖项,如日志记录器、数据库连接池、数据缓存、HTTP客户端和类似的伪全局值。实际上,如果以最大程度应用,那么容器自身是应用程序中唯一的公有顶层对象,所有其他的都由容器管理。在Go中,有几种注入方式:
1.实例初始化——当声明实例字面量时通过设置来注入依赖项
2.构造函数/工厂——通过传递给构造函数(New...)或一些其他的工厂方法,来实现依赖注入。通常,这是首选项。
3.直接字段赋值——直接通过字段赋值进行依赖注入。通常字段必须是公有的(因为依赖类型通常在不同的包中)才能启动此功能。应避免使用这种方式
4.Setter方法——通过传递给"setter"方法来实现依赖注入。该方法很少使用,因为结构体并不总是为所有的私有字段提供get/set方法,尤其作为依赖项的公有接口。
前两种形式的局限在于无法建立彼此循环依赖的POJO。通常,最好避免这样的以来图,依赖关系应该形成一个层次结构图。对于后两种,实例创建后依赖关系才设置,故会存在依赖未设置的窗口期。
1.实例初始化——当声明实例字面量时通过设置来注入依赖项
2.构造函数/工厂——通过传递给构造函数(New...)或一些其他的工厂方法,来实现依赖注入。通常,这是首选项。
3.直接字段赋值——直接通过字段赋值进行依赖注入。通常字段必须是公有的(因为依赖类型通常在不同的包中)才能启动此功能。应避免使用这种方式
4.Setter方法——通过传递给"setter"方法来实现依赖注入。该方法很少使用,因为结构体并不总是为所有的私有字段提供get/set方法,尤其作为依赖项的公有接口。
前两种形式的局限在于无法建立彼此循环依赖的POJO。通常,最好避免这样的以来图,依赖关系应该形成一个层次结构图。对于后两种,实例创建后依赖关系才设置,故会存在依赖未设置的窗口期。
Go社区的一些人认为使用DI(尤其是在由容器管理时)不是地道的Go语言用法。DI通过容器可隐藏对象间关系,而在代码中手动创建对象则更容易理解。这一论点是有道理的。但随着应用程序复杂性的增长和所涉及的部分(POJO)的增加,手动代码可能失控,自动化DI解决方案可能是恰当的(甚至是必要的)。
面向切面编程。
Java支持面向切面编程(AOP的编程风格。AOP允许用户用新的行为(代码)扩充【使用通知(advice)】代码(通常称为基础代码或者原始代码)。维基的描述如下:
一种编程范式,旨在通过允许分离横切关注点(XCC)来增加模块化。它向现有代码中添加额外的行为(advice)不通过修改代码本身,而是通过切点规范单独制定要修改的行为添加到程序中,而不会弄乱功能的核心代码。
AOP中的三个关键概念是:
1.切点(Pointcut)——表明何处使用advice;通常,一些断言(通常是正则表达式之类的模式)选择要处理的代码或数据。切点通常仅限于匹配一个或多个类型中的一个或多个方法,但一些AOP系统也允许匹配数据字段。许多连接点可以匹配切点。
2.通知(advice)——触发切点时的执行内容。有很多种advice,但最常见的时Before、After和Around
3.连接点(Join point)——代码中应用advice的实际位置
切点和通知代码通常由一种类似类的结构称为切面(aspect)定义,这是一种描述所需切点或通知的方法。Java中,有下列几种方法可以在连接点上应用advice:
1.静态重写源代码——一些预处理器(编译前)编辑基本源代码
2.静态重写目标代码——一些后置处理器(编译后)编辑基本目标代码(在Go中,这很难实现;如果在代码生成阶段实现,则比较容易,但需要编译做一些变动)
3.动态重写目标代码——一些运行时处理器通常在首次加载目标时编辑它(Go中这很难做到)
4.使用动态代理——一些运行时处理器通常在首次加载代码时包装代码(这在Go中很难做到)
Java支持面向切面编程(AOP的编程风格。AOP允许用户用新的行为(代码)扩充【使用通知(advice)】代码(通常称为基础代码或者原始代码)。维基的描述如下:
一种编程范式,旨在通过允许分离横切关注点(XCC)来增加模块化。它向现有代码中添加额外的行为(advice)不通过修改代码本身,而是通过切点规范单独制定要修改的行为添加到程序中,而不会弄乱功能的核心代码。
AOP中的三个关键概念是:
1.切点(Pointcut)——表明何处使用advice;通常,一些断言(通常是正则表达式之类的模式)选择要处理的代码或数据。切点通常仅限于匹配一个或多个类型中的一个或多个方法,但一些AOP系统也允许匹配数据字段。许多连接点可以匹配切点。
2.通知(advice)——触发切点时的执行内容。有很多种advice,但最常见的时Before、After和Around
3.连接点(Join point)——代码中应用advice的实际位置
切点和通知代码通常由一种类似类的结构称为切面(aspect)定义,这是一种描述所需切点或通知的方法。Java中,有下列几种方法可以在连接点上应用advice:
1.静态重写源代码——一些预处理器(编译前)编辑基本源代码
2.静态重写目标代码——一些后置处理器(编译后)编辑基本目标代码(在Go中,这很难实现;如果在代码生成阶段实现,则比较容易,但需要编译做一些变动)
3.动态重写目标代码——一些运行时处理器通常在首次加载目标时编辑它(Go中这很难做到)
4.使用动态代理——一些运行时处理器通常在首次加载代码时包装代码(这在Go中很难做到)
Java有几个AOP实现。最流行的是AspectJ和Spring AOP。AspectJ更全面,主要使用上述方法二和方法三。Spring AOP使用上述方法四。
AOP通常用来给代码添加行为。常见例子是向Web API处理车器添加日志记录、授权检查和事务支持。这些是XCC的例子,它们通常不属于代码的主要目的或核心关注点,但支持上下文需求。如果主代码未被代码搞乱,则最好提供它们。Go AOP选项是有限的。标准库未直接支持该功能。存在一些社区提供的选项,但可能不成熟,不如Java的功能全面。目前,Go的AOP产品不支持像JavaAOP一样非侵入式地(客户端和服务代码都没有更改)向基类型添加advice。
AOP风格地编程可能看起来很神奇(代码有新的行为,而行为的来源并不总是显而易见)。与DI容器一样,AOP风格的编程在Go中也不常用。但就像DI一样,它可以称为一种强大的支持手段。在Go中,类似AOP的行为可以通过引用代码来实现,通常称为中间价(又名软件胶水).这是通过将服务包装在符合服务原型的处理器中,在客户端和服务(因此称为中间)之间添加的功能。由于Go支持一等函数,因此中间件可以相对容易地实现。注意,任何HTTP处理程序必须遵从net/http定义的接口:
type HandlerFunc func(http.ResponseWriter, *http.Request)
给定的辅助函数,如图所示,控制台输出如下:
API server listening at: 127.0.0.1:62113
entered handler for GET /test
in handler GET /test
elapsed time for GET /test:1000282600s
exited handler for GET /test
在这里,日志记录和计时的独立功能由不同的中间件片段添加;原始处理程序不会受到任何影响。HTTP引擎也是如此。可以应用任意数量的包装器(以增加一些执行时间为代价)。一个成熟的AOP系统可能会自动应用这样的中间件,但也可以手动应
AOP通常用来给代码添加行为。常见例子是向Web API处理车器添加日志记录、授权检查和事务支持。这些是XCC的例子,它们通常不属于代码的主要目的或核心关注点,但支持上下文需求。如果主代码未被代码搞乱,则最好提供它们。Go AOP选项是有限的。标准库未直接支持该功能。存在一些社区提供的选项,但可能不成熟,不如Java的功能全面。目前,Go的AOP产品不支持像JavaAOP一样非侵入式地(客户端和服务代码都没有更改)向基类型添加advice。
AOP风格地编程可能看起来很神奇(代码有新的行为,而行为的来源并不总是显而易见)。与DI容器一样,AOP风格的编程在Go中也不常用。但就像DI一样,它可以称为一种强大的支持手段。在Go中,类似AOP的行为可以通过引用代码来实现,通常称为中间价(又名软件胶水).这是通过将服务包装在符合服务原型的处理器中,在客户端和服务(因此称为中间)之间添加的功能。由于Go支持一等函数,因此中间件可以相对容易地实现。注意,任何HTTP处理程序必须遵从net/http定义的接口:
type HandlerFunc func(http.ResponseWriter, *http.Request)
给定的辅助函数,如图所示,控制台输出如下:
API server listening at: 127.0.0.1:62113
entered handler for GET /test
in handler GET /test
elapsed time for GET /test:1000282600s
exited handler for GET /test
在这里,日志记录和计时的独立功能由不同的中间件片段添加;原始处理程序不会受到任何影响。HTTP引擎也是如此。可以应用任意数量的包装器(以增加一些执行时间为代价)。一个成熟的AOP系统可能会自动应用这样的中间件,但也可以手动应
客户端和服务器支持
html包提供处理HTML文本的函数。Java标准版(JSE)几乎没有类似的支持,更多的是由Java社区提供该功能。Go template包提供用来生成值插入文本输出的模板。它支持纯文本,并扩展了抗黑客攻击的HTML文本。
MIME包。
mime包提供MIME类型支持。它有两个子包:
# Multipart支持MIME多部件分析
# Ouotedprintable通过reader和writer提供可引用打印的编码。
mime包提供MIME类型支持。它有两个子包:
# Multipart支持MIME多部件分析
# Ouotedprintable通过reader和writer提供可引用打印的编码。
网络包。
net包提供了使用套接字级访问的TCP/IP和UDP的接口。它有几个子包。这里只介绍http子包。http包提供了创建HTTP客户端和服务器的能力。它有几个常用的子包:
# Cgi提供对通用网关结构(CGI)的支持,处理每个请求服务
# Fcgi提供对"快速"通用网关接口服务器的支持
# Cookiejar提供HTTP Cookie的支持
# Httputil提供HTTP赋值实用程序功能
# Textproto为带有文本标题和section标签的协议提供帮助,例如HTTP和SMTP
net包提供了使用套接字级访问的TCP/IP和UDP的接口。它有几个子包。这里只介绍http子包。http包提供了创建HTTP客户端和服务器的能力。它有几个常用的子包:
# Cgi提供对通用网关结构(CGI)的支持,处理每个请求服务
# Fcgi提供对"快速"通用网关接口服务器的支持
# Cookiejar提供HTTP Cookie的支持
# Httputil提供HTTP赋值实用程序功能
# Textproto为带有文本标题和section标签的协议提供帮助,例如HTTP和SMTP
net包。net包很大。它提供了通过套接字、数据报、选定协议(比如HTTP)访问TCP/IP网络的基础服务。net包有很多类型和函数。这里暂不列出,后面的case会显示API的小子集(Dial、Listen、Accept、Read和Write)的使用方式。
在学习Go的HTTP包之前,我们先简要介绍TCP/IP、HTTP、REST、RPC。终端控制协议(TCP)与Internet协议(IP)一起构成以太网的主要基础。它们一起实现了低级、不可靠数据报传输或者网络主机间的可靠套接字/会话交互。
超文本传输协议(HTTP)是一个深受欢迎的协议,基于TCP套接字传输,联合超文本标记语言(HTML)、层叠样式表(CSS)和JavaScript以及其他MIME类型,共同创造了耳熟能详的万维网(WWW)。
HTTP允许数据以多种格式在服务器和客户端(通常是浏览器)之间转换。它支持很多动作,但主要允许GET(读)、PUT(创建或替换)、POST(生成或附加)和DELETE(即CRUD)资源。
表述性状态转移(REST, 有时也叫作ReST)以HTTP为基础,但为了易用性和扩展性,做了一些限制。REST不是一种实现方式,而是一组设计指南。它反映了WWW支持最优秀的品质。它将服务器操作限制为应用于由URL标识的资源上的CRUD操作。REST最大限度地实现了HATEOAS,这体现了WWW组织。大多数RESTful API都达不到这个级别。REST ful服务是基于RPC的服务的替代方案。Go对此类服务由很多支持。
远程过程调用(RPC)是使用HTTP(或其他协议)的替代方法,其限制比REST更少(通常性能更好),它允许创建服务器提供给客户端(几乎总是程序)的任意操作(过程)。
Web服务(WS)是一种RPC形式,通常通过SOAP(一种基于XML的协议)使用HTTP实现。与RESTful服务相比,此类服务已经失宠。Go几乎没有对基于SOAP的WS的标准库支持。
在net、rpc、jsonrpc包中,Go对标准RPC支持有限。一个更强大和流行的社区选项是来自谷歌的gRP每个连接C。Go的net包中的http和rpc子包可用来开发任何这些级别的程序。这里主要介绍REST风格的访问。所有的这些都是构建在基于低级的TCP/IP套接字通信的基础之上的。Go的net包也支持该层级的编程。下面将展示一个简单的TCP套接字客户端/服务器对,演示基于套接字的基本通信。注意,与使用HTTP通信相比,使用套接字的方式需要更多的参与度(更多的代码),代码效率低下,因为每个连接只允许一个请求类似于HTTP1.0.它在读写上也是低效的,因为一次只读取/写入一个字节的数据。下面将运行一个启动服务器并发出多个请求的配置(通常在不同的机器上启动,但在这里通过多个Go协程进 行模拟)。该演示开始以随机间隔在后台发送十个请求,其中一些请求重叠,然后快速(在任何请求丢失之前)启动服务器来处理它们
在学习Go的HTTP包之前,我们先简要介绍TCP/IP、HTTP、REST、RPC。终端控制协议(TCP)与Internet协议(IP)一起构成以太网的主要基础。它们一起实现了低级、不可靠数据报传输或者网络主机间的可靠套接字/会话交互。
超文本传输协议(HTTP)是一个深受欢迎的协议,基于TCP套接字传输,联合超文本标记语言(HTML)、层叠样式表(CSS)和JavaScript以及其他MIME类型,共同创造了耳熟能详的万维网(WWW)。
HTTP允许数据以多种格式在服务器和客户端(通常是浏览器)之间转换。它支持很多动作,但主要允许GET(读)、PUT(创建或替换)、POST(生成或附加)和DELETE(即CRUD)资源。
表述性状态转移(REST, 有时也叫作ReST)以HTTP为基础,但为了易用性和扩展性,做了一些限制。REST不是一种实现方式,而是一组设计指南。它反映了WWW支持最优秀的品质。它将服务器操作限制为应用于由URL标识的资源上的CRUD操作。REST最大限度地实现了HATEOAS,这体现了WWW组织。大多数RESTful API都达不到这个级别。REST ful服务是基于RPC的服务的替代方案。Go对此类服务由很多支持。
远程过程调用(RPC)是使用HTTP(或其他协议)的替代方法,其限制比REST更少(通常性能更好),它允许创建服务器提供给客户端(几乎总是程序)的任意操作(过程)。
Web服务(WS)是一种RPC形式,通常通过SOAP(一种基于XML的协议)使用HTTP实现。与RESTful服务相比,此类服务已经失宠。Go几乎没有对基于SOAP的WS的标准库支持。
在net、rpc、jsonrpc包中,Go对标准RPC支持有限。一个更强大和流行的社区选项是来自谷歌的gRP每个连接C。Go的net包中的http和rpc子包可用来开发任何这些级别的程序。这里主要介绍REST风格的访问。所有的这些都是构建在基于低级的TCP/IP套接字通信的基础之上的。Go的net包也支持该层级的编程。下面将展示一个简单的TCP套接字客户端/服务器对,演示基于套接字的基本通信。注意,与使用HTTP通信相比,使用套接字的方式需要更多的参与度(更多的代码),代码效率低下,因为每个连接只允许一个请求类似于HTTP1.0.它在读写上也是低效的,因为一次只读取/写入一个字节的数据。下面将运行一个启动服务器并发出多个请求的配置(通常在不同的机器上启动,但在这里通过多个Go协程进 行模拟)。该演示开始以随机间隔在后台发送十个请求,其中一些请求重叠,然后快速(在任何请求丢失之前)启动服务器来处理它们
该服务器将分两部分运行:
1.一个长时间运行的循环接收连接
2.一个Go协程来处理一个接收的连接上的请求,可以由任意数量的此类处理器并发运行。
以上模式是典型的服务器请求处理模式。在其他语言中(如Java),操作系统线程(通常来自受限的线程池)通常用于每个请求,在Go中使用Go协程替代。使用Go可更好地实现请求扩展.
1.一个长时间运行的循环接收连接
2.一个Go协程来处理一个接收的连接上的请求,可以由任意数量的此类处理器并发运行。
以上模式是典型的服务器请求处理模式。在其他语言中(如Java),操作系统线程(通常来自受限的线程池)通常用于每个请求,在Go中使用Go协程替代。使用Go可更好地实现请求扩展.
0 条评论
下一页