Go IO与文件

发布时间: 更新时间: 总字数:3206 阅读时间:7m 作者: IP上海 分享 网址

本文主要介绍使用Golang如何操作文件的读和写。

文件操作

文件分类:

  • 二进制文件
  • 文本文件

文件路径:

  • 绝对路径:/usr/bin/sh
  • 相对路径:跟参考目录(如程序运行目录、程序放置目录)有关,./logs/a.log、../share/a.txt

文件读、写、追加示例

package main

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

// ExampleFile
func main() {
	// 使用 go run 时,当前路径是程序的运行路径
	//path := "/etc/hosts.bak"
	path := "a.txt"

	// 打开文件
	file, err := os.Open(path)
	// 结束时关闭文件
	defer file.Close()
	if err != nil {
		// 1. open /etc/hosts.bak: no such file or directory
		// 2. file not prem
		fmt.Println(err.Error())
		//return
	} else {
		// 读文件
		fmt.Printf("%T\n", file)
		var bs []byte = make([]byte, 10) // [0 0 0 0 0 0 0 0 0 0]
		for {
			n, err := file.Read(bs)
			if err == io.EOF || err != nil {
				if err == io.EOF {
					fmt.Println(err.Error())
				}
				break
			}
			fmt.Print(string(bs[:n]))
		}
	}

	// 写文件
	file, err = os.Create(path)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	defer file.Close()
	// 每次 write 都执行 truncate 操作
	//n, err := file.Write([]byte("hello file"))
	n, err := file.WriteString("hello file2")
	if err != nil {
		return
	}
	if err != nil {
		fmt.Println(err.Error())
		return
	} else {
		fmt.Println("write bytes", n)
	}

	// 文件追加写
	//file, err = os.OpenFile(path, os.O_APPEND|os.O_APPEND|os.O_WRONLY, os.ModeAppend)
	file, err = os.OpenFile(path, os.O_APPEND|os.O_APPEND|os.O_WRONLY, os.ModePerm)

	if err != nil {
		fmt.Println(err.Error())
		return
	}
	defer file.Close()
	file.WriteString("\n")
	t := time.Now().Format("2006/01/02 15:04:05")
	fmt.Println(t)
	n, err = file.WriteString(t)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	fmt.Println("append write", n)
	file.WriteString("\n")

	// 通过 log 模块写
	log.SetOutput(file)
	log.SetPrefix("main:")
	log.SetFlags(log.Flags() | log.Llongfile)
	log.Println(t)
	log.Println("write from log modules")
}

文件指针操作

package main

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

// ExampleFilePoint
func main() {
	path := "/etc/hosts"
	//path := "a.txt"

	// 打开文件
	file, err := os.Open(path)
	// 结束时关闭文件
	defer file.Close()
	if err != nil {
		fmt.Println(err.Error())
		return
	} else {
		// 读文件
		fmt.Printf("%T\n", file)

		// 移动文件指针
		// offset 偏移量
		// whence 相对位置
		//  0 means relative to the origin of the file, os.SEEK_SET
		//  1 means relative to the current offset, os.SEEK_CUR
		//  2 means relative to the end, os.SEEK_END
		ret, err := file.Seek(0, 2)
		if err != nil {
			return
		}
		fmt.Println("current ", ret)

		var bs []byte = make([]byte, 10) // [0 0 0 0 0 0 0 0 0 0]
		for {
			n, err := file.Read(bs)
			if err == io.EOF || err != nil {
				if err == io.EOF {
					fmt.Println(err.Error())
				}
				break
			}
			fmt.Print(string(bs[:n]))
		}
	}
}

ioutil 工具库

ioutil 提供一次读/写文件所有内容的方法

package main

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

