Golang中常用的代码优化点

2023-11-14

写代码其实也有很多套路和经验,这篇介绍几个让golang代码更优雅的四个套路。

大家好,我是轩脉刃。

这篇想和大家聊一聊golang的常用代码写法。在golang中,如果大家不断在一线写代码,一定多多少少会有一些些代码的套路和经验。这些经验是代表你对一些问题,或者一类问题的思考和解决。处理一个问题的方法有很多,如果频繁遇到同样的场景和问题,我们会自己思考有没有更好的方式来解决,所以也就有了一些代码套路了。这里,我想和大家分享一下我个人在开发过程中看到和使用到的一些常用的代码写法。

文章中总结了四个golang中常用的写法

  • 使用pkg/error而不是官方error库

  • 在初始化slice的时候尽量补全cap

  • 初始化一个类的时候,如果类的构造参数较多,尽量使用Option写法

  • 巧用大括号控制变量作用域

使用pkg/error而不是官方error库

其实我们可以思考一下,我们在一个项目中使用错误机制,最核心的几个需求是什么?

1 附加信息:我们希望错误出现的时候能附带一些描述性的错误信息,甚至于这些信息是可以嵌套的。

2 附加堆栈:我们希望错误不仅仅打印出错误信息,也能打印出这个错误的堆栈信息,让我们可以知道错误的信息。

在Go的语言演进过程中,error传递的信息太少一直是被诟病的一点。我推荐在应用层使用 github.com/pkg/errors 来替换官方的error库。

假设我们有一个项目叫errdemo,他有sub1,sub2两个子包。sub1和sub2两个包都有Diff和IoDiff两个函数。

9fd0af95b1a0d21ecb03006682781e00.png
image-20211219170503931
// sub2.go
package sub2
import (
    "errors"
    "io/ioutil"
)
func Diff(foo int, bar int) error {
    return errors.New("diff error")
}


// sub1.go
package sub1

import (
    "errdemo/sub1/sub2"
    "fmt"
    "errors"
)
func Diff(foo int, bar int) error {
    if foo < 0 {
        return errors.New("diff error")
    }
    if err := sub2.Diff(foo, bar); err != nil {
        return err
    }
    return nil
}

// main.go
package main

import (
    "errdemo/sub1"
    "fmt"
)
func main() {
    err := sub1.Diff(1, 2)
    fmt.Println(err)
}

在上述三段代码中,我们很不幸地将sub1.go中的Diff返回的error和sub2.go中Diff返回的error都定义为同样的字符串“diff error”。这个时候,在main.go中,我们返回的error,是无论如何也判断不出这个error是从sub1 还是 sub2 中抛出的。调试的时候会带来很大的困扰。

a9f84550619533071eb274ed48392709.png
image-20211219171226288

而使用 github.com/pkg/errors ,我们所有的代码都不需要进行修改,只需要将import地方进行对应的修改即可。

在main.go中使用fmt.Printf("%+v", err) 就能除了打印error的信息,也能将堆栈打印出来了。

// sub2.go
package sub2
import (
    "github.com/pkg/errors"
    "io/ioutil"
)
func Diff(foo int, bar int) error {
    return errors.New("diff error")
}


// sub1.go
package sub1

import (
    "errdemo/sub1/sub2"
    "fmt"
    "github.com/pkg/errors"
)
func Diff(foo int, bar int) error {
    if foo < 0 {
        return errors.New("diff error")
    }
    if err := sub2.Diff(foo, bar); err != nil {
        return err
    }
    return nil
}

// main.go
package main

import (
    "errdemo/sub1"
    "fmt"
)
func main() {
    err := sub1.Diff(1, 2)
    fmt.Printf("%+v", err)
}
51cb188e174af118d0df877aed2ee667.png
image-20211219171614767

看到,除了"diff error" 的错误信息之外,还将堆栈大衣拿出来了,我们能明确看到是sub2.go中第7行抛出的错误。

