原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6715063.html

1、回顾

  之前的两篇分别解析了类型别名注册器和类型处理器注册器,此二者皆是解析XML映射文件中参数类型与返回结果类型的基础,别名注册器用于通过别名找到对应的类类型,类型处理器注册器则用于通过类类型来找到对应的类型处理器与数据库类型,以此来完成进出数据库数据与java之间类型的转换。

  我们在类型处理器注册器一篇中已经简单介绍了类型处理器,那就是用于java类型与数据库类型之间进行映射处理的工具类,这一篇中要详细解析一下MyBatis中的类型处理器。

2、类型处理器

2.1 类架构

  

  从上面的图中可以看出MyBatis中整个类型处理器实现架构,TypeHandler接口定义了类型处理器,而TypeReference抽象类则定义了一个类型引用,用于引用一个泛型类型(此处很抽象,不好理解,详见后续解析),BaseTypeHandler则是类型处理器的基础,是所有类型处理器的公共模块,几乎所有的类型处理器都是通过直接继承BaseTypeHandler来实现的,这是很明显使用的是模板模式。

2.2 类型处理器接口:TypeHandler

  TypeHandler是用于定义类型处理器的接口,内部很简单:

  1. package org.apache.ibatis.type;
  2. import java.sql.CallableStatement;
  3. import java.sql.PreparedStatement;
  4. import java.sql.ResultSet;
  5. import java.sql.SQLException;
  6. /**
  7. * 类型处理器
  8. *
  9. */
  10. public interface TypeHandler<T> {
  11.  
  12. //设置参数
  13. void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  14.  
  15. //取得结果,供普通select用
  16. T getResult(ResultSet rs, String columnName) throws SQLException;
  17.  
  18. //取得结果,供普通select用
  19. T getResult(ResultSet rs, int columnIndex) throws SQLException;
  20.  
  21. //取得结果,供SP用
  22. T getResult(CallableStatement cs, int columnIndex) throws SQLException;
  23.  
  24. }

  通过上述源码可以看到这个接口中定义了类型处理器基本的四个方法,其中分为两大类,第一类是设置参数的方法setParameter(),这个方法是用于设置数据库操作的参数,例如查询参数、删除参数、更新参数等;另一类是用于取得结果的方法,这一类方法又细分为两大种,第一种是从结果集中获取结果,按照获取的方式分为两种:一种是通过列名(columnName)来获取,另一种是通过列下标(columnIndex)来获取,这两种获取方式正对应我们直接使用JDBC进行数据库查询结果中获取数据的两种方式,第二种是针对存储过程而设,通过列下标的方式来获取存储过程输出结果中的数据。

  总的来说类型处理器就是两方面的作用,一方面将Java类型的参数(T prarameter)设置到数据库操作脚本中(匹配数据库类型jdbcType),另一种是获取操作结果到Java类型(T)中。

