這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
最近項目中有個小需求,需要將查詢結果匯出到excel。之間前java比較容易,使用POI很容易就能實現,查了下golang的文檔,發現golang下邊並沒有匯出excel的包,但是卻有一個encoding/csv的包,看了下發現可以匯出csv檔案,大家都知道csv檔案其實就是文字格式設定的excel檔案,可以直接通過excel開啟或是匯入excel。
看起來挺好的,問題如願解決,但是事實證明對已一個還不成熟的語言或是庫最好還是先測一下的好。興沖沖的卸了測試例子,成功匯出了一個text.csv檔案,一切看起來都挺好的,然而開啟之後就傻眼了:中文亂碼,這個問題其實比較好理解,golang只支援utf-8,而win中文版預設字元集是GB2312(gbk),這樣看來直接轉碼就行了唄。
由於之前吃了虧,這次我們先直接報檔案轉下碼試試:直接將之前置出的text.csv另存新檔ASCII格式,開啟後發現斷行符號分行符號丟了,全部變成一行了。這下就鬱悶了,先看下源碼吧:
// Writer writes a single CSV record to w along with any necessary quoting.// A record is a slice of strings with each string being one field.func (w *Writer) Write(record []string) (err error) { for n, field := range record { if n > 0 { if _, err = w.w.WriteRune(w.Comma); err != nil { return } } // If we don't have to have a quoted field then just // write out the field and continue to the next field. if !w.fieldNeedsQuotes(field) { if _, err = w.w.WriteString(field); err != nil { return } continue } if err = w.w.WriteByte('"'); err != nil { return } for _, r1 := range field { switch r1 { case '"': _, err = w.w.WriteString(`""`) case '\r': if !w.UseCRLF { err = w.w.WriteByte('\r') } case '\n': if w.UseCRLF { _, err = w.w.WriteString("\r\n") } else { err = w.w.WriteByte('\n') } default: _, err = w.w.WriteRune(r1) } if err != nil { return } } if err = w.w.WriteByte('"'); err != nil { return } } if w.UseCRLF { _, err = w.w.WriteString("\r\n") } else { err = w.w.WriteByte('\n') } return}
可以看到代碼十分簡單,每行就是按照csv的格式寫入檔案而已。需要注意的是writer裡邊有一個UserCRLF來指定是否適應斷行符號分行符號,預設為false,問題應該就出在這裡,但是將UserCRLF設定為true之後,問題依舊,看來是轉碼有問題。
既然代碼這麼簡單,那麼還不如直接自己實現,然後轉碼輸出,這裡使用iconv-go進行轉碼,實現如下:
package componentsimport ( "bytes" "errors" iconv "github.com/djimenez/iconv-go")/** * 匯出處理 */const ( OUT_ENCODING = "gbk" //輸出編碼)/** * 匯出csv格式檔案,輸出byte數組 * 輸出編碼通過OUT_ENCODING指定 */func ExportCsv(head []string, data [][]string) (out []byte, err error) { if len(head) == 0 { err = errors.New("ExportCsv Head is nil") return } columnCount := len(head) dataStr := bytes.NewBufferString("") //添加頭 for index, headElem := range head { separate := "," if index == columnCount-1 { separate = "\n" } dataStr.WriteString(headElem + separate) } //添加資料行 for _, dataArray := range data { if len(dataArray) != columnCount { //資料項目數小於列數 err = errors.New("ExportCsv data format is error.") } for index, dataElem := range dataArray { separate := "," if index == columnCount-1 { separate = "\n" } dataStr.WriteString(dataElem + separate) } } //處理編碼 out = make([]byte, len(dataStr.Bytes())) iconv.Convert(dataStr.Bytes(), out, "utf-8", OUT_ENCODING) return}
測試一下,匯出成功,而且沒有亂碼問題。
對於目前這個項目而言,匯出簡單格式的csv就能滿足,但是如果想匯出複雜的excel檔案就不行了。考慮了下大概想出了以下幾種方法:
- 使用cgo,用c來實現匯出,這是golang處理類似問題的一貫作風,然而就匯出excel而言並不太好,因為c匯出複雜格式的excel本身就挺麻煩的
- 調用其他語言實現的模組,至於怎麼調就無所謂,如果對於大批量的匯出,其實還挺好的,可以把產生excel這樣費時費力的操作給分離出去
- 按照excel檔案格式直接產生為對應二進位檔案,這個實現起來可能比較費勁,但是確實一勞永逸的,這裡附一個excel格式的連結,有興趣的可以實現下。