叶文轩

叶文轩 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

不要重复造轮子

个人动态

叶文轩 收藏了文章 · 2019-10-09

MySQL大表优化方案

当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化:

单表优化

除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑、部署、运维的各种复杂度,一般以整型值为主的表在千万级以下,字符串为主的表在五百万以下是没有太大问题的。而事实上很多时候MySQL单表的性能依然有不少优化空间,甚至能正常支撑千万级以上的数据量:

字段

  • 尽量使用TINYINTSMALLINTMEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED

  • VARCHAR的长度只分配真正需要的空间

  • 使用枚举或整数代替字符串类型

  • 尽量使用TIMESTAMP而非DATETIME

  • 单表不要有太多字段,建议在20以内

  • 避免使用NULL字段,很难查询优化且占用额外索引空间

  • 用整型来存IP

索引

  • 索引并不是越多越好,要根据查询有针对性的创建,考虑在WHEREORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描

  • 应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描

  • 值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段

  • 字符字段只建前缀索引

  • 字符字段最好不要做主键

  • 不用外键,由程序保证约束

  • 尽量不用UNIQUE,由程序保证约束

  • 使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引

查询SQL

  • 可通过开启慢查询日志来找出较慢的SQL

  • 不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边

  • sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库

  • 不用SELECT *

  • OR改写成INOR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内

  • 不用函数和触发器,在应用程序实现

  • 避免%xxx式查询

  • 少用JOIN

  • 使用同类型进行比较,比如用'123''123'比,123123

  • 尽量避免在WHERE子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描

  • 对于连续数值,使用BETWEEN不用INSELECT id FROM t WHERE num BETWEEN 1 AND 5

  • 列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大

引擎

目前广泛使用的是MyISAM和InnoDB两种引擎:

MyISAM

MyISAM引擎是MySQL 5.1及之前版本的默认引擎,它的特点是:

  • 不支持行锁,读取时对需要读到的所有表加锁,写入时则对表加排它锁

  • 不支持事务

  • 不支持外键

  • 不支持崩溃后的安全恢复

  • 在表有读取查询的同时,支持往表中插入新纪录

  • 支持BLOBTEXT的前500个字符索引,支持全文索引

  • 支持延迟更新索引,极大提升写入性能

  • 对于不会进行修改的表,支持压缩表,极大减少磁盘空间占用

InnoDB

InnoDB在MySQL 5.5后成为默认索引,它的特点是:

  • 支持行锁,采用MVCC来支持高并发

  • 支持事务

  • 支持外键

  • 支持崩溃后的安全恢复

  • 不支持全文索引

总体来讲,MyISAM适合SELECT密集型的表,而InnoDB适合INSERTUPDATE密集型的表

系统调优参数

可以使用下面几个工具来做基准测试:

  • sysbench:一个模块化,跨平台以及多线程的性能测试工具

  • iibench-mysql:基于 Java 的 MySQL/Percona/MariaDB 索引进行插入性能测试工具

  • tpcc-mysql:Percona开发的TPC-C测试工具

具体的调优参数内容较多,具体可参考官方文档,这里介绍一些比较重要的参数:

  • back_log:back_log值指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中。也就是说,如果MySql的连接数据达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源。可以从默认的50升至500

  • wait_timeout:数据库连接闲置时间,闲置连接会占用内存资源。可以从默认的8小时减到半小时

  • max_user_connection: 最大连接数,默认为0无上限,最好设一个合理上限

  • thread_concurrency:并发线程数,设为CPU核数的两倍

  • skip_name_resolve:禁止对外部连接进行DNS解析,消除DNS解析时间,但需要所有远程主机用IP访问

  • key_buffer_size:索引块的缓存大小,增加会提升索引处理速度,对MyISAM表性能影响最大。对于内存4G左右,可设为256M或384M,通过查询show status like 'key_read%',保证key_reads / key_read_requests在0.1%以下最好

  • innodb_buffer_pool_size:缓存数据块和索引块,对InnoDB表性能影响最大。通过查询show status like 'Innodb_buffer_pool_read%',保证 (Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests越高越好

  • innodb_additional_mem_pool_size:InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小,当数据库对象非常多的时候,适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率,当过小的时候,MySQL会记录Warning信息到数据库的错误日志中,这时就需要该调整这个参数大小

  • innodb_log_buffer_size:InnoDB存储引擎的事务日志所使用的缓冲区,一般来说不建议超过32MB

  • query_cache_size:缓存MySQL中的ResultSet,也就是一条SQL语句执行的结果集,所以仅仅只能针对select语句。当某个表的数据有任何任何变化,都会导致所有引用了该表的select语句在Query Cache中的缓存数据失效。所以,当我们的数据变化非常频繁的情况下,使用Query Cache可能会得不偿失。根据命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))进行调整,一般不建议太大,256MB可能已经差不多了,大型的配置型静态数据可适当调大.
    可以通过命令show status like 'Qcache_%'查看目前系统Query catch使用大小

  • read_buffer_size:MySql读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySql会为它分配一段内存缓冲区。如果对表的顺序扫描请求非常频繁,可以通过增加该变量值以及内存缓冲区大小提高其性能

  • sort_buffer_size:MySql执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能,可以尝试增加sort_buffer_size变量的大小

  • read_rnd_buffer_size:MySql的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySql会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySql会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。

  • record_buffer:每个进行一个顺序扫描的线程为其扫描的每张表分配这个大小的一个缓冲区。如果你做很多顺序扫描,可能想要增加该值

  • thread_cache_size:保存当前没有与连接关联但是准备为后面新的连接服务的线程,可以快速响应连接的线程请求而无需创建新的

  • table_cache:类似于thread_cache_size,但用来缓存表文件,对InnoDB效果不大,主要用于MyISAM

升级硬件

Scale up,这个不多说了,根据MySQL是CPU密集型还是I/O密集型,通过提升CPU和内存、使用SSD,都能显著提升MySQL性能

读写分离

也是目前常用的优化,从库读主库写,一般不要采用双主或多主引入很多复杂性,尽量采用文中的其他方案来提高性能。同时目前很多拆分的解决方案同时也兼顾考虑了读写分离

缓存

缓存可以发生在这些层次:

  • MySQL内部:在系统调优参数介绍了相关设置

  • 数据访问层:比如MyBatis针对SQL语句做缓存,而Hibernate可以精确到单个记录,这里缓存的对象主要是持久化对象Persistence Object

  • 应用服务层:这里可以通过编程手段对缓存做到更精准的控制和更多的实现策略,这里缓存的对象是数据传输对象Data Transfer Object

  • Web层:针对web页面做缓存

  • 浏览器客户端:用户端的缓存

可以根据实际情况在一个层次或多个层次结合加入缓存。这里重点介绍下服务层的缓存实现,目前主要有两种方式:

  • 直写式(Write Through):在数据写入数据库后,同时更新缓存,维持数据库与缓存的一致性。这也是当前大多数应用缓存框架如Spring Cache的工作方式。这种实现非常简单,同步好,但效率一般。

  • 回写式(Write Back):当有数据要写入数据库时,只会更新缓存,然后异步批量的将缓存数据同步到数据库上。这种实现比较复杂,需要较多的应用逻辑,同时可能会产生数据库与缓存的不同步,但效率非常高。

表分区

MySQL在5.1版引入的分区是一种简单的水平拆分,用户需要在建表的时候加上分区参数,对应用是透明的无需修改代码

对用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表组成,实现分区的代码实际上是通过对一组底层表的对象封装,但对SQL层来说是一个完全封装底层的黑盒子。MySQL实现分区的方式也意味着索引也是按照分区的子表定义,没有全局索引

Alt text

用户的SQL语句是需要针对分区表做优化,SQL条件中要带上分区条件的列,从而使查询定位到少量的分区上,否则就会扫描全部分区,可以通过EXPLAIN PARTITIONS来查看某条SQL语句会落在那些分区上,从而进行SQL优化,如下图5条记录落在两个分区上:

mysql> explain partitions select count(1) from user_partition where id in (1,2,3,4,5);
+----+-------------+----------------+------------+-------+---------------+---------+---------+------+------+--------------------------+
| id | select_type | table          | partitions | type  | possible_keys | key     | key_len | ref  | rows | Extra                    |
+----+-------------+----------------+------------+-------+---------------+---------+---------+------+------+--------------------------+
|  1 | SIMPLE      | user_partition | p1,p4      | range | PRIMARY       | PRIMARY | 8       | NULL |    5 | Using where; Using index |
+----+-------------+----------------+------------+-------+---------------+---------+---------+------+------+--------------------------+
1 row in set (0.00 sec)

分区的好处是:

  • 可以让单表存储更多的数据

  • 分区表的数据更容易维护,可以通过清楚整个分区批量删除大量数据,也可以增加新的分区来支持新插入的数据。另外,还可以对一个独立分区进行优化、检查、修复等操作

  • 部分查询能够从查询条件确定只落在少数分区上,速度会很快

  • 分区表的数据还可以分布在不同的物理设备上,从而搞笑利用多个硬件设备

  • 可以使用分区表赖避免某些特殊瓶颈,例如InnoDB单个索引的互斥访问、ext3文件系统的inode锁竞争

  • 可以备份和恢复单个分区

分区的限制和缺点:

  • 一个表最多只能有1024个分区

  • 如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来

  • 分区表无法使用外键约束

  • NULL值会使分区过滤无效

  • 所有分区必须使用相同的存储引擎

分区的类型:

  • RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区

  • LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择

  • HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL中有效的、产生非负整数值的任何表达式

  • KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值

分区适合的场景有:

  • 最适合的场景数据的时间序列性比较强,则可以按时间来分区,如下所示:

CREATE TABLE members (
    firstname VARCHAR(25) NOT NULL,
    lastname VARCHAR(25) NOT NULL,
    username VARCHAR(16) NOT NULL,
    email VARCHAR(35),
    joined DATE NOT NULL
)
PARTITION BY RANGE( YEAR(joined) ) (
    PARTITION p0 VALUES LESS THAN (1960),
    PARTITION p1 VALUES LESS THAN (1970),
    PARTITION p2 VALUES LESS THAN (1980),
    PARTITION p3 VALUES LESS THAN (1990),
    PARTITION p4 VALUES LESS THAN MAXVALUE
);

查询时加上时间范围条件效率会非常高,同时对于不需要的历史数据能很容的批量删除。

  • 如果数据有明显的热点,而且除了这部分数据,其他数据很少被访问到,那么可以将热点数据单独放在一个分区,让这个分区的数据能够有机会都缓存在内存中,查询时只访问一个很小的分区表,能够有效使用索引和缓存

另外MySQL有一种早期的简单的分区实现 - 合并表(merge table),限制较多且缺乏优化,不建议使用,应该用新的分区机制来替代

垂直拆分

垂直分库是根据数据库里面的数据表的相关性进行拆分,比如:一个数据库里面既存在用户数据,又存在订单数据,那么垂直拆分可以把用户数据放到用户库、把订单数据放到订单库。垂直分表是对数据表进行垂直拆分的一种方式,常见的是把一个多字段的大表按常用字段和非常用字段进行拆分,每个表里面的数据记录数一般情况下是相同的,只是字段不一样,使用主键关联

比如原始的用户表是:

Alt text

垂直拆分后是:

Alt text

垂直拆分的优点是:

  • 可以使得行数据变小,一个数据块(Block)就能存放更多的数据,在查询时就会减少I/O次数(每次查询时读取的Block 就少)

  • 可以达到最大化利用Cache的目的,具体在垂直拆分的时候可以将不常变的字段放一起,将经常改变的放一起

  • 数据维护简单

缺点是:

  • 主键出现冗余,需要管理冗余列

  • 会引起表连接JOIN操作(增加CPU开销)可以通过在业务服务器上进行join来减少数据库压力

  • 依然存在单表数据量过大的问题(需要水平拆分)

  • 事务处理复杂

水平拆分

概述

水平拆分是通过某种策略将数据分片来存储,分库内分表和分库两部分,每片数据会分散到不同的MySQL表或库,达到分布式的效果,能够支持非常大的数据量。前面的表分区本质上也是一种特殊的库内分表

库内分表,仅仅是单纯的解决了单一表数据过大的问题,由于没有把表的数据分布到不同的机器上,因此对于减轻MySQL服务器的压力来说,并没有太大的作用,大家还是竞争同一个物理机上的IO、CPU、网络,这个就要通过分库来解决

前面垂直拆分的用户表如果进行水平拆分,结果是:

Alt text

实际情况中往往会是垂直拆分和水平拆分的结合,即将Users_A_MUsers_N_Z再拆成UsersUserExtras,这样一共四张表

水平拆分的优点是:

  • 不存在单库大数据和高并发的性能瓶颈

  • 应用端改造较少

  • 提高了系统的稳定性和负载能力

缺点是:

  • 分片事务一致性难以解决

  • 跨节点Join性能差,逻辑复杂

  • 数据多次扩展难度跟维护量极大

分片原则

  • 能不分就不分,参考单表优化

  • 分片数量尽量少,分片尽量均匀分布在多个数据结点上,因为一个查询SQL跨分片越多,则总体性能越差,虽然要好于所有数据在一个分片的结果,只在必要的时候进行扩容,增加分片数量

  • 分片规则需要慎重选择做好提前规划,分片规则的选择,需要考虑数据的增长模式,数据的访问模式,分片关联性问题,以及分片扩容问题,最近的分片策略为范围分片,枚举分片,一致性Hash分片,这几种分片都有利于扩容

  • 尽量不要在一个事务中的SQL跨越多个分片,分布式事务一直是个不好处理的问题

  • 查询条件尽量优化,尽量避免Select * 的方式,大量数据结果集下,会消耗大量带宽和CPU资源,查询尽量避免返回大量结果集,并且尽量为频繁使用的查询语句建立索引。

  • 通过数据冗余和表分区赖降低跨库Join的可能

这里特别强调一下分片规则的选择问题,如果某个表的数据有明显的时间特征,比如订单、交易记录等,则他们通常比较合适用时间范围分片,因为具有时效性的数据,我们往往关注其近期的数据,查询条件中往往带有时间字段进行过滤,比较好的方案是,当前活跃的数据,采用跨度比较短的时间段进行分片,而历史性的数据,则采用比较长的跨度存储。

总体上来说,分片的选择是取决于最频繁的查询SQL的条件,因为不带任何Where语句的查询SQL,会遍历所有的分片,性能相对最差,因此这种SQL越多,对系统的影响越大,所以我们要尽量避免这种SQL的产生。

解决方案

由于水平拆分牵涉的逻辑比较复杂,当前也有了不少比较成熟的解决方案。这些方案分为两大类:客户端架构和代理架构。

客户端架构

通过修改数据访问层,如JDBC、Data Source、MyBatis,通过配置来管理多个数据源,直连数据库,并在模块内完成数据的分片整合,一般以Jar包的方式呈现

这是一个客户端架构的例子:

Alt text

