2013-08-25 2 views
2

Недавно я создаю контрольные суммы для файлов в go. Мой код работает с небольшими и большими файлами. Я попробовал два метода, первый использует ioutil.ReadFile("filename"), а второй работает с os.Open("filename").Чтение файлов и контрольные суммы в go. Разница между методами

Примеры:

Первая функция работает с io/ioutil и работает для небольших файлов. Когда я пытаюсь скопировать большой файл, мой баран получает blastet, а для 1.5GB iso он использует 3GB RAM.

func byteCopy(fileToCopy string) { 
    file, err := ioutil.ReadFile(fileToCopy) //1.5GB file 
    omg(err)         //error handling function 
    ioutil.WriteFile("2.iso", file, 0777) 
    os.Remove("2.iso") 
} 

Еще хуже, когда я хочу, чтобы создать контрольную сумму с crypto/sha512 и io/ioutil. Он никогда не закончится и не прервется, потому что у него заканчивается память.

func ioutilHash() { 
    file, _ := ioutil.ReadFile(iso) 
    h := sha512.New() 
    fmt.Printf("%x", h.Sum(file)) 
} 

При использовании функции ниже все работает нормально.

func ioHash() { 
    f, err := os.Open(iso) //iso is a big ~ 1.5tb file 
    omg(err)    //error handling function 
    defer f.Close() 
    h := sha512.New() 
    io.Copy(h, f) 
    fmt.Printf("%x", h.Sum(nil)) 
} 

Мой вопрос:

Почему функция ioutil.ReadFile() не работает правильно? 1.5GB-файл не должен заполнять 16 ГБ RAM. Я не знаю, где искать сейчас. Может ли кто-нибудь объяснить различия между методами? Я не понимаю это, читая go-doc и примеры. Имея полезный код хорошо, но понимая, почему его работа над ним выше.

Заранее благодарен!

ответ

3

Следующий код не работает, как вы думаете.

func ioutilHash() { 
    file, _ := ioutil.ReadFile(iso) 
    h := sha512.New() 
    fmt.Printf("%x", h.Sum(file)) 
} 

Это первое чтение вашего 1.5GB iso. Как указывал jnml, он постоянно делает все больше и больше буферов для заполнения. В конце концов, общий размер буфера не менее 1,5 ГБ и не более 1,875 ГБ (по текущей реализации).

Однако после этого вы делаете еще один буфер! h.Sum(file) не имеет хэш-файл. Он добавляет текущий хэш в файл! Это может или не может вызвать еще одно распределение.

Настоящая проблема заключается в том, что вы берете этот файл, который теперь добавляется с хешем, и печатайте его с помощью% x.Fmt фактически предварительно вычисляет, используя тот же тип метода jnml, указав, что ioutil.ReadAll используется. Поэтому он постоянно выделял большие и большие буферы для хранения шестнадцатеричного файла. Поскольку каждая буква составляет 4 бита, это означает, что мы говорим о не менее чем 3 ГБ буфере для этого и не более 3,75 ГБ.

Это означает, что ваши активные буферы могут быть такими же большими 5.625 ГБ. Объедините это с тем, что GC не идеален и не удаляет все промежуточные буферы, и он может очень легко заполнить ваше пространство.


Правильный способ написать этот код.

func ioutilHash() { 
    file, _ := ioutil.ReadFile(iso) 
    h := sha512.New() 
    h.Write(file) 
    fmt.Printf("%x", h.Sum(nil)) 
} 

Это не делает почти номера ассигнований.


Суть в том, что ReadFile редко используется, что вы хотите использовать. Потоковая передача IO (с использованием читателей и писателей) всегда является лучшим способом, когда это вариант. Вы не только выделяете гораздо меньше, когда используете io.Copy, вы также хеш и читаете диск одновременно. В примере с ReadFile оба ресурса используются синхронно, когда они не зависят друг от друга.

1

ioutil.ReadFile работает правильно. Это ваша ошибка, чтобы злоупотреблять системными ресурсами, используя эту функцию для вещей, которые, как вы знаете, огромны.

ioutil.ReadFile - удобный помощник для файлов, которые вы уверены, заранее, что они будут маленькими. Как файлы конфигурации, большинство файлов исходного кода и т. Д. (На самом деле это оптимизирует вещи для files <= 1e9 bytes, но это деталь реализации, а не часть контракта API. Ваш файл объемом 1,5 ГБ заставляет его использовать срез, растущий и, следовательно, выделяя более одного big буфер для ваших данных в процессе чтения файла.)

Даже ваш другой подход, используя os.File, не в порядке. Вы определенно должны использовать пакет «bufio» для последовательной обработки больших файлов, см. bufio.NewReader.

Смежные вопросы