mysql联合索引如何创建

例如:

CREATE TABLE `test` (‘aaa’ varchar(16) NOT NULL default ”, ‘bbb’ varchar(16) NOT NULL default ”, ‘ccc’ int(11) UNSIGNED NOT NULL default 0, KEY `sindex` (`aaa`,`bbb`,`ccc`) ) ENGINE=MyISAM COMMENT=”;

这样就在 aaa、bbb、ccc 3列上建立联合索引了。

如果表已经建好了,那么就在phpmyadmin里面执行:
alert table test add INDEX `sindex` (`aaa`,`bbb`,`ccc`)

就可以在这3列上建立联合索引

mysql大数据高并发处理

转载地址:https://www.cnblogs.com/jakentec/p/4441081.html

一、数据库结构的设计

如果不能设计一个合理的数据库模型,不仅会增加客户端和服务器段程序的编程和维护的难度,而且将会影响系统实际运行的性能。所以,在一个系统开始实施之前,完备的数据库模型的设计是必须的。

在一个系统分析、设计阶段,因为数据量较小,负荷较低。我们往往只注意到功能的实现,而很难注意到性能的薄弱之处,等到系统投入实际运行一段时间后,才发现系统的性能在降低,这时再来考虑提高系统性能则要花费更多的人力物力,而整个系统也不可避免的形成了一个打补丁工程。

所以在考虑整个系统的流程的时候,我们必须要考虑,在高并发大数据量的访问情况下,我们的系统会不会出现极端的情况。(例如:对外统计系统在7月16日出现的数据异常的情况,并发大数据量的的访问造成,数据库的响应时间不能跟上数据刷新的速度造成。具体情况是:在日期临界时(00:00:00),判断数据库中是否有当前日期的记录,没有则插入一条当前日期的记录。在低并发访问的情况下,不会发生问题,但是当日期临界时的访问量相当大的时候,在做这一判断的时候,会出现多次条件成立,则数据库里会被插入多条当前日期的记录,从而造成数据错误。),数据库的模型确定下来之后,我们有必要做一个系统内数据流向图,分析可能出现的瓶颈。

为了保证数据库的一致性和完整性,在逻辑设计的时候往往会设计过多的表间关联,尽可能的降低数据的冗余。(例如用户表的地区,我们可以把地区另外存放到一个地区表中)如果数据冗余低,数据的完整性容易得到保证,提高了数据吞吐速度,保证了数据的完整性,清楚地表达数据元素之间的关系。而对于多表之间的关联查询(尤其是大数据表)时,其性能将会降低,同时也提高了客户端程序的编程难度,因此,物理设计需折衷考虑,根据业务规则,确定对关联表的数据量大小、数据项的访问频度,对此类数据表频繁的关联查询应适当提高数据冗余设计但增加了表间连接查询的操作,也使得程序的变得复杂,为了提高系统的响应时间,合理的数据冗余也是必要的。设计人员在设计阶段应根据系统操作的类型、频度加以均衡考虑。

另外,最好不要用自增属性字段作为主键与子表关联。不便于系统的迁移和数据恢复。对外统计系统映射关系丢失(******************)。

原来的表格必须可以通过由它分离出去的表格重新构建。使用这个规定的好处是,你可以确保不会在分离的表格中引入多余的列,所有你创建的表格结构都与它们的实际需要一样大。应用这条规定是一个好习惯,不过除非你要处理一个非常大型的数据,否则你将不需要用到它。(例如一个通行证系统,我可以将USERID,USERNAME,USERPASSWORD,单独出来作个表,再把USERID作为其他表的外键)

表的设计具体注意的问题:

1、数据行的长度不要超过8020字节,如果超过这个长度的话在物理页中这条数据会占用两行从而造成存储碎片,降低查询效率。

2、能够用数字类型的字段尽量选择数字类型而不用字符串类型的(电话号码),这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接回逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

3、对于不可变字符类型char和可变字符类型varchar 都是8000字节,char查询快,但是耗存储空间,varchar查询相对慢一些但是节省存储空间。在设计字段的时候可以灵活选择,例如用户名、密码等长度变化不大的字段可以选择CHAR,对于评论等长度变化大的字段可以选择VARCHAR。

4、字段的长度在最大限度的满足可能的需要的前提下,应该尽可能的设得短一些,这样可以提高查询的效率,而且在建立索引的时候也可以减少资源的消耗。

二、查询的优化

保证在实现功能的基础上,尽量减少对数据库的访问次数(可以用缓存保存查询结果,减少查询次数);通过搜索参数,尽量减少对表的访问行数,最小化结果集,从而减轻网络负担;能够分开的操作尽量分开处理,提高每次的响应速度;在数据窗口使用SQL时,尽量把使用的索引放在选择的首列;算法的结构尽量简单;在查询时,不要过多地使用通配符如SELECT * FROM T1语句,要用到几列就选择几列如:SELECTCOL1,COL2 FROM T1;在可能的情况下尽量限制尽量结果集行数如:SELECT TOP 300 COL1,COL2,COL3 FROM T1,因为某些情况下用户是不需要那么多的数据的。

在没有建索引的情况下,数据库查找某一条数据,就必须进行全表扫描了,对所有数据进行一次遍历,查找出符合条件的记录。在数据量比较小的情况下,也许看不出明显的差别,但是当数据量大的情况下,这种情况就是极为糟糕的了。

SQL语句在SQL SERVER中是如何执行的,他们担心自己所写的SQL语句会被SQL SERVER误解。比如:

select * from table1 where name=’zhangsan’ and tID > 10000 和执行:

select * from table1 where tID > 10000 and name=’zhangsan’

一些人不知道以上两条语句的执行效率是否一样,因为如果简单的从语句先后上看,这两个语句的确是不一样,如果tID是一个聚合索引,那么后一句仅仅从表的10000条以后的记录中查找就行了;而前一句则要先从全表中查找看有几个name=’zhangsan’的,而后再根据限制条件条件tID>10000来提出查询结果。

事实上,这样的担心是不必要的。SQL SERVER中有一个“查询分析优化器”,它可以计算出where子句中的搜索条件并确定哪个索引能缩小表扫描的搜索空间,也就是说,它能实现自动优化。虽然查询优化器可以根据where子句自动的进行查询优化,但有时查询优化器就会不按照您的本意进行快速查询。

在查询分析阶段,查询优化器查看查询的每个阶段并决定限制需要扫描的数据量是否有用。如果一个阶段可以被用作一个扫描参数(SARG),那么就称之为可优化的,并且可以利用索引快速获得所需数据。

SARG的定义:用于限制搜索的一个操作,因为它通常是指一个特定的匹配,一个值的范围内的匹配或者两个以上条件的AND连接。形式如下:

列名 操作符 <常数 或 变量> 或 <常数 或 变量> 操作符 列名

列名可以出现在操作符的一边,而常数或变量出现在操作符的另一边。如:

Name=’张三’

价格>5000

5000<价格

Name=’张三’ and 价格>5000

如果一个表达式不能满足SARG的形式,那它就无法限制搜索的范围了,也就是SQL SERVER必须对每一行都判断它是否满足WHERE子句中的所有条件。所以一个索引对于不满足SARG形式的表达式来说是无用的。

所以,优化查询最重要的就是,尽量使语句符合查询优化器的规则避免全表扫描而使用索引查询。

具体要注意的:

1.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num is null

可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:

select id from t where num=0

2.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。优化器将无法通过索引来确定将要命中的行数,因此需要搜索该表的所有行。

3.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num=10 or num=20

可以这样查询:

select id from t where num=10

union all

select id from t where num=20

4.in 和 not in 也要慎用,因为IN会使系统无法使用索引,而只能直接搜索表中的数据。如:

select id from t where num in(1,2,3)

对于连续的数值,能用 between 就不要用 in 了:

select id from t where num between 1 and 3

5.尽量避免在索引过的字符数据中,使用非打头字母搜索。这也使得引擎无法利用索引。

见如下例子:

SELECT * FROM T1 WHERE NAME LIKE ‘%L%’

SELECT * FROM T1 WHERE SUBSTING(NAME,2,1)=’L’

SELECT * FROM T1 WHERE NAME LIKE ‘L%’

即使NAME字段建有索引,前两个查询依然无法利用索引完成加快操作,引擎不得不对全表所有数据逐条操作来完成任务。而第三个查询能够使用索引来加快操作。

6.必要时强制查询优化器使用某个索引,如在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:

select id from t where num=@num

可以改为强制查询使用索引:

select id from t with(index(索引名)) where num=@num

7.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:

SELECT * FROM T1 WHERE F1/2=100

应改为:

SELECT * FROM T1 WHERE F1=100*2

SELECT * FROM RECORD WHERE SUBSTRING(CARD_NO,1,4)=’5378’

应改为:

SELECT * FROM RECORD WHERE CARD_NO LIKE ‘5378%’

SELECT member_number, first_name, last_name FROM members

WHERE DATEDIFF(yy,datofbirth,GETDATE()) > 21

应改为:

SELECT member_number, first_name, last_name FROM members

WHERE dateofbirth < DATEADD(yy,-21,GETDATE())

