PHP 多进程下载必应壁纸

手里拿着锤子,看什么都像是钉子

在放假的这几天,断断续续的看了老李关于 PHP 多进程的文章。

  • PHP多进程初探 --- 开篇
  • PHP多进程初探 --- 孤儿和僵尸
  • PHP多进程初探 --- 信号
  • PHP多进程初探 --- 利用多进程开发点儿东西吧
  • PHP多进程初探 --- 再次谈daemon进程
  • PHP多进程初探 --- 进程间通信二三事

在此基础上又看了下 owner888/phpspider 的多进程实现代码,这个是《我用爬虫一天时间“偷了”知乎一百万用户,只为证明PHP是世界上最好的语言 》一文所使用的程序。

等到自我感觉差不多已经掌握多进程时候,它就变成了我手中的锤子:

手里拿着锤子,看什么都像是钉子。

在《QueryList + Redis 下载壁纸》这篇文章中有提到,可以手动多开几个黑窗口提高壁纸下载速度。

正如文章中所说,在此之前,需要用到多进程来处理任务的时候都是用的这种“笨方法”。虽然在启动任务的时候比较麻烦,需要手动打开 n 个黑窗口,然后到指定目录下运行对应的脚本,但是在写代码的时候比较轻松,不用考虑多进程的可能导致的一些问题。

由于文中的壁纸站点倒闭了(与我无瓜),所以后面的代码换了一个站点来进行演示。

转载请注明来源地址:http://her-cat.com/posts/2020/02/02/php-multi-process-download-bing-wallpaper/

PHP 多进程的一些概念

关于 PHP 多进程,上面列出来的文章其实已经讲的差不多了,这里其实就是个观后总结,已经看完文章的可以跳过。

孤儿进程和僵尸进程

父进程在创建子进程后,需要负责子进程的回收,否则就会出现 孤儿进程僵尸进程

  • 孤儿进程:父进程在创建子进程后,子进程还在运行的时候自己先退出了,导致子进程没了爹,就变成了孤儿进程,然后被 Linux 的 “孤儿进程福利院” init 进程(进程 id 为 1)所收养。

  • 僵尸进程:父进程在创建子进程后,子进程退出了,但是父进程没有对其进行回收,导致子进程变成了僵尸进程,子进程的进程 ID、文件描述符等依然保存在系统中,极大的浪费了系统资源,相比孤儿进程危害更大。

回收子进程

在父进程中通过 pcntl_wait() 或 pcntl_waitpid() 函数对子进程进行回收,上面提到的回收其实就是对子进程的状态收集。

  • pcntl_wait():等待或返回创建的子进程状态。该函数是阻塞的,所以当执行到该函数时会阻塞在这里,直到有子进程退出或终止。

  • pcntl_waitpid():等待或返回创建的子进程状态。该函数是非阻塞的,也就是说当没有子进程需要处理时,它会返回 0 并继续执行后面的代码。

信号

信号是异步传送给进程的一种事件通知,进程无法预测何时会出现信号。

信号的产生有多种方式,比如在键盘上按下组合键 ctrl+c 或 ctrl+d 就会产生 SIGINT 信号并终止当前运行的程序;使用 posix_kill() 函数可以向指定的进程发送某种信号。

进程在收到信号后有以下三种处理方式。

  • 直接忽略:对信号不做任何处理,SIGSTOPSIGKILL 两种信号无法忽略,因为这两个信号是提供给用户停止或杀死进程最可靠的手段。

  • 捕获信号:程序自定义信号处理逻辑。

  • 系统默认动作:Linux 内核为每种信号都提供了默认动作,当程序没有主动捕获某种信号时,就会交由系统执行默认动作。大多数默认动作都是终止进程。

捕获信号的处理方式:先通过 pcntl_signal() 函数安装某个信号的回调函数,然后使用 pcntl_signal_dispatch() 调用每个等待信号通过 pcntl_signal() 安装的信号回调函数。

守护进程

非守护进程在启动后,在终端按下组合键 ctrl+c 或 ctrl+d 就会终止当前运行的程序。想要成为守护进程,首先要在父进程中创建一个子进程,然后通过 posix_setsid() 函数将该子进程作为会话的主进程,并退出父进程,断开与终端的连接。

代码实现

