json性能优化建议

       bean转换为json时,使用了反射获取,性能较差。如果数据量稍大一点,转换时的性能是个问题。

       希望后续版本加个以空间换性能的方法。

       简单修改了一下系统的json转换的代码,测试了性能差距,真的差很多。代码和测试数据如下:

这是修改后的代码:

/**
 * Json 转换 JFinal 实现.
 * 
 * json 到 java 类型转换规则:
 * string			java.lang.String
 * number			java.lang.Number
 * true|false		java.lang.Boolean
 * null				null
 * array			java.util.List
 * object			java.util.Map
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public class JFinalJson extends Json {
	private final static  Map<String,List<MethodInfo>> beanGetMethodCache = new HashMap<String,List<MethodInfo>>();
	private static int defaultConvertDepth = 15;
	private boolean useBeanCache = true;  //是否使用缓存,如果设置为false,不使用缓存,每次均反射获取bean的get方法
	protected int convertDepth = defaultConvertDepth;
	protected String timestampPattern = "yyyy-MM-dd HH:mm:ss";
	// protected String datePattern = "yyyy-MM-dd";
	
	/**
	 * 设置全局性默认转换深度
	 */
	public static void setDefaultConvertDepth(int defaultConvertDepth) {
		if (defaultConvertDepth < 2) {
			throw new IllegalArgumentException("defaultConvertDepth depth can not less than 2.");
		}
		JFinalJson.defaultConvertDepth = defaultConvertDepth;
	}
	
	public JFinalJson setConvertDepth(int convertDepth) {
		if (convertDepth < 2) {
			throw new IllegalArgumentException("convert depth can not less than 2.");
		}
		this.convertDepth = convertDepth;
		return this;
	}
	
	public JFinalJson setTimestampPattern(String timestampPattern) {
		if (StringUtils.isBlank(timestampPattern)) {
			throw new IllegalArgumentException("timestampPattern can not be blank.");
		}
		this.timestampPattern = timestampPattern;
		return this;
	}
	
	public Json setDatePattern(String datePattern) {
		if (StringUtils.isBlank(datePattern)) {
			throw new IllegalArgumentException("datePattern can not be blank.");
		}
		this.datePattern = datePattern;
		return this;
	}
	
	public static JFinalJson getJson() {
		return new JFinalJson();
	}
	
	protected String mapToJson(Map map, int depth) {
		if(map == null) {
			return "null";
		}
        StringBuilder sb = new StringBuilder();
        boolean first = true;
		Iterator iter = map.entrySet().iterator();
		
        sb.append('{');
		while(iter.hasNext()){
            if(first)
                first = false;
            else
                sb.append(',');
            
			Map.Entry entry = (Map.Entry)iter.next();
			toKeyValue(String.valueOf(entry.getKey()),entry.getValue(), sb, depth);
		}
        sb.append('}');
		return sb.toString();
	}
	
	protected void toKeyValue(String key, Object value, StringBuilder sb, int depth){
		sb.append('\"');
        if(key == null)
            sb.append("null");
        else
            escape(key, sb);
		sb.append('\"').append(':');
		
		sb.append(toJson(value, depth));
	}
	
	protected String iteratorToJson(Iterator iter, int depth) {
        boolean first = true;
        StringBuilder sb = new StringBuilder();
        
        sb.append('[');
		while(iter.hasNext()){
            if(first)
                first = false;
            else
                sb.append(',');
            
			Object value = iter.next();
			if(value == null){
				sb.append("null");
				continue;
			}
			sb.append(toJson(value, depth));
		}
        sb.append(']');
		return sb.toString();
	}
	
	/**
	 * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F).
	 */
	protected String escape(String s) {
		if(s == null)
			return null;
        StringBuilder sb = new StringBuilder();
        escape(s, sb);
        return sb.toString();
    }
	
	protected void escape(String s, StringBuilder sb) {
		for(int i=0; i<s.length(); i++){
			char ch = s.charAt(i);
			switch(ch){
			case '"':
				sb.append("\\\"");
				break;
			case '\\':
				sb.append("\\\\");
				break;
			case '\b':
				sb.append("\\b");
				break;
			case '\f':
				sb.append("\\f");
				break;
			case '\n':
				sb.append("\\n");
				break;
			case '\r':
				sb.append("\\r");
				break;
			case '\t':
				sb.append("\\t");
				break;
			//case '/':
			//	sb.append("\\/");
			//	break;
			default:
				if((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) {
					String str = Integer.toHexString(ch);
					sb.append("\\u");
					for(int k=0; k<4-str.length(); k++) {
						sb.append('0');
					}
					sb.append(str.toUpperCase());
				}
				else{
					sb.append(ch);
				}
			}
		}
	}
	
	public String toJson(Object object) {
		return toJson(object, convertDepth);
	}
	
	protected String toJson(Object value, int depth) {
		if(value == null || (depth--) < 0)
			return "null";
		
		if(value instanceof String)
			return "\"" + escape((String)value) + "\"";
		
		if(value instanceof Double){
			if(((Double)value).isInfinite() || ((Double)value).isNaN())
				return "null";
			else
				return value.toString();
		}
		
		if(value instanceof Float){
			if(((Float)value).isInfinite() || ((Float)value).isNaN())
				return "null";
			else
				return value.toString();
		}
		
		if(value instanceof Number)
			return value.toString();
		
		if(value instanceof Boolean)
			return value.toString();
		
		if (value instanceof java.util.Date) {
			if (value instanceof java.sql.Timestamp) {
				return "\"" + new SimpleDateFormat(timestampPattern).format(value) + "\"";
			}
			if (value instanceof java.sql.Time) {
				return "\"" + value.toString() + "\"";
			}
			// 优先使用对象级的属性 datePattern, 然后才是全局性的 defaultDatePattern
			String dp = datePattern != null ? datePattern : getDefaultDatePattern();
			if (dp != null) {
				return "\"" + new SimpleDateFormat(dp).format(value) + "\"";
			} else {
				return "" + ((java.util.Date)value).getTime();
			}
		}
		
		if(value instanceof Collection) {
			return iteratorToJson(((Collection)value).iterator(), depth);
		}
		
		if(value instanceof Map) {
			return mapToJson((Map)value, depth);
		}
		
		String result = otherToJson(value, depth);
		if (result != null)
			return result;
		
		// 类型无法处理时当作字符串处理,否则ajax调用返回时js无法解析
		// return value.toString();
		return "\"" + escape(value.toString()) + "\"";
	}
	
	protected String otherToJson(Object value, int depth) {
		if (value instanceof Character) {
			return "\"" + escape(value.toString()) + "\"";
		}
		
		/*if (value instanceof Model) {
			Map map = com.jfinal.plugin.activerecord.CPI.getAttrs((Model)value);
			return mapToJson(map, depth);
		}
		if (value instanceof Record) {
			Map map = ((Record)value).getColumns();
			return mapToJson(map, depth);
		}*/
		if (value.getClass().isArray()) {
			int len = Array.getLength(value);
			List<Object> list = new ArrayList<Object>(len);
			for (int i=0; i<len; i++) {
				list.add(Array.get(value, i));
			}
			return iteratorToJson(list.iterator(), depth);
		}
		if (value instanceof Iterator) {
			return iteratorToJson((Iterator)value, depth);
		}
		if (value instanceof Enumeration) {
			ArrayList<?> list = Collections.list((Enumeration<?>)value);
			return iteratorToJson(list.iterator(), depth);
		}
		if (value instanceof Enum) {
			return "\"" + ((Enum)value).toString() + "\"";
		}
		
		return beanToJson(value, depth);
	}
	
	protected String beanToJson(Object model, int depth) {	
		return mapToJson(beanToMap(model,depth), depth);
	}
	public Map beanToMap(Object model, int depth){	
		return getBeanValue(model,depth);
	}
	
	/**
	 * 获取bean的无参get方法(包括is),从静态缓存里面取,若没有会反射取一次并存入缓存
	 * 这东西应该写个反射工具类
	 * @param model
	 * @return
	 */
	public List<MethodInfo> getBeanGetMethodFromCache(Object model){	
		if(this.useBeanCache){
			List<MethodInfo> beanMethods = null;
			beanMethods = beanGetMethodCache.get(model.getClass().getName());
			if(beanMethods == null){
				synchronized (this.getClass()) {
					beanMethods = beanGetMethodCache.get(model.getClass().getName());
					if(beanMethods == null){						
						beanMethods = reflectBeanGetMethod(model);
						beanGetMethodCache.put(model.getClass().getName(), beanMethods);
					}
					return beanMethods;
				}
			}
			return beanMethods;
		}
		return reflectBeanGetMethod(model);
	}
	/**
	 * 反射获取bean的get方法(包括is)
	 * @param model
	 * @return
	 */
	public List<MethodInfo> reflectBeanGetMethod(Object model){
			Method[] ms = model.getClass().getMethods();
			List<MethodInfo> beanMethods = new ArrayList<MethodInfo>();
			for (Method m : ms) {
				String methodName = m.getName();
				int indexOfGet = methodName.indexOf("get");
				if (indexOfGet == 0 && methodName.length() > 3) {	// Only getter
					String attrName = methodName.substring(3);
					if (!attrName.equals("Class")) {				// Ignore Object.getClass()
						Class<?>[] types = m.getParameterTypes();
						if (types.length == 0) {
							beanMethods.add(new MethodInfo(attrName,m));
						}
					}
				}else{
					int indexOfIs = methodName.indexOf("is");
		               if (indexOfIs == 0 && methodName.length() > 2) {
		                  String attrName = methodName.substring(2);
		                  Class<?>[] types = m.getParameterTypes();
		                  if (types.length == 0) {
		                	  beanMethods.add(new MethodInfo(attrName,m));
		                  }
		               }
				
				}
			}
			return beanMethods;
	}
	
	/**
	 * 获取bean的无参的get和is方法的值map
	 * @param model
	 * @param depth
	 * @return
	 */
	public Map getBeanValue(Object model, int depth){
		Map map = new HashMap();
		List<MethodInfo> methods = getBeanGetMethodFromCache(model);
		for(int i = 0 ; i < methods.size(); i++){
			try {
				Object value = methods.get(i).getMethod().invoke(model);
				map.put(StrKit.firstCharToLowerCase(methods.get(i).getFieldName()), value);
			} catch (Exception e) {
				throw new RuntimeException(e.getMessage(), e);
			}
		}	
		return map;
	}
	
	
	public <T> T parse(String jsonString, Class<T> type) {
		throw new RuntimeException("默认 json 实现暂不支持 json 到 object 的转换");
	}

	public boolean isUseBeanCache() {
		return useBeanCache;
	}

	public void setUseBeanCache(boolean useBeanCache) {
		this.useBeanCache = useBeanCache;
	}
}

