stream 简介
Stream(流)是 Jazz 源代码管理系统中的重要成员,体现项目开发中的一个版本,由一个或者多个 component( 组件)构成。流的建立使得团队成员在不同的版本之间进行代码的共享与管理,满足项目迭代开发中代码管理的需要,促进团队成员之间更好的协作。组件是代码共享与管理的基本单位,由一个文件或者文件夹所构成,团队成员可以通过使用流中的组件进行代码的共享。
多个 stream 的应用场景
目前,软件开发中迭代开发非常普遍,项目的开发经常涉及两个或多个版本。即在一个版本尚未发布就需要进入下一个软件版本的开发,这样使得项目开发人员需要同时维护多个软件版本。特别是一个缺陷需要在多个版本上解决,这样在这些不同的软件版本上进行代码同步就成为团队开发人员日常的工作内容。如何简化代码同步的流程,提高多个软件版本代码同步的效率以及减少在多个软件版本中由于代码同步引起的缺陷数,对于降低软件开发成本和提高软件质量具有重要的意义。
RTC 对多 stream 代码同步的支持
源代码管理与共享是项目开发中的重要内容,而 RTC 作为主流的源代码管理工具,对项目的源代码的管理与共享提供了很好的机制。在实际的项目开发过程中,项目拥有者需要在远程 Jazz 服务器上创建 stream,项目开发人员在远程 Jazz 服务器上基于此 stream 创建自己的版本库工作空间。除此之外,开发人员还需要在本地的机器上创建自己的本地工作空间。开发人员通过 Check-in 操作将本地工作空间的改动同步到远程 Jazz 服务器上的自己的版本库工作空间,通过 Deliver 操作将远程版本库工作空间的代码同步到团队共享的 stream 上。其他团队人员则通过 Accept 操作将代码从 stream 同步到自己远程的版本库工作空间,并通过 Load 操作将版本库工作空间的源代码下载到本地的工作空间。依此类推,RTC 就可以同时管理多个 stream 并进行有效的共享,其具体的源代码管理与共享机制如图 1 所示:
图 1. RTC 中多 stream 源代码管理与共享机制
通过 RTC 中对源代码的管理与共享机制可以看出,如果不进行任何修改与配置,在多个 stream 上,开发人员只能通过单独维护每个 stream 上的源代码,并在这些 stream 上进行代码的来回复制达到多 stream 上的源代码同步,这样无疑增加了维护和同步多个 stream 上的源代码成本。事实上 RTC 可以通过更改相应的 flow target(目标流指向)来简化多 stream 上的源代码同步,有效的提高项目开发的效率。
总的来说,RTC 对多 stream 上源代码的同步提供了三种方式:各 stream 单独同步,stream 由低版本向高版本同步及 stream 由高版本向低版本同步,具体操作如下图所示(图中以两个 stream 为例,多个 stream 可以依此进行类推):
图 2. 各 stream 单独同步
图 3. stream 由低版本向高版本同步
图 4. stream 由高版本向低版本同步
从图 2 可以看出,同步 stream1 和 stream2 上的源代码需要在每个 Local workspace 上 Check-in 相应的代码,在同步第二个 stream 时需要从一个 Local workspace 上拷贝源代码到另一个 Local workspace 上,这样加大了开发人员的工作量,并且增加了一些由拷贝源代码所造成的缺陷。图 3 与图 4 的同步机制没有多大差别,图 3 所示的为源代码从低版本(stream1)向高版本(stream2)同步,开发人员先将代码提交到 stream1,然后改变 stream2 的目标流指向,接受之前提交到 stream1 上的变更集,最后通过 Jazz Server 上的 Repository workspace 将变更集提交到 stream2 上。图 4 所示为源代码从高版本(stream2)低高版本(stream1)同步,具体过程与低版本(stream1)向高版本(stream2)同步相反。由于高版本往往会包含低版本没有实现的功能,所以对于多 stream 的源代码同步,采用从低版本向高版本同步的方式遇到的潜在的源代码冲突相对较少,开发人员解决这些冲突并合并变更集所付出的成本也随之降低。因此,在实际项目开发中,对于多 stream 的源代码同步,采用低版本(stream1)向高版本(stream2)同步的方式最佳。
RTC 中多 stream 代码同步的示例
由于多 stream 从低版本中向高版本同步时潜在的冲突最少,因此本文将以这种同步方式作为示例说明如何在两个 stream 实现源代码的同步。对于三个或三个以上 stream 的代码同步,读者可以依此类推。在示例中,我们拥有两个 stream,6.3.1.1 和 6.3.2。并且我们已经创建好两个 stream 上对应的两个我们自己的版本库工作空间。
无代码冲突情况下的代码同步
打开 Pending Changes 视图,在 6.3.1.1 版本库工作空间上有个新的可交付变更集,右击选择 deliver 对其进行交付,具体操作如图 5 所示:
图 5. 交付变更集界面
打开更新版本的 6.3.2 版本库工作空间,更改其 Flow Targets 到 6.3.1.1 stream。如图 6 所示,将 6.3.1.1 stream 置成当前 flow target 且是默认 flow target,然后确认保存。
图 6. 版本库工作空间界面
保存后结果如图 7 所示。
图 7. 更改 Flow Target 界面
如图 8 所示,此时再次查看 Pending Changes,6.3.2 版本库工作空间已经指向 6.3.1.1 stream,刚才在 6.3.1.1 版本库工作空间中交付的变更集出现在 6.3.2 版本库工作空间的可接受变更集中。此时该变更集与 6.3.2 版本库工作空间中代码不存在冲突,可以直接选择接受。
图 8. 接受 Incoming 变更集界面
接受后,将 6.3.2 版本库工作空间的 flow target 改回 6.3.2 stream ,如图 9 所示:
图 9. FlowTarget 配置界面
从图 10 中可以看出,在 Pending Changes 中,6.3.2 版本库工作空间中有了新的可交付变更集,该变更集正是 6.3.1.1 版本库工作空间之前交付的变更集。由于不存在冲突,我们可以直接将该变更集交付到 6.3.2 stream 中,从而实现两个 stream 上针对该变更集的源代码同步。在下一节中我们将给出存在代码冲突的同步示例,为了模拟这种情况,我们暂且不交付此变更集。
图 10. 交付 Outgoing 变更集界面
有代码冲突情况下的代码同步
同样,打开 Pending Changes 视图,在 6.3.1.1 版本库工作空间提交一个新的变更集模拟代码冲突,右击选择 deliver 对其进行交付,具体操作如图 11 所示:
图 11. 交付 Outgoing 变更集界面
更改 6.3.2 版本库工作空间 flow target 到 6.3.1.1 stream。从 Pending Changes 视图中可以看到 6.3.1.1 版本库工作空间中新提交的变更集。该变更集前出现橙色高亮,说明该变更集和 6.3.2 版本库工作空间中可交付的变更集存在代码冲突。具体操作如图 12 所示:
图 12. 接受 Incoming 变更集界面
选择接受该变更集,此时会弹出是否自动解决冲突的对话框如图 13,我们这里建议选择 Resolve Later,以防止出现代码合并错误的情况发生。下面我会给出具体的情况说明在接受冲突变更集后如何 Resolve。Resolve 有两种方法:自动合并和手动合并。什么时候选择自动合并,什么时候需要手动合并,这对于维护代码的同步和准确性十分重要。
图 13. 冲突提示界面
选择 Resolve Later 后,将 6.3.2 版本库工作空间的 flow target 改回 6.3.2 stream。如图 14 所示,出现未解决代码冲突。双击打开冲突比较视图。
图 14. 未解决冲突界面
如果视图中仅有蓝色标识,说明没有严重冲突。如图 15 所示,可以选择 Auto Merge 自动合并代码。
图 15. 自动解决冲突操作
图 15 大图
如果视图中有红色标识,说明存在严重冲突,自动合并代码将导致代码错乱。如图 16 所示,将红色部分右侧代码拷贝覆盖到左侧,手工完成覆盖后,选择 Resolve As Merge 完成合并。
图 16. 手动解决冲突界面
图 16 大图
在弹出的对话框中选择 OK 确定手动合并操作,如图 17 所示:
图 17. 手动解决冲突确认界面
这样合并完成后,6.3.2 版本库工作空间可交付变更集如图 18 所示。此时可看到被合并的原变更集后出现 1 merged 字样,合并操作完成。对其交付后,6.3.1.1 stream 和 6.3.2 stream 在该变更集存在冲突的情况下实现了代码同步。
图 18. 交付合并后变更集界面
撤销已交付的错误代码
在同步两个或多个 stream 的时候,很可能误将不属于本 stream 的代码 deliver 到 stream 上。如果错误交付的代码过多的话,手动去删除错误交付的代码将会需要花费很大的工作量来将代码还原到原来的状态。而且,还会带来代码被错误修改的风险。而 RTC 提供了一种回退(Reverse)的功能,将错误交付的代码自动清除掉,并恢复到原来的状态。
回退(Reverse)功能的具体操作步骤如下:
- 右击错误提交的代码所相关的 work iteam,并单击弹出的 Reverse 菜单。
图 19. work iteam 的回退
- 点击 Reverse 菜单后,会弹出如下图 20 所示的窗口,指示修改前的文件将会在 Pending Patches 以 patch 的形式显示出来。然后,点击 OK 按钮。
图 20. 添加到 Pending Changes
- 如图 21 所示,在 patch 中你可以通过双击 java 文件来查看原来文件里的内容和错误交付代码后的文件的差异,来查看是否是你想要恢复的结果。
图 21. Patch 显示界面
- 将这个 patch 添加到现有的 workspace 上面,从而将原有的变更集先恢复到本地的 workspace 上。
图 22. 合并 Patch 界面
- 原有的文件将会作为未提交的文件显示出来。
图 23. 未解决冲突界面
- 通过将原有的代码重新 check-in 和 deliver,从而将被错误修改的代码从项目组共享的 stream 上删除出去。
图 24. Check-in 变更集界面
如果将代码错误提交到 stream 后,有其他开发人员提交了自己的代码,这个时候要进行回退的时候,需要查看需要回退的文件是否和其他开发人员修改的文件有冲突。如果有冲突,可以通过前面提过的方法进行代码冲突的整合;或者把错误提交代码以后其他开发人员提交的代码先回退回去,在把错误提交的代码从 stream 上删除以后,重新将其他开发人员提交的代码 deliver 到 stream 上。
致谢
衷心的感谢钱巧能工程师在本文大纲方面的讨论所提供的意见和支持。
结束语
本文详细介绍了 RTC 代码同步的原理,并且比较了目前 RTC 中多 stream 上代码同步的几种方法,最后以具体示例说明了如何在 RTC 中进行多 stream 的代码同步,解决代码同步中出现的冲突及如何撤销错误的代码同步。读者可以通过阅读本文,掌握各种在 RTC 中多 stream 代码同步的方法,并根据项目实际情况选择,灵活运用,提高项目开发的整体效率。
简介
本文介绍了 IBM Rational Team Concert(RTC)的代码评审功能(Code Review)。这一功能可以使代码评审流程变得更加规范,完善代码提交流程;对于不同区域的成员可以更高效的协同工作,在代码提交前发现到潜在的问题,尽快修复,提高代码质量,有效减少缺陷数。
代码评审的重要性
多数情况下,评审者能够发现开发人员自身不能发现的问题或潜在问题,毕竟思维方式和考虑的点等会有差异
- 优秀的评审者通过评审,不仅可以发现开发人员的不足,更可以通过开发人员的代码学到很多知识
- 对团队而言,通过评审以及互相评审,了解到其他团队成员负责的任务,必要时互相帮忙,互为后援,提高项目开发效率,降低项目风险
代码评审的规则
- 从逻辑上讲,本次修改是否符合需求,是否有类似的地方需要修改
- 可读性上,是否有足够的解释说明,以便以后维护
- 就代码规范而言,是否符合公司代码规范,以保持代码风格一致性
- 从代码性能上讲,是否有更有效的方案可以实现
- 单元测试(如果必要),测试用例是否能覆盖本次的修改
RTC 对代码评审的支持
作为目前主流的代码管理工具,RTC 对代码评审的功能已经有了很好的支持,比如利用邮件作为代码提交者与评审者通信的工具,代码评审过程中各个角色的划分,代码提交的权限设置等等,本节将具体介绍 RTC 在代码评审过程中所涉及的概念及详细配置。
对邮件的支持
在整个代码评审过程中,邮件是作为代码提交者及其他相关人员之间的重要通信工具,比如在代码评审前提交、对代码添加修改意见,团队相关人员都应收到相应的邮件提醒,用户可以通过如下配置是否启用邮件支持:
- 打开 Repository Connections 界面并连接
图 1. Repository 连接界面
- 打开用户编辑窗口,如图 2 所示:
图 2. Open My User Editor
- 找到 Mail Configuration 页面,进行相应的配置并保存,如图 3 所示:
图 3. 邮件配置界面
代码评审中所涉及的角色的划分
在整个代码评审过程中,RTC 将会涉及到如下几种角色
- Submitter:代码提交者,由实现新功能的开发者自己担任,可执行的操作有 Submit for review、Check-in,Deliver、Add Approver 等等。
- Review:代码审查人员,负责代码提交前细粒度的代码审查工作,排除潜在的缺陷,一般由团队中对所要修改的代码比较熟悉的人员担任。可执行的操作有 Add comment、Rejected、Approved 等。
- Approver:代码评阅者,负责代码提交前粗粒度的代码审查工作,一般由资深开发人员及 tech lead 担任,可执行的操作有 Add comments,Rejected、Approved 等。
- Verifier:功能的验证者,对功能的实现作一些单元测试等等。
事实上对于以上这些角色,Submitter,Reviewer 和 Approver 是必须,Verifier 是可选的,用户可以根据团队实际情况决定在代码评审过程中是否需要 Verifier。
代码提交前的权限控制
RTC 在代码提交时有了较好权限配置的支持,用户(一般由 Project owner 或团队的 tech lead 配置此权限)可以根据如下步骤进行配置:
- 连接自己所在的项目并打开,如图 4 所示:
图 4. 项目连接界面
- 切换到 Project Area 中的 Process Configuration 页面,如图 5 所示:
图 5. 流程配置界面
- 双击 Team Configuration 中的 Operation Behavior 选项,如图 6 所示:
图 6. 操作行为界面
- 从右边列表中选择 Source Control 中的 Deliver(server)选项,双击对应 Everyone(default) 图标,并双击 Add ... 按钮添加代码提交时的 Preconditions. 如图 7 所示:
图 7. Add Preconditions
- 在弹出的 Add Preconditions 对话框中选择 Require Work Item Approval 选项,如图 8 所示:
图 8. Add Preconditions 界面
- 双击 Required approvals 栏中 new ... 按钮添加代码提交时的 Required Approval,针对不同的 Approval 类型选择相应的 Approvers,单击 ok 按钮,最后保存所有配置。如图 9 所示:
图 9. 添加 Approval
经过上述一系列配置后,代码提交者必须先取得相应的 Approval 之后才能提交代码,从而达到代码提交时的权限控制,保证代码的质量。
RTC 代码评审使用示例
在 RTC 中,代码评审的流程大致如下,各个团队可以根据实际情况进行优化。
图 10. 代码评审时序图
下面我将以一个简单的实例来说明在 RTC 中是如何进行代码评审的:
- Check in 变更集
修改源代码,在 RTC 客户端中找到 Pending Changes 视图,并将这些变更集 check in 到在 Jazz repository 上的 workspace,如图 11 所示:
图 11. Check in 变更集界面
- 关联 work item
在 Pending Changes 视图中,将 check in 的变更集关联到相应的 work item(story 上的 task work item 或者是 issue 类型的 work item) 上。
图 12. 关联 work item 界面
- 提交代码给相应的 approver review
在 Pending Changes 视图中,找到相应的 Outgoing 变更集,点击右键菜单中的 Submit for Review... 选项。
图 13. Submit for Review 界面
- 添加注释和相应的 Approver
在弹出的 submit for review 的对话框中添加相应的注释及添加相应的 Approver 类型(具体类型请参考 RTC 中对代码评审章节)。
图 14. 添加注释和 Approver 对话框
- 确定需要评审代码关联的 work item
Approver 将会根据 RTC 中关于代码评审的邮件找到相应的 work item,并从 work item 中找到链接页面。
图 15. work item 链接界面
- 查找对应的变更集
Approver 从链接页面中找到需要评审的变更集,双击此变更集,变更集将会在 RTC 客户端的变更视图中显示。
图 16. 查看变更集界面
- 给出评审结果及意见
每个 Approver 根据代码评审的实际情况给出相应的修改意见和评审意见,如对于哪一行代码需要修改或者同意提交等等,具体操作如图 17 所示:
图 17. 代码评审界面
如果给出的评审结果为同意提交,则 submitter 直接进入代码提交阶段。如在此阶段给出的评审结果为拒绝,则 submitter 需要从 work item 的 overview 视图或 RTC 邮件中查看 Approver 添加的修改意见,并根据意见进行代码修改,重新提交代码评审。
- 代码提交
代码 Submitter 在 Pending Changes 视图中找到相应的 Outgoing 变更集并提交。
RTC 代码评审的注意事项
一次代码评审和所提交的一个变更集是一一对应的关系,当对一个变更集提交代码评审后,这个变更集就被冻结,此后对其中任何文件的修改都将通过另一新的变更集来跟踪。所以,对于新的修改,需要再次提交代码评审。
评审者在应用被评审者的变更集进行代码评审时,有时会和评审者本地的变更集产生冲突,此时只要将产生冲突的本地变更集暂停(Suspend),就能暂时避免冲突从而继续进行评审。
RTC 代码评审的不足及展望
虽然 RTC 已经能够支持完整的代码评审功能,但在实际使用中还有一些改进之处。比如无法动态嵌入或链接到评论所涉及的代码中的指定行,方便被评审者阅读修改意见。而且评审的粒度较大,基于每个评审者的修改意见无法针对每条评论进行接收或拒绝。此外,如果加入代码静态分析功能,就能更快的找到问题,避免人为的疏漏。
为什么很多看起来不是很复杂的网站比如 Facebook、淘宝,需要大量顶尖高手来开发?
就拿淘宝来说说,当作给新人一些科普。
先说你看到的页面上,最重要的几个:
【搜索商品】——这个功能,如果你有几千条商品,完全可以用select * from tableXX where title like %XX%这样的操作来搞定。但是——当你有10000000000(一百亿)条商品的时候,任何一个数据库都无法存放了,请问你怎么搜索?这里需要用到分 布式的数据存储方案,另外这个搜索也不可能直接从数据库里来取数据,必然要用到搜索引擎(简单来说搜索引擎更快)。好,能搜出商品了,是否大功告成可以啵 一个了呢?早着呢,谁家的商品出现在第一页?这里需要用到巨复杂的排序算法。要是再根据你的购买行为做一些个性化的推荐——这够一帮牛叉的算法工程师奋斗 终生了。
【商品详情】——就是搜索完毕,看到你感兴趣的,点击查看商品的页面,这个页面有商品的属性、详细描述、评价、卖家信息等等,这个页面的每天展示次数在 30亿以上,同样的道理,如果你做一个网站每天有10个人访问,你丝毫感觉不到服务器的压力,但是30亿,要解决的问题就多了去了。首先,这些请求不能直 接压到数据库上,任何单机或分布式的数据库,承受30亿每天的压力,都将崩溃到完全没有幸福感,这种情况下要用到的技术就是大规模的分布式缓存,所有的卖 家信息、评价信息、商品描述都是从缓存里面来取到的,甚至更加极致的一点“商品的浏览量”这个信息,每打开页面一次都要刷新,你猜能够从缓存里面来取吗? 淘宝做到了,整个商品的详情都在缓存里面。
【商品图片】——一个商品有5个图片,商品描述里面有更多图片,你猜淘宝有多少张图片要存储?100亿以上。这么多图片要是在你的硬盘里面,你怎么去查找 其中的一张?要是你的同学想拷贝你的图片,你需要他准备多少块硬盘?你需要配置多少大的带宽?你们的网卡是否能够承受?你需要多长时间拷贝给他?这样的规 模,很不幸市面上已经没有任何商业的解决方案,最终我们必须自己来开发一套存储系统,如果你听说过google的GFS,我们跟他类似,叫TFS。顺便说 一下,腾讯也有这样的一套,也叫TFS。
【广告系统】——淘宝上有很多广告,什么,你不知道?那说明我们的广告做的还不错,居然很多人不认为它是广告,卖家怎么出价去买淘宝的广告位?广告怎么展示?怎么查看广告效果?这又是一套算法精奇的系统。
【BOSS系统】——淘宝的工作人员怎么去管理这么庞大的一个系统,例如某时刻突然宣布某位作家的作品全部从淘宝消失,从数据库到搜索引擎到广告系统,里面的相关数据在几分钟内全部消失,这又需要一个牛叉的后台支撑系统。
【运维体系】——支持这么庞大的一个网站,你猜需要多少台服务器?几千台?那是零头。这么多服务器,上面部署什么操作系统,操作系统的内核能否优 化?Java虚拟机能否优化?通信模块有没有榨取性能的空间?软件怎么部署上去?出了问题怎么回滚?你装过操作系统吧,优化过吧,被360坑过没,崩溃过 没?这里面又有很多门道。
不再多写了,除了上面提到的这些,还有很多很多需要做的技术,当然并不是这些东西有多么高不可攀,任何复杂的庞大的东西都是从小到大做起来的,里面需要牛叉到不行的大犇,也需要充满好奇心的菜鸟,最后这一句,你当我是别有用心好了。
功能上面虽然不复杂,但是要完成的细节却很多. 比如news feed里面的推荐算法就很重要,要根据用户之前的记录和与好友的关系来生成. 另外就是根据用户的信息和行为,要做机器学习和数据挖掘,从而来挑出最匹配的广告.这也是比较花人力的事情.
另外Facebook的用户量奇大无比. 假设你只是做一个学校内部用的社交网站, 那肯定很简单. 但是如果考虑到上亿人在上面用. 你首先服务器就必 须是一个分布式的机群,还要保证能抗住那么大的流量. 同时为了性能够好,不得不加上mem cache和网页分块加载等功能. 还有就是每天用户产生的总数据量(状态,留言,照片,分享等)有TB的数量级,你数据库是否撑住等等.
另外树大招风,你要一个很强的安全小组来保证网站在受攻击的时候能防御好,还有要防止垃圾信息和恶心广告或者程序的散播. 另外还有为了全球化而带来的多语言问题.
总之,一个网站做大了之后,很多问题就会产生,不是在校园里面做一个学期作业那么简单.狼大人,休,行。
某个历史上的大神曾经说过一句话:
要判断一个算法的好坏,只要给它足够的数据。
当用户、数据和系统规模上到一个程度之后,所有曾经可以忽视的问题都会变得必须用从来不可想象的精力和技术去解决。
来来来 看看这个 刚看到的《当用户点击“举报”后,Facebook在后台会做哪些事情》
很多东西并不是表面看到的那样简单
身为前端攻城师,就这方面说一下这样一个Facebook看起来“很简单的网站”需要顶尖高手来开发和维护
写前端程序要考虑很多,如下:可维护性,JS的执行高效性,JS文件的大小,用户体验等等
1. 可维护性
并不是所有人写的程序都具有可维护性,清晰易懂的,这个区别在刚接触编程和高手直接的差异体现的特别明显
2. JS的执行高效性
一个网页加载JS并执行,浏览器执行并渲染了半天还在渲染,相信很多用户都不想看到吧?非常上海用户体验。
如何提升JS的执行速度呢?相信我,初学者大部分都是不知道的(排除写过浏览器内核的同学),了解浏览器如何执行JS,如何渲染DOM能帮助开发者提升执行速度
3. JS文件的大小
JS文件或者HTML或者CSS文件过大,有很多缺点
第一,受网速影响,文件大,加载速度慢
第二,Facebook的用户量非常巨大,每个人访问就算多加载1KB(即使有cache,第一次总需要加载吧),可想而知,这个流量非常巨大,流量都是要钱的啊
4. 用户体验
略之
高手往往比非高手要注意的东西要多很多,这点相信大家不会质疑吧,只是就前端方面发表一些建议
如果要把一件简单的事情搞复杂,你需要的是市场总监
如果要把一件简单的事情搞简单,你需要的是产品经理
如果要把一件复杂的事情搞复杂,你需要的是一堆码农
如果要把一件复杂的事情搞简单,你需要的是顶尖高手
明白了吧,顶尖高手的意义就在于让你觉得这个东西看起来很简单。
学“懂”计算机系统结构之后,理解这个问题比较容易点吧。
比如,你打开知乎,简单的敲下几行字,点击提交,然后你看到了你的评论,这复杂吗?不复杂。
1、为什么知乎要用浏览器才能打开呢,记事本不能访问知乎?为什么用输入自己的知乎用户名和密码才能访问呢,我QQ密码为嘛不能用?为什么我只能看到我关注内容,而不是网站所有用户的所有内容呢?
2、当敲下键盘时,电脑是如何识别按下的具体哪个字符呢?是如何经过到达的CPU呢?CPU进行了如何的处理操作呢?显示器为什么可以显示相应的字符呢?
3、当点击“提交”时,发生哪些变化呢?显示的内容是保存了在本地电脑上了,还是保存在了知乎服务器上了呢?从本地的数据是如何传输到知乎服务器呢?从物 理层、数据链路层、网络层、传输层、会话层、表示层、应用层这一系列的过程提交的评论内容传输形式是经过一系列的如何变化过程呢?
PS.对于一个字符从输入到最后结果输出的处理流程我也没搞彻底清楚。随便说说。。。希望有高手能具体解释下啊。
手上没有网站的结构层次示意图,随便拿张图说明下原因吧。像计算机最初问世的时候,操作计算机的用户既是设计者也是维护者的时代已经过去。每个节点都应该 是一个属于一个节点的专属内容,对个每个节点都应该是更清晰化更简单化的:用户的操作的应该简单化,网络的传输应该简单化,数据的存储也应该是简单化。因 此对于用户来说只需要用户界面操作就足够了,没必要把数据库之类的东西也交给用户处理的。
public class FindA{
public static void main(String args[])
throws Exception{
String candidate =
"A Matcher examines the results of applying a pattern.";
String regex = "\\ba\\w*\\b";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(candidate);
String val = null;
System.out.println("INPUT: " + candidate);
System.out.println("REGEX: " + regex +"\r\n");
while (m.find()){
val = m.group();
System.out.println("MATCH: " + val);
}
if (val == null) {
System.out.println("NO MATCHES: ");
}
}
}
\b表示单词的边界,w表示任意的可构成单词的字母数字,*表示前面的字母(当然可以
是更复杂的组之类的了东东)重复0次或0次以上,a当然还是a了。所以这个regex就
匹配单词开头为a的单词了。
二、下面总结一下基本的正则表达式的meta character以及它们含义:
. 匹配任意一个字符 $ 匹配一行的结尾 ^ 匹配一行的开头(在[]里面表示否定)
{} 定义了一个范围 [] 定义了一个字符类 () 定义了一个组
*前面出现0次以上 + 前面匹配一次以上 ?前面出现0次或一次
\ 后面的字符不会看作metacharacter \w 字母数字下划线 \W 非字母数字下划线
\d 单个数字 \D单个非数字 | 或,二者之一 &&与操作符 \b单词边界
下面看看几个简单的例子:
[abc] a、b 或 c(简单类)
[^abc] 任何字符,除了a、b 或 c(否定)
[a-zA-Z] a 到 z 或 A 到 Z,两头的字母包括在内(范围)
[a-d[m-p]] a 到 d 或 m 到 p:[a-dm-p](并集)
[a-z&&[def]] d、e 或 f(交集)
[a-z&&[^bc]] a 到 z,除了 b 和 c:[ad-z](减去)
[a-z&&[^m-p]] a 到 z,而非 m 到 p:[a-lq-z](减去)
\\ 反斜杠
\t 间隔 ('\u0009')
\n 换行 ('\u000A')
\r 回车 ('\u000D')
\d 数字 等价于[0-9]
\D 非数字 等价于[^0-9]
\s 空白符号 [\t\n\x0B\f\r]
\S 非空白符号 [^\t\n\x0B\f\r]
\w 单独字符 [a-zA-Z_0-9]
\W 非单独字符 [^a-zA-Z_0-9]
\f 换页符
\e Escape
\b 一个单词的边界
\B 一个非单词的边界
\G 前一个匹配的结束
^为限制开头
^java 条件限制为以Java为开头字符
$为限制结尾
java$ 条件限制为以java为结尾字符
. 条件限制除\n以外任意一个单独字符
java.. 条件限制为java后除换行外任意两个字符
加入特定限制条件「[]」
[a-z] 条件限制在小写a to z范围中一个字符
[A-Z] 条件限制在大写A to Z范围中一个字符
[a-zA-Z] 条件限制在小写a to z或大写A to Z范围中一个字符
[0-9] 条件限制在小写0 to 9范围中一个字符
[0-9a-z] 条件限制在小写0 to 9或a to z范围中一个字符
[0-9[a-z]] 条件限制在小写0 to 9或a to z范围中一个字符(交集)
[]中加入^后加再次限制条件「[^]」
[^a-z] 条件限制在非小写a to z范围中一个字符
[^A-Z] 条件限制在非大写A to Z范围中一个字符
[^a-zA-Z] 条件限制在非小写a to z或大写A to Z范围中一个字符
[^0-9] 条件限制在非小写0 to 9范围中一个字符
[^0-9a-z] 条件限制在非小写0 to 9或a to z范围中一个字符
[^0-9[a-z]] 条件限制在非小写0 to 9或a to z范围中一个字符(交集)
在限制条件为特定字符出现0次以上时,可以使用「*」
J* 0个以上J
.* 0个以上任意字符
J.*D J与D之间0个以上任意字符
在限制条件为特定字符出现1次以上时,可以使用「+」
J+ 1个以上J
.+ 1个以上任意字符
J.+D J与D之间1个以上任意字符
在限制条件为特定字符出现有0或1次以上时,可以使用「?」
JA? J或者JA出现
限制为连续出现指定次数字符「{a}」
J{2} JJ
J{3} JJJ
文字a个以上,并且「{a,}」
J{3,} JJJ,JJJJ,JJJJJ,???(3次以上J并存)
文字个以上,b个以下「{a,b}」
J{3,5} JJJ或JJJJ或JJJJJ
两者取一「|」
J|A J或A
Java|Hello Java或Hello
「()」中规定一个组合类型
比如,我查询<a href=\"index.html\">index</a>中<a href></a>间的数据,可写作<a.*href=\".*\">(.+?)</a>
在使用Pattern.compile函数时,可以加入控制正则表达式的匹配行为的参数:
Pattern Pattern.compile(String regex, int flag)
flag的取值范围如下:
Pattern.CANON_EQ 当且仅当两个字符的"正规分解(canonical decomposition)"都完全相同的情况下,才认定匹配。比如用了这个标志之后,表达式"a\u030A"会匹配"?"。默认情况下,不考虑"规 范相等性(canonical equivalence)"。
Pattern.CASE_INSENSITIVE(?i) 默认情况下,大小写不明感的匹配只适用于US-ASCII字符集。这个标志能让表达式忽略大小写进行匹配。要想对Unicode字符进行大小不明感的匹 配,只要将UNICODE_CASE与这个标志合起来就行了。
Pattern.COMMENTS(?x) 在这种模式下,匹配时会忽略(正则表达式里的)空格字符(译者注:不是指表达式里的"\\s",而是指表达式里的空格,tab,回车之类)。注释从#开始,一直到这行结束。可以通过嵌入式的标志来启用Unix行模式。
Pattern.DOTALL(?s) 在这种模式下,表达式'.'可以匹配任意字符,包括表示一行的结束符。默认情况下,表达式'.'不匹配行的结束符。
Pattern.MULTILINE
(?m) 在这种模式下,'^'和'$'分别匹配一行的开始和结束。此外,'^'仍然匹配字符串的开始,'$'也匹配字符串的结束。默认情况下,这两个表达式仅仅匹配字符串的开始和结束。
Pattern.UNICODE_CASE
(?u) 在这个模式下,如果你还启用了CASE_INSENSITIVE标志,那么它会对Unicode字符进行大小写不明感的匹配。默认情况下,大小写不敏感的匹配只适用于US-ASCII字符集。
Pattern.UNIX_LINES(?d) 在这个模式下,只有'\n'才被认作一行的中止,并且与'.','^',以及'$'进行匹配。
抛开空泛的概念,下面写出几个简单的Java正则用例:
◆比如,在字符串包含验证时
//查找以Java开头,任意结尾的字符串
Pattern pattern = Pattern.compile("^Java.*");
Matcher matcher = pattern.matcher("Java不是人");
boolean b= matcher.matches();
//当条件满足时,将返回true,否则返回false
System.out.println(b);
◆以多条件分割字符串时
Pattern pattern = Pattern.compile("[, |]+");
String[] strs = pattern.split("Java Hello World Java,Hello,,World|Sun");
for (int i=0;i<strs.length;i++) {
System.out.println(strs[i]);
}
◆文字替换(首次出现字符)
Pattern pattern = Pattern.compile("正则表达式");
Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World");
//替换第一个符合正则的数据
System.out.println(matcher.replaceFirst("Java"));
◆文字替换(全部)
Pattern pattern = Pattern.compile("正则表达式");
Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World");
//替换第一个符合正则的数据
System.out.println(matcher.replaceAll("Java"));
◆文字替换(置换字符)
Pattern pattern = Pattern.compile("正则表达式");
Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World ");
StringBuffer sbr = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sbr, "Java");
}
matcher.appendTail(sbr);
System.out.println(sbr.toString());
◆验证是否为邮箱地址
String str="ceponline@yahoo.com.cn";
Pattern pattern = Pattern.compile("[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+",Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(str);
System.out.println(matcher.matches());
◆去除html标记
Pattern pattern = Pattern.compile("<.+?>", Pattern.DOTALL);
Matcher matcher = pattern.matcher("<a href=\"index.html\">主页</a>");
String string = matcher.replaceAll("");
System.out.println(string);
◆查找html中对应条件字符串
Pattern pattern = Pattern.compile("href=\"(.+?)\"");
Matcher matcher = pattern.matcher("<a href=\"index.html\">主页</a>");
if(matcher.find())
System.out.println(matcher.group(1));
}
◆截取http://地址
//截取url
Pattern pattern = Pattern.compile("(http://|https://){1}[\\w\\.\\-/:]+");
Matcher matcher = pattern.matcher("dsdsds<http://dsds//gfgffdfd>fdf");
StringBuffer buffer = new StringBuffer();
while(matcher.find()){
buffer.append(matcher.group());
buffer.append("\r\n");
System.out.println(buffer.toString());
}
◆替换指定{}中文字
String str = "Java目前的发展史是由{0}年-{1}年";
String[][] object={new String[]{"\\{0\\}","1995"},new String[]{"\\{1\\}","2007"}};
System.out.println(replace(str,object));
public static String replace(final String sourceString,Object[] object) {
String temp=sourceString;
for(int i=0;i<object.length;i++){
String[] result=(String[])object[i];
Pattern pattern = Pattern.compile(result[0]);
Matcher matcher = pattern.matcher(temp);
temp=matcher.replaceAll(result[1]);
}
return temp;
}
◆以正则条件查询指定目录下文件
//用于缓存文件列表
private ArrayList files = new ArrayList();
//用于承载文件路径
private String _path;
//用于承载未合并的正则公式
private String _regexp;
class MyFileFilter implements FileFilter {
/**
* 匹配文件名称
*/
public boolean accept(File file) {
try {
Pattern pattern = Pattern.compile(_regexp);
Matcher match = pattern.matcher(file.getName());
return match.matches();
} catch (Exception e) {
return true;
}
}
}
/**
* 解析输入流
* @param inputs
*/
FilesAnalyze (String path,String regexp){
getFileName(path,regexp);
}
/**
* 分析文件名并加入files
* @param input
*/
private void getFileName(String path,String regexp) {
//目录
_path=path;
_regexp=regexp;
File directory = new File(_path);
File[] filesFile = directory.listFiles(new MyFileFilter());
if (filesFile == null) return;
for (int j = 0; j < filesFile.length; j++) {
files.add(filesFile[j]);
}
return;
}
/**
* 显示输出信息
* @param out
*/
public void print (PrintStream out) {
Iterator elements = files.iterator();
while (elements.hasNext()) {
File file=(File) elements.next();
out.println(file.getPath());
}
}
public static void output(String path,String regexp) {
FilesAnalyze fileGroup1 = new FilesAnalyze(path,regexp);
fileGroup1.print(System.out);
}
public static void main (String[] args) {
output("C:\\","[A-z|.]*");
}
@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);