软工实践第二次作业(词频统计)

Github项目地址:https://github.com/siberia0015/PersonProject-Java


PSP表格:

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning 计划  30  20
• Estimate • 估计这个任务需要多少时间  600  630
Development 开发  530  695
• Analysis • 需求分析 (包括学习新技术)  60  60
• Design Spec • 生成设计文档  10  5
• Design Review • 设计复审  10  20
• Coding Standard • 代码规范 (为目前的开发制定合适的规范)  60  140
• Design • 具体设计  120  150
• Coding • 具体编码  180  180
• Code Review • 代码复审  30  40
• Test • 测试(自我测试,修改代码,提交修改)  60  100
Reporting 报告  40  50
• Test Repor • 测试报告  15  15
• Size Measurement • 计算工作量  10  20
• Postmortem & Process Improvement Plan • 事后总结, 并提出过程改进计划  15  15
   合计 600 765

计算模块接口的设计与实现过程:

  出于开发项目和今后发展考虑,从这次作业起我将学习使用java编程。首先,题目要求程序能够接收一个文件,文件名由控制台输入,因此需要一个方法能够接收控制台输入的值作为文件的地址参数。因为题目还需要求把不同功能的模块封装成独立的模块,因此我把接收文件的代码段直接放在各个方法里。然后,题目要求代码至少要对数据进行四种统计,统计字符出现次数、单词(符合要求的)出现次数、有效行数以及出现频率最高的十个单词。我使用BufferedReader来依次读取文件的每一行,对于行数统计,我在读取的同时会记下长度不为0的行的个数;对于字符统计,我用一个StringBuffer记下每行的内容,然后把它转为String,由于此方法无法记下换行符,所以我最后用这个String的长度加上总行数(包括空白行)-1作为字符数;对于单词(有效)统计,我前面的方法同上,之后用split()方法将String分成一个String数组,通过一系列方法筛选出符合条件的元素,统计个数;对于词频统计,我用一个HashMap存放获得的符合条件的单词及其出现次数,然后对其进行排序并返回。最后,用一个输出函数接收之前方法返回的参数并新建一个新的result.txt输出。


流程图


关键代码说明:

  • 主函数 WordCount.java:

  用一个变量pathname记下控制台传入的参数,其意义是文件的绝对地址,它将作为计数函数的参数。创建数个变量分别记下字符数,单词数,有效行数以及储存单词与出现次数关系的map。,它们的值分别与计数函数的返回值一一对应。最后,把这些值传入printFile函数中,打印并创建result.txt。

 7 public class WordCount {
 8     public static void main(String[] args) {
 9         String pathname = args[0];
10         int characters = 0;
11         int words = 0;
12         int lines = 0;
13         Map<String, String> map = new HashMap<String, String>();
14         /*查询&统计*/
15         characters = lib.countChar(pathname);
16         words = lib.countWord(pathname);
17         lines = lib.countLines(pathname);
18         map = lib.countFrequency(pathname);
19         /*输出结果*/
20         lib.printFile(characters, words, lines, map);
21         System.out.println("completed");
22     }
23 }
  • 读入文件通用

  每个计数函数中通用的打开文件代码。

        try {
            String encoding = "UTF-8";
            File file = new File(filePath);
            if (file.isFile() && file.exists()) {
                InputStreamReader read = new InputStreamReader(new FileInputStream(file), encoding);
                [计数函数内容]
                read.close();
            } else {
                System.out.println("找不到指定的文件");
            }
        [计数函数内容] }