2.3 类型引用:TypeReference

  这个类型引用的作用是用于获取原生类型,Java中的原生类型又称为基本类型,即byte、short、int、long、float、double、boolean、char八大基本数据类型。

  这个类有必要重点讲解一下,同时也是为了加强一下Java中类型的概念,来看源码:

  1. package org.apache.ibatis.type;
  2.  
  3. import java.lang.reflect.ParameterizedType;
  4. import java.lang.reflect.Type;
  5.  
  6. /**
  7. * References a generic type.
  8. *
  9. * @param <T> the referenced type
  10. * @author Simone Tripodi
  11. * @since 3.1.0
  12. * 3.1新加的类型引用,为了引用一个泛型类型
  13. */
  14. public abstract class TypeReference<T> {
  15.  
  16. //引用的原生类型
  17. private final Type rawType;
  18.  
  19. protected TypeReference() {
  20. rawType = getSuperclassTypeParameter(getClass());
  21. }
  22.  
  23. Type getSuperclassTypeParameter(Class<?> clazz) {
  24. //得到泛型T的实际类型
  25. Type genericSuperclass = clazz.getGenericSuperclass();
  26. if (genericSuperclass instanceof Class) {
  27. // try to climb up the hierarchy until meet something useful
  28. if (TypeReference.class != genericSuperclass) {
  29. return getSuperclassTypeParameter(clazz.getSuperclass());
  30. }
  31. throw new TypeException("'" + getClass() + "' extends TypeReference but misses the type parameter. "
  32. + "Remove the extension or add a type parameter to it.");
  33. }
  34. //获取泛型<T>中的T类型
  35. Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
  36. // TODO remove this when Reflector is fixed to return Types
  37. if (rawType instanceof ParameterizedType) {
  38. rawType = ((ParameterizedType) rawType).getRawType();
  39. }
  40. return rawType;
  41. }
  42.  
  43. public final Type getRawType() {
  44. return rawType;
  45. }
  46.  
  47. @Override
  48. public String toString() {
  49. return rawType.toString();
  50. }
  51.  
  52. }

  这个抽象类也是被BaseTypeHandler所继承的,也就意味着几乎所有的内置类型处理器都继承了这个类,那么这个类型引用的目的到底是什么呢?

  这个问题稍后再说,我们先解析下源码:

  这个类在其无参构造器中通过调用getSuperclassTypeParameter()方法为其内部定义的final型字段rawType赋值,其参数是getClass()方法的结果,这是Object类中定义的方法,这个方法返回的是当前类(实例)的类类型。

  重点在于getSuperclassTypeParameter()方法中:

    第一步:通过给定参数clazz的getGenericSuperclass()方法来获取该类类型的上一级类型(直接超类,父类,即参数类类型继承的类的类型)并带有参数类型,即带泛型。如果要获取不带泛型的父类可使用getSuperclass()方法。

    第二步:判断第一步获取的类型是否是Class类的实例

  Class类的实例有哪些呢?

  其实每一个类都是Class类的实例,Class类是对Java中类的抽象,它本身也是一个类,但它是处于普通类上一层次的类,是类的顶层抽象。从JDK文档中可获知“Instances of the class represent classes and interfaces in a running Java application.”(意为:Class的实例表示的是在一个运行的应用中的所有类和接口)

,那么我们就明白了,Class类的实例就是接口与类。那么Java中有哪些不是Class类的实例呢?泛型类,不错,如果一个类是泛型类,那么他就不再是Class类的实例,为什么呢?

  泛型类是Java中一种独特的存在,它一般用于传递类(更准确的说是传递类型),类似于一般方法中传递对象的概念,它不是简单的类,而是一种带有抽象概念性质的一种类,它会通过所传递的类(参数化类)来指定当前类所代表的是基于基本类型中的哪一类类型。(通过两种类型来确定具体的类型(最后这个类型表示的是泛型类型整体表达的类型))

    第二步:如果第一步获取的类型是带泛型的类型,那么判断不成立,则会直接执行第35行代码,将该类型强转为参数化类型,使用其getActualTypeArguments()方法来获取其参数类型(泛型类型),因为该方法获取的泛型类型可能不是一个,所以返回的是一个数组,但是我们这里只会获取到一个,所以取第一个即可。

    但是如果第一步获取的类型不带泛型,那么就会进入条件内部执行,再次判断,获取的类型是否是TypeReference类型,如果不是该类型,则有可能是多重继承导致目标类型并不是直接继承自TypeReference,那么我们通过getSuperclass()方法获取其父类,以这个类来进行递归;但如果获取到的是TypeReference类型,只是没有添加泛型,则抛出类型异常,提示丢失泛型。

    第三步:如果第二步判断不通过,则会执行地35行代码,来获取参数类型,然后对获取的参数类型进行判断,如果该类型还是参数化类型(仍然带有泛型,即泛型嵌套的模式),那么就需要再次执行getActualTypeArguments()方法来获取其泛型类型(参数类型),最后将该类型返回(赋值给字段)

  为什么只会获取两次呢?因为,通过之前的类架构我们已经明白,具体的类型处理器最多只会存在两层继承。

  最后说一下,这个类型引用的目的,它就是为了持有这个具体的类型处理器所处理的Java类型的原生类型。我们可以看到在该类中还有两个方法getRawType()和toString()方法,这两个方法都是public修饰的,是对外公开的方法,那么也就意味着这个原生类型是为了被外部调用而设。

  通过检索发现,getRawType()方法重点被调用的地方在TypeHandlerRegistry(类型处理器注册器)中,在没有指定JavaType而只有TypeHandler的情况下,调用该TypeHandler的getRawType()方法来获取其原生类型(即参数类型)来作为其JavaType来进行类型处理器的注册。

2.4 基础类型处理器:BaseTypeHandler

  BaseTypeHandler继承了TypeReference抽象类,实现了TypeHandler接口,它本身仍然是抽象类,在它内部简单的实现了TypeHandler接口中定义的四个方法中的部分功能,所谓部分功能是指只实现了所有类型处理器公共部分,具体的不同处理部分则还是交由具体的类型处理器来自己实现,所有它内部再次定义了四个抽象类,用来指导具体类型处理器的实现。

  BaseTypeHandler中主要对设置参数与获取返回结果时数据位null的情况进行了处理,具体的参数设置方法与结果获取方法都是由具体的类型处理器来实现的。

  1. //非NULL情况,怎么设参数还得交给不同的子类完成
  2. public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  3.  
  4. //以下3个方法是取得可能为null的结果,具体交给子类完成
  5. public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
  6.  
  7. public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
  8.  
  9. public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

  上面的四个方法就是BaseTypeHandler中定义的抽象方法。MyBatis内置的类型处理器几乎都是通过继承实现上面的四个方法来完成最终定义的。

2.5 类型处理器:StringTypeHandler

  我们看个简单的例子来理解一下这个过程。下面是字符串类型处理器:StringTypeHandler的源码

  1. package org.apache.ibatis.type;
  2.  
  3. import java.sql.CallableStatement;
  4. import java.sql.PreparedStatement;
  5. import java.sql.ResultSet;
  6. import java.sql.SQLException;
  7. /**
  8. * String类型处理器
  9. * 调用PreparedStatement.setString, ResultSet.getString, CallableStatement.getString
  10. */
  11. public class StringTypeHandler extends BaseTypeHandler<String> {
  12.  
  13. @Override
  14. public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
  15. throws SQLException {
  16. ps.setString(i, parameter);
  17. }
  18.  
  19. @Override
  20. public String getNullableResult(ResultSet rs, String columnName)
  21. throws SQLException {
  22. return rs.getString(columnName);
  23. }
  24.  
  25. @Override
  26. public String getNullableResult(ResultSet rs, int columnIndex)
  27. throws SQLException {
  28. return rs.getString(columnIndex);
  29. }
  30.  
  31. @Override
  32. public String getNullableResult(CallableStatement cs, int columnIndex)
  33. throws SQLException {
  34. return cs.getString(columnIndex);
  35. }
  36. }

  上面的源码完美的诠释了之前的解析,具体的类型处理器中只需要实现这四个方法即可,前提是其继承了BaseTypeHandler抽象类。

  其中设置参数的方法中具体的实现调用了PreparedStatement的setString()方法,这个是我们很熟悉的方法。同样的,在获取结果的方法中也是通过调用ResultSet的getString()方法,和CallableStatement的getString()方法来完成具体的功能。这已经是MyBatis中最为底层的逻辑了,因为它直接调用了JDK API来实现功能。

