PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 2610 | 2655 |
? Estimate | ? 估计这个任务须要多少时间 | 2610 | 2655 |
Development | 开发 | 800 | 850 |
? Analysis | ? 需求分析 (包括学习新技术) | 300 | 350 |
? Design Spec | ? 生成设计文档 | 60 | 40 |
? Design Review | ? 设计复审 | 30 | 40 |
? Coding Standard | ? 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
? Design | ? 具体设计 | 180 | 200 |
? Coding | ? 具体编码 | 850 | 820 |
? Code Review | ? 代码复审 | 150 | 120 |
? Test | ? 测试(自我测试,修改代码,提交修改) | 80 | 60 |
Reporting | 报告 | 50 | 40 |
? Test Repor | ? 测试报告 | 30 | 30 |
? Size Measurement | ? 计算工做量 | 20 | 30 |
? Postmortem & Process Improvement Plan | ? 过后总结, 并提出过程改进计划 | 30 | 45 |
合计 | 2610 | 2655 |
刚看到基本题目时,首先想到的是用python写,毕竟写爬虫什么的,第一选择每每是python。但后来看到题目要求语言使用c++或java,因而了对比c++与java。因为本次的做业是要写文词统计+爬虫两个简单的程序,考虑到文词统计可能须要对字符串进行大量的处理,又由于java已经有封装好的Pattern、Matcher的字符串正则匹配和处理的函数,并且以前上网查到过使用jsoup(Java 的HTML解析器)写爬虫也很方便,因而两人直接敲定统一使用java编写。对文词统计基本设计思路是写两个类,一个专门用来测试,另外一个包含了基本的统计字符、行数、单词等方法。爬虫由于以前不曾写过,所以基本上是边看教程边写。html
一共两个类:Main类(测试类)、WordCount类(集合了全部统计函数的类),
类图:java
由于WordCount类只负责对外界的输入作统计功能,所以我把每一个函数写成静态方法,外界可直接经过类名调用。countChar、countWord、countLine函数分别统计字符数、单词数、有效行数;isChar、isWord、isLine函数分别判断是否为有效字符、单词、有效行数;toWordMap函数用来将从txt文件读取到的文本转化为单词—>词频(key—>value)的一个hashmap结构;outPutTop10函数将这个hashmap结构转化为动态数组结构,进行排序,输出top10关键词;outCount函数作综合输出,即调用此函数一次将字符数、单词数、有效行数、top10单词输出到result.txt文件中。node
最关键的算法在于如何正确匹配各类须要处理的字符串,还有转化hashmap结构和对hashmap中的单词进行排序等。整体没有什么特别复杂的函数,这里展现将输入的文本转为hashmap结构存储的流程图:
关键代码:python
public static int countChar(File file) throws IOException {//计算字符数 int charnum=0; BufferedReader reader=new BufferedReader(new FileReader(file)); int c; while((c=reader.read())!=-1) { if (isChar(c)) { charnum++; } } reader.close(); return charnum; } public static int countLine(File file) throws IOException {//计算行数 int linenum=0; BufferedReader reader=new BufferedReader(new FileReader(file)); String s=null; while((s=reader.readLine())!=null) { if (isLine(s)) { linenum++; } } reader.close(); return linenum; } public static int countWord(File file) throws IOException{//计算单词数 int wordnum=0; BufferedReader reader=new BufferedReader(new FileReader(file)); String s=null; while((s=reader.readLine())!=null) { String[] strings=toWordList(s);//分隔 for(int i=0;i<strings.length;i++) { if (isWord(strings[i])) { wordnum++; } } } return wordnum; } public static String[] outPutTop10(File file) throws IOException {//输出top10单词 HashMap<String, Integer> wordMap=toWordMap(file); ArrayList<Map.Entry<String,Integer>> list = new ArrayList<Map.Entry<String,Integer>>(wordMap.entrySet()); Collections.sort(list,new Comparator<Map.Entry<String,Integer>>() { //升序排序 @Override public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) { // TODO Auto-generated method stub if (o1.getValue().compareTo(o2.getValue())==0) {//若是词频相同 return -1; } return o2.getValue().compareTo(o1.getValue()); } }); int num=0; String[] top10=new String[10];//保存TOP10 for(Map.Entry<String,Integer> mapping:list){ if (num==10) { break; } top10[num]="<"+mapping.getKey()+">"+":"+mapping.getValue(); num++; } return top10; }
toWordMap():c++
public static HashMap<String, Integer> toWordMap(File file) throws IOException {//把txt文件内容构建为一个HashMap结构 HashMap<String, Integer> hashMap=new HashMap<>(); BufferedReader reader=new BufferedReader(new FileReader(file)); String s=null; int index; while((s=reader.readLine())!=null) { index=0; String[] strings=toWordList(s);//分隔为准单词数组 while(index<strings.length) { if (isWord(strings[index])) { String lowerstring=strings[index].toLowerCase();//单词转为小写 if (hashMap.containsKey(lowerstring)) {//判断是否重复 hashMap.put(lowerstring, hashMap.get(lowerstring)+1);//若是重复词频加1 } else { hashMap.put(lowerstring, 1);//若是单词不重复初值为1 } } index++; } } reader.close(); return hashMap; }
仍然是两个类:Main类(测试类)、WordCount类(集合了全部统计函数的类)
类图:git
因为需求的变动,如今须要为这些统计函数增长一些参数,以及对函数的一些修改。主要修改成:因为新增了命令行可输入参数,所以用switch对参数输入进行判断,赋值给相应变量;增长了一个inputProcess函数,消除编号行以及Abstract: ,Title: 这两个字符串,以便与以后的统计;把toWordMap函数改写为toPhraseMap函数——在基本需求中只考虑往hashmap存入单个单词的状况,如今增长了词组词频统计功能,因而把以前的单词也看做词组,写成一个存储词组的hashmap函数;剩下的就是一些正则表达式的修改,这个8谈。github
进阶需求中最复杂的代码实现就是对词组词频的统计,流程图:
关键代码,统计函数与基本需求相差不大,这里只展现toPhraseMap函数:正则表达式
public static HashMap<String, Integer> toPhraseMap(File file,int w,int m) throws IOException {//把txt文件内容构建为一个HashMap结构 HashMap<String, Integer> hashMap=new HashMap<>(); BufferedReader reader=new BufferedReader(new FileReader(file)); String s=null; int weight=1; while((s=reader.readLine())!=null) { if (Pattern.matches("[0-9]+", s)) {//跳过编号行 continue; } weight=1; if (Pattern.matches(".*Title: .*",s)&&w==1) {//遇到title行而且w=1,权值设为10 weight=10; } String ns=inputProcess(s); String[] strings=toWordList(ns);//分隔出准单词数组 String[] strings2=toBreakList(ns);//分隔出分隔符数组 String phrase=""; String lowerstring=null; boolean find=true;//find为true表明找到符合规则的词组 for(int i=0;i<strings.length-m+1;i++) { find=true; phrase=""; for(int j=i;j<i+m;j++) {//循环m次 if (isWord(strings[j])) { lowerstring=strings[j].toLowerCase();//转小写 phrase=phrase+lowerstring;//拼接为词组 if (m!=1&&j!=i+m-1) { if (strings[0].equals("")) { phrase=phrase+strings2[j];//拼接为词组 } else if (strings2[0].equals("")) { phrase=phrase+strings2[j+1];//拼接为词组 } } } else { find=false; break; } } if (find) {//若是找到一个词组 if (hashMap.containsKey(phrase)) {//若是重复词组则对应词频加weight hashMap.put(phrase, hashMap.get(phrase)+weight); } else { hashMap.put(phrase, weight);//不然初始化为weight } } } } reader.close(); return hashMap; }
Java实现方式算法
- Java中能够对html文本进行抓取和处理的第三方库有许多,但从本次项目来看,只须要抓取一部分数据,并无过多复杂的操做,所以选择简单而且易上手的Jsoup。
- 使用第三方库Jsoup能够对目标网站(CVPR)的HTML内容进行,抓取,并使用jsoup.nodes.Document对内容的DOM树进行处理,即可以获取相应内容。
查看网页HTML:
- 对获取的Document使用select定位到想要获取数据的节点,获取数据同时储存在一个ArrayList中。
获取的关键数据:
- 待爬取结束后按照要求将数据格式化保存在文件中。
保存:
- 流程图
关键代码编程
1.多线程 //建立100个线程同时工做,并让当前线程等待这些线程 ArrayList<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < threadnum; i++) { Thread newThread = new Thread(new absThread(i, dealnum, paperdt.size(), paperdt, pList)); newThread.start(); threads.add(newThread); } for (int i = 0; i < threads.size(); i++) { threads.get(i).join(); } 2.加锁保护数据 lock.lock(); pList.add(title, abs); lock.unlock();
改进方案
- 刚开始编写代码时因为没有好好考虑封装,所以改需求效率很低。改进方法是尽可能让一个函数仅实现一个比较小而具体的功能,冗余的代码都写成一个函数供须要的函数调用。
- 词组一开始觉得词组仅包含单词,不须要考虑相隔的分隔符。后来知道须要词组须要加入分隔符后,考虑将准单词和分隔符分为两个数组,单词拼接为词组时将分隔符也加入拼接成的字符串,最后结果是成功的。
经过使用第三方库能够很容易的获取页面的HTML内容,所以只须要对爬取到的内容进行逻辑处理便可,此处没有太多难题,可是由于爬取的论文有必定数量,并且此工程中所需的摘要内容还须要进入新页面进行抓取,致使1000条的内容就须要1000次的页面访问,所以爬取的效率极为低下(经测试大体须要4-5min),这显然是不行的。改进:第一反应就是使用多线程技术进行优化(毕竟用的第三方库的API进行内容抓取,此处我也不知道怎么进行优化)
爬虫改进:
- 思路:暂且先将线程数定为100,使用这100个线程同时对DOM进行处理,根据总共的论文篇数为每一个线程制定工做任务(例如这里有979篇论文,那么每一个线程大概就爬取10篇论文的内容)。这样 100个线程同时工做,就能极大提升爬虫的效率,减小爬取总时间。同时由于100个线程同时进行数据爬取和储存,所以若是不进行任何处理,极有可能会出现数据丢失(实际过程当中也确实出现了数据丢失的状况),所以须要在保存数据时进行同步或者加锁(我选择加锁),从而保证存储过程当中的稳定性。
- 结果:成功将爬取时长从4-5min优化至20-30s。
- 利:极大地提升了爬虫的效率。
- 弊:爬取并保存下来的论文列表顺序和网站上的不一致(我的认为这没什么所谓就懒得处理了)。
性能分析图(工具使用JProfiler)
优化前:
优化后:
经过本次实践,咱们意识到团队间的交流是十分重要的,由于一项工程是须要共同协做的,而每一个人对工程需求的理解还有我的的目标都是不同的,这些都会在工做的进行中暴露出来,而这些差别有可能就会成为团队中的矛盾,若是不能合理地解决这些矛盾,不只没法共同合做,甚至会引发更大的冲突(适当的争论是能够的),最糟糕的状况下整个项目中止也不是不可能,所以咱们认为有效的队内交流是十分重要的。
221600424
队友对待做业十分认真,并且善于精益求精,在代码运行效率不够满意时查找方法,进行优化。
221600427
队友编程能力很强,并且乐于爆肝,往往出现BUG都不厌其烦的调试、查错。