感谢您给予本书机会!本书是一本指南,带您了解Go标准库的可能性,其中包含了许多开箱即用的功能和解决方案。请注意,本书涵盖的解决方案主要是对标准库实现的简单演示以及其使用方式的说明。这些示例旨在为您提供解决特定问题的起点,而不是完全解决问题。
这本书适用于那些想要加强基础并揭示Go标准库隐藏部分的人。本书希望读者具有Go的基本知识。对于一些示例,了解HTML、操作系统和网络将有所帮助。
第一章,与环境交互,探讨了您的代码如何与操作系统环境交互。还涵盖了使用命令行标志和参数、消耗信号以及与子进程一起工作。
第二章,字符串和其他内容,介绍了对字符串的常见操作,从简单的子字符串搜索到文本格式化为制表符。
第三章,处理数字,介绍了基本转换和数字格式化选项。还涵盖了大数字的操作以及在输出消息中正确使用复数形式。
第五章,输入和输出,涵盖了利用标准Go接口进行的I/O操作。除了基本的I/O外,本章还涵盖了一些有用的序列化格式以及如何处理它们。
第六章,发现文件系统,讨论了与文件系统的工作,包括列出文件夹、读取和更改文件属性,以及对比文件。
第七章,连接网络,展示了连接TCP和UDP服务器的客户端实现,以及SMTP、HTTP和JSON-RPC的使用。
第八章,与数据库工作,专注于常见的数据库任务,如数据选择和提取、事务处理和执行,以及存储过程的缺点。
第九章,来到服务器端,从服务器的角度提供了对网络的视角。介绍了TCP、UDP和HTTP服务器的基础知识。
第十章,并发乐趣,涉及同步机制和对资源的并发访问。
第十一章,技巧与窍门,提供了有用的测试和改进HTTP服务器实现的技巧,并展示了HTTP/2推送的好处。
尽管Go编程平台是跨平台的,但本书中的示例通常假定使用基于Unix的操作系统,或者至少可以执行一些常见的Unix实用程序。对于Windows用户,Cygwin或GitBash实用程序可能会有所帮助。示例代码最适合这种设置:
您可以按照以下步骤下载代码文件:
文件下载完成后,请确保您使用最新版本的解压缩软件解压或提取文件夹:
本书中使用了许多文本约定。
CodeInText:指示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟URL、用户输入和Twitter句柄。这是一个例子:“验证您的GOPATH和GOROOT环境变量是否设置正确。”
代码块设置如下:
packagemainimport("log""runtime")粗体:表示一个新术语、一个重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。
警告或重要说明看起来像这样。
提示和技巧看起来像这样。
在本书中,您会经常看到几个标题(准备工作、如何做、它是如何工作的、还有更多和另请参阅)。
为了清晰地说明如何完成一个食谱,使用以下各节:
本节告诉您食谱中会有什么,并描述如何设置食谱所需的任何软件或任何初步设置。
本节包含了遵循食谱所需的步骤。
本节通常包括对前一节发生的事情的详细解释。
本节包括有关食谱的其他信息,以使您对食谱更加了解。
本节为食谱提供了其他有用信息的链接。
在本章中,将涵盖以下配方:
每个程序一旦被执行,就存在于操作系统的环境中。程序接收输入并向该环境提供输出。操作系统还需要与程序通信,让程序知道外部发生了什么。最后,程序需要做出适当的响应。
本章将带您了解系统环境的发现基础知识,通过程序参数对程序进行参数化,以及操作系统信号的概念。您还将学习如何执行和与子进程通信。
在构建程序时,最好记录环境设置、构建版本和运行时版本,特别是如果您的应用程序更复杂。这有助于您分析问题,以防出现故障。
除了构建版本和例如环境变量之外,编译二进制文件的Go版本可以包含在日志中。以下的步骤将向您展示如何将Go运行时版本包含在程序信息中。
安装并验证Go安装。以下步骤可能有所帮助:
以下步骤涵盖了解决方案:
runtime包包含许多有用的函数。要找出Go运行时版本,可以使用Version函数。文档说明该函数返回提交的哈希值,以及二进制构建时的日期或标签。
实际上,Version函数返回runtime/internal/sys的Version常量。常量本身位于$GOROOT/src/runtime/internal/sys/zversion.go文件中。
这个.go文件是由godist工具生成的,版本是通过go/src/cmd/dist/build.go文件中的findgoversion函数解析的,如下所述。
$GOROOT/VERSION优先级最高。如果文件为空或不存在,则使用$GOROOT/VERSION.cache文件。如果也找不到$GOROOT/VERSION.cache,则工具会尝试使用Git信息来解析版本,但在这种情况下,您需要为Go源代码初始化Git存储库。
参数化程序运行的最简单方法是使用命令行参数作为程序参数。
简单地说,参数化的程序调用可能如下所示:./parsecsvuser.csvrole.csv。在这种情况下,parsecsv是执行二进制文件的名称,user.csv和role.csv是修改程序调用的参数(在这种情况下是要解析的文件)。
Go标准库提供了几种访问程序调用参数的方法。最通用的方法是通过OS包中的Args变量访问参数。
通过这种方式,您可以在字符串切片中获取命令行中的所有参数。这种方法的优点是参数的数量是动态的,这样您可以,例如,将要由程序处理的文件的名称传递给程序。
上面的示例只是回显传递给程序的所有参数。最后,假设二进制文件名为test,程序运行由终端命令./testarg1arg2执行。
具体来说,os.Args[0]将返回./test。os.Args[1:]返回不带二进制名称的其余参数。在现实世界中,最好不要依赖于传递给程序的参数数量,而是始终检查参数数组的长度。否则,如果给定索引上的参数不在范围内,程序将自然地发生恐慌。
如果参数被定义为标志,-flagvalue,则需要额外的逻辑来将值分配给标志。在这种情况下,使用flag包有更好的方法来解析这些标志。这种方法是下一个配方的一部分。
前面的配方描述了如何通过非常通用的方法访问程序参数。
这个配方将提供一种通过程序标志定义接口的方法。这种方法主导了基于GNU/Linux、BSD和macOS的系统。程序调用的示例可以是ls-l,在*NIX系统上,它将列出当前目录中的文件。
Go标志处理包不支持像ls-ll这样的标志组合,其中在单个破折号后有多个标志。每个标志必须是单独的。Go标志包也不区分长选项和短选项。最后,-flag和--flag是等效的。
对于代码中的标志定义,flag包定义了两种类型的函数。
第一种类型是标志类型的简单名称,例如Int。这个函数将返回整数变量的指针,解析标志的值将存储在其中。
XXXVar函数是第二种类型。它们提供相同的功能,但需要提供变量的指针。解析的标志值将存储在给定的变量中。
Go库还支持自定义标志类型。自定义类型必须实现flag包中的Value接口。
例如,假设标志retry定义了重新连接到端点的重试限制,标志prefix定义了日志中每行的前缀,而array是作为有效负载发送到服务器的数组标志。终端中的程序调用将如./util-retry2-prefix=examplearray=1,2。
上述代码的重要部分是Parse()函数,它从Args[1:]中解析定义的标志。在定义所有标志并在访问值之前必须调用该函数。
上面的代码显示了如何从命令行标志中解析一些数据类型。类似地,其他内置类型也可以解析。
最后一个标志array演示了自定义类型标志的定义。请注意,ArrayType实现了flag包中的Value接口。
flag包包含更多函数来设计带有标志的接口。值得阅读FlagSet的文档。
通过定义新的FlagSet,可以通过调用myFlagset.Parse(os.Args[2:])来解析参数。这样你就可以基于第一个标志拥有标志子集。
前一个教程,使用flag包创建程序接口,描述了如何将标志用作程序参数。
特别是对于较大的应用程序,另一种典型的参数化方式是使用环境变量进行配置。环境变量作为配置选项显著简化了应用程序的部署。这在云基础设施中也非常常见。
通常,本地数据库连接和自动构建环境的配置是不同的。
如果配置由环境变量定义,就不需要更改应用程序配置文件甚至应用程序代码。导出的环境变量(例如DBSTRING)就是我们所需要的。如果环境变量不存在,将配置默认值也非常实用。这样,应用程序开发人员的生活就轻松多了。
本教程将演示如何读取、设置和取消设置环境变量。它还将向您展示如何在变量未设置时实现默认选项。
环境变量可以通过os包中的Getenv和Setenv函数来访问。这些函数的名称不言自明,不需要进一步的描述。
os包中还有一个有用的函数。LookupEnv函数提供两个值作为结果;变量的值,以及布尔值,定义变量在环境中是否设置。
os.Getenv函数的缺点是,即使在环境变量未设置的情况下,它也会返回空字符串。
这个缺点可以通过os.LookupEnv函数来克服,该函数返回环境变量的字符串值和一个布尔值,指示变量是否设置。
要实现检索环境变量或默认值,使用os.LookupEnv函数。简单地说,如果变量未设置,也就是第二个返回值是false,那么就返回默认值。该函数的使用是第9步的一部分。
自Go1.8版本以来,本教程使用了Go的解决方案。这是首选方案。
自Go1.8以来,os包中的Executable函数是解析可执行文件路径的首选方法。Executable函数返回执行的二进制文件的绝对路径(除非返回错误)。
为了解析二进制路径的目录,应用了filepath包中的Dir。唯一的问题是结果可能是symlink或它指向的路径。
为了克服这种不稳定的行为,可以使用filepath包中的EvalSymlinks来应用到结果路径上。通过这种方法,返回的值将是二进制文件的真实路径。
可以使用os库中的Executable函数获取二进制文件所在目录的信息。
请注意,如果代码是通过gorun命令运行的,实际的可执行文件位于临时目录中。
了解正在运行的进程的PID是有用的。PID可以被操作系统实用程序用来查找有关进程本身的信息。在进程失败的情况下,了解PID也很有价值,这样您可以在系统日志中跟踪进程行为,例如/var/log/messages,/var/log/syslog。
本示例向您展示了如何使用os包获取执行程序的PID,并将其与操作系统实用程序一起使用以获取更多信息。
os包中的Getpid函数返回进程的PID。示例代码展示了如何从操作系统实用程序ps获取有关进程的更多信息。
在应用程序启动时打印PID可能很有用,这样在崩溃时也可以通过检索到的PID来调查原因。
信号是操作系统与正在运行的进程通信的基本方式。最常见的两个信号是SIGINT和SIGTERM。这些信号会导致程序终止。
还有一些信号,比如SIGHUP。SIGHUP表示调用进程的终端已关闭,例如,程序可以决定转移到后台。
Go提供了一种处理应用程序接收到信号时的行为的方法。本示例将提供一个实现处理的示例。
在资源被获取的应用程序中,如果立即终止可能会发生资源泄漏。最好处理信号并采取一些必要的步骤来释放资源。上述代码展示了如何做到这一点的概念。
signal包中的Notify函数将帮助我们处理接收到的信号。
如果在Notify函数中未指定信号作为参数,函数将捕获所有可能的信号。
请注意,signal包的Notify函数通过sChan通道与goroutine通信。Notify然后捕获定义的信号并将其发送到goroutine进行处理。最后,exitChan用于解析进程的退出代码。
重要的信息是,如果分配的通道未准备好,Notify函数将不会阻止信号。这样信号可能会被错过。为了避免错过信号,最好创建缓冲通道。
请注意,SIGKILL和SIGSTOP信号可能无法被Notify函数捕获,因此无法处理这些信号。
Go二进制文件也可以用作各种实用程序的工具,并且可以使用gorun来替代bash脚本。出于这些目的,通常会调用命令行实用程序。
在这个示例中,将提供如何执行和处理子进程的基础知识。
测试以下命令是否在你的终端中工作:
Go标准库提供了一种简单的调用外部进程的方法。这可以通过os/exec包的Command函数来实现。
最简单的方法是创建Cmd结构并调用Run函数。Run函数执行进程并等待其完成。如果命令退出时出现错误,err值将不为空。
这更适合调用操作系统的实用程序和工具,这样程序不会挂起太久。
进程也可以异步执行。这可以通过调用Cmd结构的Start方法来实现。在这种情况下,进程被执行,但是主goroutine不会等待它结束。Wait方法可以用来等待进程结束。Wait方法完成后,进程的资源将被释放。
这个示例描述了如何简单地执行子进程。本章还提供了检索子进程信息和从子进程读取/写入的示例,介绍了如何从子进程读取和写入,并获取有用的进程信息的步骤。
调用外部进程示例描述了如何同步和异步调用子进程。自然地,要处理进程行为,你需要更多地了解进程。这个示例展示了如何在子进程终止后获取PID和基本信息。
关于运行进程的信息只能通过syscall包获得,而且高度依赖于平台。
测试sleep(Windows中为timeout)命令是否存在于终端中。
os/exec标准库提供了执行进程的方法。使用Command,将返回Cmd结构。Cmd提供了对进程表示的访问。当进程正在运行时,你只能找到PID。
你只能获取有关进程的少量信息。但是通过检索进程的PID,你可以调用操作系统的实用程序来获取更多信息。
请记住,即使子进程正在运行,也可以获取其PID。另一方面,只有在进程终止后,os包的ProcessState结构才可用。
每个执行的进程都有标准输出、输入和错误输出。Go标准库提供了读取和写入这些内容的方法。
本配方将介绍如何读取进程的输出并写入子进程的输入的方法。
验证以下命令是否在终端中工作:
os/exec包的Cmd结构提供了访问进程输出/输入的函数。有几种方法可以读取进程的输出。
读取进程输出的最简单方法之一是使用Cmd结构的Output或CombinedOutput方法(获取Stderr和Stdout)。在调用此函数时,程序会同步等待子进程终止,然后将输出返回到字节缓冲区。
除了Output和OutputCombined方法外,Cmd结构提供了Stdout属性,可以将io.Writer分配给它。分配的写入器然后作为进程输出的目的地。它可以是文件、字节缓冲区或任何实现io.Writer接口的类型。
读取进程输出的最后一种方法是通过调用Cmd结构的StdoutPipe方法获取io.Reader。StdoutPipe方法在Stdout之间创建管道,进程在其中写入输出,并提供Reader,它作为程序读取进程输出的接口。这样,进程的输出被传送到检索到的io.Reader。
向进程的stdin写入的方式相同。在所有选项中,将演示使用io.Writer的方式。
可以看到,有几种方法可以从子进程中读取和写入。使用stderr和stdin的方式几乎与步骤6-7中描述的方式相同。最后,访问输入/输出的方法可以这样分为:
IO类型更加灵活,也可以异步使用。
在这种情况下,优雅意味着应用程序捕获终止信号(如果可能的话),并在终止之前尝试清理和释放分配的资源。这个食谱将向您展示如何实现优雅关闭。
食谱处理操作系统信号描述了捕获操作系统信号。相同的方法将用于实现优雅关闭。在程序终止之前,它将清理并执行一些其他活动。
从sigChan读取是阻塞的,因此程序会一直运行,直到通过通道发送信号。sigChan是Notify函数发送信号的通道。
程序的主要代码在一个新的goroutine中运行。这样,当主函数在sigChan上被阻塞时,工作将继续。一旦从操作系统发送信号到进程,sigChan接收到信号并在从sigChan通道读取的行下面的代码执行。这段代码可以被视为清理部分。
请注意,步骤7的终端输出包含最终日志应用程序释放所有资源,这是清理部分的一部分。
有关信号捕获工作原理的详细描述在食谱处理操作系统信号中。
这个食谱与Go标准库没有直接关系,但包括如何处理应用程序的可选配置。该食谱将在实际情况下使用函数选项模式与文件配置。
函数选项模式的核心概念是配置API包含功能参数。在这种情况下,NewClient函数接受各种数量的ConfigFunc参数,然后逐个应用于defaultClient结构。这样,可以以极大的灵活性修改默认配置。
查看FromFile和FromEnv函数,它们返回ConfigFunc,实际上是访问文件或环境变量。
最后,您可以检查输出,该输出应用了配置选项和结果Client结构,其中包含来自文件和环境变量的值。
本章中的配方有:
在开发人员的生活中,对字符串和基于字符串的数据进行操作是常见任务。本章介绍如何使用Go标准库处理这些任务。毫无疑问,使用标准库可以做很多事情。
检查Go是否已正确安装。第一章的准备就绪部分,与环境交互的检索Golang版本配方将对您有所帮助。
在开发人员中,查找字符串中的子字符串是最常见的任务之一。大多数主流语言都在标准库中实现了这一点。Go也不例外。本配方描述了Go实现这一功能的方式。
Go库strings包含处理字符串操作的函数。这次可以使用Contains函数。Contains函数只是检查字符串是否包含给定的子字符串。实际上,Contains函数中使用了Index函数。
要检查字符串是否以子字符串开头,可以使用HasPrefix函数。要检查字符串是否以子字符串结尾,可以使用HasSuffix函数。
实际上,Contains函数是通过使用同一包中的Index函数实现的。可以猜到,实际实现方式是这样的:如果给定子字符串的索引大于-1,则Contains函数返回true。
HasPrefix和HasSuffix函数的工作方式不同:内部实现只是检查字符串和子字符串的长度,如果它们相等或字符串更长,则比较字符串的所需部分。
本配方描述了如何匹配精确的子字符串。通过正则表达式模式在文本中查找子字符串配方将帮助您了解如何使用正则表达式模式匹配。
将字符串分解为单词可能有些棘手。首先,决定单词是什么,分隔符是什么,是否有任何空格或其他字符。做出这些决定后,可以从strings包中选择适当的函数。本配方将描述常见情况。
将字符串拆分为单词的最简单形式考虑任何空白字符作为分隔符。具体来说,空白字符由unicode包中的IsSpace函数定义:
'\t','\n','\v','\f','\r','',U+0085(NEL),U+00A0(NBSP).strings包的Fields函数可以用于按空格字符拆分句子,如前面提到的。步骤1-5涵盖了这种简单情况。
如果需要其他分隔符,就需要使用Split函数。使用其他分隔符拆分在步骤6-8中介绍。只需注意字符串中的空白字符被省略。
如果您需要更复杂的函数来决定是否在给定点拆分字符串,FieldsFunc可能适合您。函数的一个参数是消耗给定字符串的符文并在该点返回true的函数。这个选项由步骤9-11覆盖。
正则表达式是示例中提到的最后一个选项。regexp包的Regexp结构包含Split方法,它的工作方式与您期望的一样。它在匹配组的位置拆分字符串。这种方法在步骤12-14中使用。
strings包还提供了各种SplitXXX函数,可以帮助您实现更具体的任务。
将字符串拆分为单词这个教程引导我们完成了根据定义的规则将单个字符串拆分为子字符串的任务。另一方面,本教程描述了如何使用给定的字符串作为分隔符将多个字符串连接成单个字符串。
一个真实的用例可能是动态构建SQL选择语句条件的问题。
为了将字符串切片连接成单个字符串,strings包的Join函数就在那里。简单地说,您需要提供需要连接的字符串切片。这样,您可以舒适地连接字符串切片。步骤1-5展示了使用Join函数的方法。
当然,可以通过迭代切片来手动实现连接。这样,您可以通过一些更复杂的逻辑自定义分隔符。步骤6-8只是表示手动连接如何与更复杂的决策逻辑一起使用,基于当前处理的字符串。
Join函数由bytes包提供,自然用于连接字节切片。
除了内置的+运算符外,还有更多连接字符串的方法。本教程将描述使用bytes包和内置的copy函数更高效地连接字符串的方法。
步骤1-5涵盖了将bytes包Buffer作为性能友好的字符串连接解决方案的用法。Buffer结构实现了WriteString方法,可以用于有效地将字符串连接到底层字节切片中。
在所有情况下都不需要使用这种改进,只需要在程序将要连接大量字符串的情况下考虑一下(例如,在内存中的CSV导出和其他情况)。
在步骤6-8中介绍的内置的copy函数可以用于完成string的连接。这种方法对最终字符串长度有一些假设,或者可以实时完成。然而,如果结果写入的缓冲区的容量小于已写部分和要附加的字符串的总和,缓冲区必须扩展(通常是通过分配具有更大容量的新切片)。
仅供比较,这里有一个基准代码,比较了内置的+运算符、bytes.Buffer和内置的copy的性能:
在某些情况下,输出(通常是数据输出)是通过制表文本完成的,这些文本以良好排列的单元格格式化。这种格式可以通过text/tabwriter包实现。该包提供了Writer过滤器,它将带有制表符的文本转换为格式良好的输出文本。
通过调用NewWriter函数创建具有配置参数的Writer过滤器。由此Writer写入的所有数据都根据参数进行格式化。这里使用os.Stdout仅用于演示目的。
text/tabwriter包还提供了一些更多的配置选项,比如flag参数。最有用的是tabwriter.AlignRight,它配置了写入器在每一列中将内容对齐到右侧。
strings包的Replace函数被广泛用于简单的替换。最后一个整数参数定义了将进行多少次替换(在-1的情况下,所有字符串都被替换。看到Replace的第二个用法,只有前两次出现被替换)。Replace函数的用法在步骤1-5中呈现。
除了Replace函数,Replacer结构也有WriteString方法。这个方法将使用Replacer中定义的所有替换写入给定的写入器。这种类型的主要目的是可重用性。它可以一次替换多个字符串,并且对并发使用是安全的;参见步骤6-8。
替换子字符串,甚至匹配模式的更复杂方法,自然是使用正则表达式。Regex类型指针方法ReplaceAllString可以用于此目的。步骤9-11说明了regexp包的用法。
如果需要更复杂的逻辑来进行替换,那么regexp包可能是应该使用的包。
总是有一些任务,比如验证输入、在文档中搜索信息,甚至从给定字符串中清除不需要的转义字符。对于这些情况,通常使用正则表达式。
Go标准库包含regexp包,涵盖了正则表达式的操作。
FindString或FindAllString函数是在给定字符串中查找匹配模式的最简单方法。唯一的区别是Regexp的FindString方法只会返回第一个匹配项。另一方面,FindAllString会返回一个包含所有匹配项的字符串切片。
Regexp类型提供了丰富的FindXXX方法。本教程仅描述了通常最有用的String变体。请注意,前面的代码使用了regexp包的MustCompile函数,如果正则表达式的编译失败,它会引发panic。
除了这种复杂的正则表达式模式匹配,还可以仅匹配子字符串。这种方法在本章的在字符串中查找子字符串教程中有描述。
一个鲜为人知的事实是,所有.go文件中的内容都是用UTF-8编码的。信不信由你,Unicode并不是世界上唯一的字符集。例如,Windows-1250编码在Windows用户中广泛传播。
在处理非Unicode字符串时,需要将内容转换为Unicode。本教程演示了如何解码和编码非Unicode字符串。
packagemainimport("io""os""golang.org/x/text/encoding/charmap")funcmain(){f,err:=os.OpenFile("out.txt",os.O_CREATE|os.O_RDWR,os.ModePerm|os.ModeAppend)iferr!=nil{panic(err)}deferf.Close()//Decodetounicodeencoder:=charmap.Windows1250.NewEncoder()writer:=encoder.Writer(f)io.WriteString(writer,"Gdańsk")}工作原理...包golang.org/x/text/encoding/charmap包含了简单编码和解码的Charset类型。该类型实现了创建Decoder结构的NewDecoder方法。
步骤1-5展示了解码Reader的用法。
编码工作类似。创建编码Writer,然后由该Writer写入的每个字符串都会被编码为Windows-1250编码。
请注意,Windows-1250被选择作为示例。包golang.org/x/text/encoding/charmap包含了许多其他字符集选项。
有许多实际任务需要修改大小写。让我们挑选其中的一些:
为此,strings包提供了ToLower、ToUpper、ToTitle和Title函数。
请注意,Unicode中的标题大小写映射与大写映射不同。不同之处在于字符数需要特殊处理。这些主要是连字和双字母,如fl,dz和lj,以及一些多音调希腊字符。例如,U+01C7(LJ)映射到U+01C8(Lj),而不是U+01C9(lj)。
为了进行适当的不区分大小写比较,应该使用strings包中的EqualFold函数。该函数使用大小写折叠来规范化字符串并进行比较。
有多种表格数据格式。CSV(逗号分隔值)是用于数据传输和导出的最基本格式之一。没有定义CSV的标准,但格式本身在RFC4180中有描述。
这个示例介绍了如何舒适地解析CSV格式的数据。
与简单地逐行扫描输入并使用strings.Split和其他方法解析CSV格式不同,Go提供了更好的方法。encoding/csv包中的NewReader函数返回Reader结构,该结构提供了读取CSV文件的API。Reader结构保留了变量来配置read参数,根据您的需求。
Reader的FieldsPerRecord参数是一个重要的设置。这样可以验证每行的单元格数。默认情况下,当设置为0时,它设置为第一行中的记录数。如果设置为正值,则记录数必须匹配。如果设置为负值,则不进行单元格计数验证。
另一个有趣的配置是Comment参数,它允许您定义解析数据中的注释字符。在示例中,整行都会被忽略。
Go1.10现在禁止使用荒谬的逗号和注释设置。这意味着空值、回车、换行、无效符文和Unicode替换字符。还禁止将逗号和注释设置为相等。
字符串输入可能包含过多的空白、过少的空白或不合适的空白字符。本示例包括了如何处理这些并将字符串格式化为所需格式的提示。
在代码处理之前修剪字符串是非常常见的做法,正如前面的代码所示,标准的Go库可以轻松完成这项工作。strings库还提供了更多TrimXXX函数的变体,也允许修剪字符串中的其他字符。
要修剪前导和结束的空白,可以使用strings包的TrimSpace函数。这是代码的以下部分的典型示例,这也是之前示例中包含的:
stringToTrim:="\t\t\nGo\tis\tAwesome\t\t"stringToTrim=strings.TrimSpace(stringToTrim)regex包适用于替换多个空格和制表符,可以通过这种方式准备字符串以便进一步处理。请注意,使用此方法时,换行符将被替换为一个空格。
代码的这一部分表示使用正则表达式将所有多个空格替换为单个空格:
r:=regexp.MustCompile("\\s+")replace:=r.ReplaceAllString(stringToTrim,"")填充不是strings包的显式函数,但可以通过fmt包的Sprintf函数实现。代码中的pad函数使用格式化模式%<+/-padding>s和一些简单的数学运算来找出填充。最后,填充数字前的减号作为右填充,正数作为左填充。
有关如何使用正则表达式的更多提示,您可以在本章中查看通过正则表达式模式在文本中查找子字符串的示例。
前面的示例描述了如何进行字符串填充和修剪空白。这个示例将指导您如何对文本文档进行缩进和取消缩进。将使用前面示例中的类似原则。
缩进就像填充一样简单。在这种情况下,使用相同的格式选项。indent实现的更可读形式可以使用strings包的Repeat函数。上述代码中的IndentByRune函数应用了这种方法。
在这种情况下,取消缩进意味着删除给定数量的前导空格。在上述代码中,Unindent的实现会删除最少数量的前导空格或给定的缩进。
管理字符串中的空白示例也以更宽松的方式处理空格。
本章的食谱有:
数字通常是每个应用程序的不可避免的部分——打印格式化的数字、转换基数表示等等。本章介绍了许多常见的操作。
检查Go是否已正确安装。第一章的准备就绪部分,与环境交互,将对您有所帮助。
本食谱将向您展示如何将包含数字的字符串转换为数值类型(整数或浮点值)。
在前面示例代码中的主要函数是strconv包的ParseInt函数。该函数带有三个参数:输入、输入的基数和位大小。基数确定了如何解析数字。请注意,十六进制的基数(第二个参数)为16,二进制的基数为2。strconv包的Atoi函数实际上就是带有基数10的ParseInt函数。
ParseFloat函数将字符串转换为浮点数。第二个参数是bitSize的精度。bitSize=64将导致float64。bitSize=32将导致float64,但可以在不改变其值的情况下转换为float32。
由于浮点数的表示方式,比较两个看似相同的数字时可能会出现不一致。与整数不同,IEEE浮点数只是近似值。需要将数字转换为计算机可以以二进制形式存储的形式,这会导致轻微的精度或舍入偏差。例如,值1.3可以表示为1.29999999999。可以通过一些容差进行比较。要比较任意精度的数字,可以使用big包。
在不使用任何内置包的情况下进行浮点数比较的第一种方法(步骤1-5)需要使用所谓的EPSILON常量。这是选择的足够小的增量(差异)的值,以便将两个数字视为相等。增量常数可以达到1e-8的数量级,这通常是足够的精度。
第二个选项更复杂,但对于进一步处理浮点数更有用。math/big包提供了可以配置为给定精度的Float类型。该包的优势在于精度可以比float64类型的精度高得多。出于说明目的,使用了较小的精度值来显示给定精度的四舍五入和比较。
请注意,当使用16位精度时,da和db数字相等,当使用32位精度时,它们不相等。最大可配置的精度可以从big.MaxPrec常量中获得。
将浮点数四舍五入为整数或特定精度必须正确进行。最常见的错误是将浮点类型float64转换为整数类型,并认为它已经处理好了。
一个例子可能是将数字3.9999转换为整数,并期望它变成值为4的整数。实际结果将是3。在撰写本书时,Go的当前版本(1.9.2)不包含Round函数。然而,在1.10版本中,Round函数已经在math包中实现。
将浮点数转换为整数实际上只是截断了浮点值。比如值2表示为1.999999;在这种情况下,输出将是1,这不是您期望的。
正确的浮点数四舍五入的方法是使用一个函数,该函数还会考虑小数部分。常用的四舍五入方法是向远离零的方向舍入(也称为商业舍入)。简而言之,如果数字包含小数部分的绝对值大于或等于0.5,则将数字四舍五入,否则将向下舍入。
在Round函数中,math包的Trunc函数截断了数字的小数部分。然后提取了数字的小数部分。如果值超过0.5的限制,那么就会加上与整数值相同的符号的1。
Go版本1.10使用了一个更快的实现,该实现在示例中提到。在1.10版本中,您可以直接调用math.Round函数来获得四舍五入的数字。
如前面的示例所述,浮点数的表示也使算术变得复杂。对于一般目的,内置的float64上的操作已经足够。如果需要更高的精度,则需要使用math/big包。本示例将向您展示如何处理这个问题。
big包提供了对高精度浮点数进行算术运算的支持。前面的示例说明了对数字的基本操作。请注意,代码将float64类型和big.Float类型的操作进行了比较。
通过使用高精度数字,使用big.Float类型是至关重要的。当big.Float转换回内置的float64类型时,高精度会丢失。
浮点数的比较和四舍五入在比较浮点数和四舍五入浮点数示例中有提到。
代码示例显示了整数和浮点数的最常用选项。
Go中的格式化源自C的printf函数。所谓的动词用于定义数字的格式化。例如,动词可以是%X,实际上是值的占位符。
有关所有格式选项,请参阅fmt包。strconv包在需要以不同基数格式化数字时也可能很有用。以下食谱描述了数字转换的可能性,但副作用是如何以不同基数格式化数字的选项。
在某些情况下,整数值可以用除十进制表示以外的其他表示。这些表示之间的转换很容易通过strconv包来完成。
strconv包提供了ParseInt和FormatInt函数,这些函数可以说是互补的函数。函数ParseInt能够解析任何基数表示的整数。另一方面,函数FormatInt可以将整数格式化为任何给定的基数。
最后,可以将整数的字符串表示解析为内置的int64类型,然后将解析后的整数的字符串格式化为给定的基数表示。
在为用户显示消息时,如果句子更加人性化,交互会更加愉快。Go包golang.org/x/text,即扩展包,包含了以正确方式格式化复数的功能。
执行goget-xgolang.org/x/text以获取扩展包,如果你还没有的话。
包golang.org/x/text/message包含函数NewPrinter,接受语言标识并创建格式化的I/O,与fmt包相同,但具有根据性别和复数形式翻译消息的能力。
message包的Set函数添加了翻译和复数选择。复数形式本身是根据Selectf函数设置的规则选择的。Selectf函数生成基于plural.Form或选择器的规则的catalog.Message类型。
上述示例代码使用了plural.One和plural.Other形式,以及=x, 有关选择器和形式的更多信息,请参阅golang.org/x/text/message包的文档。 本教程展示了如何生成随机数。这个功能由math/rand包提供。由math/rand生成的随机数被认为是不安全的,因为序列是可重复的,具有给定的种子。 要生成加密安全的数字,应使用crypto/rand包。这些序列是不可重复的。 上述代码介绍了如何生成随机数的两种可能性。第一种选项使用math/rand包,这是不安全的,允许我们使用相同的种子号生成相同的序列。这种方法通常用于测试。这样做的原因是为了使序列可重现。 第二个选项,即加密安全选项,是使用crypto/rand包。API使用Reader提供具有加密强大伪随机生成器实例。包本身具有默认的Reader,通常基于基于系统的随机数生成器。 复数通常用于科学应用和计算。Go将复数实现为原始类型。复数的特定操作是math/cmplx包的一部分。 基本运算符是为原始类型complex实现的。复数的其他操作由math/cmplx包提供。如果需要高精度操作,则没有big实现。 另一方面,复数可以实现为实数,并且虚部由big.Float类型表示。 三角函数运算和几何操作通常以弧度为单位进行;能够将这些转换为度数及其相反是非常有用的。本教程将向您展示如何处理这些单位之间的转换。 Go标准库不包含任何将弧度转换为度数及其相反的函数。但至少Pi常数是math包的一部分,因此可以按照示例代码中所示进行转换。 上述代码还介绍了定义具有附加方法的自定义类型的方法。这些方法通过方便的API简化了值的转换。 对数在科学应用以及数据可视化和测量中被使用。内置的math包包含了常用的对数基数。使用这些,你可以得到所有的基数。 标准包math包含了所有常用对数的函数,因此你可以轻松地得到二进制、十进制和自然对数。查看Log函数,它通过助手定义的公式计算任何以x为底的y的对数: 标准库中对数的内部实现自然是基于近似值的。这个函数可以在$GOROOT/src/math/log.go文件中找到。 哈希,或者所谓的校验和,是快速比较任何内容的最简单方法。这个示例演示了如何创建文件内容的校验和。为了演示目的,将使用MD5哈希函数。 crypto包包含了众所周知的哈希函数的实现。MD5哈希函数位于crypto/md5包中。crypto包中的每个哈希函数都实现了Hash接口。注意Hash包含了Write方法。通过Write方法,它可以被用作Writer。这可以在FileMD5函数中看到。Hash的Sum方法接受字节切片的参数,结果哈希值将放置在其中。 注意这一点。Sum方法不会计算参数的哈希值,而是将哈希计算到参数中。 另一方面,md5.Sum包函数可以直接用于生成哈希。在这种情况下,Sum函数的参数是计算出的哈希值。 自然地,crypto包实现了SHA变体和其他哈希函数。这些通常以相同的方式使用。哈希函数可以通过crypto包的常量crypto.Hash(例如,crypto.MD5.New())来访问,但是这种方式,给定函数的包也必须链接到构建的二进制文件中(可以使用空白导入,import_"crypto/md5"),否则对New的调用将会导致恐慌。 hash包本身包含了CRC校验和等内容。 本章中的食谱有: 验证Go是否正确安装。如果有任何问题,请参阅第一章中的检索Golang版本,并按照准备就绪部分的步骤进行操作。 获取当前日期是任何系统或应用程序的常见任务。让我们看看如何使用Go的标准库来完成这个任务。 不应使用Time类型的指针。如果只使用值(而不是变量的指针),则Time实例被认为是安全的,可用于多个goroutine。唯一的例外是序列化。 time包的Time类型提供了Format方法来格式化输出字符串。 参考日期的备忘录是,以数字形式给出时,表示为1,2,3,4,5,6,-7。-7值表示MST时区比UTC晚7小时。 日期格式化中使用的概念与日期解析中使用的概念相同。可以使用相同的参考日期和布局原则。本食谱将向您展示如何将字符串输入转换为Time实例。 要从Time实例获取时期值,可以调用与从时期创建Time相同名称的方法Unix。还有一个名为UnixNano的方法,它返回毫秒的计数,而不是秒。 自然地,API直接未提供的单位需要从现有单位中派生出来。 请注意,AddDate会对结果进行标准化,与time.Date函数相同。标准化意味着将月份添加到8月31日将导致10月1日,因为接下来的一个月只有30天(9月31日不存在)。 查找两个日期之间的差异并不是一项不寻常的任务。对于这个操作,Go标准包time,分别是Time类型,提供了支持方法。 Time实例的Sub方法是找出两个日期之间差异的通用方法。结果是time.Duration,表示这些日期之间的纳秒计数。 请注意,如果差异超过了最大/最小time.Duration的限制,那么将返回最大或最小值。 函数Since和Until只是计算现在和给定日期之间差异的一种更简洁的方式。它们的工作方式与它们的名称提示的一样。Since函数返回的结果与time.Now().Sub(t)相同;同样,Until返回的结果与t.Sub(time.Now())相同。 Sub方法自然也考虑了时区。因此,差异是相对于每个Time实例的位置返回的。 Ticker持有C通道,用于传递周期性的滴答声。实例是根据滴答声之间的给定间隔创建的。间隔由time.Duration值定义。 打算定期执行的代码在无限循环中的goroutine中执行。从Ticker通道读取会阻塞循环,直到传递滴答声。 请注意,一旦调用Stop方法停止Ticker,C通道并不会关闭,它只是停止传递滴答声。因此,前面的代码包含了select结构,其中停止通道可以传递停止信号。这样就可以进行优雅的关闭。 前面的示例描述了如何定期执行代码。本示例将向您展示如何延迟执行代码。 要执行带有一定延迟的代码,可以使用time包中的Timer。这个工作原理与前面的定期运行代码块中描述的相同。 相同的功能由time包的AfterFunc函数提供。它只是简化了使用。请注意,这里不需要通道。示例代码使用sync.WaitGroup来等待给定的函数执行。 操作本身被包装到一个选择语句中,该语句在time.After通道和默认选项之间进行选择,执行操作。 请注意,您需要允许代码定期从time.After通道中读取,以了解超时是否已经超过。否则,如果默认的代码分支完全阻塞执行,就没有办法知道超时是否已经过去。 示例实现使用了time.After函数,但Timer函数也可以以相同的方式使用。内置库还使用context.WithTimeout来实现超时功能。 本章包含以下教程: 检查Go是否已正确安装。第一章的准备就绪部分,与环境交互的检索Golang版本教程将对您有所帮助。 每个进程都拥有自己的标准输入、输出和错误文件描述符。stdin作为进程的输入。本教程描述了如何从stdin读取数据。 Go进程的stdin可以通过os包的Stdin获取。实际上,它是一个实现了Reader接口的File类型。从Reader读取非常容易。上述代码展示了从Stdin读取的三种常见方式。 第一个选项演示了fmt包的使用,该包提供了Scan、Scanf和Scanln函数。Scanf函数将输入读取到给定的变量中。Scanf的优点是可以确定扫描值的格式。Scan函数只是将输入读取到变量中(没有预定义的格式),而Scanln则像其名称一样,读取以换行符结束的输入。 Scanner是示例代码中显示的第二个选项,它提供了一种方便的扫描大量输入的方式。Scanner包含了Split函数,可以定义自定义的分割函数。例如,要从stdin扫描单词,可以使用bufio.ScanWords预定义的SplitFunc。 通过ReaderAPI进行读取是最后介绍的方法。这种方法可以更好地控制输入的读取方式。 正如前面的教程所述,每个进程都有stdin、stdout和stderr文件描述符。标准方法是使用stdout作为进程输出,stderr作为进程错误输出。由于这些是文件描述符,数据写入的目标可以是任何东西,从控制台到套接字。本教程将向您展示如何写入stdout和stderr。 与前面示例中的Stdin一样,Stdout和Stderr是文件描述符。因此,它们实现了Writer接口。 前面的示例展示了如何通过io.WriteString函数、WriterAPI的使用以及fmt包和FprintXX函数来写入这些内容的几种方法。 文件访问是一种非常常见的操作,用于存储或读取数据。本示例说明了如何使用标准库通过文件名和路径打开文件。 os包提供了一种简单的打开文件的方式。函数Open通过路径打开文件,只以只读模式打开。另一个函数OpenFile更强大,需要文件路径、标志和权限。 标志常量在os包中定义,可以使用二进制OR运算符|组合它们。权限由os包常量(例如os.ModePerm)或数字表示法(如0777,权限为-rwxrwxrwx)设置。 在前面的示例中,我们看到了从Stdin读取和打开文件。在本示例中,我们将稍微结合这两者,并展示如何将文件读取为字符串。 从文件中读取很简单,因为File类型实现了Reader和Writer接口。这样,所有适用于Reader接口的函数和方法都适用于File类型。前面的示例展示了如何使用Scanner读取文件并将内容写入字节缓冲区(这比字符串连接更高效)。这样,您可以控制从文件中读取的内容量。 第二种方法使用ioutil.ReadFile更简单,但应谨慎使用,因为它会读取整个文件。请记住,文件可能很大,可能会威胁应用程序的稳定性。 golang.org/x/text/encoding/charmap包包含代表广泛使用的字符集的Charmap类型指针常量。Charmap类型提供了为给定字符集创建编码器和解码器的方法。Encoder创建编码Writer,将写入的字节编码为所选字符集。类似地,Decoder可以创建解码Reader,从所选字符集解码所有读取的数据。 第二章,字符串和其他内容,还包含了编码/解码字符串到另一个字符集的教程从非Unicode字符集解码字符串。 在某些情况下,您需要从文件的特定位置读取或写入,例如索引文件。本教程将向您展示如何在平面文件操作的上下文中使用位置寻找。 前面的示例使用flatfile作为演示如何在文件中寻找、读取和写入的例子。通常,可以使用Seek方法来移动当前指针在File中的位置。它接受两个参数,即位置和如何计算位置,0-相对于文件原点,1-相对于当前位置,2-相对于文件末尾。这样,您可以在文件中移动光标。Seek方法在前面代码中的readLine函数的实现中使用。 flatfile是存储数据的最基本形式。记录结构具有固定长度,记录部分也是如此。示例中的平面文件结构是:ID-4个字符,FirstName-10个字符,LastName-10个字符。整个记录长度为24个字符,以换行符结束,即第25个字符。 os.File还包含ReadAt和WriteAt方法。这些方法消耗要写入/读取的字节和开始的偏移量。这简化了在文件中特定位置的写入和读取。 请注意,示例假定每个符文只有一个字节,这对于特殊字符等可能并不正确。 本教程描述了如何以二进制形式写入和读取任何类型。 可以使用encoding/binary包写入二进制数据。函数Write消耗应该写入数据的Writer,字节顺序(BigEndian/LittleEndian),最后是要写入Writer的值。 要类似地读取二进制数据,可以使用Read函数。请注意,从二进制源读取数据并没有什么神奇之处。您需要确定从Reader中获取的数据是什么。如果不确定,数据可能会被获取到适合大小的任何类型中。 当您需要将相同的输出写入多个目标时,内置包中提供了帮助。本教程展示了如何同时实现写入多个目标。 Hello,Goisawesome!工作原理...io包含MultiWriter函数,带有Writers的可变参数。当调用Writer上的Write方法时,数据将被写入所有底层的Writers。 进程之间的管道是使用第一个进程的输出作为其他进程的输入的简单方法。在Go中也可以使用相同的概念,例如,将数据从一个套接字传输到另一个套接字,创建隧道连接。本教程将向您展示如何使用Go内置库创建管道。 io.Pipe函数创建内存管道,并返回管道的两端,一端是PipeReader,另一端是PipeWriter。对PipeWriter的每次Write都会被阻塞,直到另一端的Read消耗。 该示例显示了从执行命令的输出到父程序的标准输出的管道输出。通过将pWriter分配给cmd.Stdout,子进程的标准输出被写入管道,goroutine中的io.Copy消耗写入的数据,将数据复制到os.Stdout。 除了众所周知的JSON和XML之外,Go还提供了二进制格式gob。本教程将介绍如何使用gob包的基本概念。 gob序列化和反序列化需要编码器和解码器。gob.NewEncoder函数创建具有底层Writer的Encoder。每次调用Encode方法都会将对象序列化为gob格式。gob格式本身是自描述的二进制格式。这意味着每个序列化的结构都以其描述为前缀。 要从序列化形式解码数据,必须通过调用gob.NewDecoder创建Decoder,并使用底层的Reader。然后,Decode接受应将数据反序列化到的结构的指针。 注意,gob格式不需要源和目标类型完全匹配。有关规则,请参考encoding/gob包。 ZIP压缩是一种广泛使用的压缩格式。通常使用ZIP格式来上传文件集或者导出压缩文件作为输出。本教程将向您展示如何使用标准库以编程方式处理ZIP文件。 内置包zip包含NewWriter和NewReader函数,用于创建zip.Writer以进行压缩,以及zip.Reader以进行解压缩。 ZIP文件的每个记录都是使用创建的zip.Writer的Create方法创建的。然后使用返回的Writer来写入内容主体。 要解压文件,使用OpenReader函数创建zipped文件中记录的ReadCloser。创建的ReaderCloser的File字段是zip.File指针的切片。通过调用Open方法并读取返回的ReadCloser来获取文件的内容。 只需在Create方法的文件名中添加斜杠即可创建文件夹。例如folder/newfile.txt。 XML是一种非常常见的数据交换格式。Go库包含对解析XML文件的支持,方式与JSON相同。通常,使用与XML方案对应的结构,并借助此帮助一次解析XML内容。问题在于当XML文件太大而无法放入内存时,因此需要分块解析文件。这个示例将揭示如何处理大型XML文件并解析所需的信息。 使用xml包的NewDecoder函数创建XML内容的Decoder。 通过在Decoder上调用Token方法,接收xml.Token。xml.Token是保存令牌类型的接口。可以根据类型定义代码的行为。示例代码测试解析的xml.StartElement是否是book元素之一。然后将数据部分解析为Book结构。这样,底层Decoder中的Reader中的指针位置将被结构数据移动,解析可以继续进行。 packagemainimport("encoding/json""fmt""strings")constjs=`[{"name":"Axel","lastname":"Fooley"},{"name":"Tim","lastname":"Burton"},{"name":"Tim","lastname":"Burton"`typeUserstruct{Namestring`json:"name"`LastNamestring`json:"lastname"`}funcmain(){userSlice:=make([]User,0)r:=strings.NewReader(js)dec:=json.NewDecoder(r)for{tok,err:=dec.Token()iferr!=nil{break}iftok==nil{break}switchtp:=tok.(type){casejson.Delim:str:=tp.String()ifstr==""||str=="{"{fordec.More(){u:=User{}err:=dec.Decode(&u)iferr==nil{userSlice=append(userSlice,u)}else{break}}}}}fmt.Println(userSlice)}![ 除了Unmarshall函数外,json包还包含DecoderAPI。使用NewDecoder可以创建Decoder。通过在解码器上调用Token方法,可以读取底层Reader并返回Token接口。这可以保存多个值。 其中之一是Delim类型,它是包含{、[、]、}中之一的rune。基于此,检测到JSON数组的开始。通过解码器上的More方法,可以检测到更多要解码的对象。 本章包含以下示例: 本章将引导您完成文件和目录中的典型操作。我们还将介绍如何获取用户主目录并为其创建临时文件。 检查Go是否已正确安装。第一章的准备就绪部分中的检索Golang版本示例将对您有所帮助。 如果您需要发现有关访问文件的基本信息,Go的标准库提供了一种方法来完成这个任务。本示例展示了如何访问这些信息。 os.File类型通过Stat方法提供对FileInfo类型的访问。FileInfo结构包含有关文件的所有基本信息。 临时文件通常在运行测试用例时使用,或者如果您的应用程序需要一个存储短期内容的地方,例如用户数据上传和当前处理的数据。本示例将介绍创建此类文件或目录的最简单方法。 ioutil包含TempFile和TempDir函数。TempFile函数消耗目录和文件前缀。返回具有底层临时文件的os.File。请注意,调用者负责清理文件。前面的示例使用os.Remove函数来清理文件。 TempDir函数的工作方式相同。不同之处在于返回包含目录路径的string。 临时file/dir名称由前缀和随机后缀组成。多个调用具有相同参数的TempFile/Dir函数的程序将不会获得相同的结果。 写入文件是每个程序员的基本任务;Go支持多种方法来完成这个任务。本示例将展示其中一些方法。 os.File类型实现了Writer接口,因此可以通过使用Writer接口的任何选项来写入文件。前面的示例使用了os.File类型的WriteString方法。通用的io.WriteString方法也可以使用。 本示例将向您展示如何安全地从多个goroutine写入文件。 并发写入文件是一个可能导致文件内容不一致的问题。最好通过使用Mutex或任何其他同步原语来同步对文件的写入。这样,您可以确保一次只有一个goroutine能够写入文件。 上述代码创建了一个带有Mutex的Writer,它嵌入了Writer(在本例中是os.File),对于每个Write调用,内部锁定Mutex以提供排他性。写操作完成后,Mutex原语会自然解锁。 这个示例将向您展示如何列出目录内容。 上面的示例中的文件夹列表使用了两种方法。第一种更简单的方法是使用listDirByReadDir函数,并利用ioutil包中的ReadDir函数。此函数返回表示实际目录内容的FileInfo结构的切片。请注意,ReadDir函数不会递归读取文件夹。实际上,ReadDir函数在内部使用os包中File类型的Readdir方法。 另一方面,更复杂的listDirByWalk使用filepath.Walk函数,该函数消耗要遍历的路径,并具有处理给定路径中的每个文件或文件夹的函数。主要区别在于Walk函数递归读取目录。这种方法的核心部分是WalkFunc类型,其函数是消耗列表的结果。请注意,该函数通过返回filepath.SkipDir错误来阻止基础文件夹上的递归调用。Walk函数还首先处理调用路径,因此您也需要处理这一点(在本例中,我们跳过打印并返回nil,因为我们需要递归处理此文件夹)。 这个示例说明了如何以编程方式更改文件权限。 os包中File类型的Chmod方法可用于更改文件权限。上面的示例只是创建文件并将权限更改为0777。 只需注意fi.Mode()被调用两次,因为它提取了文件当前状态的权限(os.FileMode)。 更改权限的最简单方法是使用os.Chmod函数,它执行相同的操作,但您不需要在代码中获取File类型。 这个示例描述了在代码中创建文件和目录的几种一般方法。 前面的示例代表了创建文件或目录的四种方法。os.Create函数是创建文件的最简单方法。使用此函数,您将以0666的权限创建文件。 如果需要使用任何其他权限配置创建文件,则应使用os包的OpenFile函数。 可以使用os包的Mkdir函数创建目录。这样,将创建具有给定权限的目录。第二个选项是使用MkdirAll函数。此函数还会创建目录,但如果给定路径包含不存在的目录,则会创建路径中的所有目录(它与Unix的mkdir实用程序的-p选项的工作方式相同)。 本教程向您展示了如何列出与给定模式匹配的文件路径。列表不必来自同一文件夹。 请注意,filepath.Glob的返回结果是与匹配路径对应的字符串切片。 本章的列出目录教程展示了更通用的方法,其中可以使用filepath.Walk函数来列出和过滤路径。 本教程为您提供了如何比较两个文件的提示。本教程将向您展示如何快速确定文件是否相同。本教程还将向您展示如何找到两者之间的差异。 可以通过几种方式来比较两个文件。本教程描述了两种基本方法。第一种方法是通过创建文件的校验和来比较整个文件。 第三章的生成校验和教程展示了如何创建文件的校验和。这种方式,getMD5SumString函数生成校验和字符串,它是MD5字节结果的十六进制表示。然后比较这些字符串。 第二种方法是逐行比较文件(在本例中是字符串内容)。如果行不匹配,则包括x标记。这是您可以比较二进制内容的方式,但您需要按字节块(字节切片)扫描文件。 os/user包包含Current函数,它提供os.User类型的指针。User包含HomeDir属性,其中包含当前用户主目录的路径。 请注意,这对于交叉编译的代码不起作用,因为实现取决于本机代码。 检查Go是否已正确安装。第一章中的准备就绪部分中的检索Golang版本示例,与环境交互,将有所帮助。验证是否有其他应用程序阻止了7070端口。 本示例解释了如何从可用的本地接口中检索IP地址。 net包包含Interfaces函数,它将网络接口列为Interface结构的切片。Interface结构具有Addrs方法,它列出可用的网络地址。这样,您可以按接口列出地址。 另一个选项是使用net包的InterfaceAddrs函数,它提供了实现Addr接口的结构体切片。这为您提供了获取所需信息的方法。 基于TCP的协议是网络通信中最重要的协议。作为提醒,HTTP、FTP、SMTP和其他协议都属于这一组。本示例让您了解如何一般连接到TCP服务器。 net包包含Dial函数,它消耗网络类型和地址。在前面的示例中,网络是tcp,地址是localhost:8080。 只是为了解释整个代码示例,使用了HTTP标准包中的HTTP服务器作为客户端的对应部分。这部分在另一个示例中有所涵盖。 这个教程将介绍如何将IP地址转换为主机地址,反之亦然。 从IP地址解析域名可以使用net包中的LookupAddr函数来完成。要从域名找出IP地址,应用LookupIP函数。 前面的教程连接到远程服务器让我们深入了解了如何在较低级别连接TCP服务器。在这个教程中,将展示如何在较高级别与HTTP服务器通信。 useRequest函数实现了相同的功能,但使用了更可定制的API和自己的Client实例。该实现利用NewRequest函数根据给定的参数创建请求:方法、URL和请求主体。内容类型必须单独设置到Header属性中。请求是通过Client上创建的Do方法执行的。 创建一个HTTP请求的教程将帮助您详细组装请求。 在许多情况下,最好使用方便的工具来操作URL,而不是试图将其作为简单的字符串处理。Go标准库自然包含了操作URL的工具。这个教程将介绍其中一些主要功能。 net/url包旨在帮助您操作和解析URL。URL结构包含了组合URL所需的字段。通过URL结构的String方法,可以轻松地将其转换为简单的字符串。 当字符串表示可用且需要额外操作时,可以利用net/url的Parse函数。这样,字符串可以转换为URL结构,并且可以修改底层URL。 这个教程将向您展示如何使用特定参数构造HTTP请求。 前面的示例描述了如何一般创建HTTP请求。本示例将详细介绍如何读取和写入请求头。 Header类型的Set方法设置给定键下的单项切片。另一方面,Add方法将值附加到切片。 使用Get方法将从给定键下的切片中检索第一个值。如果需要整个切片,则需要将Header处理为映射。可以使用Del方法删除整个头键。 在某些情况下,您需要更多控制重定向的处理方式。本示例将向您展示Go客户端实现的机制,以便您更多地控制处理HTTP重定向。 默认情况下,CheckRedirect属性为nil。在这种情况下,它最多有10次重定向。超过此计数后,重定向将停止。 RESTfulAPI是应用程序和服务器提供其服务访问的最常见方式。本示例将向您展示如何使用标准库中的HTTP客户端来消费它。 前面的示例代码显示了RESTAPI的样子以及如何使用它。请注意,decodeCity和decodeCities函数受益于请求的Body实现了Reader接口。结构的反序列化通过json.Decoder完成。 本教程将简要介绍如何使用标准库连接到SMTP服务器并发送电子邮件。 在本教程中,我们将使用谷歌Gmail账户发送电子邮件。通过一些配置,本教程也适用于其他SMTP服务器。 smtp包提供了与SMTP服务器交互的基本功能。Dial函数提供客户端。客户端最重要的方法是Mail,用于设置发件人邮件,Rcpt,用于设置收件人邮件,以及Data,提供Writer,用于写入邮件内容。最后,Quit方法发送QUIT并关闭与服务器的连接。 前面的示例使用了安全连接到SMTP服务器,因此客户端的Auth方法用于设置身份验证,并调用StartTLS方法以启动与服务器的安全连接。 请注意,Auth结构是通过smtp包的PlainAuth函数单独创建的。 本教程将说明如何使用标准库调用JSON-RPC协议的过程。 Go的标准库作为其内置包的一部分实现了JSON-RPC1.0。jsonrpc包实现了Dial函数,用于生成调用远程过程的客户端。客户端本身包含Call方法,接受过程调用、参数和结果存储的指针。 createServer将创建一个示例服务器来测试客户端调用。 HTTP协议可以用作JSON-RPC的传输层。net/rpc包包含DialHTTP函数,能够创建客户端并调用远程过程。 本章包含以下配方: 每个数据库服务器都有自己的特点,而且协议也不同。自然地,语言库内部与数据库通信必须定制以适用于特定协议。 Go标准库提供了用于与数据库服务器通信和操作的统一API。此API位于sql包中。要使用特定的数据库服务器,必须导入驱动程序。此驱动程序需要符合sql包的规范。这样,您将能够受益于统一的方法。在本章中,我们将描述数据库操作的基础知识、事务处理以及如何使用存储过程。请注意,我们将在PostgreSQL数据库上说明该方法,但这些方法适用于大多数其他数据库。 与数据库工作的关键部分是与数据库本身的连接。Go标准包仅涵盖了与数据库交互的抽象,必须使用第三方驱动程序。 在本配方中,我们将展示如何连接到PostgreSQL数据库。但是,这种方法适用于所有其他驱动程序实现了标准API的数据库。 通过在终端中调用goversion命令验证Go是否已正确安装。如果命令失败,请执行以下操作: 标准库包database/sql提供了Open函数,用于使用驱动程序名称和连接详细信息(在本例中为连接URL)初始化与数据库的连接。请注意,Open函数不会立即创建连接,可能只会验证传递给函数的参数。 可以通过返回的DB结构指针中可用的Ping方法验证与数据库的连接。 驱动程序本身在driver包的init函数中初始化。驱动程序通过sql包的Register函数向驱动程序名称注册自身。github.com/lib/pq驱动程序将自身注册为postgres。 驱动程序实现中的数据库连接可能被池化,并且可能从池中拉出的连接已经断开。本配方将展示如何验证连接是否仍然有效。 通过在终端中调用goversion命令验证Go是否已正确安装。如果命令失败,请按照本章第一个配方中的准备就绪部分进行操作。 如前一篇中提到的连接数据库,Open函数可能只是验证连接细节,但不一定立即连接数据库。实际连接到数据库通常是延迟加载的,并且是通过对数据库的第一次语句执行创建的。 DB结构的指针提供了Ping方法,通常对数据库进行幂等调用。Ping方法的变体是PingContext,它只是添加了取消或超时数据库调用的能力。请注意,如果Ping函数失败,连接将从数据库池中移除。 DB结构的指针还提供了Conn方法,用于从数据库池中检索连接。通过使用连接,您实际上保证使用相同的数据库会话。同样,DB结构的指针包含PingContext方法,Conn指针提供了PingContext方法来检查连接是否仍然活动。 在以前的示例中,我们已经学习了如何连接和验证与数据库的连接。本示例将描述如何执行针对数据库的语句。 通过在终端中调用goversion命令来验证Go是否已正确安装。如果命令失败,请按照本章第一篇中的准备工作部分进行操作。 按照本章第一篇中的说明设置PostgreSQL服务器。 通常,我们可以执行两种类型的语句。对于第一种类型的语句,我们不期望任何行作为结果,最终我们得到的是没有输出或者只是受影响的行数。这种类型的语句通过DB结构指针上的Exec方法执行。在前面的示例代码中,我们有TRUNCATE和INSERT语句。但是这种方式也可以执行DDL和DCL语句。 有四种主要的语句类别: 第二种类型是我们期望以行的形式得到结果的语句;这些通常被称为查询。这种类型的语句通常通过Query或QueryContext方法执行。 准备好的语句带来了安全性、效率和便利性。当然,可以使用它们与Go标准库一起使用;本示例将展示如何使用。 要创建准备好的语句,需要调用指向DB结构的Prepare方法。之后,使用给定的参数调用Stmt指针上的Exec或Query方法。 准备好的语句是在DB指针的范围内创建的,但是在连接池中的特定连接上。语句记住了使用过的连接,并且在调用时尝试使用相同的连接。如果连接忙或已关闭,则重新创建准备好的语句并在新连接上调用语句。 请注意,事务中准备的语句不能与DB指针一起使用,反之亦然。 通常,准备好的语句的工作方式是在数据库端创建语句。数据库返回准备好的语句的标识符。准备好的语句在以下调用期间执行,并且只提供语句的参数。 通过在终端中调用goversion命令验证Go是否已正确安装。如果命令失败,请按照本章第一个配方中的准备工作部分进行操作。 按照本章第一个配方中提到的方式设置PostgreSQL服务器。 database/sql包提供了取消挂起语句的可能性。DB结构指针的所有名为XXXContext的方法都会消耗上下文,并且可以取消挂起的语句。 只有在驱动程序支持Context变体时才能取消语句。如果不支持,将执行不带Context的变体。 使用Context变体和context.WithTimeout,您可以创建语句调用的超时。 请注意,示例代码执行以错误pq:cancelingstatementduetouserrequest结束,这与调用查询后立即调用的CancelFunc相对应。 DB结构指针的Query和QueryContext方法会导致Rows结构指针。Rows指针提供Columns和ColumnTypes方法,其中包含有关返回结果集结构的信息。 Columns方法返回带有列名的字符串切片。 ColumnTypes方法返回ColumnType指针的切片,其中包含有关返回结果集的更丰富信息。上述代码打印出了ColumnType指针公开的详细信息。 在与数据库交互时,基本部分是通过执行查询来提取数据。本配方将说明使用标准库database/sql包时如何执行此操作。 验证Go是否已正确安装,通过在终端中调用goversion命令。如果命令失败,请按照本章第一个配方中的准备工作部分进行操作。 按照本章第一个配方中的说明设置PostgreSQL服务器。 来自指向DB结构的Query方法的Rows指针提供了从结果集中读取和提取数据的方法。 请注意,首先应调用Next方法将光标移动到下一个结果行。Next方法如果有其他行则返回true,否则返回false。 在通过Next获取新行后,可以调用Scan方法将数据提取到变量中。变量的数量必须与SELECT中的列数匹配,否则Scan方法无法提取数据。 代码的重要部分是,在每次调用Next方法后,应调用Err方法来查找在读取下一行时是否出现错误。 上述示例故意对第二条记录使用了NULL值。NULL数据库值无法提取到不可为空类型,例如string,在这种情况下,必须使用NullString类型。 为了完整起见,示例代码涵盖了QueryRow方法,它与Query方法略有不同。这个方法返回指向Row结构的指针,该结构仅提供Scan方法。请注意,只有在调用Scan方法之后才能检测到没有行的情况。 有时查询结果或表的结构不清晰,需要将结果提取到某种灵活的结构中。这就引出了这个配方,其中将介绍将值提取到与列名映射的灵活结构中。 请注意,上述代码表示了两种方法。parseWithRawBytes函数使用了首选方法,但它高度依赖于驱动程序的实现。它的工作方式是创建与结果中列数相同长度的RawBytes切片。因为Scan函数需要值的指针,所以我们需要创建指向RawBytes切片(字节切片的切片)的指针切片,然后将其传递给Scan函数。 提取成功后,我们只需重新映射值。在示例代码中,我们将其转换为string,因为如果RawBytes是目标,驱动程序使用string类型来存储值。请注意,存储值的形式取决于驱动程序的实现。 第二种方法parseToMap在第一种方法不起作用的情况下是可用的。它几乎使用相同的方法,但值的切片被定义为空接口的切片。这种方法依赖于驱动程序。驱动程序应确定要分配给值指针的默认类型。 事务控制是在处理数据库时需要牢记的常见事情。本配方将向您展示如何使用sql包处理事务。 通过在终端中调用goversion命令来验证Go是否已正确安装。如果命令失败,请按照本章第一个配方中的准备工作部分进行操作。 设置PostgreSQL服务器,如本章第一个配方中所述。 正如前面的代码所示,事务处理非常简单。DB结构指针的Begin方法创建具有默认隔离级别的事务(取决于驱动程序)。事务本质上保留在单个连接上,并由返回的Tx结构指针表示。 指针Tx实现了DB结构指针可用的所有方法;唯一的例外是所有操作都在事务中完成(如果数据库能够在事务中处理语句)。通过在Tx结构指针上调用Rollback或Commit方法结束事务。在此调用之后,事务结束,其他操作将以错误ErrTxDone结束。 DB结构指针上还有一个有用的方法叫做BeginTx,它创建了事务Tx结构指针,同时也增强了给定的上下文。如果上下文被取消,事务将被回滚(进一步的Commit调用将导致错误)。BeginTx还消耗了TxOptions指针,这是可选的,可以定义隔离级别。 处理存储过程和函数总是比通常的语句更复杂,特别是如果过程包含自定义类型。标准库提供了处理这些的API,但存储过程调用的支持程度取决于驱动程序的实现。本配方将展示一个非常简单的函数/过程调用。 存储过程的调用高度依赖于驱动程序和数据库。请注意,在PostgreSQL数据库上检索结果与查询表非常相似。调用DB结构指针的Query或QueryRow方法,可以解析出结果行或行指针以获取值。 如果需要调用存储过程,MySQL驱动程序将使用CALL语句。 几乎所有驱动程序的一般问题都是存储过程的OUTPUT参数。Go1.9增加了对这些参数的支持,但常用数据库的大多数驱动程序尚未实现这一功能。因此,解决方案可能是使用具有非标准API的驱动程序。 OUTPUT参数应该工作的方式是,存储过程调用将使用database/sql包中Named函数的NamedArg参数类型。NamedArg结构体的Value字段应该是Out类型,其中包含Dest字段,用于存放OUTPUT参数的实际值。 本章涵盖了从实现简单的TCP和UDP服务器到启动HTTP服务器的主题。这些配方将引导您从处理HTTP请求、提供静态内容,到提供安全的HTTP内容。 检查Go是否已正确安装。第一章的准备就绪部分中的检索Golang版本配方将有所帮助。 确保端口8080和7070没有被其他应用程序使用。 在连接网络章节中,介绍了TCP连接的客户端部分。在本配方中,将描述服务器端。 可以使用net包创建TCP服务器。net包包含Listen函数,用于创建TCPListener,可以Accept客户端连接。Accept方法调用TCPListener上的方法,直到接收到客户端连接。如果客户端连接成功,Accept方法会返回TCPConn连接。TCPConn是连接到客户端的连接,用于读取和写入数据。 TCPConn实现了Reader和Writer接口。可以使用所有写入和读取数据的方法。请注意,读取数据时有一个分隔符字符,否则,如果客户端强制关闭连接,则会收到EOF。 请注意,此实现一次只能处理一个客户端。 用户数据报协议(UDP)是互联网的基本协议之一。本篇将向您展示如何监听UDP数据包并读取内容。 与TCP服务器一样,可以使用net包创建UDP服务器。使用ListenPacket函数创建PacketConn。 PacketConn不像TCPConn那样实现Reader和Writer接口。要读取接收到的数据包,应该使用ReadFrom方法。ReadFrom方法会阻塞,直到接收到数据包。然后返回客户端的Addr(记住UDP不是基于连接的)。要响应客户端,可以使用PacketConn的WriteTo方法;这会消耗消息和Addr,在这种情况下是客户端的Addr。 前面的配方展示了如何创建UDP和TCP服务器。示例代码尚未准备好同时处理多个客户端。在本配方中,我们将介绍如何同时处理更多客户端。 服务器运行的终端中的输出: TCP服务器的实现与本章的前一个配方创建TCP服务器相同。实现已增强,具有同时处理多个客户端的能力。请注意,我们现在在单独的goroutine中处理接受的连接。这意味着服务器可以继续使用Accept方法接受客户端连接。 因为UDP协议不是有状态的,也不保持任何连接,所以处理多个客户端的工作被移动到应用程序逻辑中,您需要识别客户端和数据包序列。只有向客户端写入响应才能使用goroutines并行化。 在Go中创建HTTP服务器非常容易,标准库提供了更多的方法来实现。让我们看看最基本的方法。 如果使用Server的Serve方法,则必须提供Listener。 应用程序通常使用URL路径和HTTP方法来定义应用程序的行为。本配方将说明如何利用标准库来处理不同的URL和方法。 参见前面的示例,了解如何使用这些。Handler接口和HandlerFunc需要实现带有请求和响应参数的函数。这样你就可以访问这两个结构。请求本身可以访问Headers、HTTP方法和其他请求参数。 具有WebUI或RESTAPI的现代应用程序通常使用中间件机制来记录活动或保护给定接口的安全性。在本示例中,将介绍实现这种中间件层。 在前面的示例中,中间件的实现利用了Golang的函数作为一等公民功能。原始的HandlerFunc被包装成检查X-Auth头的HandlerFunc。然后使用Secure函数来保护HandlerFunc,并在ServeMux的HandleFunc方法中使用。 几乎任何Web应用程序都需要提供静态文件。使用标准库可以轻松实现JavaScript文件、静态HTML页面或CSS样式表的提供。本示例将展示如何实现。 FileServer函数创建整个消耗FileSystem参数的Handler。前面的示例使用了Dir类型,它实现了FileSystem接口。FileSystem接口需要实现Open方法,该方法消耗字符串并返回给定路径的实际File。 对于某些目的,不需要使用所有JavaScript创建高度动态的WebUI,生成内容的静态内容可能已经足够。Go标准库提供了一种构建动态生成内容的方法。本示例将引导您进入Go标准库模板化。 Go标准库还包含用于模板化内容的包。html/template和text/template包提供了解析模板和使用它们创建输出的函数。解析是使用ParseXXX函数或新创建的Template结构指针的方法完成的。前面的示例使用了html/template包的ParseFiles函数。 模板本身是基于文本的文档或包含动态变量的文本片段。模板的使用基于将模板文本与包含模板中的变量值的结构进行合并。为了将模板与这些结构进行合并,有Execute和ExecuteTemplate方法。请注意,这些方法使用写入器接口,其中写入输出;在这种情况下使用ResponseWriter。 模板语法和特性在文档中有很好的解释。 重定向是告诉客户端内容已经移动或需要在其他地方完成请求的常用方式。本教程描述了如何使用标准库实现重定向。 第二种方法是使用Redirect函数,它可以为您执行重定向。该函数接受ResponseWriter、请求指针和与RequestHandler相同的URL和状态码,这些将发送给客户端。 重定向也可以通过手动设置Location头并编写适当的状态码来完成。Go库使开发人员能够轻松使用这一功能。 Cookies提供了一种在客户端方便地存储数据的方式。本教程演示了如何使用标准库设置、检索和删除cookies。 可以从Request结构中检索cookie值。具有名称参数的Cookie方法返回指向Cookie的指针,如果请求中存在cookie。 要列出请求中的所有cookie,可以调用Cookies方法。此方法返回Cookie结构指针的切片。 为了让客户端知道应该删除cookie,可以检索具有给定名称的Cookie,并将MaxAge字段设置为负值。请注意,这不是Go的特性,而是客户端应该工作的方式。 通过调用Shutdown方法,Server开始拒绝新连接并关闭打开的监听器和空闲连接。然后它无限期地等待已经挂起的连接,直到这些连接变为空闲。在所有连接关闭后,服务器关闭。请注意,Shutdown方法会消耗Context。如果提供的Context在关闭之前过期,则会返回来自Context的错误,并且Shutdown不再阻塞。 这个示例描述了创建HTTP服务器的最简单方式,它通过TLS/SSL层提供内容。 准备私钥和自签名的X-509证书。为此,可以使用OpenSSL实用程序。通过执行命令opensslgenrsa-outserver.key2048,使用RSA算法生成私钥到文件server.key。基于此私钥,可以通过调用opensslreq-new-x509-sha256-keyserver.key-outserver.crt-days365生成X-509证书。创建server.crt文件。 HTTP的POST表单是向服务器传递信息的一种常见方式,以结构化的方式。这个示例展示了如何在服务器端解析和访问这些信息。 如果只需要访问POST表单中的参数,可以使用Request的PostForm字段。这个字段只保留了POST主体中的参数。 并发行为的编程总是很困难的。Go具有非常好的机制来管理并发,如通道。除了通道作为同步机制外,Go标准库还提供了处理更传统核心方式的并发部分的包。本章描述了如何利用sync包来实现常见的同步任务。最后一个教程将展示如何简化一组goroutines的错误传播。 检查Go是否已正确安装。第一章的检索Golang版本教程中的准备就绪部分将对你有所帮助。 如果代码使用并发访问被认为对并发使用不安全的任何资源,就需要实现同步机制来保护访问。除了使用通道,还可以利用互斥锁来实现这一目的。这个教程将向你展示如何做到这一点。 同步原语Mutex由sync包提供。Mutex作为一个锁,用于保护部分或资源。一旦goroutine在Mutex上调用Lock并且Mutex处于未锁定状态,Mutex就会被锁定,goroutine就可以独占地访问临界区。如果Mutex处于锁定状态,goroutine调用Lock方法。这个goroutine会被阻塞,需要等待Mutex再次解锁。 请注意,在示例中,我们使用Mutex来同步对切片原语的访问,这被认为是不安全的并发使用。 重要的事实是Mutex在第一次使用后不能被复制。 在Golang中,map原语应被视为不安全的并发访问。在上一个教程中,我们描述了如何使用Mutex同步对资源的访问,这也可以用于对map原语的访问。但是Go标准库还提供了专为并发访问设计的map结构。这个教程将说明如何使用它。 sync包中包含了Map结构,该结构被设计用于从多个Go例程中并发使用。Map结构及其方法模仿了map原语的行为。Store方法相当于m[key]=val语句。Load方法相当于val,ok:=m[key],Range方法提供了遍历map的能力。请注意,Range函数与Map的当前状态一起工作,因此如果在运行Range方法期间更改了值,则会反映这些更改,但前提是该键尚未被访问。Range函数只会访问其键一次。 在多个goroutine运行相同代码的情况下,例如,有一个初始化共享资源的代码块,Go标准库提供了解决方案,将在下文中描述。 示例代码说明了在访问容器结构时数据的延迟加载。由于数据只应加载一次,因此在Pop方法中使用了sync包中的Once结构。Once只实现了一个名为Do的方法,该方法消耗了一个无参数的func,并且该函数在每个Once实例的执行期间只执行一次。 Do方法调用会阻塞,直到第一次运行完成。这一事实与Once旨在用于初始化的事实相对应。 资源池是提高性能和节省资源的传统方式。通常,值得使用昂贵初始化的资源进行池化。Go标准库提供了用于资源池的骨架结构,被认为对多个goroutine访问是安全的。本示例描述了如何使用它。 sync包包含了用于池化资源的结构。Pool结构具有Get和Put方法,用于检索资源并将其放回池中。Pool结构被认为对并发访问是安全的。 在创建Pool结构时,需要设置New字段。New字段是一个无参数函数,应该返回指向池化项目的指针。如果需要初始化池中的新对象,则会调用此函数。 从前面示例的日志中可以看出,Worker在返回到池中时被重用。重要的事实是,不应该对Get检索的项目和Put方法返回的项目做任何假设(比如我刚刚把三个对象放到池中,所以至少会有三个可用)。这主要是因为Pool中的空闲项目可能随时被自动删除。 如果资源初始化很昂贵,资源池化通常是值得的。然而,资源的管理也带来了一些额外的成本。 在处理并发运行的代码分支时,程序在某个时刻需要等待并发运行的代码部分。本示例介绍了如何使用WaitGroup等待运行的goroutine。 通过sync包中的WaitGroup结构,程序可以等待有限数量的goroutine完成运行。WaitGroup结构实现了Add方法,用于添加要等待的goroutine数量。然后在goroutine完成后,应调用Done方法来减少要等待的goroutine数量。Wait方法被调用时会阻塞,直到完成给定数量的Done调用(通常在goroutine结束时)。WaitGroup应该与sync包中的所有同步原语一样使用。在创建对象后,结构不应被复制。 上述代码提出了执行多个任务并输出一些结果的解决方案,我们只需要最快的一个。解决方案使用Context和取消函数来在获得第一个结果后调用取消。SearchSrc结构提供了Search方法,该方法会导致写入结果的通道。请注意,Search方法使用time.Sleep函数模拟延迟。对于来自Search方法的每个通道,合并函数触发写入最终输出通道的goroutine,该通道在main方法中读取。从merge函数产生的输出通道接收到第一个结果时,将调用存储在变量cancel中的CancelFunc来取消其余处理。 请注意,Search方法仍然需要结束,即使其结果不会被处理;因此,需要处理以避免goroutine和通道泄漏。 本教程将展示如何轻松使用errgroup扩展包来检测goroutine组中运行子任务的错误。 golang.org/x/sync/errgroup包有助于简化goroutine组的错误传播和上下文取消。Group包含消耗无参数函数返回error的Go方法。此函数应包含应由执行的goroutine完成的任务。errgroup的Group的Wait方法等待直到Go方法中执行的所有任务完成,如果其中任何一个返回err,则返回第一个非空错误。这样,就可以简单地从运行的goroutine组中传播错误。 请注意,Group也是使用上下文创建的。Context用作取消其他任务的机制,如果发生错误。在goroutine函数返回error后,内部实现会取消上下文,因此正在运行的任务也可能会被取消。 本章将涵盖以下示例: 检查Go是否已正确安装。第一章中准备就绪部分的检索Golang版本示例,与环境交互将帮助您。 确保端口8080未被其他应用程序使用。 除了使用log包中的默认记录器进行记录外,标准库还提供了一种根据应用程序或包的需求创建自定义记录器的方法。本示例将简要介绍如何创建自定义记录器。 log包提供了New函数,简化了自定义记录器的创建。New函数接受Writer作为参数,该参数可以是实现Writer接口的任何对象,以及以字符串形式的前缀和由标志组成的日志消息的形式。最后一个参数是最有趣的,因为通过它,您可以使用动态字段增强日志消息,例如日期和文件名。 测试和基准测试自然属于软件开发。作为一种现代语言,Go支持从头开始进行这些操作。在这个示例中,将描述测试的基础知识。 标准库的testing包提供了对代码测试需求的支持。test函数需要满足名称模式TestXXX。默认情况下,测试工具会查找名为xxx_test.go的文件。请注意,每个测试函数都需要接受T指针参数,该参数提供了用于测试控制的有用方法。通过T结构指针,可以设置测试的状态。例如,Fail和FailNow方法会导致测试失败。借助T结构指针的帮助,可以通过调用Skip、Skipf或SkipNow来跳过测试。 T指针的有趣方法是Helper方法。通过调用Helper方法,当前函数被标记为辅助函数,如果在该函数内调用FailNow(Fatal),则测试输出将指向测试中调用该函数的代码行,如前面示例代码中所示。 请注意,如果测试工具未以详细模式运行(使用-v标志),或者特定测试失败(仅适用于T测试),则Log方法(及其变体)将不可见。尝试在不使用-v标志的情况下运行此示例代码。 上一个示例介绍了测试包的测试部分,在本示例中将介绍基准测试的基础知识。 除了纯测试支持外,测试包还提供了用于测量代码性能的机制。为此,使用B结构指针作为参数,并且测试文件中的基准测试函数命名为BenchmarkXXXX。 基准测试函数的关键部分是操作定时器和使用循环迭代计数器N。 如您所见,定时器通过Reset/Start/StopTimer方法进行操作。通过这些方法,基准测试的结果会受到影响。请注意,定时器在基准测试函数开始时开始运行,而ResetTimer函数只是重新启动它。 在某些情况下,有用的是创建一组可能具有类似设置或清理代码的测试。这可以在没有为每个测试创建单独函数的情况下完成。 testing包的T结构还提供了Run方法,可用于运行嵌套测试。Run方法需要子测试的名称和将要执行的测试函数。例如,使用表驱动测试时,这种方法可能很有益。代码示例只是使用int值的简单切片作为输入。 基准测试结构B也包含相同的方法Run,可以提供一种创建复杂基准测试后续步骤的方法。 Go语言允许给结构化字段打标签,附加额外信息。这些信息通常用作编码器的附加信息,或者对结构体进行任何类型的额外处理。这个示例将向你展示如何访问这些信息。 可以使用reflect包提取struct标签。通过调用TypeOf,我们得到了Person的指针Type,随后通过调用Elem,我们得到了指针指向的值的Type。 结果的Type让我们可以访问struct类型Person及其字段。通过遍历字段并调用Field方法检索字段,我们可以获得StructField。StructField类型包含Tag字段,该字段提供对struct标签的访问。然后,StructTag字段上的Get方法返回特定的标签。 数据排序是一个非常常见的任务。Go标准库通过sort包简化了排序。这个示例简要介绍了如何使用它。 示例代码展示了如何舒适地使用sort包对切片进行排序的两种方式。第一种方法更加临时,它使用了sort包的Slice函数。Slice函数消耗要排序的切片和所谓的less函数,该函数定义了元素i是否应该在元素j之前排序。 第二种方法需要更多的代码和提前规划。它利用了sort包的Interface接口。该接口充当数据的代表,并要求其在排序数据上实现必要的方法:Len(定义数据的数量)、Less(less函数)、Swap(调用以交换元素)。如果数据值实现了这个接口,那么可以使用sort包的Sort函数。 原始类型切片float64、int和string在sort包中有涵盖。因此,可以使用现有的实现。例如,要对字符串切片进行排序,可以调用Strings函数。 这个示例提供了关于如何将HTTP处理程序分离成模块的建议。 为了将处理程序分离成模块,代码使用了ServeMux来为每个模块(rest和ui)进行处理。给定模块的URL处理是相对定义的。这意味着如果Handler的最终URL应该是/api/users,那么模块内定义的路径将是/users。模块本身将设置为/api/URL。 通过利用StripPrefix函数将模块插入到名为mainMux的主ServeMux指针中,模块被插入到主ServeMux中。例如,通过StripPrefix("/api",restModule())将由restModule函数创建的REST模块插入到主ServeMux中。然后模块内的处理URL将是/users,而不是/api/users。 HTTP/2规范为服务器提供了在被请求之前推送资源的能力。本示例演示了如何实现服务器推送。 准备私钥和自签名X-509证书。为此,可以使用openssl实用程序。通过执行命令opensslgenrsa-outserver.key2048,使用RSA算法生成私钥文件server.key。基于此私钥,可以通过调用opensslreq-new-x509-sha256-keyserver.key-outserver.crt-days365生成X-509证书。创建了server.crt文件。 首先,注意HTTP/2需要安全连接。服务器推送非常简单实现。自Go1.8以来,HTTP包提供了Pusher接口,可以在资源被请求之前用于Push资产。如果客户端(通常是浏览器)支持HTTP/2协议并且与服务器的握手成功,Handler或HandlerFunc中的ResponseWriter可以转换为Pusher。Pusher只提供Push方法。Push方法消耗目标(可以是绝对路径或绝对URL)到资源和PushOptions,可以提供额外选项(默认情况下可以使用nil)。 在上面的示例中,查看浏览器中开发者工具的输出。推送的资源在Initiator列中具有值Push。