﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-yeshucheng-随笔分类-分布式</title><link>http://www.blogjava.net/yeshucheng/category/34003.html</link><description>追逐自己，追逐方向，心随悟所动</description><language>zh-cn</language><lastBuildDate>Tue, 24 Aug 2010 21:13:52 GMT</lastBuildDate><pubDate>Tue, 24 Aug 2010 21:13:52 GMT</pubDate><ttl>60</ttl><item><title>[转载]memcached server LRU 深入分析</title><link>http://www.blogjava.net/yeshucheng/archive/2010/08/17/329128.html</link><dc:creator>叶澍成</dc:creator><author>叶澍成</author><pubDate>Tue, 17 Aug 2010 09:45:00 GMT</pubDate><guid>http://www.blogjava.net/yeshucheng/archive/2010/08/17/329128.html</guid><description><![CDATA[Memcached，人所皆知的remote distribute cache（不知道的可以javaeye一下下，或者google一下下，或者baidu一下下，但是鉴于baidu的排名商业味道太浓（从最近得某某事件可以看出），所以还是建议javaeye一下下），使用起来也非常的简单，它被用在了很多网站上面，几乎很少有大型的网站不会使用memcached。 <br />
<br />
曾经我也看过很多剖析memcached内部机制的文章，有一点收获，但是看过之后又忘记了，而且没有什么深刻的概念，但是最近我遇到一个问题，这个问题迫使我重新来认识memcache，下面我阐述一下我遇到的问题 <br />
<br />
问题：我有几千万的数据，这些数据会经常被用到，目前来看，它必须要放到memcached中，以保证访问速度，但是我的memcached中数据经常会有丢失，而业务需求是memcached中的数据是不能丢失的。我的数据丢失的时候，memcached server的内存才使用到60%，也就是还有40%内存被严重的浪费掉了。但不是所有的应用都是这样，其他应用内存浪费的就比较少。为什么内存才使用到60%的时候LRU就执行了呢（之所以确定是LRU执行是因为我发现我的数据丢失的总是前面放进去的，而且这个过程中，这些数据都没有被访问，比如第一次访问的时候，只能访问第1000w条，而第300w条或者之前的数据都已经丢失了，从日志里看，第300w条肯定是放进去了）。 <br />
<br />
带着这些疑问，我开始重新审视memcached这个产品，首先从它的内存模型开始：我们知道c++里分配内存有两种方式，预先分配和动态分配，显然，预先分配内存会使程序比较快，但是它的缺点是不能有效利用内存，而动态分配可以有效利用内存，但是会使程序运行效率下降，memcached的内存分配就是基于以上原理，显然为了获得更快的速度，有时候我们不得不以空间换时间。 <br />
<br />
也就是说memcached会预先分配内存，对了，memcached分配内存方式称之为allocator，首先，这里有3个概念： <br />
1 slab <br />
2 page <br />
3 chunk <br />
解释一下，一般来说一个memcahced进程会预先将自己划分为若干个slab，每个slab下又有若干个page，每个page下又有多个chunk，如果我们把这3个咚咚看作是object得话，这是两个一对多得关系。再一般来说，slab得数量是有限得，几个，十几个，或者几十个，这个跟进程配置得内存有关。而每个slab下得page默认情况是1m，也就是说如果一个slab占用100m得内存得话，那么默认情况下这个slab所拥有得page得个数就是100，而chunk就是我们得数据存放得最终地方。 <br />
<br />
举一个例子，我启动一个memcached进程，占用内存100m，再打开telnet，telnet localhost 11211，连接上memcache之后，输入stats&nbsp; slabs，回车，出现如下数据： <br />
<div class="dp-highlighter">
<div class="bar">
<div class="tools">Java代码 <a title="复制代码" onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://www.javaeye.com/topic/225692#"><img alt="复制代码" src="http://www.javaeye.com/images/icon_copy.gif" /></a></div>
</div>
<ol class="dp-j">
    <li><span><span>STAT&nbsp;</span><span class="number">1</span><span>:chunk_size&nbsp;</span><span class="number">80</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">1</span><span>:chunks_per_page&nbsp;</span><span class="number">13107</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">1</span><span>:total_pages&nbsp;</span><span class="number">1</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">1</span><span>:total_chunks&nbsp;</span><span class="number">13107</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">1</span><span>:used_chunks&nbsp;</span><span class="number">13107</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">1</span><span>:free_chunks&nbsp;</span><span class="number">0</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">1</span><span>:free_chunks_end&nbsp;</span><span class="number">13107</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">2</span><span>:chunk_size&nbsp;</span><span class="number">100</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">2</span><span>:chunks_per_page&nbsp;</span><span class="number">10485</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">2</span><span>:total_pages&nbsp;</span><span class="number">1</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">2</span><span>:total_chunks&nbsp;</span><span class="number">10485</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">2</span><span>:used_chunks&nbsp;</span><span class="number">10485</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">2</span><span>:free_chunks&nbsp;</span><span class="number">0</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">2</span><span>:free_chunks_end&nbsp;</span><span class="number">10485</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">3</span><span>:chunk_size&nbsp;</span><span class="number">128</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">3</span><span>:chunks_per_page&nbsp;</span><span class="number">8192</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">3</span><span>:total_pages&nbsp;</span><span class="number">1</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">3</span><span>:total_chunks&nbsp;</span><span class="number">8192</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">3</span><span>:used_chunks&nbsp;</span><span class="number">8192</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">3</span><span>:free_chunks&nbsp;</span><span class="number">0</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;</span><span class="number">3</span><span>:free_chunks_end&nbsp;</span><span class="number">8192</span><span>&nbsp;&nbsp;</span></span></li>
</ol>
</div>
<pre class="java" style="display: none" name="code">STAT 1:chunk_size 80
STAT 1:chunks_per_page 13107
STAT 1:total_pages 1
STAT 1:total_chunks 13107
STAT 1:used_chunks 13107
STAT 1:free_chunks 0
STAT 1:free_chunks_end 13107
STAT 2:chunk_size 100
STAT 2:chunks_per_page 10485
STAT 2:total_pages 1
STAT 2:total_chunks 10485
STAT 2:used_chunks 10485
STAT 2:free_chunks 0
STAT 2:free_chunks_end 10485
STAT 3:chunk_size 128
STAT 3:chunks_per_page 8192
STAT 3:total_pages 1
STAT 3:total_chunks 8192
STAT 3:used_chunks 8192
STAT 3:free_chunks 0
STAT 3:free_chunks_end 8192</pre>
<br />
<br />
以上就是前3个slab得详细信息 <br />
chunk_size表示数据存放块得大小，chunks_per_page表示一个内存页page中拥有得chunk得数量，total_pages表示每个slab下page得个数。total_chunks表示这个slab下chunk得总数（＝total_pages * chunks_per_page），used_chunks表示该slab下已经使用得chunk得数量，free_chunks表示该slab下还可以使用得chunks数量。 <br />
<br />
从上面得示例slab 1一共有1m得内存空间，而且现在已经被用完了，slab2也有1m得内存空间，也被用完了，slab3得情况依然如此。 而且从这3个slab中chunk得size可以看出来，第一个chunk为80b，第二个是100b，第3个是128b，基本上后一个是前一个得1.25倍，但是这个增长情况我们是可以控制得，我们可以通过在启动时得进程参数 &#8211;f来修改这个值，比如说 &#8211;f 1.1表示这个增长因子为1.1，那么第一个slab中得chunk为80b得话，第二个slab中得chunk应该是80*1.1左右。 <br />
<br />
解释了这么多也该可以看出来我遇到得问题得原因了，如果还看不出来，那我再补充关键的一句：memcached中新的value过来存放的地址是该value的大小决定的，value总是会被选择存放到chunk与其最接近的一个slab中，比如上面的例子，如果我的value是80b，那么我这所有的value总是会被存放到1号slab中，而1号slab中的free_chunks已经是0了，怎么办呢，如果你在启动memcached的时候没有追加-M（禁止LRU，这种情况下内存不够时会out of memory），那么memcached会把这个slab中最近最少被使用的chunk中的数据清掉，然后放上最新的数据。这就解释了为什么我的内存还有40%的时候LRU就执行了，因为我的其他slab中的chunk_size都远大于我的value，所以我的value根本不会放到那几个slab中，而只会放到和我的value最接近的chunk所在的slab中(而这些slab早就满了，郁闷了)。这就导致了我的数据被不停的覆盖，后者覆盖前者。 <br />
<br />
问题找到了，解决方案还是没有找到，因为我的数据必须要求命中率时100%，我只能通过调整slab的增长因子和page的大小来尽量来使命中率接近100%，但是并不能100%保证命中率是100%（这话怎么读起来这么别扭呢，自我检讨一下自己的语文水平），如果您说，这种方案不行啊，因为我的memcached server不能停啊，不要紧还有另外一个方法，就是memcached-tool，执行move命令，如：move 3 1，代表把3号slab中的一个内存页移动到1号slab中，有人问了，这有什么用呢，比如说我的20号slab的利用率非常低，但是page却又很多，比如200，那么就是200m，而2好slab经常发生LRU，明显page不够，我就可以move 20 2，把20号slab的一个内存页移动到2号slab上，这样就能更加有效的利用内存了（有人说了，一次只移动一个page，多麻烦啊？ahuaxuan说，还是写个脚本，循环一下吧）。 <br />
<br />
有人说不行啊，我的memcache中的数据不能丢失啊，ok，试试新浪的memcachedb吧，虽然我没有用过，但是建议大家可以试试，它也使利用memcache协议和berkeleyDB做的（写到这里，我不得不佩服danga了，我觉得它最大的贡献不是memcache server本身，而是memcache协议），据说它被用在新浪的不少应用上，包括新浪的博客。 <br />
<br />
补充，stats slab命令可以查看memcached中slab的情况，而stats命令可以查看你的memcached的一些健康情况，比如说命中率之类的，示例如下： <br />
<div class="dp-highlighter">
<div class="bar">
<div class="tools">Java代码 <a title="复制代码" onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://www.javaeye.com/topic/225692#"><img alt="复制代码" src="http://www.javaeye.com/images/icon_copy.gif" /></a></div>
</div>
<ol class="dp-j">
    <li><span><span>STAT&nbsp;pid&nbsp;</span><span class="number">2232</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;uptime&nbsp;</span><span class="number">1348</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;time&nbsp;</span><span class="number">1218120955</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;version&nbsp;</span><span class="number">1.2</span><span>.</span><span class="number">1</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;pointer_size&nbsp;</span><span class="number">32</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;curr_items&nbsp;</span><span class="number">0</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;total_items&nbsp;</span><span class="number">0</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;bytes&nbsp;</span><span class="number">0</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;curr_connections&nbsp;</span><span class="number">1</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;total_connections&nbsp;</span><span class="number">3</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;connection_structures&nbsp;</span><span class="number">2</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;cmd_get&nbsp;</span><span class="number">0</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;cmd_set&nbsp;</span><span class="number">0</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;get_hits&nbsp;</span><span class="number">0</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;get_misses&nbsp;</span><span class="number">0</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;bytes_read&nbsp;</span><span class="number">26</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;bytes_written&nbsp;</span><span class="number">16655</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>STAT&nbsp;limit_maxbytes&nbsp;</span><span class="number">104857600</span><span>&nbsp;&nbsp;</span></span></li>
