天天看点

八、Go-+文件操作

8.1、编码

8.1.1 、编码历史

八、Go-+文件操作

众所周知,计算机起源于美国,英文只有26个字符,算上其他所有特殊符号也不会超过128个。字节是计算机的基本储存单位,一个字节(bytes)包括八个比特位(bit),能够表示出256个二进制数字,所以美国人在这里只是用到了一个字节的前七位即127个数字来对应了127个具体字符,而这张对应表就是ASCII码字符编码表,简称ASCII表。后来为了能够让计算机识别拉丁文,就将一个字节的最高位也应用了,这样就多扩展出128个二进制数字来对应新的符号。这张对应表因为是在ASCII表的基础上扩展的最高位,因此称为扩展ASCII表。到此位置,一个字节能表示的256个二进制数字都有了特殊的符号对应。

GBK编码

但是,当计算机发展到东亚国家后,问题又出现了,像中文,韩文,日文等符号也需要在计算机上显示。可是一个字节已经被西方国家占满了。于是,我中华民族自己重写一张对应表,直接生猛地将扩展的第八位对应拉丁文全部删掉,规定一个小于127的字符的意义与原来相同,即支持ASCII码表,但两个大于127的字符连在一起时,就表示一个汉字,这样就可以将几千个汉字对应一个个二进制数了。而这种编码方式就是GB2312,也称为中文扩展ASCII码表。再后来,我们为了对应更多的汉字规定只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。这样能多出几万个二进制数字,就算甲骨文也能够用了。而这次扩展的编码方式称为GBK标准。当然,GBK标准下,一个像”苑”这样的中文符号,必须占两个字节才能存储显示。

Unicode与utf8编码

与此同时,其它国家也都开发出一套编码方式,即本国文字符号和二进制数字的对应表。而国家彼此间的编码方式是互不支持的,这会导致很多问题。于是ISO国际化标准组织为了统一编码,统计了世界上所有国家的字符,开发出了一张万国码字符表,用两个字节即六万多个二进制数字来对应。这就是Unicode编码方式。这样,每个国家都使用这套编码方式就再也不会有计算机的编码问题了。Unicode的编码特点是对于任意一个字符,都需要两个字节来存储。这对于美国人而言无异于吃上了世界的大锅饭,也就是说,如果用ASCII码表,明明一个字节就可以存储的字符现在为了兼容其他语言而需要两个字节了,比如字母I,本可以用01001001来存储,现在要用Unicode只能是00000000 01001001存储,而这将导致大量的空间被浪费掉。基于此,美国人创建了utf8编码,而utf8编码是一种针对Unicode的可变长字符编码方式,根据具体不同的字符计算出需要的字节,对于ASCII码范围的字符,就用一个字节,而且符号与数字的对应也是一致的,所以说utf8是兼容ASCII码表的。但是对于中文,一般是用三个字节存储的。

8.1.2、Go的字符与字节

byte就是字节的意思,一个字节就是8个二进制位。uint8,无符号整形,占8位,正好也是2的8次方。所以byte和 uint8 类型本质上没有区别,它表示的是 ACSII 表中的一个字符。

// byte类型
var b1 byte
b1 = 'A'  // 必须是单引号
fmt.Println(reflect.TypeOf(b1)) // 65  uint8
fmt.Printf("%c\n",b1)
fmt.Printf("%d\n",b1)  // ASCII数字
fmt.Println(b1)  // ASCII数字
// uint8类型
var b2 uint8
b2 = 65
fmt.Printf("%c\n",b2)
fmt.Printf("%d\n",b2)
fmt.Println(b2) // ASCII数字

// var b3 byte
var b3 rune
b3 = '苑'
// rune,占用4个字节,共32位比特位,所以它和 int32 本质上也没有区别。它表示的是一个 Unicode字符
fmt.Println(b3,string(b3),reflect.TypeOf(b3))
           

8.1.3、字符串与字节串

字节数组,就是一个数组,里面每一个元素都是字符,字符又跟字节划等号。所以字符串和字节数组之间可以相互转化。

// (1) 字符串类型(string) 转为字节串类型([]byte)
var s = "苑昊"
fmt.Println(s,reflect.TypeOf(s)) // 苑昊 string

var b = []byte(s)  // 默认用uft-8进行编码
fmt.Println(b,reflect.TypeOf(b)) // [232 139 145 230 152 138] []uint8

// (2) byte转为string
fmt.Println(string(b))
var data = make([]byte,4)
data[0] = 'y'
data[1] = 'u'
data[2] = 'a'
data[3] = 'n'
var str string = string(data[:])
fmt.Println(data) // [121 117 97 110]
fmt.Println(str)  // yuan
           

8.2、Go的字符串存储

8.2.1、string标准概念

Go标准库builtin给出了所有内置类型的定义。 源代码位于src/builtin/builtin.go,其中关于string的描述如下:

// string is the set of all strings of 8-bit bytes, conventionally but not

// necessarily representing UTF-8-encoded text. A string may be empty, but

// not nil. Values of string type are immutable.

type string string
           

所以string是8比特字节的集合,通常但并不一定是UTF-8编码的文本。

另外,还提到了两点,非常重要:

string可以为空(长度为0),但不会是nil;

string对象不可以修改(strings are immutable)。

8.2.2、string 数据结构

源码包src/runtime/string.go:stringStruct定义了string的数据结构:

type stringStruct struct {
    str unsafe.Pointer
    len int
}
           

其数据结构很简单:

stringStruct.str:字符串的首地址;

stringStruct.len:字符串的长度;

