java实现wc功能

 
项目相关要求
  • 基本功能
  1. 统计源文件(.java)的字符数,词数目,行数(完成)
  1. 统计txt的字符数,词数目,行数(完成)
  1. 统计docx的字符数,词数目,行数(完成)
  • 拓展功能
  1. 递归处理目录下符合条件的文件(完成)
  1. 返回更复杂的数据(代码行 / 空行 / 注释行)(完成)
  • 高级功能
  1. 实现图形界面(完成)

注:

开发语言: java(jdk1.8)

开发环境: windows

开发工具: idea

 
遇到的困难及解决方法
  • 困难描述
  1. 字符,词的统计规则
  1. 读取文件中的中文出现乱码,解决方案是改用gbk格式读取文件,但存在弊端;
  1. 不能判断文件编码格式,从而实现动态读取文件而不乱码
  1. swing控件文本内容的动态更新
  • 做过哪些尝试
  1. 选择腾讯文档对字词的统计规则
  1. 更换流,更换读取方式,指定读取文件时的编码
  1. 获取文件后缀名判断法
  1. 使用hashmap存储所需要更改的组件,在触发事件时调出相应组件
  • 是否解决
  1. 是,但存在硬编码问题,并未做操作系统的判断
  1. 是,但获取后缀名法存在风险,更安全的方法是读取文件开头的十六进制编码进行判断
  • 有何收获
  1. 复习了java的swing
  1. 复习了java的io流
  1. 对编码,格式等问题有了进一步的了解和重视
  1. 逻辑思维的锻炼
关键代码or设计说明
贴出你认为的关键代码或者设计图,并进行解释
 
