最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

純Go語(yǔ)言實(shí)現(xiàn)百萬(wàn)級(jí)數(shù)據(jù)高效導(dǎo)出Excel和CSV實(shí)戰(zhàn)指南

 更新時(shí)間:2025年09月13日 08:50:14   作者:楓葉V  
這篇文章主要為大家詳細(xì)介紹了如何通過(guò)純Go語(yǔ)言實(shí)現(xiàn)百萬(wàn)級(jí)數(shù)據(jù)高效導(dǎo)出Excel和CSV的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),需要的小伙伴可以了解下

只用 Go,一篇文章吃透“大數(shù)據(jù)導(dǎo)出”的性能坑流式思路、與可直接跑的完整示例。

重點(diǎn):極低內(nèi)存、可中斷、可并發(fā)、可在 HTTP 中直接下載。

為什么大數(shù)據(jù)導(dǎo)出總“炸內(nèi)存”

  • 一次性加載:先查全量數(shù)據(jù)再寫(xiě)文件 → O(N) 內(nèi)存直接拉滿。
  • 非流式寫(xiě)入:許多庫(kù)默認(rèn)把整表緩存在內(nèi)存里,再一次性落盤(pán)。
  • 文件格式差異:XLSX 是壓縮的 XML 打包;CSV 則是純文本。CSV 寫(xiě)入更快內(nèi)存更省,但不支持樣式/合并等。

核心原則分頁(yè)查詢 + 流式寫(xiě)入 + 限流并發(fā) + 可中斷(Context)。

技術(shù)選型(純 Go)

格式庫(kù)/包優(yōu)點(diǎn)注意點(diǎn)
XLSX[github.com/xuri/excelize/v2]支持樣式/多表/列寬/凍結(jié)窗格;有StreamWriter寫(xiě)樣式需要謹(jǐn)慎,流式寫(xiě)下不建議頻繁變更樣式
CSV標(biāo)準(zhǔn)庫(kù) encoding/csv依賴零、最快、最省內(nèi)存無(wú)樣式、用 Excel 打開(kāi)時(shí)默認(rèn)編碼/分隔符需注意

最小可跑:流式寫(xiě)出 100 萬(wàn)行 XLSX

關(guān)鍵:StreamWriter + SetRow + Flush,最后 f.Write(io.Writer)/f.SaveAs(path)

內(nèi)存監(jiān)控:runtime.ReadMemStats。

// go mod init example && go get github.com/xuri/excelize/v2
package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"strconv"
	"time"

	"github.com/xuri/excelize/v2"
)

func main() {
	start := time.Now()
	f := excelize.NewFile()
	defer f.Close()

	const sheet = "Sheet1"
	sw, err := f.NewStreamWriter(sheet)
	must(err)

	// 寫(xiě)表頭
	header := []interface{}{"ID", "Name", "Amount", "CreatedAt"}
	cell, _ := excelize.CoordinatesToCellName(1, 1)
	must(sw.SetRow(cell, header))

	// 模擬寫(xiě) 1,000,000 行
	n := 1_000_000
	for i := 1; i <= n; i++ {
		row := []interface{}{
			i,
			"User_" + strconv.Itoa(i),
			rand.Intn(10_000),
			time.Now().Add(time.Duration(i) * time.Second).Format(time.RFC3339),
		}
		cell, _ := excelize.CoordinatesToCellName(1, i+1)
		must(sw.SetRow(cell, row))

		// 每 50k 行打印一次內(nèi)存占用
		if i%50_000 == 0 {
			var m runtime.MemStats
			runtime.ReadMemStats(&m)
			fmt.Printf("[progress] rows=%d heap=%.2fMB\n", i, float64(m.HeapAlloc)/1024.0/1024.0)
		}
	}

	must(sw.Flush())

	// 可選:凍結(jié)首行 + 自動(dòng)列寬(注意:自動(dòng)列寬對(duì)流式無(wú)感,需在 Flush 后做固定寬度)
	// 凍結(jié)首行
	must(f.SetPanes(sheet, &excelize.Panes{
		Freeze:      true,
		YSplit:      1,
		ActivePane:  "bottomLeft",
		TopLeftCell: "A2",
	}))

	// 保存到磁盤(pán)(也可改為寫(xiě)到 HTTP ResponseWriter,見(jiàn)后文)
	must(f.SaveAs("bigdata.xlsx"))

	fmt.Printf("done in %v\n", time.Since(start))
}

func must(err error) {
	if err != nil {
		panic(err)
	}
}

效果:在普通開(kāi)發(fā)機(jī)上可穩(wěn)定導(dǎo)出百萬(wàn)行,堆內(nèi)存通常幾十到百余 MB(視系統(tǒng)/版本而定),遠(yuǎn)低于一次性構(gòu)建整表的做法。

極致輕量:CSV 流式導(dǎo)出

零依賴、寫(xiě)入速度快、最省內(nèi)存。

Excel 可以直接打開(kāi)(注意編碼/分隔符)。

package main

import (
	"encoding/csv"
	"fmt"
	"os"
	"strconv"
)

func main() {
	file, err := os.Create("bigdata.csv")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	w := csv.NewWriter(file)
	defer w.Flush()

	// 表頭
	_ = w.Write([]string{"ID", "Name", "Age"})

	const n = 1_000_000
	for i := 1; i <= n; i++ {
		_ = w.Write([]string{
			strconv.Itoa(i),
			"User_" + strconv.Itoa(i),
			strconv.Itoa(18 + i%30),
		})
		if i%100_000 == 0 {
			fmt.Println("rows:", i)
			w.Flush() // 顯式?jīng)_刷,降低緩沖占用
		}
	}
}

HTTP 直接下載(無(wú)中間文件)

邊寫(xiě)邊下:避免臨時(shí)大文件;但需要瀏覽器保持連接,失敗要能重試。

XLSX:流式寫(xiě)到 ResponseWriter(示例代碼)

package main

import (
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/xuri/excelize/v2"
)

func main() {
	http.HandleFunc("/export/xlsx", exportXLSX)
	http.HandleFunc("/export/csv", exportCSV)
	log.Println("listen :8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func exportXLSX(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
	w.Header().Set("Content-Disposition", "attachment; filename=\"report.xlsx\"")

	f := excelize.NewFile()
	defer f.Close()

	sw, err := f.NewStreamWriter("Sheet1")
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	_ = sw.SetRow("A1", []interface{}{"ID", "Name", "Amount", "CreatedAt"})

	n := 200_000 // 從 query 或業(yè)務(wù)層拿
	for i := 1; i <= n; i++ {
		cell, _ := excelize.CoordinatesToCellName(1, i+1)
		_ = sw.SetRow(cell, []interface{}{
			i,
			"User_" + strconv.Itoa(i),
			i % 1000,
			time.Now().Format(time.RFC3339),
		})
	}
	_ = sw.Flush()

	// 直接寫(xiě)到 ResponseWriter
	if err := f.Write(w); err != nil {
		// 客戶端中斷下載常見(jiàn):write: broken pipe
		log.Println("write err:", err)
	}
}

func exportCSV(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/csv; charset=utf-8")
	w.Header().Set("Content-Disposition", "attachment; filename=\"report.csv\"")

	cw := NewCSVStream(w)
	defer cw.Flush()

	_ = cw.Write([]string{"ID", "Name", "Age"})
	for i := 1; i <= 500_000; i++ {
		_ = cw.Write([]string{
			strconv.Itoa(i),
			"User_" + strconv.Itoa(i),
			strconv.Itoa(18 + i%30),
		})
	}
}

// 輕薄包裝:將 http.ResponseWriter 適配成 csv.Writer
type CSVStream struct{ w *csv.Writer }

func NewCSVStream(w http.ResponseWriter) *CSVStream { return &CSVStream{w: csv.NewWriter(w)} }
func (c *CSVStream) Write(rec []string) error       { return c.w.Write(rec) }
func (c *CSVStream) Flush()                         { c.w.Flush() }

數(shù)據(jù)庫(kù)分頁(yè):生產(chǎn)-消費(fèi)流式管道

目標(biāo):不在內(nèi)存里堆積全量數(shù)據(jù)。

方案:分頁(yè)拉取(或游標(biāo)查詢)→ 放入 channel → 單一 writer 順序?qū)懗觥?/p>

package main

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	_ "modernc.org/sqlite" // 為了示例,用 SQLite;生產(chǎn)用 MySQL/PostgreSQL 驅(qū)動(dòng)
)

type Row struct {
	ID        int64
	Name      string
	Amount    int64
	CreatedAt time.Time
}

func StreamRows(ctx context.Context, db *sql.DB, pageSize int, out chan<- Row) error {
	defer close(out)

	offset := 0
	for {
		// 替換為你的 SQL(MySQL 推薦 keyset/游標(biāo)翻頁(yè))
		rows, err := db.QueryContext(ctx,
			`SELECT id, name, amount, created_at FROM t ORDER BY id LIMIT ? OFFSET ?`,
			pageSize, offset,
		)
		if err != nil {
			return err
		}
		n := 0
		for rows.Next() {
			var r Row
			if err := rows.Scan(&r.ID, &r.Name, &r.Amount, &r.CreatedAt); err != nil {
				_ = rows.Close()
				return err
			}
			select {
			case out <- r: // 下游慢時(shí)自然限速
			case <-ctx.Done():
				_ = rows.Close()
				return ctx.Err()
			}
			n++
		}
		_ = rows.Close()
		if n == 0 {
			return nil // 完成
		}
		offset += n
	}
}

