-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 402 KB
/
content.json
1
{"meta":{"title":"道 - 自然 🍃","subtitle":"","description":"生活点滴和知识分享","author":"万松","url":"http://example.com","root":"/"},"pages":[{"title":"关于作者","date":"2022-09-20T01:31:49.870Z","updated":"2022-09-20T01:31:49.870Z","comments":true,"path":"about/index.html","permalink":"http://example.com/about/index.html","excerpt":"","text":"基本介绍 姓名 万松 毕业院校 南阳理工学院 专业 通信工程 工作时间 2011.03-至今 理念 事在人为 工作经历 公司 时间 职位 行业 北京科蓝软件有限公司 2011-03~2012-03 软件工程师 手机银行,支付 方正国际(北京)软件有限公司 2012-03~2014-10 软件工程师 地理信息 五八同城 2014-10~2015-03 软件工程师 二手 户外部落软件有限公司 2015-03~2015-09 软件工程师 户外 纷享销客 2015-09~至今 软件工程师 OA,移动办公,CRM,流程 工作技能 java\\js\\python\\分布式\\IM\\流程"}],"posts":[{"title":"流程-活动节点","slug":"流程-活动节点","date":"2022-11-14T14:35:59.000Z","updated":"2022-11-15T17:07:54.043Z","comments":true,"path":"2022/11/14/流程-活动节点/","link":"","permalink":"http://example.com/2022/11/14/%E6%B5%81%E7%A8%8B-%E6%B4%BB%E5%8A%A8%E8%8A%82%E7%82%B9/","excerpt":"","text":"活动包括子流程和任务 通用属性 isForCompensation: boolean = false 标识此活动是否用于补偿目的的标志。 如果为false,则此活动作为正常执行流的结果执行。如果为true,则此活动仅在检测到补偿事件并在补偿事件可见性作用域下启动时激活 loopCharacteristics: LoopCharac- teristics [0..1] 一个活动可以执行一次,也可以重复执行。如果是重复的,活动必段定义 loopCharacteristics(如果流程isExecutable属性被设置为true)。 resources: ResourceRole [0..*] 定义将执行或将负责活动的资源。资源,例如执行者,可以以特定的个人、团体、组织角色或职位或组织的形式指定。 default: SequenceFlow [0..1] 默认分支, 当其它条件不满足时 默认走该分支, 该分支不用配置条件。 ioSpecification: Input OutputSpecification [0..1] 定义了活动的输入和输出以及输入集和输出集。 properties: Property [0..*] 其它属性 boundaryEventRefs: BoundaryEvent [0..*] 绑定的边界事件 dataInputAssociations: DataIn- putAssociation [0..*] 可选引用。数据输入关联定义了如何填充活动的输入输出规范的数据输入。 dataOutputAssociations: DataOutputAssociation [0..*] 可选引用。对数据输出关联. startQuantity: integer = 1 缺省值为1。不小于1。这个属性定义了在活动开始之前不能到达的令牌的数量。 completionQuantity: integer = 1 缺省值为1。不小于1。这个属性定义了从活动中生成的令牌的数量。这个数目的令牌将在任何传出序列流(假设满足任何序列流条件)时发送。 活动与资源的关系 任务的标示 任务类图 任务类型说明服务任务 Service Task 属性 绑定一个操作,且有输入与输出implementation: string = ##webServiceoperationRef: Operation [0..1] 作用 调用外部服务 标示 类图 发送消息任务 Send Task 属性 绑定一个消息, 但不是必须的messageRef: Message [0..1]operationRef: Operation [0..1]implementation: string = ##webService 作用 发送消息 标示 类图 接收消息的任务 Receive Task 属性 绑定一个消息, 但不是必须的messageRef: Message [0..1]operationRef: Operation [0..1]implementation: string = ##webServiceinstantiate: boolean = false 作用 接收消息, 当接收到消息时自动完成任务;通常用于启用一个实例;如果想要触发一个实例,需要将属性 instantiate 设置成 true 标示 可作为开始节点 : 业务规则节点 Business Rule Task 作用 DMN 任务 标示 脚本任务 Script Task 属性 script:string[0..1] 脚本, 如果没有配置脚本, 当作一个空任务进行执行scriptFormat: string[0..1] 脚本格式,该值一定是一个 mime-type 类型, 如果脚本指定了, 该值需要必须配置 作用 执行指定的脚本 标示 人工任务 User Task 属性 implementation: string = ##unspecified 一个webservice提供实现renderings: Rendering [0..*] 这个属性充当一个hook,它允许bpmn采用者使用bpmn扩展机制来指定任务呈现方式。 作用 人员参与完成任务,该节点的实现可以交由业务方根据业务场景自行实现 标示 类图 操作任务 Manual Task 属性 只继承自Task,无其它属性 作用 不被引擎管理,可以理解为不受管的任务,即引擎不管理他的开始和结束;例如: 需要某一个人去给用户安装电话; 标示 类图 子流程 Call SubProcess Activity 属性 triggeredByEvent: boolean = false 是否有事件触发,即事件子流程method: Transaction- Method 事务子流程,该方法是用来提交或取消事务用的,如果是事务子流程,需要两个圈 作用 不被引擎管理,可以理解为不受管的任务,即引擎不管理他的开始和结束;例如: 需要某一个人去给用户安装电话; 标示 类图 子流程案例 可被调用的 活动 Call Activity 属性 calledElement: CallableElement [0..1] : CallableElements,GlobalTask 作用 用来引用全局的Task或流程 标示 类图 可被引用的元素 CallableElements 全局任务 Global Task","categories":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/categories/BPMN/"},{"name":"元素","slug":"BPMN/元素","permalink":"http://example.com/categories/BPMN/%E5%85%83%E7%B4%A0/"}],"tags":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/tags/BPMN/"},{"name":"Flow","slug":"Flow","permalink":"http://example.com/tags/Flow/"},{"name":"流程","slug":"流程","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B/"},{"name":"流程引擎","slug":"流程引擎","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B%E5%BC%95%E6%93%8E/"},{"name":"元素","slug":"元素","permalink":"http://example.com/tags/%E5%85%83%E7%B4%A0/"}]},{"title":"流程-事件","slug":"流程-事件","date":"2022-11-13T10:38:16.000Z","updated":"2022-11-13T14:13:58.074Z","comments":true,"path":"2022/11/13/流程-事件/","link":"","permalink":"http://example.com/2022/11/13/%E6%B5%81%E7%A8%8B-%E4%BA%8B%E4%BB%B6/","excerpt":"","text":"在流程产品中,比较重要的 元素 “事件” ,作为流程产品或技术的大牛们, 看了解几个; 如果需要再详细的内容,请参考: https://www.omg.org/spec/BPMN/2.0.2/PDF 优秀文章链接: dafengyiba 事件相关文章","categories":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/categories/BPMN/"},{"name":"元素","slug":"BPMN/元素","permalink":"http://example.com/categories/BPMN/%E5%85%83%E7%B4%A0/"}],"tags":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/tags/BPMN/"},{"name":"Flow","slug":"Flow","permalink":"http://example.com/tags/Flow/"},{"name":"流程","slug":"流程","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B/"},{"name":"流程引擎","slug":"流程引擎","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B%E5%BC%95%E6%93%8E/"},{"name":"元素","slug":"元素","permalink":"http://example.com/tags/%E5%85%83%E7%B4%A0/"}]},{"title":"流程引擎发展","slug":"流程引擎发展","date":"2022-11-12T04:07:30.000Z","updated":"2022-11-13T10:37:46.951Z","comments":true,"path":"2022/11/12/流程引擎发展/","link":"","permalink":"http://example.com/2022/11/12/%E6%B5%81%E7%A8%8B%E5%BC%95%E6%93%8E%E5%8F%91%E5%B1%95/","excerpt":"","text":"第一代 第二代 第三代 第三代 第五代 第六代 2003年, jBPM 1.0发布。 运行环境:J2EE 过程定义语言:jPDL(当时工作流厂商都有各自的过程定义语言和建模工具) 当时的主流的技术: applets, Swing桌面和EJB 2004年,jBPM 2.0发布 同时jBPM加入JBoss基金会. 运行环境:任何JAVA环境(POJO实现过程运行时),不需要应用服务器 2005年, jBPM 3.0发布,支持BPEL - 过程定义语言:过程虚拟机 - 架构:与二代相比,架构发生了巨大变化。可以操作的业务功能大范围扩展,不仅通过JAVA实现状态机,而且支持建模; - HIBERNETE作为持久机制并同时提供会话对象的概念, - 工作流引擎所有的相关性交互都纳入contextual block范畴 这为以后的工作流命令设计模式和命令拦截设计模式的广泛应用打下良好的基础 2009年, jBPM 4.0 alpha版发布. - PVM 成功工作流引擎的核心。 - 过程定义语言:BPMN, jPDL 和 BPEL - 因为团队人员离开并启动Activiti,正式版没能发布。 - 主要改进: - 无状态的服务API - 运行时和历史数据的分离:保证运行时持久的性能 2010年, Activiti 1发布 改变: - 版权从LGPL转到APACHE. - 过程定义语言:BPMN(唯一) - 从性能和扩展性加强PVM - 多租户支持 - 轻量级架构 2017年,flowable 6.0发布。 改变: - 过程模型:放弃PVM,使用原生BPMN,实现真正的动态过程执行和复杂的过程迁移 - 数据远完全抽象:支持NoSQL - CMMN支持 - 函数式工作流 Tom Baeyens Effektif 诞生","categories":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/categories/BPMN/"},{"name":"元素","slug":"BPMN/元素","permalink":"http://example.com/categories/BPMN/%E5%85%83%E7%B4%A0/"}],"tags":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/tags/BPMN/"},{"name":"Flow","slug":"Flow","permalink":"http://example.com/tags/Flow/"},{"name":"流程","slug":"流程","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B/"},{"name":"流程引擎","slug":"流程引擎","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B%E5%BC%95%E6%93%8E/"},{"name":"元素","slug":"元素","permalink":"http://example.com/tags/%E5%85%83%E7%B4%A0/"}]},{"title":"流程-网关Gateway","slug":"流程-网关Gateway","date":"2022-11-11T13:48:34.000Z","updated":"2022-11-13T14:12:16.503Z","comments":true,"path":"2022/11/11/流程-网关Gateway/","link":"","permalink":"http://example.com/2022/11/11/%E6%B5%81%E7%A8%8B-%E7%BD%91%E5%85%B3Gateway/","excerpt":"","text":"网关可以是流程的开端,也可以没有连出的线;网关可以有多个连入的线或多个连出的线(即,它可以合并或分流)。 排它(独占)网关 Exclusive Gateway 属性 条件(所有分支) 是否为默认分支 分支的顺序 规则 选择第一个满足条件的分支 如果没有匹配的分支,选择默认分支 表示 排它网关类图 包容网关 Inclusive Gateway 属性 条件(所有分支) 是否为默认分支 分支的顺序 规则 选择一条和多条满足条件的分支 如果没有匹配的分支,选择默认分支 可以对多条进入的线进行汇集,汇集后再判断连出分支 表示 包容网关类图 并行网关 Parallel Gateway 属性 多条连接的线 多条连出的线 规则 汇集, 并行节点 会 等待所有进入的线都到达时才向后流转 分发 表示 并行网关类图 复杂网关 Complex Gateway 属性 指定向该节点要完成的连入线的占比或数量 多条连出的分支 分支需要配置条件表达式(默认分支除外) 规则 汇集,可以激活的条件(哪些进入的线被同步等待)满足后,进行第二步的分发(可以选择 其它未到达的分支 策略, 是直接取消,还是达到时再次激活当前网关) 分发 表示 复杂网关类图 事件网关 Event-Based Gateway 属性 实例化 true or false (如果 true, 可以没有连入的线 作为流程的开端,同时事件分支类型可以选择并行) 事件网关类型(并行,排它) 规则 多条分支连出 >=2 分支需要配置事件 (只能是事件,不能是表达式) 支持的事件 Message, Signal, Timer, Conditional, and Multiple (which can only include the previous triggers) 表示 实例化=true, 可以作为流程的开始 复杂网关类图","categories":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/categories/BPMN/"},{"name":"元素","slug":"BPMN/元素","permalink":"http://example.com/categories/BPMN/%E5%85%83%E7%B4%A0/"}],"tags":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/tags/BPMN/"},{"name":"Flow","slug":"Flow","permalink":"http://example.com/tags/Flow/"},{"name":"流程","slug":"流程","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B/"},{"name":"流程引擎","slug":"流程引擎","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B%E5%BC%95%E6%93%8E/"},{"name":"元素","slug":"元素","permalink":"http://example.com/tags/%E5%85%83%E7%B4%A0/"}]},{"title":"BPMN2.0","slug":"BPMN2.0","date":"2022-11-10T14:42:50.000Z","updated":"2022-11-16T15:01:12.535Z","comments":true,"path":"2022/11/10/BPMN2.0/","link":"","permalink":"http://example.com/2022/11/10/BPMN2.0/","excerpt":"","text":"Version 2.0OMG Document Number: formal/2011-01-03Standard document URL: http://www.omg.org/spec/BPMN/2.0 一. 流程引擎发展 二. 流程元素 分类 事件 event 任务 task 子流程 sub-processes CallActivity 网关 Gateway 泳道 Lane 数据 Data Object 消息,组,备注 Message,Group,Text Annotation 线 Sequence Flow 消息 Message Flow 三. BPMN 发展 四. 开源引擎介绍 五. 主流程的引擎对比 六. 流程PaaS中的实现 七. 流程在PaaS中的最佳实践","categories":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/categories/BPMN/"},{"name":"元素","slug":"BPMN/元素","permalink":"http://example.com/categories/BPMN/%E5%85%83%E7%B4%A0/"}],"tags":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/tags/BPMN/"},{"name":"Flow","slug":"Flow","permalink":"http://example.com/tags/Flow/"},{"name":"流程","slug":"流程","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B/"},{"name":"流程引擎","slug":"流程引擎","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B%E5%BC%95%E6%93%8E/"},{"name":"元素","slug":"元素","permalink":"http://example.com/tags/%E5%85%83%E7%B4%A0/"}]},{"title":"流程好文章","slug":"流程好文章","date":"2022-11-02T16:00:00.000Z","updated":"2022-11-13T10:16:36.489Z","comments":true,"path":"2022/11/03/流程好文章/","link":"","permalink":"http://example.com/2022/11/03/%E6%B5%81%E7%A8%8B%E5%A5%BD%E6%96%87%E7%AB%A0/","excerpt":"","text":"一. 概念相关BPMN和DMN基本概念和使用案例 二. 产品及api学习 炎黄盈动@AWS PaaS流程文档","categories":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/categories/BPMN/"},{"name":"Flow","slug":"BPMN/Flow","permalink":"http://example.com/categories/BPMN/Flow/"}],"tags":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/tags/BPMN/"},{"name":"Flow","slug":"Flow","permalink":"http://example.com/tags/Flow/"},{"name":"流程","slug":"流程","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B/"},{"name":"流程引擎","slug":"流程引擎","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B%E5%BC%95%E6%93%8E/"}]},{"title":"道德经","slug":"道德经","date":"2022-09-08T16:11:18.000Z","updated":"2022-11-10T14:19:43.113Z","comments":true,"path":"2022/09/09/道德经/","link":"","permalink":"http://example.com/2022/09/09/%E9%81%93%E5%BE%B7%E7%BB%8F/","excerpt":"","text":"一. 老子老子,(前600年—前470年之后)姓李名耳,字伯阳,传说老子出生时就长有白色的眉毛及胡子, 所以被后人称为老子。中国春秋时代思想家,楚国苦县厉乡曲仁里人,是我国古代伟大的哲学家和思想家、道家学派创始 人。其被唐皇武后封为太上老君,世界文化名人,世界百位历史名人之一,存世有《道德经》(又称《老子》)。其作品的精华是朴素的辨证法,主张无为而治,其学说对中国哲学发展具有深刻影响。在道教中,老子是三清尊神之一太上老君的第十八个化身,被尊为道祖。 二. 道德经 道可道,非常道。名可名,非常名。无名天地之始。有名万物之母。故常无欲以观其妙。常有欲以观其徼。此两者同出而异名,同谓之玄。玄之又玄,众妙之门。 天下皆知美之为美,斯恶矣;皆知善之为善,斯不善已。故有无相生,难易相成,长短相形,高下相倾,音声相和,前後相随。是以圣人处无为之事,行不言之教。万物作焉而不辞。生而不有,为而不恃,功成而弗居。夫唯弗居,是以不去。 不尚贤, 使民不争。不贵难得之货,使民不为盗。不见可欲,使民心不乱。是以圣人之治,虚其心,实其腹,弱其志,强其骨;常使民无知、无欲,使夫智者不敢为也。为无为,则无不治。 道冲而用之,或不盈。渊兮似万物之宗。解其纷,和其光,同其尘,湛兮似或存。吾不知谁之子,象帝之先。 天地不仁,以万物为刍狗。圣人不仁,以百姓为刍狗。天地之间,其犹橐[tuó]迭乎?虚而不屈,动而愈出。多言数穷,不如守中。 谷神不死是谓玄牝。玄牝之门是谓天地根。绵绵若存,用之不勤。 天长地久。天地所以能长且久者,以其不自生,故能长生。是以圣人後其身而身先,外其身而身存。非以其无私邪!故能成其私。 上善若水。水善利万物而不争,处众人之所恶,故几於道。居善地,心善渊,与善仁,言善信,正善治,事善能,动善时。夫唯不争,故无尤。 持而盈之不如其己;揣而锐之不可长保;金玉满堂莫之能守;富贵而骄,自遗其咎。功遂身退,天之道。 载营魄抱一,能无离乎?专气致柔,能如婴儿乎?涤除玄览,能无疵乎?爱国治民,能无为乎?天门开阖,能为雌乎?明白四达,能无知乎。 三十幅共一毂,当其无,有车之用。埏埴以为器,当其无,有器之用。凿户牖以为室,当其无,有室之用。故有之以为利,无之以为用。 五色令人目盲,五音令人耳聋,五味令人口爽,驰骋畋猎令人心发狂,难得之货令人行妨。是以圣人,为腹不为目,故去彼取此。 宠辱若惊,贵大患若身。何谓宠辱若惊?宠为下。得之若惊失之若惊是谓宠辱若惊。何谓贵大患若身?吾所以有大患者,为吾有身,及吾无身,吾有何患。故贵以身为天下,若可寄天下。爱以身为天下,若可托天下。 视之不见名曰夷。听之不闻名曰希。抟之不得名曰微。此三者不可致诘,故混而为一。其上不皦(jiǎo),其下不昧,绳绳不可名,复归於无物。是谓无状之状,无物之象,是谓惚恍。迎之不见其首,随之不见其後。执古之道以御今之有。能知古始,是谓道纪。 古之善为士者,微妙玄通,深不可识。夫唯不可识,故强为之容。豫兮若冬涉川;犹兮若畏四邻;俨兮其若容;涣兮若冰之将释;敦兮其若朴;旷兮其若谷;混兮其若浊;澹兮其若海;飉(liáo,风的声音)兮若无止。孰能浊以静之徐清。孰能安以动之徐生。保此道者不欲盈。夫唯不盈故能蔽而新成。 致虚极守静笃。万物并作,吾以观复。夫物芸芸各复归其根。归根曰静,是谓复命;复命曰常,知常曰明。不知常,妄作凶。知常容,容乃公,公乃全,全乃天,天乃道,道乃久,没身不殆。 太上,下知有之。其次,亲而誉之。其次,畏之。其次,侮之。信不足焉,有不信焉。悠兮其贵言,功成事遂,百姓皆谓∶我自然。 大道废有仁义;慧智出有大伪;六亲不和有孝慈;国家昏乱有忠臣。 绝圣弃智,民利百倍;绝仁弃义,民复孝慈;绝巧弃利,盗贼无有;此三者,以为文不足。故令有所属,见素抱朴少私寡欲。 绝学无忧,唯之与阿,相去几何?善之与恶,相去若何?人之所畏,不可不畏。荒兮其未央哉!众人熙熙如享太牢、如春登台。我独泊兮其未兆,如婴儿之未孩;儡儡(lěi,羸弱)兮若无所归。众人皆有馀,而我独若遗。我愚人之心也哉!沌沌兮。俗人昭昭,我独昏昏;俗人察察,我独闷闷。众人皆有以,而我独顽且鄙。我独异於人,而贵食母。 孔德之容惟道是从。道之为物惟恍惟惚。惚兮恍兮其中有象。恍兮惚兮其中有物。窈兮冥兮其中有精。其精甚真。其中有信。自古及今,其名不去以阅众甫。吾何以知众甫之状哉!以此。 曲则全,枉则直,洼则盈,敝则新少则得,多则惑。是以圣人抱一为天下式。不自见故明;不自是故彰;不自伐故有功;不自矜故长;夫唯不争,故天下莫能与之争。古之所谓∶曲则全者」岂虚言哉!诚全而归之。 希言自然。故飘风不终朝,骤雨不终日。孰为此者?天地。天地尚不能久,而况於人乎?故从事於道者,同於道。德者同於德。失者同於失。同於道者道亦乐得之;同於德者德亦乐得之;同於失者失於乐得之信不足焉有不信焉。 企者不立;跨者不行。自见者不明;自是者不彰。自伐者无功;自矜者不长。其在道也曰∶馀食赘形。物或恶之,故有道者不处。 有物混成先天地生。寂兮寥兮独立不改,周行而不殆,可以为天下母。吾不知其名,强字之曰道。强为之名曰大。大曰逝,逝曰远,远曰反。故道大、天大、地大、人亦大。域中有大,而人居其一焉。人法地,地法天,天法道,道法自然。 重为轻根,静为躁君。是以君子终日行不离轻重。虽有荣观燕处超然。奈何万乘之主而以身轻天下。轻则失根,躁则失君。 善行无辙迹。善言无瑕谪。善数不用筹策。善闭无关楗而不可开。善结无绳约而不可解。是以圣人常善救人,故无弃人。常善救物,故无弃物。是谓袭明。故善人者不善人之师。不善人者善人之资。不贵其师、不爱其资,虽智大迷,是谓要妙。 知其雄,守其雌,为天下溪。为天下溪,常德不离,复归於婴儿。知其白,守其黑,为天下式。为天下式,常德不忒,复归於无极。知其荣,守其辱,为天下谷。为天下谷,常德乃足,复归於朴。朴散则为器,圣人用之则为官长。故大制不割。 将欲取天下而为之,吾见其不得已。天下神器,不可为也,为者败之,执者失之。夫物或行或随、或觑或吹、或强或羸、或挫或隳。是以圣人去甚、去奢、去泰。 以道佐人主者,不以兵强天下。其事好还。师之所处荆棘生焉。军之後必有凶年。善有果而已,不敢以取强。果而勿矜。果而勿伐。果而勿骄。果而不得已。果而勿强。物壮则老,是谓不道,不道早已。 夫佳兵者不祥之器,物或恶之,故有道者不处。君子居则贵左,用兵则贵右。兵者不祥之器,非君子之器,不得已而用之,恬淡为上。胜而不美,而美之者,是乐杀人。夫乐杀人者,则不可得志於天下矣。吉事尚左,凶事尚右。偏将军居左,上将军居右。言以丧礼处之。杀人之众,以悲哀泣之,战胜以丧礼处之。 道常无名。朴虽小天下莫能臣也。侯王若能守之,万物将自宾。天地相合以降甘露,民莫之令而自均。始制有名,名亦既有,夫亦将知止,知止可以不殆。譬道之在天下,犹川谷之於江海。 知人者智,自知者明。胜人者有力,自胜者强。知足者富。强行者有志。不失其所者久。死而不亡者,寿。 大道泛兮,其可左右。万物恃之以生而不辞,功成而不名有。衣养万物而不为主,常无欲可名於小。万物归焉,而不为主,可名为大。以其终不自为大,故能成其大。 执大象天下往。往而不害安平太。乐与饵,过客止。道之出口淡乎其无味。视之不足见。听之不足闻。用之不足既。 将欲歙之,必固张之。将欲弱之,必固强之。将欲废之,必固兴之。将欲取之,必固与之。是谓微明。柔弱胜刚强。鱼不可脱於渊,国之利器不可以示人。 道常无为,而无不为。侯王若能守之,万物将自化。化而欲作,吾将镇之以无名之朴。无名之朴,夫亦将无欲。不欲以静,天下将自定。 上德不德是以有德。下德不失德是以无德。上德无为而无以为。下德无为而有以为。上仁为之而无以为。上义为之而有以为。上礼为之而莫之以应,则攘臂而扔之。故失道而後德。失德而後仁。失仁而後义。失义而後礼。夫礼者忠信之薄而乱之首。前识者,道之华而愚之始。是以大丈夫,处其厚不居其薄。处其实,不居其华。故去彼取此。 昔之得一者。天得一以清。地得一以宁。神得一以灵。谷得一以盈。万物得一以生。侯王得一以为天下贞。其致之。天无以清将恐裂。地无以宁将恐废。神无以灵将恐歇。谷无以盈将恐竭。万物无以生将恐灭。侯王无以贞将恐蹶。故贵以贱为本,高以下为基。是以侯王自称孤、寡、不谷。此非以贱为本邪?非乎。至誉无誉。不欲琭琭如玉,珞珞如石。 反者道之动。弱者道之用。天下万物生於有,有生於无。 上士闻道勤而行之。中士闻道若存若亡。下士闻道大笑之。不笑不足以为道。故建言有之。明道若昧。进道若退。夷道若纇。上德若谷。大白若辱。广德若不足。建德若偷。质真若渝。大方无隅。大器晚成。大音希声。大象无形。道隐无名。夫唯道善贷且成。 道生一。一生二。二生三。三生万物。万物负阴而抱阳,冲气以为和。人之所恶,唯孤、寡不谷,而王公以为称,故物或损之而益,或益之而损。人之所教,我亦教之,强梁者,不得其死。吾将以为教父。 天下之至柔,驰骋天下之至坚。无有入无间,吾是以知无为之有益。不言之教,无为之益天下希及之。 名与身孰亲。身与货孰多。得与亡孰病。是故甚爱必大费。多藏必厚亡。知足不辱。知止不殆。可以长久。 大成若缺,其用不弊。大盈若冲,其用不穷。大直若屈。大巧若拙。大辩若讷。静胜躁,寒胜热。清静为天下正。 天下有道,却走马以粪。天下无道,戎马生於郊。祸莫大於不知足。咎莫大於欲得。故知足之足常足矣。 不出户知天下。不窥牖见天道。其出弥远,其知弥少。是以圣人不行而知。不见而明。不为而成。 为学日益。为道日损。损之又损,以至於无为。无为而不为。取天下常以无事,及其有事,不足以取天下。 圣人无常心。以百姓心为心。善者吾善之。不善者吾亦善之,德善。信者吾信之。不信者吾亦信之,德信。圣人在天下,歙歙(xīxī,无所偏执的样子)焉,为天下浑其心。百姓皆注其耳目,圣人皆孩之。 出生入死。生之徒,十有三。死之徒,十有三。人之生,动之於死地,亦十有三。夫何故?以其生生之厚。盖闻善摄生者,陆行不遇凶虎,入军不被甲兵。凶无所投其角。虎无所用其爪。兵无所容其刃。夫何故?以其无死地。 道生之,德畜之,物形之,势成之。是以万物莫不尊道,而贵德。道之尊,德之贵,夫莫之命而常自然。故道生之,德畜之。长之育之。亭之毒之。养之覆之。生而不有,为而不恃,长而不宰。是谓玄德。 天下有始,以为天下母。既得其母,以知其子。既知其子,复守其母,没身不殆。塞其兑,闭其门,终身不勤。开其兑,济其事,终身不救。见其小曰明,守柔曰强。用其光,复归其明,无遗身殃。是为习常。 使我介然有知,行於大道,唯施是畏。大道甚夷,而人好径。朝甚除,田甚芜,仓甚虚。服文彩,带利剑,厌饮食,财货有馀。是谓盗夸。非道也哉。 善建者不拔。善抱者不脱。子孙以祭祀不辍。修之於身其德乃真。修之於家其德乃馀。修之於乡其德乃长。修之於邦其德乃丰。修之於天下其德乃普。故以身观身,以家观家,以乡观乡,以邦观邦,以天下观天下。吾何以知天下然哉?以此。 含德之厚比於赤子。毒虫不螫,猛兽不据,攫鸟不抟。骨弱筋柔而握固。未知牝牡之合而全作,精之至也。终日号而不嗄,和之至也。知和曰常。知常曰明。益生曰祥。心使气曰强。物壮则老。谓之不道,不道早已。 知者不言。言者不知。挫其锐,解其纷,和其光,同其尘,是谓玄同。故不可得而亲。不可得而疏。不可得而利。不可得而害。不可得而贵。不可得而贱。故为天下贵。 以正治国,以奇用兵,以无事取天下。吾何以知其然哉?以此。天下多忌讳而民弥贫。民多利器国家滋昏。人多伎巧奇物泫起。法令滋彰盗贼多有。故圣人云我无为而民自化。我好静而民自正。我无事而民自富。我无欲而民自朴。 其政闷闷,其民淳淳。其政察察,其民缺缺。祸尚福之所倚。福尚祸之所伏。孰知其极,其无正。正复为奇,善复为妖。人之迷其日固久。是以圣人方而不割。廉而不刿。直而不肆。光而不耀。 治人事天莫若啬。夫唯啬是谓早服。早服谓之重积德。重积德则无不克。无不克则莫知其极。莫知其极可以有国。有国之母可以长久。是谓深根固柢,长生久视之道。 治大国若烹小鲜。以道莅天下,其迨ㄞ哄非其鬼不神,其神不伤人。非其神不伤人,圣人亦不伤人。夫两不相伤,故德交归焉。 大国者下流,天下之交。天下之牝。牝常以静胜牡。以静为下。故大国以下小国,则取小国。小国以下大国,则取大国。故或下以取,或下而取。大国不过欲兼畜人。小国不过欲入事人。夫两者各得所欲,大者宜为下。 道者万物之奥。善人之宝,不善人之所保。美言可以市尊。美行可以加人。人之不善,何弃之有。故立天子、置三公,虽有拱璧以先驷马,不如坐进此道。古之所以贵此道者何。不曰∶求以得,有罪以免邪?故为天下贵。 为无为,事无事,味无味。大小多少,报怨以德。图难於其易,为大於其细。天下难事必作於易。天下大事必作於细。是以圣人终不为大,故能成其大。夫轻诺必寡信。多易必多难。是以圣人犹难之,故终无难矣。 其安易持,其未兆易谋。其脆易泮,其微易散。为之於未有,治之於未乱。合抱之木生於毫末。九层之台起於累土。千里之行始於足下。为者败之,执者失之。是以圣人无为故无败,无执故无失。民之从事常於几成而败之。慎终如始则无败事。是以圣人欲不欲,不贵难得之货。学不学,复众人之所过,以辅万物之自然而不敢为。 古之善为道者,非以明民,将以愚之。民之难治,以其智多。故以智治国,国之贼。不以智治国,国之福。知此两者,亦稽式。常知稽式,是谓玄德。玄德深矣、远矣!与物反矣。然後乃至大顺。 江海之所以能为百谷王者,以其善下之,故能为百谷王。是以圣人欲上民,必以言下之。欲先民,必以身後之。是以圣人处上而民不重,处前而民不害。是以天下乐推而不厌。以其不争,故天下莫能与之争。 天下皆谓我道大似不肖。夫唯大故似不肖。若肖,久矣!其细也夫。我有三宝持而保之∶一曰慈, 二曰俭,三曰不敢为天下先。慈故能勇,俭故能广,不敢为天下先故能成器长。今舍慈且勇,舍俭且广,舍後且先,死矣!夫慈以战则胜,以守则固。天将救之以慈卫之。 善为士者不武。善战者不怒。善胜敌者不与。善用人者为之下。是谓不争之德。是谓用人之力。是谓配天之极。 用兵有言,吾不敢为主而为客。不敢进寸而退尺。是谓行无行。攘无臂。扔无敌。执无兵。祸莫大於轻敌。轻敌几丧吾宝。故抗兵相加哀者胜矣。 吾言甚易知、甚易行。天下莫能知、莫能行。言有宗、事有君。夫唯无知,是以我不知。知我者希,则我者贵。是以圣被褐怀玉。 知不知上,不知知病。夫唯病病,是以不病。圣人不病,以其病病。夫唯病病,是以不病。 民不畏威,则大威至。无狎其所居,无厌其所生。夫唯不厌,是以不厌。是以圣人自知不自见。自爱不自贵。故去彼取此。 勇於敢则杀。勇於不敢则活。此两者或利或害。天之所恶孰知其故。天之道不争而善胜。不言而善应。不召而自来。繟(chǎn,舒缓)然而善谋。天网恢恢疏而不失。 民不畏死,奈何以死惧之。若使民常畏死,而为奇者,吾得执而杀之,孰敢。常有司杀者杀。夫代司杀者杀,是谓代大匠斫。夫代大匠斫者,希有不伤其手矣。 民之饥以其上食税之多,是以饥。民之难治以其上之有为,是以难治。民之轻死以其求生之厚,是以轻死。夫唯无以生为者,是贤於贵生。 人之生也柔弱,其死也坚强。草木之生也柔脆,其死也枯槁。故坚强者死之徒,柔弱者生之徒。是以兵强则灭,木强则折。强大处下,柔弱处上。 天之道其犹张弓与。高者抑之,下者举之。有馀者损之,不足者补之。天之道,损有馀而补不足。人之道,则不然,损不足以奉有馀。孰能有馀以奉天下,唯有道者。是以圣人为而不恃,功成而不处。其不欲见贤邪! 天下莫柔弱於水。而攻坚强者,莫之能胜。以其无以易之。弱之胜强。柔之胜刚。天下莫不知莫能行。是以圣人云,受国之垢是谓社稷主。受国不祥是为天下王。正言若反。 和大怨必有馀怨,安可以为善。是以圣人执左契,而不责於人。有德司契,无德司彻。天道无亲常与善人。 小国寡民。使有什伯之器而不用。使民重死而不远徙。虽有舟舆无所乘之。虽有甲兵无所陈之。使民复结绳而用之。甘其食、美其服、安其居、乐其俗。邻国相望,鸡犬之声相闻。民至老死不相往来。 信言不美。美言不信。善者不辩。辩者不善。知者不博。博者不知。圣人不积。既以为人己愈有。既以与人己愈多。天之道利而不害。圣人之道为而不争。 老子-道德经","categories":[{"name":"读书学习","slug":"读书学习","permalink":"http://example.com/categories/%E8%AF%BB%E4%B9%A6%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"人生","slug":"人生","permalink":"http://example.com/tags/%E4%BA%BA%E7%94%9F/"}]},{"title":"名人-徐悲鸿-纪念馆","slug":"名人-徐悲鸿-纪念馆","date":"2022-09-04T12:12:07.000Z","updated":"2022-09-06T05:20:54.312Z","comments":true,"path":"2022/09/04/名人-徐悲鸿-纪念馆/","link":"","permalink":"http://example.com/2022/09/04/%E5%90%8D%E4%BA%BA-%E5%BE%90%E6%82%B2%E9%B8%BF-%E7%BA%AA%E5%BF%B5%E9%A6%86/","excerpt":"","text":"今天上午与亚林兄长来参观徐先生的展馆;我辈要向先生看齐,静下心做自己想做的事情,做到极致;这里整理主要生平以做备忘和自我激励 一. 详细介绍请参考美术百科-徐悲鸿 二. 历年珍贵影集 三. 代表作 《奔马图》 作于1941年秋季第二次长沙会战期间。在此幅画中,徐悲鸿运用饱酣奔放的墨色勾勒头、颈、胸、腿等大转折部位,并以干笔扫出鬃尾,使浓淡干湿的变化浑然天成。马腿的直线细劲有力,犹如钢刀,力透纸背,而腹部、臀部及鬃尾的弧线很有弹性,富于动感。整体上看,画面前大后小,透视感较强,前伸的双腿和马头有很强的冲击力,似乎要冲破画面。 《群马》 徐悲鸿的群马,是徐悲鸿马中的重要代表作之一。群马取材于1940年克什米尔当地的骏马,徐悲鸿当时见到此马格外兴奋,《群马》灵感油然而生。与其他作品不同的是,主要描绘了两匹背向观众的马,后面又配一匹侧向的马,为了使画面有变化,又在右边画了一匹低首觅食的马。 《珍妮小姐画像》 为画家徐悲鸿最著名的油画人物肖像之一,作于1939年春夏之交,徐悲鸿时年44岁。此作品是徐悲鸿为了支持国内抗战,而在南洋举行义卖募捐时的作品。画中女子珍妮小姐,祖籍广东,为当时星洲名媛。在当时传为佳话。此画得到画筹四万新币,为这一时期与南洋募捐中画筹最多的一幅(总数为十一万一千多元新币),徐悲鸿本人也是非常满意这幅作品,特意请摄影师为其和画作拍照留念,后成为《悲鸿在星洲》一书的封面。 《九方皋》九方皋故事记载于《列子》:伯乐暮年之向秦穆推荐九方皋找千里马的故事。作品是一件非常完整和精彩的以线为主要表现手段和描绘方式的优秀的中国画作品。 《田横五百士》 这幅《田横五百士》是徐悲鸿的成名大作。故事出自《史记·田儋列传》。田横是秦末齐国旧王族,继田儋之后为齐王。刘邦消灭群雄后,田横和他的五百壮士逃亡到一个海岛上。刘邦听说田横深得人心,恐日后有患,所以派使者赦田横的罪,召他回来。正是有感于田横等人”富贵不能淫,威武不能屈”的”高节”,着意选取了田横与五百壮士惜别的戏剧性场景来表现。 《负伤之狮》 创作于1938年,当时日寇侵占了大半个中国,国土沦丧,生灵涂炭,徐悲鸿怨愤难忍。他画的负伤雄狮,回首跷望,含着无限的深意。他在画上题写:“国难孔亟时与麟若先生同客重庆相顾不怿写此以聊抒怀。”表现了作者爱国忧时的思想。这是一幅现实主义和浪漫主义结合的画作。中国被称作东方的“睡狮”,被日本帝国主义侵占了中国东北大部分国土,“睡狮”已成了负伤雄狮。这头双目怒视的负伤雄狮在不堪回首的神情中,准备战斗、拼搏,蕴藏着坚强与力量。 《天高地阔任翱翔》 徐悲鸿抗战时期作逾十一平尺巨幅飞鹰《天高地阔任翱翔》赠与星洲建筑师何光耀。 《愚公移山图》作于1940年,1939至1940年,应印度大诗人泰戈尔之邀,徐悲鸿赴印度举办画展宣传抗日,这期间他创作了不少油画写生,但最重要的成果却是这幅《愚公移山图》国画。其故事取材于《列子·汤问》中的一个神话传说。 四. 收藏 下面是收藏的 “任伯年” 作品","categories":[{"name":"艺术","slug":"艺术","permalink":"http://example.com/categories/%E8%89%BA%E6%9C%AF/"}],"tags":[{"name":"绘画","slug":"绘画","permalink":"http://example.com/tags/%E7%BB%98%E7%94%BB/"},{"name":"徐悲鸿","slug":"徐悲鸿","permalink":"http://example.com/tags/%E5%BE%90%E6%82%B2%E9%B8%BF/"}]},{"title":"静静的健身-保持健康","slug":"静静的健身-保持健康","date":"2022-09-04T00:23:04.000Z","updated":"2022-09-04T11:36:46.587Z","comments":true,"path":"2022/09/04/静静的健身-保持健康/","link":"","permalink":"http://example.com/2022/09/04/%E9%9D%99%E9%9D%99%E7%9A%84%E5%81%A5%E8%BA%AB-%E4%BF%9D%E6%8C%81%E5%81%A5%E5%BA%B7/","excerpt":"","text":"一. 八段锦 早起第一件事 喜马拉雅听音频小红书视频版 二. 静坐练习 上班休息时(有些同学可以把吸烟的时间换掉) 南怀瑾静坐练习","categories":[{"name":"生活","slug":"生活","permalink":"http://example.com/categories/%E7%94%9F%E6%B4%BB/"}],"tags":[{"name":"健康","slug":"健康","permalink":"http://example.com/tags/%E5%81%A5%E5%BA%B7/"}]},{"title":"开启我的blog-新设备","slug":"开启我的blog-新设备","date":"2022-09-03T23:01:04.000Z","updated":"2022-09-19T01:42:26.614Z","comments":true,"path":"2022/09/04/开启我的blog-新设备/","link":"","permalink":"http://example.com/2022/09/04/%E5%BC%80%E5%90%AF%E6%88%91%E7%9A%84blog-%E6%96%B0%E8%AE%BE%E5%A4%87/","excerpt":"","text":"一. 下载博客git到本地二. 安装 hexo三. 当前项目目录中安装主题及node依赖包node installgit clone --depth 1 https://github.com/hexojs/hexo-theme-landscape themes/landscape 四. 测试使用hexo server 五. 遇到的问题 无法deploy git@github.com: Permission denied (publickey).fatal: Could not read from remote repository.Please make sure you have the correct access rightsand the repository exists.FATAL { err: Error: Spawn failed at ChildProcess.<anonymous> (/Users/Aaron/Documents/code/github/wansongblog/node_modules/hexo-deployer-git/node_modules/hexo-util/lib/spawn.js:51:21) at ChildProcess.emit (node:events:390:28) at Process.ChildProcess._handle.onexit (node:internal/child_process:290:12) { code: 128 }} Something's wrong. Maybe you can find the solution here: %s https://hexo.io/docs/troubleshooting.html 无法修改, 新建的文档是被锁定的 解决办法: 修改hexo权限即可 ➜ wansongblog git:(master) ✗ which hexo/usr/local/bin/hexo➜ wansongblog git:(master) ✗ ll /usr/local/bin/hexolrwxr-xr-x 1 root wheel 37 Sep 4 05:24 /usr/local/bin/hexo -> ../lib/node_modules/hexo-cli/bin/hexo➜ wansongblog git:(master) ✗ cd /usr/local/bin/➜ bin cd ../lib/node_modules➜ node_modules sudo chmod -R 777 hexo-cli 六. 自动化部署参考","categories":[{"name":"生活","slug":"生活","permalink":"http://example.com/categories/%E7%94%9F%E6%B4%BB/"}],"tags":[{"name":"人生","slug":"人生","permalink":"http://example.com/tags/%E4%BA%BA%E7%94%9F/"},{"name":"感悟","slug":"感悟","permalink":"http://example.com/tags/%E6%84%9F%E6%82%9F/"},{"name":"疑惑","slug":"疑惑","permalink":"http://example.com/tags/%E7%96%91%E6%83%91/"}]},{"title":"The Zen of Python, by Tim Peters","slug":"2015-01-1-PYTHON-初次接触","date":"2022-09-03T10:12:16.071Z","updated":"2022-09-03T10:12:16.071Z","comments":true,"path":"2022/09/03/2015-01-1-PYTHON-初次接触/","link":"","permalink":"http://example.com/2022/09/03/2015-01-1-PYTHON-%E5%88%9D%E6%AC%A1%E6%8E%A5%E8%A7%A6/","excerpt":"","text":"============= 由于自己对新知识的好奇,2014年被一位朋友吸引,当时遇到他时,他是在坚持用python进行实现自己的功能。同时用的是vim进行编写,而之前我一直是用IDE,2015年初,我开始了解python,首先我接触到的就是 import thisThe Zen of Python- **Python的原则**Beautiful is better than ugly.- **优美胜于丑陋(Python 以编写优美的代码为目标)**Explicit is better than implicit.- **明了胜于晦涩(优美的代码应当是明了的,命名规范,风格相似)**Simple is better than complex.- **简洁胜于复杂(优美的代码应当是简洁的,不要有复杂的内部实现)**Complex is better than complicated.- **复杂胜于凌乱(如果复杂不可避免,那代码间也不能有难懂的关系,要保持接口简洁)**Flat is better than nested.- **扁平胜于嵌套(优美的代码应当是扁平的,不能有太多的嵌套) **Sparse is better than dense.- **间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题)**Readability counts.- **可读性很重要(优美的代码是可读的)**Special cases aren't special enough to break the rules.- **即便假借特例的实用性之名,也不可违背这些规则(这些规则至高无上)**Although practicality beats purity.Errors should never pass silently.- **不要包容所有错误,除非你确定需要这样做(精准地捕获异常,不写 except:pass 风格的代码)**Unless explicitly silenced.- **而是尽量找一种,最好是唯一一种明显的解决方案(如果不确定,就用穷举法)**In the face of ambiguity, refuse the temptation to guess.- **当存在多种可能,不要尝试去猜测**There should be one-- and preferably only one --obvious way to do it.- **而是尽量找一种,最好是唯一一种明显的解决方案(如果不确定,就用穷举法)**Although that way may not be obvious at first unless you're Dutch.- **虽然这并不容易,因为你不是 Python 之父(这里的 Dutch 是指 Guido )**Now is better than never.Although never is often better than *right* now.- **做也许好过不做,但不假思索就动手还不如不做(动手之前要细思量) **If the implementation is hard to explain, it's a bad idea.If the implementation is easy to explain, it may be a good idea.- **如果你无法向人描述你的方案,那肯定不是一个好方案;反之亦然(方案测评标准)**Namespaces are one honking great idea -- let's do more of those!- **命名空间是一种绝妙的理念,我们应当多加利用(倡导与号召)**---- by Tim Peters python 也用了一段时间,感觉他和javascript很像,但是只是感觉,具体哪里像,后面我会归档一下,同时也是为自己理清思路。 她与java的区别,我目前感觉两种语言,只是语法上的不同,没有感觉到非常大的差别,我也会单独整理一份这两个语言的差别。但自己学习。","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"},{"name":"语言","slug":"技术/语言","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/%E8%AF%AD%E8%A8%80/"}],"tags":[{"name":"python","slug":"python","permalink":"http://example.com/tags/python/"}]},{"title":"POI设置表格自动换行","slug":"POI设置表格自动换行","date":"2020-09-27T16:25:35.000Z","updated":"2022-09-03T10:12:16.078Z","comments":true,"path":"2020/09/28/POI设置表格自动换行/","link":"","permalink":"http://example.com/2020/09/28/POI%E8%AE%BE%E7%BD%AE%E8%A1%A8%E6%A0%BC%E8%87%AA%E5%8A%A8%E6%8D%A2%E8%A1%8C/","excerpt":"","text":"在开发过程中有些同学遇到需要表格自动换行,其实poi不设置高度,设置WrapText即可 ` <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.15</version> <scope>compile</scope> </dependency> 代码片断参考 @Testpublic void xssfWrite() throws Exception { Workbook finalWb = new XSSFWorkbook(); XSSFSheet sheet = (XSSFSheet) finalWb.createSheet(System.currentTimeMillis()+""); sheet.setDefaultColumnWidth(20); SheetContent content=new SheetContent(); content.setHeaders(Lists.newArrayList("title","content")); Map<String, Object> temp = Maps.newHashMap(); temp.put("title","jdk8之前为空判断使业务代码读起来比较费劲,对整体业务逻辑的理解增加困惑;" + "jdk8支持了 Optional 之后 ,使用我们可以非常轻松的将原本一大块的判断代码块变成一句话;"); temp.put("content","左侧是自动换行"); Map<String, Object> temp2 = Maps.newHashMap(); temp2.put("title","POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(filePath));"); temp2.put("content","左侧是自动换行"); content.setValues(Lists.newArrayList(temp,temp2)); writeSheet(sheet,content); FileOutputStream bos=new FileOutputStream("异常数据.xlsx"); finalWb.write(bos);}private void writeSheet(XSSFSheet sheet, SheetContent content) { Set<Object> last= Sets.newHashSet(); for (int i = 0; i < content.getHeaders().size(); i++) { writeCell(sheet,0,i,null,content.getHeaders().get(i)); } int row=0; for (int i = 0; i < content.getValues().size(); i++) { Map<String, Object> contents=content.getValues().get(i); ArrayList<Object> temp = new ArrayList<>(contents.values()); last.add((temp.get(0))); row++; for (int i1 = 0; i1 < temp.size(); i1++) { Object item=temp.get(i1); if(item instanceof Double){ if(((Double) item).intValue()==((Double) item).doubleValue()){ item=((Double) item).intValue(); } } writeCell(sheet,row,i1,null,item); } } System.out.println(last.size()+","+last);}private void writeCell(XSSFSheet sheet, int r, int l, Color color, Object value) { XSSFRow row = sheet.getRow(r); if (row == null) { row = sheet.createRow(r); } XSSFCell cell = row.getCell(l); if (cell == null) { cell = row.createCell(l); } cell.setCellValue(value.toString()); XSSFCellStyle style = sheet.getWorkbook().createCellStyle(); if (color == null) { color = new java.awt.Color(162, 187, 185); } style.setFillForegroundColor(new XSSFColor(color)); style.setVerticalAlignment(VerticalAlignment.TOP); style.setFillPattern(CellStyle.SOLID_FOREGROUND); style.setWrapText(true); cell.setCellStyle(style);}@Datapublic static class SheetContent { private String sheetName; private List<Object> headers= Lists.newArrayList(); private List<Map<String,Object>> values=Lists.newArrayList(); public void addValue(List<Object> cells){ Map<String,Object> value=Maps.newLinkedHashMap(); for (int i = 0; i < headers.size(); i++) { value.put(headers.get(i)+"",cells.get(i)); } values.add(value); }}","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"jdk8.java","slug":"jdk8-java","permalink":"http://example.com/tags/jdk8-java/"},{"name":"poi","slug":"poi","permalink":"http://example.com/tags/poi/"}]},{"title":"jdk_null有关判断--Optional","slug":"jdk-null有关判断","date":"2020-09-23T13:56:13.000Z","updated":"2022-09-03T10:12:16.078Z","comments":true,"path":"2020/09/23/jdk-null有关判断/","link":"","permalink":"http://example.com/2020/09/23/jdk-null%E6%9C%89%E5%85%B3%E5%88%A4%E6%96%AD/","excerpt":"","text":"jdk8之前为空判断使业务代码读起来比较费劲,对整体业务逻辑的理解增加困惑;jdk8支持了 Optional 之后 ,使用我们可以非常轻松的将原本一大块的判断代码块变成一句话; 正常的判空优化效果Optional.ofNullable(null).orElse("default") 从对象中取值时String userName=null;User user=null;if(Objects.isNull(user)){ userName="username is null";}else{ userName=user.getName();}优化后userName=Optional.ofNullable(user).map((temp)->temp.getName()).orElse("default");或userName=Optional.ofNullable(user).flatMap(user1 -> Optional.ofNullable(user1.getName())).orElse("happy");","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"jdk8.java","slug":"jdk8-java","permalink":"http://example.com/tags/jdk8-java/"},{"name":"jdk","slug":"jdk","permalink":"http://example.com/tags/jdk/"}]},{"title":"Alfred Workflow快速打开idea项目","slug":"alfred快速打开idea项目","date":"2020-09-20T14:19:49.000Z","updated":"2022-09-03T10:12:16.078Z","comments":true,"path":"2020/09/20/alfred快速打开idea项目/","link":"","permalink":"http://example.com/2020/09/20/alfred%E5%BF%AB%E9%80%9F%E6%89%93%E5%BC%80idea%E9%A1%B9%E7%9B%AE/","excerpt":"","text":"Mac 安装Alfred 进行workflow的设置 keyword-->file filter--> open file","categories":[{"name":"日常工具","slug":"日常工具","permalink":"http://example.com/categories/%E6%97%A5%E5%B8%B8%E5%B7%A5%E5%85%B7/"}],"tags":[{"name":"mac","slug":"mac","permalink":"http://example.com/tags/mac/"},{"name":"alfred","slug":"alfred","permalink":"http://example.com/tags/alfred/"},{"name":"workflow","slug":"workflow","permalink":"http://example.com/tags/workflow/"}]},{"title":"redis","slug":"redis","date":"2018-04-15T01:23:48.000Z","updated":"2022-09-03T10:12:16.078Z","comments":true,"path":"2018/04/15/redis/","link":"","permalink":"http://example.com/2018/04/15/redis/","excerpt":"","text":"一. redis 实现原理五种类型的键的底层实现数据结构具体命令可参考命令 SDS( simple dynamic string) 简单动态字符串 struct sdshdr{int len;int free;char buf[];} 链表 typedef struct listNode{struct listNode *prev;struct listNode *next;void *value;}listNode;typedef struct list{listNode *head;listNode *tail;unsigned long len;void *(*dup)(void *ptr);void (*free)(void *ptr);int (*match)(void *ptr,void *key);} 字典 Redis 的字典使用哈希表作为底层实现,一个哈希敷衍里面可以有多个节点,每个节点就保存了字典中的一个键值对; 新添加一个键值对到字典里时,程序需要先根据键值对的键计算出哈希值和索引值,然后根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上面.当有两个或以上数量的键被分配到哈希数组的同一个索引上面时,我们称为冲突.这里使用链地址法解决键冲突. 哈希表 typedef struct dictht{dictEntry **table;unsigned long size;unsigned long sizemask;unsigned long used;} sizemask 值和哈希值一起决定一个键应该被放到table数组的哪个索引上面. 哈希表节点 typedef struct dictEntry{void *key;union{void *val;uint64_tu64;int64_ts64;} v;struct dictEntry *next; //解决键冲突的问题} dictEntry; 字典 typedef struct dict{dictType *type; //类型特定函数void *privdata;//私有数据dictht ht[2];//哈希表int trehashidx;//索引} dict; rehash(实际过程,是渐进式的) 当哈希表中键值的数量太多或太少时,为了让哈希表的负载因子维持在一个合理的范围之内,程序需要对哈希表的大小进行相应的扩展或者收缩. 为字段的ht[1]哈希表分配空间,这个空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量 如果是扩展操作,那么大小为第一个大于等于ht[0].used*2的2的n次方 如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2的n次方 将保存在ht[0]中的所有键值对rehash到ht[1]上面:rehash指重新计算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上 将ht[0]释放空间,同时将ht[0]和ht[1]换位置 何时进行扩展和收缩负载因子= ht[0].used(已保存的节点数量)/哈希表的大小 当负载因子大于 5 (待确认),或<0.1 时 跳跃表 skiplist 是一种有序的数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的. redis在以下两个地方用到了跳跃表: 有序集合键 zset 在集群节点中用途内部数据结构 整数集合 intset 是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现. 127.0.0.1:6379> SADD numbers 1 3 5 6 7(integer) 5127.0.0.1:6379> OBJECT ENCODING numbers"intset"127.0.0.1:6379> sadd numbers 0943890384093845903845094385(integer) 1127.0.0.1:6379> smembers numbers1) "3"2) "0943890384093845903845094385"3) "7"4) "2"5) "6"6) "1"7) "9"8) "5"127.0.0.1:6379> OBJECT ENCODING numbers"hashtable" 每当我们要将一个新元素添加到整数集合里面,并且新元素的类型比整数集合现有的所有元素的类型都要长时,整数集合需要先进行升级,然后才能将新元素添加到整数集合里面,请记住,这里不会降级的 其是Redis保存整数值的集合抽象数据结构,它可以保存int16_t,int32_t,int64_t的整数值,并且保证集合中不会出现重复元素. typedef struct intset{uint32_t encoding;//编码方式 INTSET_ENC_INT16,INTSET_ENC_INT32,INTSET_ENC_INT64uint32_t length;//集合包含的元素数量int8_t contents[]; //保存元素的数组,数组中按值的大小从小到大有序排列,并且数组中不包含任何重复项;其真正的类型取决于encoding属性的值:} 压缩列表 ziplist,是列表键和哈希键的底层实现之一.当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现. 127.0.0.1:6379> rpush kdf djf f df d f d f "sdf"(integer) 8127.0.0.1:6379> OBJECT ENCODING kdf"ziplist" 压缩列表是为了节约内存而开发的,是由一系列特殊的连续内存块组成的顺序型数据结构.一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值. 对象处理机制以及数据库的实现原理 导入 Redis 基于这些数据结构创建一个对象系统,其包含 字符串,列表对象,哈希对象,集合对象和有序集合对象 五种类型的对象,每种对象都至少一种我们前面所介绍的数据结构. 使用对象的好处,我们可以针对不同的使用场景,为对象设置多种不同的数据结构pugmww而优化对象在不同场景下的使用效率. 对象系统基于引用计数技术的内存回收机制,当程序不再使用某个对象的时候,这个对象所战胜的内存就会被自动释放;另外,Redis还通过引用计数技术实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享同一个对象来节约内存. 对象带有访问时间记录信息,该信息可以用于计算数据库的空转时长 ,在服务器启用了maxmemory功能的情况下,空转时长较大的那些键可能会优先被服务器删除. 对象的类型和编码 type Redis使用对象来表示数据库中的键值,每次我们在库中新创建一个键值对时,我们至少会创建两个对象,一个是键对象,另一个是值对象. 127.0.0.1:6379> set name aaronOK127.0.0.1:6379> get name"aaron"127.0.0.1:6379> OBJECT ENCODING name"embstr"127.0.0.1:6379> type namestring127.0.0.1:6379> OBJECT idletime name(integer) 46 每一个对象都由一个redisObject结构表示,该结构中和保存数据有关的三个属性分别是type属性,encoding属性和ptr属性: typedef struct redisObject{unsigned type:4;unsigned encoding:4;void *ptr;//每日向底层实现数据结构的指针int refcount;//引用计数unsigned lru:22;//该对象最后一次被访问的时间}robj; type记录了对象的类型,这个属性的值有 string,list,hash,set,zset 编码和底层实现 OBJECT ENCODING 对象的ptr指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定.也就是说这个对象使用了什么数据结构作为对象的底层实现,这个属性值可以是 int (long 类型)embstr (embstr编码的简单动态字符串)raw (简单动态字符串)ht (字典)linkedlist (双端链表)ziplist (压缩链表)intset (整数集合)skiplist (跳跃链表和字典) 每种类型对象都至少使用了两种不同的编码. string int/embstr/rawlist ziplist/linkedlisthash ziplist/htset intset/htzset ziplist/skiplist 数据共享 只共享0-9999的字符串对象 127.0.0.1:6379> SET a 100OK127.0.0.1:6379> OBJECT refcount a(integer) 2127.0.0.1:6379> OBJECT refcount a(integer) 2127.0.0.1:6379> SET b 100OK127.0.0.1:6379> OBJECT refcount a(integer) 3127.0.0.1:6379> 单机数据库的实现 在redisServer结构的db数组中,每个redisDb 结构代表一个数据库,启动服务器时,服务器会根据dbnum来决定应该创建多少个数据库: struct redisServer{...redisDb *db;int dbnum;...}redisClient 客户端可以根据命令select来进行切换目标数据库 数据库键空间 是一个键值对数据库服务器,其中每个数据库都由一个redisDb结构表示,其中redisDb结构的dict字典保存了数据库中的所有键值对,我们称这个字典为 键空间 typedef struct redisDb{ dict *dict; dict *expires; key 是对象,value 是过期时间 }redisDb 设置生存时间或过期时间 127.0.0.1:6379> set name wansongOK127.0.0.1:6379> expire name 10(integer) 1127.0.0.1:6379> get name"wansong"127.0.0.1:6379> get name(nil) 数据库通知 2.8 新版本中增加的功能,可以通过订阅给它的频道或者模式,来获知数据库中键的变化.及数据库中命令的执行情况. RDB 持久化和 AOF 持久化的实现原理RDB持久化功能,可以将Redis在内存中的数据库状态保存到磁盘里面,避免数据意外丢失.也可以根据服务器配置选项定期执行.该功能可以将某个时间点上的数据库状态保存到一个RDB文件中.该文件是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态. RDB文件的创建与载入 save 命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不处理任务命令请求. bgsave background saving started 该命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程继续处理命令请求 创建文件的实际工作由rdbSave函数完成,save和bgsave命令会以不同的方式调用这个函数. RDB文件的载入是自动的,当程序启动时会自动载入,另外注意AOF文件的更新频率通常比RDB高,所以: 如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件还原数据库状态 只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态. 载入RDB文件的实际工作由rdbLoad函数完成;文件载入时服务器处于阻塞状态. 自动间隔性保存 可以通过save选项设置多个保存条件,但只要其中任意一个条件被满足,服务器就会执行bgsave. save 900 1 服务器900秒之内,对数据库进行至少一次修改,就进行bgsave 事件Redis 基于 Reactor模式开发的网络事件处理器,称作 文件事件处理器 (File Event Handler) 使用I/O多路复用程序来同时监听多个套接字,并根据目前执行的任务来为套接字关联不同的事件 当被监听的套接字准备好执行连接应答,读取,写入,关闭 等操作时,与其对应的文件事件就会产生,这时1中注册好的事件处理器就来进行处理这些事件 时间事件 id/when/handlers 定时事件 周期性事件 事务实现原理 ACIDServerCron函数 服务器 默认每100毫秒执行一次 更新服务器时间缓存 更新LRU时钟(如 Redis对象都会有一个LRU属性,这个属性保存了对象最后一次被命令访问的时间) 更新服务器每秒执行命令的次数(INFO status) 更新服务器内存峰值记录 处理SIGTERM信号 每次运行时,程序会对服务器状态的shutdown_asap属性进行检查,看是否要关闭服务器 管理客户端资源: 已超时 或 是否清理输出缓冲区 管理数据库资源: 删除过期键,并在需要时 对字典进行收缩操作 检查持久化操作的运行状态 将AOF缓冲区的内容写入到AOF文件 关闭异步客户端 增加cronloops计数器的值 初始化过程初始化服务器状态结构,载入配置选项,还原数据库状态,执行事件循环 订阅与发布实现原理Lua 脚本功能的实现原理。SORT 命令的实现原理。慢查询日志的实现原理。 打开慢查询,查看日期 SLOWLOG GET高并发如何做到虽是单线程单进行,但 使用I/O多路复用(select/epoll,evport,kqueue)程序来同时监听多个套接字 的方式来处理命令请求,并与多个客户端进行通信. 二. redis 主要关注点redis 为什么是单线程redis 过期索引是如何做到的 redis 的存储结构 删除策略 * 定时删除:在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即对键执行删除操作(对内存友好,最及时)* 定期删除:每一段时间,进行数据库过期索引的扫瞄,将已经过期的键 进行删除; 至于删除多少过期键和检查哪些数据库,都由算法决定* 惰性删除: 每次取键时,校验一下是否过期,若已经过期 就进行删除其实最终使用的是 定期和惰性 两个策略 配合实现 redis 服务器配置redis 有哪些功能redis 如何Failover() 哨兵(Sentinel)和复制(Replication)Sentinel可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转移的功能;Replication则是负责让一个Redis服务器可以配备多个备份的服务器 redis 目前流程的实施架构有哪些 哨兵Sentinel,复制(replication) 集群(cluster) 三. redis 应用场景参考 Redis设计与实现","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"缓存","slug":"缓存","permalink":"http://example.com/tags/%E7%BC%93%E5%AD%98/"},{"name":"redis","slug":"redis","permalink":"http://example.com/tags/redis/"}]},{"title":"微服务相关概念","slug":"微服务相关概念","date":"2018-04-14T00:53:22.000Z","updated":"2022-09-03T10:12:16.078Z","comments":true,"path":"2018/04/14/微服务相关概念/","link":"","permalink":"http://example.com/2018/04/14/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%9B%B8%E5%85%B3%E6%A6%82%E5%BF%B5/","excerpt":"","text":"服务治理基本概念 服务的伸缩控制 身份验证与授权 * 服务注册与发现 * 反向代理与负载均衡 路由控制 * 流量切换 * 日志管理 * 性能度量、监控与调优 * 分布式跟踪 * 过载保护 * 服务降级 * 服务部署与版本升级策略支持 * 错误处理 * 国际化 服务的伸缩控制身份验证与授权服务注册与发现 dubbo zookeeper 反向代理与负载均衡 vertx nginx","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"分布式","slug":"分布式","permalink":"http://example.com/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"},{"name":"服务治理","slug":"服务治理","permalink":"http://example.com/tags/%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86/"},{"name":"算法","slug":"算法","permalink":"http://example.com/tags/%E7%AE%97%E6%B3%95/"},{"name":"CAP","slug":"CAP","permalink":"http://example.com/tags/CAP/"}]},{"title":"排序","slug":"排序","date":"2018-03-29T00:04:24.000Z","updated":"2022-09-03T10:12:16.078Z","comments":true,"path":"2018/03/29/排序/","link":"","permalink":"http://example.com/2018/03/29/%E6%8E%92%E5%BA%8F/","excerpt":"","text":"","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"算法","slug":"算法","permalink":"http://example.com/tags/%E7%AE%97%E6%B3%95/"},{"name":"排序","slug":"排序","permalink":"http://example.com/tags/%E6%8E%92%E5%BA%8F/"}]},{"title":"二分法查找及扩展","slug":"二分法查找及扩展","date":"2018-03-28T23:59:02.000Z","updated":"2022-09-03T10:12:16.078Z","comments":true,"path":"2018/03/29/二分法查找及扩展/","link":"","permalink":"http://example.com/2018/03/29/%E4%BA%8C%E5%88%86%E6%B3%95%E6%9F%A5%E6%89%BE%E5%8F%8A%E6%89%A9%E5%B1%95/","excerpt":"","text":"二分法查找 给一个有序数组,查找出k所在位置 /** * @author Aaron * @since 6.2 */public class TheFirstLessThan100 { public static int find(int[] array, int value) { int low = 0; int high = array.length - 1; int count = 0; while (low <= high) { int middle = (low + high) >>> 1; int middleValue = array[middle]; count++; if (middleValue < value) { low = middle + 1; } else if (middleValue > value) { high = middle - 1; } else { System.out.printf("times:%d,index:%d,value:%d\\n", count, middle, value); return middle; } } return -1; } public static void main(String[] args) { int[] array = new int[]{1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 8, 9, 10, 14, 17, 19, 20}; for (int i = 0; i < array.length; i++) { int index = find(array, array[i]); } }} 查出第一个大于N的位置 从有序的数组中,找出第一个大于N的数字的位置 public static int findFirstBigIndex(int[] array, int value) { int lastBigIndex=-1; int low = 0; int high = array.length - 1; int count = 0; while (low <= high) { int middle = (low + high) >>> 1; int middleValue = array[middle]; count++; if (middleValue <= value) { low = middle + 1; } else if (middleValue > value) { high = middle - 1; lastBigIndex=middle; System.out.printf("times:%d,index:%d,value:%d,middle:%d\\n", count, middle, value,middleValue); } } return lastBigIndex; } public static int findFirstBigIndex1(int[] array, int value) { int low = 0; int high = array.length - 1; int count = 0; while (low <= high) { int middle = (low + high) >>> 1; int middleValue = array[middle]; count++; if (middleValue <= value) { low = middle + 1; } else if (middleValue > value) { high = middle - 1; System.out.printf("times:%d,index:%d,value:%d,middle:%d\\n", count, middle, value,middleValue); } } return low; } public static void main(String[] args) { int[] array = new int[]{1, 2, 3, 4, 5, 6, 8, 9, 10, 14, 17, 19, 20,21,22,34,324,546}; for (int i = 0; i < array.length; i++) { int index = findFirstBigIndex1(array, i); int index2 = findFirstBigIndex(array, i); System.out.println(index+"--"+index2); } }","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"算法","slug":"算法","permalink":"http://example.com/tags/%E7%AE%97%E6%B3%95/"},{"name":"查找","slug":"查找","permalink":"http://example.com/tags/%E6%9F%A5%E6%89%BE/"}]},{"title":"微信头像九宫格算法","slug":"微信头像九宫格算法","date":"2018-03-25T02:46:33.000Z","updated":"2022-09-03T10:12:16.077Z","comments":true,"path":"2018/03/25/微信头像九宫格算法/","link":"","permalink":"http://example.com/2018/03/25/%E5%BE%AE%E4%BF%A1%E5%A4%B4%E5%83%8F%E4%B9%9D%E5%AE%AB%E6%A0%BC%E7%AE%97%E6%B3%95/","excerpt":"","text":"分别计算1-9个头像在九宫格中的位置 public static List<ImageCell> createMergeCell(int n, int totalWidth) { int totalRow = (int) Math.ceil(Math.sqrt(n)); int outline = 5; int width = ((totalWidth - outline) / totalRow); int border = width / 20; if (n == 1) { return Lists.newArrayList(new ImageCell(border, border, width - 2 * border)); } int lastAloneNum = n % totalRow; int totalFullRow = n / totalRow; int lastRow = totalRow - totalFullRow - 1; int firstStartX = (totalWidth - lastAloneNum * width) / 2; int firstStartY = lastRow * width; int otherSpace = (totalWidth - totalRow * width) / 2; int yOffset = 0; if (totalRow != totalFullRow + (lastAloneNum != 0 ? 1 : 0)) { yOffset = -width / 2; } List<ImageCell> imageCells = Lists.newArrayList(); for (int i = 0; i < n; i++) { int x = 0, y = firstStartY; if (i < lastAloneNum) { x = firstStartX + i * width; } else { x = (i - lastAloneNum) % totalRow * width; y = firstStartY + ((i - lastAloneNum) / totalRow + 1) * width; } imageCells.add(new ImageCell(x + border + otherSpace, y + border + otherSpace + yOffset, width - 2 * border)); } return imageCells; } @Data @AllArgsConstructor static class ImageCell { int x; int y; int width; } -------------------n=1--------------------- x:7,y:7,width:131-------------------n=2--------------------- x:6,y:42,width:66 x:78,y:42,width:66-------------------n=3--------------------- x:45,y:6,width:66 x:6,y:78,width:66 x:78,y:78,width:66-------------------n=4--------------------- x:6,y:6,width:66 x:78,y:6,width:66 x:6,y:78,width:66 x:78,y:78,width:66-------------------n=5--------------------- x:32,y:29,width:44 x:80,y:29,width:44 x:5,y:77,width:44 x:53,y:77,width:44 x:101,y:77,width:44-------------------n=6--------------------- x:5,y:29,width:44 x:53,y:29,width:44 x:101,y:29,width:44 x:5,y:77,width:44 x:53,y:77,width:44 x:101,y:77,width:44-------------------n=7--------------------- x:56,y:5,width:44 x:5,y:53,width:44 x:53,y:53,width:44 x:101,y:53,width:44 x:5,y:101,width:44 x:53,y:101,width:44 x:101,y:101,width:44-------------------n=8--------------------- x:32,y:5,width:44 x:80,y:5,width:44 x:5,y:53,width:44 x:53,y:53,width:44 x:101,y:53,width:44 x:5,y:101,width:44 x:53,y:101,width:44 x:101,y:101,width:44-------------------n=9--------------------- x:5,y:5,width:44 x:53,y:5,width:44 x:101,y:5,width:44 x:5,y:53,width:44 x:53,y:53,width:44 x:101,y:53,width:44 x:5,y:101,width:44 x:53,y:101,width:44 x:101,y:101,width:44","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"算法","slug":"算法","permalink":"http://example.com/tags/%E7%AE%97%E6%B3%95/"}]},{"title":"取一个数字二进制中1的个数","slug":"取一个数字二进制中1的个数","date":"2018-03-24T12:10:29.000Z","updated":"2022-09-03T10:12:16.077Z","comments":true,"path":"2018/03/24/取一个数字二进制中1的个数/","link":"","permalink":"http://example.com/2018/03/24/%E5%8F%96%E4%B8%80%E4%B8%AA%E6%95%B0%E5%AD%97%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%B8%AD1%E7%9A%84%E4%B8%AA%E6%95%B0/","excerpt":"","text":"解 二进制中1的个数 int countBits(int n) { int count=0 ; while (n>0) { count++ ; n &= (n - 1) ; } return count ;} 复杂度: < log2n 方案二 /** * Returns the number of one-bits in the two's complement binary * representation of the specified {@code int} value. This function is * sometimes referred to as the <i>population count</i>. * * @param i the value whose bits are to be counted * @return the number of one-bits in the two's complement binary * representation of the specified {@code int} value. * @since 1.5 */public static int bitCount(int i) { // HD, Figure 5-2 i = i - ((i >>> 1) & 0x55555555); i = (i & 0x33333333) + ((i >>> 2) & 0x33333333); i = (i + (i >>> 4)) & 0x0f0f0f0f; i = i + (i >>> 8); i = i + (i >>> 16); return i & 0x3f;} 复杂度: 1 方案三 public static int countBit1(int n) { int count=0 ; int temp=1; while (temp<=n) { if((temp&n)>0){ count++ ; } temp<<=1; } return count ;} 扩展 给定一个数字n计算从1到n每一个数字的二进制中包含1的个数 public static int[] countBits(int num) { int[] ret = new int[num + 1]; for (int i = 0; i <= num; i++) { int div = i / 2; int mod = i % 2; if (mod == 1) { ret[i] = ret[div] + 1; } else { ret[i] = ret[div]; } } return ret; }","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"算法","slug":"算法","permalink":"http://example.com/tags/%E7%AE%97%E6%B3%95/"}]},{"title":"java.util.concurrent概览","slug":"java-util-concurrent概览","date":"2018-03-24T09:51:58.000Z","updated":"2022-09-03T10:12:16.077Z","comments":true,"path":"2018/03/24/java-util-concurrent概览/","link":"","permalink":"http://example.com/2018/03/24/java-util-concurrent%E6%A6%82%E8%A7%88/","excerpt":"","text":"java.util.concurrent 包含许多线程安全、测试良好、高性能的并发构建块。不客气地说,创建 java.util.concurrent 的目的就是要实现 Collection 框架对数据结构所执行的并发操作。通过提供一组可靠的、高性能并发构建块,开发人员可以提高并发类的线程安全、可伸缩性、性能、可读性和可靠性。 如果一些类名看起来相似,可能是因为 java.util.concurrent 中的许多概念源自 Doug Lea 的 util.concurrent 库(请参阅 参考资料)。 JDK 5.0 中的并发改进可以分为三组:1. JVM 级别更改。 大多数现代处理器对并发对某一硬件级别提供支持,通常以 compare-and-swap (CAS)指令形式。CAS 是一种低级别的、细粒度的技术,它允许多个线程更新一个内存位置,同时能够检测其他线程的冲突并进行恢复。它是许多高性能并发算法的基础。在 JDK 5.0 之前,Java 语言中用于协调线程之间的访问的惟一原语是同步,同步是更重量级和粗粒度的。公开 CAS 可以开发高度可伸缩的并发 Java 类。这些更改主要由 JDK 库类使用,而不是由开发人员使用。 2. 低级实用程序类 -- 锁定和原子类。 使用 CAS 作为并发原语,ReentrantLock 类提供与 synchronized 原语相同的锁定和内存语义,然而这样可以更好地控制锁定(如计时的锁定等待、锁定轮询和可中断的锁定等待)和提供更好的可伸缩性(竞争时的高性能)。大多数开发人员将不再直接使用 ReentrantLock 类,而是使用在 ReentrantLock 类上构建的高级类。 3. 高级实用程序类。 这些类实现并发构建块,每个计算机科学文本中都会讲述这些类 -- 信号、互斥、闩锁、屏障、交换程序、线程池和线程安全集合类等。大部分开发人员都可以在应用程序中用这些类,来替换许多(如果不是全部)同步、wait() 和 notify() 的使用,从而提高性能、可读性和正确性。 本文主要内容本教程将重点介绍 java.util.concurrent 包提供的高级实用程序类 -- 线程安全集合、线程池和同步实用程序。这些是初学者和专家都可以使用的"现成"类。 在第一小节中,我们将回顾并发的基本知识,尽管它不应取代对线程和线程安全的了解。那些一点都不熟悉线程的读者应该先参考一些关于线程的介绍,如"Introduction to Java Threads"教程(请参阅参考资料)。 接下来的几个小节将研究 java.util.concurrent 中的高级实用程序类 -- 线程安全集合、线程池、信号和同步工具。 最后一小节将介绍 java.util.concurrent 中的低级并发构建块,并提供一些性能测评来显示新 java.util.concurrent 类的可伸缩性的改进。 基础知识 什么是线程? 所有重要的操作系统都支持进程的概念 – 独立运行的程序,在某种程度上相互隔离。 线程有时称为 轻量级进程。与进程一样,它们拥有通过程序运行的独立的并发路径,并且每个线程都有自己的程序计数器,称为堆栈和本地变量。然而,线程存在于进程中,它们与同一进程内的其他线程共享内存、文件句柄以及每进程状态。 今天,几乎每个操作系统都支持线程,允许执行多个可独立调度的线程,以便共存于一个进程中。因为一个进程中的线程是在同一个地址空间中执行的,所以多个线程可以同时访问相同对象,并且它们从同一堆栈中分配对象。虽然这使线程更易于与其他线程共享信息,但也意味着您必须确保线程之间不相互干涉。 正确使用线程时,线程能带来诸多好处,其中包括更好的资源利用、简化开发、高吞吐量、更易响应的用户界面以及能执行异步处理。 Java 语言包括用于协调线程行为的原语,从而可以在不违反设计原型或者不破坏数据结构的前提下安全地访问和修改共享变量。 线程有哪些功能? 在 Java 程序中存在很多理由使用线程,并且不管开发人员知道线程与否,几乎每个 Java 应用程序都使用线程。许多 J2SE 和 J2EE 工具可以创建线程,如 RMI、Servlet、Enterprise JavaBeans 组件和 Swing GUI 工具包。 使用线程的理由包括: • 更易响应的用户界面。 事件驱动的 GUI 工具包(如 AWT 或 Swing)使用单独的事件线程来处理 GUI 事件。从事件线程中调用通过 GUI 对象注册的事件监听器。然而,如果事件监听器将执行冗长的任务(如文档拼写检查),那么 UI 将出现冻结,因为事件线程直到冗长任务完毕之后才能处理其他事件。通过在单独线程中执行冗长操作,当执行冗长后台任务时,UI 能继续响应。 • 使用多处理器。 多处理器(MP)系统变得越来越便宜,并且分布越来越广泛。因为调度的基本单位通常是线程,所以不管有多少处理器可用,一个线程的应用程序一次只能在一个处理器上运行。在设计良好的程序中,通过更好地利用可用的计算机资源,多线程能够提高吞吐量和性能。 • 简化建模。 有效使用线程能够使程序编写变得更简单,并易于维护。通过合理使用线程,个别类可以避免一些调度的详细、交叉存取操作、异步 IO 和资源等待以及其他复杂问题。相反,它们能专注于域的要求,简化开发并改进可靠性。 • 异步或后台处理。 服务器应用程序可以同时服务于许多远程客户机。如果应用程序从 socket 中读取数据,并且没有数据可以读取,那么对 read() 的调用将被阻塞,直到有数据可读。在单线程应用程序中,这意味着当某一个线程被阻塞时,不仅处理相应请求要延迟,而且处理所有请求也将延迟。然而,如果每个 socket 都有自己的 IO 线程,那么当一个线程被阻塞时,对其他并发请求行为没有影响。 线程安全 如果将这些类用于多线程环境中,虽然确保这些类的线程安全比较困难,但线程安全却是必需的。java.util.concurrent 规范进程的一个目标就是提供一组线程安全的、高性能的并发构建块,从而使开发人员能够减轻一些编写线程安全类的负担。 线程安全类非常难以明确定义,大多数定义似乎都是完全循环的。快速 Google 搜索会显示下列线程安全代码定义的例子,但这些定义(或者更确切地说是描述)通常没什么帮助: • . . . can be called from multiple programming threads without unwanted interaction between the threads. • . . . may be called by more than on thread at a time without requiring any other action on the caller’s part. 通过类似这样的定义,不奇怪我们为什么对线程安全如此迷惑。这些定义几乎就是在说”如果可以从多个线程安全调用类,那么该类就是线程安全的”。这当然是线程安全的解释,但对我们区别线程安全类和不安全类没有什么帮助。我们使用”安全”是为了说明什么? 要成为线程安全的类,首先它必须在单线程环境中正确运行。如果正确实现了类,那么说明它符合规范,对该类的对象的任何顺序的操作(公共字段的读写、公共方法的调用)都不应该使对象处于无效状态;观察将处于无效状态的对象;或违反类的任何变量、前置条件或后置条件。 而且,要成为线程安全的类,在从多个线程访问时,它必须继续正确运行,而不管运行时环境执行那些线程的调度和交叉,且无需对部分调用代码执行任何其他同步。结果是对线程安全对象的操作将用于按固定的整体一致顺序出现所有线程。 如果没有线程之间的某种明确协调,比如锁定,运行时可以随意在需要时在多线程中交叉操作执行。 在 JDK 5.0 之前,确保线程安全的主要机制是 synchronized 原语。访问共享变量(那些可以由多个线程访问的变量)的线程必须使用同步来协调对共享变量的读写访问。java.util.concurrent 包提供了一些备用并发原语,以及一组不需要任何其他同步的线程安全实用程序类。 令人厌烦的并发 即使您的程序从没有明确创建线程,也可能会有许多工具或框架代表您创建了线程,这时要求从这些线程调用的类是线程安全的。这样会对开发人员带来较大的设计和实现负担,因为开发线程安全类比开发非线程安全类有更多要注意的事项,且需要更多的分析。 AWT 和 Swing 这些 GUI 工具包创建了称为时间线程的后台线程,将从该线程调用通过 GUI 组件注册的监听器。因此,实现这些监听器的类必须是线程安全的。 TimerTask JDK 1.3 中引入的 TimerTask 工具允许稍后执行任务或计划定期执行任务。在 Timer 线程中执行 TimerTask 事件,这意味着作为 TimerTask 执行的任务必须是线程安全的。 Servlet 和 JavaServer Page 技术 Servlet 容器可以创建多个线程,在多个线程中同时调用给定 servlet,从而进行多个请求。因此 servlet 类必须是线程安全的。 RMI 远程方法调用(remote method invocation,RMI)工具允许调用其他 JVM 中运行的操作。实现远程对象最普遍的方法是扩展 UnicastRemoteObject。例示 UnicastRemoteObject 时,它是通过 RMI 调度器注册的,该调度器可能创建一个或多个线程,将在这些线程中执行远程方法。因此,远程类必须是线程安全的。 正如所看到的,即使应用程序没有明确创建线程,也会发生许多可能会从其他线程调用类的情况。幸运的是,java.util.concurrent 中的类可以大大简化编写线程安全类的任务。 例子 -- 非线程安全 servlet 下列 servlet 看起来像无害的留言板 servlet,它保存每个来访者的姓名。然而,该 servlet 不是线程安全的,而这个 servlet 应该是线程安全的。问题在于它使用 HashSet 存储来访者的姓名,HashSet 不是线程安全的类。 当我们说这个 servlet 不是线程安全的时,是说它所造成的破坏不仅仅是丢失留言板输入。在最坏的情况下,留言板数据结构都可能被破坏并且无法恢复。public class UnsafeGuestbookServlet extends HttpServlet { private Set visitorSet = new HashSet(); protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException { String visitorName = httpServletRequest.getParameter("NAME"); if (visitorName != null) visitorSet.add(visitorName); }} 通过将 visitorSet 的定义更改为下列代码,可以使该类变为线程安全的: private Set visitorSet = Collections.synchronizedSet(new HashSet()); 如上所示的例子显示线程的内置支持是一把双刃剑 -- 虽然它使构建多线程应用程序变得很容易,但它同时要求开发人员更加注意并发问题,甚至在使用留言板 servlet 这样普通的东西时也是如此。 线程安全集合 JDK 1.2 中引入的 Collection 框架是一种表示对象集合的高度灵活的框架,它使用基本接口 List、Set 和 Map。通过 JDK 提供每个集合的多次实现(HashMap、Hashtable、TreeMap、WeakHashMap、HashSet、TreeSet、Vector、ArrayList、LinkedList 等等)。其中一些集合已经是线程安全的(Hashtable 和 Vector),通过同步的封装工厂(Collections.synchronizedMap()、synchronizedList() 和 synchronizedSet()),其余的集合均可表现为线程安全的。java.util.concurrent 包添加了多个新的线程安全集合类(ConcurrentHashMap、CopyOnWriteArrayList 和 CopyOnWriteArraySet)。这些类的目的是提供高性能、高度可伸缩性、线程安全的基本集合类型版本。java.util 中的线程集合仍有一些缺点。例如,在迭代锁定时,通常需要将该锁定保留在集合中,否则,会有抛出 ConcurrentModificationException 的危险。(这个特性有时称为条件线程安全;有关的更多说明,请参阅参考资料。)此外,如果从多个线程频繁地访问集合,则常常不能很好地执行这些类。java.util.concurrent 中的新集合类允许通过在语义中的少量更改来获得更高的并发。JDK 5.0 还提供了两个新集合接口 – Queue 和 BlockingQueue。Queue 接口与 List 类似,但它只允许从后面插入,从前面删除。通过消除 List 的随机访问要求,可以创建比现有 ArrayList 和 LinkedList 实现性能更好的 Queue 实现。因为 List 的许多应用程序实际上不需要随机访问,所以Queue 通常可以替代 List,来获得更好的性能。 弱一致的迭代器 java.util 包中的集合类都返回 fail-fast 迭代器,这意味着它们假设线程在集合内容中进行迭代时,集合不会更改它的内容。如果 fail-fast 迭代器检测到在迭代过程中进行了更改操作,那么它会抛出 ConcurrentModificationException,这是不可控异常。 在迭代过程中不更改集合的要求通常会对许多并发应用程序造成不便。相反,比较好的是它允许并发修改并确保迭代器只要进行合理操作,就可以提供集合的一致视图,如 java.util.concurrent 集合类中的迭代器所做的那样。 java.util.concurrent 集合返回的迭代器称为弱一致的(weakly consistent)迭代器。对于这些类,如果元素自从迭代开始已经删除,且尚未由 next() 方法返回,那么它将不返回到调用者。如果元素自迭代开始已经添加,那么它可能返回调用者,也可能不返回。在一次迭代中,无论如何更改底层集合,元素不会被返回两次。 CopyOnWriteArrayList 和 CopyOnWriteArraySet 可以用两种方法创建线程安全支持数据的 List – Vector 或封装 ArrayList 和 Collections.synchronizedList()。java.util.concurrent 包添加了名称繁琐的 CopyOnWriteArrayList。为什么我们想要新的线程安全的List类?为什么Vector还不够? 最简单的答案是与迭代和并发修改之间的交互有关。使用 Vector 或使用同步的 List 封装器,返回的迭代器是 fail-fast 的,这意味着如果在迭代过程中任何其他线程修改 List,迭代可能失败。 Vector 的非常普遍的应用程序是存储通过组件注册的监听器的列表。当发生适合的事件时,该组件将在监听器的列表中迭代,调用每个监听器。为了防止 ConcurrentModificationException,迭代线程必须复制列表或锁定列表,以便进行整体迭代,而这两种情况都需要大量的性能成本。 CopyOnWriteArrayList 类通过每次添加或删除元素时创建支持数组的新副本,避免了这个问题,但是进行中的迭代保持对创建迭代器时的当前副本进行操作。虽然复制也会有一些成本,但是在许多情况下,迭代要比修改多得多,在这些情况下,写入时复制要比其他备用方法具有更好的性能和并发性。 如果应用程序需要 Set 语义,而不是 List,那么还有一个 Set 版本 – CopyOnWriteArraySet。 ConcurrentHashMap 正如已经存在线程安全的 List 的实现,您可以用多种方法创建线程安全的、基于 hash 的 Map – Hashtable,并使用 Collections.synchronizedMap() 封装 HashMap。JDK 5.0 添加了 ConcurrentHashMap 实现,该实现提供了相同的基本线程安全的 Map 功能,但它大大提高了并发性。 Hashtable 和 synchronizedMap 所采取的获得同步的简单方法(同步 Hashtable 中或者同步的 Map 封装器对象中的每个方法)有两个主要的不足。首先,这种方法对于可伸缩性是一种障碍,因为一次只能有一个线程可以访问 hash 表。同时,这样仍不足以提供真正的线程安全性,许多公用的混合操作仍然需要额外的同步。虽然诸如 get() 和 put() 之类的简单操作可以在不需要额外同步的情况下安全地完成,但还是有一些公用的操作序列,例如迭代或者 put-if-absent(空则放入),需要外部的同步,以避免数据争用。 Hashtable 和 Collections.synchronizedMap 通过同步每个方法获得线程安全。这意味着当一个线程执行一个 Map 方法时,无论其他线程要对 Map 进行什么样操作,都不能执行,直到第一个线程结束才可以。 对比来说,ConcurrentHashMap 允许多个读取几乎总是并发执行,读和写操作通常并发执行,多个同时写入经常并发执行。结果是当多个线程需要访问同一 Map 时,可以获得更高的并发性。 在大多数情况下,ConcurrentHashMap 是 Hashtable或 Collections.synchronizedMap(new HashMap()) 的简单替换。然而,其中有一个显著不同,即 ConcurrentHashMap 实例中的同步不锁定映射进行独占使用。实际上,没有办法锁定 ConcurrentHashMap 进行独占使用,它被设计用于进行并发访问。为了使集合不被锁定进行独占使用,还提供了公用的混合操作的其他(原子)方法,如 put-if-absent。ConcurrentHashMap 返回的迭代器是弱一致的,意味着它们将不抛出ConcurrentModificationException ,将进行”合理操作”来反映迭代过程中其他线程对 Map 的修改。 队列 原始集合框架包含三个接口:List、Map 和 Set。List 描述了元素的有序集合,支持完全随即访问 – 可以在任何位置添加、提取或删除元素。 LinkedList 类经常用于存储工作元素(等待执行的任务)的列表或队列。然而,List 提供的灵活性比该公用应用程序所需要的多得多,这个应用程序通常在后面插入元素,从前面删除元素。但是要支持完整 List 接口则意味着 LinkedList 对于这项任务不像原来那样有效。Queue 接口比 List 简单得多,仅包含 put() 和 take() 方法,并允许比 LinkedList 更有效的实现。 Queue 接口还允许实现来确定存储元素的顺序。ConcurrentLinkedQueue 类实现先进先出(first-in-first-out,FIFO)队列,而 PriorityQueue 类实现优先级队列(也称为堆),它对于构建调度器非常有用,调度器必须按优先级或预期的执行时间执行任务。 interface Queue extends Collection { boolean offer(E x); E poll(); E remove() throws NoSuchElementException; E peek(); E element() throws NoSuchElementException;} 实现 Queue 的类是: • LinkedList 已经进行了改进来实现 Queue。 • PriorityQueue 非线程安全的优先级对列(堆)实现,根据自然顺序或比较器返回元素。 • ConcurrentLinkedQueue 快速、线程安全的、无阻塞 FIFO 队列。 任务管理之线程创建 线程最普遍的一个应用程序是创建一个或多个线程,以执行特定类型的任务。Timer 类创建线程来执行 TimerTask 对象,Swing 创建线程来处理 UI 事件。在这两种情况中,在单独线程中执行的任务都假定是短期的,这些线程是为了处理大量短期任务而存在的。 在其中每种情况中,这些线程一般都有非常简单的结构: while (true) { if (no tasks) wait for a task; execute the task;} 通过例示从 Thread 获得的对象并调用 Thread.start() 方法来创建线程。可以用两种方法创建线程:通过扩展 Thread 和覆盖 run() 方法,或者通过实现 Runnable 接口和使用 Thread(Runnable) 构造函数: class WorkerThread extends Thread { public void run() { /* do work */ }}Thread t = new WorkerThread();t.start(); 或者: Thread t = new Thread(new Runnable() { public void run() { /* do work */ }}t.start(); 重新使用线程 因为多个原因,类似 Swing GUI 的框架为事件任务创建单一线程,而不是为每项任务创建新的线程。首先是因为创建线程会有间接成本,所以创建线程来执行简单任务将是一种资源浪费。通过重新使用事件线程来处理多个事件,启动和拆卸成本(随平台而变)会分摊在多个事件上。 Swing 为事件使用单一后台线程的另一个原因是确保事件不会互相干涉,因为直到前一事件结束,下一事件才开始处理。该方法简化了事件处理程序的编写。 使用多个线程,将要做更多的工作来确保一次仅一个线程地执行线程相关的代码。 如何不对任务进行管理 大多数服务器应用程序(如 Web 服务器、POP 服务器、数据库服务器或文件服务器)代表远程客户机处理请求,这些客户机通常使用 socket 连接到服务器。对于每个请求,通常要进行少量处理(获得该文件的代码块,并将其发送回 socket),但是可能会有大量(且不受限制)的客户机请求服务。 用于构建服务器应用程序的简单化模型会为每个请求创建新的线程。下列代码段实现简单的 Web 服务器,它接受端口 80 的 socket 连接,并创建新的线程来处理请求。不幸的是,该代码不是实现 Web 服务器的好方法,因为在重负载条件下它将失败,停止整台服务器。 class UnreliableWebServer { public static void main(String[] args) { ServerSocket socket = new ServerSocket(80); while (true) { final Socket connection = socket.accept(); Runnable r = new Runnable() { public void run() { handleRequest(connection); } }; // Don't do this! new Thread(r).start(); } }} 当服务器被请求吞没时,UnreliableWebServer 类不能很好地处理这种情况。每次有请求时,就会创建新的类。根据操作系统和可用内存,可以创建的线程数是有限的。 不幸的是,您通常不知道限制是多少 – 只有当应用程序因为 OutOfMemoryError 而崩溃时才发现。 如果足够快地在这台服务器上抛出请求的话,最终其中一个线程创建将失败,生成的 Error 会关闭整个应用程序。当一次仅能有效支持很少线程时,没有必要创建上千个 线程,无论如何,这样使用资源可能会损害性能。创建线程会使用相当一部分内存,其中包括有两个堆栈(Java 和 C),以及每线程数据结构。如果创建过多线程,其中 每个线程都将占用一些 CPU 时间,结果将使用许多内存来支持大量线程,每个线程都运行得很慢。这样就无法很好地使用计算资源。 使用线程池解决问题 为任务创建新的线程并不一定不好,但是如果创建任务的频率高,而平均任务持续时间低,我们可以看到每项任务创建一个新的线程将产生性能(如果负载不可预知,还有稳定性)问题。 如果不是每项任务创建一个新的线程,则服务器应用程序必须采取一些方法来限制一次可以处理的请求数。这意味着每次需要启动新的任务时,它不能仅调用下列代码。 new Thread(runnable).start() 管理一大组小任务的标准机制是组合工作队列和线程池。工作队列就是要处理的任务的队列,前面描述的 Queue 类完全适合。线程池是线程的集合,每个线程都提取公用工作队列。当一个工作线程完成任务处理后,它会返回队列,查看是否有其他任务需要处理。如果有,它会转移到下一个任务,并开始处理。 线程池为线程生命周期间接成本问题和资源崩溃问题提供了解决方案。通过对多个任务重新使用线程,创建线程的间接成本将分布到多个任务中。作为一种额外好处,因为请求到达时,线程已经存在,从而可以消除由创建线程引起的延迟。因此,可以立即处理请求,使应用程序更易响应。而且,通过正确调整线程池中的线程数,可以强制超出特定限制的任何请求等待,直到有线程可以处理它,它们等待时所消耗的资源要少于使用额外线程所消耗的资源,这样可以防止资源崩溃。 Executor 框架 java.util.concurrent 包中包含灵活的线程池实现,但是更重要的是,它包含用于管理实现 Runnable 的任务的执行的整个框架。该框架称为 Executor 框架。 Executor 接口相当简单。它描述将运行 Runnable 的对象: public interface Executor { void execute(Runnable command);} 任务运行于哪个线程不是由该接口指定的,这取决于使用的 Executor 的实现。它可以运行于后台线程,如 Swing 事件线程,或者运行于线程池,或者调用线程,或者新的线程,它甚至可以运行于其他 JVM!通过同步的 Executor 接口提交任务,从任务执行策略中删除任务提交。Executor 接口独自关注任务提交 – 这是Executor 实现的选择,确定执行策略。这使在部署时调整执行策略(队列限制、池大小、优先级排列等等)更加容易,更改的代码最少。 java.util.concurrent 中的大多数 Executor 实现还实现 ExecutorService 接口,这是对 Executor 的扩展,它还管理执行服务的生命周期。这使它们更易于管理,并向生命可能比单独 Executor 的生命更长的应用程序提供服务。 public interface ExecutorService extends Executor { void shutdown(); List shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit); // other convenience methods for submitting tasks} Executor java.util.concurrent 包包含多个 Executor 实现,每个实现都实现不同的执行策略。什么是执行策略?执行策略定义何时在哪个线程中运行任务,执行任务可能消耗的资源级别(线程、内存等等),以及如果执行程序超载该怎么办。 执行程序通常通过工厂方法例示,而不是通过构造函数。Executors 类包含用于构造许多不同类型的 Executor 实现的静态工厂方法: • Executors.newCachedThreadPool() 创建不限制大小的线程池,但是当以前创建的线程可以使用时将重新使用那些线程。如果没有现有线程可用, • 将创建新的线程并将其添加到池中。使用不到 60 秒的线程将终止并从缓存中删除。 • Executors.newFixedThreadPool(int n) 创建线程池,其重新使用在不受限制的队列之外运行的固定线程组。在关闭前,所有线程都会因为执行 • 过程中的失败而终止,如果需要执行后续任务,将会有新的线程来代替这些线程。 • Executors.newSingleThreadExecutor() 创建 Executor,其使用在不受限制的队列之外运行的单一工作线程,与 Swing 事件线程非常相似。 • 保证顺序执行任务,在任何给定时间,不会有多个任务处于活动状态。 更可靠的 Web 服务器 – 使用 Executor 前面 如何不对任务进行管理 中的代码显示了如何不用编写可靠服务器应用程序。幸运的是,修复这个示例非常简单,只需将 Thread.start() 调用替换为向 Executor 提交任务即可: class ReliableWebServer { Executor pool = Executors.newFixedThreadPool(7); public static void main(String[] args) { ServerSocket socket = new ServerSocket(80); while (true) { final Socket connection = socket.accept(); Runnable r = new Runnable() { public void run() { handleRequest(connection); } }; pool.execute(r); } }} 注意,本例与前例之间的区别仅在于 Executor 的创建以及如何提交执行的任务。 定制 ThreadPoolExecutor Executors 中的 newFixedThreadPool 和 newCachedThreadPool 工厂方法返回的 Executor 是类 ThreadPoolExecutor 的实例,是高度可定制的。通过使用包含 ThreadFactory 变量的工厂方法或构造函数的版本,可以定义池线程的创建。ThreadFactory 是工厂对象,其构造执行程序要使用的新线程。使用定制的线程工厂,创建的线程可以包含有用的线程名称,并且这些线程是守护线程,属于特定线程组或具有特定优先级。下面是线程工厂的例子,它创建守护线程,而不是创建用户线程: public class DaemonThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setDaemon(true); return thread; }} 有时,Executor 不能执行任务,因为它已经关闭或者因为 Executor 使用受限制队列存储等待任务,而该队列已满。在这种情况下,需要咨询执行程序的 RejectedExecutionHandler 来确定如何处理任务 -- 抛出异常(默认情况),放弃任务,在调用者的线程中执行任务,或放弃队列中最早的任务以为新任务腾出空间。ThreadPoolExecutor.setRejectedExecutionHandler 可以设置拒绝的执行处理程序。 还可以扩展 ThreadPoolExecutor,并覆盖方法 beforeExecute 和 afterExecute,以添加装置,添加记录,添加计时,重新初始化线程本地变量,或进行其他执行定制。 需要特别考虑的问题 使用 Executor 框架会从执行策略中删除任务提交,一般情况下,人们希望这样,那是因为它允许我们灵活地调整执行策略,不必更改许多位置的代码。然而,当提交代码暗含假设特定执行策略时,存在多种情况,在这些情况下,重要的是选择的 Executor 实现一致的执行策略。 这类情况中的其中的一种就是一些任务同时等待其他任务完成。在这种情况下,当线程池没有足够的线程时,如果所有当前执行的任务都在等待另一项任务,而该任务因为线程池已满不能执行,那么线程池可能会死锁。 另一种相似的情况是一组线程必须作为共同操作组一起工作。在这种情况下,需要确保线程池能够容纳所有线程。 如果应用程序对特定执行程序进行了特定假设,那么应该在 Executor 定义和初始化的附近对这些进行说明,从而使善意的更改不会破坏应用程序的正确功能。 调整线程池 创建 Executor 时,人们普遍会问的一个问题是"线程池应该有多大?"。当然,答案取决于硬件和将执行的任务类型(它们是受计算限制或是受 IO 的限制?)。 如果线程池太小,资源可能不能被充分利用,在一些任务还在工作队列中等待执行时,可能会有处理器处于闲置状态。 另一方面,如果线程池太大,则将有许多有效线程,因为大量线程或有效任务使用内存,或者因为每项任务要比使用少量线程有更多上下文切换,性能可能会受损。 所以假设为了使处理器得到充分使用,线程池应该有多大?如果知道系统有多少处理器和任务的计算时间和等待时间的近似比率,Amdahl 法则提供很好的近似公式。 用 WT 表示每项任务的平均等待时间,ST 表示每项任务的平均服务时间(计算时间)。则 WT/ST 是每项任务等待所用时间的百分比。对于 N 处理器系统,池中可以近似有 N*(1+WT/ST) 个线程。 好的消息是您不必精确估计 WT/ST。"合适的"池大小的范围相当大;只需要避免"过大"和"过小"的极端情况即可。 Future 接口 Future 接口允许表示已经完成的任务、正在执行过程中的任务或者尚未开始执行的任务。通过 Future 接口,可以尝试取消尚未完成的任务,查询任务已经完成还是取消了,以及提取(或等待)任务的结果值。 FutureTask 类实现了 Future,并包含一些构造函数,允许将 Runnable 或 Callable(会产生结果的 Runnable)和 Future 接口封装。因为 FutureTask 也实现 Runnable,所以可以只将 FutureTask 提供给 Executor。一些提交方法(如 ExecutorService.submit())除了提交任务之外,还将返回 Future 接口。 Future.get() 方法检索任务计算的结果(或如果任务完成,但有异常,则抛出 ExecutionException)。如果任务尚未完成,那么 Future.get() 将被阻塞,直到任务完成;如果任务已经完成,那么它将立即返回结果。 使用 Future 构建缓存 该示例代码与 java.util.concurrent 中的多个类关联,突出显示了 Future 的功能。它实现缓存,使用 Future 描述缓存值,该值可能已经计算,或者可能在其他线程中"正在构造"。 它利用 ConcurrentHashMap 中的原子 putIfAbsent() 方法,确保仅有一个线程试图计算给定关键字的值。如果其他线程随后请求同一关键字的值,它仅能等待(通过 Future.get() 的帮助)第一个线程完成。因此两个线程不会计算相同的值。 public class Cache { ConcurrentMap> map = new ConcurrentHashMap(); Executor executor = Executors.newFixedThreadPool(8); public V get(final K key) { FutureTask f = map.get(key); if (f == null) { Callable c = new Callable() { public V call() { // return value associated with key } }; f = new FutureTask(c); FutureTask old = map.putIfAbsent(key, f); if (old == null) executor.execute(f); else f = old; } return f.get(); }} CompletionService CompletionService 将执行服务与类似 Queue 的接口组合,从任务执行中删除任务结果的处理。CompletionService 接口包含用来提交将要执行的任务的 submit() 方法和用来询问下一完成任务的 take()/poll() 方法。 CompletionService 允许应用程序结构化,使用 Producer/Consumer 模式,其中生产者创建任务并提交,消费者请求完成任务的结果并处理这些结果。CompletionService 接口由 ExecutorCompletionService 类实现,该类使用 Executor 处理任务并从 CompletionService 导出 submit/poll/take 方法。 下列代码使用 Executor 和 CompletionService 来启动许多”solver”任务,并使用第一个生成非空结果的任务的结果,然后取消其余任务: void solve(Executor e, Collection> solvers) throws InterruptedException { CompletionService ecs = new ExecutorCompletionService(e); int n = solvers.size(); List> futures = new ArrayList>(n); Result result = null; try { for (Callable s : solvers) futures.add(ecs.submit(s)); for (int i = 0; i < n; ++i) { try { Result r = ecs.take().get(); if (r != null) { result = r; break; } } catch(ExecutionException ignore) {} } } finally { for (Future f : futures) f.cancel(true); } if (result != null) use(result); } java.util.concurrent 中其他类别的有用的类也是同步工具。这组类相互协作,控制一个或多个线程的执行流。Semaphore、CyclicBarrier、CountdownLatch 和 Exchanger 类都是同步工具的例子。每个类都有线程可以调用的方法,方法是否被阻塞取决于正在使用的特定同步工具的状态和规则。 Semaphore Semaphore 类实现标准 Dijkstra 计数信号。计数信号可以认为具有一定数量的许可权,该许可权可以获得或释放。如果有剩余的许可权,acquire() 方法将成功,否则该方法将被阻塞,直到有可用的许可权(通过其他线程释放许可权)。线程一次可以获得多个许可权。 计数信号可以用于限制有权对资源进行并发访问的线程数。该方法对于实现资源池或限制 Web 爬虫(Web crawler)中的输出 socket 连接非常有用。 注意信号不跟踪哪个线程拥有多少许可权;这由应用程序来决定,以确保何时线程释放许可权,该信号表示其他线程拥有许可权或者正在释放许可权,以及其他线程知道它的许可权已释放。 互斥 计数信号的一种特殊情况是互斥,或者互斥信号。互斥就是具有单一许可权的计数信号,意味着在给定时间仅一个线程可以具有许可权(也称为二进制信号)。互斥可以用于管理对共享资源的独占访问。 虽然互斥许多地方与锁定一样,但互斥还有一个锁定通常没有的其他功能,就是互斥可以由具有许可权的线程之外的其他线程来释放。这在死锁恢复时会非常有用。 CyclicBarrier 类可以帮助同步,它允许一组线程等待整个线程组到达公共屏障点。CyclicBarrier 是使用整型变量构造的,其确定组中的线程数。当一个线程到达屏障时(通过调用 CyclicBarrier.await()),它会被阻塞,直到所有线程都到达屏障,然后在该点允许所有线程继续执行。该操作与许多家庭逛商业街相似 – 每个家庭成员都自己走,并商定 1:00 在电影院集合。当您到电影院但不是所有人都到了时,您会坐下来等其他人到达。然后所有人一起离开。 认为屏障是循环的是因为它可以重新使用;一旦所有线程都已经在屏障处集合并释放,则可以将该屏障重新初始化到它的初始状态。 还可以指定在屏障处等待时的超时;如果在该时间内其余线程还没有到达屏障,则认为屏障被打破,所有正在等待的线程会收到 BrokenBarrierException。 下列代码将创建 CyclicBarrier 并启动一组线程,每个线程将计算问题的一部分,等待所有其他线程结束之后,再检查解决方案是否达成一致。如果不一致,那么每个工作线程将开始另一个迭代。该例将使用 CyclicBarrier 变量,它允许注册 Runnable,在所有线程到达屏障但还没有释放任何线程时执行 Runnable。 class Solver { // Code sketch void solve(final Problem p, int nThreads) { final CyclicBarrier barrier = new CyclicBarrier(nThreads, new Runnable() { public void run() { p.checkConvergence(); }} ); for (int i = 0; i < nThreads; ++i) { final int id = i; Runnable worker = new Runnable() { final Segment segment = p.createSegment(id); public void run() { try { while (!p.converged()) { segment.update(); barrier.await(); } } catch(Exception e) { return; } } }; new Thread(worker).start(); }} CountdownLatch CountdownLatch 类与 CyclicBarrier 相似,因为它的角色是对已经在它们中间分摊了问题的一组线程进行协调。它也是使用整型变量构造的,指明计数的初始值,但是与 CyclicBarrier 不同的是,CountdownLatch 不能重新使用。 其中,CyclicBarrier 是到达屏障的所有线程的大门,只有当所有线程都已经到达屏障或屏障被打破时,才允许这些线程通过,CountdownLatch 将到达和等待功能分离。任何线程都可以通过调用 countDown() 减少当前计数,这种不会阻塞线程,而只是减少计数。await() 方法的行为与 CyclicBarrier.await() 稍微有所不同,调用 await() 任何线程都会被阻塞,直到闩锁计数减少为零,在该点等待的所有线程才被释放,对 await() 的后续调用将立即返回。 当问题已经分解为许多部分,每个线程都被分配一部分计算时,CountdownLatch 非常有用。在工作线程结束时,它们将减少计数,协调线程可以在闩锁处等待当前这一批计算结束,然后继续移至下一批计算。 相反地,具有计数 1 的 CountdownLatch 类可以用作”启动大门”,来立即启动一组线程;工作线程可以在闩锁处等待,协调线程减少计数,从而立即释放所有工作线程。下例使用两个 CountdownLatche。一个作为启动大门,一个在所有工作线程结束时释放线程: class Driver { // ... void main() throws InterruptedException { CountDownLatch startSignal = new CountDownLatch(1); CountDownLatch doneSignal = new CountDownLatch(N); for (int i = 0; i < N; ++i) // create and start threads new Thread(new Worker(startSignal, doneSignal)).start(); doSomethingElse(); // don't let them run yet startSignal.countDown(); // let all threads proceed doSomethingElse(); doneSignal.await(); // wait for all to finish } } class Worker implements Runnable { private final CountDownLatch startSignal; private final CountDownLatch doneSignal; Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { this.startSignal = startSignal; this.doneSignal = doneSignal; } public void run() { try { startSignal.await(); doWork(); doneSignal.countDown(); } catch (InterruptedException ex) {} // return; } } Exchanger 类方便了两个共同操作线程之间的双向交换;这样,就像具有计数为 2 的 CyclicBarrier,并且两个线程在都到达屏障时可以”交换”一些状态。(Exchanger 模式有时也称为聚集。) Exchanger 通常用于一个线程填充缓冲(通过读取 socket),而另一个线程清空缓冲(通过处理从 socket 收到的命令)的情况。当两个线程在屏障处集合时,它们交换缓冲。下列代码说明了这项技术: class FillAndEmpty { Exchanger exchanger = new Exchanger(); DataBuffer initialEmptyBuffer = new DataBuffer(); DataBuffer initialFullBuffer = new DataBuffer(); class FillingLoop implements Runnable { public void run() { DataBuffer currentBuffer = initialEmptyBuffer; try { while (currentBuffer != null) { addToBuffer(currentBuffer); if (currentBuffer.full()) currentBuffer = exchanger.exchange(currentBuffer); } } catch (InterruptedException ex) { ... handle ... } } } class EmptyingLoop implements Runnable { public void run() { DataBuffer currentBuffer = initialFullBuffer; try { while (currentBuffer != null) { takeFromBuffer(currentBuffer); if (currentBuffer.empty()) currentBuffer = exchanger.exchange(currentBuffer); } } catch (InterruptedException ex) { ... handle ...} } } void start() { new Thread(new FillingLoop()).start(); new Thread(new EmptyingLoop()).start(); } } 锁定和原子之Lock Java 语言内置了锁定工具 – synchronized 关键字。当线程获得监视器时(内置锁定),其他线程如果试图获得相同锁定,那么它们将被阻塞,直到第一个线程释放该锁定。同步还确保随后获得相同锁定的线程可以看到之前的线程在具有该锁定时所修改的变量的值,从而确保如果类正确地同步了共享状态的访问权,那么线程将不会看到变量的”失效”值,这是缓存或编译器优化的结果。 虽然同步没有什么问题,但它有一些限制,在一些高级应用程序中会造成不便。Lock 接口将内置监视器锁定的锁定行为普遍化,允许多个锁定实现,同时提供一些内置锁定缺少的功能,如计时的等待、可中断的等待、锁定轮询、每个锁定有多个条件等待集合以及无阻塞结构的锁定。 interface Lock { void lock(); void lockInterruptibly() throws IE; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws IE; void unlock(); Condition newCondition() throws UnsupportedOperationException; } ReentrantLock ReentrantLock 是具有与隐式监视器锁定(使用 synchronized 方法和语句访问)相同的基本行为和语义的 Lock 的实现,但它具有扩展的能力。 作为额外收获,在竞争条件下,ReentrantLock 的实现要比现在的 synchronized 实现更具有可伸缩性。(有可能在 JVM 的将来版本中改进 synchronized 的竞争性能。) 这意味着当许多线程都竞争相同锁定时,使用 ReentrantLock 的吞吐量通常要比 synchronized 好。换句话说,当许多线程试图访问 ReentrantLock 保护的共享资源时,JVM 将花费较少的时间来调度线程,而用更多个时间执行线程。 虽然 ReentrantLock 类有许多优点,但是与同步相比,它有一个主要缺点 – 它可能忘记释放锁定。建议当获得和释放 ReentrantLock 时使用下列结构: Lock lock = new ReentrantLock();...lock.lock();try { // perform operations protected by lock}catch(Exception ex) { // restore invariants}finally { lock.unlock();} 因为锁定失误(忘记释放锁定)的风险,所以对于基本锁定,强烈建议您继续使用 synchronized,除非真的需要 ReentrantLock 额外的灵活性和可伸缩性。 ReentrantLock 是用于高级应用程序的高级工具 – 有时需要,但有时用原来的方法就很好。 Condition 就像 Lock 接口是同步的具体化,Condition 接口是 Object 中 wait() 和 notify() 方法的具体化。Lock 中的一个方法是 newCondition(),它要求锁定向该锁定返回新的 Condition 对象限制。await()、signal() 和 signalAll() 方法类似于 wait()、notify() 和 notifyAll(),但增加了灵活性,每个 Lock 都可以创建多个条件变量。这简化了一些并发算法的实现。 ReadWriteLock ReentrantLock 实现的锁定规则非常简单 – 每当一个线程具有锁定时,其他线程必须等待,直到该锁定可用。有时,当对数据结构的读取通常多于修改时,可以使用更复杂的称为读写锁定的锁定结构,它允许有多个并发读者,同时还允许一个写入者独占锁定。该方法在一般情况下(只读)提供了更大的并发性,同时在必要时仍提供独占访问的安全性。ReadWriteLock 接口和 ReentrantReadWriteLock 类提供这种功能 – 多读者、单写入者锁定规则,可以用这种功能来保护共享的易变资源。 原子变量 即使大多数用户将很少直接使用它们,原子变量类(AtomicInteger、AtomicLong、AtomicReference 等等)也有充分理由是最显著的新并发类。这些类公开对 JVM 的低级别改进,允许进行具有高度可伸缩性的原子读-修改-写操作。大多数现代 CPU 都有原子读-修改-写的原语,比如比较并交换(CAS)或加载链接/条件存储(LL/SC)。原子变量类使用硬件提供的最快的并发结构来实现。 许多并发算法都是根据对计数器或数据结构的比较并交换操作来定义的。通过暴露高性能的、高度可伸缩的 CAS 操作(以原子变量的形式),用 Java 语言实现高性能、无等待、无锁定的并发算法已经变得可行。 几乎 java.util.concurrent 中的所有类都是在 ReentrantLock 之上构建的,ReentrantLock 则是在原子变量类的基础上构建的。所以,虽然仅少数并发专家使用原子变量类,但 java.util.concurrent 类的很多可伸缩性改进都是由它们提供的。 原子变量主要用于为原子地更新”热”字段提供有效的、细粒度的方式,”热”字段是指由多个线程频繁访问和更新的字段。另外,原子变量还是计数器或生成序号的自然机制。 性能与可伸缩性 虽然 java.util.concurrent 努力的首要目标是使编写正确、线程安全的类更加容易,但它还有一个次要目标,就是提供可伸缩性。可伸缩性与性能完全不同,实际上,可伸缩性有时要以性能为代价来获得。 性能是”可以快速执行此任务的程度”的评测。可伸缩性描述应用程序的吞吐量如何表现为它的工作量和可用计算资源增加。可伸缩的程序可以按比例使用更多的处理器、内存或 I/O 带宽来处理更多个工作量。当我们在并发环境中谈论可伸缩性时,我们是在问当许多线程同时访问给定类时,这个类的执行情况。 java.util.concurrent 中的低级别类 ReentrantLock 和原子变量类的可伸缩性要比内置监视器(同步)锁定高得多。因此,使用 ReentrantLock 或原子变量类来协调共享访问的类也可能更具有可伸缩性。 Hashtable 与 ConcurrentHashMap 作为可伸缩性的例子,ConcurrentHashMap 实现设计的可伸缩性要比其线程安全的上一代 Hashtable 的可伸缩性强得多。Hashtable 一次只允许一个线程访问 Map;ConcurrentHashMap 允许多个读者并发执行,读者与写入者并发执行,以及一些写入者并发执行。因此,如果许多线程频繁访问共享映射,使用 ConcurrentHashMap 的总的吞吐量要比使用 Hashtable 的好。 下表大致说明了 Hashtable 和 ConcurrentHashMap 之间的可伸缩性差别。在每次运行时,N 个线程并发执行紧密循环,它们从 Hashtable 或 ConcurrentHashMap 中检索随即关键字,60% 的失败检索将执行 put() 操作,2% 的成功检索执行 remove() 操作。测试在运行 Linux 的双处理器 Xeon 系统中执行。数据显示 10,000,000 个迭代的运行时间,对于 ConcurrentHashMap,标准化为一个线程的情况。可以看到直到许多线程,ConcurrentHashMap 的性能仍保持可伸缩性,而 Hashtable 的性能在出现锁定竞争时几乎立即下降。 与通常的服务器应用程序相比,这个测试中的线程数看起来很少。然而,因为每个线程未进行其他操作,仅是重复地选择使用该表,所以这样可以模拟在执行一些实际工作的情况下使用该表的大量线程的竞争。 线程 ConcurrentHashMap Hashtable 1 1.0 1.51 2 1.44 17.09 4 1.83 29.9 8 4.06 54.06 16 7.5 119.44 32 15.32 237.2 Lock 与 synchronized 与原子 下列基准说明了使用 java.util.concurrent 可能改进可伸缩性的例子。该基准将模拟旋转骰子,使用线性同余随机数生成器。有三个可用的随机数生成器的实现:一个使用同步来管理生成器的状态(单一变量),一个使用 ReentrantLock,另一个则使用 AtomicLong。下图显示了在 8-way Ultrasparc3 系统上,逐渐增加线程数量时这三个版本的相对吞吐量。(该图对原子变量方法的可伸缩性描述比较保守。) 公平与不公平 java.util.concurrent 中许多类中的另外一个定制元素是”公平”的问题。公平锁定或公平信号是指在其中根据先进先出(FIFO)的原则给与线程锁定或信号。ReentrantLock、Semaphore 和 ReentrantReadWriteLock 的构造函数都可以使用变量确定锁定是否公平,或者是否允许闯入(线程获得锁定,即使它们等待的时间不是最长)。 虽然闯入锁定的想法可能有些可笑,但实际上不公平、闯入的锁定非常普遍,且通常很受欢迎。使用同步访问的内置锁定不是公平锁定(且没有办法使它们公平)。相反,它们提供较弱的生病保证,要求所有线程最终都将获得锁定。 大多数应用程序选择(且应该选择)闯入锁定而不是公平锁定的原因是性能。在大多数情况下,完全的公平不是程序正确性的要求,真正公平的成本相当高。下表向前面的面板中的表中添加了第四个数据集,并由一个公平锁定管理对 PRNG 状态的访问。注意闯入锁定与公平锁定之间吞吐量的巨大差别。 结束语java.util.concurrent 包中包含大量有用的构建快,可以用它们来改进并发类的性能、可伸缩性、线程安全和可维护性。通过这些构建快,应该可以不再需要在您的代码中大量使用同步、wait/notify 和 Thread.start(),而用更高级别、标准化的、高性能并发实用程序来替换它们。 Exchanger,CyclicBarrier,Synchronizer 本文由 blog博主Caoer(草儿)原创,此处为转载。 由于原文两张图片不方便显示,这里暂时去掉,转载时本博(http://www.blogjava.net/mlh123caoer/archive/2007/10/24/155474.html)重新引用了图片,并调整了版面。 如原创作者认为本文侵权,请通知本博。 并发参考学习","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"多线程","slug":"多线程","permalink":"http://example.com/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"name":"并发","slug":"并发","permalink":"http://example.com/tags/%E5%B9%B6%E5%8F%91/"}]},{"title":"Dubbo","slug":"dubbo","date":"2016-10-13T07:12:23.000Z","updated":"2022-09-03T10:12:16.077Z","comments":true,"path":"2016/10/13/dubbo/","link":"","permalink":"http://example.com/2016/10/13/dubbo/","excerpt":"","text":"Dubbo在项目中的作用分布式服务架构 当服务越来越多时,服务URL配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。此时需要一个服务注册中心,动态的注册和发现服务,使服务的位置透明。并通过在消费方获取服务提供方地址列表,实现软负载均衡和Failover,降低对F5硬件负载均衡器的依赖,也能减少部分成本。 当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。这时,需要自动画出应用间的依赖关系图,以帮助架构师理清理关系。 接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标。其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阀值,记录此时的访问量,再以此访问量乘以机器数反推总容量。 架构角色 Provider: 暴露服务的服务提供方。 Consumer: 调用远程服务的服务消费方。 Registry: 服务注册与发现的注册中心。 Monitor: 统计服务的调用次调和调用时间的监控中心。 Container: 服务运行容器。 调用关系 服务容器负责启动,加载,运行服务提供者。 服务提供者在启动时,向注册中心注册自己提供的服务。 服务消费者在启动时,向注册中心订阅自己所需的服务。 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"分布式","slug":"分布式","permalink":"http://example.com/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"},{"name":"dubbo","slug":"dubbo","permalink":"http://example.com/tags/dubbo/"},{"name":"服务治理","slug":"服务治理","permalink":"http://example.com/tags/%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86/"},{"name":"rpc","slug":"rpc","permalink":"http://example.com/tags/rpc/"}]},{"title":"Paxos","slug":"paxos","date":"2016-09-16T06:58:22.000Z","updated":"2022-09-03T10:12:16.077Z","comments":true,"path":"2016/09/16/paxos/","link":"","permalink":"http://example.com/2016/09/16/paxos/","excerpt":"","text":"作者介绍1982,Lamport 提出了一种计算机容错理论,并于1900年论证。这是一种基于消传递且具有高度容错特性的一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一。 时间时钟、面包店算法、拜占庭将军及paxos算法的创建性容错 paxos的目的 提高分布式系统容错性的一致性算法 核心 一致性算法 算法三个角色: Proposer Acceptor Learner 规则paxos 描述: 参与者之间可以进行通信,可以记录一些信息,来确定最终的值 消息内容不会被篡改 知行学社的分布式系统与Paxos算法 对paxos算法核心思想的描述 在抢占式访问权的基础上引入多acceptor 保证一个epoch,只有一个proposer运行,proposer按照epoch递增的顺序依次运行。 新的epoch的proposer采用后者认同前者的思路运行。 在肯定旧epoch无法生成确定性取值时,新的epoch 会提交自己的取值。不会冲突。 一旦旧epoch形成确定性取值,新epoch肯定可以获取到此取值,并且会认同此取值,不会破坏。","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"分布式","slug":"分布式","permalink":"http://example.com/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"},{"name":"算法","slug":"算法","permalink":"http://example.com/tags/%E7%AE%97%E6%B3%95/"},{"name":"一致性算法","slug":"一致性算法","permalink":"http://example.com/tags/%E4%B8%80%E8%87%B4%E6%80%A7%E7%AE%97%E6%B3%95/"},{"name":"CAP","slug":"CAP","permalink":"http://example.com/tags/CAP/"}]},{"title":"MAC下完成人脸检测代码环境构建","slug":"MAC下完成人脸检测代码环境构建","date":"2016-09-15T10:29:07.000Z","updated":"2022-09-03T10:12:16.077Z","comments":true,"path":"2016/09/15/MAC下完成人脸检测代码环境构建/","link":"","permalink":"http://example.com/2016/09/15/MAC%E4%B8%8B%E5%AE%8C%E6%88%90%E4%BA%BA%E8%84%B8%E6%A3%80%E6%B5%8B%E4%BB%A3%E7%A0%81%E7%8E%AF%E5%A2%83%E6%9E%84%E5%BB%BA/","excerpt":"","text":"目的工作中用到图片的截图,但在使用过程中出现了一个尴尬的问题,就是截图时,有的人没有了头,只留下身子. 解决方式通过代码来检测出头部所以位置,然后来决定载哪些区域(目前,只是对一个人进行剪切。 具体方案 找到一个合适的类库来进行人头位置检测 _python-opencv_ 使用python 对人裁剪范围进行处理(GraphicsMagick)得到想要的区域 正题 如何在MAC上安装开发环境利用 python-opencv 库http://www.pyimagesearch.com/2015/06/15/install-opencv-3-0-and-python-2-7-on-osx/ cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local \\-D PYTHON2_PACKAGES_PATH=~/.virtualenvs/cv/lib/python2.7/site-packages \\-DPYTHON2_LIBRARY=/usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/bin\\-D PYTHON2_INCLUDE_DIR=/usr/local/Frameworks/Python.framework/Headers \\-D INSTALL_C_EXAMPLES=ON -D INSTALL_PYTHON_EXAMPLES=ON \\-D BUILD_EXAMPLES=ON \\-D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules .. http://docs.opencv.org/2.4/doc/tutorials/introduction/linux_install/linux_install.html#linux-installation brew tap homebrew/sciencebrew install opencv3 http://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_objdetect/py_face_detection/py_face_detection.html","categories":[{"name":"方案","slug":"方案","permalink":"http://example.com/categories/%E6%96%B9%E6%A1%88/"}],"tags":[{"name":"python","slug":"python","permalink":"http://example.com/tags/python/"},{"name":"图片处理","slug":"图片处理","permalink":"http://example.com/tags/%E5%9B%BE%E7%89%87%E5%A4%84%E7%90%86/"},{"name":"人脸识别","slug":"人脸识别","permalink":"http://example.com/tags/%E4%BA%BA%E8%84%B8%E8%AF%86%E5%88%AB/"}]},{"title":"第N次百望山到植物园","slug":"第N次百望山到植物园","date":"2016-09-12T11:23:10.000Z","updated":"2022-09-03T21:57:32.963Z","comments":true,"path":"2016/09/12/第N次百望山到植物园/","link":"","permalink":"http://example.com/2016/09/12/%E7%AC%ACN%E6%AC%A1%E7%99%BE%E6%9C%9B%E5%B1%B1%E5%88%B0%E6%A4%8D%E7%89%A9%E5%9B%AD/","excerpt":"","text":"为什么是N次2015年已经多次走这样同一个路线 为什么第N次记录了一下之前来走的时候都是正常13公里,到植物园进行小息游园后坐公交回家。但是这次遇到了一些事情,也许今后不会再次走这条路,故在此进行记录 百望山路段不得不说百望山发展之迅速,从去年到今天常走的路基本上都已经修成了马路,真的是‘大功’ 一件,这样子人们就不用走泥路了,也不用那么接地气了 ~ 百望山到黑山头路段这段路不算长,也就二三公里的样子,不过曾经一个人走过N次。清醒的记得去年走的一幕幕,黑黑的,阴阴的,凉凉的,热热的,鸟儿到处都是,有时甚至还有蛐蛐在歌唱。真可以说精神上的放松。期间老婆也走过一两次,由于全程走下来比较累,基本上都是我一个人在走。基本上每次走完,一周都感到比较轻松。 黑山头到冷泉村防火道这路其实都一两个比较大的景点,也许只有我自己认为那算做景点吧! 山泉若想去山泉取水,每次要多走2公里。一般情况下会想去但是由于后面路程的问题会选择赶路。这次我去了,其实也是被迫了基本上。嘿嘿 天泉寺今天才知道这个寺届是 天泉寺 之前走到这里总感觉是一片废墟,从没想过这地如此的大。这次到这里,发现有人在整修,并且已经把原来的地基轮廓都已经整理出来。在这里呆了差不多一个小时,感叹时间的厉害,也感叹我们在时间长流中是那么微不足道。后来又来到寺庙傍边的佛塔傍边,从佛塔的视角可以看清楚整个寺庙的全景。于是我便非常想知道,这个寺庙到底经历了哪些历史… 冷泉村到植物园这次走了一半,在去植物园的路已经被我们解放军战士把守,说是军事重地,人民不能进入。我问了一下,他们说可以从别的地方翻跃过去。同时我也看到有一帮人从这些士兵可见的范围内进行翻跃围墙。有一种说不出来的心情充上心头,但感觉在我们体制下好多类似的事情发生,从而又感叹了一些国家怎么样才能提高全民素质等等事情~~~~ 我可不想去翻墙,我还是回寺庙再去看看,同时去喝些泉水,也好久没有去泉眼了。今天泉水非常多,有一位好心的兄弟给帮我搞了一水袋。我便带着回家了。 小结不知道下次什么时候才能再进行穿越,希望放开这块路线供大家穿越。","categories":[{"name":"生活","slug":"生活","permalink":"http://example.com/categories/%E7%94%9F%E6%B4%BB/"}],"tags":[{"name":"人生感悟","slug":"人生感悟","permalink":"http://example.com/tags/%E4%BA%BA%E7%94%9F%E6%84%9F%E6%82%9F/"},{"name":"运动","slug":"运动","permalink":"http://example.com/tags/%E8%BF%90%E5%8A%A8/"},{"name":"爬山","slug":"爬山","permalink":"http://example.com/tags/%E7%88%AC%E5%B1%B1/"}]},{"title":"图片处理环境构建","slug":"图片处理环境构建","date":"2016-09-12T08:30:53.000Z","updated":"2022-09-03T10:12:16.076Z","comments":true,"path":"2016/09/12/图片处理环境构建/","link":"","permalink":"http://example.com/2016/09/12/%E5%9B%BE%E7%89%87%E5%A4%84%E7%90%86%E7%8E%AF%E5%A2%83%E6%9E%84%E5%BB%BA/","excerpt":"","text":"问题对存储的文件进行并发处理,由于处理的图片量比较大。图片处理又比较耗内存,为防止影响正常的服务,故将其与业务服务分开部署 方案python+uwsgi+imagemagick 环境安装:1.配置 16Core,16G内存,50G硬盘2.端口 13814(fcp),8010(http)3.安装 graphicsmagick jasper-1.900.1.zip jpegsrc.v9a.tar.gzwget http://www.imagemagick.org/download/delegates/jpegsrc.v9a.tar.gzwget http://www.ece.uvic.ca/~frodo/jasper/software/jasper-1.900.1.zipgm 命令ln /usr/local/graphicsmagick/bin/gm usr/bin/gm4.装音频插件和字体http://my.oschina.net/ethan09/blog/372435?fromerr=WsZSucMx安装微软雅黑字体5.python版本升级http://blog.csdn.net/jcjc918/article/details/11022345 由于django必须要用2.7,所以这里要进行升级注意修改完后 要修改/usr/bin/supervisord,/usr/bin/supervisorctl 的头为/usr/bin/python2.66.重新安装pip和c++库wget https://bootstrap.pypa.io/get-pip.pypython get-pip.py安装c++库sudo yum install GraphicsMagick-c++-develsudo yum install boost-devel7.安装python插件sudo pip install -r requirements.txtrequirements.txt内容Django==1.9.7beautifulsoup4==4.4.1protobuf==2.6.1pgmagick==0.6.2uWSGI==2.0.13.1wsgiref==0.1.2django-log-request-id==1.0.08.安装nginxyum install nginx9.安装uwsgisudo yum install uwsgisudo yum install uwsgi-plugin-pythonnginx配置location / { uwsgi_pass unix:///tmp/uwsgi.sock; include /etc/nginx/uwsgi_params; # the uwsgi_params file you installed }client_max_body_size 20M;10.安装图片处理服务cd /opt/fs/git clone http://git.firstshare.cn/Qixin/FSPythonWSGIProcess.gitcd FSPythonWSGIProcessuwsgi --ini online_uwsgi.ini --buffer-size 20971520 --daemonize ./logs/uwsgi.log11.异常*** Starting uWSGI 2.0.12 (64bit) on [Tue Jun 14 16:36:20 2016] ***compiled with version: 4.4.7 20120313 (Red Hat 4.4.7-16) on 02 January 2016 19:53:13os: Linux-2.6.32-573.8.1.el6.x86_64 #1 SMP Tue Nov 10 18:01:38 UTC 2015nodename: vlnx160170.fsceshi.commachine: x86_64clock source: unixpcre jit disableddetected number of CPU cores: 4current working directory: /home/wans/FSPythonWSGIProcesswriting pidfile to /tmp/FsPythonWSGIProcess.piddetected binary path: /usr/sbin/uwsgiyour processes number limit is 1024your memory page size is 4096 bytesdetected max file descriptor number: 60000lock engine: pthread robust mutexesthunder lock: disabled (you can enable it with --thunder-lock)uwsgi socket 0 bound to UNIX address /tmp/uwsgi.sock fd 4your server socket listen backlog is limited to 100 connectionsyour mercy for graceful operations on workers is 60 secondsmapped 1476277 bytes (1441 KB) for 10 cores*** Operational MODE: preforking ****** no app loaded. going in full dynamic mode ***关闭nginx ,启动uwsgi后再启动nginx优化点:1.调整nginxworker_processes 4;worker_cpu_affinity 1000 0100 0010 0001;2.#clusterupstream backend{server unix:///tmp/uwsgi.sock;server unix:///tmp/uwsgi1.sock;server unix:///tmp/uwsgi2.sock;server unix:///tmp/uwsgi3.sock;}location / { include uwsgi_params;uwsgi_pass backend; } 问题总结:1.yum安装遇到如下类似问题: yum install uwsgi Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile * base: mirrors.btte.net * extras: mirrors.btte.net * updates: mirrors.163.com No package uwsgi available. Error: Nothing to do 解决方案,添加epl至yum的源即可: yum install http://mirrors.isu.net.sa/pub/fedora/fedora-epel/7/x86_64/e/epel-release-7-6.noarch.rpm 参考文档:https://fedoraproject.org/wiki/EPEL/zh-cn","categories":[{"name":"方案","slug":"方案","permalink":"http://example.com/categories/%E6%96%B9%E6%A1%88/"}],"tags":[{"name":"运维","slug":"运维","permalink":"http://example.com/tags/%E8%BF%90%E7%BB%B4/"},{"name":"nginx","slug":"nginx","permalink":"http://example.com/tags/nginx/"},{"name":"fastcgi","slug":"fastcgi","permalink":"http://example.com/tags/fastcgi/"},{"name":"图片处理","slug":"图片处理","permalink":"http://example.com/tags/%E5%9B%BE%E7%89%87%E5%A4%84%E7%90%86/"}]},{"title":"我的音乐目录","slug":"我的音乐目录","date":"2016-01-09T16:00:00.000Z","updated":"2022-09-03T10:12:16.076Z","comments":true,"path":"2016/01/10/我的音乐目录/","link":"","permalink":"http://example.com/2016/01/10/%E6%88%91%E7%9A%84%E9%9F%B3%E4%B9%90%E7%9B%AE%E5%BD%95/","excerpt":"","text":"Django 当我老了 朋友 冬季到台北来看你","categories":[{"name":"生活","slug":"生活","permalink":"http://example.com/categories/%E7%94%9F%E6%B4%BB/"}],"tags":[{"name":"音乐","slug":"音乐","permalink":"http://example.com/tags/%E9%9F%B3%E4%B9%90/"}]},{"title":"将amr,caf转mp3","slug":"将amr,caf转mp3","date":"2015-12-26T16:00:00.000Z","updated":"2022-09-03T10:12:16.076Z","comments":true,"path":"2015/12/27/将amr,caf转mp3/","link":"","permalink":"http://example.com/2015/12/27/%E5%B0%86amr,caf%E8%BD%ACmp3/","excerpt":"","text":"需求 公司原有文件存储现在要用java进行重构,其中涉及到Android和ios上的音频问题。 Android录音是格式是amr,在电脑上一般是播放不出来的,必须要进行转码。 iPhone录音传上来的是caf,在android上是不能进行播放的。 由于上面的问题,我们要进行文件的转换,另外还有一个需求就是,amr转wav ,这里我们将amr和caf统一转换为map3,另外提供一个接口进行amr2wav的转换。 在网上参考了许多,总结下来还都是调用ffmpeg 进行想着音频的转换。总也来说比较简单。这里时间的关系 ,也不可能去研究底层编解码的东西。 开工安装ffmpeg 参考http://my.oschina.net/ethan09/blog/372435 查看当前ffmpeg对mp3的编解码支持情况 ffmpeg -codecs|grep mp3 D.A.L. mp3 MP3 (MPEG audio layer 3) (decoders: mp3 mp3float ) D.A.L. mp3adu ADU (Application Data Unit) MP3 (MPEG audio layer 3) (decoders: mp3adu mp3adufloat ) D.A.L. mp3on4 MP3onMP4 (decoders: mp3on4 mp3on4float ) 问题 ffmpeg: error while loading shared libraries: libavdevice.so.53: cannot open shared object file: No such file or directory ffmpeg正常安装后执行ffmpeg时出现如下错误: 解决办法: vi /etc/ld.so.conf 加入:/usr/local/lib 执行ldconfig ffmpeg -i test.amr test.mp3ffmpeg: error while loading shared libraries: libavdevice.so.56: cannot open shared object file: No such file or directory <http://www.tjcarroll.org/?p=51>","categories":[{"name":"方案","slug":"方案","permalink":"http://example.com/categories/%E6%96%B9%E6%A1%88/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"音频转换","slug":"音频转换","permalink":"http://example.com/tags/%E9%9F%B3%E9%A2%91%E8%BD%AC%E6%8D%A2/"},{"name":"arm","slug":"arm","permalink":"http://example.com/tags/arm/"},{"name":"mp3","slug":"mp3","permalink":"http://example.com/tags/mp3/"}]},{"title":"FASTDFS","slug":"FASTFS","date":"2015-12-07T16:00:00.000Z","updated":"2022-09-03T10:12:16.076Z","comments":true,"path":"2015/12/08/FASTFS/","link":"","permalink":"http://example.com/2015/12/08/FASTFS/","excerpt":"","text":"参考 基本介绍","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"分布式","slug":"分布式","permalink":"http://example.com/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"},{"name":"存储","slug":"存储","permalink":"http://example.com/tags/%E5%AD%98%E5%82%A8/"},{"name":"fastfs","slug":"fastfs","permalink":"http://example.com/tags/fastfs/"}]},{"title":"git工作与学习","slug":"git工作与学习","date":"2015-12-07T16:00:00.000Z","updated":"2022-09-03T10:12:16.076Z","comments":true,"path":"2015/12/08/git工作与学习/","link":"","permalink":"http://example.com/2015/12/08/git%E5%B7%A5%E4%BD%9C%E4%B8%8E%E5%AD%A6%E4%B9%A0/","excerpt":"","text":"tag的作用与学习 git tag ‘name’ -m ‘desc’ 创建 git tag -d ‘name’ 删除 git tag -l 查看 git push –tags 提交 git push origin :refs/tags/tags_name git 在使用项目中的实践模型 master release-6.1 develop hotfix-xxx release-release-6.1-xx 批量删除taggit tag |grep -v ‘v7.2.25-log_report_v9-20200909’ | xargs -I {} git tag -d {}","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"经验","slug":"经验","permalink":"http://example.com/tags/%E7%BB%8F%E9%AA%8C/"},{"name":"工具","slug":"工具","permalink":"http://example.com/tags/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"git","permalink":"http://example.com/tags/git/"},{"name":"代码仓库","slug":"代码仓库","permalink":"http://example.com/tags/%E4%BB%A3%E7%A0%81%E4%BB%93%E5%BA%93/"}]},{"title":"redis致命错误的出现","slug":"redis致命错误的出现","date":"2015-11-21T16:00:00.000Z","updated":"2022-09-03T10:12:16.076Z","comments":true,"path":"2015/11/22/redis致命错误的出现/","link":"","permalink":"http://example.com/2015/11/22/redis%E8%87%B4%E5%91%BD%E9%94%99%E8%AF%AF%E7%9A%84%E5%87%BA%E7%8E%B0/","excerpt":"","text":"#错误描述我们将文件缓存到redis,但是在线上出现了一个问题,就是A企业群中发的文件,在B企业群中看文件时却看到了A企业群中的图片。 #经排查,结果如下: ##案例代码如下: import com.google.common.collect.Lists; import org.apache.commons.lang3.StringUtils; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.*; /** * Created by Aaron on 15/11/12. */ public class FileRedisFactory { private static Logger log = LoggerFactory.getLogger(FileRedisFactory.class); public ThreadPoolExecutor threadPoolExecutor; // 格式为 : 127.0.0.1:6379#0;127.0.0.2:6379#1 IP:PORT#index private String hosts; private String password; private int size; private List<JedisPool> pools=Lists.newArrayList();//连接池 public FileRedisFactory(String hosts, String password) { this.hosts = hosts; this.password = password; this.init(hosts, password); } public FileRedisFactory(String hosts) { this.hosts = hosts; this.init(hosts, null); } private synchronized void init(String hosts, String password) { if (StringUtils.isNotEmpty(hosts)) { String[] hostsArray = hosts.split(";"); List<String> hostList = Arrays.asList(hostsArray); Collections.sort(hostList); System.out.println(hostList); for (String host : hostList) { try { String[] uriDb=host.split("#"); String[] host_port = uriDb[0].split(":"); String ip = host_port[0]; String port = host_port[1]; int index=uriDb.length>0?Integer.valueOf(uriDb[1]):0; GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); poolConfig.setMaxTotal(16); poolConfig.setMaxIdle(16); if (StringUtils.isNotEmpty(password)) { pools.add(new JedisPool(poolConfig, ip, Integer.valueOf(port), 2000, password, index)); } else { pools.add(new JedisPool(poolConfig, ip, Integer.valueOf(port), 2000, null, index)); } } catch (Exception e) { pools = Lists.newArrayList(); e.printStackTrace(); log.error("[ShardedRedisFactory] [initByShard] [error] [hosts:" + hosts + "] [password:" + password + "]", e); } } size = pools.size(); } this.threadPoolExecutor = new ThreadPoolExecutor(10,20,3, TimeUnit.MINUTES,new LinkedBlockingDeque<>(),new ThreadPoolExecutor.DiscardPolicy()); // 构造池 log.info("[ShardedRedisFactory] [initByShard] [success] [hosts:{}] [password:{}] [masterName:{}] [pool:{}]", hosts, password); } private JedisPool getJedisPool(int c) { int index = c % size; return pools.get(index); } private static int getIndexFromPath(String path) { String first=path.split("\\\\.")[0]; return first.charAt(first.length() - 1); } /** * @param filePath file path * @return */ public byte[] get(String filePath) { if (cacheIsAvailable()) return null; JedisPool jedisPool = getJedisPool(getIndexFromPath(filePath)); Jedis jedis = null; try { jedis = jedisPool.getResource(); return jedis.get(filePath.getBytes()); } catch (Exception e) { log.error(e.getMessage()); return null; } finally { if (jedis != null) jedisPool.returnResource(jedis); } } public void asynSet(String filePath, byte[] datas){ if (cacheIsAvailable()) return; threadPoolExecutor.execute(()->{ JedisPool jedisPool = getJedisPool(getIndexFromPath(filePath)); Jedis jedis = null; try { jedis = jedisPool.getResource(); jedis.set(filePath.getBytes(), datas); } catch (Exception e) { log.error(e.getMessage()); } finally { if (jedis != null) jedisPool.returnResource(jedis); } }); } private boolean cacheIsAvailable() { if(pools.size()==0){ return true; } return false; } } ##测试代码如下 public static void main(String[] args) { FileRedisFactory fileRedisFactory = new FileRedisFactory("172.31.xxx.xxx:6379#0;172.31.xx.xxx:6379#1;172.31.xxx.xxx:6379#2;172.31.xxx.xxx:6379#3"); System.out.println(fileRedisFactory); Map<String,String> map=new HashMap<>(); for(int i=0;i<20;i++){ map.put(""+i,""+i); } for (int j = 0; j <50 ; j++) { Thread thread=new Thread(){ @Override public void run() { while(true){ Set<String> s=map.keySet(); s.forEach(i->{ fileRedisFactory.set(i + "", (i + "").getBytes()); }); } } }; thread.start(); } for (int k = 0; k <50 ; k++) { Thread thread=new Thread(){ @Override public void run() { while(true){ Set<String> s=map.keySet(); s.forEach(i->{ byte[] first=fileRedisFactory.get(i+""); if(first==null){ System.out.println(i+""+first); return; } try{ String temp=(new String(first)); if(!temp.equals(i+"")){ System.out.println(i+"======"+temp); System.exit(-1); }}catch(Exception e){ e.printStackTrace(); } }); } } }; thread.start(); } } 若你和连接到redis服务器之间的网络不是很稳定在运行时,我们会发现,有奇怪的现像出现: 1======2 3======8 这时,我们就在对redis操作时,异常的地方修改成如下代码: catch (Exception e) { log.error(e.getMessage()); e.printStackTrace(); return null; } 就会看到报TimeOut的Exception 我们将: <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.6.2</version> </dependency> 替换成: <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.7.3</version> </dependency> 这时我们去Google上查询的同时也看了一下新的代码, 我们原来用的: jedisPool.returnResource(jedis); 已经变成了: /** * @deprecated starting from Jedis 3.0 this method won't exist. Resouce cleanup should be done * using @see {@link redis.clients.jedis.Jedis#close()} */ @Deprecated public void returnResource(final Jedis resource) { if (resource != null) { try { resource.resetState(); returnResourceObject(resource); } catch (Exception e) { returnBrokenResource(resource); throw new JedisException("Could not return the resource to the pool", e); } } } 同时我们也查到了一个文章JedisPool异常Jedis链接处理 #修改后的代码: import com.google.common.collect.Lists; import org.apache.commons.lang3.StringUtils; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.*; /** * Created by Aaron on 15/11/12. */ public class FileRedisFactory { private static Logger log = LoggerFactory.getLogger(FileRedisFactory.class); public ThreadPoolExecutor threadPoolExecutor; // 格式为 : 127.0.0.1:6379#0;127.0.0.2:6379#1 IP:PORT#index private String hosts; private String password; private int size; private List<JedisPool> pools=Lists.newArrayList();//连接池 public FileRedisFactory(String hosts, String password) { this.hosts = hosts; this.password = password; this.init(hosts, password); } public FileRedisFactory(String hosts) { this.hosts = hosts; this.init(hosts, null); } private synchronized void init(String hosts, String password) { if (StringUtils.isNotEmpty(hosts)) { String[] hostsArray = hosts.split(";"); List<String> hostList = Arrays.asList(hostsArray); Collections.sort(hostList); System.out.println(hostList); for (String host : hostList) { try { String[] uriDb=host.split("#"); String[] host_port = uriDb[0].split(":"); String ip = host_port[0]; String port = host_port[1]; int index=uriDb.length>0?Integer.valueOf(uriDb[1]):0; GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); poolConfig.setMaxTotal(16); poolConfig.setMaxIdle(16); if (StringUtils.isNotEmpty(password)) { pools.add(new JedisPool(poolConfig, ip, Integer.valueOf(port), 2000, password, index)); } else { pools.add(new JedisPool(poolConfig, ip, Integer.valueOf(port), 2000, null, index)); } } catch (Exception e) { pools = Lists.newArrayList(); log.error("[ShardedRedisFactory] [initByShard] [error] [hosts:" + hosts + "] [password:" + password + "]", e); } } size = pools.size(); } this.threadPoolExecutor = new ThreadPoolExecutor(10,20,3, TimeUnit.MINUTES,new LinkedBlockingDeque<>(50),new ThreadPoolExecutor.DiscardPolicy()); // 构造池 log.info("[ShardedRedisFactory] [initByShard] [success] [hosts:{}] [password:{}] [masterName:{}] [pool:{}]", hosts, password); } private JedisPool getJedisPool(int c) { int index = c % size; return pools.get(index); } private static int getIndexFromPath(String path) { String first=path.split("\\\\.")[0]; return first.charAt(first.length() - 1); } /** * @param filePath file path * @return */ public byte[] get(String filePath) { if (cacheIsAvailable()) return null; JedisPool jedisPool = getJedisPool(getIndexFromPath(filePath)); Jedis jedis = null; try { jedis = jedisPool.getResource(); return jedis.get(filePath.getBytes()); } catch (Exception e) { log.error(e.getMessage()); return null; } finally { if (jedis != null) jedis.close(); } } public String set(String filePath, byte[] datas) { if (cacheIsAvailable()) return null; JedisPool jedisPool = getJedisPool(getIndexFromPath(filePath)); Jedis jedis = null; try { jedis = jedisPool.getResource(); return jedis.set(filePath.getBytes(), datas); } catch (Exception e) { log.error(e.getMessage()); return null; } finally { if (jedis != null) jedis.close(); } } public void asynSet(String filePath, byte[] datas,long expx){ if (cacheIsAvailable()) return; threadPoolExecutor.execute(()->{ JedisPool jedisPool = getJedisPool(getIndexFromPath(filePath)); Jedis jedis = null; try { jedis = jedisPool.getResource(); jedis.set(filePath.getBytes(), datas,"NX".getBytes(),"EX".getBytes(),expx); } catch (Exception e) { log.error(e.getMessage()); } finally { if (jedis != null) jedis.close(); } }); } private boolean cacheIsAvailable() { if(pools.size()==0){ return true; } return false; } } #总结: 在使用做新功能时,方便时要添加开关或合理的回滚方案,方便快速的回滚减少事故的影响 排查错误,一查到底","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"},{"name":"问题总结","slug":"技术/问题总结","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/"}],"tags":[{"name":"bug","slug":"bug","permalink":"http://example.com/tags/bug/"},{"name":"缓存","slug":"缓存","permalink":"http://example.com/tags/%E7%BC%93%E5%AD%98/"},{"name":"redis","slug":"redis","permalink":"http://example.com/tags/redis/"}]},{"title":"Ansible","slug":"ansible的安装与使用","date":"2015-10-29T16:00:00.000Z","updated":"2022-09-03T10:12:16.076Z","comments":true,"path":"2015/10/30/ansible的安装与使用/","link":"","permalink":"http://example.com/2015/10/30/ansible%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8/","excerpt":"","text":"#ansible apt-get install ansible","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"运维","slug":"运维","permalink":"http://example.com/tags/%E8%BF%90%E7%BB%B4/"},{"name":"ansible","slug":"ansible","permalink":"http://example.com/tags/ansible/"}]},{"title":"JMeter","slug":"jmeter进行压力测试","date":"2015-10-09T16:00:00.000Z","updated":"2022-09-03T10:12:16.076Z","comments":true,"path":"2015/10/10/jmeter进行压力测试/","link":"","permalink":"http://example.com/2015/10/10/jmeter%E8%BF%9B%E8%A1%8C%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95/","excerpt":"","text":"##JMeter是用来做什么的 是一个Apache的一个开源程序,一个100%的针对压力模块和功能压力测试纯Java应用,最初是专门为Web应用程序而设计 的,但是目前已经扩展到别的功能 测试。 能做什么? 用来测试动态 和 静态资源(Webservices(SOAP/REST),Web dynamic languages -PHP,JAva,ASP.NET,Files,etc.Jav) ##如何本地进行服务压力测试 在lib下建一个文件夹,如tests,将所有自己用到的jar,放到这个文件夹中。 将自己的测试jar放到 lib/ext下面 将自己的程序依赖的jar文件夹,配置到配置文件user.classpath=../lib/tests ##如何分发到不同的机器进行压力测试 参考官方文档http://jmeter.apache.org/usermanual/jmeter_distributed_testing_step_by_step.pdf ##聚合分析 Label:每个 JMeter 的 element(例如 HTTP Request)都有一个 Name 属性,这里显示的就是 Name 属性的值 #Samples:表示你这次测试中一共发出了多少个请求,如果模拟10个用户,每个用户迭代10次,那么这里显示100 Average:平均响应时间——默认情况下是单个 Request 的平均响应时间,当使用了 Transaction Controller 时,也可以以Transaction 为单位显示平均响应时间 Median:中位数,也就是 50% 用户的响应时间 90% Line:90% 用户的响应时间 Note:关于 50% 和 90% 并发用户数的含义,请参考下文 http://www.cnblogs.com/jackei/archive/2006/11/11/557972.html Min:最小响应时间 Max:最大响应时间 Error%:本次测试中出现错误的请求的数量/请求的总数 Throughput:吞吐量——默认情况下表示每秒完成的请求数(Request per Second),当使用了 Transaction Controller 时,也可以表示类似 LoadRunner 的 Transaction per Second 数 KB/Sec:每秒从服务器端接收到的数据量,相当于LoadRunner中的Throughput/Sec ###问题 ####Jmeter-server启动失败:Cannot start. Unable to get local host IP address. is a loopback address 在Windows下启动Jmeter非常顺利,转到Linux下居然启动失败。 想起之前 遇到“/etc/hosts文件设置不对导致Jboss启动失败”, 立马把焦点指向/etc/hosts。 果然还是这个问题,贴/etc/hosts示例: 127.0.0.1 localhost.localdomain localhost 10.20.10.31 higkoo.rdev.company.net higkoo 执行命令`hostname`查看当前机器名如果当前机器名与/etc/hosts不一致 ,可手动先执行成一次`hostname yourhostname`或直接加到jmeter-server文件中(注意机器名中不要含域信息,譬如:myname.rdev.company.com。这样设置仍然启动失败)。 由/etc/hosts文件导致启动失败的错误有: 1 Created remote object: UnicastServerRef [liveRef: [endpoint:[10.20.10.31:62090](local),objID:[2c639f6d:12794fca52a:-7fff, 712947915258586677]]] Server failed to start: java.rmi.RemoteException: Cannot start. higkoo is a loopback address. 2 Created remote object: UnicastServerRef [liveRef: [endpoint:[10.20.10.31:38796](local),objID:[-b0d822e:12794fee8b1:-7fff, 8314597152635832475]]] Server failed to start: java.rmi.RemoteException: Cannot start. Unable to get local host IP address.","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"测试","slug":"测试","permalink":"http://example.com/tags/%E6%B5%8B%E8%AF%95/"},{"name":"性能测试","slug":"性能测试","permalink":"http://example.com/tags/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/"},{"name":"压力测试","slug":"压力测试","permalink":"http://example.com/tags/%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95/"},{"name":"jmeter","slug":"jmeter","permalink":"http://example.com/tags/jmeter/"}]},{"title":"zookeeper学习及项目实践","slug":"zookeeper学习及项目实战","date":"2015-09-24T16:00:00.000Z","updated":"2022-09-03T10:12:16.075Z","comments":true,"path":"2015/09/25/zookeeper学习及项目实战/","link":"","permalink":"http://example.com/2015/09/25/zookeeper%E5%AD%A6%E4%B9%A0%E5%8F%8A%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/","excerpt":"","text":"ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. 。 它设计一种新的数据结构——Znode,然后在该数据结构的基础上定义了一些原语,也就是一些关于该数据结构的一些操作。有了这些数据结构和原语还不够,因为我们的ZooKeeper是工作在一个分布式的环境下,我们的服务是通过消息以网络的形式发送给我们的分布式应用程序,所以还需要一个通知机制——Watcher机制。那么总结一下,ZooKeeper所提供的服务主要是通过:数据结构+原语+watcher机制,三个部分来实现的。 使用场景下会使用zookeeper 项目中在监控mongodb的oplog来进行同步数据库的变更给别的部门.若想做的多机互备,就需要使用到分布式锁,由一台机器进行对oplog的变化进行同步 项目在定时发送提醒,多台服务器进行周期扫库操作,也同样用到了1中的分布式锁 假设我们有20个搜索引擎的服务器(每个负责总索引中的一部分的搜索任务)和一个总服务器(负责向这20个搜索引擎的服务器发出搜索请求并合并结果集),一个备用的总服务器(负责当总服务器宕机时替换总服务器),一个web的cgi(向总服务器发出搜索请求)。搜索引擎的服务器中的15个服务器提供搜索服务,5个服务器正在生成索引。这20个搜索引擎的服务器经常要让正在提供搜索服务的服务器停止提供服务开始生成索引,或生成索引的服务器已经把索引生成完成可以提供搜索服务了。使用Zookeeper可以保证总服务器自动感知有多少提供搜索引擎的服务器并向这些服务器发出搜索请求,当总服务器宕机时自动启用备用的总服务器.—分布式系统协调 ZooKeeper zookeeper来源是什么?ZooKeeper是一种为分布式应用所设计的高可用、高性能且一致的开源协调服务,它提供了一项基本服务:分布式锁服务。由于ZooKeeper的开源特性,后来我们的开发者在分布式锁的基础上,摸索了出了其他的使用方法:配置维护、组服务、分布式消息队列、分布式通知/协调等。 协议 每个Server在内存中存储了一份数据; Zookeeper启动时,将从实例中选举一个leader(Leader选举算法采用了Paxos协议;Paxos核心思想:当多数Server写成功,则任务数据写成功。故 Server数目一般为奇数); Leader负责处理数据更新等操作(Zab协议); 一个更新操作成功,当且仅当大多数Server在内存中成功修改数据。 结构组成 Leader 负责进行投票的发起和决议,更新系统状态 Learner–>跟随者Follower 用于接收客户请求并向客户端返回结果,在选择中参与投票 Learner–>观察者Observer 可以接收客户端连接,将写请求转发给Leader节点,但其不参悟投票过程,只同步Leader的状态.其目的是为了扩展系统,提高读取速度 client 请求发起方 具体用在哪里 配置管理,一处修改,监听者进行更新 命名服务 分布式锁 即 Leader Election 集群管理 队列管理 Zookeeper文件系统 每个子目录项如 NameService 都被称作为znode,和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。 有四种类型的znode: PERSISTENT-持久化目录节点:客户端与zookeeper断开连接后,该节点依旧存在 PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点:客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号 EPHEMERAL-临时目录节点:客户端与zookeeper断开连接后,该节点被删除 EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点:客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号 Zookeeper的特点 最终一致性:为客户端展示同一视图,这是zookeeper最重要的功能。 可靠性:如果消息被到一台服务器接受,那么它将被所有的服务器接受。 实时性:Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。 等待无关(wait-free):慢的或者失效的client不干预快速的client的请求。 原子性:更新只能成功或者失败,没有中间状态。 顺序性:所有Server,同一消息发布顺序一致。 场景分析 分布式锁的场景使用 zkCli.sh -server x.x.x.x:4180ls /key>[data, leader][zk: x.x.x.x:4180(CONNECTED) 6] get /key/leadercZxid = 0xc1098cd0b0ctime = Sun Jul 16 13:10:01 CST 2017mZxid = 0xc1098cd0b0mtime = Sun Jul 16 13:10:01 CST 2017pZxid = 0xc112aec1c0cversion = 152dataVersion = 0aclVersion = 0ephemeralOwner = 0x0dataLength = 0numChildren = 2[zk: x.x.x.x:4180(CONNECTED) 7] ls /key/leader[_c_7ea9234d-3973-4e1d-8a6a-e2e30062cdc4-latch-0000000076, _c_5444e12a-c7ef-48bb-8ee6-271eea4a1c29-latch-0000000075][zk: x.x.x.x:4180(CONNECTED) 8] get /key/leader/_c_7ea9234d-3973-4e1d-8a6a-e2e30062cdc4-latch-000000007624cZxid = 0xc112aec1c0ctime = Fri Mar 30 16:58:50 CST 2018mZxid = 0xc112aec1c0mtime = Fri Mar 30 16:58:50 CST 2018pZxid = 0xc112aec1c0cversion = 0dataVersion = 0aclVersion = 0ephemeralOwner = 0xd5848ddc5ec71f6dataLength = 2numChildren = 0[zk: x.x.x.x:4180(CONNECTED) 9] get /key/leader/_c_5444e12a-c7ef-48bb-8ee6-271eea4a1c29-latch-00000000755cZxid = 0xc1123e0f90ctime = Tue Mar 27 10:55:03 CST 2018mZxid = 0xc1123e0f90mtime = Tue Mar 27 10:55:03 CST 2018pZxid = 0xc1123e0f90cversion = 0dataVersion = 0aclVersion = 0ephemeralOwner = 0x259977a5b1b3de0dataLength = 1numChildren = 0 经典文章链接zookeeper系列Leader选举基本概念","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"分布式","slug":"分布式","permalink":"http://example.com/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"},{"name":"zookeeper","slug":"zookeeper","permalink":"http://example.com/tags/zookeeper/"}]},{"title":"Protobuf是什么","slug":"Protobuf是什么","date":"2015-09-21T16:00:00.000Z","updated":"2022-09-03T10:12:16.075Z","comments":true,"path":"2015/09/22/Protobuf是什么/","link":"","permalink":"http://example.com/2015/09/22/Protobuf%E6%98%AF%E4%BB%80%E4%B9%88/","excerpt":"","text":"欢迎来到protocol buffer的开发者指南文档,一种语言无关、平台无关、扩展性好的用于通信协议、数据存储的结构化数据串行化方法。本文档面向希望使用protocol buffer的Java、C++或Python开发者。这个概览介绍了protocol buffer,并告诉你如何开始,你随后可以跟随编程指导深入了解protocol buffer编码方式。API参考文档同样也是提供了这三种编程语言的版本,不够协议语言和样式指导都是编写 .proto 文件。 什么是protocol bufferProtocolBuffer是用于结构化数据串行化的灵活、高效、自动的方法,有如XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。 他们如何工作你首先需要在一个 .proto 文件中定义你需要做串行化的数据结构信息。每个ProtocolBuffer信息是一小段逻辑记录,包含一系列的键值对。这里有个非常简单的 .proto 文件定义了个人信息: message Person { required string name=1; required int32 id=2; optional string email=3; enum PhoneType { MOBILE=0; HOME=1; WORK=2; } message PhoneNumber { required string number=1; optional PhoneType type=2 [default=HOME]; } repeated PhoneNumber phone=4;} 有如你所见,消息格式很简单,每个消息类型拥有一个或多个特定的数字字段,每个字段拥有一个名字和一个值类型。值类型可以是数字(整数或浮点)、布尔型、字符串、原始字节或者其他ProtocolBuffer类型,还允许数据结构的分级。你可以指定可选字段,必选字段和重复字段。你可以在proto.html找到更多关于如何编写 .proto 文件的信息。 一旦你定义了自己的报文格式(message),你就可以运行ProtocolBuffer编译器,将你的 .proto 文件编译成特定语言的类。 为什么不用XML?ProtocolBuffer拥有多项比XML更高级的串行化结构数据的特性,ProtocolBuffer: 更简单 小3-10倍 快20-100倍 更少的歧义 可以方便的生成数据存取类 例如,让我们看看如何在XML中建模Person的name和email字段: <person> <name>John Doe</name> <email>jdoe@example.com</email></person> 对应的ProtocolBuffer报文则如下: ProtocolBuffer的文本表示这不是正常时使用的二进制数据 person { name: "John Doe" email: "jdoe@example.com"} 当这个报文编码到ProtocolBuffer的二进制格式时(上面的文本仅用于调试和编辑),它只需要28字节和100-200ns的解析时间。而XML的版本需要69字节(除去空白)和 5000-10000ns的解析时间。","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"Protobuf","slug":"Protobuf","permalink":"http://example.com/tags/Protobuf/"}]},{"title":"设计模式之创建型模式","slug":"JAVA设计模式之创建型模式","date":"2015-09-12T16:00:00.000Z","updated":"2022-09-03T10:12:16.074Z","comments":true,"path":"2015/09/13/JAVA设计模式之创建型模式/","link":"","permalink":"http://example.com/2015/09/13/JAVA%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E5%88%9B%E5%BB%BA%E5%9E%8B%E6%A8%A1%E5%BC%8F/","excerpt":"","text":"##创建型模式这六个模式都是与创建对象相关的 简单工厂模式(Simple Factory); 工厂方法模式(Factory Method); 抽象工厂模式(Abstract Factory); 创建者模式(Builder); 原型模式(Prototype); 单例模式(Singleton); 简单工厂模式(Simple Factory);工厂方法模式(Factory Method);建立一个工厂类,对实现同一接口的类进行实例化创建。 `` package design.pattern.factory;/** * Created by Aaron on 15/9/13. */public interface IParser { String parse(Object obj);} package design.pattern.factory;/** * Created by Aaron on 15/9/13. */public class JSONParser implements IParser{ @Override public String parse(Object obj) { //create json string return "{class:"+obj.getClass()+"}"; }} package design.pattern.factory;/** * Created by Aaron on 15/9/13. */public class XMLParser implements IParser{ @Override public String parse(Object obj) { //create xml string.... return "<object><class>"+obj.getClass()+"</class></object>"; }} package design.pattern.factory;/** * Created by Aaron on 15/9/13. */public class ParserFactory { public static final String TYPE_XML="xml"; public static final String TYPE_JSON="json"; public static IParser buildParser(String type){ switch (type){ case ParserFactory.TYPE_XML:return new XMLParser(); case ParserFactory.TYPE_JSON:return new JSONParser(); } return null; } public static void main(String[] args){ IParser parser= ParserFactory.buildParser(ParserFactory.TYPE_JSON); System.out.print(parser.parse(parser)); }}//output {class:JSONParser} 抽象工厂模式(Abstract Factory);工厂方法,每创建一个新的类时,就要个性类工厂类,这样拓展性比较差,如何能通过不个性工厂类而进行扩展呢。这里就用到了抽象工厂模式,就是创建多个工厂,一旦要增加新的类型就增加一个新的工厂,不需要修改现有代码。 基于上面代码将ParserFactory工厂类用一个抽象工厂类和两个子工厂类进行代替 package design.pattern.abstractfactory;import design.pattern.factory.*;/** * Created by Aaron on 15/9/13. */public abstract class AbstractParserFactory { abstract IParser create();} package design.pattern.abstractfactory;/** * Created by Aaron on 15/9/13. */public class JSONParserFactory extends AbstractParserFactory { @Override IParser create() { return new JSONParser(); }} package design.pattern.abstractfactory;/** * Created by Aaron on 15/9/13. */public class XMLParserFactory extends AbstractParserFactory { @Override IParser create() { return new XMLParser(); }} 建造者模式(Builder);GoF这样定义: 建造者模式:是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 咱们这里以创建应用为例,这里我们创建两个应用,考试系统和CRM系统,创建过程是,需求->原型图->开发计划->表设计->架构设计->功能实现->测试->交付 大概是这样一个简单的过程,这里就会看到同样的构建过程得到不同的表示。 package design.pattern.builder;import java.util.Vector;/** * Created by Aaron on 15/9/13. */public class Project { private Vector<String> impleProcess=new Vector<String>(); public void process(String imple){ impleProcess.add(imple); } public Vector<String> getImpleProcess() { return impleProcess; }} package design.pattern.builder;/** * Created by Aaron on 15/9/13. */public interface IProjectBuilder { Project getProject(); void makeRequirement(); void makePrototype(); void makeScheduler(); void makeTables(); void makeAppFrameWork(); void programming(); void test(); void delivery();} package design.pattern.builder;/** * Created by Aaron on 15/9/13. */public class ExamProjectBuilder implements IProjectBuilder { private Project project; public ExamProjectBuilder() { this.project = new Project(); } public Project getProject() { return project; } @Override public void makeRequirement() { this.project.process("创建考试系统需求"); } @Override public void makePrototype() { this.project.process("创建考试原型"); } @Override public void makeScheduler() { this.project.process("创建考试计划"); } @Override public void makeTables() { this.project.process("创建考试系统表"); } @Override public void makeAppFrameWork() { this.project.process("创建考试应用架构"); } @Override public void programming() { this.project.process("考试应用代码实现"); } @Override public void test() { this.project.process("测试考试应用"); } @Override public void delivery() { this.project.process("交付考试应用"); }} package design.pattern.builder;/** * Created by Aaron on 15/9/13. */public class CRMProjectBuilder implements IProjectBuilder { public CRMProjectBuilder() { this.project = new Project(); } public Project getProject() { return project; } private Project project; @Override public void makeRequirement() { this.project.process("创建CRM系统需求"); } @Override public void makePrototype() { this.project.process("创建CRM原型"); } @Override public void makeScheduler() { this.project.process("创建CRM计划"); } @Override public void makeTables() { this.project.process("创建CRM系统表"); } @Override public void makeAppFrameWork() { this.project.process("创建CRM应用架构"); } @Override public void programming() { this.project.process("CRM应用代码实现"); } @Override public void test() { this.project.process("测试CRM应用"); } @Override public void delivery() { this.project.process("交付CRM应用"); }} package design.pattern.builder;/** * Created by Aaron on 15/9/13. */public class Director { private IProjectBuilder builder; public Director(IProjectBuilder builder){ this.builder=builder; } public Project process(){ this.builder.makeRequirement(); this.builder.makePrototype(); this.builder.makeScheduler(); this.builder.makeAppFrameWork(); this.builder.makeTables(); this.builder.programming(); this.builder.test(); this.builder.delivery(); return builder.getProject(); } public static void main(String[] args){ Director director = new Director(new CRMProjectBuilder()); Project project = director.process(); System.out.println(project.getImpleProcess()); }}//输出 [创建CRM系统需求, 创建CRM原型, 创建CRM计划, 创建CRM应用架构, 创建CRM系统表, CRM应用代码实现, 测试CRM应用, 交付CRM应用] 原型模式(Prototype);GoF这样定义: 用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。 package design.pattern.prototype;import java.io.*;import java.util.Vector;/** * Created by Aaron on 15/9/13. */public class DeepClonePrototype implements Cloneable,Serializable { public Vector<String> attrs=new Vector<String>(); public Object deepClone() throws CloneNotSupportedException, IOException, ClassNotFoundException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); return ois.readObject(); } public static void main(String[] args) throws Exception{ Object pro=new DeepClonePrototype().deepClone(); System.out.println(pro.getClass()); }} 单例模式(Singleton); 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 package design.pattern.singleton;/** * Created by Aaron on 15/9/11. */public class Singleton { private Singleton() { } private static Singleton instance = null; private static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { return instance = new Singleton(); } else { return instance; } } } else { return instance; } } public String getName() { return Singleton.class.getName(); } public static void main(String args[]) { Singleton instance = Singleton.getInstance(); System.out.print(instance.getName()); }}","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"设计模式","slug":"设计模式","permalink":"http://example.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"设计模式之结构型模式","slug":"JAVA设计模式之结构型模式","date":"2015-09-12T16:00:00.000Z","updated":"2022-09-03T10:12:16.075Z","comments":true,"path":"2015/09/13/JAVA设计模式之结构型模式/","link":"","permalink":"http://example.com/2015/09/13/JAVA%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E7%BB%93%E6%9E%84%E5%9E%8B%E6%A8%A1%E5%BC%8F/","excerpt":"","text":"##结构型模式 描述在面向对象设计中,类和对象的几种结构关系,设计好了会为后续代码的维护带来很大的方便。 外观模式(Facade); 适配器模式(Adapter); 代理模式(Proxy); 装饰模式(Decorator); 桥模式(Bridge); 组合模式(Composite); 享元模式(Flyweight); ###外观模式(Facade)又称门面模式;GoF这样定义: 为子系统中的一组接口提供一个一致的界面, Facade 模式定义了一个高层 接口,这个接口使得这一子系统更加容易使用。 外观模式,在我理解就是给一组对象提供一个对外统计的操作方式 ,让外部使用者不用操心内部工作。如一个汽车,一个电脑,你点开机,硬盘,CPU,内存,显卡就都开始工作了,关机时也一样。这里对我们操作者来说其实就是一个开关。 `` package design.pattern.facade;/** * Created by Aaron on 15/9/14. */public class User { public static void main(String[] args)throws InterruptedException{ Computer computer=new Computer(); computer.startup(); System.out.println("--------shutdown-----------"); computer.shutdown(); }} package design.pattern.facade;/** * Created by Aaron on 15/9/14. */public class Computer { private CPU cpu; private Memory memory; private GraphicsCard graphicsCard; private Disk disk; public Computer() { this.cpu = new CPU(); this.memory = new Memory(); this.graphicsCard = new GraphicsCard(); this.disk = new Disk(); } public void startup(){ this.cpu.startup(); this.memory.startup(); this.disk.startup(); this.graphicsCard.startup(); } public void shutdown(){ this.graphicsCard.shutdown(); this.disk.shutdown(); this.memory.shutdown(); this.cpu.shutdown(); }} package design.pattern.facade;/** * Created by Aaron on 15/9/14. */public class CPU { public void startup(){ System.out.println(this.getClass().getSimpleName()+"启动"); } public void shutdown(){ System.out.println(this.getClass().getSimpleName()+"关闭"); }} package design.pattern.facade;/** * Created by Aaron on 15/9/14. */public class Disk { public void startup(){ System.out.println(this.getClass().getSimpleName()+"启动"); } public void shutdown(){ System.out.println(this.getClass().getSimpleName()+"关闭"); }} package design.pattern.facade;/** * Created by Aaron on 15/9/14. */public class GraphicsCard { public void startup(){ System.out.println(this.getClass().getSimpleName()+"启动"); } public void shutdown(){ System.out.println(this.getClass().getSimpleName()+"关闭"); }} package design.pattern.facade;/** * Created by Aaron on 15/9/14. */public class Memory { public void startup(){ System.out.println(this.getClass().getSimpleName()+"启动"); } public void shutdown(){ System.out.println(this.getClass().getSimpleName()+"关闭"); }} 输出结果: CPU启动 Memory启动 Disk启动 GraphicsCard启动 --------shutdown----------- GraphicsCard关闭 Disk关闭 Memory关闭 CPU关闭 ###适配器模式(Adapter);GoF这样定义: 将一个类的接口转换成客户希望的另外一个接口。 Adapter 模式使得原本 由于接口不兼容而不能一起工作的那些类可以一起工作。 我的事例理解:如咱们家中常用的洗衣机,当我们要与我们的水龙头进行对接时,中间要借助一个中间者“转换头”,它在这里就起到了适配作用。 `` package design.pattern.adapter;/** * Created by Aaron on 15/9/14. */public class WashingMachine { public void connectPort(IWashFaucetAdapter washportadapter){ System.out.print(washportadapter.outToWashingPort()+" success!"); }} package design.pattern.adapter;/** * Created by Aaron on 15/9/14. */public interface IWashFaucetAdapter { String outToWashingPort();} package design.pattern.adapter;/** * Created by Aaron on 15/9/14. */public class WashingFaucetAdapter extends Faucet implements IWashFaucetAdapter{ public String outToWashingPort(){ return "transform"+this.port()+" to washing port!"; }} package design.pattern.adapter;/** * Created by Aaron on 15/9/14. * 水龙头 */public class Faucet { public String port(){ System.out.print("facucet port ....."); return "facucet port"; }} package design.pattern.adapter;/** * Created by Aaron on 15/9/14. */public class User { public static void main(String[] args){// 创建水龙头、洗衣机、镶接头 WashingMachine washingMachine=new WashingMachine(); WashingFaucetAdapter washingFaucetAdapter= new WashingFaucetAdapter();// 进行适配 washingMachine.connectPort(washingFaucetAdapter); }} 输出结果: facucet port .....transformfacucet port to washing port! success! ###代理模式(Proxy);GoF这样定义: 为其他对象提供一个代理以控制对这个对象的访问。 这里就以找工作为例吧,现在我们找工作都会通过找工作平台来进行找工作,因为他们有资源,他们比较专业。我们告诉他们要找什么样的工作他们就会给我们推荐什么样的工作,在这个环节中,类似51job,100offer这样的平台就是所谓的招聘代理。他代理公司进行招人。同时也方便了我们去找工作。 下面是代码实现: package design.pattern.proxy;/** * Created by Aaron on 15/9/14. */public interface IRecruitment { void recruitment(String user);} package design.pattern.proxy;/** * Created by Aaron on 15/9/14. */public class FounderWork implements IRecruitment{ public void recruitment(String user){ System.out.println(this.getClass().getSimpleName()+"招聘员工"+user+"成功!"); }} package design.pattern.proxy;/** * Created by Aaron on 15/9/14. */public class WorkProxy implements IRecruitment { private IRecruitment recruitment; public WorkProxy() { this.recruitment = new FounderWork(); } @Override public void recruitment(String user) { before(); this.recruitment.recruitment(user); after(); } public void before() { System.out.println(this.getClass().getSimpleName() + "进行招聘前工作准备!"); } public void after() { System.out.println(this.getClass().getSimpleName() + "进行招聘完成后工作收尾!"); }} package design.pattern.proxy;/** * Created by Aaron on 15/9/14. */public class User { public static void main(String[] args){ new WorkProxy().recruitment("Aaron"); }} 输出: WorkProxy进行招聘前工作准备! FounderWork招聘员工Aaron成功! WorkProxy进行招聘完成后工作收尾! ###装饰模式(Decorator);GoF这样定义: 动态地给一个对象添加一些额外的职责。就扩展功能而言, Decorator 模 式比生成子类方式更为灵活。 我们可以拿我们的扩音器为例,假如一个mp3的有声音,那么它的声音不是很大,稍微远一点我们就不能听到了,这里就会用一个扩音器,放在mp3旁边,离稍微远点也能享受音乐的快乐了。 这里,扩音器就是装饰器,他使mp3的声音变大。有时扩音器也可以改变声音的音质,变的更好听。 下面是代码实现: package design.pattern.decorator;/** * Created by Aaron on 15/9/14. */public interface ISoundable { void sound();} package design.pattern.decorator;/** * Created by Aaron on 15/9/14. */public class MP3 implements ISoundable{ public void sound(){ System.out.println("small sound from mp3!"); }} package design.pattern.decorator;/** * Created by Aaron on 15/9/14. */public class SoundDecorator implements ISoundable { private ISoundable soundable; public SoundDecorator(ISoundable soundable) { this.soundable = soundable; } public void sound(){ this.soundable.sound(); System.out.println("make sound beautiful"); System.out.println("make sound aloud "); }} package design.pattern.decorator;/** * Created by Aaron on 15/9/14. */public class User { public static void main(String[] args){ new SoundDecorator(new MP3()).sound(); }} 输出: small sound from mp3! make sound beautiful make sound aloud ###桥接模式(Bridge);GoF这样定义: 将抽象部分与它的实现部分分离,使它们都可以独立地变化。 这里还举一个生活中常用到的例子,洗衣机有多种,但我们当我们没有接到水龙头上的管子时,我们可以去商店里买,这里可能会有大小长短各不相同的管子,但都可以与我们的洗衣机相连接进行使用。 这里我们变化的是多种洗衣机和多种管子,我们为洗衣机做一个抽像类。可以设置不同的管子。 `` package design.pattern.bridge;/** * Created by Aaron on 15/9/14. */public interface IPip { String color();} package design.pattern.bridge;/** * Created by Aaron on 15/9/14. * 水龙头 */public class RedPip implements IPip{ public String color(){ return "Red"; }} package design.pattern.bridge;/** * Created by Aaron on 15/9/14. * 水龙头 */public class BluePip implements IPip{ public String color(){ return "blue pip"; }} package design.pattern.bridge;/** * Created by Aaron on 15/9/14. */public abstract class AbstractWashingMachine { private IPip pip; public IPip getPip() { return pip; } public void setPip(IPip pip) { this.pip = pip; System.out.println(this.getClass().getSimpleName()+" set "+pip.color()+" "+pip.getClass().getSimpleName()); }} package design.pattern.bridge;/** * Created by Aaron on 15/9/14. */public class ChinaWashingMachine extends AbstractWashingMachine {} package design.pattern.bridge;/** * Created by Aaron on 15/9/14. */public class HaierWashingMachine extends AbstractWashingMachine {} package design.pattern.bridge;/** * Created by Aaron on 15/9/14. */public class User { public static void main(String[] args){ new HaierWashingMachine().setPip(new BluePip()); new HaierWashingMachine().setPip(new RedPip()); new ChinaWashingMachine().setPip(new BluePip()); }} 输出: HaierWashingMachine set blue pip BluePip HaierWashingMachine set Red RedPip ChinaWashingMachine set blue pip BluePip ###组合模式(Composite);GoF这样定义: 将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使 得客户对单个对象和复合对象的使用具有一致性。 这里我们最常见的就是公司与部门的关系,其实就是整体与部分的关系。 代码 package design.pattern.composite;import java.util.Vector;/** * Created by Aaron on 15/9/14. */public abstract class AbstractCompany { private String name; private Vector<AbstractCompany> companys=new Vector<AbstractCompany>(); public String getName() { return name; } public void setName(String name) { this.name = name; } public void display(int deep) { StringBuilder sb=new StringBuilder(); for(int i=0;i<deep;i++){ sb.append("\\t"); } sb.append(this.getName()); System.out.println(sb.toString()); int l = this.getCompanys().size(); if (l > 0) { for (int i = 0; i < l; i++) { this.getCompanys().get(i).display(deep+2); } } } public Vector<AbstractCompany> getCompanys() { return companys; } public void removeCompany(AbstractCompany company){ this.companys.remove(company); } public void addCompany(AbstractCompany company) { this.companys.add(company); }} package design.pattern.composite;/** * Created by Aaron on 15/9/14. */public class Company extends AbstractCompany { public Company(String name) { this.setName(name); }} package design.pattern.composite;/** * Created by Aaron on 15/9/14. */public class TechDepartment extends AbstractCompany { public TechDepartment() { } public TechDepartment(String name) { this.setName(name); }} package design.pattern.composite;/** * Created by Aaron on 15/9/14. */public class UIDepartment extends AbstractCompany { public UIDepartment(String name) { this.setName(name); } public UIDepartment() { }} package design.pattern.composite;/** * Created by Aaron on 15/9/14. */public class CEO { public static void main(String[] args) { AbstractCompany company = new Company("总公司"); AbstractCompany abc = new TechDepartment("技术一部"); company.addCompany(abc); abc = new TechDepartment("技术二部"); company.addCompany(abc); abc = new TechDepartment("技术三部"); company.addCompany(abc); abc = new UIDepartment("UI一部"); company.addCompany(abc); abc = new UIDepartment("UI二部"); company.addCompany(abc); abc = new UIDepartment("UI三部"); company.addCompany(abc); AbstractCompany abc1 = new UIDepartment("UI一组"); abc.addCompany(abc1); abc1 = new UIDepartment("UI二组"); abc.addCompany(abc1); abc1 = new UIDepartment("UI三组"); abc.addCompany(abc1); company.display(0); }} 输出: 总公司 技术一部 技术二部 技术三部 UI一部 UI二部 UI三部 UI一组 UI二组 UI三组 ###享元模式(Flyweight)GoF这样定义: 运用共享技术有效地支持大量细粒度的对象。 咱们这里会想到数据库连接池,对,就是它,咱们先看一下类图。 `示例代码如下:` package design.pattern.flayweight;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.util.Vector;/** * Created by Aaron on 15/9/14. */public class ConnectionPool { private Vector<Connection> pool; private static ConnectionPool instance; private int poolSize=10; private String url = "jdbc:mysql://127.0.0.1:3306/mysql"; private String username = "root"; private String password = "root"; private String driverClassName = "com.mysql.jdbc.Driver"; private ConnectionPool(){ this.pool=new Vector<Connection>(); for (int i = 0; i < poolSize; i++) { try { Class.forName(driverClassName); pool.add(DriverManager.getConnection(url, username, password)); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } } public static synchronized ConnectionPool getInstance(){ if(instance==null){ return instance=new ConnectionPool(); } return instance; } public synchronized Connection getConnection(){ Connection connection=null; if(pool.size()>0){ connection=pool.get(0); pool.remove(connection); } return connection; } public synchronized void release(Connection conn){ pool.add(0,conn); }} package design.pattern.flayweight;import java.sql.Connection;/** * Created by Aaron on 15/9/14. */public class User { public static void main(String args[]){ ConnectionPool pool=ConnectionPool.getInstance(); Connection connection=pool.getConnection(); System.out.println(connection); connection=pool.getConnection(); System.out.println(connection); pool.release(connection); connection=pool.getConnection(); System.out.println(connection); }} 输出: com.mysql.jdbc.JDBC4Connection@2d8e6db6 com.mysql.jdbc.JDBC4Connection@23ab930d com.mysql.jdbc.JDBC4Connection@23ab930d","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"设计模式","slug":"设计模式","permalink":"http://example.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"JAVA设计模式之行为型模式","slug":"JAVA设计模式之行为型模式","date":"2015-09-12T16:00:00.000Z","updated":"2022-09-03T10:12:16.075Z","comments":true,"path":"2015/09/13/JAVA设计模式之行为型模式/","link":"","permalink":"http://example.com/2015/09/13/JAVA%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E8%A1%8C%E4%B8%BA%E5%9E%8B%E6%A8%A1%E5%BC%8F/","excerpt":"","text":"##行为型模式 对象的创建和结构定义好后,就是他们的行为的设计了。 模板方法模式(Template Method); 观察者模式(Observer); 状态模式(State); 策略模式(Strategy); 职责链模式(Chain of Responsibility); 命令模式(Command); 访问者模式(Visitor); 调停者模式(Mediator); 备忘录模式(Memento); 迭代器模式(Iterator); 解释器模式(Interpreter); 模板方法模式(Template Method);GoF这样定义: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 这里我们以画布上画画为例,我们定义一抽象类,其中定义一个渲染方法,渲染时有两个步骤,一个画背景,二能画主体,三加印章。咱们这里画一个圆和画一个矩形,抽象类中定义渲染时的先后流程,具体的实现有具体的子类进行实现。 代码如下: package design.pattern.temlatemothod;/** * Created by Aaron on 15/9/16. */public abstract class AbstractShape { public void render(){ this.drawBackground(); this.drawGraphics(); this.drawSignature(); } abstract void drawSignature(); abstract void drawBackground(); abstract void drawGraphics();} package design.pattern.temlatemothod;/** * Created by Aaron on 15/9/16. */public class CircleShape extends AbstractShape { @Override public void drawSignature() { System.out.println("draw circle signature!"); } @Override public void drawBackground() { System.out.println("draw circle background! "); } @Override public void drawGraphics() { System.out.println("draw circle graphics!"); }} package design.pattern.temlatemothod;/** * Created by Aaron on 15/9/16. */public class RectShape extends AbstractShape { @Override public void drawSignature() { System.out.println("draw rect signature!"); } @Override public void drawBackground() { System.out.println("draw rect background! "); } @Override public void drawGraphics() { System.out.println("draw rect graphics!"); }} package design.pattern.temlatemothod;/** * Created by Aaron on 15/9/16. */public class User { public static void main(String args[]){ new CircleShape().render(); System.out.println("-----"); new RectShape().render(); }} 输出结果: draw circle background! draw circle graphics! draw circle signature! ----- draw circle background! draw circle graphics! draw circle signature! 观察者模式(Observer);GoF这样定义: 定义对象间的一种一对多的依赖关系 , 以便当一个对象的状态发生改变时 , 所有依赖于它的对象都得到通知并自动刷新。 我们常常会遇到,当一个事件发生时,会有一些监听者进行相应的响应。这里我们的例子是, 当GPS发生变化时,它的订阅者的update的方法就会被调用。 `下面是示例代码:` package design.pattern.observer;import java.lang.reflect.Array;import java.util.ArrayList;/** * Created by Aaron on 15/9/16. */public abstract class Subject { private ArrayList<Observer> observers=new ArrayList<Observer>(); public void addObserver(Observer observer){ this.observers.add(observer); } public void removeObserver(Observer observer){ this.observers.remove(observer); } public void notifyObserver(){ for(Observer observer:observers){ observer.update(this); } }} package design.pattern.observer;import java.awt.*;/** * Created by Aaron on 15/9/16. */public class GPSSubject extends Subject { private Point point; public void move(Point point){ this.point=point; this.notifyObserver(); }} package design.pattern.observer;/** * Created by Aaron on 15/9/16. */public abstract class Observer { public Observer(){ } public abstract void update(Subject subject);} package design.pattern.observer;/** * Created by Aaron on 15/9/16. */public class MapObserver extends Observer { @Override public void update(Subject subject) { System.out.println(this.getClass().getSimpleName()+"_"+this.hashCode()+" observer:"+subject.getClass().getSimpleName()+" position changed;"); }} package design.pattern.observer;import java.awt.*;/** * Created by Aaron on 15/9/16. */public class User { public static void main(String[] args){ GPSSubject subject=new GPSSubject(); subject.addObserver(new MapObserver()); Observer observer1=null; subject.addObserver(observer1=new MapObserver()); subject.move(new Point(200, 400)); System.out.println("remove one observer from subject's observer list!"); subject.removeObserver(observer1); subject.move(new Point(200,400)); }} 状态模式(State); GoF这样定义: 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。 以下是示例代码: package design.pattern.state;/** * Created by Aaron on 15/9/20. */public class Context extends AbstractLifeState { public static OpeningState openingState = new OpeningState(); public static ClosingState closingState = new ClosingState(); public static RunningState runningState = new RunningState(); public static StoppingState stoppingState = new StoppingState(); private AbstractLifeState lifeState; public Context() { } public AbstractLifeState getLifeState() { return lifeState; } public void setLifeState(AbstractLifeState lifeState) { this.lifeState = lifeState; this.lifeState.setContext(this); } @Override public void open() { this.lifeState.open(); } @Override public void close() { this.lifeState.close(); } @Override public void run() { this.lifeState.run(); } @Override public void stop() { this.lifeState.stop(); }} package design.pattern.state;/** * Created by Aaron on 15/9/20. */public abstract class AbstractLifeState { protected Context context; public void setContext(Context context) { this.context = context; } public abstract void open(); public abstract void close(); public abstract void run(); public abstract void stop();} package design.pattern.state;/** * Created by Aaron on 15/9/20. */public class OpeningState extends AbstractLifeState { @Override public void open() { System.out.println(this.getClass().getSimpleName() + ": operate open"); } @Override public void close() { System.out.println(this.getClass().getSimpleName() + ": operate close"); this.context.setLifeState(Context.closingState); } @Override public void run() { System.out.println(this.getClass().getSimpleName() + ": operate run"); this.context.setLifeState(Context.runningState); } @Override public void stop() { System.out.println(this.getClass().getSimpleName()+": operate stop"); this.context.setLifeState(Context.stoppingState); }} package design.pattern.state;/** * Created by Aaron on 15/9/20. */public class RunningState extends AbstractLifeState { @Override public void open() { System.out.println(this.getClass().getSimpleName() + ": operate open"); context.setLifeState(Context.openingState); } @Override public void close() { System.out.println(this.getClass().getSimpleName()+": operate close"); context.setLifeState(Context.closingState); } @Override public void run() { System.out.println(this.getClass().getSimpleName()+": operate run"); } @Override public void stop() { System.out.println(this.getClass().getSimpleName()+": operate stop"); context.setLifeState(Context.stoppingState); }} package design.pattern.state;/** * Created by Aaron on 15/9/20. */public class StoppingState extends AbstractLifeState { @Override public void open() { System.out.println(this.getClass().getSimpleName() + ": operate open"); } @Override public void close() { System.out.println(this.getClass().getSimpleName()+": operate close"); } @Override public void run() { System.out.println(this.getClass().getSimpleName()+": operate run"); } @Override public void stop() { System.out.println(this.getClass().getSimpleName()+": operate stop"); }} package design.pattern.state;/** * Created by Aaron on 15/9/20. */public class ClosingState extends AbstractLifeState { @Override public void open() { System.out.println(this.getClass().getSimpleName() + ": operate open"); context.setLifeState(Context.openingState); } @Override public void close() { System.out.println(this.getClass().getSimpleName() + ": operate close"); } @Override public void run() { System.out.println(this.getClass().getSimpleName()+": operate run"); context.setLifeState(Context.runningState); } @Override public void stop() { System.out.println(this.getClass().getSimpleName()+": operate stop"); context.setLifeState(Context.stoppingState); }} package design.pattern.state;import design.pattern.flayweight.ConnectionPool;/** * Created by Aaron on 15/9/20. */public class User { public static void main(String[] args){ Context context=new Context(); context.setLifeState(Context.closingState); context.open(); context.run(); context.close(); }} 策略模式(Strategy);GoF这样定义: 定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。 计算器的实现,其中计算的 以下是示例代码: package design.pattern.strategy;/** * Created by Aaron on 15/9/21. */public interface ICalculate { double calculate(double a,double b);} package design.pattern.strategy;/** * Created by Aaron on 15/9/21. */public class AddCalculate implements ICalculate { public double calculate(double a, double b) { return a+b; }} package design.pattern.strategy;/** * Created by Aaron on 15/9/21. */public class DivisionCalculate implements ICalculate { public double calculate(double a, double b) { return a/b; }} package design.pattern.strategy;/** * Created by Aaron on 15/9/21. */public class SubtractionCalculate implements ICalculate { public double calculate(double a, double b) { return a-b; }} package design.pattern.strategy;/** * Created by Aaron on 15/9/21. */public class Context { private ICalculate calculate; public Context(ICalculate calculate){ this.calculate=calculate; } public ICalculate getCalculate() { return calculate; } public void setCalculate(ICalculate calculate) { this.calculate = calculate; } public double calculate(double a,double b){ return this.calculate.calculate(a,b); }} package design.pattern.strategy;/** * Created by Aaron on 15/9/21. */public class User { public static void main(String args[]){ Context context =new Context(new AddCalculate()); double result=context.calculate(20.0,30.3); System.out.println(result); context.setCalculate(new DivisionCalculate()); System.out.println(context.calculate(20,40)); }} 结果输出: 50.3 0.5 职责链模式(Chain of Responsibility);GoF这样定义: 典型的事例就是我们在Spring中的拦截器和Servlet中的Filter,它们都是现成的责任链模式。 `以下是示例代码:` package design.pattern.responsibilitychain;/** * Created by Aaron on 15/9/29. */public abstract class Handler { protected Handler successor; public abstract void process(); public Handler getSuccessor() { return successor; } public void setSuccessor(Handler successor) { this.successor = successor; }} package design.pattern.responsibilitychain;/** * Created by Aaron on 15/9/29. */public class LoggerHandler extends Handler { @Override public void process() { if(getSuccessor()!=null){ System.out.println(getClass().getSimpleName()+",处理请求,并调用下一个处理者"); getSuccessor().process(); }else{ System.out.println(getClass().getSimpleName()+",仅处理,无下一处理者"); } }} package design.pattern.responsibilitychain;/** * Created by Aaron on 15/9/29. */public class ValidateHandler extends Handler { @Override public void process() { if(getSuccessor()!=null){ System.out.println(getClass().getSimpleName()+",处理请求,并调用下一个处理者"); getSuccessor().process(); }else{ System.out.println(getClass().getSimpleName()+",仅处理,无下一处理者"); } }} package design.pattern.responsibilitychain;/** * Created by Aaron on 15/9/29. */public class User { public static void main(String[] args) { Handler validate = new ValidateHandler(); Handler handler = new LoggerHandler(); validate.setSuccessor(handler); validate.process(); }} 输出: ValidateHandler,处理请求,并调用下一个处理者 LoggerHandler,仅处理,无下一处理者 命令模式(Command);GoF这样定义: 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数 化;对请求排队或记录请求日志,以及支持可取消的操作。 AudioPlayer系统(转) 小女孩茱丽(Julia)有一个盒式录音机,此录音机有播音(Play)、倒带(Rewind)和停止(Stop)功能,录音机的键盘便是请求者(Invoker)角色;茱丽(Julia)是客户端角色,而录音机便是接收者角色。Command类扮演抽象命令角色,而PlayCommand、StopCommand和RewindCommand便是具体命令类。茱丽(Julia)不需要知道播音(play)、倒带(rewind)和停止(stop)功能是怎么具体执行的,这些命令执行的细节全都由键盘(Keypad)具体实施。茱丽(Julia)只需要在键盘上按下相应的键便可以了。 录音机是典型的命令模式。录音机按键把客户端与录音机的操作细节分割开来。 `以下是示例代码:` package design.pattern.command;/** * Created by Aaron on 15/9/29. */public class AudioPlay { public void play(){ System.out.println("播放...."); } public void rewind(){ System.out.println("倒带...."); } public void stop(){ System.out.println("停止...."); }} package design.pattern.command;/** * Created by Aaron on 15/9/29. */public interface Command { void execute();} package design.pattern.command;/** * Created by Aaron on 15/9/29. */public class PlayCommand implements Command { private AudioPlay audioPlay; public PlayCommand(AudioPlay audioPlay) { this.audioPlay = audioPlay; } public void execute() { this.audioPlay.play(); }} package design.pattern.command;/** * Created by Aaron on 15/9/29. */public class RewindCommand implements Command { private AudioPlay audioPlay; public RewindCommand(AudioPlay audioPlay) { this.audioPlay = audioPlay; } public void execute() { this.audioPlay.rewind(); }} package design.pattern.command;/** * Created by Aaron on 15/9/29. */public class StopCommand implements Command { private AudioPlay audioPlay; public StopCommand(AudioPlay audioPlay) { this.audioPlay = audioPlay; } public void execute() { this.audioPlay.stop(); }} package design.pattern.command;/** * Created by Aaron on 15/9/29. */public class Keypad { private Command playCommand; private Command rewindCommand; private Command stopCommand; public void setPlayCommand(Command playCommand) { this.playCommand = playCommand; } public void setRewindCommand(Command rewindCommand) { this.rewindCommand = rewindCommand; } public void setStopCommand(Command stopCommand) { this.stopCommand = stopCommand; } public void play(){ playCommand.execute(); } public void rewind(){ rewindCommand.execute(); } public void stop(){ stopCommand.execute(); }} package design.pattern.command;/** * Created by Aaron on 15/9/29. */public class User { public static void main(String[] args) { AudioPlay audioPlay = new AudioPlay(); PlayCommand playCommand = new PlayCommand(audioPlay); RewindCommand rewindCommand = new RewindCommand(audioPlay); StopCommand stopCommand = new StopCommand(audioPlay); Keypad keypad=new Keypad(); keypad.setPlayCommand(playCommand); keypad.setRewindCommand(rewindCommand); keypad.setStopCommand(stopCommand); keypad.play(); keypad.rewind(); keypad.stop(); keypad.play(); keypad.stop(); }} 输出结果: 播放.... 倒带.... 停止.... 播放.... 停止.... 访问者模式(Visitor);GoF这样定义: 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 `以下是示例代码:` ```## 调停者模式(Mediator);GoF这样定义:>用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互<div class="col-xs-12"><img src='' class='col-lg-offset-3 col-lg-6 col-xs-12 thumbnail'/></div>`以下是示例代码:````java ## 备忘录模式(Memento); GoF这样定义: > 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。 `以下是示例代码:` ```## 迭代器模式(Iterator);GoF这样定义:>提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。<div class="col-xs-12"><img src='' class='col-lg-offset-3 col-lg-6 col-xs-12 thumbnail'/></div>`以下是示例代码:````java ##解释器模式(Interpreter) GoF这样定义: >给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。 `以下是示例代码:` ```java ```°","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"设计模式","slug":"设计模式","permalink":"http://example.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"工作中使用的工具整理","slug":"工作中使用的工具整理","date":"2015-09-12T16:00:00.000Z","updated":"2022-09-03T10:12:16.075Z","comments":true,"path":"2015/09/13/工作中使用的工具整理/","link":"","permalink":"http://example.com/2015/09/13/%E5%B7%A5%E4%BD%9C%E4%B8%AD%E4%BD%BF%E7%94%A8%E7%9A%84%E5%B7%A5%E5%85%B7%E6%95%B4%E7%90%86/","excerpt":"","text":"2015目前我日常工作中常用的工具,在这里进行一次整理备份 IntelliJ IDEA Web Storm PyCharm MindPreview Lite SQLPro http://www.macsqlclient.com/ rdm redis mongodb zkcli alfred hexo zsh + iterm + powerline","categories":[{"name":"日常工具","slug":"日常工具","permalink":"http://example.com/categories/%E6%97%A5%E5%B8%B8%E5%B7%A5%E5%85%B7/"},{"name":"mac","slug":"日常工具/mac","permalink":"http://example.com/categories/%E6%97%A5%E5%B8%B8%E5%B7%A5%E5%85%B7/mac/"}],"tags":[{"name":"mac","slug":"mac","permalink":"http://example.com/tags/mac/"},{"name":"软件","slug":"软件","permalink":"http://example.com/tags/%E8%BD%AF%E4%BB%B6/"}]},{"title":"MVN日常使用","slug":"MVN日常使用","date":"2015-09-12T16:00:00.000Z","updated":"2022-09-03T10:12:16.075Z","comments":true,"path":"2015/09/13/MVN日常使用/","link":"","permalink":"http://example.com/2015/09/13/MVN%E6%97%A5%E5%B8%B8%E4%BD%BF%E7%94%A8/","excerpt":"","text":"工作中日常使用 #command 跳过测试进行 mvn -Dmaven.test.skip=true package 指定打包的环境mvn -DskipTests clean package -P FTE2 mvn -U clean package -Dmaven.test.skip=true -pl biz/pom.xml -am maven中snapshot快照库和release发布库的区别和作用 mvn -am -DskipTests -U -pl biz-web clean package","categories":[{"name":"日常工具","slug":"日常工具","permalink":"http://example.com/categories/%E6%97%A5%E5%B8%B8%E5%B7%A5%E5%85%B7/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"maven","slug":"maven","permalink":"http://example.com/tags/maven/"}]},{"title":"Java thread 多线程 ExecutorService","slug":"JAVA多线程之ExecutorService","date":"2015-09-11T16:00:00.000Z","updated":"2022-09-03T10:12:16.073Z","comments":true,"path":"2015/09/12/JAVA多线程之ExecutorService/","link":"","permalink":"http://example.com/2015/09/12/JAVA%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B9%8BExecutorService/","excerpt":"","text":"","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"多线程","slug":"多线程","permalink":"http://example.com/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"}]},{"title":"多线程 Executors","slug":"JAVA多线程之Executors","date":"2015-09-11T16:00:00.000Z","updated":"2022-09-03T10:12:16.073Z","comments":true,"path":"2015/09/12/JAVA多线程之Executors/","link":"","permalink":"http://example.com/2015/09/12/JAVA%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B9%8BExecutors/","excerpt":"","text":"","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"多线程","slug":"多线程","permalink":"http://example.com/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"}]},{"title":"ThreadPoolExecutor 源码分析","slug":"JAVA多线程之ThreadPoolExecutor","date":"2015-09-11T16:00:00.000Z","updated":"2022-09-03T10:12:16.074Z","comments":true,"path":"2015/09/12/JAVA多线程之ThreadPoolExecutor/","link":"","permalink":"http://example.com/2015/09/12/JAVA%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B9%8BThreadPoolExecutor/","excerpt":"","text":"/* * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. *//* * * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ */package java.util.concurrent;import java.util.concurrent.locks.AbstractQueuedSynchronizer;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;import java.util.concurrent.atomic.AtomicInteger;import java.util.*;/** *一个ExecutorService执行每个被提交入到线程池中的任务,通过Executors工厂方法进行配置。 *线程池处理两种不同的问题:通过减少每个任务调用的开销、提供边界和资源管理,包括线程, *任务集合的执行,从而改进了执行大量异步任务时的性能问题。ThreadPoolExcecutor也维护着一 *些统计数据,如已完成任务的数目。 *面对一个提供了许多可调用参数和可扩展性的hooks.程序员通常比较喜欢用Executors的工厂方法。 *如Executors.newCachedThreadPool(无限大小的线程池,自动线程回收)、Executors.newFixedThreadPool *(固定大小的线程池)、Executors.newSingleThreadExecutor(单个后台线程),为最常用的场 *景进行预配置。或者,使用这个类进行手动配置实现同样的效果的线程池, * * 核心和最大的线程池 * * ThreadPoolExecutor会根据线程池的大小配置corePoolSize和maximumPoolSize来自动调整池的大小, *可以通过getPoolSize查看池的大小。 * * When a new task is submitted in method {@link #execute(Runnable)}, * and fewer than corePoolSize threads are running, a new thread is * created to handle the request, even if other worker threads are * idle. If there are more than corePoolSize but less than * maximumPoolSize threads running, a new thread will be created only * if the queue is full. By setting corePoolSize and maximumPoolSize * the same, you create a fixed-size thread pool. By setting * maximumPoolSize to an essentially unbounded value such as {@code * Integer.MAX_VALUE}, you allow the pool to accommodate an arbitrary * number of concurrent tasks. Most typically, core and maximum pool * sizes are set only upon construction, but they may also be changed * dynamically using {@link #setCorePoolSize} and {@link * #setMaximumPoolSize}. </dd> * * <dt>On-demand construction</dt> * * <dd>By default, even core threads are initially created and * started only when new tasks arrive, but this can be overridden * dynamically using method {@link #prestartCoreThread} or {@link * #prestartAllCoreThreads}. You probably want to prestart threads if * you construct the pool with a non-empty queue. </dd> * * <dt>Creating new threads</dt> * * <dd>New threads are created using a {@link ThreadFactory}. If not * otherwise specified, a {@link Executors#defaultThreadFactory} is * used, that creates threads to all be in the same {@link * ThreadGroup} and with the same {@code NORM_PRIORITY} priority and * non-daemon status. By supplying a different ThreadFactory, you can * alter the thread's name, thread group, priority, daemon status, * etc. If a {@code ThreadFactory} fails to create a thread when asked * by returning null from {@code newThread}, the executor will * continue, but might not be able to execute any tasks. Threads * should possess the "modifyThread" {@code RuntimePermission}. If * worker threads or other threads using the pool do not possess this * permission, service may be degraded: configuration changes may not * take effect in a timely manner, and a shutdown pool may remain in a * state in which termination is possible but not completed.</dd> * * <dt>Keep-alive times</dt> * * <dd>If the pool currently has more than corePoolSize threads, * excess threads will be terminated if they have been idle for more * than the keepAliveTime (see {@link #getKeepAliveTime(TimeUnit)}). * This provides a means of reducing resource consumption when the * pool is not being actively used. If the pool becomes more active * later, new threads will be constructed. This parameter can also be * changed dynamically using method {@link #setKeepAliveTime(long, * TimeUnit)}. Using a value of {@code Long.MAX_VALUE} {@link * TimeUnit#NANOSECONDS} effectively disables idle threads from ever * terminating prior to shut down. By default, the keep-alive policy * applies only when there are more than corePoolSize threads. But * method {@link #allowCoreThreadTimeOut(boolean)} can be used to * apply this time-out policy to core threads as well, so long as the * keepAliveTime value is non-zero. </dd> * * <dt>Queuing</dt> * * <dd>Any {@link BlockingQueue} may be used to transfer and hold * submitted tasks. The use of this queue interacts with pool sizing: * * <ul> * * <li> If fewer than corePoolSize threads are running, the Executor * always prefers adding a new thread * rather than queuing.</li> * * <li> If corePoolSize or more threads are running, the Executor * always prefers queuing a request rather than adding a new * thread.</li> * * <li> If a request cannot be queued, a new thread is created unless * this would exceed maximumPoolSize, in which case, the task will be * rejected.</li> * * </ul> * * There are three general strategies for queuing: * <ol> * * <li> <em> Direct handoffs.</em> A good default choice for a work * queue is a {@link SynchronousQueue} that hands off tasks to threads * without otherwise holding them. Here, an attempt to queue a task * will fail if no threads are immediately available to run it, so a * new thread will be constructed. This policy avoids lockups when * handling sets of requests that might have internal dependencies. * Direct handoffs generally require unbounded maximumPoolSizes to * avoid rejection of new submitted tasks. This in turn admits the * possibility of unbounded thread growth when commands continue to * arrive on average faster than they can be processed. </li> * * <li><em> Unbounded queues.</em> Using an unbounded queue (for * example a {@link LinkedBlockingQueue} without a predefined * capacity) will cause new tasks to wait in the queue when all * corePoolSize threads are busy. Thus, no more than corePoolSize * threads will ever be created. (And the value of the maximumPoolSize * therefore doesn't have any effect.) This may be appropriate when * each task is completely independent of others, so tasks cannot * affect each others execution; for example, in a web page server. * While this style of queuing can be useful in smoothing out * transient bursts of requests, it admits the possibility of * unbounded work queue growth when commands continue to arrive on * average faster than they can be processed. </li> * * <li><em>Bounded queues.</em> A bounded queue (for example, an * {@link ArrayBlockingQueue}) helps prevent resource exhaustion when * used with finite maximumPoolSizes, but can be more difficult to * tune and control. Queue sizes and maximum pool sizes may be traded * off for each other: Using large queues and small pools minimizes * CPU usage, OS resources, and context-switching overhead, but can * lead to artificially low throughput. If tasks frequently block (for * example if they are I/O bound), a system may be able to schedule * time for more threads than you otherwise allow. Use of small queues * generally requires larger pool sizes, which keeps CPUs busier but * may encounter unacceptable scheduling overhead, which also * decreases throughput. </li> * * </ol> * * </dd> * * <dt>Rejected tasks</dt> * * <dd>New tasks submitted in method {@link #execute(Runnable)} will be * <em>rejected</em> when the Executor has been shut down, and also when * the Executor uses finite bounds for both maximum threads and work queue * capacity, and is saturated. In either case, the {@code execute} method * invokes the {@link * RejectedExecutionHandler#rejectedExecution(Runnable, ThreadPoolExecutor)} * method of its {@link RejectedExecutionHandler}. Four predefined handler * policies are provided: * * <ol> * * <li> In the default {@link ThreadPoolExecutor.AbortPolicy}, the * handler throws a runtime {@link RejectedExecutionException} upon * rejection. </li> * * <li> In {@link ThreadPoolExecutor.CallerRunsPolicy}, the thread * that invokes {@code execute} itself runs the task. This provides a * simple feedback control mechanism that will slow down the rate that * new tasks are submitted. </li> * * <li> In {@link ThreadPoolExecutor.DiscardPolicy}, a task that * cannot be executed is simply dropped. </li> * * <li>In {@link ThreadPoolExecutor.DiscardOldestPolicy}, if the * executor is not shut down, the task at the head of the work queue * is dropped, and then execution is retried (which can fail again, * causing this to be repeated.) </li> * * </ol> * * It is possible to define and use other kinds of {@link * RejectedExecutionHandler} classes. Doing so requires some care * especially when policies are designed to work only under particular * capacity or queuing policies. </dd> * * <dt>Hook methods</dt> * * <dd>This class provides {@code protected} overridable * {@link #beforeExecute(Thread, Runnable)} and * {@link #afterExecute(Runnable, Throwable)} methods that are called * before and after execution of each task. These can be used to * manipulate the execution environment; for example, reinitializing * ThreadLocals, gathering statistics, or adding log entries. * Additionally, method {@link #terminated} can be overridden to perform * any special processing that needs to be done once the Executor has * fully terminated. * * <p>If hook or callback methods throw exceptions, internal worker * threads may in turn fail and abruptly terminate.</dd> * * <dt>Queue maintenance</dt> * * <dd>Method {@link #getQueue()} allows access to the work queue * for purposes of monitoring and debugging. Use of this method for * any other purpose is strongly discouraged. Two supplied methods, * {@link #remove(Runnable)} and {@link #purge} are available to * assist in storage reclamation when large numbers of queued tasks * become cancelled.</dd> * * <dt>Finalization</dt> * * <dd>A pool that is no longer referenced in a program <em>AND</em> * has no remaining threads will be {@code shutdown} automatically. If * you would like to ensure that unreferenced pools are reclaimed even * if users forget to call {@link #shutdown}, then you must arrange * that unused threads eventually die, by setting appropriate * keep-alive times, using a lower bound of zero core threads and/or * setting {@link #allowCoreThreadTimeOut(boolean)}. </dd> * * </dl> * * <p><b>Extension example</b>. Most extensions of this class * override one or more of the protected hook methods. For example, * here is a subclass that adds a simple pause/resume feature: * * <pre> {@code * class PausableThreadPoolExecutor extends ThreadPoolExecutor { * private boolean isPaused; * private ReentrantLock pauseLock = new ReentrantLock(); * private Condition unpaused = pauseLock.newCondition(); * * public PausableThreadPoolExecutor(...) { super(...); } * * protected void beforeExecute(Thread t, Runnable r) { * super.beforeExecute(t, r); * pauseLock.lock(); * try { * while (isPaused) unpaused.await(); * } catch (InterruptedException ie) { * t.interrupt(); * } finally { * pauseLock.unlock(); * } * } * * public void pause() { * pauseLock.lock(); * try { * isPaused = true; * } finally { * pauseLock.unlock(); * } * } * * public void resume() { * pauseLock.lock(); * try { * isPaused = false; * unpaused.signalAll(); * } finally { * pauseLock.unlock(); * } * } * }}</pre> * * @since 1.5 * @author Doug Lea */public class ThreadPoolExecutor extends AbstractExecutorService { /** * The main pool control state, ctl, is an atomic integer packing * two conceptual fields * workerCount, indicating the effective number of threads * runState, indicating whether running, shutting down etc * * In order to pack them into one int, we limit workerCount to * (2^29)-1 (about 500 million) threads rather than (2^31)-1 (2 * billion) otherwise representable. If this is ever an issue in * the future, the variable can be changed to be an AtomicLong, * and the shift/mask constants below adjusted. But until the need * arises, this code is a bit faster and simpler using an int. * * The workerCount is the number of workers that have been * permitted to start and not permitted to stop. The value may be * transiently different from the actual number of live threads, * for example when a ThreadFactory fails to create a thread when * asked, and when exiting threads are still performing * bookkeeping before terminating. The user-visible pool size is * reported as the current size of the workers set. * * The runState provides the main lifecycle control, taking on values: * * RUNNING: Accept new tasks and process queued tasks * SHUTDOWN: Don't accept new tasks, but process queued tasks * STOP: Don't accept new tasks, don't process queued tasks, * and interrupt in-progress tasks * TIDYING: All tasks have terminated, workerCount is zero, * the thread transitioning to state TIDYING * will run the terminated() hook method * TERMINATED: terminated() has completed * * The numerical order among these values matters, to allow * ordered comparisons. The runState monotonically increases over * time, but need not hit each state. The transitions are: * * RUNNING -> SHUTDOWN * On invocation of shutdown(), perhaps implicitly in finalize() * (RUNNING or SHUTDOWN) -> STOP * On invocation of shutdownNow() * SHUTDOWN -> TIDYING * When both queue and pool are empty * STOP -> TIDYING * When pool is empty * TIDYING -> TERMINATED * When the terminated() hook method has completed * * Threads waiting in awaitTermination() will return when the * state reaches TERMINATED. * * Detecting the transition from SHUTDOWN to TIDYING is less * straightforward than you'd like because the queue may become * empty after non-empty and vice versa during SHUTDOWN state, but * we can only terminate if, after seeing that it is empty, we see * that workerCount is 0 (which sometimes entails a recheck -- see * below). */ private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static final int COUNT_BITS = Integer.SIZE - 3; private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS; // Packing and unpacking ctl private static int runStateOf(int c) { return c & ~CAPACITY; } private static int workerCountOf(int c) { return c & CAPACITY; } private static int ctlOf(int rs, int wc) { return rs | wc; } /* * Bit field accessors that don't require unpacking ctl. * These depend on the bit layout and on workerCount being never negative. */ private static boolean runStateLessThan(int c, int s) { return c < s; } private static boolean runStateAtLeast(int c, int s) { return c >= s; } private static boolean isRunning(int c) { return c < SHUTDOWN; } /** * Attempts to CAS-increment the workerCount field of ctl. */ private boolean compareAndIncrementWorkerCount(int expect) { return ctl.compareAndSet(expect, expect + 1); } /** * Attempts to CAS-decrement the workerCount field of ctl. */ private boolean compareAndDecrementWorkerCount(int expect) { return ctl.compareAndSet(expect, expect - 1); } /** * Decrements the workerCount field of ctl. This is called only on * abrupt termination of a thread (see processWorkerExit). Other * decrements are performed within getTask. */ private void decrementWorkerCount() { do {} while (! compareAndDecrementWorkerCount(ctl.get())); } /** * The queue used for holding tasks and handing off to worker * threads. We do not require that workQueue.poll() returning * null necessarily means that workQueue.isEmpty(), so rely * solely on isEmpty to see if the queue is empty (which we must * do for example when deciding whether to transition from * SHUTDOWN to TIDYING). This accommodates special-purpose * queues such as DelayQueues for which poll() is allowed to * return null even if it may later return non-null when delays * expire. */ private final BlockingQueue<Runnable> workQueue; /** * Lock held on access to workers set and related bookkeeping. * While we could use a concurrent set of some sort, it turns out * to be generally preferable to use a lock. Among the reasons is * that this serializes interruptIdleWorkers, which avoids * unnecessary interrupt storms, especially during shutdown. * Otherwise exiting threads would concurrently interrupt those * that have not yet interrupted. It also simplifies some of the * associated statistics bookkeeping of largestPoolSize etc. We * also hold mainLock on shutdown and shutdownNow, for the sake of * ensuring workers set is stable while separately checking * permission to interrupt and actually interrupting. */ private final ReentrantLock mainLock = new ReentrantLock(); /** * Set containing all worker threads in pool. Accessed only when * holding mainLock. */ private final HashSet<Worker> workers = new HashSet<Worker>(); /** * Wait condition to support awaitTermination */ private final Condition termination = mainLock.newCondition(); /** * Tracks largest attained pool size. Accessed only under * mainLock. */ private int largestPoolSize; /** * Counter for completed tasks. Updated only on termination of * worker threads. Accessed only under mainLock. */ private long completedTaskCount; /* * All user control parameters are declared as volatiles so that * ongoing actions are based on freshest values, but without need * for locking, since no internal invariants depend on them * changing synchronously with respect to other actions. */ /** * Factory for new threads. All threads are created using this * factory (via method addWorker). All callers must be prepared * for addWorker to fail, which may reflect a system or user's * policy limiting the number of threads. Even though it is not * treated as an error, failure to create threads may result in * new tasks being rejected or existing ones remaining stuck in * the queue. * * We go further and preserve pool invariants even in the face of * errors such as OutOfMemoryError, that might be thrown while * trying to create threads. Such errors are rather common due to * the need to allocate a native stack in Thread.start, and users * will want to perform clean pool shutdown to clean up. There * will likely be enough memory available for the cleanup code to * complete without encountering yet another OutOfMemoryError. */ private volatile ThreadFactory threadFactory; /** * Handler called when saturated or shutdown in execute. */ private volatile RejectedExecutionHandler handler; /** * Timeout in nanoseconds for idle threads waiting for work. * Threads use this timeout when there are more than corePoolSize * present or if allowCoreThreadTimeOut. Otherwise they wait * forever for new work. */ private volatile long keepAliveTime; /** * If false (default), core threads stay alive even when idle. * If true, core threads use keepAliveTime to time out waiting * for work. */ private volatile boolean allowCoreThreadTimeOut; /** * Core pool size is the minimum number of workers to keep alive * (and not allow to time out etc) unless allowCoreThreadTimeOut * is set, in which case the minimum is zero. */ private volatile int corePoolSize; /** * Maximum pool size. Note that the actual maximum is internally * bounded by CAPACITY. */ private volatile int maximumPoolSize; /** * The default rejected execution handler */ private static final RejectedExecutionHandler defaultHandler = new AbortPolicy(); /** * Permission required for callers of shutdown and shutdownNow. * We additionally require (see checkShutdownAccess) that callers * have permission to actually interrupt threads in the worker set * (as governed by Thread.interrupt, which relies on * ThreadGroup.checkAccess, which in turn relies on * SecurityManager.checkAccess). Shutdowns are attempted only if * these checks pass. * * All actual invocations of Thread.interrupt (see * interruptIdleWorkers and interruptWorkers) ignore * SecurityExceptions, meaning that the attempted interrupts * silently fail. In the case of shutdown, they should not fail * unless the SecurityManager has inconsistent policies, sometimes * allowing access to a thread and sometimes not. In such cases, * failure to actually interrupt threads may disable or delay full * termination. Other uses of interruptIdleWorkers are advisory, * and failure to actually interrupt will merely delay response to * configuration changes so is not handled exceptionally. */ private static final RuntimePermission shutdownPerm = new RuntimePermission("modifyThread"); /** * Class Worker mainly maintains interrupt control state for * threads running tasks, along with other minor bookkeeping. * This class opportunistically extends AbstractQueuedSynchronizer * to simplify acquiring and releasing a lock surrounding each * task execution. This protects against interrupts that are * intended to wake up a worker thread waiting for a task from * instead interrupting a task being run. We implement a simple * non-reentrant mutual exclusion lock rather than use * ReentrantLock because we do not want worker tasks to be able to * reacquire the lock when they invoke pool control methods like * setCorePoolSize. Additionally, to suppress interrupts until * the thread actually starts running tasks, we initialize lock * state to a negative value, and clear it upon start (in * runWorker). */ private final class Worker extends AbstractQueuedSynchronizer implements Runnable { /** * This class will never be serialized, but we provide a * serialVersionUID to suppress a javac warning. */ private static final long serialVersionUID = 6138294804551838833L; /** Thread this worker is running in. Null if factory fails. */ final Thread thread; /** Initial task to run. Possibly null. */ Runnable firstTask; /** Per-thread task counter */ volatile long completedTasks; /** * Creates with given first task and thread from ThreadFactory. * @param firstTask the first task (null if none) */ Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } /** Delegates main run loop to outer runWorker */ public void run() { runWorker(this); } // Lock methods // // The value 0 represents the unlocked state. // The value 1 represents the locked state. protected boolean isHeldExclusively() { return getState() != 0; } protected boolean tryAcquire(int unused) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; } public void lock() { acquire(1); } public boolean tryLock() { return tryAcquire(1); } public void unlock() { release(1); } public boolean isLocked() { return isHeldExclusively(); } void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } } } } /* * Methods for setting control state */ /** * Transitions runState to given target, or leaves it alone if * already at least the given target. * * @param targetState the desired state, either SHUTDOWN or STOP * (but not TIDYING or TERMINATED -- use tryTerminate for that) */ private void advanceRunState(int targetState) { for (;;) { int c = ctl.get(); if (runStateAtLeast(c, targetState) || ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))) break; } } /** * Transitions to TERMINATED state if either (SHUTDOWN and pool * and queue empty) or (STOP and pool empty). If otherwise * eligible to terminate but workerCount is nonzero, interrupts an * idle worker to ensure that shutdown signals propagate. This * method must be called following any action that might make * termination possible -- reducing worker count or removing tasks * from the queue during shutdown. The method is non-private to * allow access from ScheduledThreadPoolExecutor. */ final void tryTerminate() { for (;;) { int c = ctl.get(); if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) return; if (workerCountOf(c) != 0) { // Eligible to terminate interruptIdleWorkers(ONLY_ONE); return; } final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { try { terminated(); } finally { ctl.set(ctlOf(TERMINATED, 0)); termination.signalAll(); } return; } } finally { mainLock.unlock(); } // else retry on failed CAS } } /* * Methods for controlling interrupts to worker threads. */ /** * If there is a security manager, makes sure caller has * permission to shut down threads in general (see shutdownPerm). * If this passes, additionally makes sure the caller is allowed * to interrupt each worker thread. This might not be true even if * first check passed, if the SecurityManager treats some threads * specially. */ private void checkShutdownAccess() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkPermission(shutdownPerm); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) security.checkAccess(w.thread); } finally { mainLock.unlock(); } } } /** * Interrupts all threads, even if active. Ignores SecurityExceptions * (in which case some threads may remain uninterrupted). */ private void interruptWorkers() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) w.interruptIfStarted(); } finally { mainLock.unlock(); } } /** * Interrupts threads that might be waiting for tasks (as * indicated by not being locked) so they can check for * termination or configuration changes. Ignores * SecurityExceptions (in which case some threads may remain * uninterrupted). * * @param onlyOne If true, interrupt at most one worker. This is * called only from tryTerminate when termination is otherwise * enabled but there are still other workers. In this case, at * most one waiting worker is interrupted to propagate shutdown * signals in case all threads are currently waiting. * Interrupting any arbitrary thread ensures that newly arriving * workers since shutdown began will also eventually exit. * To guarantee eventual termination, it suffices to always * interrupt only one idle worker, but shutdown() interrupts all * idle workers so that redundant workers exit promptly, not * waiting for a straggler task to finish. */ private void interruptIdleWorkers(boolean onlyOne) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { Thread t = w.thread; if (!t.isInterrupted() && w.tryLock()) { try { t.interrupt(); } catch (SecurityException ignore) { } finally { w.unlock(); } } if (onlyOne) break; } } finally { mainLock.unlock(); } } /** * Common form of interruptIdleWorkers, to avoid having to * remember what the boolean argument means. */ private void interruptIdleWorkers() { interruptIdleWorkers(false); } private static final boolean ONLY_ONE = true; /* * Misc utilities, most of which are also exported to * ScheduledThreadPoolExecutor */ /** * Invokes the rejected execution handler for the given command. * Package-protected for use by ScheduledThreadPoolExecutor. */ final void reject(Runnable command) { handler.rejectedExecution(command, this); } /** * Performs any further cleanup following run state transition on * invocation of shutdown. A no-op here, but used by * ScheduledThreadPoolExecutor to cancel delayed tasks. */ void onShutdown() { } /** * State check needed by ScheduledThreadPoolExecutor to * enable running tasks during shutdown. * * @param shutdownOK true if should return true if SHUTDOWN */ final boolean isRunningOrShutdown(boolean shutdownOK) { int rs = runStateOf(ctl.get()); return rs == RUNNING || (rs == SHUTDOWN && shutdownOK); } /** * Drains the task queue into a new list, normally using * drainTo. But if the queue is a DelayQueue or any other kind of * queue for which poll or drainTo may fail to remove some * elements, it deletes them one by one. */ private List<Runnable> drainQueue() { BlockingQueue<Runnable> q = workQueue; ArrayList<Runnable> taskList = new ArrayList<Runnable>(); q.drainTo(taskList); if (!q.isEmpty()) { for (Runnable r : q.toArray(new Runnable[0])) { if (q.remove(r)) taskList.add(r); } } return taskList; } /* * Methods for creating, running and cleaning up after workers */ /** * Checks if a new worker can be added with respect to current * pool state and the given bound (either core or maximum). If so, * the worker count is adjusted accordingly, and, if possible, a * new worker is created and started, running firstTask as its * first task. This method returns false if the pool is stopped or * eligible to shut down. It also returns false if the thread * factory fails to create a thread when asked. If the thread * creation fails, either due to the thread factory returning * null, or due to an exception (typically OutOfMemoryError in * Thread.start()), we roll back cleanly. * * @param firstTask the task the new thread should run first (or * null if none). Workers are created with an initial first task * (in method execute()) to bypass queuing when there are fewer * than corePoolSize threads (in which case we always start one), * or when the queue is full (in which case we must bypass queue). * Initially idle threads are usually created via * prestartCoreThread or to replace other dying workers. * * @param core if true use corePoolSize as bound, else * maximumPoolSize. (A boolean indicator is used here rather than a * value to ensure reads of fresh values after checking other pool * state). * @return true if successful */ private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; } /** * Rolls back the worker thread creation. * - removes worker from workers, if present * - decrements worker count * - rechecks for termination, in case the existence of this * worker was holding up termination */ private void addWorkerFailed(Worker w) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (w != null) workers.remove(w); decrementWorkerCount(); tryTerminate(); } finally { mainLock.unlock(); } } /** * Performs cleanup and bookkeeping for a dying worker. Called * only from worker threads. Unless completedAbruptly is set, * assumes that workerCount has already been adjusted to account * for exit. This method removes thread from worker set, and * possibly terminates the pool or replaces the worker if either * it exited due to user task exception or if fewer than * corePoolSize workers are running or queue is non-empty but * there are no workers. * * @param w the worker * @param completedAbruptly if the worker died due to user exception */ private void processWorkerExit(Worker w, boolean completedAbruptly) { if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { completedTaskCount += w.completedTasks; workers.remove(w); } finally { mainLock.unlock(); } tryTerminate(); int c = ctl.get(); if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { int min = allowCoreThreadTimeOut ? 0 : corePoolSize; if (min == 0 && ! workQueue.isEmpty()) min = 1; if (workerCountOf(c) >= min) return; // replacement not needed } addWorker(null, false); } } /** * Performs blocking or timed wait for a task, depending on * current configuration settings, or returns null if this worker * must exit because of any of: * 1. There are more than maximumPoolSize workers (due to * a call to setMaximumPoolSize). * 2. The pool is stopped. * 3. The pool is shutdown and the queue is empty. * 4. This worker timed out waiting for a task, and timed-out * workers are subject to termination (that is, * {@code allowCoreThreadTimeOut || workerCount > corePoolSize}) * both before and after the timed wait, and if the queue is * non-empty, this worker is not the last thread in the pool. * * @return task, or null if the worker must exit, in which case * workerCount is decremented */ private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } } /** * Main worker run loop. Repeatedly gets tasks from queue and * executes them, while coping with a number of issues: * * 1. We may start out with an initial task, in which case we * don't need to get the first one. Otherwise, as long as pool is * running, we get tasks from getTask. If it returns null then the * worker exits due to changed pool state or configuration * parameters. Other exits result from exception throws in * external code, in which case completedAbruptly holds, which * usually leads processWorkerExit to replace this thread. * * 2. Before running any task, the lock is acquired to prevent * other pool interrupts while the task is executing, and then we * ensure that unless pool is stopping, this thread does not have * its interrupt set. * * 3. Each task run is preceded by a call to beforeExecute, which * might throw an exception, in which case we cause thread to die * (breaking loop with completedAbruptly true) without processing * the task. * * 4. Assuming beforeExecute completes normally, we run the task, * gathering any of its thrown exceptions to send to afterExecute. * We separately handle RuntimeException, Error (both of which the * specs guarantee that we trap) and arbitrary Throwables. * Because we cannot rethrow Throwables within Runnable.run, we * wrap them within Errors on the way out (to the thread's * UncaughtExceptionHandler). Any thrown exception also * conservatively causes thread to die. * * 5. After task.run completes, we call afterExecute, which may * also throw an exception, which will also cause thread to * die. According to JLS Sec 14.20, this exception is the one that * will be in effect even if task.run throws. * * The net effect of the exception mechanics is that afterExecute * and the thread's UncaughtExceptionHandler have as accurate * information as we can provide about any problems encountered by * user code. * * @param w the worker */ final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } } // Public constructors and methods /** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters and default thread factory and rejected execution handler. * It may be more convenient to use one of the {@link Executors} factory * methods instead of this general purpose constructor. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @param maximumPoolSize the maximum number of threads to allow in the * pool * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * @param unit the time unit for the {@code keepAliveTime} argument * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * @throws IllegalArgumentException if one of the following holds:<br> * {@code corePoolSize < 0}<br> * {@code keepAliveTime < 0}<br> * {@code maximumPoolSize <= 0}<br> * {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} is null */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); } /** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters and default rejected execution handler. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @param maximumPoolSize the maximum number of threads to allow in the * pool * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * @param unit the time unit for the {@code keepAliveTime} argument * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * @param threadFactory the factory to use when the executor * creates a new thread * @throws IllegalArgumentException if one of the following holds:<br> * {@code corePoolSize < 0}<br> * {@code keepAliveTime < 0}<br> * {@code maximumPoolSize <= 0}<br> * {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} * or {@code threadFactory} is null */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); } /** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters and default thread factory. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @param maximumPoolSize the maximum number of threads to allow in the * pool * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * @param unit the time unit for the {@code keepAliveTime} argument * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * @param handler the handler to use when execution is blocked * because the thread bounds and queue capacities are reached * @throws IllegalArgumentException if one of the following holds:<br> * {@code corePoolSize < 0}<br> * {@code keepAliveTime < 0}<br> * {@code maximumPoolSize <= 0}<br> * {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} * or {@code handler} is null */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); } /** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @param maximumPoolSize the maximum number of threads to allow in the * pool * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * @param unit the time unit for the {@code keepAliveTime} argument * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * @param threadFactory the factory to use when the executor * creates a new thread * @param handler the handler to use when execution is blocked * because the thread bounds and queue capacities are reached * @throws IllegalArgumentException if one of the following holds:<br> * {@code corePoolSize < 0}<br> * {@code keepAliveTime < 0}<br> * {@code maximumPoolSize <= 0}<br> * {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} * or {@code threadFactory} or {@code handler} is null */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; } /** * Executes the given task sometime in the future. The task * may execute in a new thread or in an existing pooled thread. * * If the task cannot be submitted for execution, either because this * executor has been shutdown or because its capacity has been reached, * the task is handled by the current {@code RejectedExecutionHandler}. * * @param command the task to execute * @throws RejectedExecutionException at discretion of * {@code RejectedExecutionHandler}, if the task * cannot be accepted for execution * @throws NullPointerException if {@code command} is null */ public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); } /** * Initiates an orderly shutdown in which previously submitted * tasks are executed, but no new tasks will be accepted. * Invocation has no additional effect if already shut down. * * <p>This method does not wait for previously submitted tasks to * complete execution. Use {@link #awaitTermination awaitTermination} * to do that. * * @throws SecurityException {@inheritDoc} */ public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); advanceRunState(SHUTDOWN); interruptIdleWorkers(); onShutdown(); // hook for ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } tryTerminate(); } /** * Attempts to stop all actively executing tasks, halts the * processing of waiting tasks, and returns a list of the tasks * that were awaiting execution. These tasks are drained (removed) * from the task queue upon return from this method. * * <p>This method does not wait for actively executing tasks to * terminate. Use {@link #awaitTermination awaitTermination} to * do that. * * <p>There are no guarantees beyond best-effort attempts to stop * processing actively executing tasks. This implementation * cancels tasks via {@link Thread#interrupt}, so any task that * fails to respond to interrupts may never terminate. * * @throws SecurityException {@inheritDoc} */ public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); advanceRunState(STOP); interruptWorkers(); tasks = drainQueue(); } finally { mainLock.unlock(); } tryTerminate(); return tasks; } public boolean isShutdown() { return ! isRunning(ctl.get()); } /** * Returns true if this executor is in the process of terminating * after {@link #shutdown} or {@link #shutdownNow} but has not * completely terminated. This method may be useful for * debugging. A return of {@code true} reported a sufficient * period after shutdown may indicate that submitted tasks have * ignored or suppressed interruption, causing this executor not * to properly terminate. * * @return {@code true} if terminating but not yet terminated */ public boolean isTerminating() { int c = ctl.get(); return ! isRunning(c) && runStateLessThan(c, TERMINATED); } public boolean isTerminated() { return runStateAtLeast(ctl.get(), TERMINATED); } public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (;;) { if (runStateAtLeast(ctl.get(), TERMINATED)) return true; if (nanos <= 0) return false; nanos = termination.awaitNanos(nanos); } } finally { mainLock.unlock(); } } /** * Invokes {@code shutdown} when this executor is no longer * referenced and it has no threads. */ protected void finalize() { shutdown(); } /** * Sets the thread factory used to create new threads. * * @param threadFactory the new thread factory * @throws NullPointerException if threadFactory is null * @see #getThreadFactory */ public void setThreadFactory(ThreadFactory threadFactory) { if (threadFactory == null) throw new NullPointerException(); this.threadFactory = threadFactory; } /** * Returns the thread factory used to create new threads. * * @return the current thread factory * @see #setThreadFactory(ThreadFactory) */ public ThreadFactory getThreadFactory() { return threadFactory; } /** * Sets a new handler for unexecutable tasks. * * @param handler the new handler * @throws NullPointerException if handler is null * @see #getRejectedExecutionHandler */ public void setRejectedExecutionHandler(RejectedExecutionHandler handler) { if (handler == null) throw new NullPointerException(); this.handler = handler; } /** * Returns the current handler for unexecutable tasks. * * @return the current handler * @see #setRejectedExecutionHandler(RejectedExecutionHandler) */ public RejectedExecutionHandler getRejectedExecutionHandler() { return handler; } /** * Sets the core number of threads. This overrides any value set * in the constructor. If the new value is smaller than the * current value, excess existing threads will be terminated when * they next become idle. If larger, new threads will, if needed, * be started to execute any queued tasks. * * @param corePoolSize the new core size * @throws IllegalArgumentException if {@code corePoolSize < 0} * @see #getCorePoolSize */ public void setCorePoolSize(int corePoolSize) { if (corePoolSize < 0) throw new IllegalArgumentException(); int delta = corePoolSize - this.corePoolSize; this.corePoolSize = corePoolSize; if (workerCountOf(ctl.get()) > corePoolSize) interruptIdleWorkers(); else if (delta > 0) { // We don't really know how many new threads are "needed". // As a heuristic, prestart enough new workers (up to new // core size) to handle the current number of tasks in // queue, but stop if queue becomes empty while doing so. int k = Math.min(delta, workQueue.size()); while (k-- > 0 && addWorker(null, true)) { if (workQueue.isEmpty()) break; } } } /** * Returns the core number of threads. * * @return the core number of threads * @see #setCorePoolSize */ public int getCorePoolSize() { return corePoolSize; } /** * Starts a core thread, causing it to idly wait for work. This * overrides the default policy of starting core threads only when * new tasks are executed. This method will return {@code false} * if all core threads have already been started. * * @return {@code true} if a thread was started */ public boolean prestartCoreThread() { return workerCountOf(ctl.get()) < corePoolSize && addWorker(null, true); } /** * Same as prestartCoreThread except arranges that at least one * thread is started even if corePoolSize is 0. */ void ensurePrestart() { int wc = workerCountOf(ctl.get()); if (wc < corePoolSize) addWorker(null, true); else if (wc == 0) addWorker(null, false); } /** * Starts all core threads, causing them to idly wait for work. This * overrides the default policy of starting core threads only when * new tasks are executed. * * @return the number of threads started */ public int prestartAllCoreThreads() { int n = 0; while (addWorker(null, true)) ++n; return n; } /** * Returns true if this pool allows core threads to time out and * terminate if no tasks arrive within the keepAlive time, being * replaced if needed when new tasks arrive. When true, the same * keep-alive policy applying to non-core threads applies also to * core threads. When false (the default), core threads are never * terminated due to lack of incoming tasks. * * @return {@code true} if core threads are allowed to time out, * else {@code false} * * @since 1.6 */ public boolean allowsCoreThreadTimeOut() { return allowCoreThreadTimeOut; } /** * Sets the policy governing whether core threads may time out and * terminate if no tasks arrive within the keep-alive time, being * replaced if needed when new tasks arrive. When false, core * threads are never terminated due to lack of incoming * tasks. When true, the same keep-alive policy applying to * non-core threads applies also to core threads. To avoid * continual thread replacement, the keep-alive time must be * greater than zero when setting {@code true}. This method * should in general be called before the pool is actively used. * * @param value {@code true} if should time out, else {@code false} * @throws IllegalArgumentException if value is {@code true} * and the current keep-alive time is not greater than zero * * @since 1.6 */ public void allowCoreThreadTimeOut(boolean value) { if (value && keepAliveTime <= 0) throw new IllegalArgumentException("Core threads must have nonzero keep alive times"); if (value != allowCoreThreadTimeOut) { allowCoreThreadTimeOut = value; if (value) interruptIdleWorkers(); } } /** * Sets the maximum allowed number of threads. This overrides any * value set in the constructor. If the new value is smaller than * the current value, excess existing threads will be * terminated when they next become idle. * * @param maximumPoolSize the new maximum * @throws IllegalArgumentException if the new maximum is * less than or equal to zero, or * less than the {@linkplain #getCorePoolSize core pool size} * @see #getMaximumPoolSize */ public void setMaximumPoolSize(int maximumPoolSize) { if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize) throw new IllegalArgumentException(); this.maximumPoolSize = maximumPoolSize; if (workerCountOf(ctl.get()) > maximumPoolSize) interruptIdleWorkers(); } /** * Returns the maximum allowed number of threads. * * @return the maximum allowed number of threads * @see #setMaximumPoolSize */ public int getMaximumPoolSize() { return maximumPoolSize; } /** * Sets the time limit for which threads may remain idle before * being terminated. If there are more than the core number of * threads currently in the pool, after waiting this amount of * time without processing a task, excess threads will be * terminated. This overrides any value set in the constructor. * * @param time the time to wait. A time value of zero will cause * excess threads to terminate immediately after executing tasks. * @param unit the time unit of the {@code time} argument * @throws IllegalArgumentException if {@code time} less than zero or * if {@code time} is zero and {@code allowsCoreThreadTimeOut} * @see #getKeepAliveTime(TimeUnit) */ public void setKeepAliveTime(long time, TimeUnit unit) { if (time < 0) throw new IllegalArgumentException(); if (time == 0 && allowsCoreThreadTimeOut()) throw new IllegalArgumentException("Core threads must have nonzero keep alive times"); long keepAliveTime = unit.toNanos(time); long delta = keepAliveTime - this.keepAliveTime; this.keepAliveTime = keepAliveTime; if (delta < 0) interruptIdleWorkers(); } /** * Returns the thread keep-alive time, which is the amount of time * that threads in excess of the core pool size may remain * idle before being terminated. * * @param unit the desired time unit of the result * @return the time limit * @see #setKeepAliveTime(long, TimeUnit) */ public long getKeepAliveTime(TimeUnit unit) { return unit.convert(keepAliveTime, TimeUnit.NANOSECONDS); } /* User-level queue utilities */ /** * Returns the task queue used by this executor. Access to the * task queue is intended primarily for debugging and monitoring. * This queue may be in active use. Retrieving the task queue * does not prevent queued tasks from executing. * * @return the task queue */ public BlockingQueue<Runnable> getQueue() { return workQueue; } /** * Removes this task from the executor's internal queue if it is * present, thus causing it not to be run if it has not already * started. * * <p>This method may be useful as one part of a cancellation * scheme. It may fail to remove tasks that have been converted * into other forms before being placed on the internal queue. For * example, a task entered using {@code submit} might be * converted into a form that maintains {@code Future} status. * However, in such cases, method {@link #purge} may be used to * remove those Futures that have been cancelled. * * @param task the task to remove * @return {@code true} if the task was removed */ public boolean remove(Runnable task) { boolean removed = workQueue.remove(task); tryTerminate(); // In case SHUTDOWN and now empty return removed; } /** * Tries to remove from the work queue all {@link Future} * tasks that have been cancelled. This method can be useful as a * storage reclamation operation, that has no other impact on * functionality. Cancelled tasks are never executed, but may * accumulate in work queues until worker threads can actively * remove them. Invoking this method instead tries to remove them now. * However, this method may fail to remove tasks in * the presence of interference by other threads. */ public void purge() { final BlockingQueue<Runnable> q = workQueue; try { Iterator<Runnable> it = q.iterator(); while (it.hasNext()) { Runnable r = it.next(); if (r instanceof Future<?> && ((Future<?>)r).isCancelled()) it.remove(); } } catch (ConcurrentModificationException fallThrough) { // Take slow path if we encounter interference during traversal. // Make copy for traversal and call remove for cancelled entries. // The slow path is more likely to be O(N*N). for (Object r : q.toArray()) if (r instanceof Future<?> && ((Future<?>)r).isCancelled()) q.remove(r); } tryTerminate(); // In case SHUTDOWN and now empty } /* Statistics */ /** * Returns the current number of threads in the pool. * * @return the number of threads */ public int getPoolSize() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Remove rare and surprising possibility of // isTerminated() && getPoolSize() > 0 return runStateAtLeast(ctl.get(), TIDYING) ? 0 : workers.size(); } finally { mainLock.unlock(); } } /** * Returns the approximate number of threads that are actively * executing tasks. * * @return the number of threads */ public int getActiveCount() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { int n = 0; for (Worker w : workers) if (w.isLocked()) ++n; return n; } finally { mainLock.unlock(); } } /** * Returns the largest number of threads that have ever * simultaneously been in the pool. * * @return the number of threads */ public int getLargestPoolSize() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { return largestPoolSize; } finally { mainLock.unlock(); } } /** * Returns the approximate total number of tasks that have ever been * scheduled for execution. Because the states of tasks and * threads may change dynamically during computation, the returned * value is only an approximation. * * @return the number of tasks */ public long getTaskCount() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { long n = completedTaskCount; for (Worker w : workers) { n += w.completedTasks; if (w.isLocked()) ++n; } return n + workQueue.size(); } finally { mainLock.unlock(); } } /** * Returns the approximate total number of tasks that have * completed execution. Because the states of tasks and threads * may change dynamically during computation, the returned value * is only an approximation, but one that does not ever decrease * across successive calls. * * @return the number of tasks */ public long getCompletedTaskCount() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { long n = completedTaskCount; for (Worker w : workers) n += w.completedTasks; return n; } finally { mainLock.unlock(); } } /** * Returns a string identifying this pool, as well as its state, * including indications of run state and estimated worker and * task counts. * * @return a string identifying this pool, as well as its state */ public String toString() { long ncompleted; int nworkers, nactive; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { ncompleted = completedTaskCount; nactive = 0; nworkers = workers.size(); for (Worker w : workers) { ncompleted += w.completedTasks; if (w.isLocked()) ++nactive; } } finally { mainLock.unlock(); } int c = ctl.get(); String rs = (runStateLessThan(c, SHUTDOWN) ? "Running" : (runStateAtLeast(c, TERMINATED) ? "Terminated" : "Shutting down")); return super.toString() + "[" + rs + ", pool size = " + nworkers + ", active threads = " + nactive + ", queued tasks = " + workQueue.size() + ", completed tasks = " + ncompleted + "]"; } /* Extension hooks */ /** * Method invoked prior to executing the given Runnable in the * given thread. This method is invoked by thread {@code t} that * will execute task {@code r}, and may be used to re-initialize * ThreadLocals, or to perform logging. * * <p>This implementation does nothing, but may be customized in * subclasses. Note: To properly nest multiple overridings, subclasses * should generally invoke {@code super.beforeExecute} at the end of * this method. * * @param t the thread that will run task {@code r} * @param r the task that will be executed */ protected void beforeExecute(Thread t, Runnable r) { } /** * Method invoked upon completion of execution of the given Runnable. * This method is invoked by the thread that executed the task. If * non-null, the Throwable is the uncaught {@code RuntimeException} * or {@code Error} that caused execution to terminate abruptly. * * <p>This implementation does nothing, but may be customized in * subclasses. Note: To properly nest multiple overridings, subclasses * should generally invoke {@code super.afterExecute} at the * beginning of this method. * * <p><b>Note:</b> When actions are enclosed in tasks (such as * {@link FutureTask}) either explicitly or via methods such as * {@code submit}, these task objects catch and maintain * computational exceptions, and so they do not cause abrupt * termination, and the internal exceptions are <em>not</em> * passed to this method. If you would like to trap both kinds of * failures in this method, you can further probe for such cases, * as in this sample subclass that prints either the direct cause * or the underlying exception if a task has been aborted: * * <pre> {@code * class ExtendedExecutor extends ThreadPoolExecutor { * // ... * protected void afterExecute(Runnable r, Throwable t) { * super.afterExecute(r, t); * if (t == null && r instanceof Future<?>) { * try { * Object result = ((Future<?>) r).get(); * } catch (CancellationException ce) { * t = ce; * } catch (ExecutionException ee) { * t = ee.getCause(); * } catch (InterruptedException ie) { * Thread.currentThread().interrupt(); // ignore/reset * } * } * if (t != null) * System.out.println(t); * } * }}</pre> * * @param r the runnable that has completed * @param t the exception that caused termination, or null if * execution completed normally */ protected void afterExecute(Runnable r, Throwable t) { } /** * Method invoked when the Executor has terminated. Default * implementation does nothing. Note: To properly nest multiple * overridings, subclasses should generally invoke * {@code super.terminated} within this method. */ protected void terminated() { } /* Predefined RejectedExecutionHandlers */ /** * A handler for rejected tasks that runs the rejected task * directly in the calling thread of the {@code execute} method, * unless the executor has been shut down, in which case the task * is discarded. */ public static class CallerRunsPolicy implements RejectedExecutionHandler { /** * Creates a {@code CallerRunsPolicy}. */ public CallerRunsPolicy() { } /** * Executes task r in the caller's thread, unless the executor * has been shut down, in which case the task is discarded. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } } /** * A handler for rejected tasks that throws a * {@code RejectedExecutionException}. */ public static class AbortPolicy implements RejectedExecutionHandler { /** * Creates an {@code AbortPolicy}. */ public AbortPolicy() { } /** * Always throws RejectedExecutionException. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task * @throws RejectedExecutionException always */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } } /** * A handler for rejected tasks that silently discards the * rejected task. */ public static class DiscardPolicy implements RejectedExecutionHandler { /** * Creates a {@code DiscardPolicy}. */ public DiscardPolicy() { } /** * Does nothing, which has the effect of discarding task r. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } } /** * A handler for rejected tasks that discards the oldest unhandled * request and then retries {@code execute}, unless the executor * is shut down, in which case the task is discarded. */ public static class DiscardOldestPolicy implements RejectedExecutionHandler { /** * Creates a {@code DiscardOldestPolicy} for the given executor. */ public DiscardOldestPolicy() { } /** * Obtains and ignores the next task that the executor * would otherwise execute, if one is immediately available, * and then retries execution of task r, unless the executor * is shut down, in which case task r is instead discarded. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } }}","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"多线程","slug":"多线程","permalink":"http://example.com/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"}]},{"title":"设计模式分类","slug":"JAVA设计模式一","date":"2015-09-11T16:00:00.000Z","updated":"2022-09-03T10:12:16.074Z","comments":true,"path":"2015/09/12/JAVA设计模式一/","link":"","permalink":"http://example.com/2015/09/12/JAVA%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B8%80/","excerpt":"","text":"平时工作代码进行重构时也会涉及到设计模式,另外在看一些开源框架时也会涉及到很多的设计模式。只是平时没有太全面的了解,最近面试时有同事问汲到这里,所以在这里整理以备将来随时查看。这里我举一个最容易理解的例子来解释每种设计模式首先看一下设计模式的分类及关系 它们之间的关系如图: ##创建型模式这六个模式都是与创建对象相关的 简单工厂模式(Simple Factory); 工厂方法模式(Factory Method); 抽象工厂模式(Abstract Factory); 创建者模式(Builder); 原型模式(Prototype); 单例模式(Singleton); ##结构型模式 创建对象后,对象与对象之间的依赖关系,设计好了会为后续代码的维护带来很大的方便。 外观模式(Facade); 适配器模式(Adapter); 代理模式(Proxy); 装饰模式(Decorator); 桥模式(Bridge); 组合模式(Composite); 享元模式(Flyweight) ##行为型模式 对象的创建和结构定义好后,就是他们的行为的设计了。模板方法模式(Template Method); 观察者模式(Observer); 状态模式(State); 策略模式(Strategy); 职责链模式(Chain of Responsibility); 命令模式(Command); 访问者模式(Visitor); 调停者模式(Mediator); 备忘录模式(Memento); 迭代器模式(Iterator); 解释器模式(Interpreter)","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"设计模式","slug":"设计模式","permalink":"http://example.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"个人书架","slug":"我的书架","date":"2015-09-09T16:00:00.000Z","updated":"2022-09-03T10:12:16.073Z","comments":true,"path":"2015/09/10/我的书架/","link":"","permalink":"http://example.com/2015/09/10/%E6%88%91%E7%9A%84%E4%B9%A6%E6%9E%B6/","excerpt":"","text":"最近在看的书籍: 2020.09.23 《设计数据密集型应用》 曾经看过的书籍:工具类技术书籍 《Developing an ionic Edge》 《Angular JS权威教程》(Ari Lerner) 《O’Reilly:Python学习手册(第4版)》 《Python Cookbook(第3版)中文版》 《Vim使用技巧》 《JAVA编程思想》 《JAVA并发编程实战》 《Java JDK 7学习笔记》 《大话设计模式(交互启发式教学 谈笑间详解设计模式 让你爱不释手)》 程杰 著 《精通CSS+DIV网页样式布局》前沿科技 编著 业务专业相关 《流程优化与再造》 思维构建相关 《黑客与画家》 《大教堂与集市》 《重来》 《启示录 打造用户喜爱的产品》Marty Cagan著 七印部落 译 《从0到1:开启商业与未来的秘密》【美】彼得 蒂尔 Peter Thiel 《重构 改善既有代码的设计》 【美】Martin Fowler 著,熊节 译 《JavaScript权威指南(第6版)》(美)弗兰纳根 著,淘宝前端团队 译 《干法》 【日】稻盛和夫 著,曹岫云 译 《失控》 凯文凯利 著,东西文库 译 生活向导 《平凡的世界》(路遥) 《尼古拉.特斯拉传》(Steve Law) 要看的书籍 《集装箱改变世界》 本书从集装箱的发明史娓娓道来,将一个看似平凡的主题衍变成一个个非同寻常的有趣故事,展现了一项技术的进步是如何改变世界经济形态的。它的价值不在于是什么,而在于怎样使用。在集装箱出现之前,美国的沃尔玛、法国的成衣绝对不会遍地开花。而在集装箱出现之后,货运变得如此便宜,以至于某件产品产自东半球,运至纽约销售,远比在纽约近郊生产该产品来得划算。中国也从此登上国际集装箱海运和世界工厂的舞台。读者在享受阅读的同时,还会有趣地发现,即便是一个简单的创新,也会彻底改变我们的生活","categories":[{"name":"书架","slug":"书架","permalink":"http://example.com/categories/%E4%B9%A6%E6%9E%B6/"}],"tags":[{"name":"book","slug":"book","permalink":"http://example.com/tags/book/"}]},{"title":"sed,awk","slug":"Linux sed命令与awk命令","date":"2015-09-05T16:00:00.000Z","updated":"2022-09-03T10:12:16.073Z","comments":true,"path":"2015/09/06/Linux sed命令与awk命令/","link":"","permalink":"http://example.com/2015/09/06/Linux%20sed%E5%91%BD%E4%BB%A4%E4%B8%8Eawk%E5%91%BD%E4%BB%A4/","excerpt":"","text":"Linux shell编程从初学到精通 最近工作时遇到了一个问题,就是查看进行时,只查看某些进行的进程号,若直接用ps aux|grep sms 这样会得到一大堆的东东,所以同事推荐用awk,同时也提及了sed。 这里抽时间对这两个命令做一个总结,仅为个人学习工作所用。 ##sed、awk是什么? 它们是linux\\unix系统中的两种功能强大的文本处理工具。 有一个sed的编辑器,才有了sed(stream editor)这个名字,它是一个将一系列编辑命令作用于一个文本文件的理想工具。 由于创建awk的三个作者名称 是Aho、Weinberger和Kernighan,所以得名为AWK,是一种能够对结构化数据进行操作并产生格式化报表的编程语言。 ##sed的使用 ###使用场合 编辑相对交互式广西编辑器而言太大的文件 编辑命令太复杂,在交互式文本编辑器中难以输入的情况 对文件扫描一遍,但是需要执行多个编辑函数的情况 sed只对缓冲区中的原始文件的副本进行编辑,并不编辑原始的文件。so,若要保存个性后的文件,压根将输出重定向到另一个文件。如: sed 'sed command' source-file > target-file ###调用方式如何没有指定输入文件sed将从标准输入中接受输入 在shell命令行输入命令调用sed,格式为: sed [option] ‘sed command’ 输入文件注意此处为单引号将命令引起来 将sed命令插入脚本文件后,然后通过sed命令调用它,格式为: sed [option] -f sed脚本文件 输入文件 将sed命令插入脚本文件后,最常用的方法是设置该脚本文件为可执行,然后直接执行该脚本,格式为: ./sed脚本文件 输入文件 但此命令脚本文件,应该以sha-bang(#!)开头,sha-bang后面是解析这个脚本的程序名。 ####sed命令选项及其意义 -n:不打印所有行到标准输出 -e:将下一个字符串解析为sed编辑命令,如果只传递一个编辑命令给sed,-e选项可以省略 -f:表示正在调用sed脚本文件 ###命令组成方式 定位文本行和编辑命令两部分组成 ####定位文本 使用行号,指定一行,或指定行号范围 使用正则表达式 下面是sed命令定位文本的方法 x 为指定的行号 x,y 指定行号范围 /pattern/ 查询包含模式的行 /pattern/pattern/ 查询包含两个模式的行 /pattern/,x 从与模式匹配到x号行之间的行 反之类似 x,y!查询不包括x和y行号的行 ####常用编辑命令 p 打印匹配行 = 打印文件行号 a\\ 在定位行号之后追加文本信息 i\\ 在定们行号之前插入文本信息 d 删除定位行 c\\ 用新文本替换定位文本 s 使用替换模式替换相应模式 r 从另一个文件中读广西 w 将文本写入到一个文件 y 变换字符 q 第一个模式匹配完成后退出 l 显示与八进制ASCII码等价的控制字符 {} 在定位行执行的命令组 n 读取下一个输入行,用下一个命令处理新的行 h 将模式缓冲区的文本复制到保持缓冲区 H 将模式缓冲区的文本追加到保持缓冲区 x 互换模式缓冲区和保持缓冲区的内容 g 将保持缓冲区的内容复制到模式缓冲区 G 将保持缓冲区的内容追加到模式缓冲区 ###实例我们就用下面这个文件内容作为事例参考: #!/usr/bin/env python import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test.settings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) -n 选项的使用 使用-n 不输出所有的内容 1p 输出第一行 ➜ linuxstudy sed -n '1p' manage.py2 #!/usr/bin/env python 打印3到6行 ➜ linuxstudy sed -n '3,6p' manage.py2 import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test.settings") 模式匹配 ➜ linuxstudy sed -n '/environ/p' manage.py2 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test.settings") - e 选项的使用 打印行号: ➜ linuxstudy sed -n '/env/=' manage.py2 1 6 添加e选项: ➜ linuxstudy sed -n -e '/env/p' -e '/env/=' manage.py2 #!/usr/bin/env python 1 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test.settings") 6 `sed不支持多个编辑命令的用法,带多个编辑命令的用法,一般格式为:` sed [option] -e 编辑命令 -e 编辑命令 ... -e 编辑命令 输入文件 将下面命令操作存放到一个后缀为.sed的文件中,让其可执行 #!/usr/bin/sed -f /command/a\\ we append a new line sed文本定位 匹配元字符 $和. ➜ linuxstudy sed -n '/\\./p' manage.py2 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test.settings") from django$.core.management import execute_from_command_line execute_from_command_line(sys.argv) ➜ linuxstudy sed -n '/\\$/p' manage.py2 from django$.core.management import execute_from_command_line 元字符进行匹配 $在正则中表示行尾,但在这里表示最后一行 sed的基本命令,可以放在单引号外或内都行,根据自己的习惯 ➜ linuxstudy sed '$p' manage.py2 得取最后一行 ➜ linuxstudy sed -n '/.*line/p' manage.py2 找出以line结尾的行 ! 符号,表示取反,但是不能用于模式匹配的取反 ➜ linuxstudy sed -n '2,4!p' manage.py2 不打印2到4行 使用行号与关键字匹配限定行范围 /pattern/,x和x,/pattern/ 这两种形式其实与x,y一样的,只是将x或y代替罢了 ➜ linuxstudy sed -n '4,/mana/p' manage.py2 得到的是从第四行起到与mana匹配的行的内容 if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hwbuluo.settings") from django$.core.management import execute_from_command_line sed文本编辑 插入文本 i\\ 在匹配行的前端插入 sed '定位i\\text' 文件 修改上面的追加脚本: #!/usr/bin/sed -f /command/i\\ we append a new line 修改文本 modify.sed c\\ ------------------- #!/usr/bin/sed -f /command/c\\ I modify the file. ------------------- 执行:./modify.sed 删除文本 d 与追加和插入修改有所不同,这里在后面不需要添加\\ sed '1,3d' manage.py2 替换文本 替换文本与修改文本类似,只是修改是对一整行的个性,替换则是对局部进行修改 s/被替换的字符串/新字符串/[替换选项] 替换选项及意义: g:替换文本中所有出现被替换字符串之处,若不使用此选项,则只替换每行的第一个匹配上的字符串 p:与-n选项结合,只打印替换行 w 文件名:表示将输出定向到一个文件 ➜ linuxstudy sed -n 's/command/============/p' manage.py2 from django$.core.management import execute_from_============_line execute_from_============_line(sys.argv) 也可以 linuxstudy sed -n 's/command/============/2p' manage.py2 来替换每行出现的第几个 ##awk的使用 ###使用场合###调用方式###实例 awk -F ':' 'BEGIN {count=0;} {name[count] = $1;count++;};END {for (i=0;i<NR;i++) print i,name[i]}' /etc/passwd 0 root 1 daemon 2 bin 3 sys 4 sync 5 games 6 man 7 lp 8 mail ls -l |awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is",size/1024/1024 ,"M"}' [end]size is 0.098505 M","categories":[{"name":"扩展知识","slug":"扩展知识","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/"},{"name":"linux","slug":"扩展知识/linux","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://example.com/tags/linux/"}]},{"title":"常用运维知识大杂烩","slug":"运维知识总结","date":"2015-09-05T16:00:00.000Z","updated":"2022-09-03T10:12:16.073Z","comments":true,"path":"2015/09/06/运维知识总结/","link":"","permalink":"http://example.com/2015/09/06/%E8%BF%90%E7%BB%B4%E7%9F%A5%E8%AF%86%E6%80%BB%E7%BB%93/","excerpt":"","text":"##日常系统操作 将远程文件拷贝到本地 scp username@ip:remote_filepath /local_dir 同步目录 rsync -avzr 172.xx.xx.11:/opt/jagent/tomcat* . sudo chown -R fsdevops.fsdevops sms-service/ linux 最大文件限制数 ulimit ulimit -n 资源暂时不可用,资源已耗尽 $ ps -bash: fork: retry: 资源暂时不可用 -bash: fork: retry: 资源暂时不可用 -bash: fork: retry: 资源暂时不可用 -bash: fork: retry: 资源暂时不可用 IT组哥们分析说是每个用户句柄数只有1024个,目前超了 ulimit -a 即可查询linux相关的参数 查看进程号 ps aux|grep sms|awk -F ' ' '{print $2}' ps aux|grep sms|awk -F ' ' '{kill $2}' grep的使用 cat rmq_bk_gc.log|grep -E -o '\\w+'|sort|uniq -c|sort -k 2,1 -E 正则 -o 输出 -O 标示出 sort排序 uniq group 强制用sudo保存 :w !sudo tee % 设置服务自启动 chkconfig 查看某端口被谁占用 netstat -apn netstat -apn|grep 8013 ps -aux | grep 33514/java 查看文件占用 du -hs . 监视指定网络的数据包 监视指定主机和端口的数据包 tcpdump tcp port 23 and host 210.27.48.1 防火墙 hostname iptables -t filter -I INPUT -p tcp --dport 27107 -m state --state NEW -j ACCEPT sudo iptables -A INPUT -p tcp --dport 13710 -j ACCEPT sudo iptables -A OUTPUT -p tcp --sport 13710 -j ACCEPT service iptables save vim /etc/puppet/puppet.conf service puppet restart iptables -L more /etc/sysconfig/iptables vim /etc/sysconfig/iptables service iptables reload 停止防火墙 sudo su service iptables stop 安装telnet yum install -y telnet 查询某类文件 grep netty -R . 查看内存 free curl 发送请求 目的1:通过脚本发送post请求。 答案: curl -d "leaderboard_id=7778a8143f111272&score=19&app_key=8d49f16fe034b98b&_test_user=test01" "http://172.16.102.208:8089/wiapi/score" 目的2:通过脚本发送post请求,顺便附带文本数据,比如通过"浏览"选择本地的card.txt并上传发送post请求 答案: curl -F "blob=@card.txt;type=text/plain" "http://172.16.102.208:8089/wiapi/score?leaderboard_id=7778a8143f111272&score=40&app_key=8d49f16fe034b98b&_test_user=test01" ssh免密码登陆 ssh-keygen -t rsa -P '' 将生成的文件拷到目标主机,交添加到keys文件中 cat sshnopw.pub >> /root/.ssh/authorized_keys vmstat 相比top,我可以看到整个机器的CPU,内存,IO的使用情况,而不是单单看到各个进程的CPU使用率和内存使用率 2表示每个两秒采集一次服务器状态,1表示只采集一次。 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 0 779304 67972 706748 0 0 135 45 538 1117 10 3 86 2 0 r 表示运行队列(就是说多少个进程真的分配到CPU),我测试的服务器目前CPU比较空闲,没什么程序在跑,当这个值超过了CPU数目,就会出现CPU瓶颈了。这个也和top的负载有关系,一般负载超过了3就比较高,超过了5就高,超过了10就不正常了,服务器的状态很危险。top的负载类似每秒的运行队列。如果运行队列过大,表示你的CPU很繁忙,一般会造成CPU使用率很高。 b 表示阻塞的进程,这个不多说,进程阻塞,大家懂的。 swpd 虚拟内存已使用的大小,如果大于0,表示你的机器物理内存不足了,如果不是程序内存泄露的原因,那么你该升级内存了或者把耗内存的任务迁移到其他机器。 free 空闲的物理内存的大小,我的机器内存总共8G,剩余3415M。 buff Linux/Unix系统是用来存储,目录里面有什么内容,权限等的缓存,我本机大概占用300多M cache cache直接用来记忆我们打开的文件,给文件做缓冲,我本机大概占用300多M(这里是Linux/Unix的聪明之处,把空闲的物理内存的一部分拿来做文件和目录的缓存,是为了提高 程序执行的性能,当程序使用内存时,buffer/cached会很快地被使用。) si 每秒从磁盘读入虚拟内存的大小,如果这个值大于0,表示物理内存不够用或者内存泄露了,要查找耗内存进程解决掉。我的机器内存充裕,一切正常。 so 每秒虚拟内存写入磁盘的大小,如果这个值大于0,同上。 bi 块设备每秒接收的块数量,这里的块设备是指系统上所有的磁盘和其他块设备,默认块大小是1024byte,我本机上没什么IO操作,所以一直是0,但是我曾在处理拷贝大量数据(2-3T)的机器上看过可以达到140000/s,磁盘写入速度差不多140M每秒 bo 块设备每秒发送的块数量,例如我们读取文件,bo就要大于0。bi和bo一般都要接近0,不然就是IO过于频繁,需要调整。 in 每秒CPU的中断次数,包括时间中断 cs 每秒上下文切换次数,例如我们调用系统函数,就要进行上下文切换,线程的切换,也要进程上下文切换,这个值要越小越好,太大了,要考虑调低线程或者进程的数目,例如在apache和nginx这种web服务器中,我们一般做性能测试时会进行几千并发甚至几万并发的测试,选择web服务器的进程可以由进程或者线程的峰值一直下调,压测,直到cs到一个比较小的值,这个进程和线程数就是比较合适的值了。系统调用也是,每次调用系统函数,我们的代码就会进入内核空间,导致上下文切换,这个是很耗资源,也要尽量避免频繁调用系统函数。上下文切换次数过多表示你的CPU大部分浪费在上下文切换,导致CPU干正经事的时间少了,CPU没有充分利用,是不可取的。 us 用户CPU时间,我曾经在一个做加密解密很频繁的服务器上,可以看到us接近100,r运行队列达到80(机器在做压力测试,性能表现不佳)。 sy 系统CPU时间,如果太高,表示系统调用时间长,例如是IO操作频繁。 id 空闲 CPU时间,一般来说,id + us + sy = 100,一般我认为id是空闲CPU使用率,us是用户CPU使用率,sy是系统CPU使用率。 wt 等待IO CPU时间。 jstat java虚拟机 垃圾回收状态查看 命令格式 jstat命令命令格式: jstat [Options] vmid [interval] [count] 参数说明: Options,选项,我们一般使用 -gcutil 查看gc情况 vmid,VM的进程号,即当前运行的java进程号 interval,间隔时间,单位为秒或者毫秒 count,打印次数,如果缺省则打印无数次 示例说明 示例 通常运行命令如下: jstat -gc 12538 5000 即会每5秒一次显示进程号为12538的java进成的GC情况, 显示内容如下图: jstat -gc 19014 1000 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 10752.0 10752.0 0.0 5293.9 65536.0 65224.5 175104.0 16.0 13056.0 12799.5 1536.0 1495.2 1 0.009 0 0.000 0.009 10752.0 10752.0 0.0 5293.9 65536.0 65224.5 175104.0 16.0 13056.0 12799.5 1536.0 1495.2 1 0.009 0 0.000 0.009 结果说明 显示内容说明如下(部分结果是通过其他其他参数显示的,暂不说明): S0C:年轻代中第一个survivor(幸存区)的容量 (字节) S1C:年轻代中第二个survivor(幸存区)的容量 (字节) S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节) S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节) EC:年轻代中Eden(伊甸园)的容量 (字节) EU:年轻代中Eden(伊甸园)目前已使用空间 (字节) OC:Old代的容量 (字节) OU:Old代目前已使用空间 (字节) PC:Perm(持久代)的容量 (字节) PU:Perm(持久代)目前已使用空间 (字节) YGC:从应用程序启动到采样时年轻代中gc次数 YGCT:从应用程序启动到采样时年轻代中gc所用时间(s) FGC:从应用程序启动到采样时old代(全gc)gc次数 FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s) GCT:从应用程序启动到采样时gc用的总时间(s) NGCMN:年轻代(young)中初始化(最小)的大小 (字节) NGCMX:年轻代(young)的最大容量 (字节) NGC:年轻代(young)中当前的容量 (字节) OGCMN:old代中初始化(最小)的大小 (字节) OGCMX:old代的最大容量 (字节) OGC:old代当前新生成的容量 (字节) PGCMN:perm代中初始化(最小)的大小 (字节) PGCMX:perm代的最大容量 (字节) PGC:perm代当前新生成的容量 (字节) S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比 S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比 E:年轻代中Eden(伊甸园)已使用的占当前容量百分比 O:old代已使用的占当前容量百分比 P:perm代已使用的占当前容量百分比 S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节) S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节) ECMX:年轻代中Eden(伊甸园)的最大容量 (字节) DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满) TT: 持有次数限制 MTT : 最大持有次数限制 jstack pid java查看java程序的状态 grep 正则输出 grep -o -E "[0-9]{11}" xx.log cat error.log |grep 'Failed to invoke the method'|grep '2015-12-08 20'|awk -F'Failed to invoke the method' '{print $2}'|awk '{print $1}' |sort|uniq -c 删除某些文件 find ./ -name 'xx.log' |xargs rm -rf 删除某个文件外的其它文件 ls | grep -v keep | xargs rm #删除keep文件之外的所有文件 说明: ls先得到当前的所有文件和文件夹的名字, grep -v keep,进行grep正则匹配查找keep,-v参数决定了结果为匹配之外的结果,也就是的到了keep之外的所有文件名,然后 xargs用于从 标准输入获得参数 并且传递给后面的命令,这里使用的命令是 rm,然后由rm删除前面选择的文件 查看磁盘信息 查看当前文件夹下所有文件大小(包括子文件夹) ➜ ~ du -sh 47G . 查看指定文件夹大小 # du -hs ftp 6.3G ftp 查看磁盘空间大小命令 df -h Df命令是linux系统以磁盘分区为单位查看文件系统,可以加上参数查看磁盘剩余空间信息,命令格式: df -hl 显示格式为: 文件系统 容量 已用 可用 已用% 挂载点 Filesystem Size Used Avail Use% Mounted on /dev/hda2 45G 19G 24G 44% / /dev/hda1 494 df -h Df命令是linux系统以磁盘分区为单位查看文件系统,可以加上参数查看磁盘剩余空间信息,命令格式: df -hl 显示格式为: 文件系统 容量 已用 可用 已用% 挂载点 Filesystem Size Used Avail Use% Mounted on /dev/hda2 45G 19G 24G 44% / gz解压 gzip -x ... proc 启动应用程序时,找不到log去哪了 ls -l /proc/63220/fd|grep log 查看内存信息 #linux 下安装软件 yum 指定源进行安装 yum install 软件名 --enablerepo=安装包地址 重新安装JDK 删除JDK: rpm -qa | grep jdk|xargs sudo rpm -e --nodeps download jdk wget -c -P ./ http://download.oracle.com/otn-pub/java/jdk/8u65-b17/jdk-8u65-linux-x64.rpm?AuthParam=1448637274_af870ccf6c2c78750a5977e6da301744 安装 以JDK1.8为例 拷贝到/usr/share下,mv jdk-8u65-linux-x64.rpm /usr/share 用rpm -ivh命令安装 配置环境变量 在/etc/profile下增加 # set Java environment JAVA_HOME=/usr/share/jdk-8u65-linux-x64 PATH=$JAVA_HOME/bin:$PATH CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export JAVA_HOME export PATH export CLASSPATH 测试 [root@localhost ~]# echo $JAVA_HOME /usr/share/jdk1.6.0_43 [root@localhost ~]# echo $PATH /usr/share/jdk1.6.0_43/bin:/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin [root@localhost ~]# echo $CLASSPATH .:/usr/share/jdk1.6.0_43/lib/dt.jar:/usr/share/jdk1.6.0_43/lib/tools.jar [root@localhost ~]# java -version java version "1.6.0_43" Java(TM) SE Runtime Environment (build 1.6.0_43-b01) Java HotSpot(TM) 64-Bit Server VM (build 20.14-b01, mixed mode) Managing Java sudo update-alternatives --config java 有 2 个候选项可用于替换 java (提供 /usr/bin/java)。 数据库操作 mysql授权 ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY 'root' WITH GRANT OPTION; 安装apt-gethttp://everyday-tech.com/apt-get-on-centos/ lsoflsof语法格式是:lsof [options] filename复制代码 lsof abc.txt 显示开启文件abc.txt的进程lsof -c abc 显示abc进程现在打开的文件lsof -c -p 1234 列出进程号为1234的进程所打开的文件lsof -g gid 显示归属gid的进程情况lsof +d /usr/local/ 显示目录下被进程开启的文件lsof +D /usr/local/ 同上,但是会搜索目录下的目录,时间较长lsof -d 4 显示使用fd为4的进程lsof -i 用以显示符合条件的进程情况lsof -i[46] [protocol][@hostname|hostaddr][:service|port] 46 –> IPv4 or IPv6 protocol –> TCP or UDP hostname –> Internet host name hostaddr –> IPv4地址 service –> /etc/service中的 service name (可以不止一个) port –> 端口号 (可以不止一个) traceroute IP监控某台机器到某IP的链路的连通性 nohup ping -W 1 172.31.xx.xx &>/tmp/ping.log & crontab -e * * * * * echo "`date +%d-%H:%M`" >> /tmp/ping.log","categories":[{"name":"扩展知识","slug":"扩展知识","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/"},{"name":"linux","slug":"扩展知识/linux","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/linux/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://example.com/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"Django,Mysql,空间数据是通过Google地图和高德地图进行采集的","slug":"Django+Mysql+实现周边查询","date":"2015-09-01T16:00:00.000Z","updated":"2022-09-03T10:12:16.073Z","comments":true,"path":"2015/09/02/Django+Mysql+实现周边查询/","link":"","permalink":"http://example.com/2015/09/02/Django+Mysql+%E5%AE%9E%E7%8E%B0%E5%91%A8%E8%BE%B9%E6%9F%A5%E8%AF%A2/","excerpt":"","text":"##环境 Django,Mysql,空间数据是通过Google地图和高德地图进行采集的 $ mysql -Vmysql Ver 14.14 Distrib 5.6.20, for osx10.9 (x86_64) using EditLine wrapper ##需求描述 mysql中存储了一些地理空间点类型的数,要进行周边查询。 ##MySQL空间相关的局限性 MySQL空间扩展的功能仅支持包络框相关的操作(MySQL称之为最小边框,或简称为 MBR)。也就是说,MySQL符合OGC标准。 目前,MySQL没有实现Contains,Crosses,Disjoint,Intersects,Overlaps,Touches函数,可以通过MBR来实现同样效果操作。 也就是说,在MySQL进行如contains类似的空间查询时,可以通过bbcontains来实现同样效果的操作。 注意: 只有MyISAM引擎的MySQL表才真正的支持空间索引(R-trees)。也就是说,当你要使用MySQL提供的空间扩展时,你要在快速查询空间数据和数据的完整性之间做一个选择 - MyISAM的表不支持事务和外键约束。 ##数据库配置 空间表引擎 Engine:InnoDB ###创建数据库 mysql>GRANT ALL PRIVILEGES ON *.* TO project_test@localhost IDENTIFIED BY 'project_test' WITH GRANT OPTION; mysql>GRANT ALL PRIVILEGES ON *.* TO project_test@"%" IDENTIFIED BY 'project_test' WITH GRANT OPTION; 第一句增加了一个 project_test 用户授权通过本地机(localhost)访问,密码“project_test”。 第二句则是授与 project_test 用户从任何其它主机发起的访问(通配符%)。 ###配置数据源 'default': { 'ENGINE': 'django.contrib.gis.db.backends.mysql', 'NAME': 'project_test', # Or path to database file if using sqlite3. 'USER': 'project_test', 'PASSWORD': 'project_test', 'HOST': '127.0.0.1', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. 'PORT': '3306', # Set to empty string for default.}, ##数据模型的构建 from django.contrib.gis.db import models as gismodelsclass AppPoint(gismodels.Model): description = gismodels.TextField(verbose_name=_(u"描述信息"), max_length=500, blank=True, null=True) point = gismodels.PointField(spatial_index=False) objects = gismodels.GeoManager() Django Geographic framework 1.7GeoDjango打算做世界级的地理学Web框架。它的目标是尽可能方便是的利用强大空间数据构建GIS Web 应用。 ###GeoQuerySet API class GeoQuerySet([model=None]) ####空间查询 正如使用QuerySet API时一样,在过滤器链(chaining filters)上加上GeoQuerySet进行筛选。除了通常的字段(Field lookups)查询,它还提供了空间字段GeometryField的查询。 可以在这里查看空间查询介绍 下面Django对不同数据库 空间查询操作支持统计表: Lookup Type PostGIS Oracle MySQL [7] SpatiaLite bbcontains X X X bboverlaps X X X contained X X X contains X X X X contains_properly X coveredby X X covers X X crosses X X disjoint X X X X distance_gt X X X distance_gte X X X distance_lt X X X distance_lte X X X dwithin X X equals X X X X exact X X X X intersects X X X X overlaps X X X X relate X X X same_as X X X X touches X X X X within X X X X left X right X overlaps_left X overlaps_right X overlaps_above X overlaps_below X strictly_above X strictly_below X ####我这里只关注一下对mysql的空间操作支持 按我们的需要我们选用 within bbcontains 支持:PostGIS,MySQL,SpatiaLite 查询数据库中空间数据的bbox包含在指定的空间bbox内的数据。 数据库 操作 PostGIS poly ~ geom MySQL MBRContains(poly,geom) SpatiaLite MbrContains(poly,geom) bboverlaps 支持:PostGIS,MySQL,SpatiaLite 查询数据库中空间数据的bbox与指定的空间bbox相交的数据。 数据库 操作 PostGIS poly && geom MySQL MBROverlops(poly,geom) SpatiaLite MbrOverlops(poly,geom) contained 支持:PostGIS,MySQL,SpatiaLite 查询数据库中空间数据的bbox完全包含指定的空间bbox的数据。 数据库 操作 PostGIS poly @ geom MySQL MBRWithin(poly,geom) SpatiaLite MbrWithin(poly,geom) contains 支持:PostGIS,Oracle,MySQL,SpatiaLite Example: Zipcode.objects.filter(poly__contains=geom) 查询数据库中空间数据包含指定的空间图形的数据。 数据库 操作 PostGIS ST_Contains(poly, geom) Oracle SDO_CONTAINS(poly, geom) MySQL MBRContains(poly, geom) SpatiaLite Contains(poly, geom) disjoint 支持:PostGIS,Oracle,MySQL,SpatiaLite Example: Zipcode.objects.filter(poly__disjoint=geom) 查询数据库中与指定的空间图形相离的空间数据。 数据库 操作 PostGIS ST_Disjoint(poly, geom) Oracle SDO_GEOM.RELATE(poly, geom) MySQL MBRDisjoint(poly, geom) SpatiaLite Disjoint(poly, geom) equals 支持:PostGIS,Oracle,MySQL,SpatiaLite exact,same_as 支持:PostGIS,Oracle,MySQL,SpatiaLite intersects 支持:PostGIS,Oracle,MySQL,SpatiaLite 查询数据库中与指定的空间图形相交的空间数据。 Example: Zipcode.objects.filter(poly__intersects=geom) 数据库 操作 PostGIS ST_Intersects(poly, geom) Oracle SDO_OVERLAPBDYINTERSECT(poly, geom) MySQL MBRIntersects(poly, geom) SpatiaLite Intersects(poly, geom) overlaps 支持:PostGIS,Oracle,MySQL,SpatiaLite touches 支持:PostGIS,Oracle,MySQL,SpatiaLite Example: Zipcode.objects.filter(poly__touches=geom) 查询与指定的空间几何图形相接的数据。 数据库 操作 PostGIS ST_Touches(poly, geom) Oracle SDO_TOUCH(poly, geom) MySQL MBRTouches(poly, geom) SpatiaLite Touches(poly, geom) within 支持:PostGIS,Oracle,MySQL,SpatiaLite Example: Zipcode.objects.filter(poly__within=geom) 查询包含在指定的空间几何图形中的数据。 数据库 操作 PostGIS ST_Within(poly, geom) Oracle SDO_INSIDE(poly, geom) MySQL MBRWithin(poly, geom) SpatiaLite Within(poly, geom) 现在知道了要用 within 来查询数据,另一个问题来了,如何生成半径大小为R中心坐标为(x,y)的geom呢。 ####创建空间几何图形 可以通过多种方式创建GeosGeometry。第一种方法,就是通过一些参数直接实例化。 下面是分别通过WKT,HEX,WKB和GeoJSON方式直接创建 Geometry 的方法: In [30]: pnt = GEOSGeometry('POINT(5 23)')In [31]: pnt = GEOSGeometry('010100000000000000000014400000000000003740')In [32]: pnt = GEOSGeometry(buffer('\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x14@\\x00\\x00\\x00\\x00\\x00\\x007@'))In [33]: pnt = GEOSGeometry('{ "type": "Point", "coordinates": [ 5.000000, 23.000000 ] }') # GeoJSON 另一种方式就是通过特定类型的空间几何对象的构造器来进行创建该类型的Geometry实例 In [34]: from django.contrib.gis.geos import PointIn [35]: pnt = Point(5,23)In [36]: pntOut[36]: <Point object at 0x10735bb50> 最后一种方法就是通过 fromstr()和 fromfile 工厂方法来创建Geometry实例。它们分别接收字符串或文件 In [37]: from django.contrib.gis.geos import fromstr,fromfileIn [38]: pnt = fromstr('POINT(5 23)')In [39]: pnt = fromfile('/path/to/pnt.wkt')In [34]: pnt = fromfile(open('/path/to/pnt.wkt')) ####实现查询周边几何点的功能 通过上面的学习,在Django中实现mysql数据的周边查询只能通过模糊的查询,我们这里通过构建一个包络框进行模糊查询: 构建一个包络框 from django.contrib.gis.geos import (Polygon,Point)point = Point(130,39)buffer=point.buffer(degree) 进行within查询 AppPoint.objects.filter(point__within=buffer) 问题 这里给的半径通常是米为km,但是这个构建buffer的方法需要的参数是一个度。 degree=l*180/(math.pi*6371) ##测试方法和数据 def get_point(point,r): EARTH_R=6378.137 buffer = point.buffer(r*180/(math.pi*EARTH_R)) aps=AppPoint.objects.filter(point__within=buffer) for ap in aps: print ap.point.json,(math.pi*EARTH_R*ap.point.distance(point)/180) 其中点与点间的距离方法distance在django中解释为: Returns the distance between the closest points on this Geometryand the other. Units will be in those of the coordinate system ofthe Geometry. 下面是测试数据: b = [[116.27497,39.95708,2573],[116.48103,39.96657,4292],...[116.13621,39.92686,528],[116.39494,39.87986,138],[116.389,39.8799,2151],[116.4858,39.9361,4709]]创建数据:for b in a: AppPoint.objects.create(description=b.count,point=Point(b[0],b[1])) 输出结果: get_point(Point(116.4,39.8),8){ "type": "Point", "coordinates": [ 116.4214, 39.85925 ] } 7.01270604176{ "type": "Point", "coordinates": [ 116.33663, 39.79076 ] } 7.1289114023{ "type": "Point", "coordinates": [ 116.43555, 39.80307 ] } 3.97213681829{ "type": "Point", "coordinates": [ 116.42803, 39.86696 ] } 8.08069287815{ "type": "Point", "coordinates": [ 116.41776, 39.8526 ] } 6.18016458489{ "type": "Point", "coordinates": [ 116.41467, 39.86627 ] } 7.5557334976{ "type": "Point", "coordinates": [ 116.37254, 39.82765 ] } 4.33799658047{ "type": "Point", "coordinates": [ 116.36128, 39.85648 ] } 7.62292984489{ "type": "Point", "coordinates": [ 116.41574, 39.80051 ] } 1.75308830872{ "type": "Point", "coordinates": [ 116.40075, 39.81592 ] } 1.77417182449{ "type": "Point", "coordinates": [ 116.45339, 39.83341 ] } 7.01111345466{ "type": "Point", "coordinates": [ 116.39799, 39.84366 ] } 4.86535674431{ "type": "Point", "coordinates": [ 116.38116, 39.85952 ] } 6.94973919946{ "type": "Point", "coordinates": [ 116.3385, 39.82914 ] } 7.57577153659{ "type": "Point", "coordinates": [ 116.3777, 39.86207 ] } 7.34200348969{ "type": "Point", "coordinates": [ 116.39454, 39.86518 ] } 7.28121719546{ "type": "Point", "coordinates": [ 116.41095, 39.84127 ] } 4.75311465912 ##总结 from django.contrib.gis.geos import (Polygon,Point) import math point = Point(130,39) EARTH_R=6378.137 buffer = point.buffer(r*180/(math.pi*EARTH_R)) aps=AppPoint.objects.filter(point__within=buffer)``` ##新的问题###上面这种方法得到的是没有排序的结果,目前要进行由近到远进行排序,通过`SQRT(POW( ABS( X(Location) – X(@center)), 2) + POW(ABS(Y(Location) – Y(@center)), 2))`得到一个大致的距离因子,然后根据这个进行排序。 queryset.extra(select={‘distance_factor’: “SQRT(POWER(ABS(X(point) - “+str(x)+”),2) + POWER(ABS(Y(point) - “+str(y)+”),2))”}).order_by(‘distance_factor’) ###在线上遇到了下面的问题: rps[0].point.distance(rps[1].point)python: GeometryComponentFilter.cpp:35: virtual void geos::geom::GeometryComponentFilter::filter_ro(const geos::geom::Geometry*): Assertion `0’ failed. 线上直接使用distance时报错。然后比较了一下python的distance得到的值,其实是和`SQRT(POW( ABS( X(Location) – X(@center)), 2) + POW(ABS(Y(Location) – Y(@center)), 2))`得到的值是一样的。 (‘…..python’, 0.0071949078163964595)(‘self’, 0.0071949078163964595) 故处些最终到终心点的距离使用了 `distance_factor` 来代替。##注意###[经纬度坐标系采用GCJ-2标准,对于Google,高德地图和腾讯地图可以直接使用](http://wangsheng2008love.blog.163.com/blog/static/78201689201461674727642/)###地球坐标系 (WGS-84) 到火星坐标系 (GCJ-02) 的转换算法####下面是通过html5获取坐标,然后转化后的当前我的位置截图![loading](/images/project/xy_trans_position.png =x400)####腾讯高德对其转化都有现成的实现 var a = 6378245.0 var ee = 0.00669342162296594323 function out_of_china(lat,lon){ if (lon < 72.004 || lon > 137.8347) return true if (lat < 0.8293 || lat > 55.8271) return true } function transformlat(x, y) { var result = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)) result += (20.0 * Math.sin(6.0 * Math.PI * x) + 20.0 * Math.sin(2.0 * Math.PI * x)) * 2.0 / 3.0 result += (20.0 * Math.sin(Math.PI * y) + 40.0 * Math.sin(Math.PI / 3.0 * y)) * 2.0 / 3.0 result += (160.0 * Math.sin(Math.PI / 12.0 * y) + 320.0 * Math.sin(Math.PI / 30.0 * y)) * 2.0 / 3.0 return result } function transformlon(x, y) { var result = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)) result += (20.0 * Math.sin(6.0 * Math.PI * x) + 20.0 * Math.sin(2.0 * Math.PI * x)) * 2.0 / 3.0 result += (20.0 * Math.sin(Math.PI * x) + 40.0 * Math.sin(Math.PI / 3.0 * x)) * 2.0 / 3.0 result += (150.0 * Math.sin(Math.PI / 12.0 * x) + 300.0 * Math.sin(Math.PI / 30.0 * x)) * 2.0 / 3.0 return result } function wgs2gcj(wgslat, wgslon) { if (out_of_china(wgslat, wgslon)) { return [wgslat, wgslon] } var lat = transformlat(wgslon - 105.0, wgslat - 35.0) var lon = transformlon(wgslon - 105.0, wgslat - 35.0) var rad_lat = Math.PI / 180.0 * wgslat var magic = Math.sin(rad_lat) magic = 1 - ee * magic * magic var sqrt_magic = Math.sqrt(magic) lat = (180.0 * lat) / (Math.PI * (a * (1 - ee)) / (magic * sqrt_magic)) lon = (180.0 * lon) / (Math.PI * a * Math.cos(rad_lat) / sqrt_magic) var chnlat = wgslat + lat var chnlon = wgslon + lon return [chnlat, chnlon] } ##参考 >0. [JAVSCRIPT Math](http://www.w3school.com.cn/jsref/jsref_obj_math.asp) >1. [MySQL空间数据库–查询点到多点间的最短路径](http://www.javabloger.com/article/mysql-spatial-database.html) >2. [W3 Geolocation API Specification](http://www.w3.org/TR/geolocation-API/#position_interface) >3. [关于百度map和高德map,关于map坐标系](http://wangsheng2008love.blog.163.com/blog/static/78201689201461674727642/) >4. [iOS 火星坐标相关整理及解决方案汇总](http://it.taocms.org/04/507.htm)","categories":[{"name":"方案","slug":"方案","permalink":"http://example.com/categories/%E6%96%B9%E6%A1%88/"}],"tags":[{"name":"python","slug":"python","permalink":"http://example.com/tags/python/"},{"name":"GIS","slug":"GIS","permalink":"http://example.com/tags/GIS/"},{"name":"mysql","slug":"mysql","permalink":"http://example.com/tags/mysql/"}]},{"title":"第三章 ionic 实例 Trendicity","slug":"ionic 第三章 Trendicity 项目实例","date":"2015-08-30T16:00:00.000Z","updated":"2022-09-03T10:12:16.072Z","comments":true,"path":"2015/08/31/ionic 第三章 Trendicity 项目实例/","link":"","permalink":"http://example.com/2015/08/31/ionic%20%E7%AC%AC%E4%B8%89%E7%AB%A0%20Trendicity%20%E9%A1%B9%E7%9B%AE%E5%AE%9E%E4%BE%8B/","excerpt":"","text":"现在在你的工具箱中有很多工具,让我们来谈谈开发一个真正的手机应用。Instagram是一个非常受欢迎的照片分享应用。使这些照片更加有趣并且展示一些Ionic功能,我们将会涉及到一个我们开发应用,它叫 Trendicity。这个应用以多种方式获取和展示Instagram的照片。 一种方式就是根据照片的位置在地图上进行展示。另一种方式就是将照片展示在可以滑动的卡片上,并且用户可以标示出喜欢的照片。最后一种方式,我们以列表的形式展示照片和更多有关照片信息。 这个完整的Trendicity应用可以在Github上找到。你可以下载它并在你的浏览器或者设备或仿真器上运行。 Trendicity应用是作为一个side menu 应用建成的,可是,它也很好的结合了tabs方式。我们将会从几方面去讨论这个应用。Side Menu和用户的操作选项,搜索功能,和加载服务的使用和(地图示图,卡片视图和列表视图)tabs。 我们通过构建这个应用来深入了解代码。 ##Side menu 侧边菜单由以下菜单项构成: HOME Favorites About Login/Logout 下面是Trendicity侧边菜单的截图。关于怎么样实现侧边菜单和路由时一直带着它的细节,请看第四章:实现侧边菜单和配置路由。 ##Home HOME菜单选项点击时会调用HomtCtrl和展示地图Tab。地图上将会展示附近的一些图片。 ##Favorites 大体上,Trendicity应用的收藏功的实现有三部分组成:FavoritesService,FavoritesCtrl和favorites.html模版。 收藏功能有两种类型的照片会进行收存放:用户生成的和自己收藏的。前者是通过add-favorite模态窗口添加的,后者是通过地址位置直接关联到Instagram相片的。 这一部分,我们将会实际操作创建和删除收藏,看一下收藏功能是如何实现及FavoritesCtrl和FavoritesServices之间是如何交互的。 ###收藏列表 收藏数据在一个列表中展现。当用户进入这个视图,会触发$ionicView.enter事件,这个视图中的数据将自动刷新: $scope.$on('$ionicView.enter',function(){ // Update favorites $scope.favorites = FavoritesService.getFavorites();}); 列表中的每一条收藏条目点击时都会在此相片的地理位置为中心定位到地图上。下面是收藏视图中展示收藏数据的代码: <ion-list> <ion-item class="item-icon-right" ng-repeat="favorite in favorites track by favorite.id" ui-sref="app.home.map({ latitude: favorite.lat, longitude: favorite.lng })"> \\{\\{ favorite.city \\}\\} <i class="icon ion-ios-arrow-forward icon-accessory"></i> <ion-option-button class="button-assertive" ng-click="deleteFavorite(favorite)"> Remove </ion-option-button> </ion-item></ion-list> 添加一些友好的提示信息,当没有收藏信息要展示时,做如下操作: <!-- Display message when no favorites exist --> <div class="vertical-center-container" ng-show="!favorites.length"> <div class="vertical-center text-center"> <h1><i class="icon ion-heart-broken assertive"></i></h1> <p>Looks like you haven't favorited anything yet...</p> </div> </div> ###添加收藏 为了保持代码的可维护性,我们决定将开发添加收藏功能的的代码进行解藕合。因此,我们将它拆分成了 FavoritesCtrl,FavoritesService,add-favorite.html模版,和add-favorite-form指令,让它们组合到一起完成这个功能。 添加收藏的动作,从点击右上角的’+’号打开一个添加收藏的模态窗口。 <ion-nav-buttons side="right"> <button class="button button-icon ion-ios-plus-empty" ng-click="showModalAddFavorite()"></button></ion-nav-buttons> 点击这个按钮会触发FavoriteCtrl的showModalAddFavorite()方法,它会打开添加收藏的模态窗口。 $scope.showModalAddFavorite = function(){ $scope.modalAddFavorite.show();} 模态窗口中的内容都是有add-favorite-form指令负责生成。 <ion-content> <add-favorite-form on-submit="addFavorite(favorite)"></add-favorite-form></ion-content> ![添加收藏](/images/ionic/add_favorite.png =300x) addFavoriteForm指令会在其内部处理onSubmit。当这个表单填写的没有问题提交时,会调用addFavoriteForm中的submit方法。也就是说,一旦表单没有问题,指令会调用submit方法,通过调用他的on-submit属性。 现在,指今如何知道去哪里找到addFavorite(favorite)方法呢?当我们初始化模态时,会将FavoriteCtrl的作用域一并传入。因此,这个模态的作用域是继承自它的父作用域(即收藏视图的作用域); $ionicModal.fromTemplateUrl('templates/modals/add-favorite.html', { scope: $scope }).then(function(modal) { $scope.modalAddFavorite = modal; }); 这样,addFavorite()方法已经与控制器的作用域相关联: // Add a new favorite using the service$scope.addFavorite = function(favorite) { FavoritesService.add(favorite).then(function () { $scope.favorites = FavoritesService.getFavorites(); $scope.hideModalAddFavorite(); });}; 当调用FavoritesService’s add()方法后,将会更新收藏列表并具隐藏add-favorite模态。 ###添加收藏表单指令 addFavoriteForm指令使用templates/directives/add-favorite-form.html作为它的模版。这个视图包含了表单的基础验证: <form name="formAddFavorite" no-validate> <label class="item item-input item-stacked-label" ng-class="{ 'item-error': formAddFavorite.$attempt && formAddFavorite.city.$invalid, 'item-valid': formAddFavorite.city.$valid }"> <span class="input-label">City</span> <input type="text" name="city" ng-model="favorite.city" placeholder="Dallas" required="required"> </label> <label class="item item-input item-stacked-label" ng-class="{ 'item-error': formAddFavorite.$attempt && formAddFavorite.region.$invalid, 'item-valid': formAddFavorite.region.$valid }"> <span class="input-label">State or Country</span> <input type="text" name="region" ng-model="favorite.region" placeholder="TX" required="required"> </label> <button class="button button-full button-positive" type="button" ng-click="submit()">Add</button></form> “Add”按钮会调用命令的submit()方法,会添加一个$attempt字段到表单中,标示用户已经最近已经提交了一次这个表单。这个字段被用来高亮不合法和没有输入的输入选项。 最后,当这个表单验证通过后,指令会通过模态中的一个属性调用onSubmit()方法: $scope.submit = function() { $scope.formAddFavorite.$attempt = true; if ($scope.formAddFavorite.$valid) { $scope.onSubmit({ favorite: $scope.favorite }); } }; ![合法的表单](/images/ionic/valid_form.png =300x) 而且,在这个模态关闭时,其中添加收藏的表单会自动清空。它是通过监听modal.hidden事件来进行实现的: $scope.$on('modal.hidden', function() { // Clear form $scope.favorite = null; $scope.formAddFavorite.$attempt = false; $scope.formAddFavorite.$setPristine(true); }); ###删除收藏 当你滑动收藏条目时就可以进行删除收藏操作。 ![删除收藏](/images/ionic/remove_operate.png =300x) 点击删除按钮,将会调用FavoritesCtrl的deleteFavorite()方法删除当前收藏的引用。在控制器这边,这个方法会调用FavoritesService的delete方法,将收藏列表中的当前操作的收藏项删除。 // Delete a favorite using the service and update scope var$scope.deleteFavorite = function (favorite) { $scope.favorites = FavoritesService.delete(favorite);}; ###收藏服务 FavoritesService是用标准方式来处理Trendicity中与收藏相关的服务。它将收藏相关逻辑与Trendicity其它功能隔离开。用这种方式的另一个好处就是FavoritesService服务可以在任何一个 Controller中使用。像我们在列表视图中实现的那样,这个服务被用来收藏自己喜欢的地理位置。 这个服务提供了三个方法:add(),getFavorites()和delete() 正如它们的名字叫的那样,getFavorites()会直接返回当前本地的所有收藏。 添加收藏时的处理方式根据收藏的类型而定。对于人们经常收藏的城市或区域,这个类型的收藏会用GeolocationService的addressToPosition()方法获取地理坐标。这个实现我们在稍后章节中会进行讨论。当获取到了地址经纬度,这个收藏会统一格式化一种结构存储到本地的收藏数组中。 var address = favorite.city + ', ' + favorite.region; return GeolocationService.addressToPosition(address).then(function (data) { var newLocation = { id: favoritesId, city: address, lat: data.latitude, lng: data.longitude }; favorites.push(newLocation); localStorageService.set('Favorites', favorites); }); 针对具体的收藏,收藏会在列表视图的控制器中进行合适的格式化处理,和用户添加的收藏的处理过程相信,存放到本地收藏数据中。 var newLocation = { id: favoritesId, city: address, lat: data.latitude, lng: data.longitude};favorites.push(newLocation);localStorageService.set('Favorites', favorites); 为了确保准确定位,这个服务为第一个收藏生成了一个对应的ID。每一个id都是根据当前系统的时间生成的: this.add = function (favorite) { var favorites = this.getFavorites() || []; var favoritesId = new Date().getTime();...}; 删除收藏的工作通过使用lodash的remove()方法实现。删除了收藏后,更新本地收藏并将最新的收藏列表返回。 // DELETEthis.delete = function (favorite) { var favorites = this.getFavorites(); _.remove(favorites, favorite); // jshint ignore:line localStorageService.set('Favorites', favorites); return this.getFavorites();}; ##About ‘About’菜单选项点击时会滑出界面,包含一些应用的介绍信息。这个页面会在应用程序第一次加载时显示。 ##Login/Logout 当用户还没有登陆要登陆时,点击菜单的’Login’时会显示登陆界面。一旦用户登陆后,再点开菜单,’Logout’选项将出展示。 下面是www/templates/menu.html部分代码片断,展示了如何用ng-show和ng-hide实现上述功能。 <ion-item menu-close class="item-icon-left" ng-click="login()" ng-hide="isLoggedIn()"> <i class="icon ion-log-in"></i> Login</ion-item><ion-item menu-close class="item-icon-left" ng-click="logout()" ng-show="isLoggedIn()"> <i class="icon ion-log-out"></i> Logout</ion-item> 你可以在www/controllers/app.js文件中找到isLoggedIn,login和logout方法: // Determine if the user is logged into Instagram$scope.isLoggedIn = function() { return InstagramService.isLoggedIn();};// Open the login modal$scope.login = function() { $scope.loginModal.show();};// Perform the logout action when the user invokes the logout link$scope.logout = function() { InstagramService.logout();}; isLoggedIn方法会调用InstagramService的isLoggedIn()方法来获取当前登陆状态。退出方法会调用InstagramService的logout()方法来进行登记退出动作。我们会在第八章讲解更多关于InstagramService相关的内容。 为了做更多和Instagram相关并有趣的事情,我们需要用户登陆到Instagram。为了实现这个,我们使用一个$ionicModal显示为什么让用户登陆的简要的描述。当选择了登陆选项,login方法被调用,用户会看到一个登陆模态。 ![登陆截图](/images/ionic/login.png =300x) // Create the login modal that we will use later$ionicModal.fromTemplateUrl('templates/modals/login.html', { scope: $scope, animation: 'slide-in-up'}).then(function(modal) { $scope.loginModal = modal;});// Triggered in the login modal to close it$scope.closeLogin = function() { InstagramService.loginCancelled(); $scope.loginModal.hide();};//Cleanup the modal when we're done with it!$scope.$on('$destroy', function() { $scope.loginModal.remove();}); 通常,登陆页面像下面截图中展示一样。但是由于我们要与Instagram集成,我们需要OAuth的方式进行登陆。 <ion-modal-view id="login"> <ion-header-bar class="bar-transparent"> <div class="buttons"> <button class="button button-light button-clear button-icon button-right icon ion-close" ng-click="closeLogin()"></button> </div> </ion-header-bar> <ion-content scroll="false"> <div class="vertical-center-container"> <div class="vertical-center"> <div class="row light-bg"> <div class="col"> <h3 class="text-center">Login with Instagram to</h3> <ul class="text-center"> <li><i class="icon ion-heart assertive"></i> Like Posts</li> <li><i class="icon ion-images"></i> View Your Feed</li> <li><i class="icon ion-star energized"></i> View Your Liked Posts</li> </ul> <button class="button button-block button-positive button-outline icon-left ion-social-instagram-outline" ng-click="loginToInstagram()">Login with Instagram</button> </div> </div> </div> </div> </ion-content></ion-modal-view> 用Oauth代替传统的登陆方式,让用户知道一旦登陆到了Instagram,他们能做哪些事情。一旦用户确定登陆到Instagram,我们会调用www/controllers/app.js中的loginToInstagram方法: // Perform the OAuth login to Instagram$scope.loginToInstagram = function() { $scope.loginModal.hide(); InstagramService.login();}; 这个方法将会用InAppBrowser Cordova插件执行OAuth授权。我们会使用这个插件打开一个登陆到Instagram窗口。Trendicity应用没有对用户名和密码进行处理。 ![Instagram登陆](/images/ionic/instagram_login.png =300x) ##Search 搜索图标就是在列表或卡片视图的右上角的放大镜图标。用户有以下查询选项: Trending - 按当前在Instagram上帖子的受欢迎程序进行排序。 Nearby - 当前用户所在位置1KM范围内发布的帖子。 My Feed - 当前用户提供给Instagram的信息。 My Liked Posts -当前用户喜欢的帖子。 ![search](/images/ionic/search.png =300x) 在HomeCtrl中,我们设置了查询功能。为了关注搜索功能,下面代码进行稍微的压缩。 首先,我们定义了一些作用域变量来保存帖子和查询变量。我们使用了两个javascript对象来存放,所以当值发生变化时,对象不会丢失。 $scope.model = PostsService.getModel();$scope.search = {value:POST_TYPE.NEARBY}; 接下来,我们设置$scope.getPosts方法,它将会根据查询值决定哪些查询方法会被调用。 $scope.getPosts = function(value) { if (value === POST_TYPE.TRENDING) { PostsService.findPopularPosts(); } else if (value === POST_TYPE.NEARBY) { $scope.findNearbyPosts(); } else if (value === POST_TYPE.USER_FEED) { PostsService.findUserFeedPosts(); } else if (value === POST_TYPE.LIKED) { PostsService.findLikedPosts(); }}; 通过$watch监听ionicPopover中查询变量值的变化来及时调用$scope.getPosts方法来进行查询。 $scope.updatePosts = function (searchValue) { $scope.getPosts(searchValue); $scope.closePopover(); $ionicScrollDelegate.scrollTop();};$scope.$watch('search.value', function(newValue) { // Triggered when user changes search value $scope.updatePosts(newValue);}); 为了实现ionicPopover,我们首先需要通过 fromTemplateUrl 方法加载 html 模版。我们在这里将其作用域设置为当前作用域。当template加载时,我们将popover设置到这个作用域中,方便我们后面进行调用。 $ionicPopover.fromTemplateUrl('templates/search.html', { scope: $scope,}).then(function(popover) { $scope.popover = popover;}); 下面这段 search.html 模版片段。我们使用了ion-popover-view 组件定义这个视图。我们使用 ion-header-bar 组件展示弹出窗口的标题。我们将 ion-radio 组件列表放到 ion-content 组件中。ion-radio组件选择后,会作为搜索参考值。 <ion-popover-view> <ion-header-bar> <h1 class="title">Search</h1> </ion-header-bar> <ion-content> <ion-radio ng-model="search.value" value="TR">Trending</ion-radio> <ion-radio ng-model="search.value" value="NB">Nearby</ion-radio> <ion-radio ng-model="search.value" value="UF">My Feed</ion-radio> <ion-radio ng-model="search.value" value="LP">My Liked Posts</ion-radio> </ion-content></ion-popover-view> 当用户点击在templates/home.html中定义的导航按钮时 这个 ionicPopover 将会弹出。下面地这段代码: <ion-nav-buttons side="right"> <button class="button button-icon icon ion-ios-search" ng-click="openPopover($event)"></button></ion-nav-buttons> 由上面代码可以看到,当用户点击Search图标时,会调用openPopover方法。我们要做的就是让popover显示。当选择了一个值时,将会调用closePopover方法,在这个方法中,我们要确认popover是已经定义并是打开的。如何条件满足,我们就将其关闭。最后一件事情就是为popover设置一个$destroy监听,当popover被从Dom中移除时调用。 $scope.openPopover = function($event) { $scope.popover.show($event); }; $scope.closePopover = function() { if ($scope.popover && $scope.popover.isShown()) { $scope.popover.hide(); } }; // Cleanup the popover when we're done with it! $scope.$on('$destroy', function() { if ($scope.popover) { $scope.popover.remove(); } }); ##加载服务 当照片进行加载时,用户会不知道应用在做什么,我们应该通知用户当前是在加载数据。我们可以使用$ionicLoading组件来完成这件事情。 ![loading](/images/ionic/ionic_loading.png =300x) 一种聪明的方式就是将 $ionicLoading 组件调用用放在一个HTTP拦截器中。下面是我们压缩后的代码: .factory('TrendicityInterceptor', function ($injector, $q) { var hideLoadingModalIfNecessary = function() { var $http = $http || $injector.get('$http'); if ($http.pendingRequests.length === 0) { $injector.get('$ionicLoading').hide(); } }; return { request: function(config) { $injector.get('$ionicLoading').show(); ... requestError: function(rejection) { hideLoadingModalIfNecessary(); return $q.reject(rejection); }, response: function(response) { hideLoadingModalIfNecessary(); return response; }, responseError: function(rejection) { hideLoadingModalIfNecessary(); return $q.reject(rejection); } }; }); 当HTTP请求发生时,request方法将会被调用。这里我们显示文字 “Loading”。为了避免依赖死循环,我们需要用Angular $injector组件来获取$ionicLoading服务。 当HTTP请求出现错误时,这里会调用requestError方法。我们会调用hideLoadingModalIfNecesary()方法,这个方法会检查是否有HTTP请求在被调用,如果没有ionicLoading服务会将自己隐藏。 当HTTP响应成功时,会调用response方法。之后做的事情与错误时的处理方法类似。 当HTTP响应错误时,会调用responseError方法。之后做的事情与上面两种情况处理方法类似。 为了使我们的HTTP拦截器生效,我们需要让AngularJS知道它的存在。一般我们会在www/app.js中做这件事情。这里是我们将拦截器通过$httpProvider添加到拦截器数据中的方法: .config(function($httpProvider) { $httpProvider.interceptors.push('TrendicityInterceptor');}) 注意,这里我们没有显示真正的提示信息,只是在www/app.js中设置了一个默认信息: .constant('$ionicLoadingConfig', { template: '<ion-spinner></ion-spinner>Loading...'}) 我们在第七章会详细讲解:设计应用。现在,我们只是想告诉你为什么不向ionicLoading show方法中传入信息。 ##Map视图选项 将当前提交的信息在一个可以交互的地图上展示。一个标记会将用户的位置标在地图上。别的照片按它的位置展现在地图上。当点击这个这个标记时,相关联的照片会展现出来。如果你移动地图到一个位置 ,然后点击右上角的刷新图标;就会加载当前位置周边的照片。若想了解如何实现的,请看第五章:在ionic上集成地图。 ##Card实图选项 卡片视图将以卡片叠加的方式来展现照片。若用户不喜欢当前的照片,可以将图片向左侧拖拽或划动。如果喜欢的话可以向右侧拖拽或划动。 这些可以摆动的卡券与非常受欢迎的手机应用Tinder相似。这种类似的实用的功能在Tinder中经常会被用到。 Drify(该公司负责我们的ionic技术)创始人之一Max Lynch已经创建了一个划动卡片的库,这个库经常会被ionic开发团队引用。在ionic中这样的效果展示通常会比较受欢迎的。 这个类库用CSS的动画来实现卡片左右拖拽和摆动的效果。他也使用了一个物理样式动化效果库Collide来实现将图片下拉时的动画效果。 在使用Tinder卡片库之前,一个非常相似的卡片划动效果被创建。我们只是在这里提一下它,在你看到这块时不要好奇。我们在这里没有涉及到这个库。 ###工作文件 与该视图相关的文件位置在:www/templates/tab-card.html,www/templates/card-intro.html,www/js/controllers/card.js和www/js/directives/no-touch-move.js。 ###popup介绍 当导航到卡片实图时,我们决定弹出展示说明信息,来向用户介绍和让他们熟悉如何使用卡片的操作。 我们使用$ionicPopup服务来展示Ionic样式的弹出窗信息。这个服务允许你定义四种类型的弹出窗: show(选项) :通过加载的选项设置来完全自定义弹出窗 alert(选项) :带有一个按钮的弹出窗 confirm(选项):带有确认信息和“取消”和“确认”铵钮的弹出窗 prompt(选项):和confirm一样,但是多出一个与用户交互的输入框。 为我们Trendicity应用,自定义了一个弹出窗如: // Show explanation message$ionicPopup.show({ title: 'Swipe Cards', templateUrl: 'templates/popups/card-intro.html', scope: $scope, buttons: [ { text: 'Next', type: 'button-positive', onTap: function(e) { if (slidebox.currentIndex() === 0) { // Go to next slide slidebox.next(); // Change button text e.target.innerHTML = 'OK'; e.preventDefault(); } else { // Close popup return; } } } ]}); 弹出窗的信息体部分通过template和templateUrl选项进行定义。我们这里单独定义这个模版文件:templates/card-intro.html 我们的模版文件中包含一个划动的盒子,可以流畅的在照片与照片之间进行划动。 <ion-slide-box does-continue="false" show-pager="false" delegate-handle="card-intro-slidebox" ng-init="disableSlideBox()"> <ion-slide> <img class="full-image" src="img/swipe-right.png" /> </ion-slide> <ion-slide> <img class="full-image" src="img/swipe-left.png" /> </ion-slide></ion-slide-box> 这个盒子组件是由AngularJS服务$ionicSlideBoxDelegate生成。这个服务允许你控制这个组件的行为,如划动时的效果和控制是否自动播放。另外当再次渲染这个盒子时调用update()方法。 我们这里,禁止照片的自动切换和用户划动,而所有的操作只能通过铵钮来进行完成。 // Disable intro slidebox sliding $scope.disableSlideBox = function() { $ionicSlideBoxDelegate.enableSlide(false); }; 弹出窗口提供了一个回调onTap,这里可以让用户去自己做一些事情。如果当前是第一个照片,点击“Next”铵钮,我们通过调用$ionicSlideBoxDelegate.$getByHandle(‘card-intro-slidebox’)实例的next()打开下一张照片,并且修改铵钮的文字为”OK”同时防止弹出窗口关闭。另外,当在最后一个照片时,我们会调用return并关闭弹出窗口。 最后,每次用户访问卡片视图时,我们会查看一下本地存储seenCardIntro的值来判断用户是否已经看这个卡片。 ###Card视图 在www/templates/tab-card.html文件中,我们会展示卡片集合。我们通过ng-repeat来迭代显示照片。让后我们定义一些属性来响应用户对卡片的操作。 <ion-view title="Card View" cache-view="false" no-scroll> <ion-content class="has-header padding"> <div ng-if="model.currentPosts.length"> <td-cards> <td-card ng-repeat="post in model.currentPosts" on-destroy="cardDestroyed($index)" on-transition-left="cardTransitionedLeft($index)" on-transition-right="cardTransitionedRight($index)"> <div class="image"> <div class="no-text">NOPE</div> <img ng-src="{{ post.images.low_resolution.url }}" /> <div class="yes-text">LIKE</div> </div> </td-card> </td-cards> </div> </ion-content></ion-view> ###Card视图对应的controller 我们需要CardViewCtrl使侧边滑出菜单失效,因为它会与Tinder卡片功能冲突 。这里我们通过$ionicSidemenuDelegate来设置当内容进入视图时拖拽不可用。当离开视图时,我们恢复可用,使别的视图下可以打开侧边菜单。 .controller('CardViewCtrl', function ( $scope, $ionicSideMenuDelegate, $ionicPopup, $ionicSlideBoxDelegate, $timeout, $ionicHistory, localStorageService, PostsService, InstagramService) { // Disable side-menu drag so that it doesnt interfere with our tinder cards functionality $scope.$on('$ionicView.enter', function() { $ionicHistory.clearHistory(); $ionicSideMenuDelegate.canDragContent(false); }); $scope.$on('$ionicView.leave', function() { $ionicSideMenuDelegate.canDragContent(true); }); if (!localStorageService.get('seenCardIntro')) { // Mark intro as seen localStorageService.set('seenCardIntro', true); var slidebox = $ionicSlideBoxDelegate.$getByHandle('card-intro-slidebox'); // Disable intro slidebox sliding $scope.disableSlideBox = function() { $ionicSlideBoxDelegate.enableSlide(false); }; // Show explanation message $ionicPopup.show({ title: 'Swipe Cards', templateUrl: 'templates/popups/card-intro.html', scope: $scope, buttons: [ { text: 'Next', type: 'button-positive', onTap: function(e) { if (slidebox.currentIndex() === 0) { // Go to next slide slidebox.next(); // Change button text e.target.innerHTML = 'OK'; e.preventDefault(); } else { // Close popup return; } } } ] }); } $scope.cardTransitionedLeft = function(index) { console.log('cardTransitionedLeft called with index:' + index); if (!InstagramService.isLoggedIn()) { console.log('not sure if you liked it before or not since you are not logged in; doing nothing!'); return; } var post = $scope.model.currentPosts[index]; if (post.user_has_liked) { // jshint ignore:line PostsService.dislikePost(post.id) .success(function() { console.log('you disliked it!'); }); } else { console.log('you didnt like it in the first place!'); } }; $scope.cardTransitionedRight = function(index) { console.log('cardTransitionedRight called with index:' + index); if (!InstagramService.isLoggedIn()) { console.log('not sure if you liked it before or not since you are not logged in; if you login, we will like it for you'); } var post = $scope.model.currentPosts[index]; if (!post.user_has_liked) { // jshint ignore:line PostsService.likePost(post.id) .success(function () { console.log('you liked it!'); }); } else { console.log('you already liked it previously!'); } }; $scope.cardDestroyed = function(index) { console.log('cardDestroyed called with index:' + index); $scope.model.currentPosts.splice(index, 1); };}); 这里cardTransitionedLeft和cardTransitionRight 方法基本上一样的,除了,左滑进代表不喜欢,右滑是代表喜欢。两者都要判断是否已经登陆。如何用户喜欢当前的卡片但又没有登陆时,会要求用户登陆。一旦登陆成功,这个卡片就会被收藏。这是我们安全认证方案的结果。你可以通过第六章节的 验证 来了解更多的信息。 当一个卡片一旦过渡完并且被销毁时就会调用cardDestroyed方法。我们这里只是将卡片从照片数组中删除。 ##List视图选项 Trendicity的列表选项是为了展示ionic核心的列表功能而开发的。比较受欢迎的组件为:拉取刷新,button bars action sheets,视图中的手势。这一部分我们将解析开发列表的过程,和上述的组件。 ###涉及文件 列表视图功能涉及的文件:www/templates/tab-list.html,www/js/controllers/list.js和www/js/directives/on-dbl-top.js。路由在www/js/app.js中定义的: .state('app.home.list',{ url:'/list', views:{ 'tab-list':{ templateUrl:'templates/tab-list.html', controller:'ListViewCtrl' } }} ###模板布局 列表视图模板可以被切分成三部分:刷新,帖子列表,和包含用户可操作铵钮的铵钮条。 ###刷新帖子列表 Ionic提供了非常有用的指令,名字为ion-refresher。在使用这个组件时,将标签放到你的视图上,并添加在视图控制器中添加一个处理用户交互的方法,一旦用户向下拉取内容或者触发刷新机制。 ![列表视图](/images/ionic/list_view.png =300x) 为了保存简单易用,我们选择一个刷新铵钮(ion-arrow-down-c)来代替默认的图标,同时设置下拉时提示的文字。 <ion-refresher pulling-icon="ion-ios-arrow-up" spinner="none" pulling-text="Pull to refresh..." on-refresh="doRefresh()"></ion-refresher> 注意:这个命令允许你通过下面属性来覆盖它默认的配置: on-refresh:一旦用户下拉放开后触发刷新机制。 on-pulling:一旦用户开始下拉时调用。 pulling-icon:当用户下拉时显示的图标。 pulling-text:当用户下拉时显示的文本。 refreshing-icon:当刷新机制一旦触发,这个图标就会显示。 refreshing-text:一旦数据刷新后,显示的文本。 disable-pulling-rotation:一旦on-refresh调用开始调用,停止旋转图标。 ionrefresher组件值得注意的提升就是,添加了一个定时器,当很快获取到数据时,刷新显示数据也要耗时最少要400ms。这种情况超时创造一个平滑过渡的假像,使用户感觉到数据是刷新了。 我们回到Trendicity,refresher设置当用户希望获取新的帖子时,触发控制器的doRefresh方法。下面就是刷新时调用的方法: // Refresh feed posts$scope.doRefresh = function() { $scope.getPosts($scope.search.value); // Hide ion-refresher $scope.$broadcast('scroll.refreshComplete');}; 就像前面章节解释的那样,我们使用HomeCtrl的getPosts方式","categories":[{"name":"扩展知识","slug":"扩展知识","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/"},{"name":"读书学习","slug":"扩展知识/读书学习","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/%E8%AF%BB%E4%B9%A6%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"ionic","slug":"ionic","permalink":"http://example.com/tags/ionic/"},{"name":"angularjs","slug":"angularjs","permalink":"http://example.com/tags/angularjs/"}]},{"title":"第二章 ionic 开发环境 工具和工作流程介绍","slug":"ionic 第二章 开发环境 工具和工作流程介绍","date":"2015-08-26T16:00:00.000Z","updated":"2022-09-03T10:12:16.072Z","comments":true,"path":"2015/08/27/ionic 第二章 开发环境 工具和工作流程介绍/","link":"","permalink":"http://example.com/2015/08/27/ionic%20%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%20%E5%B7%A5%E5%85%B7%E5%92%8C%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B%E4%BB%8B%E7%BB%8D/","excerpt":"","text":"本章节,我们将会学到运行一个Inoic项目需要哪些技能。你将会安装基础的构建模块,学习如何使用Ionic命令行工具(cli)初始化一个项目,学习如何添加不同的平台和插件及如何在本地浏览器,仿真器和真实的设备上进行测试。``` ##安装必备工具:Node.js和GitIonic使用功能强大的模块管理工具Node.js,Node.js 是一个Javascript基础平台,通过它可以很容易的进行web应用的构建。由于Node.js的无处不在和其包管理特性,使用ionic的安装和部署非常简单。##安装Node.jsNode.js通过下载安装器安装在windows上或到[这里](http://nodejs.org/down-load/)下载针对于OS X的安装程序。在OS X上也可以通过Homebrew进行安装。另外,你可以执行下面命令进行安装。```javascript$ brew install node 安装完后,你可以打一个命令行,输入下面命令进行测试: $ node 它会打开一个控制台。它就像是浏览器中的javascript控制台一样。你可以在这个控制台中编写合法的javascript代码。如: > console.info('Hello World');Hello Worldundefined 按两次CTRL+C就可以退出Node.js控制台 ##安装Git NPM是Node.js的依赖管理,bower是browser的依赖管理。由于ionic是基于html5的移动混合应用开发工具和服务套件。bower只是安装和依赖管理。有一些bower模块需要Git,因此在开始之前你必须要先安装Git. 可以在 git-scm.com/downloads下载各个平台的Git安装程序。 一旦你安装了Git,打开终端,输入: $ git versiongit version 2.3.2 (Apple Git-55) ##安装Installing Ionic现在你已经安装了Node.js和Git,安装Ionic就变的非常容易了。打开终端,输入: $ npm install -g cordova ionic 这行命令就是让NPM安装cordova和ionic包,和他们所依赖的模块。加上-g这个选项,意味着NPM在全局中安装它他们,因此你就可以在所有项目中使用它们。 一旦NPM选择完,就会输出一个安装汇总。下面你可以验证安装。 ##Cordova 下面花一些时间去讲解Cordova是什么,是非常值得的,因为他是Ionic为什么如此强大的最重要的原因。 Cordova 是一个社区驱动的开源项目,它是HTML和手机原生功能之间的桥梁。它使开发者能够构建一个html应用,这个应用可以直接使用手机原生的功能,如何像机,地理位置,设备的方向等。另外,Cordova目的是创建一些跨平台的接口,使你创建html应用而不用关心是iphone应用还是在android应用。 你可以在这里了解决更多关于Cordova的知识。 ##Ionic命令行 (CLI) 为了使你能更方便的使用Ionic,Ionic提供了命令行接口。 创建一个新的工程 添加平台 添加插件 编译和在仿真器中运行你的工程 在设备上运行你的 打包应用和发布 ##创建一个新的工程让我们创建一个新的应用。打开终端,定位到你想把本书涉及到的代码存放的位置。在这目录下面输入下面的创建应用的命令。 $ ionic start trendicity tabs 这个ionic start命令可以让你指定一个模版用来初始化Ionic应用的结构。上面这个例子,你指定了tabs模版。开箱即用的,Ionic支持空白模版(不指定模版,默认就是空白的),tabs和 sidemenu。另外,如何你想提供一个自定义模版,你可以指定一个CodePen URL. 一旦上面那个命令执行完,你就已经配置好了一个Ionic工程,就会看到下面信息: Your Ionic project is read to go ! Some quick tips: * cd into your project: $ cd trendicity * Setup this project to use Sass: ionic setup sass * Develop in the browser with live reload: ionic serve * Add a platform (ios or Android): ionic platform add ios [android] Note: iOS development requires OS X currently See the Android Platform Guide for full Android installation instructions: https://cordova.apache.org/docs/en/edge/guide_platforms_android_index.md.html * Build your app: ionic build <PLATFORM> * Simulate your app: ionic emulate <PLATFORM> * Run your app on a device: ionic run <PLATFORM> * Package an app using Ionic package service: ionic package <MODE> <PLATFORM>For more help use ionic --help or ionic docsVisit the Ionic docs: http://ionicframework.com/docsNew! Add push notifications to your Ionic app with Ionic Push (alpha)!https://apps.ionic.io/signup+---------------------------------------------------------++ New Ionic Updates for August 2015++ The View App just landed. Preview your apps on any device+ http://view.ionic.io++ Invite anyone to preview and test your app+ ionic share EMAIL++ Generate splash screens and icons with ionic resource+ http://ionicframework.com/blog/automating-icons-and-splash-screens/++---------------------------------------------------------+ 同时你会看到一些快捷提示。你可以随时查看这些提示,通过下面这个命令: $ ionic --help 这个将会输出一个Ionic命令行支持的指今列表 看一下刚才那个命令为你创建了哪些东西 $ cd trendicity$ ls -Ftotal 40-rw-r--r-- 1 Aaron staff 118 8 30 16:03 bower.json-rw-r--r-- 1 Aaron staff 864 8 30 16:04 config.xml-rw-r--r-- 1 Aaron staff 1388 8 30 16:03 gulpfile.jsdrwxr-xr-x 4 Aaron staff 136 8 30 16:03 hooks/-rw-r--r-- 1 Aaron staff 42 8 30 16:04 ionic.project-rw-r--r-- 1 Aaron staff 581 8 30 16:04 package.jsondrwxr-xr-x 4 Aaron staff 136 8 30 16:04 platforms/drwxr-xr-x 9 Aaron staff 306 8 30 16:04 plugins/drwxr-xr-x 3 Aaron staff 102 8 30 16:03 scss/drwxr-xr-x 8 Aaron staff 272 8 30 16:04 www/ bower.json:管理你的Bower依赖 gulpfile.js:Gulp是一个构建工具。更多的信息可参在这里找到。这个文件管理构建的流程。 ionic.project:ionic工程配置文件。 plugins/:Ionic插件文件放在此目录下。稍后你会了解更多关于这插件的信息。 www/:你的应用文件放在此目录下。 config.xml:Cordova的配置文件。 hooks/:Cordova的hooks 这里面有一个说明文件,我可以根据此文件来创建一系列的目录,这些目录里面可以创建一些脚本,这些脚本分别在ionic运行的不同时期进行执行。 package.json :管理Node.js依赖的文件 scss/: SASS 文件。 好,现在,我们把重点放在 www 目录。 localhost:trendicity Aaron$ cd wwwlocalhost:www Aaron$ ls -Ftotal 8drwxr-xr-x 3 Aaron staff 102 8 30 16:04 css/drwxr-xr-x 3 Aaron staff 102 8 30 16:04 img/-rw-r--r-- 1 Aaron staff 858 8 30 16:04 index.htmldrwxr-xr-x 4 Aaron staff 136 8 30 16:04 js/drwxr-xr-x 3 Aaron staff 102 8 30 16:03 lib/drwxr-xr-x 8 Aaron staff 272 8 30 16:04 templates/ css/:你的应用的CSS文件img/:你的应用中用到的图片index.html:你的应用的入口js/:你的javascript代码lib/:第三方库和Ionic库templates/:你的应用的HTML模版 在js引以为文件夹中会发现有三个文件: localhost:js Aaron$ ls -Ftotal 16-rw-r--r-- 1 Aaron staff 1916 8 30 16:04 app.js-rw-r--r-- 1 Aaron staff 1544 8 30 16:04 controllers.js-rw-r--r-- 1 Aaron staff 1542 8 30 16:04 services.js 若没有发现services.js文件,你可以自己创建一个。 app.js:你的应用的入口,它包括路由信息。controllers.js:包括例子应用的所有的控制器。services.js:应用的可重用的服务。 值得注意的是,这是一个简化的项目结构,随着你的项目的增长,你需要去完善它。 ##在浏览器上部署 刚开始时,与通过仿真器测试相比,在浏览器中进行测试是最快的方式。它是可以在浏览器中进行测试的,因为Ionic是一个基于html5的平台。另外,Ionic现有的内置功能让其变得更加简单。在你的命令行中的工程目录下,输入以下命令。 $ ionic serve****************************************************** Upgrade warning - for the CLI to run correctly, it is highly suggested to upgrade the following: Please update your Node runtime to version >=0.12.x******************************************************Running live reload server: http://192.168.1.7:35729Watching : [ 'www/**/*', '!www/lib/**/*' ]Running dev server: http://192.168.1.7:8100Ionic server commands, enter: restart or r to restart the client app from the root goto or g and a url to have the app navigate to the given url consolelogs or c to enable/disable console log output serverlogs or s to enable/disable server log output quit or q to shutdown the server and exit 上面的命令执行完成后,将会打开一个浏览器窗口并且访问你的应用。恭喜你,你的应用已经成功运行! 如何浏览器窗口没有自动打开, Ionic通知你在浏览器中打开这个应用。你上面你会看到 http://192.168.1.7:35729 这个命令将会监视你的工程文件的变化,因此当你改变工程中的文件时,视图也会自动刷新。你可以通过修改工程的一个文件并注意着浏览器的重新加载来进行测试。打开./www/templates/tab-dash.html 文件并且修改: <h1>Dash</Dash> 修改为: <h1>Trendicity</h1> 保存后,浏览器会立即发生刷新,你就会看到刚才修改文件后的变化。 ##代理 当你的应用需要访问不允许跨域访问的资源的APIs时,在浏览器测试时,你将会遇到问题。幸运的是Ionic团队已经 通过代理请求的方式来解决上面的问题。 在你应用的根目录下打开ionic.project。 cat ionic.project { "name": "trendicity", "app_id": ""} 添加代理数据到你的文件中: cat ionic.project { "name": "trendicity", "app_id": "", "proxies": { "path":"/proxied/resources", "proxyUrl":'https://api.somesite.com/resources' } } 在这个例子中的path是你本地想被代理的路径,这个proxyUrl是真正的你想被请求的API.在你的应用中,你现在可以请求http://localhost:8100/proxied/resources其实是访问的https://api.somesite.com/resources. 注意,代理的配置仅在浏览器中进行测试时需要用到。在设备上和仿真器中不需要用这个配置文件。 ##添加平台 当你发布之前,或者在你构建一个应用时,你需要决定你想将此应用部署到哪些平台上。然后告诉给Ionic,通过CLI,安装构建成这此平台的应用时所必须的先决条件。让我们来添加IOS平台。 $ ionic platform ios 它将会安装一些必须的依赖并准备将你的工程构建成IOS平台的应用。注意,如何你不是在Apple电脑上,你是不能够添加iOS平台的。 或,你可以添加Android平台: $ ionic platform android Android平台与iOS相比,需要安装一些额外的东西 当你都是在Apple电脑上执行上面的命令时。在稍后的章节中你会了解到更多关于Android安装的信息。 如何你决定选择在Android平台上进行测试,建议你不要使用Android默认的仿真器。因为它太慢了。你可以安装使用Geny-mogion仿真器,它比默认访真器要快一些。你可以在这里了解更多的内容。 ##构建应用 用Ionic构建应用是容易的。构建一个iOS平台的应用,通过如下命令: $ ionic build ios 一旦构建成功,你会看到输出下面信息: ** BUILD SUCCEEDED ##编译和在仿真器中运行你的工程Ionic支持直接通过CLI加载设备仿真器。加载你的编译文件到iOS仿真器(确保你已经安装了ios-sim)。 $ ionic emulate ios 如何你之前没有进行编译,Ionic会创建iOS平台的编译文件。执行这个命令之后,iOS 仿真器将会加载,并且你的应用将会在其中加载。 像是在浏览器中测试一样,你可以修改你的应用文件时,让仿真器中的应用自动刷新。执行下面的命令,等待仿真器加载你的应用,然后修改应用的文件进行观察仿真器中应用的变化。 $ ionic emulate ios -livereload 第一次修改工程,都会花大量的时间去编译和加载到仿真器。 ##在设备上运行 仿真器是非常强大的,但是在发布之前,你仍然想在真实的设备上测试你的应用。 ###iOS在iOS设备上部署,需要有一个iOS开发帐户,在Apple设备上运行XCode。iOS开发者账户是收费的。你可以在developer.apple.com/programs/了解更多。 你可以在这里下载Xcode:(developer.apple.com/xcode/downloads/)[https://developer.apple.com/xcode/downloads/] 一旦你正确安装了XCode和拥有你自己的开发者账户,在XCode中打开你工程的/platform/ios文件夹,然后进行测试。 ###Android 到目前为止,我们主要集中在构建、模拟和运行iOS应用程序。原因是,如果你在Apple电脑上进行开发,启动和运行都是非常容易的,但是它避免了使用Ionic进遇到的最大的一件事情。你编写一次应用,可以编译成两个平台的版本。让我们花一点时间看一下在Android平台上启动和运行时的一些选项。 ###Android SDK安装 第一件事就是下载和安装Android SDK.可以在这里下载:developer.android.com/sdk/。可以在这里找到安装步骤:developer.android.com/sdk/installing/。 ###Ionic box 很多人发现安装Android SDK是比较困难的过程。由于这个原因,Ionic小组已经创建了一个Vagrant box来帮助你简化这个过程。如何你对Vagrant不是很熟悉的话,它是一个创建虚拟机的平台,主要是创建开发环境。你可以在这里了解更多:www.vagrantup.com。你可以使用不同的虚拟机运行器,但是最常用的是VirtualBox。 为了使用Vagrant box,你首先安装VirtualBox,或者别的被Vagrant支付的虚拟机运行器。VirtualBox是免费的,你可以在这里进行下载:www.virtualbox.org/wiki/Downloads。一旦安装好了VirtualBox,下载并安装Vagrant:www.vagrantup.com/downloads.html。 一旦你安装好了VirtualBox和Vagrant,进入到你的工程目录下面,克隆Ionic box资源,并且启动它。 $ cd ..$ git clone https://github.com/driftyco/ionic-box$ cd ionic-box$ vagrant up 你第一次执行时,可能要花费几分钟,因为Vagrant需要下载一个虚拟机镜像文件。在你在Ionic box上编译你的应用前,你需要将你的项目代码目录与虚拟机共享。在你的编译器中打开ionic-box/Vagrantfile。找到下面这行: # config.vm.synced_folder "../data", "/vagrant_data" 修改为: config.vm.synced_folder "../trendicity", "/home/vagrant/trendicity" 保存这个文件并重新加载你的Vagrant实例: $vagrant reload 现在让我们为vagrant实例添加SSH,并针对Android进行构建。 $ vagrant sshvagrant@ionic-android:~$ cd trendicity/vagrant@ionic-android:~/trendicity$ ionic platform android 现在将你的Android设备通过USB连接到你的电脑。你可以通过下面的命令确认你的设备已经连接上。 vagrant@ionic-android:~/trendicity$ sudo /home/vagrant/android-sdk-linux/platform-tools/adb devices 确认你的设备已经连接上,然后通过下面的命令在你设备上启动应用: vagrant@ionic-android:~/trendicity$ ionic run android 当你看到下面的信息,就证明上面的命令执行成功了: Installing app on device ...Lanuching application ...LAUNCH SUCCESS 恭喜!你已经成功在android设备上运行了你的应用。 上面看上去在你的设备上运行应用程序是很多麻烦,同时它证明了android sdk安装是多么的困难,以及ionic团队如何使它些困难变的简单易用。 ##添加插件 Ionic和Cordova是非常强大的工具,但是为了提高性能和最小化你的就应用,你可能不希望默认安装所有东西。你可能添加别的功能通过添加插件的方式。让我们给将要使用地理定位功能的应用添加地理位置插件。 在你的项目根目录,输入以下命令。注意,你可以在本地命令行中执行下面命令,也可以在ionic box 的命令行中输入以下命令: $ ionic plugin add org.apache.cordova.geolocation Ionic将会安装一些必须的组件,使你的应用程序可以使用地理定位功能。稍后,当你深入到本书例子 Trendicity应用 的细节时,你会看到如何使用这个地理定位插件。 你也要以自动在这里plugins.cordova.io/了解一下别的插件,以备将来更好的使用到自己的项目中。 ##最佳源代码控制实践 因为Ionic和Cordova安装的文件对不同的平台是不一样的,这些文件是没有必要加入版本控制的。让我们看一下,哪些东西要存放到我们的版本控制中,还有哪些不用存放。 ###Git和模版化应用 如何你使用Git(本书的作者们强烈推荐使用Git),和如何通过Ionic命令行创建你的应用,你可能注意到它创建了一个.gitignore文件,它里面包含一些你不需要版本控制的文件引用。你可以选择跳过本章剩余的部分而直接查看这个文件.gitignore,下面是它的内容: $ cat .gitignore # Specifies intentionally untracked files to ignore when using Git# http://git-scm.com/docs/gitignorenode_modules/platforms/plugins/ ###根目录下面的文件 根目录下所有的文件,你就都应该加入版本控制。它们包含了项目的配置信息,没有它们,你的项目将无法正确编译。 ###包含的目录./hooks,./scss,和最重要的./www 都应该加入到你的版本控制。 ###不需要包含的目录 ./node_modules,./platforms, 和 ./plugins 都不需要加入版本控制。他们包含了许多二进制文件和一些额外的编译时生成的文件,这些文件在部署时都会重新生成。也都会在你在新的机器上检出代码时自动生成。 ##总结 你现在准备开始创建你的应用。你拥有了创建一个应用需要的所有工具:你可以通过浏览器或者仿真器进行测试,你可以通过添加插件的方式去访问一些高级的功能,并可以让你的应用在真实的设备上进行运行。","categories":[{"name":"扩展知识","slug":"扩展知识","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/"},{"name":"读书学习","slug":"扩展知识/读书学习","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/%E8%AF%BB%E4%B9%A6%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"ionic","slug":"ionic","permalink":"http://example.com/tags/ionic/"},{"name":"angularjs","slug":"angularjs","permalink":"http://example.com/tags/angularjs/"}]},{"title":"第一章ionic 基本介绍","slug":"ionic 第一章 基本介绍","date":"2015-08-24T16:00:00.000Z","updated":"2022-09-03T23:08:46.440Z","comments":true,"path":"2015/08/25/ionic 第一章 基本介绍/","link":"","permalink":"http://example.com/2015/08/25/ionic%20%E7%AC%AC%E4%B8%80%E7%AB%A0%20%E5%9F%BA%E6%9C%AC%E4%BB%8B%E7%BB%8D/","excerpt":"","text":"前言21世纪,手机应用兴起。硬件正变的更好,软件的能力也成几何倍的增长。手机应用的需求空前的高,开发者们也努力开发各种主流平台的应用以满足市场的需求。最流行的方式就是编写原生的应用,但是编写原生的应用需要对多门语言进行深入的理解,例如:IOS/OSX使用的Objective-C 和 Swift,别外一个就是Android开发使用的Java。另外开发人员要熟悉各种开发框架及SDK来生成应用。像这样当同时开发一个应用要分别开发不同平台的版本,对于一个团对来说难度就大了。混合模式开发(Hybrid development),可以说是解决上面问题的最有效的方式。其实,混杂模式应用是在本地容器上包装了一层。这种结构让我们仅通过用HTML5+CSS3和javascript就可以开发移动应用,不用直接与某一个平台进行交互。很多开发者为了满足快速增长类似于一个产品要有多平台的版本的需要,目前已经使用了这种方式。自从智能手机的到来,很多类似的框架也产生了。Sencha Touch,Kendo UI,和jQuery mobile是比较受欢迎的移动框架。也有一些控制DOM的框架,如Facebook的React库,Google的AngularJS和Backbone.js.另外,Bootstrap,Foundation和Topcoat 服务帮助你设计混杂应用。最后,有很多混杂开发平台如Apache基金会的Cordova 一个开源平台。面对那么多的框架体系,选择哪个来开发你的应用是比较头痛的事情。大部分这些框架不能充分利用手机装置并且当我们使用其生成应用时会出现各种问题。所以Ionic就出现了。 IonicIonic是由Drifty研发的,一个聪明的开发和设计小组。这个公司之前已经成功开发过多个项目,如:Jetstrap,一个bootstrap构建工具,和Codiqa,在线拖放构建混合应用程序和移动网站的工具。Drifty目前的使命是改变未来移动软件开发,特别是混杂手机应用开发,Ionic就会实现这些事情。Ionic将Apache的Cordova平台,AngularJS和Angular-ui-router通过Ionic开发好的应用架构和组件和样式组合起来。实质上Ionic集成了最好的手机框架,让你专注于自己的功能设计和实现。基于Cordova,Inoic是可以使用大量的Cordova插件,这些插件可以使用原生的一些东西,如访问设备的相机,照片和地理坐标等等。另外,这个平台还让你创建原生的通知,和捕获设备的运动方向,实际上,这样我们基本就看不出原生应用和混杂应用的区别了。更重要的是,Ionic决定集成AngularJS使你能够访问大量的功能.那是一个非常了不起的架构上的决定,因为它可以帮你把你的代码组织到controllers,services,和directives中,方便代码的管理。最后,有了AngularJS,大量开源社区的模块都可以使用了,可以使人你扩展Ionic的核心功能。如,Trendicity 这个应用,我们就集成了有名的angular-local-storage模块来存储在Instagram上发表的最喜欢的东西。而且,Ionic在开源社区中已经非常受关注。不仅仅因为这个框架它本身开源,而是它的开发团队编写了详细的文档和在CodePen上收集有用的代码样例。别一个非常好的资源就是,可以与其它社区交流和直接与ionic团队直接进行交流的公共论坛。接下来的章节,我们会用ionic一步步的创建Trendicity用ionic。此外,我们会深入学习ionic在构建混合移动应用程序中的使用。","categories":[{"name":"扩展知识","slug":"扩展知识","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/"},{"name":"读书学习","slug":"扩展知识/读书学习","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/%E8%AF%BB%E4%B9%A6%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"ionic","slug":"ionic","permalink":"http://example.com/tags/ionic/"},{"name":"angularjs","slug":"angularjs","permalink":"http://example.com/tags/angularjs/"}]},{"title":"我的读书计划","slug":"学习计划","date":"2015-08-23T16:00:00.000Z","updated":"2022-09-03T10:12:16.072Z","comments":true,"path":"2015/08/24/学习计划/","link":"","permalink":"http://example.com/2015/08/24/%E5%AD%A6%E4%B9%A0%E8%AE%A1%E5%88%92/","excerpt":"","text":"书名 作者 计划学习时间 详细记划 《Developing an Ionic Edge》 Anton Shevchenko,Robi van Baalen,keith D. Moore,Diego Netto,Alan Levicki 08.24 到 10.30 详细记划 spark Apache Spark 11.0 到 12.30 待计划","categories":[{"name":"扩展知识","slug":"扩展知识","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/"},{"name":"读书学习","slug":"扩展知识/读书学习","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/%E8%AF%BB%E4%B9%A6%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"book","slug":"book","permalink":"http://example.com/tags/book/"},{"name":"schedule","slug":"schedule","permalink":"http://example.com/tags/schedule/"},{"name":"nocomment","slug":"nocomment","permalink":"http://example.com/tags/nocomment/"}]},{"title":"ionic 学习计划","slug":"ionic schedule","date":"2015-08-23T16:00:00.000Z","updated":"2022-09-03T10:12:16.071Z","comments":true,"path":"2015/08/24/ionic schedule/","link":"","permalink":"http://example.com/2015/08/24/ionic%20schedule/","excerpt":"","text":"###1. 第一章 ionic基本介绍 08.24 ###2. 第二章 开发环境 工具和工作流程介绍 08.25-08.27 ###3. 第三章 Trendicity 项目实例 08.28-09-20 ###4. 第四章 实现菜单滑动和设置路线 09.20-10.30 ###5. 第五章 与地图的集成 10-01-10.10 ###6. 第六章 权限制控制 10.11 ###7. 第七章 设计应用 10.12-10.20 ###8. 第八章 与服务的交互 10.22 ###9. 第九章 下一步要做什么呢? 10.25","categories":[{"name":"扩展知识","slug":"扩展知识","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/"},{"name":"读书学习","slug":"扩展知识/读书学习","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/%E8%AF%BB%E4%B9%A6%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"ionic","slug":"ionic","permalink":"http://example.com/tags/ionic/"},{"name":"angularjs","slug":"angularjs","permalink":"http://example.com/tags/angularjs/"}]},{"title":"django core task","slug":"django定时调度的几种方式","date":"2015-08-20T16:00:00.000Z","updated":"2022-09-03T10:12:16.071Z","comments":true,"path":"2015/08/21/django定时调度的几种方式/","link":"","permalink":"http://example.com/2015/08/21/django%E5%AE%9A%E6%97%B6%E8%B0%83%E5%BA%A6%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F/","excerpt":"","text":"定时调度的问题1.前言 最近在项目中做一个定时统计数据的功能,如何做到定时调用统计方法呢? 在网上看了一下,大致有三种方式 1. [编写Django Command ,用cron进行定时调度](http://www.cnblogs.com/linjiqin/p/3965046.html) 2. 第一种方式的升级版实现[django-crontab实现Django定时任务](http://www.zhidaow.com/post/django-crontab) 3. 编写Django Command 然后rq_scheduler定时把任务提交给队列,由worker进行处理 这里我选用的是第三种方式 2.编写Django Command 然后rq_scheduler定时把任务提交给队列,由worker进行处理 工作原理*Putting a job in the scheduler *Running a scheduler that will move scheduled jobs into queues when the time comes *RQ Scheduler comes with a script rqscheduler that runs a scheduler process that polls Redis once every minute and move scheduled jobs to the relevant queues when they need to be executed: scheduler.schedule( scheduled_time=datetime.now(), # Time for first execution, in UTC timezone func=func, # Function to be queued args=[arg1, arg2], # Arguments passed into function when executed kwargs={'foo': 'bar'}, # Keyword arguments passed into function when executed interval=60, # Time before the function is called again, in seconds repeat=10 # Repeat this number of times (None means repeat forever)) ##使用方法 ###初始化: In [1]: from analytics.etl import analytics_etlIn [2]: analytics_etl.init() 安装rq_scheduler pip install rq_scheduler 编写测试command scheduler_command.py class Command(BaseCommand): help = "process analytics data" def handle(self, *args, **options): first_process_datetime=datetime.now()+timedelta(days=1) first_process_datetime.replace(hour=1,minute=0,second=0,microsecond=0) first_process_datetime=dateformat.local_to_utc(first_process_datetime) scheduler.schedule(first_process_datetime,process,interval=interval) ###调用job python manage.py scheduler_command --settings=local_settings ###启动rqscheduler rqscheduler执行一次就OKpython manage.py rqscheduler --settings=local_settings 启动一个rqworker default启动一个工作者,python manage.py rqworker default --settings=local_settings ###查看当前有多少job: import django_rqscheduler=django_rq.get_scheduler('default')jobs=scheduler.get_jobs() ###取消job scheduler.cancel(job)","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"python","slug":"python","permalink":"http://example.com/tags/python/"},{"name":"django","slug":"django","permalink":"http://example.com/tags/django/"},{"name":"定时","slug":"定时","permalink":"http://example.com/tags/%E5%AE%9A%E6%97%B6/"}]},{"title":"我的第一篇博客","slug":"开启我的blog","date":"2015-08-19T16:00:00.000Z","updated":"2022-09-03T21:26:27.615Z","comments":true,"path":"2015/08/20/开启我的blog/","link":"","permalink":"http://example.com/2015/08/20/%E5%BC%80%E5%90%AF%E6%88%91%E7%9A%84blog/","excerpt":"","text":"###为什么写博客 世界这么乱,装纯给谁看 我为什么写博客 我认同他们所分享的,所以我也开始了自己的博客之路 为什么现在才开始 从2014年就一直想写自己的博客,一直没有沉下心来去写。 这几天搭档出去休息,我也偷偷的休息一下,将自己的博客这一课给补上。 写哪些东西 读书笔记:记录在看书时一些笔记,方便自己日后参考 项目中遇到难点笔记:项目中遇到问题的解决过程,方便将来参考 技术分享会总结:记录在技术学习上的点滴 个人随笔 :本人一些业余随笔 关于:本人的简单介绍 为什么会选择用github pages呢 Github-Pages-Example 将纯文本转化为静态网站和博客 keep gh-pages in sync with master hexo 优化 添加网站优化搜索 npm install hexo-generator-sitemap --savenpm install hexo-generator-baidu-sitemap --save 添加flow https://github.com/bubkoo/hexo-filter-flowchart","categories":[{"name":"生活","slug":"生活","permalink":"http://example.com/categories/%E7%94%9F%E6%B4%BB/"}],"tags":[{"name":"人生","slug":"人生","permalink":"http://example.com/tags/%E4%BA%BA%E7%94%9F/"},{"name":"感悟","slug":"感悟","permalink":"http://example.com/tags/%E6%84%9F%E6%82%9F/"},{"name":"疑惑","slug":"疑惑","permalink":"http://example.com/tags/%E7%96%91%E6%83%91/"}]},{"title":"Openssl软件生成RSA私钥和公钥","slug":"由 Openssl 软件自动生成RSA 私钥和公钥","date":"2015-01-26T16:00:00.000Z","updated":"2022-09-03T10:12:16.071Z","comments":true,"path":"2015/01/27/由 Openssl 软件自动生成RSA 私钥和公钥/","link":"","permalink":"http://example.com/2015/01/27/%E7%94%B1%20Openssl%20%E8%BD%AF%E4%BB%B6%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90RSA%20%E7%A7%81%E9%92%A5%E5%92%8C%E5%85%AC%E9%92%A5/","excerpt":"","text":"##什么是RSA##什么是Openssl软件##如何用Openssl软件生成RSA私钥和公钥","categories":[{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"},{"name":"算法","slug":"技术/算法","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"加密解密","slug":"加密解密","permalink":"http://example.com/tags/%E5%8A%A0%E5%AF%86%E8%A7%A3%E5%AF%86/"},{"name":"rsa","slug":"rsa","permalink":"http://example.com/tags/rsa/"}]}],"categories":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/categories/BPMN/"},{"name":"元素","slug":"BPMN/元素","permalink":"http://example.com/categories/BPMN/%E5%85%83%E7%B4%A0/"},{"name":"Flow","slug":"BPMN/Flow","permalink":"http://example.com/categories/BPMN/Flow/"},{"name":"读书学习","slug":"读书学习","permalink":"http://example.com/categories/%E8%AF%BB%E4%B9%A6%E5%AD%A6%E4%B9%A0/"},{"name":"艺术","slug":"艺术","permalink":"http://example.com/categories/%E8%89%BA%E6%9C%AF/"},{"name":"生活","slug":"生活","permalink":"http://example.com/categories/%E7%94%9F%E6%B4%BB/"},{"name":"技术","slug":"技术","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/"},{"name":"语言","slug":"技术/语言","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/%E8%AF%AD%E8%A8%80/"},{"name":"日常工具","slug":"日常工具","permalink":"http://example.com/categories/%E6%97%A5%E5%B8%B8%E5%B7%A5%E5%85%B7/"},{"name":"方案","slug":"方案","permalink":"http://example.com/categories/%E6%96%B9%E6%A1%88/"},{"name":"问题总结","slug":"技术/问题总结","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/"},{"name":"mac","slug":"日常工具/mac","permalink":"http://example.com/categories/%E6%97%A5%E5%B8%B8%E5%B7%A5%E5%85%B7/mac/"},{"name":"书架","slug":"书架","permalink":"http://example.com/categories/%E4%B9%A6%E6%9E%B6/"},{"name":"扩展知识","slug":"扩展知识","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/"},{"name":"linux","slug":"扩展知识/linux","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/linux/"},{"name":"读书学习","slug":"扩展知识/读书学习","permalink":"http://example.com/categories/%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86/%E8%AF%BB%E4%B9%A6%E5%AD%A6%E4%B9%A0/"},{"name":"算法","slug":"技术/算法","permalink":"http://example.com/categories/%E6%8A%80%E6%9C%AF/%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"BPMN","slug":"BPMN","permalink":"http://example.com/tags/BPMN/"},{"name":"Flow","slug":"Flow","permalink":"http://example.com/tags/Flow/"},{"name":"流程","slug":"流程","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B/"},{"name":"流程引擎","slug":"流程引擎","permalink":"http://example.com/tags/%E6%B5%81%E7%A8%8B%E5%BC%95%E6%93%8E/"},{"name":"元素","slug":"元素","permalink":"http://example.com/tags/%E5%85%83%E7%B4%A0/"},{"name":"人生","slug":"人生","permalink":"http://example.com/tags/%E4%BA%BA%E7%94%9F/"},{"name":"绘画","slug":"绘画","permalink":"http://example.com/tags/%E7%BB%98%E7%94%BB/"},{"name":"徐悲鸿","slug":"徐悲鸿","permalink":"http://example.com/tags/%E5%BE%90%E6%82%B2%E9%B8%BF/"},{"name":"健康","slug":"健康","permalink":"http://example.com/tags/%E5%81%A5%E5%BA%B7/"},{"name":"感悟","slug":"感悟","permalink":"http://example.com/tags/%E6%84%9F%E6%82%9F/"},{"name":"疑惑","slug":"疑惑","permalink":"http://example.com/tags/%E7%96%91%E6%83%91/"},{"name":"python","slug":"python","permalink":"http://example.com/tags/python/"},{"name":"jdk8.java","slug":"jdk8-java","permalink":"http://example.com/tags/jdk8-java/"},{"name":"poi","slug":"poi","permalink":"http://example.com/tags/poi/"},{"name":"jdk","slug":"jdk","permalink":"http://example.com/tags/jdk/"},{"name":"mac","slug":"mac","permalink":"http://example.com/tags/mac/"},{"name":"alfred","slug":"alfred","permalink":"http://example.com/tags/alfred/"},{"name":"workflow","slug":"workflow","permalink":"http://example.com/tags/workflow/"},{"name":"java","slug":"java","permalink":"http://example.com/tags/java/"},{"name":"缓存","slug":"缓存","permalink":"http://example.com/tags/%E7%BC%93%E5%AD%98/"},{"name":"redis","slug":"redis","permalink":"http://example.com/tags/redis/"},{"name":"分布式","slug":"分布式","permalink":"http://example.com/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"},{"name":"服务治理","slug":"服务治理","permalink":"http://example.com/tags/%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86/"},{"name":"算法","slug":"算法","permalink":"http://example.com/tags/%E7%AE%97%E6%B3%95/"},{"name":"CAP","slug":"CAP","permalink":"http://example.com/tags/CAP/"},{"name":"排序","slug":"排序","permalink":"http://example.com/tags/%E6%8E%92%E5%BA%8F/"},{"name":"查找","slug":"查找","permalink":"http://example.com/tags/%E6%9F%A5%E6%89%BE/"},{"name":"多线程","slug":"多线程","permalink":"http://example.com/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"name":"并发","slug":"并发","permalink":"http://example.com/tags/%E5%B9%B6%E5%8F%91/"},{"name":"dubbo","slug":"dubbo","permalink":"http://example.com/tags/dubbo/"},{"name":"rpc","slug":"rpc","permalink":"http://example.com/tags/rpc/"},{"name":"一致性算法","slug":"一致性算法","permalink":"http://example.com/tags/%E4%B8%80%E8%87%B4%E6%80%A7%E7%AE%97%E6%B3%95/"},{"name":"图片处理","slug":"图片处理","permalink":"http://example.com/tags/%E5%9B%BE%E7%89%87%E5%A4%84%E7%90%86/"},{"name":"人脸识别","slug":"人脸识别","permalink":"http://example.com/tags/%E4%BA%BA%E8%84%B8%E8%AF%86%E5%88%AB/"},{"name":"人生感悟","slug":"人生感悟","permalink":"http://example.com/tags/%E4%BA%BA%E7%94%9F%E6%84%9F%E6%82%9F/"},{"name":"运动","slug":"运动","permalink":"http://example.com/tags/%E8%BF%90%E5%8A%A8/"},{"name":"爬山","slug":"爬山","permalink":"http://example.com/tags/%E7%88%AC%E5%B1%B1/"},{"name":"运维","slug":"运维","permalink":"http://example.com/tags/%E8%BF%90%E7%BB%B4/"},{"name":"nginx","slug":"nginx","permalink":"http://example.com/tags/nginx/"},{"name":"fastcgi","slug":"fastcgi","permalink":"http://example.com/tags/fastcgi/"},{"name":"音乐","slug":"音乐","permalink":"http://example.com/tags/%E9%9F%B3%E4%B9%90/"},{"name":"音频转换","slug":"音频转换","permalink":"http://example.com/tags/%E9%9F%B3%E9%A2%91%E8%BD%AC%E6%8D%A2/"},{"name":"arm","slug":"arm","permalink":"http://example.com/tags/arm/"},{"name":"mp3","slug":"mp3","permalink":"http://example.com/tags/mp3/"},{"name":"存储","slug":"存储","permalink":"http://example.com/tags/%E5%AD%98%E5%82%A8/"},{"name":"fastfs","slug":"fastfs","permalink":"http://example.com/tags/fastfs/"},{"name":"经验","slug":"经验","permalink":"http://example.com/tags/%E7%BB%8F%E9%AA%8C/"},{"name":"工具","slug":"工具","permalink":"http://example.com/tags/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"git","permalink":"http://example.com/tags/git/"},{"name":"代码仓库","slug":"代码仓库","permalink":"http://example.com/tags/%E4%BB%A3%E7%A0%81%E4%BB%93%E5%BA%93/"},{"name":"bug","slug":"bug","permalink":"http://example.com/tags/bug/"},{"name":"ansible","slug":"ansible","permalink":"http://example.com/tags/ansible/"},{"name":"测试","slug":"测试","permalink":"http://example.com/tags/%E6%B5%8B%E8%AF%95/"},{"name":"性能测试","slug":"性能测试","permalink":"http://example.com/tags/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/"},{"name":"压力测试","slug":"压力测试","permalink":"http://example.com/tags/%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95/"},{"name":"jmeter","slug":"jmeter","permalink":"http://example.com/tags/jmeter/"},{"name":"zookeeper","slug":"zookeeper","permalink":"http://example.com/tags/zookeeper/"},{"name":"Protobuf","slug":"Protobuf","permalink":"http://example.com/tags/Protobuf/"},{"name":"设计模式","slug":"设计模式","permalink":"http://example.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"},{"name":"软件","slug":"软件","permalink":"http://example.com/tags/%E8%BD%AF%E4%BB%B6/"},{"name":"maven","slug":"maven","permalink":"http://example.com/tags/maven/"},{"name":"book","slug":"book","permalink":"http://example.com/tags/book/"},{"name":"linux","slug":"linux","permalink":"http://example.com/tags/linux/"},{"name":"linux命令","slug":"linux命令","permalink":"http://example.com/tags/linux%E5%91%BD%E4%BB%A4/"},{"name":"GIS","slug":"GIS","permalink":"http://example.com/tags/GIS/"},{"name":"mysql","slug":"mysql","permalink":"http://example.com/tags/mysql/"},{"name":"ionic","slug":"ionic","permalink":"http://example.com/tags/ionic/"},{"name":"angularjs","slug":"angularjs","permalink":"http://example.com/tags/angularjs/"},{"name":"schedule","slug":"schedule","permalink":"http://example.com/tags/schedule/"},{"name":"nocomment","slug":"nocomment","permalink":"http://example.com/tags/nocomment/"},{"name":"django","slug":"django","permalink":"http://example.com/tags/django/"},{"name":"定时","slug":"定时","permalink":"http://example.com/tags/%E5%AE%9A%E6%97%B6/"},{"name":"加密解密","slug":"加密解密","permalink":"http://example.com/tags/%E5%8A%A0%E5%AF%86%E8%A7%A3%E5%AF%86/"},{"name":"rsa","slug":"rsa","permalink":"http://example.com/tags/rsa/"}]}