进程模型用的是单 Master 多 Worker 进程模型,Master 进程用于收集子进程的状态,一个 Worker 进程用于提取所有的壁纸下载地址,剩下 Worker 进程用于下载下载壁纸,因为下载比较耗时,所以需要多个 Worker 进程同时处理,下载壁纸的 Worker 进程数量可以自定义。

入口函数

首先看一下入口函数 run()

    public function run()
    {
        // 检查运行环境
        $this->checkEnv();
        // 守护进程
        $this->daemonize();
        // 安装信号处理器
        $this->installSignalHandler();
        // 初始化 Redis
        $this->initRedis();
        // 初始化进程
        $this->initWorkers();
        // 监听子进程状态
        $this->monitor();
    }

run() 函数已经概括了程序的运行流程。

首先检查一下当前运行环境,是否在 linux 系统中、是否安装相关扩展,最后是关于信号派遣的,PHP 7.1 新增了 pcntl_async_signals() 函数,在此之前需要 declare() 配合 pcntl_signal_dispatch() 函数进行信号派遣。

    protected function checkEnv()
    {
        if ('//' == \DIRECTORY_SEPARATOR) {
            exit('目前只支持 linux 系统'.PHP_EOL);
        }

        if (!\extension_loaded( 'pcntl') ) {
            exit('缺少 pcntl 扩展'.PHP_EOL);
        }

        if (!\extension_loaded( 'posix') ) {
            exit('缺少 posix 扩展'.PHP_EOL);
        }

        if (version_compare(PHP_VERSION, 7.1, '<')) {
            declare(ticks = 1);
        } else {
            // 启用异步信号处理
            \pcntl_async_signals(true);
        }
    }

守护进程

守护进程上面已经介绍过,可以再配合代码注释理解。

    protected function daemonize()
    {
        if (self::$options['daemonize'] !== true) {
            return;
        }

        // 设置当前进程创建的文件权限为 777
        umask(0);

        $pid = \pcntl_fork();
        if ($pid < 0) {
            $this->log('创建守护进程失败');
            exit;
        } else if ($pid > 0) {
            // 主进程退出
            exit(0);
        }

        // 将当前进程作为会话首进程
        if (\posix_setsid() < 0) {
            $this->log('设置会话首进程失败');
            exit;
        }

        // 两次 fork 保证形成的 daemon 进程绝对不会成为会话首进程
        $pid = \pcntl_fork();
        if ($pid < 0) {
            $this->log('创建守护进程失败');
            exit;
        } else if ($pid > 0) {
            // 主进程退出
            exit(0);
        }
    }

初始化 Redis

初始化 Redis 就是从配置中获取 Redis 参数,然后实例化 Predis/Client。

    protected function initRedis()
    {
        $this->redisClient = new Client(self::$options['redis']);
    }

安装信号处理器

这里只安装了 SIGINTSIGPIP 信号的处理器,收到 SIGINT 信号后,调用 stopAllWorkers() 方法给所有的 Worker 发送 SIGINT 信号,停止所有的 Worker。而收到 SIGPIPE 信号则忽略不做任何处理。

    protected function installSignalHandler()
    {
        // 捕获 SIGINT 信号,终端中断
        \pcntl_signal(SIGINT, [$this, 'stopAllWorkers'], false);

        // 捕获 SIGPIPE 信号,忽略掉所有管道事件
        \pcntl_signal(\SIGPIPE, \SIG_IGN, false);
    }

    protected function stopAllWorkers()
    {
        if (self::$maserPid !== \posix_getpid()) {
            // 子进程
            unset(self::$workers[$this->workerId]);
            exit(0);
        }

        // 父进程
        foreach (self::$workers as $pid) {
            // 给 worker 进程发送关闭信号
            \posix_kill($pid, SIGINT);
        }
    }

初始化进程

接下来就是初始化进程,先通过 posix_getpid() 函数获取当前进程的进程 ID 作为 Master 进程 ID。

再通过 forkWorker() 方法创建提取壁纸地址进程,该进程的处理方法是 extractWallpaperUrl()。因为 work id 为 0 的留给了 Master 进程,所以这里的 work id 从 1 开始。

然后根据配置项 worker_num 创建指定数量的下载壁纸的进程,该进程的处理方法是 downloadWallpaper() 方法。

    protected function initWorkers()
    {
        self::$maserPid = \posix_getpid();

        $this->forkWorker(1, [$this, 'extractWallpaperUrl']);

        $workerNum = (int) self::$options['worker_num'];

        for ($i = 0; $i < $workerNum; $i++) {
            $this->forkWorker($i + 2, [$this, 'downloadWallpaper']);
        }
    }

