后台性能测试不可不知的二三事

某月黑风高之夜,某打车平台上线了一大波(G+)优惠活动,众人纷纷下单。于是乎,该打车平台使用的智能提示服务扛不住直接趴窝了(如下图)。事后,负责智能提示服务开发和运维的有关部门开会后决定:必须对智能提示服务进行一次全面深入的性能摸底,立刻!现在!马上!

那么一大坨问题就迎面而来:对于智能提示这样的后台服务,性能测试过程中应该关心那些指标?这些指标代表什么含义?这些指标的通过标准是什么?下面将为您一一解答。

概述

      不同人群关注的性能指标各有侧重。后台服务接口的调用者一般只关心吞吐量、响应时间等外部指标。后台服务的所有者不仅仅关注外部指标,还会关注CPU、内存、负载等内部指标。

拿某打车平台来说,它所关心的是智能提示的外部指标能不能抗住因大波优惠所导致的流量激增。而对于智能提示服务的开发、运维、测试人员,不仅仅关注外部指标,还会关注CPU、内存、IO等内部指标,以及部署方式、服务器软硬件配置等运维相关事项。

外部指标

从外部看,性能测试主要关注如下三个指标

  • 吞吐量:每秒钟系统能够处理的请求数、任务数。
  • 响应时间:服务处理一个请求或一个任务的耗时。
  • 错误率:一批请求中结果出错的请求所占比例。

响应时间的指标取决于具体的服务。如智能提示一类的服务,返回的数据有效周期短(用户多输入一个字母就需要重新请求),对实时性要求比较高,响应时间的上限一般在100ms以内。而导航一类的服务,由于返回结果的使用周期比较长(整个导航过程中),响应时间的上限一般在2-5s。

对于响应时间的统计,应从均值、.90、.99、分布等多个角度统计,而不仅仅是给出均值。下图是响应时间统计的一个例子

吞吐量的指标受到响应时间、服务器软硬件配置、网络状态等多方面因素影响。

  • 吞吐量越大,响应时间越长。
  • 服务器硬件配置越高,吞吐量越大。
  • 网络越差,吞吐量越小。

在低吞吐量下的响应时间的均值、分布比较稳定,不会产生太大的波动。

在高吞吐量下,响应时间会随着吞吐量的增长而增长,增长的趋势可能是线性的,也可能接近指数的。当吞吐量接近系统的峰值时,响应时间会出现激增。

错误率和服务的具体实现有关。通常情况下,由于网络超时等外部原因造成的错误比例不应超过5%%,由于服务本身导致的错误率不应超过1% 。

内部指标

从服务器的角度看,性能测试主要关注CPU、内存、服务器负载、网络、磁盘IO等

CPU

后台服务的所有指令和数据处理都是由CPU负责,服务对CPU的利用率对服务的性能起着决定性的作用。

Linux系统的CPU主要有如下几个维度的统计数据

  • us:用户态使用的cpu时间百分比
  • sy:系统态使用的cpu时间百分比
  • ni:用做nice加权的进程分配的用户态cpu时间百分比
  • id:空闲的cpu时间百分比
  • wa:cpu等待IO完成时间百分比
  • hi:硬中断消耗时间百分比
  • si:软中断消耗时间百分比

下图是线上开放平台转发服务某台服务器上top命令的输出,下面以这个服务为例对CPU各项指标进行说明

us & sy:大部分后台服务使用的CPU时间片中us和sy的占用比例是最高的。同时这两个指标又是互相影响的,us的比例高了,sy的比例就低,反之亦然。通常sy比例过高意味着被测服务在用户态和系统态之间切换比较频繁,此时系统整体性能会有一定下降。另外,在使用多核CPU的服务器上,CPU 0负责CPU各核间的调度,CPU 0上的使用率过高会导致其他CPU核心之间的调度效率变低。因此测试过程中CPU 0需要重点关注。

ni:每个Linux进程都有个优先级,优先级高的进程有优先执行的权利,这个叫做pri。进程除了优先级外,还有个优先级的修正值。这个修正值就叫做进程的nice值。一般来说,被测服务和服务器整体的ni值不会很高。如果测试过程中ni的值比较高,需要从服务器Linux系统配置、被测服务运行参数查找原因

id:线上服务运行过程中,需要保留一定的id冗余来应对突发的流量激增。在性能测试过程中,如果id一直很低,吞吐量上不去,需要检查被测服务线程/进程配置、服务器系统配置等。

wa:磁盘、网络等IO操作会导致CPU的wa指标提高。通常情况下,网络IO占用的wa资源不会很高,而频繁的磁盘读写会导致wa激增。如果被测服务不是IO密集型的服务,那需要检查被测服务的日志量、数据载入频率等。

hi & si:硬中断是外设对CPU的中断,即外围硬件发给CPU或者内存的异步信号就是硬中断信号;软中断由软件本身发给操作系统内核的中断信号。通常是由硬中断处理程序或进程调度程序对操作系统内核的中断,也就是我们常说的系统调用(System Call)。在性能测试过程中,hi会有一定的CPU占用率,但不会太高。对于IO密集型的服务,si的CPU占用率会高一些。

内存

性能测试过程中对内存监控的主要目的是检查被测服务所占用内存的波动情况。

在Linux系统中有多个命令可以获取指定进程的内存使用情况,最常用的是top命令,如下图所示

其中

  • VIRT:进程所使用的虚拟内存的总数。它包括所有的代码,数据和共享库,加上已换出的页面,所有已申请的总内存空间
  • RES:进程正在使用的没有交换的物理内存(栈、堆),申请内存后该内存段已被重新赋值
  • SHR:进程使用共享内存的总数。该数值只是反映可能与其它进程共享的内存,不代表这段内存当前正被其他进程使用
  • SWAP:进程使用的虚拟内存中被换出的大小,交换的是已经申请,但没有使用的空间,包括(栈、堆、共享内存)
  • DATA:进程除可执行代码以外的物理内存总量,即进程栈、堆申请的总空间

从上面的解释可以看出,测试过程中主要监控RES和VIRT,对于使用了共享内存的多进程架构服务,还需要监沙发控SHR。

LOAD(服务器负载)

Linux的系统负载指运行队列的平均长度,也就是等待CPU的平均进程数

从服务器负载的定义可以看出,服务器运行最理想的状态是所有CPU核心的运行队列都为1,即所有活动进程都在运行,没有等待。这种状态下服务器运行在负载阈值下。

通常情况下,按照经验值,服务器的负载应位于阈值的70%~80%,这样既能利用服务器大部分性能,又留有一定的性能冗余应对流量增长。

Linux提供了很多查看系统负载的命令,最常用的是top和uptime

top和uptime针对负载的输出内容相同,都是系统最近1分钟、5分钟、15分钟的负载均值

查看系统负载阈值的命令如下

在性能测试过程中,系统负载是评价整个系统运行状况最重要的指标之一。通常情况下,压力测试时系统负载应接近但不能超过阈值,并发测试时的系统负载最高不能超过阈值的80%,稳定性测试时,系统负载应在阈值的50%左右。

网络

性能测试中网络监控主要包括网络流量、网络连接状态的监控。

网络流量监控

可以使用nethogs命令。该命令与top类似,是一个实时交互的命令,运行界面如下

在后台服务性能测试中,对于返回文本结果的服务,并不需要太多关注在流量方面。

网络连接状态监控

性能测试中对网络的监控主要是监控网络连接状态的变化和异常。对于使用TCP协议的服务,需要监控服务已建立连接的变化情况(即ESTABLISHED状态的TCP连接)。对于HTTP协议的服务,需要监控被测服务对应进程的网络缓冲区的状态、TIME_WAIT状态的连接数等。Linux自带的很多命令如netstat、ss都支持如上功能。下图是netstat对指定pid进程的监控结果。

磁盘IO

性能测试过程中,如果被测服务对磁盘读写过于频繁,会导致大量请求处于IO等待的状态,系统负载升高,响应时间变长,吞吐量下降。

Linux下可以用iostat命令来监控磁盘状态,如下图

  • tps:该设备每秒的传输次数。“一次传输”意思是“一次I/O请求”。多个逻辑请求可能会被合并为“一次I/O请求”。“一次传输”请求的大小是未知的
  • kB_read/s:每秒从设备(driveexpressed)读取的数据量,单位为Kilobytes
  • kB_wrtn/s:每秒向设备(driveexpressed)写入的数据量,单位为Kilobytes
  • kB_read:读取的总数据量,单位为Kilobytes
  • kB_wrtn:写入的总数量数据量,单位为Kilobytes

从iostat的输出中,能够获得系统运行最基本的统计数据。但对于性能测试来说,这些数据不能提供更多的信息。需要加上-x参数

  • rrqm/s:每秒这个设备相关的读取请求有多少被Merge了(当系统调用需要读取数据的时候,VFS将请求发到各个FS,如果FS发现不同的读取请求读取的是相同Block的数据,FS会将这个请求合并Merge)
  • wrqm/s:每秒这个设备相关的写入请求有多少被Merge了
  • await:每一个IO请求的处理的平均时间(单位是毫秒)
  • %util:在统计时间内所有处理IO时间,除以总共统计时间。例如,如果统计间隔1秒,该设备有0.8秒在处理IO,而0.2秒闲置,那么该设备的%util = 0.8/1 = 80%,该参数暗示了设备的繁忙程度。

常见性能瓶颈

  • 吞吐量到上限时系统负载未到阈值:一般是被测服务分配的系统资源过少导致的。测试过程中如果发现此类情况,可以从ulimit、系统开启的线程数、分配的内存等维度定位问题原因
  • CPU的us和sy不高,但wa很高:如果被测服务是磁盘IO密集型型服务,wa高属于正常现象。但如果不是此类服务,最可能导致wa高的原因有两个,一是服务对磁盘读写的业务逻辑有问题,读写频率过高,写入数据量过大,如不合理的数据载入策略、log过多等,都有可能导致这种问题。二是服务器内存不足,服务在swap分区不停的换入换出。
  • 同一请求的响应时间忽大忽小:在正常吞吐量下发生此问题,可能的原因有两方面,一是服务对资源的加锁逻辑有问题,导致处理某些请求过程中花了大量的时间等待资源解锁;二是Linux本身分配给服务的资源有限,某些请求需要等待其他请求释放资源后才能继续执行。
  • 内存持续上涨:在吞吐量固定的前提下,如果内存持续上涨,那么很有可能是被测服务存在明显的内存泄漏,需要使用valgrind等内存检查工具进行定位。

举个 (栗子) 例子

智能提示服务趴窝了以后,必须立刻对其做性能摸底。根据目前的情况,测试结果中需要提供外部指标和内部指标。

智能提示服务的架构和每个模块的功能如下图所示

从图中我们可以看出,测试前智能提示服务的底层数据服务已经确定了性能上限。因此,本次测试我们的任务是在底层数据服务性能为3500qps的前提下,找到智能提示服务上游各个模块的性能上限。

一个完整的后台服务性能测试流程如下图所示。

测试前准备:
  • 测试数据:由于智能提示已经在线上运行,本次测试使用智能提示趴窝那天的日志作为测试数据
  • QPS预估:本次测试就是为了找这个数
  • 服务器配置:使用与线上软硬件配置相同的服务器
压测过程:

我们使用Jmeter发送测试数据来模拟用户请求,Jmeter测试配置文件使用的原件如下图所示。从图中可以看出,性能测试的配置文件主要由数据文件配置(线程间共享方式、到达末尾时的行为等)、吞吐量控制、HTTP采样器(域名、端口、HTTP METHOD、请求body等)、响应断言(对返回结果的内容进行校验)。

数据文件配置

吞吐量控制

HTTP请求采样

响应断言

  • CPU

在linux中,sar、top、ps等命令都可以对cpu使用情况进行监控。一般来说,最常用的是top命令。top命令的输出如下:

top命令是一个交互式命令,运行后会一直保持在终端并定时刷新。在性能测试中,可以使用如下参数让top命令只运行一次

$top –n 1 –b –p ${pid}

 

  • 服务器负载

linux中,服务器负载使用uptime命令获取,该命令的输出如下图

每一列的含义如下:

“当前时间 系统运行时长 登录的用户数最 近1分钟、5分钟、15分钟的平均负载”

 

  • 内存

在linux中, top、ps命令都可以对指定进程的内存使用状况进行查看。但最准确的信息在/proc/${PID}/status中,如下图

上面命令的输出中,我们重点关注VmRSS、VmData、VmSize

 

  • 磁盘IO

磁盘监控数据使用iostat命令获取

测试报告输出

在统计完性能测试过程中收集的监控指标后,就可以输出性能报告了。

通常来说,性能报告中要包含如下内容:

  • 测试结论:包括被测服务最大QPS、响应时间等指标是否达到期望,部署建议等。
  • 测试环境描述:包括性能需求、测试用服务器配置、测试数据来源、测试方法等
  • 监控指标统计:响应时间统计、QPS、服务器指标统计、进程指标统计。建议最好用图表来表示统计数据。

结语

测试完毕后,得出的结论是单台智能提示服务的性能是300qps,线上整套智能提示服务的性能是1800qps;而月黑风高那天的流量大概是5000qps+,难怪智能提示趴窝,确实流量太大,远超线上的吞吐容量。

最后,智能提示服务申请了服务器进行扩容,并对某打车平台的流量进行了限制,既开源又节流,保证今后月黑风高之夜一众约酒、约饭、约P之人的打车体验,提高了各种约的成功率,可谓功德无量。

正则表达式30分钟入门教程

园子之前写的一篇正则表达式教程,部分翻译自codeproject的The 30 Minute Regex Tutorial

由于评论里有过长的URL,所以本页排版比较混乱,推荐你到原处查看,看完了如果有问题,再到这里来提出.

一些要说的话:

  1. 如果你没有正则表达式的基础,请跟着教程“一步步来”。请不要大概地扫两眼就说看不懂——以这种态度我写成什么样你也看不懂。当我告诉你这是“30分钟入门教程”时,请不要试图在30秒内入门。

    事实是,我身边有个才接触电脑,对操作都不是很熟练的人通过自己学习这篇教程,最后都能在文章采集系统中使用正则表达式完成任务。而且,他写的表达式中,还使用了“零宽断言”等“高级”技术。

    所以,如果你能具体地说明你的问题,我很愿意帮助你。但是如果你概括地说看不懂,那不是我的问题。

  2. 欢迎转载,但请声明作者以及来源。

正则表达式30分钟入门教程

版本:v2.31 (2009-4-11) 作者:deerchao 转载请注明来源

目录

跳过目录

  1. 本文目标
  2. 如何使用本教程
  3. 正则表达式到底是什么东西?
  4. 入门
  5. 测试正则表达式
  6. 元字符
  7. 字符转义
  8. 重复
  9. 字符类
  10. 分枝条件
  11. 反义
  12. 分组
  13. 后向引用
  14. 零宽断言
  15. 负向零宽断言
  16. 注释
  17. 贪婪与懒惰
  18. 处理选项
  19. 平衡组/递归匹配
  20. 还有些什么东西没提到
  21. 联系作者
  22. 网上的资源及本文参考文献
  23. 更新纪录

本文目标

30分钟内让你明白正则表达式是什么,并对它有一些基本的了解,让你可以在自己的程序或网页里使用它。

如何使用本教程

最重要的是——请给我30分钟,如果你没有使用正则表达式的经验,请不要试图在30内入门——除非你是超人 :)

别被下面那些复杂的表达式吓倒,只要跟着我一步一步来,你会发现正则表达式其实并没有你想像中的那么困难。当然,如果你看完了这篇教程之后,发现自己明白了很多,却又几乎什么都记不得,那也是很正常的——我认为,没接触过正则表达式的人在看完这篇教程后,能把提到过的语法记住80%以上的可能性为零。这里只是让你明白基本的原理,以后你还需要多练习,多使用,才能熟练掌握正则表达式。

除了作为入门教程之外,本文还试图成为可以在日常工作中使用的正则表达式语法参考手册。就作者本人的经历来说,这个目标还是完成得不错的——你看,我自己也没能把所有的东西记下来,不是吗?

清除格式 文本格式约定:专业术语 元字符/语法格式 正则表达式 正则表达式中的一部分(用于分析) 对其进行匹配的源字符串 对正则表达式或其中一部分的说明

隐藏边注 本文右边有一些注释,主要是用来提供一些相关信息,或者给没有程序员背景的读者解释一些基本概念,通常可以忽略。

正则表达式到底是什么东西?

字符是计算机软件处理文字时最基本的单位,可能是字母,数字,标点符号,空格,换行符,汉字等等。字符串是0个或更多个字符的序列。文本也就是文字,字符串。说某个字符串匹配某个正则表达式,通常是指这个字符串里有一部分(或几部分分别)能满足表达式给出的条件。

在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。

