在Go中使用ioutil.ReadFile()读取1200多个文件时出现错误

4

我正在尝试读取一个目录中的所有文件(+10,000个),但是当我处理了约1400个文件后,我会遇到“文件打开过多”的错误。我已经显式地调用了垃圾回收器,但这似乎对我的问题没什么帮助。我检查了ioutil包的源代码,并发现ReadFile在内部使用defer file.Close()(如预期所示)。那么问题出在哪里呢?

    const TEMPLATE = `{ "source_db" : "CDARCHIEF", "doc_type" : "%s", "referentie" : "%s", "bestandsnaam" : "%s", "tekst" : "%s" }`
const MAPPING = `{ ... }`

var DIR, DOCTYPE, URL string

func init() {
    flag.StringVar(&DIR, "d", "./", "de directory met de ge-ocrde bestanden")
    flag.StringVar(&DOCTYPE, "t", "AG", "document type [ AG, CO, NN ]")
    flag.StringVar(&URL, "url", "...", "url voor de juiste index")
}

func main() {
    flag.Parse()
    fmt.Println("CD Archive Importer")
    importDocuments()
}

func importDocuments() {
    logfile, _ := os.Create("./importer.log")
    defer logfile.Close()

    files, _ := ioutil.ReadDir(DIR)
    error_counter := 0

    for i, file := range files {
        if math.Mod(float64(i), 400.0) == 0.0 {
            runtime.GC()
            fmt.Println("Running garbage collector")
        }

        fmt.Printf("Importing ( %d / %d ) [ errors: %d ]\r", i+1, len(files), error_counter)

        contents, err := ioutil.ReadFile(DIR + "/" + file.Name())
        if err != nil {
            error_counter = error_counter + 1
            logfile.WriteString(fmt.Sprintf("[ERROR/IO] : %s | %s\n", file.Name(), err))
            continue
        }

        contents_string := strings.Replace(string(contents), "\n", " ", -1)
        contents_string = strings.Replace(contents_string, "\"", " ", -1)
        contents_string = strings.Replace(contents_string, "\\", " ", -1)

        referentie := strings.Trim(file.Name(), ".txt")
        message := strings.NewReader(fmt.Sprintf(TEMPLATE, DOCTYPE, referentie, file.Name(), contents_string))

        resp, error := http.Post(URL, "application/json", message)
        if error != nil {
            error_counter = error_counter + 1
            logfile.WriteString(fmt.Sprintf("[ERROR/NET] : %s | %s | %s\n", file.Name(), resp.Status, error))
            continue
        }
            defer resp.Body.Close()

        if resp.StatusCode != 201 {
            body, _ := ioutil.ReadAll(resp.Body)
            error_counter = error_counter + 1
            logfile.WriteString(fmt.Sprintf("[ERROR/ES] : %s | %s | %s\n", file.Name(), resp.Status, string(body)))
        }

    }

    fmt.Println("\nDone!")
}

我知道大约两年前有一个类似的问题,但是那个问题对我的问题没有用。


感谢@phihag指出这一点。在准备问题的代码时,那是一个小小的疏忽。不过我的原始问题仍然存在。 - commesan
1
你在哪个平台上?你确定你的REST相关代码没有创建任何文件描述符吗? - Dmitri Goldring
我已经在问题中添加了REST部分@DmitriGoldring,但我不认为我正在创建大量的文件描述符。或者我有什么疏忽吗? - commesan
1
你没有关闭 resp.Body。请参考 http 文档中概述部分的第二个示例。 - Dmitri Goldring
4
哦,使用延迟语句无济于事,因为在循环结束之前,你并没有从函数中返回。尝试在 ioutil.ReadAll 后的下一行直接使用 resp.Body.Close() - Dmitri Goldring
显示剩余4条评论
1个回答

0

你可能想考虑使用filepath.Walk。我已经成功地在10k+个文件上使用它,没有遇到任何问题。另外,你也可以深入源代码,看看他们在资源管理方面是否有不同的做法。

此外,for循环看起来有些复杂,你可以只使用整数和%运算符。

for i := 0; i < 1000000; i += 1 {
    if i % 5000 == 0 {
        fmt.Println(i)
    }
}

感谢您提供“%”运算符的提示。我正在开始学习编写Go语言。在深入研究包时,可能会忽略一些显而易见的方法。 - commesan
尽管错误在于没有正确关闭resp.Body。使用filepath.Walk会让我更快地启动并运行,因为defer会起作用。由于Dmitri在评论中帮助我解决了这个问题,所以我将标记此答案为已接受。功劳归于人。 - commesan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接