经验总结
为了隐藏真正的Mysql数据库,我想通过中间跳板机nginx服务做转发进行数据库连接,要怎么做?
回复数据库 • zkbhj 发起了问题 • 1 人关注 • 0 个回复 • 1230 次浏览 • 2023-07-10 11:23
如何解决Navicat链接数据库出先Access denied的问题?
回复数据库 • zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1450 次浏览 • 2023-05-18 11:41
PHP8.1的项目,用curl访问链接总是返回35号错误,怎么解决?
回复PHP • zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1970 次浏览 • 2023-03-02 17:00
Nginx+PHP-FPM架构,结合PHP8.1搭建应用服务器时的一些总结和记录
PHP • zkbhj 发表了文章 • 0 个评论 • 2331 次浏览 • 2022-12-08 14:07
一、编译安装PHP8.1:
因为编译过程中基本上遇到问题的概率并不大,我的编译过程还算顺利,因为服务器上保留了以前其他人运行的项目。PHP的版本是7.2.3,所以就编译安装了PHP8.1到一个指定的地方,然后独立安装的PHP-FPM。尽量不影响以前的项目运行。
我的编译配置信息(注意要一行,不能存在换行符):./configure --prefix=/usr/local/php8.1 --with-config-file-path=/usr/local/php8.1/etc --with-curl --with-freetype --enable-gd --with-jpeg --with-gettext --with-kerberos --with-libdir=lib64 --with-libxml --with-mysqli --with-openssl --with-pdo-mysql --with-pdo-sqlite --with-pear --enable-sockets --with-mhash --with-ldap-sasl --with-xsl --with-zlib -with-bz2 --with-iconv --enable-fpm --enable-pdo --enable-bcmath --enable-mbregex --enable-mbstring --enable-opcache --enable-pcntl --enable-soap --enable-sockets --enable-sysvsem --enable-xml --enable-sysvsem --enable-cli --enable-opcache --enable-intl --enable-calendar --enable-static --enable-mysqlnd最终我把编译好的php-fpm文件复制到了我的PHP8.1目录下,并完成了配置启动。这会儿还一切看似顺利。
编译过程中遇到了一个问题是:
configure: error: iconv does not support errno
优先参考网上的解决方案安装缺失的iconv,如果不行,在运行configure之前先运行:
export LDFLAGS="$LDFLAGS -liconv"参考官网bug讨论 https://bugs.php.net/bug.php?id=80585,在此对歪果友人诚挚的感谢。
二、部署应用:
搭建好之后,顺利启动PHP-FPM服务器,监听端口9000,然后再Nginx配置文件中,将请求信息直接给到9000端口来处理,这里就有一些“东西”要注意了,否则就遇到了下面的问题:
问题1:部署应用之后,请求失败,页面返回“File not Found”,然后查看Nginx的报错日志,显示信息是:2022/12/08 13:15:51 [error] 31547#0: *29582 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream, client: 119.116.237.75, server: web.*.com, request: "GET /site/user-agreement HTTP/2.0", upstream: "fastcgi://127.0.0.1:9000", host: "web*.*com"网上说中问题, 比如php-fpm的 运行group和user不对啦,应用目录权限不足啦,但我发现根本原因是在Nginx转发时,少了一个重要的配置信息。
由于我的yii2项目启用了 URLManage功能,为了让链接看起来更漂亮一些,所以需要在Nginx配置文件中加入下面的这个配置:if (!-e $request_filename){
rewrite ^/(.*) /index.php last;
}
所以,完整的转发配置应该是:location / {
fastcgi_pass 127.0.0.1:9000;
if (!-e $request_filename){
rewrite ^/(.*) /index.php last;
}
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}所以这块,大家应该注意一下,更改完,重新启动Nginx服务之后,项目正常启动了!
问题二、页面图标显示异常:
由于使用了第三方图标工具,打开F12工具之后,发现是静态资源服务器访问字体文件时显示跨域:Access to Font at “xxx” from origin “xxx” has been by CORS plicy:
No ‘Access-Control-Allow-Origin’header is present on the requested resource.
Origin “xxx” is therefore not allowed access.这时候,只需要在静态资源服务器的Nginx配置文件中增加下面的配置项即可:location ~* .(eot|ttf|woff|swoff2|svg|otf)$ {
add_header Access-Control-Allow-Origin [url]http://www.zkbhj.com;[/url] //只允许单域名或者 ‘*' 。不推荐 ‘*' ,会导致安全问题
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
} 查看全部
一、编译安装PHP8.1:
因为编译过程中基本上遇到问题的概率并不大,我的编译过程还算顺利,因为服务器上保留了以前其他人运行的项目。PHP的版本是7.2.3,所以就编译安装了PHP8.1到一个指定的地方,然后独立安装的PHP-FPM。尽量不影响以前的项目运行。
我的编译配置信息(注意要一行,不能存在换行符):
./configure --prefix=/usr/local/php8.1 --with-config-file-path=/usr/local/php8.1/etc --with-curl --with-freetype --enable-gd --with-jpeg --with-gettext --with-kerberos --with-libdir=lib64 --with-libxml --with-mysqli --with-openssl --with-pdo-mysql --with-pdo-sqlite --with-pear --enable-sockets --with-mhash --with-ldap-sasl --with-xsl --with-zlib -with-bz2 --with-iconv --enable-fpm --enable-pdo --enable-bcmath --enable-mbregex --enable-mbstring --enable-opcache --enable-pcntl --enable-soap --enable-sockets --enable-sysvsem --enable-xml --enable-sysvsem --enable-cli --enable-opcache --enable-intl --enable-calendar --enable-static --enable-mysqlnd最终我把编译好的php-fpm文件复制到了我的PHP8.1目录下,并完成了配置启动。这会儿还一切看似顺利。
编译过程中遇到了一个问题是:
configure: error: iconv does not support errno
优先参考网上的解决方案安装缺失的iconv,如果不行,在运行configure之前先运行:
export LDFLAGS="$LDFLAGS -liconv"参考官网bug讨论 https://bugs.php.net/bug.php?id=80585,在此对歪果友人诚挚的感谢。
二、部署应用:
搭建好之后,顺利启动PHP-FPM服务器,监听端口9000,然后再Nginx配置文件中,将请求信息直接给到9000端口来处理,这里就有一些“东西”要注意了,否则就遇到了下面的问题:
问题1:部署应用之后,请求失败,页面返回“File not Found”,然后查看Nginx的报错日志,显示信息是:
2022/12/08 13:15:51 [error] 31547#0: *29582 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream, client: 119.116.237.75, server: web.*.com, request: "GET /site/user-agreement HTTP/2.0", upstream: "fastcgi://127.0.0.1:9000", host: "web*.*com"
网上说中问题, 比如php-fpm的 运行group和user不对啦,应用目录权限不足啦,但我发现根本原因是在Nginx转发时,少了一个重要的配置信息。由于我的yii2项目启用了 URLManage功能,为了让链接看起来更漂亮一些,所以需要在Nginx配置文件中加入下面的这个配置:
if (!-e $request_filename){所以,完整的转发配置应该是:
rewrite ^/(.*) /index.php last;
}
location / {所以这块,大家应该注意一下,更改完,重新启动Nginx服务之后,项目正常启动了!
fastcgi_pass 127.0.0.1:9000;
if (!-e $request_filename){
rewrite ^/(.*) /index.php last;
}
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
问题二、页面图标显示异常:
由于使用了第三方图标工具,打开F12工具之后,发现是静态资源服务器访问字体文件时显示跨域:
Access to Font at “xxx” from origin “xxx” has been by CORS plicy:这时候,只需要在静态资源服务器的Nginx配置文件中增加下面的配置项即可:
No ‘Access-Control-Allow-Origin’header is present on the requested resource.
Origin “xxx” is therefore not allowed access.
location ~* .(eot|ttf|woff|swoff2|svg|otf)$ {
add_header Access-Control-Allow-Origin [url]http://www.zkbhj.com;[/url] //只允许单域名或者 ‘*' 。不推荐 ‘*' ,会导致安全问题
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
}
如何通过Composer只安装指定的包,不更新各种依赖的组件包?
回复工具软件 • zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 2510 次浏览 • 2023-07-09 14:32
Snowflake算法核... 显示全部 »
Snowflake算法核心
把时间戳,工作机器id,序列号组合在一起。
133
除了最高位bit标记为不可用以外,其余三组bit占位均可浮动,看具体的业务需求而定。默认情况下41bit的时间戳可以支持该算法使用到2082年,10bit的工作机器id可以支持1023台机器,序列号支持1毫秒产生4095个自增序列id。下文会具体分析。
结构为:
0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---0000000000 00
在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
/**
* ID 生成策略
* 毫秒级时间41位+机器ID 10位+毫秒内序列12位。
* 0 41 51 64
+-----------+------+------+
|time |pc |inc |
+-----------+------+------+
* 最高位bit标记为不可用
* 前41bits是以微秒为单位的timestamp。
* 接着10bits是事先配置好的机器ID。
* 最后12bits是累加计数器。
* macheine id(10bits)标明最多只能有1024台机器同时产生ID,sequence number(12bits)也标明1台机器1ms中最多产生4096个ID,
*
*/
class SnowFlake{
private static $epoch = 1462264156000;
public function createID($machineId){
/*
* Time - 41 bits
*/
$time = floor(microtime(true) * 1000);
/*
* Substract custom epoch from current time
*/
$time -= SnowFlake::$epoch;
/*
* flag number - 1 bits - can not change, beacause is shoule be a positive number
*/
$suffix = 0;
/*
* Create a base and add time to it
*/
$base = decbin(pow(2,40) - 1 + $time);
//$base = sprintf("1s", decbin($time));
/*
* Configured machine id - 10 bits - up to 512 machines
*/
$machineid = decbin(pow(2,9) - 1 + $machineId);
//$machineid = sprintf("0s", decbin($machineId));
/*
* sequence number - 12 bits - up to 2048 random numbers per machine
*/
$random = mt_rand(1, pow(2,11)-1);
$random = decbin(pow(2,11)-1 + $random);
//$random = mt_rand(1, pow(2, 12) - 1);
//$random = sprintf("2s", decbin($random));
/*
* 拼装$base
*/
$base = $suffix.$base.$machineid.$random;
/*
* 讲二进制的base转换成long
*/
$base = bindec($base);
$id = sprintf('%.0f', $base);
return $id;
}
}
我这里的PHP代码序列号是随机产生的,因为我们的业务还不达不到需要有序生成的必要, 而且对于PHP来说要序列化生成必须把这个序列存储到缓存里面去。
GIT源码:https://github.com/search?l=PHP&q=snowflake&type=Repositories&utf8=%E2%9C%93
由此可以看出来吧。它就是表示一个来源。看下图的一个请求的 Referer 信息。
Referer 的正确英语拼法是referrer 。由于早期HTTP规范的拼写错误,为了保持向后兼容就将错就错了。其它网络技术的规范企图修正此问题,使用正确拼法,所以目前拼法不统一。还有它第一个字母是大写。
Referer的作用?
1.防盗链。
刚刚前面有提到一个小 Demo 。
我在www.google.com里有一个www.baidu.com链接,那么点击这个www.baidu.com,它的header信息里就有: Referer=http://www.google.com
那么可以利用这个来防止盗链了,比如我只允许我自己的网站访问我自己的图片服务器,那我的域名是www.google.com,那么图片服务器每次取到Referer来判断一下是不是我自己的域名www.google.com,如果是就继续访问,不是就拦截。
这是不是就达到防盗链的效果了?
将这个http请求发给服务器后,如果服务器要求必须是某个地址或者某几个地址才能访问,而你发送的referer不符合他的要求,就会拦截或者跳转到他要求的地址,然后再通过这个地址进行访问。
2.防止恶意请求。
比如静态请求是*.html结尾的,动态请求是*.shtml,那么由此可以这么用,所有的*.shtml请求,必须 Referer 为我自己的网站。
Referer= http://www.google.com
空Referer是怎么回事?什么情况下会出现Referer?
首先,我们对空 Referer 的定义为, Referer 头部的内容为空,或者,一个 HTTP 请求中根本不包含 Referer 头部。
那么什么时候 HTTP 请求会不包含 Referer 字段呢?根据Referer的定义,它的作用是指示一个请求是从哪里链接过来,那么当一个请求并不是由链接触发产生的,那么自然也就不需要指定这个请求的链接来源。
比如,直接在浏览器的地址栏中输入一个资源的URL地址,那么这种请求是不会包含 Referer 字段的,因为这是一个“凭空产生”的 HTTP 请求,并不是从一个地方链接过去的。
那么在防盗链设置中,允许空Referer和不允许空Referer有什么区别?
允许 Referer 为空,意味着你允许比如浏览器直接访问,就是空。
X-Requested-With
X-Requested-With请求头用于在服务器端判断request来自Ajax请求还是传统请求。
如果 requestedWith 为 null,则为同步请求。如果 requestedWith 为 XMLHttpRequest 则为 Ajax 请求。 if (request.getHeader("x-requested-with") != null
&& request.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")) {
out.print("该请求是 AJAX 异步HTTP请求。");
}else{
out.print("该请求是传统的 同步HTTP请求。");
}
如何在发送请求是去掉它?
$.ajax({
url: 'http://www.zhangruifeng.com',
beforeSend: function( xhr ) {
xhr.setRequestHeader('X-Requested-With', {toString: function(){ return ''; }});
},
success: function( data ) {
if (console && console.log){
console.log( 'Got data without the X-Requested-With header' );
}
}
});
PHP用wkhtmltopdf及扩展实现网页生成图片或PDF
PHP • zkbhj 发表了文章 • 0 个评论 • 7322 次浏览 • 2017-11-21 10:55
本教程的主角是wkhtmltopdf,下面就先简单了解下它:
wkhtmltopdf 是一个开源、简单而有效的命令行 shell 程序,它可以将任何 HTML (网页)转换为 PDF 文档或图像(jpg、png 等)。
wkhtmltopdf 是用 C++ 编写的,并在 GNU/GPL (通用公共许可证)下发布。它使用 WebKit 渲染引擎将 HTML 页面转换为 PDF 文档且不会丢失页面的质量。这是一个用于实时创建和存储网页快照的非常有用且可信赖的解决方案。
这个程序的功能特点有以下几点:
开源并且跨平台。使用 WebKit 引擎将任意 HTML 网页转换为 PDF 文件。添加页眉和页脚的选项目录生成 (TOC) 选项。提供批量模式转换。通过绑定 libwkhtmltox 来支持 PHP 或 Python。
首先,我们需要在我们的服务器上安装 libwkhtmltox,linux下的webkit内核,根据自己的服务器配置,选择合适的安装包:
需要说明的是,libwkhtmltox现在的版本已经是0.13了,但是本人在CentOS 6.2下安装了libwkhtmltox-0.11之后,在将网页转换成图片时会出错,最终选择了0.10版本,这个版本还是相当稳定的。
13版本安装方法:wget https://bitbucket.org/wkhtmlto ... 4.rpm然后执行:[root@KaiBoss_4_45 ~]# rpm -ivh wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64.rpm
如果安装过程中出现下列报错,可以通过下面方法解决,安装缺失的依赖即可![root@KaiBoss_4_45 ~]# rpm -ivh wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64.rpm
error: Failed dependencies:
icu is needed by wkhtmltox-1:0.13.0_alpha_7b36694-1.x86_64
xorg-x11-fonts-75dpi is needed by wkhtmltox-1:0.13.0_alpha_7b36694-1.x86_64
[root@KaiBoss_4_45 ~]# yum search icu
[root@KaiBoss_4_45 ~]# yum install icu.x86_64
[root@KaiBoss_4_45 ~]# yum search 75dpi
[root@KaiBoss_4_45 ~]# yum install xorg-x11-fonts-75dpi.noarch
[root@KaiBoss_4_45 ~]# rpm -ivh wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64.rpm
Preparing... ########################################### [100%]
1:wkhtmltox ########################################### [100%]
[root@KaiBoss_4_45 ~]#
看到如上结果,就代表安装成功了!
10稳定版本安装方式:[root@KaiBoss_4_45 ~]#mkdir libwkhtmtox
[root@KaiBoss_4_45 ~]#cd libwkhtmtox
[root@KaiBoss_4_45 libwkhtmtox]# tar jxvf libwkhtmltox-0.10.0_rc2-amd64.tar.bz2
[root@KaiBoss_4_45 libwkhtmtox]# cd lib
[root@KaiBoss_4_45 lib]# cp libwkhtmltox.so /usr/local/lib/
[root@KaiBoss_4_45 lib]# cd ../include/
[root@KaiBoss_4_45 include]# cp -R wkhtmltox /usr/local/include/
接下来,需要安装phpwkhtmltox, php扩展,可调用webkit内核将网页转换成各种格式图片或者pdf。
这里需要注意,如果是PHP7,需要安装PHP7版本对应的扩展包。[root@KaiBoss_4_45 ~]#unzip php7-wkhtmltox-master.zip
[root@KaiBoss_4_45 ~]#cd php7-wkhtmltox-master
[root@KaiBoss_4_45 ~]#phpize
[root@KaiBoss_4_45 ~]#./configure
[root@KaiBoss_4_45 ~]#make install
接下来,我们修改们的php.ini配置文件,将扩展文件加进来:extension=phpwkhtmltox.so 然后,重新加载系统动态链接库(重要,否则PHP无法完成扩展phpwkhtmltox),并检查是否添加成功:[root@platform tmp]#ldconfig
[root@platform tmp]#php -m然后,就可以写一个php脚本,来测试功能啦:
测试生成图片:<?php
wkhtmltox_convert(
'image',
array(
'out' => 'zk_pic.jpg',
'in' => 'http://www.baidu.com/'
)
);
?>
下面是百度网页的生成结果
测试生成pdf:<?php
wkhtmltox_convert('pdf',
array(
'out' => '/tmp/zk_pdf.pdf',
'imageQuality' => '95'
), // global settings
array(
array(
'page' => 'http://www.ziroom.com/'
),
array(
'page' => 'http://www.baidu.com/'
)
)// object settings
);
?>
如果发现生成的图片或pdf中,中文是乱码,则需要让CentOS支持中文:[root@KaiBoss_4_45 ~]#yum groupinstall chinese-support把字体文件拷贝到/usr/share/fonts/xxx,其中xxx为新增字体文件夹,如msyh[root@KaiBoss_4_45 ~]#cd /usr/share/fonts/
[root@KaiBoss_4_45 ~]#mkdir msyh
[root@KaiBoss_4_45 ~]#cd msyh建立字体缓存[root@KaiBoss_4_45 ~]#mkfontscale
[root@KaiBoss_4_45 ~]#mkfontdir
[root@KaiBoss_4_45 ~]#fc-cache -fv
PS:让Linux CentOS支持Consolas字体(技术类博客可能会发布一个示例代码,大部分wordpress技术博客都会安装SyntaxHighlighter插件,而该插件代码显示字体首选的是Consolas字体,所以为了html转换成图片时示例代码显示得好看,我们也需要让linux支持Consolas字体)
下载或者从本地windows拷贝Consolas(注意从windows系统里面拷贝出来应该是有4个文件),并上传到linux服务器。把字体文件拷贝到/usr/share/fonts/xxx,其中xxx为新增字体文件夹,如Consolas[root@KaiBoss_4_45 ~]#cd /usr/share/fonts/
[root@KaiBoss_4_45 ~]#mkdir Consolas
[root@KaiBoss_4_45 ~]#cd Consolas
建立字体缓存[root@KaiBoss_4_45 ~]#mkfontscale
[root@KaiBoss_4_45 ~]#mkfontdir
[root@KaiBoss_4_45 ~]#fc-cache -fv
大功告成!
wkhtmtox官网:https://wkhtmltopdf.org/
所有历史版本下载:https://github.com/wkhtmltopdf ... ME.md
wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64 下载地址(百度网盘):https://pan.baidu.com/s/1mi3P9g8
稳定版0.10版本libwkhtmltox-0.10.0_rc2-amd64.tar.bz2 下载地址(百度网盘):https://pan.baidu.com/s/1c2B74PU
(非PHP7)php-wkhtmltox-master 下载地址(百度网盘):https://pan.baidu.com/s/1jHHkFTO
(PHP7)php7-wkhtmltox-master 下载地址(百度网盘):https://pan.baidu.com/s/1i5J4Mhf 查看全部
本教程的主角是wkhtmltopdf,下面就先简单了解下它:
wkhtmltopdf 是一个开源、简单而有效的命令行 shell 程序,它可以将任何 HTML (网页)转换为 PDF 文档或图像(jpg、png 等)。
wkhtmltopdf 是用 C++ 编写的,并在 GNU/GPL (通用公共许可证)下发布。它使用 WebKit 渲染引擎将 HTML 页面转换为 PDF 文档且不会丢失页面的质量。这是一个用于实时创建和存储网页快照的非常有用且可信赖的解决方案。
这个程序的功能特点有以下几点:
- 开源并且跨平台。
- 使用 WebKit 引擎将任意 HTML 网页转换为 PDF 文件。
- 添加页眉和页脚的选项
- 目录生成 (TOC) 选项。
- 提供批量模式转换。
- 通过绑定 libwkhtmltox 来支持 PHP 或 Python。
首先,我们需要在我们的服务器上安装 libwkhtmltox,linux下的webkit内核,根据自己的服务器配置,选择合适的安装包:
需要说明的是,libwkhtmltox现在的版本已经是0.13了,但是本人在CentOS 6.2下安装了libwkhtmltox-0.11之后,在将网页转换成图片时会出错,最终选择了0.10版本,这个版本还是相当稳定的。
13版本安装方法:
wget https://bitbucket.org/wkhtmlto ... 4.rpm然后执行:
[root@KaiBoss_4_45 ~]# rpm -ivh wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64.rpm
如果安装过程中出现下列报错,可以通过下面方法解决,安装缺失的依赖即可!
[root@KaiBoss_4_45 ~]# rpm -ivh wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64.rpm
error: Failed dependencies:
icu is needed by wkhtmltox-1:0.13.0_alpha_7b36694-1.x86_64
xorg-x11-fonts-75dpi is needed by wkhtmltox-1:0.13.0_alpha_7b36694-1.x86_64
[root@KaiBoss_4_45 ~]# yum search icu
[root@KaiBoss_4_45 ~]# yum install icu.x86_64
[root@KaiBoss_4_45 ~]# yum search 75dpi
[root@KaiBoss_4_45 ~]# yum install xorg-x11-fonts-75dpi.noarch
[root@KaiBoss_4_45 ~]# rpm -ivh wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64.rpm看到如上结果,就代表安装成功了!
Preparing... ########################################### [100%]
1:wkhtmltox ########################################### [100%]
[root@KaiBoss_4_45 ~]#
10稳定版本安装方式:
[root@KaiBoss_4_45 ~]#mkdir libwkhtmtox
[root@KaiBoss_4_45 ~]#cd libwkhtmtox
[root@KaiBoss_4_45 libwkhtmtox]# tar jxvf libwkhtmltox-0.10.0_rc2-amd64.tar.bz2
[root@KaiBoss_4_45 libwkhtmtox]# cd lib
[root@KaiBoss_4_45 lib]# cp libwkhtmltox.so /usr/local/lib/
[root@KaiBoss_4_45 lib]# cd ../include/
[root@KaiBoss_4_45 include]# cp -R wkhtmltox /usr/local/include/
接下来,需要安装phpwkhtmltox, php扩展,可调用webkit内核将网页转换成各种格式图片或者pdf。
这里需要注意,如果是PHP7,需要安装PHP7版本对应的扩展包。
[root@KaiBoss_4_45 ~]#unzip php7-wkhtmltox-master.zip
[root@KaiBoss_4_45 ~]#cd php7-wkhtmltox-master
[root@KaiBoss_4_45 ~]#phpize
[root@KaiBoss_4_45 ~]#./configure
[root@KaiBoss_4_45 ~]#make install
接下来,我们修改们的php.ini配置文件,将扩展文件加进来:
extension=phpwkhtmltox.so然后,重新加载系统动态链接库(重要,否则PHP无法完成扩展phpwkhtmltox),并检查是否添加成功:
[root@platform tmp]#ldconfig然后,就可以写一个php脚本,来测试功能啦:
[root@platform tmp]#php -m
测试生成图片:
<?php
wkhtmltox_convert(
'image',
array(
'out' => 'zk_pic.jpg',
'in' => 'http://www.baidu.com/'
)
);
?>
下面是百度网页的生成结果
测试生成pdf:
<?php
wkhtmltox_convert('pdf',
array(
'out' => '/tmp/zk_pdf.pdf',
'imageQuality' => '95'
), // global settings
array(
array(
'page' => 'http://www.ziroom.com/'
),
array(
'page' => 'http://www.baidu.com/'
)
)// object settings
);
?>
如果发现生成的图片或pdf中,中文是乱码,则需要让CentOS支持中文:
[root@KaiBoss_4_45 ~]#yum groupinstall chinese-support
把字体文件拷贝到/usr/share/fonts/xxx,其中xxx为新增字体文件夹,如msyh[root@KaiBoss_4_45 ~]#cd /usr/share/fonts/建立字体缓存
[root@KaiBoss_4_45 ~]#mkdir msyh
[root@KaiBoss_4_45 ~]#cd msyh
[root@KaiBoss_4_45 ~]#mkfontscale
[root@KaiBoss_4_45 ~]#mkfontdir
[root@KaiBoss_4_45 ~]#fc-cache -fv
PS:让Linux CentOS支持Consolas字体(技术类博客可能会发布一个示例代码,大部分wordpress技术博客都会安装SyntaxHighlighter插件,而该插件代码显示字体首选的是Consolas字体,所以为了html转换成图片时示例代码显示得好看,我们也需要让linux支持Consolas字体)
下载或者从本地windows拷贝Consolas(注意从windows系统里面拷贝出来应该是有4个文件),并上传到linux服务器。把字体文件拷贝到/usr/share/fonts/xxx,其中xxx为新增字体文件夹,如Consolas
[root@KaiBoss_4_45 ~]#cd /usr/share/fonts/
[root@KaiBoss_4_45 ~]#mkdir Consolas
[root@KaiBoss_4_45 ~]#cd Consolas
建立字体缓存
[root@KaiBoss_4_45 ~]#mkfontscale
[root@KaiBoss_4_45 ~]#mkfontdir
[root@KaiBoss_4_45 ~]#fc-cache -fv
大功告成!
wkhtmtox官网:https://wkhtmltopdf.org/
所有历史版本下载:https://github.com/wkhtmltopdf ... ME.md
wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64 下载地址(百度网盘):https://pan.baidu.com/s/1mi3P9g8
稳定版0.10版本libwkhtmltox-0.10.0_rc2-amd64.tar.bz2 下载地址(百度网盘):https://pan.baidu.com/s/1c2B74PU
(非PHP7)php-wkhtmltox-master 下载地址(百度网盘):https://pan.baidu.com/s/1jHHkFTO
(PHP7)php7-wkhtmltox-master 下载地址(百度网盘):https://pan.baidu.com/s/1i5J4Mhf
vim常用命令总结
工具软件 • zkbhj 发表了文章 • 0 个评论 • 3208 次浏览 • 2017-02-16 15:36
vim 选择文本,删除,复制,粘贴
文本的选择,对于编辑器来说,是很基本的东西,也经常被用到,总结如下:
v 从光标当前位置开始,光标所经过的地方会被选中,再按一下v结束。
V 从光标当前行开始,光标经过的行都会被选中,再按一下V结束。
Ctrl + v 从光标当前位置开始,选中光标起点和终点所构成的矩形区域,再按一下Ctrl + v结束。
ggVG 选中全部的文本, 其中gg为跳到行首,V选中整行,G末尾
选中后就可以用编辑命令对其进行编辑,如
d 删除
y 复制 (默认是复制到"寄存器)
p 粘贴 (默认从"寄存器取出内容粘贴)
"+y 复制到系统剪贴板(也就是vim的+寄存器)
"+p 从系统剪贴板粘贴
vim命令总结
1.删除字符
要删除一个字符,只需要将光标移到该字符上按下"x"。
2.删除一行
删除一整行内容使用"dd"命令。删除后下面的行会移上来填补空缺。
3.删除换行符
在Vim中你可以把两行合并为一行,也就是说两行之间的换行符被删除了:命令是"J"。
4.撤销
如果你误删了过多的内容。显然你可以再输入一遍,但是命令"u" 更简便,它可以撤消上一次的操作。
5.重做
如果你撤消了多次,你还可以用CTRL-R(重做)来反转撤消的动作。换句话说,它是对撤消的撤消。撤消命令还有另一种形式,"U"命令,它一次撤消对一行的全部操作。第二次使用该命令则会撤消前一个"U"的操作。用"u"和CTRL-R你可以找回任何一个操作状态。
6.追加
"i"命令可以在当前光标之前插入文本。
"a"命令可以在当前光标之后插入文本。
"o"命令可以在当前行的下面另起一行,并使当前模式转为Insert模式。
"O"命令(注意是大写的字母O)将在当前行的上面另起一行。
7.使用命令计数
假设你要向上移动9行。这可以用"kkkkkkkkk"或"9k"来完成。事实上,很多命令都可以接受一个数字作为重复执行同一命令的次数。比如刚才的例子,要在行尾追加三个感叹号,当时用的命令是"a!!!"。另一个办法是用"3a!"命令。3说明该命令将被重复执行3次。同样,删除3个字符可以用"3x"。指定的数字要紧挨在它所要修饰的命令前面。
8.退出
要退出Vim,用命令"ZZ"。该命令保存当前文件并退出Vim。
9.放弃编辑
丢弃所有的修改并退出,用命令":q!"。用":e!"命令放弃所有修改并重新载入该文件的原始内容。
10.以Word为单位的移动
使用"w"命令可以将光标向前移动一个word的首字符上;比如"3w"将光标向前移动3个words。"b"命令则将光标向后移动到前一个word的首字符上。
"e"命令会将光标移动到下一个word的最后一个字符。命令"ge",它将光标移动到前一个word的最后一个字符上。、
11.移动到行首或行尾
"$"命令将光标移动到当前行行尾。如果你的键盘上有一个键,它的作用也一样。"^"命令将光标移动到当前行的第一个非空白字符上。"0"命令则总是把光标移动到当前行的第一个字符上。键也是如此。"$"命令还可接受一个计数,如"1$"会将光标移动到当前行行尾,"2$"则会移动到下一行的行尾,如此类推。"0"命令却不能接受类似这样的计数,命令"^"前加上一个计数也没有任何效果。
12.移动到指定字符上
命令"fx"在当前行上查找下一个字符x(向右方向),可以带一个命令计数"F"命令向左方向搜索。"tx"命令形同"fx"命令,只不过它不是把光标停留在被搜索字符上,而是在它之前的一个字符上。提示:"t"意为"To"。该命令的反方向版是"Tx"。这4个命令都可以用";"来重复。以","也是重复同样的命令,但是方向与原命令的方向相反。
13.以匹配一个括号为目的移动
用命令"%"跳转到与当前光标下的括号相匹配的那一个括号上去。如果当前光标在"("上,它就向前跳转到与它匹配的")"上,如果当前在")"上,它就向后自动跳转到匹配的"("上去.
14.移动到指定行
用"G"命令指定一个命令计数,这个命令就会把光标定位到由命令计数指定的行上。比如"33G"就会把光标置于第33行上。没有指定命令计数作为参数的话, "G"会把光标定位到最后一行上。"gg"命令是跳转到第一行的快捷的方法。
另一个移动到某行的方法是在命令"%"之前指定一个命令计数比如"50%"将会把光标定位在文件的中间. "90%"跳到接近文件尾的地方。
命令"H","M","L",分别将光标跳转到第一行,中间行,结尾行部分。
15.告诉你当前的位置
使用CTRL-G命令。"set number"在每行的前面显示一个行号。相反关闭行号用命令":set nonumber"。":set ruler"在Vim窗口的右下角显示当前光标位置。
16.滚屏
CTRL-U显示文本的窗口向上滚动了半屏。CTRL-D命令将窗口向下移动半屏。一次滚动一行可以使用CTRL-E(向上滚动)和CTRL-Y(向下滚动)。要向前滚动一整屏使用命令CTRL-F。另外CTRL-B是它的反向版。"zz"命令会把当前行置为屏幕正中央,"zt"命令会把当前行置于屏幕顶端,"zb"则把当前行置于屏幕底端.
17.简单搜索
"/string"命令可用于搜索一个字符串。要查找上次查找的字符串的下一个位置,使用"n"命令。如果你知道你要找的确切位置是目标字符串的第几次出现,还可以在"n"之前放置一个命令计数。"3n"会去查找目标字符串的第3次出现。
"?"命令与"/"的工作相同,只是搜索方向相反."N"命令会重复前一次查找,但是与最初用"/"或"?"指定的搜索方向相反。
如果查找内容忽略大小写,则用命令"set ignorecase", 返回精确匹配用命令"set noignorecase" 。
18.在文本中查找下一个word
把光标定位于这个word上然后按下""键。Vim将会取当前光标所在的word并将它作用目标字符串进行搜索。"#"命令是""的反向版。还可以在这两个命令前加一个命令计数:"3*"查找当前光标下的word的第三次出现。
19.查找整个word
如果你用"/the"来查找Vim也会匹配到"there"。要查找作为独立单词的"the"使用如下命令:"/the>"。">"是一个特殊的记法,它只匹配一个word的结束处。近似地,"\<"匹配到一个word的开始处。这样查找作为一个word的"the"就可以用:"/"。
20.高亮显示搜索结果
开启这一功能用":set hlsearch",关闭这一功能:":set nohlsearch"。如果只是想去掉当前的高亮显示,可以使用下面的命令:":nohlsearch"(可以简写为noh)。
21.匹配一行的开头与结尾
^ 字符匹配一行的开头。$字符匹配一行的末尾。
所以"/was$"只匹配位于一行末尾的单词was,所以"/^was"只匹配位于一行开始的单词was。
22.匹配任何的单字符
.这个字符可以匹配到任何字符。比如"c.m"可以匹配任何前一个字符是c,后一个字符是m的情况,不管中间的字符是什么。
23.匹配特殊字符
放一个反斜杠在特殊字符前面。如果你查找"ter。",用命令"/ter\。"
24.使用标记
当你用"G"命令从一个地方跳转到另一个地方时,Vim会记得你起跳的位置。这个位置在Vim中是一个标记。使用命令" "可以使你跳回到刚才的出发点。命令可以在两点之间来回跳转。CTRL-O命令是跳转到你更早些时间停置光标的位置(提示:O意为older). CTRL-I则是跳回到后来停置光标的更新的位置(提示:I在键盘上位于O前面)。
注:使用CTRL-I 与按下键一样。
25.具名标记
命令"ma"将当前光标下的位置名之为标记"a"。从a到z一共可以使用26个自定义的标记。要跳转到一个你定义过的标记,使用命令" marks "marks就是定义的标记的名字。命令" 'a "使你跳转到a所在行的行首,"a "会精确定位a所在的位置。命令:":marks"用来查看标记的列表。
命令delm!删除所有标记。
26.操作符命令和位移
"dw"命令可以删除一个word,"d4w"命令是删除4个word,依此类推。类似有"d2e"、"d$"。此类命令有一个固定的模式:操作符命令+位移命令。首先键入一个操作符命令。比如"d"是一个删除操作符。接下来是一个位移命。比如"w"。这样任何移动光标命令所及之处,都是命令的作用范围。
27.改变文本
操作符命令是"c",改变命令。它的行为与"d"命令类似,不过在命令执行后会进入Insert模式。比如"cw"改变一个word。或者,更准确地说,它删除一个word并让你置身于Insert模式。
"cc"命令可以改变整行。不过仍保持原来的缩进。
"c$"改变当前光标到行尾的内容。
快捷命令:x 代表dl(删除当前光标下的字符)
X 代表dh(删除当前光标左边的字符)
D 代表d$(删除到行尾的内容)
C 代表c$(修改到行尾的内容)
s 代表cl(修改一个字符)
S 代表cc(修改一整行)
命令"3dw"和"d3w"都是删除3个word。第一个命令"3dw"可以看作是删除一个word的操作执行3次;第二个命令"d3w"是一次删除3个word。这是其中不明显的差异。事实上你可以在两处都放上命令记数,比如,"3d2w"是删除两个word,重复执行3次,总共是6个word。
28.替换单个字符
"r"命令不是一个操作符命令。它等待你键入下一个字符用以替换当前光标下的那个字符。"r"命令前辍以一个命令记数是将多个字符都替换为即将输入的那个字符。要把一个字符替换为一个换行符使用"r"。它会删除一个字符并插入一个换行符。在此处使用命令记数只会删除指定个数的字符:"4r"将把4个字符替换为一个换行符。
29.重复改动
"."命令会重复上一次做出的改动。"."命令会重复你做出的所有修改,除了"u"命令CTRL-R和以冒号开头的命令。"."需要在Normal模式下执行,它重复的是命令,而不是被改动的内容,
30.Visual模式
按"v"可以进入Visual模式。移动光标以覆盖你想操纵的文本范围。同时被选中的文本会以高亮显示。最后键入操作符命令。
31.移动文本
以"d"或"x"这样的命令删除文本时,被删除的内容还是被保存了起来。你还可以用p命令把它取回来。"P"命令是把被去回的内容放在光标之前,"p"则是放在光标之后。对于以"dd"删除的整行内容,"P"会把它置于当前行的上一行。"p"则是至于当前行的后一行。也可以对命令"p"和"P"命令使用命令记数。它的效果是同样的内容被取回指定的次数。这样一来"dd"之后的"3p"就可以把被删除行的3 份副本放到当前位置。
命令"xp"将光标所在的字符与后一个字符交换。
32.复制文本(VIM编辑器内复制)
"y"操作符命令会把文本复制到一个寄存器3中。然后可以用"p"命令把它取回。因为"y"是一个操作符命令,所以你可以用"yw"来复制一个word. 同样可以使用命令记数。如下例中用"y2w"命令复制两个word,"yy"命令复制一整行,"Y"也是复制整行的内容,复制当前光标至行尾的命令是"y$"。
33.文本对象
"diw" 删除当前光标所在的word(不包括空白字符) "daw" 删除当前光标所在的word(包括空白字符)
34.快捷命令
x 删除当前光标下的字符("dl"的快捷命令)
X 删除当前光标之前的字符("dh"的快捷命令)
D 删除自当前光标至行尾的内容("d$"的快捷命令)
dw 删除自当前光标至下一个word的开头
db 删除自当前光标至前一个word的开始
diw 删除当前光标所在的word(不包括空白字符)
daw 删除当前光标所在的word(包括空白字符)
dG 删除当前行至文件尾的内容
dgg 删除当前行至文件头的内容
如果你用"c"命令代替"d"这些命令就都变成更改命令。使用"y"就是yank命令,如此类推。
35.编辑另一个文件
用命令":edit foo.txt",也可简写为":e foo.txt"。
36.文件列表
可以在启动Vim时就指定要编辑多个文件,用命令"vim one.c two.c three.c"。Vim将在启动后只显示第一个文件,完成该文件的编辑后,可以用令:":next"或":n"要保存工作成果并继续下一个文件的编辑,命令:":wnext"或":wn"可以合并这一过程。
37.显示当前正在编辑的文件
用命令":args"。
38.移动到另一个文件
用命令":previous" ":prev"回到上一个文件,合并保存步骤则是":wprevious" ":wprev"。要移到最后一个文件":last",到第一个":first".不过没有":wlast"或者":wfirst"这样的命令。可以在":next"和":previous"命令前面使用一个命令计数。
39.编辑另一个文件列表
不用重新启动Vim,就可以重新定义一个文件列表。命令":args five.c six.c seven.h"定义了要编辑的三个文件。
39.自动存盘
命令":set autowrite","set aw"。自动把内容写回文件: 如果文件被修改过,在每个:next、:rewind、:last、:first、:previous、:stop、:suspend、:tag、:!、:make、CTRL-] 和 CTRL-^命令时进行。
命令":set autowriteall","set awa"。和 'autowrite' 类似,但也适用于":edit"、":enew"、":quit"、":qall"、":exit"、":xit"、":recover" 和关闭 Vim 窗口。置位本选项也意味着 Vim 的行为就像打开 'autowrite' 一样。
40.切换到另一文件
要在两个文件间快速切换,使用CTRL-^。
41.文件标记
以大写字母命名的标记。它们是全局标记,它们可以用在任何文件中。比如,正在编辑"fab1.Java",用命令"50%mF"在文件的中间设置一个名为F的标记。然后在"fab2.java"文件中,用命令"GnB"在最后一行设置名为B的标记。在可以用"F"命令跳转到文件"fab1.java"的半中间。或者编辑另一个文件,"'B"命令会再把你带回文件"fab2.java"的最后一行。
要知道某个标记所代表的位置是什么,可以将该标记的名字作为"marks"命令的参数":marks M"或者连续跟上几个参数":marks MJK"
可以用CTRL-O和CTRL-I可以跳转到较早的位置和靠后的某位置。
42.查看文件
仅是查看文件,不向文件写入内容,可以用只读形式编辑文件。用命令:
vim -R file。如果是想强制性地避免对文件进行修改,可以用命令:
vim -M file。
43.更改文件名
将现有文件存成新的文件,用命令":sav(eas) move.c"。如果想改变当前正在编辑的文件名,但不想保存该文件,就可以用命令:":f(ile) move.c"。
44.分割一个窗口
打开一个新窗口最简单的办法就是使用命令:":split"。CTRL-W 命令可以切换当前活动窗口。
45.关闭窗口
用命令:"close".可以关闭当前窗口。实际上,任何退出文件编辑的命令":quit"和"ZZ"都会关闭窗口,但是用":close"可以阻止你关闭最后一个Vim,以免以意外地整个关闭了Vim。
46.关闭除当前窗口外的所有其他窗口
用命令:":only",关闭除当前窗口外的所有其它窗口。如果这些窗口中有被修改过的,你会得到一个错误信息,同时那个窗口会被留下来。
47.为另一个文件分隔出一个窗口
命令":split two.c"可以打开第二个窗口同时在新打开的窗口中开始编辑作为
参数的文件。如果要打开一个新窗口并开始编辑一个空的缓冲区,使用命令:":new"。
48.垂直分割
用命令":vsplit或::vsplit two.c"。同样有一个对应的":vnew"命令,用于垂直分隔窗口并在其中打开一个新的空缓冲区。
49.切换窗口
CTRL-W h 到左边的窗口
CTRL-W j 到下面的窗口
CTRL-W k 到上面的窗口
CTRL-W l 到右边的窗口
CTRL-W t 到顶部窗口
CTRL-W b 到底部窗口
50.针对所有窗口操作的命令
":qall"放弃所有操作并退出,":wall"保存所有,":wqall"保存所有并退出。
51.为每一个文件打开一个窗口
使用"-o"选项可以让Vim为每一个文件打开一个窗口:
"vim -o one.txt two.txt three.txt"。
52.使用vimdiff查看不同
"vimdiff main.c~ main.c",另一种进入diff模式的办法可以在Vim运行中操作。编辑文件"main.c",然后打开另一个分隔窗口显示其不同:
":edit main.c"
":vertical diffpatch main.c.diff"。
53.页签
命令":tabe(dit) thatfile"在一个窗口中打开"thatfile",该窗口占据着整个的Vim显示区域。命令":tab split/new"结果是新建了一个拥有一个窗口的页签。以用"gt"命令在不同的页签间切换。
这是我总结的一些基本用法,可能对初用者会有帮助,独乐乐不如众乐乐,是吧!
说明:以下黑色为vi和vim均有的一般功能,而红色为Vim(Vi Improved)所特有功能。Vim一般的Unix和Linux下均有安装。
三种状态
Command: 任何输入都会作为编辑命令,而不会出现在屏幕上,任何输入都引起立即反映
Insert: 任何输入的数据都置于编辑寄存器,按ESC,可跳回command方式
Escape: 以“:”或者“/”为前导的指令,出现在屏幕的最下一行,任何输入都被当成特别指令。
离开vi
:q! 离开vi,并放弃刚在缓冲区内编辑的内容。
:wq 将缓冲区内的资料写入磁盘中,并离开vi。
:x 同wq。
(注意—— :X 是文件加密,一定要与:x存盘退出相区别)
进入输入模式
a (append) 由游标之后加入资料。
A 由该行之末加入资料。
i (insert) 由游标之前加入资料。
I 由该行之首加入资料。
o (open) 新增一行於该行之下供输入资料之用。
O 新增一行於该行之上供输入资料之用。
删除与修改
x 删除游标所在该字元。
X 删除游标所在之前一字元。
r 用接於此指令之后的字元取代(replace)游标所在字元。如:ra将游标所在字元以 a 取代之。
R 进入取代状态,直到《ESC》为止。
s 删除游标所在之字元,并进入输入模式直到《ESC》。
S 删除游标所在之该行资料,并进入输入模式直到《ESC》。
光标的移动
0 移至该行之首
$ 移至该行之末。
e 移动到下个字的最後一个字母
w 移动到下个字的第一个字母。
b 移动到上个字的第一个字母。
^ 移至该行的第一个字元处。
H 移至视窗的第一行。
M 移至视窗的中间那行。
L 移至视窗的最后一行。
G 移至该文件的最后一行。
+ 移至下一列的第一个字元处。
- 移至上一列的第一个字元处。
:n 移至该文件的第 n 列。
n+ 移至游标所在位置之后的第 n 列。
n- 移至游标所在位置之前的第 n 列。
Ctrl+g 显示该行之行号、文件名称、文件中最末行之行号、游标所在行号占总行号之百分比。
视窗的移动
Ctrl+f 视窗往下卷一页。
Ctrl+b 视窗往上卷一页。
Ctrl+d 视窗往下卷半页。
Ctrl+u 视窗往上卷半页。
Ctrl+e 视窗往下卷一行。
Ctrl+y 视窗往上卷一行。
剪切、复制、删除
Operator + Scope = command
Operator
d 剪切
y 复制。
p 粘帖,与 d 和 y 配和使用。可将最后d或y的资料放置於游标所在位置之行列下。
c 修改,类似delete与insert的组和。删除一个字组、句子等之资料,并插入新建资料。
Scope
e 由游标所在位置至该字串的最后一个字元。
w 由游标所在位置至下一个字串的第一个字元。
b 由游标所在位置至前一个字串的第一个字元。
$ 由游标所在位置至该行的最后一个字元。
0 由游标所在位置至该行的第一个字元。
整行动作
dd 删除整行。
D 以行为单位,删除游标后之所有字元。
cc 修改整行的内容。
yy 使游标所在该行复制到记忆体缓冲区。
取消前一动作(Undo)
u 恢复最后一个指令之前的结果。
U 恢复游标该行之所有改变。
u 可以多次撤消指令,一次撤消一个操作,直至本次操作开始为止。
Ctrl+r 可以恢复撤消前内容,按多次可恢复多次。
查找与替换
/字串 往游标之后寻找该字串。
?字串 往游标之前寻找该字串。
n 往下继续寻找下一个相同的字串。
N 往上继续寻找下一个相同的字串。
% 查找“(”,“)”,“{”,“}”的配对符。
s 搜寻某行列范围。
g 搜寻整个编辑缓冲区的资料。
:1,$s/old/new/g 将文件中所有的『old』改成『new』。
:10,20s/^/ / 将第10行至第20行资料的最前面插入5个空白。
(vim)
/字符串 后边输入查询内容可保存至缓冲区中,可用↑↓进行以往内容选择。
另外:将光标移动在选定单词下方按*,则可以选中此单词作为查询字符,可以避免输入一长串字符的麻烦。
大小写替换
首先用按v开启选择功能,然后用↑↓←→键来选定所要替换的字符,若是小写变大写,则按U;反之按u;
如果是选择单词,则可以在按v后,按w,最后按U/u,这样就可以将字符随意的改变大小写了,而不用删除后重新敲入。
资料的连接
句子的连接。将游标所在之下一行连接至游标该行的后面。
环境的设定
:set all 可设置的环境变量列表
:set 环境变量的当前值
:set nu 设定资料的行号。
:set nonu 取消行号设定。
:set ai 自动内缩。
:set noai 取消自动内缩。
:set ruler 会在屏幕右下角显示当前光标所处位置,并随光移动而改变,占用屏幕空间较小,使用也比较方便,推荐使用。
:set hlsearch 在使用查找功能时,会高亮显示所有匹配的内容。
:set nohlsearch 关闭此功能。
:set incsearch 使Vim在输入字符串的过程中,光标就可定位显示匹配点。
:set nowrapscan 关闭查找自动回环功能,即查找到文件结尾处,结束查找;默认状态是自动回环
ex指令
读写资料
:10,20w test 将第10行至第20行的资料写入test文件。
:10,20w>>test 将第10行至第20行的资料加在test文件之后。
:r test 将test文件的资料读入编辑缓冲区的最后。
:e [filename] 编辑新的文件。
:e! [filename] 放弃当前修改的文件,编辑新的文件。
:sh 进入shell环境,使用exit退出,回到编辑器中。
:!cmd 运行命令cmd后,返回到编辑器中。
删除、复制及搬移
:10,20d 删除第10行至第20行的资料。
:10d 删除第10行的资料。
:%d 删除整个编辑缓冲区。
:10,20co30 将第10行至第20行的资料复制至第30行之后。
:10,20mo30 将第10行至第20行的资料搬移至第30行之后。 查看全部
vim 选择文本,删除,复制,粘贴
文本的选择,对于编辑器来说,是很基本的东西,也经常被用到,总结如下:
v 从光标当前位置开始,光标所经过的地方会被选中,再按一下v结束。
V 从光标当前行开始,光标经过的行都会被选中,再按一下V结束。
Ctrl + v 从光标当前位置开始,选中光标起点和终点所构成的矩形区域,再按一下Ctrl + v结束。
ggVG 选中全部的文本, 其中gg为跳到行首,V选中整行,G末尾
选中后就可以用编辑命令对其进行编辑,如
d 删除
y 复制 (默认是复制到"寄存器)
p 粘贴 (默认从"寄存器取出内容粘贴)
"+y 复制到系统剪贴板(也就是vim的+寄存器)
"+p 从系统剪贴板粘贴
vim命令总结
1.删除字符
要删除一个字符,只需要将光标移到该字符上按下"x"。
2.删除一行
删除一整行内容使用"dd"命令。删除后下面的行会移上来填补空缺。
3.删除换行符
在Vim中你可以把两行合并为一行,也就是说两行之间的换行符被删除了:命令是"J"。
4.撤销
如果你误删了过多的内容。显然你可以再输入一遍,但是命令"u" 更简便,它可以撤消上一次的操作。
5.重做
如果你撤消了多次,你还可以用CTRL-R(重做)来反转撤消的动作。换句话说,它是对撤消的撤消。撤消命令还有另一种形式,"U"命令,它一次撤消对一行的全部操作。第二次使用该命令则会撤消前一个"U"的操作。用"u"和CTRL-R你可以找回任何一个操作状态。
6.追加
"i"命令可以在当前光标之前插入文本。
"a"命令可以在当前光标之后插入文本。
"o"命令可以在当前行的下面另起一行,并使当前模式转为Insert模式。
"O"命令(注意是大写的字母O)将在当前行的上面另起一行。
7.使用命令计数
假设你要向上移动9行。这可以用"kkkkkkkkk"或"9k"来完成。事实上,很多命令都可以接受一个数字作为重复执行同一命令的次数。比如刚才的例子,要在行尾追加三个感叹号,当时用的命令是"a!!!"。另一个办法是用"3a!"命令。3说明该命令将被重复执行3次。同样,删除3个字符可以用"3x"。指定的数字要紧挨在它所要修饰的命令前面。
8.退出
要退出Vim,用命令"ZZ"。该命令保存当前文件并退出Vim。
9.放弃编辑
丢弃所有的修改并退出,用命令":q!"。用":e!"命令放弃所有修改并重新载入该文件的原始内容。
10.以Word为单位的移动
使用"w"命令可以将光标向前移动一个word的首字符上;比如"3w"将光标向前移动3个words。"b"命令则将光标向后移动到前一个word的首字符上。
"e"命令会将光标移动到下一个word的最后一个字符。命令"ge",它将光标移动到前一个word的最后一个字符上。、
11.移动到行首或行尾
"$"命令将光标移动到当前行行尾。如果你的键盘上有一个键,它的作用也一样。"^"命令将光标移动到当前行的第一个非空白字符上。"0"命令则总是把光标移动到当前行的第一个字符上。键也是如此。"$"命令还可接受一个计数,如"1$"会将光标移动到当前行行尾,"2$"则会移动到下一行的行尾,如此类推。"0"命令却不能接受类似这样的计数,命令"^"前加上一个计数也没有任何效果。
12.移动到指定字符上
命令"fx"在当前行上查找下一个字符x(向右方向),可以带一个命令计数"F"命令向左方向搜索。"tx"命令形同"fx"命令,只不过它不是把光标停留在被搜索字符上,而是在它之前的一个字符上。提示:"t"意为"To"。该命令的反方向版是"Tx"。这4个命令都可以用";"来重复。以","也是重复同样的命令,但是方向与原命令的方向相反。
13.以匹配一个括号为目的移动
用命令"%"跳转到与当前光标下的括号相匹配的那一个括号上去。如果当前光标在"("上,它就向前跳转到与它匹配的")"上,如果当前在")"上,它就向后自动跳转到匹配的"("上去.
14.移动到指定行
用"G"命令指定一个命令计数,这个命令就会把光标定位到由命令计数指定的行上。比如"33G"就会把光标置于第33行上。没有指定命令计数作为参数的话, "G"会把光标定位到最后一行上。"gg"命令是跳转到第一行的快捷的方法。
另一个移动到某行的方法是在命令"%"之前指定一个命令计数比如"50%"将会把光标定位在文件的中间. "90%"跳到接近文件尾的地方。
命令"H","M","L",分别将光标跳转到第一行,中间行,结尾行部分。
15.告诉你当前的位置
使用CTRL-G命令。"set number"在每行的前面显示一个行号。相反关闭行号用命令":set nonumber"。":set ruler"在Vim窗口的右下角显示当前光标位置。
16.滚屏
CTRL-U显示文本的窗口向上滚动了半屏。CTRL-D命令将窗口向下移动半屏。一次滚动一行可以使用CTRL-E(向上滚动)和CTRL-Y(向下滚动)。要向前滚动一整屏使用命令CTRL-F。另外CTRL-B是它的反向版。"zz"命令会把当前行置为屏幕正中央,"zt"命令会把当前行置于屏幕顶端,"zb"则把当前行置于屏幕底端.
17.简单搜索
"/string"命令可用于搜索一个字符串。要查找上次查找的字符串的下一个位置,使用"n"命令。如果你知道你要找的确切位置是目标字符串的第几次出现,还可以在"n"之前放置一个命令计数。"3n"会去查找目标字符串的第3次出现。
"?"命令与"/"的工作相同,只是搜索方向相反."N"命令会重复前一次查找,但是与最初用"/"或"?"指定的搜索方向相反。
如果查找内容忽略大小写,则用命令"set ignorecase", 返回精确匹配用命令"set noignorecase" 。
18.在文本中查找下一个word
把光标定位于这个word上然后按下""键。Vim将会取当前光标所在的word并将它作用目标字符串进行搜索。"#"命令是""的反向版。还可以在这两个命令前加一个命令计数:"3*"查找当前光标下的word的第三次出现。
19.查找整个word
如果你用"/the"来查找Vim也会匹配到"there"。要查找作为独立单词的"the"使用如下命令:"/the>"。">"是一个特殊的记法,它只匹配一个word的结束处。近似地,"\<"匹配到一个word的开始处。这样查找作为一个word的"the"就可以用:"/"。
20.高亮显示搜索结果
开启这一功能用":set hlsearch",关闭这一功能:":set nohlsearch"。如果只是想去掉当前的高亮显示,可以使用下面的命令:":nohlsearch"(可以简写为noh)。
21.匹配一行的开头与结尾
^ 字符匹配一行的开头。$字符匹配一行的末尾。
所以"/was$"只匹配位于一行末尾的单词was,所以"/^was"只匹配位于一行开始的单词was。
22.匹配任何的单字符
.这个字符可以匹配到任何字符。比如"c.m"可以匹配任何前一个字符是c,后一个字符是m的情况,不管中间的字符是什么。
23.匹配特殊字符
放一个反斜杠在特殊字符前面。如果你查找"ter。",用命令"/ter\。"
24.使用标记
当你用"G"命令从一个地方跳转到另一个地方时,Vim会记得你起跳的位置。这个位置在Vim中是一个标记。使用命令" "可以使你跳回到刚才的出发点。命令可以在两点之间来回跳转。CTRL-O命令是跳转到你更早些时间停置光标的位置(提示:O意为older). CTRL-I则是跳回到后来停置光标的更新的位置(提示:I在键盘上位于O前面)。
注:使用CTRL-I 与按下键一样。
25.具名标记
命令"ma"将当前光标下的位置名之为标记"a"。从a到z一共可以使用26个自定义的标记。要跳转到一个你定义过的标记,使用命令" marks "marks就是定义的标记的名字。命令" 'a "使你跳转到a所在行的行首,"a "会精确定位a所在的位置。命令:":marks"用来查看标记的列表。
命令delm!删除所有标记。
26.操作符命令和位移
"dw"命令可以删除一个word,"d4w"命令是删除4个word,依此类推。类似有"d2e"、"d$"。此类命令有一个固定的模式:操作符命令+位移命令。首先键入一个操作符命令。比如"d"是一个删除操作符。接下来是一个位移命。比如"w"。这样任何移动光标命令所及之处,都是命令的作用范围。
27.改变文本
操作符命令是"c",改变命令。它的行为与"d"命令类似,不过在命令执行后会进入Insert模式。比如"cw"改变一个word。或者,更准确地说,它删除一个word并让你置身于Insert模式。
"cc"命令可以改变整行。不过仍保持原来的缩进。
"c$"改变当前光标到行尾的内容。
快捷命令:x 代表dl(删除当前光标下的字符)
X 代表dh(删除当前光标左边的字符)
D 代表d$(删除到行尾的内容)
C 代表c$(修改到行尾的内容)
s 代表cl(修改一个字符)
S 代表cc(修改一整行)
命令"3dw"和"d3w"都是删除3个word。第一个命令"3dw"可以看作是删除一个word的操作执行3次;第二个命令"d3w"是一次删除3个word。这是其中不明显的差异。事实上你可以在两处都放上命令记数,比如,"3d2w"是删除两个word,重复执行3次,总共是6个word。
28.替换单个字符
"r"命令不是一个操作符命令。它等待你键入下一个字符用以替换当前光标下的那个字符。"r"命令前辍以一个命令记数是将多个字符都替换为即将输入的那个字符。要把一个字符替换为一个换行符使用"r"。它会删除一个字符并插入一个换行符。在此处使用命令记数只会删除指定个数的字符:"4r"将把4个字符替换为一个换行符。
29.重复改动
"."命令会重复上一次做出的改动。"."命令会重复你做出的所有修改,除了"u"命令CTRL-R和以冒号开头的命令。"."需要在Normal模式下执行,它重复的是命令,而不是被改动的内容,
30.Visual模式
按"v"可以进入Visual模式。移动光标以覆盖你想操纵的文本范围。同时被选中的文本会以高亮显示。最后键入操作符命令。
31.移动文本
以"d"或"x"这样的命令删除文本时,被删除的内容还是被保存了起来。你还可以用p命令把它取回来。"P"命令是把被去回的内容放在光标之前,"p"则是放在光标之后。对于以"dd"删除的整行内容,"P"会把它置于当前行的上一行。"p"则是至于当前行的后一行。也可以对命令"p"和"P"命令使用命令记数。它的效果是同样的内容被取回指定的次数。这样一来"dd"之后的"3p"就可以把被删除行的3 份副本放到当前位置。
命令"xp"将光标所在的字符与后一个字符交换。
32.复制文本(VIM编辑器内复制)
"y"操作符命令会把文本复制到一个寄存器3中。然后可以用"p"命令把它取回。因为"y"是一个操作符命令,所以你可以用"yw"来复制一个word. 同样可以使用命令记数。如下例中用"y2w"命令复制两个word,"yy"命令复制一整行,"Y"也是复制整行的内容,复制当前光标至行尾的命令是"y$"。
33.文本对象
"diw" 删除当前光标所在的word(不包括空白字符) "daw" 删除当前光标所在的word(包括空白字符)
34.快捷命令
x 删除当前光标下的字符("dl"的快捷命令)
X 删除当前光标之前的字符("dh"的快捷命令)
D 删除自当前光标至行尾的内容("d$"的快捷命令)
dw 删除自当前光标至下一个word的开头
db 删除自当前光标至前一个word的开始
diw 删除当前光标所在的word(不包括空白字符)
daw 删除当前光标所在的word(包括空白字符)
dG 删除当前行至文件尾的内容
dgg 删除当前行至文件头的内容
如果你用"c"命令代替"d"这些命令就都变成更改命令。使用"y"就是yank命令,如此类推。
35.编辑另一个文件
用命令":edit foo.txt",也可简写为":e foo.txt"。
36.文件列表
可以在启动Vim时就指定要编辑多个文件,用命令"vim one.c two.c three.c"。Vim将在启动后只显示第一个文件,完成该文件的编辑后,可以用令:":next"或":n"要保存工作成果并继续下一个文件的编辑,命令:":wnext"或":wn"可以合并这一过程。
37.显示当前正在编辑的文件
用命令":args"。
38.移动到另一个文件
用命令":previous" ":prev"回到上一个文件,合并保存步骤则是":wprevious" ":wprev"。要移到最后一个文件":last",到第一个":first".不过没有":wlast"或者":wfirst"这样的命令。可以在":next"和":previous"命令前面使用一个命令计数。
39.编辑另一个文件列表
不用重新启动Vim,就可以重新定义一个文件列表。命令":args five.c six.c seven.h"定义了要编辑的三个文件。
39.自动存盘
命令":set autowrite","set aw"。自动把内容写回文件: 如果文件被修改过,在每个:next、:rewind、:last、:first、:previous、:stop、:suspend、:tag、:!、:make、CTRL-] 和 CTRL-^命令时进行。
命令":set autowriteall","set awa"。和 'autowrite' 类似,但也适用于":edit"、":enew"、":quit"、":qall"、":exit"、":xit"、":recover" 和关闭 Vim 窗口。置位本选项也意味着 Vim 的行为就像打开 'autowrite' 一样。
40.切换到另一文件
要在两个文件间快速切换,使用CTRL-^。
41.文件标记
以大写字母命名的标记。它们是全局标记,它们可以用在任何文件中。比如,正在编辑"fab1.Java",用命令"50%mF"在文件的中间设置一个名为F的标记。然后在"fab2.java"文件中,用命令"GnB"在最后一行设置名为B的标记。在可以用"F"命令跳转到文件"fab1.java"的半中间。或者编辑另一个文件,"'B"命令会再把你带回文件"fab2.java"的最后一行。
要知道某个标记所代表的位置是什么,可以将该标记的名字作为"marks"命令的参数":marks M"或者连续跟上几个参数":marks MJK"
可以用CTRL-O和CTRL-I可以跳转到较早的位置和靠后的某位置。
42.查看文件
仅是查看文件,不向文件写入内容,可以用只读形式编辑文件。用命令:
vim -R file。如果是想强制性地避免对文件进行修改,可以用命令:
vim -M file。
43.更改文件名
将现有文件存成新的文件,用命令":sav(eas) move.c"。如果想改变当前正在编辑的文件名,但不想保存该文件,就可以用命令:":f(ile) move.c"。
44.分割一个窗口
打开一个新窗口最简单的办法就是使用命令:":split"。CTRL-W 命令可以切换当前活动窗口。
45.关闭窗口
用命令:"close".可以关闭当前窗口。实际上,任何退出文件编辑的命令":quit"和"ZZ"都会关闭窗口,但是用":close"可以阻止你关闭最后一个Vim,以免以意外地整个关闭了Vim。
46.关闭除当前窗口外的所有其他窗口
用命令:":only",关闭除当前窗口外的所有其它窗口。如果这些窗口中有被修改过的,你会得到一个错误信息,同时那个窗口会被留下来。
47.为另一个文件分隔出一个窗口
命令":split two.c"可以打开第二个窗口同时在新打开的窗口中开始编辑作为
参数的文件。如果要打开一个新窗口并开始编辑一个空的缓冲区,使用命令:":new"。
48.垂直分割
用命令":vsplit或::vsplit two.c"。同样有一个对应的":vnew"命令,用于垂直分隔窗口并在其中打开一个新的空缓冲区。
49.切换窗口
CTRL-W h 到左边的窗口
CTRL-W j 到下面的窗口
CTRL-W k 到上面的窗口
CTRL-W l 到右边的窗口
CTRL-W t 到顶部窗口
CTRL-W b 到底部窗口
50.针对所有窗口操作的命令
":qall"放弃所有操作并退出,":wall"保存所有,":wqall"保存所有并退出。
51.为每一个文件打开一个窗口
使用"-o"选项可以让Vim为每一个文件打开一个窗口:
"vim -o one.txt two.txt three.txt"。
52.使用vimdiff查看不同
"vimdiff main.c~ main.c",另一种进入diff模式的办法可以在Vim运行中操作。编辑文件"main.c",然后打开另一个分隔窗口显示其不同:
":edit main.c"
":vertical diffpatch main.c.diff"。
53.页签
命令":tabe(dit) thatfile"在一个窗口中打开"thatfile",该窗口占据着整个的Vim显示区域。命令":tab split/new"结果是新建了一个拥有一个窗口的页签。以用"gt"命令在不同的页签间切换。
这是我总结的一些基本用法,可能对初用者会有帮助,独乐乐不如众乐乐,是吧!
说明:以下黑色为vi和vim均有的一般功能,而红色为Vim(Vi Improved)所特有功能。Vim一般的Unix和Linux下均有安装。
三种状态
Command: 任何输入都会作为编辑命令,而不会出现在屏幕上,任何输入都引起立即反映
Insert: 任何输入的数据都置于编辑寄存器,按ESC,可跳回command方式
Escape: 以“:”或者“/”为前导的指令,出现在屏幕的最下一行,任何输入都被当成特别指令。
离开vi
:q! 离开vi,并放弃刚在缓冲区内编辑的内容。
:wq 将缓冲区内的资料写入磁盘中,并离开vi。
:x 同wq。
(注意—— :X 是文件加密,一定要与:x存盘退出相区别)
进入输入模式
a (append) 由游标之后加入资料。
A 由该行之末加入资料。
i (insert) 由游标之前加入资料。
I 由该行之首加入资料。
o (open) 新增一行於该行之下供输入资料之用。
O 新增一行於该行之上供输入资料之用。
删除与修改
x 删除游标所在该字元。
X 删除游标所在之前一字元。
r 用接於此指令之后的字元取代(replace)游标所在字元。如:ra将游标所在字元以 a 取代之。
R 进入取代状态,直到《ESC》为止。
s 删除游标所在之字元,并进入输入模式直到《ESC》。
S 删除游标所在之该行资料,并进入输入模式直到《ESC》。
光标的移动
0 移至该行之首
$ 移至该行之末。
e 移动到下个字的最後一个字母
w 移动到下个字的第一个字母。
b 移动到上个字的第一个字母。
^ 移至该行的第一个字元处。
H 移至视窗的第一行。
M 移至视窗的中间那行。
L 移至视窗的最后一行。
G 移至该文件的最后一行。
+ 移至下一列的第一个字元处。
- 移至上一列的第一个字元处。
:n 移至该文件的第 n 列。
n+ 移至游标所在位置之后的第 n 列。
n- 移至游标所在位置之前的第 n 列。
Ctrl+g 显示该行之行号、文件名称、文件中最末行之行号、游标所在行号占总行号之百分比。
视窗的移动
Ctrl+f 视窗往下卷一页。
Ctrl+b 视窗往上卷一页。
Ctrl+d 视窗往下卷半页。
Ctrl+u 视窗往上卷半页。
Ctrl+e 视窗往下卷一行。
Ctrl+y 视窗往上卷一行。
剪切、复制、删除
Operator + Scope = command
Operator
d 剪切
y 复制。
p 粘帖,与 d 和 y 配和使用。可将最后d或y的资料放置於游标所在位置之行列下。
c 修改,类似delete与insert的组和。删除一个字组、句子等之资料,并插入新建资料。
Scope
e 由游标所在位置至该字串的最后一个字元。
w 由游标所在位置至下一个字串的第一个字元。
b 由游标所在位置至前一个字串的第一个字元。
$ 由游标所在位置至该行的最后一个字元。
0 由游标所在位置至该行的第一个字元。
整行动作
dd 删除整行。
D 以行为单位,删除游标后之所有字元。
cc 修改整行的内容。
yy 使游标所在该行复制到记忆体缓冲区。
取消前一动作(Undo)
u 恢复最后一个指令之前的结果。
U 恢复游标该行之所有改变。
u 可以多次撤消指令,一次撤消一个操作,直至本次操作开始为止。
Ctrl+r 可以恢复撤消前内容,按多次可恢复多次。
查找与替换
/字串 往游标之后寻找该字串。
?字串 往游标之前寻找该字串。
n 往下继续寻找下一个相同的字串。
N 往上继续寻找下一个相同的字串。
% 查找“(”,“)”,“{”,“}”的配对符。
s 搜寻某行列范围。
g 搜寻整个编辑缓冲区的资料。
:1,$s/old/new/g 将文件中所有的『old』改成『new』。
:10,20s/^/ / 将第10行至第20行资料的最前面插入5个空白。
(vim)
/字符串 后边输入查询内容可保存至缓冲区中,可用↑↓进行以往内容选择。
另外:将光标移动在选定单词下方按*,则可以选中此单词作为查询字符,可以避免输入一长串字符的麻烦。
大小写替换
首先用按v开启选择功能,然后用↑↓←→键来选定所要替换的字符,若是小写变大写,则按U;反之按u;
如果是选择单词,则可以在按v后,按w,最后按U/u,这样就可以将字符随意的改变大小写了,而不用删除后重新敲入。
资料的连接
句子的连接。将游标所在之下一行连接至游标该行的后面。
环境的设定
:set all 可设置的环境变量列表
:set 环境变量的当前值
:set nu 设定资料的行号。
:set nonu 取消行号设定。
:set ai 自动内缩。
:set noai 取消自动内缩。
:set ruler 会在屏幕右下角显示当前光标所处位置,并随光移动而改变,占用屏幕空间较小,使用也比较方便,推荐使用。
:set hlsearch 在使用查找功能时,会高亮显示所有匹配的内容。
:set nohlsearch 关闭此功能。
:set incsearch 使Vim在输入字符串的过程中,光标就可定位显示匹配点。
:set nowrapscan 关闭查找自动回环功能,即查找到文件结尾处,结束查找;默认状态是自动回环
ex指令
读写资料
:10,20w test 将第10行至第20行的资料写入test文件。
:10,20w>>test 将第10行至第20行的资料加在test文件之后。
:r test 将test文件的资料读入编辑缓冲区的最后。
:e [filename] 编辑新的文件。
:e! [filename] 放弃当前修改的文件,编辑新的文件。
:sh 进入shell环境,使用exit退出,回到编辑器中。
:!cmd 运行命令cmd后,返回到编辑器中。
删除、复制及搬移
:10,20d 删除第10行至第20行的资料。
:10d 删除第10行的资料。
:%d 删除整个编辑缓冲区。
:10,20co30 将第10行至第20行的资料复制至第30行之后。
:10,20mo30 将第10行至第20行的资料搬移至第30行之后。
58到家数据库30条军规解读
数据库 • zkbhj 发表了文章 • 0 个评论 • 3225 次浏览 • 2017-02-16 10:29
(1)必须使用InnoDB存储引擎
解读:支持事务、行级锁、并发性能更好、CPU及内存缓存页优化使得资源利用率更高
(2)必须使用UTF8字符集
解读:万国码,无需转码,无乱码风险,节省空间
(3)数据表、数据字段必须加入中文注释
解读:N年后谁tm知道这个r1,r2,r3字段是干嘛的
(4)禁止使用存储过程、视图、触发器、Event
解读:高并发大数据的互联网业务,架构设计思路是“解放数据库CPU,将计算转移到服务层”,并发量大的情况下,这些功能很可能将数据库拖死,业务逻辑放到服务层具备更好的扩展性,能够轻易实现“增机器就加性能”。数据库擅长存储与索引,CPU计算还是上移吧
(5)禁止存储大文件或者大照片
解读:为何要让数据库做它不擅长的事情?大文件和照片存储在文件系统,数据库里存URI多好
二、命名规范
(6)只允许使用内网域名,而不是ip连接数据库
(7)线上环境、开发环境、测试环境数据库内网域名遵循命名规范
业务名称:xxx
线上环境:dj.xxx.db
开发环境:dj.xxx.rdb
测试环境:dj.xxx.tdb
从库在名称后加-s标识,备库在名称后加-ss标识
线上从库:dj.xxx-s.db
线上备库:dj.xxx-sss.db
(8)库名、表名、字段名:小写,下划线风格,不超过32个字符,必须见名知意,禁止拼音英文混用
(9)表名t_xxx,非唯一索引名idx_xxx,唯一索引名uniq_xxx
三、表设计规范
(10)单实例表数目必须小于500
(11)单表列数目必须小于30
(12)表必须有主键,例如自增主键
解读:
a)主键递增,数据行写入可以提高插入性能,可以避免page分裂,减少表碎片提升空间和内存的使用
b)主键要选择较短的数据类型, Innodb引擎普通索引都会保存主键的值,较短的数据类型可以有效的减少索引的磁盘空间,提高索引的缓存效率
c) 无主键的表删除,在row模式的主从架构,会导致备库夯住
(13)禁止使用外键,如果有外键完整性约束,需要应用程序控制
解读:外键会导致表与表之间耦合,update与delete操作都会涉及相关联的表,十分影响sql 的性能,甚至会造成死锁。高并发情况下容易造成数据库性能,大数据高并发业务场景数据库使用以性能优先
四、字段设计规范
(14)必须把字段定义为NOT NULL并且提供默认值
解读:
a)null的列使索引/索引统计/值比较都更加复杂,对MySQL来说更难优化
b)null 这种类型MySQL内部需要进行特殊处理,增加数据库处理记录的复杂性;同等条件下,表中有较多空字段的时候,数据库的处理性能会降低很多
c)null值需要更多的存储空,无论是表还是索引中每行中的null的列都需要额外的空间来标识
d)对null 的处理时候,只能采用is null或is not null,而不能采用=、in、<、<>、!=、not in这些操作符号。如:where name!=’shenjian’,如果存在name为null值的记录,查询结果就不会包含name为null值的记录
(15)禁止使用TEXT、BLOB类型
解读:会浪费更多的磁盘和内存空间,非必要的大量的大字段查询会淘汰掉热数据,导致内存命中率急剧降低,影响数据库性能
(16)禁止使用小数存储货币
解读:使用整数吧,小数容易导致钱对不上
(17)必须使用varchar(20)存储手机号
解读:
a)涉及到区号或者国家代号,可能出现+-()
b)手机号会去做数学运算么?
c)varchar可以支持模糊查询,例如:like“138%”
(18)禁止使用ENUM,可使用TINYINT代替
解读:
a)增加新的ENUM值要做DDL操作
b)ENUM的内部实际存储就是整数,你以为自己定义的是字符串?
五、索引设计规范
(19)单表索引建议控制在5个以内
(20)单索引字段数不允许超过5个
解读:字段超过5个时,实际已经起不到有效过滤数据的作用了
(21)禁止在更新十分频繁、区分度不高的属性上建立索引
解读:
a)更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能
b)“性别”这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似
(22)建立组合索引,必须把区分度高的字段放在前面
解读:能够更加有效的过滤数据
六、SQL使用规范
(23)禁止使用SELECT *,只获取必要的字段,需要显示说明列属性
解读:
a)读取不需要的列会增加CPU、IO、NET消耗
b)不能有效的利用覆盖索引
c)使用SELECT *容易在增加或者删除字段后出现程序BUG
(24)禁止使用INSERT INTO t_xxx VALUES(xxx),必须显示指定插入的列属性
解读:容易在增加或者删除字段后出现程序BUG
(25)禁止使用属性隐式转换
解读:SELECT uid FROM t_user WHERE phone=13812345678 会导致全表扫描,而不能命中phone索引,猜猜为什么?(这个线上问题不止出现过一次)
(26)禁止在WHERE条件的属性上使用函数或者表达式
解读:SELECT uid FROM t_user WHERE from_unixtime(day)>='2017-02-15' 会导致全表扫描
正确的写法是:SELECT uid FROM t_user WHERE day>= unix_timestamp('2017-02-15 00:00:00')
(27)禁止负向查询,以及%开头的模糊查询
解读:
a)负向查询条件:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等,会导致全表扫描
b)%开头的模糊查询,会导致全表扫描
(28)禁止大表使用JOIN查询,禁止大表使用子查询
解读:会产生临时表,消耗较多内存与CPU,极大影响数据库性能
(29)禁止使用OR条件,必须改为IN查询
解读:旧版本Mysql的OR查询是不能命中索引的,即使能命中索引,为何要让数据库耗费更多的CPU帮助实施查询优化呢?
(30)应用程序必须捕获SQL异常,并有相应处理
总结:大数据量高并发的互联网业务,极大影响数据库性能的都不让用,不让用哟。 查看全部
(1)必须使用InnoDB存储引擎
解读:支持事务、行级锁、并发性能更好、CPU及内存缓存页优化使得资源利用率更高
(2)必须使用UTF8字符集
解读:万国码,无需转码,无乱码风险,节省空间
(3)数据表、数据字段必须加入中文注释
解读:N年后谁tm知道这个r1,r2,r3字段是干嘛的
(4)禁止使用存储过程、视图、触发器、Event
解读:高并发大数据的互联网业务,架构设计思路是“解放数据库CPU,将计算转移到服务层”,并发量大的情况下,这些功能很可能将数据库拖死,业务逻辑放到服务层具备更好的扩展性,能够轻易实现“增机器就加性能”。数据库擅长存储与索引,CPU计算还是上移吧
(5)禁止存储大文件或者大照片
解读:为何要让数据库做它不擅长的事情?大文件和照片存储在文件系统,数据库里存URI多好
二、命名规范
(6)只允许使用内网域名,而不是ip连接数据库
(7)线上环境、开发环境、测试环境数据库内网域名遵循命名规范
业务名称:xxx
线上环境:dj.xxx.db
开发环境:dj.xxx.rdb
测试环境:dj.xxx.tdb
从库在名称后加-s标识,备库在名称后加-ss标识
线上从库:dj.xxx-s.db
线上备库:dj.xxx-sss.db
(8)库名、表名、字段名:小写,下划线风格,不超过32个字符,必须见名知意,禁止拼音英文混用
(9)表名t_xxx,非唯一索引名idx_xxx,唯一索引名uniq_xxx
三、表设计规范
(10)单实例表数目必须小于500
(11)单表列数目必须小于30
(12)表必须有主键,例如自增主键
解读:
a)主键递增,数据行写入可以提高插入性能,可以避免page分裂,减少表碎片提升空间和内存的使用
b)主键要选择较短的数据类型, Innodb引擎普通索引都会保存主键的值,较短的数据类型可以有效的减少索引的磁盘空间,提高索引的缓存效率
c) 无主键的表删除,在row模式的主从架构,会导致备库夯住
(13)禁止使用外键,如果有外键完整性约束,需要应用程序控制
解读:外键会导致表与表之间耦合,update与delete操作都会涉及相关联的表,十分影响sql 的性能,甚至会造成死锁。高并发情况下容易造成数据库性能,大数据高并发业务场景数据库使用以性能优先
四、字段设计规范
(14)必须把字段定义为NOT NULL并且提供默认值
解读:
a)null的列使索引/索引统计/值比较都更加复杂,对MySQL来说更难优化
b)null 这种类型MySQL内部需要进行特殊处理,增加数据库处理记录的复杂性;同等条件下,表中有较多空字段的时候,数据库的处理性能会降低很多
c)null值需要更多的存储空,无论是表还是索引中每行中的null的列都需要额外的空间来标识
d)对null 的处理时候,只能采用is null或is not null,而不能采用=、in、<、<>、!=、not in这些操作符号。如:where name!=’shenjian’,如果存在name为null值的记录,查询结果就不会包含name为null值的记录
(15)禁止使用TEXT、BLOB类型
解读:会浪费更多的磁盘和内存空间,非必要的大量的大字段查询会淘汰掉热数据,导致内存命中率急剧降低,影响数据库性能
(16)禁止使用小数存储货币
解读:使用整数吧,小数容易导致钱对不上
(17)必须使用varchar(20)存储手机号
解读:
a)涉及到区号或者国家代号,可能出现+-()
b)手机号会去做数学运算么?
c)varchar可以支持模糊查询,例如:like“138%”
(18)禁止使用ENUM,可使用TINYINT代替
解读:
a)增加新的ENUM值要做DDL操作
b)ENUM的内部实际存储就是整数,你以为自己定义的是字符串?
五、索引设计规范
(19)单表索引建议控制在5个以内
(20)单索引字段数不允许超过5个
解读:字段超过5个时,实际已经起不到有效过滤数据的作用了
(21)禁止在更新十分频繁、区分度不高的属性上建立索引
解读:
a)更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能
b)“性别”这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似
(22)建立组合索引,必须把区分度高的字段放在前面
解读:能够更加有效的过滤数据
六、SQL使用规范
(23)禁止使用SELECT *,只获取必要的字段,需要显示说明列属性
解读:
a)读取不需要的列会增加CPU、IO、NET消耗
b)不能有效的利用覆盖索引
c)使用SELECT *容易在增加或者删除字段后出现程序BUG
(24)禁止使用INSERT INTO t_xxx VALUES(xxx),必须显示指定插入的列属性
解读:容易在增加或者删除字段后出现程序BUG
(25)禁止使用属性隐式转换
解读:SELECT uid FROM t_user WHERE phone=13812345678 会导致全表扫描,而不能命中phone索引,猜猜为什么?(这个线上问题不止出现过一次)
(26)禁止在WHERE条件的属性上使用函数或者表达式
解读:SELECT uid FROM t_user WHERE from_unixtime(day)>='2017-02-15' 会导致全表扫描
正确的写法是:SELECT uid FROM t_user WHERE day>= unix_timestamp('2017-02-15 00:00:00')
(27)禁止负向查询,以及%开头的模糊查询
解读:
a)负向查询条件:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等,会导致全表扫描
b)%开头的模糊查询,会导致全表扫描
(28)禁止大表使用JOIN查询,禁止大表使用子查询
解读:会产生临时表,消耗较多内存与CPU,极大影响数据库性能
(29)禁止使用OR条件,必须改为IN查询
解读:旧版本Mysql的OR查询是不能命中索引的,即使能命中索引,为何要让数据库耗费更多的CPU帮助实施查询优化呢?
(30)应用程序必须捕获SQL异常,并有相应处理
总结:大数据量高并发的互联网业务,极大影响数据库性能的都不让用,不让用哟。
为了隐藏真正的Mysql数据库,我想通过中间跳板机nginx服务做转发进行数据库连接,要怎么做?
回复数据库 • zkbhj 发起了问题 • 1 人关注 • 0 个回复 • 1230 次浏览 • 2023-07-10 11:23
如何解决Navicat链接数据库出先Access denied的问题?
回复数据库 • zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1450 次浏览 • 2023-05-18 11:41
PHP8.1的项目,用curl访问链接总是返回35号错误,怎么解决?
回复PHP • zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1970 次浏览 • 2023-03-02 17:00
如何通过Composer只安装指定的包,不更新各种依赖的组件包?
回复工具软件 • zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 2510 次浏览 • 2023-07-09 14:32
Yii2框架Model的save方法保存数据返回成功,但数据库数据没有保存成功
回复Yii框架 • zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1797 次浏览 • 2022-10-26 17:14
Nginx+PHP-FPM架构,结合PHP8.1搭建应用服务器时的一些总结和记录
PHP • zkbhj 发表了文章 • 0 个评论 • 2331 次浏览 • 2022-12-08 14:07
一、编译安装PHP8.1:
因为编译过程中基本上遇到问题的概率并不大,我的编译过程还算顺利,因为服务器上保留了以前其他人运行的项目。PHP的版本是7.2.3,所以就编译安装了PHP8.1到一个指定的地方,然后独立安装的PHP-FPM。尽量不影响以前的项目运行。
我的编译配置信息(注意要一行,不能存在换行符):./configure --prefix=/usr/local/php8.1 --with-config-file-path=/usr/local/php8.1/etc --with-curl --with-freetype --enable-gd --with-jpeg --with-gettext --with-kerberos --with-libdir=lib64 --with-libxml --with-mysqli --with-openssl --with-pdo-mysql --with-pdo-sqlite --with-pear --enable-sockets --with-mhash --with-ldap-sasl --with-xsl --with-zlib -with-bz2 --with-iconv --enable-fpm --enable-pdo --enable-bcmath --enable-mbregex --enable-mbstring --enable-opcache --enable-pcntl --enable-soap --enable-sockets --enable-sysvsem --enable-xml --enable-sysvsem --enable-cli --enable-opcache --enable-intl --enable-calendar --enable-static --enable-mysqlnd最终我把编译好的php-fpm文件复制到了我的PHP8.1目录下,并完成了配置启动。这会儿还一切看似顺利。
编译过程中遇到了一个问题是:
configure: error: iconv does not support errno
优先参考网上的解决方案安装缺失的iconv,如果不行,在运行configure之前先运行:
export LDFLAGS="$LDFLAGS -liconv"参考官网bug讨论 https://bugs.php.net/bug.php?id=80585,在此对歪果友人诚挚的感谢。
二、部署应用:
搭建好之后,顺利启动PHP-FPM服务器,监听端口9000,然后再Nginx配置文件中,将请求信息直接给到9000端口来处理,这里就有一些“东西”要注意了,否则就遇到了下面的问题:
问题1:部署应用之后,请求失败,页面返回“File not Found”,然后查看Nginx的报错日志,显示信息是:2022/12/08 13:15:51 [error] 31547#0: *29582 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream, client: 119.116.237.75, server: web.*.com, request: "GET /site/user-agreement HTTP/2.0", upstream: "fastcgi://127.0.0.1:9000", host: "web*.*com"网上说中问题, 比如php-fpm的 运行group和user不对啦,应用目录权限不足啦,但我发现根本原因是在Nginx转发时,少了一个重要的配置信息。
由于我的yii2项目启用了 URLManage功能,为了让链接看起来更漂亮一些,所以需要在Nginx配置文件中加入下面的这个配置:if (!-e $request_filename){
rewrite ^/(.*) /index.php last;
}
所以,完整的转发配置应该是:location / {
fastcgi_pass 127.0.0.1:9000;
if (!-e $request_filename){
rewrite ^/(.*) /index.php last;
}
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}所以这块,大家应该注意一下,更改完,重新启动Nginx服务之后,项目正常启动了!
问题二、页面图标显示异常:
由于使用了第三方图标工具,打开F12工具之后,发现是静态资源服务器访问字体文件时显示跨域:Access to Font at “xxx” from origin “xxx” has been by CORS plicy:
No ‘Access-Control-Allow-Origin’header is present on the requested resource.
Origin “xxx” is therefore not allowed access.这时候,只需要在静态资源服务器的Nginx配置文件中增加下面的配置项即可:location ~* .(eot|ttf|woff|swoff2|svg|otf)$ {
add_header Access-Control-Allow-Origin [url]http://www.zkbhj.com;[/url] //只允许单域名或者 ‘*' 。不推荐 ‘*' ,会导致安全问题
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
} 查看全部
一、编译安装PHP8.1:
因为编译过程中基本上遇到问题的概率并不大,我的编译过程还算顺利,因为服务器上保留了以前其他人运行的项目。PHP的版本是7.2.3,所以就编译安装了PHP8.1到一个指定的地方,然后独立安装的PHP-FPM。尽量不影响以前的项目运行。
我的编译配置信息(注意要一行,不能存在换行符):
./configure --prefix=/usr/local/php8.1 --with-config-file-path=/usr/local/php8.1/etc --with-curl --with-freetype --enable-gd --with-jpeg --with-gettext --with-kerberos --with-libdir=lib64 --with-libxml --with-mysqli --with-openssl --with-pdo-mysql --with-pdo-sqlite --with-pear --enable-sockets --with-mhash --with-ldap-sasl --with-xsl --with-zlib -with-bz2 --with-iconv --enable-fpm --enable-pdo --enable-bcmath --enable-mbregex --enable-mbstring --enable-opcache --enable-pcntl --enable-soap --enable-sockets --enable-sysvsem --enable-xml --enable-sysvsem --enable-cli --enable-opcache --enable-intl --enable-calendar --enable-static --enable-mysqlnd最终我把编译好的php-fpm文件复制到了我的PHP8.1目录下,并完成了配置启动。这会儿还一切看似顺利。
编译过程中遇到了一个问题是:
configure: error: iconv does not support errno
优先参考网上的解决方案安装缺失的iconv,如果不行,在运行configure之前先运行:
export LDFLAGS="$LDFLAGS -liconv"参考官网bug讨论 https://bugs.php.net/bug.php?id=80585,在此对歪果友人诚挚的感谢。
二、部署应用:
搭建好之后,顺利启动PHP-FPM服务器,监听端口9000,然后再Nginx配置文件中,将请求信息直接给到9000端口来处理,这里就有一些“东西”要注意了,否则就遇到了下面的问题:
问题1:部署应用之后,请求失败,页面返回“File not Found”,然后查看Nginx的报错日志,显示信息是:
2022/12/08 13:15:51 [error] 31547#0: *29582 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream, client: 119.116.237.75, server: web.*.com, request: "GET /site/user-agreement HTTP/2.0", upstream: "fastcgi://127.0.0.1:9000", host: "web*.*com"
网上说中问题, 比如php-fpm的 运行group和user不对啦,应用目录权限不足啦,但我发现根本原因是在Nginx转发时,少了一个重要的配置信息。由于我的yii2项目启用了 URLManage功能,为了让链接看起来更漂亮一些,所以需要在Nginx配置文件中加入下面的这个配置:
if (!-e $request_filename){所以,完整的转发配置应该是:
rewrite ^/(.*) /index.php last;
}
location / {所以这块,大家应该注意一下,更改完,重新启动Nginx服务之后,项目正常启动了!
fastcgi_pass 127.0.0.1:9000;
if (!-e $request_filename){
rewrite ^/(.*) /index.php last;
}
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
问题二、页面图标显示异常:
由于使用了第三方图标工具,打开F12工具之后,发现是静态资源服务器访问字体文件时显示跨域:
Access to Font at “xxx” from origin “xxx” has been by CORS plicy:这时候,只需要在静态资源服务器的Nginx配置文件中增加下面的配置项即可:
No ‘Access-Control-Allow-Origin’header is present on the requested resource.
Origin “xxx” is therefore not allowed access.
location ~* .(eot|ttf|woff|swoff2|svg|otf)$ {
add_header Access-Control-Allow-Origin [url]http://www.zkbhj.com;[/url] //只允许单域名或者 ‘*' 。不推荐 ‘*' ,会导致安全问题
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
}
怎么设计一个抢红包系统?
架构思想 • zkbhj 发表了文章 • 0 个评论 • 2522 次浏览 • 2022-07-03 11:46
活动规模
公司在年底,为员工准备了 25 万元的抢红包活动,在年三十晚 8 点准时开始。
技术方案
首先要理解到抢红包特殊的业务场景,红包抢到了并不等于把钱拿到手了。抢红包其实主要有 3 个核心流程:红包金额拆分->抢红包->打款。
红包金额拆分是指将指定金额拆分为指定数目红包的过程,用来确定每个红包的金额数;抢红包是用户抢红包的这个操作,典型的高并发场景,需要系统扛流量且避免红包超发的情况;打款就是将抢到的红包通过微信/银行打款到用户钱包的过程(真正把红包的钱拿到手了),因为要对接三方支付系统是整个系统比较耗时的操作,一般通过异步任务来实现;
红包金额拆分
可选的方案
拆分方式
1、实时拆分
实时拆分,指的是在抢红包时实时计算每个红包的金额,以实现红包的拆分过程,对系统性能和拆分算法要求较高,例如拆分过程要一直保证后续待拆分红包的金额不能为空,不容易做到拆分的红包金额服从正态分布规律。
2、预先生成
预先生成,指的是在红包开抢之前已经完成了红包的金额拆分,抢红包时只是依次取出拆分好的红包金额,对拆分算法要求较低,可以拆分出随机性很好的红包金额,通常需要结合队列使用。
拆分算法
红包拆分算法拆分的金额要看起来随机,最好能够服从正态分布,可以参考 微信 和 @lcode 提供的红包拆分算法。
微信拆分算法的优点是算法较简单,拆分效率高,同时由于该算法天然的特性,可以保证后续红包金额一定不为空,特别适合实时拆分场景,但缺点是会导致大额红包较大概率地在拆分的最后出现。 @lcode 拆分算法的优点是拆分金额基本符合正态分布,适合随机性要求较高的拆分场景。
我们的方案
我们这次的场景对红包金额的随机性要求不高,但是对系统可靠性要求较高,所以我们选用了预先生成方式,使用 二倍均值法 的算法拆分红包金额。
拆分算法可以描述为:假设剩余拆分金额为 M,剩余待拆分红包个数为 N,红包最小金额为 1 元,那么定义当前红包的金额为:
m=rand(1,floor(M/N∗2))
其中,floor 表示向下取整,rand(min, max) 表示从 [min, max] 区间随机一个值。M/N*2 表示剩余待拆分金额平均金额的 2 倍,因为 N >= 2,所以 M/N*2 <= M,表示一定能保证后续红包能拆分到金额。
代码实现为:for ($i = 0; $i < $N - 1; $i++) {
$max = (int)floor($M / ($N - $i)) * 2;
$m[$i] = $max ? mt_rand(1, $max) : 0;
$M -= $m[$i];
}
$m = $M;值得一提的是,为了保证红包金额差异尽量小,先将总金额平均拆分成 N+1 份,将第 N+1 份红包按照上述的红包拆分算法拆分成 N 份,这 N 份红包加上之前的平均金额才作为最终的红包金额。
抢红包
可选的方案
限流
1、前端限流
前端限制用户在 n 秒之内只能提交一次请求,虽然这种方式只能挡住小白(99% 的用户),所以也必须得做。
2、后端限流
常用的后端限流方法有 漏桶算法 和 令牌桶算法。漏桶算法 主要目的是控制请求数据注入的速率,如果此时漏桶溢出,后续的请求数据会被丢弃。而 令牌桶算法 是以一个恒定的速度往桶里放入令牌,而如果请求数据需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌时,这些请求才被丢弃,令牌桶算法的一个好处是可以方便地改变应用接受请求的速率。
防并发超发红包
1、库存加锁
可以通过加锁的方式解决,但是加锁会增加系统开销,大流量下更容易拖垮系统,也可以尝试一下基于版本号的乐观锁。
2、队列串行化请求
之所会出现超发问题,是因为并发时会出现多个进程同时操作同一资源的现象,如果使用高速队列将并行请求串行化,那么问题就不存在了。高速队列可以使用 Redis 来实现,但是还要必须保证整个流程调用链要短、要快,否则队列会积压严重,甚至会拖垮整个系统。
我们的方案
我们选用队列串行化的方案,抢红包整个过程只会操作 Redis,且都是简单高效的 Pop 和 Push 命令操作。
抢红包流程:先从红包队列中 Pop 占有红包,然后 Push 红包到任务队列(待异步打款处理),并同步告知用户抢到红包的结果,抢红包流程就结束了。
当然,在实际应用中,占有红包过程中还会有一些前置规则校验,比如用户是否已经领取过,领取次数是否已经达到上限等?红包占有流程图如下:
其中,red::list为 List 结构,存放预先拆分好金额的红包;red::task 也为 List 结构,异步打款处理任务队列;red::draw为 Hash 结构,存放红包领取记录,field为用户的 openid,value为序列化的红包信息;red::draw_count:u:openid为 k-v 结构,用户领取红包计数器。
1、怎么保证不超发
从红包占有流程图可看出,这个过程是会操作很多 Key,那怎么保证原子性?我们选用了 Lua 方案,一方面是因为首先要保证性能,没有多次请求的网络开销,另一方面 Lua 脚本执行时本身就是原子性的,满足需求。
红包占有的 Lua 脚本实现如下:-- 领取人的openid为xxxxxxxxxxx
local openid = 'xxxxxxxxxxx'
local isDraw = redis.call('HEXISTS', 'red::draw', openid)
-- 已经领取
if isDraw ~= 0 then
return true
end
-- 领取太多次了
local times = redis.call('INCR', 'red::draw_count:u:'..openid)
if times and tonumber(times) > 9 then
return 0
end
local number = redis.call('RPOP', 'red::list')
-- 没有红包
if not number then
return {}
end
-- 领取人昵称为Fhb,头像为https://xxxxxxx
local red = {money=number,name='Fhb',pic='https://xxxxxxx'}
-- 领取记录
redis.call('HSET', 'red::draw', openid, cjson.encode(red))
-- 处理队列
red['openid'] = openid
redis.call('RPUSH', 'red::task', cjson.encode(red))
return true
需要注意 Lua 脚本执行过程并不是事务的,脚本中的操作命令在执行时是有先后顺序的,当某个操作执行失败时不会回滚已经执行成功的操作。
2、怎么提高系统响应
由于抢红包和打款流程分开,抢红包过程只需操作 Redis,整个操作短且快,故不存在性能问题。
打款
我们的方案
采用 Worker 任务去消费任务队列,调用红包支付 API,以及数据持久化操作(后续对账)。尽管红包发放调用链又长又慢,但是这些 Worker 是 无状态 的,所以可以通过增加 Worker 数量,提高系统的消费处理能力。
1、怎么保证数据一致性
若红包打款失败了,前面已经告知用户抢到红包,但是却木有发,那用户肯定会很愤怒。但是根据 CAP 原理,我们通常只需做到数据最终一致性。
我们在打款流程里面做了重试机制,生成一个全局唯一的外部订单号,当某红包打款失败,就会放回任务队列重试,当然重试时要处理好幂等。
2、怎么保证Worker不异常结束
Worker 的实现如下:$maxTask = 1000;
$sleepTime = 1000;
while (true) {
while ($red = RedLogic::getTask()) {
RedLogic::doTask($red);
//处理多少个任务主动退出
$maxTask--;
if ($maxTask < 0) {
return EXIT_CODE_NORMAL;
}
}
//等待任务
usleep($sleepTime);
}
这里使用 LPOP 命令获取任务,所以使用了 while 结构,并且无任务时需要等待,可以用阻塞命令 BLPOP 来改进。
由于 Worker 需要常驻内存运行,难免会出现异常结束的情况(也有主动 Exit), 所以需要保持 Worker 一直处于运行状态。我们使用进程管理工具 Supervisor 来监控和管理 Worker 任务,当任务队列出现堆积时,增加 Worker 数量即可。Supervisor 的监控后台如下:
保障方案
资源CDN缓存
由于本次活动力度较大,静态页面占流量的很大一部分,所以静态页面在发布时都会放置一份在 CDN 上,这样回源的流量就很小了。
降级措施
尽管做了很多准备,还是无法确保万无一失,我们在每个关键节点都增加了开关,一但出现异常,可以通过配置中心人工介入做降级处理。
原文来自微信公众号:后端搬运工。
原文地址:https://mp.weixin.qq.com/s/VG_Wcxte8avnXzn4bPXiGA
欢迎大家多关注! 查看全部
活动规模
公司在年底,为员工准备了 25 万元的抢红包活动,在年三十晚 8 点准时开始。
技术方案
首先要理解到抢红包特殊的业务场景,红包抢到了并不等于把钱拿到手了。抢红包其实主要有 3 个核心流程:红包金额拆分->抢红包->打款。
- 红包金额拆分是指将指定金额拆分为指定数目红包的过程,用来确定每个红包的金额数;
- 抢红包是用户抢红包的这个操作,典型的高并发场景,需要系统扛流量且避免红包超发的情况;
- 打款就是将抢到的红包通过微信/银行打款到用户钱包的过程(真正把红包的钱拿到手了),因为要对接三方支付系统是整个系统比较耗时的操作,一般通过异步任务来实现;
红包金额拆分
可选的方案
拆分方式
1、实时拆分
实时拆分,指的是在抢红包时实时计算每个红包的金额,以实现红包的拆分过程,对系统性能和拆分算法要求较高,例如拆分过程要一直保证后续待拆分红包的金额不能为空,不容易做到拆分的红包金额服从正态分布规律。
2、预先生成
预先生成,指的是在红包开抢之前已经完成了红包的金额拆分,抢红包时只是依次取出拆分好的红包金额,对拆分算法要求较低,可以拆分出随机性很好的红包金额,通常需要结合队列使用。
拆分算法
红包拆分算法拆分的金额要看起来随机,最好能够服从正态分布,可以参考 微信 和 @lcode 提供的红包拆分算法。
微信拆分算法的优点是算法较简单,拆分效率高,同时由于该算法天然的特性,可以保证后续红包金额一定不为空,特别适合实时拆分场景,但缺点是会导致大额红包较大概率地在拆分的最后出现。 @lcode 拆分算法的优点是拆分金额基本符合正态分布,适合随机性要求较高的拆分场景。
我们的方案
我们这次的场景对红包金额的随机性要求不高,但是对系统可靠性要求较高,所以我们选用了预先生成方式,使用 二倍均值法 的算法拆分红包金额。
拆分算法可以描述为:假设剩余拆分金额为 M,剩余待拆分红包个数为 N,红包最小金额为 1 元,那么定义当前红包的金额为:
m=rand(1,floor(M/N∗2))
其中,floor 表示向下取整,rand(min, max) 表示从 [min, max] 区间随机一个值。M/N*2 表示剩余待拆分金额平均金额的 2 倍,因为 N >= 2,所以 M/N*2 <= M,表示一定能保证后续红包能拆分到金额。
代码实现为:
for ($i = 0; $i < $N - 1; $i++) {值得一提的是,为了保证红包金额差异尽量小,先将总金额平均拆分成 N+1 份,将第 N+1 份红包按照上述的红包拆分算法拆分成 N 份,这 N 份红包加上之前的平均金额才作为最终的红包金额。
$max = (int)floor($M / ($N - $i)) * 2;
$m[$i] = $max ? mt_rand(1, $max) : 0;
$M -= $m[$i];
}
$m = $M;
抢红包
可选的方案
限流
1、前端限流
前端限制用户在 n 秒之内只能提交一次请求,虽然这种方式只能挡住小白(99% 的用户),所以也必须得做。
2、后端限流
常用的后端限流方法有 漏桶算法 和 令牌桶算法。漏桶算法 主要目的是控制请求数据注入的速率,如果此时漏桶溢出,后续的请求数据会被丢弃。而 令牌桶算法 是以一个恒定的速度往桶里放入令牌,而如果请求数据需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌时,这些请求才被丢弃,令牌桶算法的一个好处是可以方便地改变应用接受请求的速率。
防并发超发红包
1、库存加锁
可以通过加锁的方式解决,但是加锁会增加系统开销,大流量下更容易拖垮系统,也可以尝试一下基于版本号的乐观锁。
2、队列串行化请求
之所会出现超发问题,是因为并发时会出现多个进程同时操作同一资源的现象,如果使用高速队列将并行请求串行化,那么问题就不存在了。高速队列可以使用 Redis 来实现,但是还要必须保证整个流程调用链要短、要快,否则队列会积压严重,甚至会拖垮整个系统。
我们的方案
我们选用队列串行化的方案,抢红包整个过程只会操作 Redis,且都是简单高效的 Pop 和 Push 命令操作。
抢红包流程:先从红包队列中 Pop 占有红包,然后 Push 红包到任务队列(待异步打款处理),并同步告知用户抢到红包的结果,抢红包流程就结束了。
当然,在实际应用中,占有红包过程中还会有一些前置规则校验,比如用户是否已经领取过,领取次数是否已经达到上限等?红包占有流程图如下:
其中,red::list为 List 结构,存放预先拆分好金额的红包;red::task 也为 List 结构,异步打款处理任务队列;red::draw为 Hash 结构,存放红包领取记录,field为用户的 openid,value为序列化的红包信息;red::draw_count:u:openid为 k-v 结构,用户领取红包计数器。
1、怎么保证不超发
从红包占有流程图可看出,这个过程是会操作很多 Key,那怎么保证原子性?我们选用了 Lua 方案,一方面是因为首先要保证性能,没有多次请求的网络开销,另一方面 Lua 脚本执行时本身就是原子性的,满足需求。
红包占有的 Lua 脚本实现如下:
-- 领取人的openid为xxxxxxxxxxx
local openid = 'xxxxxxxxxxx'
local isDraw = redis.call('HEXISTS', 'red::draw', openid)
-- 已经领取
if isDraw ~= 0 then
return true
end
-- 领取太多次了
local times = redis.call('INCR', 'red::draw_count:u:'..openid)
if times and tonumber(times) > 9 then
return 0
end
local number = redis.call('RPOP', 'red::list')
-- 没有红包
if not number then
return {}
end
-- 领取人昵称为Fhb,头像为https://xxxxxxx
local red = {money=number,name='Fhb',pic='https://xxxxxxx'}
-- 领取记录
redis.call('HSET', 'red::draw', openid, cjson.encode(red))
-- 处理队列
red['openid'] = openid
redis.call('RPUSH', 'red::task', cjson.encode(red))
return true
需要注意 Lua 脚本执行过程并不是事务的,脚本中的操作命令在执行时是有先后顺序的,当某个操作执行失败时不会回滚已经执行成功的操作。
2、怎么提高系统响应
由于抢红包和打款流程分开,抢红包过程只需操作 Redis,整个操作短且快,故不存在性能问题。
打款
我们的方案
采用 Worker 任务去消费任务队列,调用红包支付 API,以及数据持久化操作(后续对账)。尽管红包发放调用链又长又慢,但是这些 Worker 是 无状态 的,所以可以通过增加 Worker 数量,提高系统的消费处理能力。
1、怎么保证数据一致性
若红包打款失败了,前面已经告知用户抢到红包,但是却木有发,那用户肯定会很愤怒。但是根据 CAP 原理,我们通常只需做到数据最终一致性。
我们在打款流程里面做了重试机制,生成一个全局唯一的外部订单号,当某红包打款失败,就会放回任务队列重试,当然重试时要处理好幂等。
2、怎么保证Worker不异常结束
Worker 的实现如下:
$maxTask = 1000;
$sleepTime = 1000;
while (true) {
while ($red = RedLogic::getTask()) {
RedLogic::doTask($red);
//处理多少个任务主动退出
$maxTask--;
if ($maxTask < 0) {
return EXIT_CODE_NORMAL;
}
}
//等待任务
usleep($sleepTime);
}
这里使用 LPOP 命令获取任务,所以使用了 while 结构,并且无任务时需要等待,可以用阻塞命令 BLPOP 来改进。
由于 Worker 需要常驻内存运行,难免会出现异常结束的情况(也有主动 Exit), 所以需要保持 Worker 一直处于运行状态。我们使用进程管理工具 Supervisor 来监控和管理 Worker 任务,当任务队列出现堆积时,增加 Worker 数量即可。Supervisor 的监控后台如下:
保障方案
资源CDN缓存
由于本次活动力度较大,静态页面占流量的很大一部分,所以静态页面在发布时都会放置一份在 CDN 上,这样回源的流量就很小了。
降级措施
尽管做了很多准备,还是无法确保万无一失,我们在每个关键节点都增加了开关,一但出现异常,可以通过配置中心人工介入做降级处理。
原文来自微信公众号:后端搬运工。
原文地址:https://mp.weixin.qq.com/s/VG_Wcxte8avnXzn4bPXiGA
欢迎大家多关注!
跟党相关的英文单词和表述
单词本 • zkbhj 发表了文章 • 0 个评论 • 2448 次浏览 • 2021-07-01 11:16
带大家看一下有关我党的英文表达吧!the Communist Party of China (CPC)
中国共产党
members of the Communist Party of China
党员
Chinese working class
中国工人阶级
Party Constitution
中国共产党章程
the Communist Youth League of China
中国共产主义青年团
vanguard
先锋队
Chinese People's Liberation Army
中国人民解放军
Party discipline
党的纪律
applicant for Party membership
申请入党者
probationary Party member
预备党员
take an admission oath
入党宣誓
The oath reads:
It is my will to join the Communist Party of China, uphold the Party's program, observe the provisions of the Party Constitution, fulfill a Party member's duties, carry out the Party's decisions, strictly observe Party discipline, guard Party secrets, be loyal to the Party, work hard, fight for communism throughout my life, be ready at all times to sacrifice my all for the Party and the people, and never betray the Party.
誓词如下:
我志愿加入中国共产党,拥护党的纲领,遵守党的章程,履行党员义务,执行党的决定,严守党的纪律,保守党的秘密,对党忠诚,积极工作,为共产主义奋斗终身,随时准备为党和人民牺牲一切,永不叛党。
full Party member
正式党员
pay membership
交党费
Party branch
党支部
Party cell
党小组
Party cadres
党员领导干部
democratic meetings
民主生活会
a Party standing of ... years
党龄
Party Emblem and Flag
党徽党旗
The emblem of the Communist Party of China is a design of sickle and hammer.
中国共产党党徽为镰刀和锤头组成的图案。
The flag of the Communist Party of China is a red flag highlighted by a golden Party emblem on it.
中国共产党党旗为旗面缀有金黄色党徽图案的红旗。 查看全部
带大家看一下有关我党的英文表达吧!the Communist Party of China (CPC)
中国共产党
members of the Communist Party of China
党员
Chinese working class
中国工人阶级
Party Constitution
中国共产党章程
the Communist Youth League of China
中国共产主义青年团
vanguard
先锋队
Chinese People's Liberation Army
中国人民解放军
Party discipline
党的纪律
applicant for Party membership
申请入党者
probationary Party member
预备党员
take an admission oath
入党宣誓
The oath reads:
It is my will to join the Communist Party of China, uphold the Party's program, observe the provisions of the Party Constitution, fulfill a Party member's duties, carry out the Party's decisions, strictly observe Party discipline, guard Party secrets, be loyal to the Party, work hard, fight for communism throughout my life, be ready at all times to sacrifice my all for the Party and the people, and never betray the Party.
誓词如下:
我志愿加入中国共产党,拥护党的纲领,遵守党的章程,履行党员义务,执行党的决定,严守党的纪律,保守党的秘密,对党忠诚,积极工作,为共产主义奋斗终身,随时准备为党和人民牺牲一切,永不叛党。
full Party member
正式党员
pay membership
交党费
Party branch
党支部
Party cell
党小组
Party cadres
党员领导干部
democratic meetings
民主生活会
a Party standing of ... years
党龄
Party Emblem and Flag
党徽党旗
The emblem of the Communist Party of China is a design of sickle and hammer.
中国共产党党徽为镰刀和锤头组成的图案。
The flag of the Communist Party of China is a red flag highlighted by a golden Party emblem on it.
中国共产党党旗为旗面缀有金黄色党徽图案的红旗。
什么是RCEP?对我们普通人来讲有什么影响?
专业名词 • zkbhj 发表了文章 • 0 个评论 • 3071 次浏览 • 2020-11-17 16:44
RCEP,英文全称 Regional Comprehensive Economic Partnership ,中文全称 区域全面经济伙伴关系协定。是2012年由东盟发起,历时八年,成员包括中国、日本、韩国、澳大利亚、新西兰和东盟十国共15方而制定的协定。东盟十国分别是:印度尼西亚、马来西亚、菲律宾、泰国、新加坡、文莱、柬埔寨、老挝、缅甸、越南。
2020年11月15日,区域全面经济伙伴关系协定签署仪式以视频方式进行,15个RCEP成员国经贸部长将在仪式上正式签署该协定。标志着当前世界上人口最多、经贸规模最大、最具发展潜力的自由贸易区正式启航。
协议共含20个章节,分为四大板块,包括货物贸易协定、投资协定、21世纪新议题和争端解决机制。
原本应该有16国,包括印度在内,但是最后印度2019年因“有重要问题尚未得到解决”而没有加入协定。
二、意义
在疫情肆虐、世界经济严重衰退、国际贸易投资萎缩、保护主义单边主义加剧的特殊背景下,各方能够就RCEP达成共识,宣告了多边主义和自由贸易的胜利,将有力提振各方对经济增长的信心。将为区域和全球经济增长注入强劲动力。
世界正面临百年未有之大变局,RCEP的达成为亚太自贸区(FTAAP)进程提供了实现路径,进一步提升亚太地区今后在全球发展格局中的分量。
三、全球经济格局现状
在RCEP签署之前,全球从洲际合作角度,最大的三个自贸区为北美自贸区(USMCA),欧盟(EU)和中国-东盟自贸区(CAFTA)。RCEP的诞生意味着全球最大自贸区形成,全球贸易格局正式演化为北美、欧盟、亚洲三足鼎立。
四、对普通人来讲,有哪些影响
RCEP协议的签署,就意味着在15国之内,商品流动、技术流动、服务流动、资本流动,包括人员跨境流动都会更加流畅。将会有超九成商品或纳入零关税范围,会极大地降低各成员国内流通商品的销售价格。当然更顺畅的贸易往来,也会带动更多的就业和创业机会。
但这些影响实际发生也需要等到2年之后,因为协议签订之后2年内,各个国家需要完成批准程序,协议才正式生效。 查看全部
RCEP,英文全称 Regional Comprehensive Economic Partnership ,中文全称 区域全面经济伙伴关系协定。是2012年由东盟发起,历时八年,成员包括中国、日本、韩国、澳大利亚、新西兰和东盟十国共15方而制定的协定。东盟十国分别是:印度尼西亚、马来西亚、菲律宾、泰国、新加坡、文莱、柬埔寨、老挝、缅甸、越南。
2020年11月15日,区域全面经济伙伴关系协定签署仪式以视频方式进行,15个RCEP成员国经贸部长将在仪式上正式签署该协定。标志着当前世界上人口最多、经贸规模最大、最具发展潜力的自由贸易区正式启航。
协议共含20个章节,分为四大板块,包括货物贸易协定、投资协定、21世纪新议题和争端解决机制。
原本应该有16国,包括印度在内,但是最后印度2019年因“有重要问题尚未得到解决”而没有加入协定。
二、意义
在疫情肆虐、世界经济严重衰退、国际贸易投资萎缩、保护主义单边主义加剧的特殊背景下,各方能够就RCEP达成共识,宣告了多边主义和自由贸易的胜利,将有力提振各方对经济增长的信心。将为区域和全球经济增长注入强劲动力。
世界正面临百年未有之大变局,RCEP的达成为亚太自贸区(FTAAP)进程提供了实现路径,进一步提升亚太地区今后在全球发展格局中的分量。
三、全球经济格局现状
在RCEP签署之前,全球从洲际合作角度,最大的三个自贸区为北美自贸区(USMCA),欧盟(EU)和中国-东盟自贸区(CAFTA)。RCEP的诞生意味着全球最大自贸区形成,全球贸易格局正式演化为北美、欧盟、亚洲三足鼎立。
四、对普通人来讲,有哪些影响
RCEP协议的签署,就意味着在15国之内,商品流动、技术流动、服务流动、资本流动,包括人员跨境流动都会更加流畅。将会有超九成商品或纳入零关税范围,会极大地降低各成员国内流通商品的销售价格。当然更顺畅的贸易往来,也会带动更多的就业和创业机会。
但这些影响实际发生也需要等到2年之后,因为协议签订之后2年内,各个国家需要完成批准程序,协议才正式生效。
#面试题集锦#实现单链表反转
面试 • zkbhj 发表了文章 • 0 个评论 • 2862 次浏览 • 2020-08-14 17:16
示例:输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
结构体定义struct ListNode {
int val;
struct ListNode *next;
};
思路一
先对原链表做头删操作,再对新链表做头插
定义一个新head头指针,标记为newHead,将它初始为NULL,并非指向NULL,最后我们选择返回这个newHead指针作为新链表的头指针。
定义一个结点node作为"临时中转站",初始化与否并无大影响。进行循环遍历链表各个结点,判定head指针是否为空,即是否到达了原链表的结尾。如果不为空,说明还没有到达尾部。如果程序第一次运行就没有进入循环,说明传入了一个空链表,此时返回newHead新链表的头指针,同样返回NULL,这样处理也是合理的。以下开始逆序链表逻辑:在当前指针不为NULL时,先对原链表做头删操作,再对新链表做头插操作。即使用循环进行操作:让node指针指向传入函数链表的头指针head,两指针指向保持相同。然后让head指针指向它的next结点,此时旧链表已经完成了头删操作。第一个结点已经被"切割"下来。接下来要做的就是对新链表进行头插操作,使结点放入新链表。让node指针的next下一个结点指向新链表的头指针newHead,完成结点的链接,即头插入新的链表中。然后更新newHead指向为新链表的头结点。进行下一次循环。最终head指针指向了原链表的结尾,即为NULL,退出循环,此时新链表已经反转完毕,情况如图:最终返回新链表头指针newHead即可。
struct ListNode *reverseList(struct ListNode* head) {
struct ListNode *newHead = NULL;
struct ListNode *node;
while (head != NULL) {
//1. 对之前的链表做头删
node = head;
head = head->next;
//2. 对新链表做头插
node->next = newHead;
newHead = node;
}
return newHead;
}
思路二
利用选择语句,找到空链表的情况,此情况返回NULL空指针,因为空链表不能反转,或者说反转之后还是一个空链表,返回空。利用三个指针p0"前指针"、p1“当前指针”、p2"后指针"来分批处理链表元素,p0置为NULL,它将作为链表的尾结点向前推进处理,p1指向旧链表的头指针head,p2指向旧链表的头指针的next结点。开始遍历链表,循环判定因子为p1,当它为空时到达链表尾部跳出循环。否则在表中执行循环内逻辑:将p1指向的当前结点的下一个结点指向p0,即前一个结点。此时p0为NULL,那么p1的下一个结点就为空了,它现在是最后一个结点。然后将p0指针指向p1,将p1指针指向p2,注意这两步不可以调换顺序,否则正确向后挪移一位。此时完成了三个指针的一轮更迭。判定p2指针是否为空,如果为空说明此时p2到达了链表结尾,当前指针p1的指向为最后一个结点,它的next即为空。如果不为空,将p2更新到下一个结点,进行下一次循环。下一次进行循环时,就会把截断结点链接到新链表的头部,同时更新三个指针。继续循环。循环终止条件为:p1指向了链表尾部的NULL,此时p1的前指针p0即指向了反转后的链表,它就是新链表的head头指针。此时返回p0即可。
struct ListNode *reverseList(struct ListNode* head) {
if (head == NULL) {
return NULL;
}
struct ListNode *p0 = NULL;
struct ListNode *p1 = head;
struct ListNode *p2 = head->next;
while (p1 != NULL) {
p1->next = p0;
p0 = p1;
p1 = p2;
if (p2 != NULL) {
p2 = p2->next;
}
}
return p0;
}
参考文档:
https://blog.csdn.net/qq_42351880/article/details/88637387
查看全部
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
结构体定义
struct ListNode {
int val;
struct ListNode *next;
};
思路一
先对原链表做头删操作,再对新链表做头插
- 定义一个新head头指针,标记为newHead,将它初始为NULL,并非指向NULL,最后我们选择返回这个newHead指针作为新链表的头指针。
- 定义一个结点node作为"临时中转站",初始化与否并无大影响。
- 进行循环遍历链表各个结点,判定head指针是否为空,即是否到达了原链表的结尾。如果不为空,说明还没有到达尾部。如果程序第一次运行就没有进入循环,说明传入了一个空链表,此时返回newHead新链表的头指针,同样返回NULL,这样处理也是合理的。
- 以下开始逆序链表逻辑:在当前指针不为NULL时,先对原链表做头删操作,再对新链表做头插操作。即使用循环进行操作:
- 让node指针指向传入函数链表的头指针head,两指针指向保持相同。
- 然后让head指针指向它的next结点,此时旧链表已经完成了头删操作。第一个结点已经被"切割"下来。接下来要做的就是对新链表进行头插操作,使结点放入新链表。
- 让node指针的next下一个结点指向新链表的头指针newHead,完成结点的链接,即头插入新的链表中。然后更新newHead指向为新链表的头结点。进行下一次循环。
- 最终head指针指向了原链表的结尾,即为NULL,退出循环,此时新链表已经反转完毕,情况如图:
- 最终返回新链表头指针newHead即可。
struct ListNode *reverseList(struct ListNode* head) {
struct ListNode *newHead = NULL;
struct ListNode *node;
while (head != NULL) {
//1. 对之前的链表做头删
node = head;
head = head->next;
//2. 对新链表做头插
node->next = newHead;
newHead = node;
}
return newHead;
}
思路二
- 利用选择语句,找到空链表的情况,此情况返回NULL空指针,因为空链表不能反转,或者说反转之后还是一个空链表,返回空。
- 利用三个指针p0"前指针"、p1“当前指针”、p2"后指针"来分批处理链表元素,p0置为NULL,它将作为链表的尾结点向前推进处理,p1指向旧链表的头指针head,p2指向旧链表的头指针的next结点。
- 开始遍历链表,循环判定因子为p1,当它为空时到达链表尾部跳出循环。否则在表中执行循环内逻辑:将p1指向的当前结点的下一个结点指向p0,即前一个结点。此时p0为NULL,那么p1的下一个结点就为空了,它现在是最后一个结点。
- 然后将p0指针指向p1,将p1指针指向p2,注意这两步不可以调换顺序,否则正确向后挪移一位。此时完成了三个指针的一轮更迭。
- 判定p2指针是否为空,如果为空说明此时p2到达了链表结尾,当前指针p1的指向为最后一个结点,它的next即为空。如果不为空,将p2更新到下一个结点,进行下一次循环。
- 下一次进行循环时,就会把截断结点链接到新链表的头部,同时更新三个指针。继续循环。
- 循环终止条件为:p1指向了链表尾部的NULL,此时p1的前指针p0即指向了反转后的链表,它就是新链表的head头指针。此时返回p0即可。
struct ListNode *reverseList(struct ListNode* head) {
if (head == NULL) {
return NULL;
}
struct ListNode *p0 = NULL;
struct ListNode *p1 = head;
struct ListNode *p2 = head->next;
while (p1 != NULL) {
p1->next = p0;
p0 = p1;
p1 = p2;
if (p2 != NULL) {
p2 = p2->next;
}
}
return p0;
}
参考文档:
https://blog.csdn.net/qq_42351880/article/details/88637387
#每日精进#2020年8月6日
总结 • zkbhj 发表了文章 • 0 个评论 • 2781 次浏览 • 2020-08-06 09:31
第二章 信息的表示和处理
表示代码int sum(int x, int y) {
return x + y;
}当我们在不同的机器上编译上面的程序得到的机器代码都不尽相同:
Linux32 55 89 e5 8b 45 0c 03 45 08 c9 c3
Windows 55 89 e5 8b 45 0c 03 45 08 5d c3
Sun 81 c3 e0 08 90 03 00 09
因此,二进制代码是不兼容的,无法在不同机器之间移植。
这得到一个计算机系统的基本概念:从机器的角度来看,程序仅仅只是字节序列,机器没有关于原始程序的任何信息。
布尔代数简介
计算机的核心都是围绕1和0来演化的。对于0和1的起源,要追溯到1850年前后乔治·布尔的工作,所以这个也叫布尔代数。即通过将逻辑值TRUE和FALSE编码为二进制1和0设计出的一种代数,以研究逻辑推理的基本原则。
上面分别列出了~(NOT)、&(AND)、|(OR)和^(EXCLUSIVE-OR)四种基本运算。
后来创立信息论领域的Claude Shannon首先建立了布尔代数和数字逻辑之间的联系。
将上述基础的布尔运算扩展到位向量运算。位向量就是固定长度为w、由0和1组成的串。
假设 w=4,a=[0110],b=[1100]。那么四种运算 a&b、a|b、a^b、~b 结果分别如下:
布尔运算&对|满足分配率:a&(b|c) = (a&b)|(a&c);反过来,|也满足对&的分配率,即:a|(b&c) = (a|b) & (a|c)。
位向量的一个有用应用就是表示有限集合,即用位向量来给集合进行编码。
C语言的一个有用特性就是支持按位布尔运算。|、&、~、^这些运算可以用到任何“整型”的数据类型上。void inplace_swap(int *x, int *y){
*y = *x ^ *y;
*x = *x ^ *y;
*y = *x ^ *y;
}
上面这段代码,就是利用了两个事实来实现*x和*y所指向的变量值进行了交换操作。两个事实分别是:
异或运算是可交换和可结合的;
对于任意的a, a ^ a = 0;
所以上述程序的计算过程如下:
初始:*x = a *y = b
第一步:*x = a *y = a ^ b
第二步:*x = a ^ (a ^ b) = (a ^ a) ^ b = b *y = a ^ b
第三部:*x = b *y = b ^ ( a ^ b) = (b ^ b) ^ a = a
但是注意,这种方式和通常的交换两个数值的技术不一样,当移动一个值时,我们不需要第三个位置来临时存放另外一个值。这种交换方式并没有性能上的优势,它仅仅是一个智力游戏!
位级运算常见的用法就是实现掩码运算:掩码是一个位模式,表示从一个字中选出的位的集合。
比如对于掩码0xFF(最低的8位都是1)表示一个字的低位字节。x&0xFF会得到一个由x的最低有效字节组成的值。
【英文中几点钟的说法o'clock是什么的缩写?】
o'clock = of the clock.
在14世纪以前,人类还没有发明出来时钟,都是通过一些其他途径来获取和感知时间,比如日晷、沙漏等。直到 14 世纪,现代意义上的时钟雏形才得以发明。当时的时钟会自己报时“说出”:It's 7 of the clock!后来,随着时钟的普及和大众化,人们开始将 of 的 f 和 the 一带而过,简略地读成了 o'clock。
https://ask.zkbhj.com/?/article/370 查看全部
【早读:《深入理解计算机系统》】
第二章 信息的表示和处理
表示代码
int sum(int x, int y) {当我们在不同的机器上编译上面的程序得到的机器代码都不尽相同:
return x + y;
}
Linux32 55 89 e5 8b 45 0c 03 45 08 c9 c3
Windows 55 89 e5 8b 45 0c 03 45 08 5d c3
Sun 81 c3 e0 08 90 03 00 09
因此,二进制代码是不兼容的,无法在不同机器之间移植。
这得到一个计算机系统的基本概念:从机器的角度来看,程序仅仅只是字节序列,机器没有关于原始程序的任何信息。
布尔代数简介
计算机的核心都是围绕1和0来演化的。对于0和1的起源,要追溯到1850年前后乔治·布尔的工作,所以这个也叫布尔代数。即通过将逻辑值TRUE和FALSE编码为二进制1和0设计出的一种代数,以研究逻辑推理的基本原则。
上面分别列出了~(NOT)、&(AND)、|(OR)和^(EXCLUSIVE-OR)四种基本运算。
后来创立信息论领域的Claude Shannon首先建立了布尔代数和数字逻辑之间的联系。
将上述基础的布尔运算扩展到位向量运算。位向量就是固定长度为w、由0和1组成的串。
假设 w=4,a=[0110],b=[1100]。那么四种运算 a&b、a|b、a^b、~b 结果分别如下:
布尔运算&对|满足分配率:a&(b|c) = (a&b)|(a&c);反过来,|也满足对&的分配率,即:a|(b&c) = (a|b) & (a|c)。
位向量的一个有用应用就是表示有限集合,即用位向量来给集合进行编码。
C语言的一个有用特性就是支持按位布尔运算。|、&、~、^这些运算可以用到任何“整型”的数据类型上。
void inplace_swap(int *x, int *y){
*y = *x ^ *y;
*x = *x ^ *y;
*y = *x ^ *y;
}
上面这段代码,就是利用了两个事实来实现*x和*y所指向的变量值进行了交换操作。两个事实分别是:
异或运算是可交换和可结合的;
对于任意的a, a ^ a = 0;
所以上述程序的计算过程如下:
初始:*x = a *y = b
第一步:*x = a *y = a ^ b
第二步:*x = a ^ (a ^ b) = (a ^ a) ^ b = b *y = a ^ b
第三部:*x = b *y = b ^ ( a ^ b) = (b ^ b) ^ a = a
但是注意,这种方式和通常的交换两个数值的技术不一样,当移动一个值时,我们不需要第三个位置来临时存放另外一个值。这种交换方式并没有性能上的优势,它仅仅是一个智力游戏!
位级运算常见的用法就是实现掩码运算:掩码是一个位模式,表示从一个字中选出的位的集合。
比如对于掩码0xFF(最低的8位都是1)表示一个字的低位字节。x&0xFF会得到一个由x的最低有效字节组成的值。
【英文中几点钟的说法o'clock是什么的缩写?】
o'clock = of the clock.
在14世纪以前,人类还没有发明出来时钟,都是通过一些其他途径来获取和感知时间,比如日晷、沙漏等。直到 14 世纪,现代意义上的时钟雏形才得以发明。当时的时钟会自己报时“说出”:It's 7 of the clock!后来,随着时钟的普及和大众化,人们开始将 of 的 f 和 the 一带而过,简略地读成了 o'clock。
https://ask.zkbhj.com/?/article/370
#每日精进#2020年8月5日
总结 • zkbhj 发表了文章 • 0 个评论 • 2988 次浏览 • 2020-08-05 11:20
第二章 信息的表示和处理
寻址和字节顺序
在几乎所有机器上,多字节对象被存储为连续的字节序列,对象的地址为最小的地址。
排列表示一个对象的字节有两个通用的规则:
小端法:在内存中按照从最低有效字节到最高有效字节的顺序存储对象;
大端法:在内存中按照从最高有效字节到最低有效字节的顺序存储对象;一旦选择了特定的操作系统,那么字节顺序也就固定下来。Android和IOS只能够运行于小端模式下。
两种方式没有谁好谁坏之分,对于那种字节排序的选择都是任意的。
大小端的说法来自于Jonathan Swift的《格利弗游记》一书,其中交战的两个派别无法就打开一个半熟的鸡蛋应该从哪一端打开达成一致意见。
不同的端模式下,会有以下影响:
1、通过网络在不同端模式的机器间传递数据时,发送和接收时,需要转换网络标准;
2、在检查机器级程序时,对数据的解读方式;
3、在编写规避正常的类型系统的程序时。#include <stdio.h>
typedef unsigned char *byte_pointer;
//typedef char *byte_pointer;
void show_bytes(byte_pointer start, size_t len) {
size_t i;
for (i = 0; i < len; i++)
printf("%p\t0x%.2x\n", &start, start);
printf("\n");
}
void show_int(int x) {
show_bytes((byte_pointer) &x, sizeof(int));
}
void show_float(float x) {
show_bytes((byte_pointer) &x, sizeof(float));
}
void show_pointer(void *x) {
show_bytes((byte_pointer) &x, sizeof(void *));
}
在这段程序中,“byte_pointer start”告诉编译器,应该把这个指针看成指向一个字节序列,而不是指向一个原始数据类型的对象。然后,这个指针会被看成是对象使用的最低字节地址。
这种强制类型转换不会改变真实的指针,它们只是告诉编译器以新的数据类型来看待被指向的数据。
使用ASCII码作为字符码的任何系统上都是将得到相同的结果,与字节顺序和字节大小规则无关。因而,文本数据比二进制数据具有更强的平台独立性。
【HTML页面里怎么实现代码包含?】
https://ask.zkbhj.com/?/question/387
【Yii2框架中如何区分不同的场景指定赋值字段和检验规则】
场景(scenario)
分析上面问题,会发现关键点是批量赋值(massive assignment)和数据校验(validate)两个方法。如果对不同的场景指定赋值字段和检验规则,问题就迎刃而解。
Yii中的scenario有 安全属性 和 活跃属性 两个概念。安全属性用在批量赋值的load方法,只有安全属性才能被赋值;活跃属性用在规则校验的validate方法,在活跃属性集中并且定义了校验规则的属性才会被校验。活跃属性和安全属性的关系是,安全属性是活跃属性的子集。
\yii\base\Model类定义了默认场景:SCENARIO_DEFAULT(值为default)。默认场景下,出现在rules方法中的属性既是活跃属性,又是安全属性(这句话基本正确,看后续解释)。为不同场景指定活跃属性、安全属性以及校验器,可以通过覆盖senarios或rules两个方法实现(几乎每个Model类都会重写rules方法,senarios用得少)。
rules
先看rules方法。默认的属性加校验器定义方式,让每个属性既是安全属性,也是活跃属性。如果想让某个属性不是安全属性(不能通过load批量赋值),在属性名前加感叹号!即可。例如student中的user_id字段:public function rules()
{
return [
["!user_od", "required"],
["!user_id", "integer"],
["!user_od", "unique"],
// other rules
];
}user_id是活跃属性,在写入数据库时会被校验。但它不是安全属性,不能通过load方法进行赋值,解决了安全隐患。
再看rules方法按场景区分校验器规则的做法:定义校验器时on属性指定规则在哪些场景下生效,except属性则排除一些场景(如果不指定on和except,规则对所有场景生效)。例如:public function rules()
{
return [
["password", "string", "length" => [8, 16], "on" => ["signup"]], // 仅在signup场景时才被验证
["status", "integer", "except" => ["signup"], // 除了signup场景,其他情况都校验
// other rules
];
}在原来基础上新增感叹号和on/except属性,非常简便的就定义了非安全属性以及分场景指定校验规则。
scenarios
另外一种更清晰定义安全属性和活跃属性的做法是重写scenarios方法。scenarios方法返回一个数组,数组的键是场景名称,值是活跃属性集合(包饭安全属性)。例如student表的可能实现如下:public function scenarios()
{
return [
self::SCENARIO_DEFAULT => ["!user_id", "grade", "class", xxxx],
"update" => ["grade", "class", xxxx],
];
}默认情形下(学生报名),年级、班级这些信息是安全属性,但user_id不是,只能在程序内部赋值,并在插入数据时被校验;在修改信息时,user_id不是活跃属性,既不能被批量赋值,也不需要校验(事实上它不应该改变)。
scenarios方法只能定义活跃属性和安全属性,无法定义校验规则,需要和rules配合使用。
https://www.cnblogs.com/yangxunwu1992/p/6669380.html
【关于未来几年国内经济形势的分析总结】】
疫情发生以来,国内外经济形势发生了重大变化。国际局势也从全球化走向了逆全球化的道路,美国通过各种手段(贸易战、“中国病毒”论、打压华为、打压Tik Tok、关闭领事馆、干涉中国内政等)不断打压中国,各大企业也在不断将企业撤出中国。
最近最热门的经济词汇就是经济内循环,意思是说,以后要以国内市场为主,国内生产出来的东西,主要在自己国内消化掉,重新转化为生产力。也就是走内需拉动经济增长的路子。这是在疫情发生以及国内外形式发生如此变化之后的无奈之举。
我们过去拉动经济的三驾马车是外贸,基建和房地产,内需一向很薄弱,这几年内需份额有所提高,但还不足以成为拉动经济增长的主要动力。
最主要体现在居民收入不足,杠杆过高,中产被绑在房地产的战车上面等。
从微观层面看,中国消费结构呈现两边高中间低的M字型结构(健康的结构应该是相反的,两边低中间高,中产成为消费的主力)。所以中国目前的情况就是有钱人并不会受到高房价的影响而降低消费,反而还因此提高了消费能力,还在消费升级。然后相对经济收入低的人群消费能力本就低,地摊经济很火就说明这个问题。中产,由于购房等压力,从中端消费跌落到低端消费。
所以,国内要实现以经济内循环为主,国际循环为辅的国内国际双循环的新发展格局,有以下几种办法:
1、提高居民购买力
主要就是降房价和提高居民收入,提高居民收入很难,现在经济太差,没有需求就没有工作岗位,居民收入不下降已经很不错,但房价是有可能缓慢下降的
2、发展房地产。
过去十几年就是这么干的,但现在房价太高,已经到了伤害经济的底部,已经不能继续走拉抬房价发展经济的老路。
3、让股市走出慢牛。
目前看也相对可行,上层也一直在强调要通过资本市场服务实体经济,这也是我一向看好A股走牛的重要原因。
4、发展高科技和人民币国际化。
讲通俗一点,就是:
经济内循环其实就是要过苦日子的另一个代名词。也可以简单理解为好好干活,但不要想着能挣很多钱。在未来很长一段时间里面,物价会相对比较便宜。
https://zhuanlan.zhihu.com/p/165347415
【Go语言核心36讲:第12节 使用函数的正确姿势(1)】
在 Go 语言中,函数可是一等的(first-class)公民,函数类型也是一等的数据类型(函数类型属于引用类型,它的零值为nil)。
这意味着函数不但可以用于封装代码、分割功能、解耦逻辑,还可以化身为普通的值,在其他函数间传递、赋予变量、做类型判断和转换等。“函数是一等的公民”是函数式编程(functional programming)的重要特征。Go 语言在语言层面支持了函数式编程。
package main
import "fmt"
type Printer func(contents string) (n int, err error)
func printToStd(contents string) (bytesNum int, err error) {
return fmt.Println(contents)
}
func main() {
var p Printer
p = printToStd
p("something")
}函数的签名其实就是函数的参数列表和结果列表的统称,它定义了可用来鉴别不同函数的那些特征,同时也定义了我们与函数交互的方式。
各个参数和结果的名称不能算作函数签名的一部分,对于结果声明,名字也可以没有。且函数的名称也不算函数签名的一部分,只是个标识符而已。
高阶函数
1. 接受其他的函数作为参数传入;
2. 把其他的函数作为结果返回。
只要满足了其中任意一个特点,我们就可以说这个函数是一个高阶函数。
卫述语句
卫述语句是指被用来检查关键的先决条件的合法性,并在检查未通过的情况下立即终止当前代码块执行的语句。在 Go 语言中,if 语句常被作为卫述语句。
if op == nil {
return 0, errors.New("invalid operation")
} 查看全部
第二章 信息的表示和处理
寻址和字节顺序
在几乎所有机器上,多字节对象被存储为连续的字节序列,对象的地址为最小的地址。
排列表示一个对象的字节有两个通用的规则:
小端法:在内存中按照从最低有效字节到最高有效字节的顺序存储对象;
大端法:在内存中按照从最高有效字节到最低有效字节的顺序存储对象;一旦选择了特定的操作系统,那么字节顺序也就固定下来。Android和IOS只能够运行于小端模式下。
两种方式没有谁好谁坏之分,对于那种字节排序的选择都是任意的。
大小端的说法来自于Jonathan Swift的《格利弗游记》一书,其中交战的两个派别无法就打开一个半熟的鸡蛋应该从哪一端打开达成一致意见。
不同的端模式下,会有以下影响:
1、通过网络在不同端模式的机器间传递数据时,发送和接收时,需要转换网络标准;
2、在检查机器级程序时,对数据的解读方式;
3、在编写规避正常的类型系统的程序时。
#include <stdio.h>
typedef unsigned char *byte_pointer;
//typedef char *byte_pointer;
void show_bytes(byte_pointer start, size_t len) {
size_t i;
for (i = 0; i < len; i++)
printf("%p\t0x%.2x\n", &start, start);
printf("\n");
}
void show_int(int x) {
show_bytes((byte_pointer) &x, sizeof(int));
}
void show_float(float x) {
show_bytes((byte_pointer) &x, sizeof(float));
}
void show_pointer(void *x) {
show_bytes((byte_pointer) &x, sizeof(void *));
}
在这段程序中,“byte_pointer start”告诉编译器,应该把这个指针看成指向一个字节序列,而不是指向一个原始数据类型的对象。然后,这个指针会被看成是对象使用的最低字节地址。
这种强制类型转换不会改变真实的指针,它们只是告诉编译器以新的数据类型来看待被指向的数据。
使用ASCII码作为字符码的任何系统上都是将得到相同的结果,与字节顺序和字节大小规则无关。因而,文本数据比二进制数据具有更强的平台独立性。
【HTML页面里怎么实现代码包含?】
https://ask.zkbhj.com/?/question/387
【Yii2框架中如何区分不同的场景指定赋值字段和检验规则】
场景(scenario)
分析上面问题,会发现关键点是批量赋值(massive assignment)和数据校验(validate)两个方法。如果对不同的场景指定赋值字段和检验规则,问题就迎刃而解。
Yii中的scenario有 安全属性 和 活跃属性 两个概念。安全属性用在批量赋值的load方法,只有安全属性才能被赋值;活跃属性用在规则校验的validate方法,在活跃属性集中并且定义了校验规则的属性才会被校验。活跃属性和安全属性的关系是,安全属性是活跃属性的子集。
\yii\base\Model类定义了默认场景:SCENARIO_DEFAULT(值为default)。默认场景下,出现在rules方法中的属性既是活跃属性,又是安全属性(这句话基本正确,看后续解释)。为不同场景指定活跃属性、安全属性以及校验器,可以通过覆盖senarios或rules两个方法实现(几乎每个Model类都会重写rules方法,senarios用得少)。
rules
先看rules方法。默认的属性加校验器定义方式,让每个属性既是安全属性,也是活跃属性。如果想让某个属性不是安全属性(不能通过load批量赋值),在属性名前加感叹号!即可。例如student中的user_id字段:
public function rules()user_id是活跃属性,在写入数据库时会被校验。但它不是安全属性,不能通过load方法进行赋值,解决了安全隐患。
{
return [
["!user_od", "required"],
["!user_id", "integer"],
["!user_od", "unique"],
// other rules
];
}
再看rules方法按场景区分校验器规则的做法:定义校验器时on属性指定规则在哪些场景下生效,except属性则排除一些场景(如果不指定on和except,规则对所有场景生效)。例如:
public function rules()在原来基础上新增感叹号和on/except属性,非常简便的就定义了非安全属性以及分场景指定校验规则。
{
return [
["password", "string", "length" => [8, 16], "on" => ["signup"]], // 仅在signup场景时才被验证
["status", "integer", "except" => ["signup"], // 除了signup场景,其他情况都校验
// other rules
];
}
scenarios
另外一种更清晰定义安全属性和活跃属性的做法是重写scenarios方法。scenarios方法返回一个数组,数组的键是场景名称,值是活跃属性集合(包饭安全属性)。例如student表的可能实现如下:
public function scenarios()默认情形下(学生报名),年级、班级这些信息是安全属性,但user_id不是,只能在程序内部赋值,并在插入数据时被校验;在修改信息时,user_id不是活跃属性,既不能被批量赋值,也不需要校验(事实上它不应该改变)。
{
return [
self::SCENARIO_DEFAULT => ["!user_id", "grade", "class", xxxx],
"update" => ["grade", "class", xxxx],
];
}
scenarios方法只能定义活跃属性和安全属性,无法定义校验规则,需要和rules配合使用。
https://www.cnblogs.com/yangxunwu1992/p/6669380.html
【关于未来几年国内经济形势的分析总结】】
疫情发生以来,国内外经济形势发生了重大变化。国际局势也从全球化走向了逆全球化的道路,美国通过各种手段(贸易战、“中国病毒”论、打压华为、打压Tik Tok、关闭领事馆、干涉中国内政等)不断打压中国,各大企业也在不断将企业撤出中国。
最近最热门的经济词汇就是经济内循环,意思是说,以后要以国内市场为主,国内生产出来的东西,主要在自己国内消化掉,重新转化为生产力。也就是走内需拉动经济增长的路子。这是在疫情发生以及国内外形式发生如此变化之后的无奈之举。
我们过去拉动经济的三驾马车是外贸,基建和房地产,内需一向很薄弱,这几年内需份额有所提高,但还不足以成为拉动经济增长的主要动力。
最主要体现在居民收入不足,杠杆过高,中产被绑在房地产的战车上面等。
从微观层面看,中国消费结构呈现两边高中间低的M字型结构(健康的结构应该是相反的,两边低中间高,中产成为消费的主力)。所以中国目前的情况就是有钱人并不会受到高房价的影响而降低消费,反而还因此提高了消费能力,还在消费升级。然后相对经济收入低的人群消费能力本就低,地摊经济很火就说明这个问题。中产,由于购房等压力,从中端消费跌落到低端消费。
所以,国内要实现以经济内循环为主,国际循环为辅的国内国际双循环的新发展格局,有以下几种办法:
1、提高居民购买力
主要就是降房价和提高居民收入,提高居民收入很难,现在经济太差,没有需求就没有工作岗位,居民收入不下降已经很不错,但房价是有可能缓慢下降的
2、发展房地产。
过去十几年就是这么干的,但现在房价太高,已经到了伤害经济的底部,已经不能继续走拉抬房价发展经济的老路。
3、让股市走出慢牛。
目前看也相对可行,上层也一直在强调要通过资本市场服务实体经济,这也是我一向看好A股走牛的重要原因。
4、发展高科技和人民币国际化。
讲通俗一点,就是:
经济内循环其实就是要过苦日子的另一个代名词。也可以简单理解为好好干活,但不要想着能挣很多钱。在未来很长一段时间里面,物价会相对比较便宜。
https://zhuanlan.zhihu.com/p/165347415
【Go语言核心36讲:第12节 使用函数的正确姿势(1)】
在 Go 语言中,函数可是一等的(first-class)公民,函数类型也是一等的数据类型(函数类型属于引用类型,它的零值为nil)。
这意味着函数不但可以用于封装代码、分割功能、解耦逻辑,还可以化身为普通的值,在其他函数间传递、赋予变量、做类型判断和转换等。“函数是一等的公民”是函数式编程(functional programming)的重要特征。Go 语言在语言层面支持了函数式编程。
函数的签名其实就是函数的参数列表和结果列表的统称,它定义了可用来鉴别不同函数的那些特征,同时也定义了我们与函数交互的方式。
package main
import "fmt"
type Printer func(contents string) (n int, err error)
func printToStd(contents string) (bytesNum int, err error) {
return fmt.Println(contents)
}
func main() {
var p Printer
p = printToStd
p("something")
}
各个参数和结果的名称不能算作函数签名的一部分,对于结果声明,名字也可以没有。且函数的名称也不算函数签名的一部分,只是个标识符而已。
高阶函数
1. 接受其他的函数作为参数传入;
2. 把其他的函数作为结果返回。
只要满足了其中任意一个特点,我们就可以说这个函数是一个高阶函数。
卫述语句
卫述语句是指被用来检查关键的先决条件的合法性,并在检查未通过的情况下立即终止当前代码块执行的语句。在 Go 语言中,if 语句常被作为卫述语句。
if op == nil {
return 0, errors.New("invalid operation")
}
#每日精进#2020年8月4日
总结 • zkbhj 发表了文章 • 0 个评论 • 2959 次浏览 • 2020-08-04 14:51
第二章 信息的表示和处理
大多数计算机使用8位的块,或字节,作为最小的可寻址内存单位。
机器级程序将内存视为一个非常大的字节数组,称为虚拟内存。
内存中每个字节由一个唯一的数字来标识,称为它的地址。所有可能的地址的集合就称为虚拟地址空间。
所以,这个虚拟地址空间只是一个展现给机器级程序的概念性映像。实际上,它将DRAM、闪存、磁盘存储器等和操作系统软件结合起来,封装了复杂性,为程序提供一个看上去统一的字节数组。
C语言中的一个指针的值,都是某个存储块的第一个字节的虚拟地址。每个程序对象可以简单地视为一个字节块,而程序本身就是一个字节序列。
十六进制表示法
由于二进制和十进制对于描述位模式来说都非常不方便:二进制太冗长,十进制和位模式的互相转化很麻烦,替代的方法就是引入16进制。
以0x或者0X开头,0~9、A~F,不区分大小写且大小写不敏感。
重要的是二进制、十进制和十六进制之间的互相转换方法,详细的可以进入凯冰科技知识共享中心搜索相关问题或文章查看。
对于x=2的n次方这个公式,转换十六进制,可以转化为n=i+4j,然后得到的十六进制就是:0x + 2的i次方 + j个0,比如512,是2的9次方,9=1+4*2,所以十六进制就是0x200。
字数据大小
每台计算机都有一个字长,指明指针数据的标称大小。字长决定虚拟地址空间的最大大小。32位字长限制的虚拟地址空间位4千兆字节(约4GB),而现在比较普及的64位字长的虚拟空间位16EB。
大多数64位机器也可以运行32位机器编译的程序,这是一种向后兼容。//该编译后的程序可以在32或64位机器上运行
linux> gcc -m32 prog.c
//该编译后,只能在64位机器上运行
linux> gcc -m64 prog.c我们将程序称为32位程序或64位程序,区别在于该程序是如何编译的,而不是其运行的机器类型。
ISO C99引入了确定大小的数据类型,int32_t 和int64_t,其数据大小是固定的,分别为4个字节和8个字节。使用确切大小的整数类型是程序员准确控制数据表示的最佳途径。
程序员应该力图使他们的程序可以在不同的机器和编译器上可移植,可移植的一方面就是说程序对不同数据类型的确切大小不敏感。
比如许多程序员假设一个声明为int类型的程序对象能被用来存储一个指针,这在大多数32位的机器上能够正常工作,但是在一台64位的机器上却会导致问题。所以,1980到2010年期间(32位机器是主流)编写的程序,之后64位机器陆续普及之后,迁移过来的程序就暴露出来许多隐藏的对字长的依赖性问题,导致错误。
【垂直行业如电商如何衡量搜索引擎的优劣】
在电商行业中,无论是2B还是2C,最终的业务目的就是交易成单,众所周知搜索服务旨在让消费者能够更快的定位到自己想要的产品。
一般电商搜索的核心是搜索精度和搜索广度,精度就是搜索的精确性,广度就是搜索结果的范围,其关键结果肯定是“为用户找到想要的商品”,但过于追求搜索的精确度就会导致出现搜索的结果比较少或结果为0的情况,用户搜不到商品势必会引发流失,因此在搜索服务里面还可以做的就是给用户提供一些相关性搜索结果。那么搜索做的好不好,其实就是在搜索精度和搜索广度二者之间做一个比较好的平衡点。
搜索过程中遇到的问题:
1.随机性发现的Bad case
2.KPI或者OKR考核
3.业务方诉求
核心指标
“搜索PV”:指访问搜索页面的次数;“搜索UV”:访问过搜索结果页的用户数;“无结果率”:空结果PV/搜索PV,无结果率越低,代表客户搜索需求解决情况越好;“TOP5 PV—CTR”:指该query search结果中,排在前五位的item有被点击的搜索PV/该query搜索PV该指标能一定程度反应排序效果;“人均搜索PV”:搜索PV/搜索UV;该指标的含义比较复杂,一方面人均pv大的话可能代表用户对搜索比较感兴趣,但另一方面人均pv大也可能代表搜索召回的结果较差,导致用户无法使用较少的点击找到满足需求的结果;“有点击搜索PV占比”:有点击搜索PV/搜索PV数;“PV-CTR”:搜索结果页item点击数/搜索PV数;“UV-CTR”:点击的uv / 曝光的uv;“Item-CTR”:搜索结果页item点击数/搜索结果页item总曝光PV数;
搜索技术等级分类
https://developer.aliyun.com/article/769492
查看全部
【早读:《深入理解计算机系统》】
第二章 信息的表示和处理
大多数计算机使用8位的块,或字节,作为最小的可寻址内存单位。
机器级程序将内存视为一个非常大的字节数组,称为虚拟内存。
内存中每个字节由一个唯一的数字来标识,称为它的地址。所有可能的地址的集合就称为虚拟地址空间。
所以,这个虚拟地址空间只是一个展现给机器级程序的概念性映像。实际上,它将DRAM、闪存、磁盘存储器等和操作系统软件结合起来,封装了复杂性,为程序提供一个看上去统一的字节数组。
C语言中的一个指针的值,都是某个存储块的第一个字节的虚拟地址。每个程序对象可以简单地视为一个字节块,而程序本身就是一个字节序列。
十六进制表示法
由于二进制和十进制对于描述位模式来说都非常不方便:二进制太冗长,十进制和位模式的互相转化很麻烦,替代的方法就是引入16进制。
以0x或者0X开头,0~9、A~F,不区分大小写且大小写不敏感。
重要的是二进制、十进制和十六进制之间的互相转换方法,详细的可以进入凯冰科技知识共享中心搜索相关问题或文章查看。
对于x=2的n次方这个公式,转换十六进制,可以转化为n=i+4j,然后得到的十六进制就是:0x + 2的i次方 + j个0,比如512,是2的9次方,9=1+4*2,所以十六进制就是0x200。
字数据大小
每台计算机都有一个字长,指明指针数据的标称大小。字长决定虚拟地址空间的最大大小。32位字长限制的虚拟地址空间位4千兆字节(约4GB),而现在比较普及的64位字长的虚拟空间位16EB。
大多数64位机器也可以运行32位机器编译的程序,这是一种向后兼容。
//该编译后的程序可以在32或64位机器上运行我们将程序称为32位程序或64位程序,区别在于该程序是如何编译的,而不是其运行的机器类型。
linux> gcc -m32 prog.c
//该编译后,只能在64位机器上运行
linux> gcc -m64 prog.c
ISO C99引入了确定大小的数据类型,int32_t 和int64_t,其数据大小是固定的,分别为4个字节和8个字节。使用确切大小的整数类型是程序员准确控制数据表示的最佳途径。
程序员应该力图使他们的程序可以在不同的机器和编译器上可移植,可移植的一方面就是说程序对不同数据类型的确切大小不敏感。
比如许多程序员假设一个声明为int类型的程序对象能被用来存储一个指针,这在大多数32位的机器上能够正常工作,但是在一台64位的机器上却会导致问题。所以,1980到2010年期间(32位机器是主流)编写的程序,之后64位机器陆续普及之后,迁移过来的程序就暴露出来许多隐藏的对字长的依赖性问题,导致错误。
【垂直行业如电商如何衡量搜索引擎的优劣】
在电商行业中,无论是2B还是2C,最终的业务目的就是交易成单,众所周知搜索服务旨在让消费者能够更快的定位到自己想要的产品。
一般电商搜索的核心是搜索精度和搜索广度,精度就是搜索的精确性,广度就是搜索结果的范围,其关键结果肯定是“为用户找到想要的商品”,但过于追求搜索的精确度就会导致出现搜索的结果比较少或结果为0的情况,用户搜不到商品势必会引发流失,因此在搜索服务里面还可以做的就是给用户提供一些相关性搜索结果。那么搜索做的好不好,其实就是在搜索精度和搜索广度二者之间做一个比较好的平衡点。
搜索过程中遇到的问题:
1.随机性发现的Bad case
2.KPI或者OKR考核
3.业务方诉求
核心指标
- “搜索PV”:指访问搜索页面的次数;
- “搜索UV”:访问过搜索结果页的用户数;
- “无结果率”:空结果PV/搜索PV,无结果率越低,代表客户搜索需求解决情况越好;
- “TOP5 PV—CTR”:指该query search结果中,排在前五位的item有被点击的搜索PV/该query搜索PV该指标能一定程度反应排序效果;
- “人均搜索PV”:搜索PV/搜索UV;该指标的含义比较复杂,一方面人均pv大的话可能代表用户对搜索比较感兴趣,但另一方面人均pv大也可能代表搜索召回的结果较差,导致用户无法使用较少的点击找到满足需求的结果;
- “有点击搜索PV占比”:有点击搜索PV/搜索PV数;
- “PV-CTR”:搜索结果页item点击数/搜索PV数;
- “UV-CTR”:点击的uv / 曝光的uv;
- “Item-CTR”:搜索结果页item点击数/搜索结果页item总曝光PV数;
搜索技术等级分类
https://developer.aliyun.com/article/769492
#每日精进#2020年8月3日
总结 • zkbhj 发表了文章 • 0 个评论 • 2710 次浏览 • 2020-08-03 20:45
第二章 信息的表示和处理
现代计算机存储和处理信息以二值信号表示。二值信号能够很容易的被表示、存储和传输,且用二值信号进行存储和执行计算的电子电路非常简单可靠。
三种重要的数字表示:
无符号编码:基于传统的二进制表示法,表示大于或者等于0的数字;
补码编码:表示有符号整数的最常见方式;
浮点数编码:表示实数的科学计数法的以2为基数的版本;
计算机的表示法是以有限数量的位来对一个数字进行编码,所以一旦超出界限,某些运算就会溢出,导致令人吃惊的后果。
整数的计算机运算满足人们所熟知的真正整数运算的许多性质。但是浮点数不一样。
整数的表示虽然只能编码一个相对较小的数值范围,但是这种表示是精确的;
浮点数虽然可以编码一个比较大的数值范围,但是这种标示只是近似的;
通过如下命令,可以在gcc编译C程序时指定C语言版本:
linux> gcc -std=c11 zkbhj.c
//其他版本参数
//GNU 89 无,-std=gnu89
//ANSI ,ISO C90 -ansi,-std=c89
//ISO C99 -std=c99
//ISO C11 -std=c11
【Go核心36讲:第11节 通道的高级玩法】
单向通道
所谓单向通道就是,只能发不能收,或者只能收不能发的通道。
声明一个只能发(向通道发送)不能收(从通道接收),容量为1的单向通道:
var uselessChan = make(chan<- int, 1)声明一个只能收(从通道接收)不能发(向通道发送),容量为1的单向通道:
var uselessChan = make(<-chan int, 1)
与发送操作和接收操作对应,这里的“发”和“收”都是站在操作通道的代码的角度上说的。
单向通道有什么应用价值?
概括地说,单向通道最主要的用途就是约束其他代码的行为。
//参数定义中就约束 ch只能进行发送操作,不能接收
//可以限制方法函数内对参数的操作行为做限定
func SendInt(ch chan<- int) {
ch <- rand.Intn(1000)
}//这段接口声明中,就约定了所以要实现这个接口的实现类型
//都约定了这些方法的参数类型
type Notifier interface {
SendInt(ch chan<- int)
}在实际调用的时候,传递一个双向通道即可,因为Go 语言在这种情况下会自动地把双向通道转换为函数所需的单向通道。
一种专门为了操作通道而存在的语句:select语句
select语句只能与通道联用,它一般由若干个分支组成。每次执行这种语句的时候,一般只有一个分支中的代码会被运行。分支分为两种,一种叫做候选分支,另一种叫做默认分支。每个case表达式中都只能包含操作通道的表达式。
select {
case <-intChannels[0]:
fmt.Println("The first candidate case is selected.")
case <-intChannels[1]:
fmt.Println("The second candidate case is selected.")
case elem := <-intChannels[2]: fmt.Printf("The third candidate case is selected, the element is %d.\n", elem)
default: fmt.Println("No candidate case is selected!")
}select语句只能对其中的每一个case表达式各求值一次。所以,如果我们想连续或定时地操作其中的通道的话,就往往需要通过在for语句中嵌入select语句的方式实现。但这时要注意,简单地在select语句的分支中使用break语句,只能结束当前的select语句的执行,而并不会对外层的for语句产生作用。这种错误的用法可能会让这个for语句无休止地运行下去。
select语句的分支选择规则总结:
1、对于每一个case表达式,都至少会包含一个代表发送操作的发送表达式或者一个代表接收操作的接收表达式;
2、select语句包含的候选分支中的case表达式都会在该语句执行开始时先被求值,并且求值的顺序是依从代码编写的顺序从上到下的;
3、对于每一个case表达式,如果其中的发送表达式或者接收表达式在被求值时,相应的操作正处于阻塞状态,那么对该case表达式的求值就是不成功的;
4、仅当select语句中的所有case表达式都被求值完毕后,它才会开始选择候选分支。这时候,它只会挑选满足选择条件的候选分支执行。所有的都不满足,执行default;
5、如果select语句发现同时有多个候选分支满足选择条件,那么它就会用一种伪随机的算法在这些分支中选择一个并执行;
【关于730政治局会议的总结】
15大要点(内循环 + 持久战)
中国发展仍处于战略机遇期从持久战角度认识中长期问题以国内大循环为主题建立中长期协调机制牢牢把握扩大内需这个战略基点确保宏观政策落地见效保持货币供应量合理增长毫不放松抓好常态化疫情防控扩大最终消费加快新基建以新型城镇化带动投资和消费产业链补短板和锻长板从严打击证券违法活动住房不炒缓解疫情对年轻人就业影响
我们遇到的很多问题是中长期的,必须从持久战的角度加以认识
“在泡沫中狂欢的日志不多了,做好潮水退却后的准备,是每个国家,每个人都要面对的现实”
——吴晓灵 前央行副行长 查看全部
第二章 信息的表示和处理
现代计算机存储和处理信息以二值信号表示。二值信号能够很容易的被表示、存储和传输,且用二值信号进行存储和执行计算的电子电路非常简单可靠。
三种重要的数字表示:
无符号编码:基于传统的二进制表示法,表示大于或者等于0的数字;
补码编码:表示有符号整数的最常见方式;
浮点数编码:表示实数的科学计数法的以2为基数的版本;
计算机的表示法是以有限数量的位来对一个数字进行编码,所以一旦超出界限,某些运算就会溢出,导致令人吃惊的后果。
整数的计算机运算满足人们所熟知的真正整数运算的许多性质。但是浮点数不一样。
整数的表示虽然只能编码一个相对较小的数值范围,但是这种表示是精确的;
浮点数虽然可以编码一个比较大的数值范围,但是这种标示只是近似的;
通过如下命令,可以在gcc编译C程序时指定C语言版本:
linux> gcc -std=c11 zkbhj.c
//其他版本参数
//GNU 89 无,-std=gnu89
//ANSI ,ISO C90 -ansi,-std=c89
//ISO C99 -std=c99
//ISO C11 -std=c11
【Go核心36讲:第11节 通道的高级玩法】
单向通道
所谓单向通道就是,只能发不能收,或者只能收不能发的通道。
声明一个只能发(向通道发送)不能收(从通道接收),容量为1的单向通道:
var uselessChan = make(chan<- int, 1)声明一个只能收(从通道接收)不能发(向通道发送),容量为1的单向通道:
var uselessChan = make(<-chan int, 1)与发送操作和接收操作对应,这里的“发”和“收”都是站在操作通道的代码的角度上说的。
单向通道有什么应用价值?
概括地说,单向通道最主要的用途就是约束其他代码的行为。
//参数定义中就约束 ch只能进行发送操作,不能接收
//可以限制方法函数内对参数的操作行为做限定
func SendInt(ch chan<- int) {
ch <- rand.Intn(1000)
}
//这段接口声明中,就约定了所以要实现这个接口的实现类型在实际调用的时候,传递一个双向通道即可,因为Go 语言在这种情况下会自动地把双向通道转换为函数所需的单向通道。
//都约定了这些方法的参数类型
type Notifier interface {
SendInt(ch chan<- int)
}
一种专门为了操作通道而存在的语句:select语句
select语句只能与通道联用,它一般由若干个分支组成。每次执行这种语句的时候,一般只有一个分支中的代码会被运行。分支分为两种,一种叫做候选分支,另一种叫做默认分支。每个case表达式中都只能包含操作通道的表达式。
select {select语句只能对其中的每一个case表达式各求值一次。所以,如果我们想连续或定时地操作其中的通道的话,就往往需要通过在for语句中嵌入select语句的方式实现。但这时要注意,简单地在select语句的分支中使用break语句,只能结束当前的select语句的执行,而并不会对外层的for语句产生作用。这种错误的用法可能会让这个for语句无休止地运行下去。
case <-intChannels[0]:
fmt.Println("The first candidate case is selected.")
case <-intChannels[1]:
fmt.Println("The second candidate case is selected.")
case elem := <-intChannels[2]: fmt.Printf("The third candidate case is selected, the element is %d.\n", elem)
default: fmt.Println("No candidate case is selected!")
}
select语句的分支选择规则总结:
1、对于每一个case表达式,都至少会包含一个代表发送操作的发送表达式或者一个代表接收操作的接收表达式;
2、select语句包含的候选分支中的case表达式都会在该语句执行开始时先被求值,并且求值的顺序是依从代码编写的顺序从上到下的;
3、对于每一个case表达式,如果其中的发送表达式或者接收表达式在被求值时,相应的操作正处于阻塞状态,那么对该case表达式的求值就是不成功的;
4、仅当select语句中的所有case表达式都被求值完毕后,它才会开始选择候选分支。这时候,它只会挑选满足选择条件的候选分支执行。所有的都不满足,执行default;
5、如果select语句发现同时有多个候选分支满足选择条件,那么它就会用一种伪随机的算法在这些分支中选择一个并执行;
【关于730政治局会议的总结】
15大要点(内循环 + 持久战)
- 中国发展仍处于战略机遇期
- 从持久战角度认识中长期问题
- 以国内大循环为主题
- 建立中长期协调机制
- 牢牢把握扩大内需这个战略基点
- 确保宏观政策落地见效
- 保持货币供应量合理增长
- 毫不放松抓好常态化疫情防控
- 扩大最终消费
- 加快新基建
- 以新型城镇化带动投资和消费
- 产业链补短板和锻长板
- 从严打击证券违法活动
- 住房不炒
- 缓解疫情对年轻人就业影响
我们遇到的很多问题是中长期的,必须从持久战的角度加以认识
“在泡沫中狂欢的日志不多了,做好潮水退却后的准备,是每个国家,每个人都要面对的现实”
——吴晓灵 前央行副行长
#每日精进#2020年08月02日
总结 • zkbhj 发表了文章 • 0 个评论 • 2775 次浏览 • 2020-08-02 19:46
第一章 计算机系统漫游
现代系统之间利用网络通信,和其他系统连接在一起。从一个单独的系统来看,网络可以视为一个I/O设备。
系统不仅仅只是硬件,而是硬件和软件互相交织的结合体,他们之间共同协作已达到运行应用程序的最终目的。
Amdahl(安达尔定律)定律
主要思想是:当对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度。
主要观点是:想要显著加速整个系统,必须提升全系统中相当大的部分的速度。
Amdahl定律描述了改善任何过程的一般原则。
并发和并行
整个计算机发展历史中,我们一直在做两件事:一是让计算机做得更多,二是让计算机运行的更快。
并发:是一个通用概念,指一个同时具有多个活动的系统;
并行:指的是用并发来使一个系统运行得更快。
进程级并发
并发构建在进程整个抽象上,就能够设计出同时有多个程序执行的系统。
单处理器系统:只有一个处理器的系统;
多处理器系统:一个由单操作系统内核控制的多处理器组成的系统。
超线程,有时称为同时多线程,是一项允许一个CPU执行多个控制流的技术。
指令级并行
CPU可以同时执行多条指令的属性称为指令级并行。
超标量处理器:处理器能够达到比一个时钟周期一条指令更快的执行速率。
计算机系统中抽象的重要性
计算机系统提供的一些抽象,它提供不同层次的抽象表示来隐藏实际实现的复杂性。
比如上一节中:文件是对I/O设备的抽象,虚拟内存是对主存和磁盘的抽象,进程则是对一个正在运行的程序的抽象(处理器、主存和I/O的抽象)。
虚拟机,则提供对整个计算机的抽象。
第一章总结
计算机系统是由硬件和系统软件组成的,它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位,它们根据上下文有不同的解释方式。程序被其他程序翻译成不同的形式,开始是ASCII文本,然后被编译器和链接器翻译成二进制可执行文件。
处理器读取并解释存储在主存里的二进制指令。因为计算机花了大把时间用于存储器、I/O设备和CPU寄存器之间复制数据,所以讲系统中的存储设备划分成层次结构——CPU寄存器在顶部,接着是多层的硬件高速缓存存储器、DRAM主存和磁盘存储器。
在层次模型中,位于更高层的存储设备比低层的存储设备要更快,单位比特造价也更高。层次结构中较高层次存储设备可以作为较低层次存储设备的高速缓存。通过理解和运用这种存储层次结构的知识,程序员可以优化C程序的性能。
操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象:
(1)文件是对I/O设备的抽象
(2)虚拟存储器是对主存和I/O设备的抽象
(3)进程是对处理器、主存和I/O设备的抽象。
另外,虚拟机提供了对整个计算机的抽象。
最后,网络提供了计算机系统之间通信的手段。从特殊系统的角度来看,网络就是一种I/O设备。
一周的早读,完成了《深入理解计算机系统》这本又厚又重的计算机最底层原理书籍第一章的阅读和理解。
好好利用碎片时间,以及找到自己的高效时间段及学习方式很重要。
积少成多,积流成河!
明天开启第二章:程序结构和执行!
PS广而告之时间:
正在筹划一个作为技术人员角度的个人分享网站,以很程序员的视角,将学习过的一门语言、一本技术书籍,以“手册”的形式输出出来。
根据能量守恒定律,有输入就要有输出,既然自己有这样的渠道和能力,打算把体系学习过的一些内容,沉淀成一本本技术手册分享出来,可以给更多有同样需求的人提供一些有意义的帮助和指导。
还是凯冰科技10多年来坚持的一句slogan:
代码改变世界,技术改变生活
Code changes the world, technology changes life
doc.zkbhj.com
第一本就是这本《深入理解计算机系统》。嘿嘿~
【关于如何在美团里继续使用支付宝支付的方法】
额。。由于一些你懂的的原因,大部分美团用户已经无法再美团APP上用支付宝进行支付了。这里暂时不评论这件事情的对与错好与坏,只是从技术的角度,帮你找到了一个怎么继续在美团里使用支付宝支付的方法,操作步骤如下:
1、打开美团APP,点击进入个人中心页面,点击右上角的在线客服,进入客服聊天界面;
2、输入问题:支付宝无法使用;
3、在返回的结果里选择:放弃优惠,恢复支付宝。
经过上面的几步“骚操作”,就可以继续在美团里使用支付宝进行支付了!
【Go语言核心36讲:第10节 通道的基本操作 】
☆ 通道(Channel)
通道是Go 语言最有特色的数据类型,是不同Goroutine之间通信的“桥梁”。
Don’t communicate by sharing memory; share memory by communicating. (不要通过共享内存来通信,而应该通过通信来共享内存。)
通道类型的值是 Go 语言自带的、唯一一个可以满足并发安全性的类型。
当容量为0时,我们可以称通道为非缓冲通道,也就是不带缓冲的通道。而当容量大于0时,我们可以称为缓冲通道,也就是带有缓冲的通道。
一个通道相当于一个先进先出(FIFO)的队列。也就是说,通道中的各个元素值都是严格地按照发送的顺序排列的,先被发送通道的元素值一定会先被接收。元素值的发送和接收都需要用到操作符<-。我们也可以叫它接送操作符。一个左尖括号紧接着一个减号形象地代表了元素值的传输方向。
☆ 对通道的发送和接收操作都有哪些基本的特性?
1、对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的。
元素值从外界进入通道时会被复制。更具体地说,进入通道的并不是在接收操作符右边的那个元素值,而是它的副本。另一方面,元素值从通道进入外界时会被移动。这个移动操作实际上包含了两步,第一步是生成正在通道中的这个元素值的副本,并准备给到接收方,第二步是删除在通道中的这个元素值。
2、发送操作和接收操作中对元素值的处理都是不可分割的。
这里的“不可分割”的意思是,它们处理元素值时都是一气呵成的,绝不会被打断。
发送时,“复制元素值”和“放置副本到通道内部”这两个步骤不会被打断;
接收时,“复制通道内元素值”“放置副本到接收方”“删掉原值”三个步骤不会被打断。
3、发送操作在完全完成之前会被阻塞。接收操作也是如此。
以上各步骤执行期间,其他操作都会被阻塞。如此阻塞代码其实就是为了实现操作的互斥和元素值的完整。
☆ 发送操作和接收操作在什么时候可能被长时间的阻塞?
有缓冲的,如果通道已满,对它的所有发送操作都会被阻塞,直到通道中有元素值被接收走。如果通道已空,对它的所有接收操作都会被阻塞,直到通道中有新的元素值出现。所有被阻塞的goroutine都是按FIFO的策略执行的。
非缓冲通道,情况要简单一些。无论是发送操作还是接收操作,一开始执行就会被阻塞,直到配对的操作也开始执行,才会继续传递。由此可见,非缓冲通道是在用同步的方式传递数据。
对于值为nil的通道,不论它的具体类型是什么,对它的发送操作和接收操作都会永久地处于阻塞状态。它们所属的 goroutine 中的任何代码,都不再会被执行。所以我们一定不要忘记初始化通道!
☆ 发送操作和接收操作在什么时候会引发 panic?
通道一旦关闭,再对它进行发送操作,就会引发 panic。
如果我们试图关闭一个已经关闭了的通道,也会引发 panic。
通过接收表达式的第二个结果值,可以来判断通道是否关闭,但是可能有延时的。即如果通道已经关闭,但还有值未被取出,则这个时候,返回的仍然是true!
所以最佳实践告诉我们,千万不要让接收方关闭通道,而应当让发送方做这件事。 查看全部
【午读:《深入理解计算机系统》】
第一章 计算机系统漫游
现代系统之间利用网络通信,和其他系统连接在一起。从一个单独的系统来看,网络可以视为一个I/O设备。
系统不仅仅只是硬件,而是硬件和软件互相交织的结合体,他们之间共同协作已达到运行应用程序的最终目的。
Amdahl(安达尔定律)定律
主要思想是:当对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度。
主要观点是:想要显著加速整个系统,必须提升全系统中相当大的部分的速度。
Amdahl定律描述了改善任何过程的一般原则。
并发和并行
整个计算机发展历史中,我们一直在做两件事:一是让计算机做得更多,二是让计算机运行的更快。
并发:是一个通用概念,指一个同时具有多个活动的系统;
并行:指的是用并发来使一个系统运行得更快。
进程级并发
并发构建在进程整个抽象上,就能够设计出同时有多个程序执行的系统。
单处理器系统:只有一个处理器的系统;
多处理器系统:一个由单操作系统内核控制的多处理器组成的系统。
超线程,有时称为同时多线程,是一项允许一个CPU执行多个控制流的技术。
指令级并行
CPU可以同时执行多条指令的属性称为指令级并行。
超标量处理器:处理器能够达到比一个时钟周期一条指令更快的执行速率。
计算机系统中抽象的重要性
计算机系统提供的一些抽象,它提供不同层次的抽象表示来隐藏实际实现的复杂性。
比如上一节中:文件是对I/O设备的抽象,虚拟内存是对主存和磁盘的抽象,进程则是对一个正在运行的程序的抽象(处理器、主存和I/O的抽象)。
虚拟机,则提供对整个计算机的抽象。
第一章总结
计算机系统是由硬件和系统软件组成的,它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位,它们根据上下文有不同的解释方式。程序被其他程序翻译成不同的形式,开始是ASCII文本,然后被编译器和链接器翻译成二进制可执行文件。
处理器读取并解释存储在主存里的二进制指令。因为计算机花了大把时间用于存储器、I/O设备和CPU寄存器之间复制数据,所以讲系统中的存储设备划分成层次结构——CPU寄存器在顶部,接着是多层的硬件高速缓存存储器、DRAM主存和磁盘存储器。
在层次模型中,位于更高层的存储设备比低层的存储设备要更快,单位比特造价也更高。层次结构中较高层次存储设备可以作为较低层次存储设备的高速缓存。通过理解和运用这种存储层次结构的知识,程序员可以优化C程序的性能。
操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象:
(1)文件是对I/O设备的抽象
(2)虚拟存储器是对主存和I/O设备的抽象
(3)进程是对处理器、主存和I/O设备的抽象。
另外,虚拟机提供了对整个计算机的抽象。
最后,网络提供了计算机系统之间通信的手段。从特殊系统的角度来看,网络就是一种I/O设备。
一周的早读,完成了《深入理解计算机系统》这本又厚又重的计算机最底层原理书籍第一章的阅读和理解。
好好利用碎片时间,以及找到自己的高效时间段及学习方式很重要。
积少成多,积流成河!
明天开启第二章:程序结构和执行!
PS广而告之时间:
正在筹划一个作为技术人员角度的个人分享网站,以很程序员的视角,将学习过的一门语言、一本技术书籍,以“手册”的形式输出出来。
根据能量守恒定律,有输入就要有输出,既然自己有这样的渠道和能力,打算把体系学习过的一些内容,沉淀成一本本技术手册分享出来,可以给更多有同样需求的人提供一些有意义的帮助和指导。
还是凯冰科技10多年来坚持的一句slogan:
代码改变世界,技术改变生活
Code changes the world, technology changes life
doc.zkbhj.com
第一本就是这本《深入理解计算机系统》。嘿嘿~
【关于如何在美团里继续使用支付宝支付的方法】
额。。由于一些你懂的的原因,大部分美团用户已经无法再美团APP上用支付宝进行支付了。这里暂时不评论这件事情的对与错好与坏,只是从技术的角度,帮你找到了一个怎么继续在美团里使用支付宝支付的方法,操作步骤如下:
1、打开美团APP,点击进入个人中心页面,点击右上角的在线客服,进入客服聊天界面;
2、输入问题:支付宝无法使用;
3、在返回的结果里选择:放弃优惠,恢复支付宝。
经过上面的几步“骚操作”,就可以继续在美团里使用支付宝进行支付了!
【Go语言核心36讲:第10节 通道的基本操作 】
☆ 通道(Channel)
通道是Go 语言最有特色的数据类型,是不同Goroutine之间通信的“桥梁”。
Don’t communicate by sharing memory; share memory by communicating. (不要通过共享内存来通信,而应该通过通信来共享内存。)
通道类型的值是 Go 语言自带的、唯一一个可以满足并发安全性的类型。
当容量为0时,我们可以称通道为非缓冲通道,也就是不带缓冲的通道。而当容量大于0时,我们可以称为缓冲通道,也就是带有缓冲的通道。
一个通道相当于一个先进先出(FIFO)的队列。也就是说,通道中的各个元素值都是严格地按照发送的顺序排列的,先被发送通道的元素值一定会先被接收。元素值的发送和接收都需要用到操作符<-。我们也可以叫它接送操作符。一个左尖括号紧接着一个减号形象地代表了元素值的传输方向。
☆ 对通道的发送和接收操作都有哪些基本的特性?
1、对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的。
元素值从外界进入通道时会被复制。更具体地说,进入通道的并不是在接收操作符右边的那个元素值,而是它的副本。另一方面,元素值从通道进入外界时会被移动。这个移动操作实际上包含了两步,第一步是生成正在通道中的这个元素值的副本,并准备给到接收方,第二步是删除在通道中的这个元素值。
2、发送操作和接收操作中对元素值的处理都是不可分割的。
这里的“不可分割”的意思是,它们处理元素值时都是一气呵成的,绝不会被打断。
发送时,“复制元素值”和“放置副本到通道内部”这两个步骤不会被打断;
接收时,“复制通道内元素值”“放置副本到接收方”“删掉原值”三个步骤不会被打断。
3、发送操作在完全完成之前会被阻塞。接收操作也是如此。
以上各步骤执行期间,其他操作都会被阻塞。如此阻塞代码其实就是为了实现操作的互斥和元素值的完整。
☆ 发送操作和接收操作在什么时候可能被长时间的阻塞?
有缓冲的,如果通道已满,对它的所有发送操作都会被阻塞,直到通道中有元素值被接收走。如果通道已空,对它的所有接收操作都会被阻塞,直到通道中有新的元素值出现。所有被阻塞的goroutine都是按FIFO的策略执行的。
非缓冲通道,情况要简单一些。无论是发送操作还是接收操作,一开始执行就会被阻塞,直到配对的操作也开始执行,才会继续传递。由此可见,非缓冲通道是在用同步的方式传递数据。
对于值为nil的通道,不论它的具体类型是什么,对它的发送操作和接收操作都会永久地处于阻塞状态。它们所属的 goroutine 中的任何代码,都不再会被执行。所以我们一定不要忘记初始化通道!
☆ 发送操作和接收操作在什么时候会引发 panic?
通道一旦关闭,再对它进行发送操作,就会引发 panic。
如果我们试图关闭一个已经关闭了的通道,也会引发 panic。
通过接收表达式的第二个结果值,可以来判断通道是否关闭,但是可能有延时的。即如果通道已经关闭,但还有值未被取出,则这个时候,返回的仍然是true!
所以最佳实践告诉我们,千万不要让接收方关闭通道,而应当让发送方做这件事。