GitHub项目地址:https://github.com/JackyLin18/word-count
Word Count 项目要求:
wc.exe 是一个常见的工具,它能统计文本文件的字符数、单词数和行数。这个项目要求写一个命令行程序,模仿已有wc.exe 的功能,并加以扩充,给出某程序设计语言源文件的字符数、单词数和行数。
实现一个统计程序,它能正确统计程序文件中的字符数、单词数、行数,以及还具备其他扩展功能,并能够快速地处理多个文件。
具体功能要求:
程序处理用户需求的模式为:
wc.exe [parameter] [file_name]
基本功能列表:
wc.exe -c file.c //返回文件 file.c 的字符数
wc.exe -w file.c //返回文件 file.c 的词的数目
wc.exe -l file.c //返回文件 file.c 的行数
扩展功能:
-s 递归处理目录下符合条件的文件。
-a 返回更复杂的数据(代码行 / 空行 / 注释行)。
空行:本行全部是空格或格式控制字符,如果包括代码,则只有不超过一个可显示的字符,例如“{”。
代码行:本行包括多于一个字符的代码。
注释行:本行不是代码行,并且本行包括注释。一个有趣的例子是有些程序员会在单字符后面加注释:
} //注释
在这种情况下,这一行属于注释行。
[file_name]: 文件或目录名,可以处理一般通配符。
高级功能:
-x 参数。这个参数单独使用。如果命令行有这个参数,则程序会显示图形界面,用户可以通过界面选取单个文件,程序就会显示文件的字符数、行数等全部统计信息。
需求举例:
wc.exe -s -a *.c ===> 返回当前目录及子目录中所有*.c 文件的代码行数、空行数、注释行数。
解题思路:
分析项目要求,核心内容是需要读取指定文件,使用jdk提供的FileReader类和BufferedReader类,可以便捷地实现读取文件和记录字符数、行数的功能。
设计实现过程:
- 项目的代码构成:一共需要编写四个类,分别为:WordCountMain、FileUtil、ParamsUtil、FileFrame。
- WordCountMain类为程序入口类,其中包括main()方法和show()方法。main()方法的功能:调用相关方法对用户输入的参数进行判断和操作,根据用户的输入调用不同的功能方法,输出操作结果。show()方法的功能:打印用户菜单。
- FileUtil类为文件操作类,是项目的核心类,其中包括getFileList()方法,getCharsetsCount()方法,getWordsCount()方法,getLinesCount()方法和getCBNLinesCount()方法共五个静态方法。①getFileList()方法的功能:根据传入的文件是否为文件夹:是文件夹,遍历文件夹中的每一个文件。如果文件名符合通配符,将文件放入List中返回。②getCharsetsCount()方法的功能是:返回指定文件的字符数。③getWordsCount()方法的功能是:返回指定文件的单词数。④getLinesCount()方法的功能是:返回指定文件的行数目。⑤getCBNLinesCount()方法的功能是:返回指定文件的代码行数、空白行数、注释行数。
- ParamsUtil类为参数检查类,用于检查用户输入的参数是否合法,其中包括isExe()方法、isOperation()方法、isFileExist()方法这三个private方法和checkInputParams()方法这一个public方法。
- FileFrame类为用户提供图形界面,便于用户进行文件选择操作。
- 程序运行流程:程序启动,提示用户输入参数,程序通过调用ParamsUtil的checkInputParams()方法判断输入的参数是否合法,如果不合法,要求用户重新输入;如果合法,根据用户输入的参数调用对应的FileUtil类的相关方法,获得文件的相关数据后进行输出反馈给用户。
- 用户图形界面(GUI)说明:用户输入参数 “-frame” 后,调出图形界面。用户在图形界面可选择文件或者文件夹,根据用户选择的文件或者文件夹,程序自动识别出所有匹配的后缀供用户选择。同时,用户可在操作选项中选择需要进行的操作(-c, -w, -l, -a),用户选择完毕后点击确定,程序弹出窗口返回文件信息。
代码说明:
- FileUtil.getFileList(String filePath, String consistent)
1 // 根据传入的路径,如果表示的是一个文件夹,返回该文件夹下的所有符合通配符的子文件 2 public static List<File> getFileList(String filePath, String consistent) { 3 File file = new File(filePath); 4 // 存放遍历过程中的文件夹 5 LinkedList<File> temp_fileList = new LinkedList<>(); 6 // 存放需要返回的符合条件的子文件 7 List<File> fileList = new ArrayList<>(); 8 // 先将传入的指定文件放入temp_fileList中 9 temp_fileList.add(file); 10 // 如果该文件不是文件夹,将其放置fileList中并返回 11 if (!file.isDirectory()) { 12 // 如果通配符为默认的 ".*" 将所有文件加入fileList 13 if (consistent.equals(".*")) { 14 fileList.add(file); 15 } else { 16 // 如果文件与指定的格式相符,将其加入返回的fileList 17 if (filePath.endsWith(consistent)) { 18 fileList.add(file); 19 } 20 } 21 return fileList; 22 } 23 /* 24 如果temp_fileList中的每一个元素,如果该元素为文件夹,将整个元素放至temp_fileList中, 25 如果该元素为文件,但不符合通配符,不做任何处理 26 如果改文件为文件,且符合通配符,将其放至fileList中 27 */ 28 while (!temp_fileList.isEmpty()) { 29 // 遍历temp_fileList 30 for (File f : Objects.requireNonNull(temp_fileList.removeFirst().listFiles())) { 31 // 如果该元素为文件夹,将其放至temp_fileList中 32 if (f.isDirectory()) { 33 temp_fileList.add(f); 34 } else { 35 // 如果通配符为默认的 ".*" 将所有文件加入fileList 36 if (consistent.equals(".*")) { 37 fileList.add(f); 38 } else { 39 // 如果文件与指定的格式相符,将其加入返回的fileList 40 if (f.getName().endsWith(consistent)) { 41 fileList.add(f); 42 } 43 } 44 } 45 } 46 } 47 return fileList; 48 }
- FileUtil.getCharsetsCount(String filePath)
1 // "-c"操作:传入一个文件路径,返回该文件的字符数 2 public static String getCharsetsCount(String filePath) throws IOException { 3 File file = new File(filePath); 4 int charsetsCount = 0; 5 String str = null; 6 // 装饰模式,使其获得多功能 7 FileReader fileReader = new FileReader(file); 8 BufferedReader reader = new BufferedReader(fileReader); 9 while ((str = reader.readLine()) != null) { 10 // 将所有的空格去除 11 str = str.replaceAll(" ", ""); 12 charsetsCount += str.length(); 13 } 14 fileReader.close(); 15 // 返回指定文件文件名及其字符数 16 return "指定文件" + filePath + "的字符数:" + charsetsCount; 17 }
- FileUtil.getWordsCount(String filePath)
1 // "-w"操作:传入一个文件路径,返回该文件的词数 2 public static String getWordsCount(String filePath) throws IOException { 3 File file = new File(filePath); 4 int wordsCount = 0; 5 String str = null; 6 // 装饰模式,使其获得多功能 7 FileReader fileReader = new FileReader(file); 8 BufferedReader reader = new BufferedReader(fileReader); 9 while ((str = reader.readLine()) != null) { 10 // 两个单词之间使用空格分开 11 String[] strArray = str.split(" "); 12 for (String s : strArray) { 13 if (!s.equals("")) { 14 wordsCount++; 15 } 16 } 17 } 18 fileReader.close(); 19 // 返回指定文件文件名及其单词数 20 return "指定文件" + filePath + "的单词数:" + wordsCount; 21 }
- FileUtil.getLinesCount(String filePath)
1 // "-l"操作:传入一个文件路径,返回该文件的行数 2 public static String getLinesCount(String filePath) throws IOException { 3 File file = new File(filePath); 4 int linesCount = 0; 5 // 装饰模式,使其获得多功能 6 FileReader fileReader = new FileReader(file); 7 BufferedReader reader = new BufferedReader(fileReader); 8 while ((reader.readLine()) != null) { 9 // 每读取一行,行数加一 10 linesCount++; 11 } 12 fileReader.close(); 13 // 返回指定文件文件名及其行数目 14 return "指定文件" + filePath + "的行数目:" + linesCount; 15 }
- FileUtil.getCBNLinesCount(String filePath)
1 // "-a"操作:传入一个文件路径,输出该文件的代码行数、空行数、注释行数 2 3 /** 4 * 代码行:本行包括多于一个字符的代码 5 * 空行:本行全部是空格或格式控制字符,如果包括代码,则只有不超过一个可显示的字符,例如 "{" 6 * 注释行:本行不是代码行,并且本行包括注释。一个有趣的例子是有些程序员会在单字符后面加注释: 7 * } //注释 8 * 在这种情况下,这一行属于注释行 9 */ 10 public static List<String> getCBNLinesCount(String filePath) throws IOException { 11 int codeLineCounts = 0; // 代码行数目 12 int blankLineCounts = 0; // 空白行数目 13 int noteLineCounts = 0; // 注释行数目 14 File file = new File(filePath); 15 // 装饰模式,使其获得多功能 16 FileReader fileReader = new FileReader(file); 17 BufferedReader reader = new BufferedReader(fileReader); 18 String str = null; 19 while ((str = reader.readLine()) != null) { 20 // 去掉空格 21 str = str.replaceAll(" ", ""); 22 /* 23 如果包含 "//" 但是注释外的字符多于一个,为代码行 24 如果包含 "//" 而且注释外的字符少于或等于一个,为注释行 25 */ 26 if (!str.contains("//")) { 27 if (str.length() > 1) { 28 codeLineCounts++; 29 } else { 30 blankLineCounts++; 31 } 32 } else { 33 if (str.substring(0, str.indexOf("//")).length() > 1) { 34 codeLineCounts++; 35 } else { 36 noteLineCounts++; 37 } 38 } 39 } 40 // 返回指定文件文件名及其代码行数、空白行数、注释行数 41 List<String> messages = new ArrayList<>(); 42 messages.add("指定文件" + filePath + "的代码行数目:" + codeLineCounts + "\n"); 43 messages.add("指定文件" + filePath + "的空白行数目:" + blankLineCounts + "\n"); 44 messages.add("指定文件" + filePath + "的注释行数目:" + noteLineCounts + "\n"); 45 fileReader.close(); 46 return messages; 47 }
- WordCountMain.main(String[] args)
1 // 程序主入口 2 public static void main(String[] args) throws IOException, ClassNotFoundException, UnsupportedLookAndFeelException, InstantiationException, IllegalAccessException { 3 // 提供给用户菜单 4 show(); 5 // 读取用户输入的操作 6 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); 7 String input = null; 8 // 创建一个判断输入参数的类实例 9 ParamsUtil paramsUtil = new ParamsUtil(); 10 while ((input = reader.readLine()) != null) { 11 if(input.contains("-frame")){ 12 new FileFrame(); 13 } 14 // 判断用户的输入是否合法,如果合法,按用户要求的操作执行,如果不合法,要求用户重新输入 15 // 对输入进行 "添加通配符" 的处理 16 if (!input.contains("-s")) { 17 // 如果没有输入"-s",自动添加默认的通配符 18 input = input.concat(" .*"); 19 } else { 20 // 如果输入了"-s",但没有输入通配符,也添加默认的通配符 21 String[] params = input.split(" "); 22 if (!params[params.length - 1].contains(".")) { 23 input = input.concat(" .*"); 24 } 25 }27 if (paramsUtil.checkInputParams(input)) { 28 // 获得用户输入的参数数组 29 String[] params = input.split(" "); 30 int paramsLength = params.length; 31 for (int i = 1; i < paramsLength; i++) { 32 // FileUtil#getFileList()第一个参数为指定的文件路径,第二个参数为指定文件的通配符 33 switch (params[i]) { 34 case "-c": 35 // 输出指定文件的字符数 36 for (File f : FileUtil.getFileList(params[paramsLength - 2], 37 params[paramsLength - 1])) { 38 System.out.println(FileUtil.getCharsetsCount(f.getPath())); 39 } 40 break; 41 case "-w": 42 // 输出指定文件的单词数 43 for (File f : FileUtil.getFileList(params[paramsLength - 2], 44 params[paramsLength - 1])) { 45 System.out.println(FileUtil.getWordsCount(f.getPath())); 46 } 47 break; 48 case "-l": 49 // 输出指定文件的行数 50 for (File f : FileUtil.getFileList(params[paramsLength - 2], 51 params[paramsLength - 1])) { 52 System.out.println(FileUtil.getLinesCount(f.getPath())); 53 } 54 break; 55 case "-a": 56 // 输出指定文件的代码行数、空白行数、注释行数 57 for (File f : FileUtil.getFileList(params[paramsLength - 2], 58 params[paramsLength - 1])) { 59 for(String message:FileUtil.getCBNLinesCount(f.getPath())){ 60 System.out.print(message); 61 } 62 } 63 break; 64 default: 65 break; 66 } 67 } 68 } 69 // 再次显示菜单提示用户输入 70 show(); 71 } 72 }
- FileFrame
1 public class FileFrame implements ActionListener { 2 JFrame frame = new JFrame("文件选择"); 3 JTabbedPane tabPane = new JTabbedPane();// 选项卡布局 4 Container container = new Container(); 5 JPanel panel = new JPanel(); 6 JLabel directoryLabel = new JLabel("文件目录"); 7 JLabel fileLabel = new JLabel("选择文件"); 8 JLabel consistentLabel = new JLabel("指定后缀"); 9 JLabel operationLabel = new JLabel("选择操作"); 10 // 显示选择的文件夹的路径 11 JTextField text1 = new JTextField(); 12 // 显示选择的文件的路径 13 JTextField text2 = new JTextField(); 14 // 文件夹选择按钮 15 JButton directoryButton = new JButton("选择"); 16 // 文件选择按钮 17 JButton fileButton = new JButton("选择"); 18 // 后缀名下拉框 19 JComboBox<String> comboBox = new JComboBox<>(); 20 // 操作复选框 21 JCheckBox jCheckBoxC = new JCheckBox("-c"); 22 JCheckBox jCheckBoxW = new JCheckBox("-w"); 23 JCheckBox jCheckBoxL = new JCheckBox("-l"); 24 JCheckBox jCheckBoxA = new JCheckBox("-a"); 25 // 文件选择器 26 JFileChooser fileChooser = new JFileChooser(); 27 // 提交按钮 28 JButton submitButton = new JButton("确定"); 29 // 用一个List存放查询结果 30 List<String> messages = new ArrayList<>(); 31 // 用一个List存放选择的操作 32 List<JCheckBox> checkBoxList = new ArrayList<>(); 33 34 { 35 // 设置按钮格式 36 directoryButton.setBorder(BorderFactory.createRaisedBevelBorder()); 37 directoryButton.setFocusPainted(false); 38 fileButton.setBorder(BorderFactory.createRaisedBevelBorder()); 39 fileButton.setFocusPainted(false); 40 submitButton.setBorder(BorderFactory.createRaisedBevelBorder()); 41 submitButton.setFocusPainted(false); 42 } 43 44 public FileFrame() throws ClassNotFoundException, UnsupportedLookAndFeelException, InstantiationException, IllegalAccessException { 45 String lookAndFeel = UIManager.getSystemLookAndFeelClassName(); 46 UIManager.setLookAndFeel(lookAndFeel); 47 48 // 获得显示屏幕的宽度 49 double lx = Toolkit.getDefaultToolkit().getScreenSize().getWidth(); 50 // 获得显示屏幕的高度 51 double ly = Toolkit.getDefaultToolkit().getScreenSize().getHeight(); 52 53 // 设置窗口出现的位置 54 frame.setLocation(new Point((int) (lx / 2) - 150, (int) (ly / 2) - 150)); 55 // 设置窗口的大小 56 frame.setSize(400, 220); 57 // 设置布局 58 frame.setContentPane(tabPane); 59 60 // 设置标签、按钮的位置和大小 61 fileLabel.setBounds(30, 20, 70, 20); 62 text2.setBounds(100, 20, 160, 20); 63 text2.setEditable(false); 64 fileButton.setBounds(270, 20, 80, 20); 65 66 directoryLabel.setBounds(30, 50, 70, 20); 67 text1.setBounds(100, 50, 160, 20); 68 text1.setEditable(false); 69 directoryButton.setBounds(270, 50, 80, 20); 70 71 consistentLabel.setBounds(30, 80, 70, 20); 72 comboBox.setBounds(100, 80, 160, 20); 73 74 operationLabel.setBounds(30, 110, 70, 20); 75 jCheckBoxC.setBounds(100, 110, 40, 20); 76 jCheckBoxW.setBounds(140, 110, 40, 20); 77 jCheckBoxL.setBounds(180, 110, 40, 20); 78 jCheckBoxA.setBounds(220, 110, 40, 20); 79 80 submitButton.setBounds(270, 110, 80, 20); 81 82 // 为三个按钮添加事件处理 83 directoryButton.addActionListener(this); 84 fileButton.addActionListener(this); 85 submitButton.addActionListener(this); 86 87 // 将组件全部添加到container中 88 container.add(directoryLabel); 89 container.add(text1); 90 container.add(directoryButton); 91 container.add(fileLabel); 92 container.add(text2); 93 container.add(fileButton); 94 container.add(submitButton); 95 container.add(consistentLabel); 96 container.add(comboBox); 97 container.add(operationLabel); 98 container.add(jCheckBoxC); 99 container.add(jCheckBoxW); 100 container.add(jCheckBoxL); 101 container.add(jCheckBoxA); 102 103 checkBoxList.add(jCheckBoxC); 104 checkBoxList.add(jCheckBoxW); 105 checkBoxList.add(jCheckBoxL); 106 checkBoxList.add(jCheckBoxA); 107 108 // 设置窗口可见 109 frame.setVisible(true); 110 // 设置关闭窗口结束程序 111 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 112 tabPane.add(container); 113 } 114 115 public void actionPerformed(ActionEvent e) { 116 File directoryChecked = null; 117 File fileChecked = null; 118 // 点击文件夹选择按钮,选择文件夹 119 if (e.getSource().equals(directoryButton)) { 120 // 设置其只能选择到文件夹 121 fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 122 // 打开文件选择器 123 int state = fileChooser.showOpenDialog(null); 124 // 如果关闭文件选择,直接返回;否则将选择的文件的文件名显示在面板上 125 if (state == 1) { 126 return; 127 } else { 128 directoryChecked = fileChooser.getSelectedFile(); 129 text1.setText(directoryChecked.getAbsolutePath()); 130 comboBox.removeAllItems(); 131 for (String s : getConsistentList()) { 132 comboBox.addItem(s); 133 } 134 container.add(comboBox); 135 tabPane.add(container); 136 } 137 } 138 // 点击文件选择按钮,选择文件 139 if (e.getSource().equals(fileButton)) { 140 // 设置其只能选择到文件 141 fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); 142 // 打开文件选择器 143 int state = fileChooser.showOpenDialog(null); 144 // 如果关闭文件选择,直接返回;否则将选择的文件的文件名显示在面板上 145 if (state == 1) { 146 return; 147 } else { 148 fileChecked = fileChooser.getSelectedFile(); 149 text2.setText(fileChecked.getAbsolutePath()); 150 comboBox.removeAllItems(); 151 for (String s : getConsistentList()) { 152 comboBox.addItem(s); 153 } 154 container.add(comboBox); 155 tabPane.add(container); 156 } 157 } 158 // 点击提交按钮后弹出窗口,显示查询结果 159 if (e.getSource().equals(submitButton)) { 160 if (!text1.getText().equals("")) { 161 for (File f : FileUtil.getFileList(text1.getText(), (String) comboBox.getSelectedItem())) { 162 addMessages(messages, f, checkBoxList); 163 } 164 } 165 if (!text2.getText().equals("")) { 166 for (File f : FileUtil.getFileList(text2.getText(), (String) comboBox.getSelectedItem())) { 167 addMessages(messages, f, checkBoxList); 168 } 169 } 170 JOptionPane.showMessageDialog(null, messages.toArray(), 171 "选择的文件的查询结果", JOptionPane.PLAIN_MESSAGE); 172 messages = new ArrayList<>(); 173 } 174 } 175 176 // 将指定的文件进行指定操作的查询,并将结果放置messages中 177 private void addMessages(List<String> messages, File f, List<JCheckBox> checkBoxList) { 178 try { 179 if (checkBoxList.get(0).isSelected()) { 180 messages.add(FileUtil.getCharsetsCount(f.getPath()) + "\n"); 181 } 182 if (checkBoxList.get(1).isSelected()) { 183 messages.add(FileUtil.getWordsCount(f.getPath()) + "\n"); 184 } 185 if (checkBoxList.get(2).isSelected()) { 186 messages.add(FileUtil.getLinesCount(f.getPath()) + "\n"); 187 } 188 if (checkBoxList.get(3).isSelected()) { 189 messages.addAll(FileUtil.getCBNLinesCount(f.getPath())); 190 } 191 } catch (IOException ex) { 192 ex.printStackTrace(); 193 } 194 } 195 196 // 获取选择的后缀 197 private List<String> getConsistentList() { 198 // 先获取选择的文件 199 String directoryPath = text1.getText(); 200 String filePath = text2.getText(); 201 // 创建一个List存放文件的后缀 202 List<String> consistentList = new ArrayList<>(); 203 consistentList.add(".*"); 204 // 遍历所有文件,获得选择的文件、文件夹中的子文件的所有后缀名 205 if (!filePath.equals("")) { 206 String s = filePath.substring(filePath.lastIndexOf(".")); 207 // 如果List中不存在这个后缀名,将其加入List 208 if (!consistentList.contains(s)) { 209 consistentList.add(s); 210 } 211 } 212 if (!directoryPath.equals("")) { 213 for (File f : FileUtil.getFileList(directoryPath, ".*")) { 214 String s = f.getPath().substring(f.getPath().lastIndexOf(".")); 215 // 如果List中不存在这个后缀名,将其加入List 216 if (!consistentList.contains(s)) { 217 consistentList.add(s); 218 } 219 } 220 } 221 return consistentList; 222 } 223 }
测试运行:
- 测试用例:



- 单个测试“-c, -w, -l, -a, -s”每一个操作:





- 同时进行多个操作:

- 调出用户图形界面


- 使用图形界面分别进行只选择文件、只选择文件夹和同时选择文件和文件夹的操作
对指定文件进行操作:

获得文件夹中所有的文件后缀名供用户选择:

对指定文件夹中的文件进行操作:

对选择的文件、文件夹进行操作:


PSP表格:

项目小结:
- 要做好一个项目,要把基本的框架构建好,做好前期的计划工作甚至要比编码要更重要。一个好的计划能让你事半功倍,在没有做好计划之前就动手编码会使你走很多的弯路。一个项目的开发重点不只在于代码的编写,前期的计划、项目分析、规范和设计,后期的复审、测试和改进同样重要。
- 项目完成了基本要求,扩展要求和高级要求,但实际的代码中还存在不足,前期的计划不够完善导致编写代码时还会走不少的弯路,有待改进。
posted on
浙公网安备 33010602011771号