Go学习笔记02-Go语言基础

简介: Go学习笔记02-Go语言基础

编译命令

go run

go run命令直接编译和执行源码中的main函数,但是并不会留下任何可执行文件(可执行文件被放在临时文件中执行,执行结束后将被自动删除)。go run命令后可以添加参数。

来到HelloGo.go文件的目录下,执行如下命令:

go run HelloGo.go

go build

go build命令会将源码编译为可执行文件,默认将编译该目录下的所有的源码。也可以在命令后添加多个文件名,go build命令将编译这些源码,输出可执行文件。

同样来到HelloGo.go文件的目录下,执行如下命令,其中-o选项用于指定生成的可执行文件的文件名:

go build -o HelloGo HelloGo.go

或者

go build HelloGo.go

都将在当前目录下生成一个HelloGo的可执行文件。

基本语法

变量的声明与初始化

var是Go语言中声明变量的关键字。Go语言在声明变量时,会自动把变量对应的内存区域 进行初始化操作,每个变量会被初始化为其类型的默认值。即变量一经声明,则被初始化为其类型的默认值。变量声明样式如下所示:

var name T

在Go语言中,每一个声明的变量都必须被使用,否则会编译不通过。即变量一经声明,则必须使用

变量初始化样式:

var name T = 表达式

类型推导

Go语言提供了类型推导的语法糖,可精简变量初始化为以下样式:

name := 表达式

类型推导的语法糖省略了声明变量的关键字var和类型属性T

除了类型推导的语法糖特性,Go语言还提供了多重赋值匿名变量的语法糖特性。

多重赋值

多重赋值特性可以轻松实现变量变换任务,不需要借助第三方变量。如下所示:

a := 1
b := 2
b, a =a, b

匿名变量

通过在不需要的变量声明的地方使用’_'来代替变量名,我们就可以忽略部分不需要的左值。匿名变量不占用命名空间不会分配内存匿名变量与匿名变量之间也不会因为多次声明而无法使用。具体例子如下所示:

package main
import "fmt"
// 返回一个人的姓和名
func getName() (string, string){
  return "王", "小二"        // Go语言支持函数多返回值
}
func main()  {
  surname,_ := getName()      // 使用匿名变量
  _, personalName := getName()  // 使用匿名变量
  fmt.Printf("My surname is %v and my personal name is %v", surname, personalName)
}

原生数据类型

基本数据类型:整型、浮点数、布尔型、字符串型等。

整型

整型中主要有两大类,分别是:

  • 按照整型的长度划分:int8int16int32int64
  • 按照有无符号划分:uint8uint16uint32uint64

除此之外,Go语言还提供了平台自匹配长度的int类型和uint类型。

整型类型之间可以相互转换,高长度类型向低长度类型转换时仅保留高长度类型的低位值。

浮点型

浮点型主要有两种:

  • float32:单精度,存储占用4个字节,也即4*8=32位,其中1位用来符号,8位用来指数,剩下的23位表示尾数
  • float64:双精度,存储占用8个字节,也即8*8=64位,其中1位用来符号,11位用来指数,剩下的52位表示尾数

float32float64之间可以进行类型转换,但需要注意精度损失。

布尔型

truefalse。不能与整型进行强转,也无法参与数值运算。

字符串型

在Go语言中,字符串是基本类型,它基于UTF-8编码实现。在遍历字符串型,我们需要区分byterune

分别以byterune方式遍历字符串:

f := "Golang编程"
fmt.Printf("byte len of f is %v\n", len(f))
fmt.Printf("rune len of f is %v\n", utf8.RuneCountInString(f))

上述例子的输出为:

byte len of f is 12
rune len of f is 8

第一种方式,统计的是字节的长度。由于中文字符在UTF-8中占用了3个字节,所以使用len方法获得的中文字符长度为6个字节。

第二种方式,统计的是字符的长度。

在本质上,byterune的底层类型分别为uint8int32。由于int32能够表达更多的值,可以更容易处理Unicode字符,所以rune能够处理一切的字符,而byte仅仅局限与处理ASCII字符。