即:任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等号右边。

8.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where substring(name,1,3)=’abc’–name以abc开头的id

select id from t where datediff(day,createdate,’2005-11-30′)=0–‘2005-11-30’生成的id

应改为:

select id from t where name like ‘abc%’

select id from t where createdate>=’2005-11-30′ and createdate<‘2005-12-1’

9.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

10.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。

11.很多时候用 exists是一个好的选择:

select num from a where num in(select num from b)

用下面的语句替换:

select num from a where exists(select 1 from b where num=a.num)

SELECT SUM(T1.C1)FROM T1 WHERE(

(SELECT COUNT(*)FROM T2 WHERE T2.C2=T1.C2>0)

SELECT SUM(T1.C1) FROM T1WHERE EXISTS(

SELECT * FROM T2 WHERE T2.C2=T1.C2)

两者产生相同的结果,但是后者的效率显然要高于前者。因为后者不会产生大量锁定的表扫描或是索引扫描。

如果你想校验表里是否存在某条纪录,不要用count(*)那样效率很低,而且浪费服务器资源。可以用EXISTS代替。如:

IF (SELECT COUNT(*) FROM table_name WHERE column_name = ‘xxx’)

可以写成:

IF EXISTS (SELECT * FROM table_name WHERE column_name = ‘xxx’)

经常需要写一个T_SQL语句比较一个父结果集和子结果集,从而找到是否存在在父结果集中有而在子结果集中没有的记录,如:

SELECT a.hdr_key FROM hdr_tbl a—- tbl a 表示tbl用别名a代替

WHERE NOT EXISTS (SELECT * FROM dtl_tbl b WHERE a.hdr_key = b.hdr_key)

SELECT a.hdr_key FROM hdr_tbl a

LEFT JOIN dtl_tbl b ON a.hdr_key = b.hdr_key WHERE b.hdr_key IS NULL

SELECT hdr_key FROM hdr_tbl

WHERE hdr_key NOT IN (SELECT hdr_key FROM dtl_tbl)

三种写法都可以得到同样正确的结果,但是效率依次降低。

12.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。

13.避免频繁创建和删除临时表,以减少系统表资源的消耗。

14.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。

15.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

16.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

17.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。

18.尽量避免大事务操作,提高系统并发能力。

19.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

20. 避免使用不兼容的数据类型。例如float和int、char和varchar、binary和varbinary是不兼容的(条件判断时)。数据类型的不兼容可能使优化器无法执行一些本来可以进行的优化操作。例如:

SELECT name FROM employee WHERE salary > 60000

在这条语句中,如salary字段是money型的,则优化器很难对其进行优化,因为60000是个整型数。我们应当在编程时将整型转化成为钱币型,而不要等到运行时转化。

21.充分利用连接条件(条件越多越快),在某种情况下,两个表之间可能不只一个的连接条件,这时在 WHERE 子句中将连接条件完整的写上,有可能大大提高查询速度。

例:

SELECT SUM(A.AMOUNT) FROM ACCOUNT A,CARD B WHERE A.CARD_NO = B.CARD_NO

SELECT SUM(A.AMOUNT) FROM ACCOUNT A,CARD B WHERE A.CARD_NO = B.CARD_NO AND A.ACCOUNT_NO=B.ACCOUNT_NO

第二句将比第一句执行快得多。

22、使用视图加速查询

把表的一个子集进行排序并创建视图,有时能加速查询。它有助于避免多重排序 操作,而且在其他方面还能简化优化器的工作。例如:

SELECT cust.name,rcvbles.balance,……other columns

FROM cust,rcvbles

WHERE cust.customer_id = rcvlbes.customer_id

AND rcvblls.balance>0

AND cust.postcode>“98000”

ORDER BY cust.name

如果这个查询要被执行多次而不止一次,可以把所有未付款的客户找出来放在一个视图中,并按客户的名字进行排序:

CREATE VIEW DBO.V_CUST_RCVLBES

AS

SELECT cust.name,rcvbles.balance,……other columns

FROM cust,rcvbles

WHERE cust.customer_id = rcvlbes.customer_id

AND rcvblls.balance>0

ORDER BY cust.name

然后以下面的方式在视图中查询:

SELECT * FROM V_CUST_RCVLBES

WHERE postcode>“98000”

视图中的行要比主表中的行少,而且物理顺序就是所要求的顺序,减少了磁盘I/O,所以查询工作量可以得到大幅减少。

23、能用DISTINCT的就不用GROUP BY (group by 操作特别慢)

SELECT OrderID FROM Details WHERE UnitPrice > 10 GROUP BY OrderID

可改为:

SELECT DISTINCT OrderID FROM Details WHERE UnitPrice > 10

24.能用UNION ALL就不要用UNION

UNION ALL不执行SELECT DISTINCT函数,这样就会减少很多不必要的资源

35.尽量不要用SELECT INTO语句。

SELECT INOT 语句会导致表锁定,阻止其他用户访问该表。

上面我们提到的是一些基本的提高查询速度的注意事项,但是在更多的情况下,往往需要反复试验比较不同的语句以得到最佳方案。最好的方法当然是测试,看实现相同功能的SQL语句哪个执行时间最少,但是数据库中如果数据量很少,是比较不出来的,这时可以用查看执行计划,即:把实现相同功能的多条SQL语句考到查询分析器,按CTRL+L看查所利用的索引,表扫描次数(这两个对性能影响最大),总体上看询成本百分比即可。

三、算法的优化

尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。

游标提供了对特定集合中逐行扫描的手段,一般使用游标逐行遍历数据,根据取出的数据不同条件进行不同的操作。尤其对多表和大表定义的游标(大的数据集合)循环很容易使程序进入一个漫长的等特甚至死机。

在有些场合,有时也非得使用游标,此时也可考虑将符合条件的数据行转入临时表中,再对临时表定义游标进行操作,可时性能得到明显提高。

(例如:对内统计第一版)

封装存储过程

四、建立高效的索引

创建索引一般有以下两个目的:维护被索引列的唯一性和提供快速访问表中数据的策略。大型数据库有两种索引即簇索引和非簇索引,一个没有簇索引的表是按堆结构存储数据,所有的数据均添加在表的尾部,而建立了簇索引的表,其数据在物理上会按照簇索引键的顺序存储,一个表只允许有一个簇索引,因此,根据B树结构,可以理解添加任何一种索引均能提高按索引列查询的速度,但会降低插入、更新、删除操作的性能,尤其是当填充因子(Fill Factor)较大时。所以对索引较多的表进行频繁的插入、更新、删除操作,建表和索引时因设置较小的填充因子,以便在各数据页中留下较多的自由空间,减少页分割及重新组织的工作。

索引是从数据库中获取数据的最高效方式之一。95% 的数据库性能问题都可以采用索引技术得到解决。作为一条规则,我通常对逻辑主键使用唯一的成组索引,对系统键(作为存储过程)采用唯一的非成组索引,对任何外键列[字段]采用非成组索引。不过,索引就象是盐,太多了菜就咸了。你得考虑数据库的空间有多大,表如何进行访问,还有这些访问是否主要用作读写。

实际上,您可以把索引理解为一种特殊的目录。微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引、簇集索引)和非聚集索引(nonclustered index,也称非聚类索引、非簇集索引)。下面,我们举例来说明一下聚集索引和非聚集索引的区别:
其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。
我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。

如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。
我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。

进一步引申一下,我们可以很容易的理解:每个表只能有一个聚集索引,因为目录只能按照一种方法进行排序。

(一)何时使用聚集索引或非聚集索引

下面的表总结了何时使用聚集索引或非聚集索引(很重要)。

动作描述 使用聚集索引 使用非聚集索引

列经常被分组排序 应 应

返回某范围内的数据 应 不应

一个或极少不同值 不应 不应

小数目的不同值 应 不应

大数目的不同值 不应 应

频繁更新的列 不应 应

外键列 应 应

主键列 应 应

频繁修改索引列 不应 应

事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个时间列,恰好您把聚合索引建立在了该列,这时您查询2004年1月1日至2004年10月1日之间的全部数据时,这个速度就将是很快的,因为您的这本字典正文是按日期进行排序的,聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可;而不像非聚集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到具体内容。

(二)结合实际,谈索引使用的误区

理论的目的是应用。虽然我们刚才列出了何时应使用聚集索引或非聚集索引,但在实践中以上规则却很容易被忽视或不能根据实际情况进行综合分析。下面我们将根据在实践中遇到的实际问题来谈一下索引使用的误区,以便于大家掌握索引建立的方法。

1、主键就是聚集索引

这种想法笔者认为是极端错误的,是对聚集索引的一种浪费。虽然SQL SERVER默认是在主键上建立聚集索引的。

通常,我们会在每个表中都建立一个ID列,以区分每条数据,并且这个ID列是自动增大的,步长一般为1。我们的这个办公自动化的实例中的列Gid就是如此。此时,如果我们将这个列设为主键,SQL SERVER会将此列默认为聚集索引。这样做有好处,就是可以让您的数据在数据库中按照ID进行物理排序,但笔者认为这样做意义不大。

