PHP的HashTable实现
转载自: http://it.taocms.org/07/1145.htm
数据结构:
HashTable数据结构的描述在Zend/zend_hash.h文件中。首先,HashTable中的每一个元素都保存在下面这样的一个struct中:
typedef struct bucket {
ulong h; /* hash值,下标为数字索引时,h就是索引值 */
uint nKeyLength; /* key字符串的长度,当nKeyLength为0时表示是数字索引 */
void *pData; /* 实际存放的数据 */
void *pDataPtr; /* 数据指针 */
struct bucket *pListNext; /* 链表中的下一个元素 */
struct bucket *pListLast; /* 链表中的上一个元素 */
struct bucket *pNext; /* Hash拉链的下一个元素 */
struct bucket *pLast; /* Hash拉链的上一个元素 */
const char *arKey; /* key字符串指针 */
} Bucket;
PHP使用了双向链表和Hash表结合的方式实现HashTable,这使得HashTable能够在O(1)的时间复杂度上实现任意key的查询,同时又可以支持HashTable的遍历。
接下来看看HashTable的定义,大致做了下注释,和鸟哥的注释是一样的:
typedef struct _hashtable {
uint nTableSize; /* hash表的大小 */
uint nTableMask; /* 掩码,用于根据hash值计算存储位置,永远等于nTableSize-1 */
uint nNumOfElements; /* hash表中元素的个数 */
ulong nNextFreeElement; /* 下一个空闲可用位置的数字索引 */
Bucket *pInternalPointer; /* 内部指针,用于HashTable遍历 */
Bucket *pListHead; /* 双向链表的头指针 */
Bucket *pListTail; /* 双向链表的尾指针 */
Bucket **arBuckets; /* 指向bucket指针的容器 */
dtor_func_t pDestructor; /* 元素的析构函数指针 */
zend_bool persistent;
unsigned char nApplyCount; /* 循环遍历保护 */
zend_bool bApplyProtection;
#if ZEND_DEBUG
int inconsistent;
#endif
} HashTable;
为了更好的理解上面的数据结构,我参照鸟哥的手绘图,画了一个稍美观点的图示来说明上面那些个指针的作用:
上图是一个有6个元素的HashTable,并且当前正在访问第2个元素,左边arBuckets指针列表中的数字和右边bucket中的数字没有对应关系。
在PHP代码中,任何一个变量都是以zval的结构存储,而zval是使用一个zvalue_value指针来指向真正的数据的。zvalue_value是一个Union结构,这个结构包含一个HashTable指针,因此PHP中的变量就可以是一个HashTable,而且实际上我们在PHP中用到的HashTable是在HashTable上包了一层的zval结构:
struct _zval_struct {
zvalue_value value; /* 变量的值 */
zend_uint refcount__gc;
zend_uchar type; /* 变量当前的数据类型 */
zend_uchar is_ref__gc;
};
typedef struct _zval_struct zval; typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
创建HashTable:
创建(初始化)一个HashTable可以有很多种途径,但基本步骤是相通的,我以创建一个HashTable类型的zval为例来说明。
首先需要创建一个标准的zval,也就是为这个zval申请内存空间:
zval *array;
MAKE_STD_ZVAL(array); /* 为zval初始化内存空间,并将地址付给zval指针array */
MAKE_STD_ZVAL是用来创建标准zval的宏,上面的代码相当于:
array = (zval *) emalloc(sizeof(zval));
array->refcount__gc = 1;
array->is_ref__gc = 0;
然后可以调用array_init来初始化这个HashTable:
array_init(array);
以上代码相当于:
(*array).value.ht = (HashTable *) emalloc_rel(sizeof(HashTable));
_zend_hash_init((*array).value.ht, 0, NULL, ZVAL_PTR_DTOR, 0 ZEND_FILE_LINE_RELAY_CC);
(*array).type = IS_ARRAY;
初始化过程可以总结为:
- 为zval的value.ht申请sizeof(HashTable)大小的内存
- 调用_zend_hash_init函数初始化
- 设置zval的type为IS_ARRAY
其中重点就是_zend_hash_init这个函数了。如果我这里不需要创建一个HashTable类型的zval,只是创建一个HashTable而已,那么需要调用zend_hash_init这个宏来进行初始化:
HashTable *ht;
ALLOC_HASHTABLE(ht);
zend_hash_init(ht, 0, NULL, NULL, 0);
这里我简化了一下zend_hash_init的调用参数,ALLOC_HASHTABLE是为这个HashTable申请内存空间,然后zend_hash_init是个宏,实际上调用的是上面的_zend_hash_init函数,那么接下来就看一下这个函数做了什么。
ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_D
{
uint i = 3; /* 初始化HashTable大小时,是按照2的整数次幂来初始化,最小为2的3次幂 */ SET_INCONSISTENT(HT_OK); /* 调试用,初始化当前HashTable的状态 */ if (nSize >= 0x80000000) {
/* HashTable的最大值,防止溢出 */
ht->nTableSize = 0x80000000;
} else {
while ((1U << i) < nSize) { /* 根据传入的size按照2的整数次幂决定HashTable的大小 */
i++;
}
ht->nTableSize = 1 << i;
} ht->nTableMask = 0; /* 0 表示 ht->arBuckets 还没有初始化 */
ht->pDestructor = pDestructor; /* 设置析构函数为传入的函数指针 */
ht->arBuckets = (Bucket**)&uninitialized_bucket;
ht->pListHead = NULL;
ht->pListTail = NULL;
ht->nNumOfElements = 0;
ht->nNextFreeElement = 0;
ht->pInternalPointer = NULL;
ht->persistent = persistent;
ht->nApplyCount = 0;
ht->bApplyProtection = 1;
return SUCCESS;
}
代码中pHashFunction这个参数已经不用了,仅仅是为了保持向下兼容留在这里而已。pDestructor是HashTable元素的析构指针,在更新/销毁HashTable中元素的时候会用到。
zend_hash_init并没有为存放buckets申请内存空间,只是设置了初始化的size,并且设置了nTableMask为0,表示ht->arBuckets还未初始化。整个HashTable的size是按照2的整数次幂申请的,最小为2的3次幂,若空间不够则尝试2的4次幂、2的5次幂……直到大于传入的size。
那么buckets的空间是什么时候申请的呢?答案就是在操作HashTable的时候,不过不属于创建HashTable的范畴了,接下来就来看看如何操作HashTable。
操作HashTable:
添加/更新元素:
在初始化了HashTable之后,可以用zend_hash_add来向HashTable添加元素 ,zend_hash_add是一个宏:
#define zend_hash_add(ht, arKey, nKeyLength, pData, nDataSize, pDest)
_zend_hash_add_or_update(ht, arKey, nKeyLength, pData, nDataSize, pDest, HASH_ADD ZEND_FILE_LINE_CC)
那么实际上是调用_zend_hash_add_or_update这个函数:
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)
{
ulong h;
uint nIndex;
Bucket *p;
#ifdef ZEND_SIGNALS
TSRMLS_FETCH();
#endif IS_CONSISTENT(ht); if (nKeyLength <= 0) {
#if ZEND_DEBUG
ZEND_PUTS("zend_hash_update: Can't put in empty keyn");
#endif
return FAILURE;
} CHECK_INIT(ht); /* 检查是否初始化buckets空间,若没有初始化则初始化buckets的内存空间 */ h = zend_inline_hash_func(arKey, nKeyLength); /* 计算key的hash值 */
nIndex = h & ht->nTableMask; /* 利用掩码得到key的实际存储位置 */ p = ht->arBuckets[nIndex]; /* 取到指定位置的bucket指针 */
while (p != NULL) { /* 若指针不为空,则表示当前位置已有bucket了 */
if (p->arKey == arKey ||
((p->h == h) && (p->nKeyLength == nKeyLength) && !memcmp(p->arKey, arKey, nKeyLength))) {
/* 若当前bucket的key和要存入的key相同,那么需要更新 */
if (flag & HASH_ADD) { /* 如果当前指定是add操作,此时就返回失败了 */
return FAILURE;
}
HANDLE_BLOCK_INTERRUPTIONS();
#if ZEND_DEBUG
if (p->pData == pData) {
ZEND_PUTS("Fatal error in zend_hash_update: p->pData == pDatan");
HANDLE_UNBLOCK_INTERRUPTIONS();
return FAILURE;
}
#endif
if (ht->pDestructor) { /* 调用析构函数析构掉原先的值 */
ht->pDestructor(p->pData);
}
UPDATE_DATA(ht, p, pData, nDataSize); /* 替换为新的值 */
if (pDest) {
*pDest = p->pData;
}
HANDLE_UNBLOCK_INTERRUPTIONS();
return SUCCESS;
}
p = p->pNext; /* 若当前key和要存入的key不同,那么查找Hash拉链的下一个bucket
}
/* 运行到这里,表示没有找到任何已存在的key和要存入的key相同的,那么申请一个sizeof(bucket)+nKeyLength大小的新空间给key */
if (IS_INTERNED(arKey)) {
p = (Bucket *) pemalloc(sizeof(Bucket), ht->persistent);
if (!p) {
return FAILURE;
}
p->arKey = arKey;
} else {
p = (Bucket *) pemalloc(sizeof(Bucket) + nKeyLength, ht->persistent);
if (!p) {
return FAILURE;
}
p->arKey = (const char*)(p + 1);
memcpy((char*)p->arKey, arKey, nKeyLength);
}
p->nKeyLength = nKeyLength;
INIT_DATA(ht, p, pData, nDataSize); /* 执行赋值 */
p->h = h;
CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]); /* 设置乱七八槽的指针 */
if (pDest) {
*pDest = p->pData;
} HANDLE_BLOCK_INTERRUPTIONS();
CONNECT_TO_GLOBAL_DLLIST(p, ht);
ht->arBuckets[nIndex] = p;
HANDLE_UNBLOCK_INTERRUPTIONS(); ht->nNumOfElements++;
ZEND_HASH_IF_FULL_DO_RESIZE(ht); /* If the Hash table is full, resize it */
return SUCCESS;
}
我在上面加了注释,应该能看懂了,其中重点是执行赋值的地方,INIT_DATA这个宏是这么定义的:
#define INIT_DATA(ht, p, pData, nDataSize);
if (nDataSize == sizeof(void*)) {
memcpy(&(p)->pDataPtr, pData, sizeof(void *));
(p)->pData = &(p)->pDataPtr;
} else {
(p)->pData = (void *) pemalloc_rel(nDataSize, (ht)->persistent);
if (!(p)->pData) {
pefree_rel(p, (ht)->persistent);
return FAILURE;
}
memcpy((p)->pData, pData, nDataSize);
(p)->pDataPtr=NULL;
}
这里有一个tricks,PHP判断数据的大小和一个void指针相同时,就不为其申请额外的空间,而是将数据copy到pDataPtr字段中,也就是说,如果你add到HashTable的是一个指针,那么他直接被保存在pDataPtr字段中,同时pData字段也会保存一份。如果你add到HashTable的是一个更大的结构,那么PHP会为这个结构单独申请内存空间,将数据copy到这片新申请的内存空间中,然后将pDataPtr设置为NULL。这在鸟哥的那篇博客中有提到:
在Bucket中,实际的数据是保存在pData指针指向的内存块中,通常这个内存块是系统另外分配的。但有一种情况例外,就是当Bucket保存 的数据是一个指针时,HashTable将不会另外请求系统分配空间来保存这个指针,而是直接将该指针保存到pDataPtr中,然后再将pData指向本结构成员的地址。这样可以提高效率,减少内存碎片。由此我们可以看到PHP HashTable设计的精妙之处。如果Bucket中的数据不是一个指针,pDataPtr为NULL(本段来自Altair<eniac2008@hotmail.com>的”Zend HashTable详解”)
当我们要操作的是一个HashTable类型的zval时,可以用add_assoc_*系列函数:
add_assoc_null(zval *aval, char *key);
add_assoc_bool(zval *aval, char *key, zend_bool bval);
add_assoc_long(zval *aval, char *key, long lval);
add_assoc_double(zval *aval, char *key, double dval);
add_assoc_string(zval *aval, char *key, char *strval, int dup);
add_assoc_stringl(zval *aval, char *key,
char *strval, uint strlen, int dup);
add_assoc_zval(zval *aval, char *key, zval *value);
我们看其中的一个函数的实现:
#define add_assoc_string(__arg, __key, __str, __duplicate) add_assoc_string_ex(__arg, __key, strlen(__key)+1, __str, __duplicate) ZEND_API int add_assoc_string_ex(zval *arg, const char *key, uint key_len, char *str, int duplicate) /* {{{ */
{
zval *tmp; MAKE_STD_ZVAL(tmp);
ZVAL_STRING(tmp, str, duplicate); return zend_symtable_update(Z_ARRVAL_P(arg), key, key_len, (void *) &tmp, sizeof(zval *), NULL);
}
过程就是用传入的char *创建了一个字符串类型的zval,然后插入arg的下标key的位置,重点在于zend_symtable_update,这和刚才用的不太一样,那么这个函数做了什么呢?它实际上也调用了zend_hash_update,只不过在之前调用了一下ZEND_HANDLE_NUMERIC这个宏:
static inline int zend_symtable_update(HashTable *ht, const char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest)
{
ZEND_HANDLE_NUMERIC(arKey, nKeyLength, zend_hash_index_update(ht, idx, pData, nDataSize, pDest));
return zend_hash_update(ht, arKey, nKeyLength, pData, nDataSize, pDest);
}
ZEND_HANDLE_NUMERIC的作用就是当传入的key为字符串类型的数字时,将其转成ulong类型。
最后再系统看一下添加和更新相关的函数或者宏:
/* additions/updates/changes */
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);
#define zend_hash_update(ht, arKey, nKeyLength, pData, nDataSize, pDest)
_zend_hash_add_or_update(ht, arKey, nKeyLength, pData, nDataSize, pDest, HASH_UPDATE ZEND_FILE_LINE_CC)
#define zend_hash_add(ht, arKey, nKeyLength, pData, nDataSize, pDest)
_zend_hash_add_or_update(ht, arKey, nKeyLength, pData, nDataSize, pDest, HASH_ADD ZEND_FILE_LINE_CC) 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);
#define zend_hash_quick_update(ht, arKey, nKeyLength, h, pData, nDataSize, pDest)
_zend_hash_quick_add_or_update(ht, arKey, nKeyLength, h, pData, nDataSize, pDest, HASH_UPDATE ZEND_FILE_LINE_CC)
#define zend_hash_quick_add(ht, arKey, nKeyLength, h, pData, nDataSize, pDest)
_zend_hash_quick_add_or_update(ht, arKey, nKeyLength, h, pData, nDataSize, pDest, HASH_ADD ZEND_FILE_LINE_CC) ZEND_API int _zend_hash_index_update_or_next_insert(HashTable *ht, ulong h, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC);
#define zend_hash_index_update(ht, h, pData, nDataSize, pDest)
_zend_hash_index_update_or_next_insert(ht, h, pData, nDataSize, pDest, HASH_UPDATE ZEND_FILE_LINE_CC)
#define zend_hash_next_index_insert(ht, pData, nDataSize, pDest)
_zend_hash_index_update_or_next_insert(ht, 0, pData, nDataSize, pDest, HASH_NEXT_INSERT ZEND_FILE_LINE_CC) ZEND_API int zend_hash_add_empty_element(HashTable *ht, const char *arKey, uint nKeyLength);
_zend_hash_add_or_update刚才已经介绍过了,_zend_hash_index_update_or_next_insert则是用于添加或更新数字下标的元素,或者在HashTable末尾追加元素。 _zend_hash_quick_add_or_update则是为了性能考虑,当需要多次对同一个key进行操作时,可以先利用zend_get_hash_value()计算出hash值,然后在参数中传入,这样就无需每次都计算key的hash值了。
删除元素:
删除元素使用zend_hash_del系列的宏和函数,它们的定义如下:
ZEND_API int zend_hash_del_key_or_index(HashTable *ht, const char *arKey, uint nKeyLength, ulong h, int flag);
#define zend_hash_del(ht, arKey, nKeyLength)
zend_hash_del_key_or_index(ht, arKey, nKeyLength, 0, HASH_DEL_KEY)
#define zend_hash_quick_del(ht, arKey, nKeyLength, h)
zend_hash_del_key_or_index(ht, arKey, nKeyLength, h, HASH_DEL_KEY_QUICK)
#define zend_hash_index_del(ht, h)
zend_hash_del_key_or_index(ht, NULL, 0, h, HASH_DEL_INDEX)
接下来zend_hash_del_key_or_index函数的代码如下:
ZEND_API int zend_hash_del_key_or_index(HashTable *ht, const char *arKey, uint nKeyLength, ulong h, int flag)
{
uint nIndex;
Bucket *p;
#ifdef ZEND_SIGNALS
TSRMLS_FETCH();
#endif IS_CONSISTENT(ht); if (flag == HASH_DEL_KEY) { /* 如果是按照key来删除,则计算相应的hash值 */
h = zend_inline_hash_func(arKey, nKeyLength);
}
nIndex = h & ht->nTableMask; p = ht->arBuckets[nIndex];
while (p != NULL) {
if ((p->h == h)
&& (p->nKeyLength == nKeyLength)
&& ((p->nKeyLength == 0) /* 性能优化,当按照索引删除时,不去比较key的内容 */
|| !memcmp(p->arKey, arKey, nKeyLength))) { /* 按照key删除 */
HANDLE_BLOCK_INTERRUPTIONS();
if (p == ht->arBuckets[nIndex]) { /* 若命中的是hash通的首元素,则修改hash桶首元素的指针指向第二个元素 */
ht->arBuckets[nIndex] = p->pNext;
} else {
p->pLast->pNext = p->pNext; /* 否则将前一个元素的pNext指向下一个元素(也就是从Hash拉链中移除当前元素) */
}
if (p->pNext) {
p->pNext->pLast = p->pLast; /* 修改向前的指针 */
}
if (p->pListLast != NULL) { /* 开始修改双向连表的指针,和修改Hash拉链的指针类似 */
p->pListLast->pListNext = p->pListNext;
} else {
/* Deleting the head of the list */
ht->pListHead = p->pListNext;
}
if (p->pListNext != NULL) {
p->pListNext->pListLast = p->pListLast;
} else {
ht->pListTail = p->pListLast;
}
if (ht->pInternalPointer == p) { /* 若需要,调整内部指针到下一个元素 */
ht->pInternalPointer = p->pListNext;
}
if (ht->pDestructor) { /* 调用析构函数对元素进行析构 */
ht->pDestructor(p->pData);
}
if (p->pData != &p->pDataPtr) { /* 当HashTable中的元素不是指针时,释放添加时为数据申请的内存 */
pefree(p->pData, ht->persistent);
}
pefree(p, ht->persistent); /* 释放为bucket申请的内存 */
HANDLE_UNBLOCK_INTERRUPTIONS();
ht->nNumOfElements--;
return SUCCESS;
}
p = p->pNext;
}
return FAILURE;
}
当我们将数据添加到HashTable中时,需要注意PHP是如何释放内存的,我之前遇到的问题就主要集中在此,这要分几种情况来讨论。
情况一:我自己申请了一块内存,用指针p指向这块内存,调用zend_hash_add的时候,将p添加到HashTable,类似于如下代码:
my_type *p;
p = malloc(sizeof(my_type));
zend_hash_add(ht, "key", sizeof("key"), (void **)&p, sizeof(p), NULL);
PHP首先会申请sizeof(Bucket) +4的空间来存放一个Bucket,+4的空间是Bucket用来保存key的,然后PHP会判断因为当期传入pData的是个指针,所以PHP不会为pData申请额外的空间,直接将其放入pDataPtr和pData字段中。
当删除元素的时候,PHP会释放掉为Bucket申请的内存,那么在此之前的调用析构函数的步骤,就是我们唯一能够清理我们自己申请的内存的机会了。我们需要定义一个函数,并且在zend_hash_init的时候就传进去,因此代码看起来应该是这样的:
zend_hash_init(ht, 0, NULL, my_free, 0); my_type *p;
p = malloc(sizeof(my_type));
zend_hash_add(ht, "key", sizeof("key"), (void **)&p, sizeof(p), my_free); /* definition */
static void my_free(void *p){
free(*(my_type **)p);
}
情况二:我自己申请了一块内存,想将这块内存的内容add到HashTable中,那么代码看起来是这样的:
my_type *p;
p = malloc(sizeof(my_type));
zend_hash_add(ht, "key", sizeof("key"), (void *)p, sizeof(my_type), NULL);
这个时候,PHP同样会先为Bucket申请sizeof(Bucket)+4的空间,然后判断由于add进来的数据不是个指针,PHP会自己申请sizeof(my_type)大小的空间,然后将p指向的地址的内存copy进去。
当删除元素的时候,PHP同样会先调用析构函数,然后释放pData的空间,最后释放Bucket的空间。可以看出对于这种情况,PHP会自己申请/释放数据的空间,那么我们在将数据add到HashTable之后就可以释放掉它了,不过更好的办法是使用栈内存:
my_type p;
/* do some assignment here */
…… zend_hash_add(ht, "key", sizeof("key"), (void *)&p, sizeof(p), NULL);
这样不需要自己定义析构函数,挺方便。
有同学可能会问,既然第二种情况看上去这么方便,为什么还需要第一种?我们可以想象这样一个情况,我们需要插入的数据来自另外一个结构体中的指针,当我们通过HashTable对数据进行修改之后,希望通过另外一个结构体访问到这个数据时,数据也是实时更新的。这样就不能对数据进行copy,只能传递指针。同时当HashTable这个结构析构的时候,还要保证数据通过另一个结构体依然能够访问,就需要维护一个引用计数,在析构函数中判断这个引用计数的大小来决定要不要释放数据的内存。
还有两个函数涉及到内存的释放:
ZEND_API void zend_hash_destroy(HashTable *ht);
ZEND_API void zend_hash_clean(HashTable *ht);
这两个函数都会依次调用每一个元素的析构函数,清理每一个bucket的空间。他们的区别是,zend_hash_destroy会清理掉zend_hash_init申请的bucket指针列表的空间,而zend_hash_clean只是将其指针都置0。
PHP的HashTable实现的更多相关文章
- HashSet HashTable 与 TreeSet
HashSet<T>类 HashSet<T>类主要是设计用来做高性能集运算的,例如对两个集合求交集.并集.差集等.集合中包含一组不重复出现且无特性顺序的元素. HashSet& ...
- Javascript实现HashTable类
散列算法可以尽快在数据结构中找出指定的一个值,因为可以通过Hash算法求出值的所在位置,存储和插入的时候都按照Hash算法放到指定位置. <script> function HashTab ...
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...
- java面试题——HashMap和Hashtable 的区别
一.HashMap 和Hashtable 的区别 我们先看2个类的定义 public class Hashtable extends Dictionary implements Map, Clonea ...
- Map集合及与Collection的区别、HashMap和HashTable的区别、Collections、
特点:将键映射到值的对象,一个映射不能包含重复的键,每个键最多只能映射到一个值. Map集合和Collection集合的区别 Map集合:成对出现 (情侣) ...
- HashTable初次体验
用惯了数组.ArryList,初次接触到HashTable.Dictionary这种字典储存对于我来说简直就是高大上. 1.到底什么是HashTable HashTable就是哈希表,和数组一样,是一 ...
- HashMap和 Hashtable的比较
Hashtable 和 HashMap的比较 1. HashMap可以接受null(HashMap可以接受为null的键值(key)和值(value), HashTable不可以接受为null的键( ...
- hashMap和hashTable的区别
每日总结,每天进步一点点 hashMap和hashTable的区别 1.父类:hashMap=>AbstractMap hashTable=>Dictionary 2.性能:hashMap ...
- SortedList和HashTable
都是集合类,C#中同属命名空间System.Collections,“用于处理和表现类似keyvalue的键值对,其中key通常可用来快速查找,同时key是区分大小写:value用于存储对应于key的 ...
- Java Hashtable的实现
先附源码: package java.util; import java.io.*; /** * This class implements a hash table, which maps keys ...
随机推荐
- 【laravel54】详解中间件
1.中间件定义:对http请求进行一层过滤,通过过滤才能继续执行请求 2.中间件方法handle方法参数详解: 其中参数的形式可以有多个,使用[,]进行分割. 3.路由中使用中间件: 3.1 中间件使 ...
- Asp.Net中using的使用的方法(转)
摘自:http://blog.sina.com.cn/s/blog_6aa9c73801018ggw.html 一.强制资源整理(实用性非常强,希望大家多多使用,比try-catch-finaly效率 ...
- HDUOJ---1879 继续畅通工程
继续畅通工程 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Subm ...
- HDUOJ1086You can Solve a Geometry Problem too
You can Solve a Geometry Problem too Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/3 ...
- CYDIA装了个插件,想删除怎么都删除,电脑如何删除插件?
http://bbs.weiphone.com/read-htm-tid-3670917.html 装了个插件,想删除怎么都删除不掉不要跟我说在CYDIA里面删除.,在CYDIA里点击该插件就会闪退C ...
- CentOS 6.7下配置 yum 安装 Nginx。
第一步,在/etc/yum.repos.d/目录下创建一个源配置文件nginx.repo: cd /etc/yum.repos.d/ vim nginx.repo 填写如下内容: [nginx]nam ...
- PMP_PMP考试须知
考试报名 按照报名须知和填表指南中的要求提交报名材料同时交纳考试费用.北京地区的考生直接到国家外国专家局培训中心报名:外地考生到所在地报名点报名:未设有报名点的地区,可直接与国家外国专家局培训中心联系 ...
- Android性能优化之被忽视的Memory Leaks
起因 写博客就像讲故事.得有起因,经过,结果,人物.地点和时间.今天就容我给大家讲一个故事. 人物呢.肯定是我了. 故事则发生在近期的这两天,地点在coder君上班的公司.那天无意中我发现了一个奇怪的 ...
- Django ORM 数据库操作
比较有用 转自 http://blog.csdn.net/fgf00/article/details/53678205 一.DjangoORM 创建基本类型及生成数据库表结构 1.简介 2.创建数据库 ...
- intellij IDEA开发node.js
现在网上好像关于IDEA开发node.js的讲解不是太多,今天试了一下,竟然成功了.... 1.安装nodejs http://nodejs.org/download/ 自动根据系统下载自己的版本n ...