2.6  未知类型处理器:UnknownTypeHandler

  这个是MyBatis中定义的一个较为特殊的类型处理器,虽然其内部实现和普通的类型处理器如出一辙,但是它拥有一些特殊的地方,所以单独拿出来说一说。

  通过类型处理器注册器中的注册信息可以看出这种类型处理器所对应的JavaType是Object类型,对应的JdbcType是OTHER类型,这个OTHER是什么类型?我们可以这么理解,市面上数据库种类繁多,而且各有特点,这些数据库产品即满足SQL规范,同时也有各自的扩展和强化,每个数据库内部都有一些自定义的只在其内部起作用的数据类型,而这些类型反映到Java中之后是Object类型时,这里就将其统一定义为OTHER类型。

  1. private static final ObjectTypeHandler OBJECT_TYPE_HANDLER = new ObjectTypeHandler();
  2.  
  3. private TypeHandlerRegistry typeHandlerRegistry;
  4.  
  5. public UnknownTypeHandler(TypeHandlerRegistry typeHandlerRegistry) {
  6. this.typeHandlerRegistry = typeHandlerRegistry;
  7. }
  8.  
  9. @Override
  10. public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
  11. throws SQLException {
  12. TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
  13. handler.setParameter(ps, i, parameter, jdbcType);
  14. }
  15.  
  16. @Override
  17. public Object getNullableResult(ResultSet rs, String columnName)
  18. throws SQLException {
  19. TypeHandler<?> handler = resolveTypeHandler(rs, columnName);
  20. return handler.getResult(rs, columnName);
  21. }
  22.  
  23. @Override
  24. public Object getNullableResult(ResultSet rs, int columnIndex)
  25. throws SQLException {
  26. TypeHandler<?> handler = resolveTypeHandler(rs.getMetaData(), columnIndex);
  27. if (handler == null || handler instanceof UnknownTypeHandler) {
  28. handler = OBJECT_TYPE_HANDLER;
  29. }
  30. return handler.getResult(rs, columnIndex);
  31. }
  32.  
  33. @Override
  34. public Object getNullableResult(CallableStatement cs, int columnIndex)
  35. throws SQLException {
  36. return cs.getObject(columnIndex);
  37. }

  源码分析:在UnknownTypeHandler中的四个方法中,除针对存储过程结果取数据的情况之外,其余三个方法的实现均类似,都是先通过不同的resolveTypeHandler()方法来获取具体的TypeHandler,然后调用具体TypeHandler的对应方法来完成功能。那么UnknownTypeHandler中的重点就集中在这三个resolveTypeHandler()方法中了。

  1. private TypeHandler<? extends Object> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
  2. TypeHandler<? extends Object> handler;
  3. if (parameter == null) {
  4. handler = OBJECT_TYPE_HANDLER;
  5. } else {
  6. handler = typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType);
  7. // check if handler is null (issue #270)
  8. if (handler == null || handler instanceof UnknownTypeHandler) {
  9. handler = OBJECT_TYPE_HANDLER;
  10. }
  11. }
  12. return handler;
  13. }
  14.  
  15. private TypeHandler<?> resolveTypeHandler(ResultSet rs, String column) {
  16. try {
  17. Map<String,Integer> columnIndexLookup;
  18. columnIndexLookup = new HashMap<String,Integer>();
  19. ResultSetMetaData rsmd = rs.getMetaData();
  20. int count = rsmd.getColumnCount();
  21. for (int i=1; i <= count; i++) {
  22. String name = rsmd.getColumnName(i);
  23. columnIndexLookup.put(name,i);
  24. }
  25. Integer columnIndex = columnIndexLookup.get(column);
  26. TypeHandler<?> handler = null;
  27. if (columnIndex != null) {
  28. handler = resolveTypeHandler(rsmd, columnIndex);
  29. }
  30. if (handler == null || handler instanceof UnknownTypeHandler) {
  31. handler = OBJECT_TYPE_HANDLER;
  32. }
  33. return handler;
  34. } catch (SQLException e) {
  35. throw new TypeException("Error determining JDBC type for column " + column + ". Cause: " + e, e);
  36. }
  37. }
  38.  
  39. private TypeHandler<?> resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex) throws SQLException {
  40. TypeHandler<?> handler = null;
  41. JdbcType jdbcType = safeGetJdbcTypeForColumn(rsmd, columnIndex);
  42. Class<?> javaType = safeGetClassForColumn(rsmd, columnIndex);
  43. if (javaType != null && jdbcType != null) {
  44. handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
  45. } else if (javaType != null) {
  46. handler = typeHandlerRegistry.getTypeHandler(javaType);
  47. } else if (jdbcType != null) {
  48. handler = typeHandlerRegistry.getTypeHandler(jdbcType);
  49. }
  50. return handler;
  51. }
  52.  
  53. private JdbcType safeGetJdbcTypeForColumn(ResultSetMetaData rsmd, Integer columnIndex) {
  54. try {
  55. return JdbcType.forCode(rsmd.getColumnType(columnIndex));
  56. } catch (Exception e) {
  57. return null;
  58. }
  59. }
  60.  
  61. private Class<?> safeGetClassForColumn(ResultSetMetaData rsmd, Integer columnIndex) {
  62. try {
  63. return Resources.classForName(rsmd.getColumnClassName(columnIndex));
  64. } catch (Exception e) {
  65. return null;
  66. }
  67. }

  第一个resolveTypeHandler方法是由设置参数的方法调用的,目的在于获取真正的TypeHandler来进行类型处理。如果其参数parameter为null,那么直接将TypeHandler设定为ObjectTypeHandler,如果parameter不为null,则直接从类型处理器注册器中获取对应JavaType与JdbcType的类型处理器,这里存在一个#270BUG,针对无法再类型处理器注册器中获取TypeHandler获取获取到的是UnknownTypeHandler的情况进行再次处理:赋值ObjectTypeHandler。

  第二个resolveTypeHandler方法是被通过列名来获取结果数据的方法所调用的,目的同上。首先通过结果集原数据将结果集中的数据循环存放到一个HashMap集合中(以列名为键,列下标为值),然后从中获取给定列名的下标值,如果集合中存在该列名(即能获取到列下标),则调用第三个resolveTypeHandler()方法通过列下标方式来获取具体TypeHandler。当然如果不存在这个列名(亦即获取不到列下标),则直接赋值ObjectTypeHandler。

  第三个resolveTypeHandler方法是被通过列下标来获取结果数据的方法所调用的,同时也被第二个resolveTypeHandler方法所调用。分别通过safeGetJdbcTypeForColumn()方法和safeGetClassForColumn()方法来获取列下标所对应数据的JdbcType与JavaType,然后针对获取到的JdbcType和JavaType来从类型处理器注册器中获取具体的类型处理器。这里分三种情况来获取:jdbcType与JavaType均不为null的情况、只有JavaType不为null的情况和只有JdbcType不为null的情况,三者情况分别调用三种getTypeHandler()方法来完成获取功能。

  总结:由此可见UnknownTypeHandler是一种中间类型处理器,或者叫代理类型处理器,因为它本身并不会真正实现处理功能,它只是通过获取对应的类型处理器来调用其处理功能来完成功能。