// ExampleIoutil
func main() {
	//path := "/etc/hosts"
	path := "a.txt"

	// 打开文件
	file, err := os.Open(path)
	// 结束时关闭文件
	defer file.Close()
	if err != nil {
		fmt.Println(err.Error())
		return
	} else {
		// 读文件
		fmt.Printf("%T\n", file)

		bs, err := ioutil.ReadAll(file)
		if err != nil {
			fmt.Printf(err.Error())
			return
		} else {
			fmt.Print(string(bs))
		}
	}

	// 一次读取所有文件 ioutil.ReadFile
	bs, err := ioutil.ReadFile(path)
	if err != nil {
		fmt.Printf(err.Error())
		return
	}
	fmt.Println(string(bs))

	// 一次写文件 ioutil.WriteFile
	err = ioutil.WriteFile(path, []byte("hello ioutil"), os.ModePerm)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
}

bufio 缓冲

package main

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

// ExampleBufio
func main() {
	//path := "/etc/hosts"
	path := "a.txt"

	// 每次扫描一行
	// 打开文件
	file, err := os.Open(path)
	// 结束时关闭文件
	defer file.Close()
	if err != nil {
		fmt.Println(err.Error())
		return
	} else {
		fmt.Printf("%T\n", file)

		// Scanner
		sc := bufio.NewScanner(file)
		for sc.Scan() {
			// 输出一行的内容
			fmt.Println(sc.Text())
		}
	}

	// 每次读指定字符
	file, err = os.Open(path)
	// 结束时关闭文件
	defer file.Close()
	if err != nil {
		fmt.Println(err.Error())
		return
	} else {
		// Reader
		reader := bufio.NewReader(file)
		bs := make([]byte, 10)
		for {
			n, err := reader.Read(bs)
			fmt.Println(n, err)
			if err != nil || err == io.EOF {
				fmt.Println(err.Error())
				break
			} else {
				fmt.Println(string(bs[:n]))
			}
		}
	}

	// 读一行
	file, err = os.Open(path)
	// 结束时关闭文件
	defer file.Close()
	if err != nil {
		fmt.Println(err.Error())
		return
	} else {
		// ReadLine
		reader := bufio.NewReader(file)
		for {
			bs, isPrefix, err := reader.ReadLine()
			fmt.Println(bs, isPrefix, err)
			if err != nil || err == io.EOF {
				fmt.Println(err.Error())
				break
			} else {
				fmt.Println(string(bs))
			}
		}
	}

	// 按指定分隔符读
	file, err = os.Open(path)
	// 结束时关闭文件
	defer file.Close()
	if err != nil {
		fmt.Println(err.Error())
		return
	} else {
		// ReadSlice
		fmt.Println("ReadSlice/ReadString")
		reader := bufio.NewReader(file)
		for {
			bs, err := reader.ReadSlice('\n')
			//bs, err := reader.ReadString('\n')
			if err != nil || err == io.EOF {
				fmt.Println(err.Error())
				break
			} else {
				fmt.Println(string(bs))
			}
		}
	}

	// 带缓冲 io 的 write
	file, err = os.Create(path)
	// 结束时关闭文件
	defer file.Close()
	if err != nil {
		fmt.Println(err.Error())
		return
	} else {
		writer := bufio.NewWriter(file)
		writer.Write([]byte("hello\n"))
		writer.WriteString("bufio\n")
		writer.Flush()
	}
}

复制文件 copyfile

package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"os"
)

/*
go run main.go -h
Usage of /var/folders/ql/nw5zyrg905z78mq4kwmq1lg00000gn/T/go-build10851603/b001/exe/main:
  -d string
        destination file path
  -s string
        source file path
*/

func main() {
	src := flag.String("s", "", "source file path")
	dest := flag.String("d", "", "destination file path")
	help := flag.Bool("h", false, "print help")
	flag.Usage = func() {
		fmt.Println(`Usage of copyfile, copy file from one to another.
copyfile -s <src-file> -d <dest-file>
Options:`)
		flag.PrintDefaults()
	}
	flag.Parse()
	if *help || (*src == "" && *dest == "") {
		flag.Usage()
	} else {
		copyfile(*src, *dest)
	}

	//fmt.Printf("%T %T %#v %#v\n", src, *dest, src, *dest)
}

