Альтернатива ioutil.ReadAll в ходу? - PullRequest
0 голосов
/ 27 сентября 2018

Для программы, которую я делаю, эта функция запускается как процедура в цикле for в зависимости от того, сколько URL передано (без заданного количества).

func makeRequest(url string, ch chan<- string, errors map[string]error){
  res, err := http.Get(url)
  if err != nil {
    errors[url] = err
    close(ch)
    return
  }

  defer res.Body.Close()
  body, _ := ioutil.ReadAll(res.Body)
  ch <- string(body)
}

Необходимо использовать все тело ответа, чтобы ioutil.ReadAll выглядело как идеально подходящее, но без ограничения количества URL-адресов, которые могут быть переданы, и характера ReadAll, заключающегося в том, что онвсе, что хранится в памяти, начинает меньше походить на золотой билет.Я довольно новичок в Go, так что если вы решите ответить, если бы вы могли дать какое-то объяснение своему решению, это было бы очень признательно!

Ответы [ 3 ]

0 голосов
/ 27 сентября 2018

Одно понимание, которое я получил, когда узнал, как использовать Go, заключается в том, что ReadAll часто неэффективен для больших читателей и, как и в вашем случае, может привести к тому, что произвольный ввод будет очень большим и, возможно, утечкой памяти.Когда я начинал, я обычно выполнял синтаксический анализ JSON следующим образом:

data, err := ioutil.ReadAll(r)
if err != nil {
    return err
}
json.Unmarshal(data, &v)

Затем я узнал о гораздо более эффективном способе синтаксического анализа JSON, который заключается в простом использовании типа Decoder.

err := json.NewDecoder(r).Decode(&v)
if err != nil {
    return err
}

Мало того, что это более кратко, это намного более эффективно, и с точки зрения памяти, и с точки зрения времени:

  • Декодеру не нужно выделять огромный фрагмент байтачтобы приспособиться для чтения данных - он может просто повторно использовать крошечный буфер, который будет использоваться против метода Read, чтобы получить все данные и проанализировать их.Это экономит много времени при выделении ресурсов и снимает нагрузку с GC
  • . Декодер JSON может начать синтаксический анализ данных, как только поступит первая порция данных - ему не нужно ждать, пока все завершит загрузку.

Теперь, конечно, ваш вопрос не имеет ничего общего с JSON, но этот пример полезен для иллюстрации того, что если вы можете использовать Read напрямую и анализировать порции данных одновременно, сделайте это.Особенно в случае HTTP-запросов синтаксический анализ выполняется быстрее, чем чтение / загрузка, поэтому это может привести к тому, что проанализированные данные будут почти сразу готовы к моменту окончания поступления тела запроса.

В вашем случае вы, по-видимому, на самом деле ничего не делаетеобработка данных на данный момент, поэтому не так много, чтобы предложить вам конкретно помочь.Но интерфейсы io.Reader и io.Writer являются эквивалентом каналов Go UNIX, поэтому их можно использовать в самых разных местах:

Запись данных в файл:

f, err := os.Create("file")
if err != nil {
    return err 
}
defer f.Close()

// Copy will put all the data from Body into f, without creating a huge buffer in memory
// (moves chunks at a time)
io.Copy(f, resp.Body)

Печать всего в стандартный вывод:

io.Copy(os.Stdout, resp.Body)

Передача тела ответа в тело запроса:

resp, err := http.NewRequest("POST", "https://example.com", resp.Body)
0 голосов
/ 27 сентября 2018

Хотя это довольно просто на ходу

Вот клиентская программа:

package main

import (
    "fmt"
    "net/http"
)

var data []byte

func main() {
    data = make([]byte, 128)

    ch := make(chan string)

    go makeRequest("http://localhost:8080", ch)

    for v := range ch {
        fmt.Println(v)
    }
}

func makeRequest(url string, ch chan<- string) {
    res, err := http.Get(url)
    if err != nil {
        close(ch)
        return
    }
    defer res.Body.Close()
    defer close(ch) //don't forget to close the channel as well

    for n, err := res.Body.Read(data); err == nil; n, err = res.Body.Read(data) {
        ch <- string(data[:n])
    }
}

Вот программа подачи:

package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/", hello)
    http.ListenAndServe("localhost:8080", nil)
}

func hello(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, "movie.mkv")
}
0 голосов
/ 27 сентября 2018

Чтобы ограничил объем памяти, которую ваше приложение использует , общий подход заключается в чтении в буфер, который должен непосредственно решить вашу проблему ioutil.ReadAll.

* 1005Пакет * go's bufio предлагает утилиты (Scanner), которые поддерживают чтение до разделителя или чтение строки из ввода, что тесно связано с вопросом @ Howl
...