持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情。
本文学习HashTable,这是一个古老类,基本上被废除,但是学习它有助于理解HashMap。
HashTable简介
HashTable是一个key-value键值对的数据类型,较为古老,现已基本废除不再使用,使用synchronized保证同步,效率较低,一般单线程转为使用HashMap,多线程使用ConcurrentHashMap。
但学习其实现方式有助于理解hashMap的实现。
HashTable继承关系
继承Dictionary类,实现Map接口。
HashTable原理
hashTable的原理和hashMap大致相同。在一些算法和设计方面,hashTable比hashMap低下。
HashTable属性
//节点数组
private transient Entry<?,?>[] table;
//节点个数
private transient int count;
//临界值
private int threshold;
//加载因子 0.75
private float loadFactor;
//模数,hashTable被修改的次数
private transient int modCount = 0;
复制代码
HashTable插入元素
- 当key值重复,就覆盖并返回旧的value
- 当key值不重复,直接插入。如果出现冲突,拉链发解决
一、(hash & 0x7FFFFFFF) % tab.length;
取余操作,获取散列下标,相比于hashMap的一次位运算,这里效率很低。
这里巧妙在于,与0x7FFFFFFF进行与运算,避免负数的出现
public synchronized V put(K key, V value) {
// Make sure the value is not null
//不允许插入null值
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
//这里并没有对key进行为空检测,是不合理的
int hash = key.hashCode();
//hash值与0x7FFFFFFF按位与,防止出现负数。
//与tab.length做取余操作,得到散列下标
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
//不为空
for(; entry != null ; entry = entry.next) {
//出现重复则覆盖
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
//出现冲突、或对应下标节点为空,插入元素
addEntry(hash, key, value, index);
return null;
}
复制代码
addEntry方法
如果节点个数超过临界值,就扩容。
否则直接插入元素,或拉链法解决冲突
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
复制代码
rehash()方法
这里的扩容方法,只是简单的将节点散列到新的数组上,并没有做将链表变短的操作。
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {
//i下标对单向链表
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
//获取节点新下标
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
//链接起来
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
复制代码
拉链发解决冲突图示:
Properties简介
Properties 继承于 Hashtable,可以作为一个map使用。其内部扩展了一些方法,只允许添加String的key-value,不过都是基于hashTable实现的。其使用最多的是作为配置工具类使用。
使用
properties作为map使用
/**
* properties作为map使用
*/
@Test
public void propertiesUseAsMap() {
Properties prop = new Properties();
prop.put(1, "a");
prop.put(2, 2);
prop.put("3", new Object());
System.out.println(prop.get(1));
System.out.println(prop.get(2));
System.out.println(prop.get("3"));
}
复制代码
其扩展方法,只能添加字符串的key-valu
@Test
public void useAsProp() {
Properties prop = new Properties();
prop.setProperty("name","name");
prop.setProperty("age","23");
prop.setProperty("email","[email protected]");
System.out.println(prop);
prop.forEach((key, value) -> {
System.out.println("{key" + key + "," + "value" + value + "}");
});
}
复制代码
有一些类自带properties属性
/**
* 也可以自己写一个配置类
*/
@Test
public void testMyCP() {
Properties prop = MyPropClass.getProperties();
System.out.println(prop);
prop.forEach((key, value) -> {
System.out.println("{key" + key + "," + "value" + value + "}");
});
}
class MyPropClass {
private static Properties props;
private static final String name;
private static final String age;
private static final String email;
static {
props = new Properties();
name = "yyc";
age = "23";
email = "[email protected]";
initProperties();
}
private static void initProperties() {
props.setProperty("name", name);
props.setProperty("age", age);
props.setProperty("email", email);
}
public void setProperties(String key, String value) {
props.setProperty(key, value);
}
public String getProperties(String key) {
return props.getProperty(key);
}
public void setProperties(Properties properties) {
MyPropClass.props = properties;
}
public static Properties getProperties() {
return props;
}
复制代码
为了防止一些硬编码,可以使用配置文件的形式配置信息,一般用于数据库链接或一些配置的类信息。
user.properties:
name=yyc
age=23
hobby=ball
复制代码
@Test
public void readProp() throws IOException {
InputStream in = PropertiesTest.class.getClassLoader().getResourceAsStream("user.properties");
Properties prop = new Properties();
prop.load(in);
String name = prop.getProperty("name");
String age = prop.getProperty("age");
String hobby = prop.getProperty("hobby");
System.out.println("{name:" + name + "," + "age:" + age + "," + "hobby" + hobby + "}");
}
复制代码
同时也可以将Properties作为文件输出
/**
* 也可以输出为properties 或 xml
* @throws IOException
*/
@Test
public void writeProp() throws IOException {
//创建配置
Properties prop = new Properties();
prop.setProperty("name","name");
prop.setProperty("age","23");
prop.setProperty("email","[email protected]");
System.out.println(prop);
prop.forEach((key, value) -> {
System.out.println("{key" + key + "," + "value" + value + "}");
});
//创建 outputStream
FileWriter fileWriter = new FileWriter(new File("D:\\user.properties"));
//写入文件
prop.store(fileWriter,"user信息");
}
复制代码