您的位置:首页 > 编程语言 > Java开发

java容器之Map

2016-09-14 15:20 393 查看
Map的核心思想就是:将一个对象映射到另一个对象上。存储的是键值对。维护键-值(对)关联。
Map中的键,和Set中的元素一样,必须实现equals()方法(即保证键的唯一性);如果用于散列Map,还必须实现hashCode()方法;如果用于TreeMap,还必须实现实现Comparable接口。
Map的键必须是唯一的,值可以重复。
Map接口的实现类有6个:
HashMap  基于散列表的实现,追求速度的极致。插入和查询“键值对”的开销是固定的(即“先hash,再取(存)值“的开销)。可以通过构造器设置容量和负载因子,以调整容器性能。
TreeMap    基于红黑树的实现,查看“键”或“键值对”时,它们会被排序(次序由元素实现的Comparable或者TreeMap中的Comparator 决定)。TreeMap的特点在于,所得到的结果是经过排序的。TreeMap是唯一的带有Submap()方法的Map,可以返回一个子树。他也是SortedMap的唯一实现。因为是树形结构的底层实现,那么在插入新元素的时候就相对慢一些(这是基于树形结构的特点导致的,因为在插入时就确定好了顺序,那么这个过程就还包含着一个排序的过程),它的查找操作,一般应该是基于“二分法”的,所以速度不及hash方式查找的快。
LinkedHashMap 类似于HashMap,但是迭代遍历他时,取得的“键值对“的顺序是键值对的插入顺序,或者是 最近最少使用(LRU)的次序(具体使用哪个顺序,根据构造函数中传入的参数决定)。只比HashMap慢一点;而在迭代访问时反而更快,因为它使用的是链表维护内部顺序。特点就是遍历时的顺序。
WeakHashMap 弱键映射,允许释放映射所指向的对象。这是为解决某类特殊问题而设计的。如果映射之外没有引用指向某个键,那么这个键可以被垃圾收集器回收。
ConcurrentHashMap  一种线程安全的Map,不涉及同步加锁。在并发中有。
IdentifyHashMap 使用== 代替 equals() 对 键 进行比较的散列映射,转为特殊问题设计。
HashMap中的get( Object key ) 和 containKey(Object key)的工作原理都是:先拿到key的hashCode,再根据hashCode去table中寻找对应的Entry对象,再根据获取到的Entry中的key值,来比较 是否有这个key值对应的对象。
源码见下:

public boolean containsKey(Object key) {
return getEntry(key) != null;
}
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);

return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}

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

所以在HashMap中,作为键(key)的对象必须重写equals()方法和hashCode()方法,因为Object默认是使用对象的地址来计算散列码(即hashCode)的,同时Object中的equals()方法默认比较的也只是对象的地址。

散列与散列码:
     使用散列的目的在于:想要用一个对象来查找另一个对象(是通过key对象来查找Entry<Key,Value>对象)。而散列的价值在于速度。

     工作原理:(在整个的hash过程中,无论是hashCode和equals方法都是对Key进行操作的)
         鉴于数组存储对象的快速性,HashMap一般使用数组来存储Entry对象。
         通过获取key对象的hashCode,来确定数组的下标。因为数组的长度是固定的,而key的数量是不确定的,所以会产生多个key指向同一个数组位置的情况(被称为“冲突”),
          为了解决冲突,在数组中存放的不是key所对应的Entry<K,V>,而是链表,在链表中存放key所对应的Entry<K,V> 。这样多个key指向一个数组位置的冲突就解决了。
          在获取Entry<K,V>对象时,利用key的hashCode获取在数组的位置,再利用equals() 方法,把指定的值从数组对应位置的链表中取出。
          在存入Entry<K,V>对象时,利用key的hashCode确定在数组中的位置,然后在数组对应位置的链表中查找(通过equals()方法)是否已存在该Entry对象,若不存在,则将该对象添加到链表中。

另外重写hashCode()方法是有技巧的。

对于Map的优化主要是针对Map在hash过程中,对于冲突的避免,来提高查找和插入的性能。
1.容量:Map中的桶位数(即Map中用于存储键信息的数组大小);
2.初始容量:Map在创建时拥有的桶位数(可以认为是数组的初始化大小)。HashMap和HashSet中都具有允许指定初始容量的构造函数。
3.尺寸:Map中当前存储的项数。
4.负载因子:尺寸/容量。Map为空时的负载因子为0,半满的Map负载因子为0.5,以此类推。负载因子越小的Map产生冲突的可能性越小,这对于插入和查找都是最理想的(但是会减慢使用迭代器进行遍历的过程)。在构造函数中指定了负载因子后,表示当负载情况到达该负载因子的水平时,容器将自动增加容量(桶位数),实现方式时使容量大致加倍,并重新将现有的对象分不到新的桶位集中(这个过程被称为再散列)。
HashMap使用的默认负载因子是0.75。如果要知道在HashMap中存储多少项,那么创建一个具有恰当大小初始容量的Map将避免自动再散列的开销。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: