MySQL作为最流行的关系型数据库管理系统之一,是每一位开发者必备的核心技能。本文将系统性地解析MySQL的基础知识,结合关键概念与实战应用,帮助您构建扎实的数据库基础。

1. SQL与NoSQL的本质区别

SQL(结构化查询语言)数据库与NoSQL(非仅SQL)数据库代表了两种不同的数据管理哲学。

​SQL数据库​​(如MySQL、PostgreSQL):

  • 基于表格结构,数据以行和列的形式存储
  • 遵循预定义的模式(schema),结构严谨
  • 支持ACID事务(原子性、一致性、隔离性、持久性)
  • 使用SQL进行数据操作和查询
  • 适合复杂查询和高数据一致性的场景

​NoSQL数据库​​(如MongoDB、Redis):

  • 无固定模式,数据结构灵活
  • 可存储文档、键值对、宽列或图形数据
  • 通常遵循BASE原则(基本可用、软状态、最终一致性)
  • 横向扩展能力强,适合大规模数据存储
  • 适合非结构化数据和快速迭代的场景

选择依据:需要强一致性和复杂查询选SQL;需要灵活性和大规模扩展选NoSQL。

2. 数据库三大范式详解

数据库范式是设计关系型数据库的指导原则,旨在减少数据冗余和提高数据一致性。

​第一范式(1NF)​​:确保每列保持原子性,即每列都是不可再分的最小数据单元

-- 不符合1NF的设计(存储多个电话号码)
CREATE TABLE students (id INT PRIMARY KEY,name VARCHAR(50),phones VARCHAR(100) -- 存储"13800138000,13900139000"
);-- 符合1NF的设计
CREATE TABLE students (id INT PRIMARY KEY,name VARCHAR(50)
);CREATE TABLE student_phones (id INT PRIMARY KEY,student_id INT,phone VARCHAR(15),FOREIGN KEY (student_id) REFERENCES students(id)
);

​第二范式(2NF)​​:在满足1NF基础上,消除非主键列对主键的部分函数依赖

-- 不符合2NF的设计(订单详情表中产品价格依赖于产品ID而非订单ID)
CREATE TABLE order_details (order_id INT,product_id INT,product_price DECIMAL(10,2), -- 依赖于product_id而非主键quantity INT,PRIMARY KEY (order_id, product_id)
);-- 符合2NF的设计
CREATE TABLE order_details (order_id INT,product_id INT,quantity INT,PRIMARY KEY (order_id, product_id)
);CREATE TABLE products (product_id INT PRIMARY KEY,price DECIMAL(10,2)
);

​第三范式(3NF)​​:在满足2NF基础上,消除传递依赖,即非主键列之间不能有函数依赖

-- 不符合3NF的设计(学院电话依赖于学院,而学院依赖于学生ID)
CREATE TABLE students (student_id INT PRIMARY KEY,name VARCHAR(50),department VARCHAR(50),department_phone VARCHAR(15) -- 依赖于department,传递依赖于student_id
);-- 符合3NF的设计
CREATE TABLE students (student_id INT PRIMARY KEY,name VARCHAR(50),department_id INT,FOREIGN KEY (department_id) REFERENCES departments(department_id)
);CREATE TABLE departments (department_id INT PRIMARY KEY,name VARCHAR(50),phone VARCHAR(15)
);

实际应用中,有时为了性能会故意违反范式(反规范化),但需谨慎权衡。

3. MySQL连表查询深度解析

连表查询是SQL最强大的功能之一,允许从多个表中提取和组合数据。

3.1 INNER JOIN(内连接)

返回两个表中匹配条件的行

SELECT orders.order_id, customers.name, orders.order_date
FROM orders
INNER JOIN customers ON orders.customer_id = customers.customer_id;

3.2 LEFT JOIN(左连接)

返回左表所有行,即使右表中没有匹配

SELECT customers.name, orders.order_id
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id
WHERE orders.order_id IS NULL; -- 查找从未下过单的客户

3.3 RIGHT JOIN(右连接)

返回右表所有行,即使左表中没有匹配

SELECT products.name, order_details.quantity
FROM order_details
RIGHT JOIN products ON order_details.product_id = products.product_id;

