通过示例学习-Go-语言-2023-五-

通过示例学习 Go 语言 2023(五)

Go 语言中的通道(Channel)

来源:golangbyexample.com/channel-golang/

这是 Go 语言综合教程系列的第二十四章。有关该系列其他章节,请参考此链接 – Go 语言综合教程系列

下一个教程选择语句

上一个教程协程

现在让我们来看一下当前教程。以下是当前教程的目录

目录

  • 概述

  • 声明通道

  • 通道上的操作

    • 发送操作

    • 接收操作

  • 通道方向

    • 仅发送通道

    • 仅接收通道

  • 使用 cap()函数获取通道的容量 function")

  • 使用 len()函数获取通道的长度 function")

  • 关闭通道的操作

  • 在通道上的范围循环

  • 空通道

  • 摘要表

  • 结论

概述

通道是 Go 中的一种数据类型,提供了协程之间的同步和通信。可以将其视为管道,供协程之间进行通信。协程之间的这种通信不需要任何显式锁定,锁由通道自身内部管理。通道与协程一起使 Go 编程语言具有并发性。因此我们可以说,Go 语言有两个并发原语:

  • 协程 – 轻量级独立执行以实现并发/并行。

  • 通道 – 提供协程之间的同步和通信。

声明通道

每个通道变量只能容纳特定类型的数据。Go 在声明通道时使用特殊关键字chan。下面是声明通道的格式

var variable_name chan type

这仅仅声明了一个可以容纳类型数据的通道,并且默认值为 nil,因此创建了一个空通道。让我们看一个程序来确认这一点。

package main

import "fmt"

func main() {
    var a chan int
    fmt.Println(a)
}

输出

{nil}

为了定义通道,我们可以使用内置函数make.

package main

import "fmt"

func main() {
    var a chan int
    a = make(chan int)
    fmt.Println(a)
}

输出

0xc0000240c0

在你的机器上,输出的地址可能会不同。

那么这里的 make 是干什么的呢?通道在内部由hchan结构表示,其主要元素是:

type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz elements
    elemsize uint16
    closed   uint32         // denotes weather channel is closed or not
    elemtype *_type         // element type
    sendx    uint           // send index
    recvx    uint           // receive index
    recvq    waitq          // list of recv waiters
    sendq    waitq          // list of send waiters
    lock     mutex
}

使用make时,会创建一个hchan结构的实例,所有字段都初始化为其默认值。

通道上的操作

通道上可以执行两个主要操作。

  • 发送

  • 接收

让我们逐一查看每个部分。

发送操作

发送操作用于将数据发送到通道。以下是向通道发送的格式。

ch <- val

在这里

  • ch是通道变量

  • val是发送到通道的数据。

请注意val的数据类型和通道的数据类型应该匹配。

接收操作

接收操作用于从通道读取数据。以下是从通道接收的格式。

val := <- ch 

在这里

  • ch是通道变量

  • val是一个变量,用于存储从通道读取的数据。

让我们看一个示例,在这个示例中,我们将在一个 goroutine 中发送数据,并在另一个 goroutine 中接收该数据。

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)

    fmt.Println("Sending value to channel")
    go send(ch)

    fmt.Println("Receiving from channel")
    go receive(ch)

    time.Sleep(time.Second * 1)
}

func send(ch chan int) {
    ch <- 1
}

func receive(ch chan int) {
    val := <-ch
    fmt.Printf("Value Received=%d in receive function\n", val)
}

输出

Sending value to channel
Receiving from channel
Value Received=1 in receive function

在上面的程序中,我们创建了一个名为ch的通道,其数据类型为int,这意味着它只能传输类型为int的数据。函数send()receive()作为一个 goroutine 启动。我们在 send()的 goroutine 中向通道ch发送数据,并在 receive()的 goroutine 中从ch接收数据。

关于接收操作要注意一个非常重要的点是,发送到通道的特定值在任何 goroutine 中只能接收一次。正如你所见,在发送和接收通道时,goroutine 中没有使用锁。锁是由通道内部管理的,代码中不需要使用显式锁。

默认情况下,当我们使用 make 创建通道时,它会创建一个无缓冲的通道,这意味着创建的通道不能存储任何数据。因此,任何对通道的发送在另一个 goroutine 接收之前都是阻塞的。因此在send()函数中,这一行将会被阻塞。

ch <- 1

直到在receive()函数中接收到值。

val := <-ch

我们还在主函数中设置了一个超时,以允许发送和接收函数都完成。如果在主函数结束时没有超时,程序将退出,两个 goroutine 可能无法调度。

为了说明发送时的阻塞,让我们在send()函数中向通道ch发送值后添加一个日志,并在从ch接收值之前在receive()函数中设置一个超时。

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int)
	go send(ch)
	go receive(ch)
	time.Sleep(time.Second * 2)
}

func send(ch chan int) {
	ch <- 1
	fmt.Println("Sending value to channel complete")
}

func receive(ch chan int) {
	time.Sleep(time.Second * 1)
	fmt.Println("Timeout finished")
	_ = <-ch
	return
}

输出

Timeout finished
Sending value to channel complete

日志

Timeout finished

将始终位于之前

Sending value to channel complete

尝试在接收函数中更改不同的超时值。你会发现上述顺序始终存在。这表明,在无缓冲通道上发送时会阻塞,直到在其他 goroutine 上发生接收。接收通道也是阻塞的,除非有另一个 goroutine 向该通道发送数据。

为了说明接收时的阻塞,让我们在 receive()函数中接收值后添加一个日志,并在 send()函数中发送值之前设置一个超时。

package main

import (
	"fmt"
	"time"
)

func main() {
  ch := make(chan int)
  go send(ch)

  go receive(ch)
  time.Sleep(time.Second * 2)
}

func send(ch chan int) {
  time.Sleep(time.Second * 1)
  fmt.Println("Timeout finished")
  ch <- 1
}

func receive(ch chan int) {
  val := <-ch
  fmt.Printf("Receiving Value from channel finished. Value received: %d\n", val)
}

输出

