该程序应读取一个由整数对组成的文件(每行一对)并删除重复的对。虽然它适用于小型文件,但会对大型文件(例如1.5 GB的文件)引发运行时错误。最初,我认为是导致此问题的是地图数据结构,但是即使在注释掉之后,它仍然会耗尽内存。任何想法为什么会这样?如何纠正呢?这是一个内存不足的数据文件:http : //snap.stanford.edu/data/com-Orkut.html
package main
import (
"fmt"
"bufio"
"os"
"strings"
"strconv"
)
func main() {
file, err := os.Open(os.Args[1])
if err != nil {
panic(err.Error())
}
defer file.Close()
type Edge struct {
u, v int
}
//seen := make(map[Edge]bool)
edges := []Edge{}
scanner := bufio.NewScanner(file)
for i, _ := strconv.Atoi(os.Args[2]); i > 0; i-- {
scanner.Scan()
}
for scanner.Scan() {
str := scanner.Text()
edge := strings.Split(str, ",")
u, _ := strconv.Atoi(edge[0])
v, _ := strconv.Atoi(edge[1])
var key Edge
if u < v {
key = Edge{u,v}
} else {
key = Edge{v,u}
}
//if seen[key] {
// continue
//}
//seen[key] = true
edges = append(edges, key)
}
for _, e := range edges {
s := strconv.Itoa(e.u) + "," + strconv.Itoa(e.v)
fmt.Println(s)
}
}
下面给出了示例输入。该程序可以按以下方式运行(最后输入表示要跳过多少行)。去运行undup.go a.txt 1
# 3072441,117185083
1,2
1,3
1,4
1,5
1,6
1,7
1,8
我看了看这个文件:com-orkut.ungraph.txt
它包含117185082行。数据的结构方式,即每行至少16个字节。(Edge是两个64bit int)仅此一个就是1.7GB。我过去曾遇到过这个问题,这可能是一个棘手的问题。您是要解决特定用例(问题文件)还是一般用例?
在特定情况下,您可以利用有关数据的一些事项:(1)对键进行排序,并且(2)看起来它将每个连接存储两次,(3)数字似乎并不大。这里有一些想法:
如果为密钥使用较小的类型,则将使用较少的内存。试试吧uint32
。
您可以通过简单地查看第二列是否大于第一列来将密钥流式传输(不使用映射)到另一个文件:
if u < v {
// write the key to another file
} else {
// skip it because v will eventually show v -> u
}
对于一般情况,您可以使用几种策略:
如果结果列表的顺序无关紧要:使用磁盘上的哈希表存储映射。有这些一堆:性LevelDB,sqlite的,东京暴君,...一个非常好的一个走的是螺栓。
在for循环中,您只需要检查存储桶中是否包含给定密钥即可。(您可以使用编码/二进制将整数转换为字节片)。如果确实如此,则跳过它并继续。您将需要将第二个for循环处理步骤移到第一个for循环中,这样就不必存储所有键。
如果结果列表的顺序很重要(并且您不能保证输入顺序正确):您还可以使用磁盘上的哈希表,但需要对其进行排序。螺栓已排序,因此可以正常工作。将所有键添加到其中,然后在第二个循环中遍历。
这是一个示例:(此程序要花一亿时间才能运行一亿条记录)
package main
import (
"bufio"
"encoding/binary"
"fmt"
"github.com/boltdb/bolt"
"os"
"strconv"
"strings"
)
type Edge struct {
u, v int
}
func FromKey(bs []byte) Edge {
return Edge{int(binary.BigEndian.Uint64(bs[:8])), int(binary.BigEndian.Uint64(bs[8:]))}
}
func (e Edge) Key() [16]byte {
var k [16]byte
binary.BigEndian.PutUint64(k[:8], uint64(e.u))
binary.BigEndian.PutUint64(k[8:], uint64(e.v))
return k
}
func main() {
file, err := os.Open(os.Args[1])
if err != nil {
panic(err.Error())
}
defer file.Close()
scanner := bufio.NewScanner(file)
for i, _ := strconv.Atoi(os.Args[2]); i > 0; i-- {
scanner.Scan()
}
db, _ := bolt.Open("ex.db", 0777, nil)
defer db.Close()
bucketName := []byte("edges")
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucketIfNotExists(bucketName)
return nil
})
batchSize := 10000
total := 0
batch := make([]Edge, 0, batchSize)
writeBatch := func() {
total += len(batch)
fmt.Println("write batch. total:", total)
db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(bucketName)
for _, edge := range batch {
key := edge.Key()
bucket.Put(key[:], nil)
}
return nil
})
}
for scanner.Scan() {
str := scanner.Text()
edge := strings.Split(str, "\t")
u, _ := strconv.Atoi(edge[0])
v, _ := strconv.Atoi(edge[1])
var key Edge
if u < v {
key = Edge{u, v}
} else {
key = Edge{v, u}
}
batch = append(batch, key)
if len(batch) == batchSize {
writeBatch()
// reset the batch length to 0
batch = batch[:0]
}
}
// write anything leftover
writeBatch()
db.View(func(tx *bolt.Tx) error {
tx.Bucket(bucketName).ForEach(func(k, v []byte) error {
edge := FromKey(k)
fmt.Println(edge)
return nil
})
return nil
})
}
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句