当前位置: 首页 > 编程笔记 >

jdk7 中HashMap的知识点总结

巫马磊
2023-03-14
本文向大家介绍jdk7 中HashMap的知识点总结,包括了jdk7 中HashMap的知识点总结的使用技巧和注意事项,需要的朋友参考一下

HashMap中的几个重要变量

默认初始容量,必须是2的n次方

static final int DEFAULT_INITIAL_CAPACITY = 16;

最大容量,当通过构造方法传入的容量比它还大时,就用这个最大容量,必须是2的n次方

static final int MAXIMUM_CAPACITY = 1 << 30;

默认负载因子

static final float DEFAULT_LOAD_FACTOR = 0.75f;

用来存储键值对,可以看到键值对都是存储在Entry中的

transient Entry<K,V>[] table;

//capacity * load factor,超过这个数就会进行再哈希
int threshold;

HashMap中的元素是用名为table的Entry数组来保存的,默认大小是16

  • capacity:数组的容量
  • load_factor:负载因子
  • threshold:实际能承载的容量,等于上面两个相乘,当size大于threshold时,就会进行rehash

jdk7中在面对key为String的时候采用了区别对待,会有alternative hashing,但是这个在jdk8中已经被删除了

存储结构

Entry是一个链表结构,不仅包含key和value,还有可以指向下一个的next

static class Entry<K,V> implements Map.Entry<K,V> {
 final K key;
 V value;
 Entry<K,V> next;
 int hash;

 /**
  * Creates new entry.
  */
 Entry(int h, K k, V v, Entry<K,V> n) {
  value = v;
  next = n;
  key = k;
  hash = h;
 }
 ...

put方法

public V put(K key, V value) {
 if (key == null)
  return putForNullKey(value);
 int hash = hash(key);
 int i = indexFor(hash, table.length);
 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))) {
  V oldValue = e.value;
  e.value = value;
  e.recordAccess(this);
  return oldValue;
  }
 }

 modCount++;
 addEntry(hash, key, value, i);
 return null;
 }

首先通过hash方法对hashcode进行处理:

final int hash(Object k) {
 int h = 0;
 h ^= k.hashCode();

 h ^= (h >>> 20) ^ (h >>> 12);
 return h ^ (h >>> 7) ^ (h >>> 4);
 }

可以看到只是在key的hashcode值上做了一些处理,通过hash计算出来的值将会使用indexFor方法找到它应该所在的table下标:

static int indexFor(int h, int length) {
 return h & (length-1);
 }

这个方法其实相当于对table.length取模。

当需要插入的key为null时,调用putForNullKey方法处理:

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

putForNullKey方法只从table[0]这个位置开始遍历,因为key为null只放在table中的第一个位置,下标为0,在遍历中如果发现已经有key为null了,则替换新value,返回旧value,结束;如果还没有key为null,调用addEntry方法增加一个Entry:

void addEntry(int hash, K key, V value, int bucketIndex) {
 if ((size >= threshold) && (null != table[bucketIndex])) {
  resize(2 * table.length);
  hash = (null != key) ? hash(key) : 0;
  bucketIndex = indexFor(hash, table.length);
 }

 createEntry(hash, key, value, bucketIndex);
 }

可以看到jdk7中resize的条件已经发生改变了,只有当 size>=threshold并且 table中的那个槽中已经有Entry时,才会发生resize。即有可能虽然size>=threshold,但是必须等到每个槽都至少有一个Entry时,才会扩容。还有注意每次resize都会扩大一倍容量

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

最后看createEntry,它先保存这个桶中的第一个Entry,创建新的Entry放入第一个位置,将原来的Entry接在后面。这里采用的是头插法插入元素。

get方法

其实get方法和put方法如出一辙,怎么放的怎么拿

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

 return null == entry ? null : entry.getValue();
 }

key为null时,还是去table[0]去取:

private V getForNullKey() {
 for (Entry<K,V> e = table[0]; e != null; e = e.next) {
  if (e.key == null)
  return e.value;
 }
 return null;
 }

否则调用getEntry方法:

final Entry<K,V> getEntry(Object 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;
 }

