菜鸟公园
  • 编程技能
    • PHP
    • CPP
    • Golang
    • MySQL
    • 工具分享
  • 英语学习
  • 信文随笔
  • 关于本站
sign in/up

分类目录归档:编程技能

2020-11-30 作者:菜鸟DJ 0
工具分享, 编程技能

让浏览器自动移除知乎的登录弹窗(已更新)

由于某些原因,我注销了知乎的账号。但是在网页的搜索中,会出现一些知乎的内容。当我点进去会发现,知乎会强制登录才能查看内容。以前会有一个关闭的按钮,最近关闭的按钮被去除了。

作为程序员,肯定不希望阅读体验这么差,所以用 油猴工具 制作了如下脚本,自动移除弹窗,并恢复页面的滚动条。

// ==FeHelperMonkey==
// @reminder        请不要删除这部分代码注释,这是FeHelper油猴脚本能正常工作的基本条件!当然,你可以按需修改这里的内容!
// @id              mf_1606719966367
// @name            关闭知乎登录
// @url-pattern     https://*.zhihu.com/question/*
// @enable          true
// @require-js      
// @auto-refresh    0
// @updated         2020-12-01 10:03:30
// ==/FeHelperMonkey==


// 在这里,可以随便写你的代码,并且,你的代码中
// 1. 可以进行页面上的所有DOM操作
// 2. 可以访问页面上原本已挂载的所有Js变量,比如页面上已经有了jQuery,你可以直接使用
// 3. 可以依赖注入一个第三方js脚本,然后在你的代码中直接使用,如:依赖jQuery后直接使用
// 4. 好了,你的代码可以这样写:
var toast = (content,time) => {
    return new Promise((resolve,reject) => {
      let elAlertMsg = document.querySelector("#fehelper_alertmsg");
      if (!elAlertMsg) {
          let elWrapper = document.createElement('div');
          elWrapper.innerHTML = '<div id="fehelper_alertmsg" width="100%" style="position:absolute;top:10px;left:50%;-webkit-transform:translateX(-50%);z-index:100">' +
              '<tb style="background:red;display:inline-block;color:white;text-align:center;' +
              'padding:10px 10px;margin:0 auto;font-size:20px;border-radius:8px;">' + content + '</tb></div>';
          elAlertMsg = elWrapper.childNodes[0];

        document.body.appendChild(elAlertMsg);
        // elAlertMsg = document.getElementById("fehelper_alertmsg");
        // elAlertMsg.style.left=(document.body.clientWidth-elAlertMsg.offsetWidth)/2+"px";
      } else {
          elAlertMsg.querySelector('p').innerHTML = content;
          elAlertMsg.style.display = 'block';
      }

        window.setTimeout(function () {
          elAlertMsg.style.display = 'none';
            resolve && resolve();
      }, time || 1000);
  });
};

(()=>{
  toast("【菜鸟公园】关闭知乎登录弹窗",2000).then(()=>{
    let a = document.getElementsByClassName('Modal-wrapper');
    for (i=0;i<a.length;i++){
      a[i].parentNode.removeChild(a[i]);
    }
    document.documentElement.style.overflow='visible';
  });
})();

js zhihu 弹窗 油猴 登录 知乎 脚本

2020-11-11 作者:菜鸟DJ 0
工具分享, 编程技能

如何在Windows中体验极致的Linux开发环境

1. 背景

相当长的一段时间,我的工作平台都是MAC,清爽干净的界面,没有广告烦扰,方便的快捷键,好用的触摸板等等。但是换了工作之后,公司配备的是一台Windows台式机,虽然可以带自己的笔记本工作,但是总有些不便。于是一直在折腾Windows下的Linux开发环境。

之前一直是用 vm 搭建,也没有觉得不方便的地方。但是最近一直被安利 wsl2 。于是就安装试用了一番。发现在我的开发环境下完全可以抛弃 vm 了。那么对比 vm 有哪些优势劣势呢。

优势

  • 不用每次点击启动vm,然后点开机或者恢复。直接在终端中选择打开。
  • 不用配置网络,实际上wsl2 你也没有机会配置。wsl2深度融合Windows环境,在wsl启动的服务,在Windows 可以直接 localhost:port 访问
  • 挂载了本地磁盘,虽然vm也可以共享,但是没有这个方便。

劣势

  • 只有Ubuntu可以选择
  • 没有systemd,需要自己些reload脚本
  • 启用hyper v之后,影响vm和模拟器运行

2. 安装WSL2

  1. 从启用或关闭 Windows 功能里勾选适用于 Linux 的 Windows 子系统和虚拟机平台,重启电脑
  2. 以管理员身份启动 powershell ,输入下面指令

    wsl --set-default-version 2

  3. 如果提示要升级内核,按照提示走
  4. 从 Microsoft Store 下载最新 Linux 分发版, 下载完后点击启动,安装
  5. [ 可选 ] 换源,使用阿里Ubuntu镜像。 换完之后执行sudo apt update
  6. [ 可选 ] 从 Microsoft Store 安装 Windows Terminal

3. 搭建开发环境

nginx,php,fpm,mariadb,redis,golang,openssh 等软件均可以通过 apt install 来安装。
通过 service xxx start|restart|stop 来控制状态

  • 特别说明
    mariadb安装完后需要配置root账号密码,参考文章
# 第一次设置密码
sudo mysql_secure_installation

# 控制台登录, 如果有报错,记得使用sudo
sudo mysql -u root -p

# 修改mysqld 绑定IP
sudo vim /etc/mysql/my.cnf
# 注释下面的语句
bind-address  = 127.0.0.1

# php连mysql需要安装扩展
sudo apt install php-mysql

# fastcgi listen字段,需要跟nginx.conf 对应
sudo vim /etc/php/7.4/fpm/pool.d/www.conf
  • Ubuntu20.04 apt install 的服务配置文件目录整理
