Go-编程实用手册(全)

Go 编程实用手册(全)

原文:zh.annas-archive.org/md5/62FC08F1461495F0676A88A03EA0ECBA

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

本书将通过解决开发人员常见的问题来帮助您学习 Go 编程语言。您将首先安装 Go 二进制文件,并熟悉开发应用程序所需的工具。然后,您将操作字符串,并将它们用于内置结构和内置函数构造,以从两个浮点值创建复杂值。之后,您将学习如何对日期和时间执行算术运算,以及如何从字符串值中解析它们。

无论您是专业程序员还是新手,您都将学习如何在 Go 中编程各种解决方案,这将使您在掌握 Go 方面更上一层楼。这些示例涵盖了 Go 中的并发,执行各种 Web 编程任务,进行系统编程,读写文件以及许多基本的 Go 编程技能,如正确的错误处理和日志记录。

这本书适合谁

本书适合对学习 Go 语言感兴趣的软件开发人员,以及希望通过实际的代码示例进一步学习的程序员。

本书涵盖了什么

第一章《Go 入门》解决了新的 Go 开发人员以及使用其他语言的人在日常编程中面临的最常见问题。

第二章《操作字符串值》包含一些操作字符串值的示例,例如修剪字符串开头和结尾的空格,提取子字符串,替换字符串的部分,转义字符串值和大写字符串值。

第三章《类型转换》通过一些实际示例带您了解如何轻松地将一种类型转换为另一种类型。

第四章《日期和时间》解释了如何在 Go 编程语言中处理日期和时间。

第五章《映射和数组》介绍了如何在 Go 中使用映射和数组。

第六章《错误和日志》讨论了如何处理错误,并在需要时返回错误。

第七章《文件和目录》提供了在 Go 中处理文件和目录的示例。

第八章《并发》解释了如何在 Go 语言中使用并发构造。

第九章《系统编程》涵盖了如何使用 Go 处理命令行参数。

第十章《Web 编程》包含有效的示例,涉及与互联网的交互,如下载网页,创建自己的示例 Web 服务器和处理 HTTP 请求。

第十一章《关系数据库》解释了如何使用 Go 在关系数据库上读取、更新、删除和创建数据。

充分利用本书

为了更轻松地跟随本书,建议读者应该对软件开发有扎实的知识。读者应该具备基本的编程语言知识,以及对 Go 语言的概述。您将了解更多关于本书中使用的 GoLand IDE。

下载示例代码文件

您可以从www.packtpub.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support并注册,以便直接将文件发送到您的邮箱。

您可以按照以下步骤下载代码文件:

  1. www.packtpub.com上登录或注册。

  2. 选择“支持”选项卡。

  3. 单击“代码下载和勘误”。

  4. 在搜索框中输入书名,然后按照屏幕上的说明操作。

下载文件后,请确保使用最新版本的以下软件解压或提取文件夹:

  • WinRAR/7-Zip for Windows

  • Zipeg/iZip/UnRarX for Mac

  • 7-Zip/PeaZip for Linux

该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Hands-On-Go-Programming。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还有其他代码包,来自我们丰富的图书和视频目录,可在github.com/PacktPublishing/上找到。去看看吧!

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。例如:“我将使用touch命令来创建main.go文件。”

代码块设置如下:

package main
import "fmt"
func main(){
 fmt.Println(a:"Hello World")
}

任何命令行输入或输出都以以下方式书写:

$go run main.go

粗体:表示新术语、重要词汇或屏幕上看到的词语。例如,菜单或对话框中的词语会在文本中以这种方式出现。例如:“您可以运行它,然后不断点击“继续”,直到安装完成。”

警告或重要提示会以这种方式出现。

技巧和窍门会以这种方式出现。

第一章:开始使用 Go

本书将帮助您在 Go 编程之旅中,特别是在您开始积极开发 Go 应用程序时。本章节解决了新的 Go 开发人员以及与其他语言一起工作的人在日常编程中面临的最常见问题。希望您喜欢这本书并且觉得它有用。

我们将涵盖以下主题:

  • 安装 Go 二进制文件

  • 快速了解 Go 语言

安装 Go 二进制文件

让我们开始使用 Go。在本节中,我们将学习如何安装 Go 二进制文件,并简要了解 Go 语言。

要安装 Go 二进制文件,首先要做的是转到以下链接:golang.org/doc/install;您也可以直接在 Google 中搜索并访问它。您将找到一个逐步指南,介绍如何在不同平台和操作系统上安装 Go。如果您点击“下载 Go”,它将带您到下载页面,在那里您可以找到各种二进制格式。

您将在以下截图中找到 Windows、Linux 和 macOS 的 MSI:

我将使用 macOS,但您也会发现其他平台的步骤类似。让我们继续进行下载。

安装程序基本上是一个逐步向导;您只需运行它并不断点击“继续”,直到安装完成。安装完成后,您可能还需要做一件事,那就是设置您的环境变量。此外,您还需要设置您的工作区。您将有三个文件夹,binpkgsrc,如下截图所示:

src文件夹是您放置源文件的地方,pkg文件夹是 Go 存储对象文件的地方,bin文件夹是存储二进制文件(实际可执行文件)的地方。接下来,我将使用我的 shell,并且您需要使用export来设置一些环境变量。您还可以使用配置文件来设置您的环境变量。如果您查看下面的截图,您可以看到路径$PATH:/usr/local/go/bin,这就是我的 Go 二进制文件所在的位置:

因此,当我运行Go命令时,它将自动找到 Go 程序的位置。

接下来,我们设置GOPATHGOPATH基本上是您的 Go 工作区所在的位置。如果您记得的话,工作区包含三个文件夹,pkgsrcbinGoProject是该结构的父文件夹。最后是$GOPATH/bin,当您希望终端找到已安装的 Go 二进制文件时使用。只需确保在重新启动终端之前添加这三个内容并保存此文件。然后,您就可以开始使用 Go 了!

您还可以通过点击链接找出如何设置 Go 路径的环境变量,如下截图所示,该链接位于同一页面上:

您将找到不同操作系统的说明。例如,对于基于 Unix 的系统,您可以使用~/.bash_profile,或者根据您使用的 shell 不同,您可以使用各种配置文件。在我的系统中,我使用的是如下截图中所见的配置文件:

对于 Windows,在安装完成后,一旦您有了 Go 工作区,按照给定的说明进行操作,您就可以开始使用 Go 了。说明将如下截图所示:

测试您是否已安装 Go 的最快方法就是输入go,如下截图所示:

它将带出帮助教程,您可以通过使用 Go 版本查看可用命令和您拥有的代码版本。

这就是您如何轻松设置 Go 环境。在下一节中,我们将快速了解 Go 语言本身。

快速了解 Go 语言

在本节中,我们将快速了解 Go 编程语言。 Go 是一种表达力强,简洁,干净的语言;它具有并发机制,这有助于程序员编写能充分利用多核和网络机器的程序。它还可以快速编译为机器代码,并具有垃圾回收的便利性和运行时反射的强大性。它是一种静态类型的编译语言,但对大多数人来说,它感觉像是一种动态类型和解释语言。好了!让我们通过导航到tour.golang.org/welcome/1来查看 Go 的语法;这对于想要学习 Go 语法的人来说是一个很好的起点:

好的,所以如果您看一下截图中的语法,如果您来自诸如 Java 和 C#,或 C 和 C++之类的语言,您可能会发现语法有点不同。例如,如果您看一下返回类型,您实际上是在函数的末尾定义返回类型,而不是定义类型。我们还有一个主函数,这是我们应用程序的入口点,类似于许多其他编程语言,如果您看一下下面截图中显示的上下文,您会发现我们有包、变量和函数,以及流程控制语句:forif...else,以及structslicesmaps等类型:

如果您想创建一个类,比如结构,您可以使用结构类型并将其与指针结合。此外,它具有方法和接口以及并发性,但它没有泛型。

说到这一点,我还将谈论我将在整本书中使用的工具。GoLand中有几个可用的工具。 GoLand 是 JetBrains 推出的一个相对较新的 IDE。我们将在整本书中使用 GoLand。您可以轻松创建新项目并为其命名,并选择 SDK,即 Go 1.9。您还可以添加新文件或新包等。

您可以通过输入您的入口文件来定义您的配置并构建您的 Go,如下截图所示。然后,您可以运行main.go并单击 OK:

最后,按下Ctrl + r将构建您的项目,如下截图所示:

在我结束本章之前,让我快速向您展示一个仅使用终端的示例。我将使用touch命令创建main.go文件并添加以下代码:

package main
import "fmt"
func main(){
 fmt.Println(a:"Hello World")
}

您可以使用go run main.go命令运行它,然后您将获得以下输出:

您可以保存它,然后运行它。因此,这就是您如何使用终端快速编写 Go 代码并运行它。

摘要

在本章中,我们学习了如何安装 Go 二进制文件,并简要了解了 Go 语言。我们学会了如何编写 Go 代码并仅使用终端运行它。我们还看了将在所有章节中使用的工具以及可以用来开发 Go 应用程序的其他工具。我们现在准备继续下一章,在那里我们将看到一些操作字符串值的示例。

第二章:操作字符串值

现在我们已经了解了这本书将带领我们完成的内容概述。我们知道如何安装 Go 二进制文件,编写 Go 代码并使用终端运行它。在本章中,我们将学习一些操作字符串值的技巧,比如从字符串的开头和结尾修剪空格,提取子字符串,替换字符串的部分,转义字符串值中的字符,以及将字符串值大写。

从字符串的开头和结尾修剪空格

让我们从字符串的开头和结尾修剪空格开始。有许多原因你可能想要从字符串的开头和结尾删除空格;例如,如果你接受一些值,比如名字,你通常不需要在字符串值的末尾或开头有任何空格。

所以,让我们继续进行我们的项目,并看看我们如何在 Go 语言中进行这个过程。所以,你必须为修剪空格添加一个新项目,并有一个我们将放置我们的代码的main.go文件,然后我们只需要运行它;你的屏幕应该看起来像这样:

首先,让我们想象一下,我们有一个字符串变量,其中有一些空格:

package main
import (
  "fmt"
  "strings"
)
func main(){
  greetings := "\t Hello, World "
  fmt.Printf("%d %s\n", len(greetings), greetings)
}

在上面的代码片段中,/t代表制表符,后面有一些空格。有hello World和一些空格。我已经将这个字符串值与它的长度属性一起放到控制台上。len函数将给我们字符串的长度,表示字符串中的字符数。让我们运行一下:

从屏幕截图中可以看出,它有 15 个字符,包括制表符、空格和字符串的其余部分。

现在,让我们继续修剪变量中的空格。我们有strings.TrimSpace,它返回另一个字符串,如下面的屏幕截图所示:

然后我们可以将字符串捕获到一个变量中。检查以下代码:

package main
import (
 "fmt"
 "strings"
)
func main(){
 greetings := "\t Hello, World "
 fmt.Printf("%d %s\n", len(greetings), greetings)
trimmed := strings.TrimSpace(greetings)
 fmt.Printf("%d %s\n", len(trimmed), trimmed)
}

上面代码的输出如下:

看!正如你所看到的,我们的开头和结尾的空格,包括制表符,都消失了,现在我们这里有 12 个字符。这就是你在 Go 中修剪空格的方法。在下一节中,我们将看到如何从字符串值中提取子字符串。

从字符串值中提取子字符串

在这一部分,你将学习如何从字符串值中提取子字符串。Go 语言中的字符串实际上是一个只读的字节切片,这意味着你也可以对字符串执行任何切片操作。让我们去编辑器看看我们如何进行操作。

在编辑器中,添加一个新文件并将其命名为main.go。你必须将包更改为main,并添加一个名为main的新函数。这个main函数将是我们示例的入口点。所以,让我们假设我们有一个字符串值如下:

package main
import "fmt"
func main(){
 greetings := "Hello, World and Mars"

我想从字符串中删除Marsand这两个词,只提取其中的Hello, World部分。可以这样做:

package main
import "fmt"
func main(){
 greetings := "Hello, World and Mars"
 helloWorld := greetings[0:12]
 fmt.Println(helloWorld)
}

索引从 0 开始,因为它是切片。上面代码的输出如下:

如你所见,我们只提取了整个短语中的Hello, World部分。如果索引中没有 0,它仍然可以工作。如果我们只想要这个字符串的WorldMars部分,索引可以是[6:]。

这就是你从字符串值中提取子字符串的方法。在我们的下一个视频中,我们将看到如何用另一个字符串替换字符串的一部分。

替换字符串的部分

在本节中,我们将看到如何快速将字符串的一部分替换为另一个值。在 Go 语言中进行字符串操作时,您会发现在字符串包下有许多实用方法。在这里,我们将使用相同的包来将字符串的一部分替换为另一个值。让我们回到我们的编辑器,看看我们如何开始这个过程。

因此,我将有一个helloWorld变量,并且我们将用Mars替换World。检查以下代码:

package main
import (
 "strings"
 "fmt"
)
func main(){
 helloWorld := "Hello, World"
 helloMars := strings.Replace(helloWorld, "World", "Mars", 1)
 fmt.Println(helloMars)
}

下面的屏幕截图将解释我们刚刚看到的代码:

如屏幕截图所示,我们将使用strings包,它有一个replace函数,它接受我们要搜索的变量作为第一个参数,即Hello, World。旧字符串将是我们要替换的字符串中的内容,即World。新字符串将是Mars,我们要应用于此替换的重复次数将是'1'。

如果您看一下,此方法的签名返回另一个字符串,并且我们将其分配给另一个变量,在这种情况下是helloMars。因此,您将看到以下输出:

如您所见,我们已经用Mars替换了World

现在,假设我们在句子中有多个World实例,并且您使用以下代码:

package main
import (
 "strings"
 "fmt"
)
func main(){
 helloWorld := "Hello, World. How are you World, I am good, thanks World."
 helloMars := strings.Replace(helloWorld, "World", "Mars", 1)
 fmt.Println(helloMars)
}

因此,如果您有这样的字符串值,使用 1 将无济于事。它只会用Mars替换第一个World出现,但其余部分仍将保留为World,如下面的屏幕截图所示:

因此,您可以通过更改重复次数来替换尽可能多的World实例。例如,如果您想要用Mars替换前两个World实例,重复次数将为 2,依此类推。如果您想要用Mars替换所有World实例,一个快速简单的方法是使用减一,这有效地告诉 Go 用单词Mars替换您可以找到的字符串中的任何World实例。让我们运行以下代码:

package main
import (
 "strings"
 "fmt"
)
func main(){
 helloWorld := "Hello, World. How are you World, I am good, thanks World."
 helloMars := strings.Replace(helloWorld, "World", "Mars", -1)
 fmt.Println(helloMars)
}

上述代码将产生以下输出:

现在,所有world实例都已被单词Mars替换。Go 字符串包提供了许多其他选项,正如您所见,替换字符串真的很容易。在下一节中,我们将看到如何在字符串中转义字符。

在字符串中转义字符

在本节中,我们将看到如何转义字符串值中的特殊字符。与今天市场上许多其他语言类似,Go 以特殊方式处理某些字符。例如,如果 Go 在字符串值中看到\t 字符,它将将其视为制表符字符。此外,如果不进行转义,您无法在双引号内包含双引号,现在我们将看到如何转义它们以正确显示这些字符到我们的输出中。

像往常一样,我们将有我们的main.go文件和main函数。因此,让我们检查一个与之前类似的示例。

package main
import "fmt"
func main(){
  helloWorld := "Hello World, this is Tarik."
}

因此,如果我想在术语 Tarik 周围包含双引号,我可以这样做,但是,正如您所看到的,它会给我一个编译时错误,如下面的屏幕截图所示:

所以,让我们来修复这个问题。我所需要做的就是使用\。因此,每当您想要转义特殊字符时,都要用\进行转义。让我们继续并将其添加到我们的控制台:

package main
import "fmt" 
func main(){
 helloWorld := "Hello World, this is \"Tarik.\""
fmt.Println(helloWorld)
}

上述代码的输出将如下所示:

好了!正如您所看到的,它说 Hello World, this is "Tarik.",但 Tarik 被包含在两个双引号中,这是我们想要的。

现在还有其他问题。假设我想以某种原因输出\t而不带双引号:

package main
import "fmt"
func main(){
 helloWorld := "Hello World, this is \"Tarik.\" \t"
fmt.Println(helloWorld)
}

看起来可以运行,既然我们没有看到任何编译时错误,我们可以继续运行。得到以下输出:

如您所见,\t 不会出现在控制台中;实际上,我看到了一个大制表符,因为这是一个特殊字符;\t 表示制表符。还有其他类似的特殊字符,例如\n,表示换行。因此,让我们尝试运行以下代码:

package main
import "fmt"
func main(){
 helloWorld := "Hello World, this is \"Tarik.\" \t\nHello again."
 fmt.Println(helloWorld)
}

前面的代码将产生以下输出:

如您所见,Hello again不再在同一行上,而是放在了新的一行上。如果我删除/n 并再次运行代码,hello again 将回到同一行,我们还会因为特殊字符\t 而有一个大的空格:

那么,我们如何转义\t?让我们看看如果包含另一个\会发生什么,并运行以下代码:

package main
import "fmt"
func main(){
 helloWorld := "Hello World, this is \"Tarik.\" \\tHello again."
 fmt.Println(helloWorld)
}

如您在以下屏幕截图中所见,我们现在在字符串值中有了\t,Go 不再将其视为特殊字符:

这就是在 Go 中使用转义字符的方法。在我们的下一节中,我们将看到如何轻松地将字符串值大写。

大写字符串值

在本节中,我们将看到如何在 Go 中大写单词。有多种方式可以大写句子中的单词;例如,您可能希望大写句子中的所有字母,或者只是所有单词的首字母,我们将看到如何做到这一点。

让我们回到我们的编辑器。前几个步骤与从字符串的开头和结尾修剪空格时一样。然而,在这里,我们有一个变量,其值为"hello world, how are you today",我们希望只大写这个句子中所有单词的首字母。因此,我们之前在上一节中看到的 strings 包中有一个名为title的函数,该方法的签名也返回另一个字符串,我们可以将其赋给另一个变量,即HelloWorldtitle。为了继续,我们将不得不运行刚刚描述的代码:

package main
import (
 "strings"
 "fmt"
)
func main(){
 helloWorld := "hello world, how are you today!"
 helloWorldtitle := strings.Title(helloWorld)
 fmt.Println(helloWorldtitle)
}

前面的代码将产生以下输出:

如您所见,该代码导致了句子中所有首字母的大写。现在,如果我们想要大写这个句子中的所有字母,我们将不得不使用新的ToUpper函数运行以下代码:

package main
import (
 "strings"
 "fmt"
)
func main(){
 helloWorld := "hello world, how are you today!"
 helloWorldtitle := strings.Title(helloWorld)
 fmt.Println(helloWorldtitle)
helloWorldUpper := strings.ToUpper(helloWorld)
 fmt.Println(helloWorldUpper)
}

如果您打印Ln,它实际上会在新的一行中打印该字符串,而如果您不打印,它就不会这样做。我们刚刚看到的代码将产生以下输出:

这就是关于大写字符串值的全部内容!

总结

在本章中,我们学习了如何从字符串的开头和结尾修剪空格,如何从字符串值中提取子字符串,如何替换字符串的部分内容,如何在字符串中转义字符,以及如何将字符串值大写。有了这些,我们已经完成了关于字符串操作的学习。下一章将描述如何在各种类型之间进行类型转换,我们将从将 pool 转换为字符串值开始。

第三章:类型转换

在日常编程活动中,从一种类型转换为另一种类型是非常常见的操作,因此知道如何做到这一点非常重要。在本章中,我们将通过一些实际示例来学习如何轻松地将一种类型转换为另一种类型。

在本章中,我们将涵盖以下主题:

  • 从字符串的开头和结尾修剪空格

  • 从字符串值中提取子字符串

  • 替换字符串的部分

  • 在字符串中转义字符

  • 大写字符串值

将布尔值转换为字符串

我们将从学习如何将Boolean值转换为String值开始:

  1. 在我们的编辑器中,创建一个名为main.go的新文件和main函数后,让我们考虑一个名为isNew的变量,它是一个布尔值。因此值将是true

  2. 所以,假设我们想要将其打印到控制台并附上消息。请查看以下截图:

正如您所看到的,我们遇到了一个编译时错误。因此,您不能使用+运算符,我们需要将isNew布尔值转换为其字符串表示形式。

  1. 让我们使用stringconvert包,其中有各种字符串转换函数,其中,我们将使用FormatBool

  2. 获取Boolean值返回其每个字符串表示形式,此时是isNew。如果您查看签名,您会看到它根据传递的布尔值的值返回 true 或 false:

  1. 所以,让我们添加isNewStr,运行它并检查输出:

还有另一种将这些值打印到控制台的方法,称为Printf。它实际上可以将各种类型格式化到控制台。例如,我们可以使用之前介绍过的特殊字符。

请注意,我们不会为Printf使用isNewStr,因为现在我们可以使用任何类型,它将找到一个默认的字符串表示。

  1. 此外,Go 不接受未使用的变量和未使用的包,因此,我们将注释掉isNewStr := strconv.FormatBool(isNew)并删除isNewStr。现在,我们可以运行以下代码:
package main
import (
  "fmt"
)
func main(){
  isNew := true
  // isNewStr := strconv.FormatBool(isNew)
  message := "Purchased item is "
  fmt.Printf("%s %v", message, isNew)
}
  1. 得到以下输出:

  1. 现在,我们得到与之前相同的消息,这就是您如何轻松地将Boolean类型转换为String类型。

在下一节中,我们将看到如何将整数和浮点值转换为字符串。

将整数和浮点值转换为字符串

在本节中,我们将学习如何将整数和浮点值转换为字符串值。起初,这可能看起来有点复杂,但在本节之后,您将感到足够舒适以处理这些转换。所以让我们回到我们的编辑器,看看我们如何做到这一点。

将整数值转换为字符串值

让我们从将整数值转换为字符串值开始:

  1. 在字符串转换包strconv下,我们有一堆可以用于这些转换的函数;其中一个函数是FormatInt

  2. 所以让我们继续使用十进制。您可以有不同的基数,比如 16、10 和 24。

  3. 如果您查看签名,您会看到它返回一个字符串。

  4. 现在,代码将不会完全按照我们想要的方式工作,但我们将看到原因并加以修复。当您运行先前描述的代码时,将获得以下输出:

  1. 现在,我们知道它接受 64 位整数类型;让我们修改代码并再次运行以获得以下输出:

  1. 我们得到100作为字符串值返回到我们的控制台。您可能不想一直这样做,因此这是您可以运行的代码:
package main
import (
  "strconv"
  "fmt"
)
func main(){
  number := 100
  numberStr := strconv.Itoa(number)
  fmt.Println(numberStr)
}
  1. 我们使用了一个不同的函数,它会自动将整数转换为 ASCII。运行代码后,我们得到以下输出:

将浮点值转换为字符串值

让我们继续进行第二次转换,即将浮点值转换为字符串值:

  1. 在这里,我们将为numberFloat有另一个数字,例如23445221.1223,并且我们将学习将其转换为缩小值。

  2. 我们将考虑另一个函数,即FormatFloat

  3. 因此,让我们继续看一下签名:

  1. 首先,它希望我们传递一个浮点数64(我们也有浮点数32);它们是bitSizes,表示浮点数的大小。我们有格式(fmt),可以使用各种字母,如EFG;例如,G用于大指数,F用于无指数。我们有精度,基本上告诉我们想要使用小数点后的数字有多远,位大小是浮点数32或浮点数64。我们可以根据情况添加所有这些实体。因此,您可以运行以下代码:
package main
import (
 "strconv"
 "fmt"
)
func main(){
 number := 100
 numberStr := strconv.Itoa(number)
 fmt.Println(numberStr)
 numberFloat := 23445221.1223356
 numberFloatStr := strconv.FormatFloat(numberFloat, 'f', 5, 64 )
 fmt.Println(numberFloatStr)
}
  1. 上述代码的输出将如下:

  1. 让我们再玩一下精度;如果我们将其更改为3,您将获得以下输出:

  1. 输出只显示小数点后的三个字符或三个数字。如果您不知道小数点后需要多少位数,可以将精度设置为-1,输出将显示小数点后的所有数字;例如,检查以下代码:
package main
import (
  "strconv"
  "fmt"
)
func main(){
  number := 100
  numberStr := strconv.Itoa(number)
  fmt.Println(numberStr)
  numberFloat := 23445221.1223356
  numberFloatStr := strconv.FormatFloat(numberFloat, 'f',-1,64 )
  fmt.Println(numberFloatStr)
}
  1. 上述代码将给我们以下输出:

  1. 因此,当您想显示所有内容但不知道小数点后的确切数字时,您可能希望使用精度为-1

这就是您可以在 Go 中执行整数和浮点值转换为字符串值的方法。在下一节中,我们将看到如何将字符串值解析为布尔值。

将字符串值解析为布尔值

在本节中,我们将看到如何将字符串值转换为布尔值:

  1. 因此,在我们的编辑器中,我们将有一个名为isNew的变量,这将是一个字符串值,是一个真值。我们将使用一个名为strconv的包,其中有ParseBool。它返回两件事:一个是布尔值,另一个是错误。因此,让我们检查以下代码:
package main
import (
  "strconv"
  "fmt"
)
func main(){
  isNew := "true"
  isNewBool, err := strconv.ParseBool(isNew)
  if(err != nil){
    fmt.Println("failed")
  }else{
    if(isNewBool){
      fmt.Print("IsNew")
    }else{
      fmt.Println("Not new")
    }
  }
}
  1. 您应该检查错误是否不为 nil。这将意味着发生了错误,我们将不得不处理它。例如,我们只会输出一些失败消息,即failed

  2. 如果在其他语言中它不是 nil,但在这里它是 nil,那么我们将不得不检查isNew布尔值。如果看起来没问题,我们将IsNew写入输出,或者Not new

  3. 运行代码后,您将获得以下输出:

  1. 如您所见,它通过了并且没有抛出异常。如果true更改为false,我们将获得Not new的输出。当然,ParseBool方法足够灵活,可以接受各种字符串值。

  2. 如果您查看以下截图中的签名,您将看到TTRUEtrue等:

  1. 如果我们输入1而不是true,输出仍将是IsNew;如果我们输入0Ff,它将转换为false,并输出Not new

  2. 让我们看看如果我们传入J会发生什么:

package main
import (
  "strconv"
  "fmt"
)
func main(){
  isNew := "j"
  isNewBool, err := strconv.ParseBool(isNew)
  if(err != nil){
    fmt.Println("failed")
  }else{
    if(isNewBool){
      fmt.Print("IsNew")
    }else{
      fmt.Println("Not new")
    }
  }
}
  1. 代码将输出以下内容:

  1. 如您所见,输出将是failed

在下一节中,我们将向您展示如何将字符串值解析为整数和浮点类型。

将字符串值解析为整数和浮点类型

在本节中,我们将看到如何将字符串值解析为整数和浮点类型。

将字符串值解析为整数类型

假设我们有一个名为number的变量,它的字符串值为2。我们将使用strconv.ParseInt,它返回两个变量:第一个是我们期望的实际整数,另一个是在转换过程中发生错误时出现的返回变量。

如果你看一下签名,你会看到它返回整数64和一个错误:

因此,我们可以首先检查在转换过程中是否发生了任何错误;如果不是 nil,我们就可以理解发生了某些事情,然后打印Error happened

在 Go 中没有try...catch,所以如果要编写弹性代码,就必须始终进行错误检查。

现在,对于if检查,如果数字是2,我们可以输出Success。现在,让我们运行如下描述的代码:

package main
import (
  "strconv"
  "fmt"
)
func main(){

  number := "2"
  valueInt, err := strconv.ParseInt(number, 10, 32)
  if err != nil {
    fmt.Print("Error happened.")
  } else {
    if valueInt == 2{
      fmt.Println("Success")
    }
  }
}

代码的输出将如下所示:

转换成功了。你也可以尝试 64 位,结果是一样的。好了!这是从字符串转换为整数。

将字符串值解析为浮点数

现在,让我们来检查将字符串值解析为浮点数。首先,我们将使用与将字符串值解析为浮点数相同的代码,只进行了轻微的修改。修改后的代码如下:

package main
import (
  "strconv"
  "fmt"
)
func main(){

  numberFloat := "2.2"
  valueFloat, errFloat := strconv.ParseFloat(numberFloat, 64)
  if errFloat != nil {
    fmt.Print("Error happened.")
  } else {
    if valueFloat == 2.2 {
      fmt.Println("Success")
    }
  }
}

运行代码后,返回一个Success消息。这意味着我们的转换成功了,我们成功地从ParseFloat方法中得到了2.2

在下一节中,我们将学习如何将字节数组转换为字符串。

将字节数组转换为字符串

在本节中,我们将学习如何将字节数组转换为字符串:

关于本教程,你需要知道的最重要的事情是,在 Go 中,字符串变量只是字节切片。因此,将字节数组转换为字符串值和将字符串值转换为字节数组非常容易。

  1. 让我们看看如何开始。假设你有一个helloWorldByte数组;目前,它是一个字节数组,但你可以从任何流中获取它,比如网络或文件。
package main

import "fmt"

func main(){
  helloWorldByte := []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100}
  fmt.Println(string(helloWorldByte))
}
  1. 我们还有字符串构造,它使将字节数组转换为其字符串表示变得非常容易。我们将使用fmt.Println来打印helloWorldByte的字符串表示,并运行代码。

  2. 因此,让我们运行代码并检查输出:

  1. 正如你所看到的,我们非常简单地将整个字节数组转换为了字符串表示。如果你想将字符串转换为字节数组,也可以使用一个字节来做同样的事情。让我们快速地做一下。检查以下代码:
package main
import "fmt"
func main(){
  helloWorld := "Hello, World"
  fmt.Println([]byte(helloWorld))
}
  1. 运行代码后,我们得到以下输出:

将字节数组转换为字符串结束了第三章,类型转换

总结

在本章中,我们涵盖了从字符串开头和结尾修剪空格、从字符串值中提取子字符串、替换字符串的部分、在字符串中转义字符以及将字符串值大写。在第四章中,日期和时间,我们将学习日期和时间的用法,并首先学习如何找到今天的日期和时间。

第四章:日期和时间

在本章中,我们将学习如何在 Go 编程语言中处理日期和时间。你将学习如何对DateTime值进行基本操作,比如找到两个日期之间的差异、获取今天的日期、对DateTime值进行简单的算术运算以及从字符串值中解析日期。本章将涵盖以下主题:

  • 找到今天的日期和时间

  • 从日期中添加和减去

  • 找到两个日期之间的差异

  • 从字符串中解析日期和时间

找到今天的日期和时间

在本节中,我们将学习如何找到今天的日期和时间。我们可以使用time.Now来获取今天的日期,它导入了一个time包,time返回一个time类型,因此我们将其分配给另一个变量并使用String函数。以下代码将帮助你更好地理解:

package main

import (
  "time"
  "fmt"
)

func main(){
  current := time.Now()
  fmt.Println(current.String())
}

前面代码的输出如下:

如你所见,我们得到了一个包含所有内容的大字符串,当然,我们可以根据需要进行格式化。例如,我可以添加current.Format函数和一个预定义的布局,如下面的截图所示:

package main

import (
 "time"
 "fmt"
)

func main(){
 current := time.Now()
 fmt.Println(current.String())

 fmt.Println("MM-DD-YYYY :", current.Format("01-02-2006"))
}

在前面截图中显示的代码的输出如下:

在前面的截图中,你会看到今天的日期。你也可以通过绕过布局snf,提及你想要的输出格式(YYYY-MM-DD hh:mm:ss),来同时获得时间和日期,如下面的代码所示:

package main

import (
  "time"
  "fmt"
)

func main(){
  current := time.Now()
  fmt.Println(current.String())

  fmt.Println("MM-DD-YYYY :", current.Format("01-02-2006"))

  fmt.Println("YYYY-MM-DD hh:mm:ss", current.Format("2006-01-02 15:04:05"))
}

在运行前面截图中提到的代码时,我们得到了以下输出,其中包括年、月、日和时间信息。可以在以下截图中看到:

因此,这就是你如何简单地获取今天的日期,并以各种方式在 Go 语言中进行格式化。在下一节中,我们将学习如何对日期值进行添加或减去。

从日期中添加和减去

在本节中,我们将学习如何对日期值进行添加和减去操作。

添加日期

让我们继续学习如何向当前日期添加一个月。但在这之前,我们需要知道当前日期。你可以按照我们在上一节中学到的步骤来做到这一点。假设我得到了 8 月 8 日(2018-08-08 09:35:16.2687997 +0530 IST m=+0.003951601)作为输出,我们需要在这个值上再添加一个月。通过在time类型上使用AddDate函数,我们可以添加任意多的年、月和日,因为它接受三个参数。整个代码将如下所示:

package main

import (
  "time"
  "fmt"
)

func main(){
  current := time.Now()
  septDate := current.AddDate(0,1,0)

  fmt.Println(current.String())
  fmt.Println(septDate.String())
}

因此,从输出的下面截图中,你会注意到我们通过将值1传递给第二个参数,成功地向八月添加了一个额外的月份:

我们可以执行相同的步骤来添加年份。你可以将years:参数更改为1,并将输出中的2018更改为2019。这可以在下面的截图中看到:

这就是你如何添加日期值。

从日期中减去

我们要学习的第二件事是如何从当前日期中减去日期。如你在下面的代码行中所见,我们使用了Sub方法,因为它接受另一个time类型:

septDate.Sub(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))

相反,我们将使用AddDate并向参数传递一个负值。因此,让我们将其分配给另一个变量并运行以下代码:

package main

import (
  "time"
  "fmt"
)

func main(){
  current := time.Now()
  septDate := current.AddDate(1,1,0)

  fmt.Println(current.String())
  fmt.Println(septDate.String())

  //septDate.Sub(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))

  oneLessYears := septDate.AddDate(-1,0,0)
  fmt.Println(oneLessYears.String())
}

以下代码的输出将如下所示:

如你所见,我们通过从2019中减去1获得了2018

添加时间

现在,假设你需要添加时间而不是月份或年份。为了继续,我们必须使用Add,它有duration,即你想要添加的时间量。

例如,让我们假设我们想要添加 10 分钟。检查以下代码:

package main

import (
  "time"
  "fmt"
)

func main(){
  current := time.Now()
  septDate := current.AddDate(1,1,0)

  fmt.Println(current.String())
  fmt.Println(septDate.String())

  //septDate.Sub(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))

  oneLessYears := septDate.AddDate(-1,0,0)
  fmt.Println(oneLessYears.String())

  tenMoreMinutes := septDate.Add(10 * time.Minute)
  fmt.Println(tenMoreMinutes)
}

该代码返回另一种类型或值,即time类型,如果您查看输出,将会看到我们在 9 月的日期上添加了 10 分钟:

现在,如果我看一下输出,我们可以看到我们添加了10分钟,所以10:10:24变成了10:20:24。如果我将Minute改为Hour,然后运行代码,我们将看到我们从 9 月的日期中添加了10小时,可以在以下代码块中看到:

package main

import (
  "time"
  "fmt"
)

func main(){
  current := time.Now()
  septDate := current.AddDate(1,1,0)

  fmt.Println(current.String())
  fmt.Println(septDate.String())

  oneLessYears := septDate.AddDate(-1,0,0)
  fmt.Println(oneLessYears.String())

  tenMoreMinutes := septDate.Add(10 * time.Hour)
  fmt.Println(tenMoreMinutes)
}

我们将得到以下输出:

所以,这基本上就是您进行时间添加的方法。在我们的下一节中,我们将看到如何找到两个日期值之间的差异。

查找两个日期之间的差异

在本节中,我们将学习如何找到两个日期之间的差异。假设我们有两个日期,如下面的代码块所示,您将看到此方法的签名是不言自明的。因此,我们只需使用以下代码来减去第一个日期:

package main

import (
  "time"
  "fmt"
)

func main(){
  first := time.Date(2017, 1,1,0,0,0,0,time.UTC)
  second := time.Date(2018, 1,1,0,0,0,0,time.UTC)

  difference := second.Sub(first)
  fmt.Printf("Difference %v", difference)
}

现在,在我们运行代码并获得输出之前,如果您检查签名,您将看到该方法返回Duration而不是日期之间的Time类型:

回到运行我们的代码,您将看到以下输出:

这就是您简单地找到两个日期之间的差异。在我们的下一节中,我们将学习如何从给定的字符串中解析日期和时间。

解析字符串中的日期和时间

在本节中,我们将学习如何从字符串中解析日期和时间。本节将结束我们的章节。当您从字符串中解析日期和时间时,您需要两样东西:第一是布局,第二样是您想要解析的实际字符串。所以,让我们假设我们有一个变量,其中包含str := "2018-08-08T11:45:26.371Z"的字符串值。

为了让 Go 理解这一点,您需要提供一个layout属性。layout属性基本上描述了您的字符串DateTime的外观;它以年份开头,然后是月份,日期,然后是时间。与往常一样,time包为我们提供了各种实用函数,我们可以用来操作日期和时间。Parse方法返回两样东西,一个是解析日期,另一个是错误。如果在解析过程中发生任何错误,将会抛出一个错误,我们可以检查错误并查看出了什么问题,否则我们将只输出当前时间和我们解析的时间的字符串表示。所以,让我们运行以下代码:

package main

import (
  "time"
  "fmt"
)

func main(){
  str := "2018-08-08T11:45:26.371Z"
  layout := "2006-01-02T15:04:05.000Z"
  t,err := time.Parse(layout, str)
  if err != nil{
    fmt.Println(err)
  }
  fmt.Println(t.String())
}

我们运行的代码的输出如下:

正如您所看到的,我们准确地捕获了我们试图解析的日期。这就是您在 Go 中进行解析的方法。

总结

在本章中,我们学习了如何找到当前日期和时间,如何在日期上添加和减去,如何找到两个日期之间的差异,以及如何从字符串中解析日期和时间。在下一章中,您将学习如何在 Go 语言中使用映射和数组。您将看到操作和迭代数组的实际示例,合并数组和映射,以及测试映射中是否存在键。

第五章:映射和数组

在本章中,您将学习如何在 Go 中使用映射和数组。您将看到操作和迭代数组、合并数组和映射以及测试映射中是否存在键的实际示例。在本章中,我们将介绍以下配方:

  • 从列表中提取唯一的元素

  • 从数组中查找元素

  • 反转数组

  • 迭代数组

  • 将映射转换为键和值的数组

  • 合并数组

  • 合并映射

  • 测试映射中是否存在键

从数组中提取唯一的元素

首先,我们将学习如何从列表中提取唯一的元素。首先,让我们想象我们有一个包含重复元素的切片。

现在,假设我们想提取唯一的元素。由于 Go 中没有内置的构造,我们将制作自己的函数来进行提取。因此,我们有uniqueIntSlice函数,它接受intSliceintarray。我们的唯一函数将接受intSlice,并返回另一个切片。

因此,这个函数的想法是在一个单独的列表中跟踪重复的元素,如果一个元素在我们给定的列表中再次出现,那么我们就不会将该元素添加到我们的新列表中。现在,看看以下代码:

package main
import "fmt"
func main(){
  intSlice := []int{1,5,5,5,5,7,8,6,6, 6}
  fmt.Println(intSlice)
  uniqueIntSlice := unique(intSlice)
  fmt.Println(uniqueIntSlice)
}
func unique(intSlice []int) []int{
  keys := make(map[int]bool)
  uniqueElements := []int{}
  for _,entry := range intSlice {
    if _, value := keys[entry]; !value{
      keys[entry] =true
      uniqueElements = append(uniqueElements, entry)
    }
  }
  return uniqueElements
}

所以,我们将有keys,它基本上是一个映射,在其他语言中称为字典。我们将有另一个切片来保存我们的uniqueElements,我们将使用for each循环来迭代每个元素,并将其添加到我们的新列表中,如果它不是重复的。我们通过传递一个entry来基本上获取我们的值;如果值是false,那么我们将该条目添加到我们的键或映射中,并将其值设置为true,以便我们可以看到这个元素是否已经出现在我们的列表中。我们还有一个内置的append函数,它接受一个切片,并将条目附加到我们的切片末尾,返回另一个切片。运行代码后,您应该获得以下输出:

如果您看一下第一个数组,会发现有重复的元素:多个65的实例。在我们的新数组或切片中,我们没有任何重复项,这就是我们从列表中提取唯一元素的方法。

在下一节中,我们将学习如何在 Go 中从数组中查找元素。

从数组中查找元素

在本节中,我们将学习如何从数组或切片中查找元素。有许多方法可以做到这一点,但在本章中,我们将介绍其中的两种方法。假设我们有一个变量,其中包含一系列字符串。在这个切片中搜索特定字符串的第一种方法将使用for循环:

package main
import (
 "fmt"
 "sort"
)
func main() {
 str := []string{"Sandy","Provo","St. George","Salt Lake City","Draper","South Jordan","Murray"}
 for i,v := range str{
 if v == "Sandy" {
 fmt.Println(i)
 }
 }
}

运行上述代码后,我们发现单词Sandy在索引0处:

另一种方法是使用排序,我们可以先对切片进行排序,然后再搜索特定的项目。为了做到这一点,Go 提供了一个sort包。为了能够对切片进行排序,切片需要实现sort包需要的各种方法。sort包提供了一个名为sort.stringslice的类型,我们可以将我们的stringslice转换为sort提供的StringSlice类型。在这里,sortedList没有排序,所以我们必须显式对其进行排序。现在,看看以下代码:

package main
import (
  "fmt"
  "sort"
)
func main() {
  str := []string{"Sandy","Provo","St. George","Salt Lake City","Draper","South Jordan","Murray"}
  for i,v := range str{
    if v == "Sandy" {
      fmt.Println(i)
    }
  }
  sortedList := sort.StringSlice(str)
  sortedList.Sort()
  fmt.Println(sortedList)
}

该代码将给出以下输出:

你可以看到Draper先出现,然后是Murray,基本上是按升序排序的。现在,要在这里搜索特定的项目,例如Sandy,只需在main函数中添加以下代码行:

index := sortedList.Search("Sandy")
fmt.Println(index)

运行整个代码后,获得以下输出:

它输出4,这是单词Sandy的位置。这就是如何在数组中找到一个元素。同样的方法也适用于数字;例如,如果您查看sort包,您会发现IntSlice。使用整数切片确实简化了所有数字的排序和搜索操作。在我们的下一节中,我们将看到如何对数组进行反转。

反转一个数组

在本节中,我们将学习如何对数组进行反向排序。我们将有一个变量,它保存了一组数字的切片。由于您现在熟悉了 Go 中的sort包,您会知道sort包提供了许多功能,我们可以用来对数组和切片进行排序。如果您查看sort包,您会看到许多类型和函数。

现在,我们需要sort函数,它接受一个接口,这个接口在sort包中定义;因此,我们可以称之为Sort接口。我们将把我们的数字切片转换成一个接口。看看以下代码:

package main
import (
  "sort"
  "fmt"
)
func main() {
  numbers := []int{1, 5, 3, 6, 2, 10, 8}
  tobeSorted := sort.IntSlice(numbers)
  sort.Sort(tobeSorted)
  fmt.Println(tobeSorted)
}

这段代码将给出以下输出:

如果您查看输出,您会发现我们已经按升序对数字进行了排序。如果我们想按降序对它们进行排序呢?为了能够做到这一点,我们有另一种类型叫做Reverse,它实现了不同的函数来按降序对事物进行排序。看看以下代码:

package main
import (
  "sort"
  "fmt"
)
func main() {
  numbers := []int{1, 5, 3, 6, 2, 10, 8}
  tobeSorted := sort.IntSlice(numbers)
  sort.Sort(sort.Reverse(tobeSorted))
  fmt.Println(tobeSorted)
}

运行代码后,我们得到以下输出,您会看到数字按降序排列:

在下一节中,我们将看到如何遍历一个数组。

遍历一个数组

在本节中,我们将学习如何遍历一个数组。遍历一个数组是 Go 编程中最基本和常见的操作之一。让我们去我们的编辑器,看看我们如何轻松地做到这一点:

package main

import "fmt"