func ExamplePipeline(ctx context.Context, db *sql.DB) error {
	out := make(chan Row, 1024) // 小緩沖,避免放大內(nèi)存
	go func() {
		_ = StreamRows(ctx, db, 5000, out)
	}()

	// 在這里對(duì)接 XLSX/CSV Writer(單寫(xiě)者,保持順序)
	for r := range out {
		// sw.SetRow(...)/csv.Write(...)
		_ = r
	}
	return ctx.Err()
}

func main() {
	fmt.Println("見(jiàn) ExamplePipeline;將其與前文 XLSX/CSV 寫(xiě)入代碼拼裝即可。")
}

Keyset 翻頁(yè)(基于上次最大 ID)在 MySQL 大表場(chǎng)景下更穩(wěn)更快,避免 OFFSET 深翻導(dǎo)致的回表代價(jià)。

取消/限流/超時(shí)與資源回收

  • Context:貫穿 DB 查詢、業(yè)務(wù)、Writer,允許用戶在前端取消下載時(shí)快速停水關(guān)閘。
  • 限流:time.Ticker 控制每秒寫(xiě)入/查詢頁(yè)數(shù),避免壓垮 DB。
  • 回收:defer rows.Close()/file.Close()/sw.Flush(),對(duì)網(wǎng)絡(luò)中斷容錯(cuò)。
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Minute) // HTTP 請(qǐng)求 5 分鐘超時(shí)
defer cancel()

// 在所有 QueryContext/SetRow 處傳遞 ctx,遇到取消/超時(shí)立即停止

樣式/列寬/凍結(jié)窗格

  • 凍結(jié)首行:見(jiàn)前文 SetPanes 示例。
  • 列寬:SetColWidth(sheet, "A", "D", 18),注意:StreamWriter 模式下不要頻繁改動(dòng),否則性能受損。
  • 樣式:可用 NewStyle + SetCellStyle,但大數(shù)據(jù)導(dǎo)出建議樣式極簡(jiǎn)(僅表頭設(shè)一下)。
styleID, _ := f.NewStyle(&excelize.Style{Font: &excelize.Font{Bold: true}})
_ = f.SetCellStyle("Sheet1", "A1", "D1", styleID)
_ = f.SetColWidth("Sheet1", "A", "D", 18)

常見(jiàn)坑與對(duì)策清單

1. 導(dǎo)出極慢

關(guān)掉逐格設(shè)置樣式/公式;使用 SetRow 成批寫(xiě)

2. 內(nèi)存持續(xù)上漲

確認(rèn)使用 StreamWriter;減少中間切片累積;分頁(yè)尺寸控制在 1k~10k。

3.瀏覽器中斷下載

日志常見(jiàn) broken pipe,屬正常情況;上游應(yīng)放棄繼續(xù)寫(xiě)入并回收資源。

4.Excel 打開(kāi) CSV 亂碼

使用 ; charset=utf-8;Windows 場(chǎng)景可考慮 BOM(\xEF\xBB\xBF)或?qū)С?XLSX。

5.OFFSET 翻頁(yè)抖動(dòng)

大表使用 keyset(WHERE id > last_id ORDER BY id LIMIT ?)。

6.導(dǎo)出耗時(shí)過(guò)長(zhǎng)

優(yōu)化 SQL(只查必要列、加索引)、縮小時(shí)間/條件范圍、服務(wù)端先異步生成后提供下載(任務(wù)輪詢)。

7.并發(fā)順序問(wèn)題

多協(xié)程查詢 + 單 Writer 順序?qū)?;或?Sheet 并行,每個(gè) Sheet 一個(gè) Writer。

總結(jié)

首選 CSV:海量數(shù)據(jù)、追求極致性能與穩(wěn)定,CSV 基本無(wú)敵。

需要樣式就上 XLSX:務(wù)必使用 StreamWriter,樣式從簡(jiǎn),分頁(yè)拉取。

端到端流式:DB → channel → 單寫(xiě)者 → 文件/HTTP,配合 Context 實(shí)現(xiàn)可中斷與資源即時(shí)回收。