很可能你使用过Windows/Dos下用于文件查找的通配符(wildcard),也就是*?。如果你想查找某个目录下的所有的Word文档的话,你会搜索*.doc。在这里,*会被解释成任意的字符串。和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比起通配符,它能更精确地描述你的需求——当然,代价就是更复杂——比如你可以编写一个正则表达式,用来查找所有以0开头,后面跟着2-3个数字,然后是一个连字号“-”,最后是7或8位数字的字符串(像010-123456780376-7654321)。

入门

学习正则表达式的最好方法是从例子开始,理解例子之后再自己对例子进行修改,实验。下面给出了不少简单的例子,并对它们作了详细的说明。

假设你在一篇英文小说里查找hi,你可以使用正则表达式hi

这几乎是最简单的正则表达式了,它可以精确匹配这样的字符串:由两个字符组成,前一个字符是h,后一个是i。通常,处理正则表达式的工具会提供一个忽略大小写的选项,如果选中了这个选项,它可以匹配hi,HI,Hi,hI这四种情况中的任意一种。

不幸的是,很多单词里包含hi这两个连续的字符,比如him,history,high等等。用hi来查找的话,这里边的hi也会被找出来。如果要精确地查找hi这个单词的话,我们应该使用\bhi\b

\b是正则表达式规定的一个特殊代码(好吧,某些人叫它元字符,metacharacter),代表着单词的开头或结尾,也就是单词的分界处。虽然通常英文的单词是由空格,标点符号或者换行来分隔的,但是\b并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置

如果需要更精确的说法,\b匹配这样的位置:它的前一个字符和后一个字符不全是(一个是,一个不是或不存在)\w

假如你要找的是hi后面不远处跟着一个Lucy,你应该用\bhi\b.*\bLucy\b

这里,.是另一个元字符,匹配除了换行符以外的任意字符*同样是元字符,不过它代表的不是字符,也不是位置,而是数量——它指定*前边的内容可以连续重复使用任意次以使整个表达式得到匹配。因此,.*连在一起就意味着任意数量的不包含换行的字符。现在\bhi\b.*\bLucy\b的意思就很明显了:先是一个单词hi,然后是任意个任意字符(但不能是换行),最后是Lucy这个单词

换行符就是’\n’,ASCII编码为10(十六进制0x0A)的字符。

如果同时使用其它元字符,我们就能构造出功能更强大的正则表达式。比如下面这个例子:

0\d\d-\d\d\d\d\d\d\d\d匹配这样的字符串:以0开头,然后是两个数字,然后是一个连字号“-”,最后是8个数字(也就是中国的电话号码。当然,这个例子只能匹配区号为3位的情形)。

这里的\d是个新的元字符,匹配一位数字(0,或1,或2,或……)不是元字符,只匹配它本身——连字符(或者减号,或者中横线,或者随你怎么称呼它)。

为了避免那么多烦人的重复,我们也可以这样写这个表达式:0\d{2}-\d{8}。 这里\d后面的{2}({8})的意思是前面\d必须连续重复匹配2次(8次)

测试正则表达式

如果你不觉得正则表达式很难读写的话,要么你是一个天才,要么,你不是地球人。正则表达式的语法很令人头疼,即使对经常使用它的人来说也是如此。由于难于读写,容易出错,所以找一种工具对正则表达式进行测试是很有必要的。

不同的环境下正则表达式的一些细节是不相同的,本教程介绍的是微软 .Net Framework 4.0 下正则表达式的行为,所以,我向你推荐我编写的.Net下的工具 正则表达式测试器。请参考该页面的说明来安装和运行该软件。

下面是Regex Tester运行时的截图:

正则表达式测试器运行截图

元字符

现在你已经知道几个很有用的元字符了,如\b,.,*,还有\d.正则表达式里还有更多的元字符,比如\s匹配任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格等\w匹配字母或数字或下划线或汉字等

对中文/汉字的特殊处理是由.Net提供的正则表达式引擎支持的,其它环境下的具体情况请查看相关文档。

下面来看看更多的例子:

\ba\w*\b匹配以字母a开头的单词——先是某个单词开始处(\b),然后是字母a,然后是任意数量的字母或数字(\w*),最后是单词结束处(\b)

好吧,现在我们说说正则表达式里的单词是什么意思吧:就是不少于一个的连续的\w。不错,这与学习英文时要背的成千上万个同名的东西的确关系不大 :)

\d+匹配1个或更多连续的数字。这里的+是和*类似的元字符,不同的是*匹配重复任意次(可能是0次),而+则匹配重复1次或更多次

\b\w{6}\b 匹配刚好6个字符的单词

表1.常用的元字符
代码 说明
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束

正则表达式引擎通常会提供一个“测试指定的字符串是否匹配一个正则表达式”的方法,如JavaScript里的RegExp.test()方法或.NET里的Regex.IsMatch()方法。这里的匹配是指是字符串里有没有符合表达式规则的部分。如果不使用^$的话,对于\d{5,12}而言,使用这样的方法就只能保证字符串里包含5到12连续位数字,而不是整个字符串就是5到12位数字。

元字符^(和数字6在同一个键位上的符号)和$都匹配一个位置,这和\b有点类似。^匹配你要用来查找的字符串的开头,$匹配结尾。这两个代码在验证输入的内容时非常有用,比如一个网站如果要求你填写的QQ号必须为5位到12位数字时,可以使用:^\d{5,12}$

这里的{5,12}和前面介绍过的{2}是类似的,只不过{2}匹配只能不多不少重复2次{5,12}则是重复的次数不能少于5次,不能多于12次,否则都不匹配。

因为使用了^$,所以输入的整个字符串都要用来和\d{5,12}来匹配,也就是说整个输入必须是5到12个数字,因此如果输入的QQ号能匹配这个正则表达式的话,那就符合要求了。

和忽略大小写的选项类似,有些正则表达式处理工具还有一个处理多行的选项。如果选中了这个选项,^$的意义就变成了匹配行的开始处和结束处

字符转义

如果你想查找元字符本身的话,比如你查找.,或者*,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。这时你就得使用\来取消这些字符的特殊意义。因此,你应该使用\.\*。当然,要查找\本身,你也得用\\.

例如:unibetter\.com匹配unibetter.comC:\\Windows匹配C:\Windows

重复

你已经看过了前面的*,+,{2},{5,12}这几个匹配重复的方式了。下面是正则表达式中所有的限定符(指定数量的代码,例如*,{5,12}等):

表2.常用的限定符
代码/语法 说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次

下面是一些使用重复的例子:

Windows\d+匹配Windows后面跟1个或更多数字

^\w+匹配一行的第一个单词(或整个字符串的第一个单词,具体匹配哪个意思得看选项设置)

字符类

要想查找数字,字母或数字,空白是很简单的,因为已经有了对应这些字符集合的元字符,但是如果你想匹配没有预定义元字符的字符集合(比如元音字母a,e,i,o,u),应该怎么办?

很简单,你只需要在方括号里列出它们就行了,像[aeiou]就匹配任何一个英文元音字母[.?!]匹配标点符号(.或?或!)

我们也可以轻松地指定一个字符范围,像[0-9]代表的含意与\d就是完全一致的:一位数字;同理[a-z0-9A-Z_]也完全等同于\w(如果只考虑英文的话)。

下面是一个更复杂的表达式:\(?0\d{2}[) -]?\d{8}

“(”和“)”也是元字符,后面的分组节里会提到,所以在这里需要使用转义

这个表达式可以匹配几种格式的电话号码,像(010)88886666,或022-22334455,或02912345678等。我们对它进行一些分析吧:首先是一个转义字符\(,它能出现0次或1次(?),然后是一个0,后面跟着2个数字(\d{2}),然后是)空格中的一个,它出现1次或不出现(?),最后是8个数字(\d{8})。

分枝条件

不幸的是,刚才那个表达式也能匹配010)12345678(022-87654321这样的“不正确”的格式。要解决这个问题,我们需要用到分枝条件。正则表达式里的分枝条件指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用|把不同的规则分隔开。听不明白?没关系,看例子:

0\d{2}-\d{8}|0\d{3}-\d{7}这个表达式能匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)

\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}这个表达式匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。你可以试试用分枝条件把这个表达式扩展成也支持4位区号的。

\d{5}-\d{4}|\d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。之所以要给出这个例子是因为它能说明一个问题:使用分枝条件时,要注意各个条件的顺序。如果你把它改成\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。

分组

我们已经提到了怎么重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复多个字符又该怎么办?你可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作(后面会有介绍)。

(\d{1,3}\.){3}\d{1,3}是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3}匹配1到3位的数字(\d{1,3}\.){3}匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3})。

IP地址中每个数字都不能大于255,大家千万不要被《24》第三季的编剧给忽悠了……

不幸的是,它也将匹配256.300.888.999这种不可能存在的IP地址。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)

理解这个表达式的关键是理解2[0-4]\d|25[0-5]|[01]?\d\d?,这里我就不细说了,你自己应该能分析得出来它的意义。

反义

有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义

表3.常用的反义代码
代码/语法 说明
\W 匹配任意不是字母,数字,下划线,汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou这几个字母以外的任意字符

例子:\S+匹配不包含空白符的字符串

<a[^>]+>匹配用尖括号括起来的以a开头的字符串

后向引用

使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。

呃……其实,组号分配还不像我刚说得那么简单:

  • 分组0对应整个正则表达式
  • 实际上组号分配过程是要从左向右扫描两遍的:第一遍只给未命名组分配,第二遍只给命名组分配--因此所有命名组的组号都大于未命名的组号
  • 你可以使用(?:exp)这样的语法来剥夺一个分组对组号分配的参与权.

后向引用用于重复搜索前面某个分组匹配的文本。例如,\1代表分组1匹配的文本。难以理解?请看示例:

\b(\w+)\b\s+\1\b可以用来匹配重复的单词,像go go, 或者kitty kitty。这个表达式首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字(\b(\w+)\b),这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符(\s+),最后是分组1中捕获的内容(也就是前面匹配的那个单词)(\1)。

你也可以自己指定子表达式的组名。要指定一个子表达式的组名,请使用这样的语法:(?<Word>\w+)(或者把尖括号换成也行:(?’Word’\w+)),这样就把\w+的组名指定为Word了。要反向引用这个分组捕获的内容,你可以使用\k<Word>,所以上一个例子也可以写成这样:\b(?<Word>\w+)\b\s+\k<Word>\b

使用小括号的时候,还有很多特定用途的语法。下面列出了最常用的一些:

表4.常用分组语法
分类 代码/语法 说明
捕获 (exp) 匹配exp,并捕获文本到自动命名的组里
(?<name>exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?’name’exp)
(?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号
零宽断言 (?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置
注释 (?#comment) 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

我们已经讨论了前两种语法。第三个(?:exp)不会改变正则表达式的处理方式,只是这样的组匹配的内容不会像前两种那样被捕获到某个组里面,也不会拥有组号。“我为什么会想要这样做?”——好问题,你觉得为什么呢?

零宽断言

地球人,是不是觉得这些术语名称太复杂,太难记了?我也有同感。知道有这么一种东西就行了,它叫什么,随它去吧!人若无名,便可专心练剑;物若无名,便可随意取舍……

接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。最好还是拿例子来说明吧:

断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。

(?=exp)也叫零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I’m singing while you’re dancing.时,它会匹配singdanc

(?<=exp)也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。比如(?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading

假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分:((?<=\d)\d{3})+\b,用它对1234567890进行查找时结果是234567890

下面这个例子同时使用了这两种断言:(?<=\s)\d+(?=\s)匹配以空白符间隔的数字(再次强调,不包括这些空白符)

负向零宽断言

前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果我们想查找这样的单词–它里面出现了字母q,但是q后面跟的不是字母u,我们可以尝试这样:

\b\w*q[^u]\w*\b匹配包含后面不是字母u的字母q的单词。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的\w*\b将会匹配下一个单词,于是\b\w*q[^u]\w*\b就能匹配整个Iraq fighting负向零宽断言能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:\b\w*q(?!u)\w*\b

零宽度负预测先行断言(?!exp)断言此位置的后面不能匹配表达式exp。例如:\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词

同理,我们可以用(?<!exp),零宽度负回顾后发断言断言此位置的前面不能匹配表达式exp(?<![a-z])\d{7}匹配前面不是小写字母的七位数字

请详细分析表达式(?<=<(\w+)>).*(?=<\/\1>),这个表达式最能表现零宽断言的真正用途。

一个更复杂的例子:(?<=<(\w+)>).*(?=<\/\1>)匹配不包含属性的简单HTML标签内里的内容(?<=<(\w+)>)指定了这样的前缀被尖括号括起来的单词(比如可能是<b>),然后是.*(任意的字符串),最后是一个后缀(?=<\/\1>)。注意后缀里的\/,它用到了前面提过的字符转义;\1则是一个反向引用,引用的正是捕获的第一组,前面的(\w+)匹配的内容,这样如果前缀实际上是<b>的话,后缀就是</b>了。整个表达式匹配的是<b>和</b>之间的内容(再次提醒,不包括前缀和后缀本身)。

注释

小括号的另一种用途是通过语法(?#comment)来包含注释。例如:2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)

要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意的添加空格,Tab,换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉。例如,我们可以前面的一个表达式写成这样:

      (?<=    # 断言要匹配的文本的前缀
      <(\w+)> # 查找尖括号括起来的字母或数字(即HTML/XML标签)
      )       # 前缀结束
      .*      # 匹配任意文本
      (?=     # 断言要匹配的文本的后缀
      <\/\1>  # 查找尖括号括起来的内容:前面是一个"/",后面是先前捕获的标签
      )       # 后缀结束

贪婪与懒惰

当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以这个表达式为例:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。

有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。现在看看懒惰版的例子吧:

a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)ab(第四到第五个字符)

为什么第一个匹配是aab(第一到第三个字符)而不是ab(第二到第三个字符)?简单地说,因为正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权——The match that begins earliest wins。

表5.懒惰限定符
代码/语法 说明
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

处理选项

在C#中,你可以使用Regex(String, RegexOptions)构造函数来设置正则表达式的处理选项。如:Regex regex = new Regex(@”\ba\w{6}\b”, RegexOptions.IgnoreCase);

上面介绍了几个选项如忽略大小写,处理多行等,这些选项能用来改变处理正则表达式的方式。下面是.Net中常用的正则表达式选项:

表6.常用的处理选项
名称 说明
IgnoreCase(忽略大小写) 匹配时不区分大小写。
Multiline(多行模式) 更改^$的含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下,$的精确含意是:匹配\n之前的位置以及字符串结束前的位置.)
Singleline(单行模式) 更改.的含义,使它与每一个字符匹配(包括换行符\n)。
IgnorePatternWhitespace(忽略空白) 忽略表达式中的非转义空白并启用由#标记的注释。
ExplicitCapture(显式捕获) 仅捕获已被显式命名的组。

一个经常被问到的问题是:是不是只能同时使用多行模式和单行模式中的一种?答案是:不是。这两个选项之间没有任何关系,除了它们的名字比较相似(以至于让人感到疑惑)以外。

平衡组/递归匹配

这里介绍的平衡组语法是由.Net Framework支持的;其它语言/库不一定支持这种功能,或者支持此功能但需要使用不同的语法。

有时我们需要匹配像( 100 * ( 50 + 15 ) )这样的可嵌套的层次性结构,这时简单地使用\(.+\)则只会匹配到最左边的左括号和最右边的右括号之间的内容(这里我们讨论的是贪婪模式,懒惰模式也有下面的问题)。假如原来的字符串里的左括号和右括号出现的次数不相等,比如( 5 / ( 3 + 2 ) ) ),那我们的匹配结果里两者的个数也不会相等。有没有办法在这样的字符串里匹配到最长的,配对的括号之间的内容呢?

为了避免(\(把你的大脑彻底搞糊涂,我们还是用尖括号代替圆括号吧。现在我们的问题变成了如何把xx <aa <bbb> <bbb> aa> yy这样的字符串里,最长的配对的尖括号内的内容捕获出来?

这里需要用到以下的语法构造:

  • (?’group’) 把捕获的内容命名为group,并压入堆栈(Stack)
  • (?’-group’) 从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败
  • (?(group)yes|no) 如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分
  • (?!) 零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败

如果你不是一个程序员(或者你自称程序员但是不知道堆栈是什么东西),你就这样理解上面的三种语法吧:第一个就是在黑板上写一个”group”,第二个就是从黑板上擦掉一个”group”,第三个就是看黑板上写的还有没有”group”,如果有就继续匹配yes部分,否则就匹配no部分。

我们需要做的是每碰到了左括号,就在压入一个”Open”,每碰到一个右括号,就弹出一个,到了最后就看看堆栈是否为空--如果不为空那就证明左括号比右括号多,那匹配就应该失败。正则表达式引擎会进行回溯(放弃最前面或最后面的一些字符),尽量使整个表达式得到匹配。

<                         #最外层的左括号
    [^<>]*                #最外层的左括号后面的不是括号的内容
    (
        (
            (?'Open'<)    #碰到了左括号,在黑板上写一个"Open"
            [^<>]*       #匹配左括号后面的不是括号的内容
        )+
        (
            (?'-Open'>)   #碰到了右括号,擦掉一个"Open"
            [^<>]*        #匹配右括号后面不是括号的内容
        )+
    )*
    (?(Open)(?!))         #在遇到最外层的右括号前面,判断黑板上还有没有没擦掉的"Open";如果还有,则匹配失败

>                         #最外层的右括号

平衡组的一个最常见的应用就是匹配HTML,下面这个例子可以匹配嵌套的<div>标签<div[^>]*>[^<>]*(((?’Open'<div[^>]*>)[^<>]*)+((?’-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>.

还有些什么东西没提到

上边已经描述了构造正则表达式的大量元素,但是还有很多没有提到的东西。下面是一些未提到的元素的列表,包含语法和简单的说明。你可以在网上找到更详细的参考资料来学习它们–当你需要用到它们的时候。如果你安装了MSDN Library,你也可以在里面找到.net下正则表达式详细的文档。

这里的介绍很简略,如果你需要更详细的信息,而又没有在电脑上安装MSDN Library,可以查看关于正则表达式语言元素的MSDN在线文档

表7.尚未详细讨论的语法
代码/语法 说明
\a 报警字符(打印它的效果是电脑嘀一声)
\b 通常是单词分界位置,但如果在字符类里使用代表退格
\t 制表符,Tab
\r 回车
\v 竖向制表符
\f 换页符
\n 换行符
\e Escape
\0nn ASCII代码中八进制代码为nn的字符
\xnn ASCII代码中十六进制代码为nn的字符
\unnnn Unicode代码中十六进制代码为nnnn的字符
\cN ASCII控制字符。比如\cC代表Ctrl+C
\A 字符串开头(类似^,但不受处理多行选项的影响)
\Z 字符串结尾或行尾(不受处理多行选项的影响)
\z 字符串结尾(类似$,但不受处理多行选项的影响)
\G 当前搜索的开头
\p{name} Unicode中命名为name的字符类,例如\p{IsGreek}
(?>exp) 贪婪子表达式
(?<x>-<y>exp) 平衡组
(?im-nsx:exp) 在子表达式exp中改变处理选项
(?im-nsx) 为表达式后面的部分改变处理选项
(?(exp)yes|no) 把exp当作零宽正向先行断言,如果在这个位置能匹配,使用yes作为此组的表达式;否则使用no
(?(exp)yes) 同上,只是使用空表达式作为no
(?(name)yes|no) 如果命名为name的组捕获到了内容,使用yes作为表达式;否则使用no
(?(name)yes) 同上,只是使用空表达式作为no

联系作者

好吧,我承认,我骗了你,读到这里你肯定花了不止30分钟.相信我,这是我的错,而不是因为你太笨.我之所以说”30分钟”,是为了让你有信心,有耐心继续下去.既然你看到了这里,那证明我的阴谋成功了.被忽悠的感觉很爽吧?

要投诉我,或者觉得我其实可以忽悠得更高明,欢迎来我的微博让我知道. 如果你有关于正则表达式的问题, 可以到 stackoverflow 网站上提问, 记得要添加 regex 标签. 如果你更习惯于用中文交流, 可以到微博上用 #正则# 标签提出问题.

网上的资源及本文参考文献

更新纪录

  1. 2006-3-27 第一版
  2. 2006-10-12 第二版
    • 修正了几个细节上的错误和不准确的地方
    • 增加了对处理中文时的一些说明
    • 更改了几个术语的翻译(采用了MSDN的翻译方式)
    • 增加了平衡组的介绍
    • 放弃了对The Regulator的介绍,改用Regex Tester
  3. 2007-3-12 V2.1
    • 修正了几个小的错误
    • 增加了对处理选项(RegexOptions)的介绍
  4. 2007-5-28 V2.2
    • 重新组织了对零宽断言的介绍
    • 删除了几个不太合适的示例,添加了几个实用的示例
    • 其它一些微小的更改
  5. 2007-8-3 V2.21
    • 修改了几处文字错误
    • 修改/添加了对$,\b的精确说明
    • 承认了作者是个骗子
    • 给RegexTester添加了Singleline选项的相关功能
  6. 2008-4-13 v2.3
    • 调整了部分章节的次序
    • 修改了页面布局,删除了专门的参考节
    • 针对读者的反馈,调整了部分内容
  7. 2009-4-11 v2.31
    • 修改了几处文字错误
    • 添加了一些注释说明
    • 调整了一些措词
  8. 2011-8-17 v2.32
    • 更改了工具介绍,换用自行开发的正则表达式测试器

如何发现优秀的开源项目?

之前发过一系列有关 GitHub 的文章,有同学问了,GitHub 我大概了解了,Git 也差不多会使用了,但是 还是搞不清 GitHub 如何帮助我的工作,怎么提升我的工作效率?

问到点子上了,GitHub 其中一个最重要的作用就是发现全世界最优秀的开源项目,你没事的时候刷刷微博、知乎,人家没事的时候刷刷 GitHub ,看看最近有哪些流行的项目,久而久之,这差距就越来越大,那么如何发现优秀的开源项目呢?这篇文章我就来给大家介绍下。

1. 关注一些活跃的大牛

GitHub 主页有一个类似微博的时间线功能,所有你关注的人的动作,比如 star、fork 了某个项目都会出现在你的时间线上,这种方式适合我这种比较懒的人,不用主动去找项目,而这种基本是我每天获取信息的一个很重要的方式。不知道怎么关注这些人?那么很简单,关注我 stormzhang ,以及我 GitHub 上关注的一些大牛,基本就差不多了。

图片描述

点击下图的 Explore 菜单到“发现”页面

图片描述

紧接着点击 Trending 按钮

图片描述

这个 Trending 页面是干嘛的呢?直译过来就是趋势的意思,就是说这个页面你可以看到最近一些热门的开源项目,这个页面可以算是很多人主动获取一些开源项目最好的途径,可以选择「当天热门」、「一周之内热门」和「一月之内热门」来查看,并且还可以分语言类来查看,比如你想查看最近热门的 Android 项目,那么右边就可以选择 Java 语言。

图片描述

这样页面推荐大家每隔几天就去看下,主动发掘一些优秀的开源项目。

除了 Trending ,还有一种最主动的获取开源项目的方式,那就是 GitHub 的 Search 功能。

举个例子,你是做 Android 的,接触 GitHub 没多久,那么第一件事就应该输入 android 关键字进行搜索,然后右上角选择按照 star 来排序,结果如下图:

图片描述

如果你是学习 iOS 的,那么不妨同样的方法输入 iOS 关键字看看结果:

图片描述

可以看到按照 star 数,排名靠前基本是一些比较火的项目,一定是很有用,才会这么火。值得一提的是左侧依然可以选择语言进行过滤。

而对于实际项目中用到一些库,基本上都会第一时间去 GitHub 搜索下有没有类似的库,比如项目中想采用一个网络库,那么不妨输入 android http 关键字进行搜索,因为我只想找到关于 Android 的项目,所以搜索的时候都会加上 android 关键字,按照 star 数进行排序,我们来看下结果:

图片描述

可以看到 Retrofit、OkHttp、android-async-http 是最流行的网络库,只不过 android-async-http 的作者不维护了,之前很多人问我网络库用哪个比较好?哪怕你对每个网络库都不是很了解,那么单纯的按照这种方式你都该优先选择 Retrofit 或者 OkHttp,而目前绝大部分 Android 开发者确实也都是在用这两个网络库,当然还有部分在用 Volley 的,因为 google 没有选择在 GitHub 开源 volley,所以搜不到 volley 的上榜。

除此之外,GitHub 的 Search 还有一些小技巧,比如你想搜索的结果中 star 数大于1000的,那么可以这样搜索:

android http stars:>1000

当然还有其他小技巧,但是我觉得不是很重要,就不多说了。

有些人如果习惯用 Google 进行搜索,那么想搜索 GitHub 上的结果,不妨前面加 GitHub 关键字就ok了,比如我在 google 里输入 GitHub android http ,每个关键字用空格隔开,然后搜索结果如下:

图片描述

可以看到,基本也是我们想要的结果,只不过排序就不是单纯的按照 star 来排序了。

福利大放送

相信以上三种方法够大家遨游在 GitHub 的海洋了,最后给大家献上一些福利,这些项目是 GitHub 上影响力很大,同时又对你们很有用的项目:

这个项目目前 star 数排名 GitHub 第三,总 star 数超过6w,这个项目整理了所有跟编程相关的免费书籍,而且全球多国语言版的都有,中文版的在这里:free-programming-books-zh,有了这个项目,理论上你可以获取任何编程相关的学习资料,强烈推荐给你们!

俗话说,不会用 shell 的程序员不是真正的程序员,所以建议每个程序员都懂点 shell,有用不说,装逼利器啊!而 oh-my-zsh 毫无疑问就是目前最流行,最酷炫的 shell,不多说了,懂得自然懂,不懂的以后你们会懂的!

GitHub 上有各种 awesome 系列,简单来说就是这个系列搜罗整理了 GitHub 上各领域的资源大汇总,比如有 awesome-android, awesome-ios, awesome-java, awesome-python 等等等,就不截图了,你们自行去感受。

GitHub 的使用有各种技巧,只不过基本的就够我们用了,但是如果你对 GitHub 超级感兴趣,想更多的了解 GitHub 的使用技巧,那么这个项目就刚好是你需要的,每个 GitHub 粉都应该知道这个项目。

这个项目是我一个好朋友 Trinea 整理的一个开源项目,基本囊括了所有 GitHub 上的 Android 优秀开源项目,但是缺点就是太多了不适合快速搜索定位,但是身为 Android 开发无论如何你们应该知道这个项目。

这个项目跟上面的区别是,这个项目只整理了所有跟 Android UI 相关的优秀开源项目,基本你在实际开发终于到的各种效果上面都几乎能找到类似的项目,简直是开发必备。

这个项目是我的邪教群的一位管理员整理的,几乎包括了国内各种学习 Android 的资料,简直太全了,我为这个项目也稍微做了点力,强烈推荐你们收藏起来。

这个就不多说了,之前给大家推荐过的,国内一线互联网公司内部面试题库。

这是一份非常详细的面试资料,涉及 Android、Java、设计模式、算法等等等,你能想到的,你不能想到的基本都包含了,可以说是适应于任何准备面试的 Android 开发者,看完这个之后别说你还不知道怎么面试!

总结

GitHub 上优秀开源项目真的是一大堆,就不一一推荐了,授人以鱼不如授人以渔,请大家自行主动发掘自己需要的开源项目吧,不管是应用在实际项目上,还是对源码的学习,都是提升自己工作效率与技能的很重要的一个渠道,总有一天,你会突然意识到,原来不知不觉你已经走了这么远!

觉得不错,不妨随手转发、点赞,都是对我良心张莫大的鼓励!

H5、React Native、Native应用对比分析

H5、React Native、Native应用对比分析

离上次在北京开源中国盛典已经快一个月了,有点想念@oschina的小伙伴了。我必须承认oschina是国内最大的同性社交网站,这也是无可争议的事实,但是,我真想的是妹子!!![偷笑]。从上次演讲后,有一些同学陆陆续续问我演讲的PPT在哪,还有不少同学希望看到[手稿],这学习精神,在下实在是佩服啊。有着这么热情的小伙伴,我激动不已啊,所以在此公布[手稿],方便大家可以将[PPT]和[手稿]双手齐下,:)。2015年,我们要一起努力完成以前吹过的牛逼…..  下面是[多图预警],请考虑切wifi,土豪请无视我,也可拿红包砸我,:)

---------------华丽丽的分割线,主题开始---------------------

“存在即合理”。凡是存在的,都是合乎规律的。任何新事物的产生总要的它的道理;任何新事物的发展总是有着取代旧事物的能力。React Native来的正是时候,一则是因为H5发展到一定程度的受限;二则是移动市场的迅速崛起强调团队快速响应和迭代;三则是用户的体验被放大,用户要求极致的快感,除非你牛x(例如:12306最近修改手机号需要用户自己发短信接收验证码)。

以下简单的介绍下H5、React Native、Native的含义:

最近三四年间,国内外的前端与全栈开发者社区都在坚持不懈地追寻使用JavaScript与HTML、CSS技术体系开发App内场景的核心工程技术。这种技术,在国内很多公司与团队中,被通称为H5。——童遥

这段是取自@童老师给小二我新书作的序,没有断章取义的意思。很清楚,H5并不是狭义的HTML5新标签和API,而是工程化的“In App” technology。

iOS/Android ——原生应用(都懂得,不解释)。

React Native —— React & Native ,应运而生!

一、React Native的出现

React Native的出现,似乎是扛起的反H5的旗子。就像当年Facebook放弃H5,全部转向Native一样。这一点,我们需要认同和保持高度的清醒。那么,React Native是否又是在吞食Native的领地呢?技术的发展,是用户风向标的导向起的作用。任何一门技术的出现,都是当时用户需求的体现。

我们应该从以下几点看待React Native的出现。

“鉴往知来”——从过去的教训中总结经验,从用户的角度开拓未来,用户更希望产品迭代和稳定寻求一种平衡
“HTML5差强人意,但是与原生应用相比还是有些差距”——为了更高的追求! 用户体验!
“人才宝贵,快速迭代”——Web开发者相对较多,寻找平衡点
“跨平台!跨平台!跨平台!”——单一技术栈,开发者的福音
“xx是世界上最好的语言” ——工程学的范畴,没有最好,只有最适合,我还是补充一句,JS还是很好很好的。

HTML5 vs React Native ? HTML5 : React Native
结论(React Native):
1、原生应用的用户体验
2、跨平台特性
3、开发人员单一技术栈
4、上手快,入门容易
5、社区繁荣

二、3款应用效果

注:以下所有对比均在iOS平台下



上面3张图片,如果去掉第一张图的“HybirdApp”的字样,是否分得清哪个是React Native开发?哪个是Native应用。
你的第一感觉是什么?

三、工程方案

为了评估3种方案的技术优势和弱势。我们需要开发功能大致相似的App。这里,我们使用了“豆瓣”的API来开发“豆搜”应用。该应用能够搜索“图书”、“音乐”、“电影”。想当年,豆瓣以“图书评论”走红,尤其是12年当红!豆瓣是一个清新文艺的社区,一个“慢公司”。最近有一则网传消息,注意是网传——“传京东投1.5亿美元控股豆瓣”。今天,不聊豆瓣,我们要聊一个工程化的问题。

我们需要将3款App的功能做到一致,同时需要保持技术要点一致。比如React Native这里使用了TabBar,那么Native我们也必须使用TabBar。简单而言就是:功能一致,组件 & API一致。我们功能如下图所示:

1、H5方案
在H5/Hybird应用中,我们使用AngularJS开发单页webApp,然后将该WebApp内嵌入到iOS WebView中,在iOS代码中,我们使用Navigation稍微控制下跳转。
WebApp地址:http://vczero.github.io/search/html/index.html
WebApp项目地址:https://github.com/vczero/search (很简单的一个项目)
H5/Hybird项目地址:https://github.com/vczero/search_Hybird

2、React Native
在React Native中,封装必要的功能组件。
项目地址:https://github.com/vczero/React-Dou。
项目结构如下图:

3、Native(iOS)
使用React Native大致相同的组件开发App,不使用任何第三方库,代码布局。
项目地址:https://github.com/vczero/iOS-Dou

四、对比分析

很多时候,新技术的采用最希望看到的是数据,而不是简单说“用户体验棒,开发效率高,维护成本低”。不过,生活中也有这样的同学,知一二而能窥全貌。当然,本人生性胆小,也没有那么多的表哥和隔壁的老王,所以不敢早下定论,不敢太放肆。赵本山在《大笑江湖》中有句名言“May the force be with you”(别太放肆,没什么用)。因此,从以下几个方面做一个简单的对比。

---------------分析提纲----------------

1、开发方式

(1)代码结构
(2)UI布局
(3)UI截面图
(4)路由/Navigation
(5)第三方生态链

2、性能 & 体验

(1)内存
(2)CPU
(3)动画
(4)安装包体积
(5)Big ListView
(6)真机体验

3、更新 & 维护

(1)更新能力
(2)维护成本
-----------------提纲---------------

1、开发方式

很多人说React Native的代码不好看,不好理解。那是因为前端工程师都熟悉了Web的开发方式。怎么解决这个问题呢,可以先看看iOS代码,断定不熟悉iOS的同学心里会默念“一万匹**马奔腾”。那时候,你再看React Native,你会觉得使用React Native开发App是件多么美好的事!OK,我们先来看下三者在开始“一款简单App”的代码结构。
(1)代码结构
H5/Hybird的开发模式,我们需要维护3套代码,两套是Native(iOS/Android)代码,一套是WebApp版本。这里,我们使用AngularJS作为WebApp单页开发框架。如下图所示。

在React Native中,同样需要关注部分的Native代码,但是大部分还是前端熟悉的JavaScript。在“豆搜”应用中,代码结构如下:

在Native开发中,更加强调Native开发者的能力。平台是:iOS/Android。

结论:从前端角度而言,React Native跨平台特性,不要开发者深入的了解各平台就能开发一款高效App。同时,语言层面而言,JavaScript运用很广泛,入门门槛相对较低。React Native虽然抛弃了MVC分离实践,但是从业务角度而言,更为合理。一切而言:对前端,对移动领域是利好的消息。

(2)UI布局
“面容姣好”,合理的UI却总是跟着时间在变。那么UI布局就不是小事。
Web开发布局目前大多是 DIV + CSS。
React Native的布局方式是Flexbox。

   //JSX
  <ScrollView style={styles.flex_1}>
    <View style={[styles.search, styles.row]}>
      <View style={styles.flex_1}>
        <Search placeholder="请输入图书的名称" onChangeText={this._changeText}/>
      </View>
      <TouchableOpacity style={styles.btn} onPress={this._search}>
        <Text style={styles.fontFFF}>搜索</Text>
      </TouchableOpacity>
    </View>
    {
      this.state.show ?
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this._renderRow}
        />
      : Util.loading
    }
  </ScrollView>
  //样式
  var styles = StyleSheet.create({
      flex_1:{
        flex:1,
        marginTop:5
      },
      search:{
        paddingLeft:5,
        paddingRight:5,
        height:45
      },
      btn:{
        width:50,
        backgroundColor:'#0091FF',
        justifyContent:'center',
        alignItems:'center'
      },
      fontFFF:{
        color:'#fff'
      },
      row:{
        flexDirection:'row'
      }
    });

而Native布局就有种让你想吐的感觉,尤其是iOS的布局。这里不是指采用xib或者Storyboard,而是单纯的代码,例如添加一个文本:

UILabel *publisher = [[UILabel alloc]init];
publisher.frame = CGRectMake(bookImgWidth + 10, 50, 200, 30);
publisher.textColor = [UIColor colorWithRed:0.400 green:0.400 blue:0.435 alpha:1];
publisher.font = [UIFont fontWithName:@"Heiti TC" size:13];
publisher.text = obj[@"publisher"];
[item addSubview:publisher];

总结:React Native既综合了Web布局的优势,采用了FlexBox和JSX,又使用了Native原生组件。比如我们使用一个文本组件。
<Text style={{width:100;height:30;backgroundColor:'red'}}>测试</Text>

(3)UI截面图
Hybrid方式截面图

可以看到第一层列表页是完整的布局,实际上这就是Web页面;而第二层灰色的是Native的WebView组件。
iOS UI截面图


可以看到Native页面的组件特别多,即使是列表页,其中某一项都是一个组件(控件)。

当然,我们就会想,能够完全调用原生组件呢?那样性能是否更好?
React Native UI截面图


可以清楚的看到React Native调用的全部是Native组件。并且层次更深,因为React Native做了组件的封装。如上图,蓝色边框的就是RCTScrollView组件。这就是React Native相比H5更高效的原因。

(4)路由/Navigation
在Web单页面应用中,路由由History API实现。
而React Native采用的路由是原生的UINavigationController导航控制器实现。
React Native NavigatorIOS组件封装程度高;Navigator可定制化程度高。
Navigator方法如下:

getCurrentRoutes() - returns the current list of routes
jumpBack() - Jump backward without unmounting the current scene
jumpForward() - Jump forward to the next scene in the route stack
jumpTo(route) - Transition to an existing scene without unmounting
push(route) - Navigate forward to a new scene, squashing any scenes that you could jumpForward to
pop() - Transition back and unmount the current scene
replace(route) - Replace the current scene with a new route
replaceAtIndex(route, index) - Replace a scene as specified by an index
replacePrevious(route) - Replace the previous scene
immediatelyResetRouteStack(routeStack) - Reset every scene with an array of routes
popToRoute(route) - Pop to a particular scene, as specified by its route. All scenes after it will be unmounted
popToTop() - Pop to the first scene in the stack, unmounting every other scene

相对Native而言,这些接口更Native还是很相似的。

//iOS UINavigationController  
//相对Web而言,不用自己去实现路由,并且路由更加清晰         
[self.navigationController pushViewController:detail animated:YES];

“豆搜” WebApp路由(基于AngularJS)如下:

“豆搜” React Native版本导航如下:

“豆搜” iOS版本导航代码如下:

总结:React Native封装的导航控制更容易理解。

(5)第三方生态链
“我的是我的,你的也是我的。 ”——我不是“疯狂女友”,我是React Native!
我们缺少“城市列表”组件,OK,使用JSX封装一个;觉得性能太低,OK,基于React Native方案封装一个原生组件。
这个iOS图表库不错,拿来用呗! => 完美!
这一切都是基于React Native提供的模块扩展方案。
所以说:iOS第三方库 + 部分JavaScript库 = React Native 生态库(可以知道,基于React Native的扩展方案是多么方便)

2、性能 & 体验

我们都很关注一款App性能。因此测试和体验App的性能很重要。以下测试,都是基于相同的case。
测试平台:模拟器,iphone6,iOS8.4
(1)内存
首先,我们来看下Native应用占用的内存情况。一开始,原生应用启动后,占用内存是20~25M;针对相同的case,跑了2min,结果如下图:

可以看出,峰值是87.9M,均值是72M;内存释放比较及时。

我们再来看下Hybird App的情况。App已启动,占用内存35~55M;同样,跑了2min以上,结果如下图:

可以看出,峰值在137.9M,因为整个应用在WebView中,内存释放不明显,存在缓存。

最后,看下React Native的情况。App启动占用内存35~60M,同样跑2min以上,结果如下图:

可以看出,峰值在142M,内存相对释放明显。

总结:React Native和Web View在简单App上相差不大。二者主要:内存消耗主要是在网页数据上。

(2)CPU
我们可以看一下Native应用程序CPU的情况,最高值在41%。

Hybird App的最高值在30%。

React Native的最高值在34%。

总结:CPU使用率大体相近,React Native的占用率低于Native。

(3)动画
React Native提供了Animated API实现动画。简单效果,基本OK。个人觉得React Native不适合做游戏,尤其布局能力。
Native Animation提供UIView动画
H5/Hybird:采用js动画能力
总结:React Native Animated API / 封装Native动画库 可以满足基本需求

(4)安装包体积
Hybird App:
34(App壳) + 5(HTML) + 125(Angular) + 29(An-route) + 6(min.js) + 4(min.css) = 203 KB。

React Native:
不含bundle: 843KB
含bundle: 995KB

Native
83KB

React Native框架包大小
843(不含bundle) – 32(Hybird_app空壳,初识项目) = 811KB

相比快速迭代和热更新,比Native多了811KB一点都不重要,我们将图片素材、静态资源线上更新缓存起来即可减少很多体积。
总结:牺牲一点体积,换更大的灵活性!(世界上哪有那么美的事,除非丑,就会想得美,:) )。

(5)Big ListView & Scroll 性能
循环列表项500次: H5页面惨不忍睹
React Native还可以接受
Native 采用UITabView更高效,因为不渲染视图外部分。

(6)真机体验
机型:iphone4s,iOS7
Native > React Native > Hybird
如果非要给个数字的话,那我个人主观感受是:
Native: 95%+ 流畅度
React Native: 85~90% 流畅度
H5/Hybird: 70% 流畅度

总结:Native/React Native的体验相对而言更流畅。

3、更新 & 维护

(1)更新能力
H5/Hybird: 随时更新,适合做营销页面,目前携程一些BU全部都是H5页面;但是重要的部分还是Native。
React Native:React Native部分可以热更新,bug及时修复。
Native:随版本更新,尤其iOS审核严格,需要测试过关,否则影响用户。

(2)维护成本
H5/Hybird: Web代码 + iOS/Android平台支持
React Native:可以一个开发团队 + iOS/Android工程师;业务组件颗粒度小,不用把握全局即可修改业务代码。
Native:iOS/Android开发周期长,两个开发团队。

总结:React Native 统一了开发人员技术栈,代码维护相对容易。

五、综合

1、开发方式

(1)代码结构: React Native更为合理,组件化程度高
(2)UI布局:Web布局灵活度 > React Native > Native
(3)UI截面图:React Native使用的是原生组件,
(4)路由/Navigation:React Native & Native更胜一筹
(5)第三方生态链:Native modules + js modules = React Native modules

2、性能 & 体验

(1)内存:Native最少;因为React Native含有框架,所以相对较高,但是后期平稳后会优于Native。
(2)CPU:React Native居中。
(3)动画:React Native动画需求基本满足。
(4)安装包体积:React Native框架打包后,811KB。相比热更新,可以忽略和考虑资源规划。
(5)Big ListView
(6)真机体验:Native >= React Native > H5/Hybrid

3、更新 & 维护

(1)更新能力: H5/Hybird > React Native > Native
(2)维护成本: H5/Hybird <= React Native < Native

React Native定制难度相比Native有些大;但是具备跨平台能力和热更新能力。

说了这么多,最后个人建议:

一个新的App完全可以采用React Native开发,这样成本会低很多。

关于演讲:

PPT: http://www.oschina.net/doc/24007

视频:http://www.oschina.net/question/865233_2145334 (这个演讲在移动专场下半场开场)

六、图书 & 工作机会

最后说一下图书《React Native入门与实战》吧。这是一本[难产]的家伙,15年写的比较顺利,最后因为北京雾霾,印刷厂偶尔停工,一直在难产。前段时间,图书发往各地书店,到店比较迟,我猜测是因为雾霾,找不到高速出口~~目前该书由人民邮电出版社图灵原创出版,已经开卖了。本书主要是希望带领大家一起进入React Native开发领域,针对每个API和组件都有详细的案例,同时为了更好的实战,最后3章以实际案例(通讯录App,LBS App,基于豆瓣OpenAPI的搜索 App)给出,供大家参考。觉得入门是完全没问题的,后期大家可以一起交流,争取丰富国内React Native社区。

《React Native入门与实战》各网站购买地址:

京东:http://item.jd.com/10089810271.html (目前应该都有货了,微信有同学传照片给我看了,铁粉哈)

http://item.jd.com/11844102.html (京东自营,货源需要跟客服确认哦)

互动:http://product.china-pub.com/4904552 (已经有货)

天猫:https://detail.tmall.com/item.htm?spm=a220m.1000858.1000725.21.rc0yeu&id=525484671101&cat_id=2&rn=4690ef5b4e06412ffae4d1cc4f4dcc9a&user_id=1020536390&is_b=1 (天猫这家卖的还可以,不知道为啥京东看不到数据,京东应该才是技术买书的最爱啊,听说也预售了不少呢)

Tor网络突破IP封锁,爬虫好搭档【入门手册】

本文地址:http://www.cnblogs.com/likeli/p/5719230.html

前言

本文不提供任何搭梯子之类的内容,我在这里仅仅讨论网络爬虫遇到的IP封杀,然后使用Tor如何对抗这种封杀。作为一种技术上的研究讨论。

场景

我们编写的网络爬虫全网采集的时候总会有一些网站有意识的保护自己的网站内容,以防止网络爬虫的抓取。常见的方式就是通过身份验证的方式来进行人机识别。也就是在登陆(查询)的入口增加或者加固防御。这些防御有那些呢?我目前见到的有:各种验证码、参数的加密、在前端JS挖坑、访问频率限制(IP黑名单)等。

其实前面的几种我们在某些情况下都是有办法解决的,我一一举例:

1、加密参数。其实老司机们都知道了,在客户端加密参数并没有什么卵用。因为爬虫完全可以将前端的js丢到一个游览器的内核环境中去执行js,这样的话,无论你怎么加密,都没有用,因为这和在游览器中运行没有什么区别,是无法进行人机识别的。

2、前端在Js脚本中挖坑。这是一些小聪明了,毕竟被抓取的网站方是这场战争的游戏规则制定者,他们能够自己制定规则,然后在没有什么漏洞的情况下,爬虫只能按照对方指定的规则一条条的来,一个坎一个坎的去跨。

这种情况下,网站开发人员在一大堆的js中藏着一小段预警js作为地雷。毕竟一般情况下,爬虫都是直接请求后得到的响应是一段html的文本,并不会执行其中的js。那么这样就区分出来了,网站方可以在页面加载后执行一段js,这段js不用和服务器通信,就是默默的执行。若是这段js执行了,说明访问者很可能是人,若是没有执行,那么这个访问真绝对是爬虫了。我们在正文请求中附带上的cookie中加上一个特定的标记。告诉服务器这个请求不是人发起的。服务器得到这个消息后,针对IP标记,但是这次请求是允许通过的(隐藏我们的判断依据)。下次或者这个IP访问几次后,就将这个IP拉入黑名单。

3、验证码,这东西是主要防御手段,这里不多说,我博客里面也有一篇关于这个文章。但是,只要技术能力足够,验证码还是会被突破的。君不见,12306验证码防御也没什么用。

4、IP黑名单,这个是依赖于上面的一个后台防御策略。但是再某种情况下,这种策略确实很有效,而且无解。

比如:有一个查询类的网站,通过限制IP的访问次数、频率就完全可以封锁或者限制爬虫,因为爬虫的意义就是自动化的、高效的得到数据。

IP黑名单突破的方案

针对于采用黑名单的网站,我们可以使用的策略就是代理了,我们用各种方式弄到一大批的代理IP,然后通过使用这些代理IP去发起请求,IP被封锁了,就换下一个。

我们的主题,Tor网络也就用在这里了。

首先来科普一下:

 

关于Tor网络

官网:https://www.torproject.org/

Tor是什么

  Tor是互联网上用于保护您隐私最有力的工具之一,但是时至今日仍有许多人往往认为Tor是一个终端加密工具。事实上,Tor是用来匿名浏览网页和邮件发送(并非是邮件内容加密)的。今天,我们要讨论一下Tor的是如何工作的、它做什么、不会做什么,以及我们该如何正确地使用它。

Tor的工作原理是这样的

当你通过Tor发送邮件时, tor会使用一种称为“洋葱路由”的加密技术通过网络随机生成的过程传送邮件。这有点像在一叠信中放了一封密信。网络中的每个节点都会解密消息(打开的最外信封),然后发送内部加密的内容(内密封的信封)至其下一个地址。这导致如果单看一个节点是看不了信的全部内容,并且该消息的传送路径难以追踪。

 

 在Windows上使用Tor

windows上安装tor很简单,去Tor的官网下载安装洋葱游览器就可以了。

当然,我们可以只安装Tor核心,不安装任何其他附属,然后我们去找个Tor控制器,去操作Tor就可以了。

我这里有两个版本Tor控制器,Windows的Vidalia和OS X版本的Arm(Anonymizing Relay Monitor)这东西就Python开发的,可以完成Vidalia的绝大部分功能。

windows上的Vidalia:

 Mac上的Arm:

目前我使用的也就是在Mac上操作的Arm。我也是重点说Arm的。因为Window下的Tor通过C#或者Python控制都不行,不得已的情况下,换到了OS X/Linux的环境下来控制Tor。

安装Tor、Arm

首先我们得下载安装了,好消息是,Arm和Tor大部分的包管理器都有,我们可以直接下载。通过包管理器下载后,会自动安装,并完成初始化配置。

例如我在Mac上的安装以及配置:

brew install tor
brew install arm

我们另外需要安装Privoxy,需要通过Privoxy来将Socks5转换成Http。

brew install Privoxy

最后,我们还需要一个前置代理,因为Tor网络,在国内是不能访问的。所以我们需要一个在国外的前置代理,目前我自己已经搭建好了一个位于加拿大的VPN,这里可以直接用的。

配置Tor、Arm

我们需要做一些配置,我先给一张我的配置图:

这是从已经配置好的Arm看到的。如上图,绿色的字体就是我给torrc配置文件增加的内容。

我们修改配置就是在 /Users/ Likeli/.arm/torrc 路径(Mac下的路径)。完成以上修改。

关于重要参数的说明:

参数 说明
ControlPort 控制程序访问的端口(重要)
Socks5Proxy 前置代理端口
SocksProt 外部程序访问Tor的端口
MaxCircuitDirtiness 自动切换Identity的时间间隔

 

 

 

 

除了这些参数,其实还有很多的备选参数,详细说明请查看tor帮助文档,以上配置也是我从tor的官方帮助文档中找到的。

man tor

好了,配置完成了,现在去出去启动Arm,完成初始化。

在终端运行Arm,我直接用Mac 的截图工具,貌似不能构绘制圆圈勾选,这里提供几张别出弄来的图,按照选择就ok了。

Arm配置源地址:https://program-think.blogspot.com/2015/03/Tor-Arm.html?utm_source=tuicool&utm_medium=referral

 

 

配置好了,然后启动,启动成功后如下图:

下方的启动日志显示,启动进度100%。

好了,到这一步,其实代理已经通了,来测一测。

好了,搞定了,目前Tor的Socks5代理已经接通,我们直接连接127.0.0.1:9000就可以了。这里的端口是自己根据上面的配置来的。

收尾

虽然代理通了,但是还有问题,因为一般我们都是用的Http的代理。所以我们需要将Socks5代理转换成Http代理来方便我们的应用程序使用。

这里用到的工具是:Privoxy(上面的步骤中,已经通过软件库安装了)

我们需要对这个做一下一点点配置修改。

我们安装Privoxy后,打开它的配置文件:

打开后,搜索 127.0.0.1:9050

找到下图中的为之后,另起一行,插入 forward-socks5 / 127.0.0.1:9000  .

配置完成后保存关闭,若是我们尝试连接本地的8118端口,也就是 127.0.0.1:8118

若是连接不上,重启一下服务,或者重启电脑。

这里的8118端口也是可以修改的,若是修改,请直接在Privoxy的配置文件中搜索127.0.0.1:8118,并修改8118端口就可以了。

到这里我们就完成了所有的配置了,任何程序直接访问127.0.0.1:8118,并将至设置为代理,就可以躲避网站的IP限制了。

最后

以上中测试代理可以在Chrome中安装SwitchySharp插件,然后稍加配置就可以了。

好了,自由享受无限IP的刺激把,如此以后,IP黑名单(IP封锁)形同虚设~

最后附上Python控制Tor切换IP的样例代码:来源(https://stackoverflow.com/questions/9887505/how-to-change-tor-identity-in-python

复制代码
 1 import urllib2
 2 from TorCtl import TorCtl
 3 
 4 proxy_support = urllib2.ProxyHandler({"http" : "127.0.0.1:8118"})
 5 opener = urllib2.build_opener(proxy_support) 
 6 
 7 def newId():
 8     conn = TorCtl.connect(controlAddr="127.0.0.1", controlPort=9051, passphrase="your_password")
 9     conn.send_signal("NEWNYM")
10 
11 for i in range(0, 10):
12     print "case "+str(i+1)
13     newId()
14     proxy_support = urllib2.ProxyHandler({"http" : "127.0.0.1:8118"})
15     urllib2.install_opener(opener)
16     print(urllib2.urlopen("http://www.ifconfig.me/ip").read())
复制代码

 

        请尊重别人的劳动成果,让分享成为一种美德,欢迎转载。另外,文章在表述和代码方面如有不妥之处,欢迎批评指正。留下你的脚印,欢迎评论!

为什么需要分布式调用跟踪系统

随着分布式服务架构的流行,特别是微服务等设计理念在系统中的应用,业务的调用链越来越复杂,

可以看到,随着服务的拆分,系统的模块变得越来越多,不同的模块可能由不同的团队维护,

一个请求可能会涉及到几十个服务的协同处理, 牵扯到多个团队的业务系统,那么如何快速准确的定位到线上故障?
同时,缺乏一个自上而下全局的调用id,如何有效的进行相关的数据分析工作?

对于大型网站系统,如淘宝、京东等电商网站,这些问题尤其突出。

一个典型的分布式系统请求调用过程:

比较成熟的解决方案是通过调用链的方式,把一次请求调用过程完整的串联起来,这样就实现了对请求调用路径的监控。

>>调用跟踪系统的业务场景

(1)故障快速定位

通过调用链跟踪,一次请求的逻辑轨迹可以用完整清晰的展示出来。
开发中可以在业务日志中添加调用链ID,可以通过调用链结合业务日志快速定位错误信息。

(2)各个调用环节的性能分析

在调用链的各个环节分别添加调用时延,可以分析系统的性能瓶颈,进行针对性的优化。

(3)各个调用环节的可用性,持久层依赖等

通过分析各个环节的平均时延,QPS等信息,可以找到系统的薄弱环节,对一些模块做调整,如数据冗余等。

(4)数据分析等

调用链是一条完整的业务日志,可以得到用户的行为路径,汇总分析应用在很多业务场景。

>>分布式调用跟踪系统的设计

(1)分布式调用跟踪系统的设计目标

低侵入性,应用透明:

作为非业务组件,应当尽可能少侵入或者无侵入其他业务系统,对于使用方透明,减少开发人员的负担

低损耗:

服务调用埋点本身会带来性能损耗,这就需要调用跟踪的低损耗,
实际中还会通过配置采样率的方式,选择一部分请求去分析请求路径

大范围部署,扩展性:

作为分布式系统的组件之一,一个优秀的调用跟踪系统必须支持分布式部署,具备良好的可扩展性

(2)埋点和生成日志

埋点即系统在当前节点的上下文信息,可以分为客户端埋点、服务端埋点,以及客户端和服务端双向型埋点。

埋点日志通常要包含以下内容:

TraceId、RPCId、调用的开始时间,调用类型,协议类型,调用方ip和端口,请求的服务名等信息;
调用耗时,调用结果,异常信息,消息报文等;
预留可扩展字段,为下一步扩展做准备;

(3)抓取和存储日志

日志的采集和存储有许多开源的工具可以选择,
一般来说,会使用离线+实时的方式去存储日志,主要是分布式日志采集的方式。
典型的解决方案如Flume结合Kafka等MQ。

(4)分析和统计调用链数据

一条调用链的日志散落在调用经过的各个服务器上,
首先需要按 TraceId 汇总日志,然后按照RpcId 对调用链进行顺序整理。
调用链数据不要求百分之百准确,可以允许中间的部分日志丢失。

(5)计算和展示

汇总得到各个应用节点的调用链日志后,可以针对性的对各个业务线进行分析。
需要对具体日志进行整理,进一步储存在HBase或者关系型数据库中,可以进行可视化的查询。

>>调用跟踪系统的选型

大的互联网公司都有自己的分布式跟踪系统,
比如Google的Dapper,Twitter的zipkin,淘宝的鹰眼,新浪的Watchman,京东的Hydra等。

(1)Google的Drapper

Dapper是Google生产环境下的分布式跟踪系统,Dapper有三个设计目标:

低消耗:跟踪系统对在线服务的影响应该做到足够小。

应用级的透明:对于应用的程序员来说,是不需要知道有跟踪系统这回事的。如果一个跟踪系统想生效,就必须需要依赖应用的开发者主动配合,那么这个跟踪系统显然是侵入性太强的。

延展性:Google至少在未来几年的服务和集群的规模,监控系统都应该能完全把控住。

Drapper的日志格式:

dapper用span来表示一个服务调用开始和结束的时间,也就是时间区间。

dapper记录了span的名称以及每个span的ID和父ID,如果一个span没有父ID被称之为root span。所有的span都挂在一个特定的trace上,共用一个traceID,这些ID用全局64位整数标示。

Drapper如何进行跟踪收集:

分为3个阶段:

①各个服务将span数据写到本机日志上;

②dapper守护进程进行拉取,将数据读到dapper收集器里;

③dapper收集器将结果写到bigtable中,一次跟踪被记录为一行。

 

(2)淘宝的鹰眼

关于淘宝的鹰眼系统,主要资料来自于内部分享,

鹰眼埋点和生成日志:

如何抓取和存储日志:

鹰眼的实现小结:

 

详情见:淘宝-分布式调用跟踪系统介绍

参考资料:
分布式追踪系统dapper

Dapper,大规模分布式系统的跟踪系统

李林峰《分布式服务框架原理与实践》

iOS Xcode全面剖析

前言

一、创建新一个工程

二、Xcode界面详细介绍

三、Xcode菜单详解

四、Xcode快捷键介绍

五、结语

六、参考资料

前言

前几天在公司内部做了一次关于iOS的入门分享,听众有PHP、Web、Android、测试、产品、UI等,主旨是力求不懂iOS的人能了解iOS的开发流程,听后都能创建一个iOS项目并打印HelloWorld。(这是背景) 你想想就这么点需求,没啥东西吧,又因为最近项目还特别忙就什么也没有准备,以至于分享中就有了一些小尴尬::>_<:: 总结下来,像听众有技术和非技术这样而且是不懂iOS得其实是最难兼顾的,不准备还不是等死。。。

当然最大的感受还是自己掌握的iOS基础太渣,对于一个没有接触过iOS的技术或非技术,视角不一样看到的东西就不一样,他会对所有的东西都好奇都想知道是怎么回事,所以他会问一些iOS开发中不重要但是确实是属于iOS的问题, so随着分享的深入什么设计模式MVVM、runtime、乱七八糟的都讲了,但也掩盖不了自己基础的薄弱,所以近期打算重新梳理一下自己的iOS知识体系,把自以为自己懂得的知识好好总结,用博客的形式表现出来,毕竟写博客是最能检验一个人知识巩固好坏的方法了,最近一段时间一直在写PHP、JS等方向的分享,是时候回归一下本职了,希望能帮助自己巩固基础更希望能帮助跟我一样基础不是那么牢固的童鞋们

一、创建新一个工程

Xcode一直没有升级%>_<%,目前最新版本是Xcode7.3了,但本篇依据的是我目前使用的Xcode7.1.1,不过区别也不会很大的。

1、第一步打开Xcode,找到Xcode程序图标并点击

2、如下界面,我们点击新建一个项目,即第二项

1、Get started with a playground

Playground是苹果公司在2014年WWDC(苹果开发者大会)随Swift一起推出的,可以实现一边写代码,一边预览效果(即实时预览代码的效果)的工具。 相对于以前写代码要经过Build→Run漫长的等待才能看到代码的效果来说,Playground给程序员带来的方便不言而喻。 Playground的不足之处是:无法直接将Playground文件直接用到工程中,暂时不支持Objective-C 。 我们开发者可以利用Playground来快速测试一些代码和效果。

2、Create a new Xcode project

创建一个新的Xcode项目,一般正式的项目都是从此选项开始的。

3、Check out an existing project

打开一个已经存在的项目

3、选择一个项目模板创建,如下,图中已经标出不同,并点击Next

左侧栏: 左侧栏中四个分类分别代表了手机/pad、手表、电视、及PC端的开发选项,就是一款Xcode足可以开发苹果所有产品线中的所有软件,是不是特别叼? Application:即创建一个完整的项目。 Framework & Library:即写一个框架或者库(这个初学者用不到,但是等入门之后会发现这个很有用的,具体不在阐述,这可以再引申出一大篇文章了)

右侧详细: 各种预定义的模板,具体可从图中显示看出各个模板的样式,一般而言项目会选择最简单的即第三个模板。

4、填写并选择一些所创建项目的信息,如下并点击Next

1、Product Name 项目名称,比如本例中名称就是XcodeIntroduction(驼峰式命名)

2、Organization Name 组织或公司的名称,目前是王隆帅(本人^_^),可随便写。

3、Organization Identifier 组织或名称的标识,目前是本人所在的公司,可随便写。

4、Bundle Identifier 程序包标识,新建项目时是根据你创建的项目名称自动生成的不可更改,可在内部修改。

5、Language 开发语言选择,目前Xcode提供Objective-C、Swift两个选择,这里选择OC。

6、User Core Data 是否使用CoreData,iOS开发常用的一种数据库。

7、Include Unit Tests 是否添加模块单元测试Target

8、Include UI Tests 是否添加UI单元测试Target

5、选择路径创建项目,如下图,选择桌面,并点击Create。

1、create Git repository on My Mac

是否创建版本控制系统,创建的话可以选择是在本地还是在服务器。版本控制系统,或者说修改控制系统,实际上是一种检测源文件的改变并将其保存留作以后参考使用的机制(软件)。此外,它还能记录其他有用信息,比如是哪个开发者修改了代码,何时修改的,修改了哪一部分,以及其他历史信息。版本控制系统可以比较不同版本代码的不同,有必要时能恢复整个项目到以前的版本,追踪有害代码从而减少产品的错误。 通过版本控制系统,开发者可以在一个项目的不同分支上工作,当项目的各个部分开发完备时,将它们放到一起形成最终的版本,这个过程被称为合并。事实上,这种做法再团队和软件公司中相当常见:每个人负责项目的一部分,最终所有部分被整合到一起形成最终产品。

6、创建成功后的界面如下

此界面就是Xcode的主界面了,接下来,会带你一点点揭开Xcode神秘的面纱,66666。

二、Xcode界面详细介绍

五大区域介绍,如下图

1、 第一部分:顶部区域

① 程序运行相关: 从左至右依次:运行按钮、 停止按钮、 为工程选择运行平台。

② 编辑器相关: 从左至右依次:标准编辑器、 辅助编辑器、 版本编辑器。

③ 面板控制相关: 从左至右依次:隐藏(显示)左侧面板、 隐藏(显示)底部面板、 隐藏(显示)右侧面板。

2、第二部分:左面版 该面板是 Xcode 工程导航面板, 上方的八个按钮用于切换导航模式。从左至右 : 项目导航、符号导航、 搜索导航、 问题导航、 测试导航、 调试导航、 断点导航、 日志导航。

① 项目导航

项目导航组成 :

源文件 : 在 XcodeIntroduction 目录下的 “.h” 和 “.m” 后缀文件是源文件; 属性文件 : 在 XcodeIntroduction 下的 Supporting Files 目录下是属性文件 图片等; 单元测试项目 :XcodeIntroductionTests、XcodeIntroductionUITests 是工程的单元测试项目; 目标应用 :Products 目录下的 XcodeIntroductionTests.app 是目标应用;

② 符号导航

符号导航简介 : 用符号显示工程中的 类, 项目 和 属性; 表示方式 : C 表示类, M 表示方法, P 表示属性; 快速定位 : 点击对应的方法或者属性, 能快速定位到这个类中;

③ 搜索导航

在搜索框中输入要搜索的字符串, 按回车就可以搜索出包含该字符串的类;

④ 问题导航

显示项目中存在的警告或者错误;

⑤ 测试导航

点击 testExample 后面的执行按钮, 就会运行该单元测试;

⑥ 调试导航

调试导航面板中显示了各线程的详细信息;

⑦ 断点导航

列出所有的断点, 方便管理断点

⑧ 日志导航

列出项目开发过程中构建、生成、运行过程,每次该过程都可以通过日志面板查看

3、第三部分:调试面板

用于 Xcode 显示 控制台调试输出信息。

① 添加断点 如上图,在第20行打印“王隆帅的简书”代码上添加一个断点。

② 开始调试(自动判断) 点击顶部面板中的调试按钮, 如果代码中有断点, 就会自动进入调试状态, 执行到断点时会自动停止, 详细调试信息显示在底部的调试输出面板;

③ 调试面板按钮介绍(上图红框中从左至右) 1、Continue program execution : 继续执行下面的代码; 2、Step over : 单步调试, 点击一次该按钮, 执行一行代码, 如果有方法调用, 不会进入方法中; 3、Strp in : 步入调试, 点击该按钮, 会进入方法中; 4、Step out : 步出调试, 在方法中, 点击该按钮, 会退出方法, 执行方法外的单步调试; 5、点击会出现此时项目的视图层次结构 6、点击会让你选择你的地理位置

4、第四部分:右面板 包括两个部分上侧的检查器面板和下侧的库面板。


① 检查器面板

检查面板分类 : 普通源文件 : 包含 文件检查器快速帮助器; 故事版 : 界面文件, 除了文件检查器快速帮助器之外, 还有 身份检查器, 属性检查器, 大小检查器, 连接检查器;

1)文件检查器

Identity and Type : File Name(文件名), File Type(文件类型), Full Path(路径); Text Settings : Text Encoding(文件编码使用字符集), Indent Using(缩进), Wrap lines(自动换行);

2)快速帮助器

界面设计相关检查器

用户选中 “.storyboard” 或者 “.xib” 后缀的文件时, 会多出另外四个检查器;

3)身份检查器

管理界面组件类 实现类, 恢复ID 等标识性的属性;

4)属性检查器

