您的位置:首页 > 其它

集合(一):Map,HashMap,Hashtable简介

2014-10-17 01:56 218 查看
包括:

一. Map 介绍

二. HashMap 介绍

三. HashTable 介绍


一. Map

       Map 是把键映射到值,也就是以一个键值对的形式存储。一个映射不能包含重复的键,一个键只能有一个值。某些映射可以保证其顺序,如TreeMap,某些则不行,如HashMap。

二. HashMap
       它是基于哈希表的Map 接口的实现,允许使用null 值和null 键,并且不保证映射顺序。

       HashMap 有两个参数影响性能:初始容量和加载因子。加载因子表示哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目超过了容量和加载因子的乘积的时候,就会进行重哈希操作。

加载因子默认为0.75,容量默认为16。加载因子过高,容易产生哈希冲突,加载因子过小,容易浪费空间,0.75是一种折中。

       注意:HashMap 不是同步的。

       HashMap 的整体思路就是先创建一个 table 数组,然后算出哈希值,找到table 数组特定的位置,找到或者存放该值。另外,由于哈希冲突,该位置可能有一个或者多个值(使用链表法进行连接),还需要进一步判断。

主要方法:
put() 方法:

public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

在 put() 方法中, 首先会根据 key 算出哈希值,算出之后,再在固定位置进行存放,方法如下:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;

//如果该数组为空,那么就重新创建一个数组
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;

//根据哈希值找到该位置,如果该位置没有节点,那么就直接存放
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);

//如果此时已经有节点,那么实际上就是出现了哈希冲突,此时再使用链表法解决哈希冲突
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

get() 方法:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;

//首先会根据哈希值定位到数组中的元素位置
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {

//永远都是先检查第一个符不符合
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;

//如果不符合,其实这个位置已经有了哈希冲突,那么只能 e = e.next() 一个一个查找
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}

总结:

可以有空值,可以有空键,但是键不能重复,非同步。
HashMap由于使用了链表的方式,所以增删查改的效率比较高。

Ps:
为什么容量要2的n次方:http://it.deepinmind.com/%E6%80%A7%E8%83%BD/2014/04/24/hashmap-performance-in-java-8.html

三. Hashtable
       HashTable 和 HashMap 的实现差不多,可以理解为 HashMap 的一个线程安全版本。它同样有初始容量和加载因子,内部也是创建table数组,并且通过哈希算法定位,如果有哈希冲突,也像HashMap一样,采用拉链法进行解决。

成员变量:

private transient Entry<?,?>[] table;     //存放数据的数组
private transient int count;              //当前元素个数
private float loadFactor;                 //加载因子

构造函数:
public Hashtable() {
this(11, 0.75f);
}

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 = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}

public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
       默认容量大小为11个,加载因子为 0.75,当然也可以指定。

普通方法:
put() 方法:
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;

//取得key 的哈希值,也就是在 table 数组中的位置
int hash = key.hashCode();
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;
}

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++;
}

get() 方法:
@SuppressWarnings("unchecked")
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;

//定位到该位置
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

//如果该位置上有哈希冲突,也就是有多个值,那么就 for 循环取合适的那个
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}

Ps:
HashMap 和 Hashtable 的几个不同点:

HashMap 允许空值 和空键, 但是 HashTable 则不允许空值 和空键。
HashMap 不是线程安全, Hashtable 是线程安全的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Map HashMap Hashtable
相关文章推荐