code学习

【go】异常,字符串操作,文件处理

文章目录

  • ​​9 异常处理​​
  • ​​9.1 error接口​​
  • ​​9.2 panic​​
  • ​​9.3 recover​​
  • ​​10. 文本文件处理​​
  • ​​10.1 字符串处理​​
  • ​​10.1.1 字符串操作​​
  • ​​10.1.1.1 Contains​​
  • ​​10.1.1.2 Join​​
  • ​​10.1.1.3 Index​​
  • ​​10.1.1.4 Repeat​​
  • ​​10.1.1.5 Replace​​
  • ​​10.1.1.6 Split​​
  • ​​10.1.1.7 Trim​​
  • ​​10.1.1.8 Fields​​
  • ​​10.1.2 字符串转换​​
  • ​​10.1.2.1 Append​​
  • ​​10.1.2.2 Format​​
  • ​​10.1.2.3 Parse​​
  • ​​10.2 正则表达式​​
  • ​​10.3 JSON处理​​
  • ​​10.3.1 编码JSON​​
  • ​​10.3.1.1 通过结构体生成JSON​​
  • ​​10.3.1.2 通过map生成JSON​​
  • ​​10.3.2 解码JSON​​
  • ​​10.3.2.1 解析到结构体​​
  • ​​10.3.2.2 解析到interface​​
  • ​​10.4 文件操作​​
  • ​​10.4.1 相关api介绍​​
  • ​​10.4.1.1 建立与打开文件​​
  • ​​10.4.1.2 写文件​​
  • ​​10.4.1.3 读文件​​
  • ​​10.4.1.4 删除文件​​
  • ​​10.4.2 示例代码​​
  • ​​10.4.2.1 写文件​​
  • ​​10.4.2.2 读文件​​
  • ​​10.4.3 案例:拷贝文件​​
  • ​​10.4.4 输入输出设备文件的使用​​

9 异常处理

9.1 error接口

Go语言引入了一个关于错误处理的标准模式,即error接口,它是Go语言内建的接口类型,该接口的定义如下:

type error interface {
    Error() string
}      

Go语言的标准库代码包errors为用户提供如下方法:

package errors

type errorString struct { 
    text string 
}

func New(text string) error { 
    return &errorString{text} 
}

func (e *errorString) Error() string { 
    return e.text 
}      

注意:这里New 函数是把errstring赋值给error 接口类型(与接口类型赋值相同,这里是把结构体类型赋值给接口类型)

实际使用可以参考:

var tmp error
tmp = &errorString{"xxx"}      

另一个可以生成error类型值的方法是调用fmt包中的Errorf函数:

package fmt
import "errors"

func Errorf(format string, args ...interface{}) error {
    return errors.New(Sprintf(format, args...))
}      

示例代码:

import (
    "errors"
    "fmt"
)

func main() {
    var err1 error = errors.New("a normal err1")
    fmt.Println(err1) //a normal err1

    var err2 error = fmt.Errorf("%s", "a normal err2")
    fmt.Println(err2) //a normal err2
}      

函数通常在最后的返回值中返回错误信息:

import (
    "errors"
    "fmt"
)

func Divide(a, b float64) (result float64, err error) {
    if b == 0 {
        result = 0.0
        err = errors.New("runtime error: divide by zero")
        return
    }

    result = a / b
    err = nil
    return
}

func main() {
    r, err := Divide(10.0, 0)
    if err != nil {
        fmt.Println(err) //错误处理 runtime error: divide by zero
    } else {
        fmt.Println(r) // 使用返回值
    }
}      

9.2 panic

在通常情况下,向程序使用方报告错误状态的方式可以是返回一个额外的error类型值。

但是,当遇到不可恢复的错误状态的时候,如数组访问越界、空指针引用等,这些运行时错误会引起painc异常。这时,上述错误处理方式显然就不适合了。反过来讲,在一般情况下,我们不应通过调用panic函数来报告普通的错误,而应该只把它作为报告致命错误的一种方式。当某些不应该发生的场景发生时,我们就应该调用panic。

