??xml version="1.0" encoding="utf-8" standalone="yes"?>BlogJava-John Jianghttp://www.dentisthealthcenter.com/jiangshachina/a cup of Java, cheers!<br> https://github.com/johnshajiang/blogzh-cnThu, 07 Dec 2023 16:48:26 GMTThu, 07 Dec 2023 16:48:26 GMT60探烦HTTP/2: 的状??http://www.dentisthealthcenter.com/jiangshachina/archive/2016/10/08/431871.htmlJohn JiangJohn JiangSat, 08 Oct 2016 13:17:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2016/10/08/431871.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/431871.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2016/10/08/431871.html#Feedback0http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/431871.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/431871.html
探烦HTTP/2: 的状?/span>
探烦HTTP/2pd的第四篇文章Q解MHTTP/2的状态,以及状态之间的转化?2016.10.09最后更?

1. 概述
    HTTP/2的流(Stream)是有状态的。当客户端或服务器端在用某个流d送或接收特定?Frame)或包含特定标{?Flag)的Ӟ会引h的状态的转化?a >HTTP 2协议定义的流状态,如下所C:
                         +--------+
                 send PP |        | recv PP
                
,--------|  idle  |--------.
               /         |        |         \
              v          +--------+          v
       +----------+          |           +----------+
       |          |          | send H /  |          |
,------| reserved |          | recv H    | reserved |------.
|      | (local)  |          |           | (remote) |      |
|      +----------+          v           +----------+      |
|          |             +--------+             |          |
|          |     recv ES |        | send ES     |          |
|   send H |     ,-------|  open  |-------.     | recv H   |
|          |    /        |        |        \    |          |
|          v   v         +--------+         v   v          |
|      +----------+          |           +----------+      |
|      |   half   |          |           |   half   |      |
|      |  closed  |          | send R /  |  closed  |      |
|      | (remote) |          | recv R    | (local)  |      |
|      +----------+          |           +----------+      |
|           |                |                 |           |
|           | send ES /      |       recv ES / |           |
|           | send R /       v        send R / |           |
|           | recv R     +--------+   recv R   |           |
| send R /  `----------->|        |<-----------'  send R / |
| recv R                 | closed |               recv R   |
`----------------------->|        |<----------------------'
                         +--------+

   send:   endpoint sends this frame
   recv:   endpoint receives this frame

   H:  HEADERS frame (with implied CONTINUATIONs)
   PP: PUSH_PROMISE frame (with implied CONTINUATIONs)
   ES: END_STREAM flag
   R:  RST_STREAM frame
    ȝ_HTTP/2为流的整个生命周期定义了7U状态:idleQreserved (local)Qreserved (remote)QopenQhalf closed (local)Qhalf closed (remote)和closed。当一端发送或接收头部?׃个HEADERS/PUSH_PROMISE帧和紧随它的零到多个CONTINUATION帧组成的集合)或RST_STREAM帧,或包含有END_STREAM标签的(HEADERS和DATA)之后Q将改变的状态?/span>
    的状态基于各端自q视角。由于的传输会有网lgq,在同一时刻Q不同端认ؓ的流的状态可能是不同的。比如,当发送端使用一个处于idle状态的发送一个不包含END_STREAM标签的HEADERS帧之后会立即认ؓ该流处于open状态,但此时接收端未得到该HEADERS帧,所以在那一时刻Q接收端依然认ؓ该流的状态是idle?/span>

2. idle
    所有的在创徏之初都处于idle状态。处于idle状态的,只允许被用于发送HEADERS帧,但可以被用于接收HEADERS和PRIORITY帧。在一端用该状态的发送或接收HEADERS帧之后,该端会认为此的状态{变ؓopen。接收PRIORITY帧不会改变流的状态?/span>
    一个idle状态的可被另一个流通过发?接收PUSH_PROMISE帧保留着Q其在来被用于服务器端推送。被保留的流的状态则从idle变ؓreserved (local/remote)?/span>

3. open
    处于open状态的可被用于发送Q何类型的帧。用该状态的去发?接收包含有END_STREAM标签的(HEADERS和DATA)之后Q会使该的状态变成half closed (local/remote)。用open状态的发送或接收RST_STREAM帧之后,则会使它的状态{变ؓclosed?/span>

4. half closed (local/remote)
    状态half closed (local)与half closed (remote)中的local与remote的区别,完全是基于各端自q视角。对于同一个流的两端,如果一端认个流的状态是half closed (local)Q那么另一端只能认个流的状态是half closed (remote)?/span>
    处于half closed (local)状态的只能被用于发送WINDOW_UPDATEQPRIORITY和RST_STREAM帧,但可以被用于接收Mcd的。相对应圎ͼ处于half closed (remote)状态的只能被用于接收WINDOW_UPDATEQPRIORITY和RST_STREAM帧,但可以被用于发送Q何类型的帧?/span>

5. reserved (local/remote)
    与half closed (local/remote)状态相|reserved (local/remote)状态中的local与remote也是Z两端各自的视角。更具体的是Q服务器端发送PUSH_PROMISE一个idle状态的保留着以用于未来的推送,q视q个被保留的的状态ؓreserved (local)Q而客L则视q个的状态ؓreserved (remote)?/span>
    服务器端使用reserved (local)状态的向客户端发送HEADERS帧。该HEADERS帧就是服务器端推?Server Push)中被推送的响应的头部。当发送了HEADERS帧之后,服务器端视该流的状态ؓhalf closed (remote)?/span>
相应圎ͼ客户端通过reserved (remote)状态的接收到服务器端推送的响应的头部,然后会视该流的状态ؓhalf closed (local)?/span>
    扩展一下,服务器端推送中被保留的的状态在变ؓhalf closed(local/remote)之后才可能被用于接收/发送被推送的响应的体部,也就是DATA帧?/span>

6. closed
    当一端用一个流发送或接收到RST_STREAM帧,或通过状态ؓhalf closed (local/remote)的流接收/发送包含有END_STREAM标签的之后Q都会视q个的状态ؓclosed?/span>
    closed状态预C着的l结Q处于该状态的将只能发送或接收PRIORITY帧。但有一个特例。即Q如果通过使用half closed (local/remote)状态的去接收或发送包含有END_STREAM标签的(HEADERS或DATA)Q以使该的状态变为closedQ那么在此之后的较短旉内,仍然可以接收WINDOW_UPDATE或RST_STREAM帧?/span>


John Jiang 2016-10-08 21:17 发表评论
]]>
探烦HTTP/2: HPACK协议q??http://www.dentisthealthcenter.com/jiangshachina/archive/2016/09/24/431837.htmlJohn JiangJohn JiangSat, 24 Sep 2016 12:29:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2016/09/24/431837.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/431837.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2016/09/24/431837.html#Feedback0http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/431837.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/431837.html
探烦HTTP/2: HPACK协议q?/span>
探烦HTTP/2pd的第一文章已l介l了HTTP 2协议Q本文则简q用于HTTP/2头部压羃?a >HPACK协议?2016.10.01最后更?

1. 基本原理
    HPACK头部压羃的基本原理就是用烦引表?a >Huffman~码。在压羃(~码)与解?解码)q程Q可指定的头部字段(包含字段名与字段?存储在烦引表中。烦引表中的每一个条目由索引(一个整?Q字D名和字D值组成。对于存在烦引表中的头部字段Q在~码时可以仅使用索引作ؓ该字D늚代表Q在解码旉过该烦引从表中查找出对应的字段。对于其它的字符Ԍ则可以用Huffman~码q行压羃?br /> 1.1 索引?/strong>
    索引表由静态表与动态表l成。静态表由HPACK协议预定义的61个常用的头部字段l成Q其中大部分字段的gؓI。静态表是只ȝQ其中的条目及其位置均不可更攏VHPACK协议中的附录A列出了全部的静态表条目。动态表也由一pd头部字段l成Q但其中的元素不固定Q在实际操作中可以插入新的条目,也允许删除已有的条目?br />     HPACK协议要求静态表与动态表合ƈ在同一个存储空间中Q其中静态表|于前部Q动态表紧随其后。那么在整个索引表空间中Q动态表的第一个条目的索引是62。动态表的维护原则是先进先出(FIFO)。当向动态表中增加条目时Q将L从第62位插入,原有的条目将全部向右Ud一个位|。当从动态表中删除条目时Q将L从最后一位进行删除?br />     虽说Q协议要求将静态表与动态表合ƈ在一P但这只是逻辑上的要求。只要动态表的烦引是?2开始,那么各个实现可以Ҏ自己的喜好自由地使用存储数据l构。比如,可以静态表单独攑֜一个不可变的数l中Q而动态表由另一个链表进行存储,q样可能会便于插入和删除条目。只不过Q这个链表中元素的下标与动态表中条目的索引之间相差62?br />     (动?索引表中的条目允讔R复?br /> 1.2 Huffman~码
    Huffman~码是一U用于无损数据压~的权\径编码算法。在使用该算法时Q需要一张所有被~码字符的权?出现频率)代码表。在对大量的HTTP头部hq行l计之后Q得Z一份适用于HPACK的Huffman代码表,由协议中?a >附录B列出?br />
    必须注意的是QHPACK协议q不要求该协议的实现一定要使用索引表,即便某个字段已经存在于烦引表中了。而且也不要求一定要对字W串实施Huffman压羃。也是_理论上,在编码时可以不对头部字段q行M形式的压~,而只需所有的字符转化成字节Ş式?br />
2. 基本数据cd表示?/span>
    HPACK协议使用的基本数据类型只有两U:整数Q字W串。该协议使用整数去表C烦引和字符串的长度。头部字D名和g出现的数字,只会被当作字W串q行处理?br /> 2.1 整数表示?/strong>
    HPACK在表C整数时q不是把它简单的转换成二q制形式。因为HPACK希望每一个整数的表示能够从某?比特位字?octetQ下文将其简写ؓ"字节")中的M一个比特位开始,但L要在某个字节的最后一个比特位l束。比如表C?27Q让它从字节的第一个比特位开始填充,肯定会在最后一个比特位l束Q如下图所C:
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
+---+---+---+---+---+---+---+---+
如果W一个比特位被其它值占???"代表)Q只能从W二个比特位开始填充呢Q结果依然只需要一个字节,如下所C:
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| ? | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
+---+---+---+---+---+---+---+---+
但如果是从第三个比特位开始填充呢Q这时会发现一个字节已l不够了Q必要W二个字节。但能否表示成如下Ş式呢Q?br />
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| ? | ? | 1 | 1 | 1 | 1 | 1 | 1 |
+---+---+---+-------------------+
| 1 | ? | ? | ? | ? | ? | ? | ? |
+---+---+---+---+---+---+---+---+
q显然不W合HPACK协议的要求,因ؓ它希望能够在某个字节的最后一个比特位l束q个表示。ؓ辑ֈq一目的QHPACK协议设计Z一U如下图所C的表示法,
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| ? | ? | 1 | 1   1   1   1   1 |
+---+---+---+-------------------+
| 1 |    Value-(2^N-1) LSB      |
+---+---------------------------+
               ...
+---+---------------------------+
| 0 |    Value-(2^N-1) MSB      |
+---+---------------------------+
W一个字节中能够被用来填充整数表CZ的比特位?上图中的?)被称为prefix。下面是该表C法的Java语言实现Q?br />
public void encodeInteger(int value, int prefix) {
    
if (value >> prefix <= 0) {
        printBinary(value);
    } 
else {
        
int number = (1 << prefix) - 1;
        printBinary(number);
        
for (value -= number; value >= 128; value /= 128) {
            printBinary(value 
% 128 + 128);
        }
        printBinary(value);
    }
}