服务 默认路径 默认配置文件名
nginx /etc/nginx/nginx.conf nginx.conf
maraiadb /etc/mysql/mariadb.conf.d/50-server.cnf my.conf
redis /etc/redis/redis.conf redis.conf
php-fpm /etc/php/7.4/fpm/php-fpm.conf
/etc/php/7.4/fpm/pool.d/www.conf
php-fpm.conf
php /etc/php/7.4/cli/php.ini php.ini
sshd /etc/ssh/ssh_config ssh_config

4. 总结唠嗑

关于wsl2的使用说明,建议阅读官方文档
在 windows 宿主机使用 localhost 就能登录wsl2,不要被国内博客误导!!! 前提是重新安装 openssh 服务。

最后两个安装步骤虽然是可选,但是强烈建议做完,能很大程度的提升使用体验。
换源的好处自然是加快包安装速度
使用 windows Terminal 时让我有点在 MAC 下工作的感觉。总结下有几点:
– 支持多标签页
– 能快捷进入wsl,下拉菜单中点选进入
– 界面重新设计,很清爽,有 Linux 终端的味道,离 MAC 还差点
– 支持 Ctrl C 复制,Ctrl V 粘贴,我很喜欢这个设定。并且 Ctrl C原本的中断功能不受影响

wsl 不支持 systemd ,所以不用折腾开机启动了。需要的服务建议通过 sudo service 服务启动,或者写一个 shell 脚本。

wsl把 windows 磁盘挂载在 /mnt/ 目录下,可以对该目录下文件进行读写操作。

配合 VS CODE 敲代码,只需要安装一个Remote - WSL插件。在 远程资源管理器里选择 WSL Targets 一键连接。 本地开发,即在服务器上开发。NICE

测试过 php 项目 和 golang 的 web 项目,目前没有发现任何功能上的问题。

5. 目前遇到的?坑?

由于我装 ohmyzsh 了,它在git的目录会有额外的操作,所以进入到挂载在 /mnt/ 下面的目录时,会有一段时间读取过程,耗时较长。不用 ohmyzsh 不会有问题。

linux macos terminal ubuntu vm windows wsl

2020-09-05 作者:菜鸟DJ 0
信文随笔, 工具分享

一个方便的数据库操作shell脚本

用法说明

这个脚本可以方便的备份和恢复数据库,而且通过show参数可以用来查看该实例中有多少个数据库。
唯一需要手动操作的是到脚本里配置账号密码。账号密码放在脚本的目的是为了避免每次输入。

导出文件格式是tar.gz。导入的文件格式是zip。这部分跟我的习惯有关系,我备份的数据库都会在本地进行处理压缩成zip上传,就是为了防止没有经过处理的数据库直接拿去导入。

#!/bin/bash

# 这是一个自动化导出MySQL数据库的脚本

# 修改此处
user="root"
passwd="123456"
port=3306
host="127.0.0.1"
dblist=(
"test"
)
root=`pwd`"/"
function printUsage {
    echo "Usage:"
    echo "$0 [-t dump|import|show] [-f file]"
    echo "导出/导入数据库脚本 v1.1"
    echo "导出的sql以数据库命名。"
    echo "导入的文件同样是提取sql文件名!"
    echo "show 参数可以打印数据库名,用于替换dblist"
    echo "dump文件是tar.gz的压缩包,import的文件是zip的压缩包"
}
function printParaErr {
    echo "参数错误!"
}

function printFileNotExist {
    echo "文件不存在!"
}

function printFileNotSupport {
    echo "仅支持压缩文件!(zip)"
}

function cleanOutDir {
    if [ -d $outDir ]; then 
        echo "清理临时目录:" $outDir
        rm -rf $outDir
    fi
}

function addNote {
    echo "是否添加备注文件?[回车跳过]"
    read note

    if [ ${#note} -gt 1 ];then 
        cd $root$outDir
        echo $note > note.txt
    fi
}

if [ $# == 0 ]; then 
    printUsage
    exit 0
fi

GETOPTOUT=`getopt t:f: "$@"`
    set -- $GETOPTOUT
    while [ -n "$1" ]
    do
    case $1 in
        -t)
            type=$2
            shift
            ;;
        -f)
            inputfile=$2
            shift
            ;;
        --)
            shift
            break
            ;;
        *)
            printParaErr
            printUsage;
            exit 1;
            ;;
    esac
    shift
    done
if [ $type == 'dump' ]; then
    echo "目标数据库为 $user@$host:$port"
elif [ $type == 'import' ]; then
    if [ ! -f "$inputfile" ]; then
        printFileNotExist
        printUsage
        exit 0
    fi

    ext_name="${inputfile##*.}"

    if [ $ext_name != 'zip' ]; then
        printFileNotSupport
        printUsage
        exit 0
    fi


    echo "导入源数据为: $inputfile"
    echo "目标数据库为: $user@$host:$port"
elif [ $type == 'show' ]; then
    echo "当前实例存在的数据库:(已排除系统数据库)"
else
    printParaErr
    printUsage;
    exit 1;
fi

# echo "请输入数据库密码,按回车键结束"
# stty -echo # 设置输入字符不回显
# read passwd
# stty echo  # 取消不回显状态

runTime=`date '+%Y%m%d%H%M'`
outDir="./sqlHandler_$runTime/"

cleanOutDir

if [ $type == 'dump' ]; then
    echo "创建导出目录:" $outDir
    mkdir -p $outDir

    for db_name in ${dblist[*]}; do
        cmd="mysqldump -h $host -u $user -P $port -p$passwd $db_name"
        echo $cmd | sed "s/-p$passwd/-p****/g";
        $cmd > "$outDir$db_name.sql";
    done
    cd $outDir
    tar cvzf "dbbak_$runTime.tar.gz" *
    rm -rf *.sql
    addNote
    echo "导出文件为:"
    echo $root$outDir"/"`ls|grep tar`|sed -e "s/\/\//\//g" -e "s/\/\.\//\//g"

