GO 从 0 到 1 系列:11 文件与 IO 操作

[TOC]

文件与 IO 操作

基本操作(不带缓冲 IO):读、写

标准输入、输出、错误

带缓冲的 IO

文件,对我们并不陌生,文件是数据源(保存数据的地方)的一种,比如大家经常使用的word文档,txt文件,excel文件…都是文件。文件最主要的作用就是保存数据,它既可以保存一张图片,也可以保持视频,声音…

程序的路径:

文件类型:

我们在处理文本文件的时候可以用 string 去处理,也可以用 byte 去处理

但是在处理二进制文件的时候一定要用[] byte去处理

1 文件读取&文件的写入

在文件程序中是以流的形式来操作的。


流:数据在数据源(文件)和程序(内存)之间经历的路径,经常操作的有两种流如下

输入流:数据从数据源(文件)到程序(内存)的路径(也就是当我们读取文件的时候,输入流往往对应的是读的操作

输出流:数据从程序(内存)到数据源(文件)的路径(比如我们把内存中的数据重新写回文件,这个流就是从内存流向文件中,输出流往往对应的是写文件

1.1 文件的打开

我们在对文件操作的时候需要使用到 os

打开文件使用到 os.Open(name string) (*File,error)

package main

import (
    "fmt"
    "os"
)

func main() {
    filePath := "test.txt"            // 该文件必须是执行二进制程序所在的的当前目录下
    file, err := os.Open(filePath)
    fmt.Println(file, err)
}

输出

[18:01:45 root@go filereader]#go run main.go 
<nil> open test.txt: no such file or directory

# 输出 没有找到 test.txt 这个文件 , 因为当前目录中就没有 test.txt 文件

然后我在当前目录创建一个 test.txt 文件


再次输出就不会报错了

[18:06:04 root@go filereader]#go run main.go 
&{0xc0000bc120} <nil>

1.2 不带缓冲对文件的读取

先查看 os.File 这个结构体有哪些方法

对文件的话就使用 func (f *File) Read(b []byte) (n int, err error) 方法

  1. 我们先在当前目录下创建一个 test.txt 文件

[18:39:21 root@go filereader]#touch test.txt

# 写入内容 12345678
[10:33:41 root@go filereader]#echo "123456" > test.txt 
  1. 然后在编写读出程序

    package main
    
    import (
    "fmt"
    "os"
    )
    
    func main() {
    // 打开文件操作
    filePath := "test.txt"
    file, err := os.Open(filePath)
    if err != nil {
        fmt.Println(err)
    }
    // 创建一个 content 的 byte 切片,长度为 3 ,也就是说一次能够读取三个字节
    content := make([]byte, 3)
    
       // 把文件的内容处理之后放到了 content 切片中了
    // 第一次读取
    fmt.Println(file.Read(content))
    fmt.Printf("read1:%q\n", content)
    
    // 第二次读取
    fmt.Println(file.Read(content))
    fmt.Printf("read2:%q\n", content)
    
    // 第三次读取
    fmt.Println(file.Read(content))
    fmt.Printf("read3:%q\n", content)
    
    // 第四次读取
    fmt.Println(file.Read(content))
    fmt.Printf("read4:%q\n", content)
    }
    

    输出:

    [18:57:48 root@go filereader]#go run main.go 
    3 
    read1:"123"
    3 
    read2:"456"
    2 
    read3:"786"
    0 EOF
    read3:"786"
    
    # 我们发现 error 的值变为了 EOF 而不在是 nil
    # EOF 用来标识文件读取结束了

    ![image-20210619184052730](1 文件与 IO 操作.assets\image-20210619184052730.png)

他在每次读取的时候都会替换掉前面的字节内容,在第三次读取的时候只读取了两个字节,所以最后一个数就是 6 因为没有替换

1.2.1 一次性读取全部内容

由上面的操作范例,我们可以看到读取文件其实每次都是一个重复循环的过程

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    // 打开文件操作
    filePath := "test.txt"
    file, err := os.Open(filePath)
    if err != nil {
        fmt.Println(err)
        return
    }

    // 最后关闭文件句柄,以防止内存溢出,因为防止程序中间发生错误
    defer file.Close()

    // 字节长度为 3 
    content := make([]byte, 3)

    // 通过 for{} 读取文件内容
    for {
        n, err := file.Read(content)

        // 因为文件读取完毕之后输出 err = EOF 而不再是 nil 值就退出当前 for 循环
        if err != nil {
            if err != io.EOF { // EOF => 标识文件读取结束了,非 EOF 结束兵 break 退出 for 循环
                fmt.Println(err)
            }
            break
        }

        // 输出 content[:n] 中的内容,每次输出到切片的最后,(文件内容存在 content 切片中)
        // 因为使用 切片 获取的时候底层是共享同一个内存空间的
        fmt.Println(string(content[:n]))
    }
}

输出:

[19:56:18 root@go filereader]#go run main.go 
123
456
78

文件中有中文的处理输出:

通过输出,依旧能处理文件内容为中文的文件

因为中文编码为的 Unicode 码,而且正好一个 Unicode 码占 3 个字节

所以为啥 content := make([]byte, 3) 的长度要为 3 了,因为读取中文的长度

[20:07:38 root@go filereader]#go run main.go 
一
二
三
四
五
六
七
八
九

如果我们需要用对 string 的方式进行处理,或者说对文本的方式进行处理,我们用的最多的是带缓冲的 io

1.3 创建文件和不带缓存写入内容操作

![image-20210619211041887](1 文件与 IO 操作.assets\image-20210619211041887.png)

使用 os.Create 方法创建一个相对路径的 text.txt 文件

范例:

package main

import (
    "fmt"
    "os"
)

func main() {
    // 创建文件路径
    path := "text.txt"

    // 使用 os.Create() 方法
    file, err := os.Create(path)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer file.Close()

    // 通过 Write() 方法写入数据
    file.Write([]byte("[]byte切片\n"))

    // 输出写入的长度和错误信息
    fmt.Println(file.Write([]byte("12345\n")))

    // 通过 WriteString() 写入的是 string 类型
    file.WriteString("我是 writeString 写入")
}

写入内容:

1.3.4 通过命令行输入到文件中

标准输入 => 命令行的文件 os.Stdin
标准输出 => 命令行的文件 os.Stdout
标准错误输出 => 命令行的文件 os.Stderr

package main

import (
    "fmt"
    "os"
)

func main() {
    // 创建文件
    file, err := os.Create("text.txt")
    if err != nil {
        fmt.Println(err)
        return
    }

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

    // 输入内容
    name := ""
    fmt.Print("请输入需要写入到文件中的内容:")
    fmt.Scanln(&name)

    // 写入内容到文件
    file.WriteString(name)

}

输出:

[21:30:51 root@go stdio]#go run main.go 
请输入需要写入到文件中的内容:在黑夜中用光明照亮永恒

1.3.5 在程序中格式化输入内容到文件

func fmt.Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)