func copyfile(src, dest string) {
	srcFile, err := os.Open(src)
	if err != nil {
		fmt.Printf(err.Error())
		return
	}
	defer srcFile.Close()

	destFile, err := os.Create(dest)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	defer destFile.Close()

	// 复制策略:1M copy 1次
	bs := make([]byte, 1024*1024)
	reader := bufio.NewReader(srcFile)
	writer := bufio.NewWriter(destFile)
	for {
		//n, err := srcFile.Read(bs)
		n, err := reader.Read(bs)
		if err != nil {
			if err != io.EOF {
				fmt.Println(err.Error())
			}
			break
		} else {
			//destFile.Write(bs[:n])
			nn, err := writer.Write(bs[:n])
			if err != nil {
				fmt.Println(err.Error())
				return
			}
			if n != nn {
				fmt.Printf("read %d bytes, write %d bytes", n, nn)
			}
		}
	}
	err = writer.Flush()
	if err != nil {
		fmt.Println(err.Error())
		return
	}
}

文件信息和操作

package main

import (
	"fmt"
	"os"
)

func main() {
	// 文件操作
	// 重命名文件
	os.Rename("b.txt", "c.txt")
	// 删除文件
	os.Remove("c.txt")

	// 文件夹操作
	// 创建文件夹(一级)
	os.Mkdir("test", 0644)
	// 创建文件夹(多级)
	err := os.MkdirAll("test/02", 0777)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	// 重命名
	os.Rename("test", "t01")
	// 删除,必须为空文件夹
	os.Remove("t01")
	// 删除文件夹下所有文件
	err = os.RemoveAll("test")
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	// 文件是否存在
	file, err := os.Open("a.txt")
	if err != nil {
		fmt.Println(err.Error())
		if os.IsNotExist(err) {
			fmt.Println("no such file")
		} else if os.IsPermission(err) {
			fmt.Println("file permission error")
		}
		return
	}
	defer file.Close()

	file, err = os.Open("a.txt")
	if err != nil {
		fmt.Println(err.Error())
		if os.IsNotExist(err) {
			fmt.Println("no such file")
		} else if os.IsPermission(err) {
			fmt.Println("file permission error")
		}
		return
	}
	defer file.Close()

	// 获取文件信息 Stat Lstat
	for _, path := range []string{"a.txt", "test", "noexist"} {
		info, err := os.Stat(path)
		if err != nil {
			fmt.Println(err.Error())
			if os.IsNotExist(err) {
				fmt.Println("no such file")
			} else if os.IsPermission(err) {
				fmt.Println("file permission error")
			}
			return
		}
		fmt.Printf("%T %#v\n", info, info)
		fmt.Println(info.Name(), info.Mode(), info.IsDir(), info.ModTime(), info.Size())
	}

	// 读取文件夹下所有文件
	dir := "../"
	fileInfo, err := os.Stat(dir)
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	if fileInfo.IsDir() {
		file, err := os.Open(dir)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		defer file.Close()

		fileInfos, err := file.Readdir(-1) // file.ReadDir(-1) file.Readdirnames(-1)
		if err != nil {
			return
		}
		for _, fInfo := range fileInfos {
			fmt.Printf("%T %#v\n", fInfo, fInfo)
			fmt.Println(fInfo.Name(), fInfo.IsDir(), fInfo.Size())
		}
	}
}

文件目录

package main

import (
	"fmt"
	"os"
)

func main() {
	// 一些目录
	fmt.Println(os.TempDir())
	fmt.Println(os.UserCacheDir())
	fmt.Println(os.UserConfigDir())
	fmt.Println(os.UserHomeDir())
	fmt.Println(os.Getwd())
	fmt.Println(os.Getenv("PATH"))
	fmt.Println(os.Setenv("ABC", "xyz"))
	fmt.Println(os.Getenv("ABC"))

	_ = os.Chdir("/tmp")
	fmt.Println(os.Getwd())
}

文件路径

package main

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

