解决连续两次读文件第二次读取到的内容为空的问题

327 阅读4分钟

在连续两次读文件的情况下,第一次读完文件后文件指针会指向文件末尾,因此第二次读文件时会出现内容为空的问题。要解决这个问题,可以通过以下的解决方法:

1.创建读取器

func handlePDFFile(ctx context.Context, db *gorm.DB, clip *models.Clip, owner *carrot.User,
	filename string, content io.Reader, count int, total int, uploadChan chan interface{}) (int, int, error) {
	body, err := io.ReadAll(content)
	if err != nil {
		return count, total, err
	}
	modelCtx, err := api.ReadValidateAndOptimize(bytes.NewReader(body), model.NewDefaultConfiguration())
	if err != nil {
		return count, total, err
	}
	total += modelCtx.PageCount - 1
	if modelCtx.PageCount != 1 {
		for i := 1; i <= modelCtx.PageCount; i++ {
			pageContent, err := api.ExtractPage(modelCtx, i)
			if err != nil {
				return count, total, err
			}
			// 处理每一页的内容
			allCount, allTotal, err := handleImageFile(ctx, db, clip, owner, fmt.Sprintf("%s_page_%d.pdf", filename, i), pageContent, count, total, uploadChan)
			count = allCount
			total = allTotal
			if err != nil {
				if err == models.ErrUploadTerminate {
					return count, total, err
				} else if err == models.ErrInsufficientBalance {
					return count, total, err
				}
			}
		}
	} else {
		allCount, allTotal, err := handleImageFile(ctx, db, clip, owner, filename, bytes.NewReader(body), count, total, uploadChan)
		total = allTotal
		count = allCount
		if err != nil {
			if err == models.ErrUploadTerminate {
				return count, total, err
			} else if err == models.ErrInsufficientBalance {
				return count, total, err
			}
		}
	}
	return count, total, nil
}

bytes.NewReader(body)用于创建一个新的bytes.Reader(一种实现了io.Reader接口的读取器,可以用来从字节切片[]bytes中读取数据)。它的作用是将一个字节切片包装成一个读取器,以便可以像文件或网络流一样进行读取操作。

以上示例中,使用了两次bytes.ReadReader(body),第一次用于传递给api.ReadValidateAndOptimize函数,这是为了将整个pdf文件的内容(已经读取到body字节切片中)传递给该函数进行验证和优化处理。第二次用于将只含单页内容的pdf文件内容直接传递给handleImageFile函数处理。

每次调用bytes.NewReader(body)都会返回一个新的读取器,从字节切片的开头开始读取数据。这样即使已经读取了数据,也可以从头开始重新读取。这种方法很适合需要多次读取同一数据源的场景。

此外,也可以使用io.Seeker接口从头读取文件。

io.Seeker接口允许通过Seek方法调整读取位置,从而可以从头读取文件。 实现了 io.Seeker 接口的类型,例如 bytes.Reader,可以通过调用Seek(0,io.SeekStart) 将文件指针重置到文件开头,然后再次读取内容。

以上代码可以修改为:

func handlePDFFile(ctx context.Context, db *gorm.DB, clip *models.Clip, owner *carrot.User,
	filename string, content io.Reader, count int, total int, uploadChan chan interface{}) (int, int, error) {
	body, err := io.ReadAll(content)
	if err != nil {
		return count, total, err
	}
	reader := bytes.NewReader(body)
	modelCtx, err := api.ReadValidateAndOptimize(reader, model.NewDefaultConfiguration())
	if err != nil {
		return count, total, err
	}
	total += modelCtx.PageCount - 1
	if modelCtx.PageCount != 1 {
		for i := 1; i <= modelCtx.PageCount; i++ {
			pageContent, err := api.ExtractPage(modelCtx, i)
			if err != nil {
				return count, total, err
			}
			// 处理每一页的内容
			allCount, allTotal, err := handleImageFile(ctx, db, clip, owner, fmt.Sprintf("%s_page_%d.pdf", filename, i), pageContent, count, total, uploadChan)
			count = allCount
			total = allTotal
			if err != nil {
				if err == models.ErrUploadTerminate {
					return count, total, err
				} else if err == models.ErrInsufficientBalance {
					return count, total, err
				}
			}
		}
	} else {
		allCount, allTotal, err := handleImageFile(ctx, db, clip, owner, filename, reader, count, total, uploadChan)
		total = allTotal
		count = allCount
		if err != nil {
			if err == models.ErrUploadTerminate {
				return count, total, err
			} else if err == models.ErrInsufficientBalance {
				return count, total, err
			}
		}
	}
	return count, total, nil
}

修改后的代码使用bytes.NewReader(body)创建了一个新的bytes.Reader。在需要从头开始读取文件内容时,使用Seek(0,io.SeekStart)重置读取位置,这样避免了多次调用bytes.NewReader方法,实现了从头读取文件内容的需求。 通过使用io.Seeker接口,可以更加灵活地控制读取位置,从而有效地管理文件读取操作。

2.重新打开文件

在每次读取文件之前重新打开文件,这样文件指针会自动重置到文件开头。

func main() {
	// 第一次读取文件内容
	file, err := os.Open("example.txt")
	if err != nil {
		fmt.Println("Error opening file:", err)
		return
	}
	content, err := ioutil.ReadAll(file)
	if err != nil {
		fmt.Println("Error reading file:", err)
		return
	}
	file.Close()
	fmt.Println("First read content:")
	fmt.Println(string(content))

	// 第二次读取文件内容
	file, err = os.Open("example.txt")
	if err != nil {
		fmt.Println("Error opening file:", err)
		return
	}
	content, err = ioutil.ReadAll(file)
	if err != nil {
		fmt.Println("Error reading file:", err)
		return
	}
	file.Close()
	fmt.Println("Second read content:")
	fmt.Println(string(content))
}

3.使用缓冲区

将文件内容读取到内存缓冲区中,然后从缓冲区中读取数据。

func main() {
    // 读取文件内容到内存缓冲区
    content, err := ioutil.ReadFile("example.txt")
    if err != nil {
       fmt.Println("Error reading file:", err)
       return
    }

    // 第一次读取缓冲区内容
    buffer := bytes.NewBuffer(content)
    fmt.Println("First read content:")
    fmt.Println(buffer.String())

    // 第二次读取缓冲区内容
    buffer = bytes.NewBuffer(content)
    fmt.Println("Second read content:")
    fmt.Println(buffer.String())
}

4.将文件内容存储到字符串中

func main() {
    // 读取文件内容到字符串
    content, err := ioutil.ReadFile("1.txt")
    if err != nil {
       fmt.Println("Error reading file:", err)
       return
    }
    contentStr := string(content)

    // 第一次读取字符串内容
    fmt.Println("First read content:")
    fmt.Println(contentStr)
    
    // 第二次读取字符串内容
    fmt.Println("Second read content:")
    fmt.Println(contentStr)

}

这些方法各有优缺点,选择哪种方法取决于具体的应用场景和需求。例如,如果文件内容较大,重新打开文件可能更节省内存,而将内容读取到内存缓冲区或字符串中则更方便快速读取。