可以看到分片的实现是和应用服务器在一起的,通过修改Spring JDBC层来实现

客户端架构的优点是:

  • 应用直连数据库,降低外围系统依赖所带来的宕机风险

  • 集成成本低,无需额外运维的组件

缺点是:

  • 限于只能在数据库访问层上做文章,扩展性一般,对于比较复杂的系统可能会力不从心

  • 将分片逻辑的压力放在应用服务器上,造成额外风险

代理架构

通过独立的中间件来统一管理所有数据源和数据分片整合,后端数据库集群对前端应用程序透明,需要独立部署和运维代理组件

这是一个代理架构的例子:

Alt text

代理组件为了分流和防止单点,一般以集群形式存在,同时可能需要Zookeeper之类的服务组件来管理

代理架构的优点是:

  • 能够处理非常复杂的需求,不受数据库访问层原来实现的限制,扩展性强

  • 对于应用服务器透明且没有增加任何额外负载

缺点是:

  • 需部署和运维独立的代理中间件,成本高

  • 应用需经过代理来连接数据库,网络上多了一跳,性能有损失且有额外风险

各方案比较
出品方架构模型支持数据库分库分表读写分离外部依赖是否开源实现语言支持语言最后更新Github星数
MySQL FabricMySQL官方代理架构MySQLpython无限制4个月前35
Cobar阿里巴巴代理架构MySQLJava无限制两年前1287
Cobar Client阿里巴巴客户端架构MySQLJavaJava三年前344
TDDL淘宝客户端架构无限制Diamond只开源部分JavaJava未知519
Atlas奇虎360代理架构MySQLC无限制10个月前1941
Heisenberg百度熊照代理架构MySQLJava无限制2个月前197
TribeDB个人代理架构MySQLNodeJS无限制3个月前126
ShardingJDBC当当客户端架构MySQLJavaJava当天1144
Shark个人客户端架构MySQLJavaJava两天前84
KingShard个人代理架构MySQLGolang无限制两天前1836
OneProxy平民软件代理架构MySQL未知无限制未知未知
MyCat社区代理架构MySQLJava无限制两天前1270
VitessYoutube代理架构MySQLGolang无限制当天3636
Mixer个人代理架构MySQLGolang无限制9个月前472
JetPantsTumblr客户端架构MySQLRubyRuby10个月前957
HibernateShardHibernate客户端架构无限制JavaJava4年前57
MybatisShardMakerSoft客户端架构无限制JavaJava11个月前119
GizzardTwitter代理架构无限制Java无限制3年前2087

如此多的方案,如何进行选择?可以按以下思路来考虑:

  1. 确定是使用代理架构还是客户端架构。中小型规模或是比较简单的场景倾向于选择客户端架构,复杂场景或大规模系统倾向选择代理架构

  2. 具体功能是否满足,比如需要跨节点ORDER BY,那么支持该功能的优先考虑

  3. 不考虑一年内没有更新的产品,说明开发停滞,甚至无人维护和技术支持

  4. 最好按大公司->社区->小公司->个人这样的出品方顺序来选择

  5. 选择口碑较好的,比如github星数、使用者数量质量和使用者反馈

  6. 开源的优先,往往项目有特殊需求可能需要改动源代码

按照上述思路,推荐以下选择:

  • 客户端架构:ShardingJDBC

  • 代理架构:MyCat或者Atlas

兼容MySQL且可水平扩展的数据库

目前也有一些开源数据库兼容MySQL协议,如:

但其工业品质和MySQL尚有差距,且需要较大的运维投入,如果想将原始的MySQL迁移到可水平扩展的新数据库中,可以考虑一些云数据库:

NoSQL

在MySQL上做Sharding是一种戴着镣铐的跳舞,事实上很多大表本身对MySQL这种RDBMS的需求并不大,并不要求ACID,可以考虑将这些表迁移到NoSQL,彻底解决水平扩展问题,例如:

  • 日志类、监控类、统计类数据

  • 非结构化或弱结构化数据

  • 对事务要求不强,且无太多关联操作的数据

查看原文

叶文轩 收藏了文章 · 2019-04-25

关于Hexo6.0搭建个人博客(进阶篇)

本篇博文将带大家发现新大陆,教你打造炫酷的个人博客站点.

阅读本文前建议先行阅读本人另外一遍基础博文关于Hexo搭建个人博客(基础篇)

目录

  • 配置博客基本信息
  • 配置主题
  • 优化主题

    • 上传头像,并设置头像旋转效果
    • 设置个人社交图标链接
    • 设置RSS
    • 设置酷炫动态背景
    • 设置主题语言
    • 设置网站logo
    • 设置左上角或者右上角的fork me on github效果
    • 设置顶部滚动加载条
    • 自定义博客底部显示效果
    • 为首页文章添加阴影边框效果
    • 为首页添加自定义菜单栏标签

配置博客基本信息

在站点根目录下_config.yml中进行基础配置
建议下载个文本编辑器打开,这里推荐Sublime Text,

set1.png

对应显示效果(显示效果因主题不同而不同,只做描述)
show.png

show1.png

当然了,你们的主题和我的肯定是不一样的,所以下面就开始教大家挑选自己喜欢的主题
并自定义个人喜好.

配置主题

挑选主题

你可以点击这里选择你喜欢的Themes,里面有大量美观的主题

thems.png

主题很多,可以慢慢挑选,挑选好了,直接clone下来就好了.

我这里以简约著称的Next主题为例讲解,本人用的也是这款主题,当然喜欢炫酷一点的小伙伴们可以自行选择,只是优化会略有不同.
next.png

这里有下载主题有两种方式:
1.通过get 命令方式直接安装
2.直接把整个仓库clone到本地,再移动到你的站点目录Themes下

这里主要教大家用命令行方式

$ cd hexo
$ git clone https://github.com/theme-next... themes/next

进入hexo根目录下,GitBash

clone.png

clone成功后,你的Themes文件夹下就会有next主题文件了
nextfile.png

首先介绍下next文件夹下_config.yml文件,以后我们凡是修改主题,都是修改这个文件

然后下一步,我们先应用我们刚才clone下来的主题,看看什么效果

打开blog(你的博客站点跟目录)下的_config.yml文件进行设置:
theme: landscape 修改为 theme: next
config_next.png

然后hexo g hexo s 一下 ,我们来看一下效果:
nextshow.png

好了 ,没错就是这个样子(的确是丑了点,所以需要你自己去优化成你自己想要的样子啦)

当然你也可以直接clone我配置好了的主题darryrzhong-next.顺便star一下最好啦.

优化主题

到现在我们基础博客算是搭建完成了,下面手把手教大家美化主题,赶快上车吧

带大家优化主题之前,先介绍下next目录结构,了解目录结构能帮助我们更快的优化

.
├── .deploy
├── public
├── scaffolds
├── scripts
├── source
|   ├── _drafts
|   └── _posts
├── themes
├── _config.yml
└── package.json

最新的next已经把我们需要的大部分功能都已经集成了,所以我们需要修改的文件只有那么几个

  • deploy:执行hexo deploy命令部署到GitHub上的内容目录
  • public:执行hexo generate命令,输出的静态网页内容目录
  • source:站点资源目录,你写的文章,需要的素材等等都是放在这个目录下,包括以后

你需要新建菜单标面目录也是放在这里.

  • drafts:草稿文章
  • posts:成功发布的文章目录(你发布的文章都在这里面可以看到 )
  • themes:主题文件目录
  • _config.yml:全局配置文件,大多数的设置都在这里

常用的需要了解的就是这些目录了,其中高亮的地方就是整个next的核心部分了

1. 上传头像,并设置头像旋转效果

设置头像:
打开themes/next/_config.yml找到avatar: /images/avatar.gif;
其中images文件在themes/next\source\中,将你的头像图片放到images中,一般默认
命名为avatar,记得改下后缀就可以了.
设置旋转效果:
打开themes\next\source\css\_common\components\sidebar\sidebar-author.styl,
添加以下注释代码

.site-author-image {
  display: block;
  margin: 0 auto;
  padding: $site-author-image-padding;
  max-width: $site-author-image-width;
  height: $site-author-image-height;
  border: $site-author-image-border-width solid $site-author-image-border-color;

   /* 头像圆形 */
  border-radius: 80px;
  -webkit-border-radius: 80px;
  -moz-border-radius: 80px;
  box-shadow: inset 0 -1px 0 #333sf;

  /* 鼠标经过头像旋转时间 */
  -webkit-transition: -webkit-transform 1.0s ease-out;
  -moz-transition: -moz-transform 1.0s ease-out;
  transition: transform 1.0s ease-out;


}

 img:hover {
  /* 鼠标经过停止头像旋转 
  -webkit-animation-play-state:paused;
  animation-play-state:paused;*/

  /* 鼠标经过头像旋转360度 */
  -webkit-transform: rotateZ(360deg);
  -moz-transform: rotateZ(360deg);
  transform: rotateZ(360deg);
}

做完这一步,头像也就设置完成了.
avatar1.png

2.设置个人社交图标链接

social.png

打开themes/next/_config.yml,找到social;
social1.png

其中name为你图标的名字,你可以去Font Awesome中挑选你喜欢的,然后复制名字就可以了
如 icon-user-md 只需要user-md就可以了,next里面已经集成好了.如果next找不到图标的话,图标就会被一个问号所取代.

3.设置RSS

rss.png

来到你的站点根目录(比如我的blog/)下;
执行 GitBash 命令 : $ npm install --save hexo-generator-feed安装插件,插件会放在
node_modules文件夹里面.

安装好插件后,打开全局配置文件_config.yml,在末尾加入以下代码:

# Extensions
## Plugins: http://hexo.io/plugins/
plugins: hexo-generate-feed

然后打开主题配置文件_config.yml,找到rss;
添加配置:rss: /atom.xml ;
到这里就行了,重新运行一下 hexo g ,hexo s ,你就会看到效果了.

4.设置酷炫动态背景

background.png

新版本的next已经支持canvas-nest了,所以直接添加代码就可以了
打开next/layout/_layout.swig文件在</body>之前加上以下代码;

<script type="text/javascript" data-original="//cdn.bootcss.com/canvas-nest.js/1.0.0/canvas-nest.min.js"></script>
{% endif %}

打开主题配置文件_config.yml, 修改以下代码;

# Canvas-nest
# Dependencies: https://github.com/theme-next/theme-next-canvas-nest
canvas_nest: true

重新运行下就可以了,

5.设置主题语言

你的博客首页显示的默认是英文名,而我们想要有一个中文的名字的话,就需要设置下语言显示;
我们可以在nextlanguage文件下的zh-CN(中文)语言包下增加相应的字段,还可以修改其他的字段;

title:
  archive: 归档
  category: 分类
  tag: 标签
  schedule: 日程表
menu:
  home: 首页
  archives: 归档
  categories: 分类
  tags: 标签
  about: 关于
  search: 搜索
  schedule: 日程表
  sitemap: 站点地图
  commonweal: 公益 404
sidebar:
  overview: 站点概览

改成我们自己想要的显示效果后.在全局配置文件_config.yml中重新配置,
language: zh-CN ,重新运行下服务器记得,只要是全局效果,都需要重新clean一下,在重新运行下,否则看不到效果.

6.设置网站logo

效果如下:
logo.png
这个小图标就是你的logo了.
打开主题配置文件_config.yml ,找到字段favicon:;

favicon:
  small: /images/favicon-16x16-next.png
  medium: /images/favicon-32x32-next.png
  apple_touch_icon: /images/apple-touch-icon-next.png
  safari_pinned_tab: /images/logo.svg

可以看到有四种效果,一般我们只需将medium换成我们自己图标路径就行了.
对于图标大小也是有要求的,看实例就知道了,就不做过多的说明了.

7.设置左上角或者右上角的fork me on github效果

显示效果如下:
fork me github.png
实现方法如下:
当然有很多种颜色和效果,你可以在这里选择你需要的效果;然后复制你选择效果右侧的代码;

<a href="https://github.com/you"><img style="position: absolute; top: 0; left: 0; border: 0;" data-original="https://s3.amazonaws.com/github/ribbons/forkme_left_red_aa0000.png" alt="Fork me on GitHub"></a>

打开next\layout\_layout.swig,将刚才复制的代码黏贴到<div class="headband"></div>的下面;

github.png

重新运行下,看看效果吧;

8. 设置顶部滚动加载条

效果如下:
bar.png

打开next\layout\_partials\head文件,添加以下代码:

<script data-original="//cdn.bootcss.com/pace/1.0.2/pace.min.js"></script>
<link href="//cdn.bootcss.com/pace/1.0.2/themes/pink/pace-theme-flash.css" rel="stylesheet">

barset.png
以上加载条就可以正常显示了,默认颜色是粉色的,本人觉得粉色的也挺好看就没改了,
当然你可以自定义你想要的颜色,做法如下:
在刚才添加的代码后面再加上以下代码:

<style>
    .pace .pace-progress {
        background: #1E92FB; /*进度条颜色*/
        height: 3px;
    }
    .pace .pace-progress-inner {
         box-shadow: 0 0 10px #1E92FB, 0 0 5px     #1E92FB; /*阴影颜色*/
    }
    .pace .pace-activity {
        border-top-color: #1E92FB;    /*上边框颜色*/
        border-left-color: #1E92FB;    /*左边框颜色*/
    }
</style>

自定义加载条就大功改成了;

9.自定义博客底部显示效果

bottom.png

具体实现如下:
1.隐藏网页底部powered By Hexo / 强力驱动
打开themes/next/layout/_partials/footer.swig,直接隐藏以下代码即可,建议不要删除
代码如下:

<!--{% if theme.footer.powered.enable %}
  <div class="powered-by">{#
  #}{{ __('footer.powered', '<a class="theme-link" target="_blank"' + nofollow + ' href="https://hexo.io">Hexo</a>') }}{% if theme.footer.powered.version %} v{{ hexo_env('version') }}{% endif %}{#
#}</div>
{% endif %}

{% if theme.footer.powered.enable and theme.footer.theme.enable %}
  <span class="post-meta-divider">|</span>
{% endif %}

{% if theme.footer.theme.enable %}
 <div class="theme-info">{#
  #}{{ __('footer.theme') }} &mdash; {#
  #}<a class="theme-link" target="_blank"{{ nofollow }} href="https://github.com/theme-next/hexo-theme-next">{#
    #}NexT.{{ theme.scheme }}{#
  #}</a>{% if theme.footer.theme.version %} v{{ version }}{% endif %}{#
#}</div>
{% endif %}-->

2.修改网页底部的桃心

还是在themes/next/layout/_partials/footer.swig中,找到字段with-love;

<span class="with-love" id="animate">
    <i class="fa fa-{{ theme.footer.icon.name }}"></i>
  </span>

然后在图标库中找到你想要的图标,修改如下:

<span class="with-love" id="animate">
    <i class="fa fa-heart"></i>
  </span>

其中heart为图标名字,只要icon-后面的就行了;

3.实现网站底部访问量显示
打开主题配置文件 _config.yml ,
修改如下代码

busuanzi_count:
  enable: true
  total_visitors: true
  total_visitors_icon: user 
  total_views: true
  total_views_icon: eye
  post_views: true
  post_views_icon: eye

10.为首页文章添加阴影边框效果

打开next\source\css\_custom\custom.styl文件,添加以下代码:

// 主页文章添加阴影效果
 .post {
   margin-top: 60px;
   margin-bottom: 60px;
   padding: 25px;
   -webkit-box-shadow: 0 0 5px rgba(202, 203, 203, .5);
   -moz-box-shadow: 0 0 5px rgba(202, 203, 204, .5);
  }

重新运行下服务器,效果如下:
shandown.png

11.为首页添加自定义菜单栏标签

效果如下:
menu.png

实现如下:
进入站点根目录下,使用GitBash新建页面;

$ hexo new page "music"
$ hexo new page "photo"
$ hexo new page "welfare"

这里我新建三个菜单标签,分别是音乐、摄影、福利.
newPage.png

可以看到,新建页面在source文件对于的文件夹里;

打开主题配置文件_config.yml找到字段menu,添加对应菜单;
menu1.png

添加完菜单标签项后,效果如下:
menu2.png

可以看到标签名还是英文的,并且pohto项是空白的,之前已经说过,空白代表这个图标找不到,所以换一个就行了,下面我们来完善一下,将标签名改为中文;

打开next\language文件下的zh-CN(中文)语言包,添加菜单标签项;

menu:
  home: 首页
  photo: 摄影
  music: 音乐
  welfare: 福利
  archives: 归档
  categories: 分类

menu3.png

名称一定要和我们主题中的名称一致,到这里我们的菜单栏也就完成了.

到这里,关于博客的基础搭建也就全部做好了

本文只要介绍了博客外观美化的一些小技巧,使得我们的博客看起来更加的美观,关于博客内文章的美观及评论留言区等的优化、以及使用markdown写下第一篇博文等将会在下一篇高级篇手把手教大家,欢迎关注作者darryrzhong,更多干货等你来拿哟.

请赏个小红心!因为你的鼓励是我写作的最大动力!

更多精彩文章请关注
查看原文

叶文轩 关注了用户 · 2019-02-21

布客飞龙 @wizardforcel

欢迎来星球做客:t.zsxq.com/Jq3vZZB

请关注我们的公众号“ApacheCN”,回复“教程/路线/比赛/报告/技术书/轻小说/漫画/新知”来获取更多资源。

关注 801

叶文轩 收藏了文章 · 2019-01-17

计算机开放电子书汇总

开放书是指协议为Public Domain、Creative Common,以及一些开源软件协议(MIT、Apache、GPL等等)的图书。我在平时逛论坛或者刷github时,看到好的开放电子书时会将链接存到博客的某个页面上。但是链接一多起来,查找就特别不方便,于是就单独做了这样一个网站。

目前这些书主要来源于github、gitbook、极客学院wiki和kancloud。一些我认为比较精品又不提供下载连接的,会手动托管到gitbook上。这些书绝大部分是技术书,讲述计科知识的书还是比较少的,下一步的计划是寻找国外高校公开的讲义来填补。

网站:http://it-ebooks.flygon.net/
源码:https://github.com/it-ebooks/blog

归档(生成于2016.1.28):

查看原文

叶文轩 关注了专栏 · 2019-01-17

龙哥盟

龙吟九天,一曲焚尘,苍山听雪,归时余几人。

关注 885

叶文轩 收藏了文章 · 2019-01-14

PHP常用180函数总结

数学函数

1.abs(): 求绝对值

<span style="font-size: 14px;">$abs = abs(-4.2); //4.2<br></span>

输入: 数字
输出: 绝对值数字

2.ceil(): 进一法取整

<span style="font-size: 14px;">echo ceil(9.999); // 10<br></span>

输出: 浮点数进一取整

3.floor(): 去尾法取整

<span style="font-size: 14px;">    echo floor(9.999);  // 9<br></span>

输出: 浮点数直接舍去小数部分

4.fmod(): 浮点数取余

<span style="font-size: 14px;">    $x = 5.7;    $y = 1.3; // 两个浮点数,x>y 浮点余数<br>    $r = fmod($x, $y);    // $r equals 0.5, because 4 * 1.3 + 0.5 = 5.7<br></span>

5.pow(): 返回数的n次方

<span style="font-size: 14px;">    echo pow(-1, 20); // 1 基础数|n次方乘方值<br></span>

6.round(): 浮点数四舍五入

<span style="font-size: 14px;"> echo round(1.95583, 2); // 1.96, 一个数值|保留小数点后多少位,默认为0 舍入后的结果
</span>

7.sqrt(): 求平方根

<span style="font-size: 14px;">    echo sqrt(9); //3 被开方的数平方根<br></span>

8.max(): 求最大值

<span style="font-size: 14px;">    echo max(1, 3, 5, 6, 7); // 7<br></span>

多个数字或数组
返回其中的最大值

<span style="font-size: 14px;"> echo max(array(2, 4, 5)); // 5
</span>

9.min(): 求最小值

输入: 多个数字或数组

输出: 返回其中的最小值

10.mt_rand(): 更好的随机数

输入: 最小|最大,
输出: 随机数随机返回范围内的值

<span style="font-size: 14px;">    echo mt_rand(0,9);//n<br></span>

11.rand(): 随机数
输入: 最小|最大,
输出: 随机数随机返回范围内的值

12.pi(): 获取圆周率值

字符串函数
去空格或或其他字符:
13.trim(): 删除字符串两端的空格或其他预定义字符

<span style="font-size: 14px;">    $str = "\r\nHello World!\r\n";    echo trim($str);<br></span>

输入: 目标字符串
返回值: 清除后的字符串

14.rtrim(): 删除字符串右边的空格或其他预定义字符

<span style="font-size: 14px;">    $str = "Hello World!\n\n";    echo rtrim($str);<br></span>

15.chop(): rtrim()的别名

16.ltrim(): 删除字符串左边的空格或其他预定义字符

<span style="font-size: 14px;">    $str = "\r\nHello World!";    echo ltrim($str);<br></span>

17.dirname(): 返回路径中的目录部分

<span style="font-size: 14px;">    echo dirname("c:/testweb/home.php");  //c:/testweb<br></span>

输入: 一个包含路径的字符串
返回值: 返回文件路径的目录部分

字符串生成与转化:  
18.str_pad(): 把字符串填充为指定的长度

<span style="font-size: 14px;">    $str = "Hello World";    echo str_pad($str,20,".");<br></span>

输入:
要填充的字符串|新字符串的长度|供填充使用的字符串, 默认是空白

输出:
完成后的字符串

19.str_repeat(): 重复使用指定字符串

<span style="font-size: 14px;">    echo str_repeat(".",13); // 要重复的字符串|字符串将被重复的次数13个点<br></span>

20.str_split(): 把字符串分割到数组中

<span style="font-size: 14px;">print_r(str_split("Hello"));<br></span>

输入: 要分割的字符串|每个数组元素的长度,默认1

输出: 拆分后的字符串数组

21.strrev(): 反转字符串

<span style="font-size: 14px;">    echo strrev("Hello World!"); // !dlroW olleH<br></span>

输出: 目标字符串颠倒顺序后的字符串

22.wordwrap(): 按照指定长度对字符串进行折行处理

<span style="font-size: 14px;">    $str = "An example on a long word is: Supercalifragulistic";    echo wordwrap($str,15);<br></span>

输入: 目标字符串|最大宽数

输出: 折行后的新字符串

23.str_shuffle(): 随机地打乱字符串中所有字符

<span style="font-size: 14px;">    echo str_shuffle("Hello World");<br></span>

输入: 目标字符串顺序
输出: 打乱后的字符串

24.parse_str(): 将字符串解析成变量

<span style="font-size: 14px;">    parse_str("id=23&name=John%20Adams", $myArray);<br>    print_r($myArray);<br></span>

输入: 要解析的字符串|存储变量的数组名称

输出:

<span style="font-size: 14px;">Array(<br>[id] => 23[name] => John Adams)<br></span>

25.number_format(): 通过千位分组来格式化数字
输入:
要格式化的数字|规定多少个小数|规定用作小数点的字符串|规定用作千位分隔符的字符串

输出:

<span style="font-size: 14px;">1,000,000<br>1,000,000.00<br>1.000.000,00<br></span>

大小写转换:
26.strtolower(): 字符串转为小写

<span style="font-size: 14px;">    echo strtolower("Hello WORLD!");<br></span>

目标字符串
小写字符串

27.strtoupper(): 字符串转为大写

<span style="font-size: 14px;">    echo strtoupper("Hello WORLD!");<br></span>

输出: 大写字符串

28.ucfirst(): 字符串首字母大写

<span style="font-size: 14px;">    echo ucfirst("hello world"); // Hello world<br></span>

29.ucwords(): 字符串每个单词首字符转为大写

<span style="font-size: 14px;">    echo ucwords("hello world"); // Hello World<br></span>

html标签关联:
30.htmlentities(): 把字符转为HTML实体

<span style="font-size: 14px;">    $str = "John & 'Adams'";echo htmlentities($str, ENT_COMPAT); // John & 'Adams'<br></span>

31.htmlspecialchars(): 预定义字符转html编码

32.nl2br(): n转义为
标签

<span style="font-size: 14px;">    echo nl2br("One line.\nAnother line.");<br></span>

输出: 处理后的字符串

33.strip_tags(): 剥去 HTML、XML 以及 PHP 的标签

<span style="font-size: 14px;">    echo strip_tags("Hello <b>world!</b>");  <br></span>

34.addcslashes():在指定的字符前添加反斜线转义字符串中字符

<span style="font-size: 14px;">    $str = "Hello, my name is John Adams.";    echo $str;    echo addcslashes($str,'m');<br></span>

输入:
目标字符串|指定的特定字符或字符范围

35.stripcslashes(): 删除由addcslashes()添加的反斜线

<span style="font-size: 14px;">    echo stripcslashes("Hello, \my na\me is Kai Ji\m.");    // 目标字符串 Hello, my name is Kai Jim.<br></span>

36.addslashes(): 指定预定义字符前添加反斜线

<span style="font-size: 14px;">    $str = "Who's John Adams?";echo addslashes($str);<br></span>

输出: 把目标串中的’ ” 和null进行转义处理

37.stripslashes(): 删除由addslashes()添加的转义字符

<span style="font-size: 14px;">    echo stripslashes("Who\'s John Adams?"); // 清除转义符号Who's John Adams?<br></span>

38.quotemeta(): 在字符串中某些预定义的字符前添加反斜线

<span style="font-size: 14px;">    $str = "Hello world. (can you hear me?)";echo quotemeta($str);    // Hello world\. \(can you hear me\?\)<br></span>

39.chr(): 从指定的 ASCII 值返回字符

<span style="font-size: 14px;">    echo chr(052); // ASCII 值返回对应的字符<br></span>

40.ord(): 返回字符串第一个字符的ASCII值

<span style="font-size: 14px;">    echo ord("hello"); 字符串第一个字符的 ASCII 值<br></span>

字符串比较:
41.strcasecmp(): 不区分大小写比较两字符串

<span style="font-size: 14px;">    echo strcasecmp("Hello world!","HELLO WORLD!");<br></span>

输入:
两个目标字符串
输出:
大1|等0|小 -1

42.strcmp(): 区分大小写比较两字符串

43.strncmp(): 比较字符串前n个字符,区分大小写

调用: int strncmp ( string $str1 , string $str2 , int $len)
 
44.strncasecmp(): 比较字符串前n个字符,不区分大小写

调用: int strncasecmp ( string $str1 , string $str2 , int $len )

45.strnatcmp(): 自然顺序法比较字符串长度,区分大小写

调用: int strnatcmp ( string $str1 , string $str2 )

输入:
目标字符串 

46.strnatcasecmp(): 自然顺序法比较字符串长度, 不区分大小写

调用: int strnatcasecmp ( string $str1 , string $str2 )

字符串切割与拼接:

47.chunk_split():将字符串分成小块

调用: str chunk_split(str $body[,int $len[,str $end]])

输入:
$body目标字串, $len长度, $str插入结束符
输出:
分割后的字符串

48.strtok(): 切开字符串

调用: str strtok(str $str,str $token)

目标字符串$str,以$token为标志切割返回切割后的字符串

49.explode(): 使用一个字符串为标志分割另一个字符串

调用: array explode(str $sep,str $str[,int $limit])

输入: $sep为分割符,$str目标字符串,$limit返回数组最多包含元素数
输出: 字符串被分割后形成的数组

50.implode(): 同join,将数组值用预订字符连接成字符串

调用: string implode ( string $glue , array $pieces )

$glue默认, 用”则直接相连

51.substr(): 截取字符串

调用: string substr ( string $string , int $start [, int $length ] )

字符串查找替换:

52.str_replace(): 字符串替换操作,区分大小写

调用

mix str_replace(mix $search,mix $replace, mix $subject[,int &$num])

输入:
$search查找的字符串,$replace替换的字符串,$subject被查找字串, &$num
输出: 返回替换后的结果

53.str_ireplace() 字符串替换操作,不区分大小写

调用: mix str_ireplace ( mix $search , mix $replace , mix $subject [, int &$count ] )

输入:
$search查找的字符串,$replace替换的字符串,$subject被查找字串,&$num
输出: 返回替换后的结果

54.substr_count(): 统计一个字符串,在另一个字符串中出现次数

调用: int substr_count ( string $haystack , string $needle[, int $offset = 0 [, int $length ]] )

55.substr_replace(): 替换字符串中某串为另一个字符串

调用: mixed substr_replace ( mixed $string, string $replacement,int $start [, int $length ] )

56.similar_text(): 返回两字符串相同字符的数量

调用:

int similar_text(str $str1,str $str2) 
输入: 

两个比较的字符串

输出:
整形,相同字符数量

57.strrchr(): 返回一个字符串在另一个字符串中最后一次出现位置开始到末尾的字符串

调用: string strrchr ( string $haystack , mixed $needle )

58.strstr(): 返回一个字符串在另一个字符串中开始位置到结束的字符串

调用: string strstr ( string $str, string $needle , bool $before_needle )
  
59.strchr(): strstr()的别名,返回一个字符串在另一个字符串中首次出现的位置开始到末尾的字符串

调用: string strstr ( string $haystack , mixed $needle [, bool $before_needle = false ] )
  
60.stristr(): 返回一个字符串在另一个字符串中开始位置到结束的字符串,不区分大小写

调用:string stristr ( string $haystack , mixed $needle [, bool $before_needle = false ] )

61.strtr(): 转换字符串中的某些字符

调用: string strtr ( string $str , string $from , string $to )

62.strpos(): 寻找字符串中某字符最先出现的位置

调用: int strpos ( string $haystack , mixed $needle [, int $offset = 0 ] )

63.stripos(): 寻找字符串中某字符最先出现的位置,不区分大小写
调用: int stripos ( string $haystack , string $needle [, int $offset ] )

64.strrpos(): 寻找某字符串中某字符最后出现的位置

调用: int strrpos ( string $haystack , string $needle [, int $offset = 0 ] )

65.strripos(): 寻找某字符串中某字符最后出现的位置,不区分大小写

调用:

int strripos ( string $haystack , string $needle [, int $offset ] )

66.strspn(): 返回字符串中首次符合mask的子字符串长度
调用: int strspn ( string $str1 , string $str2 [, int $start [, int $length ]] )

67.strcspn(): 返回字符串中不符合mask的字符串的长度

调用: int strcspn ( string $str1 , string $str2 [, int $start [, int $length ]] )

输入:
$str1被查询,$str2查询字符串,$start开始查询的字符,$length是查询长度
输出:
返回从开始到第几个字符

字符串统计:

68.str_word_count(): 统计字符串含有的单词数

调用: mix str_word_count(str $str,[])

输入: 目标字符串
输出: 统计处的数量

69.strlen(): 统计字符串长度
函数原型: int strlen(str $str)

输入: 目标字符串
输出:整型长度

70.count_chars(): 统计字符串中所有字母出现次数(0..255)
调用: mixed count_chars ( string $string [, int $mode ] )

字符串编码:
71.md5(): 字符串md5编码

<span style="font-size: 14px;">$str = "Hello";<br></span>
echo md5($str);

数组函数

数组创建:

72.array(): 生成一个数组

<span style="font-size: 14px;">    $a=array("Dog","Cat","Horse");<br>    print_r($a);<br></span>

数组值或,键=>值一个数组型变量

73.array_combine(): 生成一个数组,用一个数组的值
作为键名,另一个数组值作为值

<span style="font-size: 14px;">    $a1=array("a","b","c","d");    $a2=array("Cat","Dog","Horse","Cow");<br>    print_r(array_combine($a1,$a2));<br></span>

输入参数: $a1为提供键,$a2提供值
输出: 合成后的数组

74.range(): 创建并返回一个包含指定范围的元素的数组。

<span style="font-size: 14px;">    $number = range(0,50,10);<br>    print_r ($number);<br></span>

输入: 0是最小值,50是最大值,10是步长
输出: 合成后的数组

75.compact(): 创建一个由参数所带变量组成的数组

<span style="font-size: 14px;">    $firstname = "Peter";    $lastname = "Griffin";    $age = "38";    $result = compact("firstname", "lastname",  "age");<br>    print_r($result);<br></span>

变量或数组

返回由变量名为键,变量值为值的数组,变量也可以为多维数组.会递归处理
76.array_fill(): 用给定的填充(值生成)数组

<span style="font-size: 14px;">    $a=array_fill(2,3,"Dog");<br>    print_r($a);<br></span>

2是键,3是填充的数量,’Dog’为填充内容返回完成的数组

数组合并和拆分:    
77.array_chunk(): 把一个数组分割为新的数组块

<span style="font-size: 14px;">    $a=array("a"=>"Cat","b"=>"Dog","c"=>"Horse","d"=>"Cow");<br>print_r(array_chunk($a,2));<br></span>

一个数组分割后的多维数组,规定每个新数组包含2个元素

78.array_merge(): 把两个或多个数组合并为一个数组。

<span style="font-size: 14px;">    $a1=array("a"=>"Horse","b"=>"Dog");    $a2=array("c"=>"Cow","b"=>"Cat");<br>    print_r(array_merge($a1,$a2));<br></span>

输入: 两个数组
输出: 返回完成后的数组

79.array_slice(): 在数组中根据条件取出一段值,并返回。

<span style="font-size: 14px;">    $a=array(0=>"Dog",1=>"Cat",2=>"Horse",3=>"Bird");<br>print_r(array_slice($a,1,2));<br></span>

输入: 一个数组
输出: 1为从’Cat’开始,2为返回两个元素

数组比较:

80.array_diff(): 返回两个数组的差集数组

<span style="font-size: 14px;">    $a1=array(0=>"Cat",1=>"Dog",2=>"Horse");$a2=array(3=>"Horse",4=>"Dog",5=>"Fish");<br>    print_r(array_diff($a1,$a2)); //返回'Cat'<br></span>

输入: 两个或多个数组
输出: $a1与$a2的不同之处

81.array_intersect(): 返回两个或多个数组的交集数组
输出:
返回’Dog’和’Horse’, $a1与$a2的相同之处

数组查找替换:

    
82.array_search(): 在数组中查找一个值,返回一个键,没有返回返回假

<span style="font-size: 14px;">    $a=array("a"=>"Dog","b"=>"Cat","c"=>"Horse");    echo array_search("Dog",$a);<br></span>

输入: 一个数组
输出: 成功返回键名,失败返回false

83.array_splice(): 把数组中一部分删除用其他值替代

<span style="font-size: 14px;">    $a1=array(0=>"Dog",1=>"Cat",2=>"Horse",3=>"Bird");    $a2=array(0=>"Tiger",1=>"Lion");<br>    array_splice($a1,0,2,$a2);<br>    print_r($a1);<br></span>

输入: 一个或多个数组
输出: $a1被移除的部分由$a2补全

84.array_sum(): 返回数组中所有值的总和

<span style="font-size: 14px;">    $a=array(0=>"5",1=>"15",2=>"25");    echo array_sum($a);<br></span>

输入: 一个数组
输出: 返回和

85.in_array(): 在数组中搜索给定的值,区分大小写

<span style="font-size: 14px;">    $people = array("Peter", "Joe", "Glenn", "Cleveland");    if (in_array("Glenn",$people) {    echo "Match found";<br>    }    else{    echo "Match not found";<br>    }<br></span>

输入: 需要搜索的值|数组
输出: true/false

86.array_key_exists(): 判断某个数组中是否存在指定的 key

输入: 需要搜索的键名|数组

数组引用操作:

87.key(): 返回数组内部指针当前指向元素的键名
   
88.current(): 返回数组中的当前元素(单元).
   
89.next(): 把指向当前元素的指针移动到下一个元素的位置,并返回当前元素的值
   
90.prev(): 把指向当前元素的指针移动到上一个元素的位置,并返回当前元素的值
   
91.end(): 将数组内部指针指向最后一个元素,并返回该元素的值(如果成功)
   
92.reset(): 把数组的内部指针指向第一个元素,并返回这个元素的值
   
93.list(): 用数组中的元素为一组变量赋值

<span style="font-size: 14px;">    $my_array=array("Dog","Cat","Horse");    list($a, $b, $c) = $my_array;<br></span>

输入: $a, $b, $c为需要赋值的变量
输出: 变量分别匹配数组中的值

94.array_shift(): 删除数组中的第一个元素,并返回被删除元素的值

<span style="font-size: 14px;">    $a=array("a"=>"Dog","b"=>"Cat","c"=>"Horse");    echo array_shift($a);<br>    print_r ($a);<br></span>

95.array_unshift(): 在数组开头插入一个或多个元素

<span style="font-size: 14px;">    $a=array("a"=>"Cat","b"=>"Dog");<br>    array_unshift($a,"Horse");<br>    print_r($a);<br></span>

96.array_push(): 向数组最后压入一个或多个元素

<span style="font-size: 14px;">$a=array("Dog","Cat");<br>array_push($a,"Horse","Bird");<br>print_r($a);<br></span>

输入: 目标数组|需要压入的值
返回值: 返回新的数组

97.array_pop(): 取得(删除)数组中的最后一个元素

<span style="font-size: 14px;">    $a=array("Dog","Cat","Horse");<br>    array_pop($a);<br>    print_r($a);<br></span>

输入: $a为目标数组
输出: 返回数组剩余元素

数组键值操作:

    
98.shuffle(): 将数组打乱,保留键名

<span style="font-size: 14px;">    $my_array = array("a" => "Dog", "b" => "Cat");<br>    shuffle($my_array);<br>    print_r($my_array);<br></span>

输入: 一个或多个数组
输出: 顺序打乱后的数组

99.count(): 计算数组中的单元数目或对象中的属性个数

<span style="font-size: 14px;">    $people = array("Peter", "Joe", "Glenn",    "Cleveland");    $result = count($people);    echo $result;<br></span>

输入: 数组
输出: 输出元素个数

100.array_flip(): 返回一个键值反转后的数组

<span style="font-size: 14px;">    $a=array(0=>"Dog",1=>"Cat",2=>"Horse");<br>print_r(array_flip($a));<br></span>

输出: 返回完成后的数组
101.array_keys(): 返回数组所有的键,组成一个数组

<span style="font-size: 14px;">    $a=array("a"=>"Horse","b"=>"Cat","c"=>"Dog");<br>    print_r(array_keys($a));<br></span>

输出: 返回由键名组成的数组

102.array_values(): 返回数组中所有值,组成一个数组

输出: 返回由键值组成的数组

103.array_reverse(): 返回一个元素顺序相反的数组
元素顺序相反的一个数组,键名和键值依然匹配

104.array_count_values(): 统计数组中所有的值出现的次数

<span style="font-size: 14px;">    $a=array("Cat","Dog","Horse","Dog");<br>    print_r(array_count_values($a));<br></span>

输出: 返回数组原键值为新键名,次数为新键值

105.array_rand(): 从数组中随机抽取一个或多个元素,注意是键名!!!

<span style="font-size: 14px;">    $a=array("a"=>"Dog","b"=>"Cat","c"=>"Horse");<br>    print_r(array_rand($a,1));<br></span>

$a为目标数组, 1为抽取第几个元素的键名返回第1个元素的键名b

106.each(): 返回数组中当前的键/值对并将数组指针向前移动一步
调用array each ( array &$array )

在执行 each() 之后,数组指针将停留在数组中的下一个单元或者当碰到数组结尾时停留在最后一个单元。如果要再用 each 遍历数组,必须使用 reset()。

返回值:
数组中当前指针位置的键/值对并向前移动数组指针。键值对被返回为四个单元的数组,键名为0,1,key和 value。单元 0 和 key 包含有数组单元的键名,1 和 value 包含有数据。
如果内部指针越过了数组的末端,则 each() 返回 FALSE。

107.array_unique(): 删除重复值,返回剩余数组

<span style="font-size: 14px;">    $a=array("a"=>"Cat","b"=>"Dog","c"=>"Cat");<br>    print_r(array_unique($a));<br></span>

输入: 数组
输入: 返回无重复值数组,键名不变

数组排序:

  
108.sort(): 按升序对给定数组的值排序,不保留键名

<span style="font-size: 14px;">    $my_array = array("a" => "Dog", "b" => "Cat", "c" => "Horse");<br>    sort($my_array);<br>    print_r($my_array);<br></span>

输出: true/false
 
109.rsort(): 对数组逆向排序,不保留键名  
110.asort(): 对数组排序,保持索引关系   
111.arsort(): 对数组逆向排序,保持索引关 
112.ksort(): 系按键名对数组排序 
113.krsort(): 将数组按照键逆向排序
114.natsort(): 用自然顺序算法对数组中的元素排序    
115.natcasesort(): 自然排序,不区分大小写   
  

文件系统函数

116.fopen(): 打开文件或者 URL

<span style="font-size: 14px;">    $handle = fopen("ftp://user:password@example.com/somefile.txt", "w");<br></span>

调用: resource fopen ( string filename, string mode [, bool use_include_path [, resource zcontext]] )

返回值: 如果打开失败,本函数返回 FALSE

117.fclose(): 关闭一个已打开的文件指针

<span style="font-size: 14px;">    $handle = fopen('somefile.txt', 'r');<br>    fclose($handle);<br>    bool fclose(resource handle)<br></span>

输出: 如果成功则返回 TRUE,失败则返回 FALSE

文件属性

118.file_exists(): 检查文件或目录是否存在

<span style="font-size: 14px;">    $filename = '/path/to/foo.txt';    if (file_exists($filename)) {    echo "exists";<br>    } else {    echo "does not exist";<br>    }<br></span>

调用: bool file_exists ( string filename )
输入: 指定的文件或目录
输出: 存在则返回 TRUE,否则返回 FALSE

119.filesize(): 取得文件大小

<span style="font-size: 14px;">    $filename = 'somefile.txt';echo $filename . ': ' . filesize($filename) .'bytes';<br></span>

调用: int filesize ( string $filename )

输出: 返回文件大小的字节数,如果出错返回 FALSE 并生成一条 E_WARNING 级的错误

120.is_readable(): 判断给定文件是否可读

<span style="font-size: 14px;">    $filename = 'test.txt';    if (is_readable($filename)) {    echo '可读';<br>    } else {    echo '不可读';<br>    }<br></span>

调用: bool is_readable ( string $filename )
输出: 如果由 filename指定的文件或目录存在并且可读则返回 TRUE

121.is_writable(): 判断给定文件是否可写

<span style="font-size: 14px;">    $filename = 'test.txt';    if (is_writable($filename)) {    echo '可写';<br>    } else {    echo '不可写';<br>    }<br></span>

调用: bool is_writable ( string $filename )
filename 参数 可以是一个允许进行是否可写检查的目录名

输出:
如果文件存在并且可写则返回 TRUE。

122.is_executable(): 判断给定文件是否可执行

<span style="font-size: 14px;">    $file = 'setup.exe';    if (is_executable($file)) {    echo '可执行';<br>    } else {    echo '不可执行';<br>    }<br></span>

调用: bool is_executable ( string $filename )
输出: 如果文件存在且可执行则返回 TRUE

123.filectime(): 获取文件的创建时间

<span style="font-size: 14px;">    $filename = 'somefile.txt';echo filectime($filename);<br></span>

调用: int filectime ( string $filename )
输出: 时间以 Unix 时间戳的方式返回,如果出错则返回FALSE

124.filemtime(): 获取文件的修改时间

<span style="font-size: 14px;">    $filename = 'somefile.txt';echo filemtime($filename);<br>    int filemtime ( string $filename )<br></span>

输出: 返回文件上次被修改的时间,出错时返回 FALSE。时间以 Unix时间戳的方式返回

125.fileatime(): 获取文件的上次访问时间

<span style="font-size: 14px;">    $filename = 'somefile.txt';echo fileatime($filename);<br></span>

调用: int fileatime (string $filename)

输出: 返回文件上次被访问的时间, 如果出错则返回FALSE. 时间以Unix时间戳的方式返回.

126.stat(): 获取文件大部分属性值

<span style="font-size: 14px;">    $filename = 'somefile.txt';<br>var_dump(fileatime($filename));<br></span>

调用: array stat (string $filename
输出: 返回由 filename 指定的文件的统计信息

文件操作

127.fwrite(): 写入文件

<span style="font-size: 14px;">    $filename = 'test.txt';    $somecontent = "添加这些文字到文件\n";    $handle = fopen($filename, 'a');<br>    fwrite($handle, $somecontent);<br>    fclose($handle);<br></span>

调用: int fwrite ( resource handle, string string [, int length] )

输出:
把 string 的内容写入 文件指针 handle 处。如果指定了 length,当写入了length个字节或者写完了string以后,写入就会停止, 视乎先碰到哪种情况

128.fputs(): 同上 
  
129.fread(): 读取文件

<span style="font-size: 14px;"> $filename = "/usr/local/something.txt";$handle = fopen($filename, "r");$contents = fread($handle, filesize($filename));<br> fclose($handle);<br></span>

调用: string fread ( int handle, int length )
从文件指针handle,读取最多 length 个字节

130.feof(): 检测文件指针是否到了文件结束的位置

<span style="font-size: 14px;">    $file = @fopen("no_such_file", "r");    while (!feof($file)) {<br>    }<br>    fclose($file);<br></span>

调用: bool feof ( resource handle )
输出: 如果文件指针到了 EOF 或者出错时则返回TRUE,否则返回一个错误(包括 socket 超时),其它情况则返回 FALSE

131.fgets(): 从文件指针中读取一行

<span style="font-size: 14px;">    $handle = @fopen("/tmp/inputfile.txt", "r");    if ($handle) {    while (!feof($handle)) {    $buffer = fgets($handle, 4096);    echo $buffer;<br>    }<br>    fclose($handle);<br>    }<br></span>

调用: string fgets ( int handle [, int length] )
输出: 从handle指向的文件中读取一行并返回长度最多为length-1字节的字符串.碰到换行符(包括在返回值中)、EOF 或者已经读取了length -1字节后停止(看先碰到那一种情况). 如果没有指定 length,则默认为1K, 或者说 1024 字节.

132.fgetc(): 从文件指针中读取字符

<span style="font-size: 14px;">    $fp = fopen('somefile.txt', 'r');    if (!$fp) {    echo 'Could not open file somefile.txt';<br>    }    while (false !== ($char = fgetc($fp))) {    echo "$char\n";<br>    }<br></span>

输入: string fgetc ( resource $handle )
输出: 返回一个包含有一个字符的字符串,该字符从 handle指向的文件中得到. 碰到 EOF 则返回 FALSE.

133.file(): 把整个文件读入一个数组中

<span style="font-size: 14px;">    $lines = file('http://www.example.com/');<br></span>

// 在数组中循环,显示 HTML 的源文件并加上行号。

<span style="font-size: 14px;">    foreach ($lines as $line_num => $line) {    echo "Line #<b>{$line_num}</b> : " .<br>    htmlspecialchars($line) . "<br />\n";<br>    }<br></span>

// 另一个例子将 web 页面读入字符串。参见 file_get_contents()。

<span style="font-size: 14px;">    $html = implode('', file('http://www.example.com/'));<br></span>

调用: array file ( string $filename [, int $use_include_path [, resource $context ]] )

输出: 数组中的每个单元都是文件中相应的一行,包括换行符在内。如果失败 file() 返回 FALSE

134.readfile(): 输出一个文件 
调用: int readfile ( string $filename [, bool $use_include_path [, resource $context ]] )

输出: 读入一个文件并写入到输出缓冲。返回从文件中读入的字节数。如果出错返回 FALSE

135.file_get_contents(): 将整个文件读入一个字符串

<span style="font-size: 14px;">    echo file_get_contents('http://www.baidu.com');<br></span>

调用:
string file_get_contents ( string $filename [, bool $use_include_path [, resource $context [, int $offset [, int $maxlen ]]]] )
 
136.file_put_contents():将一个字符串写入文件

<span style="font-size: 14px;">    file_put_contents('1.txt','aa');<br></span>

调用: int file_put_contents ( string $filename , string $data [, int $flags [, resource $context ]] )

输出: 该函数将返回写入到文件内数据的字节数

137.ftell(): 返回文件指针读/写的位置

<span style="font-size: 14px;">    $fp=fopen('tx.txt','r');<br>    fseek($fp,10);    echo ftell($fp);<br>    fread($fp,4);    echo ftell($fp);<br></span>

调用: int ftell ( resource $handle )
输出: 返回由 handle 指定的文件指针的位置,也就是文件流中的偏移量

138.fseek(): 在文件指针中定位

<span style="font-size: 14px;">    $fp=fopen('tx.txt','r');<br>    fseek($fp,10);    echo ftell($fp);<br>    fread($fp,4);    echo ftell($fp);<br></span>

调用: int fseek ( resource $handle , int $offset [, int $whence ] )
输出: 成功则返回 0;否则返回 -1

139.rewind(): 倒回文件指针的位置

<span style="font-size: 14px;">    $fp=fopen('tx.txt','r');<br>    fseek($fp,3);    echo ftell($fp);<br>    fread($fp,4);<br>    rewind($fp);    echo ftell($fp);<br></span>

调用: bool rewind ( resource $handle )
返回值: 如果成功则返回 TRUE,失败则返回 FALSE

140.flock(): 轻便的执行文件锁定

<span style="font-size: 14px;">    $fp=fopen('tx.txt','r');<br>    flock($fp, LOCK_SH);//共享锁<br>    //flock($fp, LOCK_EX);//独立锁,写文件时用它打开<br>    //flock($fp, LOCK_NB);//附加锁<br>    flock($fp, LOCK_UN);//释放锁<br>    fclose($fp);<br></span>

调用: bool flock ( int $handle , int $operation [, int &$wouldblock ] )
输出: 如果成功则返回 TRUE,失败则返回 FALSE

目录函数

141.basename(): 返回路径中的文件名部分

<span style="font-size: 14px;">    path = "/home/httpd/html/index.php";    $file = basename($path);    $file = basename($path,".php");<br></span>

调用: string basename ( string $path [, string $suffix ])
输出: 给出一个包含有指向一个文件的全路径的字符串,本函数返回基本的文件名。如果文件名是以 suffix 结
束的,那这一部分也会被去掉

142.dirname(): 返回路径中的目录部分

<span style="font-size: 14px;">    $path = "/etc/passwd";    $file = dirname($path);<br></span>

调用: string dirname ( string $path )
输出: 给出一个包含有指向一个文件的全路径的字符串,本函数返回去掉文件名后的目录名

143.pathinfo(): 返回文件路径的信息

<span style="font-size: 14px;">    echo '<pre>';<br>    print_r(pathinfo("/www/htdocs/index.html"));    echo '</pre>';<br></span>

调用: mixed pathinfo ( string $path [, int $options ] )
返回一个关联数组包含有 path 的信息

144.opendir(): 打开目录句柄

<span style="font-size: 14px;">$fp=opendir('E:/xampp/htdocs/php/study/19');echo readdir($fp);<br>closedir($fp);<br></span>

调用: resource opendir ( string $path [, resource $context ] )
返回值: 如果成功则返回目录句柄的 resource,失败则返回FALSE

145.readdir(): 从目录句柄中读取条目

<span style="font-size: 14px;">$fp=opendir('E:/xampp/htdocs/php/study/19');echo readdir($fp);<br>closedir($fp);<br></span>

调用: string readdir ( resource $dir_handle )
返回值: 返回目录中下一个文件的文件名。文件名以在文件系统中的排序返回

146.closedir(): 关闭目录句柄

<span style="font-size: 14px;"> $fp=opendir('E:/xampp/htdocs/php/study/19'); echo readdir($fp);
closedir($fp);
</span>

调用: void closedir ( resource $dir_handle )
关闭由 dir_handle 指定的目录流。流必须之前被opendir() 所打开
147.rewinddir() : 倒回目录句柄

<span style="font-size: 14px;">    $fp=opendir('E:/xampp/htdocs/php/study/19');    echo readdir($fp).'<br />';    echo readdir($fp).'<br />';    echo readdir($fp).'<br />';<br>    rewinddir($fp);    echo readdir($fp).'<br />';<br>    closedir($fp);<br></span>

调用: void rewinddir ( resource $dir_handle )
输出: 将 dir_handle 指定的目录流重置到目录的开头
148.mkdir(): 新建目录

<span style="font-size: 14px;">    mkdir('123');<br></span>

调用: bool mkdir ( string $pathname [, int $mode [, bool $recursive [, resource $context ]]] )
输出: 尝试新建一个由 pathname 指定的目录

149.rmdir(): 删除目录

<span style="font-size: 14px;">    rmdir('123');<br></span>

调用: bool rmdir ( string $dirname )
输出: 尝试删除 dirname 所指定的目录。目录必须是空的,而且要有相应的权限。如果成功则返回TRUE,失败则返回 FALSE

150.unlink(): 删除文件

<span style="font-size: 14px;">    unlink('123/1.txt');<br>    rmdir('123');<br></span>

调用: bool unlink ( string $filename )
输出: 删除 filename 。和 Unix C 的 unlink() 函数相似。如果成功则返回 TRUE,失败则返回 FALSE

151.copy(): 拷贝文件

<span style="font-size: 14px;">    copy('index.php','index.php.bak');<br></span>

调用: bool copy ( string $source , string $dest )
输出: 将文件从 source 拷贝到 dest. 如果成功则返回TRUE,失败则返回 FALSE

152.rename(): 重命名一个文件或目录

<span style="font-size: 14px;">    rename('tx.txt','txt.txt');<br></span>

调用: bool rename ( string $oldname , string $newname [, resource $context ] )
输出: 如果成功则返回 TRUE,失败则返回 FALSE

文件的上传与下载
153.is_uploaded_file():判断文件是否是通过 HTTP POST上传的

<span style="font-size: 14px;">    if(is_uploaded_file($_FILES['bus']['tmp_name'])){    if( move_uploaded_file($_FILES['bus']['tmp_name'],    $NewPath) ){    echo '上传成功<br /><img data-original="'.$NewPath.'">';<br>    }else{    exit('失败');<br>    }<br>    }else{    exit('不是上传文件');<br>    }<br></span>

调用: bool is_uploaded_file ( string $filename )  

154.move_uploaded_file(): 将上传的文件移动到新位置

<span style="font-size: 14px;">    if(is_uploaded_file($_FILES['bus']['tmp_name'])){    if( move_uploaded_file($_FILES['bus']['tmp_name'],    $NewPath) ){    echo '上传成功<br /><img data-original="'.$NewPath.'">';<br>    }else{    exit('失败');<br>    }<br>    }else{    exit('不是上传文件');<br>    }<br></span>

调用: bool move_uploaded_file ( string $filename , string

时间函数

155.time(): 返回当前的 Unix 时间戳time();
调用: int time ( void )
输出: 返回自从 Unix 纪元(格林威治时间 1970 年 1 月 1 日 00:00:00)到当前时间的秒数

156.mktime(): 取得一个日期的 Unix 时间戳

<span style="font-size: 14px;">    mktime(0, 0, 0, 4, 25, 2012);<br></span>

调用: int mktime ([ int $hour [, int $minute [, int $second [, int $month [, int $day [, int $year [, int $is_dst ]]]]]]] )
 
156.date(): 格式化一个本地时间/日期

<span style="font-size: 14px;">date('Y年m月d日 H:i:s');<br></span>

调用: string date ( string $format [, int $timestamp ] )

输出: 2016年09月10日 20:45:54

157.checkdate(): 验证一个格里高里日期
调用: bool checkdate ( int $month , int $day , int $year)
输出: 如果给出的日期有效则返回 TRUE,否则返回 FALSE

<span style="font-size: 14px;">    if(checkdate(6,31,2012)){    echo '成立';<br>    }else{    echo '不成立';<br>    }<br></span>

158.date_default_timezone_set(): 设定用于一个脚本中所有日期时间函数的默认时区

<span style="font-size: 14px;">    date_default_timezone_set('PRC');<br></span>

调用: bool date_default_timezone_set ( string $timezone_identifier)

返回值: 如果 timezone_identifier 参数无效则返回 FALSE,否则返回 TRUE。
159.getdate(): 取得日期/时间信息

调用: array getdate ([ int $timestamp ] )

输出: 返回一个根据timestamp得出的包含有日期信息的关联数组。如果没有给出时间戳则认为是当前本地时间

<span style="font-size: 14px;">    $t=getdate();<br>    var_dump($t);<br></span>

160.strtotime(): 将任何英文文本的日期时间描述解析为 Unix 时间戳

<span style="font-size: 14px;">    echo strtotime("now");<br>    int strtotime ( string $time [, int $now ] )      echo strtotime("10 September 2000");    echo strtotime("+1 day");    echo strtotime("+1 week");    echo strtotime("+1 week 2 days 4 hours 2 seconds");    echo strtotime("next Thursday");    echo strtotime("last Monday");<br></span>

161.microtime(): 返回当前 Unix 时间戳和微秒数
调用: mixed microtime ([ bool $get_as_float ] )

<span style="font-size: 14px;">    $start=microtime(true);<br>    sleep(3);    $stop=microtime(true);    echo $stop-$start;<br></span>

其他常用:

162.intval(): 获取变量的整数值
调用: int intval ( mixed $var [, int $base = 10 ] )

通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。

var: 要转换成 integer 的数量值

base: 转化所使用的进制

返回值: 成功时返回 var 的 integer 值,失败时返回 0。 空的 array 返回 0,非空的 array 返回 1。

163.sprintf(): 函数把格式化的字符串写入一个变量中
语法
sprintf(format,arg1,arg2,arg++)
参数 描述
format 必需。转换格式。
arg1 必需。规定插到 format 字符串中第一个 % 符号处的参数。
arg2 可选。规定插到 format 字符串中第二个 % 符号处的参数。
arg++ 可选。规定插到 format 字符串中第三、四等等 % 符号处的参数。
说明
参数 format 是转换的格式,以百分比符号 (“%”) 开始到转换字符结束。下面的可能的 format 值:

%% - 返回百分比符号
%b - 二进制数
%c - 依照 ASCII 值的字符
%d - 带符号十进制数
%e - 可续计数法(比如 1.5e+3)
%u - 无符号十进制数
%f - 浮点数(local settings aware)
%F - 浮点数(not local settings aware)
%o - 八进制数
%s - 字符串
%x - 十六进制数(小写字母)
%X - 十六进制数(大写字母)
arg1, arg2, ++ 等参数将插入到主字符串中的百分号 (%) 符号处。该函数是逐步执行的。在第一个 % 符号中,插入 arg1,在第二个 % 符号处,插入 arg2,依此类推。

提示和注释

注释:如果 % 符号多于 arg 参数,则您必须使用占位符。占位符插到 % 符号后面,由数字和 “$” 组成。请参见例子 3。

提示:相关函数:fprintf()、printf()、vfprintf()、vprintf() 以及 vsprintf()。

例子

例子 1

<span style="font-size: 14px;"><?php$str = "Hello";$number = 123;$txt = sprintf("%s world. Day number %u",$str,$number);echo $txt;?><br></span>

输出:

Hello world. Day number 123

164.PDO类的相关函数
prepare()
execute()
fetch()

<span style="font-size: 14px;"><?php$driver = 'mysql';$database = "dbname=CODINGGROUND";$dsn = "$driver:host=localhost;unix_socket=/home/cg/mysql/mysql.sock;$database";$username = 'root';$password = 'root';try {   $conn = new PDO($dsn, $username, $password);   echo "<h2>Database CODINGGROUND Connected<h2>";<br>}catch(PDOException $e){   echo "<h1>" . $e->getMessage() . "</h1>";<br>}$sql = 'SELECT  * FROM users';$stmt = $conn->prepare($sql);$stmt->execute();echo "<table style='width:100%'>";while($row = $stmt->fetch(PDO::FETCH_ASSOC)){  echo "<tr>";  foreach($row as $value)<br>  {    echo sprintf("<td>%s</td>", $value);<br>  }  echo "</tr>";<br>}echo "</table>";?><br></span>

165.isset(): 检测变量是否设置。
原型格式: bool isset ( mixed var [, mixed var [, ...]] )

返回值:
若变量不存在则返回 FALSE
若变量存在且其值为NULL,也返回 FALSE
若变量存在且值不为NULL,则返回 TURE
同时检查多个变量时,每个单项都符合上一条要求时才返回 TRUE,否则结果为 FALSE
如果已经使用 unset() 释放了一个变量之后,它将不再是 isset()。若使用 isset() 测试一个被设置成 NULL 的变量,将返回 FALSE。同时要注意的是一个 NULL 字节(”“)并不等同于 PHP 的 NULL 常数。

<span style="font-size: 14px;">$userInfo=’abc’;if(isset($userInfo['account'])) {$account=$userInfo['account'];
} else {$account=$userInfo;
}
</span>

166.unset(): 销毁指定的变量。
函数原型: unset(var1,var2,...)

参数 描述
var1 要销毁的变量1
var2 要销毁的变量2

<span style="font-size: 14px;"><?php<br>    $foo = 'php unset()';    unset ($foo);    echo $foo;?><br></span>

167.preg_replace_callback: 执行一个正则表达式搜索并且使用一个回调进行替换.

原型:
mixed preg_replace_callback ( mixed $pattern , callable $callback , mixed $subject [, int $limit = -1 [, int &$count ]] )
这个函数的行为除了 可以指定一个 callback 替代 replacement 进行替换 字符串的计算,其他方面等同于 preg_replace()。

pattern: 要搜索的模式,可以使字符串或一个字符串数组。

callback: 一个回调函数,在每次需要替换时调用,调用时函数得到的参数是从subject 中匹配到的结果。回调函数返回真正参与替换的字符串。这是该回调函数的签名:
string handler ( array $matches )
你可能经常会需要callback函数而仅用于preg_replace_callback()一个地方的调用。在这种情况下,你可以 使用匿名函数来定义一个匿名函数作为preg_replace_callback()调用时的回调。 这样做你可以保留所有 调用信息在同一个位置并且不会因为一个不在任何其他地方使用的回调函数名称而污染函数名称空间。

subject: 要搜索替换的目标字符串或字符串数组。

limit: 对于每个模式用于每个 subject 字符串的最大可替换次数。 默认是-1(无限制)。

count: 如果指定,这个变量将被填充为替换执行的次数。

<span style="font-size: 14px;"><?php/* 一个unix样式的命令行过滤器,用于将段落开始部分的大写字母转换为小写。 */$fp = fopen("php://stdin", "r") or die("can't read stdin");while (!feof($fp)) {    $line = fgets($fp);    $line = preg_replace_callback(        '|<p>\s*\w|',        function ($matches) {<br>            return strtolower($matches[0]);<br>        },        $line<br>    );    echo $line;<br>}<br>fclose($fp);?><br></span>

返回值:
如果subject是一个数组, preg_replace_callback()返回一个数组,其他情况返回字符串。 错误发生时返回 NULL。

如果查找到了匹配,返回替换后的目标字符串(或字符串数组), 其他情况subject 将会无变化返回。

168.json_encode(): 对变量进行 JSON 编码

函数原型: json_encode(value,option)
参数 描述
value 必填。待编码的 value ,除了resource 类型之外,可以为任何数据类型。该函数只能接受 UTF-8 编码的数据
options 可选。
JSON_HEX_QUOT 把双引号转为u0022(php 5.3)
JSON_HEX_TAG 把< > 转为 u003C 和 u003E(php 5.3)
JSON_HEX_AMP 把 & 转为 u0026(php 5.3)
JSON_HEX_APOS 把单引号转为 u0027.(php 5.3)
JSON_NUMERIC_CHECK 把数字字符串当作数字编码(php 5.3)
JSON_PRETTY_PRINT 使用空格格式化数据(php 5.4)
JSON_UNESCAPED_SLASHES 不忽略 /(php 5.4)
JSON_FORCE_OBJECT 使用非关联数组时输出一个对象而不是一个数组(php 5.3)
JSON_UNESCAPED_UNICODE 逐字编译多字节字符(php 5.4)

<span style="font-size: 14px;"><?php$arr = array ('a'=>1,'b'=>2,'c'=>3,'d'=>4,'e'=>5);echo json_encode($arr);?>
</span>

以上例程会输出:

1

<span style="font-size: 14px;">{"a":1,"b":2,"c":3,"d":4,"e":5}<br></span>

169.iconv(): 用于按规定的字符编码转换字符串。mb_convert_encoding() 函数也可以转换编码。

如果发现中文输出乱码的时候,很可能就需要使用此函数做处理。

函数原型: iconv(in_charset ,out_charset ,str )
参数 描述
in_charset 输入的字符集。
out_charset 输出的字符集。如果你在 out_charset 后添加了字符串 //TRANSLIT,将启用转写(transliteration)功能。这个意思是,当一个字符不能被目标字符集所表示时,它可以通过一个或多个形似的字符来近似表达。 如果你添加了字符串 //IGNORE,不能以目标字符集表达的字符将被默默丢弃。 否则,str 从第一个无效字符开始截断并导致一个 E_NOTICE。
str 要转换的字符串。

<span style="font-size: 14px;"><?php$text = "This is the Euro symbol '€'.";echo 'Original : ', $text, PHP_EOL;echo 'TRANSLIT : ', iconv("UTF-8", "ISO-8859-1//TRANSLIT", $text), PHP_EOL;echo 'IGNORE   : ', iconv("UTF-8", "ISO-8859-1//IGNORE", $text), PHP_EOL;echo 'Plain    : ', iconv("UTF-8", "ISO-8859-1", $text), PHP_EOL;?><br></span>

输出:

<span style="font-size: 14px;">Original : This is the Euro symbol '€








查看原文

叶文轩 收藏了文章 · 2018-11-06

资损防控体系介绍

1. 资损盲区

随着有赞支付体量的增大,资产部门承担的资金管理,风险把控的责任也越大。我们一方面要小步快跑,快速支撑业务,又要稳住底盘,守好底线。支付业务底线就是守护用户的每一分钱,不能有资金损失。在我们搭建这套体系前,有赞支付资金类的线上监控是个盲区,缺乏自我发现的能力。业务成功了,但内部对用户的资金操作可能是错误的,导致资损。而且故障发生到发现的时间很长,且大部分是用户上报,导致故障的影响面扩大,用户的信任度降低。
预防资损有很多种手段,除了事前线下通过各种测试手段保障资金安全外,线上也是非常重要的一环。除了发现问题,相应的,出现故障时,资损止血的能力也需要配套跟上。

举一个最基本的支付业务场景,在有赞内部会经历以下几个系统之间的交互:
图片描述

通过上图可以看出每个系统的处理结果,会依据系统建立的模型存储在数据库中,部分关键信息会传输给下层系统。系统之间处理的重要信息如金额、账户不一致就会导致资损。目前我们也内部对账会发现这些问题,但是内部对账都是每天跑批执行一次。如果依靠内部对账来发现这个问题,资损早就发生了。需要调用很大的人力物力去追款,大部分情况下还追不回来。我们分析了有赞近一年来的资损场景,结合历史的经验,总结出资损类故障发生有几下几大类:
1)有正确的输入,错误的输出:比如系统与系统之间的金额存储单位不一致,或者自己处理金额正确,传输给下游的金额错误,导致后面交易金额错误;
2)上下游系统的数据不一致:该处理的没处理,该到达终态的单据没有到达终态;
3)幂等控制失效,多扣款或多入账;
4)系统内部逻辑错误,无对外输出;
5)人工修复异常场景,产生资损。

2. 资损体系的诞生

基于解决以上问题的目的,我们设计了实时防控资损体系。总体设计思路围绕以下几点:
1)发现问题的实时性,减少故障的影响面;
2)信息流一致性两两比对、资金流平衡型检查;
3)全方位监控:业务触发、人工变更资金检测、历史数据检测;
4)检测的准确性,无误报;
5)和支付链路解藕,不影响主链路。

平台能力是基础,检测规则是其灵魂。基于对业务的丰富经验,我们可以沉淀一些业务资金规则,从旁路来约束和检测系统逻辑的正确性。比如支付金额-退款金额应该==结算金额,退款金额不能大于支付金额,凭证支付、现金支付无资金流类型不用调用账务,支付和账务之间会经过结算的处理,账务累计出入金额和支付的金额应该要相等。

3. 系统设计:

3.1 总体设计的架构图如下:

图片描述

系统定位于事前线下测试环境兜底,事中一致性检测,事后资金兜底,不对业务造成入侵,完全旁路运行。触发点有 2 个,业务事件消息和数据库变更 binlog 信息。

分三类信息处理:
1) 基于各个业务事件比如支付完成事件、退款完成事件、确认收货时结算完成事件,账务收支明细变更事件等,触发运行系统内配置的依赖此事件的规则;
2) 通过监听 binlog 变更,可以检测到人为操作类变更, 按定义好的逻辑生成对应的检查点,每个检查点有包含多个链路检测。触发对应的规则运行检测全链路数据的一致性、资金的平衡性;
3) 人工处理历史数据前,对历史数据的质量进行前置检测。保证不产生二次资损。
通过系统间两两核对数据一致性,或者抽象出系统内的业务规则、资金规则旁路自检来发现故障。并且实时获取数据,实时运行,对于业务处理上有滞后和缓冲的场景,我们提供了异步运行的机制,以及三次重试的机会。全面提供系统整体的容错性,无因系统设计问题导致的误报。