显而易见,聚集索引的优势是很明显的,而每个表中只能有一个聚集索引的规则,这使得聚集索引变得更加珍贵。

从我们前面谈到的聚集索引的定义我们可以看出,使用聚集索引的最大好处就是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际应用中,因为ID号是自动生成的,我们并不知道每条记录的ID号,所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。其次,让每个ID号都不同的字段作为聚集索引也不符合“大数目的不同值情况下不应建立聚合索引”规则;当然,这种情况只是针对用户经常修改记录内容,特别是索引项的时候会负作用,但对于查询速度并没有影响。

在办公自动化系统中,无论是系统首页显示的需要用户签收的文件、会议还是用户进行文件查询等任何情况下进行数据查询都离不开字段的是“日期”还有用户本身的“用户名”。

通常,办公自动化的首页会显示每个用户尚未签收的文件或会议。虽然我们的where语句可以仅仅限制当前用户尚未签收的情况,但如果您的系统已建立了很长时间,并且数据量很大,那么,每次每个用户打开首页的时候都进行一次全表扫描,这样做意义是不大的,绝大多数的用户1个月前的文件都已经浏览过了,这样做只能徒增数据库的开销而已。事实上,我们完全可以让用户打开系统首页时,数据库仅仅查询这个用户近3个月来未阅览的文件,通过“日期”这个字段来限制表扫描,提高查询速度。如果您的办公自动化系统已经建立的2年,那么您的首页显示速度理论上将是原来速度8倍,甚至更快。

2、只要建立索引就能显著提高查询速度

事实上,我们可以发现上面的例子中,第2、3条语句完全相同,且建立索引的字段也相同;不同的仅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查询速度却有着天壤之别。所以,并非是在任何字段上简单地建立索引就能提高查询速度。

从建表的语句中,我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同记录。在此字段上建立聚合索引是再合适不过了。在现实中,我们每天都会发几个文件,这几个文件的发文日期就相同,这完全符合建立聚集索引要求的:“既不能绝大多数都相同,又不能只有极少数相同”的规则。由此看来,我们建立“适当”的聚合索引对于我们提高查询速度是非常重要的。

3、把所有需要提高查询速度的字段都加进聚集索引,以提高查询速度

上面已经谈到:在进行数据查询时都离不开字段的是“日期”还有用户本身的“用户名”。既然这两个字段都是如此的重要,我们可以把他们合并起来,建立一个复合索引(compound index)。

很多人认为只要把任何字段加进聚集索引,就能提高查询速度,也有人感到迷惑:如果把复合的聚集索引字段分开查询,那么查询速度会减慢吗?带着这个问题,我们来看一下以下的查询速度(结果集都是25万条数据):(日期列fariqi首先排在复合聚集索引的起始列,用户名neibuyonghu排在后列)

我们可以看到如果仅用聚集索引的起始列作为查询条件和同时用到复合聚集索引的全部列的查询速度是几乎一样的,甚至比用上全部的复合索引列还要略快(在查询结果集数目一样的情况下);而如果仅用复合聚集索引的非起始列作为查询条件的话,这个索引是不起任何作用的。当然,语句1、2的查询速度一样是因为查询的条目数一样,如果复合索引的所有列都用上,而且查询结果少的话,这样就会形成“索引覆盖”,因而性能可以达到最优。同时,请记住:无论您是否经常使用聚合索引的其他列,但其前导列一定要是使用最频繁的列。

(三)其他注意事项

“水可载舟,亦可覆舟”,索引也一样。索引有助于提高检索性能,但过多或不当的索引也会导致系统低效。因为用户在表中每加进一个索引,数据库就要做更多的工作。过多的索引甚至会导致索引碎片。

所以说,我们要建立一个“适当”的索引体系,特别是对聚合索引的创建,更应精益求精,以使您的数据库能得到高性能的发挥

Redis缓存穿透、缓存雪崩和缓存击穿——实例

转自:https://baijiahao.baidu.com/s?id=1619572269435584821&wfr=spider&for=pc

Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。

另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。本篇文章,并不是要更加完美的解决这三个问题,也不是要颠覆业界流行的解决方案。而是,从实际代码操作,来演示这三个问题现象。之所以要这么做,是因为,仅仅看这些问题的学术解释,脑袋里很难有一个很形象的概念,有了实际的代码演示,可以加深对这些问题的理解和认识。

缓存穿透

缓存穿透,是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。

Redis缓存流程

代码流程

参数传入对象主键ID根据key从缓存中获取对象如果对象不为空,直接返回如果对象为空,进行数据库查询如果从数据库查询出的对象不为空,则放入缓存(设定过期时间)想象一下这个情况,如果传入的参数为-1,会是怎么样?这个-1,就是一定不存在的对象。就会每次都去查询数据库,而每次查询都是空,每次又都不会进行缓存。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。即便是采用UUID,也是很容易找到一个不存在的KEY,进行攻击。

小编在工作中,会采用缓存空值的方式,也就是【代码流程】中第5步,如果从数据库查询的对象为空,也放入缓存,只是设定的缓存过期时间较短,比如设置为60秒。

缓存空值

缓存雪崩

缓存雪崩,是指在某一个时间段,缓存集中过期失效。

产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。

小编在做电商项目的时候,一般是采取不同分类商品,缓存不同周期。在同一分类中的商品,加上一个随机因子。这样能尽可能分散缓存过期时间,而且,热门类目的商品缓存时间长一些,冷门类目的商品缓存时间短一些,也能节省缓存服务的资源。

缓存时间加入suijiyinzi

其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,那么那个时候数据库能顶住压力,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。

缓存击穿

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

小编在做电商项目的时候,把这货就成为“爆款”。

其实,大多数情况下这种爆款很难对数据库服务器造成压垮性的压力。达到这个级别的公司没有几家的。所以,务实主义的小编,对主打商品都是早早的做好了准备,让缓存永不过期。即便某些商品自己发酵成了爆款,也是直接设为永不过期就好了。

大道至简,mutex key互斥锁真心用不上。

JAVA实现UDP通信

UDP的Java支持
UDP协议提供的服务不同于TCP协议的端到端服务,它是面向非连接的,属不可靠协议,UDP套接字在使用前不需要进行连接。实际上,UDP协议只实现了两个功能:
1)在IP协议的基础上添加了端口;
2)对传输过程中可能产生的数据错误进行了检测,并抛弃已经损坏的数据。

Java通过DatagramPacket类和DatagramSocket类来使用UDP套接字,客户端和服务器端都通过DatagramSocket的send()方法和receive()方法来发送和接收数据,用DatagramPacket来包装需要发送或者接收到的数据。发送信息时,Java创建一个包含待发送信息的DatagramPacket实例,并将其作为参数传递给DatagramSocket实例的send()方法;接收信息时,Java程序首先创建一个DatagramPacket实例,该实例预先分配了一些空间,并将接收到的信息存放在该空间中,然后把该实例作为参数传递给DatagramSocket实例的receive()方法。在创建DatagramPacket实例时,要注意:如果该实例用来包装待接收的数据,则不指定数据来源的远程主机和端口,只需指定一个缓存数据的byte数组即可(在调用receive()方法接收到数据后,源地址和端口等信息会自动包含在DatagramPacket实例中),而如果该实例用来包装待发送的数据,则要指定要发送到的目的主机和端口。

UDP的通信建立的步骤
UDP客户端首先向被动等待联系的服务器发送一个数据报文。一个典型的UDP客户端要经过下面三步操作:
1. 创建一个DatagramSocket实例,可以有选择地对本地地址和端口号进行设置,如果设置了端口号,则客户端会在该端口号上监听从服务器端发送来的数据;
2. 使用DatagramSocket实例的send()和receive()方法来发送和接收DatagramPacket实例,进行通信;
3. 通信完成后,调用DatagramSocket实例的close()方法来关闭该套接字。
由于UDP是无连接的,因此UDP服务端不需要等待客户端的请求以建立连接。另外,UDP服务器为所有通信使用同一套接字,这点与TCP服务器不同,TCP服务器则为每个成功返回的accept()方法创建一个新的套接字。一个典型的UDP服务端要经过下面三步操作:
1. 创建一个DatagramSocket实例,指定本地端口号,并可以有选择地指定本地地址,此时,服务器已经准备好从任何客户端接收数据报文;
2. 使用DatagramSocket实例的receive()方法接收一个DatagramPacket实例,当receive()方法返回时,数据报文就包含了客户端的地址,这样就知道了回复信息应该发送到什么地方;
3. 使用DatagramSocket实例的send()方法向服务器端返回DatagramPacket实例。

一个程序为服务端,建立UDP服务端套接字。

java源程序如下:

import java.io.IOException;//导入IOException类
import java.net.DatagramPacket;//导入DatagramPacket类
import java.net.DatagramSocket;//导DatagramSocket入
import java.net.InetAddress;//导入InetAddress类
import java.util.Scanner;//导入Scanner类
/*
 * 客户端
 */
