文件操作 1.万物皆文件 UNIX 的一个基础设计就是”万物皆文件”(everything is a file)。我们不必知道一个文件到底映射成什么,操作系统的设备驱动抽象成文件。操作系统为设备提供了文件格式的接口。
Go语言中的reader和writer接口也类似。我们只需简单的读写字节,不必知道reader的数据来自哪里,也不必知道writer将数据发送到哪里。 你可以在/dev下查看可用的设备,有些可能需要较高的权限才能访问
2.基本操作 2.1 创建空文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport ( "log" "os" ) var ( newFile *os.File err error ) func main () { newFile, err = os.Create("test.txt" ) if err != nil { log.Fatal(err) } log.Println(newFile) newFile.Close() }
2.2 Truncate文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "log" "os" ) func main () { err := os.Truncate("test.txt" , 100 ) if err != nil { log.Fatal(err) } }
2.3 获取文件信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport ( "fmt" "log" "os" ) var ( fileInfo os.FileInfo err error ) func main () { fileInfo, err = os.Stat("test.txt" ) if err != nil { log.Fatal(err) } fmt.Println("File name:" , fileInfo.Name()) fmt.Println("Size in bytes:" , fileInfo.Size()) fmt.Println("Permissions:" , fileInfo.Mode()) fmt.Println("Last modified:" , fileInfo.ModTime()) fmt.Println("Is Directory: " , fileInfo.IsDir()) fmt.Printf("System interface type: %T\n" , fileInfo.Sys()) fmt.Printf("System info: %+v\n\n" , fileInfo.Sys()) }
2.4 重命名和移动文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "log" "os" ) func main () { originalPath := "test.txt" newPath := "test2.txt" err := os.Rename(originalPath, newPath) if err != nil { log.Fatal(err) } }
2.5 删除文件 1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "log" "os" ) func main () { err := os.Remove("test.txt" ) if err != nil { log.Fatal(err) } }
2.6 打开和关闭文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package mainimport ( "log" "os" ) func main () { file, err := os.Open("test.txt" ) if err != nil { log.Fatal(err) } file.Close() file, err = os.OpenFile("test.txt" , os.O_APPEND, 0666 ) if err != nil { log.Fatal(err) } file.Close() }
2.7 检查文件是否存在 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "log" "os" ) var ( fileInfo *os.FileInfo err error ) func main () { fileInfo, err := os.Stat("test.txt" ) if err != nil { if os.IsNotExist(err) { log.Fatal("File does not exist." ) } } log.Println("File does exist. File information:" ) log.Println(fileInfo) }
2.8 检查读写权限 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport ( "log" "os" ) func main () { file, err := os.OpenFile("test.txt" , os.O_WRONLY, 0666 ) if err != nil { if os.IsPermission(err) { log.Println("Error: Write permission denied." ) } } file.Close() file, err = os.OpenFile("test.txt" , os.O_RDONLY, 0666 ) if err != nil { if os.IsPermission(err) { log.Println("Error: Read permission denied." ) } } file.Close() }
2.9 改变权限、拥有者、时间戳 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "log" "os" "time" ) func main () { err := os.Chmod("test.txt" , 0777 ) if err != nil { log.Println(err) } err = os.Chown("test.txt" , os.Getuid(), os.Getgid()) if err != nil { log.Println(err) } twoDaysFromNow := time.Now().Add(48 * time.Hour) lastAccessTime := twoDaysFromNow lastModifyTime := twoDaysFromNow err = os.Chtimes("test.txt" , lastAccessTime, lastModifyTime) if err != nil { log.Println(err) } }
2.10 硬链接和软链接 一个普通的文件是一个指向硬盘的inode的地方。 硬链接创建一个新的指针指向同一个地方。只有所有的链接被删除后文件才会被删除。硬链接只在相同的文件系统中才工作。你可以认为一个硬链接是一个正常的链接。
symbolic link,又叫软连接,和硬链接有点不一样,它不直接指向硬盘中的相同的地方,而是通过名字引用其它文件。他们可以指向不同的文件系统中的不同文件。并不是所有的操作系统都支持软链接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package mainimport ( "os" "log" "fmt" ) func main () { err := os.Link("original.txt" , "original_also.txt" ) if err != nil { log.Fatal(err) } fmt.Println("creating sym" ) err = os.Symlink("original.txt" , "original_sym.txt" ) if err != nil { log.Fatal(err) } fileInfo, err := os.Lstat("original_sym.txt" ) if err != nil { log.Fatal(err) } fmt.Printf("Link info: %+v" , fileInfo) err = os.Lchown("original_sym.txt" , os.Getuid(), os.Getgid()) if err != nil { log.Fatal(err) } }
3.读写 3.1 复制文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package mainimport ( "os" "log" "io" ) func main () { originalFile, err := os.Open("test.txt" ) if err != nil { log.Fatal(err) } defer originalFile.Close() newFile, err := os.Create("test_copy.txt" ) if err != nil { log.Fatal(err) } defer newFile.Close() bytesWritten, err := io.Copy(newFile, originalFile) if err != nil { log.Fatal(err) } log.Printf("Copied %d bytes." , bytesWritten) err = newFile.Sync() if err != nil { log.Fatal(err) } }
3.2 跳转(Seek)到文件指定位置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport ( "os" "fmt" "log" ) func main () { file, _ := os.Open("test.txt" ) defer file.Close() var offset int64 = 5 var whence int = 0 newPosition, err := file.Seek(offset, whence) if err != nil { log.Fatal(err) } fmt.Println("Just moved to 5:" , newPosition) newPosition, err = file.Seek(-2 , 1 ) if err != nil { log.Fatal(err) } fmt.Println("Just moved back two:" , newPosition) currentPosition, err := file.Seek(0 , 1 ) fmt.Println("Current position:" , currentPosition) newPosition, err = file.Seek(0 , 0 ) if err != nil { log.Fatal(err) } fmt.Println("Position after seeking 0,0:" , newPosition) }
3.3 将字节写入文件 可以使用os包写入一个已经打开的文件。 因为Go可执行包是静态链接的可执行文件,你import的每一个包都会增加你的可执行文件的大小。其它的包如io
、ioutil
、bufio
提供了一些帮助方法,但是它们不是必须的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport ( "os" "log" ) func main () { file, err := os.OpenFile( "test.txt" , os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666 , ) if err != nil { log.Fatal(err) } defer file.Close() byteSlice := []byte ("Bytes!\n" ) bytesWritten, err := file.Write(byteSlice) if err != nil { log.Fatal(err) } log.Printf("Wrote %d bytes.\n" , bytesWritten) }
3.4 快速写入文件 ioutil包有一个非常有用的方法WriteFile()可以处理 创建/打开文件、写字节slice和关闭文件一系列的操作。如果你需要简洁快速地写字节slice到文件中,你可以使用它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "io/ioutil" "log" ) func main () { err := ioutil.WriteFile("test.txt" , []byte ("Hi\n" ), 0666 ) if err != nil { log.Fatal(err) } }
3.5 使用缓冲写文件 bufio包使您可以创建缓冲的 writer,以便可以在将其写入磁盘之前使用内存中的缓冲区。如果您需要在对数据进行大量操作之前将其写入磁盘以节省磁盘IO的时间,则此功能很有用。如果您一次只写一个字节,可以使用bufio把它们攒在内存缓存中,然后一次写入到硬盘中,减少硬盘的磨损以及提升性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 package mainimport ( "log" "os" "bufio" ) func main () { file, err := os.OpenFile("test.txt" , os.O_WRONLY, 0666 ) if err != nil { log.Fatal(err) } defer file.Close() bufferedWriter := bufio.NewWriter(file) bytesWritten, err := bufferedWriter.Write( []byte {65 , 66 , 67 }, ) if err != nil { log.Fatal(err) } log.Printf("Bytes written: %d\n" , bytesWritten) bytesWritten, err = bufferedWriter.WriteString( "Buffered string\n" , ) if err != nil { log.Fatal(err) } log.Printf("Bytes written: %d\n" , bytesWritten) unflushedBufferSize := bufferedWriter.Buffered() log.Printf("Bytes buffered: %d\n" , unflushedBufferSize) bytesAvailable := bufferedWriter.Available() if err != nil { log.Fatal(err) } log.Printf("Available buffer: %d\n" , bytesAvailable) bufferedWriter.Flush() bufferedWriter.Reset(bufferedWriter) bytesAvailable = bufferedWriter.Available() if err != nil { log.Fatal(err) } log.Printf("Available buffer: %d\n" , bytesAvailable) bufferedWriter = bufio.NewWriterSize( bufferedWriter, 8000 , ) bytesAvailable = bufferedWriter.Available() if err != nil { log.Fatal(err) } log.Printf("Available buffer: %d\n" , bytesAvailable) }
3.6 从文件中读取最多n个字节 os.File
提供了文件操作的基本功能, 而 io
、ioutil
、bufio
提供了额外的辅助函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package mainimport ( "os" "log" ) func main () { file, err := os.Open("test.txt" ) if err != nil { log.Fatal(err) } defer file.Close() byteSlice := make ([]byte , 16 ) bytesRead, err := file.Read(byteSlice) if err != nil { log.Fatal(err) } log.Printf("Number of bytes read: %d\n" , bytesRead) log.Printf("Data read: %s\n" , byteSlice) }
3.7 读取正好N个字节 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport ( "os" "log" "io" ) func main () { file, err := os.Open("test.txt" ) if err != nil { log.Fatal(err) } byteSlice := make ([]byte , 2 ) numBytesRead, err := io.ReadFull(file, byteSlice) if err != nil { log.Fatal(err) } log.Printf("Number of bytes read: %d\n" , numBytesRead) log.Printf("Data read: %s\n" , byteSlice) }
3.8 读取至少N个字节 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport ( "os" "log" "io" ) func main () { file, err := os.Open("test.txt" ) if err != nil { log.Fatal(err) } byteSlice := make ([]byte , 512 ) minBytes := 8 numBytesRead, err := io.ReadAtLeast(file, byteSlice, minBytes) if err != nil { log.Fatal(err) } log.Printf("Number of bytes read: %d\n" , numBytesRead) log.Printf("Data read: %s\n" , byteSlice) }
3.9 读取全部字节 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package mainimport ( "os" "log" "fmt" "io/ioutil" ) func main () { file, err := os.Open("test.txt" ) if err != nil { log.Fatal(err) } data, err := ioutil.ReadAll(file) if err != nil { log.Fatal(err) } fmt.Printf("Data as hex: %x\n" , data) fmt.Printf("Data as string: %s\n" , data) fmt.Println("Number of bytes read:" , len (data)) }
3.10 快速读取整个文件到内存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "log" "io/ioutil" ) func main () { data, err := ioutil.ReadFile("test.txt" ) if err != nil { log.Fatal(err) } log.Printf("Data read: %s\n" , data) }
3.11 使用缓存读 有缓存写也有缓存读。 缓存reader会把一些内容缓存在内存中。它会提供比os.File和io.Reader更多的函数,默认的缓存大小是4096,最小缓存是16。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package mainimport ( "os" "log" "bufio" "fmt" ) func main () { file, err := os.Open("test.txt" ) if err != nil { log.Fatal(err) } bufferedReader := bufio.NewReader(file) byteSlice := make ([]byte , 5 ) byteSlice, err = bufferedReader.Peek(5 ) if err != nil { log.Fatal(err) } fmt.Printf("Peeked at 5 bytes: %s\n" , byteSlice) numBytesRead, err := bufferedReader.Read(byteSlice) if err != nil { log.Fatal(err) } fmt.Printf("Read %d bytes: %s\n" , numBytesRead, byteSlice) myByte, err := bufferedReader.ReadByte() if err != nil { log.Fatal(err) } fmt.Printf("Read 1 byte: %c\n" , myByte) dataBytes, err := bufferedReader.ReadBytes('\n' ) if err != nil { log.Fatal(err) } fmt.Printf("Read bytes: %s\n" , dataBytes) dataString, err := bufferedReader.ReadString('\n' ) if err != nil { log.Fatal(err) } fmt.Printf("Read string: %s\n" , dataString) }
3.12 使用 scanner 读 Scanner
是 bufio
包下的类型,在处理文件中以分隔符分隔的文本时很有用。 通常我们使用换行符作为分隔符将文件内容分成多行。在CSV文件中,逗号一般作为分隔符。 os.File
文件可以被包装成 bufio.Scanner
,它就像一个缓存reader。 我们会调用 Scan()
方法去读取下一个分隔符,使用 Text()
或者 Bytes()
获取读取的数据。
分隔符可以不是一个简单的字节或者字符,有一个特殊的方法可以实现分隔符的功能,以及将指针移动多少,返回什么数据。 如果没有定制的 SplitFunc
提供,缺省的 ScanLines
会使用 newline
字符作为分隔符,其它的分隔函数还包括 ScanRunes
和 ScanWords
,皆在 bufio
包中。
1 2 3 4 5 6 7 type SplitFunc func (data []byte , atEOF bool ) (advance int , token []byte , err error)
下面的例子中,为一个文件创建了 bufio.Scanner
,并按照单词逐个读取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package mainimport ( "os" "log" "fmt" "bufio" ) func main () { file, err := os.Open("test.txt" ) if err != nil { log.Fatal(err) } scanner := bufio.NewScanner(file) scanner.Split(bufio.ScanWords) success := scanner.Scan() if success == false { err = scanner.Err() if err == nil { log.Println("Scan completed and reached EOF" ) } else { log.Fatal(err) } } fmt.Println("First word found:" , scanner.Text()) }
4.压缩 4.1 打包(Zip)文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package mainimport ( "archive/zip" "log" "os" ) func main () { outFile, err := os.Create("test.zip" ) if err != nil { log.Fatal(err) } defer outFile.Close() zipWriter := zip.NewWriter(outFile) var filesToArchive = []struct { Name, Body string } { {"test.txt" , "String contents of file" }, {"test2.txt" , "\x61\x62\x63\n" }, } for _, file := range filesToArchive { fileWriter, err := zipWriter.Create(file.Name) if err != nil { log.Fatal(err) } _, err = fileWriter.Write([]byte (file.Body)) if err != nil { log.Fatal(err) } } err = zipWriter.Close() if err != nil { log.Fatal(err) } }
4.2 抽取(Unzip)文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package mainimport ( "archive/zip" "log" "io" "os" "path/filepath" ) func main () { zipReader, err := zip.OpenReader("test.zip" ) if err != nil { log.Fatal(err) } defer zipReader.Close() for _, file := range zipReader.Reader.File { zippedFile, err := file.Open() if err != nil { log.Fatal(err) } defer zippedFile.Close() targetDir := "./" extractedFilePath := filepath.Join( targetDir, file.Name, ) if file.FileInfo().IsDir() { log.Println("Creating directory:" , extractedFilePath) os.MkdirAll(extractedFilePath, file.Mode()) } else { log.Println("Extracting file:" , file.Name) outputFile, err := os.OpenFile( extractedFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode(), ) if err != nil { log.Fatal(err) } defer outputFile.Close() _, err = io.Copy(outputFile, zippedFile) if err != nil { log.Fatal(err) } } } }
4.3 压缩文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "os" "compress/gzip" "log" ) func main () { outputFile, err := os.Create("test.txt.gz" ) if err != nil { log.Fatal(err) } gzipWriter := gzip.NewWriter(outputFile) defer gzipWriter.Close() _, err = gzipWriter.Write([]byte ("Gophers rule!\n" )) if err != nil { log.Fatal(err) } log.Println("Compressed data written to file." ) }
4.4 解压缩文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package mainimport ( "compress/gzip" "log" "io" "os" ) func main () { gzipFile, err := os.Open("test.txt.gz" ) if err != nil { log.Fatal(err) } gzipReader, err := gzip.NewReader(gzipFile) if err != nil { log.Fatal(err) } defer gzipReader.Close() outfileWriter, err := os.Create("unzipped.txt" ) if err != nil { log.Fatal(err) } defer outfileWriter.Close() _, err = io.Copy(outfileWriter, gzipReader) if err != nil { log.Fatal(err) } }
5.其它 5.1 临时文件和目录 ioutil 提供了两个函数: TempDir() 和 TempFile()。 使用完毕后,调用者负责删除这些临时文件和文件夹。 有一点好处就是当你传递一个空字符串作为文件夹名的时候,它会在操作系统的临时文件夹中创建这些项目(/tmp on Linux)。 os.TempDir()返回当前操作系统的临时文件夹。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package mainimport ( "os" "io/ioutil" "log" "fmt" ) func main () { tempDirPath, err := ioutil.TempDir("" , "myTempDir" ) if err != nil { log.Fatal(err) } fmt.Println("Temp dir created:" , tempDirPath) tempFile, err := ioutil.TempFile(tempDirPath, "myTempFile.txt" ) if err != nil { log.Fatal(err) } fmt.Println("Temp file created:" , tempFile.Name()) err = tempFile.Close() if err != nil { log.Fatal(err) } err = os.Remove(tempFile.Name()) if err != nil { log.Fatal(err) } err = os.Remove(tempDirPath) if err != nil { log.Fatal(err) } }
5.2 通过 HTTP 下载文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "os" "io" "log" "net/http" ) func main () { newFile, err := os.Create("devdungeon.html" ) if err != nil { log.Fatal(err) } defer newFile.Close() url := "http://www.devdungeon.com/archive" response, err := http.Get(url) defer response.Body.Close() numBytesWritten, err := io.Copy(newFile, response.Body) if err != nil { log.Fatal(err) } log.Printf("Downloaded %d byte file.\n" , numBytesWritten) }
5.3 哈希和摘要 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport ( "crypto/md5" "crypto/sha1" "crypto/sha256" "crypto/sha512" "log" "fmt" "io/ioutil" ) func main () { data, err := ioutil.ReadFile("test.txt" ) if err != nil { log.Fatal(err) } fmt.Printf("Md5: %x\n\n" , md5.Sum(data)) fmt.Printf("Sha1: %x\n\n" , sha1.Sum(data)) fmt.Printf("Sha256: %x\n\n" , sha256.Sum256(data)) fmt.Printf("Sha512: %x\n\n" , sha512.Sum512(data)) }
上面的例子复制整个文件内容到内存中,传递给hash函数。 另一个方式是创建一个hash writer, 使用Write、WriteString、Copy将数据传给它。 下面的例子使用 md5 hash,但你可以使用其它的Writer。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "crypto/md5" "log" "fmt" "io" "os" ) func main () { file, err := os.Open("test.txt" ) if err != nil { log.Fatal(err) } defer file.Close() hasher := md5.New() _, err = io.Copy(hasher, file) if err != nil { log.Fatal(err) } sum := hasher.Sum(nil ) fmt.Printf("Md5 checksum: %x\n" , sum) }
使用io.copy读取大文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func DownFile () { url :="http://wx.qlogo.cn/Vaz7vE1/64" resp ,err := http.Get(url) if err != nil { fmt.Fprint(os.Stderr ,"get url error" , err) } defer resp.Body.Close() data ,err := ioutil.ReadAll(resp.Body) if err != nil { panic (err) } _ =ioutil.WriteFile("/tmp/icon_wx.png" , data, 0755 ) }
上面代码,如果是大的文件的话,可能会出现内存不足的问题,因为它是需要先把请求内容全部读取到内存中,然后再写入到文件中的。
Golang中提供了 io.copy
方法,它就是在文件指针之间直接复制的,不用全读入内存,可解决这样的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func DownFile () { url :="http://wx.qlogo.cn/Vaz7vE1/64" resp ,err := http.Get(url) if err != nil { fmt.Fprint(os.Stderr ,"get url error" , err) } defer resp.Body.Close() out, err := os.Create("/tmp/icon_wx_2.png" ) wt := bufio.NewWriter(out) defer out.Close() n, err := io.Copy(wt, resp.Body) fmt.Println("write" , n) if err != nil { panic (err) } wt.Flush() }
使用 io.Copy 实现 SSH 代理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 type Endpoint struct { Host string Port int } func (endpoint *Endpoint) String () string { return fmt.Sprintf("%s:%d" , endpoint.Host, endpoint.Port) } type SSHtunnel struct { Local *Endpoint Server *Endpoint Remote *Endpoint Config *ssh.ClientConfig } func (tunnel *SSHtunnel) Start () error { listener, err := net.Listen("tcp" , tunnel.Local.String()) if err != nil { return err } defer listener.Close() for { conn, err := listener.Accept() if err != nil { return err } go tunnel.forward(conn) } } func (tunnel *SSHtunnel) forward (localConn net.Conn) { serverConn, err := ssh.Dial("tcp" , tunnel.Server.String(), tunnel.Config) if err != nil { fmt.Printf("Server dial error: %s\n" , err) return } remoteConn, err := serverConn.Dial("tcp" , tunnel.Remote.String()) if err != nil { fmt.Printf("Remote dial error: %s\n" , err) return } copyConn := func (writer, reader net.Conn) { defer writer.Close() defer reader.Close() _, err := io.Copy(writer, reader) if err != nil { fmt.Printf("io.Copy error: %s" , err) } } go copyConn(localConn, remoteConn) go copyConn(remoteConn, localConn) } func main () { localEndpoint := &Endpoint{ Host: "localhost" , Port: 9000 , } serverEndpoint := &Endpoint{ Host: "some-real-ssh-listening-port" , Port: 22 , } remoteEndpoint := &Endpoint{ Host: "www.baidu.com" , Port: 80 , } sshConfig := &ssh.ClientConfig{ User: "root" , Auth: []ssh.AuthMethod{ ssh.Password("real-password" ), }, HostKeyCallback: func (hostname string , remote net.Addr, key ssh.PublicKey) error { return nil }, } tunnel := &SSHtunnel{ Config: sshConfig, Local: localEndpoint, Server: serverEndpoint, Remote: remoteEndpoint, } tunnel.Start() }
io.Pipe 函数定义如下:
1 func Pipe () (*PipeReader, *PipeWriter)
Pipe适用于,产生了一条数据,紧接着就要处理掉这条数据 的场景。而且因为其内部是一把大锁,因此是线程安全的。
伪代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func (p *pipe) read (b []byte ) (n int , err error) { 各种加锁() for { if 有数据可以读或者哪里有错 { break } 让出时间片等待被唤醒,如果是被正常调度回来的依然不醒,必须是被指名点姓叫醒才醒() } copy (b, p.data) 通知writer可以继续写数据进来了() } func (p *pipe) write (b []byte ) (n int , err error) { 各种加锁() p.data = b 通知reader有数据了() for { if 数据被读完了或者哪里有错 { break } 让出时间片等待被唤醒,如果是被正常调度回来的依然不醒,必须是被指名点姓叫醒才醒() } p.data = nil }
示例:起了多个goroutine作为writer,每个writer内部随机生成字符串写进去。唯一的reader读取数据并打印:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 var r = rand.New(rand.NewSource(time.Now().UnixNano()))func generate (writer *PipeWriter) { arr := make ([]byte , 32 ) for { for i := 0 ; i < 32 ; i++ { arr[i] = byte (r.Uint32() >> 24 ) } n, err := writer.Write(arr) if nil != err { log.Fatal(err) } time.Sleep(200 * time.Millisecond) } } func main () { rp, wp := Pipe() for i := 0 ; i < 20 ; i++ { go generate(wp) } time.Sleep(1 * time.Second) data := make ([]byte , 64 ) for { n, err := rp.Read(data) if nil != err { log.Fatal(err) } if 0 != n { log.Println("main loop" , n, string (data)) } time.Sleep(1 * time.Second) } }
通过 io.Pipe()
生成一对 (PipeReader,PipeWriter)
在单独的协程中,向 PipeWriter
写入数据,在主协程中,通过 io.Copy()
将 PipeReader
的数据复制到标准输出 os.Stdout
上:
1 2 3 4 5 6 7 8 9 10 11 12 func main () { r, w := io.Pipe() go func () { fmt.Fprint(w, "some io.Reader stream to be read\n" ) w.Close() }() if _, err := io.Copy(os.Stdout, r); err != nil { log.Fatal(err) } }