Golang中如何处理大型.go文件与切片

Golang中如何处理大型.go文件与切片 我有一个项目,需要将大量数据存入一个切片中。

数据类似于IMDB类型的图数据库。电影和演员是节点。电影<->演员之间的关系是边。

数据将是只读的,不需要写入。

我意识到最好的方法是使用数据库,然后在启动时将数据读入Go应用程序。但是,我非常希望能在类似Google App Engine这样的场景中完成所有操作,而不需要服务器或类似的东西来托管数据库。

我的问题是:

  • 你们是如何处理大文件的?我发现如果我有非常大的Go文件,VS Code会卡住。
  • 有没有你们推荐的Go原生数据库?我找到了Dagger。但看起来我仍然需要将数据读入其中。它是100%内存驻留的。
  • 任何来自你们经验的实际建议,关于将大量数据读入切片,都将非常有价值。

我知道这是一个奇怪的问题,让我听起来像个新手程序员。我只是想跳出思维定式,让项目的托管成本尽可能低,同时搜索响应非常快。

谢谢!


更多关于Golang中如何处理大型.go文件与切片的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

感谢您的回复。

我最初尝试使用 JSON。但那只是另一个导致 VS Code 崩溃的大型文件。这就是为什么我认为直接使用结构体更好,因为最终无论如何都会用到它。这样可以消除那些开销。

更多关于Golang中如何处理大型.go文件与切片的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


既然我们正在讨论编辑器/集成开发环境,如果你认真对待Go语言编程,我建议你看看Goland(GoLand: JetBrains推出的智能IDE)。它不是免费的,但它非常棒,而且我猜它处理大文件的能力可能比VSCode强得多。

我不知道你说的大文件具体有多大,但我刚刚测试了一个2MB(约5700行)的文件,它瞬间就打开了。

很遗憾,关于数据库的问题我帮不上忙……

在处理如此庞大的静态数据时,你可以将其放入一个JSON文件中,然后使用encoding/json包进行序列化。不过,这需要程序在每次启动时重新序列化数据库,但根据数据条目的数量,这应该不会花费太长时间。

以下是一个示例,展示了如何序列化包含一些数据对象的JSON文件。

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"time"
)

const dataFile = "data.json"

type Movie struct {
	Title       string    `json:"title"`
	Actors      []string  `json:"actors"`
	ReleaseDate time.Time `json:"release_date"`
}

func main() {
	f, err := os.Open(dataFile)
	if err != nil {
		log.Fatal(err)
	}

	var movies []*Movie
	err = json.NewDecoder(f).Decode(&movies)
	if err != nil {
		log.Fatal(err)
	}

	for _, m := range movies {
		fmt.Printf("%+v\n", m)
	}
}

这样做还有一个好处,如果你以后想从REST API或其他类似来源读取JSON数据,可以轻松切换到另一个数据源。

希望这对你有所帮助。

据我所知,并没有推荐的Go源文件大小标准,但当你提到它们“巨大”时,也许你会想把它们拆分成更小的部分,哪怕只是为了取悦VSCode。:slight_smile:

VSCode确实不以高性能和CPU效率著称——毕竟,它是基于Electron构建的。

如果你熟悉使用vi,也可以切换到(Neo-)Vim。我敢说它们俩都能很好地处理大文件。我曾经用Vim编辑过大型日志文件,关闭语法高亮后它就能正常工作。

(说到高亮,可以尝试在VSCode中关闭它,看看这对编辑大文件是否有帮助。我并不是说这是一个解决方案,但我很好奇高亮是否是罪魁祸首。)

Go原生数据库:我想到的是CockroachDB,这是一个支持Postgres有线协议的分布式SQL数据库。还有一个纯Go移植(或者更确切地说是转译)的SQLite——modernc/sqlite。我猜也有相当数量的非SQL数据库可用,从像boltbbolt这样的KV存储到更复杂的数据库(如Dgraph等)。

很抱歉没有包含链接,因为我是用手机写的。也许awesome-go.com上有更多有用的数据库链接。

// 代码示例:假设这里有一些Go代码
func main() {
    fmt.Println("hello world")
}

对于处理大型数据文件和在内存中高效存储,Go语言确实有一些成熟的解决方案。以下是针对你问题的具体方案:

1. 处理大型.go文件的问题

不要将数据硬编码在.go文件中,这会导致IDE卡顿和编译缓慢。应该将数据存储在外部文件中:

// 错误做法:数据硬编码在代码中
var movies = []Movie{
    {ID: 1, Title: "The Shawshank Redemption", Year: 1994},
    // ... 成千上万行数据
}

// 正确做法:从外部文件加载
func loadMoviesFromJSON(filename string) ([]Movie, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        return nil, err
    }
    
    var movies []Movie
    if err := json.Unmarshal(data, &movies); err != nil {
        return nil, err
    }
    
    return movies, nil
}

2. Go原生内存数据库推荐

BadgerDB(推荐)

import "github.com/dgraph-io/badger/v3"

