Go 标准库 encoding/gob 快速上手

文章目录

  • 1.简介
  • 2.基础
  • 3.类型和值
  • 4.编码细节
  • 5.安全
  • 6.主要函数
    • 6.1 注册
      • 1. 接口的底层类型在运行时才能确定
      • 2.类型标识的唯一性
      • 3.安全性与显式意图
      • 4.与结构体的自动处理对比
      • 5.示例分析
      • 为什么不能像 JSON 那样自动处理?
      • 总结
    • 6.2 编码
    • 6.3 解码
  • 7.示例
    • 7.1 编解码结构体
    • 7.2 编解码接口
    • 7.3 文件读写
    • 7.4 自定义编解码方式
  • 8.优势与局限
    • 8.1 优势
    • 8.2 局限
    • 8.3 小结
  • 9.最佳实践
    • 类型兼容性
    • 注册策略
    • 安全考虑
    • 调试技巧
    • 替代方案考虑
  • 参考文献

1.简介

encoding/gob 是 Go 语言标准库中一个用于 Go 数据结构与二进制流之间序列化和反序列化的制协议包。

gob 包用来管理 gob 流,它可以实现在编码器(发送器)和解码器(接收器)之间进行二进制数据流的发送,一般用来传递远端程序调用的参数和结果,比如 net/rpc 包就有用到这个。

gob 全称 GOlang Binary。go 代表 Go 语言,binary 表示其使用二进制编码(而非 JSON/XML 等文本格式)。

2.基础

gob 流具有自描述性。流中的每个数据项前面都有一个前缀(采用一个预定义类型的集合)指明其类型。指针不会被传输,但它们指向的内容会被传输;也就是说,值会被展平。不允许使用零指针,因为它们没有值。递归类型可以正常工作,但递归值(带循环的数据)存在问题。这种情况可能会有所改变。

要使用 gob,需要先创建一个编码器,并向其提供一系列数据项,这些数据项可以是值,也可以是可以解引用到值的地址。编码器会确保所有类型信息在需要之前都已发送。在接收端,解码器会从已编码的数据流中检索值,并将其解包到局部变量中。

3.类型和值

源和目标的值/类型不必完全对应。

对于结构体,如果源中有字段(通过名称标识),但接收变量中没有,则将被忽略。如果接收变量中有字段,但传输类型或值中没有,则目标变量中也会忽略这些字段。如果两个变量中都存在同名字段,则它们的类型必须兼容。接收方和发送方都会执行所有必要的间接和解引用操作,以便在 gob 和实际的 Go 值之间进行转换。

例如,一个 gob 类型如下:

struct { A, B int }

可以从以下任意 Go 类型发送或接收:

struct { A, B int } // 相同
*struct { A, B int } // 结构的额外间接
struct { *A, **B int } // 字段的额外间接
struct { A, B int64 } // 不同的具体值类型;见下文

它也可以被接收进以下任何一个:

struct { A, B int } 	// 相同
struct { B, A int } 	// 顺序不重要;按名称匹配
struct { A, B, C int } 	// 忽略额外字段 (C)
struct { B int } 		// 忽略缺失字段(A);数据从数据流中将被删除
struct { B, C int } 	// 忽略缺失字段(A);忽略额外字段(C)。

尝试接收下面这些类型将引发解码错误:

struct { A int; B uint } 	// 改变 B 的符号
struct { A int; B float } 	// 改变 B 的类型
struct { } 					// 没有共同的字段名称
struct { C, D int } 		// 没有共同的字段名称

整数的传输方式有两种:任意精度有符号整数或任意精度无符号整数。

gob 格式不区分 int8、int16 等整数类型;只区分有符号整数和无符号整数。如下所述,发送方以变长编码发送值;接收方接收该值并将其存储在目标变量中。浮点数始终使用 IEEE 754 64 位精度发送。

有符号整数可以被任何有符号整数变量接收:int、int16 等;无符号整数可以被任何无符号整数变量接收;浮点值可以被任何浮点变量接收。但是,目标变量必须能够表示该值,否则解码操作将失败。

