热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

mysql35.join语句怎么优化?

mysql在上一篇文章中,我和你介绍了join语句的两种算法,分别是IndexNested-LoopJoin(NLJ)和BlockNested-Loo

mysql

在上一篇文章中,我和你介绍了 join 语句的两种算法,分别是 Index Nested-Loop Join(NLJ) 和 Block Nested-Loop Join(BNL)。

我们发现在使用 NLJ 算法的时候,其实效果还是不错的,比通过应用层拆分成多个语句然后再拼接查询结果更方便,而且性能也不会差。

但是,BNL 算法在大表 join 的时候性能就差多了,比较次数等于两个表参与 join 的行数的乘积,很消耗 CPU 资源。

当然了,这两个算法都还有继续优化的空间,我们今天就来聊聊这个话题。

为了便于分析,我还是创建两个表 t1、t2 来和你展开今天的问题。


create table t1(id int primary key, a int, b int, index(a));
create table t2 like t1;
drop procedure idata;
delimiter ;;
create procedure idata()
begindeclare i int;set i&#61;1;while(i<&#61;1000)doinsert into t1 values(i, 1001-i, i);set i&#61;i&#43;1;end while;set i&#61;1;while(i<&#61;1000000)doinsert into t2 values(i, i, i);set i&#61;i&#43;1;end while;end;;
delimiter ;
call idata();

为了便于后面量化说明&#xff0c;我在表 t1 里&#xff0c;插入了 1000 行数据&#xff0c;每一行的 a&#61;1001-id 的值。也就是说&#xff0c;表 t1 中字段 a 是逆序的。同时&#xff0c;我在表 t2 中插入了 100 万行数据。


Multi-Range Read 优化

介绍 join 语句的优化方案之前&#xff0c;我需要先和你介绍一个知识点&#xff0c;即&#xff1a;Multi-Range Read 优化 (MRR)。这个优化的主要目的是尽量使用顺序读盘。

在第 4 篇文章中&#xff0c;我和你介绍 InnoDB 的索引结构时&#xff0c;提到了“回表”的概念。我们先来回顾一下这个概念。回表是指&#xff0c;InnoDB 在普通索引 a 上查到主键 id 的值后&#xff0c;再根据一个个主键 id 的值到主键索引上去查整行数据的过程。

然后&#xff0c;有同学在留言区问到&#xff0c;回表过程是一行行地查数据&#xff0c;还是批量地查数据&#xff1f;

我们先来看看这个问题。假设&#xff0c;我执行这个语句&#xff1a;

select * from t1 where a>&#61;1 and a<&#61;100;

主键索引是一棵 B&#43; 树&#xff0c;在这棵树上&#xff0c;每次只能根据一个主键 id 查到一行数据。因此&#xff0c;回表肯定是一行行搜索主键索引的&#xff0c;基本流程如图 1 所示。

在这里插入图片描述
如果随着 a 的值递增顺序查询的话&#xff0c;id 的值就变成随机的&#xff0c;那么就会出现随机访问&#xff0c;性能相对较差。虽然“按行查”这个机制不能改&#xff0c;但是调整查询的顺序&#xff0c;还是能够加速的。

因为大多数的数据都是按照主键递增顺序插入得到的&#xff0c;所以我们可以认为&#xff0c;如果按照主键的递增顺序查询的话&#xff0c;对磁盘的读比较接近顺序读&#xff0c;能够提升读性能。

这&#xff0c;就是 MRR 优化的设计思路。此时&#xff0c;语句的执行流程变成了这样&#xff1a;


  1. 根据索引 a&#xff0c;定位到满足条件的记录&#xff0c;将 id 值放入 read_rnd_buffer 中 ;
  2. 将 read_rnd_buffer 中的 id 进行递增排序&#xff1b;
  3. 排序后的 id 数组&#xff0c;依次到主键 id 索引中查记录&#xff0c;并作为结果返回。