到此這篇關(guān)于純Go語(yǔ)言實(shí)現(xiàn)百萬(wàn)級(jí)數(shù)據(jù)高效導(dǎo)出Excel和CSV實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)Go百萬(wàn)級(jí)數(shù)據(jù)導(dǎo)出內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語(yǔ)言版本管理module以及go.sum詳解

    Go語(yǔ)言版本管理module以及go.sum詳解

    本文介紹了Go語(yǔ)言版本管理go module以及go.sum詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • 在Gin框架中正確刪除Cookie的操作指南

    在Gin框架中正確刪除Cookie的操作指南

    在現(xiàn)代Web開(kāi)發(fā)中,Cookie管理是構(gòu)建用戶友好應(yīng)用的關(guān)鍵環(huán)節(jié),Gin作為Go語(yǔ)言中最受歡迎的Web框架之一,提供了簡(jiǎn)潔而強(qiáng)大的Cookie處理機(jī)制,本文將深入探討如何在Gin框架中正確刪除Cookie,需要的朋友可以參考下
    2025-08-08
  • 一文詳解如何使用Go語(yǔ)言生成二維碼

    一文詳解如何使用Go語(yǔ)言生成二維碼

    使用Go語(yǔ)言編程時(shí),生成任意內(nèi)容的二維碼是非常方便的,下面這篇文章主要給大家介紹了關(guān)于如何使用Go語(yǔ)言生成二維碼的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • 如何使用Golang打包jar應(yīng)用

    如何使用Golang打包jar應(yīng)用

    go:embed 是 Go 1.16 引入的一個(gè)強(qiáng)大功能,它允許在編譯時(shí)將外部文件或目錄嵌入到 Go 程序中,下面介紹如何使用 go:embed 來(lái)嵌入JAR 文件,感興趣的朋友一起看看吧
    2025-04-04
  • golang獲取prometheus數(shù)據(jù)(prometheus/client_golang包)

    golang獲取prometheus數(shù)據(jù)(prometheus/client_golang包)

    本文主要介紹了使用Go語(yǔ)言的prometheus/client_golang包來(lái)獲取Prometheus監(jiān)控?cái)?shù)據(jù),具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-03-03
  • Go語(yǔ)言結(jié)合GORM實(shí)現(xiàn)數(shù)據(jù)庫(kù)事務(wù)處理的示例代碼

    Go語(yǔ)言結(jié)合GORM實(shí)現(xiàn)數(shù)據(jù)庫(kù)事務(wù)處理的示例代碼

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言結(jié)合GORM實(shí)現(xiàn)數(shù)據(jù)庫(kù)事務(wù)處理的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-08-08
  • Go語(yǔ)言fmt包的具體使用

    Go語(yǔ)言fmt包的具體使用

    Go語(yǔ)言的fmt包提供了豐富多樣的輸入輸出功能,無(wú)論是格式化輸出、文件寫(xiě)入,還是從各種輸入源讀取數(shù)據(jù),都能輕松應(yīng)對(duì),具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-07-07
  • Golang泛型的使用方法詳解

    Golang泛型的使用方法詳解

    這篇文章主要介紹了Golang中泛型的使用,Go和Python語(yǔ)言不同,處理不同數(shù)據(jù)類型非常嚴(yán)格。如Python可以定義函數(shù)帶兩個(gè)數(shù)值類型并返回較大的數(shù)值,但可以不嚴(yán)格指定參數(shù)類型為float或integer
    2022-12-12
  • Golang對(duì)struct字段重新排序優(yōu)化數(shù)據(jù)結(jié)構(gòu)性能實(shí)踐

    Golang對(duì)struct字段重新排序優(yōu)化數(shù)據(jù)結(jié)構(gòu)性能實(shí)踐

    這篇文章主要為大家介紹了Golang對(duì)struct字段重新排序優(yōu)化數(shù)據(jù)結(jié)構(gòu)性能實(shí)踐,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Golang中json和jsoniter的區(qū)別使用示例

    Golang中json和jsoniter的區(qū)別使用示例

    這篇文章主要介紹了Golang中json和jsoniter的區(qū)別使用示例,本文給大家分享兩種區(qū)別,結(jié)合示例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2023-12-12

最新評(píng)論

阿克| 松江区| 呼伦贝尔市| 休宁县| 陆良县| 日照市| 栾城县| 茂名市| 墨竹工卡县| 雅江县| 镇远县| 天津市| 图片| 宜宾市| 宝坻区| 凤庆县| 中西区| 玉龙| 久治县| 红桥区| 诸暨市| 富民县| 宜川县| 乌鲁木齐县| 青田县| 寿光市| 广丰县| 杭锦旗| 绿春县| 广汉市| 金湖县| 绵竹市| 诸暨市| 吴桥县| 建湖县| 宁远县| 柞水县| 左云县| 许昌市| 马山县| 高邮市|