// 可以看到在 Fprintf 函数中第一个参数是写入的变量,第二个参数为写入的解析,第三个参数为写入的内容
// Fprintf 根据格式说明符格式化并写入 w 。它返回写入的字节数和遇到的任何写入错误。

在程序代码中将写入的内容通过格式化输入到文件中

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Create("text.txt")
    if err != nil {
        fmt.Println(err)
        return
    }

    defer file.Close()

    name := "ll"
    fmt.Fprintf(file, "i am %s", name)

}

没有输出内容

写入到文件后的内容

2 OpenFile 和 文件位置操作


在写文件的时候要使用到 OpenFile 函数,并且这里面有三个参数,然后整个 openfile 函数会返回两个返回参数,一个是文件指针,一个是 err ,也就是说打开文件错误就会返回 err

第一个参数 name :

要把这个内容写入到那个文件里面去

第二个参数 flag :

是打开文件的模式,多个模式之间可以通过 | 组合使用

const (
    O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
    O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
    O_RDWR   int = syscall.O_RDWR   // 读写模式打开文件
    O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部
    O_CREATE int = syscall.O_CREAT  // 如果不存在将创建一个新文件
    O_EXCL   int = syscall.O_EXCL   // 和O_CREATE配合使用,文件必须不存在
    O_SYNC   int = syscall.O_SYNC   // 打开文件用于同步I/O
    O_TRUNC  int = syscall.O_TRUNC  // 如果可能,打开时清空文件
)

第三个参数 FileMode:

打开文件权限

type FileMode(在 windows 下无效,只能在 unix 和 linux 下有用)

type FileMode uint32

FileMode代表文件的模式和权限位。这些字位在所有的操作系统都有相同的含义,因此文件的信息可以在不同的操作系统之间安全的移植。不是所有的位都能用于所有的系统,唯一共有的是用于表示目录的ModeDir位。

const (
    // 单字符是被String方法用于格式化的属性缩写。
    ModeDir        FileMode = 1 << (32 - 1 - iota) // d: 目录
    ModeAppend                                     // a: 只能写入,且只能写入到末尾
    ModeExclusive                                  // l: 用于执行
    ModeTemporary                                  // T: 临时文件(非备份文件)
    ModeSymlink                                    // L: 符号链接(不是快捷方式文件)
    ModeDevice                                     // D: 设备
    ModeNamedPipe                                  // p: 命名管道(FIFO)
    ModeSocket                                     // S: Unix域socket
    ModeSetuid                                     // u: 表示文件具有其创建者用户id权限
    ModeSetgid                                     // g: 表示文件具有其创建者组id的权限
    ModeCharDevice                                 // c: 字符设备,需已设置ModeDevice
    ModeSticky                                     // t: 只有root/创建者能删除/移动文件
    // 覆盖所有类型位(用于通过&获取类型位),对普通文件,所有这些位都不应被设置
    ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice
    ModePerm FileMode = 0777                        // 覆盖所有Unix权限位(用于通过&获取类型位)
)

这些被定义的位是 FileMode 最重要的位。另外 9 个不重要的位为标准 Unix rwxrwxrwx 权限(任何人都可读、写、运行)。这些(重要)位的值应被视为公共API的一部分,可能会用于线路协议或硬盘标识:它们不能被修改,但可以添加新的位。

2.1 如果文件不存在就创建,并追加内容

package main

import (
    "fmt"
    "os"
    "time"
)