func main() {
	// 获取程序运行当前目录
	fmt.Println(filepath.Abs("."))

	fmt.Println(os.Args)
	fmt.Println(os.Args[0])
	// 获取当前可执行文件的绝对路径
	p, _ := filepath.Abs(os.Args[0])
	fmt.Println(p)

	// 获取文件名
	fmt.Println(path.Base("/etc/hosts"))     // hosts
	fmt.Println(filepath.Base("/etc/hosts")) // hosts
	fmt.Println(filepath.Base("/etc"))       // etc
	// 获取文件父目录
	fmt.Println(filepath.Dir("/etc/hosts")) // /etc
	d := filepath.Dir(p)

	// 获取文件后缀
	fmt.Println(filepath.Ext("/etc/hosts"))   //
	fmt.Println(filepath.Ext("/main.go"))     // .go
	fmt.Println(filepath.Ext("/xianbin.png")) // .png

	// 目录拼接
	fmt.Println(d + "/etc/main.conf")
	fmt.Println(path.Join(d, "etc", "main.conf"))
	home, _ := os.UserHomeDir()
	fmt.Println(path.Join(home, "etc", "main.conf"))

	fmt.Println(filepath.Split("/etc/hosts"))

	// 按文件格式搜索,示例获取当前目录下所有以 .go 结尾的文件
	fmt.Println(filepath.Glob("./*.go"))
	fmt.Println(filepath.Glob("../[ml]*/*.go"))

	// 获取当前路径下所有文件
	filepath.Walk(".", func(path string, info fs.FileInfo, err error) error {
		fmt.Printf("%s %T %#v\n", path, info, info)
		return nil
	})

	// 获取当前路径下所有文件夹
	filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
		fmt.Printf("%s %T %#v %v\n", path, d, d.Name(), d.IsDir())
		return err
	})
}

标准输入输出

package main

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

func main() {
	// fmt 打印到标准输出上 os.Stdout
	fmt.Println("hello world")
	_, err := os.Stdout.Write([]byte("hello world\n"))
	if err != nil {
		return
	}

	// 标准输入 os.Stdin
	bs := make([]byte, 20)
	n, err := os.Stdin.Read(bs)
	if err != nil {
		return
	}
	fmt.Println(string(bs[:n]))

	// 从命令行读入一行数据
	sc := bufio.NewScanner(os.Stdin)
	for sc.Scan() {
		fmt.Println(sc.Text())
		break
	}

	// 将内容输出到指定 io.Writer
	_, _ = fmt.Fprintln(os.Stdout, "hello world")
	_, _ = fmt.Fprintf(os.Stdout, "%s", "hello world")

	// os.Stderr
}

gob 示例

包 gob 管理 gob 流 - 在编码器(发送器)和解码器(接收器)之间交换的二进制值

gob 是 Golang 特有的,一般用于持久化数据。

gob 的使用,基本数据类型不需要注册,自定义的struct需要注册,如下:

gob.Register(&City{})
package main

import (
	"encoding/gob"
	"fmt"
	"os"
)

const TEMP_FILE = "test.gob"

func write(e interface{}) error {
	os.Remove(TEMP_FILE)
	file, err := os.Create(TEMP_FILE)
	if err != nil {
		return err
	}
	defer file.Close()

	encoder := gob.NewEncoder(file)
	err = encoder.Encode(e)
	if err != nil {
		return err
	}

	return nil
}

func read() ([]string, error) {
	var e []string
	file, err := os.Open(TEMP_FILE)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	decoder := gob.NewDecoder(file)
	err = decoder.Decode(&e)
	if err != nil {
		return nil, err
	}
	return e, nil
}

func read2() ([]City, error) {
	var c []City
	file, err := os.Open(TEMP_FILE)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	decoder := gob.NewDecoder(file)
	err = decoder.Decode(&c)
	if err != nil {
		return nil, err
	}
	return c, nil
}

type Addr struct {
	Street string
}

// 命名嵌套结构体
type City struct {
	name string
	Addr
}

