<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          go實現(xiàn)一個簡單的文件反序列化器

          共 4635字,需瀏覽 10分鐘

           ·

          2021-10-21 10:55

          1. ?需求

          現(xiàn)在有一個文件,文件中的第一行是 "name,address,phone,country,male,age" 表示這個文件的后續(xù)內(nèi)容類型,可以視為列名。之后的每一行都是這幾部分?jǐn)?shù)據(jù),使用","分割。例如,從第二行開始后續(xù)的每一行的內(nèi)容大致為:"crastom,hone,111111111,china,true,20"。

          如果想要提取這些內(nèi)容,是不是很簡單,只需要使用:

          strings.Split(line, ",")

          就可以獲得每一行中的各部分內(nèi)容。然后把每部分?jǐn)?shù)據(jù)賦值給一個結(jié)構(gòu)體,例如:

          type Person struct {  Name string  Address string  Phone string  Country string  Fale bool  Age int}

          這樣就完成了,但是如果之后需要解析更多的字段呢,或者需要解析的字段類型出現(xiàn)變化呢。

          因此,本文就用go實現(xiàn)一種簡單的Unmarshaler,它可以從文件中Unmarshal出所需要的數(shù)據(jù),并且不需要寫冗長的賦值語句;可以適用于不同的文件內(nèi)容。

          2. ?思路

          實現(xiàn)的思路也比較簡單:

          1. 使用第一行headLine,來初始化一個Unmarshaler,分析headLine中每個name對應(yīng)的位置。例如,headLine為"name,age,address,male",那么name對應(yīng)idx為0,age為1,依次類推。

          2. 實現(xiàn)Unmarshal函數(shù)時,傳入需要反序列化的一行數(shù)據(jù)line以及存放數(shù)據(jù)的結(jié)構(gòu)體ds。結(jié)構(gòu)體中通過字段的tag或者字段名獲取該字段的數(shù)據(jù)在一行中對應(yīng)的位置。例如,line為"crastom,20,home,true",那么crastom就對應(yīng)與headLine的name,以此類推。

          3. 在Unmarshaler中,找到數(shù)據(jù)的位置,從line中取出數(shù)據(jù),然后通過反射設(shè)置ds對應(yīng)字段的內(nèi)容即可。line中獲取到的內(nèi)容都是string,而ds的字段中可能存在多種類型:int、bool、string、float64等。針對不同的類型,需要設(shè)計成為可注冊的處理方式,這樣遇到對應(yīng)的類型直接取出對應(yīng)的parseFunc即可處理。

          3. ?golang實現(xiàn)

          3.1. ?Unmarshaler數(shù)據(jù)結(jié)構(gòu)

          Unmarshaler的數(shù)據(jù)結(jié)構(gòu)定義為fln,options是fln的相關(guān)配置;total是headLine中的列數(shù);headToIdx是一個map,將headLine中列名與它的位置idx對應(yīng)起來。

          type fln struct {  options   *Options  total     int  headToIdx map[string]int}

          3.2. ?初始化Unmarshaler

          func NewFln(oos ...Option) (Unmarshaler, error) {  options := Options{}  for _, o := range oos {    o(&options)}  err := checkOptions(&options)  if err != nil {    return nil, err}
          f := &fln{ options: &options, headToIdx: make(map[string]int),} err = f.parseHeadLine() if err != nil { return nil, err} return f, nil}

          這個函數(shù)用來新建一個Unmarshaler,傳入的參數(shù)oos是用來配置Options的,Options和Option的定義如下:

          // Options 解析參數(shù)type Options struct {  // 文件的第一行,  // 例如:"name,age,country"這些聲明字段  HeadLine string  // 文件中每一行各部分內(nèi)容  // 的分割符,默認(rèn)使用"\t"  Spliter string}
          // Option 用來設(shè)置optionstype Option func(*Options)
          // WithHeadLineOptions中添加headLinefunc WithHeadLine(line string) Option { return func(o *Options) { o.HeadLine = line }}
          // WithSpliter 設(shè)置options中的spliterfunc WithSpliter(spliter string) Option { return func(o *Options) { o.Spliter = spliter }}

          Options就是fln的相關(guān)參數(shù)配置,而Option就是用來處理Options的函數(shù),目前有WithHeadLine以及WithSpliter這兩個函數(shù)。

          而上面的parseHeadLine實現(xiàn)很簡單,就是把headLine通過split分割成string數(shù)據(jù),然后映射到headToIdx中。

          3.3. ?Unmarshal實現(xiàn)

          方法簽名如下:

          func (f *fln) Unmarshal(data []byte, ptr interface{}) error

          data即每一行需要反序列化的數(shù)據(jù),ptr則是一個結(jié)構(gòu)體指針,用來存放數(shù)據(jù)。

          接下來是簡略實現(xiàn)思路:

          1. 將data轉(zhuǎn)化為string然后分割成string數(shù)組:datas

          2. 對ptr指向的結(jié)構(gòu)體中字段遍歷,跳過無法設(shè)置值的字段。

          3. 通過字段名或tag獲取該字段對應(yīng)的數(shù)據(jù)在datas中的位置idx,然后設(shè)置該字段的值為datas[idx]

          for i := 0; i < elev.NumField(); i++ {    fieldt := elet.Field(i)    fieldv := elev.Field(i)    if !fieldv.CanSet() {      continue    }    tagName := fieldt.Tag.Get(TAG_NAME)    fieldName := fieldt.Name    idx := f.getIdxFromName(tagName, fieldName)    if idx == -1 {      continue    }    content := data[idx]    setValue(fieldv, fieldt, content)}

          在上面的setValue函數(shù)中,首先將content轉(zhuǎn)換為fieldt的類型,然后通過fieldv.SetXxx進(jìn)行設(shè)置。

          func setValue(fieldv reflect.Value, fieldt reflect.StructField, value string) error {  var err error  defer func() {    if err != nil {      err = fmt.Errorf("error from setValue: %+v", err)    }}()  pf, ok := parseValueFuncs[fieldt.Type.Kind()]  if !ok {    return fmt.Errorf("not suppoted type: %+v", fieldt.Type)}  err = pf(fieldv, value)  return err}

          setValue函數(shù)中,在parseValueFuncs找到對應(yīng)的轉(zhuǎn)換函數(shù)parseFunc:pf,然后用執(zhí)行pf。

          那parseValeFuncs中的parseFunc是如何設(shè)置的呢?

          type parseFunc func(reflect.Value, string) error
          var parseValueFuncs map[reflect.Kind]parseFunc
          func RegisteParseFunc(pf parseFunc, ks ...reflect.Kind) { for _, k := range ks { parseValueFuncs[k] = pf }}

          在init函數(shù)中,已經(jīng)實現(xiàn)了int、float64、string、bool等類型的parseFunc,對于其他的類型,可以自己實現(xiàn),然后注冊到fln中。

          3.4. ?測試

          附加一個簡單的例子,幫助理解。

          type DS struct {  Name    string `fln:"myname"`  MyAge   int    `fln:"age"`  Address string `fln:"address"`  Male    bool   `fln:"mymale"`}
          func TestNewFLN(t *testing.T) { head := "name,age,address,male" data := "crastom,10,home,true" want := DS{ Name: "crastom", MyAge: 10, Address: "home", Male: true, } convey.Convey("test_new_fln", t, func() { f, err := NewFln( WithHeadLine(head), WithSpliter(","), ) convey.So(f, convey.ShouldNotBeNil) convey.So(err, convey.ShouldBeNil) ds := DS{} err = f.Unmarshal([]byte(data), &ds) convey.So(err, convey.ShouldBeNil) convey.So(reflect.DeepEqual(want, ds), convey.ShouldBeTrue) t.Logf("%+v", ds) })}

          4. ?總結(jié)

          這個反序列化的工具比較簡單,主要內(nèi)容就是使用反射設(shè)置字段數(shù)據(jù)。但是fln的配置Options以及parseFunc的注冊還是值得一看的,方便后續(xù)新功能的添加。相關(guān)代碼見github:

          https://github.com/crazyStrome/file-line-notion



          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號 「polarisxu」,回復(fù)?ebook?獲??;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 28
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩二级片视频 | 国产偷窥熟女精品视频大全 | 婷婷五月天丁香激情 | 香蕉久久视频 | 操妞视频在线观看免费网站 |