其实 github.com/pkg/errors 的原理也是非常简单,它利用了fmt包的一个特性:

其中在打印error之前会判断当前打印的对象是否实现了Formatter接口,这个formatter接口只有一个format方法

d5e130dd58c04fa2a7ed66ab43c65acd.png
image-20211219171930031

所以在 github.com/pkg/errors 中提供的各种初始化error方法(包括errors.New)就是封装了一个fundamental 结构,这个结构中带着error的信息和堆栈信息

dfb1e16754f9151fd7eb5dd9da68b5df.png
image-20211219172218939

它实现了Format方法。

460ed9c9606656a98aaf57d0f5bc71c8.png
image-20211219172234195

在初始化slice的时候尽量补全cap

当我们要创建一个slice结构,并且往slice中append元素的时候,我们可能有两种写法来初始化这个slice。

方法1:

package main

import "fmt"

func main() {
 arr := []int{}
 arr = append(arr, 1,2,3,4, 5)
 fmt.Println(arr)
}

方法2:

package main

import "fmt"

func main() {
   arr := make([]int, 0, 5)
   arr = append(arr, 1,2,3,4, 5)
   fmt.Println(arr)
}

方法2相较于方法1,就只有一个区别:在初始化[]int slice的时候在make中设置了cap的长度,就是slice的大小。

这两种方法对应的功能和输出结果是没有任何差别的,但是实际运行的时候,方法2会比少运行了一个growslice的命令。

这个我们可以通过打印汇编码进行查看:

方法1:

d35224864597acd992b47cf79973e5f0.png
image-20211219173237557

方法2:

a4040373a811e755c5afdf8a4709dc22.png
image-20211219174112164

我们看到方法1中使用了growsslice方法,而方法2中是没有调用这个方法的。

这个growslice的作用就是扩充slice的容量大小。就好比是原先我们没有定制容量,系统给了我们一个能装两个鞋子的盒子,但是当我们装到第三个鞋子的时候,这个盒子就不够了,我们就要换一个盒子,而换这个盒子,我们势必还需要将原先的盒子里面的鞋子也拿出来放到新的盒子里面。所以这个growsslice的操作是一个比较复杂的操作,它的表现和复杂度会高于最基本的初始化make方法。对追求性能的程序来说,应该能避免尽量避免。

具体对growsslice函数具体实现同学有兴趣的可以参考源码src的 runtime/slice.go 。

当然,我们并不是每次都能在slice初始化的时候就能准确预估到最终的使用容量的。所以这里使用了一个“尽量”。明白是否设置slice容量的区别,我们在能预估容量的时候,请尽量使用方法2那种预估容量后的slice初始化方式。

初始化一个类的时候,如果类的构造参数较多,尽量使用Option写法

我们一定遇到需要初始化一个类的时候,大部分的时候,初始化一个类我们会使用类似下列的New方法。

package newdemo

type Foo struct {
   name string
   id int
   age int

   db interface{}
}

func NewFoo(name string, id int, age int, db interface{}) *Foo {
   return &Foo{
      name: name,
      id:   id,
      age:  age,
      db:   db,
   }
}

我们定义一个NewFoo方法,其中存放初始化Foo结构所需要的各种字段属性。

这个写法乍看之下是没啥问题的,但是一旦Foo结构内部的字段进行了变化,增加或者减少了,那么这个初始化函数NewFoo就怎么看怎么别扭了。参数继续增加?那么所有调用方的地方也都需要进行修改了,且按照代码整洁的逻辑,参数多于5个,这个函数就很难使用了。而且,如果这5个参数都是可有可无的参数,就是有的参数可以允许不填写,有默认值,比如age这个字段,如果不填写,在后续的业务逻辑中可能没有很多影响,那么我在实际调用NewFoo的时候,age这个字段还需要传递0值。

foo := NewFoo("jianfengye", 1, 0, nil)

这种语意逻辑就不对了。

这里其实有一种更好的写法:使用Option写法来进行改造。Option写法顾命思议,将所有可选的参数作为一个可选方式,一般我们会一定一个“函数类型”来代表这个Option,然后配套将所有可选字段设计一个这个函数类型的具体实现。而在具体的使用的时候,使用可变字段的方式来控制有多少个函数类型会被执行。比如上述的代码,我们会改造为:

type Foo struct {
 name string
 id int
 age int

 db interface{}
}

// FooOption 代表可选参数
type FooOption func(foo *Foo)

// WithName 代表Name为可选参数
func WithName(name string) FooOption {
   return func(foo *Foo) {
      foo.name = name
   }
}

// WithAge 代表age为可选参数
func WithAge(age int) FooOption {
   return func(foo *Foo) {
      foo.age = age
   }
}

// WithDB 代表db为可选参数
func WithDB(db interface{}) FooOption {
   return func(foo *Foo) {
      foo.db = db
   }
}

// NewFoo 代表初始化
func NewFoo(id int, options ...FooOption) *Foo {
   foo := &Foo{
      name: "default",
      id:   id,
      age:  10,
      db:   nil,
   }
   for _, option := range options {
      option(foo)
   }
   return foo
}

解释下上面的这段代码,我们创建了一个FooOption的函数类型,这个函数类型代表的函数结构是 func(foo *Foo) ,很简单,将foo指针传递进去,能让内部函数进行修改。

然后我们定义了三个返回了FooOption的函数:

  • WithName

  • WithAge

  • WithDB

以WithName为例,这个函数参数为string,返回值为FooOption。在返回值的FooOption中,根据参数修改了Foo指针。

// WithName 代表Name为可选参数
func WithName(name string) FooOption {
   return func(foo *Foo) {
      foo.name = name
   }
}

顺便说一下,这种函数我们一般都以With开头,表示我这次初始化“带着”这个字段。

而最后NewFoo函数,参数我们就改造为两个部分,一个部分是“非Option”字段,就是必填字段,假设我们的Foo结构实际上只有一个必填字段id,而其他字段皆是选填的。而其他所有选填字段,我们使用一个可变参数 options 替换。

NewFoo(id int, options ...FooOption)

在具体的实现中,也变化成2个步骤:

  • 按照默认值初始化一个foo对象

  • 遍历options改造这个foo对象

按照这样改造之后,我们具体使用Foo结构的函数就变为如下样子:

// 具体使用NewFoo的函数
func Bar() {
   foo := NewFoo(1, WithAge(15), WithName("foo"))
   fmt.Println(foo)
}

可读性是否高了很多?New一个Foo结构,id为1,并且带着指定age为15,指定name为“foo”。

后续如果Foo多了一个可变属性,那么只需要多一个WithXXX的方法,而NewFoo函数不需要任何变化,调用方只有需要指定这个可变属性的地方增加WithXXX即可。扩展性非常好。

这种Option的写法在很多著名的库中都有使用到,gorm, go-redis等。所以我们要把这种方式熟悉起来,一旦我们在需要对一个比较复杂的类进行初始化的时候,这种方法应该是最优的方式了。

巧用大括号控制变量作用域

在golang写的过程中,你一定有过为 := 和 = 烦恼的时刻。一个变量,到写的时候,我还要记得前面是否已经定义过了,如果没有定义过,使用 := ,如果已经定义过,使用 =。

当然很多时候可能你不会犯这种错误,变量命名的比较好的话,我们是很容易记得是否前面有定义过的。但是更多时候,对于err这种通用的变量名字,你可能就不一定记得了。

这个时候,巧妙使用大括号,就能很好避免这个问题。

我举一个我之前写一个命令行工具的例子,大家知道写命令行工具,对传递的参数的解析是需要有一些逻辑的,“如果参数中有某个字段,那么解析并存储到变量中,如果没有,记录error”,这里我就使用了大括号,将每个参数的解析和处理错误的逻辑都封装起来。