private void printBinary(int value) {
    System.out.println(String.format(
"%8s", Integer.toBinaryString(value)).replace(" ""0"));
}
Ҏ上述法可知Q当prefix?Ӟ127的表C法如下图所C:
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| ? | ? | 1 | 1 | 1 | 1 | 1 | 1 |
+---+---+---+-------------------+
| 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
+---+---+---+-------------------+
2.2 字符串表C法
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| H |    String Length (7+)     |
+---+---------------------------+
|  String Data (Length octets)  |
+-------------------------------+
HPACK协议使用上图展示的表C法Q它׃部分l成Q?br /> [1]Huffman标志Q表C字符串是否ؓHuffman~码Q占用一个比特位?br /> [2]字符串长度,一个?.1节所q方法表C的整数Q其中prefix??br /> [3]字符串倹{若Huffman标志?Q该值就是原始字W串的字节,否则该值是lHuffman~码q的数据。由于经Huffman~码q的数据q不L能在一个字节的最后一个比特位处结束,所以可能会使用EOS(end-of-string)W号q行填充?br />
3. 头部字段表示?/span>
    有了W?节介l的基本数据cd的表C法作ؓ基础Q现在就可以阐述头部字段的表C法了。HPACK协议字D表C法分成3U类型。在表示法开头有一个或若干个比特位用于表示cd?br /> 3.1 已在索引表的头部字段
    cd标识占用1个比特位Qgؓ1。烦引用prefix?的整数表C法。在解码Ӟ不会更新动态表?br />
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 1 |        Index (7+)         |
+---+---------------------------+
3.2 置入烦引表的头部字D?/strong>
    cd标识占用2个比特位Qgؓ01。在解码Ӟ会向动态表内插入新条目。这U类型又被分成两U情况:
[1]头部字段名已在烦引表中,字段名烦引用prefix?的整数表C法Q而字Dg用字W串表示法?br />
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 1 |      Index (6+)       |
+---+---+-----------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+
[2]头部字段名不在烦引表中,字段名和字段值均使用字符串表C法Q而第一个字节的?个比特位均?填充?br />
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 1 |           0           |
+---+---+-----------------------+
| H |     Name Length (7+)      |
+---+---------------------------+
|  Name String (Length octets)  |
+---+---------------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+
3.2 暂不|入索引表的头部字段
    cd标识占用4个比特位Qgؓ0000。在解码Ӟ不向动态表内插入新条目。这U类型又被分成两U情况:
[1]头部字段名已在烦引表中,字段名烦引用prefix?的整数表C法Q而字Dg用字W串表示法?br />
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 |  Index (4+)   |
+---+---+-----------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+
[2]头部字段名不在烦引表中,字段名和字段值均使用字符串表C法Q而第一个字节的?个比特位均?填充?br />
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 |       0       |
+---+---+-----------------------+
| H |     Name Length (7+)      |
+---+---------------------------+
|  Name String (Length octets)  |
+---+---------------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+---+---------------------------+
3.3 怸|入索引表的头部字段
    cd标识占用4个比特位Qgؓ0001。在解码Ӟ不向动态表内插入新条目。这U类型又被分成两U情况:
[1]头部字段名已在烦引表中,字段名烦引用prefix?的整数表C法Q而字Dg用字W串表示法?br />
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 |  Index (4+)   |
+---+---+-----------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+
[2]头部字段名不在烦引表中,字段名和字段值均使用字符串表C法Q而第一个字节的?个比特位均?填充?nbsp;  
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 |       0       |
+---+---+-----------------------+
| H |     Name Length (7+)      |
+---+---------------------------+
|  Name String (Length octets)  |
+---+---------------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+
    可以发现Q?.2节与3.3节中的表C法除了cd标识不同之外Q其它的都完全相同。那么它们的区别是什么呢Q类?000表示的字D在l过多次解码与编码时Q可能会被某个中介者置入烦引表中。而类?001表示法强调了该字D|论在M时候都不可|入索引表。类?001可用于表C包含有敏感信息Q如密码Q的字段|以避免对q些D行压~时产生的风险?br />
4. 动态表的管?/span>
    动态表中的条目被认为是有尺寸的Q其计算公式为:字段名的字节长度+字段值的字节长度+32。字D名/值的长度是指它们的原始字节的长度Q而非l过Huffman~码后的字节的长度?br />     动态表的尺寸就是其中所有条目的寸之和。动态表的最大尺寸是有限的,可以通过下面的整数表C法来通知协议的现实去改变动态表的最大尺寸?br />
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 |   Max size (5+)   |
+---+---------------------------+
    当插入新的条目或改变动态表的最大尺寸时Q可能导致已有的一个或多个条目被逐出Q甚xI整个动态表。将动态表的最大尺寸设|ؓ0是合法的Q实际上Q这是一U常用的清空动态表的途径?/div>

John Jiang 2016-09-24 20:29 发表评论
]]>
探烦HTTP/2: 初试HTTP/2(?http://www.dentisthealthcenter.com/jiangshachina/archive/2016/09/20/431814.htmlJohn JiangJohn JiangTue, 20 Sep 2016 08:42:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2016/09/20/431814.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/431814.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2016/09/20/431814.html#Feedback1http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/431814.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/431814.html
探烦HTTP/2: 初试HTTP/2
目前支持HTTP/2的服务器端与客户端实现已有不,探烦HTTP/2pd的第二篇分别以Jetty和curl作ؓ服务器端和客LQ描qCHTTP/2试环境的搭E。本文还用这个测试环境去展示Jetty在实现HTTP/2时的一个局限和一个Bug?2016.09.22最后更?

1. HTTP/2的实?/span>
    目前已经有众多的服务器端和客L实现了对HTTP/2的支持。在服务器端Q著名的Apache httpd?.4.17版,Nginx?.9.5版,开始支持HTTP/2。在客户端,L的浏览器Q如ChromeQFireFox和IEQ的最新版均支持HTTP/2Q但它们都只支持q行在TLS上的HTTP/2(即h2)。用Java语言实现的,则有Jetty和NettyQ它们都实现了服务器端和客户端。此处有一份HTTP/2实现的列表:https://github.com/http2/http2-spec/wiki/Implementations
    另外Q还有一些工h持对HTTP/2的分析与调试Q如curl和WireShark。这里也有一份此cdL列表Q?a >https://github.com/http2/http2-spec/wiki/Tools

2. 服务器端
    作ؓJavaE序员,选用一ƾ用Java语言~写的开源HTTP/2服务器端实现g是很自然的结果。实际上Q在日后的研I中Q我们也需要查看服务器端的源代码。这对于深入地理解HTTP/2Qƈ发现实现中可能的问题Q具有现实意义?/span>
    本文选择Jetty的最新版?.3.11作ؓ服务器端。Jetty是一个成熟的Servlet容器Q这为开发Web应用E序提供了极大便利。而本文第1节中提到的Netty是一个传输层框架Q它专注于网l程序。可以用Nettyd发一个Servlet容器Q但q显然不如直接用Jetty方便?/span>
    安装和配|Jetty是一件很Ҏ的事情,具体q程如下所C?/span>
    假设此时已经下蝲q解压好了Jetty 9.3.11的压~文Ӟ目录名ؓjetty-9.3.11。在其中创徏一个test-base子目录,作ؓ要创徏的Jetty Base的目录?/span>
$ cd jetty-9.3.11
$ mkdir test-base
$ cd test-base
在创建BaseӞ加入支持httpQhttpsQhttp2(h2)Qhttp2c(h2c)和deploy的模块?/span>
$ java -jar ../start.jar --add-to-startd=http,https,http2,http2c,deploy

ALERT: There are enabled module(s) with licenses.
The following 1 module(s):
 + contains software not provided by the Eclipse Foundation!
 + contains software not covered by the Eclipse Public License!
 + has not been audited for compliance with its license

 Module: alpn
  + ALPN is a hosted at github under the GPL v2 with ClassPath Exception.
  + ALPN replaces/modifies OpenJDK classes in the java.sun.security.ssl package.
  + http://github.com/jetty-project/jetty-alpn
  + http://openjdk.java.net/legal/gplv2+ce.html

Proceed (y/N)? y
INFO: server          initialised (transitively) in ${jetty.base}\start.d\server.ini
INFO: http            initialised in ${jetty.base}\start.d\http.ini
INFO: ssl             initialised (transitively) in ${jetty.base}\start.d\ssl.ini
INFO: alpn            initialised (transitively) in ${jetty.base}\start.d\alpn.ini
INFO: http2c          initialised in ${jetty.base}\start.d\http2c.ini
INFO: https           initialised in ${jetty.base}\start.d\https.ini
INFO: deploy          initialised in ${jetty.base}\start.d\deploy.ini
INFO: http2           initialised in ${jetty.base}\start.d\http2.ini
DOWNLOAD: http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.5.v20150921/alpn-boot-8.1.5.v20150921.jar to ${jetty.base}\lib\alpn\alpn-boot-8.1.5.v20150921.jar
DOWNLOAD: https://raw.githubusercontent.com/eclipse/jetty.project/master/jetty-server/src/test/config/etc/keystore?id=master to ${jetty.base}\etc\keystore
MKDIR: ${jetty.base}\webapps
INFO: Base directory was modified
    注意Q在上述q程中,会根据当前环境变量中使用的Java版本(此处?.8.0_60)M载一个对应的TLS-ALPN实现jar文g(此处为alpn-boot-8.1.5.v20150921.jar)Q该jar会用于对h2的支持。当启动JettyӞ该jar会被Java的Bootstrap class loader加蝲到类路径中?/span>
    创徏一个最单的Web应用Q它在根目录下包含一个文本文件indexQ内容ؓ"HTTP/2 Test"?/span>再包含一个简单的ServletQ代码如下:
package test;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestServlet extends HttpServlet {

    
private static final long serialVersionUID = 5222793251610509039L;

    @Override
    
public void doGet(HttpServletRequest request, HttpServletResponse response)
            
throws ServletException, IOException {
        response.getWriter().println("Test");
    }

    @Override
    
public void doPost(HttpServletRequest request, HttpServletResponse response)
            
throws ServletException, IOException {
        doGet(request, response);
    }
}
web.xml主要是定义了一个ServletQ具体内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    metadata-complete="false" version="3.1">

    
<welcome-file-list>
        
<welcome-file>index</welcome-file>
    
</welcome-file-list>

    
<servlet>
        
<servlet-name>test</servlet-name>
        
<servlet-class>test.TestServlet</servlet-class>
    
</servlet>
    
<servlet-mapping>
        
<servlet-name>test</servlet-name>
        
<url-pattern>/test/*</url-pattern>
    
</servlet-mapping>
</web-app>
该应用的部v路径为jetty-9.3.11/test-base/webapps/test.war。在该WAR文g所在的目录下,创徏一个test.xmlQ其内容如下所C:
<?xml version="1.0"  encoding="ISO-8859-1"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">

<Configure class="org.eclipse.jetty.webapp.WebAppContext">
  
<Set name="contextPath">/</Set>
  
<Set name="war"><SystemProperty name="jetty.base" default="."/>/webapps/test.war</Set>
</Configure>
启动Jetty服务器,使用默认的HTTP和HTTPS端口Q分别ؓ8080?443?/span>
$ java -jar ../start.jar
2016-09-15 21:15:51.190:INFO:oejs.Server:main: jetty-9.3.11.v20160721
2016-09-15 21:15:51.237:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///D:/http2/jetty/jetty-9.3.11/test-base/webapps/] at interval 1
2016-09-15 21:15:52.251:INFO:oejw.StandardDescriptorProcessor:main: NO JSP Support for /test.war, did not find org.eclipse.jetty.jsp.JettyJspServlet
2016-09-15 21:15:52.313:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@4520ebad{/test.war,file:///D:/http2/jetty/jetty-9.3.11/test-base/webapps/test.war/,AVAILABLE}{D:\http2\jetty\jetty-9.3.11\test-base\webapps\test.war}
2016-09-15 21:15:52.391:INFO:oejw.StandardDescriptorProcessor:main: NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet
2016-09-15 21:15:52.391:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@711f39f9{/,file:///D:/http2/jetty/jetty-9.3.11/test-base/webapps/test.war/,AVAILABLE}{/test.war}
2016-09-15 21:15:52.532:INFO:oejs.AbstractConnector:main: Started ServerConnector@1b68ddbd{HTTP/1.1,[http/1.1, h2c, h2c-17, h2c-16, h2c-15, h2c-14]}{0.0.0.0:8080}
2016-09-15 21:15:52.735:INFO:oejus.SslContextFactory:main: x509=X509@e320068(jetty,h=[jetty.eclipse.org],w=[]) for SslContextFactory@1f57539(file:///D:/http2/jetty/jetty-9.3.11/test-base/etc/keystore,file:///D:/http2/jetty/jetty-9.3.11/test-base/etc/keystore)
2016-09-15 21:15:52.735:INFO:oejus.SslContextFactory:main: x509=X509@76f2b07d(mykey,h=[],w=[]) for SslContextFactory@1f57539(file:///D:/http2/jetty/jetty-9.3.11/test-base/etc/keystore,file:///D:/http2/jetty/jetty-9.3.11/test-base/etc/keystore)
2016-09-15 21:15:53.234:INFO:oejs.AbstractConnector:main: Started ServerConnector@4b168fa9{SSL,[ssl, alpn, h2, h2-17, h2-16, h2-15, h2-14, http/1.1]}{0.0.0.0:8443}
2016-09-15 21:15:53.249:INFO:oejs.Server:main: Started @3940ms
    Ҏ上述日志可知QJetty启用了Web应用test.warQ还启动了两个ServerConnectorQ一个支持h2cQ另一个支持h2。值得注意的是Q这两个ServerConnectorq分别支持h2c-17, h2c-16, h2c-15, h2c-14和h2-17, h2-16, h2-15, h2-14。这是因为,HTTP/2在正式发布之前,先后发布?8个草案,其编号ؓ00-17。所以,q里的h2c-XX和h2-XX指的是WXX可案?/span>

3. 客户?/span>
    其实最方便的客L是览器了。只要用的FireFox或Chrome版本不是太老,肯定都已l支持了HTTP/2Q而且q一功能是默认打开的。也是_当用FireFox去访问前面所部v的Web应用Ӟ是在用HTTP/2Q但你不会感觉到q种变化。用FireFox提供的Developer Tools中的Network工具查看服务器端的响应,会发现HTTP版本为HTTP/2.0。但此处希望q个客户端能够提供更Z富的与服务器端进行交互的功能Q那么浏览器ƈ不合适了?br />    Jetty也实C支持HTTP/2的客LQ但q个客户端是一个APIQ需要编写程序去讉KHTTP/2服务器端。而且Q目前该API的设计抽象层ơ较低,需要应用程序员对HTTP/2协议Q比如各UQ有较深入的了解。这对于初涉HTTP/2的开发者来_昄很不合适。本文选择使用C语言~写的一个工P其实也是HTTP/2的客L实现之一Qcurl?/span>
    curl在支持HTTP/2Ӟ实际上是使用了nghttp2的C库,所以需要先安装nghttp2。另外,Z让curl支持h2Q就必须要有TLS-ALPN的支持。那么,一般地q需要安装OpenSSL 1.0.2+?/span>
    |络上关于在Linux下安装支持HTTP/2的curl的资源有很多Q过Eƈ不难Q但有点儿繁Q要安装的依赖比较多Q本文就不赘qC。如果是使用WindowsQ笔者比较推荐通过Cygwin来安装和使用curl。在Windows中安装Cygwin非常单,在Cygwin中执行各U命令时Q感觉上如同在使用LinuxQ尽它q不是一个虚拟机。通过Cygwin安装curlQ它会自动地安装所需的各U依赖程序和库?/span>
    在笔者的机器上,通过查看curl的版本会出现如下信息Q?/span>
curl 7.50.2 (x86_64-unknown-cygwin) libcurl/7.50.2 OpenSSL/1.0.2h zlib/1.2.8 libidn/1.29 libpsl/0.14.0 (+libidn/1.29) libssh2/1.7.0 nghttp2/1.14.0
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: Debug IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets Metalink PSL
׃可知Q笔者用的curl版本?.50.2Qnghttp2版本?.14.0Q而OpenSSL版本?.0.2h?/span>

4. W一ơ尝?/span>
    在第一ơ尝试中Q只需要简单地讉KW?节中部v的Web应用中的静态文本文件indexQ以感受下h2cQ完整命令如下:
$ curl -v --http2 http://localhost:8080/index
在输Z包含有如下的内容Q?/span>
...
> GET /index HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.50.2
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAP__
>
...
< HTTP/1.1 101 Switching Protocols
* Received 101
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
...
< HTTP/2 200
< server: Jetty(9.3.11.v20160721)
< last-modified: Wed, 14 Sep 2016 12:52:32 GMT
< content-length: 11
< accept-ranges: bytes
<
...
HTTP/2 Test
">"是客L发送的hQ?<"是服务器端发送的响应Q?*"是curl对当前过E的说明?/span>l合本系?a href="http://www.dentisthealthcenter.com/jiangshachina/archive/2016/09/19/431811.html">W一文?/a>中所q的HTTP 2协议Q可以有以下的基本理解?/span>
[1]客户端发起了一个HTTP/1.1的请求,其中携带有Upgrade头部Q要求服务器端升U到HTTP/2(h2c)?/span>
> GET /index HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.50.2
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAP__
>
[2]服务器端同意升Q返回响?101 Switching Protocols"Q然后客L收到?01响应QHTTP/2q接q行认?/span>
< HTTP/1.1 101 Switching Protocols
* Received 101
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
[3]服务器端响应最l结果。状态行中出现的HTTP版本为HTTP/2Q状态代码ؓ200Q且后面没有跟着"OK"。最后输Zindex文g的内?HTTP/2 Test"?/span>
< HTTP/2 200
< server: Jetty(9.3.11.v20160721)
< last-modified: Wed, 14 Sep 2016 12:52:32 GMT
< content-length: 11
< accept-ranges: bytes
<
...
HTTP/2 Test

5. 一个局?/span>
    q次Q在发v的请求中包含体部Q命令如下:
$ curl -v --http2 -d "body" http://localhost:8080/index
在输Z包含有如下的内容Q?/span>
...
> POST /index HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.50.2
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAP__
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
...
< HTTP/1.1 200 OK
< Last-Modified: Wed, 14 Sep 2016 12:52:32 GMT
< Accept-Ranges: bytes
< Content-Length: 11
...
HTTP/2 Test
    和第4节中的输行比较,会发现缺了"101 Switching Protocols"那一D,而且最l响应状态行中出现的HTTP版本是HTTP/1.1。这p明服务器端不同意升Q后面l用HTTP/1.1。刚刚部|的Jetty未做M改变怎么会突然不支持HTTP/2了呢Q或者这是curl的问题?其实Q?/span>q是因ؓJetty服务器端在实现h2c时不支持h中包含体部。另外,Apache httpd也有同样的问题。如果是使用h2Q则没有q个限制。这背后的原因超Z本文的范_不作表述?/span>

6. 一个Bug
    在这ơ尝试中Q测试一下两端对100-continue的支持。如果请求中使用了头?Expect: 100-continue"Q那么正常地该请求要有体部。但׃在第5节中介绍的问题,此时不能再用h2cQ而只能用h2。另外,q次不访问静态文Ӟ而是讉KServlet(此处?test)。完整命令如下:
$ curl -vk --http2 -H "Expect: 100-continue" -d "body" https://localhost:8443/test
在输出的最后出C如下信息Q?/span>
curl: (92) HTTP/2 stream 1 was not closed cleanly: CANCEL (err 8)
q其实是Jetty的一?a >BugQ正在开发中?.3.12已经修复了它?/span>

7. 结
    HTTP/2依然是新潮的技术,对各家的实现Q无论是服务器端Q客LQ还是分析工P都要持有一份怀疑态度。这些实现和工具都是E序Q都有可能存在bug。而且协议对许多细节没有作定,各家都会发挥自己的想像力。比如,Apache httpd和Jetty在实现服务器端推送时Q其方式׃相同?br />    在开发自qHTTP/2实现或应用的时候,需要同时用已有的不同服务器端和客L去部|多套测试环境进行对比分析?br />


John Jiang 2016-09-20 16:42 发表评论
]]>
探烦HTTP/2: HTTP 2协议q??http://www.dentisthealthcenter.com/jiangshachina/archive/2016/09/19/431811.htmlJohn JiangJohn JiangMon, 19 Sep 2016 03:36:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2016/09/19/431811.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/431811.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2016/09/19/431811.html#Feedback0http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/431811.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/431811.html
探烦HTTP/2: HTTP/2协议q?/span>
HTTP/2的协议包含着两个RFCQHypertext Transfer Protocol Version 2 (RFC7540)Q即HTTP/2QHPACK: Header Compression for HTTP/2 (RFC7541)Q即HPACK。RFC7540描述了HTTP/2的语义,RFC7541则描qC用于HTTP/2的头部压~的格式。本文只涉及HTTP/2协议Q本pd的后l文章将会涉及HPACK协议?2016.10.13最后更?

1. HTTP/2要解决的问题
     HTTP/1.0只允许在一个TCPq接中出C个请求。后来的HTTP/1.1虽然引入了请求流水线Q以允许在一个连接中发送多个请求,但这只是部分地解决了hq发的问题。服务器端在q回响应Ӟq是必须要按照它接收到的h的顺序进行返回。如果排在前面的响应要消耗较长的旉Q那依然会对后面的响应的造成dQ亦即线头阻?Head-of-line blocking)。所以,客户端必要使用多条q接d起多个的h以实现ƈ发,q进而减gq。更大的q发会增大服务器的负载,也会占用更大的网l带宽。另外,头部通常会包含有大量的信息,如cookieQ而这也会增加|络传输的开销?/span>
     HTTP/2允许在同一个TCPq接中交错地出现多个h与响应,亦即多工(Multiplex)。同Ӟ它用了一个高效的~码Ҏ对头部进行压~。HTTP/2q允许对hq行优先U排序,以便让更为重要的h得以更快的完成,q会q一步提高性能。HTTP/2q改变了服务器端只能被动地向客户端返回响应的定式Q允许服务器端主动地向客L推送数据,q就可以减少客户端发赯求的数量?/span>
     MQHTTP/2主要是解x能问题?/span>

2. 发vHTTP/2
     HTTP/2会用与HTTP/1相同的URI schemeQ即http和https。而且实现HTTP/2的服务器端也不会使用不同的端口去分别支持HTTP/1和HTTP/2。这h利于qxCHTTP/1升到HTTP/2。毕竟目前已部v的绝大部分网l服务都只支持HTTP/1Q当未来它们升到HTTP/2Ӟ如果换用了不同URI scheme或端口,那么肯定会对客户端生极大的影响。但是HTTP/2协议行在http和https上的HTTP/2分别定义了两个不同的标识W:h2c和h2。h2c中的"c"指的是cleartextQ即明文。本文后面会使用h2c指代q行在http2?直接使用TCP)的HTTP/2Q而用h2指代q行在https?使用TLS)的HTTP/2?/span>
     那么Q支持HTTP/2的客L如何知道它所q接的服务器端是否也支持HTTP/2呢?
     对于h2cQ支持HTTP/2的客L可以在发Lh中用HTTP/1.1的Upgrade头部d试要求服务器升到HTTP/2。该h的格式如下:
GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
HTTP2-Settings是一个经由BASE64~码q的字符Ԍ其原始内Ҏ客户端将要发送的SETTINGS帧的载荷Q即一些配|参数?/span>
     如果服务器端支持HTTP/2Q它响?101 Switching Protocols"Q表C可以进行升U。该响应的格式如下:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
     如果服务器端不支持HTTP/2Q则会忽略Upgradeh头部Q后l依然用HTTP/1.1?/span>
     对于h2Q会使用到协议Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension (RFC7301)Q即TLS-ALPN。该协议允许客户端和服务器端׃用何U版本的HTTPq行协商。如果TLS-ALPN在现实中q行良好的话Q也许某天还会用该Ҏd商用别的协议?/span>
     当客L与服务器端都同意使用HTTP/2Ӟ双方都需要各自发Z个连接序a(Connection Preface)以进行最后的认?/span>
     客户端在接收到服务器端的"101 Switching Protocols"响应(针对h2c)或TLSq接的第一个应用数据字?针对h2)之后会立卛_接序a。该序言的开头是"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"(其十六进制Ş式ؓ"0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a")(1)Q后面必d跟一个SETTINGS帧,哪怕这个是空的?/span>
     服务器端的连接序a则由一个SETTINGS帧构成,该必须是服务器端在HTTP/2q接中发送的W一个。这个SETTINGS帧可以ؓI,也可以包含一些希望客L如何与自p行通信的必要配|信息?/span>

3. ?Frame)
     HTTP/2消息使用二进制格?实际~码时用十六进制书?Q相比于文本格式Q这样可以提高消息处理的效率。HTTP/2消息的最单元ؓ帧,它由头部与蝲?Payload)l成。每个的长度必L一个或多个8比特位字?octetQ下文将其简写ؓ"字节")?/span>
     帧头部依ơ包含有如下?个字D:
     长度(Length)Q该字段占用24个比特位Q代表载荷的长度。该长度是一?4位的无符h数?/span>
     cd(Type)Q该字段占用8个比特位Q代表的类型?/span>
     标志(Flags)Q该字段占用8个比特位Q代表所定义的一个或多个标志。ƈ不是所有的帧都定义了标志?/span>
     保留?R)Q该字段占用1个比特位Q其语义未被定义。在d帧时Q该位需要被忽略Q但在发送Ӟ该位需要保持ؓ0(0x0)?/span>
     标识符(Stream Identifier)Q该字段占用31个比特位Q代表该帧所在流的标识符?/span>
     在头部之后,紧接着的就是蝲荗蝲Ll构与内容完全由帧的cd军_Q它的长度也是不定的?br />
     HTTP/2定义了如?0U不同类型的帧?/span>
     DATAQ用于携带一l长度不定的字节。一个或多个DATA可作求或响应的蝲荗?/span>
     HEADERSQ用于开启一个流Qƈ可携带一个头部块片断。头部块指由一个HEADERS/PUSH_PROMISE帧和紧随它的零到多个CONTINUATION帧组成的集合Q因为只有它们才可能携带头部信息。这个集合可被分割成一个或一l字节,q样的字节被UCؓ头部块片断。头部块中各个特定类型的帧必ȝ紧相邻,不能出现其它cd的?/span>
     PRIORITYQ用于指定发送端的流优先U?/span>
     RST_STREAMQ用于立即终止流。当希望取消一个流或发生错误时Q就可发送RST_STREAM帧?/span>
     SETTINGSQ用于携带可以媄响两端之间通信方式的配|参数。SETTINGS帧定义了一个ACK标志Q用于指C帧所讄的参数是否已被接收端L。当收到一个SETTINGS且其中的ACK标志?Ӟ接收端必d可能快的应用其中已被更新的参数?/span>
     PUSH_PROMISEQ用于向接收端通知发送端要创徏的流。当接收端接收到该Ӟ新的尚未被发送端创徏Q但发送端承诺会创。该帧用于实现HTTP/2的重要特?服务器端推?Server Push)"?/span>
     PINGQ用于测量发送端与接收端之间的最往q时间。这与用众所周知的ping命o的目的相|是ؓ了测试某个空闲的q接是否q可用?/span>
     GOAWAYQ用于发起对q接的关闭,或触发严重的错误条g。该帧允怸端,在完成对之前已创建的的处理的同Ӟ优雅地停止接收新的流。一端在创徏新的,另一端在发送GOAWAYQ这两者之间天然存在着竞争关系。ؓ了就对这U情况,发送端在发送GOAWAY时会让它携带?该发送端所知晓?接收端最后创建的的标识W,当该GOAWAY被发送之后,发送端会忽视由接收端创徏的Q何一个标识符比该标识W大的流?/span>
     WINDOW_UPDATEQ用于流量控制。该帧的载荷׃个单比特保留位和一?1比特位的无符h数组成。该整数向该帧的接收端指CZ其向当前量控制H口所能增加传输量的倹{?/span>
     CONTINUATIONQ用于l发送头部块片断。只要同一个流中前面的帧是HEADERSQPUSH_PROMISE或CONTINUATIONQƈ且该帧没有设|END_HEADERS标志Q那么可无限量地发送CONTINUATION帧?/span>
     部分帧,DATAQHEADERS和PUSH_PROMISEQ的载荷中可能包含填?Padding)。填白在业务上没有实际的用处Q它的出现是Z安全目的。比如,可以用它来扰乱实际数据的长度Q以减轻特定的HTTPd?br />
     发送端发送的帧的最大长度要重接收端设定的SETTINGS_MAX_FRAME_SIZE的倹{但该值的范围要介?^14?^24-1个字节之间?/span>

4. ?Stream)
     是用于在客L与服务器端之间进行传送的通道Q同一个TCPq接中可以同时有多个,如下图所C,
┌────────┐          Connection           ┌────────┐
│        │ ============================= │        │
│        │    --------------------- <-- Stream    │
│        │    ┌─────┐┌─────────┐┌─┐      │        │
│        │    └─────┘└─────────┘└─┘ <-- Frame     │
│        │    ---------------------      │        │
│ Client │                               │ Server │
│        │    ----------                 │        │
│        │    ┌──┐┌────┐                 │        │
│        │    └──┘└────┘                 │        │
│        │    ----------                 │        │
│        │ ============================= │        │
└────────┘                               └────────┘
服务器端和客L可以交错地向同一个连接中的不同流中传送。可以把一个流看作HTTP/1中的一个连接。客L与服务器端在同一个流中的交互依然遵@发送请?{待响应模式。两端都可以创徏新的,׃nҎ创徏的流Q也可以关闭Ҏ创徏的流。在流中的序是有意义的,接收端会以接收到的顺序去处理帧?br />      每个都有一个标识符Q是一?1比特位的无符合整数。在同一个连接中Q流标识W是唯一的。由客户端创建的的标识Wؓ奇数Q由服务器创建的的标识Wؓ偶数。但标识Wؓ0的流可看作连接,用于q接控制信息Q创建新的流时不可用该标识W。同一个连接中的Q何一个流的标识符都不可重用,即便q个已被关闭了。对于长旉没有中断的连接,可能会出现标识符不够用的情况Q那时就必须强制创徏一个新的连接?br />      HTTP/2协议为流的生命周期定义了7U状?sup>(2)QidleQreserved(local)Qreserved(remote)QopenQhalf closed(local)Qhalf closed(remote)和closed。当一端接收或发送头部块?帧DATA和HEADERS?标志RST_STREAM后可使流的状态发生{变?/span>
     使用来实现多工׃引v对TCPq接使用的竞争,q会造成的d。基于WINDOW_UPDATE的流量控制方案可以确保相同连接中的流怺之间不会产生破坏性干扰。流量控制可以作用于两个层面Q即单个或整个q接。只有DATA需要遵守流量控Ӟ所有其它的帧所有消耗的I间均不会占用流量控制窗口。HTTP/2协议只是定义了WINDOW_UPDATE帧的l构和语义,协议的实现可以选择M适用自己的流量控制算法?/span>
     可以有优先U。客L在创Z个新的流Ӟ可在HEADERS中指定优先权重。在后箋M旉Q通过PRIORITY可以改变的优先U权重。在q发能力有限的情况下Q高权重的帧会被优先传送。权重的值必M??56之间Q默认权重ؓ16?/span>与之间还可以有依赖关p,q种关系会组成一依赖关pL。一个流能够指定自己成ؓ另一个流的子。这一q程Q可以是非排他的Q也可以是排他的。非排他性依赖,是指一个流在将自己变成另一个流的子的q程中,允许另一个流q有别的子流Q即允许有自q兄弟存在。排他性依赖,指在前述q程中,不允许另一个流q有别的子流。如果另一个流已经有子了Q那么该会把所有潜在的兄弟先变成自己的子,然后再自己成ؓ另一个流的唯一子流。其实,排他性依赖的作用是Z能够打破已有的关pLQ在既成的父子节点中插入新的节点。否则,只能为已有节Ҏ加子节点Q那么关pL不可能q行重构。所有的在被创建时Q默认成为标识符?x0的流的子。在"服务器端推?中生成的"推?将自动地成为生成该推送流的流的子,光认权重也?6?/span>

5. 消息交换
5.1 h/响应交换
     HTTP/2沿袭了HTTP/1的语义,x有的h与响应语义均得到了保留,管传递这些语义的语法已经改变了?/span>
     一个HTTP/2消息由如下几个部分组成:
     [1]仅对于响应消息,可以包含一个携带有1xx响应头部的头部块。该头部块由一个HEADERS帧和紧随它的零到多个CONTINUATION帧组成?/span>
     [2]一个头部块。该头部块由一个HEADERS帧和紧随它的零到多个CONTINUATION帧组成?/span>
     [3]零到多个携带有体?Body)消息的DATA帧。HTTP/1中用的"分块(chunked)"体部不适用于HTTP/2。因Z个体部可由多个DATA帧组成,所以HTTP/2的体部天然就是可分块的?/span>
     [4]一个可能存在的包含着N消息的头部块。该头部块由一个HEADERS帧和紧随它的零到多个CONTINUATION帧组成?br />
     HTTP/2仍然沿用HTTP/1中的头部字段Q但字段名称中的字母必须全部为小写。另外,q将HTTP/1消息开始行(h中的h?/a>与响应中?a >状态行)中的消息Q分解成了若q伪头部字段Q此cdD均以冒?:)开头?/span>
     HTTP/1h行格式ؓ"method request-target HTTP-version"Q对应的HTTP/2伪头部字D|:method=method?path=request-targetQ但HTTP-version无对应字D,默认为HTTP/2?/span>
     HTTP/1状态行格式?HTTP-version status-code reason-phrase"Q对应的HTTP/2伪头部字D|:status=status-code。但HTTP-version无对应字D,默认为HTTP/2Qreason-phrase也无对应字段Q因为可以通过状态代码查扑ֈ其对应的reason-phrase。HTTP/2协议是在量减少冗余消息?/span>
     HTTP/2协议qؓh头部定义了另外两个伪字段Q?/span>
     :schemeQURI中的scheme部分。它可以不仅仅是http或httpsQ因为有时候可能会与非HTTP服务q行交互?/span>
     :authorityQURI中的授权部分。即Qscheme://user:password@host:port/path?query#fragment中的"user:password@host:port"?/span>
     HTTP/2协议8.1.3节中l出一些简单示例,展示了如何将HTTP/1消息对应到HTTP/2消息?/span>
5.2 服务器端推?/span>
     HTTP/2的服务器端推送是传统的请?响应模式的一U特DŞ式。服务器端在收到客户端的h(主请?之后Qؓ了主动向客户端推送更多的内容Q会自动地生成若q新的请?推送请?。服务器向客L发送的响应中,不仅包含对主h的响?d?Q还包含Ҏ送请求的响应(推送响??/span>
     客户端可以通过发送包含有SETTINGS_MAX_CONCURRENT_STREAMS参数的SETTINGS帧去用服务器端推送,也可以通过发送RST_STREAM帧去取消已经发v的服务器端推送,但不能发送包含有END_STREAM标志的?br />
(1)"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"中的"PRI"?SM"合v来就?RRISM(镜)"。呵呵,HTTPbis工作l这是想表达什么意思呢 ;-)
(2)本系列的后箋文章解读了流的状态?br />


John Jiang 2016-09-19 11:36 发表评论
]]>
Play OpenJDK: 允许你的包名?java."开??http://www.dentisthealthcenter.com/jiangshachina/archive/2015/11/01/428010.htmlJohn JiangJohn JiangSun, 01 Nov 2015 12:06:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2015/11/01/428010.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/428010.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2015/11/01/428010.html#Feedback0http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/428010.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/428010.html
Play OpenJDK: 允许你的包名?java."开?/span>

本文是Play OpenJDK的第二篇Q介l了如何H破JDK不允许自定义的包名以"java."开头这一限制。这一技巧对于基于已有的JDK向java.*中添加新c还是有所帮助的?2015.11.02最后更?

无论是经验丰富的JavaE序员,q是Java的初学者,M有一些h或有意或无意地创Z个包名ؓ"java"的类。但Z安全斚w的考虑QJDK不允许应用程序类的包名以"java"开_即不允许javaQjava.fooq样的包名。但javaxQjavaexq样的包名是允许的?br />
1. 例子
比如Q以OpenJDK 8为基Q臆造这样一个例子。笔者想向OpenJDK贡献一个同步的HashMapQ即cSynchronizedHashMapQ而该cȝ包名׃ؓjava.util。SynchronizedHashMap是HashMap的同步代理,׃q两个类是在同一包内QSynchronizedHashMap不仅可以讉KHashMap的publicҎ与变量,q可以访问HashMap的protected和defaultҎ与变量。SynchronizedHashMap看v来可能像下面q样Q?br />
package java.util;

public class SynchronizedHashMap<K, V> {

    
private HashMap<K, V> hashMap = null;

    
public SynchronizedHashMap(HashMap<K, V> hashMap) {
        
this.hashMap = hashMap;
    }

    
public SynchronizedHashMap() {
        
this(new HashMap<>());
    }

    
public synchronized V put(K key, V value) {
        
return hashMap.put(key, value);
    }

    
public synchronized V get(K key) {
        
return hashMap.get(key);
    }

    
public synchronized V remove(K key) {
        
return hashMap.remove(key);
    }

    
public synchronized int size() {
        
return hashMap.size; // 直接调用HashMap.size变量Q而非HashMap.size()Ҏ
    }
}

2. ClassLoader的限?/span>
使用javacȝ译源文gSynchronizedHashMap.javaq没有问题,但在使用~译后的SynchronizedHashMap.classӞJDK的ClassLoader则会拒绝加蝲java.util.SynchronizedHashMap?br />设想有如下的应用E序Q?br />
import java.util.SynchronizedHashMap;

public class SyncMapTest {

    
public static void main(String[] args) {
        SynchronizedHashMap
<String, String> syncMap = new SynchronizedHashMap<>();
        syncMap.put(
"Key""Value");
        System.out.println(syncMap.get(
"Key"));
    }
}
使用java命o去运行该应用Ӟ会报如下错误Q?br />
Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.util
    at java.lang.ClassLoader.preDefineClass(ClassLoader.java:
659)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:
758)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:
142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:
467)
    at java.net.URLClassLoader.access$
100(URLClassLoader.java:73)
    at java.net.URLClassLoader$
1.run(URLClassLoader.java:368)
    at java.net.URLClassLoader$
1.run(URLClassLoader.java:362)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:
361)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:
424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:
331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:
357)
    at SyncMapTest.main(SyncMapTest.java:
6)
ҎClassLoader.preDefineClass()的源代码如下Q?br />
private ProtectionDomain preDefineClass(String name,
        ProtectionDomain pd)
{
    
if (!checkName(name))
        
throw new NoClassDefFoundError("IllegalName: " + name);

    
if ((name != null&& name.startsWith("java.")) {
        
throw new SecurityException
            (
"Prohibited package name: " +
            name.substring(
0, name.lastIndexOf('.')));
    }
    
if (pd == null) {
        pd 
= defaultDomain;
        }

    
if (name != null) checkCerts(name, pd.getCodeSource());

    
return pd;
}
很清楚地Q该Ҏ会先查待加蝲的类全名(卛_?cd)是否?java."开_如是Q则抛出SecurityException。那么可以尝试修改该Ҏ的源代码Q以H破q一限制?br />从JDK中的src.zip中拿出java/lang/ClassLoader.java文gQ修改其中的preDefineClassҎ以去除相关限制。重新编译ClassLoader.javaQ将生成的ClassLoader.classQClassLoader$1.classQClassLoader$2.classQClassLoader$3.classQClassLoader$NativeLibrary.classQClassLoader$ParallelLoaders.class和SystemClassLoaderAction.classL换JDK/jre/lib/rt.jar中对应的cR?br />再次q行SyncMapTestQ却仍然会抛出相同的SecurityExceptionQ如下所C:
Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.util
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:
760)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:
142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:
467)
    at java.net.URLClassLoader.access$
100(URLClassLoader.java:73)
    at java.net.URLClassLoader$
1.run(URLClassLoader.java:368)
    at java.net.URLClassLoader$
1.run(URLClassLoader.java:362)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:
361)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:
424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:
331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:
357)
    at SyncMapTest.main(SyncMapTest.java:
6)
此时是由ҎClassLoader.defineClass1()抛出的SecurityException。但q是一个nativeҎQ那么仅通过修改Java代码是无法解册个问题的(JDK真是层层N?。原来在Hotspot的C++源文件hotspot/src/share/vm/classfile/systemDictionary.cpp中有如下语句Q?br />
const char* pkg = "java/";
if (!HAS_PENDING_EXCEPTION &&
    !class_loader.is_null() &&
    parsed_name !
= NULL &&
    !strncmp((const char*)parsed_name->bytes()
, pkg, strlen(pkg))) {
  // It is illegal to define classes in the 
"java." package from
  // JVM_DefineClass or jni_DefineClass unless you're the bootclassloader
  ResourceMark rm(THREAD)
;
  char* name = parsed_name->as_C_string();
  char* index = strrchr(name, '/');
  *index = '\0'; // chop to just the package name
  while ((index = strchr(name, '/')) != NULL) {
    *index 
= '.'; // replace '/' with '.' in package name
  }
  const char* fmt 
= "Prohibited package name: %s";
  size_t len = strlen(fmt) + strlen(name);
  char* message = NEW_RESOURCE_ARRAY(char, len);
  jio_snprintf(message, len, fmt, name);
  Exceptions::_throw_msg(THREAD_AND_LOCATION,
    vmSymbols::java_lang_SecurityException()
, message);
}
修改该文件以去除掉相关限Ӟq按照本pd?a href="http://www.dentisthealthcenter.com/jiangshachina/archive/2015/10/30/427994.html">W一文?/a>中介l的Ҏ去重新构Z个OpenJDK。那么,q个新的JDK不会再对包名有M限制了?br />
3. 覆盖Java核心APIQ?/strong>
开发者们在用主IDE时会发现Q如果工E有多个jar文g或源文g目录中包含相同的c,q些IDE会根据用h定的优先U顺序来加蝲q些cR比如,在Eclipse中,右键点击某个Java工程-->属?->Java Build Path-->Order and ExportQ在q里调整各个cd或源文g目录的位|,卛_指定加蝲cȝ优先U?br />当开发者在使用某个开源类?jar文g)Ӟ惛_其中某个c进行修改,那么可以将该类的源代码复制出来Qƈ在Java工程中创Z个同名类Q然后指定Eclipse优先加息自己创徏的类。即Q在~译时与q行时用自己创徏的类去覆盖类库中的同名类。那么,是否可以如法炮制去覆盖Java核心API中的cdQ?br />考虑去覆盖类java.util.HashMapQ只是简单在它的put()Ҏd一条打印语。那么就需要将src.zip中的java/util/HashMap.java复制出来Qƈ在当前Java工程中创Z个同名类java.util.HashMapQƈ修改put()ҎQ如下所C:
package java.util;

public class HashMap<K,V> extends AbstractMap<K,V>
    
implements Map<K,V>, Cloneable, Serializable {
    .
    
public V put(K key, V value) {
        System.out.printf(
"put - key=%s, value=%s%n", key, value);
        
return putVal(hash(key), key, value, falsetrue);
    }
    
}
此时Q在Eclipse环境中,SynchronizedHashMap使用的java.util.HashMap被认为是上述新创建的HashMapcR那么运行应用程序SyncMapTest后的期望输出应该如下所C:
put - key=Key, value=Value
Value
但运行SyncMapTest后的实际输出却ؓ如下Q?br />
Value
看v来,新创建的java.util.HashMapq没有被使用上。这是ؓ什么呢Q能?惛_"到的原因q是cd载器。关于Javacd载器的讨Z本文的范_而且关于该主题的文章已是汗牛充栋Q但本文仍会q其要点?br />Javacd载器׃至上分ؓ三个层次Q引导类加蝲?Bootstrap Class Loader)Q扩展类加蝲?Extension Class Loader)和应用程序类加蝲?Application Class Loader)。其中引导类加蝲器用于加载rt.jarq样的核心类库。ƈ且引导类加蝲器ؓ扩展cd载器的父加蝲器,而扩展类加蝲器又为应用程序类加蝲器的父加载器。同时JVM在加载类时实行委托模式。即Q当前类加蝲器在加蝲cLQ会首先委托自己的父加蝲器去q行加蝲。如果父加蝲器已l加载了某个c,那么子加载器不会再ơ加载?br />׃可知Q当应用E序试图加蝲java.util.MapӞ它会首先逐向上委托父加载器d载该c,直到引导cd载器加蝲到rt.jar中的java.util.HashMap。由于该cdl被加蝲了,我们自己创徏的java.util.HashMap׃会被重复加蝲?br />使用java命oq行SyncMapTestE序时加上VM参数-verbose:classQ会在窗口中打印出Ş式如下的语句Q?br />
[Opened /home/ubuntu/jdk1.8.0_custom/jre/lib/rt.jar]
[Loaded java.lang.Object from /home/ubuntu/jdk1.8.0_custom/jre/lib/rt.jar]

[Loaded java.util.HashMap from /home/ubuntu/jdk1.8.0_custom/jre/lib/rt.jar]
[Loaded java.util.HashMap$Node from /home/ubuntu/jdk1.8.0_custom/jre/lib/rt.jar]

[Loaded java.util.SynchronizedHashMap from file:/home/ubuntu/projects/test/classes/]
Value
[Loaded java.lang.Shutdown from /home/ubuntu/jdk1.8.0_custom/jre/lib/rt.jar]
[Loaded java.lang.Shutdown$Lock from /home/ubuntu/jdk1.8.0_custom/jre/lib/rt.jar]
从中可以看出Q类java.util.HashMap实是从rt.jar中加载到的。但理论上,可以通过自定义类加蝲器去打破委托模式Q然而这是另一个话题了?/div>


John Jiang 2015-11-01 20:06 发表评论
]]>
Play OpenJDK: 构徏你自qJDK(?http://www.dentisthealthcenter.com/jiangshachina/archive/2015/10/30/427994.htmlJohn JiangJohn JiangFri, 30 Oct 2015 15:17:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2015/10/30/427994.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/427994.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2015/10/30/427994.html#Feedback0http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/427994.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/427994.html
Play OpenJDK: 构徏你自qJDK

计划使Play OpenJDK成ؓ一l介l如何用ƈ参与OpenJDK目的系列文章。本文是该系列的W一文章,它基于OpenJDK 8的源代码介绍了构Z个属于自qJDKҎ?2015.10.30最后更?

1. OpenJDK
曄的Sun Microsystems. IncQ也是Java语言的发明者,它的JDK代码贡献出来Q成立了一个开源项目,?a >OpenJDK?br />    同时Q它也是一个社区。相关的公司Q组l和个h在这个社Z协作开发OpenJDK。社区根据不同的领域或项目提供了一pd?a >邮g列表Q利益相x或对其感兴趣的个人都可以订阅q些邮g列表去进行关注和讨论。例如,Java核心API的邮件列表是core-libs-dev@openjdk.java.netQ关于java.langQjava.util{核心API的新Ҏ都会在q里q行讨论Qƈ对其最l的实现代码q行审查。Q何将要进入OpenJDK版本库的源代码,无论是品代?卻I要随JDK发布的程?Q还是测试代码,都需要在C֌中进行公开的代码审查?br />    选择一个自己感兴趣的领域或目Q加入它的邮件列表,长期跟踪它的发展Q看着专家们的讨论、争论推动JDK的演q,学习开发者们的API设计与代码实玎ͼ...Q这些对自己的成镉K是极有帮助的。也许,q能看到一些有的八卦;-)

2. 准备工作
在几U主操作系l,Linux(如Ubuntu和Fedora)QWindows(7?)QMacOS(Lion和Moutain Lion)Q中都可以构建OpenJDKQ具体的l节可以参见官方的一文?/a>?br />    本文选择使用Ubuntu 14.04。坦白地_选择使用UbuntuQ实是因为在Linux环境中构建OpenJDK非常单。若在Windows中进行构建,则需要安装Visual Studio C++~译器。MacOSQ嗯Q我没有MBP。可能更多hqx是用WindowsQ但安装一个Linux也不ȝ。先安装免费的VM工具VirtaulBoxQ再M载Ubuntu?a >ISO文gQ然后用VirtualBoxd装Ubuntu。VirtualBox单易用,|上的相兌料也非常的多?br />    OpenJDK的源文g版本库基?a >Mercurial(hg)Q它是一个与Git怼的分布式版本控制工具。在Ubuntu中安装Mercurial只需要执行命?br />
$ sudo apt-get install mercurial
    OpenJDK中各目的源代码版本库的路径均在http://hg.openjdk.java.net/之下Q在q里可以扑ֈ6Q?Q?Q?和Jigsaw的源代码。其中JDK 8的最新开发版本库路径为http://hg.openjdk.java.net/jdk8u/jdk8u-dev/?br />    另外Q构建OpenJDK旉要一个启动JDKQ本文选择构徏OpenJDK 8Q那么启动JDK的版本应不低??br />
3. 下蝲源代?/span>
克隆版本库,
$ hg clone http://hg.openjdk.java.net/jdk8u/jdk8u-dev/ jdk8-src
requesting all changes
adding changesets
adding manifests
adding file changes
added 
1570 changesets with 1958 changes to 141 files
updating to branch default
85 files updated, 0 files merged, 0 files removed, 0 files unresolved
q入源代码目?br />
$ cd jdk8-src/
OpenJDK的源代码版本库实际上包含多个独立的子版本库,需要执行如下脚本去分别下蝲各个子版本库的源代码Q?br />
$ sh get_source.sh
# Repositories:  corba jaxp jaxws langtools jdk hotspot nashorn
                corba:   hg clone http://hg.openjdk.java.net/jdk8u/jdk8u-dev/corba corba
                 jaxp
:   hg clone http://hg.openjdk.java.net/jdk8u/jdk8u-dev/jaxp jaxp
                corba
:   requesting all changes
                 jaxp
:   requesting all changes

4. 构徏
调用configureq行预构建,其中的参?-with-boot-jdk用于指定启动JDK的\径。如果启动JDK的java命o已存在于PATH环境变量中,该参数可以忽略?br />
$ sh configure --with-boot-jdk=/path/to/boot/jdk
该过E会构建环境是否符合要求,如有问题Q它会给出提C。比如,
configure: error: Could not find X11 libraries. You might be able to fix this by running 'sudo apt-get install libX11-dev libxext-dev libxrender-dev libxtst-dev libxt-dev'.
此时Ҏ提示安装所需要的库即可。再重新执行上述configure命oQ可能还会提C缺其它的库,那么再次Ҏ提示q行安装。如此反复,只到预构建成功完成?br />最后就是进行构建,直接执行如下命oQ?br />
$ make all
在此q程中可以会遇到一些警告,不必理会Q耐心{待...完成后,会出现如下的汇M息,
----- Build times -------
Start 2015-10-30 22:11:10
End   2015-10-30 22:52:54
00:01:01 corba
00:01:08 demos
00:06:49 docs
00:19:37 hotspot
00:01:47 images
00:00:35 jaxp
00:00:49 jaxws
00:08:23 jdk
00:01:09 langtools
00:00:25 nashorn
00:41:44 TOTAL
-------------------------
Finished building OpenJDK for target 'all'
在当前\径下会生成一个build目录Q构建好的JDK在那里面。新JDK的具体\径类gbuild/linux-x86_64-normal-server-release/images/jdk。可以执行如下命令去试q个JDKQ?br />
$ build/linux-x86_64-normal-server-release/images/jdk/bin/java -version
openjdk version "1.8.0-internal"
OpenJDK Runtime Environment (build 1.8.0-internal-ubuntu_2015_10_30_22_07-b00)
OpenJDK 64-Bit Server VM (build 25.66-b00, mixed mode)


John Jiang 2015-10-30 23:17 发表评论
]]>
利用Java SE 8处理数据II(?http://www.dentisthealthcenter.com/jiangshachina/archive/2014/08/15/417011.htmlJohn JiangJohn JiangFri, 15 Aug 2014 11:57:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2014/08/15/417011.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/417011.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2014/08/15/417011.html#Feedback2http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/417011.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/417011.html
利用Java SE 8处理数?/strong>
-- l合Stream API的高U操作去表示富数据处理查?/div>
本文?/span>Java Magazine 201405/06刊中的一文章,也是文章pd"利用Java SE 8处理数?中的W二,它基于flatMap()和collect()介绍了Java的高用法(2014.08.15最后更?

在本pd的第一文章中Q你看到了Java让你能够用与数据库操作相似的Ҏd理集合。作Z个复习,清单1的例子展CZ如何使用Stream APIL得大交易的金额之和。我们组Z一个管道,它由中间操作(filter和map)与最l操?reduce)构成Q图1形象地展C它?/span>
清单1
int sumExpensive =
        transactions.stream()
        .filter(t -> t.getValue() > 1000)
        .map(Transaction::getValue)
        .reduce(0, Integer::sum);
?

然而在pd的第一部分中,q没有研I这两个ҎQ?/span>
flatMapQ这是一个中间操作,它允许将一?map"和一?flatten"操作l合在一?/span>
collectQ这是一个最l操作,它依据不同的方式Q将中的元素归集ؓ一个结果?/span>
q两个方法对于表达更为复杂的查询是十分有用的。例如,你可以将flatMap和collectl合hQ生成代表一个文字流中每个字母出现的ơ数的Map对象Q如清单2所C。如果第一ơ看到这D代码觉得很惊奇Ӟ但请不要担心。本文的目的是要解释ƈ探究q两个方法更多的l节?/span>
清单2
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.*;

Stream
<String> words = Stream.of("Java""Magazine""is""the""best");
Map
<String, Long> letterToCount =
        words.map(w 
-> w.split(""))
        .flatMap(Arrays::stream)
        .collect(groupingBy(identity(), counting()));
清单2中的代码会生成如清?C的l果。棒极了Q不是吗Q让我们开始探IflatMap和collectҎ是如何工作的?/span>
清单3
[a:4, b:1, e:3, g:1, h:1, i:2, ..]

flatMapҎ
假设你想扑և文g中所有独一唯二的字。你会怎么做呢Q?/span>
你可能认很简单;我们可以Files.lines()Q在前面的文章中已见q了q个ҎQ因为它会返回一个包含文件中所有行的流。然后我们就可以使用mapҎ每一行拆分成字,最后再使用distinctҎ去除重复的字。第一ơ尝试得到的代码可能如清?所C?/span>
清单4
Files.lines(Paths.get("stuff.txt"))
        .map(line 
-> line.split("\\s+")) // Stream<String[]>
        .distinct() // Stream<String[]>
        .forEach(System.out::println);
不幸的是Q这D늨序ƈ不十分正。如果运行它Q会得到令h生疑的结果,与下面的输出有些cMQ?/span>
[Ljava.lang.String;@7cca494b
[Ljava.lang.String;@7ba4f24f
...
我们的第一ơ尝试确实打印出了代表几个流对象的字W串。那发生了什么呢Q该Ҏ的问题是Q传lmapҎ的Lambda表达式返回的是文件中每一行的String数组(String[])。而我们真正想要的是一个表C文字的的Stream<String>对象?/span>
q运的是Q对于该问题有一个解x案,是使用flatMapҎ。让我们一步一步地看看如何得到正确的解x法?/span>
首先Q我们需要字的流Q而不是数l的。有一个名为Arrays.stream()的方法,它将使用一个数l作为参敎ͼq生成一个流。请看清?中的例子?/span>
清单5
String[] arrayOfWords = {"Java""Magazine"};
Stream
<String> streamOfwords = Arrays.stream(arrayOfWords);
让我们在前面的流道中用该ҎQ看看会发生什?见清?)。这个方案依然行不通。那是因为我们最l得到的是一l流的流(准确地说Q就是Stream<Stream<String>>)。确切地是,我们首先每一行{换ؓ一个字的数l,然后使用ҎArrays.stream()每一个数l{换成一个流?/span>
清单6
Files.lines(Paths.get("stuff.txt"))
       .map(line 
-> line.split("\\s+")) // Stream<String[]>
       .map(Arrays::stream) // Stream<Stream<String>>
       .distinct() // Stream<Stream<String>>
       .forEach(System.out::println);
我们使用flatMap()Ҏ去解册个问题,如清?所C。用flatMap()Ҏ能够用流中的内容Q而不是流L换每一个生成的数组。换a之,通过map(Arrays::stream)Ҏ生成的全部独立的被合ƈ?扁^?Z个流。图2形象地展CZ使用flatMap()Ҏ的效果?/span>
清单7
Files.lines(Paths.get("stuff.txt"))
       .map(line 
-> line.split("\\s+")) // Stream<String[]>
       .flatMap(Arrays::stream) // Stream<String>
       .distinct() // Stream<String>
       .forEach(System.out::println);
本质上,flatMap让你可以使用其它去替换另一个流中的每个元素Q然后再所有生成的连合ƈZ个流?/span>
h意,flatMap()是一个通用的模式,在用Optaional或CompletableFutureӞ你还会看到它?/span>

collectҎ
现在让我们看看collectҎ的更多细节。在本系列的W一文章中你所看到的方法,要么q回另一个流(卻Iq些Ҏ是中间操?Q要么返回一个|例如一个booleanQ一个intQ或一个Optional对象(卻Iq些Ҏ是最l操??/span>
collect是一个最l方法,但它有点儿不同,因ؓ你可以用它将一个Stream对象转ؓ一个List对象。例如,Z得到一个包含有所有高金额交易ID的列表,你可以用像清单8那样的代码?/span>
清单8
import static java.util.stream.Collectors.*;

List
<Integer> expensiveTransactionsIds =
        transactions.stream()
        .filter(t 
-> t.getValue() > 1000)
        .map(Transaction::getId)
        .collect(toList());
传递给collectҎ的参数就是一个类型ؓjava.util.stream.Collector的对象。这个Collector对象是干什么的Q本质上看,它描qC如何按照需要去攉中的元素,再将它们生成Z个最l结果。之前用到的工厂ҎCollector.toList()会返回一个Collector对象Q它描述了如何将一个Stream对象归集Z个List对象。而且QCollctors内徏有有许多怼的方法。例如,使用toSet()Ҏ可以一个Stream对象转化Z个Set对象Q它会删除所有重复的元素。清?中的代码展示了如何生成一个仅仅包含高金额交易所在城市的Set对象?注意Q在后面的例子中Q我们假设CollectorscM的工厂方法都已通过语句import static java.util.stream.Collectors.*被静态引入了)
清单9
Set<String> cities =
        transactions.stream()
        .filter(t 
-> t.getValue() > 1000)
        .map(Transaction::getCity)
        .collect(toSet());
注意Q无法保证会q回何种cd的Set对象。但是,通过使用toCollection()Q你可以q行更多的控制。例如,若你惛_C个HashSetQ可以传一个构造器ltoCollectionҎ(见清?0)?/span>
清单10
Set<String> cities =
        transactions.stream()
        .filter(t 
-> t.getValue() > 1000)
        .map(Transaction::getCity)
        .collect(toCollection(HashSet::
new));
然而,qƈ不是你能用collect和Collector所做的全部事情。实际上Q这只是你能用它们所做的事情中的极小部分。下面是一些你所能表辄查询的例子:
交易按货币分类Qƈ计算每种货币的交易金额之?q回一个Map<Currency, Integer>对象)
交易划分成两组Q高金额交易和非高金额交?q回一个Map<Boolean, List<Transaction>>对象)
创徏多层分组Q例如先按交易发生的城市分组Q再q一步按它们是否为高金额交易q行分组(q回一个Map<String, Map<Boolean, List<Transaction>>>)
兴奋吗?很好。让我们看看Q你是如何用Stream API和Collector来表达上q查询的。我们首先从一个简单的例子开始,q个例子要对q个进?ȝ"Q计它的^均|最大值和最倹{然后我们再看看如何表达单的分组Q最后,再看看如何将Collectorl合hd建更为强大的查询Q例如多层分l?/span>
ȝ。让我们用一些简单的例子来热w一下。在之前的文章中Q你已经看到如何使用reduceҎ去计流中元素的数量Q最|最大值和q_|以及如何使用基本数据cd元素的流。有一些预定义的CollectorcM能让你完成那些功能。例如,可以使用counting()Ҏ去计元素的数量Q如清单11所C?/span>
清单11
long howManyTransactions = transactions.stream().collect(counting());
你可以用summingDouble()QsummingInt()和summingLong()分别Ҏ中元素类型ؓDoubleQInt或Long的属性求和。在清单12中,我们计算Z所有交易的金额之和?/span>
清单12
int totalValue = transactions.stream().collect(summingInt(Transaction::getValue));
cM的,使用averagingDouble()QaveragingInt()和averagingLong()去计^均|如清?3所C?/span>
清单13
double average = transactions.stream().collect(averagingInt(Transaction::getValue));
另外Q用maxBy()和minBy()ҎQ可以计出中值最大和最的元素。但q里有一个问题:你需要ؓ中元素定义一个顺序,以能够对它们q行比较。这是Z么maxByQ)和minBy()Ҏ使用使用一个Comparator对象作ؓ参数Q图3表明了这一炏V?/span>
?

在清?4的例子中Q我们用了静态方法comparing()Q它传入的函数作ؓ参数Q从中生成一个Comparator对象。该函数用于从流的元素中解析出用于进行比较的关键字。在q个例子中,通过使用交易金额作ؓ比较的关键字Q我们找C那笔最高金额的交易?/span>
清单14
Optional<Transaction> highestTransaction =
        transactions.stream()
        .collect(maxBy(comparing(Transaction::getValue)));
q有一个reducing()ҎQ由它生的Collector对象会把中的所有元素归集在一P对它们重复的应用同一个操作,直到产生l果。该Ҏ与之前看q的reduce()Ҏ在原理上一L。例如,清单15展示了用了Zreducing()Ҏ的另一U方式去计算所有交易的金额之和?/span>
清单15
int totalValue = transactions.stream().collect(reducing(0, Transaction::getValue, Integer::sum));
reducing()Ҏ使用三个参数Q?/span>
初始?如果ؓI,则返回它)Q此处,该gؓ0?/span>
应用于流中每个元素的函数对象Q此处,该函C解析出每W交易的金额?/span>
两个由解析函数生成的金额合q在一LҎQ此处,我们只是把金额加h?/span>
你可能会_"{等Q用其它的方法,如reduce()Qmax()和min()Q我已经可以做到q些了。那么,你ؓ什么还要给我看q些Ҏ呢?"后面Q你会看到我们Collectorl合hL建更为复杂的查询(例如Q对加法q_数进行分l?Q所以,q也能更易于理解q些内徏的Collector?/span>
分组。这是一个普通的数据库查询操作,它用属性去数据q行分组。例如,你也许想按币U对一l交易进行分l。若你用如清单16所C的代码Q通过昑ּ的遍历去表达q个查询Q那会是很痛苦的?/span>
清单16
Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap< >();
for(Transaction transaction : transactions) {
    Currency currency 
= transaction.getCurrency();
    List
<Transaction> transactionsForCurrency =
    transactionsByCurrencies.get(currency);

    
if (transactionsForCurrency == null) {
        transactionsForCurrency 
= new ArrayList<>();
        transactionsByCurrencies.put(currency, transactionsForCurrency);
    }
    transactionsForCurrency.add(transaction);
}
你首先需要创Z个Map对象Q它收集所有的交易记录。然后,你需要遍历所有的交易记录Qƈ解析出每W交易的币种。在交易记录用一个值插入Map中之前,需要先查一下,q个List是否已经创徏q了Q等{?/span>
真是令h汗颜啊,因ؓ我们惌?按币U对交易q行分组"。ؓ什么不得不涉及q么多代码呢Q有好消息:有一个称为groupingBy()的CollectorQ它允许我们以简z的方式来表达这个例子。我们可以用清?7中的例子来表达这个相同的查询Q现在代码的阅读更接q问题语句了?/span>
清单17
Map<Currency, List<Transaction>> transactionsByCurrencies =
        transactions.stream().collect(groupingBy(Transaction::getCurrency));
工厂ҎgroupingBy()使用一个函数对象作为参敎ͼ该函C解析出用于分cM易记录的关键字。我们称个函Cؓ分类函数。在此处Qؓ了按币种对交易进行分l,我们传入一个方法引用,Transaction::getCurrency。图4演示了这个分l操作?/span>
?

分割。有一个称为partitioningBy()的工厂方法,它可被视ZU特D的groupingBy()Ҏ。它使用一个谓语作为参?该参数返回一个boolean?Q然后根据元素是否满个谓语对它们q行分组。换a之,它将l成的交易分割成了l构Map<Boolean, List<Transaction>>。例如,如若你想交易分割成两组--低廉的和昂贵?-你就可以像清?8那样M用partitioningBy()产生的Collector。此例中的Lambda表达式,t->t.getValue() > 1000Q就是一个将交易分成低廉和昂늚谓语?/span>
清单18
Map<Boolean, List<Transaction>> partitionedTransactions =
        transactions.stream().collect(partitioningBy(t 
-> t.getValue() > 1000));
l合Collector。如果你熟悉SQLQ你应该知道可以GROUP BY与函数COUNT()和SUM()一块儿使用Q以按币U和交易金额之和q行分组。那么,使用Stream API是否也可以实现相似的功能呢?当然可以。确切地_有一个重载的groupingBy()ҎQ它使用另一个Collector作ؓW二个参数。这个额外的Collector对象用于定义在用由groupingBy()产生的Collector时如何汇集所有与关键字相关的元素?/span>
好吧Q这听v来有些抽象,那么让我们看一个简单的例子。我们想Z每个城市的交易金额之和生成一个城市的Map对象(见清?9)。在此处Q我们告诉groupingBy()Ҏ使用getCity()Ҏ作ؓ分类Ҏ。那么,得到的Mapl果的Key׃ؓ城市。正常地Q我们期望对Map中每个键所对应的|即List<Transaction>对象Q用groupingBy()Ҏ?/span>
清单19
Map<String, Integer> cityToSum =
        transactions.stream().collect(groupingBy(Transaction::getCity,
        summingInt(Transaction::getValue)));
然后Q我们却是传入了另一个Collector对象Q它由summingInt()Ҏ产生Q该Ҏ会将所有与特定城市相关的交易记录的金额加v来。结果,我们得到了一个Map<String, Integer>对象Q它每个城市与它们对应的所有交易的金额之和q行了映。酷Q不是吗Q想惌个:基本的groupingBy(Transaction:getCity)Ҏ其实只是groupingBy(Transaction:getCity, toList())的简写?/span>
让我们看看另一个例子。如果你想生成这样一个MapQ它Ҏ个城市与它的最大金额的交易记录q行映射Q那要怎么做呢Q你可能已经猜到了,我们可以重用前面q的由maxBy()Ҏ产生的CollectorQ如清单20所C?/span>
清单20
Map<String, Optional<Transaction>> cityToHighestTransaction =
        transactions.stream().collect(groupingBy(Transaction::getCity,
        maxBy(comparing(Transaction::getValue))));
你已l看到Stream API很善于表达,我们正在构徏的一些十分有的查询都可以写的简z些。你q能惌出回C前去遍历地处理一个集合吗Q让我们看一个更为复杂的例子Q以l束q篇文章。你已看到groupingBy()Ҏ可以一个Collector对象作ؓ参数Q再Ҏq一步的分类规则L集流中的元素。因为groupingBy()Ҏ本n得到的也是一个Collector对象Q那么通过传入另一个由groupingBy()Ҏ得到的Collector对象Q该Collector定义了第二的分c规范,我们p够创建多层次分组?/span>
在清?1的代码中Q先按城市对交易记录q行分组Q再q一步对每个城市中的交易记录按币U进行分l,以得到每个城市中每个币种的所有交易记录的q_金额。图5Ş象地展示了这U机制?/span>
清单21
Map<String, Map<Currency, Double>> cityByCurrencyToAverage =
        transactions.stream().collect(groupingBy(Transaction::getCity,
        groupingBy(Transaction::getCurrency,  
        averagingInt(Transaction::getValue))));
?

创徏你自qCollector。到目前为止Q我们展C的全部Collector都实C接口java.util.stream.Collector。这意味着Q你可以实现自己的CollectorQ以"定制"归一操作。但是对于这个主题,再写一文章可能更合适一些,所以我们不会在本文中讨个问题?/span>

l论
在本文中Q我们探讨了Stream API中的两个高QflatMap和collect。它们是可以加到你的兵器库中的工P可以用来表述丰富的数据处理查询?/span>
特别圎ͼ你也已经看到了,collect()Ҏ可被用于归纳Q分l和分割操作。另外,q些操作q可能被l合在一PL建更Z富的查询Q例?生一个两层Map对象Q它代表每个城市中每个币U的q_交易金额"?/span>
然而,本文也没有查I到所有的内徏Collector实现。请你去看看Collectorsc,q试试其它的Collector实现Q例如由mapping()Qjoining()和collectingAndThen()Q也怽会发现它们也很有用?/span>


John Jiang 2014-08-15 19:57 发表评论
]]>
利用Java SE 8处理数?I)(?http://www.dentisthealthcenter.com/jiangshachina/archive/2014/07/27/416235.htmlJohn JiangJohn JiangSun, 27 Jul 2014 12:54:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2014/07/27/416235.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/416235.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2014/07/27/416235.html#Feedback6http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/416235.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/416235.html
利用Java SE 8处理数?/span>
-- 使用Java操作去表达复杂的数据查?/span>

本文?a >Java Magazine 201403/04刊中的一文章,也是文章pd"利用Java SE 8处理数?中的W一,它概qCJava的基本原理与基本应用,是一很好的Java Streams API的入门文章?2014.07.27最后更?

    没有集合对象Q你会怎么P几乎每个Java应用都会创徏q处理集合。它们是许多~程d的基Q集合你能够对数据q行分组和处理。例如,你也怼创徏一个关于银行交易的集合Q该集合代表了某个用L银行水清单。然后,你可能想要处理整个集合去出该用戯了多钱。尽数据处理十分重要,但Java在此斚w表现的远不完?/span>
    首先Q典型的集合处理模式与类SQL操作怼Q诸?查找"(如找出最大金额的那笔交易)或?分组"(如,与购买杂货相关的交易进行分l?。大部分数据库允怽以声明的形式L定这些操作。例如,后面的SQL查询会让你找到那W最大金额交易的IDQ?SELECT id, MAX(value) from transctions"?/span>
    如你所见,我们q不需要去实现如何计算最大?例如Q用@环和一个变量去q踪q个最大?。我们仅需要表达什么是我们惌的。这U基本思想意味着Q你不太需要担心去昑ּ地实现这些查?-它们已经Z处理好了。ؓ什么我们不能在处理集合时也q样做呢Q你发现自己有多次都是在一遍又一遍地使用循环去重复实现这些操作呢Q?/span>
    其次Q我们如何才能更高效地去处理大型集合Q理x况下Q在加速进行处理时Q你会想到利用多核架构。然而,~写q行E序既困隑֏Ҏ出错?/span>
    Java SE 8赶来帮忙了!Java API的设计者们在升UAPI时引入了一U新的称之ؓJava??的抽象,它允怽以声明Ş式去处理数据。另外,Java可以利用到多核架构而不必编写一行多U程代码。听h不错Q不是吗Q这是本文章系列所要探I的主题?/span>
    Java能为我们做些什么呢Q在探究q些l节之前Q让我们先看一个例子,q样你才能对q种新的使用Java SE 8 Java的~程风格有感觉。假设我们要扑ֈ所有类型ؓgrocery的交易ƈq回它们的ID列表Qƈ按交易金额的递减序对该列表q行排序。在Java SE 7中,我们应该会把清单1所C的E序那样d。而在Java SE 8中,我们则会像清?所C的那样d现?/span>
清单1
List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions){
  
if(t.getType() == Transaction.GROCERY){
    groceryTransactions.add(t);
  }
}
Collections.sort(groceryTransactions, 
new Comparator(){
  
public int compare(Transaction t1, Transaction t2){
    
return t2.getValue().compareTo(t1.getValue());
  }
});
List
<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
  transactionsIds.add(t.getId());
}

清单2
List<Integer> transactionsIds =
    transactions.stream()
                .filter(t 
-> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

    ?形象地解释了那段Java SE 8E序。首先,我们调用List对象中的Java?)Ҏ从交易列?数据)中获取一个Java对象。然后,多个操作(qoQ排序,映射Q归?链接在一起Ş成了一条线Q这条线可以被看作构成了一条数据查询?/span>


    那么如何q行地执行该E序呢?在Java SE 8中这很简单:只需要用parallelJava?)ҎL换Java?)ҎQ如清单3所C。Java?API会在内部对你的查询进行解构,q利用上你机器中的多核处理器?/span>
清单3
List<Integer> transactionsIds =
    transactions.parallelStream()
                .filter(t 
-> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

    在该关于Java SE 8 Java的文章pdl束Ӟ你将能够使用Java?API~写出像清单3那样的功能强大的查询E序?/span>

Java入?/span>
    让我们先从一点理论开始。Java的定义是什么?一个简短的定义是"来自于一个数据源的能够支持聚合操作的一串元?。让我们把它拆开来说Q?/span>
    一串元?/strong>QJavaؓ一串特定类型值的集合提供了一个接口。然后,Java实际上q不存储元素Q它们会在需要时被用上?/span>
    数据?/strong>QJava要使用一个提供数据的源,诸如集合对象Q数l或I/O资源?/span>
    聚合操作QJava支持类SQL的操作,以及来自于函数编E语a的通用操作Q诸如过滤,映射Q归一Q查找,匚wQ排序,{等?/span>
    另外Q与集合操作非常不同的是QJava操作拥有两基本特质:
    道Q许多Java操作会q回它们自己Q这׃得这些操作能够链接在一起以l成一个大型管道。这样就可以q行一些诸如惰性和短\之类的优化,后面我们会进行探I?/span>
    内部遍历Q集合是昑ּ地进行遍?外部遍历)Q但不同于集合,Java是在幕后进行遍历。让我们重新看看之前的示例代码来解释q些原理。图2形象地解释了清单2的更多细节?/span>


    首先Q通过调用Java?)ҎQ我们从交易列表中得C一个Java对象。那么数据源是交易列表Q它向Java中提供一串元素。然后,我们对该Java应用了一pd的聚合操作:qo(提供一个谓语去qo元素)Q排?提供一个比较器d元素q行排序)Q以及映?解析Z?。所有的操作都会q回该Java,以便能够链接q些操作ȝ成一个管道,q可被看作是Ҏ据源的一个查询?/span>
    在调用collect()操作之前Q没有实际工作会被执行。collect()Ҏ开始处理这个管道以q回一个结?某个不是Java的对象Q在此处Q是一个List对象)。现在还不需要去xcollect()ҎQ我们会在以后的文章M探究竟。此Ӟ你会发现collect会将各种数据加工Ҏ作ؓ参数Q将攉到的Java元素归lؓ一个结果。此处,toList()描qC一个将Java对象{化ؓList对象的加工方法?/span>
    在探I与Java有关的各个Ҏ之前Q最好是停下来深入思考一下Java和集合之间观念上的不同之处?/span>

Java?vs. 集合
    已有的Java集合概念与新的Java概念都Z串元素提供了接口。那它们有何不同吗?单地_集合是关于数据的Q而Java是关于计算的?/span>
    xq种情况Q一部存储在DVD中的电媄。这是一个集?可能是字节,可能是--在此处,我们不必兛_q些)Q因为它包含有全部的数据l构。现在再xq种情况Q这部电p转化成了数据,通过互联|去观看它。此时它是一?字节或?。流视频播放器只需要下载一些晚于用户当前所观看位置的ឮ可以了。这P你就可以在大部分D计算出来之前先展C流开头处的?x化一场现场直播的球比赛)?/span>
    _看之,集合与流的区别就是与何时处理数据有关。集合是内存中的数据l构Q它包含有当前数据结构中的全部?-所有元素加入到集合之前Q必dҎ有元素进行处理,相反圎ͼJava只是逻辑上固定的数据l构Q它里面的元素只会根据需要进行处理?/span>
    使用Collection接口Q要求用户实现遍?例如Q用增强的for循环Q即foreach)Q这被称之ؓ外部循环。相反地QStreamcd使用内部遍历--它已lؓ你实现好了遍历,它会兛_存储的l果值的位置Q你仅需要提供一个函数告诉它要做些什么就行了。清?(寚w合的外部遍历)和清?(对Java的内部遍历)中的代码形象地展CZq一不同之处?/span>
清单4
List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: transactions){
    transactionIds.add(t.getId());
}

清单5
List<Integer> transactionIds =
    transactions.stream()
                .map(Transaction::getId)
                .collect(toList());

    在清?中,我们昑ּ且顺序地遍历了交易列表,抽取了每个交易IDQ然后将它加C个收集器中。相反地Q当使用时Q没有显式的遍历。清?中的代码构徏了一个查询,其中的map操作被设定ؓ一个参敎ͼ它会抽取交易IDQ然后collect操作会把l果Stream对象转化成一个List对象?/span>
你现在应该知道什么是Java,以及如何M用它。现在让我们看看Java所支持的操作之间的区别Q这样你p构徏自己的数据查询了?/span>

Java操作:使用去处理数据

    java.util.stream.Stream接口定义了许多操作,它们可被归集ZcR在?所C的例子中,你可以看到如下操作:
    qoQ排序和映射Q它们可被连接在一L成一个管?/span>
    攉Q它关闭了这个管道ƈq回l果
    能够被连接在一LJava操作被UCؓ中间操作。这些操作之所以能被连接在一P是因为它们都会返回Stream对象。这些操作从q个道中返回结果,l果的类型可以是ListQIntegerQ甚xvoid(MStream以外的类?
    你也许很好奇Z么这U区别很重要。是q样的,在这个Java管道的最l操作被调用之前Q中间操作ƈ不会执行M处理Q它们是"惰?Ҏ。这是因Z间方法经怼?合ƈ"Q在最l操作中它们会被合成为单一的执行\径?/span>
清单6
List<Integer> numbers = Arrays.asList(12345678);
List
<Integer> twoEvenSquares =
    numbers.stream()
           .filter(n 
-> {
                    System.out.println(
"filtering " + n);
                    
return n % 2 == 0;
                  })
           .map(n 
-> {
                    System.out.println(
"mapping " + n);
                    
return n * n;
                  })
           .limit(
2)
           .collect(toList());

    例如Q思考下清单6中的E序Q它是从l定的数列中计算奇数的^斏V你可能会很惊讶Q它打印出如下结果:
filtering 1
filtering 
2
mapping 
2
filtering 
3
filtering 
4
mapping 
4

    q是因ؓlimit(2)使用了短路;我们只需要处理流的一部分Q而不是全部,dC个结果。这qg评一个由and操作W关联v来的大型布尔表达式链Q一旦某个表辑ּq回了falseQ那么就可以认ؓ整个表达式链是falseQ而不必测评所有的表达式了。在q个例子中,limit()Ҏ返回的Java的长度限定?。另外,filter与map被合q在了同一条执行\径中了?/span>

    归纳一下到目前为止Q在使用Java时我们所学到的内容,总言之,涉及三个斚wQ?/span>
    一个数据源(例如一个集?Q对它执行查?/span>
    一个中间操作的链,它组成一个流的管?/span>
    一个最l操作,它执行流的管道ƈ产生l果
    现在让我们看看Java所支持的一些操作。参考java.util.stream.Stream接口可以得到q些Ҏ的完整清单,再看看本文末所l出的资源,它包含有更多的例子?/span>

    qo。有多种Ҏ可以Ҏ中的元素q行qoQ?/span>
    filter(Predicate)Q用一个谓?java.util.function.Predicate)作ؓ参数Q它会返回一个包含所有匹配给定谓语条件元素的Java?/span>
    distinctQ返回一个包含有唯一元素的Java?/span>
    limit(n)Q返回的的长度不能过n?/span>
    skip(n)Q返回的将不包括前n个元素?/span>

    查找与匹?/strong>。一个通用的数据处理模式就要考虑一些元素是否匹配给定的属性。你可以使用anyMatchQallMatch和noneMatchҎ帮你做到q一炏V这些方法都会用一个谓语参数ƈq回booleang为结?所以,它们是最l操?。例如,使用allMatchLZ易流中所有金额大?00的交易,如清?所C?/span>
清单7
boolean expensive =
    transactions.stream()
                .allMatch(t 
-> t.getValue() > 100);

    另外QStream接口提供了方法findFirst和findAnyQ以取出中M元素。它们可以与其它的流操作Q如filterQ结合v来用。findFirst和findAny都会q回一个Optinal对象(见清?)?/span>
清单8
Optional<Transaction> =
    transactions.stream()
                .findAny(t 
-> t.getType() == Transaction.GROCERY);

    Optional<T>c?java.util.Optional)是一个容器类Q它代表一个存在或不存在的倹{清?中的E序QfindAnyҎ可能没有扑ֈMcd为grocery的交易。Optionalcd含多个方法去试一个元素是否存在。例如,如果交易存在Q通过使用ifPresentҎQ我们可以选择一个操作去应用q个Optaional对象Q如清单9所C?此处只是打印交易)?/span>
清单9
transactions.stream()
              .findAny(t 
-> t.getType() == Transaction.GROCERY)
              .ifPresent(System.out::println);

    映射。Java支持mapҎQ它使用一个函?java.util.function.Function)作ؓ参数Q将元素投影到其它形式。这个函C被应用到每个元素Qƈ元?映射"到新的元素?/span>
    例如Q你可能会想C用它L取流中每个元素的信息。在清单10的例子中Q我们返回了一个列表中每个字的长度?/span>
清单10
List<String> words = Arrays.asList("Oracle""Java""Magazine");
 List
<Integer> wordLengths =
    words.stream()
         .map(String::length)
         .collect(toList());

    归一。到目前为止Q我们已见过的最l操作会q回boolean(allMatch{等)Qvoid(forEach)或Optaional对象(findAny{等)。我们也使用collectҎStream对象中的所有元素放C个List对象中?/span>
    然而,你也可以流中的元素攑ֈ一个查询中Q该查询可表达更为复杂的数据处理Q例?拥有最大ID"或?出所以交易金额的?。这可能对Java用上reduceҎQ该Ҏ会对每个元素重复地应用一个操?例如Q加上两个数?Q直到生成结果。在函数式编E中Q这常被UCؓ折叠操作。因操作可被看作重复?折叠"一张很长的U?Stream对象)Q直到这张纸的面U变得只有一点儿了。这是折叠操作的结果?/span>
    看看我们是如何用@环去计算一个组数字的和会有助于理解q个问题Q?/span>
int sum = 0;
for (int x : numbers) {
  sum 
+= x;
}

    列表中的每一个数字元素都被P代地l合在一Pq用一个额外的操作W去产生l果。本质上Q我们就是把一l数?归一"成一个数字。在q段代码中有两个参数Q数字和变量的初始|卌例中?Q以及用于合q所有元素的操作W,x例中??/span>
清单11
int sum = numbers.stream().reduce(0, (a, b) -> a + b);

    对Java用reduceҎQ我们可以计出中的所有元素g和,如清?1所C。reduceҎ使用两个参数Q?/span>
    初始|0
    BinaryOperation<T>Q合q两个元素,q生一个新?/span>
    reduceҎ本质上就是重复应用模式的抽象。其它的查询Q如"计算产量"?计算最大?(如清?2所C?则是reduceҎ的特别实例?/span>
清单12
int product = numbers.stream().reduce(1, (a, b) -> a * b);
int product = numbers.stream().reduce(1, Integer::max);

数字?/span>
    你已l看到可以用reduceҎ去计整数流的和。然后,q也是有成本的:我们重复执行了许多拆操作以Integer对象加到一赗如果我们能调用一个sumҎQɽE序的意图更为明显,像清单13那样Q岂不是更好Q?/span>
清单13
int statement =
    transactions.stream()
                .map(Transaction::getValue)
                .sum(); 
// error since Stream has no sum method

    Java 8引入的三个特定的基本数据cd的流接口来应对这个问?-IntStreamQDoubleStream和LongStream--它们专注于元素分别ؓintQdouble和long型的Java。将一个流转化为特定类型的,你最怋用的Ҏ是mapToIntQmapToDouble和mapToLong。这些方法与我们较早前看到的mapҎ是一LQ但它们会返回特定类型的Stream对象Q而不是Stream<T>对象。例如,我们可以改进下清?3中的代码Q如清单14所C那栗你也可以用装操作将一个基本数据类型的{化成一个用包装对象的?/span>
清单14
int statementSum =
    transactions.stream()
                .mapToInt(Transaction::getValue)
                .sum(); 
// works!

    最后,数字的另一U有用的形式是数字区间。比如,你可能想生成介于1?00之间的所有数字。ؓ了帮助生成这U区_Java SE 8在IntStreamQDoubleStream和LongStream中分别引入了两个静态方法:range和rangeClosed?/span>
    q两个方法都会用两个参敎ͼW一个参数是起始|W二个参数是l止倹{但是rangeҎ生成的区间不会包含终止值本w,但rangeClosed生成的区间则会包含。清?5是一个用rangeClosedҎ的例子,它返回一个包含有全部介于10?0之间奇数的流?/span>
清单15
IntStream oddNumbers =
    IntStream.rangeClosed(
1030)
             .filter(n 
-> n % 2 == 1);

构徏?/span>
    有多U途径可以LZ个流。你已经看过如何从集合对象中构徏。另外,我们q操控过数字。你也可以从|数组或文件中d建流。另外,你甚至于可以从一个函C生成无限?/span>
    可以直截了当C值或数组中创建流Q只需要用一些静态方法即可,对于|是Stream.of()Q而对于数l,则要调用Arrays.stream()。如清单16所C?/span>
清单16
Stream<Integer> numbersFromValues = Stream.of(1234);
int[] numbers = {1234};
IntStream numbersFromArray 
= Arrays.stream(numbers);

    你也可以一个文件{化ؓ其内容行的流Q用静态方法Files.lines()卛_。清?7׃用该Ҏ计算了文件中行的数量?/span>
清单17
long numberOfLines =
    Files.lines(Paths.get(“yourFile.txt”), Charset.defaultCharset())
         .count();

    无限?/strong>。最后,在ȝ本文之前Q有一个o人非常兴奋的L。到现在为止Q你应该理解到流中的元素是按需生成的。有两个静态方?-Stream.iterate()和Stream.generate()--可以让你从一个函C创徏。然而,因ؓ被用的元素是按需生成的,所以这两个Ҏ可以"永远?生成元素。这是Z么我们称它ؓ无限:它就是没有固定大的,但它做的事情与一个从固定集合生成的流是一L?/span>
    清单18是一个用iterateҎ的例子,它会包含10的所有倍数。iterateҎ使用一个v始?此处?)和一个Lambda表达?cd为UnaryOperator<T>)去顺序地生成每一个新倹{?/span>
清单18
Stream<Integer> numbers = Stream.iterate(0, n -> n + 10);

    我们也可以用limitҎQ以从一个无限流中得C个固定流。如清单19所C,可以流的长度限制ؓ5?/span>
清单19
numbers.limit(5).forEach(System.out::println); // 0, 10, 20, 30, 40

l论
    Java SE 8引入了Streams APIQ它让你能够表达更ؓ复杂的数据处理查询。在本文中,你已见到可以支持许多操作,诸如qoQ映,归一和P代,把它们结合在一起可以写出简z的、更富表现力的数据处理查询。这U新的编E方法远不同于Java SE 8之前的集合处理。但是,它有许多好处。首先,它利用到了诸如惰性或短\q样的技术,以优化数据处理查询的性能。其ơ,能够自动地利用上多核架构Q以q行地处理流。在本文章系l的W二部分中,我们探索更高的操作,例如flatMap和collect。请l箋x?/span>


John Jiang 2014-07-27 20:54 发表评论
]]>
2014q?4月美国非农业׃情况报告(?http://www.dentisthealthcenter.com/jiangshachina/archive/2014/05/05/413246.htmlJohn JiangJohn JiangSun, 04 May 2014 16:12:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2014/05/05/413246.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/413246.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2014/05/05/413246.html#Feedback1http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/413246.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/413246.html
2014q?4月美国非农业׃情况报告
本文是上周五(2014.05.02)才发布的国2014q?4月度非农业就业报?/a>中的概要部分Q与软g技术毫无干pR这份报告在出台后的极短旉内对外汇市场造成了极大媄响,Ua是出于好奇看了一下这份报告,学到了不英文行业名Uͼ以及英文在数字方面的用法?2014.05.05最后更?

    今天Q美国劳工统计v报告Q?月䆾的全部非农业׃人数增加?88000Q而且׃率降低了0.4个百分点Q达到百分之6.3。就业增长的扩大受益于专业与商业服务业,零售业,饮业和建筑业?/span>

家庭调查数据
    ?月䆾Q失业率从百分之6.7降到了百分之6.3Q失业h数则?80万,减少?3.3万。这两项l计展示了前4个月的动态。今q以来,׃率和׃人数分别减少?.2个百分点?90万h?详见表A-1)
    在主要的力_者分l中Q?月的׃率分别下降了Q成q男?5.9%)Q成q女?5.7%)Q青年(19.1%)Q白?5.3%)Q黑?11.6%)Q以及西班牙?7.3%)。亚裔的无业率ؓ5.7%(未扣除季节性因?Q今q以来,该值的变化很小?详见表A-1QA-2和A-3)
    ?月里Q再ơ失业者与新工作者分别减了41.7万和12.6万?再次׃者之前工作过Q但在开始重新找工作之前他们q不作力_力;而新工作者是指之前从未工作过的h?丢掉工作的和只完成时工作的力_者的数量减少?5.3万,辑ֈ520万?详见表A-11)
    长期׃?指失业时间达到或过27周的?的数量在4月䆾减少?8.7万,辑ֈ350万;q些个体占失业h数的35.3%。在q去?2个月里,长期׃者的数量已经减少?0.8万?详见表A-12)
    随着民用力_力在3月增?0.3万之后,民用力_力在4月里减少?0.6万。在最q几个月里,׃参与率ƈ没有昄出清晰的势Q当前的gd10月的值是相同的。在q去q个月中Q就业者与人口比例没有发生变化(58.9%)Q但在过一q中略有变化?详见表A-1)
    Zl济原因的兼职劳动?有时候也UCZ情愿的兼职劳动?的数量在4月略有变化,辑ֈ750万。这些个体之所以兼职是׃他们的工作时间被削减Q或者是他们无法扑ֈ全职工作?详见表A-8)
    4月䆾?20万准待业力_力,比今q早些时候有d下降(该数据未扣除季节性因?。这些个体ƈ不算作劳动力Q尽他们愿意ƈ且也可以d作,而且在前12个月的某些时候也找过工作。但是他们ƈ不被计算在失业者中Q因为在该调查开始的?周内Q他们ƈ没有L工作?详见表A-16)
    在这些准待业者中Q本月有78.3万就业信心p,比今q早些时候略有变?该数据未扣除季节性因?。就业信心p目前ƈ没有d扑ַ作,因ؓ他们认ؓ没有合适他们的工作。剩余的140万准待业者没有去L工作的原因则是诸如就学或家庭责Q?详见表A-16)

机构调查数据
    全部的非农业׃者在4月䆾增长?8.8万。在?2个月中,q_每个月增?9.9万个工作岗位。就业增长在4月里得到了扩大,q得益于专业与商业服务业Q零售业Q餐饮业和徏{业的就业增ѝ?详见表B-1)
    专业与商业服务业?月䆾增加?.5万个׃岗位。在?2个月内,该行业每个月会^均增?.5万个׃。在本月Q该行业的就业增长仍在l,其中临时辅助服务?2.4+?Q公怸企业理(1.2+?Q计机pȝ设计及其相关服务?0.9+??/span>
    零售业的׃岗位在本月增加了3.5万个。在q去?2个月中,该行业的׃已经增加?2.7万。在零售业中Q本月的工作岗位增长发生在饮食商?0.9+?Q日用百货商?0.8+?Q汽车与雉件商(0.6+?和非实体商店(0.4+?。电子与电器商店在本月则丢失?.1万工作岗位。批发业在过M个月内增加了1.6万个工作岗位Q而它在过Mq内增加?2.6万个工作岗位?/span>
    饮业在本月增加的就业数(3.3+?则与q去12个月的^均增?2.8??处于同一水^U上?/span>
    建筑行业的就业在4月䆾增加?.2万个Q这些工作增长分别ؓ重型和民用工E徏{业(1.1+?和住宅徏{业(0.7+?。在q去一q中Q徏{业已经增加?8.9万个工作岗位Q而且其中几乎四分之三的增镉K发生在过?个月中?/span>
    ȝ行业本月增加?.9万个岗位Q与q去12个月的^均增?1.7??水^相持q뀂包括会员协会,个h服务业与z业等其它服务性行业的׃在这个月内增加了15000个?/span>
    采矿业在4月䆾增加?0000个工作岗位,大部分的增长都是在针寚w矿的支持zd?7000+)?/span>
    其它主要行业Q包括制造业Q运输与仓储业,信息服务业,金融服务Q以及政府服务业Q的׃在这个月内少有变化?/span>
    全部U营非农业就业者的q_周工作时间没有变化,仍ؓ34.5时。制造业的周工作旉减少?.2个小Ӟ辑ֈ40.8个小时。工厂的加班旉没有变化Q仍?.5个小时。私营非农业׃者中的生产与非管理阶层员工的q_周工作时间也没有发生变化Q依然ؓ33.7个小时?详见B-2和B-7)
    本月Q私营非农业׃者的q_时薪仍然?4.31元。在q去12个月中,q_时薪已经增长?.9%。私营的生与非理阶层员工的^均时薪则增长?%Q达?0.50元?详见表B-3和B-8)
    二月份的全部的非农业׃岗位?9.7+万修正ؓ22.2+万,而对于三月䆾的这一数据Q则是从19.2+万修正ؓ20.3+万。根据这些修正,二月份与三月份的׃增长比之前报告的要高?.6万个?br />


John Jiang 2014-05-05 00:12 发表评论
]]>
Java 8的语a变化(?http://www.dentisthealthcenter.com/jiangshachina/archive/2014/04/19/412695.htmlJohn JiangJohn JiangSat, 19 Apr 2014 15:48:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2014/04/19/412695.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/412695.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2014/04/19/412695.html#Feedback0http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/412695.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/412695.html
Java 8的语a变化
--理解Lambda表达式和变化的接口类是如何Java 8成ؓ新的语言
本文是IBM developerWorks中的一介lJava 8关键新特性的文章Q它主要xLambda表达式和改进的接口?2014.04.19最后更?

    Java 8包含了一l重要的新的语言Ҏ,使你能够更方便地构造程序。Lambda表达为内联的代码块定义了一U新的语法,l予你与匿名内部cȝ同的灉|性,但又没有那么多模板代码。接口的改变使得能够为已有接口加入新的特性,而不必打破现有代码的兼容性。了解这些语a变化是怎样一起工作的Q请阅读本系列另一文?Java 8q发基础"Q可以看到如何在Java 8中使用Lambda?/span>
    Java 8的最大改变就是增加了对Lambda表达式的支持。Lambda表达式一U通过引用q行传递的代码块。它cM于某些其它语a的闭包:代码实现了一个功能,可以传入一个或多个参数Q还可以q回一个结果倹{闭包被定义在一个上下文中,它可以访?在Lambda中是只读讉K)上下文中的倹{?/span>
    如果你不熟悉闭包Q也不必担心。Java 8的Lambda表达式是几乎每个Java开发者都熟悉的匿名内部类的一个高效版规范。如果你只想在一个位|实C个接口,或是创徏一个基cȝ子类Ӟ匿名内部cMؓ此提供了一U内联实现。Lambda表达式也用于相同的方式,但是它用一U羃略的语法Q得这些实现比一个标准的内部cd义更为简z?/span>
    在本文中Q你看到如何在不同的场景下使用Lambda表达式,q且你会学到与Java接口定义相关的扩展。在本文章的姊妹JVMq发pd?Java 8q发基础"一文中Q可以看到更多用Lambda表达式的例子Q包括在Java 8特性中的应用?/span>

q入Lambda
    Lambda表达式就是Java 8所U的函数接口的实玎ͼ一个接口只定义一个抽象方法。只定义一个抽象方法的限制是非帔R要的Q因为Lambda表达式的语法q不会用方法名。相反,该表辑ּ会用动态类型识?匚w参数和返回类型,很多动态语a都这么做)M证提供的Lambda能够与期望的接口Ҏ兼容?/span>
    在清?所C的单例子中Q一个Lambda表达式被用来对Name实例q行排序。main()Ҏ中的W一个代码块使用一个匿名内部类d现Comparator<Name>接口Q第二个语句块则使用Lambda表达式?/span>
清单1. 比较Lambda表达式与匿名内部c?/span>
public class Name {
    
public final String firstName;
    
public final String lastName;

    
public Name(String first, String last) {
        firstName 
= first;
        lastName 
= last;
    }

    
// only needed for chained comparator
    public String getFirstName() {
        
return firstName;
    }

    
// only needed for chained comparator
    public String getLastName() {
        
return lastName;
    }

    
// only needed for direct comparator (not for chained comparator)
    public int compareTo(Name other) {
        
int diff = lastName.compareTo(other.lastName);
        
if (diff == 0) {
            diff 
= firstName.compareTo(other.firstName);
        }
        
return diff;
    }
    
}

public class NameSort {
    
    
private static final Name[] NAMES = new Name[] {
        
new Name("Sally""Smith"),
        
    };
    
    
private static void printNames(String caption, Name[] names) {
        
    }

    
public static void main(String[] args) {

        
// sort array using anonymous inner class
        Name[] copy = Arrays.copyOf(NAMES, NAMES.length);
        Arrays.sort(copy, 
new Comparator<Name>() {
            @Override
            
public int compare(Name a, Name b) {
                
return a.compareTo(b);
            }
        });
        printNames(
"Names sorted with anonymous inner class:", copy);

        
// sort array using lambda expression
        copy = Arrays.copyOf(NAMES, NAMES.length);
        Arrays.sort(copy, (a, b) 
-> a.compareTo(b));
        printNames(
"Names sorted with lambda expression:", copy);
        
    }
}
    在清?中,Lambda被用于取代匿名内部类。这U匿名内部类在应用中非常普遍Q所以Lambda表达式很快就赢得了Java8E序员们的青睐?在本例中Q同时用匿名内部类和Lambda表达式去实现NamecM的一个方法,以方便对q两U方法进行比较。如果在Lambda中对compareTo()Ҏq行内联的话Q该表达式将会更加简z?

标准的函数式接口
    Z应用LambdaQ新的包java.util.function中定义了q泛的函数式接口。它们被归结为如下几个类别:
    函数Q用一个参敎ͼZ参数的D回结果?/span>
    谓语Q用一个参敎ͼZ参数的D回布结果?/span>
    双函敎ͼ使用两个参数Q基于参数的D回结果?/span>
    供应器:不用Q何参敎ͼ但会q回l果?/span>
    消费者:使用一个参敎ͼ但不q回Ml果?/span>
多数cd都包含多个不同的变体Q以便能够作用于基本数据cd的参数和q回倹{许多接口所定义的方法都可被用于l合对象Q如清单2所C:
清单2. l合谓语
// use predicate composition to remove matching names
List<Name> list = new ArrayList<>();
for (Name name : NAMES) {
    list.add(name);
}
Predicate
<Name> pred1 = name -> "Sally".equals(name.firstName);
Predicate
<Name> pred2 = name -> "Queue".equals(name.lastName);
list.removeIf(pred1.or(pred2));
printNames(
"Names filtered by predicate:", list.toArray(new Name[list.size()]));
    清单2定义了一对Predicate<Name>变量Q一个用于匹配名为Sally的名字,另一个用于匹配姓为Queue的名字。调用方法pred1.or(pred2)会构造一个组合谓语,该谓语先后用了两个谓语Q当它们中的M一个返回trueӞq个l合谓语将q回true(q就相当于早期Java中的逻辑操作W||)。List.removeIf()Ҏ应用这个组合谓语去删除列表中的匚w名字?/span>
    Java 8定义了许多有用的java.util.function包中接口的组合接口,但这U组合ƈ不都是一L。所有的谓语的变?DoublePredicateQIntPredicateQLongPredicate和Predicate<T>)都定义了相同的组合与修改ҎQand()Qnegate()和or()。但是Function<T>的基本数据类型变体就没有定义Ml合与修Ҏ法。如果你拥有使用函数式编E语a的经验,那么你可能就发会发现q些不同之处和奇怪的忽略?/span>

改变接口
    在Java 8中,接口(如清?的Comparator)的结构已发生了改变,部分原因是ؓ了让Lambda更好用。Java 8之前的接口只能定义常量,以及必须被实现的抽象Ҏ。而Java 8中的接口则能够定义静态与默认Ҏ。接口中的静态方法与抽象cM的静态方法是完全一L。默认方法则更像旧式的接口方法,但提供了该方法的一个实现。该Ҏ实现可用于该接口的实现类Q除非它被实现类覆盖掉了?/span>
    默认Ҏ的一个重要特性就是它可以被加入到已有接口中,但又不会破坏已用了q些接口的代码的兼容?除非已有代码恰y使用了相同名字的ҎQƈ且其目的与默认方法不?。这是一个非常强大的功能QJava 8的设计者们利用q一Ҏؓ许多已有Javacd加入了对Lambda表达式的支持。清?展CZq样的一个例子,它是清单1中对名字q行排序的第三种实现方式?/span>
清单3. ?提取比较器链
// sort array using key-extractor lambdas
copy = Arrays.copyOf(NAMES, NAMES.length);
Comparator
<Name> comp = Comparator.comparing(name -> name.lastName);
comp 
= comp.thenComparing(name -> name.firstName);
Arrays.sort(copy, comp);
printNames(
"Names sorted with key extractor comparator:", copy);
    清单3首先展示了如何用新的Comparator.comparing()静态方法去创徏一个基于键-提取(Key-Extraction) Lambda的比较器(从技术上看,?提取Lambda是java.util.function.Function<T,R>接口的一个实例,它返回的比较器的cd适用于类型TQ而提取的键的cdR则要实现Comparable接口)。它q展CZ如何使用新的Comparator.thenComparing()默认Ҏȝ合用比较器Q清?p回了一个新的比较器Q它会先按姓排序Q再按名排序?/span>
    你也许期望能够对比较器进行内联,如:
Comparator<Name> comp = Comparator.comparing(name -> name.lastName)
    .thenComparing(name 
-> name.firstName);
但不q地是,Java 8的类型推g允许q么做。ؓ从静态方法中得到期望cd的结果,你需要ؓ~译器提供更多的信息Q可以用如下Q何一UŞ式:
Comparator<Name> com1 = Comparator.comparing((Name name1) -> name1.lastName)
    .thenComparing(name2 
-> name2.firstName);
Comparator
<Name> com2 = Comparator.<Name,String>comparing(name1 -> name1.lastName)
    .thenComparing(name2 
-> name2.firstName);
    W一U方式在Lambda表达式中加入参数的类型:(Name name1) -> name1.lastName。有了这个辅助信息,~译才能知道下面它该做些什么。第二种方式是告诉编译器要传递给Function接口(在此处,该接口通过Lambda表达式实?中comparing()Ҏ的泛型变量T和R的类型?/span>
    能够方便地构建比较器以及比较器链是Java 8中很有用的特性,但它的代h增加了复杂度。Java 7的Comparator接口定义了两个方?compare()ҎQ以及遍布于每个对象中的equals()Ҏ)。而在Java 8中,该接口则定义?8个方?除了原有?个方法,q新加入?个静态方法和7个默认方?。你发玎ͼZ能够使用Lambda而造成的这U接口膨胀会重C相当一部分Java标准cd中?/span>

像Lambda那样使用已有Ҏ
    如果一个存在的Ҏ已经实现了你的需求,你可以直接用一个方法引用对它进行传递。清?展示了这U方法?/span>
清单4. 对已有方法用Lambda

// sort array using existing methods as lambdas
copy = Arrays.copyOf(NAMES, NAMES.length);
comp 
= Comparator.comparing(Name::getLastName).thenComparing(Name::getFirstName);
Arrays.sort(copy, comp);
printNames(
"Names sorted with existing methods as lambdas:", copy);
    清单4做着与清?相同的事情,但它使用了已有方法。用Java 8的Ş?cd:Ҏ?的方法引用语法,你可以用Q意方法,像Lambda表达式那栗其效果׃你定义一个Lambda表达式去调用该方法一栗对cȝ静态方法,特定对象或Lambda输入cd的实例方?如在清单4中,getFirstName()和getLastName()Ҏ是Namecȝ实例Ҏ)Q以及类构造器Q都可以使用Ҏ引用?/span>
    Ҏ引用不仅方便Q因为它们比使用Lambda表达式可能更高效Q而且为编译器提供了更好的cd信息(q也是Z么在上一节的Lambda中?thenComparing()构造Comparator会出现问题,而在清单4却能正常工作)。如果既可以使用对已有方法的Ҏ引用Q也可以使用Lambda表达式,请用前者?/span>

捕获与非捕获Lambda
    你在本文中已见过的Lambda表达式都是非捕获的,意即Q它们都是把传入的值当作接口方法参C用的单Lambda表达式。Java 8的捕获Lambda表达式则是用外围环境中的倹{捕获LambdacM于某些JVM语言(如Scala)使用的闭包,但Java 8的实C之有所不同Q因为来自在外围环境中的值必d明ؓfinal。也是_q些D么确实ؓfinal(如同以前的Java版本中由匿名内部cL引用的?Q要么在外围环境中不会被修改。这一规范适用于Lambda表达式和匿名内部cR有一些方法可以绕q对值的final限制。例如,在Lambda中仅使用特定变量的当前|你可以添加一个新的方法,把这些g为方法参敎ͼ再将捕获的?以恰当的接口引用q种形式)q回lLambda。如果期望一个LambdaM改外围环境中的|那么可以用一个可修改的持有器c?Holder)对这些D行包装?/span>
    相比于捕获LambdaQ可以更高效地处理非捕获LambdaQ那是因为编译能够把它生成ؓcM的静态方法,而运行时环境可以直接内联的调用这些方法。捕获Lambda也许低效一些,但在相同上下文环境中它至可以表现的和匿名内部类一样好?/span>

q后的Lambda
    Lambda表达式看h像匿名内部类Q但它们的实现方法不同。Java的内部类有很多构造器Q每个内部类都会有一个字节码U别的独立类文g。这׃产生大量的重复代?大部分是在常量池实体?Q类加蝲时会造成大量的运行时开销Q哪怕只有少量的代码也会有如此后果?/span>
    Java 8没有为Lambda生成独立的类文gQ而是使用了在Java 7中引入的invokedynamic字节码指令。invokedynamic作用于一个启动方法,当该ҎW一ơ被调用时它会{而去创徏Lambda表达式的实现。然后,该实C被返回ƈ被直接调用。这样就避免了独立类文g带来的空间开销Q以及加载类的大量运行时开销。确切地_Lambda功能的实现被丢给了启动程序。目前Java 8生成的启动程序会在运行时为Lambda创徏一个新c,但在来会用不同的Ҏd现?/span>
    Java 8使用的优化得通过invokedynamic指o实现的Lambda在实际中q行正常。多数其它的JVM语言Q包括Scala (2.10.x)Q都会ؓ闭包使用~译器生成的内部cR在来Q这些语a可能会{而用invokedynamic指oQ以便利用到Java 8(及其后版本)的优化?/span>

Lambda的局?/span>
    如在本文开始时我所提到的,Lambda表达式L某些Ҏ函数式接口的实现。你可以仅把Lambda当作接口引用M递,而对于其它的接口实现Q你也可以只是把Lambda当作q些特定接口M用。清?展示了这U局限性,在该CZ使用了一对相同的(名称除外)函数式接口。Java 8~译接受String::lenght来作两个接口的Lambda实现。但是,在一个Lambd表达式被定义为第一个接口的实例之后Q它不能够用于第二个接口的实例?/span>
清单5. Lambda的局?/span>
private interface A {
    
public int valueA(String s);
}
private interface B {
    
public int valueB(String s);
}
public static void main(String[] args) {
    A a 
= String::length;
    B b 
= String::length;

    
// compiler error!
    
// b = a;

    
// ClassCastException at runtime!
    
// b = (B)a;

    
// works, using a method reference
    b = a::valueA;
    System.out.println(b.valueB(
"abc"));
}
    M对Java接口概念有所了解的h都不会对清单5中的E序感到惊讶Q因为那是Java接口一直所做的事情(除了最后一点,那是Java 8新引入的Ҏ引用)。但是用其它函数式~程语言Q例如ScalaQ的开发者们则会认ؓ接口的这U限制是不自然的?/span>
    函数式编E语a是用函数cdQ而不是接口,d义变量。在q些~程语言中会很普遍的使用高函数Q把函数作ؓ参数传递给其它的函敎ͼ或者把函数当作值去q回。其l果是你会得到比Lambda更ؓ灉|的编E风|q包括用函数去l合其它函数以构句块的能力。因为Java 8没有定义函数cdQ你不能使用q种Ҏȝ合Lambda表达式。你可以l合接口(如清?所C?Q但只能是与Java 8中已写好的那些接口相关的特定接口。仅在新的java.util.function包内Q就Ҏ讑֮?3个接口去使用Lambda。把它们加入到数以百计的已有接口中,你将看到q种Ҏ在组合接口时L会有严重的限制?/span>
    使用接口而不是在向Java中引入函数类型是一个精妙的选择。这样就在防止对Javacdq行重大改动的同时也能够对已有类库用Lambda表达式。它的坏作用是对Java 8造成了极大的限制Q它只能UCؓ"接口~程"或是cd数式~程Q而不是真正的函数式编E。但依靠JVM上其它语aQ也包括函数式语aQ的优点Q这些限制ƈ不可怕?/span>

l论
    Lambda是Java语言的最主要扩展Q伴着它们的兄弟新Ҏ?-Ҏ引用Q随着E序被移植到Java 8QLambda很快成为所有Java开发者不可或~的工具。当与Java 8结合v来时QLambdaq别有用。查看文?JVMq发: Java 8q发基础"Q可以了解到Lambda和流l合h使用是如何简化ƈ发编E以及提高程序效率的?/span>


John Jiang 2014-04-19 23:48 发表评论
]]>
Java的8个特??http://www.dentisthealthcenter.com/jiangshachina/archive/2014/02/28/410455.htmlJohn JiangJohn JiangFri, 28 Feb 2014 07:25:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2014/02/28/410455.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/410455.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2014/02/28/410455.html#Feedback0http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/410455.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/410455.html
Java的8个特?/span>
本文是稍早前java.net推荐的一?/span>博文Q描qCJava的8个有用的Ҏ?2014.03.07最后更?

Lamba表达式是Java8到目前ؓ止最的Ҏ。但我认为有一个秘密武器展CZLambdaq一"语法p?在提高代码可L和可写性方面是何其的强大。当你在改进代码的表现力Ӟ那么在对代码的理解方面你׃升到了新的境界,q能使最W拙的工作变得简单?/span>

是什么秘密武器呢Q就是Java Stream API。最q我参与了一个在U比赛,是在一个对性能要求较高的环境中单地使用Java Stream。o我惊讶的是,q个API让编写主要的循环E序变得十分单,而且能很好地适应我所做出的众多变化。下面就是我所学到?个特性?/span>

1. Java不需要Lambda表达?/strong>
管q个API实从Lambda表达式中获准良多Q但你ƈ不必非得使用Lambda。你可以回过M用匿名内部类Q但Z么要q么做呢Q较可能的场景是Q用一个方法引?例如Integer::valueOf)Q或者一个实例对象。用方法引用可复杂的多行逻辑|于循环体之外,如你在优化一个hash set查找时所看到的。而实例对象可用于实现"四h?的策略模式。但请不要用匿名内部类Q除非你不得不这么做?br />
2. H入内q行调试
你可以在的M位置攑օ你所惛_q去的媒质,q个媒质UCؓpeek。该操作使用了一个消费者对象,q期望不产生Ml果Q因为Lambda一般只q回I。我喜欢把peek用于向系l发送调试信息,如
.peek(System.out::println)
.peek(it 
-> System.out.printf("it is %s%n", it)

3. 化随机成员
ƈ不局限于集合或数l,甚至是固定链表。如果你能创Z个Iterator或Supplier Lambda来创建流中的|然后你就可以使用cjava.util.stream.StreamSupport中的Ҏ来创Z个流了。可以设想一个用持l测量|如内存消耗量或网l吞量,来驱动的?br />
4. 化随机?/strong>
如果你正在寻找一个简单的随机敎ͼ例如可以通过java.util.RandomQ这个类现在有了三个新的setҎints()Qlongs()和doubles()来创建流。这些方法的重蝲版本可以让你讄边界Q随机种子以及流中随机数的总量?br />
5. 化I/O Reader
JavaE序员的另一个常见工作就是一行一行地解析文g。现在java.io.BufferedReader有了一个新Ҏlines()Q它会将I/O{化ؓ一个字W串,以便于流的处理?br />
6. 化文g?/strong>
如果讉K的文件ƈ不是你的菜,那就试试讉K一个文件树会怎么Pcjava.nio.file.Files中有几个Ҏ都可以返回流。list()Ҏ列Z个目录下的所有文Ӟwalk()Ҏ会递归地做到这一点,而filter()Ҏ也会递归地访问这些文Ӟ但会使用一些属性来q行qo(当你有一个Path对象Q有些事情会变得复杂h)。你依然可以使用lines(Path)Ҏ来通过去获取内容?br />
7. 化复杂文本
如果你依然念念不忘文本处理,但内容ƈ不是Z行,那么可以在java.util.regex.Pattern实例中用splitAsStream(CharSequence)Ҏ。这对于处理有数百万列的CSV文g或CLASSPATH十分有用?br />
8. 化ZIP文g
说到寚wCLASSPATH的搜索,你也可以很简单地调用名ؓstream的方法来化java.util.zip.ZipFiles和java.util.jar.JarFilesQ它会相应地q回一个ZipEntry或JarEntry实例?br />
如果你都已经q过q些事了Q那么你肯定知道它们q不是Java的基本用途。不q将来会有够多的博文去涉及Java的基础。我只是认ؓ上述q些都是被掩藏v来的宝藏Q它们揭CZJava的潜质?/div>

John Jiang 2014-02-28 15:25 发表评论
]]>
使用Gradle构徏Java Web应用(?http://www.dentisthealthcenter.com/jiangshachina/archive/2014/01/23/409285.htmlJohn JiangJohn JiangThu, 23 Jan 2014 13:22:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2014/01/23/409285.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/409285.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2014/01/23/409285.html#Feedback8http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/409285.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/409285.html
使用Gradle构徏Java Web应用
本文是发布在java.net上的一摘自于<Gradle in Action>一书中?a >节?/a>Q介l了使用Gradle构徏Java Web应用的过E。刚刚接触GradleQ看Cq篇文Q随手译了出?-) (2014.01.23最后更?

当今世界Q一z忙。在职业生和私人生zMQ我们中间的许多同时理多个目。你可能常常发现自己处于不知所措及失控的状态。保持规整ƈ专注于h值的关键是一个维护良好的工作清单。当Ӟ你可能L把你的Q务写在一张纸上,但是你也怸可能在你所处的M地方都可方便地获得这些工作条目?对互联网的访问几乎是无处不在的,无论是通过你的Ud电话Q还是公q|络接入炏V在<Gradle in Action>一书中Q如?所C的说明性示例是一个很有吸引力的可视化Web应用?/span>

? To Do应用可以通过互联|进行访问,q用它ȝ理数据存储中的工作条?/strong>


Gradle插g表现的如同一个能器Q它会自动地执行q些d。一个插仉过引入特定领域的规范以及对~省值敏感的dd工程q行扩展。随Gradle发布的插件之一是Java插g。该Java插gl不仅仅是提供了源码~译和打包这L基础功能。它为工E徏立了一整套标准的目录布局Q它会确保以正确的顺序去执行dQ这Pq些d在Java工程环境中才是有意义的。现在是时候ؓ我们的应用去创徏一个构本ƈM用这个Java插g了?/span>

构徏Java应用
一开始,每个Gradle工程都会创徏一个名为build.gradle的构本。ؓ了创脚本Qƈ告诉该工E用Java插gQ应该像q样dQ?/span>
apply plugin: 'java'
Z构徏你的Java代码Q一行代码就够了。但Gradle怎么知道d儿找你的源文件呢Q该Java插g引入的规范之一是源代码的路径。默认地Q该插g会到目录src/main/java中搜M品的源代码?/span>

构徏Web应用
通过War插gQGradle也提供了构徏Web应用的扩展支持。War插g扩展了Java插gQ它加入了针对Web应用E序开发的规范Qƈ支持归集WAR文g。让我们也在q个工程中用用War插gQ?/span>
apply plugin: 'war'
Web应用源文件的默认路径是src/main/webapp。假设你已经明确了该应用所必要的JavacR那么要使品的全部源代码和Web资源文g处于正确路径下,该工E的目录布局应该像下面这P
.
├── build.gradle
└── src
    └── main
        ├── java
        │   └── com
        │       └── manning
        │           └── gia
        │               └── todo
        │                   ├── model
        │                   │   └── ToDoItem.java
        │                   ├── repository
        │                   │   ├── InMemoryToDoRepository.java
        │                   │   └── ToDoRepository.java
        │                   └── web
        │                       └── ToDoServlet.java
        └── webapp                                               #A
            ├── WEB-INF
            │   └── web.xml                                      #B
            ├── css                                              #C
            │   ├── base.css
            │   └── bg.png
            └── jsp                                              #D
                ├── index.jsp
                └── todo-list.jsp

#A Web源文仉认目?br /> #B Web应用描述W文?br /> #C 存储描述如何展现HTML元素的样式单文g的目?br /> #D 存放JSP形式的动态脚本化视图lg

声明外部依赖
在实现这个Web应用的过E,我们使用的一些类Q例如javax.servlet.HttpServletQƈ非Java标准?Java SE)的一部分。在构徏工程之前Q我们需要确保已l声明了q些外部依赖。在Javapȝ中,依赖cd是以JAR文g的Ş式去发布和用的。许多类库可以从仓库Q如一个文件系l或中央服务器,中获得。ؓ了用依赖,Gradle要求你至定义一个仓库。出于一些考虑Q我们将使用公共的可通过互联|进行访问的Maven Central仓库?/span>
repositories {
   mavenCentral()                   #A
}
#A 通过http://repo1.maven.org/maven2讉KMaven2中央仓库的简短标?/span>
在Gradle中,依赖是通过配置Ҏq行分组的。我们将来Servlet依赖使用的配|项是providedCompile。该配置用于那些在~译时而非q行时所需的依赖。像JSTLq样的运行时依赖Q在~译时不会被用到Q但在运行时则会被用到。它们都会成为WAR文g的一部分。下面的配置语句块声明了我们应用所需的外部类库:
dependencies {
   providedCompile 'javax.servlet:servlet-api:2.5'
   runtime 'javax.servlet:jstl:1.1.2'
}

构徏工程
我们已经准备好构个工E了。另到工E中的一个Java插gd名ؓbuild。该d编译源代码Q运行测试程序ƈ归集WAR文g--所有的q些d都将以正的序被执行。执行命令gradle build之后Q你可能会得到Ş如下面的输出Q?/span>
$ gradle build
:compileJava                                      #A
:processResources UP-TO-DATE
:classes
:war                                              #B
:assemble
:compileTestJava UP-TO-DATE                       #C
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test                                             #D
:check
:build

#A ~译产品的Java源代?br /> #B War插g提供的Q务,用于归集WAR文g
#C ~译Java试源代?br /> #D q行单元试

上述输出的每一行都代表执行了一个由Java或War插g提供的Q务。你可能会注意到Q有一些Q务被标记为UP-TO-DATE。它的意思是指该d被蟩q去了。Gradle的增量构建支持策略会自动识别不需要执行的工作。特别是在大型商业项目中Q该Ҏ会极大地节省时间?/span>
在该工程的根节目录中Q你会发现一个名为build的子目录Q它包含有执行构Z后的全部输出Q包括类文gQ测试报告,归集的WAR文gQ以及像manifestq样的在打包旉要的临时文g。如下就是执行构建工作之后的工程目录l构Q?/span>
.
├── build
│   ├── classes
│   │   └── main                                          #A
│   │       └── com
│   │           └── manning
│   │               └── gia
│   │                   └── todo
│   │                       ├── model
│   │                       │   └── ToDoItem.class
│   │                       ├── repository
│   │                       │   ├── InMemoryToDoRepository.class
│   │                       │   └── ToDoRepository.class
│   │                       └── web
│   │                           ├── ToDoServlet$ToDoListStats.class
│   │                           └── ToDoServlet.class
│   ├── dependency-cache
│   ├── libs
│   │   └── todo-webapp.war                               #B
│   ├── reports
│   │   └── tests
│   │       ├── base-style.css
│   │       ├── css3-pie-1.0beta3.htc
│   │       ├── index.html
│   │       ├── report.js
│   │       └── style.css
│   ├── test-results
│   │   └── binary
│   │       └── test
│   │           └── results.bin
│   └── tmp
│       └── war
│           └── MANIFEST.MF                                #C
├── build.gradle
└── src

#A 包含JavacL件的默认目录
#B 归集的WAR文g
#C 用于WAR的时manifest文g
你已l知道如何从一个基于标准目录结构的Web工程L建WAR文g。现在是时候将它布|到一个Servlet容器中去了。在下一节中Q我们将在本地开发机器中启动Jetty去运行这个Web应用?/span>

q行应用
在本地机器中q行一个Web应用应该很容易,能够实践快速应用开?RAD)Qƈ能够提供快速的启动旉。最地是,它不要求你部|一个Web容器q行时环境。Jetty一个流行的轻量U开源Web容器Q它支持前面提到的所有特性。在q个Web应用中加入一个HTTP模块Q它变成了一个嵌入式实现。Gradle的Jetty插g扩展了War插gQ它提供的Q务可以将一个Web应用部v到嵌入式容器中,q能够启动该应用。在你的构徏脚本中,可以像如下那样用这个插Ӟ
apply plugin: 'jetty'
q个被我们用于启动Web应用的Q务名为jettyRun。它甚至可以在无需创徏WAR文g的情况下启动一个Jetty容器。执行上q命令后会得到如下Ş式的输出Q?/span>
$ gradle jettyRun
:compileJava
:processResources UP-TO-DATE
:classes
> Building > :jettyRun > Running at http://localhost:8080/todo-webapp-jetty
在上q输出的最后一行中Q该插g告诉了你Jetty卛_侦听的请求地址。打开一个你喜欢的浏览器Qƈ输入上述地址。最后,我们会看到这个To Do Web应用的行为。图2展示在一个浏览器中查看到该应用界面的截屏?/span>

? To Do应用的Web界面及其行ؓ

在你通过l合键CTRL+Cd止这个应用之前,Gradle会让它一直运行。Jetty如何知道使用哪个端口和上下文环境去运行这个Web应用Q再说一遍,q就是规范。Jettyq行Web应用所使用的默认端口就?080?/span>

ȝ
只需要较的努力Q你可以用GradleL建ƈq行一个Java Web应用。只要你严格遵@标准目录l构Q那么你的构本仅需要两行代码?/span>


John Jiang 2014-01-23 21:22 发表评论
]]>
Javaq发基础实践--死锁(?http://www.dentisthealthcenter.com/jiangshachina/archive/2013/12/29/408180.htmlJohn JiangJohn JiangSun, 29 Dec 2013 12:19:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/12/29/408180.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/408180.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/12/29/408180.html#Feedback1http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/408180.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/408180.html
Javaq发基础实践--死锁
本文?a href="http://www.dentisthealthcenter.com/jiangshachina/category/53896.html">Javaq发基础实践pd中的一,介绍了最单的死锁场景Qƈ使用jstack产生的thread dump来查找死锁?2013.12.29最后更?

1. 死锁
Z能够l护U程的安全性,Java提供的锁机制Q但不恰当地使用锁则可能产生死锁。死锁是q发~程中一个无法绕开的问题。只要在一个Q务中使用了一个以上的锁,那么存在死锁的风险?br />死锁产生的直接原因非常简单,即两个线E在怺{待Ҏ所执有的锁?br />
2. 锁顺序死?/span>
在死锁场景中Q最典型的就是锁序死锁Q代码清?是一个很常见的示例?br />
清单1
public class DeadLock {

    
private Object leftLock = new Object();
    
private Object rightLock = new Object();

    
public void leftRight() {
        
synchronized (leftLock) {
            
try {
                TimeUnit.SECONDS.sleep(
3);
            } 
catch (InterruptedException e) {
                e.printStackTrace();
            }

            
synchronized (rightLock) {
                System.out.println(
"leftRight");
            }
        }
    }

    
public void rightLeft() {
        
synchronized (rightLock) {
            
try {
                TimeUnit.SECONDS.sleep(
3);
            } 
catch (InterruptedException e) {
                e.printStackTrace();
            }

            
synchronized (leftLock) {
                System.out.println(
"leftRight");
            }
        }
    }

    
public static void main(String[] args) {
        
final DeadLock deadLock = new DeadLock();

        Thread t1 
= new Thread(new Runnable() {

            @Override
            
public void run() {
                deadLock.leftRight();
            }
        });

        Thread t2 
= new Thread(new Runnable() {

            @Override
            
public void run() {
                deadLock.rightLeft();
            }
        });

        t1.start();
        t2.start();
    }
}

3. Thread Dump
JDK提供了一l命令行工具Q其中就包括jstack。通过jstack可以获取当前正运行的Javaq程的java stack和native stack信息。如果Javaq程崩溃了,也可以通过它来获取core file中的java stack和native stack信息Q以方便我们定位问题?br />Z能够使用jstack去输出目标Javaq程的thread dumpQ首先必要弄清楚在执行清单1的程序时Q该E序的进E号。JDK提供的另一个命令行工具jps可以获取pȝ中所有Javaq程的相关信息?br />在命令行H口中执行命?em>jpsQ即可以得到清单2所C的l果
清单2
C:\Documents and Settings\Administrator>jps
2848
4552 DeadLock
5256 Jps
其中4552是在笔者机器上执行E序DeadLock时所生成Javaq程的进E号?br />然后再执行命?em>jstack 4552Q在W者的机器上就会得到清?所C的l果
清单3
C:\Documents and Settings\Administrator>jstack 
4552
2013-12-29 18:45:41
Full thread dump Java HotSpot(TM) Client VM (
23.25-b01 mixed mode, sharing):

"DestroyJavaVM" prio=6 tid=0x00878800 nid=0xd00 waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" prio=6 tid=0x02b56c00 nid=0x14ec waiting for monitor entry [0x02fdf000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at concurrency.deadlock.DeadLock.rightLeft(DeadLock.java:
33)
        - waiting to lock <0x22be6598> (a java.lang.Object)
        - locked <0x22be65a0> (a java.lang.Object)
        at concurrency.deadlock.DeadLock$
2.run(DeadLock.java:53)
        at java.lang.Thread.run(Thread.java:
724)

"Thread-0" prio=6 tid=0x02b55c00 nid=0x354 waiting for monitor entry [0x02f8f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at concurrency.deadlock.DeadLock.leftRight(DeadLock.java:
19)
        - waiting to lock <0x22be65a0> (a java.lang.Object)
        - locked <0x22be6598> (a java.lang.Object)
        at concurrency.deadlock.DeadLock$
1.run(DeadLock.java:45)
        at java.lang.Thread.run(Thread.java:
724)

"Service Thread" daemon prio=6 tid=0x02b34800 nid=0x133c runnable [0x00000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread0" daemon prio=10 tid=0x02b13800 nid=0x10fc waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" daemon prio=10 tid=0x02b11c00 nid=0x1424 waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=10 tid=0x02b10800 nid=0x1100 runnable [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=8 tid=0x02af4c00 nid=0x1238 in Object.wait() [0x02daf000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x22b60fb8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:
135)
        - locked <0x22b60fb8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:
151)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:
189)

"Reference Handler" daemon prio=10 tid=0x02af0000 nid=0x12e8 in Object.wait() [0x02d5f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x22b60da0> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:
503)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:
133)
        - locked <0x22b60da0> (a java.lang.ref.Reference$Lock)

"VM Thread" prio=10 tid=0x02aee400 nid=0x129c runnable

"VM Periodic Task Thread" prio=10 tid=0x02b48000 nid=0x89c waiting on condition

JNI global references: 
117


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x02af4a3c (object 0x22be6598
, a java.lang.Object),
  which is held by 
"Thread-0"
"Thread-0":
  waiting to lock monitor 0x02af310c (object 0x22be65a0
, a java.lang.Object),
  which is held by 
"Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at concurrency.deadlock.DeadLock.rightLeft(DeadLock.java:
33)
        - waiting to lock <0x22be6598> (a java.lang.Object)
        - locked <0x22be65a0> (a java.lang.Object)
        at concurrency.deadlock.DeadLock$
2.run(DeadLock.java:53)
        at java.lang.Thread.run(Thread.java:
724)
"Thread-0":
        at concurrency.deadlock.DeadLock.leftRight(DeadLock.java:
19)
        - waiting to lock <0x22be65a0> (a java.lang.Object)
        - locked <0x22be6598> (a java.lang.Object)
        at concurrency.deadlock.DeadLock$
1.run(DeadLock.java:45)
        at java.lang.Thread.run(Thread.java:
724)

Found 
1 deadlock.
在上q输ZQ我们可以很明确地看C个死?br />
"Thread-1":
  waiting to lock monitor 0x02af4a3c (object 0x22be6598
, a java.lang.Object),
  which is held by 
"Thread-0"
"Thread-0":
  waiting to lock monitor 0x02af310c (object 0x22be65a0
, a java.lang.Object),
  which is held by 
"Thread-1"
q且它还标明了程序是在哪个地Ҏ发现了上q死?br />
"Thread-1":
        at concurrency.deadlock.DeadLock.rightLeft(DeadLock.java:
33)
        - waiting to lock <0x22be6598> (a java.lang.Object)
        - locked <0x22be65a0> (a java.lang.Object)
        at concurrency.deadlock.DeadLock$
2.run(DeadLock.java:53)
        at java.lang.Thread.run(Thread.java:
724)
"Thread-0":
        at concurrency.deadlock.DeadLock.leftRight(DeadLock.java:
19)
        - waiting to lock <0x22be65a0> (a java.lang.Object)
        - locked <0x22be6598> (a java.lang.Object)
        at concurrency.deadlock.DeadLock$
1.run(DeadLock.java:45)
        at java.lang.Thread.run(Thread.java:
724)

4. 结
死锁产生的直接原因非常简单,即两个线E在怺{待Ҏ所执有的锁。锁序死锁是其中最l典的场景,此外q有动态的锁顺序死锁。虽然表现Ş式有所不同Q但本质上都是两个线E在以不同的序来获取相同锁Ӟ发生了死锁问题?br />使用thread dump可以帮助我们分析死锁产生的原因。除了直接用jstack命o来获取thread dump输出以外QJDKq提供了jvisualvm工具Q它能以可视化的方式展示JavaE序的进E号q导出thread dump?/div>

John Jiang 2013-12-29 20:19 发表评论
]]>
Java Concurrent Animated(?http://www.dentisthealthcenter.com/jiangshachina/archive/2013/12/07/407310.htmlJohn JiangJohn JiangSat, 07 Dec 2013 09:45:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/12/07/407310.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/407310.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/12/07/407310.html#Feedback1http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/407310.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/407310.html
Java Concurrent Animated
    在最C期的Java Magazine中有一访谈,介绍了一个学习Javaq发~程的动d?a >Java Concurrent Animated。该应用以十分直观的方式展示了Javaq发工具包中的每一个重要组Ӟ降低了学习Javaq发~程的难度?2013.12.07最后更?

Java MagazineQ有多少人已l试用过了你的Java Concurrent Animated应用Q?/span>
GraziQ该应用是在2009q?月被引入的,从那时算P已经有了大约20000的下载量。但考虑到已有约一千万的Java开发者,q个下蝲量才只是开始。按国家区分Q下载最多的分别是美?23%)Q印?14)和中?7%)?/span>
    你可以下载一个可以执行的JAR文gQ然后仅需双击它就可以q行了。该应用是由菜单驱动的,或者也可以使用向上或向下键在不同的囑փ和动M间进行导航。它能运行在诸如WindowsQMacQLinux{等所有的q_上。它要求安装Java SE 6或更高的版本?/span>

Java MagazineQ对q个应用最典型的反馈是什么?
GraziQ大家告诉我q个工具很好用。许多h实Ҏ感到兴奋Q尤其是那些正试囑֐团队教授合适ƈ发技术的老师与领g。Java是最早在核心cd中引入ƈ发的语言之一。在当时Q这是一个很强大的特性,但我们很快就发现一个非怼U的程序员与会写出很糟p的q发代码。进行恰当的q发~程是一件困隄x不可能的事情Q但是如何h们能׃旉ȝ解一些现有的框架Q那么在q行q发~码时所产生潜在错误׃变得极少?/span>
    例如Q去看看Java内存模型。开发者经常忽视Java内存模型Q而像个幸的ȝ一样在~码Q那么他们的E序会不太正常,因ؓJava虚拟?JVM)和服务器可能无法利用到由Java内存模型所提供的优化。由于内核在速度与数量上都有了增长,厂商们期望能够高效地利用到这些内核,然而由于错误的q发理Q本来如期运行的E序却开始遇C一些零星的错误?/span>

Java MagazineQ你是说Q这个应用会以我们所虚构的方式去使开发者们能够更快且直观地掌握Javaq发的原理与实践Q?/span>
GraziQ那是达到这一目的一个有的途径。你知道的,Java Concurrent Animatedq不是一个Flash动画。它是一l可交互的JavaE序Q也卻I每个动画都是真地在用它所要演C的q发lg。在屏幕的右Ҏ一个展CZ码片断的面板Q由于动ȝq行Q它会动态地高亮昄及恢复正在执行的代码?/span>
    让你l你一个例子,q个例子发生在ReadWriteLockq个动画中。ReadWriteLock用于保数据的一致性。它允许不受数量限制的线E去获取读锁Qƈ能ƈ发地对这个锁q行操作。但是,写线E在获取q个锁之前只能等待所有的ȝE执行结束。一旦一个写U程获得了这个锁Q那么其它的ȝE或写线E将无法获取它?/span>
    假设一个写U程正在{待正在执行中的ȝE去释放q个读锁Q但H然一个新的读U程跑过来了。那么谁应该获得q个锁会比较好呢Q这个新的读U程应该跑到写线E前面去吗?毕竟Q如果其它的ȝE已l获得了q个锁,那么新来的读U程Z么要ȝ一个尚在等待中的写U程呢?而这实际上这正是Java 5所q的事儿。但某次我在Java 6上运行这个动LQ我注意到行为发生了改变。即Q随后而来的读U程在获取到q个锁之前可能要{待所有的写线E先释放锁?/span>
    我认个新的行为是一个BUGQ且向ƈ发专家Heinz Kabutz博士提及了此事。博士解释道Q这不是一个错误,而一个特性。如果允许新到的ȝE蟩到正处于{待中的写线E的前面去,q就存在产生U程饥饿条g的高风险。因为,存在一U很大的可能性,可能没有M写线E能获得q个锁,它们永q等待着。这是一个如何用动d警示依赖于JVMq行时版本的U程行ؓ的例子?/span>

Java MagazineQ以动画教程的Ş式来展示Ҏ|在Javaq发~程中有何与众不同吗Q?/span>
GraziQMiller定律教会我们Q我们的大脑在某一时刻能处理的思维的数量是有限的。hcd脑們֐于进行顺序的思维处理Q那么即便我们能够克服n体上的束~,q能够去正确地进行理解,在以后也很难q回臛_去重新构造前面的思维处理。可以肯定地是,如果另一个开发者在以后能深入对其进行研IӞ那么仍然非常难以从原有的思维成果中再ơ捕捉到认知轨迹。这L话,脆弱的代码就会很H然C能正常工作了?/span>
    通过使用框架Q我们不仅将q发~程委托l了创徏和维护该框架的聪明开发者们Q而且qؓ沟通设计时引入了一个词典。所以,我可以说Q?#8220;下面的代码会当作CyclicBarrierL?#8221;Q而h们会明白那是什么意思。通过为java.util.concurrent中的所有组仉引入一个可交互化的动画应用Q开发者们点着鼠标p很方便地他们所探究的功能进行可视化Q理解q些法变得真心单了?/span>

Java MagazineQ你当时正在研究某些直觉Q这些直觉可以帮助更方便地学习ƈ发编E。从开发者的反馈来看Q这些直觉看h是有效的?/span>
GraziQ是的。例如,我前面解释的ReadWriteLock基本功能。读者们可能理解了,也可能没有。现在让我们看看q个与其有关的动画,如图?所C?/span>

    l色U程是读U程Q最上面的白色线E?带着菱Ş头)是一个写U程Q它下面的白色线E是一个新的读U程Q该U程在获取锁之前必须要等待所有的ȝE与写线E执行完毕。如果你点击按钮q观看这些动画,会比通过览J冗的解释性文字去q行理解要简单得多了?/span>

Java MagazineQHeinz Kabutz评论道,Java被构建成能够一ơ性做许多事情Q而这正与q发完全相关。你的学习系l是如何提高E序员的技能,以便他们能降低ƈ发错误的风险?/span>
GraziQ经常地Q当我要努力克服一个ƈ发问题时Q我知道解决Ҏ存在于某个设计模式中,但是哪一个呢Q在开发者探M个正解x案时QJava Concurrent AnimatedZ们提供了一个所有方案的目录Q在Ȁ发出正确Ҏ的过E中Q它扮演着向导的角艌Ӏ?/span>

Java MagazineQ当你管理的团队正在使用Javaq发Qƈ且你和你的团队都x好地ȝ解Javaq发QJava Concurrent Animated有着它的出发炏V是什么导致你使用动画呢,能描qCq个q程吗?
GraziQ我的培训是针对投资部门的服务器端Java应用Q在那里Qƈ发是一个通常都会受到x的问题。交易员们要求gq要低,q样可以保他们在这个需要于一毫秒H口旉内捕捉交易机会的比赛中不会成为失败者。批量处理也要求快速完成,{等。所以我开始看到那些可怕的只写(write-only)lgQ这些组件Z在ƈ发编E挣扎着。我自己也n处其中?/span>
    某天下午Q我正坐在机场内Q将要前往芝加哥ؓ我的团队做一个关于ƈ发的讲演。我正对讲演的PPTq行最后的处理Q那l灯片着重演CZ每一个重要的lg。ؓ了引导我览java.util.concurrent中每个ƈ发组件的状态,我写了一些状态机Q它们展CZ一些供我参考用的简单文本消息。在之前的生涯中Q我曑֜一家互联网创业公司中开发交互式的游戏,所以我懂得许多与动ȝ关的知识。这使我惛_可以PPT替换成一l交互式的动d用,那会更ؓ直观?/span>
    在等飞机的过E中Q我写了一个初步的动画引擎Q然后在我的状态机中调用了q个引擎。到了第二天早晨Q我已经有一个可用的原型E序。多q来Q我一直致力于q个框架Qƈ且吸引了其他专家的徏议。我传递过一份早期版本给Brian GoetzQo人惊讶的是,他ؓ每个动画E序都给Z。我他的所有徏议到吸收C该框架中。在我的W一ơJavaOne讲演中,Kirk Pepperdine加入了进来。他为动d用在真正的PPT中加入描qͼ以便讲演者能C正在讨论的内宏V随后我加上那些描述Q这实非常有用--不只是对讲演者有用,对于l端用户也很有用。Heinz Kabutz也加入了那场讲演Qƈ修改某些动画Q以使它们更为直观?/span>
    在另一演中Q一个很有激情的软g咨询师Oliver Zeigermann指出Q很昄~少了针对ConcurrentHashMap的动甅R我问他是否有兴A献这个动画,随后他添加了那个很有价值的动画E序?/span>

Java MagazineQ你能带着我们q一遍Javaq发工具包中的类吗?q能否解释一下这些动ȝ序是如何使开发者们更易于深入理解这些类Q?/span>
GraziQ好的,但在没有动画E序的情况下实很难办到。让我们看看CyclicBarrierQ它有两个重要的状态,如图2和图3所C,它们展示了一个障和四个成员。在?中,我们可以看到有三个成员已l到了,所以它们被Ll箋前进。图3展示了,一旦第四个成员也到达了Q每个成员又可以向前C?/span>



    q就形象地诠释了障碍的概念,亦即Q在所有成员到N点之前Q每个成员必ȝ待。随着q发lg复杂度的增加--例如Fork/Join的动画,以及那些演示原生的wait和notify机制的动?-使用动画E序的好处就更不肖说了?/span>

Java MagazineQ谈谈在创徏q些动画E序的过E中所遇到的一些挑战?/span>
GraziQ有一些挑战。开始时Q线E被表示成箭头。对于多数ƈ发组Ӟq种表示法是有效的。后来我们必L供一个可视化ҎQ不仅要表示U程Q还要表CBlockingQueue中的对象。所以,我不得不引入一个称之ؓ"_cd(sprite-type)"的概念,然后我们有了一个箭头型的精늱型和一个新?对象"型的_cd。后来,ConcurrentHashMap和AtomicInteger又需要新的精늱型,因ؓ我们试图要对他们的计与交换行ؓq行可视化?/span>
    后面又来了Fork/JoinQ新的挑战就是如何去表现那些完全不同于现有框架所表现的可视化部g。还有一个挑战,即Fork/Join动画需要解决一个实际的问题Q但q个动画应该解决一个什么样的问题呢Q?/span>
    开始时Q我让这个动ȝ序去求Fibonacci数列Q但却行不通。我在这个问题上U结了两天时_直到我认识到Fibonacci数列(Fn+1=Fn+Fn-1)无法高效地ƈ行化Q因为每个值都依赖于它前面的倹{所以,无论你如何试囑֯其实施ƈ行化Q它天生是一个顺序化的计。所以我换成了另一个问?-查找数组中的最大元素,q样好了。在q个动画中,你可以很_地看到如何用一个随机的数列去解册个问?如图4所C??/span>


Java MagazineQ你都在哪里讲演q这些动ȝ序?
GraziQ在JavaOne中讲演过几次Q在其它的许多会议,如奥斯陆中的JavaZoneQ苏黎世的JazoonQ纽U的QConQ以及许多SIG(特别兴趣l?和JUG(Java用户l?中也都讲演过。我喜欢讲演Q我也喜Ƣ周怸界,而Java Concurrent Animated为我提供了一个极好的Zdq两件事情。它总能获得极高的评仗?/span>
    Java Concurrent Animated的讲演提供了一U意识,q且它们也向出席的开发者们展示了下载这一框架的h|而且它还展示了,如果你拥有了框架和灵感启q,学习q发~程会是多么的容易?/span>


John Jiang 2013-12-07 17:45 发表评论
]]>
Javaq发基础实践--分而治??http://www.dentisthealthcenter.com/jiangshachina/archive/2013/10/23/405577.htmlJohn JiangJohn JiangWed, 23 Oct 2013 15:27:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/10/23/405577.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/405577.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/10/23/405577.html#Feedback0http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/405577.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/405577.html阅读全文

John Jiang 2013-10-23 23:27 发表评论
]]>
Javaq发基础实践--退ZQ务II(?http://www.dentisthealthcenter.com/jiangshachina/archive/2013/10/07/404690.htmlJohn JiangJohn JiangMon, 07 Oct 2013 08:55:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/10/07/404690.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/404690.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/10/07/404690.html#Feedback3http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/404690.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/404690.html
Javaq发基础实践--退ZQ务II
?a href="http://www.dentisthealthcenter.com/jiangshachina/category/53896.html">本系?/a>?a href="http://www.dentisthealthcenter.com/jiangshachina/archive/2013/09/21/404269.html">上一?/a>中所q的退出ƈ发Q务的方式都是ZJDK 5之前的APIQ本文将介绍使用由JDK 5引入的ƈ发工具包中的API来退ZQ务?2013.10.08最后更?

    在本pd的前一中讲述了三U退出ƈ发Q务的方式--停止U程Q可取消的Q务;中断Q但都是ZJDK 5之前的API。本将介绍由JDK 5引入的java.concurrent包中的Future来取消Q务的执行?br />
1. Future模式
    Future是ƈ发编E中的一U常见设计模式,它相当于是Proxy模式与Thread-Per-Message模式的结合。即Q每ơ都创徏一个单独的U程L行一个耗时的Q务,q且创徏一个Future对象L有实际的d对象Q在来需要的时候再去获取实际Q务的执行l果?br />依然先创Z个用于扫描文件的dFileScannerTaskQ如代码清单1所C,
清单1
public class FileScannerTask implements Runnable {

    
private File root = null;

    
private ArrayList<String> filePaths = new ArrayList<String>();

    
public FileScannerTask(File root) {
        
if (root == null || !root.exists() || !root.isDirectory()) {
            
throw new IllegalArgumentException("root must be directory");
        }

        
this.root = root;
    }

    @Override
    
public void run() {
        travleFiles(root);
    }

    
private void travleFiles(File parent) {
        String filePath 
= parent.getAbsolutePath();
        filePaths.add(filePath);

        
if (parent.isDirectory()) {
            File[] children 
= parent.listFiles();
            
if (children != null) {
                
for (File child : children) {
                    travleFiles(child);
                }
            }
        }
    }

    
public List<String> getFilePaths() {
        
return (List<String>) filePaths.clone();
    }
}
此处的文件扫描Q务,提供了一个getFilePaths()Ҏ以允讔R旉可以取出当前已扫描过的文件的路径(相当于一个Q务快?。然后,创徏一个针对该d的Futurec,如代码清?所C,
清单2
public class FileScannerFuture {

    
private FileScannerTask task = null;

    
public FileScannerFuture(FileScannerTask task) {
        
new Thread(task).start();
        
this.task = task;
    }

    
public List<String> getResult() {
        
return task.getFilePaths();
    }
}
FileScannerFuture持有FileScannerTask的引用,q创Z个独立的U程来执行该d。在d的执行过E中Q应用程序可以在"未来"的某个时d获取一个Q务的快照Q如代码清单3所C,
清单3
public static void main(String[] args) throws Exception {
    FileScannerFuture future 
= new FileScannerFuture(new FileScannerTask(new File("C:")));

    TimeUnit.SECONDS.sleep(
1);
    List
<String> filePaths1 = future.getResult();
    System.out.println(filePaths1.size());

    TimeUnit.SECONDS.sleep(
1);
    List
<String> filePaths2 = future.getResult();
    System.out.println(filePaths2.size());
}

2. 使用q发工具包中的Future实现
    前面所展示的Future实现十分的简陋,没有实际应用的意义。用FileScannerFutureQ应用程序在获取filePathsӞ无法得知其获取的是否为最l结果,x法判断FileScannerTask是否已经完成。而且Q也不能在必要时停止FileScannerTask的执行。毫无疑问,由JDK 5引入的ƈ发工具包肯定会提供此cd用工P如FutureTask。ؓ了用ƈ发工具包中的FutureQ需要修改前q的FileScannerTask实现Q让其实现Callable接口Q如代码清单4所C,
清单4
public class FileScannerTask implements Callable<List<String>> {

    
private File root = null;

    
private List<String> filePaths = new ArrayList<String>();

    
public FileScannerTask(File root) {
        
if (root == null || !root.exists() || !root.isDirectory()) {
            
throw new IllegalArgumentException("root must be directory");
        }

        
this.root = root;
    }

    @Override
    
public List<String> call() {
        travleFiles(root);
        
return filePaths;
    }

    
private void travleFiles(File parent) {
        String filePath 
= parent.getAbsolutePath();
        filePaths.add(filePath);

        
if (parent.isDirectory()) {
            File[] children 
= parent.listFiles();
            
if (children != null) {
                
for (File child : children) {
                    travleFiles(child);
                }
            }
        }
    }

    
public List<String> getFilePaths() {
        
return (List<String>) filePaths.clone();
    }
}
应用E序也要相应的修Ҏ如代码清?所C,使用ExecutorService来提交Q务,q创Z个Future/FutureTask实例?br />
清单5
public static void main(String[] args) {
    ExecutorService executorService 
= Executors.newCachedThreadPool();
    Future
<List<String>> future = executorService.submit(new FileScannerTask(new File("C:")));

    
try {
        List
<String> filePaths = future.get();
        System.out.println(filePaths.size());
    } 
catch (InterruptedException e) {
        e.printStackTrace();
    } 
catch (ExecutionException e) {
        e.printStackTrace();
    }

    executorService.shutdown();
}
此处是调用Future.get()Ҏ来获取Q务的执行l果Q如果Q务没有执行完毕,那么该方法将会被d。该Future实现的好处就是,正常情况下,只有在Q务执行完毕之后才能获取其l果Q以保证该结果是最l执行结果?br />
3. 使用Future取消d
    Future除了定义有可获取执行l果的getҎ(get()以及get(long timeout, TimeUnit unit))Q还定义了三个方法:cancel()QisCancelled()以及isDone()Q用于取消Q务,以及判定d是否已被取消、已执行完毕。如代码清单6所C,
清单6
public interface Future<V> {

    
boolean cancel(boolean mayInterruptIfRunning);
    
boolean isCancelled();
    
boolean isDone();
    
}
其中Qcancel()Ҏ中的boolean参数若ؓtrueQ表C在取消该Q务时Q若执行该Q务的U程仍在q行中,则对其进行中断。如代码清单7所C,若Q务执行超时了Q那么就取消它?br />
清单7
public static void main(String[] args) {
    ExecutorService executorService 
= Executors.newCachedThreadPool();
    Future
<List<String>> future = executorService.submit(new FileScannerTask(new File("C:")));

    
try {
        List
<String> filePaths = future.get(1, TimeUnit.SECONDS);
        System.out.println(filePaths.size());
    } 
catch (InterruptedException e) {
        e.printStackTrace();
    } 
catch (ExecutionException e) {
        e.printStackTrace();
    } 
catch (TimeoutException e) {
        e.printStackTrace();
    } 
finally {
        future.cancel(
true);
    }

    executorService.shutdown();
}
在实际应用中Q取消Q务的原由肯定不仅仅只是超时这么简单,q可能是׃接受C用户的指令。此Ӟ则可能会从另一个独立线E去取消该Q务。除了取消Q务之外,有时q需要取ZQ务中已经生成的部分结果。但Z能够响应d的退出,首先需要修改FileScannerTaskQ得当d被取?中断)Ӟd能够真正的快速停止ƈq回Q如代码清单8所C,
清单8
public class FileScannerTask implements Callable<List<String>> {

    

    
private void travleFiles(File parent) {
        
if (Thread.currentThread().isInterrupted()) {
            
return;
        }

        String filePath 
= parent.getAbsolutePath();
        filePaths.add(filePath);

        
if (parent.isDirectory()) {
            File[] children 
= parent.listFiles();
            
if (children != null) {
                
for (File child : children) {
                    travleFiles(child);
                }
            }
        }
    }

    
}
相应C改应用程序的代码Q如代码清单9所C,
清单9
public static void main(String[] args) {
    ExecutorService executorService 
= Executors.newCachedThreadPool();
    FileScannerTask task 
= new FileScannerTask(new File("C:"));
    
final Future<List<String>> future = executorService.submit(task);
    
    
new Thread(new Runnable() {
        
        @Override
        
public void run() {
            
try {
                TimeUnit.SECONDS.sleep(
1);
            } 
catch (InterruptedException e) {
                e.printStackTrace();
            }
            future.cancel(
true);
        }
    }).start();
    
    
try {
        List
<String> filePaths = future.get();
        System.out.println(filePaths.size());
    } 
catch (InterruptedException e) {
        e.printStackTrace();
    } 
catch (ExecutionException e) {
        e.printStackTrace();
    } 
catch (CancellationException e) {
        List
<String> filePaths = task.getFilePaths();
        System.out.println(
"Partly result: " + filePaths.size());
    }
    
    executorService.shutdown();
}
׃可知Q此处用Future.cancel(true)的本质依然是利用了线E的中断机制?br />
4. 结
    使用Future可以在Q务启动之后的特定时机再去获取d的执行结果。由JDK 5引入的ƈ发工具包中提供的Future实现不仅可以获取d的执行结果,q可以用于取消Q务的执行?/div>

John Jiang 2013-10-07 16:55 发表评论
]]>
Javaq发基础实践--退ZQ务I(?http://www.dentisthealthcenter.com/jiangshachina/archive/2013/09/21/404269.htmlJohn JiangJohn JiangSat, 21 Sep 2013 11:11:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/09/21/404269.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/404269.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/09/21/404269.html#Feedback0http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/404269.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/404269.html
Javaq发基础实践--退ZQ务I
计划写一?Javaq发基础实践"pdQ算作本人对Javaq发学习与实늚单ȝ。本文是该系列的W一,介绍了退出ƈ发Q务的最单方法?2013.09.25最后更?

在一个ƈ发Q务被启动之后Q不要期望它L会执行完成。由于时间限Ӟ资源限制Q用h作,甚至是Q务中的异?其是运行时异常)Q?..都可能造成d不能执行完成。如何恰当地退ZQ务是一个很常见的问题,而且实现Ҏ也不一而?/span>

1. d
创徏一个ƈ发Q务,递归地获取指定目录下的所有子目录与文件的l对路径Q最后再这些\径信息保存到一个文件中Q如代码清单1所C:
清单1
public class FileScanner implements Runnable {

    
private File root = null;

    
private List<String> filePaths = new ArrayList<String>();

    
public FileScanner1(File root) {
        
if (root == null || !root.exists() || !root.isDirectory()) {
            
throw new IllegalArgumentException("root must be legal directory");
        }

        
this.root = root;
    }

    @Override
    
public void run() {
        travleFiles(root);
        
try {
            saveFilePaths();
        } 
catch (Exception e) {
            e.printStackTrace();
        }
    }

    
private void travleFiles(File parent) {
        String filePath 
= parent.getAbsolutePath();
        filePaths.add(filePath);

        
if (parent.isDirectory()) {
            File[] children 
= parent.listFiles();
            
for (File child : children) {
                travleFiles(child);
            }
        }
    }

    
private void saveFilePaths() throws IOException {
        FileWriter fos 
= new FileWriter(new File(root.getAbsoluteFile()
                
+ File.separator + "filePaths.out"));
        
for (String filePath : filePaths) {
            fos.write(filePath 
+ "\n");
        }
        fos.close();
    }
}

2. 停止U程
有一个很直接Q也很干脆的方式来停止线E,是调用Thread.stop()ҎQ如代码清单2所C:
清单2
public static void main(String[] args) throws Exception {
    FileScanner task 
= new FileScanner(new File("C:"));
    Thread taskThread 
= new Thread(task);
    taskThread.start();

    TimeUnit.SECONDS.sleep(
1);
    taskThread.stop();
}
但是Q地球h都知道Thread.stop()在很久很久之前就不推荐用了。根?a >官方文的介l,该方法存在着固有的不安全性。当停止U程Ӟ会释放该线E所占有的全部监视锁Q这׃造成受这些锁保护的对象的不一致性。在执行清单2的应用程序时Q它的运行结果是不确定的。它可能会输Z个文Ӟ其中包含部分的被扫描q的目录和文件。但它也很有可能什么也不输出,因ؓ在执行FileWriter.write()的过E中Q可能由于线E停止而造成了I/O异常Q得最l无法得到输出文件?/span>

3. 可取消的d
另外一U十分常见的途径是,在设计之初,我们׃d是可被取消的。一般地Q就是提供一个取消标志或讑֮一个取消条Ӟ一旦Q务遇到该标志或满了取消条gQ就会结束Q务的执行。如代码清单3所C:
清单3
public class FileScanner implements Runnable {

    
private File root = null;

    
private List<String> filePaths = new ArrayList<String>();

    
private boolean cancel = false;

    
public FileScanner(File root) {
        
    }

    @Override
    
public void run() {
        
    }

    
private void travleFiles(File parent) {
        
if (cancel) {
            
return;
        }

        String filePath 
= parent.getAbsolutePath();
        filePaths.add(filePath);

        
if (parent.isDirectory()) {
            File[] children 
= parent.listFiles();
            
for (File child : children) {
                travleFiles(child);
            }
        }
    }

    
private void saveFilePaths() throws IOException {
        
    }

    
public void cancel() {
        cancel 
= true;
    }
}
新的FileScanner实现提供一个cancel标志QtravleFiles()会遍历新的文件之前检该标志Q若该标志ؓtrueQ则会立卌回。代码清?是用新d的应用程序?/span>
清单4
public static void main(String[] args) throws Exception {
    FileScanner task 
= new FileScanner(new File("C:"));
    Thread taskThread 
= new Thread(task);
    taskThread.start();

    TimeUnit.SECONDS.sleep(
3);
    task.cancel();
}
但有些时候用可取消的Q务,q不能快速地退ZQ务。因ZQ务在取消标志之前,可能正处于等待状态,甚至可能被阻塞着。对清单2中的FileScannerE作修改Q让每次讉K新的文g之前先睡?0U钟Q如代码清单5所C:
清单5
public class FileScanner implements Runnable {

    

    
private void travleFiles(File parent) {
        
try {
            TimeUnit.SECONDS.sleep(
10);
        } 
catch (InterruptedException e) {
            e.printStackTrace();
        }

        
if (cancel) {
            
return;
        }

        
    }

    
private void saveFilePaths() throws IOException {
        
    }

    
public void cancel() {
        cancel 
= true;
    }
}
再执行清?中的应用E序Ӟ可能发现dq没有很快速的退出,而是又等待了大约7U钟才退出。如果在查cancel标志之前要先获取某个受锁保护的资源,那么该Q务就会被dQƈ且无法确定何时能够退出。对于这U情况,需要用中断了?/span>

4. 中断
中断是一U协作机Ӟ它ƈ不会真正地停止一个线E,而只是提醒线E需要被中断Qƈ线E的中断状态设|ؓtrue。如果线E正在执行一些可抛出InterruptedException的方法,如Thread.sleep()QThread.join()和Object.wait()Q那么当U程被中断时Q上q方法就会抛出InterruptedExceptionQƈ且中断状态会被重新设|ؓfalse。Q务程序只要恰当处理该异常Q就可以正常地退ZQ务。对清单5再稍作修改,卻I如果d在睡眠时遇上了InterruptedExceptionQ那么就取消d。如代码清单6所C:
清单6
public class FileScanner implements Runnable {

    

    
private void travleFiles(File parent) {
        
try {
            TimeUnit.SECONDS.sleep(
10);
        } 
catch (InterruptedException e) {
            cancel();
        }

        
if (cancel) {
            
return;
        }

        
    }

    
}
同时清?中的应用E序Q此时将调用Thread.interrupt()ҎM断线E,如代码清?所C:
清单7
public static void main(String[] args) throws Exception {
    FileScanner3 task 
= new FileScanner3(new File("C:"));
    Thread taskThread 
= new Thread(task);
    taskThread.start();

    TimeUnit.SECONDS.sleep(
3);
    taskThread.interrupt();
}
或者更q一步,仅用中断状态来控制E序的退出,而不再用可取消的Q?卻I删除cancel标志)Q将清单6中的FileScanner修改成如下:
清单8
public class FileScanner implements Runnable {

    

    
private void travleFiles(File parent) {
        
try {
            TimeUnit.SECONDS.sleep(
10);
        } 
catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        
if (Thread.currentThread().isInterrupted()) {
            
return;
        }

        
    }

    
}
再次执行清单7的应用程序后Q新的FileScanner也能x的退Z。值得注意的是Q因为当sleep()Ҏ抛出InterruptedExceptionӞ该线E的中断状态将又会被设|ؓfalseQ所以必要再次调用interrupt()Ҏ来保存中断状态,q样在后面才可以利用中断状态来判定是否需要返回travleFiles()Ҏ。当Ӟ对于此处的例子,在收到InterruptedException时也可以选择直接q回Q如代码清单9所C:
清单9
public class FileScanner implements Runnable {

    

    
private void travleFiles(File parent) {
        
try {
            TimeUnit.SECONDS.sleep(
10);
        } 
catch (InterruptedException e) {
            
return;
        }

        
    }

    
}

5 结
本文介绍了三U简单的退出ƈ发Q务的ҎQ停止线E;使用可取消Q务;使用中断。毫无疑问,停止U程是不可取的。用可取消的Q务时Q要避免d׃被阻塞而无法及Ӟ甚至永远无法被取消。一般地Q恰当地使用中断是取消Q务的首选方式?/span>


John Jiang 2013-09-21 19:11 发表评论
]]>
Z喜欢在ThoughtWorks工作(?http://www.dentisthealthcenter.com/jiangshachina/archive/2013/08/22/403156.htmlJohn JiangJohn JiangThu, 22 Aug 2013 06:43:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/08/22/403156.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/403156.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/08/22/403156.html#Feedback3http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/403156.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/403156.html
Z喜欢在ThoughtWorks工作
本文是ThoughtWorks首席U学家Martin Fowler接受InformIT采访时的访谈?/a>Q谈C在ThoughtWorks工作的情况,对ThoughWorks感兴的朋友们可以看看?2013.08.23最后更?

Martin Fowler分n了他关于ThoughtWorks的看法,q是一家关注可持箋性以及经和C会公正的Y件开发公司。Martin谈到了他是如何开始了在ThoughtWorks的工作,他ؓ什么会喜欢q家公司的文化,以及Ҏ的Y件开发者的?/span>

InformITQ?/strong>你是怎样得到?a >ThoughtWorks
的工作?
Martin FowlerQ?/strong>围绕一个当时他们正从事的项目的域模型,他们让我做些咨询工作。我们进展的不错Q最后我p然而然地加入进来了。在此期_他们大步地{向极限编E这一软g开发风根{约九个月后Q他们向我提供了一份offer。由于他们是我最喜欢的客P我就军_加入他们?/span>

InformITQ?/strong>你在那儿工作多长旉了?
Martin FowlerQ?/strong>12q?/span>

InformITQ?/strong>ThoughtWorks与其它公司有何不同?
Martin FowlerQ?/strong>Ҏ上,q种不同要归lؓ人。他们善于雇佣既聪明又乐于合作的人。特别是Q这里会更多C正直的态度d注h。我发现Q相比于q去多年中共事过的大多数客户Q我更加信Q我的同事。还有对完成高质量工作ƈ期望做到更好的真挚热情,对于像我q样的作者,q是极好的素材?/span>

InformITQ?/strong>对于在那儿工作,你最喜欢的是什么?
Martin FowlerQ?/strong>ThoughtWorks有许多东西可?a >d。只是困难之处在于,我要挑哪块儿去讲q呢?/span>

InformITQ?/strong>工作在ThoughtWorksQ你最自豪的是什么?
Martin FowlerQ?/strong>我最自豪的是Q我们从一家几百h的美国公司成长ؓ在世界范围内拥有两千人的公司Q而且原封不动C持了公司文化中的_N。我不确定我在其中扮演了什么角Ԍ但这里一直是我喜Ƣ工作的地方Qƈ且公怸直在宣传我,使大家能有兴与ThoughtWorks协作Q我Ҏ感到高兴?/span>
管如此Q我仍不能肯定这U情冉|否要大大地归功于我。在与我本h更明相关的工作中,我必d_我很高兴在过dq中建立?a >martinfowler.com。ؓ了关注今后在改进q个站点时所做的事情Q该站点已成Z个丰富的资源和永恒的q题?/span>
在更需要协作的工作前沿Q我实很高兴地看到我的一些同事已l成Z内D重的"大嘴"。我不认为我在这其中有很大的作用--我给予的M帮助能够L地超q他们自w的努力--但这实是我最惛_的A献?/span>

InformITQ?/strong>Z么会有h想着去ThoughtWorks工作Q?/span>
Martin FowlerQ?/strong>对于l验的人,我想最大的吸引力是Q能在许多不同类型的目中学会做好Y件开发的能力。当ӞThoughtWorks的项目ƈ非完,但我认ؓ它们比绝大多数Y仉目要好得多。我听过很多ThoughtWorks前员工们谈到他们在公司工作的岁月中对于Y件开发学C许多?/span>
而在所有的原因当中Q旅行机会也是很重要的。如果你惌上一大段旉在世界上不同的地行工作,例如在美国h在印度工作,或巴西h在中国工作,那么ThoughtWorks提供了大量的此类Z。这也与我们日益兛_C会正义的问题有养I我认为对于许多有l验的h来说q也是一个重要的因素?/span>

InformITQ?/strong>对于一个刚刚开始在ThoughtWorks工作的新员工Q你有什么徏议吗Q?/span>
Martin FowlerQ?/strong>对于在ThoughtWorks工作的h们,最沮的事情之一是我们不做职业规划Q这׃很容易危险地在项目间漂来漂去。对于有些h来说q不是问题,但如果你惌定一个职业方向,那么你必d自己d。这意味着大量的交际,L机遇QƈU极推进。这不是一U直接的途径Q但在我成ؓ独立咨询师之前,没有q种促进我前行的职业计划Q就会有相反的效果?/span>

InformITQ?/strong>告诉我们一?只会发生在ThoughtWorks"的故事?/span>
Martin FowlerQ?/span>我记得有ơ被拉去讨论一个和BigCo相关的很有前景的目。这W交易确实很大,在初始阶D就要耗费U?0+?q。但存在着一些对BigCo在道徯录方面的担忧Q尤其是在发展中国家。在q一讨论中,Ua的ThoughtWorks要义是,CFOhȀ昂地反对接下q笔高利润额的工作,而所有的高领导们则們֐着一位来自南方国家最q才受雇于ThoughtWorks的初U开发者,他描qCBigCo的行径是怎样损害着他的国家?/span>


John Jiang 2013-08-22 14:43 发表评论
]]>
Oracle OpenWorld 2013(上v)(?http://www.dentisthealthcenter.com/jiangshachina/archive/2013/08/02/402274.htmlJohn JiangJohn JiangFri, 02 Aug 2013 03:50:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/08/02/402274.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/402274.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/08/02/402274.html#Feedback0http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/402274.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/402274.html
Oracle OpenWorld 2013(上v)
Oracle OpenWorld 2013(上v)已经l束Q我全程参与了此ơ技术大会。本文是我参会的所见、所闅R所思与所感,有文Q有图,且无码?2013.08.03最后更?

Oracle OpenWorld 2013(Shanghai) lasted four days, and included over 300 keynotes, sessions and hands-on labs. 18000+ attendees, including company leaders, industries experts and developers, joined the event, which was said the biggest IT activity in Asia-Pacific area as never before.

Sessions
* Java strategy/technical keynote
Every JavaOne conference must has such keynote, which introduces and demonstrates the latest new JDK versions and features, and prospects upcoming JDK version and features. All parts of Java ecosystem, including JME, JSE, JEE and JavaFX, are involved in the keynote. I was deeply impressed by JavaFX demo by Jim Weaver. The Java champion played musical instruments with a JavaFX application; that's really cool. In this JavaOne, JavaFX has a separated track, and the technology is very highlighted by Oracle. The software giant expects JavaFX to enhance Java's performance on rich application. But outspokenly, it's difficult to take end users to install a JRE beforehand if they just want to run a desktop application.


* Why should I switch to Java SE 7?
JDK 6 has rested in peace(exactly, the version isn't completely frozen), and JDK 7 has been released for two years, and even JDK 8 will come next March. But do you migrate your JRE to Java 7? Strings in switch statements, diamond operator, try with resources, multi-catch statement, ... Do you like the compiler sugar? Anyway, the features could improve development productivity. But fork-join framework really touches my heart. Because of complete backward compatibility, you just need to run your application in the new platform, and the performance could be accelerated.

* The road to Lambda
Java has been being criticised for implementing modern language features, like closure. The argument around closure implementation has lasted for a long time. Finally, Lambda, the closure for Java, has been finished, and will be distributed with JDK 8. Lambda is regarded as the biggest change since Generics in Java 5. Function Programming (FP) is popular, and Java developers can utilise Lambda expression to apply FP. And JDK 8 extends Collection framework to take advantage of Lambda as powerful as possible. For example, currently we need an iterator or loop to iterate a Collection container, that's so-called external iteration. JDK 8 adds a new method forEach() for Collection interface, and we can iterate a Collection object via internal iteration, that means it's no need to care the iteration details by developers. In fact, it's impossible to debug the iterating process. And the process may apply fork-join if possible to improve performance. That sounds cool!

* Effective Scala
Dynamic programming languages are emerging, and more languages, such as Groovy, Scala and Clojure, can run on JVM, and JDK 7 introduced a new instruction "invokeDynamic" to support dynamic languages better. Therefore, I have to pay some attention to other JVM languages. The session "Effective Scala" was provided by TypeSafe, a company founded by Scala creators; Spring founder Rod Johnson joined the company as member of board of directors last year. The session simulated the style of Effective Java, and also introduced some best practices and coding regulations. Frankly, I don't know Scala syntax, like traits, totally, so I don't understand the lecture exactly. But I still obtained something new, like Cake pattern. Do you know the design pattern? I never hear it before this session, but the pattern is well-known in Scala world. It's time to learn another language, and I think Groovy is a good start-up due to it is closer to Java.

* Taobao GCIH

Taobao creates GCIH (GC Invisible Heap) based on Oracle VM Hotspot, and allows different VM to access a shared heap. In Taobao business scenario, the shared heap only stores read-only objects. All of the objects are initialized after the application is launched, and not be cleaned before the application shutdowns. Therefore, single VM can consume less memory, that means we can deploy more VMs in single machine. And GCIH cannot be accessed by GC so that reducing the garbage collection overhead.

* What do you mean, backwards compatibility?
10gen, the company behind MongoDB, presented the topic. Because old MongoDB driver isn't well designed, so it's difficult to understand and use the driver API. For example, the driver has a lot of find() methods, but how do I know which one is my want. According to the session, method chaining not only makes codes more readable, and the idiom benefits the backward compatibility as well. Generally, method chaining only handle one parameter at each invoking, and application codes don't care the sequence of invoking. In this case, it's no need to provide the methods with several arguments, and the methods are possible to be deprecated in later time due to their ambiguity. Of course, method chaining isn't designed for backward compatibility. And applying backward compatibility is still a hard job, because you don't know what would happen in the future. In fact, many and many APIs are deprecated in later JDK versions.

Appreciation Party
In the third evening, Oracle catered a small but exciting party to every attendee. Beer, snacks, professional band, beautiful girls, hot performances, ... were on the stage. All of performances were shown by Oracle employees, and we just rock!



I was on the scene
In OTN lounge, I communicated with Jim Weaver and his assistant about JavaFX and UI testing. In this event, my small dream, taking a photo with Java mascot -- Duke, came true ^_^ Additionally, don't forget iron man :-)


Event Organization
This is my sixth SunTechDays/OOW, and I must say that the conference was organized the best this time. Professional agenda, considerate attendee service, delicious dessert and drinking, all of them, except for lunch, must be delighted.



Finally, I must say the event is well worth participating; join it, and enjoy it :-)


John Jiang 2013-08-02 11:50 发表评论
]]>
世界一直在??http://www.dentisthealthcenter.com/jiangshachina/archive/2013/07/14/401546.htmlJohn JiangJohn JiangSun, 14 Jul 2013 03:15:00 GMThttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/07/14/401546.htmlhttp://www.dentisthealthcenter.com/jiangshachina/comments/401546.htmlhttp://www.dentisthealthcenter.com/jiangshachina/archive/2013/07/14/401546.html#Feedback3http://www.dentisthealthcenter.com/jiangshachina/comments/commentRss/401546.htmlhttp://www.dentisthealthcenter.com/jiangshachina/services/trackbacks/401546.html
世界一直在?/span>
    最q有点儿Ԍ看了一些文章,有了一些感触,写成一小文。Y件世界真的变化很大,没有永恒的终l者。今天的l结者,明天可能׃被别人终l。道理大安懂,但现实依然很D酷?2013.07.29最后更?

    This week, I read some articles about some API and tools that developers, especially Java guys, must know. Fortunately, I really know some of them, but unfortunately, I really miss something.

    Please let me introduce some cases at first:
    1. In our real projects, we only use JDK 6, but the version had been in End-Of-Life; we never touch JDK 7, but JDK 8 is upcoming. I don't know how much time we would spend on accepting Lambda expression. In fact, at present, a lot of Java developers cannot understand Generics exactly, however the syntax has been introduced for more than 8 years. Of course, Java Generics is a bit ambiguous, so it may be difficult to understand.
    2. Ant was ever the standard for building, and it still being used by many projects, even new ones. Maven was designed to terminate Ant due to the older cannot make life easy. Some conceptions of Maven, such as build life cycle, dependency management, default directory structure, are very advanced. But Maven dependency and transitive dependency management is nightmare, you have to include/exclude this or that. And extending Maven is also a hard job. I have real experience on both of them, I even wrote some popular preliminary blogs about Maven several years ago. But what I really don't know? I don't know Maven is becoming legacy, and worse, a new super star Gradle is on stage. Outspokenly, I never hear of the artifact before this week :-( Outstanding Spring framework is a very case about the trend. At beginning, obviously Spring is built by Ant, then the framework switched to Maven some years ago, but last year Spring migrated to Gradle.
    3. Google-Collections was well-known if you used it or not, and I know Guava however I never use the API. But what I really don't know? I don't know google-collections was closed several years ago, and even it was combined by Guava, which is a new rock star in Java ecosystem.

  OH, something is born, and then grows, and then rests in peace. That's nature, and we have to face it, but why I don't know? Exactly, I have no idea.
  World has been changing, and is changing faster as never before. How to keep us up-to-date with new fashion? I think the question may be asked by every "old" developer. After a long term career life, some of us may become veteran, but absolutely, it's impossible that everyone become expert, particularly the expert in underlying fields. We just be proficient in some programming languages, frameworks, APIs, or tools. So we must update our brains continuously.
  Maybe the issue is one of the middle life crisis problems, good luck for you and me :-)

John Jiang 2013-07-14 11:15 发表评论
]]>
久久一级片
<noframes id="395jp"><noframes id="395jp"><video id="395jp"><video id="395jp"></video></video>
<i id="395jp"><font id="395jp"><delect id="395jp"></delect></font></i>
<nobr id="395jp"></nobr><noframes id="395jp"><noframes id="395jp"><dl id="395jp"></dl><video id="395jp"></video><noframes id="395jp"><dl id="395jp"></dl>
<video id="395jp"><video id="395jp"><dl id="395jp"></dl></video></video> <nobr id="395jp"><nobr id="395jp"><meter id="395jp"></meter></nobr></nobr>
<video id="395jp"></video><nobr id="395jp"></nobr>
<video id="395jp"></video>