管理界面组件 拉伸方式, 背景色 等属性;

5)大小检查器

管理界面组件 宽高 xy轴坐标 等属性;

6)连接检查器

管理界面组件 与程序代码之间的关联性;


② 库面板(从左至右)

项目的各种库文件,方便查找使用。

1)文件模板库

管理文件模板, 可以快速创建指定类型文件, 可以直接拖入项目中;

2)代码片段库

管理各种代码片段, 可以直接拖入源代码中;

3)对象库

界面组件, 可以直接拖入 故事板中;

4)媒体库

管理各种 图片, 音频 等多媒体资源;


5、第五部分:详细编码区 该区域是代码编写的主要区域。

三、Xcode菜单详解

四、Xcode快捷键介绍

Xcode中的快捷键确实会让人眼花缭乱,在此只介绍主界面上按钮点击的快捷键(有很强的记忆规律),代码相关、运行相关的代码可以看这里,这里面的道道还是很多的Y^o^Y

1、第一个需要知道的是Xcode的各区域与修饰键的关系,下面是一个快速浏览

Command:用来导航,控制导航区域 Alt:控制右边的一些东西,比如Assistant Editor,utility editor Control:编辑区域上的Jump bar的一些交互

如下图

下面是最常用的组合键:

Command 1~ 8: 跳转到导航区的不同位置 Command 0 :显示/隐藏导航区 Command Alt 1~ 6:在不同检测器之间跳转 Command Alt 0: 显示/关闭工具区. Control Command Alt 1~4: 在不同库之间跳转 Control 1~ 6: 在Jump bar的不同标签页的跳转。

