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

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

在 Go(Golang)中,go.mod 文件中的依赖为//indirect。

来源:golangbyexample.com/indirect-dependency-golang/

目录

概述

  • 示例

概述

正如名称所示,//indirect表示在go.mod文件中的依赖是间接的。go.mod文件仅记录直接依赖。然而,在以下情况下,它可能记录间接依赖。

  • 任何未列入直接依赖的go.mod文件中的间接依赖,或者如果直接依赖没有go.mod文件,则该依赖将以//indirect作为后缀添加到 go.mod 文件中。

  • 任何未在模块的任何源文件中导入的依赖。如果通过 go get 添加了该依赖,它将被记录为//indirect

示例

让我们通过示例理解上述两点。为此,首先创建一个模块。

git mod init learn

现在创建一个文件learn.go

package main

import (
	"github.com/gocolly/colly"
)

func main() {
	_ = colly.NewCollector()
}

请注意,我们在learn.go中指定了该依赖。

github.com/gocolly/colly

因此,github.com/gocolly/collylearn模块的直接依赖,因为它在模块中被直接导入。让我们将 colly 版本 v1.2.0 作为依赖添加到 go.mod 文件中。

module learn

go 1.14

require	github.com/gocolly/colly v1.2.0

现在让我们运行以下命令。

go mod tidy

在运行该命令后,我们再次检查go.mod文件的内容。由于 colly 版本 v1.2.0 没有go.mod文件,colly 所需的所有依赖将以//indirect作为后缀添加到go.mod文件中。

执行cat go.mod

module learn

go 1.14

require (
	github.com/PuerkitoBio/goquery v1.6.0 // indirect
	github.com/antchfx/htmlquery v1.2.3 // indirect
	github.com/antchfx/xmlquery v1.3.3 // indirect
	github.com/gobwas/glob v0.2.3 // indirect
	github.com/gocolly/colly v1.2.0
	github.com/kennygrant/sanitize v1.2.4 // indirect
	github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
	github.com/temoto/robotstxt v1.1.1 // indirect
	golang.org/x/net v0.0.0-20201027133719-8eef5233e2a1 // indirect
	google.golang.org/appengine v1.6.7 // indirect
)

所有其他依赖均以//indirect作为后缀。此外,所有直接和间接依赖的校验和将记录在 go.sum 文件中。

我们还提到,任何未在源文件中导入的依赖将标记为//indirect。为此,请删除上面创建的learn.go。同时清理go.mod文件以移除所有 require 行。现在运行以下命令。

go get github.com/pborman/uuid

现在检查go.mod文件的内容。

module learn

go 1.14

require github.com/pborman/uuid v1.2.1 //indirect

请注意,该依赖被记录为//indirect,因为它未在任何源文件中导入。现在创建 uuid.go 文件,如下所示,导入github.com/pborman/uuid作为依赖。

package main

import (
	"fmt"
	"strings"

	"github.com/pborman/uuid"
)

func main() {
	uuidWithHyphen := uuid.NewRandom()
	uuid := strings.Replace(uuidWithHyphen.String(), "-", "", -1)
	fmt.Println(uuid)
}

现在进行 go build。在 go build 后再次检查 go.mod 文件的内容。由于该依赖在 uuid.go 模块的源文件中被要求,//indirect将被移除。

module learn

go 1.14

require github.com/pborman/uuid v1.2.1

* 或在 Go (Golang) 中解引用指针

来源:golangbyexample.com/dereferencing-pointer-golang/

目录

  • 概述

  • 程序

概述

***** 运算符可以用来:

  • 解引用指针意味着获取存储在指针地址中的值。

  • 还可以改变该指针位置的值

程序

package main

import "fmt"

func main() {
	a := 2
	b := &a
	fmt.Println(a)
	fmt.Println(*b)

	*b = 3
	fmt.Println(a)
	fmt.Println(*b)

	a = 4
	fmt.Println(a)
	fmt.Println(*b)
}

输出

2
2
3
3
4
4

ab 在内部引用同一个变量。因此,改变一个的值会反映在另一个上。并且,**** 和 & 也可以一起使用,但它们会相互抵消。

因此,下面的两个是等价的,并将打印 2

  • a

  • *&a

下面的三个是等价的,并将打印存储在 b 变量中的变量 a 的地址

  • b

  • *&b

  • &*b

注意: *a 不是有效的操作,因为 a 不是指针

关于 GOLANG

来源:golangbyexample.com/about-golang/

这是 golang 综合教程系列的第一章。有关系列其他章节,请参阅此链接 – Golang 综合教程系列

以下是当前教程的目录。

目录

  • 概述

  • 历史

  • GO 的不同之处

    • 简单易用的语法

    • 快速编译的编译语言

    • 静态类型语言

    • 垃圾回收

    • 内置并发

    • 标准库

    • 工具

  • 与其他编程语言的比较

  • GO 的缺点

    • GO 中没有继承

    • GO 中没有泛型

    • 没有编译时多态或函数重载

  • 结论:

概述

这是第一篇将为你介绍 GOLANG 语言的教程。Go 是一种静态类型的编译编程语言。它是一种开源语言,由谷歌开发。

由于 GO 是一种开源编程语言,它托管在 GitHub 上,并且任何人都可以贡献代码。

github.com/golang/go

Go 具有 C 风格的语法,但提供内存安全、垃圾回收和内置并发。

历史

GO 于 2011 年首次稳定发布。

GO 的不同之处

让我们看看一些使 Go 与其他编程语言不同的特点。

简单易用的语法

Go 的语法与 C 相似,语法非常简单。学习起来容易,几乎没有复杂的方面。它的特性不算繁杂,因此编写可读和可维护的代码变得更容易。此外,与其他主流语言相比,GO 的关键字非常少,GO 规范提到有 25 个关键字。

golang.org/ref/spec#Keywords

快速编译的编译语言

Go 编译成本地可执行文件。它的编译速度非常快,这归因于几个原因。您可以查看这个链接以获取有关 Go 编译速度快的更多细节。当您构建一个 Go 程序时,生成的二进制文件就是它的本身。没有其他依赖项。您可以将这个二进制文件移植到任何支持 Go 的平台上,并在那执行。这与其他编程语言如 JAVA、Python 的情况不同,因为创建一个自包含的二进制文件时需要处理和打包一堆依赖项。在 Go 中没有动态链接库的概念,实际上它支持静态链接。Go 与所有库静态链接成一个单独的庞大二进制文件。

静态类型语言