一般而言,当panic异常发生时,程序会中断运行,并立即执行在该goroutine(可以先理解成线程,在中被延迟的函数(defer 机制)。随后,程序崩溃并输出日志信息。日志信息包括panic value和函数调用的堆栈跟踪信息。

不是所有的panic异常都来自运行时,直接调用内置的panic函数也会引发panic异常;panic函数接受任何值作为参数。

func panic(v interface{})      

调用panic函数引发的panic异常:

func TestA() {
    fmt.Println("func TestA()")
}

func TestB() {
    panic("func TestB(): panic")
}

func TestC() {
    fmt.Println("func TestC()")
}

func main() {
    TestA()
    TestB()//TestB()发生异常,中断程序
    TestC()
}      

运行结果:

【go】异常,字符串操作,文件处理

内置的panic函数引发的panic异常:

func TestA() {
    fmt.Println("func TestA()")
}

func TestB(x int) {
    var a [10]int
    a[x] = 222 //x值为11时,数组越界
}

func TestC() {
    fmt.Println("func TestC()")
}

func main() {
    TestA()
    TestB(11)//TestB()发生异常,中断程序
    TestC()
}      

运行结果:

【go】异常,字符串操作,文件处理

9.3 recover

运行时panic异常一旦被引发就会导致程序崩溃。这当然不是我们愿意看到的,因为谁也不能保证程序不会发生任何运行时错误。

不过,Go语言为我们提供了专用于“拦截”运行时panic的内建函数——recover。它可以是当前的程序从运行时panic的状态中恢复并重新获得流程控制权。

func recover() interface{}      

注意:recover只有在defer调用的函数中有效。

如果调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。

示例代码:

func TestA() {
    fmt.Println("func TestA()")
}

func TestB() (err error) {
    defer func() { //在发生异常时,设置恢复
        if x := recover(); x != nil {
            //panic value被附加到错误信息中;
          //并用err变量接收错误信息,返回给调用者。
            err = fmt.Errorf("internal error: %v", x)
        }
    }()

    panic("func TestB(): panic")
}

func TestC() {
    fmt.Println("func TestC()")
}

func main() {
    TestA()
    err := TestB()
    fmt.Println(err)
    TestC()

    /*
        运行结果:
        func TestA()
        internal error: func TestB(): panic
        func TestC()
    */
}      

延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后⼀个错误可被捕获:

func test() {
    defer func() {
        fmt.Println(recover())
    }()

    defer func() {
        panic("defer panic")
    }()

    panic("test panic")
}

func main() {
    test()
    //运行结果:defer panic
}      

10. 文本文件处理

10.1 字符串处理

字符串在开发中经常用到,包括用户的输入,数据库读取的数据等,我们经常需要对字符串进行分割、连接、转换等操作,我们可以通过Go标准库中的strings和strconv两个包中的函数进行相应的操作。

10.1.1 字符串操作

下面这些函数来自于strings包,这里介绍一些我平常经常用到的函数,更详细的请参考官方的文档。

10.1.1.1 Contains

func Contains(s, substr string) bool
功能:字符串s中是否包含substr,返回bool值

示例代码:
    fmt.Println(strings.Contains("seafood", "foo"))
    fmt.Println(strings.Contains("seafood", "bar"))
    fmt.Println(strings.Contains("seafood", ""))
    fmt.Println(strings.Contains("", ""))
    //运行结果:
    //true
    //false
    //true
    //true      

10.1.1.2 Join

func Join(a []string, sep string) string
功能:字符串链接,把slice a通过sep链接起来

示例代码:
    s := []string{"foo", "bar", "baz"}
    fmt.Println(strings.Join(s, ", "))
    //运行结果:foo, bar, baz      

10.1.1.3 Index

func Index(s, sep string) int
功能:在字符串s中查找sep所在的位置,返回位置值,找不到返回-1

示例代码:
    fmt.Println(strings.Index("chicken", "ken"))
    fmt.Println(strings.Index("chicken", "dmr"))
    //运行结果:
    //    4
    //    -1      

10.1.1.4 Repeat

func Repeat(s string, count int) string
功能:重复s字符串count次,最后返回重复的字符串

示例代码:
    fmt.Println("ba" + strings.Repeat("na", 2))
    //运行结果:banana      

10.1.1.5 Replace

func Replace(s, old, new string, n int) string
功能:在s字符串中,把old字符串替换为new字符串,n表示替换的次数,小于0表示全部替换

示例代码:
    fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))
    fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))
    //运行结果:
    //oinky oinky oink
    //moo moo moo      

