<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>dennis_zane</title>
    <description>blog:   www.rubyeye.net
msn:    killme6115@126.com
email:  killme2008@gmail.com</description>
    <link>http://dennis-zane.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>Ruby写Servlet的小例子</title>
        <author>dennis_zane</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://dennis-zane.javaeye.com/blog/218517" style="color:red;">http://dennis-zane.javaeye.com/blog/218517</a>&nbsp;
          发表时间: 2008年07月23日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>
&nbsp;&nbsp;&nbsp; Ruby也能写servlet?是的，没开玩笑，而且挺方便的，因为Ruby的标准库就自带了一个webrick，webrick本身又有一个serlvet容器，随时随地启动一个web server，实在是很方便。<br />
&nbsp;&nbsp;&nbsp; 先看个最简单的例子，输出hello到浏览器：</p>
<pre name="code" class="ruby">require 'webrick'
require 'net/http'
include WEBrick

class HelloServlet &lt; HTTPServlet::AbstractServlet
  def hello(resp)
    resp[&quot;Content-Type&quot;]=&quot;text/html;charset=utf-8&quot;
    resp.body=&quot;hello,ruby servlet&quot;
  end
  private :hello
  def do_GET(req,resp)
    hello(resp)
  end
  def do_POST(req,resp)
    hello(resp)
  end
end
if $0==__FILE__
  server=HTTPServer.new(:Port=&gt;3000)
  server.mount(&quot;/hello&quot;,HelloServlet)
  trap(&quot;INT&quot;){ server.shutdown }
  server.start
end</pre>
&nbsp;
<p>
&nbsp;&nbsp;&nbsp; 是不是跟java很像？所有的serlvet都要继承自<span style="color: #000000;">HTTPServlet::AbstractServlet，并实现do_GET或者do_POST方法。在这行代码：<br />
</span>
</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">server=HTTPServer.new(:Port=&gt;3000)</pre>
<p><br />
&nbsp;&nbsp;&nbsp; 我们启动了一个HTTP Server，端口是3000，然后将<span style="color: #000000;">HelloServlet挂载到/hello这个路径上，因此，执行这个脚本后，可以通过</span>
<a href="http://localhost:3000/hello" target="_blank">http://localhost:3000/hello</a>调用HelloServlet，简单地只是显示字符串<span style="color: #800000;">&quot;</span>
<span style="color: #800000;">hello,ruby&nbsp;servlet</span>
<span style="color: #800000;">&quot;。<br />
&nbsp;&nbsp;&nbsp; </span>
这
个简单的例子没有任何交互，并且显示的html也是写死在脚本中，显然更好的方式应该通过模板来提供，可以使用Ruby标准库的erb模板。再给个有简单
交互的例子，现在要求用户输入姓名，然后提交给HelloServlet，显示&quot;hello,某某某&quot;。嗯，来个最简单的提交页面：</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">&lt;html&gt;
&lt;body&gt;
&lt;center&gt;
   &lt;form action=&quot;http://localhost:3000/hello&quot; method=&quot;post&quot;&gt;
     &lt;input type=&quot;text&quot; name=&quot;name&quot; size=10/&gt;&lt;br/&gt;&lt;br/&gt;
     &lt;input type=&quot;submit&quot; name=&quot;submit&quot; value=&quot;submit&quot;/&gt;
   &lt;/form&gt;
   &lt;/center&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
&nbsp;
<p><br />
&nbsp;&nbsp;&nbsp; 注意到，我们采用POST方法提交。再看看erb模板：</p>
<pre name="code" class="ruby">&lt;html&gt;
    &lt;head&gt;&lt;/head&gt;
    &lt;body&gt;
      hello,&lt;%=name%&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre>
&nbsp;
<p>&nbsp;&nbsp;&nbsp; 其中的name是我们将要绑定的变量，根据用户提交的参数。最后，修改下HelloServlet：</p>
<pre name="code" class="ruby">require 'webrick'
require 'net/http'
include WEBrick

class HelloServlet &lt; HTTPServlet::AbstractServlet
  def do_GET(req,resp)
    do_POST(req,resp)
  end
  def do_POST(req,resp)
    name=req.query[&quot;name&quot;]
    #读取模板文件
    template=IO.read(File.dirname(__FILE__)+&quot;/hello.html&quot;)
    message=ERB.new(template)
    resp[&quot;Content-Type&quot;]=&quot;text/html;charset=utf-8&quot;
    resp.body=message.result(binding)
  end
end
if $0==__FILE__
  server=HTTPServer.new(:Port=&gt;3000)
  server.mount(&quot;/hello&quot;,HelloServlet)
  trap(&quot;INT&quot;){ server.shutdown }
  server.start
end</pre>
&nbsp;
<p>
&nbsp;&nbsp;&nbsp; 与前一个例子相比，不同点有二，一是通过<span style="color: #000000;">req.query[</span>
<span style="color: #800000;">&quot;</span>
<span style="color: #800000;">name</span>
<span style="color: #800000;">&quot;</span>
<span style="color: #000000;">]获得用户提交的参数name，二是resp的body是由模板产生，而不是写死在代码中。</span>
在一些临时报表、临时数据的展示上，可以充分利用Ruby的这些标准库来快速实现。</p>
          <br/>
          <span style="color:red;">
            <a href="http://dennis-zane.javaeye.com/blog/218517#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 23 Jul 2008 12:05:38 +0800</pubDate>
        <link>http://dennis-zane.javaeye.com/blog/218517</link>
        <guid>http://dennis-zane.javaeye.com/blog/218517</guid>
      </item>
      <item>
        <title>工作的几个tip</title>
        <author>dennis_zane</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://dennis-zane.javaeye.com/blog/212483" style="color:red;">http://dennis-zane.javaeye.com/blog/212483</a>&nbsp;
          发表时间: 2008年07月07日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p> 1、如果用java6的ScriptEngineManager来调用JRuby，并且脚本使用到了Ruby的标准库（比如我用到了YAML库），如果没有正确设置，是会找不到标准库的。通过打印$:变量可以看到文件的加载路径，比如在某台机器上的打印的结果：<br />
&nbsp;&nbsp; 
puts $:<br />
&nbsp;&nbsp; 
输出：<br />
.<br />
/root/.jruby/lib/ruby/site_ruby/1.8<br />
/root/.jruby/lib/ruby/site_ruby<br />
/root/.jruby/lib/ruby/1.8<br />
/root/.jruby/lib/ruby/1.8/java<br />
lib/ruby/1.8<br />
......略<br />
<br />
显然，默认会到当前用户的主目录下寻找.jruby隐藏目录，将此目录作为Ruby的安装目录，因此，可以在~user/.jruby放置一个jruby实现，一劳永逸地解决问题，不然就要自己手工添加完整的路径到$:变量中。<br />
<br />
2、nio的临时selector的使用，了解grizzly的都知道，Grizzly框架有一个比较与众不同的地方在于使用临时selector注册channel进行读或者写。这个带来什么好处呢？一个是，通常我们可能将read派发到其他线程中去，如果一次没有读完，那么就得继续注册OP_READ到主selector上；注意，nio在一些平台上有个问题，就是SelectionKey.interestOps方法跟Selector.select方法会有并发冲突，产生奇怪的现象，因此，你会看到大多数的nio框架都会保证SelectionKey.interestOps跟Selector.select的调用在同一个线程；在没有读完继续注册这个场景下，免不了线程间的context 
switch，如果采用一个临时selector注册并读取，就可以避免这个切换开销。另外，对于write调用，通常你可能这样写：</p>
<pre name="code" class="java">while (byteBuffer.hasRemaining()) {
  int len = socketChannel.write(byteBuffer);
  if (len &lt; 0){
   throw new EOFException(); 
  }
}</pre>
&nbsp;
<p><br />
&nbsp;&nbsp; 
在负载比较高的时候，write返回0的次数会越来越多，while循环将空耗多次导致CPU占用偏高，这个问题在win32上比较严重，同样可以采用临时selector的解决（Cindy2.x是留在队列，等待下次写）。下例是采用临时Selector进行读的例子：</p>
<pre name="code" class="java"> Selector readSelector = SelectorFactory.getSelector();
                SelectionKey tmpKey = sc.register(readSelector,
                        SelectionKey.OP_READ);
                tmpKey.interestOps(tmpKey.interestOps() | SelectionKey.OP_READ);
                int code = readSelector.select(1000);
                tmpKey.interestOps(tmpKey.interestOps()
                        &amp; (~SelectionKey.OP_READ));
                if (code &gt; 0) {
                    do {
                        n = sc.read(in);
                    } while (n &gt; 0 &amp;&amp; in.hasRemaining());
                    in.flip();
                    decode();
                    in.compact();
                }
                SelectorFactory.returnSelector(readSelector);</pre>
&nbsp;
<p><!--   {cps..1}--><span style="color: #000000;">&nbsp;&nbsp;</span>
<span style="color: #000000;">&nbsp;&nbsp; </span>
这样的方式，某种意义上可以认为是non-blocking模式下的阻塞读，在网络条件稳定的情况下（比如内网），能带来比较高的效率。<br />
<br />
3、<a href="http://code.google.com/p/spymemcached/">spymemcached</a>
，是另一个memcached的java 
client实现，采用nio。最近遇到的问题是它跟原来的<a href="http://whalin.com/memcached/">MemcachedClient</a>
的兼容问题，用它去操作MemcachedClient存储的数据。spymemcached是通过Transcoder来实现序列化，Transcoder的WhalinTranscoder实现类兼容了Greg 
Whalin的MemcachedClient：</p>
<pre name="code" class="java">private Transcoder whalinTranscoder = new WhalinTranscoder();


Future&lt;Object&gt; f = memcachedClient.asyncGet(id, whalinTranscoder);</pre>
&nbsp;
<p><br />
&nbsp;&nbsp; 
各个方法都有重载的版本用以指定Transcoder。</p>
          <br/>
          <span style="color:red;">
            <a href="http://dennis-zane.javaeye.com/blog/212483#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 07 Jul 2008 20:47:58 +0800</pubDate>
        <link>http://dennis-zane.javaeye.com/blog/212483</link>
        <guid>http://dennis-zane.javaeye.com/blog/212483</guid>
      </item>
      <item>
        <title>值的纪念的一天</title>
        <author>dennis_zane</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://dennis-zane.javaeye.com/blog/209371" style="color:red;">http://dennis-zane.javaeye.com/blog/209371</a>&nbsp;
          发表时间: 2008年06月29日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp; &nbsp;&nbsp; 沸沸扬扬的华南虎案（更想说是闹剧）终于告一段落，在广大网民的火眼金睛下，假老虎终究没有变成真老虎，没有上演指&ldquo;画&rdquo;为&ldquo;虎&rdquo;的现代成语故事。尽管处理结果有那么点抓小放大的意思，但还是值的纪念的一个日子。</p>
<p>更正：我还是太乐观了，贵州某地发生的事情，让我无法平静，我能做些什么？</p>
          <br/>
          <span style="color:red;">
            <a href="http://dennis-zane.javaeye.com/blog/209371#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sun, 29 Jun 2008 16:08:41 +0800</pubDate>
        <link>http://dennis-zane.javaeye.com/blog/209371</link>
        <guid>http://dennis-zane.javaeye.com/blog/209371</guid>
      </item>
      <item>
        <title>关于加班</title>
        <author>dennis_zane</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://dennis-zane.javaeye.com/blog/206964" style="color:red;">http://dennis-zane.javaeye.com/blog/206964</a>&nbsp;
          发表时间: 2008年06月23日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp;&nbsp;&nbsp; 加班在国内的公司好像是司空见惯的事情，更司空见惯的是加班不给加班费。从业三年，加班次数也不少，不过最长的一次是连续加了一个月的班，天天10点多才