func main() {
	// 数组验证
	cities := []string{"shanghai", "zhengzhou", "shangqiu"}
	_ = write(cities)

	e, err := read()
	fmt.Println(e, err)

	// 结构体验证,嵌套的属性必须可访问
	cities2 := []City{
		City{
			name: "shanghai",
			Addr: Addr{Street: "huangpuqu"},
		},
		{
			name: "zhengzhou",
			Addr: Addr{Street: "zhongyuanqu"},
		},
	}
	err = write(cities2)
	fmt.Println(err)
	c2, err := read2()
	fmt.Printf("%#v %#v\n", c2, err)
}

csv 示例

CSV 文件是一种以纯文本形式存储表格数据的简单文件格式。在 CSV 中,每列数据由特殊分隔符分割(如逗号,分号或制表符)

$ cat abc.csv
no,url
1,https://www.xiexianbin.cn/index.html
2,https://www.xiexianbin.cn/404.html

代码示例:

package main

import (
	"encoding/csv"
	"fmt"
	"io"
	"os"
	"strconv"
)

const TEMP_FILE = "test.csv"

func write(m map[int]string) error {
	//os.Remove(TEMP_FILE)
	file, err := os.Create(TEMP_FILE)
	if err != nil {
		return err
	}
	defer file.Close()

	// write title
	writer := csv.NewWriter(file)
	err = writer.Write([]string{"no", "url"})
	if err != nil {
		return err
	}

	// write content
	for k, v := range m {
		err = writer.Write([]string{strconv.Itoa(k), v})
		if err != nil {
			return err
		}
	}
	writer.Flush()

	return nil
}

func read() ([][]string, error) {
	var records [][]string
	file, err := os.Open(TEMP_FILE)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	reader := csv.NewReader(file)
	// records, err := reader.ReadAll()
	for {
		line, err := reader.Read()
		if err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
		records = append(records, line)
	}
	return records, nil
}

func main() {
	m := map[int]string{}
	m[1] = "https://www.xiexianbin.cn/index.html"
	m[2] = "https://www.xiexianbin.cn/404.html"
	err := write(m)
	fmt.Println(err)

	records, err := read()
	fmt.Println(records, err)
}

缓冲相关

实现 io.ReaderRead(b []byte) (n int, err error) 方法的:

  • strings.Reader
  • bufio.Reader
  • bytes.Reader
  • bytes.Buffer

实现 io.WriterWrite(p []byte) (int, error) 方法的:

  • bufio.Writer
  • strings.Builder
  • bytes.Buffer
package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"strings"
)

func main() {
	// string Reader 读数据
	reader := strings.NewReader("hello\n world")
	bs := make([]byte, 2)
	for {
		n, err := reader.Read(bs)
		if err != nil {
			if err != io.EOF {
				fmt.Println(err)
			}
			break
		}
		fmt.Print(string(bs[:n]))
	}
	fmt.Println()

	// 文件指针还原
	_, _ = reader.Seek(0, 0)

	// 使用 bufio 带缓冲的读
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}

	// string Builder 写数据,如字符串拼接,效率比较高
	//var builder strings.Builder
	builder := strings.Builder{}
	builder.Write([]byte("hello strings builder"))
	builder.WriteString("hello builder")
	fmt.Println(builder.String(), builder.Len())

	// https://pkg.go.dev/bytes
	// bytes.Reader
	breader := bytes.NewReader([]byte("hello bytes Reader"))
	bs = make([]byte, 5)
	for {
		n, err := breader.Read(bs)
		if err != nil {
			if err != io.EOF {
				fmt.Println(err)
			}
			break
		}
		fmt.Print(n, " ", string(bs[:n]))
	}
	fmt.Println()

	// bytes.Buffer
	// buffer := bytes.NewBuffer([]bytes(""))
	buffer := bytes.NewBufferString("hello bytes.Buffer")
	buffer.Write([]byte("\nxianbin"))
	buffer.WriteString("\nhave a nice day!")
	fmt.Println(buffer.String())

	bs = make([]byte, 4)
	fmt.Println(buffer.Read(bs))
	fmt.Println(string(bs))

	// 读到指定分隔符,包含分隔符 delim
	line, err := buffer.ReadString('\n')
	fmt.Println(line, err)

	bs2, err := buffer.ReadBytes('\n')
	fmt.Println(string(bs2), err)
}

