go语言path/filepath包之Walk源码解析

go语言的path/filepath包提供了很多兼容各个操作系统的文件路径实用操作方法,今天只来看看Walk方法:

  • Walk(root stirng, walkFn WalkFunc) error
    该方法主要用于递归遍历目录:

walk方法会遍历root下的所有文件(包含root)并对每一个目录和文件都调用walkFunc方法。在访问文件和目录时发生的错误都会通过error参数传递给WalkFunc方法。文件是按照词法顺序进行遍历的,这个通常让输出更漂亮,但是也会导致处理非常大的目录时效率会降低。另外,Walk函数不会遍历符号链接。

方法名 定义
WalkFunc type WalkFunc func(path string, info os.FileInfo, err error) error
Walk func Walk(root string, walkFn WalkFunc) error
  • type WalkFunc func(path string, info os.FileInfo, err error) 函数
    根据文件信息path和info进行自定义操作:

WalkFunc是一个方法类型,Walk函数在遍历文件或者目录时调用。调用时将参数传递给path,将Walk函数中的root作为前缀。将root + 文件名或者目录名作为path传递给WalkFunc函数。例如在"dir"目录下遍历到"a"文件,则path="dir/a";Info是path所指向文件的文件信息。如果在遍历过程中出现了问题,传入参数err会描述这个问题。WalkFunc函数可以处理这个问题,Walk将不会再深入该目录。如果函数会返回一个错误,Walk函数会终止执行;只有一个例外,我们也通常用这个来跳过某些目录。当WalkFunc的返回值是filepaht.SkipDir时,Walk将会跳过这个目录,照常执行下一个文件。

  • Walk(root string, walkFn WalkFunc) 函数
//这里的参数root可以是文件名也可以是目录名;walkFn是自定义的函数
func Walk(root string, walkFn WalkFunc) error {

    //获取root的描述信息
    info, err := os.Lstat(root)
    if err != nil {
        //如果获取描述信息发生错误,返回err由定义的walkFn函数处理
        err = walkFn(root, nil, err)
    } else {
        //调用walk(root, info, walkFn)函数进行递归遍历root
        err = walk(root, info, walkFn)
    }
    if err == SkipDir {
        return nil
    }
    return err
}
  • func walk(path string, info os.FileInfo, walkFn WalkFunc) error 函数进行递归遍历
func walk(path string, info os.FileInfo, walkFn WalkFunc) error {

    //调用定义的walkFn自定义函数处理
    err := walkFn(path, info, nil)
    if err != nil {
        //返回错误,且该目录可以跳过
        if info.IsDir() && err == SkipDir {
            return nil
        }
        return err
    }

    //如果是文件,则遍历下一个
    if !info.IsDir() {
        return nil
    }

    //读取该path下的所有目录和文件
    names, err := readDirNames(path)
    if err != nil {
        //发生错误,调用自定义函数处理
        return walkFn(path, info, err)
    }

    //遍历文件和目录列表
    for _, name := range names {
        //路径path/name
        filename := Join(path, name)
        //获取该文件或者目录信息
        fileInfo, err := lstat(filename)
        if err != nil {
            //发生错误,调用自定义函数处理
            if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir {
                return err
            }
        } else {
            //这里递归调用,获取root下各级文件和目录信息,在自定义函数walkFn里做处理
            err = walk(filename, fileInfo, walkFn)
            if err != nil {
                //遍历文件发生错误或者目录发生错误且不能跳过,则返回err
                if !fileInfo.IsDir() || err != SkipDir {
                    return err
                }
            }
        }
    }
    return nil
}

实例演练:

将一个目录或者文件压缩为zip包:

func main() {
    //oldFileName可以是文件或者目录
    oldFileName := "root.log"

    currentTime := time.Now()

    //获取s
    mSecond := fmt.Sprintf("%03d", currentTime.Nanosecond() / 1e6)

    //zip文件名
    zipFileName := strings.Split(oldFileName, ".")[0] + "_" + currentTime.Format("20060102150405") + mSecond + ".zip"

    //压缩文件
    zipFile(oldFileName, zipFileName)
}

func zipFile(source, target string) error{

    //创建目标zip文件
    zipFile , err := os.Create(target)

    if err != nil {
        fmt.Println(err)
        return err
    }

    //关闭文件
    defer zipFile.Close()

    //创建一个写zip的writer
    archive := zip.NewWriter(zipFile)

    defer archive.Close()

    return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {

        if err != nil {
            return err
        }

        //将文件或者目录信息转换为zip格式的文件信息
        header, err := zip.FileInfoHeader(info)

        if err != nil{
            return err
        }

        if !info.IsDir() {
            // 确定采用的压缩算法(这个是内建注册的deflate)
            header.Method = zip.Deflate
        }

        //
        header.SetModTime(time.Unix(info.ModTime().Unix(), 0))

        //文件或者目录名
        header.Name = path

        //创建在zip内的文件或者目录
        writer, err := archive.CreateHeader(header)

        if err != nil{
            return err
        }

        //如果是目录,只需创建无需其他操作
        if info.IsDir() {
            return nil
        }

        //打开需要压缩的文件
        file, err := os.Open(path)

        if err != nil{
            return err
        }

        defer file.Close()

        //将待压缩文件拷贝给zip内文件
        _, err = io.Copy(writer, file)

        return err

    })
}

这种压缩文件的方式避免了zip包在linux上解压以后文件的修改时间为1979年12月31日的问题

linux上解压后的时间和原文件时间一样

package main

import (
    "time"
    "fmt"
    "strings"
    "os"
    "archive/zip"
    "io/ioutil"
    "path/filepath"
    "io"
)

func main() {

    //oldFileName可以是文件或者目录
    oldFileName := "root.log"

    compressZip(oldFileName)
}

func compressZip(oldFileName string) string{

    fd, err := ioutil.ReadFile(oldFileName)

    if err != nil {
        fmt.Println("ReadFile ", oldFileName, "is error ", err)
        return ""
    }

    currentTime := time.Now()

    mSecond := fmt.Sprintf("%03d", currentTime.Nanosecond() / 1e6)

    zipFileName := strings.Split(oldFileName, ".")[0] + "_" + currentTime.Format("20060102150405") + mSecond + ".zip"

    fw, err := os.OpenFile(zipFileName, os.O_RDWR | os.O_CREATE | os.O_TRUNC, 400)

    if err != nil {
        fmt.Println("OpenFile ", zipFileName, "is error ", err)
        return ""
    }

    defer fw.Close()

    w := zip.NewWriter(fw)

    defer w.Close()

    f, err := w.Create(filepath.Base(oldFileName))

    if err != nil {
        fmt.Println("Create ", oldFileName, "is error ", err)
        return ""
    }

    _, err = f.Write(fd)

    if err != nil {
        fmt.Println("Write newFileName is error ", err)
        return ""
    }

    return zipFileName
}
  • 以上这种方式压缩的zip包在linux上解压后时间为1979年12月31日。不利于生产环境问题定位。
解压后时间不对
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 大街对面的楼上,镜面的玻璃幕墙反射着耀眼的光芒,照在一张巨大的招贴上。一款游戏的广告,一个小女孩大声的喊:胜利吧,...
    80天旅行阅读 183评论 0 0
  • 一部好的悬疑片不只是完美的逻辑推理,而是看完后有深刻的反思,最近很多悬疑片披着悬疑惊悚的外衣,专注揭露人性。 这部...
    方圆丶几何阅读 1,049评论 6 6
  • 独在异乡为异客, 每逢佳节倍思亲。 小时候,老妈喜欢唠叨我,那时候的我其实比较听话的了,只是老妈脾气有点急躁,看不...
    好一个北方姑娘阅读 208评论 0 0
  • 如何做好企业人力资源管理费用预算 我有更好答案 最佳答案 1.强化人力资源费用预算与控制管理意识。首先,要加大宣传...
    BDBS阅读 1,026评论 0 6