ioutil包二


ioutil包二

 (原創隨筆,轉載請注明出處 http://www.cnblogs.com/majianguo/p/8016426.html)

ioutil包實現了一些I/O實用功能,導出了7個函數和1個變量:

func NopCloser(r io.Reader) io.ReadCloser

func ReadAll(r io.Reader) ([]byte, error)

func ReadDir(dirname string) ([]os.FileInfo, error)

func ReadFile(filename string) ([]byte, error)

func TempDir(dir, prefix string) (name string, err error)

func TempFile(dir, prefix string) (f *os.File, err error)

func WriteFile(filename string, data []byte, perm os.FileMode) error

var Discard io.Writer = devNull(0)

前一篇隨筆Golang文件I/O 一》介紹了用ReadFileWriteFile讀寫文件,本文介紹ioutil包的其他功能。

 

ioutil.ReadAll

 

ReadFileWriteFile可一次性讀寫文件的全部數據,非常便捷,除去錯誤判斷,只需一行代碼即可搞定。ReadAllReadFile類似,可一次性從io.Reader接口讀取所有內容。

以下是來自godoc的內容:

func ReadAll(r io.Reader) ([]byte, error)

ReadAll reads from r until an error or EOF and returns the data it read. A successful call returns err == nil, not err == EOF. Because ReadAll is defined to read from src until EOF, it does not treat an EOF from Read as an error to be reported.

 

ReadAllr中讀取數據直到發生錯誤或遇到EOF,返回讀到的數據。調用成功返回err == nil,而不是err == EOF。因為ReadAll被定義為從源讀取數據直到結束(EOF),不會將讀取過程中遇到的EOF作為error報告。

godoc中提供了一個ReadAllExample,從一個strings.NewReader  構造的io.Reader中讀取所有數據:

r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.")

 

b, err := ioutil.ReadAll(r)

if err != nil {

    log.Fatal(err)

}

fmt.Printf("%s", b)

  

再看另外一個比較實用的例子,發起一個http request,然后從response中讀取數據:

package main

 

import (

    "fmt"

    "io/ioutil"

    "log"

    "net/http"

)

 

func main() {

    resp,err := http.Get("http://www.baidu.com")

    if err != nil {

        log.Fatal(err)

    }

 

    bData,err := ioutil.ReadAll(resp.Body)

    if err != nil {

        log.Fatal(err)

    }

defer resp.Body.Close()

    fmt.Println(string(bData))

}

 

  

通過ioutil包的源碼可以看到ReadFileReadAll調用了同一個函數readAll

func readAll(r io.Reader, capacity int64) (b []byte, err error)

 

func ReadAll(r io.Reader) ([]byte, error) {

    return readAll(r, bytes.MinRead)

}

func ReadFile(filename string) ([]byte, error) {

    f, err := os.Open(filename)

    if err != nil {

        return nil, err

    }

    defer f.Close()

    // It's a good but not certain bet that FileInfo will tell us exactly how much to

    // read, so let's try it but be prepared for the answer to be wrong.

    var n int64

 

    if fi, err := f.Stat(); err == nil {

        // Don't preallocate a huge buffer, just in case.

        if size := fi.Size(); size < 1e9 {

            n = size

        }

    }

    // As initial capacity for readAll, use n + a little extra in case Size is zero,

    // and to avoid another allocation after Read has filled the buffer. The readAll

    // call will read into its allocated internal buffer cheaply. If the size was

    // wrong, we'll either waste some space off the end or reallocate as needed, but

    // in the overwhelmingly common case we'll get it just right.

    return readAll(f, n+bytes.MinRead)

}

  

 

 

io.TempFile\io.TempDir

TempFileTempDir在指定位置生成臨時文件和臨時文件夾。

以下來自godoc

 

func TempDir(dir, prefix string) (name string, err error)

TempDir creates a new temporary directory in the directory dir with a name beginning with prefix and returns the path of the new directory. If dir is the empty string, TempDir uses the default directory for temporary files (see os.TempDir). Multiple programs calling TempDir simultaneously will not choose the same directory. It is the caller's responsibility to remove the directory when no longer needed.

 

TempDir在指定的dir目錄新創建以prefix為前綴的臨時目錄,返回新臨時目錄的路徑。如果dir為空字符串,TempDir使用默認的存放臨時文件的目錄(os.TempDir,操作系統臨時目錄)。多個程序同時調用TempDir不會返回相同目錄。調用者負責刪除不再需要的臨時目錄。

   Godoc中的Example,創建臨時目錄,退出時調用os.RemoveAll刪除:

content := []byte("temporary file's content")

dir, err := ioutil.TempDir("", "example")

if err != nil {

    log.Fatal(err)

}

defer os.RemoveAll(dir) // clean up

tmpfn := filepath.Join(dir, "tmpfile")

if err := ioutil.WriteFile(tmpfn, content, 0666); err != nil {

    log.Fatal(err)

}

  

 

如果dir指定的目錄不存在,返回err

CreateFile d:/notexistdir: The system cannot find the file specified.

 

Godoc中關於TempFile的說明如下:

func TempFile(dir, prefix string) (f *os.File, err error)

TempFile creates a new temporary file in the directory dir with a name beginning with prefix, opens the file for reading and writing, and returns the resulting *os.File. If dir is the empty string, TempFile uses the default directory for temporary files (see os.TempDir). Multiple programs calling TempFile simultaneously will not choose the same file. The caller can use f.Name() to find the pathname of the file. It is the caller's responsibility to remove the file when no longer needed.

TempFiledir目錄中新創建以prefix為前綴的臨時文件,打開文件用於讀寫,返回os.File指針。如果dir為空字符串,TempFile使用默認的存放臨時文件的目錄(os.TempDir,操作系統臨時目錄)。多個程序同時調用TempFile不會選擇相同的文件。調用者負責刪除不再需要的臨時文件。

 

Godoc中關於TempFileExample,在操作系統臨時目錄中創建臨時文件,退出時調用os.Remove刪除臨時文件:

content := []byte("temporary file's content")

tmpfile, err := ioutil.TempFile("", "example")

if err != nil {

    log.Fatal(err)

}

defer os.Remove(tmpfile.Name()) // clean up

if _, err := tmpfile.Write(content); err != nil {

    log.Fatal(err)

}

if err := tmpfile.Close(); err != nil {

    log.Fatal(err)

}

  

如果dir不存在,返回err

open d:\notexistdir\tmp946090042: The system cannot find the path specified.

 

 

Windows中,dir為空字符串時,os.TempDir返回的目錄為:

C:\Users\Dell\AppData\Local\Temp\

 

 

ioutil.ReadDir

ioutil.ReadDir可以用於遍歷目錄。Godoc中關於ReadDir的說明如下:

func ReadDir(dirname string) ([]os.FileInfo, error)

ReadDir reads the directory named by dirname and returns a list of directory entries sorted by filename.

ReadDir返回dirname目錄的目錄條目列表,按照文件名排序。

 

Godoc中的Example

files, err := ioutil.ReadDir(".")

if err != nil {

    log.Fatal(err)

}

for _, file := range files {

    fmt.Println(file.Name())

}

  

 

略作修改,即可迭代遍歷目錄:

package main

 

import (

    "log"

    "fmt"

    "io/ioutil"

)

 

func main() {

 

err := listFolder("d:/Tmp","")

if err != nil {

     log.Fatal(err)

}

    

}

 

func listFolder(dir string,indent string)(err error){

    fis ,err := ioutil.ReadDir(dir)

    if err != nil {

        return err

    }

    for _,fi := range fis {

        if fi.IsDir(){

            fmt.Printf("%sd:%s\r\n",indent,fi.Name())           

            err = listFolder(dir+fi.Name()+"/",indent+"\t")

            if err != nil {

                return err

            }

        }else{

            fmt.Printf("%sf:%s\r\n",indent,fi.Name())

        }

        

    }

    return 

}

  

 

ioutil.NopCloser

 

func NopCloser(r io.Reader) io.ReadCloser

NopCloser returns a ReadCloser with a no-op Close method wrapping the provided Reader r.

NopCloser函數將io.Reader接口適配為io.ReadCloser接口,為其封裝了一個無操作的Close方法。

 

其源碼非常簡單;

type nopCloser struct {

    io.Reader

}

 

func (nopCloser) Close() error { return nil }

 

// NopCloser returns a ReadCloser with a no-op Close method wrapping

// the provided Reader r.

func NopCloser(r io.Reader) io.ReadCloser {

    return nopCloser{r}

}

  

 

這里使用了一個隱藏實現的技術,go標准庫大量使用。非導出結構體nopCloser有一個匿名成員io.ReadernopCloser實現了Closer方法,從而nopCloser實現了io.ReadCloser接口;ioutil.NopCloser函數返回nopCloser實現了從io.Readerio.ReadCloser的適配。對調用者來說,內部的實現細節被隱藏掉了。

Io.nopClosernet包中有大量的使用,src\net\http\httputil中的一個例子:

 

// drainBody reads all of b to memory and then returns two equivalent

// ReadClosers yielding the same bytes.

//

// It returns an error if the initial slurp of all bytes fails. It does not attempt

// to make the returned ReadClosers have identical error-matching behavior.

func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {

    if b == http.NoBody {

        // No copying needed. Preserve the magic sentinel meaning of NoBody.

        return http.NoBody, http.NoBody, nil

    }

    var buf bytes.Buffer

    if _, err = buf.ReadFrom(b); err != nil {

        return nil, b, err

    }

    if err = b.Close(); err != nil {

        return nil, b, err

    }

    return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil

}

 

  

 

Ioutil.NopCloserbytes.NewReader構造的io.Reader接口封裝成一個io.ReadCloser接口。

 

 

ioutil.Discard

變量定義如下:

// Discard is an io.Writer on which all Write calls succeed

// without doing anything.

var Discard io.Writer = devNull(0)

  

 

ioutil.Discard是一個io.Writer變量,對它的所有寫調用都成功,不做任何操作。

 

func (devNull) Write(p []byte) (int, error) {

    return len(p), nil

}

 

func (devNull) WriteString(s string) (int, error) {

    return len(s), nil

}

  

 

 

ioutil.Discard在標准庫net包和測試用例中有大量使用,以下代碼節選自src\net\http\server.go

 

 

    // Per RFC 2616, we should consume the request body before

    // replying, if the handler hasn't already done so. But we

    // don't want to do an unbounded amount of reading here for

    // DoS reasons, so we only try up to a threshold.

    // TODO(bradfitz): where does RFC 2616 say that? See Issue 15527

    // about HTTP/1.x Handlers concurrently reading and writing, like

    // HTTP/2 handlers can do. Maybe this code should be relaxed?

    if w.req.ContentLength != 0 && !w.closeAfterReply {

        var discard, tooBig bool

 

        switch bdy := w.req.Body.(type) {

        case *expectContinueReader:

            if bdy.resp.wroteContinue {

                discard = true

            }

        case *body:

            bdy.mu.Lock()

            switch {

            case bdy.closed:

                if !bdy.sawEOF {

                    // Body was closed in handler with non-EOF error.

                    w.closeAfterReply = true

                }

            case bdy.unreadDataSizeLocked() >= maxPostHandlerReadBytes:

                tooBig = true

            default:

                discard = true

            }

            bdy.mu.Unlock()

        default:

            discard = true

        }

 

        if discard {

            _, err := io.CopyN(ioutil.Discard, w.reqBody, maxPostHandlerReadBytes+1)

            switch err {

            case nil:

                // There must be even more data left over.

                tooBig = true

            case ErrBodyReadAfterClose:

                // Body was already consumed and closed.

            case io.EOF:

                // The remaining body was just consumed, close it.

                err = w.reqBody.Close()

                if err != nil {

                    w.closeAfterReply = true

                }

            default:

                // Some other kind of error occurred, like a read timeout, or

                // corrupt chunked encoding. In any case, whatever remains

                // on the wire must not be parsed as another HTTP request.

                w.closeAfterReply = true

            }

        }

 

        if tooBig {

            w.requestTooLarge()

            delHeader("Connection")

            setHeader.connection = "close"

        }

    }

 

  

 

RFC2616的規范,http server在回復之前需要消耗掉所有的request body

 

 (原創隨筆,轉載請注明出處 http://www.cnblogs.com/majianguo/p/8016426.html)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM