Let's Go!

发布新日志

  • 如何在面试时选择合适的测试人员? (转)

    2011-12-06 17:54:39

    [原创]如何在面试时选择合适的测试人员?

     

       各位,大家好!今天分享一下我在面试测试人员时常问的一些问题及为什么,仅供各位参考,谢谢!

    你最近3-5年的职业规划是什么?

       重点考察测试人员的职业发展方向是否与当前职位招聘相符? 从其中可以侧面看出来其员工稳定性。

     

    一个项目测试结束,有没什么经验总结?如果有,具体是如何开展的?

       重点考察测试人员对自己能力提升方面,有没有提高总结的地方,从项目中吸取的经验与教训。从中可以看出来,测试人员是否属行自我驱动型人才!

     

    为什么会选择做测试这份工作?

       重点考察测试人员对待测试工作的态度及是否有发展潜力?面试过很多测试人员,经常见到的回答,自己是女孩子,做测试细心,各位你认为这样回答你会满意吗?其码不是我想要的答案!

     

    请说出一个你以前参与项目,对你测试经验提升很高的,具体是哪方面?

       重点考察测试人员在以往的测试工作中能力提升方面,有哪些?然后重点询问此部分内容,是否测试经验增长,具备一定的深度?

     

    通常做测试时会碰到,提交的某个bug开发人员不认同你的观点?这时你如何办?

       重点考察测试人员是否坚持自已的价值观?是否具备协调沟通处理问题能力?

     

    6有没有看过什么测试书,具体是哪本?带给你的收获是?

      重点考察测试人员是否为测试这个职业肯付出多少?从中也可以看出这个测试人员是否上进心?是否有求知心?我的定义是如果哪个应聘者来面试时,都没系统的看过一本测试书籍,基本上不会录取!

     

    如果安排一项测试技术研究工作,你如何应对?

    重点考察测试人员是否具体测试技术专研精神?是否喜欢接受挑战?是否属于以后培养骨干对象?

     

    某个项目上线后,出现问题,恰巧你是负责的,你如何应对这突如其来的事件?

    重点考察测试人员应对问题的压力,责任感,及如何处理项目上线后的技术问题及应对解决能力。

     

    周末放假有什么业余爱好?

       重点考察面试测试人员性格特质,测试工作本身就是复杂且富有技术性的工作,而且不同的职位所需要的测试人员性格品质差异性很大。

     

    10 公司产品,具体应用什么编程技术?具体的架构是?具体的应用场景有哪些?

       重点考察测试人员对以往的工作所负责的产品测试,是否具备一定的深度!通常我都是让面试者自己讲述或是在纸上画出具体系统架构的图!

     

    11 公司测试团队的规模如何,具体你所处的角色是什么?

       重点考察测试人员在以往的公司测试团队中,具体的工作职责,评判其工作是否与当要求职位是否符合?是否有哪些优缺点?

     

    12 特定测试技术考察:性能测试,安全性测试,自动化测试等以前有开展过没?如果有,具体是如何实施的?

       重点考察测试人员技术能力,是否在各方面都有所涉及?或是在各方面技术上都有一定深度?当然从中也能看出一个测试人员是否属于是技术路线发展方向!

     

    13你自己所期待加入的测试团队是什么样的?

       重点考察测试人员在以前测试团队中有哪些不协调?当然最重要的是也能提供给你一些信息,这个员工以后如何更好的管理与沟通!


    转自:http://www.cnblogs.com/mayingbao/archive/2011/12/01/2270452.html



    HttpWatch工具简介及使用技巧

    http://www.cnblogs.com/mayingbao/archive/2007/11/30/978530.html
  • 浏览器是如何工作的?(工作原理)(三)

    2011-12-06 17:29:02


    对规则进行处理以简化匹配过程

    样式规则有几个来源:

    · 外部样式表或style标签内的css规则

    · 行内样式属性

    · html可视化属性(映射为相应的样式规则)

    后面两个很容易匹配到元素,因为它们所拥有的样式属性和html属性可以将元素作为key进行映射。

    就像前面问题2所提到的,css的规则匹配可能很狡猾,为了解决这个问题,可以先对规则进行处理,以使其更容易被访问。

    解析完样式表之后,规则会根据选择符添加一些hash映射,映射可以是根据id、class、标签名或是任何不属于这些分类的综合映射。如果选择符为id,规则将被添加到id映射,如果是class,则被添加到class映射,等等。

    这个处理是匹配规则更容易,不需要查看每个声明,我们能从映射中找到一个元素的相关规则,这个优化使在进行规则匹配时减少了95+%的工作量。

    来看下面的样式规则:

    p.error {color:red}

    #messageDiv {height:50px}

    div {margin:5px}

    第一条规则将被插入class映射,第二条插入id映射,第三条是标签映射。

    下面这个html片段:

    <p class=”error”>an error occurred </p>

    <div id=” messageDiv”>this is a message</div>

    我们首先找到p元素对应的规则,class映射将包含一个“error”的key,找到p.error的规则,div在id映射和标签映射中都有相关的规则,剩下的工作就是找出这些由key对应的规则中哪些确实是正确匹配的。

    例如,如果div的规则是

    table div {margin:5px}

    这也是标签映射产生的,因为key是最右边的选择符,但它并不匹配这里的div元素,因为这里的div没有table祖先。

    Webkit和Firefox都会做这个处理。

    以正确的级联顺序应用规则

    样式对象拥有对应所有可见属性的属性,如果特性没有被任何匹配的规则所定义,那么一些特性可以从parent的样式对象中继承,另外一些使用默认值。

    这个问题的产生是因为存在不止一处的定义,这里用级联顺序解决这个问题。

    样式表的级联顺序

    一个样式属性的声明可能在几个样式表中出现,或是在一个样式表中出现多次,因此,应用规则的顺序至关重要,这个顺序就是级联顺序。根据css2的规范,级联顺序为(从低到高):

    1. 浏览器声明

    2. 用户声明

    3. 作者的一般声明

    4. 作者的important声明

    5. 用户important声明

    浏览器声明是最不重要的,用户只有在声明被标记为important时才会覆盖作者的声明。具有同等级别的声明将根据specifity以及它们被定义时的顺序进行排序。Html可视化属性将被转换为匹配的css声明,它们被视为最低优先级的作者规则。

    Specifity

    Css2规范中定义的选择符specifity如下:

    · 如果声明来自style属性,而不是一个选择器的规则,则计1,否则计0(=a)

    · 计算选择器中id属性的数量(=b)

    · 计算选择器中class及伪类的数量(=c)

    · 计算选择器中元素名及伪元素的数量(=d)

    连接a-b-c-d四个数量(用一个大基数的计算系统)将得到specifity。这里使用的基数由分类中最高的基数定义。例如,如果a为14,可以使用16进制。不同情况下,a为17时,则需要使用阿拉伯数字17作为基数,这种情况可能在这个选择符时发生html body div div …(选择符中有17个标签,一般不太可能)。

    一些例子:

    * {} /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */

    li {} /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */

    li:first-line {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */

    ul li {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */

    ul ol+li {} /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */

    h1 + *[rel=up]{} /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */

    ul ol li.red {} /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */

    li.red.level {} /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */

    #x34y {} /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */

    style=”” /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */

    规则排序

    规则匹配后,需要根据级联顺序对规则进行排序,webkit先将小列表用冒泡排序,再将它们合并为一个大列表,webkit通过为规则复写“>”操作来执行排序:

    static bool operator >(CSSRuleData& r1, CSSRuleData& r2)

    {

    int spec1 = r1.selector()->specificity();

    int spec2 = r2.selector()->specificity();

    return (spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2;

    }

    逐步处理 Gradual process

    webkit使用一个标志位标识所有顶层样式表都已加载,如果在attch时样式没有完全加载,则放置占位符,并在文档中标记,一旦样式表完成加载就重新进行计算。

    布局 Layout

    当渲染对象被创建并添加到树中,它们并没有位置和大小,计算这些值的过程称为layout或reflow。

    Html使用基于流的布局模型,意味着大部分时间,可以以单一的途径进行几何计算。流中靠后的元素并不会影响前面元素的几何特性,所以布局可以在文档中从右向左、自上而下的进行。也存在一些例外,比如html tables。

    坐标系统相对于根frame,使用top和left坐标。

    布局是一个递归的过程,由根渲染对象开始,它对应html文档元素,布局继续递归的通过一些或所有的frame层级,为每个需要几何信息的渲染对象进行计算。

    根渲染对象的位置是0,0,它的大小是viewport-浏览器窗口的可见部分。

    所有的渲染对象都有一个layout或reflow方法,每个渲染对象调用需要布局的children的layout方法。

    Dirty bit 系统

    为了不因为每个小变化都全部重新布局,浏览器使用一个dirty bit系统,一个渲染对象发生了变化或是被添加了,就标记它及它的children为dirty-需要layout。存在两个标识-dirty及children are dirty,children are dirty说明即使这个渲染对象可能没问题,但它至少有一个child需要layout。

    全局和增量 layout

    当layout在整棵渲染树触发时,称为全局layout,这可能在下面这些情况下发生:

    1. 一个全局的样式改变影响所有的渲染对象,比如字号的改变

    2. 窗口resize

    layout也可以是增量的,这样只有标志为dirty的渲染对象会重新布局(也将导致一些额外的布局)。增量 layout会在渲染对象dirty时异步触发,例如,当网络接收到新的内容并添加到Dom树后,新的渲染对象会添加到渲染树中。

    图20:增量 layout

    异步和同步layout

    增量layout的过程是异步的,Firefox为增量layout生成了reflow队列,以及一个调度执行这些批处理命令。Webkit也有一个计时器用来执行增量layout-遍历树,为dirty状态的渲染对象重新布局。

    另外,当脚本请求样式信息时,例如“offsetHeight”,会同步的触发增量布局。

    全局的layout一般都是同步触发。

    有些时候,layout会被作为一个初始layout之后的回调,比如滑动条的滑动。

    优化

    当一个layout因为resize或是渲染位置改变(并不是大小改变)而触发时,渲染对象的大小将会从缓存中读取,而不会重新计算。

    一般情况下,如果只有子树发生改变,则layout并不从根开始。这种情况发生在,变化发生在元素自身并且不影响它周围元素,例如,将文本插入文本域(否则,每次击键都将触发从根开始的重排)。

    layout过程

    layout一般有下面这几个部分:

    1. parent渲染对象决定它的宽度

    2. parent渲染对象读取chilidren,并:

    1. 放置child渲染对象(设置它的x和y)

    2. 在需要时(它们当前为dirty或是处于全局layout或者其他原因)调用child渲染对象的layout,这将计算child的高度

    3. parent渲染对象使用child渲染对象的累积高度,以及margin和padding的高度来设置自己的高度-这将被parent渲染对象的parent使用

    4. 将dirty标识设置为false

    Firefox使用一个“state”对象(nsHTMLReflowState)做为参数去布局(firefox称为reflow),state包含parent的宽度及其他内容。

    Firefox布局的输出是一个“metrics”对象(nsHTMLReflowMetrics)。它包括渲染对象计算出的高度。

    宽度计算

    渲染对象的宽度使用容器的宽度、渲染对象样式中的宽度及margin、border进行计算。例如,下面这个div的宽度:

    <div style=”width:30%”/>

    webkit中宽度的计算过程是(RenderBox类的calcWidth方法):

    · 容器的宽度是容器的可用宽度和0中的最大值,这里的可用宽度为:contentWidth=clientWidth()-paddingLeft()-paddingRight(),clientWidth和clientHeight代表一个对象内部的不包括border和滑动条的大小

    · 元素的宽度指样式属性width的值,它可以通过计算容器的百分比得到一个绝对值

    · 加上水平方向上的border和padding

    到这里是最佳宽度的计算过程,现在计算宽度的最大值和最小值,如果最佳宽度大于最大宽度则使用最大宽度,如果小于最小宽度则使用最小宽度。最后缓存这个值,当需要layout但宽度未改变时使用。

    Line breaking

    当一个渲染对象在布局过程中需要折行时,则暂停并告诉它的parent它需要折行,parent将创建额外的渲染对象并调用它们的layout。

    绘制 Painting

    绘制阶段,遍历渲染树并调用渲染对象的paint方法将它们的内容显示在屏幕上,绘制使用UI基础组件,这在UI的章节有更多的介绍。

    全局和增量

    和布局一样,绘制也可以是全局的-绘制完整的树-或增量的。在增量的绘制过程中,一些渲染对象以不影响整棵树的方式改变,改变的渲染对象使其在屏幕上的矩形区域失效,这将导致操作系统将其看作dirty区域,并产生一个paint事件,操作系统很巧妙的处理这个过程,并将多个区域合并为一个。Chrome中,这个过程更复杂些,因为渲染对象在不同的进程中,而不是在主进程中。Chrome在一定程度上模拟操作系统的行为,表现为监听事件并派发消息给渲染根,在树中查找到相关的渲染对象,重绘这个对象(往往还包括它的children)。

    绘制顺序

    css2定义了绘制过程的顺序-http://www.w3.org/TR/CSS21/zindex.html。这个就是元素压入堆栈的顺序,这个顺序影响着绘制,堆栈从后向前进行绘制。

    一个块渲染对象的堆栈顺序是:

    1. 背景色

    2. 背景图

    3. border

    4. children

    5. outline

    Firefox显示列表

    Firefox读取渲染树并为绘制的矩形创建一个显示列表,该列表以正确的绘制顺序包含这个矩形相关的渲染对象。

    用这样的方法,可以使重绘时只需查找一次树,而不需要多次查找——绘制所有的背景、所有的图片、所有的border等等。

    Firefox优化了这个过程,它不添加会被隐藏的元素,比如元素完全在其他不透明元素下面。

    Webkit矩形存储

    重绘前,webkit将旧的矩形保存为位图,然后只绘制新旧矩形的差集。

    动态变化

    浏览器总是试着以最小的动作响应一个变化,所以一个元素颜色的变化将只导致该元素的重绘,元素位置的变化将大致元素的布局和重绘,添加一个Dom节点,也会大致这个元素的布局和重绘。一些主要的变化,比如增加html元素的字号,将会导致缓存失效,从而引起整数的布局和重绘。

    渲染引擎的线程

    渲染引擎是单线程的,除了网络操作以外,几乎所有的事情都在单一的线程中处理,在Firefox和Safari中,这是浏览器的主线程,Chrome中这是tab的主线程。

    网络操作由几个并行线程执行,并行连接的个数是受限的(通常是2-6个)。

    事件循环

    浏览器主线程是一个事件循环,它被设计为无限循环以保持执行过程的可用,等待事件(例如layout和paint事件)并执行它们。下面是Firefox的主要事件循环代码。

    while (!mExiting)

    NS_ProcessNextEvent(thread);

    CSS2 可视模型 CSS2 visual module

    画布 The Canvas

    根据CSS2规范,术语canvas用来描述格式化的结构所渲染的空间——浏览器绘制内容的地方。画布对每个维度空间都是无限大的,但浏览器基于viewport的大小选择了一个初始宽度。

    根据http://www.w3.org/TR/CSS2/zindex.html的定义,画布如果是包含在其他画布内则是透明的,否则浏览器会指定一个颜色。

    CSS盒模型

    CSS盒模型描述了矩形盒,这些矩形盒是为文档树中的元素生成的,并根据可视的格式化模型进行布局。每个box包括内容区域(如图片、文本等)及可选的四周padding、border和margin区域。

    每个节点生成0-n个这样的box。

    所有的元素都有一个display属性,用来决定它们生成box的类型,例如:

    block-生成块状box

    inline-生成一个或多个行内box

    none-不生成box

    默认的是inline,但浏览器样式表设置了其他默认值,例如,div元素默认为block。可以访问http://www.w3.org/TR/CSS2/sample.html查看更多的默认样式表示例。

    定位策略 Position scheme

    这里有三种策略:

    1. normal-对象根据它在文档的中位置定位,这意味着它在渲染树和在Dom树中位置一致,并根据它的盒模型和大小进行布局

    2. float-对象先像普通流一样布局,然后尽可能的向左或是向右移动

    3. absolute-对象在渲染树中的位置和Dom树中位置无关

    static和relative是normal,absolute和fixed属于absolute。

    在static定位中,不定义位置而使用默认的位置。其他策略中,作者指定位置——top、bottom、left、right。

    Box布局的方式由这几项决定:box的类型、box的大小、定位策略及扩展信息(比如图片大小和屏幕尺寸)。

    Box类型

    Block box:构成一个块,即在浏览器窗口上有自己的矩形

    Inline box:并没有自己的块状区域,但包含在一个块状区域内

    block一个挨着一个垂直格式化,inline则在水平方向上格式化。

    Inline盒模型放置在行内或是line box中,每行至少和最高的box一样高,当box以baseline对齐时——即一个元素的底部和另一个box上除底部以外的某点对齐,行高可以比最高的box高。当容器宽度不够时,行内元素将被放到多行中,这在一个p元素中经常发生。

    定位 Position

    Relative

    相对定位——先按照一般的定位,然后按所要求的差值移动。

    Floats

    一个浮动的box移动到一行的最左边或是最右边,其余的box围绕在它周围。下面这段html:

    <p>

    <img style=”float:right” src=”images/image.gif” width=”100″ height=”100″>Lorem ipsum dolor sit amet, consectetuer…

    </p>

    将显示为:

    Absolute和Fixed

    这种情况下的布局完全不顾普通的文档流,元素不属于文档流的一部分,大小取决于容器。Fixed时,容器为viewport(可视区域)。

    图17:fixed

    注意-fixed即使在文档流滚动时也不会移动。

    Layered representation

    这个由CSS属性中的z-index指定,表示盒模型的第三个大小,即在z轴上的位置。Box分发到堆栈中(称为堆栈上下文),每个堆栈中靠后的元素将被较早绘制,栈顶靠前的元素离用户最近,当发生交叠时,将隐藏靠后的元素。堆栈根据z-index属性排序,拥有z-index属性的box形成了一个局部堆栈,viewport有外部堆栈,例如:

    <STYLE. type=”text/css”>

    div {

    position: absolute;

    left: 2in;

    top: 2in;

    }

    </STYLE>

    <P>

    <DIV

    style=”z-index: 3;background-color:red; width: 1in; height: 1in; “>

    </DIV>

    <DIV

    style=”z-index: 1;background-color:green;width: 2in; height: 2in;”>

    </DIV>

    </p>

    结果是:

    虽然绿色div排在红色div后面,可能在正常流中也已经被绘制在后面,但z-index有更高优先级,所以在根box的堆栈中更靠前。

    国外也有网友根据浏览器的工作原理绘制了几张工作流程图,方便大家通过简易的图片来了解这个辛苦的过程:

    浏览器工作原理

    原文:http://taligarsiel.com/Projects/howbrowserswork1.htm
    编译:zzzaquarius

  • 浏览器是如何工作的?(工作原理)(二)

    2011-12-06 17:26:29


    浏览器容错 Browsers error tolerance

    你从来不会在一个html页面上看到“无效语法”这样的错误,浏览器修复了无效内容并继续工作。

    以下面这段html为例:

    <html>

    <mytag>

    </mytag>

    <div>

    <p>

    </div>

    Really lousy HTML

    </p>

    </html>

    这段html违反了很多规则(mytag不是合法的标签,p及div错误的嵌套等等),但是浏览器仍然可以没有任何怨言的继续显示,它在解析的过程中修复了html作者的错误。

    浏览器都具有错误处理的能力,但是,另人惊讶的是,这并不是html最新规范的内容,就像书签及前进后退按钮一样,它只是浏览器长期发展的结果。一些比较知名的非法html结构,在许多站点中出现过,浏览器都试着以一种和其他浏览器一致的方式去修复。

    Html5规范定义了这方面的需求,webkit在html解析类开始部分的注释中做了很好的总结。

    解析器将符号化的输入解析为文档并创建文档,但不幸的是,我们必须处理很多没有很好格式化的html文档,至少要小心下面几种错误情况。

    1. 在未闭合的标签中添加明确禁止的元素。这种情况下,应该先将前一标签闭合

    2. 不能直接添加元素。有些人在写文档的时候会忘了中间一些标签(或者中间标签是可选的),比如HTML HEAD BODY TR TD LI等

    3. 想在一个行内元素中添加块状元素。关闭所有的行内元素,直到下一个更高的块状元素

    4. 如果这些都不行,就闭合当前标签直到可以添加该元素。

    下面来看一些webkit容错的例子:

    </br>替代<br>

    一些网站使用</br>替代<br>,为了兼容IE和Firefox,webkit将其看作<br>。

    代码:

    if (t->isCloseTag(brTag) && m_document->inCompatMode()) {

    reportError(MalformedBRError);

    t->beginTag = true;

    }

    Note-这里的错误处理在内部进行,用户看不到。

    迷路的表格

    这指一个表格嵌套在另一个表格中,但不在它的某个单元格内。

    比如下面这个例子:

    <table>

    <table>

    <tr><td>inner table</td></tr>

    </table>

    <tr><td>outer table</td></tr>

    </table>

    webkit将会将嵌套的表格变为两个兄弟表格:

    <table>

    <tr><td>outer table</td></tr>

    </table>

    <table>

    <tr><td>inner table</td></tr>

    </table>

    代码:

    if (m_inStrayTableContent && localName == tableTag)

    popBlock(tableTag);

    webkit使用堆栈存放当前的元素内容,它将从外部表格的堆栈中弹出内部的表格,则它们变为了兄弟表格。

    嵌套的表单元素

    用户将一个表单嵌套到另一个表单中,则第二个表单将被忽略。

    代码:

    if (!m_currentFormElement) {

    m_currentFormElement = new HTMLFormElement(formTag, m_document);

    }

    太深的标签继承

    www.liceo.edu.mx是一个由嵌套层次的站点的例子,最多只允许20个相同类型的标签嵌套,多出来的将被忽略。

    代码:

    bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName)

    {

    unsigned i = 0;

    for (HTMLStackElem* curr = m_blockStack;

    i < cMaxRedundantTagDepth && curr && curr->tagName == tagName;

    curr = curr->next, i++) { }

    return i != cMaxRedundantTagDepth;

    }

    放错了地方的html、body闭合标签

    又一次不言自明。

    支持不完整的html。我们从来不闭合body,因为一些愚蠢的网页总是在还未真正结束时就闭合它。我们依赖调用end方法去执行关闭的处理。

    代码:

    if (t->tagName == htmlTag || t->tagName == bodyTag )

    return;

    所以,web开发者要小心了,除非你想成为webkit容错代码的范例,否则还是写格式良好的html吧。

    CSS解析 CSS parsing

    还记得简介中提到的解析的概念吗,不同于html,css属于上下文无关文法,可以用前面所描述的解析器来解析。Css规范定义了css的词法及语法文法。

    看一些例子:

    每个符号都由正则表达式定义了词法文法(词汇表):

    comment ///*[^*]*/*+([^/*][^*]*/*+)*//

    num [0-9]+|[0-9]*”.”[0-9]+

    nonascii [/200-/377]

    nmstart [_a-z]|{nonascii}|{escape}

    nmchar [_a-z0-9-]|{nonascii}|{escape}

    name {nmchar}+

    ident {nmstart}{nmchar}*

    “ident”是识别器的缩写,相当于一个class名,“name”是一个元素id(用“#”引用)。

    语法用BNF进行描述:

    ruleset

    : selector [ ',' S* selector ]*

    ‘{’ S* declaration [ ';' S* declaration ]* ‘}’ S*

    ;

    selector

    : simple_selector [ combinator selector | S+ [ combinator selector ] ]

    ;

    simple_selector

    : element_name [ HASH | class | attrib | pseudo ]*

    | [ HASH | class | attrib | pseudo ]+

    ;

    class

    : ‘.’ IDENT

    ;

    element_name

    : IDENT | ‘*’

    ;

    attrib

    : ‘[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*

    [ IDENT | STRING ] S* ] ‘]’

    ;

    pseudo

    : ‘:’ [ IDENT | FUNCTION S* [IDENT S*] ‘)’ ]

    ;

    说明:一个规则集合有这样的结构

    div.error , a.error {

    color:red;

    font-weight:bold;

    }

    div.error和a.error时选择器,大括号中的内容包含了这条规则集合中的规则,这个结构在下面的定义中正式的定义了:

    ruleset

    : selector [ ',' S* selector ]*

    ‘{’ S* declaration [ ';' S* declaration ]* ‘}’ S*

    ;

    这说明,一个规则集合具有一个或是可选个数的多个选择器,这些选择器以逗号和空格(S表示空格)进行分隔。每个规则集合包含大括号及大括号中的一条或多条以分号隔开的声明。声明和选择器在后面进行定义。

    Webkit CSS 解析器 Webkit CSS parser

    Webkit使用Flex和Bison解析生成器从CSS语法文件中自动生成解析器。回忆一下解析器的介绍,Bison创建一个自底向上的解析器,Firefox使用自顶向下解析器。它们都是将每个css文件解析为样式表对象,每个对象包含css规则,css规则对象包含选择器和声明对象,以及其他一些符合css语法的对象。

    图12:解析css

    脚本解析 Parsing scripts

    本章将介绍Javascript。

    处理脚本及样式表的顺序 The order of processing scripts and style. sheets

    脚本

    web的模式是同步的,开发者希望解析到一个script标签时立即解析执行脚本,并阻塞文档的解析直到脚本执行完。如果脚本是外引的,则网络必须先请求到这个资源——这个过程也是同步的,会阻塞文档的解析直到资源被请求到。这个模式保持了很多年,并且在html4及html5中都特别指定了。开发者可以将脚本标识为defer,以使其不阻塞文档解析,并在文档解析结束后执行。Html5增加了标记脚本为异步的选项,以使脚本的解析执行使用另一个线程。

    预解析 Speculative parsing

    Webkit和Firefox都做了这个优化,当执行脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。需要注意的是,预解析并不改变Dom树,它将这个工作留给主解析过程,自己只解析外部资源的引用,比如外部脚本、样式表及图片。

    样式表 Style. sheets

    样式表采用另一种不同的模式。理论上,既然样式表不改变Dom树,也就没有必要停下文档的解析等待它们,然而,存在一个问题,脚本可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很多问题,这看起来是个边缘情况,但确实很常见。Firefox在存在样式表还在加载和解析时阻塞所有的脚本,而chrome只在当脚本试图访问某些可能被未加载的样式表所影响的特定的样式属性时才阻塞这些脚本。

    渲染树的构造 Render tree construction

    当Dom树构建完成时,浏览器开始构建另一棵树——渲染树。渲染树由元素显示序列中的可见元素组成,它是文档的可视化表示,构建这棵树是为了以正确的顺序绘制文档内容。

    Firefox将渲染树中的元素称为frames,webkit则用renderer或渲染对象来描述这些元素。

    一个渲染对象直到怎么布局及绘制自己及它的children。

    RenderObject是Webkit的渲染对象基类,它的定义如下:

    class RenderObject{

    virtual void layout();

    virtual void paint(PaintInfo);

    virtual void rect repaintRect();

    Node* node; //the DOM node

    RenderStyle* style; // the computed style

    RenderLayer* containgLayer; //the containing z-index layer

    }

    每个渲染对象用一个和该节点的css盒模型相对应的矩形区域来表示,正如css2所描述的那样,它包含诸如宽、高和位置之类的几何信息。盒模型的类型受该节点相关的display样式属性的影响(参考样式计算章节)。下面的webkit代码说明了如何根据display属性决定某个节点创建何种类型的渲染对象。

    RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)

    {

    Document* doc = node->document();

    RenderArena* arena = doc->renderArena();

    RenderObject* o = 0;

    switch (style->display()) {

    case NONE:

    break;

    case INLINE:

    o = new (arena) RenderInline(node);

    break;

    case BLOCK:

    o = new (arena) RenderBlock(node);

    break;

    case INLINE_BLOCK:

    o = new (arena) RenderBlock(node);

    break;

    case LIST_ITEM:

    o = new (arena) RenderListItem(node);

    break;

    }

    return o;

    }

    元素的类型也需要考虑,例如,表单控件和表格带有特殊的框架。

    在webkit中,如果一个元素想创建一个特殊的渲染对象,它需要复写“createRenderer”方法,使渲染对象指向不包含几何信息的样式对象。

    渲染树和Dom树的关系 The render tree relation to the DOM tree

    渲染对象和Dom元素相对应,但这种对应关系不是一对一的,不可见的Dom元素不会被插入渲染树,例如head元素。另外,display属性为none的元素也不会在渲染树中出现(visibility属性为hidden的元素将出现在渲染树中)。

    还有一些Dom元素对应几个可见对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。例如,select元素有三个渲染对象——一个显示区域、一个下拉列表及一个按钮。同样,当文本因为宽度不够而折行时,新行将作为额外的渲染元素被添加。另一个多个渲染对象的例子是不规范的html,根据css规范,一个行内元素只能仅包含行内元素或仅包含块状元素,在存在混合内容时,将会创建匿名的块状渲染对象包裹住行内元素。

    一些渲染对象和所对应的Dom节点不在树上相同的位置,例如,浮动和绝对定位的元素在文本流之外,在两棵树上的位置不同,渲染树上标识出真实的结构,并用一个占位结构标识出它们原来的位置。

    图12:渲染树及对应的Dom树

    创建树的流程 The flow of constructing the tree

    Firefox中,表述为一个监听Dom更新的监听器,将frame的创建委派给Frame. Constructor,这个构建器计算样式(参看样式计算)并创建一个frame。

    Webkit中,计算样式并生成渲染对象的过程称为attachment,每个Dom节点有一个attach方法,attachment的过程是同步的,调用新节点的attach方法将节点插入到Dom树中。

    处理html和body标签将构建渲染树的根,这个根渲染对象对应被css规范称为containing block的元素——包含了其他所有块元素的顶级块元素。它的大小就是viewport——浏览器窗口的显示区域,Firefox称它为viewPortFrame,webkit称为RenderView,这个就是文档所指向的渲染对象,树中其他的部分都将作为一个插入的Dom节点被创建。

    样式计算 Style. Computation

    创建渲染树需要计算出每个渲染对象的可视属性,这可以通过计算每个元素的样式属性得到。

    样式包括各种来源的样式表,行内样式元素及html中的可视化属性(例如bgcolor),可视化属性转化为css样式属性。

    样式表来源于浏览器默认样式表,及页面作者和用户提供的样式表——有些样式是浏览器用户提供的(浏览器允许用户定义喜欢的样式,例如,在Firefox中,可以通过在Firefox Profile目录下放置样式表实现)。

    计算样式的一些困难:

    1. 样式数据是非常大的结构,保存大量的样式属性会带来内存问题

    2. 如果不进行优化,找到每个元素匹配的规则会导致性能问题,为每个元素查找匹配的规则都需要遍历整个规则表,这个过程有很大的工作量。选择符可能有复杂的结构,匹配过程如果沿着一条开始看似正确,后来却被证明是无用的路径,则必须去尝试另一条路径。

    例如,下面这个复杂选择符

    div div div div{…}

    这意味着规则应用到三个div的后代div元素,选择树上一条特定的路径去检查,这可能需要遍历节点树,最后却发现它只是两个div的后代,并不使用该规则,然后则需要沿着另一条路径去尝试

    3. 应用规则涉及非常复杂的级联,它们定义了规则的层次

    我们来看一下浏览器如何处理这些问题:

    共享样式数据

    webkit节点引用样式对象(渲染样式),某些情况下,这些对象可以被节点间共享,这些节点需要是兄弟或是表兄弟节点,并且:

    1. 这些元素必须处于相同的鼠标状态(比如不能一个处于hover,而另一个不是)
    2. 不能有元素具有id
    3. 标签名必须匹配
    4. class属性必须匹配
    5. 对应的属性必须相同
    6. 链接状态必须匹配
    7. 焦点状态必须匹配
    8. 不能有元素被属性选择器影响
    9. 元素不能有行内样式属性
    10. 不能有生效的兄弟选择器,webcore在任何兄弟选择器相遇时只是简单的抛出一个全局转换,并且在它们显示时使整个文档的样式共享失效,这些包括+选择器和类似:first-child和:last-child这样的选择器。

    Firefox规则树 Firefox rule tree

    Firefox用两个树用来简化样式计算-规则树和样式上下文树,webkit也有样式对象,但它们并没有存储在类似样式上下文树这样的树中,只是由Dom节点指向其相关的样式。

    图14:Firefox样式上下文树

    样式上下文包含最终值,这些值是通过以正确顺序应用所有匹配的规则,并将它们由逻辑值转换为具体的值,例如,如果逻辑值为屏幕的百分比,则通过计算将其转化为绝对单位。样式树的使用确实很巧妙,它使得在节点中共享的这些值不需要被多次计算,同时也节省了存储空间。

    所有匹配的规则都存储在规则树中,一条路径中的底层节点拥有最高的优先级,这棵树包含了所找到的 所有规则匹配的路径(译注:可以取巧理解为每条路径对应一个节点,路径上包含了该节点所匹配的所有规则)。规则树并不是一开始就为所有节点进行计算,而是 在某个节点需要计算样式时,才进行相应的计算并将计算后的路径添加到树中。

    我们将树上的路径看成辞典中的单词,假如已经计算出了如下的规则树:

    假如需要为内容树中的另一个节点匹配规则,现在知道匹配的规则(以正确的顺序)为B-E-I,因为我们已经计算出了路径A-B-E-I-L,所以树上已经存在了这条路径,剩下的工作就很少了。

    现在来看一下树如何保存。

    结构化

    样式上下文按结构划分,这些结构包括类似border或color这样的特定分类的样式信息。一个结构中的所有特性不是继承的就是非继承的,对继承的特性,除非元素自身有定义,否则就从它的parent继承。非继承的特性(称为reset特性)如果没有定义,则使用默认的值。

    样式上下文树缓存完整的结构(包括计算后的值),这样,如果底层节点没有为一个结构提供定义,则使用上层节点缓存的结构。

    使用规则树计算样式上下文

    当为一个特定的元素计算样式时,首先计算出规则树中的一条路径,或是使用已经存在的一条,然后使 用路径中的规则去填充新的样式上下文,从样式的底层节点开始,它具有最高优先级(通常是最特定的选择器),遍历规则树,直到填满结构。如果在那个规则节点 没有定义所需的结构规则,则沿着路径向上,直到找到该结构规则。

    如果最终没有找到该结构的任何规则定义,那么如果这个结构是继承型的,则找到其在内容树中的parent的结构,这种情况下,我们也成功的共享了结构;如果这个结构是reset型的,则使用默认的值。

    如果特定的节点添加了值,那么需要做一些额外的计算以将其转换为实际值,然后在树上的节点缓存该值,使它的children可以使用。

    当一个元素和它的一个兄弟元素指向同一个树节点时,完整的样式上下文可以被它们共享。

    来看一个例子:假设有下面这段html

    <html>

    <body>

    <div class=”err” id=”div1″>

    <p>this is a

    <span class=”big”> big error </span>

    this is also a

    <span class=”big”> very big error</span>

    error

    </p>

    </div>

    <div class=”err” id=”div2″>another error</div>

    </body>

    </html>

    以及下面这些规则

    1. div {margin:5px;color:black}

    2. .err {color:red}

    3. .big {margin-top:3px}

    4. div span {margin-bottom:4px}

    5. #div1 {color:blue}

    6. #div2 {color:green}

    简化下问题,我们只填充两个结构——color和margin,color结构只包含一个成员-颜色,margin结构包含四边。

    生成的规则树如下(节点名:指向的规则)

    上下文树如下(节点名:指向的规则节点)

    假设我们解析html,遇到第二个div标签,我们需要为这个节点创建样式上下文,并填充它的样式结构。

    我们进行规则匹配,找到这个div匹配的规则为1、2、6,我们发现规则树上已经存在了一条我们可以使用的路径1、2,我们只需为规则6新增一个节点添加到下面(就是规则树中的F)。

    然后创建一个样式上下文并将其放到上下文树中,新的样式上下文将指向规则树中的节点F。

    现在我们需要填充这个样式上下文,先从填充margin结构开始,既然最后一个规则节点没有添加margin结构,沿着路径向上,直到找到缓存的前面插入节点计算出的结构,我们发现B是最近的指定margin值的节点。因为已经有了color结构的定义,所以不能使用缓存的结构,既然color只有一个属性,也就不需要沿着路径向上填充其他属性。计算出最终值(将字符串转换为RGB等),并缓存计算后的结构。

    第二个span元素更简单,进行规则匹配后发现它指向规则G,和前一个span一样,既然有兄弟节点指向同一个节点,就可以共享完整的样式上下文,只需指向前一个span的上下文。

    因为结构中包含继承自parent的规则,上下文树做了缓存(color特性是继承来的,但Firefox将其视为reset并在规则树中缓存)。

    例如,如果我们为一个paragraph的文字添加规则:

    p {font-family:Verdana;font size:10px;font-weight:bold}

    那么这个p在内容树中的子节点div,会共享和它parent一样的font结构,这种情况发生在没有为这个div指定font规则时。

    Webkit中,并没有规则树,匹配的声明会被遍历四次,先是应用非important的高优先级属性(之所以先应用这些属性,是因为其他的依赖于它们-比如display),其次是高优先级important的,接着是一般优先级非important的,最后是一般优先级important的规则。这样,出现多次的属性将被按照正确的级联顺序进行处理,最后一个生效。

    总结一下,共享样式对象(结构中完整或部分内容)解决了问题1和3,Firefox的规则树帮助以正确的顺序应用规则。

    对规则进行处理以简化匹配过程

    ----->见下文

  • 浏览器是如何工作的?(工作原理)(一)

    2011-12-06 17:24:50

    简介

    浏览器可以被认为是使用最广泛的软件,本文将介绍浏览器的工 作原理,我们将看到,从你在地址栏输入google.com到你看到google主页过程中都发生了什么。

    将讨论的浏览器

    今天,有五种主流浏览器——IE、Firefox、Safari、Chrome及Opera。

    本文将基于一些开源浏览器的例子——Firefox、 Chrome及Safari,Safari是部分开源的。

    根据W3C(World Wide Web Consortium 万维网联盟)的浏览器统计数据,当前(2011年9月),Firefox、Safari及Chrome的市场占有率综合已快接近50%。(原文为2009年10月,数据没有太大变化)因此,可以说开源浏览器将近占据了浏览器市场的半壁江山。

    浏览器的主要功能

    浏览器的主要功能是将用户选择得web资源呈现出来,它需要从服务器请求资源,并将其显示在浏览器窗口中,资源的格式通常是HTML,也包括PDF、image及其他格式。用户用URI(Uniform. Resource Identifier 统一资源标识符)来指定所请求资源的位置,在网络一章有更多讨论。

    HTML和CSS规范中规定了浏览器解释html文档的方式,由 W3C组织对这些规范进行维护,W3C是负责制定web标准的组织。

    HTML规范的最新版本是HTML4(http://www.w3.org/TR/html401/),HTML5还在制定中(译注:两年前),最新的CSS规范版本是2(http://www.w3.org/TR/CSS2),CSS3也还正在制定中(译注:同样两年前)。

    这些年来,浏览器厂商纷纷开发自己的扩展,对规范的遵循并不完善,这为web开发者带来了严重的兼容性问题。

    但是,浏览器的用户界面则差不多,常见的用户界面元素包括:

    • 用来输入URI的地址栏
    • 前进、后退按钮
    • 书签选项
    • 用于刷新及暂停当前加载文档的刷新、暂停按钮
    • 用于到达主页的主页按钮

    奇怪的是,并没有哪个正式公布的规范对用户界面做出规定,这些是多年来各浏览器厂商之间相互模仿和不断改进得结果。

    HTML5并没有规定浏览器必须具有的UI元素,但列出了一些常用元素,包括地址栏、状态栏及工具栏。还有一些浏览器有自己专有得功能,比如Firefox得下载管理。更多相关内容将在后面讨论用户界面时介绍。

    浏览器的主要构成High Level Structure

    浏览器的主要组件包括:

    1. 用户界面- 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分
    2. 浏览器引擎- 用来查询及操作渲染引擎的接口
    3. 渲染引擎- 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来
    4. 网络- 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作
    5. UI 后端- 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口
    6. JS解释器- 用来解释执行JS代码
    7. 数据存储- 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术

    图1:浏览器主要组件

    需要注意的是,不同于大部分浏览器,Chrome为每个Tab分配了各自的渲染引擎实例,每个Tab就是一个独立的进程。

    对于构成浏览器的这些组件,后面会逐一详细讨论。

    组件间的通信 Communication between the components

    Firefox和Chrome都开发了一个特殊的通信结构,后面将有专门的一章进行讨论。

    渲染引擎 The rendering engine

    渲染引擎的职责就是渲染,即在浏览器窗口中显示所请求的内容。

    默认情况下,渲染引擎可以显示html、xml文档及图片,它也可以借助插件(一种浏览器扩展)显示其他类型数据,例如使用PDF阅读器插件,可以显示PDF格式,将由专门一章讲解插件及扩展,这里只讨论渲染引擎最主要的用途——显示应用了CSS之后的html及图片。

    渲染引擎 Rendering engines

    本文所讨论得浏览器——Firefox、Chrome和Safari是基于两种渲染引擎构建的,Firefox使用Geoko——Mozilla自主研发的渲染引擎,Safari和Chrome都使用webkit。

    Webkit是一款开源渲染引擎,它本来是为linux平台研发的,后来由Apple移植到Mac及Windows上,相关内容请参考http://webkit.org。

    主流程 The main flow

    渲染引擎首先通过网络获得所请求文档的内容,通常以8K分块的方式完成。

    下面是渲染引擎在取得内容之后的基本流程:

    解析html以构建dom树->构建render树->布局render树->绘制render树

    图2:渲染引擎基本流程

    渲染引擎开始解析html,并将标签转化为内容树中的dom节点。接着,它解析外部CSS文件及style标签中的样式信息。这些样式信息以及html中的可见性指令将被用来构建另一棵树——render树。

    Render树由一些包含有颜色和大小等属性的矩形组成,它们将被按照正确的顺序显示到屏幕上。

    Render树构建好了之后,将会执行布局过程,它将确定每个节点在屏幕上的确切坐标。再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。

    值得注意的是,这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

    图3:webkit主流程

    图4:Mozilla的Geoko 渲染引擎主流程

    从图3和4中可以看出,尽管webkit和Gecko使用的术语稍有不同,他们的主要流程基本相同。Gecko称可见的格式化元素组成的树为frame树,每个元素都是一个frame,webkit则使用render树这个名词来命名由渲染对象组成的树。Webkit中元素的定位称为布局,而Gecko中称为回流。Webkit称利用dom节点及样式信息去构建render树的过程为attachment,Gecko在html和dom树之间附加了一层,这层称为内容接收器,相当制造dom元素的工厂。下面将讨论流程中的各个阶段。

    解析 Parsing-general

    既然解析是渲染引擎中一个非常重要的过程,我们将稍微深入的研究它。首先简要介绍一下解析。

    解析一个文档即将其转换为具有一定意义的结构——编码可以理解和使用的东西。解析的结果通常是表达文档结构的节点树,称为解析树或语法树。

    例如,解析“2+3-1”这个表达式,可能返回这样一棵树。

    图5:数学表达式树节点

    文法 Grammars

    解析基于文档依据的语法规则——文档的语言或格式。每种可被解析的格式必须具有由词汇及语法规则组成的特定的文法,称为上下文无关文法。人类语言不具有这一特性,因此不能被一般的解析技术所解析。

    解析器-词法分析器 Parser-Lexer combination

    解析可以分为两个子过程——语法分析及词法分析

    词法分析就是将输入分解为符号,符号是语言的词汇表——基本有效单元的集合。对于人类语言来说,它相当于我们字典中出现的所有单词。

    语法分析指对语言应用语法规则。

    解析器一般将工作分配给两个组件——词法分析器(有时也叫分词器)负责将输入分解为合法的符号,解析器则根据语言的语法规则分析文档结构,从而构建解析树,词法分析器知道怎么跳过空白和换行之类的无关字符。

    图6:从源文档到解析树

    解析过程是迭代的,解析器从词法分析器处取道一个新的符号,并试着用这个符号匹配一条语法规则, 如果匹配了一条规则,这个符号对应的节点将被添加到解析树上,然后解析器请求另一个符号。如果没有匹配到规则,解析器将在内部保存该符号,并从词法分析器 取下一个符号,直到所有内部保存的符号能够匹配一项语法规则。如果最终没有找到匹配的规则,解析器将抛出一个异常,这意味着文档无效或是包含语法错误。

    转换 Translation

    很多时候,解析树并不是最终结果。解析一般在转换中使用——将输入文档转换为另一种格式。编译就是个例子,编译器在将一段源码编译为机器码的时候,先将源码解析为解析树,然后将该树转换为一个机器码文档。

    图7:编译流程

    解析实例 Parsing example

    图5中,我们从一个数学表达式构建了一个解析树,这里定义一个简单的数学语言来看下解析过程。

    词汇表:我们的语言包括整数、加号及减号。

    语法:

    1. 该语言的语法基本单元包括表达式、term及操作符

    2. 该语言可以包括多个表达式

    3. 一个表达式定义为两个term通过一个操作符连接

    4. 操作符可以是加号或减号

    5. term可以是一个整数或一个表达式

    现在来分析一下“2+3-1”这个输入

    第一个匹配规则的子字符串是“2”,根据规则5,它是一个term,第二个匹配的是“2+3”,它符合第2条规则——一个操作符连接两个term,下一次匹配发生在输入的结束处。“2+3-1”是一个表达式,因为我们已经知道“2+3”是一个term,所以我们有了一个term紧跟着一个操作符及另一个term。“2++”将不会匹配任何规则,因此是一个无效输入。

    词汇表及语法的定义

    词汇表通常利用正则表达式来定义。

    例如上面的语言可以定义为:

    INTEGER:0|[1-9][0-9]*

    PLUS:+

    MINUS:-

    正如看到的,这里用正则表达式定义整数。

    语法通常用BNF格式定义,我们的语言可以定义为:

    expression := term operation term

    operation := PLUS | MINUS

    term := INTEGER | expression

    如果一个语言的文法是上下文无关的,则它可以用正则解析器来解析。对上下文无关文法的一个直观的定义是,该文法可以用BNF来完整的表达。可查看http://en.wikipedia.org/wiki/Context-free_grammar。

    解析器类型 Types of parsers

    有两种基本的解析器——自顶向下解析及自底向上解析。比较直观的解释是,自顶向下解析,查看语法的最高层结构并试着匹配其中一个;自底向上解析则从输入开始,逐步将其转换为语法规则,从底层规则开始直到匹配高层规则。

    来看一下这两种解析器如何解析上面的例子:

    自顶向下解析器从最高层规则开始——它先识别出“2+3“,将其视为一个表达式,然后识别出”2+3-1“为一个表达式(识别表达式的过程中匹配了其他规则,但出发点是最高层规则)。

    自底向上解析会扫描输入直到匹配了一条规则,然后用该规则取代匹配的输入,直到解析完所有输入。部分匹配的表达式被放置在解析堆栈中。

    Stack

    Input

    2 + 3 – 1
    term + 3 – 1
    term operation 3 – 1
    expression - 1
    expression operation 1
    expression

    自底向上解析器称为shift reduce 解析器,因为输入向右移动(想象一个指针首先指向输入开始处,并向右移动),并逐渐简化为语法规则。

    自动化解析 Generating parse

    解析器生成器这个工具可以自动生成解析器,只需要指定语言的文法——词汇表及语法规则,它就可以生成一个解析器。创建一个解析器需要对解析有深入的理解,而且手动的创建一个由较好性能的解析器并不容易,所以解析生成器很有用。Webkit使用两个知名的解析生成器——用于创建语法分析器的Flex及创建解析器的Bison(你可能接触过Lex和Yacc)。Flex的输入是一个包含了符号定义的正则表达式,Bison的输入是用BNF格式表示的语法规则。rs automatically

    HTML解析器 HTML Parser

    HTML解析器的工作是将html标识解析为解析树。

    HTML文法定义 The HTML grammar definition

    W3C组织制定规范定义了HTML的词汇表和语法。

    非上下文无关文法 Not a context free grammar

    正如在解析简介中提到的,上下文无关文法的语法可以用类似BNF的格式来定义。

    不幸的是,所有的传统解析方式都不适用于html(当然我提出它们并不只是因为好玩,它们将用来解析css和js),html不能简单的用解析所需的上下文无关文法来定义。

    Html 有一个正式的格式定义——DTD(Document Type Definition 文档类型定义)——但它并不是上下文无关文法,html更接近于xml,现在有很多可用的xml解析器,html有个xml的变体——xhtml,它们间的不同在于,html更宽容,它允许忽略一些特定标签,有时可以省略开始或结束标签。总的来说,它是一种soft语法,不像xml呆板、固执。

    显然,这个看起来很小的差异却带来了很大的不同。一方面,这是html流行的原因——它的宽容使web开发人员的工作更加轻松,但另一方面,这也使很难去写一个格式化的文法。所以,html的解析并不简单,它既不能用传统的解析器解析,也不能用xml解析器解析。

    HTML DTD

    Html适用DTD格式进行定义,这一格式是用于定义SGML家族的语言,包括了对所有允许元素及它们的属性和层次关系的定义。正如前面提到的,html DTD并没有生成一种上下文无关文法。

    DTD有一些变种,标准模式只遵守规范,而其他模式则包含了对浏览器过去所使用标签的支持,这么做是为了兼容以前内容。最新的标准DTD在http://www.w3.org/TR/html4/strict.dtd

    DOM

    输出的树,也就是解析树,是由DOM元素及属性节点组成的。DOM是文档对象模型的缩写,它是html文档的对象表示,作为html元素的外部接口供js等调用。

    树的根是“document”对象。

    DOM和标签基本是一一对应的关系,例如,如下的标签:

    <html>

    <body>

    <p>

    Hello DOM

    </p>

    <div><img src=”example.png” /></div>

    </body>

    </html>

    将会被转换为下面的DOM树:

    图8:示例标签对应的DOM树

    和html一样,DOM的规范也是由W3C组织制定的。访问http://www.w3.org/DOM/DOMTR,这是使用文档的一般规范。一个模型描述一种特定的html元素,可以在http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.htm 查看html定义。

    这里所谓的树包含了DOM节点是说树是由实现了DOM接口的元素构建而成的,浏览器使用已被浏览器内部使用的其他属性的具体实现。

    解析算法 The parsing algorithm

    正如前面章节中讨论的,hmtl不能被一般的自顶向下或自底向上的解析器所解析。

    原因是:

    1. 这门语言本身的宽容特性

    2. 浏览器对一些常见的非法html有容错机制

    3. 解析过程是往复的,通常源码不会在解析过程中发生改变,但在html中,脚本标签包含的“document.write ”可能添加标签,这说明在解析过程中实际上修改了输入

    不能使用正则解析技术,浏览器为html定制了专属的解析器。

    Html5规范中描述了这个解析算法,算法包括两个阶段——符号化及构建树。

    符号化是词法分析的过程,将输入解析为符号,html的符号包括开始标签、结束标签、属性名及属性值。

    符号识别器识别出符号后,将其传递给树构建器,并读取下一个字符,以识别下一个符号,这样直到处理完所有输入。

    图9:HTML解析流程

    符号识别算法 The tokenization algorithm

    算法输出html符号,该算法用状态机表示。每次读取输入流中的一个或多个字符,并根据这些字符转移到下一个状态,当前的符号状态及构建树状态共同影响结果,这意味着,读取同样的字符,可能因为当前状态的不同,得到不同的结果以进入下一个正确的状态。

    这个算法很复杂,这里用一个简单的例子来解释这个原理。

    基本示例——符号化下面的html:

    <html>

    <body>

    Hello world

    </body>

    </html>

    初始状态为“Data State”,当遇到“<”字符,状态变为“Tag open state”,读取一个a-z的字符将产生一个开始标签符号,状态相应变为“Tag name state”,一直保持这个状态直到读取到“>”,每个字符都附加到这个符号名上,例子中创建的是一个html符号。

    当读取到“>”,当前的符号就完成了,此时,状态回到“Data state”,“<body>”重复这一处理过程。到这里,html和body标签都识别出来了。现在,回到“Data state”,读取“Hello world”中的字符“H”将创建并识别出一个字符符号,这里会为“Hello world”中的每个字符生成一个字符符号。

    这样直到遇到“</body>”中的“<”。现在,又回到了“Tag open state”,读取下一个字符“/”将创建一个闭合标签符号,并且状态转移到“Tag name state”,还是保持这一状态,直到遇到“>”。然后,产生一个新的标签符号并回到“Data state”。后面的“</html>”将和“</body>”一样处理。

    图10:符号化示例输入

    树的构建算法 Tree construction algorithm

    在树的构建阶段,将修改以Document为根的DOM树,将元素附加到树上。每个由符号识别器识别生成的节点将会被树构造器进行处理,规范中定义了每个符号相对应的Dom元素,对应的Dom元素将会被创建。这些元素除了会被添加到Dom树上,还将被添加到开放元素堆栈中。这个堆栈用来纠正嵌套的未匹配和未闭合标签,这个算法也是用状态机来描述,所有的状态采用插入模式。

    来看一下示例中树的创建过程:

    <html>

    <body>

    Hello world

    </body>

    </html>

    构建树这一阶段的输入是符号识别阶段生成的符号序列。

    首先是“initial mode”,接收到html符号后将转换为“before html”模式,在这个模式中对这个符号进行再处理。此时,创建了一个HTMLHtmlElement元素,并将其附加到根Document对象上。

    状态此时变为“before head”,接收到body符号时,即使这里没有head符号,也将自动创建一个HTMLHeadElement元素并附加到树上。

    现在,转到“in head”模式,然后是“after head”。到这里,body符号会被再次处理,将创建一个HTMLBodyElement并插入到树中,同时,转移到“in body”模式。

    然后,接收到字符串“Hello world”的字符符号,第一个字符将导致创建并插入一个text节点,其他字符将附加到该节点。

    接收到body结束符号时,转移到“after body”模式,接着接收到html结束符号,这个符号意味着转移到了“after after body”模式,当接收到文件结束符时,整个解析过程结束。

    图11:示例html树的构建过程

    解析结束时的处理 Action when the parsing is finished

    在这个阶段,浏览器将文档标记为可交互的,并开始解析处于延时模式中的脚本——这些脚本在文档解析后执行。

    文档状态将被设置为完成,同时触发一个load事件。

    Html5规范中有符号化及构建树的完整算法(http://www.w3.org/TR/html5/syntax.html#html-parser)。

    浏览器容错 Browsers error tolerance

    ---->见下文

  • 浏览器加载和渲染html的顺序(Yahoo对网页设计性能的建议) (转)

    2011-11-30 18:54:07

    浏览器加载和渲染html的顺序 


    前阵子,在组内给大家做了一次关于“浏览器加载和渲染HTML的顺序”的分享,这里再总结一下吧。
    1.浏览器加载和渲染html的顺序

    1、IE下载的顺序是从上到下,渲染的顺序也是从上到下,下载和渲染是同时进行的。
    2、在渲染到页面的某一部分时,其上面的所有部分都已经下载完成(并不是说所有相关联的元素都已经下载完)
    3、如果遇到语义解释性的标签嵌入文件(JS脚本,CSS样式),那么此时IE的下载过程会启用单独连接进行下载。
    4、并且在下载后进行解析,解析过程中,停止页面所有往下元素的下载。阻塞加载
    5、样式表在下载完成后,将和以前下载的所有样式表一起进行解析,解析完成后,将对此前所有元素(含以前已经渲染的)重新进行渲染。
    6、JS、CSS中如有重定义,后定义函数将覆盖前定义函数
    2. JS的加载
    2.1 不能并行下载和解析(阻塞下载)
    2.2 当引用了JS的时候,浏览器发送1个js request就会一直等待该request的返回。因为浏览器需要1个稳定的DOM树结构,而JS中很有可能有代码直接改变了DOM树结构,比如使用 document.write 或 appendChild,甚至是直接使用的location.href进行跳转,浏览器为了防止出现JS修改DOM树,需要重新构建DOM树的情况,所以 就会阻塞其他的下载和呈现.
    3.如何加快HTML页面加载速度
    1,页面减肥
    页面的肥瘦是影响加载速度最重要的因素
    删除不必要的空格、注释
    将inline的script和css移到外部文件
    可以使用HTML Tidy来给HTML减肥,还可以使用一些压缩工具来给JavaScript减肥
    2,减少文件数量
    减少页面上引用的文件数量可以减少HTTP连接数
    许多JavaScript、CSS文件可以合并最好合并,人家财帮子都把自己的JavaScript. functions和Prototype.js合并到一个base.js文件里去了
    3,减少域名查询
    DNS查询和解析域名也是消耗时间的,所以要减少对外部JavaScript、CSS、图片等资源的引用,不同域名的使用越少越好
    4,缓存重用数据
    使用缓存吧
    5,优化页面元素加载顺序
    首先加载页面最初显示的内容和与之相关的JavaScript和CSS
    然后加载DHTML相关的东西
    像什么不是最初显示相关的图片、flash、视频等很肥的资源就最后加载
    6,减少inline JavaScript的数量
    浏览器parser会假设inline JavaScript会改变页面结构,所以使用inline JavaScript开销较大
    不要使用document.write()这种输出内容的方法,使用现代W3C DOM方法来为现代浏览器处理页面内容
    7,使用现代CSS和合法的标签
    使用现代CSS来减少标签和图像,例如使用现代CSS+文字完全可以替代一些只有文字的图片
    使用合法的标签避免浏览器解析HTML时做“error correction”等操作,还可以被HTML Tidy来给HTML减肥
    8,Chunk your content
    不要使用嵌套tables
    <table>
      <table>
        <table>
          ..
        <table>
      <table>
    <table>
    而使用非嵌套tables或者divs
    <table>...</table>
    <table>...</table>
    <table>...</table>
    将基于大块嵌套的tables的layout分解成小tables,这样显示时不用加载整个页面(或大table)的内容
    9,指定图像和tables的大小
    如果浏览器可以立即决定图像或tables的大小,那么它就可以马上显示页面而不要重新做一些布局安排的工作
    这不仅加快了页面的显示,也预防了页面完成加载后布局的一些不当的改变
    image使用height和width
    table使用table-layout: fixed并使用col和colgroup标签指定columns的width
    10,根据用户浏览器明智的选择策略
    IE、Firefox、Safari等等等等
    11,页面结构的例子
    · HTML

        · HEAD

            · LINK ...
            CSS files required for page appearance. Minimize the number of files for performance while keeping unrelated CSS in separate files for maintenance.

            · SCRIPT. ...
            JavaScript. files for functions required during the loading of the page, but not any DHTML that can only run after page loads.
            Minimize the number of files for performance while keeping unrelated JavaScript. in separate files for maintenance.

        · BODY
        · User visible page content in small chunks (tables / divs) that can be displayed without waiting for the full page to download.

            · SCRIPT. ...
            Any scripts which will be used to perform. DHTML. DHTML script. typically can only run after the page has completely loaded and all necessary objects have been initialized. There is no need to load these scripts before the page content. That only slows down the initial appearance of the page load.
            Minimize the number of files for performance while keeping unrelated JavaScript. in separate files for maintenance.
            If any images are used for rollover effects, you should preload them here after the page content has downloaded.



    4.HTML页面加载和解析流程
    1.用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件; 
    2.浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文件; 
    3.浏览器又发出CSS文件的请求,服务器返回这个CSS文件; 
    4.浏览器继续载入html中<body>部分的代码,并且CSS文件已经拿到手了,可以开始渲染页面了; 
    5.浏览器在代码中发现一个<img>标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码; 
    6.服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码; 
    7.浏览器发现了一个包含一行Javascript代码的<script>标签,赶快运行它; 
    8.Javascript脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个<div> (style.display=”none”)。杯具啊,突然就少了这么一个元素,浏览器不得不重新渲染这部分代码; 
    9.终于等到了</html>的到来,浏览器泪流满面…… 
    10.等等,还没完,用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下<link>标签的CSS路径; 
    11.浏览器召集了在座的各位<div><span><ul><li>们,“大伙儿收拾收拾行李,咱得重新来过……”,浏览器向服务器请求了新的CSS文件,重新渲染页面。

    5.Yahoo对网页设计性能的建议,个人感觉是说得非常好的。
      英文版:http://developer.yahoo.com/performance/rules.html
      中文翻译:http://www.cnblogs.com/smjack/archive/2009/02/24/1396895.html

    参考资料:
    http://hideto.javaeye.com/blog/133384
    http://blog.chinaacc.com/liuzhantao/blog/20100430-3015241029081.html

    ---------------------------------------------

    转自: http://renyongjie668.blog.163.com/blog/static/1600531201097062789/

    =================================================

    提高网站访问速度的34条军规
    http://www.cnblogs.com/smjack/archive/2009/02/24/1396895.html


    这是雅虎上的一篇文章,最近正在研究提高网页响应速度和用户体验的方法,拿来翻译一下,加深理解。

    原文:best practices for speeding up your web site

    • 提高网站访问速度的34条军规 1-3

      • 减少HTTP请求数量

      • 使用内容分布式网络

      • 给头部添加一个失效期或者cache-control

    • 提高网站访问速度的34条军规 4-6

      • 压缩组件

      • 把样式表放于前面

      • 把脚本放在最后

    • 提高网站访问速度的34条军规 7-10

      • 不使用CSS表达式

      • 使用外部的Javascript和CSS

      • 减少DNS的查询

      • 缩小Javascript和CSS

    • 提高网站访问速度的34条军规 11-13

      • 避免重定向

      • 移除重复的脚本

      • 设定ETags

    • 提高网站访问速度的34条军规 14-17

      • 让Ajax可以缓存

      • 更早的刷新缓冲区

      • 在Ajax请求中使用GET方法

      • 后加载组件

    • 提高网站访问速度的34条军规 18-19

      • 预先加载组件

      • 减小DOM元素的数量

    • 提高网站访问速度的34条军规 20-25

    • 提高网站访问速度的34条军规 26-30

    • 提高网站访问速度的34条军规 31-34

  • PHP的GC垃圾收集机制

    2011-09-02 17:58:32

    今天听了php的gc :

    1. 256字节以下不会立刻回收变量空间
    2. unset:断开符号的引用连接,并且计数-1
    3. NULL:强制将计数清零
    4. xdebug ---->进行调试,打印内存使用情况
    php 5.3:
    1)不会立刻回收,会在根缓冲区满后执行GC
    2)能将内存泄露控制在一个阈值以下(与根缓冲区大小有关)。

     

    另附三篇文章:

    从 PHP 代码分析 PHP 的 GC(垃圾回收) 机制
    http://bbs.chinaunix.net/thread-1610024-1-1.html

    浅谈PHP5中垃圾回收算法(Garbage Collection)的演化
    http://www.cnblogs.com/leoo2sk/archive/2011/02/27/php-gc.html

    PHP的GC垃圾收集机制
    http://www.cnblogs.com/dkblog/archive/2010/06/04/1980694.html

     

    详细内容:

    从 PHP 代码分析 PHP 的 GC(垃圾回收) 机制
    众所周知, PHP 引擎本身是用 C 写的,提到 C 不能不提的就是 GC(垃圾回收).通过 PHP 手册我们了解到, PHP 引擎会自动进行 GC 动作.那么我们不禁要问,到底它是怎么回收的, & 引用操作是不是指针, unset()了一个变量时它是不是真的被回收了呢?这些看似手册有提及的问题,如果仔细分析会发现,远没有那么简单泛泛.也许有人会跳出来说:看 PHP源码不就知道了.是的,等你通读了 PHP 源码后这个问题肯定不在话下了,然本篇要仅从 PHP本身来分析这些看似平常却被忽视的小细节,当然了,其中难免水平所限,有所疏漏,热烈欢迎广大 phper 来共同讨论.

    首先咱先看到例子,最简单不过的执行流程了:
    Example 1: gc.php
    <?php
    error_reporting
    (E_ALL);
    $a = 'I am test.';
    $b = & $a;

    echo
    $b ."\n";

    ?>

    不用说 % php -f gc.php 输出结果非常明了:
    hy0kl% php -f gc.php
    I am test.


    好,下一个:
    Example 2:
    <?php
    error_reporting
    (E_ALL);
    $a = 'I am test.';
    $b = & $a;

    $b = 'I will change?';                                                           

    echo
    $a ."\n";
    echo
    $b ."\n";

    ?>
    执行结果依然很明显:
    hy0kl% php -f gc.php
    I will change?
    I will change?


    君请看:
    Example 3:
    <?php
    error_reporting
    (E_ALL);
    $a = 'I am test.';
    $b = & $a;  

    unset(
    $a);

    echo
    $a ."\n";
    echo
    $b ."\n";
    ?>
    是不是得想一下下呢?
    hy0kl% php -f gc.php
    Notice: Undefined variable: a in /usr/local/www/apache22/data/test/gc.php on line 8
    I am test.

    有点犯迷糊了吗?

    君再看:
    Example 4:
    <?php
    error_reporting
    (E_ALL);
    $a = 'I am test.';
    $b = & $a;

    unset(
    $b);                                                                       

    echo
    $a ."\n";
    echo
    $b ."\n";

    ?>
    其实如果 Example 3 理解了,这个与之异曲同工.
    hy0kl% php -f gc.php
    I am test.
    Notice: Undefined variable: b in /usr/local/www/apache22/data/test/gc.php on line 9


    君且看:
    Example 5:
    <?php
    error_reporting
    (E_ALL);
    $a = 'I am test.';
    $b = & $a;

    $a = null;

    echo
    '$a = '. $a ."\n";
    echo
    '$b = '. $b ."\n";

    ?>
    猛的第一感觉是什么样的?
    hy0kl% php -f gc.php
    $a =
    $b =

    没错,这就是输出结果,对 PHP GC 已有深入理解的 phper 不会觉得有什么奇怪,说实话,当我第一次运行这段代码时很意外,却让我对 PHP GC 有更深刻的理解了.那么下面与之同工的例子自然好理解了.

    Example 6:
    <?php                                                                           
    error_reporting
    (E_ALL);
    $a = 'I am test.';
    $b = & $a;

    $b = null;

    echo
    '$a = '. $a ."\n";
    echo
    '$b = '. $b ."\n";

    ?>

    OK,如果上面的例子的结果对看官来说无任何细节可言,那您可关闭本窗口了,欢迎有空再来!

    下面我们来详细分析 GC 与引用.
    1. 所有例子中,创建了一个变量,这个过程通俗一点讲:是在内存中开辟了一块空间,在里面存放了一个字符串 I am test. . PHP 内部有个符号表,用来记录各块内存引用计数,那么此时会将这块内存的引用计数 加 1,并且用一个名为 $a 的标签(变量)指向这块内存,方便依标签名来操作内存.

    2. 对变量 $a 进行 & 操作,我的理解是找到 $a 所指向的内存,并为 $b 建立同样的一引用指向,并将存放字符串 I am test. 的内存块在符号表中引用计数 加 1.换言之,我们的脚本执行到这一行的时候,存放字符串 I am test. 的那块内存被引用了两次.这里要强调的是, & 操作是建立了引用指向,而不是指针, PHP 没有指针的概念!同时有人提出说类似于 UNIX 的文件软链接.可以在一定程度上这么理解: 存放字符 I am test. 的那块内存是我们的一个真实的文件,而变量 $a$b 是针对真实文件建立的软链接,但它们指向的是同一个真实文件. So, 我们看到,在 Example 2  中给 $b 赋值的同时, $a 的值也跟着变化了.与通过某一软链操作了文件类似.

    3. 在 Example 3 与 4 中,进行了 unset() 操作.根据实际的执行结果,可以看出: unset() 只是断开这个变量对它原先指向的内存的引用,使变量本身成为没有定义过空引用,所在调用时发出了 Notice ,并且使那块内存在符号表中引用计数 减 1,并没有影响到其他指向这块内存的变量.换言之,只有当一块内存在符号表中的引用计数为 0 时, PHP 引擎才会将这块内存回收.
    PHP 手册
    4.0.0                 unset() became an expression. (In PHP 3,         unset() would always return 1).
    这意味着什么?
    看看下面的代码与其结果:
    <?php
    error_reporting
    (E_ALL);
    $a = 'I am test.';
    $b = & $a;

    unset(
    $a);
    unset(
    $a);
    unset(
    $a);

    echo
    '$a = '. $a ."\n";
    echo
    '$b = '. $b ."\n";

    ?>
    hy0kl% php -f gc.php

    Notice: Undefined variable: a in /usr/local/www/apache22/data/test/gc.php on line 10
    $a =
    $b = I am test.
    第一次 unset() 的操作已经断开了指向,所以后继的操作不会对符号表的任何内存的引用记数造成影响了.

    4. 通过 Example 5 & 6 可以明确无误得出: 赋值 null操作是相当猛的,它会直接将变量所指向的内存在符号号中的引用计数置 0,那这块内存自然被引擎回收了,至于何时被再次利用不得而知,有可能马上被用作存储别的信息,也许再也没有使用过.但是无论如何,原来所有指向那块内存变量都将无法再操作被回收的内存了,任何试图调用它的变量都将返回 null.

    <?php
    error_reporting
    (E_ALL);
    $a = 'I am test.';
    $b = & $a;

    $b = null;

    echo
    '$a = '. $a ."\n";
    echo
    '$b = '. $b ."\n";

    if (
    null === $a)
    {                                                                                
    echo
    '$a is null.';     
    } else
    {
    echo
    'The type of $a is unknown.';     
    }

    ?>
    hy0kl% php -f gc.php
    $a =
    $b =
    $a is null.


    综上所述,充分说明了为什么我们在看开源产品源码的时候,常看到一些比较大的临时变量,或使用完不再调用的重用信息都会被集中或显示的赋值为 null 了.它相当于 UNIX 中直接将真实文件干掉了,所有指向它的软链接自然成了空链了.
    之前在讨论到这些细节点时有很多想当然的念头,在实际的执行了测试代码后才发现: 哦,原来如此!
    纸上得来终觉浅,绝知此事须躬行.

    作者: hy0kl
    永久链接: 从 PHP 代码分析 PHP 的 GC(垃圾回收) 机制
    Email/MSN/Gtalk: hy0kle@gmail.com
    Time: 2009.11.07

     

     

    浅谈PHP5中垃圾回收算法(Garbage Collection)的演化

    前言

    PHP是一门托管型语言,在PHP编程中程序员不需要手工处理内存资源的分配与释放(使用C编写PHP或Zend扩展除外),这就意味着PHP本身实现了垃圾回收机制(Garbage Collection)。现在如果去PHP官方网站(php.net)可以看到,目前PHP5的两个分支版本PHP5.2和PHP5.3是分别更新的,这是因为许多项目仍然使用5.2版本的PHP,而5.3版本对5.2并不是完全兼容。PHP5.3在PHP5.2的基础上做了诸多改进,其中垃圾回收算法就属于一个比较大的改变。本文将分别讨论PHP5.2和PHP5.3的垃圾回收机制,并讨论这种演化和改进对于程序员编写PHP的影响以及要注意的问题。

    PHP变量及关联内存对象的内部表示

    垃圾回收说到底是对变量及其所关联内存对象的操作,所以在讨论PHP的垃圾回收机制之前,先简要介绍PHP中变量及其内存对象的内部表示(其C源代码中的表示)。

    PHP官方文档中将PHP中的变量划分为两类:标量类型和复杂类型。标量类型包括布尔型、整型、浮点型和字符串;复杂类型包括数组、对象和资源;还有一个NULL比较特殊,它不划分为任何类型,而是单独成为一类。

    所有这些类型,在PHP内部统一用一个叫做zval的结构表示,在PHP源代码中这个结构名称为“_zval_struct”。zval的具体定义在PHP源代码的“Zend/zend.h”文件中,下面是相关代码的摘录。

    01 typedef union _zvalue_value {
    02     long lval;                  /* long value */
    03     double dval;                /* double value */
    04     struct {
    05         char *val;
    06         int len;
    07     } str;
    08     HashTable *ht;              /* hash table value */
    09     zend_object_value obj;
    10 } zvalue_value;
    11   
    12 struct _zval_struct {
    13     /* Variable information */
    14     zvalue_value value;     /* value */
    15     zend_uint refcount__gc;
    16     zend_uchar type;    /* active type */
    17     zend_uchar is_ref__gc;
    18 };

    其中联合体“_zvalue_value”用于表示PHP中所有变量的值,这里之所以使用union,是因为一个zval在一个时刻只能表示一种类型的变量。可以看到_zvalue_value中只有5个字段,但是PHP中算上NULL有8种数据类型,那么PHP内部是如何用5个字段表示8种类型呢?这算是PHP设计比较巧妙的一个地方,它通过复用字段达到了减少字段的目的。例如,在PHP内部布尔型、整型及资源(只要存储资源的标识符即可)都是通过lval字段存储的;dval用于存储浮点型;str存储字符串;ht存储数组(注意PHP中的数组其实是哈希表);而obj存储对象类型;如果所有字段全部置为0或NULL则表示PHP中的NULL,这样就达到了用5个字段存储8种类型的值。

    而当前zval中的value(value的类型即是_zvalue_value)到底表示那种类型,则由“_zval_struct”中的type确定。_zval_struct即是zval在C语言中的具体实现,每个zval表示一个变量的内存对象。除了value和type,可以看到_zval_struct中还有两个字段refcount__gc和is_ref__gc,从其后缀就可以断定这两个家伙与垃圾回收有关。没错,PHP的垃圾回收全靠这俩字段了。其中refcount__gc表示当前有几个变量引用此zval,而is_ref__gc表示当前zval是否被按引用引用,这话听起来很拗口,这和PHP中zval的“Write-On-Copy”机制有关,由于这个话题不是本文重点,因此这里不再详述,读者只需记住refcount__gc这个字段的作用即可。

    PHP5.2中的垃圾回收算法——Reference Counting

    PHP5.2中使用的内存回收算法是大名鼎鼎的Reference Counting,这个算法中文翻译叫做“引用计数”,其思想非常直观和简洁:为每个内存对象分配一个计数器,当一个内存对象建立时计数器初始化为1(因此此时总是有一个变量引用此对象),以后每有一个新变量引用此内存对象,则计数器加1,而每当减少一个引用此内存对象的变量则计数器减1,当垃圾回收机制运作的时候,将所有计数器为0的内存对象销毁并回收其占用的内存。而PHP中内存对象就是zval,而计数器就是refcount__gc。

    例如下面一段PHP代码演示了PHP5.2计数器的工作原理(计数器值通过xdebug得到):

    1 <?php
    2   
    3 $val1 = 100; //zval(val1).refcount_gc = 1;
    4 $val2 = $val1; //zval(val1).refcount_gc = 2,zval(val2).refcount_gc = 2(因为是Write on copy,当前val2与val1共同引用一个zval)
    5 $val2 = 200; //zval(val1).refcount_gc = 1,zval(val2).refcount_gc = 1(此处val2新建了一个zval)
    6 unset($val1); //zval(val1).refcount_gc = 0($val1引用的zval再也不可用,会被GC回收)
    7   
    8 ?>

    Reference Counting简单直观,实现方便,但却存在一个致命的缺陷,就是容易造成内存泄露。很多朋友可能已经意识到了,如果存在循环引用,那么Reference Counting就可能导致内存泄露。例如下面的代码:

    1 <?php
    2   
    3 $a = array();
    4 $a[] = & $a;
    5 unset($a);
    6   
    7 ?>

    这段代码首先建立了数组a,然后让a的第一个元素按引用指向a,这时a的zval的refcount就变为2,然后我们销毁变量a,此时a最初指向的zval的refcount为1,但是我们再也没有办法对其进行操作,因为其形成了一个循环自引用,如下图所示:

    image

    其中灰色部分表示已经不复存在。由于a之前指向的zval的refcount为1(被其HashTable的第一个元素引用),这个zval就不会被GC销毁,这部分内存就泄露了。

    这里特别要指出的是,PHP是通过符号表(Symbol Table)存储变量符号的,全局有一个符号表,而每个复杂类型如数组或对象有自己的符号表,因此上面代码中,a和a[0]是两个符号,但是a储存在全局符号表中,而a[0]储存在数组本身的符号表中,且这里a和a[0]引用同一个zval(当然符号a后来被销毁了)。希望读者朋友注意分清符号(Symbol)的zval的关系。

    在PHP只用于做动态页面脚本时,这种泄露也许不是很要紧,因为动态页面脚本的生命周期很短,PHP会保证当脚本执行完毕后,释放其所有资源。但是PHP发展到目前已经不仅仅用作动态页面脚本这么简单,如果将PHP用在生命周期较长的场景中,例如自动化测试脚本或deamon进程,那么经过多次循环后积累下来的内存泄露可能就会很严重。这并不是我在耸人听闻,我曾经实习过的一个公司就通过PHP写的deamon进程来与数据存储服务器交互。

    由于Reference Counting的这个缺陷,PHP5.3改进了垃圾回收算法。

    PHP5.3中的垃圾回收算法——Concurrent Cycle Collection in Reference Counted Systems

    PHP5.3的垃圾回收算法仍然以引用计数为基础,但是不再是使用简单计数作为回收准则,而是使用了一种同步回收算法,这个算法由IBM的工程师在论文Concurrent Cycle Collection in Reference Counted Systems中提出。

    这个算法可谓相当复杂,从论文29页的数量我想大家也能看出来,所以我不打算(也没有能力)完整论述此算法,有兴趣的朋友可以阅读上面的提到的论文(强烈推荐,这篇论文非常精彩)。

    我在这里,只能大体描述一下此算法的基本思想。

    首先PHP会分配一个固定大小的“根缓冲区”,这个缓冲区用于存放固定数量的zval,这个数量默认是10,000,如果需要修改则需要修改源代码Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES然后重新编译。

    由上文我们可以知道,一个zval如果有引用,要么被全局符号表中的符号引用,要么被其它表示复杂类型的zval中的符号引用。因此在zval中存在一些可能根(root)。这里我们暂且不讨论PHP是如何发现这些可能根的,这是个很复杂的问题,总之PHP有办法发现这些可能根zval并将它们投入根缓冲区。

    当根缓冲区满额时,PHP就会执行垃圾回收,此回收算法如下:

    1、对每个根缓冲区中的根zval按照深度优先遍历算法遍历所有能遍历到的zval,并将每个zval的refcount减1,同时为了避免对同一zval多次减1(因为可能不同的根能遍历到同一个zval),每次对某个zval减1后就对其标记为“已减”。

    2、再次对每个缓冲区中的根zval深度优先遍历,如果某个zval的refcount不为0,则对其加1,否则保持其为0。

    3、清空根缓冲区中的所有根(注意是把这些zval从缓冲区中清除而不是销毁它们),然后销毁所有refcount为0的zval,并收回其内存。

    如果不能完全理解也没有关系,只需记住PHP5.3的垃圾回收算法有以下几点特性:

    1、并不是每次refcount减少时都进入回收周期,只有根缓冲区满额后在开始垃圾回收。

    2、可以解决循环引用问题。

    3、可以总将内存泄露保持在一个阈值以下。

    PHP5.2与PHP5.3垃圾回收算法的性能比较

    由于我目前条件所限,我就不重新设计试验了,而是直接引用PHP Manual中的实验,关于两者的性能比较请参考PHP Manual中的相关章节:http://www.php.net/manual/en/features.gc.performance-considerations.php

    首先是内存泄露试验,下面直接引用PHP Manual中的实验代码和试验结果图:

    01 <?php
    02 class Foo
    03 {
    04     public $var = '3.1415962654';
    05 }
    06   
    07 $baseMemory = memory_get_usage();
    08   
    09 for ( $i = 0; $i <= 100000; $i++ )
    10 {
    11     $a = new Foo;
    12     $a->self = $a;
    13     if ( $i % 500 === 0 )
    14     {
    15         echo sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "\n";
    16     }
    17 }
    18 ?>
    PHP内存泄露试验

    可以看到在可能引发累积性内存泄露的场景下,PHP5.2发生持续累积性内存泄露,而PHP5.3则总能将内存泄露控制在一个阈值以下(与根缓冲区大小有关)。

    另外是关于性能方面的对比:

    01 <?php
    02 class Foo
    03 {
    04     public $var = '3.1415962654';
    05 }
    06   
    07 for ( $i = 0; $i <= 1000000; $i++ )
    08 {
    09     $a = new Foo;
    10     $a->self = $a;
    11 }
    12   
    13 echo memory_get_peak_usage(), "\n";
    14 ?>
    这个脚本执行1000000次循环,使得延迟时间足够进行对比。

    然后使用CLI方式分别在打开内存回收和关闭内存回收的的情况下运行此脚本:

    1 time php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php
    2 # and
    3 time php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php
    在我的机器环境下,运行时间分别为6.4s和7.2s,可以看到PHP5.3的垃圾回收机制会慢一些,但是影响并不大。

    与垃圾回收算法相关的PHP配置

    可以通过修改php.ini中的zend.enable_gc来打开或关闭PHP的垃圾回收机制,也可以通过调用gc_enable()或gc_disable()打开或关闭PHP的垃圾回收机制。在PHP5.3中即使关闭了垃圾回收机制,PHP仍然会记录可能根到根缓冲区,只是当根缓冲区满额时,PHP不会自动运行垃圾回收,当然,任何时候您都可以通过手工调用gc_collect_cycles()函数强制执行内存回收。

     

     

    PHP的GC垃圾收集机制

    每一种语言都有自己的自动垃圾回收机制,让程序员不必过分关心程序内存分配,但是在OOP中,有些对象需要显式的销毁;防止程序执行内存溢出。

    一、PHP 垃圾回收机制(Garbage Collector 简称GC)

    在PHP中,没有任何变量指向这个对象时,这个对象就成为垃圾。PHP会将其在内存中销毁;这是PHP 的GC垃圾处理机制,防止内存溢出。

    当一个 PHP线程结束时,当前占用的所有内存空间都会被销毁,当前程序中所有对象同时被销毁。GC进程一般都跟着每起一个SESSION而开始运行的.gc目的是为了在session文件过期以后自动销毁删除这些文件.

    二、__destruct /unset

    __destruct() 析构函数,是在垃圾对象被回收时执行。

    unset 销毁的是指向对象的变量,而不是这个对象。

    三、 Session 与 GC

    由于PHP的工作机制,它并没有一个daemon线程来定期的扫描Session 信息并判断其是否失效,当一个有效的请求发生时,PHP 会根据全局变量 session.gc_probability 和session.gc_divisor的值,来决定是否启用一个GC, 在默认情况下, session.gc_probability=1, session.gc_divisor =100 也就是说有1%的可能性启动GC(也就是说100个请求中只有一个gc会伴随100个中的某个请求而启动).

    GC 的工作就是扫描所有的Session信息,用当前时间减去session最后修改的时间,同session.gc_maxlifetime参数进行比较,如果生存时间超过gc_maxlifetime(默认24分钟) ,就将该session删除。

    但是,如果你Web服务器有多个站点,多个站点时,GC处理session可能会出现意想不到的结果,原因就是:GC在工作时,并不会区分不同站点的session.

    那么这个时候怎么解决呢?
    1. 修改session.save_path,或使用session_save_path() 让每个站点的session保存到一个专用目录,

    2. 提供GC的启动率,自然,GC的启动率提高,系统的性能也会相应减低,不推荐。

    3. 在代码中判断当前session的生存时间,利用session_destroy()删除.
     
  • 测试blog推荐

    2011-07-18 11:00:50

     

    1. 卖烧烤的鱼 http://www.cnblogs.com/mayingbao/

    2. Jason的测试人生  http://www.futurehandw.com/

    3. http://hi.baidu.com/higkoo/blog/category/Loadrunner/index/0

    4. 淘宝测试  http://qa.taobao.com/

    5.gzh0222的专栏

    http://blog.csdn.net/gzh0222/article/category/852986

     

    6.

    http://www.bish.co.uk/forum/


    Useful tools when performance testing  http://www.bish.co.uk/forum/index.php?topic=27.msg31#msg31

    LoadRunner y-lib library files from Floris Kraak
    http://www.bish.co.uk/forum/index.php?topic=84.0

     

     

  • 测试?资深?管理?你伤不起!!! 与 组织定律:彼得原则、墨菲原则、帕金森定律

    2011-06-12 18:29:06

    彼得原则、墨菲原则、帕金森定律分别是指的什么?

    http://zhidao.baidu.com/question/201600455.html
    如题,为什么说是最杰出的三大发现?理解的人用简短易懂的话讲一下,谢绝大批量的复制黏贴,帕金森百科有,可以更简单精辟点说。谢谢

    最佳答案

    彼得原理(The Peter Principle)正是彼得根据千百个有关组织中不能胜任的失败实例的分析而归纳出来  彼得原理
    的。其具体内容是:“在一个等级制度中,每个职工趋向于上升到他所不能胜任的地位”。

    什么是墨菲定律?最简单的表达形式是“有可能出错的事情,就会出错(Anything that can go wrong will go wrong)。”

    帕金森现象
    定律一:
    冗员增加原理:官员数量增加与工作量并无关系,而是由两个源动因造成的。每一个官员都希望增加部属而不是对手(如“投票”);官员们彼此为对方制造工作(如行政审批,工商、税务、审计、公安,既得利益驱使)
    定律二:
    中间派决定原理:为了争取中间派的支持,双方颇费心机进行争取,特别是双方势均力敌的情况下。所以,不是竞争对手而是中间派成了主角。对决定的内容不十分清楚的人,意志薄弱的人,耳朵不大灵光的人
    定律三:
    鸡毛蒜皮定律:大部分官员由不懂得百万、千万元而只懂得千元的人组成,以至于讨论各种财政议案所费的时间与涉及的金额呈反比,即涉及的金额越大,讨论的时间越短,反之时间则越长。鸡毛蒜皮的事情则花费很多时间。
    定律四:
    办公场合的豪华程度与机关的事业和效率呈反比:事业处于成长期的机关一般没有足够的兴趣和时间设计完美无缺的总部。所以,“设计完美乃是凋零的象征”,“完美就是结局,结局就是死亡”。
    定律五:
    鸡尾酒会公式:会议与鸡尾酒会(饭局)同在。把会场从左到右分为A-F六段,从进门处到最远端分为1-8八段,则可划分出48个区域;在假定酒会开始的时间为H,且最后一名客人离开的时间是最初一名客人进场后2小时20分钟,则,重要人物都会在H+75至H+90的时间在E/7区域集合,最重要的人物自然会在其中。
    定律六:
    嫉妒症(分三个时期):在嫉妒症流行的机关里,高级主管辛苦而迟钝,中层干部勾心斗角,底层人员垂头丧气而不务正业。 第一阶段,出现了既无能又好嫉妒的人物,即患上了“庸妒症(平庸而嫉妒)”; 第二阶段,这些庸妒症患者不幸进入或原本就在高层,尽一切可能手段排斥比自己强的人,拒绝提升能力强的人;“愚蠢比赛” 第三阶段,机关仿佛被喷了DDT,凡才智者一概不得入内,机关病入膏肓,此时的机关已经无药可救了
    定律七:
    退休混乱(50岁现象):一般退休的年龄是R,在前3年(R-3)人的精力会开始减退;问题在于如何挑选合适的接替者,工作表现越优秀,任职时间越长,越难寻得合适的接替者,而在位者总会设法阻止职位较低的人接近自己的职位,以至不得不延长自己的退休时间。



    推荐答案
    彼得定律--------不称职定律
    不断因为称职而得到提升的时候,其结果必定是阶段性的不称职
    释:面临升职时需要全面考虑自己能否在新的岗位上称职,不要贸然上任;
    作为领导,面临提升他人的时候,需要考虑是否提升会给被提升人带去不称职压力,从而导致因不称职的发生,而致使事物整体遭遇风险。

    帕金森定律-----懈怠定律
    不称职的责任人,一定会将“不称职风险”转嫁给更不称职的下属
    释:当一件事物的责任人是不称职的时候,将导致这件事物的大部分参与人都是不称职的

    墨菲定律--------错误风险定律
    如果一件事,有可能发生错误,那这件事就必然会发生错误。
    释:只要有风险发生的因素存在,就一定会有风险发生的可能;
    只要有风险发生的可能,就一定会有风险发生的时候;

    之所以说这三大定律是21世纪最著名的原理,是因为这三条原理解释,并剖析了目前人类社会,高度文明后所必然出现的社会问题,而且提出了避免这些问题必然发生的手段和方法。
    我们大多数人对这些看似简单的问题,认识并不透彻。从而导致了原理所述结果发生的必然性
    希望可以和大家一起讨论这些命题
    QQ:6503420
    Mail:zhengyuliang76@163.com
    郑宇梁






    测试?资深?管理?你伤不起!!!


    http://www.51testing.com/?uid-68857-action-viewspace-itemid-236965


    话说我们测试部门巅峰时期曾经有150多人,后来由于集团业务发展,测试部门分成了两个,我们部门保有110人左右,另一个测试部门约有70多 人吧。新生的部门我不了解,单看我所在的这个庞大的测试部门,我这两年来(除了今年,今年笔者不吱声了)一直不断地诟病方方面面的问题,向领导反馈但由于 没有给出实际解决方案而最终无法“上达天听”,最后也就变成了敢怒不屑言,现在索性写出来给自己、给大家看看吧,或许从字面上能发现一些解决的途径。

    整体层次感模糊

    说到资深测试工程师这个头衔,我觉得更多的不是体现技术能力和头脑的灵活,而是工作经验、综合能力和资历,在公司内部有一定的人脉,有着与干系人较多的合作经验。当然,不同的公司依据自身的体制对测试人员的称呼也有所不同,我们这里不去纠结这个。我们公司是采用职级制度,初级测试工程师的可能就是指本科毕业3年以内的同事,也就是在工作2年后由初级升到资深,当然这两年的考核要在一定的百分比之内。那么由初级升到资深看起来是件很简单的事情了,或许正是因为这个资深的头衔来的太过简单吧,我们的整个测试部门没有综合能力上的层次感,有些工作3年或者5年的同事与工作2年 之内的同事在各个方面没有多大差别。另一方面,在日常工作中领导并没有把测试分析、测试设计、和测试执行在资深测试工程师和初级测试工程师之间进行明确的 分工,往往由某一个人全程负责测试分析、设计和执行。虽然我们尝试过使用测试执行工程师(执行员),但是沟通成本太高几乎让所有的测试工程师都抱怨不休; 另外,对测试执行员没有有效的培养和激励机制,而且领导决定原则上只招收大专毕业生(摆明了就是学历歧视),导致他们本身工作也没有多大积极性,这个划分 体系也就渐趋式微了。

    针 对这种情况,我认为应该从职责和职位上进行细化分解,把测试工作逐层分离,这样对于测试部门这样的成本中心来说,人员流失就是不很大的问题了。因为依据能 力合理分解工作,职责分明,进一步铲除吃大锅饭的意识,让能力更强的人承担更加有价值的工作,也能吸引更多人才,而不会让这些真正的“能人”的出走带动连 锁效应,造成很大的人员损失,因为这些能力更强的测试人员的非职权影响力也是不可小视的;我亲见过(在我上一家公司)一个牛人辞职陆续带走一群人的情况。 具体如何分层划分测试部门的职责和职位呢,具体情况具体对待,我觉得大致可以参考如下结构,由上级领导进行技术和综合能力的考评,而不使用资历评判:

    Ø 测试部门经理、测试总监、质量总监——负责管理整个测试部门的人力资源;

    Ø 分组测试经理——适合20人以上的测试部门,因为每个团队最佳配置应该是89个人;

    Ø 技术主管/测试架构师——每个分组配备一位技术主管或者测试架构师;

    Ø 高级测试工程师——通常5年以上经验而且达到一定技术层次;

    Ø 中级测试工程师——通常3年以上经验并且满足一定技术层次;

    Ø 初级测试工程师——通常是应届毕业初几年,刚开始工作的同事。

    作为一个测试主管,如果你让大家所从事的工作内容都基本一致,而你却跟别人说资深测试工程师的一个人月的成本是15万,初级测试工程师的成本是95,你这不是自欺欺人么?除了资深测试工程师的工资比初级测试工程师的高一些还有什么差别呢?另外,既然工作内容一样,凭什么资深的要比非资深的工资高呢?

    当然,或许你会回答说资深测试工程师业务知识熟悉得多,那么我们来看看包含业务知识在内的各个方面的综合能力发展情况吧。

    综合能力不健全

    既然是软件测试,那么测试人员至少应该具备三个能力:业务知识、IT专业技能、职业技能。一般情况下,对于资深测试工程师来说,由于长期在一个公司或者一个行业工作(喜欢换行的咱们不讨论),他们的业务知识一般的确比初级测试工程师熟悉很多。

    而他们优势最明显的是职业技能,职业技能对测试来说较多的体现在沟通能力、协调能力上。但是,在我们公司,这个词并不见得代表一种正面的解释,是因为有部分人把 这种能力变成了扯皮、推诿的能力!尤其在一个部门非常多的公司,长期的跨部门协作练就了他们敏感的神经和打哈哈神功:不轻易明确自己所代表的测试部门的立 场,不发表与多数人意见相悖的言论,哪怕自己有十足的把握大家的看法是有疏忽或者片面的,自我保护意识很强……甚至有些人根本不认为自己有权利和义务去否 决有严重问题的版本的下发!十分崇尚升级,事无巨细,只要自己没有经历过的便推到领导那里,即便自己有权利和能力去分析决断。看起来是一个十分没有个性、 十分听话的员工或者团队,如果本职工作进行得不错,那么领导是十分喜欢的,因为他们从不“捣乱”。相反,也有一部分人喜欢“倒江湖”,相信自己能够代表所 有人,很豪爽,喜欢随意许诺,有时候犯的错误会让领导“恨得牙直痒痒”。相比之下,初级测试工程师由于大都刚从校园出来,显得稚嫩而直率的多,虽然错误不 会少犯,但是如果善加诱导,他们的进步会让人看在眼里、喜在心里。如何能够让他们避免过多接触“扯皮神功”和“倒江湖神功”就要看公司的体制了。就我们公 司来看,如果没有重大变革发生,大部分应届毕业生将会延续前辈们的旅程,可惜我也只是动动嘴皮子,未必能提供什么有建设性的意见,但愿英明的领导们能洞察 这些细节,不要让这种怪圈循环持续下去了。

    IT专业技能上,有两种发展趋势:一种是随着经验越多技术越强、大局意识越强;另外一种是被温水煮的青蛙,慢慢的失去跳跃的能力,那么等着被开水烫死,要么跳出去……由于温差太大而被冻死。很悲剧的是我周围有大半的测试同事走向了第二条道路,可能随着年龄的增长,学习和接受新知识的能力在下降吧,我们很多资深的老同事对新技术、新方法持怀疑、观望甚至抵触的态度。这样一来整个测试部门的情绪看起来并不那么高,学习氛围不是很好,整体技术水平不高,而且断层明显。比如在我们部门,且不讨论测试分析设计本身的能力,了解QTP的可能有90人,但是精通的不过23人,其余的只停留在简单的录制、修改的层次;了解Selenium20来人,精通的可能仅12人甚至没有;了解OracleSQL100多人,懂得优化算法和SQL效率优化的也不过35人;了解性能测试的大约50人,精通它的23人而已,还有中间件、操作系统……而且我发现,就是那么几个人对各种技术都有涉猎,而其余的同事则似乎是没有强压根本不会去学习,遑论深入研究了。我分析造成这种局面的原因有这么几点:

    Ø 价 值取向或者说兴趣不在技术本身,对于偏资深的测试工程师来说,大部分可能都已经成家,他们不愿意挤出时间在技术研究学习上,而更乐于回去照顾宝宝。我曾经 尝试举办很多次技术分享和请外援来进行技术培训,但是大家都报着拿培训积分的态度去参加,培训完毕立刻就忘记了,根本不去主动尝试研究和应用。这一点基本 无法改变,除非有足够多的利益放在他们面前,但是这这种诱惑也会养成娇纵的情绪,是不可取的。

    Ø 公司运作方式决定了大家的工作很忙碌,一个测试人员要应对10来个开发人员的代码,压力大貌似很锻炼人,但是只是锻炼我们时间管理和处理并行任务的能力,并没有给我们留下多少学习的空间。而且在这种情况下工作,即便有朝一日清闲下来,很多人也不再愿意学习了:人的能力就像弹簧,拉直了就再也缩不回去了!

    Ø 有些领导信奉这么一个观点:公司付薪水是让你们工作的而不是学习的云云……对这种说法吾深鄙视之!殊不知所谓的学习不仅是对个人的提升,也是在为公司提高产能。如果整个测试部门在使用QTP作为UI测试的工具,那么就不能留出一些时间让对Selenium很有兴趣的同事去研究一下Selenium么?假如有朝一日需要这么一种工具或框架的支持,谁能担当此任呢?

    Ø 测试部门的培训机制不健全,偶尔有培训的机会,便进行大规模的“推广培训”,没有提供有效的技术深入探讨和学习的机会,例如参加测试沙龙、举办技术交流会议,或者搭建知识共享体系,仅有的一个Wiki到如今我也不知道自己的用户名和密码是啥。负责培训的人好像只是为了花完这笔培训预算而进行培训,根本不在乎大家整体的知识层次是怎样的,也就不去仔细的调查分析。如果不分析就提供几门看似华丽但是毫无用处的课程让大家自己去选,还要这个负责人有什么用呢?

    这 样看来,随着我们的“资”变得越来越深,我们的综合能力好像并没有得到质的提升,所谓的资深测试工程师,好像除了业务知识并没有什么优势了。相反初级测试 工程师看起来则更有学习的欲望,更容易管理。人说职业发展要经历:有冲劲期、疲惫麻木期,之后再回到有干劲的状态,这种理论在我们这基本属于扯淡,因为我 们的测试人员看起来只要一旦疲惫就必定会辞职……恢复有干劲的状态那是在别处的事情了,我们这是看不着了吧。

    工作效率与方法

    本 来不想说这档子事情的,显得自己没气量,不过既然是分享,那就把细节都告诉大家,这样大家替笔者分析的时候就不会漏过细节了……我也承认自己心胸的确不够 宽广,要不然也不会蛋疼于我所看到的种种情况了。大部分公司都有个“优秀员工”的表彰的吧,我们公司也有。其实在笔者心目中,这个称号很值钱,自己不敢轻 易去想的,尤其是我刚入司第二年见到我一个很强的师兄获得这个殊荣之后。这位师兄是运维人员,他为我们公司开发了运营监控平台,改变了应用系统每晚要人值 守的循例。在我看来,他的JAVA编码强过一般的开发部门的编码人员;数据库与一般DBA水平相当甚至更高一些;对中间件的熟知程度赶得上了大部分基础架构专业中间件管理人员。在得知这么一位牛人存在之后,我就一直不停的向他请教和学习,而且他也很耐心,所以我觉得他被称为“优秀员工”是必须的,而且别的“优秀员工”也应该如此。

    但是今年的公告却让我改变了一些看法,我们测试部门的一位同事竟然也获得了这个殊荣,不过这位同事并不是我想象中的那么优秀。粗略估算,他负责的关键系统全年发布了约20个版本左右,几乎每个版本的发布前一天他都加班到深夜,而且基本都是和开发、部署、中间件、DBA一 起定位处理一些问题。但是,几乎每一次都被证明是测试环境数据或者配置问题,虽然开发不停的抱怨为何不早点发现问题或者为啥总是环境有问题,但是他们还是 得陪着解决问题。每个月的加班记录表中总会有几笔他的记录,而且基本都是在临近版本发布的时候,而单就加班这一件事情来说,可能领导看到的是他很认真负责 的跟踪每一个发现的问题吧,那么如果别人不会把问题留在最后才发现就拿不到“优秀员工”的称号?其实这只是很小的一个例子,笔者虽然有点羡慕嫉妒恨,但其 实也只是想借此表明工作方法和效率是很重要的;我们再看一些其他的很有代表性的例子:

    Ø 经常在爬在别人工位上一起讨论问题的时候发现有些人的邮箱里总有几百上千封的未读邮件,请问您能保证这其中没有急需您来配合处理的事情么?

    Ø 经常有人来问我:上次你说那XXX是怎么回事来着,我记不得了,我回答说自己找邮件呗,我都整理了总结文档发给大家了啊;答曰:邮箱太乱,找不到……拜托,Outlook是可以设置规则的好不?

    Ø 经常有人为了自动化测试的指标达成情况找我来做数据采集,其实就是到QC数据库里面查一些数据而已,我就很纳闷为什么大家不能保存一下我发给你们的数据采集脚本呢?

    Ø 经常侧耳听到有人在和UAT人员沟通时说:等一下,我帮你找一下测试环境的地址……哎,这些地址太多了,再等一会哈……我就没明白,为什么不能在IE上设置链接分类来快捷打开呢,难道这些工作只做一次,下次无需再进行了么?

    Ø 经常见到为芝麻大点小事组织多方会议,但是会议很快就进入了大家各自发牢骚的议程,没有结论,然后回去继续邮件沟通,N个人用N*N封邮件来解决一件事情,这……值得么?

    Ø 经常……

    工 作压力大没错,但是我们很多同事却忽略了造成我们工作压力大的一方面原因是我们自身的工作效率是可改善的。如果在日常工作中注重一些细节的效率提升和方法 的改进,那么我们的境遇或许不会像目前这么悲惨了。因为领导只要不是爱心泛滥或者是个笨蛋,他们都能看得到大家工作量上的差别,他们不会一直认为相同工作 量的情况下你的加班是额外的付出,时间长了他们会认为你无论做任何事情都需要加班,那是因为你做事情喜欢拖沓,原本就不值得同情!

    这 点对资历较老的同事来说显得更加重要,因为长期的工作很容易让人形成习惯和思维定势,处理问题的手法千篇一律、毫无新意,而且对于工作时间较长的同事来 说,改变这种习惯所需要付出的代价会比刚毕业的新员工大很多。就像中学时做几何证明题,一道合理的辅助线比一道一般的辅助线对这道题目来说作用是不一样 的。虽然方法都是对的,但是合理的“投机取巧”让我们能够省出更多的时间来处理其他的问题。我们现在的情况是,至上而下,从没有人去关注这些细节,也不会 从理论上去推算这对我们的工作有什么影响。只是一味鼓吹自己的管理技术多么强大,要求员工按照自己定制的规范与流程去工作是毫无意义的,管理者只有通过指 导或者组织交流等行为来影响或者改变部属的行为,从而达到提升工作效率的效果才是王道。那么我们是否都能够把自己的管理工作做好呢,笔者这里冒死来批判一 下我们的有些主管和准主管的行为吧。

    做管理你称职么

    对于大领导来说,最悲剧的行为是喜欢“搞运动”,但是又收不到预期的效果。在分析原因的时候我们会发现,BOSS的想法没错,小弟们按照得到的指示去操作也没错,那么错在哪里呢?基层主管!对于我们测试来说,发展自动化测试是没有什么争议的事情,提高测试分析、设计能力以达到更好的测试效果也是没有争议的事情。笔者就以此两个例子来分析一下,我们的运动为什么没有达到预期的效果呢?

    首先说第一个:自动化测试发展建设,我们07年开始进行自动化测试建设,引入QTP这种工具,而在此之前笔者已经有一年的QTP使用经验了。奇怪的是,没有任何征兆,指标直接下达,要求半年之内所有关键系统自动化覆盖率70%,大家知道这意味着什么么?100个没有使用过自动化测试工具/QTP的测试人员要在半年内完成大约10000个自动化测试脚本的编写。须知我们当时每半个月就有一个版本要发布到生产,有些系统频率可能会更高,每天能挤出半小时时间就已经相当不容易了,何来精力去做这些压根就毫无根基的事情呢。后来才得知是一群测试分组经理坐在一起“研究”了一把,觉得QTP录制起来很快,每人每天搞几十个应该不成问题……我勒个去哦!宋峰宋老师有篇文章说“六拍”说得很好,而且我觉得说得就是当时这种场景,不过这是由一群基层主管替领导拍胸脯,为下属拍脑袋!结果我不说大家也能猜得到,我们达成了指标,更精彩的是,在接下来两年里面,我们的QTP脚本被废弃、重新开发再废弃再重新开发好几次!拿周立波的话来说:啊——爽滑到底!

    这个例子说明了什么?做IT管理首先得懂技术,不了解细节和深入的问题就不要轻易拍脑袋,要仔细调研分析,征求多方意见,观察行业动态等等,总之要先做足了功课。否则的话,跟您这一比,“酒驾”啊、超速啊神马的压根都不算个事,人家那害不了几个人,您这害了很多人——几乎就是半辈子!

    再一个就是我本人引发的(至少我自己一厢情愿的认为和我有关吧)血案,系统功能点和测试分支的整理,不知诸位听说过没有,事情的发展还是远远超出了我的预期的。089月 我在西安休年假,领导就最近的几个测试遗漏问题咨询开发和一些测试同事,看有没有什么好的办法能够规避这种问题的产生。休假嘛,骨头痒痒,就洋洋洒洒写了 几百字回去,主要意思就是要把测试分析做到位,把测试部门的职能细化,不能由一个人从头到脚的去照顾一个系统的需求。可能领导觉得这种说法挺有道理,后来 又去咨询了一些砖家叫兽吧,最终在09年初把这个想法汇报给了新任部门经理。在层层决断之后,由我们公司的总助拍板说,好,这件事情是有意义的,你们做吧,给个明确的计划,并且把这项工作列入KPI之一,用于考核测试人员的工作成果,这件事情由XXX负责统筹吧。然后XXX组长创造性地把这个十分抽象的工作实例化了,经过几次PK未果之后,我放弃了反抗,眼睁睁的看着这项运动如火如荼的展开了。这位负责人如何实例化这项工作的呢?她要求所有测试分组,把代码的分支罗列出来,分配到不同的功能点下,记录在QC中,进行详细的说明;后续做了很多的QC二次开发,做的页面逻辑控制让人一开始做测试需求录入便有要死的感觉。要知道我负责的是个中型系统,它的package代码就有几十万行,掺杂着JAVA无 比强大的页面、组件复用,厘清分支谈何容易呢。按照惯例,在几个月之内完成,我们都完成了,至于后面所花费的时间真的很让人心疼,尤其是这件事情本身为后 续的测试需求分析、录入工作带来了很大的不便。那么起到什么效果了呢?我还真不清楚,也没有人敢在年度报告上提这项工作的量化成果,只是会提到我们的功能 点、分支的回归测试覆盖率达到了多少,至于功能点、分支分析的全不全就无人问津了。我不知道这个事情责任到底是不是在我,若真的如我自作多情所猜想的一 样,是因为我的言论诱发了这件事情的话,我真的会有想砍掉自己指头的冲动;若不是呢,那我可以坦然一些,至少面对同事们的时候不会为此而羞愤,而即便是这 样,我也还是为自己没勇气越级诉求扭转这项工作的方向而惭愧。

    这件事情自上而下看起来都合情合理,问题就出在执行上,可能所有人的初衷都是一样的:那就是改善系统测试的质量,降低生产缺陷量。但是在执行的时候,这位负责人并没有去分析这件事情本身所带来的负面效应,也没有真正理解问题提出者的意思:加强系统测试需求分析,分解测试过程。她一厢情愿的认为加强测试需求分析就是把测试人员对需求的理解录入到QC指定的目录下,以证明测试人员对这个需求的理解是正确的;把测试需求分析和测试用例分开管理就可以达到测试过程分解的效果。谁知道对需求理解正确就一定会没有问题出现呢?关联影响和深埋在应用系统中的历史遗留缺陷如何分析出来呢?其实这件事情做与不做都能达到对新功能测试的 质量保证,领导的核心需求是能够将通常看不到的隐患用一种机制来把它挖出来,显然这件事情算是失败了的,至少我认为是个失败。无论你是否属于管理层,在做 管理、决断的时候所需要具备的素质还是很多的,笔者不可能面面俱到的描述。这里的两个“运动”表明,如果连基础的一些素质都没有具备的话,做管理工作会将 一群人引向成功之母那里。

    看起来笔者似乎只有失败的例子,没有什么值得分享的了?不是的,在09年底的又一次自动化运动中,笔者的主管征求组员们的意见,最终以质量承诺换取了低于部门10%的 自动化覆盖率指标。事实证明,这是这两年来最让笔者开心的事情了,因为在随后一年多的每日回归里面,我们组的脚本通过率平均高出平均水平很多,稳居部门第 一,而测试缺陷发现和对回归测试的帮助也是有目共睹的。由此一事,笔者总结出一点经验:测试主管一定要懂得拒绝不切实际的想法、拒绝不客观行为和超出下属 能力范围的工作。

    我们没有理由去质疑领导们的初衷,没有哪个领导无知到只会用面子工程来装点自己,但是领导的思想在传达和执行的过程中是否被正确理解和分析是很关键的。如今,笔者看到我们部门在进行新一轮的运动:接口测试自动化和算法测试自动化,在搭建起STAF平台之后,似乎没有看到测试框架的搭建和组件的抽离便开始让所有测试人员投入到代码编写的洪流中去了。不知我获得的信息和理解是否有误,但愿上天有好生之德,保佑我们不再为一堆散乱无组织的代码去买单了罢。



  • 十六个暴有名气的美国新闻网站

    2011-06-10 14:02:52

     

    精华内容:这十六个美国新闻网站大家即使没亲眼看过也应该经常听说,没错,它们就是来自美国的具有世界影响力的十六个最强悍的报纸,杂志的网站,千万别错过任何一个! 国外网站: 美国新闻网站》的原始完整内容保存在wwww.kincool.cn/hosting/hosting_23.html

    本文的国外网站由“精华库”-www.kincool.cn-提供。

    美国新闻网站NO_1: 今日美国 - http://www.usatoday.com/
        《今日美国》是当今美国新闻网站第一大报,也是美国新闻网站NO_1: 唯一的彩色版全国性对开日报,属全美最大的甘尼特报团,1982年9月15日创刊,总部设在弗吉尼亚州的罗斯林.它内容简明、编排新颖.

    美国新闻网站NO_1: CNN有线电视新闻网 - http://www.cnn.com/
        CNN(Cable News Network)是美国新闻网站NO_1: 有线电视新闻网,全球以新闻播报为主的电视台.CNN国际新闻网为全球最先进的新闻组织,带给您每周七天,每天二十四小时的全球直播新闻报导.

    美国新闻网站NO_2: 福布斯杂志   - http://www.forbes.com/
        《福布斯》杂志是美国新闻网站中最早的大型商业杂志,也是全球最为着名的财经出版物之一.《福布斯》为双周刊杂志,每期刊登60多篇对公司和公司经营者的评论性文章,语言简练,内容均为原创.

    美国新闻网站NO_2: 纽约时报 http://www.nytimes.com/
        美国新闻网站NO_1: 主要报纸纽约时报(The New York Time)的网站.纽约时报是一份在美国新闻网站NO_1: 纽约出版的报纸,在全世界发行,有相当的影响力.它有时也被戏称为"灰色女士"(The Gray Lady)

    美国新闻网站NO_1: 财富杂志   http://www.fortune.com/
        《财富》杂志于1929年由美国新闻网站NO_1: 人亨利.卢斯创办,目前已经做到了全球化的影响力.《财富》的核心竞争力是其和"资本主义商业成功联系在一起"(鲁斯语)的高度的权威性,为全球商人熟知

    美国新闻网站NO_3: 国家地理杂志   http://www.nationalgeographic.com/
        美国国家地理杂志创刊于1888年,它由美国 的一家非盈利科学教育组织--"国家地理协会"(Natioinal Geographic Society)创办.国家地理杂志近千分之一的选片率十分苛刻


    美国新闻网站NO_4: 福克斯广播公司FOX   http://www.fox.com/
        美国福克斯广播公司FOX(Fox Broadcasting Corporation),公司总部设在洛杉矶.1986年10月在美国新创办的商业电视网.1987年4月开始播出广播网联播节目.

    美国新闻网站NO_5: 华尔街日报   http://www.wsj.com/
        《华尔街日报》(The Wall Street Journal),其是美国新闻网站乃至全世界影响力最大,侧重金融、商业领域报导的日报,创办于1889年.日发行量达200万份.同时出版了亚洲版、欧洲版、网络版 .


    美国新闻网站NO_6: 时代周刊   http://www.time.com/
        美国《时代》周刊,创办于1923年3月.是美国新闻网站NO_1: 影响最大的新闻周刊,有世界"史库"之称.《时代》是美国新闻网站NO_1: 三大时事性周刊之一,内容广泛,对国际问题发表主张和对国际重大事件进行跟踪报道.

    美国新闻网站NO_7: 人物杂志   http://www.people.com/
        美国《人物》杂志1974年创刊,视角专注于美国新闻网站NO_1: 的名人和流行文化,是 Time Warner 媒体集团旗下杂志.《人物》是全球定位最精确又最有效的杂志之一,聚焦主流的风云人物.

    美国新闻网站NO_8: 娱乐周刊   http://www.ew.com/
        美国《娱乐周刊》是美国着名的王牌娱乐周刊,是一本图文兼备的大众电影杂志,刊载美国 娱乐(包括电影、录像、音乐会、唱片、磁带和图书等)方面的消息报道、产品介绍、采访记、评论 .

    美国新闻网站NO_9: 全国广播公司NBC   http://www.nbc.com/
        美国新闻网站NO_1: 全国广播公司NBC(National Broadcasting Company),是美国新闻网站NO_1: 一家主流广播电视网络公司,总部设在纽约的洛克菲勒中心,以孔雀为标记.目前是传媒联合大企业NBC Universal的一部分 .

    美国新闻网站NO_10: 读者文摘   http://www.rd.com/
        美国读者文摘(Reader's Digest),美国最有影响力的杂志,也是世界上最成功的杂志之一.《读者文摘》杂志的风格以温情和人性见长,他们"用持久的、人性的东西打败了时尚的、热点 .


    美国新闻网站NO_11: 花花公子杂志   http://www.playboy.com/
        美国《花花公子》杂志的官方网站,该杂志每月出版,内容除了女性裸照外,尚有文章介绍时装、饮食、体育、消费等.杂志上面的封面女郎,千娇百媚,风情万种.

    美国新闻网站NO_12: 名利场杂志   http://www.vanityfair.com/
        美国名利场杂志是美国着名生活杂志,1913年创刊.主要宣扬当代文化的刊物.内容包括政治、名人、图书、幽默、新闻、艺术和摄影等.名利场杂志已经成为公认的美国最重要的杂志之一.

    美国新闻网站NO_13: 科学在线   http://www.sciencemag.org/
        美国《科学》周刊由托马斯·爱迪生创办于1880年,自1900年起成为美国科学促进会的官方刊物.杂志的早期以报告物理类科学研究着称,有无线电报技术、新的化学元素...

    美国新闻网站NO_14: Market Watch   http://www.marketwatch.com/
        Market Watch是道琼斯旗下的新闻网站,主要关注重大新闻,服务的对象更多的是面向散户投资人.Market Watch还提供供个人投资者订阅的产品.

    转自:http://hi.baidu.com/100books/blog/item/d94ab73cb1f9a806baa167de.html

     

  • HTTP_POST_GET_区别详解

    2011-06-08 18:22:28

     

    HTTP POST GET 本质区别详解

    原理区别

       
    一般在浏览器中输入网址访问资源都是通过GET方式;在FORM提交中,可以通过Method指定提交方式为GET或者POST,默认为GET提交


    Http
    定义了与服务器交互的不同方法,最基本的方法有4种,分别是GETPOSTPUT
    DELETE

    URL
    全称是资源描述符,我们可以这样认为:一个URL地址,它用于描述一个网络上的资源,而HTTP中的 GETPOSTPUTDELETE就对应着对这个资源的查 ,改 ,增 ,删 4个操作。到这里,大家应该有个大概的了解了,GET一般用于获取/查询 资源信息,而POST一般用于更新 资源信息(个人认为这是GETPOST的本质区别,也是协议设计者的本意,其它区别都是具体表现形式的差异 )


      根据HTTP规范,GET用于信息获取,而且应该是安全的和幂等的


      1.所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,GET请求一般不应产生副作用。就是说,它仅仅是获取资源信息,就像数据库 查询一样,不会修改,增加数据,不会影响资源的状态。


      * 注意:这里安全的含义仅仅是指是非修改信息。


      2.幂等的意味着对同一URL的多个请求应该返回同样的结果。这里我再解释一下幂等 这个概念:


      幂等 idempotentidempotence)是一个数学或计算机学概念,常见于抽象代数中。

      幂等有以下几种定义:

      对于单目运算,如果一个运算对于在范围内的所有的一个数多次进行该运算所得的结果和进行一次该运算所得的结果是一样的,那么我们就称该运算是 幂等的。比如绝对值运算就是一个例子,在实数集中,有abs(a) = abs(abs(a))

      对于双目运算,则要求当参与运算的两个值是等值的情况下,如果满足运算结果与参与运算的两个值相等,则称该运算幂等,如求两个数的最大值的函 数,有在在实数集中幂等,即max(x,x)  =  x

    看完上述解释后,应该可以理解GET幂等的含义了。


      但在实际应用中,以上2条规定并没有这么严格。引用别人文章的例子:比如,新闻站点的头版不断更新。虽然第二次请求会返回不同的一批新闻,该 操作仍然被认为是安全的和幂等的,因为它总是返回当前的新闻。从根本上说,如果目标是当用户打开一个链接时,他可以确信从自身的角度来看没有改变资源即 可。


      根据HTTP规范,POST表示可能修改变服务器上的资源的请求 。继续引用上面的例子:还是新闻以网站为例,读者对新闻发表自己的评论应该通过POST实现,因为在评论提交后站点的资源已经不同了,或者说资源被修改 了。


      上面大概说了一下HTTP规范中,GETPOST的一些原理性的问题。但在实际的做的时候,很多人却没有按照HTTP规范去做,导致这个问 题的原因有很多,比如说:


      1.很多人贪方便,更新资源时用了GET,因为用POST必须要到FORM(表单),这样会麻烦一点。


      2.对资源的增,删,改,查操作,其实都可以通过GET/POST完成,不需要用到PUTDELETE


      3.另外一个是,早期的但是Web MVC框架设计者们并没有有意识地将URL当作抽象的资源来看待和设计 。还有一个较为严重的问题是传统的Web MVC框架基本上都只支持GETPOST两种HTTP方法,而不支持PUTDELETE方法。


      * 简单解释一下MVCMVC本来是存在于Desktop程序中的,M是指数据模型,V是指用户界面,C则是控制器。使用MVC的目的是将MV的实现代码 分离,从而使同一个程序可以使用不同的表现形式。


      以上3点典型地描述了老一套的风格(没有严格遵守HTTP规范),随着架构的发展,现在出现REST(Representational State Transfer),一套支持HTTP规范的新风格,这里不多说了,可以参考《RESTful Web Services》。


    表现形式区别


      
    搞清了两者的原理区别,我们再来看一下他们实际应用中的区别:


       
    为了理解两者在传输过程中的不同,我们先看一下HTTP协议的格式:


        HTTP
    请求:


    <request line>

    <headers>

    <blank line>

    <request-body>]

    HTTP请求中,第一行必须是一个请求行(request line),用来说明请求类型、要访问的资源以及使用的HTTP版本。紧接着是一个首部(header)小节,用来说明服务器要使用的附加信息。在首部之 后是一个空行,再此之后可以添加任意的其他数据[称之为主体(body]


    GET
    POST方法实例:

    GET /books/?sex=man&name=Professional HTTP/1.1
    Host: www.wrox.com
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
    Gecko/20050225 Firefox/1.0.1
    Connection: Keep-Alive

    POST / HTTP/1.1
    Host: www.wrox.com
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
    Gecko/20050225 Firefox/1.0.1
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 40
    Connection: Keep-Alive
        
    ----此处空一行----

    name=Professional%20Ajax&publisher=Wiley



    有了以上对HTTP请求的了解和示例,我们再来看两种提交方式的区别:


      
    1GET提交,请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,多个参数用&连接;例 如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0 %E5%A5%BD。如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,得出如: %E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII


      POST提交:把提交的数据放置在是HTTP包的包体中。上文示例中红色字体标明的就是实际的传输数据


        
    因此,GET提交的数据会在地址栏中显示出来,而POST提交,地址栏不会改变


        (2)
    传输数据的大小:首先声明:HTTP协议没有对传输的数据大小进行限制,HTTP协议规范也没有对URL长度进行限制。


    而在实际开发中存在的限制主要有:


    GET:
    特定浏览器和服务器对URL长度有限制,例如IEURL长度的限制是2083字节(2K+35)。对于其他浏览器,如 NetscapeFireFox等,理论上没有长度限制,其限制取决于操作系统的支持。


    因此对于GET提交时,传输数据就会受到URL长度的限制。


    POST:
    由于不是通过URL传值,理论上数据不受限。但实际各个WEB服务器会规定对post提交数据大小进行限制,ApacheIIS6 有各自的配置。


    (3)
    安全性:


    .POST
    的安全性要比GET的安全性高。注意:这里所说的安全性和上面GET提到的安全不是同个概念。上面安全的含义仅仅是不作数据修 改,而这里安全的含义是真正的Security的含义,比如:通过GET提交数据,用户名和密码将明文出现在URL上,因为(1)登录页面有可能被浏览器 缓存, (2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用GET提交数据还可能会造成Cross-site request forgery攻击


    4Http get,post,soap协议都是在http上运行的

    1
    get:请求参数是作为一个key/value对的序列(查询字符串)附加到URL上的

           
    查询字符串的长度受到web浏览器和web服务器的限制(如IE最多支持2048个字符),不适合传输大型数据集同时,它很不安全

    2
    post:请求参数是在http标题的一个不同部分(名为entity body)传输的,这一部分用来传输表单信息,因此必须将Content-type设置为:application/x-www-form- urlencodedpost设计用来支持web窗体上的用户字段,其参数也是作为key/value对传输。

         
    但是:它不支持复杂数据类型,因为post没有定义传输数据结构的语义和规则。

    3
    soap:是http post的一个专用版本,遵循一种特殊的xml消息格式

           Content-type
    设置为: text/xml   任何数据都可以xml


    HTTP响应

    1
    HTTP响应格式:



    <status line>
    <headers>
    <blank line>
    [<response-body>]

    在响应中唯一真正的区别在于第一行中用状态信息代替了请求信息。状态行(status line)通过提供一个状态码来说明所请求的资源情况。

         
    HTTP
    响应实例:


    HTTP/1.1 200 OK
    Date: Sat, 31 Dec 2005 23:59:59 GMT
    Content-Type: text/html;charset=ISO-8859-1
    Content-Length: 122
    html

    head

    titleWrox Homepage/title

    /head

    body

    !-- body goes here --

    /body

    /html

    2
    .最常用的状态码有:


    200 (OK): 找到了该资源,并且一切正常。

    304 (NOT MODIFIED): 该资源在上次请求之后没有任何修改。这通常用于浏览器的缓存机制。

    401 (UNAUTHORIZED): 客户端无权访问该资源。这通常会使得浏览器要求用户输入用户名和密码,以登录到服务器。

    403 (FORBIDDEN): 客户端未能获得授权。这通常是在401之后输入了不正确的用户名或密码。

    404 (NOT FOUND): 在指定的位置不存在所申请的资源。


    完整示例:


    例子:



    HTTP GET

    发送


    GET /DEMOWebServices2.8/Service.asmx/CancelOrder?UserID=string&PWD=string&OrderConfirmation=string HTTP/1.1
    Host: api.efxnow.com

    回复


    HTTP/1.1 200 OK
    Content-Type: text/xml; charset=utf-8
    Content-Length: length

    <?xml version="1.0" encoding="utf-8"?>
    <objPlaceOrderResponse xmlns="https://api.efxnow.com/webservices2.3">
    <Success>boolean</Success>
    <ErrorDescription>string</ErrorDescription>
    <ErrorNumber>int</ErrorNumber>
    <CustomerOrderReference>long</CustomerOrderReference>
    <OrderConfirmation>string</OrderConfirmation>
    <CustomerDealRef>string</CustomerDealRef>
    </objPlaceOrderResponse>



    HTTP POST

    发送


    POST /DEMOWebServices2.8/Service.asmx/CancelOrder HTTP/1.1
    Host: api.efxnow.com
    Content-Type: application/x-www-form-urlencoded
    Content-Length: length

    UserID=string&PWD=string&OrderConfirmation=string

    回复


    HTTP/1.1 200 OK
    Content-Type: text/xml; charset=utf-8
    Content-Length: length

    <?xml version="1.0" encoding="utf-8"?>
    <objPlaceOrderResponse xmlns="https://api.efxnow.com/webservices2.3">
    <Success>boolean</Success>
    <ErrorDescription>string</ErrorDescription>
    <ErrorNumber>int</ErrorNumber>
    <CustomerOrderReference>long</CustomerOrderReference>
    <OrderConfirmation>string</OrderConfirmation>
    <CustomerDealRef>string</CustomerDealRef>
    </objPlaceOrderResponse>



    SOAP 1.2

    发送


    POST /DEMOWebServices2.8/Service.asmx HTTP/1.1
    Host: api.efxnow.com
    Content-Type: application/soap+xml; charset=utf-8
    Content-Length: length

    <?xml version="1.0" encoding="utf-8"?>
    <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
    <soap12:Body>
        <CancelOrder xmlns="https://api.efxnow.com/webservices2.3">
          <UserID>string</UserID>
          <PWD>string</PWD>
          <OrderConfirmation>string</OrderConfirmation>
        </CancelOrder>
    </soap12:Body>
    </soap12:Envelope>

    回复


    HTTP/1.1 200 OK
    Content-Type: application/soap+xml; charset=utf-8
    Content-Length: length

    <?xml version="1.0" encoding="utf-8"?>
    <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
    <soap12:Body>
        <CancelOrderResponse xmlns="https://api.efxnow.com/webservices2.3">
          <CancelOrderResult>
            <Success>boolean</Success>
            <ErrorDescription>string</ErrorDescription>
            <ErrorNumber>int</ErrorNumber>
            <CustomerOrderReference>long</CustomerOrderReference>
            <OrderConfirmation>string</OrderConfirmation>
            <CustomerDealRef>string</CustomerDealRef>
          </CancelOrderResult>
        </CancelOrderResponse>
    </soap12:Body>
    </soap12:Envelope>

    出处:http://blog.csdn.net/gideal_wang/archive/2009/07/02/4316691.aspx

     

     

    附件中有另一文章论述:

    http_get_post.rar(981 KB)

  • 06_redis学习笔记整理

    2011-04-15 00:04:46

     

    redis学习笔记整理

     

    一、. redis 环境搭建. 2

    二、. redis学习笔记之数据类型. 3

    三、. redis学习笔记之排序. 11

    四、. redis学习笔记之事务. 16

    五、. redis学习笔记之pipeline 20

    六、. redis学习笔记之发布订阅. 23

    七、. redis学习笔记之持久化. 28

    八、. redis学习笔记之主从复制. 30

    九、. redis学习笔记之虚拟内存. 31

     

    word下载:   Redis学习笔记整理.rar(28.9 KB)

  • 05_LAMP+Java+Memcache环境搭建

    2011-04-14 23:30:07

    LAMP+Java+Memcache环境搭建 By Morgan Zhengrf
    目 录
    一、Linux OS版本 .................................................................................................................... 1
    二、所需软件 ........................................................................................................................... 1
    三、安装apache ...................................................................................................................... 1
    四、安装GD、libxml、freetype、libmcrypt、openssl、jpeg、libpng和zlib ..................... 1
    五、安装MySQL ....................................................................................................................... 3
    六、安装PHP ........................................................................................................................... 4
    七、安装及配置JDK ................................................................................................................ 5
    八、安装及配置PHP扩展JAVA ............................................................................................. 5
    九、安装及配置PHP扩展memcache .................................................................................... 6
    1、安装libevent ................................................................................................................... 6
    2、安装memcached ............................................................................................................ 6
    3、安装及配置memcache .................................................................................................. 7
    1
    一、Linux OS版本
    CentOS Release 5.3 (Final)
    二、所需软件
    Apache: httpd-2.2.9.tar.gz GD2: gd-2.0.35.tar.gz Libxml: libxml2-2.6.32.tar.gz Freetype: freetype-2.3.5.tar.gz Jpeg: jpegsrc.v6b.tar.gz Libpng: libpng-1.2.29.tar.gz Zlib: zlib-1.2.3.tar.gz MySQL: mysql-5. 0.67.tar.gz PHP: php-5.2.6.tar.gz (不安装ZendOptimizer:ZendOptimizer-3.3.0a-linux-glibc21-i386.tar.gz) JDK: jdk-6u22-linux-i586.bin Php-java-bridge: php-java-bridge_4.0.7.tar.gz
    (http://ftp.heanet.ie/disk1/sourceforge/p/project/ph/php-java-bridge/OldFiles/)
    Libevent: libevent-1.4.9-stable.tar.gz(http://monkey.org/~provos/libevent/)
    Memcached: memcached-1.2.7.tar.gz(http://pecl.php.net/package/memcache)
    Memcache: memcache-2.2.5.tgz(http://www.danga.com/memcached/)
    三、安装apache
    # tar xzvf httpd-2.2.9.tar.gz # cd http-2.2.9 # ./configure --prefix=/usr/local/apache --enable-rewrite=shared --enable-proxy=shared --enable-proxy-ajp=shared --enable-mods-shared=all --enable-so --enable-dav # make # make install
    四、安装GD、libxml、freetype、libmcrypt、openssl、jpeg、libpng和zlib
    tar xzvf gd-2.0.35.tar.gz cd gd/2.0.35/ ./configure --prefix=/usr/local/gd2 make make install tar xzvf libxml2-2.6.32.tar.gz
    2
    cd libxml2-2.6.32 ./configure --prefix=/usr/local/libxml2 make make install tar xzvf freetype-2.3.5.tar.gz cd freetype-2.3.5 ./configure --prefix=/usr/local/freetpye make make install tar xzvf libmcrypt-2.5.8.tar.gz cd libmcrypt-2.5.8 ./configure --prefix=/usr/local/libmcrypt make make install tar xzvf openssl-0.9.8g.tar.gz cd openssl-0.9.8g ./config --prefix=/usr/local/openssl make make install mkdir -p /usr/local/jpeg6/include mkdir /usr/local/jpeg6/lib mkdir /usr/local/jpeg6/bin mkdir -p /usr/local/jpeg6/man/man1 tar xzvf jpegsrc.v6b.tar.gz cd jpeg-6b ./configure --prefix=/usr/local/jpeg6 make make install tar xzvf libpng-1.2.29.tar.gz cd libpng-1.2.29 ./configure --prefix=/usr/local/libpng make make install tar xzvf zlib-1.2.3.tar.gz cd zlib-1.2.3 ./configure --prefix=/usr/local/zlib make make install
    3
    五、安装MySQL
    # groupadd mysql # useradd –g mysql mysql # mkdir –p /usr/local/mysql/data # chown –R root /usr/local/mysql # chgrp –R mysql /usr/local/mysql # chown –R mysql /usr/local/mysql/data # chmod –R 755 /usr/local/mysql/data # chown –R mysql:mysql /usr/local/mysql/data ########################### ## Before installing mysql, please ensure that the gcc-c++ package is installed. ## If not, please intall gcc-c++ package first. ## In the CentOS Linux, you can use the command " yum install gcc-c++ " to install. ########################## ########################## ## MySQL事件调度器是在MySQL 5.1中新增的另一个特色功能, ## 可以作为定时任务调度器, ## 取代部分原先只能用操作系统任务调度器才能完成的定时功能。 ## 特点:MySQL的事件调度器则可以实现每秒钟执行一个任务。 ########################## # tar xzvf mysql-5.0.67.tar.gz # cd mysql-5.0.67. # ./configure --prefix=/usr/local/mysql --with-charset=utf8 --with-extra-charsets=all --localstatedir=/usr/local/mysql/data # make # make install ## generate the mysql's system database # /usr/local/mysql/bin/mysql_install_db --user=mysql& ## start MySQL Server # /usr/local/mysql/bin/mysqld_safe --user=mysql& ## attention: ## stop MySQL Server:/usr/local/mysql/share/mysql/mysql.server stop ## set root’s password # /usr/local/mysql/bin/mysqladmin -u root password "123456"
    4
    # mysql -uroot -p mysql> grant all privileges on *.* to 'root'@'%' identified by '123456'; mysql> flush privileges; mysql> exit
    六、安装PHP
    # mkdir -p /usr/local/php/etc # tar jxvf php-5.2.6.tar.gz # cd php-5.2.6 # ./configure --prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc --with-apxs2=/usr/local/apache/bin/apxs --with-mysql=/usr/local/mysql --with-mysqli=/usr/local/mysql/bin/mysql_config --with-gd=/usr/local/gd2 --with-libxml-dir=/usr/local/libxml2 --with-freetype-dir=/usr/local/freetype --with-mcrypt=/usr/local/libmcrypt --with-openssl=/usr/local/openssl --with-jpeg-dir=/usr/local/jpeg6 --with-png-dir=/usr/local/libpng --with-zlib --with-zlib-dir=/usr/local/zlib --with-gettext --with-curl --with-ldap --with-iconv --enable-gd-native-ttf --enable-ftp --enable-mbstring --enable-embed --enable-calendar --enable-magic-quotes --enable-sockets --enable-sysvsem --enable-sysvshm --enable-sysvmsg --enable-pcntl --enable-shmop # make # make install # cp php.ini-dist /usr/local/php/etc/php.ini 修改apache的httpd.conf文件,在文件的最后添加如下信息: # vi /usr/local/apache/conf/httpd.conf AddType application/x-httpd-php .php 在httpd.conf文件中增加 index.php <IfModule dir_module> DirectoryIndex index.php index.html index.htm </IfModule> 重启apache # /usr/local/apache/bin/apachectl restart
    5
    七、安装及配置JDK
    # chmod a+x jdk-6u22-linux-i586.bin # ./ jdk-6u22-linux-i586.bin # mv jdk1.6.0_22 /usr/local/java # vi /etc/profile 在profile文件的末尾添加如下内容 export JAVA_HOME=/usr/local/java export JRE_HOME=$JAVA_HOME/jre export CLASSPATH=./:$JAVA_HOME/lib:$JAVA_HOME/jre/lib export PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin:./ # source /etc/profile 重新打开一个终端,执行如下命令: # java –version java version "1.6.0_01" Java(TM) SE Runtime Environment (build 1.6.0_01-b06) Java HotSpot(TM) Client VM (build 1.6.0_01-b06, mixed mode) 说明JDK安装成功。
    八、安装及配置PHP扩展JAVA
    # tar xzvf php-java-bridge_4.0.7.tar.gz # cd php-java-bridge-4.0.7 # /usr/local/php/bin/phpize Configuring for: PHP Api Version: 20041225 Zend Module Api No: 20060613 Zend Extension Api No: 220060519 # ./configure --with-java=/usr/local/java,/usr/local/java/jre # make # make install Installing shared extensions: /usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/ # cd /usr/local/php/lib/php/extensions/no-debug-non-zts-20060613 # ln -s java.so libphp_java.so # mv * ../ 配置 php.ini 文件 # vi php.ini 加入 java.class.path=/usr/local/php/lib/php/extensions
    6
    java.home =/usr/local/java java.library =/usr/local/java/lib java.library.path =/usr/local/php/lib/php/extensions extension_dir = /usr/local/php/lib/php/extensions extension=java.so 6、重启apache # /usr/local/apache/bin/apachectl restart //usr/local/java/bin/java -Djava.library.path=/usr/local/php/lib/php/extensions -Djava.class.path=/usr/local/php/lib/php/extensions/JavaBridge.jar -Djava.awt.headless=true -Dphp.java.bridge.base=/usr/local/php/lib/php/extensions php.java.bridge.Standalone LOCAL:@java-bridge-61a3 1 若重启完apache,没有出现开启 JavaBridge.jar的服务程序,请手动重启: # java -jar /usr/local/php/lib/php/extensions/JavaBridge.jar SERVLET:8080 3 /log/JavaBridge.log 测试php-java-bridge是否安装成功: test.php文件内容: <?php $system=new Java("java.lang.System"); print 'Java version = '.$System->getProperty('java.version'); echo "the process closes...."; ?> 然后,执行php命令: # /usr/local/php/bin/php test.php Java version=1.6.0_01 <br> 说明安装成功!
    九、安装及配置PHP扩展memcache
    1、安装libevent
    # tar xzvf libevent-1.4.9-stable.tar.gz # cd libevent-1.4.9-stable # ./configure --prefix=/usr --with-php-config=/usr/local/php/bin/php-config # make # make install # ls /usr/lib/ |grep libevent
    2、安装memcached
    # tar xzvf memcached-1.2.7.tar.gz # cd memcached-1.2.7 #./configure --with-libevent=/usr --with-php-config=/usr/local/php/bin/php-config
    7
    # make # make install # ls /usr/local/bin/memcached
    3、安装及配置memcache
    # tar xzvf memcache-2.2.5.tgz # cd memcache-2.2.5 # /usr/local/php/bin/phpize #./configure --enable-memcache --with-php-config=/usr/local/php/bin/php-config --with-zlib-dir # make # make install Installing shared extensions: /usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/ # cd /usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/ # mv * ../ # vi /usr/local/php/etc/php.ini 在php.ini文件的末尾添加如下内容: [Memcache] extension_dir=/usr/local/php/lib/php/extensions extension=memcache.so 重新启动apache服务: # /usr/local/apache/bin/apachectl restart 启动memcache服务: # /usr/local/bin/memcached -d -m 10 -u root -l 127.0.0.1 -p 12006 -c 256 -P /tmp/memcached.pid
    【相关内容链接:http://morgan363.javaeye.com】

    pdf下载: LAMP+Java+Memcache环境搭建.rar(307 KB)

     

  • 04_Ubuntu10下Memcached1.4.5安装

    2011-04-14 23:23:56

    Ubuntu10下Memcached1.4.5安装

     

    Memcached是高性能的分布式内存缓存服务器。一般通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。Memcached常用于存放对数据库操作的结果集以及session信息。memcached是居家旅行必备之佳品,所以也要装备一下。

    安装libevent2.0.10
    这个是装Memcached1.4.5的前提条件。Ubuntu10已经有libevent1.4的了,可是版本过低。 下载地址: http://monkey.org/~provos/libevent/

    下载: libevent-2.0.10.rar(775 KB)

    改名-解压-编译安装

    Java代码  
    1. mv libevent-2.0.10-stable.tar.gz libevent2010.tar.gz   
    2. tar –zxvf libevent2010.tar.gz    
    3. cd libevent-2.0.10-stable   
    4. ./configure -prefix=/usr/local   
    5. make   
    6. make install  


    安装完后可以查看下/usr/local/lib是否有libevent等文件
    Java代码  
    1. ls -al /usr/local/lib|grep libevent   



    安装memcache1.4.5
    下载地址: http://code.google.com/p/memcached/downloads/list
    下载: memcached-1.4.5.rar(296 KB)

    Java代码  
    1. tar –zxvf memcached-1.4.5.tar.gz   
    2. cd memcached-1.4.5  
    3. ./configure   
    4. make   
    5. sudo make install  


    查看安装结果
    Java代码
    1. ls -al /usr/local/bin/memcached  


    启动memcached
    Java代码  
    1. /usr/local/bin/memcached -d -m 128 -u root -p 11211 –P /tmp/memcached.pid  

    参数: -d 启动守护进程(后台运行)
          -m 分配给memcache使用的内存,单位是MB
          -u 运行memcached的用户
          -l 监听的服务器IP
          -p 监听的服务器端口,默认是11211
          -P(大写) 保存Memcache的pid文件,后面跟路径

    启动Memcached报错
    guangbo@guangbo-laptop:/usr/local/bin$ /usr/local/bin/memcached -d -m 128 -u root -p 11211 -P /tmp/memcached.pid
    /usr/local/bin/memcached: error while loading shared libraries: libevent-2.0.so.5: cannot open shared object file: No such file or directory
    /usr/local/lib下安装有libevent2.0相关的包。

    解决方法:建立软连接
    Java代码  
    1. sudo ln -s /usr/local/lib/libevent-2.0.so.5 /usr/lib/libevent-2.0.so.5  


    查看是否建立成功
    guangbo@guangbo-laptop:/usr/local/lib$
    Java代码  
    1. ls -al /usr/lib|grep libevent  


    telnet测试memcached
    Java代码  
    1. telnet 192.168.1.2 11211  

    Trying 192.168.1.2...
    Connected to 192.168.1.2.
    Escape character is '^]'

    查看版本
    Java代码  
    1. version  

    VERSION 1.4.5

    stats查看memcached的详细信息
    stats
    Java代码  
    1.   
    STAT pid 9192
    STAT uptime 352
    STAT time 1300907990
    STAT version 1.4.5
    STAT pointer_size 32


    Ok,安装memcached1.4.5成功。这次比较顺利,没有搞nginx那么多错。
     


    原文:
    http://www.iteye.com/topic/974445

     

     

     


     

  • 03_Java环境下Memcached应用详解

    2011-04-14 23:21:59

    Java环境下Memcached应用详解

     

    这里将介绍Java环境下Memcached应用,Memcached主要是集群环境下的缓存解决方案,希望本文对大家有所帮助。

      本文将对在Java环境下Memcached应用进行详细介绍。Memcached主要是集群环境下的缓存解决方案,可以运行在Java或者.NET平台上,这里我们主要讲的是Windows下的Memcached应用。

      这些天在设计SNA的架构,接触了一些远程缓存、集群、session复制等的东西,以前做企业应用的时候感觉作用不大,现在设计面对internet的系统架构时就非常有用了,而且在调试后看到压力测试的情况还是比较好的。

      在缓存的选择上有过很多的思考,虽然说memcached结合java在序列化上性能不怎么样,不过也没有更好的集群环境下的缓存解决方案了,就选择了memcached。本来计划等公司买的服务器到位装个linux再来研究memcached,但这两天在找到了一个windows下的Memcached版本,就动手开始调整现有的框架了。

      Windows下的Server端很简单,不用安装,双击运行后默认服务端口是11211,没有试着去更改端口,因为反正以后会用Unix版本,到时再记录安装步骤。下载客户端的JavaAPI包,接口非常简单,参考API手册上就有现成的例子。

      目标,对旧框架缓存部分进行改造:

      1、缓存工具类

      2、hibernate的provider

      3、用缓存实现session机制

      今天先研究研究缓存工具类的改造,在旧框架中部分函数用了ehcache对执行结果进行了缓存处理,现在目标是提供一个缓存工具类,在配置文件中配置使用哪种缓存(memcached或ehcached),使其它程序对具体的缓存不依赖,同时使用AOP方式来对方法执行结果进行缓存。

      首先是工具类的实现:

      在Spring中配置

      Java代码


    <bean id="cacheManager"     
    class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">      
    <property name="configLocation">      
    <value>classpath:ehcache.xmlvalue>      
    property>      
    bean>      
     
    <bean id="localCache"     
    class="org.springframework.cache.ehcache.EhCacheFactoryBean">      
    <property name="cacheManager" ref="cacheManager" />      
    <property name="cacheName"     
    value="×××.cache.LOCAL_CACHE" />      
    bean>      
     
    <bean id="cacheService"     
    class="×××.core.cache.CacheService" init-method="init" destroy-method="destory">      
    <property name="cacheServerList" value="${cache.servers}"/>      
    <property name="cacheServerWeights" value="${cache.cacheServerWeights}"/>      
    <property name="cacheCluster" value="${cache.cluster}"/>      
    <property name="localCache" ref="localCache"/>      
    bean>     
      
    <bean id="cacheManager"  
    class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">  
    <property name="configLocation">  
    <value>classpath:ehcache.xmlvalue>  
    property>  
    bean>  
    <bean id="localCache"  
    class="org.springframework.cache.ehcache.EhCacheFactoryBean">  
    <property name="cacheManager" ref="cacheManager" />  
    <property name="cacheName"  
    value="×××.cache.LOCAL_CACHE" />  
    bean>  
     
    <bean id="cacheService"  
    class="×××.core.cache.CacheService" init-method="init" destroy-method="destory">  
    <property name="cacheServerList" value="${cache.servers}"/>  
    <property name="cacheServerWeights" value="${cache.cacheServerWeights}"/>  
    <property name="cacheCluster" value="${cache.cluster}"/>  
    <property name="localCache" ref="localCache"/>  
    bean> 

      在properties文件中配置${cache.servers} ${cache.cacheServerWeights} ${cache.cluster}

      具体工具类的代码

      Java代码


    /**    
    * @author Marc    
    *     
    */     
    public class CacheService {      
    private Log logger = LogFactory.getLog(getClass());      
    private Cache localCache;      
    String cacheServerList;      
    String cacheServerWeights;      
    boolean cacheCluster = false;      
    int initialConnections = 10;      
    int minSpareConnections = 5;      
    int maxSpareConnections = 50;      
    long maxIdleTime = 1000 * 60 * 30; // 30 minutes     
    long maxBusyTime = 1000 * 60 * 5; // 5 minutes     
    long maintThreadSleep = 1000 * 5; // 5 seconds     
    int socketTimeOut = 1000 * 3; // 3 seconds to block on reads     
    int socketConnectTO = 1000 * 3; // 3 seconds to block on initial     
    // connections. If 0, then will use blocking     
    // connect (default)     
    boolean failover = false; // turn off auto-failover in event of server     
    // down     
    boolean nagleAlg = false; // turn off Nagle's algorithm on all sockets in     
    // pool     
    MemCachedClient mc;      
    public CacheService(){      
    mc = new MemCachedClient();      
    mc.setCompressEnable(false);      
    }      
    /**    
    * 放入    
    *     
    */     
    public void put(String keyObject obj) {      
    Assert.hasText(key);      
    Assert.notNull(obj);      
    Assert.notNull(localCache);      
    if (this.cacheCluster) {      
    mc.set(key, obj);      
    } else {      
    Element element = new Element(key, (Serializable) obj);      
    localCache.put(element);      
    }      
    }      
    /**    
    * 删除     
    */     
    public void remove(String key){      
    Assert.hasText(key);      
    Assert.notNull(localCache);      
    if (this.cacheCluster) {      
    mc.delete(key);      
    }else{      
    localCache.remove(key);      
    }      
    }      
    /**    
    * 得到    
    */     
    public Object get(String key) {      
    Assert.hasText(key);      
    Assert.notNull(localCache);      
    Object rt = null;      
    if (this.cacheCluster) {      
    rt = mc.get(key);      
    } else {      
    Element element = null;      
    try {      
    element = localCache.get(key);      
    } catch (CacheException cacheException) {      
    throw new DataRetrievalFailureException("Cache failure: "     
    + cacheException.getMessage());      
    }      
    if(element != null)      
    rt = element.getValue();      
    }      
    return rt;      
    }      
    /**    
    * 判断是否存在    
    *     
    */     
    public boolean exist(String key){      
    Assert.hasText(key);      
    Assert.notNull(localCache);      
    if (this.cacheCluster) {      
    return mc.keyExists(key);      
    }else{      
    return this.localCache.isKeyInCache(key);      
    }      
    }      
    private void init() {      
    if (this.cacheCluster) {      
    String[] serverlist = cacheServerList.split(",");      
    Integer[] weights = this.split(cacheServerWeights);      
    // initialize the pool for memcache servers     
    SockIOPool pool = SockIOPool.getInstance();      
    pool.setServers(serverlist);      
    pool.setWeights(weights);      
    pool.setInitConn(initialConnections);      
    pool.setMinConn(minSpareConnections);      
    pool.setMaxConn(maxSpareConnections);      
    pool.setMaxIdle(maxIdleTime);      
    pool.setMaxBusyTime(maxBusyTime);      
    pool.setMaintSleep(maintThreadSleep);      
    pool.setSocketTO(socketTimeOut);      
    pool.setSocketConnectTO(socketConnectTO);      
    pool.setNagle(nagleAlg);      
    pool.setHashingAlg(SockIOPool.NEW_COMPAT_HASH);      
    pool.initialize();      
    logger.info("初始化memcached pool!");      
    }      
    }      
     
    private void destory() {      
    if (this.cacheCluster) {      
    SockIOPool.getInstance().shutDown();      
    }      
    }      
    }     
    /**   
    * @author Marc   
    *    
    */  
    public class CacheService {   
    private Log logger = LogFactory.getLog(getClass());   
    private Cache localCache;   
    String cacheServerList;   
    String cacheServerWeights;   
    boolean cacheCluster = false;   
    int initialConnections = 10;   
    int minSpareConnections = 5;   
    int maxSpareConnections = 50;   
    long maxIdleTime = 1000 * 60 * 30; // 30 minutes   
    long maxBusyTime = 1000 * 60 * 5; // 5 minutes   
    long maintThreadSleep = 1000 * 5; // 5 seconds   
    int socketTimeOut = 1000 * 3; // 3 seconds to block on reads   
    int socketConnectTO = 1000 * 3; // 3 seconds to block on initial   
    // connections. If 0, then will use blocking   
    // connect (default)   
    boolean failover = false; // turn off auto-failover in event of server   
    // down   
    boolean nagleAlg = false; // turn off Nagle's algorithm on all sockets in   
    // pool   
    MemCachedClient mc;   
    public CacheService(){   
    mc = new MemCachedClient();   
    mc.setCompressEnable(false);   
    }   
    /**   
    * 放入   
    *    
    */  
    public void put(String key, Object obj) {   
    Assert.hasText(key);   
    Assert.notNull(obj);   
    Assert.notNull(localCache);   
    if (this.cacheCluster) {   
    mc.set(key, obj);   
    } else {   
    Element element = new Element(key, (Serializable) obj);   
    localCache.put(element);   
    }   
    }   
    /**   
    * 删除    
    */  
    public void remove(String key){   
    Assert.hasText(key);   
    Assert.notNull(localCache);   
    if (this.cacheCluster) {   
    mc.delete(key);   
    }else{   
    localCache.remove(key);   
    }   
    }   
    /**   
    * 得到   
    */  
    public Object get(String key) {   
    Assert.hasText(key);   
    Assert.notNull(localCache);   
    Object rt = null;   
    if (this.cacheCluster) {   
    rt = mc.get(key);   
    } else {   
    Element element = null;   
    try {   
    element = localCache.get(key);   
    } catch (CacheException cacheException) {   
    throw new DataRetrievalFailureException("Cache failure: "  
    + cacheException.getMessage());   
    }   
    if(element != null)   
    rt = element.getValue();   
    }   
    return rt;   
    }   
    /**   
    * 判断是否存在   
    *    
    */  
    public boolean exist(String key){   
    Assert.hasText(key);   
    Assert.notNull(localCache);   
    if (this.cacheCluster) {   
    return mc.keyExists(key);   
    }else{   
    return this.localCache.isKeyInCache(key);   
    }   
    }   
    private void init() {   
    if (this.cacheCluster) {   
    String[] serverlist = cacheServerList.split(",");   
    Integer[] weights = this.split(cacheServerWeights);   
    // initialize the pool for memcache servers   
    SockIOPool pool = SockIOPool.getInstance();   
    pool.setServers(serverlist);   
    pool.setWeights(weights);   
    pool.setInitConn(initialConnections);   
    pool.setMinConn(minSpareConnections);   
    pool.setMaxConn(maxSpareConnections);   
    pool.setMaxIdle(maxIdleTime);   
    pool.setMaxBusyTime(maxBusyTime);   
    pool.setMaintSleep(maintThreadSleep);   
    pool.setSocketTO(socketTimeOut);   
    pool.setSocketConnectTO(socketConnectTO);   
    pool.setNagle(nagleAlg);   
    pool.setHashingAlg(SockIOPool.NEW_COMPAT_HASH);   
    pool.initialize();   
    logger.info("初始化memcachedpool!");   
    }   
    }   
    private void destory() {   
    if (this.cacheCluster) {   
    SockIOPool.getInstance().shutDown();   
    }   
    }   

      然后实现函数的AOP拦截类,用来在函数执行前返回缓存内容

      Java代码


    public class CachingInterceptor implements MethodInterceptor {      
     
    private CacheService cacheService;      
    private String cacheKey;      
     
    public void setCacheKey(String cacheKey) {      
    this.cacheKey = cacheKey;      
    }      
     
    public void setCacheService(CacheService cacheService) {      
    this.cacheService = cacheService;      
    }      
     
    public Object invoke(MethodInvocation invocation) throws Throwable {      
    Object result = cacheService.get(cacheKey);      
    //如果函数返回结果不在Cache中,执行函数并将结果放入Cache     
    if (result == null) {      
    result = invocation.proceed();      
    cacheService.put(cacheKey,result);      
    }      
    return result;      
    }      
    }     
    public class CachingInterceptor implements MethodInterceptor {   
     
    private CacheService cacheService;   
    private String cacheKey;   
     
    public void setCacheKey(String cacheKey) {   
    this.cacheKey = cacheKey;   
    }   
     
    public void setCacheService(CacheService cacheService) {   
    this.cacheService = cacheService;   
    }   
     
    public Object invoke(MethodInvocation invocation) throws Throwable {   
    Object result = cacheService.get(cacheKey);   
    //如果函数返回结果不在Cache中,执行函数并将结果放入Cache   
    if (result == null) {   
    result = invocation.proceed();   
    cacheService.put(cacheKey,result);   
    }   
    return result;   
    }   

      Spring的AOP配置如下:

      Java代码


    <aop:config proxy-target-class="true">      
    <aop:advisor      
    pointcut="execution(* ×××.PoiService.getOne(..))"     
    advice-ref="PoiServiceCachingAdvice" />      
    aop:config>      
     
    <bean id="BasPoiServiceCachingAdvice"     
    class="×××.core.cache.CachingInterceptor">      
    <property name="cacheKey" value="PoiService" />      
    <property name="cacheService" ref="cacheService" />      
    bean>  

     

    转自:http://webservices.ctocio.com.cn/java/270/9189770.shtml

  • 02_搭建java socket服务器+mysql+tomcat+memcached环境

    2011-04-14 23:20:39

    搭建java socket服务器+mysql+tomcat+memcached环境

     

    以Redhat CentOS 5.0系统为例

    1.安装jdk:卸载过时jdk1.4:
     rpm -qa | grep gcj      ← 确认gcj的版本号
     java-1.4.2-gcj-compat-1.4.2.0-27jpp   ← 根据版本号卸载gcj

     yum -y remove java-1.4.2-gcj-compat   ← 卸载gcj
     

    下载jdk1.6

    wget http://cds.sun.com/is-bin/INTERSHOP.enfinity/WFS/CDS-CDS_Developer-Site/en_US/-/USD/VerifyItem-Start/jdk-6u18-linux-x64-rpm.bin?BundledLineItemUUID=JGlIBe.mxBkAAAEn1_NilR04&OrderID=TK1IBe.mMWcAAAEnzPNilR04&ProductID=p_9IBe.pFJcAAAElWitRSbJV&FileName=/jdk-6u18-linux-x64-rpm.bin

     付给权限:chmod +x filename
     安装:./filename


     getconf LONG_BIT 查看电脑位数
     ls -l $(which java)  查看jdk默认安装路径


     查看jdk安装路径:ls -l $(which java)
     配置jdk:vi /etc/profile

    在末尾加上

    export JAVA_HOME=/usr/java/jdk1.6.0_10/  (jdk实际安装目录)
    export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
    export PATH=$PATH:$JAVA_HOME/bin

    2.安装tomcat:下载tomcat,可以太平洋获取地址 用wget命令下载

    tar -xzvf tomcatFileName

    mv tomcat  /usr/local/

    配置tomcat:vi /etc/profile

    export CATALINA_BASE=/usr/local/tomcat
    export CATALINA_HOME=/usr/local/tomcat
    export CATALINA_TMPDIR=/usr/local/tomcat/temp
    export JRE_HOME=/usr/java/jdk1.6.0_10 (jdk实际安装目录)

    source  /etc/profile 重新加载配置文件

    修改端口:
    /etc/init.d/iptables status
    /sbin/iptables -I INPUT -p tcp --dport 843 -j ACCEPT
    /sbin/iptables -I INPUT -p tcp --dport 8080 -j ACCEPT
    /etc/rc.d/init.d/iptables save
    service iptables restart

    tomcat优化:tomcat:
     http://www.javaeye.com/topic/463830
     
     
     tomcat原来参数
     <Connector executor="tomcatThreadPool"
                   port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
     
     tomcat参数优化:
     <Connector port="8080" maxHttpHeaderSize="8192"
      useBodyEncodingForURI="true"  maxThreads="1000"  minSpareThreads="25"
      maxSpareThreads="75"   enableLookups="false" redirectPort="8443"
      acceptCount="100"  compression="on" compressionMinSize="2048"  
      compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"
            connectionTimeout="20000" disableUploadTimeout="true" URIEncoding="UTF-8"/>

    tomcat默认可以使用的内存为128MB,Windows下,在文件{tomcat_home}/bin/catalina.bat,Unix下,在文件$CATALINA_HOME/bin/catalina.sh的前面,增加如下设置:
    JAVA_OPTS='$JAVA_OPTS -Xms[初始化内存大小] -Xmx[可以使用的最大内存]

    设置环境变量:export JAVA_OPTS=”$JAVA_OPTS -Xms[初始化内存大小] -Xmx[可以使用的最大内存]”
    一般说来,你应该使用物理内存的 80% 作为堆大小。如果本机上有Apache服务器,可以先折算Apache需要的内存,然后修改堆大小。
    建议设置为70%;建议设置[[初始化内存大小]等于[可以使用的最大内存],这样可以减少平凡分配堆而降低性能。

    JAVA_OPTS="-server -Xms1024M -Xmx1024M -Xss128k -Xmn500m -XX:MaxPermSize=64M -XX:+AggressiveOpts -XX:+UseConcMarkSweepGC  -XX:+UseBiasedLocking"

    3.mysql安装:

      以下载MySQL-community-5.0.45-0.rhel5.src.rpm为例:

     检查是否存在mysql用户可用如下命令:

           #grep "^mysql: " /etc/passwd  -i

    如果没有,则需要创建该用户:

           #useradd  mysql  /opt/mysql

    下载MySQL-community-5.0.45-0.rhel5.src.rpm

     安装:#rpm -ivh MySQL-community-5.0.45-0.rhel5.src.rpm

    在/usr/src/redhat/SOURCES/目录中,找到mysql-5.0.45a.tar.gz,然后解包:

           #tar -zxvf mysql-5.0.45.tar.gz

        解开该压缩包,生成mysql-5.0.45目录,在该目录下能够看到bin和scripts子目录。bin目录包含客户程序和服务器,scripts目录

        包含mysql_install_db脚本,用于初始化服务器的存取权限。
    将MySQL安装到/opt/mysql目录下:

            #./configure  --prefix  /opt/mysql

    最后,执行以下命令编译并安装MySQL:

            #make

            #make  check

            #su  mysql

            #make  install

            #cd  scripts

            #./mysql_install_db

     测试安装是否成功可以用以下命令,看到mysql>提示符后,则表明MySQLP安装成功。

            #/opt/mysql/bin/mysql_safe  &

            #/opt/mysql/bin/mysql  -u  root

      用命令行方式启动停止MySQL服务

        在RHEL5中,MySQL可能是系统自带的,也可能是用户根据需要编译安装的,因此,在通过命令行方式启动和停止MySQL服务时,

        使用的命令也有所不同。

        1如果是RHEL5自带的MySQL,则使用如下命令:

            #service mysqld start     =#/etc/init.d/mysqld start     //启动MySQL服务;

            #service mysqld stop     =#/etc/init.d/mysqld stop     //停止MySQL服务;

            #service mysqld restart   =/etc/init.d/mysqld restart   //重新启动MySQL服务;

            #mysqladmin status                                    //检查MySQL服务状态;

       2如果不是系统自带的MySQL服务,则使用如下方法:

         在默认的情况下,没有设置为系统服务的形式,需要通过以下方式启动,&表示后台运行:

            #/opt/mysql/bin/mysqld_safe  &

         类似地,停止MySQL服务时应使用如下命令:

            #/opt/mysql  /bin/mysqladmin  shutdown

      自动启动MySQL服务

        1对于系统自带的MySQL服务:

            #ntsysv

        2对于手工安装的MySQL服务

            通过在/etc/rc.local文件的尾部追加启动MySQL的命令来实现自动启动。

        3图形界面下-系统-管理-服务器设置-服务-服务配置-mysqld-选中复选框,同样可以实现系统启动自运行mysqld。

     <附:mysql编码问题>http://tdcq.javaeye.com/blog/363955

    4.memcached安装:下载libevent和memcached

    http://www.monkey.org/~provos/libevent/

    http://memcached.org/
    解包
    cd /usr/local
    tar -xzvf libevent-1.4.tar.gz
    tar -xzvf memcached-1.2.5.tar.gz

    处理libevent
    cd /usr/local/libevent-1.2/
    ./configure --prefix=/usr/local/libevent
    make
    make install

    处理memcached
    cd /usr/local/memcached-1.2.5/
    ./configure --prefix=/usr/local/memcached --with-libevent=/usr/local/libevent
    make
    make install

    32位机
    ln -s /usr/local/libevent/lib/libevent-1.4.so.1 /usr/lib
    ln -s /usr/local/libevent/lib/libevent-1.4.so.2 /usr/lib


    64位机
    ln -s /usr/local/libevent/lib/libevent-1.2.so.1 /usr/lib64

    启动命令:memcached -p 11211 -d -u root -m 512 -c 1024 -vvv

     

    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/li610925548/archive/2010/09/02/5859108.aspx
  • 01_Memcached学习笔记——windows上初步使用

    2011-04-14 23:17:15

    http://navylee.iteye.com/blog/753234

    此页有程序下载

     

    最近一直在做一个项目的前期设计工作,考虑到后期系统的扩展和性能问题也找了很多解决方法,有一个就是用到了数据库的缓存工具memcached(当然该工具并不仅仅局限于数据库的缓存)。先简单的介绍下什么是memcached。

        Memcached是高性能的,分布式的内存对象缓存系统,用于在动态应用中减少数据库负载,提升访问速度。Memcached由Danga Interactive开发,用于提升LiveJournal.com访问速度的。LJ每秒动态页面访问量几千次,用户700万。Memcached将数据库负载大幅度降低,更好的分配资源,更快速访问。

        上网baidu了很多东西,几乎都差不多,而且基于java的说的很少,所有只有在研究了各个其他语言类的应用后再来尝试在java上进行简单的操作应用。先从memcached上进行说明,memcached的最新版是采用c语言进行开发和设计的,据说旧版的是采用perl语言开发的,而且它是一个应用软件来的,是作为缓存服务器的服务器端运行在服务器上的,需要使用特定的语言编写客户端与其进行通信来进行数据的缓存和获取。通常我们是把memcached安装运行在web服务器上,然后通过对需要的数据进行缓存,据我目前所知,所有数据的缓存设置和存取操作,以及数据的更新后替换操作全部需要程序来进行,而不是自动进行的(自动不知道能不能成功,呵呵)。下面从一个实际的例子来应用memcached。

       首先到http://danga.com/memcached/下载memcached的windows版本和java客户端jar包,目前最新版本是memcached-1.2.1-win32.zip和java_memcached-release_1.6.zip,分别解压后即可!首先是安装运行memcached服务器,我们将memcached-1.2.1-win32.zip解压后,进入其目录,然后运行如下命令:

    c:>memcached.exe -d install
    c:>memcached.exe -l 127.0.0.1 -m 32 -d start

        第一行是安装memcached成为服务,这样才能正常运行,否则运行失败!第一行是启动memcached的,作为测试我们就简单的只分配32M内存了,然后监听本机端口和以守护进行运行。执行完毕后,我们就可以在任务管理器中见到memcached.exe这个进程了。好了,我们的服务器已经正常运行了, 下面我们就来写java的客户端连接程序。

        我们将java_memcached-release_1.6.zip解压后的目录中的java_memcached-release_1.6.jar文件复制到java项目的lib目录下,然后我们来编写代码,比如我提供的一个应用类如下:

    package utils.cache;

    import java.util.Date;

    import com.danga.MemCached.MemCachedClient;
    import com.danga.MemCached.SockIOPool;


    /**
    * 使用memcached的缓存实用类.
    *
    *
    @author 铁木箱子
    *
    */
    public class MemCached
    {
       
    // 创建全局的唯一实例
        protected static MemCachedClient mcc = new MemCachedClient();
       
       
    protected static MemCached memCached = new MemCached();
       
       
    // 设置与缓存服务器的连接池
        static {
           
    // 服务器列表和其权重
            String[] servers = {"127.0.0.1:11211"};
            Integer[] weights
    = {3};

           
    // 获取socke连接池的实例对象
            SockIOPool pool = SockIOPool.getInstance();

           
    // 设置服务器信息
            pool.setServers( servers );
            pool.setWeights( weights );

           
    // 设置初始连接数、最小和最大连接数以及最大处理时间
            pool.setInitConn( 5 );
            pool.setMinConn(
    5 );
            pool.setMaxConn(
    250 );
            pool.setMaxIdle(
    1000 * 60 * 60 * 6 );

           
    // 设置主线程的睡眠时间
            pool.setMaintSleep( 30 );

           
    // 设置TCP的参数,连接超时等
            pool.setNagle( false );
            pool.setSocketTO(
    3000 );
            pool.setSocketConnectTO(
    0 );

           
    // 初始化连接池
            pool.initialize();

           
    // 压缩设置,超过指定大小(单位为K)的数据都会被压缩
            mcc.setCompressEnable( true );
            mcc.setCompressThreshold(
    64 * 1024 );
        }
       
       
    /**
         * 保护型构造方法,不允许实例化!
         *
        
    */
       
    protected MemCached()
        {
           
        }
       
       
    /**
         * 获取唯一实例.
         *
    @return
        
    */
       
    public static MemCached getInstance()
        {
           
    return memCached;
        }
       
       
    /**
         * 添加一个指定的值到缓存中.
         *
    @param key
         *
    @param value
         *
    @return
        
    */
       
    public boolean add(String key, Object value)
        {
           
    return mcc.add(key, value);
        }
       
       
    public boolean add(String key, Object value, Date expiry)
        {
           
    return mcc.add(key, value, expiry);
        }
       
       
    public boolean replace(String key, Object value)
        {
           
    return mcc.replace(key, value);
        }
       
       
    public boolean replace(String key, Object value, Date expiry)
        {
           
    return mcc.replace(key, value, expiry);
        }
       
       
    /**
         * 根据指定的关键字获取对象.
         *
    @param key
         *
    @return
        
    */
       
    public Object get(String key)
        {
           
    return mcc.get(key);
        }
       
       
    public static void main(String[] args)
        {
            MemCached cache
    = MemCached.getInstance();
            cache.add(
    "hello", 234);
            System.out.print(
    "get value : " + cache.get("hello"));
        }
    }

        那么我们就可以通过简单的像main方法中操作的一样存入一个变量,然后再取出进行查看,我们可以看到先调用了add,然后再进行get,我们运行一次后,234这个值已经被我们存入了memcached的缓存中的了,我们将main方法中红色的那一行注释掉后,我们再运行还是可以看到get到的value也是234,即缓存中我们已经存在了数据了。

        对基本的数据我们可以操作,对于普通的POJO而言,如果要进行存储的话,那么比如让其实现java.io.Serializable接口,因为memcached是一个分布式的缓存服务器,多台服务器间进行数据共享需要将对象序列化的,所以必须实现该接口,否则会报错的。比如我们写一个简单的测试Bean如下:

    class TBean implements java.io.Serializable
    {
       
    private static final long serialVersionUID = 1945562032261336919L;
       
       
    private String name;

       
    public String getName()
        {
           
    return name;
        }

       
    public void setName(String name)
        {
           
    this.name = name;
        }
    }

        然后我们在main方法中加入如下几行代码:

    TBean tb = new TBean();
    tb.setName(
    "铁木箱子");
    cache.add(
    "bean", tb);
    TBean tb1
    = (TBean)cache.get("bean");
    System.out.print(
    "name=" + tb1.getName());
    tb1.setName(
    "铁木箱子_修改的");
    tb1
    = (TBean)cache.get("bean");
    System.out.print(
    "name=" + tb1.getName());

        我们首先把TBean的一个实例放入缓存中,然后再取出来,并进行名称的修改,然后我们再取这个对象,我们再看其名称,发现修改的对象并不是缓存中的对象,而是通过序列化过来的一个实例对象,这样我们就无须担心对原生类的无意修改导致缓存数据失效了,呵呵~~看来我也是多此一想啊。所以这表明从缓存中获取的对象是存入对象的一个副本,对获取对象的修改并不能真正的修改缓存中的数据,而应该使用其提供的replace等方法来进行修改。

        以上是我在windows下对memcached的一点小学习和实践,在以后的项目开发过程中将会更深入的学习和应用这一缓存工具,也希望和有兴趣的同行一起讨论学习该工具的使用~~ 微笑 



    原址:

    http://j2ee.blog.sohu.com/70343632.html

    或者:

    http://sailinglee.iteye.com/blog/752847

  • Facebook后台技术探秘

    2011-04-14 11:29:44

    Facebook后台技术探秘

          每月570000000000页面浏览量,每个月超过30亿的图片上传,5亿的用户数量,Facebook的后台是用哪些技术保障网站的流畅运行呢?      

          在今年举行的Facebook F8开发者大会上,51CTO带您了解了其最新的开放图战略和语义搜索。今天我们一起来了解Facebook背后的软件,看看作为当今世界上访问量最大的网站之一,Facebook是如何保证5亿用户的系统一直稳定可靠的运行。

          Facebook的扩展性挑战

          在我们讨论细节之前,这里有一些Facebook已经做的软件规模:

          ◆Facebook有570000000000每月页面浏览量 (据Google Ad Planner)

          ◆Facebook的照片量比其他所有图片网站加起来还多(包括Flickr等网站)

          ◆每个月超过30亿张照片被上传

          ◆Facebook的系统服务每秒处理120万张照片,这不包括CDN服务中处理的照片

          ◆每月超过25亿条的内容 (状态更新,评论等)被共享

          ◆Facebook有超过30,000服务器(这个数字是去年的)

          Facebook扩展所依赖的软件

          Facebook是在某些程度上说仍然是LAMP的站点,但它比普通的LAMP大得多,以纳入其他元素和很多服务,并修改现行的做法。

          例如:

          ◆Facebook仍使用PHP,但它已经为它建立一个编译器,以便它可以分为本地代码打开了Web服务器,从而提高性能。

          ◆Facebook使用Linux,但他特别为网络吞吐量做了优化。

          ◆Facebook使用MySQL,但主要是作为一个Key-value的持久性存储,Jions和服务器逻辑操作在Web服务器上操作。因为在那里更容易执行。

          还有是自编写的系统,如Haystack,一个高度可扩展的对象存储,用来存储Facebook的照片。还有Scribe,一个日志系统,可以运行在Facebook的巨大规模上的日志系统。

          现在我们介绍一下全球最大的社会网络网站的所使用的软件吧。

          Memcached

          memcached的是现在互联网最有名的软件之一了。 这是一个分布式内存缓存系统,用来作为Web服务器和MySQL服务器之间的缓存层(因为数据库访问比较慢)。 多年以来,Facebook已经提出了一些优化Memcached和一些周边软件的办法。如压缩network stack。

          Facebook的每时每刻都有数10TB的数据缓存在Memcached的数千台服务器上。 它可能是世界上最大的Memcached的集群了。

          HipHop for PHP

          PHP作为一种脚本语言,和本地程序相比是运行缓慢的。 HipHop可以将PHP转换成C + +代码,然后再进行编译,可以获得更好的性能。 因为Facebook严重依赖PHP,这使得其可以让Web服务器运行的更有效率。

          一个工程师小团队在Facebook(一开始只有三人)花了18个月时间开发HipHop,现在已经是可用状态。

          Haystack

          Haystack是Facebook的高性能照片存储/检索系统(严格来说,是一个对象存储,因此它并不一定要存储照片)。 它有许多工作要做;有超过20亿张上传的照片,并且每一个被保存在四个不同的分辨率,因此有超过800亿张照片。

          它不仅是对能够处理的上亿的照片,运行表现也是至关重要的。 正如我们前面提到的,Facebook的服务约120万张照片每秒 ,这个数字不包括CDN上的。 这是一个惊人的数字。关于Facebook的图片存储请参考51CTO之前的报道《Facebook图片存储架构技术全解析》。

          BigPipe

          BigPipe是Facebook开发的一个动态的网页服务系统。 Facebook使用它来按section(称为“pagelets”)处理每个网页,以获取最佳性能。

          例如,在聊天窗口是分开的,新闻Feed也是分开的,等等。 这些pagelets可以在一个页面表现的时候同时使用,这是该页面表现的时候获取进来的。即使某些工程的一部分关闭或中端,用户也可以获得一部分网页。

          Cassandra

          Cassandra是一个不会单点失败的分布式存储系统。 这是为NoSQL运动的一个重要组成部分,并已公开的源代码(它甚至成为一个Apache项目)。Facebook在搜索功能中使用它。

          除了Facebook,还有一些人也用它,例如Digg的。 不过最近Twitter放弃了Cassandra。关于Cassandra的更多介绍可以参考51CTO的专题《奔向自由?Cassandra数据库应用指南》。

          Scribe

          Scribe是一个灵活的日志系统,Facebook在他的内部大量使用。 它的能够处理在Facebook的大规模日志记录,并自动处理新的日志记录类别,Facebook有数百个日志类别(categories)。
          

          Hadoop and Hive

          Hadoop的是一个开源的map-reduce实现,使得它可以在进行大数据上进行运算。 Facebook的使用这个进行数据分析(而我们都知道,Facebook已经大量的数据)。 Hive就是发源于Facebook,使得对于Hadoop使用的SQL查询成为可能,从而是其更容易对非程序员使用。

          Hadoop和Hive是开源的(Apache项目),有为数众多的追随者,例如雅虎和Twitter。

          Thrift

          Facebook使用的几种不同的语言和不同的services。 PHP是最终用于前端,Erlang是用于聊天,Java和C ++也使用于多种场所,也许还有其他语言。Thrift是一个内部开发的跨语言的框架,联系语言,使他们可以在一起合作,从而使他们之间可以交互。 这使得Facebook可以更容易为继续保持其跨语言的发展。

          Facebook已经让Thrift开源。更多的语言支持已被添加到Thrift。

          Varnish

          Varnish是一个HTTP加速器,可以作为一个负载平衡器,并缓存的内容,然后可以以闪电般的速度送达。

          Facebook使用的arnish来处理照片和个人资料图片,处理每天数十亿的要求。 和其他的东西一样,Varnish是开源的。

          保持Facebook 顺畅运行的其他东西

          我们已经提到的软件,组成了Facebook的系统,并帮助运行在大规模上。 但是,处理这么大的系统是一个复杂的任务,因此我们将列出一些其他的东西,他们保持了Facebook的平稳运行。

          渐进发布和暗启动

          Facebook有一个他们所谓的守门人制度(Gatekeeper),允许他们可以给不同的用户运行两套不同的系统。 这让Facebook渐进的发布新的功能,A / B测试,只为Facebook雇员发布等的某些特性。

          Gatekeeper也可以让Facebook实现“暗启动”,这是在用户使用一些功能之前,就激活某些功能(因为用户没有察觉,所以称之为暗启动)。 这将作为一个现实世界的压力测试,在正式启动前,帮助揭露一些功能障碍和其他问题。 暗启动通常是在正式启动前两个星期。

          Profiling的直播系统

          Facebook的仔细监控其系统,有趣的是它也负责监察每一个PHP函数在生产环境的性能。 检测各个PHP的环境的配置运行情况。使用开源工具,XHProf 。

          渐进的利用关闭功能来提升性能

          如果Facebook运行时出现性能问题,有一个办法,就是逐步禁用不太重要的功能,以增强Facebook的大量核心功能表现。

          我们没有提及的事情

          我们没有提到硬件相关的事情,但这也是提高可伸缩性的重要一环。例如,就像其他大型站点,Facebook利用CDN来处理静态内容。Facebook还有一个the huge data center,可以帮助他扩展更多的服务。

          Facebook的开源情节

          不仅是Facebook使用(和帮助),如Linux,Memcached的,MySQL和Hadoop的开源软件,以及许多其他情况下,也贡献许多了其内部开发的软件。

          Facebook亦开源了Tornado,一个高性能的网络服务器框架,由FriendFeed团队开发。关于开放源码软件清单,可以在Facebook’s Open Source page.找到。
           ( 本文转载自 颜开的博客 ,原文: Facebook背后的软件 。)
  • 专访人人网黄晶:SNS网站后台架构探秘

    2011-04-14 11:27:51

     人人网作为国内大型SNS站点的代表,其目前已经拥有真实注册用户超过7000万、PV达到了4亿、日登录2200万人次。面对如此庞大的访问量,人人网的后台架构是怎样的一番景象呢?关于此,51CTO独家专访了现任人人网产品技术高级总监黄晶老师。

    51CTO:作为国内大型SNS站点的代表,我们知道人人网后台主要采用Java语言编写,同其他形式的站点相比,SNS站点在网站架构方面有什么样的特点?

    黄晶老师:SNS网站用户数据量庞大且关系复杂,对实时性要求非常高,因此要求SNS网站架构要有很强的水平扩展性。

    51CTO:在后台语言的选择上,有其他大型SNS站点使用PHP,人人网则选择了Java,使用Java的优势在体现哪些方面?

    黄晶老师:Java的优势体现在当项目日渐复杂的时候,可以通过Java良好的OO特性,保持非常好的模块性,在进行网站重构的时候比较方便,在代码量增大的时候也可保持良好的可管理性。

    51CTO:现在很多大型网站的后台都使用了大量的开源软件,人人网是否也是如此呢?咱们的开发团队是否也在开发自己的框架呢?

    黄晶老师:人人网也使用了大量开源软件,比如MySQL、Memcached、ICE、Hadoop等,同时我们也根据业务需要研发了自己的框架,比如MVC框架,分布式KV存储系统。

    51CTO:一个网站在发展过程中,后台都会经历不断的重构,从初期的校内网、到现在的人人网,网站架构最大的变化在哪?

    黄晶老师:从初期的校内网到现在人人网,最大的变化是:很多底层服务都从利用已有开源软件的搭建,变成了根据业务需要,由我们自己开发专门的服务所代替,这样能够提高资源利用程度,提高整个系统的可用性。

    51CTO:我们知道人人网的用户量非常庞大,在数据库方面,人人网采用了哪种数据库?除了高性能、可扩展性外,人人网对数据库的需求还有哪些?在整个后台系统架构中,往往数据库的性能也会出现瓶颈,从早期的校内网到现在,我们对数据库性能的优化方面都做了哪些大的改变?

    黄晶老师:我们采用的数据库是MySQL,在需求方面,我们也非常关注高可用性。早期校内从单数据库,到主从接口,发展到后来垂直拆分,然后水平拆分,然后在每个节点上实现主-主提高可用性,到异地备份容灾。目前我们的数据库已经有非常强的水平扩展能力和非常高的可用性。

    关于MySQL,51CTO推荐专题:MySQL数据库入门与精通教程

    51CTO:数据缓存在后台架构中同样非常重要,在数据库服务器、Web服务器以及两者之间,人人网都采用了哪些缓存手段?

    黄晶老师:我们的数据库用到了部分自身缓存机制,比如尽可能利用innodb的pool和MySQL的 Query Cache。在中间用到Memcached,以及基于ICE通讯框架由我们自己编写的包含业务逻辑处理能力的缓存服务,在我们自行开发的分布式KV系统中也会充分利用内存Cache加速。

    推荐阅读:分布式缓存系统memcached简介与实践

    51CTO:目前国内外也有很多大型站点在使用NoSQL,从功能上来将,其非常适合应用在SNS、微博等站点,人人网是否在考虑使用NoSQL呢?

    黄晶老师:对于NoSQL,我们已经考虑并在逐步试用自行开发的Nuclear分布式KV存储系统。

    关于NoSQL,51CTO推荐专题:NoSQL:关系型数据库终结者?

    51CTO:上传照片或者图片是SNS网站用户很常用的一个功能,对于Web服务器来讲,图片是非常消耗资源的,那么目前人人网每天大概有要处理多少张图片?相比较其他数据而言,图片会占用大量存储空间,给服务器带来不小的压力,我们知道Facebook有着一套自己的图片存储架构,咱们人人网在这方面是怎么做的呢?

    黄晶老师:现在人人网每天要处理千万张级别的照片数量。我们使用由C++专门编写的Web服务来处理照片上传和压缩工作。存储是用一套分布式文件存储系统,在小文件很多的情况下,也会采用把小文件聚合为大文件的方式提高性能,这样的做法也便于大量小文件的备份。

    51CTO:目前SNS站点都有很强的实时性,用户能够第一时间看到好友都在做什么,像人人网以及Facebook等,还有即时聊天功能,我们是通过什么样的手段来最大限度的优化每一个页面,从而尽可能的减少服务器的请求时间,提高用户体验的呢?

    黄晶老师:我们的做法是尽可能把数据放在内存中,提高数据存取速度。另外,复杂的页面采用并发机制,多线程同时从多个后台源取数据拼成页面。

    51CTO:最后,还想请黄晶老师谈一谈SNS网站后台技术的发展趋势,以及从事SNS后台开发的开发者需要关注那几方面?

    黄晶老师:鉴于SNS网站的一些业务特点,在后台技术中,我们最关注的仍然是高性能,可扩展性,高可用性。并且 SNS所提供的服务也在变化中,对于业务的多变,架构要有灵活适应的能力,否则需要提供新服务的时候重构之前的系统工作量很大。类似Google的 GFS/Bigtable/MapReduce一系列通用的分布式系统非常优秀,可以支撑很多Google的业务需要。所以在SNS网站架构中也需要一个能灵活应对业务变化的一套健壮的分布式系统。

    【51CTO独家特稿,转载请注明原文出处及作者!】

    转自: http://my.oschina.net/laotao020/blog/10973

  • 软件自动化测试框架设计参考准则 (转载)

    2011-02-23 09:35:37

    软件自动化测试框架设计参考准则

      测试框架是在所有不同的测试自动化阶段定义的一整套指导准则:需求分析阶段、脚本设计阶段、执行阶段、报告和维护阶段。框架即对于内部复杂架构的一种包装,这样的包装可以使得最终用户方便的使用。框架还具有对于流程标准的强制执行性。

      目前为止,还没有一种关于如何开发测试框架以及在开发过程中需要考虑哪些因素的准则。有很多记载着各式各样的测试框架以及它们各自是如何工作的白皮书,但是这些白皮书中还没有任何一篇文档是记录着测试框架设计共同需要考虑的因素。本文基于测试框架需求,涵盖了测试框架各个方面以及一些必备的基本要素。

      1. 自动化测试框架的类型 – 目前普遍存在的框架有以下几种:

      ●  数据驱动框架 – 当测试对象流程固定不变(仅仅数据发生变化),可以使用这种测试框架。测试数据是由外部提供的,比如说Excel表、XML等等

      ●  关键字驱动框架 – 这种自动化测试框架提供了一些通用的关键字,这些关键字适用于各种类型的系统。它还为自动化测试工具和被测系统提供了抽象性。举个例子,它可以使用相同的测试用例来测试类似的Web和Windows系统。

      ●  混合型的框架 – 混合型自动化测试框架同时具有数据驱动型和关键字驱动型框架的优点。这种测试框架不但具有通用的关键字,还有基于被测系统业务逻辑的关键字。例如“登录”、“退出”是可以被使用的仅局限于某系统的关键字。

      2. 不要过分的改造 – 自动化测试框架应该尽可能的使自动化测试工具发挥它自己强大的功能,而不是通过实现新的关键字来重新定义整套语言。开发一套关键字驱动的自动化测试框架的代价是很大的而且非常耗时。开发一套混合型的自动化测试框架的代价就相对较小而且开发周期短。

      3. 可重用性 – 测试框架应该尽最大可能提高可重用性。把单独的Action组合成业务逻辑可以提供可重用性。举个例子,可以把类似于“输入用户名”、“输入密码”和“点击登录”这些Action组合成一个可被重用的模块:“登录”

      4. 支持系统的不同版本 – 自动化测试框架应该允许重复使用基线化脚本,这样可以保证这份脚本能被用来对被测系统的多个版本进行测试。对不同系统的支持有两种方式:

      ●  复制和修改 – 这种方法包含了新建基线脚本的一个拷贝、修改这份拷贝用以测试特定版本的项目。51Testing软件测试网 ^b1o.N8W0Y

      ●  重用和升级 – 这种方法包含了重用基线脚本、提供一个此脚本的升级和优化用以测试特定版本的项目。这样做可以最大化的保障可重用性,这也是推荐的方法。

      5. 支持脚本版本化 – 测试脚本应该被储存在类似于CVS、微软的VSS等版本控制工具中。这样做可以保障在灾难发生的时候可以被恢复。

      6. 将开发和发布环境分开 – 自动化应当和其它开发项目同等看待。测试脚本应当在一个测试环境下创建和调试。一旦测试脚本测试通过后唯一该做的就是将它部署到发布环境。在紧急发布版本的情况也同样适用这种方法。

      7. 外部可配置性 – 脚本的可配置项应当被保存在一个外部文档中。系统的URL、版本、路径等都可以被视作可配置项放在外部文件中。这样做可以使得在不同的环境中都可以执行测试脚本。需要注意的是外部配置文件的路径不要写死,如果把它写死了虽然在任何环境中都还是可以运行脚本,但是每次只能在一个环境运行。配置文件的路径使用相对路径即可解决这个问题。

      8. 自身可配置性 – 理想的测试框架应该是自身可配置的。一旦部署到系统中之后应当不需要再做任何手工配置,脚本应当自动配置完成一些必要的设置。

      9. 任何对象改动引起的变动应该是最小的 – 自动化过程中最为常见的问题是对象识别的变更。测试框架应该支持可以很容易的来完成这些修改。这可以通过将所有的对象识别设置储存在一个共享文件来实现。这个文件可以是XML文件、Excel文件、数据库或者自动化所特有的格式。这里有两种可能的方式来实现对象识别设置的方式:51Testing软件测试网PX6}0j&re

      ●  静态方法 – 这种方法中,所有对象定义都在测试最初被加载到内存中。任何对象定义变更只能通过停止和重新运行测试来实现。

      ●  动态方法 – 对象定义是通过需求拉动的。这种方式和静态方式比较而言显得较为缓慢。但是对于非常大的脚本而言,并且对象识别需要在运行时做修正的情况下,动态方法是适用的。

      10. 测试执行 – 自动化测试框架也许需要满足以下需求(基于实际需求)

      ●  执行单独的测试用例;

      ●  批量执行测试用例(一组测试用例);

      ●  只执行失败的测试用例;

      ●  可以在前一个(一组)测试用例执行结果的基础上,执行下一个(一组)测试用例;

      根据实际需求还会有许多其他情况。一个框架可能无法实现所有这些需求,但它应具有足够的灵活性以适应今后此类需求。51Testing软件测试网?#q!U l!Y%K1n

      11. 状态监测 - 一个框架应允许实时监控执行状态,一旦失败能够发出警报。这样可以在出现failure之后迅速反馈。

      12. 报表 – 不同的系统对报表有不同的需求,有时候需要一组测试的整体报表,有时候只需要单个测试用例级别的测试执行报表。一个好的测试框架应该有足够的弹性来按需支持不同的报表。

      13. 发生更改的时候对测试工具尽量小的依赖性 – 一些测试脚本的更改可能只能在打开测试工具后,在测试工具中进行修改,然后保存。测试脚本应该在没有测试工具的情况下也可以对脚本进行更改。这样的实现可以减少license的购买从而为公司节省开支。这样的实现还可以让所有想去修改脚本的人无需安装测试工具也可以很方便的对脚本进行修改。

      14. 方便的调试 – 调试在自动化过程中占据了大量的时间,因此在调试这个过程中需要加以特别的关注。关键字驱动的测试框架因为使用了外部的数据源(比如Excel数据表)去读取脚本中的关键字和测试过程,所以较难调试。

      15. 日志 - 生成日志是执行的重要组成部分。在一个测试案例的不同点生成调试信息这是非常重要的。这些信息有助于快速地找到问题的范围,同时缩短了修改时间。

      16. 易用性 - 该框架应易于学习和使用。对框架的人员培训费时且昂贵。有一个好文档的框架更容易理解和使用。

      17. 灵活性 - 框架应该足够的灵活,以适应任何改进,而不会影响已有的测试案例。

      18. 性能的影响 – 框架还应考虑对执行性能的影响。一个复杂的框架会增加脚本的加载或执行时间,这一定不是我们所期望的。像缓存技术,当执行时编译所有代码到单个库中等...只要可能都应该用于性能的改善。

      19. 框架支持工具 – 开发一些外部工具来完成任务,这对框架设计会有帮助。这是一些例子:51Testing软件测试网:N v7nsq

      ●  从本地文件夹上传脚本到QC51Testing软件测试网F}'Z]F&J Ah

      ●  结合库文件到当前打开的脚本51Testing软件测试网+[SF1nR"]N

      ●  同步本地和QC上的脚本文件

      20. 编码标准 - 编码标准应确保脚本的一致性,可读性和易于维护。编码标准应包含下列内容:

      ●  命名规范(变量、子程序、函数、文件名、脚本名称等),例如i_VarName为整数变量, fn_i_FuncName为返回值是整数的函数;

      ●  库、子程序、函数的头部注释。这应包含,如版本历史,创建者,最后修订者,最后修订日期,说明,参数,示例;

      ●  对象命名规范。例如txt_FieldName为一个文本框;

      我们应该把自动化测试看作是一个开发项目,而不仅仅是记录和回放事件。先有一个良好的框架,再开始自动化测试,这样可以确保较低的维护成本。当你在开发一个框架,进行框架的需求分析时,可以参考本文谈到的这些准则。

    google_protectAndRun("render_ads.js::google_render_ad", google_handleError, google_render_ad);


  • 软件测试中通用数据生成方法

    2011-02-23 09:32:36

    软件测试中通用数据生成方法

    http://softtest.chinaitlab.com/zixun/810015.html

    软件测试中非常重要的一个工作就是生成和维护测试数据,而这个工作恰恰是繁琐、重复而极易出错的。无疑找到一种通用的数据生成方法是极具意义的。本文阐释了如何使用脚本语言 PHP,加上简单的 ini 配置文件来达到这个目的的。

      测试的数据生成和维护在软件测试中是非常重要的一环。很多用例实际上就是在修改所测程序的输入数据以确保程序的逻辑是按照自己的预期进行地。

      比如我们测试一个用户登录系统,我们需要测试正常用户名 + 正常密码、正常用户名 + 错误密码、错误用户名 + 错误密码等基本的用例。在执行用例之前,就需要事先在数据库中设置好相应的数据,比如有一条记录为正常用户名 + 正常密码,然后我们在登陆界面输入该用户名和密码,预期结果为正常登陆。

      不同的程序有不同格式的输入数据。但不管格式千变万化,我们总可以把它们归结为基于行和列的格式,就像数据库中的表一样。一行为一条记录,每一条记录都有相同的字段组成,每一个字段有自己的数据格式,字段和字段之间可能有分隔符。

      我们可以在执行每一个用例时,手工修改数据,然后再执行用例。但这样存在一些问题。

      1. 重复,数据重用性差。当前用例所需的数据很有可能在下个用例中被破坏了。

      2. 效率低,尤其是当数据格式比较复杂,而且又需要大量数据的时候。

      3. 不灵活。但数据发生变动的时候,数据的维护成本会很高。

      4. 容易出错。

      那有没有一种方法来解决这个问题呢?答案是肯定的。下面我们一起来实现一个简单的工具来解决这个问题。

      需要实现的基本功能

      首先我们来列举一下这个软件测试工具需要实现的基本功能:

      1. 通用性:能够描述各种不同格式的数据。

      2. 扩展性:当需要新的数据格式时,可以任意扩展。

      3. 易用性:配置文件不易复杂。

      4. 跨平台:我们需要一款可以在windows、linux、FreeBSD等系统下面运行的工具。

      我们选择的开发工具

      我们选择的开发工具是 PHP,配置文件采用了 ini 格式的文件。

      之所以选择 PHP,是因为 PHP 是解释性脚本语言,其弱类型的特点以及强大的数组、字符串处理功能,十分适合我们这种应用场合。而且 PHP 有着良好的扩平台性,使用 PHP 开发的脚本基本上不用修改就可以在各个平台下面运行。

      之所以选择 ini 格式的文件来作为配置,是因为 ini 文件相比较于 xml 而言比较简单。而且程序处理起来也非常的方便。在 PHP 中使用 parse_ini_file 的内置函数就可以解析整个 ini 文件。

      配置语法

      首先我们需要来定义一下我们的配置语法。前面讲到,数据是由行和列组成,每一列中有若干字段,每一个字段有自己的生成数据类型,有自己的前缀,字段和字段之间还有分隔符。我们最终的配置语法格式如下:

      清单一:example.ini

      [field1]

      datatype="list, range=[10-20]"

      prefix="int_"

      postfix=" "

      [field2]

      datatype="list, range=[A-Z, a-z]"

      prefix="char_"

      postfix=" "

      [field3]

      prefix=""

      datatype="list, range=[abc,123,xyz, 100-110]"

      postfix=" "

      [field4]

      prefix=""

      datatype="list, range=[100-200:2]"

      postfix=" "

      我们来解释一下语法的格式:

      1. 字段名使用[]引起来。后面使用 key=value 的形式来定义这个字段的属性。

      2. 字段有三个基本的属性:datatype 指定字段的取值范围,prefix 设定字段的前缀,postfix 则设定字段的后缀。

      3. datatype 中最基本的数据类型就是 list,一个无所不包的列表。你所需要指定的就是这个字段的取值范围参数 range。

      4. range 参数可以采用 1-10 这样的区间表达,也可以用逗号“ , ”来连接多个区间或者元素。区间还可以指定递增的步长。

      上面的 example.ini 文件中定义了四个字段,字段和字段之间使用两个空格分隔,第一个字段的取值范围为 10-20,前缀为 int_,第二个字段取值范围是大小写英文字母,前缀为 char_,第三个字段取值范围是混合的,第四个字段则从 100 到 200,递增步长为 2 。

      清单二:生成的数据

      int_10 char_A abc 100

      int_11 char_B 123 102

      int_12 char_C xyz 104

      int_13 char_D 100 106

      int_14 char_E 101 108

      int_15 char_F 102 110

      int_16 char_G 103 112

      int_17 char_H 104 114

      int_18 char_I 105 116

      int_19 char_J 106 118

      代码实现:

      第一步:解析 ini 文件

      error_reporting(0);

      $iniFile = $argv[1]; // 第一个参数为配置文件。

      $dataCount = $argv[2]; // 第二个参数为要生成的记录数。

      $fields = parse_ini_file($iniFile, true); // 将 ini 配置文件转换为一个数组。

      第二步:生成每一个字段的取值列表

      foreach($fields as $fieldName => $field)

      {

      $list = array();

      $equalPos = strpos($field['datatype'], '='); // 取得等号的为止。 range=[]

      $range = substr($field['datatype'], $equalPos + 2, -1); // 取得 range 的列表(去掉了 [])

      $items = explode(',', $range); // 得到所有的 item 元素。

      /* 循环处理每一个 item,如果是一个区间,则调用 range 函数。 */

      foreach($items as $item)

      {

      if(strpos($item, '-'))

      {

      list($min, $max) = explode('-', $item);

      $list = array_merge($list, range($min, $max)); // 追加到 field 的 list 列表中。

      }

      else

      {

      $list[] = $item;

      }

      }

      $field['list'] = $list; // 将最终的 list 列表赋值给 field 。

      $field['pointer'] = 0; // 初始化这个列表的指针。

      $fields[$fieldName] = $field; // 写回整个 fields 数组。

      }

      第三步:循环输出数据

      /* 循环输出数据。 */

      for($i = 0; $i < $dataCount; $i ++)

      {

      foreach($fields as $fieldName => $field)

      {

      $pointer = $field['pointer'];

      /* 如果指针已经到到了列表的尾部,重新指向列表开始。 */

      if($pointer == count($field['list'])) $pointer = 0;

      echo $field['prefix']; // 输出前缀。

      echo $field['list'][$pointer]; // 输出当前指针所对应的列表中的取值。

      echo $field['postfix']; // 输出后缀。

      $pointer ++;

      $fields[$fieldName]['pointer'] = $pointer;

      }

      echo "\n";

      }

      这样我们用了不到 50 行的代码就完成了一个基本的数据生成工具。这其实已经可以满足基本的数据生成任务了。但是还需要进一步进行完善。

      需要进一步完善的地方

      第一:数据的扩展性

      虽然 list 类型已经足够灵活,但有时候还无法满足需要。比如一个比较复杂的字段,由若干小字段组成。这时可以将这个复合字段再通过一个 ini 文件来定义,这样就有了无限可能。

      比如,我们在 example.ini 文件中增加一个日期的字段,格式为 yyyy 年 mm 月 dd 日的格式

      清单三:example.ini 中新增一个字段

      [field5]

      prefix=""

      datatype="custom" " 定义这个字段的数据类型为 custom

      datacfg="custom.ini" " 定义这个字段的配置文件为 custom.ini

      postfix=""

      清单四:custom.ini

      [field5.1]

      prefix=""

      datatype="list, range=[1980-1999]"

      postfix=" 年 "

      [field5.2]

      prefix=""

      datatype="list, range=[1-12]"

      postfix=" 月 "

      [field5.3]

      prefix=""

      datatype="list, range=[1-31]"

      postfix=" 日 "

      这可以通过 php 中的递归函数来实现这个解析。即当解析到一个字段发现是自定义类型的时候,就读取这个字段所对应的 ini 配置文件,然后再解析它的每一个字段。具体的代码就不给出了,读者朋友们可以尝试实现。

      第二:列表可以更加灵活

      列表 range 参数还可以更加灵活,比如:

      1. 可以指定步长:range=[1-100:2],表示从 1 到 100,步长为 2 递增。

      2. 可以指定输出的格式:range=[1-100:2]&format=0.2f,这个地方的 format 可以是 sprintf 函数的格式标签。

      3. 可以指定是否随机。现在生成的数据是按照列表中的顺序来生成的。可以通过一个 rand 参数来指定是否随机。

      第三:不同的输出格式

      我们现在输出的是行与列的格式,其实我们还可以定义其他不同的输出格式。比如输出为数据库的 insert sql 语句。输出为 xml 格式的数据。这些实现起来都不是特别的麻烦,代码中做些处理就可以了。

      结束语

      PHP 是一款非常优秀的脚本语言(虽然它主要用在 web 开发上)。使用 PHP 可以快速地写出很多小工具来帮助我们来完成某个任务。本文只是给出了一个数据生成方法的简单实现,各位读者朋友完全可以发挥自己的想象,开发出更加灵活、好用的工具来。具体到我们公司的应用,我们现在基本上可以使用这个工具来生成各种各样不同的测试数据。而且有了这些固定的测试数据,就可以实现自动化测试。
    google_protectAndRun("render_ads.js::google_render_ad", google_handleError, google_render_ad);
821/512345>
Open Toolbar