为什么要分表和分区?

目录

🥶为什么要分表和分区?

🥶分表

🥶Mysql分表分为垂直切分和水平切分

🥶分表的几种方式:

🥶mysql集群

🥶利用merge存储引擎来实现分表  

🥶分区

🥶什么是分区?

🥶分区的分类

🥶分区的优点


 

为什么要分表和分区?

      我们的数据库数据越来越大,随之而来的是单个表中数据太多。以至于查询速度变慢,而且由于表的锁机制导致应用操作也搜到严重影响,出现了数据库性能瓶颈。
        mysql 中有一种机制是表锁定和行锁定,是为了保证数据的完整性。表锁定表示你们都不能对这张表进行操作,必须等我对表操作完才行。行锁定也一样,别的sql 必须等我对这条数据操作完了,才能对这条数据进行操作。当出现这种情况时,我们可以考虑分表或分区。

分表

分表是将一个大表按照一定的规则分解成多张具有独立存储空间的实体表,每个表都对应三个文件, MYD 数据文件,.MYI 索引文件, .frm 表结构文件。这些表可以分布在同一块磁盘上,也可以在不同的机器上。 app 读写的时候根据事先定义好的规则得到对应的表名,然后去操作它。
将单个数据库表进行拆分,拆分成多个数据表,然后用户访问的时候,根据一定的算法(如用 hash 的方式,也可以用求余(取模)的方式),让用户访问不同的表,这样数据分散到多个数据表中,减少了单个数据表的访问压力。提升了数据库访问性能。分表的目的就在于此,减小数据库的负担,缩短查询时间

Mysql分表分为垂直切分和水平切分

垂直切分是指数据表列的拆分,把一张列比较多的表拆分为多张表 通常我们按以下原则进行垂直拆分 : 把不常用的字段单独放在一张表; text blob binary large object ,二进制大对象)等大字段拆分出来放在附表中 ;经常组合查询的列放在一张表中; 垂直拆分更多时候就应该在数据表设计之初就执行的步骤,然后查询的时候用join关键起来即可

分表的几种方式:

mysql集群

它并不是分表,但起到了和分表相同的作用。集群可分担数据库的操作次数,将任务分担到多台数据库上。集群可以读写分离,减少读写压力。从而提升数据库性能。

预先估计会出现大数据量并且访问频繁的表,将其分为若干个表

根据一定的算法(如用 hash 的方式,也可以用求余(取模)的方式)让用户访问不同的表。 例如论坛里面发表帖子的表,时间长了这张表肯定很大,几十万,几百万都有可能。聊天室里面信息表,几十个人在一起一聊一个晚上,时间长了,这张表的数据肯定很大。像这样的情况很多。所以这种能预估出来的大数据量表,我们就事先分出个N 个表,这个 N 是多少,根据实际情况而定。以聊天信息表为例:我们事先建 100 个这样的表, message_00,message_01,message_02..........message_98,message_99.然后根据用户的 ID 来判断这个用户的聊天信息放到哪张表里面,可以用hash 的方式来获得,也可以用求余的方式来获得,方法很多。 或者可以设计每张表容纳的数据量是N条,那么如何判断某张表的数据是否容量已满呢?可以在程序段对于要新增数据的表,在插入前先做统计表记录数量的操作,当<N条数据,就直接插入,当已经到达阀值,可以在程序段新创建数据库表(或者已经事先创建好),再执行插入操作)。

利用merge存储引擎来实现分表  