代码大致如下:

var name string
var folder string
var mod string
...
{
   prompt := &survey.Input{
      Message: "请输入目录名称:",
   }
   err := survey.AskOne(prompt, &name)
   if err != nil {
      return err
   }

   ...
}
{
   prompt := &survey.Input{
      Message: "请输入模块名称(go.mod中的module, 默认为文件夹名称):",
   }
   err := survey.AskOne(prompt, &mod)
   if err != nil {
      return err
   }
   ...
}
{
   // 获取hade的版本
   client := github.NewClient(nil)
   prompt := &survey.Input{
      Message: "请输入版本名称(参考 https://github.com/gohade/hade/releases,默认为最新版本):",
   }
   err := survey.AskOne(prompt, &version)
   if err != nil {
      return err
   }
   ...
}

首先我将最终解析出来的最终变量在最开始做定义,然后使用三个大括号,分别将 name, mod, version 三个变量的解析逻辑封装在里面。而在每个大括号里面,err变量的作用域就完全局限在括号中了,每次都可以直接使用 := 来创建一个新的 err并处理它,不需要额外思考这个err 变量是否前面已经创建过了。

如果你自己观察,大括号在代码语义上还有一个好处,就是归类和展示。归类的意思是,这个大括号里面的变量和逻辑是一个完整的部分,他们内部创建的变量不会泄漏到外部。这个等于等于告诉后续的阅读者,你在阅读的时候,如果对这个逻辑不感兴趣,不阅读里面的内容,而如果你感兴趣的话,可以进入里面进行阅读。基本上所有IDE都支持对大括号封装的内容进行压缩,我使用Goland,压缩后,我的命令行的主体逻辑就更清晰了。

4aef126e4c3ab858fc9caf1ff587d66d.png
image-20211220095540148

所以使用大括号,结合IDE,你的代码的可读性能得到很大的提升。

总结

文章中总结了四个golang中常用的写法

  • 使用pkg/error而不是官方error库

  • 在初始化slice的时候尽量补全cap

  • 初始化一个类的时候,如果类的构造参数较多,尽量使用Option写法

  • 巧用大括号控制变量作用域

这几种写法和注意事项是在工作过程和阅读开源项目中的一些总结和经验,每个经验都是对应为了解决不同的问题。

虽然说golang已经对代码做了不少的规范和优化,但是好的代码和不那么好的代码是有一些差距的,这些写法优化点就是其中一部分。本文列出的只是四个点,当然还有很多类似的golang写法优化点,相信大家在工作生活中也能遇到不少,只要大家平时能多思考多总结多动手,也能积攒出属于自己的一本小小的优化手册的。

8066e90e19d9ecbe57765603b78cdee3.png

9313847617d9ec87c0133e08d7cf6a2e.png

Hi,我是轩脉刃,一个名不见经传码农,体制内的小愤青,躁动的骚年,2022年想坚持写一些学习/工作/思考笔记,谓之倒逼学习。欢迎关注个人公众号:轩脉刃的刀光剑影。

MORE | 更多原创文章

一种优雅的Golang的库插件注册加载机制

Redis的事件处理机制                

你最好的一条职业建议是什么

