这是我参与「第五届青训营」伴学笔记创作活动的第6天。
io包
Go 语言中,为了方便开发者使用,将 IO 操作封装在了如下几个包中:
- io 为 IO 原语(I/O primitives)提供基本的接口 os File Reader Writer
- io/ioutil 封装一些实用的 I/O 函数
- fmt 实现格式化 I/O,类似 C 语言中的 printf 和 scanf format fmt
- bufio 实现带缓冲I/O
基本的IO接口
- 在 io 包中最重要的是两个接口:Reader 和 Writer 接口。
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
- 实现Reader和Writer接口的类型
- os.File 同时实现了 io.Reader 和 io.Writer
- strings.Reader 实现了 io.Reader
- bufio.Reader/Writer 分别实现了 io.Reader 和 io.Writer
- bytes.Buffer 同时实现了 io.Reader 和 io.Writer
- bytes.Reader 实现了 io.Reader
- compress/gzip.Reader/Writer 分别实现了 io.Reader 和 io.Writer
- crypto/cipher.StreamReader/StreamWriter 分别实现了 io.Reader 和 io.Writer
- crypto/tls.Conn 同时实现了 io.Reader 和 io.Writer
- encoding/csv.Reader/Writer 分别实现了 io.Reader 和 io.Writer
ioutil包
- 封装一些实用的IO函数 |名称 | 作用 | | --------- | ----------------------------- | | ReadAll | 读取数据,返回读到的字节 slice | | ReadDir | 读取一个目录,返回目录入口数组 []os.FileInfo | | ReadFile | 读一个文件,返回文件内容(字节slice) | | WriteFile | 根据文件路径,写入字节slice | | TempDir | 在一个目录中创建指定前缀名的临时目录,返回新临时目录的路径 | | TempFile | 在一个目录中创建指定前缀名的临时文件,返回 os.File
fmt包
- fmt包实现了格式化的I/O函数,这点类似C语言中的printf和scanf,但是更加简单
- Scanning
- Scan、Scanf 和 Scanln 从os.Stdin 中读取;
- Fscan、Fscanf 和 Fscanln 从指定的 io.Reader 中读取;
- Sscan、Sscanf 和 Sscanln 从实参字符串中读取。
- Scanln、Fscanln 和 Sscanln在换行符处停止扫描,且需要条目紧随换行符之后;
- Scanf、Fscanf 和 Sscanf需要输入换行符来匹配格式中的换行符;其它函数则将换行符视为空格。
- Scanf、Fscanf 和 Sscanf 根据格式字符串解析实参,类似于 Printf。例如,%x会将一个整数扫描为十六进制数,而 %v 则会扫描该值的默认表现格式。
package main import "fmt" // scan func test1() { var age int fmt.Println("请输入年龄:") fmt.Scan(&age) fmt.Printf("age: %v\n", age) } // Scanf func test2() { var name string fmt.Println("请输入姓名:") fmt.Scanf("%s", &name) fmt.Printf("name: %v\n", name) } // Scanln func test3() { var age int fmt.Println("请输入年龄:") fmt.Scanln(&age) fmt.Printf("age: %v\n", age) } func main() { // test2() test3() } - Errorf
func Errorf(format string, a ...interface{}) error
Errorf 根据于格式说明符进行格式化,并将字符串作为满足 error 的值返回,其返回类型是error.
- Fprint
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
Fprint 使用其操作数的默认格式进行格式化并写入到 w。当两个连续的操作数均不为字符串时,它们之间就会添加空格。它返回写入的字节数以及任何遇到的错误。
- Fprintf
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
Fprintf 根据于格式说明符进行格式化并写入到 w。它返回写入的字节数以及任何遇到的写入错误。
- Fprintln
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
Fprintln 使用其操作数的默认格式进行格式化并写入到 w。其操作数之间总是添加空格,且总在最后追加一个换行符。它返回写入的字节数以及任何遇到的错误。
bufio
- bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。
- 常量
const (
defaultBufSize = 4096
)
- 变量
var (
ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
ErrBufferFull = errors.New("bufio: buffer full")
ErrNegativeCount = errors.New("bufio: negative count")
)
//会被Scanner类型返回的错误
var (
ErrTooLong = errors.New("bufio.Scanner: token too long")
ErrNegativeAdvance = errors.New("bufio.Scanner: SplitFunc returns negative advance count")
ErrAdvanceTooFar = errors.New("bufio.Scanner: SplitFunc returns advance count beyond input")
)
- Reader : 实现了给一个io.Reader接口对象附加缓冲
type Reader struct {
buf []byte
rd io.Reader // reader provided by the client
r, w int // buf read and write positions
err error
lastByte int // last byte read for UnreadByte; -1 means invalid
lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid
}
func main() {
s := strings.NewReader("ABCEFG")
// NewReader创建一个具有默认大小缓冲、从r读取的*Reader。NewReader 相当于 NewReaderSize(rd, 4096)
str := strings.NewReader("123455")
br := bufio.NewReader(s)
b, _ := br.ReadString('\n')
fmt.Println(b)
// Reset丢弃缓冲中的数据,清除任何错误,将b重设为其下层从r读取数据。
br.Reset(str)
b, _ = br.ReadString('\n')
fmt.Println(b)
}
- Read :Read读取数据写入p。本方法返回写入p的字节数。本方法一次调用最多会调用下层Reader接口一次Read方法,因此返回值n可能小于len(p)。读取到达结尾时,返回值n将为0而err将为io.EOF。
func main() {
s := strings.NewReader("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
br := bufio.NewReader(s)
p := make([]byte, 10)
for {
n, err := br.Read(p)
if err == io.EOF {
break
} else {
fmt.Printf("string(p): %v\n", string(p[0:n]))
}
}
}
- ReadeByte 和 UnreadByte
- ReadByte读取并返回一个字节。如果没有可用的数据,会返回错误。
- UnreadByte吐出最近一次读取操作读取的最后一个字节。(只能吐出最后一个,多次调用会出问题)
func main() {
s := strings.NewReader("ABCDEFG")
br := bufio.NewReader(s)
c, _ := br.ReadByte()
fmt.Printf("%c\n", c)
c, _ = br.ReadByte()
fmt.Printf("%c\n", c)
br.UnreadByte()
c, _ = br.ReadByte()
fmt.Printf("%c\n", c)
}
- ReadRune 和 UnreadRune
- ReadRune读取一个utf-8编码的unicode码值,返回该码值、其编码长度和可能的错误。如果utf-8编码非法,读取位置只移动1字节,返回U+FFFD,返回值size为1而err为nil。如果没有可用的数据,会返回错误。
- UnreadRune吐出最近一次ReadRune调用读取的unicode码值。如果最近一次读取不是调用的ReadRune,会返回错误。(从这点看,UnreadRune比UnreadByte严格很多)
func main() {
s := strings.NewReader("你好,世界!")
br := bufio.NewReader(s)
c, size, _ := br.ReadRune()
fmt.Printf("%c %v\n", c, size)
c, size, _ = br.ReadRune()
fmt.Printf("%c %v\n", c, size)
br.UnreadRune()
c, size, _ = br.ReadRune()
fmt.Printf("%c %v\n", c, size)
}
- ReadBytes
func (b *Reader) ReadBytes(delim byte) (line []byte, err error)
- ReadBytes读取直到第一次遇到delim字节,返回一个包含已读取的数据和delim字节的切片。如果ReadBytes方法在读取到delim之前遇到了错误,它会返回在错误之前读取的数据以及该错误(一般是io.EOF)。当且仅当ReadBytes方法返回的切片不以delim结尾时,会返回一个非nil的错误。
- ReadString
func (b *Reader) ReadString(delim byte) (line string, err error)
- ReadString读取直到第一次遇到delim字节,返回一个包含已读取的数据和delim字节的字符串。如果ReadString方法在读取到delim之前遇到了错误,它会返回在错误之前读取的数据以及该错误(一般是io.EOF)。当且仅当ReadString方法返回的切片不以delim结尾时,会返回一个非nil的错误。
- WriteTo
func (b *Reader) WriteTo(w io.Writer) (n int64, err error)
- WriteTo方法实现了io.WriterTo接口。
func main() {
s := strings.NewReader("ABCEFGHIJKLMN")
br := bufio.NewReader(s)
b := bytes.NewBuffer(make([]byte, 0))
br.WriteTo(b)
fmt.Printf("%s\n", b)
}
- Writer
- Writer实现了为io.Writer接口对象提供缓冲。如果在向一个Writer类型值写入时遇到了错误,该对象将不再接受任何数据,且所有写操作都会返回该错误。在所有数据都写入后,调用者有义务调用Flush方法以保证所有的数据都交给了下层的io.Writer。
type Writer struct {
err error
buf []byte
n int
wr io.Writer
}
- 其他Writer相关方法类似于Reader
- ReadWriter
- ReadWriter类型保管了指向Reader和Writer类型的指针,故实现了io.ReadWriter接口。
type ReadWriter struct {
*Reader
*Writer
}
-
Scanner
-
Scanner类型提供了方便的读取数据的接口,如从换行符分隔的文本里读取每一行。成功调用的Scan方法会逐步提供文件的token,跳过token之间的字节。token由SplitFunc类型的分割函数指定;默认的分割函数会将输入分割为多个行,并去掉行尾的换行标志。本包预定义的分割函数可以将文件分割为行、字节、unicode码值、空白分隔的word。调用者可以定制自己的分割函数。扫描会在抵达输入流结尾、遇到的第一个I/O错误、token过大不能保存进缓冲时,不可恢复的停止。当扫描停止后,当前读取位置可能会远在最后一个获得的token后面。需要更多对错误管理的控制或token很大,或必须从reader连续扫描的程序,应使用bufio.Reader代替。
-
Split
func (s *Scanner) Split(split SplitFunc)
- Split设置Scanner相关的分割函数,此方法必须在Scan之前调用
func main() {
s := strings.NewReader("ABC DEF GHI JKL")
bs := bufio.NewScanner(s)
bs.Split(bufio.ScanWords)
for bs.Scan() {
fmt.Println(bs.Text())
}
}
- Scan
func (s *Scanner) Scan() bool
- Scan方法获取当前位置的token(该token可以通过Bytes或Text方法获得),并让Scanner的扫描位置移动到下一个token。当扫描因为抵达输入流结尾或者遇到错误而停止时,本方法会返回false。在Scan方法返回false后,Err方法将返回扫描时遇到的任何错误;除非是io.EOF,此时Err会返回nil。
- Bytes方法返回最近一次Scan调用生成的token。底层数组指向的数据可能会被下一次Scan的调用重写。
- Text方法返回最近一次Scan调用生成的token,会申请创建一个字符串保存token并返回该字符串。
- Err返回Scanner遇到的第一个非EOF的错误。
os模块
文件目录
package main
import (
"fmt"
"os"
)
// 创建文件
func createFile() {
f, err := os.Create("test.txt")
if err != nil {
fmt.Printf("err: %v\n", err)
} else {
fmt.Printf("f: %v\n", f)
}
}
// 创建目录
func createDir() {
// 创建单个目录
/* err := os.Mkdir("test", os.ModePerm)
if err != nil {
fmt.Printf("err: %v\n", err)
} */
err := os.MkdirAll("test/a/b", os.ModePerm)
if err != nil {
fmt.Printf("err: %v\n", err)
}
}
// 删除目录
func removeDir() {
/* err := os.Remove("test.txt")
if err != nil {
fmt.Printf("err: %v\n", err)
} */
err := os.RemoveAll("test")
if err != nil {
fmt.Printf("err: %v\n", err)
}
}
// 获得工作目录
func getWd() {
dir, err := os.Getwd()
if err != nil {
fmt.Printf("err: %v\n", err)
} else {
fmt.Printf("dir: %v\n", dir)
}
}
// 修改工作目录
func chWd() {
err := os.Chdir("d:/")
if err != nil {
fmt.Printf("err: %v\n", err)
}
fmt.Println(os.Getwd())
}
// 获得临时目录
func getTemp() {
s := os.TempDir()
fmt.Printf("s: %v\n", s)
}
// 重命名文件
func renameFile() {
err := os.Rename("test.txt", "test2.txt")
if err != nil {
fmt.Printf("err: %v\n", err)
}
}
// 读文件
func readFile() {
b, err := os.ReadFile("test2.txt")
if err != nil {
fmt.Printf("err: %v\n", err)
} else {
fmt.Printf("b: %v\n", string(b[:]))
}
}
// 写文件
func writeFile() {
s := "hello world"
os.WriteFile("test2.txt", []byte(s), os.ModePerm)
}
func main() {
// createFile()
// createDir()
// removeDir()
// removeDir()
// getWd()
// chWd()
// renameFile()
// readFile()
// writeFile()
// getTemp()
}
File文件读取操作
package main
import (
"fmt"
"os"
)
// 打开关闭文件
func openCloseFile() {
// 只能读
f, _ := os.Open("a.txt")
fmt.Printf("f.Name(): %v\n", f.Name())
// 根据第二个参数 可以读写或者创建
f2, _ := os.OpenFile("a1.txt", os.O_RDWR|os.O_CREATE, 0755)
fmt.Printf("f2.Name(): %v\n", f2.Name())
err := f.Close()
fmt.Printf("err: %v\n", err)
err2 := f2.Close()
fmt.Printf("err2: %v\n", err2)
}
// 创建文件
func createFile() {
// 等价于:OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
f, _ := os.Create("a2.txt")
fmt.Printf("f.Name(): %v\n", f.Name())
// 第一个参数 目录默认:Temp 第二个参数 文件名前缀
f2, _ := os.CreateTemp("", "temp")
fmt.Printf("f2.Name(): %v\n", f2.Name())
}
// 读操作
func readOps() {
// 循环读取
/* f, _ := os.Open("a.txt")
for {
buf := make([]byte, 6)
n, err := f.Read(buf)
fmt.Println(string(buf))
fmt.Printf("n: %v\n", n)
if err == io.EOF {
break
}
}
f.Close()
*/
/* buf := make([]byte, 10)
f2, _ := os.Open("a.txt")
// 从5开始读10个字节
n, _ := f2.ReadAt(buf, 5)
fmt.Printf("n: %v\n", n)
fmt.Printf("string(buf): %v\n", string(buf))
f2.Close() */
// 测试 a目录下面有b和c目录
/* f, _ := os.Open("a")
de, _ := f.ReadDir(-1)
for _, v := range de {
fmt.Printf("v.IsDir(): %v\n", v.IsDir())
fmt.Printf("v.Name(): %v\n", v.Name())
} */
// 定位
f, _ := os.Open("a.txt")
f.Seek(3, 0)
buf := make([]byte, 10)
n, _ := f.Read(buf)
fmt.Printf("n: %v\n", n)
fmt.Printf("string(buf): %v\n", string(buf))
f.Close()
}
func main() {
// openCloseFile()
// createFile()
readOps()
}
File文件写操作
package main
import "os"
func write() {
f, _ := os.OpenFile("a.txt", os.O_RDWR|os.O_TRUNC, 0755)
f.Write([]byte(" hello golang"))
f.Close()
}
func writeString() {
f, _ := os.OpenFile("a.txt", os.O_RDWR|os.O_APPEND, 0755)
f.WriteString("hello java")
f.Close()
}
func writeAt() {
f, _ := os.OpenFile("a.txt", os.O_RDWR, 0755)
f.WriteAt([]byte("aaa"), 3)
f.Close()
}
func main() {
// write()
// writeString()
writeAt()
}
进程相关操作
package main
import (
"fmt"
"os"
"time"
)
func main() {
// 获得当前正在运行的进程id
fmt.Printf("os.Getpid(): %v\n", os.Getpid())
// 父id
fmt.Printf("os.Getppid(): %v\n", os.Getppid())
//设置新进程的属性
attr := &os.ProcAttr{
//files指定新进程继承的活动文件对象
//前三个分别为,标准输入、标准输出、标准错误输出
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
//新进程的环境变量
Env: os.Environ(),
}
//开始一个新进程
p, err := os.StartProcess("C:\Windows\System32\notepad.exe", []string{"C:\Windows\System32\notepad.exe", "D:\a.txt"}, attr)
if err != nil {
fmt.Println(err)
}
fmt.Println(p)
fmt.Println("进程ID:", p.Pid)
//通过进程ID查找进程
p2, _ := os.FindProcess(p.Pid)
fmt.Println(p2)
//等待10秒,执行函数
time.AfterFunc(time.Second*10, func() {
//向p进程发送退出信号
p.Signal(os.Kill)
})
//等待进程p的退出,返回进程状态
ps, _ := p.Wait()
fmt.Println(ps.String())
}
环境相关
package main
import (
"fmt"
"os"
)
func main() {
// 获得所有环境变量
s := os.Environ()
fmt.Printf("s: %v\n", s)
// 获得某个环境变量
s2 := os.Getenv("GOPATH")
fmt.Printf("s2: %v\n", s2)
// 设置环境变量
os.Setenv("env1", "env1")
s2 = os.Getenv("aaa")
fmt.Printf("s2: %v\n", s2)
fmt.Println("-----------")
// 查找
s3, b := os.LookupEnv("env1")
fmt.Printf("b: %v\n", b)
fmt.Printf("s3: %v\n", s3)
// 替换
os.Setenv("NAME", "gopher")
os.Setenv("BURROW", "/usr/gopher")
fmt.Println(os.ExpandEnv("$NAME lives in ${BURROW}."))
// 清空环境变量
// os.Clearenv()
}