iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > GO >带指针的结构体序列化
  • 756
分享到

带指针的结构体序列化

2024-04-04 23:04:14 756人浏览 独家记忆
摘要

哈喽!大家好,很高兴又见面了,我是编程网的一名作者,今天由我给大家带来一篇《带指针的结构体序列化》,本文主要会讲到等等知识点,希望大家一起学习进步,也欢迎大家关注、点赞、收藏、转发! 下面就一起来看

哈喽!大家好,很高兴又见面了,我是编程网的一名作者,今天由我给大家带来一篇《带指针的结构体序列化》,本文主要会讲到等等知识点,希望大家一起学习进步,也欢迎大家关注、点赞、收藏、转发! 下面就一起来看看吧!

问题内容

具有如下结构层次结构:

type domainstore struct {
    domains []*domain
    users []*user
}

type domain struct {
    name    string
    records []*record
    owner   *user
}

type user struct {
    name      string
    email     string
    domains []*domain
}

type record struct {
    name      string
    host      string
}

单个 domainstore 具有域和用户列表,并在域和用户之间具有指针。

我正在寻找一种对文件进行序列化/反序列化的方法。我一直在尝试使用 Gob,但指针(根据设计)序列化不正确(其扁平化)。

考虑给每个对象一个唯一的 id 并创建一个函数来序列化/反序列化每种类型,但这似乎需要大量工作/样板。有什么策略建议吗?

我想将整个 domainstore 保留在内存中,并根据用户请求序列化为文件。

主要问题:如何序列化/反序列化并保持指针指向同一对象而不是同一对象的不同副本

gob 和 JSON 似乎都“只是”复制对象的值并进行反序列化,最终得到对象的多个独立副本。

使用 gob ang json 会发生以下情况:

之前,a & c 都指向 b:

a -> b <- c

使用 json/gob 反序列化后:

a -> b1 , c -> b2

a 和 c 指向不同的对象,具有相同的值。但是,如果我更改 b1,b2 中不会更改。

--- 更新 ---

编组时我可以获得对象的内存位置并将其用作 id:

func (u *user) marshaljson() ([]byte, error) {
    return json.marshal(&jsonuser{
        id:       fmt.sprintf("%p", u),
        name:     u.name,
        email:    u.email,
    })
}

当编组域时,我可以替换

func (d *domain) marshaljson() ([]byte, error) {
    return json.marshal(&struct {
        id       string `json:"id"`
        name     string `json:"name"`
        user     string `json:"user"`
    }{
        id:       fmt.sprintf("%p", d),
        name:     d.name,
        user:     fmt.sprintf("%p", d.user),
    })
}

现在我只是需要能够解组这个,这给我带来了 unmarshaljson 需要访问 id 及其各自对象的映射的问题。

func (u *User) UnmarshalJSON(data []byte) error {
  // need acces to a map shared by all UnmarshalJSON functions
}


解决方案


可以使用以下方法来完成:

  1. 所有对象都放置在 state 对象的地图中。
  2. 当 state 对象中的对象被编组时,所有使用指针引用的对象都将替换为该对象的内存位置。
  3. 当使用先前读取的对象的全局列表恢复未编组的指针时。

代码将会运行,只是为了说明方法,我是 go 新手,所以请耐心等待。

package main

import (
    "encoding/json"
    "errors"
    "fmt"
    "log"
    "strings"
)

type user struct {
    name  string
    email string
}
type jsonuser struct {
    id    string `json:"id"`
    name  string `json:"name"`
    email string `json:"email"`
}

func (u *user) print(level int) {
    ident := strings.repeat("-", level)
    log.println(ident, "username:", u.name, u.email)
}
func (u *user) id() string {
    return fmt.sprintf("%p", u)
}
func (u *user) marshaljson() ([]byte, error) {
    return json.marshal(&jsonuser{
        id:    u.id(),
        name:  u.name,
        email: u.email,
    })
}
func (u *user) unmarshaljson(data []byte) error {
    aux := &jsonuser{}
    if err := json.unmarshal(data, &aux); err != nil {
        return err
    }
    u.name = aux.name
    u.email = aux.email
    load_helper[aux.id] = u
    log.println("added user with id ", aux.id, u.name)
    return nil
}