上面提到了 forkWorker 方法,这个方法其实跟老李文章中写的创建子进程代码差不多,在父进程中记录子进程的进程 ID,在进程中调用匿名函数处理业务逻辑。

    protected function forkWorker($workerId, $callback)
    {
        $pid = \pcntl_fork();

        if ($pid > 0) {
            // 父进程记录子进程 PID
            self::$workers[$workerId] = $pid;
        } elseif ($pid === 0) {
            // 子进程处理业务逻辑
            $this->workerId = $workerId;

            if ($callback instanceof \Closure) {
                $callback();
            } else if (isset($callback[1]) && is_object($callback[0])) {
                \call_user_func($callback);
            }

            exit(0);
        } else {
            $this->log('进程创建失败');
            exit;
        }
    }

壁纸采集逻辑

提取壁纸地址和下载壁纸的逻辑跟之前写的那篇文章差不多。

    protected function extractWallpaperUrl()
    {
        $this->log('提取壁纸地址进程启动...');

        $page = 1;

        do {
            $html = \file_get_contents("http://bing.ioliu.cn/?p={$page}");
            \preg_match_all('/<img([^>]*)\ssrc="([^\s>]+)"/', $html,$matches);

            if (empty($matches[2]) || \count($matches[2]) === 3) {
                $this->log('壁纸地址提取完毕, 当前页码: %s', $page);
                break;
            }

            $urls = \array_unique(\array_filter($matches[2]));

            if (!empty($urls)) {
                // 将壁纸 url 放入队列中
                $this->redisClient->sadd(self::$options['queue_key'], $urls);
            }

            $this->log('提取壁纸数量: %s, 当前页面: %s', count($urls), $page++);
        } while (true);
    }

    protected function downloadWallpaper()
    {
        $this->log('下载壁纸进程启动...');

        while (self::$freeTime < self::$options['max_free_time']) {
            $url = $this->redisClient->spop(self::$options['queue_key']);

            if (empty($url)) {
                $this->log('空闲时间: %s/%ss', self::$freeTime++, self::$options['max_free_time']);
                \sleep(1);
                continue;
            }

            try {
                $result = $this->saveWallpaper($url);
                if (!$result) {
                    $this->redisClient->sadd(self::$options['queue_key'], [$url]);
                }
            } catch (\Exception $e) {
                $result = false;
                $this->log('保存壁纸异常: %s', $e->getMessage());
            }

            $this->log('壁纸下载%s, %s', $result ? '成功' : '失败', $url);
        }
    }

监听子进程状态

进程到目前已经创建完了,接下来就是父进程对子进程状态进行监听,如果该已经已退出就将它从 self::workers 数组中删除,如果没有在运行中的子进程则退出父进程。

acceptSignal() 方法中通过 pcntl_wait() 函数阻塞获取退出的进程 ID。

    protected function monitor()
    {
        while (true) {
            $pid = $this->acceptSignal();

            if ($pid > 0) {
                $this->log('子进程退出信号, PID: %s', $pid);
                // 翻转 workers 的键值
                $workers = \array_flip(self::$workers);
                $workerId = $workers[$pid];
                // 删除子进程
                unset(self::$workers[$workerId]);
                // 如果没有在运行的子进程则退出主进程
                count(self::$workers) === 0 && exit(0);
            } else {
                $this->log('其它信号, PID: %s', $pid);
                exit(0);
            }
        }
    }

    protected function acceptSignal()
    {
        if (\version_compare(PHP_VERSION, 7.1, '>=')) {
            return \pcntl_wait($status, WUNTRACED);
        }

        // 调用等待信号的处理器
        \pcntl_signal_dispatch();
        $pid = \pcntl_wait($status, WUNTRACED);
        \pcntl_signal_dispatch();

        return $pid;
    }

使用

$options 为构造函数的可选参数,以下为配置项的默认参数。

$options = [
    'daemonize'     => false,   // 是否 daemon 化
    'worker_num'    => 3,       // 下载壁纸进程数量
    'max_free_time' => 60,      // 最大空闲时间(秒)
    'save_dir'      => __DIR__.'/wallpaper',    // 壁纸保存位置
    'queue_key'     => 'wallpaper_url_queue',   // 壁纸下载地址的 redis key
    'redis' => [                                // redis 配置
        'scheme' => 'tcp',
        'host'   => '127.0.0.1',
        'port'   => 6379,
    ],
];

