• [技术干货] 【Golang入门教程】Go语言变量的声明-转载
     前言: 在Go语言中,变量的声明是编写程序时的基础之一。  使用 var 关键字可以定义单个或多个变量,并且可以选择是否初始化这些变量。  Go语言的静态类型系统要求在声明变量时指定变量的类型,但也提供了类型推断功能,使得在某些情况下可以省略类型声明。  本文将介绍如何使用 var 关键字进行变量声明,并提供一些示例来帮助理解。  基本类型: Go语言的基本类型有:  bool string int、int8、int16、int32、int64 uint、uint8、uint16、uint32、uint64、uintptr byte // uint8 的别名 rune // int32 的别名 代表一个 Unicode 码 float32、float64 complex64、complex128 当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil 等。  所有的内存在 Go 中都是经过初始化的。  变量的命名规范: 关于Go语言变量命名的一些建议和规范:  使用有意义的名称:  变量名应该能够清晰地描述其用途和含义,避免使用单个字符或者含糊不清的命名。  驼峰命名法:  在Go语言中,推荐使用驼峰命名法(camelCase)命名变量,即第一个单词的首字母小写,后续单词的首字母大写,例如 userName、totalCount。  避免缩写:  尽量避免使用缩写,除非是广为人知的缩写,否则会降低代码的可读性。例如,使用 totalCount 要比 totalCnt 更容易理解。  使用名词命名:  变量名应该是名词,而不是动词,因为变量是用来表示数据或者状态的。  遵循约定:  遵循项目或团队的命名约定,以保持代码风格的一致性。  避免与关键字冲突:  不要使用Go语言的关键字作为变量名,避免引起混淆和错误。  短小精悍:  变量名应该简洁明了,尽量不要过长,但也要保证足够清晰。  保持一致性:  在整个项目中保持变量命名的一致性,避免出现不同的命名风格。  变量的声明 变量的声明是指在程序中明确告诉编译器,某个标识符被用作变量,并可能给予其一个初始值。  变量的声明通常是为了在程序中引入新的标识符,并为其分配存储空间,以便在程序执行期间存储和操作数据。  一般语法: var identifier type // 变量声明,不初始化 var identifier type = expression // 变量声明并初始化 其中:  var 是Go语言的关键字,用于声明变量。 identifier 是变量的名称,应符合命名规则。 type 是变量的数据类型,表示变量可以存储的数据类型。 expression 是变量的初始值(可选),用于初始化变量。 var age int // 声明一个名为 age 的 int 类型变量,不初始化 var name string = "John" // 声明并初始化一个名为 name 的 string 类型变量 var isStudent bool = true // 声明并初始化一个名为 isStudent 的 bool 类型变量 1 2 3 简短语法: Go语言还提供了简短声明语法 :=,用于声明并初始化变量,它可以更简洁地声明变量,但只能在函数内部使用。例如:  age := 25 // 简短声明并初始化一个名为 age 的变量 name := "John" // 简短声明并初始化一个名为 name 的变量 1 2 举例: 在Go语言中,使用 var 关键字声明变量。以下是几个示例:  1.声明单个变量: var age int var name string var isStudent bool 2.声明多个变量: var x, y int var x, y *int var name, email string var isActive, isAdmin bool 3.批量声明变量: var (     a int     b string     c []float32     d func() bool     e struct {         x int     } ) 4.声明并初始化变量: var age int = 25 var name string = "John" var isStudent bool = true 1 2 3 5.声明多个变量并初始化: var x, y int = 10, 20 var name, email string = "Alice", "alice@example.com" var isActive, isAdmin bool = true, false 1 2 3 6.简短语法赋值: age := 25 // 简短声明并初始化一个名为 age 的变量 name := "John" // 简短声明并初始化一个名为 name 的变量 1 2 在Go语言中,如果变量有初始值,则可以省略类型,由编译器根据初始值推断类型:  var age = 25 var name = "John" var isStudent = true 1 2 3 总结: 变量的声明是每个程序员在编写Go语言程序时必须掌握的重要概念之一。  通过使用 var 关键字,我们可以轻松地定义和初始化变量,从而使我们的代码更加清晰和易于理解。  掌握变量声明的基本语法和最佳实践,将有助于编写出可维护和高效的Go语言程序。  希望本文能够更好地理解Go语言中变量声明的使用方法,并在实践中灵活运用。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/jinxinxin1314/article/details/136421022 
  • [技术干货] 【Golang入门教程】Go语言变量的声明-转载
     前言: 在Go语言中,变量的声明是编写程序时的基础之一。  使用 var 关键字可以定义单个或多个变量,并且可以选择是否初始化这些变量。  Go语言的静态类型系统要求在声明变量时指定变量的类型,但也提供了类型推断功能,使得在某些情况下可以省略类型声明。  本文将介绍如何使用 var 关键字进行变量声明,并提供一些示例来帮助理解。  基本类型: Go语言的基本类型有:  bool string int、int8、int16、int32、int64 uint、uint8、uint16、uint32、uint64、uintptr byte // uint8 的别名 rune // int32 的别名 代表一个 Unicode 码 float32、float64 complex64、complex128 当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil 等。  所有的内存在 Go 中都是经过初始化的。  变量的命名规范: 关于Go语言变量命名的一些建议和规范:  使用有意义的名称:  变量名应该能够清晰地描述其用途和含义,避免使用单个字符或者含糊不清的命名。  驼峰命名法:  在Go语言中,推荐使用驼峰命名法(camelCase)命名变量,即第一个单词的首字母小写,后续单词的首字母大写,例如 userName、totalCount。  避免缩写:  尽量避免使用缩写,除非是广为人知的缩写,否则会降低代码的可读性。例如,使用 totalCount 要比 totalCnt 更容易理解。  使用名词命名:  变量名应该是名词,而不是动词,因为变量是用来表示数据或者状态的。  遵循约定:  遵循项目或团队的命名约定,以保持代码风格的一致性。  避免与关键字冲突:  不要使用Go语言的关键字作为变量名,避免引起混淆和错误。  短小精悍:  变量名应该简洁明了,尽量不要过长,但也要保证足够清晰。  保持一致性:  在整个项目中保持变量命名的一致性,避免出现不同的命名风格。  变量的声明 变量的声明是指在程序中明确告诉编译器,某个标识符被用作变量,并可能给予其一个初始值。  变量的声明通常是为了在程序中引入新的标识符,并为其分配存储空间,以便在程序执行期间存储和操作数据。  一般语法: var identifier type // 变量声明,不初始化 var identifier type = expression // 变量声明并初始化 其中:  var 是Go语言的关键字,用于声明变量。 identifier 是变量的名称,应符合命名规则。 type 是变量的数据类型,表示变量可以存储的数据类型。 expression 是变量的初始值(可选),用于初始化变量。 var age int // 声明一个名为 age 的 int 类型变量,不初始化 var name string = "John" // 声明并初始化一个名为 name 的 string 类型变量 var isStudent bool = true // 声明并初始化一个名为 isStudent 的 bool 类型变量 简短语法: Go语言还提供了简短声明语法 :=,用于声明并初始化变量,它可以更简洁地声明变量,但只能在函数内部使用。例如:  age := 25 // 简短声明并初始化一个名为 age 的变量 name := "John" // 简短声明并初始化一个名为 name 的变量 举例: 在Go语言中,使用 var 关键字声明变量。以下是几个示例:  1.声明单个变量: var age int var name string var isStudent bool 2.声明多个变量: var x, y int var x, y *int var name, email string var isActive, isAdmin bool 3.批量声明变量: var (     a int     b string     c []float32     d func() bool     e struct {         x int     } ) 4.声明并初始化变量: var age int = 25 var name string = "John" var isStudent bool = true 5.声明多个变量并初始化: var x, y int = 10, 20 var name, email string = "Alice", "alice@example.com" var isActive, isAdmin bool = true, false 1 2 3 6.简短语法赋值: age := 25 // 简短声明并初始化一个名为 age 的变量 name := "John" // 简短声明并初始化一个名为 name 的变量 在Go语言中,如果变量有初始值,则可以省略类型,由编译器根据初始值推断类型:  var age = 25 var name = "John" var isStudent = true 1 2 3 总结: 变量的声明是每个程序员在编写Go语言程序时必须掌握的重要概念之一。  通过使用 var 关键字,我们可以轻松地定义和初始化变量,从而使我们的代码更加清晰和易于理解。  掌握变量声明的基本语法和最佳实践,将有助于编写出可维护和高效的Go语言程序。  希望本文能够更好地理解Go语言中变量声明的使用方法,并在实践中灵活运用。  ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/jinxinxin1314/article/details/136421022 
  • [技术干货] 使用Golang开发一个简易版shell【转】
    核心流程框架搭建打印提示符读取用户输入解析输入命令执行退出功能内建命令总结之前看到 Github 有个 build-your-own-x 的仓库,觉得挺有意思的,有不少有趣的实现。我就想着多尝试实现些这样的小项目,看看不同的领域。一方面提升我的编程能力,另外,也希望能发现一些不错的项目。今天的项目在 build-your-own-x 中也能找到,即 build your own shell。这个项目能帮助学习 Go 如何进行如 IO 输入输出、如何发起进程调用等操作。核心流程首先,我声明这是个简陋的 shell,但能帮助我们更好理解 Shell。它支持如提示符打印、读取用户输入、解析输入内容、执行命令,另外还支持开发内建命令。接下来,我将从零开始一步步复现我的整个开发过程。框架搭建我从创建一个 Shell 结构体开始,这是整个 shell 程序的核心,它其中包含一个 bufio.Reader 从标准输入读取用户输入。 type Shell struct {   reader      *bufio.Reader }  func NewShell() *Shell {   return &Shell{     reader: bufio.NewReader(os.Stdin),   } } 如上,通过 NewShell 构造函数创建 Shell 实例。这个函数返回一个新的 Shell 实例,其中包含了初始化的 bufio.Reader。为了方便扩展,接下来添加了几个方法,分别是:• PrintPrompt用于打印提示符;• ReadInput用于读取用户输入;• ParseInput用于解析输入并分割成命令名和参数;• ExecuteCmd用于执行命令。定义如下:func (s *Shell) PrintPrompt() func (s *Shell) ReadInput() (string, error) func (s *Shell) ParseInput(input string) (string, []string) func (s *Shell) ExecuteCmd(cmdName string, cmdArgs []string) error它们就是核心流程中最重要的四个方法,都是在 RunAndListen 方法中被调用,如下所示: func (s *Shell) RunAndListen() error {   for {     s.PrintPrompt()      input, err := s.ReadInput()     if err != nil {       fmt.Fprintln(os.Stderr, err)       continue     }      cmdName, cmdArgs := s.ParseInput(input)      if err := s.ExecuteCmd(cmdName, cmdArgs); err != nil {       fmt.Fprintln(os.Stderr, err)       continue     }   } } 主函数 main 的代码不复杂,如下所示:func main() {     s := NewShell()     _ = s.RunAndListen() }通过 NewShell 创建 Shell 示例,调用 RunAndListen 监听用户输入即可。接下来,我开始介绍其中每一步的实现过程。打印提示符首先,打印提示符的代码,非常简单,如下所示:func (s *Shell) PrintPrompt() {   fmt.Print("$ ") }单纯的打印 $ 作为提示符,更复杂的场景可以加上路径提示,如:[~/demo/shell]$修改后的代码如下所示: func (s *Shell) PrintPrompt() {   // 获取当前工作目录   cwd, err := os.Getwd()   if err != nil {     // 如果无法获取工作目录,打印错误并使用默认提示符     fmt.Println("Error getting current directory:", err)     fmt.Print("$ ")     return   }    // 获取当前用户的HOME目录   homeDir, err := os.UserHomeDir()   if err != nil {     fmt.Println("Error getting home directory:", err)     fmt.Print("$ ")     return   }    // 如果当前工作目录以HOME目录开头,则用'~'替换掉HOME目录部分   if strings.HasPrefix(cwd, homeDir) {     cwd = strings.Replace(cwd, homeDir, "~", 1)   }    // 打印包含当前工作目录的提示符   fmt.Printf("[%s]$ ", cwd) } 这是非常粗糙的拿到目录并打印出来。通常 Shell 的提示符是可以自定义,有兴趣可以在这里扩展个接口类型,用于不同提示符的格式化实现。读取用户输入最简单的读取用户输入的代码,代码如下: func (s *Shell) ReadInput() (string, error) {   input, err := s.reader.ReadString('\n')   if err != nil {     return "", err   }    return input, nil } 按 \n 分割命令,分割出来的文本可以理解为一次执行请求。但实际情况是在使用 Shell 时,我们会发现一些特殊符号是要处理,如引号。例如:[~/demo/shell]$ echo ' Hello World! Nice to See you! '下面是一个简化的实现: func (s *Shell) ReadInput() (string, error) {   var input []rune   var inSingleQuote, inDoubleQuote bool    for {     r, _, err := s.reader.ReadRune()     if err != nil {       return "", err     }      // Check for quote toggle     switch r {     case '\'':       inSingleQuote = !inSingleQuote     case '"':       inDoubleQuote = !inDoubleQuote   }      // Break on newline if not in quotes     if r == '\n' && !inSingleQuote && !inDoubleQuote {       break     }      input = append(input, r)   }    return string(input), nil } 如上的代码中,逐一读取输入内容。程序中,通过判断当前是处于引号中,保证正确识别用户输入。如果你读过我之前一篇文章,熟练使用 bufio.Scanner 类型,也可以用它提供的自定义分割规则的方式,在这个场景下也可以使用。我的完整源码 goshell 就是基于 Scanner 实现的。另外,这个输入不支持删除,如果我输出错了,只能退出重来,也是挺头疼的。如果要实现,要依赖于其他库实现。解析输入读取完成,通过 ParseInput 方法解析成 cmdName 和 cmdArgs,代码如下: func (s *Shell) ParseInput(input string) (string, []string) {   input = strings.TrimSuffix(input, "\n")   input = strings.TrimSuffix(input, "\r")    args := strings.Split(input, " ")    return args[0], args[1:] } 真正的 Shell 肯定比这个强大的多了。最容易想到的,一次 shell 执行请求可能包含多个命令,甚至是 shell 脚本。太复杂的能力实现起来太麻烦,我们可以支持一个最简单的能力,分号分割运行多个命令。$ cd /; ls我们修改代码,支持这个能力。 type CmdRequest struct {   Name string   Args []string }  func (s *Shell) ParseInput(input string) []*CmdRequest {   subInputs := strings.Split(input, ";")    cmdRequests := make([]*CmdRequest, 0, len(subInputs))   for _, subInput := range subInputs {     subInput = strings.Trim(subInput, " ")     subInput = strings.TrimSuffix(subInput, "\n")     subInput = strings.TrimSuffix(subInput, "\r")     args := strings.Split(subInput, " ")     cmdRequests = append(cmdRequests, &CmdRequest{Name: args[0], Args: args[1:]})   }    return cmdRequests } 上面代码里,定义了一个新类型 CmdRequest,它用于保存从用户输入解析而来的命令名和命令参数。由于修改了 ParseInput 的返回类型,RunAndListen 中的逻辑就要改动了。如下所示: for {   // ...    cmdRequests := s.ParseInput(input)   for _, cmdRequest := range cmdRequests {     cmdName := cmdRequest.Name     cmdArgs := cmdRequest.Args      if err := s.ExecuteCmd(cmdName, cmdArgs); err != nil {       fmt.Fprintln(os.Stderr, err)       continue     }   } } 到此,通过分号分割多命令也是支持的了。命令执行最后一步就是执行命令了。代码如下所示: func (s *Shell) ExecuteCmd(cmdName string, cmdArgs []string) error {   cmd := exec.Command(cmdName, cmdArgs...)   cmd.Stderr = os.Stderr   cmd.Stdout = os.Stdout    return cmd.Run() } 我使用的是标准库 exec 包中的 Command 类型创建一个命令用于执行外部命令。这个命令的标准输出和标准错误都被设置为当前进程的对应输出,这样命令的输出就可以直接显示给用户。最后,通过调用 cmd.Run() 执行该命令即可。退出功能在初步测试中,我发现 shell 还不支持退出。为了解决这个问题,我在 RunAndListen 循环中加入了对 exit 命令的检查。 for {   cmdName := cmdRequest.Name   cmdArgs := cmdRequest.Args    if cmdName == "exit" {     return nil   }    if err := s.ExecuteCmd(cmdName, cmdArgs); err != nil {   } } 如果用户输入的是exit,循环将终止,直接退出 shell。
  • [技术干货] 详解Golang如何使用Debug库优化代码【转】
    简介debug包的核心组件debug/elf:解析ELF格式文件debug/dwarf:DWARF调试信息的读取debug/macho:Mach-O文件格式的解析debug/pe:PE文件格式的解析debug/gosym:Go程序符号表的解析高级功能与技巧性能分析与pprof包的应用深入理解堆栈跟踪内存分析的实践使用debug/gosym解析符号表debug与其他标准库的结合应用代码示例:综合应用案例常见问题解答总结简介在现代软件开发中,调试是一个不可或缺的环节。特别是对于使用Golang的开发者而言,理解并有效利用标准库中的debug包,可以极大地提高调试效率和代码质量。本文旨在深入介绍Golang的debug标准库,帮助开发者掌握其核心功能和实战应用。Golang作为一种高性能、高效率的编程语言,其标准库提供了丰富的工具和包,以支持各种程序开发和维护任务。其中,debug库作为这些工具中的一员,专注于提供调试支持。它包含多个子包,如debug/elf、debug/dwarf、debug/macho等,各自负责不同调试任务,例如文件格式解析、调试信息读取等。通过合理使用这些工具,开发者可以有效地进行性能分析、错误跟踪和数据检查,从而提升代码质量和运行效率。本文将针对Golang的debug库进行全面解读,涵盖其核心组件、高级功能和实战技巧。文章将通过丰富的代码示例和详细的功能说明,帮助读者深入理解每个组件的使用方法和应用场景。无论您是初入Golang世界的新手,还是寻求深入了解库功能的资深开发者,本文都将为您提供有价值的参考和指导。接下来,我们将深入探索debug库的核心组件,解析它们的基本功能和使用场景,以及它们在实际开发中的应用示例。通过这些内容,您将能够更加熟练地运用Golang的debug库,提高您的软件开发和调试能力。debug包的核心组件debug/elf:解析ELF格式文件ELF(Executable and Linkable Format)格式是在Unix系统中广泛使用的一种标准文件格式,用于可执行文件、目标代码、共享库和核心转储。在Golang的debug/elf包中,提供了一系列工具来读取、解析和检查ELF文件。这对于理解程序如何在操作系统上运行非常有用,尤其是在跨平台开发和调试时。使用debug/elf,开发者可以获取ELF文件的详细信息,如头部信息、段(section)列表和程序头部(program header)等。例如,通过这个包可以检查一个二进制文件是否含有特定的段或符号,从而帮助理解其结构和潜在的执行行为。debug/dwarf:DWARF调试信息的读取DWARF是一种标准的调试数据格式,用于在编译时记录程序中各种元素的信息。Golang的debug/dwarf包提供了读取和解析这些信息的能力,使开发者能够更深入地理解程序的结构和状态。通过使用debug/dwarf,开发者可以访问变量、类型信息、函数调用等详细的调试信息。这对于高级调试任务,如断点设置、性能分析和复杂错误排查非常有帮助。debug/macho:Mach-O文件格式的解析Mach-O是macOS操作系统中使用的一种文件格式,用于可执行文件和动态库。debug/macho包允许开发者在Go中读取和解析这种格式的文件。这在进行macOS或iOS平台的程序开发和调试时尤为重要。利用debug/macho,开发者可以获取Mach-O文件的结构信息,例如段、符号表和动态库依赖等。这样的信息对于理解程序在Apple平台上的行为和性能优化至关重要。debug/pe:PE文件格式的解析PE(Portable Executable)格式是在Windows操作系统中使用的可执行文件格式。debug/pe包提供了在Go中读取和解析PE格式文件的功能。对于那些需要在Windows平台上开发和调试程序的Golang开发者来说,这个包是一个重要的工具。通过debug/pe,可以访问Windows可执行文件的关键信息,如头部信息、段和导入/导出表。这对于深入理解程序在Windows环境中的运行和交互非常有价值。debug/gosym:Go程序符号表的解析debug/gosym包用于解析Go程序的符号表。符号表是编译后的程序中包含的,用于存储变量名、函数名等信息的部分。这个包对于那些需要深入了解程序内部结构和函数调用关系的开发者来说非常有用。利用debug/gosym,开发者可以在运行时获取关于程序结构的详细信息,这对于调试和性能优化尤其重要。
  • [技术干货] 【Golang】go编程语言适合哪些项目开发?转载
     前言 在当今数字化时代,软件开发已成为各行各业的核心需求之一。  而选择适合的编程语言对于项目的成功开发至关重要。  本文将重点探讨Go编程语言适合哪些项目开发,以帮助读者在选择合适的编程语言时做出明智的决策。  Go 编程语言适合哪些项目开发? Go是由Google开发的一种开源编程语言,于2009年首次发布。它的设计目标是提供一种简单、高效、可靠的编程语言,适用于大规模项目的开发。以下是Go语言适合的项目类型:  1. 网络编程项目: Go语言具有出色的网络编程能力,特别适合开发网络服务和分布式系统。它提供了高效的并发模型,能够处理大量并发连接,同时保持良好的性能。  2. 大数据处理项目: Go语言的并发模型和高性能使其成为处理大数据量的理想选择。它能够轻松处理并发任务,提高数据处理的效率和速度。  3. 云计算项目: Go语言对于云计算项目来说非常适用。它提供了丰富的标准库和强大的并发模型,可以简化开发过程,并具备高度可扩展性。  此外,基于云的应用程序通常比使用传统方法构建的应用程序更快且可扩展性更强,因为它们在已针对性能和可扩展性进行了优化的环境中运行。  因此,在开发基于云的应用程序时,Golang 是你的最佳选择。  4. Web开发项目: Go语言拥有轻量级的HTTP服务器,使其成为构建高性能Web应用程序的理想选择。  它支持快速开发和部署,并且具有良好的性能和可靠性。  很多人使用 Golang 是因为它非常快,而且它可以用来并行运行进程,这样他们就不必互相等待。  它内置了对并发的支持,并促进了单个进程中线程和处理器之间的并行性。  这可以使你的网站更容易快速加载并为你提供最佳的用户体验。  5. 嵌入式系统项目: 由于其小巧的二进制文件和低内存占用,Go语言非常适合嵌入式系统的开发。它可以在资源受限的环境中运行,并提供了简单易用的接口和工具。  6.API开发: 它具有以下特点,使其成为API开发的理想选择:  1. 并发性能: Go语言内置了轻量级的协程(goroutine)和通道(channel),使并发编程变得简单而高效。这使得Go语言非常适合处理高并发的API请求,能够轻松处理大量的并发连接。  2. 高性能: Go语言通过优化编译器和运行时环境,提供了出色的性能。它的执行速度快,内存占用低,这使得Go语言在处理大数据量和高负载的API请求时表现出色。  3. 标准库支持: Go语言内置了丰富的标准库,包括用于HTTP请求处理、JSON解析、加密、并发控制等常用功能的库。这些库使得API开发变得更加简单和高效。  4. 跨平台支持: Go语言可以编译成机器码,而不依赖于虚拟机或解释器。这使得Go语言的API可以在不同的操作系统和硬件平台上运行,提供了更大的灵活性和可移植性。  5. 简洁易用: Go语言的语法简洁明了,具有良好的可读性和可维护性。它提供了简单而强大的工具和接口,使API开发变得更加简单和快速。  Go语言在API开发方面具有并发性能、高性能、标准库支持、跨平台支持以及简洁易用等优势。  这使得Go语言成为开发高效、可靠且易于维护的API的理想选择。希望本文能够帮助读者了解Go语言在API开发中的应用,并在项目开发中做出明智的选择。  总结 Go编程语言凭借其简洁、高效和可靠的特性,适用于各种项目开发。无论是网络编程、大数据处理、云计算、Web开发还是嵌入式系统,Go语言都能够提供出色的性能和可扩展性。希望本文能够帮助读者了解Go语言的优势,并在项目开发中做出明智的选择。  ———————————————— 版权声明:本文为CSDN博主「The-Venus」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/jinxinxin1314/article/details/135005140 
  • [专题汇总] 它来了,它来了,8月份技术干货它来了。
     大家好,8月份给大家整理带来的是codeArts版本最新的技术干货合集,涉及java,golang,python希望可以帮助到大家。  1.shell检查硬盘状态 https://bbs.huaweicloud.com/forum/thread-149840-1-1.html  2.Golang之sync.Pool对象池对象重用机制总结【转】 https://bbs.huaweicloud.com/forum/thread-0265128851946454378-1-1.html  3.Go defer 去掉闭包函数及用法分析【转】 https://bbs.huaweicloud.com/forum/thread-0236128851600101424-1-1.html  4.Golang中的闭包(Closures)详解【转】 https://bbs.huaweicloud.com/forum/thread-0297128851484457502-1-1.html  5.Go语言之io.ReadAtLeast函数的基本使用和原理解析【转】 https://bbs.huaweicloud.com/forum/thread-0299128851274052618-1-1.html  6.Go语言应用闭包之返回函数【转】 https://bbs.huaweicloud.com/forum/thread-0224128850654756516-1-1.html  7.一文详解Go语言io包中的discard类型【转】 https://bbs.huaweicloud.com/forum/thread-0265128850459435377-1-1.html  8.golang的tunny的用法示例教程【转】 https://bbs.huaweicloud.com/forum/thread-0263128850335015408-1-1.html  9.Go语言tunny的workerWrapper使用教程示例【转】 https://bbs.huaweicloud.com/forum/thread-0265128850197454376-1-1.html  10.GO语言实现日志切割的示例详解【转】 https://bbs.huaweicloud.com/forum/thread-0236128849958046421-1-1.html  11.一文带你了解Golang中的泛型【转】 https://bbs.huaweicloud.com/forum/thread-0263128849797446407-1-1.html  12.Go整合ElasticSearch的示例代码【转】 https://bbs.huaweicloud.com/forum/thread-0299128848232195616-1-1.html  13.Golang仿ps实现获取Linux进程信息【转】 https://bbs.huaweicloud.com/forum/thread-0286128848118160409-1-1.html  14.Go数据结构之HeapMap实现指定Key删除堆【转】 https://bbs.huaweicloud.com/forum/thread-0224128829750512487-1-1.html  15.基于Golang编写贪吃蛇游戏【转】 https://bbs.huaweicloud.com/forum/thread-0275128829535850518-1-1.html  16.用Go获取短信验证码的示例代码【转】 https://bbs.huaweicloud.com/forum/thread-0275128829460656517-1-1.html  17.golang使用sync.singleflight解决热点缓存穿透问题【转】 https://bbs.huaweicloud.com/forum/thread-0275128829216193516-1-1.html  18.vue 实现 pdf 预览功能 https://bbs.huaweicloud.com/forum/thread-0224128574304525257-1-1.html  19.Nacos部署中的一些常见问题 https://bbs.huaweicloud.com/forum/thread-0286128573822017203-1-1.html  20.用ngrok实现内网穿透,一行命令就搞定! https://bbs.huaweicloud.com/forum/thread-0224128573537080255-1-1.html  21.过滤器+slf4j(MDC)实现日志记录请求ID https://bbs.huaweicloud.com/forum/thread-0263128573250127242-1-1.html  22.阿里EasyExcel快速导出demo https://bbs.huaweicloud.com/forum/thread-0265128573093017187-1-1.html  23.业务开发时,接口不能对外暴露怎么办? https://bbs.huaweicloud.com/forum/thread-0224128572747042254-1-1.html  24.centos同步windows时间 https://bbs.huaweicloud.com/forum/thread-0286128572497851201-1-1.html  25.切面实现下单请求防重提交功能(自定义注释@repeatSubmit) https://bbs.huaweicloud.com/forum/thread-0263128572383761239-1-1.html 
  • [技术干货] Golang之sync.Pool对象池对象重用机制总结【转】
    sync.Pool作用对象重用机制,为了减少GC,sync.Pool是可伸缩的,并发安全的两个结构体type Pool struct {     local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal     localSize uintptr        // size of the local array     // New optionally specifies a function to generate     // a value when Get would otherwise return nil.     // It may not be changed concurrently with calls to Get.     New func() interface{} } // Local per-P Pool appendix. type poolLocal struct {     private interface{}   // Can be used only by the respective P.     shared  []interface{} // Can be used by any P.     Mutex                 // Protects shared.     pad     [128]byte     // Prevents false sharing. }Pool是提供外部使用的对象,Pool有两个重要的成员,local是一个poolLocal数组,localSize是工作线程的数量( runtime.GOMAXPROCS(0)),Pool为每个线程分配一个poolLocal对象写入和读取Pool.Get 先获取当前线程私有值(poolLocal.private)获取否则则从共享列表(poolLocal.shared)获取否则则从其他线程的共享列表获取否则直接通过New()分配一个返回值Pool.Put 当前线程私有制为空,赋值给私有值否则追加到共享列表sync.Pool注意点临时性,当发生GC时,Pool的对象会被清除,并且不会有通知无状态,当前线程中的PoolLocal.shared的对象可能会被其他线程偷走大规模Goroutine的瓶颈会对垃圾回收(gc)造成负担,需要频繁的释放内存虽然goroutine只分配2KB,但是大量gorotine会消耗完内存,并且gc也是goroutine调用的原理和作用原理类似是IO多路复用,就是尽可能复用,池化的核心优势就在于对goroutine的复用。此举首先极大减轻了runtime调度goroutine的压力,其次,便是降低了对内存的消耗
  • [技术干货] Go defer 去掉闭包函数及用法分析【转】
    引言在 Go 语言里,defer 关键字是大家很爱用的。因为他有着 defer+recover+panic 的组合拳打法,还有种各种 defer close 等常用场景。defer 常见用法在语法上,Go defer 的代码示例如下:package mainimport "fmt"func main() {defer fmt.Println("煎鱼你好!")fmt.Println("放学别走")}输出结果:放学别走煎鱼你好!那 defer 在 Go 里的常见用法有哪些呢?首先是上文用到的,直接 defer + 函数:defer f()其次是 defer+闭包的方式:defer func() {result := f()// do something with result}()其他还有在面试题上常被考究的传参变形:func f1() int {i := 1defer func() {i++}()...}func f2() int {i := 1defer func(i int) {i++}(i)....}这些代码看起来,我们总是在对 defer 做闭包的各种声明和使用。defer 会不会就是和闭包天生一对?新提案:defer 代码块最近大家也在讨论一个与之相关的 Go 提案《proposal: Go 2: deferred code blocks》,由 @Damien Lloyd 提出,想看看有没有机会把 defer 的新语法落地。作者给出的代码示例:func fn() {     f, err := os.Create("eddycjy.txt")     if err != nil {         panic(err)     }     defer {        err := f.Close()        if err != nil {           panic(err)        }     } }在 fn 函数,声明了 defer {...},代码块内是对 f.Close 的兜底判断和异常抛出。在函数结束后执行这整个代码块。反对的声音当然,这看着似乎是比较美好的。看起来原提案作者只是简化了 defer 是的闭包使用,调整了作用域的范围。但在社区内其实遭受比较多的反对声音。包含但不限于:1、收益比不高:这个提案只是避免了 func() 和 () 等闭包声明,但是却要增加新的 defer 语法(语言语法更改会带来高昂成本),这个变更的 ROI 不高。2、破坏兼容性:原 defer 关键字调用总是会跟着函数的词法调用,有良好的一致性。如果进行修改,会产生新的隐晦,破坏一致性。也会对现有的许多工具(例如:静态分析工具)产生影响,全要改。3、作用域问题:原本 defer func{}() 的代码块结构下,你的代码作用域都限于闭包函数下。而使用新的 defer {} 的结构,该返回和操作,是否应该会影响到外部函数的结果?(这是最有争议的一点,作者也比较前言不搭后语,没明确指明语法意思)总结一开始乍一眼一看,感觉只是把 defer 关键字语句简化一下,好像特别好,省了几个单词。就像 if err != nil 也会有提要用 Rust 的 ? 等用法来替代的。经过社区网友们指出后,发现这里猫腻不少。一门已经有 10+ 年的编程语言,还有 Go1 兼容性保障的。做出这类带作用域的提案变更,是有比较大的风险的。同时对于 Go 工具链的影响,也是非常大的。一改,直接都完犊子了。确实需要尽量深思。原作者完全没提到
  • [技术干货] Golang中的闭包(Closures)详解【转】
    在讲解闭包之前先看一下 Golang 中的匿名函数。匿名函数(Anonymous Functions)匿名函数也可以称为函数字面量、lambda 函数或者闭包。闭包的概念起源于 lambda 微积分中表达式的数学求值。从技术上讲,匿名函数和闭包之间有细微的区别:匿名函数是没有名称的函数,而闭包则是函数的实例。在 Golang 中要实现闭包,是离不开匿名函数的。先看一个普通函数的例子,例如:func add(x, y int) {     fmt.Println(x + y) }调用方式如下:add(1, 2) // 输出 3接下来看下如何使用匿名函数来实现相同的功能:func(x, y int) {         fmt.Println(x + y)     }(1, 2)这个匿名函数和上面的普通的函数的功能是一样的,区别是没有名字定义之后就直接调用接下来,使用通过创建一个返回一个函数的函数的方式来使用一个匿名函数。函数一般都是返回整数、字符串、结构体等基本类型,但是在 Golang 中一个函数可以返回另一个函数。如下是 Golang 官方的一个例子:func adder() func(int) int {     sum := 0     return func(x int) int {         sum += x         return sum     } }这个函数的返回类型是 func(int) int 类型的函数,可以将这个函数的返回值赋值给一个变量,然后可以像调用一个函数的方式使用调用这个变量,例如:pos := adder() pos(1)闭包(Closures)通过上文的讲解我们已经知道了匿名函数的定义以及使用方式,也了解了一个函数可以返回另一个函数,接下来讲解下闭包。在 Golang 中,闭包是一个引用了作用域之外的变量的函数。闭包的存在时间可以超过创建它的作用域,因此它可以访问该作用域中的变量,即使在该作用域被销毁之后。上文中的 adder() 函数返回的就是一个典型的闭包。Golang 中的匿名函数也被称为闭包,匿名函数是一种特殊类型的函数,没有名称,闭包可以被认为是一种特殊类型的匿名函数。Golang 中的闭包由两部分组成:函数体和函数执行时的上下文环境。函数体定义了闭包的逻辑,上下文环境则包含了函数外部的变量。当闭包被创建时,会将外部变量的引用保存在上下文环境中,并且在函数体内部可以随时访问这些外部变量。看个将上文中的 adder() 函数稍作修改的例子:package main import "fmt" func adder() func(int) int {     sum := 0     return func(x int) int {         fmt.Println("执行前 sum =", sum)         sum += x         return sum     } } func main() {     pos := adder()     for i := 0; i < 4; i++ {         fmt.Println("执行后 sum =", pos(1))     } }运行结果如下:执行前 sum = 0执行后 sum = 1执行前 sum = 1执行后 sum = 2执行前 sum = 2执行后 sum = 3执行前 sum = 3执行后 sum = 4可以看出,闭包函数引用的外部变量被保存在了上下文环境中(一直不被销毁),每次执行闭包,闭包内的变量又保存了上一次运行后的值。
  • [技术干货] Go语言之io.ReadAtLeast函数的基本使用和原理解析【转】
    1. 引言io.ReadAtLeast 函数是Go标准库提供的一个非常好用的函数,能够指定从数据源最少读取到的字节数。本文我们将从io.ReadAtLeast 函数的基本定义出发,讲述其基本使用和实现原理,以及一些注意事项,基于此完成对io.ReadAtLeast 函数的介绍。2. 基本说明2.1 基本定义io.ReadAtLeast 函数用于从读取器(io.Reader)读取至少指定数量的字节数据到缓冲区中。函数定义如下:1func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)其中r 是数据源,从它读取数据,而buf是用于接收读取到的数据的字节切片,min是要读取的最小字节数。io.ReadAtLeast 函数会尝试从读取器中最少读取 min 个字节的数据,并将其存储在 buf 中。2.2 使用示例下面是一个示例代码,演示如何使用 io.ReadAtLeast 函数从标准输入读取至少 5 个字节的数据:package main import (         "fmt"         "io"         "os" ) func main() {         buffer := make([]byte, 10)         n, err := io.ReadAtLeast(os.Stdin, buffer, 5)         if err != nil {                 fmt.Println("读取过程中发生错误:", err)                 return         }         fmt.Printf("成功读取了 %d 个字节:%s\n", n, buffer) }在这个例子中,我们创建了一个长度为 10 的字节切片 buffer,并使用 io.ReadAtLeast 函数从标准输入读取至少 5 个字节的数据到 buffer 中。下面是一个可能的输出,具体如下:12hello,world成功读取了 10 个字节:hello,worl这里其指定 min 为5,也就是最少读取5个字节的数据,此时调用io.ReadAtLeast函数一次性读取到了10个字节的数据,此时也满足要求。这里也间接说明了io.ReadAtLeast只保证最少要读取min个字节的数据,但是并不限制更多数据的读取。3. 实现原理在了解了io.ReadAtLeast 函数的基本定义和使用后,这里我们来对io.ReadAtLeast 函数的实现来进行基本的说明,加深对io.ReadAtLeast 函数的理解。其实 io.ReadAtLeast 的实现非常简单,其定义一个变量n, 保存了读取到的字节数,然后不断调用数据源Reader中的 Read 方法读取数据,然后自增变量n 的值,直到 n 大于 最小读取字节数为止。下面来看具体代码的实现:func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {    // 传入的缓冲区buf长度 小于 最小读取字节数min的值,此时直接返回错误    if len(buf) &lt; min {       return 0, ErrShortBuffer    }    // 在 n &lt; min 时,不断调用Read方法读取数据    // 最多读取 len(buf) 字节的数据    for n &lt; min &amp;&amp; err == nil {       var nn int       nn, err = r.Read(buf[n:])       // 自增 n 的值       n += nn    }    if n &gt;= min {       err = nil    } else if n &gt; 0 &amp;&amp; err == EOF {       // 读取到的数据字节数 小于 min值,同时数据已经全部读取完了,此时返回 ErrUnexpectedEOF       err = ErrUnexpectedEOF    }    return }4. 注意事项4.1 注意无限等待情况的出现从上面io.ReadAtLeast 的实现可以看出来,如果一直没有读取到指定数量的数据,同时也没有发生错误,将一直等待下去,直到读取到至少指定数量的字节数据,或者遇到错误为止。下面举个代码示例来展示下效果:func main() {    buffer := make([]byte, 5)    n, err := io.ReadAtLeast(os.Stdin, buffer, 5)    if err != nil {       fmt.Println("读取过程中发生错误:", err)       return    }    fmt.Printf("成功读取了 %d 个字节:%s\n", n, buffer) }在上面代码的例子中,会调用io.ReadAtLeast 函数从标准输入中读取 5 个字节的数据,如果标准输入一直没有输够5个字节,此时这个函数将会一直等待下去。比如下面的这个输入,首先输入了he两个字符,然后回车,由于还没有达到5个字符,此时io.ReadAtLeast函数一直不会返回,只有再输入llo这几个字符后,才满足5个字符,才能够继续执行,所以在使用io.ReadAtLeast函数时,需要注意无限等待的情况。he llo 成功读取了 5 个字节:he ll4.2 确保 buf 的大小足够容纳至少 min 个字节的数据在调用io.ReadAtLeast函数时,需要保证缓冲区buf的大小需要满足min,如果缓冲区的大小比 min 参数还小的话,此时将永远满足不了 最少读取 min个字节数据的要求。从上面io.ReadAtLeast 的实现可以看出来,如果其发现buf的长度小于 min,其也不会尝试去读取数据,其会直接返回一个ErrShortBuffer 的错误,下面通过一个代码展示下效果:func main() {    buffer := make([]byte, 3)    n, err := io.ReadAtLeast(os.Stdin, buffer, 5)    if err != nil {       fmt.Println("读取过程中发生错误:", err)       return    }    fmt.Printf("成功读取了 %d 个字节:%s\n", n, buffer) }比如上述函数中,指定的buffer的长度为3,但是io.ReadAtLeast要求最少读取5个字节,此时buffer并不能容纳5个字节的数据,此时将会直接ErrShortBuffer错误,如下:读取过程中发生错误: short buffer5. 总结io.ReadAtLeast函数是Go语言标准库提供的一个工具函数,能够从数据源读取至少指定数量的字节数据到缓冲区中。 我们先从 io.ReadAtLeast 函数的基本定义出发,之后通过一个简单的示例,展示如何使用io.ReadAtLeast函数实现至少读取指定字节数据。接着我们讲述了io.ReadAtLeast函数的实现原理,其实就是不断调用源Reader的Read方法,直接读取到的数据数满足要求。在注意事项方面,则强调了调用io.ReadAtLeast 可能出现无限等待的问题,以及需要确保 buf 的大小足够容纳至少 min 个字节的数据。
  • [技术干货] Go语言应用闭包之返回函数【转】
    应用闭包在程序 function_return.go 中我们将会看到函数 Add2 和 Adder 均会返回签名为 func(b int) int 的函数:func Add2() (func(b int) int) func Adder(a int) (func(b int) int)函数 Add2 不接受任何参数,但函数 Adder 接受一个 int 类型的整数作为参数。我们也可以将 Adder 返回的函数存到变量中(function_return.go)。package main import "fmt" func main() {     // make an Add2 function, give it a name p2, and call it:     p2 := Add2()     fmt.Printf("Call Add2 for 3 gives: %v\n", p2(3))     // make a special Adder function, a gets value 2:     TwoAdder := Adder(2)     fmt.Printf("The result is: %v\n", TwoAdder(3)) } func Add2() func(b int) int {     return func(b int) int {         return b + 2     } } func Adder(a int) func(b int) int {     return func(b int) int {         return a + b     } }输出:Call Add2 for 3 gives: 5The result is: 5下例为一个略微不同的实现(function_closure.go):package main import "fmt" func main() {     var f = Adder()     fmt.Print(f(1), " - ")     fmt.Print(f(20), " - ")     fmt.Print(f(300)) } func Adder() func(int) int {     var x int     return func(delta int) int {         x += delta         return x     } }函数 Adder() 现在被赋值到变量 f 中(类型为 func(int) int)。输出:1 - 21 - 321三次调用函数 f 的过程中函数 Adder() 中变量 delta 的值分别为:1、20 和 300。我们可以看到,在多次调用中,变量 x 的值是被保留的,即 0 + 1 = 1,然后 1 + 20 = 21,最后 21 + 300 = 321:闭包函数保存并积累其中的变量的值,不管外部函数退出与否,它都能够继续操作外部函数中的局部变量。这些局部变量同样可以是参数,例如之前例子中的 Adder(as int)。这些例子清楚地展示了如何在 Go 语言中使用闭包。在闭包中使用到的变量可以是在闭包函数体内声明的,也可以是在外部函数声明的:var g int go func(i int) {     s := 0     for j := 0; j < i; j++ { s += j }     g = s }(1000) // Passes argument 1000 to the function literal.这样闭包函数就能够被应用到整个集合的元素上,并修改它们的值。然后这些变量就可以用于表示或计算全局或平均值。学习并理解以下程序的工作原理:一个返回值为另一个函数的函数可以被称之为工厂函数,这在您需要创建一系列相似的函数的时候非常有用:书写一个工厂函数而不是针对每种情况都书写一个函数。下面的函数演示了如何动态返回追加后缀的函数:func MakeAddSuffix(suffix string) func(string) string {     return func(name string) string {         if !strings.HasSuffix(name, suffix) {             return name + suffix         }         return name     } }现在,我们可以生成如下函数:addBmp := MakeAddSuffix(".bmp") addJpeg := MakeAddSuffix(".jpeg")然后调用它们:addBmp("file") // returns: file.bmp addJpeg("file") // returns: file.jpeg可以返回其它函数的函数和接受其它函数作为参数的函数均被称之为高阶函数,是函数式语言的特点。我们已经在第 6.7 中得知函数也是一种值,因此很显然 Go 语言具有一些函数式语言的特性。闭包在 Go 语言中非常常见,常用于 goroutine 和管道操作
  • [技术干货] 一文详解Go语言io包中的discard类型【转】
    1. 引言io.discard是Go语言标准库提供一个结构体类型,其在丢弃不需要的数据场景下非常好用。本文我们将从io.discard 类型的基本定义出发,讲述其基本使用和实现原理,接着简单描述 io.discard 的使用场景,基于此完成对 io.discard 类型的介绍。2. 介绍2.1 基本定义io.discard 是 Go语言提供的一个Writer,这个Writer 比较特殊,其不会做任何事情。它会将写入的数据立即丢弃,不会做任何处理。其定义如下:type discard struct{} func (discard) Write(p []byte) (int, error) {} func (discard) WriteString(s string) (int, error) {} func (discard) ReadFrom(r Reader) (n int64, err error) {}discard 结构体类型没有定义任何字段,同时还提供了Write ,ReadFrom和WriteString 方法,Write 方法和WriteString 方法分别接收字节切片和字符串,然后返回写入的字节数。同时还实现了io.ReaderFrom 接口,这个是为了在使用 io.Copy 函数时,将数据从源复制到io.discard 时,避免不必要的操作。从上面discard 的定义可以看起来,其不是一个公开类型的结构体类型,所以我们并不能创建结构体实例。事实上Go语言提供了一个io.discard 实例的预定义常量,我们直接使用,无需自己创建实例,定义如下:var Discard Writer = discard{}2.2 使用说明下面通过一个丢弃网络连接中不再需要的数据的例子,来展示io.Discard 的使用,代码示例如下:package main import (         "fmt"         "io"         "net"         "os" ) func discardData(conn net.Conn, bytesToDiscard int64) error {         _, err := io.CopyN(io.Discard, conn, bytesToDiscard)         return err } func main() {         conn, err := net.Dial("tcp", "example.com:80")         if err != nil {                 fmt.Println("连接错误:", err)                 return         }         defer conn.Close()         bytesToDiscard := int64(1024) // 要丢弃的字节数         err = discardData(conn, bytesToDiscard)         if err != nil {                 fmt.Println("丢弃数据错误:", err)                 return         }         fmt.Println("数据已成功丢弃。") }在上面示例中,我们建立了网络连接,然后连接中的前1024个字节的数据是不需要的。这个时候,我们通过io.CopyN 函数将数据从conn 拷贝到io.Discard 当中,基于io.Discard 丢弃数据的特性,成功将连接的前1024个字节丢弃掉,而不需要自定义缓冲区之类的操作,简单高效。3. 实现原理io.Discard的目的是在某些场景下提供一个满足io.Writer接口的实例,但用户对于数据的写入操作并不关心。它可以被用作一个黑洞般的写入目标,默默地丢弃所有写入它的数据。所以io.discard 的实现也相对比较简单,不对输入的数据进行任何处理即可,下面我们来看具体的实现。首先是io.discard 结构体的定义,没有定义任何字段,因为本来也不需要执行任何写入操作:type discard struct{}而对于Write 和 WriteString 方法,其直接返回了传入参数的长度,往该Writer 写入的数据不会被写入到其他地方,而是被直接丢弃:func (discard) Write(p []byte) (int, error) {    return len(p), nil } func (discard) WriteString(s string) (int, error) {    return len(s), nil }同时discard 也实现了io.ReaderFrom 接口,实现了ReadFrom 方法,实现也是非常简单,从blackHolePool 缓冲池中获取字节切片,然后不断读取数据,读取完成之后,再将字节切片重新放入缓冲池当中:// 存在一个字节切片缓冲池 var blackHolePool = sync.Pool{    New: func() any {       b := make([]byte, 8192)       return &b    }, } func (discard) ReadFrom(r Reader) (n int64, err error) {    // 从缓冲池中取出一个 字节切片    bufp := blackHolePool.Get().(*[]byte)    readSize := 0    for {       // 不断读取数据,bufp 只是作为一个读取数据的中介,读取到的数据并无意义       readSize, err = r.Read(*bufp)       n += int64(readSize)       if err != nil {          // 将字节切片 重新放入到 blackHolePool 当中          blackHolePool.Put(bufp)          if err == EOF {             return n, nil          }          return       }    } }在io.Copy 函数中,将调用discard 中的ReadFrom 方法,能够将Writer中的所有数据读取完,然后丢弃掉。4. 使用场景io.Discard 给我们提供了一个io.Writer 接口的实例,同时其又不会真实得写入数据,这个在某些场景下非常有用。有时候,我们可能需要一个实现io.Writer 接口的实例,但是我们并不关心数据写入Writer 的结果,也不关心数据是否写到了哪个地方,此时io.Discard 就给我们提供了一个方便的解决方案。同时io.Discard 可以作为一个黑洞写入目标,能够将数据默默丢弃掉,不会进行实际的处理和存储。所以如果我们想要丢弃某些数据,亦或者是需要一个io.Writer接口的实例,但是对于写入结果不需要关注时,此时使用io.Discard&nbsp;是非常合适的。5. 总结io.discard 函数是Go语言标准库中一个实现了Writer接口的结构体类型,能够悄无声息得实现数据的丢弃。 我们先从io.discard 类型的基本定义出发,之后通过一个简单的示例,展示如何使用io.discard 类型实现对不需要数据的丢弃。接着我们讲述了io.discard 类型的实现原理,其实就是不对写入的数据执行任何操作。在使用场景下,我们想要丢弃某些数据,亦或者是需要一个io.Writer接口的实例,但是对于写入结果不需要关注时,此时使用io.Discard 是非常合适的。基于此,便完成了对io.discard 类型的介绍,希望对你有所帮助。
  • [技术干货] golang的tunny的用法示例教程【转】
    本文主要研究一下tunnyWorkertype Worker interface {     // Process will synchronously perform a job and return the result.     Process(interface{}) interface{}     // BlockUntilReady is called before each job is processed and must block the     // calling goroutine until the Worker is ready to process the next job.     BlockUntilReady()     // Interrupt is called when a job is cancelled. The worker is responsible     // for unblocking the Process implementation.     Interrupt()     // Terminate is called when a Worker is removed from the processing pool     // and is responsible for cleaning up any held resources.     Terminate() } Worker接口定义了Process、BlockUntilReady、Interrupt、Terminate方法closureWorkertype closureWorker struct {     processor func(interface{}) interface{} } func (w *closureWorker) Process(payload interface{}) interface{} {     return w.processor(payload) } func (w *closureWorker) BlockUntilReady() {} func (w *closureWorker) Interrupt()       {} func (w *closureWorker) Terminate()       {}closureWorker定义了processor属性,它实现了Worker接口的Process、BlockUntilReady、Interrupt、Terminate方法,其中Process方法委托给processorcallbackWorkertype callbackWorker struct{} func (w *callbackWorker) Process(payload interface{}) interface{} {     f, ok := payload.(func())     if !ok {         return ErrJobNotFunc     }     f()     return nil } func (w *callbackWorker) BlockUntilReady() {} func (w *callbackWorker) Interrupt()       {} func (w *callbackWorker) Terminate()       {} callbackWorker定义了processor属性,它实现了Worker接口的Process、BlockUntilReady、Interrupt、Terminate方法,其中Process方法执行的是payload函数Pooltype Pool struct {     queuedJobs int64     ctor    func() Worker     workers []*workerWrapper     reqChan chan workRequest     workerMut sync.Mutex } func New(n int, ctor func() Worker) *Pool {     p := &Pool{         ctor:    ctor,         reqChan: make(chan workRequest),     }     p.SetSize(n)     return p } func NewFunc(n int, f func(interface{}) interface{}) *Pool {     return New(n, func() Worker {         return &closureWorker{             processor: f,         }     }) } func NewCallback(n int) *Pool {     return New(n, func() Worker {         return &callbackWorker{}     }) } Pool定义了queuedJobs、ctor、workers、reqChan、workerMut属性;New方法根据n和ctor创建Pool;NewFunc方法根据n和f来创建closureWorker;NewCallback方法创建callbackWorkerProcessfunc (p *Pool) Process(payload interface{}) interface{} {     atomic.AddInt64(&p.queuedJobs, 1)     request, open := <-p.reqChan     if !open {         panic(ErrPoolNotRunning)     }     request.jobChan <- payload     payload, open = <-request.retChan     if !open {         panic(ErrWorkerClosed)     }     atomic.AddInt64(&p.queuedJobs, -1)     return payload } Process方法首先递增queuedJobs,然后从reqChan读取request,然后往jobChan写入payload,之后再等待retChan,最后递减queuedJobsSetSizefunc (p *Pool) SetSize(n int) {     p.workerMut.Lock()     defer p.workerMut.Unlock()     lWorkers := len(p.workers)     if lWorkers == n {         return     }     // Add extra workers if N > len(workers)     for i := lWorkers; i < n; i++ {         p.workers = append(p.workers, newWorkerWrapper(p.reqChan, p.ctor()))     }     // Asynchronously stop all workers > N     for i := n; i < lWorkers; i++ {         p.workers[i].stop()     }     // Synchronously wait for all workers > N to stop     for i := n; i < lWorkers; i++ {         p.workers[i].join()     }     // Remove stopped workers from slice     p.workers = p.workers[:n] }SetSize方法首先通过workerMut加锁,然后根据lWorkers创建newWorkerWrapper,之后执行worker.stop,再执行worker.join(),然后清空workersClosefunc (p *Pool) Close() {     p.SetSize(0)     close(p.reqChan) }Close方法执行SetSize(0)及close(p.reqChan)实例func TestFuncJob(t *testing.T) {     pool := NewFunc(10, func(in interface{}) interface{} {         intVal := in.(int)         return intVal * 2     })     defer pool.Close()     for i := 0; i < 10; i++ {         ret := pool.Process(10)         if exp, act := 20, ret.(int); exp != act {             t.Errorf("Wrong result: %v != %v", act, exp)         }     } }TestFuncJob通过NewFunc创建pool,
  • [技术干货] Go语言tunny的workerWrapper使用教程示例【转】
    序本文主要研究一下tunny的workerWrapperworkerWrappertype workerWrapper struct {     worker        Worker     interruptChan chan struct{}     // reqChan is NOT owned by this type, it is used to send requests for work.     reqChan chan<- workRequest     // closeChan can be closed in order to cleanly shutdown this worker.     closeChan chan struct{}     // closedChan is closed by the run() goroutine when it exits.     closedChan chan struct{} } func newWorkerWrapper(     reqChan chan<- workRequest,     worker Worker, ) *workerWrapper {     w := workerWrapper{         worker:        worker,         interruptChan: make(chan struct{}),         reqChan:       reqChan,         closeChan:     make(chan struct{}),         closedChan:    make(chan struct{}),     }     go w.run()     return &w }workerWrapper包装了worker,定义了interruptChan、reqChan、closeChan、closedChan属性interruptfunc (w *workerWrapper) interrupt() {     close(w.interruptChan)     w.worker.Interrupt() } interrupt方法关闭w.interruptChan,执行w.worker.Interrupt()runfunc (w *workerWrapper) run() {     jobChan, retChan := make(chan interface{}), make(chan interface{})     defer func() {         w.worker.Terminate()         close(retChan)         close(w.closedChan)     }()     for {         // NOTE: Blocking here will prevent the worker from closing down.         w.worker.BlockUntilReady()         select {         case w.reqChan <- workRequest{             jobChan:       jobChan,             retChan:       retChan,             interruptFunc: w.interrupt,         }:             select {             case payload := <-jobChan:                 result := w.worker.Process(payload)                 select {                 case retChan <- result:                 case <-w.interruptChan:                     w.interruptChan = make(chan struct{})                 }             case _, _ = <-w.interruptChan:                 w.interruptChan = make(chan struct{})             }         case <-w.closeChan:             return         }     } }run首先创建jobChan、retChan,然后for循环执行select读取reqChan,之后读取jobChan的payload,进行处理,然后写入到retChanstopfunc (w *workerWrapper) stop() {     close(w.closeChan) }stop方法关闭w.closeChanjoinfunc (w *workerWrapper) join() {     <-w.closedChan } join方法则等待w.closedChan小结tunny的workerWrapper包装了worker,定义了interruptChan、reqChan、closeChan、closedChan属性,它提供了interrupt、run、stop、join方法。doc
  • [技术干货] GO语言实现日志切割的示例详解【转】
    准备工作日志记录对程序排查问题比较关键,记录下GO中日志选择,从以下出发点考虑:日志文件能自动切割,以免过大能记录从哪个文件哪行代码调用的,方便排查问题配置简单明了库文件使用人数较多,稳定经过一段时间摸索,最终选择了Logrus和lumberjack两个库,使用人数都比较多。安装两个库go get gopkg.in/natefinch/lumberjack.v2 go get github.com/sirupsen/logrus代码实际中一行配置就可以完成,后续只用调用logrus.Debug、logrus.Info同标准库一样只用log改为logrus十分简单明了    logrus.SetOutput(io.MultiWriter(os.Stdout, &lumberjack.Logger{         Filename:   "go-log.log",         MaxBackups: 10,         MaxSize:    20,     }))整体代码如下,基本能满足实际使用需求个别需求调整可以参考这两个文档 package main  import (     "io"     "os"     "time"      "github.com/sirupsen/logrus"     "gopkg.in/natefinch/lumberjack.v2" )  func main() {     // 同时输出到终端和文件     logrus.SetOutput(io.MultiWriter(os.Stdout, &lumberjack.Logger{         Filename:   "go-log.log",         MaxBackups: 10,         MaxSize:    20,     }))     /*只输出到文件的话只用以下写法     logrus.SetOutput(&lumberjack.Logger{         Filename:   "go-log.log",         MaxBackups: 10,         MaxSize:    20,     })     */      // 这个在日志中记录代码位置,十分有用     logrus.SetReportCaller(true)          // 设置输出等级,按实际需求设置,可以忽略     logrus.SetLevel(logrus.InfoLevel)     logrus.Debug("debug message")     logrus.Infof("info message at %s\n", time.Now().Format("2006-01-02 15:04:05"))     logrus.Error("error message") } 
总条数:166 到第
上滑加载中