diff --git a/.gitignore b/.gitignore index 47fef4d4ee0a71b98c2cd2795f96d73d3e19fb09..33e605f718e829198416fbd503368b795edf97e6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ /common/build/ /logs/ .idea/ -.gradle/ \ No newline at end of file +.gradle/ +build/ \ No newline at end of file diff --git a/SERIALIZE.md b/SERIALIZE.md index fb732d48a938df992a18b7c7e30b6adc1b2e388b..3dfd20aa3368a9492e2e2419ed1bfba4a67debed 100644 --- a/SERIALIZE.md +++ b/SERIALIZE.md @@ -9,49 +9,45 @@ N久之前,leo曾经问我枚举的应用,我清楚地记得菜鸟教程(htt ![](./asset/img/enumeration.png) -当时我还找到了给leo看,现在看来是我断章取义了。因为那时候很少会接触到枚举,所以我以为这玩意真的没救了。 +当时我还找到了给leo看,说这玩意要被取代了.现在看来是我断章取义了。因为那时候很少会接触到枚举,所以我以为这玩意真的没救了。 + 现在看来,看多了不去实践与思考,正应了一句话“尽信书不如无书”。 最近《阿里巴巴开发规范-嵩山版版》有下面的一句话,可能会与接下来的内容相冲。不知道为啥不能返回枚举类型,有待考究。 ![](./asset/img/emum-respone.png) -无论枚举要怎么使用,我还是按照自己的相关来实践了一把,由于项目中有很多枚举,使用和管理起来非常晕乎乎的。需要把枚举与Integer转来转去, -前端传输过来了一个Integer,需要手动将Integer转成枚举,存储到数据库的时候,又得将枚举转成Integer保存。如果纯粹使用Integer传值,编码又不能知道 -这个数字代表啥意思,最后找来找去。不光是后端很是晕乎乎的。前端由于也只接受了Integer,需要显示文字的时候,只能前后端共同约定,一旦后端修改了枚举,那么 -前端必须同步修改。所以我在网上找了一些解决办法,但是都不尽人意。最后求助于jackson的维护者解决了枚举正反序列化的问题。 +无论枚举要怎么使用,我还是按照自己的相关需求来实践了一把,由于项目中有很多枚举,使用和管理起来非常晕乎乎的。需要把枚举与Integer转来转去,前端传输过来了一个Integer,需要手动将Integer转成枚举,存储到数据库的时候,又得将枚举转成Integer保存。如果纯粹使用Integer传值,编码又不能知道这个数字代表啥意思,最后找来找去。不光是后端很是晕乎乎的。前端由于也只接受了Integer,需要显示文字的时候,只能前后端共同定,一旦后端修改了枚举,那么前端必须同步修改。所以我在网上找了一些解决办法,但是都不尽人意。最后折腾了jackson源码并求助于jackson的维护者解决了枚举正反序列化的问题。 ## 基础框架 + 框架 | 官网 | 版本 --- | --- | --- SpringBoot| https://spring.io/projects/spring-boot | V2.2.9-GA SpringWebMVC| https://spring.io/projects/spring-framework | 5.2.8.REALEASE Mybatis-plus | https://mybatis.plus/ | 3.4.0 jackson | https://github.com/FasterXML/jackson | 2.10.5 - + > 上面的框架的各个版本可能代码有点差别,但是基本思想都是一样的,所以版本不会有很大影响。此外其他计算机语言或者框架实现本文的思想都是可以的。 -## 概要介绍 +## 模型介绍 -先看下一般工程的基本结构 +先看下一般工程的基本模型 ![](./asset/img/structure.png) -本文的重点是枚举的正反序列化,但是为了让整个枚举在工程中的应用比较完整,也会描述下枚举在DAO层的操作。jackson的 -正反序列化主要应用在Controller层的`参数接收`与`结果返回`。在参数接收的时候有两种形式,一种的前端通过表单提交的数据 -,另一种是从body提交的json数据,两种有很大的区别,在Controller的方法里面主要体现在body提交的json数据需要在对象前面 -加上`@RequestBody`.当然两者本质上有点区别,由于表单提交的不是json,所以无法采用json反序列化,但是本文中会顺带描述到表单提交的数据如何转换成枚举。 - +本文的重点是枚举的正反序列化,但是为了让整个枚举在工程中的应用比较完整,也会描述下枚举在DAO层的操作。jackson的正反序列化主要应用在Controller层的`参数接收`与`结果返回`。在参数接收的时候有两种形式,一种的前端通过表单提交的数据,另一种是从body提交的json数据,两种有很大的区别,在Controller的方法里面主要体现在body提交的json数据需要在对象前面加上`@RequestBody`.当然两者本质上有点区别,由于表单提交的不是json,所以无法采用json反序列化,但是本文中会顺带描述到表单提交的数据如何转换成枚举。 + ## show you code - + ### 工程源代码 https://gitee.com/eric-tutorial/SpringCloud-multiple-gradle -> 篇幅有限,只讲述重点代码逻辑,完整的可以参考源代码。 +> 篇幅有限,只讲述重点代码逻辑,完整的可以参考源代码。项目基于Gradle构建. ### 定义枚举 @@ -93,14 +89,14 @@ public class UserParam { } ``` -上面代码可以看出来框架在接受参数的时候将网络传输过来的数据进行了反序列化,在返回给前端的时候进行了正序列化成json返回的。默认的jackson是无法 -直接按照code来正反序列化枚举的,jackson有一套自己的枚举序列化机制,从源代码中看出来,它是按照name和ordinal来正反序列化的。但是这个不能满足我自己定义的 -code和description来正反序列化的需求。因此我在网上搜了下,看看有木有人完成这样的需求,我想这个需求应该比较正常,网上一搜果然有很多。很快就有了下面的代码。 +上面代码可以看出来框架在接受参数的时候将网络传输过来的数据进行了反序列化,在返回给前端的时候进行了正序列化成json返回的。默认的jackson是无法直接按照`GenderEnum`中的`code`来正反序列化枚举的,因为jackson有一套自己的枚举序列化机制,从源代码中看出来,它是按照name和ordinal来正反序列化的。但是这个不能满足我自己定义的`code`和`description`来正反序列化的需求。因此我在网上搜了下,看看有木有人完成这样的需求,我想这个需求应该比较正常,网上一搜果然有很多。很快就有了下面的代码(最后发现都是采用默认的jackson枚举正反序列化器,并不满足需求)。 ### 自定义的枚举序列化器 #### 面向接口编程 +为我需要正序列化的枚举统一定义了一个接口.所以需要参与正序列化的枚举都得实现这个接口. + ```java public interface BaseEnum { /** @@ -137,6 +133,9 @@ public class BaseEnumSerializer extends JsonSerializer { } ``` + +效果就是既返回`code`和`description`,前端既知道`code`也知道`description`.`description`可以直接显示,`code`可以用来返回给后端的操作.前端再也不用同步修改`description`了,也不需要自己判断`code`是啥意思,直接显示`description`即可.皆大欢喜. + #### 反序列化器 ```java @@ -155,22 +154,23 @@ public class BaseEnumDeserializer extends JsonDeserializer { JsonStreamContext parsingContext = p.getParsingContext(); String currentName = parsingContext.getCurrentName();//字段名 Object currentValue = parsingContext.getCurrentValue();//前端注入的对象(ResDTO) - Field field = ReflectionUtils.getField(currentValue.getClass(), currentName); // 通过对象和属性名获取属性的类型 -// 获取对应得枚举类 + Field field = ReflectionUtils.getField(currentValue.getClass(), currentName); // 通过对象和属性名获取属性的类型 + // 获取对应得枚举类 Class enumClass = field.getType(); -// 根据对应的值和枚举类获取相应的枚举值 + // 根据对应的值和枚举类获取相应的枚举值 BaseEnum anEnum = DefaultInputJsonToEnum.getEnum(inputParameter, enumClass); log.info("\n====>测试反序列化枚举[{}]==>[{}.{}]", inputParameter, anEnum.getClass(), anEnum); return anEnum; } catch (Exception e) { - // logger.error("JsonEnumDeserializer deserialize error: " + Throwables.getStackTraceAsString(e)); -// System.out.println("JsonEnumDeserializer deserialize error: " + Throwables.getStackTraceAsString(e)); throw new RuntimeException(e); } } } ``` + +效果就是反序列化器用来解决参数接受的时候,将前端传过来的`code`转成`Enum`.方便枚举在程序中的操作,降低程序的复杂度,使编码更加简单,代码清晰明了. + #### 注入到SpringBoot框架中 ```java @@ -192,30 +192,189 @@ public class BaseEnumDeserializer extends JsonDeserializer { } ``` -经过测试,枚举序列化后返回到前端的效果如下,与期望的效果一致,这样的好处就是前端不需要管数字是啥意思,直接显示description即可,无论后端枚举是否修改, -前端都不需要关心了。 +经过测试,枚举序列化后返回到前端的效果如下,与期望的效果一致,这样的好处就是前端不需要管数字是啥意思,直接显示`description`即可,无论后端枚举是否修改,前端都不需要关心了。 ![](./asset/img/enums.png) -经过反复测试与人分享成果的时候,发现一个非常严重的问题,虽然前端接收参数的时候也可以反序列化成枚举,但是实际上没有按照code来反序列化。最后只能把jackson -的源代码拉下来调试,经过调试发现,jackson反序列化的时候一直使用的是自带的枚举反序列化器,并没有使用自定义枚举反序列化器。此外还从源码中分析出来 -为啥有的枚举反序列化就能正常,但是有点不能。原来自带的枚举反序列化器是按照ordinal来反序列化的,也就是说只有当code与ordinal一致的时候就会造成一种假象, -以为是code反序列化来的,其实依旧是ordinal反序列化来的。 +经过反复测试与人分享成果的时候,发现一个非常严重的问题,虽然前端接收参数的时候也可以反序列化成枚举,但是实际上没有按照`code`来反序列化。最后只能把jackson的源代码拉下来调试,经过调试发现,jackson反序列化的时候一直使用的是默认的枚举反序列化器,并没有使用自定义枚举反序列化器。 -> Java的枚举本质上是java.lang.Enum.class,自带有ordinal和name两个属性。ordinal可以理解成数组的下标。 +com.fasterxml.jackson.databind.deser.BasicDeserializerFactory#createEnumDeserializer + + +```java + /** + * Factory method for constructing serializers of {@link Enum} types. + */ + @Override + public JsonDeserializer createEnumDeserializer(DeserializationContext ctxt, + JavaType type, BeanDescription beanDesc) + throws JsonMappingException + { + final DeserializationConfig config = ctxt.getConfig(); + final Class enumClass = type.getRawClass(); + // 23-Nov-2010, tatu: Custom deserializer? + JsonDeserializer deser = _findCustomEnumDeserializer(enumClass, config, beanDesc); + + if (deser == null) { + // 12-Feb-2020, tatu: while we can't really create real deserializer for `Enum.class`, + // it is necessary to allow it in one specific case: see [databind#2605] for details + // but basically it can be used as polymorphic base. + // We could check `type.getTypeHandler()` to look for that case but seems like we + // may as well simply create placeholder (AbstractDeserializer) regardless + if (enumClass == Enum.class) { + return AbstractDeserializer.constructForNonPOJO(beanDesc); + } + + ValueInstantiator valueInstantiator = _constructDefaultValueInstantiator(ctxt, beanDesc); + SettableBeanProperty[] creatorProps = (valueInstantiator == null) ? null + : valueInstantiator.getFromObjectArguments(ctxt.getConfig()); + // May have @JsonCreator for static factory method: + for (AnnotatedMethod factory : beanDesc.getFactoryMethods()) { + if (_hasCreatorAnnotation(ctxt, factory)) { + if (factory.getParameterCount() == 0) { // [databind#960] + deser = EnumDeserializer.deserializerForNoArgsCreator(config, enumClass, factory); + break; + } + Class returnType = factory.getRawReturnType(); + // usually should be class, but may be just plain Enum (for Enum.valueOf()?) + if (returnType.isAssignableFrom(enumClass)) { + deser = EnumDeserializer.deserializerForCreator(config, enumClass, factory, valueInstantiator, creatorProps); + break; + } + } + } + + // Need to consider @JsonValue if one found + if (deser == null) { + deser = new EnumDeserializer(constructEnumResolver(enumClass, + config, beanDesc.findJsonValueAccessor()), + config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)); + } + } + + // and then post-process it too + if (_factoryConfig.hasDeserializerModifiers()) { + for (BeanDeserializerModifier mod : _factoryConfig.deserializerModifiers()) { + deser = mod.modifyEnumDeserializer(config, type, beanDesc, deser); + } + } + return deser; + } +``` + +从上面可以看出来枚举反系列化器是怎么找到的.仔细阅读后发现,上面并没有按照接口 `BaseEnum` 来查找反序列化器,这也是为啥自定义的反序列化器没有生效的原因. + +既然我发现了这个问题,我直接在github拉下来了jackson代码,然后修改成按照接口查找自定义反序列化器的方式提交了我的代码. +```java + List interfaces = type.getInterfaces(); -调试过程中最让人百思不得解的是,自定义的正反枚举序列化器,序列化器是可以按照自己定义的接口来序列化,但是反序列化不行。最后经过反复调试,发现正反序列化过程有点区别, -正序列化的时候会找父类找接口,按照父类或者接口定义的序列化器来序列化。而反序列化的时候不会。体会一下,可以理解成一个正序列化的时候,准确度可以忽略,反正都是丢出去的。但是反序列化的时候 -必须保证精度,否则无法正确反序列化,那么对应的对象无法获取到正确的值。好比,银行存钱的时候不需要密码,取钱的时候就需要密码一样,看似一个对称的过程,但是校验机制还是有点区别的。 + for (JavaType javaType : interfaces) { + Class rawClass = javaType.getRawClass(); + deser = _findCustomEnumDeserializer(rawClass, config, beanDesc); + if (deser != null) { + return deser; + } + } +``` +pull request之后,管理者很快给我回复了。我们来回扯了几个回合之后,我们得到一个更加合理的解决办法. +这个问题,这个也是本文的重点。就是重写查找枚举反序列化器的方法,把我写的代码放在一个重写类里面即可. -既然我发现了这个问题,我直接在github拉下来了代码,然后修改成按照接口查找自定义序列化器的方式提交了我的代码,pull request之后,很快管理者给我回复了。我们来回扯了几个回合之后,老哥们终于帮我解决了 -这个问题,这个也是本文的重点。 https://github.com/FasterXML/jackson-databind/pull/2842 -![](./asset/img/github.com_FasterXML_jackson-databind_pull_2842.png) +![](./asset/img/github.com_FasterXML_jackson-databind_pull_28422.png) + +于是下面的代码就来了,依据开闭原则,修改源代码的事情不太能发生,管理者说修改违背了原有的思想,所以我的PR最后被我自己关闭了。 + +com.fasterxml.jackson.databind.module.SimpleDeserializers#findEnumDeserializer + +```java + @Override + public JsonDeserializer findEnumDeserializer(Class type, + DeserializationConfig config, BeanDescription beanDesc) + throws JsonMappingException + { + if (_classMappings == null) { + return null; + } + JsonDeserializer deser = _classMappings.get(new ClassKey(type)); + if (deser == null) { + // 29-Sep-2019, tatu: Not 100% sure this is workable logic but leaving + // as is (wrt [databind#2457]. Probably works ok since this covers direct + // sub-classes of `Enum`; but even if custom sub-classes aren't, unlikely + // mapping for those ever requested for deserialization + if (_hasEnumDeserializer && type.isEnum()) { + deser = _classMappings.get(new ClassKey(Enum.class)); + } + } + return deser; + } +``` + +从上面看出来这个就是查找枚举反序列化器的逻辑,重写SimpleDeserializers类即可.上面这个代码是无法按照接口找到反序列化器的,所以重写它,让它按照我期望的接口方式找到即可,最后也成功了. + + +此外还从源码中分析出来 +为啥有的枚举反序列化就能正常,但是有的不能完成翻序列化。原来默认的枚举反序列化器是按照`ordinal`来反序列化的,也就是说只有当`code`与`ordinal`一致的时候就会造成一种假象, +以为是`code`反序列化来的,其实依旧是`ordinal`反序列化来的。 + +从下面代码中可以看出来,枚举存储在数组中,而`ordinal`刚好是下标. + +com.fasterxml.jackson.databind.deser.std.EnumDeserializer#deserialize + +```java + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException + { + JsonToken curr = p.currentToken(); + + // Usually should just get string value: + if (curr == JsonToken.VALUE_STRING || curr == JsonToken.FIELD_NAME) { + CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING) + ? _getToStringLookup(ctxt) : _lookupByName; + final String name = p.getText(); + Object result = lookup.find(name); + if (result == null) { + return _deserializeAltString(p, ctxt, lookup, name); + } + return result; + } + // But let's consider int acceptable as well (if within ordinal range) + if (curr == JsonToken.VALUE_NUMBER_INT) { + // ... unless told not to do that + int index = p.getIntValue(); + if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) { + return ctxt.handleWeirdNumberValue(_enumClass(), index, + "not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow" + ); + } + if (index >= 0 && index < _enumsByIndex.length) { + return _enumsByIndex[index]; + } + if ((_enumDefaultValue != null) + && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) { + return _enumDefaultValue; + } + if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { + return ctxt.handleWeirdNumberValue(_enumClass(), index, + "index value outside legal index range [0..%s]", + _enumsByIndex.length-1); + } + return null; + } + return _deserializeOther(p, ctxt); + } + +``` + +![](./asset/img/ordinal-param.png) +![](./asset/img/code-ordinal.png) +![](./asset/img/ordinal数组.png) + +> Java的枚举本质上是java.lang.Enum.class,自带有ordinal和name两个属性。ordinal可以理解成数组的下标。 + +调试过程中最让人百思不得解的是,自定义的正反枚举序列化器,序列化器是可以按照自己定义的接口来序列化,但是反序列化不行。最后经过反复调试,发现正反序列化过程有点区别,正序列化的时候会找父类找接口,按照父类或者接口定义的序列化器来序列化。而反序列化的时候不会。体会一下,可以理解成一个正序列化的时候,准确度可以忽略,反正都是丢出去的。但是反序列化的时候必须保证精度,否则无法正确反序列化,那么对应的对象无法获取到正确的值。瞎扯一下.好比,银行存钱的时候不需要密码,取钱的时候就需要密码一样,看似一个对称的过程,但是校验机制还是有点区别的,可以细细体会这种方式的必要性。 -于是下面的代码就来了,依据开闭原则,修改源代码的事情不太能发生,所以我的PR最后被我自己关闭了。 #### 重写SimpleDeserializers的findEnumDeserializer方法 @@ -233,10 +392,10 @@ public class SimpleDeserializersWrapper extends SimpleDeserializers { if (enumDeserializer != null) { return enumDeserializer; } - logger.info("\n重写枚举查找逻辑"); for (Class typeInterface : type.getInterfaces()) { enumDeserializer = this._classMappings.get(new ClassKey(typeInterface)); if (enumDeserializer != null) { + logger.info("\n====>重写枚举查找逻辑[{}]",enumDeserializer); return enumDeserializer; } } @@ -269,7 +428,9 @@ public class SimpleDeserializersWrapper extends SimpleDeserializers { ### 时间等序列化 -一般来说,会在Date上满加上时间序列化的注解,但是也可以针对Date自定义正反序列化器,就可以很轻松解决问题。 +一般来说,会在Date上满加上时间序列化的注解`@JsonFormat`,但是也可以针对Date自定义正反序列化器,就可以很轻松解决问题。 + +> 仔细阅读jackson的源代码你会发现这个还是里面有很多的默认序列化器,用来解决一些常用的类型序列化. ### 表单提交的数据转成枚举 @@ -282,8 +443,8 @@ public class SimpleDeserializersWrapper extends SimpleDeserializers { ### DAO 层处理枚举存到数据库 -这个不涉及序列化,只能算是一种类型转化。 -具体就是在枚举上面上一个注解 + +具体就是在枚举的属性上面上一个注解 ```java @EnumValue//标记数据库存的值是code @@ -294,98 +455,16 @@ public class SimpleDeserializersWrapper extends SimpleDeserializers { mybatis-plus: type-enums-package: hxy.dream.entity.enums ``` -上面两步,就是借助mybatis-plus完成了枚举存储到数据库,与读取的时候转换的问题。这个比较简单,框架也就是做这些事情的,让开发者专注于业务, -而不是实现技术的本身(不是说不要钻研技术底层原理)。 +上面两步,就是借助mybatis-plus完成了枚举存储到数据库,与读取的时候转换的问题。这个比较简单,框架也就是做这些事情的,让开发者专注于业务,而不是实现技术的本身(不是说不要钻研技术底层原理)。 > 参考 mybatis-plus:https://mp.baomidou.com/guide/enum.html -Google 官方将自动补全功能称之为“预测”,而不是“建议”,为什么呢?其实是有充分理由的。自动补全功能是为了**帮助用户完成他们打算进行的搜索**,而不是建议用户要执行什么搜索。 - -那么,Google 是如何确定这些“预测”的?其实,Google 会根据趋势搜索 [trends](https://trends.google.com/trends/?geo=US) 给到我们这些“预测”。简单来说,哪个热门、哪个搜索频率高,就更可能推给我们。当然,这也与我们当前所处的位置以及我们的搜索历史相关。 - -另外,这些“预测”也会随着我们键入的关键字的变更而更改。例如,当我们把键入的关键字从 `juej` 更改为 `juex` 时,与“掘金”相关的预测会“消失”,同时,与“觉醒”、“决心”相关联的词会出现。 - -![](https://gitee.com/yanglbme/resource/raw/master/doocs-md/juex.gif) - -## 为什么看不到某些联想词? - -如果我们在输入某个关键字时看不到联想词,那么表明 Google 的算法可能检测到: - -- 这个关键字不是热门字词; -- 搜索的字词太新了,我们可能需要等待几天或几周才能看到联想词; -- 这是一个侮辱性或敏感字词,这个搜索字词违反了 Google 的相关政策。更加详细的情况,可以了解 [Google 搜索自动补全政策](https://support.google.com/websearch/answer/7368877)。 - -## 为什么会看到某些不当的联想词? - -Google 拥有专门设计的系统,可以自动捕获不适当的预测结果而不显示出来。然而,Google 每天需要处理数十亿次搜索,这意味着 Google 每天会显示数十亿甚至上百亿条预测。再好的系统,也可能存在缺陷,不正确的预测也可能随时会出现。 - -我们作为 Google 搜索的用户,如果认定某条预测违反了相关的搜索自动补全政策,可以进行举报反馈,点击右下角“**举报不当的联想查询**”并勾选相关选项即可。 - -![](https://gitee.com/yanglbme/resource/raw/master/doocs-md/report.gif) - -## 如何实现自动补全算法? - -目前,Google 官方似乎并没有公开搜索自动补全的算法实现,但是业界在这方面已经有了不少研究。 - -一个好的自动补全器必须是快速的,并且在用户键入下一个字符后立即更新联想词列表。**自动补全器的核心是一个函数,它接受输入的前缀,并搜索以给定前缀开头的词汇或语句列表**。通常来说,只需要返回少量的数目即可。 - -接下来,我们先从一个简单且低效的实现开始,并在此基础上逐步构建更高效的方法。 - -### 词汇表实现 - -一个**简单粗暴的实现方式**是:顺序查找词汇表,依次检查每个词汇,看它是否以给定的前缀开头。 - -但是,此方法需要将前缀与每个词汇进行匹配检查,若词汇量较少,这种方式可能勉强行得通。但是,如果词汇量规模较大,效率就太低了。 - -一个**更好的实现方式是**:让词汇按字典顺序排序。借助二分搜索算法,可以快速搜索有序词汇表中的前缀。由于二分搜索的每一步都会将搜索的范围减半,因此,总的搜索时间与词汇表中单词数量的对数成正比,即时间复杂度是 `O(log N)`。二分搜索的性能很好,但有没有更好的实现呢?当然有,往下看。 - -### 前缀树实现 - -通常来说,许多词汇都以相同的前缀开头,比如 `need`、`nested` 都以 `ne` 开头,`seed`、`speed` 都以 `s` 开头。要是为每个单词分别存储公共前缀似乎很浪费。 - -![](https://gitee.com/yanglbme/resource/raw/master/doocs-md/pretree.png) - -前缀树是一种利用公共前缀来加速补全速度的数据结构。前缀树在节点树中排列一组单词,单词沿着从根节点到叶子节点的路径存储,树的层次对应于前缀的字母位置。 - -前缀的补全是顺着前缀定义的路径来查找的。例如,在上图的前缀树中,前缀 `ne` 对应于从子节点取左边缘 `N` 和唯一边缘 `E` 的路径。然后可以通过继续遍历从 `E` 节点可以达到的所有叶节点来生成补全列表。在图中,`ne` 的补全可以是两个分支:`-ed` 和 `-sted`。如果在数中找不到由前缀定义的路径,则说明词汇表中不包含以该前缀开头的单词。 - -### 有限状态自动机(DFA)实现 - -前缀树可以有效处理公共前缀,但是,对于其他共享词部分,仍会分别存储在每个分支中。比如,后缀 `ed`、`ing`、`tion` 在英文单词中特别常见。在上一个例子中,`e`、`d` 分别存放在了每一个分支上。 - -有没有一种方法可以更加节省存储空间呢?有的,那就是 DFA。 - -
-
- -在上面的例子中,单词 `need`、`nested`、`seed` 和 `speed` 仅由 9 个节点组成,而上一张图中的前缀树包含了 17 个节点。 - -可以看出,最小化前缀树 DFA 可以在很大程度上减少数据结构的大小。即使词汇量很大,最小化 DFA 通常也适合在内存中存储,避免昂贵的磁盘访问是实现快速自动补全的关键。 - -### 一些扩展 - -上面介绍了如何利用合理的数据结构实现基本的自动补全功能。这些数据结构可以通过多种方式进行扩展,从而改善用户体验。 - -通常,满足特定前缀的词汇可能很多,而用户界面上能够显示的却不多,我们更希望能显示最常搜索或者最有价值的词汇。这通常可以通过为词汇表中的每个单词增加一个代表单词值的**权重** `weight`,并且按照权重高低来排序自动补全列表。 - -- 对于排序后的词汇表来说,在词汇表每个元素上增加 `weight` 属性并不难; -- 对于前缀树来说,将 `weight` 存储在叶子节点中,也是很简单的一个实现; -- 对于 `DFA` 来说,则较为复杂。因为一个叶子节点可以通过多条路径到达。一种解决方案是将权重关联到路径而不是叶子节点。 - -目前有不少开源库都提供了这个功能,比如主流的搜索引擎框架 [Elasticsearch](https://www.elastic.co/products/elasticsearch)、[Solr](https://lucene.apache.org/solr/) 等,基于此,我们可以实现高效而强大的自动补全功能。 -#### 推荐阅读 -- [阿里又一个 20k+ stars 开源项目诞生,恭喜 fastjson!](https://mp.weixin.qq.com/s/RNKDCK2KoyeuMeEs6GUrow) -- [刷掉 90% 候选人的互联网大厂海量数据面试题(附题解 + 方法总结)](https://mp.weixin.qq.com/s/rjGqxUvrEqJNlo09GrT1Dw) -- [好用!期待已久的文本块功能究竟如何在 Java 13 中发挥作用?](https://mp.weixin.qq.com/s/kalGv5T8AZGxTnLHr2wDsA) -- [2019 GitHub 开源贡献排行榜新鲜出炉!微软谷歌领头,阿里跻身前 12!](https://mp.weixin.qq.com/s/_q812aGD1b9QvZ2WFI0Qgw) ---- +## 总结 -欢迎关注我的公众号“**Doocs 开源社区**”,原创技术文章第一时间推送。 +以上的操作完成了枚举的从前端接收,反序列化成枚举对象在程序中表达。然后再存储到数据库中。从数据库中取code转成枚举,在程序中表达,再序列化枚举后传输给前端。一个非常完整的循环,基本上满足了程序中对枚举使用的需求。 -
- -
+--- \ No newline at end of file diff --git a/app/src/main/java/hxy/dream/app/service/impl/UserServiceImpl.java b/app/src/main/java/hxy/dream/app/service/impl/UserServiceImpl.java index 3f72de31b36172404732bcbe1394ca323993410d..10325aff86b6fd5b9ea6206c77244ca60f817af2 100644 --- a/app/src/main/java/hxy/dream/app/service/impl/UserServiceImpl.java +++ b/app/src/main/java/hxy/dream/app/service/impl/UserServiceImpl.java @@ -33,9 +33,6 @@ public class UserServiceImpl implements UserService { } int insert = userMapper.insert(userModel); log.debug("\n====>插入影响行数:{}", insert); - UserDTO userDTO = new UserDTO(); - userDTO.setId("111"); - userModel.setUserDTO(userDTO); return userModel; } diff --git a/asset/img/code-ordinal.png b/asset/img/code-ordinal.png new file mode 100644 index 0000000000000000000000000000000000000000..f4364a0e2399b905f6dbbc64aa0f3832b109be6a Binary files /dev/null and b/asset/img/code-ordinal.png differ diff --git a/asset/img/github.com_FasterXML_jackson-databind_pull_28422.png b/asset/img/github.com_FasterXML_jackson-databind_pull_28422.png new file mode 100644 index 0000000000000000000000000000000000000000..c98251fd869edb9b7aebed4ebad720f88607b427 Binary files /dev/null and b/asset/img/github.com_FasterXML_jackson-databind_pull_28422.png differ diff --git a/asset/img/ordinal-param.png b/asset/img/ordinal-param.png new file mode 100644 index 0000000000000000000000000000000000000000..102a905166263a31de924ddafcbc3c721eaef3cb Binary files /dev/null and b/asset/img/ordinal-param.png differ diff --git "a/asset/img/ordinal\346\225\260\347\273\204.png" "b/asset/img/ordinal\346\225\260\347\273\204.png" new file mode 100644 index 0000000000000000000000000000000000000000..613e1e0be3541458e4a2e07ec9a9722fb6f09d58 Binary files /dev/null and "b/asset/img/ordinal\346\225\260\347\273\204.png" differ diff --git a/common/src/main/java/hxy/dream/common/serializer/BaseEnumDeserializer.java b/common/src/main/java/hxy/dream/common/serializer/BaseEnumDeserializer.java index 9b91e0a89a86915d5400cdc86e77f2a7b53df7ec..e85b9b1484e05c6146e36754968996c77d6d7e1f 100644 --- a/common/src/main/java/hxy/dream/common/serializer/BaseEnumDeserializer.java +++ b/common/src/main/java/hxy/dream/common/serializer/BaseEnumDeserializer.java @@ -41,8 +41,6 @@ public class BaseEnumDeserializer extends JsonDeserializer { log.info("\n====>测试反序列化枚举[{}]==>[{}.{}]", inputParameter, anEnum.getClass(), anEnum); return anEnum; } catch (Exception e) { - // logger.error("JsonEnumDeserializer deserialize error: " + Throwables.getStackTraceAsString(e)); -// System.out.println("JsonEnumDeserializer deserialize error: " + Throwables.getStackTraceAsString(e)); throw new RuntimeException(e); } } diff --git a/common/src/main/java/hxy/dream/common/serializer/DateJsonDeserializer.java b/common/src/main/java/hxy/dream/common/serializer/DateJsonDeserializer.java index b118506a1b001dec32a8f108debd299cf27aaafe..9eb170a27bb20ef8896dd690892e9e2c247701d0 100644 --- a/common/src/main/java/hxy/dream/common/serializer/DateJsonDeserializer.java +++ b/common/src/main/java/hxy/dream/common/serializer/DateJsonDeserializer.java @@ -22,7 +22,6 @@ public class DateJsonDeserializer extends JsonDeserializer { } } catch (Exception e) { - System.out.println(e.getMessage()); throw new RuntimeException(e); } } diff --git a/common/src/main/java/hxy/dream/common/serializer/DefaultInputJsonToEnum.java b/common/src/main/java/hxy/dream/common/serializer/DefaultInputJsonToEnum.java index 64c900c6458a3b91c26edd2a3447494015775ff0..217961df692de116cbf280f075e1dcf8ff842ba6 100644 --- a/common/src/main/java/hxy/dream/common/serializer/DefaultInputJsonToEnum.java +++ b/common/src/main/java/hxy/dream/common/serializer/DefaultInputJsonToEnum.java @@ -49,7 +49,6 @@ public class DefaultInputJsonToEnum { return (BaseEnum) valueOfMethod.invoke(null, inputParameter); } catch (Exception e) { //这里异常无需抛出,因为这个枚举根据name获取不到就直接抛出异常了... - //logger.warn("未获取枚举的name值,入参: " + inputParameter + "," + "class : " + enumClass); return null; } } diff --git a/common/src/main/java/hxy/dream/common/serializer/SimpleDeserializersWrapper.java b/common/src/main/java/hxy/dream/common/serializer/SimpleDeserializersWrapper.java index 0d4074474db872536e3995f454d269908a6ab0e2..75848f6a2bf7532e7d7d948bd4df34c6a1a5a924 100644 --- a/common/src/main/java/hxy/dream/common/serializer/SimpleDeserializersWrapper.java +++ b/common/src/main/java/hxy/dream/common/serializer/SimpleDeserializersWrapper.java @@ -20,10 +20,10 @@ public class SimpleDeserializersWrapper extends SimpleDeserializers { if (enumDeserializer != null) { return enumDeserializer; } - logger.info("\n重写枚举查找逻辑"); for (Class typeInterface : type.getInterfaces()) { enumDeserializer = this._classMappings.get(new ClassKey(typeInterface)); if (enumDeserializer != null) { + logger.info("\n重写枚举查找逻辑[{}]",enumDeserializer); return enumDeserializer; } } diff --git a/dao/src/main/java/hxy/dream/dao/modle/UserModel.java b/dao/src/main/java/hxy/dream/dao/modle/UserModel.java index 2db217713f2bd1807b14f76c10b383743526747b..d6951aa27bea7092af4d22692dc09a0e87681948 100644 --- a/dao/src/main/java/hxy/dream/dao/modle/UserModel.java +++ b/dao/src/main/java/hxy/dream/dao/modle/UserModel.java @@ -1,15 +1,16 @@ package hxy.dream.dao.modle; import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.activerecord.Model; -import hxy.dream.entity.dto.UserDTO; import hxy.dream.entity.enums.GenderEnum; import lombok.Data; import lombok.EqualsAndHashCode; +/** + * @author iris + */ @Data @TableName("users") @EqualsAndHashCode(callSuper=false) @@ -20,6 +21,4 @@ public class UserModel extends Model { Integer age; GenderEnum gender; String password; - @TableField(exist = false) - UserDTO userDTO; }