结构体、数组和切片也受支持。结构体仅对导出的字段进行编码和解码。字符串和字节数组支持一种特殊且高效的表示形式(见下文)。切片解码时,如果现有切片具有容量,则切片将进行扩展;如果容量不足,则分配一个新数组。无论如何,结果切片的长度都会包含解码后的元素数量。

通常,如果需要分配内存,解码器会分配内存。如果不需要,它会使用从流中读取的值来更新目标变量。解码器不会先初始化目标变量,因此如果目标是复合值(如 map、struct 或 slice),解码后的值将按元素合并到现有变量中。

函数和通道不会通过 gob 发送。尝试在顶层编码此类值将会失败。chan 或 func 类型的结构体字段将被视为未导出的字段,并被忽略。

4.编码细节

本节记录了编码细节,这些细节对大多数用户来说并不重要(可以跳过)。详细信息按自下而上的方式呈现。

无符号整数的发送方式有两种。

如果小于 128,则以包含该值的字节形式发送。否则,将以最小长度的大端字节序(高字节优先)字节流的形式发送,该字节流包含该值,并在其前面附加一个字节,该字节包含字节数(取反)。因此,0 的发送方式为 (00),7 的发送方式为 (07),256 的发送方式为 (FE 01 00)。

布尔值在无符号整数内进行编码:0 表示假,1 表示真。

有符号整数 i 被编码在无符号整数 u 中。u 中的位 1 及以上表示其值;位 0 表示在接收时是否需要对其进行补码。编码算法如下:

var u uint
if i < 0 {u = (^uint(i) << 1) | 1 // complement i, bit 0 is 1
} else {u = (uint(i) << 1) // do not complement i, bit 0 is 0
}
encodeUnsigned(u)

因此,低位类似于符号位,但将其设为补码位可以保证最大负整数不是特例。例如,-129=^128=(^256>>1)编码为 (FE 01 01)。

浮点数始终以 float64 值的表示形式发送。该值使用math.Float64bits转换为 uint64 。然后,uint64 会被字节反转,并作为常规无符号整数发送。字节反转意味着尾数的指数和高精度部分先发送。由于低位通常为零,这可以节省编码字节数。例如,17.0 仅用三个字节编码 (FE 31 40)。

字符串和字节切片以无符号计数的形式发送,后跟该值的许多未解释的字节。

所有其他切片和数组都以无符号计数的形式发送,后跟使用标准 gob 编码递归发送的多个元素。

映射以无符号计数的形式发送,后跟对应数量的键值对和元素对。发送的是空但非零的映射,因此如果接收方尚未分配映射,则在接收时始终会分配一个,除非发送的映射为零且不在顶层。

在切片和数组以及映射中,所有元素,甚至是零值元素,都会被传输,即使所有元素都是零。

结构体以 (字段编号,字段值) 对的序列形式发送。字段值使用其类型的标准 gob 编码递归发送。如果某个字段的值为其类型的零值(数组除外;参见上文),则该字段将在传输中被省略。字段编号由编码结构体的类型定义:编码类型的第一个字段为字段 0,第二个字段为字段 1,依此类推。在对值进行编码时,为了提高效率,字段编号会进行增量编码,并且字段始终按字段编号递增的顺序发送;因此增量是无符号的。增量编码的初始化将字段编号设置为 -1,因此值为 7 的无符号整数字段 0 将被传输为无符号增量 = 1、无符号值 = 7 或 (01 07)。最后,在所有字段都发送完毕后,一个终止标记表示结构体的结束。该标记是一个增量 = 0 的值,其表示形式为 (00)。

接口类型不进行兼容性检查;所有接口类型在传输时都被视为单个“接口”类型的成员,类似于 int 或 []byte ——实际上它们都被视为 interface{}。接口值以字符串形式传输,该字符串标识要发送的具体类型(该名称必须通过调用 Register 预定义),后跟表示后续数据长度的字节数(因此,如果无法存储该值,则可以跳过该值),然后是对存储在接口值中的具体(动态)值的常规编码。(nil 接口值由空字符串标识,不传输任何值。)解码器接收后,会验证解包后的具体项是否满足接收变量的接口要求。

