1、Hashtable 简介
和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。
Hashtable 的函数都是同步的,这意味着它是 线程安全的。它的key、value都不可以为null。此外,Hashtable中的 映射不是有序的。
Hashtable 的实例有两个参数影响其性能:初始容量 和 加载因子。容量 是哈希表中桶 的数量, 初始容量 就是哈希表创建时的容量。注意,哈希表的状态为 open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。 加载因子 是对哈希表在其容量自动增加之前可以达到多满的一个尺度。初始容量和加载因子这两个参数只是对该实现的提示。关于何时以及是否调用 rehash 方法的具体细节则依赖于该实现。
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数 Hashtable 操作中,包括 get 和 put 操作,都反映了这一点)。
2、Hashtable 特点
* 1). 线程安全。
* 2). Key、Value均不能为null。
* 3). 包含了一个Entry[]数组,而Entry又是一个链表,用来处理冲突。
* 4).每个Key对应了Entry数组中固定的位置(记为index),称为槽位(Slot)。槽位计算公式为: (key.hashCode() & 0x7FFFFFFF) % Entry[].length() 。
* 5).当Entry[]的实际元素数量(Count)超过了分配容量(Capacity)的75%时,新建一个Entry[]是原先的2倍,并重新Hash(rehash)。
rehash的核心思路是,将旧Entry[]数组的元素重新计算槽位,散列到新Entry[]中。
api
synchronized void clear()
synchronized Object clone()
boolean contains(Object value)
synchronized boolean containsKey(Object key)
synchronized boolean containsValue(Object value)
synchronized Enumeration<V> elements()
synchronized Set<Entry<K, V>> entrySet()
synchronized boolean equals(Object object)
synchronized V get(Object key)
synchronized int hashCode()
synchronized boolean isEmpty()
synchronized Set<K> keySet()
synchronized Enumeration<K> keys()
synchronized V put(K key, V value)
synchronized void putAll(Map<? extends K, ? extends V> map)
synchronized V remove(Object key)
synchronized int size()
synchronized String toString()
synchronized Collection<V> values()
3、源码分析
Dictionary抽象类
public abstract class Dictionary<K,V> {
public Dictionary() {
}
abstract public int size();
abstract public boolean isEmpty();
abstract public Enumeration<K> keys();
abstract public Enumeration<V> elements();
abstract public V get(Object key);
abstract public V put(K key, V value);
abstract public V remove(Object key);
}
Entry类
private static class Entry<K,V> implements Map.Entry<K,V> {
// 哈希值
int hash;
K key;
V value;
// 指向的下一个Entry,即链表的下一个节点
Entry<K,V> next;
// 构造函数
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
protected Object clone() {
return new Entry<K,V>(hash, key, value,
(next==null ? null : (Entry<K,V>) next.clone()));
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
// 设置value。若value是null,则抛出异常。
public V setValue(V value) {
if (value == null)
throw new NullPointerException();
V oldValue = this.value;
this.value = value;
return oldValue;
}
// 覆盖equals()方法,判断两个Entry是否相等。
// 若两个Entry的key和value都相等,则认为它们相等。
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
(value==null ? e.getValue()==null : value.equals(e.getValue()));
}
public int hashCode() {
return hash ^ (value==null ? 0 : value.hashCode());
}
public String toString() {
return key.toString()+"="+value.toString();
}
}
属性
/**
* 保存hashtable里key-value的数组。
* Hashtable同样采用单链表解决冲突,每一个Entry本质上是一个单向链表
*/
private transient Entry<K,V>[] table;
// Hashtable中键值对的数量
private transient int count;
// 阈值,用于判断是否需要调整Hashtable的容量(threshold = 容量*加载因子)
private int threshold;
// 加载因子
private float loadFactor;
//Hashtable被改变的次数,用于fail-fast机制的实现
private transient int modCount = 0;
// 序列版本号
private static final long serialVersionUID = 1421746759512286392L;
Hashtable主要对外方法
1、contains() 和 containsValue() 的作用都是判断Hashtable是否包含“值(value)”
public boolean containsValue(Object value) {
return contains(value);
}
public synchronized boolean contains(Object value) {
// Hashtable中“键值对”的value不能是null,
// 若是null的话,抛出异常!
if (value == null) {
throw new NullPointerException();
}
// 从后向前遍历table数组中的元素(Entry)
// 对于每个Entry(单向链表),逐个遍历,判断节点的值是否等于value
Entry tab[] = table;
for (int i = tab.length ; i-- > 0 ;) {
for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true;
}
}
}
return false;
}
2、containsKey() 的作用是判断Hashtable是否包含key
public synchronized boolean containsKey(Object key) {
Entry tab[] = table;
int hash = key.hashCode();
// 计算索引值,
// % tab.length 的目的是防止数据越界
int index = (hash & 0x7FFFFFFF) % tab.length;
// 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return true;
}
}
return false;
}
elements() 的作用是返回“所有value”的枚举对象
// 获取Hashtable的枚举类对象
private <T> Enumeration<T> getEnumeration(int type) {
if (count == 0) {
return (Enumeration<T>)emptyEnumerator;
} else {
return new Enumerator<T>(type, false);
}
}public synchronized Enumeration<V> elements() {
return this.<V>getEnumeration(VALUES);
}
// 获取Hashtable的枚举类对象
private <T> Enumeration<T> getEnumeration(int type) {
if (count == 0) {
return (Enumeration<T>)emptyEnumerator;
} else {
return new Enumerator<T>(type, false);
}
}
get() 的作用就是获取key对应的value,没有的话返回null。跟containKeys方法一样,只是返回值不同
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = key.hashCode();
// 计算索引值,
int index = (hash & 0x7FFFFFFF) % tab.length;
// 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素
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;
}
put让Hashtable对象可以通过put()将“key-value”添加到Hashtable中。
public synchronized V put(K key, V value) {
// Hashtable中不能插入value为null的元素!!!
if (value == null) {
throw new NullPointerException();
}
// 若“Hashtable中已存在键为key的键值对”,
// 则用“新的value”替换“旧的value”
Entry tab[] = table;
int hash = key.hashCode();
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;
}
}
// 若“Hashtable中不存在键为key的键值对”,
// (01) 将“修改统计数”+1
modCount++;
// (02) 若“Hashtable实际容量” > “阈值”(阈值=总的容量 * 加载因子)
// 则调整Hashtable的大小
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
index = (hash & 0x7FFFFFFF) % tab.length;
}
// (03) 将“Hashtable中index”位置的Entry(链表)保存到e中
Entry<K,V> e = tab[index];
// (04) 创建“新的Entry节点”,并将“新的Entry”插入“Hashtable的index位置”,并设置e为“新的Entry”的下一个元素(即“新Entry”为链表表头)。
tab[index] = new Entry<K,V>(hash, key, value, e);
// (05) 将“Hashtable的实际容量”+1
count++;
return null;
}
refesh()
// rehash(): 再次hash。当Entry[]的实际存储数量占分配容量的约75%时,扩容并且重新计算各个对象的槽位
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ;
protected void rehash() {
int oldCapacity = table.length;
Entry[] oldMap = table;
int newCapacity = (oldCapacity << 1) + 1; // 2倍+1
Entry[] newMap = new Entry[newCapacity];
threshold = (int)(newCapacity * loadFactor);
table = newMap;
for( int i=oldCapacity; i-- >0;){ // i的取值范围为 [oldCapacity-1,0]
for (Entry<K,V> old = oldMap[i]; old!=null;){ // 遍历旧Entry[]
Entry<K,V> e = old;
int index = (e.hash & 0x7FFFFFFF) % newCapacity; // 重新计算各个元素在新Entry[]中的槽位index。
e.next = newMap[index]; // 已经存在槽位中的Entry放到当前元素的next中
newMap[index]=e; // 放到槽位中
old = old.next;
}
}
}
二、Properties类
先说如何使用这个类
@Test
public void testProperties(){
InputStream inProperties = null;
try {
//-------------------------- 1、读取.properties文件 ---------------------------------
Properties properties1 = new Properties();
// inProperties = new FileInputStream("test.properties");//通过文件路径获取
/*利用反射读取配置文件dim.properties里的信息:调用getResourceAsStream 获取类路径下的文件对应的输入流*/
inProperties = getClass().getClassLoader().getResourceAsStream("test.properties");
properties1.load(inProperties);
/*获取配置文件的内容,中文可能会出现乱码,需要转换一下,用GBK或者UTF-8 */
String name = (new String(properties1.getProperty("name").getBytes("ISO-8859-1"), "GBK"));
String age = properties1.getProperty("age");
System.out.println("--------------- "+name+", "+age+" -------------------------");
//--------------------------- 2、读取.xml文件 -------------------------------
Properties properties2 = new Properties();
inProperties = getClass().getClassLoader().getResourceAsStream("test.xml");
properties2.load(inProperties);
properties2.list(System.out);//将属性文件中的键值对儿打印到控制台
//--------------------------- 3、将内容保存到文件中---------------------------------
Properties prop = new Properties();
prop.setProperty("one", "a");
prop.setProperty("two", "b");
prop.setProperty("three", "c"); //中文会乱码
prop.storeToXML(new FileOutputStream("e:/test.xml"), "saveXML");//将键值对儿保存到XML文件中
prop.store(new FileOutputStream("e:/test.properties"), "saveProperties");//将键值对儿保存到普通的属性文件中
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
assert inProperties != null;
inProperties.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}