func main() {

    // 多个模式之间可以通过 | 组合使用
    // os.O_WRONLY|os.O_CREATE|os.O_APPEND 这是写入如果没有 test.log 就创建同时向该文件追加内容
    // 文件权限为 0666
    file, err := os.OpenFile("test.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer file.Close()

    // 追加日志系统的 logs 时间显示
    fmt.Fprintf(file, "%s\n", time.Now().Format("[2006-01-02 15:04:05]"))

}

创建后的 test.log 文件

2.2 文件只读操作

package main

import (
    "fmt"
    "io"
    "os"
)

func Read() {
    FileName := "test.txt"

    // 通过 openfile 中的 os.O_RDONLY 只读模块,权限为 0666
    file, err := os.OpenFile(FileName, os.O_RDONLY|os.O_CREATE, 0666)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer file.Close()

    // 读取文件的 []byte 切片
    str := make([]byte, 3)

    // 全量读取
    for {
        n, err := file.Read(str)
        if err != nil {
            if err != io.EOF {
                fmt.Println(err)
                break
            }
        }

        // 判断文件内容是否为空
        if n == 0 {
            break
        }
        fmt.Print(string(str[:n]))
    }

    // 写入内容,我爱祖国!!
    fmt.Println(file.Write([]byte("我爱祖国!!")))
}

func main() {
    Read()
}

执行程序

[18:25:31 root@go fileonly]#go run main.go 
0 write test.txt: bad file descriptor

# 0 write test.txt: bad file descriptor: 0 写入 错误的文件描述符

但是由于我们的在使用 os.OpenFile 函数的时候用的是使用的 os.O_RDONLY 只读模式,所以创建出来的 test.txt 文件里面的内容并不会被写入file.Write([]byte("我爱祖国!!"))

test.txt 文件内容为空

2.3 文件只写操作

package main

import (
    "fmt"
    "io"
    "os"
)

func Read() {
    FileName := "test.txt"

    // 通过 openfile 中的 os.O_RDONLY 只写模块,权限为 0666
    file, err := os.OpenFile(FileName, os.O_WRONLY, 0666)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer file.Close()

    fmt.Println(file.Write([]byte("我爱祖国!!")))

    // 读取文件的 []byte 切片
    str := make([]byte, 3)

    // 全量读取
    for {
        n, err := file.Read(str)
        if err != nil {
            if err != io.EOF {
                fmt.Println(err)
                break
            }
        }

        // 判断文件内容是否为空
        if n == 0 {
            break
        }
        fmt.Print(string(str[:n]))
    }

}

func main() {
    Read()
}

执行程序

[18:31:36 root@go fileonly]#go run main.go 
14 <nil>
read test.txt: bad file descriptor

# read test.txt: bad file descriptor: 读 test.txt 错误的文件描述符

2.4 文件读写操作(仅供了解)

package main

import (
    "fmt"
    "os"
)

func Read_Write(name string) {
    file, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0666)
    if err != nil {
        return
    }
    defer file.Close()

    // 写入内容
    file.Write([]byte("1234567"))

    // 读取内容
    srt := make([]byte, 10)
    fmt.Println(file.Read(srt[:]))
}

func main() {
    Read_Write("test.txt")
}

执行文件:

[18:43:29 root@go filereadwrtier]#go run main.go 
0 EOF

发现已经写入到了文件中

但是为什么读取的时候会是 0 EOF 呢,因为在我们写入文件的时候他的执行光标已经到了 test.txt 文件的末尾,所以在读取的时候就会没有内容,那我们想要读取信息的话可以将在最后的光标移动到最前面,由前面开始读取到最后

见下面文件位置光标操作范例

2.5 文件位置将光标移动到最前面操作

范例:

使用 Seek()


往前移动就是 - 负数,往后移动就是正数。

package main

import (
    "fmt"
    "os"
)

func Read_Write(name string) {
    file, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0666)
    if err != nil {
        return
    }
    defer file.Close()

    file.Write([]byte("123"))

    srt := make([]byte, 10)

    // 第一个 0 表示文件的原始位置
    // 第二个 0 表示相对于当前偏移
    file.Seek(0, 0)
    fmt.Println(file.Read(srt))
    fmt.Println(string(srt))
}

func main() {
    Read_Write("test.txt")
}

执行

[19:04:42 root@go filereadwrtier]#go run main.go 
3 <nil>       # 有 7 个长度
123         # 文件内容

2.6 光标指定向前移动次数范例

package main

import (
    "fmt"
    "os"
)

func Read_Write(name string) {
    file, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0666)
    if err != nil {
        return
    }
    defer file.Close()

    file.Write([]byte("123"))

    srt := make([]byte, 10)

    // 第一个 -2 表示文件的向前偏移位置
    // 第二个 1 表示相对于当前偏移
    file.Seek(-2, 1)
    fmt.Println(file.Read(srt))
    fmt.Println(string(srt))
}

func main() {
    Read_Write("test.txt")
}

执行:

[19:07:10 root@go filereadwrtier]#go run main.go 
2 <nil>
23

2.7 将光标移动到最前面写入内容

如果将光标移动到最前面,然后再写入内容的话,就会发现它会将所对应的内容覆盖掉

package main

import (
    "os"
)

func Read_Write(name string) {
    file, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0666)
    if err != nil {
        return
    }
    defer file.Close()

    // 写入 123
    file.Write([]byte("123"))

    // 第一个 0 表示文件光标移动到最开始的地方
    // 第二个 0 表示相对于当前偏移
    file.Seek(0, 0)

    // 写入 ab
    file.WriteString("ab")
}

func main() {
    Read_Write("test.txt")
}

执行之后查看文件内容,最前面的 12 被覆盖为了 ab

3 带缓冲IO

带缓冲的执行速度比不带缓存的要快,因为在应用层是有一层缓存的

io 包主要提供对流的基本操作功能

a) 常用常量

  • EOF:表示文件读取结束

b) 常用函数

  • Copy: 将输出流复制到输入流中
  • CopyBuffer:将输出流复制到输出流中,同时拷贝到字节切片中
  • CopyN:从输入流中复制 N 个字节到输出流
  • WriteString:像输出流中写入字符串

3.1 带缓冲的读取

func (b *Reader) ReadBytes(delim byte) ([]byte, error) 该方法是当读到某个字符以后才进行停止,或者说当文件结束的时候我就停止。

func (b *Reader) ReadRune() (r rune, size int, err error) 该方法就是可以处理中文的格式。比如每次读取一个 Unicode 的字符

func (b *Reader) ReadString(delim byte) (string, error) 该方法是当读到某个字符串以后才进行停止,或者说当文件结束的时候我就停止。