type record struct {
    type     string // mx / a / cname / txt / redir / svr
    name     string // @ / www
    host     string // ip / address
    priority int    // used for mx
    port     int    // used for svr
}
type jsonrecord struct {
    id       string
    type     string
    name     string
    host     string
    priority int
    port     int
}

func (r *record) print(level int) {
    ident := strings.repeat("-", level)
    log.println(ident, "", r.type, r.name, r.host)
}
func (r *record) id() string {
    return fmt.sprintf("%p", r)
}
func (r *record) marshaljson() ([]byte, error) {
    return json.marshal(&jsonrecord{
        id:       r.id(),
        name:     r.name,
        type:     r.type,
        host:     r.host,
        priority: r.priority,
        port:     r.port,
    })
}
func (r *record) unmarshaljson(data []byte) error {
    aux := &jsonrecord{}
    if err := json.unmarshal(data, &aux); err != nil {
        return err
    }
    r.name = aux.name
    r.type = aux.type
    r.host = aux.host
    r.priority = aux.priority
    r.port = aux.port
    load_helper[aux.id] = r
    log.println("added record with id ", aux.id, r.name)
    return nil
}

type domain struct {
    name    string
    user    *user     // user id
    records []*record // record id's
}
type jsondomain struct {
    id      string   `json:"id"`
    name    string   `json:"name"`
    user    string   `json:"user"`
    records []string `json:"records"`
}

func (d *domain) print(level int) {
    ident := strings.repeat("-", level)
    log.println(ident, "domain:", d.name)
    d.user.print(level + 1)
    log.println(ident, " records:")
    for _, r := range d.records {
        r.print(level + 2)
    }
}
func (d *domain) id() string {
    return fmt.sprintf("%p", d)
}
func (d *domain) marshaljson() ([]byte, error) {
    var record_ids []string
    for _, r := range d.records {
        record_ids = append(record_ids, r.id())
    }
    return json.marshal(jsondomain{
        id:      d.id(),
        name:    d.name,
        user:    d.user.id(),
        records: record_ids,
    })
}
func (d *domain) unmarshaljson(data []byte) error {
    log.println("unmarshaljson domain")
    aux := &jsondomain{}
    if err := json.unmarshal(data, &aux); err != nil {
        return err
    }
    d.name = aux.name
    d.user = load_helper[aux.user].(*user) // restore pointer to domains user
    for _, record_id := range aux.records {
        d.records = append(d.records, load_helper[record_id].(*record))
    }
    return nil
}

type state struct {
    users   map[string]*user
    records map[string]*record
    domains map[string]*domain
}

func newstate() *state {
    s := &state{}
    s.users = make(map[string]*user)
    s.domains = make(map[string]*domain)
    s.records = make(map[string]*record)
    return s
}
func (s *state) print() {
    log.println("state:")
    log.println("users:")
    for _, u := range s.users {
        u.print(1)
    }
    log.println("domains:")
    for _, d := range s.domains {
        d.print(1)
    }
}
func (s *state) newuser(name string, email string) *user {
    u := &user{name: name, email: email}
    id := fmt.sprintf("%p", u)
    s.users[id] = u
    return u
}
func (s *state) newdomain(user *user, name string) *domain {
    d := &domain{name: name, user: user}
    s.domains[d.id()] = d
    return d
}
func (s *state) newmxrecord(d *domain, rtype string, name string, host string, priority int) *record {
    r := &record{type: rtype, name: name, host: host, priority: priority}
    d.records = append(d.records, r)
    s.records[r.id()] = r
    return r
}
func (s *state) finddomain(name string) (*domain, error) {
    for _, v := range s.domains {
        if v.name == name {
            return v, nil
        }
    }
    return nil, errors.new("not found")
}
func save(s *state) (string, error) {
    b, err := json.marshalindent(s, "", "    ")
    if err == nil {
        return string(b), nil
    } else {
        log.println(err)
        return "", err
    }
}

