文件合并
问题:把一个目录下的所有.txt文件合成一个大的.txt文件,再对这个大文件进行压缩
分析思路:
想要把一个目录下的所有txt文件合成一个大的文件big.txt,再对这个大文件压缩big.zlib
创建文件
1.先创建 big.txt ,一般都会加defer fout.Close() ,这是让文件当前函数执行完毕后关闭
//创建文件
fout, writer, _, err := openFileAndCreateWriter("big.txt")
if err != nil {
fmt.Println(err)
return
}
defer fout.Close()
fout2, _, writer2, err := openFileAndCreateWriter("big.zlib")
if err != nil {
fmt.Println(err)
return
}
defer fout2.Close()
读取目录
- 读取指定整个目录,用
ioutil.ReadDir(dir)
if files, err := ioutil.ReadDir(dir); err != nil {
// ioutil.ReadDir这是再读取指定目录文件
fmt.Println(err)
return
} else { .....}
遍历
- 再 for遍历判断读取目录中每一个文件的类型,如果还是目录就直接跳过,我们只要.txt文件。使用
IsDir来判断是不是目录,使用HasSuffix判断文件后缀 ,值得学习的是HasPrefix是前缀
for _, file := range files {
if file.IsDir() {
// 如果读到的是一个目录,那跳过这次
continue
}
baseName := file.Name() // 获取当前文件名
if strings.HasSuffix(baseName, ".txt") {
inPath := filepath.Join(dir, baseName)
readFile(inPath, writer, writer2)
}
}
这个range遍历一个 files会返回index和value(这里用一个变量file接收),index用不到所以直接下划线
HasPrefix是前缀,HasSuffix是后缀 如果当前读到的baseName文件后缀为 .txt
判断读取
- 开始读取文件代码里封装了readFile(),在mergeFile里调用所以没调用一次readFile就是一个文件正在被打开使用
os.Open(inPath)打开文件,打开出错直接return,没有出错就defer 让它在函数执行完毕后就关闭
if fin, err := os.Open(inPath); err != nil {
fmt.Println(err)
return //新建函数可以直接return了
} else {
defer fin.Close()
reader := bufio.NewReader(fin)
..........
}
开始读取
- 怎么读?使用
reader.ReadString('\n')一行一行读,用err==io.EOF判断是否达到末尾。注意这里是for里放if 然后if中一行一行读,所以reader.ReadString('\n')参数用 '\n' 读到这个说明这行读完了
for {
if line, err := reader.ReadString('\n'); err != nil { //读的过程有err,有err不一定等于出错,也可能是读到末尾了
if err == io.EOF { //如果到了文件末尾
if len(line) > 0 { // 文件最后一行没有换行符
writer.WriteString(line) //把读到的内容写入到 fout(big.txt)文件里
writer.WriteString("\n") //自己加上换行符
writer2.Write([]byte(line)) //把读到的内容写入到 fout(big.txt)文件里
writer2.Write([]byte{'\n'}) //自己加上换行符
}
}
break
} else { //没出错
writer.WriteString(line) //直接写入
writer2.Write([]byte(line))
}
}
还有一个小细节,这个文件读完的时候可能不是空行结尾的,所以用
len(line) > 0来判断文件尾有没有换行符
压缩
-
好了读完的内容要写到big.txt后缀big.zlib里面去 .txt的可以直接用string写,但是zlib压缩的只能用切片来写所以txt用
writer.WriteString(line)。而zlib用
writer2.Write([]byte(line))好,还要记得我们自己加上换行符,因为两个文件衔接要换行。这样才不会让前一个文件的末尾和后一个文件的开始 贴成一行
// 压缩
func zipFile() (*zlib.Writer, *os.File, error) {
fout, err := os.OpenFile("big.zlib", os.O_CREATE|os.O_TRUNC, os.ModePerm)
if err != nil {
fmt.Println(err)
return nil, nil, err
}
// defer fout.Close() // 这个defer是函数执行完后关闭不是马上关闭
writer := zlib.NewWriter(fout) // 这是创建一个写入器
return writer, fout, nil
}
发现有些问题,可以优化这个压缩代码所以:
优化
// 压缩 //还是垃圾有问题,需要优化
func zipFile() (*zlib.Writer, *os.File, error) {
fout, err := os.OpenFile("big.zlib", os.O_CREATE|os.O_TRUNC, os.ModePerm)
if err != nil {
fmt.Println(err)
return nil, nil, err
}
// defer fout.Close() // 这个defer是函数执行完后关闭不是马上关闭
writer := zlib.NewWriter(fout) // 这是创建一个写入器
return writer, fout, nil
}
// 优化
func openFileAndCreateWriter(filename string) (*os.File, *bufio.Writer, *zlib.Writer, error) {
fout, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC, os.ModePerm)
if err != nil {
return nil, nil, nil, err
}
writer := bufio.NewWriter(fout)
var zlibWriter *zlib.Writer
if strings.HasSuffix(filename, ".zlib") {
zlibWriter = zlib.NewWriter(fout)
}
return fout, writer, zlibWriter, nil
}
- 避免频繁的文件创建和关闭: 在原始版本中,每次调用
zipFile都会创建一个新文件。在优化版本中,只在第一次调用时创建文件,后续调用重用同一个文件。
- 使用缓冲写入: 引入了
bufio.Writer,它对写入的数据进行缓冲,减少频繁的磁盘写入操作,提高性能。
- 条件化的压缩: 优化版本中根据文件名是否以 ".zlib" 后缀来决定是否使用
zlib.Writer进行压缩。这样,只有需要压缩的文件才会使用压缩操作,避免不必要的开销。
刷新
- 最后记得刷新 文件
writer.Flush()
//刷新
writer.Flush()
writer2.Flush()
完整实例代码:
package main
import (
"bufio"
"compress/zlib"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
func readFile(inPath string, writer *bufio.Writer, writer2 *zlib.Writer) {
if fin, err := os.Open(inPath); err != nil {
fmt.Println(err)
//continue //这里不能用return,因为这个文件打开出错而已,其他文件可能可以
return //新建函数可以直接return了
} else {
defer fin.Close()
reader := bufio.NewReader(fin)
for {
if line, err := reader.ReadString('\n'); err != nil { //读的过程有err,有err不一定等于出错,也可能是读到末尾了
if err == io.EOF { //如果到了文件末尾
if len(line) > 0 { // 文件最后一行没有换行符
writer.WriteString(line) //把读到的内容写入到 fout(big.txt)文件里
writer.WriteString("\n") //自己加上换行符
writer2.Write([]byte(line)) //把读到的内容写入到 fout(big.txt)文件里
writer2.Write([]byte{'\n'}) //自己加上换行符
}
}
break
} else { //没出错
writer.WriteString(line) //直接写入
writer2.Write([]byte(line))
}
}
}
}
// 压缩 //还是垃圾有问题,需要优化
func zipFile() (*zlib.Writer, *os.File, error) {
fout, err := os.OpenFile("big.zlib", os.O_CREATE|os.O_TRUNC, os.ModePerm)
if err != nil {
fmt.Println(err)
return nil, nil, err
}
// defer fout.Close() // 这个defer是函数执行完后关闭不是马上关闭
writer := zlib.NewWriter(fout) // 这是创建一个写入器
return writer, fout, nil
}
// 优化
func openFileAndCreateWriter(filename string) (*os.File, *bufio.Writer, *zlib.Writer, error) {
fout, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC, os.ModePerm)
if err != nil {
return nil, nil, nil, err
}
writer := bufio.NewWriter(fout)
var zlibWriter *zlib.Writer
if strings.HasSuffix(filename, ".zlib") {
zlibWriter = zlib.NewWriter(fout)
}
return fout, writer, zlibWriter, nil
}
func mergeFile(dir string) {
// func OpenFile(name string, flag int, perm FileMode) (*File, error)
/*
参数一:name 是要打开或创建的文件名。
参数二:flag 是打开文件的标志,它是一个位掩码,
可以使用 os.O_CREATE 表示如果文件不存在则创建,
os.O_TRUNC 表示打开时清空文件内容,
os.O_APPEND 表示在文件末尾添加内容等等。
多个标志可以通过按位或运算符 | 组合在一起。
参数三:perm 是文件的权限,用于指定新创建的文件的权限位。
它是一个八进制数,表示文件的读、写和执行权限。可以使用 os.ModePerm 表示默认的文件权限。
perm 参数用于指定新创建的文件的权限位,即文件的读、写和执行权限。它是一个八进制数,通常以三个八进制位表示,分别对应着所有者、所属组和其他用户的权限。
在 Go 的 os 包中,预定义了一些常用的文件权限常量,包括:
os.ModeDir:目录权限,用于表示新创建的目录的权限。
os.ModeAppend:追加权限,用于在文件打开时在末尾添加内容。
os.ModeExclusive:独占权限,用于以独占方式打开文件。
os.ModeTemporary:临时权限,用于表示临时文件的权限。
os.ModeSymlink:符号链接权限,用于表示符号链接的权限。
通常,我们使用 os.ModePerm 表示默认的文件权限,它是一个八进制数 0777,表示所有者、所属组和其他用户都具有读、写和执行权限。
*/
// fout, err := os.OpenFile("big.txt", os.O_CREATE|os.O_TRUNC, os.ModePerm)
// if err != nil {
// fmt.Println(err)
// return
// }
// defer fout.Close() //打开成功后 这个defer是函数执行完毕后 close掉,不是马上关闭
// writer := bufio.NewWriter(fout) // 这是创建一个写入器
// writer2, fout2, _ := zipFile()
// defer fout2.Close()
//创建文件
fout, writer, _, err := openFileAndCreateWriter("big.txt")
if err != nil {
fmt.Println(err)
return
}
defer fout.Close()
fout2, _, writer2, err := openFileAndCreateWriter("big.zlib")
if err != nil {
fmt.Println(err)
return
}
defer fout2.Close()
//
if files, err := ioutil.ReadDir(dir); err != nil { // ioutil.ReadDir这是再读取指定目录文件
fmt.Println(err)
return
} else {
for _, file := range files { // 这个range遍历一个 files 会返回index和value(这里用一个变量file接收),index用不到所以直接下划线
if file.IsDir() { // 如果读到的是一个目录,那跳过这次
continue
}
baseName := file.Name() // 获取当前文件名
if strings.HasSuffix(baseName, ".txt") { // HasPrefix是前缀,HasSuffix是后缀 如果当前读到的baseName文件后缀为 .txt
inPath := filepath.Join(dir, baseName)
readFile(inPath, writer, writer2)
}
}
}
//刷新
writer.Flush()
writer2.Flush()
}