开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

你用PHP爬取过数据吗? 解析json 解析dom树 总结和

发表于 2021-11-27

「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」

PHP除了做服务端开发已经,也是可以像Python一样来爬取数据的。

我认为爬取数据有两种实现思路:

1种是请求数据接口,解析json数据

另外1种是获得网页的dom树,解析dom树,获得数据。

今天重点介绍第二种,引用php的一个插件:simple_html_dom,解析json的思路也提一下。

image.png

解析json

解析json这种方式很好理解,所有支持请求网络的开发语言都能支持

思路就是获得爬取链接返回的json数据(也可以是其他类型的数据,json比较常用)

我们拿到json数据后进行解析,结合自身需求来处理数据。

这个比较通用,就不举例子了。

解析dom树

重点讲解一下PHP是如何爬取目标网站,解析dom树的。

举个栗子:

工具类代码:

做了缓存处理,我的场景只要请求的url相同返回的数据是不变的,所以做了缓存处理

请求的url之前请求通,不会重复请求,而是会从缓存中取值

sleep()的作用是避免给抓取数据的网站造成网络问题,控制一下请求频率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
php复制代码public static function crawlContent($url, $encode = true)
{
$file_name = '../cache/' . md5($url);
if (!file_exists($file_name)) {
@touch($file_name);
}
$content = file_get_contents($file_name);
if (empty($content)) {
$content = Request::curl($url);
if (empty($content)) {
sleep(rand(3,10));
$content = Request::curl($url);
}
$encode && $content = iconv("GBK", "UTF-8//IGNORE", $content);
file_put_contents($file_name, $content);
}
return $content;
}

封装的 curl 工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
php复制代码public static function curl($url , $configs = array())
{
$b = microtime(true);
$new_ch = curl_init();
self::_setopt($new_ch , $url , $configs);
$result = curl_exec($new_ch);
$e = microtime(true);
if (curl_errno($new_ch))
{
Logger::log('CULR_BAD\t' . "curl_errno:" . curl_errno($new_ch) . "\t" . curl_error($new_ch) . "\t" . ($e - $b) . "\t" . $url);
}
curl_close($new_ch);
return $result;
}

解析dom树示例代码

上面的工具类代码我们获得了目标网页的数据

通过 str_get_html 函数获得字符串

再按照 simple_html_dom 获得元素的方式获得对应的值就可以了了

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
php复制代码
<?php
// 查找id='#container'的元素
$ret = $html->find('#container');

// 找到所有class=foo的元素
$ret = $html->find('.foo');

// 查找多个html标签
$ret = $html->find('a, img');

// 还可以这样用
$ret = $html->find('a[title], img[title]');
?>

更多取值方法可以查看官方文档,非常的简单好用,这里就不再这里赘述了。

我的示例代码

下面是我的示例代码,整理了一些爬取数据时需要注意的问题:

  1. 如果涉及抓取图片的话,最好把图片上传到自己的云存储
  2. 合理的控制爬取频率(已封装到工具类中)
  3. 合理的使用代理(已封装到工具类中)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
php复制代码<?php
include '../include/Config.php';
include '../include/Db.php';
include '../include/Logger.php';
include '../include/Request.php';
include '../include/simple_html_dom.php';
include '../include/Utils.php';
//综合脚本 一套代码完成 抓取商品详情链接 抓取商品图 下载商品图
$db_aliexpress = new Db($db_xxxx);

parseContent($db_xxxx);

function parseContent($spider)
{
for($p=9;$p<50;$p++){
$url = 'https://www.xxxxx.com/store/5435064/search/'.$p.'.html?SortType=bestmatch_sort';
var_dump($url);
$m_content = Utils::curlProxy($url, false);
$detail_html = str_get_html($m_content);
if ($detail_html) {
$content = $detail_html->find('.m-o-large-all-detail .ui-box .ui-box-body ul li');
if ($content) {
$counts = count($content);
for ($i = 0; $i < $counts; $i++) {
$name = trim($detail_html->find('.m-o-large-all-detail .ui-box .ui-box-body ul li .detail h3 a', $i)->plaintext); //名称
$href = 'https:'.trim($detail_html->find('.m-o-large-all-detail .ui-box .ui-box-body ul li .detail h3 a', $i)->href); //url
preg_match_all('/[1-9]\d*/', $href, $matches);
$source_id = $matches[0][0];
$price = trim($detail_html->find('.m-o-large-all-detail .ui-box .ui-box-body ul li .cost b', $i)->plaintext); //价格
$data = array(
'name' => $name,
'source_url' => $href,
'source_id' => $source_id,
'price'=>$price,
'created_at'=>date('Y-m-d H:i:s'),
'date'=>date('m-d'),
);
$id = $spider->insert('products', $data);
var_dump("商品信息:products_" . $id);
if ($id){
//存储图片
catchThumbs($id,$href,$spider);
}
}
}
}
}
}

//抓取商品图
function catchThumbs($id,$detail_url,$spider)
{
$m_content = Utils::curlProxy($detail_url, false);

$before = strpos($m_content, 'imagePathList');
$after = strpos($m_content,'ImageModule',1);
$count = $after-$before-24;

$thumbs = substr($m_content,$before+15,$count);

$data = array(
'thumbs'=>$thumbs,
'updated_at'=>date('Y-m-d H:i:s'),
);
$res = $spider->update('products', $data, 'id = '.$id);
var_dump('抓取商品图:'.$res." id=".$id);
if ($res){
//更新成功则下载图片
downloadThumbs($id,$thumbs,$spider);
}
}

//下载商品图
function downloadThumbs($id,$thumbs,$spider)
{
$Ymd = date('md');
$thumbs = json_decode($thumbs,true);
foreach ($thumbs as $key=> $thumb){
$image_name = $Ymd.'-'.$id.'-'.($key+1).'.jpg';
$image_name = './pics/'.$image_name;
saveImage($thumb,$image_name);
}
}

/**
* 从网上下载图片保存到服务器
* @param $path 图片网址
* @param $image_name 保存到服务器的路径 './public/upload/users_avatar/'.time()
*/
function saveImage($path, $image_name) {
$ch = curl_init ($path);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
$img = curl_exec ($ch);
curl_close ($ch);
//$image_name就是要保存到什么路径,默认只写文件名的话保存到根目录
$fp = fopen($image_name,'w');//保存的文件名称用的是链接里面的名称
fwrite($fp, $img);
fclose($fp);
}

总结和注意

以上就是PHP爬取数据的思路总结啦~

注意:在未经授权的情况下爬取数据是违法的!

本文仅是提供爬取数据的PHP实现思路而已,网络世界不是法外之地,谨慎使用爬取技术哦~

大家有什么好想法欢迎在评论区讨论

硬核文章推荐

PHP转Go 2021年年中总结

如何第一时间收到接口报错?不用测试妹子再质疑你是不是接口挂了。

Git使用实战:多人协同开发,紧急修复线上bug的Git操作指南。

性能优化反思:不要在for循环中操作DB

性能优化反思:不要在for循环中操作DB 进阶版

最后

👍🏻:觉得有收获请点个赞鼓励一下!

🌟:收藏文章,方便回看哦!

💬:评论交流,互相进步!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python的线程08 使用队列来改造转账场景 再次看看转账

发表于 2021-11-27

「这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战」

正式的Python专栏第46篇,同学站住,别错过这个从0开始的文章!

前篇我们验收(学习)了队列Queue,这次趁热学委展示一下使用队列解决转账场景的问题。

再次看看转账场景的问题

前面有两篇文章展示了转账反复读写amount,导致结果出错。

1
2
3
4
5
6
7
8
python复制代码
xuewei_account = dict()
xuewei_account['amount'] = 100

# amount为负数即是转出金额
def transfer(money):
for i in range(100000):
xuewei_account['amount'] = xuewei_account['amount'] + money

我们前几篇使用多个线程反复转长:+1和-1。

按常理,结果应该仍旧是100.

这个是全部代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
python复制代码#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/26 12:02 上午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : threadsafe_queue1.py
# @Project : hello
import random
import threading
import datetime
import time

xuewei_account = dict()
xuewei_account['amount'] = 100


# amount为负数即是转出金额
def transfer(money):
for i in range(100000):
xuewei_account['amount'] = xuewei_account['amount'] + money


# 创建20个任务重复给学委账户转账
threads = []
for i in range(10):
t1 = threading.Thread(target=lambda: transfer(-1))
threads.append(t1)
t2 = threading.Thread(target=lambda: transfer(1))
threads.append(t2)

for t in threads:
t.start()
for t in threads:
t.join()

print("-" * 16)
print("活跃线程数:", threading.active_count())
print("活跃线程:", threading.current_thread().name)
print("学委账户余额:", xuewei_account)

等待所有转账线程运行结束,我们看到结果是错误的:

屏幕快照 2021-11-27 下午11.21.21.png

这种问题怎么使用队列来解决呢?

前面说了,多线程反复读写共享数据,是问题的根源。

改代码为同步互斥模式,保证任意一个时间一个线程更新共享数据,那么问题就解决了。(这前面也展示了,用的是Lock锁的方案)

这个能怎么用队列呢?

可以先思考10秒,根据学习到的加锁和队列的特性,想想这个怎么做。

好,答案现在揭晓:

Queue这个队列有多个函数,一个是put函数,一个是get函数。

一个负责放入数据到队尾,一个可以从对头取出元素。

刚好适合转账业务,我们是不是可以把每次转账操作变成一个一个指令/事件。 比如下面的:

1
2
3
4
5
6
7
python复制代码event(amount=1,acount=xuewei_account)
....
event(amount=-1,acount=xuewei_account)
....
event(amount=1,acount=xuewei_account)
....
event(amount=-1,acount=xuewei_account)

20个线程,每个10万次数据读写,共200万个事件。

所以我们可以把这个事情转换为:200万个转账事件。

因为Queue是线程安全的,所以我们可以并发200万次转账,另外交给一线程进行转账处理。

这样就保证每次只有一个线程对xuewei_account学委账户进行读写。

改造,使用队列来解决问题

展示代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
python复制代码#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/26 12:02 上午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : threadsafe_queue2.py
# @Project : hello
import random
import threading
import datetime
import time
import queue

q = queue.Queue()

xuewei_account = dict()
xuewei_account['amount'] = 100


# amount为负数即是转出金额
def transfer(money):
for i in range(100000):
q.put(money)


def handle_amount():
while not q.empty():
amount = q.get()
xuewei_account['amount'] += amount


def monitor_q():
counter = 0
time.sleep(3)
while counter < 1000 and not q.empty():
print("q size:", q.qsize())
time.sleep(3)
counter+=1


q_thread = threading.Thread(name="Q监控", target=monitor_q)
q_thread.start()
# 创建20个任务重复给学委账户转账
threads = []
for i in range(10):
t1 = threading.Thread(target=lambda: transfer(-1))
threads.append(t1)
t2 = threading.Thread(target=lambda: transfer(1))
threads.append(t2)

for t in threads:
t.start()

vip_thread = threading.Thread(name="处理转账专线", target=handle_amount)
vip_thread.start()



for t in threads:
t.join()
vip_thread.join()

print("-" * 16)
print("活跃线程数:", threading.active_count())
print("活跃线程:", threading.current_thread().name)
print("学委账户余额:", xuewei_account)

这里运行了多个线程执行转账(发送转账金额进队列)。

然后运行一个vip通道(单独线程)处理学委账户的转账业务。

同时也运行了一个监控队列的线程,每隔一段时间打印队列的任务情况。

下面是运行结果,运行几次结果都是正确的。

屏幕快照 2021-11-27 下午11.36.54.png

运行几次最终账户余额都是100, 改造成功。

总结

本篇学委分享了线程安全的队列Queue解决并发转账问题。

其实代码还可以再度优化的,为了控制篇幅,代码也不少,希望读者朋友们能够先看熟学会,掌握队列的使用。

对了,喜欢Python的朋友,请关注学委的 Python基础专栏 or Python入门到精通大专栏

持续学习持续开发,我是雷学委!

编程很有趣,关键是把技术搞透彻讲明白。

欢迎关注微信,点赞支持收藏!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java线程池实践原理学习(一)

发表于 2021-11-27

这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

因为后面可能涉及到从云端拉去大量的数据到本地,我自己认为应该要用到多线程来加快执行任务的时间,所以我打算在这篇文章中,和大家一起学习线程池的原理,作为技术沉淀。

一、写在前面

1.1 线程的状态

其实我本人对待线程一直是抱着他们是计算机里的小精灵🧚‍♀️来看待的,他们会休息会工作会抢东西,创造他们需要时间,安排他们工作也需要时间,这就是线程的上下文切换。在Java中,线程可以分为5种状态,如下所示:

  1. 新建(NEW):新创建了一个线程对象
  2. 可运行(RUNNABLE) 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu的使用权。
  3. 运行(RUNNING) :可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。
  4. 阻塞(BLOCKED) :阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
* 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
* 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入 **锁池(lock pool)** 中。
* 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
  1. 死亡(DEAD) :线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。

1.2 线程池是什么

线程池(Thread Pool)是一种基于池化思想管理线程的工具,线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。

当然,使用线程池可以带来一系列好处:

  • 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造 成的损耗。
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。

二、线程池核心设计与实现

这节我们将会根据JDK1.8中的ThreadPoolExecute来分析Java线程池的核心设计与实现。

image.png

  • Executor提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供 Runnable 对象,将任务的运行逻辑提交 到执行器 (Executor) 中,由 Executor 框架完成线程的调配和任务的执行部分。
  • ExecutorService 接口增加了一些能力:
+ 扩充执行任务的能力,补充可以为一个或一批异步任务生成 Future 的方法;
+ 提供了管控线程池的方法,比如停止线程池的运行。
  • AbstractExecutorService 则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。
  • ThreadPoolExecutor 实现最复杂的运行部分,ThreadPoolExecutor 将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。

下图为线程池中的运行流程:
6A0B7609-FBDE-4121-BC33-6FF4A81446DD.png

我们可以看到线程池内部就像生产者和消费者之间的关系。主要分成了两个部分,分别是任务管理和线程管理,生产者提供任务后,由线程管理者来负责后续的流转。

  • 直接申请线程执行任务
  • 缓冲到队列中等待线程执行
  • 拒绝该任务

在下节内容中,我将和大家一起学习线程池的生命周期管理。

感谢美团技术团队的技术支持。美团技术团队

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot中集成Redis

发表于 2021-11-27

「这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战」

  1. Redis

Redis作为非关系型数据库,在项目开发中被大量的使用,尤其是作为数据的缓存来保证请求数据的快速响应,提升服务的用户体验。

今天就来学习一下Spring Boot项目中如何集成Redis作为缓存数据库。

1.1 引入Redis相关依赖

SpringBoot中对Redis的集进行了封装,提供了Spring Data Redis框架支持,Spring Data Redis在底层Spring的架构中定义与Redis数据库交互的逻辑,用户在使用时不需要关心如何管理,只需要在应用程序层面去操作Redis。

使用时需要引starter-data-redis依赖:

1
2
3
4
5
xml复制代码<!-- redis依赖信息 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

1.2 依赖包含信息

依赖引入后,可以看到maven projects中相关的依赖包信息,其中包括了spring-data-redis和lettuce-core两个依赖包。

  • spring-data-redis包含了Spring对data处理的一些公共包
  • lettuce-core包是starter-data-redis中对lettuce的默认支持

image-20211127220016311

1.3 Jedis客户端依赖

如果需要使用Jedis来操作Redis,只需要将lettuce的依赖替换为Jedis即可。

1
2
3
4
5
xml复制代码<!--导入jedis-->        
<dependency>            
   <groupId>redis.clients</groupId>            
   <artifactId>jedis</artifactId>        
</dependency>
  1. Redis配置

Spring Boot在org.springframework.boot.autoconfigure.data.redis包中为redis提供了自动配置。其中定义有:

  • RedisAutoConfiguration自动配置类
  • RedisPropertiesRedis属性信息读取类
  • RedisConnectionConfiguration连接配置基类
    • JedisConnectionConfiguration,Jedis连接配置类
    • LettuceConnectionConfiguration,Lettuce连接配置类

2.1 配置文件中定义Redis信息

有了RedisProperties,并于Spring Boot配置文件中定义Redis相关属性后,就会在项目启动时会注入到Redis中去。Redis可以自定义的配置项内容有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
properties复制代码# Redis配置项,以spring.redis为前缀
# 数据库索引(默认为0)
spring.redis.database=0
# 服务器地址
spring.redis.host=127.0.0.1
# 端口
spring.redis.port=6379
# 密码(默认为空,为空时不设置该属性)
spring.redis.password=redis
# 超时时间(毫秒)
spring.redis.timeout=30000
​
# 建议使用lettuce 可以换成jedis,spring默认集成lettuce
spring.redis.client-type=lettuce
​
# 如果使用jedis客户端,则下面定义的内容需要将lettuce换成jedis
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=10
# 最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=-2
# 最大空闲连接数
spring.redis.lettuce.pool.max-idle=10
# 最小空闲连接
spring.redis.lettuce.pool.min-idle=0

2.2 自定义Redis配置类

前面说到spring中为redis定义了专门的RedisAutoConfiguration自动配置类,其中定义创建泛型为<Object, Object>的RedisTemplate对象,为了允许自定义配置Bean,自动配置类使用@ConditionalOnMissingBean注解,表示当其他地方定义一个RedisTemplate的Bean时,会替代自动配置中的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
java复制代码@Configuration(
   proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
   public RedisAutoConfiguration() {
  }
​
   @Bean
   @ConditionalOnMissingBean(
       name = {"redisTemplate"}
  )
   @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
   public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
       RedisTemplate<Object, Object> template = new RedisTemplate();
       template.setConnectionFactory(redisConnectionFactory);
       return template;
  }
​
   @Bean
   @ConditionalOnMissingBean
   @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
   public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
       return new StringRedisTemplate(redisConnectionFactory);
  }
}

为了使用操作更方便,我们定义一个<String, Object>泛型的RedtTemplate,这样对于数据的存放和读取都更合适。

  • RedisTemplate序列化默认是采用JDK的序列化策略,可以在配置类中设置为其他策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码//自定义RedisConfig
@Configuration
public class RedisConfig {
​
   @Bean
   public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
       RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
       redisTemplate.setConnectionFactory(factory);
       //采用String的序列化方式
       redisTemplate.setKeySerializer(new StringRedisSerializer());
       // value序列化方式采用jackson
       redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
       redisTemplate.setHashKeySerializer(new StringRedisSerializer());
       redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
       
       return redisTemplate;
  }
}
  1. 使用Redis

3.1 SpringBoot项目使用Redis

在项目中配置Redis完成后,直接执行项目即可,运行时需要注意在配置文件中定义的Redis信息是否正确,如果Redis无法连接则项目启动报错。

  • 服务器地址要时Redis运行的服务地址,如果是本地则为127.0.0.1
  • 设置Redis启动的端口号,默认是6379
  • Redis连接密码即服务器中启动Redis服务的密码,如果没有密码则不要配置该属性,否则也会报错

项目成功启动后,为了验证Redis是否可用,可以在单元测试中使用redisTemplate来存储和获取数据到redis中。

1
2
3
4
5
6
7
8
9
java复制代码@Autowired
private RedisTemplate redisTemplate;
​
@Test
public void testRedis(){
   ValueOperations<String, String> operations = redisTemplate.opsForValue();
   operations.set("name", "tom");
   System.out.println(operations.get("name"));
}

执行单元测试后输出存储的tom值,说明数据已经存储在Redis中了。

image-20211127230828473

3.2 Windows下安装Redis

如果还没有安装Redis服务,或者想要在Redis服务中查看存入的数据,可以参照windows本地Redis服务的创建方法。

Redis官方并不建议在Windows下使用Redis,因此没有提供windows版本,而微软官方却为windows用户提供了可用的redis应用。

windows下Redis版本 ,下载时可以选择安装包或免安装版本。

下载安装完成后得到Redis目录,可以使用其中的redis-server和redis-cli程序来运行redis

  • redis-server作为服务端,启动后开启命令行来运行redis服务
  • redis-cli作为客户端,可以执行redis相关命令查看和存储数据

image-20211127231640248

如果是免安装版,每次redis服务都在前台运行不够简洁,还可以通过windows命令行将redis-server设置为后台启动。相关命令有:

  1. 将redis程序解压后放到合适的位置
  2. 打开一个命令窗口,进入到解压目录,输入命令:redis-server redis.windows.conf
  3. 再打开一个新的命令窗口,输入命令:redis-server --service-install redis.windows.conf
    • 这样就可以将redis部署为windows下的一个服务
  4. 安装完成后,使用命令启动服务:redis-server --service-start
  5. 如果想要停止或卸载redis服务,只需要执行相关命令
    • 停止服务命令:redis-server --service-stop
    • redis的卸载命令:redis-server --service-uninstall

进入redis客户端后,可以使用redis命令来查看存储的数据:

image-20211127232321547

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【设计模式系列】中介者模式 絮絮叨叨 概述 中介者模式的结构

发表于 2021-11-27

这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战

絮絮叨叨

朋友们好,我是小黑,我租的房子马上要到期了,今天趁着周末,去重新找房子。

想必在北京工作或者上学的朋友应该都知道,租房子这事儿可太难了,很难找到一个价格又便宜,通勤时间又短,房间装修又好的。比找到世界上最好的编程语言都难(PHP是最好的编程语言)。

因为我的同事朋友们也没有多余的房子出租,所以呢,没有条件直接和房东进行租赁交易,当然了,有些朋友会去一些小区找一些大爷大妈,找业主直租的,我呢比较怕麻烦,又怕被骗被坑,所以我都是直接找“某壳”、“某家”、“某爱某家”这样的公司,在他们的平台上找房,这些公式一般都被称之为中介,哈哈,说了这么多废话,终于把主题引出来啦。

没错,本期内容主要介绍【设计模式系列】的中介者模式。

概述

中介者设计模式是行为设计模式之一,用于降低系统中不同对象之间通信的耦合度和复杂度。

中介者设计模式在多个组件相互交互的应用程序中非常有用。

如果对象之间直接交互,则系统组件之间紧密耦合,这使得可维护性成本更高,并且不难扩展。

中介者式侧重于在对象之间提供用于通信的中介,并帮助实现对象之间的松散耦合。

就像我今天去租房这个例子,如果我和房东直接对接,那么对于我来说,首先不容易找到房东,房东也不容易找到我,然后我可能还要同时找多个房东,房东也有可能要和其他租客沟通看房谈价格,因为我不一定就会租嘛,整个交互模式大概是下面这样。

而中介就是为了专门解决租客和房东直接沟通(通信)的问题,来降低我和房东的沟通成本(耦合度和复杂度),沟通模式变成下面这样。

中介者模式的结构

结终者模式的结构相对来说还是比较简单的。在整个结构中,主要包含以下部分:

  • 通信对象(多个通信对象之间要互相通信)
  • 中介(处理各个对象之间的通信)

我们通过代码来实现以下我们这个结构。

首先不管是租客还是房东,他们首先得是人,中介我们这里先限定只能对人的租房需求进行处理。

所以我们先定义代表人类的抽象类Human:

1
2
3
4
5
6
7
8
9
10
11
java复制代码package com.heiz.design.mediator;

/**
* @author 小黑说Java
* @ClassName Human
* @Description 代表人类
* @date 2021/11/27
**/
public abstract class Human {
public abstract void xuqiu();
}

然定义一个代表租客的SocialLivestock类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码package com.heiz.design.mediator;

/**
* @author 小黑说Java
* @ClassName SocialLivestock
* @Description 租客 (不要翻译了,类名是“社畜”)
* @date 2021/11/27
**/
public class SocialLivestock extends Human {
@Override
public void xuqiu() {
System.out.println("租客:我要租一间小房子");
}
}

然后定义代表房东的Landlord类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码package com.heiz.design.mediator;

/**
* @author 小黑说Java
* @ClassName Landlord
* @Description 房东
* @date 2021/11/27
**/
public class Landlord extends Human {
@Override
public void xuqiu() {
System.out.println("我有一间房屋需要出租");
}
}

接下来定义代表中介的RoomMediator类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码package com.heiz.design.mediator;

/**
* @author yuriy.lu
* @ClassName RoomMediator
* @Description 中介
* @date 2021/11/27
**/
public class RoomMediator {

public void zulin(Human human) {
human.xuqiu();
System.out.println("中介帮您解决~");
}
}

我们再简单测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码package com.heiz.design.mediator;

/**
* @author 小黑说Java
* @ClassName RoomMediatorTest
* @Description
* @date 2021/11/27
**/
public class RoomMediatorTest {

public static void main(String[] args) {
Human landlord = new Landlord();
Human socialLivestock = new SocialLivestock();

RoomMediator roomMediator = new RoomMediator();
roomMediator.zulin(landlord);
roomMediator.zulin(socialLivestock);
}

}

执行程序,输出结果:

JDK中的中介者模式

  • java.util.Timer类的schedule()方法
  • JUC包中的Executor类的execute()方法
  • 反射包java.lang.reflect中Method类的 invoke()方法

小结

中介者模式主要是为了降低对象之间通信的复杂度,降低耦合度,各个类之间解耦,符合迪米特法则。

但是中介者模式也有缺点,就是当沟通逻辑复杂之后,中介类会变得特别复杂。

扩展阅读:Java中的SOLID原则


我是小黑,一个在互联网“苟且”的程序员。

流水不争先,贵在滔滔不绝

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

手把手教你如何实现代码扩展点设计

发表于 2021-11-27

引言

在我们写业务代码的时候,无可避免肯定会涉及到业务逻辑分支,从而写if-else类似的语句。如果当前逻辑只有一个if-else,则不会过度影响代码可读性,但是当if 代码块的逻辑膨胀、else代码块的逻辑膨胀,那么整体代码的可读性就非常差,因为随着逻辑膨胀,if-else 代码块还会继续出现更多的if-else。

正常情况下,初学者一般都会封装方法,即把if代码块的逻辑封装到另外一个方法里面,这样子看起来恢复了之前的样子,只有一个if-else。但是,这样其实并没有解决根本问题:代码可读性差。 因为在阅读代码的时候,还是会进入封装的方法内,然后方法内会有更多的if-else等着你。随着逻辑分支的增多,人的脑力一般是否无法同时记住之前的逻辑,所以整体代码的可读性还是很差。

那遇到这类问题,我们如何解决呢?

思路

根本原因是我们脑力无法去深入梳理逻辑,归纳逻辑,总是看后面忘了前面,那么如果我们把多个层级的逻辑梳理出来,归纳成单层级逻辑,那么整体的代码可读性就容易理解了。原来的多层级逻辑实例如下图:

tapd_30605327_1624761756_64.png

类似这种,就是多层级逻辑结构,如果只是封装方法,那么作为代码阅读者就会陷入各种if else 内部的方法跳转中,而无法从整体去理解业务。

那么基于这种常见的多层级逻辑,我们应该拆分为单层级逻辑,如下图:

tapd_30605327_1624761968_82.png

这样子,我们在代码处理上,就可以拆分为1个业务逻辑处理接口、4个业务逻辑处理实现类,1个工厂类或者上下文类。 然后在主逻辑代码块上通过工厂类获取封业务逻辑处理接口实例,然后调用处理方法。从而实现业务的封装和抽象,即把这块相关逻辑抽象为一个接口,不同的实现。下次如果再增加一个逻辑,则只要新增一个实现类,在工厂方法新增一个类型标识即可,主逻辑代码不变。

实现方案一

这里以路边的地磁停车位为例,具体的线下业务是在停车场会安装一个地磁硬件,当有车进来的时候会上报一个车辆驶入事件,车出去的时候会上报一个车辆驶出事件,除了这2事件,当车位被占用或者空闲的时候,会上报2个心跳:持续占用和持续空闲

结合上面的业务,不同事件会有不同的处理方式,这里我们会创建一个状态处理接口,该接口有2个方法,一个返回状态枚举、一个处理方法,如下:

1
2
3
4
5
6
arduino复制代码public interface StateHandler {

DeviceStatusEnum state();

void handle(String deviceNo, String code);
}

然后,我们再创建一个工厂类或者叫上下文处理类,通过Spring的构造器注入所有实现StateHandler接口的实现类,然后放入当前实例的缓存map中,另外还提供一个根据枚举返回处理器的方法,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typescript复制代码@Component
public class StateContext {

public Map<DeviceStatusEnum, StateHandler> stateMap = new HashMap<>();

public StateContext(List<StateHandler> stateHandlerList){
stateHandlerList.forEach(handler -> {
stateMap.put(handler.state(), handler);
});
}

public StateHandler getHandler(DeviceStatusEnum state){
return stateMap.get(state);
}
}

接下来就是具体状态的处理实现类,这里只放车辆驶出的处理类,如下:

1
2
3
4
5
6
7
8
9
10
11
12
typescript复制代码@Component
public class OutStateHandler implements StateHandler{
@Override
public DeviceStatusEnum state() {
return DeviceStatusEnum.OUT;
}

@Override
public void handle(String deviceNo, String code) {
// TODO
}
}

这样子,在主逻辑代码只要根据状态类型,从工厂类获取处理器类,然后执行方法即可,如下:

1
2
3
ini复制代码DeviceStatusEnum state = DeviceStatusEnum.get(status.getStatus());
StateHandler handler = stateContext.getHandler(state);
handler.handle(request.getSN(), request.getBerthCode());

这样子即可消除对应的if-else

问题

通过上述的方案一,我们实现了消除基本的if-else,但是我们再深入思考下,方案一会有什么问题?
很明显,这是针对具体业务的一个实现方式,该实现方式需要1个上下文类、1个接口、对应的N个实现类,那么当我们的业务代码有多个不同业务的if-else,每个业务都需要创建2+N个类,造成了类膨胀。

所以,针对该问题,我们还需要对上述的实现方式进行抽象,让它更实用 。

实现方案二(最终解决方案)

通过对方案一实现的思考,我们可以对以下几个点进行优化

  • 针对接口类,我们可以抽象不同业务的接口,该接口只有一个方法,用于返回具体某个业务的枚举类
  • 针对枚举类,不同业务会有不同的枚举类,而枚举类的抽象就是他们的父类:Enum
  • 针对上下文类,原来的map缓存key是具体某个枚举类,value是不同的实现类;而这里为了实现多个业务扩展点,我们key可以设计为枚举类的父类Enum,value为不同实现类的列表

具体的代码如下:

  1. 创建一个抽象接口,用于返回具体某个业务的枚举类
1
2
3
csharp复制代码public interface IExtensionHandler<Y extends Enum>{
Y extension();
}

这里的泛型Y就是具体某个业务的枚举类

  1. 创建一个上下文类的接口,并实现其默认实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码public interface IExtensionHandlerFactory {
/**
* 添加扩展处理器
* @param extensionHandler 处理器
* @param <Y> 扩展点
*/
<Y extends Enum<Y>>void addExtensionHandler(IExtensionHandler<Y> extensionHandler);

/**
* 获取扩展点处理器
* @param extension 扩展点
* @param type 处理器类型
* @param <T> 扩展处理器
* @param <Y> 扩展点
*/
<T extends IExtensionHandler<Y>,Y extends Enum<Y>> T getExtensionHandler(Y extension, Class<T> type);
}

上下文类的默认实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
typescript复制代码@Slf4j
public class DefaultExtensionHandlerFactoryImpl implements IExtensionHandlerFactory, ApplicationContextAware {

private final Map<Enum, List<IExtensionHandler>> serviceListCache = new ConcurrentHashMap<>();
private final Map<ExtensionCacheKey, IExtensionHandler> serviceCache = new ConcurrentHashMap<>();

@Override
public <Y extends Enum<Y>> void addExtensionHandler(IExtensionHandler<Y> extensionHandler) {
Assert.notNull(extensionHandler.extension(), "add extension handler failed, bean class " + extensionHandler.getClass().getName() + " extension is null");
serviceListCache.putIfAbsent(extensionHandler.extension(), new LinkedList<>());
serviceListCache.get(extensionHandler.extension()).add(extensionHandler);
}

@Override
public <T extends IExtensionHandler<Y>, Y extends Enum<Y>> T getExtensionHandler(Y extension, Class<T> type) {
ExtensionCacheKey<Y> cacheKey = new ExtensionCacheKey(extension, type);
IExtensionHandler result = this.serviceCache.get(cacheKey);
if (result == null) {
List<IExtensionHandler> extensionHandlers = serviceListCache.getOrDefault(extension, Collections.synchronizedList(Collections.emptyList()));
for (IExtensionHandler extensionHandler : extensionHandlers) {
if (type.isAssignableFrom(extensionHandler.getClass())) {
result = extensionHandler;
serviceCache.put(cacheKey, result);
break;
}
}
if (result == null) {
log.warn("No IExtensionHandler found by CacheKey : " + cacheKey + " !");
}
}
return (T) result;
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String,IExtensionHandler> handlerMap = applicationContext.getBeansOfType(IExtensionHandler.class);
log.info("疯狂加载扩展ing ...");
long start = System.currentTimeMillis();
handlerMap.forEach((k, v) -> {
//排除组合类自己
if (!this.getClass().isAssignableFrom(v.getClass())) {
addExtensionHandler(v);
}
});
long end = System.currentTimeMillis();
log.info("加载扩展点结束,耗时: {}, 扩展点个数: {}", end - start, handlerMap.size());
}
}

在默认的上下文实现类中,我们还多了一个Map,该Map是为了提高扩展点的查找而设计的,如果只是使用serviceListCache,那么每次根据某个业务的枚举类会返回对应的扩展处理器列表,需要再循环一次才能找到对应的处理器,这里是设计了一个缓存Key: ExtensionCacheKey, 代码实现如下

1
2
3
4
5
6
7
8
9
less复制代码@Data
@AllArgsConstructor
@ToString
@EqualsAndHashCode
public class ExtensionCacheKey<T extends Enum> {

private T extension;
private Class<? extends IExtensionHandler<T>> extensionHandlerClass;
}

一个业务枚举类,可能会有不同类型的扩展处理接口,serviceListCache的value值为什么设计成List和为什么需要设计一个ExtensionCacheKey的原因所在。

最后

这个扩展点设计虽然简单,但是在我实践的项目中有大量的使用,也推荐大家理解并使用,对于复杂业务的处理非常有帮助。

另外扩展点设计还有另外一种方式,基于注解的方式,具体实现可以搜下 COLA 4.0

获取完整源码地址:gitee.com/anyin/shiro…

有问题的话,欢迎添加个人微信好友进行讨论,个人微信: daydaycoupons

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

消息队列入门学习 为什么要使用消息队列? 如何保证消息队列的

发表于 2021-11-27

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

为什么要使用消息队列?

MQ 的应用场景比较多,但是比较核心的应用场景是:解耦、异步、削峰。

  • 解耦:将消息写入消息队列,需要消息的时候自己从消息队列中订阅,从而原系统不需要做任何修改。
  • 异步:将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度
  • 削峰:原系统慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。

消息队列的应用场景

解耦

应用解耦场景一:比如订单系统,下单后需要调配送系统通知发货、调积分系统增加积分等。正常情况下,需要在下单完成后手动写代码调用这些接口,如果某个系统调用失败,则下单失败。这时这些系统是耦合在一起的,其中一个系统出故障,会导致整个下单失败。
如果引入 MQ,下单后,把数据推送到 MQ 中,由配送系统、短信系统自己拉取数据进行消费,这时订单系统和配送系统、短信系统就解耦开了,这两个系统的故障也不会影响到下单的过程,下单后直接给用户返回下单成功。如下图 1、图 2 分别为解耦前后的情况。
消息队列-解耦场景

解耦场景 2:还是订单系统,用户下单后,需要推送订单数据给其他部门系统(如大数据部门)做一些如统计分析类的工作。正常情况下也是需要再下单完成之后,给其他系统推送订单数据,这也会有一些问题,比如大数据部门需要增删传输的数据字段、其他部门也需要这个订单数据、某个部门突然又不需要这些数据了,这些操作都需要修改代码才能实现,就跟其他系统耦合在一起了。
引入 MQ 后,就不在需要订单系统主动调其他系统的接口推送数据了,订单系统只需要把数据推送到 MQ 中,其他哪些部门哪些系统需要订单数据,自己编写代码去 MQ 拉取即可。如下图:
消息队列-解耦场景

异步

不知道大家有没有调用过第三方的系统,有过经验的童鞋都知道,说起第三方系统脑海里想到的一个字就是:坑!你永远不知道它啥时候就挂了,也不知道啥时候响应就会慢得跟蜗牛一样,稳定性实在是不好吐槽,五味杂陈啊。
就好像订单系统,你的短信发送功能是第三方短信系统提供的,发货功能是第三方物流系统提供的。当接口故障的时候你需要自己编写复杂的重试逻辑,不管你是同步还是异步调用;重试失败后还要把数据持久化下来,用定时任务在未来某个时间重新推送,因为可能只是当时人家系统刚好不可用了,过个半小时一小时系统恢复了,那你还是需要重新推送的。这一整个逻辑是会复杂,还会影响性能。
如果引入 MQ,那么我们只需把消息推送到 MQ 中,再从 MQ 中消费即可。如果接口不可用,直接给 MQ 返回消费失败,下次还可以重新拉取消息进行消费,不再需要手动编写复杂的重试代码等。如下图
消息队列-异步

削峰

多数情况下,系统的瓶颈都会在数据库,假设数据库每秒可以支撑 6000 个请求,在一些如秒杀、双十一等高峰期场景中,每秒的请求达到了 3W,那么这时数据库是扛不住这么高的并发的,而 MQ 一般抗住几万的并发没有任何问题。
这时我们就可以引入 MQ,请求到达后先写入 MQ 中,再从 MQ 中慢慢消费消息落库,此时写入 MQ 的请求仍然是每秒 3W,但是从 MQ 消费的消息控制在每秒 6000,积压的请求可以在后面空闲期慢慢消费完成。这样,每秒 3W 的请求也可以扛下来,如下图
消息队列-削峰

消息队列有什么缺点

通常来说,应用一项技术,解决了一些问题的同时,也必然会引出其他的新的问题。只不过解决新问题会比旧问题更容易些,也就是新技术的引用是利大于弊的。引入 MQ,也同样会导致一些问题,它的缺点有以下几个:

  • 系统可用性降低

我们知道,系统引入的外部依赖越多,就越容易挂掉。你看啊,本来你有 ABCD 四个系统,A 直接调用 BCD 三个系统的接口就 OK 了,但是你引入了一个 MQ,结果 MQ 挂了,搞得整套系统都不可用了。

  • 系统复杂度提高

还是 ABCD 四个系统,本来是用的技术栈都蛮简单的,结果你引入了 MQ,导致经常会出现一些数据丢失、数据重复消费等奇奇怪怪的问题,导致系统的复杂度大大提高。

  • 一致性问题

还是 ABCD 四个系统,本来 A 调用系统 BCD,调用 B 失败就不再调用系统 CD,直接回滚数据了。结果你引入 MQ,系统 A 把数据推送给 BCD 就直接返回成功了,但是只有 BC 消费消息成功了,系统 D 消费时写入失败了,你咋保证系统的数据一致性?
既然消息队列会产生这些问题,我们使用 MQ 的时候就必须要解决这些问题,否则,我们实际上是在给未来的自己挖坑。下面我列出几个常见的需要解决的消息队列问题:

  • 如何保证消息队列的高可用?
  • 如何保证消息不被重复消费?
  • 如何处理消息丢失的问题?
  • 如何保证消息的顺序性?
  • 如何处理消息队列大量消息积压?

消息队列的选型

目前比较流行的消息队列中间件有:ActiveMQ、RabbitMQ、RocketMQ、Kafka。这里简单对比下不同的消息队列的优缺点:

  • ActiveMQ:单机吞吐量:万级;时效性:毫秒级;可用性:高;消息可靠性:小概率丢数据;功能支持:极其完备
  • RabbitMQ:单机吞吐量:万级;时效性:微秒级;可用性:高;消息可靠性:基本不丢;功能支持:erlang 开发
  • RocketMQ:单机吞吐量:十万级;时效性:毫秒级;可用性:非常高;消息可靠性:可配置 0 丢失;功能支持:分布式
  • Kafka:单机吞吐量:十万级;时效性:毫秒级;可用性:非常高;消息可靠性:可配置 0 丢失;功能支持:分布式,一般配合大数据类的系统来进行实时数据计算、日志采集等场景,行业内的事实标准

通过以上的对比,ActiveMQ 现在使用得越来越少了,社区也不太活跃了;RabbitMQ 是开源的,也很稳定,社区也比较活跃,但是使用 erlang 开发,对公司来说不太可控,建议中小型公司使用;RabbitMQ 是使用 Java 开发的,社区相对比较活跃,对于公司也比较可控,毕竟能修改它的源码的大神也很多;Kafka 一般用在大数据领域的实时计算、日志采集等场景。

如何保证消息队列的高可用?

什么是高可用性?

对于高可用性,维基百科上是这么描述的:高可用性(英语:high availability,缩写为 HA),IT 术语,指系统无中断地执行其功能的能力,代表系统的可用性程度。

如何保证消息队列的高可用?

RocketMQ 高可用

RocketMQ 进程我们一般称为 Broker,Broker 通常是集群部署的,每个 Broker 需要注册到 NameServer 中。从这里我们可以知道高可用有两个地方需要解决,一是 Broker 挂了怎么办,二是 NameServer 挂了怎么办。
对于 NameServer,它的高可用保障是集群化部署,各个 NameServer 之间互不通信,每个 NameServer 都有一份完整的 Broker 路由信息。当某一个 NameServer 挂掉后,对集群没有任何影响,只要还有一个 NameServer 存活,就能提供完整的服务。
对于 Broker,它高可用保障是主从架构和多副本策略。Broker 有 Master 和 Slave 两种角色,Master 和 Salve 上的数据是一模一样的,Master Broker 收到消息后会同步给 Slave Broker。每个 Master Broker 和 Slave Broker 都会向所有 NameServer 注册。Master 宕机了,会重新选举一个 Slave 作为 Master 继续提供写服务,对读服务无影响,每个 Slave 都可以读但是不可以写;Slave 宕机了,对服务无影响。

RabbitMQ 高可用

RabbitMQ 不是分布式的,它有三种模式:单机模式、普通集群模式、镜像集群模式。其中只有镜像集群模式是能够保障高可用的,而生产环境不会使用单机模式。

  • 普通集群模式:集群中所有节点都存储队列元数据,但是只有一个节点存队列内容,此节点宕机则数据会丢失
  • 镜像集群模式:集群中每个节点都存储完整的队列元数据和队列内容,是一份完整的镜像数据。某个节点宕机,对整体服务没有影响。缺点是不支持分布式,每台机器都有一份完整的数据。

Kafka 高可用

Kafka 高可用与 RocketMQ 类似,都是通过主从多备份架构实现的。每个 leader broker 可以有多个 follower broker,生产者生产和消费者消费均操作 leader broker 节点,当 leader broker 宕机后自动切换 follower 为 leader。

如何保证消息不被重复消费?

为什么会出现重复消费的问题?

RabbitMQ、RocketMQ、Kafka 都有可能出现重复消费的问题,导致重复消费的原因可能出现在生产者,也可能出现在 MQ 或 消费者。这里说的重复消费问题是指同一个数据被执行了两次,不单单指 MQ 中一条消息被消费了两次,也可能是 MQ 中存在两条一模一样的消费。

  • 生产者:生产者可能会重复推送一条数据到 MQ 中,为什么会出现这种情况呢?也许是一个 Controller 接口被重复调用了 2 次,没有做接口幂等性导致的;也可能是推送消息到 MQ 时响应比较慢,生产者的重试机制导致再次推送了一次消息。
  • MQ:在消费者消费完一条数据响应 ack 信号消费成功时,MQ 突然挂了,导致 MQ 以为消费者还未消费该条数据,MQ 恢复后再次推送了该条消息,导致了重复消费。
  • 消费者:消费者已经消费完了一条消息,正准备但是还未给 MQ 发送 ack 信号时,此时消费者挂了,服务重启后 MQ 以为消费者还没有消费该消息,再次推送了该条消息。

如何保证消息队列的幂等性?

消息的重复消费问题实际上涉及到消息者消费消息的幂等性问题。重复消费问题通常在消费者端解决,当然生产者端也最好简单控制下不要生产重复数据,但是一般情况下 MQ 是允许存在多条一样的数据的,只是消费端就不允许消费两条一样的数据,所以幂等性保障通常都是在消费者端实现。

那么消费者怎么解决重复消费问题呢?这个问题解决起来也比较简单,这里提供两种方法

  • 状态判断法:消费者消费数据后把消费数据记录在 redis 中,下次消费时先到 redis 中查看是否存在该消息,存在则表示消息已经消费过,直接丢弃消息。
  • 业务判断法:通常数据消费后都需要插入到数据库中,使用数据库的唯一性约束防止重复消费。每次消费直接尝试插入数据,如果提示唯一性字段重复,则直接丢失消息。一般都是通过这个业务判断的方法就可以简单高效地避免消息的重复处理了。

总结

上面描述了为什么会出现重复消费的问题,生产者、MQ、消费者都有可能导致消息的重复消费。重复消费问题通常是在消费者端解决,而我们一般默认 MQ 中是有可能存在两条一模一样的数据的,消费者要做幂等性处理。而幂等性处理最简单高效的处理是插表时根据唯一性字段判断,如订单号等。

如何处理消息丢失的问题?

为什么消息会丢失?

跟消息重复问题类似,消息丢失也可能出现在生产者、MQ、消费者三者中。这三者导致消息丢失的原因是什么呢?

  • 生产者:生产者推送消息到 MQ 中,由于网络抖动等原因消息没有推送到 MQ 中,或者消息推送到 MQ 中了但是 MQ 内部出错了,导致消息丢失。
  • MQ:MQ 接收到消息后先把消息暂存在 OS Cache 中,消费者还没消费的时候 MQ 自己挂了,导致消息丢失。
  • 消费者:消费者消费到了这条消息,但是还没来得及处理,消费者自己挂了,但是消费者已经告诉了 MQ 自己已经消费完了,导致消息丢失。

如何解决消息丢失的问题

不同的消息队列解决消息丢失的方法是不同的,下面分别介绍不同 MQ 是如何解决消息丢失问题的。

RabbitMQ

生产者导致消息丢失

RabbitMQ 有两种方案可以避免消息丢失,一种是 RabbitMQ 的事务机制,一种是 Confirm 模式。这里先看一下事务机制。

RabbitMQ 客户端中 Channel 接口有这么几个函数,channel.txSelect 用来开启一个事务,channel.txCommit 用来提交事务,channel.txRollback 用来回滚事务。为了避免消息丢失,我们可以在发送消息前,先开启执行 txSelect 方法开启一个事务,接着发送消息,如果消息投递失败,执行 txRollback 回滚事务,再执行重试操作重新发送,如果消息投递成功,执行 txCommit 方法提交事务。

这个方案可以保证我们的消息一定是投递成功的,但是几乎没有人使用这种方案。因为这个方案是同步阻塞的,也就是一条消息发送后,一定要等到 MQ 回应之后执行了提交或回滚事务操作,才能继续往下执行。大致过程如下图所示:
消息丢失-RabbitMQ生产者
RabbitMQ 还提供了另一种方法避免生产者消息丢失问题,那就是 Confirm 模式。Confirm 模式是这样子的,生产者发送消息后,不需要等待 MQ 的回应,MQ 接收成功后,会回调生产者的 ack 接口通知生产者消息投递成功了,如果 MQ 接收失败,会回调 nack 接口通知生产者消息投递失败了,生产者可以重新对这条消息进行投递。大致过程如下图所示:
消息丢失-RabbitMQ生产者

RabbitMQ 导致消息丢失

RabbitMQ 自己弄丢了数据是由于持久化导致的。通常 RabbitMQ 接收到消息之后写入 OS Cache 中,就会给生产者返回接收成功的回应,这时如果 RabbitMQ 挂了,消息也就丢失了。解决方法可以结合生产者的 Confirm 模式,配置 RabbitMQ 持久化到磁盘之后,才给生产者返回 ack 信号。

消费者导致消息丢失

RabbitMQ 在消费者端弄丢数据,是由于 RabbitMQ 的默认自动提交 ack 导致的。解决方法就是关闭 RabbitMQ 的自动响应 ack 即可。这样消息没有处理完成,消费者挂了,RabbitMQ 会认为消息没有处理成功,会再次推送消息给消费者处理。
消息丢失-RabbitMQ消费者

Kafka

生产者导致消息丢失

对于 Kafka 来说,生产者基本不会弄丢消息,因为生产者发送消息会等待 Kafka 响应成功,如果响应失败,生产者会自动不断地重试。

Kafka 弄丢了数据

Kafka 通常会一台 leader + 两台 follower,当生产者消息刚写入 leader 成功,但是还没同步到 follower 时,leader 宕机了,此时会重新选举 leader,新的 leader 由于还未同步到这条数据,导致该条消息丢失。

解决办法是做一些配置,当有其他 follower 同步到了消息后才通知生产者消息接收成功了。配置如下:

  • 给 topic 设置 replication.factor参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。
  • 在 Kafka 服务端设置 min.insync.replicas 参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower。
  • 在 producer 端设置 acks=all :这个是要求每条数据,必须是写入所有 replica 之后,才能认为是写成功了。

按上面的配置配置后,就可以保证在 Kafka Broker 端就算 leader 故障了,进行新 leader 选举切换时,也不会丢失数据。

消费者导致消息丢失

Kafka 消费端弄丢数据原因跟 RabbitMQ 类似,Kafka 消费者会在接收到消息的时候,会自动提交一个 offset 给 Kafka,告诉 Kafka 消息已经处理了。处理方法也跟 RabbitMQ 类似,关闭 offset 的自动提交即可。

RocketMQ

RocketMQ 导致数据丢失的原因与前面的 RabbitMQ 和 Kafka 都很类似。生产者就是因为网络抖动等原因消息投递失败,或者 RocketMQ 自身的 Master 节点故障,主备切换故障之类的,消费者则有可能是异步处理导致还未处理成功就给 RocketMQ 提交了 offset 标识消息已处理了。

在 RocketMQ 中,事务消息可以保证消息零丢失。RocketMQ 的事务消息流程大致如下图所示:
消息丢失-RocketMQ

在上面的事务消息流程中,基于这三个业务流程:发送 half 消息 -> 处理其他业务 -> commit/rollback。我们来讨论下面的几种情况:

  • 万一生产者发送 half 消息失败,怎么办?

可以做重试或记录消息到如文件、数据库等地方,直接给用户返回失败,本次请求失败。

  • 万一生产者发送 half 消息成功,但是处理其他业务失败,又该怎么办呢?

生产者发送 rollback 请求回滚 RocketMQ 中该条消息,本次请求失败。

  • 万一生产者发送 half 消息成功,但是 RocketMQ 由于某些原因如网络超时等导致没有响应,怎么处理?

由于 half 消息已发送成功,此时 RocketMQ 中已经有该条消息了,RocketMQ 会有一个补偿机制,补偿机制会回调你开发好的一个接口,询问你这条消息是要 commit 还是 rollback。

  • 万一生产者发送 half 消息成功,但是请求 commit 或 rollback 的时候失败了呢?

这个问题与上面的问题一样,都是通过 RocketMQ 的补偿机制来处理。

总结

上文分别从生产者、MQ 自身、消费者介绍了导致消息丢失的原因,消息丢失问题是一个比较常见但有必须解决的问题,通常使用消息队列的业务都是比较重要的业务,不能接受数据的丢失。

接着介绍了不同的 MQ 是如何解决消息丢失问题的。消费端导致的消息丢失都是由于数据还未处理成功确提前通知 MQ 消息已经处理成功了,禁止自动提交或异步操作即可,处理起来比较简单;生产者和 MQ 自身导致的消息丢失则比较难处理,RabbitMQ 使用了 Confirm 模式避免消息丢失;Kafka 则配置所有 follower 同步成功才给生产者响应推送消息成功;RocketMQ 则使用事务消息来保证消息的零丢失,针对不同的异常情况还提供了补偿机制进行处理。

如何保证消息的顺序性?

为什么出现顺序错乱?

在生产中经常会有一些类似报表系统这样的系统,需要做 MySQL 的 binlog 同步。比如订单系统要同步订单表的数据到大数据部门的 MySQL 库中用于报表统计分析,通常的做法是基于 Canal 这样的中间件去监听订单数据库的 binlog,然后把这些 binlog 发送到 MQ 中,再由消费者从 MQ 中获取 binlog 落地到大数据部门的 MySQL 中。

在这个过程中,可能会有对某个订单的增删改操作,比如有三条 binlog 执行顺序是增加、修改、删除;消费者愣是换了顺序给执行成删除、修改、增加,这样能行吗?肯定是不行的。

RabbitMQ 消息顺序错乱

对于 RabbitMQ 来说,导致上面顺序错乱的原因通常是消费者是集群部署,不同的消费者消费到了同一订单的不同的消息,如消费者 A 执行了增加,消费者 B 执行了修改,消费者 C 执行了删除,但是消费者 C 执行比消费者 B 快,消费者 B 又比消费者 A 快,就会导致消费 binlog 执行到数据库的时候顺序错乱,本该顺序是增加、修改、删除,变成了删除、修改、增加。

如下图是 RabbitMQ 可能出现顺序错乱的问题示意图:
消息顺序错乱-RabbitMQ

Kafka 消息顺序错乱

对于 Kafka 来说,一个 topic 下同一个 partition 中的消息肯定是有序的,生产者在写的时候可以指定一个 key,通过我们会用订单号作为 key,这个 key 对应的消息都会发送到同一个 partition 中,所以消费者消费到的消息也一定是有序的。

那么为什么 Kafka 还会存在消息错乱的问题呢?问题就出在消费者身上。通常我们消费到同一个 key 的多条消息后,会使用多线程技术去并发处理来提高消息处理速度,否则一条消息的处理需要耗时几十 ms,1 秒也就只能处理几十条消息,吞吐量就太低了。而多线程并发处理的话,binlog 执行到数据库的时候就不一定还是原来的顺序了。

如下图是 Kafka 可能出现乱序现象的示意图:
消息顺序错乱-Kafka

RocketMQ 消息顺序错乱

对于 RocketMQ 来说,每个 Topic 可以指定多个 MessageQueue,当我们写入消息的时候,会把消息均匀地分发到不同的 MessageQueue 中,比如同一个订单号的消息,增加 binlog 写入到 MessageQueue1 中,修改 binlog 写入到 MessageQueue2 中,删除 binlog 写入到 MessageQueue3 中。

但是当消费者有多台机器的时候,会组成一个 Consumer Group,Consumer Group 中的每台机器都会负责消费一部分 MessageQueue 的消息,所以可能消费者 A 消费了 MessageQueue1 的消息执行增加操作,消费者 B 消费了 MessageQueue2 的消息执行修改操作,消费者 C 消费了 MessageQueue3 的消息执行删除操作,但是此时消费 binlog 执行到数据库的时候就不一定是消费者 A 先执行了,有可能消费者 C 先执行删除操作,因为几台消费者是并行执行,是不能够保证他们之间的执行顺序的。

如下图是 RocketMQ 可能出现乱序现象的示意图:
消息顺序错乱-RocketMQ

如何保证消息的顺序性?

知道了为什么会出现顺序错乱之后,就要想办法保证消息的顺序性了。从前面可以知道,顺序错乱要么是由于多个消费者消费到了同一个订单号的不同消息,要么是由于同一个订单号的消息分发到了 MQ 中的不同机器中。不同的消息队列保证消息顺序性的方案也各不相同。

RabbitMQ 保证消息的顺序性

RabbitMQ 的问题是由于不同的消息都发送到了同一个 queue 中,多个消费者都消费同一个 queue 的消息。解决这个问题,我们可以给 RabbitMQ 创建多个 queue,每个消费者固定消费一个 queue 的消息,生产者发送消息的时候,同一个订单号的消息发送到同一个 queue 中,由于同一个 queue 的消息是一定会保证有序的,那么同一个订单号的消息就只会被一个消费者顺序消费,从而保证了消息的顺序性。
如下图是 RabbitMQ 保证消息顺序性的方案:
消息顺序-RabbitMQ

Kafka 保证消息的顺序性

Kafka 从生产者到消费者消费消息这一整个过程其实都是可以保证有序的,导致最终乱序是由于消费者端需要使用多线程并发处理消息来提高吞吐量,比如消费者消费到了消息以后,开启 32 个线程处理消息,每个线程线程处理消息的快慢是不一致的,所以才会导致最终消息有可能不一致。

所以对于 Kafka 的消息顺序性保证,其实我们只需要保证同一个订单号的消息只被同一个线程处理的就可以了。由此我们可以在线程处理前增加个内存队列,每个线程只负责处理其中一个内存队列的消息,同一个订单号的消息发送到同一个内存队列中即可。
如下图是 Kafka 保证消息顺序性的方案:
消息顺序-Kafka

RocketMQ 保证消息的顺序性

RocketMQ 的消息乱序是由于同一个订单号的 binlog 进入了不同的 MessageQueue,进而导致一个订单的 binlog 被不同机器上的 Consumer 处理。

要解决 RocketMQ 的乱序问题,我们只需要想办法让同一个订单的 binlog 进入到同一个 MessageQueue 中就可以了。因为同一个 MessageQueue 内的消息是一定有序的,一个 MessageQueue 中的消息只能交给一个 Consumer 来进行处理,所以 Consumer 消费的时候就一定会是有序的。
如下图是 RocketMQ 保证消息顺序性的方案:
消息顺序-RocketMQ

总结

上文介绍了不同的消息队列出现顺序错乱问题的原因,也分别给出了常用消息队列保证消息顺序性的解决方案。消息的顺序性其实是 MQ 中比较值得注意的一个常见问题,特别是对于同一订单存在多条消息的这种情况,不同的执行顺序可能导致完全不同的结果,顺序的错乱可能会导致业务上的很多问题,而且往往这些问题还是比较难排查的。不过也不是所有消息都需要考虑它的全局顺序性,不相关的消息就算顺序错乱对业务也是毫无影响的,需要根据具体问题来看。

如何处理消费者故障导致的百万消息积压?

我们先思考一下导致消息队列消息百万积压都是怎么造成的。首先,可能是消费端出问题了,比如宕机等情况,或者消费端消费突然变得极慢,就会导致消息不断积压;也有可能是消费端依赖的服务器挂掉了,比如依赖的 NoSQL/MySQL 挂掉了,导致消费者自己没法正常运作了,导致消息的积压。

怎么解决百万消息积压问题?

如果积压的这些消息是允许丢失的,那么很简单,马上修改消费者代码直接丢弃消息即可,这个速度会很快,所以积压消息处理起来也非常地迅速。

但是往往来说,很多消息都是不允许直接丢弃的。所以我们还是需要快速地处理,怎么快速地处理呢?最简单高效的办法就是临时部署足够多的消费者,一起来消费这些消息。当然,在此之前,需要先恢复系统的正常服务。

比如对于 RocketMQ 来说,原本一个 Topic 只有 4 个 MessageQueue,对应 4 个消费者。很明显如果消息积压了百万条,那么 4 个消息消费是不能够快速处理掉这一批积压消息的。我们可以修改 4 台原消费者代码,不直接处理消息,而是先把消息发送到一台新的 RocketMQ 中,这台新的 RocketMQ 一个 Topic 有 20 个 MessageQueue,这时我们可以临时部署 20 个消费者一起消费这批数据,消息的消费速度提高了 5 倍,很快积压的百万消息都会被处理完毕。处理完积压的消息之后就可以下线临时部署的 20 台消费者了。
消息积压

参考
www.infoq.cn/profile/BF1…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

PyQt5案例汇总(完整版) 菜单栏和工具栏 布局管理 事件

发表于 2021-11-27

起步

PyQt5是一套绑定Qt5的应用程序框架。他在Python 2.x和3.x中都是可用的。该教程使用的是Python3.x。

Qt库是一套最有用的GUI库。

PyQt5是作为一套Python模块实现的。他已经超过620个类和6000个函数与方法。他是一个运行在所有主流操作系统上的多平台组件,包括Unix,Windows和Mac OS。

说明

下面小编就给大家提供一些简单的pyqt5的案例,如有需要拿走不谢!!!

本文转载from:PyQt5-Chinese-tutorial

菜单栏和工具栏

01窗口居中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
python复制代码
# 导入需要的包和模块
import sys
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QDesktopWidget
# QDesktopWidget这个库提供了用户的桌面信息,包括屏幕的大小
from PyQt5.QtWidgets import QApplication

# 创建一个类
class Ex(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.resize(250,150)
self.center()
# 这个方法调用我们下面写的,实现对话框居中的方法
self.setWindowTitle('chuangkou要居中')
self.show()

def center(self):
qr = self.frameGeometry()
# 得到了主窗口大小
print('qr:',qr)
cp = QDesktopWidget().availableGeometry().center()
# 获取显示器的分辨率,然后得到中间点的位置
print('cp:',cp)
qr.moveCenter(cp)
# 然后把自己的窗口的中心点放到qr的中心点
self.move(qr.topLeft())

app = QApplication(sys.argv)
demo1 = Ex()
sys.exit(app.exec_())

02 状态栏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
python复制代码
# 导入需要的包和模块
import sys
# from PyQt5.QtWidgets import QWidget
# from PyQt5.QtWidgets import QDesktopWidget
# QDesktopWidget这个库提供了用户的桌面信息,包括屏幕的大小
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QMainWindow


class Ex(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
# 状态栏是由这个创建的
self.statusBar().showMessage('准备')
# 调用QtGui.QMainWindow 类的 statusBar()方法
#3 创建状态栏.第一次调用创建一个状态栏,返回一个状态栏对象.
#3 showMessage()方法在状态栏上显示一条信息
self.setGeometry(300,300,250,150)
self.setWindowTitle('标题还是要取的')
#显示
self.show()

app = QApplication(sys.argv)
demo1 = Ex()
sys.exit(app.exec_())

03菜单栏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
python复制代码
import sys
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QAction
from PyQt5.QtWidgets import qApp
from PyQt5.QtGui import QIcon

class Ex(QMainWindow):

def __init__(self):
super(Ex, self).__init__()
self.initUI()

def initUI(self):
exitAct = QAction(QIcon("exit.png"),'&Exit',self)
print(exitAct)
exitAct.setShortcut("ctrl+q")
exitAct.setStatusTip('tuichu应用')
exitAct.triggered.connect(qApp.quit)

self.statusBar()

menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAct)

self.setGeometry(300,300,399,200)
self.setWindowTitle('决赛你电脑的')
self.show()

app = QApplication(sys.argv)
demo1 = Ex()
sys.exit(app.exec_())

04子菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
python复制代码
import sys
from PyQt5.QtWidgets import QMainWindow, QAction, QMenu, QApplication

class Example(QMainWindow):

def __init__(self):
super().__init__()
self.initUI()


def initUI(self):

menubar = self.menuBar()
fileMenu = menubar.addMenu('File')
impMenu = QMenu('Import', self)
impAct = QAction('Import mail', self)
impMenu.addAction(impAct)

newAct = QAction('New', self)

fileMenu.addAction(newAct)
fileMenu.addMenu(impMenu)

self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Submenu')
self.show()


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

05 勾选菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
python复制代码
import sys
from PyQt5.QtWidgets import QMainWindow, QAction, QApplication

class Example(QMainWindow):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.statusbar = self.statusBar()
self.statusbar.showMessage('Ready')

menubar = self.menuBar()
viewMenu = menubar.addMenu('View')

# 本例创建了一个行为菜单。这个行为/动作能切换状态栏显示或者隐藏。
viewStatAct = QAction('View statusbar', self, checkable=True)
viewStatAct.setStatusTip('View statusbar') # 用checkable选项创建一个能选中的菜单。
viewStatAct.setChecked(True) # 默认设置为选中状态
viewStatAct.triggered.connect(self.toggleMenu)

viewMenu.addAction(viewStatAct)
# 依据选中状态切换状态栏的显示与否。
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Check menu')
self.show()

def toggleMenu(self, state):

if state:
self.statusbar.show()
else:
self.statusbar.hide()

app = QApplication(sys.argv)
demo1 = Example()
sys.exit(app.exec_())

06 右键菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
python复制代码
import sys
from PyQt5.QtWidgets import QMainWindow, qApp, QMenu, QApplication


class Example(QMainWindow):

def __init__(self):
super().__init__()

self.initUI()

def initUI(self):
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Context menu')
self.show()

def contextMenuEvent(self, event):
cmenu = QMenu(self)

newAct = cmenu.addAction("New")
print(newAct)
opnAct = cmenu.addAction("Open")
print(opnAct)
quitAct = cmenu.addAction("Quit")
action = cmenu.exec_(self.mapToGlobal(event.pos()))

if action == quitAct:
qApp.quit()
elif action == opnAct:
print('打开就打开')
elif action == newAct:
print('新建就新建')


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

07工具栏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
python复制代码
# 菜单栏包含了所有的命令,工具栏就是常用的命令的集合。

import sys
from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication
from PyQt5.QtGui import QIcon

class Example(QMainWindow):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

exitAct = QAction(QIcon('logo.png'), 'Exit', self)
exitAct.setShortcut('Ctrl+Q')
exitAct.triggered.connect(qApp.quit)
# 和上面的菜单栏差不多,这里使用了一个行为对象,
# 这个对象绑定了一个标签,一个图标和一个快捷键。
# 这些行为被触发的时候,会调用QtGui.QMainWindow的quit方法退出应用。
self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(exitAct)

self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Toolbar')
self.show()


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
# 上面的例子中,我们创建了一个工具栏这个工具栏只有一个退出应用的动作

08主窗口(啥都有的呢)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
python复制代码
# 本模块的功能:<>
import sys
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QTextEdit
from PyQt5.QtWidgets import QAction
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QIcon


class Example(QMainWindow):

def __init__(self):
super().__init__()
self.initUI()


def initUI(self):

textEdit = QTextEdit()
self.setCentralWidget(textEdit)

exitAct = QAction(QIcon('logo.png'), '退退退', self)
exitAct.setShortcut('Ctrl+Q')
exitAct.setStatusTip('退出应用')
exitAct.triggered.connect(self.close)

self.statusBar()

menubar = self.menuBar()
fileMenu = menubar.addMenu('文件')
fileMenu.addAction(exitAct)

toolbar = self.addToolBar('退出')
toolbar.addAction(exitAct)

self.setGeometry(300, 300, 350, 250)
self.setWindowTitle('代码编辑工具')
self.show()


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

布局管理

09绝对定位的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
python复制代码
import sys
from PyQt5.QtWidgets import QWidget, QLabel, QApplication

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

lbl1 = QLabel('Zetcode', self)
lbl1.move(15, 10)

lbl2 = QLabel('tutorials', self)
lbl2.move(35, 40)

lbl3 = QLabel('for programmers', self)
lbl3.move(55, 70)

self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Absolute')
self.show()


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

# 绝对定位其实说白了就是使用相对于原点的像素来进行计算

10 盒子布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
python复制代码
import sys
from PyQt5.QtWidgets import (QWidget, QPushButton,
QHBoxLayout, QVBoxLayout, QApplication)


class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

okButton = QPushButton("OK")
cancelButton = QPushButton("Cancel")

hbox = QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(okButton)
hbox.addWidget(cancelButton)

vbox = QVBoxLayout()
vbox.addStretch(1)
vbox.addLayout(hbox)

self.setLayout(vbox)

self.setGeometry(300, 300, 300, 150)
self.setWindowTitle('Buttons')
self.show()


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

11 栅格布局(表格)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
python复制代码
import sys
from PyQt5.QtWidgets import (QWidget, QGridLayout,
QPushButton, QApplication)


class Example(QWidget):

def __init__(self):
super().__init__()
self.initUI()


def initUI(self):

grid = QGridLayout()
self.setLayout(grid)

names = ['Cls', 'Bck', '', 'Close',
'7', '8', '9', '/',
'4', '5', '6', '*',
'1', '2', '3', '-',
'0', '.', '=', '+']

positions = [(i,j) for i in range(5) for j in range(4)]

for position, name in zip(positions, names):

if name == '':
continue
button = QPushButton(name)
grid.addWidget(button, *position)

self.move(300, 150)
self.setWindowTitle('Calculator')
self.show()


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

12 制作提交反馈信息的布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
python复制代码
import sys
from PyQt5.QtWidgets import \
(QWidget, QLabel, QLineEdit, QTextEdit, QGridLayout, QApplication)


class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()

def initUI(self):
title = QLabel('Title')
author = QLabel('Author')
review = QLabel('Review')

titleEdit = QLineEdit()
authorEdit = QLineEdit()
reviewEdit = QTextEdit()

grid = QGridLayout()
grid.setSpacing(10)

grid.addWidget(title, 1, 0)
grid.addWidget(titleEdit, 1, 1)

grid.addWidget(author, 2, 0)
grid.addWidget(authorEdit, 2, 1)

grid.addWidget(review, 3, 0)
grid.addWidget(reviewEdit, 3, 1, 5, 1)

self.setLayout(grid)

self.setGeometry(300, 300, 350, 300)
self.setWindowTitle('Review')
self.show()


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

事件和信号

13 信号和槽机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
python复制代码
'''
事件
signals and slots 被其他人翻译成信号和槽机制,(⊙o⊙)…我这里还是不翻译好了。
所有的应用都是事件驱动的。事件大部分都是由用户的行为产生的,当然也有其他的事件产生方式,
比如网络的连接,窗口管理器或者定时器等。调用应用的exec_()方法时,应用会进入主循环,主循环会监听和分发事件。
在事件模型中,有三个角色:
事件源
事件
事件目标
事件源就是发生了状态改变的对象。事件是这个对象状态改变的内容。
事件目标是事件想作用的目标。事件源绑定事件处理函数,然后作用于事件目标身上。
PyQt5处理事件方面有个signal and slot机制。Signals and slots用于对象间的通讯。
事件触发的时候,发生一个signal,slot是用来被Python调用的
(相当于一个句柄?这个词也好恶心,就是相当于事件的绑定函数)slot只有在事件触发的时候才能调用。
'''

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import \
(QWidget, QLCDNumber, QSlider, QVBoxLayout, QApplication)


class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

lcd = QLCDNumber(self)
print("lcd:",lcd)
sld = QSlider(Qt.Horizontal, self)
print("sld",sld)
vbox = QVBoxLayout()
print(vbox)
vbox.addWidget(lcd)
vbox.addWidget(sld)

self.setLayout(vbox)
print(lcd.display)
sld.valueChanged.connect(lcd.display)

self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('信号和槽机制的')
self.show()


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

14 重构事件处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
python复制代码
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QApplication

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('事件的手')
self.show()


def keyPressEvent(self, e):

if e.key() == Qt.Key_Escape:
self.close()


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

15 事件对像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
python复制代码
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QApplication, QGridLayout, QLabel

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

grid = QGridLayout()
grid.setSpacing(10)

x = 0
y = 0

self.text = "x: {0}, y: {1}".format(x, y)

self.label = QLabel(self.text, self)
grid.addWidget(self.label, 0, 0, Qt.AlignTop)

self.setMouseTracking(True)

self.setLayout(grid)

self.setGeometry(300, 300, 350, 200)
self.setWindowTitle('Event object')
self.show()


def mouseMoveEvent(self, e):

x = e.x()
y = e.y()

text = "x: {0}, y: {1}".format(x, y)
self.label.setText(text)


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

16事件发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
python复制代码
import sys
from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication


class Example(QMainWindow):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

btn1 = QPushButton("按钮老大", self)
btn1.move(30, 50)

btn2 = QPushButton("按钮老二", self)
btn2.move(150, 50)

btn1.clicked.connect(self.buttonClicked)
btn2.clicked.connect(self.buttonClicked)

self.statusBar()

self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('事件发送')
self.show()


def buttonClicked(self):

sender = self.sender()
self.statusBar().showMessage(sender.text() + '被按那儿了')


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

17 信号发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
python复制代码
import sys
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QMainWindow, QApplication


class Communicate(QObject):

closeApp = pyqtSignal()


class Example(QMainWindow):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.c = Communicate()
self.c.closeApp.connect(self.close)

self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('Emit signal')
self.show()


def mousePressEvent(self, event):

self.c.closeApp.emit()


if __name__ == '__main__':

app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

对话框

18 对话框(能够输入文字呦呦)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
python复制代码
from PyQt5.QtWidgets import (QWidget, QPushButton, QLineEdit,
QInputDialog, QApplication)
import sys

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.btn = QPushButton('Dialog', self)
self.btn.move(20, 20)
self.btn.clicked.connect(self.showDialog)

self.le = QLineEdit(self)
self.le.move(130, 22)

self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('Input dialog')
self.show()


def showDialog(self):

text, ok = QInputDialog.getText(self, 'Input Dialog',
'Enter your name:')

if ok:
self.le.setText(str(text))

print(text+"哈哈")


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

19 选取颜色(NB坏了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
python复制代码
# 本模块的功能:<QColorDialog提供颜色的选择>
# TODO 这个厉害,直接调用系统的颜色选择框
# TODO 强,实在是强
from PyQt5.QtWidgets import (QWidget, QPushButton, QFrame,
QColorDialog, QApplication)
from PyQt5.QtGui import QColor
import sys

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

col = QColor(0, 0, 0)

self.btn = QPushButton('Dialog', self)
self.btn.move(20, 20)

self.btn.clicked.connect(self.showDialog)

self.frm = QFrame(self)
self.frm.setStyleSheet("QWidget { background-color: %s }"
% col.name())
self.frm.setGeometry(130, 22, 100, 100)

self.setGeometry(300, 300, 250, 180)
self.setWindowTitle('Color dialog')
self.show()


def showDialog(self):

col = QColorDialog.getColor()

if col.isValid():
self.frm.setStyleSheet("QWidget { background-color: %s }"
% col.name())


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

加油,都看到一半了你!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
python复制代码
'''

┌─┐ ┌─┐ + +
┌──┘ ┴───────┘ ┴──┐++
│ │
│ ─── │++ + + +
███████───███████ │+
│ │+
│ ─┴─ │
│ │
└───┐ ┌───┘
│ │
│ │ + +
│ │
│ └──────────────┐
│ │
│ ├─┐
│ ┌─┘
│ │
└─┐ ┐ ┌───────┬──┐ ┌──┘ + + + +
│ ─┤ ─┤ │ ─┤ ─┤
└──┴──┘ └──┴──┘ + + + +
神兽保佑
代码无BUG!


'''

20 选择字体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
python复制代码
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QPushButton,
QSizePolicy, QLabel, QFontDialog, QApplication)
import sys

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

vbox = QVBoxLayout()

btn = QPushButton('来来来', self)
btn.setSizePolicy(QSizePolicy.Fixed,
QSizePolicy.Fixed)

btn.move(20, 20)

vbox.addWidget(btn)

btn.clicked.connect(self.showDialog)

self.lbl = QLabel('Knowledge only matters', self)
self.lbl.move(130, 20)

vbox.addWidget(self.lbl)
self.setLayout(vbox)

self.setGeometry(300, 300, 250, 180)
self.setWindowTitle('字体目录')
self.show()


def showDialog(self):

font, ok = QFontDialog.getFont()
if ok:
self.lbl.setFont(font)
print('选择的字体是',end="")
print(font)
print(type(font))


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

21 选择文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
python复制代码
from PyQt5.QtWidgets import \
(QMainWindow, QTextEdit, QAction, QFileDialog, QApplication)
from PyQt5.QtGui import QIcon
import sys

class Example(QMainWindow):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.textEdit = QTextEdit()
self.setCentralWidget(self.textEdit)
self.statusBar()

openFile = QAction(QIcon('images/open.png'), 'Open', self)
openFile.setShortcut('Ctrl+O')
openFile.setStatusTip('打开一个新的文件')
openFile.triggered.connect(self.showDialog)

menubar = self.menuBar()
fileMenu = menubar.addMenu('&文件')
fileMenu.addAction(openFile)

self.setGeometry(300, 300, 350, 300)
self.setWindowTitle('File dialog')
self.show()


def showDialog(self):

fname = QFileDialog.getOpenFileName(self, 'Open file', '/home')

if fname[0]:
f = open(fname[0], 'r')

with f:
data = f.read()
self.textEdit.setText(data)
print(data)
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

控件

22 QCheckBox是啥玩意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
python复制代码
from PyQt5.QtWidgets import QWidget, QCheckBox, QApplication
from PyQt5.QtCore import Qt
import sys

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

cb = QCheckBox('改改改', self)
cb.move(20, 20)
cb.toggle()
cb.stateChanged.connect(self.changeTitle)

self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('真正的标题')
self.show()


def changeTitle(self, state):

if state == Qt.Checked:
self.setWindowTitle('假装有标题')
else:
self.setWindowTitle('没了')


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

23 切换按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
python复制代码
from PyQt5.QtWidgets import (QWidget, QPushButton,
QFrame, QApplication)
from PyQt5.QtGui import QColor
import sys

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.col = QColor(0, 0, 0)

redb = QPushButton('Red', self)
redb.setCheckable(True)
redb.move(10, 10)

redb.clicked[bool].connect(self.setColor)

greenb = QPushButton('Green', self)
greenb.setCheckable(True)
greenb.move(10, 60)

greenb.clicked[bool].connect(self.setColor)

blueb = QPushButton('Blue', self)
blueb.setCheckable(True)
blueb.move(10, 110)

blueb.clicked[bool].connect(self.setColor)

self.square = QFrame(self)
self.square.setGeometry(150, 20, 100, 100)
self.square.setStyleSheet("QWidget { background-color: %s }" %
self.col.name())

self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('Toggle button')
self.show()


def setColor(self, pressed):

source = self.sender()

if pressed:
val = 255
else:
val = 0

if source.text() == "Red":
self.col.setRed(val)
elif source.text() == "Green":
self.col.setGreen(val)
else:
self.col.setBlue(val)

self.square.setStyleSheet("QFrame { background-color: %s }" %
self.col.name())


if __name__ == '__main__':

app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

24 滑块是个好东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
python复制代码
from PyQt5.QtWidgets import (QWidget, QSlider,
QLabel, QApplication)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
import sys


class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()

def initUI(self):

sld = QSlider(Qt.Horizontal, self)
sld.setFocusPolicy(Qt.NoFocus)
sld.setGeometry(30, 40, 100, 30)
sld.valueChanged[int].connect(self.changeValue)

self.label = QLabel(self)
self.label.setPixmap(QPixmap('images/logo.png'))
self.label.setGeometry(160, 40, 80, 30)

self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('s什么鸡儿玩意r')
self.show()

def changeValue(self, value):

if value == 0:
self.label.setPixmap(QPixmap('images/1.png'))
elif value > 0 and value <= 30:
self.label.setPixmap(QPixmap('images/2.png'))
elif value > 30 and value < 80:
self.label.setPixmap(QPixmap('images/3.png'))
else:
self.label.setPixmap(QPixmap('images/4.png'))


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

25 进度条

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
python复制代码
from PyQt5.QtWidgets import (QWidget, QProgressBar,
QPushButton, QApplication)
from PyQt5.QtCore import QBasicTimer
import sys

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.pbar = QProgressBar(self)
self.pbar.setGeometry(30, 40, 200, 25)

self.btn = QPushButton('走你', self)
self.btn.move(40, 80)
self.btn.clicked.connect(self.doAction)

self.timer = QBasicTimer()
self.step = 0

self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('进度条就比较强了')
self.show()


def timerEvent(self, e):

if self.step >= 200:
self.timer.stop()
self.btn.setText('完成吧')
return

self.step = self.step + 1
self.pbar.setValue(self.step)

def doAction(self):

if self.timer.isActive():
self.timer.stop()
self.btn.setText('走走走')
else:
self.timer.start(200, self)
self.btn.setText('停,呗走了')


if __name__ == '__main__':

app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

26 日历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
python复制代码
from PyQt5.QtWidgets import (QWidget, QCalendarWidget,
QLabel, QApplication, QVBoxLayout)
from PyQt5.QtCore import QDate
import sys

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

vbox = QVBoxLayout(self)

cal = QCalendarWidget(self)
cal.setGridVisible(True)
cal.clicked[QDate].connect(self.showDate)

vbox.addWidget(cal)

self.lbl = QLabel(self)
date = cal.selectedDate()
self.lbl.setText(date.toString())

vbox.addWidget(self.lbl)

self.setLayout(vbox)

self.setGeometry(300, 300, 350, 300)
self.setWindowTitle('看日历,认准我')
self.show()


def showDate(self, date):

self.lbl.setText(date.toString())

if __name__ == '__main__':

app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

27 图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
python复制代码
from PyQt5.QtWidgets import (QWidget, QHBoxLayout,
QLabel, QApplication)
from PyQt5.QtGui import QPixmap
import sys

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

hbox = QHBoxLayout(self)
pixmap = QPixmap("images/09f04")

lbl = QLabel(self)
lbl.setPixmap(pixmap)

hbox.addWidget(lbl)
self.setLayout(hbox)

self.move(300, 200)
self.setWindowTitle('Red Rock')
self.show()


if __name__ == '__main__':

app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

28行编辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
python复制代码
import sys
from PyQt5.QtWidgets import \
(QWidget, QLabel, QLineEdit, QApplication)


class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()

def initUI(self):
self.lbl = QLabel(self)
qle = QLineEdit(self)

qle.move(60, 100)
self.lbl.move(60, 40)

qle.textChanged[str].connect(self.onChanged)

self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('QLineEdit')
self.show()

def onChanged(self, text):
self.lbl.setText(text)
self.lbl.adjustSize()


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

29 QSplitter是啥玩意呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
python复制代码
from PyQt5.QtWidgets import (QWidget, QHBoxLayout, QFrame,
QSplitter, QStyleFactory, QApplication)
from PyQt5.QtCore import Qt
import sys

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

hbox = QHBoxLayout(self)

topleft = QFrame(self)
topleft.setFrameShape(QFrame.StyledPanel)

topright = QFrame(self)
topright.setFrameShape(QFrame.StyledPanel)

bottom = QFrame(self)
bottom.setFrameShape(QFrame.StyledPanel)

splitter1 = QSplitter(Qt.Horizontal)
splitter1.addWidget(topleft)
splitter1.addWidget(topright)

splitter2 = QSplitter(Qt.Vertical)
splitter2.addWidget(splitter1)
splitter2.addWidget(bottom)

hbox.addWidget(splitter2)
self.setLayout(hbox)

self.setGeometry(300, 300, 400, 300)
self.setWindowTitle('QSplitter')
self.show()


def onChanged(self, text):

self.lbl.setText(text)
self.lbl.adjustSize()


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

30 下拉选框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
python复制代码
from PyQt5.QtWidgets import (QWidget, QLabel,
QComboBox, QApplication)
import sys

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.lbl = QLabel("Ubuntu", self)

combo = QComboBox(self)
combo.addItem("Ubuntu")
combo.addItem("Windows")
combo.addItem("centos")
combo.addItem("deepin")
combo.addItem("redhat")
combo.addItem("debain")
combo.move(50, 50)
self.lbl.move(50, 150)

combo.activated[str].connect(self.onActivated)

self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('下拉选框练习 ')
self.show()


def onActivated(self, text):

self.lbl.setText(text)
self.lbl.adjustSize()


app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

拖拽

31 简单的拖放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
python复制代码from PyQt5.QtWidgets import (QPushButton, QWidget,
QLineEdit, QApplication)
import sys

class Button(QPushButton):

def __init__(self, title, parent):
super().__init__(title, parent)

self.setAcceptDrops(True)


def dragEnterEvent(self, e):

if e.mimeData().hasFormat('text/plain'):
e.accept()
else:
e.ignore()

def dropEvent(self, e):

self.setText(e.mimeData().text())


class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

edit = QLineEdit('', self)
edit.setDragEnabled(True)
edit.move(30, 65)

button = Button("Button", self)
button.move(190, 65)

self.setWindowTitle('Simple drag and drop')
self.setGeometry(300, 300, 300, 150)


if __name__ == '__main__':

app = QApplication(sys.argv)
ex = Example()
ex.show()
app.exec_()

32 拖放按钮组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
python复制代码
from PyQt5.QtWidgets import QPushButton, QWidget, QApplication
from PyQt5.QtCore import Qt, QMimeData
from PyQt5.QtGui import QDrag
import sys

class Button(QPushButton):

def __init__(self, title, parent):
super().__init__(title, parent)


def mouseMoveEvent(self, e):

if e.buttons() != Qt.RightButton:
return

mimeData = QMimeData()

drag = QDrag(self)
drag.setMimeData(mimeData)
drag.setHotSpot(e.pos() - self.rect().topLeft())

dropAction = drag.exec_(Qt.MoveAction)


def mousePressEvent(self, e):

super().mousePressEvent(e)

if e.button() == Qt.LeftButton:
print('按我嘎哈')


class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.setAcceptDrops(True)

self.button = Button('来吧!!!', self)
self.button.move(100, 65)

self.setWindowTitle('点击还能挪')
self.setGeometry(300, 300, 280, 150)


def dragEnterEvent(self, e):

e.accept()


def dropEvent(self, e):

position = e.pos()
self.button.move(position)

e.setDropAction(Qt.MoveAction)
e.accept()


if __name__ == '__main__':

app = QApplication(sys.argv)
ex = Example()
ex.show()
app.exec_()
"""
拖拽
在GUI里,拖放是指用户点击一个虚拟的对象,拖动,然后放置到另外一个对象上面的动作。
一般情况下,需要调用很多动作和方法,创建很多变量。
拖放能让用户很直观的操作很复杂的逻辑。
一般情况下,我们可以拖放两种东西:数据和图形界面。
把一个图像从一个应用拖放到另外一个应用上的实质是操作二进制数据。
把一个表格从Firefox上拖放到另外一个位置 的实质是操作一个图形组。
"""

绘图

33 文本的涂鸦(这个好玩哈)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
python复制代码
import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QFont
from PyQt5.QtCore import Qt

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.text = "涂鸦要涂的有灵魂"

self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('绘画板')
self.show()


def paintEvent(self, event):

qp = QPainter()
qp.begin(self)
self.drawText(event, qp,168, 34, 243)
qp.end()

# qp1 = QPainter()
# qp1.begin(self)
# self.drawText(event, qp1,168, 34, 23)
# qp1.end()


def drawText(self, event, qp, r,g,b):

qp.setPen(QColor(r,g,b))
qp.setFont(QFont('微软雅黑', 15))
qp.drawText(event.rect(), Qt.AlignCenter, self.text)


if __name__ == '__main__':

app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

34 点的绘画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
python复制代码
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter
from PyQt5.QtCore import Qt
import sys, random

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.setGeometry(300, 300, 300, 190)
self.setWindowTitle('一大堆点点儿')
self.show()


def paintEvent(self, e):

qp = QPainter()
qp.begin(self)
self.drawPoints(qp)
qp.end()


def drawPoints(self, qp):

qp.setPen(Qt.red)
size = self.size()

for i in range(1000):
x = random.randint(1, size.width()-1)
y = random.randint(1, size.height()-1)
qp.drawPoint(x, y)


if __name__ == '__main__':

app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

35 颜色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
python复制代码
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QBrush
import sys

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.setGeometry(300, 300, 350, 100)
self.setWindowTitle('Colours')
self.show()


def paintEvent(self, e):

qp = QPainter()
qp.begin(self)
self.drawRectangles(qp)
qp.end()


def drawRectangles(self, qp):

col = QColor(0, 0, 0)
col.setNamedColor('#d4d4d4')
qp.setPen(col)

qp.setBrush(QColor(200, 0, 0))
qp.drawRect(10, 15, 90, 60)

qp.setBrush(QColor(255, 80, 0, 160))
qp.drawRect(130, 15, 90, 60)

qp.setBrush(QColor(25, 0, 90, 200))
qp.drawRect(250, 15, 90, 60)


if __name__ == '__main__':

app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

36 QPen是笔么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
python复制代码
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtCore import Qt
import sys

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.setGeometry(300, 300, 280, 270)
self.setWindowTitle('Pen styles')
self.show()


def paintEvent(self, e):

qp = QPainter()
qp.begin(self)
self.drawLines(qp)
qp.end()


def drawLines(self, qp):

pen = QPen(Qt.black, 2, Qt.SolidLine)

qp.setPen(pen)
qp.drawLine(20, 40, 250, 40)

pen.setStyle(Qt.DashLine)
qp.setPen(pen)
qp.drawLine(20, 80, 250, 80)

pen.setStyle(Qt.DashDotLine)
qp.setPen(pen)
qp.drawLine(20, 120, 250, 120)

pen.setStyle(Qt.DotLine)
qp.setPen(pen)
qp.drawLine(20, 160, 250, 160)

pen.setStyle(Qt.DashDotDotLine)
qp.setPen(pen)
qp.drawLine(20, 200, 250, 200)

pen.setStyle(Qt.CustomDashLine)
pen.setDashPattern([1, 4, 5, 4])
qp.setPen(pen)
qp.drawLine(20, 240, 250, 240)


if __name__ == '__main__':

app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

37 QBrush是啥?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
python复制代码
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QBrush
from PyQt5.QtCore import Qt
import sys

class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.setGeometry(300, 300, 355, 280)
self.setWindowTitle('Brushes')
self.show()


def paintEvent(self, e):

qp = QPainter()
qp.begin(self)
self.drawBrushes(qp)
qp.end()


def drawBrushes(self, qp):

brush = QBrush(Qt.SolidPattern)
qp.setBrush(brush)
qp.drawRect(10, 15, 90, 60)

brush.setStyle(Qt.Dense1Pattern)
qp.setBrush(brush)
qp.drawRect(130, 15, 90, 60)

brush.setStyle(Qt.Dense2Pattern)
qp.setBrush(brush)
qp.drawRect(250, 15, 90, 60)

brush.setStyle(Qt.DiagCrossPattern)
qp.setBrush(brush)
qp.drawRect(10, 105, 90, 60)

brush.setStyle(Qt.Dense5Pattern)
qp.setBrush(brush)
qp.drawRect(130, 105, 90, 60)

brush.setStyle(Qt.Dense6Pattern)
qp.setBrush(brush)
qp.drawRect(250, 105, 90, 60)

brush.setStyle(Qt.HorPattern)
qp.setBrush(brush)
qp.drawRect(10, 195, 90, 60)

brush.setStyle(Qt.VerPattern)
qp.setBrush(brush)
qp.drawRect(130, 195, 90, 60)

brush.setStyle(Qt.BDiagPattern)
qp.setBrush(brush)
qp.drawRect(250, 195, 90, 60)


if __name__ == '__main__':

app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

38 贝赛尔曲线(这个学过PS的都知道)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
python复制代码
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QPainterPath
from PyQt5.QtCore import Qt
import sys


class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()

def initUI(self):
# 用QPainterPath路径创建贝塞尔曲线。
# 使用cubicTo()方法生成,分别需要三个点:起始点,控制点和终止点。
self.setGeometry(300, 300, 380, 250)
self.setWindowTitle('绘制贝塞尔曲线')
self.show()

def paintEvent(self, e):
qp = QPainter()
qp.begin(self)
qp.setRenderHint(QPainter.Antialiasing)
self.drawBezierCurve(qp)
qp.end()

def drawBezierCurve(self, qp):
path = QPainterPath()
path.moveTo(30, 30)
path.cubicTo(30, 30, 350, 30, 200, 150)

qp.drawPath(path)


if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

39自定义组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
python复制代码
from PyQt5.QtWidgets import (QWidget, QSlider, QApplication,
QHBoxLayout, QVBoxLayout)
from PyQt5.QtCore import QObject, Qt, pyqtSignal
from PyQt5.QtGui import QPainter, QFont, QColor, QPen
import sys

class Communicate(QObject):

updateBW = pyqtSignal(int)


class BurningWidget(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

self.setMinimumSize(1, 30)
self.value = 75
self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]


def setValue(self, value):

self.value = value


def paintEvent(self, e):

qp = QPainter()
qp.begin(self)
self.drawWidget(qp)
qp.end()


def drawWidget(self, qp):

MAX_CAPACITY = 700
OVER_CAPACITY = 750

font = QFont('Serif', 7, QFont.Light)
qp.setFont(font)

size = self.size()
w = size.width()
h = size.height()

step = int(round(w / 10))


till = int(((w / OVER_CAPACITY) * self.value))
full = int(((w / OVER_CAPACITY) * MAX_CAPACITY))

if self.value >= MAX_CAPACITY:

qp.setPen(QColor(255, 255, 255))
qp.setBrush(QColor(255, 255, 184))
qp.drawRect(0, 0, full, h)
qp.setPen(QColor(255, 175, 175))
qp.setBrush(QColor(255, 175, 175))
qp.drawRect(full, 0, till-full, h)

else:

qp.setPen(QColor(255, 255, 255))
qp.setBrush(QColor(255, 255, 184))
qp.drawRect(0, 0, till, h)


pen = QPen(QColor(20, 20, 20), 1,
Qt.SolidLine)

qp.setPen(pen)
qp.setBrush(Qt.NoBrush)
qp.drawRect(0, 0, w-1, h-1)

j = 0

for i in range(step, 10*step, step):

qp.drawLine(i, 0, i, 5)
metrics = qp.fontMetrics()
fw = metrics.width(str(self.num[j]))
qp.drawText(i-fw/2, h/2, str(self.num[j]))
j = j + 1


class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()


def initUI(self):

OVER_CAPACITY = 750

sld = QSlider(Qt.Horizontal, self)
sld.setFocusPolicy(Qt.NoFocus)
sld.setRange(1, OVER_CAPACITY)
sld.setValue(75)
sld.setGeometry(30, 40, 150, 30)

self.c = Communicate()
self.wid = BurningWidget()
self.c.updateBW[int].connect(self.wid.setValue)

sld.valueChanged[int].connect(self.changeValue)
hbox = QHBoxLayout()
hbox.addWidget(self.wid)
vbox = QVBoxLayout()
vbox.addStretch(1)
vbox.addLayout(hbox)
self.setLayout(vbox)

self.setGeometry(300, 300, 390, 210)
self.setWindowTitle('Burning widget')
self.show()


def changeValue(self, value):

self.c.updateBW.emit(value)
self.wid.repaint()


if __name__ == '__main__':

app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

终于翻到底部了,能看到这里,给你自己一个奖励吧!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
python复制代码
'''

┌─┐ ┌─┐ + +
┌──┘ ┴───────┘ ┴──┐++
│ │
│ ─── │++ + + +
███████───███████ │+
│ │+
│ ─┴─ │
│ │
└───┐ ┌───┘
│ │
│ │ + +
│ │
│ └──────────────┐
│ │
│ ├─┐
│ ┌─┘
│ │
└─┐ ┐ ┌───────┬──┐ ┌──┘ + + + +
│ ─┤ ─┤ │ ─┤ ─┤
└──┴──┘ └──┴──┘ + + + +
神兽保佑
代码无BUG!


'''

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Golang如何通过指针运算获取切片大小和容量?

发表于 2021-11-27

「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战」

如果你有接触过C语言, 相信你会对其指针功能的强大印象深刻, 但大多数情况下,指针这个功能只会让开发者头发越来越少。

因此,到了Golang的设计者们汲取C语言的教训, 把指针的功能设置得很克制,并且大多数开发者使用到仅仅是取地址和解引用等功能。

阅读本文前,请确保你能够理解:

  • 什么是指针?
  • 什么是取地址?
  • 什么是解引用?

unsafe

包如其名, 官方给起这个名字就是说“这玩意太危险, 不要随便用”。虽然看着不太安全,官方还是向开发者提供了这一功能,并且起了个不太吉利的名字,颇有点甩锅的意味。

unsafe包中可导出东西不多,我们来快速过一下本文需要使用到的类型和函数。

ArbitraryType

ArbitraryType 是int类型的别名, 我们知道在不同的平台下int类型大小会随之改变,如在x86中int类型为32位, 在x64平台中为64位, 会出现在文档中仅仅是为了起到说明作用。

1
2
3
go复制代码// ArbitraryType is here for the purposes of documentation only and is not actually
// part of the unsafe package. It represents the type of an arbitrary Go expression.
type ArbitraryType int

Pointer

Pointer的实际类型是*int, Go官方将其当作C语言中的无类型指针void*来使用, 其类型声明如下

1
go复制代码type Pointer *ArbitraryType

在官方文档中我们可以找到如下解释

  • 任何类型的指针都可以转换成Pointer
  • Pointer可以转换成任意类型的指针
  • uintptr可以转换成Pointer
  • Pointer可以转换成uintptr

uintptr是一个足够存储系统中所有内存地址的整型(x86中为32位, x64中为64位), 可以用于运算。

Go在GC的时候不会将其uintptr当做指针(不会给目标对象加引用)。

Sizeof

Sizeof 方法会返回变量对应类型所占用的字节数。

1
2
3
4
5
6
7
8
9
10
11
12
13
go复制代码func TestSizeof(t *testing.T) {
b := byte(0xFF)
t.Logf("Size of byte %d", unsafe.Sizeof(b))
type Msg struct{
Id int64
Message string
}
msg := Msg{}
t.Logf("Sizeof msg %d", unsafe.Sizeof(msg))
t.Logf("Sizeof msg ptr %d", unsafe.Sizeof(&msg))
slice := make([]int, 0)
t.Logf("Sizeof slice %d", unsafe.Sizeof(slice))
}

输出如下所示:

image.png

切片的结构

Go中的切片是官方提供的可以动态扩容的数组,其本质是编译器的提供的语法糖,在运行时其结构体如下所示:

1
2
3
4
5
go复制代码type SliceHeader struct {
Data uintptr
Len int
Cap int
}

关于切片的更多细节,可以阅读Go语言设计与实现-切片, 写得非常棒, 十分推荐

  • Data指向了一块连续的内存区域用于存储切片的元素
  • Len 用于记录切片大小
  • Cap 用于记录切片的容量

以上三个字段在内存中是连续存储的, 这是平台无关性的,因此我们可以通过指针运算来获取切片的大小和容量。

实战

热身!通过指针运算获取数组元素

Tip: 在golang中无法直接对指针进行运算,我们需要将其转换为uintptr类型

以下代码的基本逻辑就是获取数组首元素的指针*int然后将其转换为unsafe.Pointer类型, 再通过unsafe.Sizeof获取对应类型的大小, 最后二者相加便获得了下一个数组元素的位置,并将其转换为unsafe.Pointer类型。

由于unsafe.Pointer可以转换为任意类型的指针, 我们便将其转换*int指针, 并进行解引用获取对应值。

1
2
3
4
5
6
7
8
9
10
11
12
go复制代码func TestArray(t *testing.T) {
array := make([]int, 0, 10)
array = append(array, 1024)
array = append(array, 7777)
firstPtr := unsafe.Pointer(&array[0])
// 指针运算
secondPtr := unsafe.Pointer(uintptr(firstPtr) + unsafe.Sizeof(array[0]))
val := (*int)(secondPtr)
t.Logf("array address %p", array)
t.Logf("array[0]=%d address=%p", array[0], firstPtr)
t.Logf("array[1]=%d address=%p", *val, secondPtr)
}

最后输出如下所示:

image.png

观察输出结果我们可以发现array存储的时指向首元素的地址,并且是个指针类型。
根据切片的结构定义我们可以确定array的地址的就是SliceHeader结构的首地址。

定位Len和Cap字段

由于结构体在内存中是连续存储的,因此在x64平台下有:

  • Len字段为SliceHeader结构的首地址 + 8
  • Cap字段为SliceHeader结构的首地址 + 16

在上文中的测试用例中补充如下代码:

1
2
3
4
5
6
7
go复制代码// 取 DataPtr 的地址, 然后我们可以根据结构体的定义算出Len字段和Cap字段的地址
dataPtr := unsafe.Pointer(&array)
t.Logf("Data ptr=%p", dataPtr)
lenPtr :=(*int)(unsafe.Pointer(uintptr(dataPtr) + 8))
capPtr :=(*int)(unsafe.Pointer(uintptr(dataPtr) + 16))
t.Logf("len ptr=%p value=%d", lenPtr, *lenPtr)
t.Logf("cap ptr=%p value=%d", capPtr, *capPtr)

以上代码的出发点就是对array(指针)进行取地址获得SliceHeader的首地址并进行运算。

输出结果如下所示:

image.png

总结

  • 指针运算虽然风骚,在普通业务开发最好别用,会被打!
  • unsafe.Pointer类似于C语言的中无类型指针void*
  • 想要指针运算需要将其转换uintptr

最后附上指针类型转换图:

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

PyQt5学习笔记11-15 PyQt5的第十一课 - Py

发表于 2021-11-27

PyQt5的第十一课 - PyQt5三原色案例

知识回顾

  • QGridLayout网格式布局
  • 默认索引从0开始
  • QTextEdit多行文本框
  • 注意:网格布局是可以扩展性的,可以随着系统界面变化而变化的

案例目标

制作一个界面如右图.功能是描述三原色.在设计中,我们往往有三原色概念

基本上所哟的颜色都可以由三种颜色调和而成,这三种颜色分别是RGB

,我们使用三个按钮分别表示这个三种颜色,每个按钮都可以有哦两种状态

为蓝色,未选中按钮为默认灰色,在界面中用一个区块(QFrame)来表示颜色

思路

  • 制作界面采用PyQt5进行布局
  • 采用合资布局的方法进行界面布局
  • 按钮的状态功能加入
  • 设置Qframe的背景颜色
  • 设置按钮的点击事件
  • 设置三原色情况,最终还是要通过改变Qframe样式来实现颜色变化

框架容器QFrame

  • 必须使用类库QFrame
  • 初始化:
  • myframe = QFrame()
  • 注意点
  • 父容器可以采用后期添加到盒子布局的方式进行设置
  • 当然可以可以在初始化的时候设置
  • 父容器的设置会影响子容器的生命周期的

控件设置CSS风格

  • 可以通过setStyleSheet方法来设置
  • 这个方法几乎在所丶有的QWidget的控件之上都能实现
  • 使用举例:
    myframe.setStyleSheet(“QWidget{background-color:black}”)
  • 在setStyleSheet方法中使用CSS类型的字符串
  • 这个CSS风格使用格式:
  • 需要控制的空阿金标签或者行为,然后写一个大括号{},我们在大括号 内写各类控制某个标签的样式.样式的书写风格就是 属性:值

按钮是否选中可以选中与不选中的切换

使用setChecable方法,默认情况下这个方法设置的值为false,我们设置为True才行

槽函数传参

  • 槽函数定义需要看我们的信号源是否有参数
  • 信号源中有参数的部分的信号参数我们需要使用中括号
  • 使用举例:btnRed.clicked[bool].connect(self.setColor)
  • 如何传递控件对象?
  • 使用self.sender()就会返回触发事件对象本身

三原色函数

  • 使用QColor来实现
  • 红,绿,蓝范围在[0,255]之间
  • 第1个参数表示红色
  • 第2个参数表示绿色
  • 第3个参数表示蓝色

总结强调

  • 掌握三原色函数
  • 掌握布局的思想
  • 掌握按钮的选择状态
  • 掌握信号槽传参和控件事件的传递

PyQt5的第十二课 - PyQt5三原色

知识回顾

  • 程序中的三原色
  • 按钮状态的改变Qpushbutton装填的改变,设置是否选重中checked
  • 利用我们已经学过的布局思想

qlineEdit使用

  • 它是一个单行文本框
  • 事件:文本改变事件.textchange类似这样的代码的事件.
  • 结合qt中基本的信号槽机制
  • 注意:信号在传递参数的时候要把小括号改成

设置标签宽度自适应

  • 标签使用Qlabel
  • 使用标签的时候,标签默认的宽度是固定的,当标签文本内容宽度的显示区域的时候
  • 超出部分的内容就会进行遮挡.此时我们可以考虑让标签的内容积习难改in自定义区域适应
  • 标签区域自适应适应函数adjustsize

总结强调

掌握qlineEdit的文本改变事件
掌握文本标签的区域自适应
掌握事件的信号槽关系以及传递参数

PyQt5的第十三课 - PyQt5复选框 QCheckBox控件事件应用

知识回顾

  • 文本框事件的改变,qlineEdit单行文本框
  • 标签自适应adjustsize方法

复选框

  • 使用控件QCheckBox
  • 使用格式
  • 变量对象 = QCheckBox(显示文本,父容器)
  • 这里的父容器可以是一个QWidget类型

我们案例中的QFrame实际上本质是一个QWidget

复选框状态改变信号

  • 使用statechanged信号,注意这个信号可以传以参数,这个参数是一个int类型.
  • 使用格式:
  • self.复选框.stateChanged[int].connect(self.myState)
  • 复选框.状态改变信号[整型参数].connect(类中的自定义方法)
  • 使用案例:
  • self.ck1.stateChanged[int].connect(self.myState)

如何区分通过信号传递过来的不同控件?

  • 直接通过self对象的sender方法就可以进行区分了

Qt中有很多枚举类表示

  • 比如我们今天学习的选中复选框使用2对应表示为Qt.Checked
  • 写成枚举类型的好处就是方便我们阅读代码.

总结强调

  • 复选框的状态改变事件statechanged
  • qt枚举
  • 掌握把控件放到容器中qframe

PyQt5的第十四课 - PyQt5滑块控件Qslider的应用

知识回顾

  • 掌握了复选框QCheckBox
  • 枚举类中的值为2
  • 在Qframe中的应用

Qslider控件

  • 这个是一个滑块控件.用于方便左右滑动
  • 往往这类滑动更多的用于屏幕可以触碰的设备
  • 实则就是调用Qslider库
  • 进行实例化后进行调用
  • 使用格式:
    变量名称 = Qslider(方向,父容器)
  • 水平方向值为1,垂直方向值为2(说白了你不写常量,你直接就写1,2都行了)
  • 这个控件可以水平放置,也可以垂直放置Qt.Vertical
  • 应用举例:
  • QSlider(Qt.Horizontal.self)

枚举类的存在类库

  • QtCore核心库

滑块控件的最值设置(范围设置)

  • 最小值设置
  • 举例设置最小值为0:
  • sl.setMinimum(0)
  • 最大值设置
  • 举例设置最大值为255:
  • sl.setMaximum(255)

滑块的滑动值变化事件

  • 滑块的格式:滑块对象名称.valueChanged[int].connect(对应)
  • sl.valueChanged[int].connect(self.myValue)

总结强调

  • 掌握滑块控件Qslider的使用
  • 掌握滑块值的事件
  • 掌握枚举类库的使用

PyQt5的第十五课 - PyQt5进度条QProgressBar应用

知识回顾

  • 滑动控件qslider
  • 关键:设置最大值,最小值,绝对范围
  • 核心类库QtCore,枚举类Qt

进度条qprogressbar

使用思想:

  • 载入类库
  • 初始化对象
  • 设置最小值和最大值
  • 时钟的使用QBasicTimer,跟槽方法对应类库中的timeevent

制作案例

  • 界面由进度条和按钮组成
  • 进度条的值范围为0-100
  • 按钮的状态为:”开始”, “暂停”,”终止”
  • 按钮需要控制进度条进度

导入时钟类库

from pyqt5.qtcore import qbasictimer

初始化进度条

1
2
3
4
python复制代码# 载入进度条
self.pgb = QProgressBar(self) # 类对象的初始化
self.pgb.move(50,50) # 将进度条移动到指定的位置
self.pgb.resize(300,20) # 设置进度条宽高

设置进度条的范围

1
2
3
python复制代码self.pgb.setMinimum(0)  # 设置最小值
self.pgb.setMaximum(100) # 设置最大值
self.pgb.setValue(50) # 设置当前进度

时钟控件

  • 做用:每搁多少时间执行一次时钟内部的代码
  • 时间单位:毫秒
  • 1秒 = 1000毫秒

QBasicTimer控价解析

  • isActive方法:返回时钟控件是否开启,如果开启返回true,否则为false
  • start方法:使得时钟控件开启来,需要传入时钟间隔,时间单位为毫秒
  • 简单的参数使用格式
  • start(时间,self)
  • stop方法:使得时钟控件关闭.
  • timerID方法:返回当前时钟控件的ID,主要用于多个时钟控件使用的时候,区分不同的时钟控件.
  • 对应的槽方法是Qwidget控件自带的timerEvent事件.
  • 时钟控件的每个多少时间要运行一次的代码就是在timerEvent方法中,我们在使用时钟控件的时候要重写这个方法

timerEvent方法

1
2
3
4
5
6
7
8
python复制代码# 声明一个时钟控件
self.timer1 = QBasicTimer()
# 开始计时
self.timer1.start(1000,self)
# 这玩意是系统带的,自动就调用它,默认1秒钟一次
def timerEvent(self, e):
self.pv += 1
self.pgb.setValue(self.pv) # 设置当前进度

时钟控件状态切断的核心代码

1
2
3
4
5
6
python复制代码if self.timer1.isActive():  # 检测是否开启
self.timer1.stop(50, self)
self.btn.setText("开始") # 这里的按钮的状态显示的是按钮下次的行为
else:
self.timer1.start(50, self)
self.btn.setText("停止")

总结强调

  • 掌握Qprogressbar的控件的配置
  • 掌握时钟控件的基本使用QBasicTimer
  • 理解按钮控制时钟控件达到进度条的运行的思想.

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…147148149…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%