688
社区成员
发帖
与我相关
我的任务
分享| 这个作业属于哪个课程 | <2023年福大-软件工程实践-W班> |
|---|---|
| 这个作业要求在哪里 | <作业要求在这里> |
| 这个作业的目标 | 以Asutralian Open为背景建立一个赛事数据查询器,锻炼读取文件的能力,并将项目上传至GitCode,撰写相关文档,熟悉软件开发基本步骤 |
| 其他参考文献 | 参考较多,见文末 |
说明:本次实践使用Java语言编写
| PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 不确定 | 不确定 |
| • Estimate | • 估计这个任务需要多少时间 | 15 | 20 |
| Development | 开发 | 不确定 | 不确定 |
| • Analysis | • 需求分析 (包括学习新技术) | 60 | 90 |
| • Design Spec | • 生成设计文档 | 20 | 20 |
| • Design Review | • 设计复审 | 15 | 25 |
| • Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 10 |
| • Design | • 具体设计 | 20 | 30 |
| • Coding | • 具体编码 | 420 | 600 |
| • Code Review | • 代码复审 | 15 | 15 |
| • Test | • 测试(自我测试,修改代码,提交修改) | 170 | 220 |
| Reporting | 报告 | 150 | 180 |
| • Test Repor | • 测试报告 | 180 | 150 |
| • Size Measurement | • 计算工作量 | 15 | 15 |
| • Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 25 | 50 |
| 合计 | 1050 | 1425 |
A:首先进入Australian Open官网,将鼠标放在TOURNAMENT上,点击Results,按下F12进入开发者模式并刷新(向服务器发出请求):

然后点击XHR,并按大小排序,会发现有一个名为results的文件,这个就是我们需要的JSON文件:

双击查看,由文件路径可知,该文件为1月29日的JSON文件(14+16-1),如果我们需要其他日期的文件,可以通过修改路径上的数字来获得,如果我们需要赛季数据,可以通过把MD改成Q,day后的数字改成对应数字获得(1、2、3、4):

接着我们右键点击查看源代码,可以看到格式并不美观:

我们把源代码全部复制,使用JSON在线解析网站,可以很方便地把格式不规范的JSON文件转换为格式美观的JSON文件:

点击保存本地,修改文件名为对应日期即可。
A:通过使用阿里巴巴的fastjson第三方包,可以从JSON文件中提取想要的数据:
JSON对象字符串转为JSON对象
JSONObject jsonObj = JSON.parseObject(jsonStr);
JSON数组字符串转为JSON数组
JSONArray jsonArr = JSON.parseArray(jsonStr);
jsonArray.size();// 获取数组元素个数
List<TrackNodeDto> nodes = jsonArray.toJavaList(TrackNodeDto.class);//转化为特定的List
在程序中,利用fastjson提取JSON数据的代码如下:
//从Json字符串中获取节点数据并存入ArrayList
public void jsonToString(String json){
jsonToPlayerMap(json);
jsonToTeamMap(json);
if (json!=null) {
JSONObject jsonObject = JSON.parseObject(json);
JSONArray matches = jsonObject.getJSONArray("matches");
for (var tempActivity:matches){
JSONObject activity = (JSONObject)tempActivity;
String startTime = activity.getString("actual_start_time");
JSONArray teams = activity.getJSONArray("teams");
if (activity.getJSONObject("match_status") != null&&
activity.getJSONObject("match_status").getString("code").equals("W")){
dailySchedule.timeList.add("null");
dailySchedule.ScoreList.add("null");
dailySchedule.WinnerList.add("null");
continue;
}
if (teams == null)
continue;
JSONObject teamA = teams.getJSONObject(0);
JSONObject teamB = teams.getJSONObject(1);
String winnerTeamID = teamA.getString("status")==null?teamB.getString("team_id"):
teamA.getString("team_id");
JSONArray scoresA = teamA.getJSONArray("score");
JSONArray scoresB = teamB.getJSONArray("score");
dailySchedule.timeList.add(startTime);
String winnerName = null;
ArrayList<String> winnerID = teamMap.get(winnerTeamID);
for (String id:winnerID){
if (winnerName == null)
winnerName = playerMap.get(id);
else
winnerName = winnerName + " & " + playerMap.get(id);
}
String score = null;
for (var tempScoreA:scoresA){
JSONObject scoreA = (JSONObject)tempScoreA;
int set = scoreA.getInteger("set");
JSONObject scoreB = scoresB.getJSONObject(set-1);
String gradeA = scoreA.getString("game");
String gradeB = scoreB.getString("game");
if (score==null)
score = gradeA + ':' + gradeB;
else
score = score + " | " + gradeA + ':' + gradeB;
}
dailySchedule.ScoreList.add(score);
dailySchedule.WinnerList.add(winnerName);
}
}
}
A:识别用户输入数据的重点在于如何处理读入的字符串(比如对空白字符的处理)以及如 何判断输入的合法性。由于正确的指令只有两种:players 和 result xxxx ,且严格区分大 小写,所以可以先判断是否为players,直接使用字符串的equals函数即可。再判断是否为 result 0116形式的指令,可以使用正则分割的形式,判断分割后的字符串长度是否为2, 首项是否为result。上述判断都不满足时就是错误指令.
相关思路的流程图如下:

A:这个问题涉及到java文件操作,一种可行且简单的方法是使用RandomAccessFile。由于我们只需要删掉文件的最后一行的换行符,所以可以使用setLength直接将文件长度缩短1个字节即可。
RandomAccessFile raf = new RandomAccessFile(fileOutput,"rw");
if(raf.length()!=0){
raf.setLength(raf.length()-1);//remove \n in the end
raf.close();
}
//主类,主要用来读input.txt、分析指令,并调用其他类的相应获取、写入函数得到内容,并写入output.txt
public class AOSearch{...}
//core模块,用于获取选手信息并写入文件
public class PlayerList{...}
//core模块,用于分析指令正误、获取指定日期的赛程并写入文件
public class ResultList{...}
//于性能优化后加入的新类,主要目的是存储选手信息
public class PlayerStruct{...}
//于性能优化后加入的新类,主要目的是存储某一天的赛程信息
public class ResultStruct{...}
AOSeach类
//存放曾经读到的赛程信息
public static HashMap<String,ResultStruct> ResultData = new HashMap<>();
//存放曾经读到的排行榜信息
public static PlayerStruct PlayerData = new PlayerStruct();
//程序入口,读文件并调用其他函数来写文件
public static void main(String[] args) throws IOException{...}
//分析input.txt的指令,根据分析结果调用相应的处理函数
public static void writeToFile(){...}
//处理无法识别的指令,即写入“Error”到文件
public static void writeInvalidCommandInfo(){...}
PlayerStruct(工具类,存放所有选手信息)
public ArrayList<String> fullNameList = null; //记录选手全名
public ArrayList <String> genderList = null; //记录选手性别
public ArrayList <String> nationalityList = null; //记录选手国籍
ResultStruct(工具类,一个对象存放单日赛事情况)
public ArrayList <String> timeList = null; //记录比赛时间
public ArrayList <String> WinnerList = null; //记录胜出者
public ArrayList <String> ScoreList =null; //记录比分列表
PlayerList类
public void getPlayerList() throws IOException{...}
//PlayerList为空时调用,初始化rankingData
public void initializeArray(){...}
//PlayerList为空时调用,向PlayerData填充信息
public static void fillArray() throws IOException{...}
//写入选手信息到文件
public void writeCertainResult() throws IOException{...}
ResultList类
//0116对应的int值,作为日期起始值判断日期是否在范围内
public static final int BEGIN_OF_WINTEROLYMPIC = 116;
//0116对应的int值,作为日期结束值判断日期是否在范围内
public static final int END_OF_WINTEROLYMPIC = 129;
//编号到选手姓名的映射表
public HashMap<String, String> playerMap = new HashMap<>();
//队伍id到成员id的映射表
public HashMap<String, ArrayList<String>> teamMap = new HashMap<String, ArrayList<String>>();
//获取赛程日期,根据具体日期或赛季执行不同的动作
public void getResultList(String date) throws IOException{...}
//判断日期是否在赛会期间
public static boolean isValidDate(String testString){...}
//根据提供的日期键值dateKey,填充ResultData
public void fillArray(String dateKey) throws IOException, ParseException{...}
//初始化ResultData中的一个成员,以存放赛程信息
public void initializeArray(){...}
//将已存在ResultData的赛事信息写入文件
public void writeCertainResultByMap(String dateKey) throws IOException{...}
//从Json字符串中获取节点数据并存入ArrayList
public void jsonToString(String json){...}
//处理不在赛会日期范围内的情况,输出N/A
public static void writeInvalidDateInfo() throws IOException{...}
//建立队伍id到成员id的映射表
private void jsonToTeamMap(String json)
//建立选手编号到选手姓名的映射表
private void jsonToPlayerMap(String json)
//处理不在赛事时间内的情况,输出N/A
public static void writeInvalidDateInfo(){...}
//处理无效日期格式(如116,02xx)的情况,输出N/A
public static void writeInvalidDateFormatInfo() throws IOException{...}
- AOSearch.writeToFile():
//分析input.txt指令,根据分析结果调用相应处理函数
public static void writeToFile(){
String tempString ;
try{
while((tempString=br.readLine())!=null){
if(tempString.trim().equals("")){
}
else if(tempString.trim().equals("players")){
(new PlayerList()).getPlayerList();
}
else{
tempString = tempString.trim();
String[] stringSlice = tempString.split("\\s+");
try{
if(!stringSlice[0].equals("result")){
writeInvalidCommandInfo();
}
else if(stringSlice.length!=2){
writeInvalidDateContentInfo();
}
else{
(new ResultList()).getResultList(tempString);
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}
catch (IOException e) {
e.printStackTrace();
}
}
-ResultList.getResultList()
//获取赛程,根据具体日期或赛季执行不同的动作
public void getResultList(String date) throws IOException {
String dateStr = date.replace("result", "").replaceAll(" ", "").trim(); //get the date we need
//日期格式应为四位数字xxxx,或赛程格式Qx
try {
if (!dateStr.equals("Q1" ) && !dateStr.equals("Q2" ) && !dateStr.equals("Q3" ) && !dateStr.equals("Q4" ) && dateStr.length() != 4) {
writeInvalidDateFormatInfo();
} else {
boolean isValidDateFlag = isValidDate(dateStr);
if (isValidDateFlag) {
if (AOSearch.ResultData != null && AOSearch.ResultData.containsKey(dateStr)) {
writeCertainResultByMap(dateStr);
} else {
String sourcefile = "src"+File.separator+"data"+File.separator
+ "schedule" + File.separator + dateStr + ".json";
File file = new File(sourcefile);
try {
br = new BufferedReader(new FileReader(file));
initializeArrayList();
fillArrayList(dateStr);
writeCertainResultByMap(dateStr);
br.close();
} catch (ParseException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
} else {
writeInvalidDateInfo();
}
}
} catch (NumberFormatException e) {
writeInvalidDateFormatInfo();
}
}
-ResultList.writeCertainResultByMap()
//将已存在ResultData的赛事信息写入文件
public void writeCertainResultByMap(String dateKey) throws IOException {
ResultStruct result = AOSearch.ResultData.get(dateKey);
for (int i = 0; i < result.timeList.size(); i++) {
if(result.timeList.get(i)=="null" && result.WinnerList.get(i)=="null"
&& result.ScoreList.get(i)=="null"){
AOSearch.bw.write("W/O\n-----\n");
}
else{
AOSearch.bw.write("time:"+result.timeList.get(i) + '\n');
AOSearch.bw.write("winner:"+result.WinnerList.get(i) + '\n');
AOSearch.bw.write("score:"+result.ScoreList.get(i)+ '\n');
AOSearch.bw.write("-----\n");
}
}
}
-ResultList.jsonToPlayerMap
//建立选手编号到选手姓名的映射表
private void jsonToPlayerMap(String json){
if(json != null){
JSONObject jsonObj = JSON.parseObject(json);
JSONArray players = jsonObj.getJSONArray("players");
for (var tempPlayer:players){
JSONObject player = (JSONObject)tempPlayer;
playerMap.put(player.getString("uuid"),player.getString("short_name"));
}
}
else{
System.out.println("未能成功读取json");
}
}
-PlayerList.getPlayerInfoFromJson
//从Json获取Player数据
public void getPlayerInfoFromJson(String json){
if(json!=null){
JSONObject jsonObj = JSON.parseObject(json);
JSONArray players = jsonObj.getJSONArray("players");
for(var tempPlayer:players){
JSONObject player = (JSONObject)tempPlayer;
AOSearch.PlayerData.fullNameList.add(player.getString("full_name"));
AOSearch.PlayerData.genderList.add(player.getString("gender"));
AOSearch.PlayerData.nationalityList.add(player.getJSONObject("nationality").getString("name"));
}
}
}
优化前性能如下:
输出所有Json文件的数据(result 0116-0129、players、result Q1-Q4)

真的是久到离谱

程序存在的一些问题
优化思路

大概快了两倍

主要思路:采用junit4,以程序写入的方式,在AOSearch中进行测试,获得测试结果,并观察代码覆盖率。
@Test
public void testMain() {
AOSearch.main(new String[]{"input.txt","output.txt"});
try{
AOSearch.main(new String[]{"input.txt"});
}catch (ArrayIndexOutOfBoundsException e)
{
System.out.println("WARNING:You have not given 2 arguments!");
e.printStackTrace();
}
AOSearch.main(new String[]{"123.txt","output.txt"});
AOSearch.writeInvalidCommandInfo();
AOSearch.writeInvalidDateContentInfo();
}
@Test
public void testWriteToFile() {
}
@Test
public void testWriteInvalidCommandInfo() {
}
@Test
public void testWriteInvalidDateContentInfo() {
}


可以发现覆盖率还是很高的

由于之前极少有对异常处理的经历,所以对这部分不是很了解。大部分异常是idea自动补全的,遇到异常时只是简单的输出一下异常信息。下面给几个例子:
1、IOException
本次作业最可能出现的异常,发生于文件读写不成功等一系列与文件有关的操作时。




附:参考文献
博客园-如何将java项目打成jar包
CSDN-Java JUnit单元测试教程
fastjson的基本使用方法
博客园-获取Java程序运行时间
完成的很不错,解题思路分析清楚,流程图绘制的让人一下子能理解你的思路。