哈希表结构
哈希表是一种通过关键码去寻找值的映射的数据结构
哈希表就需要通过哈希函数将特定的键映射到特定值的数据结构,维护了键和值之间的对应关系
键:称之为关键字,唯一的表示要存储的数据,也可以是数据本身或者是数据的一部分
哈希函数:将键映射到数据对应存放的位置的函数
哈希冲突:哈希函数将两个不同或者更多的不同的键映射到同一个索引位置
哈希表结构解决两个问题:合理的构建哈希函数,如何解决哈希冲突
哈希函数:
直接寻址法:H(key) = key或者H(key) = a.key+b(a,b为常量)
除留余数法:H(key) = key mod p(p<m,m表示表长)
哈希冲突解决:
链地址法:在冲突的地方通过链表来解决
h(x) = x mod 10
线性探测法:
线性探测:H(i) = i 或者H(i)=i*c(c是常量)
随机探测:H(i) =randon(i)
Map接口:
Map接口是顶层接口,特点是数据以键值对的形式存储
Map接口中提供的方法:
HashMap<String ,String> hashMap = new HashMap<>();
/**
* int size()
* 表示Map集合中元素的个数
* 返回int类型表示数据个数
*/
int size = hashMap.size();
System.out.println(size);
/**
* V put(K key, V value)
* 将指定的键key和value存储到集合中,如果当前插入的数据的key已经存在,最新的value会覆盖原有的value
* 返回值是键值对的值的类型
* map接口下集合特点:key不能重复
* 如果当前key不存在时,插入数据返回为null
* 如果当前key存在时,返回旧的value值
*/
String put = hashMap.put("k1","v1");
System.out.println(put);
String put1 = hashMap.put("k1","v2");
System.out.println(put1);
/**
* V get(Object key)
* 获取键所映射的值,如果不存在则返回为null
*/
String k1 = hashMap.get("k1");
System.out.println(k1);
String k2 = hashMap.get("k2");
System.out.println(k2);
/**
* boolean isEmpty()
* 判断集合是否为空
* 返回时boolean类型,true表示空,false表示不为空
*/
hashMap.isEmpty();
/**
* boolean containsKey(Object key)
* 判断map集合中是否存在指定的键
* 参数key:需要判断是否存在指定的键
* 返回的是Boolean类型
*/
boolean k11 = hashMap.containsKey("k3");
System.out.println(k11);
/**
* boolean containsValue(Object value)
* *判断map集合中是否存在指定的值
*参数:需要判断是否存在指定的值
* 返回的是Boolean类型
*/
boolean v11 = hashMap.containsValue("v1");
System.out.println(v11);
/**
* V remove(Object key)
* 通过key删除集合中该key对应的键值对
* 参数key:指定的键
* 返回值;是value值,如果key不存在返回null
*/
String k12 = hashMap.remove("k1");
/**
* void clear()
* 删除集合中所有的元素
*/
hashMap.clear();
/**
* Set<Map.Entry<K,V>> entrySet()
* 将map集合转化为set集合,多用于遍历键值对
*
*/
Set<Map.Entry<String,String>> entries = hashMap.entrySet();
//map接口按照键值对遍历
Iterator<Map.Entry<String,String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String,String> entry = iterator.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"<:>"+value);
}
/**
* Set<K> keySet()
* 获取map集合中所有的键的set集合,多用于遍历键
*/
hashMap.keySet();
//遍历map中所有的键
System.out.println("遍历键---");
Iterator<String> iterator1 = hashMap.keySet().iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
/**
* Collection<V> values()
* 获取map集合中所有值的collection的集合,多用于值的遍历
*/
hashMap.values();
//遍历map中所有的值
System.out.println("值的遍历------");
Iterator<String> iterator2 = hashMap.values().iterator();
while (iterator2.hasNext()){
System.out.println(iterator2.next());
}
Map集合中实现类遍历的三种方式:
//map接口按照键值对遍历
Iterator<Map.Entry<String,String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String,String> entry = iterator.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"<:>"+value);
}
//遍历map中所有的键
System.out.println("遍历键---");
Iterator<String> iterator1 = hashMap.keySet().iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
//遍历map中所有的值
System.out.println("值的遍历------");
Iterator<String> iterator2 = hashMap.values().iterator();
while (iterator2.hasNext()){
System.out.println(iterator2.next());
}
HashMap
1、HashMap中key不能重复、value值可以重复
2、HashMap中key和value都可以为null
3、不能保证插入的数据的顺序,随着时间的推移,同一个元素的位置可能会发生改变
4、底层采用的数据结构是哈希表结构
通过HashMap源码结构来研究HashMap的原理及特点:
1、继承机构
2、属性及默认值
3、构造函数
4、底层数据结构
5、扩容机制
6、常见方法解析
继承结构:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
HashMap实现了Map接口
实现了Cloneable接口,并且实现了Serializable接口,能够实现对象的序列化问题
HashMap同时继承自AbstractMap和Map接口,AbstractMap声明如下:
public abstract class AbstractMap<K,V> implements Map<K,V>
对于Map接口中通用的方法做了实现,方便子类字节复用,AbstractMap相当于辅助类,提供了一些操作的默认实现,具体的子类中如果没有特殊行为,可以直接使用AbstractMap提供的实现
属性及默认值:
//map中默认的容量初始值:16 ->2^4
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//map的最大限制值 2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的加载因子:和HashMap扩容相关的参数
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//空数组结构
static final Entry<?,?>[] EMPTY_TABLE = {};
//集合元素存放的位置table
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
//记录集合存储的数据个数,指的是entry实体的个数
transient int size;
//扩容阈值: 扩容阈值= capacity * load factor
int threshold;
//加载因子 加载因子范围 0< loadFactor < 1
final float loadFactor;
//版本控制号,和业务无关
transient int modCount;
底层的数据结构:
//集合元素存放的位置table:table本身是一个数组,数组中存储的数据类型Entry类型
transient Entry<K,V>[] table
static class Entry<K,V> implements Map.Entry<K,V> {
final K key; //关键字key
V value; //键值对的value
Entry<K,V> next; //下一个节点
int hash; //哈希值
//entry构造函数
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
}
底层存储数据是table属性,table是一个数组,数组存放的数据类型是entry实体,而entry节点本身带有next指针,可以构建单向链表,即底层数据结构是数组+链表,本质上是哈希表,哈希冲突采用的是链地址法
构造函数:
//通过初始容量 initialCapacity和加载因子loadFactor来初始化容量
public HashMap(int initialCapacity, float loadFactor) {
//参数合法性校验
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
//并未真正实例化HashMap实例,主要是进行参数设置,在插入元素(第一个元素)才进行真正实例化,延时实例化
}
//通过初始容量initialCapacity来实例化对象,加载因子是默认值:0.75
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//无参的构造函数 初始容量是16,加载因子是0.75
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//通过map的实例集合来实例化HashMap
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold);
putAllForCreate(m);
}
常见方法:
put方法:
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
//在插入第一个元素时集合为空,做集合初始化
inflateTable(threshold);
}
//当key为null时,将键值对特殊处理存放在索引位置为0的索引位置,直接返回
if (key == null)
return putForNullKey(value);
//key不为null,按照以下逻辑处理
//通过key进行哈希,找到当前key存放的位置i
int hash = hash(key);
int i = indexFor(hash, table.length);
//通过位置找到该位置的起始节点,进行遍历,需要先判断key是否存在,key存在则更新value值
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
//key已经存在
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//说明当前key不存在,需要新增一个节点来存放key-value键值对
modCount++;
addEntry(hash, key, value, i);
return null;
}
//新增节点
void addEntry(int hash, K key, V value, int bucketIndex) {
//扩容时机,当size大于扩容阈值时,要考虑扩容
//将容量扩容为原大小的2倍,并且将原哈希表上的所有节点重新哈希到新哈希表中
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
//待插入的节点是原节点的2倍关系,因此需要对映射的位置重新设定
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
//新构建一个entry节点,并将bucketIndex位置的头结点作为新节点的下一个节点,即新插入节点作为头结点
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//newCapacity容量为原大小的2倍,
Entry[] newTable = new Entry[newCapacity];
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
//节点重哈希,即遍历原哈希表中每一个节点,重新哈希到新的哈希表中
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
final int hash(Object k) {
int h = hashSeed;
//如果是字符串,通过sun.misc.Hashing.stringHash32转化为一个32位的哈希值
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
private void inflateTable(int toSize) {
//辅助方法,实例化哈希表的数组
// Find a power of 2 >= toSize
//容量满足2的倍数关系,是大于给定容量的最小的2倍数值
int capacity = roundUpToPowerOf2(toSize);
//扩容阈值 假如容量是16,加载因子是0.75 阈值:capacity * loadFactor=12
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//实例化table数组
table = new Entry[capacity];
//和求哈希函数相关的操作
initHashSeedAsNeeded(capacity);
}
//获取大于当前number的最小的2倍数关系
private static int roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative";
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
//key为null是处理逻辑,key为null时,将键值对存在在0号索引位置
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
哈希算法设计理念:
final int hash(Object k) {
int h = hashSeed;
//如果是字符串,通过sun.misc.Hashing.stringHash32转化为一个32位的哈希值
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
让length为2的指数倍,通过Hashcode(key)&(length-1)
通过位操作操作速度是很快的,结合异或操作能够使每个key都能冲突最小的情况下映射到[0,length)的索引位置上,设计理念就是将32位的哈希值的高位和低位做异或运算。能够将冲突降到最低。
8
00101100 00101010
&
00000000 00000111
------------------
00000000 00000010 =2
特点总结:
1、底层数据结构是哈希表
2、初始的默认容量是16
3、HashMap中容量必须满足是2的倍数关系(2、4、8、16…),如果给定一个初始容量值,集合容量需要满足大于初始容量的最小的2的倍数值作为集合容量
4、HashMap中key不能重复,而value可以重复
5、HashMap中key和value都可以为null
6、当集合中元素个数size大于扩容阈值(扩容时机)考虑扩容,将新哈希容量设置为原有容量的2倍(2*table.length),将原有集合中的节点全部重新哈希
put操作步骤:
1、先判断key是否为null,特殊处理,存放在哈希表的0号卡槽位置
2、key为null的数据是否存在,遍历0号卡槽位置的链表,如果存在(==)则更新value,返回
3、如果不存在,新建节点(addentry)
4、key不为null,通过key来计算哈希值,找到在哈希表的卡槽位置(hash,indexFor)
5、在对应卡槽获取链表,遍历找key是否存在,如果存在(hash&&equals)则更新value,返回
6、key在链表不存在,新建节点(addEntry)
7、考虑是否扩容(size>threshold),需要扩容,将新的大小为原来的2倍,然后将原哈希表中的数据都重新哈希到新的哈希表中, 并更新当前插入节点的卡槽位置
8、 采用头插入将新entry节点插入到给定的卡槽位置
get方法:
public V get(Object key) {
//需要判断如果key为null,需要在特定位置0号位置查找
if (key == null)
return getForNullKey();
//key不为null的处理逻辑
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
//key为null的value获取,通过遍历0号索引链表,查找key==null是否存在,存在则返回value
private V getForNullKey() {
if (size == 0) {
return null;
}
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
//key不为null的处理逻辑
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
//通过key的哈希操作找到key所对应的索引位置的头结点开始进行遍历
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) {
Object k;
if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
remove删除:
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
//通过key来删除对应键值对实体
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
//通过key的哈希操作找到对应key的索引位置
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];//后一个索引位置
Entry<K,V> e = prev; //前一个索引位置
//单向链表的删除逻辑
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
//要删除的是头结点,将next节点作为i索引位置的头结点
table[i] = next;
else
//将e节点从链表中剔除
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
HashMap应用场景:
做数据统计,存在一些数据,需要统计每一个数字出现的次数,key是每一个数字,value就表示数据的每个数据出现的次数
存在1~1000的数据共1万个,需要统计每个数据出现的次数?
需要初始化数据:数据范围1~1000,需要总量是1万个,借助于random产生随机数
考虑1万个数据存储,需要借助于集合(ArrayList或者LinkedList)
做数据统计,需要使用HashMap
打印各个数据出现的次数。 例如:数字1 出现99次
public class TestCount {
private static ArrayList<Integer> data = new ArrayList<Integer>(10000);
//数据初始化
public static void initData() {
Random random = new Random();
for(int i = 0;i <1000;i++){
int i1 = random.nextInt(1000)+1; //[0,999]
data.add(i1);
}
}
public static void count(){
//使用hashMap来计数,key:是数据,value:出现的次数
HashMap<Integer,Integer> hashMap = new HashMap<>();
Iterator iterator = data.iterator();
//通过遍历集合获取所有数据出现的次数
while(iterator.hasNext()){
Integer i = (Integer) iterator.next();
if(hashMap.containsKey(i)){
hashMap.put(i,hashMap.get(i)+1);
}else{
hashMap.put(i,1);
}
}
Iterator<Map.Entry<Integer,Integer>> iterator1 = hashMap.entrySet().iterator();
while (iterator1.hasNext()){
Map.Entry<Integer,Integer> entry = iterator1.next();
System.out.println("数据:"+entry.getKey()+"出现:"+entry.getValue());
}
}
public static void main(String[] args) {
initData();
count();
}
}
HashTable源码分析
继承结构:
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
//HashTable继承自Dictionary父类,该父类提供了很多方法的实现,可以直接复用,但该类在后续JDK中逐渐被AbstractMap替代
//实现了Map接口,实现了克隆,实现了序列化
属性及默认值:
//存储数据
private transient Entry<K,V>[] table;
//表示集合中元素的个数
private transient int count;
//扩容阈值
private int threshold;
//加载因子
private float loadFactor;
//版本号
private transient int modCount = 0;
底层数据结构:
//存放数据位置table 是一个entry类型的数组
private transient Entry<K,V>[] table;
private static class Entry<K,V> implements Map.Entry<K,V> {
int hash;
final K key;
V value;
Entry<K,V> next;
}
底层数据结构是数组加链表,即哈希表结构
构造函数:
//通过初始容量和加载因子构造
public Hashtable(int initialCapacity, float loadFactor) {
//参数的合法性校验
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
//初始化table数组
table = new Entry[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
initHashSeedAsNeeded(initialCapacity);
}
//通过初始容量来实例化HashTable,默认的加载因子是0.75
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
//通过无参的构造函数实例化HashTable 默认的初始容量是11
public Hashtable() {
this(11, 0.75f);
}
//通过一个map集合实例化HashTable实例
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
常用方法:
插入元素:put方法
public synchronized V put(K key, V value) {
//HashTable中key和value不能为null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
//通过key进行哈希找到key存放的位置
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
//key在集合中已经存在,对value值进行更新
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
//扩容时机,当存储数据量count大于等于扩容阈值,考虑扩容
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
//table大小改变,需要对新节点位置进行重新哈希查找
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
//将新节点采用头插法插入到链表中
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
//扩容
protected void rehash() {
int oldCapacity = table.length;
Entry<K,V>[] oldMap = table;
//扩容大小:2*oldCapacity+1
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<K,V>[] newMap = new Entry[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
boolean rehash = initHashSeedAsNeeded(newCapacity);
table = newMap;
//将原集合中所有的节点重新哈希到新的哈希表
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
if (rehash) {
e.hash = hash(e.key);
}
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
remove方法:
public synchronized V remove(Object key) {
Entry tab[] = table;
//通过对key哈希找到对应位置
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
//在对应位置的链表上删除节点,本质上是单链表删除问题
for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
V oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
HashTable特点:
1、底层采用的数据结构是哈希表
2、默认的初始容量是11,默认的加载因子是0.75f
3、HashTable是线程安全的,在多线程下可以放心使用,不会出现并发异常问题(synchronized)
4、HashTable中key和value都不能为null
5、HashTable中key是不能重复的,value是可以重复的
6、HashTable的扩容是按照原大小的2倍+1
总结
HashTable和HashMap不同点:
1、继承结构不同,HashTable继承自Dictionary,HashMap继承自AbstractMap
2、初始容量默认值不同:HashTable的初始容量为11,HashMap的初始容量为16
3、能否存储null:HashTable的key和value都不能为null,HashMap的key和value都能为null
4、线程安全性:HashTable是线程安全的,HashMap是非线程安全的,能否使HashMap线程安全呢?可以,即 使用Collections.synchronizedMap(hashMap);
5、哈希算法不同
6、扩容大小不同:HashTable是按照2oldlength+1 进行扩容,HashMap是按照2oldlength即指数扩容
HashTable和HashMap相同点:
1、底层数据结构是哈希表(JDK 1.7)
2、key-value键值对中,key是不能重复的,value是可以重复的
3、HashMap和Hashtable都是插入无序的
4、默认加载因子都是0.75