指针

在C/C++语言中,指针直接操作内存的特性使得C/C++具备极高的性能,开发人员通过它直接操作和管理大块内存数据。但与此同时,指针偏移、运算和内存释放可能引发的错误也让指针编程饱受诟病。

Go语言限制了指针类型的偏移和运算能力,使得指针类型具备了指针高效访问的特性,但又不会发生指针偏移,避免了非法修改敏感数据的问题。同时Go语言中提供的自动垃圾回收机制,也减少了指针占用内存回收的复杂性。

在Go语言中,指针包含以下三个概念:

  • 指针地址
  • 指针类型
  • 指针取值

在程序运行过程中,每一个变量的值都保存在内存中,变量对应的内存有其特定的地址。假设某一个变量的类型为T,在Go语言中,我们可以通过取址符号&获取该变量对应内存的地址,生成该变量对应的指针。此时**,指针的值即变量的内存地址**,指针类型即*T,称为T的指针类型,*代表指针。

Go语言也提供根据指针获取变量值的取指操作*,通过取值操作*可以获取指针对应变量的值和对变量进行赋值操作。具体代码如下所示:

func main() {
  str := "Golang is Good!"
  // 获取 str 的指针
  strPrt := &str
  fmt.Printf("str type is %T, value is %v, address is %p\n", str, str, &str)
  fmt.Printf("strPtr type is %T, and value is %v\n", strPrt, strPrt)
  // 获取指针对应变量的值
  newStr := *strPrt 
  fmt.Printf("newStr type is %T, value is %v, and address is %p\n", newStr, newStr, &newStr)
  // 通过指针对变量进行赋值
  *strPrt = "Java is Good too!" 
  fmt.Printf("newStr type is %T, value is %v, and address is %p\n", newStr, newStr, &newStr)
  fmt.Printf("str type is %T, value is %v, address is %p\n", str, str, &str)
}

输出的结果为:

str type is string, value is Golang is Good!, address is 0xc00004a250
strPtr type is *string, and value is 0xc00004a250
newStr type is string, value is Golang is Good!, and address is 0xc00004a280
newStr type is string, value is Golang is Good!, and address is 0xc00004a280
str type is string, value is Java is Good too!, address is 0xc00004a250

在上述代码中,我们通过strPtr指针获取str的值赋予给newStr变量。

可以观察到strnewStr是两个不同的变量,它们对应的内存不一样,赋值过程中发生了值拷贝

值拷贝会创建新的内存空间,然后将原有变量的值复制到新的内存空间中,形成两个独立的变量。通过指针修改str变量的值并不会影响到newStr,因为这两个变量对应的内存地址不一样。

除了使用&对变量进行取址操作创建指针,还可以使用new函数直接分配内存,并返回指向内存的指针,此时内存中的值会被初始化为类型的默认值。如下例所示:

// 通过new函数创建一个*string指针
str := new(string)
// 通过指针对变量进行赋值
*str = "Golang is Good!"

在Go语言的flag包中,命令行参数一般以指返回。

常量

变量的值在运行时可变,而常量的值在声明之后不允许变化。通过const关键字可以声明常量。如下例所示:

const name T = 表达式

类型推导

Go语言的类型推导省略常量声明时的类型T同时声明多个常量。如下例所示:

// 省略类型T
const name = 表达式
// 同时声明多个常量
const (
  name1 = 表达式1
  name2 = 表达式2
)

类型别名

Go语言提供了类型别名的语法特性。类型别名本质上与原类型是属于同一个类型的,它相当于原类型T的一个别称。定义一个类型别名的样式如下:

type name = T

类型定义

类型定义会创建一个新的类型,新建的类型将具备原类型T的特性类型定义的样式如下:

type name T

通过一个例子理解类型别名和类型定义之间的区别:

type aliasInt = int // 定义一个类型别名
type myInt int // 定义一个新的类型
func main()  {
   var alias aliasInt
   fmt.Printf("alias value is %v, type is %T\n", alias, alias)
   var myint myInt
   fmt.Printf("myint value is %v, type is %T\n", myint, myint)
 }