这里&#xff0c;read_rnd_buffer 的大小是由 read_rnd_buffer_size 参数控制的。如果步骤 1 中&#xff0c;read_rnd_buffer 放满了&#xff0c;就会先执行完步骤 2 和 3&#xff0c;然后清空 read_rnd_buffer。之后继续找索引 a 的下个记录&#xff0c;并继续循环。

另外需要说明的是&#xff0c;如果你想要稳定地使用 MRR 优化的话&#xff0c;需要设置set optimizer_switch&#61;“mrr_cost_based&#61;off”。&#xff08;官方文档的说法&#xff0c;是现在的优化器策略&#xff0c;判断消耗的时候&#xff0c;会更倾向于不使用 MRR&#xff0c;把 mrr_cost_based 设置为 off&#xff0c;就是固定使用 MRR 了。&#xff09;

下面两幅图就是使用了 MRR 优化后的执行流程和 explain 结果。

在这里插入图片描述
在这里插入图片描述
从图 3 的 explain 结果中&#xff0c;我们可以看到 Extra 字段多了 Using MRR&#xff0c;表示的是用上了 MRR 优化。而且&#xff0c;由于我们在 read_rnd_buffer 中按照 id 做了排序&#xff0c;所以最后得到的结果集也是按照主键 id 递增顺序的&#xff0c;也就是与图 1 结果集中行的顺序相反。

到这里&#xff0c;我们小结一下。

MRR 能够提升性能的核心在于&#xff0c;这条查询语句在索引 a 上做的是一个范围查询&#xff08;也就是说&#xff0c;这是一个多值查询&#xff09;&#xff0c;可以得到足够多的主键 id。这样通过排序以后&#xff0c;再去主键索引查数据&#xff0c;才能体现出“顺序性”的优势。


Batched Key Access

理解了 MRR 性能提升的原理&#xff0c;我们就能理解 MySQL 在 5.6 版本后开始引入的 Batched Key Access(BKA) 算法了。这个 BKA 算法&#xff0c;其实就是对 NLJ 算法的优化。

我们再来看看上一篇文章中用到的 NLJ 算法的流程图&#xff1a;
在这里插入图片描述
NLJ 算法执行的逻辑是&#xff1a;从驱动表 t1&#xff0c;一行行地取出 a 的值&#xff0c;再到被驱动表 t2 去做 join。也就是说&#xff0c;对于表 t2 来说&#xff0c;每次都是匹配一个值。这时&#xff0c;MRR 的优势就用不上了。

那怎么才能一次性地多传些值给表 t2 呢&#xff1f;方法就是&#xff0c;从表 t1 里一次性地多拿些行出来&#xff0c;一起传给表 t2。

既然如此&#xff0c;我们就把表 t1 的数据取出来一部分&#xff0c;先放到一个临时内存。这个临时内存不是别人&#xff0c;就是 join_buffer。

通过上一篇文章&#xff0c;我们知道 join_buffer 在 BNL 算法里的作用&#xff0c;是暂存驱动表的数据。但是在 NLJ 算法里并没有用。那么&#xff0c;我们刚好就可以复用 join_buffer 到 BKA 算法中。

如图 5 所示&#xff0c;是上面的 NLJ 算法优化后的 BKA 算法的流程。
在这里插入图片描述
图中&#xff0c;我在 join_buffer 中放入的数据是 P1~P100&#xff0c;表示的是只会取查询需要的字段。当然&#xff0c;如果 join buffer 放不下 P1~P100 的所有数据&#xff0c;就会把这 100 行数据分成多段执行上图的流程。

那么&#xff0c;这个 BKA 算法到底要怎么启用呢&#xff1f;

如果要使用 BKA 优化算法的话&#xff0c;你需要在执行 SQL 语句之前&#xff0c;先设置

set optimizer_switch&#61;&#39;mrr&#61;on,mrr_cost_based&#61;off,batched_key_access&#61;on&#39;;

其中&#xff0c;前两个参数的作用是要启用 MRR。这么做的原因是&#xff0c;BKA 算法的优化要依赖于 MRR。


