Go语言关于路径的操作有path
和filepath
两个包。他们都可以处理路径相关操作,但你是否疑惑过这两个包该用哪个?它们有什么区别呢?小小的脑袋充满大大的疑惑呢。
![疑惑.jpg](https://i-blog.csdnimg.cn/blog_migrate/bfcb35e37a4e439a07c373e525f4fa5d.jpeg)
它们的区别其实看看源码就明白了,path.Dir
源码在path/path.go
,filepath.Dir
源码在path/filepath/path.go
。
在path/path.go
文件开头有这样一段注释:
// Package path implements utility routines for manipulating slash-separated
// paths.
//
// The path package should only be used for paths separated by forward
// slashes, such as the paths in URLs. This package does not deal with
// Windows paths with drive letters or backslashes; to manipulate
// operating system paths, use the path/filepath package.
翻译过来就是说:
path
包提供了斜线分隔路径的常规操作。但是,它只能用来处理正斜杠(
/
)分隔的路径。不能处理带有驱动符或反斜杠(\
)的Windows路径,要操作不同操作系统的路径,请使用path/filepath
包。
如何快速区分正斜杠和反斜杠呢?
\|/
:往左偏的是反斜杠,往右偏的正斜杠。左反右正,右撇子表示很赞。
如果对 驱动符 感到疑惑,请看后面。
盲猜在path/filepath/path.go
文件开头也有一段注释:
// Package filepath implements utility routines for manipulating filename paths
// in a way compatible with the target operating system-defined file paths.
//
// The filepath package uses either forward slashes or backslashes,
// depending on the operating system. To process paths such as URLs
// that always use forward slashes regardless of the operating
// system, see the path package.
翻译过来是:
filepath
包提供了兼容操作系统的路径操作。它使用正斜杠还是反斜杠取决于操作系统。处理总是使用正斜杠的路径(如URL),请参见
path
包。
看完注释其实已经很清楚了,在处理路径时,应尽量使用filepath
包,处理url
时,使用path
包。
为了呼应标题,我们来看看Dir
函数的一些细节。
首先是path
包的实现:
func Dir(path string) string {
dir, _ := Split(path)
return Clean(dir)
}
func Split(path string) (dir, file string) {
i := strings.LastIndex(path, "/")
return path[:i+1], path[i+1:]
}
path
包的Dir
函数简单粗暴,直接以最后一个/
切分路径,然后清理。关于Clean
函数,可以看看它的注释:
// Clean returns the shortest path name equivalent to path
// by purely lexical processing. It applies the following rules
// iteratively until no further processing can be done:
//
// 1. Replace multiple slashes with a single slash.
// 2. Eliminate each . path name element (the current directory).
// 3. Eliminate each inner .. path name element (the parent directory)
// along with the non-.. element that precedes it.
// 4. Eliminate .. elements that begin a rooted path:
// that is, replace "/.." by "/" at the beginning of a path.
//
// The returned path ends in a slash only if it is the root "/".
//
// If the result of this process is an empty string, Clean
// returns the string ".".
//
// See also Rob Pike, ``Lexical File Names in Plan 9 or
// Getting Dot-Dot Right,''
// https://9p.io/sys/doc/lexnames.html
Clean
函数通过词法处理得到一个和原路径等价的最短路径。规则如下:
- 多个连续斜杠变成一个斜杠
- 消除
.
路径- 消除内部的
..
以及(紧挨)..
前面的(一个)路径- 消除根路径(
/
)后的..
,即(开头的)/..
替换成/
只有根路径才会以斜杠结尾,换句话说
Clean
返回的路径会去掉末尾的斜杠。
如果参数是空字符串,Clean
函数返回.
再来看看filepath
包中Dir
的实现:
func Dir(path string) string {
vol := VolumeName(path)
i := len(path) - 1 //记录最后一个分隔符下标
for i >= len(vol) && !os.IsPathSeparator(path[i]) { //寻找最后一个分隔符
i--
}
dir := Clean(path[len(vol) : i+1]) //路径清理
if dir == "." && len(vol) > 2 {
// must be UNC
return vol
}
return vol + dir
}
这段逻辑也很简单:
- 首先获取路径的卷名
- 在卷名之后的路径中找到最后一个分隔符
- 对卷名和最后一个分隔符之间的路径做清理
- 拼接卷名和清理后的路径
此处Clean
函数要比path
包的稍微复杂,单基本规则还是一样的。
这里提到的卷名,包括前面的驱动符,是否让你感到疑惑呢?它们其实是同一个东西,看看VolumeName
函数的实现就会明白了。
// VolumeName returns leading volume name.
// Given "C:\foo\bar" it returns "C:" on Windows.
// Given "\\host\share\foo" it returns "\\host\share".
// On other platforms it returns "".
func VolumeName(path string) string {
return path[:volumeNameLen(path)]
}
// volumeNameLen returns length of the leading volume name on Windows.
// It returns 0 elsewhere.
func volumeNameLen(path string) int {
if len(path) < 2 {
return 0
}
// with drive letter
c := path[0]
if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
return 2
}
// is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
!isSlash(path[2]) && path[2] != '.' {
// first, leading `\\` and next shouldn't be `\`. its server name.
for n := 3; n < l-1; n++ {
// second, next '\' shouldn't be repeated.
if isSlash(path[n]) {
n++
// third, following something characters. its share name.
if !isSlash(path[n]) {
if path[n] == '.' {
break
}
for ; n < l; n++ {
if isSlash(path[n]) {
break
}
}
return n
}
break
}
}
}
return 0
}
在Windows上其实就是你的盘符,比如C:
。VolumeName
通过卷名长度来切片路径得到卷名,卷名长度通过volumeNameLen
函数获取,在Windows上就是2。
这里涉及到一个东西叫做通用命名规范,英文名叫UNC (Universal Naming Convention)。这是一个通用的目录命名规范,我们没搜到规范原文,基本上你见到的目录都是遵循这个规范的,感兴趣的可以去搜索,各显神通吧。
![](https://i-blog.csdnimg.cn/blog_migrate/7c94b8dd83463e8a0fddfb58cc06e044.jpeg)