倔强如我 發表於 2020-2-20 11:12:00

go 文件与目录操作

<p>&nbsp;</p>
<h1>文件打开与关闭</h1>
<h3><strong>文件打开</strong></h3>
<p><strong>原始的文件打开函数:</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func OpenFile(name string, flag int, perm FileMode) (*File, error)</pre>
</div>
<p>name:绝对路径或相对路径(相对于进程当前工作目录)<br>flag:指定文件的访问模式,在os中这些参数被定义为常量</p>
<div class="cnblogs_code"><img id="code_img_closed_f065b509-6a74-4e01-9432-5bd3cf930f4b" class="code_img_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code_img_opened_f065b509-6a74-4e01-9432-5bd3cf930f4b" class="code_img_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="">
<div id="cnblogs_code_open_f065b509-6a74-4e01-9432-5bd3cf930f4b" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">const</span><span style="color: rgba(0, 0, 0, 1)"> (
O_RDONLY </span><span style="color: rgba(0, 0, 255, 1)">int</span> = syscall.O_RDONLY <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 只读模式打开文件</span>
O_WRONLY <span style="color: rgba(0, 0, 255, 1)">int</span> = syscall.O_WRONLY <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 只写模式打开文件</span>
O_RDWR <span style="color: rgba(0, 0, 255, 1)">int</span> = syscall.O_RDWR <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 读写模式打开文件</span>
O_APPEND <span style="color: rgba(0, 0, 255, 1)">int</span> = syscall.O_APPEND <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 写操作时将数据附加到文件尾部</span>
O_CREATE <span style="color: rgba(0, 0, 255, 1)">int</span> = syscall.O_CREAT <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果不存在将创建一个新文件</span>
O_EXCL <span style="color: rgba(0, 0, 255, 1)">int</span> = syscall.O_EXCL <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 和 O_CREATE 配合使用,文件必须不存在</span>
O_SYNC <span style="color: rgba(0, 0, 255, 1)">int</span> = syscall.O_SYNC <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 打开文件用于同步 I/O</span>
O_TRUNC <span style="color: rgba(0, 0, 255, 1)">int</span> = syscall.O_TRUNC <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果可能,打开时清空文件</span>
)</pre>
</div>
<span class="cnblogs_code_collapse">flag参数</span></div>
<p>其中,O_RDONLY、O_WRONLY、O_RDWR 应该只指定一个,剩下的通过 | 操作符来指定。该函数内部会给 flags 加上 syscall.O_CLOEXEC,在 fork 子进程时会关闭通过 OpenFile 打开的文件,即子进程不会重用该文件描述符。</p>
<p>perm:指定了文件的模式和权限位,类型是 os.FileMode,文件模式位常量定义在 os</p>
<div class="cnblogs_code"><img id="code_img_closed_86cca1c6-ace1-42cf-b012-7401c77f9807" class="code_img_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code_img_opened_86cca1c6-ace1-42cf-b012-7401c77f9807" class="code_img_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="">
<div id="cnblogs_code_open_86cca1c6-ace1-42cf-b012-7401c77f9807" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">const</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 单字符是被 String 方法用于格式化的属性缩写。</span>
ModeDir FileMode = <span style="color: rgba(128, 0, 128, 1)">1</span> &lt;&lt; (<span style="color: rgba(128, 0, 128, 1)">32</span> - <span style="color: rgba(128, 0, 128, 1)">1</span> - iota) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> d: 目录</span>
ModeAppend <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> a: 只能写入,且只能写入到末尾</span>
ModeExclusive <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> l: 用于执行</span>
ModeTemporary <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> T: 临时文件(非备份文件)</span>
ModeSymlink <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> L: 符号链接(不是快捷方式文件)</span>
ModeDevice <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> D: 设备</span>
ModeNamedPipe <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> p: 命名管道(FIFO)</span>
ModeSocket <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> S: Unix 域 socket</span>
ModeSetuid <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> u: 表示文件具有其创建者用户 id 权限</span>
ModeSetgid <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> g: 表示文件具有其创建者组 id 的权限</span>
ModeCharDevice <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> c: 字符设备,需已设置 ModeDevice</span>
ModeSticky <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> t: 只有 root/ 创建者能删除 / 移动文件

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 覆盖所有类型位(用于通过 &amp; 获取类型位),对普通文件,所有这些位都不应被设置</span>
ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket |<span style="color: rgba(0, 0, 0, 1)"> ModeDevice
ModePerm FileMode </span>= <span style="color: rgba(128, 0, 128, 1)">0777</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 覆盖所有 Unix 权限位(用于通过 &amp; 获取类型位)</span>
)</pre>
</div>
<span class="cnblogs_code_collapse">perm参数</span></div>
<p>以上常量在所有操作系统都有相同的含义(可用时),因此文件的信息可以在不同的操作系统之间安全的移植。不是所有的位都能用于所有的系统,唯一共有的是用于表示目录的 ModeDir 位。</p>
<p>以上这些被定义的位是 FileMode 最重要的位。另外 9 个位(权限位)为标准 Unix rwxrwxrwx 权限(所有人都可读、写、运行)。</p>
<p>FileMode 还定义了几个方法,用于判断文件类型的 IsDir() 和 IsRegular(),用于获取权限的 Perm()。</p>
<p>返回的 error,具体实现是 *os.PathError,它会记录具体操作、文件路径和错误原因。</p>
<p><strong>一般打开文件使用下面这两个函数:</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func Open(name string) (*File, error) {
    return OpenFile(name, O_RDONLY, 0)
}