输出结果为:

alias value is 0, type is int
myint value is 0, type is main.myInt

从输出结果中,我们可以看到通过类型别名aliasInt声明的alias变量还是int型,而重新定义的myInt属于新的类型,但是通过它声明的变量myintalias一样都为0。

分支与循环控制

Go语言的分支控制与其他语言相似,但是更为简略,简单的表达样式如下:

if expression1 {
  branch1
} else if expression2 {
  branch2
} else {
  branch3
}

Go语言中规定与if匹配的{必须与if和表达式位于同一行,同样的,else也必须与上一个分支的}位于同一行,否则会发生编译错误。表达式两边可以省略()

Go语言还提供了switch语句对大量的值和表达式进行判断。

  • 为了避免人为错误,switch中的每一个case都是独立的代码块,不需要通过break跳出switch选择体。
  • 如果需要继续执行接下来的case判断,需要添加fallthrough关键字对上下两个case进行连接。
  • 除了支持数值常量,Go语言的switch还能对字符串 、表达式等复杂情况进行处理。

一个简单的例子如下所示:

// 根据人名分配工作
name := "小红"
switch name {
    case "小明":
    fmt.Println("扫地")
    case "小红":
    fmt.Println("擦黑板")
    case "小刚":
    fmt.Println("倒垃圾")
    default:
    fmt.Println("没人干活")
}

在上面的例子中,每一个case都是字符串样式,且无需通过break控制跳出。

如果我们需要在case中判断表达式,在这种情况下switch后面不需要指定判断变量,这种形式就和if-else类似。如下例所示:

// 根据分数判断成绩程度
score := 90
switch {
    case score < 100 && score >= 90:
    fmt.Println("优秀")
    case score < 90 && score >= 80:
    fmt.Println("良好")
    case score < 80 && score >= 60:
    fmt.Println("及格")
    case score < 60:
    fmt.Println("不及格")
    default:
    fmt.Println("分数错误")
}

Go语言的循环体仅提供了for关键字,没有其他语言中提供的while或者do-while形式。基本样式如下:

for init; condition; end {
  循环体代码
}

这其中,初始语句、条件表达式、结束语句都可以不写。如果三者都缺省,这将变成一个无限循环语句,可以通过break关键字跳出循环体,或者使用continue关键字继续下一个循环。

Go中常用的容器

  • 当我们在程序中操作大量同类型变量时,为了方便数据的存储和操作,我们需要借助容器的力量。
  • Go语言中以标准库的方式提供了常用的容器实现,主要有固定大小的数组可以动态扩容的切片双向列表以及**key-value方式存储的字典**等。

数组

数组是一段存储固定类型固定长度的连续内存空间,它的大小在声明时就已经固定。数组的声明样式如下所示:

var name [size]T
  • size必须在静态编译时就确定其大小,不能动态指定
  • T表示数组成员的类型,可为任意类型

在Go语言,可以在声明时使用初始化列表对数组进行初始化,也可以通过下标对数据成员进行访问和赋值。如下所示:

var classMates1 [3]string
// 通过下标为数组成员赋值
classMates1[0] = "小明"
classMates1[1] = "小红"
classMates1[2] = "小李"
fmt.Println(classMates1)
// 通过下标访问数组成员
fmt.Println("The No.1 student is " + classMates1[0])
// 通过初始化列表声明并初始化数组
classMates2  := [...]string{"小明", "小红", "小李"}
fmt.Println(classMates2)

输出结果为:

[小明 小红 小李]
The No.1 student is 小明
[小明 小红 小李]
  • 使用初始化列表初始化数组时,需要注意[]内的数组大小需要和{}内的数组成员的数量一致。
  • 上述例子中,我们使用了...让编译器为我们根据{}内成员的数量确定数组的大小。

除此之外,我们还可以使用指针操作数组。如下例所示:

classMates3 := new([3]string)
classMates3[0] = "小明"
classMates3[1] = "小红"
classMates3[2] = "小李"
fmt.Println(*classMates3)

