diff --git a/221801427/README.md b/221801427/README.md new file mode 100644 index 00000000..6db2a0ad --- /dev/null +++ b/221801427/README.md @@ -0,0 +1,80 @@ +# PersonalProject-Java + + +---------- + + +## 功能简介 +输入英文文本(只考虑Ascii码,汉字不需考虑) +
1. 统计字符数(空格,水平制表符,换行符,均算字符) +
2. 统计有效行数(任何包含非空白字符的行,都需要统计) +
3. 统计单词数(至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写) +
4. 统计频率最高的10个单词的出现次数(值降序,键字典序) +
按以上顺序逐行打印至输出文本,输出的格式如下 + +> characters: number
+words: number
+lines: number
+word1: number
+word2: number
+... + + +---------- + + +## 运行方式 +运行cmd + + javac -encoding UTF-8 WordCount.java + java WordCount input.txt output.txt + + +---------- + + +## 作业链接 +[寒假作业(2/2) 作业要求](https://edu.cnblogs.com/campus/fzu/2021SpringSoftwareEngineeringPractice/homework/11740) + + +---------- + + +## 博客链接 +[我的博客](https://www.cnblogs.com/railgunSE/) + + +---------- + + +## 签入记录 +### 第一次commit +建立了基本架构,制定了代码规范 +### 第二次commit +实现字符计算,以及对应的文件读写功能 +### 第三次commit +重写了算法实现,增加计算行数功能 +### 第四次commit +增加了计算有效单词总数的功能 +### 第五次commit +增加了统计词频功能,输出功能也完整了 +### 第六次commit +改用StringBuilder提高性能,修改、增加了一些代码规范上的细节 +### 第七次commit +通过去删去不必要的字符操作,改进了部分算法效率 +### 第八次commit +修改了读取算法与计算行数算法以适应读取\r的需要 +### 第九次commit +输出格式,细节修改,所需功能基本完成完备 +### 第十次commit +提取了读取字符串并拆分为有效单词Map的方法,提高效率 +### 第十一次commit +改用NIO读取文件以提升性能 +### 第十二次commit +优化了性能:采用StringTokenizer分割字符串,采用多线程
删去了之前尝试各类算法时遗留的不必要的导入 +### 第十三次commit +合并计算字符数与单词数的类,添加注释与README文本
更改编码模式为UTF-8解决提交时的乱码问题 + + + + diff --git a/221801427/codestyle.md b/221801427/codestyle.md new file mode 100644 index 00000000..6e7c9d1e --- /dev/null +++ b/221801427/codestyle.md @@ -0,0 +1,110 @@ +## 代码规范 + +- 缩进 + +>1、使用的缩进的字符数为4个空格,用Tab(定义Tab键扩展成为4个空格键)来进行缩进,Tab键在不同的情况下会显示不同的长度,严重干扰阅读体验。4个空格的距离从可读性来说,正好。 + +>2、在使用if或while等嵌套结构时,在if或while等语句下再进行Tab(扩展为4个空格)缩进,使程序看起来更清晰、美观。 + +- 变量命名 + +>1、局部变量使用lowerCamelCase风格,必须遵从驼峰形式。局部变量名采用英文单词的来命名,这样更能明白变量的含义。绝不使用拼音与英文混合的方式或直接使用中文的方式。 +>>正例:studentId/teacherId +反例: DaZhePromotion [打折] / getPingfenByName() [评分] / int某变量 = 3 + +>2、遇到含义相同的变量,会在末尾用1,2来区别。但也有例外,在使用循环变量时,因无特殊含义,仅用i,j,k来定义。 +>>正例:p1/p2/p3 和 for(i = 0;。。。) + +>3、在标识符命名方面,少采用缩写,若采用缩写则查询国际通用的写法,用会发音的部分来命名,并在不容易理解的缩写命名后添加注释。标识符长度跟随所要表达的含义来增减长度,以使其更容易理解。在标识符命名中,最重要的是要保持一致性——在整个程序中,对变量或是函数一类使用相同的命名规则。 +>>正例:int temp 和 int tmp + +- 每行最多字符数 + +>1、每行行宽(含缩进)不允许超过100个字符。 + +>2、当一个程序行很长时,在适当位置运用回车键将程序分行,使其不破坏语句的完整性,也使语句看起来显得不混乱、零散。 + +- 函数最大行数 + +>1、一个函数允许包含语句的行数我限制为80行,不包括空行和注释。 + +>2、一个函数的最大长度和函数的复杂程度以及缩进大小成反比。 + +- 函数、类命名 + +>1、函数名使用lowerCamelCase风格,必须遵从驼峰形式。函数名用动词打头,以名词结尾的形式(少数会只有动词命名),使函数功能更容易理解。 +>>正例:countWord/getId + +>2、类名使用UpperCamelCase风格,必须遵从驼峰形式。 +>>正例:WordCount/Lib + +- 常量 + +>1、常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚。 +>>正例:public static final int YEAR = 365; +>>正例: MAX_STOCK_COUNT +反例: MAX_COUNT + +>2、如果变量值仅在一个范围内变化用Enum类。如果还带有名称之外的延伸属性,必须使用Enum类,类名后缀带Enum以清晰表示,枚举成员名称需要全大写,单词间用下划线隔开。下面正例中的数字就是延伸信息,表示星期几。 +>>正例:publicEnum{MONDAY(1),TUESDAY(2),WEDNESDAY(3), +THURSDAY(4),FRIDAY(5),SATURDAY(6),SUNDAY(7);} + +- 空行规则 + +>1、变量定义和方法实现间增加空行,便于阅读。 + +>2、在函数之间,增加空行来分割函数,这样使函数之间显得不拥挤,区分更加明显。 + +- 注释规则 + +>1、类、类方法的注释使用/*内容*/格式。 + +>2、一般在变量名后,添加一行注释。 +>>正例:char option; //功能选项 + +>3、边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。 + +>4、注释尽量只用ASCII字符,不要用中文或其他特殊字符,利于程序的可移植性。 + +- 操作符前后空格 + +>1、一元运算符与变量之间没有空格 +>>正例:i++/!flag + +>2、二元运算符与变量之间必须有空格。 +>>正例:int i = 0;/sum = x + y; + +- 大括号位置 + +>1、每个“{”和“}”都独占一行。 +>>正例:
if (condition)
+{
+  DoSomething();
+}
+else
+{
+  DoSomethingElse();
+} + +>2、在if等语句块较为简单时,也不省略左右大括号的使用,以使程序显得更清晰、工整。 + +- 其他规则 + +>1、需要使用变量时,在使用前定义,避免一些需要用到其值时,无法获得的情况。例如: +>>正例:int i; +    for(i = 0;。。。) + +>2、多个变量定义时分行写,使程序更加美观。 +>>正例:x = 1; +   y = 2; +   z = 3; + +>3、变量初始化时,提倡在尽可能小的作用域中声明变量,离第一次使用越近越好。这使得代码易于阅读,易于定位变量的声明位置、变量类型和初始值。 +>>正例:int i = 3; + +>4、多个不同的运算符同时存在时合理使用括号来明确优先级。 +>>例:2 << 2 + 1 * 3 - 4 +  2 << (2 + 1 * 3 - 4) + +>5、避免相同的代码段在多个地方出现相同的代码,必须归纳出来并且用一个类封装起来。 +语句嵌套层次不得超过3层,超出的必须抽取出中间函数。 \ No newline at end of file diff --git a/221801427/src/WordCount.java b/221801427/src/WordCount.java new file mode 100644 index 00000000..5ea66f62 --- /dev/null +++ b/221801427/src/WordCount.java @@ -0,0 +1,95 @@ +import lib.service.*; +import lib.tool.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +public class WordCount +{ + /** + * @param inputFileName + * @param outputFileName + */ + private static String inputFileName; + private static String outputFileName; + + public WordCount(String inputFileName, String outputFileName) + { + WordCount.inputFileName = inputFileName; + WordCount.outputFileName = outputFileName; + } + + public void Count() + { + final String content = FileReader.readFile(inputFileName);// 从文件读取字符串 + final HashMap words = StringAnalyser.analyseString(content);// 从字符串拆分有效单词,统计入HashMap + + ExecutorService executor = Executors.newCachedThreadPool(); + + Future charCnt = executor.submit(new Callable() + { + // 统计字符数 + public Integer call() + { + return CharAndWordCounter.countChar(content); + } + }); + + Future lineCnt = executor.submit(new Callable() + { + // 统计有效行数 + public Integer call() + { + return LineCounter.countLine(content); + } + }); + + Future wordCnt = executor.submit(new Callable() + { + // 统计有效单词数 + public Integer call() + { + return CharAndWordCounter.countWord(words); + } + }); + + Future>> freqList = executor + .submit(new Callable>>() + { + // 排序词频前10单词 + public ArrayList> call() + { + return FrequencySorter.sortFrequency(words); + } + }); + + try + { + FilePrinter.writeFile(charCnt.get(), wordCnt.get(), lineCnt.get(), freqList.get(), outputFileName);// 打印结果 + executor.shutdown(); + } + catch (InterruptedException | ExecutionException e) + { + e.printStackTrace(); + } + } + + public static void main(String[] args) + { + WordCount cmd; + + if (args.length != 2) + { + System.out.println("Invalid input"); + return; + } + cmd = new WordCount(args[0], args[1]);// 传入参数(输入输出文件名) + // cmd = new WordCount("src/input.txt", "src/output.txt"); + cmd.Count();// 统计 + } +} diff --git a/221801427/src/lib/service/CharAndWordCounter.java b/221801427/src/lib/service/CharAndWordCounter.java new file mode 100644 index 00000000..f45229ca --- /dev/null +++ b/221801427/src/lib/service/CharAndWordCounter.java @@ -0,0 +1,24 @@ +package lib.service; + +import java.util.HashMap; + +public class CharAndWordCounter +{ + /** + * @param content + * @return 字符总数 + */ + public static int countChar(String content) + { + return content.length();// 直接返回字符串长度 + } + + /** + * @param words + * @return 单词总数 + */ + public static int countWord(HashMap words) + { + return words.size();// 返回HashMap规模表示有效单词总数 + } +} diff --git a/221801427/src/lib/service/FrequencySorter.java b/221801427/src/lib/service/FrequencySorter.java new file mode 100644 index 00000000..139ec497 --- /dev/null +++ b/221801427/src/lib/service/FrequencySorter.java @@ -0,0 +1,29 @@ +package lib.service; + +import java.util.*; +import java.util.stream.Collectors; + +public class FrequencySorter +{ + public static final int MAX_SIZE = 10; + + /** + * @param words + * @return freqList + */ + public static ArrayList> sortFrequency(HashMap words) + { + words = words.entrySet().stream() + .sorted(Map.Entry.comparingByValue()// 值排序 + .reversed()// 倒序为降序 + .thenComparing(Map.Entry.comparingByKey()))// 键排序(字典序) + .limit(MAX_SIZE)// 前MAX_SIZE个 + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, + (oldVal, newVal) -> oldVal, LinkedHashMap::new));// 返回map + + ArrayList> freqList = new ArrayList>( + words.entrySet()); + + return freqList; + } +} diff --git a/221801427/src/lib/service/LineCounter.java b/221801427/src/lib/service/LineCounter.java new file mode 100644 index 00000000..9043817d --- /dev/null +++ b/221801427/src/lib/service/LineCounter.java @@ -0,0 +1,28 @@ +package lib.service; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LineCounter +{ + private static final String VALID_LINE_REGEX = "(^|\n)\\s*\\S+"; + private static final Pattern VALID_LINE_PATTERN = Pattern.compile(VALID_LINE_REGEX); + + /** + * @param content + * @return 有效行数 + */ + public static int countLine(String content) + { + int cnt = 0; + Matcher matcher; + + matcher = VALID_LINE_PATTERN.matcher(content); + while (matcher.find()) + { + //利用正则表达式匹配有效行,空行不统计,包括\t \r \n 空格以及由他们组成的情况 + cnt++; + } + return cnt; + } +} diff --git a/221801427/src/lib/service/StringAnalyser.java b/221801427/src/lib/service/StringAnalyser.java new file mode 100644 index 00000000..c6b687dd --- /dev/null +++ b/221801427/src/lib/service/StringAnalyser.java @@ -0,0 +1,45 @@ +package lib.service; + +import java.util.*; +import java.util.regex.Pattern; + +public class StringAnalyser +{ + private static final String FILTER_REGEX = "[^0-9A-Za-z]"; + private static final String VALID_WORD_REGEX = "[a-z]{4}[0-9a-z]*"; + + /** + * @param content + * @return words + */ + public static HashMap analyseString(String content) + { + int cnt; // 临时变量用于计入键值(单词出现次数) + String word; + + HashMap words = new HashMap(); + StringTokenizer tokenizer = new StringTokenizer(content.replaceAll(FILTER_REGEX, " ")); + // 先将分隔符全替换为空格,再利用 StringTokenizer 切分单词 + + while (tokenizer.hasMoreTokens()) + { + word = tokenizer.nextToken(" "); + if (Pattern.matches(VALID_WORD_REGEX, word)) + { + // 利用正则表达式统计有效字符:至少有四位且都为字母,后跟若干字母或数字,不区分大小写,计入HashMap。 + if (words.containsKey(word)) + { + // 单词已统计到过 + cnt = words.get(word); + words.put(word, cnt + 1); + } + else + { + // 单词初次统计到 + words.put(word, 1); + } + } + } + return words; + } +} diff --git a/221801427/src/lib/tool/FilePrinter.java b/221801427/src/lib/tool/FilePrinter.java new file mode 100644 index 00000000..1fc79793 --- /dev/null +++ b/221801427/src/lib/tool/FilePrinter.java @@ -0,0 +1,50 @@ +package lib.tool; + +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; + +public class FilePrinter +{ + /** + * @param charCnt + * @param wordCnt + * @param lineCnt + * @param freqList + * @param outputFileName + */ + public static void writeFile(int charCnt, int wordCnt, int lineCnt, + ArrayList> freqList, String outputFileName) + { + // 接收需要统计的数值并打印 + File file = new File(outputFileName); + FileWriter fileWriter = null; + BufferedWriter bufferedWriter = null; + try + { + fileWriter = new FileWriter(file); + bufferedWriter = new BufferedWriter(fileWriter);// 使用BufferedWriter提高性能 + + bufferedWriter.write("characters: " + charCnt + "\n"); + bufferedWriter.write("words: " + wordCnt + "\n"); + bufferedWriter.write("lines: " + lineCnt + "\n"); + for (HashMap.Entry map : freqList) + { + bufferedWriter.write(map.getKey() + ": " + map.getValue() + "\n");// 打印HashMap的键与对应值 + } + bufferedWriter.flush(); + bufferedWriter.close(); + } + catch (FileNotFoundException e) + { + System.out.println("File Not Found"); + e.printStackTrace(); + } + catch (IOException e) + { + System.out.println("Error Writing File"); + e.printStackTrace(); + } + } +} + diff --git a/221801427/src/lib/tool/FileReader.java b/221801427/src/lib/tool/FileReader.java new file mode 100644 index 00000000..08f8e069 --- /dev/null +++ b/221801427/src/lib/tool/FileReader.java @@ -0,0 +1,62 @@ +package lib.tool; + +import java.io.*; +import java.nio.*; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; + +import sun.misc.Cleaner; +import sun.nio.ch.DirectBuffer; + +public class FileReader +{ + /** + * @param inputFileName + * @return 读取出的字符串 + */ + @SuppressWarnings("resource") + public static String readFile(String inputFileName) + { + //通过MappedByteBuffer读取文件 + File file = new File(inputFileName); + long len = file.length(); + MappedByteBuffer mappedByteBuffer = null; + + try + { + mappedByteBuffer = new RandomAccessFile(file, "r").getChannel().map(FileChannel.MapMode.READ_ONLY, 0, len); + // 通过RandomAccessFile获取FileChannel,并通过FileChannel.map方法,把文件映射到虚拟内存,返回逻辑地址。 + if (mappedByteBuffer != null) + { + return StandardCharsets.UTF_8.decode(mappedByteBuffer).toString().toLowerCase(); + } + else + { + return "";// 空白文件则返回空 + } + } + catch (FileNotFoundException e) + { + System.out.println("File Not Found"); + e.printStackTrace(); + } + catch (IOException e) + { + System.out.println("Error Reading File"); + e.printStackTrace(); + } + finally + { + if (mappedByteBuffer != null) + { + // 垃圾回收 + Cleaner cleaner = ((DirectBuffer) mappedByteBuffer).cleaner(); + if (cleaner != null) + { + cleaner.clean(); + } + } + } + return "";// 未读取到则返回空 + } +}