func main(){
  numbers := []int{1, 5, 3, 6, 2, 10, 8}

  for index,value := range numbers{
     fmt.Printf("Index: %v and Value: %v\n", index, value)
  }
}

我们从上述代码中获得以下输出:

这就是您如何轻松地遍历各种类型的切片,包括字符串切片、字节切片或字节数组。

有时,您不需要index。在这种情况下,您可以使用下划线(_)来忽略它。这意味着您只对值感兴趣。为了执行这个操作,您可以输入以下代码:

package main

import "fmt"

func main(){
  numbers := []int{1, 5, 3, 6, 2, 10, 8}
  for _,value := range numbers{
    // fmt.Printf("Index: %v and Value: %v\n", index, value)
    fmt.Println(value)
  }
}

这段代码的输出将如下所示:

这就是您如何轻松地遍历各种类型的切片。在下一节中,我们将看到如何将一个 map 转换成一个键和值的数组。

将一个 map 转换成一个键和值的数组

在本节中,我们将看到如何将一个 map 转换成一个键和值的数组。让我们想象一个名为nameAges的变量,它有一个map,如下面的代码块所示,我们将字符串值映射到整数值。还有名字和年龄。

我们需要添加一个名为NameAge的新结构,它将有Name作为字符串和Age作为整数。我们现在将遍历我们的nameAges映射。我们将使用一个for循环,当您在映射类型上使用范围运算符时,它会返回两个东西,一个键和一个值。因此,让我们编写这段代码:

package main
import "fmt"
type NameAge struct{
  Name string
  Age int
}
func main(){
  var nameAgeSlice []NameAge
  nameAges := map[string]int{
    "Michael": 30,
    "John": 25,
    "Jessica": 26,
    "Ali": 18,
  }
  for key, value := range nameAges{
    nameAgeSlice = append(nameAgeSlice, NameAge {key, value})
  }

  fmt.Println(nameAgeSlice)

}

运行上述代码后,您将获得以下输出:

这就是如何将一个 map 轻松转换成一个数组。在下一节中,我们将学习如何在 Go 中合并数组。

合并数组

在本节中,我们将看到如何在 Go 中轻松合并两个数组。假设我们有两个数组,我们将把它们合并。如果您之前使用过append,您会知道它可以接受任意数量的参数。让我们看看以下代码:

package main
import "fmt"
func main(){
  items1 := []int{3,4}
  items2 := []int{1,2}
  result := append(items1, items2...)
  fmt.Println(result)
}

运行以下代码后,您将获得以下输出:

现在,我们在输出中看到了[3 4 1 2]。你可以向数组中添加更多的值,仍然可以合并它们。这就是我们如何在 Go 中轻松合并两个数组。在下一节中,我们将看到如何这次合并地图。

合并地图

在本节中,我们将学习如何合并地图。查看以下截图中的两张地图:

正如你所看到的,有四个项目,这些地图基本上是将一个字符串映射到一个整数。

如果你不使用逗号,就像在上述截图中22后面所示的那样,你将得到一个编译时异常。这是因为在 Go 中自动添加了一个分号,这在这段代码中是不合适的。

好的,让我们继续合并这两张地图。不幸的是,没有内置的方法可以做到这一点,所以我们只需要迭代这两张地图,然后将它们合并在一起。查看以下代码:

package main
import "fmt"
func main(){
  map1 := map[string]int {
   "Michael":10,
   "Jessica":20,
   "Tarik":33,
   "Jon": 22,
  }
  fmt.Println(map1)

  map2 := map[string]int {
    "Lord":11,
    "Of":22,
    "The":36,
    "Rings": 23,
  }
  for key, value := range map2{
    map1[key] = value
  }
  fmt.Println(map1)
}

上述代码的输出如下:

好的,第一行,你可以看到,只有我们使用的初始元素,第二行包含基本上所有的东西,也就是来自map2的所有项目。这就是你可以快速将两张地图合并成一张地图的方法。在下一节中,我们将学习如何测试地图中键的存在。

测试地图中键的存在

在本节中,我们将看到如何检查给定地图中键是否存在。因此,我们有一个地图nameAges,它基本上将名字映射到年龄。查看以下代码:

package main
import "fmt"
func main() {
  nameAges := map[string]int{
    "Tarik": 32,
    "Michael": 30,
    "Jon": 25,
  }

  fmt.Println(nameAges["Tarik"])
}

如你从以下截图中所见,我们基本上从Tarik键中获取了值。因此,它只返回了一个值,即32

然而,还有另一种使用这个地图的方法,它返回两个东西:第一个是值,第二个是键是否存在。例如,查看以下代码:

package main
import "fmt"
func main() {
  nameAges := map[string]int{
    "Tarik": 32,
    "Michael": 30,
    "Jon": 25,
  }

  value, exists := nameAges["Tarik"]
  fmt.Println(value)
  fmt.Println(exists)
}

输出将如下所示:

正如你所看到的,代码返回true,因为地图中存在Tarik,存在于nameAges中。现在,如果我们在地图中输入一个不存在的名字会怎么样呢?如果我们在nameAges中用Jessica替换Tarik,代码将返回0false,而不是之前得到的32true

此外,你可以使用 Go 的if条件,这是一个条件检查。查看以下代码:

package main
import "fmt"
func main() {
  nameAges := map[string]int{
    "Tarik": 32,
    "Michael": 30,
    "Jon": 25,
  }
  if _, exists := nameAges["Jessica"]; exists{
    fmt.Println("Jessica has found")
  }else {
    fmt.Println("Jessica cannot be found")
  }
}

如果你查看以下输出,你会看到我们得到了Jessica 找不到

这意味着它不存在。现在,如果我将Jessica添加到地图中并运行以下代码会怎么样:

package main
import "fmt"
func main() {
  nameAges := map[string]int{
    "Tarik": 32,
    "Michael": 30,
    "Jon": 25,
    "Jessica" : 20,
  }
  if _, exists := nameAges["Jessica"]; exists{
    fmt.Println("Jessica can be found")
  }else {
    fmt.Println("Jessica cannot be found")
  }
}

如你从上述代码的输出中所见,代码返回Jessica 可以找到

实际上,我们甚至可以在if后面添加一个value,就像我们之前看到的那样,并用以下代码打印出value

package main
import "fmt"
func main() {
  nameAges := map[string]int{
    "Tarik": 32,
    "Michael": 30,
    "Jon": 25,
    "Jessica" : 20,
  }
  if value, exists := nameAges["Jessica"]; exists{
    fmt.Println(value)
  }else {
    fmt.Println("Jessica cannot be found")
  }
}

我们将得到以下输出:

这就是你可以简单地查看给定地图中键是否存在的方法。

总结

本章带你了解了许多主题,比如从列表中提取唯一元素,从数组中找到一个元素,反转一个数组,将地图转换为键和值的数组,合并数组,合并地图,以及测试地图中键的存在。在第六章中,错误和日志,我们将看到有关错误和日志的用法,我们将从在 Go 中创建自定义错误类型开始。

第六章:错误和日志记录

在本章中,我们将学习如何处理错误并在需要时返回错误。Go 的错误机制与一些其他流行语言的不同,本节将教你如何按照 Go 的方式处理错误。我们还将学习如何在应用程序中执行简单的日志记录操作,以便更好地调试你的运行应用程序。在本章中,我们将涵盖以下主题:

  • 创建自定义错误类型

  • 在 Go 中的 try...catch 等价物

  • 在你的应用程序中进行简单的日志记录

  • 优雅地处理 panic

创建自定义错误类型

让我们从创建自定义错误类型开始。如果你来自 C#和 Java 等语言,你可能会发现 Go 中的错误机制有些不同。此外,创建自定义错误的方式非常简单,因为 Go 是一种鸭子类型的语言,这意味着只要你的结构满足一个接口,你就可以使用。让我们继续使用一个新类型创建我们自己的自定义错误。所以,我将有两个字段,ShortMessageDetailedMessage,类型为字符串。你可以有尽可能多的字段,以捕获有关错误的更多信息。此外,为了满足error接口,我将实现一个新方法,*MyError,它将返回一个string值,我们可以将这个错误输出到控制台或某个日志文件中。

然后,我要做的是返回错误消息。所以,你可以很简单地从你的方法中返回这个错误类型。假设我们有一个doSomething方法返回一个错误。假设我们在该方法中做了一些代码,并且由于某种原因返回了一个错误,比如一个ShortMessage实例为"Wohoo something happened!"。当然,你可能需要在这里使用更有意义的消息,并且不要忘记使用&运算符。它将获取你的*MyError对象的地址,因为我们在这里使用的是指针。如果你不这样做,你会看到有一个类型错误,修复这个错误的一种方法是删除那个*指针,错误就会被修复。但你可能不想有多个相同对象的副本,所以不要按照我刚刚描述的做法,你可以很容易地这样做:发送一个引用回去,这样你就有更好的内存管理。现在让我们看一下整个代码:

package main

import "fmt"

type MyError struct{
  ShortMessage string
  DetailedMessage string
  //Name string
  //Age int
}

func (e *MyError) Error() string {
  return e.ShortMessage + "\n" +e.DetailedMessage

}
  func main(){
    err:= doSomething()
    fmt.Print(err)
}
func doSomething() error {
  //Doing something here...
  return &MyError{ShortMessage:"Wohoo something happened!", DetailedMessage:"File cannot found!"}
}

所以,让我们运行一下,当然它会返回一些错误;我们只需要在这里添加err,然后运行到控制台。现在,我们可以看到我们的消息或错误消息被写入到控制台中,如下面的截图所示:

这就是你可以简单地创建自己的错误消息类型。在我们的下一节中,我们将学习 Go 中的try...catch等价物。

在 Go 中的 try...catch 等价物

与其他语言不同,Go 中没有try...catch块。在本节中,我们将看到 Go 如何处理基本错误。所以,我们首先要看的是如何处理 API 调用返回的错误。我们可以使用time.Parse()方法,因为它接受一个布局和一个值字符串。它返回两个东西,一个是parsedDate,另一个是一个错误。Go 大多数时候不是返回异常,而是返回一个错误作为它的第二个参数。

现在,你可以处理这个错误,检查parsedDate是否为 nil。如果在 Go 中不是 nil,那么我们知道发生了错误,我们需要处理它。如果什么都没发生,我们可以安全地继续下一行,即将parsedDate的内容写入输出。所以,检查下面的代码示例:

package main

import (
  "time"
  "fmt"
)

func main(){
  parsedDate, err:= time.Parse("2006", "2018")
  if err != nil {
    fmt.Println("An error occured", err.Error())
  }else{
    fmt.Println(parsedDate)
  }
}

上述代码将给出以下输出:

你可以看到它运行正常。如果我们在2018后面添加一些string值会发生什么?让我们添加abc,然后运行代码。如果你看到以下截图,你会看到在解析时间时发生了错误;它还添加了错误消息An error occured parsing time "2018 abc": extra text: abc,如下截图所示:

现在,本节的第二部分是当你自己返回一个错误时。假设我们有一个doSomething函数,它返回一个err类型。检查以下代码:

package main
import (
  "fmt"
  "errors"
)
func main(){
  _, err := doSomething()
  if err != nil {
    fmt.Println(err)
  }
}
func doSomething() (string,error) {
  return "", errors.New("Something happened.")
}

上述代码将产生以下输出:

这就是你可以在 Go 中做一个简单的try...catch的等价物。在下一节中,我们将看到如何在你的应用程序中进行简单的日志记录。

在你的应用程序中进行简单的日志记录

在本节中,我们将学习如何在应用程序中进行简单的日志记录。当然,你可以用各种方法来做这个,也有第三方包可以让你这样做,但我们将使用 Go 提供的log包。所以,我们首先要做的是使用os包创建一个新文件,如果在创建log文件时出现错误,我们将把它写入控制台。我们还将使用defer函数。在main方法退出之前,这个defer函数将被调用,下一步是设置输出:

package main
import (
  "os"
  "fmt"
  "log"
)
func main(){
  log_file, err := os.Create("log_file")
  if err != nil{
    fmt.Println("An error occured...")
  }
  defer log_file.Close()
  log.SetOutput(log_file)

  log.Println("Doing some logging here...")
  log.Fatalln("Fatal: Application crashed!")
}

当我们运行上述代码时,将创建一个名为log_file的新文件,其中包含以下内容:

你可能想知道致命错误和普通信息错误之间的区别。让我们重新排列这两行,看看新的顺序的行为。因此,我们将首先运行Fatalln,然后运行Println如下:

package main
import (
  "os"
  "fmt"
  "log"
)
func main(){
  log_file, err := os.Create("log_file")
  if err != nil{
    fmt.Println("An error occured...")
  }
  defer log_file.Close()
  log.SetOutput(log_file)
  log.Fatalln("Fatal: Application crashed!")
  log.Println("Doing some logging here...")
}

如果你现在运行上述代码并检查log_file的内容,你会发现第二个Println没有被写入:

区别在于Fatalln类似于Println,但后面只有一个对os.Exit的调用。因此,它基本上写入一个日志并退出应用程序,这就是两者之间的简单区别。这就是你可以在你的应用程序中简单地进行日志记录。当然,如果你不想一直设置输出,你可以将main函数封装到你的包中,就像我们在这里做的那样。在下一节中,我们将看到如何优雅地处理恐慌。

优雅地处理恐慌

在本节中,我们将看到如何优雅地处理恐慌。与错误不同,如果你不从恐慌中恢复,它将停止程序的执行。因此,处理它们是重要的,如果你希望你的程序继续执行。首先,让我们看看如何在 Go 程序中抛出恐慌。你可以简单地使用一个叫做panic的关键字,这是一个内置函数,类型为 panic,运行它以获得输出:

还有另一种方法。让我们在这里使用另一个函数并写一些东西。假设我们正在做某事,由于某种原因它突然恐慌了。这可能是一个第三方方法,这意味着它位于第三方包中,所以我们可能无法完全控制该包。因此,如果你运行上述代码,这是我们将在应用程序窗口中看到的内容,以及我们想要写入控制台的消息,如下所示:

我们还在这里看到了我们的panic的堆栈跟踪。首先,它触发了主要消息,后来又触发了writeSomething()方法。那么我们如何处理这个panic呢?我们有这个defer关键字,你必须使用这个deferdefer的意思是;嗯,就在你的方法退出之前,你想运行另一段代码,所以你只需传递一个函数,然后说“我想运行这个defer函数”。当然,它需要像这样:defer func(){}(),或者你可以在这里直接说defer writeSomething()。没关系,但是因为我要运行一些代码,所以我在这里将它们封装在函数中。我们还有另一个关键字叫做recover,它在main函数退出之前运行defer函数。此外,在这个函数中,我们尝试recover

如果发生了 panic,这个recover会返回一些东西,如果没有 panic,那就意味着它不会返回任何东西。因此,r的值将是nil,这意味着我们不会向控制台写任何东西,因为我们不需要。但是,如果发生了 panic,那么我们就会进入if条件,然后写下来自recover构建方法的任何内容,然后继续运行以下代码,我们将得到相应的输出:

所以,现在你可以看到我们基本上说Recovered in f,消息就是 panic 抛出的内容,这是我们在这里写的。如果你想看到这个过程的继续,我们可以从main函数中复制defer func()函数。接下来,我们将创建另一个名为sayHello()的方法,并将defer func()粘贴到其中。我想向你展示的是,我们已经从 panic 中恢复了,所以执行也会到达这一行。所以,我们可以继续运行以下代码:

package main

import "fmt"

func main(){
  sayHello()
  fmt.Println("After the panic was recovered!")
}

func sayHello(){
  defer func(){
    if r := recover(); r != nil {
      fmt.Println("Recovered in f", r)
    }
  }()
  writeSomething()
}

func writeSomething(){
  /// Doing something here..
  panic("Write operation error")
}

在执行main函数之后,现在我们看到消息:

如果我们没有defer函数,让我们看看它会如何表现。现在你看到它没有触发main函数,我们有 panic 和所有的堆栈跟踪,这就是你如何在应用程序中优雅地处理 panic。

摘要

本章是关于错误和日志记录的介绍。在下一章中,我们将学习如何在操作系统中处理文件和目录。我们还将学习解析和使用各种格式,如 XML、YAML 和 JSON。

第七章:文件和目录

在上一章中,您学会了如何处理错误和日志记录。在本章中,我们将看到如何在 Go 语言中处理文件和目录的相关操作。您还将了解解析和使用各种格式,如 XML、YAML 和 JSON。本章将涵盖以下主题:

  • 检查文件是否存在

  • 读取文本文件的全部内容

  • 写入文件

  • 创建临时文件

  • 计算文件中的行数

  • 在文件中读取特定行

  • 比较两个文件的内容

  • 删除文件

  • 复制或移动文件

  • 重命名文件

  • 删除目录及其内容

  • 列出目录下的所有文件

检查文件是否存在

我们将从检查文件是否存在开始。因此,首先让我们通过单击 New | File 并将其命名为log.txt来创建一个文件。

要开始检查文件是否存在,我们将使用os.Stat包。它返回两个值:第一个是文件信息,第二个是错误。我们不需要文件信息,只需要错误本身,因为我们将检查错误以查看文件是否存在。如果错误是nil(没有错误发生),那么文件存在。请查看以下代码:

package main
import (
  "os"
  "fmt"
)
func main(){
  if _, err := os.Stat("log.txt"); err == nil{
    fmt.Println("Log.txt file exists")
  }
}

运行上述代码时,您将获得以下输出:

要以相反的方式检查文件是否存在,我们只需输入os.IsNotExist()并传递我们捕获的err并将其打印到控制台。请查看以下代码:

package main
import (
  "os"
  "fmt"
)
func main(){
  if _, err := os.Stat("log.txt"); os.IsNotExist(err){
    fmt.Println("Log.txt file does not exist")
  }else{
    fmt.Println("Log.txt file exists")
  }
}

运行上述代码时,我们将得到相同的输出,显示Log.txt 文件存在。现在,让我们尝试运行相同的代码,但这次删除log.txt文件。您将得到以下输出:

您可以看到现在输出显示Log.txt 文件不存在,这样您就可以轻松地检查文件是否存在。在下一节中,我们将看到如何读取文件的全部内容。

读取文本文件的全部内容

在本节中,我们将看到如何读取文件的全部内容。我们将创建一个名为names的新文件,我有一堆名字,例如TarikGuneyMichaelJohnMontana。我们将读取这个文件。我们将使用提供读取文件功能的io实用程序包,并接受文件的路径,即names.txt。它返回两个东西:文件的实际内容和错误。如果没有错误发生,我们将首先将contentBytes转换为string表示。现在让我们使用以下代码将内容写入控制台:

package main
import (
  "io/ioutil"
  "fmt"
)
func main(){
  contentBytes, err := ioutil.ReadFile("names.txt")
  if err == nil{
    var contentStr string = string(contentBytes)
    fmt.Println(contentStr)
  }
}

通过在终端中使用go run main.go命令运行代码,您将获得以下输出:

因此,您可以看到我们已经从文件中读取了所有的名字。这就是您如何轻松地将文件的全部内容读入内存中。

在下一节中,我们将看到如何写入文件。

写入文件

在这一部分,我们将看到如何写入文件。与读取文件类似,我们将使用ioutil包。我们将使用ioutil.WriteFile函数,它接受三个参数。第一个参数是我们要写入的文件名,第二个是我们要写入的数据,最后一个是文件权限。这里的优势是,如果文件不存在,WriteFile将使用perm参数给出的权限创建文件,如果文件已经存在,则在写入之前将截断文件。我们将继续往我们的文件中写入一些内容,因为我们的文件还不存在,它会为我们创建一个新文件。我们将写入Hello, World,这是一个string参数,我们将把它转换为byte数组,然后才能传递给WriteFile。文件名将是hello_world,第二个参数将是hello变量的字节表示。这将返回一个错误。如果它不是nil,意味着发生了某些事情。让我们检查一下代码:

package main
import (
  "io/ioutil"
  "fmt"
)
func main() {
  hello := "Hello, World"
  err := ioutil.WriteFile("hello_world", []byte(hello), 0644)
  if err != nil{
    fmt.Println(err)
  }
}

运行代码时,你会看到没有错误发生,我们的hello_world文件存在。如果你打开文件,你会看到Hello, World已经被写入了:

如果我们再次用不同的stringHello, World Again运行代码,你会看到之前的内容被清除并替换为新内容,如下截图所示:

这基本上就是如何写入文件。在我们的下一部分中,我们将看到如何创建临时文件。

创建临时文件

在这一部分,我们将看到如何创建临时文件。我们还将有一个包含字符串的变量,叫做helloWorld := "Hello, World"。我们将使用ioutil包,它提供了TempFile()方法。第一个参数是目录;如果你不给它传递任何东西,它将使用默认的临时目录,这在这种情况下我们将使用,第二个是给你的临时文件一个前缀,将是hello_world_temp。它返回两个东西:第一个是创建的临时文件,第二个是错误(err)。现在,如果发生任何错误,我们将会抛出错误作为消息。

当你完成临时文件后,建议删除文件,我们可以使用defer函数,其中有一个os.Remove()方法。你只需要提供文件名,它就会找到并删除它。现在我们要把helloWorld写入我们的文件。现在让我们检查一下代码:

package main
import (
 "io/ioutil"
 "fmt"
)
func main(){
 helloWorld := "Hello, World"
 file, err := ioutil.TempFile("", "hello_world_temp")
 if err != nil{
 panic(err)
 }
 defer os.Remove(file.Name())
 if _, err := file.Write([]byte(helloWorld)); err != nil {
 panic(err)
 }
 fmt.Println(file.Name())
}

运行上述代码,你将得到以下输出:

路径是我们的文件所在的位置,选择的部分是我们文件的名称,这是一个临时文件,当然,这个文件会被删除。如果没有被删除,我们会在那个位置看到它。现在,我们不会删除文件,只需注释掉前面代码块中的deferos.Remove(file.Name())一行并运行它。

此外,我们将打开文件,并使用终端,我们将显示该文件的内容,使用less命令(在 Linux 中)和more <命令(在 Windows 中),如截图所示:

如果你看前面的截图,你会看到Hello, World在那里。

这就是你如何在 Go 中创建临时文件。

在我们的下一部分中,我们将看到如何计算文件的行数。

在文件中计算行数

在本节中,我们将看到如何计算文件的行数。假设我们有一个文件,每行都有一堆名字,我们必须计算文件中有多少行。首先,我们将使用os.Open包打开我们的文件,文件名将是names.txt。它返回一个错误,但是对于这个例子,我们不会关心错误,因为我们知道文件是存在的。因此,我将使用文件扫描程序来扫描文件。我们有bufio.NewScanner包,其中有新的扫描程序,它接受一个读取器,因此我们可以传递文件。行数将从0开始,我们将对fileScanner.scan进行此操作。因此,只要它扫描,它将增加行数。最后,我们将将行号写入控制台。当一切都完成时,我们将使用defer file.Close()函数。让我们检查代码:

package main
import (
  "os"
  "bufio"
  "fmt"
)
func main() {
  file, _ := os.Open("names.txt")
  fileScanner := bufio.NewScanner(file)
  lineCount := 0;
  for fileScanner.Scan(){
    lineCount++
  }
  defer file.Close()
  fmt.Println(lineCount)
}

运行上述代码时,您将获得以下输出:

输出打印出5,您也可以通过查看文件并手动计数来确认。

在我们的下一节中,我们将看到如何读取文件中的特定行。

读取文件中的特定行

在本节中,我们将看到如何读取文件中的特定行。我们有一个名为names.txt的文件,每行都有一堆名字:

我们只想从文件中读取第三行。查看以下代码:

package main
import (
  "os"
  "bufio"
  "fmt"
)
func main(){
  fmt.Println(ReadLine(3))
}
func ReadLine(lineNumber int) string{
  file, _ := os.Open("names.txt")
  fileScanner := bufio.NewScanner(file)
  lineCount := 0
  for fileScanner.Scan(){
    if lineCount == lineNumber{
      return fileScanner.Text()
    }
    lineCount++
  }
  defer file.Close()
  return ""
}

首先,我们将有一个ReadLine()函数,它接受行号并返回一个字符串。首先,我们将使用os.Open()函数打开文件,然后我们将使用fileScanner。然后我们将传递文件,我们将使用的行数将从0开始。如果行数等于给定给我们的行号,那么我们将返回文件scanner.txt;否则,我们将递增计数器。最后,我们将使用defer file.Close()函数。运行上述代码时,您将获得以下输出:

因此,它返回john,这是第三行,从0开始计数。如果您希望与人们在文件中计算行数时的预期更加一致,可以更改索引并从1开始。这样您就可以轻松地从文件中获取特定行。

在我们的下一节中,我们将看到如何比较两个文件的内容。

比较两个文件的内容

在本节中,我们将看到如何比较两个文件的内容。首先,我们将创建两个文本文件,内容相同,以便比较,one.txttwo.txt。我们将使用ioutil包将文件读入内存;与往常一样,我们将确保在导入第一个文件时没有错误,如果有错误,我们将简单地发生 panic。我们还将导入第二个文件。有一种非常简单的方法来检查这两个文件是否具有相同的内容(相等),即使用byte包下定义的Equal函数。查看以下代码:

package main
import (
  "io/ioutil"
  "bytes"
  "fmt"
)
func main(){
  one, err := ioutil.ReadFile("one.txt")
  if err != nil{
    panic(err)
  }
  two, err2 := ioutil.ReadFile("two.txt")
  if err2 != nil{
    panic(err2)
  }
  same := bytes.Equal(one, two)
  fmt.Println(same)
}

运行上述代码时,您将获得以下输出:

输出为true,这意味着文件的内容相等。如果更改一个文件中的内容并再次运行相同的代码,则输出为false。这就是您检查两个不同文件中的内容是否相同的方法。

在下一节中,我们将学习如何使用 Go 语言删除文件。

删除文件

在这一部分,我们将看到如何在 Go 中删除文件。删除文件是 Go 中最简单的操作之一,因为os包提供了一个名为Remove()的函数,允许您删除任何文件。因此,首先,我们将创建一个新文件并命名为new.txt。下面的屏幕截图将显示在创建new.txt后的文件夹结构:

我们将看到如何删除new.txt文件。Remove()函数接受您文件的路径。如果发生错误,它会返回一个错误,我们将“捕获”该错误,如果它不是nil,则会触发。查看以下代码:

package main
import "os"
func main() {
  err := os.Remove("new.txt")
  if err != nil{
    panic(err)
  }
}

让我们运行代码并检查输出:

您可以看到new.txt文件已经消失,我们已成功删除了该文件。因此,我要继续运行这个,正如您所看到的,new.txt文件消失了。让我们再次运行这个并看看当您尝试删除一开始不存在的文件时,我们将得到什么类型的恐慌和错误消息:

好了,这就是您如何在 Go 中轻松删除文件。在下一节中,我们将看到如何复制或移动文件。

复制或移动文件

在这一部分,我们将看到如何复制或移动文件。您可以以各种方式执行此操作,其中一些取决于您将要使用的操作系统。但是,我们将看到在不过多依赖操作系统的情况下复制或移动文件的最简单方法。首先,我们将添加一个要复制的文件并命名为original.txt,并添加一些包含Hello, World的内容。然后,我们将使用os.Open()打开文件,它将返回两个东西,原始文件和一个错误。如果没有错误,我们将继续执行defer,然后关闭文件。此外,我们将使用os.Create()创建一个新文件在相同的位置,它也会返回一个错误。现在最简单的方法是使用io.Copy()。因此,代码将看起来像这样:

package main
import (
  "os"
  "io"
)
func main(){
  original, err := os.Open("original.txt")
  if err != nil{
    panic(err)
  }
  defer original.close()
  original_copy, err2 := os.Create("copy.txt")
  if err2 != nil{
    panic(err2)
  }
  defer original_copy.Close()
  _, err3 := io.Copy(original_copy, original)
  if err3 != nil{
    panic(err3)
  }
}

运行代码后,我们看到copy.txt出现,并且打开它时,我们可以看到其中包含从original.txt文件复制的Hello, World

现在,让我们来看如何移动文件。首先,我们将创建一个名为target的新文件夹,并将original.txt复制到target中,并删除放置在target文件夹外部的original.txt文件。为此,original_copy, err2 := os.Create("copy.txt")将更改为original_copy, err2 := os.Create("target/original.txt")。如果您看到以下屏幕截图,您将注意到original.txt文件已被复制到target文件夹下:

我们现在可以删除外部的original.txt文件。在上述代码的main函数末尾添加以下两行代码:

original.Close()
os.Remove("original.txt")

运行上述代码后,您将获得以下输出:

正如您所看到的,该代码有效地通过移动和删除文件来移动original.txt文件。这就是您如何简单地在 Go 中复制和移动文件。

在下一节中,我们将看到如何在 Go 中轻松重命名文件。

重命名文件

在这一部分,我们将看到如何在 Go 中重命名文件。首先,我们将创建一个新文件并命名为old.txt,文件夹结构将如下屏幕截图所示:

我们将更改此文件的名称为new.txt。要做到这一点,最简单的方法是使用os包提供的Rename()函数。该函数接受旧路径old.txt和新路径new.txt。让我们来看看代码:

package main
import "os"
func main() {
  os.Rename("old.txt", "new.txt")
}

在运行代码时,您可以看到在以下屏幕截图中,名称old.txt已更改为new.txt

因此,这基本上是我们如何在 Go 中重命名文件的方法。

在下一节中,我们将看到如何删除目录及其所有内容。

删除目录及其内容

在本节中,我们将看到如何删除目录及其内容。我们将使用os包,它提供了两个函数,Remove()RemoveAll()。我们将检查这两个函数。首先,我们将创建一个名为hello的新目录,并保持为空,如下面的屏幕截图所示:

如前所述,我们将使用os包,它接受文件或目录。我们将传递一个目录,如果发生任何事情,它总是返回一个错误。我们必须检查这个错误是否不是nil。请查看以下代码:

package main
import (
  "os"
  "fmt"
)
func main(){
  err := os.Remove("hello")
  if err != nil{
    fmt.Println(err)
  }
}

如果您运行代码,将获得以下文件夹结构作为输出:

如果您比较两个输出屏幕截图,您会发现我们已成功删除了hello目录。但是,如果目录中有文件(比如world.txt),也就是说,目录不是空的,并且您运行相同的代码,那么目录将不会被删除,并且如果hello目录中有文件,则会显示以下消息:

现在,有一个选项可以删除文件以及目录。我们可以使用我们在本节开头提到的RemoveAll()函数。要做到这一点,只需将上述代码中的err:= os.Remove(“hello”)更改为err:= os.RemoveAll(“hello”)

在运行上述代码时,您会发现您已成功删除了文件和目录,并且您将再次查看以下文件夹结构:

在下一节中,我们将看到如何列出目录下的所有文件。

列出目录下的所有文件

在本节中,我们将看到如何列出目录下的所有文件。我们将创建一个名为hello的新目录,其中包括三个文件,即jupiter.txtmars.txtworld.txt

我们要做的是读取所有文件并将它们的名称输出到控制台。我们将使用ioutil.ReadDir包并传入hello,这是我们目录的名称。这将返回两种类型的东西:两个文件和一个错误。我们将检查错误是否不是nil,并使用 panic 打印出内容。我们还将使用for循环来遍历文件。请查看以下代码:

package main
import (
  "io/ioutil"
  "fmt"
)
func main() {
  files, err := ioutil.ReadDir("hello")
  if err != nil{
    panic(nil)
  }
  for _,f := range files{
    fmt.Println(f.Name())
  }
}

如果您运行上述代码,将获得以下输出:

这就是您如何简单列出目录下的所有文件。

摘要

在本章中,您学习了如何在操作系统中处理文件和目录。您还学习了解析和使用各种格式,如 XML,YAML 和 JSON。在下一章中,我们将学习有关并发的技巧,并且我们将从同时运行多个函数开始。

第八章:并发

Go 最强大的一点是它与 API 的并发。在本章中,你将学习如何在 Go 语言中利用并发构造。本章将涵盖以下主题:

  • 并发运行多个函数

  • 在并发运行函数之间传递数据

  • 等待所有并发函数完成

  • 选择并发函数的结果

并发运行多个函数

让我们开始并发运行多个函数。

看一下以下代码块中的代码:

import (
  "fmt"
  "time"
)

func main() {

  names := []string{"tarik", "john", "michael", "jessica"}

  for _, name := range names {
   time.Sleep(1 * time.Second)
   fmt.Println(name)
  }
ages := []int{1, 2, 3, 4, 5}
  for _, age:= range ages {
    time.Sleep(1 * time.Second)
    fmt.Println(age)
  }
}

从上面的代码可以看出,有两个不同的列表;每个列表都有至少花费一秒钟才能完成的项目,但出于练习目的,我们不会有任何实际的代码,只是fmt.Println。我们在每次迭代中都添加了time.Sleep一秒钟。如前面的代码所示,我们首先处理名称,然后处理年龄。你可以注意到的一件事是它们实际上并不相互依赖;它们实际上是两项不同的工作。所以,让我们继续运行这个程序,看看在控制台上的效果如何:

如果你观察输出的过程,你会发现每行输出在传递下一个之前等待了一秒钟。你会发现它们实际上是顺序的代码片段,尽管它们并不相互依赖。在继续到第二个for循环之前,我们必须等待循环完成。

我们可以通过使用并发模式使这个过程更具可扩展性和效率。为此,我们将在 Go 中使用 Go 例程。Go 例程比线程更轻量级,而且与线程不同,它们是自愿地而不是强制性地交还控制权。随着我们继续前进,你会更多地了解我所说的具体含义。检查以下代码:

package main

import (
  "fmt"
  "time"
)
func main() {

  go func() {
    names := []string{"tarik", "john", "michael", "jessica"}

    for _, name := range names {
      time.Sleep(1 * time.Second)
      fmt.Println(name)
    }
  }()

  go func(){
    ages := []int{1, 2, 3, 4, 5}
    for _, age:= range ages {
      time.Sleep(1 * time.Second)
      fmt.Println(age)
    }
  }()
  time.Sleep(10*time.Second)
}

如你所见,我们已经将代码转换为独立的功能片段,使用了 Go 关键字和匿名函数来创建 Go 例程。我们对年龄也做了同样的事情。运行代码时,你将得到以下输出:

如你所见,与以前顺序显示输出不同,它是随机显示的。这意味着两个循环是同时进行处理的。

如果我们移除time.Sleep(使用//注释掉它),我们将在控制台上看不到任何结果。这是因为主应用程序也是在一个 Go 例程下运行的,这意味着我们有三个 Go 例程:我们输入的两个和整个主应用程序。如前所述,问题在于 Go 例程自愿地而不是强制性地将控制权交还给其他 Go 例程。这意味着当你使用time.Sleep时,控制权将交给其他 Go 例程,我们的系统将正常工作。

现在,如果我们使用1秒而不是上次代码中看到的10秒,会发生什么?你将得不到任何输出。这是因为1秒对于所有 Go 例程来说不足以完成任务。一旦主 Go 例程完成了它的处理,它就会关闭整个应用程序,并且不会给其他 Go 例程足够的时间来完成。有一种处理这种情况的方法,我们有另一个叫做通道的构造。因此,为了简单起见,我们将删除第二个 Go 例程,现在使用通道。检查以下代码:

package main

import (
    "time"
  "fmt"
)

func main() {

  nameChannel := make(chan string)

  go func() {
    names := []string{"tarik", "john", "michael", "jessica"}

    for _, name := range names {
    time.Sleep(1 * time.Second)
      //fmt.Println(name)
    nameChannel <- name
    }
  }()

  for data:= range nameChannel{
    fmt.Println(data)
  }
}

当你运行上面的代码时,你将得到以下异常:

出现这种异常的原因是,当你完成一个通道时,你需要关闭它,否则for循环将一直等待。然而,因为你的 Go 例程已经完成了该通道,循环将陷入死锁并停止你的应用程序。关闭通道的一种方法是添加下面突出显示的代码行:

package main

import (
    "time"
  "fmt"
)

func main() {

  nameChannel := make(chan string)

  go func() {
    names := []string{"tarik", "john", "michael", "jessica"}

    for _, name := range names {
    time.Sleep(1 * time.Second)
      //fmt.Println(name)
    nameChannel <- name
    }
    close(nameChannel)
    //nameChannel <- ""
  }()

  for data:= range nameChannel{
    fmt.Println(data)

    }

  //<-nameChannel
}

当一个通道关闭时,循环将终止。所以,让我们继续运行这个程序并检查输出:

如您所见,这里没有任何异常,一切看起来都很好。如果您不关心结果,并且想要使用我们的第一种方法,可以使用以下代码:

package main

import (
  "fmt"
  "time"
)

func main() {
  nameChannel := make(chan string)
  go func() {
    names := []string{"tarik", "john", "michael", "jessica"}
    for _, name := range names {
      time.Sleep(1 * time.Second)
      fmt.Println(name)
    }
    nameChannel <- ""
  }()
  <-nameChannel
}

我们所做的是将所有内容写入控制台,一旦循环结束,就设置了nameChannel。此外,在这种情况下,我们会等待直到从名称通道获取一些数据,因此不会终止应用程序。一旦从名称通道获取到一些数据,我们就会读取它,但实际上并不会将其分配给任何变量。当main Go 例程继续执行到下一行时,那里没有代码,因此main函数退出。因此,我们的应用程序关闭了。您将获得以下输出:

这就是您可以使用通道和函数执行并发操作的方法。在结束之前,让我们重申一点关于通道。如果通道为空并且您尝试读取它,它将阻塞其 Go 例程。一旦填充,我们可以从中读取一些东西;我们读取数据并继续。之所以main Go 例程无法退出是因为我们之前没有向其发送任何值,这比在我们之前的示例中使用计时器要更有效。

在下一节中,我们将看到如何在并发运行的函数之间传递数据。

在并发运行的函数之间传递数据

在本节中,我们将看到如何在 Go 例程之间传递数据。假设我们有两个 Go 例程。第一个 Go 例程对数据执行一些操作,并将数据交给另一个 Go 例程,后者对该数据执行第二个处理阶段。现在,我们需要一种方法在第一个 Go 例程和第二个 Go 例程之间传递数据。正如您所看到的,我们可能需要在两个 Go 例程之间进行一些同步,因为第二个 Go 例程将不得不等待,直到第一个 Go 例程向其提供一些数据。

首先,我们将使用以下代码:

package main
import "fmt"
func main(){
  nameChannel := make(chan string)
  done := make(chan string)
  go func(){
    names := []string {"tarik", "michael", "gopi", "jessica"}
    for _, name := range names {
      // doing some operation
      fmt.Println("Processing the first stage of: " + name)
      nameChannel <- name
    }
    close(nameChannel)
  }()
  go func(){
    for name := range nameChannel{
      fmt.Println("Processing the second stage of: " + name)
    }
    done <- ""
  }()
  <-done
}

如果您查看代码,您会看到我们再次使用了通道:nameChannel。由于我们需要从两个 Go 例程中访问nameChannel,因此我们必须在main函数内声明它。在第一个 Go 例程中,我们将向nameChannel传递一些数据,即namename变量是包含一些数据的字符串数组,来自第一个 Go 例程。在第二个 Go 例程中,我们将使用nameChannel并读取它,因为它已经填充。此外,我们还必须使用另一个 Go 例程来向主 Go 例程发出信号,指示所有 Go 例程都已完成(done := make(chan string))。我们还必须终止应用程序以避免任何死锁,使用close函数。当通道关闭时,for循环将被终止,Go 例程将向done变量发送一些数据。然后,我们的主 Go 例程将读取它并继续执行下一行,退出main函数,应用程序就完成了。这就是无缓冲通道;也就是说,您可以发送单个数据,必须在发送更多数据之前读取并清空它,否则它将被阻塞。

另一种方法是使用缓冲通道来提高性能。对前面的代码进行轻微修改将有所帮助。我们将添加整数5,这意味着您可以在不等待的情况下将五个数据发送到nameChannel中。检查修改后的代码:

package main
import "fmt"
func main(){
  nameChannel := make(chan string, 5)
  done := make(chan string)
  go func(){
    names := []string {"tarik", "michael", "gopi", "jessica"}
    for _, name := range names {
      // doing some operation
      fmt.Println("Processing the first stage of: " + name)
      nameChannel <- name
    }
    close(nameChannel)
  }()
  go func(){
    for name := range nameChannel{
      fmt.Println("Processing the second stage of: " + name)
    }
    done <- ""
  }()
  <-done
}

例如,它将发送一些数据,但不会等待,因为还有四个位置。因此,它将进入第二次迭代,并将一些数据发送到其中,直到计数达到5。好处是,当我们向名称通道发送数据时,我们也从中读取数据。以下将是输出:

这是如何在多个 Go 例程之间传递数据的方法。在下一节中,我们将看到如何等待所有并发函数完成。

等待所有并发函数完成

在本节中,我们将看到如何等待所有并发函数完成。假设我们有如下代码片段:

package main

import (
  "fmt"
  )

func main() {
  for i := 0; i < 10; i++ {
    go func(){
      fmt.Println("Hello World")
    }()
  }
}

假设我们想在循环中创建多个 Go 例程。在这种情况下,假设我们想要有 10 个 Go 例程加上主 Go 例程,因此总共有 11 个 Go 例程。如果运行前面屏幕截图中显示的代码,将不会有任何输出。

等待所有这些 Go 例程完成,以便我们可以向控制台显示一些内容的一种方法是使用time.Sleep,如以下代码所示:

package main

import (
  "fmt"
  "time"
)

func main() {
  for i := 0; i < 10; i++ {
   go func(){
      fmt.Println("Hello World")
    }()
  }

  time.Sleep(10*time.Second)
}

运行上述代码后,您将获得以下输出:

现在,您已经获得了一个输出,但是这种方法的问题是,通常您不知道所有 Go 例程完成需要多长时间;因此,您无法真正预测时间。因此,我们可以使用 Go 库本身提供的sync.WaitGroup。顾名思义,它基本上是一组等待,您可以使用它来等待所有 Go 例程完成。检查以下代码:

package main
import (
  "fmt"
  "sync"
)

func main() {
  var wg sync.WaitGroup
  for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(){
      fmt.Println("Hello World")
      wg.Done()
    }()
  }
  wg.Wait()
}

因此,在每次迭代中,我们可以向我们的等待组添加一个新项,这在这种情况下将是1。因此,我们基本上会将WaitGroup中的等待数量增加1。当 Go 例程完成时,它将使用wg.Done()进行信号传递,这将基本上减少组中的等待数量1。此外,wg.Wait将阻塞我们的主 Go 例程,直到所有 Go 例程都完成。运行代码后,我们将获得以下输出:

这是你可以简单等待应用程序中所有 Go 例程完成的方法。在下一节中,我们将看到如何选择并发函数的结果,因为它们被返回。

选择并发函数的结果

在本节中,我们将看到如何选择并发排名函数的结果。假设我们的main函数中有两个 Go 例程,它们基本上正在设置自己的通道:channel1channel2。假设我们想先读取任何内容,然后继续下一行。为此,Go 提供了一个名为select的内置结构,select基本上等待通道填充并且看起来像switch语句。让我们继续看看现在的样子:

package main
import (
  "time"
  "fmt"
)
func main() {
  channel1 := make(chan string)
  channel2 := make(chan string)
  go func(){
    time.Sleep(1*time.Second)
    channel1 <- "Hello from channel1"
  }()
  go func(){
    time.Sleep(1 * time.Second)
    channel2 <- "Hello from channel2"
  }()
  var result string
  select {
  case result = <-channel1:
    fmt.Println(result)
  case result = <-channel2:
    fmt.Println(result)
  }
}

因此,您只需说select,并且例如说channel1,当channel1准备就绪时,我们将执行类似创建string类型的result变量的操作。因此,在这里,我将把channel1的值分配给将使用Println打印到控制台的result变量。在第二种情况下,如果不是channel1而是准备好读取的channel2,那么我们将将其读取到我们的result变量中。select语句在这里不会同时使用两种情况;例如,如果channel1channel2同时准备就绪,那么select语句将随机选择其中一个。

由于channel1已准备就绪,我们从channel1得到了Hello作为输出。如果我们再次运行代码,您将从以下屏幕截图中看到channel2

因此,您可以轻松地看到输出中的随机性。这就是它的工作原理。

现在,可能会有一些情况需要多次等待。在这种情况下,您可以使用循环:

package main
import (
 "time"
 "fmt"
)
func main() {
 channel1 := make(chan string)
 channel2 := make(chan string)
go func(){
 time.Sleep(1*time.Second)
 channel1 <- "Hello from channel1"
 }()
go func(){
 time.Sleep(1 * time.Second)
 channel2 <- "Hello from channel2"
 }()
var result string
 for {
 select {
 case result = <-channel1:
 fmt.Println(result)
 case result = <-channel2:
 fmt.Println(result)
 }
 case <-quit:
 return
 }
}

想象一下,你正在编写一些必须不断等待某些传入数据的东西,当数据进来时,你希望将其写入控制台。或者你可能想对这些数据进行一些操作。在这种情况下,你可以在一个无限循环中等待它们。如果你想要跳出这个循环,你可以读取另一个通道,比如quit。如果quit已经存在,那么你可以直接跳出这个循环,或者如果它是一个函数,你可以使用 return,这样也会跳出函数。

所以,这就是你如何可以轻松地在 Go 中读取来自多个函数的数据。这就结束了我们的并发章节。

总结

在这一章中,你学会了如何在 Go 语言中利用并发构造。在下一章中,我们将学习系统编程,并将从捕获信号开始。您还将学习如何使用 Go 处理命令行参数。

第九章:系统编程

系统编程允许你处理系统消息并运行处理任务。在本章中,你将学习如何使用 Go 处理命令行参数。本章将涵盖以下主题:

  • 捕获信号

  • 从 Go 应用程序中运行子进程

  • 处理命令行参数

捕获信号

在我们深入了解如何捕获信号之前,让我们先了解一下信号是什么,以及你如何使用它们。信号是一种有限的进程间通信形式,通常用于 Unix 和类 Unix 操作系统。信号是一种异步通知,发送给同一进程中的特定线程或另一个目标进程,通知它发生了某个事件。你可以捕获信号的原因有很多;例如,你可以捕获来自另一个进程的终止信号,以执行一些终止清理操作。在 Go 中,Go 信号通知通过在我们的通道上发送os.signal值来工作。现在,让我们继续看看在我们的 Go 应用程序中是什么样子。

首先,我们将创建一个名为 signals 的新通道,并在这里使用os.signal。如果你想捕获多个信号,你可以使用一个带缓冲的通道,并将 3 或 4 作为整数类型。要一次只捕获一个信号,我们可以输入 1,或者你可以只传递这个,那么默认值将自动为 1。我们还需要一些其他通道来通知我们已经完成了信号处理,这样我们就可以终止我们的应用程序或执行其他操作。在我们的signal包中,有一个名为Notify()的方法,所以让我们继续看看文档,它说Notify 会导致包信号将传入的信号中继到通道。因此,Go 将自动监听信号,并将这些信号关联到我们将作为其第一个参数提供的通道上。现在,检查以下代码:

package main
import (
  "os"
  "os/signal"
  "syscall"
  "fmt"
)
func main(){
  signals := make (chan os.Signal, 1)
  done := make(chan bool)
  signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
  go func (){
    sig := <- signals
    fmt.Println(sig)
    fmt.Println("Signal captured and processed...")
    done <- true
  }()
  fmt.Println("Waiting for signal")
  <-done
  fmt.Println("Exiting the application...")
}

有参数可以过滤你想要监听的信号,即syscall.SIGINTsyscall.SIGTERM。此外,我们将创建一个 Go 例程,简单地监听这个信号并执行一个操作。此外,我们将读取这个值并将信号的内容写入控制台。我们将添加一个print语句,说明信号已捕获并处理...。此外,done <- true将帮助我们处理信号。最后,我们将输入print语句等待信号,然后我们完成了信号的捕获和处理。让我们继续运行代码以获得输出。我们现在将运行main.go应用程序,它将打印等待信号

现在,我们可以发送一个信号来关闭应用程序,使用Ctrl + C命令,正如你在下面的截图中所看到的,发生了中断。我们的中断被捕获并处理,现在我们退出应用程序,这也可以在下面的截图中看到:

这就是你可以简单地捕获进程并在你的 Go 应用程序中使用信号的方法。在下一节中,我们将看到如何从 Go 应用程序中运行子进程。

运行子进程

在这个视频中,我们将看到如何在应用程序中运行子进程。在我们的应用程序中,我们将运行一个名为ls(在 Linux 中)和dir(在 Windows 中)的命令。lsdir命令是一个简单地列出给定目录中所有文件的应用程序。因此,从我们当前的目录中,它将给我们返回hello.txtmain.go文件。我们将在我们的应用程序中运行这个ls实用程序应用。因此,我们首先要做的是使用exec包,它提供了命令。我们将使用ls命令,现在不传递任何参数。这将返回命令本身。你会发现两个函数;一个是start,另一个是run

startr的区别在于,如果您查看文档,您会发现run启动指定的命令并等待其完成。根据您的要求,您可以选择startrun

我们还有PID,即进程 ID,并且我们将将其输出到控制台。因此,让我们继续运行代码。您会看到以下内容:

package main

import (
  "os/exec"
  "fmt"
  )

func main() {
  lsCommand := exec.Command("ls")
  lsCommand.Start()
  fmt.Println(lsCommand.Process.Pid)
}

您将得到以下输出:

如您所见,我们得到了进程 ID,但尚未看到目录中的文件。现在,让我们尝试run。我们希望读取来自ls命令的任何内容,然后将其打印到控制台上。我们将使用“lsCommand.Output()”,它返回一个字节数组和一个错误,但我们现在将忽略错误。好了!现在让我们检查上述代码:

package main
import (
  "os/exec"
  "fmt"
)
func main() {
  lsCommand := exec.Command("ls")
  output,_ := lsCommand.Output()
  lsCommand.Run()
  fmt.Println(lsCommand.Process.Pid)
  fmt.Println(string(output))
}

我们还将清除终端,然后检查输出:

如您所见,它给了我们两个文件名和进程 ID。这就是您可以简单地从 Go 应用程序中运行进程的方法。当然,还有更多的方法。您可以运行其他类型的进程,例如 Google Chrome 或 Firefox,或者您开发的另一个应用程序。因此,当您需要从应用程序内部启动进程时,这是一个非常强大的工具。在下一节中,我们将看到如何处理命令行参数。

处理命令行参数

在本节中,我们将看到如何处理命令行参数。命令行参数的典型示例是ls -a。在这里,a是传递给我们最后一个命令的命令行参数,ls是操作系统中的一个程序。根据传递给ls命令的参数,它的行为会有所不同。

例如,如果我们键入ls,它将显示所有可见文件。如果我们键入ls -a,那么它将显示该目录下的所有内容,包括不可见项目,这可以在以下截图中看到:

因此,我们将对我们的程序执行相同的操作。您可以使用os.Args来读取传递给应用程序的参数。我们将读取并将这些参数写入控制台,然后查看在我们向应用程序传递一些参数后的外观。我们首先需要清除我们的终端并输入go run main.go。由于最初我们不会传递任何参数,因此我们可以期望只看到一个参数,那就是我们可执行文件的路径。但是,由于我们使用go run,它将为我们创建一个临时可执行文件并运行它,因此那是temp位置:

如果我们键入go run main.go -someArgument,我们将得到第二个项目,即- someArgument

如果我们不关心第一个参数,我们可以使用realArgs

package main
import (
  "os"
  "fmt"
)

func main(){
  realArgs := os.Args[1:]
  fmt.Println(realArgs)
}

您将得到以下输出:

让我们继续检查一个真实的例子。假设我们只期望传递一个参数。检查以下代码:

package main
import (
  "os"
  "fmt"
)
func main(){
  realArgs := os.Args[1:]
  if len(realArgs) == 0{
    fmt.Println("Please pass an argument.")
    return
  }
  if realArgs[0] == "a"{
    writeHelloWorld()
  }else if realArgs[0] == "b"{
    writeHelloMars()
  }else{
    fmt.Println("Please pass a valid argument.")
  }
}
func writeHelloWorld(){
  fmt.Println("Hello, World")
}
func writeHelloMars(){
  fmt.Println("Hello, Mars")
}

正如您在前面的代码中所看到的,我们已经输入了realArgs[0] == "a",这将运行一个名为“writeHelloWorld()”的函数;如果是realArgs[0] == "b",那么它将运行“writeHelloMars()”,对于任何默认情况,我们将打印一个警告,“请传递有效的参数”。现在,我们将添加“writeHelloWorld()”和“writeHelloMars()”函数。此外,我们将使用内置函数来获取我们的realArgs的长度,如果是0,我们将打印“请传递参数”。完成后,我们需要添加一个return语句并退出。

运行代码后,您将得到以下输出:

正如你所看到的,我们收到了我们的第一条消息。如果我们输入 go run main.go a,我们会在控制台上看到 Hello, World 的输出,如下面的截图所示:

如果我们输入 go run main.go b,我们会在控制台上看到 Hello, Mars 的输出,如下面的截图所示:

这就是你如何在 Go 应用程序中执行命令行参数处理的方法。这就结束了我们的章节。

总结

在这一章中,你学会了捕获信号、运行子进程和处理命令行参数。在下一章中,你将学习如何从互联网上下载网页和文件。你还将看到如何创建文件和 Web 服务器,以及处理 HTTP 请求和响应。

第十章:Web 编程

在这一章中,我们将看到一些有效的配方,这些配方将涉及与互联网的交互,比如下载网页,创建我们自己的示例网页服务器,以及处理 HTTP 请求。本章将涵盖以下主题:

  • 从互联网下载网页

  • 从互联网下载文件

  • 创建一个简单的网页服务器

  • 创建一个简单的文件服务器

从互联网下载网页

让我们从如何从互联网下载网页开始。我们将从定义我们的 URL 开始,它将是golang.org,然后我们将使用net/http包来获取此 URL 的内容。这将返回两个东西:responseerror

如果您快速查看这里的文档,您会发现它发出了一个get请求来指定 URL,并且还根据响应返回了一些 HTTP 代码:

检查以下代码:

package main
import (
  "net/http"
  "io/ioutil"
  "fmt"
)
func main(){
  url := "http://golang.org"
  response, err := http.Get(url)
  if err != nil{
   panic(err)
  }
  defer response.Body.Close()
  html, err2 := ioutil.ReadAll(response.Body)
  if err2 != nil{
    panic(err)
  }
  fmt.Println(html)
}

如果发生错误,我们将调用panic,因此我们输入panic(err),其中我们将err作为其参数。当一切都完成时,我们将不得不关闭主体。让我们继续在终端中运行此代码,以获得以下结果:

如您所见,它是一个字节数组,我们将把它改为string

package main
import (
  "net/http"
  "io/ioutil"
  "fmt"
)
func main(){
  url := "http://golang.org"
  response, err := http.Get(url)
  if err != nil{
    panic(err)
  }
  defer response.Body.Close()
  html, err2 := ioutil.ReadAll(response.Body)
  if err2 != nil{
    panic(err)
  }
  fmt.Println(string(html))
}

如果我们现在运行代码,我们将获得以下输出:

现在我们在控制台上打印出了这个 HTML 源代码,这就是您可以简单地使用 Go 从互联网下载网页的方法。在下一节中,我们将看到如何从互联网下载文件。

从互联网下载文件

在本节中,我们将看到如何从互联网下载文件。为此,我们将以下载图像为例。我们将输入图像的 URL,即 Go 的标志。检查以下代码:

package main
import (
  "net/http"
  "os"
  "io"
  "fmt"
)
func main(){
  imageUrl := "https://golang.org/doc/gopher/doc.png"
  response, err := http.Get(imageUrl)
  if err != nil{
    panic(err)
  }
  defer response.Body.Close()
  file, err2 := os.Create("gopher.png")
  if err2 != nil{
    panic(err2)
  }
  _, err3 := io.Copy(file, response.Body)
  if err3 != nil{
    panic(err3)
  }
  file.Close()
  fmt.Println("Image downloading is successful.")
}

如您所见,我们在这里使用了http.Get()方法。如果我们的err不是nil,我们会输入panic(err),然后退出defer response.Body.Close()函数。在我们的函数退出之前,我们将关闭out响应的主体。因此,我们首先要做的是创建一个新文件,以便我们可以将图像的内容复制到文件中。如果错误再次不是nil,我们将会发生 panic,并且将使用io.Copy()。我们将简单地写入图像下载成功到控制台。

让我们继续运行代码来检查输出:

哇!下载成功了。这就是您可以使用 Golang 从互联网下载图像或任何类型的文件的方法。在下一节中,我们将看到如何创建一个简单的网页服务器。

创建一个简单的网页服务器

在本节中,我们将看到如何在 Go 中创建一个简单的网页服务器。由于内置的 API,使用 Go 创建一个简单的网页服务器非常容易。首先,我们将使用net/http包。net/http包有HandleFunc()方法,这意味着它将接受两个参数。第一个是 URL 的路径,第二个是您想要处理传入请求的函数。检查以下代码:

package main
import "net/http"
func sayHello(w http.ResponseWriter, r *http.Request){
  w.Write([]byte("Hello, world"))
}
func main(){
  http.HandleFunc("/", sayHello)
  err := http.ListenAndServe(":5050", nil)
  if(err != nil){
    panic(err)
  }
}

只要您的方法签名满足func sayHello(w http.ResponseWriter, r *http.Request){}类型的方法,它将被我们的HandleFunc()接受。我们将使用sayHello作为我们的函数,并且它将返回两件事,首先是http.ResponseWriter,而第二件事是请求本身作为指针。由于它将是一个 hello 服务器,我们只需将一些数据写回我们的响应,为此,我们将使用我们的响应写入器。由于我们必须监听特定端口,我们将使用http.ListenAndServe。此外,我们使用了5050;只要可用,您可以选择任何端口。我们还向函数添加了nil,如果发生意外情况,它将返回错误,如果错误不是nil,我们将会恐慌。所以让我们继续运行代码,并尝试使用浏览器访问路径。我们必须先运行我们的main.go文件并允许它,以便我们可以访问它:

完成后,我们将不得不打开一个浏览器选项卡,并尝试访问http://localhost:5050/

您将清楚地看到Hello, world。现在,让我们用一个查询字符串或 URL 参数做一个更快的示例。我们将修改方法,以便我们可以决定要对哪个行星说“你好”。检查以下代码:

package main
import "net/http"
func sayHello(w http.ResponseWriter, r *http.Request){
  planet := r.URL.Query().Get("planet")
  w.Write([]byte("Hello, " + planet))
}
func main(){
  http.HandleFunc("/", sayHello)
  err := http.ListenAndServe(":5050", nil)
  if(err != nil){
    panic(err)
  }
}

我们有一个具有查询功能的 URL。我们将读取查询字符串,也称为名为planet的 URL 参数,并将其值分配给一个变量。我们必须停止当前服务器并再次运行它。打开http://localhost:5050/后,我们看不到任何行星的名称:

因此,您可以将 URL 更改为http://localhost:5050/?planet=World并重试:

瞧!现在让我们尝试使用Jupiter相同的方法:

这就是我们如何快速在 Go 中创建自己的 Web 服务器。

在下一节中,我们将看到如何创建一个简单的文件服务器。

创建一个简单的文件服务器

在本节中,我们将看到如何创建一个简单的文件服务器。文件服务器背后的主要思想是提供静态文件,例如图像、CSS 文件或 JavaScript 文件,在我们的代码中,我们将看到如何做到这一点。检查以下代码:

package main

import "net/http"

func main() {
  http.Handle("/", http.FileServer(http.Dir("./images")))
  http.ListenAndServe(":5050", nil)
}

正如您所看到的,我们已经使用了 HTTP 处理,而这个HandlehandleFunc不同,并接受处理程序接口作为第二个参数;第一个参数是pattern。我们将使用一个名为FileServer的特殊 API,在这里它将作为文件服务器工作;我们将在服务器中添加一个位置(图像目录,./images)来提供静态文件。

因此,当请求到达路由路径时,文件服务器将服务请求,并且它将在位置http.Dir("./images")下提供静态文件。我们将使用http.ListenAndServe(":5050", nil),就像在上一节中一样。此外,如前一节所述,我们将运行服务器,允许权限,并在浏览器中键入localhost:5050

您可以看到我们位置上的文件列表,如果我们单击 gopher_aviator.png,它会给我们该位置的图像:

如果我们返回并单击另一个(gopher.png),它将显示以下图像:

或者,您可以注释掉前面代码中的http.Handle("/", http.FileServer(http.Dir("./images"))),并将nil替换为位置。如果您按照我们之前所做的相同步骤,并检查浏览器,它仍然会正确地给我们这两个图像,这就是您如何在 Go 中创建一个简单的文件服务器。

摘要

在本章中,您学习了如何从互联网上下载网页,如何从互联网上下载文件,如何创建一个简单的 Web 服务器,以及如何创建一个简单的文件服务器。下一章将带您了解如何使用 Go 语言在关系型数据库上读取、更新、删除和创建数据的方法。

第十一章:关系数据库

Go 可以与各种关系数据库一起工作,包括 SQL Server、MySQL、Postgres SQL 和 SQLite。在本章中,我们将使用 SQLite。与其他更先进的数据库引擎相比,SQLite 可能稍微受限,但对于我们的示例来说,它基本上是足够的。在本节中,您将学习如何使用 Go 读取、更新、删除和创建关系数据库中的数据。

本章将涵盖以下主题:

  • 从数据库中读取数据

  • 将数据插入数据库

  • 在数据库中更新数据

  • 从数据库中删除数据

从数据库中读取数据

让我们开始学习如何从 SQL 数据库中读取数据。在开始之前,我们将不得不创建一个名为personal.db的数据库。我们将使用一个名为 SQLite 的 DB 浏览器,它允许我们创建新的 SQLite 数据库,编辑它们,添加新记录等。您可以在sqlitebrowser.org/找到有关该工具的更多信息并下载它。这是一个免费工具,它可以在 Windows、macOS 和 Linux 上使用。让我们从一个示例开始。请查看以下屏幕截图:

在这里,我们只有一个名为profile的表。在这个表上的操作足以让我们学会如何与 SQLite 数据库交互,然后您可以使用相同的技术与 MySQL 或 SQL Server 交互。如果您查看屏幕截图,您会看到我们有三条记录和四列:ProfileIdFirstNameLastNameAgeFirstNameLastName列是字符串或文本,Age列是一个数字,ProfileId是我们的主键;它也是一个整数列。因此,让我们继续创建我们自己的结构和代码:

package main
import (_ "github.com/mattn/go-sqlite3"
  "database/sql"
  "fmt"
)

type Profile struct{
  ProfileId int
  FirstName string
  LastName string
  Age int
}
func main(){
  db, err := sql.Open("sqlite3", "./personal.db")
  checkError(err)
  var profile Profile
  rows, err := db.Query("select ProfileId, FirstName, LastName, Age from Profile")
  checkError(err)
  for rows.Next(){
    err := rows.Scan(&profile.ProfileId, &profile.FirstName, &profile.LastName, &profile.Age)
    checkError(err)
    fmt.Println(profile)
  }
  rows.Close()
  db.Close()
}
func checkError(err error) {
  if (err != nil) {
    panic(err)
  }
}

现在,让我们来解释一下代码。我们使用了结构类型将来自 SQL 数据库的数据映射到我们的内存对象。我们需要导入两个包:第一个是 SQL 数据库,第二个是go-sqlite3。我们将进行一个空白导入,这将自动删除 SQL 数据库导入,但这没关系,因为我们稍后会再次导入它。我们之所以进行空白导入,是因为如果此包中有初始化代码,它仍将被执行。这个包将自己注册为底层的 SQL 驱动程序,因此我们仍将使用 SQL 数据库包作为我们的 API,但该 API 将在后台使用go-sqlite3包与我们的数据库交互,正如您将看到的,Go 中的数据库交互非常简单。因此,我们要做的第一件事是打开数据库。当我们使用 SQL 包时,您会看到它自动导入我们的 SQL 数据库。

此外,我们将使用 SQLite 版本 3 的 SQLite 驱动程序,并且我们还将指定我们的数据库位于何处。数据源名称可能会根据您使用的数据库类型而更改;它可能是一个 URL,但在我们的情况下,它是一个文件,因为 SQLite 使用数据文件。因此,我们将输入./personal.db。我们还添加了错误检查实用程序函数,这样我们就不必一直检查错误。我们只需说checkError,错误就会被检查。我们将使用 DB 查询来查询我们的数据库,它返回两件事:一个是行,另一个是错误。数据库查询基本上在这里接受一个 SQL 查询。我们还将使用for循环,rows.next来迭代每一行和rows.scan来获取每一行的值。尊重您的列的顺序很重要,因为它们来自 profile 数据库;如果您需要不同的顺序,您可以在此处指定*:"select * from Profile"。我通常建议明确指定每一行,而不是使用通配符(*)。

当您运行此代码时,您将获得以下输出:

如您所见,我们能够在表中捕获我们的数据库记录(ProfileIdFirstNameLastNameAge)。

现在,让我们快速看一下如何进行过滤。因此,我们将使用where子句,如果您了解 SQL,就会知道where子句用于过滤。我们将按ProfileId进行过滤。请查看此方法的签名:

签名中的第二个参数是占位符的参数,由于它是一个非常古老的函数,只要您有匹配的占位符,就可以提供尽可能多的参数。我们将添加2,如您在以下代码片段中所见;您也可以使用变量名:

var profile Profile
rows, err := db.Query("select ProfileId, FirstName, LastName, Age from Profile where ProfileID = ?", 2)
checkError(err)

现在,让我们继续运行修改后的代码:

package main
import (_ "github.com/mattn/go-sqlite3"
  "database/sql"
  "fmt"
)
type Profile struct{
  ProfileId int
  FirstName string
  LastName string
  Age int
}
func main(){
  db, err := sql.Open("sqlite3", "./personal.db")
  checkError(err)
  var profile Profile
  rows, err := db.Query("select ProfileId, FirstName, LastName, Age from Profile where ProfileID = ?", 2)
  checkError(err)
  for rows.Next(){
    err := rows.Scan(&profile.ProfileId, &profile.FirstName, &profile.LastName, &profile.Age)
    checkError(err)
    fmt.Println(profile)
  }
  rows.Close()
  db.Close()
}
func checkError(err error) {
  if (err != nil) {
    panic(err)
  }
}

运行前述代码后,您将获得以下输出:

因此,我们从数据库中获取了第二条记录。您还可以使用多个where子句,如下面的代码所示:

package main
import (_ "github.com/mattn/go-sqlite3"
  "database/sql"
  "fmt"
)
type Profile struct{
  ProfileId int
  FirstName string
  LastName string
  Age int
}
func main(){
  db, err := sql.Open("sqlite3", "./personal.db")
  checkError(err)
  var profile Profile
  rows, err := db.Query("select ProfileId, FirstName, LastName, Age from Profile where FirstName = ? and LastName = ?","Tarik", "Guney")
  checkError(err)
  for rows.Next(){
    err := rows.Scan(&profile.ProfileId,   &profile.FirstName, &profile.LastName, &profile.Age)
    checkError(err)
    fmt.Println(profile)
  }
  rows.Close()
  db.Close()
}
func checkError(err error) {
  if (err != nil) {
    panic(err)
  }
}

您将获得以下输出:

完美!这就是我们期望的记录。这就是您可以在 Go 中轻松查询 SQL 数据库的方式。

在接下来的部分,我们将看到如何向 SQLite 数据库中插入数据。

将数据插入数据库

在本节中,我们将看到如何向数据库中插入数据。我们将使用我们在上一节中开发的代码,并添加一个新的代码片段,将数据插入到我们的personal.db数据库中。我们将添加statementerr,并使用insert语句将名称添加到我们的Profile表中。我们将指定要将数据插入的列,但我们不会指定ProfileId,因为它是表的主键。我们将输入FirstNameLastNameAge,值将只是占位符。我们还将使用statement.Exec并为占位符提供值,例如JessicaMcArthur30。以下是代码:


package main
import (_ "github.com/mattn/go-sqlite3"
  "database/sql"
  "fmt"
)
type Profile struct{
  ProfileId int
  FirstName string
  LastName string
  Age int
}
func main(){
  db, err := sql.Open("sqlite3", "./personal.db")
  checkError(err)
  statement, err := db.Prepare("insert into Profile (FirstName, LastName, Age) values(?,?,?)")
  checkError(err)
  statement.Exec("Jessica", "McArthur", 30)
  var profile Profile
  rows, err := db.Query("select ProfileId, FirstName, LastName, Age from Profile")
  checkError(err)
  for rows.Next(){
    err := rows.Scan(&profile.ProfileId, &profile.FirstName, &profile.LastName, &profile.Age)
    checkError(err)
    fmt.Println(profile)
  }
  rows.Close()
  db.Close()
}

func checkError(err error) {
  if (err != nil) {
    panic(err)
  }
}

以下是前述代码的输出:

如您所见,我们的 ID 是5,名字是Jessica,姓氏是McArthur,年龄是30。这就是您可以简单地使用 Go 向数据库中插入数据的方式。

在我们的下一部分中,我们将看到如何更新数据库中的现有数据。

在数据库中更新数据

在本节中,我们将看到如何更新数据库中的现有数据。我们将使用我们在上一节中开发的相同代码,但是我们将更改一些字段。

我们将在 SQL 中使用update语句。因此,以下字段将被更改:

statement, err := db.Prepare("update Profile set FirstName = ? where ProfileId = ?")
checkError(err)

statement.Exec("Martha", 5)

一旦我们更新了我们的个人资料记录,我们将列出我们profile表中的所有记录。如果您还记得我们上一节,最后一条记录的个人资料 ID 是5,我们将对其进行更改。上一节输出的最后一行是{5 Jessica McArthur 30},我们现在将更改更新代码的名字:

package main
import (_ "github.com/mattn/go-sqlite3"
  "database/sql"
  "fmt"
)
type Profile struct{
  ProfileId int
  FirstName string
  LastName string
  Age int
}
func main(){
  db, err := sql.Open("sqlite3", "./personal.db")
  checkError(err)
  statement, err := db.Prepare("update Profile set FirstName = ? where ProfileId = ?")
  checkError(err)
  statement.Exec("Martha", 5)
  var profile Profile
  rows, err := db.Query("select ProfileId, FirstName, LastName, Age from Profile")
  checkError(err)
  for rows.Next(){
    err := rows.Scan(&profile.ProfileId, &profile.FirstName, &profile.LastName, &profile.Age)
    checkError(err)
    fmt.Println(profile)
  }
  rows.Close()
  db.Close()
}
func checkError(err error) {
  if (err != nil) {
    panic(err)
  }
}

如果运行代码,您将获得以下输出:

您可以看到,我们已成功将名称Jessica更改为Martha。这就是您可以在 Go 中简单进行更新的方式。

在我们的下一部分中,我们将看到如何从数据库中删除数据。

从数据库中删除数据

在本节中,我们将看到如何从数据库中删除数据。我们仍将使用我们在上一节中开发的旧代码,并对其进行一些小的修改。请查看以下代码:

package main
import (
  _ "github.com/mattn/go-sqlite3"
  "database/sql"
  "fmt"
)
type Profile struct{
  ProfileId int
  FirstName string
  LastName string
  Age int
}
func main(){
  db, err := sql.Open("sqlite3", "./personal.db")
  checkError(err)

  var profile Profile
  rows, err := db.Query("select ProfileId, FirstName, LastName, Age from Profile")
  checkError(err)
  for rows.Next(){
    err := rows.Scan(&profile.ProfileId, &profile.FirstName, &profile.LastName, &profile.Age)
    checkError(err)
    fmt.Println(profile)
  }
  rows.Close()
  db.Close()
}
func checkError(err error) {
  if (err != nil) {
    panic(err)
  }
}

前述代码的输出将如下所示:

现在,要删除数据,比如说第 3 行,您将需要对代码进行一些修改。我们将对statementerrstatement.Exec进行一些小的修改。

因此,为了实现我们想要的,我们将使用以下修改后的代码:

package main
import (
  _ "github.com/mattn/go-sqlite3"
  "database/sql"
  "fmt"
)
type Profile struct{
  ProfileId int
  FirstName string
  LastName string
  Age int
}
func main(){
  db, err := sql.Open("sqlite3", "./personal.db")
  checkError(err)
  statement ,err := db.Prepare("delete from Profile where  ProfileId = ?")
  checkError(err)
  statement.Exec(3)

  var profile Profile
  rows, err := db.Query("select ProfileId, FirstName, LastName, Age from Profile")
  checkError(err)
  for rows.Next(){
    err := rows.Scan(&profile.ProfileId, &profile.FirstName, &profile.LastName, &profile.Age)
    checkError(err)
    fmt.Println(profile)
  }
  rows.Close()
  db.Close()
}
func checkError(err error) {
  if (err != nil) {
    panic(err)
  }
}

你可以看到我们使用了db.Prepare。我们从profile中提供了ProfileId的引导,其中ProfileId是一个占位符。我们还使用了statement.Exec,它将使用参数执行;重要的是参数的数量要与你在代码中放置的占位符数量相匹配。让我们运行代码并检查输出:

因此,如果你比较两个输出,你会发现我们成功删除了第三个条目,现在我们只有4个条目,第三个条目已经被删除。这就是你可以简单地从数据库中删除数据的方法。

总结

这基本上结束了我们的书。你将学到很多关于 Go 的知识,现在你可以在各种场景中有效地运用这些知识。你现在可以通过遵循本书中包含的简洁易懂的配方来克服开发者面临的最常见挑战。祝一切顺利!

posted @ 2024-05-04 22:33  绝不原创的飞龙  阅读(5)  评论(0编辑  收藏  举报