HashMap 并发 Map 是什么 内部原理什么 存储方式 hashcode 扩容 默认容量等

1. HashMap的数据结构

http://blog.csdn.net/gaopu12345/article/details/50831631   ??看一下java

 

数据结构中有数组和链表来实现对数据的存储,但这二者基本上是两个极端。算法

  数组

数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特色是:寻址容易,插入和删除困难。数组

链表

链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特色是:寻址困难,插入和删除容易。数据结构

哈希表

那么咱们能不能综合二者的特性,作出一种寻址容易,插入删除也容易的数据结构?答案是确定的,这就是咱们要提起的哈希表。哈希表((Hash table)既知足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。less

  哈希表有多种不一样的实现方法,我接下来解释的是最经常使用的一种方法—— 拉链法,咱们能够理解为“链表的数组” ,如图:dom

 

 

  从上图咱们能够发现哈希表是由数组+链表组成的,一个长度为16的数组中,每一个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。通常状况是经过hash(key)%len得到,也就是元素的key的哈希值对数组长度取模获得。好比上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。因此十二、2八、108以及140都存储在数组下标为12的位置。函数

  HashMap其实也是一个线性的数组实现的,因此能够理解为其存储数据的容器就是一个线性数组。这可能让咱们很不解,一个线性的数组怎么实现按键值对来存取数据呢?这里HashMap有作一些处理。性能

  首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value咱们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,咱们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。测试

    /**优化

     * The table, resized as necessary. Length MUST Always be a power of two.

     */

    transient Entry[] table;

2. HashMap的存取实现

     既然是线性数组,为何能随机存取?这里HashMap用了一个小算法,大体是这样实现:

// 存储时:
int hash = key.hashCode(); // 这个hashCode方法这里不详述,只要理解每一个key的hash是一个固定的int值
int index = hash % Entry[].length;
Entry[index] = value;

// 取值时:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];

 

1)put

疑问:若是两个key经过hash%Entry[].length获得的index相同,会不会有覆盖的危险?

  这里HashMap里面用到链式数据结构的一个概念。上面咱们提到过Entry类里面有一个next属性,做用是指向下一个Entry。打个比方, 第一个键值对A进来,经过计算其key的hash获得的index=0,记作:Entry[0] = A。一会后又进来一个键值对B,经过计算其index也等于0,如今怎么办?HashMap会这样作:B.next = A,Entry[0] = B,若是又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样咱们发现index=0的地方其实存取了A,B,C三个键值对,他们经过next这个属性连接在一块儿。因此疑问不用担忧。也就是说数组中存储的是最后插入的元素。到这里为止,HashMap的大体实现,咱们应该已经清楚了。

 public V put(K key, V value) {

        if (key == null)

            return putForNullKey(value); //null老是放在数组的第一个链表中

        int hash = hash(key.hashCode());

        int i = indexFor(hash, table.length);

        //遍历链表

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {

            Object k;

            //若是key在链表中已存在,则替换为新value

            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

        }

 

        modCount++;

        addEntry(hash, key, value, i);

        return null;

    }

 

void addEntry(int hash, K key, V value, int bucketIndex) {

    Entry<K,V> e = table[bucketIndex];

    table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //参数e, 是Entry.next

    //若是size超过threshold,则扩充table大小。再散列

    if (size++ >= threshold)

            resize(2 * table.length);

}

  固然HashMap里面也包含一些优化方面的实现,这里也说一下。好比:Entry[]的长度必定后,随着map里面数据的愈来愈长,这样同一个index的链就会很长,会不会影响性能?HashMap里面设置一个因子,随着map的size愈来愈大,Entry[]会以必定的规则加长长度。

2)get

 public V get(Object key) {

        if (key == null)

            return getForNullKey();

        int hash = hash(key.hashCode());

        //先定位到数组元素,再遍历该元素处的链表

        for (Entry<K,V> e = table[indexFor(hash, table.length)];

             e != null;

             e = e.next) {

            Object k;

            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

                return e.value;

        }

        return null;

}

 