Timeout finished
Receiving Value from channel finished. Value received: 1

在上面的程序中,我们在发送到通道之前添加了一个超时。

日志

Timeout finished

将始终在之前。

Receiving Value from channel finished. Value received: 1

尝试在 send()函数中更改不同的超时值。你将注意到上述顺序始终存在。这说明在无缓冲通道上的接收会被阻塞,直到其他 goroutine 在该通道上进行发送。

我们也可以在主函数中接收这个值。

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int)
    fmt.Println("Sending value to channel start")
    go send(ch)
    val := <-ch
    fmt.Printf("Receiving Value from channel finished. Value received: %d\n", val)
}

func send(ch chan int) {
    ch <- 1
}

输出

Sending value to channel start
Receiving Value from channel finished. Value received: 1

目前为止,我们已经看到无缓冲通道的例子。无缓冲通道没有任何存储,因此,对于无缓冲通道。

  • 除非有其他 goroutine 来接收,否则在通道上发送会被阻塞。

  • 接收会被阻塞,直到另一侧有其他 goroutine 发送。

在 Go 中,你也可以创建一个缓冲通道。缓冲通道有一定的容量来保存数据,因此,对于缓冲通道:

  • 只有在缓冲区满时,发送到缓冲通道才会被阻塞。

  • 接收仅在通道为空时被阻塞。

这是使用 make 函数创建缓冲通道的语法。

a = make(chan <type>, capacity)</type>

第二个参数指定通道的容量。无缓冲通道的容量为零。这就是为什么如果没有接收者,发送会被阻塞;如果没有发送者,接收会被阻塞。

让我们看一个缓冲通道的程序。

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 1)
    ch <- 1
    fmt.Println("Sending value to channnel complete")
    val := <-ch
    fmt.Printf("Receiving Value from channel finished. Value received: %d\n", val)
}

在上面的程序中,我们创建了一个长度为 1 的缓冲通道,如此。

ch := make(chan int, 1)

我们在主 goroutine 中发送一个值并接收同一个值。这是可能的,因为如果缓冲通道没有满,发送到缓冲通道不会被阻塞。因此,下面的行对于缓冲通道不会阻塞。

ch <- 1

该通道的容量为一。因此,向通道发送数据不会被阻塞,值会存储在通道的缓冲区中。因此,在同一个 goroutine 中发送和接收仅对缓冲通道有效。让我们深入探讨上述提到的两个重要点。

  • 当通道已满时,发送到通道会被阻塞。

  • 当通道为空时,接收会被阻塞。

让我们看看每个程序。

当通道满时,发送被阻塞

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 1)
    ch <- 1
    ch <- 1
    fmt.Println("Sending value to channnel complete")
    val := <-ch
    fmt.Printf("Receiving Value from channel finished. Value received: %d\n", val)
}

输出

fatal error: all goroutines are asleep - deadlock!

在上面的程序中,我们创建了一个容量为一的通道。之后,我们向通道发送一个值,然后再向通道发送另一个值。

ch <- 1
ch <- 1

发送到通道的第二个请求被阻塞,因为缓冲区已满,因此导致了死锁情况,因为程序无法继续,这就是为什么你会看到输出为。

fatal error: all goroutines are asleep - deadlock!

当通道为空时,接收被阻塞

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 1)
    ch <- 1
    fmt.Println("Sending value to channnel complete")
    val := <-ch
    val = <-ch
    fmt.Printf("Receiving Value from channel finished. Value received: %d\n", val)
}

输出

fatal error: all goroutines are asleep - deadlock!

在上面的程序中,我们创建了一个容量为一的通道,之后我们向通道发送一个值,然后从通道接收一个值。接着,我们尝试第二次接收通道中的值,结果导致了死锁,因为程序无法继续,因为通道为空,无法接收。这就是为什么你会看到输出为。

fatal error: all goroutines are asleep - deadlock!

这说明如果通道缓冲区为空,接收将被阻塞。

通道方向

到目前为止,我们已经看到双向通道,既可以发送又可以接收数据。在 golang 中,也可以创建单向通道。可以创建一个只能发送数据的通道,也可以创建一个只能接收数据的通道。这由通道的箭头方向决定。

  • 只能发送数据的通道。

这是这种通道的语法

chan<- int
  • 只能发送数据的通道

这是这种通道的语法

<-chan in

现在的问题是,为什么你想创建一个只能发送数据或只能接收数据的通道。这在将通道传递给函数时非常有用,因为我们希望限制函数只能发送数据或接收数据。

有多种方法可以将通道作为函数参数传递。通道的箭头方向指定数据流的方向。

  • chan  :双向通道(可读可写)

  • chan <-  :仅向通道写入

  • <- chan  :仅从通道读取(输入通道)

仅发送通道

  • 这种通道的签名只能发送数据,传递给函数作为参数时会如下所示。
