前言:在学习完HTML、CSS、JavaScript之后,我们就可以开始做一些小项目了,本篇文章所讲的小项目为——包变脸的,老弟。
✨✨✨这里是秋刀鱼不做梦的BLOG
✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSDN博客
先让我们看一下效果: 那么我们如何去实现这样的小案例呢?在下文中我们对每一段重要的代码都进行了解释,读者可以根据注释对代码进行理解。
1.骨架 —— HTML代码 HTML代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>秋刀鱼不做梦</title> <!-- 引入外部样式表 --> <link rel="stylesheet" href="./表情.css"> </head> <body> <!-- 表情图标的列表 --> <ul id="emoji"> <!-- 第一层,深度为0.2 --> <li class="layer" data-depth="0.2"> <!-- 面部 --> <div class="face"></div> </li> <!-- 第二层,深度为0.3 --> <li class="layer" data-depth="0.3"> <!-- 光泽 --> <div class="shine"></div> </li> <!-- 第三层,深度为0.8 --> <li class="layer" data-depth="0.8"> <!-- 左眼 --> <div class="
目录
1. 数组的概念
2. 一维数组的创建及其初始化
2.1 数组的创建
2.2 数组的初始化
考点总结:当我们未对数组进行初始化时,数组里面的元素打印出来是乱码,但哪怕只对数组里面一个元素赋值,之后未被赋值的元素也会默认赋值为0
3. 一维数组的使用
3.1 下标引用操作符
3.2 数组的输入
4. 一维数组在内存中的存储
1. 数组的概念 数组是一组相同类型元素的集合
注意;数组中可以存放一个或者多个数据,但是不能让数组里面的元素个数为0
数组中存放的每一个数据,它们的类型是相同的
数组分为一维数组和多维数组,多维数组中常见的一般为二维数组
2. 一维数组的创建及其初始化 2.1 数组的创建 一维数组创建的语法如下:
type(类型) arr_name[常量值]
其中type就是对数组中所存放的数据类型做出一个定义,这个类型可以是char、int、short int、double等等类型的数据
arr_name值得就是数组的名字,这个名字可以由程序员自己根据实际情况而起名
而常量值就是用来指定数组的大小的,这个数组的大小是根据实际的需求指定的
比如现在我想要创建一个数组来存放我们学校1000个人的年龄大小
就如上图这样,这个数组就创建成功了
数组里面元素的类型和数组的名字,都是我们根据情况所设定的
比如数组就还可以用来存放名字,体重
2.2 数组的初始化 在我们之前学习变量的时候,我们就知道我们一开始需要给变量赋值,这被称为变量的初始化,同理,我们根据我们的需要创建了数组后,那我们自然也同样需要对数组进行一个初始化
那么数组应该如何进行初始化呢???
数组的初始化一般使用大括号进行,只需要将数据放入大括号里面即可
这样便将age数组里面的五个元素分别初始化为5,4,3,2,1
那肯定有人想问了,如果我们不初始化数组的话,数组里面的元素默认会是几呢?
大家可以看到,如果不初始化数组,那么打印数组内部的各个元素出来的将会是一些乱码
那又有一个问题了,将数组每一个元素都需要一个一个的初始化实在是太麻烦了,如果我们只初始化几个,那剩下的未被初始化的数组元素打印出来还会是乱码吗?
可以看得到,在我们只对数组一个元素命名为0时,其他元素最后也默认为0
而我们对数组一个元素命名为其他数字的时候,其他未被命名的数组元素也同样默认为0
考点总结:当我们未对数组进行初始化时,数组里面的元素打印出来是乱码,但哪怕只对数组里面一个元素赋值,之后未被赋值的元素也会默认赋值为0 所以我们经常对数组初始化采用只对数组里面第一个元素赋值为0,来进行数组的初始化
2.3 数组的类型
数组本身也是有类型的,但数组的类型和我们之前学习到的变量的类型是有很大区别的,比如上面所写的
int age [5]
char name[5]
这些数组的类型实际上就是去除数组名之后剩下的东西
比如第一个数组的数组类型就是:int [5]
下一个就是:char [5]
3. 一维数组的使用 3.1 下标引用操作符 我们可以用一维数组来存放我们的数据,那么我们存放之后如何提取呢???
什么是 Gerapy Gerapy 是一个基于 Scrapy 的分布式爬虫管理框架。它提供了一个图形化的用户界面,使得用户可以更方便地进行 Scrapy 项目的管理和调度。Gerapy 支持项目的创建、编辑、部署以及调度任务的管理。
功能作用 项目管理:Gerapy 允许用户通过 Web 界面创建、编辑和管理 Scrapy 项目。部署功能:用户可以直接通过 Gerapy 部署 Scrapy 项目到多台服务器,实现分布式爬取。任务调度:Gerapy 提供了任务调度功能,可以定时运行爬虫任务,并支持任务的监控和管理。Web UI 界面:Gerapy 提供了一个简单易用的 Web 用户界面,使得管理和运行爬虫变得更加直观和方便。 安装 Gerapy pip install gerapy 使用 gerapy -v 查看是否安装成功
初始化 Gerapy 项目 gerapy init 会在命令执行目录生成一个 gerapy 文件
启动 Gerapy 服务 在 gerapy 项目目录下执行以下命令:
gerapy migrate # 初始化数据库sqlite3数据库 gerapy runserver # 启动 gerapy 服务 访问 gerapy 的web页面地址:http://127.0.0.1:8000/
创建登录账号密码
gerapy createsuperuser 主机管理 添加 Scarpyd 主机节点
# 1 Maven设置 2 字体样式,字体颜色 3 插件 1,fitten code和通义灵码
2,one dark theme主题
3,mybatisX
4,Rainbow Brackets
5,Key Promoter X
设置 自动导入包
SpringBoot集成kafka-消费者批量消费消息 1、消费者2、生产者3、application.yml配置文件4、实体类5、生产者发送消息测试类6、测试6.1、测试启动生产者6.2、测试启动消费者 1、消费者 设置批量接收消息
package com.power.consumer; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; import java.util.List; @Component public class EventConsumer { @KafkaListener(topics = {"batchTopic"},groupId="batchGroup") public void onEvent(List<ConsumerRecord<String,String>> records){ System.out.println("批量消费:records.size() = "+records.size()+", records = "+records); } } 2、生产者 package com.power.producer; import com.power.model.User; import com.power.util.JSONUtils; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Date; @Component public class EventProducer { @Resource private KafkaTemplate<String,Object> kafkaTemplate; public void sendEvent(){ for (int i = 0; i < 125; i++) { User user = User.
单例模式 单例模式是一种常见的设计模式,旨在确保一个类只有一个实例,并提供一个全局访问点。这种模式常用于需要控制资源的类,比如配置管理、线程池等。
主要特性: 唯一性:类只有一个实例。全局访问:提供一个静态方法获取该实例。延迟加载(可选):实例在第一次使用时创建。 实现方法 1. 饿汉式单例 在类加载时就创建实例,线程安全,但不支持延迟加载。
public class Singleton { // 静态实例 private static final Singleton instance = new Singleton(); // 私有构造函数 private Singleton() {} // 公共方法获取实例 public static Singleton getInstance() { return instance; } } 2. 懒汉式单例 在第一次调用时创建实例,支持延迟加载,线程不安全的实现。
public class Singleton { // 静态实例 private static Singleton instance; // 私有构造函数 private Singleton() {} // 公共方法获取实例 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 3.
你是否曾经好奇过,当你在网上购物或使用手机应用时,背后的数据是如何被存储和分析的?答案就在数据仓库中。本文将为你揭开数据仓库的神秘面纱,深入探讨其核心组成部分,以及这些组件如何协同工作,将海量数据转化为有价值的商业洞察。
目录 引言:数据仓库的魔力1. 数据源和数据集成:数据仓库的"进水口"1.1 多样化的数据源1.2 数据集成:ETL过程1.3 实时数据集成:从批处理到流处理1.4 数据质量管理1.5 数据血缘和影响分析 2. 数据存储:数据仓库的"心脏"2.1 数据模型:星型模式vs雪花模式星型模式雪花模式 2.2 分区和分桶分区(Partitioning)分桶(Bucketing) 2.3 列式存储vs行式存储行式存储列式存储 2.4 数据压缩2.5 数据分层2.6 数据湖与数据仓库的融合 3. 元数据管理:数据仓库的"大脑"3.1 元数据的类型3.2 元数据仓库3.3 元数据管理工具3.4 数据目录 4. 数据访问和分析工具:数据仓库的"出口" 引言:数据仓库的魔力 想象一下,你正在经营一家全球性的电子商务公司。每天,成千上万的订单涌入,客户遍布全球各地,产品种类繁多。如何从这些看似杂乱无章的数据中,提取出有价值的信息,指导业务决策?这就是数据仓库发挥魔力的地方。
数据仓库就像是一个巨大的数据中枢,它将来自不同来源的数据整合在一起,经过清洗、转换和组织,最终呈现出一幅清晰的业务全景图。但是,要实现这一点,数据仓库需要依靠几个关键组件的紧密配合。
接下来,我们将深入探讨数据仓库的四大核心组成部分:
数据源和数据集成数据存储元数据管理数据访问和分析工具
让我们开始这段探索数据仓库内部结构的奇妙旅程吧!
1. 数据源和数据集成:数据仓库的"进水口" 1.1 多样化的数据源 数据仓库的第一个关键组成部分是数据源。在我们的电子商务公司示例中,数据可能来自以下几个方面:
交易系统:记录每一笔订单的详细信息客户关系管理(CRM)系统:存储客户的个人信息和互动历史库存管理系统:跟踪产品库存和供应链信息网站和移动应用:捕获用户行为数据,如浏览历史、点击流等社交媒体平台:收集客户评论和反馈外部数据源:如市场调研报告、竞争对手信息等 这些数据源的格式可能各不相同,有结构化的(如关系型数据库中的表格数据),也有半结构化的(如JSON或XML格式的日志文件),还有非结构化的(如客户评论文本)。
1.2 数据集成:ETL过程 将这些杂乱的数据转化为有意义的信息,需要经过一个被称为ETL(Extract, Transform, Load)的过程:
提取(Extract): 从各个源系统中提取数据转换(Transform): 清洗、转换和整合数据加载(Load): 将处理后的数据加载到数据仓库中 让我们通过一个具体的例子来说明ETL过程:
假设我们需要整合来自交易系统和CRM系统的数据,以分析客户购买行为。
import pandas as pd from sqlalchemy import create_engine # 连接到源数据库 transaction_db = create_engine('postgresql://user:password@localhost:5432/transaction_db') crm_db = create_engine('mysql://user:password@localhost:3306/crm_db') # 提取数据 transactions = pd.
SQL查询过程 查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。分析器: 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。优化器: 按照 MySQL 认为最优的方案去执行。执行器: 执行语句,然后从存储引擎返回数据。 执行语句之前会先判断是否有权限,如果没有权限的话,就会报错。插件式存储引擎:主要负责数据的存储和读取,采用的是插件式架构,支持 InnoDB、MyISAM、Memory 等多种存储引擎。 创建高性能索引 什么是索引 数据库表中的一种数据结构,用于加速数据检索。通过创建索引,数据库可以更快速地找到所需的数据,而不需要扫描整个表。
为什么需要索引 MySQL 索引优化是提高数据库查询性能的重要手段。索引可以显著减少数据库需要扫描的数据量,从而加快查询速度。但索引的使用和管理也需要谨慎,错误的索引策略可能会导致性能下降或增加维护成本。
索引原理 数据结构:
B+ 树:MySQL 的默认索引结构是 B+ 树,它是一种自平衡的树数据结构。B+ 树的每个节点包含多个键值和指向子节点的指针。B+ 树的叶子节点还包含数据指针或者数据行的 ID(在聚簇索引中)。在 B+ 树中,所有叶子节点处于同一层级,这使得查找、插入和删除操作的时间复杂度为 O(log N)。哈希表:用于 HASH 索引(如 MEMORY 存储引擎),哈希表通过计算哈希值来直接访问数据,但不支持范围查询。 索引类型 主键索引(Primary Key Index):唯一标识表中的每一行,自动创建在主键列上。唯一索引(Unique Index):确保索引列中的所有值都是唯一的。普通索引(Regular Index):最常用的索引类型,没有唯一性限制。全文索引(Full-Text Index):用于全文搜索,如搜索包含特定单词的文本。组合索引(Composite Index):索引多个列,可以加速对这些列的组合查询。 索引优化 唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。为经常需要排序、分组和联合操作的字段建立索引。为常作为查询条件的字段建立索引。限制索引的数目:越多的索引,会使更新表变得很浪费时间。尽量使用数据量少的索引。如果索引的值很长,那么查询的速度会受到影响。尽量使用前缀来索引,如果索引字段的值很长,最好使用值的前缀来索引。删除不再使用或者很少使用的索引最左前缀匹配原则,非常重要的原则。尽量选择区分度高的列作为索引索引列不能参与计算,保持列“干净”:带函数的查询不参与索引。尽量的扩展索引,不要新建索引。 慢SQL优化 抓取慢SQL 启用慢查询日志 慢查询日志可以记录所有执行时间超过指定阈值的查询。
编辑 MySQL 配置文件(通常是 my.cnf 或 my.ini):
slow_query_log = 1 slow_query_log_file = /path/to/your/slow-query.log long_query_time = 2 slow_query_log: 启用慢查询日志(1 表示启用,0 表示禁用)。
线程是什么 线程是操作系统中调度的基本单位,是比进程更小的执行单元。线程在进程内部运行,共享该进程的资源,如内存和文件句柄,但每个线程都有自己的执行栈和程序计数器。
线程的主要特点包括:
轻量级:线程相较于进程更加轻量,创建和销毁的开销较小。共享资源:同一进程中的线程共享该进程的内存空间和资源,从而可以更高效地进行数据交换。并发执行:多个线程可以并发执行,充分利用多核处理器,提高程序的执行效率。简化管理:线程的切换和管理相对于进程更为简单和迅速,有助于提升系统的响应速度。 线程的使用在现代操作系统中非常普遍,尤其是在需要高并发和高性能的应用场景中,例如网络服务器和多任务应用程序等。
为什么要有线程 首先, "并发编程" 成为 "刚需"。
单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU资源。
有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编
程。
其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量。
创建线程比创建进程更快.销毁线程比销毁进程更快.调度线程比调度进程更快. 创建出一个线程 在Java中,可以通过两种主要方式创建线程:继承Thread类和实现Runnable接口。下面分别介绍这两种方式,并附上代码示例。
方法一:继承 Thread 类 创建一个子类,继承Thread类,并重写run()方法,该方法包含了线程的执行代码。创建子类的实例,然后调用start()方法来启动线程。 示例代码:
class MyThread extends Thread { @Override public void run() { // 线程执行的代码 for (int i = 0; i < 5; i++) { System.out.println("Thread running: " + i); try { Thread.sleep(1000); // 暂停1秒 } catch (InterruptedException e) { e.
文章目录 一、快速响应与精准问题定位1. 实时监控与预警系统2. 高效的日志管理和分析3. 分布式追踪与调用链分析4. 紧急响应机制 二、建立健全的应急预案与备份机制1. 制定详尽的应急预案2. 定期应急演练3. 数据备份与快速恢复4. 冗余部署与负载均衡 三、事后总结与持续改进1. 全面复盘与反思2. 转化成果与落地实施3. 建立持续改进机制4. 培养危机意识和文化 四、示例场景1. 日志记录3. 紧急响应流程 在AIGC时代,技术的飞速发展不仅带来了前所未有的创新机遇,也伴随着日益复杂的技术挑战和潜在的风险。面对突如其来的技术故障和危机,开发团队必须具备高度的敏锐性、快速响应能力和有效的解决方案,以确保软件服务的稳定性和用户体验的连续性。
一、快速响应与精准问题定位 1. 实时监控与预警系统 开发团队应构建全面的实时监控体系,利用先进的监控工具(如Prometheus、Grafana等)对系统性能、资源使用情况、网络状态等进行实时监控。通过预设的阈值和智能算法,实现异常情况的自动预警,确保问题能够在第一时间被发现。
2. 高效的日志管理和分析 日志是诊断问题的重要线索。开发团队应采用集中式日志管理系统(如ELK Stack)来收集、存储和分析各类日志数据。通过日志分析,可以快速定位问题发生的具体时间和位置,为问题解决提供有力支持。
3. 分布式追踪与调用链分析 在微服务架构下,服务间的调用关系错综复杂。开发团队应引入分布式追踪系统(如Jaeger、Zipkin等),实现服务调用链的追踪和可视化。这有助于快速定位跨服务调用中的故障点,提高问题排查效率。
4. 紧急响应机制 建立明确的紧急响应流程和机制,包括快速组建应急响应小组、启动应急预案、通知相关利益方等。通过高效的沟通和协作,确保问题能够在最短时间内得到解决。
二、建立健全的应急预案与备份机制 1. 制定详尽的应急预案 针对可能发生的各类技术故障和危机,开发团队应提前制定详尽的应急预案。预案应包括故障类型、处理流程、责任人、通讯方式等关键信息,确保每位成员都清楚自己的职责和行动步骤。
2. 定期应急演练 通过定期举行应急演练,模拟真实场景下的故障处理过程,检验应急预案的有效性和可操作性。演练过程中应注重团队协作、沟通效率和问题解决能力的提升。
3. 数据备份与快速恢复 数据是企业的核心资产。开发团队应建立完善的数据备份机制,确保关键数据的定期备份和存储安全。同时,建立快速恢复流程,以便在数据丢失或损坏时能够迅速恢复业务运行。
4. 冗余部署与负载均衡 通过冗余部署和负载均衡技术,提高系统的可用性和容错能力。在关键服务节点上部署多个实例,并通过负载均衡器实现流量的合理分配和故障切换,确保服务的连续性和稳定性。
三、事后总结与持续改进 1. 全面复盘与反思 每次危机过后,开发团队都应组织全面的复盘会议。通过深入分析故障原因、处理过程、影响范围等方面的问题,总结经验教训和不足之处。同时,反思应急响应机制的有效性和团队协作能力等方面的问题,为未来的工作提供改进方向。
2. 转化成果与落地实施 将复盘结果转化为具体的改进措施和行动计划,并明确责任人和时间节点。通过制定详细的改进方案和实施计划,确保改进措施能够得到有效落地和实施。同时,建立跟踪和评估机制,对改进效果进行持续监测和评估。
3. 建立持续改进机制 开发团队应建立持续改进机制,将危机应对和问题解决作为日常工作的一部分。通过引入敏捷开发、持续集成/持续部署(CI/CD)等先进理念和方法,不断优化系统架构、提升代码质量和团队协作能力。同时,加强员工培训和技能提升工作,提高团队成员的技术水平和应急响应能力。
4. 培养危机意识和文化 通过定期培训和案例分析等方式,培养团队成员的危机意识和应对能力。让“居安思危”成为团队文化的一部分,使每位成员都能够在日常工作中保持高度的警惕性和敏感性。同时,鼓励团队成员积极参与危机应对和问题解决工作,形成积极向上的工作氛围和团队精神。
四、示例场景 假设我们有一个Web服务,该服务在处理请求时可能会因为某些原因(如数据库连接失败、外部服务响应超时等)而抛出异常。我们需要实现一个系统来监控这些异常,并在发生时立即通知开发团队。
1. 日志记录 首先,我们需要确保服务在运行过程中能够详细记录日志。这里使用Python的logging模块作为示例:
import logging # 配置日志 logging.
github官网代码示例:https://github.com/rmax/scrapy-redis/blob/master/example-project/example/spiders/myspider_redis.py
什么是 Scrapy-Redis Scrapy-Redis 是一个基于 Scrapy 的扩展,用于实现分布式爬虫。它利用 Redis 作为分布式队列来共享待爬取的 URL 和去重数据,这样可以让多个爬虫实例(即多个爬虫节点)并行工作,从而实现大规模的分布式数据抓取。
把普通爬虫改造成分布式爬虫 使用普通爬虫,改造成分布式爬虫,更便于理解 1. 安装 scrapy_redis框架模块 pip install scrapy_redis 2. 爬虫类修改如下: import scrapy # --- 1. 导入分布式爬虫类 from scrapy_redis.spiders import RedisSpider # --- 2. 继承分布式爬虫类 class BaiduSpider(RedisSpider): name = "baidu" # --- 3. 注释原来普通爬虫的 allowed_domains,start_urls # allowed_domains = ["baidu.com"] # start_urls = ["https://www.baidu.com"] # --- 4. 设置redis的key,起始url就存在这个key里 redis_key = "baidu" # --- 5. 设置 __init__,固定写法如下 def __init__(self, *args, **kwargs): domain = kwargs.
堆《数据结构》 1. 堆排序1.1 建堆向上调整建堆向下调整建堆 1.2 利用堆删除思想来进行排序1.3Top-k问题 2.堆的时间复杂度 1. 堆排序 1.1 建堆 建大堆
建小堆
向上调整建堆 AdjustUp建堆
void AdjustUp(HPDataType* a, int child) { // 初始条件 // 中间过程 // 结束条件 int parent = (child - 1) / 2; //while (parent >= 0) while (child > 0) { if (a[child] < a[parent]) { Swap(&a[child], &a[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } } void headsport(int* a, int n)//建堆 { for (int i = 0;i < n;i++) { AdjustUp(a, i);//建堆(降序建大堆)向上调整建堆 } } void test1() { int arr[] = {1,5,3,2,7,9,4,6,8}; headsport(arr, sizeof(arr) / sizeof(arr[0])); for (int i = 0;i < sizeof(arr) / sizeof(arr[0]);i++) { printf("
多元线性回归 指的 是一个样本 有多个特征的 线性回归问题。 w 被统称为 模型的 参数,其中 w0 被称为截距(intercept),w1~wn 被称为 回归系数(regression coefficient)。这个表达式和 y=az+b 是同样的性质。其中 y 是目标变量,也就是 标签。xi1~xin 是样本 i 上的特征 不同特征。如果考虑有 m 个样本,则回归结果 可以被写作:
实例代码:
#一元线型回归模型 print("一元线型回归模型") import sklearn.linear_model as lm import numpy as np x=[1,3,5,7] x=np.array(x).reshape(-1,1) print(x) y = np.array([2, 4, 6, 8]) model=lm.LinearRegression() model.fit(x,y) print("Intercept:",model.intercept_) print("Coefficients:",model.coef_) print("Prediction for 60:",model.predict(np.array([60]).reshape((-1, 1)))) #二元线型回归模型(多元线型回归与此类似) print("二元线型回归模型") x1=[1,2,3,4] x2=[2,4,6,8] #矩形转置 x=np.array([x1,x2]).T print(x) x=np.array([x1,x2]).reshape(-1,2) y = np.array([2, 4, 6, 8]) model=lm.LinearRegression() model.
首先给一个我之前写的双指针在链表类题中的妙用的link:双指针在链表中的妙用 tip1 来自“合并两个有序链表” 题目链接戳这里
这道题注意的就是如果是要返回一个新链表的头结点,一定要新建一个头结点: ListNode* prehead = new ListNode(-1); 之后再对prehead的next进行添加,而不是对传进来的参数节点(比如list1)进行遍历、或其他改变后,再直接返回参数节点(当list1遍历的时候指向了尾结点,这样就找不到开始的头结点了)!代码如下 /** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { ListNode* prehead = new ListNode(-1); // 申请一片新的空间的办法 ListNode* pre = prehead; // 暂时指向这片区域,然后之后只更改next while (list1 !
一、消息的概 从广义角度来说,消息其实就是信息,但是和信息又有所不同。信息通常被定义为一组数据,而消息除了具有数据的特征之外,还有消息的来源与接收的概念。通常发送消息的一方称为消息的生产者,接收消息的一方称为消息的消费者。这样比较后,发现其实消息和信息差别还是很大的。
为什么要设置生产者和消费者呢?这就是要说到消息的意义了。信息通常就是一组数据,但是消息由于有了生产者和消费者,就出现了消息中所包含的信息可以被二次解读,生产者发送消息,可以理解为生产者发送了一个信息,也可以理解为生产者发送了一个命令;消费者接收消息,可以理解为消费者得到了一个信息,也可以理解为消费者得到了一个命令。对比一下我们会发现信息是一个基本数据,而命令则可以关联下一个行为动作,这样就可以理解为基于接收的消息相当于得到了一个行为动作,使用这些行为动作就可以组织成一个业务逻辑,进行进一步的操作。总的来说,消息其实也是一组信息,只是为其赋予了全新的含义,因为有了消息的流动,并且是有方向性的流动,带来了基于流动的行为产生的全新解读。开发者就可以基于消息的这种特殊解,将其换成代码中的指令。
对于消息的理解,初学者总认为消息内部的数据非常复杂,这是一个误区。比如我发送了一个消息,要求接受者翻译发送过去的内容。初学者会认为消息中会包含被翻译的文字,已经本次操作要执行翻译操作而不是打印操作。其实这种现象有点过度解读了,发送的消息中仅仅包含被翻译的文字,但是可以通过控制不同的人接收此消息来确认要做的事情。例如发送被翻译的文字仅到A程序,而A程序只能进行翻译操作,这样就可以发送简单的信息完成复杂的业务了,是通过接收消息的主体不同,进而执行不同的操作,而不会在消息内部定义数据的操作行为,当然如果开发者希望消息中包含操作种类信息也是可以的,只是提出消息的内容可以更简单,更单一。
对于消息的生产者与消费者的工作模式,还可以将消息划分成两种模式,同步消费与异步消息。
所谓同步消息就是生产者发送完消息,等待消费者处理,消费者处理完将结果告知生产者,然后生产者继续向下执行业务。这种模式过于卡生产者的业务执行连续性,在现在的企业级开发中,上述这种业务场景通常不会采用消息的形式进行处理。
所谓异步消息就是生产者发送完消息,无需等待消费者处理完毕,生产者继续向下执行其他动作。比如生产者发送了一个日志信息给日志系统,发送过去以后生产者就向下做其他事情了,无需关注日志系统的执行结果。日志系统根据接收到的日志信息继续进行业务执行,是单纯的记录日志,还是记录日志并报警,这些和生产者无关,这样生产者的业务执行效率就会大幅度提升。并且可以通过添加多个消费者来处理同一个生产者发送的消息来提高系统的高并发性,改善系统工作效率,提高用户体验。一旦某一个消费者由于各种问题宕机了,也不会对业务产生影响,提高了系统的高可用性。
以上简单的介绍了一下消息这种工作模式存在的意义,希望对各位学习者有所帮助。
二、Java处理消息的标准规范(了解) 目前企业级开发中广泛使用的消息处理技术共三大类,具体如下:
JMS
AMQP
MQTT
为什么是三大类,而不是三个技术呢?因为这些都是规范,就想JDBC技术,是个规范,开发针对规范开发,运行还要靠实现类,例如MySQL提供了JDBC的实现,最终运行靠的还是实现。并且这三类规范都是针对异步消息进行处理的,也符合消息的设计本质,处理异步的业务。对以上三种消息规范做一下普及
JMS JMS(Java Message Service),这是一个规范,作用等同于JDBC规范,提供了与消息服务相关的API接口。
JMS消息模型
JMS规范中规范了消息有两种模型。分别是点对点模型和发布订阅模型。
点对点模型:peer-2-peer,生产者会将消息发送到一个保存消息的容器中,通常使用队列模型,使用队列保存消息。一个队列的消息只能被一个消费者消费,或未被及时消费导致超时。这种模型下,生产者和消费者是一对一绑定的。
发布订阅模型:publish-subscribe,生产者将消息发送到一个保存消息的容器中,也是使用队列模型来保存。但是消息可以被多个消费者消费,生产者和消费者完全独立,相互不需要感知对方的存在。
以上这种分类是从消息的生产和消费过程来进行区分,针对消息所包含的信息不同,还可以进行不同类别的划分。
JMS消息种类
根据消息中包含的数据种类划分,可以将消息划分成6种消息。
TextMessage
MapMessage
BytesMessage
StreamMessage
ObjectMessage
Message (只有消息头和属性)
JMS主张不同种类的消息,消费方式不同,可以根据使用需要选择不同种类的消息。但是这一点也成为其诟病之处,后面再说。整体上来说,JMS就是典型的保守派,什么都按照J2EE的规范来,做一套规范,定义若干个标准,每个标准下又提供一大批API。目前对JMS规范实现的消息中间件技术还是挺多的,毕竟是皇家御用,肯定有人舔,例如ActiveMQ、Redis、HornetMQ。但是也有一些不太规范的实现,参考JMS的标准设计,但是又不完全满足其规范,例如:RabbitMQ、RocketMQ。
AMQP JMS的问世为消息中间件提供了很强大的规范性支撑,但是使用的过程中就开始被人诟病,比如JMS设置的极其复杂的多种类消息处理机制。本来分门别类处理挺好的,为什么会被诟病呢?原因就在于JMS的设计是J2EE规范,站在Java开发的角度思考问题。但是现实往往是复杂度很高的。比如我有一个.NET开发的系统A,有一个Java开发的系统B,现在要从A系统给B系统发业务消息,结果两边数据格式不统一,没法操作。JMS不是可以统一数据格式吗?提供了6种数据种类,总有一款适合你啊。NO,一个都不能用。因为A系统的底层语言不是Java语言开发的,根本不支持那些对象。这就意味着如果想使用现有的业务系统A继续开发已经不可能了,必须推翻重新做使用Java语言开发的A系统。
这时候有人就提出说,你搞那么复杂,整那么多种类干什么?找一种大家都支持的消息数据类型不就解决这个跨平台的问题了吗?大家一想,对啊,于是AMQP孕育而生。
单从上面的说明中其实可以明确感知到,AMQP的出现解决的是消息传递时使用的消息种类的问题,化繁为简,但是其并没有完全推翻JMS的操作API,所以说AMQP仅仅是一种协议,规范了数据传输的格式而已。
AMQP(advanced message queuing protocol):一种协议(高级消息队列协议,也是消息代理规范),规范了网络交换的数据格式,兼容JMS操作。 优点
具有跨平台性,服务器供应商,生产者,消费者可以使用不同的语言来实现
JMS消息种类
AMQP消息种类:byte[]
AMQP在JMS的消息模型基础上又进行了进一步的扩展,除了点对点和发布订阅的模型,开发了几种全新的消息模型,适应各种各样的消息发送。
AMQP消息模型
direct exchange
fanout exchange
topic exchange
headers exchange
system exchange
目前实现了AMQP协议的消息中间件技术也很多,而且都是较为流行的技术,例如:RabbitMQ、StormMQ、RocketMQ
MQTT MQTT(Message Queueing Telemetry Transport)消息队列遥测传输,专为小设备设计,是物联网(IOT)生态系统中主要成分之一。由于与JavaEE企业级开发没有交集,此处不作过多的说明。
除了上述3种J2EE企业级应用中广泛使用的三种异步消息传递技术,还有一种技术也不能忽略,Kafka。
KafKa Kafka,一种高吞吐量的分布式发布订阅消息系统,提供实时消息功能。Kafka技术并不是作为消息中间件为主要功能的产品,但是其拥有发布订阅的工作模式,也可以充当消息中间件来使用,而且目前企业级开发中其身影也不少见。
本节内容讲围绕着上述内容中的几种实现方案讲解springboot整合各种各样的消息中间件。由于各种消息中间件必须先安装再使用,下面的内容采用Windows系统安装,降低各位学习者的学习难度,基本套路和之前学习NoSQL解决方案一样,先安装再整合。
三、SpringBoot整合ActiveMQ ActiveMQ是MQ产品中的元老级产品,早期标准MQ产品之一,在AMQP协议没有出现之前,占据了消息中间件市场的绝大部分份额,后期因为AMQP系列产品的出现,迅速走弱,目前仅在一些线上运行的产品中出现,新产品开发较少采用。
目录
前言:
1.前置说明
2.链式二叉树的遍历 2.1 前序,中序及后续遍历
2.2 前序遍历实现
2.3 中序遍历实现 2.4 后续遍历实现 3.结点个数以及高度等 3.1 结点个数
3.2 结点高度 3.3 叶子结点的个数 前言: 在之前的学习中,我们初步学习了二叉树的概念和实现二叉树的顺序结构,最主要的是使用二叉树的顺序结构建堆,从而实现堆排序,这一章我们要学习的是二叉树的另一个结构——二叉树的链式结构,与顺序结构不同的是,顺序结构的底层是一个数组,链式结构是使用递归将多个结点链接起来组成的二叉树,讲到这里,递归还不是很熟悉的小伙伴需要回去复习递归的知识才能更好的理解链式二叉树的实现,话不多是,我们马上开始这一期的学习吧。
1.前置说明 在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们会过头再来研究二叉树真正的创建方式。
使用结构体来定义二叉树的结点,里面包含了它的数据和它的左子树,右子树,我们不知道将来会存什么类型的数据在结点中,所以使用typedef关键字来对它的数据类型改名,现在我们使用的是int类型,如果我们以后要使用char类型,只需要将第一行的int改成char就能实现了,如果我们不使用这个操作,将来要更改数据类型时,只能在各个函数中一个一个改,几十行几百行代码我们要改类型工作量还不是很大,如果是几万行几十万行其中的工作量有多大可想而知。
typedef int BTDateType; typedef struct BinaryTreeNode { BTDateType data; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BTNode; BTNode* creratNode() { BTNode* node1 = BuyNode(1); BTNode* node2 = BuyNode(2); BTNode* node3 = BuyNode(3); BTNode* node4 = BuyNode(4); BTNode* node5 = BuyNode(5); BTNode* node6 = BuyNode(6); node1->left = node2; node2->left = node3; node1->right = node4; node4->left = node5; node4->right = node6; return node1; } 注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。 再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:
浅谈Kafka(三) 文章目录 浅谈Kafka(三)Kafka目录介绍基础操作JMX接口消费者是否能够消费指定分区的消息生产者是否发送消息到leader创建主题时如何把分区放到不同broker中Kafka新建的分区在哪个目录创建Kafka java示例 Kafka目录介绍 bin:执行脚本config:配置文件libs:运行所需要的jar包logs:日志文件site-docs:网站的帮助文档 基础操作 创建topic、生产消息到Kafka、从Kafka消费消息。 # 创建主题test bin/kafka-topics.sh --create-topic test --bootstrap-server localhost:9092 # 查看目前Kafka中的主题 bin/kafka-topics.sh --list --bootstrap-server localhost:9092 # 从Kafka中读取消息 bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning 图形化操作工具有KafkaTools、Kafka-Eagle等。内置性能测试工具 kafka-producer-perf-test.shkafka-consumer-perf-test.sh # 基于1个分区1个副本的基准测试 bin/kafka-topics.sh --bootstrap-server lcoalhost:9092 --create-topic benchmark --partitions 1 --replication-factor 1 # --throughput指定吞吐量,-l不指定,--record-size指定record数据大小 bin/kafka-producer-perf-test.sh --topic benchmark --num-records 50000000 -–throughput -l -–record-size 1000 –-producer-props bootstrap.servers=localhost:9092 acks=1 Kafka集群搭建 Kafka版本号:kafka_2.12-2.4.1,kafka采用scale开发,2.12为scale的版本号。 1. 安装包的上传解压 2. 修改kafka的config目录下的server.properties配置文件 broker.id=0 指定broker的id log.dirs=/data 指定kafka数据的位置 3. 把安装好的kafka复制到另外两台机器 4. 配置环境变量 vi /etc/profile; export KAFKA_HOME=/kafka_2.
初识Pytest Pytest1.Pytest的特点:2.Pytest的基本使用规则3.pytest安装1)使用编译器安装2)使用命令安装 4.pytest规则 Pytest Pytest是python的一个第三方单元测试库,它的目的是让单元测试变得容易,并且也能扩展到支持应用层面复杂的功能测试。
1.Pytest的特点: 1.支持用简单的assert语句实现丰富的断言,无需复杂的self.assert函数
# 相等性断言 assert a == b # 真值断言 当expression值为真时通过 assert expression # 包含断言 assert item in iterable assert item not in iterable # 近似值断言(需要导入pytest.approx) assert a == approx(b) # 异常断言(需要使用到pytest.raises) with pytest.raises(ValueError) as excinfo: func_that_raises() assert str(excinfo.value) == "错误信息" 2.自动识别测试模块以及测试函数
测试文件识别 1.pytest会以命名规则来查找对应测试模块源文件,查找文件的格式为前置test_和后置_test,不符合条件的会被忽略。
2.使用递归查找符合这个规范的文件。
测试函数识别 1.在测试源文件中会将所有test开头的函数或者方法会被认定为测试函数或者测试方法,pytest会执行这些方法,不符合条件的会被忽略。
2.pytest也支持类和方法编写测试,但测试方法同样需要遵循以test开头的命名规则。
自定义规则 1.在pytest.ini中可以修改默认测试发现规则,可以指定不同的测试文件命名规则或者测试函数命名规则。
插件优化 1.开发者可以下载第三方插件来添加新的测试规则以及优化现有的规则。
3.模块化用于管理各种测试资源
pytest管理测试志愿使用的是Fixture机制,它提供了一个为测试用例提供了一个设置资源的机制,使得测试环境、测试数据变得更加模块化和灵活。
4.对unittest完全兼容,对nose基本兼容
对于unittest,pytest可以自动识别并执行unittest风格的测试用例,包括使用unittest编写的测试方法,测试类。这相当于你在unittest中写的自动化测试方法可以容易的进行迁移到pytest中,而不需要大量修改。nose是一个比较老的python框架,但pytest也保持对该框架的兼容,对于大多数的nose测试用例,pytest可以保持基本支持。 5.支持python3和pypy3
现在python3已经成为python的一个主要版本,也是推荐的主流版本,开发者可以使用pytest在python3上进行使用,利用更多python3的特性,提升测试效率。pypy3是一个流行的python解释器,使用即时编译来提高python程序的运行效率,与标准的解释器相比,这个pypy3更加能够提升程序的执行效率,尤其是在处理大量计算或者循环时。 6.丰富的插件生态,社区生态繁荣,维护效率高,可扩展性强
pytest有非常庞大的社区交流地,当开发时遇到困难可以获得及时的解决。使用pytest进行扩展时,因为大量的插件生态而使得开发者可以快速找到自己需要的工具。
2.Pytest的基本使用规则 用例编写规则
1)测试文件名必须以test_或者_test结尾。
如‘test_ab.
简介 MetaObjectHandler 是一个非常有用的组件,用于处理实体对象中的字段填充逻辑,比如自动填充创建时间、更新时间、创建人、修改人等字段。
组件介绍 MetaObjectHandler 接口允许在不修改业务代码的情况下,对实体类中的字段进行自动填充。这通常用于记录创建时间、更新时间、创建人、修改人等元数据信息。例如,在用户注册时自动设置创建时间,在更新用户信息时自动更新最后修改时间。
此处实现自动填充创建人、修改人
创建时间、修改时间等交给MySQL进行自动填充
使用 实现 MetaObjectHandler 接口:
首先需要创建一个类实现 MetaObjectHandler 接口,并重写其中的方法。 package com.zk.app.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.zk.app.utils.UserThreadLocalUtil; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; /** * @program: ZK * @description: 自定义mubatisplust填充 * @author: zk * @create: 2024-07-17 11:30 **/ @Component public class ZKMetaObjectHandler implements MetaObjectHandler { /** * 创建人字段 */ private static final String CREATE_USER_ID = "createUserId"; /** * 修改人字段 */ private static final String UPDATE_USER_ID = "updateUserId"; @Override public void insertFill(MetaObject metaObject) { this.
目录 一、缓存应用二、淘汰机制三、LRU 算法四、LFU 算法 一、缓存应用 一个系统中不同层面数据访问速度不一样,以计算机为例,CPU、内存和磁盘这三层的访问速度从几十 ns 到 100ns,再到几 ms,性能的差异很大,如果每次 CPU 处理数据时都要到磁盘读取数据,系统运行速度会大大降低。
所以,计算机系统中,默认有两种缓存:
(1)CPU 里面的末级缓存,即 LLC,用来缓存内存中的数据,避免每次从内存中存取数据。(2)内存中的高速页缓存,即 page cache,用来缓存磁盘中的数据,避免每次从磁盘中存取数据。
在一个层次化的系统中,缓存一定是一个快速子系统,数据存在缓存中时,能避免每次从慢速子系统中存取数据。对应到互联网应用来说,Redis 就是快速子系统,而数据库就是慢速子系统了。
Redis 是一个独立的系统软件,如果应用程序想使用 Redis 缓存,就需要增加相应的代码。所以,我们也把 Redis 称为旁路缓存,也就是说,读取缓存、读取数据库和更新缓存的操作都需要在应用程序中来完成。
Redis 缓存按照是否接受写请求,分为只读缓存和读写缓存两种类型,只读缓存能加速读请求,而读写缓存可以同时加速读写请求。读写缓存又分为同步直写和异步写回,可以根据业务需求在保证性能和保证数据可靠性之间进行选择。
二、淘汰机制 缓存的容量终究是有限的,需要按一定规则淘汰出去,为新来的数据腾出空间,提高缓存命中率,提升应用的访问性能。缓存容量的规划通常是需要结合应用数据实际访问特征和成本开销来综合考虑的,建议把缓存容量设置为总数据量的 15% 到 30%,兼顾访问性能和内存空间开销。设置容量命令(如4gb):CONFIG SET maxmemory 4gb
8种淘汰策略:noeviction、volatile-random、volatile-ttl、volatile-lru、volatile-lfu、allkeys-lru、allkeys-random、allkeys-lfu
大体分为两类,noeviction(不淘汰数据),缓存被写满了,再有写请求时 Redis 不再提供服务,直接返回错误。另外7种是一类,按照一定范围对缓存数据进行淘汰,对设置过期时间的数据进行淘汰,和对所有数据进行淘汰。分类如图:
具体策略如下:
(1)volatile-ttl: 根据过期时间的先后进行删除,越早过期的越先被删除。(2)volatile-rando: 在设置了过期时间的键值对中,进行随机删除。(3)volatile-lru: 使用 LRU 算法筛选设置了过期时间的键值对。(4)volatile-lfu: 使用 LFU 算法选择设置了过期时间的键值对。(5)allkeys-random:从所有键值对中随机选择并删除数据。(6)allkeys-lru: 使用 LRU 算法在所有数据中进行筛选。(7)allkeys-lfu: 使用 LFU 算法在所有数据中进行筛选。 三、LRU 算法 LRU 算法全称 Least Recently Used,按照最近最少使用的原则来筛选数据,最不常用的数据会被筛选出来,而最近频繁使用的数据会留在缓存中。
LRU 会把所有的数据组织成一个链表,链表的头和尾分别表示 MRU 端和 LRU 端,分别代表最近最常使用的数据和最近最不常用的数据。