3)null key的存取

null key老是存放在Entry[]数组的第一个元素。

   private V putForNullKey(V value) {

        for (Entry<K,V> e = table[0]; e != null; e = e.next) {

            if (e.key == null) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

        }

        modCount++;

        addEntry(0, null, value, 0);

        return null;

    }

 

    private V getForNullKey() {

        for (Entry<K,V> e = table[0]; e != null; e = e.next) {

            if (e.key == null)

                return e.value;

        }

        return null;

    }

 

4)肯定数组index:hashcode % table.length取模

HashMap存取时,都须要计算当前key应该对应Entry[]数组哪一个元素,即计算数组下标;算法以下:

   /**

     * Returns index for hash code h.

     */

    static int indexFor(int h, int length) {

        return h & (length-1);

    }

 

按位取并,做用上至关于取模mod或者取余%。

这意味着数组下标相同,并不表示hashCode相同。

5)table初始大小

 

  public HashMap(int initialCapacity, float loadFactor) {

        .....

 

        // Find a power of 2 >= initialCapacity

        int capacity = 1;

        while (capacity < initialCapacity)

            capacity <<= 1;

 

        this.loadFactor = loadFactor;

        threshold = (int)(capacity * loadFactor);

        table = new Entry[capacity];

        init();

    }

 

注意table初始大小并非构造函数中的initialCapacity!!

而是 >= initialCapacity的2的n次幂!!!!

————为何这么设计呢?—— 

3. 解决hash冲突的办法 (下文详解)

  1. 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
  2. 再哈希法
  3. 链地址法
  4. 创建一个公共溢出区

Java中hashmap的解决办法就是采用的链地址法。

 

4. 再散列rehash过程

当哈希表的容量超过默认容量时,必须调整table的大小。当容量已经达到最大可能值时,那么该方法就将容量调整到Integer.MAX_VALUE返回,这时,须要建立一张新表,将原表的映射到新表中。

   /**

     * Rehashes the contents of this map into a new array with a

     * larger capacity.  This method is called automatically when the

     * number of keys in this map reaches its threshold.

     *

     * If current capacity is MAXIMUM_CAPACITY, this method does not

     * resize the map, but sets threshold to Integer.MAX_VALUE.

     * This has the effect of preventing future calls.

     *

     * @param newCapacity the new capacity, MUST be a power of two;

     *        must be greater than current capacity unless current

     *        capacity is MAXIMUM_CAPACITY (in which case value

     *        is irrelevant).

     */

    void resize(int newCapacity) {

        Entry[] oldTable = table;

        int oldCapacity = oldTable.length;

        if (oldCapacity == MAXIMUM_CAPACITY) {

            threshold = Integer.MAX_VALUE;

            return;

        }

 

        Entry[] newTable = new Entry[newCapacity];

        transfer(newTable);

        table = newTable;

        threshold = (int)(newCapacity * loadFactor);

    }

 

    /**

     * Transfers all entries from current table to newTable.

     */

    void transfer(Entry[] newTable) {

        Entry[] src = table;

        int newCapacity = newTable.length;

        for (int j = 0; j < src.length; j++) {

            Entry<K,V> e = src[j];

            if (e != null) {

                src[j] = null;

                do {

                    Entry<K,V> next = e.next;

                    //从新计算index

                    int i = indexFor(e.hash, newCapacity);

                    e.next = newTable[i];

                    newTable[i] = e;

                    e = next;

                } while (e != null);

            }

        }

    }

 

=============================     华丽的分割线   ==============

hashmap的扩容机制