3、自定义类型处理器

  有关自定义类型处理器,我们只做简单介绍,其实它也很是简单,我们只要继承BaseTypeHandler<T>抽象类即可,实现其中的四个方法。我们这里举个简单的例子,假如说MyBatis内置的StringTypeHandler无法满足我们的需求,我们可以对其进行扩展自定义,我们自定义一个新的字符串类型处理器:MyStringTypeHandler,代码如下:

  1. package org.apache.ibatis.type;
  2.  
  3. import java.sql.CallableStatement;
  4. import java.sql.PreparedStatement;
  5. import java.sql.ResultSet;
  6. import java.sql.SQLException;
  7.  
  8. public class MyStringTypeHandler extends BaseTypeHandler<String> {
  9.  
  10. @Override
  11. public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
  12. throws SQLException {
  13. System.out.println("新的逻辑");
  14. ps.setString(i, parameter);
  15. System.out.println("新的逻辑");
  16. }
  17.  
  18. @Override
  19. public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
  20. System.out.println("新的逻辑");
  21. return rs.getString(columnName);
  22. }
  23.  
  24. @Override
  25. public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
  26. System.out.println("新的逻辑");
  27. return rs.getString(columnIndex);
  28. }
  29.  
  30. @Override
  31. public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
  32. System.out.println("新的逻辑");
  33. return cs.getString(columnIndex);
  34. }
  35.  
  36. }

  定义好类型处理器之后,然后我们需要的就是将自定义的类型处理器注册到TypeHandlerRegistry中,方法也简单。

  1. <typeHandlers>
  2. <typeHandler handler="org.apache.ibatis.type.MyStringTypeHandler"/>
  3. </typeHandlers>

  当然我们也可以指定JavaType与jdbcType,获取直接使用package方式进行设置,但是如果你只是自定义了很少的类型处理器,没有必要采用package方式设置,因为这种方式会扫描整个包下的类,无形中造成了时延。

  然后这个新的类型处理器就会添加到TypeHandlerRegistry中了,它会在背后默默实现功能。

4、总结

  至此我们将Type模块解析完毕,说的很是粗糙,但这是自己学习提高的过程,特此记录,期待下一篇。