尽管 Go 具有非常简洁的语法,但它是一种强大且静态的类型语言。该语言不允许隐式转换。由于是静态类型,绝大多数与类型不匹配相关的错误在编译时就会被捕获,不像 Python、Ruby 等语言。

垃圾回收

Go 是一种垃圾回收语言。这意味着您不必担心释放内存。因此,您不必像在 C、C++ 中那样关心 malloc() 和 free() 语句。

内置并发

Go 通过其内置并发试图解决与其他语言相关的两个主要问题。

  • 线程的创建并不高效地使用内存。

  • 线程之间的通信较为困难,存在线程锁定、死锁等问题。

Go 对并发的处理可以最好地描述为。

Do not communicate by sharing memory; instead, share memory by communicating

Go 通过其两种并发原语来实现上述目标。

  • Goroutine – 轻量级独立执行以实现并发/并行。其大小从 8kb 开始,而 JAVA 线程的大小约为 1 MB。

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

标准库

Go 的标准库功能丰富,支持几乎所有构建各种类型应用所需的功能。Go 社区还在 Go 标准库的基础上创建了大量现成可用的包,并可在 Github 上获取。

工具

Go 对工具的支持强大且丰富。它有用于代码格式化、单元测试、竞争检测、代码检查、内存分析工具、生成文档的工具等。

与其他编程语言的比较

如果将上述所有优点与其他编程语言进行比较,您会注意到。

与 Java 的比较

  • Go 的速度与 JAVA 相当,但由于其更简单的语法,与 JAVA 相比,学习、编写和维护 Go 代码更容易。

与 C/C++ 的比较

  • Go 的速度比 C 稍慢,但由于其简化的语法,与 C/C++ 相比,学习、编写和维护 Go 代码更容易。

  • GO 也比 C/C++更安全,因为它是垃圾收集语言,也不允许存在于 C/C++中的棘手指针操作。作为一门 GC 语言,你无需担心 malloc()和 free()操作。

使用 Python/Ruby/Java Script

  • 与上述编程语言相比,GO 非常快速

  • 这些语言的代码可读性更强,但 GO 是编译型和静态类型的语言,有助于在编译阶段捕获一些棘手的错误。

GO 的缺点

让我们看看 GO 的一些缺点

GO 中没有继承

Go 没有类,只有结构体。尽管结构体的功能与类非常相似,但 GO 更倾向于组合而非继承。GO 也不支持类型层次结构,没有像 JAVA 中的 extends 或 implements 这样的关键字。因此在这方面,GO 不是一种纯粹的面向对象编程语言。缺少这些特性可能对习惯于面向对象编程的编码者来说显得奇怪。

GO 中没有泛型

由于 GO 中缺少泛型,它期望你为想要做的事情编写非常明确的代码。尽管有关于很快会向 GO 添加泛型支持的讨论。

没有编译时多态或函数重载

在 GO 中不可能进行函数重载。请查看这个常见问题了解原因 golang.org/doc/faq#overloading

根据上述常见问题,没有这些东西会简单得多。尽管有一些变通方法可以实现相同的功能。可以参考这个链接

golangbyexample.com/function-method-overloading-golang/

结论:

总结一下我们到目前为止讨论的内容

  • Go 是静态类型,其性能和运行时效率与 C/C++相当

  • 语法非常简单,写代码像在 Python、Ruby 中一样容易

  • 内置的并发支持,拥有良好的标准库和丰富的工具。

  • GO 不是一种纯粹的面向对象编程语言,与其他主流语言相比,其错误处理风格不同

这是对 Golang 的基本介绍。希望你喜欢这篇文章。请在评论中分享反馈、改进意见或错误。

下一个教程 – GO 安装

GO 中的抽象类:完整指南

来源:golangbyexample.com/go-abstract-class/

Go 接口没有字段,也不允许在其中定义方法。任何类型都需要实现接口的所有方法才能成为该接口类型。有些用例中,提供方法的默认实现和默认字段在 GO 中是有用的。在理解如何实现之前,首先让我们了解抽象类的要求:

  1. 抽象类应该有默认字段

  2. 抽象类应该有默认方法

  3. 不应该能够直接创建抽象类的实例。

我们将使用接口(抽象接口)结构(抽象具体类型)的组合。它们可以提供抽象类的功能。请参见下面的程序:

package main

import "fmt"

//Abstract Interface
type iAlpha interface {
    work()
    common()
}

//Abstract Concrete Type
type alpha struct {
    name string
}

func (a *alpha) common() {
    fmt.Println("common called")
}

//Implementing Type
type beta struct {
    alpha
}

func (b *beta) work() {
    fmt.Println("work called")
    fmt.Printf("name is %s\n", b.name)
    b.common()
}

func main() {
    a := alpha{
        name: "test",
    }
    b := &beta{
        alpha: a,
    }
    b.work()
}

输出:

work called
name is test
common called

在上述程序中:

  • 我们创建了一个抽象接口iAlpha、一个抽象具体结构alpha和一个实现者结构beta

  • alpha结构嵌入在beta结构中。

  • beta 结构能够访问默认字段“name”

  • beta 结构能够访问默认方法“common”

  • 无法创建iAlpha的直接实例,因为alpha结构仅实现了iAlpha的一个方法。

因此它满足所有三个要求,但上述方法也有一个限制。无法从 alpha 的“common”方法调用“work”方法。基本上,无法从抽象具体类型的默认方法调用抽象接口的未定义方法。不过,有一种方法可以解决这个问题。请参见下面的程序。

package main

import "fmt"

//Abstract Interface
type iAlpha interface {
    work()
    common()
}

//Abstract Concrete Type
type alpha struct {
    name string
    work func()
}

func (a *alpha) common() {
    fmt.Println("common called")
    a.work()
}

//Implementing Type
type beta struct {
    alpha
}

func (b *beta) work() {
    fmt.Println("work called")
    fmt.Printf("name is %s\n", b.name)
}

func main() {
    a := alpha{
        name: "test",
    }
    b := &beta{
        alpha: a,
    }
    b.alpha.work = b.work
    b.common()
}

输出:

common called
work called
name is test

在上述程序中:

  • 我们在 alpha 中创建了一个类型为 func 的新字段“work”

  • 我们将 alpha 的“work”方法赋值给 beta 的“work”方法

上述程序唯一的问题是可以直接实例化alpha结构,并通过提供work方法的定义,创建类型为iAlpha的实例。这违反了上述抽象类要求的第 3 点,因为在未创建我们自己的新类型的情况下,我们能够创建iAlpha的类型。让我们尝试解决这个问题。下面的程序还解决了无法从默认方法调用未定义方法的问题。

package main

import "fmt"

//Abstract Interface
type iAlpha interface {
    work()
    common(iAlpha)
}

//Abstract Concrete Type
type alpha struct {
    name string
}

func (a *alpha) common(i iAlpha) {
    fmt.Println("common called")
    i.work()
}

//Implementing Type
type beta struct {
    alpha
}

func (b *beta) work() {
    fmt.Println("work called")
    fmt.Printf("Name is %s\n", b.name)
}

func main() {
    a := alpha{
        name: "test",
    }
    b := &beta{
        alpha: a,
    }
    b.common(b)
}

输出:

common called
work called
Name is test

在上述程序中:

  • 所有默认方法将接受接口类型iAlpha的第一个参数。所有alpha结构的未定义方法将通过从默认方法传入的这个参数进行调用。

结论: 我们可以在上述程序中看到,能够满足抽象类的所有三个要求。这是模拟 GO 中抽象类的一种方法。

Go 中的抽象工厂设计模式

来源:golangbyexample.com/abstract-factory-design-pattern-go/

注意:如果你对如何在 GO 中实现所有其他设计模式感兴趣,请查看这个完整参考 – Go 中的所有设计模式 (Golang)

目录

** 定义:

  • 代码:

定义:

抽象工厂设计模式是一种创建型设计模式,它允许你创建一组相关的对象。它是工厂模式的抽象化。最好用一个例子来解释。假设我们有两个工厂

  • nike

  • adidas

想象一下,你需要购买一个包含短裤的运动装备。通常情况下,你会想要购买一个同一工厂的完整运动装备,即nikeadidas。这就是抽象工厂发挥作用的地方,因为你想要的具体产品是短裤,这些产品将由nikeadidas的抽象工厂创建。

这两个工厂——nikeadidas实现了iSportsFactory接口。

我们有两个产品接口。

  • iShoe – 该接口由nikeShoeadidasShoe具体产品实现。

  • iShort – 该接口由nikeShortadidasShort具体产品实现。

现在让我们看看代码

代码:

iSportsFactory.go

package main

import "fmt"

type iSportsFactory interface {
    makeShoe() iShoe
    makeShort() iShort
}

func getSportsFactory(brand string) (iSportsFactory, error) {
    if brand == "adidas" {
        return &adidas{}, nil
    }
    if brand == "nike" {
        return &nike{}, nil
    }
    return nil, fmt.Errorf("Wrong brand type passed")
}

adidas.go

package main

type adidas struct {
}

func (a *adidas) makeShoe() iShoe {
    return &adidasShoe{
        shoe: shoe{
            logo: "adidas",
            size: 14,
        },
    }
}

func (a *adidas) makeShort() iShort {
    return &adidasShort{
        short: short{
            logo: "adidas",
            size: 14,
        },
    }
} 

nike.go

package main

type nike struct {
}

func (n *nike) makeShoe() iShoe {
    return &nikeShoe{
        shoe: shoe{
            logo: "nike",
            size: 14,
        },
    }
}

func (n *nike) makeShort() iShort {
    return &nikeShort{
        short: short{
            logo: "nike",
            size: 14,
        },
    }
}

iShoe.go

package main

type iShoe interface {
    setLogo(logo string)
    setSize(size int)
    getLogo() string
    getSize() int
}

type shoe struct {
    logo string
    size int
}

func (s *shoe) setLogo(logo string) {
    s.logo = logo
}

func (s *shoe) getLogo() string {
    return s.logo
}

func (s *shoe) setSize(size int) {
    s.size = size
}

func (s *shoe) getSize() int {
    return s.size
} 

adidasShoe.go

package main

type adidasShoe struct {
	shoe
} 

nikeShoe.go

package main

type nikeShoe struct {
    shoe
}

iShort.go

package main

type iShort interface {
    setLogo(logo string)
    setSize(size int)
    getLogo() string
    getSize() int
}

type short struct {
    logo string
    size int
}

func (s *short) setLogo(logo string) {
    s.logo = logo
}

func (s *short) getLogo() string {
    return s.logo
}

func (s *short) setSize(size int) {
    s.size = size
}

func (s *short) getSize() int {
    return s.size
}

adidasShort.go

package main

type adidasShort struct {
    short
}

nikeShort.go

package main

type nikeShort struct {
    short
}

main.go

package main

import "fmt"

func main() {
    adidasFactory, _ := getSportsFactory("adidas")
    nikeFactory, _ := getSportsFactory("nike")
    nikeShoe := nikeFactory.makeShoe()
    nikeShort := nikeFactory.makeShort()
    adidasShoe := adidasFactory.makeShoe()
    adidasShort := adidasFactory.makeShort()
    printShoeDetails(nikeShoe)
    printShortDetails(nikeShort)
    printShoeDetails(adidasShoe)
    printShortDetails(adidasShort)
}

func printShoeDetails(s iShoe) {
    fmt.Printf("Logo: %s", s.getLogo())
    fmt.Println()
    fmt.Printf("Size: %d", s.getSize())
    fmt.Println()
}

func printShortDetails(s iShort) {
    fmt.Printf("Logo: %s", s.getLogo())
    fmt.Println()
    fmt.Printf("Size: %d", s.getSize())
    fmt.Println()
}

输出:

Logo: nike
Size: 14
Logo: nike
Size: 14
Logo: adidas
Size: 14
Logo: adidas
Size: 14
```*


<!--yml

类别:未分类

日期:2024-10-13 06:23:01

-->

# 在 Go 语言(Golang)中访问接口的基础变量

> 来源:[`golangbyexample.com/access-underlying-type-interface-golang/`](https://golangbyexample.com/access-underlying-type-interface-golang/)

目录

+   概述

+   类型断言

+   类型切换

# **概述**

像其他任何变量一样,接口变量由类型和值表示。接口值在底层由两个元组组成。

+   基础类型

+   基础值

请查看下面的图示,说明我们上面提到的内容。

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

接口的基础变量可以通过两种方式访问。

+   类型断言

+   类型切换

# **类型断言**

类型断言提供了一种通过断言正确的基础值类型来访问接口值内部的基础变量的方法。下面是该语法,其中 i 是一个接口。

```go
val := i.({type})

上述语句正在断言接口中的基础值的类型为{type}。如果这是真的,那么基础值将被赋值给val.如果不是,则上述语句会导致恐慌。

假设我们有如下的接口动物

type animal interface {
    breathe()
    walk()
}

我们还有一个实现了动物接口的狮子结构体。

type lion struct {
    age int
}

代码

package main