输出结果为:

[小明 小红 小李]
  • 在上述代码中,我们通过new函数申请了[3]string的内存空间并初始化,返回其对应的指针
  • 需要注意的是,该指针无法支持偏移和运算,这是Go语言对指针类型的限制。
  • 我们可以通过指针直接操作数组,这与C语言中的指针功能无异。

newmake的区别:new即分配内存又初始化,make只分配内存不初始化。

切片

  • 切片是对数组的一个连续片段的引用,它是一个容量可变的序列
  • 我们可以简单将切片理解为动态数组,它的内部结构包括底层数组指针、大小和容量,它通过指针引用底层数组,把对数据的读写操作限定在指定的区域内。

切片的结构体由三部分组成:

  • array:指向底层存储数据数组的指针
  • len:指当前切片的长度,即成员数量
  • cap:指当前切片的容量,它总是大于等于len

从原生数组中生成切片

我们可以从原有数组中生成一个切片,生成的切片指针即指向原数组。生成的样式如下:

slice := source[begin:end]
  • source表示生成切片的原有数组
  • begin表示切片的开始位置,end表示切片的结束位置,
  • 不包含end索引指向的原有数组成员,即左闭右开。

具体例子如下所示:

sourceArray := [...]int{1,2,3}
slice := sourceArray[0:1]
fmt.Printf("slice value is %v\n", slice)
fmt.Printf("slice len is %v\n", len(slice))
fmt.Printf("slice cap is %v\n", cap(slice))
slice[0] = 4
fmt.Printf("slice value is %v\n", slice)
fmt.Printf("sourceArray value is %v\n", sourceArray)

输出的结果为:

slice value is [1]
slice len is 1
slice cap is 3
slice value is [4]
sourceArray value is [4 2 3]
  • 因为切片作为指向原有数组的引用,对切片修改就是对原数组进行修改

动态创建切片

通过make函数(只分配内存空间,并不初始化)动态创建切片,在创建过程中指定切片的长度和容量。样式如下所示:

make([]T, size, cap)
  • T即切片中的成员类型
  • size为当前切片具备的长度
  • cap为当前切片预分配的长度,即切片的容量

例子如下所示:

slice = make([]int, 2, 4)
fmt.Printf("slice value is %v\n", slice)
fmt.Printf("slice len is %v\n", len(slice))
fmt.Printf("slice cap is %v\n", cap(slice))

输出的结果为:

slice value is [0 0]
slice len is 2
slice cap is 4

从上述输出可以看出make函数创建的新切片中的成员都被初始化为类型的初始值。

声明新的切片

直接声明新的切片类似于数组的初始化,但是不需要指定其大小,否则就变成了数组。样式如下所示:

var name []T

此时声明的切片并没有分配内存,我们可以在声明切片的同时对其进行初始化,如下例所示:

ex := []int{1, 2, 3}
fmt.Printf("ex value is %v\n", ex)
fmt.Printf("ex len is %v\n", len(ex))
fmt.Printf("ex cap is %v\n", cap(ex))

输出的结果为:

ex value is [1 2 3]
ex len is 3
ex cap is 3
  • 此时声明的切片大小和容量都为3

向切片添加元素

  • Go语言中提供了append内建函数用于动态向切片添加元素,它将返回新的切片。
  • 如果当前切片的容量可以容纳更多的元素,即len小于cap,添加操作将在切片指向的原有数组上进行,这将会覆盖掉原有数组的值。
  • 如果当前切片的容量不足以容纳更多的元素,那么切片将会进行扩容
  • 扩容的具体过程为:申请一个新的连续内存空间,空间大小一般为原有容量的两倍,然后将原来数组中的数据复制到新的数组中,同时将切片中的指针指向新的数组,最后将新的元素添加到新的数组中。

通过下例演示切片的动态扩容:

arr1 := [...]int{1,2,3,4}
arr2 := [...]int{1,2,3,4}
sli1 := arr1[0:2] // 长度为2,容量为4
sli2 := arr2[2:4] // 长度为2,容量为2
fmt.Printf("sli1 pointer is %p, len is %v, cap is %v, value is %v\n", &sli1, len(sli1), cap(sli1), sli1)
fmt.Printf("sli2 pointer is %p, len is %v, cap is %v, value is %v\n", &sli2, len(sli2), cap(sli2), sli2)
newSli1 := append(sli1, 5)
fmt.Printf("newSli1 pointer is %p, len is %v, cap is %v, value is %v\n", &newSli1, len(newSli1), cap(newSli1), newSli1)
fmt.Printf("source arr1 become %v\n", arr1)
newSli2 := append(sli2, 5)
fmt.Printf("newSli2 pointer is %p, len is %v, cap is %v, value is %v\n", &newSli2, len(newSli2), cap(newSli2), newSli2)
fmt.Printf("source arr2 become %v\n", arr2)

上例的输出结果为:

sli1 pointer is 0xc000004078, len is 2, cap is 4, value is [1 2]
sli2 pointer is 0xc000004090, len is 2, cap is 2, value is [3 4]
newSli1 pointer is 0xc0000040d8, len is 3, cap is 4, value is [1 2 5]
source arr1 become [1 2 5 4]
newSli2 pointer is 0xc000004108, len is 3, cap is 4, value is [3 4 5]
source arr2 become [1 2 3 4]
  • 通过上面的例子,我们可以发现,容量足够的sli1直接将append添加的新元素覆盖到原有数组arr1中。而容量不够的sli2进行了扩容操作,申请了新的底层数组,不在原数组的基础上进行操作。在实际使用的过程要记住这两种的区别。

如果原有数组可以添加新的元素,但切片自身的容量已经饱和,此时进行append操作,同样会进行扩容,申请新的内存空间。如下例所示:

arr3 := [...]int{1,2,3,4}
sli3 := arr3[0:2:2] // 长度为2,容量为2
fmt.Printf("sli3 pointer is %p, len is %v, cap is %v, value is %v\n", &sli3, len(sli3), cap(sli3), sli3)
newSli3 := append(sli3,5)
fmt.Printf("newSli3 pointer is %p, len is %v, cap is %v, value is %v\n", &newSli3, len(newSli3), cap(newSli3), newSli3)
fmt.Printf("source arr3 become %v\n", arr3)

对应的输出结果为:

sli3 pointer is 0xc000004138, len is 2, cap is 2, value is [1 2]
newSli3 pointer is 0xc000004168, len is 3, cap is 4, value is [1 2 5]
source arr3 become [1 2 3 4]
  • 在上述代码中,我们指定了创建切片的第三个参数cap。这里的cap不是切片容量,切片容量是cap-begin

为了方便切片的数据快速复制到另一个切片中,Go语言提供了内建的copy函数。它的使用样式如下:

copy(destSli, srcSli []T)

它的返回结果为实际发生复制的元素个数。

列表

Go语言中的列表即双向链表,它适合于存储需要经常进行元素插入和删除操作的元素集合

列表的初始化样式如下所示:

// 方法一
var name list.List
// 方法二
name := list.New()
  • 方法一直接声明初始化列表,方法二使用container/list包中的New函数初始化列表,返回列表对应的指针。
  • 可以注意到,例表没有限制其内保存成员的类型,即任意类型的成员都可以同时存在列表中。

演示列表的插入、删除和遍历操作:

tmpList := list.New()
// 尾插
for i := 1; i <= 10; i++ {
    tmpList.PushBack(i)
}
// 头插
first := tmpList.PushFront(0)
// 删除
tmpList.Remove(first)
// 遍历
for l := tmpList.Front(); l != nil; l = l.Next() {
    fmt.Print(l.Value, " ")
}

字典

Go语言中的字典用于存储键值对,其中每一个键都会映射到一个值。其内部通过散列表的方式实现。定义的样式如下所示:

name := make(map[keyType]valueType)

通过一个简单的例子演示map使用方式:

classMates1 := make(map[int]string)
// 添加映射关系
classMates1[0] = "小明"
classMates1[1] = "小红"
classMates1[2] = "小张"
fmt.Printf("id %v is %v\n", 1, classMates1[1])
// 在声明时初始化数据
classMates2 := map[int]string{
  0 : "小明",
  1 : "小红",
  2 : "小张",
}
fmt.Printf("id %v is %v\n", 3, classMates2[3])
  • 如上代码所示,我们可以使用make函数为map分配内存空间后,再为map一一添加键值对映射关系。
  • 也可以直接在声明时通过类JSON格式添加键值对映射关系 。
  • map中可以通过键直接查询对应的值,如果不存在这样的键,将会返回值类型的默认值。

可以采用以下方式查询某个键是否存在于map中:

mate,ok := classMates2[1]

如果键存在于map中,布尔型ok将会是truemate为对应的值。

容器遍历

下例通过for-range遍历数组、切片、字典

// 数组的遍历
nums := [...]int{1, 2, 3, 4, 5, 6, 7, 8}
for k, v := range nums {
    // k为下标,v为对应的值
    fmt.Println(k, v, " ")
}
fmt.Println()
// 切片的遍历
slis := []int{1, 2, 3, 4, 5, 6, 7, 8}
for k, v := range slis {
    // k为下标,v为对应的值
    fmt.Println(k, v, " ")
}
fmt.Println()
// 字典的遍历
tmpMap := map[int]string{
    0: "小明",
    1: "小红",
    2: "小张",
}
for k, v := range tmpMap {
    // k为键值,v为对应值
    fmt.Println(k, v, " ")
}
  • 可以将不需要的键或值改为匿名变量
  • 列表的遍历比较特殊,需要配合Front函数获取列表的头元素,再使用其Next函数依次往下遍历,具体代码可见上面列表部分的举例。

特别注意:在for-range遍历过程中,键和值是值拷贝,对它们修改不会影响容器内成员变化。