10.1.1.6 Split

func Split(s, sep string) []string
功能:把s字符串按照sep分割,返回slice

示例代码:
    fmt.Printf("%q\n", strings.Split("a,b,c", ","))
    fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
    fmt.Printf("%q\n", strings.Split(" xyz ", ""))
    fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))
    //运行结果:
    //["a" "b" "c"]
    //["" "man " "plan " "canal panama"]
    //[" " "x" "y" "z" " "]
    //[""]      

10.1.1.7 Trim

func Trim(s string, cutset string) string
功能:在s字符串的头部和尾部去除cutset指定的字符串

示例代码:
    fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! "))
    //运行结果:["Achtung"]      

10.1.1.8 Fields

func Fields(s string) []string
功能:去除s字符串的空格符,并且按照空格分割返回slice

示例代码:
    fmt.Printf("Fields are: %q", strings.Fields("  foo bar  baz   "))
    //运行结果:Fields are: ["foo" "bar" "baz"]      

Fields方法和Split方法的区别,Fields只能分割空格,而Split可以分割指定的字符串

10.1.2 字符串转换

字符串转化的函数在strconv中,如下也只是列出一些常用的。

10.1.2.1 Append

Append 系列函数将整数等转换为字符串后,添加到现有的字节数组中。

示例代码:
    str := make([]byte, 0, 100)
    str = strconv.AppendInt(str, 4567, 10) //以10进制方式追加
    str = strconv.AppendBool(str, false)
    str = strconv.AppendQuote(str, "abcdefg")
    str = strconv.AppendQuoteRune(str, '单')

    fmt.Println(string(str)) //4567false"abcdefg"'单'      

10.1.2.2 Format

Format 系列函数把其他类型的转换为字符串。

示例代码:
    a := strconv.FormatBool(false)
    b := strconv.FormatInt(1234, 10)
    c := strconv.FormatUint(12345, 10)
    d := strconv.Itoa(1023)
    fmt.Println(a, b, c, d) //false 1234 12345 1023      

10.1.2.3 Parse

Parse 系列函数把字符串转换为其他类型。

示例代码:
package main

import (
    "fmt"
    "strconv"
)

func checkError(e error) {
    if e != nil {
        fmt.Println(e)
    }
}
func main() {
    a, err := strconv.ParseBool("false")
    checkError(err)
    b, err := strconv.ParseFloat("123.23", 64)
    checkError(err)
    c, err := strconv.ParseInt("1234", 10, 64)
    checkError(err)
    d, err := strconv.ParseUint("12345", 10, 64)
    checkError(err)
    e, err := strconv.Atoi("1023")
    checkError(err)
    fmt.Println(a, b, c, d, e) //false 123.23 1234 12345 1023
}      

字符串操作示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    //"hellogo"中是否包含"hello", 包含返回true, 不包含返回false
    fmt.Println(strings.Contains("hellogo", "hello"))
    fmt.Println(strings.Contains("hellogo", "abc"))

    //Joins 组合
    s := []string{"abc", "hello", "mike", "go"}
    buf := strings.Join(s, "x")
    fmt.Println("buf = ", buf)

    //Index, 查找子串的位置
    fmt.Println(strings.Index("abcdhello", "hello"))
    fmt.Println(strings.Index("abcdhello", "go")) //不包含子串返回-1

    buf = strings.Repeat("go", 3)
    fmt.Println("buf = ", buf) //"gogogo"

    //Split 以指定的分隔符拆分
    buf = "hello@abc@go@mike"
    s2 := strings.Split(buf, "@")
    fmt.Println("s2 = ", s2)

    //Trim去掉两头的字符
    buf = strings.Trim("      are u ok?          ", " ") //去掉2头空格
    fmt.Printf("buf = #%s#\n", buf)

    //去掉空格,把元素放入切片中
    s3 := strings.Fields("      are u ok?          ")
    //fmt.Println("s3 = ", s3)
    for i, data := range s3 {
        fmt.Println(i, ", ", data)
    }

}      