读取word文档的方法
不同于可以直接使用流来读取txt等文件的内容,针对office文件,java提供对应的jar包:poi包;使用该包即可轻易读取word文档,这里我仅对docx格式的word文档进行读取,doc格式的文件读取与此大同小异,故不作赘述.
try (InputStream is = new FileInputStream(file)) {
StringBuffer sb = new StringBuffer();
XWPFDocument xp = new XWPFDocument(is);
XWPFWordExtractor wordReader = new XWPFWordExtractor(xp);
String[] line = wordReader.getText().split("\n");
 
if (line.length < 1 || line == null) {
return null;
}
 
for (int j = 0; j < line.length; j++) {
System.out.println(line[j]);
lineNum += 1;
if (line[j].trim().toString().length() <= 1) {
spaceLineNum += 1;
continue;
}
String[] result = calcuChar(line[j]).split("==");
wordNum += Integer.parseInt(result[0]);
charNum += Integer.parseInt(result[1]);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

 

对于注释行的计算逻辑:

这里的注释指: //  和  /**/;对于 // 的校验相对简单,关键在于对 /**/的校验;这里flag就发挥了作用,即在前面的行扫描中是否遇到了/*.如果该次扫描检测到该行存在/*,flag则变为true,这样在一定程度上对后面扫描到的* 和*/能有一定的可鉴程度,当扫面到*或者*/时,flag为true代表改行为注释行,否则为代码或者空行,在尚未能够判断遇到的行是否为注释行时,temp负责存储存在疑问的行数,待能确定flag的值时,相当于计时器的存在.

            boolean flag = false;
            int temp = 0;
            while ((line = br.readLine()) != null) {
                System.out.println("该行内容为:" + line);
                lineNum += 1;
                System.out.println();
                if (line.trim().length() <= 1) {
                    spaceLineNum += 1;
                    continue;
                }


                if (line.trim().contains("//")) {
                    annotationNum += 1;
                    continue;
                }
                if (line.trim().contains("/*")) {
                    temp += 1;
                    flag = true;
                }

                if (!line.trim().contains("/*") && !line.trim().contains("*/") && line.trim().contains("*")) {
                    if (flag) {
                        temp += 1;
                    }
                }

                if (line.trim().contains("*/")) {
                    if (flag) {
                        flag = false;
                        annotationNum = temp + 1;
                        temp = 0;
                        if (lineNum > annotationNum) {
                            lineNum = lineNum - annotationNum;
                        }
                        continue;
                    }
                }
}

字符的计算

这里对字符的计算进行封装,采取的策略是一行一行的计算字符,最后因为返回值限制,故而返回字符串形式,"词数==字符数",这里双等号的意义是为方便split方法.

 public static String calcuChar(String line) {
        int wordNum = 0;
        int charNum = 0;
        for (int i = 0; i < line.split("\\s+").length; i++) {
            String tempWord = line.split("\\s+")[i].trim();
            if ("".equals(tempWord) || tempWord == null) {
                continue;
            }

            if (pattern.matcher(tempWord).find()) {
                char c[] = tempWord.toCharArray();
                //汉字前一个字母是否已经被算入
                boolean flag = true;
                if (pattern.matcher(String.valueOf(c[0])).matches()) {
                    wordNum += 1;
                    charNum += 2;
                    flag = false;
                } else {
                    charNum += 1;
                }
                for (int k = 1; k < c.length; k++) {
                    if (pattern.matcher(String.valueOf(c[k])).matches()) {
                        if (flag) {
                            wordNum += 1;
                            flag = false;
                        }
                        wordNum += 1;
                        charNum += 2;
                    } else {
                        flag = true;
                        charNum += 1;
                    }
                }
                if (flag) {
                    wordNum += 1;
                }
            } else {
                wordNum += 1;
                charNum += line.split("\\s+")[i].trim().length();
            }
        }
        return wordNum + "==" + charNum;
    }

图形界面的构建

最初采用hashmap存取组件来实现更新时获取对应组件进行更新.但后面因加入了多线程读取多个文件的缘故,舍弃了该方法,将图形界面的构建分成三部分,代码一是主界面的构建,也算是程序启动的入口.代码二是主界面按钮的实现以及监听事件;监听事件里每次读取到一个文件就new一个线程创建该文件的显示界面,再通过代码三来实现数据的绑定;这里用到的ResultVo是因返回参数的数量较多,故而将这些属性封装成一个类,参考代码四

public static void main(String[] args) {
        JFrame frame = new JFrame("主页面");

        frame.setSize(350, 200);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


        JPanel panel = new JPanel();
        frame.add(panel);

        placeComponents(panel);

        // 设置界面可见
        frame.setVisible(true);
    }
View Code
private static void placeComponents(JPanel panel) {

        panel.setLayout(null);

        JLabel userLabel = new JLabel("请选择文件:");
        userLabel.setBounds(10, 20, 80, 25);
        JLabel findFiles = new JLabel("递归回显:");
        findFiles.setBounds(10, 60, 80, 25);
        panel.add(userLabel);
        panel.add(findFiles);

//        递归读文件
        JButton jButton2 = new JButton("请选择文件");
        jButton2.setBounds(100, 60, 100, 30);
        jButton2.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JFileChooser chooser = new JFileChooser("C:\\Users\\hp\\Desktop");
                chooser.setMultiSelectionEnabled(false);
                chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
                int returnVal;
                returnVal = chooser.showOpenDialog(jButton2);
                if (returnVal == JFileChooser.OPEN_DIALOG) {
                    System.out.println(chooser.getSelectedFile());
                    List<String> pathList = findFilePath(chooser.getSelectedFile().getAbsolutePath());
                    if (pathList != null) {
                        JFrame frame = new JFrame("文件列表");
                        frame.setSize(350, 200);
                        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                        JPanel panel = new JPanel();
                        frame.add(panel);
                        frame.setVisible(true);
                        int step = 10;
                        for (String path : pathList) {
                            System.out.println(path);
                            JLabel fileName = new JLabel("文件:"+path);
                            fileName.setBounds(10, 10+step, 80, 25);
                            panel.add(fileName);
                            step += 10;
                        }
                    } else {
                        JOptionPane.showMessageDialog(null, "该文件夹下不存在符合条件的文件", "扫描失败", JOptionPane.ERROR_MESSAGE);
                    }
                }
            }
        });
        panel.add(jButton2);
        JButton jButton = new JButton("请选择文件");
        jButton.setBounds(100, 20, 100, 30);
        jButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JFileChooser chooser = new JFileChooser("C:\\Users\\hp\\Desktop");
                chooser.setMultiSelectionEnabled(true);

                int returnVal;
                returnVal = chooser.showOpenDialog(jButton);
                if (returnVal == JFileChooser.APPROVE_OPTION) {
                    File[] files = chooser.getSelectedFiles();
                    for (File f : files) {
                        if (f.getAbsolutePath().substring(f.getAbsolutePath().lastIndexOf(".") + 1).equals("docx")) {
                            new Thread(new Runnable() {
                                @Override
                                public void run() {
                                    ResultVo vo = ReadUtils.readDoc(f);
                                    if (vo == null) {
                                        JOptionPane.showMessageDialog(null, "文件路径错误", "扫描失败", JOptionPane.ERROR_MESSAGE);
                                    } else {
                                        JFrame frame = new JFrame("文件信息");
                                        frame.setSize(350, 200);
                                        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                                        JPanel panel = new JPanel();
                                        frame.add(panel);
                                        frame.setVisible(true);
                                        placeComponents(panel, vo);
                                    }
                                }
                            }).start();
                        } else if (f.getAbsolutePath().substring(f.getAbsolutePath().lastIndexOf(".") + 1).equals("txt") ||
                                f.getAbsolutePath().substring(f.getAbsolutePath().lastIndexOf(".") + 1).equals("java")) {
                            new Thread(new Runnable() {
                                @Override
                                public void run() {
                                    ResultVo vo = ReadUtils.readTxt(f);
                                    if (vo == null) {
                                        JOptionPane.showMessageDialog(null, "文件路径错误", "扫描失败", JOptionPane.ERROR_MESSAGE);
                                    } else {
                                        System.out.println(1);
                                        JFrame frame = new JFrame("文件信息");
                                        frame.setSize(350, 200);
                                        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                                        JPanel panel = new JPanel();
                                        frame.add(panel);
                                        frame.setVisible(true);
                                        placeComponents(panel, vo);
                                    }
                                }
                            }).start();
                        } else {
                            JOptionPane.showMessageDialog(null, "暂不支持对该类型文件的扫描 ", "错误 ", JOptionPane.ERROR_MESSAGE);
                        }
                    }
                }
            }
        });
        panel.add(jButton);
    }
