Go语言实践Go

Go语言文件处理(下)

2019-05-07  本文已影响0人  帅气的昵称都有人用了

读写文件

我们在读写文件中最为常见的操作有:复制文件,编辑,跳转,替换等

复制文件

先来看复制文件是如何操作的:

package main

import (
  "io"
  "os"
  "log"

)

var (
  newFile *os.File
  fileInfo os.FileInfo
  err error
  path = "test/test2/"
  fileName = "demo"
  filePath = path + fileName
)

func main() {
  originalFile, err := os.Open(filePath)
  if err != nil {
    log.Fatal(err)
  }
  defer originalFile.Close()
  // 创建新的文件作为目标文件
  newFile, err := os.Create(filePath + "_copy")
  if err != nil {
    log.Fatal(err)
  }
  defer newFile.Close()
  // 从源文件中复制字节到目标文件
  bytesWritten, err := io.Copy(newFile, originalFile)
  if err != nil {
    log.Fatal(err)
  }
  log.Printf("The file has been copyed, size is %d bytes.", bytesWritten)
  // 将文件内容flush到硬盘中
  err = newFile.Sync()
  if err != nil {
    log.Fatal(err)
  }
}
/** The result is:
2019/05/07 13:46:12 open test/test2/demo: no such file or directory
 */

因为我这里并没有这个文件,因此返回的是no such file or directory的这行错误。
在我们复制操作中,我们需要注意几个地方:

Create函数执行后需要Close()函数来关闭并回收资源
调用io包中的复制函数之后文件内容并没有真正保存在文件中,使用Sync()函数同步之后才真正的保存到硬盘中。

跳转函数

跳转到文件指定位置是复杂的文件操作基础,在Go语言中,我们使用的是Seek()函数:

package main

import (
  "fmt"
  "log"
  "os"
)

var (
  newFile *os.File
  err error
  path = "test/test2/"
  fileName = "demo"
  filePath = path + fileName
)

func main() {
  file, _ := os.Open(filePath)
  defer file.Close()

  // 偏离位置,可以是正数也可以是负数
  var offset int64 = 5
  // 用来计算offset的初始位置
  // 0 = 文件开始位置
  // 1 = 当前位置
  // 2 = 文件结尾处
  whence := 0

  newPosition, err := file.Seek(offset, whence)
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println("Move to the position 5: ", newPosition)
  // 从当前位置回退2字节
  newPosition, err = file.Seek(-2, 1)
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println("The new position is: ", newPosition)
  // 使用下面的方法得到当前位置
  newPosition, err = file.Seek(0, 1)
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println("The current position is: ", newPosition)
  // 转到文件开始处
  newPosition, err = file.Seek(0, 0)
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println("Move to the starting position: ", newPosition)
}

我们可以通过几个简单的例子发现,Seek()和我们鼠标的光标非常类似,就是可以移动到任意位置,然后进行相应的操作。

写入函数

写入函数有大致三种:

// 写入byte类型的信息到文件
func (file *FIle) Write(b []byte) (n int, err Error) 
// 在制定位置开始写入byte类型的信息
func (file *File) WriteAt(b []byte, off int64) (n int, err Error) 
// 写入string信息到文件
func (fiel *FIle) WriteString(s string) (ret int, err Error)

还是来看一下实例:

package main

import (
  "log"
  "os"
)

var (
  newFile *os.File
  err error
  path = "test/test2/"
  fileName = "demo"
  filePath = path + fileName
)

func main() {
  file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
  if err != nil {
    log.Fatal(err)
  }
  defer file.Close()
  file.Write([]byte("Write byte. \r\n"))
  file.WriteString("Write string. \r\n")
  file, err = os.Open(filePath)
  if err != nil {
    log.Fatal(err)
  }
  buf := make([]byte, 1024)
  for {
    n, _ := file.Read(buf)
    if 0 == n {
      break
    }
    os.Stdout.Write(buf[:n])
  }
  file.Close()
}

权限控制

查看文件、文件夹的权限实例如下:

package main

import (
  "log"
  "os"
)

var (
  newFile *os.File
  err error
  path = "test/test2/"
  fileName = "demo"
  filePath = path + fileName
)