10.2 正则表达式

正则表达式是一种进行模式匹配和文本操纵的复杂而又强大的工具。虽然正则表达式比纯粹的文本匹配效率低,但是它却更灵活。按照它的语法规则,随需构造出的匹配模式就能够从原始文本中筛选出几乎任何你想要得到的字符组合。

Go语言通过regexp标准包为正则表达式提供了官方支持,如果你已经使用过其他编程语言提供的正则相关功能,那么你应该对Go语言版本的不会太陌生,但是它们之间也有一些小的差异,因为Go实现的是RE2标准,除了\C,详细的语法描述参考:http://code.google.com/p/re2/wiki/Syntax

其实字符串处理我们可以使用strings包来进行搜索(Contains、Index)、替换(Replace)和解析(Split、Join)等操作,但是这些都是简单的字符串操作,他们的搜索都是大小写敏感,而且固定的字符串,如果我们需要匹配可变的那种就没办法实现了,当然如果strings包能解决你的问题,那么就尽量使用它来解决。因为他们足够简单、而且性能和可读性都会比正则好。

示例代码:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    context1 := "3.14 123123 .68 haha 1.0 abc 6.66 123."

    //MustCompile解析并返回一个正则表达式。如果成功返回,该Regexp就可用于匹配文本。
    //解析失败时会产生panic
    // \d 匹配数字[0-9],d+ 重复>=1次匹配d,越多越好(优先重复匹配d)
    exp1 := regexp.MustCompile(`\d+\.\d+`)

    //返回保管正则表达式所有不重叠的匹配结果的[]string切片。如果没有匹配到,会返回nil。
    //result1 := exp1.FindAllString(context1, -1) //[3.14 1.0 6.66]
    result1 := exp1.FindAllStringSubmatch(context1, -1) //[[3.14] [1.0] [6.66]]

    fmt.Printf("%v\n", result1)
    fmt.Printf("\n------------------------------------\n\n")

    context2 := `
        <title>标题</title>
        <div>你过来啊</div>
        <div>hello mike</div>
        <div>你大爷</div>
        <body>呵呵</body>
    `
    //(.*?)被括起来的表达式作为分组
    //匹配<div>xxx</div>模式的所有子串
    exp2 := regexp.MustCompile(`<div>(.*?)</div>`)
    result2 := exp2.FindAllStringSubmatch(context2, -1)

    //[[<div>你过来啊</div> 你过来啊] [<div>hello mike</div> hello mike] [<div>你大爷</div> 你大爷]]
    fmt.Printf("%v\n", result2)
    fmt.Printf("\n------------------------------------\n\n")

    context3 := `
        <title>标题</title>
        <div>你过来啊</div>
        <div>hello 
        mike
        go</div>
        <div>你大爷</div>
        <body>呵呵</body>
    `
    exp3 := regexp.MustCompile(`<div>(.*?)</div>`)
    result3 := exp3.FindAllStringSubmatch(context3, -1)

    //[[<div>你过来啊</div> 你过来啊] [<div>你大爷</div> 你大爷]]
    fmt.Printf("%v\n", result3)
    fmt.Printf("\n------------------------------------\n\n")

    context4 := `
        <title>标题</title>
        <div>你过来啊</div>
        <div>hello 
        mike
        go</div>
        <div>你大爷</div>
        <body>呵呵</body>
    `
    exp4 := regexp.MustCompile(`<div>(?s:(.*?))</div>`)
    result4 := exp4.FindAllStringSubmatch(context4, -1)

    /*
        [[<div>你过来啊</div> 你过来啊] [<div>hello
            mike
            go</div> hello
            mike
            go] [<div>你大爷</div> 你大爷]]
    */
    fmt.Printf("%v\n", result4)
    fmt.Printf("\n------------------------------------\n\n")

    for _, text := range result4 {
        fmt.Println(text[0]) //带有div
        fmt.Println(text[1]) //不带带有div
        fmt.Println("================\n")
    }
}      

注意:下面这句代码的用法,​

​.*?​

​表示匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复(惰性匹配),在这里的应用实例是匹配两个div标签当中的任意内容

exp3 := regexp.MustCompile(`<div>(.*?)</div>`)      

正则表达式处理例子2