3.1.1 Read()方法读取

  1. 创建一个test.txt 文件,写入内容

  1. 编写代码

    package main
    
    import (
    "bufio"
    "fmt"
    "os"
    )
    
    func main() {
    // 打开文件
    filePath := "test.txt"
    file, err := os.Open(filePath)
    if err != nil {
        return
    }
    
    // 关闭文件
    defer file.Close()
    
    // 将文件读取到 NewReader 缓存中,并返回其缓冲区具有默认大小的新读取器,将文件内容写入 reader。
    reader := bufio.NewReader(file)
    
    // 创建 str 每次读取的 byte 数量
    str := make([]byte, 3)
    n, err := reader.Read(str)
    
       // 读取前三个
    fmt.Println(n, err, str[:n])
    
    }

    执行:

    [22:03:54 root@go testreadr]#go run main.go 
    3  [49 50 51]
    
    # 由于没有转为 string 类型,所以 [49 50 51] 是对应的 Unicode 编码

3.1.2 ReadByte() 方法

该方法只能够一次读取一个字节

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    // 打开文件
    filePath := "test.txt"
    file, err := os.Open(filePath)
    if err != nil {
        return
    }

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

// 将文件读取到 NewReader 缓存中,并返回其缓冲区具有默认大小的新读取器,将文件内容写入 reader。
    reader := bufio.NewReader(file)

    // ReadByte() (byte, error) 返回 byte 和 err
    n1, err1 := reader.ReadByte()

    fmt.Println(n1, string(n1), err1) // 转为 string 输出 n1
}

执行

[22:08:54 root@go testreadr]#go run main.go 
49 1 <nil>

# 49 为 1 的 Unicode 编码
# 1 就是转为 string 的 byte 类型
# nil = error 为空

3.1.3 ReadBytes() 读取到固定字节结束

ReadBytes(delim byte) ([]byte, error)

  1. 编写 test.txt 文件

    内容为:

  2. 当我们读取到内容中的 | 结束

    package main
    
    import (
    "bufio"
    "fmt"
    "os"
    )
    
    func main() {
    // 打开文件
    filePath := "test.txt"
    file, err := os.Open(filePath)
    if err != nil {
        return
    }
    
    // 关闭文件
    defer file.Close()
    
    // 将文件读取到 NewReader 缓存中,并返回其缓冲区具有默认大小的新读取器,将文件内容写入 reader。
    reader := bufio.NewReader(file)
    
    // reader.ReadBytes('|') 读取到中划线结束,并且将 | 读取
    n, err := reader.ReadBytes('|')
    if err != nil {
        return
    }
    fmt.Printf("%v\n%v\n", n, string(n))
    
    n1, err1 := reader.ReadBytes('|')
    if err1 != nil {
        return
    }
    fmt.Printf("%v\n%v\n", n1, string(n1))
    
    n2, err2 := reader.ReadBytes('|')
    fmt.Printf("%v\n%v\n%v\n", n2, string(n2), err2)
    }
    

    执行

    [22:24:29 root@go testreadr]#go run main.go 
    [49 50 51 52 53 124]
    12345|
    
    [54 55 56 124]
    678|
    
    [57 120 120 120 120 120 10 97 98 99 10 49 50 51 120 121 122 10 120 121 122]
    9xxxxx
    abc
    123xyz
    xyz
    EOF
    
    # EOF 由于最后一次读取完毕所有 err 就为 EOF

我们可以看到一共读取了三次,因为在 test.txt 文件中有两个 |

3.1.4 ReadLine() 换行读取读取一行

ReadLine() (line []byte, isPrefix bool, err error)

  1. 编写 test.txt 文件

    内容为:

  1. 代码

    package main
    
    import (
    "bufio"
    "fmt"
    "os"
    )
    
    func main() {
    // 打开文件
    filePath := "test.txt"
    file, err := os.Open(filePath)
    if err != nil {
        return
    }
    
    // 关闭文件
    defer file.Close()
    
    // 将文件读取到 NewReader 缓存中,并返回其缓冲区具有默认大小的新读取器,将文件内容写入 reader。
    reader := bufio.NewReader(file)
    
       // 有三个返回值,一般忽略第二个
    n, _, err := reader.ReadLine()
    fmt.Println(n, string(n), err)
    }
    

    执行

    [22:29:03 root@go testreadr]#go run main.go 
    [49 50 51 52 53 124 54 55 56 124 57 120 120 120 120 120] 12345|678|9xxxxx 
    
    # 12345|678|9xxxxx 将第一行读取

3.1.5 ReadSlice() 读取到固定字节结束

ReadBytes() 类似

  1. 编写 test.txt 文件

    内容为:

代码:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    // 打开文件
    filePath := "test.txt"
    file, err := os.Open(filePath)
    if err != nil {
        return
    }

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

    // 将文件读取到 NewReader 缓存中,并返回其缓冲区具有默认大小的新读取器,将文件内容写入到 reader。
    reader := bufio.NewReader(file)

    // | 为分隔符
    n, err := reader.ReadSlice('|')
    fmt.Println(n, string(n), err)
}

执行

[22:40:25 root@go testreadr]#go run main.go 
[49 50 51 52 53 124] 12345| <nil>

# 12345| 

通过输出发现和 ReadBytes() 类似

3.1.6 ReadString() 和 ReadBytes() 类似,返回 string

代码

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    // 打开文件
    filePath := "test.txt"
    file, err := os.Open(filePath)
    if err != nil {
        return
    }

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

    // 将文件读取到 NewReader 缓存中,并返回其缓冲区具有默认大小的新读取器,将文件内容写入到 reader。
    reader := bufio.NewReader(file)

    // | 为分隔符
    n, err := reader.ReadString('|')
    fmt.Println(n, err)
}

执行

[22:40:26 root@go testreadr]#go run main.go 
12345| <nil>

3.1.7 WriteTo() 将输入输出

package main

import (
    "bufio"
    "os"
)