如果将一个值传递给 Encoder.Encode,并且其类型不是结构体(或指向结构体的指针等),为了简化处理,它会被表示为一个包含一个字段的结构体。这样做唯一可见的效果是在值之后编码一个零字节,就像在已编码结构体的最后一个字段之后一样,这样解码算法就能知道顶级值何时完成。

类型的表示如下所述。当在编码器 (Encoder) 和解码器 (Decoder) 之间的给定连接上定义类型时,它会被分配一个有符号整数类型 ID。当调用 Encoder.Encode(v) 时,它会确保为 v 的类型及其所有元素分配一个 ID,然后发送 (typeid, encoding-v) 对,其中 typeid 是 v 编码类型的类型 ID,encoded-v 是值 v 的 gob 编码。

为了定义类型,编码器选择一个未使用的正类型 ID,并发送对 (-type id, encoding-type),其中 encoding-type 是 wireType 描述的 gob 编码,由以下类型构成:

type wireType struct {ArrayT           *arrayTypeSliceT           *sliceTypeStructT          *structTypeMapT             *mapTypeGobEncoderT      *gobEncoderTypeBinaryMarshalerT *gobEncoderTypeTextMarshalerT   *gobEncoderType
}
type arrayType struct {CommonTypeElem typeIdLen  int
}
type CommonType struct {Name string // the name of the struct typeId  int    // the id of the type, repeated so it's inside the type
}
type sliceType struct {CommonTypeElem typeId
}
type structType struct {CommonTypeField []fieldType // the fields of the struct.
}
type fieldType struct {Name string // the name of the field.Id   int    // the type id of the field, which must be already defined
}
type mapType struct {CommonTypeKey  typeIdElem typeId
}
type gobEncoderType struct {CommonType
}

如果有嵌套类型 ID,则在使用顶级类型 ID 描述编码 v 之前,必须定义所有内部类型 ID 的类型。

为了简化设置,连接被定义为先验地理解这些类型,以及基本 gob 类型 int、uint 等。它们的 ID 是:

bool        1
int         2
uint        3
float       4
[]byte      5
string      6
complex     7
interface   8
// gap for reserved ids.
WireType    16
ArrayType   17
CommonType  18
SliceType   19
StructType  20
FieldType   21
// 22 is slice of fieldType.
MapType     23

最后,通过调用 Encode 创建的每条消息前面都会有一个编码的无符号整数计数,表示消息中剩余的字节数。在初始类型名称之后,接口值也以相同的方式包装;实际上,接口值的行为就像是 Encode 的递归调用。

总结一下,gob 流看起来像这样:

(byteCount (-type id, encoding of a wireType)* (type id, encoding of a value))*

其中 * 表示零次或多次重复,并且值的类型 ID 必须预先定义或在流中的值之前定义。

兼容性:此软件包的任何未来更改都将尽力保持与使用先前版本编码的流的兼容。也就是说,此软件包的任何发布版本都应该能够解码使用任何先前发布版本写入的数据,但可能会受到安全修复等问题的制约。有关背景信息,请参阅 Go 兼容性文档:https://golang.org/doc/go1compat。

有关 gob wire 格式的设计讨论,请参阅 “Gobs of data”:https://blog.golang.org/gobs-of-data。

5.安全

此软件包并非针对对抗性输入进行加固,且不在 https://go.dev/security/policy 的范围内。具体而言,解码器仅对解码后的输入大小进行基本的完整性检查,且其限制不可配置。解码来自不受信任来源的 gob 数据时应格外小心,因为这可能会消耗大量资源。

6.主要函数

6.1 注册

Register 和 RegisterName 函数都用于注册具体类型以实现接口的编解码。

  • func Register
// Register 记录 value 具体值对应的类型和其名称。
// 该名称将用来识别发送或接受接口类型值时下层的具体类型。
// 本函数只应在初始化时调用,如果类型和名字的映射不是一一对应的,会 panic。
func Register(value any)
  • func RegisterName
// RegisterName 与 Register 类似,但使用提供的名称而不是类型的默认名称。
func RegisterName(name string, value any)

func Register 底层会调用 func RegisterName。

为什么编解码接口要提前注册具体类型呢?

主要原因涉及类型安全、编解码机制和运行时动态处理的需求。以下是详细解释:

1. 接口的底层类型在运行时才能确定

  • 接口变量在编译时只有抽象类型信息(如 Animal),但实际存储的是具体类型(如 DogCat)。
  • Gob 在编码时需要知道接口背后的具体类型,才能正确序列化数据;解码时需要通过注册信息还原出正确的具体类型。
  • 如果不注册:解码器无法知道应该将数据还原为 Dog 还是 Cat,导致解码失败。

2.类型标识的唯一性

  • Gob 通过注册机制为每个具体类型分配唯一的内部标识符(如 main.Dog)。
  • 编码时,Gob 会写入这个标识符;解码时,通过标识符查找已注册的类型,并调用对应的解码方法。
  • 如果不注册:解码器无法将接收到的数据映射到正确的 Go 类型。

3.安全性与显式意图

  • 强制注册是一种安全机制,防止意外解码未预期的类型(类似反序列化攻击)。
  • 开发者必须显式声明“允许通过接口传输哪些具体类型”,避免运行时不可控行为。

4.与结构体的自动处理对比

  • 结构体:如果编解码双方有相同的类型定义,Gob 可以自动推导类型信息,因为结构体名称和字段是确定的。
  • 接口:具体类型是动态的,无法通过静态分析确定,必须依赖运行时的注册信息。

5.示例分析

type Animal interface { Speak() string }
type Dog struct { Name string }
type Cat struct { Name string }func init() {gob.Register(Dog{}) // 必须注册gob.Register(Cat{}) // 必须注册
}func send(a Animal) {// 编码时,Gob 需要知道 a 的具体类型是 Dog 还是 Cat// 通过注册表,可以找到 Dog 或 Cat 的类型标识符
}

为什么不能像 JSON 那样自动处理?

  • JSON 等文本协议通过字段名(如 "type": "dog")显式标识类型,但 Gob 是二进制协议,设计上追求高效和紧凑,不存储冗余的类型描述。
  • Gob 的注册机制避免了每次传输都附带完整的类型信息,提升了性能。

总结

Gob 要求注册接口的具体类型,本质上是为了解决接口的动态类型特性与二进制编解码的静态需求之间的矛盾。这是一种在灵活性、安全性和性能之间的权衡设计。

6.2 编码

数据在传输时会先经过编码(序列化)后再进行传输,与编码相关的有三个方法:

  • func NewEncoder
// NewEncoder 返回一个将在 io.Writer 上传输的新编码器。
func NewEncoder(w io.Writer) *Encoder
  • func (*Encoder) Encode
// Encode 会传输接口值所表示的数据项,并保证所有必要的类型信息都已传输完毕。
// 向 Encoder 传递一个 nil 指针会导致 panic,因为 gob 无法传输此类数据。
func (enc *Encoder) Encode(e any) error
  • func (*Encoder) EncodeValue
// EncodeValue 传输反射值所表示的数据项,并保证所有必要的类型信息都已传输完毕。
// 将 nil 指针传递给 EncodeValue 会导致 panic,因为它们无法通过 gob 传输。
func (enc *Encoder) EncodeValue(value reflect.Value) error

6.3 解码

接收到数据后需要对数据进行解码(序列化),与解码相关的有三个方法:

  • func NewDecoder
// NewDecoder 返回一个从 io.Reader 读取数据的新解码器。
// 如果 r 未实现 io.ByteReader 接口,则会将其包装在 bufio.Reader 中。
func NewDecoder(r io.Reader) *Decoder
  • func (*Decoder) Decode
// Decode 从输入流中读取下一个值,并将其存储在空接口值所表示的数据中。
// 如果 e 为 nil,则该值将被丢弃。否则,e 的底层值必须是指向下一个接收数据项的正确类型的指针。
// 如果输入位于 EOF,解码将返回 io.EOF 并且不修改 e。
func (dec *Decoder) Decode(e any) error
  • func (*Decoder) DecodeValue
// DecodeValue 从输入流中读取下一个值。
// 如果 v 为零的 reflect.Value(v.Kind() == Invalid),DecodeValue 会丢弃该值。否则,它会将值存储到 v 中。在这种情况下,v 必须表示一个指向数据的非零指针,或者是一个可赋值的 reflect.Value(v.CanSet())。
// 如果输入位于 EOF,DecodeValue 会返回 io.EOF 并且不会修改 v。
func (dec *Decoder) DecodeValue(v reflect.Value) error

7.示例

7.1 编解码结构体

package mainimport ("bytes""encoding/gob""fmt""log"
)type Person struct {Name stringAge  int
}func main() {// 创建数据alice := Person{Name: "Alice", Age: 30}// 序列化var buf bytes.Bufferencoder := gob.NewEncoder(&buf)if err := encoder.Encode(alice); err != nil {log.Fatal("Encode error:", err)}fmt.Printf("Serialized data: %x\n", buf.Bytes())// 反序列化var bob Persondecoder := gob.NewDecoder(&buf)if err := decoder.Decode(&bob); err != nil {log.Fatal("Decode error:", err)}fmt.Printf("Deserialized: %+v\n", bob)
}

运行输出:

Serialized data: 247f03010106506572736f6e01ff8000010201044e616d65010c00010341676501040000000cff800105416c696365013c00
Deserialized: {Name:Alice Age:30}

7.2 编解码接口

package mainimport ("bytes""encoding/gob""fmt""log"
)type Animal interface {Sound() string
}type Dog struct{ Name string }func (d Dog) Sound() string { return "Woof!" }type Cat struct{ Name string }func (c Cat) Sound() string { return "Meow!" }func interfaceExample() {// 注册具体类型gob.Register(Dog{})gob.Register(Cat{})animals := []Animal{Dog{Name: "Rex"},Cat{Name: "Whiskers"},}// 序列化var buf bytes.Bufferif err := gob.NewEncoder(&buf).Encode(animals); err != nil {log.Fatal(err)}// 反序列化var decoded []Animalif err := gob.NewDecoder(&buf).Decode(&decoded); err != nil {log.Fatal(err)}for _, a := range decoded {fmt.Printf("%T: %s says %s\n", a, a.(interface{ GetName() string }).GetName(), a.Sound())}
}// 为类型添加GetName方法以便类型断言
func (d Dog) GetName() string { return d.Name }
func (c Cat) GetName() string { return c.Name }func main() {interfaceExample()
}

运行输出:

main.Dog: Rex says Woof!
main.Cat: Whiskers says Meow!

注意,编解码接口时需要提前注册具体类型,否则会报如下错误:

gob: type not registered for interface: main.Dog

7.3 文件读写

也可以使用 gob 将序列化后的数据持久化到磁盘文件。

func fileStorage() {type Config struct {APIKey stringPort   int}cfg := Config{APIKey: "secret123", Port: 8080}// 写入文件file, err := os.Create("config.gob")if err != nil {log.Fatal(err)}defer file.Close()if err := gob.NewEncoder(file).Encode(cfg); err != nil {log.Fatal(err)}// 从文件读取file, err = os.Open("config.gob")if err != nil {log.Fatal(err)}defer file.Close()var loaded Configif err := gob.NewDecoder(file).Decode(&loaded); err != nil {log.Fatal(err)}fmt.Printf("Loaded config: %+v\n", loaded)
}

运行输出:

Loaded config: {APIKey:secret123 Port:8080}

7.4 自定义编解码方式

Gob 可以通过调用相应的方法(按优先顺序)对实现了 GobEncoder 或 encoding.BinaryMarshaler 接口的任何类型的值进行编码。

Gob 可以通过调用相应的方法(按优先顺序)对实现了 GobDecoder 或 encoding.BinaryUnmarshaler 接口的任何类型的值进行解码。

