go同名字段复制工具函数(类似BeanUtils.copy)介绍和性能测试

介绍说明:

本文是在介绍一个go的不同类型结构体的复制工具函数,能减少重复劳动,提高开发效率,让代码可读性强,减少出错几率

本篇包含背景的介绍,工具函数源码的展示,性能测试等部分

问题背景:

故事的起因是看到了业务代码里有些字段比较多的部分,比如获取订单详情大概涉及到大几十个字段的复制,会有几十行甚至上百行的x.a = y.a这样的代码

 

即问题的根源是dto和vo是不同类型的结构题,但是又有很多同类型同名的公共字段需要复制(因结构体类型不同,无法直接copy出一份,只能挨个字段手动赋值)

解决方案:

由此想到了java里这种问题可以用beanUtils.copy方法解决,可以自动复制同名同类型字段的值

beanUtil是利用反射实现的,既然go也有反射,那么理论上也可以实现,就在网上找了下,果然有人写过了,很短:

代码:

func SimpleCopyProperties(dst, src interface{}) (err error) {
	// 防止意外panic
	defer func() {
		if e := recover(); e != nil {
			err = errors.New(fmt.Sprintf("%v", e))
		}
	}()

	dstType, dstValue := reflect.TypeOf(dst), reflect.ValueOf(dst)
	srcType, srcValue := reflect.TypeOf(src), reflect.ValueOf(src)

	// dst必须结构体指针类型
	if dstType.Kind() != reflect.Ptr || dstType.Elem().Kind() != reflect.Struct {
		return errors.New("dst type should be a struct pointer")
	}

	// src必须为结构体或者结构体指针,.Elem()类似于*ptr的操作返回指针指向的地址反射类型
	if srcType.Kind() == reflect.Ptr {
		srcType, srcValue = srcType.Elem(), srcValue.Elem()
	}
	if srcType.Kind() != reflect.Struct {
		return errors.New("src type should be a struct or a struct pointer")
	}

	// 取具体内容
	dstType, dstValue = dstType.Elem(), dstValue.Elem()

	// 属性个数
	propertyNums := dstType.NumField()

	for i := 0; i < propertyNums; i++ {
		// 属性
		property := dstType.Field(i)
		// 待填充属性值
		propertyValue := srcValue.FieldByName(property.Name)

		// 无效,说明src没有这个属性 || 属性同名但类型不同
		if !propertyValue.IsValid() || property.Type != propertyValue.Type() {
			continue
		}
 
		if dstValue.Field(i).CanSet() {
			dstValue.Field(i).Set(propertyValue)
		}
	}

	return nil
}

注意事项:

试了一下是可以的,但是有些问题需要注意:

参数传递时,第二个参数使用指针还是实例请自行斟酌,第一个参数必须是指针,涉及的字段必须是对外的,也就是大写开头,因为反射无法获取非对外字段

需要注意的是,该拷贝方法为浅拷贝,换句话说,如果说对象内嵌套有其他的引用类型如Slice,Map等,用此方法完成拷贝后,源对象中的引用类型属性内容发生了改变,该对象对应的属性中内容也会改变。

除此之外,关于go反射的性能问题,做了个简单的测试,测试代码放在demo项目里了

性能测试:

众所周知大部分语言的反射都有些性能问题,所以想测试一下性能怎么样,会不会使用后耗时明显变长

测试方法:

反射工具执行一次自动复制和直接字段赋值(即上边问题背景的方式赋值) 时间花费循环一万次取平均值

测试结果:

对于1个int的结构体两种操作的时间比是6倍:

反射平均时间256ns, 直接set平均时间41ns, 反射时间是直接set的6倍

7个int的结构提是10倍:

反射平均时间434ns, 直接set平均时间43ns, 反射时间是直接set的10倍

7个int的结构体是33倍

反射平均时间1.452µs, 直接set平均时间43ns, 反射时间是直接set的33倍

7个string的结构体是35倍(为了看一下不同的基本类型之间知否有差别)

反射平均时间1.468µs, 直接set平均时间40ns, 反射时间是直接set的35倍

16个基本类型:

反射平均时间3.479µs, 直接set平均时间42ns, 反射时间是直接set的82倍

28个基本类型字段:

反射平均时间7.353µs, 直接set平均时间56ns, 反射时间是直接set的129倍

33个基本类型字段测试结果:

反射平均时间9.071µs, 直接set平均时间56ns, 反射时间是直接set的160倍

 

go同名字段复制工具函数(类似BeanUtils.copy)介绍和性能测试_第1张图片

结果分析

  • 直接取值设置字段值几乎不会随字段的增多而增多,在本机环境下时长约为42ns
  • 通过反射自动设置同名字段会随字段增多而线性增长

结论:

对于几十个字段的结构体之间复制字段,耗时大概在几微秒到几十微秒(即千分之一毫秒到百分之一毫秒)的样子,复制字段的操作经常出现在model、dto、vo之间的复制,通常一个请求中的复制不会超过3次,一个结构体几十个字段已经很多了,虽然是线性增长但是总耗时其实是可以忽略的,这种场景下我觉得是值得一用的,可以提高开发效率,避免开发手动操作失误

 

工具函数的来源:https://github.com/fishyxin/simple-copy-properties

你可能感兴趣的:(GO)