func main() {
  file, err := os.OpenFile(filePath, os.O_WRONLY, 0666)
  if err != nil && os.IsPermission(err) {
    log.Fatal("Wrong: Cannot write.")
  } else if os.IsNotExist(err) {
    log.Fatal("File not exist.")
  } else {
    log.Fatal(err)
  }
  file.Close()
  file, err = os.OpenFile(filePath, os.O_RDONLY, 0666)
  if err != nil && os.IsPermission(err) {
    log.Fatal("Wrong: Cannot read.")
  } else if os.IsNotExist(err) {
    log.Fatal("File not exist.")
  } else {
    log.Fatal(err)
  }
  file.Close()
}

在Go语言中自然也可以改变文件的权限,初此之外还可以改变文件的拥有者以及时间戳等内容。修改命令与Linux中的chmodchown命令类似。

func main() {
  err := os.Chmod(filePath, 0777)
  if err != nil {
    log.Println(err)
  }
  
  err = os.Chown(filePath, os.Getuid(), os.Getgid())
  if err != nil {
    log.Fatal(err)
  }
  
  // 查看文件信息
  fileInfo, err = os.Stat(filePath)
  if err != nil {
    if os.IsNotExist(err) {
      log.Fatal("File not exist.")
    }
    log.Fatal(err)
  }
  fmt.Println("The time is: ", fileInfo.ModTime())
  // 改变时间戳
  twoDaysFromNow := time.Now().Add(48 * time.Hour)
  lastAccessTime := twoDaysFromNow
  lastModifyTIme := twoDaysFromNow
  err = os.Chtimes(filePath, lastAccessTime, lastModifyTIme)
  if err != nil {
    log.Println(err)
  }
}

文件链接

我们先来区分一下硬链接和软连接的区别:
若一个 inode 号对应多个文件名,则称这些文件为硬链接。硬链接可以通过命令linkln进行创建。由于硬链接是有着相同 inode 号仅文件名不同的文件,因此硬链接存在以下几点特性:

文件有相同的 inodedata block
只能对已存在的文件进行创建;
不能交叉文件系统进行硬链接的创建;
不能对目录进行创建,只可对文件创建;
删除一个硬链接文件并不影响其他有相同 inode 号的文件。

而软链接与硬链接不同,若文件用户数据块中存放的内容是另一文件的路径名的指向,则该文件就是软连接。
而且软链接相比起硬链接会更简单一些:

软链接有自己的文件属性及权限等;
可对不存在的文件或目录创建软链接;
软链接可交叉文件系统;
软链接可对文件或目录创建;
创建软链接时,链接计数 i_nlink 不会增加;
删除软链接并不影响被指向的文件,但若被指向的原文件被删除,则相关软连接被称为死链接(即 dangling link,若被指向路径文件被重新创建,死链接可恢复为正常的软链接)。

func main() {
  hardlink := filePath + "hl"
  err := os.Link(filePath, hardlink)
  if err != nil {
     log.Fatal(err)
  }
  fmt.Println("Create a hardlink.")
  
  softlink := filePath + "sl"
  err = os.Symlink(fileName, softlink)
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println("Create a softlink.")
  
  /** Lstat 返回一个文件信息,但当文件是一个软链接时,它返回软链接的信息,而不是引用的文件的信息*/
  // Symlink 在 Windows 中不工作
  fileInfo, err := os.Lstat(softlink)
  if err != nil {
    log.Fatal(err)
  }
  fmt.Printf("Link information: %+v", fileInfo)
  
  // 改变软链接的拥有者不会影响原始文件
  err = os.Lchown(softlink, os.Getuid(), os.Getgid())
  if err != nil {
    log.Fatal(err)
  }
}

其中的os.Lstat()的函数名中可以看出这是一个关于软链接的函数,它是用于查看软链接自己的属性,使用os.Stat()函数会获取软链接指向的原文件信息。
我们看一下os.Link()os.Symlink()的函数定义:

func Symlink(oldname, newname string) error
func Link(oldname, newname string) error

我们发现两个函数的参数是完全一样的,但是,我们要注意,在Symlink()函数中,我们传递的是fileName,而在Link()函数中,我们传递的是filePath
这是因为硬链接是从根目录开始创建的,而软链接则是根据文件的相对位置而创建的。

上一篇下一篇

猜你喜欢

热点阅读