目录
相关文章
|
6天前
|
人工智能 Go 调度
掌握Go并发:Go语言并发编程深度解析
掌握Go并发:Go语言并发编程深度解析
|
22小时前
|
Go 开发者
Golang深入浅出之-Go语言项目构建工具:Makefile与go build
【4月更文挑战第27天】本文探讨了Go语言项目的构建方法,包括`go build`基本命令行工具和更灵活的`Makefile`自动化脚本。`go build`适合简单项目,能直接编译Go源码,但依赖管理可能混乱。通过设置`GOOS`和`GOARCH`可进行跨平台编译。`Makefile`适用于复杂构建流程,能定义多步骤任务,但编写较复杂。在选择构建方式时,应根据项目需求权衡,从`go build`起步,逐渐过渡到Makefile以实现更高效自动化。
9 2
|
22小时前
|
存储 Go
Golang深入浅出之-Go语言依赖管理:GOPATH与Go Modules
【4月更文挑战第27天】Go语言依赖管理从`GOPATH`进化到Go Modules。`GOPATH`时代,项目结构混乱,可通过设置多个工作空间管理。Go Modules自Go 1.11起提供更现代的管理方式,通过`go.mod`文件控制依赖。常见问题包括忘记更新`go.mod`、处理本地依赖和模块私有化,可使用`go mod tidy`、`replace`语句和`go mod vendor`解决。理解并掌握Go Modules对现代Go开发至关重要。
7 2
|
1天前
|
存储 安全 中间件
【Go语言专栏】Go语言中的安全认证与授权机制
【4月更文挑战第30天】本文探讨了Go语言中实现安全认证与授权的方法。认证机制包括HTTP Basic Auth、表单认证、OAuth和JWT,可借助`net/http`及第三方库实现。授权则通过中间件或拦截器,如RBAC、ABAC和上下文相关授权,`casbin`和`go-permission`等库提供解决方案。实践中,需设计认证流程、存储用户凭证、实现逻辑、定义授权策略和编写中间件,并确保安全性。案例分析展示了认证授权在RESTful API服务中的应用。在Go开发中,不断学习和优化安全策略以应对安全挑战至关重要。
|
1天前
|
SQL 关系型数据库 MySQL
【Go语言专栏】使用Go语言连接MySQL数据库
【4月更文挑战第30天】本文介绍了如何使用Go语言连接和操作MySQL数据库,包括选择`go-sql-driver/mysql`驱动、安装导入、建立连接、执行SQL查询、插入/更新/删除操作、事务处理以及性能优化和最佳实践。通过示例代码,展示了连接数据库、使用连接池、事务管理和性能调优的方法,帮助开发者构建高效、稳定的Web应用。
|
1天前
|
缓存 监控 前端开发
【Go 语言专栏】Go 语言中的 WebSocket 与 Socket.IO 集成
【4月更文挑战第30天】本文介绍了在 Go 语言中集成 WebSocket 与 Socket.IO 的相关技术,WebSocket 是一种高效的双向通信协议,Socket.IO 是一个实时通信库,提供丰富的事件处理。集成两者能实现更强大的实时通信功能。文章讨论了 Go 中 WebSocket 的实现,Socket.IO 与 WebSocket 的关系,集成的意义及步骤,并提醒注意协议兼容性、消息格式等问题。此外,还提到了性能优化策略和应用案例,如实时聊天、数据监控和在线协作工具。通过集成,开发者可以构建出满足多样化需求的实时通信应用。
|
1天前
|
缓存 监控 前端开发
【Go 语言专栏】Go 语言中的 WebSocket 实时通信应用
【4月更文挑战第30天】本文探讨了Go语言在WebSocket实时通信中的应用。WebSocket作为全双工通信协议,允许持续的双向通信。Go语言凭借其高效和并发特性,适合构建实时应用。文中概述了在Go中实现WebSocket的基本步骤,包括服务器和客户端的建立与通信,并列举了实时聊天、数据监控和在线协作等应用案例。同时,强调了消息格式、并发处理、错误处理和安全性的注意事项。通过数据压缩、缓存管理和连接管理等策略可优化性能。Go语言还能与数据库和前端框架结合,提升用户体验。总之,Go语言为WebSocket实时通信提供了强大支持,有望在更多领域发挥作用。
|
1天前
|
网络协议 Java Go
【Go语言专栏】Go语言中的WebSocket实时通信应用
【4月更文挑战第30天】Go语言(Golang)是Google开发的编程语言,适用于云计算、微服务等领域。本文介绍了WebSocket,一种实现浏览器与服务器全双工通信的协议,其特点是实时性、全双工和轻量级。在Go中实现WebSocket,可以使用gorilla/websocket库。示例展示了如何创建服务器端和客户端,实现消息的收发。WebSocket广泛应用于聊天、游戏、通知推送和实时数据同步等场景。学习Go语言中的WebSocket对于开发实时通信应用至关重要。
|
1天前
|
中间件 Go
【Go语言专栏】Go语言中的中间件与路由处理
【4月更文挑战第30天】本文探讨了Go语言中的路由和中间件处理。路由负责根据URL映射到处理器,`net/http`包提供基础支持,而Gin和Echo等第三方库则提供更强大功能。中间件在请求处理链中执行预处理任务,如日志记录和认证。`net/http`包通过嵌套处理器实现中间件,而Gin和Echo则有更简洁的中间件接口。通过路由和中间件,可构建高效、结构良好的Web应用。建议进一步阅读官方文档以深化理解。
|
1天前
|
存储 关系型数据库 Go
【Go语言专栏】基于Go语言的RESTful API开发
【4月更文挑战第30天】本文介绍了使用Go语言开发RESTful API的方法,涵盖了路由、请求处理、数据存储和测试关键点。RESTful API基于HTTP协议,无状态且使用标准方法表示操作。在Go中,通过第三方库如`gorilla/mux`进行路由映射,使用`net/http`处理请求,与数据库交互可选ORM库`gorm`,测试则依赖于Go内置的`testing`框架。Go的简洁性和并发性使得它成为构建高效API的理想选择。