public class UDPClient {//公共类
    public static void main(String[] args) throws IOException {//主程序入口
        /*
         * 向服务器端发送数据
         */      
        InetAddress address = InetAddress.getByName("localhost"); // 1.定义服务器的地址、端口号、数据
        int port = 8800;//定义端口类型
       while(true) {//通过循环不同的向客户端发送和接受数据
        Scanner scanner = new Scanner(System.in);//从键盘接受数据
        String send = scanner.nextLine();//nextLine方式接受字符串
        byte[] data = send.getBytes();//将接收到的数据变成字节数组      
        DatagramPacket packet = new DatagramPacket(data, data.length, address, port);//2.创建数据报,包含发送的数据信息     
        DatagramSocket socket = new DatagramSocket(); // 3.创建DatagramSocket对象       
        socket.send(packet);// 4.向服务器端发送数据报

        /*
         * 接收服务器端响应的数据
         */      
        byte[] data2 = new byte[1024];//创建字节数组 
        DatagramPacket packet2 = new DatagramPacket(data2, data2.length);// 1.创建数据报,用于接收服务器端响应的数据          
        socket.receive(packet2);// 2.接收服务器响应的数据
        //3.读取数据
        String reply = new String(data2, 0, packet2.getLength());//创建字符串对象
        System.out.println("我是客户端,服务器说:" + reply);//输出提示信息
        socket.close();//4.关闭资源
       }
    }
}

另外一个程序为客户端,建立UDP客户端套接字。
java源程序如下:

import java.io.IOException;//导入IOException类
import java.net.DatagramPacket;//导入DatagramPacket类
import java.net.DatagramSocket;//导入DatagramSocket类
import java.net.InetAddress;//导入InetAddress类
import java.util.Scanner;//导入Scanner类

/*
 * 服务器端,实现基于UDP的用户登陆
 */
public class UDPServer {//公共类
    public static void main(String[] args) throws IOException {//主程序入口
        /*
         * 接收客户端发送的数据
         */      
        DatagramSocket socket = new DatagramSocket(8800);  // 1.创建服务器端DatagramSocket,指定端口
        // 2.创建数据报,用于接收客户端发送的数据
        byte[] data = new byte[1024];//创建字节数组,指定接收的数据包的大小
        DatagramPacket packet = new DatagramPacket(data, data.length);

        // 3.接收客户端发送的数据
        System.out.println("****服务器端已经启动,等待客户端发送数据");//输出提示信息
       while(true) {//通过循环不停的向客户端发送数据和接收数据
        socket.receive(packet);// 此方法在接收到数据报之前会一直阻塞
        // 4.读取数据 
        String info = new String(data, 0, packet.getLength());//创建字符串对象
        System.out.println("我是服务器,客户端说:" + info);//输出提示信息    

        /*
         * 向客户端响应数据
         */
        // 1.定义客户端的地址、端口号、数据    
        InetAddress address = packet.getAddress();//获取发送端的地址
        int port = packet.getPort();//获取 发送端进程所绑定的端口  
        Scanner scanner = new Scanner(System.in);//从键盘接受数据  
        String send = scanner.nextLine();//nextLine方式接受字符串  
        byte[] data2 = send.getBytes();//将接收到的数据转换为字节数组
        DatagramPacket packet2 = new DatagramPacket(data2, data2.length,address,port);// 2.创建数据报,包含响应的数据信息      
        socket.send(packet2); // 3.响应客户端  
       } 

    }
}
这里写图片描述

运行结果如下:

2020年iOS面试题及答案

问题来源:链接:https://juejin.im/post/5e75aba6e51d4526d71d6558

1,分类和扩展有什么区别?可以分别用来做什么?分类有哪些局限性?分类的结构体里面有哪些成员?

①类别中原则上只能增加方法(能添加属性的的原因只是通过runtime能添加属性的的原因只是通过runtime的objc_setAssociatedObject和objc_getAssociatedObject方法解决无setter/getter的问题而已);
②类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的(
用范围只能在自身类,而不是子类或其他地方);
③类扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。
④类扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。
⑤定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。

最重要的还是类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。
分类方法未实现,编译器也不会报警告。
分类方法与原类中相同会优先调用分类。

分类的结构体

typedef struct objc_category *Category;
struct objc_category {
  char *category_name                          OBJC2_UNAVAILABLE; // 分类名
  char *class_name                             OBJC2_UNAVAILABLE; // 分类所属的类名
  struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE; // 实例方法列表
  struct objc_method_list *class_methods       OBJC2_UNAVAILABLE; // 类方法列表
  struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}

2,讲一下atomic的实现机制;为什么不能保证绝对的线程安全(最好可以结合场景来说)?

atomic是在setter和getter方法里会使用自旋锁spinlock_t来保证setter方法和getter方法的线程的安全。可以看做是getter方法获取到返回值之前不会执行setter方法里的赋值代码。如果不加atomic,可能在getter方法读取的过程中,再别的线成立发生setter操作,从而出现异常值。

加上atomic后,setter和getter方法是线程安全的,原子性的,但是出了getter方法和setter方法后就不能保证线程安全了

@property (atomic, strong) NSArray*                arr;
//thread A
for (int i = 0; i < 10000; i ++) {
    if (i % 2 == 0) {
        self.arr = @[@"1", @"2", @"3"];
    }
    else {
        self.arr = @[@"1"];
    }
}

//thread B
for (int i = 0; i < 100000; i ++) {
    if (self.arr.count >= 2) {
        NSString* str = [self.arr objectAtIndex:1];
    }
}

上面的例子线程B里面可能会因为数组越界而引起crash,因为加入在B线程里判断self.arr.count >= 2的时候数组是self.arr = @[@“1”, @“2”, @“3”];但是当调用[self.arr objectAtIndex:1]可能self.arr的值已经在线程A里被更改为了@[@“1”],此时数组越界了。因此,虽然self.arr是atomic的,还是会出现线程安全问题。

3,被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?
被weak修饰的对象在被释放时候会置为nil,不同于assign;

Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。

1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

struct SideTable {
    // 保证原子操作的自旋锁
    spinlock_t slock;
    // 引用计数的 hash 表
    RefcountMap refcnts;
    // weak 引用全局 hash 表
    weak_table_t weak_table;
}

struct weak_table_t {
    // 保存了所有指向指定对象的 weak 指针
    weak_entry_t *weak_entries;
    // 存储空间
    size_t    num_entries;
    // 参与判断引用计数辅助量
    uintptr_t mask;
    // hash key 最大偏移值
    uintptr_t max_hash_displacement;
};

4,Autoreleasepool 所使用的数据结构是什么? AutoreleasePoolPage 结构体了解么?

Autoreleasepool所使用的数据结构是什么?AutoreleasePoolPage结构体了解么

6,iOS 中内省的几个方法? class 方法和 objc_getClass 方法有什么区别?

1.当参数obj为Object实例对象
object_getClass(obj)与[obj class]输出结果一直,均获得isa指针,即指向类对象的指针。

2.当参数obj为Class类对象
object_getClass(obj)返回类对象中的isa指针,即指向元类对象的指针;[obj class]返回的则是其本身。

3.当参数obj为Metaclass类对象
object_getClass(obj)返回元类对象中的isa指针,因为元类对象的isa指针指向根类,所有返回的是根类对象的地址指针;[obj class]返回的则是其本身。

4.obj为Rootclass类对象
object_getClass(obj)返回根类对象中的isa指针,因为跟类对象的isa指针指向Rootclass‘s metaclass(根元类),即返回的是根元类的地址指针;[obj class]返回的则是其本身。

总结:
经上面初步的探索得知,object_getClass(obj)返回的是obj中的isa指针;而[obj class]则分两种情况:一是当obj为实例对象时,[obj class]中class是实例方法:- (Class)class,返回的obj对象中的isa指针;二是当obj为类对象(包括元类和根类以及根元类)时,调用的是类方法:+ (Class)class,返回的结果为其本身。

7,RunLoop的作用是什么?它的内部工作机制了解么?(最好结合线程和内存管理来说)
Runloop是什么?
深入理解RunLoop | Garan no dou

字面意思是“消息循环、运行循环”,runloop内部实际上就是一个do-while循环,它在循环监听着各种事件源、消息,对他们进行管理并分发给线程来执行。

1.通知观察者将要进入运行循环。
线程和 RunLoop 之间是一一对应的

2.通知观察者将要处理计时器。

3.通知观察者任何非基于端口的输入源即将触发。

4.触发任何准备触发的基于非端口的输入源。

5.如果基于端口的输入源准备就绪并等待触发,请立即处理该事件。转到第9步。

6.通知观察者线程即将睡眠。

7.将线程置于睡眠状态,直到发生以下事件之一:

事件到达基于端口的输入源。
计时器运行。
为运行循环设置的超时值到期。
运行循环被明确唤醒。

8.通知观察者线程被唤醒。

9.处理待处理事件。

如果触发了用户定义的计时器,则处理计时器事件并重新启动循环。转到第2步。
如果输入源被触发,则传递事件。
如果运行循环被明确唤醒但尚未超时,请重新启动循环。转到第2步。

10.通知观察者运行循环已退出。

8:苹果是如何实现 autoreleasepool的?