import "fmt"

type animal interface {
	breathe()
	walk()
}

type lion struct {
	age int
}

func (l lion) breathe() {
	fmt.Println("Lion breathes")
}

func (l lion) walk() {
	fmt.Println("Lion walk")
}

type dog struct {
	age int
}

func (d dog) breathe() {
	fmt.Println("Dog breathes")
}

func (d dog) walk() {
	fmt.Println("Dog walk")
}

func main() {
	var a animal

	a = lion{age: 10}
	print(a)

}

func print(a animal) {
	l := a.(lion)
	fmt.Printf("Age: %d\n", l.age)

	//d := a.(dog)
	//fmt.Printf("Age: %d\n", d.age)
}

输出

Age: 10

这就是我们如何将类型为动物的变量a断言为具有基础类型狮子

l := a.(lion)

下面的行将导致恐慌,因为基础类型是狮子而不是。取消注释该行以查看。

//d := a.(dog)

类型断言提供了另一种获取基础值的方法,也能防止恐慌。其语法为:

val, ok := i.(<type>)</type>

在这种情况下,类型断言返回两个值,第一个值与上面讨论的相同,另一个值是布尔值,表示类型断言是否正确。这个值是

  • 如果类型断言正确,则为真,这意味着断言的类型与基础类型相同。

  • 如果类型断言失败则为假。

所以第二种方法是进行类型断言的好方法,因为它可以防止恐慌。让我们来看一个例子。

package main

import "fmt"

type animal interface {
	breathe()
	walk()
}

type lion struct {
	age int
}

func (l lion) breathe() {
	fmt.Println("Lion breathes")
}

func (l lion) walk() {
	fmt.Println("Lion walk")
}

type dog struct {
	age int
}

func (d dog) breathe() {
	fmt.Println("Dog breathes")
}

func (d dog) walk() {
	fmt.Println("Dog walk")
}

func main() {
	var a animal

	a = lion{age: 10}
	print(a)

}

func print(a animal) {
	l, ok := a.(lion)
	if ok {
		fmt.Println(l)
	} else {
		fmt.Println("a is not of type lion")
	}

	d, ok := a.(dog)
	if ok {
		fmt.Println(d)
	} else {
		fmt.Println("a is not of type lion")
	}
}

输出:

{10}
a is not of type lion

让我们现在继续讨论类型切换。

类型切换

类型切换使我们能够逐个进行上述类型断言。请查看下面的代码示例。

package main

import "fmt"

type animal interface {
	breathe()
	walk()
}

type lion struct {
	age int
}

func (l lion) breathe() {
	fmt.Println("Lion breathes")
}

func (l lion) walk() {
	fmt.Println("Lion walk")
}

type dog struct {
	age int
}

func (d dog) breathe() {
	fmt.Println("Dog breathes")
}

func (d dog) walk() {
	fmt.Println("Dog walk")
}

func main() {
	var a animal

	a = lion{age: 10}
	print(a)

}

func print(a animal) {
	switch v := a.(type) {
	case lion:
		fmt.Println("Type: lion")
	case dog:
		fmt.Println("Type: dog")
	default:
		fmt.Printf("Unknown Type %T", v)
	}
}

输出:

Type: lion

在上面的代码中,通过使用类型切换,我们确定接口变量a中包含的值的类型是狮子或其他某种类型。如果需要,可以在案例语句中添加更多不同的类型。

在 Go (Golang) 中访问和设置结构体字段

来源:golangbyexample.com/accessing-setting-struct-fields-golang/

概述

Go 结构体是命名的数据字段集合,可以包含不同类型的数据。结构体作为一个容器,具有不同的异构数据类型,共同表示一个实体。例如,不同的属性用于表示一个组织中的员工。员工可以有

  • 字符串类型的姓名

  • 整型的年龄

  • time.Time 类型的出生日期

  • 整型的工资

.. 以及其他。结构体可以用来表示一个员工

type employee struct {
    name   string
    age    int
    salary int
}

目录

** 访问和设置结构体字段

访问和设置结构体字段

结构体变量可以如下创建

emp := employee{name: "Sam", age: 31, salary: 2000}

一旦创建了结构体变量,可以使用点运算符访问结构体字段。以下是获取值的格式

n := emp.name

同样也可以给结构体字段赋值。

emp.name = "some_new_name"

让我们看一个例子

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func main() {
    emp := employee{name: "Sam", age: 31, salary: 2000}

    //Accessing a struct field
    n := emp.name
    fmt.Printf("Current name is: %s\n", n)

    //Assigning a new value
    emp.name = "John"
    fmt.Printf("New name is: %s\n", emp.name)
}

输出

Current name is: Sam
New name is: John

Go (GoLang) 中的适配器设计模式

来源:golangbyexample.com/adapter-design-pattern-go/

注意:有兴趣了解所有其他设计模式如何在 GO 中实现。请参见此完整参考 – Go 中的所有设计模式 (Golang)

目录

** 介绍:

  • 何时使用

  • UML 图

  • 映射

  • 示例:

介绍:

此设计模式是一种结构设计模式。该模式通过示例最佳理解。假设你有两台笔记本电脑

  1. MacBook Pro

  2. Windows 笔记本电脑

MacBook Pro 的 USB 端口是方形的,而 Windows 的 USB 端口是圆形的。作为客户端,你有一根方形的 USB 电缆,所以只能插入 Mac 笔记本电脑。因此,你看到了这里的问题。

问题:

  • 我们有一个类(客户端)期望对象的某些特性(此处为方形 USB 端口),但我们还有另一个称为适配者的对象(此处为 Windows 笔记本电脑),它通过不同的接口(圆形端口)提供相同的功能。

这就是适配器模式派上用场的地方。我们创建一个称为适配器的类,它将

  • 遵循客户端期望的相同接口(此处为方形 USB 端口)

  • 将客户端的请求转换为适配者所期望的形式。基本上,在我们的示例中,充当一个接受方形 USB 端口并将其插入 Windows 笔记本电脑的圆形端口的适配器。

何时使用

  • 当对象实现与客户端要求不同的接口时,请使用此设计模式。

UML 图

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

下面是与上述示例对应的映射 UML 图

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

映射

下面的表格表示 UML 图中的参与者与代码中实际实现的参与者之间的映射。

目标 computer.go
具体原型 1 mac.go
具体原型 2 (适配器) windowsAdapter.go
适配者 windows.go
客户端 client.go

示例

computer.go

package main

type computer interface {
    insertInSquarePort()
}

mac.go

package main

import "fmt"

type mac struct {
}

