合肥大型网站制,微网站建设网络,福田网站建设标准数据,移动网络1. 写在前面
公司的新业务开发需要用到go语言#xff0c;虽然之前没接触过这门语言#xff0c;但在大模型的帮助下#xff0c;边看项目边写代码也能进行go的项目开发#xff0c;不过#xff0c;写了一段时间代码之后#xff0c;总感觉对go语言本身#xff0c;我的知识体…1. 写在前面
公司的新业务开发需要用到go语言虽然之前没接触过这门语言但在大模型的帮助下边看项目边写代码也能进行go的项目开发不过写了一段时间代码之后总感觉对go语言本身我的知识体系里面并没有一个比较完整的架子学习到的知识零零散散不成体系虽然能完成工作但心里比较虚没有沉淀下知识。所以想借着这个机会用两周的时间系统的学习下go语言 在知识体系里面搭一个属于go语言的知识框架把知识拎起来 也便于后续知识的扩充与回看。
此次学习依然是业余时间看文档的方式搭建知识框架(工作之后发现看视频比较慢没时间看) 参看的文档是C语言中文网go教程 上面内容整理的很详细非常适合初学者搭建知识体系。只不过内容比较多 这次还是和之前一样 整体过一遍教程 把我觉得现阶段比较关键的知识梳理出来过于简单的知识作整合对重点知识用其他一些资料补充再用一些实验作为辅助理解先跳过一些demo示例实践 基础知识作整理关键知识作扩展搭建基础框架后面再从项目中提炼新知识作补充完善框架。
PS 由于我有C/C、Java、Python等语言基础所以针对教程的前面常识部分作了整合和删减小白的话建议去原网站学习哈。
Go语言系统部分打算用3篇文章搭建知识框架基础篇、进阶篇和杂项篇每一篇里面的内容各个模块划分的比较清晰这样后面针对新知识方便补充。今天是最后一篇内容 想整理教程里面额外的一些杂项主要包括常用的包以及文件处理部分由于这两块内容偏实践 我没有整理太多还是先有个简单的架子后面接触到之后随时补充。
大纲如下
go包go文件处理
Ok, let’s go!
2 包
2.1 初识
Go语言是使用包来组织源代码的包package是多个 Go 源码的集合是一种高级的代码复用方案。Go语言中为我们提供了很多内置包如 fmt、os、io 等。
任何源代码文件必须属于某个包同时源码文件的第一行有效代码必须是package pacakgeName 语句通过该语句声明自己所在的包
// 导入方式
import 包的路径
import (包 1 的路径包 2 的路径
)// 导入路径
// 包的绝对路径就是GOROOT/src/或GOPATH/src/后面包的存放路径 , 最好是用这种方式导入 不要用相对路径容易出错
import database/sql/driver
import database/sql// 引用格式
// 最常规
import fmt
fmt.Println(hello world)// 自定义别名 类似Python的 improt xx as x
import F fmt
F.Println(hello world)// 省略引用 相当于把 fmt 包直接合并到当前程序中在使用 fmt 包内的方法可以直接引用
import . fmt
Println(hello world)// 匿名引用 只是希望执行包初始化的 init 函数而不使用包内部的数据
import _ fmt
// 后面不用这个包里面的方法 编译器不会报错// init()调用顺序为 main() 中引用的包以深度优先顺序初始化 main→A→B→C 则 init的调用方法 C.init→B.init→A.init→main
// 同一个包中的多个 init() 函数的调用顺序不可预期我们可以自定义的包方便管理自己写的源代码但有几点需要注意
创建的自定义的包需要将其放在 GOPATH 的 src 目录下也可以是 src 目录下的某个子目录而且两个不同的包不能放在同一目录下这样会引起编译错误一个包中可以有任意多个文件文件的名字也没有任何规定但后缀必须是 .go这里我们假设包名就是 .go 的文件名如果一个包有多个 .go 文件则其中会有一个 .go 文件的文件名和包名相同。使用 import 语句导入包时使用的是包所属文件夹的名称包中的函数名第一个字母要大写否则无法在外部调用同样的如果想让包内的变量结构体接口类型常量等在包外被访问也需要字段名或者方法名的首字母要大写调用自定义包时使用 包名.函数名 的方式
2.2 go语言封装
在Go语言中封装就是把抽象出来的字段和对字段的操作封装在一起数据被保护在内部程序的其它包只能通过被授权的方法才能对字段进行操作。
封装的好处1. 隐藏实现细节2. 可以对数据据进行验证保证数据安全合理。
如何体现封装
对结构体中的属性进行封装通过方法包实现封装。
封装的实现步骤
将结构体、字段的首字母小写给结构体所在的包提供一个工厂模式的函数首字母大写类似一个构造函数提供一个首字母大写的 Set 方法类似其它语言的 public用于对属性判断并赋值提供一个首字母大写的 Get 方法类似其它语言的 public用于获取属性的值。
// 对于员工不能随便查看年龄工资等隐私并对输入的年龄进行合理的验证
目录结构
* main* main.go
* model* model.go// model源码
package model
import fmt
type person struct {Name stringage int //其它包不能直接访问..sal float64
}
//写一个工厂模式的函数相当于构造函数
func NewPerson(name string) *person {return person{Name : name,}
}
//为了访问age 和 sal 我们编写一对SetXxx的方法和GetXxx的方法
func (p *person) SetAge(age int) {if age 0 age 150 {p.age age} else {fmt.Println(年龄范围不正确..)//给程序员给一个默认值}
}
func (p *person) GetAge() int {return p.age
}
func (p *person) SetSal(sal float64) {if sal 3000 sal 30000 {p.sal sal} else {fmt.Println(薪水范围不正确..)}
}
func (p *person) GetSal() float64 {return p.sal
}// main里面
package main
import (fmt../model // 这里就是相对路径导入方式
)
func main() {p : model.NewPerson(smith)p.SetAge(18)p.SetSal(5000)fmt.Println(p)fmt.Println(p.Name, age , p.GetAge(), sal , p.GetSal())2.3 go语言内置包
安装go的时候自动会安装一些内置包 在$GOROOT/src/pkg 目录中可以查看这些包常用的整理如下
包名作用fmt格式化的标准输入输出这与C语言中的 printf 和 scanf 类似。其中的 fmt.Printf() 和 fmt.Println() 是开发者使用最为频繁的函数sort对切片和用户定义的集合排序strconv将字符串转换成基本数据类型或者从基本数据类型转换为字符串的功能os操作系统函数接口sync实现多线程中锁机制以及其他同步互斥机制flag命令行参数的规则定义和传入参数解析的功能 很常用。encoding/json提供了对 JSON 的基本支持比如从一个对象序列化为 JSON 字符串或者从 JSON 字符串反序列化出一个具体的对象等net/httpHTTP 相关服务主要包括 http 请求、响应和 URL 的解析以及基本的 http 客户端和扩展的 http 服务reflect运行时反射允许程序通过抽象类型操作对象。通常用于处理静态类型 interface{} 的值并且通过 Typeof 解析出其动态类型信息通常会返回一个有接口类型 Type 的对象os/exec执行linux命令strings处理字符串的一些函数集合包括合并、查找、分割、比较、后缀检查、索引、大小写处理等等log在程序中输出日志
2.4 单例模式
单例模式是常用的模式之一在它的核心结构中只包含一个被称为单例的特殊类能够保证系统运行中一个类只创建一个实例
主要解决一个全局使用的类频繁地创建与销毁。
何时使用当想控制实例数目节省系统资源的时候。
如何解决判断系统是否已经有这个单例如果有则返回如果没有则创建。
关键代码构造函数是私有的。
应用实例
1、一个班级只有一个班主任。2、Windows 是多进程多线程的在操作一个文件的时候就不可避免地出现多个进程或线程同时操作一个文件的现象所以所有文件的处理必须通过唯一的实例来进行。3、一些设备管理器常常设计为单例模式比如一个电脑有两台打印机在输出的时候就要处理不能两台打印机打印同一个文件。
优点
1、在内存里只有一个实例减少了内存的开销尤其是频繁的创建和销毁实例比如管理学院首页页面缓存。2、避免对资源的多重占用比如写文件操作。
缺点没有接口不能继承与单一职责原则冲突一个类应该只关心内部逻辑而不关心外面怎么样来实例化。
使用场景
1、要求生产唯一序列号。2、WEB 中的计数器不用每次刷新都在数据库里加一次用单例先缓存起来。3、创建的一个对象需要消耗的资源过多比如 I/O 与数据库的连接等。
实现方式四种
// 懒汉式 创建对象时比较懒先不急着创建对象在需要加载配置文件的时候再去创建
// 非线程安全 多线程情况下可能会创建多次对象
//使用结构体代替类
type Tool struct {values int
}
//建立私有变量
var instance *Tool
//获取单例对象的方法引用传递返回
func GetInstance() *Tool {if instance nil {instance new(Tool)}return instance
}
// 为了保证线程安全 采用加锁的方式 但性能会有所下降
//锁对象
var lock sync.Mutex
//加锁保证线程安全
func GetInstance() *Tool {lock.Lock() // 加锁defer lock.Unlock() // 延迟释放 等函数执行完释放锁if instance nil {instance new(Tool)}return instance
}// 饿汉式 在系统初始化的时候就已经把对象创建好了需要用的时候直接拿过来用就好了
// 直接创建好对象不需要判断为空同时也是线程安全唯一的缺点是在导入包的同时会创建该对象并持续占有在内存中
type cfg struct {
}
var cfg *config
func init() {cfg new(config)
}
// NewConfig 提供获取实例的方法
func NewConfig() *config {return cfg
}
type config struct {
}
//全局变量
var cfg *config new(config)
// NewConfig 提供获取实例的方法
func NewConfig() *config {return cfg
}// 双重检查
// 懒汉式线程安全的基础上再进行优化减少加锁的操作保证线程安全的同时不影响性能
//锁对象
var lock sync.Mutex
//第一次判断不加锁第二次加锁保证线程安全一旦对象建立后获取对象就不用加锁了。
func GetInstance() *Tool {if instance nil {lock.Lock()if instance nil {instance new(Tool)}lock.Unlock()}return instance
}// Sync.Once 通过 sync.Once 来确保创建对象的方法只执行一次, 内部本质上也是双重检查的方式
var once sync.Once
func GetInstance() *Tool {once.Do(func() {instance new(Tool)})return instance
}2.5 sync包与锁
sync 包里提供了互斥锁 Mutex 和读写锁 RWMutex 用于处理并发过程中可能出现同时两个或多个协程或线程读或写同一个变量的情况。
锁是 sync 包中的核心它主要有两个方法分别是加锁Lock和解锁Unlock。
在并发的情况下多个线程或协程同时其修改一个变量使用锁能保证在某一时间内只有一个协程或线程修改这一变量。
// 不使用锁的并发情况可能得不到想要的结果
package main
import (fmttime
)
func main() {var a 0for i : 0; i 1000; i {go func(idx int) {a 1fmt.Println(a)}(i)}time.Sleep(time.Second)
}
// 结果
831
833
835
837
839
841
843
845
847
682
851
853
855
857
858
860
862
686
752// 协程的执行顺序 1. 从寄存器读取 a 的值 2. 然后做加法运算 3. 最后写到寄存器。
// 按照上面的顺序假如有一个协程取得 a 的值为 3然后执行加法运算此时又有一个协程对 a 进行取值得到的值同样是 3最终两个协程的返回结果是相同的。
// 而锁的概念就是当一个协程正在处理 a 时将 a 锁定其它协程需要等待该协程处理完成并将 a 解锁后才能再进行操作也就是说同时处理 a 的协程只能有一个从而避免上面示例中的情况出现互斥锁 解决上面的问题可以用互斥锁搞定 互斥锁这个名字来自互斥的概念。互斥锁用于在代码上创建一个临界区保证同一时间只有一个 goroutine 可以执行这个临界代码。
func (m *Mutex) Lock()
func (m *Mutex) Unlock()package main
import (fmttimesync
)
func main() {var a 0var lock sync.Mutexfor i : 0; i 1000; i {go func(idx int) {lock.Lock() // 加锁defer lock.Unlock() // 函数执行完了之后释放锁a 1fmt.Printf(goroutine %d, a%d\n, idx, a)}(i)}time.Sleep(time.Second)
}// 一个互斥锁只能同时被一个 goroutine 锁定其它 goroutine 将阻塞直到互斥锁被解锁重新争抢对互斥锁的锁定读写锁有如下四个方法
写操作的锁定和解锁分别是func (*RWMutex) Lock和func (*RWMutex) Unlock读操作的锁定和解锁分别是func (*RWMutex) Rlock和func (*RWMutex) RUnlock。
读写锁的区别在于
当有一个 goroutine 获得写锁定其它无论是读锁定还是写锁定都将阻塞直到写解锁当有一个 goroutine 获得读锁定其它读锁定仍然可以继续;当有一个或任意多个读锁定写锁定将等待所有读锁定解锁之后才能够进行写锁定。
所以说这里的读锁定RLock目的其实是告诉写锁定有很多协程或者进程正在读取数据写操作需要等它们读读解锁完才能进行写写锁定。即
同时只能有一个 goroutine 能够获得写锁定同时可以有任意多个 gorouinte 获得读锁定同时只能存在写锁定或读锁定读和写互斥
理解起来就是
如果有人在写的时候 其他人读和写都不行 因为其他人如果读 可能在写的前后读的数据不一致产生幻读其他人如果写那造成写冲突有人在读的时候其他人可以读但是不能写防止别人读错 因为写可能会导致正在读的人读错
package main
import (fmtmath/randsync
)
// count的全局变量用于读写操作
var count int
// 读写锁实例
var rw sync.RWMutexfunc main() {// 搞一个通道容量为10ch : make(chan struct{}, 10)// 启动了5个goroutine用于读for i : 0; i 5; i {go read(i, ch)}// 启动了5个goroutine用于写for i : 0; i 5; i {go write(i, ch)}// 通过从ch通道读取10次数据空结构体struct{}确保之前启动的所有goroutine完成它们的操作。// 这是一种等待所有goroutine完成的简单方法for i : 0; i 10; i {-ch}
}
func read(n int, ch chan struct{}) {rw.RLock() // 加读锁fmt.Printf(goroutine %d 进入读操作...\n, n)v : count // 读数据countfmt.Printf(goroutine %d 读取结束值为%d\n, n, v)rw.RUnlock() // 释放读锁ch - struct{}{} // 向ch通道发送一个空结构体表示读操作完成
}
func write(n int, ch chan struct{}) {rw.Lock() // 加写锁fmt.Printf(goroutine %d 进入写操作...\n, n)v : rand.Intn(1000) // 修改countcount vfmt.Printf(goroutine %d 写入结束新值为%d\n, n, v)rw.Unlock() // 释放写锁ch - struct{}{} // 向ch发送一个空结构体表明写操作完成
}// result
goroutine 0 进入读操作...
goroutine 0 读取结束值为0
goroutine 4 进入写操作...
goroutine 4 写入结束新值为81
goroutine 1 进入读操作...
goroutine 1 读取结束值为81
goroutine 4 进入读操作...
goroutine 4 读取结束值为81
goroutine 2 进入读操作...
goroutine 2 读取结束值为81
goroutine 3 进入读操作...
goroutine 3 读取结束值为81
goroutine 0 进入写操作...
goroutine 0 写入结束新值为887
goroutine 1 进入写操作...
goroutine 1 写入结束新值为847
goroutine 2 进入写操作...
goroutine 2 写入结束新值为59
goroutine 3 进入写操作...
goroutine 3 写入结束新值为81上面这段代码展示了如何在Go中使用读写锁和goroutines安全地执行并发读写操作并通过一个通道来同步goroutine的执行确保所有goroutine执行完成后主函数才退出。读写锁sync.RWMutex允许多个goroutines同时读取一个共享资源而不会相互干扰但是如果有goroutine正在写入其他goroutine无论是读取还是写入操作都会被阻塞直到写锁被释放。
下面看一个读读不互斥的例子和写读互斥的例子
// 两个哥们同时读
package main
import (synctime
)
var m *sync.RWMutex
func main() {m new(sync.RWMutex)// 多个同时读 互不影响go read(1)go read(2)time.Sleep(2*time.Second)
}
func read(i int) {println(i,read start)m.RLock()println(i,reading)time.Sleep(1*time.Second)m.RUnlock()println(i,read over)
}// 结果
1 read start
1 reading // 1读的时候2也可以读
2 read start
2 reading
1 read over
2 read overfunc main() {m new(sync.RWMutex)// 写的时候啥也不能干go write(1)go read(2)go write(3)time.Sleep(5*time.Second) // 这个如果sleep时间太短其他协程可能执行不完
}
func read(i int) {println(i,read start)m.RLock()println(i,reading)time.Sleep(1*time.Second)m.RUnlock()println(i,read over)
}
func write(i int) {println(i,write start)m.Lock()println(i,writing)time.Sleep(1*time.Second)m.Unlock()println(i,write over)
}// 结果
1 write start
1 writing
2 read start
3 write start
1 write over
2 reading
2 read over
3 writing
3 write overwrite(1) 是第一个尝试获取写锁的goroutine。read(2)会被阻塞直到写锁被释放但因为所有goroutine都被设计要运行超过主函数的time.Sleep(2*time.Second)限制read(2)可能来不及在主函数结束前完成。write(3)的情况和read(2)类似它会等待写锁被write(1)释放。
为了防止这种情况可以考虑根据实际需要调整goroutine的启动逻辑以及增大main()函数中time.Sleep的持续时间以确保所有goroutine都有足够的时间执行。
Go语言并发模型的一个关键特征是通过goroutines和channels来实现并发编程而在并发编程中死锁、活锁和饥饿是三种常见的问题
// 死锁是指两个或两个以上的执行单元在Go中通常是goroutines互相等待对方释放资源导致它们永久阻塞的一种情况。
// 如果每一个goroutine在等待另一个goroutine同时这就形成了一个循环等待的条件从而造成死锁。
func main() {ch1 : make(chan int)ch2 : make(chan int) go func() {-ch1ch2 - 1}()go func() {-ch2ch1 - 1}()// 这会导致死锁因为两个goroutine都在等待对方发送数据
}// 活锁是指程序没有被阻塞即执行单元都在运行但是由于某种条件始终不能得到满足导致程序无法向前推进。
// 虽然线程或goroutines还在继续执行但是它们却做不了任何有用的工作。
// 场景 两个goroutine彼此都在尝试避让对方例如基于某些条件重试操作但这些条件却令彼此永远无法继续
func main() {ch : make(chan int)done : make(chan bool)var sharedResource intgo func() { // Goroutine 1试图增加sharedResource到一个特定的值for sharedResource 10 {select {case val : -ch:sharedResource valdefault:fmt.Println(Goroutine 1等待增加)time.Sleep(100 * time.Millisecond) // 等待资源希望稍后能增加它}}done - true}()go func() { // Goroutine 2试图减少sharedResource到一个特定的值for sharedResource -10 {select {case val : -ch:sharedResource - valdefault:fmt.Println(Goroutine 2等待减少)time.Sleep(100 * time.Millisecond) // 等待资源希望稍后能减少它}}done - true}()go func() { // Goroutine 3周期性地尝试发送变化到资源for {select {case ch - 1: // 尝试发送1到channelfmt.Println(发送1)case ch - 2: // 尝试发送2到channelfmt.Println(发送2)case -time.After(50 * time.Millisecond): // 每50毫秒发送一次资源// Nothing to do here.}time.Sleep(50 * time.Millisecond) // 等待50毫秒再试}}()// 等待至少一个goroutine完成-done
}
// 有三个goroutine第一个goroutine试图连续增加一个共享资源的值到10第二个goroutine试图减少它到-10而第三个周期性地尝试改变资源。前两个goroutine在默认情况下只是等待并且定期打印出它们在等待的消息。第三个goroutine在发送值后等待一段时间再尝试发送。由于这三个goroutine之间的交互方式它们可能永远不会让 sharedResource 达到任何一个goroutine的完成条件因为另一方马上就会扭转它们的操作。虽然这些goroutines在做“工作”但是这种工作并不会推动程序朝着完成目标前进因此就发生了活锁// 饥饿发生在一个或多个执行单元无法获得它们所需要的资源因而无法进行下去。
// 这通常是由于资源被其他执行单元长时间占用所致。在饥饿的场景中受影响的执行单元并没有被死锁因为没有循环等待的条件而是因为总是有其他执行单元比它更优先获得资源。
func main() {runtime.GOMAXPROCS(3)var wg sync.WaitGroupconst runtime 1 * time.Secondvar sharedLock sync.MutexgreedyWorker : func() {defer wg.Done()var count intfor begin : time.Now(); time.Since(begin) runtime; {sharedLock.Lock()time.Sleep(3 * time.Nanosecond)sharedLock.Unlock()count}fmt.Printf(Greedy worker was able to execute %v work loops\n, count)}politeWorker : func() {defer wg.Done()var count intfor begin : time.Now(); time.Since(begin) runtime; {sharedLock.Lock()time.Sleep(1 * time.Nanosecond)sharedLock.Unlock()sharedLock.Lock()time.Sleep(1 * time.Nanosecond)sharedLock.Unlock()sharedLock.Lock()time.Sleep(1 * time.Nanosecond)sharedLock.Unlock()count}fmt.Printf(Polite worker was able to execute %v work loops\n, count)}wg.Add(2)go greedyWorker()go politeWorker()wg.Wait()
}
// Greedy worker was able to execute 276 work loops
// Polite worker was able to execute 92 work loops// 贪婪的 worker 会贪婪地抢占共享锁以完成整个工作循环而平和的 worker 则试图只在需要时锁定。
// 两种 worker 都做同样多的模拟工作sleeping 时间为 3ns可以看到在同样的时间里贪婪的 worker 工作量几乎是平和的 worker 工作量的两倍解决方法
死锁确保程序设计避免循环等待或者使用超时时间来避免无限等待。活锁添加随机因素或者调整重试策略使得重试操作最终能够成功。饥饿公平地分配资源或者使用更复杂的锁机制来确保长时间等待的执行单元也能获得资源。
2.6 Inject包与依赖注入
正常情况下对函数或方法的调用是主动直接行为在调用某个函数之前需要清楚地知道被调函数的名称是什么参数有哪些类型等等。
所谓的控制反转就是将这种主动行为变成间接的行为我们不用直接调用函数或对象而是借助框架代码进行间接的调用和初始化这种行为称作“控制反转”库和框架能很好的解释控制反转的概念。
依赖注入是实现控制反转的一种方法如果说控制反转是一种设计思想那么依赖注入就是这种思想的一种实现通过注入参数或实例的方式实现控制反转。如果没有特殊说明我们可以认为依赖注入和控制反转是一个东西。
控制反转的价值在于解耦有了控制反转就不需要将代码写死可以让控制反转的的框架代码读取配置动态的构建对象 这一点在Java的Spring框架中突出。
inject 是依赖注入的Go语言实现它能在运行时注入参数调用方法是 Martini 框架Go语言中著名的 Web 框架的基础核心。
// Inject包借助反射实现函数的注入调用
import (fmtgithub.com/codegangsta/inject
)
type S1 interface{}
type S2 interface{}
func Format(name string, company S1, level S2, age int) {fmt.Printf(name %s, company%s, level%s, age %d!\n, name, company, level, age)
}
func main() {//控制实例的创建inj : inject.New()//实参注入inj.Map(tom)inj.MapTo(tencent, (*S1)(nil))inj.MapTo(T4, (*S2)(nil))inj.Map(23)//函数反转调用inj.Invoke(Format) // name tom, companytencent, levelT4, age 23!
}
// inject 提供了一种注入参数调用函数的通用功能inject.New() 相当于创建了一个控制实例由其来实现对函数的注入调用// inject 包对 struct 类型的注入
type S1 interface{}
type S2 interface{}
type Staff struct {Name string injectCompany S1 injectLevel S2 injectAge int inject
}
func main() {//创建被注入实例s : Staff{}//控制实例的创建inj : inject.New()//初始化注入值inj.Map(tom)inj.MapTo(tencent, (*S1)(nil))inj.MapTo(T4, (*S2)(nil))inj.Map(23)//实现对 struct 注入inj.Apply(s)//打印结果fmt.Printf(s %v\n, s) // s {tom tencent T4 23}
}其他包例如time、os、flag包等 等用到的时候再补充。
3 文件处理
这里主要是整理用go如何去读各种类型的文件以及写各种类型的文件。
3.1 Json文件读写
package main
import (encoding/jsonfmtos
)
type People struct {Name stringAge int
}
func main() {peoples : []People{{zhongqiang, 20}, {zhangsan,30}}// 创建文件filePtr, err : os.Create(info.json)if err ! nil {fmt.Println(文件创建失败, err.Error())return}defer filePtr.Close()// 创建Json编码器encoder : json.NewEncoder(filePtr)err encoder.Encode(peoples)if err ! nil {fmt.Println(编码错误, err.Error())} else {fmt.Println(编码成功)}// 读json文件filePtr1, err1 : os.Open(info.json)if err1 ! nil {fmt.Println(文件打开失败 [Err:%s], err1.Error())return}defer filePtr1.Close()var peoples1 []People// 创建json解码器decoder : json.NewDecoder(filePtr1)err decoder.Decode(peoples1)if err ! nil {fmt.Println(解码失败, err.Error())} else {fmt.Println(解码成功)fmt.Println(peoples1) // [{zhongqiang 20} {zhangsan 30}]}
}
3.2 XML文件的读写
package main
import (encoding/xmlfmtos
)
type People struct {Name stringAge int
}
func main() {peoples : []People{{zhongqiang, 20}, {zhangsan,30}}// 创建文件filePtr, err : os.Create(info.xml)if err ! nil {fmt.Println(文件创建失败, err.Error())return}defer filePtr.Close()// 创建xml编码器encoder : xml.NewEncoder(filePtr)err encoder.Encode(peoples)if err ! nil {fmt.Println(编码错误, err.Error())} else {fmt.Println(编码成功)}// 读xml文件filePtr1, err1 : os.Open(info.xml)if err1 ! nil {fmt.Println(文件打开失败 [Err:%s], err1.Error())return}defer filePtr1.Close()peoples1 : People{}// 创建xml解码器decoder : xml.NewDecoder(filePtr1)err decoder.Decode(peoples1)if err ! nil {fmt.Println(解码失败, err.Error())} else {fmt.Println(解码成功)fmt.Println(peoples1) // [{zhongqiang 20} {zhangsan 30}]}
}3.3 Gob读取
这个是go语言专属 类似于python里面的Pickle以二进制形式序列化和反序列化数据
func main() {// 写info : map[string]string{name: zhongqiang,age: 30,}name : demo.gobFile, _ : os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0777)defer File.Close()enc : gob.NewEncoder(File)if err : enc.Encode(info); err ! nil {fmt.Println(err)}// 读var M map[string]stringFile1, _ : os.Open(demo.gob)D : gob.NewDecoder(File1)D.Decode(M)fmt.Println(M) // map[age:30 name:zhongqiang]
}3.4 txt文件读取
package main
import (bufiofmtosio
)
func main() {//创建一个新文件写入内容filePath : ./output.txtfile, err : os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)if err ! nil {fmt.Printf(打开文件错误 %v \n, err)return}//及时关闭defer file.Close()//写入内容str1 : zhongqiang\n // \n\r表示换行 txt文件要看到换行效果要用 \r\nstr2 : zhangsan\nstr3 : lisi\n//写入时使用带缓存的 *Writerwriter : bufio.NewWriter(file)writer.WriteString(str1)writer.WriteString(str2)writer.WriteString(str3)//因为 writer 是带缓存的因此在调用 WriterString 方法时内容是先写入缓存的//所以要调用 flush方法将缓存的数据真正写入到文件中。writer.Flush()//打开文件file1, err1 : os.Open(./output.txt)if err1 ! nil {fmt.Println(文件打开失败 , err1)}//及时关闭 file 句柄否则会有内存泄漏defer file1.Close()//创建一个 *Reader 是带缓冲的reader : bufio.NewReader(file1)for {str, err : reader.ReadString(\n) //读到一个换行就结束if err io.EOF { //io.EOF 表示文件的末尾break}fmt.Print(str)}fmt.Println(文件读取结束...)
}// func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
// 下面列举了一些常用的 flag 文件处理参数// O_RDONLY只读模式打开文件// O_WRONLY只写模式打开文件// O_RDWR读写模式打开文件// O_APPEND写操作时将数据附加到文件尾部追加// O_CREATE如果不存在将创建一个新文件// O_EXCL和 O_CREATE 配合使用文件必须不存在否则返回一个错误// O_SYNC当进行一系列写操作时每次都要等待上次的 I/O 操作完成再进行// O_TRUNC如果可能在打开时清空文件。先整理这几个常用的后面如果再遇到新的现补充。
4. 小总
这篇文章就整理到这里吧 内容比较简单也无需记住 偏实践 主要还是当作文档随时查询的时候方便。 到这里整个go语言学习系列就结束了 学习时长2周 把go的整体内容过了一遍 3篇笔记搭建了一个知识框架后面如果再进行go的开发碰到新知识就大概知道是哪块的内容知道和哪些内容有关联了然后再补充到对应的体系里面慢慢完善就好。
我还是比较喜欢这种体系框架式的学习方式这样能让碎片化的知识有关联 更容易理解和记录学习知识不能孤立的学习某个点得想办法联系到已有的知识框架中然后横向和纵向反复和其他点作对比才能更好的消化它 如果没有合适的知识框架放就得需要突击一段时间学习整理一个初版的框架出来这个突击不要求能把知识学习多么深入只是为了先摸一遍看看到底有啥先有个大概然后在实践里面去完善架子慢慢的就有新的体系。 这是我理想里面的知识“大同” 很多领域很多知识其实底层是相通的 是有关联的还得继续多读书多学习多扩充知识的广度与深度慢慢的就会有感觉 加油呀