var load_helper map[string]interface{}

func load(s *state, blob string) {
    load_helper = make(map[string]interface{})
    if err := json.unmarshal([]byte(blob), s); err != nil {
        log.println(err)
    } else {
        log.println("ok")
    }
}

func test_state() {

    s := newstate()
    u := s.newuser("ownername", "[email protected]")
    d := s.newdomain(u, "somedomain.com")
    s.newmxrecord(d, "mx", "@", "192.168.1.1", 10)
    s.newmxrecord(d, "a", "www", "192.168.1.1", 0)

    s.print()

    x, _ := save(s) // saved to json string

    log.println("state saved, the json string is:")
    log.println(x)

    s2 := newstate() // create a new empty state
    load(s2, x)
    s2.print()

    d, err := s2.finddomain("somedomain.com")
    if err == nil {
        d.user.name = "changed"
    } else {
        log.println("error:", err)
    }
    s2.print()
}

func main() {
    test_state()
}

这是相当多的代码,并且对象和序列化之间存在太多耦合。另外,全局变量 load_helper 也很糟糕。改进的想法将不胜感激。

另一种方法是使用反射来制定更通用的解决方案。以下是使用此方法的示例:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "strings"
    "reflect"
)

func pprint(x interface{}) {
    b, err := json.marshalindent(x, "", "  ")
    if err != nil {
        fmt.println("error:", err)
    }
    fmt.println(string(b))  
}


var typereGIStry = make(map[string]reflect.type)

// register a type to make it possible for the save/load functions
// to serialize it.
func register(v interface{}) {
    t := reflect.typeof(v)
    n := t.name()
    fmt.println("register type",n)
    typeregistry[n] = reflect.typeof(v)
}

// make an instance of a type from the string name of the type.
func makeinstance(name string) reflect.value {
    v := reflect.new(typeregistry[name]).elem()
    return v
}

// translate a string type name tpo a real type.
func gettypefromstring(name string) reflect.type {
    return typeregistry[name]
}


// serializeable interface must be supported by all objects passed to the load / save functions.
type serializeable interface {
    id() string
}

// genericsave saves the object d
func genericsave(d interface{}) (string, error) {
    r := make(map[string]interface{})
    v := reflect.valueof(d)
    t := reflect.typeof(d)
    if t.kind()==reflect.ptr {
        t=t.elem()
        v=v.elem()
    }
    r["_type"]=t.name()
    r["_id"]=fmt.sprintf("%p", d)
    for i := 0; i < t.numfield(); i++ {
        f := t.field(i)
        name := f.name
        vf := v.fieldbyname(name)
//      fmt.println("field", i+1, "name is", name, "type is", f.type.name(), "and kind is", f.type.kind())      
//      fmt.println("v:", vf)
        if f.tag != "" {
            store:=strings.split(f.tag.get("store"),",")
            switch store[1] {
            case "v":
                switch t.field(i).type.name() {
                case "string":
                    r[store[0]]=vf.string()
                case "int":
                    r[store[0]]=vf.int()
                }
            case "p":
                vals:=vf.methodbyname("id").call([]reflect.value{})
                r[store[0]]=vals[0].string()
            case "lp":
                tr:=[]string{}
                for j := 0; j < vf.len(); j++ {
                    vals:=vf.index(j).methodbyname("id").call([]reflect.value{})
                    tr=append(tr,vals[0].string())
                }
                r[store[0]]=tr
            }
        }
    }   
    m,_:=json.marshal(r)
    return string(m),nil
}

// save saves the list of objects.
func save(objects []serializeable) []byte {
    lst:=[]string{}
    for _,o := range(objects) {
        os,_:= genericsave(o) // o.save()
        lst=append(lst,os)
    }
    m,_:=json.marshal(lst)
    return m
}


func tostructptr(obj interface{}) interface{} {
    vp := reflect.new(reflect.typeof(obj))
    vp.elem().set(reflect.valueof(obj))
    return vp.interface()
}