$wallpaper = new BingWallpaperDownloader($options);

$wallpaper->run();

运行效果

vagrant@homestead:~/code/her-cat/download_bing_wallpaper$ php index.php
[2020-02-02 10:41:34] [worker-1] 提取壁纸地址进程启动...
[2020-02-02 10:41:34] [worker-3] 下载壁纸进程启动...
[2020-02-02 10:41:34] [worker-2] 下载壁纸进程启动...
[2020-02-02 10:41:34] [worker-4] 下载壁纸进程启动...
[2020-02-02 10:41:35] [worker-1] 提取壁纸数量: 12, 当前页面: 1
[2020-02-02 10:41:35] [worker-2] 壁纸下载成功, http://h1.ioliu.cn/bing/NutcrackerSeason_EN-AU8373379424_1920x1080.jpg
[2020-02-02 10:41:35] [worker-3] 壁纸下载成功, http://h1.ioliu.cn/bing/zhenghe_ZH-CN9628081460_1920x1080.jpg
[2020-02-02 10:41:36] [worker-4] 壁纸下载成功, http://h1.ioliu.cn/bing/MonumentFountain_EN-AU10536043652_1920x1080.jpg
[2020-02-02 10:41:37] [worker-2] 壁纸下载成功, http://h1.ioliu.cn/bing/JeanLafitte_EN-AU11428973003_1920x1080.jpg
[2020-02-02 10:41:37] [worker-1] 提取壁纸数量: 12, 当前页面: 2
[2020-02-02 10:41:37] [worker-3] 壁纸下载成功, http://h1.ioliu.cn/bing/MorondavaBaobab_EN-AU11363642614_1920x1080.jpg
[2020-02-02 10:41:37] [worker-3] 壁纸下载成功, http://h1.ioliu.cn/bing/SnowHare_ZH-CN9767012872_1920x1080.jpg
[2020-02-02 10:41:38] [worker-4] 壁纸下载成功, http://h1.ioliu.cn/bing/ShenandoahAutumn_EN-AU11784755049_1920x1080.jpg
^C[2020-02-02 10:41:38] [worker-0] 其它信号, PID: -1

保存的壁纸

vagrant@homestead:~/code/her-cat/download_bing_wallpaper/wallpaper$ ls
AbstractSaltBeds_ZH-CN8351691359_1920x1080.jpg         MauiEucalyptus_ZH-CN5616197787_1920x1080.jpg
AcadiaBlueberries_ZH-CN6014510748_1920x1080.jpg        may1_ZH-CN8582006115_1920x1080.jpg
AdelieBreeding_ZH-CN1750945258_1920x1080.jpg           MeerkatHuddle_ZH-CN1358126294_1920x1080.jpg
AdobeSantaFe_EN-AU3063917358_1920x1080.jpg             MeerkatMob_ZH-CN3788674757_1920x1080.jpg
AerialKluaneNP_ZH-CN4080112842_1920x1080.jpg           MeteorCrater_EN-AU9993563603_1920x1080.jpg

最后

完整代码:http://github.com/her-cat/wallpaper_crawler/blob/master/BingWallpaperDownloader.php

关于 PHP 多进程的实践到这里就结束了,目前来看代码好像没啥太问题,后面有问题再来改吧。

溜了...

博客地址:她和她的猫,欢迎关注。
本文转载于网络 如有侵权请联系删除