浩哥解析MyBatis源码(十)——Type类型模块之类型处理器的更多相关文章

  1. 浩哥解析MyBatis源码(八)——Type类型模块之TypeAliasRegistry(类型别名注册器)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6705769.html 1.回顾 前面几篇讲了数据源模块,这和之前的事务模块都是enviro ...

  2. 浩哥解析MyBatis源码(十二)——binding绑定模块之MapperRegisty

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6758456.html 1.回顾 之前解析了解析模块parsing,其实所谓的解析模块就是为 ...

  3. 浩哥解析MyBatis源码(九)——Type类型模块之类型处理器注册器(TypeHandlerRegistry)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6709157.html 1.回顾 上一篇研究的是类型别名注册器TypeAliasRegist ...

  4. 浩哥解析MyBatis源码(二)——Environment环境

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6625612.html 本应该先开始说Configuration配置类的,但是这个类有点过于 ...

  5. 浩哥解析MyBatis源码(四)——DataSource数据源模块

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6634880.html 1.回顾 上一文中解读了MyBatis中的事务模块,其实事务操作无非 ...

  6. 浩哥解析MyBatis源码(五)——DataSource数据源模块之非池型数据源

    1 回顾 上一篇中我解说了数据源接口DataSource与数据源工厂接口DataSourceFactory,这二者是MyBatis数据源模块的基础,包括本文中的非池型非池型数据源(UnpooledDa ...

  7. 浩哥解析MyBatis源码(六)——DataSource数据源模块之池型数据源

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6675674.html 1 回顾 上一文中解读了MyBatis中非池型数据源的源码,非池型也 ...

  8. 浩哥解析MyBatis源码(十一)——Parsing解析模块之通用标记解析器(GenericTokenParser)与标记处理器(TokenHandler)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6724223.html 1.回顾 上面的几篇解析了类型模块,在MyBatis中类型模块包含的 ...

  9. 浩哥解析MyBatis源码(七)——DataSource数据源模块之托管数据源

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6675700.html 1 回顾 之前介绍的非池型与池型数据源都是MyBatis自己定义的内 ...

随机推荐

  1. SqlService性能检测和优化工具

    工具概要 如果你的数据库应用系统中,存在有大量表,视图,索引,触发器,函数,存储过程,sql语句等等,又性能低下,而苦逼的你又要对其优化,那么你该怎么办?哥教你,首先你要知道问题出在哪里?如果想知道问 ...

  2. 强化学习读书笔记 - 06~07 - 时序差分学习(Temporal-Difference Learning)

    强化学习读书笔记 - 06~07 - 时序差分学习(Temporal-Difference Learning) 学习笔记: Reinforcement Learning: An Introductio ...

  3. HttpClient filter中间转发从A tomcat转发至B tomcat

    BackFilter.java 主要解决基于HttpGet/HttpPost以及基于HttpPost的附件流转发import java.io.IOException; import java.io.I ...

  4. 自定义cell设置现价,原价(加横线)

    原价,现价分别是连个label.这两个label不能直接限制死他们的宽度,因为他们的宽度不确定,而由于lable的特殊性,不设置它的宽度约束时,宽度取决于文字的内容,所以两个lable的约束设置好一些 ...

  5. 每天一个Linux命令 6

    rpm包管理--yum在线管理 ip地址配置和网络yum源ip地址配置 #setup 使用setup工具 #vi /etc/sysconfig/network-scripts/ifcfg-eth0把O ...

  6. 100本最棒的web前端图书推荐

    前端技术,要学习的内容太多了,当你不知道从哪里开始的时候,你就先从看书开始,边看书边码代码,这个是学习编程必须的过程,因为你看一百遍,还不如自己写一遍,写一遍,第一可以加印象,第二便于更好的理解. 熟 ...

  7. KVC与KVO理解

    转载:https://magicalboy.com/kvc_and_kvo/ KVC 与 KVO 理解 KVC 与 KVO 是 Objective C 的关键概念,个人认为必须理解的东西,下面是实例讲 ...

  8. 分享几个不错的Android开源音视频播放器

    整理了一下Github上几个开源的音视频播放器项目,有兴趣的同学可以clone代码去研究学习.   UniversalMusicPlayer https://github.com/googlesamp ...

  9. 【一】Swift 3.0 新浪微博项目实战 -整体框架搭建

    最近要接手swift,所以找了个视频跟着做一下实战项目,在此记录一下过程和心得 框架搭建和目录拆分 关键词:MVVM 架构,桥接文件 桥接文件用于引入OC的头文件,Swift就可以正常使用(宏除外). ...

  10. linux 私房菜 CH8 linux 磁盘与文件系统管理

    索引式文件系统 superblock 记录此系统的整体信息,包括 inode/block 的总量.使用量.剩余量,以及文件系统的格式与相关信息等: inode 记录档案的属性,一个档案占用一个 ino ...