• Java的java.util.Hashtable.java研究


      Hashtable用一个内部数组用于存放数据:private transient Entry[] table;

      Entry是一个内部类:private static class Entry<K,V> implements Map.Entry<K,V>

      Entry类似于链表中的节点类,主要属性是hash,key,value,next:

    int hash;
    K key;
    V value;
    Entry<K,V> next;

      它们的关系为:例如myHashtable.put(myKey,myValue),则在内部数组中,某下标处,多出一个链表节点myEntry。该节点的数据是:

    myEntry.hash = myKey.hashCode();

    myEntry.key = myKey;

    myEntry.value = myValue;

    myEntry.next = previousEntry;//(上一个存放在此的数据)

      Hashtable的结构为:

      

      椭圆形是Entry[] table,桶形是Entry。后插入的Entry会被放在查找的前面。

      可以看出Entry[] table的每个节点事实上可以存放多条数据,但会降低读取速度。

      Hashtable的读取不是像链表一样一个个找的,而是用key通过某函数f(key)直接计算出它在(或可能在)Entry[] table数组中的下标。所以Hashtable的主要算法是一个函数f(key),该函数的结果是该key在(或可能在)Entry[] table中的下标,然后再在此下标处查找链表中的每一个结点。

      显然,Hashtable的主要性能和特性都和f(key)有很大关系,在java的Hashtable中,是这样计算的:

     Entry tab[] = table;
     int hash = key.hashCode();
     int index = (hash & 0x7FFFFFFF) % tab.length;//index事实上就是f(key)

      用key的hash和0x7FFFFFFF取位与运算,这会将key.hashCode()的高位去除,再取它与table.length的模,得到的结果index必有0<=index<table.length。

      其它f(key)的思路有:

      1、f(key) = key.hashCode() & (table.length-1),此时table的长度应为2^n,这样位与运算时,table.length-1的低位都为1,而key.hashCode()的高位都被去除。否则比如若table.length=5(101),则hash&(length-1)只有两种结果:0(0)和4(100)。所以table中的1,2,3下标处不会有数据,大幅度影响性能。(*一)

      2、有的f(key)得到index后,若该位置有值(Entry),则把index增加一个增量i,直到找到空位置,若到达结尾就从开关再找。这种结构下hash中不存在链表,所以读取速度提高,但写入速度降低,而且可存放的数据小于Entry[]的长度。在这种情况下,table.length最好是一个质数,可以有效提高table中元素的利用率,具体原因就不再赘述了。(*二)

      在java中,Hashtable.new有两个重要参数:public Hashtable(int initialCapacity, float loadFactor)

      int initialCapacity是初始容量,也就是一开始时Entry[] table的length;

      float loadFactor是加载因子,该因子反映了Entry[] table中数据能存放的密度,如:initialCapacity=15,loadFactor=0.5,则该Hashtable最多存放7个数据。注意,在java中,该值是可以大于1的。在上述(*二)中,该值不能大于1。

      显然,loadFactor的取值对Hashtable的性能有很大影响,想像一个极端的情况:initialCapacity=1,loadFactor=100,那个该Hashtable能存放100个数据,但它却成为了一个链表,并且比普通链表的效率还低(另外Hashtable的链表是不会有重复值的)。loadFactor的值越小,就越节省内存(Entry[] table中的空元素变少),但读取速度变慢(Entry[] table中元素的链表变长)。

      找到一个合适的loadFactor能提高其总体运行速度,java把这个值设为了0.75:

    public Hashtable(int initialCapacity) {
    this(initialCapacity, 0.75f);
    }

      一般loadFactor在1以下,而事实上在很多f(key)的算法中,该值是可以大于1的,如java.util.Hashtable。

      了解上述后,Hashtable的get、put、remove操作就变得很简单了:

      get:通过f(key)算出index,然后遍历链表。

      put:通过f(key)算出index,然后遍历链表,若key已经存在,则改变value,否则插入新的链表节点。其中可能存在rehash操作。

      remove:通过f(key)算出index,然后遍历链表,若key已经存在,则进行链表的删除操作。

      当Entry[] table中的元素个数超过了initialCapacity*loadFactor,Hashtable就要进行扩容:

    protected void rehash()

      rehash的过程说起来很简单:提高initialCapacity的值,把原Entry[] table中的值复制到新Entry[] table,复制时因为table.length有变化,所以需要重新计算每个元素的index,即f(key)。复制时需重新计算所有元素(包括链表中的)的新index。

      显然rehash是非常耗时的,它也是影响插入数据的速度的很重要的一点。所以如果要进行多次插入操作,可以在Hashtable.new的时候,取一个较大的initialCapacity,这样能显著提高运行速度。在java中,rehash对容量的提升是:

    int newCapacity = oldCapacity * 2 + 1;

      java的想法似乎是希望所有table.length都是(2^n)-1,而事实上在java的f(key)算法int index = (hash & 0x7FFFFFFF) % tab.length;中,该值对于Entry[] table中元素的链表长度影响并不大,在(*一)中,该值最好是2^n。

      为了更好理解和测试Hashtable,我根据java的Hashtable的算法和思想,写了一个ruby版的hashtable。

      附上代码:

    class Myhashdata
      attr_accessor :hash, :key, :value, :nextdata
    
      def initialize hash, key, value, nextdata
        @hash = hash
        @key = key
        @value = value
        @nextdata = nextdata
      end
    end
    
    class Myhash
    
      @@defalutloadfactor = 0.75
    
      def initialize capacity, loadfactor = @@defalutloadfactor
        @capacity = capacity
        @loadfactor = loadfactor
        @threshold = @capacity*@loadfactor
        @dataarr = []
        @count = 0
      end
    
      def msg
        puts '@capacity:' + @capacity.to_s
        puts '@threshold:' + @threshold.to_s
        puts '@count:' + @count.to_s
      end
    
      def printall
        msg
        for i in 0...@capacity
          data = @dataarr[i]
          puts 'index:'+i.to_s
          while !data.nil?
            puts "\tkey:" + data.key.to_s
            puts "\tvalue:" + data.value.to_s
            data = data.nextdata
          end
        end
      end
    
      def gethash obj
        return obj.hash
      end
    
      def getindex hash
        return (hash&2147483647)%@capacity
    #    return mailto:hash&@capacity
      end
    
      def put key, value
        hash = gethash(key)
        index = getindex(hash)
        tmpdata = @dataarr[index]
    
        while !tmpdata.nil?
          if tmpdata.key==key && tmpdata.hash==hash
            oldvalue = tmpdata.value
            tmpdata.value = value
            return oldvalue
          end
          tmpdata = tmpdata.nextdata
        end
    
        if @count >= @threshold
    #      rehash
          puts 'count out of threshold, needs rehash'
        end
    
        tmpdata = @dataarr[index]
        @dataarr[index] = Myhashdata.new(hash, key, value, tmpdata)
        @count = @count +1
        return nil
      end
    
      def get key
        hash = gethash(key)
        index = getindex(hash)
        tmpdata = @dataarr[index]
        while !tmpdata.nil?
          if tmpdata.key==key && tmpdata.hash==hash
            return tmpdata.value
          end
          tmpdata = tmpdata.nextdata
        end
        return nil
      end
    
      def remove key
        hash = gethash(key)
        index = getindex(hash)
        tmpdata = @dataarr[index]
        prev = nil
        while !tmpdata.nil?
          if tmpdata.key==key && tmpdata.hash==hash
            if prev.nil?
              @dataarr[index] = tmpdata.nextdata
            else
              prev.nextdata = tmpdata.nextdata
            end
            @count = @count - 1
            return tmpdata.value
          end
          prev = tmpdata
          tmpdata = tmpdata.nextdata
        end
        return nil
      end
      
      def rehash newcapacity = nil
        oldcapacity = @capacity
        if !newcapacity.nil? && newcapacity>oldcapacity
          @capacity = newcapacity
        else
          @capacity = oldcapacity*2-1
        end
        @threshold = @capacity*@loadfactor
        newdataarr = []
        for i in 0...oldcapacity
          tmpdata = @dataarr[i]
          while !tmpdata.nil?
            olddata = tmpdata.nextdata
            index = getindex(tmpdata.hash)
            tmpdata.nextdata = newdataarr[index]
            newdataarr[index] = tmpdata
    
            tmpdata = olddata
          end
        end
        @dataarr = newdataarr
      end
      
    end
    

      

  • 相关阅读:
    银行家算法实例(转)
    DNS中的七大资源记录介绍!(转)
    android之存储篇_SQLite数据库_让你彻底学会SQLite的使用(转)
    回顾HTML5的语义化元素
    vueJs2.0学习笔记(六)
    vueJs2.0学习笔记(五)
    vueJs的学习笔记(四)
    vueJs2.0学习笔记(三)
    vueJs的学习笔记(二)
    vueJs 2.0学习笔记(一)
  • 原文地址:https://www.cnblogs.com/zycjwdss/p/1816873.html
Copyright © 2020-2023  润新知