func main() {
    // 打开文件
    filePath := "test.txt"
    file, err := os.Open(filePath)
    if err != nil {
        return
    }

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

    // 将文件读取到 NewReader 缓存中,并返回其缓冲区具有默认大小的新读取器,将文件内容写入到 reader。
    reader := bufio.NewReader(file)

    // 使用 WriteTo() 然后调用 os.Stdout 终端输出 test.txt 文件类容
    reader.WriteTo(os.Stdout)
}

执行

[22:46:44 root@go testreadr]#go run main.go 
12345|678|9xxxxx
abc
123xyz
xyz

WriteTo 能够实现对新文件的写入

也就是当我们将原有的文件内容需要写入到一个新的文件中

如下代码:

package main

import (
    "bufio"
    "os"
)

func main() {

    fileName := "test.txt"

    // 定义新文件
    NewFileName := "test.log"

    f, _ := os.Open(fileName)
    defer f.Close()

    // 将 newfilename 变量的新文件进行创建
    NewFile, _ := os.Create(NewFileName)
    defer NewFile.Close()

    // 通过 buff 缓冲区将旧文件 f 的内容读取到 read 变量中
    read := bufio.NewReader(f)

    // 再通过 read 缓冲变量使用 writeto 函数将数据写入到创建的 newfile 中
    read.WriteTo(NewFile)
}
# 查看旧文件内容
root@consul-3:~/go/src/2022/day2# cat test.txt 
1234|5678|9xxx
abc
123xyz

# 执行代码
xyzroot@consul-3:~/go/src/2022/day2# go run main.go 

# 查看新创建的文件内容,可以看到数据已经完全写入
root@consul-3:~/go/src/2022/day2# cat test.log 
1234|5678|9xxx
abc
123xyz

3.2 带缓存的输入

带缓冲的 io 输入,可以解决必须输入一个正确的字符串问题

package main

import (
    "bufio"
    "os"
)

func main() {

    // 调用 bufio.NewScanner() 然后在里面调用 os.Stdin 标准输入
    scanner := bufio.NewScanner(os.Stdin)
    scanner.Scan()
}

执行

[21:39:36 root@go testbufio]#go run main.go 
213

一次性输入多个带缓冲数据

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    scanner := bufio.NewScanner(os.Stdin)

    // 通过 for 循环调用  scanner.Scan() 一次性输入多个带缓冲的值
    for scanner.Scan() {

        // scanner.Text() 将所有输入的值转为 string 类型输出
        fmt.Println(scanner.Text())
    }
}

执行

[21:47:52 root@go testbufio]#go run main.go 
12312
12312
asdasd
asdasd
zxczx
zxczx
^Csignal: interrupt

自己手动编写一个 带缓冲输入的函数

package main

import (
    "bufio"
    "os"
    "strconv"
)

// 读取一行转换一行,返回 int 和 err 类型
func ScanInt() (int, error) {
    scanner := bufio.NewScanner(os.Stdin)
    if scanner.Scan() {
        // strconv.Atoi(s string) (int, error) 将 string 转换为 int 返回
        // (*bufio.Scanner).Text() string 返回的是 string
        return strconv.Atoi(scanner.Text())
    }
    return 0, scanner.Err()
}

func main() {
    ScanInt()
}

3.3 带缓冲的写

3.3.1 WriteString() 写入 string 类型

package main

import (
    "bufio"
    "os"
)

func main() {
    // 创建 test.txt
    file, err := os.Create("test.txt")
    if err != nil {
        return
    }
    defer file.Close()

    // 传递 file 到 bufio.NewWriter()
    writer := bufio.NewWriter(file)

    // 写入 string 类型 123123123
    writer.WriteString("123123123")

    // 刷新缓存,如果不刷新缓存该文件只能被创建则没有内容写入,因为缓存还在应用程序层还在内存中
    writer.Flush()
}

执行

[23:01:09 root@go testwriter]#go run main.go 

已经写入

使用函数方法实现创建文件和写入内容:

package main

import (
    "bufio"
    "fmt"
    "os"
)

// 创建文件并返回 *os.File 类型
func CreateFile(name string) *os.File {
    file, err := os.Create(name)
    if err != nil {
        panic(err)
    }
    return file
}

// 编写写入函数,接收 *os.File 类型,因为在 *os.File 中有 io.Writer 方法
func Write(f *os.File) {

    // 通过 bufio 创建一个新的 buff 写入寄存器,并将内容写入到 f 文件中
    write := bufio.NewWriter(f)
    defer f.Close()

    // 通过终端输入内容
    str := ""
    fmt.Scanln(&str)

    // 写入 str 变量接入的内容
    write.WriteString(str)

    // 刷新缓存
    write.Flush()
}

func main() {

    fileName := "hi.log"

    Write(CreateFile(fileName))
}
root@consul-3:~/go/src/2022/day2# go run main.go 
哈哈哈哈

# 写入成功
root@consul-3:~/go/src/2022/day2# cat hi.log 
哈哈哈哈

4 文件目录操作

目录是读取该目录下的文件。

这时候就有一个问题,我们如何判断是文件夹还是目录,如果是 dir 我们如何读取目录下的文件名。

4.1 获取文件信息

目录结构

os.File.Stat() 我们想判断一些文件的信息就可以使用该函数

func (*os.File).Stat() (fs.FileInfo, error)

package main

import (
    "fmt"
    "os"
)

func main() {
    // 不管是 file 还是 dir 我们在打开的时候都是用 open
    // 打开文件和打开文件夹都是使用 os.Open
    path := "testdir"
    file, err := os.Open(path)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer file.Close()

    // 返回 fileInfo 和一个 err
    fileInfo, err := file.Stat()
    fmt.Println(fileInfo, err)

    fmt.Println(fileInfo.Name())    // 输出文件名称
    fmt.Println(fileInfo.Size())    //  输出文件大小
    fmt.Println(fileInfo.IsDir())   // 判断是否为文件夹
    fmt.Println(fileInfo.ModTime()) // 文件最后的修改时间
    fmt.Println(fileInfo.Mode())    // 文件权限属性
}

