相信大家都用过Fastjson,这是阿里开源的JSON库,在阿里部门的开源项目中被广泛使用。尽管它有时被昵称为“慢而快”,但从功能丰富性、易用性和源代码设计的角度来看,Fastjson是一个优秀的工具库。在使用Fastjson的时候,经常会配置一些枚举参数,比如日期格式,格式化输出,NULL值格式等,如下:;//JSON.toJSONStringpublicstaticStringtoJSONString(Objectobject,SerializerFeature...features);这种配置方式用起来还是蛮爽的,你想要什么输出配置,使用JAVA的变长参数(varargs)可以在方法后面动态添加;如果你加上importstatic,你甚至不需要写SerializerFeature。但是想一想,Fastjson在接受了这个动态参数数组之后,是怎么知道我们传递了哪些参数的呢?当接受遍历数组时,每个等于比较?例如://之所以要写三个for循环,是因为不同的特征在大概率下的处理时机不同,所以不能用一个for循环来处理。for(SerializerFeaturefeature:features){if(feature.equals(SerializerFeature.WriteDateUseDateFormat)){//解决WriteDateUseDateFormat}}for(SerializerFeaturefeature:features){if(feature.equals(SerializerFeature.PrettyFormat)){//解决PrettyFormat}}for(SerializerFeaturefeature:features){if(feature.equals(SerializerFeature.WriteNullStringAsEmpty)){//解决WriteNullStringAsEmpty}}这不“优雅”,每次都需要遍历,白白浪费性能!或者只是使用循环获取一些变量来存储这些布尔值:booleanwriteDateUseDateFormatEnable=false;布尔PrettyFormatEnable=false;布尔WriteNullStringAsEmptyEnable=false;for(SerializerFeaturefeature:features){if(feature.equals(SerializerFeature.WriteDateUseDateFormat)){writeDateUseDateFormatEnable=true;}if(feature.equals(SerializerFeature.PrettyFormat)){PrettyFormatEnable=true;}if(feature.equals(SerializerFeature.WriteNullStringAsEmpty)){WriteNullStringAsEmptyEnable=true;}}这样比上面好一点,但还是需要循环判断,并且为每个Feature额外增加了一个变量来存储,同样不“优雅”。Fastjson使用了一种非常巧妙的方式来处理这个动态枚举参数。枚举中的序数(ordinal)在正式介绍之前,你需要了解枚举中的一个概念——序数(ordinal),每个枚举类都会有一个ordinal属性,这个序数表示当前枚举值在类中的序号枚举类。比如下面的枚举类中,四个枚举值F_A/F_B/F_C/F_D的序数分别为0/1/2/3publicenumFeature{F_A,//序数0F_B,//序数1F_C,//ordinal2F_D,//ordinal3;}通过ordinal()方法可以获取枚举实例的序号值,如Feature._F_A_.ordinal()中的魔法fastjson了解枚举序号后,我们现在就来看看Fastjson是怎么玩的吧。SerializerFeature的源码中有一个掩码(mask),这个掩码的值为1<<序数枚举中的位掩码-MaskpublicenumSerializerFeature{WriteDateUseDateFormat,PrettyFormat,WriteNullStringAsEmpty...SerializerFeature(){mask=(1<<序号());}公共最终int掩码;...}掩码掩码在位操作中的作用一般是保持/改变/删除某些(某些)位的值,有一张很形象的图(这张图可以简单理解为白色像素代表1,黑色像素代表0,按下and后,会显示1的像素位):那么在SerializerFeature中,WriteDateUseDateFormat、PrettyFormat、WriteNullStringAsEmpty这三个值分别为0/1/2,左移后,它们对应的二进制位如下:0001WriteDateUseDateFormat0010PrettyFormat0100WriteNullStringAsEmpty...1000这里的方法很巧妙,用1将序数左移,可以得到A序号为1的数,例如序号为1,则第0位为1,序号为3,则第4位为1,以此类推,使得掩码中为1的位枚举中的每个值都会对不同的多重配置进行处理,光看这个bitmask还是没用的,还是看实战吧。现在定义一个初始值为0的features变量,用来存放所有的featuresintfeatures=0;使用位或(OR)操作特征和屏蔽特征|=SerializerFeature.WriteDateUseDateFormat.getMask();0000[input(features)](|)0001[mask(featuremask)]------------0001[output(newfeatures)]特征bitOR运算为0001后,第0位变为1,表示启用第1位的枚举值(WriteDateUseDateFormat),然后继续执行位或PrettyFormat,features|=SerializerFeature.PrettyFormat。得到面具();0001[input(features)](|)0010[mask(featuremask)]------------0011[output(newfeatures)]此时此时features为0011,第二位也变为1,表示第二位的枚举值(PrettyFormat)也开启,判断是否配置了features的值,或者需要简单的判断方法检查某个枚举值是否设置:publicstaticbooleanisEnabled(intfeatures,SerializerFeaturefeature){return(features&feature.mask)!=0;}将特征的掩码和一个特征进行按位与后,它可以得到一个数,里面有一位。在位与运算中,只有高位和低位都为1,返回的位才为1,那么只要返回的结果位中有任何一个1,这个数就不会为0;所以只要结果不为0,就可以表明已经设置了这个Feature。0011[input(features)](&)0010[mask(PrettyFormat)]------------0010[output(newfeatures)]比如上例中,当前特征为0011,与PrettyFormat的mask进行按位与运算后,可以得到0010,结果不为0,所以PrettyFormat已经设置完毕example//StoreallconfiguredFeatureintfeatures=0;//每次添加一个Feature,取当前Feature的特征和mask做按位或运算features|=SerializerFeature.WriteDateUseDateFormat.getMask();features|=SerializerFeature.PrettyFormat.getMask();//然后通过位与运算的结果可以判断一个Feature是否配置booleanwriteDateUseDateFormatEnabled=SerializerFeature.isEnabled(features,SerializerFeature.WriteDateUseDateFormat);booleanprettyFormatEnabled=SerializerFeature.isEnabled(features,SerializerFeature.PrettyFormat);布尔writeFeNullfeatures.PrettyStringerESerializerFeature.WriteNullStringAsEmpty);System.out.println("writeDateUseDateFormatEnabled:"+writeDateUseDateFormatEnabled);System.out.println("prettyFormatEnabled:"+prettyFormatEnabled);System.out.println("writeNullStringAsEmpty:"+writeNullStringAsEmpty);//outputwriteDateUseDateFormatEnabled:trueprettyFormatEnabled:truewriteNullStringAsEmpty:falseSummary不仅是Fastjson,Jackson中对Feature的处理也是基于枚举序号+位掩码的逻辑。两者的实现完全一样,就是算主流怎么做的来源|https://juejin.cn/post/697156...
