Go 语言 sort 包详解:从基础排序到自定义排序(含底层原理+零基础看懂)

Go 语言 sort 包详解:从基础排序到自定义排序(含底层原理+零基础看懂)

在 Go 开发中,排序是高频使用的基础功能,Go 标准库提供了开箱即用的 sort 包,无需依赖第三方库,就能完成基本类型切片排序自定义结构体排序逆序排序检查有序性等操作。它基于高效的排序算法实现,兼顾易用性与性能,是 Go 开发者必须掌握的核心工具。

本文将带你从零到一掌握 sort 包的所有常用用法,用最通俗的方式讲透底层原理,附可直接运行的代码示例。

一、sort 包核心特性

  1. 支持int、float64、string 三种基本类型的直接排序;
  2. 支持自定义类型/结构体 排序,只需实现 sort.Interface 接口;
  3. 支持逆序排序稳定排序(保持相等元素的原始顺序);
  4. 提供有序性检查二分查找等辅助函数;
  5. 底层使用优化后的快速排序/归并排序,性能优异。

二、基本类型切片排序(最常用)

sort 包为三种基本类型提供了直接调用的便捷函数,一行代码完成排序。

1. 整数切片排序

go 复制代码
package main

import (
	"fmt"
	"sort"
)

func main() {
	// 定义无序整数切片
	nums := []int{9, 3, 6, 1, 7, 2}
	
	// 升序排序(从小到大)
	sort.Ints(nums)
	fmt.Println("整数升序:", nums) // [1 2 3 6 7 9]

	// 逆序排序(从大到小)
	sort.Sort(sort.Reverse(sort.IntSlice(nums)))
	fmt.Println("整数降序:", nums) // [9 7 6 3 2 1]
}

2. 浮点数切片排序

go 复制代码
func main() {
	floats := []float64{3.14, 1.59, 2.65, 0.78}
	// 升序
	sort.Float64s(floats)
	fmt.Println("浮点数升序:", floats) // [0.78 1.59 2.65 3.14]
}

3. 字符串切片排序

字符串按照 Unicode 编码值 排序(数字 < 大写字母 < 小写字母)。

go 复制代码
func main() {
	strs := []string{"banana", "apple", "cherry", "123", "Dog"}
	sort.Strings(strs)
	fmt.Println("字符串排序:", strs) // [123 Dog apple banana cherry]
}

三、自定义结构体排序(核心用法)

实际开发中,我们经常需要对结构体切片按某个字段排序(比如按年龄、分数、价格排序)。

实现方式:两种方案

方案 1:实现 sort.Interface 接口(标准用法)

sort.Interface 要求实现 3 个方法:

  • Len() int:返回切片长度
  • Less(i, j int) bool:排序规则(i 位置元素是否排在 j 前面)
  • Swap(i, j int):交换两个元素

示例:对学生结构体按分数降序排序

go 复制代码
package main

import (
	"fmt"
	"sort"
)

// 定义学生结构体
type Student struct {
	Name  string
	Score int
}

// 定义切片类型,用于实现排序接口
type StudentSlice []Student