func process(ch chan<- int){ //doSomething }
  • 尝试从这样的通道接收数据将出现以下错误。
invalid operation: <-ch (receive from send-only type chan<- int)

尝试取消注释代码中的以下行以查看上述错误

s := <-ch

代码:

package main
import "fmt"
func main() {
    ch := make(chan int, 3)
    process(ch)
    fmt.Println(<-ch)
}
func process(ch chan<- int) {
    ch <- 2
    //s := <-ch
}

输出: 2

仅接收通道

  • 这种通道的签名只能接收数据,传递给函数作为参数时会如下所示。
func process(ch <-chan int){ //doSomething }
  • 尝试向这样的通道发送数据将出现以下错误。
invalid operation: ch <- 2 (send to receive-only type <-chan int)

尝试取消注释代码中的以下行以查看上述错误

ch <- 2

代码:

package main
import "fmt"
func main() {
    ch := make(chan int, 3)
    ch <- 2
    process(ch)
    fmt.Println()
}
func process(ch <-chan int) {
    s := <-ch
    fmt.Println(s)
    //ch <- 2
}

输出: 2

使用 cap()函数获取通道容量

缓冲通道的容量是该通道可以容纳的元素数量。容量指通道的缓冲区大小。创建通道时可以在使用 make 函数时指定通道的容量。第二个参数是容量。

无缓冲通道的容量始终为零

package main

import "fmt"

func main() {
    ch := make(chan int, 3)
    fmt.Printf("Capacity: %d\n", cap(ch))
}

输出

Capacity: 3

在上述程序中,我们在 make 函数中指定了容量为 3

make(chan int, 3)

使用 len()函数获取通道长度

内置的len()函数可用于获取通道的长度。通道的长度是通道中已存在的元素数量。因此,长度实际上表示通道缓冲区中排队的元素数量。通道的长度始终小于或等于通道的容量。

无缓冲通道的长度始终为零

package main

import "fmt"

func main() {
	ch := make(chan int, 3)
	ch <- 5
	fmt.Printf("Len: %d\n", len(ch))

	ch <- 6
	fmt.Printf("Len: %d\n", len(ch))
	ch <- 7
	fmt.Printf("Len: %d\n", len(ch))
}

输出

Len: 1
Len: 2
Len: 3

在上述代码中,首先创建了一个容量为 3 的通道。之后,我们不断向通道发送一些值。正如你从输出中注意到的那样,每次发送操作后,通道的长度增加 1,因为长度表示通道缓冲区中的项目数量。

通道的关闭操作

关闭是一个内置函数,可用于关闭通道。关闭通道意味着无法再向通道发送数据。通道通常在所有数据已发送且没有更多数据可发送时关闭。让我们看看一个程序。

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)
    go sum(ch, 3)
    ch <- 2
    ch <- 2
    ch <- 2
    close(ch)
    time.Sleep(time.Second * 1)
}

func sum(ch chan int, len int) {
    sum := 0
    for i := 0; i < len; i++ {
        sum += <-ch
    }
    fmt.Printf("Sum: %d\n", sum)
}

输出

Sum: 6

在上面的程序中,我们创建了一个通道。然后我们在一个 goroutine 中调用sum函数。在主函数中,我们向通道发送 3 个值,之后关闭通道,表示无法再向通道发送值。sum函数使用 for 循环遍历通道并计算总值。

在关闭的通道上发送将导致恐慌。

请看下面的程序。

package main
func main() {
    ch := make(chan int)
    close(ch)
    ch <- 2
}

输出

panic: send on closed channel

关闭已经关闭的通道也会导致恐慌。

在接收一个通道时,我们也可以使用一个额外的变量来确定通道是否已关闭。下面是相应的语法。

val,ok <- ch

ok 的值将是

  • 如果通道未关闭则为真。

  • 如果通道已关闭则为假。

package main
import (
    "fmt"
)
func main() {
    ch := make(chan int, 1)
    ch <- 2
    val, ok := <-ch
    fmt.Printf("Val: %d OK: %t\n", val, ok)

    close(ch)
    val, ok = <-ch
    fmt.Printf("Val: %d OK: %t\n", val, ok)
}

输出

Val: 2 OK: true
Val: 0 OK: false

在上面的程序中创建了一个容量为 1 的通道。然后我们向通道发送了一个值。第一次接收中的ok变量为真,因为通道未关闭。第二次接收中的 ok 变量为假,因为通道已关闭。

通道上的范围循环

可以使用范围循环从通道接收数据,直到其关闭。

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int, 3)
	ch <- 2
	ch <- 2
	ch <- 2
	close(ch)
	go sum(ch)
	time.Sleep(time.Second * 1)
}

func sum(ch chan int) {
	sum := 0
	for val := range ch {
		sum += val
	}
	fmt.Printf("Sum: %d\n", sum)
}

输出

Sum: 6

在上面的程序中,我们创建了一个通道。在主函数中向通道发送了三个值,之后关闭了通道。然后我们调用求和函数,并将通道传递给该函数。在求和函数中,我们对通道进行了范围循环。在遍历完通道中的所有值后,范围循环将退出,因为通道已关闭。

现在脑海中浮现的问题是,如果在主函数中不关闭通道,会发生什么。尝试注释掉关闭通道的那一行。然后运行程序。它也会输出死锁,因为范围循环在求和函数中将永远不会完成。

fatal error: all goroutines are asleep - deadlock!

零通道

通道的零值为零。因此,仅声明一个通道会创建一个默认的零通道,因为通道的零值为零。让我们看看一个演示程序。

package main

import "fmt"

func main() {
    var a chan int
    fmt.Print("Default zero value of channel: ")
    fmt.Println(a)
}

输出

nil

关于零通道需要注意的一些要点。

  • 向零通道发送将永远阻塞。

  • 从零通道接收将永远阻塞。

  • 关闭零通道会导致恐慌。

摘要表

到目前为止,我们已经看到了 5 个关于通道的操作。

  • 发送

  • 接收

  • 关闭

  • 长度

  • 容量

让我们看看一个摘要表,展示不同类型通道上每个操作的结果。

命令 无缓冲通道(未关闭且非零) 缓冲通道(未关闭且非零) 已关闭通道 零通道
发送 如果没有对应接收者则阻塞,否则成功 如果通道已满则阻塞,否则成功 恐慌 永远阻塞
接收 如果没有相应的发送者则阻塞,否则成功 如果通道为空则阻塞,否则成功 如果通道为空则接收数据类型的默认值,否则接收实际值 永远阻塞
关闭 成功 成功 恐慌 恐慌
长度 0 通道缓冲区中排队的元素数量 -0 如果是无缓冲通道-如果是有缓冲通道则为缓冲区中排队的元素数量 0
容量 0 通道缓冲区的大小 -0 如果是无缓冲通道-如果是有缓冲通道则为缓冲区的大小 0

结论

这就是关于 golang 中通道的所有内容。希望你喜欢这篇文章。请在评论中分享反馈/改进/错误。

下一篇教程选择语句

上一篇教程协程

Go (Golang) 中的字符常量

来源:golangbyexample.com/character-constant-golang/

目录

  • 概述

  • 示例

    • 类型字符常量

    • 未类型命名字符常量

    • 未类型未命名字符常量

概述