func Create(name string) (*File, error) {
    return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}</pre>
</div>
<h3>文件关闭</h3>
<p><code>close()</code>&nbsp;系统调用关闭一个打开的文件描述符,并将其释放回调用进程,供该进程继续使用。当进程终止时,将自动关闭其已打开的所有文件描述符。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func (f *File) Close() error</pre>
</div>
<p><code>os.File.Close()</code>&nbsp;是对&nbsp;<code>close()</code>&nbsp;的封装。我们应该养成关闭不需要的文件的良好编程习惯。文件描述符是资源,Go 的 gc 是针对内存的,并不会自动回收资源,如果不关闭文件描述符,长期运行的服务可能会把文件描述符耗尽。</p>
<p>所以,通常的写法如下:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">file, err := os.Open("/tmp/studygolang.txt")
if err != nil {
    // 错误处理,一般会阻止程序往下执行
    return
}
defer file.Close()</pre>
</div>
<p>关于返回值&nbsp;<code>error</code></p>
<p>以下两种情况会导致&nbsp;<code>Close</code>&nbsp;返回错误:</p>
<pre><code>1. 关闭一个未打开的文件;
2. 两次关闭同一个文件;
</code></pre>
<p>通常,我们不会去检查&nbsp;<code>Close</code>&nbsp;的错误。</p>
<h3>文件打开与关闭示例</h3>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">package main


import (
    "bufio"
    "fmt"
    "io"
    "io/ioutil"
    "os"
)

func main00(){
    //打开文件与1关闭文件
    file,err :=os.Open("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt")
    if err == nil {
      fmt.Printf("文件打开成功")
      fmt.Println(file)
    } else{
      fmt.Printf("文件打开失败,err:",err)
      return
    }
    defer func(){
      file.Close()
      fmt.Printf("关闭文件")
    }()
    //
}</pre>
</div>
<h3>判断文件是否存在</h3>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;"> //使用os包状态检测结合os.IsNotExist(err)判断文件是否存在
    //获取指定文件的信息
    fileInfo,err := os.Stat("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt")
    if err != nil {
      fmt.Println("err=",err)
      //校验错误是否为【文件不存在错误】
      if os.IsNotExist(err){
            fmt.Println("文件不存在!")
      }
      return
    }else{
      fmt.Println("文件存在!")
      fmt.Println(fileInfo)
    }</pre>
