本来转自这个地址(http://blog.csdn.net/dbt666666/article/details/16974415
)的博文, 不过太差了,遂自己实现一把真正的topK问题,网上的文章只作为本文的参考。因为很早写的,当时在windows上实践的,不过应当实现有点问题,确实也不是真正的TopK求解。鉴于本文截止2020-05-01
的阅读量已经很多了,所以本文标题就不改了;2020-05-01
(今天)重新实践下,并重写本文,并标记本文为原创。
请按照目录阅读,要明白我们的问题,我们怎么求解,我们怎么输出,这里先不考虑大数据方案,就单纯的一个能让大一,大二同学就能直接实践并且能够学到知识
你只有一个2C4G的机器(即内存是有限的)
然后很简单的一个问题: 一个6G的txt文件,每一行都出现了一个IP,要统计这其中出现频次最高的IP,返回出现TopK的IP,输出如下
<topCnt1, Ip1>
<topCnt2, Ip1>
...
<topCntK, IpK>
分治思想应该是容易想到的,如果读大二还不知道,那么赶快去学习
程序模拟产生了一个1G行,每行一个[0,100000]区间的整数
这里根据IP Hash 到 1024个小文件中,显然有:
在单个文件中,topK的才是最终整体有可能的topK(一个文件中可能出现相同次数的,也要考虑进来);非topK的直接抛弃即可
*注意: 我们是普通的Hash,不过数据还是相对均匀的,所以每个小文件确实是挺小的;考虑现实中数据极端情况,可能出现分治分不了
,业即:大数据常见的数据倾斜问题(这里引入下,不做过多说明)
对每个小文件,可以用堆,hash,内部排序等等方法进行处理;
结果如下: 第一列是次数,第二列是对应的IP
/*
访问次数最多IP的top_k:10
11178 39293
11143 69144
11140 29718
11133 67370
11131 40798
11130 72952
11127 57661
11122 25185
11120 93643
11118 40562
*/
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author mubi
* @Date 2020/5/1 12:15
*/
class IP implements Serializable {
private static final long serialVersionUID = -8903000680469719698L;
private String ip;
private int count;
public IP(String ip2, Integer integer) {
this.ip = ip2;
this.count = integer;
}
public int getCount() {
return count;
}
public String getIp() {
return ip;
}
}
/**
* @Author mubi
* @Date 2020/5/1 12:15
*/
public class Data {
static String fileLoc = "/Users/mubi/test_data/nums.txt";
static int TOP_K = 10;
static int FILE_CNT = 1024;
/**
* 将大文件hash成1024个小文件,顺序读写,按照IP hashCode 打散
*
* 则显然在单个文件中,topK的才是最终整体有可能的topK(一个文件中可能出现相同次数的,也要考虑进来)
*/
private static void hashToSmallFiles() throws IOException {
BufferedReader br = new BufferedReader(new FileReader(fileLoc));
String ip;
HashMap fileWriters = new HashMap();
while ((ip = br.readLine()) != null) {
int tmp = Math.abs(ip.hashCode() % FILE_CNT);
String fileName = fileLoc + tmp + ".txt";
FileWriter fw;
if (fileWriters.containsKey(fileName)) {
fw = fileWriters.get(fileName);
} else {
fw = new FileWriter(fileName, true);
fileWriters.put(fileName, fw);
}
fw.write(ip + "\n");
}
br.close();
for (FileWriter ff : fileWriters.values()) {
ff.close();
}
}
/**
* 多线程处理
*/
static Map> mpList = new ConcurrentHashMap<>();
static class MyThread implements Runnable{
int index;
public MyThread(int index){
this.index = index;
}
@Override
public void run() {
try {
List list = new ArrayList<>();
File file = new File(fileLoc + index + ".txt");
if (!file.exists()) {
return;
}
long startTime = System.currentTimeMillis();
BufferedReader br1 = new BufferedReader(new FileReader(file));
String ip1;
// 这里就是单个小文件求topK的问题了
HashMap hm = new HashMap();
while ((ip1 = br1.readLine()) != null) {
if (!hm.containsKey(ip1)) {
hm.put(ip1, 1);
}
else {
hm.put(ip1, hm.get(ip1) + 1);
}
}
TreeMap treeMap = new TreeMap<>((a,b)->{
return -(a-b);
});
// 这里使用 TreeMap 有序结构处理
for(String ip: hm.keySet()){
int num = hm.get(ip);
treeMap.put(num, ip);
}
int iCnt = 0;
for(Integer cnt: treeMap.keySet()){
IP ip = new IP(treeMap.get(cnt), cnt);
iCnt++;
if(iCnt > TOP_K){
if(iCnt == list.get(TOP_K-1).getCount()){
list.add(ip);
}else {
break;
}
}else {
list.add(ip);
}
}
long endTime = System.currentTimeMillis();
mpList.put(index, list);
System.out.println("已经统计文件:" + fileLoc + index + ".txt,用时:" + (endTime - startTime) + " 毫秒");
}catch (Exception e){
}
}
}
/**
* 利用HashMap<> 统计每个小文件频数最高的ip;
* @return 所有小文件的结果 组成List 返回
* @throws FileNotFoundException
* @throws IOException
*/
private static Map> countEverySmallFile() throws FileNotFoundException, IOException {
try {
Thread[] thread = new Thread[FILE_CNT];
for (int i = 0; i < FILE_CNT; i++) {
thread[i] = new Thread(new MyThread(i));
}
for (int i = 0; i < FILE_CNT; i++) {
thread[i].start();
}
for (int i = 0; i < FILE_CNT; i++) {
thread[i].join();
}
}catch (Exception e){
}
return mpList;
}
/**
* 由 1024 个 topK 结果,综合起来,再求出最后的 topK
* 综合计算TopK
*/
private static List calculateResult(Map> mpList) {
HashMap hm = new HashMap();
for(Integer fileIndex: mpList.keySet()){
List ipList = mpList.get(fileIndex);
for(IP ip: ipList){
if (! hm.containsKey(ip.getIp())) {
hm.put(ip.getIp(), ip.getCount());
}else {
hm.put(ip.getIp(), hm.get(ip.getIp()) + ip.getCount());
}
}
}
TreeMap treeMap = new TreeMap<>((a,b)->{
return -(a-b);
});
// 这里使用 TreeMap 有序结构处理
for(String ip: hm.keySet()){
int num = hm.get(ip);
treeMap.put(num, ip);
}
List list = new ArrayList<>();
int iCnt = 0;
for(Integer cnt: treeMap.keySet()){
IP ip = new IP(treeMap.get(cnt), cnt);
iCnt++;
if(iCnt > TOP_K){
if(iCnt == list.get(TOP_K-1).getCount()){
list.add(ip);
}else {
break;
}
}else {
list.add(ip);
}
}
return list;
}
public static void findIp() throws IOException, ClassNotFoundException {
// 当个文件计算完
Map> mpList = countEverySmallFile();
// 最后统计
List ipList = calculateResult(mpList);
System.out.println("访问次数最多IP的top_k:" + TOP_K);
for(IP ip: ipList){
System.out.println(ip.getCount() + "\t" + ip.getIp());
}
}
/**
* 产生大文件
* 每一行是一个【0,100000】之间随机整数(用来表示出现的IP)
*/
public static void getBigFile() {
try
{
File file = new File(fileLoc);
if(!file.exists())
{ //如果不存在则创建
file.createNewFile();
System.out.println("文件创建完成,开始写入");
}
FileWriter fw = new FileWriter(file); //创建文件写入
BufferedWriter bw = new BufferedWriter(fw);
//产生随机数据,写入文件
Random random = new Random();
for(int i=0;i<1024*1024*1024;i++)
{
int randint = (int)Math.floor((random.nextDouble()*100000.0));//产生【0,10000】之间随机数
bw.write(String.valueOf(randint)); //写入一个随机数
bw.newLine(); //新的一行
}
bw.close();
fw.close();
System.out.println("文件写入完成");
}
catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
// getBigFile();
long startTime = System.currentTimeMillis();
// hashToSmallFiles();
// 约 283059
System.out.println("hashToSmallFiles cost " + (System.currentTimeMillis() - startTime) + " 毫秒");
startTime = System.currentTimeMillis();
findIp();
// 约 91043
System.out.println("findIp cost " + (System.currentTimeMillis() - startTime) + " 毫秒");
System.out.println("Finished");
}
}
/*
访问次数最多IP的top_k:10
11178 39293
11143 69144
11140 29718
11133 67370
11131 40798
11130 72952
11127 57661
11122 25185
11120 93643
11118 40562
*/
主要是学习分治思想,当然也是考察对CPU计算,磁盘IO,多线程,Hash / 堆 等数据结构的认识。回到问题,实践方式很多,本文只是一个参考,还请读者务必明白,后面学习大数据相关也离不开这些基础。
如果有交流的同学,请直接评论或私聊