深入PHP中的HashTable结构详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入PHP中的HashTable结构详解相关的知识,希望对你有一定的参考价值。

参考技术A

深入PHP中的HashTable结构详解

  深入PHP中的HashTable结构详解

  对php内核有一定了解的人应该都知道php的精髓就是HashTable,HashTable在php的实现中无处不在。包括php的数组、什么全局变量、局部变量的作用域等等,php的hashtable拆开来说就是四部分:

  hash函数:用的是time33的散列函数,将一个字符串的key转换成一个数字

  一个C数组:用来储存桶(buckets)的

  两个双向的链表:第一个双向链表是数组的每个元素(桶bucket)是一个双向链表,这样做是为了解决hash冲突;第二个双向链表是数组将每一个桶(bucket)连接起来,这里要连接的也就是第一个双向链表的链表头,这样做是为了遍历整个hash表用的,鸟哥有篇blog是讲php的foreach的,这里这样设计就是给foreach用的==>《深入理解PHP之数组(遍历顺序)》

  我这里不再说hashtable的struct和bucket的`struct了,因为下面的推荐链接几乎都讲了,我不觉得我能描述和说的比他们好,每个人的水平不一样,我就以我现在的技术水平来描述,所以我就只把我整理的一些东西记录一下

  下面是php中hash实现的两个文件:zend_hash.c zend_hash.h。这两个文件里面实现了一堆的api,也引申出了一堆的api,下面是实现出来的api的原型

  复制代码 代码如下:

  ZEND_API ulong zend_hash_func(const char *arKey, uint nKeyLength)

  ZEND_API ulong zend_get_hash_value(const char *arKey, uint nKeyLength)

  ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)

  ZEND_API void zend_hash_set_apply_protection(HashTable *ht, zend_bool bApplyProtection)

  ZEND_API int _zend_hash_add_or_update(HashTable *ht, const char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC)

  ZEND_API int _zend_hash_quick_add_or_update(HashTable *ht, const char *arKey, uint nKeyLength, ulong h, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC)

  ZEND_API int _zend_hash_index_update_or_next_(HashTable *ht, ulong h, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC)

  ZEND_API int zend_hash_rehash(HashTable *ht)

  static int zend_hash_do_resize(HashTable *ht)

  ZEND_API int zend_hash_del_key_or_index(HashTable *ht, const char *arKey, uint nKeyLength, ulong h, int flag)

  ZEND_API void zend_hash_destroy(HashTable *ht)

  ZEND_API void zend_hash_clean(HashTable *ht)

  static Bucket *zend_hash_apply_r(HashTable *ht, Bucket *p)

  ZEND_API void zend_hash_graceful_destroy(HashTable *ht)

  ZEND_API void zend_hash_graceful_reverse_destroy(HashTable *ht)

  ZEND_API void zend_hash_apply(HashTable *ht, apply_func_t apply_func TSRMLS_DC)

  ZEND_API void zend_hash_apply_with_argument(HashTable *ht, apply_func_arg_t apply_func, void *argument TSRMLS_DC)

  ZEND_API void zend_hash_apply_with_arguments(HashTable *ht TSRMLS_DC, apply_func_args_t apply_func, int num_args, …)

  ZEND_API void zend_hash_reverse_apply(HashTable *ht, apply_func_t apply_func TSRMLS_DC)

  ZEND_API void zend_hash_copy(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, void *tmp, uint size)

  ZEND_API void _zend_hash_merge(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, void *tmp, uint size, int overwrite ZEND_FILE_LINE_DC)

  static zend_bool zend_hash_replace_checker_wrapper(HashTable *target, void *source_data, Bucket *p, void *pParam, merge_checker_func_t merge_checker_func)

  ZEND_API void zend_hash_merge_ex(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, uint size, merge_checker_func_t pMergeSource, void *pParam)

  ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData)

  ZEND_API int zend_hash_quick_find(const HashTable *ht, const char *arKey, uint nKeyLength, ulong h, void **pData)

  ZEND_API int zend_hash_exists(const HashTable *ht, const char *arKey, uint nKeyLength)

  ZEND_API int zend_hash_quick_exists(const HashTable *ht, const char *arKey, uint nKeyLength, ulong h)

  ZEND_API int zend_hash_index_find(const HashTable *ht, ulong h, void **pData)

  ZEND_API int zend_hash_index_exists(const HashTable *ht, ulong h)

  ZEND_API int zend_hash_num_elements(const HashTable *ht)

  ZEND_API int zend_hash_get_pointer(const HashTable *ht, HashPointer *ptr)

  ZEND_API int zend_hash_set_pointer(HashTable *ht, const HashPointer *ptr)

  ZEND_API void zend_hash_internal_pointer_reset_ex(HashTable *ht, HashPosition *pos)

  ZEND_API void zend_hash_internal_pointer_end_ex(HashTable *ht, HashPosition *pos)

  ZEND_API int zend_hash_move_forward_ex(HashTable *ht, HashPosition *pos)

  ZEND_API int zend_hash_move_backwards_ex(HashTable *ht, HashPosition *pos)

  ZEND_API int zend_hash_get_current_key_ex(const HashTable *ht, char **str_index, uint *str_length, ulong *num_index, zend_bool duplicate, HashPosition *pos)

  ZEND_API int zend_hash_get_current_key_type_ex(HashTable *ht, HashPosition *pos)

  ZEND_API int zend_hash_get_current_data_ex(HashTable *ht, void **pData, HashPosition *pos)

  ZEND_API int zend_hash_update_current_key_ex(HashTable *ht, int key_type, const char *str_index, uint str_length, ulong num_index, int mode, HashPosition *pos)

  ZEND_API int zend_hash_sort(HashTable *ht, sort_func_t sort_func, compare_func_t compar, int renumber TSRMLS_DC)

  ZEND_API int zend_hash_compare(HashTable *ht1, HashTable *ht2, compare_func_t compar, zend_bool ordered TSRMLS_DC)

  ZEND_API int zend_hash_minmax(const HashTable *ht, compare_func_t compar, int flag, void **pData TSRMLS_DC)

  ZEND_API ulong zend_hash_next_free_element(const HashTable *ht)

  void zend_hash_display_pListTail(const HashTable *ht)

  void zend_hash_display(const HashTable *ht)

;

深入理解java集合框架之---------HashTable集合

HashTable是什么

  1. HashTable是基于哈希表的Map接口的同步实现
  2. HashTable中元素的key是唯一的,value值可重复
  3. HashTable中元素的key和value不允许为null,如果遇到null,则返回NullPointerException
  4. HashTable中的元素是无序的

  

public class Hashtable<K,V>  
    extends Dictionary<K,V>  
    implements Map<K,V>, Cloneable, java.io.Serializable{}

HashTable跟HashMap一样,同样是链表散列的数据结构,从源码中我们可以看出,Hashtable 继承于Dictionary类,实现了Map, Cloneable,Serializable接口

  1. Dictionary类是任何可将键映射到相应值的类的抽象父类,每个键和值都是对象
  2. Dictionary源码注释指出 Dictionary 这个类过时了,新的实现类应该实现Map接口

Hashtable成员变量

  1. table:一个Entry[]数组类型,而Entry(在 HashMap 中有讲解过)就是一个单向链表。哈希表的”key-value键值对”都是存储在Entry数组中的
  2. count:Hashtable的大小,它是Hashtable保存的键值对的数量
  3. threshold:Hashtable的阈值,用于判断是否需要调整Hashtable的容量,threshold的值 = (容量 * 负载因子)
  4. loadFactor:负载因子
  5. modCount:用来实现fail-fast机制的

Hashtable构造方法

  Hashtable 一共提供了 4 个构造方法

  1. public Hashtable(int initialCapacity, float loadFactor): 用指定初始容量和指定加载因子构造一个新的空哈希表
  2. public Hashtable(int initialCapacity):用指定初始容量和默认的加载因子 (0.75) 构造一个新的空哈希表
  3. public Hashtable():默认构造函数,容量为 11,加载因子为 0.75
  4. public Hashtable(Map< ? extends K, ? extends V> t):构造一个与给定的Map具有相同映射关系的新哈希表

  

public synchronized V put(K key, V value) {
    //确保value不为null
    if (value == null) {
        throw new NullPointerException();
    }

    //确保key不在hashtable中
    //首先,通过hash方法计算key的哈希值,并计算得出index值,确定其在table[]中的位置
    //其次,迭代index索引位置的链表,如果该位置处的链表存在相同的key,则替换value,返回旧的value
    Entry tab[] = table;
    int hash = hash(key);
    int index = (hash & 0x7FFFFFFF) % tab.length;
    for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            V old = e.value;
            e.value = value;
            return old;
        }
    }

    modCount++;
    if (count >= threshold) {
        //如果超过阀值,就进行rehash操作
        rehash();

        tab = table;
        hash = hash(key);
        index = (hash & 0x7FFFFFFF) % tab.length;
    }

    //将值插入,返回的为null
    Entry<K,V> e = tab[index];
    // 创建新的Entry节点,并将新的Entry插入Hashtable的index位置,并设置e为新的Entry的下一个元素
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
    return null;
}

  存储的流程如下:

  1. 判断value是否为空,为空则抛出异常
  2. 计算key的hash值,并根据hash值获得key在table数组中的位置index 

    如果table[index]元素为空,将元素插入到table[index]位置

              如果table[index]元素不为空,则进行遍历链表,如果遇到相同的key,则新的value替代旧的value,并返回旧 value,否则将元素插入到链头,返回null

    

Hashtable的获取

  

public synchronized V get(Object key) {
    Entry tab[] = table;
    int hash = hash(key);
    int index = (hash & 0x7FFFFFFF) % tab.length;
    for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            return e.value;
        }
    }
    return null;
}

  获取的流程如下:

  1、通过 hash()方法求得key的哈希值

  2、根据hash值得到index索引

  3、迭代链表,返回匹配的key的对应的value,找不到则返回null

  

Hashtable遍历方式

  Hashtable有4种遍历方式:

  

//1、使用keys()
Enumeration<String> en1 = table.keys();
    while(en1.hasMoreElements()) {
    en1.nextElement();
}

//2、使用elements()
Enumeration<String> en2 = table.elements();
    while(en2.hasMoreElements()) {
    en2.nextElement();
}

//3、使用keySet()
Iterator<String> it1 = table.keySet().iterator();
    while(it1.hasNext()) {
    it1.next();
}

//4、使用entrySet()
Iterator<Entry<String, String>> it2 = table.entrySet().iterator();
    while(it2.hasNext()) {
    it2.next();
}

Hashtable与HashMap的区别

  

HashtableHashMap
方法是同步的 方法是非同步的
基于Dictionary类 基于AbstractMap,而AbstractMap基于Map接口的实现
key和value都不允许为null,遇到null,直接返回 NullPointerException key和value都允许为null,遇到key为null的时候,调用putForNullKey方法进行处理,而对value没有处理
hash数组默认大小是11,扩充方式是old*2+1 hash数组的默认大小是16,而且一定是2的指数

 

多线程存在的问题

  1、如果涉及到多线程同步时,建议采用HashTable

  2、没有涉及到多线程同步时,建议采用HashMap

  3、Collections 类中存在一个静态方法:synchronizedMap(),该方法创建了一个线程安全的 Map 对象,并把它作为一个封装的对象来返回

  synchronizedMap()其实就是对Map的方法加层同步锁,从源码中可以看出

  

//Collections.synchronizedMap(Map<K, V>)    
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
     return new SynchronizedMap<K,V>(m);
}


private static class SynchronizedMap<K,V> implements Map<K,V>, Serializable {
    private static final long serialVersionUID = 1978198479659022715L;

    private final Map<K,V> m;     // Backing Map
    //同步锁
    final Object      mutex;        // Object on which to synchronize

    SynchronizedMap(Map<K,V> m) {
        if (m==null)
            throw new NullPointerException();
        this.m = m;
        //把this本身作为锁监视器, 这样任何线程访问他的方法都要获取该监视器.
        mutex = this;
    }


    SynchronizedMap(Map<K,V> m, Object mutex) {
        this.m = m;
        this.mutex = mutex;
    }

    public int size() {
        synchronized(mutex) {return m.size();}
    }

    //重写map的emty方法
    public boolean isEmpty() {
        synchronized(mutex) {return m.isEmpty();}
    }
    public boolean containsKey(Object key) {
        synchronized(mutex) {return m.containsKey(key);}
    }
    public boolean containsValue(Object value) {
        synchronized(mutex) {return m.containsValue(value);}
    }
    public V get(Object key) {
        synchronized(mutex) {return m.get(key);}
    }

    public V put(K key, V value) {
        synchronized(mutex) {return m.put(key, value);}
    }
    public V remove(Object key) {
        synchronized(mutex) {return m.remove(key);}
    }
    public void putAll(Map<? extends K, ? extends V> map) {
        synchronized(mutex) {m.putAll(map);}
    }
    public void clear() {
        synchronized(mutex) {m.clear();}
    }

    private transient Set<K> keySet = null;
    private transient Set<Map.Entry<K,V>> entrySet = null;
    private transient Collection<V> values = null;

    //重写keySet方法
    public Set<K> keySet() {
        synchronized(mutex) {
            if (keySet==null)
                //mutex传给SynchronizedSet, 这样对于set内部操作也需要获取锁.
                keySet = new SynchronizedSet<K>(m.keySet(), mutex);
            return keySet;
        }
    }

    public Set<Map.Entry<K,V>> entrySet() {
        synchronized(mutex) {
            if (entrySet==null)
                entrySet = new SynchronizedSet<Map.Entry<K,V>>(m.entrySet(), mutex);
            return entrySet;
        }
    }

    public Collection<V> values() {
        synchronized(mutex) {
            if (values==null)
                values = new SynchronizedCollection<V>(m.values(), mutex);
            return values;
        }
    }

    public boolean equals(Object o) {
        synchronized(mutex) {return m.equals(o);}
    }
    public int hashCode() {
        synchronized(mutex) {return m.hashCode();}
    }
    public String toString() {
        synchronized(mutex) {return m.toString();}
    }
    private void writeObject(ObjectOutputStream s) throws IOException {
        synchronized(mutex) {s.defaultWriteObject();}
    }
}

  

以上是关于深入PHP中的HashTable结构详解的主要内容,如果未能解决你的问题,请参考以下文章

Java技术探索「ConcurrentHashMap」深入浅出的源码分析(JDK1.8版本)

深度剖析ConcurrentHashMap源码

深度剖析ConcurrentHashMap源码

Hashtable之Properties

HashMap与Hashtable的分析

深入Java集合学习系列:Hashtable的实现原理