func main() {
    // 打开数据库(纯内存模式)
    opts := badger.DefaultOptions("").WithInMemory(true)
    db, err := badger.Open(opts)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // 写入数据
    err = db.Update(func(txn *badger.Txn) error {
        return txn.Set([]byte("movie:1"), []byte(`{"title":"Inception"}`))
    })
    
    // 读取数据
    err = db.View(func(txn *badger.Txn) error {
        item, err := txn.Get([]byte("movie:1"))
        if err != nil {
            return err
        }
        return item.Value(func(val []byte) error {
            fmt.Printf("Movie: %s\n", val)
            return nil
        })
    })
}

BoltDB(简单易用)

import "go.etcd.io/bbolt"

func main() {
    db, err := bbolt.Open("movies.db", 0600, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // 创建bucket并存储数据
    err = db.Update(func(tx *bbolt.Tx) error {
        b, err := tx.CreateBucketIfNotExists([]byte("Movies"))
        if err != nil {
            return err
        }
        return b.Put([]byte("1"), []byte("The Godfather"))
    })
}

3. 高效内存数据结构实现

对于图数据库场景,可以使用以下结构:

type Graph struct {
    movies  map[int]*Movie
    actors  map[int]*Actor
    // 使用map实现快速查找
    movieActors map[int][]int  // movieID -> []actorID
    actorMovies map[int][]int  // actorID -> []movieID
}

type Movie struct {
    ID    int
    Title string
    Year  int
}

type Actor struct {
    ID   int
    Name string
}

func NewGraph() *Graph {
    return &Graph{
        movies:      make(map[int]*Movie),
        actors:      make(map[int]*Actor),
        movieActors: make(map[int][]int),
        actorMovies: make(map[int][]int),
    }
}

func (g *Graph) AddMovie(movie *Movie, actorIDs []int) {
    g.movies[movie.ID] = movie
    g.movieActors[movie.ID] = actorIDs
    
    for _, actorID := range actorIDs {
        g.actorMovies[actorID] = append(g.actorMovies[actorID], movie.ID)
    }
}

// 快速查找演员参演的电影
func (g *Graph) GetMoviesByActor(actorID int) []*Movie {
    movieIDs := g.actorMovies[actorID]
    movies := make([]*Movie, len(movieIDs))
    
    for i, id := range movieIDs {
        movies[i] = g.movies[id]
    }
    return movies
}

4. 数据加载和序列化优化

使用高效的序列化格式和流式加载:

import (
    "encoding/gob"
    "os"
)

// 保存为二进制格式
func SaveGraph(g *Graph, filename string) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    
    encoder := gob.NewEncoder(file)
    return encoder.Encode(g)
}

// 加载二进制数据
func LoadGraph(filename string) (*Graph, error) {
    file, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    
    var g Graph
    decoder := gob.NewDecoder(file)
    if err := decoder.Decode(&g); err != nil {
        return nil, err
    }
    return &g, nil
}

5. 完整示例:内存图数据库

package main

import (
    "encoding/json"
    "log"
    "os"
    "sync"
)

type MemoryGraphDB struct {
    mu          sync.RWMutex
    movies      map[string]*Movie
    actors      map[string]*Actor
    connections map[string][]string // movieID -> []actorID
}

func NewMemoryGraphDB() *MemoryGraphDB {
    return &MemoryGraphDB{
        movies:      make(map[string]*Movie),
        actors:      make(map[string]*Actor),
        connections: make(map[string][]string),
    }
}

func (db *MemoryGraphDB) LoadFromFile(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return err
    }
    
    var dataset struct {
        Movies  []*Movie `json:"movies"`
        Actors  []*Actor `json:"actors"`
        Links   []struct {
            MovieID string   `json:"movie_id"`
            ActorIDs []string `json:"actor_ids"`
        } `json:"links"`
    }
    
    if err := json.Unmarshal(data, &dataset); err != nil {
        return err
    }
    
    db.mu.Lock()
    defer db.mu.Unlock()
    
    for _, movie := range dataset.Movies {
        db.movies[movie.ID] = movie
    }
    
    for _, actor := range dataset.Actors {
        db.actors[actor.ID] = actor
    }
    
    for _, link := range dataset.Links {
        db.connections[link.MovieID] = link.ActorIDs
    }
    
    return nil
}

func (db *MemoryGraphDB) GetMovieActors(movieID string) []*Actor {
    db.mu.RLock()
    defer db.mu.RUnlock()
    
    actorIDs, exists := db.connections[movieID]
    if !exists {
        return nil
    }
    
    actors := make([]*Actor, len(actorIDs))
    for i, id := range actorIDs {
        actors[i] = db.actors[id]
    }
    return actors
}

这种方案可以在Google App Engine等无服务器环境中运行,启动时加载数据到内存,之后提供快速的只读访问。对于约100MB的数据集,加载时间通常在1-2秒内完成,内存占用约为原始JSON文件的1.5-2倍。

回到顶部