package mainimport ("bytes""encoding/gob""fmt""log"
)// Vector 类型实现了BinaryMarshal/BinaryUnmarshal 方法,这样我们就可以发送和接受 gob 类型的数据。
type Vector struct {x, y, z int
}func (v Vector) MarshalBinary() ([]byte, error) {// A simple encoding: plain text.var b bytes.Buffer_, _ = fmt.Fprintln(&b, v.x, v.y, v.z)return b.Bytes(), nil
}// UnmarshalBinary 修改接收器,所以必须要传递指针类型
func (v *Vector) UnmarshalBinary(data []byte) error {// A simple encoding: plain text.b := bytes.NewBuffer(data)_, err := fmt.Fscanln(b, &v.x, &v.y, &v.z)return err
}// 此示例传输实现自定义编码和解码方法的值。
func main() {var network bytes.Buffer// 创建一个编码器发送数据enc := gob.NewEncoder(&network)err := enc.Encode(Vector{3, 4, 5})if err != nil {log.Fatal("encode:", err)}// 创建一个解码器接收数据dec := gob.NewDecoder(&network)var v Vectorerr = dec.Decode(&v)if err != nil {log.Fatal("decode:", err)}fmt.Printf("%#v\n", v)
}

运行输出:

main.Vector{x:3, y:4, z:5}

8.优势与局限

8.1 优势

  • Go 原生高性能

gob 使用二进制格式进行编解码,性能比 JSON/XML 快 2-5 倍,数据体积小 30-70%。

  • 零配置自动化

自动处理复杂类型:

type Complex struct {Slice  []*intMap    map[string]chan struct{}Func   func() // 不支持!
}
// 自动支持:切片、指针、映射、结构体嵌套
  • 版本演进支持

对结构体新增、删除字段或顺序调整有较好的兼容性。

// V1 结构
type User struct { ID int; Name string }// V2 结构(添加字段)
type User struct { ID int; Name string; Email string }// V1 数据 → V2 解码:Email 自动置零值// 旧版本
type Config struct { Host string; Port int }// 新版本(字段调序)仍可兼容
type Config struct { Port int; Host string }
  • 循环引用处理
type Node struct {Value intNext  *Node // 循环指针
}n1 := &Node{Value: 1}
n2 := &Node{Value: 2, Next: n1}
n1.Next = n2 // 循环引用// Gob 完美序列化/反序列化

8.2 局限

  • 跨语言不兼容

gob 是 Golang 自有的二进制编解码方案,与其他语言不兼容。

  • 接口类型约束

编解码接口类型时,必须预注册。

type Encoder interface { Encode() []byte }func main() {var enc Encoder = MyEncoder{}gob.Register(MyEncoder{}) // 必须!gob.Encode(enc)
}
  • 结构演进限制

破坏性变更不可逆。

变更类型是否兼容后果
添加字段新字段为零值
删除字段忽略不存在的字段,正常解码
重命名字段数据丢失
修改字段类型解码崩溃
  • 安全风险

反序列化漏洞。

// 不可信来源的gob数据可能:
// 1. 导致内存耗尽(大容器攻击)
// 2. 触发未预期类型重建
// 3. 暴露私有字段(通过反射)
  • 性能边界

不适合性能要求的极端场景。

场景Gob 性能替代方案
100K+ QPS⚠️ 中等FlatBuffers
微秒级延迟要求❌ 不足Cap’n Proto
移动设备⚠️ 较重MessagePack

8.3 小结

优势与局限对比表:

特性优势局限性
语言支持Go原生深度优化仅限Go,无跨语言能力
开发效率零配置/自动类型处理接口需手动注册
性能比文本协议快5倍仍慢于FlatBuffers等零拷贝方案
数据兼容支持向前扩展字段删除/重命名字段破坏兼容性
类型系统完美支持Go复杂类型不支持func、chan等类型
安全无远程代码执行风险仍可能遭受资源耗尽攻击
调试便捷性数据不可读(需专用工具)JSON更易调试

9.最佳实践

类型兼容性

  • 添加新字段到结构体末尾以保持向后兼容。
  • 不要删除或重命名字段。
// 兼容性示例
type V1 struct { A int }
type V2 struct { A int B string // 新增字段在末尾
}

注册策略

  • 在 init() 函数中注册类型。

  • 跨服务使用 RegisterName 保持名称一致。

安全考虑

  • 不要反序列化不可信来源的数据。
  • 对于网络传输,添加加密/认证层。

调试技巧

// 调试编码数据
fmt.Printf("%x\n", buf.Bytes())// 或者转换为字符串查看(可能包含可读内容)
fmt.Println(buf.String())

替代方案考虑

  • 需要跨语言:使用 JSON 或 Protocol Buffers。
  • 需要人类可读:使用 JSON。
  • 极致性能:考虑 MessagePack 或 FlatBuffers。

