【IT168 技术文档】在仔细分析源代码之前,我们来看看Hashtable提供的一些接口方法。
public
int
size();
public
boolean
isEmpty() ;
public
synchronized
Enumeration keys();
public
synchronized
Enumeration elements();
public
synchronized
boolean
contains(Object value) ;
public
synchronized
boolean
containsKey(Object key);
public
synchronized
Object get(Object key);
public
synchronized
Object put(Object key, Object value) ;
public
synchronized
Object remove(Object key);
public
synchronized
void
clear() ;
public
synchronized
void
clear() ;
上面的方法我就不一一介绍了,具体的用法也是很简单,相对大家对此也比较熟悉了。
Hashtable的用法
Hashtable 有2个构造函数
public
Hashtable(
int
initialCapacity);
//
指定容量大小
public
Hashtable() {
this
(
11
);
//
默认的容量是11,为什么是11,而不是10呢?
}
Demo1
Hashtable sTable
=
new
Hashtable();
sTable.put(
"
wuhua
"
,
"
wuhua
"
);
sTable.remove(
"
wuhua
"
);
sTable.clear();
上面是简单的用法。
Hashtable源代码解读
在了解源代码之前,我们先来了解下一些java不常用的关键字。
当串行化某个对象时,如果该对象的某个变量是transient,那么这个变量不会被串行化进去。也就是说,假设某个类的成员变量是transient,那么当通过ObjectOutputStream把这个类的某个实例保存到磁盘上时,实际上transient变量的值是不会保存的。因为当从磁盘中读出这个对象的时候,对象的该变量会没有被赋值。 另外这篇文章还提到,当从磁盘中读出某个类的实例时,实际上并不会执行这个类的构造函数,而是读取这个类的实例的状态,并且把这个状态付给这个类的对象。
引用地址:http://blog.chinaunix.net/u/22516/showart.php?id=380029
Transient 这个关键字很重要,来看下源代码里面有几处是用到这个关键字的。
private
transient
HashtableEntry table[];
private
transient
int
count;
源代码中只有上面两个字段的定义是用到的,但是这两个字段是用于存储Hashtable的容器字段,因此可以说Hashtable是不允许序列化的。
Hashtable有2个内部类
HashtableEntry -- 用于存放key-value,nextElement的类。
class
HashtableEntry {
int
hash;
Object key;
Object value;
HashtableEntry next;
}
HashtableEnumerator 遍历的枚举类。
class
HashtableEnumerator
implements
Enumeration {
boolean
keys;
int
index;
HashtableEntry table[];
HashtableEntry entry;
HashtableEnumerator(HashtableEntry table[],
boolean
keys) {
this
.table
=
table;
this
.keys
=
keys;
this
.index
=
table.length;
}
public
boolean
hasMoreElements() {
if
(entry
!=
null
) {
return
true
;
}
while
(index
--
>
0
) {
if
((entry
=
table[index])
!=
null
) {
return
true
;
}
}
return
false
;
}
public
Object nextElement() {
if
(entry
==
null
) {
while
((index
--
>
0
)
&&
((entry
=
table[index])
==
null
));
}
if
(entry
!=
null
) {
HashtableEntry e
=
entry;
entry
=
e.next;
return
keys
?
e.key : e.value;
}
throw
new
NoSuchElementException(
/*
#ifdef VERBOSE_EXCEPTIONS
*/
//
/ skipped "HashtableEnumerator"
/*
#endif
*/
);
}
}
代码写的是相当的简介。有一些比较技巧性的用法也是相当的不错,比如:
if
(entry
==
null
) {
while
((index
--
>
0
)
&&
((entry
=
table[index])
==
null
));
} 这段写的是相当的好,可见作者的功力,循环变量table
while
(index
--
>
0
)
//
循环变量,查看是否有下一个元素,
if
((entry
=
table[index])
!=
null
) {
return
true
;
}
}
了解了Hashtable的数据存放格式,我们看看存放的关键逻辑
在put,remove,get方法中存在。
int
index
=
(hash
&
0x7FFFFFFF
)
%
tab.length;
这样的函数,这个函数的意义上,根据散列值来获取对象的存储位置。
来欣赏下代码片段:
public
synchronized
Object get(Object key) {
HashtableEntry tab[]
=
table;
int
hash
=
key.hashCode();
int
index
=
(hash
&
0x7FFFFFFF
)
%
tab.length;
for
(HashtableEntry e
=
tab[index] ; e
!=
null
; e
=
e.next) {
if
((e.hash
==
hash)
&&
e.key.equals(key)) {
return
e.value;
}
}
return
null
;
}
从上面的代码可以分析出。首先获取key的散列值,并且根据散列值进行key的Index定位
这里存在同一个index多个HashtableEntry 存在,所以才会有了next的变量,next就是存放相同位置不同key的实体。
下面再来看看Hashtable里面一个扩充容器的算法。
protected
void
rehash() {
int
oldCapacity
=
table.length;
HashtableEntry oldTable[]
=
table;
int
newCapacity
=
oldCapacity
*
2
+
1
;
HashtableEntry newTable[]
=
new
HashtableEntry[newCapacity];
threshold
=
(
int
)((newCapacity
*
loadFactorPercent)
/
100
);
table
=
newTable;
for
(
int
i
=
oldCapacity ; i
--
>
0
;) {
//
循环遍历oldTable的对应的实体,并且遍历对应的实体的没一个对象,进行
//
重新分配index,再进行保存
for
(HashtableEntry old
=
oldTable[i] ; old
!=
null
; ) {
HashtableEntry e
=
old;
old
=
old.next;
int
index
=
(e.hash
&
0x7FFFFFFF
)
%
newCapacity;
e.next
=
newTable[index];
//
e 的next 指向当前索引
newTable[index]
=
e;
//
}
}
}
for
(HashtableEntry old
=
oldTable[i] ; old
!=
null
; )
这段的用法很奇怪,我以前没有使用过,上面的代码等同于
HashtableEntry old
=
oldTable[i];
Whild(old
!=
null
){
...............................
...............................
}
不过个人比较习惯使用第2种方式