elif [ $type == 'import' ]; then
    echo "创建临时目录:" $outDir
    mkdir -p $outDir
    unzip -d $outDir $inputfile
    files=$(ls $outDir)
    cd $outDir
    echo "mysql command:"
    for file in ${files[*]}; do
        db_name="${file%.*}"
        # ext_name="${file##*.}"
        cmd="mysql -h $host -u $user -P $port -p$passwd --default-character-set=utf8 $db_name"
        echo -e "\t"$cmd " < $file" | sed "s/-p$passwd/-p****/g";
        $cmd < $file
    done
    cd $root
    cleanOutDir
    # $cmd < $inputfile
elif [ $type == 'show' ]; then
    rm -rf $outDir
    cmd="mysql -h $host -u $user -P $port -p$passwd "
    $cmd -e "show databases;" |grep -v -E 'Database|mysql|information_schema|performance_schema'|sed -e 's/^/"/g' -e 's/$/"/g'
    # echo $result
fi

if [[ $? == 0 ]]; then
    echo "操作完成!";
else
    echo "┗━━ 操作失败!";
fi

bash MySQL mysqldump shell

2020-05-03 作者:菜鸟DJ 0
PHP

PHP中静态变量作用域浅谈

最近看代码,发现有个代码写的有意思,在函数内用static修饰局部变量。当时心想,难道是跟global一样,扩大作用域?又看到同一个类里面,两个方法都用static修饰了同样的变量,我明白了,事情肯定没有我想象的这么简单,所以我写了下面的这个方法来测试下。

<?php
class MyTest
{
    public static function func1($arg1)
    {
        static $result = [];
        $p = serialize($arg1);
        var_dump($p);
        if (isset($result[$p])) {
            return $result[$p];
        }
        $result[$p] = date('Y-m-d H:i:s') . " by func1 rand:".rand(0,100);
        return $result[$p];
    }
    public static function func2($arg1)
    {
        static $result = [];
        $p = serialize($arg1);
        var_dump($p);
        if (isset($result[$p])) {
            return $result[$p];
        }
        $result[$p] = date('Y-m-d H:i:s') . " by func2 rand:".rand(0,100);
        return $result[$p];
    }
}
$arg = ['1' => '2'];
var_dump(MyTest::func1($arg));
var_dump(MyTest::func2($arg));
var_dump(MyTest::func1($arg));
var_dump(MyTest::func2($arg));

这个输出应该是如何呢?大家不妨想一想。同样的static $result 结果会相同吗?

好了我们一起来看输出。

string(18) "a:1:{i:1;s:1:"2";}"
string(36) "2020-04-28 03:30:04 by func1 rand:45"
string(18) "a:1:{i:1;s:1:"2";}"
string(36) "2020-04-28 03:30:04 by func2 rand:40"
string(18) "a:1:{i:1;s:1:"2";}"
string(36) "2020-04-28 03:30:04 by func1 rand:45"
string(18) "a:1:{i:1;s:1:"2";}"
string(36) "2020-04-28 03:30:04 by func2 rand:40"

从上面可以看出,static $result的作用域还是自己的函数内。但是在类实例化的生命周期内,当函数返回结果时,其生命周期并没有释放。
那么我们可以利用这个特性来缓存函数操作的结果,而又不需要定义类的成员变量。

2020-03-28 作者:菜鸟DJ 0
工具分享

手机app https抓包步骤一揽

1. 工具准备

  • WireShark
    下载地址 开源工具,免费使用,支持WIN与OSX平台。
    Wireshark(前称Ethereal)是一个网络封包分析软件。网络封包分析软件的功能是撷取网络封包,并尽可能显示出最为详细的网络封包资料。Wireshark使用WinPCAP作为接口,直接与网卡进行数据报文交换。

  • Fiddler
    下载地址 开源工具,免费试用,支持WIN平台,OSX需要自行编译。
    Fiddler是一个http协议调试代理工具,它能够记录并检查所有你的电脑和互联网之间的http通讯,设置断点,查看所有的“进出”Fiddler的数据(指cookie,html,js,css等文件)。 Fiddler 要比其他的网络调试器要更加简单,因为它不仅仅暴露http通讯还提供了一个用户友好的格式。

2. 如何使用

http的抓包使用Fiddler 或者其他代理软件很直接的就可以完成。但是https的抓包因为经过了加密,所以并不能直接看到抓包的内容,只能看到密文。那么如何使用http来进行抓包呢?

设置界面-打开HTTPS抓包
设置界面-打开代理
设置完成后,关闭软件重新打开。条件允许的情况下最好是重启电脑。
然后在手机里输入网址 http://pc.ip:8080 看是否出现如下截图。出现表示fiddler设置正确。
手机安装证书
iOS只能下载该证书,要安装证书需要2个步骤。 安卓机暂不清楚。
1. 点设置-> 通用 -> 描述文件。 找到刚下载的“DO_NOT_TRUST_FiddlerRoot”点击安装
2. 点设置-> 通用 -> 关于本机 -> 证书信任设置。 点击完全信任。

然后打开浏览器,输入任意网址,能正常打开即表示完成。

3. 抓包结果

现在打开手机app,便可以看到fiddler里出现http和https的访问记录了。看下面的实例。

2020-02-27 作者:菜鸟DJ 0
PHP

记一下RabblitMQ的安装和RPC的工作模式

1. 什么是Rabbitmq

Rabbitmq 是一款由erlang开发的基于AMQP协议的消息队列。官网地址https://www.rabbitmq.com 有六种工作模式。详细的教程在本文就不再赘述,可以直接去官网阅读。

2. 安装RabbitMq

安装环境CentOS7,步骤如下:

  • 从源码安装
#下面是从源码master分支安装示例
git clone https://github.com/erlang/otp.git
cd otp
./otp_build autoconf
./configure
make
make install

安装完成之后,记得修改/etc/profile,设置erlang的路径

vim /etc/profile
export ERLANG="/usr/local/lib/erlang/bin/"
export PATH=/usr/local/php/bin:/usr/local/nginx/sbin:$PATH:$ERLANG

