9. Go语言配置管理神器Viper的基本使用

2024-03-07 20:52

本文主要是介绍9. Go语言配置管理神器Viper的基本使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 导言
  • 一、把值存入Viper
    • 1. 建立默认值
    • 2. 读取配置文件
    • 3. 写入配置文件
    • 4. 监控并重新读取配置文件
    • 5. 从io.Reader读取配置
    • 6. 覆盖设置
    • 7. 注册和使用别名
    • 8. 使用环境变量
    • 9. 使用Flags
    • 10. 远程Key/Value存储支持
  • 二、从Viper获取值
    • 1. 访问嵌套的键
    • 2. 提取子树
    • 3. 反序列化
    • 4. 序列化成字符串
    • 5. 使用单个还是多个Viper实例?
  • 三、使用Viper示例

导言

Viper是适用于Go应用程序的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式。

安装

go get github.com/spf13/viper

什么是Viper?

Viper是适用于Go应用程序(包括Twelve-Factor App)的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式。它支持以下特性:

  • 设置默认值
  • JSON、TOML、YAML、HCL、envfileJava properties格式的配置文件读取配置信息
  • 实时监控和重新读取配置文件(可选)
  • 从环境变量中读取
  • 从远程配置系统(etcdConsul)读取并监控配置变化
  • 从命令行参数读取配置
  • buffer读取配置
  • 显式配置值

为什么选择Viper?

在构建现代应用程序时,你无需担心配置文件格式。

Viper能够为你执行下列操作:

  • 查找、加载和反序列化JSON、TOML、YAML、HCL、envfile和Java properties格式的配置文件。
  • 提供一种机制为你的不同配置选项设置默认值。
  • 提供一种机制来通过命令行参数覆盖指定选项的值。
  • 提供别名系统,以便在不破坏现有代码的情况下轻松重命名参数。
  • 当用户提供了与默认值相同的命令行或配置文件时,可以很容易地分辨出它们之间的区别。

Viper会按照下面的优先级。每个项目的优先级都高于它下面的项目:

  • 显式调用Set设置值
  • 命令行参数(flag)
  • 环境变量
  • 配置文件
  • key/value存储
  • 默认值

重要: 目前Viper配置的键(Key)是大小写不敏感的。目前正在讨论是否将这一选项设为可选。

一、把值存入Viper

1. 建立默认值

一个好的配置系统应该支持默认值。键不需要默认值,但如果没有通过配置文件、环境变量、远程配置或命令行标志(flag)设置键,则默认值非常有用。

例如:

viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})

2. 读取配置文件

Viper需要最少知道在哪里查找配置文件的配置。Viper支持JSON、TOML、YAML、HCL、envfile和Java properties格式的配置文件。Viper可以搜索多个路径,但目前单个Viper实例只支持单个配置文件。Viper不默认任何配置搜索路径,将默认决策留给应用程序。

下面是一个如何使用Viper搜索和读取配置文件的示例。不需要任何特定的路径,但是至少应该提供一个配置文件预期出现的路径。

viper.SetConfigName("config") // 配置文件名称(无扩展名)
// viper.SetConfigType("yaml") // 如果配置文件的名称中没有扩展名,则需要配置此项,针对远程配置
viper.AddConfigPath("/etc/appname/")   // 查找配置文件所在的路径
viper.AddConfigPath("$HOME/.appname")  // 多次调用以添加多个搜索路径
viper.AddConfigPath(".")               // 还可以在工作目录中查找配置
err := viper.ReadInConfig() // 查找并读取配置文件
if err != nil { // 处理读取配置文件的错误panic(fmt.Errorf("Fatal error config file: %s \n", err))
}

注意事项:

  1. viper.SetConfigName("config") 会在指定一个或多个路径下寻找config文件,如果找到多个,则报错。注:config.yamlconfig.json名字都是config,所以也会认为是找到了两个文件,viper不知道使用哪一个而报错。
  2. viper.SetConfigType("yaml")是针对远程配置的,远程配置通过字节传输,所以指定一下用何种方式解析配置。

你可以像下面这样处理找不到配置文件的特定情况:

if err := viper.ReadInConfig(); err != nil {if _, ok := err.(viper.ConfigFileNotFoundError); ok {// 配置文件未找到错误;如果需要可以忽略} else {// 配置文件被找到,但产生了另外的错误}
}// 配置文件找到并成功解析

注意[自1.6起]: 你也可以有不带扩展名的文件,并以编程方式指定其格式。对于位于用户$HOME目录中的配置文件没有任何扩展名,如.bashrc。

3. 写入配置文件

从配置文件中读取配置文件是有用的,但是有时你想要存储在运行时所做的所有修改。为此,可以使用下面一组命令,每个命令都有自己的用途:

  • WriteConfig - 将当前的viper配置写入预定义的路径并覆盖(如果存在的话)。如果没有预定义的路径,则报错。
  • SafeWriteConfig - 将当前的viper配置写入预定义的路径。如果没有预定义的路径,则报错。如果存在,将不会覆盖当前的配置文件。
  • WriteConfigAs - 将当前的viper配置写入给定的文件路径。将覆盖给定的文件(如果它存在的话)。
  • SafeWriteConfigAs - 将当前的viper配置写入给定的文件路径。不会覆盖给定的文件(如果它存在的话)。
    根据经验,标记为safe的所有方法都不会覆盖任何文件,而是直接创建(如果不存在),而默认行为是创建或截断。

一个小示例:

viper.WriteConfig() // 将当前配置写入“viper.AddConfigPath()”和“viper.SetConfigName”设置的预定义路径
viper.SafeWriteConfig()
viper.WriteConfigAs("/path/to/my/.config")
viper.SafeWriteConfigAs("/path/to/my/.config") // 因为该配置文件写入过,所以会报错
viper.SafeWriteConfigAs("/path/to/my/.other_config")

4. 监控并重新读取配置文件

Viper支持在运行时实时读取配置文件的功能。

需要重新启动服务器以使配置生效的日子已经一去不复返了,viper驱动的应用程序可以在运行时读取配置文件的更新,而不会错过任何消息。

只需告诉viper实例watchConfig。可选地,你可以为Viper提供一个回调函数,以便在每次发生更改时运行。

确保在调用WatchConfig()之前添加了所有的配置路径。

viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {// 配置文件发生变更之后会调用的回调函数fmt.Println("Config file changed:", e.Name)
})

5. 从io.Reader读取配置

Viper预先定义了许多配置源,如文件、环境变量、标志和远程K/V存储,但你不受其约束。你还可以实现自己所需的配置源并将其提供给viper。

viper.SetConfigType("yaml") // 或者 viper.SetConfigType("YAML")// 任何需要将此配置添加到程序中的方法。
var yamlExample = []byte(`
Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- go
clothing:jacket: leathertrousers: denim
age: 35
eyes : brown
beard: true
`)viper.ReadConfig(bytes.NewBuffer(yamlExample))viper.Get("name") // 这里会得到 "steve"

6. 覆盖设置

这些可能来自命令行标志,也可能来自你自己的应用程序逻辑。

viper.Set("Verbose", true)
viper.Set("LogFile", LogFile)

7. 注册和使用别名

别名允许多个键引用单个值

viper.RegisterAlias("loud", "Verbose")  // 注册别名(此处loud和Verbose建立了别名)viper.Set("verbose", true) // 结果与下一行相同
viper.Set("loud", true)   // 结果与前一行相同viper.GetBool("loud") // true
viper.GetBool("verbose") // true

8. 使用环境变量

Viper完全支持环境变量。这使Twelve-Factor App开箱即用。有五种方法可以帮助与ENV协作:

  • AutomaticEnv()
  • BindEnv(string…) : error
  • SetEnvPrefix(string)
  • SetEnvKeyReplacer(string…) *strings.Replacer
  • AllowEmptyEnv(bool)

使用ENV变量时,务必要意识到Viper将ENV变量视为区分大小写。

Viper提供了一种机制来确保ENV变量是惟一的。通过使用SetEnvPrefix,你可以告诉Viper在读取环境变量时使用前缀。BindEnv和AutomaticEnv都将使用这个前缀。

BindEnv使用一个或两个参数。第一个参数是键名称,第二个是环境变量的名称。环境变量的名称区分大小写。如果没有提供ENV变量名,那么Viper将自动假设ENV变量与以下格式匹配:前缀+ “_” +键名全部大写。当你显式提供ENV变量名(第二个参数)时,它 不会 自动添加前缀。例如,如果第二个参数是“id”,Viper将查找环境变量“ID”。

在使用ENV变量时,需要注意的一件重要事情是,每次访问该值时都将读取它。Viper在调用BindEnv时不固定该值。

AutomaticEnv是一个强大的助手,尤其是与SetEnvPrefix结合使用时。调用时,Viper会在发出viper.Get请求时随时检查环境变量。它将应用以下规则。它将检查环境变量的名称是否与键匹配(如果设置了EnvPrefix)。