3.2 处理流程图如下:

图片描述
经过系统的沉淀之后,我们将过程中的数据存储到了 hbase,把整个支付过程落地成了可视化试图,可清晰的查看每个环节的数据形态,具体数据就不展示了。
比如一笔订单可以看到,当前已经是退款完成状态,对应的支付成功时支付、结算、计费、账务数据形态:
图片描述

退款完成环节支付、结算、计费、账务数据形态:
图片描述

3.3 资金熔断:

熔断的处理流程图如下:
图片描述

基于我们之前建立的异常发现能力,同时我们需要具备资金止损能力。建立后台触发熔断操作入口,人工录入熔断配置或资损防控检测出异常新增并生效熔断配置,应急情况生效熔断,日常支付链路不会过熔断判断。
熔断支持按业务码纬度、指定的单号、商户号熔断;
目前我们在业务方接入的熔断埋点有 3 个点:退款、结算、出金。为什么考虑这三个地方埋点呢?
1) 我们整个系统的定位都是不侵入主链路,对用户无感知的,所以支付环节不考虑埋点。且钱不能流出有赞的体系外,一旦流出则无法追回。
2) 在支付链路产生的故障,考虑在退款、结算环节来做拦截,且支付完成后,钱停留在有赞的中间户,此时订正支付链路数据,对商户来说无感知。
3) 一旦在结算环节出现问题,则考虑最后一道兜底,出金报送银联前进行拦截。
确认无误或故障处理完成后,触发解熔断操作,业务继续处理或驳回。

