Golang處理parquet文件實(shí)戰(zhàn)指南
前言
Parquet是Apache基金會(huì)支持的項(xiàng)目,是面向列存儲(chǔ)二進(jìn)制文件格式。支持不同類(lèi)型的壓縮方式,廣泛用于數(shù)據(jù)科學(xué)和大數(shù)據(jù)環(huán)境,如Hadoop生態(tài)。
本文主要介紹Go如何生成和處理parquet文件。
創(chuàng)建結(jié)構(gòu)體
首先創(chuàng)建struct,用于表示要處理的數(shù)據(jù):
type user struct { ID string `parquet:"name=id, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` FirstName string `parquet:"name=firstname, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` LastName string `parquet:"name=lastname, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` Email string `parquet:"name=email, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` Phone string `parquet:"name=phone, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` Blog string `parquet:"name=blog, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` Username string `parquet:"name=username, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` Score float64 `parquet:"name=score, type=DOUBLE"` CreatedAt time.Time //wont be saved in the parquet file }
這里要提醒的是tag,用于說(shuō)明struct中每個(gè)字段在生成parquet過(guò)程中如何被處理。
parquet-go包可以處理parquet數(shù)據(jù),更多的tag可以參考其官網(wǎng)。
生成parquet文件
下面現(xiàn)給出生成parquet文件的代碼,然后分別進(jìn)行說(shuō)明:
package main import ( "fmt" "log" "time" "github.com/bxcodec/faker/v3" "github.com/xitongsys/parquet-go-source/local" "github.com/xitongsys/parquet-go/parquet" "github.com/xitongsys/parquet-go/reader" "github.com/xitongsys/parquet-go/writer" ) type user struct { ID string `parquet:"name=id, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` FirstName string `parquet:"name=firstname, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` LastName string `parquet:"name=lastname, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` Email string `parquet:"name=email, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` Phone string `parquet:"name=phone, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` Blog string `parquet:"name=blog, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` Username string `parquet:"name=username, type=BYTE_ARRAY, encoding=PLAIN_DICTIONARY"` Score float64 `parquet:"name=score, type=DOUBLE"` CreatedAt time.Time //wont be saved in the parquet file } const recordNumber = 10000 func main() { var data []*user //create fake data for i := 0; i < recordNumber; i++ { u := &user{ ID: faker.UUIDDigit(), FirstName: faker.FirstName(), LastName: faker.LastName(), Email: faker.Email(), Phone: faker.Phonenumber(), Blog: faker.URL(), Username: faker.Username(), Score: float64(i), CreatedAt: time.Now(), } data = append(data, u) } err := generateParquet(data) if err != nil { log.Fatal(err) } } func generateParquet(data []*user) error { log.Println("generating parquet file") fw, err := local.NewLocalFileWriter("output.parquet") if err != nil { return err } //parameters: writer, type of struct, size pw, err := writer.NewParquetWriter(fw, new(user), int64(len(data))) if err != nil { return err } //compression type pw.CompressionType = parquet.CompressionCodec_GZIP defer fw.Close() for _, d := range data { if err = pw.Write(d); err != nil { return err } } if err = pw.WriteStop(); err != nil { return err } return nil }
定義結(jié)構(gòu)體上面已經(jīng)說(shuō)明,但需要提醒的是類(lèi)型與文檔保持一致:
Primitive Type | Go Type |
---|---|
BOOLEAN | bool |
INT32 | int32 |
INT64 | int64 |
INT96(deprecated) | string |
FLOAT | float32 |
DOUBLE | float64 |
BYTE_ARRAY | string |
FIXED_LEN_BYTE_ARRAY | string |
接著就是使用faker包生成模擬數(shù)據(jù)。然后調(diào)用err := generateParquet(data)
方法。該方法大概邏輯為:
- 首先準(zhǔn)備輸出文件,然后基于本地輸出文件構(gòu)造pw,用于寫(xiě)parquet數(shù)據(jù):
fw, err := local.NewLocalFileWriter("output.parquet") if err != nil { return err } //parameters: writer, type of struct, size pw, err := writer.NewParquetWriter(fw, new(user), int64(len(data))) if err != nil { return err } //compression type pw.CompressionType = parquet.CompressionCodec_GZIP defer fw.Close()
然后設(shè)置壓縮類(lèi)型,并通過(guò)defer操作確保關(guān)閉文件。下面開(kāi)始寫(xiě)數(shù)據(jù):
for _, d := range data { if err = pw.Write(d); err != nil { return err } } if err = pw.WriteStop(); err != nil { return err } return nil
循環(huán)寫(xiě)數(shù)據(jù),最后調(diào)用pw.WriteStop()
停止寫(xiě)。 成功寫(xiě)文件后,下面介紹如何讀取parquet文件。
讀取parquet文件
首先介紹如何一次性讀取文件,主要用于讀取較小的文件:
func readParquet() ([]*user, error) { fr, err := local.NewLocalFileReader("output.parquet") if err != nil { return nil, err } pr, err := reader.NewParquetReader(fr, new(user), recordNumber) if err != nil { return nil, err } u := make([]*user, recordNumber) if err = pr.Read(&u); err != nil { return nil, err } pr.ReadStop() fr.Close() return u, nil }
大概流程如下:首先定義本地文件,然后構(gòu)造pr用于讀取parquet文件:
fr, err := local.NewLocalFileReader("output.parquet") if err != nil { return nil, err } pr, err := reader.NewParquetReader(fr, new(user), recordNumber) if err != nil { return nil, err }
然后定義目標(biāo)內(nèi)容容器u,一次性讀取數(shù)據(jù):
u := make([]*user, recordNumber) if err = pr.Read(&u); err != nil { return nil, err } pr.ReadStop() fr.Close()
但一次性大量記錄加載至內(nèi)存可能有問(wèn)題。這是官方文檔提示:
If the parquet file is very big (even the size of parquet file is small, the uncompressed size may be very large), please don’t read all rows at one time, which may induce the OOM. You can read a small portion of the data at a time like a stream-oriented file.
大意是不要一次讀取文件至內(nèi)存,可能造成OOM。實(shí)際應(yīng)用中應(yīng)該分頁(yè)讀取,下面通過(guò)代碼進(jìn)行說(shuō)明:
func readPartialParquet(pageSize, page int) ([]*user, error) { fr, err := local.NewLocalFileReader("output.parquet") if err != nil { return nil, err } defer func() { _ = fr.Close() }() pr, err := reader.NewParquetReader(fr, new(user), int64(pageSize)) if err != nil { return nil, err } defer pr.ReadStop() //num := pr.GetNumRows() pr.SkipRows(int64(pageSize * page)) u := make([]*user, pageSize) if err = pr.Read(&u); err != nil { return nil, err } return u, nil }
與上面函數(shù)差異不大,首先函數(shù)包括兩個(gè)參數(shù),用于指定頁(yè)大小和頁(yè)數(shù),關(guān)鍵代碼是跳過(guò)一定記錄:
pr.SkipRows(int64(pageSize * page))
根據(jù)這個(gè)方法可以獲得總行數(shù),pr.GetNumRows()
,然后結(jié)合頁(yè)大小計(jì)算總頁(yè)數(shù),最后循環(huán)可以實(shí)現(xiàn)分頁(yè)查詢(xún)。
計(jì)算列平均值
既然使用了Parquet列存儲(chǔ)格式,下面演示下如何計(jì)算Score列的平均值。
func calcScoreAVG() (float64, error) { fr, err := local.NewLocalFileReader("output.parquet") if err != nil { return 0.0, err } pr, err := reader.NewParquetColumnReader(fr, recordNumber) if err != nil { return 0.0, err } num := int(pr.GetNumRows()) data, _, _, err := pr.ReadColumnByPath("parquet_go_root\u0001score", num) if err != nil { return 0.0, err } var result float64 for _, i := range data { result += i.(float64) } return (result / float64(num)), nil }
首先打開(kāi)文件,然后調(diào)用pr.GetNumRows()方法獲取總行數(shù)。然后基于路徑指定列,其中parquet_go_root
為根路徑,因?yàn)榍懊媸褂米止?jié)數(shù)組,這里分割符變?yōu)閈u0001,完整路徑為:parquet_go_root\u0001score
。
總結(jié)
到此這篇關(guān)于Golang處理parquet文件的文章就介紹到這了,更多相關(guān)Golang處理parquet文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
簡(jiǎn)單對(duì)比一下?C語(yǔ)言?與?Go語(yǔ)言
這篇文章主要介紹了簡(jiǎn)單對(duì)比一下?C語(yǔ)言?與?Go語(yǔ)言的相關(guān)資料,需要的朋友可以參考下2023-08-08Go底層channel實(shí)現(xiàn)原理及示例詳解
這篇文章主要介紹了Go底層channel實(shí)現(xiàn)原理及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08用go寫(xiě)的五子棋預(yù)測(cè)算法的實(shí)現(xiàn)
這篇文章主要介紹了用go寫(xiě)的五子棋預(yù)測(cè)算法的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12Go語(yǔ)言bufio庫(kù)的全面指南與實(shí)戰(zhàn)技巧詳解
這篇文章主要為大家全面介紹一下?bufio?庫(kù)的核心組件與功能,包括?Reader、Writer?和?Scanner?等并深入探討它們?cè)趯?shí)際編程中的運(yùn)用場(chǎng)景和技巧,感興趣的可以了解下2024-01-01Go結(jié)構(gòu)體從基礎(chǔ)到應(yīng)用深度探索
本文深入探討了結(jié)構(gòu)體的定義、類(lèi)型、字面量表示和使用方法,旨在為讀者呈現(xiàn)Go結(jié)構(gòu)體的全面視角,通過(guò)結(jié)構(gòu)體,開(kāi)發(fā)者可以實(shí)現(xiàn)更加模塊化、高效的代碼設(shè)計(jì),這篇文章旨在為您提供關(guān)于結(jié)構(gòu)體的深入理解,助您更好地利用Go語(yǔ)言的強(qiáng)大功能2023-10-10Golang回調(diào)函數(shù)與閉包和接口函數(shù)的定義及使用介紹
這篇文章主要介紹了Golang回調(diào)函數(shù)與閉包和接口函數(shù)的定義及使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-05-05教你一分鐘配置好Go語(yǔ)言開(kāi)發(fā)環(huán)境(多種操作系統(tǒng))
在這篇文章中,我們從頭到尾一步步指導(dǎo)你配置Golang開(kāi)發(fā)環(huán)境,并編寫(xiě)你的第一個(gè)"Hello,?World!"程序,我們?cè)敿?xì)解釋了在多種操作系統(tǒng)(包括Windows、Linux和macOS)下的安裝過(guò)程、環(huán)境變量設(shè)置以及如何驗(yàn)證安裝是否成功2023-09-09Golang defer延遲語(yǔ)句的實(shí)現(xiàn)
defer擁有注冊(cè)延遲調(diào)用的機(jī)制,本文主要介紹了Golang defer延遲語(yǔ)句的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07