Как читать / писать из / в файл с помощью 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. Они используют значительно больший буфер, чем он.

user7610
источник
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();
}
peterSO
источник
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.

Джереми Уолл
источник