</div>
<h1>文件读取</h1>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func (f *File) Read(b []byte) (n int, err error)</pre>
</div>
<p>Read 方法从 f 中读取最多 len(b) 字节数据并写入 b。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取 0 个字节且返回值 err 为 io.EOF。</p>
<p>从方法声明可以知道,File 实现了 io.Reader 接口。</p>
<p>Read 对应的系统调用是 read。</p>
<p>对比下 ReadAt 方法:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func (f *File) ReadAt(b []byte, off int64) (n int, err error)</pre>
</div>
<p>ReadAt 从指定的位置(相对于文件开始位置)读取长度为 len(b) 个字节数据并写入 b。它返回读取的字节数和可能遇到的任何错误。当 n&lt;len(b) 时,本方法总是会返回错误;如果是因为到达文件结尾,返回值 err 会是 io.EOF。它对应的系统调用是 pread。</p>
<p>Read 和 ReadAt 的区别:前者从文件当前偏移量处读,且会改变文件当前的偏移量;而后者从 off 指定的位置开始读,且不会改变文件当前偏移量。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func main01(){
    //以只读方式打开一个文件,创建其带缓冲的读取器,逐行读取到末尾
    //4=readable,2=writeable,1=executeable,6=4+2
    file,err :=os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",os.O_RDONLY,0666)
    //判断读入是否成功
    if err == nil {
      fmt.Printf("文件打开成功")
      //打印内容
      fmt.Println(file)
    } else{
      fmt.Printf("文件打开失败,err:",err)
      return
    }
    //延迟关闭文件:在函数return前执行的程序
    defer func(){
      file.Close()
      fmt.Printf("关闭文件")
    }()
    //创建文件的读取器
    Reader := bufio.NewReader(file)
    for{
      //一次读取一行
      data,err := Reader.ReadString('\n')
      if err == nil{
            fmt.Println(data)
      }else{
            //到达文件结尾,跳出循环
            if err == io.EOF{
                fmt.Println("已经文件结尾")
                break
            }else{
                //读取异常,打印异常并结束
                fmt.Println("读取失败,err:",err)
                return
            }
      }
    }
}  </pre>
</div>
<h1>文件写入</h1>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func (f *File) Write(b []byte) (n int, err error)</pre>
</div>
<p>Write 向文件中写入 len(b) 字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值 n!=len(b),本方法会返回一个非 nil 的错误。</p>
<p>从方法声明可以知道,File 实现了 io.Writer 接口。</p>
<p>Write 对应的系统调用是 write。</p>
<p>Write 与 WriteAt 的区别同 Read 与 ReadAt 的区别一样。为了方便,还提供了 WriteString 方法,它实际是对 Write 的封装。</p>
<p>注意:Write 调用成功并不能保证数据已经写入磁盘,因为内核会缓存磁盘的 I/O 操作。如果希望立刻将数据写入磁盘(一般场景不建议这么做,因为会影响性能),有两种办法:</p>
<p>1. 打开文件时指定 `os.O_SYNC`;<br>2. 调用 `File.Sync()` 方法。<br>说明:File.Sync() 底层调用的是 fsync 系统调用,这会将数据和元数据都刷到磁盘;如果只想刷数据到磁盘(比如,文件大小没变,只是变了文件数据),需要自己封装,调用 fdatasync 系统调用。(syscall.Fdatasync)</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func mian04(){
    //以【创写追加】或【创写覆盖】方式打开一个文件,缓冲写入几行数据,倒干缓冲区
    //打开文件,模式:不存在就创建+只写+追加,生成的文件的权限666
    file,err :=os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",os.O_CREATE|os.O_WRONLY|os.O_APPEND,0666)
    //判断读入是否成功
    if err != nil {
      fmt.Printf("文件打开失败,err:",err)
      return
    }
    //延迟关闭文件:在函数return前执行的程序
    defer func(){
      file.Close()
      fmt.Printf("关闭文件")
    }()
    //分批写入数据
    writer := bufio.NewWriter(file)
    writer.WriteString("hello")
    writer.WriteString("2019")
    writer.WriteString("。。。。")
    //写入一个字符
    writer.WriteRune('你')
    writer.WriteRune('好')
    //写一位数据
    writer.WriteByte(123)
    //写一个字节数据,范围:0-255
    writer.Write([]byte{123,222,21})
    //把缓冲区清空,立即将最后的数据写入文件
    writer.Flush()
}
func main05(){
    //反引号代表保留原始排版的字符串
    data := `测试文档、1、2、3、4、5。`
    fmt.Printf("data type=%T,value=%v\n",data,data)
    //将数据字符串转换为原始字节切片
    dataBytes := []byte(data)
    fmt.Printf("data type=%T,value=%v\n",dataBytes,dataBytes)
    //向指定文件写入上面的字节数据
    err := ioutil.WriteFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",dataBytes,0666)
    if err != nil {
      fmt.Println("写入错误")
    }else{
      fmt.Println("写入成功")
    }
}</pre>
</div>
<h1>文件拷贝</h1>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">//使用ioutil包做一个傻瓜式拷贝
//使用io.Copy进行文件拷贝
//使用缓冲1K的缓冲区配合缓冲读写器进行图片拷贝
func main07(){
    //使用ioutil包做一个傻瓜式拷贝
    data,_:= ioutil.ReadFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt")
    err := ioutil.WriteFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test1.txt",data,0666)
    if err != nil{
      fmt.Println("拷贝失败",err)
    }else{
      fmt.Println("拷贝成功")
    }
    fmt.Println("执行完成")
}

