JDK HashMap学习

一、 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

Technorati 标签: JDK7,HashMap,源码学习,数据结构

时间: 2024-10-15 20:47:57

JDK HashMap学习的相关文章

JDK源代码学习系列04----ArrayList

                                                                         JDK源代码学习系列04----ArrayList 1.ArrayList简单介绍 ArrayList是基于Object[] 数组的,也就是我们常说的动态数组.它能非常方便的实现数组的添加删除等操作. public class ArrayList<E> extends AbstractList<E> implements List<

HashMap 学习心得

1.构造 HashMap 底层数据结构线性数组,HashMap有一个静态内部类Entry,Entry有四个属性,key,value,next,hash Entry就是HashMap键值对实现的一个基础bean,HashMap的数据全都存在了Entry[]里面, 所以说HashMap是一个线性数组 2.hash碰撞 hash值不会碰撞,因为Entry的next属性,作用是指向下一个Entry.打个比方,第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] =

JDK HashMap

传统 HashMap 的缺点 JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布. 当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势. 针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题. HashMap 在 JDK 1.8 中新增的数据

HashMap学习笔记

概述 HashMap是Map接口的一个哈希表的实现,内部是一个数组表示的.数组中的元素叫做一个Node,一个Node可以一个是一个简单的表示键值对的二元组,也可以是一个复杂的TreeNode.如果是一个一个简单的二元组,则可以通过Node的next域构成构成一个链表. transient Node<K,V>[] table; 当需要遍历Map的时候,建议使用entrySet域来进行遍历,而不是keySet.因为entrySet其实返回的是一个特殊的set,这个set并未保存任何元素,而是定义了

《Java JDK 7 学习笔记》课后练习题1---欢迎纠错

1.()组织负责监督审查Java相关技术规格的演进. A. JCP B. Apache C. EU D. W3C 2.Java技术规格必须以()正式文件提交审查. A. RFC B. JSR C. ISO D. IEEE 3.Java的原始码扩展名和编译完后扩展名正确的是(). A. *.txt.*.java B. *.c.*.class C. *.java.*.class D. *.cpp.*.java 4.对JVM来说,可执行文件的扩展名正确的是(). A. *.java B. *.clas

HashMap 学习

1. 部分源码解析 1.1 变量和常量 // 默认的初始化容量,十进制数为 16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 // 极限容量,也就是说 table 数组再大也不能超过这个容量 static final int MAXIMUM_CAPACITY = 1 << 30; // 默认的负载因子:0.75(关系到 HashMap 本身的性能) static final float DEFAULT_

HashMap 学习 (JDK8)

1.hashmap中hash函数的实现中,异或运算操作的结果是什么,为什么要做这样的异或运算 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } 假设h是 1000010001110001000001111000000,h>>>16的结果就是一个新的32位数,高16位为0,低16位为h的高16位,这个新数记为q

jdk src 学习 Threadlocal

示例: import java.io.Serializable; public class TestThreadLocal implements Serializable { /** * */ private static final long serialVersionUID = -1279921928557717157L; int age; public static void main(String[] argv) throws Exception { TestThreadLocal tt

20145105 《Java程序设计》第1周学习总结

20145105 <Java程序设计>第1周学习总结 教材学习内容总结 学习了教材的第一章后,我初步了解了Java的发展历程,以及什么是JCP,JSR,JVM.JCP是一个开放性国际组织,可以让Java的演进被公开监督:任何想加入Java的功能或特性,必须以JSR正式文件的方式提交:JVM是Java程序唯一认识的操作系统,可执行.class文件.也知道了JRE与JDK的区别,并按照书上步骤下载并安装了JDK. 学习第二章后,我按照教材的步骤编写了HelloWorld.并用命令提示符经过设置路径