Go语言文件处理(下)
读写文件
我们在读写文件中最为常见的操作有:复制文件,编辑,跳转,替换等。
复制文件
先来看复制文件是如何操作的:
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
中的chmod
和chown
命令类似。
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
号对应多个文件名,则称这些文件为硬链接。硬链接可以通过命令link
或ln
进行创建。由于硬链接是有着相同 inode 号仅文件名不同的文件,因此硬链接存在以下几点特性:
文件有相同的
inode
及data 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
。
这是因为硬链接是从根目录开始创建的,而软链接则是根据文件的相对位置而创建的。