// load creates a list of serializeable objects from json blob
func load(blob []byte) []serializeable {
    objects := []serializeable{}
    loadhelper := make(map[string]interface{})
    var olist []interface{}
    if err := json.unmarshal(blob, &olist); err != nil {
        log.println(err)
    } else {
        for _,o := range(olist) {

            var omap map[string]interface{}
            json.unmarshal([]byte(o.(string)), &omap)

            t:= gettypefromstring(omap["_type"].(string))
            obj := reflect.new(t).elem() 

            for i := 0; i < t.numfield(); i++ {
//              n:=t.field(i).name
//              fmt.println(i,n,t.field(i).type.name())

                if t.field(i).tag != "" {
                    store:=strings.split(t.field(i).tag.get("store"),",")
//                  fmt.println(store)
                    switch store[1] {
                    case "v":
                        switch t.field(i).type.name() {
                        case "string":
                            obj.fieldbyindex([]int{i}).setstring(omap[store[0]].(string))
                        case "int":
                            obj.fieldbyindex([]int{i}).setint(int64(omap[store[0]].(float64)))
                        }
                    case "p":
                        nobj:=loadhelper[omap[store[0]].(string)]
                        obj.fieldbyindex([]int{i}).set(reflect.valueof(nobj.(*user)))
                    case "lp":
                        ptritemtype:=t.field(i).type.elem()
                        slice := reflect.zero(reflect.sliceof(  ptritemtype   ))//.interface()
                        for _, pid := range(omap[store[0]].([]interface{})) {
                            nobj:=loadhelper[pid.(string)]
                            slice=reflect.append(slice,  reflect.valueof(nobj)  )
                        }
                        obj.fieldbyindex([]int{i}).set(slice)                       
                    }
                }
            }
            oi:=tostructptr(obj.interface())
            oip:=oi.(serializeable)
            objects=append(objects,oip)
            loadhelper[omap["_id"].(string)]=oip
        }
    }
    return objects

}





type user struct {
    name  string `store:"name,v"`
    email string `store:"email,v"`
}
func (u *user) id() string {
    return fmt.sprintf("%p", u)
}
func (u *user) save() (string, error) {
    return genericsave(u)
}
func (u *user) print() {
    fmt.println("user:",u.name)
}


type record struct {
    type     string `store:"type,v"`// mx / a / cname / txt / redir / svr
    name     string `store:"name,v"`// @ / www
    host     string `store:"host,v"`// ip / address
    priority int    `store:"priority,v"`// used for mx
    port     int    `store:"port,v"`// used for svr
}
func (r *record) id() string {
    return fmt.sprintf("%p", r)
}
func (r *record) save() (string, error) {
    return genericsave(r)
}
func (r *record) print() {
    fmt.println("record:",r.type,r.name,r.host)
}


type domain struct {
    name    string    `store:"name,v"`
    user    *user     `store:"user,p"`    // user id
    records []*record `store:"record,lp"` // record id's
}
func (d *domain) id() string {
    return fmt.sprintf("%p", d)
}
func (d *domain) save() (string, error) {
    return genericsave(d)
}
func (d *domain) print() {
    fmt.println("domain:",d.name)
    d.user.print()
    fmt.println("records:")
    for _, r := range d.records {
        r.print()
    }
}


type dbm struct {
    domains []*domain
    users []*user
    records []*record
}
func (dbm *dbm) aDDDomain(d *domain) {
    dbm.domains=append(dbm.domains,d)
}
func (dbm *dbm) adduser(u *user) {
    dbm.users=append(dbm.users,u)
}
func (dbm *dbm) addrecord(r *record) {
    dbm.records=append(dbm.records,r)
}
func (dbm *dbm) getobjects() []serializeable {
    objects:=[]serializeable{}
    for _,r := range(dbm.records) {
        objects=append(objects, r)
    }
    for _,u := range(dbm.users) {
        objects=append(objects, u)
    }
    for _,d := range(dbm.domains) {
        objects=append(objects, d)
    }
    return objects
}
func (dbm *dbm) setobjects(objects []serializeable) {
    for _,o := range(objects) {
        switch o.(type) {
        case *record:
            fmt.println("record")
            dbm.addrecord(o.(*record))
        case *user:
            fmt.println("record")
            dbm.adduser(o.(*user))
        case *domain:
            fmt.println("record")
            dbm.adddomain(o.(*domain))
        }
    }
}