4. 总结

建立了这一整套体系后,半年时间内,我们已经在线下环境联调时就成功兜底资金处理 bug,线上也避免了多起问题。并定期的进行故障演练来检测平台能力。
本文主要介绍大体的设计和实现思路,后续会有详细的技术细节介绍,敬请期待。资损防控路漫漫,共勉。
图片描述

查看原文

叶文轩 关注了专栏 · 2018-11-06

有赞技术

有赞技术相关内容

关注 6681

叶文轩 收藏了文章 · 2018-10-15

MySQL实验: 实践索引对全列匹配、最左前缀匹配、范围查询等条件的影响以及了解脏读、幻读等

索引实验

实验目的:了解索引对于全列匹配,最左前缀匹配、范围查询的影响。实验所用数据库见文章最底部连接。

实验软件版本:5.7.19-0ubuntu0.16.04.1-log (Ubuntu)
实验存储引擎:InnoDB

show index from `employees`.`titles`

clipboard.png

实验一、全列匹配

explain select * from `employees`.`titles` where `emp_no`='10001' and title='Senior Engineer' and `from_date`='1986-06-26';

clipboard.png

很明显,当按照索引中所有列进行精确匹配(这里精确匹配指“=”或“IN”匹配)时,索引可以被用到。这里有一点需要注意,理论上索引对顺序是敏感的,但是由于MySQL的查询优化器会自动调整where子句的条件顺序以使用适合的索引。

explain select * from `employees`.`titles` where `from_date`='1986-06-26' and `emp_no`='10001' and title='Senior Engineer';

clipboard.png

实验二、最左前缀匹配

explain select * from `employees`.`titles` where `emp_no`='10001';

clipboard.png

当查询条件精确匹配索引的左边连续一个或几个列时,如<emp_no>或<emp_no, title>,所以可以被用到,但是只能用到一部分,即条件所组成的最左前缀。上面的查询从分析结果看用到了PRIMARY索引,但是key_len为4,说明只用到了索引的第一列前缀。

实验三、查询条件用到了索引中列的精确匹配,但是中间某个条件未提供

explain select * from `employees`.`titles` where `emp_no`='10001' and `from_date` = '1986-06-26' ;

clipboard.png

此时索引使用情况和实验二相同,因为title未提供,所以查询只用到了索引的第一列,而后面的from_date虽然也在索引中,但是由于title不存在而无法和左前缀连接,因此需要对结果进行扫描过滤from_date(这里由于emp_no唯一,所以不存在扫描)。

如果想让from_date也使用索引而不是where过滤,可以增加一个辅助索引<emp_no, from_date>,此时上面的查询会使用这个索引。除此之外,还可以使用一种称之为“隔离列”的优化方法,将emp_no与from_date之间的“坑”填上。

看下title一共有几种不同的值。

select distinct(title) from `employees`.`titles`;

clipboard.png

只有7种。在这种成为“坑”的列值比较少的情况下,可以考虑用“IN”来填补这个“坑”从而形成最左前缀:

explain select * from `employees`.`titles`
where `emp_no` = '10001'
and `title` IN ('Senior Engineer', 'Staff', 'Engineer', 'Senior Staff', 'Assistant Engineer', 'Technique Leader', 'Manager')
and `from_date` = '1986-06-26';

