??xml version="1.0" encoding="utf-8" standalone="yes"?>
曄我也看过很多剖析memcached内部机制的文章,有一ҎP但是看过之后又忘CQ而且没有什么深ȝ概念Q但是最q我遇到一个问题,q个问题q我重新来认识memcacheQ下面我阐述一下我遇到的问?
问题Q我有几千万的数据,q些数据会经常被用到Q目前来看,它必要攑ֈmemcached中,以保证访问速度Q但是我的memcached中数据经怼有丢失,而业务需求是memcached中的数据是不能丢q。我的数据丢q时候,memcached server的内存才使用?0%Q也是q有40%内存被严重的费掉了。但不是所有的应用都是q样Q其他应用内存浪费的比较少。ؓ什么内存才使用?0%的时候LRU执行了呢(之所以确定是LRU执行是因为我发现我的数据丢失的L前面放进ȝQ而且q个q程中,q些数据都没有被讉KQ比如第一ơ访问的时候,只能讉KW?000w条,而第300w条或者之前的数据都已l丢׃Q从日志里看Q第300w条肯定是放进MQ?
带着q些疑问Q我开始重新审视memcachedq个产品Q首先从它的内存模型开始:我们知道c++里分配内存有两种方式Q预先分配和动态分配,昄Q预先分配内存会使程序比较快Q但是它的缺Ҏ不能有效利用内存Q而动态分配可以有效利用内存,但是会ɽE序q行效率下降Qmemcached的内存分配就是基于以上原理,昄Z获得更快的速度Q有时候我们不得不以空间换旉?
也就是说memcached会预先分配内存,对了Qmemcached分配内存方式UC为allocatorQ首先,q里?个概念:
1 slab
2 page
3 chunk
解释一下,一般来说一个memcahcedq程会预先将自己划分q个slabQ每个slab下又有若q个pageQ每个page下又有多个chunkQ如果我们把q?个咚咚看作是object得话Q这是两个一对多得关pR再一般来_slab得数量是有限得,几个Q十几个Q或者几十个Q这个跟q程配置得内存有兟뀂而每个slab下得page默认情况?mQ也是说如果一个slab占用100m得内存得话,那么默认情况下这个slab所拥有得page得个数就?00Q而chunk是我们得数据存攑־最l地斏V?
举一个例子,我启动一个memcachedq程Q占用内?00mQ再打开telnetQtelnet localhost 11211Q连接上memcache之后Q输入stats slabsQ回车,出现如下数据Q?
以上是?个slab得详l信?
chunk_size表示数据存放块得大小Qchunks_per_page表示一个内存页page中拥有得chunk得数量,total_pages表示每个slab下page得个数。total_chunks表示q个slab下chunk得LQ=total_pages * chunks_per_pageQ,used_chunks表示该slab下已l用得chunk得数量,free_chunks表示该slab下还可以使用得chunks数量?
从上面得CZslab 1一共有1m得内存空_而且现在已经被用完了Qslab2也有1m得内存空_也被用完了,slab3得情况依然如此?而且从这3个slab中chunk得size可以看出来,W一个chunk?0bQ第二个?00bQ第3个是128bQ基本上后一个是前一个得1.25倍,但是q个增长情况我们是可以控制得Q我们可以通过在启动时得进E参?–f来修改这个|比如?–f 1.1表示q个增长因子?.1Q那么第一个slab中得chunk?0b得话Q第二个slab中得chunk应该?0*1.1左右?
解释了这么多也该可以看出来我遇到得问题得原因了,如果q看不出来,那我再补充关键的一句:memcached中新的valueq来存放的地址是该value的大决定的QvalueL会被选择存放到chunk与其最接近的一个slab中,比如上面的例子,如果我的value?0bQ那么我q所有的valueL会被存放?号slab中,?号slab中的free_chunks已经?了,怎么办呢Q如果你在启动memcached的时候没有追?MQ禁止LRUQ这U情况下内存不够时会out of memoryQ,那么memcached会把q个slab中最q最被使用的chunk中的数据清掉Q然后放上最新的数据。这p释了Z么我的内存还?0%的时候LRU执行了Q因为我的其他slab中的chunk_size都远大于我的valueQ所以我的valueҎ不会攑ֈ那几个slab中,而只会放到和我的value最接近的chunk所在的slab?而这些slab早就满了Q郁闷了)。这导致了我的数据被不停的覆盖Q后者覆盖前者?
问题扑ֈ了,解决Ҏq是没有扑ֈQ因为我的数据必要求命中率?00%Q我只能通过调整slab的增长因子和page的大来量来命中率接q?00%Q但是ƈ不能100%保证命中率是100%Q这话怎么读v来这么别扭呢Q自我检讨一下自q语文水^Q,如果您说Q这U方案不行啊Q因为我的memcached server不能停啊Q不要紧q有另外一个方法,是memcached-toolQ执行move命oQ如Qmove 3 1Q代表把3号slab中的一个内存页Ud?号slab中,有h问了Q这有什么用呢,比如说我?0号slab的利用率非常低,但是page却又很多Q比?00Q那么就?00mQ?好slabl常发生LRUQ明显page不够Q我可以move 20 2Q把20号slab的一个内存页Ud?号slab上,q样p更加有效的利用内存了Q有了,一ơ只Ud一个pageQ多ȝ啊?ahuaxuan_q是写个脚本Q@环一下吧Q?
有h说不行啊Q我的memcache中的数据不能丢失啊,okQ试试新的memcachedb吧,虽然我没有用q,但是大家可以试试Q它也利用memcache协议和berkeleyDB做的Q写到这里,我不得不佩服danga了,我觉得它最大的贡献不是memcache server本nQ而是memcache协议Q,据说它被用在新浪的不应用上Q包括新的博客?
补充Qstats slab命o可以查看memcached中slab的情况,而stats命o可以查看你的memcached的一些健h况,比如说命中率之类的,CZ如下Q?
从上面的数据可以看到q个memcachedq程的命中率很好Qget_misses低达0个,怎么回事啊,因ؓq个q程使我刚启动的Q我只用telnetq了一下,所以curr_connections?Q而total_items?Q因为我没有放数据进去,get_hits?Q因为我没有调用getҎQ最后的l果是misses当然?Q哇哦,换句话说命中率就?00%Q又yy了?
该到ȝ的时候了Q从q篇文章里我们可以得C下几个结论:
l论一Qmemcached得LRU不是全局的,而是针对slab的,可以说是区域性的?
l论二,要提高memcached的命中率Q预估我们的value大小q且适当的调整内存页大小和增长因子是必须的?
l论三,带着问题扄案理解的要比随便看看的效果好得多?
]]>
]]>
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>
]]>
]]>
վ֩ģ壺
˾Ʒþ|
Ʒһ߹ۿ|
þþþþþAv
|
Ƶ߹ۿѲӰԺ|
Ƭ51˿Ӱ|
ŷһվ7777|
ԻƤȫƵѹ30|
ƷŮþþþþþ|
վѹۿ|
ҹƵ
|
߿ƬѲ˳Ƶ|
ƷҹӰ|
AƬһ|
˳վ߹ۿ|
ҰƵѹۿ|
jizzձ|
AVþþƷ|
˾Ʒҹapp|
ŷպĶ|
һëƬ|
þþƷƵ|
av벻þ|
һëƬ߲Ųշ|
ŮƵۿ|
վ߲|
ձһ|
aƵ߹ۿ|
պ ɫ ͼվ|
Ʒmnbavվ|
˾Ʒѿ|
ĻƷ|
Ʒva߹ۿѿ|
þþþó˾ƷѲŶ|
AVۺɫAV|
һһëƬ|
99þ99þѾƷС˵|
Ʒ|
ۺɫһС˵|
AҹƬƷվ|
99Ƶ߾Ʒ|
gayˬˬƵ|