func teststate() {

    register(user{})
    register(domain{})
    register(record{})

    dbm:=dbm{}

    u := &user{name: "martin", email: "[email protected]"}
    dbm.adduser(u)

    r1 := &record{name: "@", type: "mx", host: "mail.ishost.dk"}
    r2 := &record{name: "@", type: "mx", host: "mail.infoserv.dk"}
    dbm.addrecord(r1)
    dbm.addrecord(r2)

    d := &domain{user:u, name: "martin", records: []*record{r1, r2}}
    dbm.adddomain(d)

    x:=save(dbm.getobjects())

    fmt.println("== saved objects")
//  fmt.println(string(x))

    fmt.println("== loading")

    dbm2:=dbm{}
    dbm2.setobjects(load(x))


    u2:=dbm2.users[0]
    u2.print()
    u2.name="kurt"
    u2.print()

    d2:=dbm2.domains[0]
    d2.print()
    d2.user.name="zig"
    u2.print()

}

func main() {
    teststate()
}

使用encoding/json

致元帅:

// marshal is a function that marshals the object into an
// io.reader.
// by default, it uses the json marshaller.
var marshal = func(v interface{}) (io.reader, error) {
  b, err := json.marshalindent(v, "", "\t")
  if err != nil {
    return nil, err
  }
  return bytes.newreader(b), nil
}

解组:

// Unmarshal is a function that unmarshals the data from the
// reader into the specified value.
// By default, it uses the JSON unmarshaller.
var Unmarshal = func(r io.Reader, v interface{}) error {
  return json.NewDecoder(r).Decode(v)
}

不确定还有更多内容,

您可以做的另一件事是将所有这些存储为 json 格式的字符串

终于介绍完啦!小伙伴们,这篇关于《带指针的结构体序列化》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~编程网公众号也会发布golang相关知识,快来关注吧!

您可能感兴趣的文档:

--结束END--

本文标题: 带指针的结构体序列化