clipboard.png

这次key_len为59,说明索引被用全了,但是从type和rows看出IN实际上执行了一个range查询,这里检查了7个key。看下两种查询的性能比较:

clipboard.png

“填坑”后性能提升了一点。如果经过emp_no筛选后余下很多数据,则后者性能优势会更加明显。当然,如果title的值很多,用填坑就不合适了,必须建立辅助索引。

实验四:查询条件没有指定索引第一列

explain select * from `employees`.`titles` where `from_date` = '1986-06-26';

clipboard.png

由于不是最左前缀,索引这样的查询显然用不到索引。

实验五:匹配某列的前缀字符串

explain select * from `employees`.`titles`where `emp_no` = '10001' and `title` like 'Senior%';

clipboard.png

此时可以用到索引。如果配符%不出现在开头,则可以用到索引,但根据具体情况不同可能只会用其中一个前缀。

实验六:范围查询

explain select * from `employees`.`titles` where `emp_no` < '10010' and `title` = 'Senior Engineer';

clipboard.png

范围列可以用到索引(必须是最左前缀),但是范围列后面的列无法用到索引。同时,索引最多用于一个范围列,因此如果查询条件中有两个范围列则无法全用到索引。

explain select * from `employees`.`titles`
where `emp_no` < '10010'
and `title` = 'Senior Engineer'
and `from_date` between '1986-01-01' and '1986-12-11';

clipboard.png

可以看到索引对第二个范围索引无能为力。这里特别要说明MySQL一个有意思的地方,那就是仅用explain可能无法区分范围索引和多值匹配,因为在type中这两者都显示为range。同时,用了“between”并不意味着就是范围查询,例如下面的查询:

explain select * from `employees`.`titles`
where `emp_no` between '10001' and '10010'
and `title` = 'Senior Enginee'
and `from_date` between '1986-01-01' and '1986-12-31';

clipboard.png

看起来是用了两个范围查询,但作用于emp_no上的“BETWEEN”实际上相当于“IN”,也就是说emp_no实际是多值精确匹配。可以看到这个查询用到了索引全部三个列。因此在MySQL中要谨慎地区分多值匹配和范围匹配,否则会对MySQL的行为产生困惑。

实验七:查询条件中含有函数或表达式

如果查询条件中含有函数或表达式,则MySQL不会为这列使用索引(虽然某些在数学意义上可以使用)。例如:

explain select * from `employees`.`titles` where `emp_no` = '10001' and left(`title`, 6) = 'Senior';

clipboard.png

虽然这个查询和实验五中功能相同,但是由于使用了函数left,则无法为title列应用索引,而实验五中用LIKE则可以。再如:

explain select * from `employees`.`titles` where `emp_no` - 1 = '10000';

clipboard.png

显然这个查询等价于查询emp_no为10001的函数,但是由于查询条件是一个表达式,MySQL无法为其使用索引。因此在写查询语句时尽量避免表达式出现在查询中,而是先手工私下代数运算,转换为无表达式的查询语句。

索引选择性与前缀索引

索引选择性

所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:

Index Selectivity = Cardinality / #T

显然选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的。例如,上文用到的employees.titles表,如果title字段经常被单独查询,是否需要建索引,我们看一下它的选择性:

select count(distinct(title))/count(*) as selectivity from `employees`.`titles`;

clipboard.png

title的选择性不足0.0001(精确值为0.00001579),所以实在没有什么必要为其单独建索引。

前缀索引

有一种与索引选择性有关的索引优化策略叫做前缀索引,就是用列的前缀代替整个列作为索引key,当前缀长度合适时,可以做到既使得前缀索引的选择性接近全列索引,同时因为索引key变短而减少了索引文件的大小和维护开销。

explain select * from `employees`.`employees` where `first_name` = 'Eric' and `last_name` = 'Anido';

因为employees表只有一个索引<emp_no>,那么如果我们想按名字搜索一个人,就只能全表扫描了:

clipboard.png

如果频繁按名字搜索员工,这样显然效率很低,因此我们可以考虑建索引。有两种选择,建<first_name>或<first_name, last_name>,看下两个索引的选择性:

select count(distinct(first_name))/count(*) as selectivity from `employees`.`employees`;

clipboard.png

select count(distinct(concat(first_name, last_name)))/count(*) as selectivity from `employees`.`employees`;

clipboard.png

<first_name>显然选择性太低,<first_name, last_name>选择性很好,但是first_name和last_name加起来长度为30,有没有兼顾长度和选择性的办法?可以考虑用first_name和last_name的前几个字符建立索引,例如<first_name, left(last_name, 4)>,看看其选择性:

select count(distinct(concat(first_name, left(last_name, 4))))/count(*) as selectivity from `employees`.`employees`;

clipboard.png

加索引

ALTER TABLE employees.employees
ADD INDEX `first_name_last_name4` (first_name, last_name(4));

前缀索引兼顾索引大小和查询速度,但是其缺点是不能用于ORDER BY和GROUP BY操作,也不能用于Covering index(即当索引本身包含查询所需全部数据时,不再访问数据文件本身)。

MySQL事务隔离层级实验

实验目的:了解MySQL中事务隔离级别以及什么是脏读,幻读,不可重复读。

实验一:脏读

定义:在两个事务中,一个事务读到了另一个事务未提交的数据。因为数据可能被回滚,不符合隔离性的定义。

1.新建数据库连接执行一下操作

set global transaction isolation level read uncommitted;
set autocommit = 0;
begin;
update `employees`.`titles` set `title` = 'Senior Engineer 1' where `emp_no` = 100001;

注意还没有执行 commit

2.然后新建一个连接 可以看到读到了另一个事物还未被commit的数据,这就是所谓的脏读。
clipboard.png

实验二:幻读

定义:一个事务批量读取了一批数据时,另一个事务提交了新的数据,当之前的事务再次读取时,会产生幻影行。

如丙存款100元未提交,这时银行做报表统计account表中所有用户的总额为500元,然后丙提交了,这时银行再统计发现帐户为600元了,造成虚读同样会使银行不知所措,到底以哪个为准。

1.设置事物隔离级别。

set global transaction isolation level read committed;
begin;
select * from `employees`.`titles` where `titles`.`from_date` = '1994-12-15';

clipboard.png

2.新开一个连接

begin;
insert into `titles` values (499999, 'Engineer', '1994-12-15', '1994-12-15');
commit;

3.回到第一步的窗口,查询数据。

select * from `employees`.`titles` where `titles`.`from_date` = '1994-12-15';
commit;

clipboard.png

实验三:不可重复读

定义:不可重复读指在一个事务内读取表中的某一行数据,多次读取结果不同。

例如银行想查询A帐户余额,第一次查询A帐户为200元,此时A向帐户内存了100元并提交了,银行接着又进行了一次查询,此时A帐户为300元了。银行两次查询不一致,可能就会很困惑,不知道哪次查询是准的。
  不可重复读和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。
  很多人认为这种情况就对了,无须困惑,当然是后面的为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。

  1. 开启连接查询值。
begin;
select * from `employees`.`titles` where `emp_no` = 100001;
select * from `employees`.`titles` where `emp_no` = 100001;

clipboard.png

2.新开一个连接修改emp_no为100001的title的值。

begin;
update `employees`.`titles` set `title` = 'Senior Engineer 1' where `emp_no` = 100001;
commit;

3.回到第一步的连接再次查询

select * from `employees`.`titles` where `emp_no` = 100001;

clipboard.png

MySQL事务隔离级别

  • 未提交读:第一个事务还未提交,另一个事务就可以读取,导致脏读。
  • 提交读(不可重复读):一个事务未提交对其他事务不可见,但是会产生幻读和不可重复读。
  • 可重复读(mysql默认隔离级别):保证同一个事务下多次读取的结果一致,但是会产生幻读。
  • 可串行化:严格的串行阻塞,并发能力不好。
隔离级别脏读不可重复读幻读
Read Uncommitted
Read Committed
Repeatable Read (默认)
Serializable

参考资料

1.走进mysql基础
2.MySQL索引背后的数据结构及算法原理
3.datacharmer/test_db

查看原文

叶文轩 评论了文章 · 2018-09-30

2018年Android的保活方案效果统计

一、常见保活方案

1、监听广播:监听全局的静态广播,比如时间更新的广播、开机广播、解锁屏、网络状态、解锁加锁亮屏暗屏(3.1版本),高版本需要应用开机后运行一次才能监听这些系统广播,目前此方案失效。可以更换思路,做APP启动后的保活(监听广播启动保活的前台服务)

2、定时器、JobScheduler:假如应用被系统杀死,那么定时器则失效,此方案失效。JobService在5.0,5.1,6.0作用很大,7.0时候有一定影响(可以在电源管理中给APP授权)

3、双进程(NDK方式Fork子进程)、双Service守护:高版本已失效,5.0起系统回收策略改成进程组。双Service方案也改成了应用被杀,任何后台Service无法正常状态运行

4、提高Service优先级:只能一定程度上缓解Service被立马回收

二、保活

  • 1、AIDL方式单进程、双进程方式保活Service
  • 2、降低oom_adj的值:常驻通知栏(可通过启动另外一个服务关闭Notification,不对oom_adj值有影响)、使用”1像素“的Activity覆盖在getWindow()的view上、循环播放无声音频(黑科技,7.0下杀不掉)
  • 3、监听锁屏广播:使Activity始终保持前台
  • 4、使用自定义锁屏界面:覆盖了系统锁屏界面。
  • 5、通过android:process属性来为Service创建一个进程
  • 6、跳转到系统白名单界面让用户自己添加app进入白名单

三、复活

  • 1、JobScheduler:原理类似定时器,5.0,5.1,6.0作用很大,7.0时候有一定影响(可以在电源管理中给APP授权)
  • 2、推送互相唤醒复活:极光、友盟、以及各大厂商的推送
  • 3、同派系APP广播互相唤醒:比如今日头条系、阿里系
方案实现效果统计

1、双进程守护方案(基于onStartCommand() return START_STICKY)

  • 1、原生5.0、5.1:原生任务栏滑动清理app,Service会被杀掉,然后被拉起,接着一直存活
  • 2、金立F100(5.1):一键清理直接杀掉整个app,包括双守护进程。不手动清理情况下,经测试能锁屏存活至少40分钟
  • 3、华为畅享5x(6.0):一键清理直接杀掉整个app,包括双守护进程。不手动清理下,锁屏只存活10s。结论:双进程守护方案失效。
  • 4、美图m8s(7.1.1):一键清理直接杀掉整个app,包括双守护进程。不清理情况下,锁屏会有被杀过程(9分钟左右被杀),之后重新复活,之后不断被干掉然后又重新复活。结论:双守护进程可在后台不断拉起Service。
  • 5、原生7.0:任务栏清除APP后,Service存活。使用此方案后Service照样存活。
  • 6、LG V30+(7.1.2):不加双进程守护的时候,一键清理无法杀掉服务。加了此方案之后也不能杀掉服务,锁屏存活(测试观察大于50分钟)
  • 7、小米8(8.1):一键清理直接干掉app并且包括双守护进程。不清理情况下,不加守护进程方案与加守护进程方案Service会一直存活,12分钟左右closed。结论:此方案没有起作用

结论:除了华为此方案无效以及未更改底层的厂商不起作用外(START_STICKY字段就可以保持Service不被杀)。此方案可以与其他方案混合使用

2、监听锁屏广播打开1像素Activity(基于onStartCommand() return START_STICKY)
  • 1、原生5.0、5.1:锁屏后3s服务被干掉然后重启(START_STICKY字段起作用)
  • 2、华为畅享5x(6.0):锁屏只存活4s。结论:方案失效。
  • 3、美图m8s(7.1.1):同原生5.0
  • 4、原生7.0:同美图m8s。
  • 5、LG V30+(7.1.2):锁屏后情况跟不加情况一致,服务一致保持运行,结论:此方案不起作用
  • 6、小米8(8.1):关屏过2s之后app全部被干掉。结论:此方案没有起作用

结论:此方案无效果

3、故意在后台播放无声的音乐(基于onStartCommand() return START_STICKY)

  • 1、原生5.0、5.1:锁屏后3s服务被干掉然后重启(START_STICKY字段起作用)
  • 2、华为畅享5x(6.0):一键清理后服务依然存活,需要单独清理才可杀掉服务,锁屏8分钟后依然存活。结论:此方案适用
  • 3、美图m8s(7.1.1):同5.0
  • 4、原生7.0:任务管理器中关闭APP后服务被干掉,大概过3s会重新复活(同仅START_STICKY字段模式)。结论:看不出此方案有没有其作用
  • 5、LG V30+(7.1.2):使用此方案前后效果一致。结论:此方案不起作用
  • 6、小米8(8.1):一键清理可以杀掉服务。锁屏后保活超过20分钟

结论:成功对华为手机保活。小米8下也成功突破20分钟

4、使用JobScheduler唤醒Service(基于onStartCommand() return START_STICKY)

  • 1、原生5.0、5.1:任务管理器中干掉APP,服务会在周期时间后重新启动。结论:此方案起作用
  • 2、华为畅享5x(6.0):一键清理直接杀掉APP,过12s左右会自动重启服务,JobScheduler起作用
  • 3、美图m8s(7.1.1):一键清理直接杀掉APP,无法自动重启
  • 4、原生7.0:同美图m8s(7.1.1)
  • 5、小米8(8.1):同美图m8s(7.1.1)

结论:只对5.0,5.1、6.0起作用

5、混合使用的效果,并且在通知栏弹出通知

  • 1、原生5.0、5.1:任务管理器中干掉APP,服务会在周期时间后重新启动。锁屏超过11分钟存活
  • 2、华为畅享5x(6.0):一键清理后服务依然存活,需要单独清理才可杀掉服务。结论:方案适用。
  • 3、美图m8s(7.1.1):一键清理APP会被杀掉。正常情况下锁屏后服务依然存活。
  • 4、原生7.0:任务管理器中关闭APP后服务被干掉,过2s会重新复活
  • 5、小米8(8.1):一键清理可以杀掉服务,锁屏下后台保活时间超过38分钟
  • 6、荣耀10(8.0):一键清理杀掉服务,锁屏下后台保活时间超过23分钟

结论:高版本情况下可以使用弹出通知栏、双进程、无声音乐提高后台服务的保活概率

实现具体过程

一、双进程实现方案

使用AIDL绑定方式新建2个Service优先级(防止服务同时被系统杀死)不一样的守护进程互相拉起对方,并在每一个守护进程的ServiceConnection的绑定回调里判断保活Service是否需要重新拉起和对守护线程进行重新绑定。

1、新建一个AIDL文件

KeepAliveConnection
interface KeepAliveConnection  {
}

2、新建一个服务类StepService,onBind()方法返回new KeepAliveConnection.Stub()对象,并在ServiceConnection的绑定回调中对守护进程服务类GuardService的启动和绑定。