在 Go 中,字符常量表示为单引号之间的值。请参考这篇文章以更好地理解 Golang 中的字符。

同样,为了更好地理解 Golang 中的字符常量,理解 Go 中的类型和未类型常量是很重要的。请参考这篇文章了解更多 – golangbyexample.com/typed-untyped-constant-golang/

一旦你阅读完这篇文章,你将会明白常量可以用三种方式声明

  • 类型常量

  • 未类型未命名常量

  • 未类型命名常量

字符的情况也是如此。让我们看一个程序来理解它

示例

下面是一个说明字符常量的程序。

package main

import "fmt"

func main() {
	type myChar int32

	//Typed character constant
	const aa int32 = 'a'
	var uu = aa
	fmt.Println("Untyped unnamed character constant")
	fmt.Printf("uu: Type: %T Value: %v\n\n", uu, uu)

	//Below line will raise a compilation error
	//var vv myBool = aa

	//Untyped named character constant
	const bb = 'a'

	var ww myChar = bb
	var xx = bb
	fmt.Println("Untyped named character constant")
	fmt.Printf("ww: Type: %T Value: %v\n", ww, ww)
	fmt.Printf("xx: Type: %T Value: %v\n\n", xx, xx)

	//Untyped unnamed character constant
	var yy myChar = 'a'
	var zz = 'a'
	fmt.Println("Untyped unnamed character constant")
	fmt.Printf("yy: Type: %T Value: %v\n", yy, yy)
	fmt.Printf("zz: Type: %T Value: %v\n", zz, zz)
}

输出:

Untyped unnamed character constant
uu: Type: int32 Value: 97

Untyped named character constant
ww: Type: main.myChar Value: 97
xx: Type: int32 Value: 97

Untyped unnamed character constant
yy: Type: main.myChar Value: 97
zz: Type: int32 Value: 97

在上述程序中,我们创建了一个新类型myChar

type myChar int32

上述程序还展示了

  • 类型字符常量

  • 未类型未命名字符常量

  • 未类型命名字符常量

让我们理解它们的每一个及其行为

类型字符常量

定义如下

const aa int32 = 'a'

请注意,上述代码中的以下行将导致编译错误。这是因为变量aa的类型为int32。因此,下面的行将导致编译错误,因为它无法赋值给类型为myChar的变量。

var v myChar = aa

但是类型字符串常量可以赋值给使用var关键字创建的变量,如下所示

var uu = aa

未类型命名字符常量

定义如下

const bb = 'a'

未类型命名字符串常量可以赋值给类型为myChar的变量以及使用var关键字创建的变量,因为它是未类型的,所以常量的类型将根据赋值给它的变量类型决定。

var ww myChar = bb
var xx = bb

未类型未命名字符常量

它如下所示

'a'

未类型未命名字符串常量可以赋值给类型为myChar的变量以及使用var关键字创建的变量,因为它是未类型的,所以常量的类型将根据赋值给它的变量类型决定。

var yy myChar = 'a'
var zz = 'a'

Go 语言中的字符(Golang)

来源:golangbyexample.com/character-in-go/

目录

  • 概述

  • 代码示例

  • 注意事项

概述

Golang 没有任何‘char’数据类型。因此

  • byte用于表示 ASCII 字符。byte 是 uint8 的别名,因此为 8 位或 1 字节,可以表示所有从 0 到 255 的 ASCII 字符

  • rune用于表示所有 UNICODE 字符,包括所有存在的字符。rune 是int32的别名,能够表示所有 UNICODE 字符。其大小为 4 字节。

  • 一个长度为一的string也可以隐式表示一个字符。一个字符字符串的大小将取决于该字符的编码。对于 utf-8 编码,它将在 1-4 字节之间

要声明byterune,我们使用单引号。在声明byte时,必须指定类型。如果不指定类型,则默认类型为rune

要声明一个string,我们使用双引号或反引号。双引号字符串支持转义字符,而反引号字符串是原始字面字符串,不支持任何转义。

代码示例

请参见下面的程序。它显示

  • 表示字符‘a‘的 byte

  • 表示英镑符号‘£‘的 rune

  • 一个字符微符号‘µ’的字符串

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    //If you don't specify type here
    var b byte = 'a'

    fmt.Println("Priting Byte:")
    //Print Size, Type and Character
    fmt.Printf("Size: %d\nType: %s\nCharacter: %c\n", unsafe.Sizeof(b), reflect.TypeOf(b), b)

    r := '£'

    fmt.Println("\nPriting Rune:")
    //Print Size, Type, CodePoint and Character
    fmt.Printf("Size: %d\nType: %s\nUnicode CodePoint: %U\nCharacter: %c\n", unsafe.Sizeof(r), reflect.TypeOf(r), r, r)

    s := "µ" //Micro sign
    fmt.Println("\nPriting String:")
    fmt.Printf("Size: %d\nType: %s\nCharacter: %s\n", unsafe.Sizeof(s), reflect.TypeOf(s), s)
}

输出:

Priting Byte:
Size: 1
Type: uint8
Character: a

Priting Rune:
Size: 4
Type: int32
Unicode CodePoint: U+00A3
Character: £

Priting String:
Size: 16
Type: string
Character: µ

注意事项

  • 用非 ASCII 字符声明 byte 会导致编译器错误,如下所示。我尝试用一个对应代码为 285 的字符
constant 285 overflows byte
  • 在初始化byterune时,只能在单引号内声明一个字符。当尝试在单引号中添加两个字符时,将生成如下编译器警告
invalid character literal (more than one character)

检查当前登录用户在 Linux 上所属的所有组。

来源:golangbyexample.com/current-log-user-groups/

在 Linux 上,groups命令可以用来查看用户属于哪些组。

只需在终端中输入groups。以下是我在 Mac 机器上的输出。

staff everyone localaccounts _appserverusr admin

检查 Go (Golang) 中是否设置了环境变量

来源:golangbyexample.com/check-environment-variable-set-go/