增加类
/**
 * 
 */
public class MethodInfo {
	private String fieldname;
	private Method method;
	public MethodInfo(String attrName, Method m) {
		this.fieldname = attrName;
		this.method = m;
	}
	public String getFieldName() {
		return fieldname;
	}
	public void setFieldName(String name) {
		this.fieldname = name;
	}
	public Method getMethod() {
		return method;
	}
	public void setMethod(Method method) {
		this.method = method;
	}
	
}


性能测试基本在1倍左右,若bean中有复杂的数据,性能差距会缩小。

评论区

logicman

2018-08-12 02:23

我还能改吗?这东西搞错了。

logicman

2018-08-12 03:14

怎么能删除上面的东西,实际逻辑写错了,实际测试数据是5000条数据以内,大约2倍的效率提升。

JFinal

2018-08-12 09:39

在这里改
http://www.jfinal.com/my/feedback

注意右侧的修改链接 回复删除

JFinal

2018-08-12 10:58

beanGetValueToMap 这个方法没有分享出来,里面怎么写的?

此外, JFinalJson 只有几百行代码,当初主要是为了在某些场景下对第三方无强制依赖而开发的,也没有做深入的性能优化,因此 json 模块里头还提供了 fastjson、jackson 两个第三方实现,但需要引入第三方 jar 包

logicman

2018-08-13 13:51

热门反馈

扫码入社