go标准库一 | 青训营笔记

67 阅读10分钟

这是我参与「第五届青训营」伴学笔记创作活动的第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
    1. Scan、Scanf 和 Scanln 从os.Stdin 中读取;
    2. Fscan、Fscanf 和 Fscanln 从指定的 io.Reader 中读取;
    3. Sscan、Sscanf 和 Sscanln 从实参字符串中读取。
    4. Scanln、Fscanln 和 Sscanln在换行符处停止扫描,且需要条目紧随换行符之后;
    5. Scanf、Fscanf 和 Sscanf需要输入换行符来匹配格式中的换行符;其它函数则将换行符视为空格。
    6. 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目录下面有bc目录
	/* 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()

}