这个作业属于哪个课程 | 福州大学软件工程实践个人编程作业https://edu.cnblogs.com/campus/fzu/SE2020 |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/SE2020/homework/11167 |
这个作业的目标 | 对于第一次编程作业的经验总结 |
学号 | 081800414 |
使用语言 | Java |
PSP 表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
Estimate | 估计这个任务需要多少时间 | 24h | 35h |
Development | 开发 | ||
Analysis | 需求分析 (包括学习新技术) | 600 | 600 |
Design Spec | 生成设计文档 | 20 | 20 |
Design Review | 设计复审 | 30 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
Design | 具体设计 | 40 | 70 |
Coding | 具体编码 | 400 | 500 |
Code Review | 代码复审 | 30 | 40 |
Test | 测试(自我测试,修改代码,提交修改) | 120 | 500 |
Reporting | 报告 | ||
Test Report | 测试报告 | 30 | 50 |
Size Measurement | 计算工作量 | 40 | 40 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 100 | 240 |
合计 | 1440 | 2140 |
解题思路与学习历程描述
拿到题的一瞬间是懵逼的,完全看不懂在说什么。又是大数据又是各种命令格式的,然后往下翻到样例代码看到又是Python的,尝试看了一下结果啥也没看懂...于是下载了示例数据,发现是一种json格式的文件。观察了文件的特征后,进行查阅了资料确认了该任务是对JSON的解析,通过关键词获取所要求的信息,并进行统计。最终决定用Java编写,于是漫长的写题之路开始了。
-
在群里看到大佬们提到
gradle
,在拿到题的当天上网搜寻了gradle的内容,在网上下载了gradle并配置了环境变量(配置了好久....) -
打开了github,学习了助教给的github使用教程。fork 并git clone了Java的repo。
-
继续了解gradle,一段时间后打开clone 的repo发现助教已经基本配置好了....
-
由于Java的文件io操作不是很熟悉,上网查询后,学会使用
BufferedReadr
的readline
方法,将JSON按行读入存在ArrayList
中进行后续的解析。 -
要解析JSON,于是上网搜寻了解析JSON相关的库,最终决定使用
fastJSON
,下载了Jar包,发现一直import不进去。于是搜寻资料,发现maven
, -
搜寻maven资料,了解了本地仓库,中央仓库,远程仓库,maven外部依赖及搜索顺序,maven插件 ,顺便看了一下
xml
文件的语法。 -
回到项目中,经过查询了解到在
build.gradle
中可以配置fastjson。dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' compile group: 'commons-cli', name: 'commons-cli', version: '1.4' compile group: 'commons-io', name: 'commons-io', version: '2.7' compile group: 'com.alibaba', name: 'fastjson', version: '1.2.73' }
-
配置完fastjson后开始进行解析的尝试,通过fastjson的库函数轻松地将JSON字符串转换为JSON对象,然后通过JSON对象 ,获取所需要的id与event名称
-
开始思考数据怎么存放,经过查阅与咨询林璟大佬后决定用3个Map分别对应三种查询类型。创建class
Result
存储4种事件的计数。 -
编写程序,获取
actor
的login
(user) 与repo
的name
(项目)后, 思考第三种情况如何表示,决定用login+name
组成新字符串ID表示每个人在项目的情况。 -
完善程序后开始尝试遍历3个Map检查是否正确,前几次运行样例前1000行JSON程序都正常。后截取了样例程序的1000-2000行数据,遇到程序运行错误,错误提示为fastJSON syntax error,百思不得其解后,选取部分样例数据并打断点测试,找到语法错误的一行后对其进行研究。上网搜索JSON标准格式与fastJSON API文档,后发现是fastJSON转换字符串过程中的转义符号作怪,于是编写
justify
方法,将JSON字符串进行截取,程序再次运行成功。 -
成功输出了3种类型的ID的对应event的统计数量,计时已经过去了17个h
-
隔天助教群里发出测试程序。通过python运行后得知正确的输出格式并得用命令行执行程序,对原程序进行返工修改。
-
了解命令行的使用方法,编写main函数中对应的命令行判别程序。
-
Java 执行时得将程序打包成Jar文件。于是尝试进行打包,通过查阅资料,发现
Artifacts
可以进行打包,尝试多次后无果遂放弃。进一步查询发现gradle自带打包功能,于是重新进行gradle的了解了,知道了gradle的apply
和plugins
两种插件,并知道Gradle 默认会从src/main/java
搜寻打包源码,在src/test/java
下搜寻测试源码。并且src/main/resources
下的所有文件按都会被打包,所有src/test/resources
下的文件 都会被添加到类路径用以执行测试。所有文件都输出到 build 下,打包的文件输出到 build/libs 下。同时了解到Java 插件添加了众多任务。但是它们只是在需要构建项目的时候才能发挥作用。最常用的就是 build 任务,这会构建整个项目。当执行 gradle build 时,Gralde 会编译并执行单元测试,并且将src/main/*
下面 class 和资源文件打包。于是尝试用 命令gradle build
进行打包,结果出现Could not receive a message from daemon
的情况。查询并略微了解了daemon
后重新进行打包,打包成功。 -
通过命令行运行jar文件,出现错误提示,fastJSON无法获取,以为是依赖配置有问题尝试多次无果后询问助教得知 gradle build打包的jar文件是不包含依赖的,只包含项目源码.正确的包含依赖依赖的打包是用
gradle shadowjar
。查询资料了解之后重新尝试打包并运行成功。 -
运行成功后发现init后执行下一条指令是失败的,资源被释放了。于是想办法将Map数组输出成并存储起来,并在查询时读入。尝试通过fastJSON转换为JSON对象,并转换为JSON字符串存储文件时发现,JSON字符串只有String部分,Result全为空白, 在群里询问大佬后,通过对Result的get与set方法补充,同时implements
Serializable
接口。成功进行Map转存JSON字符串并存储。 -
在群里的聊天记录中得知文件读取为读取文件夹下的一系列文件,之前的文件读取方式无法进行,进行修改后成功。后查询资料后发现
Apache commons io
库进行文件读写特别方便,学习并配置后,通过FileUtils
的writeStringToFile
方法快速存储了输出的3个map的json文件。(工具类库是真好用啊^) -
对程序进行细节修改与多次debug后成功通过4条测试,Commit到Github上也成功运行,菜鸡终于把题完整的搞出来了。泪目。
设计实现过程
代码说明
justify方法
private static String justify(String s) {
int cnt = 0;
int index = -1;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '\"') {
cnt++;
}
if (cnt == 40) {
index = i;
break;
}
}
String ans = s.substring(0, index);
ans = ans + "\"}}";
return ans;
}
//读入的JSON字符串“裁剪方法”
- justify方法将读入的JSON字符串裁剪到我们所需要的信息的位置。为什么cnt=40?因为我对着样例的数据数的hhh
main
//命令行读入与判别
public static void main(String[] args) throws IOException {
String testUser = null, testEvent = null, testRepo = null, filename = null;
//命令行读入并判断
if (args.length != 0) {
if (args[0].equals("--init") || args[0].equals("-i ")) {
filename = args[1];
init(filename);
} else if (args[0].equals("-u") || args[0].equals("--user")) {
if (args.length > 2) {
testUser = args[1];
if (args[2].equals("-e") || args[2].equals("--event")) {
testEvent = args[3];
countFromUser(testUser, testEvent);
} else if (args[2].equals("-r") || args[2].equals("repo")) {
testRepo = args[3];
testEvent = args[5];
countFromUserAndRepo(testUser + testRepo, testEvent);
}
}
} else if (args[0].equals("-r") || args[0].equals("--repo")) {
testRepo = args[1];
testEvent = args[3];
countFromRepo(testRepo, testEvent);
}
}
}
- main 读入命令行参数并判别init与三种查询类型
init方法
private static void init(String fileName) throws IOException {
ArrayList<String> jsonList = new ArrayList<>();
File dir = new File(fileName);
//读入文件列表转存JSON于jsonList中
File[] files = dir.listFiles(file -> file.getName().endsWith(".json"));
try {
for (File jsonFile : files) {
FileReader fileReader = new FileReader(jsonFile);
BufferedReader bf = new BufferedReader(fileReader);
String str;
while ((str = bf.readLine()) != null) {
jsonList.add(str);
}
bf.close();
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
//遍历jsonList,进行统计
for (int i = 0; i < jsonList.size(); i++) {
//"裁剪“
String tmp = jsonList.get(i);
tmp = justify(tmp);
//获取所需信息
JSONObject jso1 = JSON.parseObject(tmp);
String actor = jso1.getString("actor");
JSONObject jso2 = JSON.parseObject(actor);
String id = jso2.getString("login");
String repo = jso1.getString("repo");
JSONObject jso3 = JSON.parseObject(repo);
String repoId = jso3.getString("name");
//创建第三种查询的ID
String idAndRepoId = id + repoId;
//创建3种Result并插入对应的Map
if (!map1.containsKey(id)) {
map1.put(id, new Result());
}
if (!map2.containsKey(repoId)) {
map2.put(repoId, new Result());
}
if (!map3.containsKey(idAndRepoId)) {
map3.put(idAndRepoId, new Result());
}
Result res = map1.get(id);
Result repoRes = map2.get(repoId);
Result iAndRepoRes = map3.get(idAndRepoId);
String event = jso1.getString("type");
if (event.equals("PushEvent")) {
res.PushEvent++;
repoRes.PushEvent++;
iAndRepoRes.PushEvent++;
} else if (event.equals("IssueCommentEvent")) {
res.IssuesEvent++;
repoRes.IssuesEvent++;
iAndRepoRes.IssuesEvent++;
} else if (event.equals("PullRequestEvent")) {
res.PullRequestEvent++;
repoRes.PullRequestEvent++;
iAndRepoRes.PullRequestEvent++;
} else if (event.equals("IssuesEvent")) {
res.IssuesEvent++;
repoRes.IssuesEvent++;
iAndRepoRes.IssuesEvent++;
} else {
continue;
}
map1.put(id, res);
map2.put(repoId, repoRes);
map3.put(idAndRepoId, iAndRepoRes);
}
//map转换为JSON String
String s1 = JSONObject.toJSONString(map1);
String s2 = JSONObject.toJSONString(map2);
String s3 = JSONObject.toJSONString(map3);
// 将json写入文件
FileUtils.writeStringToFile(new File("map1.json"), s1, "UTF-8");
FileUtils.writeStringToFile(new File("map2.json"), s2, "UTF-8");
FileUtils.writeStringToFile(new File("map3.json"), s3, "UTF-8");
}
查询方法(user)
public static void countFromUser(String user, String event) throws IOException {
String tmp = event.substring(0, 1);
tmp = tmp.toLowerCase();
event = tmp + event.substring(1, event.length());
String s = FileUtils.readFileToString(new File("map1.json"), "UTF-8");
JSONObject jsonObject = JSONObject.parseObject(s);
JSONObject object = jsonObject.getJSONObject(user);
if (object != null) {
System.out.println(object.getInteger(event));
}
}
- 读入对应map的json文件,将JSON字符串转JSON对象并获取对应key(user)的Result。
代码规范
https://github.com/Pikapika-sk/2020-personal-java/edit/master/Java代码规范-洪司坤.md
总结
对我来说真难
好几个下午+好几个晚上。写博客的时候发现原来在这个过程中学到了很多新知识,希望我下次需要用的时候还记得哈哈哈。
可以提高的地方:搞多线程和单元测试