source /etc/profile
  • 安装socat
    sudo yum install socat
  • 下载Rabbitmq-server安装包
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.2/rabbitmq-server-3.8.2-1.el7.noarch.rpm
rpm -ivh --nodeps rabbitmq-server-3.8.2-1.el7.noarch.rpm

一定要注意加上--nodeps, 不然会提示erlang依赖版本问题

  • 启动服务端
rabbitmq-plugins list #查看组件是否正常显示
rabbitmq-plugins enable rabbitmq_management # 加载web管理组件
rabbitmq-server #启动服务端

在浏览器里输入http://localhost:15672,看后台是否出来,默认账号密码都是guest

3. RPC下如何工作

在上面的web页可以添加用户,由于默认的guest是不允许在外网访问,所以我们新增了yhw这个用户
从官网下载教程代码。到php目录下执行composer install。修改好配置之后我们启动rpc_server.php和rpc_client.php即可。
最后附上修改的测试代码。
rpc_server.php

<?php

require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

$connection = new AMQPStreamConnection('10.211.55.3', 5672, 'yhw', 'yhw');
$channel = $connection->channel();

$channel->queue_declare('rpc_queue', false, false, false, false);

function fib($n)
{
    if ($n == 0) {
        return 0;
    }
    if ($n == 1) {
        return 1;
    }
    return fib($n - 1) + fib($n - 2);
}

// 我增加的测试函数
function sayHello($n)
{
    return sprintf(
        "%s Hello gentleman, I am rabbitmq, working on RPC mode. You typed %d, the result is %d",
        date('Y-m-d H:i:s'),
        $n,
        fib($n)
    );
}

echo " [x] Awaiting RPC requests\n";
$callback = function ($req) {
    $n = intval($req->body);
    echo ' [.] fib(', $n, ")\n";

    $msg = new AMQPMessage(
        (string)sayHello($n),
        array('correlation_id' => $req->get('correlation_id'))
    );

    $req->delivery_info['channel']->basic_publish(
        $msg,
        '',
        $req->get('reply_to')
    );
    $req->delivery_info['channel']->basic_ack(
        $req->delivery_info['delivery_tag']
    );
};

$channel->basic_qos(null, 1, null);
$channel->basic_consume('rpc_queue', '', false, false, false, false, $callback);

while ($channel->is_consuming()) {
    $channel->wait();
}

$channel->close();
$connection->close();
?>

rpc_client.php

<?php

require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

class FibonacciRpcClient
{
    private $connection;
    private $channel;
    private $callback_queue;
    private $response;
    private $corr_id;

    public function __construct()
    {
        $this->connection = new AMQPStreamConnection(
            '10.211.55.3',
            5672,
            'yhw',
            'yhw'
        );
        $this->channel = $this->connection->channel();
        list($this->callback_queue, ,) = $this->channel->queue_declare(
            "",
            false,
            false,
            true,
            false
        );
        $this->channel->basic_consume(
            $this->callback_queue,
            '',
            false,
            true,
            false,
            false,
            array(
                $this,
                'onResponse'
            )
        );
    }

    public function onResponse($rep)
    {
        if ($rep->get('correlation_id') == $this->corr_id) {
            $this->response = $rep->body;
        }
    }

    public function call($n)
    {
        $this->response = null;
        $this->corr_id = uniqid();

        // 此处为调用参数,以及回复
        $msg = new AMQPMessage(
            (string) $n,
            array(
                'correlation_id' => $this->corr_id,
                'reply_to' => $this->callback_queue
            )
        );
        $this->channel->basic_publish($msg, '', 'rpc_queue');
        while (!$this->response) {
            $this->channel->wait();
        }
        return $this->response;
    }
}

$fibonacci_rpc = new FibonacciRpcClient();
$n = (int)$argv[1];
while(1){
    $response = $fibonacci_rpc->call($n);
    echo ' [.] Got ', $response, "\n";
    usleep(100);
}
?>

稍微改动下,客户端的代码,多起几个php脚本,看执行结果是否能回复正确。看下面截图结果完全正确。
运行结果

看看后台

2020-02-23 作者:菜鸟DJ 0
Golang, PHP, 信文随笔

聊一聊快排算法

曾经有一个人让我写下快排算法,我给他写出来了,然后就被Diss了,怎么申请这么多临时变量。来,各位看官一起看看本菜🐔的作业。各位看官也可以动手写一写。

快速排序的逻辑 — 来自百度百科

快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

<?php

$input = [100, 23, 452, 234, 56223, 6234, 134, 51, 234];
$ret = quickSort($input);
printf("%s\n", json_encode($ret));


function quickSort($input)
{
    $len = count($input);
    if ($len <= 1) {
        return $input;
    }

    $temp = $input[0];
    $left = $right = [];
    for ($i = 1; $i < $len; $i++) {
        if ($temp >= $input[$i]) {
            $left[] = $input[$i];
        } else {
            $right[] = $input[$i];
        }
    }

    $left = quickSort($left);
    $right = quickSort($right);
    return array_merge($left, [$temp], $right);
}

基本上也是符合快速排序的标准,不过确实$left和$right这两个属于数组copy,会需要大量的内存空间的。但是既然说到利用了中间变量来存放数组,那么是否有别的方式呢?

答案肯定是有的了,既然不能用中间变量存数组,那么我可以使用交换的方式,因为反正都是比大小,我们通过一个临时变量将原数组的数据挖一个出来,然后再去比较,可以看下面的示例。

[100, 23, 452, 234, 56223, 6234, 134, 51, 234]
第一趟,我先取100出来,然后从右向左扫描,比他小的(51<100)放入他的坑,那么结果如下:
[---, 23, 452, 234, 56223, 6234, 134, 51, 234]
从左扫发现51,填入左边的坑。
[51, 23, 452, 234, 56223, 6234, 134, ---, 234]
然后从51的下个值往右扫,发现452>100,那么把452填入上面挖的坑
[51, 23, ---, 234, 56223, 6234, 134, 452, 234]
然后从452往左扫描,发现没有小于100的。那么100 就只能去填坑了。自此我们做了一个循环。
[51, 23, 100 234, 56223, 6234, 134, 452, 234]