回家，比起某些同学深更半夜还在加班的差了些。在目前的公司，除了刚开始偶尔加班外，现在我基本就是下班就走人了，还没有因为我负责的模块延期导致的项目
的延期，相反，自信工作的效率还是比较高的，自然上班时间能搞定的事情，何必拖到下班后？<br />
&nbsp;&nbsp;&nbsp;
不排除有些人喜欢安静的环境，下班后，人少了，一个人敲代码感觉更好，效率更高。这其实颠倒了问题的本质，本质是公司没有为员工创造一个舒适的工作环
境，&ldquo;家具警察&rdquo;们是绝不乐意给你安排一个私人小空间的。问题是，代码不是生活的全部，如果天天这样晚上在公司加班，可以想见这人的生活该如何枯燥，而生
活枯燥的人很容易失去想象力和直觉，并且心理上难免会有点毛病。公司不应该鼓励加班，公司应该鼓励的是大家在上班期间高效地完成工作，然后尽快回家去&ldquo;生
活&rdquo;。其实吧，如果我们上班少看点新闻，少开点小差，这个效率绝对是不低的。<br />
&nbsp;&nbsp;&nbsp;
听闻有领导对我们部门不满，说我们部门总是下班后人都走光了：）这样的观点真是奇怪，难道加班就能显示这个部门很努力、很负责？况且，我看到很多加班的同
学更多是在玩游戏或者干自己的事情，公司领导们难道看不到这一点？或者他们只要看见有人在那，心里就比较舒服点。这其实很奇怪的，我认为经常加班的部门绝
对是有问题的，而且很大程度上是项目管理不当引起的。</p>
          <br/>
          <span style="color:red;">
            <a href="http://dennis-zane.javaeye.com/blog/206964#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 23 Jun 2008 01:29:06 +0800</pubDate>
        <link>http://dennis-zane.javaeye.com/blog/206964</link>
        <guid>http://dennis-zane.javaeye.com/blog/206964</guid>
      </item>
      <item>
        <title>TCP的TIME_WAIT状态</title>
        <author>dennis_zane</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://dennis-zane.javaeye.com/blog/206963" style="color:red;">http://dennis-zane.javaeye.com/blog/206963</a>&nbsp;
          发表时间: 2008年06月23日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp;&nbsp;&nbsp; 主动关闭的Socket端会进入TIME_WAIT状态，并且持续2MSL时间长度，MSL就是maximum segment
lifetime(最大分节生命期），这是一个IP数据包能在互联网上生存的最长时间，超过这个时间将在网络中消失。MSL在RFC
1122上建议是2分钟，而源自berkeley的TCP实现传统上使用30秒，因而，TIME_WAIT状态一般维持在1-4分钟。<br />
&nbsp;&nbsp;&nbsp; TIME_WAIT状态存在的理由：<br />
1）可靠地实现TCP全双工连接的终止<br />
&nbsp;&nbsp;&nbsp;
在进行关闭连接四路握手协议时，最后的ACK是由主动关闭端发出的，如果这个最终的ACK丢失，服务器将重发最终的FIN，因此客户端必须维护状态信息允
许它重发最终的ACK。如果不维持这个状态信息，那么客户端将响应RST分节，服务器将此分节解释成一个错误（在java中会抛出connection
reset的SocketException)。因而，要实现TCP全双工连接的正常终止，必须处理终止序列四个分节中任何一个分节的丢失情况，主动关闭
的客户端必须维持状态信息进入TIME_WAIT状态。<br />
<br />
2）允许老的重复分节在网络中消逝&nbsp; <br />
&nbsp;&nbsp;&nbsp;
TCP分节可能由于路由器异常而&ldquo;迷途&rdquo;，在迷途期间，TCP发送端可能因确认超时而重发这个分节，迷途的分节在路由器修复后也会被送到最终目的地，这个
原来的迷途分节就称为lost
duplicate。在关闭一个TCP连接后，马上又重新建立起一个相同的IP地址和端口之间的TCP连接，后一个连接被称为前一个连接的化身
（incarnation)，那么有可能出现这种情况，前一个连接的迷途重复分组在前一个连接终止后出现，从而被无解成从属于新的化身。为了避免这个情
况，TCP不允许处于TIME_WAIT状态的连接启动一个新的化身，因为TIME_WAIT状态持续2MSL，就可以保证当成功建立一个TCP连接的时
候，来自连接先前化身的重复分组已经在网络中消逝。<br />
<br />
&nbsp; 新的SCTP协议通过在消息头部添加验证标志避免了TIME_WAIT状态。</p>
          <br/>
          <span style="color:red;">
            <a href="http://dennis-zane.javaeye.com/blog/206963#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 23 Jun 2008 01:27:58 +0800</pubDate>
        <link>http://dennis-zane.javaeye.com/blog/206963</link>
        <guid>http://dennis-zane.javaeye.com/blog/206963</guid>
      </item>
      <item>
        <title>NIO的SelectableChannel关闭的一个问题</title>
        <author>dennis_zane</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://dennis-zane.javaeye.com/blog/204969" style="color:red;">http://dennis-zane.javaeye.com/blog/204969</a>&nbsp;
          发表时间: 2008年06月18日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>
&nbsp;&nbsp;&nbsp; SocketChannel和ServerSocketChannel，两者的父类是SelectableChannel，它在jdk中的文档有这么段话：</p>
<p id="e6:y3">&nbsp;</p>
<p>&nbsp;&nbsp; Once registered with a selector, a channel remains
registered until it is deregistered.This involves deallocating whatever
resources were allocated to the channel by the selector.<br id="a8x_0" />
&nbsp;&nbsp;&nbsp; A channel cannot be deregistered directly; instead, the key
representing its registration must be cancelled. Cancelling a key
requests that the channel be deregistered during the selector's<strong> next selection</strong>
 operation.</p>
<p>&nbsp;</p>
<p id="e6:y3">&nbsp;&nbsp;&nbsp; 也就是说关闭一个已经注册的SelectableChannel需要两个步骤：</p>
<p id="e6:y3">1）取消注册的key，这个可以通过SelectionKey.cancel方法，也可以通过SelectableChannel.close方法，或者中断阻塞在该channel上的IO操作的线程来做到。</p>
<p id="e6:y3">2）后续的Selector.selectXXX方法的调用才<strong>真正地关闭</strong>
本地Socket。</p>
<p id="e6:y3">&nbsp;&nbsp;&nbsp; 因而，如果，如果在取消SelectionKey后没有调用到selector的select方法（因为Client一般在取消key后，
我们都会终止调用select的循环，当然，server关闭一个注册的channel我们是不会终止select循环的），那么本地socket将进入<strong>CLOSE-WAIT</strong>
状态（等待本地Socket关闭）。简单的解决办法是在
SelectableChannel.close方法之后调用Selector.selectNow方法，类似:</p>
<p id="e6:y3">&nbsp;&nbsp; Selector sel;<br id="w350" />
&nbsp;&nbsp; SocketChannel sch;<br id="w3500" />
&nbsp;&nbsp;
// &hellip;<br id="w3501" />
&nbsp;&nbsp;
sch.close();<br id="w3502" />
&nbsp;&nbsp;
sel.selectNow();</p>
<p id="e6:y3"><br id="w3503" />
</p>
<p id="e6:y3">&nbsp;&nbsp;&nbsp; Nio编程有很多这样细节性的东西需要注意，通常情况下还是利用成熟的框架为妙。</p>
          <br/>
          <span style="color:red;">
            <a href="http://dennis-zane.javaeye.com/blog/204969#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 18 Jun 2008 01:53:06 +0800</pubDate>
        <link>http://dennis-zane.javaeye.com/blog/204969</link>
        <guid>http://dennis-zane.javaeye.com/blog/204969</guid>
      </item>
      <item>
        <title>java语言的演化——读JavaOne ppt笔记</title>
        <author>dennis_zane</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://dennis-zane.javaeye.com/blog/204958" style="color:red;">http://dennis-zane.javaeye.com/blog/204958</a>&nbsp;
          发表时间: 2008年06月18日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp;&nbsp;&nbsp; JavaOne2008上有个session《Upcoming Java Programming Language
Features》，讲了即将到来的jdk7可能引入的新的语言特性，比较开眼界的是jsr308对Annotation的扩展使用，更多地作为断言或者
说checker使用以便减少bug。在ppt前面，我觉的更有意思的是对java语言演化的讲述，做个笔记。</p>
<p align="justify" lang="zh-CN"><span style="font-family: 宋体;">1</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">、应用</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">VS.</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">语言</span>
</p>
<p align="justify" lang="zh-CN"><span style="font-family: 宋体;">应用是特性越多越好，应用是</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">rich</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">的；而语言是</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">pure</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">的，更少的、普通的特性更好。</span>
</p>
<p align="justify" lang="zh-CN"><span style="font-family: 宋体;">2</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">、添加一个</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">java</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">语言特性的三个前提：尊重过去、着眼未来以及顾及模型。</span>
</p>
<p align="justify" lang="zh-CN"><span style="font-family: 宋体;">无论是增加、减少或者改变一个</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">feature</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">都可能</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;"><strong>broken</strong>
</span>
<span style="font-family: 宋体;">已经存在的代码，一个新增加的</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">feature</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">必须兼容已经存在的代码，兼容是个沉重的包袱，就</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">java</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">语言而言，我相当认同<strong>尊重过去</strong>
这一点，哪怕加入闭包这样的特性也不应当以损坏兼容性为代价，更好的选择是将这些特性让</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">jvm</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">上的新语言去实现（比如JRuby、Scala），</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">java</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">语言作为成熟的工业语言本身不应当做太大的改变。而着眼未来，也就是说新的语言特性应该为未来的语法扩展留有空间，它的语法</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">/</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">语义不应当跟现存的或者潜在的特性相冲突，以便可以持续地演化。再谈顾及当前的模型，一门语言代表着一种计算模型，比如</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">simula</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">是</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">OO</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">模型（</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">classes)</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">，</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;"><a href="http://www.erlang.org/">Erlang</a>
</span>
<span style="font-family: 宋体;">就是</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">inter-process
communication</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">的模型</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">(actor</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">）。</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">Java</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">语言也有一个简单的模型：首先它是&ldquo;高层&rdquo;语言，是一门通用、并发、基于类的</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">OO</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">语言，其次，它跟</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">API</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">、</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">JVM</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">有良好的结合。</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">Java</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">语言模型有四个原则：</span>
</p>
<p align="justify" lang="zh-CN"><span style="font-family: 宋体;">a)</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">鼓励</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">high-level</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">实践，通过抽象来隐藏偶然复杂度。简而言之：</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">do
the right thing</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">。</span>
</p>
<p align="justify" lang="zh-CN"><span style="font-family: 宋体;">b)</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">追求清晰，程序被读的时候远远多于写。简而言之：</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">do
the thing right.</span>
</p>
<p align="justify" lang="zh-CN"><span style="font-family: 宋体;">c)</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">青睐静态类型，静态类型能增进对代码的信心，静态类型能证明</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">bug</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">在编译时的不存在，而测试和动态类型能证明</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">bug</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">的存在。我的观点是，大多数难以寻找和解决的</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">bug</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">都是</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">runtime</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">的，静态类型在此方面能给出的帮助有限，充分并且适宜的测试更加能增强你对应用的信心。</span>
</p>
<p align="justify" lang="zh-CN"><span style="font-family: 宋体;">d)</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">语言比之</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">API</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">的更广泛。</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">one
language,many
api</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">。</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">API</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">来去匆匆，而语言却是</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">forever</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">，因而将语言和</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">API</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">分离是明智的，一些特性可以做为库来实现，</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">jdk5</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">并发库的引入就很好，</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">jdk7</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">在并发方面同样将引入</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">fork/join</span>
<span style="font-family: 宋体;">
</span>
<span style="font-family: 宋体;">模型。</span>
</p>
<p><span style="font-family: 宋体;">&nbsp;&nbsp;&nbsp;&nbsp;
</span>
<span style="font-family: 宋体;">java语言
的演化也当遵循这四个原则。再看看传说中的闭包语言，当它跟泛型结合的时候写出来的东西还谈得上清晰吗？闭包的实现能否解决兼容性问题也是个疑问。就四个
提案，C3S需要引入method关键字（类似lambda)，FCM的#号看起来比较怪异，在我看来，CICE和BGGA更符合胃口，CICE对
java语言的变动应该最小，学习曲线也比较平缓，BGGA的=&gt;符号更有函数式语言的味道。闭包的引入，某种程度上能减少敲击键盘的次数并实现一
些高阶功能，特别是在聚合操作（如filter、map等）和单抽象方法类（如Runnable,Callable）的使用上，但是在现代IDE的自动化
帮助下，这个带来的价值是值的怀疑的。</span>
</p>
          <br/>
          <span style="color:red;">
            <a href="http://dennis-zane.javaeye.com/blog/204958#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 18 Jun 2008 00:22:00 +0800</pubDate>
        <link>http://dennis-zane.javaeye.com/blog/204958</link>
        <guid>http://dennis-zane.javaeye.com/blog/204958</guid>
      </item>
      <item>
        <title>JRuby中调用java带可变参数的方法</title>
        <author>dennis_zane</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://dennis-zane.javaeye.com/blog/203341" style="color:red;">http://dennis-zane.javaeye.com/blog/203341</a>&nbsp;
          发表时间: 2008年06月14日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>

