分析一个文本文件中各个词出现的频率
要求:
写一个程序,分析一个文本文件中各个词出现的频率,并且把频率最高的10个词打印出来。文本文件大约是30KB~300KB大小。
解决步骤:
1、读取一个 txt 文本文件;
2、统计文件里面每个词出现的次数;
3、进行排序,打印出频率最高的10个词。
编程语言:java;
测试文本:D:\wordtest.txt 大小:417 KB (427,605 字节)
性能测试工具:JDK自带的 VisualVM插件
初步思路:
1、将文件内容存放在 StringBuffer 里面;
2、利用 split() 函数分割字符串,按照 ","、"."、"?"、":"、"空格"、"回车" 来分割,得到一个数组;
3、遍历数组,将其放入 Map<String,Integer> 中,key=词,value=出现次数;
4、对Map 进行排序,得到出现频率最高的10个词;
程序实现:
1 package homework; 2 3 import java.io.BufferedReader; 4 import java.io.FileNotFoundException; 5 import java.io.FileReader; 6 import java.io.IOException; 7 import java.util.HashMap; 8 import java.util.Iterator; 9 import java.util.Map; 10 import java.util.Set; 11 import java.util.StringTokenizer; 12 import java.util.TreeSet; 13 14 public class FileWordsCount { 15 public static void main(String[] args) { 16 17 long time1 = System.currentTimeMillis(); 18 19 try { 20 BufferedReader br = new BufferedReader(new FileReader("D:\\wordtest.txt")); 21 String s; 22 StringBuffer sb = new StringBuffer(); 23 while((s = br.readLine())!= null){ 24 sb.append(s); 25 } 26 Map<String,Integer> map = new HashMap<String,Integer>(); 27 StringTokenizer st = new StringTokenizer(sb.toString(), ",.! ?\n"); 28 while(st.hasMoreTokens()){ 29 String letter = st.nextToken(); 30 int count; 31 if(map.get(letter) == null){ 32 count = 1; 33 }else{ 34 count = map.get(letter).intValue()+1; 35 } 36 map.put(letter, count); 37 } 38 Set<WordEntity> set = new TreeSet<WordEntity>(); 39 for(String key:map.keySet()){ 40 set.add(new WordEntity(key, map.get(key))); 41 } 42 /*System.out.print("单词 次数\n"); 43 for(Iterator<WordEntity> it = set.iterator();it.hasNext();){ 44 WordEntity w = it.next(); 45 System.out.println(w.getKey()+" :"+w.getCount()); 46 }*/ 47 48 int count = 1; 49 for(Iterator<WordEntity> it = set.iterator();it.hasNext();){ 50 WordEntity w = it.next(); 51 System.out.println("Top"+count+": "+w.getKey()+" 次数:"+w.getCount()); 52 if(count == 10){ 53 break; 54 } 55 count++; 56 } 57 } catch (FileNotFoundException e) { 58 System.out.println("文件未找到!"); 59 } catch (IOException e){ 60 System.out.println("文件读异常!"); 61 } 62 long time2 = System.currentTimeMillis(); 63 System.out.println("耗时:"); 64 System.out.println(time2 - time1+"ms"); 65 } 66 }
1 package homework; 2 3 public class WordEntity implements Comparable<WordEntity>{ 4 5 public WordEntity(String key,Integer count){ 6 this.key = key; 7 this.count = count; 8 } 9 10 public String getKey(){ 11 return key; 12 } 13 14 public Integer getCount(){ 15 return count; 16 } 17 18 @Override 19 //cmp升序。-cmp降序排列 TreeSet会调用WorkForMap的compareTo方法来决定自己的排序 20 public int compareTo(WordEntity o) { 21 int cmp = count.intValue() - o.count.intValue(); 22 return (cmp == 0 ? key.compareTo(o.key):-cmp); 23 } 24 25 @Override 26 public String toString() { 27 return key + "出现次数:" + count; 28 } 29 30 private String key; 31 private Integer count; 32 33 }
运行结果:
不足与改进:
单词分割没有考虑全面,比如"_"和"—",还有"单双引号",省略号等,字母的大小写等;改进程序,用正则表达式来匹配单词,存储在TreeMap,要按照TreeMap的value排序,默认是key排序,可以将Map.Entry放在集合里,重写比较器,再用 Collections.sort(list,comparator) 进行排序。
改进代码:
1 package homework; 2 3 import java.io.BufferedReader; 4 import java.io.FileReader; 5 import java.util.ArrayList; 6 import java.util.Collections; 7 import java.util.Comparator; 8 import java.util.List; 9 import java.util.Map; 10 import java.util.TreeMap; 11 import java.util.regex.Matcher; 12 import java.util.regex.Pattern; 13 14 public class WordCount { 15 public static void main(String[] args) throws Exception { 16 17 long time1 = System.currentTimeMillis(); 18 19 BufferedReader reader = new BufferedReader(new FileReader( 20 "D:\\wordtest.txt")); 21 StringBuffer buffer = new StringBuffer(); 22 String line = null; 23 while ((line = reader.readLine()) != null) { 24 buffer.append(line); 25 } 26 reader.close(); 27 Pattern expression = Pattern.compile("[a-zA-Z]+");// 定义正则表达式匹配单词 28 String string = buffer.toString(); 29 Matcher matcher = expression.matcher(string);// 30 Map<String, Integer> map = new TreeMap<String, Integer>(); 31 String word = ""; 32 int times = 0; 33 while (matcher.find()) {// 是否匹配单词 34 word = matcher.group();// 得到一个单词-树映射的键 35 if (map.containsKey(word)) {// 如果包含该键,单词出现过 36 times = map.get(word);// 得到单词出现的次数 37 map.put(word, times + 1); 38 } else { 39 map.put(word, 1);// 否则单词第一次出现,添加到映射中 40 } 41 } 42 /* 43 * 核心:如何按照TreeMap 的value排序而不是key排序.将Map.Entry放在集合里,重写比较器,在用 44 * Collections.sort(list, comparator);进行排序 45 */ 46 47 List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>( 48 map.entrySet()); 49 /* 50 * 重写比较器 51 * 取出单词个数(value)比较 52 */ 53 Comparator<Map.Entry<String, Integer>> comparator = new Comparator<Map.Entry<String, Integer>>() { 54 public int compare(Map.Entry<String, Integer> left, 55 Map.Entry<String, Integer> right) { 56 return (left.getValue()).compareTo(right.getValue()); 57 } 58 }; 59 Collections.sort(list, comparator);// 排序 60 // 打印 61 int last = list.size() - 1; 62 for (int i = last; i > last-10; i--) { 63 String key = list.get(i).getKey(); 64 Integer value = list.get(i).getValue(); 65 System.out.print("Top"+i+" : "); 66 System.out.println(key + " " + value); 67 } 68 long time2 = System.currentTimeMillis(); 69 System.out.println("耗时:"); 70 System.out.println(time2 - time1+"ms"); 71 } 72 }
运行结果:
结果对比:
程序1 和程序2 结果前9名相似,最后一个虽然不一样,但是出现次数一样,为什么不一样呢?因为程序一里面TreeMap排序是按照key排序,所以虽然次数相同,但是 play 比 was 排在前面。
性能测试:
1. 整体:
图1:程序运行前(初始状态)
图2:程序1 (改进前)测试情况
图3:程序2(改进后)测试情况
2. CUP & GC
3. Heap
程序1
程序2
4. Threads
程序1
程序2
5. Class
程序1
程序2
改进与扩展:
对于处理大数据采用拆分的方法,比较好。使用Java正则表达式,如果文章比较大,会造成栈溢出,因为测试文本在500KB以下,所以没有溢出。但是如果测试很大的文本,就不行。除此之外,还可以进行扩展,可以将正则表达式改变,设计一个对中英文混合,或者中文文件查找算法及程序实现,同时考虑读取存储方式的改进,提高效率和性能。