Go 1.19.4 序列化和反序列化-Day 16

1. 序列化和反序列化

1.1 序列化

1.1.1 什么是序列化

序列化它是一种将程序中的数据结构(map、slice、array等)或对象状态转换成一系列字节序列的过程,这些字节可以被存储或通过网络发送。

在GO中,序列化通常涉及到将结构体或其他数据类型转换成JSON、XML、二进制等格式。

1.1.2 为什么要序列化

内存中的map、slice、array以及各种对象,如何保存到一个文件中? 如果是自己定义的结构体的实例,如何保存到一个文件中?

这就需要设计一套协议,按照某种规则,把内存中数据保存到文件中,而文件是一个字节序列,所以必须把数据转换成字节序列,输出到文件,这就是序列化。 

序列化的需求主要来自于以下几个方面:

  1. 数据持久化:我们需要将数据保存到硬盘或数据库中,以便在程序关闭后再次启动时能够恢复数据。
  2. 网络通信:在分布式系统中,服务之间需要通过网络进行通信,序列化是将数据转换成适合网络传输格式的关键步骤。
  3. 跨语言交互:不同的编程语言可能有不同的数据结构表示方式,序列化提供了一种通用的方法来确保数据在不同语言之间能够被正确理解和使用。

 1.1.3 何时进行序列化

  1. 数据存储:在将数据写入文件或数据库之前。
  2. 网络传输:在将数据发送到远程服务之前。
  3. 数据交换:在不同系统或服务之间交换数据时。

1.1.4 如何进行序列化

  1. 选择序列化格式:根据需求选择合适的序列化格式,如JSON、XML或二进制。
  2. 定义数据结构:在GO中定义要序列化的数据结构,通常是通过定义一个或多个结构体。
  3. 序列化数据:使用GO标准库中的序列化函数,如json.Marshalxml.Marshal,将数据结构转换成字节流。

 1.2 反序列化

反序列化是将序列化的数据(通常是字节流)转换回内存中的数据结构的过程。

1.3 总结

  • serialization 序列化:将内存中对象存储下来,把它变成一个个字节。转为二进制数据。
  • deserialization 反序列化:将文件的一个个字节恢复成内存中对象。从 二进制 数据中恢复序列化保存到文件就是持久化。

可以将数据序列化后持久化,或者网络传输;也可以将从文件中或者网络接收到的字节序列反序列化。

我们可以把数据和二进制序列之间的相互转换称为二进制序列化、反序列化,把数据和字符序列之间的相互转换称为字符序列化、反序列化。
 

字符序列化:JSON、XML等。
 

二进制序列化:Protocol Buffers、MessagePack等。

2. Json(文本序列化)

JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于1999年发布的ES3 (ECMAScript是w3c组织制定的JavaScript规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。

JSON是什么?字符串!

应该说,目前JSON得到几乎所有浏览器的支持。参看 http://json.org/。

2.1 JSON的数据类型

2.1.1 值

双引号引起来的字符串、数值、true和false、null、对象、数组,这些都是值。

2.1.2 字符串

由双引号包围起来的任意字符的组合,可以有转义字符。

2.1.3 数值

有正负,有整数、浮点数。

 

2.1.4 对象

无序的键值对的集合,格式: {"key1":value1, ... ,"keyn":valulen},key必须是一个字符串,需要双引号包围这个字符串。 value可以是任意合法的值。

2.1.5 数组

有序的值的集合 格式:[val1,...,valn]。

2.1.6 示例

{"person": [{"name": "tom","age": 18},{"name": "jerry","age": 16}],"total": 2
}

特别注意:JSON是字符串,是文本。JavaScript引擎可以将这种字符串解析为某类型的数据。

2.2 json包

1. json.Marsha(序列化)

        语法:

                func json.Marshal(v any) ([]byte, error)

        参数:

              v any:  v 是一个空接口类型(interface{}),这意味着你可以传递任何类型的数据结构给它。

        返回值:

                []byte:字节切片([]byte),包含了 JSON 格式的数据。

                error:如果序列化过程中出现错误,这个错误会被返回。
 

2. json.Unmarshal(反序列化)

        语法:

                func json.Unmarshal(data []byte, v any) error

        参数:

                data []byte:这是一个包含JSON数据的字节切片。

                v any:这是一个空接口类型(interface{}),它可以接受任何类型的值。在Unmarshal函数中,这个参数通常是一个指向Go结构体的指针,或者是你想将JSON数据解码到的其他类型的变量。

        返回值:

                error:如果在解码过程中遇到任何错误,这个错误会被返回。如果没有错误发生,返回值将是nil

2.2.1 基本类型序列化

package mainimport ("encoding/json""fmt"
)func main() {var data = []any{// 100: 3个字符100,// 2.5: 3个字符2.5,// true: 4个字符true,// false: 5个字符false,// nil(null): 4个字符nil,// "accc":6个字符"accc",}// 对切片中的元素挨个进行序列化for i, v := range data {b, err := json.Marshal(v)if err != nil {continue}fmt.Printf("i=%d | v的类型=%T | v的值=%[2]v | b的类型=%[3]T | b的值=%[3]v | string(b)的类型=%[4]T | string(b)的值=%[4]v\n", i, v, b, string(b))}// 对整个切片序列化fmt.Println(json.Marshal(data))t, _ := json.Marshal(data)fmt.Println(string(t))fmt.Printf("%q", string(t)) // 注意,实际上序列化后的json数据,是有双引号的。
}
========调试结果========
i=0 | v的类型=int | v的值=100 | b的类型=[]uint8 | b的值=[49 48 48] | string(b)的类型=string | string(b)的值=100
i=1 | v的类型=float64 | v的值=2.5 | b的类型=[]uint8 | b的值=[50 46 53] | string(b)的类型=string | string(b)的值=2.5
i=2 | v的类型=bool | v的值=true | b的类型=[]uint8 | b的值=[116 114 117 101] | string(b)的类型=string | string(b)的值=true
i=3 | v的类型=bool | v的值=false | b的类型=[]uint8 | b的值=[102 97 108 115 101] | string(b)的类型=string | string(b)的值=false
i=4 | v的类型=<nil> | v的值=<nil> | b的类型=[]uint8 | b的值=[110 117 108 108] | string(b)的类型=string | string(b)的值=null
i=5 | v的类型=string | v的值=accc | b的类型=[]uint8 | b的值=[34 97 99 99 99 34] | string(b)的类型=string | string(b)的值="accc"
[91 49 48 48 44 50 46 53 44 116 114 117 101 44 102 97 108 115 101 44 110 117 108 108 44 34 97 99 99 99 34 93] <nil>
[100,2.5,true,false,null,"accc"]
"[100,2.5,true,false,null,\"accc\"]"

这里说下为什么上面b的值如100是[49 48 48]?

首先json本身的数据类型就是string,表达100的时候,直接就是1 0 0,但不能添加双引号,否则数据类型错误。上面看到的引号,只是在go中的界定符,json中实际不存在,看v的值就知道了。

也就是说100在json中是3个字符,对应的ASCII编码表就是:字符1=49(十进制)、字符0=48(十进制)。

在说下nil,它是一个特殊的数据类型,本意为空,转换为字符串后,就会变成null。

2.2.1.1 存储序列化数据

实际的存储,肯定是输出到文件或者数据库,这里只是演示一下把json数据写入到二维切片。

package mainimport ("encoding/json""fmt"
)func main() {var data = []any{100, 2.5, true, false, nil, "accc"}// 定义二维切片,存储json序列化数据var target = make([][]byte, 0, len(data))// 对切片中的元素进行序列化for _, v := range data {b, err := json.Marshal(v)if err != nil {continue}target = append(target, b)// fmt.Printf("i=%d | v的类型=%T | v的值=%[2]v | b的类型=%[3]T | b的值=%[3]v | string(b)的类型=%[4]T | string(b)的值=%[4]v\n", i, v, b, string(b))}fmt.Println(target)}
=====================调试结果=====================
[[49 48 48] [50 46 53] [116 114 117 101] [102 97 108 115 101] [110 117 108 108] [34 97 99 99 99 34]]
2.2.1.2 反序列化
package mainimport ("encoding/json""fmt"
)func main() {var data = []any{100, 2.5, true, false, nil, "accc"}var target = make([][]byte, 0, len(data))for _, v := range data {b, err := json.Marshal(v)if err != nil {continue}target = append(target, b)}// fmt.Println(target)fmt.Println("==============反序列化==============")for i, v := range target {// json.Unmarshal需要的any类型,反序列化后的数据,也是存在a里面的var a anyerr := json.Unmarshal(v, &a)if err != nil {// 如果反序列化失败,则跳过本次循环,继续下一次循环continue}fmt.Printf("%d 反序列化后的值=%[3]v 反序列化后的类型=%[3]T 反序列化前的类型=%[2]T %[2]v \n", i, v, a)}
}
==============反序列化==============
0 反序列化后的值=100 反序列化后的类型=float64 反序列化前的类型=[]uint8 [49 48 48] 
1 反序列化后的值=2.5 反序列化后的类型=float64 反序列化前的类型=[]uint8 [50 46 53] 
2 反序列化后的值=true 反序列化后的类型=bool 反序列化前的类型=[]uint8 [116 114 117 101] 
3 反序列化后的值=false 反序列化后的类型=bool 反序列化前的类型=[]uint8 [102 97 108 115 101] 
4 反序列化后的值=<nil> 反序列化后的类型=<nil> 反序列化前的类型=[]uint8 [110 117 108 108] 
5 反序列化后的值=accc 反序列化后的类型=string 反序列化前的类型=[]uint8 [34 97 99 99 99 34]

为什么 1002.5 在反序列化后变成了 float64?

这是因为 JSON 标准中数字默认表示为双精度浮点数(float64)。即使它们看起来像是整数,Go 语言的 json 包在没有小数点的情况下也会将它们解码为 float64 类型。

2.2.1.3 数组序列化
package mainimport ("encoding/json""fmt"
)func main() {// var data = []any{100, 2.5, true, false, nil, "accc"}var data = []any{[...]int{97, 98, 99}}var target = make([][]byte, 0, len(data))for _, v := range data {b, err := json.Marshal(v)if err != nil {continue}target = append(target, b)}// fmt.Println(target)fmt.Println("==============反序列化==============")for i, v := range target {// json.Unmarshal需要的any类型,反序列化后的数据,也是存在a里面的var a anyerr := json.Unmarshal(v, &a)if err != nil {// 如果反序列化失败,则跳过本次循环,继续下一次循环continue}fmt.Printf("%d 反序列化前的值=%[2]v 反序列化后的值=%[3]v 反序列化后的类型=%[3]T 反序列化前的类型=%[2]T\n", i, v, a)}
}
==============反序列化==============
0 反序列化前的值=[91 57 55 44 57 56 44 57 57 93] 反序列化后的值=[97 98 99] 反序列化后的类型=[]interface {} 反序列化前的类型=[]uint8

json [91 57 55 44 57 56 44 57 57 93],实际对应的go中的字符串是:

实际就是json序列化后,如原来的99,字符序列化后就只占用2个字节了,\x39\x39,如果用整数表达,可以用一个字节,那就是0x39。

2.2.2 结构体序列化

package mainimport ("encoding/json""fmt"
)type Person struct {Name stringAge  int
}func main() {var data = Person{"Tom", 32}fmt.Println("=======序列化=======")// 序列化b, err := json.Marshal(data)if err != nil {panic(err)}// 序列化后,原有的person类型就被json丢弃了,因为json本身没有person类型fmt.Printf("序列化前的数据类型=%T 序列化前的值=%[1]v\n序列化后的数据类型=%[2]T 序列化后的值=%[2]v\n", data, b)fmt.Println(string(b))fmt.Println("=======反序列化=======")var a Personerr2 := json.Unmarshal(b, &a)// err2 := json.Unmarshal([]byte(`{"Name":"Tom","Age":32}`), &a)if err2 != nil {panic(err2)}fmt.Printf("反序列化前的类型=%T 反序列化前的值=%[1]v\n反序列化后的类型=%[2]T 反序列化后的值=%+[2]v\n", b, a)
}
=======序列化=======
序列化前的数据类型=main.Person 序列化前的值={Tom 32}
序列化后的数据类型=[]uint8 序列化后的值=[123 34 78 97 109 101 34 58 34 84 111 109 34 44 34 65 103 101 34 58 51 50 125]
{"Name":"Tom","Age":32}
=======反序列化=======
反序列化前的类型=[]uint8 反序列化前的值=[123 34 78 97 109 101 34 58 34 84 111 109 34 44 34 65 103 101 34 58 51 50 125]
反序列化后的类型=main.Person 反序列化后的值={Name:Tom Age:32}

2.2.3 切片序列化

package mainimport ("encoding/json""fmt"
)type Person struct {Name stringAge  int
}func main() {var data = []Person{{Name: "AAA", Age: 20},{Name: "aaa", Age: 32},}fmt.Println("=======序列化=======")// 序列化b, err := json.Marshal(data)if err != nil {panic(err)}// 序列化后,原有的person类型就被json丢弃了,因为json本身没有person类型fmt.Printf("序列化前的数据类型=%T 序列化前的值=%[1]v\n序列化后的数据类型=%[2]T 序列化后的值=%[2]v\n", data, b)fmt.Println(string(b))fmt.Println("=======反序列化=======")var a []Personerr2 := json.Unmarshal(b, &a)if err2 != nil {panic(err2)}fmt.Printf("反序列化前的类型=%T 反序列化前的值=%[1]v\n反序列化后的类型=%[2]T 反序列化后的值=%+[2]v\n", b, a)
}
=======序列化=======
序列化前的数据类型=[]main.Person 序列化前的值=[{AAA 20} {aaa 32}]
序列化后的数据类型=[]uint8 序列化后的值=[91 123 34 78 97 109 101 34 58 34 65 65 65 34 44 34 65 103 101 34 58 50 48 125 44 123 34 78 97 109 101 34 58 34 97 97 97 34 44 34 65 103 101 34 58 51 50 125 93]
[{"Name":"AAA","Age":20},{"Name":"aaa","Age":32}]
=======反序列化=======
反序列化前的类型=[]uint8 反序列化前的值=[91 123 34 78 97 109 101 34 58 34 65 65 65 34 44 34 65 103 101 34 58 50 48 125 44 123 34 78 97 109 101 34 58 34 97 97 97 34 44 34 65 103 101 34 58 51 50 125 93]
反序列化后的类型=[]main.Person 反序列化后的值=[{Name:AAA Age:20} {Name:aaa Age:32}]

2.2.4 字段标签

结构体的字段可以增加标签tag,序列化、反序列化时使用。

(1)在字段类型后,可以跟反引号引起来的一个标签,用json为key,value用双引号引起来写,key与 value直接使用冒号,这个标签中不要加入多余空格,否则语法错误。

Name  string `json:"姓名"`:

        这里表示Name序列化后,就在json中显示为“姓名”。

        json表示json库使用,双引号内第一个参数用来指定字段转换使用的名称,多个参数使用逗号隔开。

Email string `json:"邮箱,omitempty"`:

        omitempty为序列化时忽略空值,也就是该字段 不序列化。
        空值为false、0、空数组、空切片、空map、空串、nil空指针、nil接口值。

        空数组、空切片、空串、空map,长度len为0,也就是容器没有元素。

(2)如果使用 - ,该字段将被忽略

Name string `json:"-"`,:

        序列化后没有该字段,反序列化也不会转换该字段。

Name string `json:"-,"`:

        序列化后该字段显示但名为 "-" ,反序列化也会转换该字段。


(3)多标签使用空格间隔

Name string `json:"name,omitempty" msgpack:"myname"`

package mainimport ("encoding/json""fmt"
)type Person struct {Name  string `json:"姓名"`Age   int    `json:"年龄"`Email string `json:"邮箱,omitempty"` //omitempty 选项表示如果 Email 字段为空(即零值),则在 JSON 输出中省略该字段。
}func main() {var data = []Person{{Name: "AAA", Age: 20},{Name: "aaa", Age: 32},}fmt.Println("=======序列化=======")// 序列化b, err := json.Marshal(data)if err != nil {panic(err)}// 序列化后,原有的person类型就被json丢弃了,因为json本身没有person类型fmt.Printf("序列化前的数据类型=%T 序列化前的值=%[1]v\n序列化后的数据类型=%[2]T 序列化后的值=%[2]v\n", data, b)fmt.Println(string(b))fmt.Println("=======反序列化=======")var a []Personerr2 := json.Unmarshal(b, &a)if err2 != nil {panic(err2)}fmt.Printf("反序列化前的类型=%T 反序列化前的值=%[1]v\n反序列化后的类型=%[2]T 反序列化后的值=%+[2]v\n", b, a)
}
=======序列化=======
序列化前的数据类型=[]main.Person 序列化前的值=[{AAA 20 } {aaa 32 }]
序列化后的数据类型=[]uint8 序列化后的值=[91 123 34 229 167 147 229 144 141 34 58 34 65 65 65 34 44 34 229 185 180 233 190 132 34 58 50 48 125 44 123 34 229 167 147 229 144 141 34 58 34 97 97 97 34 44 34 229 185 180 233 190 132 34 58 51 50 125 93]
[{"姓名":"AAA","年龄":20},{"姓名":"aaa","年龄":32}]
=======反序列化=======
反序列化前的类型=[]uint8 反序列化前的值=[91 123 34 229 167 147 229 144 141 34 58 34 65 65 65 34 44 34 229 185 180 233 190 132 34 58 50 48 125 44 123 34 229 167 147 229 144 141 34 58 34 97 97 97 34 44 34 229 185 180 233 190 132 34 58 51 50 125 93]
反序列化后的类型=[]main.Person 反序列化后的值=[{Name:AAA Age:20 Email:} {Name:aaa Age:32 Email:}]

3. MessagePack(二进制序列化)

MessagePack是一个基于二进制高效的对象序列化类库,可用于跨语言通信。 它可以像JSON那样,在 许多种语言之间交换结构对象。但是它比JSON更快速也更轻巧。 支持Python、Ruby、Java、C/C++、 Go等众多语言。宣称比Google Protocol Buffers还要快4倍。

安装

go get github.com/vmihailenco/msgpack/v5

基本使用方法和json包类似

package mainimport ("fmt""github.com/vmihailenco/msgpack/v5"
)type Person struct {Name string `json:"name" msgpack:"myname"`Age  int    `json:"age" msgpack:"myage"`
}func main() {// 序列化var data = []Person{{Name: "Tom", Age: 16},{Name: "Jerry", Age: 32},}b, err := msgpack.Marshal(data) // 方法都和json兼容if err != nil {panic(err)}fmt.Println(b, len(b), string(b)) // 二进制// 反序列化// 知道目标类型var j []Personerr = msgpack.Unmarshal(b, &j)if err != nil {fmt.Println(err)return}fmt.Printf("%T: %+[1]v\n", j)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1543830.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

VirtualBox+Vagrant快速搭建Centos7系统【最新详细教程】

VirtualBoxVagrant快速搭建Centos7系统 &#x1f4d6;1.安装VirtualBox✅下载VirtualBox✅安装 &#x1f4d6;2.安装Vagrant✅下载Vagrant✅安装 &#x1f4d6;3.搭建Centos7系✅初始化Vagrantfile文件生成✅启动Vagrantfile文件✅解决 vagrant up下载太慢的问题✅配置网络ip地…

Apache Iceberg Architecture—Iceberg 架构详解

Apache Iceberg Architecture Apache Iceberg 的架构可以分为三个主要层次&#xff1a;Iceberg Catalog、元数据层和数据层。 一、 Iceberg Catalog&#xff08;目录&#xff09; Iceberg Catalog 是 Iceberg 的顶层组件&#xff0c;负责管理所有 Iceberg 表的元数据和元数据操…

HTML常用的文本标签

常用文本标签 <span>: 元素没有特定含义 <b>: 定义粗体文字 <i>: 定义斜体文字 <em>: 定义着重文字 <strong>: 定义加重语气 <del>: 定义删除字 <span>文本标签</span><br><b>文本标签</b><b…

Java框架学习(Spring)(tx)(03)

简介&#xff1a;以本片记录在尚硅谷学习ssm-spring-tx时遇到的小知识 详情移步&#xff1a;想参考的朋友建议全部打开相互配合学习&#xff01; 视频&#xff1a; 057-spring-tx-编程式和声明式事务理解_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1AP411s7D7?p5…

MySQL tinyint(1)类型数据在经过flink cdc同步到doris后只有0/1问题定位与解决

背景&#xff1a; 近期在负责公司数据仓库搭建事宜&#xff0c;踩了一些坑后&#xff0c;终于通了&#xff0c;目标报表也成功迁移到了新方案上&#xff0c;可在数据验收的时候发现&#xff0c;同一个订单查询出了多条记录&#xff0c;原本以为只是简单的left join出多条记录问…

Unreal Engine 5 C++: 插件编写03 | MessageDialog

在虚幻引擎编辑器中编写Warning弹窗 准备工作 FMessageDialog These functions open a message dialog and display the specified informations there. EAppReturnType::Type 是 Unreal Engine 中用于表示应用程序对话框&#xff08;如消息对话框&#xff09;返回结果的枚举…

【算法笔记】二分查找 红蓝染色法

目录 二分查找 红蓝染色法&#xff08;感谢灵神&#xff09;闭区间[left, right]左闭右开区间[left, right)开区间(left, right)变式 二分查找 红蓝染色法&#xff08;感谢灵神&#xff09; 这里是灵神的教学视频&#xff1a;二分查找 红蓝染色法_哔哩哔哩_ bilibili 学了二分…

ubuntu中通过源码安装pointnet2_ops_lib

注&#xff1a;本帖所用环境为&#xff1a;ubuntu 24.04、 cuda 12.04 文章目录 1. 克隆 PointNet 源码库2. 安装依赖3. 编译 pointnet2_ops_lib4. 测试安装 1. 克隆 PointNet 源码库 首先&#xff0c;克隆 PointNet 的 GitHub 仓库&#xff1a; git clone https://github.co…

加密软件是怎么实现文件加密的

1、选择加密算法&#xff1a;加密软件支持多种加密算法&#xff0c;如对称加密算法&#xff08;如AES、DES&#xff09;和非对称加密算法&#xff08;如RSA&#xff09;。用户可根据需求和安全性要求选择合适的算法。 2、生成密钥&#xff1a;加密算法需要一定的密钥来对文件进…

代码随想录Day17 图论-1

DFS和BFS基础 做图论这部分的题目DFS和BFS少不了 DFS是深搜 沿着一条路一直搜索下去直到无法继续向下 再通过回溯 换一条路进行搜索 BFS是广搜 就是从当前节点出发 一直把当前节点所连接的所有节点都搜索过之后 进入下一节点在开始相同的搜索过程 98.所有可达路径 题意很简…

linux环境oracle11.2.0.4打补丁(p31537677_112040_Linux-x86-64.zip)

上传补丁及opatch工具 创建目录并上传opatch工具和补丁包 [oraclerhel64 ~]$ mkdir /u01/psu [oraclerhel64 ~]$ cd /u01/psu [oraclerhel64 psu]$ ll total 514572 -rw-r--r-- 1 oracle oinstall 391781147 Sep 23 17:37 p31537677_112040_Linux-x86-64.zip -rw-r--r-- 1 or…

iOS 顶级神器,巨魔录音机更新2.1正式版

嘿&#xff0c;这是黑猫。如果巨魔没有通话录音机&#xff0c;那它的价值至少减半。 用户的痛点就是商机&#xff0c;因此开发通话录音功能的巨魔开发者&#xff0c;不约而同地选择了付费制。 而在一众录音机中&#xff0c; TrollRecorder 巨魔录音机可以说是用户体验最好&am…

【深度学习】03-神经网络2-1损失函数

在神经网络中&#xff0c;不同任务类型&#xff08;如多分类、二分类、回归&#xff09;需要使用不同的损失函数来衡量模型预测和真实值之间的差异。选择合适的损失函数对于模型的性能至关重要。 这里的是API 的注意⚠️&#xff0c;但是在真实的公式中&#xff0c;目标值一定是…

STM32 的 SDIO 接口(基于STM32F429HAL库)

目录 一、引言 二、SDIO 控制器组成 1.时钟管理模块 2.命令通道模块 3.数据通道模块 4.中断管理模块 三、STM32F429 的 SDIO 特性 1.高速数据传输 2.兼容性强 3.灵活的配置选项 4.可靠性和稳定性 四、HAL 库中的 SDIO 相关结构和函数 1.SD_HandleTypeDef结构体…

基于SpringBoot+Vue的在线问诊管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

OpenCV特征检测(12)检测图像中的潜在角点函数preCornerDetect()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算用于角点检测的特征图。 该函数计算源图像的基于复杂空间导数的函数 dst ( D x src ) 2 ⋅ D y y src ( D y src ) 2 ⋅ D x x src − 2 …

vue使用PDF.JS踩的坑--部署到服务器上显示pdf.mjs viewer.mjs找不到资源

之前项目使用的pdf.js 是2.15.349版本&#xff0c;最近换了一个4.6.82的版本&#xff0c;在本地上浏览文件运行的好好的&#xff0c;但是发布到服务器&#xff08;IIS&#xff09;上打不开文件&#xff0c;控制台提示找不到pdf.mjs viewer.mjs。 之前使用的2.15.349pdf和viewer…

自动换行且带下划线的居中长标题的论文封面一种绘图实现

自动换行且带下划线的居中长标题的论文封面一种绘图实现 引言 在一些学位论文的封面上要求标题带有下划线&#xff0c;但长标题的情况下标题自动换行后下划线就会面临一些问题。 因此&#xff0c;往往需要一些特殊的处理。 在《如何制作自动换行且有定长下划线的论文封面模板…

第九节 Opencv自带颜色表操作

知识点&#xff1a;Look Up lTable&#xff08;LUT&#xff09;查找表 了解LUT查找表的作用与用法&#xff0c;代码实现与API介绍 -applyColorMap&#xff08;src,dst,COLORMAP&#xff09; -src表示输入图像 -dst表示输出图像 匹配到的颜色LUT&#xff0c;Opencv支持13种…

17.2 ksm源码讲解

本节重点介绍 : k8s资源对象的 buildStores构造函数注入MetricFamiliesk8s client-go 之 Reflector listAndWatch 方法watchHandler 监听更新&#xff0c;调用add等action 架构图总结 项目地址 地址 go get go get -v -d k8s.io/kube-state-metrics/v2v2.1.1源码分析 m…