</ol>
</div>
<pre class="java" style="display: none" name="code">STAT pid 2232
STAT uptime 1348
STAT time 1218120955
STAT version 1.2.1
STAT pointer_size 32
STAT curr_items 0
STAT total_items 0
STAT bytes 0
STAT curr_connections 1
STAT total_connections 3
STAT connection_structures 2
STAT cmd_get 0
STAT cmd_set 0
STAT get_hits 0
STAT get_misses 0
STAT bytes_read 26
STAT bytes_written 16655
STAT limit_maxbytes 104857600</pre>
<br />
从上面的数据可以看到这个memcached进程的命中率很好，get_misses低达0个，怎么回事啊，因为这个进程使我刚启动的，我只用telnet连了一下，所以curr_connections为1，而total_items为0，因为我没有放数据进去，get_hits为0，因为我没有调用get方法，最后的结果就是misses当然为0，哇哦，换句话说命中率就是100%，又yy了。 <br />
<br />
该到总结的时候了，从这篇文章里我们可以得到以下几个结论： <br />
结论一，memcached得LRU不是全局的，而是针对slab的，可以说是区域性的。 <br />
结论二，要提高memcached的命中率，预估我们的value大小并且适当的调整内存页大小和增长因子是必须的。 <br />
结论三，带着问题找答案理解的要比随便看看的效果好得多。 <br />
<img src ="http://www.blogjava.net/yeshucheng/aggbug/329128.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yeshucheng/" target="_blank">叶澍成</a> 2010-08-17 17:45 <a href="http://www.blogjava.net/yeshucheng/archive/2010/08/17/329128.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>从套接字衍生到RMI代码思路</title><link>http://www.blogjava.net/yeshucheng/archive/2009/02/03/253144.html</link><dc:creator>叶澍成</dc:creator><author>叶澍成</author><pubDate>Tue, 03 Feb 2009 13:56:00 GMT</pubDate><guid>http://www.blogjava.net/yeshucheng/archive/2009/02/03/253144.html</guid><wfw:comment>http://www.blogjava.net/yeshucheng/comments/253144.html</wfw:comment><comments>http://www.blogjava.net/yeshucheng/archive/2009/02/03/253144.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.blogjava.net/yeshucheng/comments/commentRss/253144.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yeshucheng/services/trackbacks/253144.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在上篇blog中谈到RMI的问世由来只是大致的把一些概念结构说明了下，自己静静想想要有好的说明干脆用代码说明比较妥当也最为有说明性。事后自己倒腾了一个简单的代码DEMO。代码中有个简单的场景，比如你是属于某地区医保范围内的成员，到医院看病，这个候医院为了审核你的相关个人资料需要到医保管理部门调阅信息，你只需要给出用户名称或者其他一个有...&nbsp;&nbsp;<a href='http://www.blogjava.net/yeshucheng/archive/2009/02/03/253144.html'>阅读全文</a><img src ="http://www.blogjava.net/yeshucheng/aggbug/253144.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yeshucheng/" target="_blank">叶澍成</a> 2009-02-03 21:56 <a href="http://www.blogjava.net/yeshucheng/archive/2009/02/03/253144.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>RMI的原理和实现</title><link>http://www.blogjava.net/yeshucheng/archive/2009/02/02/252932.html</link><dc:creator>叶澍成</dc:creator><author>叶澍成</author><pubDate>Mon, 02 Feb 2009 04:04:00 GMT</pubDate><guid>http://www.blogjava.net/yeshucheng/archive/2009/02/02/252932.html</guid><wfw:comment>http://www.blogjava.net/yeshucheng/comments/252932.html</wfw:comment><comments>http://www.blogjava.net/yeshucheng/archive/2009/02/02/252932.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.blogjava.net/yeshucheng/comments/commentRss/252932.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yeshucheng/services/trackbacks/252932.html</trackback:ping><description><![CDATA[<p>&nbsp;</p>
&nbsp;
<h2><span style="font-family: 黑体">综述</span></h2>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Rmi<span style="font-family: 宋体">自从</span>JDK1.1<span style="font-family: 宋体">就已经出现了。而对于为什么在</span>JAVA<span style="font-family: 宋体">的世界里需要一个这样</span> <span style="font-family: 宋体">思想理念就需要看下：</span><a href="http://www.blogjava.net/yeshucheng/archive/2009/02/02/252931.html">RMI<span style="font-family: 宋体">问世由来</a></span><span style="font-family: 宋体">。其实真正在国内使用到它的比较少，不过在前些年比较火的</span>EJB<span style="font-family: 宋体">就是在它的基础上进一步深化的。从本质上来讲</span>RMI<span style="font-family: 宋体">的兴起正是为了设计分布式的客户、服务器结构需求而应运而生的，而它的这种</span>B/S<span style="font-family: 宋体">结构思想能否和我们通常的</span>JAVA<span style="font-family: 宋体">编程更加贴切呢？言外之意就是能否让这种分布式的状态做到更加透明，作为开发人员只需要按照往常一样开发</span>JAVA<span style="font-family: 宋体">应用程序一样来开发分布式的结构。那现在的问题是如何来划平这个鸿沟呢？首先我们来分析下在</span>JAVA<span style="font-family: 宋体">世界里它的一些特点因素：</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="font-family: Wingdings">l<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>JAVA<span style="font-family: 宋体">使用垃圾收集确定对象的生命周期。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="font-family: Wingdings">l<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>JAVA<span style="font-family: 宋体">使用异常处理来报告运行期间的错误。这里就要和我们网络通讯中的异常相联系起来了。在</span>B/S<span style="font-family: 宋体">结构的网络体系中我们的这种错误性是非常常见的。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="font-family: Wingdings">l<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>JAVA<span style="font-family: 宋体">编写的对象通过调用方法来调用。由于网络通讯把我们的客户与服务器之间阻隔开了。但是代理的一种方式可以很好的提供一种这样的假象，让开发人员或者使用者都感觉是在本地调用。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="font-family: Wingdings">l<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>JAVA<span style="font-family: 宋体">允许一种高级的使用类加载器（</span>CLassLoader<span style="font-family: 宋体">）机制提供系统类路径中没有的类。这话什么意思？</span></p>
<h2><span style="font-family: 黑体">主要特点</span></h2>
<p style="margin-left: 21pt"><span style="font-family: 宋体">上面说到了分布式的方式和我们的</span>JAVA<span style="font-family: 宋体">中如何更好的划平这个鸿沟，需要具备的特质。</span></p>
<p><span style="font-family: 宋体">那这里我们来看看我们所谓的</span>RMI<span style="font-family: 宋体">到底跟我们普通的</span>JAVA<span style="font-family: 宋体">（或者说</span>JavaBean<span style="font-family: 宋体">）存在一些什么样的差异：</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="font-family: Wingdings">l<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><strong>RMI</strong><strong><span style="font-family: 宋体">远程异常（</span>Remote Exception</strong><strong><span style="font-family: 宋体">）：</span></strong><span style="font-family: 宋体">在上面我们也提到了一个网络通讯难免有一些无论是软件级别的还是硬件级别的异常现象，有时候这些异常或许是一种无法预知的结果。让我们开发人缘如何来回溯这种异常信息，这个是我们开发人员要关心的。因此在调用远程对象的方法中我们必须在远程接口中（接口是一种规范的标准行为）所以在调用的这个方法体上需要签名注明：</span>java.rmi,RemoteException.<span style="font-family: 宋体">。这也就注明了此方法是需要调用远程对象的。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="font-family: Wingdings">l<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><strong><span style="font-family: 宋体">值传递</span> </strong><span style="font-family: 宋体">：当把对象作为参数传递给一个普通的</span>JAVA<span style="font-family: 宋体">对象方法调用时，只是传递该对象的<strong><em>引用</em></strong>。请注意这里谈到的是对象的&#8220;引用&#8221;一词，如果在修改该参数的时候，是直接修改原始对象。它并不是所谓的一个对象的备份或者说拷贝（说白了就是在本</span>JVM<span style="font-family: 宋体">内存中的对象）。但是如果说使用的是</span>RMI<span style="font-family: 宋体">对象，则完全是拷贝的。这与普通对象有着鲜明的对比。也正是由于这种拷贝的资源消耗造就了下面要说到的性能缺失了。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="font-family: Wingdings">l<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><strong><span style="font-family: 宋体">调用开销</span></strong><span style="font-family: 宋体">：凡是经过网络通讯理论上来说都是一种资源的消耗。它需要通过编组与反编组方式不断解析类对象。而且</span>RMI<span style="font-family: 宋体">本身也是一种需要返回值的一个过程定义。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="font-family: Wingdings">l<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><strong><span style="font-family: 宋体">安全性</span></strong><span style="font-family: 宋体">：一谈到网络通讯势必会说到如何保证安全的进行。</span></p>
<p><strong>&nbsp;</strong></p>
<h2><span style="font-family: 黑体">概念定义</span></h2>
<p style="text-indent: 21pt"><span style="font-family: 宋体">在开始进行原理梳理之前我们需要定义清楚几个名词。对于这些名词的理解影响到后的深入进行。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt">1.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><strong>Stub</strong><span style="font-family: 宋体">（存根，有些书上也翻译成：桩基在</span>EJB<span style="font-family: 宋体">的相关书籍中尤为体现这个意思）：</span></p>
<p style="text-indent: 21pt"><span style="font-family: 宋体">这里举例说明这个概念起（或许不够恰当）。例如大家因公出差后，都有存在一些报销的发票或者说小票。对于你当前手头所拿到的发票并不是一个唯一的，它同时还在你发生消费的地点有一个复印件，而这个复印件就是所谓的存根。但是这个存根上并没有很多明细的描述，只是有一个大概的金额定义。它把很多的细节费用都忽略了。所以这个也是我们说的存根定义。而在我们</span>RMI<span style="font-family: 宋体">的存根定义就是使用了这样一个理解：在与远程发生通讯调用时，把通讯调用的所有细节都通过对象的封装形式给隐藏在后端。这本身就符合</span>OOAD<span style="font-family: 宋体">的意思理念。而暴露出来的就是我们的接口方式，而这种接口方式又和服务器的对象具有相同的接口（这里就和我们前面举例说的报销单据联系上了，报销单据的存根不知道会有一个什么形式发生具体问题，而你手执的发票具体就需要到贵公司去报销费用，而这里的公司财务处就是所谓的服务器端，它才是真正干实质性问题的。）因此作为开发人员只需要把精力集中在业务问题的解决上，而不需要考虑复杂的分布式计算。所有这些问题都交给</span>RMI<span style="font-family: 宋体">去一一处理。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt">2.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><strong>Skeleton</strong>(<span style="font-family: 宋体">一些书翻译叫骨架，也叫结构体</span>)<span style="font-family: 宋体">：它的内部就是真正封装了一个类的形成调用体现机制。包括我们熟知的</span>ServerSocket<span style="font-family: 宋体">创建、接受、监听、处理等。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt">3.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><strong>Mashalling</strong>(<span style="font-family: 宋体">编组</span>)<span style="font-family: 宋体">：在内存中的对象转换成字节流，以便能够通过网络连接传输。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt">4.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><strong>Unmashalling</strong>(<span style="font-family: 宋体">反编组</span>)<span style="font-family: 宋体">：在内存中把字节流转换成对象，以便本地化调用。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt">5.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><strong>Serialization</strong>(<span style="font-family: 宋体">序列化</span>)<span style="font-family: 宋体">：编组中使用到的技术叫序列化。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt">6.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><strong>Deserializationg</strong>(<span style="font-family: 宋体">反序列化</span>)<span style="font-family: 宋体">：反编组中使用到的技术叫反序列化。</span></p>
<p><strong>&nbsp;</strong></p>
<h2><span style="font-family: 黑体">客户端</span></h2>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="font-family: 宋体">既然我们知道</span>stub<span style="font-family: 宋体">主要是以接口的方式来暴露体现，而</span>stub<span style="font-family: 宋体">主要</span> <span style="font-family: 宋体">也是以代理的方式来具体实施。那在</span>RMI<span style="font-family: 宋体">中的这种接口有哪些特性呢？（</span>Remote Interface<span style="font-family: 宋体">）</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt">1)<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: 宋体">必须扩展（</span>extends<span style="font-family: 宋体">）</span>java.rmi.Remote<span style="font-family: 宋体">接口，因为远程接口并不包含任何一个方法，而是作为一个标记出现</span><span style="font-family: 宋体">，它就是需要告诉</span>JVM<span style="font-family: 宋体">在</span>RunTime<span style="font-family: 宋体">的时候哪些是常规对象，哪些属于远程对象。通过这种标识的定义能让</span>JVM<span style="font-family: 宋体">了解类中哪些方法需要编组，通过了编组的方式才能通过网络序列化的调用；</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt">2)<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: 宋体">接口必须为</span>public<span style="font-family: 宋体">（公共），它的好处不言而喻的——能够方便的让所有人员来调用。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt">3)<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: 宋体">接口方法还需要以异常抛出（例如：</span>RemoteException<span style="font-family: 宋体">），至于它的用处我们在前面也提到这里就不再复述；</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt">4)<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: 宋体">在调用一个远程对象期间（运行期间），方法的参数和返回值都要必须是可序列化的。至于为什么需要这么做？这里的缘由不用多说大家也应该清楚了解。</span></p>
<h2><span style="font-family: 黑体">服务端</span></h2>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="font-family: 宋体">既然我们知道</span>stub<span style="font-family: 宋体">所做的事情是一个简单的代理转发动作，那我们真正要做的对象就在服务端来做了。对于使用简单的</span>RMI<span style="font-family: 宋体">我们直接去指定，但是往往一旦使用了</span>RMI<span style="font-family: 宋体">对象就存在非常多的远程方法调用，这个时候服务器端对于这么多的调用如何来判别或者说识别呢？这里就要说到的是对于</span>RMI<span style="font-family: 宋体">实现它会创建一个标识符，以便以后的</span>stub<span style="font-family: 宋体">可以调用转发给服务器对象使用了，而这种方式我们通常叫服务器</span>RMI<span style="font-family: 宋体">的注册机制。言外之意就是让服务器端的对象注册在</span>RMI<span style="font-family: 宋体">机制中，然后可以导出让今后的</span>stub<span style="font-family: 宋体">按需来调用。那它又是如何做到这种方式的呢？对于</span>RMI<span style="font-family: 宋体">来说有两种方式可以达到这种效果：</span></p>
<p style="margin-left: 41.95pt; text-indent: -21pt; tab-stops: list 41.95pt">a)<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: 宋体">直接使用</span>UnicastRemoteObject<span style="font-family: 宋体">的静态方法：</span>exportObject<span style="font-family: 宋体">；</span></p>
<p style="margin-left: 41.95pt; text-indent: -21pt; tab-stops: list 41.95pt">b)<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: 宋体">继承</span>UnicastRemoteObject<span style="font-family: 宋体">类则缺省的构造函数</span>exportObject<span style="font-family: 宋体">。</span></p>
<p style="text-indent: 20.95pt"><span style="font-family: 宋体">现在大家又会问他们之间又有什么区别呢？我该使用哪种方式来做呢，这不是很难做抉择吗？从一般应用场景来说区别并不是很大，但是，这里说了&#8220;但是&#8221;哦，呵呵。大家知道继承的方式是把父类所具备的所有特质都可以完好无损的继承到子类中而对于类的总老大：</span>Object<span style="font-family: 宋体">来说里面有：</span>equals()<span style="font-family: 宋体">、</span>hashCode()<span style="font-family: 宋体">、</span>toString()<span style="font-family: 宋体">等方法。这是个什么概念呢？意思就是说如果对于本地化的调用，他们两个的方法（</span>a,b<span style="font-family: 宋体">）基本区别不是很大。但是我们这里强调的</span>RMI<span style="font-family: 宋体">如果是一种分布式的特定场景，具备使用哈希表这种特性就显得尤为重要了。</span></p>
<p style="text-indent: 20.95pt"><span style="font-family: 宋体">刚才说了服务端采用什么方法行为导出对象的。那现在导出后的对象又对应会发生什么情况呢？</span></p>
<p style="text-indent: 20.95pt"><span style="font-family: 宋体">首先被导出的对象被分配一个标识符，这个标识符被保存为：</span>java.rmi.server.ObjID<span style="font-family: 宋体">对象中并被放到一个对象列表中或者一个映射中。而这里的</span>ID<span style="font-family: 宋体">是一个关键字，而远程对象则是它的一个值（说到这大家有没有觉得它原理非常像</span>HashMap<span style="font-family: 宋体">的特质呢？没错，其实就是使用了它的特性），这样它就可以很好的和前面创建的</span>stub<span style="font-family: 宋体">沟通。如果调用了静态方法</span>UnicastRemoteObject.export(Remote &#8230;)<span style="font-family: 宋体">，</span>RMI<span style="font-family: 宋体">就会选择任意一个端口号，但这只是第一调用发生在随后的</span>exportObject<span style="font-family: 宋体">每次调用都把远程对象导出到该远程对象第一被导出时使用的端口号。这样就不会产生混乱，会把先前一一导出的对象全部放入到列表中。当然如果采用的是指定端口的，则按照对应显示的调用方式使用。这里稍作强调的是一个端口可以导出任意数目的对象。</span></p>
<span style="font-size: 10.5pt; font-family: 宋体">（待续</span><span style="font-size: 10.5pt; font-family: 'Times New Roman'">&#8230;&#8230;</span><span style="font-size: 10.5pt; font-family: 宋体">）</span>
 <img src ="http://www.blogjava.net/yeshucheng/aggbug/252932.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yeshucheng/" target="_blank">叶澍成</a> 2009-02-02 12:04 <a href="http://www.blogjava.net/yeshucheng/archive/2009/02/02/252932.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>RMI问世由来</title><link>http://www.blogjava.net/yeshucheng/archive/2009/02/02/252931.html</link><dc:creator>叶澍成</dc:creator><author>叶澍成</author><pubDate>Mon, 02 Feb 2009 03:57:00 GMT</pubDate><guid>http://www.blogjava.net/yeshucheng/archive/2009/02/02/252931.html</guid><wfw:comment>http://www.blogjava.net/yeshucheng/comments/252931.html</wfw:comment><comments>http://www.blogjava.net/yeshucheng/archive/2009/02/02/252931.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/yeshucheng/comments/commentRss/252931.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yeshucheng/services/trackbacks/252931.html</trackback:ping><description><![CDATA[<span style="font-family: 宋体">&nbsp;&nbsp;&nbsp;&nbsp;大家都知道对于互联网的世界网络通讯是其本质特征。而对于一个分布式式计算来说更是如此。在它的环境中使用了客户</span>/<span style="font-family: 宋体">服务器结构特点，使用的一个核心技术就是网络通讯层。在最早的</span>OSI<span style="font-family: 宋体">的概念基础上，建立了完善具体协议层。而客户想要能够与位于其他物理层主机上的服务器通讯，需要能够想服务器发送数据，然后以某种方式获得响应。这当中就牵涉到我们熟悉的协议层面了，在这里就不再复述这些协议概念了。对于网络通讯来说我们所要了解的是最为常用的就是两种连接方式：无连接协议（</span>UDP<span style="font-family: 宋体">）、面向连接协议（</span>TCP/IP<span style="font-family: 宋体">）。</span>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="font-family: 宋体">多数网络编程库中（以</span>JAVA<span style="font-family: 宋体">为主来说明），在</span>JAVA<span style="font-family: 宋体">平台中一样的提供了这些元素。而作为面向连接协议来说使用的是套接字（</span>Socket<span style="font-family: 宋体">）进行了更进一步的抽象描述。一般我们在</span>JAVA<span style="font-family: 宋体">的网络编程中都觉得在使用套接字这块相对方便，它不需要你去更多的了解操作系统的细节以及硬件的传递处理方式。</span>TCP/IP<span style="font-family: 宋体">的所有细节之处都得到了套接字的封装使用，让程序员关注到业务层面的处理。</span></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="font-family: 宋体">对象是一种抽象思维物质，对于计算机来说它只对数字电路的存储方式能够加以识别而且在网络传输当中也是一种信号量，而这一切只有使用字节流方式传输才是真正需要做到的。所以在本地主机与远程服务器的通讯传输就在对象与字节流之间不断相互转化才是我们真正需要的人性物质与机器所需要的。（有点墨迹了，切入整体）总体来说就是需要两种方式来认定这种传输行为：编组（</span>Marshalling<span style="font-family: 宋体">）与反编组（</span>Unmarshalling<span style="font-family: 宋体">）。而这一切的手段方式才是通过：序列化（</span>Serialiazable<span style="font-family: 宋体">）与反序列化的方式不断完成。如下图所示：
<div align="center"><img height="99" alt="" src="http://www.blogjava.net/images/blogjava_net/yeshucheng/clip_image002.jpg" width="491" border="0" /></div>
</span>
<p>&nbsp;</p>
<p style="text-align: center" align="center"><span style="font-size: 9pt; font-family: 宋体">图：对象到字节再到对象的转换</span></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="font-family: 宋体">对于数据的传输本质就是上图说明的。那我们一般是如何使用套接字来构造我们这一行为呢？对于这里强调的主要是一种大致方法说明，所以只能以部分代码来说明客户端怎么来发送这个请求。</span></p>
<div style="border-right: windowtext 1pt solid; padding-right: 4pt; border-top: windowtext 1pt solid; padding-left: 4pt; background: #d9d9d9; padding-bottom: 1pt; border-left: windowtext 1pt solid; padding-top: 1pt; border-bottom: windowtext 1pt solid">
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; background: #d9d9d9; padding-bottom: 0cm; border-left: medium none; text-indent: 21pt; padding-top: 0cm; border-bottom: medium none; text-align: left" align="left"><span style="font-size: 10pt; color: black; font-family: 'Courier New'">Socket </span><span style="font-size: 10pt; color: #0000c0; font-family: 'Courier New'">socket</span><span style="font-size: 10pt; color: black; font-family: 'Courier New'">=</span><strong><span style="font-size: 10pt; color: #7f0055; font-family: 'Courier New'">new</span></strong><span style="font-size: 10pt; color: black; font-family: 'Courier New'"> Socket(</span><span style="font-size: 10pt; color: #2a00ff; font-family: 'Courier New'">"http://www.wgh.com.cn"</span><span style="font-size: 10pt; color: black; font-family: 'Courier New'">,8888);</span></p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; background: #d9d9d9; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none; text-align: left" align="left"><span style="font-size: 10pt; color: black; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp; OutputStream </span><span style="font-size: 10pt; color: #0000c0; font-family: 'Courier New'">out</span><span style="font-size: 10pt; color: black; font-family: 'Courier New'">=</span><span style="font-size: 10pt; color: #0000c0; font-family: 'Courier New'">socket</span><span style="font-size: 10pt; color: black; font-family: 'Courier New'">.getOutputStream();</span></p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; background: #d9d9d9; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none; text-align: left" align="left"><span style="font-size: 10pt; color: black; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp;</span><span style="font-size: 10pt; color: black; font-family: 'Courier New'">&nbsp;ObjectOutputStream </span><span style="font-size: 10pt; color: #0000c0; font-family: 'Courier New'">obj</span><span style="font-size: 10pt; color: black; font-family: 'Courier New'">=</span><strong><span style="font-size: 10pt; color: #7f0055; font-family: 'Courier New'">new</span></strong><span style="font-size: 10pt; color: black; font-family: 'Courier New'"> ObjectOutputStream(</span><span style="font-size: 10pt; color: #0000c0; font-family: 'Courier New'">out</span><span style="font-size: 10pt; color: black; font-family: 'Courier New'">);</span></p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; background: #d9d9d9; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none; text-align: left" align="left"><span style="font-size: 10pt; color: black; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp; obj<u>.</u>writeObject(<u>object</u>);</span></p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; background: #d9d9d9; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none; text-align: left" align="left"><span style="font-size: 10pt; color: black; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp;</span><span style="font-size: 10pt; color: black; font-family: 'Courier New'">&nbsp;InputStream </span><span style="font-size: 10pt; color: #0000c0; font-family: 'Courier New'">in</span><span style="font-size: 10pt; color: black; font-family: 'Courier New'">=</span><span style="font-size: 10pt; color: #0000c0; font-family: 'Courier New'">socket</span><span style="font-size: 10pt; color: black; font-family: 'Courier New'">.getInputStream();</span></p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; background: #d9d9d9; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none; text-align: left" align="left"><span style="font-size: 10pt; color: black; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp; ObjectInputStream </span><span style="font-size: 10pt; color: #0000c0; font-family: 'Courier New'">objIn</span><span style="font-size: 10pt; color: black; font-family: 'Courier New'">=</span><strong><span style="font-size: 10pt; color: #7f0055; font-family: 'Courier New'">new</span></strong><span style="font-size: 10pt; color: black; font-family: 'Courier New'"> ObjectInputStream(</span><span style="font-size: 10pt; color: #0000c0; font-family: 'Courier New'">in</span><span style="font-size: 10pt; color: black; font-family: 'Courier New'">);</span></p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; background: #d9d9d9; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none; text-align: left" align="left"><span style="font-size: 10pt; color: black; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp; Object objFoo=(Object)objIn.readObject();</span></p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; background: #d9d9d9; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none; text-align: left" align="left"><span style="font-size: 10pt; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp; //todo </span><span style="font-size: 10pt; font-family: 宋体">这里就是具体进行操作的相关传值参数处理了</span><span style="font-size: 10pt; font-family: 'Courier New'">&#8230;</span></p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; background: #d9d9d9; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none; text-align: left" align="left"><span style="font-size: 10pt; color: black; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp; obj.<u>close</u>();</span></p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; background: #d9d9d9; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none; text-align: left" align="left"><span style="font-size: 10pt; color: black; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp; objIn.<u>close</u>();</span></p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; background: #d9d9d9; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none"><span style="font-size: 10pt; color: black; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp; socket.<u>close</u>();</span></p>
</div>
<p style="text-indent: 21pt"><span style="font-size: 10pt; color: black; font-family: 宋体">而作为服务器的接收方则把以上数据做一个逆转相反处理就可以。即服务器需要读取发送过来的对象数据，最终得到结果。现在假设还是一个甚至更多这样的对象处理，我们又要处理和以上代码差不多的过程。好，到这里我们可曾想到难道没有一种办法把这些过多的重复过程做一个通用的方式来提供吗？我如果不想去做这些繁杂的对象处理可以吗？比如，我想直接得到：</span></p>
<div style="border-right: windowtext 1pt solid; padding-right: 4pt; border-top: windowtext 1pt solid; padding-left: 4pt; background: #d9d9d9; padding-bottom: 1pt; border-left: windowtext 1pt solid; padding-top: 1pt; border-bottom: windowtext 1pt solid">
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; background: #d9d9d9; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none"><span style="font-size: 10pt; color: black; font-family: 'Courier New'">//</span><span style="font-size: 10pt; color: black; font-family: 宋体">其中</span><span style="font-size: 10pt; color: black; font-family: 'Courier New'">clientObjectji</span><span style="font-size: 10pt; color: black; font-family: 宋体">就是从客户端传输过来的副本；</span></p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; background: #d9d9d9; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none"><span style="font-size: 10pt; color: black; font-family: 'Courier New'">MyObject myObject=server.getFoo(clientObject);</span></p>
</div>
<p><span style="color: black; font-family: 宋体">这样就能让我们把底层的那些庞杂数据转换能够透明封装起来呢？既然这个问题一经提出，那就意味着肯定有很多类似的需求。技术永远都是在需求的提出应运而生的。上面提出的需求就是我们要讨论的，既然我们想把一些套接字的重复处理过程来个封装清理，那需要面对的问题是什么呢？</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="color: black; font-family: 'Courier New'">1.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="color: black; font-family: 宋体">能够把所有的相同处理过程全部都移入到服务端呢？</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="color: black; font-family: 'Courier New'">2.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="color: black; font-family: 宋体">对于客户端来说能否只预留接口行为呢？</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="color: black; font-family: 'Courier New'">3.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="color: black; font-family: 宋体">把过多的复杂处理过程完善的做个封装？</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="color: black; font-family: 'Courier New'">4.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="color: black; font-family: 宋体">如果以上过程能够形成，那客户端又是如何办到可以连接到服务器端的组件行为呢？</span></p>
<p style="margin-left: 21pt"><span style="color: black; font-family: 宋体">既然能够把遇到的问题提出然后总结出来也就意味着我们需要去解决它。不知道是否还</span></p>
<p><span style="color: black; font-family: 宋体">记得设计模式中有一个叫：代理模式？没错，就是这个代理模式开始我们的描述。代理是一个实现给定接口的对象，但是不直接去执行代码结果，而是代表其他一些对象执行实际计算的对象。怎么理解这个话呢？举例说，如今很多城市都有火车票或者飞机票的代售点，这里的代售点其实就是采用了一种代理机制。我们想买某天的火车或者飞机票有时候并不需要到火车站或者飞机票的总点去购买票，而是找一个你最近的代售点去购买。代售点就是起到一个中间桥梁的作用，至于买票人员无需关心他们如何去订购，这些具体的动作都由他们内部去处理，你只关心最终是否有你需要的票就行。知道这个原理接下来就好理解很多了，我们最好以类图的方式来说明这个代理的机制，如图所示：
<div align="center"><img height="150" alt="" src="http://www.blogjava.net/images/blogjava_net/yeshucheng/clip_image2.jpg" width="443" border="0" /></div>
<br />
</span>
<p>&nbsp;</p>
<p><span style="color: black; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp; </span><span style="color: black; font-family: 宋体">到这里如果还觉得抽象，没关系接下来我以更加贴切的实例来结合类图的方式给出对应的参照说明。假如我们把上面的</span><span style="color: black; font-family: 'Courier New'">proxy</span><span style="color: black; font-family: 宋体">模式再做个深入的探讨剖析（结合上面说的客户端发送参数作为请求和提出的问题综述）。大家都知道一个接口是能够在安全甚至在扩展上能够帮助我们非常大的功能。作为客户端最为希望的就是只关心我们需要的参数（或者变量）、返回值，而它如何而来，做了哪些具体工作这些都不是客户端关心的。</span><span style="color: black; font-family: 'Courier New'">Ok,</span><span style="color: black; font-family: 宋体">现在结合我们说的接口方式，确实可以解决这个问题（接口的简单化，没有具体实现），但是你可能会问：</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="color: black; font-family: 'Courier New'">1.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="color: black; font-family: 宋体">既然接口如此简单，那参数又是如何传递过去的呢？</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt"><span style="color: black; font-family: 'Courier New'">2.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="color: black; font-family: 宋体">服务端又如何知道我要的是什么呢？</span></p>
<p style="text-indent: 21pt"><span style="color: black; font-family: 宋体">带着问题我们来解决这个问题，当然也是大家所关心的问题了。现在开始要对于上面的</span><span style="color: black; font-family: 'Courier New'">proxy</span><span style="color: black; font-family: 宋体">模式做个深入剖析了。我们先来看一个</span><span style="color: black; font-family: 'Courier New'">proxy</span><span style="color: black; font-family: 宋体">模式演变的过程的图示：
<div align="center"><img height="184" alt="" src="http://www.blogjava.net/images/blogjava_net/yeshucheng/clip_image0.jpg" width="463" border="0" /></div>
</span>
<p>&nbsp;</p>
<p style="text-indent: 21pt; text-align: center" align="center"><span style="color: black; font-family: 宋体">图：</span><span style="color: black; font-family: 'Courier New'">RMI</span><span style="color: black; font-family: 宋体">核心结构</span></p>
<p style="text-indent: 21pt"><span style="color: black; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp; </span><span style="color: black; font-family: 宋体">我们可以从图示看出从传统的</span><span style="color: black; font-family: 'Courier New'">proxy</span><span style="color: black; font-family: 宋体">模式变化到一个变化的结构有什么不同呢？对于先前我们提出的两个问题就可以很好的做出解释了：</span></p>
<p style="margin-left: 42pt; text-indent: -21pt; tab-stops: list 42.0pt"><span style="color: black; font-family: Wingdings">n<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="color: black; font-family: 宋体">既然接口如此简单，那参数又是如何传递过去的呢？</span></p>
<p style="margin-left: 21pt"><span style="color: black; font-family: 'Courier New'">A:</span><span style="color: black; font-family: 宋体">既然是对客户端只开接口暴露，那么我们是就需要一个后台的</span><span style="color: black; font-family: 'Courier New'">socket</span><span style="color: black; font-family: 宋体">来传输接口中已经定义好的参数，通过参数的编组（序列化）方式请求到远程服务上去响应处理。这当中要求定义到对方服务的服务名称和端口号。（这里也就是我们最先提到的那段代码了）</span></p>
<p style="margin-left: 42pt; text-indent: -21pt; tab-stops: list 42.0pt"><span style="color: black; font-family: Wingdings">n<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="color: black; font-family: 宋体">服务端又如何知道我要的是什么呢？</span></p>
<p style="margin-left: 21pt"><span style="color: black; font-family: 'Courier New'">A:ok,</span><span style="color: black; font-family: 宋体">既然客户端是通过</span><span style="color: black; font-family: 'Courier New'">socket</span><span style="color: black; font-family: 宋体">来发送数据，那势必一定需要</span><span style="color: black; font-family: 'Courier New'">ServerSocket</span><span style="color: black; font-family: 宋体">来做这个响应的接收处理了。问题是传过来的参数如何与我们的业务实现类关联上呢？所以这个也就是</span><span style="color: black; font-family: 'Courier New'">skeleton</span><span style="color: black; font-family: 宋体">的职责所在了，在</span><span style="color: black; font-family: 'Courier New'">skeleton</span><span style="color: black; font-family: 宋体">的封装处理中（启动中就把响应实现类给嵌入，聚合实现类），然后通过类转换处理和匹配处理来得到需要响应的结果了。</span></p>
<p><span style="color: black; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp; </span></p>
<p align="right"><span style="color: black; font-family: 'Courier New'">&nbsp;&nbsp;&nbsp; </span><span style="color: black; font-family: 宋体">本来说到这想大概有个收尾，但是总觉得还没有把一些问题说透彻。索性想再深入写写。<br />
&nbsp;&nbsp;&nbsp;&nbsp;<a title="从套接字衍生到RMI代码思路" href="http://www.blogjava.net/yeshucheng/archive/2009/02/03/253144.html">从套接字衍生到RMI代码思路</a></span></p>
<img src ="http://www.blogjava.net/yeshucheng/aggbug/252931.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yeshucheng/" target="_blank">叶澍成</a> 2009-02-02 11:57 <a href="http://www.blogjava.net/yeshucheng/archive/2009/02/02/252931.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>HashMap原理及冲突之简谈</title><link>http://www.blogjava.net/yeshucheng/archive/2008/09/15/229053.html</link><dc:creator>叶澍成</dc:creator><author>叶澍成</author><pubDate>Mon, 15 Sep 2008 13:53:00 GMT</pubDate><guid>http://www.blogjava.net/yeshucheng/archive/2008/09/15/229053.html</guid><wfw:comment>http://www.blogjava.net/yeshucheng/comments/229053.html</wfw:comment><comments>http://www.blogjava.net/yeshucheng/archive/2008/09/15/229053.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.blogjava.net/yeshucheng/comments/commentRss/229053.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yeshucheng/services/trackbacks/229053.html</trackback:ping><description><![CDATA[&nbsp;
<p style="text-indent: 21pt"><span style="font-family: 宋体">了解</span>HashMap<span style="font-family: 宋体">原理对于日后的缓存机制多少有些认识。在网络中也有很多方面的帖子，但是很多都是轻描淡写，很少有把握的比较准确的信息，在这里试着不妨说解一二。</span></p>
<p>&nbsp;&nbsp;&nbsp; <span style="font-family: 宋体">对于</span>HashMap<span style="font-family: 宋体">主要以键值</span>(key-value)<span style="font-family: 宋体">的方式来体现，笼统的说就是采用</span>key<span style="font-family: 宋体">值的哈希算法来，外加取余最终获取索引，而这个索引可以认定是一种地址，既而把相应的</span>value<span style="font-family: 宋体">存储在地址指向内容中。这样说或许比较概念化，也可能复述不够清楚，来看列式更加清晰：</span></p>
<div style="border-right: windowtext 0.5pt solid; padding-right: 4pt; border-top: windowtext 0.5pt solid; padding-left: 4pt; background: #d9d9d9; padding-bottom: 1pt; border-left: windowtext 0.5pt solid; padding-top: 1pt; border-bottom: windowtext 0.5pt solid">
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp; hash=key.hashCode();//------------------------1</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp; index=hash%table.lenth;//table<span style="font-family: 宋体">表示当前对象的长度</span>-----------------------2</p>
</div>
<p><span style="font-family: 宋体">其实最终就是这两个式子决定了值得存储位置。但是以上两个表达式还有欠缺。为什么这么说？例如在</span>key.hashCode()<span style="font-family: 宋体">后可能存在是一个负整数，你会问：是啊，那这个时候怎么办呢？所以在这里就需要进一步加强改造式子</span>2<span style="font-family: 宋体">了，修改后的：</span></p>
<div style="border-right: windowtext 0.5pt solid; padding-right: 4pt; border-top: windowtext 0.5pt solid; padding-left: 4pt; background: #d9d9d9; padding-bottom: 1pt; border-left: windowtext 0.5pt solid; padding-top: 1pt; border-bottom: windowtext 0.5pt solid">
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp; index=<span style="font-family: 宋体">（</span>hash&amp;Ox7FFFFFFF)%table.lenth;</p>
</div>
<p><span style="font-family: 宋体">到这里又迷惑了，为什么上面是这样的呢？对于先前我们谈到在</span>hash<span style="font-family: 宋体">有可能产生负数的情况，这里我们使用当前的</span>hash<span style="font-family: 宋体">做一个&#8220;与&#8221;操作，在这里需要和</span>int<span style="font-family: 宋体">最大的值相&#8220;与&#8221;。这样的话就可以保证数据的统一性，把有符号的数值给&#8220;与&#8221;掉。而一般这里我们把二进制的数值转换成</span>16<span style="font-family: 宋体">进制的就变成了：</span>Ox7FFFFFFF<span style="font-family: 宋体">。（注：与操作的方式为，不同为</span>0<span style="font-family: 宋体">，相同为</span>1<span style="font-family: 宋体">）。而对于</span>hashCode()<span style="font-family: 宋体">的方法一般有：</span></p>
<div style="border-right: windowtext 0.5pt solid; padding-right: 4pt; border-top: windowtext 0.5pt solid; padding-left: 4pt; background: #d9d9d9; padding-bottom: 1pt; border-left: windowtext 0.5pt solid; padding-top: 1pt; border-bottom: windowtext 0.5pt solid">
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp; public int hashCode(){</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int hash=0,offset,len=count;</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char[]&nbsp;var=value;</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int i=0;i&lt;len;i++){</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; h=31*hash+var[offset++];</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return hash;</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
</div>
<p style="text-indent: 21pt"><span style="font-family: 宋体">说道这里大家可能会说，到这里算完事了吧。但是你可曾想到如果数据都采用上面的方式，最终得到的可能</span>index<span style="font-family: 宋体">会相同怎么办呢？如果你想到的话，那恭喜你</span>!<span style="font-family: 宋体">又增进一步了，这里就是要说到一个名词：冲突率。是的就是前面说道的一旦</span>index<span style="font-family: 宋体">有相同怎么办？数据又该如何存放呢，而且这个在数据量非常庞大的时候这个基率更大。这里按照算法需要明确的一点：每个键（</span>key<span style="font-family: 宋体">）被散列分布到任何一个数组索引的可能性相同，而且不取决于其它键分布的位置。这句话怎么理解呢？从概率论的角度，也就是说如果</span>key<span style="font-family: 宋体">的个数达到一个极限，每个</span>key<span style="font-family: 宋体">分布的机率都是均等的。更进一步就是：即便</span>key1<span style="font-family: 宋体">不等于</span>key2<span style="font-family: 宋体">也还是可能</span>key1.hashCode()=key2.hashCode()<span style="font-family: 宋体">。</span></p>
<p style="text-indent: 21pt"><span style="font-family: 宋体">对于早期的解决冲突的方法有折叠法（</span>folding)<span style="font-family: 宋体">，例如我们在做系统的时候有时候会采用部门编号附加到某个单据标号后，这里比如产生一个</span>9<span style="font-family: 宋体">～</span>11<span style="font-family: 宋体">位的编码。通过对半折叠做。</span></p>
<p><span style="font-family: 宋体">现在的策略有：</span></p>
<p style="margin-left: 18pt; text-indent: -18pt; tab-stops: list 18.0pt">1.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: 宋体">键式散列</span>&nbsp;</p>
<p style="margin-left: 18pt; text-indent: -18pt; tab-stops: list 18.0pt">2.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: 宋体">开放地址法</span></p>
<p><span style="font-family: 宋体">在了解这两个策略前，我们先定义清楚几个名词解释：</span></p>
<div style="border-right: windowtext 0.5pt solid; padding-right: 4pt; border-top: windowtext 0.5pt solid; padding-left: 4pt; background: #d9d9d9; padding-bottom: 1pt; border-left: windowtext 0.5pt solid; padding-top: 1pt; border-bottom: windowtext 0.5pt solid">
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">threshold[<span style="font-family: 宋体">阀值</span>]<span style="font-family: 宋体">，对象大小的边界值</span>;</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">loadFactor[<span style="font-family: 宋体">加载因子</span>]=n/m <span style="font-family: 宋体">；其中</span>n<span style="font-family: 宋体">代表对象元素个数，</span>m<span style="font-family: 宋体">表示当前表的容积最大值</span></p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">threshold=(int)table.length*loadFactor</p>
</div>
<p><span style="font-family: 宋体">清晰了这几个定义，我们再来看具体的解决方式</span></p>
<p><span style="font-family: 宋体">键式散列：</span></p>
<p style="text-indent: 27pt"><span style="font-family: 宋体">我们直接看一个实例，这样就更加清晰它的工作方式，从而避免文字定义。我们这里还是来举一个图书编号的例子，下面比如有这样一些编号：</span></p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: black">78938</span><span style="color: black">-000</span><span style="color: black">0</span></p>
<p style="text-indent: 27pt"><span style="color: black">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>45678-0001</p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 72678-0002</p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 24678-0001</p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;16678-0001</p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 98678-0003</p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 85678-0002</p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 45232-0004</p>
<p><span style="font-family: 宋体">步骤：</span></p>
<p style="margin-left: 18pt; text-indent: -18pt; tab-stops: list 18.0pt">1.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: 宋体">把编号作为</span>key,<span style="font-family: 宋体">即：</span>int hash=key.hashCode();</p>
<p style="margin-left: 18pt; text-indent: -18pt; tab-stops: list 18.0pt">2.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int index=hash%<span style="font-family: 宋体">表大小；</span></p>
<p style="margin-left: 18pt; text-indent: -18pt; tab-stops: list 18.0pt">3.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: 宋体">逐步按顺序插入对象中</span></p>
<p><span style="font-family: 宋体">现在问题出现了：对于编号通过散列算法后很可能产生相同的索引值，意味着存在冲突。</span></p>
<p style="text-align: center" align="center"><img height="436" alt="" src="http://www.blogjava.net/images/blogjava_net/yeshucheng/111.JPG" width="617" border="0" /></p>
<p><span style="font-family: 宋体">解释上面的操作：如果对于</span>key.hashCode()<span style="font-family: 宋体">产生了冲突（比如途中对于插入</span>24678-0001<span style="font-family: 宋体">对于通过哈希算法后可能产生的</span>index<span style="font-family: 宋体">或许也是</span>501<span style="font-family: 宋体">），既而把相应的前驱有相同的</span>index<span style="font-family: 宋体">的对象指向当前引用。这也就是大家认定的单链表方式。以此类推</span>&#8230;</p>
<p><span style="font-family: 宋体">而这里存在冲突对象的元素放在</span>Entry<span style="font-family: 宋体">对象中，</span>Entry<span style="font-family: 宋体">具有以下一些属性：</span></p>
<p>int hash;</p>
<p>Object key;</p>
<p>Entry next;</p>
<p><span style="font-family: 宋体">对于</span>Entry<span style="font-family: 宋体">对象就可以直接追溯到链表数据结构体中查阅。</span></p>
<p><span style="font-family: 宋体">开放地址法：</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt">1.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: 宋体">线性地址探测法：</span></p>
<p><span style="font-family: 宋体">如何理解这个概念呢，一句话：就是通过算法规则在对象地址</span>N+1<span style="font-family: 宋体">中查阅找到为</span>NULL<span style="font-family: 宋体">的索引内容。</span></p>
<p><span style="font-family: 宋体">处理方式：如果</span>index<span style="font-family: 宋体">索引与当前的</span>index<span style="font-family: 宋体">有冲突，即把当前的索引</span>index+1<span style="font-family: 宋体">。如果在</span>index+1<span style="font-family: 宋体">已经存在占位现象（</span>index+1<span style="font-family: 宋体">的内容不为</span>NULL<span style="font-family: 宋体">）试图接着</span>index+2<span style="font-family: 宋体">执行。。。直到找到索引为内容为</span>NULL<span style="font-family: 宋体">的为止。这种处理方式也叫：线性地址探测法</span>(offset-of-1)</p>
<p><span style="font-family: 宋体">如果采用线性地址探测法会带来一个效率的不良影响。现在我们来分析这种方式会带来哪些不良因素。大家试想下如果一个非常庞大的数据存储在</span>Map<span style="font-family: 宋体">中，假如在某些记录集中有一些数据非常相似（他们产生的索引在内存的某个块中非常的密集），也就是说他们产生的索引地址是一个连续数值，而造成数据成块现象。另一个致命的问题就是在数据删除后，如果再次查询可能无法定到下一个连续数字，这个又是一个什么概念呢？例如以下图片就很好的说明开发地址散列如何把数据按照算法插入到对象中：</span></p>
<p style="text-align: center" align="center"><img height="502" alt="" src="http://www.blogjava.net/images/blogjava_net/yeshucheng/222.JPG" width="512" border="0" /></p>
<p><span style="font-family: 宋体">对于上图的注释步骤说明：</span></p>
<p style="line-height: 119%; text-align: center" align="center"><span style="font-family: 宋体">从数据&#8220;</span>78938-0000<span style="font-family: 宋体">&#8221;开始通过哈希算法按顺序依次插入到对象中，例如</span>78938-0000<span style="font-family: 宋体">通过换</span></p>
<p style="line-height: 119%"><span style="font-family: 宋体">算得到索引为</span>0<span style="font-family: 宋体">，当前所指内容为</span>NULL<span style="font-family: 宋体">所以直接插入；</span><span style="color: black">45678-0001</span><span style="color: black; font-family: 宋体">同样通过换算得到索引为地址</span><span style="color: black">501</span><span style="color: black; font-family: 宋体">所指内容，当前内容为</span><span style="color: black">NULL</span><span style="color: black; font-family: 宋体">所以也可以插入；</span><span style="color: black">72678-0002</span><span style="color: black; font-family: 宋体">得到索引</span><span style="color: black">502</span><span style="color: black; font-family: 宋体">所指内容，当前内容为</span><span style="color: black">NULL</span><span style="color: black; font-family: 宋体">也可以插入；请注意当</span><span style="color: black">24678-0001</span><span style="color: black; font-family: 宋体">得到索引也为</span><span style="color: black">501</span><span style="color: black; font-family: 宋体">，当前地址所指内容为</span><span style="color: black">45678-0001</span><span style="color: black; font-family: 宋体">。即表示当前数据存在冲突，则直接对地址</span><span style="color: black">501+1=502</span><span style="color: black; font-family: 宋体">所指向内容为</span><span style="color: black">72678-0002</span><span style="color: black; font-family: 宋体">不为</span><span style="color: black">NULL</span><span style="color: black; font-family: 宋体">也不允许插入，再次对索引</span><span style="color: black">502+1=503</span><span style="color: black; font-family: 宋体">所指内容为</span><span style="color: black">NULL</span><span style="color: black; font-family: 宋体">允许插入。。。依次类推只要对于索引存在冲突现象，则逐次下移位知道索引地址所指为</span><span style="color: black">NULL</span><span style="color: black; font-family: 宋体">；如果索引不冲突则还是按照算法放入内容。对于这样的对象是一种插入方式，接下来就是我们的删除</span><span style="color: black">(remove)</span><span style="color: black; font-family: 宋体">方法了。按照常理对于删除，方式基本区别不大。但是现在问题又出现了，如果删除的某个数据是一个存在冲突索引的内容，带来后续的问题又会接踵而来。那是什么问题呢？我们还是同样来看看图示的描述，对于图</span><span style="color: black">-2</span><span style="color: black; font-family: 宋体">中如果删除</span><span style="color: black">(remove)</span><span style="color: black; font-family: 宋体">数据</span><span style="color: black">24678-0001</span><span style="color: black; font-family: 宋体">的方法如下图所示：</span></p>
<p style="line-height: 119%; text-align: center" align="center"><img height="502" alt="" src="http://www.blogjava.net/images/blogjava_net/yeshucheng/333.JPG" width="512" border="0" /></p>
<p style="line-height: 119%"><span style="font-family: 宋体">对于我们会想当然的觉得只要把指向数据置为</span>NULL<span style="font-family: 宋体">就可以</span>,<span style="font-family: 宋体">这样的做法对于删除来说当然是没有问题的。如果再次定位检索数据</span>16678-0001<span style="font-family: 宋体">不会成功，因为这个时候以前的链路已经堵上了，但是需要检索的数据事实上又存在。那我们如何来解决这个问题呢？对于</span>JDK<span style="font-family: 宋体">中的</span>Entry<span style="font-family: 宋体">类中的方法存在一个：</span>boolean markedForRemoval;<span style="font-family: 宋体">它就是一个典型的删除标志位，对于对象中如果需要删除时，我们只是对于它做一个&#8220;软删除&#8221;即置一个标志位为</span>true<span style="font-family: 宋体">就可以。而插入时，默认状态为</span>false<span style="font-family: 宋体">就可以。这样的话就变成以下图所示：</span></p>
<p style="line-height: 119%; text-align: center" align="center"><img height="502" alt="" src="http://www.blogjava.net/images/blogjava_net/yeshucheng/444.JPG" width="512" border="0" /></p>
<p style="line-height: 119%"><span style="font-family: 宋体">通过以上方式更好的解决冲突地址删除数据无法检索其他链路数据问题了。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; line-height: 119%; tab-stops: list 21.0pt">2.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: 宋体">双散列（余商法）</span></p>
<p style="margin-left: 21pt; text-indent: 21pt; line-height: 119%"><span style="font-family: 宋体">在了解开放地址散列的时候我们一直在说解决方法，但是大家都知道一个数据结构的完善更多的是需要高效的算法。这当中我们却没有涉及到。接下来我们就来看看在开放地址散列中它存在的一些不足以及如何改善这样的方法，既而达到无论是在方法的解决上还是在算法的复杂度上更加达到高效的方案。</span></p>
<p style="margin-left: 21pt; text-indent: 21pt; line-height: 119%"><span style="font-family: 宋体">在图</span>2-1<span style="font-family: 宋体">中类似这样一些数据插入进对象，存在冲突采用不断移位加一的方式，直到找到不为</span>NULL<span style="font-family: 宋体">内容的索引地址。也正是由于这样一种可能加大了时间上的变慢。大家是否注意到像图这样一些数据目前呈现出一种连续索引的插入，而且是一种成块是的数据。如果数据量非常的庞大，或许这种可能性更大。尽管它解决了冲突，但是对于数据检索的时间度来说，我们是不敢想象的。所有分布到同一个索引</span>index<span style="font-family: 宋体">上的</span>key<span style="font-family: 宋体">保持相同的路径：</span>index,index+1,index+2&#8230;<span style="font-family: 宋体">依此类推。更加糟糕的是索引键值的检索需要从索引开始查找。正是这样的原因，对于线性探索法我们需要更进一步的改进。而刚才所描述这种成块出现的数据也就定义成：簇。而这样一种现象称之为：主簇现象。</span></p>
<p style="line-height: 119%"><span style="font-family: 宋体">（主簇：就是冲突处理允许簇加速增长时出现的现象）而开放式地址冲突也是允许主簇现象产生的。那我们如何来避免这种主簇现象呢？这个方式就是我们要来说明的：双散列解决冲突法了。主要的方式为：</span></p>
<p style="background: #e5e5e5; margin-left: 21pt; text-indent: -21pt; line-height: 119%; tab-stops: list 21.0pt"><span style="font-family: Wingdings">u<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>int hash=key.hasCode();</p>
<p style="background: #e5e5e5; margin-left: 21pt; text-indent: -21pt; line-height: 119%; tab-stops: list 21.0pt"><span style="font-family: Wingdings">u<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>int index=(hash&amp;Ox7FFFFFFF)%table.length;</p>
<p style="background: #e5e5e5; margin-left: 21pt; text-indent: -21pt; line-height: 119%; tab-stops: list 21.0pt"><span style="font-family: Wingdings">u<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="font-family: 宋体">按照以上方式得到索引存在冲突，则开始对当前索引移位，而移位方式为：</span></p>
<p style="background: #e5e5e5; line-height: 119%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; offset=(hash&amp;Ox7FFFFFFF)/table.length;</p>
<p style="background: #e5e5e5; margin-left: 21pt; text-indent: -21pt; line-height: 119%; tab-stops: list 21.0pt"><span style="font-family: Wingdings">u<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="font-family: 宋体">如果第一次移位还存在同样的冲突，则继续：当前冲突索引位置（索引号</span>+<span style="font-family: 宋体">余数）</span>%<span style="font-family: 宋体">表</span>.length</p>
<p style="background: #e5e5e5; margin-left: 21pt; text-indent: -21pt; line-height: 119%; tab-stops: list 21.0pt"><span style="font-family: Wingdings">u<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="font-family: 宋体">如果存在的余数恰好是表的倍数，则作偏移位置为一下移，依此类推</span></p>
<p style="text-indent: 21pt; line-height: 119%"><span style="font-family: 宋体">这样双散列冲突处理就避免了主簇现象。至于</span>HashSet<span style="font-family: 宋体">的原理基本和它是一致的，这里不再复述。在这里其实还是主要说了一些简单的解决方式，而且都是在一些具体参数满足条件下的说明，像一旦数据超过初始值该需要</span>rehash<span style="font-family: 宋体">，加载因子一旦大于</span>1.0<span style="font-family: 宋体">是何种情况等等。还有很多问题都可以值得我们更加进一步讨论的，比如：在</span>java.util.HashMap<span style="font-family: 宋体">中的加载因子为什么会是</span>0.75<span style="font-family: 宋体">，而它默认的初始大小为什么又是</span>16<span style="font-family: 宋体">等等这些问题都还值得说明。要说明这些问题可能又需要更加详尽的说明清楚。</span></p>
<img src ="http://www.blogjava.net/yeshucheng/aggbug/229053.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yeshucheng/" target="_blank">叶澍成</a> 2008-09-15 21:53 <a href="http://www.blogjava.net/yeshucheng/archive/2008/09/15/229053.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>