最后也是最简单的就是回车键,当它和Command组合使用时,可以是你在Xcode中不同编辑器来回切换。

Command + Enter: 显示标准单窗口编辑器 Command Alt Enter:你可以猜下它的作用,它的功能是打开Assistant editor Command Alt Shift Enter: 打开版本控制编辑器

同样重要的是显示/隐藏调试区的快捷键是 Command + Shift + Y ,要记住这个你可以通过这句话来记忆 “Y is my code not working?” (译者注:Y谐音Why)。 如果你忘记了一些快捷键,你可以在Xcode的菜单栏Navigate一项中找到大部分快捷键。在即将完成这一部分的学习之时,你会惊奇的发现你仅仅只是用了键盘就让Xcode发生这各种变换。

五、结语

工欲善其事必先利其器, Xcode如此叼, 掌握了它, iOS还不随你玩!目前最新的版本为Xcode7.3, 提醒大家, 谨慎升级, 根据认识朋友的升级感受, 目测比较坑, 尤其是自动补全机制, 升级前可自行谷歌先行者的感受…( ⊙o⊙ )千真万确。

本文由作者 王隆帅 编写,转载请保留版权网址,感谢您的理解与分享,让生活变的更美好!

六、参考资料

http://www.360doc.com/content/15/0324/17/20918780_457719719.shtmlhttp://jingyan.baidu.com/article/4b07be3cb3c94048b380f3de.htmlhttp://www.cocoachina.com/ios/20140524/8536.htmlhttp://blog.csdn.net/shulianghan/article/details/38424965http://www.jianshu.com/p/8bcdf44b6cf1http://www.cocoachina.com/ios/20140731/9284.html

