这个作业属于哪个课程 | 软件工程-23年春季学期 |
---|---|
这个作业的要求在哪里 | 软件工程实践第二次作业—文件读取 |
这个作业的目标 | 完成对澳大利亚网球公开赛相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序 |
其他参考文献 | 《构建之法》 |
GitCode项目地址
仓库地址
PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 40 |
Estimate | • 估计这个任务需要多少时间 | 20 | 20 |
Development | 开发 | 980 | 1080 |
• Analysis | • 需求分析 (包括学习新技术) | 380 | 400 |
• Design Spec | • 生成设计文档 | 30 | 40 |
• Design Review | • 设计复审 | 20 | 20 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 30 | 20 |
• Design | • 具体设计 | 60 | 100 |
• Coding | • 具体编码 | 240 | 280 |
• Code Review | • 代码复审 | 120 | 100 |
• Test | • 测试(自我测试,修改代码,提交修改) | 160 | 180 |
Reporting | 报告 | 60 | 60 |
• Test Repor | • 测试报告 | 60 | 60 |
• Size Measurement | • 计算工作量 | 20 | 20 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 40 | 50 |
合计 | 2240 | 2470 |
解题思路描述
获取JSON数据
解析JSON数据
获取players的数据
获取results的数据
接口设计和实现过程
Java中有三个解析JSON的主流类库:FastJSON、Gson和Jackson,这里采用FastJSON来对JSON数据进行解析,三个类库的性能对比详见三个主流的JSON解析库对比
接口设计
程序流程
关键代码展示
main()方法 ---- 接收指令并判断指令正误,执行对应指令
public static void main(String[] args) {
//创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder().initialCapacity(5).maximumSize(19).expireAfterWrite(17, TimeUnit.SECONDS).build();
//命令行输入指令
if (args.length != 2) {
try {
throw new Exception("输入指令格式有误");
} catch (Exception e) {
e.printStackTrace();
return; //指令错误,终止程序
}
}
String inputFile = args[0]; //输入文件
String outputFile = args[1]; //输出文件
//清空原文件的内容
File file = new File(outputFile);
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(file); //文件不存在时则会自动创建
fileWriter.write(""); //清空
} catch (IOException e) {
e.printStackTrace();
}
//分析指令
List<String> cmd = null;
try {
cmd = Lib.getInput(inputFile);
} catch (Exception e) {
e.printStackTrace();
return; //输入文件不存在,终止程序
}
//解析每行指令
for (String s : cmd) {
if (s.isEmpty()) { //空行直接跳过不用处理
continue;
}
s = s.trim(); //清空指令前后的空格
if ("players".equals(s)) { //players指令
String playersInfo;
if (cache.getIfPresent(s) == null) { //如果缓存中没有这条指令则将其存入缓存
playersInfo = Lib.showPlayers(Lib.readJsonFile(Lib.PLAYERS_JSON));
cache.put(s, playersInfo);
} else { //如果缓存中存在这条指令则从缓存中获取
playersInfo = cache.getIfPresent(s);
}
Lib.setOutput(outputFile, playersInfo);
} else { //下一轮筛选
String[] arr = s.split(" ");
if ("result".equals(arr[0])) { //输入了result
if (arr.length != 2) { //输入的指令个数不对
StringBuffer naMeg = new StringBuffer();
naMeg.append("N/A\n");
naMeg.append("-----\n");
Lib.setOutput(outputFile, naMeg.toString());
continue;
} else { //指令个数为2
String jsonFile = Lib.RESULT_JSON + arr[1] + ".json";
String resultsInfo;
//判断文件是否存在
File f = new File(jsonFile);
if (!f.exists()) { //如果文件不存在
StringBuffer naMeg = new StringBuffer();
naMeg.append("N/A\n");
naMeg.append("-----\n");
Lib.setOutput(outputFile, naMeg.toString());
continue;
}
//使用缓存优化读取JSON数据
if (cache.getIfPresent(s) == null) {
resultsInfo = Lib.showResult(Lib.readJsonFile(jsonFile));
cache.put(s, resultsInfo);
} else {
resultsInfo = cache.getIfPresent(s);
}
Lib.setOutput(outputFile, resultsInfo);
}
} else { //输入了错误的指令
StringBuffer errorMsg = new StringBuffer();
errorMsg.append("Error\n");
errorMsg.append("-----\n");
Lib.setOutput(outputFile, errorMsg.toString());
}
}
}
}
readJsonFile()方法 ---- 获取JSON文件数据并将其JSON数据转换为字符串
//读取Json文件为字符串 String jsonStr = readJsonFile(filePath);
public static String readJsonFile(String fileName) {
String jsonStr = "";
File jsonFile;
FileReader fileReader = null;
Reader reader = null;
StringBuffer sb;
try {
jsonFile = new File(fileName);
fileReader = new FileReader(jsonFile);
reader = new InputStreamReader(new FileInputStream(jsonFile), "utf-8");
int ch = 0;
sb = new StringBuffer();
while ((ch = reader.read()) != -1) {
sb.append((char) ch);
}
jsonStr = sb.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileReader.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return jsonStr;
}
showPlayers()方法 ---- 将players JSON字符串信息整理并返回
//输出players信息
public static String showPlayers(String jsonStr) {
//将Json字符串通过fastjson转为JSONObject对象
JSONObject jsonObject = JSONObject.parseObject(jsonStr);
JSONArray players = JSONArray.parseArray(jsonObject.getString("players"));
StringBuffer sb = new StringBuffer();
String full_name = null; //选手全名
String gender = null; //选手性别
String nationality = null; //国籍
for (int i = 0; i < players.size(); i++) {
JSONObject player = players.getJSONObject(i);
full_name = player.getString("full_name");
gender = player.getString("gender");
nationality = player.getJSONObject("nationality").getString("name");
sb.append("full_name:" + full_name + "\n");
sb.append("gender:" + (gender.equals("M") ? "male" : "female") + "\n");
sb.append("nationality:" + nationality + "\n");
sb.append("-----\n");
}
return sb.toString(); //返回整理后的字符串
}
getInput()方法 ---- 获取输入指令
//获取输入指令 String input = getInput(filePath);
public static List<String> getInput(String fileName) throws Exception {
File file = new File(fileName);
//若输入文件不存在则报错
if (!file.exists()) {
throw new Exception("输入文件不存在");
}
FileReader reader;
BufferedReader bufferedReader = null;
StringBuffer sb;
String str = null;
List<String> list = new ArrayList<>();
try {
file = new File(fileName);
reader = new FileReader(file);
bufferedReader = new BufferedReader(reader);
sb = new StringBuffer();
String s = "";
while ((s = bufferedReader.readLine()) != null) {
list.add(s); //指令字符串列表
sb.append(s + "\n");
}
str = sb.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return list;
}
setOutput()方法 ---- 将整理的数据写入文件
//进行输出 String output = setOutput(fileName, data);
public static void setOutput(String fileName, String data) {
File file = new File(fileName);
//若文件不存在则创建文件
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
Writer writer = null;
StringBuilder outputStr = new StringBuilder();
try {
outputStr.append(data); //结尾不换行
writer = new FileWriter(file, true); //追加
writer.write(outputStr.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
性能改进
使用缓存机制优化文件读写
通过将指令所对应的JSON数据存入缓存中可以减少JSON数据的读取时间
单元测试
测试命令行指令格式正误判断、input.txt文件中指令正误判断、对比输出结果是否正确
提高覆盖率要尽可能覆盖到程序中不同的分支结构,Lib类中有一些处理流异常的指令无法覆盖到,所以Lib类的覆盖率会相对低一点
异常处理
心得体会
通过这次的作业,我学会了如何使用Java去解析JSON数据。因为以前处理的JSON数据都是比较简洁比较好整理的,所以看到作业复杂的JSON数据感觉很乱,理不清数据的联系。后面通过请教同学,自己慢慢搞懂的数据的结构和逻辑,写起代码就变得容易一些了。
刚开始本来是使用Gson解析JSON,但后面发现使用FastJSON会方便一些,又把用Gson的地方改成FastJSON,但修改类库时报了很多错误,后面发现使用maven构建项目只需导入坐标即可,不用去下载jar包,又改成了maven的形式。测试的时候本来是使用JUnit4,但发现很多教程中的方法都是使用JUnit5,JUnit4和JUnit5使用起来有点区别,后面又改成了使用JUnit5。总之,一开始就得规划好要使用的工具,不然后面要改动代码的话就会牵一发而动全身。
总结,还是得不断学习,拓展自己的技术栈,这样遇到问题时才能更加得心应手。