执行:

[09:50:40 root@go file]#go run main.go 
&{testdir 6 2147484141 {401043485 63759921874 0x55e540} {2050 100729273 2 16877 0 0 0 0 6 4096 0 {1624325074 403043485} {1624325074 401043485} {1624325074 401043485} [0 0 0]}} <nil>
testdir                                 # 输出文件名称
6                                       # 输出文件大小
true                                    # 判断是否为文件夹
2021-06-22 09:24:34.401043485 +0800 CST # 文件最后的修改时间
drwxr-xr-x                              # 文件权限属性

4.2 读取目录信息

func (f *File) Readdirnames(n int) (names []string, err error)

如果想要查看所有信息只需要将 Readdirnames(-1) 写个 -1 即可

1.编写程序

package main

import (
    "fmt"
    "os"
)

func main() {
    // 不管是 file 还是 dir 我们在打开的时候都是用 open
    // 打开文件和打开文件夹都是使用 os.Open
    path := "testdir"
    file, err := os.Open(path)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer file.Close()

    // -1:表示输出所有的 testdir 目录下的信息
    fmt.Println(file.Readdirnames(-1))
}

2.执行

[09:55:01 root@go file]#go run main.go 
[] <nil>

# 由于当前 testdir 目录下没有任何数据所以返回的是空

3.现在往 testdir 目录中创建数据

[09:55:03 root@go file]#touch testdir/11.txt
[09:57:41 root@go file]#mkdir testdir/temp

4.再次执行该程序,我们可以看到输出的内容就是刚才我们创建的11.txt 文件和 temp 目录,但是不会显示 testdir 子目录下的内容

[09:57:56 root@go file]#go run main.go 
[11.txt temp] <nil>

4.3 获取指定目录信息

func (*os.File).ReadDir(n int) ([]fs.DirEntry, error)

我们通过遍历 []fs.DirEntry 切片,我们可以获取到该目录下的数据信息

1.编写代码

package main

import (
    "fmt"
    "os"
)

func main() {
    // 不管是 file 还是 dir 我们在打开的时候都是用 open
    // 打开文件和打开文件夹都是使用 os.Open
    path := "testdir"
    file, err := os.Open(path)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer file.Close()

    // -1:表示输出所有的 testdir 目录下的信息
    fileInfos, err := file.ReadDir(-1)
    for _, fileInfo := range fileInfos {

        // fileInfo.Name() 获取指定目录下所有文件
        // fileInfo.IsDir() 判断他们是否都为 目录
        fmt.Println(fileInfo.Name(), fileInfo.IsDir())
    }
}

执行:

[10:05:56 root@go file]#go run main.go 
11.txt false    # 是文件所以为 false
temp true       # 是目录

4.4 对文件的常规操作

有的时候我们需要对文件进行拷贝或者说进行删除。

// 文件
创建 => os.Create
读取 => os.Open
获取属性 => os.Open() Stat 获取 / os.Stat
修改属性:权限、所属人 os.Chmod(修改文件权限) os.Chown(修改文件所属者)

重命名:    os.Rename() // 重命名
删除文件    os.Remove()

// 目录
创建 => os.Mkdir
读取 => os.Open
获取属性 =>  os.Open() Stat 获取 / os.Stat
修改属性
os.Chmod() // 修改文件权限
os.Chown() // 修改文件所属者
删除文件夹

4.4.1 文件/文件夹移动

func os.Rename(oldpath string, newpath string) error

package main

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

func main() {
    fileName := "test.txt"

    dirName := "temp"

    // 创建目录
    err := os.Mkdir(dirName, 066)
    if err != nil {
        log.Println(err)
        return
    }

    // 创建文件
    file, err := os.Create(fileName)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer file.Close()

    // 将 test.txt 文件移动到 dirName 目录中
    os.Rename(fileName, dirName+"/"+fileName)
}

执行

[10:49:21 root@go fileop]#go run main.go

4.4.2 删除文件

不仅能够移除文件还可以移除目录

func os.Remove(name string) error

package main

import "os"

func main() {
    // 删除 b.txt
    os.Remove("b.txt")

    // 删除 e
    os.Remove("e")
}

执行

[10:51:17 root@go fileop]#go run main.go 

执行之后被删

删除所有文件,我们看到 a 目录下还有 b、c、d

[11:11:24 root@go fileop]#tree 
.
├── a
│   └── b
│       └── c
│           └── d
└── main.go

编写代码

package main

import "os"

func main() {
    // 删除 a 目录
    os.RemoveAll("a")
}

执行

[11:13:43 root@go fileop]#go run main.go 
[11:13:45 root@go fileop]#tree 
.
└── main.go
# 已经删除只剩一个 mian.go 文件

4.4.3 创建目录

func os.Mkdir(name string, perm fs.FileMode) error

package main

import "os"

func main() {
    // 创建 test 目录权限为 os.ModePerm
    os.Mkdir("test", os.ModePerm)
}

执行

[11:07:03 root@go fileop]#go run main.go 
[11:07:13 root@go fileop]#ll
total 4
-rw-r--r-- 1 root root 74 Jun 22 11:07 main.go
drwxr-xr-x 2 root root  6 Jun 22 11:07 test     # 目录创建

但是当我创建目录的时候,如果说父目录不存在我们该怎么做呢,这时候就需要使用 os.MkdirAll

func os.MkdirAll(path string, perm fs.FileMode) error

package main

import "os"

func main() {
    // a/b/c/d 同时创建 a、b、c、d 这 4 个目录
    os.MkdirAll("a/b/c/d", os.ModePerm)
}

执行

[11:07:25 root@go fileop]#go run main.go 
[11:11:24 root@go fileop]#tree  
.
├── a
│   └── b
│       └── c
│           └── d
└── main.go
# 通过 tree 命令查看已经创建

