首页 > php入门 > 记录一次百万连续ID获取中断数值的算法

记录一次百万连续ID获取中断数值的算法

场景介绍:有100万篇文章生成了100个html文件,后来删除部分文章,但是文件并未删除,通过遍历html文件所在目录然后根据已知的ID集合判断是否该删除文件导致IO访问过于频繁且执行时间极长。


算法介绍:

这里有100万个数字组成的ID,其中ID是自增但是不连续的,现在需要求出中断部分的ID集合。


思路:已知这100万个ID是有序但不连续的,可以尝试构造一个for循环,使用二分查找判断是否存在于这100万个元素中,如果不存在则加入到中断部分集合。


遇到的问题:由于数据源过大导致二分查找消耗内存巨大且运行时间非常长,需要改进思路。后来经过分析,这两个集合都是正序的,完全可以定义一个游标进行单层循环。


代码实现:

<?php
set_time_limit(0);

header("Content-type: text/html; charset=utf-8");

$stime = microtime(true);

// 按照ID正序排列, 假设有1000000个ID, 1/10000几率中断
$checkIdListSize = 1000000;

$checkIdList = generateList($checkIdListSize, $checkIdListSize/10000);

p("初始: ".tosize(memory_get_usage()));

// 得到没有审核的ID集合
$uncheckIdList = array();
$cursorList = array();

// 使用偏移计数器
$offset = 0;

// 不能以$checkIdList的最小值和最大值作为边界,而应该以1和可能存在的最大ID作为边界
for($i = 1; $i<$checkIdList[count($checkIdList)-1]; $i++) {
	$cursor = $checkIdList[$offset];
	if($i == $cursor) {
		$offset++;
		continue;
	}else if($i < $cursor) {
		$cursorList[] = $i;
	}
}

// 打印中断的ID
p(implode(",", $uncheckIdList));
p(implode(",", $cursorList));


// 打印中断的ID
//p(implode(",", $uncheckIdList));

p("使用: ".tosize(memory_get_usage()));

p("峰值: ".tosize(memory_get_peak_usage()));

//获取程序执行结束的时间
$etime = microtime(true);
//计算差值
$total = $etime-$stime;

p("当前页面执行时间为:{$total} 秒");

/**
 * 随机生成一个正序排列、不连续的数组
 * $maxSize 数组大小
 * $disruptedNum 中断、不连续的数量
 */
function generateList($maxSize, $disruptedNum) {
	$checkIdList = array();
	$shuffleKeys = array();
	// 模拟这1000000个的key
	for($i=0; $i<$maxSize; $i++) {
		$shuffleKeys[] = $i;
		$checkIdList[] = $i+1;
	}
	// 打乱顺序
	shuffle($shuffleKeys);

	// 随机删除其中10000个ID
	$randomKeys = array_slice($shuffleKeys, 0, $disruptedNum);

	// 设置为null保证$checkIdList的长度不会改变
	foreach($randomKeys as $randomKey) {
		$checkIdList[$randomKey] = null;
	}

	// 删除为null的元素并重新索引以保证后面进行二分查找时传入的是有序数组
	$checkIdList = array_values(array_filter($checkIdList));

	return $checkIdList;
}

/**
 * 二分查找法, $array必须是已经排好序的数组
 * $array为数据源
 * $find为待查找的数字
 */
function binarySearch($array, $find) {
	if(empty($array)) {
		return -1;
	}
    $start = 0;
    $end = count($array)-1;
    while ($start <= $end) {
        $mid = floor(($start + $end) / 2);
        if ($array[$mid] == $find) {
            return $mid;
        }
        if ($array[$mid] > $find) {
            //left
            $end = $mid - 1;
        } else {
            $start = $mid + 1;
        }
    }
	return -1;
}

//打印变量
function p($var) {
    echo "<pre>";
    if($var === false)
    {
        echo 'false';
    }else if($var === null){
        print_r('null');
    }else if($var === ''){
        print_r("''");
    }else{
        print_r($var);
    }
    echo "</pre>";
}

/**
*@功能:将filesize大小转化为常用大小
*@参数:类型为number,$bytes是filesize结果,大小Byte
*@返回:类型为string
 */
function tosize($bytes) {
    if ($bytes >= 1099511627776) {                  //如果提供的字节数大于等于2的40次方,则条件成立
        $return = round($bytes / 1099511627776, 2); //将字节大小转换为同等的T大小
        $suffix = 'TB';                             //单位为TB
    } elseif ($bytes >= 1073741824) {               //如果提供的字节数大于等于2的30次方,则条件成立
        $return = round($bytes / 1073741824, 2);    //将字节大小转换为同等的G大小
        $suffix = 'GB';                             //单位为GB
    } elseif ($bytes >= 1048576) {                  //如果提供的字节数大于等于2的20次方,则条件成立
        $return = round($bytes / 1048576, 2);       //将字节大小转换为同等的M大小
        $suffix = 'MB';                             //单位为MB
    } elseif ($bytes >= 1024) {                     //如果提供的字节数大于等于2的10次方,则条件成立
        $return = round($bytes / 1024, 2);          //将字节大小转换为同等的K大小
        $suffix = 'KB';                             //单位为KB
    } else {                                        //否则提供的字节数小于2的10次方,则条件成立
        $return = $bytes;                           //字节大小单位不变
        $suffix = 'Byte';                           //单位为Byte
    }
    return $return ." " . $suffix;                  //返回合适的文件大小和单位
}

--------------------------------------------------------------------------------------

当然,当数据量超过百万,很有可能出现一种情况,就是单个结果集就超过了内存上限,解决办法就是适当的切割大小。一般我们从数据库中查询出来的数据不能太大,使用limit做限制后得到的结果集比较小,所以还是可以用上面的方式获取中断的ID部分。




本文地址:http://www.phprm.com/start/million-id.html

转载随意,但请附上文章地址:-)

标签:million

发表留言