??xml version="1.0" encoding="utf-8" standalone="yes"?>
private String id;
private String name;
private String broker;
private String date;
private String body;
//get/set
public String toString(){
return "【编号ؓ:"+id+",q告名称?"+name+",代理商ؓ:"+broker+",发布日期?"+date+",内容?"+body+"?/span>";
}
}
//调用dc?/p>
//d?/p>
在过ȝ电脑都已?/span>CPU作ؓ主要的处理方式,无论?/span>PC或者是服务器都是如此。系l调用某一个时d能有一个线E运行。当然这当中采用了比较多的策略来做时间片轮询。通过不断的调度切换来q行U程q行Q而这U方式就叫做q发Q?/span>concurrentQ?/span>
随着工艺水^的逐渐提升Q?/span>CPU的技术也在不断增q。因此在如今多个CPU已经不是什么特别的Q而大家常总SMP的方式来形容多个CPU来处理两个或者两个以上的U程q行方式q为ƈ行(parallelQ?/span>
l承ThreadQ实?/span>start()Ҏ
要实现线E运行,JAVA中有两种方式Q?/span>
实现RunnableQ然后再传递给Thread实例
注意Q线E对象和U程是两个截然不同的概念?/span>
U程对象?/span>JVM产生的一个普通的Object子类
U程?/span>CPU分配l这个对象的一个运行过E?/span>
public class Test {
public static void main(String[] args) throws Exception{
MyThread mt = new MyThread();
mt.start();
mt.join();
Thread.sleep(3000);
mt.start();
}
}
当线E对?/span>mtq行完成?/span>,我们让主U程休息一下,然后我们再次在这个线E对象上启动U程.l果我们看到Q?/span>
Exception in thread "main" java.lang.IllegalThreadStateException
Ҏ原因是在以下源代码中扑ևQ?/span>
public synchronized void start() {
if (started)
throw new IllegalThreadStateException();
started = true;
group.add(this);
start0();
}
一?/span>Thread的实例一旦调?/span>start()ҎQ这个实例的started标记标CؓtrueQ事实中不管q个U程后来有没有执行到底,只要调用了一?/span>start()再也没有机会运行了Q这意味着Q?/span>
【通过Thread实例?/span>start()Q一?/span>Thread的实例只能生一个线E?/span>
当一个线E对象调?/span>interrupt()ҎQ它对应的线Eƈ没有被中断,只是改变了它的中断状态。当前U程的状态变以中断状态,如果没有其它影响Q线E还会自ql执行。只有当U程执行?/span>sleepQ?/span>waitQ?/span>join{方法时Q或者自己检查中断状态而抛出异常的情况下,U程才会抛出异常?/span>
join()ҎQ正如第一节所aQ在一个线E对象上调用joinҎQ是当前U程{待q个U程对象对应的线E结?/span>
例如Q有两个工作Q工?/span>A要耗时10U钟Q工?/span>B要耗时10U或更多。我们在E序中先生成一个线E去做工?/span>BQ然后做工作A?/span>
new B().start();//做工?/span>B
A();//做工?/span>A
工作A完成后,下面要等待工?/span>B的结果来q行处理。如果工?/span>Bq没有完成我׃能进行下面的工作CQ所以:
B b = new B();
b.start();//做工?/span>B
A();//做工?/span>A
b.join();//{工?/span>B完成.
C();//l箋工作C
原则Q?/span>join是测试其它工作状态的唯一正确Ҏ?/span>
yield()Ҏ也是cL法,只在当前U程上调用,理由同上Q它L让当前线E放弃本ơ分配到的时间片Q调用这个方法不会提高Q何效率,只是降低?/span>CPU的d期上面介l的U程一些方法,Z(基础?/span>)而言只能单提及。以后具体应用中我会l合实例详细?/span>
原则Q【不是非常必要的情况下,没有理由调用它?/span>
首先明确一点他们的属于普通对象方法,q是线E对象方法;其次它们只能在同步方法中调用。线E要惌用一个对象的wait()Ҏp先获得该对象的监视锁,而一旦调?/span>wait()后又立即释放该锁?/span>
多个U程同时操作某一对象Ӟ一个线E对该对象的操作可能会改变其状态,而该状态会影响另一U程对该对象的真正结果?/span>
把一个单元声明ؓsynchornized,可以让在同一旉只有一个线E操作该Ҏ。作忆可以把synchronized看作是一个锁。但是我们要理解锁是被动的,q是d的呢Q换而言之它到底锁什么了Q锁谁了Q?/span>
例如Q?/span>
synchronized(obj){
//todo…
}
如果代码q行到此处,synchronized首先获取obj参数对象的锁Q若没有获取U程只能{待Q如果多个线E运行到q只能有一个线E获?/span>obj的锁Q然后再执行{}中的代码。因?/span>obj作用范围不同Q控制程序也不同?/span>
如果一个方法声明ؓsynchornized的,则等同于把在ZҎ上调?/span>synchornized(this)?/span>
如果一个静态方法被声明?/span>synchornizedQ则{同于把在ؓ个方法上调用synchornized(c?/span>.class)
要让一个线E得到真正意义的停止Q需要了解当前的U程在干什么,如果U程当前没有做什么,那立刻让Ҏ退出,当然是没有Q何问题,但是如果Ҏ正在手头赶工Q那必让他停止,然后收拾D局。因此,首先需要了解步骤:
1. 正常q行Q?/span>
2. 处理l束前的工作,也就是准备结束;
3. l束退出?/span>
注:以上部分概括某位牛h大哥的笔讎ͼl常拜读他的博客?/span>java.nio包中我们可以直接来操作相对应?/span>API了。可以让java更加方便的直接控制和q用~冲区。缓冲区有几个需要了解的特定概念需要详来解释Q才能更好的知道我们下面一些列需要针对的问题实质?/span>
定wQ?/span>capacityQ:思义是表示~冲Z可以保存多少数据Q?/span>
极限Q?/span>limitQ:~冲Z的当前数据终l点。不q它是可以动态改变的Q这样做的好处也是充分利用重用性;
位置(position)Q这个也好理解,其实是指明下一个需要读写数据的位置?/span>
上面上个关系q可以具体用囄的方式来表达整体概念Q如下图所C:
l clear()Q首先把极限讄为容量,再者就是需要把位置讄?/span>0Q?/span>
l flip()Q把极限讄Z|区Q再者就是需要把位置讄?/span>0Q?/span>
l rewind()Q不改变极限Q不q还是需要把位置讄?/span>0?/span>
最为最基础的缓冲区ByteBufferQ它存放的数据单元是字节。首先要的是ByteBuffer没有提供公开的构造方法,只是提供了两个静态的工厂Ҏ?/span>
l allocate(int capacity)Q返回一?/span>ByteBuffer对象Q参数表C缓冲区定w大小?/span>
l allocateDirect (int capacity)Q返回一?/span>ByteBuffer对象Q参C是一栯C缓冲区定w大小?/span>
在这里需要注意的是在使用两者的时候需要特别小心,allocateDirect和当前操作系l联pȝ非常紧密Q它牉|C?/span>native method的方法,大家知道一旦本地方法就是需要考虑调用dllQ动态链接库Q这个时候基本也失MJAVA语言的特性,a外之意对于耗资源非常大。所以如果考虑到当前用的~存区比较庞大而且是一个长期驻留用的Q这个时候可以考虑使用它?/span>
Rmi自从JDK1.1已l出C。而对于ؓ什么在JAVA的世界里需要一个这?/span> 思想理念需要看下:RMI问世由来。其实真正在国内使用到它的比较少Q不q在前些q比较火?/span>EJB是在它的基上进一步深化的。从本质上来?/span>RMI的兴h是ؓ了设计分布式的客戗服务器l构需求而应q而生的,而它的这U?/span>B/Sl构思想能否和我们通常?/span>JAVA~程更加贴切呢?a外之意就是能否让q种分布式的状态做到更加透明Q作为开发h员只需要按照往怸样开?/span>JAVA应用E序一h开发分布式的结构。那现在的问题是如何来划q个`沟呢Q首先我们来分析下在JAVA世界里它的一些特点因素:
l JAVA使用垃圾攉定对象的生命周期?/span>
l JAVA使用异常处理来报告运行期间的错误。这里就要和我们|络通讯中的异常相联pv来了。在B/Sl构的网l体pM我们的这U错误性是非常常见的?/span>
l JAVA~写的对象通过调用Ҏ来调用。由于网l通讯把我们的客户与服务器之间阻隔开了。但是代理的一U方式可以很好的提供一U这L假象Q让开发h员或者用者都感觉是在本地调用?/span>
l JAVA允许一U高U的使用cd载器Q?/span>CLassLoaderQ机制提供系l类路径中没有的cR这话什么意思?
上面说到了分布式的方式和我们?/span>JAVA中如何更好的划^q个鸿沟Q需要具备的特质?/span>
那这里我们来看看我们所谓的RMI到底跟我们普通的JAVAQ或者说JavaBeanQ存在一些什么样的差异:
l RMIq程异常Q?/span>Remote ExceptionQ:在上面我们也提到了一个网l通讯隑օ有一些无论是软gU别的还是硬件别的异常现象Q有时候这些异常或许是一U无法预知的l果。让我们开发h~如何来回溯q种异常信息Q这个是我们开发h员要兛_的。因此在调用q程对象的方法中我们必须在远E接口中Q接口是一U规范的标准行ؓQ所以在调用的这个方法体上需要签名注明:java.rmi,RemoteException.。这也就注明了此Ҏ是需要调用远E对象的?/span>
l g?/span> Q当把对象作为参C递给一个普通的JAVA对象Ҏ调用Ӟ只是传递该对象?strong>引用。请注意q里谈到的是对象?#8220;引用”一词,如果在修改该参数的时候,是直接修改原始对象。它q不是所谓的一个对象的备䆾或者说拯Q说白了是在本JVM内存中的对象Q。但是如果说使用的是RMI对象Q则完全是拷贝的。这与普通对象有着鲜明的对比。也正是׃q种拯的资源消耗造就了下面要说到的性能~失了?/span>
l 调用开销Q凡是经q网l通讯理论上来说都是一U资源的消耗。它需要通过~组与反~组方式不断解析cd象。而且RMI本n也是一U需要返回值的一个过E定义?/span>
l 安全?/span>Q一谈到|络通讯势必会说到如何保证安全的q行?/span>
在开始进行原理梳理之前我们需要定义清楚几个名词。对于这些名词的理解影响到后的深入进行?/span>
1. StubQ存根,有些书上也翻译成Q桩基在EJB的相关书c中ؓ体现q个意思)Q?/span>
q里举例说明q个概念P或许不够恰当Q。例如大家因公出差后Q都有存在一些报销的发或者说票。对于你当前手头所拿到的发ƈ不是一个唯一的,它同时还在你发生消费的地Ҏ一个复CgQ而这个复Cg是所谓的存根。但是这个存根上q没有很多明l的描述Q只是有一个大概的金额定义。它把很多的l节费用都忽略了。所以这个也是我们说的存根定义。而在我们RMI的存根定义就是用了q样一个理解:在与q程发生通讯调用Ӟ把通讯调用的所有细节都通过对象的封装Ş式给隐藏在后端。这本nq?/span>OOAD的意思理c而暴露出来的是我们的接口方式,而这U接口方式又和服务器的对象具有相同的接口Q这里就和我们前面D例说的报销单据联系上了Q报销单据的存根不知道会有一个什么Ş式发生具体问题,而你手执的发具体就需要到贵公司去报销费用Q而这里的公司财务处就是所谓的服务器端Q它才是真正q实质性问题的。)因此作ؓ开发h员只需要把_֊集中在业务问题的解决上,而不需要考虑复杂的分布式计算。所有这些问题都交给RMIM一处理?/span>
2. Skeleton(一些书译叫骨Ӟ也叫l构?/span>)Q它的内部就是真正封装了一个类的Ş成调用体现机制。包括我们熟知的ServerSocket创徏、接受、监听、处理等?/span>
3. Mashalling(~组)Q在内存中的对象转换成字节流Q以便能够通过|络q接传输?/span>
4. Unmashalling(反编l?/span>)Q在内存中把字节{换成对象Q以便本地化调用?/span>
5. Serialization(序列?/span>)Q编l中使用到的技术叫序列化?/span>
6. Deserializationg(反序列化)Q反~组中用到的技术叫反序列化?/span>
既然我们知道stub主要是以接口的方式来暴露体现Q?/span>stub主要 也是以代理的方式来具体实施。那?/span>RMI中的q种接口有哪些特性呢Q(Remote InterfaceQ?/span>
1) 必须扩展Q?/span>extendsQ?/span>java.rmi.Remote接口Q因E接口ƈ不包含Q何一个方法,而是作ؓ一个标记出?/span>Q它是需要告?/span>JVM?/span>RunTime的时候哪些是常规对象Q哪些属于远E对象。通过q种标识的定义能?/span>JVM了解cM哪些Ҏ需要编l,通过了编l的方式才能通过|络序列化的调用Q?/span>
2) 接口必须?/span>publicQ公共)Q它的好处不a而喻的——能够方便的让所有h员来调用?/span>
3) 接口Ҏq需要以异常抛出Q例如:RemoteExceptionQ,至于它的用处我们在前面也提到q里׃再复qͼ
4) 在调用一个远E对象期_q行期间Q,Ҏ的参数和q回值都要必L可序列化的。至于ؓ什么需要这么做Q这里的~由不用多说大家也应该清楚了解?/span>
既然我们知道stub所做的事情是一个简单的代理转发动作Q那我们真正要做的对象就在服务端来做了。对于用简单的RMI我们直接L定,但是往往一旦用了RMI对象存在非常多的远E方法调用,q个时候服务器端对于这么多的调用如何来判别或者说识别呢?q里p说到的是对于RMI实现它会创徏一个标识符Q以便以后的stub可以调用转发l服务器对象使用了,而这U方式我们通常叫服务器RMI的注册机制。言外之意就是让服务器端的对象注册在RMI机制中,然后可以导出让今后的stub按需来调用。那它又是如何做到这U方式的呢?对于RMI来说有两U方式可以达到这U效果:
a) 直接使用UnicastRemoteObject的静态方法:exportObjectQ?/span>
b) l承UnicastRemoteObjectcd~省的构造函?/span>exportObject?/span>
现在大家又会问他们之间又有什么区别呢Q我该用哪U方式来做呢Q这不是很难做抉择吗Q从一般应用场景来说区别ƈ不是很大Q但是,q里说了“但是”哦,呵呵。大家知道承的方式是把父类所具备的所有特质都可以完好无损的承到子类中而对于类的总老大Q?/span>Object来说里面有:equals()?/span>hashCode()?/span>toString(){方法。这是个什么概念呢Q意思就是说如果对于本地化的调用Q他们两个的ҎQ?/span>a,bQ基本区别不是很大。但是我们这里强调的RMI如果是一U分布式的特定场景,具备使用哈希表这U特性就昑־ؓ重要了?/span>
刚才说了服务端采用什么方法行为导出对象的。那现在导出后的对象又对应会发生什么情况呢Q?/span>
首先被导出的对象被分配一个标识符Q这个标识符被保存ؓQ?/span>java.rmi.server.ObjID对象中ƈ被放C个对象列表中或者一个映中。而这里的ID是一个关键字Q而远E对象则是它的一个|说到q大家有没有觉得它原理非常像HashMap的特质呢Q没错,其实是使用了它的特性)Q这样它可以很好的和前面创建的stub沟通。如果调用了静态方?/span>UnicastRemoteObject.export(Remote …)Q?/span>RMI׃选择L一个端口号Q但q只是第一调用发生在随后的exportObject每次调用都把q程对象导出到该q程对象W一被导出时使用的端口号。这样就不会产生混ؕQ会把先前一一导出的对象全部放入到列表中。当然如果采用的是指定端口的Q则按照对应昄的调用方式用。这里稍作强调的是一个端口可以导ZQ意数目的对象?/span>
Q待l?/span>……Q?/span>多数|络~程库中Q以JAVAZ来说明)Q在JAVAq_中一L提供了这些元素。而作为面向连接协议来说用的是套接字Q?/span>SocketQ进行了更进一步的抽象描述。一般我们在JAVA的网l编E中都觉得在使用套接字这块相Ҏ便,它不需要你L多的了解操作pȝ的细节以及硬件的传递处理方式?/span>TCP/IP的所有细节之处都得到了套接字的封装用,让程序员xC务层面的处理?/span>
对象是一U抽象思维物质Q对于计机来说它只Ҏ字电路的存储方式能够加以识别而且在网l传输当中也是一U信号量Q而这一切只有用字节流方式传输才是真正需要做到的。所以在本地L与远E服务器的通讯传输在对象与字节流之间不断怺转化才是我们真正需要的人性物质与机器所需要的。(有点墨迹了,切入整体QM来说是需要两U方式来认定q种传输行ؓQ编l(MarshallingQ与反编l(UnmarshallingQ。而这一切的手段方式才是通过Q序列化Q?/span>SerialiazableQ与反序列化的方式不断完成。如下图所C:
图:对象到字节再到对象的转换 对于数据的传输本质就是上图说明的。那我们一般是如何使用套接字来构造我们这一行ؓ呢?对于q里的主要是一U大致方法说明,所以只能以部分代码来说明客L怎么来发送这个请求?/span> Socket socket=new Socket("http://www.wgh.com.cn",8888); OutputStream out=socket.getOutputStream(); ObjectOutputStream obj=new ObjectOutputStream(out); obj.writeObject(object); InputStream in=socket.getInputStream(); ObjectInputStream objIn=new ObjectInputStream(in); Object objFoo=(Object)objIn.readObject(); //todo q里是具体q行操作的相关传值参数处理了… obj.close(); objIn.close(); socket.close(); 而作为服务器的接收方则把以上数据做一个逆{相反处理可以。即服务器需要读取发送过来的对象数据Q最l得到结果。现在假设还是一个甚x多这L对象处理Q我们又要处理和以上代码差不多的q程。好Q到q里我们可曾惛_N没有一U办法把q些q多的重复过E做一个通用的方式来提供吗?我如果不惛_做这些繁杂的对象处理可以吗?比如Q我想直接得刎ͼ //其中clientObjectji是从客L传输q来的副本; MyObject myObject=server.getFoo(clientObject); q样p让我们把底层的那些庞杂数据{换能够透明装h呢?既然q个问题一l提出,那就意味着肯定有很多类似的需求。技术永q都是在需求的提出应运而生的。上面提出的需求就是我们要讨论的,既然我们x一些套接字的重复处理过E来个封装清理,那需要面对的问题是什么呢Q?/span> 1. 能够把所有的相同处理q程全部都移入到服务端呢Q?/span> 2. 对于客户端来说能否只预留接口行ؓ呢? 3. 把过多的复杂处理q程完善的做个封装? 4. 如果以上q程能够形成Q那客户端又是如何办到可以连接到服务器端的组件行为呢Q?/span> 既然能够把遇到的问题提出然后ȝ出来也就意味着我们需要去解决它。不知道是否q?/span> 记得设计模式中有一个叫Q代理模式?没错Q就是这个代理模式开始我们的描述。代理是一个实现给定接口的对象Q但是不直接L行代码结果,而是代表其他一些对象执行实际计的对象。怎么理解q个话呢QD例说Q如今很多城市都有火车票或者飞机票的代售点Q这里的代售点其实就是采用了一U代理机制。我们想买某天的火R或者飞机票有时候ƈ不需要到火R站或者飞机票的ȝ去购买票Q而是找一个你最q的代售点去购买。代售点是起到一个中间桥梁的作用Q至于买h员无需兛_他们如何去订购,q些具体的动作都׃们内部去处理Q你只关心最l是否有你需要的就行。知道这个原理接下来好理解很多了,我们最好以cd的方式来说明q个代理的机Ӟ如图所C:
到这里如果还觉得抽象Q没关系接下来我以更加脓切的实例来结合类囄方式l出对应的参照说明。假如我们把上面?/span>proxy模式再做个深入的探讨剖析Q结合上面说的客L发送参C求和提出的问题综qͼ。大安知道一个接口是能够在安全甚臛_扩展上能够帮助我们非常大的功能。作为客L最为希望的是只关心我们需要的参数Q或者变量)、返回|而它如何而来Q做了哪些具体工作这些都不是客户端关心的?/span>Ok,现在l合我们说的接口方式Q确实可以解册个问题(接口的简单化Q没有具体实玎ͼQ但是你可能会问Q?/span> 1. 既然接口如此单,那参数又是如何传递过ȝ呢? 2. 服务端又如何知道我要的是什么呢Q?/span> 带着问题我们来解册个问题,当然也是大家所兛_的问题了。现在开始要对于上面?/span>proxy模式做个深入剖析了。我们先来看一?/span>proxy模式演变的过E的囄Q?
图:RMI核心l构 我们可以从图C看Z传统?/span>proxy模式变化C个变化的l构有什么不同呢Q对于先前我们提出的两个问题可以很好的做出解释了: n 既然接口如此单,那参数又是如何传递过ȝ呢? A:既然是对客户端只开接口暴露Q那么我们是需要一个后台的socket来传输接口中已经定义好的参数Q通过参数的编l(序列化)方式h到远E服务上d应处理。这当中要求定义到对Ҏ务的服务名称和端口号。(q里也就是我们最先提到的那段代码了) n 服务端又如何知道我要的是什么呢Q?/span> A:ok,既然客户端是通过socket来发送数据,那势必一定需?/span>ServerSocket来做q个响应的接收处理了。问题是传过来的参数如何与我们的业务实现cd联上呢?所以这个也是skeleton的职责所在了Q在skeleton的封装处理中Q启动中把响应实现cȝ嵌入Q聚合实现类Q,然后通过c{换处理和匚w处理来得到需要响应的l果了?/span> 本来说到q想大概有个收尾Q但是总觉得还没有把一些问题说透彻。烦性想再深入写写?br />
从套接字衍生到RMI代码思\
]]>
对于HashMap主要以键?/span>(key-value)的方式来体现Q笼l的说就是采?/span>key值的哈希法来,外加取余最l获取烦引,而这个烦引可以认定是一U地址Q既而把相应?/span>value存储在地址指向内容中。这栯或许比较概念化,也可能复qC够清楚,来看列式更加清晰Q?/span>
int hash=key.hashCode();//------------------------1
int index=hash%table.lenth;//table表示当前对象的长?/span>-----------------------2
其实最l就是这两个式子军_了值得存储位置。但是以上两个表辑ּq有Ơ缺。ؓ什么这么说Q例如在key.hashCode()后可能存在是一个负整数Q你会问Q是啊,那这个时候怎么办呢Q所以在q里需要进一步加强改造式?/span>2了,修改后的Q?/span>
int index=Q?/span>hash&Ox7FFFFFFF)%table.lenth;
到这里又qh了,Z么上面是q样的呢Q对于先前我们谈到在hash有可能生负数的情况Q这里我们用当前的hash做一?#8220;?#8221;操作Q在q里需要和int最大的值相“?#8221;。这L话就可以保证数据的统一性,把有W号的数值给“?#8221;掉。而一般这里我们把二进制的数D{换成16q制的就变成了:Ox7FFFFFFF。(注:与操作的方式为,不同?/span>0Q相同ؓ1Q。而对?/span>hashCode()的方法一般有Q?/span>
public int hashCode(){
int hash=0,offset,len=count;
char[] var=value;
for(int i=0;i<len;i++){
h=31*hash+var[offset++];
}
return hash;
}
说道q里大家可能会说Q到q里完事了吧。但是你可曾惛_如果数据都采用上面的方式Q最l得到的可能index会相同怎么办呢Q如果你惛_的话Q那恭喜?/span>!又增q一步了Q这里就是要说到一个名词:冲突率。是的就是前面说道的一?/span>index有相同怎么办?数据又该如何存放呢,而且q个在数据量非常庞大的时候这个基率更大。这里按照算法需要明的一点:每个键(keyQ被散列分布CQ何一个数l烦引的可能性相同,而且不取决于其它键分布的位置。这句话怎么理解呢?从概率论的角度,也就是说如果key的个数达C个极限,每个key分布的机率都是均{的。更q一步就是:即便key1不等?/span>key2也还是可?/span>key1.hashCode()=key2.hashCode()?/span>
对于早期的解军_H的Ҏ有折叠法Q?/span>folding)Q例如我们在做系l的时候有时候会采用部门~号附加到某个单据标号后Q这里比如生一?/span>9?/span>11位的~码。通过对半折叠做?/span>
现在的策略有Q?/span>
1. 键式散列
2. 开攑֜址?/span>
在了解这两个{略前,我们先定义清楚几个名词解释:
threshold[阀?/span>]Q对象大的边界?/span>;
loadFactor[加蝲因子]=n/m Q其?/span>n代表对象元素个数Q?/span>m表示当前表的容积最大?/span>
threshold=(int)table.length*loadFactor
清晰了这几个定义Q我们再来看具体的解x?/span>
键式散列Q?/span>
我们直接看一个实例,q样更加清晰它的工作方式,从而避免文字定义。我们这里还是来举一个图书编L例子Q下面比如有q样一些编P
78938-0000
45678-0001
72678-0002
24678-0001
16678-0001
98678-0003
85678-0002
45232-0004
步骤Q?/span>
1. 把编号作?/span>key,卻Iint hash=key.hashCode();
2. int index=hash%表大;
3. 逐步按顺序插入对象中
现在问题出现了:对于~号通过散列法后很可能产生相同的烦引|意味着存在冲突?/span>
解释上面的操作:如果对于key.hashCode()产生了冲H(比如途中对于插入24678-0001对于通过哈希法后可能生的index或许也是501Q,既而把相应的前驱有相同?/span>index的对象指向当前引用。这也就是大家认定的单链表方式。以此类?/span>…
而这里存在冲H对象的元素攑֜Entry对象中,Entryh以下一些属性:
int hash;
Object key;
Entry next;
对于Entry对象可以直接追溯到链表数据l构体中查阅?/span>
开攑֜址法:
1. U性地址探测法:
如何理解q个概念呢,一句话Q就是通过法规则在对象地址N+1中查阅找CؓNULL的烦引内宏V?/span>
处理方式Q如?/span>index索引与当前的index有冲H,x当前的烦?/span>index+1。如果在index+1已经存在占位现象Q?/span>index+1的内容不?/span>NULLQ试图接着index+2执行。。。直到找到烦引ؓ内容?/span>NULL的ؓ止。这U处理方式也叫:U性地址探测?/span>(offset-of-1)
如果采用U性地址探测法会带来一个效率的不良影响。现在我们来分析q种方式会带来哪些不良因素。大家试想下如果一个非常庞大的数据存储?/span>Map中,假如在某些记录集中有一些数据非常相|他们产生的烦引在内存的某个块中非常的密集Q,也就是说他们产生的烦引地址是一个连l数|而造成数据成块现象。另一个致命的问题是在数据删除后Q如果再ơ查询可能无法定C一个连l数字,q个又是一个什么概念呢Q例如以下图片就很好的说明开发地址散列如何把数据按照算法插入到对象中:
对于上图的注释步骤说明:
从数?#8220;78938-0000”开始通过哈希法按顺序依ơ插入到对象中,例如78938-0000通过?/span>
得到烦引ؓ0Q当前所指内容ؓNULL所以直接插入;45678-0001同样通过换算得到索引为地址501所指内容,当前内容?/span>NULL所以也可以插入Q?/span>72678-0002得到索引502所指内容,当前内容?/span>NULL也可以插入;h意当24678-0001得到索引也ؓ501Q当前地址所指内容ؓ45678-0001。即表示当前数据存在冲突Q则直接对地址501+1=502所指向内容?/span>72678-0002不ؓNULL也不允许插入Q再ơ对索引502+1=503所指内容ؓNULL允许插入。。。依ơ类推只要对于烦引存在冲H现象,则逐次下移位知道烦引地址所指ؓNULLQ如果烦引不冲突则还是按照算法放入内宏V对于这L对象是一U插入方式,接下来就是我们的删除(remove)Ҏ了。按照常理对于删除,方式基本区别不大。但是现在问题又出现了,如果删除的某个数据是一个存在冲H烦引的内容Q带来后l的问题又会接踵而来。那是什么问题呢Q我们还是同h看看囄的描qͼ对于?/span>-2中如果删?/span>(remove)数据24678-0001的方法如下图所C:
对于我们会想当然的觉得只要把指向数据|ؓNULL可?/span>,q样的做法对于删除来说当然是没有问题的。如果再ơ定位检索数?/span>16678-0001不会成功Q因个时候以前的链\已经堵上了,但是需要检索的数据事实上又存在。那我们如何来解册个问题呢Q对?/span>JDK中的EntrycM的方法存在一个:boolean markedForRemoval;它就是一个典型的删除标志位,对于对象中如果需要删除时Q我们只是对于它做一?#8220;软删?#8221;即置一个标志位?/span>true可以。而插入时Q默认状态ؓfalse可以。这L话就变成以下图所C:
通过以上方式更好的解军_H地址删除数据无法索其他链路数据问题了?/span>
2. 双散列(余商法)
在了解开攑֜址散列的时候我们一直在说解x法,但是大家都知道一个数据结构的完善更多的是需要高效的法。这当中我们却没有涉及到。接下来我们来看看在开攑֜址散列中它存在的一些不以及如何改善这LҎQ既而达到无论是在方法的解决上还是在法的复杂度上更加达到高效的Ҏ?/span>
在图2-1中类D样一些数据插入进对象Q存在冲H采用不断移位加一的方式,直到扑ֈ不ؓNULL内容的烦引地址。也正是׃q样一U可能加大了旉上的变慢。大家是否注意到像图q样一些数据目前呈现出一U连l烦引的插入Q而且是一U成块是的数据。如果数据量非常的庞大,或许q种可能性更大。尽它解决了冲H,但是对于数据索的旉度来_我们是不敢想象的。所有分布到同一个烦?/span>index上的key保持相同的\径:index,index+1,index+2…依此cL。更加糟p的是烦引键值的索需要从索引开始查找。正是这L原因Q对于线性探索法我们需要更q一步的改进。而刚才所描述q种成块出现的数据也定义成Q簇。而这样一U现象称之ؓQ主现象?/span>
Q主:是冲突处理允许加速增长时出现的现象)而开攑ּ地址冲突也是允许ȝ现象产生的。那我们如何来避免这U主现象呢Q这个方式就是我们要来说明的Q双散列解决冲突法了。主要的方式为:
u int hash=key.hasCode();
u int index=(hash&Ox7FFFFFFF)%table.length;
u 按照以上方式得到索引存在冲突Q则开始对当前索引UMQ而移位方式ؓQ?/span>
offset=(hash&Ox7FFFFFFF)/table.length;
u 如果W一ơ移位还存在同样的冲H,则l:当前冲突索引位置Q烦引号+余数Q?/span>%?/span>.length
u 如果存在的余数恰好是表的倍数Q则作偏UM|ؓ一下移Q依此类?/span>
q样双散列冲H处理就避免了主现象。至?/span>HashSet的原理基本和它是一致的Q这里不再复q。在q里其实q是主要说了一些简单的解决方式Q而且都是在一些具体参数满x件下的说明,像一旦数据超q初始D需?/span>rehashQ加载因子一旦大?/span>1.0是何U情늭{。还有很多问题都可以值得我们更加q一步讨论的Q比如:?/span>java.util.HashMap中的加蝲因子Z么会?/span>0.75Q而它默认的初始大ؓ什么又?/span>16{等q些问题都还值得说明。要说明q些问题可能又需要更加详的说明清楚?/span>