一、当咱们往hashmap中put元素的时候,先根据key的hash值获得这个元素在 数组中的位置(即下标),而后就能够把这个元素放到对应的位置中了。若是这个元素所在的位子上已经存放有其余元素了,那么在同一个位子上的元素将以链表的 形式存放,新加入的放在链头,好比a->b->c,新加入的d放到a的位置前面,最早加入的放在链尾,也就是c。最后变成d->a->b->c,从hashmap中get元素时,首先计算key的hashcode,找到数组中对应位置的某一元素, 而后经过key的equals方法在对应位置的链表中找到须要的元素。

二、

在hashmap中要找到某个元素,须要根据key的hash值来求得对应数组中的位置。如何计算这个位置就是hash算法。前面说过hashmap的数据结构是数组和链表的结合,因此咱们固然但愿这个hashmap里面的元素位置尽可能的分布均匀些,尽可能使得每一个位置上的元素数量只有一个,那么当咱们用hash算法求得这个位置 的时候,立刻就能够知道对应位置的元素就是咱们要的,而不用再去遍历链表。因此咱们首先想到的就是把hashcode对数组长度取模运算,这样一来,元素的分布相对来讲是比较均匀的。可是,“模”运算的消耗仍是比较大的,能不能找一种更快速,消耗更小的方式那?java中时这样作的,

Java代码

staticintindexFor(inth,intlength){ 
returnh&(length-1); 
}

首 先算得key得hashcode值,而后跟数组的长度-1作一次“与”运算(&)。看上去很简单,其实比较有玄机。好比数组的长度是2的4次方, 那么hashcode就会和2的4次方-1作“与”运算。不少人都有这个疑问,为何hashmap的数组初始化大小都是2的次方大小时,hashmap 的效率最高,我以2的4次方举例,来解释一下为何数组大小为2的幂时hashmap访问的性能最高。 看下图,左边两组是数组长度为16(2的4次方),右边两组是数组长度为15。两组的hashcode均为8和9,可是很明显,当它们和1110“与”的 时候,产生了相同的结果,也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到同一个链表上,那么查询的时候就须要遍历这个链 表,获得8或者9,这样就下降了查询的效率。同时,咱们也能够发现,当数组长度为15的时候,hashcode的值会与14(1110)进行“与”,那么 最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费至关大,更糟的是 这种状况中,数组可使用的位置比数组长度小了不少,这意味着进一步增长了碰撞的概率,减慢了查询的效率!因此说,当数组长度为2的n次幂的时候,不一样的key算得得index相同的概率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的概率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。说到这里,咱们再回头看一下hashmap中默认的数组大小是多少,查看源代码能够得知是16,为何是16,而不是15,也不是20呢,看到上面 annegu的解释以后咱们就清楚了吧,显然是由于16是2的整数次幂的缘由,在小数据量的状况下16比15和20更能减小key之间的碰撞,而加快查询 的效率。

三、

当hashmap中的元素愈来愈多的时候,碰撞的概率也就愈来愈高(由于数组的长度是固定的),因此为了提升查询的效率,就要对hashmap的数组进行 扩容,数组扩容这个操做也会出如今ArrayList中,因此这是一个通用的操做,不少人对它的性能表示过怀疑,不过想一想咱们的“均摊”原理,就释然了, 而在hashmap数组扩容以后,最消耗性能的点就出现了:原数组中的数据必须从新计算其在新数组中的位置,并放进去,这就是resize。

那么hashmap何时进行扩容呢?当hashmap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的 默认值为0.75,也就是说,默认状况下,数组大小为16,那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,而后从新计算每一个元素在数组中的位置,而这是一个很是消耗性能的操做,因此若是咱们已经预知hashmap中元素的个数,那 么预设元素的个数可以有效的提升hashmap的性能。

好比说,咱们有1000个元素new HashMap(1000), 可是理论上来说new HashMap(1024)更合适,不过上面annegu已经说过,即便是1000,hashmap也自动会将其设置为1024。 可是new HashMap(1024)还不是更合适的,由于0.75*1000 < 1000, 也就是说为了让0.75 * size > 1000, 咱们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。

 