BNL 算法的性能问题

说完了 NLJ 算法的优化&#xff0c;我们再来看 BNL 算法的优化。

我在上一篇文章末尾&#xff0c;给你留下的思考题是&#xff0c;使用 Block Nested-Loop Join(BNL) 算法时&#xff0c;可能会对被驱动表做多次扫描。如果这个被驱动表是一个大的冷数据表&#xff0c;除了会导致 IO 压力大以外&#xff0c;还会对系统有什么影响呢&#xff1f;

在第 33 篇文章中&#xff0c;我们说到 InnoDB 的 LRU 算法的时候提到&#xff0c;由于 InnoDB 对 Bufffer Pool 的 LRU 算法做了优化&#xff0c;即&#xff1a;第一次从磁盘读入内存的数据页&#xff0c;会先放在 old 区域。如果 1 秒之后这个数据页不再被访问了&#xff0c;就不会被移动到 LRU 链表头部&#xff0c;这样对 Buffer Pool 的命中率影响就不大。

但是&#xff0c;如果一个使用 BNL 算法的 join 语句&#xff0c;多次扫描一个冷表&#xff0c;而且这个语句执行时间超过 1 秒&#xff0c;就会在再次扫描冷表的时候&#xff0c;把冷表的数据页移到 LRU 链表头部。

这种情况对应的&#xff0c;是冷表的数据量小于整个 Buffer Pool 的 3/8&#xff0c;能够完全放入 old 区域的情况。

如果这个冷表很大&#xff0c;就会出现另外一种情况&#xff1a;业务正常访问的数据页&#xff0c;没有机会进入 young 区域。

由于优化机制的存在&#xff0c;一个正常访问的数据页&#xff0c;要进入 young 区域&#xff0c;需要隔 1 秒后再次被访问到。但是&#xff0c;由于我们的 join 语句在循环读磁盘和淘汰内存页&#xff0c;进入 old 区域的数据页&#xff0c;很可能在 1 秒之内就被淘汰了。这样&#xff0c;就会导致这个 MySQL 实例的 Buffer Pool 在这段时间内&#xff0c;young 区域的数据页没有被合理地淘汰。

也就是说&#xff0c;这两种情况都会影响 Buffer Pool 的正常运作。

大表 join 操作虽然对 IO 有影响&#xff0c;但是在语句执行结束后&#xff0c;对 IO 的影响也就结束了。但是&#xff0c;对 Buffer Pool 的影响就是持续性的&#xff0c;需要依靠后续的查询请求慢慢恢复内存命中率。

为了减少这种影响&#xff0c;你可以考虑增大 join_buffer_size 的值&#xff0c;减少对被驱动表的扫描次数。

也就是说&#xff0c;BNL 算法对系统的影响主要包括三个方面:


  1. 可能会多次扫描被驱动表&#xff0c;占用磁盘 IO 资源&#xff1b;
  2. 判断 join 条件需要执行 M*N 次对比&#xff08;M、N 分别是两张表的行数&#xff09;&#xff0c;如果是大表就会占用非常多的 CPU 资源&#xff1b;
  3. 可能会导致 Buffer Pool 的热数据被淘汰&#xff0c;影响内存命中率。

我们执行语句之前&#xff0c;需要通过理论分析和查看 explain 结果的方式&#xff0c;确认是否要使用 BNL 算法。如果确认优化器会使用 BNL 算法&#xff0c;就需要做优化。优化的常见做法是&#xff0c;给被驱动表的 join 字段加上索引&#xff0c;把 BNL 算法转成 BKA 算法。

接下来&#xff0c;我们就具体看看&#xff0c;这个优化怎么做&#xff1f;


BNL 转 BKA

一些情况下&#xff0c;我们可以直接在被驱动表上建索引&#xff0c;这时就可以直接转成 BKA 算法了。

但是&#xff0c;有时候你确实会碰到一些不适合在被驱动表上建索引的情况。比如下面这个语句&#xff1a;