SetEnvKeyReplacer允许你使用strings.Replacer对象在一定程度上重写 Env 键。如果你希望在Get()调用中使用-或者其他什么符号,但是环境变量里使用_分隔符,那么这个功能是非常有用的。可以在viper_test.go中找到它的使用示例。

或者,你可以使用带有NewWithOptions工厂函数的EnvKeyReplacer。与SetEnvKeyReplacer不同,它接受StringReplacer接口,允许你编写自定义字符串替换逻辑。

默认情况下,空环境变量被认为是未设置的,并将返回到下一个配置源。若要将空环境变量视为已设置,请使用AllowEmptyEnv方法。

Env 示例:

SetEnvPrefix("spf") // 将自动转为大写
BindEnv("id")os.Setenv("SPF_ID", "13") // 通常是在应用程序之外完成的id := Get("id") // 13

9. 使用Flags

Viper 具有绑定到标志的能力。具体来说,Viper支持Cobra库中使用的Pflag。

与BindEnv类似,该值不是在调用绑定方法时设置的,而是在访问该方法时设置的。这意味着你可以根据需要尽早进行绑定,即使在init()函数中也是如此。

对于单个标志,BindPFlag()方法提供此功能。

例如:

serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))

你还可以绑定一组现有的pflags (pflag.FlagSet):

举个例子:

pflag.Int("flagname", 1234, "help message for flagname")pflag.Parse()
viper.BindPFlags(pflag.CommandLine)i := viper.GetInt("flagname") // 从viper而不是从pflag检索值

在 Viper 中使用 pflag 并不阻碍其他包中使用标准库中的 flag 包。pflag 包可以通过导入这些 flags 来处理flag包定义的flags。这是通过调用pflag包提供的便利函数AddGoFlagSet()来实现的。

例如:

package mainimport ("flag""github.com/spf13/pflag"
)func main() {// 使用标准库 "flag" 包flag.Int("flagname", 1234, "help message for flagname")pflag.CommandLine.AddGoFlagSet(flag.CommandLine)pflag.Parse()viper.BindPFlags(pflag.CommandLine)i := viper.GetInt("flagname") // 从 viper 检索值...
}

flag接口

如果你不使用Pflag,Viper 提供了两个Go接口来绑定其他 flag 系统。

FlagValue表示单个flag。这是一个关于如何实现这个接口的非常简单的例子:

type myFlag struct {}
func (f myFlag) HasChanged() bool { return false }
func (f myFlag) Name() string { return "my-flag-name" }
func (f myFlag) ValueString() string { return "my-flag-value" }
func (f myFlag) ValueType() string { return "string" }

一旦你的 flag 实现了这个接口,你可以很方便地告诉Viper绑定它:

viper.BindFlagValue("my-flag-name", myFlag{})

FlagValueSet代表一组 flags 。这是一个关于如何实现这个接口的非常简单的例子:

type myFlagSet struct {flags []myFlag
}func (f myFlagSet) VisitAll(fn func(FlagValue)) {for _, flag := range flags {fn(flag)}
}

一旦你的flag set实现了这个接口,你就可以很方便地告诉Viper绑定它:

fSet := myFlagSet{flags: []myFlag{myFlag{}, myFlag{}},
}
viper.BindFlagValues("my-flags", fSet)

10. 远程Key/Value存储支持

在Viper中启用远程支持,需要在代码中匿名导入viper/remote这个包。

import _ "github.com/spf13/viper/remote"

Viper将读取从Key/Value存储(例如etcd或Consul)中的路径检索到的配置字符串(如JSON、TOML、YAML、HCL、envfile和Java properties格式)。这些值的优先级高于默认值,但是会被从磁盘、flag或环境变量检索到的配置值覆盖。(译注:也就是说Viper加载配置值的优先级为:磁盘上的配置文件>命令行标志位>环境变量>远程Key/Value存储>默认值。)

Viper使用crypt从K/V存储中检索配置,这意味着如果你有正确的gpg密匙,你可以将配置值加密存储并自动解密。加密是可选的。

你可以将远程配置与本地配置结合使用,也可以独立使用。

crypt有一个命令行助手,你可以使用它将配置放入K/V存储中。crypt默认使用在http://127.0.0.1:4001的etcd。

$ go get github.com/bketelsen/crypt/bin/crypt
$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json

确认值已经设置:

$ crypt get -plaintext /config/hugo.json

有关如何设置加密值或如何使用Consul的示例,请参见crypt文档。

远程Key/Value存储示例-未加密

etcd

viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
// 因为在字节流中没有文件扩展名,所以这里需要设置下类型。
// 支持的扩展名有 "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
viper.SetConfigType("json") 
err := viper.ReadRemoteConfig()

Consul
你需要 Consul Key/Value存储中设置一个Key保存包含所需配置的JSON值。例如,创建一个key : MY_CONSUL_KEY将下面的值存入Consul key/value 存储:

{"port": 8080,"hostname": "baidu.com"
}
viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")
viper.SetConfigType("json") // 需要显示设置成json
err := viper.ReadRemoteConfig()fmt.Println(viper.Get("port")) // 8080
fmt.Println(viper.Get("hostname")) // baidu.com

Firestore

viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document")
viper.SetConfigType("json") // 配置的格式: "json", "toml", "yaml", "yml"
err := viper.ReadRemoteConfig()

当然,你也可以使用SecureRemoteProvider。

远程Key/Value存储示例-加密

viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
viper.SetConfigType("json") 
err := viper.ReadRemoteConfig()

监控etcd中的更改-未加密

// 或者你可以创建一个新的viper实例
var runtime_viper = viper.New()runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
runtime_viper.SetConfigType("yaml") // 第一次从远程读取配置
err := runtime_viper.ReadRemoteConfig()// 反序列化
runtime_viper.Unmarshal(&runtime_conf)// 开启一个单独的goroutine一直监控远端的变更
go func(){for {time.Sleep(time.Second * 5) // 每次请求后延迟一下// 目前只测试了etcd支持err := runtime_viper.WatchRemoteConfig()if err != nil {log.Errorf("unable to read remote config: %v", err)continue}// 将新配置反序列化到我们运行时的配置结构体中。你还可以借助channel实现一个通知系统更改的信号runtime_viper.Unmarshal(&runtime_conf)}
}()

二、从Viper获取值

在Viper中,有几种方法可以根据值的类型获取值。存在以下功能和方法:

  • Get(key string) : interface{}
  • GetBool(key string) : bool
  • GetFloat64(key string) : float64
  • GetInt(key string) : int
  • GetIntSlice(key string) : []int
  • GetString(key string) : string
  • GetStringMap(key string) : map[string]interface{}
  • GetStringMapString(key string) : map[string]string
  • GetStringSlice(key string) : []string
  • GetTime(key string) : time.Time
  • GetDuration(key string) : time.Duration
  • IsSet(key string) : bool
  • AllSettings() : map[string]interface{}

需要认识到的一件重要事情是,每一个Get方法在找不到值的时候都会返回零值。为了检查给定的键是否存在,提供了IsSet()方法。

例如:

viper.GetString("logfile") // 不区分大小写的设置和获取
if viper.GetBool("verbose") {fmt.Println("verbose enabled")
}

1. 访问嵌套的键

访问器方法也接受深度嵌套键的格式化路径。例如,如果加载下面的JSON文件:

{"host": {"address": "localhost","port": 5799},"datastore": {"metric": {"host": "127.0.0.1","port": 3099},"warehouse": {"host": "198.0.0.1","port": 2112}}
}

Viper可以通过传入.分隔的路径来访问嵌套字段:

GetString("datastore.metric.host") // (返回 "127.0.0.1")

这遵守上面建立的优先规则;搜索路径将遍历其余配置注册表,直到找到为止。(译注:因为Viper支持从多种配置来源,例如磁盘上的配置文件>命令行标志位>环境变量>远程Key/Value存储>默认值,我们在查找一个配置的时候如果在当前配置源中没找到,就会继续从后续的配置源查找,直到找到为止。)

例如,在给定此配置文件的情况下,datastore.metric.host和datastore.metric.port均已定义(并且可以被覆盖)。如果另外在默认值中定义了datastore.metric.protocol,Viper也会找到它。

然而,如果datastore.metric被直接赋值覆盖(被flag,环境变量,set()方法等等…),那么datastore.metric的所有子键都将变为未定义状态,它们被高优先级配置级别“遮蔽”(shadowed)了。

最后,如果存在与分隔的键路径匹配的键,则返回其值。例如:

{"datastore.metric.host": "0.0.0.0","host": {"address": "localhost","port": 5799},"datastore": {"metric": {"host": "127.0.0.1","port": 3099},"warehouse": {"host": "198.0.0.1","port": 2112}}
}
GetString("datastore.metric.host") // 返回 "0.0.0.0"

2. 提取子树

从Viper中提取子树。

例如,viper实例现在代表了以下配置:

app:cache1:max-items: 100item-size: 64cache2:max-items: 200item-size: 80

执行后:

subv := viper.Sub("app.cache1")

subv现在就代表:

max-items: 100
item-size: 64

假设我们现在有这么一个函数:

func NewCache(cfg *Viper) *Cache {...}

它基于subv格式的配置信息创建缓存。现在,可以轻松地分别创建这两个缓存,如下所示:

cfg1 := viper.Sub("app.cache1")
cache1 := NewCache(cfg1)cfg2 := viper.Sub("app.cache2")
cache2 := NewCache(cfg2)

3. 反序列化

你还可以选择将所有或特定的值解析到结构体、map等。

有两种方法可以做到这一点:

Unmarshal(rawVal interface{}) : error
UnmarshalKey(key string, rawVal interface{}) : error

举个例子:

type config struct {Port intName stringPathMap string `mapstructure:"path_map"`
}var C configerr := viper.Unmarshal(&C)
if err != nil {t.Fatalf("unable to decode into struct, %v", err)
}

如果你想要解析那些键本身就包含.(默认的键分隔符)的配置,你需要修改分隔符:

v := viper.NewWithOptions(viper.KeyDelimiter("::"))v.SetDefault("chart::values", map[string]interface{}{"ingress": map[string]interface{}{"annotations": map[string]interface{}{"traefik.frontend.rule.type":                 "PathPrefix","traefik.ingress.kubernetes.io/ssl-redirect": "true",},},
})type config struct {Chart struct{Values map[string]interface{}}
}var C configv.Unmarshal(&C)

Viper还支持解析到嵌入的结构体:

/*
Example config:module:enabled: truetoken: 89h3f98hbwf987h3f98wenf89ehf
*/
type config struct {Module struct {Enabled boolmoduleConfig `mapstructure:",squash"`}
}// moduleConfig could be in a module specific package
type moduleConfig struct {Token string
}var C configerr := viper.Unmarshal(&C)
if err != nil {t.Fatalf("unable to decode into struct, %v", err)
}

Viper在后台使用github.com/mitchellh/mapstructure来解析值,其默认情况下使用mapstructure tag。

4. 序列化成字符串

你可能需要将viper中保存的所有设置序列化到一个字符串中,而不是将它们写入到一个文件中。你可以将自己喜欢的格式的序列化器与AllSettings()返回的配置一起使用。

import (yaml "gopkg.in/yaml.v2"// ...
)func yamlStringSettings() string {c := viper.AllSettings()bs, err := yaml.Marshal(c)if err != nil {log.Fatalf("unable to marshal config to YAML: %v", err)}return string(bs)
}

5. 使用单个还是多个Viper实例?

Viper是开箱即用的。你不需要配置或初始化即可开始使用Viper。由于大多数应用程序都希望使用单个中央存储库管理它们的配置信息,所以viper包提供了这个功能。它类似于单例模式。

在上面的所有示例中,它们都以其单例风格的方法演示了如何使用viper。

使用多个viper实例
你还可以在应用程序中创建许多不同的viper实例。每个都有自己独特的一组配置和值。每个人都可以从不同的配置文件,key value存储区等读取数据。每个都可以从不同的配置文件、键值存储等中读取。viper包支持的所有功能都被镜像为viper实例的方法。

例如:

x := viper.New()
y := viper.New()x.SetDefault("ContentDir", "content")
y.SetDefault("ContentDir", "foobar")//...

当使用多个viper实例时,由用户来管理不同的viper实例。

三、使用Viper示例

这里用一个demo演示如何在gin框架搭建的web项目中使用viper。

package mainimport ("fmt""github.com/gin-gonic/gin""github.com/spf13/viper"
)func main() {viper.SetConfigName("config")  // 指定配置文件名称(不需要带后缀)// viper.SetConfigType("yaml")    // 指定配置文件类型viper.AddConfigPath("./conf/") // 指定查找配置文件的路径(这里使用相对路径)err := viper.ReadInConfig()    // 读取配置信息if err != nil {                // 读取配置信息失败panic(fmt.Errorf("Fatal error config file: %s \n", err))}r := gin.Default()if err := r.Run(fmt.Sprintf(":%d", viper.Get("port"))); err != nil {panic(err)}
}

./conf/config.yaml文件内容如下:

port: 8123

以上内容翻译自Viper官方README文档,为方便理解部分内容稍作修改。

参考链接:https://github.com/spf13/viper/blob/master/README.md

这篇关于9. Go语言配置管理神器Viper的基本使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/784796

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

基本知识点

1、c++的输入加上ios::sync_with_stdio(false);  等价于 c的输入,读取速度会加快(但是在字符串的题里面和容易出现问题) 2、lower_bound()和upper_bound() iterator lower_bound( const key_type &key ): 返回一个迭代器,指向键值>= key的第一个元素。 iterator upper_bou

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验