======================= 华丽的分割线     =====================================

哈希表及处理hash冲突的方法

看了ConcurrentHashMap的实现, 使用的是拉链法.

虽然咱们不但愿发生冲突,但实际上发生冲突的可能性还是存在的。当关键字值域远大于哈希表的长度,并且事先并不知道关键字的具体取值时。冲突就不免会发生。

另外,当关键字的实际取值大于哈希表的长度时,并且表中已装满了记录,若是插入一个新记录,不只发生冲突,并且还会发生溢出。

所以,处理冲突和溢出是哈希技术中的两个重要问题。

 

 

哈希法又称散列法、杂凑法以及关键字地址计算法等,相应的表称为哈希表。这种方法的基本思想是:首先在元素的关键字k和元素的存储位置p之间创建一个对应关系f,使得p=f(k),f称为哈希函数。建立哈希表时,把关键字为k的元素直接存入地址为f(k)的单元;之后当查找关键字为k的元素时,再利用哈希函数计算出该元素的存储位置p=f(k),从而达到按关键字直接存取元素的目的。

   当关键字集合很大时,关键字值不一样的元素可能会映象到哈希表的同一地址上,即 k1≠k2 ,但 H(k1)=H(k2),这种现象称为冲突,此时称k1和k2为同义词。实际中,冲突是不可避免的,只能经过改进哈希函数的性能来减小冲突。

综上所述,哈希法主要包括如下两方面的内容:

 1)如何构造哈希函数

 2)如何处理冲突。

8.4.1   哈希函数的构造方法

    构造哈希函数的原则是:①函数自己便于计算;②计算出来的地址分布均匀,即对任一关键字k,f(k) 对应不一样地址的几率相等,目的是尽量减小冲突。

下面介绍构造哈希函数经常使用的五种方法。

1. 数字分析法

      若是事先知道关键字集合,而且每一个关键字的位数比哈希表的地址码位数多时,能够从关键字中选出分布较均匀的若干位,构成哈希地址。例如,有80个记录,关键字为8位十进制整数d1d2d3…d7d8,如哈希表长取100,则哈希表的地址空间为:00~99。假设通过分析,各关键字中 d4和d7的取值分布较均匀,则哈希函数为:h(key)=h(d1d2d3…d7d8)=d4d7。例如,h(81346532)=43,h(81301367)=06。相反,假设通过分析,各关键字中 d1和d8的取值分布极不均匀, d1 都等于5,d8 都等于2,此时,若是哈希函数为:h(key)=h(d1d2d3…d7d8)=d1d8,则全部关键字的地址码都是52,显然不可取。

2. 平方取中法

当没法肯定关键字中哪几位分布较均匀时,能够先求出关键字的平方值,而后按须要取平方值的中间几位做为哈希地址。这是由于:平方后中间几位和关键字中每一位都相关,故不一样关键字会以较高的几率产生不一样的哈希地址。

例:咱们把英文字母在字母表中的位置序号做为该英文字母的内部编码。例如K的内部编码为11,E的内部编码为05,Y的内部编码为25,A的内部编码为01, B的内部编码为02。由此组成关键字“KEYA”的内部代码为11052501,同理咱们能够获得关键字“KYAB”、“AKEY”、“BKEY”的内部编码。以后对关键字进行平方运算后,取出第7到第9位做为该关键字哈希地址,如图8.23所示。

 

 

关键字

内部编码

内部编码的平方值

H(k)关键字的哈希地址

KEYA

11050201

122157778355001

778

KYAB

11250102

126564795010404

795

AKEY

01110525

001233265775625

265

BKEY

02110525

004454315775625

315

图8.23平方取中法求得的哈希地址