arc下编译器会优化成
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);
复制代码
向一个结构AutoreleasePoolPage,中写入需要自动释放的对象,类似一种标记,调用objc_autoreleasePoolPop(context)后,就会把这中间的对象release一下。
这里要注意的是,方法返回值是怎么做到自动释放的?
其使用Thread Local Storage(TLS)线程局部存储,每次存入线程或者从线程取出来。
我们没有卸载{}中的自动释放对象,会在每个runloop结束时候去释放,相当于一个大的autoreleasepool中。
参考文章
苹果是如何实现autoreleasepool的

8,哪些场景可以触发离屏渲染?(知道多少说多少)

  • shouldRasterize(光栅化)
  • masks(遮罩)
  • shadows(阴影)
  • edge antialiasing(抗锯齿)
  • group opacity(不透明)
  • 复杂形状设置圆角等
  • 渐变

3人点赞日记本

作者:在ios写bug的杰克
链接:https://www.jianshu.com/p/061d76449e2a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2020年Android面试题大全(附答案)

面试题包含java基础,数据结构,网络,Android,设计模式,Jvm,Kotlin等。适合中高级工程师。

一:Java基础

1.Object

equals和==的区别?equals和hashcode的关系?

==:基本类型比较值,引用类型比较地址。

equals:默认情况下,equals作为对象中的方法,比较的是地址,不过可以根据业务,修改equals方法,比如String就重写了equals方法。

默认情况下,equals相等,hashcode必相等,hashcode相等,equals不是必相等、hashcode基于内存地址计算得出,可能会相等,虽然几率微乎其微。

2.String

String,StringBuffer和StringBuilder的区别?

String:String属于不可变对象,每次修改都会生成新的对象。

StringBuilder:可变对象,非多线程安全,效率高于StringBuffer

StringBuffer:可变对象,多线程安全。

效率:StringBuilder>StringBuffer>String

3.面向对象的特征

java中抽象类和接口的特点?

共同点:

1.抽象类和接口都不能生成具体的实例。

2.都是作为上层使用。

不同点:

1.抽象类可以有属性和成员方法,接口不可以。

2.一个类只能继承一个类,但是可以实现多个接口。

3.抽象类中的变量是普通变量,接口中的变量是静态变量。

4.抽象类表达的是一种is-a的关系,即父类和派生子类在概念上的本质是相同的。

5.接口表达的是一种like-a的关系,即接口和实现类的关系只是实现了定义行为,并无本质上的联系。

关于多态的理解?

多态是面向对象的三大特性:继承,封装和多态之一。

多态的定义:允许不同类对同一消息做出响应。

多态存在的条件

1.要有继承。

2.要有复写。

3.父类引用指向子类对象。

java中多态的实现方式:接口实现,继承父类进行方法重写,同一个类中的方法重载。

4.集合

HashMap的特点是什么?HashMap的原理?

HashMap的特点:

1.通过键的Hash值确定数组的位置。

2.找到以后,如果该位置无节点,直接存放。

3.该位置有节点即位置发生冲突,遍历该节点以及后续的节点,比较key值,相等则覆盖。

4.没有就新增节点,默认使用链表,相连节点数超过8的时候,在jdk1.8中会变成红黑树。

5.如果Hashmap中的数组使用情况超过一定比例,就回扩容,默认扩容两倍。

这是存入的过程。需要注意的是:

key的hash值计算过程是高16位不变,低16位取抑或,让更多位参与进来,可以有效的减少碰撞的发生。

初始数组容量为16,默认不超过的比例为0.75.

5.泛型

泛型的本质是参数化类型,在不创建新的类型的情况下,通过泛型指定不同的类型来控制形参具体限制的类型。也就是说在泛型的使用中,操作的数据类型被指定为一个参数,这种参数可以被用在类,接口和方法中,分别被称为泛型类,泛型接口和泛型方法。

泛型是java中的一种语法糖,能够在代码编写的时候起到类型检测的作用,但是虚拟机是不支持这些语法的。

泛型的优点:

1.类型安全,避免类型的强转。

2.提高了代码的可读性,不必要等到运行的时候才去强制转换。

什么是类型擦除?

不管泛型的类型传入哪一种类型实参,对于java来说,都会被当成同一类处理,在内存中也只占用一块空间,通俗一点来说,就是泛型之作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的信息擦除,也就是说,成功编译过后的class文件时不包含任何泛型信息的。

6.反射

动态代理和静态代理

静态代理很简单,运用的就是代理模式:声明一个接口,再分别实现一个真实的主题类和代理主题类,通过让代理类持有真实主题类,从而控制用户对真实主题的访问。

动态代理指的是在运行时动态生成代理类,即代理类的字节码在运行时生成并载入当前的ClassLoader。

动态代理的原理是使用反射,思路和上面的一致。

使用动态代理的好处:

1.不需要为RealSubject写一个形式完全一样的代理类。

2.使用一些动态代理的方法可以在运行时制定代理类的逻辑,从而提升系统的灵活性。

java并发

java并发中考察频率较高的有线程,线程池,锁,线程间的等待和唤醒,线程特性和阻塞队列等。

1.线程

线程的状态有哪些?下图为一张状态转换图

线程中wait和sleep的区别?

wait方法即释放cpu,又释放锁。

sleep方法只释放cpu,但是不释放锁。

进程和线程的区别?

进程是资源分配的最小单位,线程是程序执行的最小单位,一个进程可以包含多个线程,在Android中,一个进程通常是一个App,App中会有一个主线程,主线程可以用来操作界面元素。如果有耗时操作,必须开启子线程执行。不然会导致ANR。进程间的数据是独立的,线程间的数据可以共享。

2.线程池

线程池地位十分重要,基本上涉及到跨线程的框架都是用到了线程池,比如OkHttp,RxJava,LiveData以及协程等。

与新建一个线程相比,线程池的特点?

1.节省开销,线程池中的线程可以重复利用。

2.速度快,任务来了就开始,省去创建线程的时间。

3.线程可控,线程数量可控和任务可控。

4.功能强大,可以定时和重复执行任务。

线程池中的几个参数是什么意思,线程池的种类有哪些?

线程池的构造函数如下:

publicThreadPoolExecutor(intcorePoolSize,

intmaximumPoolSize,

longkeepAliveTime,

TimeUnit unit,

BlockingQueue workQueue){

    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,

             Executors.defaultThreadFactory(), defaultHandler);

}

参数含义:

corePoolize:核心线程数量,不会释放。

maximumPoolSize:允许使用的最大线程池数量,非核心线程数量,闲置时会释放。

keepAliveTime:闲置线程允许的最大闲置时间。

unit:闲置时间的单位。

workQueue:阻塞队列,不同的阻塞队列有不同的特性。

线程池分为四个类型:

CachedThreadPool:闲置线程超时会释放,没有闲置线程的情况下,每次都会创建新的线程。

FixedThreadPool:线程池只能存放指定数量的线程池,线程不会释放,可重复利用。

SingleThreadExecutor:单线程的线程池。

ScheduledThreadPool:可定时和重复执行的线程池。

线程池的工作流程?

1.任务来了,优先考虑核心线程。

2.核心线程满了,进入阻塞队列。

3.阻塞队列满了,考虑非核心线程。

4.非核心线程满了,再触发拒绝任务。

3.锁

死锁触发的四大条件?

1.互斥锁

2.请求与保持

3.不可剥夺

4.循环的请求与等待

synchronized关键字的使用?synchronized的参数放入对象和Class有什么区别?

synchronized关键字的用法:

1.修饰方法

2.修饰代码块,需要自己提供锁对象,锁对象包括对象本身,对象的Class和其他对象。

放入对象和Class的区别是:

1.锁住的对象不同:成员方法锁住的实例对象,静态方法锁住的是Class。

2.访问控制不同:如果锁住的是实例,只会针对同一个对象方法进行同步访问,多线程访问同一个对象的synchronized代码块是串行的,访问不同对象是并行的。如果锁住的是类,多线程访问的不管是同一对象还是不同对象的synchronized代码块都是串行的。

synchronized的原理?

任何一个对象都有一个monitor与之相关联,JVM基于进入和退出mointor对象来实现代码块同步和方法同步,两者实现细节不同:

1.代码块同步:在编译字节码的时候,代码块起始的地方插入monitorenter指令,异常和代码块结束处插入monitorexit指令,线程在执行monitorenter指令的时候尝试获取monitor对象的所有权。获取 不到的情况下就是阻塞。

2.方法同步:synchronized方法在method_info结构有AAC_synchronized标记,线程在执行的时候获取对应的锁,从而实现同步方法。

synchronized和Lock的区别 ?

主要区别:

1.synchronized是Java中的关键字,是Java的内置实现;Lock是Java中的接口。

2.synchronized遇到异常会释放锁;Lock需要在发生异常的时候调用成员方法Lock#unlock()方法。

3.synchronized是不可以中断的,Lock可中断。

4.synchronized不能去尝试获得锁,没有获得锁就会被阻塞;Lock可以去尝试获得锁,如果未获得可以尝试处理 其他逻辑。

5.synchronized多线程效率步入Lock,不过Java1.6以后已经对synchronized进行大量的优化,所以性能上来讲,其实差不了多少。