那么下一个循环我们怎么开始呢?因为100的右边肯定是大于它的,100的左边肯定是小于它的。PS.这不是废话吗?上面的快排就是这样要求的。
我们就从51,到100 这三个元素之间继续排序。
[51,23,100]
[--,23,100]
[23,--,100]
[23,50,100]
最左边的完成了,那么我们来看看最右边的
[234,56223,6234, 134, 452, 234]
.... 各位看官自己实现下看看?

好了直接上代码

<?php 
function quickSort(&$input, $low, $high)
{
    if ($high <= $low) return;
    $i = $low;
    $j = $high;
    $key = $input[$low];
    while ($i < $j) {
        while ($i < $j && $input[$j] >= $key) {
            $j--;
        }
        if ($i < $j) {
            $input[$i] = $input[$j];
            $i++;
        }
        while ($i < $j && $input[$i] < $key) {
            $i++;
        }
        if ($i < $j) {
            $input[$j] = $input[$i];
            $j--;
        }
        // printf("find i(%d),j(%d)\n", $i, $j);

    }

    $input[$i] = $key;
    quickSort($input, $low, $i - 1);
    quickSort($input, $i + 1, $high);
    // return $input;
}

为了节省递归参数赋值,我们传了引用进去,更加节省内存空间。那么具体效果如何呢,我们来看看跟内置函数sort的对比结果。

排序个数:100000
内置排序耗时:27.215004 ms
手写排序耗时:104.742050 ms
结果抽查 1

排序个数:200000
内置排序耗时:61.784983 ms
手写排序耗时:241.871119 ms
结果抽查 1

排序个数:300000
内置排序耗时:87.085009 ms
手写排序耗时:448.505878 ms
结果抽查 1

排序个数:400000
内置排序耗时:120.175838 ms
手写排序耗时:618.201971 ms
结果抽查 1

排序个数:500000
内置排序耗时:149.783850 ms
手写排序耗时:796.765804 ms
结果抽查 1

排序个数:600000
内置排序耗时:182.029963 ms
手写排序耗时:1063.417196 ms
结果抽查 1

排序个数:700000
内置排序耗时:209.767103 ms
手写排序耗时:1333.762884 ms
结果抽查 1

排序个数:800000
内置排序耗时:241.870165 ms
手写排序耗时:1633.098841 ms
结果抽查 1

排序个数:900000
内置排序耗时:272.398949 ms
手写排序耗时:1975.782871 ms
结果抽查 1

排序个数:1000000
内置排序耗时:299.492121 ms
手写排序耗时:2303.519964 ms
结果抽查 1

可以看出虽然我们采用了传引用的方法,在大量的数据排序的时候,自己写的还是没有内置的厉害。基本上内置的算法只需要自己写的1/5的时间。
那么是否别的语言也是这样的呢?我们来看看golang吧。

排序个数: 100000
内置排序耗时: 16.072056ms
手写排序耗时: 6.95578ms
结果抽查: true

排序个数: 200000
内置排序耗时: 31.831613ms
手写排序耗时: 14.822814ms
结果抽查: true

排序个数: 300000
内置排序耗时: 46.872946ms
手写排序耗时: 24.190585ms
结果抽查: true

排序个数: 400000
内置排序耗时: 66.008501ms
手写排序耗时: 33.844555ms
结果抽查: true

排序个数: 500000
内置排序耗时: 77.824994ms
手写排序耗时: 43.05986ms
结果抽查: true

排序个数: 600000
内置排序耗时: 106.42095ms
手写排序耗时: 53.728681ms
结果抽查: true

排序个数: 700000
内置排序耗时: 117.377987ms
手写排序耗时: 65.855924ms
结果抽查: true

排序个数: 800000
内置排序耗时: 125.352682ms
手写排序耗时: 80.507389ms
结果抽查: true

排序个数: 900000
内置排序耗时: 140.563745ms
手写排序耗时: 92.550294ms
结果抽查: true

排序个数: 1000000
内置排序耗时: 162.15016ms
手写排序耗时: 100.330999ms
结果抽查: true

可以看到golang手写的居然比内置的快。100万个int排序,golang内置需要162ms,手写的居然只要100ms,php内置需要299ms,而手写居然需要2300多ms。
看来php的执行效率确实低,不过惊讶的是php内置函数排序居然只比golang慢一倍。

电脑配置

CPU:2.8 GHz 双核Intel Core i5 
MEM:8 GB 1600 MHz DDR3
PHP version:7.4.2
Golang version :go1.13.7

最后贴上golang代码

package main

import (
    "fmt"
    "math/rand"
    "sort"
    "time"
)

func quickSortCustom(nums *[]int, start int, end int) {
    // 起点和终点重合的时候,退出
    if start >= end {
        return
    }
    i, j := start, end
    //左节点的坑挖出来,备用
    mid := (*nums)[start]
    for {
        // 左节点位移到跟右节点重合时退出for循环
        if i >= j {
            break
        }

        // 右节点开始向左查找,直到比基准值小
        for {

            if j > i && (*nums)[j] >= mid {
                j--
            } else {
                break
            }
        }
        // 把右节点的坑,填入左节点。现在右节点j空出来了
        if i < j {
            (*nums)[i] = (*nums)[j]
            i++
        }

        // 左节点开始向右寻找,直到找到比基准值大的
        for {
            if j > i && (*nums)[i] < mid {
                i++
            } else {
                break
            }
        }
        //把左节点的值填入右节点的坑,(上面的右节点j是空的没有变过)。此时左节点i空出来了
        if i < j {
            (*nums)[j] = (*nums)[i]
            j--
        }

    }
    // 把最开始挖出来的坑放到新坑里面
    (*nums)[i] = mid
    quickSortCustom(nums, start, i-1)
    quickSortCustom(nums, i+1, end)
}