select * from t1 join t2 on (t1.b&#61;t2.b) where t2.b>&#61;1 and t2.b<&#61;2000;

我们在文章开始的时候&#xff0c;在表 t2 中插入了 100 万行数据&#xff0c;但是经过 where 条件过滤后&#xff0c;需要参与 join 的只有 2000 行数据。如果这条语句同时是一个低频的 SQL 语句&#xff0c;那么再为这个语句在表 t2 的字段 b 上创建一个索引就很浪费了。

但是&#xff0c;如果使用 BNL 算法来 join 的话&#xff0c;这个语句的执行流程是这样的&#xff1a;


  1. 把表 t1 的所有字段取出来&#xff0c;存入 join_buffer 中。这个表只有 1000 行&#xff0c;join_buffer_size 默认值是 256k&#xff0c;可以完全存入。
  2. 扫描表 t2&#xff0c;取出每一行数据跟 join_buffer 中的数据进行对比&#xff0c;
    • 如果不满足 t1.b&#61;t2.b&#xff0c;则跳过&#xff1b;
    • 如果满足 t1.b&#61;t2.b, 再判断其他条件&#xff0c;也就是是否满足 t2.b 处于[1,2000]的条件&#xff0c;如果是&#xff0c;就作为结果集的一部分返回&#xff0c;否则跳过。

我在上一篇文章中说过&#xff0c;对于表 t2 的每一行&#xff0c;判断 join 是否满足的时候&#xff0c;都需要遍历 join_buffer 中的所有行。因此判断等值条件的次数是 1000*100 万 &#61;10 亿次&#xff0c;这个判断的工作量很大。

在这里插入图片描述
在这里插入图片描述
可以看到&#xff0c;explain 结果里 Extra 字段显示使用了 BNL 算法。在我的测试环境里&#xff0c;这条语句需要执行 1 分 11 秒。

在表 t2 的字段 b 上创建索引会浪费资源&#xff0c;但是不创建索引的话这个语句的等值条件要判断 10 亿次&#xff0c;想想也是浪费。那么&#xff0c;有没有两全其美的办法呢&#xff1f;

这时候&#xff0c;我们可以考虑使用临时表。使用临时表的大致思路是&#xff1a;


  1. 把表 t2 中满足条件的数据放在临时表 tmp_t 中&#xff1b;
  2. 为了让 join 使用 BKA 算法&#xff0c;给临时表 tmp_t 的字段 b 加上索引&#xff1b;
  3. 让表 t1 和 tmp_t 做 join 操作。

此时&#xff0c;对应的 SQL 语句的写法如下&#xff1a;


create temporary table temp_t(id int primary key, a int, b int, index(b))engine&#61;innodb;
insert into temp_t select * from t2 where b>&#61;1 and b<&#61;2000;
select * from t1 join temp_t on (t1.b&#61;temp_t.b);

图 8 就是这个语句序列的执行效果。

在这里插入图片描述
可以看到&#xff0c;整个过程 3 个语句执行时间的总和还不到 1 秒&#xff0c;相比于前面的 1 分 11 秒&#xff0c;性能得到了大幅提升。接下来&#xff0c;我们一起看一下这个过程的消耗&#xff1a;


  1. 执行 insert 语句构造 temp_t 表并插入数据的过程中&#xff0c;对表 t2 做了全表扫描&#xff0c;这里扫描行数是 100 万。
  2. 之后的 join 语句&#xff0c;扫描表 t1&#xff0c;这里的扫描行数是 1000&#xff1b;join 比较过程中&#xff0c;做了 1000 次带索引的查询。相比于优化前的 join 语句需要做 10 亿次条件判断来说&#xff0c;这个优化效果还是很明显的。

总体来看&#xff0c;不论是在原表上加索引&#xff0c;还是用有索引的临时表&#xff0c;我们的思路都是让 join 语句能够用上被驱动表上的索引&#xff0c;来触发 BKA 算法&#xff0c;提升查询性能。


扩展 -hash join

看到这里你可能发现了&#xff0c;其实上面计算 10 亿次那个操作&#xff0c;看上去有点儿傻。如果 join_buffer 里面维护的不是一个无序数组&#xff0c;而是一个哈希表的话&#xff0c;那么就不是 10 亿次判断&#xff0c;而是 100 万次 hash 查找。这样的话&#xff0c;整条语句的执行速度就快多了吧&#xff1f;

确实如此。

这&#xff0c;也正是 MySQL 的优化器和执行器一直被诟病的一个原因&#xff1a;不支持哈希 join。并且&#xff0c;MySQL 官方的 roadmap&#xff0c;也是迟迟没有把这个优化排上议程。

实际上&#xff0c;这个优化思路&#xff0c;我们可以自己实现在业务端。实现流程大致如下&#xff1a;


  1. select * from t1;取得表 t1 的全部 1000 行数据&#xff0c;在业务端存入一个 hash 结构&#xff0c;比如 C&#43;&#43; 里的 set、PHP 的数组这样的数据结构。
  2. select * from t2 where b>&#61;1 and b<&#61;2000; 获取表 t2 中满足条件的 2000 行数据。
  3. 把这 2000 行数据&#xff0c;一行一行地取到业务端&#xff0c;到 hash 结构的数据表中寻找匹配的数据。满足匹配的条件的这行数据&#xff0c;就作为结果集的一行。

理论上&#xff0c;这个过程会比临时表方案的执行速度还要快一些。如果你感兴趣的话&#xff0c;可以自己验证一下。


小结

今天&#xff0c;我和你分享了 Index Nested-Loop Join&#xff08;NLJ&#xff09;和 Block Nested-Loop Join&#xff08;BNL&#xff09;的优化方法。

在这些优化方法中&#xff1a;


  1. BKA 优化是 MySQL 已经内置支持的&#xff0c;建议你默认使用&#xff1b;
  2. BNL 算法效率低&#xff0c;建议你都尽量转成 BKA 算法。优化的方向就是给被驱动表的关联字段加上索引&#xff1b;
  3. 基于临时表的改进方案&#xff0c;对于能够提前过滤出小数据的 join 语句来说&#xff0c;效果还是很好的&#xff1b;
  4. MySQL 目前的版本还不支持 hash join&#xff0c;但你可以配合应用端自己模拟出来&#xff0c;理论上效果要好于临时表的方案。

思考题

我们在讲 join 语句的这两篇文章中&#xff0c;都只涉及到了两个表的 join。那么&#xff0c;现在有一个三个表 join 的需求&#xff0c;假设这三个表的表结构如下&#xff1a;


CREATE TABLE &#96;t1&#96; (&#96;id&#96; int(11) NOT NULL,&#96;a&#96; int(11) DEFAULT NULL,&#96;b&#96; int(11) DEFAULT NULL,&#96;c&#96; int(11) DEFAULT NULL,PRIMARY KEY (&#96;id&#96;)
) ENGINE&#61;InnoDB;create table t2 like t1;
create table t3 like t2;
insert into ... //初始化三张表的数据

语句的需求实现如下的 join 逻辑&#xff1a;

select * from t1 join t2 on(t1.a&#61;t2.a) join t3 on (t2.b&#61;t3.b) where t1.c>&#61;X and t2.c>&#61;Y and t3.c>&#61;Z;

现在为了得到最快的执行速度&#xff0c;如果让你来设计表 t1、t2、t3 上的索引&#xff0c;来支持这个 join 语句&#xff0c;你会加哪些索引呢&#xff1f;

同时&#xff0c;如果我希望你用 straight_join 来重写这个语句&#xff0c;配合你创建的索引&#xff0c;你就需要安排连接顺序&#xff0c;你主要考虑的因素是什么呢&#xff1f;


t1增加索引c
t2增加组合索引b,c
t3增加组合索引b,c
select * from t1 straight_join t2 on(t1.a&#61;t2.a) straight_join t3 on (t2.b&#61;t3.b) where t1.c>&#61;X and t2.c>&#61;Y and t3.c>&#61;Z;



前提假设&#xff1a;t1.c>&#61;X可以让t1成为小表。同时打开BKA和MRR。
1、t1表加&#xff08;c,a)索引。理由&#xff1a;A、t1.c>&#61;X可以使用索引&#xff1b;B、加上a的联合索引&#xff0c;join buffer里放入的是索引&#xff08;c,a&#xff09;而不是去主键表取整行&#xff0c;用于与表t2的t1.a &#61; t2.a的join查询&#xff0c;不过返回SELECT * 最终还是需要回表。
2、t2表加(a,b,c)索引。理由&#xff1a;A、加上a避免与t1表join查询的BNL&#xff1b;B、理由同【1-B】&#xff1b;C、加上c不用回表判断t2.c>&#61;Y的筛选条件
3、t3表加&#xff08;b,c&#xff09;索引。理由&#xff1a;A、避免与t2表join查询的BNL;C、理由同【2-C】
问题&#xff1a;
1、【1-B】和【2-B】由于select *要返回所有列数据&#xff0c;不敢肯定join buffer里是回表的整行数据还是索引&#xff08;c,a)的数据&#xff0c;需要老师解答一下&#xff1b;不过值得警惕的是&#xff0c;返回的数据列对sql的执行策略有非常大的影响。
2、在有join查询时&#xff0c;被驱动表是先做join连接查询&#xff0c;还是先筛选数据再从筛选后的临时表做join连接&#xff1f;这将影响上述的理由【2-C】和【3-C】
使用straight_join强制指定驱动表&#xff0c;我会改写成这样:select * from t2 STRAIGHT_JOIN t1 on(t1.a&#61;t2.a) STRAIGHT_JOIN t3 on (t2.b&#61;t3.b) where t1.c>&#61;X and t2.c>&#61;Y and t3.c>&#61;Z;
考虑因素包括&#xff1a;
1、驱动表使用过滤条件筛选后的数据量&#xff0c;使其成为小表&#xff0c;上面的改写也是基于t2是小表
2、因为t2是跟t1,t3都有关联查询的&#xff0c;这样的话我猜测对t1,t3的查询是不是可以并行执行&#xff0c;而如果使用t1,t3作为主表的话&#xff0c;是否会先跟t2生成中间表&#xff0c;是个串行的过程&#xff1f;
3、需要给t1加&#xff08;a,c)索引&#xff0c;给t2加&#xff08;c,a,b&#xff09;索引。



推荐阅读
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文由编程笔记小编整理,介绍了PHP中的MySQL函数库及其常用函数,包括mysql_connect、mysql_error、mysql_select_db、mysql_query、mysql_affected_row、mysql_close等。希望对读者有一定的参考价值。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 本文介绍了如何使用python从列表中删除所有的零,并将结果以列表形式输出,同时提供了示例格式。 ... [详细]
  • 如何在php中将mysql查询结果赋值给变量
    本文介绍了在php中将mysql查询结果赋值给变量的方法,包括从mysql表中查询count(学号)并赋值给一个变量,以及如何将sql中查询单条结果赋值给php页面的一个变量。同时还讨论了php调用mysql查询结果到变量的方法,并提供了示例代码。 ... [详细]
  • Day2列表、字典、集合操作详解
    本文详细介绍了列表、字典、集合的操作方法,包括定义列表、访问列表元素、字符串操作、字典操作、集合操作、文件操作、字符编码与转码等内容。内容详实,适合初学者参考。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • Java程序设计第4周学习总结及注释应用的开发笔记
    本文由编程笔记#小编为大家整理,主要介绍了201521123087《Java程序设计》第4周学习总结相关的知识,包括注释的应用和使用类的注释与方法的注释进行注释的方法,并在Eclipse中查看。摘要内容大约为150字,提供了一定的参考价值。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
author-avatar
会满足cy
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有