golang之文件读写/复制/断点续传

 

目录

一、常用读取文件的三种方式

(1)利用os和file原生操作读文件

(2)使用bufio读取文件

(3)使用ioutil读取文件

二、常用写文件的三种方式

(1)利用os和file原生操作写文件

(2)使用bufio写文件

(3)使用ioutil写文件

三、常用复制文件的四种方式

(1)利用os和file原生操作复制文件

(2)使用bufio读写操作来复制文件

(3)使用ioutil读写操作来复制文件

(4)io.copy()来复制

​四、其他操作

(1)指定断点位置读写文件

(2)***文件复制之断点续传***

(3)遍历文件夹


一、常用读取文件的三种方式

(1)利用os和file原生操作读文件

注意:以open打开的方式是只读的

(2)使用bufio读取文件

注意:默认缓冲区大小是4096,这里我们可以自定义缓冲区大小

(3)使用ioutil读取文件

注意:ioutil是一次性读取整个文件,不适用于大文件读写操作,可能发生内存泄漏

二、常用写文件的三种方式

(1)利用os和file原生操作写文件

OpenFile是一个更一般性的文件打开函数,大多数调用者都应用Open或Create代替本函数。它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。

可指定选项【只读,只写,可读写,追加……】和模式【 ModePerm FileMode = 0777 // 覆盖所有Unix权限位】

下面列举了一些常用的 flag 文件处理参数:

  • O_RDONLY:只读模式打开文件;
  • O_WRONLY:只写模式打开文件;
  • O_RDWR:读写模式打开文件;
  • O_APPEND:写操作时将数据附加到文件尾部(追加);
  • O_CREATE:如果不存在将创建一个新文件;
  • O_EXCL:和 O_CREATE 配合使用,文件必须不存在,否则返回一个错误;
  • O_SYNC:当进行一系列写操作时,每次都要等待上次的 I/O 操作完成再进行;
  • O_TRUNC:如果可能,在打开时清空文件。

(2)使用bufio写文件

注意:bufio是写在缓冲区里面,一定要Flush,否则文件中没有数据

(3)使用ioutil写文件

三、常用复制文件的四种方式

(1)利用os和file原生操作复制文件

(2)使用bufio读写操作来复制文件

(3)使用ioutil读写操作来复制文件

(4)io.copy()来复制


四、其他操作

(1)指定断点位置读写文件

(2)***文件复制之断点续传***

如果在传输文件的过程中发生断电等情况,文件传输了一半后异常终止了,当恢复后不希望重新传输,而希望接着传输。

可创建一个临时文件用户保存当前已经传输的字节数,每次传输时先读取临时文件的记录。如传输完成则删除临时文件

//断点续传
func main() {
	srcFile := "D:/development/jetbrains/goland/workspace/src/Go-Player/hanru_package/a.jpg"
	distFile := "copy_"+srcFile[strings.LastIndex(srcFile, "/")+1:]
	tempFile := distFile + "temp.txt"

	file1, err := os.Open(srcFile)
	handelError(err)
	file2, err := os.OpenFile(distFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)
	handelError(err)
	file3, err := os.OpenFile(tempFile, os.O_CREATE|os.O_RDWR, os.ModePerm) //可读可写
	handelError(err)

	defer file1.Close()
	defer file2.Close()
	defer file3.Close()

	//先读取临时文件中的数据,再seek
	file3.Seek(0, io.SeekStart)
	bs := make([]byte, 100, 100)
	n1, err := file3.Read(bs)
	//handelError(err)
	countStr := string(bs[:n1])
	count, err := strconv.ParseInt(countStr, 10, 64)
	//handelError(err)
	//fmt.Println(count)

	//设置读,写的位置
	file1.Seek(count, io.SeekStart)
	file2.Seek(count, io.SeekStart)
	data := make([]byte, 1024, 1024)
	n2 := -1            //读取的数据量
	n3 := -1            //写出的数据量
	total := int(count) //读取的总量

	//复制文件
	for {
		n2, err = file1.Read(data)
		if err == io.EOF || n2 == 0 {
			fmt.Println("文件复制完毕:", total)
			file3.Close()
			//一旦复制完,就删除临时文件
			os.Remove(tempFile)
			break
		}
		n3, err = file2.Write(data[:n2])
		total += n3

		//将赋值的总量存储到临时文件中
		file3.Seek(0, io.SeekStart)
		file3.WriteString(strconv.Itoa(total))

		fmt.Println("已经复制了", total, "字节数据")
		//模拟断电
		if total > 5000 {
			panic("断电啦")
		}
	}
}

func handelError(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

第一次传输的时候,在其中模拟断电(panic),如下图所示,文件复制到  5120  字节时断电。后恢复,文件接着从5123字节开始复制,而不会从头开始复制~~

(3)遍历文件夹

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于go的大文件切片上传、断点续传、秒传.zip 1、如何唯一标示一个文件文件的信息后端会存储在mysql数据库表中。 在上传之前,前端通过 spark-md5.js 计算文件的md5值以此去唯一的标示一个文件。 spark-md5.js 地址:satazor/js-spark-md5 README.md中有spark-md5.js的使用demo,可以去看看。 2、断点续传是如何实现的? 断点续传可以实现这样的功能,比如RD上传200M的文件,当用户上传完199M时,断网了,有了断点续传的功能,我们允许RD再次上传时,能从第199M的位置重新上传。 实现原理: 实现断点续传的前提是,大文件切片上传。然后前端得问后端哪些chunk曾经上传过,让前端跳过这些上传过的chunk就好了。 前端的上传器(uploader.js)在上传时会先发送一个GET请求,这个请求不会携带任何chunk数据,作用就是向后端询问哪些chunk曾经上传过。 后端会将这些数据保存在mysql数据库表中。比如按这种格式:1:2:3:5表示,曾经上传过的分片有1,2,3,5。第四片没有被上传,前端会跳过1,2,3,5。 仅仅会将第四个chunk发送给后端。 3、秒传是如何实现的? 秒传实现的功能是:当RD重复上传一份相同的文件时,除了第一次上传会正常发送上传请求后,其他的上传都会跳过真正的上传,直接显示秒成功。 实现方式: 后端存储着当前文件的相关信息。为了实现秒传,我们需要搞一个字段(isUploaded)表示当前md5对应的文件是否曾经上传过。 后端在处理 前端的上传器(uploader.js)发送的第一个GET请求时,会将这个字段发送给前端,比如 isUploaded = true。前端看到这个信息后,直接跳过上传,显示上传成功。 4、上传暂停是如何实现的? 上传的暂停:并不是去暂停一个已经发送出去的正在进行数据传输的http请求~ 而是暂停发送起发送下一个http请求。 就我们的项目而言,因为我们的文件本来就是先切片,对于我们来说,暂停文件的上传,本质上就是暂停发送下一个chunk。 5、前端上传并发数是多少? 前端的uploader.js中默认会三条线程启动并发上传,前端会在同一时刻并发 发送3个chunk,后端就会相应的为每个请求开启三个协程处理上传的过来的chunk。 在我们的项目中,会将前端并发数调整成了1。原因如下: 因为考虑到了断点续传的实现,后端需要记录下曾经上传过哪些切片。(这个记录在mysql的数据库表中,以 ”1:2:3:4:5“ )这种格式记录。 Mysql5.7默认的存储引擎是innoDB,默认的隔离级别是RR。如果我们将前端的并发数调大,就会出现下面的异常情况: 1. goroutine1 获取开启事物,读取当前上传到记录是 1:2 (未提交事物) 2. goroutine1 在现有的记录上加上自己处理的分片3,并和现有的1:2拼接在一起成1:2:3 (未提交事物) 3. goroutine2 获取开启事物,(因为RR,所以它读不到1:2:3)读取当前上传到记录是 1:2 (未提交事物) 4. goroutine1 提交事物,将1:2:3写回到mysql 5. goroutine2 在现有的记录上加上自己处理的分片4,并和现有的1:2拼接在一起成1:2:4 (提交事物) 可以看到,如果前端并发上传,后端就会出现分片丢失的问题。 故前端将并发数置为1。
Golang 中,Future 和 Promise 的概念通常被称为 Channel。Channel 是一种用于在 Goroutine 之间进行通信和同步的机制,类似于 Unix 系统中的管道。Future 和 Promise 的概念可以通过 Channel 来实现。 Future 是一个表示异步操作结果的对象,它可以让你在不阻塞当前线程的情况下等待异步操作完成。在 Golang 中,可以使用一个带缓冲的 Channel 来实现 Future。当异步操作完成时,会向 Channel 中写入结果,而 Future 对象会等待 Channel 中有数据可读。 例如,下面的代码演示了如何使用 Channel 实现 Future: ``` func asyncOperation() <-chan int { result := make(chan int, 1) go func() { // 异步操作 time.Sleep(time.Second) result <- 42 }() return result } func main() { // 启动异步操作 future := asyncOperation() // 在不阻塞当前线程的情况下等待异步操作完成 fmt.Println("Waiting for async operation...") value := <-future fmt.Printf("Async operation completed with result: %d\n", value) } ``` Promise 是一个表示异步操作的未来结果的对象,它可以让你在异步操作完成之前先返回一个对象,然后在异步操作完成之后再向该对象写入结果。在 Golang 中,可以使用一个未缓冲的 Channel 来实现 Promise。当异步操作完成时,会向 Channel 中写入结果,而 Promise 对象在等待 Channel 可写时会被阻塞。 例如,下面的代码演示了如何使用 Channel 实现 Promise: ``` type Promise struct { result chan int } func asyncOperation() *Promise { promise := &Promise{make(chan int)} go func() { // 异步操作 time.Sleep(time.Second) promise.result <- 42 }() return promise } func main() { // 启动异步操作 promise := asyncOperation() // 在异步操作完成之前先返回一个对象 fmt.Println("Async operation started...") // 在异步操作完成之后再获取结果 value := <-promise.result fmt.Printf("Async operation completed with result: %d\n", value) } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值