func (m *mac) insertInSquarePort() {
    fmt.Println("Insert square port into mac machine")
}

windowsAdapter.go

package main

type windowsAdapter struct {
	windowMachine *windows
}

func (w *windowsAdapter) insertInSquarePort() {
	w.windowMachine.insertInCirclePort()
} 

windows.go

package main

import "fmt"

type windows struct{}

func (w *windows) insertInCirclePort() {
    fmt.Println("Insert circle port into windows machine")
}

client.go

package main

type client struct {
}

func (c *client) insertSquareUsbInComputer(com computer) {
    com.insertInSquarePort()
}

main.go

package main

func main() {
    client := &client{}
    mac := &mac{}
    client.insertSquareUsbInComputer(mac)
    windowsMachine := &windows{}
    windowsMachineAdapter := &windowsAdapter{
        windowMachine: windowsMachine,
    }
    client.insertSquareUsbInComputer(windowsMachineAdapter)
}

输出:

Insert square port into mac machine
Insert circle port into windows machine

在 Go(Golang)中向项目或模块添加依赖。

来源:golangbyexample.com/add-dependency-module-golang/

目录。

** 概述

  • 直接添加到 go.mod 文件

  • 执行 go get

  • 将依赖添加到源代码并执行 go mod tidy

概述

模块是 Go 支持的依赖管理。模块的定义是一个相关包的集合,根目录下有 go.mod 文件。go.mod 文件定义了

  • 模块导入路径。

  • 模块的依赖要求以成功构建。它定义了模块的依赖需求并将其锁定到正确的版本。

模块的依赖可以分为两种。

  • 直接依赖(Direct -A direct dependency)是模块直接导入的依赖。

  • 间接依赖(Indirect)是由模块的直接依赖导入的依赖。此外,go.mod 文件中提到但在模块的任何源文件中未导入的依赖也视为间接依赖。

让我们探索几种将依赖项添加到项目的方法。

  • 直接添加到 go.mod 文件

  • 执行 go get

  • 将依赖添加到源代码并执行 go mod tidy

在查看每种方式之前,我们先创建一个模块。

go mod init learn

此命令将在同一目录下创建 go.mod 文件。由于这是一个空模块,因此尚未指定任何直接依赖。现在让我们探索不同的添加依赖方式。

直接添加到 go.mod 文件

我们也可以直接将依赖添加到 go.mod 文件中。让我们来做这个。在 go.mod 文件中添加以下依赖。

require github.com/pborman/uuid v1.2.1

有了这个依赖项,go.mod 文件看起来如下。

module learn

go 1.14

require github.com/pborman/uuid v1.2.1

现在我们需要下载新添加的依赖项。我们可以使用以下命令。

go mod download

此命令将下载 github.com/pborman/uuid 模块及其所有依赖。此外,它还会更新 go.sum 文件,包含所有直接和间接依赖的校验和和版本。go build 和 go install 也会下载依赖并构建二进制文件。go run 也会下载并运行二进制文件。go mod download 命令用于在不构建或运行的情况下预下载依赖。

执行 go get

只需执行 go get 也会将依赖添加到 go.mod 文件中。将我们之前添加的 uuid 依赖从 go.mod 文件中删除,并清理 go.sum 文件。现在运行以下命令。

export GO111MODULE=on
go get github.com/pborman/uuid

现在检查 go.mod 文件的内容。执行 cat go.mod

module learn

go 1.14

require github.com/pborman/uuid v1.2.1 //indirect

该依赖项将被标记为//indirect,因为它没有在任何源文件中使用。使用这个依赖项后,如果你执行go build//indirect将会被 Go 自动移除。同时,它会更新go.sum文件,包含所有直接和间接依赖项的校验和与版本。

将依赖项添加到你的源代码中并执行go mod tidy

我们在上面的例子中已经看到过这个方法。基本上,go mod tidy命令确保你的 go.mod 文件反映出你在项目中实际使用的依赖项。当我们运行go mod tidy命令时,它会做两件事情。

  • 添加任何在源文件中导入的依赖项。

  • 移除任何在go.mod文件中提到但没有在任何源文件中导入的依赖项。

让我们看一个例子。移除我们之前在 go.mod 文件中添加的依赖项。你的 go.mod 文件应该如下所示。

module learn

go 1.14

另外,让我们在同一目录下创建一个名为uuid.go的文件,内容如下。

uuid.go

package main

import (
	"fmt"
	"strings"

	"github.com/pborman/uuid"
)

func main() {
	uuidWithHyphen := uuid.NewRandom()
	uuid := strings.Replace(uuidWithHyphen.String(), "-", "", -1)
	fmt.Println(uuid)
}

请注意,我们在 uuid.go 中也导入了该依赖项。

"github.com/pborman/uuid"

让我们运行下面的命令。

go mod tidy

该命令将下载在你的源文件中所需的所有依赖项,并用该依赖项更新go.mod文件。运行此命令后,让我们再次检查go.mod文件的内容。

执行cat go.mod

module learn

go 1.14

require github.com/pborman/uuid v1.2.1

它列出了在 uuid 文件中指定的直接依赖项,以及该依赖项的确切版本。现在让我们检查一下go.sum文件。执行cat go.sum

github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=

现在让我们看一个例子,如果依赖项在源文件中不需要,go mod tidy将从go.mod文件中移除该依赖项。为了说明这一点,让我们移除之前创建的uuid.go文件。现在运行该命令。

go mod tidy -v

它将输出如下。

unused github.com/pborman/uuid

现在检查一下go.mod文件的内容。它将如下所示。

module learn

go 1.14

require github.com/pborman/uuid v1.2.1

行将被移除,因为在任何源文件中都不需要它。此外,所有github.com/pborman/uuid及其依赖项的条目也将从go.sum文件中删除。

在 Go(Golang)中加或减时间

来源:golangbyexample.com/add-or-subtract-to-time-go/

目录

**概览

  • Add to time

  • Subtract to time

概览

golang 中的 time 包定义了两种添加或减去时间的方法。

  • Add 函数 – 用于 添加/减去 一个 持续时间时间 t。由于 持续时间 可以用小时、分钟、秒、毫秒、微秒和纳秒表示,因此 Add 函数可以用于从时间中添加/减去这些单位。它的签名是
func (t Time) Add(d Duration) Time
  • AddDate 函数 – 用于给时间 t 添加/减去年份、月份和天数。它的签名是
func (t Time) AddDate(years int, months int, days int) Time

注意:正值用于加时间,负值用于减时间。让我们看一个关于加减时间的工作示例。