5 io工具&文件路径

在 go 里面有一些工具是对 io 相关的操作。

5.1 ioutil 包

iotuil 包有一些几个函数

[14:24:41 root@go utils]#go doc ioutil
package ioutil // import "io/ioutil"

var Discard io.Writer = io.Discard
func NopCloser(r io.Reader) io.ReadCloser
func ReadAll(r io.Reader) ([]byte, error)
func ReadDir(dirname string) ([]fs.FileInfo, error)
func ReadFile(filename string) ([]byte, error)
func TempDir(dir, pattern string) (name string, err error)
func TempFile(dir, pattern string) (f *os.File, err error)
func WriteFile(filename string, data []byte, perm fs.FileMode) error

5.1.1 ReadAll() 读取文件中全部内容

func ioutil.ReadAll(r io.Reader) ([]byte, error)

  1. 创建一个文件 test.txt ,文件内容如下

  1. 编写程序

    package main
    
    import (
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "time"
    )
    
    // 定义文件
    var fileName = "test.txt"
    
    func createfile_write() {
       // 创建文件
    os.Create(fileName)
    
       // 写入内容
    err := ioutil.WriteFile(fileName, []byte("123123\naaa\n中文\n啊实打实的"), 0666)
    if err != nil {
        log.Println(err)
        return
    }
    }
    
    func read() {
       // 打开刚才创建的文件
    file, err := os.Open(fileName)
    if err != nil {
        log.Println(err)
        return
    }
    
       defer file.Close()
    
       // 读取
    n, err := ioutil.ReadAll(file)
    if err != nil {
        log.Println(err)
        return
    }
    fmt.Println(string(n))
    }
    
    func main() {
    createfile_write()
    fmt.Println("文件创建并写入完毕,等待 5s ...")
    time.Sleep(time.Second * 5)
    read()
    fmt.Println("文件读取完毕!")
    }
    

    执行

    [23:42:39 root@go day1]#go run main.go 
    文件创建并写入完毕,等待 5s ...
    123123
    aaa
    中文
    啊实打实的
    文件读取完毕!
    
    # 通过执行我们可以发现已经读取了所有的内容

5.1.2 ReadFile() 返回文件所有内容

func ioutil.ReadFile(filename string) ([]byte, error)

ReadFile读取按文件名命名的文件并返回内容。成功的调用返回err==nil,而不是err==EOF。因为ReadFile读取整个文件,所以它不会将来自Read的EOF视为要报告的错误。

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {

    // 定义文件路径,调用 ioutil.ReadFile() 传入文件路径
    filePath := "test.txt"
    str, err := ioutil.ReadFile(filePath)
    if err == nil {
        fmt.Println(string(str))
    }
}

执行

[15:03:47 root@go utils]#go run main.go 
123123
aaa
中文
啊实打实的

5.1.3 ReadDir() 返回所有文件夹的内容

func ioutil.ReadDir(dirname string) ([]fs.FileInfo, error)

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "time"
)

var dir = "/data/"

// 创建目录
func createDir() {
    os.Mkdir(dir, 0666)
}

// 创建文件并移动到刚创建的目录中
func createFile_mvFile() {
    file := []string{"1.txt", "2.txt", "3.txt"}

    for _, v := range file {
        _, err := os.Create(dir + v)
        if err != nil {
            log.Println(err)
            return
        }
    }
}

// 读取刚才创建的目录
func readDir() {
    fsInfo, err := ioutil.ReadDir(dir)

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

    for _, v := range fsInfo {
        fmt.Println(v.Name())
    }

}

func main() {
    fmt.Println("创建目录并暂停两秒!")
    createDir()
    time.Sleep(time.Second * 2)
    fmt.Println("创建文件并将文件移动到创建的目录中!")
    createFile_mvFile()
    fmt.Println("读取目录:")
    readDir()
}

执行

[23:56:52 root@go day1]#go run main.go 
创建目录并暂停两秒!
创建文件并将文件移动到创建的目录中!
读取目录:
1.txt
2.txt
3.txt

5.1.4 WriteFile() 写入数据到文件中

func ioutil.WriteFile(filename string, data []byte, perm fs.FileMode) error

WriteFile 将数据写入按文件名命名的文件。

如果文件不存在,WriteFile 将使用 perm 权限创建它

(在umask之前);否则WriteFile会在写入之前将其截断,而不更改权限。

package main

import (
    "io/ioutil"
)

func main() {
 // 如果没有 a.txt 就自动创建,内容为 []byte("这是一个测试文件") ,权限0666
    ioutil.WriteFile("a.txt", []byte("这是一个测试文件"), 0666)
}

执行

[15:19:33 root@go utils]#go run main.go 

5.2 对文件路径进行处理

在 go 中有一个 path 包和一个 filepath

path 包中的东西包含在 filepath 里面

[15:39:14 root@go filepath]#go doc path
package path // import "path"

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.

var ErrBadPattern = errors.New("syntax error in pattern")
func Base(path string) string       # 获取文件得名字
func Clean(path string) string
func Dir(path string) string        # 获取父目录得名字
func Ext(path string) string        # 获取文件后缀    
func IsAbs(path string) bool        # 判断是否为绝对路径
func Join(elem ...string) string    # 多个路径连接起来用 join
func Match(pattern, name string) (matched bool, err error)
# 文件路径匹配模式

func Split(path string) (dir, file string) # 判断父目录和文件分开

5.2.1 filepath 包

[15:48:54 root@go filepath]#go doc filepath
package filepath // import "path/filepath"

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.