3. 分段叠加法

      这种方法是按哈希表地址位数将关键字分红位数相等的几部分(最后一部分能够较短),而后将这几部分相加,舍弃最高进位后的结果就是该关键字的哈希地址。具体方法有折叠法移位法。移位法是将分割后的每部分低位对齐相加,折叠法是从一端向另外一端沿分割界来回折叠(奇数段为正序,偶数段为倒序),而后将各段相加。例如:key=12360324711202065,哈希表长度为1000,则应把关键字分红3位一段,在此舍去最低的两位65,分别进行移位叠加和折叠叠加,求得哈希地址为105和907,如图8.24所示。

 

 

1   2   3                    1   2   3

6   0   3                    3   0   6

2   4   7                    2   4   7

1   1   2                    2   1   1

+)   0   2   0               +)  0   2   0

        ————————            —————————

        1   1   0   5                    9   0   7

 

(a)移位叠加                    (b) 折叠叠加

 

                      图8.24 由叠加法求哈希地址

 

4. 除留余数法

假设哈希表长为m,p为小于等于m的最大素数,则哈希函数为

h(k)=k  %  p ,其中%为模p取余运算。

例如,已知待散列元素为(18,75,60,43,54,90,46),表长m=10,p=7,则有

    h(18)=18 % 7=4    h(75)=75 % 7=5    h(60)=60 % 7=4   

    h(43)=43 % 7=1    h(54)=54 % 7=5    h(90)=90 % 7=6   

    h(46)=46 % 7=4

此时冲突较多。为减小冲突,可取较大的m值和p值,如m=p=13,结果以下:

    h(18)=18 % 13=5    h(75)=75 % 13=10    h(60)=60 % 13=8    

    h(43)=43 % 13=4    h(54)=54 % 13=2    h(90)=90 % 13=12   

    h(46)=46 % 13=7

此时没有冲突,如图8.25所示。

 

0      1      2     3     4     5      6     7     8     9     10     11    12

 

 

 

54

 

43

18

 

46

60

 

75

 

90

                      图8.25  除留余数法求哈希地址

 

5. 伪随机数法

    采用一个伪随机函数作哈希函数,即h(key)=random(key)。

在实际应用中,应根据具体状况,灵活采用不一样的方法,并用实际数据测试它的性能,以便作出正确断定。一般应考虑如下五个因素 :

l         计算哈希函数所需时间 (简单)。

l         关键字的长度。

l         哈希表大小。

l         关键字分布状况。

l         记录查找频率

8.4.2   处理冲突的方法

   经过构造性能良好的哈希函数,能够减小冲突,但通常不可能彻底避免冲突,所以解决冲突是哈希法的另外一个关键问题。建立哈希表和查找哈希表都会遇到冲突,两种状况下解决冲突的方法应该一致。下面以建立哈希表为例,说明解决冲突的方法。经常使用的解决冲突方法有如下四种:

1.         开放定址法

这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另外一个哈希地址p1,若是p1仍然冲突,再以p为基础,产生另外一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。这种方法有一个通用的再散列函数形式:

          Hi=(H(key)+di)% m   i=1,2,…,n

    其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不一样,相应的再散列方式也不一样。主要有如下三种:

l         线性探测再散列

    dii=1,2,3,…,m-1

这种方法的特色是:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。

l         二次探测再散列

    di=12,-12,22,-22,…,k2,-k2    ( k<=m/2 )

    这种方法的特色是:冲突发生时,在表的左右进行跳跃式探测,比较灵活。

l         伪随机探测再散列

    di=伪随机数序列。

具体实现时,应创建一个伪随机数发生器,(如i=(i+p) % m),并给定一个随机数作起点。