这个方法也是通过key的hashcode计算出它应该所在的下标,再遍历这个下标的Entry链,如果key的内存地址相等(即同一个引用)或者equals相等,则说明找到了

hash的原则

A、等幂性。不管执行多少次获取Hash值的操作,只要对象不变,那么Hash值是固定的。如果第一次取跟第N次取不一样,那就用起来很麻烦.

B、对等性。若两个对象equal方法返回为true,则其hash值也应该是一样的。举例说明:若你将objA作为key存入HashMap中,然后new了一个objB。在你看来objB和objA是一个东西(因为他们equal),但是使用objB到hashMap中却取不出来东西。

C、互异性。若两个对象equal方法返回为false,hash值有可能相同,但最好是不同的,这个不是必须的,只是这样做会提高hash类操作的性能(碰撞几率低)。

解决hash碰撞的方法:

  • 开放地址法
  • 链地址法

hashmap采用的就是链地址法,这种方法好处是无堆积现象,但是next指针会占用额外空间

和jdk8中的HashMap区别

在jdk8中,仍然会根据key.hashCode()计算出hash值,再通过这个hash值去定位这个key,但是不同的是,当发生冲突时,会采用链表和红黑树两种方法去处理,当结点个数较少时用链表(用Node存储),个数较多时用红黑树(用TreeNode存储),同时结点也不叫Entry了,而是分成了Node和TreeNode。再最坏的情况下,链表查找的时间复杂度为O(n),而红黑树一直是O(logn),这样会提高HashMap的效率。jdk8中的HashMap中定义了一个变量TREEIFY_THRESHOLD,当节点个数>= TREEIFY_THRESHOLD - 1时,HashMap将采用红黑树存储

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

 类似资料:
  • 本文向大家介绍Python中的引用知识点总结,包括了Python中的引用知识点总结的使用技巧和注意事项,需要的朋友参考一下 本篇介绍Python中的引用。 首先想一想如图示例。 在python中,值是靠引用来传递来的。 用id()来判断两个变量是否为同一个值的引用。如图。 图解引用。如图。 可变类型与不可变类型。如图。

  • 本文向大家介绍Java中MyBatis Plus知识点总结,包括了Java中MyBatis Plus知识点总结的使用技巧和注意事项,需要的朋友参考一下 好程序员Java教程分享MyBatis Plus介绍: 1.MyBatis Plus 介绍 MyBatis Plus 是国内人员开发的 MyBatis 增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。  MyBat

  • 本文向大家介绍python中HTMLParser模块知识点总结,包括了python中HTMLParser模块知识点总结的使用技巧和注意事项,需要的朋友参考一下 本章内容,我们主要来讲一下Python内置的HTML解析库HTMLParser模块,基本上也是应用于页面抓取上,假设,我们需要去收集页面上已存在的静态链接,但是页面肯定代码量都非常大,并且页面也很多,这样看来,会比较麻烦,工作量也非常大,这

  • 本文向大家介绍python中re模块知识点总结,包括了python中re模块知识点总结的使用技巧和注意事项,需要的朋友参考一下 一、什么是正则表达式? 正则表达式,又称规则表达式,通常被用来检索、替换那些符合某个模式(规则)的文本。 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑

  • 本文向大家介绍Linux shell知识点汇总,包括了Linux shell知识点汇总的使用技巧和注意事项,需要的朋友参考一下 实际上Shell是一个命令解释器,它解释由用户输入的命令并且把它们送到内核。不仅如此,Shell有自己的编程语言用于对命令的编辑,它允许用户编写由shell命令组成的程序。Shell编程语言具有普通编程语言的很多特点,比如它也有循环结构和分支控制结构等,用这种编程语言编写

  • 本文向大家介绍MySQL数据库中null的知识点总结,包括了MySQL数据库中null的知识点总结的使用技巧和注意事项,需要的朋友参考一下 在mysql数据库中,null是一个经常出现的情况,关于mysql中的null,有哪些注意事项呢?下面简单总结归纳下,后续会不断补充。 1. is null 首先判断数据库中某一列的值是否为null,不能用等于来判断,必须使用 is。比如,select * f