type IntSlice []int

func (s IntSlice) Less(i, j int) bool { return s[i] < s[j] }
func (s IntSlice) Len() int           { return len(s) }
func (s IntSlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

func main() {
    startTime := time.Now()
    for i := 100000; i <= 1000000; i += 100000 {
        numsIn := make(IntSlice, 0)
        numsMy := make([]int, 0)
        for n := 0; n < i; n++ {
            //rand.Seed(int64(n))
            x := rand.Intn(10000)
            numsIn = append(numsIn, x)
            numsMy = append(numsMy, x)

        }

        l := len(numsMy)
        //fmt.Println("原始:", numsMy, "排序个数:", l)
        fmt.Println("排序个数:", l)

        // ---------------- 内置排序 开始----------------
        startTime = time.Now()
        sort.Ints(numsIn)
        cost := time.Since(startTime)
        fmt.Println("内置排序耗时:", cost)
        // ---------------- 内置排序 结束----------------

        // ---------------- 手写排序 开始----------------
        startTime = time.Now()
        quickSortCustom(&numsMy, 0, l-1)
        cost = time.Since(startTime)
        fmt.Println("手写排序耗时:", cost)
        // ---------------- 手写排序 结束----------------

        fmt.Println("结果抽查:", numsMy[l/2] == numsIn[l/2])

        //fmt.Println("内置:", numsIn)
        //fmt.Println("手写:", numsMy)

        fmt.Println(" ")

    }

    //nums := []int{8081, 7887, 1847, 4059, 2081, 1318, 4425, 2540, 456, 3300}
    //fmt.Println("原始的数据:", nums)
    //quickSortCustom(&nums, 0, len(nums)-1)
    //fmt.Println("自己排序后:", nums)
    //
    //nums2 := IntSlice{8081, 7887, 1847, 4059, 2081, 1318, 4425, 2540, 456, 3300}
    ////fmt.Println("排序前:",nums2)
    //sort.Stable(nums2)
    //fmt.Println("内置排序后:", nums2)
}

2019-12-26 作者:菜鸟DJ 0
工具分享

MAC & WIN 平台效率工具清单

1. 数据库GUI工具

1.1 Sequal Pro

官方的release版本已经在2016年停更了,但是master分支仍然有人在更新。我认为这是OSX平台下最轻量,使用最舒服的DB GUI工具,没有之一。
功能特色:交互简洁,免费开源,支持查询结果集增删改。
下载:这里存放的是我编译的版本

1.2 DBeaver

DBeaver 是对目前市面流行的DB支持最全面的工具,而且社区版足够用。唯一的缺点是使用java编写的,有时候会卡。

2. Redis GUI工具

2.1 Medis

Medis是一款基于electron构建的Redis 管理工具,非常的好用。在大数量key的情况下吊打RedisDesktopManager。
官方不再提供OSX平台的安装包,在MAS上有提供下载,但是版本太老。同样下面分享我的编译版本。
下载:这里存放的是我编译的版本 提取码: pan6

3. API调试

3.1 PostMan

功能很齐全的API调试工具,界面也很优化。支持配置云同步。结果json格式化。
下载请到官网

4. 绘图工具

4.1 OpenBoard —— 一款打草稿的工具

全平台的开源软件,结合绘图板,完全可以抛弃草稿本,而且带录屏功能,可以用来制作课件视频。

5. 终端工具

5.1 Mac平台

iterm2 + ohmyzsh

5.2 Win平台

XShell

XShell 有两个功能我非常喜欢。
– 对终端显示文本可以自定义高亮,支持正则表达式,并且无需更改主机的配色。
– 支持ssh通道的代理,通过代理连接梯子服务器简直太舒服了。iterm2怎么配置摸索好久还是没找到解决方案。
附上我的正则高亮配置

用途 表达式
时间 \d{4}[\-\/]\d{2}[\-\/]\d{2}\ \d{2}\:\d{2}\:\d{2}
API (?<=\[)\/(\S)*(?=(\]\[))
IPv4 ((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}

6. 开发环境搭建工具

6.1 oneinstack

php,db,nosql db,nginx,还有各种组件。懒人开发必备。

2019-09-05 作者:菜鸟DJ 0
MySQL

你真的会用MySQL里的max函数吗?

在MySQL我们经常会用到count,max,min这些函数,但是在使用这些函数的时候你确定你写的sql一定正确吗?话不多说,我们来个需求。

假设现在有一张消费表如下:

CREATE TABLE `tb_user_consume` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(11) unsigned DEFAULT NULL,
  `goods_id` int(11) unsigned DEFAULT NULL,
  `goods_name` varchar(50) CHARACTER SET utf8mb4 DEFAULT NULL,
  `price` decimal(10,2) unsigned DEFAULT NULL,
  `num` int(10) unsigned DEFAULT NULL,
  `total` decimal(10,2) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `uq_user_good` (`user_id`,`goods_id`),
  KEY `ind_total` (`total`),
  KEY `idx_user` (`user_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

现在我们需要取出每个用户购买最多的商品名。看似一个很简单的需求,基本上都能在脑海里想出来下面的SQL。

SELECT max(num),user_id,goods_name FROM tb_user_consume GROUP BY user_id LIMIT 10;

从语句上面看,好像没有什么问题都满足需求,我们执行下来验证下。
返回的结果如下:

max(num)    user_id     goods_name
982         1000000     1号商品
898         1000001     1号商品
982         1000002     1号商品
883         1000003     1号商品
706         1000004     1号商品
940         1000005     1号商品
761         1000006     1号商品
958         1000007     1号商品
739         1000008     1号商品
967         1000009     1号商品

感觉有点不对劲,难道1号商品这么畅销吗?大家都是买1号商品,我们来看下原始数据。

id  user_id goods_id    goods_name  price   num total
1   1000000 1   1号商品    1.30    982 1276.60
2   1000000 2   2号商品    1.15    212 243.80
3   1000000 3   3号商品    3.40    356 1210.40
4   1000000 4   4号商品    18.99   974 18496.26
5   1000000 5   5号商品    7.40    760 5624.00
6   1000001 1   1号商品    1.30    25  32.50
7   1000001 2   2号商品    1.15    898 1032.70
8   1000001 3   3号商品    3.40    743 2526.20
9   1000001 4   4号商品    18.99   311 5905.89
10  1000001 5   5号商品    7.40    732 5416.80
11  1000002 1   1号商品    1.30    982 1276.60
12  1000002 2   2号商品    1.15    674 775.10
13  1000002 3   3号商品    3.40    378 1285.20
14  1000002 4   4号商品    18.99   285 5412.15
15  1000002 5   5号商品    7.40    519 3840.60

我们很清楚的看出来了,1000001 购买的商品是2号商品居多,结合上面的查询结果来看,max(num)似乎没有问题,那么出问题似乎的是在goods_name的显示上。
查下网上的教程查询的结果集都只会包含group的那一列,和max的结果值。
那么我们还得自己来想解决办法,做一个子查询先按照user_id分组取出最大的num值,创建临时表和源表用user_id和num查询,于是就有了下面的SQL。

SELECT a.user_id,a.`goods_name`,a.num FROM tb_user_consume a JOIN (SELECT user_id , max(num) AS num FROM tb_user_consume  where user_id &lt; 1000010 GROUP BY user_id) b ON a.user_id = b.user_id AND a.num = b.num;

查询结果为

user_id goods_name  num
1000000 1号商品    982
1000001 2号商品    898
1000002 1号商品    982
1000003 3号商品    883
1000004 5号商品    706
1000005 1号商品    940
1000006 3号商品    761
1000007 3号商品    958
1000008 4号商品    739
1000009 5号商品    967

对比数据发现查询结果准确无误。所以千万不能偷懒使用简单的使用max查询其他列。

2019-08-23 作者:菜鸟DJ 0
PHP

MySQL 分组之后如何取Top(N)?

最近碰到一个有意思的问题,因为MySQL里没有top n的用法,所以如果要实现取数据的前几操作只能通过排序之后加limit限制数量,但是这种用法又跟group 冲突。这篇文章就是来分析下分组取topN的解题思路。

现在创建一个测试表。用户的商品消费数据(测试表就不建立索引了)

CREATE TABLE `tb_user_consume` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(11) unsigned DEFAULT NULL,
  `goods_id` int(11) unsigned DEFAULT NULL,
  `goods_name` varchar(50) CHARACTER SET utf8mb4 DEFAULT NULL,
  `price` decimal(10,2) unsigned DEFAULT NULL,
  `num` int(10) unsigned DEFAULT NULL,
  `total` decimal(10,2) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `tb_user_consume`(`user_id`,`goods_id`,`goods_name`,`price`,`num`,`total`)
VALUES
(100,1,"1号商品",1.3,10, price*num),
(100,2,"2号商品",1.15,12, price*num),
(100,3,"3号商品",3.4,5, price*num),
(100,4,"4号商品",18.99,2, price*num),
(100,5,"5号商品",7.4,9, price*num),'
(101,1,"1号商品",1.3,13, price*num),
(101,2,"2号商品",1.15,12, price*num),
(101,3,"3号商品",3.4,20, price*num),
(101,4,"4号商品",18.99,8, price*num),
(101,5,"5号商品",7.4,7, price*num),'
(102,1,"1号商品",1.3,21, price*num),
(102,2,"2号商品",1.15,3, price*num),
(102,3,"3号商品",3.4,51, price*num),
(102,4,"4号商品",18.99,23, price*num),
(102,5,"5号商品",7.4,22, price*num),'
(103,1,"1号商品",1.3,2, price*num),
(103,2,"2号商品",1.15,7, price*num),
(103,3,"3号商品",3.4,9, price*num),
(103,4,"4号商品",18.99,22, price*num),
(103,5,"5号商品",7.4,99, price*num),'
(104,1,"1号商品",1.3,77, price*num),
(104,2,"2号商品",1.15,54, price*num),
(104,3,"3号商品",3.4,23, price*num),
(104,4,"4号商品",18.99,23, price*num),
(104,5,"5号商品",7.4,44, price*num)
;

假如现在有一个需求是,筛选出用户消费商品总价最高的前三个商品。

粗一看,这个需求也没有什么实现上的难度,就是根据用户分组,取出表里total最高的三行记录就可以了。
对没有错,需求就是这么简单,解题思路也不难,那么我们开始着手编码了。

第一步,做一个子查询,

取出表里total最高的三行记录

sql写起来也很简单,如下所示

SELECT * FROM `tb_user_consume` WHERE user_id = 100 ORDER BY total DESC LIMIT 3;

第二步,按照用户分组
取出所有用户

SELECT * FROM `tb_user_consume` ORDER BY total DESC LIMIT 3 GROUP BY user_id;

看这个好像是满足了需求,别急,我们运行一下。

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘GROUP BY user_id’ at line 1

报错了,很明显上面的sql有语法错误。limit 只能用在查询语句的最后面。
那么我们要怎么去实现这个需求呢?用单一的子查询好像都没法直接的按照用户分组来取数据。
一般到这种时候,我们很可能就直接用代码来解决了。

先取出所有用户列表。 SELECT DISTINCT(user_id) AS uid FROM tb_user_consume;

然后遍历用户列表,按照上面的查询语句查出所有的用户前三total信息 SELECT * FROM tb_user_consume WHERE user_id = 100 ORDER BY total DESC LIMIT 3;

这种方法不是不可取,在表里的数据不多的时候,用这个也能完成需求,抛去执行效率不说,我们就说开发效率,又是写代码,又是写sql。还要去联调,是不是很费时费力?
那么到底能不能通过sql语句直接查询出来呢?

首先我们要想,上面不能实现的痛点在哪里?没有办法先limit 3,对不对?那我们能不能通过排序筛选的方式来实现,排序后达到三个的数量我们就停止。
按照机器的思维应该是,先order by user_id, 然后 order by total desc。

SELECT * FROM tb_user_consume ORDER BY user_id ,total DESC;

在这个结果集里当user_id 输出3个记录行就停止。 本文的重点来了 怎么实现这个呢?
通过谷歌(其实是百度)发现mysql里有一个 case when的条件判断。正好满足我们的需求。【 在这个结果集里当user_id 输出3个记录行就停止 】 美哉!开撸。

SELECT @rnd :=
    CASE
    WHEN @userid = `user_id` THEN
    @rnd := @rnd+1
    ELSE 1
    END rnd, @userid := `user_id`, user_id,total,goods_id,goods_name,price
FROM `tb_user_consume`,
    (SELECT @rnd := 1,
         @userid:=0) b
    ORDER BY  `user_id` ,`total` DESC

像这样我们就可以以rnd变量来标记我们的结果集排序结果了。这样我们把它作为一个子查询在外面加上限制条件就拿到指定的行数。
最终的sql如下:

SELECT *
FROM 
    (SELECT @rnd :=
        CASE
        WHEN @userid = `user_id` THEN
        @rnd := @rnd+1
        ELSE 1
        END rnd, @userid := `user_id`, user_id,total,goods_id,goods_name,price
    FROM `tb_user_consume`,
        (SELECT @rnd := 1,
         @userid:=0) b
        ORDER BY  `user_id` ,`total` DESC) aa
    WHERE rnd <=3;

最终的查询展示结果:

+------+----------------------+---------+--------+----------+------------+-------+
| rnd  | @userid := `user_id` | user_id | total  | goods_id | goods_name | price |
+------+----------------------+---------+--------+----------+------------+-------+
|    1 |                  100 |     100 |  66.60 |        5 | 5号商品    |  7.40 |
|    2 |                  100 |     100 |  37.98 |        4 | 4号商品    | 18.99 |
|    3 |                  100 |     100 |  17.00 |        3 | 3号商品    |  3.40 |
|    1 |                  101 |     101 | 151.92 |        4 | 4号商品    | 18.99 |
|    2 |                  101 |     101 |  68.00 |        3 | 3号商品    |  3.40 |
|    3 |                  101 |     101 |  51.80 |        5 | 5号商品    |  7.40 |
|    1 |                  102 |     102 | 436.77 |        4 | 4号商品    | 18.99 |
|    2 |                  102 |     102 | 173.40 |        3 | 3号商品    |  3.40 |
|    3 |                  102 |     102 | 162.80 |        5 | 5号商品    |  7.40 |
|    1 |                  103 |     103 | 732.60 |        5 | 5号商品    |  7.40 |
|    2 |                  103 |     103 | 417.78 |        4 | 4号商品    | 18.99 |
|    3 |                  103 |     103 |  30.60 |        3 | 3号商品    |  3.40 |
|    1 |                  104 |     104 | 436.77 |        4 | 4号商品    | 18.99 |
|    2 |                  104 |     104 | 325.60 |        5 | 5号商品    |  7.40 |
|    3 |                  104 |     104 | 100.10 |        1 | 1号商品    |  1.30 |
+------+----------------------+---------+--------+----------+------------+-------+

参考文章: 我的mysql如何分组取top10?

<?php
$host = '127.0.0.1';
$dbname = 'yang';
$port = 3306;

$db = new PDO("mysql:host=$host;dbname=$dbname;port=$port", 'root', '12345');

$goods_info = [
    ['id' => 1, 'name' => '1号商品', 'price' => 1.30],
    ['id' => 2, 'name' => '2号商品', 'price' => 1.15],
    ['id' => 3, 'name' => '3号商品', 'price' => 3.40],
    ['id' => 4, 'name' => '4号商品', 'price' => 18.99],
    ['id' => 5, 'name' => '5号商品', 'price' => 7.40],
];

function insert($goods_info, PDO &$db, $start)
{
    $sql = 'insert into tb_user_consume(user_id,goods_id,goods_name,price,num,total) values';
    for ($i = $start; $i < $start + 50000; $i++) {
        foreach ($goods_info as $info) {
            $num = mt_rand(0, 1000);
            $total = $num * $info['price'];
            $sql .= sprintf("(%d,%d,\"%s\",%f,%d,%f),", $i, $info['id'], $info['name'], $info['price'], $num, $total);
        }
    }
    $sql = substr($sql, 0, -1);
    //echo $sql;

    $db->prepare($sql)->execute();
}

// 批量添加测试数据
//for ($j = 1000000; $j < 2000000; $j += 50000) {
//    insert($goods_info,$db,$j);
//}

// 执行时间
$start = time();
select($db);
echo "cost:".(time()-$start)."\n";

function select(PDO &$db){
    for ($i = 1000000; $i < 2000000; $i++) {
        $sql = 'SELECT * FROM `tb_user_consume` WHERE user_id = '.$i.' ORDER BY total DESC LIMIT 3; ';
        $ret = $db->query($sql)->fetchAll();
        //var_dump($ret);
    }
}

MySQL php

文章分页

1 2 下一页
10/13

天气

分类目录

热门文章

记一次和流氓软件战斗的过程0 comments
手机app https抓包步骤一揽0 comments
记一下RabblitMQ的安装和RPC的工作模式0 comments
聊一聊快排算法0 comments
MAC & WIN 平台效率工具清单0 comments
你真的会用MySQL里的max函数吗?0 comments
MySQL 分组之后如何取Top(N)?0 comments
B站代码解读 — LRUCache0 comments
解搜索二维矩阵题0 comments
解一道字符串变化题0 comments

微信公众号:菜鸟公园

微信公众号
微信公众号:菜鸟公园
隐私政策