加时间

以下代码可以用于加时间

package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.Now()

    //Add 1 hours
    newT := t.Add(time.Hour * 1)
    fmt.Printf("Adding 1 hour\n: %s\n", newT)

    //Add 15 min
    newT = t.Add(time.Minute * 15)
    fmt.Printf("Adding 15 minute\n: %s\n", newT)

    //Add 10 sec
    newT = t.Add(time.Second * 10)
    fmt.Printf("Adding 10 sec\n: %s\n", newT)

    //Add 100 millisecond
    newT = t.Add(time.Millisecond * 10)
    fmt.Printf("Adding 100 millisecond\n: %s\n", newT)

    //Add 1000 microsecond
    newT = t.Add(time.Millisecond * 10)
    fmt.Printf("Adding 1000 microsecond\n: %s\n", newT)

    //Add 10000 nanosecond
    newT = t.Add(time.Nanosecond * 10000)
    fmt.Printf("Adding 1000 nanosecond\n: %s\n", newT)

    //Add 1 year 2 month 4 day
    newT = t.AddDate(1, 2, 4)
    fmt.Printf("Adding 1 year 2 month 4 day\n: %s\n", newT)
}

输出

Adding 1 hour:
 2020-02-01 02:16:35.893847 +0530 IST m=+3600.000239893

Adding 15 minute:
 2020-02-01 01:31:35.893847 +0530 IST m=+900.000239893

Adding 10 sec:
 2020-02-01 01:16:45.893847 +0530 IST m=+10.000239893

Adding 100 millisecond:
 2020-02-01 01:16:35.903847 +0530 IST m=+0.010239893

Adding 1000 microsecond:
 2020-02-01 01:16:35.903847 +0530 IST m=+0.010239893

Adding 1000 nanosecond:
 2020-02-01 01:16:35.893857 +0530 IST m=+0.000249893

Adding 1 year 2 month 4 day:
 2021-04-05 01:16:35.893847 +0530 IST

减去时间

以下代码可以用于减去时间

package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.Now()

    //Subtract 1 hours
    newT := t.Add(-time.Hour * 1)
    fmt.Printf("Subtracting 1 hour:\n %s\n", newT)

    //Subtract 15 min
    newT = t.Add(-time.Minute * 15)
    fmt.Printf("Subtracting 15 minute:\n %s\n", newT)

    //Subtract 10 sec
    newT = t.Add(-time.Second * 10)
    fmt.Printf("Subtracting 10 sec:\n %s\n", newT)

    //Subtract 100 millisecond
    newT = t.Add(-time.Millisecond * 10)
    fmt.Printf("Subtracting 100 millisecond:\n %s\n", newT)

    //Subtract 1000 microsecond
    newT = t.Add(-time.Millisecond * 10)
    fmt.Printf("Subtracting 1000 microsecond:\n %s\n", newT)

    //Subtract 10000 nanosecond
    newT = t.Add(-time.Nanosecond * 10000)
    fmt.Printf("Subtracting 1000 nanosecond:\n %s\n", newT)

    //Subtract 1 year 2 month 4 day
    newT = t.AddDate(-1, -2, -4)
    fmt.Printf("Subtracting 1 year 2 month 4 day:\n %s\n", newT)
}

输出:

Subtracting 1 hour:
 2020-02-01 00:18:29.772673 +0530 IST m=-3599.999784391

Subtracting 15 minute:
 2020-02-01 01:03:29.772673 +0530 IST m=-899.999784391

Subtracting 10 sec:
 2020-02-01 01:18:19.772673 +0530 IST m=-9.999784391

Subtracting 100 millisecond:
 2020-02-01 01:18:29.762673 +0530 IST m=-0.009784391

Subtracting 1000 microsecond:
 2020-02-01 01:18:29.762673 +0530 IST m=-0.009784391

Subtracting 1000 nanosecond:
 2020-02-01 01:18:29.772663 +0530 IST m=+0.000205609

Subtracting 1 year 2 month 4 day:
 2018-11-27 01:18:29.772673 +0530 IST
```*


<!--yml

category: 未分类

date: 2024-10-13 06:45:36

-->

# 在 Go (Golang) 中添加两个二进制数的程序

> 来源:[`golangbyexample.com/add-two-binary-numbers-golang/`](https://golangbyexample.com/add-two-binary-numbers-golang/)

目录

+   概述

+   程序

## **概述**

目标是将两个给定的二进制数相加。二进制数仅由数字 0 和 1 组成。下面是单个数字的二进制加法逻辑

+   0+0 = 和是 0,进位是 0

+   0+1 = 和是 1,进位是 0

+   1+0 = 和是 0,进位是 0

+   1+1 = 和是 0,进位是 1

+   1+1+1 = 和是 1,进位是 1

示例

```go
Input: "101" + "11"
Output: "1000"

Input: "111" + "101"
Output: "1100"

程序

以下是相同程序

package main

import (
	"fmt"
	"strconv"
)

func addBinary(a string, b string) string {
	lenA := len(a)
	lenB := len(b)

	i := lenA - 1
	j := lenB - 1

	var output string
	var sum int
	carry := 0
	for i >= 0 && j >= 0 {
		first := int(a[i] - '0')
		second := int(b[j] - '0')

		sum, carry = binarySum(first, second, carry)

		output = strconv.Itoa(sum) + output
		i = i - 1
		j = j - 1
	}

	for i >= 0 {
		first := int(a[i] - '0')

		sum, carry = binarySum(first, 0, carry)

		output = strconv.Itoa(sum) + output
		i = i - 1

	}

	for j >= 0 {
		second := int(b[j] - '0')

		sum, carry = binarySum(0, second, carry)

		output = strconv.Itoa(sum) + output
		j = j - 1
	}

	if carry > 0 {
		output = strconv.Itoa(1) + output
	}

	return output
}

func binarySum(a, b, carry int) (int, int) {
	output := a + b + carry

	if output == 0 {
		return 0, 0
	}

	if output == 1 {
		return 1, 0
	}

	if output == 2 {
		return 0, 1
	}

	if output == 3 {
		return 1, 1
	}

	return 0, 0
}

func main() {
	output := addBinary("101", "11")
	fmt.Println(output)

	output = addBinary("111", "101")
	fmt.Println(output)
}

输出

1000
1100

注意: 查看我们的 Golang 高级教程。本系列教程内容详尽,我们尝试涵盖所有概念及示例。本教程适合那些希望获得 Golang 专业知识和扎实理解的人 – Golang 高级教程

