zoukankan      html  css  js  c++  java
  • Esper学习之十五:Pattern(二)

     上一篇开始了新一轮语法——Pattern的讲解,一开始为大家普及了几个基础知识,其中有说到操作符。当时只是把它们都列举出来了,所以今天这篇就是专门详解这些操作符的,但是由于篇幅限制,本篇先会讲几个,剩余的后面几篇会逐个讲解。

    1. Followed-by
           如果各位有看过官方文档,应该会发现Followed-by的讲解是在比较靠后的位置,而放在第一的是Every关键字。我把它提前主要是因为其他的关键字结合Followed-by能更好的说明那个关键字的特点。如果不习惯我这样的顺序硬要跟着文档学习的朋友,可以跳过这一节先看后面的内容。

           Followed-by,顾名思义就是“紧跟,跟随”的意思,通常用于事件之间的关联。举个现实生活中的例子:比如下班了,我用钥匙把家门打开,这是一个事件,紧接着我打开了浴室的灯,这也是一个事件,由于我之前在中央控制系统里设定了一个规则:打开家门后如果开了浴室的灯,热水就会放好,我一会儿就能洗澡了。所以我之前的一系列操作就触发了放热水这个动作。可能这个例子不是比较实际,但是应该能很清楚的说明这个关键字的含义吧。

           Followed-by的操作符用减号和大于号组成,即->,和C语言里的指针一模一样。操作符两边是事件名称,表示右边的事件跟随左边的事件发生之后发生,即进入引擎。如果只是简单的事件follow,在操作符的两边写上事先定义的事件名或者类全名。例如:

    [plain] view plaincopy
     
    1. AppleEvent -> BananaEvent  
    2.   
    3. // or  
    4.   
    5. com.xxx.Benz -> com.yyy.Audi  

    但是如果前后事件之间有关联,那事件名或者类全名就需要设置一个别名。例如:

    [plain] view plaincopy
     
    1. a=AppleEvent -> b=BananaEvent(b.price = a.price)     // equals: a=AppleEvent -> b=BananaEvent(price = a.price)  

           设置别名的原因很简单,就是为了方便描述事件之间的关联信息。但是有意思的是,对于前两个简单follow不设置别名esper不会报语法错误,但是实际运行时你无法通过api获取满足条件的事件,而带上别名的事件是可以正常获取的。

    先上一个简单的例子:

    [java] view plaincopy
     
    1. package example;  
    2.   
    3. import com.espertech.esper.client.EPAdministrator;  
    4. import com.espertech.esper.client.EPRuntime;  
    5. import com.espertech.esper.client.EPServiceProvider;  
    6. import com.espertech.esper.client.EPServiceProviderManager;  
    7. import com.espertech.esper.client.EPStatement;  
    8. import com.espertech.esper.client.EventBean;  
    9. import com.espertech.esper.client.UpdateListener;  
    10.   
    11. /** 
    12.  * Created by Luonanqin on 9/5/14. 
    13.  */  
    14. class FollowedEvent {  
    15.   
    16.     private int size;  
    17.   
    18.     public int getSize() {  
    19.         return size;  
    20.     }  
    21.   
    22.     public void setSize(int size) {  
    23.         this.size = size;  
    24.     }  
    25.   
    26.     public String toString() {  
    27.         return "FollowedEvent{" + "size=" + size + '}';  
    28.     }  
    29. }  
    30.   
    31. class PatternFollowedListener implements UpdateListener {  
    32.   
    33.     public void update(EventBean[] newEvents, EventBean[] oldEvents) {  
    34.         if (newEvents != null) {  
    35.             for (int i = 0; i < newEvents.length; i++) {  
    36.                 System.out.println();  
    37.                 EventBean event = newEvents[i];  
    38.                 System.out.println("Result:");  
    39.                 System.out.println(event.get("a") + " " + event.get("b"));  
    40.             }  
    41.         }  
    42.     }  
    43. }  
    44.   
    45. public class PatternFollowedTest {  
    46.   
    47.     public static void main(String[] args) {  
    48.         EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider();  
    49.         EPAdministrator admin = epService.getEPAdministrator();  
    50.         EPRuntime runtime = epService.getEPRuntime();  
    51.   
    52.         String followed = FollowedEvent.class.getName();  
    53.   
    54.         String epl = "select * from pattern[every a=" + followed + " -> b=" + followed + "(size < a.size)]";  
    55.         System.out.println("EPL: " + epl+" ");  
    56.         EPStatement stat = admin.createEPL(epl);  
    57.         stat.addListener(new PatternFollowedListener());  
    58.   
    59.         FollowedEvent f1 = new FollowedEvent();  
    60.         f1.setSize(1);  
    61.         System.out.println("Send Event1: " + f1);  
    62.         runtime.sendEvent(f1);  
    63.         System.out.println();  
    64.   
    65.         FollowedEvent f2 = new FollowedEvent();  
    66.         f2.setSize(3);  
    67.         System.out.println("Send Event2: " + f2);  
    68.         runtime.sendEvent(f2);  
    69.         System.out.println();  
    70.   
    71.         FollowedEvent f3 = new FollowedEvent();  
    72.         f3.setSize(2);  
    73.         System.out.println("Send Event3: " + f3);  
    74.         runtime.sendEvent(f3);  
    75.     }  
    76. }  

    执行结果:

    [plain] view plaincopy
     
    1. EPL: select * from pattern[every a=example.FollowedEvent -> b=example.FollowedEvent(size < a.size)]  
    2.   
    3. Send Event1: FollowedEvent{size=1}  
    4.   
    5. Send Event2: FollowedEvent{size=3}  
    6.   
    7. Send Event3: FollowedEvent{size=2}  
    8.   
    9. Result:  
    10. FollowedEvent{size=3} FollowedEvent{size=2}  

           例子中的pattern是由every和followed-by两个结构组合而成,所实现的效果是针对每一个事件,都监听其follow后同类型的事件的size值小于follow前的事件。只要满足pattern定义,通过get对应的别名就可以获得触发时的具体事件。 并且满足触发条件的事件不会再次被监听。大家重点关注followed-by,every之后会有详细说明。

           以上面的例子来说,我们会监听所有进入引擎的FollowedEvent事件,并等待匹配规则的follow事件。但是如果满足条件的事件一直不发生,那么之前的等待事件就会一直存于引擎内,这势必会引起内存溢出,或者我们有某种需求,即只保留某一部分的事件用来等待匹配的follow事件。Esper为此提供了进阶的Followed-by语法:

    [plain] view plaincopy
     
    1. lhs_expression -[limit_expression]> rhs_expression  

           lhs_expression和rhs_expression就是之前所说的发生顺序有关联的两个事件,而中间的“->”被一个限制表达式分开了,这里的限制表达式需要返回一个int数值,也可以直接写一个数字。整体的含义是:当左边事件等待满足条件的右边事件时,最多只保留n个左边事件在引擎内等待触发,其余事件不留在引擎内。而这个n就是限制表达式的返回值。无论左边事件数量是否达到n,只要满足条件的右边事件到达并触发后,引擎便重新等待新的左边事件并重新计数,直到超过n。。。这个过程会不断循环。完整示例如下:

    [java] view plaincopy
     
    1. package example;  
    2.   
    3. import com.espertech.esper.client.EPAdministrator;  
    4. import com.espertech.esper.client.EPRuntime;  
    5. import com.espertech.esper.client.EPServiceProvider;  
    6. import com.espertech.esper.client.EPServiceProviderManager;  
    7. import com.espertech.esper.client.EPStatement;  
    8. import com.espertech.esper.client.EventBean;  
    9. import com.espertech.esper.client.UpdateListener;  
    10.   
    11. /** 
    12.  * Created by Luonanqin on 9/10/14. 
    13.  */  
    14. class LimitEvent {  
    15.   
    16.     private int age;  
    17.   
    18.     public int getAge() {  
    19.         return age;  
    20.     }  
    21.   
    22.     public void setAge(int age) {  
    23.         this.age = age;  
    24.     }  
    25.   
    26.     public String toString() {  
    27.         return "LimitEvent{" + "age=" + age + '}';  
    28.     }  
    29. }  
    30.   
    31. class LimitFollowedListener implements UpdateListener {  
    32.   
    33.     public void update(EventBean[] newEvents, EventBean[] oldEvents) {  
    34.         if (newEvents != null) {  
    35.             System.out.println(" Result: ");  
    36.             for (int i = 0; i < newEvents.length; i++) {  
    37.                 EventBean event = newEvents[i];  
    38.                 System.out.println("a=" + event.get("a") + " b=" + event.get("b"));  
    39.             }  
    40.   
    41.             System.out.println();  
    42.         }  
    43.     }  
    44. }  
    45.   
    46. public class LimitFollowedTest {  
    47.   
    48.     public static void main(String[] args) {  
    49.         EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider();  
    50.         EPAdministrator admin = epService.getEPAdministrator();  
    51.         EPRuntime runtime = epService.getEPRuntime();  
    52.   
    53.         String limit = LimitEvent.class.getName();  
    54.         String follow = FollowedEvent.class.getName();  
    55.   
    56.         /* 在每次触发完成前最多只保留2个a事件,触发条件为b的size值大于a的age */  
    57.         String epl = "every a=" + limit + " -[2]> b=" + follow + "(size > a.age)";  
    58.         System.out.println("EPL: " + epl + " ");  
    59.   
    60.         EPStatement stat = admin.createPattern(epl);  
    61.         stat.addListener(new LimitFollowedListener());  
    62.   
    63.         System.out.println("First Send! ");  
    64.   
    65.         LimitEvent l1 = new LimitEvent();  
    66.         l1.setAge(1);  
    67.         System.out.println("Send Event: " + l1);  
    68.         runtime.sendEvent(l1);  
    69.   
    70.         LimitEvent l2 = new LimitEvent();  
    71.         l2.setAge(2);  
    72.         System.out.println("Send Event: " + l2);  
    73.         runtime.sendEvent(l2);  
    74.   
    75.         LimitEvent l3 = new LimitEvent();  
    76.         l3.setAge(0);  
    77.         System.out.println("Send Event: " + l3);  
    78.         runtime.sendEvent(l3);  
    79.   
    80.         FollowedEvent f1 = new FollowedEvent();  
    81.         f1.setSize(3);  
    82.         System.out.println("Send Event: " + f1);  
    83.         runtime.sendEvent(f1);  
    84.   
    85.         FollowedEvent f2 = new FollowedEvent();  
    86.         f2.setSize(4);  
    87.         System.out.println("Send Event: " + f2);  
    88.         runtime.sendEvent(f2);  
    89.   
    90.         System.out.println();  
    91.         System.out.println("Second Send! ");  
    92.         System.out.println("Send Event: "+l1);  
    93.         runtime.sendEvent(l1);  
    94.         System.out.println("Send Event: " + l2);  
    95.         runtime.sendEvent(l2);  
    96.         System.out.println("Send Event: " + l3);  
    97.         runtime.sendEvent(l3);  
    98.         System.out.println("Send Event: " + f1);  
    99.         runtime.sendEvent(f1);  
    100.     }  
    101. }  

    执行结果:

    [plain] view plaincopy
     
    1. EPL: every a=example.LimitEvent -[2]> b=example.FollowedEvent(size > a.age)  
    2.   
    3. First Send!  
    4.   
    5. Send Event: LimitEvent{age=1}  
    6. Send Event: LimitEvent{age=2}  
    7. Send Event: LimitEvent{age=0}  
    8. Send Event: FollowedEvent{size=3}  
    9.   
    10. Result:   
    11. a=LimitEvent{age=1} b=FollowedEvent{size=3}  
    12. a=LimitEvent{age=2} b=FollowedEvent{size=3}  
    13.   
    14. Send Event: FollowedEvent{size=4}  
    15.   
    16. Second Send!  
    17.   
    18. Send Event: LimitEvent{age=1}  
    19. Send Event: FollowedEvent{size=3}  
    20.   
    21. Result:   
    22. a=LimitEvent{age=1} b=FollowedEvent{size=3}  

           例子中的epl已经给了注释说明含义,从运行结果中也可以看出,第一次发送时,FollowedEvent只触发了前两个age为1和2的LimitEvent,说明-[2]>起到了限制等待的LimitEvent事件数量为2的效果。第二次发送时,可以看到第一次触发之后-[2]>重新开始计数,FollowedEvent到达后就直接触发了。

    关于限制左边事件的内容,有些是通过配置完成。比如说针对所有的pattern语句都限制,不必每次都写在句子中。这个在之后的配置章节会有讲解。

    2.Every
    这个操作符想必大家已经不陌生了。例子也看了这么多,顾名思义也能想到它代表的就是“每一个”的意思。实际上他表示的是,为操作符后的事件或者子pattern表达式建立一个监听实例,只要满足触发条件这个实例就到此结束。比如:

    [plain] view plaincopy
     
    1. 1).  
    2. select * from pattern[every LimitEvent]  
    3. // equals to  
    4. select * from LimitEvent  
    5.   
    6. 2).  
    7. every FollowedEvent(size > 2)  
    8.   
    9. 3).  
    10. every a=LimitEvent -> b=FollowedEvent(size > a.age)  

           第一个例子是针对每一个LimitEvent事件都监听,所以等同于另一个非pattern写法。第二个例子监听每一个FollowedEvent,且事件的size要大于2,也就是一个filter。第三个例子我在之前有讲过,->的左右组合在一起可以算是一个子表达式(即followed-by),但是every真的是针对这个子表达式么?其实不然,every的优先级要大于->,所以every为每一个LimitEvent建立监听实例,并根据一定条件等待FollowedEvent。

    我之所以先讲Followed-by,就是因为every和->的不同优先级会把各位弄晕,所以先让大家把->搞清楚再来看各种组合情况。

    我把文档里的一个优先级例子放在这里专门讲解下,完整例子我就不写了。

    [plain] view plaincopy
     
    1. 假设事件传入引擎的顺序是这样的:  
    2.   
    3. A1 B1 C1 B2 A2 D1 A3 B3 E1 A4 F1 B4  
    4.   
    5. 注意:every优先级高于->,但是圆括号优先级高于所有操作符  
    6.   
    7. Pattern 1:  
    8. every ( A -> B )  
    9.   
    10. 匹配结果:  
    11. {A1, B1}  
    12. {A2, B3}  
    13. {A4, B4}  
    14.   
    15. 说明:因为有括号,所以every针对的是每一个A->B。A2后面的B3到达前,出现了A3,但是B3到达后并未匹配A3,说明every只有在一个完整的匹配发生后再对A进行新的监听,因此A3不会被监听。比如说:A1 A2 A3 A4 B1这样的发生顺序只会导致A1->B1  
    16.   
    17. Pattern 2:  
    18. every A -> B  
    19.   
    20. 匹配结果:  
    21. {A1, B1}  
    22. {A2, B3} {A3, B3}  
    23. {A4, B4}  
    24.   
    25. 说明:由于没有括号,所以every的优先级大于->,所以every针对的是A,而不是A->B。也就是说,引擎每进入一个A,every都为其新建一个pattern实例等待B事件的发生。所以可以从结果中可以看出,B3进入引擎后同时触发了A2和A3  
    26.   
    27. Pattern 3:  
    28. A -> every B  
    29.   
    30. 匹配结果:  
    31. {A1, B1}  
    32. {A1, B2}  
    33. {A1, B3}  
    34. {A1, B4}  
    35.   
    36. 说明:every的优先级大于->,且every只作用于B,所以->只会针对第一个A事件起作用,并且每一个B都可以匹配这个A。  
    37.   
    38. Pattern 4:  
    39. every A -> every B  
    40.   
    41. 匹配结果:  
    42. {A1, B1}  
    43. {A1, B2}  
    44. {A1, B3} {A2, B3} {A3, B3}  
    45. {A1, B4} {A2, B4} {A3, B4} {A4, B4}  
    46.   
    47. 说明:A和B都用every修饰,every的优先级大于->,所以针对每一个A和B都可以匹配->。再说得通俗一点,只要A在B前进入引擎,那么A后面的B都可以和这个A匹配成功。  

           上面的四个例子可以很清楚的表达出every的意思,更为复杂的例子就是将A和B替换成别的子pattern表达式,各位可以试着自己写写看一下效果。比如:every A->B->C

    3. Every-Distinct
    Every-Distinct和Every基本一样,唯一的区别是Every-Distinct会根据事件的某些属性过滤掉重复的,避免触发监听器。其余用法和Every相同。具体语法如下:

    [plain] view plaincopy
     
    1. every-distinct(distinct_value_expr [, distinct_value_expr[...] [, expiry_time_period])  

    distinct_value_expr表示参与过滤的事件属性,比如:

    [plain] view plaincopy
     
    1. every-distinct(a.num) a=A  

    并且可以多个属性联合在一起,就像联合主键那样。比如:

    [plain] view plaincopy
     
    1. every-distinct(a.num, b.age) a=A -> b=B  

           用于过滤的事件属性值在量很大的情况下会占用很多内存,所以我们需要给它设定一个过期值,也就是语法中的expiry_time_period。这个关键字表示的时间到达时,pattern重新匹配新的事件,而不受之前事件的用于过滤的属性值的影响。比如说:

    [plain] view plaincopy
     
    1. EPL: every-distinct(a.num, 3 sec) a=A  
    2.   
    3. Send: A1: {num=1}  
    4. Send: A2: {num=1}  
    5.   
    6. After 3 seconds  
    7.   
    8. Send: A3: {num=1}  

           第一次发送num为1的A1事件会触发监听器,紧接着发送num为1的A2事件,不会触发,因为num=1已经出现过了,所以A2被过滤。3秒过后,pattern被重置,这时发送A3,监听器收到该事件。这个过程一直持续,即每3秒重置一次pattern,直到EPL实例被销毁。

    语法讲解就这么多,以下几个点需要各位注意:
    1).语法后面跟的子表达式如果返回false,pattern也会被重置。这一点下面的例子会说明。
    2).无论多少个事件的属性参与过滤,其事件名必须设置别名,以“别名.属性名”的方式写在圆括号内。
    3).事件别名一定要在子表达式中定义,否则语法错误。比如:a=A -> every-distinct(a.aprop) b=B

    总结上面的所有内容,我写了个完整的例子供大家参考。

    [java] view plaincopy
     
    1. package example;  
    2.   
    3. import com.espertech.esper.client.EPAdministrator;  
    4. import com.espertech.esper.client.EPRuntime;  
    5. import com.espertech.esper.client.EPServiceProvider;  
    6. import com.espertech.esper.client.EPServiceProviderManager;  
    7. import com.espertech.esper.client.EPStatement;  
    8. import com.espertech.esper.client.EventBean;  
    9. import com.espertech.esper.client.UpdateListener;  
    10.   
    11. /** 
    12.  * Created by Luonanqin on 9/15/14. 
    13.  */  
    14. class EveryDistinctEvent {  
    15.   
    16.     private int num;  
    17.   
    18.     public int getNum() {  
    19.         return num;  
    20.     }  
    21.   
    22.     public void setNum(int num) {  
    23.         this.num = num;  
    24.     }  
    25.   
    26.     public String toString() {  
    27.         return "EveryDistinctEvent{" + "num=" + num + '}';  
    28.     }  
    29. }  
    30.   
    31. class EveryDistinctListener implements UpdateListener {  
    32.   
    33.     public void update(EventBean[] newEvents, EventBean[] oldEvents) {  
    34.         if (newEvents != null) {  
    35.             System.out.println(" Result: ");  
    36.             for (int i = 0; i < newEvents.length; i++) {  
    37.                 EventBean event = newEvents[i];  
    38.                 System.out.println(event.get("a"));  
    39.             }  
    40.         }  
    41.     }  
    42. }  
    43.   
    44. public class EveryDistinctTest {  
    45.   
    46.     public static void main(String[] args) throws InterruptedException {  
    47.         EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider();  
    48.         EPAdministrator admin = epService.getEPAdministrator();  
    49.         EPRuntime runtime = epService.getEPRuntime();  
    50.   
    51.         String everyDistinct = EveryDistinctEvent.class.getName();  
    52.         String limit = LimitEvent.class.getName();  
    53.   
    54.         String epl1 = "every-distinct(a.num) a=" + everyDistinct;  
    55.         System.out.println("EPL1: " + epl1);  
    56.         EPStatement stat1 = admin.createPattern(epl1);  
    57.         stat1.addListener(new EveryDistinctListener());  
    58.   
    59.         EveryDistinctEvent ed1 = new EveryDistinctEvent();  
    60.         ed1.setNum(1);  
    61.   
    62.         EveryDistinctEvent ed2 = new EveryDistinctEvent();  
    63.         ed2.setNum(2);  
    64.   
    65.         EveryDistinctEvent ed3 = new EveryDistinctEvent();  
    66.         ed3.setNum(1);  
    67.   
    68.         System.out.println(" Send Event: " + ed1);  
    69.         runtime.sendEvent(ed1);  
    70.         System.out.println(" Send Event: " + ed2);  
    71.         runtime.sendEvent(ed2);  
    72.         System.out.println(" Send Event: " + ed3);  
    73.         runtime.sendEvent(ed3);  
    74.   
    75.         stat1.destroy();  
    76.   
    77.         String epl2 = "every-distinct(a.num) (a=" + everyDistinct + " and not " + limit + ")";  
    78.         System.out.println(" EPL2: " + epl2);  
    79.         EPStatement stat2 = admin.createPattern(epl2);  
    80.         stat2.addListener(new EveryDistinctListener());  
    81.   
    82.         LimitEvent l1 = new LimitEvent();  
    83.   
    84.         System.out.println(" Send Event: " + ed1);  
    85.         runtime.sendEvent(ed1);  
    86.         System.out.println(" Send Event: " + ed2);  
    87.         runtime.sendEvent(ed2);  
    88.         System.out.println(" Send Event: " + l1);  
    89.         runtime.sendEvent(l1);  
    90.         System.out.println(" Send Event: " + ed3);  
    91.         runtime.sendEvent(ed3);  
    92.   
    93.         stat2.destroy();  
    94.   
    95.         String epl3 = "every-distinct(a.num, 3 sec) a=" + everyDistinct;  
    96.         System.out.println(" EPL3: " + epl3);  
    97.         EPStatement stat3 = admin.createPattern(epl3);  
    98.         stat3.addListener(new EveryDistinctListener());  
    99.   
    100.         System.out.println(" Send Event: " + ed1);  
    101.         runtime.sendEvent(ed1);  
    102.         System.out.println(" Send Event: " + ed2);  
    103.         runtime.sendEvent(ed2);  
    104.         System.out.println(" Sleep 3 seconds!");  
    105.         Thread.sleep(3000);  
    106.         System.out.println(" Send Event: " + ed3);  
    107.         runtime.sendEvent(ed3);  
    108.     }  
    109. }  

    执行结果:

    [plain] view plaincopy
     
    1. EPL1: every-distinct(a.num) a=example.EveryDistinctEvent  
    2.   
    3. Send Event: EveryDistinctEvent{num=1}  
    4.   
    5. Result:   
    6. EveryDistinctEvent{num=1}  
    7.   
    8. Send Event: EveryDistinctEvent{num=2}  
    9.   
    10. Result:   
    11. EveryDistinctEvent{num=2}  
    12.   
    13. Send Event: EveryDistinctEvent{num=1}  
    14.   
    15. EPL2: every-distinct(a.num) (a=example.EveryDistinctEvent and not example.LimitEvent)  
    16.   
    17. Send Event: EveryDistinctEvent{num=1}  
    18.   
    19. Result:   
    20. EveryDistinctEvent{num=1}  
    21.   
    22. Send Event: EveryDistinctEvent{num=2}  
    23.   
    24. Result:   
    25. EveryDistinctEvent{num=2}  
    26.   
    27. Send Event: LimitEvent{age=0}  
    28.   
    29. Send Event: EveryDistinctEvent{num=1}  
    30.   
    31. Result:   
    32. EveryDistinctEvent{num=1}  
    33.   
    34. EPL3: every-distinct(a.num, 3 sec) a=example.EveryDistinctEvent  
    35.   
    36. Send Event: EveryDistinctEvent{num=1}  
    37.   
    38. Result:   
    39. EveryDistinctEvent{num=1}  
    40.   
    41. Send Event: EveryDistinctEvent{num=2}  
    42.   
    43. Result:   
    44. EveryDistinctEvent{num=2}  
    45.   
    46. Sleep 3 seconds!  
    47.   
    48. Send Event: EveryDistinctEvent{num=1}  
    49.   
    50. Result:   
    51. EveryDistinctEvent{num=1}  

           在EPL2中,every-distinct后面的子表达式是EveryDistinctEvent and not LimitEvent,所以在发送EveryDistinctEvent之后发送LimitEvent,就导致子表达式false,所以在此发送num=1的EveryDistinctEvent时监听器被触发。

    原文 http://blog.csdn.net/luonanqin/article/details/39321249

  • 相关阅读:
    书到用时方恨少---记录读书历程
    JAVASCRIPT数据类型(值类型-引用类型-类型总览)
    jQuery基本API小结(下)---工具函数-基本插件
    jQuery基本API小结(上)--选择器-DOM操作-动画-Ajax
    【转】javascript 执行环境,变量对象,作用域链
    JavaScript知识总结--对象的相关概念
    JavaScript知识总结--引用类型(Object-Array-Function-Global-Math)
    JavaScript知识总结--历史-html引用方式-基础概念
    Java--神奇的hashcode
    Java-从堆栈常量池解析equals()与==
  • 原文地址:https://www.cnblogs.com/yudar/p/4872664.html
Copyright © 2011-2022 走看看