在Go语言中,自定义固定长度数组类型是很常见的操作,比如我们可以定义一个长度为20的字节数组来表示SHA1哈希值,将其命名为Sha1Hash类型。这类类型在实际使用中经常需要判断两个值是否相等,但是很多开发者不清楚正确的比较方式,容易出现错误。

自定义固定长度数组类型的定义
首先我们来看如何定义这类自定义类型,以Sha1Hash为例,SHA1哈希结果的长度是20字节,所以对应的固定长度数组类型定义如下:
// 定义Sha1Hash类型,本质是长度为20的字节数组 type Sha1Hash [20]byte
这种类型本质是固定长度的数组,数组的元素类型是byte,长度是20,和普通的内置数组类型相比,它有了自己的类型名称,可以用于更明确的语义表达。
为什么不能直接使用比较运算符
很多开发者会尝试直接使用==运算符来比较两个Sha1Hash类型的值,比如下面的代码:
func main() {
var hash1 Sha1Hash
var hash2 Sha1Hash
// 尝试直接比较
if hash1 == hash2 {
println("两个哈希相等")
}
}
实际上,Go语言中数组是值类型,并且支持==和!=比较运算符,只要数组的元素类型是可比较的,那么整个数组就可以比较。因为byte类型是可比较的,所以长度为20的byte数组也是可比较的,上面的代码是可以正常编译运行的。但是如果我们对自定义类型做了其他处理,比如给类型添加了方法,或者将类型定义为切片,就会出现问题。
需要注意,如果自定义类型是切片类型,比如type Sha1Hash []byte,那么切片是不可比较的,直接使用==会编译报错。而我们这里讨论的是固定长度数组类型,所以基础类型是数组的情况下,直接比较是可行的,但前提是数组长度固定且元素类型可比较。
更安全通用的比较实现方案
虽然固定长度数组可以直接比较,但是为了代码的通用性,或者应对后续类型可能的调整,我们可以实现专门的比较方法,以下是几种常见的实现方式。
逐个元素对比
逐个对比数组的每个元素,这种方式逻辑清晰,性能也比较好,适合固定长度较短的数组:
// 逐个元素比较两个Sha1Hash是否相等
func (h Sha1Hash) Equal(other Sha1Hash) bool {
for i := 0; i < len(h); i++ {
if h[i] != other[i] {
return false
}
}
return true
}
转换为字节切片比较
可以将数组转换为切片,然后使用bytes包的比较函数,这种方式代码更简洁:
import "bytes"
// 转换为字节切片后比较
func (h Sha1Hash) EqualWithBytes(other Sha1Hash) bool {
return bytes.Equal(h[:], other[:])
}
使用反射比较
如果需要通用的比较逻辑,不局限于固定长度数组,可以使用反射来实现,但是反射的性能会比前两种方式差一些:
import "reflect"
// 使用反射比较两个值是否相等
func CompareWithReflect(a, b Sha1Hash) bool {
return reflect.DeepEqual(a, b)
}
不同方案的对比
我们可以通过下面的表格来看几种方案的特点:
| 比较方案 | 适用场景 | 性能 | 代码复杂度 |
|---|---|---|---|
| 直接使用==运算符 | 固定长度数组,元素可比较,不需要额外逻辑 | 最高 | 最低 |
| 逐个元素对比 | 固定长度数组,需要明确的比较逻辑 | 较高 | 中等 |
| 字节切片比较 | 数组元素为字节类型 | 较高 | 低 |
| 反射比较 | 需要通用比较逻辑,不局限于固定数组 | 较低 | 低 |
使用示例
下面是完整的使用示例,展示不同比较方式的效果:
package main
import (
"bytes"
"fmt"
"reflect"
)
// 定义Sha1Hash类型
type Sha1Hash [20]byte
// 逐个元素比较方法
func (h Sha1Hash) Equal(other Sha1Hash) bool {
for i := 0; i < len(h); i++ {
if h[i] != other[i] {
return false
}
}
return true
}
// 字节切片比较方法
func (h Sha1Hash) EqualWithBytes(other Sha1Hash) bool {
return bytes.Equal(h[:], other[:])
}
func main() {
var hash1 Sha1Hash
var hash2 Sha1Hash
// 给hash1赋值
hash1[0] = 1
// 直接比较
fmt.Println("直接==比较:", hash1 == hash2)
// 逐个元素比较
fmt.Println("逐个元素比较:", hash1.Equal(hash2))
// 字节切片比较
fmt.Println("字节切片比较:", hash1.EqualWithBytes(hash2))
// 反射比较
fmt.Println("反射比较:", reflect.DeepEqual(hash1, hash2))
}
运行上面的代码,所有比较方式都会返回false,因为hash1和hash2的内容不同,说明几种比较方式的结果是一致的。
注意事项
在实际使用中需要注意以下几点:
- 如果自定义类型的基础类型是可比较的固定长度数组,直接使用
==是可行的,但是如果后续将基础类型改为切片,代码就会编译报错,所以建议实现专门的Equal方法提升代码的可维护性。 - 反射比较的方式性能较差,如果是在高频调用的场景中,不建议使用反射方式。
- 如果数组长度很长,逐个元素对比的方式可能代码会比较冗余,这时候可以优先考虑字节切片比较的方式。