如果你对如何在 Golang 中实现所有设计模式感兴趣,那么这篇文章就是为你准备的 – 所有设计模式 Golang

用 Go(Golang)实现两个由链表表示的数字相加

来源:golangbyexample.com/add-numbers-linked-list-golang/

目录

  • 概述

概述

假设链表已经被反转。也就是说,假设这两个数字是

  • 478

  • 339

输出应该是 817

假设这两个数字以反向方式表示为链表

8->7->4
9->3->3

期望的输出链表也是反向的

7->1->8

这是相应的程序

package main

import "fmt"

func main() {
	first := initList()	
        first.AddFront(4)
	first.AddFront(7)
	first.AddFront(8)

	second := initList()
	second.AddFront(3)
	second.AddFront(3)
	second.AddFront(9)

	result := addTwoNumbers(first.Head, second.Head)
	result.Traverse()
}

func initList() *SingleList {
	return &SingleList{}
}

type ListNode struct {
	Val  int
	Next *ListNode
}

func (l *ListNode) Traverse() {
	for l != nil {
		fmt.Println(l.Val)
		l = l.Next
	}
}

type SingleList struct {
	Len  int
	Head *ListNode
}

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

func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
	var prev *ListNode
	var result *ListNode
	carry := 0

	for l1 != nil && l2 != nil {

		sum := l1.Val + l2.Val + carry

		carry = sum / 10
		if sum >= 10 {
			sum = sum % 10
		}

		tempNode := &ListNode{Val: sum}

		if prev == nil {
			result = tempNode
			prev = tempNode
		} else {
			prev.Next = tempNode
			prev = prev.Next
		}

		l1 = l1.Next

		l2 = l2.Next
	}

	for l1 != nil {
		sum := l1.Val + carry
		carry = sum / 10
		if sum >= 10 {
			sum = sum % 10
		}
		tempNode := &ListNode{Val: sum}
		prev.Next = tempNode
		l1 = l1.Next
	}

	for l2 != nil {
		sum := l2.Val + carry
		carry = sum / 10
		if sum >= 10 {
			sum = sum % 10
		}
		tempNode := &ListNode{Val: sum}
		prev.Next = tempNode
		l2 = l2.Next
	}

	if carry > 0 {
		tempNode := &ListNode{Val: carry}
		prev.Next = tempNode
	}
	return result
}

输出

7
1
8

关于 Base64 编码/解码的所有内容 – 完整指南

来源:golangbyexample.com/base64-complete-guide/

目录

  • 概述

  • 为什么需要 Base64 编码

  • Base64 编码/解码如何工作

  • Base64 解码

  • 填充是否必要

  • 大小

  • Base64 替代方案

  • Base64 其他实现

  • Base64 应用

  • 结论

概述

Base64 也被称为 Base64 内容传输编码。Base64 是将二进制数据编码为 ASCII 文本。但它只使用 64 个字符和一个填充字符,这些字符在大多数字符集中都存在。因此,它是一种仅使用可打印字符表示二进制数据的方法。这些可打印字符为

  • 小写字母 a-z

  • 大写字母 A-Z

  • 数字 0-9

  • 字符 +/

  • = 被用作填充字符

Base64 编码用于在不正确处理二进制数据的介质上传输数据。因此,对数据进行 Base64 编码是为了确保数据在通过该媒体时保持完整而不被修改。Base64 编码的样子如下。

OWRjNGJjMDY5MzVmNGViMGExZTdkMzNjOGMwZTI3ZWI==

为什么需要 Base64 编码

让我们谈谈历史,以了解为什么需要 base64。最初,当邮件系统启动时,仅传输文本。后来,电子邮件也开始支持附件,包括视频和音频。视频和音频是二进制数据,当通过互联网传输二进制数据时,有很大可能性会损坏。问题是为什么。一些媒体(如电子邮件)仅支持文本数据。它们仅用于流式文本,因此这些协议可能将二进制数据解释为控制字符,或者二进制数据中的某些特殊字符可能会被不同地解释。二进制数据的问题在于,二进制数据中的某些字符在某些媒体中可能具有特殊含义。换句话说,这些媒体不是 8 位干净,仅用于处理文本数据。

因此,base64 是为不支持二进制数据的媒体设计的,仅处理文本字符。保证数据不会损坏。它主要用于仅支持 ASCII 数据的遗留系统。

但为什么是 64 个字符而不是更多。这 64 个字符在大多数字符集中都有,因此可以合理地相信数据在另一端不会损坏。

注意,这是一种编码,而不是加密。编码和加密之间有什么区别?

编码

编码意味着将数据转换为另一种格式,以便可以被只理解转换格式的系统正确使用。编码只是一种公开可用的算法。没有涉及秘密密钥,并且是可逆的。因此,任何知道算法的人都可以简单地逆转并获取原始数据。现在问题是,为什么需要它。一些传输媒介只理解文本数据或一定数量的字符。您的二进制数据无法通过这样的媒介传输,因为存在数据损坏的可能性。

编码并不提供任何安全性,而只是为了在理解不同格式的不同媒体之间的兼容性。

加密

加密是为了保密。它始终由一个密钥保护,旨在由知道密钥的任何人进行解密。因此,在加密的情况下,数据只能由拥有密钥的人逆转。

例如,在网络上发送密码。在这种情况下,我们对密码进行加密,以便任何无意中的人无法读取它。例如 HTTPS。

因此,编码本质上是为了兼容性,而不是为了加密。

Base64 编码/解码的工作原理

Base64 编码在tools.ietf.org/html/rfc4648中有描述。

Base64 编码将把每 3 个字节的数据转换为 4 个编码字符。它将从左到右开始扫描,然后选择前 3 个字节的数据,表示 3 个字符。这 3 个字节将是 24 位。现在,它将把这 24 位分成四个部分,每部分 6 位。然后每个 6 位组将在下面的表中进行索引,以获取映射字符。

 Value Encoding  Value Encoding  Value Encoding  Value Encoding
         0 A            17 R            34 i            51 z
         1 B            18 S            35 j            52 0
         2 C            19 T            36 k            53 1
         3 D            20 U            37 l            54 2
         4 E            21 V            38 m            55 3
         5 F            22 W            39 n            56 4
         6 G            23 X            40 o            57 5
         7 H            24 Y            41 p            58 6
         8 I            25 Z            42 q            59 7
         9 J            26 a            43 r            60 8
        10 K            27 b            44 s            61 9
        11 L            28 c            45 t            62 +
        12 M            29 d            46 u            63 /
        13 N            30 e            47 v
        14 O            31 f            48 w         (pad) =
        15 P            32 g            49 x
        16 Q            33 h            50 y