如何封装安全的go

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Golang中常用的代码优化点 的相关文章

  • 添加不同形状的 numpy 数组

    我想添加两个不同形状的 numpy 数组 但不进行广播 而是将 缺失 值视为零 可能最简单的例子是 1 2 3 2 gt 3 2 3 or 1 2 3 2 1 gt 3 2 3 1 0 0 我事先不知道形状 我正在弄乱每个 np shape
  • 从 127.0.0.1 到 2130706433,然后再返回

    使用标准 Java 库 从 IPV4 地址的点分字符串表示形式获取的最快方法是什么 127 0 0 1 到等效的整数表示 2130706433 相应地 反转所述操作的最快方法是什么 从整数开始2130706433到字符串表示形式 127 0
  • Java TestNG 与跨多个测试的数据驱动测试

    我正在电子商务平台中测试一系列商店 每个商店都有一系列属性 我正在考虑对其进行自动化测试 是否有可能有一个数据提供者在整个测试套件中提供数据 而不仅仅是 TestNG 中的测试 我尝试不使用 testNG xml 文件作为机制 因为这些属性
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • Pygame:有没有简单的方法可以找到按下的任何字母数字的字母/数字?

    我目前正在开发的游戏需要让人们以自己的名义在高分板上计时 我对如何处理按键有点熟悉 但我只处理过寻找特定的按键 有没有一种简单的方法可以按下任意键的字母 而不必执行以下操作 for event in pygame event get if
  • JRE 系统库 [WebSphere v6.1 JRE](未绑定)

    将项目导入 Eclipse 后 我的构建路径中出现以下错误 JRE System Library WebSphere v6 1 JRE unbound 谁知道怎么修它 右键单击项目 特性 gt Java 构建路径 gt 图书馆 gt JRE
  • getResourceAsStream() 可以找到 jar 文件之外的文件吗?

    我正在开发一个应用程序 该应用程序使用一个加载配置文件的库 InputStream in getClass getResourceAsStream resource 然后我的应用程序打包在一个 jar文件 如果resource是在里面 ja
  • python获取上传/下载速度

    我想在我的计算机上监控上传和下载速度 一个名为 conky 的程序已经在 conky conf 中执行了以下操作 Connection quality alignr wireless link qual perc wlan0 downspe
  • 使用 \r 并打印一些文本后如何清除控制台中的一行?

    对于我当前的项目 有一些代码很慢并且我无法使其更快 为了获得一些关于已完成 必须完成多少的反馈 我创建了一个进度片段 您可以在下面看到 当你看到最后一行时 sys stdout write r100 80 n I use 80覆盖最终剩余的
  • 将图像分割成多个网格

    我使用下面的代码将图像分割成网格的 20 个相等的部分 import cv2 im cv2 imread apple jpg im cv2 resize im 1000 500 imgwidth im shape 0 imgheight i
  • AWS 无法从 START_OBJECT 中反序列化 java.lang.String 实例

    我创建了一个 Lambda 函数 我想在 API 网关的帮助下通过 URL 访问它 我已经把一切都设置好了 我还创建了一个application jsonAPI Gateway 中的正文映射模板如下所示 input input params
  • 如何在seaborn displot中使用hist_kws

    我想在同一图中用不同的颜色绘制直方图和 kde 线 我想为直方图设置绿色 为 kde 线设置蓝色 我设法弄清楚使用 line kws 来更改 kde 线条颜色 但 hist kws 不适用于显示 我尝试过使用 histplot 但我无法为
  • 对年龄列进行分组/分类

    我有一个数据框说df有一个柱子 Ages gt gt gt df Age 0 22 1 38 2 26 3 35 4 35 5 1 6 54 我想对这个年龄段进行分组并创建一个像这样的新专栏 If age gt 0 age lt 2 the
  • 如何在 Ubuntu 中将 Go 程序作为守护进程启动?

    在 Ubuntu 中将 Go 程序作为守护进程启动的正确方法是什么 然后我将使用 Monit 对其进行监控 我应该做这样的事情 go run myapp go 我应该考虑 Go 特有的事情吗 您应该为您的程序构建一个可执行文件 go bui
  • 为字典中的一个键附加多个值[重复]

    这个问题在这里已经有答案了 我是 python 新手 我有每年的年份和值列表 我想要做的是检查字典中是否已存在该年份 如果存在 则将该值附加到特定键的值列表中 例如 我有一个年份列表 并且每年都有一个值 2010 2 2009 4 1989
  • 如何从指定日期获取上周五的日期? [复制]

    这个问题在这里已经有答案了 如何找出上一个 上一个 星期五 或指定日期的任何其他日期的日期 public getDateOnDay Date date String dayName 我不会给出答案 先自己尝试一下 但是 也许这些提示可以帮助
  • 如何从泛型类调用静态方法?

    我有一个包含静态创建方法的类 public class TestClass public static
  • 有没有办法为Java的字符集名称添加别名

    我收到一个异常 埋藏在第 3 方库中 消息如下 java io UnsupportedEncodingException BIG 5 我认为发生这种情况是因为 Java 没有定义这个名称java nio charset Charset Ch
  • 使用 Python 的 matplotlib 选择在屏幕上显示哪些图形以及将哪些图形保存到文件中

    我想用Python创建不同的图形matplotlib pyplot 然后 我想将其中一些保存到文件中 而另一些则应使用show 命令 然而 show 显示all创建的数字 我可以通过调用来避免这种情况close 创建我不想在屏幕上显示的绘图
  • 按日期对 RecyclerView 进行排序

    我正在尝试按日期对 RecyclerView 进行排序 但我尝试了太多的事情 我不知道现在该尝试什么 问题就出在这条线上适配器 notifyDataSetChanged 因为如果我不放 不会显示错误 但也不会更新 recyclerview