string数据结构跟切片有些类似,只不过切片还有一个表示容量的成员,事实上string和切片,准确的说是byte切片经常发生转换。这个后面再详细介绍。

8.2.3、为什么字符串不允许修改?

像C++语言中的string,其本身拥有内存空间,修改string是支持的。但Go的实现中,string不包含内存空间,只有一个内存的指针,这样做的好处是string变得非常轻量,可以很方便的进行传递而不用担心内存拷贝。

因为string通常指向字符串字面量,而字符串字面量存储位置是只读段,而不是堆或栈上,所以才有了string不可修改的约定。

8.3、读写文件

8.3.1、打开文件

os.Open()函数能够打开一个文件,返回一个*File和一个err。

//打开文件
file, err := os.Open("./满江红")
if err != nil {
    fmt.Println("err: ", err)
}
//关闭文件句柄
defer file.Close()
           

8.3.2、读文件

package main

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

func readBytes(file *os.File) {
    var b = make([]byte, 3)
    n, err := file.Read(b)
    if err != nil {
        fmt.Println("err:", err)
        return
    }
    fmt.Printf("读取字节数:%d\n", n)
    fmt.Printf("切片值:%v\n", b)
    fmt.Printf("读取内容:%v\n", string(b[:n]))

}

func readLines(file *os.File) {
    reader := bufio.NewReader(file)
    for {

        // (1) 按行都字符串
        strs, err := reader.ReadString('\n') // 读取到换行符为止,读取内容包括换行符
        fmt.Print(err, strs)

        // (2) 按行都字节串
        // bytes, err := reader.ReadBytes('\n')
        // fmt.Print(bytes)
        // fmt.Print(string(bytes))
        if err == io.EOF { //io.EOF 读取到了文件的末尾
            // fmt.Println("读取到文件末尾!")
            break
        }

    }
}

func readFile() {
    content, err := ioutil.ReadFile("满江红") //包含了打开文件和读取整个文件,适用于较小文件
    if err != nil {
        fmt.Println("read file failed, err:", err)
        return
    }
    fmt.Print(string(content))
}

func main() {

    //打开文件
    file, err := os.Open("满江红") // 相对路径或者绝对路径
    if err != nil {
        fmt.Println("err: ", err)
    }
    //关闭文件句柄
    defer file.Close()

    // (1) 按字节读取数据
    // readBytes(file)
    // (2) 按行读取文件
    // readLines(file)
    // (3) 读取整个文件
    // readFile()

}
           

8.3.3、写文件

package main

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

func writeBytesOrStr(file *os.File) {
    str := "满江红\n"
    //写入字节切片数据
    file.Write([]byte(str))
    //直接写入字符串数据
    file.WriteString("怒发冲冠,凭栏处、潇潇雨歇。")
}

func writeByBufio(file *os.File) {
    writer := bufio.NewWriter(file)
    //将数据先写入缓存,并不会到文件中
    writer.WriteString("大浪淘沙\n")
    // 必须flush将缓存中的内容写入文件
    // writer.Flush()
}

func writeFile() {
    str := "怒发冲冠,凭栏处、潇潇雨歇。"
    err := ioutil.WriteFile("满江红3", []byte(str), 0666)
    if err != nil {
        fmt.Println("write file failed, err:", err)
        return
    }
}

func main() {
    /*
        os.O_RDONLY: 以只读的方式打开
        os.O_WRONLY: 以只写的方式打开
        os.O_RDWR : 以读写的方式打开
        os.O_APPEND: 以追加的方式打开
        os.O_CREAT: 创建并打开一个新文件
        os.O_TRUNC: 打开一个文件并截断它的长度为零(必须有写权限)
        os.O_EXCL: 如果指定的文件存在,返回错误
    */

    file, err := os.OpenFile("满江红new", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
    if err != nil {
        fmt.Println("open file failed, err:", err)
        return
    }
    defer file.Close()

    // 写字节或者字符串
    writeBytesOrStr(file)
    // flush写
     writeByBufio(file)
    // 写文件
    writeFile()

}
           

8.3.4、读写文件

读取一个文件前三行内容,并追加一行hello world

package main

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

func main() {

    file, err := os.OpenFile("读写满江红", os.O_APPEND|os.O_RDWR, 0666)
    if err != nil {
        fmt.Println("open file failed, err:", err)
        return
    }
    defer file.Close()

    // 读取前三行内容
    reader := bufio.NewReader(file)
    fmt.Println("读取的前三行内容:")

    for i := 0; i < 3; i++ {
        // (1) 按行都字符串
        strs, err := reader.ReadString('\n') // 读取到换行符为止,读取内容包括换行符
        // (2) 按行都字节串
        // bytes, err := reader.ReadBytes('\n')
        if err == io.EOF { //io.EOF 读取到了文件的末尾
            // fmt.Println("读取到文件末尾!")
            break
        }
        fmt.Print(strs)
    }

    // 追加‘hello world’
    writer := bufio.NewWriter(file)
    //将数据先写入缓存,并不会到文件中
    writer.WriteString("hello world")
    // 必须flush将缓存中的内容写入文件
    writer.Flush()

}

           

8.4、文件操作

(1) 删除文件

os.Remove(fname)

(2) 创建目录

dname :="/tmp/d"

os.Mkdir(dname,os.ModeDir|os.ModePerm)

func main() {
    f,err:=os.Stat("满江红")
    if err ==nil {
        fmt.Println("name:",f.Name())
        fmt.Println("size:",f.Size())
        fmt.Println("is dir:",f.IsDir())
        fmt.Println("mode::",f.Mode())
        fmt.Println("modTime:",f.ModTime())
    }
}