package main

import (
    "fmt"
    "regexp"
)

func main() {

    buf := "abc azc a7c aac 888 a9c  tac"

    //1) 解释规则, 它会解析正则表达式,如果成功返回解释器
    //reg1 := regexp.MustCompile(`a.c`)
    //reg1 := regexp.MustCompile(`a[0-9]c`)
    reg1 := regexp.MustCompile(`a\dc`)
    if reg1 == nil { //解释失败,返回nil
        fmt.Println("regexp err")
        return
    }

    //2) 根据规则提取关键信息
    result1 := reg1.FindAllStringSubmatch(buf, -1)
    fmt.Println("result1 = ", result1)

}      

正则表达式处理例子3

package main

import (
    "fmt"
    "regexp"
)

func main() {
    buf := "43.14 567 agsdg 1.23 7. 8.9 1sdljgl 6.66 7.8   "

    //解释正则表达式, +匹配前一个字符的1次或多次
    reg := regexp.MustCompile(`\d+\.\d+`)
    if reg == nil {
        fmt.Println("MustCompile err")
        return
    }

    //提取关键信息
    //result := reg.FindAllString(buf, -1)
    result := reg.FindAllStringSubmatch(buf, -1)
    fmt.Println("result = ", result)

}      

正则表达式参考图片

【go】异常,字符串操作,文件处理

10.3 JSON处理

JSON (JavaScript Object Notation)是一种比XML更轻量级的数据交换格式,在易于人们阅读和编写的同时,也易于程序解析和生成。尽管JSON是JavaScript的一个子集,但JSON采用完全独立于编程语言的文本格式,且表现为键/值对集合的文本描述形式(类似一些编程语言中的字典结构),这使它成为较为理想的、跨平台、跨语言的数据交换语言。

【go】异常,字符串操作,文件处理

开发者可以用 JSON 传输简单的字符串、数字、布尔值,也可以传输一个数组,或者一个更复杂的复合结构。在 Web 开发领域中, JSON被广泛应用于 Web 服务端程序和客户端之间的数据通信。

Go语言内建对JSON的支持。使用Go语言内置的encoding/json 标准库,开发者可以轻松使用Go程序生成和解析JSON格式的数据。

JSON官方网站:http://www.json.org/

在线格式化:http://www.json.cn/

10.3.1 编码JSON

10.3.1.1 通过结构体生成JSON

使用json.Marshal()函数可以对一组数据进行JSON格式的编码。 json.Marshal()函数的声明如下:

func Marshal(v interface{}) ([]byte, error)      

还有一个格式化输出:

// MarshalIndent 很像 Marshal,只是用缩进对输出进行格式化
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)      

格式化输出,有助于查看返回的json结果,呈现的效果如下:

【go】异常,字符串操作,文件处理
  1. 编码JSON

示例代码:

package main

import (
    "encoding/json"
    "fmt"
)

type IT struct {
    Company  string
    Subjects []string
    IsOk     bool
    Price    float64
}

func main() {
    t1 := IT{"itcast", []string{"Go", "C++", "Python", "Test"}, true, 666.666}

    //生成一段JSON格式的文本
    //如果编码成功, err 将赋于零值 nil,变量b 将会是一个进行JSON格式化之后的[]byte类型
    //b, err := json.Marshal(t1)
    //输出结果:{"Company":"itcast","Subjects":["Go","C++","Python","Test"],"IsOk":true,"Price":666.666}

    b, err := json.MarshalIndent(t1, "", "    ")
    /*
        输出结果:
        {
            "Company": "itcast",
            "Subjects": [
                "Go",
                "C++",
                "Python",
                "Test"
            ],
            "IsOk": true,
            "Price": 666.666
        }
    */
    if err != nil {
        fmt.Println("json err:", err)
    }
    fmt.Println(string(b))
}      
  1. struct tag
    【go】异常,字符串操作,文件处理

我们看到上面的输出字段名的首字母都是大写的,如果你想用小写的首字母怎么办呢?把结构体的字段名改成首字母小写的?JSON输出的时候必须注意,只有导出的字段(首字母是大写)才会被输出,如果修改字段名,那么就会发现什么都不会输出,所以必须通过struct tag定义来实现。

