码迷,mamicode.com
首页 > 其他好文 > 详细

JDK HashMap学习

时间:2015-09-07 19:27:33      阅读:143      评论:0      收藏:0      [点我收藏+]

标签:

一、 HashMap概述:

Map 集合类用于存储元素对(称作“键”和“值”),其中每个键映射到一个值。

二、 HashMap内部存储

使用实现Map接口的内部类存储元素,Map接口定义如下:

interface Entry<K,V> {

K getKey();

V getValue();

V setValue(V value);

boolean equals(Object o);

int hashCode();

}

内部类如下:

static class Entry<K,V> implements Map.Entry<K,V> {

final K key; // 本元素键引用

V value; // 本元素值引用

Entry<K,V> next; // 指向相同hash值的下一个元素

int hash; // 将通过key对象hashCode()方法获取的哈希值传入HashMap类 // 中的final int hash(Object k)方法获取的值

……

}

一个简单的HashMap对象的内部存储图如下,对于Entry e元素,存放在index数组的第e.hash & (index.length-1)个位置的链表中。使用hash()函数重新计算得出的哈希值是为了减少碰撞冲突的发生。

技术分享

三、 HashMap重要API

initial capacity 初始容量,新建该HashMap对象使用的数组大小,

load factor 装载因子,用来度量该HashMap对象使用的数组所能容纳的元素个数:当尝试添加一个元素之前,如果发现当前已存储元素个数为index.length*load factor,则先新建一个容量为index.length*2的数组,并把原来数组中的元素重新计算索引值,放到对应的数组链表项中。然后再添加该元素。

构造函数:

public HashMap(int initialCapacity, float loadFactor)

public HashMap(int initialCapacity)

public HashMap()

// 初始时新建存储原来Map接口对应的类对象的键和值的Entry元素添加到该

// HashMap对象

public HashMap(Map<? extends K, ? extends V> m)

// 获取该HashMap对象指定键对应的key,如果不存在则返回null。但是当返回null时候,不表示不存在对应的Entry元素,也可能该元素存储的时候value就为空。查找过程如下:

首先将通过key对象hashCode()方法获取的哈希值传入HashMap类中的final int hash(Object k)方法获取一个哈希值hash,在index数组的第hash & (index.length-1)个位置的链表中查找某个Entry e元素,并满足(k = e.key) == key || (key != null && key.equals(k)),即引用同一个元素,或者通过equals方法判断相等。

public V get(Object key)

// 判断该HashMap对象中是否包含key指定的项

public boolean containsKey(Object key) {

return getEntry(key) != null;

}

// 如果该HashMap对象中存在key对应的Entry e,则把e的值设为value;否则新建一个Entry并添加。同样,尝试添加一个元素之前,如果发现当前已存储元素个数为index.length*load factor,则先新建一个容量为index.length*2的数组,并把原来数组中的元素重新计算索引值,放到对应的数组链表项中。然后再添加该元素。

public V put(K key, V value)

// 删除该HashMap对象中key对应的Entry e并返回e;如果不存在对应的e则返回空。该操作不会改变内部存储的数组大小。

public V remove(Object key)

四、 tips

1. 键的不变性

l 你向HashMap中插入一个对象,它的键就是“1”。HashMap从键(即“1”)的散列码中生成哈希值。Map在新创建的记录中存储这个哈希值。

l 你改动键的内部值,将其变为“2”。键的哈希值发生了改变,但是HashMap并不知道这一点(因为存储的是旧的哈希值)。

l 你试着通过修改后的键获取相应的对象。Map会计算新的键(即“2”)的哈希值,从而找到Entry对象所在的链表(桶)。既然你已经修改了键,Map会试着在错误的桶中寻找Entry对象,很可能找不到。

2. 非线性安全:可通过将HashMap方法操作放入synchronized块中实现线性安全。

3. 性能问题

initial capacity 初始容量

load factor 装载因子

如果你需要存储大量数据,你应该在创建HashMap时指定一个初始的容量,这个容量应该接近你期望的大小。

如果你不这样做,Map会使用默认的大小,即16,factorLoad的值是0.75。前11次调用put()方法会非常快,但是第12次(16*0.75)调用时会创建一个新的长度为32的内部数组(以及对应的链表/树),第13次到第22次调用put()方法会很快,但是第23次(32*0.75)调用时会重新创建(再一次)一个新的内部数组,数组的长度翻倍。然后内部调整大小的操作会在第48次、96次、192次…..调用put()方法时触发。如果数据量不大,重建内部数组的操作会很快,但是数据量很大时,花费的时间可能会从秒级到分钟级。通过初始化时指定Map期望的大小,你可以避免调整大小操作带来的消耗。

但这里也有一个缺点:如果你将数组设置的非常大,例如2^28,但你只是用了数组中的2^26个桶,那么你将会浪费大量的内存(在这个示例中大约是2^30字节)。

key对象哈希函数hashCode()

在最好的情况下,get()和put()方法都只有O(1)的复杂度。但是,如果你不去关心键的哈希函数,那么你的put()和get()方法可能会执行非常慢。put()和get()方法的高效执行,取决于数据被分配到内部数组(桶)的不同的索引上。如果键的哈希函数设计不合理,你会得到一个非对称的分区(不管内部数据的是多大)。所有的put()和get()方法会使用最大的链表,这样就会执行很慢,因为它需要迭代链表中的全部记录。在最坏的情况下(如果大部分数据都在同一个桶上),那么你的时间复杂度就会变为O(n)。

下面是一个可视化的示例。

技术分享 技术分享

4. Java 8 中的变化

和Java 7相比,Node可以被扩展成TreeNode。TreeNode是一个红黑树的数据结构,它可以存储更多的信息,这样我们可以在O(log(n))的复杂度下添加、删除或者获取一个元素。

技术分享

参考文章:

Java HashMap工作原理. http://www.importnew.com/16599.html

Java Map 集合类简介.

http://www.oracle.com/technetwork/cn/articles/maps1-100947-zhs.html

JDK HashMap学习

标签:

原文地址:http://www.cnblogs.com/shouhui/p/4789674.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!