func main08(){
    //使用io.Copy进行文件拷贝

    //打开拷贝源文件,模式为只读模式
    srcFile,err := os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",os.O_RDONLY,0666)

    //打开目标文件,模式为创建|写入
    dstFile,err := os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test1.txt",os.O_WRONLY |os.O_CREATE,0666)

    //执行源文件到目标文件的拷贝,writen为拷贝字节数
    writen,err := io.Copy(dstFile,srcFile)

    if err == nil {
      fmt.Println("拷贝成功,字节数=",writen)
    }else{
      fmt.Println("拷贝失败,err=",err)
    }
}

func main09(){
    //使用缓冲1K的缓冲区配合缓冲读写器进行图片拷贝

    //打开源文件
    srcFile,_ := os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",os.O_RDONLY,0666)

    //打开目标文件
    dstFile,_ := os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test1.txt",os.O_WRONLY |os.O_CREATE,0666)

    defer func(){
      srcFile.Close()
      dstFile.Close()
      fmt.Println("文件全部关闭!")
    }()
    //创建源文件的缓冲读取器
    reder := bufio.NewReader(srcFile)
    //创建目标文件的缓冲写出器
    writer := bufio.NewWriter(dstFile)

    //创建缓冲区
    buffer := make([]byte,1024)

    //将数据存入,直到io.EOF
    for {
      //循环的读取数据到缓冲区,然后把缓冲区的数据写入到目标文件
      _,err :=reder.Read(buffer)
      if err !=nil{
            if err == io.EOF{
                fmt.Println("源文件读取完毕!")
                break
            }else{
                fmt.Println("读取文件发生错误,err=",err)
            }
      }else{
            _,err := writer.Write(buffer)
            if err !=nil {
                fmt.Println("写出错误,err=",err)
                return
            }
      }
      }
}</pre>
</div>
<h1 id="改变文件偏移量:seek">改变文件偏移量:Seek</h1>
<p>对于每个打开的文件,系统内核会记录其文件偏移量,有时也将文件偏移量称为读写偏移量或指针。文件偏移量是指执行下一个&nbsp;<code>Read</code>&nbsp;或&nbsp;<code>Write</code>&nbsp;操作的文件其实位置,会以相对于文件头部起始点的文件当前位置来表示。文件第一个字节的偏移量为 0。</p>
<p>文件打开时,会将文件偏移量设置为指向文件开始,以后每次&nbsp;<code>Read</code>&nbsp;或&nbsp;<code>Write</code>&nbsp;调用将自动对其进行调整,以指向已读或已写数据后的下一个字节。因此,连续的&nbsp;<code>Read</code>&nbsp;和&nbsp;<code>Write</code>&nbsp;调用将按顺序递进,对文件进行操作。</p>
<p>而&nbsp;<code>Seek</code>&nbsp;可以调整文件偏移量。方法定义如下:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func (f *File) Seek(offset int64, whence int) (ret int64, err error)</pre>
</div>
<p><code>Seek</code>&nbsp;设置下一次读 / 写的位置。offset 为相对偏移量,而 whence 决定相对位置:0 为相对文件开头,1 为相对当前位置,2 为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。使用中,whence 应该使用&nbsp;<code>os</code>&nbsp;包中的常量:<code>SEEK_SET</code>、<code>SEEK_CUR</code>&nbsp;和&nbsp;<code>SEEK_END</code>。</p>
<p>注意:<code>Seek</code>&nbsp;只是调整内核中与文件描述符相关的文件偏移量记录,并没有引起对任何物理设备的访问。</p>
<p>一些&nbsp;<code>Seek</code>&nbsp;的使用例子(file 为打开的文件对象),注释说明了将文件偏移量移动到的具体位置:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">file.Seek(0, os.SEEK_SET)    // 文件开始处
file.Seek(0, SEEK_END)      // 文件结尾处的下一个字节
file.Seek(-1, SEEK_END)      // 文件最后一个字节
file.Seek(-10, SEEK_CUR)   // 当前位置前 10 个字节
file.Seek(1000, SEEK_END)    // 文件结尾处的下 1001 个字节</pre>
</div>
<p>最后一个例子在文件中会产生“空洞”。</p>
<p><code>Seek</code>&nbsp;对应系统调用&nbsp;<code>lseek</code>。该系统调用并不适用于所有类型,不允许将&nbsp;<code>lseek</code>&nbsp;应用于管道、FIFO、socket 或 终端。</p>
<p>&nbsp;</p>
<h1>文件目录操作</h1>
<div>
<p>创建名称为name的目录,权限设置是perm,例如0777</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func Mkdir(name string, perm FileMode) error</pre>
</div>
<p>根据path创建多级子目录,例如astaxie/test1/test2。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;"> func MkdirAll(path string, perm FileMode) error</pre>
</div>
<p>删除名称为name的目录,当目录下有文件或者其他目录时会出错</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func Remove(name string) error</pre>
</div>
<p>根据path删除多级子目录,如果path是单个名称,那么该目录下的子目录全部删除。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func RemoveAll(path string) error </pre>
</div>
<p>更改文件名,<code>Rename</code>&nbsp;修改一个文件的名字或移动一个文件。如果&nbsp;<code>newpath</code>&nbsp;已经存在,则替换它。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func Rename(oldpath, newpath string) error
</pre>
</div>
<p>  </p>
<p>例子:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">package main

import (
    "fmt"
    "os"
)

func main() {
    os.Mkdir("test", 0777)
    os.MkdirAll("test/test1/test2", 0777)
    err := os.Remove("test")
    if err != nil {
      fmt.Println(err)
    }
    os.RemoveAll("test")
}
</pre>
</div>
<p>  </p>
<p>&nbsp;</p>
<p>笔记来源:</p>
<p>https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter06/06.1.html</p>
</div><br><br>
来源:https://www.cnblogs.com/-wenli/p/12334844.html
頁: [1]
查看完整版本: go 文件与目录操作