// 实现 sort.Interface 三个方法
func (s StudentSlice) Len() int           { return len(s) }
func (s StudentSlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
// 排序规则:按分数降序
func (s StudentSlice) Less(i, j int) bool { return s[i].Score > s[j].Score }

func main() {
	students := []Student{
		{"张三", 85},
		{"李四", 92},
		{"王五", 78},
	}

	// 排序
	sort.Sort(StudentSlice(students))
	fmt.Println("按分数降序排序:")
	for _, s := range students {
		fmt.Printf("%s: %d分\n", s.Name, s.Score)
	}
}
方案 2:使用 sort.Slice(极简写法,推荐)

Go 1.8+ 提供了 sort.Slice 函数,无需实现接口,直接传入排序规则,代码更简洁:

go 复制代码
func main() {
	students := []Student{
		{"张三", 85},
		{"李四", 92},
		{"王五", 78},
	}

	// 一行代码排序:按分数升序
	sort.Slice(students, func(i, j int) bool {
		return students[i].Score < students[j].Score
	})

	fmt.Println("按分数升序排序:")
	for _, s := range students {
		fmt.Printf("%s: %d分\n", s.Name, s.Score)
	}
}

四、零基础必看:sort 底层原理 + 为什么实现3个方法就能排序?

很多新手都会疑惑:为什么我只写了 Len、Less、Swap 三个方法,sort 就能帮我排序?

我用最通俗、零基础能懂的方式,把底层逻辑讲透。

1. 核心类比:sort 包 = 全自动排序机器人

你可以把 Go 的 sort 包想象成一个只会指挥排序流程的机器人

  • 不知道你要排什么数据(学生、商品、水果都可以);
  • 不会自己判断大小
  • 不会自己交换数据
  • 它只负责「循环比较、指挥排序」。

这个机器人只需要你告诉它 3 件事,就能完成排序:

  1. 一共有多少个数据?→ 对应 Len() 方法
  2. 两个数据谁排在前面?→ 对应 Less() 方法
  3. 两个数据怎么交换位置?→ 对应 Swap() 方法

2. 3个方法的真实作用(直白解释)

go 复制代码
// 1. 机器人问:有多少个数据要排?你回答长度
func (s StudentSlice) Len() int           { return len(s) }

// 2. 机器人说:把第i个和第j个数据换位置!你执行交换
func (s StudentSlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

// 3. 机器人问:第i个数据应该排在第j个前面吗?你定规则
func (s StudentSlice) Less(i, j int) bool { return s[i].Score > s[j].Score }

重点拆解 Swap 交换:
s[i], s[j] = s[j], s[i] 是 Go 特色语法:先把右边两个值全部取出来,再一次性赋值给左边,直接完成两个元素的位置互换,不需要额外定义临时变量,一行就能交换成功。

3. sort 源码底层极简逻辑(看懂就通透)

Go sort 包的底层源码,根本不关心你排的是什么数据,它只做三件事:

go 复制代码
// 简化后的 sort 核心源码
func Sort(data Interface) {
    // 1. 调用你写的 Len(),知道数据长度
    n := data.Len()

    // 2. 循环比较(底层是优化后的快速排序)
    for i := 1; i < n; i++ {
        // 3. 调用你写的 Less(),判断谁在前谁在后
        for j := i; j > 0 && data.Less(j, j-1); j-- {
            // 4. 需要换位置时,调用你写的 Swap()
            data.Swap(j, j-1)
        }
    }
}

4. 一句话总结底层逻辑

  • 你负责:告诉排序机器人「长度、比较规则、交换方式」
  • sort 包负责:执行排序流程
    两者配合,就能对任何数据完成排序!

五、进阶用法:稳定排序 + 有序检查

1. 稳定排序

sort.Stable:排序后,相等元素保持原始顺序(适用于有优先级的场景)。

go 复制代码
// 对整数切片做稳定升序排序
sort.Stable(sort.IntSlice(nums))

2. 检查切片是否有序

go 复制代码
nums := []int{1,2,3,6}
// 检查整数是否升序
fmt.Println(sort.IntsAreSorted(nums)) // true

// 检查字符串是否有序
fmt.Println(sort.StringsAreSorted(strs))

六、完整示例:综合排序场景

go 复制代码
package main

import (
	"fmt"
	"sort"
)

type Product struct {
	Name  string
	Price float64
	Stock int
}

func main() {
	products := []Product{
		{"手机", 5999.0, 100},
		{"电脑", 8999.0, 50},
		{"耳机", 399.0, 200},
	}

	// 按价格升序排序
	sort.Slice(products, func(i, j int) bool {
		return products[i].Price < products[j].Price
	})
	fmt.Println("=== 按价格排序 ===")
	for _, p := range products {
		fmt.Printf("%s: %.1f元\n", p.Name, p.Price)
	}
}

七、总结

  1. 基础排序 :直接用 sort.Ints()sort.Float64s()sort.Strings(),一行搞定;
  2. 结构体排序 :标准写法实现 Len、Less、Swap 三个方法,极简写法用 sort.Slice
  3. 底层核心:sort 是排序机器人,你提供规则,它执行流程,配合即可排序;
  4. 交换原理s[i], s[j] = s[j], s[i] 先取值再赋值,直接完成位置互换;
  5. 进阶能力:支持逆序、稳定排序、有序性检查,满足所有开发场景。

Go 的 sort 包设计简洁、功能强大,完全覆盖日常开发的所有排序需求,是 Go 语言中最实用的标准库之一。

相关推荐
叁散2 小时前
ESP32 LCD1602显示实验报告
算法
过期动态2 小时前
【LeetCode 热题 100】盛最多水的容器
java·数据结构·spring boot·算法·leetcode·spring cloud·职场和发展
凌波粒2 小时前
LeetCode--700.二叉搜索树中的搜索(二叉树)
算法·leetcode·职场和发展
君为先-bey3 小时前
LeMiCa——基于扩散模型的高效视频生成的词典序最小化路径缓存
python·算法·机器学习·扩散模型
洛水水3 小时前
【力扣100题】58.轮转数组
算法·leetcode
资深流水灯工程师3 小时前
LMS 最小均方算法在 DSP 上的 C 语言实现
算法
风筝在晴天搁浅3 小时前
阿里 LeetCode 876.链表的中间节点
算法·leetcode·链表
玖釉-3 小时前
二叉树展开为链表:从先序遍历到原地指针重排
c++·windows·算法·leetcode·链表
05候补工程师3 小时前
【408考研·数据结构专题】二叉树、树与森林、线索树及哈夫曼树核心考点与秒杀技巧深度总结
数据结构·经验分享·笔记·考研·算法