1.需求:
读取一个文档,并统计出其中重复性单词的TopN。
这个文档364万行,那么我们如何统计呢?
2.分析
利用IO流将文档的单词读取
将其存为map的K,V,新的单词记为K,出现次数记为V,
利用比较器进行比较,遇到重复的单词找到他对应的V加一。
3.实现代码
本代码选用的文档地址E:\words.txt
建议将自己的文档改为.txt结尾,这样不会报错
修改此地址即可
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class topn {
public static void main(String[] args) throws Exception {
/*
* 首先将文件的内容读取到缓冲区bufferedreader中,
* 利用的是BufferedReader中的readline()读取文件中的每一个文本行,
* readline()并不是一行一行读取的,而是一个文本行一个文本行读取。 什么是文本行?
* 看JavaAPI中BufferedReader类总中的readline()的解释。
*/
BufferedReader readfile = new BufferedReader(new FileReader("E:\\words.txt"));
StringBuffer sb = new StringBuffer();
String text = null;
while ((text = readfile.readLine()) != null) {
// 将从文件中读出来的字符串追加,形成一个字符串
sb.append(text);
}
readfile.close();
// 用Pattern类中的complie()方法,将正则表达式编译到模式中
Pattern patten = Pattern.compile("[a-zA-Z]+");
// 用Pattern类中的matcher()方法,生成一个匹配器对象,Matcher类是匹配器类
String sbstring = sb.toString();
Matcher matcher = patten.matcher(sbstring);
Map<String, Integer> tp = new TreeMap<String, Integer>();
while (matcher.find()) {
// 用Matcher类中的find()方法,查找与模式匹配的下一个子序列
String word = matcher.group();
// 用Matcher类中的group()方法, 返回匹配的子序列
if (tp.containsKey(word)) {
// 统计每个单词出现的次数
Integer wordfrequency = tp.get(word);
tp.put(word, wordfrequency + 1);
} else {
tp.put(word, 1);
}
}
/*
* 将treemap中的键值对的set视图存入ArrayList中,其中的类型必须是Map.Entry,
* 因为TreeMap中的entrySet()方法的返回类型就是Map.Entry类型,其实Map.Entry就是个接口。
* 将treemap存入ArrayList的目的就是用Collections类中的sort()方法进行排序,
* 其中的sort(List<T>list,Comparator)是按照指定的比较器进行排序
*/
List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(tp.entrySet());
/*
* 重写Comparator比较器,目的是让TreeMap按照value进行降序排列,这里的重写比较器用的是匿名类,
* 先创建实现Comparator接口的类,并重写其中的compare方法,并不是接口实例化了。
*/
Comparator<Map.Entry<String, Integer>> comparator = new Comparator<Map.Entry<String, Integer>>() {
// 如果是实现升序就是return(param1.getValue().compareTo(param2.getValue());
public int compare(Map.Entry<String, Integer> param1, Map.Entry<String, Integer> param2) {
return (param2.getValue().compareTo(param1.getValue()));
}
};
// 按照指定的比较器,对list列表进行升序或者降序排序
Collections.sort(list, comparator);
Scanner intn = new Scanner(System.in);
System.out.println("请输入想要前几名单词排行");
int n = intn.nextInt();
System.out.println();
for (int i = 0; i < n; i++) {
String key = list.get(i).getKey();
Integer value = list.get(i).getValue();
System.out.println((i+1)+":"+ key + ":" + value);
}
}
}
4.比较器
比较器中的compare方法只是负责将两个参数比较的结果返回,并不负责进行排序。进行排
序的是sort()方法,它根据compare()方法的返回值进行相应排序。
第一个参数小于第二个参数 返回负数 ;第一个参数大于第二个参数 返回正数;第一个参数等于第二个参数 返回0。
5.API
sort(List <T> list, Comparator <? super T> c)
根据指定比较器产生的顺序对指定列表进行排序。
Comparator里的compare方法在api里解释是:比较用来排序的两个参数。
根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
6.思想
问题:
重写Comparator里的compare方法,compare根据传进去的两个对象进行对比后返回一个整数,compare只是返回了一个整数来表明<,=,>,
但是并没有根据compare的返回结果对这两个对象进行某种排序,那么最后比较器是怎么产生顺序的?
一般思路都是从升序开始(可能人的意识是看到排序最初的印象是升序),但是因为sort本身是个模板,也就是希望sort不改变,由用户传入的比较器来决定排序。
所以6L的伪代码就是基于升序的基础去处理的,sort里面只需要关心比较器的返回值,如果返回值大于0,则说明比较参数1大于比较参数2,则把参数1"沉底"(换过来说就是参数2冒泡),
这样当用户改变比较器,原本参数1大于参数2应该返回大于0的,可用户却故意返回小于0,
这样相当于比较器欺骗了sort,sort因为只关心结果,于是就认为参数2大于参数1,于是把参数2"沉底"(参数1冒泡),这样就相当于把“小”的放到最底,于是也就成了“降序”排序。
所以,排序的模板一般来说都是基于升序实现的,如果要达到“降序”,就由用户去实现比较器然后传给sort方法就可以了
7.效果
可以看到,我尝试输出前10的排名还是很成功的