const Separator = os.PathSeparator ...
var ErrBadPattern = errors.New("syntax error in pattern")
var SkipDir error = fs.SkipDir
func Abs(path string) (string, error) # 获取绝对路径
func Base(path string) string         # 获取文件名称
func Clean(path string) string        # 清除文件中得符号
func Dir(path string) string          # 获取父目录
func EvalSymlinks(path string) (string, error)
func Ext(path string) string          # 用来获取后缀
func FromSlash(path string) string
func Glob(pattern string) (matches []string, err error)
func HasPrefix(p, prefix string) bool # 判断文件是不是以什么为前缀得
func IsAbs(path string) bool          # 用来判断绝对路径
func Join(elem ...string) string      # 多个路径连接起来用 join
func Match(pattern, name string) (matched bool, err error)
func Rel(basepath, targpath string) (string, error)
func Split(path string) (dir, file string)
func SplitList(path string) []string
func ToSlash(path string) string
func VolumeName(path string) string
func Walk(root string, fn WalkFunc) error # 
func WalkDir(root string, fn fs.WalkDirFunc) error
type WalkFunc func(path string, info fs.FileInfo, err error) error

5.2.1.1 filepath.Abs() 获取文件绝对路径

func Abs(path string) (string, error) 获取绝对路径

package main

import (
    "fmt"
    "path/filepath"
)

func main() {

    // 获取 main.go 这个文件的绝对路径
    file, err := filepath.Abs("main.go")
    if err == nil {
        fmt.Println(file)
    }
}

执行

[16:19:04 root@go filepath]#go run main.go 
/data/go/day5/codes/filepath/main.go

5.2.1.2 filepath.Base() 获取自身文件名

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    // 获取当前文件的 main.go 名称
    fmt.Println(filepath.Base("main.go"))
}

执行

[16:21:58 root@go filepath]#go run main.go 
main.go

5.2.1.3 filepath.Dir() 获取父目录

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    // 获取绝对路径
    file, _ := filepath.Abs("main.go")

    // 然后再获取他的父目录
    fmt.Println(filepath.Dir(file))
}

执行

[16:24:15 root@go filepath]#go run main.go 
/data/go/day5/codes/filepath

5.2.1.4 filepath.Split() 分割

将文件和父目录进行分割

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    path, _ := filepath.Abs("main.go")

    // 进行分割操作
    fmt.Println(filepath.Split(path))

}

执行

[16:27:32 root@go filepath]#go run main.go 
/data/go/day5/codes/filepath/ main.go

5.2.1.5 filepath.Ext() 返回文件后缀

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    path, _ := filepath.Abs("main.go")

    fmt.Println(filepath.Ext(path))

}

执行

[16:32:49 root@go filepath]#go run main.go 
.go

5.2.1.6 filepath.HasPrefix() 以什么为前缀

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    path, _ := filepath.Abs("main.go")

    // 判断前缀是否为 /data 目录下
    fmt.Println(filepath.HasPrefix(path, "/data"))
}

执行

[16:37:04 root@go filepath]#go run main.go 
true

5.2.1.7 filepath.IsAbs() 判断是否为绝对路径

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    path, _ := filepath.Abs("main.go")

    fmt.Println(filepath.IsAbs(path))
}

执行

[16:37:50 root@go filepath]#go run main.go 
true

5.2.1.8 filepath.Glob() 用于文件匹配

这里我创建了以下几个文件


这种方式类似于正则表达式

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    fmt.Println(filepath.Glob("dir/*.go"))
    fmt.Println(filepath.Glob("dir/*.c"))
    fmt.Println(filepath.Glob("dir/*.txt"))
}

执行

[16:49:48 root@go filepath]#go run main.go 
[dir/a.go] <nil>
[dir/c.c] <nil>
[dir/b.txt] <nil>

# 我们可以看到已经匹配到了这几个文件

当然也能够匹配其他目录下的内容

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    // 这里我匹配 / 下得所有文件将其输出
    fmt.Println(filepath.Glob("/*"))
}
root@consul-3:~/go/src/2022/day2# go run main.go 
[/apps /bin /boot /data /dev /etc /home /initrd.img /initrd.img.old /lib /lib64 /lost+found /media /mnt /opt /proc /root /run /sbin /snap /srv /swapfile /sys /tmp /usr /var /vmlinuz /vmlinuz.old] <nil>

5.2.1.9 filepath.Walk() 将所有目录文件全部输出

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    // 拿到 dir 目录全路径
    path, err := filepath.Abs("dir")
    if err != nil {
        return
    }

    // 通过 filepath.Walk() 回调函数
    filepath.Walk("dir", func(path string, info os.FileInfo, err error) error {
        // 将 walk 函数中的引用 dir 目录下的 path 和 info 函数中将 name 输出
        fmt.Println(path, info.Name())
        return nil
    })
}

执行

[17:12:03 root@go filepath]#go run main.go 
dir dir
dir a.go
dir b.txt
dir c.c

# 已经将 dir 目录下所有的文件全部输出
5.2.1.9.1 filepath.WalkDir() 指定查看目录下得所有文件
package main

import (
    "fmt"
    "io/fs"
    "path/filepath"
)

func main() {

    // 指定查看 /root/go/src/2022/day2 下的内容
    filepath.WalkDir("/root/go/src/2022/day2", func(path string, d fs.DirEntry, err error) error {
        // 输出路径和文件 name
        fmt.Println(path, d.Name())
        return nil
    })
}
root@consul-3:~/go/src/2022/day2# go run main.go 
/root/go/src/2022/day2 day2
/root/go/src/2022/day2/haha.txt haha.txt
/root/go/src/2022/day2/main.go main.go

#...... 省略 

5.2.1.10filepath.Join() 用来连接多个路径

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    // 将 /root 和 /data 进行链接
    fmt.Println(filepath.Join("/root", "/data"))
}

执行

[17:12:27 root@go filepath]#go run main.go 
/root/data

# 他就会把路径拼接起来
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