3.4 FULL OUTER JOIN(全外连接)

MySQL不直接支持FULL OUTER JOIN,但可通过UNION实现

SELECT customers.name, orders.order_id
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id
UNION
SELECT customers.name, orders.order_id
FROM customers
RIGHT JOIN orders ON customers.customer_id = orders.customer_id;

3.5 CROSS JOIN(交叉连接)

返回两个表的笛卡尔积

SELECT sizes.size, colors.color
FROM sizes
CROSS JOIN colors;

4. 避免重复插入数据的多种策略

4.1 使用PRIMARY KEY或UNIQUE约束

CREATE TABLE users (email VARCHAR(100) PRIMARY KEY, -- 主键自动具有唯一性name VARCHAR(50) NOT NULL
);-- 或者使用UNIQUE约束
CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY,email VARCHAR(100) UNIQUE, -- 添加唯一约束name VARCHAR(50) NOT NULL
);

4.2 INSERT IGNORE

忽略重复键错误,但会忽略所有错误,需谨慎使用

INSERT IGNORE INTO users (email, name) VALUES ('test@example.com', 'Test User');

4.3 ON DUPLICATE KEY UPDATE

遇到重复时执行更新操作

INSERT INTO users (email, name) 
VALUES ('test@example.com', 'Test User')
ON DUPLICATE KEY UPDATE name = VALUES(name);

4.4 REPLACE INTO

先删除重复行再插入新行(注意:这会删除整行而不仅仅是更新指定字段)

REPLACE INTO users (email, name) VALUES ('test@example.com', 'New Test User');

5. 数据类型深度解析

5.1 CHAR与VARCHAR的区别

  • ​CHAR​​:固定长度字符串,长度范围为0-255字符

    • 适合存储长度相对固定的数据(如MD5哈希、国家代码)
    • 存储时总会占用指定长度的空间,短字符串会用空格填充
    • 检索速度通常比VARCHAR快
  • ​VARCHAR​​:可变长度字符串,长度范围为0-65,535字符

    • 适合存储长度变化较大的数据
    • 仅占用实际数据长度+1或+2字节(长度前缀)的空间
    • 更新可能引起页分裂,影响性能

选择建议:长度基本固定的字段用CHAR,变化较大的用VARCHAR。

5.2 VARCHAR后面的数字代表什么?

VARCHAR(50)中的50表示​​最大字符数​​,而非字节数。在utf8mb4字符集下:

  • 每个字符最多占用4字节
  • 实际存储空间 = 字符数 × 每个字符的字节数 + 长度前缀(1-2字节)

5.3 INT(1)和INT(10)的区别

括号中的数字是​​显示宽度​​,仅影响某些客户端显示格式,不影响存储大小或范围。

  • 所有INT类型都占用4字节存储空间
  • 范围都是-2147483648到2147483647(有符号)或0到4294967295(无符号)
  • 使用ZEROFILL时,显示宽度才有实际意义
CREATE TABLE test_int (num1 INT(1) ZEROFILL, -- 显示为0001num2 INT(10) ZEROFILL -- 显示为0000000001
);

5.4 TEXT数据类型的容量限制

TEXT类型不能"无限大",但有多种变体满足不同需求:

  • TINYTEXT: 最大255字节(约255个字符)
  • TEXT: 最大65,535字节(约64KB)
  • MEDIUMTEXT: 最大16,777,215字节(约16MB)
  • LONGTEXT: 最大4,294,967,295字节(约4GB)

5.5 IP地址的存储最佳实践

​推荐方案​​:使用INT UNSIGNED存储IPv4地址

-- 存储时转换
INSERT INTO logs (ip_address) VALUES (INET_ATON('192.168.1.1'));-- 查询时转换回可读格式
SELECT id, INET_NTOA(ip_address) as ip FROM logs;-- 对于IPv6,使用VARBINARY(16)
ALTER TABLE logs ADD ipv6_address VARBINARY(16);

6. 外键约束全面解析

外键用于维护表间引用完整性,确保数据一致性。

6.1 创建外键约束

CREATE TABLE orders (order_id INT PRIMARY KEY,customer_id INT,order_date DATE,FOREIGN KEY (customer_id) REFERENCES customers(customer_id)ON DELETE CASCADEON UPDATE CASCADE
);