本文链接: https://www.lsjlt.com/news/596096.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 带指针的结构体序列化
    哈喽!大家好,很高兴又见面了,我是编程网的一名作者,今天由我给大家带来一篇《带指针的结构体序列化》,本文主要会讲到等等知识点,希望大家一起学习进步,也欢迎大家关注、点赞、收藏、转发! 下面就一起来看...
    99+
    2024-04-04
  • C#调用带结构体指针Dll的方法
    在C#中调用C(C++)类的DLL的时候,有时候C的接口函数包含很多参数,而且有的时候这些参数有可能是个结构体,而且有可能是结构体指针,那么在C#到底该如何安全的调用这样的DLL接口...
    99+
    2024-04-02
  • Go 结构体序列化的实现
    目录更改JSON对象中的键在JSON对象中隐藏结构体字段附加内容结构体标签string指令本文,我们将回到之前写的showMovieHandler方法,并更新它以返回一个JSON响应...
    99+
    2024-04-02
  • C#结构体指针的用法
    本篇内容介绍了“C#结构体指针的用法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!C#语言有很多值得学习的地方,这里我们主要介绍C#解析结构...
    99+
    2023-06-17
  • C语言 - 结构体、结构体数组、结构体指针和结构体嵌套
    结构体的意义 问题:学籍管理需要每个学生的下列数据:学号、姓名、性别、年龄、分数,请用 C 语言程序存储并处理一组学生的学籍。 单个学生学籍的数据结构: 学号(num): int 型姓名(name) :char [ ] 型性别(sex):c...
    99+
    2023-08-30
    c语言 开发语言
  • golang结构体序列化怎么实现
    在Go中,可以使用encoding/json包来实现结构体的序列化和反序列化。 首先,需要导入encoding/json包: imp...
    99+
    2023-10-22
    golang
  • C语言结构体指针的具体使用
    目录什么是结构体指针?如何访问结构体成员?如何传递结构体指针作为参数?结构体指针数组在 C语言中,结构体指针是一种非常有用的数据类型,它可以让我们更方便地操作结构体。结构体指针可以指...
    99+
    2023-05-20
    C语言结构体指针
  • 将结构体初始化为指针的目的是什么?
    Golang小白一枚,正在不断学习积累知识,现将学习到的知识记录一下,也是将我的所得分享给大家!而今天这篇文章《将结构体初始化为指针的目的是什么?》带大家来了解一下##content_title##...
    99+
    2024-04-04
  • c语言结构体指针数组怎么初始化
    在C语言中,结构体指针数组的初始化可以通过以下几种方式进行: 逐个初始化:通过逐个为每个元素赋值。例如: struct Pers...
    99+
    2023-10-27
    c语言
  • Go结构体序列化的实现是怎样的
    Go结构体序列化的实现是怎样的,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。我们将回到之前写的showMovieHandler方法,并更新它以返回一个JSON响...
    99+
    2023-06-29
  • C#中怎么定义结构体指针
    本篇文章为大家展示了C#中怎么定义结构体指针,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。C#结构体指针之C#结构体的定义:[StructLayout(LayoutKind.Sequential)]...
    99+
    2023-06-18
  • 如何访问结构体的指针切片字段内的嵌入结构体
    php小编草莓为您介绍如何访问结构体的指针切片字段内的嵌入结构体。在Go语言中,我们可以使用指针切片来存储结构体数据,并且在结构体中嵌入其他结构体作为字段。然而,访问嵌入结构体字段需要...
    99+
    2024-02-09
    go语言
  • 解析C/C++指针、函数、结构体、共用体
    目录指针变量与地址指针与指针变量占内存空间指针运算指针 变量与地址 变量给谁用的变量是对某一块空间的抽象命名。变量名就是你抽象出来的某块空间的别名。指针就是地址。指向某个地址。 指针...
    99+
    2024-04-02
  • go语言结构体指针操作的方法
    本篇内容介绍了“go语言结构体指针操作的方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!指针指针是代表某个内存地址的值。内存地址储存另一个...
    99+
    2023-06-30
  • C语言结构体指针的示例分析
    这篇文章给大家分享的是有关C语言结构体指针的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。写结构体指针前,先说一下 . 号和 -> 的区别记得当初刚学C语言的...
    99+
    2023-06-20
  • C语言结构体指针引用详解
    目录指向结构体变量的指针指向结构体数组的指针结构体指针,可细分为指向结构体变量的指针和指向结构体数组的指针。 指向结构体变量的指针 前面我们通过“结构体变量名.成员名”的方式引用结构...
    99+
    2024-04-02
  • C语言结构体指针怎么引用
    这篇文章主要介绍“C语言结构体指针怎么引用”,在日常操作中,相信很多人在C语言结构体指针怎么引用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C语言结构体指针怎么引用”的疑惑有所帮助!接下来,请跟着小编一起来...
    99+
    2023-06-25
  • C语言结构体指针案例解析
    写结构体指针前,先说一下 . 号和 -> 的区别 记得当初刚学C语言的时候,搞不清结构体的 . 号和 -> ,经常混淆二...
    99+
    2024-04-02
  • Golang怎么使用gob实现结构体的序列化
    本文小编为大家详细介绍“Golang怎么使用gob实现结构体的序列化”,内容详细,步骤清晰,细节处理妥当,希望这篇“Golang怎么使用gob实现结构体的序列化”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。Gol...
    99+
    2023-07-05
  • c语言结构体指针如何使用
    在C语言中,我们可以使用指针来操作结构体变量。首先,我们需要定义一个结构体类型,然后声明一个结构体变量。接下来,我们可以使用指针来操...
    99+
    2023-10-28
    c语言
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作