针对JSON的输出,我们在定义struct tag的时候需要注意的几点是:

 字段的tag是"-",那么这个字段不会输出到JSON

 tag中带有自定义名称,那么这个自定义名称会出现在JSON的字段名中

 tag中如果带有"omitempty"选项,那么如果该字段值为空,就不会输出到JSON串中

 如果字段类型是bool, string, int, int64等,而tag中带有",string"选项,那么这个字段在输出到JSON的时候会把该字段对应的值转换成JSON字符串

示例代码:

type IT struct {
    //Company不会导出到JSON中
    Company string `json:"-"`

    // Subjects 的值会进行二次JSON编码
    Subjects []string `json:"subjects"`

    //转换为字符串,再输出
    IsOk bool `json:",string"`

    // 如果 Price 为空,则不输出到JSON串中
    Price float64 `json:"price, omitempty"`
}

func main() {
    t1 := IT{Company: "itcast", Subjects: []string{"Go", "C++", "Python", "Test"}, IsOk: true}

    b, err := json.Marshal(t1)
    //json.MarshalIndent(t1, "", "    ")
    if err != nil {
        fmt.Println("json err:", err)
    }
    fmt.Println(string(b))
    //输出结果:{"subjects":["Go","C++","Python","Test"],"IsOk":"true","price":0}
}      

10.3.1.2 通过map生成JSON

// 创建一个保存键值对的映射
t1 := make(map[string]interface{})
t1["company"] = "itcast"
t1["subjects "] = []string{"Go", "C++", "Python", "Test"}
t1["isok"] = true
t1["price"] = 666.666

b, err := json.Marshal(t1)
//json.MarshalIndent(t1, "", "    ")
if err != nil {
    fmt.Println("json err:", err)
}
fmt.Println(string(b))
//输出结果:{"company":"itcast","isok":true,"price":666.666,"subjects ":["Go","C++","Python","Test"]}      

10.3.2 解码JSON

可以使用json.Unmarshal()函数将JSON格式的文本解码为Go里面预期的数据结构。

json.Unmarshal()函数的原型如下:

func Unmarshal(data []byte, v interface{}) error      

该函数的第一个参数是输入,即JSON格式的文本(比特序列),第二个参数表示目标输出容器,用于存放解码后的值。

10.3.2.1 解析到结构体

type IT struct {
    Company  string   `json:"company"`
    Subjects []string `json:"subjects"`
    IsOk     bool     `json:"isok"`
    Price    float64  `json:"price"`
}

func main() {
    b := []byte(`{
    "company": "itcast",
    "subjects": [
        "Go",
        "C++",
        "Python",
        "Test"
    ],
    "isok": true,
    "price": 666.666
}`)

    var t IT
    err := json.Unmarshal(b, &t)
    if err != nil {
        fmt.Println("json err:", err)
    }
    fmt.Println(t)
    //运行结果:{itcast [Go C++ Python Test] true 666.666}

    //只想要Subjects字段
    type IT2 struct {
        Subjects []string `json:"subjects"`
    }

    var t2 IT2
    err = json.Unmarshal(b, &t2)
    if err != nil {
        fmt.Println("json err:", err)
    }
    fmt.Println(t2)
    //运行结果:{[Go C++ Python Test]}
}      

10.3.2.2 解析到interface

示例代码:

func main() {
    b := []byte(`{
    "company": "itcast",
    "subjects": [
        "Go",
        "C++",
        "Python",
        "Test"
    ],
    "isok": true,
    "price": 666.666
}`)

    var t interface{}
    err := json.Unmarshal(b, &t)
    if err != nil {
        fmt.Println("json err:", err)
    }
    fmt.Println(t)

    //类型断言, 值,它是value类型
    //可以对某个值做单独的类型断言,直接赋值,不用遍历
    //如: m["company"].(string) 这样就可以直接赋值
    
    //用法见如下代码   
    //var str string
    //str = m["company"].(string) //err, 无法转换
    //fmt.Println("str = ", str)

    //切忌以下为错误用法,无法直接转化
    //  var str string
    //  str = string(m["company"]) //err, 无法转换
    //  fmt.Println("str = ", str)

    //使用断言判断类型
    m := t.(map[string]interface{})
    for k, v := range m {
        switch vv := v.(type) {
        case string:
            fmt.Println(k, "is string", vv)
        case int:
            fmt.Println(k, "is int", vv)
        case float64:
            fmt.Println(k, "is float64", vv)
        case bool:
            fmt.Println(k, "is bool", vv)
        case []interface{}:
            fmt.Println(k, "is an array:")
            for i, u := range vv {
                fmt.Println(i, u)
            }
        default:
            fmt.Println(k, "is of a type I don't know how to handle")
        }
    }
}      