27个iOS开源库,让你的开发坐上火箭吧

27个iOS开源库,让你的开发坐上火箭吧

你不会想错过他们,真的。

我爱开源。

并且我喜欢开发者们,把他们宝贵的私人时间用来创造神奇的东西,然后他们会和其他人分享并且不求回报。开源作者和贡献者,你们是最帅的。感谢你们一直以来的工作。


所以,因为我是一个分类整理狂,这是我从iOS开源库中选出的最喜欢的这些项目的顺序都是随机的,全部都很酷。

绝大部分库是支持CocoaPods的,所以把它们添加到你的Xcode项目中轻而易举。

文章的尾部你会看到一个太长不看的版本——一个简单的列表,只有标题和到项目的链接。如果你发现这篇文章是有用的,把它和你的iOS开发者兄弟们分享。好东西需要被传播。

DZNEmptyDataSet

这本应该是iOS中一个标准、内置的解决空table和collection view的方式。默认的如果你的table view是空的,屏幕就是空的。但这不是你能提供的最好的用户体验。

用了这个库,你只需要遵循一系列协议,iOS会优雅地接管你的collection view并且会正确、好看地显示给用户信息。很明显,每个iOS项目都应该采用。

这是完全可自定义的。

CocoaPods:

pod ‘DZNEmptyDataSet’

GitHub

2. PDTSimpleCalendar

你的app是否需要一个简单、好看并且有效的日历组件呢?

现在你有了——PDTSimpleCalendar很有可能是最棒的iOS日历组件。有很多方式来自定义它,逻辑有效而且好看。

CocoaPods:

pod ‘PDTSimpleCalendar’

GitHub

3. MagicalRecord

他们说,Core Data很简单。他们说,它很好很简单。哈哈,你是认真的吗,苹果?一顿陈词滥调的代码被添加到每个项目里,这真的不够优雅和简单。更不用说添加、移除和更新很多实体,保存上下文,为不同的环境创建不同的Core Data栈,等等等等。我当然很喜欢Core Data,但是苹果真的可以通过一个简单的好方法来简化它——MagicalRecord方法。

MagicalRecord的工作就像一个Core Data的包装,并且向开发者隐藏了所有无关的东西。如果你曾经用过活跃纪录模式(例如Ruby on Rails),那你已经掌握它了。如果你在app里用Core Data的话真的真心推荐这个库。

CocoaPods:

pod ‘MagicalRecord’

GitHub

4. Chameleon

如果你读到了这一点,你是一个很好的程序员,而不是一个设计师。这就是为你准备的。

Chameleon是iOS的一个颜色框架。它用好看、摩登的扁平化颜色扩展了UIColor。它也给了我们能力来创建调色板,里面是我们自己定义的颜色。它可以做很多其他事,探索readme文件。如果你想要好看的应用程序,快把这个库加到你的项目里吧。

Chameleon基础扁平化颜色

CocoaPods:

pod ‘ChameleonFramework’

GitHub

5. Alamofire

Alamofire是一个用Swift写的优雅的网络库。你曾经用过AFNetworking吗?ALamofire是它的弟弟。年轻也更有才华,当然啦(AFNetworking是用Objective-C写的)。

需要做网络相关比如下载、上传、获取JSON等等?Alamofire是为你准备的。GitHub上8000人的选择不会错。

CocoaPods:

pod ‘MagicalRecord’

GitHub

6. TextFieldEffects

你不觉得标准的UITextField有一点无聊吗?我也是——所以对TextFieldEffects说hello吧!我不会写太多,我只会展示你一些这个库可以做的例子:

是的,这些就是简单的易用的控制器。你甚至可以用storyboard里的IBDesignable!

不幸的是这个库不支持CocoaPods(如果你来自未来,而这在一些时间之前改变了的话,请在Twitter上让我知道),但它支持Carthage。你也可以简单的从GitHub下载项目,并且把它添加到你的workspace里。

Carthage:

github “raulriera/TextFieldEffects”

GitHub

7. GPUImage

你曾经创建过一个摄像机app吗?如果没有,看完这个库你就肯定会的。

GPUImage可能性

GPUImage提供我们一个GPU-accelerated摄像头效果(图像和视频都可以),熊熊燃烧般的速度。App Store里有几百个app使用这个库——其中就有我的一个:

我的一个app中用的GPUImage

GitHub上8869个收藏并且还在持续增加。

CocoaPods:

pod ‘GPUImage’

GitHub

8. iRate

在App Store获得更多评价最好的方式是什么?我没有明确的数据来回答这个问题,但如果要我来猜测的话,我会说只要简单的询问用户就可以了。也许这是一个老套的方式——大部分开发者现在创建自定义的app内置提醒——但如果你没有时间或者不想所有事都从头做起,用iRate比不用要好。并且这就是iRate——一个小库,你包含在你的项目里并且忘记询问用户去评价了——iRate会自动替你完成,在合适的时间里。

CocoaPods:

pod ‘iRate’

GitHub

9. GameCenterManager

喜欢或讨厌一个人,在这种情况下管理Game Center非常简单,只需要一点我们最著名的反模式的帮助(你的游戏里只有Game Center,对吧?)

诚实地说,在iOS里香草管理Game Center并不是那么困难,但用这个库就是简单和快速。更好的是好的敌人。

我的一个游戏里就用了它,真是一个愉悦的体验。

CocoaPods:

pod ‘GameCenterManager’

GitHub

10. PKRevealController 2

这是一个真正的宝石,我最喜欢的iOS控件之一。PKRevealController是一个可滑动的侧边栏(向左、向右或者都可以),用你的手指来滑动(或者只通过点击按钮,但这样就没有滑动酷了)。

我使用过一些其它库,提供这种空间,PKRevealController是最好的。设置起来很简单,高度可自定义,手势识别非常非常好。它可以在iOS SDK中包含作为一个标准空间,真的。

CocoaPods:

pod ‘PKRevealController’

GitHub

11. SlackTextViewController

你曾经用过Slack iOS app吗?如果你在一个更大的软件公司工作的话,很有可能回答是。对于没有这么回答的人——Slack很坚硬。Slack的iOS app也是,特别是对于很好的、自定义的文本输入控件…就是你现在有的——你的app里可以用的代码!

自增长文本域?对的。手势识别,自动填充,多媒体粘贴?对的。简单的易用的?对的。你还可能需要什么?

CocoaPods:

pod ‘SlackTextViewController’

GitHub

12. RETableViewManager

RETableViewManager会帮助你动态创建和管理你的table view,都用代码。它提供我们预先定义好的cell(布尔型、文本、日期等等。——看下面的截图),你也可以创建你的自定义视图,和默认的一起使用。

左侧截图好老套!

这些你都可以在storybard里做而不需要这个库的帮忙,但有的时候代码比可视化编辑器更简单。

CocoaPods:

pod ‘RETableViewManager’

GitHub

13. PermissionScope

有用的库,通过在询问用户之前提示用户需要系统许可带来更好的用户体验。高度可接受程度->更多用户活动使用app->更好的留存->更好的数据->更多的下载。被高度推荐的pod。

CocoaPods:

pod ‘PermissionScope’

GitHub

14. SVProgressHUD

这个图片正在被正确的加载,不会等很久也不需要刷新页面。这就是SVProgressHUD在你的app里看起来的样子。如果你需要自定义等待指示,这里就有一个(很有可能就是最好的)。

CocoaPods:

pod ‘SVProgressHUD’

GitHub

15. FontAwesomeKit

Font Awesome很酷,用这个库你可以轻松地添加字体到你的项目里,并且在多种方式里使用它。

CocoaPods:

pod ‘FontAwesomeKit’

GitHub

16. SnapKit

喜欢auto layout?你应该!

至少在storyboard里创建的时候。

用代码创建constraints是痛苦的,如果没有帮助的话,但幸运的是SnapKit在这里,使用它你可以轻松写你的constraints,声明方式。看看吧。

CocoaPods:

pod ‘SnapKit’

GitHub

17. MGSwipeTableCell

另一个UI组件,在很多app里都常见,苹果应该考虑在iOS标准库里加入类似的东西。可滑动的table cell,这是这个pod最好的描述。最好的一个。

这些只是3个动画种类,还有很多。探索readme文件。

CocoaPods:

pod ‘MGSwipeTableCell’

GitHub

18. Quick

Swift里的单元测试,给Swift(好吧,要给Objective-C),和Xcode融为一体。如果你是一个Objective-C粉,我会推荐你Specta而不是这个,但对于Swift Quick很有可能是最好的。

CocoaPods:

pod ‘Quick’

GitHub

19. IAPHelper

app内购带给我们很多陈词滥调的代码,用这个库就不需要了,把最有关交易金钱的普遍的任务从iOS用户到你的(或者你的公司的)钱包简单包装起来。

CocoaPods:

pod ‘IAPHelper’

GitHub

20. ReactiveCocoa

好吧,这里我们有一个小怪兽。

ReactiveCocoa并不是很小、易用的项目,就像这个列表里其它项目一样。ReactiveCocoa带给我们一种完全不同的编程方式/结构,基于值的信号和流。这是完全的头脑风暴,首先你需要忘记你曾经学习的来理解它是如何工作的。这不是一个简单的任务,但是是有回报的。

这不是一个合适的地方来教你使用ReactiveCocoa,但我会给你一些好的资源,如果你感兴趣的话。

Getting Started with ReactiveCocoa

ReactiveCocoa

ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2

CocoaPods:

pod ‘ReactiveCocoa’

GitHub

21. SwiftyJSON

让Swift里的JSON解析更简单。

CocoaPods:

pod ‘SwiftyJSON’

GitHub

22. Spring

做动画更简单、可链和声明。

CocoaPods:

pod ‘Spring’

GitHub

23. FontBlaster

在app里加载自定义字体更简单。

CocoaPods:

pod ‘FontBlaster’

GitHub

24. TAPromotee

交叉推销你的app是最棒的销售策略之一,你可以在它们中免费实现。用这个库非常简单,你不再能为自己辩解为什么不用它——增加TAPromotee到你的podfile,配置、然后免费享受更多下载。

CocoaPods:

pod ‘TAPromotee’

GitHub

25. Concorde

在你的app加载很多JPEG吗?用Concorde你可以加载地更好看。有进度的方式。

CocoaPods:

pod ‘Concorde’

GitHub

26. KeychainAccess

管理钥匙串权限的小帮手库。

CocoaPods:

pod ‘KeychainAccess’

GitHub

27. iOS-charts

最后但重要的——iOS图表库!很简单和有效,我不会在这儿写太多——滑动到下面去看在你的app用它可以干嘛。

是的,所有东西都是可用的,作为一个可放进去(好吧,也许是“可写进去”)的组件。

不幸的是目前还没有CocoaPods支持,所以你需要手动拖动项目到你的Xcode workspace中。

所有库的太长不看列表,快速访问:

1. DZNEmptyDataSet [UI, 空table view解决方案]

2. PDTSimpleCalendar [UI, 可放入日历组件]

3. MagicalRecord [Core Data帮手实现活动记录模式]

4. Chameleon [UI, 颜色框架]

5. Alamofire [Swift网络]

6. TextFieldEffects [UI, 自定义外观text fields]

7. GPUImage [快速图像处理]

8. iRate [获得用户评价]

9. GameCenterManager [轻松管理Game Center]

10. PKRevealController [UI, 滑动边栏]

11. SlackTextViewController [UI, 高度自定义text field]

12. RETableViewManager [用代码动态创建table view]

13. PermissionScope [UI, 巧妙的提前问用户要系统许可]

14. SVProgressHUD [UI, 自定义等待菊花]

15. FontAwesomeKit [轻松地添加酷字体到你的app中]

16. SnapKit [用代码轻松auto layout]

17. MGSwipeTableCell [UI, 可滑动的table view cells]

18. Quick [Swift 单元测试框架]

19. IAPHelper [app内购封装帮手]

20. ReactiveCocoa [FRP框架]

21. SwiftyJSON [Swift JSON库]

22. Spring [动画框架]

23. FontBlaster [轻松在app中加载自定义字体]

24. TAPromotee [在你的app中交叉提示,置入界面]

25. Concorde [下载和解码进度化JPEGs]

26. KeychainAccess [轻松管理钥匙串]

27. iOS-charts [漂亮的图表库]


感谢阅读,真是一个长列表!如果你认为创建是有价值的,请分享它,通过点击文章下方的分享按钮——更多人会从中受益。并且如果你是一个Meduim用户,请点击推荐按钮——它会鼓舞我创建更多iOS开发文章!

One more thing——如果你在读这篇文章,你很有可能是个iOS开发者。很多iOS开发者也是iPhone用户——所以我有一些你可能感兴趣的东西。

我运行着一个每周一次手选最好的iOS app和游戏——看看吧,我关注高质量并且只挑选酷的app。连接也会很酷!

你也可以在推特上follow我,我最经常讨论iOS开发的地方。

推荐阅读:

27 places to learn iOS development. Best ones.

52 people every iOS developer should follow on Twitter

 

文/张嘉夫(简书作者)
原文链接:http://www.jianshu.com/p/228535226656
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

给定一个数字数组,从这个数组里找出所有的数字组合,且各组合里的数字相加之和等于同一个数字

最近在做在线问卷考试的时候想到这么一个功能,在试卷内给定140分值的试题,从这些试题中随机生成一张100分值的试卷,这个想法的意思说成算法描述就是:

给定一个随意的数组,如[1,4,3,2,5,7,6,8,9],再给定一个数值,如10,要求:从数组中找出所有的组合,使组合内各数字的值相加等于10;如列出某些组合:[1,2,3,4],[2,3,5],[2,8]等等。

花了一上午的时间,把这个算法实现了(领导给我派了另外的活,我却在研究昨天晚上想到的算法问题,不太好吧?各位朋友,别告诉领导哦)。

题目分析:从题目中得出,由于组合内数字的个数不定,只要各数字相加之和等于固定的值就行,所以首先想到用递归的方式来处理,过程发下

假定数组为[1,2,3,4,5,6,8,7,9]

从第一个数开始,一共有三种状态:

(1)等于给定数,则结合缓存里的数产生一种组合并保存下来,再清空缓存并将除了此数之外的此组合的另外的数字存入缓存里

(2)小于给定数,则保存此数至缓存之后,给定数减去此数,然后进入下一次递归,之后将此数从缓存中删除,处理完之后,再跳过过此数,给定数不变,进入到下一次递归

