8 个最佳 PHP 库
PHP标准库 (SPL)的目的就是提供一组接口,让开发者在PHP5中充分利用面向对象编程。因此本文我们搜集了8个最好的,能辅助开发者简化他们的工作,为他们的开发任务服务的PHP库。 如果你喜欢本文,也许你对我们的其他文章感兴趣:8个给开发者的最好的PHP工具和应用 1. Whoops : 更好的php错误报告库 Whoops是一个易于处理和调试错误的PHP库 。它提供基于堆栈的的错误处理和好看的错误界面。它有个简单的API来处理异常,跟踪帧和数据,并能和任何框架整合(随时可用的集成端和Silex)。 Source 2. PhpFastCache phpFastCache 是一个开源的 PHP 缓存库,只提供一个简单的 PHP 文件,可方便集成到已有项目,支持多种缓存方法,包括:apc, memcache, memcached, wincache, files, pdo and mpdo。可通过简单的 API 来定义缓存的有效时间。 Source 3. Eden : 功能强大的 PHP 库 Eden是一个开源且免费的PHP快速开发类库。它包含很多组件用来自动加载、事件驱动、文档系统、缓存、模板、国际化、数据库、web服务、支付网关、装载和云服务技术。为了给我们最好的选择,他已经将现有的函数实现了与谷歌服务(Youtube, Drive, Contacts, Analytics, Checkout, Maps),Facebook(脸谱), Twitter(推特), Tumblr(轻博客), PayPal(贝宝), Authorize.net, FedEx(联邦快递), UPS(联合包裹服务公司), Amazon + Rackspace Clouds(亚马逊+Rackspace 云)等服务的交互。 Source 4. Php Error PHP Error 是一个开源的 PHP 库,用于转换标准的 PHP 错误信息,主要用于开发过程中的调试。PHP Error 紧密集成到 PHP 环境中,显示带语法高亮的错误提示。 Source 5. Detector Detector是一个开源的PHP类库用于检测关于用户的浏览器环境的许多东西。它可以获得浏览器的使用和浏览器的html5 css3功能,分析是否移动电话、平板电脑、桌面或网页爬虫和其他项如:颜色深度, 视口尺寸、cookie等支持。类库可以自动适应新的浏览器、版本和设备对每一个浏览器使用独特的用户代理字符。 Source 6. Opauth Opauth 是一个开源的 PHP 库,提供了 OAuth 认证的支持,让你无需关注不同 Provider 之间的差别,提供统一标准的访问方法。目前支持 Google、Twitter 和 Facebook,其他的 Provider 支持也将陆续提供。同时也支持处理任何 […]
View Details县及县以上行政区划代码(截止2010年12月31日)
110000 北京市 110100 市辖区110101 东城区110102 西城区110105 朝阳区110106 丰台区110107 石景山区110108 海淀区110109 门头沟区110111 房山区110112 通州区110113 顺义区110114 昌平区110115 大兴区110116 怀柔区110117 平谷区110200 县110228 密云县110229 延庆县120000 天津市120100 市辖区120101 和平区120102 河东区120103 河西区120104 南开区120105 河北区120106 红桥区120110 东丽区120111 西青区120112 津南区120113 北辰区120114 武清区120115 宝坻区120116 滨海新区120200 县120221 宁河县120223 静海县120225 蓟县130000 河北省130100 石家庄市130101 市辖区130102 长安区130103 桥东区130104 […]
View Details中华人民共和国行政区划
行政区划是国家为便于行政管理而分级划分的区域。因此,行政区划亦称行政区域。中国宪法规定,中华人民共和国的行政区域划分如下: (一)全国分为省、自治区、直辖市; (二)省、自治区分为自治州、县、自治县、市; (三)县、自治县分为乡、民族乡、镇。 直辖市和较大的市分为区、县。自治州分为县、自治县、市。 自治区、自治州、自治县都是民族自治地方。国家在必要时得设立特别行政区。在特别行政区内实行的制度按照具体情况由全国人民代表大会以法律规定。 目前中国有34个省级行政区,包括23个省、5个自治区、4个直辖市、2个特别行政区。在历史上和习惯上,各省级行政区都有简称。省级人民政府驻地称省会(首府),中央人民政府所在地是首都。北京是中国的首都。 乡镇是中国最基层的行政单位。自治区、自治州、自治县是少数民族聚居地区的民族自治地方,它们都是祖国不可分割的部分。国家根据需要,还可以设立特别行政区。此外,为了便于行政管理和经济建设,为了加强民族团结,国家可根据需要对行政区划作必要的调整和变更。 香港和澳门是中国领土的一部分。中国政府已于1997年7月1日对香港恢复行使主权,成立了香港特别行政区。于1999年12月20日对澳门恢复行使主权,成立了澳门特别行政区。 中华人民共和国行政区划图(点击进入) 目前,全国共划分为23个省、5个自治区、4个直辖市、2个特别行政区(见下表)。 北 京 天 津 河 北 山 西 内蒙古 辽 宁 吉 林 黑龙江 上 海 江 苏 浙 江 安 徽 福 建 江 西 山 东 河 南 湖 北 湖 南 广 东 广 西 海 南 重 庆 四 川 贵 州 云 南 西 藏 陕 西 甘 肃 青 海 宁 夏 新 疆 香 港 澳 门 台 湾 中华人民共和国行政区划与地名法律法规 1.国务院关于行政区划管理的规定(1985-1-15) 2.地名管理条例(1986-1-23) 3.行政区域边界争议处理条例(1989-2-3) 4.行政区域界线管理条例(2002-5-23) 5.地方志工作条例(2006-5-18) 中国历史政区 夏商周 国家的出现 分封制 九州 春秋战国 县、郡的出现 秦 […]
View Details网易说央视不能采访苹果--从苹果道歉说起
最近,苹果公司道歉,体现了苹果是一家负责任的国际大公司。在这过程中,央视315的曝光及随后的跟踪报道起到了重要的作用。作为一介平民,我的最朴素的感觉是,最起码苹果用户的利益得到了更加好滴保障,是个好事情。 但是,网易在客户端新媒体发布了奇文,讲央视不能采访苹果。我觉得很奇怪,如果央视不去采访,网易不高兴,采访了,也不高兴。到底为什么网易总是不高兴呢!? 网易说自己是有态度滴媒体。所谓的有态度是什么呢?就是下面这篇文章(附后)。 我只想说,网易,315曝光的你窃取客户资料的事情,你们还没有道歉呢。什么时候道歉? ===================== 央视不请自来,“苹果”可以说 “不”(网易新媒体) 作为写字楼业主的“苹果”公司有权阻止记者进入其内部区域,而商业公司也有权决定是否、何时、何地、如何接受采访。苹果对央视说“不”,并无不当之处。 另一面专题:央视不请自来,“苹果”可以说“不” 导语:据央视报道,近日,苹果上海总公司拒绝央视记者进入其内部区域进行拍摄,并以记者预约未获得回应为由而拒绝了央视的采访。央视因此称其“傲慢”。作为写字楼业主的“苹果”公司有权阻止记者进入其内部区域,而商业公司也有权决定是否、何时、何地、如何接受采访。苹果对央视说“不”,并无不当之处。 在“苹果”地盘必须听“苹果”的 “苹果”作为写字楼承租人,理所应当有权对其承租的场所进行支配 “苹果”电脑贸易上海有限公司位于南京七路东海广场内。根据视频显示,央视记者乘电梯到达苹果公司所在的6号楼21层,电梯们甫一开启,便遇到了“苹果”公司的工作人员。在央视记者试图对苹果公司进行拍摄时,苹果公司工作人员在阻拦的同时回应“这是我们苹果的地方”,这句话也成为这篇《央视记者采访苹果遭拒,总部员工蛮横阻拦》的报道中最惹眼的一句话,成为了央视记者眼中苹果“傲慢”的依据。然而,苹果工作人员这句“这是我们苹果的地方”却是有凭有据经得起推敲的。 “东海广场”开发公司“SOHO中国”官方网站的介绍,“SOHO东海”是上海南京西路CBD中心的一座甲级写字楼,包括“苹果”、法国阳狮集团、德国驻上海总领事馆、希思黎、迪奥在内的多家公司、驻华机构在此租用楼层进行办公。“苹果”公司对该写字楼的租用情况存在两种可能:第一种可能是“苹果”公司是将整个东海广场6号楼21层包租下来,那么毫无疑问,在21层范围内,“苹果”公司当然享有排他的权利。即便是完全禁止记者进入该楼层,也是有理有据的。第二种可能是“苹果”公司是与其他公司共同合租21层,那么,走廊作为写字楼的公摊部分和居民楼的公摊部分一样,并不是“公共”的,就是归业主所“共有”,苹果公司也有对其进行占有、使用、收益或处分的权利。如此一来,“苹果”工作人员说“这是我们苹果的地方”确实是理直气壮。 “唐英年僭建风波”中,记者为避免“侵入他人土地”,宁愿在吊车上拍摄,也不敢擅入唐宅 明确了“苹果”公司作为写字楼承租人的权利后,央视报道中拍摄受到阻挠的问题也自然有了合理解释:“苹果”公司作为写字楼承租人,有权在租赁合同规定的时间内,按照合同规定的方式使用承租房屋,并且排除他人的妨碍。住户或用户阻止未经同意的拍摄行为只是在承租人行使权利的举动而已,并非傲慢之举。 以香港为例,在香港,记者在公众街道上可以自由采访。记者在公众地方居高临下拍摄住宅内的情况,或者在街道上拍摄屋内的情况是被允许的。例如,2012年2月13日,唐英年被《明报》揭发位于九龙塘约道5号及7号的大宅涉嫌僭建地库;其后唐英年承认事件并公开道歉,但否认有任何的隐瞒。根据香港法例,未经住户或用户同意,不得进入住宅内拍摄。为了拍摄到唐英年住宅内的情况,香港媒体使用了吊车,将摄影师升至空中,对唐英年的住宅进行拍摄。 至于进入住宅及写字楼,需取得住户或用户的同意,否则即属于“侵入他人土地”(trespass),可能需面对民事责任。 “苹果”公司没有义务必须接受采访 采访权不具国家强制力,采访人与被采访人地位平等,未经同意而进行采访是侵权行为 “苹果”公司被央视批评的第二个问题是拒绝接受采访。苹果公司是否有权拒绝采访,或者以“没有预约”的借口拒绝采访。这个问题的核心,要看“接受采访”是否是苹果公司的法定义务。 目前,在西方各国一般没有对采访权作法律上的直接规定。英美涉及新闻媒介的判例不多,但至少是在一些重要的判例中,没有直接使用采访权的概念。在成文法国家的新闻法中,如法国、瑞典、芬兰、丹麦、希腊等国的新闻法,都没有规定新闻记者的采访权。有学者认为,采访权是一种“请求权”,它实质上是请求他人同意接受采访的一种权利。这种看法将采访看作双方合意的行为:采访人提出采访请求,被采访人同意接受采访后,采访人方可行使权利。我国民法学家杨立新认为,新闻权的权利来源于我国宪法规定的新闻自由,采访权是一种与义务相对应的权利,而不是具有国家强制力的权利。 涉及到公民隐私、商业秘密,即使披着采访的外套,也不能掩盖强制采访这种行为的侵权本质。因为前者的权利是基本人权,而采访权是衍生权利,不能以牺牲基本人权来实现。采访者与被采访者之间就是一种平等的关系,强行采访就是禁止的侵权行为。 商业公司无必须接受采访的义务,曾有判例认为拒绝记者不意味着一定损害公众知情权 记者(甚至其他公民)有采访或者说发问的权利,企业自然也有拒绝采访和拍摄的权利,在全世界都是如此。根据具体的法律规定,进行信息公开(有可能是对特定人)是法定义务,接受记者采访并不是法定义务,接受特定媒体采访更不是。 以“BALTIMORESUN诉EHRLICH案”为例,美国马里兰州州长罗伯特?奥利克(Robert L Ehrlich)由于不满《巴尔的摩太阳报》(Baltimore Sun)一位记者及另一位专栏作家对他的新闻处理不公平,决定不再接受两人的采访,不回电话,也不配合任何需求,而且下令部属把两人列为拒绝往来户。《巴尔的摩太阳报》将罗伯特?奥利克告到法院,但法官却裁定,这种拒绝接受采访的举动没有违反宪法规定,州长有权利这么做。在判决中,法官认为,公众人物拒绝记者不免得罪媒体,但是州长并没有让所有记者都吃闭门羹,也没有忽略与其他媒体打交道的责任,也就是说,拒绝少数记者并没有导致公众知情的权利受损。 采访权、报道权针对的对象是除记者及当事人之外的第三方,强调第三方无权无故干扰采访,而非记者想采访,当事人就必须接受记者的采访。苹果作为一个企业,即便其规模再大、盈利再多,也是没有接受采访的法定义务的。你想要采访,我就必须接受,这样的“采访”与“审问”无异。 除法定强制公开信息之外,商业公司有权自愿选择信息公开的内容和方式 事实上,除公司概况及主营业务信息、基本财务信息、重大关联交易信息、审计意见、股东及董事人员信息等根据法律规定必须公开的信息之外,上市公司基于公司形象、投资者关系、回避诉讼风险等考虑,可以自愿选择信息披露的内容和方式。 企业可以根据需要和企业的自身情况选择是否披露以及选择何种方式披露。自愿信息披露在很大程度上取决于企业管理部门对信息重要性的判断和成本效益的比较。自愿信息披露能够提高公司信息的完整性和可靠性、增强公司股票流动性和提高股票价格,.有助于树立公司社会责任方面的良好形象。对于商业公司而言,是否接受媒体采访只是对信息披露方式的选择。接受采访可能有利于商业公司,比如,“苹果”公司如果选择接受采访,就能更好地向公众解释央视的疑问,打消消费者的误解。不过,这些都建立在“自愿”的基础上,属于公司自己的可选择项,只要公司愿意承担后果,选择不接受采访也是公司的权利。 无礼、傲慢的是“央视”而非“苹果”公司 前台物业与工作人员多次提醒央视记者采访需预约后,并未预约的记者仍执意进行采访 纵观这次采访的全过程,无礼和傲慢的一方不是“店大欺客”的“苹果”,而恰恰是央视的记者。 央视记者刚走入东海广场的商务楼,前台物业立刻向记者表示,如果有预约,可以叫预约的人将采访者接上楼,如果没有预约,则不能采访苹果公司。“东海广场”作为私人产业,由业主或产权人授权物业公司按照业主公约或者产权人的要求来进行管理。作为承租人的“苹果”公司已经明确表示未经预约,不接受采访。央视记者不顾前台物业提醒自行上楼,显然已经违背物业公司的意愿。 央视记者在到达“苹果”公司所在的21层后,“苹果”公司工作人员又前后三次告知采访需要“预约”。事实上,在“苹果”公司的官方网站上即可以找到联系采访的预约方式。央视的记者在交涉过程中也透露,自己知晓预约方式,但仍不停质问“找谁”、“没回复怎么办”、“他贵姓”。 通过企业的公关负责人或指定的公关公司预约是基本流程。任何一个正规企业都不会允许未经预约的媒体进入公司内部。事实上,即便是执法机关,没有合法的手续也无权强行进入一家公司。“早有心理准备”的央视记者不顾劝阻执意采访,苹果做出拒绝采访的反应也很正常——并非因为“苹果”公司的“傲慢”。 结语:“苹果”公司即非政府部门,也非事业单位、国有企业。面对“不请自来”的央视,作为商业公司的“苹果”当然有权说“不”。 转自:http://bbs.tianya.cn/post-worldlook-718458-1.shtml
View Details好看的励志电影
1、《荒岛余生》,我最喜欢的电影之一,汤姆·汉克斯主演,在我最低谷的时候给了我巨大的力量。 2、《风雨哈佛路》,一个最贫困的哈佛女孩从不退缩的奋斗,看完让人满身温暖,央视曾经播过。 3、《奔腾年代》,真实故事改编,一个中年丧子富翁,一个不得志的赛马教练,一个从未获得成功的骑师以及一匹瘸马共同创造的奇迹。 4、《铁拳男人》,拉塞·尔克劳主演,一个过气拳击手在拳台上为生存为荣誉而战。 5、《听见天堂》,一个热爱电影的盲童选择了用耳朵代替眼睛,去记录他生活的点点滴滴。 6、《洛奇》,史泰龙的经典励志片。 7、《卡特教练》,一个篮球教练率领一群看不到出路的孩子们为梦想作战。 8、《追梦女孩》,碧昂斯主演,3个黑人女孩踏上充满未知的星途最后获得成功的故事。 9、《阳光小美女》,充满亲情温暖的励志片。 10、《闻香识女人》,阿尔帕·西诺主演,里面的一曲《一步之遥》探戈舞曲奏响生命的热情。 11、《放牛班的春天》,即使是被社会遗忘的孩子,也有可能绽放生命的光芒。 12、《荒野生存》,一个理想主义者的传奇,一个流浪的故事,每一步都充满了艰辛,以坚韧的毅力,实践着寻找自我的梦想。 13、《侧耳倾听》,宫崎骏编剧近藤喜文导演的动画,一个少女初恋和成长的故事。 14、《当幸福来敲门》,威尔·史密斯难得的非商业电影,和儿子一起出演,很有噱头,而且很励志,很好看。 15、《黑暗中的舞者》拉尔斯·冯·特里厄的歌舞经典作品,一部别出心裁且唯美感性的歌舞片,有一点浪漫,也有一点灰色,但是却充满力量。 16、《心灵捕手》,马特·戴蒙和本·阿弗莱克两哥们的剧本,罗宾?威廉姆斯出任绿叶,一个天才少年的非典型成长片。 17、《永不妥协》,茱莉亚?罗伯茨借此片获得影后,十分真诚的励志电影。 18、《死亡诗社》,罗宾?威廉姆斯主演的经典励志电影,还是扮演一个春风化雨的老师,绝对推荐。 19、《百万宝贝》,奥斯卡最佳影片,克林特?伊斯特伍德导演,一个女子拳击手的奋斗里程。 20、《跳出我天地》,一个11岁小男孩破除重重阻碍追求梦想的芭蕾之路。 21、《三傻大闹宝莱坞》 22、《我的名字叫可汗》—沙鲁克?罕主演,与阿米尔汗同属印度三大男演员,这电影也超赞,励志。 23、《卡特教练/铁血教练》 24、《海上钢琴师/声光伴我飞/1900零的传奇》 25、《幸福终点站》请享受困难中的幸福 26、《贫民窟的百万富翁》 27、《辛德勒的名单》 28、《小鞋子》 29、《勇敢的心》也许英雄并不是无所不能的神明,但英雄一定是无所畏惧的勇士。在你站在霓虹闪烁的街头,当你面对卑鄙委琐的笑脸,你又想起了那个让你汗颜的华莱士,这时你收起脸上惯带的笑容,默默地向梅尔.吉布致敬,从来没有这么庄重。(励志电影 www.lz13.cn)因为他让我们明白,什么才是真正的英雄。“Freedom!”华莱士临死前的一声呐喊,把你的血也点燃了。 30、《奔腾年代》一个不甘寂寞的商人,从自行车配件维修、到销售汽车、再到经营马匹,本身他就是一个社会发展的缩影、一个努力不息的形象,自身的经历成为他演讲有力的支持与鼓励。 31、《美丽心灵》一个80岁时凭自己20岁的理论获得诺贝尔经济学奖的人。一个伟大的学者,一个生活的强者,一辈子都在和自己严重的幻想症做斗争。 32、《地球上的星星》-不仅阿米尔汗主演,而且是他自己导演的一部电影,优质老师引导“残障”天才少年走上正确道路。 33、《叫我第一名》--美国,青春励志,算不上喜剧,是喜剧结尾…… 34、《八月迷情》--首推,在风行和皮皮播放器上可以找到。 35、《心灵捕手》--马特.达蒙自编自演的影片,奥斯卡最佳原创剧本奖。 36、《想飞的钢琴少年》 37、《心灵点滴》 38、《闪电奇迹》 39、《我是山姆》他只有6岁儿童的智商,却有着对女儿无限的爱。 40、《风语者》收获了友谊,也收获了感动。 41、《天堂电影院》回忆过去,往事渐渐清晰。 42、《斯巴达300勇士》悲壮的史诗电影,无限感慨。 43、《洛奇》传奇拳手,面对年龄,对手,疾病,经济窘迫的无数挑战。 44、《无主之城》国内电影无法企及的高度。 45、《后天》灾难中,我们活了下来,信念和智慧让我们生生不息。 46、《楚门的世界》人生不过是一场戏。 47、《雨人》智障类电影的先河。 48、《机器管家》机器人?人? 49、《美国往事》不止是一段往事而已,在这段往事背后,还存在着很多很多。 50、《搏击俱乐部》毁灭=新生? 51、《愤怒的公牛》为了尊严,我们成为愤怒的公牛。 52、《美丽心灵》美丽心灵还是完美大脑? 53、《美丽人生》虽然最终他那样地死了,但是他的人生依旧闪亮着。 54、《飞越疯人院》自由何以成悲剧。 55、《告别昨日》告别往日的一切,重设未来。 56、《大鱼》人生是部童话。 57、《神奇遥控器》请放慢时间,体味人生。 58、《心灵捕手》 59、《放牛班的春天》 60、《闻香识女人》给自己一个活下去的理由。 61、《钢琴家》战争成就的钢琴家。 62、《奔腾年代》马和人一样,需要有人来激发他的潜能,人和马一样,你可以摧毁我的身体,但你击不倒我的心。 63、《杜拉拉升职记》 64、《地球上的星星》-不仅阿米尔汗主演,而且是他自己导演的一部电影,优质老师引导“残障”天才少年走上正确道路。 65、《叫我第一名》(Front of the Class) 66、《时尚》印度片讲述T台模特成长历程辛酸经历最后破茧成蝶的故事告诉我们如何看人对事如何在低谷中重生珍惜那些真心陪伴我们的人。 67、《滑稽舞娘》在音乐中励志的故事舞台虽小,但足以让我美艳惊人,绝处逢生。今年新出的片子。 68、《壮志凌云》 69、《百万美元宝贝》 70、《光荣之路》 80、《和平战士》 81、《绿色奇迹》 82、《雨人/手足情未了/手足情深》
View DetailsC#.NET Array扩展 Join/Compress/Decompress/Deserialize/IsInArray/Action
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.Data; using System.Data.Common; using System.Web.Script.Serialization; using System.Globalization; using System.Collections; using System.IO; using System.Drawing; using System.IO.Compression; using System.Runtime.Serialization.Formatters.Binary; namespace Pub.Class { public static class ArrayExtensions { public static bool IsNullEmpty(this Array array) { return array == null || array.Length == 0; } public static bool IsNullEmpty(this ArrayList list) { return (list == null) || (list.Count == 0); } public static int GetInArrayID(this string[] stringArray, string strSearch, bool caseInsensetive) { for (int i = 0; i < stringArray.Length; i++) { if (caseInsensetive) { if(strSearch.ToLower() == stringArray[i].ToLower()) return i; } else { if(strSearch == stringArray[i]) return i; } } return -1; } public static int GetInArrayID(this string[] stringArray, string strSearch) { return GetInArrayID(stringArray, strSearch, true); } public static string ToDelimitedString<T>(this T[] array, string format, string delimiter) { if (array == null || array.Length == 0) return string.Empty; if (format.IsNullEmpty()) format = "{0}"; StringBuilder builder = new StringBuilder(); for (int index = 0; index < array.Length; ++index) { if (index != 0) builder.Append(delimiter); builder.AppendFormat(format, array[index]); } return builder.ToString(); } public static T[] RemoveDuplicates<T>(this T[] array) { ArrayList al = new ArrayList(); for (int i = 0; i < array.Length; i++) { if (!al.Contains(array[i])) al.Add(array[i]); } return (T[])al.ToArray(typeof(T)); } public static T[] Slice<T>(this T[] array, int start, int end) { if (start >= array.Length) { start = 0; end = 0; } if (end < 0) end = array.Length - start - end; if (end <= start) end = start; if (end >= array.Length) end = array.Length-1; int len = end - start + 1; T[] res = new T[len]; for (int i = 0; i < len; i++) res[i] = array[i + start]; return res; } public static string Join<T>(this T[] array, string splitStr) { StringBuilder sb = new StringBuilder(); foreach(T info in array) { sb.AppendFormat("{0}{1}", info.ToString(), splitStr); } return sb.ToString().Left(sb.Length - splitStr.Length); } public static void Action<T>(this T[] inArray, Action<T, Int32> inAction) { for (int i0 = 0; i0 < inArray.GetLength(0); i0++) { inAction(inArray[i0], i0); } } public static void Action<T>(this T[,] inArray, Action<T, Int32, Int32> inAction) { for (int i0 = 0; i0 < inArray.GetLength(0); i0++) { for (int i1 = 0; i1 < inArray.GetLength(1); i1++) inAction(inArray[i0, i1], i0, i1); } } public static void Action<T>(this T[,,] inArray, Action<T, Int32, Int32, Int32> inAction) { for (int i0 = 0; i0 < inArray.GetLength(0); i0++) { for (int i1 = 0; i1 < inArray.GetLength(1); i1++) { for (int i2 = 0; i2 < inArray.GetLength(2); i2++) inAction(inArray[i0, i1, i2], i0, i1, i2); } } } public static void Action<T>(this T[,] inArray, Int32 inDimension, Int32 inIndex, Action<T, Int32> inAction) { if (inDimension == 0) { for (int i = 0; i < inArray.GetLength(1); i++) inAction(inArray[inIndex, i], i); } else if (inDimension == 1) { for (int i = 0; i < inArray.GetLength(0); i++) inAction(inArray[i, inIndex], i); } else { throw new ArgumentException("inDimension must be zero or one"); } } public static void Action<T>(this T[,,] inArray, Int32 inDimension, Int32 inIndex, Action<T, Int32, Int32> inAction) { if (inDimension == 0) { for (int i0 = 0; i0 < inArray.GetLength(1); i0++) { for (int i1 = 0; i1 < inArray.GetLength(2); i1++) inAction(inArray[inIndex, i0, i1], i0, i1); } } else if (inDimension == 1) { for (int i0 = 0; i0 < inArray.GetLength(0); i0++){ for (int i1 = 0; i1 < inArray.GetLength(2); i1++) inAction(inArray[i0, inIndex, i1], i0, i1); } } else if (inDimension == 2) { for (int i0 = 0; i0 < inArray.GetLength(0); i0++){ for (int i1 = 0; i1 < inArray.GetLength(1); i1++) inAction(inArray[i0, i1, inIndex], i0, i1); } } else { throw new ArgumentException("inDimension must be zero or one or two"); } } public static Image ToImage(this byte[] bytes) { if (bytes != null) { MemoryStream ms = new MemoryStream(bytes, 0, bytes.Length); ms.Write(bytes, 0, bytes.Length); return Image.FromStream(ms, true); } return null; } public static bool ToFile(this byte[] bytes, string fileName, FileMode fileMode) { bool returnValue = true; FileAccess fileAccess = FileAccess.ReadWrite; if (fileMode == FileMode.Append) fileAccess = FileAccess.Write; FileStream fs = new FileStream(fileName, fileMode, fileAccess); BinaryWriter bw = new BinaryWriter(fs); try { bw.Write(bytes); } catch (Exception) { returnValue = false; } finally { fs.Close(); bw.Close(); } return returnValue; } public static byte[] Compress(this byte[] data) { using (MemoryStream output = new MemoryStream()) { using (DeflateStream def = new DeflateStream(output, CompressionMode.Compress)) { def.Write(data, 0, data.Length); } return output.ToArray(); } } public static byte[] Decompress(this byte[] data) { using (MemoryStream input = new MemoryStream()) { input.Write(data, 0, data.Length); input.Position = 0; using (DeflateStream def = new DeflateStream(input, CompressionMode.Decompress)) { using (MemoryStream output = new MemoryStream()) { byte[] buff = new byte[64]; int read = -1; read = def.Read(buff, 0, buff.Length); while (read > 0) { output.Write(buff, 0, read); read = def.Read(buff, 0, buff.Length); } def.Close(); return output.ToArray(); } } } } public static T Decompress<T>(this byte[] compressedData) where T : class { return compressedData.Decompress().Deserialize<T>(); } public static T Deserialize<T>(this byte[] data) where T : class { var formatter = new BinaryFormatter(); using (MemoryStream ms = new MemoryStream(data)) return formatter.Deserialize(ms) as T; } public static bool IsInArray(this string[] stringArray, string strSearch, bool caseInsensetive) { return stringArray.GetInArrayID(strSearch, caseInsensetive) >= 0; } public static bool IsInArray(this string[] stringarray, string str) { return stringarray.IsInArray(str, false); } public static bool IsInIPArray(this string[] iparray, string ip) { string[] userip = ip.Split(@"."); for (int ipIndex = 0; ipIndex < iparray.Length; ipIndex++) { string[] tmpip = iparray[ipIndex].Split(@"."); int r = 0; for (int i = 0; i < tmpip.Length; i++) { if (tmpip[i] == "*") return true; if (userip.Length > i) { if (tmpip[i] == userip[i]) r ++; else break; } else break; } if (r == 4) return true; } return false; } //Action<string, int> MsgW3 = (s, i) => { Msg.Write(s + i); Msg.Write("<br />"); }; //"test1,test2,test3".Split(',').Action<string>(MsgW3); } } |
转自:http://www.cnblogs.com/livexy/archive/2010/07/06/1772502.html
View Details深入浅出Node.js(八):Connect模块解析(之二)静态文件中间件
上一篇专栏简单介绍了Connect模块的基本架构,它的执行模型十分简单,中间件机制也使得它十分易于扩展,具备良好的可伸缩性。在Connect的良好机制下,我们本章开始将逐步解开Connect生态圈中中间件部分,这部分给予Connect良好的功能扩展。 静态文件中间件 也许你还记得我曾经写过的Node.js静态文件服务器实战,那篇文章中我叙述了如何利用Node.js实现一个静态文件服务器的许多技术细节,包括路由实现,MIME,缓存控制,传输压缩,安全、欢迎页、断点续传等。但是这里我们不需要去亲自处理细节,Connect的static中间件为我们提供上述所有功能。代码只需寥寥3行即可:
1 2 3 |
var connect = require('connect'); var app = connect(); app.use(connect.static(__dirname + '/public')); |
在项目中需要临时搭建静态服务器,也无需安装apache之类的服务器,通过NPM安装Connect之后,三行代码即可解决需求。这里需要提及的是在使用该模块的一点性能相关的细节。 动静分离 前一章提及,app.use()方法在没有指定路由信息时,相当于app.use("/", middleware)。这意味着静态文件中间件将会在处理所有路径的请求。在动静态请求混杂的场景下,静态中间件会在动态请求时也调用fs.stat来检测文件系统是否存在静态文件。这造成了不必要的系统调用,使得性能降低。 解决影响性能的方法既是动静分离。利用路由检测,避免不必要的系统调用,可以有效降低对动态请求的性能影响。
1 |
app.use('/public', connect.static(__dirname + '/public')); |
在大型的应用中,动静分离通常无需到一个Node.js实例中进行,CDN的方式直接在域名上将请求分离。小型应用中,适当的进行动静分离即可避免不必要的性能损耗。 缓存策略 缓存策略包含客户端和服务端两个部分。客户端的缓存,主要是利用浏览器对HTTP协议响应头中cache-control和expires字段的支持。浏览器在得到明确的相应头后,会将文件缓存在本地,依据cache-control和expires的值进行相应的过期策略。这使得重复访问的过程中,浏览器可以从本地缓存中读取文件,而无需从网络读取文件,提升加载速度,也可以降低对服务器的压力。默认情况下静态中间件的最大缓存时设置为0,意味着它在浏览器关闭后就被清除。这显然不是我们所期望的结果。除非是在开发环境可以无视maxAge的设置外,生产环境请务必设置缓存,因为它能有效节省网络带宽。
1 |
app.use('/public', connect.static(__dirname + '/public', {maxAge: 86400000})); |
maxAge选项的单位为毫秒。YUI3的CDN服务器设置过期时间为10年,是一个值得参考的值。静态文件如果在客户端被缓存,在需要清除缓存的时候,又该如何清除呢?这里的实现方法较多,一种较为推荐的做法是为文件进行md5处理。
1 |
http://some.url/some.js?md5 |
当文件内容产生改变时,md5值也将发生改变,浏览器根据URL的不同会重新获取静态文件。md5的方式可以避免不必要的缓存清除,也能精确清除缓存。由于浏览器本身缓存容量的限制,尽管我们可能设置了10年的过期时间,但是也许两天之后就被新的静态文件挤出了本地缓存。这将持续引起静态服务器的响应,也即意味着,客户端缓存并不能完全解决降低服务器压力的问题。为了解决静态服务器重复读取磁盘造成的压力,这里需要引出第二个相关的中间件:staticCache。
1 2 |
app.use(connect.staticCache()); app.use(“/public”, connect.static(__dirname + '/public', {maxAge: 86400000})); |
这是一个提供上层缓存功能的中间件,能够将磁盘中的文件加载到内存中,以提高响应速度和提高性能。它的官方测试数据如下:
1 2 3 |
static(): 2700 rps node-static: 5300 rps static() + staticCache(): 7500 rps |
另一个专门用于静态文件托管的模块叫node-static,其性能是Connect静态文件中间件的效率的两倍。但是在缓存中间件的协助下,可以弥补性能损失。事实上,这个中间件在生产环境下并不推荐被使用,而且它将在Connect 3.0版本中被移除。但是它的实现中有值得玩味的地方,这有助于我们认识Node.js模型的优缺点。staticCache中间件有两个主要的选项:maxObjects和maxLength。代表的是能存储多少个文件和单个文件的最大尺寸,其默认值为128和256kb。为何会有这两个选项的设定,原因在于V8有内存限制的原因,作为缓存,如果没有良好的过期策略,缓存将会无限增加,直到内存溢出。设置存储数量和单个文件大小后,可以有效抑制缓存区的大小。事实上,该缓存还存在的缺陷是单机情况下,通常为了有效利用CPU,Node.js实例并不只有一个,多个实例进程之间将会存在冗余的缓存占用,这对于内存使用而言是浪费的。除此之外,V8的垃圾回收机制是暂停JavaScript线程执行,通过扫描的方式决定是否回收对象。如果缓存对象过大,键太多,则扫描的时间会增加,会引起JavaScript响应业务逻辑的速度变慢。但是这个模块并非没有存在的意义,上述提及的缺陷大多都是V8内存限制和Node.js单线程的原因。解决该问题的方式则变得明了。风险转移是Node.js中常用于解决资源不足问题的方式,尤其是内存方面的问题。将缓存点,从Node.js实例进程中转移到第三方成熟的缓存中去即可。这可以保证: 缓存内容不冗余。 集中式缓存,减少不一致性的发生。 缓存的算法更优秀以保持较高的命中率。 让Node.js保持轻量,以解决它更擅长的问题。 Connect推荐服务器端缓存采用varnish这样的成熟缓存代理。而笔者目前的项目则是通过Redis来完成后端缓存的任务。 参考内容 https://www.varnish-cache.org/releases http://www.senchalabs.org/connect/static.html http://www.senchalabs.org/connect/staticCache.html 转自:http://www.infoq.com/cn/articles/nodejs-8-connect-module-part-2
View Details深入浅出Node.js(七):Connect模块解析(之一)
Connect模块背景 Node.js的愿望是成为一个能构建高速,可伸缩的网络应用的平台,它本身具有基于事件,异步,非阻塞,回调等特性,这在前几篇专栏中有过描述。正是基于这样的一些特性,Node.js平台上的Web框架也具有不同于其他平台的一些特性,其中Connect是众多Web框架中的佼佼者。Connect在它的官方介绍中,它是Node的一个中间件框架。超过18个捆绑的中间件和一些精选第三方中间件。尽管Connect可能不是性能最好的Node.jsWeb框架,但它却几乎是最为流行的Web框架。为何Connect能在众多框架中胜出,其原因不外乎有如下几个: 模型简单 中间件易于组合和插拔 中间件易于定制和优化 丰富的中间件 Connect自身十分简单,其作用是基于Web服务器做中间件管理。至于如何如何处理网络请求,这些任务通过路由分派给管理的中间件们进行处理。它的处理模型仅仅只是一个中间队列,进行流式处理而已,流式处理可能性能不是最优,但是却是最易于被理解和接受。基于中间件可以自由组合和插拔的情况,优化它十分容易。Connect模块目前在NPM仓库的MDO(被依赖最多的模块)排行第八位。但这并没有真实反映出它的价值,因为排行第五位的Express框架实际上是依赖Connect创建而成的。关于Express的介绍,将会在后续的专栏中一一为你讲解。 中间件 让我们回顾一下Node.js最简单的Web服务器是如何编写的:
1 2 3 4 5 |
var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, '127.0.0.1'); |
我们从最朴素的Web服务器处理流程开始,可以看到HTTP模块基于事件处理网络访问无外乎两个主要的因素,请求和响应。同理的是Connect的中间件也是扮演这样一个角色,处理请求,然后响应客户端或是让下一个中间件继续处理。如下是一个中间件最朴素的原型:
1 2 3 |
function (req, res, next) { // 中间件 } |
在中间件的上下文中,有着三个变量。分别代表请求对象、响应对象、下一个中间件。如果当前中间件调用了res.end()结束了响应,执行下一个中间件就显得没有必要。 流式处理 为了演示中间件的流式处理,我们可以看看中间件的使用形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var app = connect(); // Middleware app.use(connect.staticCache()); app.use(connect.static(__dirname + '/public')); app.use(connect.cookieParser()); app.use(connect.session()); app.use(connect.query()); app.use(connect.bodyParser()); app.use(connect.csrf()); app.use(function (req, res, next) { // 中间件 }); app.listen(3001); |
Conncet提供use方法用于注册中间件到一个Connect对象的队列中,我们称该队列叫做中间件队列。 Conncet的部分核心代码如下,它通过use方法来维护一个中间件队列。然后在请求来临的时候,依次调用队列中的中间件,直到某个中间件不再调用下一个中间件为止。
1 2 3 4 5 6 7 8 9 10 |
app.stack = []; app.use = function(route, fn){ // … // add the middleware debug('use %s %s', route || '/', fn.name || 'anonymous'); this.stack.push({ route: route, handle: fn }); return this; }; |
值得注意的是,必须要有一个中间件调用res.end()方法来告知客户端请求已被处理完成,否则客户端将一直处于等待状态。流式处理也是Node.js中用于流程控制的经典模式,Connect模块是典型的应用了它。流式处理的好处在于,每一个中间层的职责都是单一的,开发者通过这个模式可以将复杂的业务逻辑进行分解。 路由 从前文可以看到其实app.use()方法接受两个参数,route和fn,既路由信息和中间件函数,一个完整的中间件,其实包含路由信息和中间件函数。路由信息的作用是过滤不匹配的URL。请求在遇见路由信息不匹配时,直接传递给下一个中间件处理。通常在调用app.use()注册中间件时,只需要传递一个中间件函数即可。实际上这个过程中,Connect会将/作为该中间件的默认路由,它表示所有的请求都会被该中间件处理。中间件的优势类似于Java中的过滤器,能够全局性地处理一些事务,使得业务逻辑保持简单。任何事物均有两面性,当你调用app.use()添加中间件的时候,需要考虑的是中间件队列是否太长,因为每一层中间件的调用都是会降低性能的。为了提高性能,在添加中间件的时候,如非全局需求的,尽量附带上精确的路由信息。以multipart中间件为例,它用于处理表单提交的文件信息,相对而言较为耗费资源。它存在潜在的问题,那就是有可能被人在客户端恶意提交文件,造成服务器资源的浪费。如果不采用路由信息加以限制,那么任何URL都可以被攻击。
1 |
app.use("/upload", connect.multipart({ uploadDir: path })); |
加上精确的路由信息后,可以将问题减小。 MVC目录 借助Connect可以自由定制中间件的优势,可以自行提升性能或是设计出适合自己需要的项目。Connect自身提供了路由功能,在此基础上,可以轻松搭建MVC模式的框架,以达到开发效率和执行效率的平衡。以下是笔者项目中采用的目录结构,清晰地划分目录结构可以帮助划分代码的职责,此处仅供参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
├── Makefile // 构建文件,通常用于启动单元测试运行等操作 ├── app.js // 应用文件 ├── automation // 自动化测试目录 ├── bin // 存放启动应用相关脚本的目录 ├── conf // 配置文件目录 ├── controllers // 控制层目录 ├── helpers // 帮助类库 ├── middlewares // 自定义中间件目录 ├── models // 数据层目录 ├── node_modules // 第三方模块目录 ├── package.json // 项目包描述文件 ├── public // 静态文件目录 │ ├── images // 图片目录 │ ├── libs // 第三方前端JavaScript库目录 │ ├── scripts // 前端JavaScript脚本目录 │ └── styles // 样式表目录 ├── test // 单元测试目录 └── views // 视图层目录 |
参考: Connect主页 http://www.senchalabs.org/connect/ NPM仓库 http://search.npmjs.org/ 转自:http://www.infoq.com/cn/articles/nodejs-connect-module
View Details深入浅出Node.js(六):Buffer那些事儿
作为前端的JSer,是一件非常幸福的事情,因为在字符串上从来没有出现过任何纠结的问题。我们来看看PHP对字符串长度的判断结果:
1 2 3 4 5 |
<? php echo strlen("0123456789"); echo strlen("零一二三四五六七八九"); echo mb_strlen("零一二三四五六七八九", "utf-8"); echo "\n"; |
以上三行判断分别返回10、30、10。对于中国人而言,strlen这个方法对于Unicode的判断结果是非常让人疑惑。而看看JavaScript中对字符串长度的判断,就知道这个length属性对调用者而言是多么友好。
1 2 3 |
console.log("0123456789".length); // 10 console.log("零一二三四五六七八九".length); /10 console.log("\u00bd".length); // 1 |
尽管在计算机内部,一个中文字和一个英文字占用的字节位数是不同的,但对于用户而言,它们拥有相同的长度。我认为这是JavaScript中 String处理得精彩的一个点。正是由于这个原因,所有的数据从后端传输到前端被调用时,都是这般友好的字符串。所以对于前端工程师而言,他们是没有字 符串Buffer的概念的。如果你是一名前端工程师,那么从此在与Node.js打交道的过程中,一定要小心Buffer啦,因为它比传统的String 要调皮一点。 你该小心Buffer啦 像许多计算机的技术一样,都是从国外传播过来的。那些以英文作为母语的传道者们应该没有考虑过英文以外的使用者,所以你有可能看到如下这样一段代码在向你描述如何在data事件中连接字符串。
1 2 3 4 5 6 7 8 9 |
var fs = require('fs'); var rs = fs.createReadStream('testdata.md'); var data = ''; rs.on("data", function (trunk){ data += trunk; }); rs.on("end", function () { console.log(data); }); |
如果这个文件读取流读取的是一个纯英文的文件,这段代码是能够正常输出的。但是如果我们再改变一下条件,将每次读取的buffer大小变成一个奇数,以模拟一个字符被分配在两个trunk中的场景。
1 |
var rs = fs.createReadStream('testdata.md', {bufferSize: 11}); |
我们将会得到以下这样的乱码输出:
1 |
事件循���和请求���象构成了Node.js���异步I/O模型的���个基本���素,这也是典���的消费���生产者场景。 |
造成这个问题的根源在于data += trunk语句里隐藏的错误,在默认的情况下,trunk是一个Buffer对象。这句话的实质是隐藏了toString的变换的:
1 |
data = data.toString() + trunk.toString(); |
由于汉字不是用一个字节来存储的,导致有被截破的汉字的存在,于是出现乱码。解决这个问题有一个简单的方案,是设置编码集:
1 |
var rs = fs.createReadStream('testdata.md', {encoding: 'utf-8', bufferSize: 11}); |
这将得到一个正常的字符串响应:
1 |
事件循环和请求对象构成了Node.js的异步I/O模型的两个基本元素,这也是典型的消费者生产者场景。 |
遗憾的是目前Node.js仅支持hex、utf8、ascii、binary、base64、ucs2几种编码的转换。对于那些因为历史遗留问题依旧还生存着的GBK,GB2312等编码,该方法是无能为力的。 有趣的string_decoder 在这个例子中,如果仔细观察,会发现一件有趣的事情发生在设置编码集之后。我们提到data += trunk等价于data = data.toString() + trunk.toString()。通过以下的代码可以测试到一个汉字占用三个字节,而我们按11个字节来截取trunk的话,依旧会存在一个汉字被分割在两个trunk中的情景。
1 2 |
console.log("事件循环和请求对象".length); console.log(new Buffer("事件循环和请求对象").length); |
按照猜想的toString()方式,应该返回的是事件循xxx和请求xxx象才对,其中“环”字应该变成乱码才对,但是在设置了encoding(默认的utf8)之后,结果却正常显示了,这个结果十分有趣。 在好奇心的驱使下可以探查到data事件调用了string_decoder来进行编码补足的行为。通过string_decoder对象输出第一个截取Buffer(事件循xx)时,只返回事件循这个字符串,保留xx。第二次通过string_decoder对象输出时检测到上次保留的xx,将上次剩余内容和本次的Buffer进行重新拼接输出。于是达到正常输出的目的。 string_decoder,目前在文件流读取和网络流读取中都有应用到,一定程度上避免了粗鲁拼接trunk导致的乱码错误。但是,遗憾在于string_decoder目前只支持utf8编码。它的思路其实还可以扩展到其他编码上,只是最终是否会支持目前尚不可得知。 连接Buffer对象的正确方法 那么万能的适应各种编码而且正确的拼接Buffer对象的方法是什么呢?我们从Node.js在github上的源码中找出这样一段正确读取文件,并连接buffer对象的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var buffers = []; var nread = 0; readStream.on('data', function (chunk) { buffers.push(chunk); nread += chunk.length; }); readStream.on('end', function () { var buffer = null; switch(buffers.length) { case 0: buffer = new Buffer(0); break; case 1: buffer = buffers[0]; break; default: buffer = new Buffer(nread); for (var i = 0, pos = 0, l = buffers.length; i < l; i++) { var chunk = buffers[i]; chunk.copy(buffer, pos); pos += chunk.length; } break; } }); |
在end事件中通过细腻的连接方式,最后拿到理想的Buffer对象。这时候无论是在支持的编码之间转换,还是在不支持的编码之间转换(利用iconv模块转换),都不会导致乱码。 简化连接Buffer对象的过程 上述一大段代码仅只完成了一件事情,就是连接多个Buffer对象,而这种场景需求将会在多个地方发生,所以,采用一种更优雅的方式来完成该过程是必要的。笔者基于以上的代码封装出一个bufferhelper模块,用于更简洁地处理Buffer对象。可以通过NPM进行安装:
1 |
npm install bufferhelper |
下面的例子演示了如何调用这个模块。与传统data += trunk之间只是bufferHelper.concat(chunk)的差别,既避免了错误的出现,又使得代码可以得到简化而有效地编写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var http = require('http'); var BufferHelper = require('bufferhelper'); http.createServer(function (request, response) { var bufferHelper = new BufferHelper(); request.on("data", function (chunk) { bufferHelper.concat(chunk); }); request.on('end', function () { var html = bufferHelper.toBuffer().toString(); response.writeHead(200); response.end(html); }); }).listen(8001); |
所以关于Buffer对象的操作的最佳实践是: 保持编码不变,以利于后续编码转换 使用封装方法达到简洁代码的目的 参考 https://github.com/joyent/node/blob/master/lib/fs.js#L107 https://github.com/JacksonTian/bufferhelper 转自:http://www.infoq.com/cn/articles/nodejs-about-buffer
View Details深入浅出Node.js(四):Node.js的事件机制
专栏的第四篇文章《Node.js的事件机制》。之前介绍了Node.js的模块机制,本文将深入Node.js的事件部分。 Node.js的事件机制 Node.js在其Github代码仓库(https://github.com/joyent/node)上有着一句短短的介绍:Evented I/O for V8 JavaScript。这句近似广告语的句子却道尽了Node.js自身的特色所在:基于V8引擎实现的事件驱动IO。在本文的这部分内容中,我来揭开这Evented这个关键词的一切奥秘吧。 Node.js能够在众多的后端JavaScript技术之中脱颖而出,正是因其基于事件的特点而受到欢迎。拿Rhino来做比较,可以看出Rhino引擎支持的后端JavaScript摆脱不掉其他语言同步执行的影响,导致JavaScript在后端编程与前端编程之间有着十分显著的差别,在编程模型上无法形成统一。在前端编程中,事件的应用十分广泛,DOM上的各种事件。在Ajax大规模应用之后,异步请求更得到广泛的认同,而Ajax亦是基于事件机制的。在Rhino中,文件读取等操作,均是同步操作进行的。在这类单线程的编程模型下,如果采用同步机制,无法与PHP之类的服务端脚本语言的成熟度媲美,性能也没有值得可圈可点的部分。直到Ryan Dahl在2009年推出Node.js后,后端JavaScript才走出其迷局。Node.js的推出,我觉得该变了两个状况: 龙生网络 统一了前后端JavaScript的编程模型。 利用事件机制充分利用用异步IO突破单线程编程模型的性能瓶颈,使得JavaScript在后端达到实用价值。 有了第二次浏览器大战中的佼佼者V8的适时助力,使得Node.js在短短的两年内达到可观的运行效率,并迅速被大家接受。这一点从Node.js项目在Github上的流行度和NPM上的库的数量可见一斑。 至于Node.js为何会选择Evented I/O for V8 JavaScript的结构和形式来实现,可以参见一下2011年初对作者Ryan Dahl的一次采访:http://bostinno.com/2011/01/31/node-js-interview-4-questions-with-creator-ryan-dahl/ 。 事件机制的实现 Node.js中大部分的模块,都继承自Event模块(http://nodejs.org/docs/latest/api/events.html )。Event模块(events.EventEmitter)是一个简单的事件监听器模式的实现。具有addListener/on,once,removeListener,removeAllListeners,emit等基本的事件监听模式的方法实现。它与前端DOM树上的事件并不相同,因为它不存在冒泡,逐层捕获等属于DOM的事件行为,也没有preventDefault()、stopPropagation()、 stopImmediatePropagation() 等处理事件传递的方法。 从另一个角度来看,事件侦听器模式也是一种事件钩子(hook)的机制,利用事件钩子导出内部数据或状态给外部调用者。Node.js中的很多对象,大多具有黑盒的特点,功能点较少,如果不通过事件钩子的形式,对象运行期间的中间值或内部状态,是我们无法获取到的。这种通过事件钩子的方式,可以使编程者不用关注组件是如何启动和执行的,只需关注在需要的事件点上即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var options = { host: 'www.google.com', port: 80, path: '/upload', method: 'POST' }; var req = http.request(options, function (res) { console.log('STATUS: ' + res.statusCode); console.log('HEADERS: ' + JSON.stringify(res.headers)); res.setEncoding('utf8'); res.on('data', function (chunk) { console.log('BODY: ' + chunk); }); }); req.on('error', function (e) { console.log('problem with request: ' + e.message); }); // write data to request body req.write('data\n'); req.write('data\n'); req.end(); |
在这段HTTP request的代码中,程序员只需要将视线放在error,data这些业务事件点即可,至于内部的流程如何,无需过于关注。 值得一提的是如果对一个事件添加了超过10个侦听器,将会得到一条警告,这一处设计与Node.js自身单线程运行有关,设计者认为侦听器太多,可能导致内存泄漏,所以存在这样一个警告。调用:
1 |
emitter.setMaxListeners(0); |
可以将这个限制去掉。 其次,为了提升Node.js的程序的健壮性,EventEmitter对象对error事件进行了特殊对待。如果运行期间的错误触发了error事件。EventEmitter会检查是否有对error事件添加过侦听器,如果添加了,这个错误将会交由该侦听器处理,否则,这个错误将会作为异常抛出。如果外部没有捕获这个异常,将会引起线程的退出。 事件机制的进阶应用 继承event.EventEmitter 实现一个继承了EventEmitter类是十分简单的,以下是Node.js中流对象继承EventEmitter的例子:
1 2 3 4 |
function Stream() { events.EventEmitter.call(this); } util.inherits(Stream, events.EventEmitter); |
Node.js在工具模块中封装了继承的方法,所以此处可以很便利地调用。程序员可以通过这样的方式轻松继承EventEmitter对象,利用事件机制,可以帮助你解决一些问题。 多事件之间协作 在略微大一点的应用中,数据与Web服务器之间的分离是必然的,如新浪微博、Facebook、Twitter等。这样的优势在于数据源统一,并且可以为相同数据源制定各种丰富的客户端程序。以Web应用为例,在渲染一张页面的时候,通常需要从多个数据源拉取数据,并最终渲染至客户端。Node.js在这种场景中可以很自然很方便的同时并行发起对多个数据源的请求。
1 2 3 4 5 6 7 8 9 |
api.getUser("username", function (profile) { // Got the profile }); api.getTimeline("username", function (timeline) { // Got the timeline }); api.getSkin("username", function (skin) { // Got the skin }); |
Node.js通过异步机制使请求之间无阻塞,达到并行请求的目的,有效的调用下层资源。但是,这个场景中的问题是对于多个事件响应结果的协调并非被Node.js原生优雅地支持。为了达到三个请求都得到结果后才进行下一个步骤,程序也许会被变成以下情况:
1 2 3 4 5 6 7 |
api.getUser("username", function (profile) { api.getTimeline("username", function (timeline) { api.getSkin("username", function (skin) { // TODO }); }); }); |
这将导致请求变为串行进行,无法最大化利用底层的API服务器。 为解决这类问题,我曾写作一个模块(EventProxy,https://github.com/JacksonTian/eventproxy)来实现多事件协作,以下为上面代码的改进版:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var proxy = new EventProxy(); proxy.all("profile", "timeline", "skin", function (profile, timeline, skin) { // TODO }); api.getUser("username", function (profile) { proxy.emit("profile", profile); }); api.getTimeline("username", function (timeline) { proxy.emit("timeline", timeline); }); api.getSkin("username", function (skin) { proxy.emit("skin", skin); }); |
EventProxy也是一个简单的事件侦听者模式的实现,由于底层实现跟Node.js的EventEmitter不同,无法合并进Node.js中。但是却提供了比EventEmitter更强大的功能,且API保持与EventEmitter一致,与Node.js的思路保持契合,并可以适用在前端中。 这里的all方法是指侦听完profile、timeline、skin三个方法后,执行回调函数,并将侦听接收到的数据传入。 最后还介绍一种解决多事件协作的方案:Jscex(https://github.com/JeffreyZhao/jscex )。Jscex通过运行时编译的思路(需要时也可在运行前编译),将同步思维的代码转换为最终异步的代码来执行,可以在编写代码的时候通过同步思维来写,可以享受到同步思维的便利写作,异步执行的高效性能。如果通过Jscex编写,将会是以下形式:
1 2 3 4 5 6 7 |
var data = $await(Task.whenAll({ profile: api.getUser("username"), timeline: api.getTimeline("username"), skin: api.getSkin("username") })); // 使用data.profile, data.timeline, data.skin // TODO |
此节感谢Jscex作者@老赵(http://blog.zhaojie.me/)的指正和帮助。 利用事件队列解决雪崩问题 所谓雪崩问题,是在缓存失效的情景下,大并发高访问量同时涌入数据库中查询,数据库无法同时承受如此大的查询请求,进而往前影响到网站整体响应缓慢。那么在Node.js中如何应付这种情景呢。
1 2 3 4 5 |
var select = function (callback) { db.select("SQL", function (results) { callback(results); }); }; |
以上是一句数据库查询的调用,如果站点刚好启动,这时候缓存中是不存在数据的,而如果访问量巨大,同一句SQL会被发送到数据库中反复查询,影响到服务的整体性能。一个改进是添加一个状态锁。
1 2 3 4 5 6 7 8 9 10 |
var status = "ready"; var select = function (callback) { if (status === "ready") { status = "pending"; db.select("SQL", function (results) { callback(results); status = "ready"; }); } }; |
但是这种情景,连续的多次调用select发,只有第一次调用是生效的,后续的select是没有数据服务的。所以这个时候引入事件队列吧:
1 2 3 4 5 6 7 8 9 10 11 12 |
var proxy = new EventProxy(); var status = "ready"; var select = function (callback) { proxy.once("selected", callback); if (status === "ready") { status = "pending"; db.select("SQL", function (results) { proxy.emit("selected", results); status = "ready"; }); } }; |
这里利用了EventProxy对象的once方法,将所有请求的回调都压入事件队列中,并利用其执行一次就会将监视器移除的特点,保证每一个回调只会被执行一次。对于相同的SQL语句,保证在同一个查询开始到结束的时间中永远只有一次,在这查询期间到来的调用,只需在队列中等待数据就绪即可,节省了重复的数据库调用开销。由于Node.js单线程执行的原因,此处无需担心状态问题。这种方式其实也可以应用到其他远程调用的场景中,即使外部没有缓存策略,也能有效节省重复开销。此处也可以用EventEmitter替代EventProxy,不过可能存在侦听器过多,引发警告,需要调用setMaxListeners(0)移除掉警告,或者设更大的警告阀值。 参考: http://nodejs.org/docs/latest/api/events.html https://github.com/JacksonTian/eventproxy/blob/master/README.md https://github.com/JeffreyZhao/jscex/blob/master/README-cn.md
View Details