服务器端json数据文件分割合并解决方案
问题引入
Json 是什么就不多说了,本文把Json理解成一种协议。
印象之中,Json貌似是前端的专属,其实不然,服务器端组织数据,依然可以用Json协议。
比如说,某公司有一套测评题目(基于Json协议),这些题目比较珍贵,不想直接放在js中,所以就将题目文件放在服务器端,然后通过一个接口去请求,多一层控制,就多了一层保护,通过在接口上加权限,可保证数据安全。
如此一来,服务器端必定会有一个Json文件(纯文本文件),Json文件中包含Json数据。
假设Json数据结构如下:
{
"name": "题库",
"items": [{
"name": "测评-1",
"items": [/*...*/]
},{
"name": "测评-2",
"items": [/*...*/]
},{
"name": "测评-3",
"items": [/*...*/]
}/*...*/]
}
暂不讨论这样设计的合理性,假定已经是这么设计了,无法再做更改。但凡是有些规模的项目,需求变动都比较频繁,项目工期也比较紧张,不得不做出妥协,完美的设计是不存在的。
随着时间和规模的增长,测评会越来越多,而且每个测评本身包含的数据也不少,这样一来,这个Json文件会越来越大。
众所周知,IO操作是一个巨大的瓶颈,如果Json文件太大,占用IO过多,将导致性能严重下降。同时Json文件太大,也不好管理,不符合开闭原则。
因此,我们迫切需要对Json文件进行拆分,把数据量大、独立性强、自成一体的Json数据转移到主体的外部,单独放在一个Json文件中,这样不仅缩小了单个文件的体积,也方便管理。
其实这样做最大的优点是可以实现懒加载,或者说是按需加载。
这样的情景很常见,比如在进行数据检索时,一般情况下,会先看到一个数据概要列表,列出几项重要信息,其他次要信息需要点击“详情”按钮时,才去加载。
拿上边测评的例子来说,第一步仅需显示出有哪些测评,然后根据用户的选择,再去加载对应测评的详细信息。没有必要一上来就把所有的信息都返回给客户端,不仅浪费资源,还降低了数据安全性。
如何才能实现Json文件的合并呢?请看下章~~~
解决方案:Jean
Jean是一个Java工具类,她可以实现Json文件合并、依赖管理,灵感来自于前端模块化开发。
这名字是怎么来的呢?前端模块化开发,国内比较厉害的就是Sea.js了,小菜要写的是Java工具类,要不就叫Jea?于是赶紧上网查查Jea有没有啥特殊含义,万一是敏感词就不好了。结果一查,查到了Jean,可翻译为“珍”,相当不错的名字嘛,就是她了!
Jean的思想是在Json文件中,加入一段特殊代码,来引入其他Json文件,有点像Jsp中的include。语法为:@Jean("family","./items/family.js")。可以把@Jean()理解成函数调用,里边有两个参数,第一个参数是属性名称,第二个参数是依赖文件的相对路径。
文章开篇测评的例子,可以写成这样:
{
"name": "题库",
"items": [{
"name": "测评-1",
@Jean("items","./items1/test.js")
},{
"name": "测评-2",
@Jean("items","./items2/test.js")
},{
@Jean("items","./items3/test.js"),
"name": "测评-3"
}/*...*/]
}
假设./items1/test.js中内容为:
{
name: "测评-1-内容"
}
由此可以看出,@Jean在Json文件中的写法,就和普通的属性写法一样,如果是写在最后边,末尾就不用加逗号,其他情况同样需要加逗号。
通过工具类解析之后,@Jean("items","./items1/test.js")会变成:"items": {name: "测评-1-内容"},替换之后,为了保证格式正确,所以写@Jean的时候需要按照正常的语法加逗号。
第一个参数,将会转换成@Jean占位符被替换后的Json属性名称,如果不写,默认为"jean"。
第二个参数是该属性依赖的Json文件的相对路径,当然是相对于当前Json文件的,Jean会根据当前Json文件的路径,找到依赖的Json文件,然后读取内容,再合并到当前Json文件中。目前小菜实现的Jean工具类,只能识别./和../两种相对路径语法(含义与HTML相对路径语法相同)。
所以,@Jean仅仅是一个占位符,包含有@Jean的Json字符串,必须经过Jean工具类处理之后,才是合法的Json字符串。同时,Jean仅仅关心依赖,而不关心依赖的组织形式,这样可以带来巨大的灵活性,无论怎样组织文件结构,最终体现到Jean的仅仅是一个相对路径而已。
Jean工具类提供了三个public方法:
/**
* 解析所有的jean表达式
* @param json json字符串
* @param jsonPath json字符串所在路径,完整路径
* @return 解析后的json字符串
*/
public static String parseAll(String json,String jsonPath){} /**
* 解析单个jean表达式
* @param express jean表达式
* @param jsonPath json字符串所在路径,完整路径
* @return 解析结果
*/
public static String parseOne(String express,String jsonPath){} /**
* 解析特定的jean表达式
* @param json json字符串
* @param jsonPath json字符串所在路径,完整路径
* @param names 需要解析的属性名称列表
* @return 解析后的json字符串
*/
public static String parseTarget(String json,String jsonPath,List<String> names){}
第一个方法就是说给我一个包含@Jean的Json字符串,再给我这个Json字符串所在文件的绝对路径,我就把所有的@Jean解析成依赖文件中的内容。
为啥非要单独传入一个绝对路径呢?其实可以直接传入Json文件的路径,这样既能拿到需要解析的Json字符串,又能获取当前Json文件的绝对路径。但这样有一个缺点,就是每调用一次,就要读一次文件,小菜单独把路径写成一个参数,就是要把读文件的过程留给用户,具体怎么读,由用户说了算,最终把需要解析的Json字符串和参照路径给我就可以了。例如:
String json = "{@Jean(\"item1\",\"./../../item.js\"),@Jean(\"item2\",\"../item.js\")}";
System.out.println(parseAll(json, "E:/root/json")); //print {"item1": {"name": "xxx1"},"item2": {"name": "xxx2"}}
第二个方法可以直接解析一个@Jean表达式,不多解释。例如:
String expression = "@Jean(\"item1\",\"./../../item.js\")";
System.out.println(parseOne(expression, "E:/root/json")); //print "item1": {"name": "xxx1"}
第三个方法可以解析指定的@Jean表达式,@Jean表达式第一个参数是属性名称,想解析哪个属性,就把它放在List<String>中,其他不做解析的,属性值为null。这样就实现了懒加载。例如:
List<String> names = new ArrayList<String>();
names.add("item1");
String json = "{@Jean(\"item1\",\"./../../item.js\"),@Jean(\"item2\",\"../item.js\")}";
System.out.println(parseTarget(json, "E:/root/json", names)); //print {"item1": {"name": "xxx"},"item2": null}
Jean源码
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern; /**
* json文件合并工具类
* @author 杨元
*/
public class Jean { /**
* 识别jean表达式
*/
private static Pattern jeanRegex = Pattern.compile("(@Jean\\((\"[^\"]*\",)?\"[^\"]*\"\\))");
/**
* 识别jean表达式中的所有参数
*/
private static Pattern paramRegex = Pattern.compile("\"([^\"]*)\"");
/**
* 识别jean表达式中的name参数
*/
private static Pattern nameRegex = Pattern.compile("\"([^\"]*)\",");
/**
* 默认属性名称
*/
private static String defaultName = "jean"; /**
* 解析所有的jean表达式
* @param json json字符串
* @param jsonPath json字符串所在路径,完整路径
* @return 解析后的json字符串
*/
public static String parseAll(String json,String jsonPath){
//识别jean表达式
List<String> jeans = regexMatchList(jeanRegex, json);
jeans = noRepeat(jeans); //解析
for(String jean : jeans){
json = json.replace(jean, parse(jean, jsonPath));
} return json;
} /**
* 解析单个jean表达式
* @param express jean表达式
* @param jsonPath json字符串所在路径,完整路径
* @return 解析结果
*/
public static String parseOne(String express,String jsonPath){
return parse(express, jsonPath);
} /**
* 解析特定的jean表达式
* @param json json字符串
* @param jsonPath json字符串所在路径,完整路径
* @param names 需要解析的属性名称列表
* @return 解析后的json字符串
*/
public static String parseTarget(String json,String jsonPath,List<String> names){
//识别jean表达式
List<String> jeans = regexMatchList(jeanRegex, json);
jeans = noRepeat(jeans);
//处理属性名映射
Map<String, Boolean> nameMap = new HashMap<String, Boolean>();
for(String s : names){
nameMap.put(s, true);
} //解析
String replacement = "";
Matcher matcher = null;
String name = "";
for(String jean : jeans){
matcher = nameRegex.matcher(jean); //判断是否传入属性名称
if(matcher.find()){
name = matcher.group(1);
//判断是否需要解析
if(nameMap.get(name) != null){
replacement = parse(jean, jsonPath);
}else{
//不需要解析直接将属性值写为null
replacement = "\""+name+"\": null";
}
}else{
//无属性名直接用默认的jean
replacement = "\""+defaultName+"\": null";
} json = json.replace(jean, replacement);
} return json;
} /**
* 解析jean表达式
* @param express jean表达式
* @param jsonPath json文件所在路径,完整路径
* @return jean表达式执行结果
*/
private static String parse(String express,String jsonPath){
//识别参数
List<String> params = regexMatchList(paramRegex, express);
//默认属性名称
String name = defaultName;
//格式化路径
jsonPath = removeSuffix(jsonPath, "/"); //判断是否传入了属性名称
if(params.size() > 1){
name = params.get(0);
} //解析路径
String path = getAbsolutePath(jsonPath, params.get(params.size()-1)); //读取内容并返回
name = wrapWith(name, "\"");
return name + ": " + readJsonFile(path);
} /**
* 从字符串中移除指定后缀
* @param source 源字符串
* @param suffix 需要移除的后缀
* @return 处理后的源字符串
*/
private static String removeSuffix(String source,String suffix){
if(source.endsWith(suffix)){
source = source.substring(0, source.length()-suffix.length());
} return source;
} /**
* list内容去重
* @param list 内容为string的list
* @return 内容去重后的list
*/
private static List<String> noRepeat(List<String> list){
Map<String, String> map = new HashMap<String, String>();
List<String> result = new ArrayList<String>(); for(String s : list){
map.put(s, null);
} for(String s : map.keySet()){
result.add(s);
} return result;
} /**
* 用指定的字符串包裹内容
* @param content 内容
* @param wrap 包裹字符串
* @return 包裹后的内容
*/
private static String wrapWith(String content,String wrap){
return wrap+content+wrap;
} /**
* 读取Json文件(纯文本文件,utf-8编码)
* 这个方法可以替换成自己项目中封装的方法
* @param path 文件路径
* @return 文件内容
*/
private static String readJsonFile(String path){
String encoding = "utf-8";
StringBuilder sb = new StringBuilder(256); File file = new File(path);
InputStreamReader iReader = null;
BufferedReader bReader = null; try{
iReader = new InputStreamReader(new FileInputStream(file), encoding);
bReader = new BufferedReader(iReader);
String line = null; while((line = bReader.readLine()) != null){
sb.append(line.trim());
} bReader.close();
iReader.close(); }catch(Exception e){
if(iReader != null){
try {
iReader.close();
} catch (IOException e1) {
iReader = null;
}
}
if(bReader != null){
try {
bReader.close();
} catch (IOException e1) {
bReader = null;
}
}
} return sb.toString();
} /**
* 将相对路径转换成绝对路径
* 只识别 ./ ../
* @param refrence 基准参照路径
* @param relative 相对路径表达式
* @return 绝对路径
*/
private static String getAbsolutePath(String refrence,String relative){
if(relative.startsWith("./")){
refrence = getAbsolutePath(refrence, relative.replaceFirst("\\./", ""));
}else if(relative.startsWith("../")){
refrence = getAbsolutePath(refrence.substring(0, refrence.lastIndexOf("/")),
relative.replaceFirst("\\.\\./", ""));
}else{
refrence = refrence + "/" + relative;
} return refrence;
} /**
* 将正则表达式的匹配结果转换成列表
* @param regex 正则表达式对象
* @param input 要检索的字符串
* @return 结果列表
*/
private static List<String> regexMatchList(Pattern regex,String input){
List<String> result = new ArrayList<String>();
Matcher matcher = regex.matcher(input);
while(matcher.find()){
result.add(matcher.group(1));
} return result;
} }
其他
欢迎留言,共同探讨!
服务器端json数据文件分割合并解决方案的更多相关文章
- 文件分割合并DOS版
这个从163邮箱里翻出来的程序,2004年的修改日期,放这另存一下. 当时拿了一本C++的书来学,学了一阵就琢磨着做一个东东,然后就想起一个以前印象深刻的软件,叫做笨笨狗分割器. 当时主要还是靠3.5 ...
- 【shell】数据文件分割
有时候我们必须把数据文件分割为更小的文件,这样方便我们邮件发送或者查看文件内容.split命令则可以用来分割文件. 一.根据大小来分割文件 1.一般分割 例如:现在有文件tmp.log,大小为:368 ...
- SpringMVC——返回JSON数据&&文件上传下载
--------------------------------------------返回JSON数据------------------------------------------------ ...
- ios开发值json数据文件的存取
将Json存进本地文件夹 NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainM ...
- ios开发 json数据文件的存取
将Json存进本地文件夹 NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomai ...
- [IOS]UIWebView实现保存页面和读取服务器端json数据
如何通过viewView保存访问过的页面?和如何获取并解析服务器端发送过来的json数据?通过一个简单的Demo来学习一下吧! 操作步骤: 1.创建SingleViewApplication应用,新建 ...
- vs中web配置可浏览json数据文件
在日常的前端开发中,我们会制作一些数据文件,常用的有后缀名为json的文件,但是vs在启动项目时,是不能浏览json文件的,常见的错误提示如下图所示 解决这个错误,只需要在web.config文件中配 ...
- Logstash学习之路(二)Elasticsearch导入json数据文件
一.数据从文件导入elasticsearch 1.数据准备: 1.数据文件:test.json 2.索引名称:index 3.数据类型:doc 4.批量操作API:bulk {"index& ...
- Spark1.6.2 java实现读取json数据文件插入MySql数据库
public class Main implements Serializable { /** * */ private static final long serialVersionUID = -8 ...
随机推荐
- 8.9 CSS知识点2
4.关系选择符 包含选择符(Descendant combinator) E F 选择所有被E元素包含的F元素 <style type="text/css"> h1 ...
- file access , argc, argv[ ]
_____main函数含有 两个参数 ,argc ,argv[] 这两个参数用以指示命令行输入的参数信息. argc 的值是输入的参数的数量.argv是一个数组,每个数组元素指向一个string字符串 ...
- Delphi关于记录文件的操作
http://www.cnblogs.com/railgunman/archive/2010/08/16/1801004.html Delphi关于记录文件的操作 本例子几个变量的说明TFileR ...
- 9.springMVC中的拦截器
springMVC中的拦截器大概大致可以分为以下几个步骤去学习: 1.自定义一个类实现HandlerInterceptor接口,这里要了解其中几个方法的作用 2.在springMVC的配置文件中添加拦 ...
- 性能检测工具介绍-Linux系统命令行
本文介绍的关于Linux自带命令进行性能检测的介绍,详细介绍这些linux自带的工具的使用. 一.uptime uptime命令的显示结果包括服务器已经运行了多长时间,有多少登陆用户和对服务器性能的总 ...
- jquery中append跟prepend的用法
jquery中append和prepend的用法 append 是插入到元素中,并放到元素内的最后面prepend 是插入到元素中,并放到元素内的最前面例$("body"). ...
- MySQL里的wait_timeout
如果你没有修改过MySQL的配置,缺省情况下,wait_timeout的初始值是28800. wait_timeout过大有弊端,其体现就是MySQL里大量的SLEEP进程无法及时释放,拖累系统性能, ...
- Cadence16.6安装破解
1.软件安装 1.运行stepup.exe.出现下面界面后开始安装License manager和project installation. 注意:只安装第一项License manager和第二项p ...
- iOS10适配及Xcode8配置
一.证书管理 用Xcode8打开工程后,比较明显的就是下图了,这个是苹果的新特性,可以帮助我们自动管理证书.建议大家勾选这个Automatically manage signing(Ps.但是在bea ...
- Ubuntu终端Terminal常用快捷键
快捷键 功能 Tab 自动补全 Ctrl+a 光标移动到开始位置 Ctrl+e 光标移动到最末尾 Ctrl+k 删除此处至末尾的所有内容 Ctrl+u 删除此处至开始的所有内容 Ctrl+d 删除当前 ...