(3)大于给定数,跳过此数,进入到下一次递归

 

经过这样处理之后,用递归的方法就实现了此算法,C#代码如下:

/// <summary> 
/// 给定一个数字数组,从这个数组中找出所有的组合,使组合中各数字相加等于一个给定数值 
/// </summary> 
/// <param name="nums">给定数组</param> 
/// <param name="num">给定数值</param> 
/// <param name="index">当前操作的数字在数组中的index</param> 
/// <param name="count">存储所有组合的可能数</param> 
/// <param name="results">存储所有组合的情况</param> 
static void NumCount(double[] nums, double num, int index, ref int count, ref List<List<double>> results) 
{ 
    //当前操作的数字=给定数值 
    if (nums[index] == num) 
    { 
        //保存此组合 
        results[results.Count - 1].Add(nums[index]); 
        count += 1; 
        //清空缓存并将除了此数之外的此组合的另外的数字存入缓存里 
        results.Add(new List<double>()); 
        for (int i = 0; i < results[results.Count - 2].Count - 1; i++) 
        { 
            results[results.Count - 1].Add(results[results.Count - 2][i]); 
        } 
        if (index == nums.Length - 1) 
        { 
            return; 
        } 
        //进入到下一递归 
        NumCount(nums, num, index + 1, ref count, ref results); 
    } 
    //大于给定数,跳过此数,进入到下一次递归 
    else if (nums[index] > num) 
    { 
        if (index == nums.Length - 1) 
        { 
            return; 
        } 
        //此组合无效,跳到下一递归 
        NumCount(nums, num, index + 1, ref count, ref results); 
    } 
    //小于给定数,则保存此数至缓存之后,给定数减去此数,然后进入下一次递归,之后将此数从缓存中删除 
    else
    { 
        if (index == nums.Length - 1) 
        { 
            return; 
        } 
        results[results.Count - 1].Add(nums[index]); 
        //给定数减去此数 
        var tempNum = num - nums[index]; 
        //进入到下一递归 
        NumCount(nums, tempNum, index + 1, ref count, ref results); 
        //将此数从缓存中删除 
        results[results.Count - 1].RemoveAt(results[results.Count - 1].Count - 1); 
        if (index < nums.Length - 1) 
        { 
            //再跳过过此数,给定数不变,进入到下一次递归 
            NumCount(nums, num, index + 1, ref count, ref results); 
        } 
    } 
}

运行结果:

 

这篇文章是我一个字一个字敲出来的,觉得好就给个赞吧,3Q

JSON风格指南

版本:0.9

英文版:https://google.github.io/styleguide/jsoncstyleguide.xml

翻译:Darcy Liu

译文状态:草稿

简介

该风格指南是对在Google创建JSON APIs而提供的指导性准则和建议。总体来讲,JSON APIs应遵循JSON.org上的规范。这份风格指南澄清和标准化了特定情况,从而使Google的JSON APIs有一种标准的外观和感觉。这些指南适用于基于RPC和基于REST风格的API的JSON请求和响应。

定义

为了更好地实现这份风格指南的目的,下面几项需要说明:

  • 属性(property) – JSON对象内的键值对(name/value pair)
  • 属性名(property name) – 属性的名称(或键)
  • 属性值(property value) – 分配给属性的值

示例:

{
  // 一组键值对称作一个 "属性".
  "propertyName": "propertyValue"
}

Javascript的数字(number)包含所有的浮点数,这是一个宽泛的指定。在这份指南中,数字(number)指代Javascript中的数字(number)类型,而整型(integer)则指代整型。

一般准则

注释

JSON对象中不包含注释。

JSON对象中不应该包含注释。该指南中的某些示例含有注释。但这仅仅是为了说明示例。

{
  // 你可能在下面的示例中看到注释,
  // 但不要在你的JSON数据中加入注释.
  "propertyName": "propertyValue"
}

双引号

使用双引号

如果(某个)属性需要引号,则必须使用双引号。所有的属性名必须在双引号内。字符类型的属性值必须使用双引号。其它类型值(如布尔或数字)不应该使用双引号。

扁平化数据 VS 结构层次

不能为了方便而将数据任意分组

JSON中的数据元素应以扁平化方式呈现。不能为了方便而将数据任意分组。

在某些情况下,比如描述单一结构的一批属性,因为它被用来保持结构层次,因而是有意义的。但是遇到这些情况还是应当慎重考虑,记住只有语义上有意义的时候才使用它。例如,一个地址可以有表示两种方式,但结构化的方式对开发人员来讲可能更有意义:

扁平化地址:

{
  "company": "Google",
  "website": "http://www.google.com/",
  "addressLine1": "111 8th Ave",
  "addressLine2": "4th Floor",
  "state": "NY",
  "city": "New York",
  "zip": "10011"
}

结构化地址:

{
  "company": "Google",
  "website": "http://www.google.com/",
  "address": {
    "line1": "111 8th Ave",
    "line2": "4th Floor",
    "state": "NY",
    "city": "New York",
    "zip": "10011"
  }
}

属性名准则

属性名格式

选择有意义的属性名

属性名必须遵循以下准则:

  • 属性名应该是具有定义语义的有意义的名称。
  • 属性名必须是驼峰式的,ASCII码字符串。
  • 首字符必须是字母,下划线(_)或美元符号($)。
  • 随后的其他字符可以是字母,数字,下划线(_)或美元符号($)。
  • 应该避免使用Javascript中的保留关键字(下文附有Javascript保留字清单)

这些准则反映JavaScript标识符命名的指导方针。使JavaScript的客户端可以使用点符号来访问属性。(例如,result.thisIsAnInstanceVariable).

下面是一个对象的一个属性的例子:

{
  "thisPropertyIsAnIdentifier": "identifier value"
}

JSON Map中的键名

在JSON Map中键名可以使用任意Unicode字符

当JSON对象作为Map(映射)使用时,属性的名称命名规则并不适用。Map(也称作关联数组)是一个具有任意键/值对的数据类型,这些键/值对通过特定的键来访问相应的值。JSON对象和JSON Map在运行时看起来是一样的;这个特性与API设计相关。当JSON对象被当作map使用时,API文件应当做出说明。

Map的键名不一定要遵循属性名称的命名准则。键名可以包含任意的Unicode字符。客户端可使用maps熟悉的方括号来访问这些属性。(例如result.thumbnails["72"]

{
  // "address" 属性是一个子对象
  // 包含地址的各部分.
  "address": {
    "addressLine1": "123 Anystreet",
    "city": "Anytown",
    "state": "XX",
    "zip": "00000"
  },
  // "address" 是一个映射
  // 含有响应规格所对应的URL,用来映射thumbnail url的像素规格
  "thumbnails": {
    "72": "http://url.to.72px.thumbnail",
    "144": "http://url.to.144px.thumbnail"
  }
}

保留的属性名称

某些属性名称会被保留以便能在多个服务间相容使用

保留属性名称的详细信息,连同完整的列表,可在本指南后面的内容中找到。服务应按照被定义的语义来使用属性名称。

单数属性名 VS 复数属性名

数组类型应该是复数属性名。其它属性名都应该是单数。

数组通常包含多个条目,复数属性名就反映了这点。在下面这个保留名称中可以看到例子。属性名items是复数因为它描述的是一组对象。大多数的其它字段是单数。

当然也有例外,尤其是涉及到数字的属性值的时候。例如,在保留属性名中,totalItemstotalItem更合理。然后,从技术上讲,这并不违反风格指南,因为 totalItems 可以被看作 totalOfItems, 其中 total 是单数(依照风格指南),OfItems 用来限定总数。字段名也可被改为 itemCount,这样看起来更象单数.

{
  // 单数
  "author": "lisa",
  // 一组同胞, 复数
  "siblings": [ "bart", "maggie"],
  // "totalItem" 看起来并不对
  "totalItems": 10,
  // 但 "itemCount" 要好些
  "itemCount": 10
}

命名冲突

通过选择新的属性名或将API版本化来避免命名冲突

新的属性可在将来被添加进保留列表中。JSON中不存在命名空间。如果存在命名冲突,可通过选择新的属性名或者版本化来解决这个问题。例如,假设我们由下面的JSON对象开始:

{
  "apiVersion": "1.0",
  "data": {
    "recipeName": "pizza",
    "ingredients": ["tomatoes", "cheese", "sausage"]
  }
}

如果我们希望将来把ingredients列为保留字,我们可以通过下面两件事情来达成:

1.选一个不同的名字

{
  "apiVersion": "1.0",
  "data": {
    "recipeName": "pizza",
    "ingredientsData": "Some new property",
    "ingredients": ["tomatoes", "cheese", "sausage"]
  }
}

2.在主版本上重新命名属性

{
  "apiVersion": "2.0",
  "data": {
    "recipeName": "pizza",
    "ingredients": "Some new property",
    "recipeIngredients": ["tomatos", "cheese", "sausage"]
  }
}

属性值准则

属性值格式

属性值必须是Unicode 的 booleans(布尔), 数字(numbers), 字符串(strings), 对象(objects), 数组(arrays), 或 null.

JSON.org上的标准准确地说明了哪些类型的数据可以作为属性值。这包含Unicode的布尔(booleans), 数字(numbers), 字符串(strings), 对象(objects), 数组(arrays), 或 null。JavaScript表达式是不被接受的。APIs应该支持该准则,并为某个特定的属性选择最合适的数据类型(比如,用numbers代表numbers等)。

好的例子:

{
  "canPigsFly": null,     // null
  "areWeThereYet": false, // boolean
  "answerToLife": 42,     // number
  "name": "Bart",         // string
  "moreData": {},         // object
  "things": []            // array
}

不好的例子:

{
  "aVariableName": aVariableName,         // Bad - JavaScript 标识符
  "functionFoo": function() { return 1; } // Bad - JavaScript 函数
}

空或Null 属性值

考虑移除空或null值

如果一个属性是可选的或者包含空值或null值,考虑从JSON中去掉该属性,除非它的存在有很强的语义原因。

{
  "volume": 10,

  // 即使 "balance" 属性值是零, 它也应当被保留,
  // 因为 "0" 表示 "均衡" 
  // "-1" 表示左倾斜和"+1" 表示右倾斜
  "balance": 0,

  // "currentlyPlaying" 是null的时候可被移除
  // "currentlyPlaying": null
}

枚举值

枚举值应当以字符串的形式呈现

随着APIs的发展,枚举值可能被添加,移除或者改变。将枚举值当作字符串可以使下游用户幽雅地处理枚举值的变更。

Java代码:

public enum Color {
  WHITE,
  BLACK,
  RED,
  YELLOW,
  BLUE
}

JSON对象:

{
  "color": "WHITE"
}

属性值数据类型

上面提到,属性值必须是布尔(booleans), 数字(numbers), 字符串(strings), 对象(objects), 数组(arrays), 或 null. 然而在处理某些值时,定义一组标准的数据类型是非常有用的。这些数据类型必须始终是字符串,但是为了便于解析,它们也会以特定的方式被格式化。

日期属性值

日期应该使用RFC3339建议的格式

日期应该是RFC 3339所建议的字符串格式。

{
  "lastUpdate": "2007-11-06T16:34:41.000Z"
}

时间间隔属性值

时间间隔应该使用ISO 8601建议的格式

时间间隔应该是ISO 8601所建议的字符串格式。

{
  // 三年, 6个月, 4天, 12小时,
  // 三十分钟, 5秒
  "duration": "P3Y6M4DT12H30M5S"
}

纬度/经度属性值

纬度/经度应该使用ISO 6709建议的格式

纬度/经度应该是ISO 6709所建议的字符串格式。 而且, 它应该更偏好使用 e ±DD.DDDD±DDD.DDDD 角度格式.

{
  // 自由女神像的纬度/经度位置.
  "statueOfLiberty": "+40.6894-074.0447"
}

JSON结构和保留属性名

为了使APIs保持一致的借口,JSON对象应当使用以下的结构。该结构适用于JSON的请求和响应。在这个结构中,某些属性名将被保留用作特殊用途。这些属性并不是必需的,也就是说,每个保留的属性可能出现零次或一次。但是如果服务需要这些属性,建议遵循该命名条约。下面是一份JSON结构语义表,以Orderly格式呈现(现在已经被纳入 JSONSchema)。你可以在该指南的最后找到关于JSON结构的例子。

object {
  string apiVersion?;
  string context?;
  string id?;
  string method?;
  object {
    string id?
  }* params?;
  object {
    string kind?;
    string fields?;
    string etag?;
    string id?;
    string lang?;
    string updated?; # date formatted RFC 3339
    boolean deleted?;
    integer currentItemCount?;
    integer itemsPerPage?;
    integer startIndex?;
    integer totalItems?;
    integer pageIndex?;
    integer totalPages?;
    string pageLinkTemplate /^https?:/ ?;
    object {}* next?;
    string nextLink?;
    object {}* previous?;
    string previousLink?;
    object {}* self?;
    string selfLink?;
    object {}* edit?;
    string editLink?;
    array [
      object {}*;
    ] items?;
  }* data?;
  object {
    integer code?;
    string message?;
    array [
      object {
        string domain?;
        string reason?;
        string message?;
        string location?;
        string locationType?;
        string extendedHelp?;
        string sendReport?;
      }*;
    ] errors?;
  }* error?;
}*;

JSON对象有一些顶级属性,然后是data对象或error对象,这两者不会同时出现。下面是这些属性的解释。

顶级保留属性名称

顶级的JSON对象可能包含下面这些属性

apiVersion

属性值类型: 字符串(string)
父节点: -

呈现请求中服务API期望的版本,以及在响应中保存的服务API版本。应随时提供apiVersion。这与数据的版本无关。将数据版本化应该通过其他的机制来处理,如etag。

示例:

{ "apiVersion": "2.1" }

context

属性值类型: 字符串(string)
父节点: -

客户端设置这个值,服务器通过数据作出回应。这在JSON-P和批处理中很有用,用户可以使用context将响应与请求关联起来。该属性是顶级属性,因为不管响应是成功还是有错误,context总应当被呈现出来。context不同于id在于context由用户提供而id由服务分配。

示例:

请求 #1:

http://www.google.com/myapi?context=bart

请求 #2:

http://www.google.com/myapi?context=lisa

响应 #1:

{
  "context": "bart",
  "data": {
    "items": []
  }
}

响应 #2:

{
  "context": "lisa",
  "data": {
    "items": []
  }
}

公共的JavaScript处理器通过编码同时处理以下两个响应:

function handleResponse(response) {
  if (response.result.context == "bart") {
    // 更新页面中的 "Bart" 部分。
  } else if (response.result.context == "lisa") {
    // 更新页面中的 "Lisa" 部分。
  }
}

id

属性值类型: 字符串(string)
父节点: -

服务提供用于识别响应的标识(无论请求是成功还是有错误)。这对于将服务日志和单独收到的响应对应起来很有用。

示例:

{ "id": "1" }

method

属性值类型: 字符串(string)
父节点: -

表示对数据即将执行,或已被执行的操作。在JSON请求的情况下,method属性可以用来指明对数据进行何种操作。在JSON响应的情况下,method属性表明对数据进行了何种操作。

一个JSON-RPC请求的例子,其中method属性表示要在params上执行的操作:

{
  "method": "people.get",
  "params": {
    "userId": "@me",
    "groupId": "@self"
  }
}

params

属性值类型: 对象(object)
父节点: -

这个对象作为输入参数的映射发送给RPC请求。它可以和method属性一起用来执行RPC功能。若RPC方法不需要参数,则可以省略该属性。

示例:

{
  "method": "people.get",
  "params": {
    "userId": "@me",
    "groupId": "@self"
  }
}

data

属性值类型: 对象(object)
父节点: -

包含响应的所有数据。该属性本身拥有许多保留属性名,下面会有相应的说明。服务可以自由地将自己的数据添加到这个对象。一个JSON响应要么应当包含一个data对象,要么应当包含error对象,但不能两者都包含。如果dataerror同时出现,则error对象优先。

error

属性值类型: 对象(object)
父节点: -

表明错误发生,提供错误的详细信息。错误的格式支持从服务返回一个或多个错误。一个JSON响应可以有一个data对象或者一个error对象,但不能两者都包含。如果dataerror都出现,error对象优先。

示例:

{
  "apiVersion": "2.0",
  "error": {
    "code": 404,
    "message": "File Not Found",
    "errors": [{
      "domain": "Calendar",
      "reason": "ResourceNotFoundException",
      "message": "File Not Found
    }]
  }
}

data对象的保留属性名

JSON对象的data属性可能包含以下属性。

data.kind

属性值类型: 字符串(sting)
父节点: data

kind属性是对某个特定的对象存储何种类型的信息的指南。可以把它放在data层次,或items的层次,或其它任何有助于区分各类对象的对象中。如果kind对象被提供,它应该是对象的第一个属性(详见下面的属性顺序部分)。

示例:

// "Kind" indicates an "album" in the Picasa API.
{"data": {"kind": "album"}}

data.fields

属性值类型: 字符串(string)
父节点: data

表示做了部分GET之后响应中出现的字段,或做了部分PATCH之后出现在请求中的字段。该属性仅在做了部分GET请求/批处理时存在,且不能为空。

示例:

{
  "data": {
    "kind": "user",
    "fields": "author,id",
    "id": "bart",
    "author": "Bart"
  }
}   

data.etag

属性值类型: 字符串(string)
父节点: data

响应时提供etag。关于GData APIs中的ETags详情可以在这里找到:http://code.google.com/apis/gdata/docs/2.0/reference.html#ResourceVersioning

示例:

{"data": {"etag": "W/"C0QBRXcycSp7ImA9WxRVFUk.""}}

data.id

属性值类型: 字符串(string)
父节点: data

一个全局唯一标识符用于引用该对象。id属性的具体细节都留给了服务。

示例:

{"data": {"id": "12345"}}

data.lang

属性值类型: 字符串(string)(格式由BCP 47指定)
父节点: data (或任何子元素)

表示该对象内其他属性的语言。该属性模拟HTML的lang属性和XML的xml:lang属性。值应该是BCP 47中定义的一种语言值。如果一个单一的JSON对象包含的数据有多种语言,服务负责制定和标明lang属性的适当位置。

示例:

{"data": {
  "items": [
    { "lang": "en",
      "title": "Hello world!" },
    { "lang": "fr",
      "title": "Bonjour monde!" }
  ]}
}

data.updated

属性值类型: 字符串(string)(格式由RFC 3339指定)
父节点: data

指明条目更新的最后日期/时间(RFC 3339),由服务规定。

示例:

{"data": {"updated": "2007-11-06T16:34:41.000Z"}}

data.deleted

属性值类型: 布尔(boolean)
父节点: data (或任何子元素)

一个标记元素,当出现时,表示包含的条目已被删除。如果提供了删除属性,它的值必须为true;为false会导致混乱,应该避免。

示例:

{"data": {
  "items": [
    { "title": "A deleted entry",
      "deleted": true
    }
  ]}
}

data.items

属性值类型: 数组(array)
父节点: data

属性名items被保留用作表示一组条目(例如,Picasa中的图片,YouTube中的视频)。这种结构的目的是给与当前结果相关的集合提供一个标准位置。例如,知道页面上的items是数组,JSON输出便可能插入一个通用的分页系统。如果items存在,它应该是data对象的最后一个属性。(详见下面的属性顺序部分)。

示例:

{
  "data": {
    "items": [
      { /* Object #1 */ },
      { /* Object #2 */ },
      ...
    ]
  }
}

用于分页的保留属性名

下面的属性位于data对象中,用来给一列数据分页。一些语言和概念是从OpenSearch规范中借鉴过来的。

下面的分页数据允许各种风格的分页,包括:

  • 上一页/下一页 – 允许用户在列表中前进和后退,一次一页。nextLinkpreviousLink属性 (下面的”链接保留属性名”部分有描述) 用于这种风格的分页。
  • 基于索引的分页 – 允许用户直接跳到条目列表的某个条目位置。例如,要从第200个条目开始载入10个新的条目,开发者可以给用户提供一个URL的查询字符串?startIndex=200
  • 基于页面的分页 – 允许用户直接跳到条目内的具体页。这跟基于索引的分页很类似,但节省了开发者额外的步骤,不需再为新一页的条目计算条目索引。例如,开发人员可以直接跳到第20页,而不是跳到第200条条目。基于页面分页的网址,可以使用查询字符串?page=1?page=20pageIndextotalPages 属性用作这种风格的分页.

在这份指南的最后可以找到如何使用这些属性来实现分页的例子。

data.currentItemCount

属性值类型: 整数(integer)
父节点: data

结果集中的条目数目。应该与items.length相等,并作为一个便利属性提供。例如,假设开发者请求一组搜索条目,并且要求每页10条。查询集共有14条。第一个条目页将会有10个条目,因此itemsPerPagecurrentItemCount都应该等于10。下一页的条目还剩下4条;itemsPerPage仍然是10,但是currentItemCount是4.

示例:

{
  "data": {
    // "itemsPerPage" 不需要与 "currentItemCount" 匹配
    "itemsPerPage": 10,
    "currentItemCount": 4
  }
}

data.itemsPerPage

属性值类型: 整数(integer)
父节点: data

items结果的数目。未必是data.items数组的大小;如果我们查看的是最后一页,data.items的大小可能小于itemsPerPage。但是,data.items的大小不应超过itemsPerPage

示例:

{
  "data": {
    "itemsPerPage": 10
  }
}

data.startIndex

属性值类型: 整数(integer)
父节点: data

data.items中第一个条目的索引。为了一致,startIndex应从1开始。例如,第一组items中第一条的startIndex应该是1。如果用户请求下一组数据,startIndex可能是10。

示例:

{
  "data": {
    "startIndex": 1
  }
}

data.totalItems

属性值类型: 整数(integer)
父节点: data

当前集合中可用的总条目数。例如,如果用户有100篇博客文章,响应可能只包含10篇,但是totalItems应该是100。

示例:

{
  "data": {
    "totalItems": 100
  }
}

data.pagingLinkTemplate

属性值类型: 字符串(string)
父节点: data

URL模板指出用户可以如何计算随后的分页链接。URL模板中也包含一些保留变量名:表示要载入的条目的{index},和要载入的页面的{pageIndex}

示例:

{
  "data": {
    "pagingLinkTemplate": "http://www.google.com/search/hl=en&q=chicago+style+pizza&start={index}&sa=N"
  }
}

data.pageIndex

属性值类型: 整数(integer)
父节点: data

条目的当前页索引。为了一致,pageIndex应从1开始。例如,第一页的pageIndex是1。pageIndex也可以通过基于条目的分页而计算出来pageIndex = floor(startIndex / itemsPerPage) + 1

示例:

{
  "data": {
    "pageIndex": 1
  }
}

data.totalPages

属性值类型: 整数(integer)
父节点: data

当前结果集中的总页数。totalPages也可以通过上面基于条目的分页属性计算出来: totalPages = ceiling(totalItems / itemsPerPage).

示例:

{
  "data": {
    "totalPages": 50
  }
}

用于链接的保留属性名

下面的属性位于data对象中,用来表示对其他资源的引用。有两种形式的链接属性:1)对象,它可以包含任何种类的引用(比如JSON-RPC对象),2)URL字符串,表示资源的URIs(后缀总为’Link’)。