相关文章

  • django 连接数据库出现1045错误的解决方式

    根据菜鸟教程Django教程学习,运行”pythonmanage.pymigrate”报错,出现django.db.utils.OperationalError:(1045,“Accessdeniedforuser‘账号’@’localhost’(usingpassword:YES)”) 错误。这种错误指的是连接数据库时账号密码错误。1.只需要修改setting.py文件里的DATABASES即可:(按照图中注释修改)DATABASES={ 'default':{ 'ENGINE':'django.db.backends.mysql',#引擎,根据使用数据库类型进行更换 'NAME':'test',#这里填写你的数据库名字 'USER':'test',#这里填写你的连接用户名 'PASSWORD':'test123',#填写你的连接密码 'HOST':'localhost

  • ROS平台下INDEMIND双目惯性模组运行实时ORB教程及Demo

    本文涉及很多代码及文字,排版、文字错误请见谅。阅读时间预计30分钟本文涉及图像、数据均由INDEMIND双目视觉惯性模组采集前两天,我们更新了INDEMIND双目惯性模组在ROS平台下实时运行ORB-SLAM的教程与Demo,但很多小伙伴根据教程修改后仍运行出错,这次我们把修改好的代码及文件上传至GitHub,各位同学按教程修改后,可根据我们提供的代码进行对比,确保万无一失。GitHub链接:https://github.com/INDEMINDtech/ORB-SLAM2-一:环境介绍系统:Ubuntu16.04ROSORB依赖库:Pangolin、OpenCV、Eigen3、DBoW2、g2o,ros二:下载SDK及ORB-SLAM源码下载地址:SDK:http://indemind.cn/sdk.htmlORB-SLAM:https://github.com/raulmur/ORB_SLAM2三:修改源码1、下载好SDK之后,进入SDK-Linux/demo_ros/src目录。将下载好的放在该目录下,并对其改名,改为 ORB_SLAM2.进入ORB_SLAM2/Examples

  • 大厂不是衡量能力的唯一出路,上财学姐毕业三年的经验分享

    Datawhale干货 作者:玲玲,上海财经大学,Datawhale成员距离我的社招已经过去半年了,虽然没有去成大厂,但现在的工作我还是挺满意的。像一位朋友说的,评价一个人的标准应该是多方位的,大厂不是衡量能力的唯一出路,没去大厂也不意味着面试经验失去了价值。所以才有了这篇文章,希望我的这篇经验贴能给大家带来一点帮助~个人情况上财本硕,金融信息工程专业,在某二线互联网企业算法工程师工作2年多。无竞赛无论文,投递的岗位是NLP算法工程师。简历简历是面试敲门砖,多花一些时间好好打磨简历绝对是没错的。简历主要包括个人基本信息,教育经历,工作经历,论文发表,专业技能几个方面。由于我没有论文发表经验,所以工作经历就是我的重点关注项目。我的第一件事就是打磨简历,保证通过简历筛选这一关。可以找一个简历的范本对照着填充自己的内容,保证简历的结构清晰,内容精炼,项目经历和应聘岗位相关,最好是发给有当面试官经历的朋友看看。但其实我在上家公司做过的项目并不是那么的贴近NLP算法,想面试NLP算法工程师岗位存在一定的难度。记得当时我写完简历的第一版之后,发给一个朋友看,他直接说我的项目太简单了(虽然直接但也是

  • QRCode.js 生成二维码

    之前朋友问我关于二维码的,当时为了帮他我就去问问了前端的怎么编写的二维码。现在刚好我的项目里也需要设置二维码,刚好用上了。嘻嘻! QRCode.js是一个用于生成二维码图片的插件。 http://code.ciaoca.com/javascript/qrcode/ 去这里看吧! 我朋友用的是java编写的。很棒的! 1packagetwoDimensionCode; 2 3importjava.awt.Color; 4importjava.awt.Graphics2D; 5importjava.awt.image.BufferedImage; 6importjava.io.File; 7importjava.io.FileOutputStream; 8importjava.io.IOException; 9importjava.io.InputStream; 10importjava.io.OutputStream; 11 12importjavax.imageio.ImageIO; 13 14importjp.sourceforge.qrcode.QRCodeDecode

  • java面向对象编程

    一、类与实例   类的定义:class类名(一般大写字母开头,命名规则驼峰命名)   类是抽象概念:例如人类,是不存在的    classPerson{ privateStringname; privateintage; publicvoidsetName(Stringname){ if(name==null||name.isEmpty()){ thrownewIllegalArgumentException("invalidname"); } this.name=name; } publicvoidsetNameAndAge(Stringname,intage){ this.name=name; this.age=age; } publicvoidsetAge(intage){ this.age=age; } publicStringgetName(){ returnthis.name; } publicintgetAge(){ returnthis.age; } }复制 ViewCode   实例:类的具体化,例如:张三、李四     创建实例:类实例名=new类();    

  • Sublime必用快捷键[私人]

      最近一年前端开发都是用sublime这款编辑器,相对于webStorm强大而启动慢、editplus快启动而功能弱,sublime恰好在两者之间;而且其指令行安装、更新、卸载插件比eclipse之流更迎合程序员,所以个人代码开发的首选编辑器当然是它!“工欲善其事必先利其器”,开发工具用多了都晓得“快捷键”就是使用IDE的“利器”。以下就是自己日常使用sublime都会用上的“必用快捷键”。    所有IDE必用: ctrl+c复制 ctrl+x剪切 ctrl+v粘贴 ctrl+z回滚 ctrl+y前进ctrl+a全选复制     sublime必用: ctrl+w关闭当前代码文件 ctrl+PageUp切换到左边文件(如果左边没有则切换到最右文件) ctrl+PageDown切换到右边文件(如果右边没有则切换到最左文件) ctrl+P查找、打开当前工程内的文件(最喜欢的快捷键) ctrl+G跳转到某行代码ctrl+H在打开代码文件代替代码 ctrl+F在打开代码文件搜索代码 ctrl+shift+D复制当前行代码到下一行ctrl+shift+K

  • 从SAP ECC升级到SAP S4HANA, 几个Key Points

    从SAPECC升级到SAPS4HANA,几个KeyPoints     自从SAP公司的拳头产品S/4HANA横空出世以来,就引起了世界范围内的众多客户以及ERP咨询业界的强烈关注。   笔者发现很多早些年就实施了SAPECC老版本的跨国企业,依旧在使用这些老版本的SAP系统,对于它们而言SAPERP系统早已成为企业供应链管理的战略平台,升级SAP系统是一个大动作,不到万不得已不好轻易实行。   而对于众多第一次实施SAP系统的企业而言,直接实施SAP的比较新的S4/HANA系统,完全回避了SAP系统升级的问题。   关于SAP系统从ECC到S4HANA的升级项目,将会是未来很多年里SAP咨询行业里的重要项目机会。一些跨国企业将会作出相关预算,投入重金在IT信息化方面,用于将十多年前就已经实施过的SAPECC项目升级到SAPS4HANA平台。笔者在内的诸多同行,也将会从这些项目中受益。   本文主要关注从SAPECC升到SAPS4HANA的几个关键点。如下文字,部分来自互联网。     A,SAPECC和S

  • docker安装nacos

    网上安装的说明很多,比较好一点是https://www.cnblogs.com/binz/p/12295346.html和 https://blog.csdn.net/u011374856/article/details/109204466,我一般希望把配置和日志文件放到ubuntu物理机上,首先说说我的逻辑,1直接运行docker,2.把docker里面的配置拷贝到ubuntu上,3重新云心新的实例 首先还是准备数据库部分,sql地址:https://github.com/alibaba/nacos/blob/master/distribution/conf/nacos-mysql.sql /******************************************/ /*数据库全名=nacos_config*/ /*表名称=config_info*/ /******************************************/ CREATETABLE`config_info`( `id`bigint(20)NOTNULLAUTO_INCREMENT

  • Leetcode简单题背后的数学规律 | LCP 11. 期望个数统计

    最近签到打卡,每日额外再刷两道题攒积分。遇到一个简单题LCP11.期望个数统计,挺有意思的,记录一下分析过程并重温概率学知识。 题目 给定n个数的数组scores,小A和小B负责对scores按从高到低选择元素,对于其中相同的元素会等概率选择。求A和B选择的顺序中相同次序是同一元素的总个数的期望。 分析 首先很容易将数组划分为不同段,只出现一次的元素选择是完全一致的,只有出现至少2次的元素需要重新计算期望。不同段之间是独立的(从高到低选择),那么针对每一段重复k个元素来研究其期望,最终结果是各段期望之和。 子问题:长度为m的数组,A和B随机选择m次,求选择的排列中同一位置是同一元素的总个数的期望。 原问题这里每个元素值一样,但实际是考虑在原位置上是同一元素,则可以看作是不同值更方便。 进一步,可以固定其中一个人选择次序,求另一个人m!中情况中元素出现相同位置个数的期望。 很容易写出k位置个不相同时的答案(写错了!!!使k个位置互不相同的排列数并不是\({C_{m}^{k}(k!-1)}\)): 【【【【分析错了忽略该部分】】】】 概率为 \[\frac{C_{m}^{k}(k!-1)}

  • IOS开发基础知识--碎片48

    1:AssertionfailureindequeueReusableCellWithIdentifier:forIndexPath:  staticNSString*CellIdentifier=@"Cell"; UITableViewCell*cell=[tableViewdequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];复制 上面是在IOS9以下一直报闪退;后来改成下面解决: staticNSString*CellIdentifier=@"Cell"; UITableViewCell*cell=[tableViewdequeueReusableCellWithIdentifier:CellIdentifier]; if(cell==nil){ cell=[[UITableViewCellalloc]initWithStyle:UITableViewCellStyleSubtitlereuseIdentifier:CellIdentifier]; }复制 2:Core

  • 不写完不回家的TreeSet

      TreeSet详解   继承架构图:                 |——SortedSet接口——TreeSet实现类 Set接口——|——HashSet实现类                 |——LinkedHashSet实现类 实现的接口: HashSet与TreeSet都是基于Set接口的实现类。其中TreeSet是Set的子接口SortedSet的实现类。Set接口及其子接口、实现类的结构如下所示:     2.TreeSet实现原理:     其实TrreSet是依靠TrreMap实现的:         &nb

  • PHP调试小错误汇总

    1.中文显示乱码 解决:加上header('Content-type:text'); 2.Unabletofindthewrapper"https 解决:到php.ini中把extension=php_openssl.dll前面的;删掉,重启apache服务

  • 张仰彪排序法

    起因 在泡CSDN,无意间刷到了一个看上去很有趣的东西,叫张仰彪排序法,看了看感觉还行,就写一篇笔记吧。 基本思路 思路十分新颖,总体来说就是排座位:找到自己的排名再移过去。 具体步骤如下: 初始数组:{\({\color{red}4}\),5,2,4,1,3,0,2,2,0} 第1步: 排位置0上的数"4",有7个比它小的数,它的排名为7, 由于排名7大于位置0, 所以将它与位置7上的数"2"交换。 数组现在变为: 复制 {\({\color{red}2}\),5,2,4,1,3,0,4,2,0} 第2步: 排位置0上的数"2",有3个比它小的数,它的排名为3, 由于排名3大于位置0, 所以将它与位置3上的数"4"交换。 数组现在变为: 复制 {\({\color{red}4}\),5,2,2,1,3,0,4,2,0} 第3步: 排位置0上的数"4",有7个比它小的数,它的排名为7, 由于排名7大于位置0, 所以将它与位置7上的数"4"交换。 由于将要交换的两个数相等。 所以延后一个位置,将它与位置8上的数"2"交换, 数组现在变为: 复制 {\({\co

  • redis(二)

    Redis简单使用   一.Redis的安装 Redis作为一款目前这个星球上性能最高的非关系型数据库之一.拥有每秒近十万次的读写能力.其实力只能用恐怖来形容.   mac版本,brew: https://blog.csdn.net/weixin_45509705/article/details/119242390   windows版本: 安装redis Redis是我见过这个星球上最好安装的软件了.比起前面的那一坨.它简直了... 直接把压缩包解压.然后配置一下环境变量就可以了.   接下来,在环境变量中将该文件夹配置到path中.   win7的同学自求多福吧...   我们给redis多配置几个东西(修改redis的配置文件,mac是:redis.conf,windows是:redis.windows-service.conf) 关闭bind #bind127.0.0.1::1#注释掉它复制 关闭保护模式windows不用设置 protected-modeno  #设置为no复制

  • 将一个list中的元素的某一属性取出来单独放到一个list里面

    有很多时候我们会遇到这样的场景,就是要将一个list中的某一个元素中的某一属性单独拿出来放在一个新的list里面,这中时候,我们就可以用以下的方法来进行实现: List<DTO>items=newArrayList<>(); List<String>collect=items.stream().map(DTO::getId).collect(Collectors.toList()); 复制 这样我们就获取到了DTO中的id的一个list。 作者:慢慢积累终成山 来源:CSDN 原文:https://blog.csdn.net/YHyanghaoaixin/article/details/84772510 Listentoyourheart.

  • 内存,寄存器与汇编语言 vol.1

     所有机器能够识别的语言都是由10组成的机器语言,所有高级语言都需要经汇编语言由编译器转换成最基础的机器语言让计算机执行。相比于高级语言,汇编语言更加直接执行更加高效,然而编写起来更不人性化。  不同芯片的汇编语言不同,每家公司有不同的ISA(指令集)。指令与数据在形式上是一样的。 数据大小: 一个二进制位称为bit  1M=1024K  1K=1024B1B(Byte字节)=8bit   内存地址空间:   并不是平时所买的内存条才算内存,在各个硬件中也有许许多多的储存单元(显存etc),在逻辑上cpu将所有储存器整合视为一个内存成为内存地址空间,按储存性质可以分为       ROM与RAM,rom(只读)在断电时不会丢失数据,可以存放bios这样重要的程序。 cpu对硬件的控制:  cpu对硬件控制首先对对应的内存进行数据传输与读写进行,这一过程通过总线完成,从逻辑上来分总线分为  地址线,数

  • [map的实现原理--红黑树]

    map--红黑树 事情起因于pta的某道题-树种统计 数据结构题肯定不能用STL里面的map,但是这个题明显就是用map做,于是我想能不能自己实现一个map呢?了解到map的实现原理是红黑树! 前置知识:二叉搜索树 红黑树的基本原理就是二叉搜索树,二叉搜索树又叫二叉排序树,定义是左儿子比他小,右儿子比他大。那么这样查找的时候,就可以按照这种方式以logn的级别去查找,效率很高。但要考虑一种最坏的情况那就是从开头到结尾,元素的插入都是按照从大到小(或从大到小)的顺序去插入,比如插入的数是54321,那么这个树的形状就是这个样子 那么这样的话,查找的复杂度就会变成O(n),因为他每次都会从他的左儿子往下找,就相当于遍历一个数组一样,那么有没有一种方法可以解决这种问题呢?有!它就是红黑树,那么它为什么可以控制这个链状的结构呢,那就是跟他的性质有关系了。 性质 1.结点有颜色,且要么是红,要么是黑。 2.根结点是黑色。 3.所有叶子都是黑色。(注意这里的叶子指的是空结点) 4.每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点) 5.从任一结点到其每个叶子的

  • [部署日记]Android Studio在安装完后,sdk依旧提示SDK emulator directory is missing

    问题起源: 今天在闲着没事搞了个go的rest-api,用postman测试了一下可行,于是一拍大腿决定写一个安卓手机程序,于是一拍大腿重新下载了卸载没多久因为没空间放原神的AndroidStudio。 虽说AndroidDeveloper站点过了认证,使得AndroidStudio能直连的方式在https://developer.android.google.cn/studio/里下载      习惯性地往下翻"sdk"字样下载:      然后它说 //关键提示:ThesetoolsareincludedinAndroidStudio.复制 行吧,又包含到AndroidStudio里面了是吧,设置打开       不是吧啊卓,说好的集成了呢? (随之在"Edit"后更改物理位置) //这是AndroidStudio给的sdk默认安装位置: C:\Users\Administrator\AppData\Local\Android\Sdk复制 更改后保存   //迫真翻

  • awk 多分隔符

    1、awk-F'[,\t]':[]之间的内容为分隔符,此处,逗号和\t是分隔符 2、FS="[,\t]"

  • 框架面试

    Spring SpringDI(IOC)是什么?如何实现? SpringAOP是什么?使用场景?底层如何实现?   项目里用到了AOP? 为什么要使用aop,直接抽取方法封装起来不行吗? 待定  springbean的生命周期,注入方式 参考:https://www.cnblogs.com/youngao/p/12576719.html springmvc执行流程  Spring的事务管理怎么做的?除了注解,项目里还用到哪些事务管理?   5.Spring事务传播机制有哪几种 7.Spring如何解决循环依赖 8.Spring如何实现懒加载      问了自己实现的SpringIOC的整个流程,直接细节到了代码,说了一遍没听清楚,然后又说了一遍。   Spring 介绍IOC 有一个第三方的工具库,我想把它包装成一个Bean进行自动装配,应该怎么做 如果想在一个service执行前后打印日志,如何做(AOP) 常用框架:SpringBoot,SpringBoot启动原理?Spring,Spr

  • Cracking the coding interview 第一章问题及解答

    Crackingthecodinginterview第一章问题及解答 不管是不是要挪地方,面试题具有很好的联系代码总用,参加新工作的半年里,做的大多是探索性的工作,反而代码写得少了,不高兴,最近开始重新捡起面试题,来练练手,让自己保持代码的感觉。 代码主要是c的,可以避免使用容器之类的封装。因为使用c的话更能触及细节,而这也正是面试题所要考察的。同时,尽量为每道题添加了单元测试的用例。 代码是在windows下编辑运行的,只能保证在windows下正常运行,因为windows下的c编译器和linux下的c编译器对c/c++的支持是不同的,所以不能保证在linux运行。关于两者的差异可以在网上搜一下。 代码放在github上,地址。问题的描述都在对应的代码文件中。目前只是第一章,其他的章节还在进行中。 如果代码有问题,请指正,谢谢。 yetuweiba

相关推荐

推荐阅读