悲观锁和乐观锁的举例?以及他们的相关实现?

悲观锁和乐观锁的概念:

悲观锁:悲观锁会认为,修改共享数据的时候其他线程也会修改数据,因此只在不会受到其他线程干扰的情况下执行,这样会导致其他有需要锁的线程挂起,等到持有锁的线程释放锁。

乐观锁:每次不加锁,每次直接修改共享数据假设其他线程不会修改,如果发生冲突就直接重试,直到成功为止。

举例:

悲观锁:典型的悲观锁是独占锁,有synchronized,ReentrantLock。

乐观锁:典型的乐观锁是CAS,实现CAS的atomic为代表的一系列类。

CAS是什么?底层原理?

CAS全程Compare And Set,核心的三个元素是:内存位置,预期原值和新值,执行CAS的时候,会将内存位置的值与预期原值进行比较,如果一致,就将原值更新为新值,否则就不更新。

底层原理:是借助CPU底层指令cmpxchg实现原子操作。

4.线程间通讯

notify和notifyAll方法的区别?

notify随机唤醒一个线程,notifyAll唤醒所有等待的线程,让他们竞争锁。

wait/notify和Condition类实现的等待通知有什么区别?

synchronized与wait/notify结合的等待通知只有一个条件,而Condition类可以实现多个条件等待。

5.多线程间的特性

多线程间的有序性,可见性和原子性是什么意思?

原子性:执行一个或者多个操作的时候,要么全部执行,要么都不执行,并且中间过程中不会被打断。Java中的原子性可以通过独占锁和CAS去保证。

可见性:指多线程访问同一个变量的时候,一个线程修改了变量的值,其他线程能够立刻看得到修改的值。锁和volatile能够保证可见性。

有序性:程序执行的顺序按照代码先后的顺序执行。锁和volatile能够保证有序性。

happens-before原则有哪些?

Java内存模型具有一些先天的有序性,它通常叫做happens-before原则。

如果两个操作的先后顺序不能通过happens-before原则推倒出来,那就不能保证他们的先后执行顺序。虚拟机就可以随意打乱执行命令。happens-before原则有:

1.程序次序规则:单线程程序的执行结果得和看上去代码执行的结果要一致。

2.锁定规则:一个锁的lock操作一定发生在上一个unlock操作之后。

3.volatile规则:对volatile变量的写操作一定先行于后面对这个变量的对操作。

4.传递规则:A发生在B前面,B发生在C前面,那么A一定发生在C前面。

5.线程启动规则:线程的start方法先行发生于线程中的每个动作。

6.线程中断规则:对线程的interrup操作先行发生于中断线程的检测代码。

7.线程终结原则:线程中所有的操作都先行发生于线程的终止检测。

8.对象终止原则:一个对象的初始化先行发生于他的finalize()方法的执行。

前四条规则比较重要。

volatile的原理?

可见性:如果对声明了volatile的变量进行写操作的时候,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写入到系统内存。

多处理器的环境下,其他处理器的缓存还是旧的,为了保证各个处理器一致,会通过嗅探在总线上传播的数据来检测自己的数据是否过期,如果过期,会强制重新将系统内存的数据读取到处理器缓存。

有序性:Lock前缀的指令相当于一个内存栅栏,它确保指令排序的时候,不会把后面的指令排到内存栅栏的前面,也不会吧把前面的指令排到内存栅栏的后面。

6.阻塞队列

通常的阻塞队列有哪几种?特点是什么?

ArrayBlockQueue:基于数组实现的有界的FIFO(先进先出)阻塞队列。

LinkedBlockQueue:基于链表实现的无界的FIFO(先进先出)阻塞队列。

SynchronousQueue:内部没有任何缓存的阻塞队列。

PriorityBlockingQueue:具有优先级的无限阻塞队列。

ConcurrentHashMap的原理

数据结构的实现跟HashMap一样,不做介绍。

JDK1.8之前采用的是分段锁,核心类是一个Segment,Segment继承了ReentrantLock,每个Segment对象管理若干个桶,多个线程访问同一个元素的时候只能去竞争获取锁。

JDK1.8采用了CAS+synchronized,插入键值对的时候如果当前桶中没有Node节点,使用CAS方式进行更新,如果有Node节点,则使用synchronized的方式进行更新。

二:网络基础知识

1.HTTP和HTTPS

HTTP是哪一层的协议,常见的HTTP状态码有那些?分别代表什么意思?

HTTP协议是应用层协议。

常见的HTTP状态码有:

1xx请求已经接收,继续处理

2xx服务器已经正确处理请求,比如200

3xx重定向,需要做进一步的处理才能完成请求

4xx服务器无法理解的请求,比如404,访问的资源不存在

5xx服务器收到请求以后,处理错误

HTTP1.1和HTTP2有什么区别?

HTTP2.0基于1.1,HTTP2.0增加了:

1.二进制格式:HTTP1.1使用纯文本进行通信,HTTP2.0使用二进制进行传输。

2.Head压缩:对已经发送的Header使用键值建立索引表,相同的Header使用索引表示。

3.服务器推送:服务器可以进行主动推送

4.多路复用:一个TCP连接可以划分成多个流,每个流都会分配Id,客户端可以借助流和服务端建立全双工进行通信,并且流具有优先级。

HTTP和HTTPS有什么区别?

简单来说,HTTP和HTTPS的关系是:HTTPS=HTTP+SSL/TLS

区别如下:

HTTP作用于应用层,使用80端口,起始地址是http://明文传输,消息容易被拦截,串改。

HTTPS作用域传输层,使用443端口,起始地址是https://,需要下载CA证书,传输的过程需要加密,安全性高。

SSL/TLS的握手过程?

这里借用《趣谈网络协议》的图片:

HTTPS传输过程中是如何处理进行加密的?为什么又对称加密的情况下仍然需要进行非对称加密?

过程和上图类似,一次获取证书,公钥,最后生成对称加密的钥匙进行对称加密。

对称加密可以保证加密效率,但是不能解决秘钥传输问题;非对称加密可以解决传输问题,但是效率不高。

2.TCP相关

TCP的三次握手过程,为什么需要三次,而不是两次或者四次?

只发送两次,服务端是不知道自己发送的消息能不能被客户端接收到。

因为TCP握手是三次,所以此时双方都已经知道自己发送的消息能够被对方收到,所以,第四次的发送就显得多余了。

TCP的四次挥手过程?

大致意思:

Client:我要断开连接了

Server:我收到你的消息了

Server:我也要断开连接了

Client:收到你要断开连接的消息了

之后Client等待两个MSL(数据包在网络上生存的最长时间),如果服务端没有回消息就彻底断开了。

TCP和UDP有什么区别?

TCP:基于字节流,面向连接,可靠,能够进行全双工通信,除此以外,还能进行流量控制和拥塞控制,不过效率略低。

UDP:基于报文,面向无连接,不可靠,但是传输效率高。

总的来说,TCP使用于传输效率要求低,准确性要求高或要求有连接。而UDP适用于对准确性要求低,传输效率要求较高的场景,比如语音通话,直播等。

TCP为什么是一种可靠的协议?如何做到流量控制和拥塞控制?

TCP可靠:是因为可以做到数据包发送的有序,无差错和无重复。

流量控制:是通过滑动窗口实现的,因为发送方和接收方消息发送速度和接收速度不一定对等,所以需要一个滑动窗口来平衡处理效率,并且保证没有差错和有序的接收数据包。

拥塞控制:慢开始和拥塞避免,快重传和快恢复算法,这些算法主要是为了适应网络中的带宽而做出的调整。

三:设计模式

1.六大原则

单一职责:合理分配类和函数的职责

开闭原则:开放扩展,关闭修改

里式替换:继承

依赖倒置:面向接口

接口隔离:控制接口的粒度

迪米特:一个类应该对其他的类了解最少

2.单例模式

单例的常用写法有哪几种?

懒汉模式:该模式主要问题是每次获取实例都需要同步,造成不必要的同步开销。

public classSingleInstance{

    private static SingleInstance instance;

    private SingleInstance(){}

    publicstaticsynchronizedSingleInstancegetInstance(){

        if(instance == null) {

            instance = new SingleInstance();

        }

        return instance;

    }

}

DCL模式:高并发环境下可能发生问题

public classSingleInstance{

    private static SingleInstance instance;

    privateSingleInstance(){}

    publicstaticSingleInstancegetInstance(){

        if(instance == null) {

            synchronized (SingleInstance.class) {

                if(instance == null) {

                    instance = new SingleInstance();

                }

            }

        }

        return instance;

    }

}

静态内部类单例

public classSingleInstance{

    privateSingleInstance(){}

    publicstaticSingleInstancegetInstance(){

        return SingleHolder.instance;

    }

    private static classSingleHolder{

        private static final SingleInstance instance = new SingleInstance();

    }

}

枚举单例

public enum SingletonEnum {

    INSTANCE

}

优点:线程安全和反序列化不会生成新的实例。

DCL模式会有什么问题?

对象生成实例的过程中,大概会经过以下过程:

1.为对象分配内存空间

2.初始化对象中的成员变量

3.将对象指向分配的内存空间(此时对象就不为null)

由于JVM会优化指令顺序,也就是说2和3的顺序是不能保证的。在多线程的情况下,当一个线程完成了1.3过程后,当前线程的时间片已用完,这个时候会切换到另一个线程,另一个线程调用这个单例,会使用这个还没初始化完成的实例。

解决方法是使用volatile关键字:

3.需要关注的设计模式

重点了解以下的几种常用的设计模式:

1.工厂模式和抽象工厂模式:注意他们的区别。

2.责任链模式:View的事件分发和OkHttp的调用过程都使用到了责任链模式。

3.观察者模式:重要性不言而喻。

4.代理模式:建议了解一下动态代理。

4.MVC/MVP/MVVM

MVC,MVP和MVVM应该是设计模式中考察频率最高的知识点了,严格意义上来说,他们不能算是设计模式,而是框架。

MVC、MVP和MVVM是什么?

MVC:Model-View-Controller,是一种分层解耦的框架,Model层提供本地数据和网络请求,View层处理视图,Controller处理逻辑,存在问题是Controller层的划分不明显,Model层和View层存在耦合。

MVP:Model_View_Presenter,是对MVC的升级,Model层和View层与MVC的意思一致,但Model层和View层不再存在耦合,而是通过Presenter层这个桥梁进行交流。

MVVM:Model_View_ViewModel:不同于上面的两个框架,ViewModel持有数据状态,当数据状态改变的时候,会自动通知View层进行更新。

MVC和MVP的区别是什么?

MVP是MVC的进一步解耦,简单来讲,在MVC中,View层既可以和Controller层交互,又可以和Model层交互,而在MVP中,View层只能和Presenter层交互,Model层也只能和Presenter层交互,减少了View层和Model层的耦合,更容易定位错误来源。

MVVM和MVP的最大区别在哪里?

MVP中的每个方法都需要你去主动调用,他其实是被动的,而MVVM中有数据驱动这个概念,当你的持有的数据状态发生变更的时候,你的View可以监听到这个变化,从而主动去更新,这其实是主动的。

ViewModel如何知道View层的生命周期?

事实上,如果你仅仅使用ViewModel,他是感知不了生命周期,他需要结合LiveData去感知生命周期,如果仅仅使用DataBinding去实现MVVM,它对数据源使用了弱引用,所以一定程度上可以避免内存泄露的发生。

四:Android基础

1.Activity

Activity的四大启动模式,以及应用场景?

standard:标准模式,每次都会在活动栈中生成一个新的Activity实例,通常我们使用的活动都是标准模式。

singleTop:栈顶复用,如果Activity实例已经存在栈顶,那么就不会再活动栈中创建新的实例。比较常见的场景就是给通知跳转的Activity设置,因为你肯定不想前台Activity已经是该Activity的情况下,点击通知,又创建一个同样的Activity。

singleTask:栈内复用,如果Activity实例在当前栈中已经存在,就会将当前Activity实例上面的其他Activity实例都移除出栈,常见于跳转到主界面。

singleInstance:单例模式,创建一个新的任务栈,这个活动实例独自处在这个活动栈中。

2.屏幕适配

使用的屏幕适配方案?原理是什么?

屏幕适配一般采用的头条的屏幕适配方案。简单来说,以屏幕的一边作为适配,通常是宽。

原理:设备像素px和设备独立像素dp之间的关系。

px = dp*density

假设UI给的设计图屏幕宽度基于360dp,那么设备宽的像素点已知,即px,dp也已知,360dp,所以density = px/dp,之后根据这个修改系统中跟density相关的知识点即可。

3.Android消息机制

Android消息机制介绍?

Android消息机制中的四大概念:

ThreadLocal:当前线程存储的数据仅能从当前线程取出。

MessageQueue:具有时间优先级的消息队列。

Looper:轮询消息队列,看是否有新的消息到来。

Handler:具体处理逻辑的地方。

过程:

1.准备工作:创建Handler,如果是在子线程中创建,还需要调用Looper#prepare(),在Handler的构造函数中,会绑定其中的Looper和MessageQueue。

2.发送消息:创建消息,使用Handler发送。

3.进入MessageQueue:因为Handler中绑定这消息队列,所以Message很自然的被放进消息队列。

4.Looper轮询消息队列:Looper是一个死循环,一直观察有没有新的消息到来,之后从Message取出绑定的Handler,最后调用Handler中的处理逻辑,这一切都发生在Looper循环的线程,这也是Handler能够在指定线程处理任务的原因。

Looper在主线程中死循环为什么没有导致界面的卡顿?

1.导致卡死的是在UI线程中执行耗时操作导致界面出现掉帧,甚至ANR。Looper.Loop()这个操作本身不会导致这个情况。

2.有人可能说,我在点击事件中设置死循环会导致界面卡死,同样都是死循环,不都一样吗?Looper会在没有消息的时候阻塞当前线程,释放CPU资源,等到有消息到来的时候,再唤醒主线程。

3.App进程中是需要死循环的,如果循环结束的话,App进程就结束了。

建议阅读:

《Android中为什么主线程不会因为Looper.loop()里的死循环卡死?》

https://www.zhihu.com/question/34652589

IdleHandler介绍?

IdleHandler是在Handler空闲时处理空闲任务的一种机制。

执行场景:

MessageQueue没有消息,队列为空的时候。

MessageQueue属于延迟消息,当前没有消息执行的时候。

会不会发生死循环:

肯定会,MessageQueue使用计数的方法保证一次调用MessageQueue#next方法只会使用一次的IdleHandler集合。

4.View事件分发机制和View绘制原理。

建议阅读《Android开发艺术探索》第三章,很详细。

5.Bitmap

Bitmap的内存计算方式?

在已知图片的长和宽的像素情况下,影响内存大小的因素会有资源文件位置和像素点大小。

常见的像素点有:

ARGB_8888:4个字节

ARGB_4444,ARGB_565:2个字节

资源文件位置:

不同dpi对应存放的文件夹

比如一个一张图片的像素为180*180px,dpi(设备独立像素密度)为320,如果它仅仅存放在drawable-hdpi,则有

横向像素点=180*320/240+0.5f = 240px

纵向像素点=180*320/240+0.5f  = 240px

如果它仅仅存放在drawable-xxhdpi,则有

横向像素点=180*320/480+0.5f=120px

纵向像素点=180*320/480+0.5f=120px

所以,对于一张180*180px的图片,设备dpi为320,资源图片仅仅存在drawable-hdpi,像素点大小为ARGB_4444,最后生成的文件内存大小为:

横向像素点=180*320/240+0.5f=240px

纵向像素点=180*320/240+0.5f=240px

内存大小=240*240*2 = 115200 byte 越等于 112.5kb

建议阅读;

《Android Bitmap的内存大小是如何计算的?》

https://ivonhoe.github.io/2017/03/22/Bitmap&Memory/

Bitmap的高效加载?

Bitmap的高效加载在Glide中也用到了,思路:

1.获取需要的长和宽,一般获取控件的长和宽。

2.设置BitmapFactory.Options中的inJustDecodeBounds为true,可以帮助我们在不加载内存的方式获得Bitmap的长和宽。

3.对需要的长和宽和Bitmap的长和宽进行对比,从而获得压缩比例,放入BitmapFactory.Options中的inSampleSize属性。

4.设置BitmapFactory.Options中的inJustDecodeBounds为false,将图片加载进内存,进而设置到控件中。

五:Android进阶

Android进阶中重点考察Android Framework,性能优化和第三方框架。

1.Binder

Binder的介绍?与其他IPC方式的优缺点?

Binder是Android中特有的IPC方式,引用《Android开发艺术探索》中的话

从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为虚拟的物理设备,它的设备驱动是/dev/binder;从Android Frameword来讲,Binder是Service Manager连接各种Manager和对应的ManagerService的桥梁,从面向对象和CS模型来讲,Client通过Binder和远程的Server进行通讯。

基于Binder,Android还实现了其他的IPC方式,比如AIDL,Messenger和ContentProvider。与其他IPC比较:

效率高:出了内存共享外,其他IPC都需要进行两次数据拷贝,而因为Binder使用内存映射的关系,仅需要一次数据拷贝。

安全性好:接收方可以从数据包中获取发送发的进程Id和用户Id,方便验证发送方的身份,其他IPC想要实验只能够主动存入,但是这有可能在发送的过程中被修改。

Binder的通信过程?Binder的原理?

其实这个过程也可以从AIDL生成的代码中看出。

原理:

Binder的结构:

Client:服务的请求方。

Server:服务的提供方。

Service Manager:为Server提供Binder的注册服务,为Client提供Binder的查询服务,Server、Client和Service Manager的通讯都是通过Binder。

Binder驱动:负责Binder通信机制的建立,提供一系列底层支持。

从上图中,Binder通信的过程是这样的:

1.Server在Service Manager中注册:Server进程在创建的时候,也会创建对应的Binder实体,如果要提供服务给Client,就必须为Binder实体注册一个名字。