如果要把已有的大数据量表分开比较痛苦,最痛苦的事就是改代码,因为程序里面的 sql 语句已经写好了,用merge存储引擎来实现分表 , 这种方法比较适合。 merge 分表,分为主表和子表,主表类似于一个壳子,逻辑上封装了子表,实际上数据都是存储在子表中的。 我们可以通过主表插入和查询数据,如果清楚分表规律,也可以直接操作子表。
下面我们来实现一个简单的利用merge 存储引擎来实现分表的演示: 创建一个完整表存储着
所有的成员信息(表名为 member
mysql> drop database IF EXISTS test;
mysql> use test;
create table member(
id bigint auto_increment primary key,
name varchar(20),
sex tinyint not nulldefault '0'
)engine=myisam default charset=utf8 auto_increment=1;

加入点数据:

mysql> insert into member(name,sex) values('tom1',1);
mysql> insert into member(name,sex) select name,sex from member;

 第二条语句多执行几次就有了很多数据

mysql> select * from member;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 1 | tom1  | 1 |
| 2 | tom1  | 1 |
| 3 | tom1  | 1 |
| 4 | tom1  | 1 |
| 5 | tom1  | 1 |
| 6 | tom1  | 1 |
| 7 | tom1  | 1 |
| 8 | tom1  | 1 |
| 9 | tom1  | 1 |
| 10 | tom1 | 1 |
| 11 | tom1 | 1 |
| 12 | tom1 | 1 |
| 13 | tom1 | 1 |
| 14 | tom1 | 1 |
| 15 | tom1 | 1 |
| 16 | tom1 | 1 |
+----+------+-----+

     下面我们进行分表,这里我们把member分两个表tb_member1,tb_member2

mysql> use test;
DROP table IF EXISTS tb_member1;
create table tb_member1(id bigint primary key ,name varchar(20),sex tinyint not null default '0'
)ENGINE=MyISAM DEFAULT CHARSET=utf8 ; 
DROP table IF EXISTS tb_member2;
create table tb_member2(id bigint primary key,name varchar(20),sex tinyint not null default '0'
)ENGINE=MyISAM DEFAULT CHARSET=utf8; 
//创建tb_member2也可以用下面的语句 create table tb_member2 like tb_member1;
创建主表tb_member
DROP table IF EXISTS tb_member;
create table tb_member(
id bigint primary key ,
name varchar(20),
sex tinyint not null default '0'
) ENGINE=MERGE UNION=(tb_member1,tb_member2) INSERT_METHOD=LAST CHARSET=utf8 ;
注:THOD = LAST表示插入到最后的一张表里面。INSERT_METHOD = first表示插入到第一张表里面。 查看一下tb_member表的结构::INSERT_METHOD,此参数INSERT_METHOD = NO 表示该表不能做任何写入操作只作为查询使用,INSERT_ME
mysql> desc tb_member;
mysql> desc tb_member;
+-------+-------------+------+-----+---------+------------------------------------
-----+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+------------------------------------
-----+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| sex | tinyint(4) | NO | | 0 | |
+-------+-------------+------+-----+---------+------------------------------------
------+
3 rows in set (0.00 sec)
注:查看子表与主表的字段定义要一致
接下来,我们把数据分到两个分表中去:
mysql> insert into tb_member1(id,name,sex) select id,name,sex from member where
id%2=0;
mysql> insert into tb_member2(id,name,sex) select id,name,sex from member where
id%2=1;
查看两个子表的数据:
mysql> select * from tb_member1;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 16 | tom1 | 1 |
| 14 | tom1 | 1 |
| 12 | tom1 | 1 |
| 10 | tom1 | 1 |
| 8 | tom1 | 1 |
| 6 | tom1 | 1 |
| 4 | tom1 | 1 |
| 2 | tom1 | 1 |
+----+------+-----+
8 rows in set (0.00 sec)
mysql> select * from tb_member2;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 3 | tom1 | 1 |
| 1 | tom1 | 1 |
| 5 | tom1 | 1 |
| 7 | tom1 | 1 |
| 9 | tom1 | 1 |
| 11 | tom1 | 1 |
| 13 | tom1 | 1 |
| 15 | tom1 | 1 |
+----+------+-----+
8 rows in set (0.00 sec)
查看一下主表的数据:
mysql> select * from tb_member;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 16 | tom1 | 1 |
| 14 | tom1 | 1 |
| 12 | tom1 | 1 |
| 10 | tom1 | 1 |
| 8 | tom1  | 1 |
| 6 | tom1  | 1 |
| 4 | tom1  | 1 |
| 2 | tom1  | 1 |
| 15 | tom1 | 1 |
| 13 | tom1 | 1 |
| 11 | tom1 | 1 |
| 9 | tom1  | 1 |
| 7 | tom1  | 1 |
| 5 | tom1  | 1 |
| 3 | tom1  | 1 |
| 1 | tom1  | 1 |
+----+------+-----+
16 rows in set (0.00 sec)
mysql> select * from tb_member where id=3;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 3 | tom1 | 1    |
+----+------+-----+
1 row in set (0.00 sec)
注意:总表只是一个外壳,存取数据发生在一个一个的子表里面。 注意:每个子表都有自已独立的相关表文 件,而主表只是一个壳,并没有完整的相关表文件
[root@localhost ~]# ls -l /usr/local/mysql/data/test/tb_member*
-rw-r-----. 1 mysql mysql 8614 Sep 15 21:49
/usr/local/mysql/data/test/tb_member1.frm
-rw-r-----. 1 mysql mysql 320 Sep 16 00:02
/usr/local/mysql/data/test/tb_member1.MYD
-rw-r-----. 1 mysql mysql 2048 Sep 16 00:43
/usr/local/mysql/data/test/tb_member1.MYI
-rw-r-----. 1 mysql mysql 8614 Sep 15 21:50
/usr/local/mysql/data/test/tb_member2.frm
-rw-r-----. 1 mysql mysql 180 Sep 16 00:02
/usr/local/mysql/data/test/tb_member2.MYD
-rw-r-----. 1 mysql mysql 2048 Sep 16 00:43
/usr/local/mysql/data/test/tb_member2.MYI
-rw-r-----. 1 mysql mysql 8614 Sep 16 21:12
/usr/local/mysql/data/test/tb_member3.frm
-rw-r-----. 1 mysql mysql 0 Sep 16 21:12
/usr/local/mysql/data/test/tb_member3.MYD
-rw-r-----. 1 mysql mysql 1024 Sep 16 21:12
/usr/local/mysql/data/test/tb_member3.MYI
-rw-r-----. 1 mysql mysql 8614 Sep 16 21:14
/usr/local/mysql/data/test/tb_member.frm
-rw-r-----. 1 mysql mysql 53 Sep 16 21:14
/usr/local/mysql/data/test/tb_member.MRG

分区

什么是分区?

分区可以按照不同的标准进行划分,比如按照范围、列表、哈希或者键值等方式进行分区。每个分区都可以存储部分数据,这样可以减少查询的数据量,从而提高查询的速度

分区的分类

  1. 范围分区(Range Partitioning):按照某个范围的值划分分区,例如按照日期、年龄、价格等范围划分。可以使用VALUES LESS THANVALUES IN来定义分区的范围。

  2. 列表分区(List Partitioning):根据列值匹配来划分分区,可以根据具体的列值来划分,例如按照国家、地区等列表信息进行分区。可以使用VALUES IN来定义分区的列表。

  3. 哈希分区(Hash Partitioning):根据列值的哈希值进行划分分区,确保数据分布均匀,适用于无法使用范围或列表进行明确划分的情况。

  4. 键值分区(Key Partitioning):根据列的哈希值或者键值的哈希值进行划分,类似于哈希分区,但可以指定列作为分区的键。

  5. 子分区(Subpartitioning):在分区内部再进行子分区,可以进行更细粒度的数据划分。子分区可以按照范围、列表、哈希或键值等方式进行划分。

分区的优点

  1. 提高查询性能:分区可以将数据划分为多个逻辑部分,可以仅查询特定分区中的数据,而不需要扫描整个表。这样可以大大提高查询的速度,特别是对于大型表和频繁查询的情况。

  2. 提高维护效率:通过分区,可以对某个特定分区执行维护操作,如备份、恢复和优化等。这样可以减少对整个表的操作,提高维护的效率,减少维护所需的时间和资源。

  3. 改善数据管理:通过分区,可以将不同的数据存储在不同的分区中,从而更好地管理数据。可以根据数据的特性、生命周期等因素设置不同的存储策略,比如将热数据和冷数据分开存储,根据需要进行灵活的存储扩展。

  4. 增强容错性:分区可以将数据分布在不同的物理位置上,可以在出现故障或灾难恢复时提供更好的容错性。例如,在某个存储设备发生故障时,其他分区的数据仍然可用,可以快速进行故障切换或恢复。

  5. 简化数据删除:通过分区,可以更容易地删除或清理过期的数据。可以只清理某些分区中的数据,而不需要对整个表进行操作,从而简化了数据清理的过程。

下面我们先演示一个按照范围 (range) 方式的表分区 创建 range 分区表
mysql> use test2;
mysql> create table if not exist user (-> id int not null auto_increment,-> name varchar(30) not null default '',-> sex int(1) not null default '0',-> primary key(id)-> )default charset=utf8 auto_increment=1-> partition by range(id) (-> partition p0 values less than (3),-> partition p1 values less than (6),-> partition p2 values less than (9),-> partition p3 values less than (12),-> partition p4 values less than maxvalue
-> );
插入些数据
mysql> insert into test2.user(name,sex)values ('tom1','0');
mysql> insert into test2.user(name,sex)values ('tom2','1');
mysql> insert into test2.user(name,sex)values ('tom3','1');
mysql> insert into test2.user(name,sex)values ('tom4','0');
mysql> insert into test2.user(name,sex)values ('tom5','0');
mysql> insert into test2.user(name,sex)values ('tom6','1');
mysql> insert into test2.user(name,sex)values ('tom7','1');
mysql> insert into test2.user(name,sex)values ('tom8','1');
mysql> insert into test2.user(name,sex)values ('tom9','1');
mysql> insert into test2.user(name,sex)values ('tom10','1');
mysql> insert into test2.user(name,sex)values ('tom11','1');
mysql> insert into test2.user(name,sex)values ('tom12','1');
mysql> insert into test2.user(name,sex)values ('tom13','1');
mysql> insert into test2.user(name,sex)values ('tom14','1');
到存放数据库表文件的地方看一下
[root@localhost ~]# ls -l /usr/local/mysql/data/test2/user*
-rw-r-----. 1 mysql mysql 8614 Sep 16 21:46 /usr/local/mysql/data/test2/user.frm
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:48
/usr/local/mysql/data/test2/user#P#p0.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:48
/usr/local/mysql/data/test2/user#P#p1.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:49
/usr/local/mysql/data/test2/user#P#p2.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:49
/usr/local/mysql/data/test2/user#P#p3.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:49
/usr/local/mysql/data/test2/user#P#p4.ibd
mysql> select count(id) as count from user;// 给查询结果指定列名。
+-------+
| count |
+-------+
| 14    |
+-------+
1 row in set (0.00 sec)
information_schema 系统库中的 partitions 表中查看分区信息
mysql> select * from information_schema.partitions where table_schema='test2' and
table_name='user'\G;
从某个分区中查询数据
mysql> select * from test2.user partition(p0);
新增分区
mysql> alter table test2.user add partition (partition partionname values less
than (n));
删除分区 当删除了一个分区,也同时删除了该分区中所有的数据
ALTER TABLE test2.user DROP PARTITION p3;
分区的合并 下面的 SQL ,将 p1 – p3 合并为 2 个分区 p01– p02
mysql> alter table test2.user-> reorganize partition p1,p2,p3 into-> (partition p01 values less than (8),-> partition p02 values less than (12)-> );
[root@localhost ~]# ls -l /usr/local/mysql/data/test2/user*
-rw-r-----. 1 mysql mysql 8614 Sep 16 22:06 /usr/local/mysql/data/test2/user.frm
-rw-r-----. 1 mysql mysql 98304 Sep 16 22:06
/usr/local/mysql/data/test2/user#P#p01.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 22:06
/usr/local/mysql/data/test2/user#P#p02.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:48
/usr/local/mysql/data/test2/user#P#p0.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:49
/usr/local/mysql/data/test2/user#P#p4.ibd
mysql> select * from test2.user partition(p01);
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 3 | tom3 | 1 |
| 4 | tom4 | 0 |
| 5 | tom5 | 0 |
| 6 | tom6 | 1 |
| 7 | tom7 | 1 |
+----+------+-----+
5 rows in set (0.00 sec)
未分区表和分区表性能测试 创建一个未分区的表
mysql> create table test2.tab1(c1 int,c2 varchar(30),c3 date);
创建分区表,按日期的年份拆分
mysql> CREATE TABLE test2.tab2 ( c1 int, c2 varchar(30) , c3 date ) 
PARTITION BY RANGE (year(c3)) (PARTITION p0 VALUES LESS THAN (1995), 
PARTITION p1 VALUES LESS THAN (1996) , PARTITION p2 VALUES LESS THAN (1997) ,
PARTITION p3 VALUES LESS THAN (1998) , PARTITION p4 VALUES LESS THAN (1999) , 
PARTITION p5 VALUES LESS THAN (2000) , PARTITION p6 VALUES LESS THAN (2001) , 
PARTITION p7 VALUES LESS THAN (2002) , PARTITION p8 VALUES LESS THAN (2003) , 
PARTITION p9 VALUES LESS THAN (2004) , PARTITION p10 VALUES LESS THAN (2010), 
PARTITION p11 VALUES LESS THAN MAXVALUE );
注意:最后一行,考虑到可能的最大值 通过存储过程插入100万条测试数据 创建存储过程:
mysql> delimiter $$//指定存储过程结束符
mysql>CREATE PROCEDURE load_part_tab() begin declare v int default 0; while v < 2000000 do insert into test2.tab1 values (v,'testing partitions',adddate('1995-01-01',
(rand(v)*36520) mod 3652)); set v = v + 1; end while; end $$
注:RAND()函数在0和1之间产生一个随机数,如果一个整数参数N被指定,它被用作种子值。每个种子产生的随机数序列是不同的。
执行存储过程 load_part_tab test2.tab1 表插入数据
mysql> delimiter ; // 注意有空格
mysql> call load_part_tab();
向test2.tab2表中插入数据
mysql> insert into test2.tab2 select * from test2.tab1;
测试SQL性能
mysql> select count(*) from test2.tab1 where c3 > '1995-01-01' and c3 < '1995-12-
31';
+----------+
| count(*) |
+----------+
| 219642 |
+----------+
1 row in set (0.84 sec)
mysql> select count(*) from test2.tab2 where c3 > '1995-01-01' and c3 < '1995-12-
31'; 
+----------+
| count(*) |
+----------+
| 219642 |
+----------+
1 row in set (0.09 sec)
结果表明分区表比未分区表的执行时间少很多。 通过 explain 语句来分析执行情况
mysql> flush tables;
mysql> explain select count(*) from test2.tab1 where c3 > '1995-01-01' and c3 <
'1995-12-31'\G;
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: tab1partitions: NULLtype: ALL
possible_keys: NULLkey: NULLkey_len: NULLref: NULL
rows: 2001552filtered: 11.11Extra: Using where
1 row in set, 1 warning (0.00 sec)
mysql> explain select count(*) from test2.tab2 where c3 > '1995-01-01' and c3 <
'1995-12-31'\G;
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: tab2partitions: p1type: ALL
possible_keys: NULLkey: NULLkey_len: NULLref: NULL
rows: 220206filtered: 11.11Extra: Using where
1 row in set, 1 warning (0.00 sec)
explain 语句显示了 SQL 查询要处理的记录数目可以看出分区表比未分区表的明显扫描的记录要少很多。 创建索引后情况测试
mysql> create index idx_of_c3 on test2.tab1(c3);
Query OK, 0 rows affected (5.07 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> create index idx_of_c3 on test2.tab2(c3);
Query OK, 0 rows affected (4.87 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> flush tables;
mysql> select count(*) from test2.tab1 where c3 > '1996-01-01' and c3 < '1996-12-
31';
+----------+
| count(*) |
+----------+
| 220264   |
+----------+
1 row in set (0.12 sec)
重启 mysqld 服务
mysql> select count(*) from test2.tab2 where c3 > '1996-01-01' and c3 < '1996-12-
31';
+----------+
| count(*) |
+----------+
| 220264   |
+----------+
1 row in set (0.11 sec) 
:

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

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

相关文章

OpenCv之图像轮廓

目录 一、图像轮廓定义 二、绘制轮廓 三、计算轮廓面积与周长 一、图像轮廓定义 图像轮廓是具有相同颜色或灰度的连续带你的曲线.轮廓在形状分析和物体的检测和识别中很有用 轮廓的作用: 用于图形分析物体的识别与检测 注意点: 为了检测的准确性&#xff0c;需要先对图像…

【Apifox】国产测试工具雄起

在开发过程中&#xff0c;我们总是避免不了进行接口的测试&#xff0c; 而相比手动敲测试代码&#xff0c;使用测试工具进行测试更为便捷&#xff0c;高效 今天发现了一个非常好用的接口测试工具Apifox 相比于Postman&#xff0c;他还拥有一个非常nb的功能&#xff0c; 在接…

springboot整合feign实现RPC调用,并通过Hystrix实现降级

目录 一、服务提供者 二、服务消费者 三、测试效果 四、开启Hystrix实现降级功能 feign/openfeign和dubbo是常用的微服务RPC框架&#xff0c;由于feigin内部已经集成ribbon&#xff0c;自带了负载均衡的功能&#xff0c;当有多个同名的服务注册到注册中心时&#xff0c;会根…

【已解决】哪些软件可以解压RAR文件?

RAR文件是我们日常生活及工作中经常用的压缩文件&#xff0c;文件压缩后可以更方便储存或者传输&#xff0c;后续要使用的时候再进行解压。 那RAR文件如何解压呢&#xff1f;哪些软件可以用来解压RAR文件&#xff1f;在这一方面还是小白的小伙伴可以来看看下面的分享。 解压任…

Cadence Allegro PCB设计88问解析(三十一) 之 Allegro 中 打印(Plot)设置

一个学习信号完整性仿真的layout工程师 在PCB进行投板时&#xff0c;往往会打印一下装备层(Assembly)&#xff0c;给贴片&#xff0c;用于核对器件的信息等。下面简单介绍Allegro中打印(Plot)设置。 1. 在Allegro的菜单下选择File命令&#xff0c;点击Plot Setup&#xff0c;会…

【自动驾驶汽车量子群粒子过滤器】用于无人驾驶汽车列车定位的量子粒子滤波研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

SpringMVC学习笔记--下篇

SpringMVC学习笔记 文章目录 SpringMVC学习笔记1、JSON1.1、什么是JSON1.2、JSON 和 JavaScript 对象互转1.3、Controller返回JSON数据1.3.1、使用Jackson工具1.3.1.1、乱码问题的代码优化1.3.1.2、集合测试1.3.1.3、输出时间对象1.3.1.4、抽取为工具类 1.3.2、使用FastJson的工…

CCF真题练习:202209-1如此编码

题目背景 某次测验后&#xff0c;顿顿老师在黑板上留下了一串数字 23333 便飘然而去。凝望着这个神秘数字&#xff0c;小 P 同学不禁陷入了沉思…… 题目描述 已知某次测验包含 n 道单项选择题&#xff0c;其中第 i 题&#xff08;1≤i≤n&#xff09;有 个选项&#xff0c;…

Android ViewGroup onDraw为什么没调用

ViewGroup&#xff0c;它本身并没有任何可画的东西&#xff0c;它是一个透明的控件&#xff0c;因些并不会触发onDraw&#xff0c;但是你现在给LinearLayout设置一个背景色&#xff0c;其实这个背景色不管你设置成什么颜色&#xff0c;系统会认为&#xff0c;这个LinearLayout上…

android APP外包开发的三种方式

开发android APP有三种方式&#xff0c;分别是原生开发、混合开发和无代码开发&#xff0c;原生开发对开发者有一定要求&#xff0c;但用户体验好&#xff1b;混合开发是使用H5开发&#xff0c;对开发者要求相对较低&#xff1b;而无代码开发则是通过操作界面搭建APP&#xff0…

(数组与矩阵) 剑指 Offer 50. 第一个只出现一次的字符 ——【Leetcode每日一题】

❓ 剑指 Offer 50. 第一个只出现一次的字符 难度&#xff1a;简单 在字符串 s 中找出第一个只出现一次的字符。如果没有&#xff0c;返回一个单空格。 s 只包含小写字母。 示例 1: 输入&#xff1a;s “abaccdeff” 输出&#xff1a;‘b’ 示例 2: 输入&#xff1a;s “”…

Linux之vi命令

vi编辑器 vim/vi是Unix / Linux上最常用的文本编辑器而且功能非常强大。 只有命令&#xff0c;没有菜单。 建议使用vim命令&#xff0c;如果没有这个命令可以使用 yum install -y vim 进行安装 命令模式&#xff1a;又称一般模式 编辑模式&#xff1a;又称底行模式&#xff0c;…