1. 程式人生 > >Python之美[從菜鳥到高手]--讀"一道面試題看 HashMap 的儲存方式"的聯想

Python之美[從菜鳥到高手]--讀"一道面試題看 HashMap 的儲存方式"的聯想

在 HashMap 中存放的一系列鍵值對,其中鍵為某個我們自定義的型別。放入 HashMap 後,我們在外部把某一個 key 的屬性進行更改,然後我們再用這個 key 從 HashMap 裡取出元素,這時候 HashMap 會返回什麼?

如何面試者直接答“這要看自定義型別的hash值了”,我想面試官會非常滿意。

聯想到python中dict的實現,python中字典一般不存在這個問題,因為key的hash值預設是id值,一個物件的id是固定的。

看如下程式碼:


我們可以通過__hash__修改預設hash值,所以__hash__方法還是要看具體業務邏輯,比如我們業務任務name值一樣就是同一個物件,看如下程式碼:

class B:
	def __init__(self,name):
		self.name=name
	def __hash__(self):
		return hash(self.name)
d={}
b1=B('skycrab')
b2=B('skycrab1')
d[b1]=1
d[b2]=2
b2.name='skycrab'
d[b2]=3
print d
我信心滿滿的認為name為‘skycrab‘的值會被更新為3,可事實如下:

{<__main__.B instance at 0x02543210>: 2, <__main__.B instance at 0x02543210>: 3, <__main__.B instance at 0x025436C0>: 1}


這讓我百思不得其解,hash值明明一樣,為什麼會認為是不同物件,導致增加了一個。突然靈光一閃,難道key也需要做比較?開啟python原始碼我們看看lookdict函式,

當更新字典時會去尋找合適的hashtable位置,呼叫的就是lookdict函式。

static dictentry *
lookdict(dictobject *mp, PyObject *key, register long hash)
{
	register size_t i;
	register size_t perturb;
	register dictentry *freeslot;
	register size_t mask = (size_t)mp->ma_mask;
	dictentry *ep0 = mp->ma_table;
	register dictentry *ep;
	register int cmp;
	PyObject *startkey;

	i = (size_t)hash & mask;
	ep = &ep0[i];
	if (ep->me_key == NULL || ep->me_key == key)
		return ep;

	if (ep->me_key == dummy)
		freeslot = ep;
	else {
		if (ep->me_hash == hash) {
			startkey = ep->me_key;
			cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); //比較key的值
			if (cmp < 0)
				return NULL;
			if (ep0 == mp->ma_table && ep->me_key == startkey) {
				if (cmp > 0) //只有key相等才會返回已有的位置,否則會尋找一個新的位置
					return ep;
			}
			else {
				/* The compare did major nasty stuff to the
				 * dict:  start over.
				 * XXX A clever adversary could prevent this
				 * XXX from terminating.
 				 */
 				return lookdict(mp, key, hash);
 			}
		}
		freeslot = NULL;
	}

上面是lookdict的部分原始碼(最後沒有大括號),如上程式碼註釋,原來只有hash值一樣且key值相等才有更新。那麼這就好辦了,定義__eq__方法即可:
class B:
	def __init__(self,name):
		self.name=name
	def __hash__(self):
		return hash(self.name)
	def __eq__(self,r):
		if self.name == r.name:
			return True
		else:
			return False
d={}
b1=B('skycrab')
b2=B('skycrab1')
d[b1]=1
d[b2]=2
b2.name='skycrab'
d[b2]=3
print d
這下結果終於符合期望了,{<__main__.B instance at 0x025F2620>: 2, <__main__.B instance at 0x025F25F8>: 3}

這裡我們擴充套件一下,python中的dict預設採用hash_map的儲存結構,所以查詢效率很高,但hash_map的查詢效率不穩定。

hash_map的時間複雜度在O(1)-O(N),而基於樹結構的map時間複雜度O(logN),比較穩定。

所以在C++中使用hash_map還是map是有考究的,具體可以看看【C++對話系列-產生真正的hash物件】一節。