2.Client通过Service Manager获取服务:Client知道服务中Binder实体的名字后,通过名字从Service Manager获取Binder实体的引用。

3.Client使用服务与Server进行通信:Client通过调用Binder实体与Server进行通信。

更详细一点?

Binder通信的实质是利用内存映射,将用户进程的内存地址和内核的内存地址映射为同一块物理地址,也就是说他们使用的同一块物理空间,每次创建Binder的时候大概分配128的空间。数据进行传输的时候,从这个内存空间分配一点,用完了再释放即可。

2.序列化

Android有那些序列化方式?

为了解决Android中内存序列化速度过慢的问题,Android使用了Parcelable.

3.Framework

Zygote孕育进程过程?

Activity的启动过程?

建议阅读:

《3分钟看懂Activity启动流程》

https://www.jianshu.com/p/9ecea420eb52

App的启动过程?

介绍下App进程和System Server进程如何联系:

App进程

ActivityThread:依赖于UI线程,实际处理与AMS中交互的工作。

ActivityManagerService:负责Activity,Service等的生命周期工作。

ApplicationThread:System Server进程中ApplicationThreadProxy的服务端,帮助System Server进程跟App进程交流。

System Server:Android核心的进程,掌管着Android系统中各种重要的服务。

具体过程:

1.用户点击App图标,Lanuacher进程通过Binder联系到System Server进程发起startActivity。

2.System Server通过Socket联系到Zygote,fork出一个新的App进程。

3.创建一个新的App进程以后,Zygote启动App进程的ActivityThread#main()方法。

4.在ActivityThread中,调用AMS进行ApplicationThread的绑定。

5.AMS发送创建Application的消息给ApplicationThread,进而转交给ActivityThread中的H,他是一个Handler,接着进行Application的创建工作。

6.AMS以同样的方式创建Activity,接着就是大家熟悉的创建Activity的工作了。

APK的安装过程?

建议阅读:

《Android Apk安装过程分析》

https://www.jianshu.com/p/953475cea991

Activity启动过程跟Window的关系?

建议阅读:

《简析Window、Activity、DecorView以及ViewRoot之间的错综关系》

https://juejin.im/post/5dac6aa2518825630e5d17da

Activity,Window,ViewRoot和DecorView之间的关系?

建议阅读:

《总结UI原理和高级的UI优化方式》

https://www.jianshu.com/p/8766babc40e0

4.Context

关于Context的理解?

建议阅读:

《Android Context 上下文 你必须知道的一切》

https://blog.csdn.net/lmj623565791/article/details/40481055

5.断点续传

多线程断点续传?

基础知识:

1.Http基础:在Http请求中,可以加入请求头Range,下载特定区间的文件数。

2.RandomAccessFile:支持随机访问,可以从指定位置进行数据的读写。

6.性能优化

平时做了哪些性能优化?

建议阅读:

《Android 性能优化最佳实践》

https://juejin.im/post/5b50b017f265da0f7b2f649c

7.第三方库

一定要在熟练使用后再去查看原理。

Glide

Glide考察的频率挺高的,常见的问题有:

1.Glide和其他图片加载框架的比较?

2.如何设计一个图片加载框架?

3.Glide缓存实现机制?

4.Glide如何处理生命周期?

建议阅读:

《Glide最全解析》

https://blog.csdn.net/sinyu890807/category_9268670.html

《面试官:简历上最好不要写Glide,不是问源码那么简单》

https://juejin.im/post/5dbeda27e51d452a161e00c8

OkHttp

OkHttp常见知识点:

1.责任链模式

2.interceptors和networkInterceptors的区别?

建议看一遍源码,过程并不复杂。

Retrofit

Retrofit常见问题:

1.设计模式和封层解耦的理念

2.动态代理

建议看一遍源码,过程并不复杂。

RxJava

RxJava难在各种操作符,我们了解一下大致的设计思想即可。

建议阅读一些RxJava的文章。

Android Jetpack(非必须)

我们主要阅读了Android Jetpack中以下库的源码:

1.Lifecycle:观察者模式,组件生命周期中发送事件。

2.DataBinding:核心就是利用LiveData或者Observable xxx 实现的观察者模式,对16进制的状态位更新,之后根据这个状态位取更新对应的内容。

3.LiveData:观察者模式,事件的生产消费模型。

4.ViewModel:借用Activity异常销毁时存储隐藏Fragment的机制存储ViewModel,保证数据的生命周期尽可能的延长。

5.Paging:设计思想。

建议阅读:

《Android Jetpack源码分析系列》

https://blog.csdn.net/mq2553299/column/info/24151

作者:简雨山舍
链接:https://www.jianshu.com/p/554bb50385b7
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Cocos Creator 怎么POST和get

function sendPostRequest(ServerLink,str,callback,errcall) {
var sendstr=JSON.stringify(str);
var xhr = cc.loader.getXMLHttpRequest();
xhr.open(“POST”, ServerLink);
//xhr.open(“GET”, ServerLink+link+”?”+parm,false);
xhr.setRequestHeader(“Content-Type”,”application/x-www-form-urlencoded”);
xhr.send(sendstr);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && (xhr.status >= 200 && xhr.status <= 207)) {
var result = JSON.parse(xhr.responseText);
if(result[“act”]==”erro”) {
errcall(result[“msg”]);
return;
}
callback(result);
}
};
}

fpm与swoole区别

PHP-FPM

早期版本的 PHP 并没有内置的 WEB 服务器,而是提供了 SAPI(Server API)给第三方做对接。现在非常流行的 php-fpm 就是通过 FastCGI 协议来处理 PHP 与第三方 WEB 服务器之间的通信。 (推荐学习: swoole视频教程)

比如 Nginx + php-fpm 的组合,这种方式运行的 fpm 是 Master/Worker 模式,启动一个 Master 进程监听来自 Nginx 的请求,再 fork 多个 Worker 进程处理请求。每个 Worker 进程只能处理一个请求,单一进程的生命周期大体如下:

初始化模块。

初始化请求。此处请求是请求 PHP 执行代码的意思,并非 HTTP 的请求。

执行 PHP 脚本。

结束请求。

关闭模块。

Swoole 采用的也是 Master/Worker 模式,不同的是 Master 进程有多个 Reactor 线程,Master 只是一个事件发生器,负责监听 Socket 句柄的事件变化。Worker 以多进程的方式运行,接收来自 Reactor 线程的请求,并执行回调函数(PHP 编写的)。启动 Master 进程的流程大致是:

初始化模块。

初始化请求。因为 swoole 需要通过 cli 的方式运行,所以初始化请求时,不会初始化 PHP 的全局变量,如 $_SERVER, $_POST, $_GET 等。

执行 PHP 脚本。包括词法、语法分析,变量、函数、类的初始化等,Master 进入监听状态,并不会结束进程。

Swoole 加速的原理

由 Reactor(epoll 的 IO 复用方式)负责监听 Socket 句柄的事件变化,解决高并发问题。

通过内存常驻的方式节省 PHP 代码初始化的时间,在使用笨重的框架时,用 swoole 加速效果是非常明显的。

对比不同

PHP-FPM

Master 主进程 / Worker 多进程模式。

启动 Master,通过 FastCGI 协议监听来自 Nginx 传输的请求。

每个 Worker 进程只对应一个连接,用于执行完整的 PHP 代码。

PHP 代码执行完毕,占用的内存会全部销毁,下一次请求需要重新再进行初始化等各种繁琐的操作。

只用于 HTTP Server。

Swoole

Master 主进程(由多个 Reactor 线程组成)/ Worker 多进程(或多线程)模式

启动 Master,初始化 PHP 代码,由 Reactor 监听 Socket 句柄的事件变化。

Reactor 主线程负责子多线程的均衡问题,Manager 进程管理 Worker 多进程,包括 TaskWorker 的进程。

每个 Worker 接受来自 Reactor 的请求,只需要执行回调函数部分的 PHP 代码。

只在 Master 启动时执行一遍 PHP 初始化代码,Master 进入监听状态,并不会结束进程。

不仅可以用于 HTTP Server,还可以建立 TCP 连接、WebSocket 连接。

以上就是fpm与swoole区别的详细内容,更多请关注php中文网其它相关文章!

Cocos Creator加载网络图片

//设置显示图片
function setImg(imgNode, spriteFrame) {
    imgNode.getComponent(cc.Sprite).spriteFrame = spriteFrame;
}
//加载网络图片
function loadImgByUrl(imgNode, remoteUrl, imageType) {
    if (!imageType) {
        imageType = “png”;
    }
    cc.loader.load({
        url: remoteUrl,
        type: imageType
    }, function (err, texture) {
        if (err) {
            return;
        }
        setImg(imgNode, new cc.SpriteFrame(texture));
    });
}
//加载手机本地图片
function loadLocal(imgNode, absolutePath) {
    cc.loader.load(absolutePath, function (err, texture) {
        if (err) {
            return;
        }
        setImg(imgNode, new cc.SpriteFrame(texture));
    });
}


module.exports = {
    loadImgByUrl: loadImgByUrl,
    loadLocal: loadLocal,
    setImg: setImg,
};