标签:
SlowMap.java说明了创建一个新的Map并不困难。但正如它的名称SlowMap所示,它不会很快,如果有更好的选择就应该放弃它。它的问题在于对键的查询,键没有按照任何特定的顺序保存,所以只能使用简单的线性查询,而线性查询是最慢的查询方式。
散列的价值在于速度:
散列使得查询得以快速进行。由于瓶颈在于键的查询速度,因此解决方案之一就是保持键的排序状态,然后使用Collections.binarySearch()进行查询。
散列则更进一步,它将键保存在某处,以便能够很快的找到。存储一组元素的最快数据结构是数组,所以使用它来表示键的信息(请小心留意,我说的是键的信息,而不是键本身)。但是因为数组不能调整容量,因此就有了一个问题:我们希望在Map中保存的数量是不确定的值,但是如果键的数量被数组的容量限制了,该怎么办呢?
答案就是:数组并不保存键本身。而是通过键对象生成一个数字,将其作为数组的下标。这个数字就是散列码,由定义在Object中的、且可能由你的类覆盖的hashCode()方法(计算机科学术语称为散列函数)生成。
为了解决数组被固定的问题,不同的键可能产生相同的下标。也就是说,可能会有冲突。因此,数组多大就不重要了,任何键总能在数组中找到它的位置。
于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能够保证没有冲突(如果值的数量是固定的,那么就有可能)那可能就是一个完美的散列函数,但是这种情况只是特例。通常,冲突由外部链接处理:数组并不直接保存值,而是保存值的list。然后对list中的值使用equals()方法进行线性的查询。这部分的查询自然会比较慢,但是,如果散列函数好的话,数组的每个位置就只有较少的值。因此,不是查询整个list,而是快速的跳到素数的某个位置,只对很少的元素进行比较。这边是HashMap快的原因。
理解了散列的原理,我们就能实现一个简单的散列Map了:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121class
MapEntry<k, v=
""
>
implements
Map.Entry<k, v=
""
> {
private
K key;
private
V value;
public
MapEntry(K key, V value) {
this
.key = key;
this
.value = value;
}
public
K getKey() {
return
key;
}
public
V getValue() {
return
value;
}
public
V setValue(V value) {
V result =
this
.value;
this
.value = value;
return
result;
}
@Override
public
int
hashCode() {
return
(key ==
null
?
0
: key.hashCode())
^ (value ==
null
?
0
: value.hashCode());
}
@Override
public
boolean
equals(Object o) {
if
(!(o
instanceof
MapEntry)) {
return
false
;
}
MapEntry me = (MapEntry) o;
return
(key ==
null
? me.getKey() ==
null
: key.equals(me.getKey()))
&& (value ==
null
? me.getValue() ==
null
: value.equals(me
.getValue()));
}
@Override
public
String toString() {
return
key +
" = "
+ value;
}
}
class
SimpleHashMap<k, v=
""
>
extends
AbstractMap<k, v=
""
> {
static
final
int
SIZE =
997
;
@SuppressWarnings
(
"unchecked"
)
LinkedList<mapentry<k, v=
""
>>[] buckets =
new
LinkedList[SIZE];
public
V put(K key, V value) {
V oldValue =
null
;
int
index = Math.abs(key.hashCode()) % SIZE;
if
(buckets[index] ==
null
) {
buckets[index] =
new
LinkedList<mapentry<k, v=
""
>>();
}
LinkedList<mapentry<k, v=
""
>> bucket = buckets[index];
MapEntry<k, v=
""
> pair =
new
MapEntry<k, v=
""
>(key, value);
boolean
found =
false
;
ListIterator<mapentry<k, v=
""
>> it = bucket.listIterator();
while
(it.hasNext()) {
MapEntry<k, v=
""
> iPair = it.next();
if
(iPair.getKey().equals(key)) {
oldValue = iPair.getValue();
it.set(pair);
found =
true
;
break
;
}
}
if
(!found) {
buckets[index].add(pair);
}
return
oldValue;
}
public
V get(Object key) {
int
index = Math.abs(key.hashCode()) % SIZE;
if
(buckets[index] ==
null
) {
return
null
;
}
for
(MapEntry<k, v=
""
> iPair : buckets[index]) {
if
(iPair.getKey().equals(key)) {
return
iPair.getValue();
}
}
return
null
;
}
@Override
public
Set<java.util.map.entry<k, v=
""
>> entrySet() {
Set<map.entry<k, v=
""
>> set =
new
HashSet<map.entry<k,v>>();
for
(LinkedList<mapentry<k, v=
""
>> bucket : buckets) {
if
(bucket ==
null
) {
continue
;
}
for
(MapEntry<k, v=
""
> mpair : bucket) {
set.add(mpair);
}
}
return
set;
}
}
public
class
Main2 {
public
static
void
main(String[] args) {
{CAPE VERDE=Praia, ANGOLA=Luanda, ETHIOPIA=Addis Ababa, BENIN=Porto-Novo, CONGO=Brazzaville, LESOTHO=Maseru, CENTRAL AFRICAN REPUBLIC=Bangui, EQUATORIAL GUINEA=Malabo, ERITREA=Asmara, COMOROS=Moroni, BURKINA FASO=Ouagadougou, GABON=Libreville, THE GAMBIA=Banjul, GUINEA=Conakry, EGYPT=Cairo, BURUNDI=Bujumbura, ALGERIA=Algiers, CAMEROON=Yaounde, GHANA=Accra, KENYA=Nairobi, COTE D
‘IVOIR (IVORY COAST)=Yamoussoukro, BISSAU=Bissau, DJIBOUTI=Dijibouti, CHAD=N‘
djamena, BOTSWANA=Gaberone}
[CAPE VERDE = Praia, ANGOLA = Luanda, ETHIOPIA = Addis Ababa, BENIN = Porto-Novo, CONGO = Brazzaville, LESOTHO = Maseru, CENTRAL AFRICAN REPUBLIC = Bangui, EQUATORIAL GUINEA = Malabo, ERITREA = Asmara, COMOROS = Moroni, BURKINA FASO = Ouagadougou, GABON = Libreville, THE GAMBIA = Banjul, GUINEA = Conakry, EGYPT = Cairo, BURUNDI = Bujumbura, ALGERIA = Algiers, CAMEROON = Yaounde, GHANA = Accra, KENYA = Nairobi, COTE D
‘IVOIR (IVORY COAST) = Yamoussoukro, BISSAU = Bissau, DJIBOUTI = Dijibouti, CHAD = N‘
djamena, BOTSWANA = Gaberone]
SimpleHashMap<string, string=
""
> m =
new
SimpleHashMap<string, string=
""
>();
m.putAll(Countries.capitals(
25
));
System.out.println(m);
System.out.println(m.entrySet());
}
}</string,></string,></k,></mapentry<k,></map.entry<k,v></map.entry<k,></java.util.map.entry<k,></k,></k,></mapentry<k,></k,></k,></mapentry<k,></mapentry<k,></mapentry<k,></k,></k,></k,></k,>
由于散列表中的“槽位”(slot)通常称为桶位(bucket),因此我们将表示实际散列表的数组命名为bucket。
为使散列分布均匀,桶的数量通常使用质数。注意,为了能够自动处理冲突,使用了一个LinkedList的数组;
每一个新的元素只是直接添加到list末尾的某个特定的桶位中。即使Java不允许你创建泛型数组,那你也可以创建指向这种数组的引用。这里,向上转型为这种数组是很方便的,这样可以防止在后面的代码中进行额外的转型。
对于put方法,hashCode()将针对键而被调用,并且其结果被强制转换为正数。为了是产生的数组适合bucket数组的大小,取摸操作符将按照该数组的尺寸取模。如果数组的某个位置是null,这表示还没有元素被散列至此,所以,为了保存刚散列到该定位的对象需要创建爱你一个新的LinkedList。一般的过程是,查看当前位置的list中是否有相同的元素,如果有,则将旧的值付给oldValue,然后用新值取代旧值。标记found用来跟踪是否找到旧的键值对,如果没有,则将新的添加到list的末尾。
get()方法按照与put()方法相同的方式计算bucktes数组中的索引(这很重要,保证计算出相同的位置)如果此位置存在,则进行查询。
注意,这个实现并不意味着对性能进行了调优;它只是想要展示散列映射表执行的各种操作。
标签:
原文地址:http://www.cnblogs.com/cunkouzh/p/5427585.html