随机推荐

  • APK的安装流程

    文章目录 我们来思考一下Android系统是如何安装一个APK文件的 从直观的流程上 当我们点击一个APK文件或者从应用商店下载一个APK文件 会弹起一个安装对话框 点击安装就可以安装应用 那么这里面的流程是什么样的呢 首先很容易想到的是
  • QT增加链接库和头文件搜索目录(相对目录)

    QT开发的时候 需要增加链接的动态库或者静态库 或者搜索的头文件 正常情况下 使用相对目录是最好的 下面是常用的方法 1 增加库依赖 如下 OUT PWD表示QT编译后的输出目录 比如Debug或者Release目录 后续发布的时候 把so
  • 【设计模式】Java设计模式——模板方法模式(Template Pattern)

    文章目录 1 介绍 1 1 定义 1 2 作用 2 模式结构 2 1 UML类图 2 2 模式组成 3 代码实例 3 1 背景 3 2 应用 4 优点 5 缺点 6 应用场景 1 介绍 1 1 定义 模板方法模式 Template Patt
  • 上古神器Vim

    玩转Vim 世界上10种人 会用Vim的和不会用Vim的 玩转Vim 从放弃到爱不释手 什么是Vim Linux下两大编辑神器之一Vim Linux Unix下使用最多的编辑器 Vi的改进版 可能是最难上手的编辑器之一 为什么要学习Vim
  • 【经验分享】用PS如何将图片的四角做成圆弧角

    经验分享 用PS如何将图片的四角做成圆弧角 在很多情况下圆角图片看起来更美观整洁 今天分享一下自己经常使用 PS 是如何将图片做出圆弧角 仅供参考 以下面这张图片为例 一 在 PS 中打开素材图片 选择 圆角矩形工具 二 在上方选项卡中选择
  • 基于javaweb的网上电商项目(前后端分离+java+vue+springboot+ssm+mysql+redis)

    基于javaweb的网上电商项目 前后端分离 java vue springboot ssm mysql redis 运行环境 Java 8 MySQL 5 7 Node js 10 开发工具 后端 eclipse idea myeclip
  • chrony服务部署,实现时间同步

    目录 chrony服务部署实验 实验一 第一台机器从阿里云同步时间 第二台机器从第一台机器同步时间 实验二 第一台服务器使用系统时间作为第二台服务器的时钟源 第一台服务器层级设置为6 问题排错 NTP 是网络时间协议 Network Tim
  • 记一次ubuntu16误删libc.so.6操作的恢复过程

    背景 操作系统 ubuntu16 glibc版本 2 23 修改原因 经过一系列报错和手工构建之后 vulkansdk成功安装 起码运行 vulkansdk成功 在进行 vulkaninfo进行验证时 报错 意思是当前glibc版本过低 需
  • 数字化转型面临的五大挑战

    如果将数字化转型分成五个阶段 全球大多数组织正处在第二和第三的僵局阶段 而处于第四和第五阶段的组织 全球不超过25 总体而言 未来数字化转型将面临五大挑战 一是陈旧的考核体系 数字企业需要新的度量标准来了解进度和引导投资 如果考核体系不变
  • 1_01李婉玲_函数_1019

    day04作业练习 作业01 小明和他家人在泰国旅游 到3个不同的饭店吃饭 账单 bill 分别是124元 48元和268元 为了给服务员小费 tip 小明创建了一个简单 的小费计算器函数 tipCalculator a 如果账单小于50元
  • spring jdbctemplate操作数据库

  • 【高数】Abel定理,幂级数的和收敛半径,不同幂级数收敛半径的比较,缺项幂级数的解法

    目录 一 收敛区间及收敛点 二 收敛半径的变化 三 借助正项级数敛散性求幂级数收敛区间 四 缺项幂级数的解法 五 小结 一 收敛区间及收敛点 现对该形式的幂级数进行如下讨论 1 幂级数的收敛区间 对称中心是x0 收敛半径是R 2 经有限次的
  • 最美丽的理论:爱因斯坦引力场方程的推导

    全文共2948字 预计学习时长9分钟 图源 superprof 伟大的前苏联物理学家列夫 朗道和叶夫根尼 利夫希茨在他们的著作 经典场论 中写道 建立在相对论基础上的引力场理论被称为广义相对论 它是由爱因斯坦建立的 并且可能是现存的物理理论
  • 局域网速度测试,三款软件轻松搞定(转)

    局域网络可谓随处可见 我们也十分关注其实际运行速度如何 比如两台计算机间的文件传输 访问对方计算机的快慢等 而决定局域网络速度的因素很多 又不可能通过简单的操作检测出速度的大小 同时也希望能有一些软件能帮助我们管理局域网 以方便故障的排查
  • LeetCode 1967. 作为子字符串出现在单词中的字符串数目(BM、KMP)

    给你一个字符串数组 patterns 和一个字符串 word 统计 patterns 中有多少个字符串是 word 的子字符串 返回字符串数目 子字符串 是字符串中的一个连续字符序列 示例 1 输入 patterns a abc bc d
  • 【GD32F303开发之开发工具的安装与配置】

    GD32F303开发系列文章目录 第一章 GD32微控制器开发工具的安装与配置 第二章 GD32基准工程实验 第三章 GD32串口通信实验 第四章 GD32EXMC与LCD显示实验 文章目录 GD32F303开发系列文章目录 前言 一 GD
  • 为了使用学校GPU集群而离线安装Python和pytorch

    参考 https blog csdn net zhangdongren article details 82685932 学校GPU集群为无网环境 所以要离线安装 1 下载python 源文件 2 解压到 下的某个目录 make编译源文件
  • 冒泡排序法

    冒泡排序 冒泡排序的排序过程 N个数最外层的for循环次数为 N 1 次 内层for循环为 N 1 i 次 每一轮凑从第1个数开始对比 每一次都是和它下一个元素对比 将最大值元素放到最后 其他元素向前移位 重复以上动作 知道N 1次循环全部
  • 钢琴音阶识别_基础知识:关于大小音阶的简单说明

    大小音阶 Scales 简单说明 音阶是音乐的构造块 building blocks 有上千种不同的音阶 有时称调式 每一种都有其特性与功能 peculiarities and functions 从我们称为 西方 音乐中 从欧洲国家起源的
  • Golang中常用的代码优化点

    写代码其实也有很多套路和经验 这篇介绍几个让golang代码更优雅的四个套路 大家好 我是轩脉刃 这篇想和大家聊一聊golang的常用代码写法 在golang中 如果大家不断在一线写代码 一定多多少少会有一些些代码的套路和经验 这些经验是代