data.self / data.selfLink

属性值类型: 对象(object)/字符串(string)
父节点: data

自身链接可以用于取回条目数据。比如,在用户的Picasa相册中,条目中的每个相册对象都会包含一个selfLink用于检索这个相册的相关数据。

示例:

{
  "data": {
    "self": { },
    "selfLink": "http://www.google.com/feeds/album/1234"
  }
}

data.edit / data.editLink

属性值类型: 对象(object)/字符串(string)
父节点: data

编辑链接表明用户可以发送更新或删除请求。这对于REST风格的APIs很有用。该链接仅在用户能够更新和删除该条目时提供。

示例:

{
  "data": {
    "edit": { },
    "editLink": "http://www.google.com/feeds/album/1234/edit"
  }
}

data.next / data.nextLink

属性值类型: 对象(object)/字符串(string)
父节点: data

该下一页链接标明如何取得更多数据。它指明载入下一组数据的位置。它可以同itemsPerPagestartIndextotalItems 属性一起使用用于分页数据。

示例:

{
  "data": {
    "next": { },
    "nextLink": "http://www.google.com/feeds/album/1234/next"
  }
}

data.previous / data.previousLink

属性值类型: 对象(object)/字符串(string)
父节点: data

该上一页链接标明如何取得更多数据。它指明载入上一组数据的位置。它可以连同itemsPerPagestartIndextotalItems 属性用于分页数据。

示例:

{
  "data": {
    "previous": { },
    "previousLink": "http://www.google.com/feeds/album/1234/next"
  }
}

错误对象中的保留属性名

JSON对象的error属性应包含以下属性。

error.code

属性值类型: 整数(integer)
父节点: error

表示该错误的编号。这个属性通常表示HTTP响应码。如果存在多个错误,code应为第一个出错的错误码。

示例:

{
  "error":{
    "code": 404
  }
}

error.message

属性值类型: 字符串(string)
父节点: error

一个人类可读的信息,提供有关错误的详细信息。如果存在多个错误,message应为第一个错误的错误信息。

示例:

{
  "error":{
    "message": "File Not Found"
  }
}   

error.errors

属性值类型: 数组(array)
父节点: error

包含关于错误的附加信息。如果服务返回多个错误。errors数组中的每个元素表示一个不同的错误。

示例:

{ "error": { "errors": [] } }   

error.errors[].domain

属性值类型: 字符串(string)
父节点: error.errors

服务抛出该错误的唯一识别符。它帮助区分服务的从普通协议错误(如,找不到文件)中区分出具体错误(例如,给日历插入事件的错误)。

示例:

{
  "error":{
    "errors": [{"domain": "Calendar"}]
  }
}

error.errors[].reason

属性值类型: 字符串(string)
父节点: error.errors

该错误的唯一识别符。不同于error.code属性,它不是HTTP响应码。

示例:

{
  "error":{
    "errors": [{"reason": "ResourceNotFoundException"}]
  }
}

error.errors[].message

属性值类型: 字符串(string)
父节点: error.errors

一个人类可读的信息,提供有关错误的更多细节。如果只有一个错误,该字段应该与error.message匹配。

示例:

{
  "error":{
    "code": 404
    "message": "File Not Found",
    "errors": [{"message": "File Not Found"}]
  }
}       

error.errors[].location

属性值类型: 字符串(string)
父节点: error.errors

错误发生的位置(根据locationType字段解释该值)。

示例:

{
  "error":{
    "errors": [{"location": ""}]
  }
}

error.errors[].locationType

属性值类型: 字符串(string)
父节点: error.errors

标明如何解释location属性。

示例:

{
  "error":{
    "errors": [{"locationType": ""}]
  }
}

error.errors[].extendedHelp

属性值类型: 字符串(string)
父节点: error.errors

help text的URI,使错误更易于理解。

示例:(注:原示例这里有笔误,中文版这里做了校正)

{
  "error":{
    "errors": [{"extendedHelp": "http://url.to.more.details.example.com/"}]
  }
}

error.errors[].sendReport

属性值类型: 字符串(string)
父节点: error.errors

report form的URI,服务用它来收集错误状态的数据。该URL会预先载入描述请求的参数

示例:

{
  "error":{
    "errors": [{"sendReport": "http://report.example.com/"}]
  }
}

属性顺序

在JSON对象中属性可有任意顺序。然而,在某些情况下,有序的属性可以帮助分析器快速解释数据,并带来更好的性能。在移动环境下的解析器就是个例子,在这种情况下,性能和内存是至关重要的,不必要的解析也应尽量避免。

Kind属性

Kind属性应为第一属性

假设一个解析器负责将一个原始JSON流解析成一个特定的对象。kind属性会引导解析器将适合的对象实例化。因而它应该是JSON对象的第一个属性。这仅适用于对象有一个kind属性的情况(通常可以在dataitems属性中找到)。

Items属性

items应该是data对象的最后一个属性

这使得阅读每一个具体条目前前已读所有的集合属性。在有很多条目的情况下,这样就避免了开发人员只需要从数据的字段时不必要的解析这些条目。

这让阅读所有集合属性先于阅读单个条目。如遇多个条目的情况,当开发者仅需要数据中的字段时,这就可避免解析不必要的条目。

属性顺序示例:

// "kind" 属性区分 "album" 和 "photo".
// "Kind" 始终是它父对象的第一个属性.
// "items" 属性是 "data" 对象的最后一个属性.
{
  "data": {
    "kind": "album",
    "title": "My Photo Album",
    "description": "An album in the user's account",
    "items": [
      {
        "kind": "photo",
        "title": "My First Photo"
      }
    ]
  }
}

示例

YouTube JSON API

这是YouTube JSON API响应对象的示例。你可以从中学到更多关于YouTube JSON API的内容:http://code.google.com/apis/youtube/2.0/developers_guide_jsonc.html

{
  "apiVersion": "2.0",
  "data": {
    "updated": "2010-02-04T19:29:54.001Z",
    "totalItems": 6741,
    "startIndex": 1,
    "itemsPerPage": 1,
    "items": [
      {
        "id": "BGODurRfVv4",
        "uploaded": "2009-11-17T20:10:06.000Z",
        "updated": "2010-02-04T06:25:57.000Z",
        "uploader": "docchat",
        "category": "Animals",
        "title": "From service dog to SURFice dog",
        "description": "Surf dog Ricochets inspirational video ...",
        "tags": [
          "Surf dog",
          "dog surfing",
          "dog",
          "golden retriever",
        ],
        "thumbnail": {
          "default": "http://i.ytimg.com/vi/BGODurRfVv4/default.jpg",
          "hqDefault": "http://i.ytimg.com/vi/BGODurRfVv4/hqdefault.jpg"
        },
        "player": {
          "default": "http://www.youtube.com/watch?v=BGODurRfVv4&feature=youtube_gdata",
          "mobile": "http://m.youtube.com/details?v=BGODurRfVv4"
        },
        "content": {
          "1": "rtsp://v5.cache6.c.youtube.com/CiILENy73wIaGQn-Vl-0uoNjBBMYDSANFEgGUgZ2aWRlb3MM/0/0/0/video.3gp",
          "5": "http://www.youtube.com/v/BGODurRfVv4?f=videos&app=youtube_gdata",
          "6": "rtsp://v7.cache7.c.youtube.com/CiILENy73wIaGQn-Vl-0uoNjBBMYESARFEgGUgZ2aWRlb3MM/0/0/0/video.3gp"
        },
        "duration": 315,
        "rating": 4.96,
        "ratingCount": 2043,
        "viewCount": 1781691,
        "favoriteCount": 3363,
        "commentCount": 1007,
        "commentsAllowed": true
      }
    ]
  }
}

分页示例

如何将Google搜索条目作为JSON对象展现出来,对分页变量也有特别关注。

这个示例仅用作说明。下面的API实际上并不存在。

这是Google搜索结果页面的示例: image

image

这是该页面JSON形式的呈现:

{
  "apiVersion": "2.1",
  "id": "1",
  "data": {
    "query": "chicago style pizza",
    "time": "0.1",
    "currentItemCount": 10,
    "itemsPerPage": 10,
    "startIndex": 11,
    "totalItems": 2700000,
    "nextLink": "http://www.google.com/search?hl=en&q=chicago+style+pizza&start=20&sa=N"
    "previousLink": "http://www.google.com/search?hl=en&q=chicago+style+pizza&start=0&sa=N",
    "pagingLinkTemplate": "http://www.google.com/search/hl=en&q=chicago+style+pizza&start={index}&sa=N",
    "items": [
      {
        "title": "Pizz'a Chicago Home Page"
        // More fields for the search results
      }
      // More search results
    ]
  }
}

这是如何展现屏幕截图中的色块的例子(背景颜色对应下图中的颜色)

  • Results 11 – 20 of about 2,700,000 = startIndex
  • Results 11 – 20 of about 2,700,000 = startIndex + currentItemCount – 1
  • Results 11 – 20 of about 2,700,000 = totalItems
  • Search results = items (formatted appropriately)
  • Previous/Next = previousLink / nextLink
  • Numbered links in “Gooooooooooogle” = Derived from “pageLinkTemplate”. The developer is responsible for calculating the values for {index} and substituting those values into the “pageLinkTemplate”. The pageLinkTemplate’s {index} variable is calculated as follows:
    • Index #1 = 0 * itemsPerPage = 0
    • Index #2 = 2 * itemsPerPage = 10
    • Index #3 = 3 * itemsPerPage = 20
    • Index #N = N * itemsPerPage

附录

附录A:JavaScript中的保留字

下列JavaScript保留字应该避免在属性名中使用

下面的但在在JavaScript语言中被保留,不能作为在点访问符中使用。这份名单代表此时的最佳关键字的知识;列表可能会改变或根据您的特定的执行环境更改。

下面是JavaScript语言中的保留字,且不能在点访问符中使用。这份清单集合了当前最新的关键字,该清单可能会根据具体的执行环境而有所变更或改变。

来自ECMAScript 语言规范第五版

abstract
boolean break byte
case catch char class const continue
debugger default delete do double
else enum export extends
false final finally float for function
goto
if implements import in instanceof int interface
let long
native new null
package private protected public
return
short static super switch synchronized
this throw throws transient true try typeof
var volatile void
while with
yield

除了特别说明,该页面的内容均由共同创作协议(CC BY 3.0)授权许可,示例代码均由Apache 2.0许可证授权许可)