&nbsp;&nbsp;&nbsp; 今天同事遇到的问题，用JRuby调用一个java方法，该方法使用了jdk1.5的可变参数。我一开始以为只要简单地将可变参数表示为数组即可，例如下面的两个java类：</p>
<pre name="code" class="java">public class Echo{
    public void echo(String name){
       System.out.println(name);
    }
}
public class Test{
    public void hello(String name,Echoargs){
        System.out.println(&quot;hello,&quot;+name);
        for(Echo e:args){
            e.echo(name);
        }
    }
}</pre>
&nbsp;
<p>
&nbsp;&nbsp; 我想在jruby中调用Test的hello方法，该方法有个可变参数args。所谓可变参数经过编译后其实也就是数组，这个可以通过观察字节码知道，那么如果用数组来调用可以不？</p>
<pre name="code" class="java">require 'java'
require 'test.jar'
include_class 'Test'
include_class 'Echo'
t.hello(&quot;dennis&quot;)  #报错，参数不匹配
t.hello(&quot;dennis&quot;,[])  #报错，类型不匹配</pre>
&nbsp;
<p>
&nbsp;&nbsp;
很遗憾，这样调用是错误的，原因如上面的注释。具体到类型不匹配，本质的原因是JRuby中的数组与java中对数组的字节码表示是不一致的，JRuby
中的数组是用org.jruby.RubyArray类来表示，而hello方法需要的数组却是是[LEcho。解决的办法就是将JRuby的数组转成
java需要的类型，通过to_java方法，因而下面的调用才是正确的，尽管显的麻烦：</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">require 'java'
require 'test.jar'
include_class 'Test'
include_class 'Echo'
t=Test.new
t.hello(&quot;dennis&quot;,[].to_java(&quot;Echo&quot;))
e1=Echo.new
t.hello(&quot;dennis&quot;,[e1].to_java(&quot;Echo&quot;))
e2=Echo.new
t.hello(&quot;dennis&quot;,[e1,e2].to_java(&quot;Echo&quot;))</pre>
&nbsp;
<p><br /></p>
          <br/>
          <span style="color:red;">
            <a href="http://dennis-zane.javaeye.com/blog/203341#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sat, 14 Jun 2008 22:41:18 +0800</pubDate>
        <link>http://dennis-zane.javaeye.com/blog/203341</link>
        <guid>http://dennis-zane.javaeye.com/blog/203341</guid>
      </item>
      <item>
        <title>理发二三事</title>
        <author>dennis_zane</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://dennis-zane.javaeye.com/blog/203115" style="color:red;">http://dennis-zane.javaeye.com/blog/203115</a>&nbsp;
          发表时间: 2008年06月13日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp;</p>
<p class="MsoPlainText"><span><span lang="EN-US"></span>
</span>
</p>
<p class="MsoPlainText"><span lang="EN-US"><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span>头发乃人之元，很多人看人，第一眼看的就是你的头发，当然看<span lang="EN-US">MM</span>
的部位可能不一样。我的头发从来都是像野草一样，从来没放心思在上面，都是理一次头发大半年不管他，长的跟杂草似的难受，然后回家找小区内的师傅理发&mdash;&mdash;或者说剃头，而且发型从来都是板寸头，短短的，免的大半年不回家还得在外面理发。因而，我的理发时间跟我的回家时间一样。<span lang="EN-US"></span>
</span>
</p>
<p class="MsoPlainText" style="text-indent: 21pt;"><span>小时候，那时候住在老家，没有专门的理发店，也可能是离我家的小村庄很远。有个走村串户的剃头匠，大概几个月能在村里见他一次，带着一身行头，谁家有人要剃头，就请他过去。记的那时是没用电的理发工具，一把类似夹子的东西，老师傅手工在各式各样的脑袋上推来推去，然后搞些肥皂水就着自家的井水冲洗。再然后，很久没再见到这位老师傅，隔壁村开始开了家理发店，然后大人们就带着我们走路或者骑着自行车去隔壁村剃头了，记的那位师傅还是我们大队的干部，隔壁是猪肉店，至于剃头的细节却是记不清了。再后来隔壁村又开了一家理发店，是个小青年开的，印象特别深的一次是我奶奶带我过去理发，小青年说如果我帮他打一桶井水，钱可以减半。我帮他打了桶井水，然后真给我减半了，就着自己打的井水冲洗我的不规则的大脑袋。后来，跟父母搬到了县城，去外地读书，就再也没在老家理发过了，好几年之后路过小青年开的那家店，装修了，蓝色的玻璃后面，小青年变成了师傅，指导着自己的徒弟在理发，我想，大概他也记不得当年那个给他打水的小屁孩了。<span lang="EN-US"></span>
</span>
</p>
<p class="MsoPlainText" style="text-indent: 21pt;"><span>进城了，我理发就固定在小区的周师傅这了。上大学，基本是五一、十一和春节回家的时候剃一次，哪怕再长，还是不愿意在福州理发，分不清是习惯还是莫名其妙的坚持了。其实挺不好意思的，每次都留那么长的头发，周师傅也还是固定收我<span lang="EN-US">5</span>
块钱，不过今年涨价了，<span lang="EN-US">7</span>
块。下午剃头的时候，周师傅的女儿找他要<span lang="EN-US">100</span>
来块钱，旁边的人问周师傅她拿去做啥呢？周师傅说去染发，语气有点黯然。父亲理的老式发型，女儿是不会喜欢的。我的头发就这样随着我离家的远近轮回着，现在在广州，回家还是容易的，以后去哪还是不知道的事情，希望还能半年回家一次理发。<span lang="EN-US"></span>
</span>
</p>
          <br/>
          <span style="color:red;">
            <a href="http://dennis-zane.javaeye.com/blog/203115#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 13 Jun 2008 21:55:30 +0800</pubDate>
        <link>http://dennis-zane.javaeye.com/blog/203115</link>
        <guid>http://dennis-zane.javaeye.com/blog/203115</guid>
      </item>
      <item>
        <title>Hadoop分布式文件系统：架构和设计要点(翻译)</title>
        <author>dennis_zane</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://dennis-zane.javaeye.com/blog/200508" style="color:red;">http://dennis-zane.javaeye.com/blog/200508</a>&nbsp;
          发表时间: 2008年06月05日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp;</p>
<p class="western" id="nekf0" style="margin-bottom: 0in;">Hadoop<span style="font-family: 宋体,SimSun;"><span id="nekf2" lang="zh-CN">分布式文件系统：架构和设计要点</span></span><br id="nekf3" /><br id="nekf4" /><span style="font-family: 宋体,SimSun;"><span id="nekf6" lang="zh-CN">一、前提和设计目标</span></span><br id="nekf7" />1<span style="font-family: 宋体,SimSun;"><span id="nekf9" lang="zh-CN">、硬件错误是常态，而非异常情况，</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf11" lang="zh-CN">可能是有成百上千的</span></span>server<span style="font-family: 宋体,SimSun;"><span id="nekf13" lang="zh-CN">组成，任何一个组件都有可能一直失效，因此错误检测和快速、自动的恢复是</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf15" lang="zh-CN">的核心架构目标。</span></span><br id="nekf16" />2<span style="font-family: 宋体,SimSun;"><span id="nekf18" lang="zh-CN">、跑在</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf20" lang="zh-CN">上的应用与一般的应用不同，它们主要是以流式读为主，做批量处理；比之关注数据访问的低延迟问题，更关键的在于数据访问的高吞吐量。</span></span><br id="nekf21" />3<span style="font-family: 宋体,SimSun;"><span id="nekf23" lang="zh-CN">、</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf25" lang="zh-CN">以支持大数据集合为目标，一个存储在上面的典型文件大小一般都在千兆至</span></span>T<span style="font-family: 宋体,SimSun;"><span id="nekf27" lang="zh-CN">字节，一个单一</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf29" lang="zh-CN">实例应该能支撑数以千万计的文件。</span></span><br id="nekf30" />4<span style="font-family: 宋体,SimSun;"><span id="nekf32" lang="zh-CN">、
</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf34" lang="zh-CN">应用对文件要求的是</span></span>write-one-read-many<span style="font-family: 宋体,SimSun;"><span id="nekf36" lang="zh-CN">访问模型。一个文件经过创建、写，关闭之后就不需要改变。这一假设简化了数据一致性问
题，使高吞吐量的数据访问成为可能。典型的如</span></span>MapReduce<span style="font-family: 宋体,SimSun;"><span id="nekf38" lang="zh-CN">框架，或者一个</span></span>web
crawler<span style="font-family: 宋体,SimSun;"><span id="nekf40" lang="zh-CN">应用都很适合这个模型。</span></span><br id="nekf41" />5<span style="font-family: 宋体,SimSun;"><span id="nekf43" lang="zh-CN">、移动计算的代价比之移动数据的代价低。一个应用请求的计算，离它操作的数据越近就越高效，这在数据达到海量级别的时候更是如此。将计算移动到数据附近，比之将数据移动到应用所在显然更好，</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf45" lang="zh-CN">提供给应用这样的接口。</span></span><br id="nekf46" />6<span style="font-family: 宋体,SimSun;"><span id="nekf48" lang="zh-CN">、在异构的软硬件平台间的可移植性。</span></span><br id="nekf49" /><br id="nekf50" /><span style="font-family: 宋体,SimSun;"><span id="nekf52" lang="zh-CN">二、</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf54" lang="zh-CN">和</span></span>Datanode<br id="nekf55" />&nbsp;&nbsp;&nbsp;
HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf57" lang="zh-CN">采用</span></span>master/slave<span style="font-family: 宋体,SimSun;"><span id="nekf59" lang="zh-CN">架构。一个</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf61" lang="zh-CN">集群是有一个</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf63" lang="zh-CN">和一定数目的</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf65" lang="zh-CN">组成。</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf67" lang="zh-CN">是一个中心服
务器，负责管理文件系统的</span></span>namespace<span style="font-family: 宋体,SimSun;"><span id="nekf69" lang="zh-CN">和客户端对文件的访问。</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf71" lang="zh-CN">在集群中一般是一个节点一个，负责管理节点上它们附带的存储。在内
部，一个文件其实分成一个或多个</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf73" lang="zh-CN">，这些</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf75" lang="zh-CN">存储在</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf77" lang="zh-CN">集合里。</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf79" lang="zh-CN">执行文件系统的</span></span>namespace<span style="font-family: 宋体,SimSun;"><span id="nekf81" lang="zh-CN">操作，例如
打开、关闭、重命名文件和目录，同时决定</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf83" lang="zh-CN">到具体</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf85" lang="zh-CN">节点的映射。</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf87" lang="zh-CN">在</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf89" lang="zh-CN">的指挥下进行</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf91" lang="zh-CN">的创
建、删除和复制。</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf93" lang="zh-CN">和</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf95" lang="zh-CN">都是设计成可以跑在普通的廉价的运行</span></span>linux<span style="font-family: 宋体,SimSun;"><span id="nekf97" lang="zh-CN">的机器上。</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf99" lang="zh-CN">采用</span></span>java<span style="font-family: 宋体,SimSun;"><span id="nekf101" lang="zh-CN">语言开发，因此可以部
署在很大范围的机器上。一个典型的部署场景是一台机器跑一个单独的</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf103" lang="zh-CN">节点，集群中的其他机器各跑一个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf105" lang="zh-CN">实例。这个架构并不排
除一台机器上跑多个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf107" lang="zh-CN">，不过这比较少见。</span></span><br id="nekf108" /><img src="http://docs.google.com/File?id=ddxkntwd_24fwt83gcp_b" id="nekf109" border="0" height="394" align="bottom" alt="" width="577" /><br id="nekf110" /><span style="font-family: 宋体,SimSun;"><span id="nekf112" lang="zh-CN">单一节点的</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf114" lang="zh-CN">大大简化了系统的架构。</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf116" lang="zh-CN">负责保管和管理所有的</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf118" lang="zh-CN">元数据，因而用户数据就不需要通过</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf120" lang="zh-CN">（也就是说文件数据的读写是直接在</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf122" lang="zh-CN">上）。</span></span><br id="nekf123" /><br id="nekf124" /><span style="font-family: 宋体,SimSun;"><span id="nekf126" lang="zh-CN">三、文件系统的</span></span>namespace<br id="nekf127" />&nbsp;&nbsp;
HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf129" lang="zh-CN">支持传统的层次型文件组织，与大多数其他文件系统类似，用户可以创建目录，并在其间创建、删除、移动和重命名文件。</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf131" lang="zh-CN">不支持</span></span>user
quotas<span style="font-family: 宋体,SimSun;"><span id="nekf133" lang="zh-CN">和访问权限，也不支持链接（</span></span>link)<span style="font-family: 宋体,SimSun;"><span id="nekf135" lang="zh-CN">，不过当前的架构并不排除实现这些特性。</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf137" lang="zh-CN">维护文件系统的</span></span>namespace<span style="font-family: 宋体,SimSun;"><span id="nekf139" lang="zh-CN">，任何对文
件系统</span></span>namespace<span style="font-family: 宋体,SimSun;"><span id="nekf141" lang="zh-CN">和文件属性的修改都将被</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf143" lang="zh-CN">记录下来。应用可以设置</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf145" lang="zh-CN">保存的文件的副本数目，文件副本的数目称为文件的
</span></span>replication<span style="font-family: 宋体,SimSun;"><span id="nekf147" lang="zh-CN">因子，这个信息也是由</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf149" lang="zh-CN">保存。</span></span><br id="nekf150" /><br id="nekf151" /><span style="font-family: 宋体,SimSun;"><span id="nekf153" lang="zh-CN">四、数据复制</span></span><br id="nekf154" />&nbsp;&nbsp;&nbsp;
HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf156" lang="zh-CN">被设计成在一个大集群中可以跨机器地可靠地存储海量的文件。它将每个文件存储成</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf158" lang="zh-CN">序列，除了最后一个</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf160" lang="zh-CN">，所有的</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf162" lang="zh-CN">都是同
样的大小。文件的所有</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf164" lang="zh-CN">为了容错都会被复制。每个文件的</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf166" lang="zh-CN">大小和</span></span>replication<span style="font-family: 宋体,SimSun;"><span id="nekf168" lang="zh-CN">因子都是可配置的。</span></span>Replication<span style="font-family: 宋体,SimSun;"><span id="nekf170" lang="zh-CN">因子可
以在文件创建的时候配置，以后也可以改变。</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf172" lang="zh-CN">中的文件是</span></span>write-one<span style="font-family: 宋体,SimSun;"><span id="nekf174" lang="zh-CN">，并且严格要求在任何时候只有一个</span></span>writer<span style="font-family: 宋体,SimSun;"><span id="nekf176" lang="zh-CN">。</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf178" lang="zh-CN">全权管
理</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf180" lang="zh-CN">的复制，它周期性地从集群中的每个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf182" lang="zh-CN">接收心跳包和一个</span></span>Blockreport<span style="font-family: 宋体,SimSun;"><span id="nekf184" lang="zh-CN">。心跳包的接收表示该</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf186" lang="zh-CN">节点正常工
作，而</span></span>Blockreport<span style="font-family: 宋体,SimSun;"><span id="nekf188" lang="zh-CN">包括了该</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf190" lang="zh-CN">上所有的</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf192" lang="zh-CN">组成的列表。</span></span></p>
<p class="western" id="nekf193" style="margin-bottom: 0in;"><img src="http://docs.google.com/File?id=ddxkntwd_25gq43fcg4_b" id="nekf194" border="0" height="352" align="bottom" alt="" width="576" /><br id="nekf195" />1<span style="font-family: 宋体,SimSun;"><span id="nekf197" lang="zh-CN">、副本的存放，副本的存放是</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf199" lang="zh-CN">可靠性和性能的关键。</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf201" lang="zh-CN">采用一种称为</span></span>rack-aware<span style="font-family: 宋体,SimSun;"><span id="nekf203" lang="zh-CN">的策略来改进数据的可靠性、有效性和网络带宽的利
用。这个策略实现的短期目标是验证在生产环境下的表现，观察它的行为，构建测试和研究的基础，以便实现更先进的策略。庞大的</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf205" lang="zh-CN">实例一般运行在多个机
架的计算机形成的集群上，不同机架间的两台机器的通讯需要通过交换机，显然通常情况下，同一个机架内的两个节点间的带宽会比不同机架间的两台机器的带宽
大。</span></span><br id="nekf206" />&nbsp;&nbsp;&nbsp; <span style="font-family: 宋体,SimSun;"><span id="nekf208" lang="zh-CN">通过一个称为</span></span>Rack
Awareness<span style="font-family: 宋体,SimSun;"><span id="nekf210" lang="zh-CN">的过程，</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf212" lang="zh-CN">决定了每个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf214" lang="zh-CN">所属的</span></span>rack
id<span style="font-family: 宋体,SimSun;"><span id="nekf216" lang="zh-CN">。一个简单但没有优化的策略就是将副本存放在单独的机架上。这样可以防止整个机架（非副本存放）失效的情况，并且允许读数据的时候可以从多个机架读
取。这个简单策略设置可以将副本分布在集群中，有利于组件失败情况下的负载均衡。但是，这个简单策略加大了写的代价，因为一个写操作需要传输</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf218" lang="zh-CN">到
多个机架。</span></span><br id="nekf219" />&nbsp;&nbsp;&nbsp;
<span style="font-family: 宋体,SimSun;"><span id="nekf221" lang="zh-CN">在大多数情况下，</span></span>replication<span style="font-family: 宋体,SimSun;"><span id="nekf223" lang="zh-CN">因子是</span></span>3<span style="font-family: 宋体,SimSun;"><span id="nekf225" lang="zh-CN">，</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf227" lang="zh-CN">的存放策略是将一个副本存放在本地机架上的节点，一个副本放在同一机架上的另一个节点，最后一
个副本放在不同机架上的一个节点。机架的错误远远比节点的错误少，这个策略不会影响到数据的可靠性和有效性。三分之一的副本在一个节点上，三分之二在一个
机架上，其他保存在剩下的机架中，这一策略改进了写的性能。</span></span><br id="nekf228" /><br id="nekf229" />2<span style="font-family: 宋体,SimSun;"><span id="nekf231" lang="zh-CN">、副本的选择，为了降低整体的带宽消耗和读延时，</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf233" lang="zh-CN">会尽量让</span></span>reader<span style="font-family: 宋体,SimSun;"><span id="nekf235" lang="zh-CN">读最近的副本。如果在</span></span>reader<span style="font-family: 宋体,SimSun;"><span id="nekf237" lang="zh-CN">的同一个机架上有一个副本，那么就读该副本。如果一个</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf239" lang="zh-CN">集群跨越多个数据中心，那么</span></span>reader<span style="font-family: 宋体,SimSun;"><span id="nekf241" lang="zh-CN">也将首先尝试读本地数据中心的副本。</span></span><br id="nekf242" /><br id="nekf243" />3<span style="font-family: 宋体,SimSun;"><span id="nekf245" lang="zh-CN">、</span></span>SafeMode<br id="nekf246" />&nbsp;&nbsp;&nbsp;
Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf248" lang="zh-CN">启动后会进入一个称为</span></span>SafeMode<span style="font-family: 宋体,SimSun;"><span id="nekf250" lang="zh-CN">的特殊状态，处在这个状态的</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf252" lang="zh-CN">是不会进行数据块的复制的。</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf254" lang="zh-CN">从所有的
</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf256" lang="zh-CN">接收心跳包和</span></span>Blockreport<span style="font-family: 宋体,SimSun;"><span id="nekf258" lang="zh-CN">。</span></span>Blockreport<span style="font-family: 宋体,SimSun;"><span id="nekf260" lang="zh-CN">包括了某个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf262" lang="zh-CN">所有的数据块列表。每个</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf264" lang="zh-CN">都有指定的最
小数目的副本。当</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf266" lang="zh-CN">检测确认某个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf268" lang="zh-CN">的数据块副本的最小数目，那么该</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf270" lang="zh-CN">就会被认为是安全的；如果一定百分比（这
个参数可配置）的数据块检测确认是安全的，那么</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf272" lang="zh-CN">将退出</span></span>SafeMode<span style="font-family: 宋体,SimSun;"><span id="nekf274" lang="zh-CN">状态，接下来它会确定还有哪些数据块的副本没有达到指定数目，并将
这些</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf276" lang="zh-CN">复制到其他</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf278" lang="zh-CN">。</span></span><br id="nekf279" /><br id="nekf280" /><span style="font-family: 宋体,SimSun;"><span id="nekf282" lang="zh-CN">五、文件系统元数据的持久化</span></span><br id="nekf283" />&nbsp;&nbsp;&nbsp;
Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf285" lang="zh-CN">存储</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf287" lang="zh-CN">的元数据。对于任何对文件元数据产生修改的操作，</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf289" lang="zh-CN">都使用一个称为</span></span>Editlog<span style="font-family: 宋体,SimSun;"><span id="nekf291" lang="zh-CN">的事务日志记录下来。例如，
在</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf293" lang="zh-CN">中创建一个文件，</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf295" lang="zh-CN">就会在</span></span>Editlog<span style="font-family: 宋体,SimSun;"><span id="nekf297" lang="zh-CN">中插入一条记录来表示；同样，修改文件的</span></span>replication<span style="font-family: 宋体,SimSun;"><span id="nekf299" lang="zh-CN">因子也将往
</span></span>Editlog<span style="font-family: 宋体,SimSun;"><span id="nekf301" lang="zh-CN">插入一条记录。</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf303" lang="zh-CN">在本地</span></span>OS<span style="font-family: 宋体,SimSun;"><span id="nekf305" lang="zh-CN">的文件系统中存储这个</span></span>Editlog<span style="font-family: 宋体,SimSun;"><span id="nekf307" lang="zh-CN">。整个文件系统的</span></span>namespace<span style="font-family: 宋体,SimSun;"><span id="nekf309" lang="zh-CN">，包括</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf311" lang="zh-CN">到文件
的映射、文件的属性，都存储在称为</span></span>FsImage<span style="font-family: 宋体,SimSun;"><span id="nekf313" lang="zh-CN">的文件中，这个文件也是放在</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf315" lang="zh-CN">所在系统的文件系统上。</span></span><br id="nekf316" />&nbsp;&nbsp;&nbsp;
Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf318" lang="zh-CN">在内存中保存着整个文件系统</span></span>namespace<span style="font-family: 宋体,SimSun;"><span id="nekf320" lang="zh-CN">和文件</span></span>Blockmap<span style="font-family: 宋体,SimSun;"><span id="nekf322" lang="zh-CN">的映像。这个关键的元数据设计得很紧凑，因而一个带有</span></span>4G<span style="font-family: 宋体,SimSun;"><span id="nekf324" lang="zh-CN">内存的
</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf326" lang="zh-CN">足够支撑海量的文件和目录。当</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf328" lang="zh-CN">启动时，它从硬盘中读取</span></span>Editlog<span style="font-family: 宋体,SimSun;"><span id="nekf330" lang="zh-CN">和</span></span>FsImage<span style="font-family: 宋体,SimSun;"><span id="nekf332" lang="zh-CN">，将所有</span></span>Editlog<span style="font-family: 宋体,SimSun;"><span id="nekf334" lang="zh-CN">中的事务作
用（</span></span>apply)<span style="font-family: 宋体,SimSun;"><span id="nekf336" lang="zh-CN">在内存中的</span></span>FsImage
<span style="font-family: 宋体,SimSun;"><span id="nekf338" lang="zh-CN">，并将这个新版本的</span></span>FsImage<span style="font-family: 宋体,SimSun;"><span id="nekf340" lang="zh-CN">从内存中</span></span>flush<span style="font-family: 宋体,SimSun;"><span id="nekf342" lang="zh-CN">到硬盘上</span></span>,<span style="font-family: 宋体,SimSun;"><span id="nekf344" lang="zh-CN">然后再</span></span>truncate<span style="font-family: 宋体,SimSun;"><span id="nekf346" lang="zh-CN">这个旧的</span></span>Editlog<span style="font-family: 宋体,SimSun;"><span id="nekf348" lang="zh-CN">，因为这个旧的</span></span>Editlog<span style="font-family: 宋体,SimSun;"><span id="nekf350" lang="zh-CN">的事务都已经
作用在</span></span>FsImage<span style="font-family: 宋体,SimSun;"><span id="nekf352" lang="zh-CN">上了。这个过程称为</span></span>checkpoint<span style="font-family: 宋体,SimSun;"><span id="nekf354" lang="zh-CN">。在当前实现中，</span></span>checkpoint<span style="font-family: 宋体,SimSun;"><span id="nekf356" lang="zh-CN">只发生在</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf358" lang="zh-CN">启动时，在不久的将来我们将
实现支持周期性的</span></span>checkpoint<span style="font-family: 宋体,SimSun;"><span id="nekf360" lang="zh-CN">。</span></span><br id="nekf361" />&nbsp;&nbsp;&nbsp;
Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf363" lang="zh-CN">并不知道关于文件的任何东西，除了将文件中的数据保存在本地的文件系统上。它把每个</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf365" lang="zh-CN">数据块存储在本地文件系统上隔离的文件中。
</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf367" lang="zh-CN">并不在同一个目录创建所有的文件，相反，它用启发式地方法来确定每个目录的最佳文件数目，并且在适当的时候创建子目录。在同一个目录创建
所有的文件不是最优的选择，因为本地文件系统可能无法高效地在单一目录中支持大量的文件。当一个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf369" lang="zh-CN">启动时，它扫描本地文件系统，对这些本地
文件产生相应的一个所有</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf371" lang="zh-CN">数据块的列表，然后发送报告到</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf373" lang="zh-CN">，这个报告就是</span></span>Blockreport<span style="font-family: 宋体,SimSun;"><span id="nekf375" lang="zh-CN">。</span></span><br id="nekf376" /><br id="nekf377" /><span style="font-family: 宋体,SimSun;"><span id="nekf379" lang="zh-CN">六、通讯协议</span></span><br id="nekf380" />&nbsp;&nbsp;&nbsp;
<span style="font-family: 宋体,SimSun;"><span id="nekf382" lang="zh-CN">所有的</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf384" lang="zh-CN">通讯协议都是构建在</span></span>TCP/IP<span style="font-family: 宋体,SimSun;"><span id="nekf386" lang="zh-CN">协议上。客户端通过一个可配置的端口连接到</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf388" lang="zh-CN">，通过</span></span>ClientProtocol<span style="font-family: 宋体,SimSun;"><span id="nekf390" lang="zh-CN">与
</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf392" lang="zh-CN">交互。而</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf394" lang="zh-CN">是使用</span></span>DatanodeProtocol<span style="font-family: 宋体,SimSun;"><span id="nekf396" lang="zh-CN">与</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf398" lang="zh-CN">交互。从</span></span>ClientProtocol<span style="font-family: 宋体,SimSun;"><span id="nekf400" lang="zh-CN">和
</span></span>Datanodeprotocol<span style="font-family: 宋体,SimSun;"><span id="nekf402" lang="zh-CN">抽象出一个远程调用</span></span>(RPC<span style="font-family: 宋体,SimSun;"><span id="nekf404" lang="zh-CN">），在设计上，</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf406" lang="zh-CN">不会主动发起</span></span>RPC<span style="font-family: 宋体,SimSun;"><span id="nekf408" lang="zh-CN">，而是是响应来自客户端和
</span></span>Datanode <span style="font-family: 宋体,SimSun;"><span id="nekf410" lang="zh-CN">的</span></span>RPC<span style="font-family: 宋体,SimSun;"><span id="nekf412" lang="zh-CN">请求。</span></span><br id="nekf413" /><br id="nekf414" /><span style="font-family: 宋体,SimSun;"><span id="nekf416" lang="zh-CN">七、健壮性</span></span><br id="nekf417" />&nbsp;&nbsp;&nbsp;
HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf419" lang="zh-CN">的主要目标就是实现在失败情况下的数据存储可靠性。常见的三种失败：</span></span>Namenode
failures, Datanode failures<span style="font-family: 宋体,SimSun;"><span id="nekf421" lang="zh-CN">和网络分割（</span></span>network
partitions)<span style="font-family: 宋体,SimSun;"><span id="nekf423" lang="zh-CN">。</span></span><br id="nekf424" />1<span style="font-family: 宋体,SimSun;"><span id="nekf426" lang="zh-CN">、硬盘数据错误、心跳检测和重新复制</span></span><br id="nekf427" />&nbsp;&nbsp;&nbsp;
<span style="font-family: 宋体,SimSun;"><span id="nekf429" lang="zh-CN">每个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf431" lang="zh-CN">节点都向</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf433" lang="zh-CN">周期性地发送心跳包。网络切割可能导致一部分</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf435" lang="zh-CN">跟</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf437" lang="zh-CN">失去联系。
</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf439" lang="zh-CN">通过心跳包的缺失检测到这一情况，并将这些</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf441" lang="zh-CN">标记为</span></span>dead<span style="font-family: 宋体,SimSun;"><span id="nekf443" lang="zh-CN">，不会将新的</span></span>IO<span style="font-family: 宋体,SimSun;"><span id="nekf445" lang="zh-CN">请求发给它们。寄存在</span></span>dead
Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf447" lang="zh-CN">上的任何数据将不再有效。</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf449" lang="zh-CN">的死亡可能引起一些</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf451" lang="zh-CN">的副本数目低于指定值，</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf453" lang="zh-CN">不断地跟踪需要复制的
</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf455" lang="zh-CN">，在任何需要的情况下启动复制。在下列情况可能需要重新复制：某个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf457" lang="zh-CN">节点失效，某个副本遭到损坏，</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf459" lang="zh-CN">上的硬盘错
误，或者文件的</span></span>replication<span style="font-family: 宋体,SimSun;"><span id="nekf461" lang="zh-CN">因子增大。</span></span><br id="nekf462" /><br id="nekf463" />2<span style="font-family: 宋体,SimSun;"><span id="nekf465" lang="zh-CN">、集群均衡</span></span><br id="nekf466" />&nbsp;&nbsp;
HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf468" lang="zh-CN">支持数据的均衡计划，如果某个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf470" lang="zh-CN">节点上的空闲空间低于特定的临界点，那么就会启动一个计划自动地将数据从一个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf472" lang="zh-CN">搬移
到空闲的</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf474" lang="zh-CN">。当对某个文件的请求突然增加，那么也可能启动一个计划创建该文件新的副本，并分布到集群中以满足应用的要求。这些均衡计划目前
还没有实现。</span></span><br id="nekf475" /><br id="nekf476" />3<span style="font-family: 宋体,SimSun;"><span id="nekf478" lang="zh-CN">、数据完整性</span></span><br id="nekf479" />&nbsp;
<span style="font-family: 宋体,SimSun;"><span id="nekf481" lang="zh-CN">从某个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf483" lang="zh-CN">获取的数据块有可能是损坏的，这个损坏可能是由于</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf485" lang="zh-CN">的存储设备错误、网络错误或者软件</span></span>bug<span style="font-family: 宋体,SimSun;"><span id="nekf487" lang="zh-CN">造成的。</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf489" lang="zh-CN">客户端
软件实现了</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf491" lang="zh-CN">文件内容的校验和。当某个客户端创建一个新的</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf493" lang="zh-CN">文件，会计算这个文件每个</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf495" lang="zh-CN">的校验和，并作为一个单独的隐藏文件保存这些
校验和在同一个</span></span>HDFS
namespace<span style="font-family: 宋体,SimSun;"><span id="nekf497" lang="zh-CN">下。当客户端检索文件内容，它会确认从</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf499" lang="zh-CN">获取的数据跟相应的校验和文件中的校验和是否匹配，如果不匹配，客户端可以选择
从其他</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf501" lang="zh-CN">获取该</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf503" lang="zh-CN">的副本。</span></span><br id="nekf504" /><br id="nekf505" />4<span style="font-family: 宋体,SimSun;"><span id="nekf507" lang="zh-CN">、元数据磁盘错误</span></span><br id="nekf508" />&nbsp;&nbsp;&nbsp;
FsImage<span style="font-family: 宋体,SimSun;"><span id="nekf510" lang="zh-CN">和</span></span>Editlog<span style="font-family: 宋体,SimSun;"><span id="nekf512" lang="zh-CN">是</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf514" lang="zh-CN">的核心数据结构。这些文件如果损坏了，整个</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf516" lang="zh-CN">实例都将失效。因而，</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf518" lang="zh-CN">可以配置成支持维护多
个</span></span>FsImage<span style="font-family: 宋体,SimSun;"><span id="nekf520" lang="zh-CN">和</span></span>Editlog<span style="font-family: 宋体,SimSun;"><span id="nekf522" lang="zh-CN">的拷贝。任何对</span></span>FsImage<span style="font-family: 宋体,SimSun;"><span id="nekf524" lang="zh-CN">或者</span></span>Editlog<span style="font-family: 宋体,SimSun;"><span id="nekf526" lang="zh-CN">的修改，都将同步到它们的副本上。这个同步操作可能会降低
</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf528" lang="zh-CN">每秒能支持处理的</span></span>namespace<span style="font-family: 宋体,SimSun;"><span id="nekf530" lang="zh-CN">事务。这个代价是可以接受的，因为</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf532" lang="zh-CN">是数据密集的，而非元数据密集。当</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf534" lang="zh-CN">重启的
时候，它总是选取最近的一致的</span></span>FsImage<span style="font-family: 宋体,SimSun;"><span id="nekf536" lang="zh-CN">和</span></span>Editlog<span style="font-family: 宋体,SimSun;"><span id="nekf538" lang="zh-CN">使用。</span></span><br id="nekf539" />&nbsp;&nbsp;
Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf541" lang="zh-CN">在</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf543" lang="zh-CN">是单点存在，如果</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf545" lang="zh-CN">所在的机器错误，手工的干预是必须的。目前，在另一台机器上重启因故障而停止服务的</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf547" lang="zh-CN">这个功能还没实现。</span></span><br id="nekf548" /><br id="nekf549" />5<span style="font-family: 宋体,SimSun;"><span id="nekf551" lang="zh-CN">、快照</span></span><br id="nekf552" />&nbsp;&nbsp;
<span style="font-family: 宋体,SimSun;"><span id="nekf554" lang="zh-CN">快照支持某个时间的数据拷贝，当</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf556" lang="zh-CN">数据损坏的时候，可以恢复到过去一个已知正确的时间点。</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf558" lang="zh-CN">目前还不支持快照功能。</span></span><br id="nekf559" /><br id="nekf560" /><span style="font-family: 宋体,SimSun;"><span id="nekf562" lang="zh-CN">八、数据组织</span></span><br id="nekf563" />1<span style="font-family: 宋体,SimSun;"><span id="nekf565" lang="zh-CN">、数据块</span></span><br id="nekf566" />&nbsp;&nbsp;&nbsp;
<span style="font-family: 宋体,SimSun;"><span id="nekf568" lang="zh-CN">兼容</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf570" lang="zh-CN">的应用都是处理大数据集合的。这些应用都是写数据一次，读却是一次到多次，并且读的速度要满足流式读。</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf572" lang="zh-CN">支持文件的</span></span>write-
once-read-many<span style="font-family: 宋体,SimSun;"><span id="nekf574" lang="zh-CN">语义。一个典型的</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf576" lang="zh-CN">大小是</span></span>64MB<span style="font-family: 宋体,SimSun;"><span id="nekf578" lang="zh-CN">，因而，文件总是按照</span></span>64M<span style="font-family: 宋体,SimSun;"><span id="nekf580" lang="zh-CN">切分成</span></span>chunk<span style="font-family: 宋体,SimSun;"><span id="nekf582" lang="zh-CN">，每个</span></span>chunk<span style="font-family: 宋体,SimSun;"><span id="nekf584" lang="zh-CN">存储于不同的
</span></span>Datanode<br id="nekf585" />2<span style="font-family: 宋体,SimSun;"><span id="nekf587" lang="zh-CN">、步骤</span></span><br id="nekf588" />&nbsp;&nbsp;&nbsp;
<span style="font-family: 宋体,SimSun;"><span id="nekf590" lang="zh-CN">某个客户端创建文件的请求其实并没有立即发给</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf592" lang="zh-CN">，事实上，</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf594" lang="zh-CN">客户端会将文件数据缓存到本地的一个临时文件。应用的写被透明地重定向到
这个临时文件。当这个临时文件累积的数据超过一个</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf596" lang="zh-CN">的大小（默认</span></span>64M)<span style="font-family: 宋体,SimSun;"><span id="nekf598" lang="zh-CN">，客户端才会联系</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf600" lang="zh-CN">。</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf602" lang="zh-CN">将文件名插入文件系
统的层次结构中，并且分配一个数据块给它，然后返回</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf604" lang="zh-CN">的标识符和目标数据块给客户端。客户端将本地临时文件</span></span>flush<span style="font-family: 宋体,SimSun;"><span id="nekf606" lang="zh-CN">到指定的
</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf608" lang="zh-CN">上。当文件关闭时，在临时文件中剩余的没有</span></span>flush<span style="font-family: 宋体,SimSun;"><span id="nekf610" lang="zh-CN">的数据也会传输到指定的</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf612" lang="zh-CN">，然后客户端告诉</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf614" lang="zh-CN">文件已经
关闭。此时</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf616" lang="zh-CN">才将文件创建操作提交到持久存储。如果</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf618" lang="zh-CN">在文件关闭前挂了，该文件将丢失。</span></span><br id="nekf619" />&nbsp;&nbsp;
<span style="font-family: 宋体,SimSun;"><span id="nekf621" lang="zh-CN">上述方法是对通过对</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf623" lang="zh-CN">上运行的目标应用认真考虑的结果。如果不采用客户端缓存，由于网络速度和网络堵塞会对吞估量造成比较大的影响。</span></span><br id="nekf624" /><br id="nekf625" />3<span style="font-family: 宋体,SimSun;"><span id="nekf627" lang="zh-CN">、流水线复制</span></span><br id="nekf628" />&nbsp;&nbsp;&nbsp;
<span style="font-family: 宋体,SimSun;"><span id="nekf630" lang="zh-CN">当某个客户端向</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf632" lang="zh-CN">文件写数据的时候，一开始是写入本地临时文件，假设该文件的</span></span>replication<span style="font-family: 宋体,SimSun;"><span id="nekf634" lang="zh-CN">因子设置为</span></span>3<span style="font-family: 宋体,SimSun;"><span id="nekf636" lang="zh-CN">，那么客户端会从</span></span>Namenode
<span style="font-family: 宋体,SimSun;"><span id="nekf638" lang="zh-CN">获取一张</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf640" lang="zh-CN">列表来存放副本。然后客户端开始向第一个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf642" lang="zh-CN">传输数据，第一个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf644" lang="zh-CN">一小部分一小部分（</span></span>4kb)<span style="font-family: 宋体,SimSun;"><span id="nekf646" lang="zh-CN">地接收数
据，将每个部分写入本地仓库，并且同时传输该部分到第二个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf648" lang="zh-CN">节点。第二个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf650" lang="zh-CN">也是这样，边收边传，一小部分一小部分地收，存储
在本地仓库，同时传给第三个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf652" lang="zh-CN">，第三个</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf654" lang="zh-CN">就仅仅是接收并存储了。这就是流水线式的复制。</span></span><br id="nekf655" /><br id="nekf656" /><span style="font-family: 宋体,SimSun;"><span id="nekf658" lang="zh-CN">九、可访问性</span></span><br id="nekf659" />&nbsp;&nbsp;&nbsp;
HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf661" lang="zh-CN">给应用提供了多种访问方式，可以通过</span></span>DFSShell<span style="font-family: 宋体,SimSun;"><span id="nekf663" lang="zh-CN">通过命令行与</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf665" lang="zh-CN">数据进行交互，可以通过</span></span>java
API<span style="font-family: 宋体,SimSun;"><span id="nekf667" lang="zh-CN">调用，也可以通过</span></span>C<span style="font-family: 宋体,SimSun;"><span id="nekf669" lang="zh-CN">语言的封装</span></span>API<span style="font-family: 宋体,SimSun;"><span id="nekf671" lang="zh-CN">访问，并且提供了浏览器访问的方式。正在开发通过</span></span>WebDav<span style="font-family: 宋体,SimSun;"><span id="nekf673" lang="zh-CN">协议访问的方式。具体使用参考文档。</span></span><br id="nekf674" /><span style="font-family: 宋体,SimSun;"><span id="nekf676" lang="zh-CN">十、空间的回收</span></span><br id="nekf677" />1<span style="font-family: 宋体,SimSun;"><span id="nekf679" lang="zh-CN">、文件的删除和恢复</span></span><br id="nekf680" />&nbsp;&nbsp;&nbsp;
<span style="font-family: 宋体,SimSun;"><span id="nekf682" lang="zh-CN">用户或者应用删除某个文件，这个文件并没有立刻从</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf684" lang="zh-CN">中删除。相反，</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf686" lang="zh-CN">将这个文件重命名，并转移到</span></span>/trash<span style="font-family: 宋体,SimSun;"><span id="nekf688" lang="zh-CN">目录。当文件还在</span></span>/trash<span style="font-family: 宋体,SimSun;"><span id="nekf690" lang="zh-CN">目
录时，该文件可以被迅速地恢复。文件在</span></span>/trash<span style="font-family: 宋体,SimSun;"><span id="nekf692" lang="zh-CN">中保存的时间是可配置的，当超过这个时间，</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf694" lang="zh-CN">就会将该文件从</span></span>namespace<span style="font-family: 宋体,SimSun;"><span id="nekf696" lang="zh-CN">中删除。
文件的删除，也将释放关联该文件的数据块。注意到，在文件被用户删除和</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf698" lang="zh-CN">空闲空间的增加之间会有一个等待时间延迟。</span></span><br id="nekf699" />&nbsp;&nbsp;&nbsp;
<span style="font-family: 宋体,SimSun;"><span id="nekf701" lang="zh-CN">当被删除的文件还保留在</span></span>/trash<span style="font-family: 宋体,SimSun;"><span id="nekf703" lang="zh-CN">目录中的时候，如果用户想恢复这个文件，可以检索浏览</span></span>/trash<span style="font-family: 宋体,SimSun;"><span id="nekf705" lang="zh-CN">目录并检索该文件。</span></span>/trash<span style="font-family: 宋体,SimSun;"><span id="nekf707" lang="zh-CN">目录仅仅保存被删除
文件的最近一次拷贝。</span></span>/trash<span style="font-family: 宋体,SimSun;"><span id="nekf709" lang="zh-CN">目录与其他文件目录没有什么不同，除了一点：</span></span>HDFS<span style="font-family: 宋体,SimSun;"><span id="nekf711" lang="zh-CN">在该目录上应用了一个特殊的策略来自动删除文件，目前的默认策略是
删除保留超过</span></span>6<span style="font-family: 宋体,SimSun;"><span id="nekf713" lang="zh-CN">小时的文件，这个策略以后会定义成可配置的接口。</span></span><br id="nekf714" /><br id="nekf715" />2<span style="font-family: 宋体,SimSun;"><span id="nekf717" lang="zh-CN">、</span></span>Replication<span style="font-family: 宋体,SimSun;"><span id="nekf719" lang="zh-CN">因子的减小</span></span><br id="nekf720" />&nbsp;&nbsp;&nbsp;
<span style="font-family: 宋体,SimSun;"><span id="nekf722" lang="zh-CN">当某个文件的</span></span>replication<span style="font-family: 宋体,SimSun;"><span id="nekf724" lang="zh-CN">因子减小，</span></span>Namenode<span style="font-family: 宋体,SimSun;"><span id="nekf726" lang="zh-CN">会选择要删除的过剩的副本。下次心跳检测就将该信息传递给</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf728" lang="zh-CN">，
</span></span>Datanode<span style="font-family: 宋体,SimSun;"><span id="nekf730" lang="zh-CN">就会移除相应的</span></span>block<span style="font-family: 宋体,SimSun;"><span id="nekf732" lang="zh-CN">并释放空间，同样，在调用</span></span>setReplication<span style="font-family: 宋体,SimSun;"><span id="nekf734" lang="zh-CN">方法和集群中的空闲空间增加之间会有一个时间延迟。</span></span><br id="nekf735" /><br id="nekf736" /><span style="font-family: 宋体,SimSun;"><span id="nekf738" lang="zh-CN">参考资料：</span></span><br id="nekf739" />HDFS
Java API: http://hadoop.apache.org/core/docs/current/api/<br id="nekf740" />HDFS
source code: http://hadoop.apache.org/core/version_control.html<br id="nekf741" />&nbsp;&nbsp;&nbsp;
<br id="nekf742" /><br id="nekf743" /><br id="nekf744" /><br id="nekf745" /><br id="nekf746" /><br id="nekf747" /><br id="nekf748" /><br id="nekf749" />
</p>
          <br/>
          <span style="color:red;">
            <a href="http://dennis-zane.javaeye.com/blog/200508#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 05 Jun 2008 14:26:24 +0800</pubDate>
        <link>http://dennis-zane.javaeye.com/blog/200508</link>
        <guid>http://dennis-zane.javaeye.com/blog/200508</guid>
      </item>
      <item>
        <title>两段java代码的比较</title>
        <author>dennis_zane</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://dennis-zane.javaeye.com/blog/198999" style="color:red;">http://dennis-zane.javaeye.com/blog/198999</a>&nbsp;
          发表时间: 2008年05月31日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>