os.LookupEnv 函数可用于检查特定环境变量是否已设置。它返回一个布尔值,如果给定的环境变量已设置,则为 true,否则为 false。让我们来看一个工作代码:

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    //Set env a to b
    err := os.Setenv("a", "x")
    if err != nil {
        log.Fatal(err)
    }

    val, present := os.LookupEnv("a")
    fmt.Printf("a env variable present: %t\n", present)
    fmt.Println(val)

    val, present = os.LookupEnv("b")
    fmt.Printf("b env variable present: %t\n", present)
}

输出:

a env variable present: true
x
b env variable present: false

在 Go (Golang) 中检查文件或目录是否存在

来源:golangbyexample.com/check-if-file-or-directory-exists-go/

os.Statos.IsNotExist() 可用于检查特定文件或目录是否存在。

目录

** 文件存在

  • 文件夹存在

**文件存在   **

package main

import (
    "log"
    "os"
)

func main() {
    fileinfo, err := os.Stat("temp.txt")
    if os.IsNotExist(err) {
        log.Fatal("File does not exist.")
    }
    log.Println(fileinfo)
}

文件夹存在

package main

import (
    "log"
    "os"
)

func main() {
    folderInfo, err := os.Stat("temp")
    if os.IsNotExist(err) {
        log.Fatal("Folder does not exist.")
    }
    log.Println(folderInfo)
}

检查给定的链表在 Go(Golang)中是否有循环。

来源:golangbyexample.com/ilinked-list-cycle-golang/

目录

  • 概述

  • 程序

概述

目标是判断给定的链表是否有循环。如果链表中的最后一个节点指向前面的某个节点,则存在循环。

示例

