Как читать / писать из / в файл с помощью Go?


284

Я пытался изучать Go самостоятельно, но я был в тупике, пытаясь читать и записывать в обычные файлы.

Я могу так далеко inFile, _ := os.Open(INFILE, 0, 0), но на самом деле получить содержимое файла не имеет смысла, потому что функция чтения принимает []byteв качестве параметра.

func (file *File) Read(b []byte) (n int, err Error)

Ответы:


476

Давайте создадим Go 1-совместимый список всех способов чтения и записи файлов в Go.

Поскольку файловый API недавно изменился, и большинство других ответов не работают с Go 1. Они также пропускают, bufioчто важно, ИМХО.

В следующих примерах я копирую файл, читая его и записывая в файл назначения.

Начните с основ

package main

import (
    "io"
    "os"
)

func main() {
    // open input file
    fi, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    // close fi on exit and check for its returned error
    defer func() {
        if err := fi.Close(); err != nil {
            panic(err)
        }
    }()

    // open output file
    fo, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    // close fo on exit and check for its returned error
    defer func() {
        if err := fo.Close(); err != nil {
            panic(err)
        }
    }()

    // make a buffer to keep chunks that are read
    buf := make([]byte, 1024)
    for {
        // read a chunk
        n, err := fi.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        // write a chunk
        if _, err := fo.Write(buf[:n]); err != nil {
            panic(err)
        }
    }
}

Здесь я использовал os.Openи os.Createкоторые являются удобными обертками вокруг os.OpenFile. Нам обычно не нужно звонить OpenFileнапрямую.

Обратите внимание на лечение EOF. Readпытается заполнить bufкаждый вызов и возвращает io.EOFкак ошибку, если при этом он достигает конца файла. В этом случае bufвсе равно будут храниться данные. Последующие вызовы Readвозвращают ноль как количество прочитанных байтов и io.EOFкак ошибку. Любая другая ошибка приведет к панике.

С помощью bufio

package main

import (
    "bufio"
    "io"
    "os"
)

func main() {
    // open input file
    fi, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    // close fi on exit and check for its returned error
    defer func() {
        if err := fi.Close(); err != nil {
            panic(err)
        }
    }()
    // make a read buffer
    r := bufio.NewReader(fi)

    // open output file
    fo, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    // close fo on exit and check for its returned error
    defer func() {
        if err := fo.Close(); err != nil {
            panic(err)
        }
    }()
    // make a write buffer
    w := bufio.NewWriter(fo)

    // make a buffer to keep chunks that are read
    buf := make([]byte, 1024)
    for {
        // read a chunk
        n, err := r.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        // write a chunk
        if _, err := w.Write(buf[:n]); err != nil {
            panic(err)
        }
    }

    if err = w.Flush(); err != nil {
        panic(err)
    }
}

bufioпросто выступает в качестве буфера здесь, потому что мы не имеем ничего общего с данными. В большинстве других ситуаций (особенно с текстовыми файлами) bufioэто очень полезно, предоставляя нам хороший API для простого и гибкого чтения и записи, в то время как он обрабатывает буферизацию за кулисами.

С помощью ioutil

package main

import (
    "io/ioutil"
)

func main() {
    // read the whole file at once
    b, err := ioutil.ReadFile("input.txt")
    if err != nil {
        panic(err)
    }

    // write the whole body at once
    err = ioutil.WriteFile("output.txt", b, 0644)
    if err != nil {
        panic(err)
    }
}

Проще простого! Но используйте его, только если вы уверены, что не имеете дело с большими файлами.


55
Для тех, кто сталкивается с этим вопросом, он был задан в 2009 году до того, как были представлены эти библиотеки, поэтому, пожалуйста, используйте этот ответ в качестве руководства!
— Сет Хениг

1
Согласно golang.org/pkg/os/#File.Write , когда Write не записал все байты, он возвращает ошибку. Поэтому дополнительная проверка в первом примере ( panic("error in writing")) не требуется.
— Айк

15
Обратите внимание, что в этих примерах не проверяется ошибка, возвращаемая функцией fo.Close (). Из man-страниц Linux close (2): Не проверка возвращаемого значения close () является распространенной, но, тем не менее, серьезной ошибкой программирования. Вполне возможно, что ошибки в предыдущей операции write (2) сначала сообщаются в финальном закрытии (). Не проверка возвращаемого значения при закрытии файла может привести к потере данных без вывода сообщений. Это особенно заметно в случае NFS и дисковой квоты.
— Ник Крейг-Вуд,

12
Итак, что такое "большой" файл? 1KB? 1 МБ? 1 Гб? Или «большой» зависит от аппаратного обеспечения машины?
— 425nesp

3
@ 425nesp Читает весь файл в память, поэтому зависит от объема доступной памяти на работающей машине.
— Мостафа

50

Это хорошая версия:

package main

import (
  "io/ioutil"; 
  )


func main() {
  contents,_ := ioutil.ReadFile("plikTekstowy.txt")
  println(string(contents))
  ioutil.WriteFile("filename", contents, 0644)
}

8
Это хранит весь файл в памяти. Поскольку файл может быть большим, это не всегда то, что вы хотите сделать.
— user7610

9
Кроме того, 0x777является поддельным. В любом случае, это должно быть больше как 0644или 0755(восьмеричное, а не шестнадцатеричное).
— CNST

@cnst изменил его на 0644 с 0x777
— Трентон

31

С помощью io.Copy

package main

import (
    "io"
    "log"
    "os"
)

func main () {
    // open files r and w
    r, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    defer r.Close()

    w, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    defer w.Close()

    // do the actual work
    n, err := io.Copy(w, r)
    if err != nil {
        panic(err)
    }
    log.Printf("Copied %v bytes\n", n)
}