通过这份快速指南,您应该能够立即开始使用 encoding/gob 进行高效的数据序列化操作。对于大多数 Go 服务间的通信需求,gob 提供了简单高效的解决方案。


参考文献

gob package - encoding/gob

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu ifconfig 查不到ens33网卡

BUG&#xff1a;ifconfig查看网络配置信息&#xff1a; 终端输入以下命令&#xff1a; sudo service network-manager stop sudo rm /var/lib/NetworkManager/NetworkManager.state sudo service network-manager start - service network - manager stop &#xff1a;停止…

算法-数论

C-小红的数组查询&#xff08;二&#xff09;_牛客周赛 Round 95 思路&#xff1a;不难看出a数组是有循环的 d3,p4时&#xff0c;a数组&#xff1a;1、0、3、2、1、0、3、2....... 最小循环节为4&#xff0c;即最多4种不同的数 d4,p6时&#xff0c;a数组&#xff1a;1、5、3、…

CSS中text-align: justify文本两端对齐

text-align: justify; 是 CSS 中用于控制文本对齐方式的属性值&#xff0c;它的核心作用是让文本两端对齐&#xff08;分散对齐&#xff09;&#xff0c;使段落左右边缘整齐排列。以下是详细解析&#xff1a; 作用效果 均匀分布间距 浏览器会自动调整单词/字符之间的间距&#…

WebFuture:启动数据库提示: error while loading shared libraries: libaio.so.1问题处理

问题分析 当出现./mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory这个错误时&#xff0c;这意味着 MySQL 服务器&#xff08;mysqld&#xff09;在启动过程中无法找到libaio.so.1这个共享库文件。li…

74常用控件_QSpacerItem的使用

目录 代码⽰例: 创建⼀组左右排列的按钮. Spacer 使⽤布局管理器的时候, 可能需要在控件之间, 添加⼀段空⽩. 就可以使⽤ QSpacerItem 来表⽰. 核⼼属性 属性说明width宽度height高度hData水平方向的 sizePolicy - QSizePolicy::Ignored&#xff1a;忽略控件的尺寸&#xf…

vmware 设置 dns

vmware 设置 dns 常用的 DNS&#xff08;Domain Name System&#xff09;服务器地址可以帮助你更快、更安全地解析域名。以下是一些国内外常用的公共 DNS 服务&#xff1a; 国内常用 DNS 阿里云 DNS IPv4: 223.5.5.5、223.6.6.6IPv6: 2400:3200::1、2400:3200:baba::1特点&am…

从一次日期格式踩坑经历,谈谈接口设计中的“约定大于配置“

从一次日期格式踩坑经历&#xff0c;谈谈接口设计中的"约定大于配置" 背景 最近在对接一个第三方接口时&#xff0c;遇到了一个有趣的"坑"。接口文档中要求传入一个符合 RFC3339 格式的日期时间字符串&#xff0c;格式示例为&#xff1a;2019-10-01T08:1…

高考数学易错考点01 | 临阵磨枪

文章目录 前言集合与函数不等式数列三角函数 前言 本篇内容下载于网络&#xff0c;网络上的都是以 WORD 版本呈现&#xff0c;缺字缺图很不完整&#xff0c;没法使用&#xff0c;我只是做了补充和完善。有空准备进行第二次完善&#xff0c;添加问题解释的链接。 集合与函数 …

YOLO12 改进|融入 Mamba 架构:插入视觉状态空间模块 VSS Block 的硬核升级

在医学图像分割领域&#xff0c;传统卷积神经网络&#xff08;CNNs&#xff09;受限于局部感受野&#xff0c;难以捕捉长距离依赖关系&#xff0c;而基于 Transformer 的模型因自注意力机制的二次计算复杂度&#xff0c;在处理高分辨率图像时效率低下。近年来&#xff0c;状态空…

MATLAB遍历生成20到1000个节点的无线通信网络拓扑推理数据

功能&#xff1a; 遍历生成20到1000个节点的无线通信网络拓扑推理数据&#xff0c;包括网络拓扑和每个节点发射的电磁信号&#xff0c;采样率1MHz/3000&#xff0c;信号时长5.7s&#xff0c;单帧数据波形为实采 数据生成效果&#xff1a; 拓扑及空间位置&#xff1a; 节点电磁…

oss:上传图片到阿里云403 Forbidden

访问图片出现403Forbidden问题&#xff0c;我们可以直接登录oss账号&#xff0c;查看对应权限是否开通&#xff0c;是否存在跨域问题

香橙派3B学习笔记8:snap安装管理软件包_打包俩个有调用的python文件

现在尝试一下打包多个有互相调用的 py程序&#xff1a; ssh &#xff1a; orangepi本地ip 密码 &#xff1a; orangepi 操作系统发行版&#xff1a; 基于 Ubuntu 20.04.6 LTS&#xff08;Focal Fossa&#xff09;的定制版本&#xff0c;专门为 Orange Pi 设备优化。PRETTY_NAM…

Spring Boot 中实现 HTTPS 加密通信及常见问题排查指南

Spring Boot 中实现 HTTPS 加密通信及常见问题排查指南 在金融行业安全审计中&#xff0c;未启用HTTPS的Web应用被列为高危漏洞。通过正确配置HTTPS&#xff0c;可将中间人攻击风险降低98%——本文将全面解析Spring Boot中HTTPS的实现方案与实战避坑指南。 一、HTTPS 核心原理与…

前端对WebSocket进行封装,并建立心跳监测

WebSocket的介绍&#xff1a; WebSocket 是一种在客户端和服务器之间进行全双工、双向通信的协议。它是基于 HTTP 协议&#xff0c;但通过升级&#xff08;HTTP 升级请求&#xff09;将连接转换为 WebSocket 协议&#xff0c;从而提供更高效的实时数据交换。 WebSocket 的特点…

【AI】智驾地图在不同自动驾驶等级中的作用演变

一、功能价值动态模型&#xff1a;基于自动驾驶等级的权重迁移 功能演变四阶段&#xff1a; █ 辅助阶段&#xff08;L2&#xff09;&#xff1a;单功能补足 → █ 拓展阶段&#xff08;L2 NOA&#xff09;&#xff1a;多模态增强 → █ 融合阶段&#xff08;L3&#xff09;…

Java处理字符数组转换为开始日期和结束日期

在Java中处理字符数组表示的TransactionTime&#xff08;例如["2025-06-01","2025-06-10"]&#xff09;&#xff0c;将其转换为开始时间和结束时间&#xff0c;推荐使用Java 8的java.time API&#xff08;如LocalDate&#xff09;。以下是完整代码示例&…

【笔记】Poetry虚拟环境创建示例

#工作记录 【笔记】结合 Conda任意创建和配置不同 Python 版本的双轨隔离的 Poetry 虚拟环境-CSDN博客 在PowerShell中&#xff1a; Windows PowerShell Copyright (C) Microsoft Corporation. All rights reserved.Install the latest PowerShell for new features and improv…

20242817李臻-安全文件传输系统-项目验收

安全文件传输系统项目报告 项目概述 本实验旨在设计并实现一个完整的安全文件管理系统&#xff0c;基于SM2SM3SM4混合密码体系&#xff0c;构建了一个具备高安全性的C/S架构文件传输平台。项目采用C/S架构&#xff0c;使用Qt框架开发&#xff0c;满足Linux系统调用、Socket网…

2025年- H76-Lc184--55.跳跃游戏(贪心)--Java版

1.题目描述 2.思路 只要是在最大覆盖范围覆盖了&#xff0c;就是覆盖了。 局部最优&#xff1a;每遍历一个元素取它最大的覆盖范围 全局最优&#xff1a;在这个序列里&#xff0c;可以得到最大的覆盖范围。如果覆盖范围能达到最后一个元素&#xff0c;就是全局最优 &#xff0…

05.查询表

查询表 字段显示可以使用别名: col1 AS alias1, col2 AS alias2, … WHERE子句:指明过滤条件以实现“选择"的功能: 过滤条件: 布尔型表达式算术操作符:,-,*,/,%比较操作符:,<>(相等或都为空),<>,!(非标准SQL),>,>,<,<范围查询: BETWEEN min_num …