第一个程序：</p>
<pre name="code" class="java">import java.util.ArrayList;
import java.util.List;

public class TailRecursionTest {
    public static void main(String[] args) {
        TailRecursionTest t = new TailRecursionTest();
        for (int i = 0; i &lt; 10000; i++)
            t.a(0);
    }

    public void a(int j) {
        j++;
        List list = new ArrayList&lt;Integer&gt;(100000);
        // 对list进行处理
    }
}</pre>
&nbsp;
<p>
&nbsp;&nbsp;&nbsp; 没啥特殊的，仅仅是为了测试，我们将a方法调用10000次，a方法创建一个有100000个元素的list的局部变量。<br />
第二个程序：</p>
<p>&nbsp;</p>
<pre name="code" class="java">import java.util.ArrayList;
import java.util.List;

public class TailRecursionTest2 {
    public static void main(String[] args) {
        TailRecursionTest2 t = new TailRecursionTest2();
        t.a(0);
    }

    public void a(int j) {
        System.out.println(j);
        j++;
        if (j == 10000)
            return;
        List list = new ArrayList&lt;Integer&gt;(100000);
        // 对list进行处理
        a(j);
    }
}</pre>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 也没啥特殊的，就是将循环换成了递归，a方法做的事情没变。两个都跑一下，程序1顺利结束，程序2出问题了，啥问题？如下：</p>
<pre name="code" class="java">161
162
163
164
165
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.ArrayList.&lt;init&gt;(Unknown Source)
    at TailRecursionTest2.a(TailRecursionTest2.java:17)
    at TailRecursionTest2.a(TailRecursionTest2.java:20)
    at TailRecursionTest2.a(TailRecursionTest2.java:20)
    at TailRecursionTest2.a(TailRecursionTest2.java:20)
    at TailRecursionTest2.a(TailRecursionTest2.java:20)</pre>
<p>&nbsp;&nbsp;&nbsp;
我倒，才运行166次了，heap就满了。问题在哪呢？oh,yep，你肯定想到了，是不是重复创建list这个大集合引起的呢？它不是局部变量吗？怎么
也会溢出？是的，list是局部变量，在a的方法栈里引用着，指向heap上的大对象，更关键的问题在于，java是没有尾递归优化的，递归方法是不会使
用同一个栈帧，每一次递归调用，都将压入新的栈帧，并且这个栈帧上又new了一个list变量，引用着heap上新的一个大集合。随着栈深度的增加，
jvm里维持着一条长长的方法调用轨迹以便你能回来，在方法没有返回之前，这些list变量一直被各自的栈帧引用着，不能被GC，你说，能不OOM吗？</p>
<p>
&nbsp;&nbsp;&nbsp; 也许，你想到了个补救方法来挽救程序2，就是每次在处理完list后，我把它设置为null，不让栈帧继续引用着它，咱编写对gc友好的代码，这不就行了，试试：</p>
<pre name="code" class="java">import java.util.ArrayList;
import java.util.List;

public class TailRecursionTest2 {
    public static void main(String[] args) {
        TailRecursionTest2 t = new TailRecursionTest2();
        t.a(0);
    }

    public void a(int j) {
        System.out.println(j);
        j++;
        if (j == 10000)
            return;
        List list = new ArrayList&lt;Integer&gt;(100000);
        // 对list进行处理
        list = null;  //gc友好
        a(j);
    }
}</pre>
&nbsp;
<p>
&nbsp;&nbsp;&nbsp; 得意洋洋，我跑一下看看，这次跑到4000多次，但是：</p>
<pre name="code" class="java">......
4289
4290
4291
4292
java.lang.StackOverflowError
    at sun.nio.cs.ext.DoubleByteEncoder.encodeArrayLoop(Unknown Source)
    at sun.nio.cs.ext.DoubleByteEncoder.encodeLoop(Unknown Source)
    at java.nio.charset.CharsetEncoder.encode(Unknown Source)