![](https://gitee.com/OpenDocCN/geekdoc-golang-zh/raw/master/docs/go-exam-2023/img/ec43f0fba8d2f6b590561005049e44a3.png)

上述链表有循环。以下是我们可以遵循的方法。

  • 有两个指针,一个是慢指针,另一个是快指针。两者最初都指向头节点。

  • 现在将慢指针移动 1 个节点,将快指针移动 2 个节点。

slow := slow.Next
fast := fast.Next.Next
  • 如果慢指针和快指针在任何时刻相同,则链表有循环。

程序

这是相应的程序。

package main

import "fmt"

func main() {
	first := initList()
	ele4 := first.AddFront(4)
	first.AddFront(3)
	ele2 := first.AddFront(2)
	first.AddFront(1)

	//Create cycle
	ele4.Next = ele2

	output := hasCycle(first.Head)
	fmt.Println(output)

}

type ListNode struct {
	Val  int
	Next *ListNode
}

type SingleList struct {
	Len  int
	Head *ListNode
}

func (s *SingleList) AddFront(num int) *ListNode {
	ele := &ListNode{
		Val: num,
	}
	if s.Head == nil {
		s.Head = ele
	} else {
		ele.Next = s.Head
		s.Head = ele
	}
	s.Len++
	return ele
}

func initList() *SingleList {
	return &SingleList{}
}
func hasCycle(head *ListNode) bool {

	if head == nil || head.Next == nil {
		return false
	}

	hasCycle := false
	slow := head
	fast := head

	for slow != nil && fast != nil && fast.Next != nil {
		slow = slow.Next
		fast = fast.Next.Next

		if slow == fast {
			hasCycle = true
			break
		}
	}

	return hasCycle

}

输出

true

注意: 请查看我们的 Golang 高级教程。本系列教程详尽,我们尝试覆盖所有概念并提供示例。本教程适合希望获得专业知识和对 Golang 有深入理解的人 – Golang 高级教程

如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是,那么这篇文章适合你 – 所有设计模式 Golang

检查给定的树是否为二叉搜索树(BST)在 Go (Golang)中

来源:golangbyexample.com/tree-is-bst-golang/

目录

  • 概述

  • 程序

概述

我们可以使用以下策略来判断给定的树是否为 BST。

  • 对于给定的当前节点,如果左子树和右子树都是 BST

  • 左子树中的最大值小于当前节点值。

  • 右子树中的最小值大于当前节点值。

这是相应的程序。

程序

package main

import (
	"fmt"
	"math"
)

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

func isValidBST(root *TreeNode) bool {
	if root == nil {
		return true
	}
	isValid, _, _ := isValidBSTUtil(root)
	return isValid
}

func isValidBSTUtil(node *TreeNode) (bool, int, int) {

	if node.Left == nil && node.Right == nil {
		return true, node.Val, node.Val
	}

	min := node.Val
	max := node.Val

	isValidLeft := true
	var leftMin, leftMax int
	if node.Left != nil {
		isValidLeft, leftMin, leftMax = isValidBSTUtil(node.Left)

		if !isValidLeft {
			return false, 0, 0
		}
		if node.Val <= leftMax {
			return false, 0, 0
		}

		min = leftMin
	}

	isValidRight := true
	var rightMin, rightMax int

	if node.Right != nil {
		isValidRight, rightMin, rightMax = isValidBSTUtil(node.Right)

		if !isValidRight {
			return false, 0, 0
		}

		if node.Val >= rightMin {
			return false, 0, 0
		}
		max = rightMax
	}

	return true, min, max
}

func minOfFour(a, b, c, d int) int {
	return int(math.Min(float64(a), math.Min(float64(b), math.Min(float64(c), float64(d)))))
}

func maxOfFour(a, b, c, d int) int {
	return int(math.Max(float64(a), math.Max(float64(b), math.Max(float64(c), float64(d)))))
}

func main() {
	root := TreeNode{Val: 2}
	root.Left = &TreeNode{Val: 1}
	root.Right = &TreeNode{Val: 3}

	isValidBST := isValidBST(&root)
	fmt.Println(isValidBST)

}

输出

true

注意: 请查看我们的 Golang 高级教程。本系列教程内容详尽,我们尽力覆盖所有概念并提供示例。本教程适合那些希望深入了解 Golang 并获得专业知识的人—— Golang 高级教程

如果你有兴趣了解如何在 Golang 中实现所有设计模式。如果是,那么这篇文章适合你—— 所有设计模式 Golang

在 Go(Golang)中检查链表是否是循环链表

来源:golangbyexample.com/linked-list-is-circular-go/

![链表 - 是否是循环链表图像](https://gitee.com/OpenDocCN/geekdoc-golang-zh/raw/master/docs/go-exam-2023/img/fd1244a69df50e7bcb299f396b6f796a.png)

目录

  • 概述

  • 程序

概述

检查链表是否是循环链表。如果所有节点以循环的形式连接,则链表是循环的。

程序

在下面的程序中,我们首先创建一个链表。然后检查它是否是循环链表。它会先打印 false。之后,我们将链表转换为循环链表。然后再次检查它是否是循环链表。现在打印 true。

package main
import "fmt"
type node struct {
    data string
    next *node
}
type singlyLinkedList struct {
    len  int
    head *node
}
func initList() *singlyLinkedList {
    return &singlyLinkedList{}
}
func (s *singlyLinkedList) AddFront(data string) {
    node := &node{
        data: data,
    }
    if s.head == nil {
        s.head = node
    } else {
        node.next = s.head
        s.head = node
    }
    s.len++
    return
}

func (s *singlyLinkedList) Traverse() error {
    if s.head == nil {
        return fmt.Errorf("TraverseError: List is empty")
    }
    current := s.head
    for current != nil {
        fmt.Println(current.data)
        current = current.next
    }
    return nil
}

//Function to convert singly linked list to circular linked list
func (s *singlyLinkedList) ToCircular() {
    current := s.head
    for current.next != nil {
        current = current.next
    }
    current.next = s.head
}

func (s *singlyLinkedList) IsCircular() bool {
    if s.head == nil {
        return true
    }
    current := s.head.next
    for current.next != nil && current != s.head {
        current = current.next
    }
    return current == s.head
}

func main() {
    singleList := initList()
    fmt.Printf("AddFront: D\n")
    singleList.AddFront("D")
    fmt.Printf("AddFront: C\n")
    singleList.AddFront("C")
    fmt.Printf("AddFront: B\n")
    singleList.AddFront("B")
    fmt.Printf("AddFront: A\n")
    singleList.AddFront("A")
    err := singleList.Traverse()
    if err != nil {
        fmt.Println(err.Error())
    }
    isCircular := singleList.IsCircular()
    fmt.Printf("Before: Is Circular: %t\n", isCircular)

    fmt.Printf("Size: %d\n", singleList.len)
    singleList.ToCircular()

    isCircular = singleList.IsCircular()
    fmt.Printf("After: Is Circular: %t\n", isCircular)
}

输出

AddFront: D
AddFront: C
AddFront: B
AddFront: A
A
B
C
D
Before: Is Circular: false
Size: 4
After: Is Circular: true

在 Go(Golang)中检查一个数字是否是回文。

来源:golangbyexample.com/check-number-palindrome-golang/

目录

  • 概述

  • 第一种解决方案 – 反转数字

  • 第二种解决方案 – 使用递归

概述

例如,下面的数字是回文。

1
121
12321
9
0

下面的数字不是回文。

-121
1211

我们有两种解决方案来判断一个数字是否是回文。

  • 反转数字。如果反转后的数字等于原始数字,则该数字是回文。

  • 另一种方法是使用递归并传入数字的指针。在递归树向下移动时,将数字除以 10。当向上移动递归树时,将指针处的值除以 10。在递归树的任何步骤中,当它们相遇时,原始数字的最后一位将是第一位,指针处数字的第一位将是最后一位。我们可以比较这两者以检查它们是否相等。这个检查在每次相遇时都会进行。

第一种解决方案 – 反转数字

以下是相应的程序

package main

import (
	"fmt"
	"math"
)

func main() {
	output := isPalindrome(121)
	fmt.Println(output)

	output = isPalindrome(12)
	fmt.Println(output)

	output = isPalindrome(1234)
	fmt.Println(output)

	output = isPalindrome(12321)
	fmt.Println(output)

	output = isPalindrome(-101)
	fmt.Println(output)

}

func isPalindrome(x int) bool {

	if x < 0 {
		return false
	}
	if x < 10 {
		return true
	}
	xReversed := reverse(x)

	return xReversed == x
}

func reverse(x int) int {
	sign := "positive"
	if x >= 0 {
		sign = "positive"
	} else {
		sign = "negative"
	}

	x = int(math.Abs(float64(x)))

	var reversedDigit int

	for x > 0 {
		lastDigit := x % 10
		reversedDigit = reversedDigit*10 + lastDigit

		x = x / 10
	}

	if sign == "negative" {
		reversedDigit = reversedDigit * -1
	}

	return reversedDigit
}

输出

true
false
false
true
false

第二种解决方案 – 使用递归

以下是相应的程序

package main

import "fmt"

func main() {
	a := 121
	output := isPalindrome(a, &a)
	fmt.Println(output)

	a = 12
	output = isPalindrome(a, &a)
	fmt.Println(output)

	a = 1234
	output = isPalindrome(a, &a)
	fmt.Println(output)

	a = 12321
	output = isPalindrome(a, &a)
	fmt.Println(output)

	a = -121
	output = isPalindrome(-a, &a)
	fmt.Println(output)

}

func isPalindrome(x int, dup *int) bool {
	if x < 0 {
		return false
	}
	if x < 10 {
		return true
	}

	palin := isPalindrome(x/10, dup)

	*dup = *dup / 10
	lastDigit := x % 10

	if palin && *dup%10 == lastDigit {
		return true
	}

	return false

}

输出

true
false
false
true
false
```*


<!--yml

类别:未分类

日期:2024-10-13 06:15:37

-->

# 在 Go (Golang)中检查一个数字是负数还是正数

> 来源:[`golangbyexample.com/num-positive-negative-go/`](https://golangbyexample.com/num-positive-negative-go/)

目录

+   概述

+   代码

# **概述**

**math**包提供了一个**Signbit**方法,可以用来检查给定的数字是负数还是正数。

+   对于负数,它返回 true

+   对于正数,它返回 false

以下是该函数的签名。它接收一个浮点数作为输入,并返回一个布尔值

```go
func Signbit(x float64) bool 

代码

package main

import (
    "fmt"
    "math"
)

func main() {
    //Will return false for positive
    res := math.Signbit(4)
    fmt.Println(res)

    //Will return true for negative
    res = math.Signbit(-4)
    fmt.Println(res)

    //Will return false for zerp
    res = math.Signbit(0)
    fmt.Println(res)

    //Will return false for positive float
    res = math.Signbit(-0)
    fmt.Println(res)
}

输出:

false
true
false
false

检查 Go (Golang) 中 HTTP 请求是否存在特定 header。

来源:golangbyexample.com/header-present-http-golang/

目录

  • 概述

概述

以下是 header 在 Go 中表示的格式。

type Header map[string][]string

所以在内部,header 是一个键值映射。

  • 键以规范形式表示。规范形式意味着第一个字符和连字符后面的任何字符都是大写字母。其余字符都是小写字母。规范形式的示例包括:
Content-Type
Accept-Encoding
  • 值表示为字符串数组。为什么是字符串数组?因为在请求中使用相同键的两个 header 和不同值是完全可以的。两个值将被收集到数组中。

例如,如果在传入请求中有以下 headers:

content-type: applcation/json
foo: bar1
foo: bar2

然后在服务器上,header 将如下所示:

map[string][]string {
  "Content-Type": {"application/json"},
  "Foo": {"bar1" , "bar2"}
}

由于 header 是一个映射,我们可以利用映射的特性检查某个特定键是否存在。以下是检查映射中键是否存在的格式。

val, ok := mapName[key]

这里有两种情况

  • 如果键存在,val 变量将是映射中键的值,而 ok 变量将为 true。

  • 如果键不存在,val 变量将是值类型的默认零值,而 ok 变量将为 false。

让我们来看一个例子。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    handler := http.HandlerFunc(handleRequest)
    http.Handle("/example", handler)
    http.ListenAndServe(":8080", nil)
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
    headers := r.Header
    val, ok := headers["Content-Type"]
    if ok {
        fmt.Printf("Content-Type key header is present with value %s\n", val)
    } else {
        fmt.Println("Content-Type key header is not present")
    }
    val, ok = headers["someKey"]
    if ok {
        fmt.Printf("someKey key header is present with value %s\n", val)
    } else {
        fmt.Println("someKey key header is not present")
    }
}

在上述程序中,我们启动了一个监听端口 8080 的服务器。我们还在该端点定义了一个 URL。运行此服务器并进行以下 API 调用。

curl -v -X POST http://localhost:8080/example -H "content-type: application/json" 

运行此 API 后,请检查终端中的输出。它将输出结果。你可以检查输出。content-type header 存在,因此输出其值。someKey header 不存在,因此打印未找到。此外,请注意,在访问 header 映射时,我们需要以规范形式输入键。例如,即使 curl 中提供的 header 是:

content-type

但是在代码中访问 header 映射时,我们使用的键是规范形式 Content-Type

val, ok := headers["Content-Type"]

如果我们只想检查一个键是否存在而不需要值,则可以用空标识符即“_”替代值。

_, ok = employeeSalary["Sam"]

检查字符串在 Go (Golang)中是否包含另一个字符串

来源:golangbyexample.com/check-if-substring-golang/

目录

  • 概述

  • 代码:

概述

在 Golang 中,字符串是 UTF-8 编码的。GO 的strings包提供了一个Contains方法,可以用来检查特定字符串是否是另一个字符串的子字符串。

以下是函数的签名

func Contains(s, substr string) bool

如你所见,Compare 函数的返回值是一个布尔值。这个值将是

  • true 是substrs中存在

  • false 是substrs中不存在

代码:

package main

import (
    "fmt"
    "strings"
)

func main() {
    present := strings.Contains("abc", "ab")
    fmt.Println(present)

    present = strings.Contains("abc", "xyz")
    fmt.Println(present)
}

输出:

true
false
```*


<!--yml

类别:未分类

日期:2024-10-13 06:52:44

-->

# 检查字符串在 Go (Golang) 中是否包含单个或多个空格

> 来源:[`golangbyexample.com/string-whitespace-golang/`](https://golangbyexample.com/string-whitespace-golang/)

目录

+   概述

+   程序

# **概述**

可以使用简单的正则表达式来检查字符串在 Go 中是否包含单个或多个空格。

这是相同的程序

# **程序**

```go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	//Single whitespace
	sampleWord := "Good morning"

	isWhitespacePresent := regexp.MustCompile(`\s`).MatchString(sampleWord)
	fmt.Println(isWhitespacePresent)

	//Multiple Whitespace
	sampleWord = "Good   morning"
	isMultipleWhitespacePresent := regexp.MustCompile(`\s*`).MatchString(sampleWord)
	fmt.Println(isMultipleWhitespacePresent)
}

