Як читати / писати з / у файл за допомогою 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що важливо IMHO.

У наступних прикладах я копіюю файл, читаючи з нього та записуючи у файл призначення.

Почніть з основ

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 man-сторінок Linux close (2): Не перевіряти значення повернення close () є поширеною, але все-таки серйозною помилкою програмування. Цілком можливо, що помилки в попередній операції запису (2) спочатку повідомляються в кінцевому завершенні (). Якщо не перевірити значення повернення при закритті файлу, це може призвести до беззвучної втрати даних. Особливо це можна спостерігати з NFS та з дисковою квотою.
Нік Крейг-Вуд

12
Отже, що таке "великий" файл? 1 КБ? 1 МБ? 1 Гб? Або "велике" залежить від апаратури машини?
425nesp

3
@ 425nesp Він зчитує весь файл у пам'ять, тому це залежить від кількості наявної пам’яті в запущеній машині.
Мостафа

49

Це хороша версія:

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)
Шай Цадок

Крім того, якщо ви запишете у вже існуючий файл, 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: використовуйте defer
Джеймс Антілл

Але чому байт [256] не приймається, а чітко дурне і багатослівне (але, мабуть, не помилкове) в InBuf: = make ([] байт, 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

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

Документи кажуть

Зчитування читає до len (b) байтів з файлу. Він повертає кількість прочитаних байтів та помилку, якщо така є. EOF сигналізується нульовим підрахунком з помилкою, встановленою EOF.

Це не працює?

EDIT: Крім того, я думаю, ви, можливо, вам слід використовувати інтерфейси Reader / Writer, оголошені в пакеті bufio, замість того, щоб використовувати пакет os .


У вас є мій голос, тому що ви фактично визнаєте те, що реально бачать люди, читаючи документацію, замість того, щоб папугувати те, що звикли їхати, ЗАБЕЗПЕЧЕНО (не читають ЗАБЕЗПЕЧЕНО), коли вони читають документацію функції, з якою вони вже знайомі.
Кардіфф космічний чоловік

1

Метод Read бере параметр байта, тому що це буфер, який він буде читати. Це загальна ідіома в деяких колах і має певний сенс, коли ви думаєте про це.

Таким чином ви можете визначити, скільки байтів прочитає читач і перевірити повернення, щоб побачити, скільки байтів насправді було прочитано, і обробити будь-які помилки належним чином.

Як зазначають інші у своїх відповідях, буфіо - це, мабуть, те, що потрібно читати з більшості файлів.

Я додам ще одну підказку, оскільки це дійсно корисно. Читання рядка з файлу найкраще здійснюється не методом ReadLine, а методом ReadBytes або ReadString.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.