在Golang的标准库中,sort包提供了完整的排序功能,不仅可以处理int、string等内置类型的默认排序,还支持开发者通过实现特定接口来自定义排序规则,满足切片、结构体集合等不同场景的排序需求。sort包的核心设计基于接口抽象,只要类型实现了sort.Interface接口定义的三个方法,就可以使用sort包提供的排序函数完成排序操作。

sort包的核心接口
sort.Interface接口是自定义排序的基础,该接口定义了三个必须实现的方法,具体如下:
- Len() int:返回集合的长度,也就是需要排序的元素总数
- Less(i, j int) bool:判断索引i的元素是否应该排在索引j的元素前面,返回true表示i位置的元素更小,排在前面
- Swap(i, j int):交换索引i和索引j位置的两个元素
只要自定义类型实现了这三个方法,就可以调用sort.Sort()函数完成排序,同时sort包也提供了sort.Stable()函数用于稳定排序,稳定排序会保持相等元素的原有相对顺序。
切片自定义排序实现
对于内置类型的切片,sort包已经提供了默认的排序函数,比如sort.Ints()用于int切片升序排序,sort.Strings()用于字符串切片升序排序。如果需要自定义切片的排序规则,比如int切片降序排序,就需要自定义类型实现sort.Interface接口。
int切片降序排序示例
下面的代码实现了int切片的降序排序:
package main
import (
"fmt"
"sort"
)
// 定义自定义类型,基于[]int
type IntSliceDesc []int
// 实现Len方法,返回切片长度
func (s IntSliceDesc) Len() int {
return len(s)
}
// 实现Less方法,i位置元素大于j位置元素时返回true,实现降序
func (s IntSliceDesc) Less(i, j int) bool {
return s[i] > s[j]
}
// 实现Swap方法,交换两个位置的元素
func (s IntSliceDesc) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func main() {
nums := []int{3, 1, 4, 1, 5, 9, 2, 6}
// 将[]int转换为自定义类型
sortNums := IntSliceDesc(nums)
// 调用sort.Sort进行排序
sort.Sort(sortNums)
fmt.Println("降序排序后的切片:", sortNums)
}
运行上述代码,输出结果为:降序排序后的切片: [9 6 5 4 3 2 1 1],可以看到切片已经按照从大到小的顺序完成排序。
字符串切片按长度排序示例
如果需要对字符串切片按照字符串长度进行排序,同样可以通过实现接口完成:
package main
import (
"fmt"
"sort"
)
// 定义自定义类型,基于[]string
type StringSliceByLength []string
func (s StringSliceByLength) Len() int {
return len(s)
}
// 按字符串长度升序排序,长度相同则按字典序排序
func (s StringSliceByLength) Less(i, j int) bool {
if len(s[i]) != len(s[j]) {
return len(s[i]) < len(s[j])
}
return s[i] < s[j]
}
func (s StringSliceByLength) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func main() {
strs := []string{"go", "golang", "python", "java", "c", "javascript"}
sortStrs := StringSliceByLength(strs)
sort.Sort(sortStrs)
fmt.Println("按长度排序后的字符串切片:", sortStrs)
}
运行结果为:按长度排序后的字符串切片: [c go java python golang javascript],符合按长度升序的排序预期。
结构体自定义排序实现
在实际开发中,经常需要对结构体切片进行排序,比如按照结构体的某个字段排序,或者按照多个字段的优先级排序,这时候同样需要实现sort.Interface接口,在Less方法中定义排序规则即可。
单字段排序示例
假设我们有一个学生结构体,包含姓名和分数两个字段,需要按照分数从高到低排序:
package main
import (
"fmt"
"sort"
)
// 定义学生结构体
type Student struct {
Name string
Score int
}
// 定义学生切片类型
type StudentSlice []Student
func (s StudentSlice) Len() int {
return len(s)
}
// 按分数降序排序,分数相同则按姓名字典序升序
func (s StudentSlice) Less(i, j int) bool {
if s[i].Score != s[j].Score {
return s[i].Score > s[j].Score
}
return s[i].Name < s[j].Name
}
func (s StudentSlice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func main() {
students := []Student{
{Name: "张三", Score: 85},
{Name: "李四", Score: 92},
{Name: "王五", Score: 85},
{Name: "赵六", Score: 78},
}
sortStudents := StudentSlice(students)
sort.Sort(sortStudents)
fmt.Println("按分数排序后的学生列表:")
for _, stu := range sortStudents {
fmt.Printf("姓名:%s,分数:%dn", stu.Name, stu.Score)
}
}
运行结果为:
按分数排序后的学生列表:
姓名:李四,分数:92
姓名:张三,分数:85
姓名:王五,分数:85
姓名:赵六,分数:78
可以看到分数高的学生排在前面,分数相同的张三和王五按照姓名字典序排列,符合我们的排序规则。
多字段优先级排序示例
如果需要按照多个字段的优先级排序,比如先按年龄升序,年龄相同再按工资降序,只需要在Less方法中先判断第一个字段,第一个字段相等时再判断第二个字段即可:
package main
import (
"fmt"
"sort"
)
// 定义员工结构体
type Employee struct {
Name string
Age int
Salary int
}
// 定义员工切片类型
type EmployeeSlice []Employee
func (e EmployeeSlice) Len() int {
return len(e)
}
// 先按年龄升序,年龄相同按工资降序
func (e EmployeeSlice) Less(i, j int) bool {
if e[i].Age != e[j].Age {
return e[i].Age < e[j].Age
}
return e[i].Salary > e[j].Salary
}
func (e EmployeeSlice) Swap(i, j int) {
e[i], e[j] = e[j], e[i]
}
func main() {
employees := []Employee{
{Name: "员工A", Age: 25, Salary: 8000},
{Name: "员工B", Age: 30, Salary: 12000},
{Name: "员工C", Age: 25, Salary: 10000},
{Name: "员工D", Age: 30, Salary: 10000},
}
sortEmployees := EmployeeSlice(employees)
sort.Sort(sortEmployees)
fmt.Println("按年龄和工资排序后的员工列表:")
for _, emp := range sortEmployees {
fmt.Printf("姓名:%s,年龄:%d,工资:%dn", emp.Name, emp.Age, emp.Salary)
}
}
运行结果为:
按年龄和工资排序后的员工列表:
姓名:员工C,年龄:25,工资:10000
姓名:员工A,年龄:25,工资:8000
姓名:员工B,年龄:30,工资:12000
姓名:员工D,年龄:30,工资:10000
可以看到年龄25的员工排在前面,同年龄的员工C工资更高排在前面,年龄30的员工同理,符合多字段排序的预期。
简化自定义排序的方法
如果觉得每次实现三个方法比较繁琐,sort包还提供了sort.Slice()函数,该函数只需要传入一个比较函数就可以完成排序,不需要自定义类型实现接口,使用起来更加简洁。比如上面的学生排序用sort.Slice()实现如下:
package main
import (
"fmt"
"sort"
)
type Student struct {
Name string
Score int
}
func main() {
students := []Student{
{Name: "张三", Score: 85},
{Name: "李四", Score: 92},
{Name: "王五", Score: 85},
{Name: "赵六", Score: 78},
}
// 使用sort.Slice,传入比较函数,i位置元素应该排在j前面时返回true
sort.Slice(students, func(i, j int) bool {
if students[i].Score != students[j].Score {
return students[i].Score > students[j].Score
}
return students[i].Name < students[j].Name
})
fmt.Println("按分数排序后的学生列表:")
for _, stu := range students {
fmt.Printf("姓名:%s,分数:%dn", stu.Name, stu.Score)
}
}
该方式和之前实现接口的效果完全一致,但是代码更加简洁,适合临时需要自定义排序的场景。如果需要复用排序逻辑,还是建议实现sort.Interface接口,方便多处调用。