一个求90分位数的算法优化

题目

给定文件,每个文件中有一行逗号分隔的数据,请找出该数据流中tp90 line,即第90百分位数。即按顺序处于第90%位置的数。

说明

比如文件内容: 2,3,4,5,10,8,9,1,6,7 排序后第90%位置为第9个,即为9。 注意如果第90%长度不是整数,则向下取整。如数据流长度为115,115 * 90% = 103.5,则取第103个数。

思路

其实很简单,最简单的做法就是转成数组,使用jdk自带的sort(TimSort)方法排序,然后求出对应值即可。1000万数据在笔记本(SSD、8G内存、i5处理器)上运行,大概耗时7s左右。

优化

优化思路

  • 不用全部排序,我们只需要大致定位区间,然后在这个区间排序即可
  • 亿级以内无需使用多线程,多线程的损耗大于收益,比如对cpu的二级缓存不友好等,这是很重要的优化点
  • jdk的集合类,包装类能不用就不用,哪怕是一个字符串转数字,就有不小的优化空间,同样也是缓存友好

上代码

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;

public class Solution {
    public static final int ASCII_0 = 48;
    public int getTp90Line(File file){
        int[] record = new int[1024*10];//初始化一堆区间,用来记录这个区间内有多少个数
        byte[] buffer = new byte[1024*1024*10];//IO缓冲
        int all = 0;
        int temp = 0;
        try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))){
            int i;
            while((i = bis.read(buffer,0,buffer.length)) > 0){
                for(int a = 0;a<i;a++){
                    if(buffer[a] == ','){
                        all++;
                        int index = temp >> 10;//除以1024,用位运算
                        if(index >= record.length){
                            //扩容
                            int[] newRecord = new int[index+1];
                            System.arraycopy(record,0,newRecord,0,record.length);
                            record = newRecord;
                        }
                        record[index]++;
                        temp = 0;
                    }else{
                        int n = buffer[a] - ASCII_0;
                        temp = temp*10+n;//字符串转数字,使用基本类型计算
                    }
                }
            }
            all++;
            int index = temp >> 10;
            if(index >= record.length){
                int[] newRecord = new int[index+1];
                System.arraycopy(record,0,newRecord,0,record.length);
                record = newRecord;
            }
            record[index]++;
        } catch (Exception e){
            e.printStackTrace();
        }
        int tp90 = (int) (all*0.9d)-1;
        int start = 0;
        int lessThanTp90 = 0;
        while(start < record.length){
            int t = lessThanTp90 + record[start];
            if(t > tp90){
                break;
            }else{
                lessThanTp90 = t;
            }
            start++;
        }
        //找到目标所在区间
        int[] targetSection = new int[record[start]];
        int len = 0;
        try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))){
            int i;//第二次读取,因为已经确定了90分位数所在区间,那么只需要记录该区间的值,然后排序即可
            while((i = bis.read(buffer,0,buffer.length)) > 0){
                for(int a = 0;a<i;a++){
                    if(buffer[a] == ','){
                        int index = temp >> 10;
                        if(index == start ){
                            targetSection[len++]=temp;
                        }
                        temp = 0;
                    }else{
                        int n = buffer[a] - ASCII_0;
                        temp = temp*10+n;
                    }
                }
            }
            int index = temp >> 10;
            if(index == start ){
                targetSection[len++]=temp;
            }
        } catch (Exception e){
            e.printStackTrace();
        }
        Arrays.sort(targetSection);
        return targetSection[tp90 - lessThanTp90];
    }
}

实际效果

同样的配置,1000万数据大概在300~400ms,内存占用更是小了很多,时间上有大概20倍的提升,空间提升(应该也超过20倍)不考虑,需要注意的是,要根据数据的分布因地制宜,这个算法只是一些优化的思路,也就是说,在一些场景下,自己实现一些比较简陋的类库,结合自身的数据情况,大幅提升应用的性能,毕竟7秒到350毫秒,体验上有云泥之别,而350毫秒到35毫秒,却没有那么明显。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值