6.2 外键操作选项

  • ​RESTRICT​​(默认):阻止删除或更新被引用的行
  • ​CASCADE​​:级联操作,主表删除/更新时,从表相关行也删除/更新
  • ​SET NULL​​:主表删除/更新时,从表外键字段设为NULL
  • ​NO ACTION​​:与RESTRICT类似,但检查时机略有不同

6.3 外键的优缺点

​优点​​:

  • 保证数据完整性和一致性
  • 防止误删重要数据
  • 明确表间关系,提高可读性

​缺点​​:

  • 增加性能开销(每次修改都需要检查外键约束)
  • 可能导致死锁问题
  • 使分库分表更复杂

7. IN与EXISTS关键字的深度对比

7.1 IN运算符

SELECT * FROM employees 
WHERE department_id IN (SELECT department_id FROM departments WHERE location = 'NY');
  • 先执行子查询,将结果集物化
  • 然后执行主查询,检查值是否在物化结果集中
  • 适合子查询结果集较小的情况

7.2 EXISTS运算符

SELECT * FROM employees e
WHERE EXISTS (SELECT 1 FROM departments d WHERE d.department_id = e.department_id AND d.location = 'NY'
);
  • 对外部查询的每一行执行子查询
  • 子查询使用关联条件,通常可利用索引
  • 适合外部查询结果集较小或子查询能够有效利用索引的情况

7.3 性能对比建议

  • 当子查询结果集小且外部查询大时,使用IN
  • 当外部查询结果集小且子查询可有效利用索引时,使用EXISTS
  • 在可能的情况下,尽量使用JOIN代替IN或EXISTS

8. MySQL常用函数大全

8.1 字符串函数

-- 连接字符串
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM employees;-- 字符串截取
SELECT SUBSTRING('MySQL', 3, 3); -- 结果: 'SQL'-- 字符串替换
SELECT REPLACE('Hello World', 'World', 'MySQL'); -- 结果: 'Hello MySQL'-- 大小写转换
SELECT LOWER('MySQL'), UPPER('mysql');-- 去除空格
SELECT TRIM('  MySQL  '), LTRIM('  MySQL'), RTRIM('MySQL  ');

8.2 数值函数

-- 四舍五入
SELECT ROUND(123.4567, 2); -- 结果: 123.46-- 向上取整/向下取整
SELECT CEIL(123.4), FLOOR(123.8); -- 结果: 124, 123-- 绝对值
SELECT ABS(-123); -- 结果: 123-- 随机数
SELECT RAND(); -- 生成0-1之间的随机浮点数

8.3 日期时间函数

-- 当前日期时间
SELECT NOW(), CURDATE(), CURTIME();-- 日期加减
SELECT DATE_ADD(NOW(), INTERVAL 7 DAY); -- 7天后
SELECT DATE_SUB(NOW(), INTERVAL 1 MONTH); -- 1个月前-- 日期格式化
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'); -- 2023-01-01 12:34:56-- 日期提取
SELECT YEAR(NOW()), MONTH(NOW()), DAY(NOW());

8.4 聚合函数

SELECT COUNT(*) AS total_rows,AVG(salary) AS average_salary,MAX(salary) AS max_salary,MIN(salary) AS min_salary,SUM(salary) AS total_salary
FROM employees;

9. SQL查询语句执行顺序详解

理解SQL执行顺序是编写高效查询的关键:

  1. ​FROM​​ 和 JOIN:确定数据来源,执行表连接
  2. ​WHERE​​:过滤不符合条件的行
  3. ​GROUP BY​​:按指定列分组
  4. ​HAVING​​:过滤分组后的结果
  5. ​SELECT​​:选择要返回的列
  6. ​DISTINCT​​:去除重复行
  7. ​ORDER BY​​:排序结果集
  8. ​LIMIT​​/OFFSET:限制返回行数
-- 示例查询及其执行顺序
SELECT DISTINCT department, AVG(salary) as avg_salary
FROM employees
WHERE hire_date > '2020-01-01'
GROUP BY department
HAVING AVG(salary) > 50000
ORDER BY avg_salary DESC
LIMIT 10;

10. 实战SQL题目解析