/**
 * 主进程 双进程通讯
 *
 * @author LiGuangMin
 * @time Created by 2018/8/17 11:26
 */
public class StepService extends Service {

    private final static String TAG = StepService.class.getSimpleName();
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Logger.d(TAG, "StepService:建立链接");
            boolean isServiceRunning = ServiceAliveUtils.isServiceAlice();
            if (!isServiceRunning) {
                Intent i = new Intent(StepService.this, DownloadService.class);
                startService(i);
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            // 断开链接
            startService(new Intent(StepService.this, GuardService.class));
            // 重新绑定
            bindService(new Intent(StepService.this, GuardService.class), mServiceConnection, Context.BIND_IMPORTANT);
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new KeepAliveConnection.Stub() {
        };
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        startForeground(1, new Notification());
        // 绑定建立链接
        bindService(new Intent(this, GuardService.class), mServiceConnection, Context.BIND_IMPORTANT);
        return START_STICKY;
    }

}

3、对守护进程GuardService进行和2一样的处理

/**
 * 守护进程 双进程通讯
 *
 * @author LiGuangMin
 * @time Created by 2018/8/17 11:27
 */
public class GuardService extends Service {
    private final static String TAG = GuardService.class.getSimpleName();
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Logger.d(TAG, "GuardService:建立链接");
            boolean isServiceRunning = ServiceAliveUtils.isServiceAlice();
            if (!isServiceRunning) {
                Intent i = new Intent(GuardService.this, DownloadService.class);
                startService(i);
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            // 断开链接
            startService(new Intent(GuardService.this, StepService.class));
            // 重新绑定
            bindService(new Intent(GuardService.this, StepService.class), mServiceConnection, Context.BIND_IMPORTANT);
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new KeepAliveConnection.Stub() {
        };
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        startForeground(1, new Notification());
        // 绑定建立链接
        bindService(new Intent(this, StepService.class), mServiceConnection, Context.BIND_IMPORTANT);
        return START_STICKY;
    }

}

4、在Activity中在启动需要保活的DownloadService服务后然后启动保活的双进程

public class MainActivity extends AppCompatActivity {
    private TextView mShowTimeTv;
    private DownloadService.DownloadBinder mDownloadBinder;
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mDownloadBinder = (DownloadService.DownloadBinder) service;
            mDownloadBinder.setOnTimeChangeListener(new DownloadService.OnTimeChangeListener() {
                @Override
                public void showTime(final String time) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mShowTimeTv.setText(time);
                        }
                    });
                }
            });
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent(this, DownloadService.class);
        startService(intent);
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
        //双守护线程,优先级不一样
        startAllServices();
    }

    @Override
    public void onContentChanged() {
        super.onContentChanged();
        mShowTimeTv = findViewById(R.id.tv_show_time);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mServiceConnection);
    }

    /**
     * 开启所有守护Service
     */
    private void startAllServices() {
        startService(new Intent(this, StepService.class));
        startService(new Intent(this, GuardService.class));
    }
}

二、监听到锁屏广播后使用“1”像素Activity提升优先级

1、该Activity的View只要设置为1像素然后设置在Window对象上即可。在Activity的onDestroy周期中进行保活服务的存活判断从而唤醒服务。"1像素"Activity如下

public class SinglePixelActivity extends AppCompatActivity {
    private static final String TAG = SinglePixelActivity.class.getSimpleName();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Window mWindow = getWindow();
        mWindow.setGravity(Gravity.LEFT | Gravity.TOP);
        WindowManager.LayoutParams attrParams = mWindow.getAttributes();
        attrParams.x = 0;
        attrParams.y = 0;
        attrParams.height = 1;
        attrParams.width = 1;
        mWindow.setAttributes(attrParams);
        ScreenManager.getInstance(this).setSingleActivity(this);
    }

    @Override
    protected void onDestroy() {
        if (!SystemUtils.isAppAlive(this, Constant.PACKAGE_NAME)) {
            Intent intentAlive = new Intent(this, DownloadService.class);
            startService(intentAlive);
        }
        super.onDestroy();
    }
}

2、对广播进行监听,封装为一个ScreenReceiverUtil类,进行锁屏解锁的广播动态注册监听

public class ScreenReceiverUtil {
    private Context mContext;
    private SreenBroadcastReceiver mScreenReceiver;
    private SreenStateListener mStateReceiverListener;

    public ScreenReceiverUtil(Context mContext) {
        this.mContext = mContext;
    }

    public void setScreenReceiverListener(SreenStateListener mStateReceiverListener) {
        this.mStateReceiverListener = mStateReceiverListener;
        // 动态启动广播接收器
        this.mScreenReceiver = new SreenBroadcastReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_USER_PRESENT);
        mContext.registerReceiver(mScreenReceiver, filter);
    }

    public void stopScreenReceiverListener() {
        mContext.unregisterReceiver(mScreenReceiver);
    }

    /**
     * 监听sreen状态对外回调接口
     */
    public interface SreenStateListener {
        void onSreenOn();

        void onSreenOff();

        void onUserPresent();
    }

    public class SreenBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (mStateReceiverListener == null) {
                return;
            }
            if (Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏
                mStateReceiverListener.onSreenOn();
            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏
                mStateReceiverListener.onSreenOff();
            } else if (Intent.ACTION_USER_PRESENT.equals(action)) { // 解锁
                mStateReceiverListener.onUserPresent();
            }
        }
    }
}

3、对1像素Activity进行防止内存泄露的处理,新建一个ScreenManager类

public class ScreenManager {
    private static final String TAG = ScreenManager.class.getSimpleName();
    private static ScreenManager sInstance;
    private Context mContext;
    private WeakReference<Activity> mActivity;

    private ScreenManager(Context mContext) {
        this.mContext = mContext;
    }

    public static ScreenManager getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new ScreenManager(context);
        }
        return sInstance;
    }

    /** 获得SinglePixelActivity的引用
     * @param activity
     */
    public void setSingleActivity(Activity activity) {
        mActivity = new WeakReference<>(activity);
    }

    /**
     * 启动SinglePixelActivity
     */
    public void startActivity() {
        Intent intent = new Intent(mContext, SinglePixelActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(intent);
    }

    /**
     * 结束SinglePixelActivity
     */
    public void finishActivity() {
        if (mActivity != null) {
            Activity activity = mActivity.get();
            if (activity != null) {
                activity.finish();
            }
        }
    }
}

4、对1像素的Style进行特殊处理,在style文件中新建一个SingleActivityStyle

<style name="SingleActivityStyle" parent="android:Theme.Holo.Light.NoActionBar">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowFrame">@null</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowIsFloating">true</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:backgroundDimEnabled">false</item>
        <item name="android:windowAnimationStyle">@null</item>
        <item name="android:windowDisablePreview">true</item>
        <item name="android:windowNoDisplay">false</item>

5、让SinglePixelActivity使用singleInstance启动模式,在manifest文件中

 <activity
            android:name=".activity.SinglePixelActivity"
            android:configChanges="keyboardHidden|orientation|screenSize|navigation|keyboard"
            android:excludeFromRecents="true"
            android:finishOnTaskLaunch="false"
            android:launchMode="singleInstance"
            android:theme="@style/SingleActivityStyle" />

6、在保活服务类DownloadService中对监听的广播进行注册和对SinglePixelActivity进行控制

public class DownloadService extends Service {
    public static final int NOTICE_ID = 100;
    private static final String TAG = DownloadService.class.getSimpleName();
    private DownloadBinder mDownloadBinder;
    private NotificationCompat.Builder mBuilderProgress;
    private NotificationManager mNotificationManager;

    private ScreenReceiverUtil mScreenListener;
    private ScreenManager mScreenManager;
    private Timer mRunTimer;

    private int mTimeSec;
    private int mTimeMin;
    private int mTimeHour;

    private ScreenReceiverUtil.SreenStateListener mScreenListenerer = new ScreenReceiverUtil.SreenStateListener() {
        @Override
        public void onSreenOn() {
            mScreenManager.finishActivity();
            Logger.d(TAG, "关闭了1像素Activity");
        }

        @Override
        public void onSreenOff() {
            mScreenManager.startActivity();
            Logger.d(TAG, "打开了1像素Activity");
        }

        @Override
        public void onUserPresent() {
        }
    };
    private OnTimeChangeListener mOnTimeChangeListener;

    @Override
    public void onCreate() {
        super.onCreate();

//        注册锁屏广播监听器
        mScreenListener = new ScreenReceiverUtil(this);
        mScreenManager = ScreenManager.getInstance(this);
        mScreenListener.setScreenReceiverListener(mScreenListenerer);
        
        mDownloadBinder = new DownloadBinder();
        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Logger.d(TAG, "onStartCommand");
        startRunTimer();
        return START_STICKY;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {

        return mDownloadBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Logger.d(TAG, "onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        NotificationManager mManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        if (mManager == null) {
            return;
        }
        mManager.cancel(NOTICE_ID);
        stopRunTimer();
//        mScreenListener.stopScreenReceiverListener();
    }


    private void startRunTimer() {
        TimerTask mTask = new TimerTask() {
            @Override
            public void run() {
                mTimeSec++;
                if (mTimeSec == 60) {
                    mTimeSec = 0;
                    mTimeMin++;
                }
                if (mTimeMin == 60) {
                    mTimeMin = 0;
                    mTimeHour++;
                }
                if (mTimeHour == 24) {
                    mTimeSec = 0;
                    mTimeMin = 0;
                    mTimeHour = 0;
                }
                String time = "时间为:" + mTimeHour + " : " + mTimeMin + " : " + mTimeSec;
                if (mOnTimeChangeListener != null) {
                    mOnTimeChangeListener.showTime(time);
                }
                Logger.d(TAG, time);
            }
        };
        mRunTimer = new Timer();
        // 每隔1s更新一下时间
        mRunTimer.schedule(mTask, 1000, 1000);
    }

    private void stopRunTimer() {
        if (mRunTimer != null) {
            mRunTimer.cancel();
            mRunTimer = null;
        }
        mTimeSec = 0;
        mTimeMin = 0;
        mTimeHour = 0;
        Logger.d(TAG, "时间为:" + mTimeHour + " : " + mTimeMin + " : " + mTimeSec);
    }

    public interface OnTimeChangeListener {
        void showTime(String time);
    }

    public class DownloadBinder extends Binder {
        public void setOnTimeChangeListener(OnTimeChangeListener onTimeChangeListener) {
            mOnTimeChangeListener = onTimeChangeListener;
        }
    }
}

3、在后台播放音乐

1、准备一段无声的音频,新建一个播放音乐的Service类,将播放模式改为无限循环播放。在其onDestroy方法中对自己重新启动。

public class PlayerMusicService extends Service {
    private final static String TAG = PlayerMusicService.class.getSimpleName();
    private MediaPlayer mMediaPlayer;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Logger.d(TAG, TAG + "---->onCreate,启动服务");
        mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silent);
        mMediaPlayer.setLooping(true);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                startPlayMusic();
            }
        }).start();
        return START_STICKY;
    }

    private void startPlayMusic() {
        if (mMediaPlayer != null) {
            Logger.d(TAG, "启动后台播放音乐");
            mMediaPlayer.start();
        }
    }

    private void stopPlayMusic() {
        if (mMediaPlayer != null) {
            Logger.d(TAG, "关闭后台播放音乐");
            mMediaPlayer.stop();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopPlayMusic();
        Logger.d(TAG, TAG + "---->onCreate,停止服务");
        // 重启自己
        Intent intent = new Intent(getApplicationContext(), PlayerMusicService.class);
        startService(intent);
    }
}

2、 在保活的DownloadServie服务类的onCreate方法中对PlayerMusicService进行启动

 Intent intent = new Intent(this, PlayerMusicService.class);
 startService(intent);

3、在Manifest文件中进行注册

  <service
            android:name=".service.PlayerMusicService"
            android:enabled="true"
            android:exported="true"
            android:process=":music_service" />

4、使用JobScheduler唤醒Service

1、新建一个继承自JobService的ScheduleService类,在其onStartJob回调中对DownloadService进行存活的判断来重启。

public class ScheduleService extends JobService {
    private static final String TAG = ScheduleService.class.getSimpleName();

    @Override
    public boolean onStartJob(JobParameters params) {

        boolean isServiceRunning = ServiceAliveUtils.isServiceAlice();
        if (!isServiceRunning) {
            Intent i = new Intent(this, DownloadService.class);
            startService(i);
            Logger.d(TAG, "ScheduleService启动了DownloadService");
        }
        jobFinished(params, false);
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
}

2、 在DownloadService服务类中进行JobScheduler的注册和使用

 /**
     * 使用JobScheduler进行保活
     */
    private void useJobServiceForKeepAlive() {
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        if (jobScheduler == null) {
            return;
        }
        jobScheduler.cancelAll();
        JobInfo.Builder builder =
            new JobInfo.Builder(1024, new ComponentName(getPackageName(), ScheduleService.class.getName()));
        //周期设置为了2s
        builder.setPeriodic(1000 * 2);
        builder.setPersisted(true);
        builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
        int schedule = jobScheduler.schedule(builder.build());
        if (schedule <= 0) {
            Logger.w(TAG, "schedule error!");
        }
    }

3、在manifest文件中进行权限设置

 <service
            android:name=".service.ScheduleService"
            android:enabled="true"
            android:exported="true"
     android:permission="android.permission.BIND_JOB_SERVICE" />

关于推送类拉活

根据华为官方文档集成HUAWEI Push

  • 1、华为畅玩5X(6.0):APP全部进程被杀死时可以被拉起。
  • 2、华为nove 3e(8.0):APP全部进程被杀死时无法被拉起,能收到推送。
  • 3、华为荣耀10(8.1):同2

结论:理论情况下,华为推送应该可以拉起华为机器才对,感觉是我没花钱的原因

补充:ServiceAliveUtils 类如下
public class ServiceAliveUtils {

    public static boolean isServiceAlice() {
        boolean isServiceRunning = false;
        ActivityManager manager =
            (ActivityManager) MyApplication.getMyApplication().getSystemService(Context.ACTIVITY_SERVICE);
        if (manager == null) {
            return true;
        }
        for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if ("demo.lgm.com.keepalivedemo.service.DownloadService".equals(service.service.getClassName())) {
                isServiceRunning = true;
            }
        }
        return isServiceRunning;
    }
}

作者:minminaya
链接:https://www.jianshu.com/p/b53...

阅读更多

做后台是选择Java 、Go ,还是 PHP?

NDK项目实战—高仿360手机助手之卸载监听

AndroidUtils:Android开发不得不收藏的Utils

(Android)面试题级答案(精选版)

Google开发者大会:你不得不知的Tensorflow小技巧

相信自己,没有做不到的,只有想不到的

在这里获得的不仅仅是技术!

查看原文

认证与成就

  • 获得 3 次点赞
  • 获得 18 枚徽章 获得 0 枚金徽章, 获得 4 枚银徽章, 获得 14 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-03-07
个人主页被 649 人浏览