输出

true
true

注意: 请查看我们的 Golang 高级教程。该系列教程内容详尽,我们尝试用例子覆盖所有概念。本教程适合那些希望获得专业知识和对 Golang 有深入理解的人— Golang 高级教程

如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是的话,那么这篇文章就是为你准备的—

所有设计模式 Golang

注意: 请查看我们的系统设计教程系列 系统设计问题

检查字符串是否以某个后缀结尾(Go 语言)

来源:golangbyexample.com/go-strings-ends-suffix/

目录

  • 概述**

  • 代码:

概述

在 Go 中,字符串是 UTF-8 编码的。Go 的 strings 包提供了一个 HasSuffix 方法,可以用来检查字符串是否以某个特定后缀结尾

以下是该函数的签名

func HasSuffix(s, prefix string) bool

让我们来看一下工作代码

代码:

package main

import (
    "fmt"
    "strings"
)

func main() {
    //Input string contains the suffix
    res := strings.HasSuffix("abcdef", "ef")
    fmt.Println(res)

    //Input string doesn't contain the suffix
    res = strings.HasPrefix("abcdef", "fg")
    fmt.Println(res)
}

输出:

true
false
```*


<!--yml

类别:未分类

日期:2024-10-13 06:09:51

-->

# 在 Go (Golang) 中检查字符串是否为数字

> 来源:[`golangbyexample.com/check-if-string-is-number-golang/`](https://golangbyexample.com/check-if-string-is-number-golang/)

**strconv.Atoi()** 函数可用于检查字符串是否为数字。如果传入的字符串不是数字,此函数将返回错误。该函数将数字解析为基数 10。

[`golang.org/pkg/strconv/#Atoi`](https://golang.org/pkg/strconv/#Atoi)