Если вы не хотите изобретать велосипед, то io.Copyи io.CopyNможет служить вам хорошо. Если вы проверите источник функции io.Copy, это не что иное, как одно из решений Mostafa (собственно, «базовое»), упакованное в библиотеку Go. Они используют значительно больший буфер, чем он.


5
Стоит упомянуть одну вещь - чтобы быть уверенным, что содержимое файла было записано на диск, его необходимо использовать w.Sync()послеio.Copy(w, r)
— Shay Tsadok,

Кроме того, если вы записываете в уже существующий файл, io.Copy()будут записываться только те данные, которыми вы его кормите, поэтому, если в существующем файле было больше содержимого, он не будет удален, что может привести к повреждению файла.
— Invidian

1
@ Invidian Все зависит от того, как вы открываете файл назначения. Если вы это сделаете w, err := os.Create("output.txt"), то, что вы описываете, не произойдет, потому что «Create создает или усекает указанный файл. Если файл уже существует, он усекается». golang.org/pkg/os/#Create .
— user7610

Это должен быть правильный ответ, так как он не заново изобретает колесо, при этом не нужно читать весь файл сразу, прежде чем читать его.
— Эли Дэвис

11

В новых версиях Go чтение / запись в / из файла становится проще. Чтобы прочитать из файла:

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    data, err := ioutil.ReadFile("text.txt")
    if err != nil {
        return
    }
    fmt.Println(string(data))
}

Для записи в файл:

package main

import "os"

func main() {
    file, err := os.Create("text.txt")
    if err != nil {
        return
    }
    defer file.Close()

    file.WriteString("test\nhello")
}

Это перезапишет содержимое файла (создайте новый файл, если его там не было).


10

[]byteявляется срезом (похожим на подстроку) всего или части байтового массива. Думайте о срезе как о структуре значений со скрытым полем указателя, чтобы система могла найти и получить доступ ко всему или части массива (среза), а также к полям для длины и емкости среза, к которым вы можете получить доступ, используя len()иcap() функции ,

Вот вам рабочий стартовый комплект, который читает и печатает двоичный файл; вам нужно изменить inNameбуквальное значение, чтобы ссылаться на небольшой файл в вашей системе.

package main
import (
    "fmt";
    "os";
)
func main()
{
    inName := "file-rw.bin";
    inPerm :=  0666;
    inFile, inErr := os.Open(inName, os.O_RDONLY, inPerm);
    if inErr == nil {
        inBufLen := 16;
        inBuf := make([]byte, inBufLen);
        n, inErr := inFile.Read(inBuf);
        for inErr == nil {
            fmt.Println(n, inBuf[0:n]);
            n, inErr = inFile.Read(inBuf);
        }
    }
    inErr = inFile.Close();
}

9
Соглашение Go состоит в том, чтобы сначала проверить на наличие ошибок и позволить нормальному коду находиться за пределами ifблока
— hasen

@Jurily: если файл открыт при возникновении ошибки, как закрыть его?
— peterSO

10
@peterSO: использовать отсрочку
— Джеймс Антилл

Но почему [256] байт не принят и явно глупо и многословно (но, очевидно, не неправильно) inBuf: = make ([] byte, 256)?
— Кардифф космический человек

7

Попробуй это:

package main

import (
  "io"; 
  )


func main() {
  contents,_ := io.ReadFile("filename");
  println(string(contents));
  io.WriteFile("filename", contents, 0644);
}

1
Это будет работать, если вы хотите прочитать весь файл сразу. Если файл действительно большой или вы хотите прочитать только его часть, это может быть не то, что вы ищете.
— Эван Шоу

3
Вы должны действительно проверить код ошибки, а не игнорировать его так !!
— hasen

7
Это было перенесено в пакет ioutil. Так что это будет ioutil.ReadFile ()
— Кристофер

Я исправил так, что это говорит 0644
— Иоаким

1

Просто просматривая документацию, кажется, что вы должны просто объявить буфер типа [] byte и передать его для чтения, который затем прочитает до такого количества символов и вернет количество фактически прочитанных символов (и ошибку).

Документы говорят

Read читает до len (b) байтов из файла. Он возвращает количество прочитанных байтов и ошибку, если таковая имеется. EOF сигнализируется нулевым счетом с ошибкой, установленной в EOF.

Это не работает?

РЕДАКТИРОВАТЬ: Кроме того, я думаю, что вы, возможно, следует использовать интерфейсы Reader / Writer, объявленные в пакете bufio , вместо использования пакета os .


У вас есть мой голос, потому что вы фактически признаете то, что видят реальные люди, когда они читают документацию, вместо того, чтобы попугайничать, что те, кто привык к Go, НАПОМИНАНЫ (не читают НАПОМИНАНО), когда они читают документацию функции, с которой они уже знакомы.
— Кардифф космонавт

1

Метод Read принимает параметр байта, потому что это буфер, в который он будет читать. Это распространенная идиома в некоторых кругах, и она имеет смысл, когда вы думаете об этом.

Таким образом, вы можете определить, сколько байтов будет прочитано читателем, и просмотреть возвращаемый результат, чтобы увидеть, сколько фактически было прочитано байтов, и соответствующим образом обработать любые ошибки.

Как указали другие в своих ответах, bufio - это, вероятно, то, что вы хотите прочитать из большинства файлов.

Я добавлю еще одну подсказку, поскольку она действительно полезна. Чтение строки из файла лучше всего выполнять не методом ReadLine, а методом ReadBytes или ReadString.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.