10.1 题目一:不存在01课程但存在02课程的学生的成绩

SELECT sc.stuid, sc.subject_id, sc.score
FROM studentscore sc
WHERE sc.stuid IN (SELECT stuid FROM studentscore WHERE subject_id = '02'
) AND sc.stuid NOT IN (SELECT stuid FROM studentscore WHERE subject_id = '01'
);

10.2 题目二:查询总分排名5-10名的学生

SELECT stuid, SUM(score) as total_score
FROM studentscore
GROUP BY stuid
ORDER BY total_score DESC
LIMIT 4, 6; -- 从第5名开始,取6条记录(5-10名)

10.3 题目三:查询班级选课情况

SELECT s.student_id, s.name, GROUP_CONCAT(DISTINCT c.course_name ORDER BY c.course_name SEPARATOR ', ') as courses
FROM students s
LEFT JOIN student_courses sc ON s.student_id = sc.student_id
LEFT JOIN courses c ON sc.course_id = c.course_id
WHERE s.class_id = '特定班级ID'
GROUP BY s.student_id, s.name;

11. 用MySQL实现可重入锁

11.1 创建锁表

CREATE TABLE system_locks (lock_name VARCHAR(100) PRIMARY KEY,lock_owner VARCHAR(100) NOT NULL,lock_count INT DEFAULT 0,acquired_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB;

11.2 获取锁存储过程

DELIMITER $$CREATE PROCEDURE acquire_lock(IN p_lock_name VARCHAR(100),IN p_owner VARCHAR(100),IN p_timeout INT
)
BEGINDECLARE v_start_time INT DEFAULT UNIX_TIMESTAMP();DECLARE v_acquired BOOLEAN DEFAULT FALSE;DECLARE v_lock_count INT;WHILE NOT v_acquired AND UNIX_TIMESTAMP() - v_start_time < p_timeout DO-- 检查锁是否已被当前所有者持有SELECT lock_count INTO v_lock_count FROM system_locks WHERE lock_name = p_lock_name AND lock_owner = p_owner;IF v_lock_count IS NOT NULL THEN-- 重入锁:增加计数UPDATE system_locks SET lock_count = lock_count + 1, updated_at = CURRENT_TIMESTAMPWHERE lock_name = p_lock_name AND lock_owner = p_owner;SET v_acquired = TRUE;ELSE-- 尝试获取新锁BEGININSERT INTO system_locks (lock_name, lock_owner, lock_count)VALUES (p_lock_name, p_owner, 1);SET v_acquired = TRUE;EXCEPTIONWHEN 1062 THEN -- 唯一键冲突,锁已被其他进程持有DO SLEEP(0.1); -- 短暂等待后重试END;END IF;END WHILE;SELECT v_acquired as lock_acquired;
END
$$DELIMITER ;

11.3 释放锁存储过程

DELIMITER $$CREATE PROCEDURE release_lock(IN p_lock_name VARCHAR(100),IN p_owner VARCHAR(100)
)
BEGINDECLARE v_lock_count INT;START TRANSACTION;SELECT lock_count INTO v_lock_count FROM system_locks WHERE lock_name = p_lock_name AND lock_owner = p_ownerFOR UPDATE;IF v_lock_count IS NOT NULL THENIF v_lock_count > 1 THEN-- 减少重入计数UPDATE system_locks SET lock_count = lock_count - 1, updated_at = CURRENT_TIMESTAMPWHERE lock_name = p_lock_name AND lock_owner = p_owner;ELSE-- 完全释放锁DELETE FROM system_locks WHERE lock_name = p_lock_name AND lock_owner = p_owner;END IF;END IF;COMMIT;
END
$$DELIMITER ;

11.4 使用示例

-- 获取锁
CALL acquire_lock('order_processing', 'service_1', 10);-- 执行需要加锁的操作...-- 释放锁
CALL release_lock('order_processing', 'service_1');

总结

MySQL基础知识涵盖了从数据类型选择到复杂查询优化的各个方面。掌握这些核心概念对于构建高效、可靠的数据库应用至关重要。在实际开发中,应该:

  1. 根据业务需求合理选择数据类型和表结构设计
  2. 理解SQL执行顺序,编写高效的查询语句
  3. 合理使用索引和约束保证数据完整性和查询性能
  4. 在复杂场景下考虑使用事务和锁机制保证数据一致性

持续学习和实践是掌握MySQL的关键,建议通过实际项目不断深化对这些概念的理解和应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/98766.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/98766.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/98766.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

4、幽络源微服务项目实战:后端公共模块创建与引入多租户模块

前言 上节我们将电网巡检系统的前端vue2项目创建、配置&#xff0c;并构建了最基础的多租户界面&#xff0c;本节来继续构建后端的公共模块、多租户模块&#xff0c;并将公共模块引入到多租户模块中。 创建公共模块和多租户模块 在back父工程下创建两个Module&#xff0c;和…

STM32学习路线开启篇:芯片简介与课程简介

编写不易,请多多指教,觉得不错可以关注一下,相互学习 前言 一、课程配套资源 1、面包板 2、面包板专用的跳线 3、面包板的飞线 4、杜邦线 5、STM32F103C8T6最小系统板 6、0.96寸的OLED显示屏模块 7、电位器 8、按钮 9、LED灯 10、STLINK 11、USB转串口(TTL)模块 12、源蜂鸣器模…

图像直方图

图像直方图就是用来统计图像像素值分布的。灰度图分布读取灰度图phone cv2.imread(phone.png, cv2.IMREAD_GRAYSCALE) a phone.ravel() plt.hist(a, bins256) plt.show()如何可以获得当前像素值分布读取各通道的像素值分布img cv2.imread(phone.png) colors (b, g, r) for …

分类别柱状图(Vue3)

效果图&#xff1a;需求&#xff1a;男女年龄段占比<template><div class"go-ClassifyBar01"><v-chartref"vChartRef":option"option"style"width: 100%; height: 800px"></v-chart></div> </templa…

Apache Dubbo学习笔记-使用Dubbo发布、调用服务

Apache Dubbo经常作为一个RPC框架来使用&#xff0c;这篇文章主要介绍使用Dubbo配合注册中心来发布和调用服务。 Apache Dubbo和Spring Boot、JDK的版本对应关系。 Dubbo 分支最新版本JDKSpring Boot组件版本详细说明3.3.x (当前文档)3.3.08, 17, 212.x、3.x详情- 版本变更记录…

Python学习——字典和文件

前面python的学习中我们已经学习了python的函数和列表元组相关的内容&#xff0c;接下来我们来学习剩下的python语法&#xff1a;字典和文件 相关代码已经上传到作者的个人gitee&#xff1a;楼田莉子/Python 学习喜欢请点个赞谢谢 目录 字典 创建字典 查找key 新增/修改元素 …

swiper插件的使用

官方网址&#xff1a;https://www.swiper.com.cn/ 1、点击导航栏&#xff0c;获取Swiper里边的下载Swiper 2、选择要下载的版本【本次案例版本5.4.5】&#xff0c;然后解压缩文件夹&#xff0c;拿到swiper.min.js和swiper.min.css文件&#xff0c;放到项目对应的css文件和js文…

Vue3+JS 组合式 API 实战:从项目痛点到通用 Hook 封装

Vue3 组合式 API 的实战技巧 —— 组合式 API 帮我解决了不少 Options API 难以应对的问题&#xff0c;尤其是在代码复用和复杂组件维护上。一、为什么放弃 Options API&#xff1f;聊聊三年项目里的真实痛点​刚接触 Vue3 时&#xff0c;我曾因 “惯性” 继续用 Options API 写…

把 AI 塞进「电梯按钮」——基于 64 kB 零样本声纹的离线故障预测按钮

标签&#xff1a;零样本声纹、电梯按钮、离线 AI、TinyML、RISC-V、低功耗、GD32V303、故障预警 ---- 1. 背景&#xff1a;为什么按钮要「听声音」&#xff1f; 全国 700 万台电梯&#xff0c;按钮故障率 0.3 %/年&#xff0c;却常出现&#xff1a; • 机械卡滞、触点氧化&…

清华大学联合项目 论文解读 | MoTo赋能双臂机器人:实现零样本移动操作

研究背景 移动操作是机器人领域的核心挑战&#xff0c;它使机器人能够在各种任务和动态日常环境中为人类提供帮助。传统的移动操作方法由于缺乏大规模训练&#xff0c;往往难以在不同任务和环境中实现泛化。而现有操作基础模型虽在固定基座任务中表现出强泛化性&#xff0c;却无…

go webrtc - 2 webrtc重要概念

webrtc是一套音视频传输技术生态&#xff0c;不是一个协议或一个什么东西。3种模式本文基于 SFU 形式阐述&#xff01;重要概念&#xff1a;sfu 服务负责&#xff1a;信令 服务负责&#xff1a;peerConnection&#xff1a;track&#xff1a;房间&#xff1a;虚拟分组概念用户&a…

“下游任务”概念详解:从定义到应用场景

“下游任务”概念详解&#xff1a;从定义到应用场景 一、什么是“下游任务”&#xff1f; 在机器学习&#xff08;尤其是深度学习&#xff09;中&#xff0c;“下游任务”&#xff08;Downstream Task&#xff09;是相对“上游过程”而言的目标任务——可以理解为&#xff1a;我…

视频怎么做成 GIF?用 oCam 一键录制 GIF 动画超简单

GIF 动图因其生动直观、无需点击播放的特点&#xff0c;越来越受欢迎。你是否也曾看到一段有趣的视频&#xff0c;想把它做成 GIF 发给朋友或用在PPT里&#xff1f;其实&#xff0c;将视频片段转换为 GIF 并不需要复杂的视频剪辑技术&#xff0c;使用一款支持直接录制为 GIF 的…

Vue.config.js中的Webpack配置、优化及多页面应用开发

Vue.config.js中的Webpack配置、优化及多页面应用开发 在Vue CLI 3项目中&#xff0c;vue.config.js文件是工程化配置的核心入口&#xff0c;它通过集成Webpack配置、优化策略和多页面开发支持&#xff0c;为项目构建提供高度可定制化的解决方案。本文将从基础配置、性能优化、…

行业学习【电商】:直播电商的去头部化、矩阵号?

声明&#xff1a;以下部分内容含AI生成这两个词是当前直播电商和MCN领域的核心战略&#xff0c;理解了它们就理解了行业正在发生的深刻变化。一、如何理解“去头部化”&#xff1f;“去头部化” 指的是平台或MCN机构有意识地减少对超头部主播&#xff08;如曾经的李佳琦、薇娅&…

【MFC视图和窗口基础:文档/视图的“双胞胎”魔法 + 单文档程序】

大家好&#xff0c;我是你的MFC编程小伙伴&#xff01;学MFC就像探险古墓&#xff1a;到处是神秘的“房间”&#xff08;窗口&#xff09;和“宝藏”&#xff08;数据&#xff09;。今天咱们聊聊核心概念 – 视图、窗口和文档。这些是MFC的“骨架”&#xff0c;懂了它们&#x…

深度学习(六):代价函数的意义

在深度学习的浩瀚世界中&#xff0c;代价函数&#xff08;Cost Function&#xff09;&#xff0c;又称损失函数&#xff08;Loss Function&#xff09;或目标函数&#xff08;Objective Function&#xff09;&#xff0c;扮演着至关重要的角色&#xff0c;它就像一个导航员&…

Kable使用指南:Android BLE开发的现代化解决方案

概述 Kable&#xff08;com.juul.kable:core&#xff09;是一个专为Android蓝牙低功耗&#xff08;BLE&#xff09;开发设计的Kotlin协程友好库。它通过提供简洁的API和响应式编程模式&#xff0c;极大地简化了BLE设备交互的复杂性。本文将详细介绍Kable的使用方法&#xff0c;…

Android图案解锁绘制

使用到的库是Pattern Locker,根据示例进行了修改,把默认样式和自定义样式进行了合并调整。 设置密码 布局 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xm…

Kotlin 协程之 Flow 的理解使用及源码解析

前言 在前面的文章中&#xff0c;我们已经讨论了 Channel 的概念和基本使用以及 Channel 的高阶应用。这篇我们来看日常开发中更常用的Flow。 “冷流” 和 “热流” 的本质 先来梳理一下所谓的 “冷流” 和 “热流”。 核心概念 我们已经知道 Channel 是 “热流”&#xff…