例如,已知哈希表长度m=11,哈希函数为:H(key)= key  %  11,则H(47)=3,H(26)=4,H(60)=5,假设下一个关键字为69,则H(69)=3,与47冲突。若是用线性探测再散列处理冲突,下一个哈希地址为H1=(3 + 1)% 11 = 4,仍然冲突,再找下一个哈希地址为H2=(3 + 2)% 11 = 5,仍是冲突,继续找下一个哈希地址为H3=(3 + 3)% 11 = 6,此时再也不冲突,将69填入5号单元,参图8.26 (a)。若是用二次探测再散列处理冲突,下一个哈希地址为H1=(3 + 12)% 11 = 4,仍然冲突,再找下一个哈希地址为H2=(3 - 12)% 11 = 2,此时再也不冲突,将69填入2号单元,参图8.26 (b)。若是用伪随机探测再散列处理冲突,且伪随机数序列为:2,5,9,……..,则下一个哈希地址为H1=(3 + 2)% 11 = 5,仍然冲突,再找下一个哈希地址为H2=(3 + 5)% 11 = 8,此时再也不冲突,将69填入8号单元,参图8.26 (c)。

 

 

0        1       2      3      4      5       6      7      8       9      10    

 

 

 

 

47

26

60

69

 

 

 

 

         (a) 用线性探测再散列处理冲突

 

 

0        1       2      3      4      5       6      7      8       9      10    

 

 

 

69

47

26

60

 

 

 

 

 

         (b) 用二次探测再散列处理冲突

 

 

0        1       2      3      4      5       6      7      8       9      10    

 

 

 

 

47

26

60

 

 

69

 

 

         (c) 用伪随机探测再散列处理冲突

 

                      图8.26开放地址法处理冲突

从上述例子能够看出,线性探测再散列容易产生“二次汇集”,即在处理同义词的冲突时又致使非同义词的冲突。例如,当表中i, i+1 ,i+2三个单元已满时,下一个哈希地址为i, 或i+1 ,或i+2,或i+3的元素,都将填入i+3这同一个单元,而这四个元素并不是同义词。线性探测再散列的优势是:只要哈希表不满,就必定能找到一个不冲突的哈希地址,而二次探测再散列和伪随机探测再散列则不必定。

2.         再哈希法

    这种方法是同时构造多个不一样的哈希函数:

    Hi=RH1(key)  i=1,2,…,k

当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突再也不产生。这种方法不易产生汇集,但增长了计算时间。

3.         链地址法

    这种方法的基本思想是将全部哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,于是查找、插入和删除主要在同义词链中进行。链地址法适用于常常进行插入和删除的状况。

例如,已知一组关键字(32,40,36,53,16,46,71,27,42,24,49,64),哈希表长度为13,哈希函数为:H(key)= key % 13,则用链地址法处理冲突的结果如图8.27所示:

 

 
  哈希表及处理冲突的方法(转) - 另外一片天空 - 仰望天空

 

图8.27  链地址法处理冲突时的哈希表

 

本例的平均查找长度 ASL=(1*7+2*4+3*1)=1.5

 

 

         

(2)拉链法的优势

与开放定址法相比,拉链法有以下几个优势:

①拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,所以平均查找长度较短;

②因为拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前没法肯定表长的状况;

③开放定址法为减小冲突,要求装填因子α较小,故当结点规模较大时会浪费不少空间。而拉链法中可取α≥1,且结点较大时,拉链法中增长的指针域可忽略不计,所以节省空间;

④在用拉链法构造的散列表中,删除结点的操做易于实现。只要简单地删去链表上相应的结点便可。而对开放地址法构造的散列表,删除结点不能简单地将被删结 点的空间置为空,不然将截断在它以后填人散列表的同义词结点的查找路径。这是由于各类开放地址法中,空地址单元(即开放地址)都是查找失败的条件。所以在 用开放地址法处理冲突的散列表上执行删除操做,只能在被删结点上作删除标记,而不能真正删除结点。

 

(3)拉链法的缺点

     拉链法的缺点是:指针须要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可以使装填因子变小,这又减小了开放定址法中的冲突,从而提升平均查找速度。??????

 

4、创建公共溢出区

这种方法的基本思想是:将哈希表分为基本表溢出表两部分,凡是和基本表发生冲突的元素,一概填入溢出表

相关文章
相关标签/搜索