</pre>
&nbsp;
<p>
&nbsp;&nbsp;&nbsp; 没办法啊，人家sun的jdk就是不支持尾递归优化(据说传闻在jdk5的某个版本是有尾递归优化的），很不给你面子的栈溢出了。ibm的jdk据说支持尾递归优化，上面这个程序在ibm的jdk上可能可以正常结束，未经测试。<br />
<br />
总结：在java里，递归最好咱还是别用，老老实实地while、for；就算递归了，最好递归方法不要new太大的对象，除非你能确定递归的深度不是那么大，否则OOM和堆栈溢出的阴影将笼罩着你。</p>
          <br/>
          <span style="color:red;">
            <a href="http://dennis-zane.javaeye.com/blog/198999#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sat, 31 May 2008 17:36:19 +0800</pubDate>
        <link>http://dennis-zane.javaeye.com/blog/198999</link>
        <guid>http://dennis-zane.javaeye.com/blog/198999</guid>
      </item>
      <item>
        <title>Logic Programming With Prolog学习笔记（二）</title>
        <author>dennis_zane</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://dennis-zane.javaeye.com/blog/198101" style="color:red;">http://dennis-zane.javaeye.com/blog/198101</a>&nbsp;
          发表时间: 2008年05月29日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp;</p>
<p class="western" id="f33v899" style="margin-bottom: 0in;"><span style="font-family: 宋体,SimSun;"><span id="f33v901" lang="zh-CN">第六章：循环</span>
</span>
</p>
<p class="western" id="f33v902" style="margin-bottom: 0in;">1<span style="font-family: 宋体,SimSun;"><span id="f33v904" lang="zh-CN">、一定次数的循环，看代码，与</span>
</span>
<a href="http://www.erlang.org/">Erlang</a>
<span style="font-family: 宋体,SimSun;"><span id="f33v906" lang="zh-CN">一模一样：</span>
</span>
</p>
<p class="western" id="f33v907" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">loop(0).</span>
</span>
</p>
<p class="western" id="f33v910" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">loop(N):-N&gt;0,write('The
value is: '),write(N),nl,</span>
</span>
</p>
<p class="western" id="f33v913" style="text-indent: 0.14in; margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">M
is N-1,loop(M).</span>
</span>
</p>
<p class="western" id="f33v916" style="margin-bottom: 0in;"><span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v919" lang="zh-CN">再看一个例子：</span>
</span>
</span>
</p>
<p class="western" id="f33v920" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">output_values(Last,Last):-
write(Last),nl,</span>
</span>
</p>
<p class="western" id="f33v923" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">write('end
of example'),nl.</span>
</span>
</p>
<p class="western" id="f33v926" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">output_values(First,Last):-First=\=Last,write(First),</span>
</span>
</p>
<p class="western" id="f33v929" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">nl,N
is First+1,output_values(N,Last).</span>
</span>
</p>
<p class="western" id="f33v932" style="margin-bottom: 0in;">2<span style="font-family: 宋体,SimSun;"><span id="f33v934" lang="zh-CN">、循环直到条件满足：</span>
</span>
</p>
<p class="western" id="f33v935" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">go:-loop(start).
</span>
</span>
</p>
<p class="western" id="f33v938" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">loop(end).</span>
</span>
</p>
<p class="western" id="f33v941" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">loop(X):-X\=end,write('Type
end to end'),read(Word),</span>
</span>
</p>
<p class="western" id="f33v944" style="text-indent: 0.21in; margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">write('Input
was '),write(Word),nl,loop(Word).</span>
</span>
</p>
<p class="western" id="f33v947" style="margin-bottom: 0in;"><span style="font-family: 宋体,SimSun;"><span id="f33v949" lang="zh-CN">通过</span>
</span>
;/2<span style="font-family: 宋体,SimSun;"><span id="f33v951" lang="zh-CN">谓词，可以改写为：</span>
</span>
</p>
<p class="western" id="f33v952" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">loop:-write('Type
end to end'),read(Word),</span>
</span>
</p>
<p class="western" id="f33v955" align="left" style="text-indent: 0.14in; margin-bottom: 0in;">
<span style="font-family: Courier,monospace;"><span style="font-size: x-small;">write('Input was
'),write(Word),nl,</span>
</span>
</p>
<p class="western" id="f33v958" style="text-indent: 0.21in; margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">(Word=end;loop).</span>
</span>
</p>
<p class="western" id="f33v961" style="text-indent: 0.21in; margin-bottom: 0in;"><br id="f33v962" />
</p>
<p class="western" id="f33v963" style="text-indent: 0.21in; margin-bottom: 0in;"><span style="font-size: x-small;"><span style="font-family: Courier,monospace;">3</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v967" lang="zh-CN"><span style="font-size: x-small;">、使用</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">repeat</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v972" lang="zh-CN"><span style="font-size: x-small;">谓词，这个谓词名称是典型的用词不当，</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">repeat</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v977" lang="zh-CN"><span style="font-size: x-small;">并不重复任何东西，它仅仅是在任何时候执行的时候都是</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">success</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v982" lang="zh-CN"><span style="font-size: x-small;">。那么当回溯到</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">repeat</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v987" lang="zh-CN"><span style="font-size: x-small;">的时候，因为它是成功的，那么就要继续从</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">left-&gt;right</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v992" lang="zh-CN"><span style="font-size: x-small;">的求值目标，直到后续的某个目标满足为止，例如：</span>
</span>
</span>
</p>
<p class="western" id="f33v994" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">get_answer(Ans):-</span>
</span>
</p>
<p class="western" id="f33v997" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">write('Enter
answer to question'),nl,</span>
</span>
</p>
<p class="western" id="f33v1000" align="left" style="text-indent: 0.14in; margin-bottom: 0in;">
<span style="font-family: Courier,monospace;"><span style="font-size: x-small;">repeat,write('answer yes
or no'),read(Ans),</span>
</span>
</p>
<p class="western" id="f33v1003" align="left" style="text-indent: 0.14in; margin-bottom: 0in;">
<span style="font-family: Courier,monospace;"><span style="font-size: x-small;">valid(Ans),write('Answer
is '),write(Ans),nl.</span>
</span>
</p>
<p class="western" id="f33v1006" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">valid(yes).
valid(no).</span>
</span>
</p>
<p class="western" id="f33v1009" align="left" style="margin-bottom: 0in;"><span style="font-family: 宋体,SimSun;"><span id="f33v1011" lang="zh-CN"><span style="font-size: x-small;">这个程序检测输入，要求玩家必须输入</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">yes</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1016" lang="zh-CN"><span style="font-size: x-small;">或者</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">no</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1021" lang="zh-CN"><span style="font-size: x-small;">才算结束，在</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">repeat</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1026" lang="zh-CN"><span style="font-size: x-small;">到</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">valid(Ans)</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1031" lang="zh-CN"><span style="font-size: x-small;">之间，如果没有输入</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">yes</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1036" lang="zh-CN"><span style="font-size: x-small;">或者</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">no</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1041" lang="zh-CN"><span style="font-size: x-small;">，将循环多次，直到</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">valid(Ans)</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1046" lang="zh-CN"><span style="font-size: x-small;">目标被满足（也就是输入</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">yes</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1051" lang="zh-CN"><span style="font-size: x-small;">或者</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">no</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1056" lang="zh-CN"><span style="font-size: x-small;">）。回溯到</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">repeat</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1061" lang="zh-CN"><span style="font-size: x-small;">的时候，总是成功，那么就继续求值后续的目标</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">write('answer
yes or no'),read(Ans),repeat</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1066" lang="zh-CN"><span style="font-size: x-small;">左边的部分永远不会被回溯到。</span>
</span>
</span>
</p>
<p class="western" id="f33v1068" align="left" style="margin-bottom: 0in;"><br id="f33v1069" />
</p>
<p class="western" id="f33v1070" align="left" style="margin-bottom: 0in;"><span style="font-size: x-small;"><span style="font-family: Courier,monospace;">4</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1074" lang="zh-CN"><span style="font-size: x-small;">、</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">fail</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1079" lang="zh-CN"><span style="font-size: x-small;">谓词</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">,fail</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1084" lang="zh-CN"><span style="font-size: x-small;">谓词求值总是</span>
</span>
</span>
<span style="font-size: x-small;"><span style="font-family: Courier,monospace;">fail</span>
</span>
<span style="font-family: 宋体,SimSun;"><span id="f33v1089" lang="zh-CN"><span style="font-size: x-small;">，因此强迫回溯开始，例如下面的例子：</span>
</span>
</span>
</p>
<p class="western" id="f33v1091" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">dog(fido).</span>
</span>
</p>
<p class="western" id="f33v1094" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">dog(fred).</span>
</span>
</p>
<p class="western" id="f33v1097" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">dog(jonathan).</span>
</span>
</p>
<p class="western" id="f33v1100" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">all_dogs:-</span>
</span>
</p>
<p class="western" id="f33v1103" align="left" style="margin-bottom: 0in;">
<span style="font-family: Courier,monospace;"><span style="font-size: x-small;">dog(X),write(X),write('
is a dog'),nl,fail.</span>
</span>
</p>
<p class="western" id="f33v1106" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">all_dogs.</span>
</span>
</p>
<p class="western" id="f33v1109" align="left" style="margin-bottom: 0in;"><br id="f33v1110" />
</p>
<p class="western" id="f33v1111" align="left" style="margin-bottom: 0in;"><span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1114" lang="zh-CN">谓词</span>
</span>
</span>
<span style="font-family: Courier,monospace;"><span style="font-size: x-small;">all_dogs</span>
</span>
<span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1119" lang="zh-CN">用于查询数据库中所有的</span>
</span>
</span>
<span style="font-family: Courier,monospace;"><span style="font-size: x-small;">dog</span>
</span>
<span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1124" lang="zh-CN">，注意，最后的</span>
</span>
</span>
<span style="font-family: Courier,monospace;"><span style="font-size: x-small;">all_dogs.</span>
</span>
<span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1129" lang="zh-CN">必须存在，不然</span>
</span>
</span>
<span style="font-family: Courier,monospace;"><span style="font-size: x-small;">all_dogs.</span>
</span>
<span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1134" lang="zh-CN">在查找完所有的</span>
</span>
</span>
<span style="font-family: Courier,monospace;"><span style="font-size: x-small;">dog</span>
</span>
<span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1139" lang="zh-CN">之后将总是</span>
</span>
</span>
<span style="font-family: Courier,monospace;"><span style="font-size: x-small;">fail</span>
</span>
<span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1144" lang="zh-CN">。</span>
</span>
</span>
</p>
<p class="western" id="f33v1145" align="left" style="margin-bottom: 0in;"><br id="f33v1146" />
</p>
<p class="western" id="f33v1147" align="left" style="margin-bottom: 0in;"><span style="font-family: 宋体,SimSun;"><span id="f33v1149" lang="zh-CN"><span style="font-size: x-small;">第六章：预防回溯</span>
</span>
</span>
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><span style="font-family: Courier,monospace;"><span style="font-size: x-small;">1</span>
</span>
<span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1156" lang="zh-CN">、</span>
</span>
</span>
<span style="font-family: Courier,monospace;"><span style="font-size: x-small;">cut</span>
</span>
<span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1161" lang="zh-CN">谓词：用于中止回溯，也可用!号表示。例如下面的例子：</span>
</span>
</span>
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1161" lang="zh-CN">classify(0,zero).<br id="f1c90" />
classify(N,negative):-N&lt;0.<br id="f1c91" />
classify(N,positive).</span>
</span>
</span>
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><br id="f1c92" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1161" lang="zh-CN">用于检验某个数是正、负或者零。执行：</span>
</span>
</span>
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1161" lang="zh-CN">classify(-4,X).<br id="tzb10" />
X = negative ;<br id="tzb11" />
X = positive</span>
</span>
</span>
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1161" lang="zh-CN">由于不能中止回溯，当</span>
</span>
</span>
<span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1161" lang="zh-CN">classify(N,negative):-N&lt;0.执行后，后续的也将执行，当然，你可以修改为：</span>
</span>
</span>
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1161" lang="zh-CN">classify(0,zero).<br id="f1c90" />
classify(N,negative):-N&lt;0.<br id="f1c91" />
classify(N,positive):-N&gt;0.</span>
</span>
</span>
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1161" lang="zh-CN">如果用cut谓词更好：</span>
</span>
</span>
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><span style="font-family: 宋体,SimSun;"><span style="font-size: x-small;"><span id="f33v1161" lang="zh-CN">classify(0,zero):-!.<br id="byn20" />
classify(N,negative):-N&lt;0,!.<br id="byn21" />
classify(N,positive).</span>
</span>
</span>
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">尽管一些程序可以不通过cut谓词进行修改，但是有一些程序（特别是当一个谓词调用另一个谓词的时候）却是不得不借住cut谓词来中止回溯，才能实现正确的行为。</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">cut的另一个用途就是确定通常情况下以外的异常，与fail搭配使用，我们知道fail强迫回溯开始</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">例如有以下事实：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">bird(sparrow).<br id="z:u60" />
bird(eagle).<br id="z:u61" />
bird(duck).<br id="z:u62" />
bird(crow).<br id="z:u63" />
bird(ostrich).<br id="z:u64" />
bird(puffin).<br id="z:u65" />
bird(swan).<br id="z:u66" />
bird(albatross).<br id="z:u67" />
bird(starling).<br id="z:u68" />
bird(owl).<br id="z:u69" />
bird(kingfisher).<br id="z:u610" />
bird(thrush).</p>
<p>
<br id="z:u611" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><br id="z:u612" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">假设ostrich不能fly，我们的can_fly谓词可能实现为：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">can_fly(ostrich):-fail.<br id="iqmj0" />
can_fly(X):-bird(X).</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">但是由于fail强制回溯，那么can_fly(ostrich).还是成功，怎么办呢？用cut：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">can_fly(ostrich):-!,fail.<br id="syyc0" />
can_fly(X):-bird(X).</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">cut中止了回溯。</p>
<p>
<br id="si660" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><br id="si661" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">第8章：改变Prolog数据库</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">1、改变数据库：加入和删除语句</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">如果删除和加入语句仅仅靠consult和reconsult谓词是低效，因此Prolog提供了BIPs用于删除或者增加数据库中的语句。</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">如果一个谓词可以被assertz, retract等BIPs修改，那么它必须声明是动态的，否则Prolog将报错。动态声明必须放在谓词声明的前面，最好放在整个程序的前面，声明方式如下：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">dynamic(mypred/3).</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">这就将mypred/3谓词声明为动态，可用BIPs进行增删了。</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">1）增加语句，通过谓词assertz/1和asserta/1，两者的区别在于：前者将语句加入相应谓词的后面，而后者将语句加入相应谓词的开始处。例如：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">?-assertz(dog(fido)).<br id="ed0y0" />
?-assertz((go:-write('hello world'),nl)).</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">?-assertz(dog(X)).<br id="wl8_0" />
?-assertz((go(X):-write('hello '),write(X),nl)).</p>
<p>
<br id="wl8_1" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><br id="wl8_2" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">2）
删除语句，也有两个谓词：retract/1和retractall/1，两者的区别在于：前者接受一个参数，并且是一条语句，删除数据库中第一条与该语
句匹配的语句；后者仅接受语句的head部分，用于删除所有的满足该head的语句。例如，假设数据库中有如下语句：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">dog(jim).<br id="leg70" />
dog(fido).<br id="leg71" />
dog(X).</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">执行</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">?-retract(dog(fido)).</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">删除数据库中的第2条语句，执行</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">?-retract(dog(X)).</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">却是删除dog(jim).因为这是第一条与(dog(X)匹配的语句，而最后的dog(X).反而得到保留。</p>
<p>
<br id="uz3a0" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><br id="uz3a1" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">retractall(mypred(_,_,_)).删除所有的mypred/3谓词语句。</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">retractall(parent(john,Y)).删除所有的第一个参数的john的parent/2语句。</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">retractall(mypred).删除所有的mypred/0谓词。</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><br id="x35l0" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">2、维护事实库，利用文件读写IO谓词，和本章介绍的增删谓词，就用文本文件维护事实库了，具体例子不说了。</p>
<p>
<br id="g4050" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><br id="g4051" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">第9章：列表处理<br id="duk90" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">1、list在Prolog中是以[]包括的，以,号隔开的term组成，例如[a,b,c,d]，空列表就是[]。了解过Erlang或者scheme的朋友，应该对列表很熟悉。Erlang中的列表与Prolog中的列表概念一脉相承。</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">2、这一章，真没啥好细谈的，列几个BIPs吧</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">1）member，判断元素是否在列表中</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">?- member(a,[a,b,c]).<br id="cj1o0" />
yes</p>
<p>
?- member(mypred(a,b,c),[q,r,s,mypred(a,b,c),w]).<br id="bl9j0" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">yes</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">如果member的第一个参数是未绑定的变量，那么该变量将从左到右依次绑定列表中的元素。<br id="bl9j1" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">2）length谓词，确定列表长度，第2个参数如果是变量，将变量绑定为列表参数，如果是数字，就将该数字与长度比较，相等则success，否则fail。</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">?- length([a,b,c,d],X).<br id="bqop0" />
X = 4</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">?- length([a,b,c],3).<br id="bqop1" />
yes</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">?- length([a,b,c],4).<br id="bqop2" />
no</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">3）reverse谓词，如果两个变量都是list，就判断是否互相倒序，如果一个是变量，一个是list，就将变量绑定为list的倒序：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">?- reverse([1,2,3,4],L).<br id="zc7z0" />
L = [4,3,2,1]<br id="zc7z1" />
?- reverse(L,[1,2,3,4]).<br id="zc7z2" />
L = [4,3,2,1]</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">?- reverse([1,2,3,4],[4,3,2,1]).<br id="zc7z3" />
yes</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">4）append谓词，三个参数，如果前两个是list，第三个为变量，那么将变量绑定为两个list合并连接的列表：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">?- append([1,2,3,4],[5,6,7,8,9],L).<br id="zz5m0" />
L = [1,2,3,4,5,6,7,8,9]<br id="zz5m1" />
?- append([],[1,2,3],L).<br id="zz5m2" />
L = [1,2,3]</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><br id="zz5m3" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">如果前两个参数包括变量，第三个是列表，那么将回溯寻找所有可能的列表组合：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">?- append(L1,L2,[1,2,3,4,5]).<br id="qvm40" />
L1 = [] ,<br id="qvm41" />
L2 = [1,2,3,4,5] ;<br id="qvm42" />
L1 = [1] ,<br id="qvm43" />
L2 = [2,3,4,5] ;<br id="qvm44" />
L1 = [1,2] ,<br id="qvm45" />
L2 = [3,4,5] ;<br id="qvm46" />
L1 = [1,2,3] ,<br id="qvm47" />
L2 = [4,5] ;<br id="qvm48" />
L1 = [1,2,3,4] ,<br id="qvm49" />
L2 = [5] ;<br id="qvm410" />
L1 = [1,2,3,4,5] ,<br id="qvm411" />
L2 =[] ;</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">no</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><br id="kdag0" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">5）
findall/3谓词比较有趣，有点类似select的概念，它有三个参数，第一个参数是一个变量或者带变量的表达式，用于确定想要find并且
collect的元素结构，第二个参数是一个goal，用于执行数据库中是否有匹配项，第三个参数是变量，用于绑定最后收集到的匹配的元素列表，例子：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">假设我们已经如下事实：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">person(john,smith,45,london).<br id="qy300" />
person(mary,jones,28,edinburgh).<br id="qy301" />
person(michael,wilson,62,bristol).<br id="qy302" />
person(mark,smith,37,cardiff).<br id="qy303" />
person(henry,roberts,23,london).</p>
<p>
<br id="qy304" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><br id="qy305" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">那么执行：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">findall(S,person(_,S,_,_),L).</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">将返回：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">L = [smith,jones,wilson,smith,roberts]</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">L收集了所有person的姓。如果执行：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">?- findall([Forename,Surname],person(Forename,Surname,_,_),L).</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><br id="aoml0" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">将返回所有person的姓名组成的列表的列表：</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">L = [[john,smith],[mary,jones],[michael,wilson],[mark,smith],[henry,roberts]]</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;"><br id="nmzh0" />
</p>
<p class="western" id="f33v1151" align="left" style="margin-bottom: 0in;">这是个非常有用的谓词。</p>
<p>
<br id="mh740" />
</p>
<p class="western" id="f33v1151" align="left" style="ma