filetype(https://github.com/h2non/filetype)是一个 Go 语言的第三方库,可以根据文件的魔数(magic numbers)签名来推断文件的类型和 MIME 类型。它支持多种常见的文件类型,包括图片、视频、音频、文档、压缩包等。它还提供了一些便捷的函数和类型匹配器,可以方便地对文件进行分类和筛选。它的特点有:
- 支持多种文件类型,提供文件扩展名和正确的 MIME 类型
- 可以根据扩展名或 MIME 类型来发现文件类型
- 可以根据类别(图片、视频、音频等)来发现文件类型
- 可以添加自定义的新类型和匹配器
- 简单而语义化的 API
- 即使处理大文件也非常快速
- 只需要前 262 字节表示最大的文件头,所以你可以只传递一个切片
- 无依赖(只有 Go 代码,不需要 C 编译)
- 跨平台的文件识别
实现原理分析
filetype 的实现原理是基于文件的魔数(magic numbers)签名来进行类型检测的。魔数是一种特定的字节序列,通常出现在文件的开头,用来标识文件的格式或内容。不同的文件类型有不同的魔数,比如 JPEG 文件的魔数是 FF D8 FF
,PNG 文件的魔数是 89 50 4E 47
,ZIP 文件的魔数是 50 4B 03 04
等。通过读取文件的前几个字节,就可以根据魔数来判断文件的类型。
这个库的核心原理还是基于魔数来推断文件类型,但是它没有直接定义一个 Matcher
接口,而是定义了一个函数类型 type Matcher func([]byte) bool
。然后,它为每种支持的文件类型定义了一个 Matcher
函数,并将它们注册到一个全局的 matchers.Map
中。
当用户调用 filetype.Match(buf)
函数时,这个函数会遍历所有注册的 Matcher
函数,并调用它们,如果有一个找到了匹配的文件类型,返回对应的 Type
结构体和一个空错误。如果没有找到匹配的文件类型,就返回 Unknown
类型和一个错误信息。
这个库还允许用户自定义新的文件类型和匹配器,并将它们添加到全局的 Types
和 matchers.Map
中。
filetype 还提供了一些辅助函数,比如 IsImage(buf)
、IsVideo(buf)
、IsAudio(buf)
等,用来判断给定的字节切片是否属于某个类别。它们都是基于 Matchers
变量来实现的,只是过滤了一些不属于该类别的类型。例如,IsImage(buf)
函数会遍历所有属于图片类别(MIME 类型以 image/
开头)的匹配器,并返回是否有任何一个匹配器返回 true。
filetype 还提供了一些其他函数,比如 IsSupported(ext)
、IsMIMESupported(mime)
、GetType(ext)
、GetMIME(ext)
等,用来根据扩展名或 MIME 类型来查询或发现文件类型。它们都是基于 types.go
文件中定义的一个全局变量 Types
来实现的,它是一个映射表,存储了所有已注册的类型和对应的扩展名和 MIME 类型。例如,IsSupported(ext)
函数会在 Types
中查找是否有对应扩展名的类型存在,并返回 true 或 false。
使用示例
下面给出一些使用 filetype 库的具体例子:
简单地检测文件类型
代码语言:javascript复制package main
import (
"fmt"
"io/ioutil"
"github.com/h2non/filetype"
)
func main() {
// 读取一个文件
buf, _ := ioutil.ReadFile("sample.jpg")
// 匹配文件类型
kind, _ := filetype.Match(buf)
if kind == filetype.Unknown {
fmt.Println("Unknown file type")
return
}
fmt.Printf("File type: %s. MIME: %sn", kind.Extension, kind.MIME.Value)
}
输出:
代码语言:javascript复制File type: jpg. MIME: image/jpeg
检查文件类别
代码语言:javascript复制package main
import (
"fmt"
"io/ioutil"
"github.com/h2non/filetype"
)
func main() {
// 读取一个文件
buf, _ := ioutil.ReadFile("sample.jpg")
// 检查是否是图片
if filetype.IsImage(buf) {
fmt.Println("File is an image")
} else {
fmt.Println("Not an image")
}
}
输出:
代码语言:javascript复制File is an image
查询支持的类型
代码语言:javascript复制package main
import (
"fmt"
"github.com/h2non/filetype"
)
func main() {
// 检查是否支持某个扩展名
if filetype.IsSupported("jpg") {
fmt.Println("Extension supported")
} else {
fmt.Println("Extension not supported")
}
// 检查是否支持某个 MIME 类型
if filetype.IsMIMESupported("image/jpeg") {
fmt.Println("MIME type supported")
} else {
fmt.Println("MIME type not supported")
}
}
输出:
代码语言:javascript复制Extension supported
MIME type supported
添加自定义类型和匹配器
代码语言:javascript复制package main
import (
"fmt"
"github.com/h2non/filetype"
)
// 定义一个新的类型
var fooType = filetype.NewType("foo", "foo/foo")
// 定义一个新的匹配器
func fooMatcher(buf []byte) bool {
return len(buf) > 1 && buf[0] == 0x01 && buf[1] == 0x02
}
func main() {
// 注册新的匹配器和类型
filetype.AddMatcher(fooType, fooMatcher)
// 检查是否支持新的扩展名
if filetype.IsSupported("foo") {
fmt.Println("New supported type: foo")
}
// 检查是否支持新的 MIME 类型
if filetype.IsMIMESupported("foo/foo") {
fmt.Println("New supported MIME type: foo/foo")
}
// 尝试匹配新的类型
fooFile := []byte{0x01, 0x02}
kind, _ := filetype.Match(fooFile)
if kind == filetype.Unknown {
fmt.Println("Unknown file type")
} else {
fmt.Printf("File type matched: %sn", kind.Extension)
}
}
输出:
代码语言:javascript复制New supported type: foo
New supported MIME type: foo/foo
File type matched: foo
优缺点分析,和标准库 http.DetectContentType 的对比
filetype 库相比于标准库中提供的 http.DetectContentType 函数有以下优缺点:
优点:
- 支持更多种类和更细分的文件类型,比如视频、音频、文档等。
- 提供更准确和更规范化的 MIME 类型,比如
image/jpeg
而不是image/jpg
。 - 提供更多便捷和灵活的函数和接口,比如根据类别、扩展名或 MIME 类型来检测或发现文件类型。
- 提供可插拔性,可以添加自定义的类型和匹配器。
- 提供更简单而语义化的 API。
缺点:
- 需要额外引入这个第三方库,并保持更新。
- 可能存在一些未知或不常见格式的检测不准确或不支持的情况(但比标准库的好很多)
性能分析
为了评估 filetype 库的性能,我们可以使用 Go 的标准测试工具来进行基准测试(benchmark)。我编写一个简单的测试文件,比如 filetype_test.go
,其中包含以下代码:
package main
import (
"io/ioutil"
"testing"
"github.com/h2non/filetype"
)
// 读取一个文件并返回字节切片
func readFile(filename string) []byte {
buf, _ := ioutil.ReadFile(filename)
return buf
}
// 测试文件类型检测的性能
func BenchmarkMatch(b *testing.B) {
// 读取一个图片文件
buf := readFile("sample.jpg")
// 重置计时器
b.ResetTimer()
// 循环执行 b.N 次
for i := 0; i < b.N; i {
// 调用 filetype.Match 函数
filetype.Match(buf)
}
}
// 测试标准库函数的性能
func BenchmarkDetect(b *testing.B) {
// 读取一个图片文件
buf := readFile("sample.jpg")
// 重置计时器
b.ResetTimer()
// 循环执行 b.N 次
for i := 0; i < b.N; i {
// 调用 http.DetectContentType 函数
http.DetectContentType(buf)
}
}
然后我们运行 go test -bench=. -benchmem
来执行基准测试,得到以下输出:
goos: darwin
goarch: arm64
pkg: test/filetype
BenchmarkMatch-8 268744659 4.381 ns/op 0 B/op 0 allocs/op
BenchmarkDetect-8 8401593 142.1 ns/op 0 B/op 0 allocs/op
PASS
ok test/filetype 3.478s
从输出中我们可以看到,filetype 的性能远高于标准库。(可能和标准库读取前 512 字节有关,而 filetype 只需要读取前 262 个字节)
综上所述,我们可以得出以下结论:
- filetype 库是一个快速、无依赖的 Go 语言文件类型检测库,它支持多种常见的文件类型,并提供了一些便捷和灵活的函数和接口。
- filetype 库的实现原理是基于文件的魔数(magic numbers)签名来进行类型检测的,它只需要前 262 字节表示最大的文件头,所以它可以只传递一个切片,不需要全部内容。
- filetype 库的性能非常高,即使处理大文件也不会有明显的延迟。