运行结果:

【go】异常,字符串操作,文件处理

这里涉及到go的类型断言

(1)语法:
  <目标类型的值>,<布尔参数> := <表达式>.( 目标类型 ) // 安全类型断言
  <目标类型的值> := <表达式>.( 目标类型 )  //非安全类型断言

(2)类型断言的本质,跟类型转换类似,都是类型之间进行转换,不同之处在于,类型断言实在接口之间进行,相当于Java中,对于一个对象,把一种接口的引用转换成另一种。      

我们先来看一个最简单的错误的类型断言:

func test6() {
    var i interface{} = "kk"
    j := i.(int)
    fmt.Printf("%T->%d\n", j, j)
}      

var i interface{} = “KK” 某种程度上相当于java中的,Object i = “KK”;

现在把这个 i 转换成 int 类型,系统内部检测到这种不匹配,就会调用内置的panic()函数,抛出一个异常。

改一下,把 i 的定义改为:var i interface{} = 99,就没问题了。输出为:

int->99      

以上是不安全的类型断言。我们来看一下安全的类型断言:

func test6() {
    var i interface{} = "TT"
    j, b := i.(int)
    if b {
        fmt.Printf("%T->%d\n", j, j)
    } else {
        fmt.Println("类型不匹配")
    }
}      

输出“类型不匹配”

10.4 文件操作

10.4.1 相关api介绍

10.4.1.1 建立与打开文件

新建文件可以通过如下两个方法:

func Create(name string) (file *File, err Error)
根据提供的文件名创建新的文件,返回一个文件对象,默认权限是0666的文件,返回的文件对象是可读写的。

func NewFile(fd uintptr, name string) *File
根据文件描述符创建相应的文件,返回一个文件对象      

通过如下两个方法来打开文件:

func Open(name string) (file *File, err Error)
该方法打开一个名称为name的文件,但是是只读方式,内部实现其实调用了OpenFile。

func OpenFile(name string, flag int, perm uint32) (file *File, err Error)
打开名称为name的文件,flag是打开的方式,只读、读写等,perm是权限      

10.4.1.2 写文件

func (file *File) Write(b []byte) (n int, err Error)
写入byte类型的信息到文件

func (file *File) WriteAt(b []byte, off int64) (n int, err Error)
在指定位置开始写入byte类型的信息

func (file *File) WriteString(s string) (ret int, err Error)
写入string信息到文件      

10.4.1.3 读文件

func (file *File) Read(b []byte) (n int, err Error)
读取数据到b中

func (file *File) ReadAt(b []byte, off int64) (n int, err Error)
从off开始读取数据到b中      

10.4.1.4 删除文件

func Remove(name string) Error
调用该函数就可以删除文件名为name的文件      

10.4.2 示例代码

10.4.2.1 写文件

package main

import (
    "fmt"
    "os"
)

func main() {
    fout, err := os.Create("./xxx.txt") //新建文件
    //fout, err := os.OpenFile("./xxx.txt", os.O_CREATE, 0666)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer fout.Close() //main函数结束前, 关闭文件

    for i := 0; i < 5; i++ {
        outstr := fmt.Sprintf("%s:%d\n", "Hello go", i)
        fout.WriteString(outstr)     //写入string信息到文件
        fout.Write([]byte("abcd\n")) //写入byte类型的信息到文件
    }
}      

xxx.txt内容如下:

【go】异常,字符串操作,文件处理

10.4.2.2 读文件

