要求:
寫一個程序,分析一個文本文件中各個詞出現的頻率,並且把頻率最高的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以下,所以沒有溢出。但是如果測試很大的文本,就不行。除此之外,還可以進行擴展,可以將正則表達式改變,設計一個對中英文混合,或者中文文件查找算法及程序實現,同時考慮讀取存儲方式的改進,提高效率和性能。