catch (Exception e) { System.out.println("读取文件内容出错"); e.printStackTrace(); }
  • 统计字符数 countChar:

  用bufferedreader按行读取文件内容并记下总行数,用stringbuffer转载每行数据并转换成string统计长度,字符数即为长度加上总行数减一(第一行不需要换行符)。

                int totalLine=0;
                /*读取文件数据*/
                StringBuffer sb=null;
                BufferedReader br1;
                try{
                    br1=new BufferedReader(new FileReader(file));
                    String temp=br1.readLine();
                    sb=new StringBuffer();
                    while(temp!=null){
                        sb.append(temp);
                        /*统计总行数*/
                        totalLine++;
                        temp=br1.readLine();
                    }
                }
                catch (Exception e){
                    e.printStackTrace();
                }
                /*读取的内容*/
                String info=sb.toString();
                /*统计字符个数*/
                characters=info.length()+totalLine-1;                    
  • 统计单词数 countWord:

  同上个方法得到包含文件内容的string,用split方法把字符串分割为一个个单词,将字符串前四个字符切割并把所有字母替换成 “” ,若字符串长度仍大于等于4,则判定为合格字符。

                /*读取文件数据*/
                StringBuffer sb = null;
                BufferedReader br1;
                try {
                    br1 = new BufferedReader(new FileReader(file));
                    String temp = br1.readLine();
                    sb = new StringBuffer();
                    while (temp != null) {
                        sb.append(temp);
                        sb.append(" ");//每行结束多读一个空格
                        temp = br1.readLine();

                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                String info = sb.toString();
                String s[] = info.split(",|\\.| |\\?|\\!|\\'|\t");
                /*统计单词个数*/for (int i = 0; i < s.length; i++) {
                    if (s[i].length() >= 4) {
                        String temp = s[i].substring(0, 4);
                        temp = temp.replaceAll("[^a-zA-Z]", "");
                        if (temp.length() >= 4) {
                            words++;
                        }
                    }
                }
  • 统计有效行数 countLines:

  比较简单,没什么好说的,就是读取时判断如果每行不为空则计数器+1。

                    br1 = new BufferedReader(new FileReader(file));
                    String temp = br1.readLine();
                    while (temp != null) {
                        /*统计有效行数*/
                        if (!temp.isEmpty()) {
                            lines++;
                        }
                        temp = br1.readLine();

                    }
  • 统计单词出现次数 countFrequency:

  本次作业难点之一。先把记录文件内容的string分割为一个个单词并判断是否合法,方法同上。之后建立一个map用来存放单词与其出现次数,若遇到map中没有出现的单词则将单词加入map,若已存在则value+1。全部单词录入完成后对map进行排序,将排序后的map返回。

          Map<String,String> map = new HashMap<String,String>();                
          String info=sb.toString(); String s[]=info.split(",|\\.| |\\?|\\!|\\'|\t"); /*统计单词个数*/ for(int i=0;i<s.length;i++){ if(s[i].length()>=4) { String temp = s[i].substring(0, 4); temp=temp.replaceAll("[^a-zA-Z]", ""); if (temp.length() >= 4) { if (map.containsKey(s[i].toLowerCase())) {//判断Map集合对象中是否包含指定的键名 map.put(s[i].toLowerCase(), Integer.parseInt(map.get(s[i].toLowerCase())) + 1 + ""); } else { map.put(s[i].toLowerCase(), 1 + ""); } } } } /*map排序*/ map=sortMapByValue(map);
  • 创建新文件 printFile:

  通过传入的int characters, int words, int lines, Map<String, String> map,以特定格式在当前目录下新建/更新result.txt。

  代码略。
  • map排序

  因为java的map和c++不同,貌似不会自动排序,因此只好自己写排序函数。

/*map排序*/
    public static Map<String, String> sortMapByValue(Map<String, String> oriMap) {
        if (oriMap == null || oriMap.isEmpty()) {
            return null;
        }
        Map<String, String> sortedMap = new LinkedHashMap<String, String>();
        List<Map.Entry<String, String>> entryList = new ArrayList<Map.Entry<String, String>>(
                oriMap.entrySet());
        Collections.sort(entryList, new MapValueComparator());
        Iterator<Map.Entry<String, String>> iter = entryList.iterator();
        Map.Entry<String, String> tmpEntry = null;
        while (iter.hasNext()) {
            tmpEntry = iter.next();
            sortedMap.put(tmpEntry.getKey(), tmpEntry.getValue());
        }
        return sortedMap;
    }
  • 比较器MapValueComparator:
 1 import java.util.Comparator;
 2 import java.util.Map;
 3 
 4 public class MapValueComparator implements Comparator<Map.Entry<String, String>> {
 5 
 6     @Override
 7     public int compare(Map.Entry<String, String> me1, Map.Entry<String, String> me2) {
 8         int flag=me2.getValue().compareTo(me1.getValue());
 9         if(flag==0){
10             flag=me1.getKey().compareTo(me2.getKey());
11         }
12         return flag;
13         //return me1.getValue().compareTo(me2.getValue());
14     }
15 }

计算模块接口部分的性能改进

  因为一开始写的时候比较随意,把几个统计方法都揉在一个方法里写了,这样复用效率不高,因此我将原先的大方法里的各个代码块独立出来,形成了最终的方法。并把开发过程中的一些冗余代码进行了删减。

计算模块部分单元测试展示

  测试用例1

    

  测试结果1

    

      

  测试用例2

  测试结果2


计算模块部分异常处理说明

  通过try catch语句处理异常,在程序出错时方便查找出错点。当路径出错时,提示找不到指定文件;当无法打开文件时,提示无法打开文件。因为我对这方面知识较少,因此不太掌握这方面的知识,不是特别理解。


感想:

  经过这次实践,我认识到了准备工作和自学能力的重要性。开始编写时没有做清楚计划,写完程序后代码揉成一团,之后找出关键语句单独封装耗费了很多时间。在开发过程中遇到很多复杂的问题,如果没有掌握自学能力的话学起来非常耗时且困难。第一次按照较规范化的格式编写代码,有很多地方代码冗余或者缺乏注释,我以后会逐渐改正的。

原文地址:https://www.cnblogs.com/cykablyat/p/9620614.html