HashTable的基本介绍:
(1)存放的元素是键值对:即K-V
(2)hashtable的键和值都不能为null,否则会抛出NullPointerException
(3)hashTable使用方法基本上和HashMap一样
(4)hashtable是线程安全的(synchronized),hashMap是线程不安全的
我们通过put方法来进行查看:
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
(5)简单看下底层结构
我们查看源码图:
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
我们进行设计代码进行查看:
package com.rgf.map;
import java.util.Hashtable;
@SuppressWarnings({"all"})
public class HashTableExercise {
public static void main(String[] args) {
Hashtable table = new Hashtable();
table.put("john",100);
table.put(null,100);
table.put("john",null);
table.put("Lucy",100);
table.put("lic",100);
table.put("lic",88);
System.out.println(table);
}
}
我们运行之后如下所示:
我们发现当键和值为空的时候,抛出异常:NullPointerExcepion。
我们修改后代码如下所示:
package com.rgf.map;
import java.util.Hashtable;
@SuppressWarnings({"all"})
public class HashTableExercise {
public static void main(String[] args) {
Hashtable table = new Hashtable();
table.put("john",100);//OK
// table.put(null,100);//异常 NullPointerException
// table.put("john",null);//异常
table.put("Lucy",100);//OK
table.put("lic",100);//
table.put("lic",88);//替换
System.out.println(table);
}
}
运行如下所示:
我们下来进行debug,从这行代码开始打断点: table.put("john",100);的演示:
我们发现底层有数组,类型为Hashtable$Entry[ ],是Hashtable的内部类,初始化大小为11
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
为Hashtable里面的静态内部类。实现了Map.Entry接口。
threshold 8=11*0.75
我们进行查看如何进行扩容:
package com.rgf.map; import java.util.Hashtable; @SuppressWarnings({"all"}) public class HashTableExercise { public static void main(String[] args) { Hashtable table = new Hashtable(); table.put("john",100);//OK // table.put(null,100);//异常 NullPointerException // table.put("john",null);//异常 table.put("Lucy",100);//OK table.put("lic",100);// table.put("lic",88);//替换 table.put("hello1",88); table.put("hello2",88); table.put("hello3",88); table.put("hello4",88); table.put("hello5",88); table.put("hello6",88); System.out.println(table); } }
我们进行debug之后,如下所示:
我们发现此时扩容为23,临界值为17.
我们发现原先的大小为11,扩容为23,并不是以两倍进行扩容。而是按照自己的扩容机制进行扩容。
我们设置代码如下所示:
package com.rgf.map; import java.util.Hashtable; @SuppressWarnings({"all"}) public class HashTableExercise { public static void main(String[] args) { Hashtable table = new Hashtable(); table.put("john",100);//OK // table.put(null,100);//异常 NullPointerException // table.put("john",null);//异常 table.put("Lucy",100);//OK table.put("lic2",100);// table.put("lic",88);//替换 table.put("hello1",88); table.put("hello2",88); table.put("hello3",88); table.put("hello4",88); table.put("hello5",88); table.put("hello6",88); System.out.println(table); } }
我们在第九个进行打断点:
table.put("hello5",88);
我们进行debug如下所示:
我们先进行自由装箱:
退出之后,我们进入put方法:
执行如下方法:hashtable的底层是Entry数组。这条语句是执行添加。添加K-V,封装到entry里面。
addEntry(hash, key, value, index); return null; }
我们将断点设置到该方法,进入断点如下所示:
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 ;) { 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; } } }
我们发现其中扩容的关键代码如下:
int newCapacity = (oldCapacity << 1) + 1;
原先表左移一位,即乘以2后再加1.即原先的11*2+1=23,所以当 if (count >= threshold) {满足的时候,就进行扩容。
HashMap和Hashtable进行比对:
版本 | 线程安全(同步) | 效率 | 允许null键null值 | |
HashMap | 1.2 | 不安全 | 高 | 可以 |
HashTable | 1.0 | 安全 | 较低 | 不可以 |
Properties基本介绍:
1.Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据
2.他的使用特点和Hashtable类似
3.Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改。
4.说明:工作后 xxx.properties文件通常作为配置文件。
我们设计代码如下所示:
package com.rgf.map;
import java.util.Properties;
public class Properties_ {
public static void main(String[] args) {
//1.Propertise继承Hashtable
//2.可以通过k-v存放数据,当然key 和 value不能为空
Properties properties = new Properties();
properties.put(null,"abc"); //抛出空指针异常
properties.put("abc",null); //抛出空指针异常
}
}
运行之后如下所示:
我们发现出现空值会抛出异常。
我们的代码设计如下所示:
package com.rgf.map;
import java.util.Properties;
public class Properties_ {
public static void main(String[] args) {
//1.Propertise继承Hashtable
//2.可以通过k-v存放数据,当然key 和 value不能为空
Properties properties = new Properties();
properties.put("john",100); //k-v
properties.put("lucy",100);
properties.put("lic",100);
properties.put("lic",88); //如果有相同的key,value被替换
System.out.println("properties="+properties);
//通过k获取对应值
properties.get("lic");
System.out.println(properties.get("lic")); //88
//删除
properties.remove("lic");
System.out.println("properties="+properties);
//修改
properties.put("john","约翰");
System.out.println("properties="+properties);
}
}
我们运行如下所示:
总结如下:在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择。分析如下:
(1)先判断存储的类型(一组对象(单列)或一组键值对(双列))
(2)一组对象(单列):Collection接口
允许重复:List(有序可重复)
增删多:LinkedList(底层维护了一个双向链表)
改查多:ArrayList(底层维护了Object类型的可变数组)
不允许重复:Set(无序不可重复)
无序:HashSet(底层是HashMap,维护了一个哈希表,即(数组+链表+红黑树)
排序:TreeSet
插入和取出顺序一致:LinkedHashSet【底层是LinkedHashMap,LinkedHashMap的底层是HashMap】,维护数组+双向链表
(3)一组键值对(双列):Map
键无序:HashMap(底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树)
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap
读取文件:Prperties