假设场景:

某大型网站,活跃用户上亿个。(当然不是指同时在线人数,这里指的是再一段时间内有访问操作的用户数量,比如一个小时内)。

现在要每隔1小时,统计一次活跃用户排行榜(用户点击本网站的一个连接,活跃度就加1,按活跃度进行排名)。

首先,在此场景下,解决此问题不涉及数据库操作(也不可能用户点击一下,就更新一下数据库!),访问记录就是记录在日志文件中,例如:

zhangsan, http://a.com/b/

zhangsan, http://a.com/c/

lisi, http://a.com/b/

lisi, http://a.com/e/

lisi, http://a.com/x/

然后,我们不考虑用户访问量的统计过程,假设根据日志文件已经得出了这样的文件:

zhangsan 2

lisi 3

其中,2、3分别表示对应用户的活跃度,我们要按此进行排序,但是用户总量有一亿个!

接着,我们继续抽象、简化。既然活跃度用整数表示,我们就单独来考虑整数排序的问题,即,用户名也先不考虑了,就处理一亿个整数的排序。

先尝试直接使用TreeSet来排序。

TreeSet底层是红黑树实现的,排序是很高效的,这里不深究,我们就用它来完成排序:

1. 生产测试数据

package com.bebebird.data.handler;import java.io.File;
import java.io.PrintWriter;
import java.util.Random;/*** * @author sundeveloper* * 创建测试数据**/
public class DataProducer {/*** 创建数据* @param count 数据量* @param out 输出文件路径*/public static void produce(int count, String out) {long t1 = System.currentTimeMillis();File file = new File(out);if(file.exists())file.delete();try (PrintWriter writer = new PrintWriter(file, "UTF-8");) {Random random = new Random();for(int i=0; i<count; i++){writer.write(random.nextInt(count) + "\n");}}catch (Exception e){e.printStackTrace();}long t2 = System.currentTimeMillis();System.out.println("创建成功!耗时:" + (t2 - t1) + "毫秒。");}}

调用produce()方法,指定数据量和数据输出路径,来生产测试数据。

2. 利用TreeSet对数据进行排序:

package com.bebebird.data.handler;import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.TreeSet;/*** * @author sundeveloper* * 使用TreeSet自动将数据排序* * 处理数据量能达到千万级,一千万数据排序大约用时20秒。**/
public class SimpleTreeSetHandler {private Integer[] datas = null;/*** 排序* @param in 数据文件路径*/public void sort(String in){long t1 = System.currentTimeMillis();File file = new File(in);if(!file.exists())return;try(BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8"));){TreeSet<Integer> set = new TreeSet<>();String line = null;while((line = reader.readLine()) != null && !"".equals(line)){set.add(new Integer(line));}this.datas = set.toArray(new Integer[set.size()]);}catch(Exception e){e.printStackTrace();}long t2 = System.currentTimeMillis();System.out.println("排序完成!耗时:" + (t2 - t1) + "毫秒。");}/*** 从pos开始,获取count个数* @param pos* @param count* @return*/public Integer[] limit(int pos, int count){long t1 = System.currentTimeMillis();if(pos < 0 || count <= 0){return null;}Integer[] result = new Integer[count];for (int i = 0; i < count && pos + i < this.datas.length; i++) {result[i] = this.datas[pos + i];}long t2 = System.currentTimeMillis();System.out.println("取数成功!耗时:" + (t2 - t1) + "毫秒。");return result;}// 测试:// 创建1千万随机数,进行排序public static void main(String[] args) {DataProducer.produce(10000000, "data");SimpleTreeSetHandler handler = new SimpleTreeSetHandler();handler.sort("data");Integer[] limit = handler.limit(10, 10);System.out.println(Arrays.asList(limit));}
}

调用SimpleTreeSetHandler的sort()方法,指定数据文件路径,对其排序。

经测试,直接使用TreeSet来处理,一千万数据量很轻松就能处理,大概排序耗时20秒左右。

但是,一亿数据量时就废了!CPU满,内存占用上2.5G左右,并且N多分钟后不出结果,只能结束进程!(有条件的话,可以试试,具体多久能排出来)

机器配置简要说明:2.5 GHz Intel Core i5,系统内存10G。

3. 既然用TreeSet处理一千万数据很容易,那么把一亿条分成10个一千万不就能够处理了?每个一千万用时20秒,10个一千万大概200秒,三分钟拍出来还是可以接受的!(当然,这么算不准确,但大概是这个数量级的!)

package com.bebebird.data.handler;import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;/*** * @author sundeveloper** 将数据进行分成若干片段;* 分别对每个片段进行排序,存入临时文件;* 将临时文件进行合并**/
public class DivideTreeSetHandler {/*** 排序* @param in 数据文件路径* @param size 每个数据文件的大小(行数)*/public List<String> divide(String in, int size){long t1 = System.currentTimeMillis();File file = new File(in);if(!file.exists())return null;List<String> outs = new ArrayList<String>();try(BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8"));){int fileNo = 0; // 临时文件编号Set<Integer> set = new TreeSet<Integer>();while(true){String line = reader.readLine();// 读取结束!if(line == null){writeSetToTmpFile(set, fileNo, outs);break;}// 空行,跳过if("".equals(line.trim())){continue;}set.add(new Integer(line));// 数据量达到:if(set.size() >= size){writeSetToTmpFile(set, fileNo, outs);fileNo ++;}}}catch(Exception e){e.printStackTrace();}long t2 = System.currentTimeMillis();System.out.println("拆分完成!耗时:" + (t2 - t1) + "毫秒。");return outs;}// set数据写入到文件中:private void writeSetToTmpFile(Set<Integer> set, int fileNo, List<String> outs) {long t1 = System.currentTimeMillis();File file = new File("tmp_" + fileNo);if(file.exists())file.delete();try (PrintWriter writer = new PrintWriter(file, "UTF-8");) {Iterator<Integer> iterator = set.iterator();while(iterator.hasNext()){writer.write(iterator.next() + "\n");}set.clear();}catch (Exception e){e.printStackTrace();}long t2 = System.currentTimeMillis();System.out.println("生成临时文件:" + file.getAbsolutePath() + "!耗时:" + (t2 - t1) + "毫秒。");outs.add(file.getAbsolutePath());}/*** 合并数据* @param ins*/public String combine(List<String> ins) {long t1 = System.currentTimeMillis();if(ins == null || ins.size() <= 1)return null;File file = new File("tmp");if(file.exists())file.delete();try(PrintWriter writer = new PrintWriter(file, "UTF-8");){List<BufferedReader> readers = new ArrayList<>();for (String in : ins) {readers.add(new BufferedReader(new InputStreamReader(new FileInputStream(in),"UTF-8")));}while(readers.size() > 0){BufferedReader reader0 = readers.get(0);while(true){String line = reader0.readLine();if(line == null){readers.remove(0);break;}if("".equals(line.trim()))continue;// 用个set记录从多个文件中取出的数据,这些数据需要继续排序:Set<Integer> set = new TreeSet<Integer>();int data = new Integer(line);// 先把data放入set:set.add(data);for(int i = readers.size() - 1; i > 0; i--){BufferedReader readeri = readers.get(i);while(true){// 设置一个标记,如果后边datai大于data了,需要reset到此处!readeri.mark(1024); String linei = readeri.readLine();if(linei == null){readers.remove(i);break;}if("".equals(linei.trim()))continue;int datai = new Integer(linei);// datai小于data,则把datai放入set,会自动排序if(datai < data){set.add(datai);}// datai等于data,则暂时退出,停止读取else if(datai == data){break;}// datai大于data,则往回退一行(退到标记处),停止读取else{readeri.reset();break;}}}// 按data查找,小于data的值,都已经存入set了,此时把set输出到文件中:Iterator<Integer> iterator = set.iterator();while(iterator.hasNext()){writer.write(iterator.next() + "\n");}set.clear();}}}catch(Exception e){e.printStackTrace();}long t2 = System.currentTimeMillis();System.out.println("合并完成!耗时:" + (t2 - t1) + "毫秒。");return file.getAbsolutePath();}/*** 从pos开始,获取count个数* @param pos* @param count* @return*/public Integer[] limit(int pos, int count, String in){// TODO : 从排序后的文件中读取数据即可!不写了!return null;}// 测试:public static void main(String[] args) {// 数据量:int dataCount = 100000000;// 分页数(拆分文件数):int pageCount = 10;// 每页数据量:int perPageCount = dataCount / pageCount;// 生成一亿数据:DataProducer.produce(dataCount, "data");DivideTreeSetHandler handler = new DivideTreeSetHandler();// 拆分排序:List<String> tmps = handler.divide("data", perPageCount);// 合并排序:String tmp = handler.combine(tmps);// 获取数据:Integer[] limit = handler.limit(10, 10, tmp);}}

调用DivideTreeSetHandler的divide()方法,指定数据文件、拆分的每页放多少数据,将数据拆分。当然,拆分的时候就已经分别使用TreeSet排序了!

调用DivideTreeSetHandler的combine()方法,将拆分后的若干个文件进行合并,合并的过程中同样也会排序!

最终,输出一个完全排序了的文件。

经测试,一亿数据量,拆分加合并共用时约3.6分钟(包含各种IO操作的用时),可以接受。

到这里,核心问题解决了,剩余的就是对象排序了,把用户、活跃度封装成对象,用TreeSet将对象进行排序,对象实现compareTo,重写hashcode、equals等等,就不再多说了。

当然,DivideTreeSetHandler的还有很多优化空间,比如,可以把拆分、合并用多线程来处理。这里就先不搞了,有空再说。

说明:

写代码时,并不知道这种排序算法已经有名字了(叫“归并排序”),还想着为其命名呢~

实际上,是受到hadoop的map-reduce思想的启发,想到用这个方法来处理。

思想都是想通的:一个人搞不了了,就要分而治之!

一亿条数据的排序处理相关推荐

  1. 10亿条数据去重后排序和在线日志人数统计

    一:10亿条数据排序 思路:数据量比较大,普通比较会占用很多的内存,可以采用其他方法,构造一个字节数组 每个字节的值代表连续八个整形数据的值是否存在,即使包括最大的整数值,大概内存512m 源码如下 ...

  2. mysql 主键倒序查询速度慢_一亿条数据order by主键降序速度很慢

    我用sysbench造了1亿条数据,mysql用了30分钟,tidb总共花了3个小时,感觉tidb在批量插入时比较慢,如果后面程序做分页查询会很慢的. count总数.降序排序也比mysql慢. ti ...

  3. net.sz.framework 框架 ORM 消消乐超过亿条数据排行榜分析 天王盖地虎

    序言 天王盖地虎, 老婆马上生孩子了,在家待产,老婆喜欢玩消消乐类似的休闲游戏,闲置状态,无聊的分析一下消消乐游戏的一些技术问题: 由于我主要是服务器研发,客户端属于半吊子,所以就分析一下消消乐排行榜 ...

  4. mysql二亿大表_面对有2亿条数据的mysql表

    看到这个2亿5千条数据的表,我的内心是拒绝的,各种条件筛选要取出相应的数据,被折磨了两天,现在记录下心路历程 先分享下mysql相关的知识点1 名词解释 主键(PRIMARY KEY): 唯一索引,不 ...

  5. 如何用 Python 分析 14 亿条数据?

    (点击视学算法公众号,可快速关注) 英文:Steve Stagg,翻译:Ryden Sun juejin.im/post/5aceae206fb9a028d2084fea Google Ngram v ...

  6. mysql 5000万条数据库_1亿条数据如何分表100张到Mysql数据库中(PHP)

    下面通过创建100张表来演示下1亿条数据的分表过程,具体请看下文代码. 当数据量猛增的时候,大家都会选择库表散列等等方式去优化数据读写速度.笔者做了一个简单的尝试,1亿条数据,分100张表.具体实现过 ...

  7. 1亿条数据如何分表100张到Mysql数据库中(PHP)

    来源:http://www.jb51.net/article/70265.htm 这篇文章主要介绍了当数据量猛增的时候如何把一亿条数据分表100张到Mysql数据库中,需要的朋友可以参考下 下面通过创 ...

  8. mysql一张表1亿天数据_1亿条数据在PHP中实现Mysql数据库分表100张

    转: 1亿条数据在PHP中实现Mysql数据库分表100张 http://php-z.com/thread-2115-1-1.html (出处: PHP-Z) 当数据量猛增的时候,大家都会选择库表散列 ...

  9. [译] 使用 python 分析 14 亿条数据

    原文地址:Analysing 1.4 billion rows with python 原文作者:Steve Stagg 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold ...

  10. python 抓取微博评论破亿_如果利用Python分析14亿条数据!资深程序员手把手教你!过亿级!...

    挑战 1-gram 的数据集在硬盘上可以展开成为 27 Gb 的数据,这在读入 python 时是一个很大的数据量级.Python可以轻易地一次性地处理千兆的数据,但是当数据是损坏的和已加工的,速度就 ...

最新文章

  1. 调整体态的最佳瑜珈调息法
  2. Vue 中的compile操作方式
  3. linux CMA使用机制分析--基于SigmaStar SSD202
  4. 【COGS1752】 BOI2007—摩基亚Mokia
  5. Spring boot + mybatis plus 快速构建项目,生成基本业务操作代码。
  6. 应用层协议:HTTP与HTTPS协议详解、二者的区别
  7. Linux虚拟化KVM-Qemu分析(三)之KVM源码(1)kvm_init
  8. python连接mysql的操作
  9. 【最短路问题】leetcode743. 网络延迟时间
  10. WARNING: Ignoring invalid distribution -ip (e:\python\lib\site-packages)
  11. SurfaceView浅析
  12. 绘制自己的人际关系图_攒人脉,建圈子,从绘制一张人际关系图开始。
  13. 在ROS中使用行为树
  14. 英国第一位重要的浪漫主义诗人
  15. D2. Seating Arrangements (hard version)
  16. 如何在CAD中进行批量展点(平面和三维)
  17. 主板、内存条、硬盘、显卡认识和选购
  18. 【思维导图训练1】--思维导图的基本概述
  19. 微信公众号开发__微信网页授权并获取用户基本信息(是否关注公众号、头像、昵称等)
  20. css背景图片和背景颜色一起显示

热门文章

  1. mac安装正在计算机,出现“无法在计算机上安装macOS”错误时该怎么办?
  2. Linux如何查看和设置DNS服务器地址
  3. Server、Service和Servlet的区分
  4. UIStoryBoard 中修改控件borderColor
  5. 速卖通奇门+聚石塔流程
  6. 网站加速教程--提升性能的同时节约10倍成本
  7. JAVA写arp协议_ARP协议动态交互仿真系统的设计
  8. Excel如何来绘制不同函数的图像;不同数学符号可以在word中打处理;
  9. 干货满满!龙蜥社区Meetup走进龙芯圆满结束,5大技术分享精彩回顾
  10. 荣耀XIO升级鸿蒙,距断供不到10天 华为大招来了:不止鸿蒙