Go语言入门——进阶语法篇(四)
文章目录
- 异常处理与文件
- 异常处理
- error 处理
- panic 与 recover
- 延迟处理
- 文件读写
- 带缓冲区
- 使用 ioutil
- 文件追加
- 文件的其他操作
- 获取文件信息
- 判断文件是否存在
- 文件拷贝
- 移动、删除和重命名
- 欢迎关注我的公众号:编程之路从0到1
异常处理与文件
异常处理
error 处理
Go语言没有类似Java或Python那种try...catch...
机制处理异常,Go的哲学是与众不同的,Go的设计者认为主流的异常处理机制是一种被过度滥用的技巧,而且存在很大的潜在危害,Go的异常处理(或者说是错误处理)是一种非常简单直观的方式。通常的,我们在写Java、Python之类的代码时,遇到可能存在的异常,直接用try
括起来,使用catch
捕获,然后就万事大吉了,当系统长时间的运行时,大大增加了不稳定性,所积累的问题可能在某一刻爆发。而Go者使用一种称为"恐慌的"
机制,在有必要时,直接让系统宕机,让问题发生时立刻暴露出来,不必累积。很难说哪种设计更好,但Go语言确实简化了代码。
Go语言引入了一个错误处理的标准接口:error
接口,并习惯性的默认将错误作为最后一个返回值,当然,如果有的话。如果我们要自定义错误类型,实现该接口即可
type error interface {
Error() string
}
错误处理示例
type MyError struct {
content string
errorCode int
}
// 让MyError实现error接口
func (this *MyError) Error() string {
return fmt.Sprintf("%d:%s",this.errorCode,this.content)
}
func div(a, b int) (r int, err error) {
var e *MyError = new(MyError)
if b == 0 {
e.errorCode = 101
e.content = "除数不能为0"
return 0, e
}
return a / b, nil
}
func main() {
r, e := div(1, 0)
if e != nil {
// 错误不为空,打印错误
fmt.Println(e)
return
}
fmt.Println(r)
}
打印结果:
101:除数不能为0
自定义类型实现error
接口可以提供更丰富的错误信息,但有时候我们希望快速的生成一个简单的错误,而不是写个结构体,那么Go还提供了一种快捷创建错误的方式,使用errors
包
package main
import (
"fmt"
"errors"
)
var errByZero = errors.New("除数不能为0")
func div(a, b int) (r int, err error) {
if b == 0 {
return 0, errByZero
}
return a / b, nil
}
注意,为了提升性能,errors.New
方法不建议在函数中调用,错误的内容是不会变的,可以在函数外声明好需要的错误,就如同声明一些常量一样。该方法虽然简单,但是包含的错误信息有限,酌情使用。
panic 与 recover
panic
词义为恐慌,recover
则表示恢复。
仍以除数是0为例
func div(a, b int) int {
if b == 0 {
panic("crash:除数为0")
}
return a / b
}
func main() {
r := div(1, 0)
fmt.Println(r)
}
运行代码后,程序直接奔溃,并输出了调用信息
panic: crash:除数为0
goroutine 1 [running]:
main.div(...)
C:/Users/ysk/Desktop/hello.go:12
main.main()
C:/Users/ysk/Desktop/hello.go:18 +0x41
exit status 2
这样,当我在开发和调试时,出现问题,通过手动调用panic
让程序崩溃,及时发现并解决问题,包括生成环境中的测试,而不是等到系统上线运行一段时候之后才发现问题。
有时候,我们可能很害怕奔溃,recover
则可以在这种奔溃发生时,恢复程序,使得程序可以继续运行。简单说,panic
和recover
的组合,可以模拟实现Java中的try...catch
机制,将异常捕获,而不是继续向上传递。但这并不是Go语言所推崇的用法。
func main() {
// 加上一段代码,defer后面跟一个匿名函数,匿名函数中使用recover捕获到错误
defer func(){
err := recover()
fmt.Printf("处理:%s\n",err)
}()
r := div(1, 0)
fmt.Println(r)
}
打印结果:
处理:crash:除数为0
可以看到,使用recover
处理后,程序不再奔溃了。
延迟处理
上面示例出现了一个关键字defer
,该关键字就是用于延迟处理。我们上面说了Java中的try
、catch
,那怎么能没有finally
呢。defer
其实就相当于finally
,在整个函数调用完后,最后执行一些关闭句柄的功能。Go中,defer
除了关闭句柄,还可用于释放并发锁。
func main() {
defer fmt.Println("这是defer调用")
fmt.Println("Hello,world!")
fmt.Println("Hello,go!")
}
打印结果:
Hello,world!
Hello,go!
这是defer调用
可以看到,defer
语句写在最先,但是却在最后才被执行。同一个函数中是可以使用多个defer
语句的,多个defer
语句的执行顺序遵循栈结构特点,先进后出,最先的defer
语句最后执行
func main() {
defer fmt.Println("这是defer语句1")
defer fmt.Println("这是defer语句2")
fmt.Println("Hello,world!")
defer fmt.Println("这是defer调用3")
fmt.Println("Hello,go!")
defer fmt.Println("这是defer调用4")
}
打印结果:
Hello,world!
Hello,go!
这是defer调用4
这是defer调用3
这是defer语句2
这是defer语句1
文件读写
带缓冲区
package main
import (
"fmt"
"os"
"io"
"bufio" // 导入缓冲包
)
func main() {
// 1. 创建文件
file , err := os.Create("D:/test.txt")
if err != nil{
fmt.Println("创建文件失败")
return
}
// 2. 关闭文件
defer file.Close()
// 3. 创建写入缓冲
w := bufio.NewWriter(file)
// 4. 写入字符串
len, err := w.WriteString("hello world")
if err != nil {
fmt.Println("写入失败")
}else{
fmt.Printf("写入了%d个字符\n",len)
}
// 4. 刷新缓冲,写入硬盘
w.Flush()
// -------------------分割线---------------------
// 1. 打开文件
rfile, e := os.Open("D:/test.txt")
if e != nil{
fmt.Println("打开文件失败")
return
}
// 2.关闭文件
defer rfile.Close()
// 3. 创建读取缓冲
r := bufio.NewReader(rfile)
// 4. 读取文件。使用带缓冲的方式读取文件,只能一行一行的读取
// 该方式适用于高效的读写大文件
for {
str,err := r.ReadString('\n')
fmt.Println(str)
if err == io.EOF { // io.EOF表示文件的末尾
break
}
}
}
小结
- 使用
os.Create
创建文件会覆盖掉已存在的文件 - 缓冲区默认大小为4096,可以使用
NewReaderSize
和NewWriterSize
在创建缓冲时指定大小 - 如需以二进制方式读写文件,将
WriteString
、ReadString
换成WriteByte(c byte)
和ReadByte()
,亦可使用Write(p []byte)
和Read(p []byte)
方法。区别是WriteByte
和ReadByte
每次读写一个字节,Write
和Read
每次读写一个切片的字节。
使用 ioutil
在操作小文件时,可以不指定缓冲区,那么就可以使用一种更简单的方式读写文件。
package main
import (
"fmt"
"io/ioutil"
)
func main() {
// 1. 字符串转字节切片
buf := []byte("golang write string")
// 2. 将字节切片直接写入文件,文件不存在则创建,存在则覆盖
err := ioutil.WriteFile("D:/io_test.txt", buf, 0666)
if err != nil {
fmt.Println("写入失败")
} else {
fmt.Println("写入成功")
}
// -------------------分割线---------------------
// 1. 读取指定文件,返回字节切片
data, e := ioutil.ReadFile("D:/io_test.txt")
if e != nil {
fmt.Println(e)
} else {
// 2. 将字节切片转字符串输出
fmt.Println(string(data))
}
}
小结
- 使用
ioutil
不需要手动打开和关闭文件,打开和关闭操作已被封装了 - 使用
ioutil.WriteFile
仍然存在覆盖已有文件的问题,如需对文件进行追加操作,应使用其他方式 WriteFile
和ReadFile
是以字节的方式操作文件,如需处理文本文件,应手动转换字节与字符串
文件追加
很多时候我们不希望新文件覆盖旧文件,而是在旧文件中继续添加内容。这时候必须使用指定模式的方式来打开文件。
package main
import (
"fmt"
"bufio"
"os"
)
func main() {
// 1. 打开文件,指定文件操作模式: 读写追加
file,err := os.OpenFile("D:/test.txt",os.O_RDWR|os.O_APPEND,0666)
if err != nil{
fmt.Println("打开文件失败")
return
}
// 2. 函数执行结束关闭文件
defer file.Close()
// 3. 创建写缓冲
writer := bufio.NewWriter(file)
writer.WriteString("text content")
// 4. 刷新缓冲
writer.Flush()
}
常用组合
模式组合 | 说明 |
---|---|
O_RDONLY | 只读模式 |
O_WRONLY | 只写模式 |
O_RDWR | 可读可写模式 |
os.O_WRONLY|os.O_CREATE | 写|创建 |
os.O_WRONLY|os.O_TRUNC | 写|覆盖 |
O_WRONLY|os.O_APPEND | 写|追加 |
os.O_RDWR|os.O_APPEND | 读写|追加 |
小结
os.OpenFile
函数的最后一个参数表示Unix
系统中的文件权限,在Windows系统上被忽略。
文件的其他操作
获取文件信息
package main
import (
"fmt"
"os"
)
func main() {
// 获取文件信息
fileInfo, err := os.Stat("D:/test.txt")
if err != nil{
fmt.Println("打开文件失败")
return
}
fmt.Println("文件名:", fileInfo.Name())
fmt.Println("文件大小:", fileInfo.Size())
fmt.Println("文件权限:", fileInfo.Mode())
fmt.Println("最后修改时间:", fileInfo.ModTime())
fmt.Println("是否是文件夹:", fileInfo.IsDir())
fmt.Printf("系统信息:%+v\n", fileInfo.Sys())
}
判断文件是否存在
package main
import (
"fmt"
"os"
)
// 定义一个函数,判断文件是否存在
func exists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func main() {
if b,_ := exists("D:/test.txt");b {
fmt.Println("文件存在")
}else{
fmt.Println("文件不存在")
}
}
文件拷贝
package main
import (
"fmt"
"os"
"io"
"bufio"
)
func main() {
var dst string = "D:/files/test.txt" // 目标路径
var src string = "D:/test.txt" // 源路径
srcFile ,err := os.Open(src)
if err != nil {
fmt.Println("打开失败:",err)
return
}
defer srcFile.Close()
reader := bufio.NewReader(srcFile)
dstFile,err := os.OpenFile(dst,os.O_WRONLY|os.O_CREATE,0666)
if err != nil {
fmt.Println("打开失败:",err)
return
}
defer dstFile.Close()
writer := bufio.NewWriter(dstFile)
// 使用Coyp函数完成文件拷贝
if _, err = io.Copy(writer, reader); err != nil {
fmt.Println("拷贝失败:",err)
return
}
fmt.Println("拷贝成功!")
}
移动、删除和重命名
package main
import (
"fmt"
"os"
)
func main() {
src ,dst := "D:/test.txt","D:/workspace/test.txt"
// 文件移动(Rename既可重命名也可移动文件)
err := os.Rename(src, dst)
if err != nil {
fmt.Println("移动失败:",err)
return
}
// 文件删除
err = os.Remove("D:/io_test.txt")
if err != nil {
fmt.Println("删除失败:",err)
return
}
// 文件重命名
oldName, newName := "D:/logcat.txt", "D:/log.txt"
err = os.Rename(oldName, newName)
if err != nil {
fmt.Println("重命名失败:",err)
return
}
}
还没有评论,来说两句吧...