md5 golang 实现

package main

import (
	"bufio"
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"flag"
	"fmt"
	"io"
	"os"
	"strings"
)

func md5reader(reader *bufio.Reader) (string, error) {
	m := md5.New()
	bs := make([]byte, 1024*1024)
	for {
		n, err := reader.Read(bs)
		if err != nil {
			if err == io.EOF {
				break
			}
			return "", err
		}
		m.Write(bs[:n])
	}

	return fmt.Sprintf("%x", m.Sum(nil)), nil
}

func md5str(content string) (string, error) {
	//m := md5.New()
	//m.Write([]byte(content))
	//return fmt.Sprintf("%x", m.Sum(nil))
	//return fmt.Sprintf("%x", md5.Sum([]byte(content)))
	reader := bufio.NewReader(strings.NewReader(content))
	return md5reader(reader)
}

func md5file1(path string) (string, error) {
	fileInfo, err := os.Stat(path)
	if err != nil {
		return "", err
	}
	if fileInfo.IsDir() {
		return "", fmt.Errorf("%s is direction.", path)
	}

	file, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer file.Close()

	reader := bufio.NewReader(file)
	return md5reader(reader)
}

// 小文件
func md5file2(path string) string {
	file, err := os.Open(path)
	if err != nil {
		return ""
	}
	defer file.Close()

	hash := md5.New()
	if _, err := io.Copy(hash, file); err != nil {
		return ""
	}

	return hex.EncodeToString(hash.Sum(nil))
}

// 大文件
func md5file3(path string) string {
	file, err := os.Open(path)
	if err != nil {
		return ""
	}
	defer file.Close()

	hash, buf := md5.New(), make([]byte, 1<<10)
	for {
		n, err := file.Read(buf)
		if n == 0 {
			if err == nil {
				continue
			}
			if err == io.EOF {
				break
			}
			return ""
		}

		io.Copy(hash, bytes.NewReader(buf[:n]))
	}

	return hex.EncodeToString(hash.Sum(nil))
}

// 快速,推荐
func md5file4(filePath string) (string, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return "", err
	}
	hash := md5.New()
	_, _ = io.Copy(hash, file)
	return hex.EncodeToString(hash.Sum(nil)), nil
}

func main() {
	s := flag.String("s", "", "strings to md5sum")
	f := flag.String("f", "", "file path to md5sum")
	h := flag.Bool("h", false, "show help")
	flag.Parse()
	flag.Usage = func() {
		fmt.Println(`
Usage md5sum [-s <strings>|-p <file path>]

Options:
`)
		flag.PrintDefaults()
	}

	if *h || (*s == "" && *f == "") {
		flag.Usage()
	}

	var md5result string
	var err error
	if *s != "" {
		md5result, err = md5str(*s)
	} else if *f != "" {
		md5result, err = md5file1(*f)
	}
	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println(md5result)
	}
}

bcrypt 示例

bcrypt 同一个的字符串,每次加密的结果均不相同,能有效防止暴力破解。

package main

import (
	"fmt"

	"golang.org/x/crypto/bcrypt"
)

func GeneratePassword(rowText string) (string, error) {
	bs, err := bcrypt.GenerateFromPassword([]byte(rowText), 0)
	if err == nil {
		return string(bs), nil
	}
	return "", nil
}

func CompareHashAndPassword(hashText, rowText string) error {
	return bcrypt.CompareHashAndPassword([]byte(hashText), []byte(rowText))
}

func ExampleT() {
	rowText := "123456"
	hashText, err := GeneratePassword(rowText)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("ok")
	}

	err = CompareHashAndPassword(hashText, rowText)
	if err != nil {
		panic(fmt.Sprintf("bcrypt.CompareHashAndPassword fail: %s", err.Error()))
	}

	// Output:
	// ok
}
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数