PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
- Estimate | 估计这个任务须要多少时间 | 1275 | 2145 |
Development | 开发 | ||
- Analysis | 需求分析 (包括学习新技术) | 180 | 210 |
- Design Spec | 生成设计文档 | 100 | 150 |
- Design Review | 设计复审 | 60 | 40 |
- Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 20 |
- Design | 具体设计 | 30 | 40 |
- Coding | 具体编码 | 600 | 520 |
- Code Review | 代码复审 | 60 | 70 |
- Test | 测试(自我测试,修改代码,提交修改) | 120 | 980 |
Reporting | 报告 | ||
- Test Report | 测试报告 | 45 | 60 |
- Size Measurement | 计算工做量 | 20 | 25 |
- Postmortem & Process Improvement Plan | 过后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 1275 | 2145 |
首先,咱们组内对任务需求进行分析,结合以前的代码经验,将问题划分为两大步骤。第一是对文件进行读取,得到文件内容。第二是对读取到文件内容进行分析及统计。因而建立出Main、Process、Result三个类,做用分别Main是程序入口、Process对读取到的文件内容进行分析统计、Result作为结构记录相应字符数、行数和单词及其频数。而后在Process类中统计字符数用String类的Length()算出字符数并记录,统计行数时经过按行读取文件计算出行数并记录,统计单词时依靠正则表达式匹配单词、统计频次并记录到Map结构中。并做出相应的类图使需求在类图都有对应的方法获得实现
最后设计比较刁钻的测试数据测试代码正确性、而且在代码中用时相对较长的部分进行力所能及的代码优化。html
基础及进阶需求均使用工具JProfiler监控处理随机生成的约700m大小文件分析内存,处理器占用。java
在这次程序编写的过程当中,主要处理程序迭代了两次.在第一个原型程序时由于采用了按行读取再按字处理的方式致使效率较为低下,一个字符一般要用几个IF语句判断是否符合需求的要求,再加上不利于后续功能扩展便将其从新编写,采用正则表达式按行处理文件.ios
不论在基础仍是进阶功能的编写中发现程序运行时要反复调用几个方法,而在方法中要不断建立数个Pattern类来构建正则表达式,所以在优化的过程当中首先考虑将方法设置为静态类型,并将几个Pattern类的正则表达式在Process类构造的时候便进行编译,提升效率.
测试效果采用一个21M大小左右文件进行
基础未优化前
git
基础优化后:
github
进阶需求采用一个约22M大小文件测试
优化前:
正则表达式
优化后
算法
有必定效果但并非十分明显api
各方法耗时:数组
能够发如今哈希表处理的时候耗时占比较多.在程序中为了节约时间咱们便直接使用JAVA提供的map结构储存单词,数量的键值对,可是JAVA自己提供的方法不能很好的知足咱们的需求好比说单词数量排序的问题,目前解决方案是将MAP导出为一个list表,对此进行排序.在咱们规划中是采用一个相似于treemap的顺序结构来进行有序插入,在节约排序时间的同时节约大量为了排序重复储存的内存空间.可是自定义结构须要花费大量时间测试可靠性,而且完成此类型超大文件所需的时间在一分钟左右,不算太过离谱,因而便计划在完成全部任务后最后进行.服务器
此外因为前期分析得当,提早采用了Bufferedreader以带缓冲的方式读取文件,节约了大量的IO时间,使得即使在机械硬盘上文件读取部分花费时间占比不到10%,没有进一步优化的必要.
各方法耗时:
从图中能够得出在使用正则表达式完成需求的耗时占比较多,但考虑到需求并未明确说起只处理特定格式的文件,不能使用取巧办法忽略需求中说起的无关信息,此外考虑到主要处理文件需求为CVPR论文信息,在此类文件一般不大的状况下考虑到紧迫的时间安排再也不进一步优化.
爬虫部分程序主要由两部分组成,首先爬取位于 http://openaccess.thecvf.com/CVPR2018.py 的论文连接列表,将其中的论文URL添加到带爬取URL表中,再从URL列表取出URL不断爬取论文信息
最开始的原型实现使用单线程进行,因为该网站服务器位于境外,再加上带爬取URL有千条之多,所以速度十分缓慢.
采用JProfiler分析时间占用,通过团队团队讨论后发现,第二部分为大量独立任务构成,十分适合进行多线程优化,而且由于各个结果之间相互独立,不须要太多的多线程同步处理,还利于程序编写.所以咱们首先对此进行了优化.
优化前:
大约花费293秒
优化后:
大约花费25秒,效率提高十分明显
在此基础上,咱们进一步分析发现由于受制于该网站服务器位于境外的网络缘由,每一个线程进行网络通讯的时候等待时间较久且容易出错,所以进一步的优化考虑为采用位于境外代理池替换本机进行信息爬取,但受制于成本以及时间并未付诸实施.
public int getWord_count() { return result.getWord_count(); } public int getChar_count() { return result.getChar_count(); } public int getLine_count() { return result.getLine_count(); }
private static void process_line_withRegularExpression(String str, Result resultClass){ Matcher m=p.matcher(str); while(m.find()) { //System.out.println(m.group(2)); resultClass.addWord(m.group(2)); } }
while ((s = bufferedReader.readLine()) != null) { i++;//行计数 resultClass.char_count_plus(s.length());//统计字符数 s = s.replaceAll("[^\\x00-\\x80]", ""); s=trim(s);//去掉开头不显示字符 if(s.length()!=0){//如有可显示字符则处理 resultClass.line_count_plus();//统计行数 process_line_withRegularExpression(s,resultClass); } }
public void addWord(String word) { String tempWord = word.toLowerCase(); if (resultMap.containsKey(tempWord)) {//resultMap使用HashMap结构 resultMap.put(tempWord, resultMap.get(tempWord) + 1); word_count_plus(); } else { resultMap.put(tempWord, 1); word_count_plus(); } }
public List<Map.Entry<String, Integer>> sort() { //从HashMap恢复entry集合 //从resultMap.entrySet()建立LinkedList。咱们将排序这个链表来解决顺序问题。 //咱们之因此要使用链表来实现这个目的,是由于在链表中插入元素比数组列表更快。 List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(resultMap.entrySet()); Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() { //经过传递链表和自定义比较器来使用Collections.sort()方法排序链表。 //降序排序 public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) { if(o2.getValue()==o1.getValue()) return o1.getKey().compareTo(o2.getKey()); return o2.getValue().compareTo(o1.getValue()); //使用自定义比较器,基于entry的值(Entry.getValue()),来排序链表。 } });
private static void process_line_withPhrase(String str, Result resultClass, int number,int weight) { Matcher m = phrasePattern.matcher(str); PhraseFactory phraseFactory = new PhraseFactory(number);//短语构造类 PhraseBorder phraseBorder;//result while (m.find()) { if (m.start() == 0 || !Character.isDigit(str.charAt(m.start() - 1))){ //判断单词开头是否为数字 resultClass.word_count_plus(); if ((phraseBorder = phraseFactory.storeBorder(m.start(), m.end())) != null) { //储存单词位置 int newEnd = trimTail(str, phraseBorder.start, phraseBorder.end); //System.out.println(str.substring(phraseBorder.start, newEnd)); resultClass.addPhrase(str.substring(phraseBorder.start, newEnd), weight); //添加短语至结果集 } } } }
public PhraseBorder storeBorder(int start, int end) {//添加新的单词位置 if (!isFull()) { if (isFormerEnd(start)) { pool[iterator].start = start; pool[iterator].end = end; addIterator(); } else { clear(); pool[iterator].start = start; pool[iterator].end = end; addIterator(); } } else { if (isFormerEnd(start)) { pool[iterator].start = start; pool[iterator].end = end; addIterator(); int outputStart = pool[startPoint].start; addStartPoint();//后移开头指针等于输出 return new PhraseBorder(outputStart, end); } else { clear(); pool[iterator].start = start; pool[iterator].end = end; addIterator(); } } return null; }
public int initialization(String URL) {//得到待爬取URL列表 try { Document document = Jsoup.connect(URL).timeout(1000 * 60).maxBodySize(0).get();// Elements links = document.getElementsByTag("a"); int i = 0; for (Element link : links) { String href = link.attr("href"); if (href.contains("content_cvpr_2018/html/")) { URLs[i] = "http://openaccess.thecvf.com/" + href; System.out.format("%d:%s\n", i, URLs[i]); ++i; } } return URL_size = i; } catch (IOException e) { e.printStackTrace(); } return -1; } public void workMethod() {//爬取管理方法 ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < URL_size; i++) { cachedThreadPool.execute(() -> getContent(getURL())); } try { bufferedWriter.flush(); } catch (IOException e) { e.printStackTrace(); } cachedThreadPool.shutdown(); } public void getContent(String URL) {//得到内容方法 try { Document document = Jsoup.connect(URL).timeout(1000 * 600).maxBodySize(0).get();// Elements links = document.getElementsByTag("div"); String content = null, title = null; for (Element link : links) { switch (link.id()) {//case "authors"://and time case "papertitle": title = link.text(); break; case "abstract": content = link.text(); break; } } synchronized (this) { System.out.println(outputNumber); System.out.println(); bufferedWriter.write(String.valueOf(outputNumber)); bufferedWriter.newLine(); System.out.format("Title: %s\n", title); bufferedWriter.write(String.format("Title: %s\n", title)); System.out.format("Abstract: %s\n", content); bufferedWriter.write(String.format("Abstract: %s\n", content)); bufferedWriter.newLine(); bufferedWriter.newLine(); outputNumber++; } System.out.println(); } catch (IOException e) { e.printStackTrace(); } } public synchronized String getURL() {//得到待爬取URL if (currentURL < URL_size) return URLs[currentURL++]; else return null; }
基础测试部分使用C++语言编写了一个随机生成字符的程序,随机生成行数、字符串长度,而后写入到input.txt文件中。在生成字符串的同时,记录生成字符数,行数以及定义上的空行数。而后将生成的input.txt文件作为WordCount程序的测试文件,而后在Eclipse中使用Junit进行单元测试。
#include<cstdio> #include<time.h> #include<stdlib.h> #include <iostream> #include <fstream> using namespace std; int main() { srand(time(0)); //产生随机化种子 int count = 0; int count1 = 0; int n = rand() % 1000+99; //在1000-99的范围内随机产生字符串个数 FILE *fp = fopen("input.txt","w"); int temp=n; while (temp--) //依次产生n个字符串 { int k = rand() % 150 + 0; //随机生成一个字符串的长度 if(k==0){ count1++; } count+=k; for (int i = 0; i < k; i++) { int x; x = rand() % (127 - 32) + 32; fprintf(fp, "%c", x); //将x转换为字符输出 } fprintf(fp, "\n"); } printf("line: %d", n ); printf("空白line: %d", count1 ); printf("\n character: %d\n",count); return 0; }
注:官网爬取结果存在非ASCII字符且位于两个合法单词之间的状况, 本次程序对于此类状况按两个单词处理
若按一个单词计算此类状况单词数量应少掉10个左右
结果
-统计行数
构造思路:1.中间增长空白行
2.单行放入定义空白符
-统计单词
构造思路:1.多种不合法单词,如1aaaa、the、abc123a等
2.分割符分割单词,如task-masn、mesk--a123等
-统计词组
构造思路:1.单词间添加非法单词,如aaaa in bbbb等
2.单词间添加分割符,如aaaa(%bbbb等
-极端状况处理
构造思路:生成大型文本文件(738m)测试程序稳定性
character: 37854 line: 500 无效空白line: 3
character: 75998 line: 1016 无效空白line: 6
在此次结对任务中遇到的困难是比较多的,首先在阅读任务、分析需求的时候就出现了至关多的分歧,分隔符划分、词组匹配等。相信这也不仅是咱们赶上的困难,从微信群里就能够看出,这一点能够说是全班同窗的共同问题。这一部分的解决方法就是讨论,不止结对两人间讨论,群内讨论、与其余小组进行讨论而且交换测试数据互相纠错。其次是爬虫部分,由于以前没有接触过,因此是现学的技巧。由于时间限制,因此这一部分仍是有点难度的。最后是代码Debug,由于在需求分析时的出现的问题,在后面代码Debug的时候花费了至关多的时间,在给出的PSP表中能够看出,本来预计120分钟要完成的测试(自我测试,修改代码,提交修改)项中,实际花费时间拉长到了980分钟。
最后这次做业原本在规划时划拨了完整的一天时间进行代码的优化,可是需求一直不断在变化,天天在完成新功能的同时还要对以前完成的部分进行修改以适应新的需求.并且因为许多需求此前定义不明确致使咱们在需求分析时考虑的状况不够贴近实际,在编码时发现此前的需求分析及功能设计不能知足须要,须要从新构建,而且代码也得推倒重写,浪费了大量时间
并且因为需求定义不明,致使咱们对部分状况的处理结果进行了讨论并定义当前程序存在BUG,可是等到咱们花费大量时间查找文件内容并定位到具体致使BUG缘由,随后又花费必定时间进行修复,挤占了本就为数很少的代码测试时间,结果一觉醒来发现作了无用功?
在此次的结对任务中,个人队友起到了一个中流砥柱的做用。他不只有很强的代码开发能力,编写的代码思路清晰,对咱们后面的代码测试、修改有很大帮助。并且在沟通交流中能够清晰的表达本身的想法思路,同时又能快速理解我所表达的思路观点,所以在沟通交流上能够说是配合的至关愉快。除此以外,他还拥有很强的自学能力,可以在短期内学习爬虫技术,并编写爬虫程序实现对给定网站论文信息的爬取。最后,在后期的代码测试、Debug部分,会废寝忘食的修改错误代码。针对代码优化部分,他老是能想出优秀的方案来实现对代码的优化,提升程序的效率。能够说是有能力还肝,我要向他看齐。