函数的签名是

```go
func Atoi(s string) (int, error)

有效代码:

package main

import (
    "fmt"
    "strconv"
)
func main() {
    x := "1234"
    val, err := strconv.Atoi(x)
    if err != nil {
        fmt.Printf("Supplied value %s is not a number\n", x)
    } else {
        fmt.Printf("Supplied value %s is a number with value %d\n", x, val)
    }

    y := "123b"
    val, err = strconv.Atoi(y)
    if err != nil {
        fmt.Printf("Supplied value %s is not a number\n", y)
    } else {
        fmt.Printf("Supplied value %s is a number with value %d\n", y, val)
    }
}

输出:

Supplied value 1234 is a number with value 1234
Supplied value 123b is not a number

在 Go(Golang)中检查 IP 地址是 IPV4 还是 IPV6

来源:golangbyexample.com/check-ip-address-is-ipv4-or-ipv6-go/

  • IPV4 地址是 4 字节字符串,每个字节用点(‘.’)分隔

  • IPV6 地址由 8 组 4 个十六进制数字组成,每组用冒号(‘:’)分隔

以下代码

  • 如果给定的 IP 地址无效,则打印无效

  • 否则打印该 IP 地址是 ipV4 还是 ipV6

代码:

package main

import (
    "fmt"
    "net"
)

func main() {
    validIPV4 := "10.40.210.253"
    checkIPAddressType(validIPV4)

    invalidIPV4 := "1000.40.210.253"
    checkIPAddressType(invalidIPV4)

    valiIPV6 := "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
    checkIPAddressType(valiIPV6)

    invalidIPV6 := "2001:0db8:85a3:0000:0000:8a2e:0370:7334:3445"
    checkIPAddressType(invalidIPV6)
}

func checkIPAddressType(ip string) {
    if net.ParseIP(ip) == nil {
        fmt.Printf("Invalid IP Address: %s\n", ip)
        return
    }
    for i := 0; i < len(ip); i++ {
        switch ip[i] {
        case '.':
            fmt.Printf("Given IP Address %s is IPV4 type\n", ip)
            return
        case ':':
            fmt.Printf("Given IP Address %s is IPV6 type\n", ip)
            return
        }
    }
}

输出:

Given IP Address 10.40.210.253 is IPV4 type
Invalid IP Address: 1000.40.210.253
Given IP Address 2001:0db8:85a3:0000:0000:8a2e:0370:7334 is IPV6 type
Invalid IP Address: 2001:0db8:85a3:0000:0000:8a2e:0370:7334:3445

检查一个项是否存在于 Go (Golang) 的切片中

来源:golangbyexample.com/item-exists-slice-golang/

让我们看看一个工作代码

package main

import "fmt"

func main() {
    source := []string{"san", "man", "tan"}
    result := find(source, "san")
    fmt.Printf("Item san found: %t\n", result)
    result = find(source, "can")
    fmt.Printf("Item san found: %t\n", result)
}

func find(source []string, value string) bool {
    for _, item := range source {
        if item == value {
            return true
        }
    }
    return false
}

输出:

Item san found: true
Item can found: false

检查文件是否是目录在 Go (Golang)

来源:golangbyexample.com/check-if-file-is-a-directory-go/

请查看下面的代码以了解一个文件是否是文件或是目录

  • 如果 temp 是一个文件,输出将是 = “temp 是一个文件”

  • 如果 temp 是一个目录,输出将是 = “temp 是一个目录”

package main

import (
    "fmt"
    "log"
    "os"
)

var (
    fileInfo *os.FileInfo
    err      error
)

func main() {
    info, err := os.Stat("temp")
    if os.IsNotExist(err) {
        log.Fatal("File does not exist.")
    }
    if info.IsDir() {
        fmt.Println("temp is a directory")
    } else {
        fmt.Println("temp is a file")
    }
}

检查 N 及其双倍是否存在于 Go (Golang)中

来源:golangbyexample.com/number-double-golang/

目录

  • 概述

  • 程序

概述

给定一个数组。目标是找出是否存在任何数字,其双倍也存在。

示例 1

Input: [8,5,4,3]
Output: true
Explanation: 4 and 8

示例 2

Input: [1,3,7,9]
Output: false
Explanation: There exists no number for which its double exist

这个思路是使用一个映射。对于每个元素,我们将检查并返回 true 如果

  • 如果它的双倍存在于映射中

  • 如果数字是偶数,那么如果它的一半存在于映射中

程序

下面是相同程序

package main

import "fmt"

func checkIfExist(arr []int) bool {
	numMap := make(map[int]bool)

	for i := 0; i < len(arr); i++ {
		if numMap[arr[i]*2] {
			return true
		}
		if arr[i]%2 == 0 && numMap[arr[i]/2] {
			return true
		}
		numMap[arr[i]] = true
	}
	return false
}

func main() {
	output := checkIfExist([]int{8, 5, 4, 3})
	fmt.Println(output)

	output = checkIfExist([]int{1, 3, 7, 9})
	fmt.Println(output)

}

输出:

true
false

注意: 请查看我们的 Golang 高级教程。该系列教程详细且我们努力涵盖所有概念及示例。此教程适合希望获得 Golang 专业知识和扎实理解的读者 - Golang 高级教程

如果您有兴趣了解所有设计模式如何在 Golang 中实现。如果是,那么这篇文章适合您 - 所有设计模式 Golang

此外,您还可以查看我们的系统设计教程系列 - 系统设计教程系列

posted @ 2024-10-19 08:38  绝不原创的飞龙  阅读(2)  评论(0编辑  收藏  举报