func main() {
    fin, err := os.Open("./xxx.txt") //打开文件
    if err != nil {
        fmt.Println(err)
    }
    defer fin.Close()

    buf := make([]byte, 1024) //开辟1024个字节的slice作为缓冲
    for {
        n, _ := fin.Read(buf) //读文件
        if n == 0 {           //0表示已经到文件结束
            break
        }

        fmt.Println(string(buf)) //输出读取的内容
    }
}      

文件读写例子2

package main

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

func WriteFile(path string) {
    //打开文件,新建文件
    f, err := os.Create(path)
    if err != nil {
        fmt.Println("err = ", err)
        return
    }

    //使用完毕,需要关闭文件
    defer f.Close()

    var buf string

    for i := 0; i < 10; i++ {
        //"i = 1\n", 这个字符串存储在buf中
        buf = fmt.Sprintf("i = %d\n", i)
        //fmt.Println("buf = ", buf)

        n, err := f.WriteString(buf)
        if err != nil {
            fmt.Println("err = ", err)
        }
        fmt.Println("n = ", n)
    }
}

func ReadFile(path string) {
    //打开文件
    f, err := os.Open(path)
    if err != nil {
        fmt.Println("err = ", err)
        return
    }

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

    buf := make([]byte, 1024*2) //2k大小

    //n代表从文件读取内容的长度
    n, err1 := f.Read(buf)
    if err1 != nil && err1 != io.EOF { //文件出错,同时没有到结尾
        fmt.Println("err1 = ", err1)
        return
    }

    fmt.Println("buf = ", string(buf[:n]))

}

//每次读取一行
func ReadFileLine(path string) {
    //打开文件
    f, err := os.Open(path)
    if err != nil {
        fmt.Println("err = ", err)
        return
    }

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

    //新建一个缓冲区,把内容先放在缓冲区
    r := bufio.NewReader(f)

    for {
        //遇到'\n'结束读取, 但是'\n'也读取进入
        buf, err := r.ReadBytes('\n')
        if err != nil {
            if err == io.EOF { //文件已经结束
                break
            }
            fmt.Println("err = ", err)
        }

        fmt.Printf("buf = #%s#\n", string(buf))
    }

}

func main() {
    path := "./demo.txt"

    //WriteFile(path)
    //ReadFile(path)
    ReadFileLine(path)
}      

10.4.3 案例:拷贝文件

package main

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

func main() {
    args := os.Args //获取用户输入的所有参数

    //如果用户没有输入,或参数个数不够,则调用该函数提示用户
    if args == nil || len(args) != 3 {
        fmt.Println("useage : xxx srcFile dstFile")
        return
    }

    srcPath := args[1] //获取输入的第一个参数
    dstPath := args[2] //获取输入的第二个参数
    fmt.Printf("srcPath = %s, dstPath = %s\n", srcPath, dstPath)

    if srcPath == dstPath {
        fmt.Println("源文件和目的文件名字不能相同")
        return
    }

    srcFile, err1 := os.Open(srcPath) //打开源文件
    if err1 != nil {
        fmt.Println(err1)
        return
    }

    dstFile, err2 := os.Create(dstPath) //创建目的文件
    if err2 != nil {
        fmt.Println(err2)
        return
    }

    buf := make([]byte, 1024) //切片缓冲区
    for {
        //从源文件读取内容,n为读取文件内容的长度
        n, err := srcFile.Read(buf)
        if err != nil && err != io.EOF {
            fmt.Println(err)
            break
        }

        if n == 0 {
            fmt.Println("文件处理完毕")
            break
        }

        //切片截取
        tmp := buf[:n]
        //把读取的内容写入到目的文件
        dstFile.Write(tmp)
    }

    //关闭文件
    srcFile.Close()
    dstFile.Close()
}      

10.4.4 输入输出设备文件的使用

package main

import (
    "fmt"
    "os"
)

func main() {

    //os.Stdout.Close() //关闭后,无法输出
    //fmt.Println("are u ok?") //往标准输出设备(屏幕)写内容

    //标准设备文件(os.Stdout),默认已经打开,用户可以直接使用
    //os.Stdout
    os.Stdout.WriteString("are u ok?\n")

    //os.Stdin.Close() //关闭后,无法输入
    var a int
    fmt.Println("请输入a: ")
    fmt.Scan(&a) //从标准输入设备中读取内容,放在a中
    fmt.Println("a = ", a)
}