View Code
 private static void placeComponents(JPanel panel, ResultVo vo) {
        JLabel fileName = new JLabel("文件名:" + vo.getFileName());
        fileName.setBounds(10, 30, 80, 25);
        panel.add(fileName);

        JLabel charNum = new JLabel("字符数:" + vo.getCharNum());
        charNum.setBounds(10, 40, 80, 25);
        panel.add(charNum);

        JLabel wordNum = new JLabel("词数:" + vo.getWordNum());
        wordNum.setBounds(10, 55, 80, 25);
        panel.add(wordNum);

        JLabel lineNum = new JLabel("行数:" + vo.getLineNum());
        lineNum.setBounds(10, 70, 80, 25);
        panel.add(lineNum);

        JLabel spaceLineNum = new JLabel("空行数:" + vo.getSpaceLineNum());
        spaceLineNum.setBounds(10, 85, 80, 25);
        panel.add(spaceLineNum);

        JLabel annotationNum = new JLabel("注释行数:" + 
        vo.getAnnotationNum());
        annotationNum.setBounds(10, 100, 80, 25);
        panel.add(annotationNum);
    }
View Code
public class ResultVo {
    private int wordNum = 0;
    private int charNum = 0;
    private int lineNum = 0;
    private int spaceLineNum = 0;
    private int annotationNum = 0;
    private String fileName = "";

    public int getWordNum() {
        return wordNum;
    }

    public void setWordNum(int wordNum) {
        this.wordNum = wordNum;
    }

    public int getCharNum() {
        return charNum;
    }

    public void setCharNum(int charNum) {
        this.charNum = charNum;
    }

    public int getLineNum() {
        return lineNum;
    }

    public void setLineNum(int lineNum) {
        this.lineNum = lineNum;
    }

    public int getSpaceLineNum() {
        return spaceLineNum;
    }

    public void setSpaceLineNum(int spaceLineNum) {
        this.spaceLineNum = spaceLineNum;
    }

    public int getAnnotationNum() {
        return annotationNum;
    }

    public void setAnnotationNum(int annotationNum) {
        this.annotationNum = annotationNum;
    }

    @Override
    public String toString() {
        return "ResultVo{" +
                "wordNum=" + wordNum +
                ", charNum=" + charNum +
                ", lineNum=" + lineNum +
                ", spaceLineNum=" + spaceLineNum +
                ", annotationNum=" + annotationNum +
                ", fileName='" + fileName + '\'' +
                '}';
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }
}
View Code

项目测试

启动程序

多文件读取(docx,txt,java,空文件):

递归回显:

 

 非法读取校验:

 

 代码覆盖率:

整体覆盖率80%

PSP
Planning
计划
 预估耗时(分钟 实际耗时(分钟) 
· Estimate
· 估计这个任务需要多少时间
180  240
Development
开发
 150  180
· Analysis
· 需求分析 (包括学习新技术)
 40  40
· Design Spec
· 生成设计文档
 30  30
· Design Review
· 设计复审 (和同事审核设计文档)
   
· Coding Standard
· 代码规范 (为目前的开发制定合适的规范)
 20  20
· Design
· 具体设计
 20  20
· Coding
· 具体编码
 150  180
· Code Review
· 代码复审
 20  20
· Test
· 测试(自我测试,修改代码,提交修改)
 60  60
Reporting
报告
 100  100
· Test Report
· 测试报告
 10  7
· Size Measurement
· 计算工作量
 30  30
· Postmortem & Process Improvement Plan
· 事后总结, 并提出过程改进计划
 10  10
 
合计
 640  697
 
 
 总结
  最初是想用网页作为图形化界面,因为自身对前端的熟悉程度要远熟于java的swing.但最终还是选择了swing,也算作是复习.此次就我个人而言,正则表达式发挥了极大的作用,节省了不少代码量.加之其应用及其广泛,有时间需要好好弥补这方面的知识,这样用起来才能更加得心应手.

 
 
 
posted @ 2018-09-13 00:57  黑鴉  阅读(125)  评论(0编辑  收藏  举报