让我们用一个例子来理解。假设我们有以下字符串

ab@

上述字符串的位表示为

 a          b        @
01100001  01100010  01000000

这 24 位将被分组为 4 组,每组 6 位。

011000 010110 001001 000000

上述位的数值表示为

 24    22     9      0
011000 010110 001001 000000

使用上述数字在 Base64 表中进行索引。下面是映射。

24 Y
22 W
9 J
0 A

因此,Base64 编码的字符串将是

YWJA

如果输入字符串不是 3 的倍数会怎样?在这种情况下,填充字符=将出现。

假设输入字符串有 4 个字符。

ab@c

上述字符串的位表示为

 a          b        @        c
01100001  01100010  01000000 01100011

前三个字节将被组合在一起。最后的字节将用 4 个额外的零进行填充,以使整体位数可被 6 整除。

011000 010110 001001 000000 011000 110000
  24     22     9      0      24     48

使用上述数字在上表中进行索引。下面是映射。

24 Y
22 W
9 J
0 A
24 Y
48 w

这将变为

YWJAYw==

每两个额外的零由=字符表示。由于我们添加了 4 个额外的零,因此在末尾有两个=。

现在让我们看另一个例子,其中输入字符串有 5 个字符。

ab@cd

上述字符串的位表示为

 a          b        @        c       d
01100001  01100010  01000000 01100011 01100100

前三个字节将组合在一起。最后两个字节将组合在一起,并用 2 个额外的零填充,以使总体位数能被 6 整除。

011000 010110 001001 000000 011000 110110 010000
  24     22     9      0      24     54     16

使用上述数字在上表中索引。以下是映射。

24 Y
22 W
9 J
0 A
24 Y
54 2
16 Q

这将变成

YWJAY2Q=

每两个额外的零由=字符表示。由于我们添加了 2 个额外的零,因此末尾有一个单独的=。另外,请注意,每个带填充的 Base64 编码字符串的长度都是 4 的倍数。

实际字符串 Base64 编码 字符串 长度
ab@ YWJA 4
ab@c YWJAYw== 8
ab@cd YWJAY2Q= 8

对于编码,我们只有上述讨论过的三种情况。

  • 输入字符串的位数能被 6 整除。不添加填充。例如 ab@

  • 输入字符串的位数不能被 6 整除,余数为 4. 将添加双==填充。例如 ab@c

  • 位数不能被 6 整除,余数为 2. 将添加一个单个的=填充。例如 ab@cd

现在脑海中浮现的问题是,填充是否必要。答案是这要看情况。在我们看到解码如何工作后将进行讨论。

Base64 解码

现在让我们将 Base64 编码的字符串解码为原始字符串。解码是编码的反向操作。让我们以这个例子为例。

YWJAY2Q=

按每 4 个字符分组。

YWJA 

Y2Q=

现在从每个组中去掉尾随的=字符。对于剩下的字符串,将其转换为上表中的对应位表示。

 Y      W       J      A     
011000 010110 001001 000000

 Y      2      Q
011000 110110 010000

现在按 8 位分组。保留尾随的零。这是为了考虑添加的尾随=。尾随的零可以是 00 或 0000。

01100001  01100010  01000000 

01100011 01100100

现在对于上面的每个字节,根据 ASCII 表分配字符。

 01100001  01100010  01000000
   a          b        @ 

01100011 01100100
  c       d

因此最终字符串将是

ab@cd

为什么我们在解码时将 Base64 编码的字符串分成 4 个字符一组?原因在于填充,这将在下一节中明确。

是否需要填充

你可能在想,Base64 编码的填充=是否必要,因为我们在解码时简单地丢弃了填充。答案是这要看情况。

  • 当你发送单个字符串时,填充不是必需的。

  • 当你连接多个字符串的 Base64 编码时,填充是重要的。如果未填充的字符串被连接,则将无法得到原始字符串,因为关于添加的字节的信息将丢失。作为说明,请考虑下面的内容。

实际字符串 带填充的 Base64 编码 不带填充的 Base64 编码
a YQ== YQ
bc YmM= YmM
def ZGVm ZGVm

现在我们来考虑两种情况。

当连接的字符串未填充时

在这种情况下,连接的 Base64 字符串将是

YQYmMZGVm

尝试解码,你会得到下面的最终字符串,但这是不正确的。

a&1

当连接的字符串带填充时

在这种情况下,连接的 Base64 字符串将是

YQ==YmM=ZGVm

尝试按 4 个字符分组解码,你将得到最终的字符串如下,这是正确的。

abcdef

现在再次出现的问题是,为什么需要连接多个 Base64 编码的字符串。答案是,在有流数据的情况下,想要在数据到达时发送 Base64 编码的数据总是好的。例如,视频缓冲。

所以,尽管在所有情况下并不是绝对必要,填充还是被鼓励。

大小

基本上,Base64 在填充的情况下将 3 字节编码为 4 个 ASCII 字符。每个四个 ASCII 字符在网络上发送时将各占 1 字节。因此,结果大小总是比原始大小多 33.33%。所以如果字符串的原始大小为 n 字节,则 Base64 编码后的大小为:

n*4/3

Base64 替代方案

还有许多其他编码选项,但其中一些显著更难,而其他一些则占用太多空间。例如,Base 80 占用较少空间,但由于二进制的平方是自然基数,编码相对复杂。另外还有十六进制编码。它简单,但占用更多空间。

  • 十六进制 – 其字符集为 16。

  • Base36 – 不区分大小写的编码

  • Base80

  • Base58

还有其他替代方案。

Base64 其他实现

还有两个其他的 Base64 实现。

  • URL 中的 Base64。在此,‘+’‘\’ 符号分别被替换为 ‘+’‘-‘。这是因为 ‘+’‘\’ 在 URL 编码中进一步被编码为十六进制序列,从而增加了 URL 的长度。例如,‘+’ 会被转换为 ‘%2B’,‘\’ 会在 URL 编码中被编码为 ‘%2F’。

  • 文件名中的 Base64。在此 ‘\’ 被替换为 ‘-‘。这是因为 ‘\’ 在 Unix 和 Windows 的文件路径中被使用。

Base64 应用程序

  • 电子邮件中的二进制数据传输,例如发送视频和音频作为电子邮件附件。

  • 基本认证以 Base64 编码形式发送在 HTTP 协议中。

还有其他 Base64 应用程序。

结论

这都是关于 Base64 编码的内容。希望你喜欢这篇文章。请在评论中分享反馈*

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